From fef6d35d82c22d7451bdaccc346f993c6d922cd9 Mon Sep 17 00:00:00 2001 From: Xiang wangx Date: Thu, 16 Dec 2021 17:55:36 +0800 Subject: drm/tilcdc: add const to of_device_id struct of_device_id should normally be const. Signed-off-by: Xiang wangx Reviewed-by: Jyri Sarha Signed-off-by: Jyri Sarha Link: https://patchwork.freedesktop.org/patch/msgid/20211216095536.58577-1-wangxiang@cdjrlc.com --- drivers/gpu/drm/tilcdc/tilcdc_drv.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c index 3ddb7c710a3d..cc567c87057d 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_drv.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c @@ -60,8 +60,6 @@ void tilcdc_module_cleanup(struct tilcdc_module *mod) list_del(&mod->list); } -static struct of_device_id tilcdc_of_match[]; - static int tilcdc_atomic_check(struct drm_device *dev, struct drm_atomic_state *state) { @@ -587,7 +585,7 @@ static int tilcdc_pdev_remove(struct platform_device *pdev) return 0; } -static struct of_device_id tilcdc_of_match[] = { +static const struct of_device_id tilcdc_of_match[] = { { .compatible = "ti,am33xx-tilcdc", }, { .compatible = "ti,da850-tilcdc", }, { }, -- cgit From ef2f5d0aa1214940f123721794f244f7287633ba Mon Sep 17 00:00:00 2001 From: "H. Nikolaus Schaller" Date: Thu, 2 Dec 2021 19:39:46 +0100 Subject: drm/ingenic: prepare ingenic drm for later addition of JZ4780 This changes the way the regmap is allocated to prepare for the later addition of the JZ4780 which has more registers and bits than the others. Therefore we make the regmap as big as the reg property in the device tree tells. Suggested-by: Paul Cercueil Signed-off-by: H. Nikolaus Schaller Signed-off-by: Paul Cercueil Link: https://patchwork.freedesktop.org/patch/msgid/ee65e70e91268b3963f8a6581c8aa6c3b643c53e.1638470392.git.hns@goldelico.com --- drivers/gpu/drm/ingenic/ingenic-drm-drv.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c index b4943a56be09..9c60fc4605e4 100644 --- a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c +++ b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c @@ -173,7 +173,6 @@ static const struct regmap_config ingenic_drm_regmap_config = { .val_bits = 32, .reg_stride = 4, - .max_register = JZ_REG_LCD_SIZE1, .writeable_reg = ingenic_drm_writeable_reg, }; @@ -1011,6 +1010,8 @@ static int ingenic_drm_bind(struct device *dev, bool has_components) struct ingenic_drm_bridge *ib; struct drm_device *drm; void __iomem *base; + struct resource *res; + struct regmap_config regmap_config; long parent_rate; unsigned int i, clone_mask = 0; int ret, irq; @@ -1056,14 +1057,16 @@ static int ingenic_drm_bind(struct device *dev, bool has_components) drm->mode_config.funcs = &ingenic_drm_mode_config_funcs; drm->mode_config.helper_private = &ingenic_drm_mode_config_helpers; - base = devm_platform_ioremap_resource(pdev, 0); + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(base)) { dev_err(dev, "Failed to get memory resource\n"); return PTR_ERR(base); } + regmap_config = ingenic_drm_regmap_config; + regmap_config.max_register = res->end - res->start; priv->map = devm_regmap_init_mmio(dev, base, - &ingenic_drm_regmap_config); + ®map_config); if (IS_ERR(priv->map)) { dev_err(dev, "Failed to create regmap\n"); return PTR_ERR(priv->map); -- cgit From b807fd2c43fe008eb6f4083e196ea38dadad9680 Mon Sep 17 00:00:00 2001 From: Paul Boddie Date: Thu, 2 Dec 2021 19:39:47 +0100 Subject: drm/ingenic: Add support for JZ4780 and HDMI output Add support for the LCD controller present on JZ4780 SoCs. This SoC uses 8-byte descriptors which extend the current 4-byte descriptors used for other Ingenic SoCs. Tested on MIPS Creator CI20 board. Signed-off-by: Paul Boddie Signed-off-by: Ezequiel Garcia Signed-off-by: H. Nikolaus Schaller Signed-off-by: Paul Cercueil Link: https://patchwork.freedesktop.org/patch/msgid/caf9e56b64b0717563e03172b30fa03736e92e15.1638470392.git.hns@goldelico.com --- drivers/gpu/drm/ingenic/ingenic-drm-drv.c | 53 +++++++++++++++++++++++++++++++ drivers/gpu/drm/ingenic/ingenic-drm.h | 38 ++++++++++++++++++++++ 2 files changed, 91 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c index 9c60fc4605e4..542c4af70661 100644 --- a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c +++ b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c @@ -6,6 +6,7 @@ #include "ingenic-drm.h" +#include #include #include #include @@ -49,6 +50,11 @@ struct ingenic_dma_hwdesc { u32 addr; u32 id; u32 cmd; + /* extended hw descriptor for jz4780 */ + u32 offsize; + u32 pagewidth; + u32 cpos; + u32 dessize; } __aligned(16); struct ingenic_dma_hwdescs { @@ -60,6 +66,7 @@ struct jz_soc_info { bool needs_dev_clk; bool has_osd; bool map_noncoherent; + bool use_extended_hwdesc; unsigned int max_width, max_height; const u32 *formats_f0, *formats_f1; unsigned int num_formats_f0, num_formats_f1; @@ -446,6 +453,9 @@ static int ingenic_drm_plane_atomic_check(struct drm_plane *plane, if (!crtc) return 0; + if (plane == &priv->f0) + return -EINVAL; + crtc_state = drm_atomic_get_existing_crtc_state(state, crtc); if (WARN_ON(!crtc_state)) @@ -662,6 +672,33 @@ static void ingenic_drm_plane_atomic_update(struct drm_plane *plane, hwdesc->cmd = JZ_LCD_CMD_EOF_IRQ | (width * height * cpp / 4); hwdesc->next = dma_hwdesc_addr(priv, next_id); + if (priv->soc_info->use_extended_hwdesc) { + hwdesc->cmd |= JZ_LCD_CMD_FRM_ENABLE; + + /* Extended 8-byte descriptor */ + hwdesc->cpos = 0; + hwdesc->offsize = 0; + hwdesc->pagewidth = 0; + + switch (newstate->fb->format->format) { + case DRM_FORMAT_XRGB1555: + hwdesc->cpos |= JZ_LCD_CPOS_RGB555; + fallthrough; + case DRM_FORMAT_RGB565: + hwdesc->cpos |= JZ_LCD_CPOS_BPP_15_16; + break; + case DRM_FORMAT_XRGB8888: + hwdesc->cpos |= JZ_LCD_CPOS_BPP_18_24; + break; + } + hwdesc->cpos |= (JZ_LCD_CPOS_COEFFICIENT_1 << + JZ_LCD_CPOS_COEFFICIENT_OFFSET); + hwdesc->dessize = + (0xff << JZ_LCD_DESSIZE_ALPHA_OFFSET) | + FIELD_PREP(JZ_LCD_DESSIZE_HEIGHT_MASK, height - 1) | + FIELD_PREP(JZ_LCD_DESSIZE_WIDTH_MASK, width - 1); + } + if (drm_atomic_crtc_needs_modeset(crtc_state)) { fourcc = newstate->fb->format->format; @@ -693,6 +730,9 @@ static void ingenic_drm_encoder_atomic_mode_set(struct drm_encoder *encoder, | JZ_LCD_CFG_SPL_DISABLE | JZ_LCD_CFG_REV_DISABLE; } + if (priv->soc_info->use_extended_hwdesc) + cfg |= JZ_LCD_CFG_DESCRIPTOR_8; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) cfg |= JZ_LCD_CFG_HSYNC_ACTIVE_LOW; if (mode->flags & DRM_MODE_FLAG_NVSYNC) @@ -1468,10 +1508,23 @@ static const struct jz_soc_info jz4770_soc_info = { .num_formats_f0 = ARRAY_SIZE(jz4770_formats_f0), }; +static const struct jz_soc_info jz4780_soc_info = { + .needs_dev_clk = true, + .has_osd = true, + .use_extended_hwdesc = true, + .max_width = 4096, + .max_height = 2048, + .formats_f1 = jz4770_formats_f1, + .num_formats_f1 = ARRAY_SIZE(jz4770_formats_f1), + .formats_f0 = jz4770_formats_f0, + .num_formats_f0 = ARRAY_SIZE(jz4770_formats_f0), +}; + static const struct of_device_id ingenic_drm_of_match[] = { { .compatible = "ingenic,jz4740-lcd", .data = &jz4740_soc_info }, { .compatible = "ingenic,jz4725b-lcd", .data = &jz4725b_soc_info }, { .compatible = "ingenic,jz4770-lcd", .data = &jz4770_soc_info }, + { .compatible = "ingenic,jz4780-lcd", .data = &jz4780_soc_info }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, ingenic_drm_of_match); diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.h b/drivers/gpu/drm/ingenic/ingenic-drm.h index 22654ac1dde1..cb1d09b62588 100644 --- a/drivers/gpu/drm/ingenic/ingenic-drm.h +++ b/drivers/gpu/drm/ingenic/ingenic-drm.h @@ -44,8 +44,11 @@ #define JZ_REG_LCD_XYP1 0x124 #define JZ_REG_LCD_SIZE0 0x128 #define JZ_REG_LCD_SIZE1 0x12c +#define JZ_REG_LCD_PCFG 0x2c0 #define JZ_LCD_CFG_SLCD BIT(31) +#define JZ_LCD_CFG_DESCRIPTOR_8 BIT(28) +#define JZ_LCD_CFG_RECOVER_FIFO_UNDERRUN BIT(25) #define JZ_LCD_CFG_PS_DISABLE BIT(23) #define JZ_LCD_CFG_CLS_DISABLE BIT(22) #define JZ_LCD_CFG_SPL_DISABLE BIT(21) @@ -63,6 +66,7 @@ #define JZ_LCD_CFG_DE_ACTIVE_LOW BIT(9) #define JZ_LCD_CFG_VSYNC_ACTIVE_LOW BIT(8) #define JZ_LCD_CFG_18_BIT BIT(7) +#define JZ_LCD_CFG_24_BIT BIT(6) #define JZ_LCD_CFG_PDW (BIT(5) | BIT(4)) #define JZ_LCD_CFG_MODE_GENERIC_16BIT 0 @@ -132,6 +136,7 @@ #define JZ_LCD_CMD_SOF_IRQ BIT(31) #define JZ_LCD_CMD_EOF_IRQ BIT(30) #define JZ_LCD_CMD_ENABLE_PAL BIT(28) +#define JZ_LCD_CMD_FRM_ENABLE BIT(26) #define JZ_LCD_SYNC_MASK 0x3ff @@ -153,6 +158,7 @@ #define JZ_LCD_RGBC_EVEN_BGR (0x5 << 0) #define JZ_LCD_OSDC_OSDEN BIT(0) +#define JZ_LCD_OSDC_ALPHAEN BIT(2) #define JZ_LCD_OSDC_F0EN BIT(3) #define JZ_LCD_OSDC_F1EN BIT(4) @@ -176,6 +182,38 @@ #define JZ_LCD_SIZE01_WIDTH_LSB 0 #define JZ_LCD_SIZE01_HEIGHT_LSB 16 +#define JZ_LCD_DESSIZE_ALPHA_OFFSET 24 +#define JZ_LCD_DESSIZE_HEIGHT_MASK GENMASK(23, 12) +#define JZ_LCD_DESSIZE_WIDTH_MASK GENMASK(11, 0) + +#define JZ_LCD_CPOS_BPP_15_16 (4 << 27) +#define JZ_LCD_CPOS_BPP_18_24 (5 << 27) +#define JZ_LCD_CPOS_BPP_30 (7 << 27) +#define JZ_LCD_CPOS_RGB555 BIT(30) +#define JZ_LCD_CPOS_PREMULTIPLY_LCD BIT(26) +#define JZ_LCD_CPOS_COEFFICIENT_OFFSET 24 +#define JZ_LCD_CPOS_COEFFICIENT_0 0 +#define JZ_LCD_CPOS_COEFFICIENT_1 1 +#define JZ_LCD_CPOS_COEFFICIENT_ALPHA1 2 +#define JZ_LCD_CPOS_COEFFICIENT_1_ALPHA1 3 + +#define JZ_LCD_RGBC_RGB_PADDING BIT(15) +#define JZ_LCD_RGBC_RGB_PADDING_FIRST BIT(14) +#define JZ_LCD_RGBC_422 BIT(8) +#define JZ_LCD_RGBC_RGB_FORMAT_ENABLE BIT(7) + +#define JZ_LCD_PCFG_PRI_MODE BIT(31) +#define JZ_LCD_PCFG_HP_BST_4 (0 << 28) +#define JZ_LCD_PCFG_HP_BST_8 (1 << 28) +#define JZ_LCD_PCFG_HP_BST_16 (2 << 28) +#define JZ_LCD_PCFG_HP_BST_32 (3 << 28) +#define JZ_LCD_PCFG_HP_BST_64 (4 << 28) +#define JZ_LCD_PCFG_HP_BST_16_CONT (5 << 28) +#define JZ_LCD_PCFG_HP_BST_DISABLE (7 << 28) +#define JZ_LCD_PCFG_THRESHOLD2_OFFSET 18 +#define JZ_LCD_PCFG_THRESHOLD1_OFFSET 9 +#define JZ_LCD_PCFG_THRESHOLD0_OFFSET 0 + struct device; struct drm_plane; struct drm_plane_state; -- cgit From 72345114c96b8211ec693b1ce59f95a83a613f1d Mon Sep 17 00:00:00 2001 From: Zack Rusin Date: Wed, 15 Dec 2021 13:41:45 -0500 Subject: drm/vmwgfx: Fix a size_t/long int format specifier mismatch On i386 size_t is of course 32bits and using long int throws warnings, trivially fix it by using the dedicated size_t format. This is enough to fix the following warning found by the kernel test robot: drivers/gpu/drm/vmwgfx/vmwgfx_gem.c: In function 'vmw_bo_print_info': >> drivers/gpu/drm/vmwgfx/vmwgfx_gem.c:230:33: warning: format '%ld' expects argument of type 'long int', but argument 4 has type 'size_t' {aka 'unsigned int'} [-Wformat=] 230 | seq_printf(m, "\t\t0x%08x: %12ld bytes %s, type = %s", | ~~~~^ | | | long int | %12d 231 | id, bo->base.base.size, placement, type); | ~~~~~~~~~~~~~~~~~~ | | Reported-by: kernel test robot Signed-off-by: Zack Rusin Fixes: 8afa13a0583f ("drm/vmwgfx: Implement DRIVER_GEM") Reviewed-by: Martin Krastev Reviewed-by: Maaz Mombasawala Link: https://patchwork.freedesktop.org/patch/msgid/20211215184147.3688785-1-zack@kde.org --- drivers/gpu/drm/vmwgfx/vmwgfx_gem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_gem.c b/drivers/gpu/drm/vmwgfx/vmwgfx_gem.c index c016c434b6cb..ce609e7d758f 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_gem.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_gem.c @@ -227,7 +227,7 @@ static void vmw_bo_print_info(int id, struct vmw_buffer_object *bo, struct seq_f break; } - seq_printf(m, "\t\t0x%08x: %12ld bytes %s, type = %s", + seq_printf(m, "\t\t0x%08x: %12zu bytes %s, type = %s", id, bo->base.base.size, placement, type); seq_printf(m, ", priority = %u, pin_count = %u, GEM refs = %d, TTM refs = %d", bo->base.priority, -- cgit From 49d535d64d52945e2c874f380705675e20a02b6a Mon Sep 17 00:00:00 2001 From: Zack Rusin Date: Wed, 15 Dec 2021 13:41:46 -0500 Subject: drm/vmwgfx: Remove explicit transparent hugepages support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Old versions of the svga device used to export virtual vram, handling of which was optimized on top of transparent hugepages support. Only very old devices (OpenGL 2.1 support and earlier) used this code and at this point performance differences are negligible. Because the code requires very old hardware versions to run it has been largely untested and unused for a long time. Furthermore removal of the ttm hugepages support in: commit 0d979509539e ("drm/ttm: remove ttm_bo_vm_insert_huge()") broke the coherency mode in vmwgfx when running with hugepages. Fixes: 0d979509539e ("drm/ttm: remove ttm_bo_vm_insert_huge()") Signed-off-by: Zack Rusin Cc: Jason Gunthorpe Cc: Thomas Hellström Cc: Christian König Cc: Daniel Vetter Reviewed-by: Martin Krastev Reviewed-by: Maaz Mombasawala Link: https://patchwork.freedesktop.org/patch/msgid/20211215184147.3688785-2-zack@kde.org --- drivers/gpu/drm/vmwgfx/Makefile | 1 - drivers/gpu/drm/vmwgfx/vmwgfx_drv.c | 8 -- drivers/gpu/drm/vmwgfx/vmwgfx_drv.h | 5 - drivers/gpu/drm/vmwgfx/vmwgfx_thp.c | 184 ------------------------------------ 4 files changed, 198 deletions(-) delete mode 100644 drivers/gpu/drm/vmwgfx/vmwgfx_thp.c (limited to 'drivers') diff --git a/drivers/gpu/drm/vmwgfx/Makefile b/drivers/gpu/drm/vmwgfx/Makefile index e9d13f155d8c..eee73b9aa404 100644 --- a/drivers/gpu/drm/vmwgfx/Makefile +++ b/drivers/gpu/drm/vmwgfx/Makefile @@ -13,6 +13,5 @@ vmwgfx-y := vmwgfx_execbuf.o vmwgfx_gmr.o vmwgfx_hashtab.o vmwgfx_kms.o vmwgfx_d vmwgfx_gem.o vmwgfx-$(CONFIG_DRM_FBDEV_EMULATION) += vmwgfx_fb.o -vmwgfx-$(CONFIG_TRANSPARENT_HUGEPAGE) += vmwgfx_thp.o obj-$(CONFIG_DRM_VMWGFX) := vmwgfx.o diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c index 2d59bdad0373..fe36efdb7ff5 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c @@ -701,23 +701,15 @@ static int vmw_dma_masks(struct vmw_private *dev_priv) static int vmw_vram_manager_init(struct vmw_private *dev_priv) { int ret; -#ifdef CONFIG_TRANSPARENT_HUGEPAGE - ret = vmw_thp_init(dev_priv); -#else ret = ttm_range_man_init(&dev_priv->bdev, TTM_PL_VRAM, false, dev_priv->vram_size >> PAGE_SHIFT); -#endif ttm_resource_manager_set_used(ttm_manager_type(&dev_priv->bdev, TTM_PL_VRAM), false); return ret; } static void vmw_vram_manager_fini(struct vmw_private *dev_priv) { -#ifdef CONFIG_TRANSPARENT_HUGEPAGE - vmw_thp_fini(dev_priv); -#else ttm_range_man_fini(&dev_priv->bdev, TTM_PL_VRAM); -#endif } static int vmw_setup_pci_resources(struct vmw_private *dev, diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h index 4ec2b99351cf..e2baca9a55f8 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h @@ -1564,11 +1564,6 @@ void vmw_bo_dirty_unmap(struct vmw_buffer_object *vbo, vm_fault_t vmw_bo_vm_fault(struct vm_fault *vmf); vm_fault_t vmw_bo_vm_mkwrite(struct vm_fault *vmf); -/* Transparent hugepage support - vmwgfx_thp.c */ -#ifdef CONFIG_TRANSPARENT_HUGEPAGE -extern int vmw_thp_init(struct vmw_private *dev_priv); -void vmw_thp_fini(struct vmw_private *dev_priv); -#endif /** * VMW_DEBUG_KMS - Debug output for kernel mode-setting diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_thp.c b/drivers/gpu/drm/vmwgfx/vmwgfx_thp.c deleted file mode 100644 index 2a3d3468e4e0..000000000000 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_thp.c +++ /dev/null @@ -1,184 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 OR MIT -/* - * Huge page-table-entry support for IO memory. - * - * Copyright (C) 2007-2019 Vmware, Inc. All rights reservedd. - */ -#include "vmwgfx_drv.h" -#include -#include -#include - -/** - * struct vmw_thp_manager - Range manager implementing huge page alignment - * - * @manager: TTM resource manager. - * @mm: The underlying range manager. Protected by @lock. - * @lock: Manager lock. - */ -struct vmw_thp_manager { - struct ttm_resource_manager manager; - struct drm_mm mm; - spinlock_t lock; -}; - -static struct vmw_thp_manager *to_thp_manager(struct ttm_resource_manager *man) -{ - return container_of(man, struct vmw_thp_manager, manager); -} - -static const struct ttm_resource_manager_func vmw_thp_func; - -static int vmw_thp_insert_aligned(struct ttm_buffer_object *bo, - struct drm_mm *mm, struct drm_mm_node *node, - unsigned long align_pages, - const struct ttm_place *place, - struct ttm_resource *mem, - unsigned long lpfn, - enum drm_mm_insert_mode mode) -{ - if (align_pages >= bo->page_alignment && - (!bo->page_alignment || align_pages % bo->page_alignment == 0)) { - return drm_mm_insert_node_in_range(mm, node, - mem->num_pages, - align_pages, 0, - place->fpfn, lpfn, mode); - } - - return -ENOSPC; -} - -static int vmw_thp_get_node(struct ttm_resource_manager *man, - struct ttm_buffer_object *bo, - const struct ttm_place *place, - struct ttm_resource **res) -{ - struct vmw_thp_manager *rman = to_thp_manager(man); - struct drm_mm *mm = &rman->mm; - struct ttm_range_mgr_node *node; - unsigned long align_pages; - unsigned long lpfn; - enum drm_mm_insert_mode mode = DRM_MM_INSERT_BEST; - int ret; - - node = kzalloc(struct_size(node, mm_nodes, 1), GFP_KERNEL); - if (!node) - return -ENOMEM; - - ttm_resource_init(bo, place, &node->base); - - lpfn = place->lpfn; - if (!lpfn) - lpfn = man->size; - - mode = DRM_MM_INSERT_BEST; - if (place->flags & TTM_PL_FLAG_TOPDOWN) - mode = DRM_MM_INSERT_HIGH; - - spin_lock(&rman->lock); - if (IS_ENABLED(CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD)) { - align_pages = (HPAGE_PUD_SIZE >> PAGE_SHIFT); - if (node->base.num_pages >= align_pages) { - ret = vmw_thp_insert_aligned(bo, mm, &node->mm_nodes[0], - align_pages, place, - &node->base, lpfn, mode); - if (!ret) - goto found_unlock; - } - } - - align_pages = (HPAGE_PMD_SIZE >> PAGE_SHIFT); - if (node->base.num_pages >= align_pages) { - ret = vmw_thp_insert_aligned(bo, mm, &node->mm_nodes[0], - align_pages, place, &node->base, - lpfn, mode); - if (!ret) - goto found_unlock; - } - - ret = drm_mm_insert_node_in_range(mm, &node->mm_nodes[0], - node->base.num_pages, - bo->page_alignment, 0, - place->fpfn, lpfn, mode); -found_unlock: - spin_unlock(&rman->lock); - - if (unlikely(ret)) { - kfree(node); - } else { - node->base.start = node->mm_nodes[0].start; - *res = &node->base; - } - - return ret; -} - -static void vmw_thp_put_node(struct ttm_resource_manager *man, - struct ttm_resource *res) -{ - struct ttm_range_mgr_node *node = to_ttm_range_mgr_node(res); - struct vmw_thp_manager *rman = to_thp_manager(man); - - spin_lock(&rman->lock); - drm_mm_remove_node(&node->mm_nodes[0]); - spin_unlock(&rman->lock); - - kfree(node); -} - -int vmw_thp_init(struct vmw_private *dev_priv) -{ - struct vmw_thp_manager *rman; - - rman = kzalloc(sizeof(*rman), GFP_KERNEL); - if (!rman) - return -ENOMEM; - - ttm_resource_manager_init(&rman->manager, - dev_priv->vram_size >> PAGE_SHIFT); - - rman->manager.func = &vmw_thp_func; - drm_mm_init(&rman->mm, 0, rman->manager.size); - spin_lock_init(&rman->lock); - - ttm_set_driver_manager(&dev_priv->bdev, TTM_PL_VRAM, &rman->manager); - ttm_resource_manager_set_used(&rman->manager, true); - return 0; -} - -void vmw_thp_fini(struct vmw_private *dev_priv) -{ - struct ttm_resource_manager *man = ttm_manager_type(&dev_priv->bdev, TTM_PL_VRAM); - struct vmw_thp_manager *rman = to_thp_manager(man); - struct drm_mm *mm = &rman->mm; - int ret; - - ttm_resource_manager_set_used(man, false); - - ret = ttm_resource_manager_evict_all(&dev_priv->bdev, man); - if (ret) - return; - spin_lock(&rman->lock); - drm_mm_clean(mm); - drm_mm_takedown(mm); - spin_unlock(&rman->lock); - ttm_resource_manager_cleanup(man); - ttm_set_driver_manager(&dev_priv->bdev, TTM_PL_VRAM, NULL); - kfree(rman); -} - -static void vmw_thp_debug(struct ttm_resource_manager *man, - struct drm_printer *printer) -{ - struct vmw_thp_manager *rman = to_thp_manager(man); - - spin_lock(&rman->lock); - drm_mm_print(&rman->mm, printer); - spin_unlock(&rman->lock); -} - -static const struct ttm_resource_manager_func vmw_thp_func = { - .alloc = vmw_thp_get_node, - .free = vmw_thp_put_node, - .debug = vmw_thp_debug -}; -- cgit From 11343099d5ae6c7411da1425b6b162c89fb5bf10 Mon Sep 17 00:00:00 2001 From: Zack Rusin Date: Wed, 15 Dec 2021 13:41:47 -0500 Subject: drm/vmwgfx: Remove unused compile options Before the driver had screen targets support we had to disable explicit bringup of its infrastructure because it was breaking screen objects support. Since the implementation of screen targets landed there hasn't been a reason to explicitly disable it and the options were never used. Remove of all that unused code. Signed-off-by: Zack Rusin Fixes: d80efd5cb3de ("drm/vmwgfx: Initial DX support") Reviewed-by: Martin Krastev Link: https://patchwork.freedesktop.org/patch/msgid/20211215184147.3688785-3-zack@kde.org --- drivers/gpu/drm/vmwgfx/vmwgfx_drv.h | 3 --- drivers/gpu/drm/vmwgfx/vmwgfx_mob.c | 12 +++--------- drivers/gpu/drm/vmwgfx/vmwgfx_stdu.c | 4 ++-- 3 files changed, 5 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h index e2baca9a55f8..d6b66636a19b 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h @@ -59,11 +59,8 @@ #define VMWGFX_DRIVER_MINOR 20 #define VMWGFX_DRIVER_PATCHLEVEL 0 #define VMWGFX_FIFO_STATIC_SIZE (1024*1024) -#define VMWGFX_MAX_RELOCATIONS 2048 -#define VMWGFX_MAX_VALIDATIONS 2048 #define VMWGFX_MAX_DISPLAYS 16 #define VMWGFX_CMD_BOUNCE_INIT_SIZE 32768 -#define VMWGFX_ENABLE_SCREEN_TARGET_OTABLE 1 #define VMWGFX_PCI_ID_SVGA2 0x0405 #define VMWGFX_PCI_ID_SVGA3 0x0406 diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_mob.c b/drivers/gpu/drm/vmwgfx/vmwgfx_mob.c index 2d91a44a3b22..0a8cc28d6606 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_mob.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_mob.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 OR MIT /************************************************************************** * - * Copyright 2012-2015 VMware, Inc., Palo Alto, CA., USA + * Copyright 2012-2021 VMware, Inc., Palo Alto, CA., USA * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the @@ -29,12 +29,6 @@ #include "vmwgfx_drv.h" -/* - * If we set up the screen target otable, screen objects stop working. - */ - -#define VMW_OTABLE_SETUP_SUB ((VMWGFX_ENABLE_SCREEN_TARGET_OTABLE ? 0 : 1)) - #ifdef CONFIG_64BIT #define VMW_PPN_SIZE 8 #define VMW_MOBFMT_PTDEPTH_0 SVGA3D_MOBFMT_PT64_0 @@ -75,7 +69,7 @@ static const struct vmw_otable pre_dx_tables[] = { {VMWGFX_NUM_GB_CONTEXT * sizeof(SVGAOTableContextEntry), NULL, true}, {VMWGFX_NUM_GB_SHADER * sizeof(SVGAOTableShaderEntry), NULL, true}, {VMWGFX_NUM_GB_SCREEN_TARGET * sizeof(SVGAOTableScreenTargetEntry), - NULL, VMWGFX_ENABLE_SCREEN_TARGET_OTABLE} + NULL, true} }; static const struct vmw_otable dx_tables[] = { @@ -84,7 +78,7 @@ static const struct vmw_otable dx_tables[] = { {VMWGFX_NUM_GB_CONTEXT * sizeof(SVGAOTableContextEntry), NULL, true}, {VMWGFX_NUM_GB_SHADER * sizeof(SVGAOTableShaderEntry), NULL, true}, {VMWGFX_NUM_GB_SCREEN_TARGET * sizeof(SVGAOTableScreenTargetEntry), - NULL, VMWGFX_ENABLE_SCREEN_TARGET_OTABLE}, + NULL, true}, {VMWGFX_NUM_DXCONTEXT * sizeof(SVGAOTableDXContextEntry), NULL, true}, }; diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_stdu.c b/drivers/gpu/drm/vmwgfx/vmwgfx_stdu.c index 8f0d651d0144..ae9a6044d448 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_stdu.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_stdu.c @@ -1872,8 +1872,8 @@ int vmw_kms_stdu_init_display(struct vmw_private *dev_priv) int i, ret; - /* Do nothing if Screen Target support is turned off */ - if (!VMWGFX_ENABLE_SCREEN_TARGET_OTABLE || !dev_priv->has_mob) + /* Do nothing if there's no support for MOBs */ + if (!dev_priv->has_mob) return -ENOSYS; if (!(dev_priv->capabilities & SVGA_CAP_GBOBJECTS)) -- cgit From 60c9ecd705be3a28f79d70ea21c3939db668bf46 Mon Sep 17 00:00:00 2001 From: Zack Rusin Date: Wed, 15 Dec 2021 15:02:24 -0500 Subject: drm/vmwgfx: Fix possible usage of an uninitialized variable vmw_user_bo_lookup can fail to lookup user buffers, especially because the buffer handles come from the userspace. The return value has to be checked before the buffers are put back. This was spotted by Dan's Smatch statick checker: drivers/gpu/drm/vmwgfx/vmwgfx_bo.c:574 vmw_user_bo_synccpu_release() error: uninitialized symbol 'vmw_bo'. Signed-off-by: Zack Rusin Reported-by: Dan Carpenter Fixes: 8afa13a0583f ("drm/vmwgfx: Implement DRIVER_GEM") Reviewed-by: Martin Krastev Link: https://patchwork.freedesktop.org/patch/msgid/20211215200224.3693345-1-zack@kde.org --- drivers/gpu/drm/vmwgfx/vmwgfx_bo.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_bo.c b/drivers/gpu/drm/vmwgfx/vmwgfx_bo.c index 15fead85450c..31aecc46624b 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_bo.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_bo.c @@ -568,10 +568,12 @@ static int vmw_user_bo_synccpu_release(struct drm_file *filp, struct vmw_buffer_object *vmw_bo; int ret = vmw_user_bo_lookup(filp, handle, &vmw_bo); - if (!(flags & drm_vmw_synccpu_allow_cs)) { - atomic_dec(&vmw_bo->cpu_writers); + if (!ret) { + if (!(flags & drm_vmw_synccpu_allow_cs)) { + atomic_dec(&vmw_bo->cpu_writers); + } + ttm_bo_put(&vmw_bo->base); } - ttm_bo_put(&vmw_bo->base); return ret; } -- cgit From 007354597d5c12a8d44a34c66a2e1a089bc7d3d8 Mon Sep 17 00:00:00 2001 From: Ben Skeggs Date: Tue, 23 Nov 2021 18:17:43 +1000 Subject: drm/nouveau/disp: remove some remnant of a rework Signed-off-by: Ben Skeggs Reviewed-by: Lyude Paul Signed-off-by: Karol Herbst Link: https://gitlab.freedesktop.org/drm/nouveau/-/merge_requests/17 --- drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h index e484d0c3b0d4..7653d80b4076 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h @@ -9,10 +9,7 @@ #include struct nvkm_dp { - union { - struct nvkm_outp base; - struct nvkm_outp outp; - }; + struct nvkm_outp outp; struct nvbios_dpout info; u8 version; -- cgit From be5b6985fbbe6ba9580351b3c9168c84e51bee5c Mon Sep 17 00:00:00 2001 From: Ben Skeggs Date: Tue, 23 Nov 2021 18:17:49 +1000 Subject: drm/nouveau/disp/dp: explicitly control scrambling when setting pattern TPS1/2/3 require scrambling to be disabled. The IED scripts on earlier boards used to handle this, but appear not to anymore. TPS4 support will also require scrambling to remain enabled. Signed-off-by: Ben Skeggs Reviewed-by: Lyude Paul Signed-off-by: Karol Herbst Link: https://gitlab.freedesktop.org/drm/nouveau/-/merge_requests/17 --- drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c | 5 +++++ drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h | 1 + drivers/gpu/drm/nouveau/nvkm/engine/disp/sorg94.c | 13 ++++++++++++- drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgf119.c | 14 +++++++++++++- drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm107.c | 17 ++++++++++++++--- 5 files changed, 45 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c index 9669472a2749..3c0554a60ba9 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c @@ -154,6 +154,11 @@ nvkm_dp_train_pattern(struct lt_state *lt, u8 pattern) nvkm_rdaux(dp->aux, DPCD_LC02, &sink_tp, 1); sink_tp &= ~DPCD_LC02_TRAINING_PATTERN_SET; sink_tp |= pattern; + + if (pattern != 0) + sink_tp |= DPCD_LC02_SCRAMBLING_DISABLE; + else + sink_tp &= ~DPCD_LC02_SCRAMBLING_DISABLE; nvkm_wraux(dp->aux, DPCD_LC02, &sink_tp, 1); } diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h index 7653d80b4076..9ce91f79ca35 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h @@ -49,6 +49,7 @@ void nvkm_dp_disable(struct nvkm_outp *, struct nvkm_ior *); #define DPCD_LC01_LANE_COUNT_SET 0x1f #define DPCD_LC02 0x00102 #define DPCD_LC02_TRAINING_PATTERN_SET 0x03 +#define DPCD_LC02_SCRAMBLING_DISABLE 0x20 #define DPCD_LC03(l) ((l) + 0x00103) #define DPCD_LC03_MAX_PRE_EMPHASIS_REACHED 0x20 #define DPCD_LC03_PRE_EMPHASIS_SET 0x18 diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorg94.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorg94.c index 4d59d02525d9..56b8f4411988 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorg94.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorg94.c @@ -77,7 +77,18 @@ g94_sor_dp_pattern(struct nvkm_ior *sor, int pattern) { struct nvkm_device *device = sor->disp->engine.subdev.device; const u32 loff = nv50_sor_link(sor); - nvkm_mask(device, 0x61c10c + loff, 0x0f000000, pattern << 24); + u32 data; + + switch (pattern) { + case 0: data = 0x00001000; break; + case 1: data = 0x01000000; break; + case 2: data = 0x02000000; break; + default: + WARN_ON(1); + return; + } + + nvkm_mask(device, 0x61c10c + loff, 0x0f001000, data); } void diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgf119.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgf119.c index 3b3643fb1019..c431e0b9fc11 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgf119.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgf119.c @@ -92,7 +92,19 @@ gf119_sor_dp_pattern(struct nvkm_ior *sor, int pattern) { struct nvkm_device *device = sor->disp->engine.subdev.device; const u32 soff = nv50_ior_base(sor); - nvkm_mask(device, 0x61c110 + soff, 0x0f0f0f0f, 0x01010101 * pattern); + u32 data; + + switch (pattern) { + case 0: data = 0x10101010; break; + case 1: data = 0x01010101; break; + case 2: data = 0x02020202; break; + case 3: data = 0x03030303; break; + default: + WARN_ON(1); + return; + } + + nvkm_mask(device, 0x61c110 + soff, 0x1f1f1f1f, data); } int diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm107.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm107.c index 38045c92197f..5aa7138f3472 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm107.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm107.c @@ -28,11 +28,22 @@ gm107_sor_dp_pattern(struct nvkm_ior *sor, int pattern) { struct nvkm_device *device = sor->disp->engine.subdev.device; const u32 soff = nv50_ior_base(sor); - const u32 data = 0x01010101 * pattern; + u32 mask = 0x1f1f1f1f, data; + + switch (pattern) { + case 0: data = 0x10101010; break; + case 1: data = 0x01010101; break; + case 2: data = 0x02020202; break; + case 3: data = 0x03030303; break; + default: + WARN_ON(1); + return; + } + if (sor->asy.link & 1) - nvkm_mask(device, 0x61c110 + soff, 0x0f0f0f0f, data); + nvkm_mask(device, 0x61c110 + soff, mask, data); else - nvkm_mask(device, 0x61c12c + soff, 0x0f0f0f0f, data); + nvkm_mask(device, 0x61c12c + soff, mask, data); } static const struct nvkm_ior_func -- cgit From b96a1d8c5189452d80551f2c38ef50c533c19669 Mon Sep 17 00:00:00 2001 From: Ben Skeggs Date: Tue, 23 Nov 2021 18:17:52 +1000 Subject: drm/nouveau/disp/dp: generate supported link rates table at detect time Replaces the static list used during link training with a table built dynamically from the union of source and sink capabilities. Preparation for adding support for HBR3, LTTPR and eDP 1.4 link rates. Signed-off-by: Ben Skeggs Reviewed-by: Lyude Paul Signed-off-by: Karol Herbst Link: https://gitlab.freedesktop.org/drm/nouveau/-/merge_requests/17 --- drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c | 108 ++++++++++---------------- drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h | 6 ++ 2 files changed, 45 insertions(+), 69 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c index 3c0554a60ba9..8b49398a91c5 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c @@ -350,63 +350,13 @@ nvkm_dp_train_init(struct nvkm_dp *dp) } } -static const struct dp_rates { - u32 rate; - u8 bw; - u8 nr; -} nvkm_dp_rates[] = { - { 2160000, 0x14, 4 }, - { 1080000, 0x0a, 4 }, - { 1080000, 0x14, 2 }, - { 648000, 0x06, 4 }, - { 540000, 0x0a, 2 }, - { 540000, 0x14, 1 }, - { 324000, 0x06, 2 }, - { 270000, 0x0a, 1 }, - { 162000, 0x06, 1 }, - {} -}; - static int nvkm_dp_train(struct nvkm_dp *dp, u32 dataKBps) { struct nvkm_ior *ior = dp->outp.ior; - const u8 sink_nr = dp->dpcd[DPCD_RC02] & DPCD_RC02_MAX_LANE_COUNT; - const u8 sink_bw = dp->dpcd[DPCD_RC01_MAX_LINK_RATE]; - const u8 outp_nr = dp->outp.info.dpconf.link_nr; - const u8 outp_bw = dp->outp.info.dpconf.link_bw; - const struct dp_rates *failsafe = NULL, *cfg; - int ret = -EINVAL; + int ret = -EINVAL, nr, rate; u8 pwr; - /* Find the lowest configuration of the OR that can support - * the required link rate. - * - * We will refuse to program the OR to lower rates, even if - * link training fails at higher rates (or even if the sink - * can't support the rate at all, though the DD is supposed - * to prevent such situations from happening). - * - * Attempting to do so can cause the entire display to hang, - * and it's better to have a failed modeset than that. - */ - for (cfg = nvkm_dp_rates; cfg->rate; cfg++) { - if (cfg->nr <= outp_nr && cfg->bw <= outp_bw) { - /* Try to respect sink limits too when selecting - * lowest link configuration. - */ - if (!failsafe || - (cfg->nr <= sink_nr && cfg->bw <= sink_bw)) - failsafe = cfg; - } - - if (failsafe && cfg[1].rate < dataKBps) - break; - } - - if (WARN_ON(!failsafe)) - return ret; - /* Ensure sink is not in a low-power state. */ if (!nvkm_rdaux(dp->aux, DPCD_SC00, &pwr, 1)) { if ((pwr & DPCD_SC00_SET_POWER) != DPCD_SC00_SET_POWER_D0) { @@ -416,25 +366,22 @@ nvkm_dp_train(struct nvkm_dp *dp, u32 dataKBps) } } + ior->dp.mst = dp->lt.mst; + ior->dp.ef = dp->dpcd[DPCD_RC02] & DPCD_RC02_ENHANCED_FRAME_CAP; + ior->dp.nr = 0; + /* Link training. */ - OUTP_DBG(&dp->outp, "training (min: %d x %d MB/s)", - failsafe->nr, failsafe->bw * 27); + OUTP_DBG(&dp->outp, "training"); nvkm_dp_train_init(dp); - for (cfg = nvkm_dp_rates; ret < 0 && cfg <= failsafe; cfg++) { - /* Skip configurations not supported by both OR and sink. */ - if ((cfg->nr > outp_nr || cfg->bw > outp_bw || - cfg->nr > sink_nr || cfg->bw > sink_bw)) { - if (cfg != failsafe) - continue; - OUTP_ERR(&dp->outp, "link rate unsupported by sink"); + for (nr = dp->links; ret < 0 && nr; nr >>= 1) { + for (rate = 0; ret < 0 && rate < dp->rates; rate++) { + if (dp->rate[rate].rate * nr >= dataKBps || WARN_ON(!ior->dp.nr)) { + /* Program selected link configuration. */ + ior->dp.bw = dp->rate[rate].rate / 27000; + ior->dp.nr = nr; + ret = nvkm_dp_train_links(dp); + } } - ior->dp.mst = dp->lt.mst; - ior->dp.ef = dp->dpcd[DPCD_RC02] & DPCD_RC02_ENHANCED_FRAME_CAP; - ior->dp.bw = cfg->bw; - ior->dp.nr = cfg->nr; - - /* Program selected link configuration. */ - ret = nvkm_dp_train_links(dp); } nvkm_dp_train_fini(dp); if (ret < 0) @@ -543,9 +490,32 @@ nvkm_dp_enable(struct nvkm_dp *dp, bool enable) dp->present = true; } - if (!nvkm_rdaux(aux, DPCD_RC00_DPCD_REV, dp->dpcd, - sizeof(dp->dpcd))) + if (!nvkm_rdaux(aux, DPCD_RC00_DPCD_REV, dp->dpcd, sizeof(dp->dpcd))) { + const u8 rates[] = { 0x14, 0x0a, 0x06, 0 }; + const u8 *rate; + int rate_max; + + dp->rates = 0; + dp->links = dp->dpcd[DPCD_RC02] & DPCD_RC02_MAX_LANE_COUNT; + dp->links = min(dp->links, dp->outp.info.dpconf.link_nr); + + rate_max = dp->dpcd[DPCD_RC01_MAX_LINK_RATE]; + rate_max = min(rate_max, dp->outp.info.dpconf.link_bw); + + if (1) { + for (rate = rates; *rate; rate++) { + if (*rate <= rate_max) { + if (WARN_ON(dp->rates == ARRAY_SIZE(dp->rate))) + break; + + dp->rate[dp->rates].rate = *rate * 27000; + dp->rates++; + } + } + } + return true; + } } if (dp->present) { diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h index 9ce91f79ca35..8a8d0da58277 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h @@ -20,6 +20,12 @@ struct nvkm_dp { bool present; u8 dpcd[16]; + struct { + u32 rate; + } rate[3]; + int rates; + int links; + struct mutex mutex; struct { atomic_t done; -- cgit From 3edcd504077c02c9e6e7b687a43849eacbb8088d Mon Sep 17 00:00:00 2001 From: Ben Skeggs Date: Tue, 23 Nov 2021 18:17:54 +1000 Subject: drm/nouveau/disp/dp: add support for tps4 Required for HBR3 and LTTPR. Signed-off-by: Ben Skeggs Reviewed-by: Lyude Paul Signed-off-by: Karol Herbst Link: https://gitlab.freedesktop.org/drm/nouveau/-/merge_requests/17 --- drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c | 19 ++++++++++++++----- drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h | 3 ++- drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm107.c | 1 + 3 files changed, 17 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c index 8b49398a91c5..565ee63c0f1d 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c @@ -153,7 +153,7 @@ nvkm_dp_train_pattern(struct lt_state *lt, u8 pattern) nvkm_rdaux(dp->aux, DPCD_LC02, &sink_tp, 1); sink_tp &= ~DPCD_LC02_TRAINING_PATTERN_SET; - sink_tp |= pattern; + sink_tp |= (pattern != 4) ? pattern : 7; if (pattern != 0) sink_tp |= DPCD_LC02_SCRAMBLING_DISABLE; @@ -168,10 +168,17 @@ nvkm_dp_train_eq(struct lt_state *lt) bool eq_done = false, cr_done = true; int tries = 0, i; - if (lt->dp->dpcd[DPCD_RC02] & DPCD_RC02_TPS3_SUPPORTED) - nvkm_dp_train_pattern(lt, 3); - else - nvkm_dp_train_pattern(lt, 2); + { + if (lt->dp->dpcd[DPCD_RC00_DPCD_REV] >= 0x14 && + lt->dp->dpcd[DPCD_RC03] & DPCD_RC03_TPS4_SUPPORTED) + nvkm_dp_train_pattern(lt, 4); + else + if (lt->dp->dpcd[DPCD_RC00_DPCD_REV] >= 0x12 && + lt->dp->dpcd[DPCD_RC02] & DPCD_RC02_TPS3_SUPPORTED) + nvkm_dp_train_pattern(lt, 3); + else + nvkm_dp_train_pattern(lt, 2); + } do { if ((tries && @@ -245,6 +252,8 @@ nvkm_dp_train_links(struct nvkm_dp *dp) ior->dp.nr, ior->dp.bw * 27); /* Intersect misc. capabilities of the OR and sink. */ + if (disp->engine.subdev.device->chipset < 0x110) + dp->dpcd[DPCD_RC03] &= ~DPCD_RC03_TPS4_SUPPORTED; if (disp->engine.subdev.device->chipset < 0xd0) dp->dpcd[DPCD_RC02] &= ~DPCD_RC02_TPS3_SUPPORTED; lt.pc2 = dp->dpcd[DPCD_RC02] & DPCD_RC02_TPS3_SUPPORTED; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h index 8a8d0da58277..1becf9da3ed7 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h @@ -45,6 +45,7 @@ void nvkm_dp_disable(struct nvkm_outp *, struct nvkm_ior *); #define DPCD_RC02_TPS3_SUPPORTED 0x40 #define DPCD_RC02_MAX_LANE_COUNT 0x1f #define DPCD_RC03 0x00003 +#define DPCD_RC03_TPS4_SUPPORTED 0x80 #define DPCD_RC03_MAX_DOWNSPREAD 0x01 #define DPCD_RC0E_AUX_RD_INTERVAL 0x0000e @@ -54,7 +55,7 @@ void nvkm_dp_disable(struct nvkm_outp *, struct nvkm_ior *); #define DPCD_LC01_ENHANCED_FRAME_EN 0x80 #define DPCD_LC01_LANE_COUNT_SET 0x1f #define DPCD_LC02 0x00102 -#define DPCD_LC02_TRAINING_PATTERN_SET 0x03 +#define DPCD_LC02_TRAINING_PATTERN_SET 0x0f #define DPCD_LC02_SCRAMBLING_DISABLE 0x20 #define DPCD_LC03(l) ((l) + 0x00103) #define DPCD_LC03_MAX_PRE_EMPHASIS_REACHED 0x20 diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm107.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm107.c index 5aa7138f3472..3696bfd3bfd7 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm107.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm107.c @@ -35,6 +35,7 @@ gm107_sor_dp_pattern(struct nvkm_ior *sor, int pattern) case 1: data = 0x01010101; break; case 2: data = 0x02020202; break; case 3: data = 0x03030303; break; + case 4: data = 0x1b1b1b1b; break; default: WARN_ON(1); return; -- cgit From 9543e3c0511da64a77b1af3ab1f7199c39226e1b Mon Sep 17 00:00:00 2001 From: Ben Skeggs Date: Tue, 23 Nov 2021 18:17:56 +1000 Subject: drm/nouveau/disp/dp: fixup cr/eq delays for 1.4 Also use usleep_range() instead of [um]delay() to be a bit nicer. Signed-off-by: Ben Skeggs Reviewed-by: Lyude Paul Signed-off-by: Karol Herbst Link: https://gitlab.freedesktop.org/drm/nouveau/-/merge_requests/17 --- drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c | 18 ++++++++++-------- drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h | 3 ++- 2 files changed, 12 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c index 565ee63c0f1d..eee2451123c9 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c @@ -54,10 +54,7 @@ nvkm_dp_train_sense(struct lt_state *lt, bool pc, u32 delay) struct nvkm_dp *dp = lt->dp; int ret; - if (dp->dpcd[DPCD_RC0E_AUX_RD_INTERVAL]) - mdelay(dp->dpcd[DPCD_RC0E_AUX_RD_INTERVAL] * 4); - else - udelay(delay); + usleep_range(delay, delay * 2); ret = nvkm_rdaux(dp->aux, DPCD_LS02, lt->stat, 6); if (ret) @@ -166,7 +163,7 @@ static int nvkm_dp_train_eq(struct lt_state *lt) { bool eq_done = false, cr_done = true; - int tries = 0, i; + int tries = 0, usec = 0, i; { if (lt->dp->dpcd[DPCD_RC00_DPCD_REV] >= 0x14 && @@ -178,12 +175,14 @@ nvkm_dp_train_eq(struct lt_state *lt) nvkm_dp_train_pattern(lt, 3); else nvkm_dp_train_pattern(lt, 2); + + usec = (lt->dp->dpcd[DPCD_RC0E] & DPCD_RC0E_AUX_RD_INTERVAL) * 4000; } do { if ((tries && nvkm_dp_train_drive(lt, lt->pc2)) || - nvkm_dp_train_sense(lt, lt->pc2, 400)) + nvkm_dp_train_sense(lt, lt->pc2, usec ? usec : 400)) break; eq_done = !!(lt->stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE); @@ -205,13 +204,16 @@ nvkm_dp_train_cr(struct lt_state *lt) { bool cr_done = false, abort = false; int voltage = lt->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET; - int tries = 0, i; + int tries = 0, usec = 0, i; nvkm_dp_train_pattern(lt, 1); + if (lt->dp->dpcd[DPCD_RC00_DPCD_REV] < 0x14) + usec = (lt->dp->dpcd[DPCD_RC0E] & DPCD_RC0E_AUX_RD_INTERVAL) * 4000; + do { if (nvkm_dp_train_drive(lt, false) || - nvkm_dp_train_sense(lt, false, 100)) + nvkm_dp_train_sense(lt, false, usec ? usec : 100)) break; cr_done = true; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h index 1becf9da3ed7..104f847b5abf 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h @@ -47,7 +47,8 @@ void nvkm_dp_disable(struct nvkm_outp *, struct nvkm_ior *); #define DPCD_RC03 0x00003 #define DPCD_RC03_TPS4_SUPPORTED 0x80 #define DPCD_RC03_MAX_DOWNSPREAD 0x01 -#define DPCD_RC0E_AUX_RD_INTERVAL 0x0000e +#define DPCD_RC0E 0x0000e +#define DPCD_RC0E_AUX_RD_INTERVAL 0x7f /* DPCD Link Configuration */ #define DPCD_LC00_LINK_BW_SET 0x00100 -- cgit From f21e5fa191be05616e3250723c5c4cadc9baf84a Mon Sep 17 00:00:00 2001 From: Ben Skeggs Date: Tue, 23 Nov 2021 18:18:00 +1000 Subject: drm/nouveau/disp/dp: add support for lttprs Add support for Link-Training Tunable PHY Repeaters, required to support higher bit rates on longer cables, as well as USB-C on certain docks and laptops. Signed-off-by: Ben Skeggs Reviewed-by: Lyude Paul Signed-off-by: Karol Herbst Link: https://gitlab.freedesktop.org/drm/nouveau/-/merge_requests/17 --- drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c | 114 +++++++++++++++++++++++--- drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h | 12 +++ 2 files changed, 114 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c index eee2451123c9..b0dfa08f799a 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c @@ -41,6 +41,10 @@ struct lt_state { struct nvkm_dp *dp; + + int repeaters; + int repeater; + u8 stat[6]; u8 conf[4]; bool pc2; @@ -52,11 +56,26 @@ static int nvkm_dp_train_sense(struct lt_state *lt, bool pc, u32 delay) { struct nvkm_dp *dp = lt->dp; + u32 addr; int ret; usleep_range(delay, delay * 2); - ret = nvkm_rdaux(dp->aux, DPCD_LS02, lt->stat, 6); + if (lt->repeater) + addr = DPCD_LTTPR_LANE0_1_STATUS(lt->repeater); + else + addr = DPCD_LS02; + + ret = nvkm_rdaux(dp->aux, addr, <->stat[0], 3); + if (ret) + return ret; + + if (lt->repeater) + addr = DPCD_LTTPR_LANE0_1_ADJUST(lt->repeater); + else + addr = DPCD_LS06; + + ret = nvkm_rdaux(dp->aux, addr, <->stat[4], 2); if (ret) return ret; @@ -82,6 +101,7 @@ nvkm_dp_train_drive(struct lt_state *lt, bool pc) struct nvbios_dpout info; struct nvbios_dpcfg ocfg; u8 ver, hdr, cnt, len; + u32 addr; u32 data; int ret, i; @@ -110,6 +130,9 @@ nvkm_dp_train_drive(struct lt_state *lt, bool pc) OUTP_TRACE(&dp->outp, "config lane %d %02x %02x", i, lt->conf[i], lpc2); + if (lt->repeater != lt->repeaters) + continue; + data = nvbios_dpout_match(bios, dp->outp.info.hasht, dp->outp.info.hashm, &ver, &hdr, &cnt, &len, &info); @@ -126,7 +149,12 @@ nvkm_dp_train_drive(struct lt_state *lt, bool pc) ocfg.pe, ocfg.tx_pu); } - ret = nvkm_wraux(dp->aux, DPCD_LC03(0), lt->conf, 4); + if (lt->repeater) + addr = DPCD_LTTPR_LANE0_SET(lt->repeater); + else + addr = DPCD_LC03(0); + + ret = nvkm_wraux(dp->aux, addr, lt->conf, 4); if (ret) return ret; @@ -143,12 +171,18 @@ static void nvkm_dp_train_pattern(struct lt_state *lt, u8 pattern) { struct nvkm_dp *dp = lt->dp; + u32 addr; u8 sink_tp; OUTP_TRACE(&dp->outp, "training pattern %d", pattern); dp->outp.ior->func->dp.pattern(dp->outp.ior, pattern); - nvkm_rdaux(dp->aux, DPCD_LC02, &sink_tp, 1); + if (lt->repeater) + addr = DPCD_LTTPR_PATTERN_SET(lt->repeater); + else + addr = DPCD_LC02; + + nvkm_rdaux(dp->aux, addr, &sink_tp, 1); sink_tp &= ~DPCD_LC02_TRAINING_PATTERN_SET; sink_tp |= (pattern != 4) ? pattern : 7; @@ -156,16 +190,23 @@ nvkm_dp_train_pattern(struct lt_state *lt, u8 pattern) sink_tp |= DPCD_LC02_SCRAMBLING_DISABLE; else sink_tp &= ~DPCD_LC02_SCRAMBLING_DISABLE; - nvkm_wraux(dp->aux, DPCD_LC02, &sink_tp, 1); + nvkm_wraux(dp->aux, addr, &sink_tp, 1); } static int nvkm_dp_train_eq(struct lt_state *lt) { + struct nvkm_i2c_aux *aux = lt->dp->aux; bool eq_done = false, cr_done = true; int tries = 0, usec = 0, i; + u8 data; - { + if (lt->repeater) { + if (!nvkm_rdaux(aux, DPCD_LTTPR_AUX_RD_INTERVAL(lt->repeater), &data, sizeof(data))) + usec = (data & DPCD_RC0E_AUX_RD_INTERVAL) * 4000; + + nvkm_dp_train_pattern(lt, 4); + } else { if (lt->dp->dpcd[DPCD_RC00_DPCD_REV] >= 0x14 && lt->dp->dpcd[DPCD_RC03] & DPCD_RC03_TPS4_SUPPORTED) nvkm_dp_train_pattern(lt, 4); @@ -208,7 +249,7 @@ nvkm_dp_train_cr(struct lt_state *lt) nvkm_dp_train_pattern(lt, 1); - if (lt->dp->dpcd[DPCD_RC00_DPCD_REV] < 0x14) + if (lt->dp->dpcd[DPCD_RC00_DPCD_REV] < 0x14 && !lt->repeater) usec = (lt->dp->dpcd[DPCD_RC0E] & DPCD_RC0E_AUX_RD_INTERVAL) * 4000; do { @@ -247,7 +288,7 @@ nvkm_dp_train_links(struct nvkm_dp *dp) .dp = dp, }; u32 lnkcmp; - u8 sink[2]; + u8 sink[2], data; int ret; OUTP_DBG(&dp->outp, "training %d x %d MB/s", @@ -303,6 +344,20 @@ nvkm_dp_train_links(struct nvkm_dp *dp) ior->func->dp.power(ior, ior->dp.nr); + /* Select LTTPR non-transparent mode if we have a valid configuration, + * use transparent mode otherwise. + */ + if (dp->lttpr[0] >= 0x14) { + data = DPCD_LTTPR_MODE_TRANSPARENT; + nvkm_wraux(dp->aux, DPCD_LTTPR_MODE, &data, sizeof(data)); + + if (dp->lttprs) { + data = DPCD_LTTPR_MODE_NON_TRANSPARENT; + nvkm_wraux(dp->aux, DPCD_LTTPR_MODE, &data, sizeof(data)); + lt.repeaters = dp->lttprs; + } + } + /* Set desired link configuration on the sink. */ sink[0] = ior->dp.bw; sink[1] = ior->dp.nr; @@ -314,11 +369,19 @@ nvkm_dp_train_links(struct nvkm_dp *dp) return ret; /* Attempt to train the link in this configuration. */ - memset(lt.stat, 0x00, sizeof(lt.stat)); - ret = nvkm_dp_train_cr(<); - if (ret == 0) - ret = nvkm_dp_train_eq(<); - nvkm_dp_train_pattern(<, 0); + for (lt.repeater = lt.repeaters; lt.repeater >= 0; lt.repeater--) { + if (lt.repeater) + OUTP_DBG(&dp->outp, "training LTTPR%d", lt.repeater); + else + OUTP_DBG(&dp->outp, "training sink"); + + memset(lt.stat, 0x00, sizeof(lt.stat)); + ret = nvkm_dp_train_cr(<); + if (ret == 0) + ret = nvkm_dp_train_eq(<); + nvkm_dp_train_pattern(<, 0); + } + return ret; } @@ -501,6 +564,29 @@ nvkm_dp_enable(struct nvkm_dp *dp, bool enable) dp->present = true; } + /* Detect any LTTPRs before reading DPCD receiver caps. */ + if (!nvkm_rdaux(aux, DPCD_LTTPR_REV, dp->lttpr, sizeof(dp->lttpr)) && + dp->lttpr[0] >= 0x14 && dp->lttpr[2]) { + switch (dp->lttpr[2]) { + case 0x80: dp->lttprs = 1; break; + case 0x40: dp->lttprs = 2; break; + case 0x20: dp->lttprs = 3; break; + case 0x10: dp->lttprs = 4; break; + case 0x08: dp->lttprs = 5; break; + case 0x04: dp->lttprs = 6; break; + case 0x02: dp->lttprs = 7; break; + case 0x01: dp->lttprs = 8; break; + default: + /* Unknown LTTPR count, we'll switch to transparent mode. */ + WARN_ON(1); + dp->lttprs = 0; + break; + } + } else { + /* No LTTPR support, or zero LTTPR count - don't touch it at all. */ + memset(dp->lttpr, 0x00, sizeof(dp->lttpr)); + } + if (!nvkm_rdaux(aux, DPCD_RC00_DPCD_REV, dp->dpcd, sizeof(dp->dpcd))) { const u8 rates[] = { 0x14, 0x0a, 0x06, 0 }; const u8 *rate; @@ -509,9 +595,13 @@ nvkm_dp_enable(struct nvkm_dp *dp, bool enable) dp->rates = 0; dp->links = dp->dpcd[DPCD_RC02] & DPCD_RC02_MAX_LANE_COUNT; dp->links = min(dp->links, dp->outp.info.dpconf.link_nr); + if (dp->lttprs && dp->lttpr[4]) + dp->links = min_t(int, dp->links, dp->lttpr[4]); rate_max = dp->dpcd[DPCD_RC01_MAX_LINK_RATE]; rate_max = min(rate_max, dp->outp.info.dpconf.link_bw); + if (dp->lttprs && dp->lttpr[1]) + rate_max = min_t(int, rate_max, dp->lttpr[1]); if (1) { for (rate = rates; *rate; rate++) { diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h index 104f847b5abf..8728d5e0fb8a 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h @@ -18,6 +18,8 @@ struct nvkm_dp { struct nvkm_notify hpd; bool present; + u8 lttpr[6]; + u8 lttprs; u8 dpcd[16]; struct { @@ -114,4 +116,14 @@ void nvkm_dp_disable(struct nvkm_outp *, struct nvkm_ior *); #define DPCD_SC00_SET_POWER 0x03 #define DPCD_SC00_SET_POWER_D0 0x01 #define DPCD_SC00_SET_POWER_D3 0x03 + +#define DPCD_LTTPR_REV 0xf0000 +#define DPCD_LTTPR_MODE 0xf0003 +#define DPCD_LTTPR_MODE_TRANSPARENT 0x55 +#define DPCD_LTTPR_MODE_NON_TRANSPARENT 0xaa +#define DPCD_LTTPR_PATTERN_SET(i) ((i - 1) * 0x50 + 0xf0010) +#define DPCD_LTTPR_LANE0_SET(i) ((i - 1) * 0x50 + 0xf0011) +#define DPCD_LTTPR_AUX_RD_INTERVAL(i) ((i - 1) * 0x50 + 0xf0020) +#define DPCD_LTTPR_LANE0_1_STATUS(i) ((i - 1) * 0x50 + 0xf0030) +#define DPCD_LTTPR_LANE0_1_ADJUST(i) ((i - 1) * 0x50 + 0xf0033) #endif -- cgit From 405d5382ba083e6e54df354909b1583f1e3cb0db Mon Sep 17 00:00:00 2001 From: Ben Skeggs Date: Tue, 23 Nov 2021 18:18:03 +1000 Subject: drm/nouveau/disp/dp: add support for hbr3 Signed-off-by: Ben Skeggs Reviewed-by: Lyude Paul Signed-off-by: Karol Herbst Link: https://gitlab.freedesktop.org/drm/nouveau/-/merge_requests/17 --- drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c | 2 +- drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c index b0dfa08f799a..ab78842371e8 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c @@ -588,7 +588,7 @@ nvkm_dp_enable(struct nvkm_dp *dp, bool enable) } if (!nvkm_rdaux(aux, DPCD_RC00_DPCD_REV, dp->dpcd, sizeof(dp->dpcd))) { - const u8 rates[] = { 0x14, 0x0a, 0x06, 0 }; + const u8 rates[] = { 0x1e, 0x14, 0x0a, 0x06, 0 }; const u8 *rate; int rate_max; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h index 8728d5e0fb8a..3b751cd09a16 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h @@ -24,7 +24,7 @@ struct nvkm_dp { struct { u32 rate; - } rate[3]; + } rate[4]; int rates; int links; -- cgit From 70704fbf67ddc07ffc81073a3af1f7b2171697eb Mon Sep 17 00:00:00 2001 From: Ben Skeggs Date: Tue, 23 Nov 2021 18:18:06 +1000 Subject: drm/nouveau/disp/dp: add support for eDP link rates eDP 1.4 adds a table of link rates supported by the sink to DPCD, as well as a LINK_RATE_SET register to select between the entries in it. If present, we will use this data to generate our internal link rate table rather than using the standard list based on MAX_LINK_RATE. Some recent laptops report MAX_LINK_RATE=0, and require this support. Signed-off-by: Ben Skeggs Reviewed-by: Lyude Paul Signed-off-by: Karol Herbst Link: https://gitlab.freedesktop.org/drm/nouveau/-/merge_requests/17 --- drivers/gpu/drm/nouveau/nouveau_dp.c | 15 ++++++ drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c | 63 ++++++++++++++++++++-- drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h | 7 ++- .../gpu/drm/nouveau/nvkm/engine/disp/sorga102.c | 4 ++ 4 files changed, 84 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/nouveau/nouveau_dp.c b/drivers/gpu/drm/nouveau/nouveau_dp.c index 040ed88d362d..3a288d0b848f 100644 --- a/drivers/gpu/drm/nouveau/nouveau_dp.c +++ b/drivers/gpu/drm/nouveau/nouveau_dp.c @@ -147,6 +147,21 @@ nouveau_dp_detect(struct nouveau_connector *nv_connector, nv_encoder->dp.link_nr = dpcd[DP_MAX_LANE_COUNT] & DP_MAX_LANE_COUNT_MASK; + if (connector->connector_type == DRM_MODE_CONNECTOR_eDP && dpcd[DP_DPCD_REV] >= 0x13) { + struct drm_dp_aux *aux = &nv_connector->aux; + int ret, i; + u8 sink_rates[16]; + + ret = drm_dp_dpcd_read(aux, DP_SUPPORTED_LINK_RATES, sink_rates, sizeof(sink_rates)); + if (ret == sizeof(sink_rates)) { + for (i = 0; i < ARRAY_SIZE(sink_rates); i += 2) { + int val = ((sink_rates[i + 1] << 8) | sink_rates[i]) * 200 / 10; + if (val && (i == 0 || val > nv_encoder->dp.link_bw)) + nv_encoder->dp.link_bw = val; + } + } + } + NV_DEBUG(drm, "display: %dx%d dpcd 0x%02x\n", nv_encoder->dp.link_nr, nv_encoder->dp.link_bw, dpcd[DP_DPCD_REV]); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c index ab78842371e8..8e09315b8fb3 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c @@ -278,7 +278,7 @@ nvkm_dp_train_cr(struct lt_state *lt) } static int -nvkm_dp_train_links(struct nvkm_dp *dp) +nvkm_dp_train_links(struct nvkm_dp *dp, int rate) { struct nvkm_ior *ior = dp->outp.ior; struct nvkm_disp *disp = dp->outp.disp; @@ -359,7 +359,7 @@ nvkm_dp_train_links(struct nvkm_dp *dp) } /* Set desired link configuration on the sink. */ - sink[0] = ior->dp.bw; + sink[0] = (dp->rate[rate].dpcd < 0) ? ior->dp.bw : 0; sink[1] = ior->dp.nr; if (ior->dp.ef) sink[1] |= DPCD_LC01_ENHANCED_FRAME_EN; @@ -368,6 +368,19 @@ nvkm_dp_train_links(struct nvkm_dp *dp) if (ret) return ret; + if (dp->rate[rate].dpcd >= 0) { + ret = nvkm_rdaux(dp->aux, DPCD_LC15_LINK_RATE_SET, &sink[0], sizeof(sink[0])); + if (ret) + return ret; + + sink[0] &= ~DPCD_LC15_LINK_RATE_SET_MASK; + sink[0] |= dp->rate[rate].dpcd; + + ret = nvkm_wraux(dp->aux, DPCD_LC15_LINK_RATE_SET, &sink[0], sizeof(sink[0])); + if (ret) + return ret; + } + /* Attempt to train the link in this configuration. */ for (lt.repeater = lt.repeaters; lt.repeater >= 0; lt.repeater--) { if (lt.repeater) @@ -453,7 +466,7 @@ nvkm_dp_train(struct nvkm_dp *dp, u32 dataKBps) /* Program selected link configuration. */ ior->dp.bw = dp->rate[rate].rate / 27000; ior->dp.nr = nr; - ret = nvkm_dp_train_links(dp); + ret = nvkm_dp_train_links(dp, rate); } } } @@ -552,6 +565,47 @@ done: return ret; } +static bool +nvkm_dp_enable_supported_link_rates(struct nvkm_dp *dp) +{ + u8 sink_rates[DPCD_RC10_SUPPORTED_LINK_RATES__SIZE]; + int i, j, k; + + if (dp->outp.conn->info.type != DCB_CONNECTOR_eDP || + dp->dpcd[DPCD_RC00_DPCD_REV] < 0x13 || + nvkm_rdaux(dp->aux, DPCD_RC10_SUPPORTED_LINK_RATES(0), sink_rates, sizeof(sink_rates))) + return false; + + for (i = 0; i < ARRAY_SIZE(sink_rates); i += 2) { + const u32 rate = ((sink_rates[i + 1] << 8) | sink_rates[i]) * 200 / 10; + + if (!rate || WARN_ON(dp->rates == ARRAY_SIZE(dp->rate))) + break; + + if (rate > dp->outp.info.dpconf.link_bw * 27000) { + OUTP_DBG(&dp->outp, "rate %d !outp", rate); + continue; + } + + for (j = 0; j < dp->rates; j++) { + if (rate > dp->rate[j].rate) { + for (k = dp->rates; k > j; k--) + dp->rate[k] = dp->rate[k - 1]; + break; + } + } + + dp->rate[j].dpcd = i / 2; + dp->rate[j].rate = rate; + dp->rates++; + } + + for (i = 0; i < dp->rates; i++) + OUTP_DBG(&dp->outp, "link_rate[%d] = %d", dp->rate[i].dpcd, dp->rate[i].rate); + + return dp->rates != 0; +} + static bool nvkm_dp_enable(struct nvkm_dp *dp, bool enable) { @@ -603,12 +657,13 @@ nvkm_dp_enable(struct nvkm_dp *dp, bool enable) if (dp->lttprs && dp->lttpr[1]) rate_max = min_t(int, rate_max, dp->lttpr[1]); - if (1) { + if (!nvkm_dp_enable_supported_link_rates(dp)) { for (rate = rates; *rate; rate++) { if (*rate <= rate_max) { if (WARN_ON(dp->rates == ARRAY_SIZE(dp->rate))) break; + dp->rate[dp->rates].dpcd = -1; dp->rate[dp->rates].rate = *rate * 27000; dp->rates++; } diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h index 3b751cd09a16..8e59dd469da6 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h @@ -23,8 +23,9 @@ struct nvkm_dp { u8 dpcd[16]; struct { + int dpcd; /* -1, or index into SUPPORTED_LINK_RATES table */ u32 rate; - } rate[4]; + } rate[8]; int rates; int links; @@ -51,6 +52,8 @@ void nvkm_dp_disable(struct nvkm_outp *, struct nvkm_ior *); #define DPCD_RC03_MAX_DOWNSPREAD 0x01 #define DPCD_RC0E 0x0000e #define DPCD_RC0E_AUX_RD_INTERVAL 0x7f +#define DPCD_RC10_SUPPORTED_LINK_RATES(i) 0x00010 +#define DPCD_RC10_SUPPORTED_LINK_RATES__SIZE 16 /* DPCD Link Configuration */ #define DPCD_LC00_LINK_BW_SET 0x00100 @@ -75,6 +78,8 @@ void nvkm_dp_disable(struct nvkm_outp *, struct nvkm_ior *); #define DPCD_LC10_LANE3_POST_CURSOR2_SET 0x30 #define DPCD_LC10_LANE2_MAX_POST_CURSOR2_REACHED 0x04 #define DPCD_LC10_LANE2_POST_CURSOR2_SET 0x03 +#define DPCD_LC15_LINK_RATE_SET 0x00115 +#define DPCD_LC15_LINK_RATE_SET_MASK 0x07 /* DPCD Link/Sink Status */ #define DPCD_LS02 0x00202 diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorga102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorga102.c index 033827de9116..d2c05f5c4aa0 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorga102.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorga102.c @@ -37,6 +37,10 @@ ga102_sor_dp_links(struct nvkm_ior *sor, struct nvkm_i2c_aux *aux) case 0x0a: clksor |= 0x00040000; break; case 0x14: clksor |= 0x00080000; break; case 0x1e: clksor |= 0x000c0000; break; + case 0x08: clksor |= 0x00100000; break; + case 0x09: clksor |= 0x00140000; break; + case 0x0c: clksor |= 0x00180000; break; + case 0x10: clksor |= 0x001c0000; break; default: WARN_ON(1); return -EINVAL; -- cgit From 943e6a8beeac1b676265f2dd81a69d7bede5e41d Mon Sep 17 00:00:00 2001 From: Claudio Suarez Date: Thu, 2 Dec 2021 10:49:49 +0100 Subject: mock a drm_plane in igt_check_plane_state to make the test more robust igt_check_plane_state test crashes in drm_atomic_helper_check_plane_state when trying to de-reference drm_plane_state->plane->dev due to the lack of a struct drm_plane in the mock struct drm_plane_state. Since drm_plane_state always should contain a plane, the mock also needs a plane to be the test more robust and realistic. Add it. Signed-off-by: Claudio Suarez Signed-off-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/YaiWvQESctDMipjh@gineta.localdomain --- drivers/gpu/drm/selftests/test-drm_plane_helper.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/selftests/test-drm_plane_helper.c b/drivers/gpu/drm/selftests/test-drm_plane_helper.c index 0a9553f51796..ceebeede55ea 100644 --- a/drivers/gpu/drm/selftests/test-drm_plane_helper.c +++ b/drivers/gpu/drm/selftests/test-drm_plane_helper.c @@ -87,11 +87,15 @@ int igt_check_plane_state(void *ignored) DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, }; + struct drm_plane plane = { + .dev = NULL + }; struct drm_framebuffer fb = { .width = 2048, .height = 2048 }; struct drm_plane_state plane_state = { + .plane = &plane, .crtc = ZERO_SIZE_PTR, .fb = &fb, .rotation = DRM_MODE_ROTATE_0 -- cgit From 521d459b1935628a7caa0753429f880dae2dbfc9 Mon Sep 17 00:00:00 2001 From: Claudio Suarez Date: Thu, 2 Dec 2021 10:51:12 +0100 Subject: drm: fix error found in some cases after the patch d1af5cd86997 The patch d1af5cd86997 ("drm: get rid of DRM_DEBUG_* log calls in drm core, files drm_a*.c") fails when the drm_device cannot be found in the parameter plane_state->crtc. Fix it using plane_state->plane. Reported-by: kernel test robot Fixes: 6e22dc358377 ("drm: get rid of DRM_DEBUG_* log calls in drm core, files drm_a*.c") Signed-off-by: Claudio Suarez [danvet: fix Fixes: line] Signed-off-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/YaiXEARd8z2C463h@gineta.localdomain --- drivers/gpu/drm/drm_atomic_helper.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index aef2fbd676e5..a7a05e1e26bb 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -828,8 +828,8 @@ int drm_atomic_helper_check_plane_state(struct drm_plane_state *plane_state, } if (!crtc_state->enable && !can_update_disabled) { - drm_dbg_kms(plane_state->crtc->dev, - "Cannot update plane of a disabled CRTC.\n"); + drm_dbg_kms(plane_state->plane->dev, + "Cannot update plane of a disabled CRTC.\n"); return -EINVAL; } @@ -839,8 +839,8 @@ int drm_atomic_helper_check_plane_state(struct drm_plane_state *plane_state, hscale = drm_rect_calc_hscale(src, dst, min_scale, max_scale); vscale = drm_rect_calc_vscale(src, dst, min_scale, max_scale); if (hscale < 0 || vscale < 0) { - drm_dbg_kms(plane_state->crtc->dev, - "Invalid scaling of plane\n"); + drm_dbg_kms(plane_state->plane->dev, + "Invalid scaling of plane\n"); drm_rect_debug_print("src: ", &plane_state->src, true); drm_rect_debug_print("dst: ", &plane_state->dst, false); return -ERANGE; @@ -864,8 +864,8 @@ int drm_atomic_helper_check_plane_state(struct drm_plane_state *plane_state, return 0; if (!can_position && !drm_rect_equals(dst, &clip)) { - drm_dbg_kms(plane_state->crtc->dev, - "Plane must cover entire CRTC\n"); + drm_dbg_kms(plane_state->plane->dev, + "Plane must cover entire CRTC\n"); drm_rect_debug_print("dst: ", dst, false); drm_rect_debug_print("clip: ", &clip, false); return -EINVAL; -- cgit From 5995aef006698bb639547a439f47492de5c37f05 Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Mon, 13 Dec 2021 14:36:24 +0100 Subject: drm/bridge: ti-sn65dsi83: Make enable GPIO optional The enable signal may not be controllable by the kernel. Make it optional. This is a similar to commit bbda1704fc15 ("drm/bridge: ti-sn65dsi86: Make enable GPIO optional") Reviewed-by: Laurent Pinchart Reviewed-by: Sam Ravnborg Signed-off-by: Alexander Stein Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20211213133626.2498056-3-alexander.stein@ew.tq-group.com --- drivers/gpu/drm/bridge/ti-sn65dsi83.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c index 945f08de45f1..065610edc37a 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi83.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi83.c @@ -662,7 +662,8 @@ static int sn65dsi83_probe(struct i2c_client *client, } /* Put the chip in reset, pull EN line low, and assure 10ms reset low timing. */ - ctx->enable_gpio = devm_gpiod_get(ctx->dev, "enable", GPIOD_OUT_LOW); + ctx->enable_gpio = devm_gpiod_get_optional(ctx->dev, "enable", + GPIOD_OUT_LOW); if (IS_ERR(ctx->enable_gpio)) return PTR_ERR(ctx->enable_gpio); -- cgit From 5664e3c907e20523cda622268716867e77648d0c Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Mon, 13 Dec 2021 14:36:26 +0100 Subject: drm/bridge: ti-sn65dsi83: Add vcc supply regulator support VCC needs to be enabled before releasing the enable GPIO. Reviewed-by: Laurent Pinchart Signed-off-by: Alexander Stein Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20211213133626.2498056-5-alexander.stein@ew.tq-group.com --- drivers/gpu/drm/bridge/ti-sn65dsi83.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c index 065610edc37a..5650a793db81 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi83.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi83.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -143,6 +144,7 @@ struct sn65dsi83 { struct mipi_dsi_device *dsi; struct drm_bridge *panel_bridge; struct gpio_desc *enable_gpio; + struct regulator *vcc; int dsi_lanes; bool lvds_dual_link; bool lvds_dual_link_even_odd_swap; @@ -337,6 +339,12 @@ static void sn65dsi83_atomic_enable(struct drm_bridge *bridge, u16 val; int ret; + ret = regulator_enable(ctx->vcc); + if (ret) { + dev_err(ctx->dev, "Failed to enable vcc: %d\n", ret); + return; + } + /* Deassert reset */ gpiod_set_value(ctx->enable_gpio, 1); usleep_range(1000, 1100); @@ -486,11 +494,16 @@ static void sn65dsi83_atomic_disable(struct drm_bridge *bridge, struct drm_bridge_state *old_bridge_state) { struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge); + int ret; /* Put the chip in reset, pull EN line low, and assure 10ms reset low timing. */ gpiod_set_value(ctx->enable_gpio, 0); usleep_range(10000, 11000); + ret = regulator_disable(ctx->vcc); + if (ret) + dev_err(ctx->dev, "Failed to disable vcc: %d\n", ret); + regcache_mark_dirty(ctx->regmap); } @@ -599,6 +612,11 @@ static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model) ctx->panel_bridge = panel_bridge; + ctx->vcc = devm_regulator_get(dev, "vcc"); + if (IS_ERR(ctx->vcc)) + return dev_err_probe(dev, PTR_ERR(ctx->vcc), + "Failed to get supply 'vcc'\n"); + return 0; } -- cgit From 249af7242af72a826dbe93f7058237eee4fac1d6 Mon Sep 17 00:00:00 2001 From: John Harrison Date: Wed, 22 Dec 2021 17:31:28 -0800 Subject: drm/i915/guc: Report error on invalid reset notification Don't silently drop reset notifications from the GuC. It might not be safe to do an error capture but we still want some kind of report that the reset happened. Signed-off-by: John Harrison Reviewed-by: Matthew Brost Link: https://patchwork.freedesktop.org/patch/msgid/20211223013128.1739792-1-John.C.Harrison@Intel.com --- drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c index e7517206af82..0fbf24b8d5e1 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c @@ -3979,6 +3979,11 @@ static void guc_handle_context_reset(struct intel_guc *guc, !context_blocked(ce))) { capture_error_state(guc, ce); guc_context_replay(ce); + } else { + drm_err(&guc_to_gt(guc)->i915->drm, + "Invalid GuC engine reset notificaion for 0x%04X on %s: banned = %d, blocked = %d", + ce->guc_id.id, ce->engine->name, intel_context_is_banned(ce), + context_blocked(ce)); } } -- cgit From a88afcfa258672601eda5966439d2fc310a34cb7 Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Wed, 22 Dec 2021 14:35:32 -0800 Subject: drm/i915/execlists: Weak parallel submission support for execlists A weak implementation of parallel submission (multi-bb execbuf IOCTL) for execlists. Doing as little as possible to support this interface for execlists - basically just passing submit fences between each request generated and virtual engines are not allowed. This is on par with what is there for the existing (hopefully soon deprecated) bonding interface. We perma-pin these execlists contexts to align with GuC implementation. v2: (John Harrison) - Drop siblings array as num_siblings must be 1 v3: (John Harrison) - Drop single submission v4: (John Harrison) - Actually drop single submission - Use IS_ERR check on return value from intel_context_create - Set last request to NULL on unpin Signed-off-by: Matthew Brost Reviewed-by: John Harrison Signed-off-by: John Harrison Link: https://patchwork.freedesktop.org/patch/msgid/20211222223532.28698-1-matthew.brost@intel.com --- drivers/gpu/drm/i915/gem/i915_gem_context.c | 11 ++++--- drivers/gpu/drm/i915/gt/intel_context.c | 4 +-- .../gpu/drm/i915/gt/intel_execlists_submission.c | 38 ++++++++++++++++++++++ drivers/gpu/drm/i915/gt/intel_lrc.c | 4 +++ drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c | 2 -- 5 files changed, 51 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gem/i915_gem_context.c b/drivers/gpu/drm/i915/gem/i915_gem_context.c index 1f8d7bb32d39..db4d03a35719 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_context.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_context.c @@ -570,10 +570,6 @@ set_proto_ctx_engines_parallel_submit(struct i915_user_extension __user *base, struct intel_engine_cs **siblings = NULL; intel_engine_mask_t prev_mask; - /* FIXME: This is NIY for execlists */ - if (!(intel_uc_uses_guc_submission(&to_gt(i915)->uc))) - return -ENODEV; - if (get_user(slot, &ext->engine_index)) return -EFAULT; @@ -583,6 +579,13 @@ set_proto_ctx_engines_parallel_submit(struct i915_user_extension __user *base, if (get_user(num_siblings, &ext->num_siblings)) return -EFAULT; + if (!intel_uc_uses_guc_submission(&to_gt(i915)->uc) && + num_siblings != 1) { + drm_dbg(&i915->drm, "Only 1 sibling (%d) supported in non-GuC mode\n", + num_siblings); + return -EINVAL; + } + if (slot >= set->num_engines) { drm_dbg(&i915->drm, "Invalid placement value, %d >= %d\n", slot, set->num_engines); diff --git a/drivers/gpu/drm/i915/gt/intel_context.c b/drivers/gpu/drm/i915/gt/intel_context.c index ba083d800a08..5d0ec7c49b6a 100644 --- a/drivers/gpu/drm/i915/gt/intel_context.c +++ b/drivers/gpu/drm/i915/gt/intel_context.c @@ -79,7 +79,8 @@ static int intel_context_active_acquire(struct intel_context *ce) __i915_active_acquire(&ce->active); - if (intel_context_is_barrier(ce) || intel_engine_uses_guc(ce->engine)) + if (intel_context_is_barrier(ce) || intel_engine_uses_guc(ce->engine) || + intel_context_is_parallel(ce)) return 0; /* Preallocate tracking nodes */ @@ -563,7 +564,6 @@ void intel_context_bind_parent_child(struct intel_context *parent, * Callers responsibility to validate that this function is used * correctly but we use GEM_BUG_ON here ensure that they do. */ - GEM_BUG_ON(!intel_engine_uses_guc(parent->engine)); GEM_BUG_ON(intel_context_is_pinned(parent)); GEM_BUG_ON(intel_context_is_child(parent)); GEM_BUG_ON(intel_context_is_pinned(child)); diff --git a/drivers/gpu/drm/i915/gt/intel_execlists_submission.c b/drivers/gpu/drm/i915/gt/intel_execlists_submission.c index a69df5e9e77a..be56d0b41892 100644 --- a/drivers/gpu/drm/i915/gt/intel_execlists_submission.c +++ b/drivers/gpu/drm/i915/gt/intel_execlists_submission.c @@ -2599,6 +2599,43 @@ static void execlists_context_cancel_request(struct intel_context *ce, current->comm); } +static struct intel_context * +execlists_create_parallel(struct intel_engine_cs **engines, + unsigned int num_siblings, + unsigned int width) +{ + struct intel_context *parent = NULL, *ce, *err; + int i; + + GEM_BUG_ON(num_siblings != 1); + + for (i = 0; i < width; ++i) { + ce = intel_context_create(engines[i]); + if (IS_ERR(ce)) { + err = ce; + goto unwind; + } + + if (i == 0) + parent = ce; + else + intel_context_bind_parent_child(parent, ce); + } + + parent->parallel.fence_context = dma_fence_context_alloc(1); + + intel_context_set_nopreempt(parent); + for_each_child(parent, ce) + intel_context_set_nopreempt(ce); + + return parent; + +unwind: + if (parent) + intel_context_put(parent); + return err; +} + static const struct intel_context_ops execlists_context_ops = { .flags = COPS_HAS_INFLIGHT, @@ -2617,6 +2654,7 @@ static const struct intel_context_ops execlists_context_ops = { .reset = lrc_reset, .destroy = lrc_destroy, + .create_parallel = execlists_create_parallel, .create_virtual = execlists_create_virtual, }; diff --git a/drivers/gpu/drm/i915/gt/intel_lrc.c b/drivers/gpu/drm/i915/gt/intel_lrc.c index b3489599e4de..84456ffeb4cd 100644 --- a/drivers/gpu/drm/i915/gt/intel_lrc.c +++ b/drivers/gpu/drm/i915/gt/intel_lrc.c @@ -1065,6 +1065,10 @@ lrc_pin(struct intel_context *ce, void lrc_unpin(struct intel_context *ce) { + if (unlikely(ce->parallel.last_rq)) { + i915_request_put(ce->parallel.last_rq); + ce->parallel.last_rq = NULL; + } check_redzone((void *)ce->lrc_reg_state - LRC_STATE_OFFSET, ce->engine); } diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c index 0fbf24b8d5e1..06ebcea3c6a2 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c @@ -3248,8 +3248,6 @@ static void guc_parent_context_unpin(struct intel_context *ce) GEM_BUG_ON(!intel_context_is_parent(ce)); GEM_BUG_ON(!intel_engine_is_virtual(ce->engine)); - if (ce->parallel.last_rq) - i915_request_put(ce->parallel.last_rq); unpin_guc_id(guc, ce); lrc_unpin(ce); } -- cgit From c3c2ac4c77ee2191ae2f1e8d140a59e852104e3b Mon Sep 17 00:00:00 2001 From: John Harrison Date: Tue, 21 Dec 2021 13:02:12 -0800 Subject: drm/i915/guc: Check for wedged before doing stuff A fault injection probe test hit a BUG_ON in a GuC error path. It showed that the GuC code could potentially attempt to do many things when the device is actually wedged. So, add a check in to prevent that. v2: Use intel_gt_is_wedged instead of testing bits directly in the GuC submission code (review feedback from Tvrtko). Signed-off-by: John Harrison Reviewed-by: Matthew Brost Link: https://patchwork.freedesktop.org/patch/msgid/20211221210212.1438670-1-John.C.Harrison@Intel.com --- drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c index 06ebcea3c6a2..9989d121127d 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c @@ -1349,7 +1349,8 @@ submission_disabled(struct intel_guc *guc) struct i915_sched_engine * const sched_engine = guc->sched_engine; return unlikely(!sched_engine || - !__tasklet_is_enabled(&sched_engine->tasklet)); + !__tasklet_is_enabled(&sched_engine->tasklet) || + intel_gt_is_wedged(guc_to_gt(guc))); } static void disable_submission(struct intel_guc *guc) @@ -1725,7 +1726,7 @@ void intel_guc_submission_reset_finish(struct intel_guc *guc) { /* Reset called during driver load or during wedge? */ if (unlikely(!guc_submission_initialized(guc) || - test_bit(I915_WEDGED, &guc_to_gt(guc)->reset.flags))) { + intel_gt_is_wedged(guc_to_gt(guc)))) { return; } -- cgit From cbffbac9c14220b8716b0a9c29d72243f6b14ef3 Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Sat, 18 Dec 2021 16:19:09 -0800 Subject: drm/i915: Fix possible uninitialized variable in parallel extension 'prev_engine' was declared inside the output loop and checked in the inner after at least 1 pass of either loop. The variable should be declared outside both loops as it needs to be persistent across the entire loop structure. Fixes: e5e32171a2cf ("drm/i915/guc: Connect UAPI to GuC multi-lrc interface") Signed-off-by: Matthew Brost Reviewed-by: Lucas De Marchi Signed-off-by: John Harrison Link: https://patchwork.freedesktop.org/patch/msgid/20211219001909.24348-1-matthew.brost@intel.com --- drivers/gpu/drm/i915/gem/i915_gem_context.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gem/i915_gem_context.c b/drivers/gpu/drm/i915/gem/i915_gem_context.c index db4d03a35719..7ff1d1e35534 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_context.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_context.c @@ -564,6 +564,7 @@ set_proto_ctx_engines_parallel_submit(struct i915_user_extension __user *base, container_of_user(base, typeof(*ext), base); const struct set_proto_ctx_engines *set = data; struct drm_i915_private *i915 = set->i915; + struct i915_engine_class_instance prev_engine; u64 flags; int err = 0, n, i, j; u16 slot, width, num_siblings; @@ -632,7 +633,6 @@ set_proto_ctx_engines_parallel_submit(struct i915_user_extension __user *base, /* Create contexts / engines */ for (i = 0; i < width; ++i) { intel_engine_mask_t current_mask = 0; - struct i915_engine_class_instance prev_engine; for (j = 0; j < num_siblings; ++j) { struct i915_engine_class_instance ci; -- cgit From 62eeb9ae1364cd96991ccc6e3c5c69d66b8c64df Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Tue, 14 Dec 2021 11:59:13 -0800 Subject: drm/i915: Increment composite fence seqno Increment composite fence seqno on each fence creation. Fixes: 544460c33821 ("drm/i915: Multi-BB execbuf") Signed-off-by: Matthew Brost Reviewed-by: John Harrison Signed-off-by: John Harrison Link: https://patchwork.freedesktop.org/patch/msgid/20211214195913.35735-1-matthew.brost@intel.com --- drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c index e7f548a22970..3a5b247be738 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c @@ -3150,7 +3150,7 @@ eb_composite_fence_create(struct i915_execbuffer *eb, int out_fence_fd) fence_array = dma_fence_array_create(eb->num_batches, fences, eb->context->parallel.fence_context, - eb->context->parallel.seqno, + eb->context->parallel.seqno++, false); if (!fence_array) { kfree(fences); -- cgit From 6edf615618b8259f16eeb1df98f0ba0d2312c22e Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Wed, 29 Dec 2021 09:58:44 +0100 Subject: drm/bridge: sn65dsi83: Fix an error handling path in sn65dsi83_probe() sn65dsi83_parse_dt() takes a reference on 'ctx->host_node' that must be released in the error handling path of this function and of the probe. This is only done in the remove function up to now. Fixes: ceb515ba29ba ("drm/bridge: ti-sn65dsi83: Add TI SN65DSI83 and SN65DSI84 driver") Signed-off-by: Christophe JAILLET Reviewed-by: Laurent Pinchart Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/4bc21aed4b60d3d5ac4b28d8b07a6fdd8da6a536.1640768126.git.christophe.jaillet@wanadoo.fr --- drivers/gpu/drm/bridge/ti-sn65dsi83.c | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c index 5650a793db81..19daaddd29a4 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi83.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi83.c @@ -573,10 +573,14 @@ static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model) ctx->host_node = of_graph_get_remote_port_parent(endpoint); of_node_put(endpoint); - if (ctx->dsi_lanes < 0 || ctx->dsi_lanes > 4) - return -EINVAL; - if (!ctx->host_node) - return -ENODEV; + if (ctx->dsi_lanes < 0 || ctx->dsi_lanes > 4) { + ret = -EINVAL; + goto err_put_node; + } + if (!ctx->host_node) { + ret = -ENODEV; + goto err_put_node; + } ctx->lvds_dual_link = false; ctx->lvds_dual_link_even_odd_swap = false; @@ -603,11 +607,13 @@ static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model) ret = drm_of_find_panel_or_bridge(dev->of_node, 2, 0, &panel, &panel_bridge); if (ret < 0) - return ret; + goto err_put_node; if (panel) { panel_bridge = devm_drm_panel_bridge_add(dev, panel); - if (IS_ERR(panel_bridge)) - return PTR_ERR(panel_bridge); + if (IS_ERR(panel_bridge)) { + ret = PTR_ERR(panel_bridge); + goto err_put_node; + } } ctx->panel_bridge = panel_bridge; @@ -618,6 +624,10 @@ static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model) "Failed to get supply 'vcc'\n"); return 0; + +err_put_node: + of_node_put(ctx->host_node); + return ret; } static int sn65dsi83_host_attach(struct sn65dsi83 *ctx) @@ -692,8 +702,10 @@ static int sn65dsi83_probe(struct i2c_client *client, return ret; ctx->regmap = devm_regmap_init_i2c(client, &sn65dsi83_regmap_config); - if (IS_ERR(ctx->regmap)) - return PTR_ERR(ctx->regmap); + if (IS_ERR(ctx->regmap)) { + ret = PTR_ERR(ctx->regmap); + goto err_put_node; + } dev_set_drvdata(dev, ctx); i2c_set_clientdata(client, ctx); @@ -710,6 +722,8 @@ static int sn65dsi83_probe(struct i2c_client *client, err_remove_bridge: drm_bridge_remove(&ctx->bridge); +err_put_node: + of_node_put(ctx->host_node); return ret; } -- cgit From 04b8a5d9cfd171f65df75f444b5617a372649edd Mon Sep 17 00:00:00 2001 From: Martin Blumenstingl Date: Fri, 31 Dec 2021 00:55:14 +0100 Subject: drm/meson: osd_afbcd: Add an exit callback to struct meson_afbcd_ops Use this to simplify the driver shutdown. It will also come handy when fixing the error handling in meson_drv_bind_master(). Signed-off-by: Martin Blumenstingl Fixes: d1b5e41e13a7e9 ("drm/meson: Add AFBCD module driver") Acked-by: Neil Armstrong Signed-off-by: Neil Armstrong Link: https://patchwork.freedesktop.org/patch/msgid/20211230235515.1627522-2-martin.blumenstingl@googlemail.com --- drivers/gpu/drm/meson/meson_drv.c | 6 ++--- drivers/gpu/drm/meson/meson_osd_afbcd.c | 41 ++++++++++++++++++++++----------- drivers/gpu/drm/meson/meson_osd_afbcd.h | 1 + 3 files changed, 30 insertions(+), 18 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/meson/meson_drv.c b/drivers/gpu/drm/meson/meson_drv.c index 80f1d439841a..b919271a6e50 100644 --- a/drivers/gpu/drm/meson/meson_drv.c +++ b/drivers/gpu/drm/meson/meson_drv.c @@ -385,10 +385,8 @@ static void meson_drv_unbind(struct device *dev) free_irq(priv->vsync_irq, drm); drm_dev_put(drm); - if (priv->afbcd.ops) { - priv->afbcd.ops->reset(priv); - meson_rdma_free(priv); - } + if (priv->afbcd.ops) + priv->afbcd.ops->exit(priv); } static const struct component_master_ops meson_drv_master_ops = { diff --git a/drivers/gpu/drm/meson/meson_osd_afbcd.c b/drivers/gpu/drm/meson/meson_osd_afbcd.c index ffc6b584dbf8..0cdbe899402f 100644 --- a/drivers/gpu/drm/meson/meson_osd_afbcd.c +++ b/drivers/gpu/drm/meson/meson_osd_afbcd.c @@ -79,11 +79,6 @@ static bool meson_gxm_afbcd_supported_fmt(u64 modifier, uint32_t format) return meson_gxm_afbcd_pixel_fmt(modifier, format) >= 0; } -static int meson_gxm_afbcd_init(struct meson_drm *priv) -{ - return 0; -} - static int meson_gxm_afbcd_reset(struct meson_drm *priv) { writel_relaxed(VIU_SW_RESET_OSD1_AFBCD, @@ -93,6 +88,16 @@ static int meson_gxm_afbcd_reset(struct meson_drm *priv) return 0; } +static int meson_gxm_afbcd_init(struct meson_drm *priv) +{ + return 0; +} + +static void meson_gxm_afbcd_exit(struct meson_drm *priv) +{ + meson_gxm_afbcd_reset(priv); +} + static int meson_gxm_afbcd_enable(struct meson_drm *priv) { writel_relaxed(FIELD_PREP(OSD1_AFBCD_ID_FIFO_THRD, 0x40) | @@ -172,6 +177,7 @@ static int meson_gxm_afbcd_setup(struct meson_drm *priv) struct meson_afbcd_ops meson_afbcd_gxm_ops = { .init = meson_gxm_afbcd_init, + .exit = meson_gxm_afbcd_exit, .reset = meson_gxm_afbcd_reset, .enable = meson_gxm_afbcd_enable, .disable = meson_gxm_afbcd_disable, @@ -269,6 +275,18 @@ static bool meson_g12a_afbcd_supported_fmt(u64 modifier, uint32_t format) return meson_g12a_afbcd_pixel_fmt(modifier, format) >= 0; } +static int meson_g12a_afbcd_reset(struct meson_drm *priv) +{ + meson_rdma_reset(priv); + + meson_rdma_writel_sync(priv, VIU_SW_RESET_G12A_AFBC_ARB | + VIU_SW_RESET_G12A_OSD1_AFBCD, + VIU_SW_RESET); + meson_rdma_writel_sync(priv, 0, VIU_SW_RESET); + + return 0; +} + static int meson_g12a_afbcd_init(struct meson_drm *priv) { int ret; @@ -286,16 +304,10 @@ static int meson_g12a_afbcd_init(struct meson_drm *priv) return 0; } -static int meson_g12a_afbcd_reset(struct meson_drm *priv) +static void meson_g12a_afbcd_exit(struct meson_drm *priv) { - meson_rdma_reset(priv); - - meson_rdma_writel_sync(priv, VIU_SW_RESET_G12A_AFBC_ARB | - VIU_SW_RESET_G12A_OSD1_AFBCD, - VIU_SW_RESET); - meson_rdma_writel_sync(priv, 0, VIU_SW_RESET); - - return 0; + meson_g12a_afbcd_reset(priv); + meson_rdma_free(priv); } static int meson_g12a_afbcd_enable(struct meson_drm *priv) @@ -380,6 +392,7 @@ static int meson_g12a_afbcd_setup(struct meson_drm *priv) struct meson_afbcd_ops meson_afbcd_g12a_ops = { .init = meson_g12a_afbcd_init, + .exit = meson_g12a_afbcd_exit, .reset = meson_g12a_afbcd_reset, .enable = meson_g12a_afbcd_enable, .disable = meson_g12a_afbcd_disable, diff --git a/drivers/gpu/drm/meson/meson_osd_afbcd.h b/drivers/gpu/drm/meson/meson_osd_afbcd.h index 5e5523304f42..e77ddeb6416f 100644 --- a/drivers/gpu/drm/meson/meson_osd_afbcd.h +++ b/drivers/gpu/drm/meson/meson_osd_afbcd.h @@ -14,6 +14,7 @@ struct meson_afbcd_ops { int (*init)(struct meson_drm *priv); + void (*exit)(struct meson_drm *priv); int (*reset)(struct meson_drm *priv); int (*enable)(struct meson_drm *priv); int (*disable)(struct meson_drm *priv); -- cgit From fa747d75f65d1b1cbc3f4691fa67b695e8a399c8 Mon Sep 17 00:00:00 2001 From: Martin Blumenstingl Date: Fri, 31 Dec 2021 00:55:15 +0100 Subject: drm/meson: Fix error handling when afbcd.ops->init fails When afbcd.ops->init fails we need to free the struct drm_device. Also all errors which come after afbcd.ops->init was successful need to exit the AFBCD, just like meson_drv_unbind() does. Fixes: d1b5e41e13a7e9 ("drm/meson: Add AFBCD module driver") Signed-off-by: Martin Blumenstingl Acked-by: Neil Armstrong Signed-off-by: Neil Armstrong Link: https://patchwork.freedesktop.org/patch/msgid/20211230235515.1627522-3-martin.blumenstingl@googlemail.com --- drivers/gpu/drm/meson/meson_drv.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/meson/meson_drv.c b/drivers/gpu/drm/meson/meson_drv.c index b919271a6e50..26aeaf0ab86e 100644 --- a/drivers/gpu/drm/meson/meson_drv.c +++ b/drivers/gpu/drm/meson/meson_drv.c @@ -302,42 +302,42 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) if (priv->afbcd.ops) { ret = priv->afbcd.ops->init(priv); if (ret) - return ret; + goto free_drm; } /* Encoder Initialization */ ret = meson_encoder_cvbs_init(priv); if (ret) - goto free_drm; + goto exit_afbcd; if (has_components) { ret = component_bind_all(drm->dev, drm); if (ret) { dev_err(drm->dev, "Couldn't bind all components\n"); - goto free_drm; + goto exit_afbcd; } } ret = meson_encoder_hdmi_init(priv); if (ret) - goto free_drm; + goto exit_afbcd; ret = meson_plane_create(priv); if (ret) - goto free_drm; + goto exit_afbcd; ret = meson_overlay_create(priv); if (ret) - goto free_drm; + goto exit_afbcd; ret = meson_crtc_create(priv); if (ret) - goto free_drm; + goto exit_afbcd; ret = request_irq(priv->vsync_irq, meson_irq, 0, drm->driver->name, drm); if (ret) - goto free_drm; + goto exit_afbcd; drm_mode_config_reset(drm); @@ -355,6 +355,9 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) uninstall_irq: free_irq(priv->vsync_irq, drm); +exit_afbcd: + if (priv->afbcd.ops) + priv->afbcd.ops->exit(priv); free_drm: drm_dev_put(drm); -- cgit From 22f99f2db44771cf9569fc2e8c547ad3153d9384 Mon Sep 17 00:00:00 2001 From: Antonio Borneo Date: Sat, 18 Dec 2021 22:50:52 +0100 Subject: drm/stm: dsi: move lane capability detection in probe() There is no need to re-compute the dsi lane capability because it only depends on dsi hw version. Since dsi hw version is detected at probe(), move there also the assignment of dsi lane capability. Signed-off-by: Antonio Borneo Acked-by: Philippe Cornu Reviewed-by: Philippe Cornu Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20211218215055.212421-1-antonio.borneo@foss.st.com --- drivers/gpu/drm/stm/dw_mipi_dsi-stm.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/stm/dw_mipi_dsi-stm.c b/drivers/gpu/drm/stm/dw_mipi_dsi-stm.c index 32cb41b2202f..480fdf256f01 100644 --- a/drivers/gpu/drm/stm/dw_mipi_dsi-stm.c +++ b/drivers/gpu/drm/stm/dw_mipi_dsi-stm.c @@ -247,14 +247,6 @@ dw_mipi_dsi_get_lane_mbps(void *priv_data, const struct drm_display_mode *mode, int ret, bpp; u32 val; - /* Update lane capabilities according to hw version */ - dsi->lane_min_kbps = LANE_MIN_KBPS; - dsi->lane_max_kbps = LANE_MAX_KBPS; - if (dsi->hw_version == HWVER_131) { - dsi->lane_min_kbps *= 2; - dsi->lane_max_kbps *= 2; - } - pll_in_khz = (unsigned int)(clk_get_rate(dsi->pllref_clk) / 1000); /* Compute requested pll out */ @@ -417,6 +409,14 @@ static int dw_mipi_dsi_stm_probe(struct platform_device *pdev) goto err_dsi_probe; } + /* set lane capabilities according to hw version */ + dsi->lane_min_kbps = LANE_MIN_KBPS; + dsi->lane_max_kbps = LANE_MAX_KBPS; + if (dsi->hw_version == HWVER_131) { + dsi->lane_min_kbps *= 2; + dsi->lane_max_kbps *= 2; + } + dw_mipi_dsi_stm_plat_data.base = dsi->base; dw_mipi_dsi_stm_plat_data.priv_data = dsi; -- cgit From 5f4f958a0d9dfd7a569c56e76840e39b0c434378 Mon Sep 17 00:00:00 2001 From: Antonio Borneo Date: Sat, 18 Dec 2021 22:50:53 +0100 Subject: drm/bridge/synopsys: dsi: extend the prototype of mode_valid() To evaluate the validity of a video mode, some additional internal value has to be passed to the platform implementation. Extend the prototype of mode_valid(). Signed-off-by: Antonio Borneo Reviewed-by: Philippe Cornu Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20211218215055.212421-2-antonio.borneo@foss.st.com --- drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c index e44e18a0112a..3f6564762e24 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c @@ -998,7 +998,10 @@ dw_mipi_dsi_bridge_mode_valid(struct drm_bridge *bridge, enum drm_mode_status mode_status = MODE_OK; if (pdata->mode_valid) - mode_status = pdata->mode_valid(pdata->priv_data, mode); + mode_status = pdata->mode_valid(pdata->priv_data, mode, + dsi->mode_flags, + dw_mipi_dsi_get_lanes(dsi), + dsi->format); return mode_status; } -- cgit From e01356d18273bd52bc88179f907b9d12978ebddd Mon Sep 17 00:00:00 2001 From: Antonio Borneo Date: Sat, 18 Dec 2021 22:50:54 +0100 Subject: drm/stm: dsi: provide the implementation of mode_valid() The dsi has several constraints on the video modes it can support, mainly due to the frequencies that can be generated by the PLL integrated in the DSI device. Verify that the required HS clock can be generated by the PLL. The dsi clock from the dsi PLL and the ltdc pixel clock are asynchronous. The dsi needs to return in LP mode during HFP or HBP to re-synchronize at each video line. Verify that the duration of HFP and HBP allows the dsi to enter in LP mode. Signed-off-by: Antonio Borneo Acked-by: Philippe Cornu Reviewed-by: Philippe Cornu Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20211218215055.212421-3-antonio.borneo@foss.st.com --- drivers/gpu/drm/stm/dw_mipi_dsi-stm.c | 98 +++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/stm/dw_mipi_dsi-stm.c b/drivers/gpu/drm/stm/dw_mipi_dsi-stm.c index 480fdf256f01..89897d5f5c72 100644 --- a/drivers/gpu/drm/stm/dw_mipi_dsi-stm.c +++ b/drivers/gpu/drm/stm/dw_mipi_dsi-stm.c @@ -322,6 +322,103 @@ dw_mipi_dsi_phy_get_timing(void *priv_data, unsigned int lane_mbps, return 0; } +#define CLK_TOLERANCE_HZ 50 + +static enum drm_mode_status +dw_mipi_dsi_stm_mode_valid(void *priv_data, + const struct drm_display_mode *mode, + unsigned long mode_flags, u32 lanes, u32 format) +{ + struct dw_mipi_dsi_stm *dsi = priv_data; + unsigned int idf, ndiv, odf, pll_in_khz, pll_out_khz; + int ret, bpp; + + bpp = mipi_dsi_pixel_format_to_bpp(format); + if (bpp < 0) + return MODE_BAD; + + /* Compute requested pll out */ + pll_out_khz = mode->clock * bpp / lanes; + + if (pll_out_khz > dsi->lane_max_kbps) + return MODE_CLOCK_HIGH; + + if (mode_flags & MIPI_DSI_MODE_VIDEO_BURST) { + /* Add 20% to pll out to be higher than pixel bw */ + pll_out_khz = (pll_out_khz * 12) / 10; + } else { + if (pll_out_khz < dsi->lane_min_kbps) + return MODE_CLOCK_LOW; + } + + /* Compute best pll parameters */ + idf = 0; + ndiv = 0; + odf = 0; + pll_in_khz = clk_get_rate(dsi->pllref_clk) / 1000; + ret = dsi_pll_get_params(dsi, pll_in_khz, pll_out_khz, &idf, &ndiv, &odf); + if (ret) { + DRM_WARN("Warning dsi_pll_get_params(): bad params\n"); + return MODE_ERROR; + } + + if (!(mode_flags & MIPI_DSI_MODE_VIDEO_BURST)) { + unsigned int px_clock_hz, target_px_clock_hz, lane_mbps; + int dsi_short_packet_size_px, hfp, hsync, hbp, delay_to_lp; + struct dw_mipi_dsi_dphy_timing dphy_timing; + + /* Get the adjusted pll out value */ + pll_out_khz = dsi_pll_get_clkout_khz(pll_in_khz, idf, ndiv, odf); + + px_clock_hz = DIV_ROUND_CLOSEST_ULL(1000ULL * pll_out_khz * lanes, bpp); + target_px_clock_hz = mode->clock * 1000; + /* + * Filter modes according to the clock value, particularly useful for + * hdmi modes that require precise pixel clocks. + */ + if (px_clock_hz < target_px_clock_hz - CLK_TOLERANCE_HZ || + px_clock_hz > target_px_clock_hz + CLK_TOLERANCE_HZ) + return MODE_CLOCK_RANGE; + + /* sync packets are codes as DSI short packets (4 bytes) */ + dsi_short_packet_size_px = DIV_ROUND_UP(4 * BITS_PER_BYTE, bpp); + + hfp = mode->hsync_start - mode->hdisplay; + hsync = mode->hsync_end - mode->hsync_start; + hbp = mode->htotal - mode->hsync_end; + + /* hsync must be longer than 4 bytes HSS packets */ + if (hsync < dsi_short_packet_size_px) + return MODE_HSYNC_NARROW; + + if (mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { + /* HBP must be longer than 4 bytes HSE packets */ + if (hbp < dsi_short_packet_size_px) + return MODE_HSYNC_NARROW; + hbp -= dsi_short_packet_size_px; + } else { + /* With sync events HBP extends in the hsync */ + hbp += hsync - dsi_short_packet_size_px; + } + + lane_mbps = pll_out_khz / 1000; + ret = dw_mipi_dsi_phy_get_timing(priv_data, lane_mbps, &dphy_timing); + if (ret) + return MODE_ERROR; + /* + * In non-burst mode DSI has to enter in LP during HFP + * (horizontal front porch) or HBP (horizontal back porch) to + * resync with LTDC pixel clock. + */ + delay_to_lp = DIV_ROUND_UP((dphy_timing.data_hs2lp + dphy_timing.data_lp2hs) * + lanes * BITS_PER_BYTE, bpp); + if (hfp < delay_to_lp && hbp < delay_to_lp) + return MODE_HSYNC; + } + + return MODE_OK; +} + static const struct dw_mipi_dsi_phy_ops dw_mipi_dsi_stm_phy_ops = { .init = dw_mipi_dsi_phy_init, .power_on = dw_mipi_dsi_phy_power_on, @@ -332,6 +429,7 @@ static const struct dw_mipi_dsi_phy_ops dw_mipi_dsi_stm_phy_ops = { static struct dw_mipi_dsi_plat_data dw_mipi_dsi_stm_plat_data = { .max_data_lanes = 2, + .mode_valid = dw_mipi_dsi_stm_mode_valid, .phy_ops = &dw_mipi_dsi_stm_phy_ops, }; -- cgit From 3b26a2916844cf2680de10206e193e1d13f0eb88 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Fri, 19 Nov 2021 20:23:24 +0530 Subject: drm/bridge: chipone-icn6211: Switch to atomic operations Replace atomic version of the pre_enable/enable/post_disable operations to continue the transition to the atomic API. Also added default drm atomic operations for duplicate, destroy and reset state API's in order to have smooth transition on atomic API's. Tested on Allwinner R16/R40 DSI. Signed-off-by: Jagan Teki Reviewed-by: Robert Foss Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20211119145325.1775046-1-jagan@amarulasolutions.com --- drivers/gpu/drm/bridge/chipone-icn6211.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/chipone-icn6211.c b/drivers/gpu/drm/bridge/chipone-icn6211.c index a6151db95586..77b3e2c29461 100644 --- a/drivers/gpu/drm/bridge/chipone-icn6211.c +++ b/drivers/gpu/drm/bridge/chipone-icn6211.c @@ -4,6 +4,7 @@ * Author: Jagan Teki */ +#include #include #include #include @@ -61,7 +62,8 @@ static inline int chipone_dsi_write(struct chipone *icn, const void *seq, chipone_dsi_write(icn, d, ARRAY_SIZE(d)); \ } -static void chipone_enable(struct drm_bridge *bridge) +static void chipone_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) { struct chipone *icn = bridge_to_chipone(bridge); struct drm_display_mode *mode = bridge_to_mode(bridge); @@ -114,7 +116,8 @@ static void chipone_enable(struct drm_bridge *bridge) usleep_range(10000, 11000); } -static void chipone_pre_enable(struct drm_bridge *bridge) +static void chipone_atomic_pre_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) { struct chipone *icn = bridge_to_chipone(bridge); int ret; @@ -145,7 +148,8 @@ static void chipone_pre_enable(struct drm_bridge *bridge) usleep_range(10000, 11000); } -static void chipone_post_disable(struct drm_bridge *bridge) +static void chipone_atomic_post_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) { struct chipone *icn = bridge_to_chipone(bridge); @@ -169,10 +173,13 @@ static int chipone_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flag } static const struct drm_bridge_funcs chipone_bridge_funcs = { - .attach = chipone_attach, - .post_disable = chipone_post_disable, - .pre_enable = chipone_pre_enable, - .enable = chipone_enable, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_pre_enable = chipone_atomic_pre_enable, + .atomic_enable = chipone_atomic_enable, + .atomic_post_disable = chipone_atomic_post_disable, + .attach = chipone_attach, }; static int chipone_parse_dt(struct chipone *icn) -- cgit From 50d76e3d3ec0c18dbe2c801af676f7e7cbedbff0 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Fri, 19 Nov 2021 20:23:25 +0530 Subject: drm/bridge: chipone-icn6211: Add mode_set API Get the display mode settings via mode_set bridge function instead of explicitly de-reference. Signed-off-by: Jagan Teki Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20211119145325.1775046-2-jagan@amarulasolutions.com Reviewed-by: Robert Foss --- drivers/gpu/drm/bridge/chipone-icn6211.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/chipone-icn6211.c b/drivers/gpu/drm/bridge/chipone-icn6211.c index 77b3e2c29461..e8f36dca56b3 100644 --- a/drivers/gpu/drm/bridge/chipone-icn6211.c +++ b/drivers/gpu/drm/bridge/chipone-icn6211.c @@ -31,6 +31,7 @@ struct chipone { struct device *dev; struct drm_bridge bridge; + struct drm_display_mode mode; struct drm_bridge *panel_bridge; struct gpio_desc *enable_gpio; struct regulator *vdd1; @@ -43,11 +44,6 @@ static inline struct chipone *bridge_to_chipone(struct drm_bridge *bridge) return container_of(bridge, struct chipone, bridge); } -static struct drm_display_mode *bridge_to_mode(struct drm_bridge *bridge) -{ - return &bridge->encoder->crtc->state->adjusted_mode; -} - static inline int chipone_dsi_write(struct chipone *icn, const void *seq, size_t len) { @@ -66,7 +62,7 @@ static void chipone_atomic_enable(struct drm_bridge *bridge, struct drm_bridge_state *old_bridge_state) { struct chipone *icn = bridge_to_chipone(bridge); - struct drm_display_mode *mode = bridge_to_mode(bridge); + struct drm_display_mode *mode = &icn->mode; ICN6211_DSI(icn, 0x7a, 0xc1); @@ -165,6 +161,15 @@ static void chipone_atomic_post_disable(struct drm_bridge *bridge, gpiod_set_value(icn->enable_gpio, 0); } +static void chipone_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct chipone *icn = bridge_to_chipone(bridge); + + drm_mode_copy(&icn->mode, adjusted_mode); +} + static int chipone_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags) { struct chipone *icn = bridge_to_chipone(bridge); @@ -179,6 +184,7 @@ static const struct drm_bridge_funcs chipone_bridge_funcs = { .atomic_pre_enable = chipone_atomic_pre_enable, .atomic_enable = chipone_atomic_enable, .atomic_post_disable = chipone_atomic_post_disable, + .mode_set = chipone_mode_set, .attach = chipone_attach, }; -- cgit From 7c442e76c06cb1bef16a6c523487438175584eea Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Mon, 27 Dec 2021 09:25:22 +0000 Subject: drm/bridge: Fix free wrong object in sii8620_init_rcp_input_dev rc_dev is allocated by rc_allocate_device(), and doesn't assigned to ctx->rc_dev before calling rc_free_device(ctx->rc_dev). So it should call rc_free_device(rc_dev); Fixes: e25f1f7c94e1 ("drm/bridge/sii8620: add remote control support") Signed-off-by: Miaoqian Lin Reviewed-by: Robert Foss Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20211227092522.21755-1-linmq006@gmail.com --- drivers/gpu/drm/bridge/sil-sii8620.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/sil-sii8620.c b/drivers/gpu/drm/bridge/sil-sii8620.c index 843265d7f1b1..ec7745c31da0 100644 --- a/drivers/gpu/drm/bridge/sil-sii8620.c +++ b/drivers/gpu/drm/bridge/sil-sii8620.c @@ -2120,7 +2120,7 @@ static void sii8620_init_rcp_input_dev(struct sii8620 *ctx) if (ret) { dev_err(ctx->dev, "Failed to register RC device\n"); ctx->error = ret; - rc_free_device(ctx->rc_dev); + rc_free_device(rc_dev); return; } ctx->rc_dev = rc_dev; -- cgit From 77177c934c9a90002dbfead4067ba1f4ae0b5182 Mon Sep 17 00:00:00 2001 From: Yannick Fertre Date: Mon, 6 Dec 2021 14:47:35 +0100 Subject: drm/stm: remove conflicting framebuffers In case of using simplefb or another conflicting framebuffer, call drm_aperture_remove_framebuffers() to remove memory allocated. Signed-off-by: Yannick Fertre Reviewed-by: Thomas Zimmermann Acked-by: Philippe Cornu Signed-off-by: Philippe Cornu Link: https://patchwork.freedesktop.org/patch/msgid/20211206134735.13537-1-yannick.fertre@foss.st.com --- drivers/gpu/drm/stm/drv.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/stm/drv.c b/drivers/gpu/drm/stm/drv.c index 222869b232ae..9f441aadf2d5 100644 --- a/drivers/gpu/drm/stm/drv.c +++ b/drivers/gpu/drm/stm/drv.c @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -183,6 +184,10 @@ static int stm_drm_platform_probe(struct platform_device *pdev) DRM_DEBUG("%s\n", __func__); + ret = drm_aperture_remove_framebuffers(false, &drv_driver); + if (ret) + return ret; + dma_set_coherent_mask(dev, DMA_BIT_MASK(32)); ddev = drm_dev_alloc(&drv_driver, dev); -- cgit From 1726cee3d0537baa58dc58cf0a1630aa7ce93af7 Mon Sep 17 00:00:00 2001 From: Yannick Fertre Date: Fri, 3 Dec 2021 09:56:18 +0100 Subject: drm/stm: ltdc: support of new hardware version Add support of new hardware version 0x40100. Signed-off-by: Yannick Fertre Reviewed-by: Raphael Gallais-Pou Tested-by: Raphael Gallais-Pou Acked-by: Philippe Cornu Signed-off-by: Philippe Cornu Link: https://patchwork.freedesktop.org/patch/msgid/20211203085618.11314-1-yannick.fertre@foss.st.com --- drivers/gpu/drm/stm/ltdc.c | 172 +++++++++++++++++++++++++++++++++++++-------- drivers/gpu/drm/stm/ltdc.h | 3 +- 2 files changed, 145 insertions(+), 30 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/stm/ltdc.c b/drivers/gpu/drm/stm/ltdc.c index dbdee954692a..c0619f372630 100644 --- a/drivers/gpu/drm/stm/ltdc.c +++ b/drivers/gpu/drm/stm/ltdc.c @@ -46,15 +46,15 @@ #define HWVER_10200 0x010200 #define HWVER_10300 0x010300 #define HWVER_20101 0x020101 +#define HWVER_40100 0x040100 /* * The address of some registers depends on the HW version: such registers have - * an extra offset specified with reg_ofs. + * an extra offset specified with layer_ofs. */ -#define REG_OFS_NONE 0 -#define REG_OFS_4 4 /* Insertion of "Layer Conf. 2" reg */ -#define REG_OFS (ldev->caps.reg_ofs) -#define LAY_OFS 0x80 /* Register Offset between 2 layers */ +#define LAY_OFS_0 0x80 +#define LAY_OFS_1 0x100 +#define LAY_OFS (ldev->caps.layer_ofs) /* Global register offsets */ #define LTDC_IDR 0x0000 /* IDentification */ @@ -75,29 +75,34 @@ #define LTDC_LIPCR 0x0040 /* Line Interrupt Position Conf. */ #define LTDC_CPSR 0x0044 /* Current Position Status */ #define LTDC_CDSR 0x0048 /* Current Display Status */ +#define LTDC_FUT 0x0090 /* Fifo underrun Threshold */ /* Layer register offsets */ -#define LTDC_L1LC1R (0x80) /* L1 Layer Configuration 1 */ -#define LTDC_L1LC2R (0x84) /* L1 Layer Configuration 2 */ -#define LTDC_L1CR (0x84 + REG_OFS)/* L1 Control */ -#define LTDC_L1WHPCR (0x88 + REG_OFS)/* L1 Window Hor Position Config */ -#define LTDC_L1WVPCR (0x8C + REG_OFS)/* L1 Window Vert Position Config */ -#define LTDC_L1CKCR (0x90 + REG_OFS)/* L1 Color Keying Configuration */ -#define LTDC_L1PFCR (0x94 + REG_OFS)/* L1 Pixel Format Configuration */ -#define LTDC_L1CACR (0x98 + REG_OFS)/* L1 Constant Alpha Config */ -#define LTDC_L1DCCR (0x9C + REG_OFS)/* L1 Default Color Configuration */ -#define LTDC_L1BFCR (0xA0 + REG_OFS)/* L1 Blend Factors Configuration */ -#define LTDC_L1FBBCR (0xA4 + REG_OFS)/* L1 FrameBuffer Bus Control */ -#define LTDC_L1AFBCR (0xA8 + REG_OFS)/* L1 AuxFB Control */ -#define LTDC_L1CFBAR (0xAC + REG_OFS)/* L1 Color FrameBuffer Address */ -#define LTDC_L1CFBLR (0xB0 + REG_OFS)/* L1 Color FrameBuffer Length */ -#define LTDC_L1CFBLNR (0xB4 + REG_OFS)/* L1 Color FrameBuffer Line Nb */ -#define LTDC_L1AFBAR (0xB8 + REG_OFS)/* L1 AuxFB Address */ -#define LTDC_L1AFBLR (0xBC + REG_OFS)/* L1 AuxFB Length */ -#define LTDC_L1AFBLNR (0xC0 + REG_OFS)/* L1 AuxFB Line Number */ -#define LTDC_L1CLUTWR (0xC4 + REG_OFS)/* L1 CLUT Write */ -#define LTDC_L1YS1R (0xE0 + REG_OFS)/* L1 YCbCr Scale 1 */ -#define LTDC_L1YS2R (0xE4 + REG_OFS)/* L1 YCbCr Scale 2 */ +#define LTDC_L1C0R (ldev->caps.layer_regs[0]) /* L1 configuration 0 */ +#define LTDC_L1C1R (ldev->caps.layer_regs[1]) /* L1 configuration 1 */ +#define LTDC_L1RCR (ldev->caps.layer_regs[2]) /* L1 reload control */ +#define LTDC_L1CR (ldev->caps.layer_regs[3]) /* L1 control register */ +#define LTDC_L1WHPCR (ldev->caps.layer_regs[4]) /* L1 window horizontal position configuration */ +#define LTDC_L1WVPCR (ldev->caps.layer_regs[5]) /* L1 window vertical position configuration */ +#define LTDC_L1CKCR (ldev->caps.layer_regs[6]) /* L1 color keying configuration */ +#define LTDC_L1PFCR (ldev->caps.layer_regs[7]) /* L1 pixel format configuration */ +#define LTDC_L1CACR (ldev->caps.layer_regs[8]) /* L1 constant alpha configuration */ +#define LTDC_L1DCCR (ldev->caps.layer_regs[9]) /* L1 default color configuration */ +#define LTDC_L1BFCR (ldev->caps.layer_regs[10]) /* L1 blending factors configuration */ +#define LTDC_L1BLCR (ldev->caps.layer_regs[11]) /* L1 burst length configuration */ +#define LTDC_L1PCR (ldev->caps.layer_regs[12]) /* L1 planar configuration */ +#define LTDC_L1CFBAR (ldev->caps.layer_regs[13]) /* L1 color frame buffer address */ +#define LTDC_L1CFBLR (ldev->caps.layer_regs[14]) /* L1 color frame buffer length */ +#define LTDC_L1CFBLNR (ldev->caps.layer_regs[15]) /* L1 color frame buffer line number */ +#define LTDC_L1AFBA0R (ldev->caps.layer_regs[16]) /* L1 auxiliary frame buffer address 0 */ +#define LTDC_L1AFBA1R (ldev->caps.layer_regs[17]) /* L1 auxiliary frame buffer address 1 */ +#define LTDC_L1AFBLR (ldev->caps.layer_regs[18]) /* L1 auxiliary frame buffer length */ +#define LTDC_L1AFBLNR (ldev->caps.layer_regs[19]) /* L1 auxiliary frame buffer line number */ +#define LTDC_L1CLUTWR (ldev->caps.layer_regs[20]) /* L1 CLUT write */ +#define LTDC_L1CYR0R (ldev->caps.layer_regs[21]) /* L1 Conversion YCbCr RGB 0 */ +#define LTDC_L1CYR1R (ldev->caps.layer_regs[22]) /* L1 Conversion YCbCr RGB 1 */ +#define LTDC_L1FPF0R (ldev->caps.layer_regs[23]) /* L1 Flexible Pixel Format 0 */ +#define LTDC_L1FPF1R (ldev->caps.layer_regs[24]) /* L1 Flexible Pixel Format 1 */ /* Bit definitions */ #define SSCR_VSH GENMASK(10, 0) /* Vertical Synchronization Height */ @@ -208,7 +213,10 @@ enum ltdc_pix_fmt { /* Indexed formats */ PF_L8, /* Indexed 8 bits [8 bits] */ PF_AL44, /* Alpha:4 bits + indexed 4 bits [8 bits] */ - PF_AL88 /* Alpha:8 bits + indexed 8 bits [16 bits] */ + PF_AL88, /* Alpha:8 bits + indexed 8 bits [16 bits] */ + PF_ABGR8888, /* ABGR [32 bits] */ + PF_BGRA8888, /* BGRA [32 bits] */ + PF_BGR565 /* RGB [16 bits] */ }; /* The index gives the encoding of the pixel format for an HW version */ @@ -234,6 +242,102 @@ static const enum ltdc_pix_fmt ltdc_pix_fmt_a1[NB_PF] = { PF_ARGB4444 /* 0x07 */ }; +static const enum ltdc_pix_fmt ltdc_pix_fmt_a2[NB_PF] = { + PF_ARGB8888, /* 0x00 */ + PF_ABGR8888, /* 0x01 */ + PF_RGBA8888, /* 0x02 */ + PF_BGRA8888, /* 0x03 */ + PF_RGB565, /* 0x04 */ + PF_BGR565, /* 0x05 */ + PF_RGB888, /* 0x06 */ + PF_ARGB1555 /* 0x07 */ +}; + +/* Layer register offsets */ +static const u32 ltdc_layer_regs_a0[] = { + 0x80, /* L1 configuration 0 */ + 0x00, /* not available */ + 0x00, /* not available */ + 0x84, /* L1 control register */ + 0x88, /* L1 window horizontal position configuration */ + 0x8c, /* L1 window vertical position configuration */ + 0x90, /* L1 color keying configuration */ + 0x94, /* L1 pixel format configuration */ + 0x98, /* L1 constant alpha configuration */ + 0x9c, /* L1 default color configuration */ + 0xa0, /* L1 blending factors configuration */ + 0x00, /* not available */ + 0x00, /* not available */ + 0xac, /* L1 color frame buffer address */ + 0xb0, /* L1 color frame buffer length */ + 0xb4, /* L1 color frame buffer line number */ + 0x00, /* not available */ + 0x00, /* not available */ + 0x00, /* not available */ + 0x00, /* not available */ + 0xc4, /* L1 CLUT write */ + 0x00, /* not available */ + 0x00, /* not available */ + 0x00, /* not available */ + 0x00 /* not available */ +}; + +static const u32 ltdc_layer_regs_a1[] = { + 0x80, /* L1 configuration 0 */ + 0x84, /* L1 configuration 1 */ + 0x00, /* L1 reload control */ + 0x88, /* L1 control register */ + 0x8c, /* L1 window horizontal position configuration */ + 0x90, /* L1 window vertical position configuration */ + 0x94, /* L1 color keying configuration */ + 0x98, /* L1 pixel format configuration */ + 0x9c, /* L1 constant alpha configuration */ + 0xa0, /* L1 default color configuration */ + 0xa4, /* L1 blending factors configuration */ + 0xa8, /* L1 burst length configuration */ + 0x00, /* not available */ + 0xac, /* L1 color frame buffer address */ + 0xb0, /* L1 color frame buffer length */ + 0xb4, /* L1 color frame buffer line number */ + 0xb8, /* L1 auxiliary frame buffer address 0 */ + 0xbc, /* L1 auxiliary frame buffer address 1 */ + 0xc0, /* L1 auxiliary frame buffer length */ + 0xc4, /* L1 auxiliary frame buffer line number */ + 0xc8, /* L1 CLUT write */ + 0x00, /* not available */ + 0x00, /* not available */ + 0x00, /* not available */ + 0x00 /* not available */ +}; + +static const u32 ltdc_layer_regs_a2[] = { + 0x100, /* L1 configuration 0 */ + 0x104, /* L1 configuration 1 */ + 0x108, /* L1 reload control */ + 0x10c, /* L1 control register */ + 0x110, /* L1 window horizontal position configuration */ + 0x114, /* L1 window vertical position configuration */ + 0x118, /* L1 color keying configuration */ + 0x11c, /* L1 pixel format configuration */ + 0x120, /* L1 constant alpha configuration */ + 0x124, /* L1 default color configuration */ + 0x128, /* L1 blending factors configuration */ + 0x12c, /* L1 burst length configuration */ + 0x130, /* L1 planar configuration */ + 0x134, /* L1 color frame buffer address */ + 0x138, /* L1 color frame buffer length */ + 0x13c, /* L1 color frame buffer line number */ + 0x140, /* L1 auxiliary frame buffer address 0 */ + 0x144, /* L1 auxiliary frame buffer address 1 */ + 0x148, /* L1 auxiliary frame buffer length */ + 0x14c, /* L1 auxiliary frame buffer line number */ + 0x150, /* L1 CLUT write */ + 0x16c, /* L1 Conversion YCbCr RGB 0 */ + 0x170, /* L1 Conversion YCbCr RGB 1 */ + 0x174, /* L1 Flexible Pixel Format 0 */ + 0x178 /* L1 Flexible Pixel Format 1 */ +}; + static const u64 ltdc_format_modifiers[] = { DRM_FORMAT_MOD_LINEAR, DRM_FORMAT_MOD_INVALID @@ -1158,7 +1262,8 @@ static int ltdc_get_caps(struct drm_device *ddev) switch (ldev->caps.hw_version) { case HWVER_10200: case HWVER_10300: - ldev->caps.reg_ofs = REG_OFS_NONE; + ldev->caps.layer_ofs = LAY_OFS_0; + ldev->caps.layer_regs = ltdc_layer_regs_a0; ldev->caps.pix_fmt_hw = ltdc_pix_fmt_a0; /* * Hw older versions support non-alpha color formats derived @@ -1174,12 +1279,21 @@ static int ltdc_get_caps(struct drm_device *ddev) ldev->caps.nb_irq = 2; break; case HWVER_20101: - ldev->caps.reg_ofs = REG_OFS_4; + ldev->caps.layer_ofs = LAY_OFS_0; + ldev->caps.layer_regs = ltdc_layer_regs_a1; ldev->caps.pix_fmt_hw = ltdc_pix_fmt_a1; ldev->caps.non_alpha_only_l1 = false; ldev->caps.pad_max_freq_hz = 150000000; ldev->caps.nb_irq = 4; break; + case HWVER_40100: + ldev->caps.layer_ofs = LAY_OFS_1; + ldev->caps.layer_regs = ltdc_layer_regs_a2; + ldev->caps.pix_fmt_hw = ltdc_pix_fmt_a2; + ldev->caps.non_alpha_only_l1 = false; + ldev->caps.pad_max_freq_hz = 90000000; + ldev->caps.nb_irq = 2; + break; default: return -ENODEV; } diff --git a/drivers/gpu/drm/stm/ltdc.h b/drivers/gpu/drm/stm/ltdc.h index f153b908c70e..55a125f89af6 100644 --- a/drivers/gpu/drm/stm/ltdc.h +++ b/drivers/gpu/drm/stm/ltdc.h @@ -14,7 +14,8 @@ struct ltdc_caps { u32 hw_version; /* hardware version */ u32 nb_layers; /* number of supported layers */ - u32 reg_ofs; /* register offset for applicable regs */ + u32 layer_ofs; /* layer offset for applicable regs */ + const u32 *layer_regs; /* layer register offset */ u32 bus_width; /* bus width (32 or 64 bits) */ const u32 *pix_fmt_hw; /* supported pixel formats */ bool non_alpha_only_l1; /* non-native no-alpha formats on layer 1 */ -- cgit From 7020449b8f5ac0f7444a584645edec02f7168f1a Mon Sep 17 00:00:00 2001 From: Xin Ji Date: Fri, 19 Nov 2021 09:58:04 +0800 Subject: drm/bridge: anx7625: Check GPIO description to avoid crash As GPIO probe function "devm_gpiod_get_optional()" may return error code, driver should identify GPIO desc as NULL to avoid crash. Acked-by: Tzung-Bi Shih Signed-off-by: Xin Ji Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20211119015804.3824027-1-xji@analogixsemi.com Reviewed-by: Robert Foss --- drivers/gpu/drm/bridge/analogix/anx7625.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c index 2346dbcc505f..9765ac07f46d 100644 --- a/drivers/gpu/drm/bridge/analogix/anx7625.c +++ b/drivers/gpu/drm/bridge/analogix/anx7625.c @@ -1098,9 +1098,18 @@ static void anx7625_init_gpio(struct anx7625_data *platform) /* Gpio for chip power enable */ platform->pdata.gpio_p_on = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR_OR_NULL(platform->pdata.gpio_p_on)) { + DRM_DEV_DEBUG_DRIVER(dev, "no enable gpio found\n"); + platform->pdata.gpio_p_on = NULL; + } + /* Gpio for chip reset */ platform->pdata.gpio_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR_OR_NULL(platform->pdata.gpio_reset)) { + DRM_DEV_DEBUG_DRIVER(dev, "no reset gpio found\n"); + platform->pdata.gpio_reset = NULL; + } if (platform->pdata.gpio_p_on && platform->pdata.gpio_reset) { platform->pdata.low_power_mode = 1; -- cgit From f5f05ddc37e0445567e4a2369b73ecf9ee2e187d Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Wed, 22 Dec 2021 08:33:48 +0000 Subject: drm/bridge: anx7625: Fix null vs IS_ERR() checking in anx7625_register_i2c_dummy_clients Since i2c_new_client_device() function return error pointers. The i2c_new_dummy_device() function does not return NULL, It returns error pointers too. Using IS_ERR() to check the return value to fix this. Fixes: 8bdfc5dae4e3("drm/bridge: anx7625: Add anx7625 MIPI DSI/DPI to DP") Signed-off-by: Miaoqian Lin Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20211222083350.18514-1-linmq006@gmail.com Reviewed-by: Robert Foss --- drivers/gpu/drm/bridge/analogix/anx7625.c | 32 ++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c index 9765ac07f46d..7b24213f7b13 100644 --- a/drivers/gpu/drm/bridge/analogix/anx7625.c +++ b/drivers/gpu/drm/bridge/analogix/anx7625.c @@ -1971,40 +1971,54 @@ static const struct drm_bridge_funcs anx7625_bridge_funcs = { static int anx7625_register_i2c_dummy_clients(struct anx7625_data *ctx, struct i2c_client *client) { + int err = 0; + ctx->i2c.tx_p0_client = i2c_new_dummy_device(client->adapter, TX_P0_ADDR >> 1); - if (!ctx->i2c.tx_p0_client) - return -ENOMEM; + if (IS_ERR(ctx->i2c.tx_p0_client)) + return PTR_ERR(ctx->i2c.tx_p0_client); ctx->i2c.tx_p1_client = i2c_new_dummy_device(client->adapter, TX_P1_ADDR >> 1); - if (!ctx->i2c.tx_p1_client) + if (IS_ERR(ctx->i2c.tx_p1_client)) { + err = PTR_ERR(ctx->i2c.tx_p1_client); goto free_tx_p0; + } ctx->i2c.tx_p2_client = i2c_new_dummy_device(client->adapter, TX_P2_ADDR >> 1); - if (!ctx->i2c.tx_p2_client) + if (IS_ERR(ctx->i2c.tx_p2_client)) { + err = PTR_ERR(ctx->i2c.tx_p2_client); goto free_tx_p1; + } ctx->i2c.rx_p0_client = i2c_new_dummy_device(client->adapter, RX_P0_ADDR >> 1); - if (!ctx->i2c.rx_p0_client) + if (IS_ERR(ctx->i2c.rx_p0_client)) { + err = PTR_ERR(ctx->i2c.rx_p0_client); goto free_tx_p2; + } ctx->i2c.rx_p1_client = i2c_new_dummy_device(client->adapter, RX_P1_ADDR >> 1); - if (!ctx->i2c.rx_p1_client) + if (IS_ERR(ctx->i2c.rx_p1_client)) { + err = PTR_ERR(ctx->i2c.rx_p1_client); goto free_rx_p0; + } ctx->i2c.rx_p2_client = i2c_new_dummy_device(client->adapter, RX_P2_ADDR >> 1); - if (!ctx->i2c.rx_p2_client) + if (IS_ERR(ctx->i2c.rx_p2_client)) { + err = PTR_ERR(ctx->i2c.rx_p2_client); goto free_rx_p1; + } ctx->i2c.tcpc_client = i2c_new_dummy_device(client->adapter, TCPC_INTERFACE_ADDR >> 1); - if (!ctx->i2c.tcpc_client) + if (IS_ERR(ctx->i2c.tcpc_client)) { + err = PTR_ERR(ctx->i2c.tcpc_client); goto free_rx_p2; + } return 0; @@ -2021,7 +2035,7 @@ free_tx_p1: free_tx_p0: i2c_unregister_device(ctx->i2c.tx_p0_client); - return -ENOMEM; + return err; } static void anx7625_unregister_i2c_dummy_clients(struct anx7625_data *ctx) -- cgit From 92e794fab87af0793403d5e4a547f0be94a0e656 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Mon, 22 Nov 2021 12:36:32 +0530 Subject: drm: exynos: dsi: Convert to bridge driver Some display panels would come up with a non-DSI output, those can have an option to connect the DSI host by means of interface bridge converter. This DSI to non-DSI interface bridge converter would requires DSI Host to handle drm bridge functionalities in order to DSI Host to Interface bridge. This patch convert the existing to a drm bridge driver with a built-in encoder support for compatibility with existing component drivers. Signed-off-by: Jagan Teki Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20211122070633.89219-2-jagan@amarulasolutions.com --- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 73 +++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 22 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index 8d137857818c..174590f543c3 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -257,6 +257,7 @@ struct exynos_dsi { struct drm_connector connector; struct drm_panel *panel; struct list_head bridge_chain; + struct drm_bridge bridge; struct drm_bridge *out_bridge; struct device *dev; @@ -287,9 +288,9 @@ struct exynos_dsi { #define host_to_dsi(host) container_of(host, struct exynos_dsi, dsi_host) #define connector_to_dsi(c) container_of(c, struct exynos_dsi, connector) -static inline struct exynos_dsi *encoder_to_dsi(struct drm_encoder *e) +static inline struct exynos_dsi *bridge_to_dsi(struct drm_bridge *b) { - return container_of(e, struct exynos_dsi, encoder); + return container_of(b, struct exynos_dsi, bridge); } enum reg_idx { @@ -1374,9 +1375,10 @@ static void exynos_dsi_unregister_te_irq(struct exynos_dsi *dsi) } } -static void exynos_dsi_enable(struct drm_encoder *encoder) +static void exynos_dsi_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) { - struct exynos_dsi *dsi = encoder_to_dsi(encoder); + struct exynos_dsi *dsi = bridge_to_dsi(bridge); struct drm_bridge *iter; int ret; @@ -1399,7 +1401,8 @@ static void exynos_dsi_enable(struct drm_encoder *encoder) list_for_each_entry_reverse(iter, &dsi->bridge_chain, chain_node) { if (iter->funcs->pre_enable) - iter->funcs->pre_enable(iter); + iter->funcs->atomic_pre_enable(iter, + old_bridge_state); } } @@ -1413,7 +1416,7 @@ static void exynos_dsi_enable(struct drm_encoder *encoder) } else { list_for_each_entry(iter, &dsi->bridge_chain, chain_node) { if (iter->funcs->enable) - iter->funcs->enable(iter); + iter->funcs->atomic_enable(iter, old_bridge_state); } } @@ -1429,9 +1432,10 @@ err_put_sync: pm_runtime_put(dsi->dev); } -static void exynos_dsi_disable(struct drm_encoder *encoder) +static void exynos_dsi_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) { - struct exynos_dsi *dsi = encoder_to_dsi(encoder); + struct exynos_dsi *dsi = bridge_to_dsi(bridge); struct drm_bridge *iter; if (!(dsi->state & DSIM_STATE_ENABLED)) @@ -1443,7 +1447,7 @@ static void exynos_dsi_disable(struct drm_encoder *encoder) list_for_each_entry_reverse(iter, &dsi->bridge_chain, chain_node) { if (iter->funcs->disable) - iter->funcs->disable(iter); + iter->funcs->atomic_disable(iter, old_bridge_state); } exynos_dsi_set_display_enable(dsi, false); @@ -1451,7 +1455,7 @@ static void exynos_dsi_disable(struct drm_encoder *encoder) list_for_each_entry(iter, &dsi->bridge_chain, chain_node) { if (iter->funcs->post_disable) - iter->funcs->post_disable(iter); + iter->funcs->atomic_post_disable(iter, old_bridge_state); } dsi->state &= ~DSIM_STATE_ENABLED; @@ -1494,9 +1498,9 @@ static const struct drm_connector_helper_funcs exynos_dsi_connector_helper_funcs .get_modes = exynos_dsi_get_modes, }; -static int exynos_dsi_create_connector(struct drm_encoder *encoder) +static int exynos_dsi_create_connector(struct exynos_dsi *dsi) { - struct exynos_dsi *dsi = encoder_to_dsi(encoder); + struct drm_encoder *encoder = &dsi->encoder; struct drm_connector *connector = &dsi->connector; struct drm_device *drm = encoder->dev; int ret; @@ -1522,9 +1526,21 @@ static int exynos_dsi_create_connector(struct drm_encoder *encoder) return 0; } -static const struct drm_encoder_helper_funcs exynos_dsi_encoder_helper_funcs = { - .enable = exynos_dsi_enable, - .disable = exynos_dsi_disable, +static int exynos_dsi_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct exynos_dsi *dsi = bridge_to_dsi(bridge); + + return drm_bridge_attach(bridge->encoder, dsi->out_bridge, NULL, 0); +} + +static const struct drm_bridge_funcs exynos_dsi_bridge_funcs = { + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_enable = exynos_dsi_atomic_enable, + .atomic_disable = exynos_dsi_atomic_disable, + .attach = exynos_dsi_attach, }; MODULE_DEVICE_TABLE(of, exynos_dsi_of_match); @@ -1543,7 +1559,7 @@ static int exynos_dsi_host_attach(struct mipi_dsi_host *host, dsi->out_bridge = out_bridge; list_splice_init(&encoder->bridge_chain, &dsi->bridge_chain); } else { - int ret = exynos_dsi_create_connector(encoder); + int ret = exynos_dsi_create_connector(dsi); if (ret) { DRM_DEV_ERROR(dsi->dev, @@ -1596,7 +1612,7 @@ static int exynos_dsi_host_detach(struct mipi_dsi_host *host, if (dsi->panel) { mutex_lock(&drm->mode_config.mutex); - exynos_dsi_disable(&dsi->encoder); + exynos_dsi_atomic_disable(&dsi->bridge, NULL); dsi->panel = NULL; dsi->connector.status = connector_status_disconnected; mutex_unlock(&drm->mode_config.mutex); @@ -1702,12 +1718,16 @@ static int exynos_dsi_bind(struct device *dev, struct device *master, drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS); - drm_encoder_helper_add(encoder, &exynos_dsi_encoder_helper_funcs); - ret = exynos_drm_set_possible_crtcs(encoder, EXYNOS_DISPLAY_TYPE_LCD); if (ret < 0) return ret; + ret = drm_bridge_attach(&dsi->encoder, &dsi->bridge, NULL, 0); + if (ret) { + drm_encoder_cleanup(&dsi->encoder); + return ret; + } + in_bridge_node = of_graph_get_remote_node(dev->of_node, DSI_PORT_IN, 0); if (in_bridge_node) { in_bridge = of_drm_find_bridge(in_bridge_node); @@ -1723,10 +1743,9 @@ static void exynos_dsi_unbind(struct device *dev, struct device *master, void *data) { struct exynos_dsi *dsi = dev_get_drvdata(dev); - struct drm_encoder *encoder = &dsi->encoder; - - exynos_dsi_disable(encoder); + exynos_dsi_atomic_disable(&dsi->bridge, NULL); + drm_encoder_cleanup(&dsi->encoder); mipi_dsi_host_unregister(&dsi->dsi_host); } @@ -1819,6 +1838,12 @@ static int exynos_dsi_probe(struct platform_device *pdev) pm_runtime_enable(dev); + dsi->bridge.funcs = &exynos_dsi_bridge_funcs; + dsi->bridge.of_node = dev->of_node; + dsi->bridge.type = DRM_MODE_CONNECTOR_DSI; + + drm_bridge_add(&dsi->bridge); + ret = component_add(dev, &exynos_dsi_component_ops); if (ret) goto err_disable_runtime; @@ -1833,6 +1858,10 @@ err_disable_runtime: static int exynos_dsi_remove(struct platform_device *pdev) { + struct exynos_dsi *dsi = platform_get_drvdata(pdev); + + drm_bridge_remove(&dsi->bridge); + pm_runtime_disable(&pdev->dev); component_del(&pdev->dev, &exynos_dsi_component_ops); -- cgit From aee039e66035b66f0c587cc1b0dd32fb04c9a892 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Mon, 22 Nov 2021 12:36:33 +0530 Subject: drm: exynos: dsi: Add mode_set function Get the display mode settings via mode_set bridge function instead of explicitly de-reference. Signed-off-by: Jagan Teki Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20211122070633.89219-3-jagan@amarulasolutions.com --- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index 174590f543c3..3d4713346949 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -260,6 +260,7 @@ struct exynos_dsi { struct drm_bridge bridge; struct drm_bridge *out_bridge; struct device *dev; + struct drm_display_mode mode; void __iomem *reg_base; struct phy *phy; @@ -883,7 +884,7 @@ static int exynos_dsi_init_link(struct exynos_dsi *dsi) static void exynos_dsi_set_display_mode(struct exynos_dsi *dsi) { - struct drm_display_mode *m = &dsi->encoder.crtc->state->adjusted_mode; + struct drm_display_mode *m = &dsi->mode; unsigned int num_bits_resol = dsi->driver_data->num_bits_resol; u32 reg; @@ -1526,6 +1527,15 @@ static int exynos_dsi_create_connector(struct exynos_dsi *dsi) return 0; } +static void exynos_dsi_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct exynos_dsi *dsi = bridge_to_dsi(bridge); + + drm_mode_copy(&dsi->mode, adjusted_mode); +} + static int exynos_dsi_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags) { @@ -1540,6 +1550,7 @@ static const struct drm_bridge_funcs exynos_dsi_bridge_funcs = { .atomic_reset = drm_atomic_helper_bridge_reset, .atomic_enable = exynos_dsi_atomic_enable, .atomic_disable = exynos_dsi_atomic_disable, + .mode_set = exynos_dsi_mode_set, .attach = exynos_dsi_attach, }; -- cgit From 816e3be74f16f3064e74acb3e6ed69e27f14231b Mon Sep 17 00:00:00 2001 From: Jasmine Newsome Date: Tue, 4 Jan 2022 12:36:42 -0800 Subject: drm/i915/gem: Use local pointer ttm for __i915_ttm_move MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using local pointer ttm as argument in __i915_ttm_move instead of bo->ttm, as local pointer was previously assigned to bo->ttm in function. This will make code a bit more readable. Reviewed-by: Thomas Hellström Signed-off-by: Jasmine Newsome Signed-off-by: Thomas Hellström Link: https://patchwork.freedesktop.org/patch/msgid/20220104203642.231878-1-jasmine.newsome@intel.com --- drivers/gpu/drm/i915/gem/i915_gem_ttm_move.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gem/i915_gem_ttm_move.c b/drivers/gpu/drm/i915/gem/i915_gem_ttm_move.c index ee9612a3ee5e..8653855d808b 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_ttm_move.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_ttm_move.c @@ -525,7 +525,7 @@ int i915_ttm_move(struct ttm_buffer_object *bo, bool evict, return ret; } - migration_fence = __i915_ttm_move(bo, ctx, clear, dst_mem, bo->ttm, + migration_fence = __i915_ttm_move(bo, ctx, clear, dst_mem, ttm, dst_rsgt, true, &deps); i915_deps_fini(&deps); } -- cgit From 9294914dd5507eca56566c470c56327f46dfd4fa Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 5 Jan 2022 10:08:02 +0100 Subject: drm/bridge: parade-ps8640: Link device to ensure suspend/resume order Entering suspend while the display attached to this bridge is still on makes the resume sequence to resume the bridge first, display last: when this happens, we get a timeout while resuming the bridge, as its MCU will get stuck due to the display being unpowered. On the other hand, on mt8173-elm, closing the lid makes the display to get powered off first, bridge last, so at resume time the sequence is swapped (compared to the first example) and everything just works as expected. Add a stateless device link to the DRM device that this bridge belongs to, ensuring a correct resume sequence and solving the unability to correctly resume bridge operation in the first mentioned example. Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20220105090802.73564-1-angelogioacchino.delregno@collabora.com Reviewed-by: Robert Foss --- drivers/gpu/drm/bridge/parade-ps8640.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/parade-ps8640.c b/drivers/gpu/drm/bridge/parade-ps8640.c index 818704bf5e86..54723f068884 100644 --- a/drivers/gpu/drm/bridge/parade-ps8640.c +++ b/drivers/gpu/drm/bridge/parade-ps8640.c @@ -102,6 +102,7 @@ struct ps8640 { struct regulator_bulk_data supplies[2]; struct gpio_desc *gpio_reset; struct gpio_desc *gpio_powerdown; + struct device_link *link; bool pre_enabled; }; @@ -456,14 +457,36 @@ static int ps8640_bridge_attach(struct drm_bridge *bridge, return ret; } + ps_bridge->link = device_link_add(bridge->dev->dev, dev, DL_FLAG_STATELESS); + if (!ps_bridge->link) { + dev_err(dev, "failed to create device link"); + ret = -EINVAL; + goto err_devlink; + } + /* Attach the panel-bridge to the dsi bridge */ - return drm_bridge_attach(bridge->encoder, ps_bridge->panel_bridge, - &ps_bridge->bridge, flags); + ret = drm_bridge_attach(bridge->encoder, ps_bridge->panel_bridge, + &ps_bridge->bridge, flags); + if (ret) + goto err_bridge_attach; + + return 0; + +err_bridge_attach: + device_link_del(ps_bridge->link); +err_devlink: + drm_dp_aux_unregister(&ps_bridge->aux); + + return ret; } static void ps8640_bridge_detach(struct drm_bridge *bridge) { - drm_dp_aux_unregister(&bridge_to_ps8640(bridge)->aux); + struct ps8640 *ps_bridge = bridge_to_ps8640(bridge); + + drm_dp_aux_unregister(&ps_bridge->aux); + if (ps_bridge->link) + device_link_del(ps_bridge->link); } static struct edid *ps8640_bridge_get_edid(struct drm_bridge *bridge, -- cgit From 96211b7c56b109a52768e6cc5e23a1f79316eca0 Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Wed, 5 Jan 2022 10:41:09 +0000 Subject: drm/bridge: Add missing pm_runtime_disable() in __dw_mipi_dsi_probe If the probe fails, we should use pm_runtime_disable() to balance pm_runtime_enable(). Add missing pm_runtime_disable() for __dw_mipi_dsi_probe. Fixes: 46fc51546d44 ("drm/bridge/synopsys: Add MIPI DSI host controller bridge") Signed-off-by: Miaoqian Lin Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20220105104113.31415-1-linmq006@gmail.com Reviewed-by: Robert Foss --- drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c index 3f6564762e24..2a58b0b7ace5 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c @@ -1202,6 +1202,7 @@ __dw_mipi_dsi_probe(struct platform_device *pdev, ret = mipi_dsi_host_register(&dsi->dsi_host); if (ret) { dev_err(dev, "Failed to register MIPI host: %d\n", ret); + pm_runtime_disable(dev); dw_mipi_dsi_debugfs_remove(dsi); return ERR_PTR(ret); } -- cgit From b146e343a9e05605b491b1bf4a2b62a39d5638d8 Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Wed, 5 Jan 2022 10:48:26 +0000 Subject: drm/bridge: nwl-dsi: Fix PM disable depth imbalance in nwl_dsi_probe The pm_runtime_enable will increase power disable depth. Thus a pairing decrement is needed on the error handling path to keep it balanced according to context. Fixes: 44cfc6233447 ("drm/bridge: Add NWL MIPI DSI host controller support") Signed-off-by: Miaoqian Lin Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20220105104826.1418-1-linmq006@gmail.com Reviewed-by: Robert Foss --- drivers/gpu/drm/bridge/nwl-dsi.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/nwl-dsi.c b/drivers/gpu/drm/bridge/nwl-dsi.c index a7389a0facfb..fc3ad9fab867 100644 --- a/drivers/gpu/drm/bridge/nwl-dsi.c +++ b/drivers/gpu/drm/bridge/nwl-dsi.c @@ -1206,6 +1206,7 @@ static int nwl_dsi_probe(struct platform_device *pdev) ret = nwl_dsi_select_input(dsi); if (ret < 0) { + pm_runtime_disable(dev); mipi_dsi_host_unregister(&dsi->dsi_host); return ret; } -- cgit From 848915c35e22d36614f2b0aeca6ebf7361e8e204 Mon Sep 17 00:00:00 2001 From: Michał Winiarski Date: Tue, 21 Dec 2021 21:59:46 +0200 Subject: drm/i915/gt: Use to_gt() helper for GGTT accesses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GGTT is currently available both through i915->ggtt and gt->ggtt, and we eventually want to get rid of the i915->ggtt one. Use to_gt() for all i915->ggtt accesses to help with the future refactoring. During the probe of i915 the early intiialization of the gt (intel_gt_init_hw_early()) is moved prior to any access to the ggtt. This because it's in that moment we assign the ggtt to the gt and we want to do that before using it. Signed-off-by: Michał Winiarski Cc: Michal Wajdeczko Signed-off-by: Andi Shyti Reviewed-by: Sujaritha Sundaresan Reviewed-by: Matt Roper Signed-off-by: Matt Roper Link: https://patchwork.freedesktop.org/patch/msgid/20211221195946.3180-1-andi.shyti@linux.intel.com --- drivers/gpu/drm/i915/gt/intel_ggtt.c | 14 +++++++------- drivers/gpu/drm/i915/gt/intel_ggtt_fencing.c | 6 +++--- drivers/gpu/drm/i915/gt/intel_region_lmem.c | 4 ++-- drivers/gpu/drm/i915/gt/selftest_reset.c | 2 +- drivers/gpu/drm/i915/i915_drv.c | 4 ++-- 5 files changed, 15 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/intel_ggtt.c b/drivers/gpu/drm/i915/gt/intel_ggtt.c index b6dbd12af74e..ab5e5a097b03 100644 --- a/drivers/gpu/drm/i915/gt/intel_ggtt.c +++ b/drivers/gpu/drm/i915/gt/intel_ggtt.c @@ -84,7 +84,7 @@ int i915_ggtt_init_hw(struct drm_i915_private *i915) * beyond the end of the batch buffer, across the page boundary, * and beyond the end of the GTT if we do not provide a guard. */ - ret = ggtt_init_hw(&i915->ggtt); + ret = ggtt_init_hw(to_gt(i915)->ggtt); if (ret) return ret; @@ -705,14 +705,14 @@ int i915_init_ggtt(struct drm_i915_private *i915) { int ret; - ret = init_ggtt(&i915->ggtt); + ret = init_ggtt(to_gt(i915)->ggtt); if (ret) return ret; if (INTEL_PPGTT(i915) == INTEL_PPGTT_ALIASING) { - ret = init_aliasing_ppgtt(&i915->ggtt); + ret = init_aliasing_ppgtt(to_gt(i915)->ggtt); if (ret) - cleanup_init_ggtt(&i915->ggtt); + cleanup_init_ggtt(to_gt(i915)->ggtt); } return 0; @@ -755,7 +755,7 @@ static void ggtt_cleanup_hw(struct i915_ggtt *ggtt) */ void i915_ggtt_driver_release(struct drm_i915_private *i915) { - struct i915_ggtt *ggtt = &i915->ggtt; + struct i915_ggtt *ggtt = to_gt(i915)->ggtt; fini_aliasing_ppgtt(ggtt); @@ -770,7 +770,7 @@ void i915_ggtt_driver_release(struct drm_i915_private *i915) */ void i915_ggtt_driver_late_release(struct drm_i915_private *i915) { - struct i915_ggtt *ggtt = &i915->ggtt; + struct i915_ggtt *ggtt = to_gt(i915)->ggtt; GEM_WARN_ON(kref_read(&ggtt->vm.resv_ref) != 1); dma_resv_fini(&ggtt->vm._resv); @@ -1191,7 +1191,7 @@ int i915_ggtt_probe_hw(struct drm_i915_private *i915) { int ret; - ret = ggtt_probe_hw(&i915->ggtt, to_gt(i915)); + ret = ggtt_probe_hw(to_gt(i915)->ggtt, to_gt(i915)); if (ret) return ret; diff --git a/drivers/gpu/drm/i915/gt/intel_ggtt_fencing.c b/drivers/gpu/drm/i915/gt/intel_ggtt_fencing.c index f8948de72036..beabf3bc9b75 100644 --- a/drivers/gpu/drm/i915/gt/intel_ggtt_fencing.c +++ b/drivers/gpu/drm/i915/gt/intel_ggtt_fencing.c @@ -728,8 +728,8 @@ static void detect_bit_6_swizzle(struct i915_ggtt *ggtt) swizzle_y = I915_BIT_6_SWIZZLE_NONE; } - i915->ggtt.bit_6_swizzle_x = swizzle_x; - i915->ggtt.bit_6_swizzle_y = swizzle_y; + to_gt(i915)->ggtt->bit_6_swizzle_x = swizzle_x; + to_gt(i915)->ggtt->bit_6_swizzle_y = swizzle_y; } /* @@ -896,7 +896,7 @@ void intel_gt_init_swizzling(struct intel_gt *gt) struct intel_uncore *uncore = gt->uncore; if (GRAPHICS_VER(i915) < 5 || - i915->ggtt.bit_6_swizzle_x == I915_BIT_6_SWIZZLE_NONE) + to_gt(i915)->ggtt->bit_6_swizzle_x == I915_BIT_6_SWIZZLE_NONE) return; intel_uncore_rmw(uncore, DISP_ARB_CTL, 0, DISP_TILE_SURFACE_SWIZZLING); diff --git a/drivers/gpu/drm/i915/gt/intel_region_lmem.c b/drivers/gpu/drm/i915/gt/intel_region_lmem.c index fde2dcb59809..21215a080088 100644 --- a/drivers/gpu/drm/i915/gt/intel_region_lmem.c +++ b/drivers/gpu/drm/i915/gt/intel_region_lmem.c @@ -15,7 +15,7 @@ static int init_fake_lmem_bar(struct intel_memory_region *mem) { struct drm_i915_private *i915 = mem->i915; - struct i915_ggtt *ggtt = &i915->ggtt; + struct i915_ggtt *ggtt = to_gt(i915)->ggtt; unsigned long n; int ret; @@ -131,7 +131,7 @@ intel_gt_setup_fake_lmem(struct intel_gt *gt) if (!i915->params.fake_lmem_start) return ERR_PTR(-ENODEV); - GEM_BUG_ON(i915_ggtt_has_aperture(&i915->ggtt)); + GEM_BUG_ON(i915_ggtt_has_aperture(to_gt(i915)->ggtt)); /* Your mappable aperture belongs to me now! */ mappable_end = pci_resource_len(pdev, 2); diff --git a/drivers/gpu/drm/i915/gt/selftest_reset.c b/drivers/gpu/drm/i915/gt/selftest_reset.c index 8a873f6bda7f..37c38bdd5f47 100644 --- a/drivers/gpu/drm/i915/gt/selftest_reset.c +++ b/drivers/gpu/drm/i915/gt/selftest_reset.c @@ -19,7 +19,7 @@ __igt_reset_stolen(struct intel_gt *gt, intel_engine_mask_t mask, const char *msg) { - struct i915_ggtt *ggtt = >->i915->ggtt; + struct i915_ggtt *ggtt = gt->ggtt; const struct resource *dsm = >->i915->dsm; resource_size_t num_pages, page; struct intel_engine_cs *engine; diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c index a4e8f938ff61..21911ceb2d92 100644 --- a/drivers/gpu/drm/i915/i915_drv.c +++ b/drivers/gpu/drm/i915/i915_drv.c @@ -569,6 +569,8 @@ static int i915_driver_hw_probe(struct drm_i915_private *dev_priv) i915_perf_init(dev_priv); + intel_gt_init_hw_early(to_gt(dev_priv), &dev_priv->ggtt); + ret = i915_ggtt_probe_hw(dev_priv); if (ret) goto err_perf; @@ -585,8 +587,6 @@ static int i915_driver_hw_probe(struct drm_i915_private *dev_priv) if (ret) goto err_ggtt; - intel_gt_init_hw_early(to_gt(dev_priv), &dev_priv->ggtt); - ret = intel_gt_probe_lmem(to_gt(dev_priv)); if (ret) goto err_mem_regions; -- cgit From 204129a211fc486164c25e6082941e4fe3ba889d Mon Sep 17 00:00:00 2001 From: Michał Winiarski Date: Wed, 5 Jan 2022 00:35:50 +0200 Subject: drm/i915: Use to_gt() helper for GGTT accesses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GGTT is currently available both through i915->ggtt and gt->ggtt, and we eventually want to get rid of the i915->ggtt one. Use to_gt() for all i915->ggtt accesses to help with the future refactoring. Signed-off-by: Michał Winiarski Cc: Michal Wajdeczko Signed-off-by: Andi Shyti Reviewed-by: Matt Roper Signed-off-by: Matt Roper Link: https://patchwork.freedesktop.org/patch/msgid/20220104223550.56135-1-andi.shyti@linux.intel.com --- drivers/gpu/drm/i915/gvt/dmabuf.c | 2 +- drivers/gpu/drm/i915/i915_debugfs.c | 4 ++-- drivers/gpu/drm/i915/i915_drv.c | 4 ++-- drivers/gpu/drm/i915/i915_drv.h | 2 +- drivers/gpu/drm/i915/i915_gem.c | 23 ++++++++++++----------- drivers/gpu/drm/i915/i915_gem_gtt.c | 6 +++--- drivers/gpu/drm/i915/i915_getparam.c | 2 +- drivers/gpu/drm/i915/i915_perf.c | 6 +++--- 8 files changed, 25 insertions(+), 24 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gvt/dmabuf.c b/drivers/gpu/drm/i915/gvt/dmabuf.c index 8e65cd8258b9..94c3eb1586b0 100644 --- a/drivers/gpu/drm/i915/gvt/dmabuf.c +++ b/drivers/gpu/drm/i915/gvt/dmabuf.c @@ -84,7 +84,7 @@ static int vgpu_gem_get_pages( kfree(st); return ret; } - gtt_entries = (gen8_pte_t __iomem *)dev_priv->ggtt.gsm + + gtt_entries = (gen8_pte_t __iomem *)to_gt(dev_priv)->ggtt->gsm + (fb_info->start >> PAGE_SHIFT); for_each_sg(st->sgl, sg, page_num, i) { dma_addr_t dma_addr = diff --git a/drivers/gpu/drm/i915/i915_debugfs.c b/drivers/gpu/drm/i915/i915_debugfs.c index bd0944d76656..110720bbe8cf 100644 --- a/drivers/gpu/drm/i915/i915_debugfs.c +++ b/drivers/gpu/drm/i915/i915_debugfs.c @@ -391,9 +391,9 @@ static int i915_swizzle_info(struct seq_file *m, void *data) intel_wakeref_t wakeref; seq_printf(m, "bit6 swizzle for X-tiling = %s\n", - swizzle_string(dev_priv->ggtt.bit_6_swizzle_x)); + swizzle_string(to_gt(dev_priv)->ggtt->bit_6_swizzle_x)); seq_printf(m, "bit6 swizzle for Y-tiling = %s\n", - swizzle_string(dev_priv->ggtt.bit_6_swizzle_y)); + swizzle_string(to_gt(dev_priv)->ggtt->bit_6_swizzle_y)); if (dev_priv->quirks & QUIRK_PIN_SWIZZLED_PAGES) seq_puts(m, "L-shaped memory detected\n"); diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c index 21911ceb2d92..c23a27e02e95 100644 --- a/drivers/gpu/drm/i915/i915_drv.c +++ b/drivers/gpu/drm/i915/i915_drv.c @@ -1142,7 +1142,7 @@ static int i915_drm_suspend(struct drm_device *dev) intel_suspend_hw(dev_priv); - i915_ggtt_suspend(&dev_priv->ggtt); + i915_ggtt_suspend(to_gt(dev_priv)->ggtt); i915_save_display(dev_priv); @@ -1257,7 +1257,7 @@ static int i915_drm_resume(struct drm_device *dev) if (ret) drm_err(&dev_priv->drm, "failed to re-enable GGTT\n"); - i915_ggtt_resume(&dev_priv->ggtt); + i915_ggtt_resume(to_gt(dev_priv)->ggtt); intel_dmc_ucode_resume(dev_priv); diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index cb4cf6786bd3..bce55d1081b3 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -1958,7 +1958,7 @@ static inline bool i915_gem_object_needs_bit17_swizzle(struct drm_i915_gem_objec { struct drm_i915_private *i915 = to_i915(obj->base.dev); - return i915->ggtt.bit_6_swizzle_x == I915_BIT_6_SWIZZLE_9_10_17 && + return to_gt(i915)->ggtt->bit_6_swizzle_x == I915_BIT_6_SWIZZLE_9_10_17 && i915_gem_object_is_tiled(obj); } diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c index 915bf431f320..e3730096abd9 100644 --- a/drivers/gpu/drm/i915/i915_gem.c +++ b/drivers/gpu/drm/i915/i915_gem.c @@ -88,7 +88,8 @@ int i915_gem_get_aperture_ioctl(struct drm_device *dev, void *data, struct drm_file *file) { - struct i915_ggtt *ggtt = &to_i915(dev)->ggtt; + struct drm_i915_private *i915 = to_i915(dev); + struct i915_ggtt *ggtt = to_gt(i915)->ggtt; struct drm_i915_gem_get_aperture *args = data; struct i915_vma *vma; u64 pinned; @@ -289,7 +290,7 @@ static struct i915_vma *i915_gem_gtt_prepare(struct drm_i915_gem_object *obj, bool write) { struct drm_i915_private *i915 = to_i915(obj->base.dev); - struct i915_ggtt *ggtt = &i915->ggtt; + struct i915_ggtt *ggtt = to_gt(i915)->ggtt; struct i915_vma *vma; struct i915_gem_ww_ctx ww; int ret; @@ -350,7 +351,7 @@ static void i915_gem_gtt_cleanup(struct drm_i915_gem_object *obj, struct i915_vma *vma) { struct drm_i915_private *i915 = to_i915(obj->base.dev); - struct i915_ggtt *ggtt = &i915->ggtt; + struct i915_ggtt *ggtt = to_gt(i915)->ggtt; i915_gem_object_unpin_pages(obj); if (drm_mm_node_allocated(node)) { @@ -366,7 +367,7 @@ i915_gem_gtt_pread(struct drm_i915_gem_object *obj, const struct drm_i915_gem_pread *args) { struct drm_i915_private *i915 = to_i915(obj->base.dev); - struct i915_ggtt *ggtt = &i915->ggtt; + struct i915_ggtt *ggtt = to_gt(i915)->ggtt; intel_wakeref_t wakeref; struct drm_mm_node node; void __user *user_data; @@ -522,7 +523,7 @@ i915_gem_gtt_pwrite_fast(struct drm_i915_gem_object *obj, const struct drm_i915_gem_pwrite *args) { struct drm_i915_private *i915 = to_i915(obj->base.dev); - struct i915_ggtt *ggtt = &i915->ggtt; + struct i915_ggtt *ggtt = to_gt(i915)->ggtt; struct intel_runtime_pm *rpm = &i915->runtime_pm; intel_wakeref_t wakeref; struct drm_mm_node node; @@ -823,7 +824,7 @@ void i915_gem_runtime_suspend(struct drm_i915_private *i915) */ list_for_each_entry_safe(obj, on, - &i915->ggtt.userfault_list, userfault_link) + &to_gt(i915)->ggtt->userfault_list, userfault_link) __i915_gem_object_release_mmap_gtt(obj); /* @@ -831,8 +832,8 @@ void i915_gem_runtime_suspend(struct drm_i915_private *i915) * in use by hardware (i.e. they are pinned), we should not be powering * down! All other fences will be reacquired by the user upon waking. */ - for (i = 0; i < i915->ggtt.num_fences; i++) { - struct i915_fence_reg *reg = &i915->ggtt.fence_regs[i]; + for (i = 0; i < to_gt(i915)->ggtt->num_fences; i++) { + struct i915_fence_reg *reg = &to_gt(i915)->ggtt->fence_regs[i]; /* * Ideally we want to assert that the fence register is not @@ -873,7 +874,7 @@ i915_gem_object_ggtt_pin_ww(struct drm_i915_gem_object *obj, u64 size, u64 alignment, u64 flags) { struct drm_i915_private *i915 = to_i915(obj->base.dev); - struct i915_ggtt *ggtt = &i915->ggtt; + struct i915_ggtt *ggtt = to_gt(i915)->ggtt; struct i915_vma *vma; int ret; @@ -1123,7 +1124,7 @@ err_unlock: /* Minimal basic recovery for KMS */ ret = i915_ggtt_enable_hw(dev_priv); - i915_ggtt_resume(&dev_priv->ggtt); + i915_ggtt_resume(to_gt(dev_priv)->ggtt); intel_init_clock_gating(dev_priv); } @@ -1146,7 +1147,7 @@ void i915_gem_driver_unregister(struct drm_i915_private *i915) void i915_gem_driver_remove(struct drm_i915_private *dev_priv) { - intel_wakeref_auto_fini(&dev_priv->ggtt.userfault_wakeref); + intel_wakeref_auto_fini(&to_gt(dev_priv)->ggtt->userfault_wakeref); i915_gem_suspend_late(dev_priv); intel_gt_driver_remove(to_gt(dev_priv)); diff --git a/drivers/gpu/drm/i915/i915_gem_gtt.c b/drivers/gpu/drm/i915/i915_gem_gtt.c index cd5f2348a187..2f2ba7a2955d 100644 --- a/drivers/gpu/drm/i915/i915_gem_gtt.c +++ b/drivers/gpu/drm/i915/i915_gem_gtt.c @@ -56,7 +56,7 @@ void i915_gem_gtt_finish_pages(struct drm_i915_gem_object *obj, struct sg_table *pages) { struct drm_i915_private *i915 = to_i915(obj->base.dev); - struct i915_ggtt *ggtt = &i915->ggtt; + struct i915_ggtt *ggtt = to_gt(i915)->ggtt; /* XXX This does not prevent more requests being submitted! */ if (unlikely(ggtt->do_idle_maps)) @@ -103,7 +103,7 @@ int i915_gem_gtt_reserve(struct i915_address_space *vm, GEM_BUG_ON(!IS_ALIGNED(size, I915_GTT_PAGE_SIZE)); GEM_BUG_ON(!IS_ALIGNED(offset, I915_GTT_MIN_ALIGNMENT)); GEM_BUG_ON(range_overflows(offset, size, vm->total)); - GEM_BUG_ON(vm == &vm->i915->ggtt.alias->vm); + GEM_BUG_ON(vm == &to_gt(vm->i915)->ggtt->alias->vm); GEM_BUG_ON(drm_mm_node_allocated(node)); node->size = size; @@ -201,7 +201,7 @@ int i915_gem_gtt_insert(struct i915_address_space *vm, GEM_BUG_ON(start >= end); GEM_BUG_ON(start > 0 && !IS_ALIGNED(start, I915_GTT_PAGE_SIZE)); GEM_BUG_ON(end < U64_MAX && !IS_ALIGNED(end, I915_GTT_PAGE_SIZE)); - GEM_BUG_ON(vm == &vm->i915->ggtt.alias->vm); + GEM_BUG_ON(vm == &to_gt(vm->i915)->ggtt->alias->vm); GEM_BUG_ON(drm_mm_node_allocated(node)); if (unlikely(range_overflows(start, size, end))) diff --git a/drivers/gpu/drm/i915/i915_getparam.c b/drivers/gpu/drm/i915/i915_getparam.c index 7f80ad247bc8..5b8a2157d797 100644 --- a/drivers/gpu/drm/i915/i915_getparam.c +++ b/drivers/gpu/drm/i915/i915_getparam.c @@ -31,7 +31,7 @@ int i915_getparam_ioctl(struct drm_device *dev, void *data, value = pdev->revision; break; case I915_PARAM_NUM_FENCES_AVAIL: - value = i915->ggtt.num_fences; + value = to_gt(i915)->ggtt->num_fences; break; case I915_PARAM_HAS_OVERLAY: value = !!i915->overlay; diff --git a/drivers/gpu/drm/i915/i915_perf.c b/drivers/gpu/drm/i915/i915_perf.c index 170bba913c30..298857b69180 100644 --- a/drivers/gpu/drm/i915/i915_perf.c +++ b/drivers/gpu/drm/i915/i915_perf.c @@ -1630,8 +1630,8 @@ static int alloc_noa_wait(struct i915_perf_stream *stream) struct drm_i915_gem_object *bo; struct i915_vma *vma; const u64 delay_ticks = 0xffffffffffffffff - - intel_gt_ns_to_clock_interval(stream->perf->i915->ggtt.vm.gt, - atomic64_read(&stream->perf->noa_programming_delay)); + intel_gt_ns_to_clock_interval(to_gt(stream->perf->i915), + atomic64_read(&stream->perf->noa_programming_delay)); const u32 base = stream->engine->mmio_base; #define CS_GPR(x) GEN8_RING_CS_GPR(base, x) u32 *batch, *ts0, *cs, *jump; @@ -3542,7 +3542,7 @@ err: static u64 oa_exponent_to_ns(struct i915_perf *perf, int exponent) { - return intel_gt_clock_interval_to_ns(perf->i915->ggtt.vm.gt, + return intel_gt_clock_interval_to_ns(to_gt(perf->i915), 2ULL << exponent); } -- cgit From 5c24c9d227e9bbcba509752a32b3da5faa149aec Mon Sep 17 00:00:00 2001 From: Michał Winiarski Date: Sun, 19 Dec 2021 23:24:57 +0200 Subject: drm/i915/gem: Use to_gt() helper for GGTT accesses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GGTT is currently available both through i915->ggtt and gt->ggtt, and we eventually want to get rid of the i915->ggtt one. Use to_gt() for all i915->ggtt accesses to help with the future refactoring. Signed-off-by: Michał Winiarski Cc: Michal Wajdeczko Signed-off-by: Andi Shyti Reviewed-by: Sujaritha Sundaresan Reviewed-by: Matt Roper Signed-off-by: Matt Roper Link: https://patchwork.freedesktop.org/patch/msgid/20211219212500.61432-4-andi.shyti@linux.intel.com --- drivers/gpu/drm/i915/gem/i915_gem_context.h | 2 +- drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c | 2 +- drivers/gpu/drm/i915/gem/i915_gem_mman.c | 19 ++++++++++--------- drivers/gpu/drm/i915/gem/i915_gem_pm.c | 2 +- drivers/gpu/drm/i915/gem/i915_gem_shrinker.c | 6 +++--- drivers/gpu/drm/i915/gem/i915_gem_stolen.c | 8 +++++--- drivers/gpu/drm/i915/gem/i915_gem_tiling.c | 15 ++++++++------- .../gpu/drm/i915/gem/selftests/i915_gem_client_blt.c | 2 +- drivers/gpu/drm/i915/gem/selftests/i915_gem_context.c | 2 +- drivers/gpu/drm/i915/gem/selftests/i915_gem_mman.c | 19 ++++++++++--------- drivers/gpu/drm/i915/gem/selftests/i915_gem_object.c | 2 +- 11 files changed, 42 insertions(+), 37 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gem/i915_gem_context.h b/drivers/gpu/drm/i915/gem/i915_gem_context.h index babfecb17ad1..e5b0f66ea1fe 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_context.h +++ b/drivers/gpu/drm/i915/gem/i915_gem_context.h @@ -174,7 +174,7 @@ i915_gem_context_get_eb_vm(struct i915_gem_context *ctx) vm = ctx->vm; if (!vm) - vm = &ctx->i915->ggtt.vm; + vm = &to_gt(ctx->i915)->ggtt->vm; vm = i915_vm_get(vm); return vm; diff --git a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c index 3a5b247be738..c0e6712651f3 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c @@ -1095,7 +1095,7 @@ static inline struct i915_ggtt *cache_to_ggtt(struct reloc_cache *cache) { struct drm_i915_private *i915 = container_of(cache, struct i915_execbuffer, reloc_cache)->i915; - return &i915->ggtt; + return to_gt(i915)->ggtt; } static void reloc_cache_unmap(struct reloc_cache *cache) diff --git a/drivers/gpu/drm/i915/gem/i915_gem_mman.c b/drivers/gpu/drm/i915/gem/i915_gem_mman.c index 2e9088b7df91..fae458019172 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_mman.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_mman.c @@ -294,7 +294,7 @@ static vm_fault_t vm_fault_gtt(struct vm_fault *vmf) struct drm_device *dev = obj->base.dev; struct drm_i915_private *i915 = to_i915(dev); struct intel_runtime_pm *rpm = &i915->runtime_pm; - struct i915_ggtt *ggtt = &i915->ggtt; + struct i915_ggtt *ggtt = to_gt(i915)->ggtt; bool write = area->vm_flags & VM_WRITE; struct i915_gem_ww_ctx ww; intel_wakeref_t wakeref; @@ -387,16 +387,16 @@ retry: assert_rpm_wakelock_held(rpm); /* Mark as being mmapped into userspace for later revocation */ - mutex_lock(&i915->ggtt.vm.mutex); + mutex_lock(&to_gt(i915)->ggtt->vm.mutex); if (!i915_vma_set_userfault(vma) && !obj->userfault_count++) - list_add(&obj->userfault_link, &i915->ggtt.userfault_list); - mutex_unlock(&i915->ggtt.vm.mutex); + list_add(&obj->userfault_link, &to_gt(i915)->ggtt->userfault_list); + mutex_unlock(&to_gt(i915)->ggtt->vm.mutex); /* Track the mmo associated with the fenced vma */ vma->mmo = mmo; if (CONFIG_DRM_I915_USERFAULT_AUTOSUSPEND) - intel_wakeref_auto(&i915->ggtt.userfault_wakeref, + intel_wakeref_auto(&to_gt(i915)->ggtt->userfault_wakeref, msecs_to_jiffies_timeout(CONFIG_DRM_I915_USERFAULT_AUTOSUSPEND)); if (write) { @@ -511,7 +511,7 @@ void i915_gem_object_release_mmap_gtt(struct drm_i915_gem_object *obj) * wakeref. */ wakeref = intel_runtime_pm_get(&i915->runtime_pm); - mutex_lock(&i915->ggtt.vm.mutex); + mutex_lock(&to_gt(i915)->ggtt->vm.mutex); if (!obj->userfault_count) goto out; @@ -529,7 +529,7 @@ void i915_gem_object_release_mmap_gtt(struct drm_i915_gem_object *obj) wmb(); out: - mutex_unlock(&i915->ggtt.vm.mutex); + mutex_unlock(&to_gt(i915)->ggtt->vm.mutex); intel_runtime_pm_put(&i915->runtime_pm, wakeref); } @@ -732,13 +732,14 @@ i915_gem_dumb_mmap_offset(struct drm_file *file, u32 handle, u64 *offset) { + struct drm_i915_private *i915 = to_i915(dev); enum i915_mmap_type mmap_type; if (HAS_LMEM(to_i915(dev))) mmap_type = I915_MMAP_TYPE_FIXED; else if (pat_enabled()) mmap_type = I915_MMAP_TYPE_WC; - else if (!i915_ggtt_has_aperture(&to_i915(dev)->ggtt)) + else if (!i915_ggtt_has_aperture(to_gt(i915)->ggtt)) return -ENODEV; else mmap_type = I915_MMAP_TYPE_GTT; @@ -786,7 +787,7 @@ i915_gem_mmap_offset_ioctl(struct drm_device *dev, void *data, switch (args->flags) { case I915_MMAP_OFFSET_GTT: - if (!i915_ggtt_has_aperture(&i915->ggtt)) + if (!i915_ggtt_has_aperture(to_gt(i915)->ggtt)) return -ENODEV; type = I915_MMAP_TYPE_GTT; break; diff --git a/drivers/gpu/drm/i915/gem/i915_gem_pm.c b/drivers/gpu/drm/i915/gem/i915_gem_pm.c index ac56124760e1..6da68b38f00f 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_pm.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_pm.c @@ -23,7 +23,7 @@ void i915_gem_suspend(struct drm_i915_private *i915) { GEM_TRACE("%s\n", dev_name(i915->drm.dev)); - intel_wakeref_auto(&i915->ggtt.userfault_wakeref, 0); + intel_wakeref_auto(&to_gt(i915)->ggtt->userfault_wakeref, 0); flush_workqueue(i915->wq); /* diff --git a/drivers/gpu/drm/i915/gem/i915_gem_shrinker.c b/drivers/gpu/drm/i915/gem/i915_gem_shrinker.c index cc927e49d21f..f9f68830c084 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_shrinker.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_shrinker.c @@ -401,9 +401,9 @@ i915_gem_shrinker_vmap(struct notifier_block *nb, unsigned long event, void *ptr I915_SHRINK_VMAPS); /* We also want to clear any cached iomaps as they wrap vmap */ - mutex_lock(&i915->ggtt.vm.mutex); + mutex_lock(&to_gt(i915)->ggtt->vm.mutex); list_for_each_entry_safe(vma, next, - &i915->ggtt.vm.bound_list, vm_link) { + &to_gt(i915)->ggtt->vm.bound_list, vm_link) { unsigned long count = vma->node.size >> PAGE_SHIFT; struct drm_i915_gem_object *obj = vma->obj; @@ -418,7 +418,7 @@ i915_gem_shrinker_vmap(struct notifier_block *nb, unsigned long event, void *ptr i915_gem_object_unlock(obj); } - mutex_unlock(&i915->ggtt.vm.mutex); + mutex_unlock(&to_gt(i915)->ggtt->vm.mutex); *(unsigned long *)ptr += freed_pages; return NOTIFY_DONE; diff --git a/drivers/gpu/drm/i915/gem/i915_gem_stolen.c b/drivers/gpu/drm/i915/gem/i915_gem_stolen.c index 7df50fd6cc7b..26975d857776 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_stolen.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_stolen.c @@ -71,7 +71,7 @@ void i915_gem_stolen_remove_node(struct drm_i915_private *i915, static int i915_adjust_stolen(struct drm_i915_private *i915, struct resource *dsm) { - struct i915_ggtt *ggtt = &i915->ggtt; + struct i915_ggtt *ggtt = to_gt(i915)->ggtt; struct intel_uncore *uncore = ggtt->vm.gt->uncore; struct resource *r; @@ -582,6 +582,7 @@ i915_pages_create_for_stolen(struct drm_device *dev, static int i915_gem_object_get_pages_stolen(struct drm_i915_gem_object *obj) { + struct drm_i915_private *i915 = to_i915(obj->base.dev); struct sg_table *pages = i915_pages_create_for_stolen(obj->base.dev, obj->stolen->start, @@ -589,7 +590,7 @@ static int i915_gem_object_get_pages_stolen(struct drm_i915_gem_object *obj) if (IS_ERR(pages)) return PTR_ERR(pages); - dbg_poison(&to_i915(obj->base.dev)->ggtt, + dbg_poison(to_gt(i915)->ggtt, sg_dma_address(pages->sgl), sg_dma_len(pages->sgl), POISON_INUSE); @@ -602,9 +603,10 @@ static int i915_gem_object_get_pages_stolen(struct drm_i915_gem_object *obj) static void i915_gem_object_put_pages_stolen(struct drm_i915_gem_object *obj, struct sg_table *pages) { + struct drm_i915_private *i915 = to_i915(obj->base.dev); /* Should only be called from i915_gem_object_release_stolen() */ - dbg_poison(&to_i915(obj->base.dev)->ggtt, + dbg_poison(to_gt(i915)->ggtt, sg_dma_address(pages->sgl), sg_dma_len(pages->sgl), POISON_FREE); diff --git a/drivers/gpu/drm/i915/gem/i915_gem_tiling.c b/drivers/gpu/drm/i915/gem/i915_gem_tiling.c index ef4d0f7dc118..c3d432e314c9 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_tiling.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_tiling.c @@ -181,7 +181,8 @@ static int i915_gem_object_fence_prepare(struct drm_i915_gem_object *obj, int tiling_mode, unsigned int stride) { - struct i915_ggtt *ggtt = &to_i915(obj->base.dev)->ggtt; + struct drm_i915_private *i915 = to_i915(obj->base.dev); + struct i915_ggtt *ggtt = to_gt(i915)->ggtt; struct i915_vma *vma, *vn; LIST_HEAD(unbind); int ret = 0; @@ -336,7 +337,7 @@ i915_gem_set_tiling_ioctl(struct drm_device *dev, void *data, struct drm_i915_gem_object *obj; int err; - if (!dev_priv->ggtt.num_fences) + if (!to_gt(dev_priv)->ggtt->num_fences) return -EOPNOTSUPP; obj = i915_gem_object_lookup(file, args->handle); @@ -362,9 +363,9 @@ i915_gem_set_tiling_ioctl(struct drm_device *dev, void *data, args->stride = 0; } else { if (args->tiling_mode == I915_TILING_X) - args->swizzle_mode = to_i915(dev)->ggtt.bit_6_swizzle_x; + args->swizzle_mode = to_gt(dev_priv)->ggtt->bit_6_swizzle_x; else - args->swizzle_mode = to_i915(dev)->ggtt.bit_6_swizzle_y; + args->swizzle_mode = to_gt(dev_priv)->ggtt->bit_6_swizzle_y; /* Hide bit 17 swizzling from the user. This prevents old Mesa * from aborting the application on sw fallbacks to bit 17, @@ -419,7 +420,7 @@ i915_gem_get_tiling_ioctl(struct drm_device *dev, void *data, struct drm_i915_gem_object *obj; int err = -ENOENT; - if (!dev_priv->ggtt.num_fences) + if (!to_gt(dev_priv)->ggtt->num_fences) return -EOPNOTSUPP; rcu_read_lock(); @@ -435,10 +436,10 @@ i915_gem_get_tiling_ioctl(struct drm_device *dev, void *data, switch (args->tiling_mode) { case I915_TILING_X: - args->swizzle_mode = dev_priv->ggtt.bit_6_swizzle_x; + args->swizzle_mode = to_gt(dev_priv)->ggtt->bit_6_swizzle_x; break; case I915_TILING_Y: - args->swizzle_mode = dev_priv->ggtt.bit_6_swizzle_y; + args->swizzle_mode = to_gt(dev_priv)->ggtt->bit_6_swizzle_y; break; default: case I915_TILING_NONE: diff --git a/drivers/gpu/drm/i915/gem/selftests/i915_gem_client_blt.c b/drivers/gpu/drm/i915/gem/selftests/i915_gem_client_blt.c index 75947e9dada2..c08f766e6e15 100644 --- a/drivers/gpu/drm/i915/gem/selftests/i915_gem_client_blt.c +++ b/drivers/gpu/drm/i915/gem/selftests/i915_gem_client_blt.c @@ -543,7 +543,7 @@ static bool has_bit17_swizzle(int sw) static bool bad_swizzling(struct drm_i915_private *i915) { - struct i915_ggtt *ggtt = &i915->ggtt; + struct i915_ggtt *ggtt = to_gt(i915)->ggtt; if (i915->quirks & QUIRK_PIN_SWIZZLED_PAGES) return true; diff --git a/drivers/gpu/drm/i915/gem/selftests/i915_gem_context.c b/drivers/gpu/drm/i915/gem/selftests/i915_gem_context.c index 3f41fe5ec9d4..80d99b9c694f 100644 --- a/drivers/gpu/drm/i915/gem/selftests/i915_gem_context.c +++ b/drivers/gpu/drm/i915/gem/selftests/i915_gem_context.c @@ -1374,7 +1374,7 @@ static int igt_ctx_readonly(void *arg) goto out_file; } - vm = ctx->vm ?: &i915->ggtt.alias->vm; + vm = ctx->vm ?: &to_gt(i915)->ggtt->alias->vm; if (!vm || !vm->has_read_only) { err = 0; goto out_file; diff --git a/drivers/gpu/drm/i915/gem/selftests/i915_gem_mman.c b/drivers/gpu/drm/i915/gem/selftests/i915_gem_mman.c index 743e6ab2c40b..743a098facf2 100644 --- a/drivers/gpu/drm/i915/gem/selftests/i915_gem_mman.c +++ b/drivers/gpu/drm/i915/gem/selftests/i915_gem_mman.c @@ -307,7 +307,7 @@ static int igt_partial_tiling(void *arg) int tiling; int err; - if (!i915_ggtt_has_aperture(&i915->ggtt)) + if (!i915_ggtt_has_aperture(to_gt(i915)->ggtt)) return 0; /* We want to check the page mapping and fencing of a large object @@ -320,7 +320,7 @@ static int igt_partial_tiling(void *arg) obj = huge_gem_object(i915, nreal << PAGE_SHIFT, - (1 + next_prime_number(i915->ggtt.vm.total >> PAGE_SHIFT)) << PAGE_SHIFT); + (1 + next_prime_number(to_gt(i915)->ggtt->vm.total >> PAGE_SHIFT)) << PAGE_SHIFT); if (IS_ERR(obj)) return PTR_ERR(obj); @@ -366,10 +366,10 @@ static int igt_partial_tiling(void *arg) tile.tiling = tiling; switch (tiling) { case I915_TILING_X: - tile.swizzle = i915->ggtt.bit_6_swizzle_x; + tile.swizzle = to_gt(i915)->ggtt->bit_6_swizzle_x; break; case I915_TILING_Y: - tile.swizzle = i915->ggtt.bit_6_swizzle_y; + tile.swizzle = to_gt(i915)->ggtt->bit_6_swizzle_y; break; } @@ -440,7 +440,7 @@ static int igt_smoke_tiling(void *arg) IGT_TIMEOUT(end); int err; - if (!i915_ggtt_has_aperture(&i915->ggtt)) + if (!i915_ggtt_has_aperture(to_gt(i915)->ggtt)) return 0; /* @@ -457,7 +457,7 @@ static int igt_smoke_tiling(void *arg) obj = huge_gem_object(i915, nreal << PAGE_SHIFT, - (1 + next_prime_number(i915->ggtt.vm.total >> PAGE_SHIFT)) << PAGE_SHIFT); + (1 + next_prime_number(to_gt(i915)->ggtt->vm.total >> PAGE_SHIFT)) << PAGE_SHIFT); if (IS_ERR(obj)) return PTR_ERR(obj); @@ -486,10 +486,10 @@ static int igt_smoke_tiling(void *arg) break; case I915_TILING_X: - tile.swizzle = i915->ggtt.bit_6_swizzle_x; + tile.swizzle = to_gt(i915)->ggtt->bit_6_swizzle_x; break; case I915_TILING_Y: - tile.swizzle = i915->ggtt.bit_6_swizzle_y; + tile.swizzle = to_gt(i915)->ggtt->bit_6_swizzle_y; break; } @@ -856,6 +856,7 @@ static int wc_check(struct drm_i915_gem_object *obj) static bool can_mmap(struct drm_i915_gem_object *obj, enum i915_mmap_type type) { + struct drm_i915_private *i915 = to_i915(obj->base.dev); bool no_map; if (obj->ops->mmap_offset) @@ -864,7 +865,7 @@ static bool can_mmap(struct drm_i915_gem_object *obj, enum i915_mmap_type type) return false; if (type == I915_MMAP_TYPE_GTT && - !i915_ggtt_has_aperture(&to_i915(obj->base.dev)->ggtt)) + !i915_ggtt_has_aperture(to_gt(i915)->ggtt)) return false; i915_gem_object_lock(obj, NULL); diff --git a/drivers/gpu/drm/i915/gem/selftests/i915_gem_object.c b/drivers/gpu/drm/i915/gem/selftests/i915_gem_object.c index 740ee8086a27..fe0a890775e2 100644 --- a/drivers/gpu/drm/i915/gem/selftests/i915_gem_object.c +++ b/drivers/gpu/drm/i915/gem/selftests/i915_gem_object.c @@ -43,7 +43,7 @@ static int igt_gem_huge(void *arg) obj = huge_gem_object(i915, nreal * PAGE_SIZE, - i915->ggtt.vm.total + PAGE_SIZE); + to_gt(i915)->ggtt->vm.total + PAGE_SIZE); if (IS_ERR(obj)) return PTR_ERR(obj); -- cgit From 59dc4632e35becca1186dbab83ac7cf8603afaa9 Mon Sep 17 00:00:00 2001 From: Michał Winiarski Date: Sun, 19 Dec 2021 23:24:58 +0200 Subject: drm/i915/display: Use to_gt() helper for GGTT accesses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GGTT is currently available both through i915->ggtt and gt->ggtt, and we eventually want to get rid of the i915->ggtt one. Use to_gt() for all i915->ggtt accesses to help with the future refactoring. Signed-off-by: Michał Winiarski Cc: Michal Wajdeczko Signed-off-by: Andi Shyti Reviewed-by: Sujaritha Sundaresan Reviewed-by: Matt Roper Signed-off-by: Matt Roper Link: https://patchwork.freedesktop.org/patch/msgid/20211219212500.61432-5-andi.shyti@linux.intel.com --- drivers/gpu/drm/i915/display/intel_fbc.c | 2 +- drivers/gpu/drm/i915/display/intel_fbdev.c | 2 +- drivers/gpu/drm/i915/display/intel_plane_initial.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/display/intel_fbc.c b/drivers/gpu/drm/i915/display/intel_fbc.c index c40444206425..9208d52e3cec 100644 --- a/drivers/gpu/drm/i915/display/intel_fbc.c +++ b/drivers/gpu/drm/i915/display/intel_fbc.c @@ -395,7 +395,7 @@ static void gen7_fbc_activate(struct drm_i915_private *dev_priv) SNB_CPU_FENCE_ENABLE | params->fence_id); intel_de_write(dev_priv, DPFC_CPU_FENCE_OFFSET, params->fence_y_offset); - } else if (dev_priv->ggtt.num_fences) { + } else if (to_gt(dev_priv)->ggtt->num_fences) { intel_de_write(dev_priv, SNB_DPFC_CTL_SA, 0); intel_de_write(dev_priv, DPFC_CPU_FENCE_OFFSET, 0); } diff --git a/drivers/gpu/drm/i915/display/intel_fbdev.c b/drivers/gpu/drm/i915/display/intel_fbdev.c index adc3a81be9f7..41d279db2be6 100644 --- a/drivers/gpu/drm/i915/display/intel_fbdev.c +++ b/drivers/gpu/drm/i915/display/intel_fbdev.c @@ -180,7 +180,7 @@ static int intelfb_create(struct drm_fb_helper *helper, struct drm_device *dev = helper->dev; struct drm_i915_private *dev_priv = to_i915(dev); struct pci_dev *pdev = to_pci_dev(dev_priv->drm.dev); - struct i915_ggtt *ggtt = &dev_priv->ggtt; + struct i915_ggtt *ggtt = to_gt(dev_priv)->ggtt; const struct i915_ggtt_view view = { .type = I915_GGTT_VIEW_NORMAL, }; diff --git a/drivers/gpu/drm/i915/display/intel_plane_initial.c b/drivers/gpu/drm/i915/display/intel_plane_initial.c index dcd698a02da2..0d06d9760f42 100644 --- a/drivers/gpu/drm/i915/display/intel_plane_initial.c +++ b/drivers/gpu/drm/i915/display/intel_plane_initial.c @@ -93,7 +93,7 @@ initial_plane_vma(struct drm_i915_private *i915, goto err_obj; } - vma = i915_vma_instance(obj, &i915->ggtt.vm, NULL); + vma = i915_vma_instance(obj, &to_gt(i915)->ggtt->vm, NULL); if (IS_ERR(vma)) goto err_obj; -- cgit From 17190a3492bb20ce1cc695fdadc7e4f7ad5b44ff Mon Sep 17 00:00:00 2001 From: Michał Winiarski Date: Sun, 19 Dec 2021 23:24:59 +0200 Subject: drm/i915/selftests: Use to_gt() helper for GGTT accesses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GGTT is currently available both through i915->ggtt and gt->ggtt, and we eventually want to get rid of the i915->ggtt one. Use to_gt() for all i915->ggtt accesses to help with the future refactoring. Signed-off-by: Michał Winiarski Cc: Michal Wajdeczko Signed-off-by: Andi Shyti Reviewed-by: Sujaritha Sundaresan Reviewed-by: Matt Roper Signed-off-by: Matt Roper Link: https://patchwork.freedesktop.org/patch/msgid/20211219212500.61432-6-andi.shyti@linux.intel.com --- drivers/gpu/drm/i915/selftests/i915_gem.c | 8 ++++---- drivers/gpu/drm/i915/selftests/i915_gem_gtt.c | 6 +++--- drivers/gpu/drm/i915/selftests/i915_request.c | 2 +- drivers/gpu/drm/i915/selftests/i915_vma.c | 2 +- drivers/gpu/drm/i915/selftests/mock_gem_device.c | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/selftests/i915_gem.c b/drivers/gpu/drm/i915/selftests/i915_gem.c index b5576888cd78..1628b81d0a35 100644 --- a/drivers/gpu/drm/i915/selftests/i915_gem.c +++ b/drivers/gpu/drm/i915/selftests/i915_gem.c @@ -41,7 +41,7 @@ static int switch_to_context(struct i915_gem_context *ctx) static void trash_stolen(struct drm_i915_private *i915) { - struct i915_ggtt *ggtt = &i915->ggtt; + struct i915_ggtt *ggtt = to_gt(i915)->ggtt; const u64 slot = ggtt->error_capture.start; const resource_size_t size = resource_size(&i915->dsm); unsigned long page; @@ -99,7 +99,7 @@ static void igt_pm_suspend(struct drm_i915_private *i915) intel_wakeref_t wakeref; with_intel_runtime_pm(&i915->runtime_pm, wakeref) { - i915_ggtt_suspend(&i915->ggtt); + i915_ggtt_suspend(to_gt(i915)->ggtt); i915_gem_suspend_late(i915); } } @@ -109,7 +109,7 @@ static void igt_pm_hibernate(struct drm_i915_private *i915) intel_wakeref_t wakeref; with_intel_runtime_pm(&i915->runtime_pm, wakeref) { - i915_ggtt_suspend(&i915->ggtt); + i915_ggtt_suspend(to_gt(i915)->ggtt); i915_gem_freeze(i915); i915_gem_freeze_late(i915); @@ -125,7 +125,7 @@ static void igt_pm_resume(struct drm_i915_private *i915) * that runtime-pm just works. */ with_intel_runtime_pm(&i915->runtime_pm, wakeref) { - i915_ggtt_resume(&i915->ggtt); + i915_ggtt_resume(to_gt(i915)->ggtt); i915_gem_resume(i915); } } diff --git a/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c b/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c index 575705c3bce9..462bb39347a2 100644 --- a/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c +++ b/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c @@ -1122,7 +1122,7 @@ static int exercise_ggtt(struct drm_i915_private *i915, u64 hole_start, u64 hole_end, unsigned long end_time)) { - struct i915_ggtt *ggtt = &i915->ggtt; + struct i915_ggtt *ggtt = to_gt(i915)->ggtt; u64 hole_start, hole_end, last = 0; struct drm_mm_node *node; IGT_TIMEOUT(end_time); @@ -1182,7 +1182,7 @@ static int igt_ggtt_page(void *arg) const unsigned int count = PAGE_SIZE/sizeof(u32); I915_RND_STATE(prng); struct drm_i915_private *i915 = arg; - struct i915_ggtt *ggtt = &i915->ggtt; + struct i915_ggtt *ggtt = to_gt(i915)->ggtt; struct drm_i915_gem_object *obj; intel_wakeref_t wakeref; struct drm_mm_node tmp; @@ -2114,7 +2114,7 @@ int i915_gem_gtt_live_selftests(struct drm_i915_private *i915) SUBTEST(igt_cs_tlb), }; - GEM_BUG_ON(offset_in_page(i915->ggtt.vm.total)); + GEM_BUG_ON(offset_in_page(to_gt(i915)->ggtt->vm.total)); return i915_subtests(tests, i915); } diff --git a/drivers/gpu/drm/i915/selftests/i915_request.c b/drivers/gpu/drm/i915/selftests/i915_request.c index 92a859b34190..7f66f6d299b2 100644 --- a/drivers/gpu/drm/i915/selftests/i915_request.c +++ b/drivers/gpu/drm/i915/selftests/i915_request.c @@ -843,7 +843,7 @@ static struct i915_vma *empty_batch(struct drm_i915_private *i915) intel_gt_chipset_flush(to_gt(i915)); - vma = i915_vma_instance(obj, &i915->ggtt.vm, NULL); + vma = i915_vma_instance(obj, &to_gt(i915)->ggtt->vm, NULL); if (IS_ERR(vma)) { err = PTR_ERR(vma); goto err; diff --git a/drivers/gpu/drm/i915/selftests/i915_vma.c b/drivers/gpu/drm/i915/selftests/i915_vma.c index 5c5809dfe9b2..5ba0f1052eee 100644 --- a/drivers/gpu/drm/i915/selftests/i915_vma.c +++ b/drivers/gpu/drm/i915/selftests/i915_vma.c @@ -982,7 +982,7 @@ static int igt_vma_remapped_gtt(void *arg) intel_wakeref_t wakeref; int err = 0; - if (!i915_ggtt_has_aperture(&i915->ggtt)) + if (!i915_ggtt_has_aperture(to_gt(i915)->ggtt)) return 0; obj = i915_gem_object_create_internal(i915, 10 * 10 * PAGE_SIZE); diff --git a/drivers/gpu/drm/i915/selftests/mock_gem_device.c b/drivers/gpu/drm/i915/selftests/mock_gem_device.c index 8aa7b1d33865..0b469ae0f474 100644 --- a/drivers/gpu/drm/i915/selftests/mock_gem_device.c +++ b/drivers/gpu/drm/i915/selftests/mock_gem_device.c @@ -69,7 +69,7 @@ static void mock_device_release(struct drm_device *dev) i915_gem_drain_workqueue(i915); i915_gem_drain_freed_objects(i915); - mock_fini_ggtt(&i915->ggtt); + mock_fini_ggtt(to_gt(i915)->ggtt); destroy_workqueue(i915->wq); intel_region_ttm_device_fini(i915); -- cgit From cdeea858d8df9891ed75415f377b3564f899a659 Mon Sep 17 00:00:00 2001 From: Andi Shyti Date: Sun, 19 Dec 2021 23:25:00 +0200 Subject: drm/i915: Remove unused i915->ggtt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reference to the GGTT from the private date is not used anymore. Remove it. The ggtt in the root gt will now be dynamically allocated and the deallocation handled by the drmm_* managed allocation. Suggested-by: Matt Roper Signed-off-by: Andi Shyti Cc: Michał Winiarski Reviewed-by: Sujaritha Sundaresan Reviewed-by: Matt Roper Signed-off-by: Matt Roper Link: https://patchwork.freedesktop.org/patch/msgid/20211219212500.61432-7-andi.shyti@linux.intel.com --- drivers/gpu/drm/i915/gt/intel_gt.c | 8 ++++++-- drivers/gpu/drm/i915/gt/intel_gt.h | 2 +- drivers/gpu/drm/i915/i915_drv.c | 4 +++- drivers/gpu/drm/i915/i915_drv.h | 2 -- drivers/gpu/drm/i915/selftests/i915_gem_gtt.c | 20 +++++++++++--------- drivers/gpu/drm/i915/selftests/i915_vma.c | 20 +++++++++++--------- drivers/gpu/drm/i915/selftests/mock_gem_device.c | 9 +++++++-- drivers/gpu/drm/i915/selftests/mock_gtt.c | 9 ++++----- drivers/gpu/drm/i915/selftests/mock_gtt.h | 3 ++- 9 files changed, 45 insertions(+), 32 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/intel_gt.c b/drivers/gpu/drm/i915/gt/intel_gt.c index 446e56ce7f70..3ebf8136e44b 100644 --- a/drivers/gpu/drm/i915/gt/intel_gt.c +++ b/drivers/gpu/drm/i915/gt/intel_gt.c @@ -3,6 +3,8 @@ * Copyright © 2019 Intel Corporation */ +#include + #include "intel_gt_debugfs.h" #include "gem/i915_gem_lmem.h" @@ -83,9 +85,11 @@ int intel_gt_probe_lmem(struct intel_gt *gt) return 0; } -void intel_gt_init_hw_early(struct intel_gt *gt, struct i915_ggtt *ggtt) +int intel_gt_assign_ggtt(struct intel_gt *gt) { - gt->ggtt = ggtt; + gt->ggtt = drmm_kzalloc(>->i915->drm, sizeof(*gt->ggtt), GFP_KERNEL); + + return gt->ggtt ? 0 : -ENOMEM; } static const struct intel_mmio_range icl_l3bank_steering_table[] = { diff --git a/drivers/gpu/drm/i915/gt/intel_gt.h b/drivers/gpu/drm/i915/gt/intel_gt.h index 3ace129eb2af..94e1bac8c0cc 100644 --- a/drivers/gpu/drm/i915/gt/intel_gt.h +++ b/drivers/gpu/drm/i915/gt/intel_gt.h @@ -36,7 +36,7 @@ static inline struct intel_gt *huc_to_gt(struct intel_huc *huc) void intel_gt_init_early(struct intel_gt *gt, struct drm_i915_private *i915); void __intel_gt_init_early(struct intel_gt *gt, struct drm_i915_private *i915); -void intel_gt_init_hw_early(struct intel_gt *gt, struct i915_ggtt *ggtt); +int intel_gt_assign_ggtt(struct intel_gt *gt); int intel_gt_probe_lmem(struct intel_gt *gt); int intel_gt_init_mmio(struct intel_gt *gt); int __must_check intel_gt_init_hw(struct intel_gt *gt); diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c index c23a27e02e95..d61b4a3016f8 100644 --- a/drivers/gpu/drm/i915/i915_drv.c +++ b/drivers/gpu/drm/i915/i915_drv.c @@ -569,7 +569,9 @@ static int i915_driver_hw_probe(struct drm_i915_private *dev_priv) i915_perf_init(dev_priv); - intel_gt_init_hw_early(to_gt(dev_priv), &dev_priv->ggtt); + ret = intel_gt_assign_ggtt(to_gt(dev_priv)); + if (ret) + goto err_perf; ret = i915_ggtt_probe_hw(dev_priv); if (ret) diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index bce55d1081b3..63a579e952b9 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -1008,8 +1008,6 @@ struct drm_i915_private { struct drm_atomic_state *modeset_restore_state; struct drm_modeset_acquire_ctx reset_ctx; - struct i915_ggtt ggtt; /* VM representing the global address space */ - struct i915_gem_mm mm; /* Kernel Modesetting */ diff --git a/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c b/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c index 462bb39347a2..357ced0b88e7 100644 --- a/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c +++ b/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c @@ -1737,26 +1737,28 @@ int i915_gem_gtt_mock_selftests(void) SUBTEST(igt_gtt_insert), }; struct drm_i915_private *i915; - struct i915_ggtt *ggtt; + struct intel_gt *gt; int err; i915 = mock_gem_device(); if (!i915) return -ENOMEM; - ggtt = kmalloc(sizeof(*ggtt), GFP_KERNEL); - if (!ggtt) { - err = -ENOMEM; + /* allocate the ggtt */ + err = intel_gt_assign_ggtt(to_gt(i915)); + if (err) goto out_put; - } - mock_init_ggtt(i915, ggtt); - err = i915_subtests(tests, ggtt); + gt = to_gt(i915); + + mock_init_ggtt(gt); + + err = i915_subtests(tests, gt->ggtt); mock_device_flush(i915); i915_gem_drain_freed_objects(i915); - mock_fini_ggtt(ggtt); - kfree(ggtt); + mock_fini_ggtt(gt->ggtt); + out_put: mock_destroy_device(i915); return err; diff --git a/drivers/gpu/drm/i915/selftests/i915_vma.c b/drivers/gpu/drm/i915/selftests/i915_vma.c index 5ba0f1052eee..de37cfa4c65f 100644 --- a/drivers/gpu/drm/i915/selftests/i915_vma.c +++ b/drivers/gpu/drm/i915/selftests/i915_vma.c @@ -922,26 +922,28 @@ int i915_vma_mock_selftests(void) SUBTEST(igt_vma_partial), }; struct drm_i915_private *i915; - struct i915_ggtt *ggtt; + struct intel_gt *gt; int err; i915 = mock_gem_device(); if (!i915) return -ENOMEM; - ggtt = kmalloc(sizeof(*ggtt), GFP_KERNEL); - if (!ggtt) { - err = -ENOMEM; + /* allocate the ggtt */ + err = intel_gt_assign_ggtt(to_gt(i915)); + if (err) goto out_put; - } - mock_init_ggtt(i915, ggtt); - err = i915_subtests(tests, ggtt); + gt = to_gt(i915); + + mock_init_ggtt(gt); + + err = i915_subtests(tests, gt->ggtt); mock_device_flush(i915); i915_gem_drain_freed_objects(i915); - mock_fini_ggtt(ggtt); - kfree(ggtt); + mock_fini_ggtt(gt->ggtt); + out_put: mock_destroy_device(i915); return err; diff --git a/drivers/gpu/drm/i915/selftests/mock_gem_device.c b/drivers/gpu/drm/i915/selftests/mock_gem_device.c index 0b469ae0f474..28a0f054009a 100644 --- a/drivers/gpu/drm/i915/selftests/mock_gem_device.c +++ b/drivers/gpu/drm/i915/selftests/mock_gem_device.c @@ -194,8 +194,13 @@ struct drm_i915_private *mock_gem_device(void) mock_init_contexts(i915); - mock_init_ggtt(i915, &i915->ggtt); - to_gt(i915)->vm = i915_vm_get(&i915->ggtt.vm); + /* allocate the ggtt */ + ret = intel_gt_assign_ggtt(to_gt(i915)); + if (ret) + goto err_unlock; + + mock_init_ggtt(to_gt(i915)); + to_gt(i915)->vm = i915_vm_get(&to_gt(i915)->ggtt->vm); mkwrite_device_info(i915)->platform_engine_mask = BIT(0); to_gt(i915)->info.engine_mask = BIT(0); diff --git a/drivers/gpu/drm/i915/selftests/mock_gtt.c b/drivers/gpu/drm/i915/selftests/mock_gtt.c index 1802baf80a17..7863d0f4bbd7 100644 --- a/drivers/gpu/drm/i915/selftests/mock_gtt.c +++ b/drivers/gpu/drm/i915/selftests/mock_gtt.c @@ -104,12 +104,12 @@ static void mock_unbind_ggtt(struct i915_address_space *vm, { } -void mock_init_ggtt(struct drm_i915_private *i915, struct i915_ggtt *ggtt) +void mock_init_ggtt(struct intel_gt *gt) { - memset(ggtt, 0, sizeof(*ggtt)); + struct i915_ggtt *ggtt = gt->ggtt; - ggtt->vm.gt = to_gt(i915); - ggtt->vm.i915 = i915; + ggtt->vm.gt = gt; + ggtt->vm.i915 = gt->i915; ggtt->vm.is_ggtt = true; ggtt->gmadr = (struct resource) DEFINE_RES_MEM(0, 2048 * PAGE_SIZE); @@ -128,7 +128,6 @@ void mock_init_ggtt(struct drm_i915_private *i915, struct i915_ggtt *ggtt) ggtt->vm.vma_ops.unbind_vma = mock_unbind_ggtt; i915_address_space_init(&ggtt->vm, VM_CLASS_GGTT); - to_gt(i915)->ggtt = ggtt; } void mock_fini_ggtt(struct i915_ggtt *ggtt) diff --git a/drivers/gpu/drm/i915/selftests/mock_gtt.h b/drivers/gpu/drm/i915/selftests/mock_gtt.h index e3f224f43beb..d6eb90bd7f3f 100644 --- a/drivers/gpu/drm/i915/selftests/mock_gtt.h +++ b/drivers/gpu/drm/i915/selftests/mock_gtt.h @@ -27,8 +27,9 @@ struct drm_i915_private; struct i915_ggtt; +struct intel_gt; -void mock_init_ggtt(struct drm_i915_private *i915, struct i915_ggtt *ggtt); +void mock_init_ggtt(struct intel_gt *gt); void mock_fini_ggtt(struct i915_ggtt *ggtt); struct i915_ppgtt *mock_ppgtt(struct drm_i915_private *i915, const char *name); -- cgit From d14f0c205302648df29c494a1a81ec3d897a2552 Mon Sep 17 00:00:00 2001 From: José Expósito Date: Sun, 26 Dec 2021 12:24:58 +0100 Subject: drm/plane: Make format_mod_supported truly optional MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The documentation for "drm_plane_funcs.format_mod_supported" reads: This *optional* hook is used for the DRM to determine if the given format/modifier combination is valid for the plane. This allows the DRM to generate the correct format bitmask (which formats apply to which modifier), and to validate modifiers at atomic_check time. *If not present*, then any modifier in the plane's modifier list is allowed with any of the plane's formats. However, where the function is not present, an invalid IN_FORMATS blob property with modifiers but no formats is exposed to user-space. This breaks the latest Weston [1]. For testing purposes, I extracted the affected code to a standalone program [2]. Make "create_in_format_blob" behave as documented. [1] https://gitlab.freedesktop.org/wayland/weston/-/blob/9.0/libweston/backend-drm/kms.c#L431 [2] https://github.com/JoseExposito/drm-sandbox/blob/main/in_formats.c Signed-off-by: José Expósito Reviewed-by: Simon Ser Signed-off-by: Simon Ser Link: https://patchwork.freedesktop.org/patch/msgid/20211226112503.31771-2-jose.exposito89@gmail.com --- drivers/gpu/drm/drm_plane.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/drm_plane.c b/drivers/gpu/drm/drm_plane.c index 82afb854141b..deeec60a3315 100644 --- a/drivers/gpu/drm/drm_plane.c +++ b/drivers/gpu/drm/drm_plane.c @@ -202,17 +202,13 @@ static int create_in_format_blob(struct drm_device *dev, struct drm_plane *plane memcpy(formats_ptr(blob_data), plane->format_types, formats_size); - /* If we can't determine support, just bail */ - if (!plane->funcs->format_mod_supported) - goto done; - mod = modifiers_ptr(blob_data); for (i = 0; i < plane->modifier_count; i++) { for (j = 0; j < plane->format_count; j++) { - if (plane->funcs->format_mod_supported(plane, + if (!plane->funcs->format_mod_supported || + plane->funcs->format_mod_supported(plane, plane->format_types[j], plane->modifiers[i])) { - mod->formats |= 1ULL << j; } } @@ -223,7 +219,6 @@ static int create_in_format_blob(struct drm_device *dev, struct drm_plane *plane mod++; } -done: drm_object_attach_property(&plane->base, config->modifiers_property, blob->base.id); -- cgit From 506d9d7414cfbdce02b7126fc8644f04ef234680 Mon Sep 17 00:00:00 2001 From: yangcong Date: Fri, 31 Dec 2021 12:50:56 +0800 Subject: drm/panel: Update Boe-tv110c9m initial code Optimize two problems a)Turn off low voltage detection register.During the esd test, the low-voltage detection ic may be triggered to enter the slpin state, so a black screen occurs on the panel. b)Optimize CMD2 page1 gamma. Signed-off-by: yangcong Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patchwork.freedesktop.org/patch/msgid/20211231045056.118640-1-yangcong5@huaqin.corp-partner.google.com --- drivers/gpu/drm/panel/panel-boe-tv101wum-nl6.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/panel/panel-boe-tv101wum-nl6.c b/drivers/gpu/drm/panel/panel-boe-tv101wum-nl6.c index 5fcbde789ddb..1be150ac758f 100644 --- a/drivers/gpu/drm/panel/panel-boe-tv101wum-nl6.c +++ b/drivers/gpu/drm/panel/panel-boe-tv101wum-nl6.c @@ -86,7 +86,7 @@ static const struct panel_init_cmd boe_tv110c9m_init_cmd[] = { _INIT_DCS_CMD(0x0F, 0x73), _INIT_DCS_CMD(0x95, 0xE6), _INIT_DCS_CMD(0x96, 0xF0), - _INIT_DCS_CMD(0x30, 0x11), + _INIT_DCS_CMD(0x30, 0x00), _INIT_DCS_CMD(0x6D, 0x66), _INIT_DCS_CMD(0x75, 0xA2), _INIT_DCS_CMD(0x77, 0x3B), @@ -112,17 +112,17 @@ static const struct panel_init_cmd boe_tv110c9m_init_cmd[] = { _INIT_DCS_CMD(0xB1, 0x00, 0xD2, 0x01, 0x0B, 0x01, 0x34, 0x01, 0x76, 0x01, 0xA3, 0x01, 0xEF, 0x02, 0x27, 0x02, 0x29), _INIT_DCS_CMD(0xB2, 0x02, 0x5F, 0x02, 0x9E, 0x02, 0xC9, 0x03, 0x00, 0x03, 0x26, 0x03, 0x53, 0x03, 0x63, 0x03, 0x73), - _INIT_DCS_CMD(0xB3, 0x03, 0x86, 0x03, 0x9A, 0x03, 0xA7, 0x03, 0xCF, 0x03, 0xDE, 0x03, 0xE0), + _INIT_DCS_CMD(0xB3, 0x03, 0x86, 0x03, 0x9A, 0x03, 0xAF, 0x03, 0xDF, 0x03, 0xF5, 0x03, 0xE0), _INIT_DCS_CMD(0xB4, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x45, 0x00, 0x65, 0x00, 0x81, 0x00, 0x99, 0x00, 0xAE, 0x00, 0xC1), _INIT_DCS_CMD(0xB5, 0x00, 0xD2, 0x01, 0x0B, 0x01, 0x34, 0x01, 0x76, 0x01, 0xA3, 0x01, 0xEF, 0x02, 0x27, 0x02, 0x29), _INIT_DCS_CMD(0xB6, 0x02, 0x5F, 0x02, 0x9E, 0x02, 0xC9, 0x03, 0x00, 0x03, 0x26, 0x03, 0x53, 0x03, 0x63, 0x03, 0x73), - _INIT_DCS_CMD(0xB7, 0x03, 0x86, 0x03, 0x9A, 0x03, 0xA7, 0x03, 0xCF, 0x03, 0xDE, 0x03, 0xE0), + _INIT_DCS_CMD(0xB7, 0x03, 0x86, 0x03, 0x9A, 0x03, 0xAF, 0x03, 0xDF, 0x03, 0xF5, 0x03, 0xE0), _INIT_DCS_CMD(0xB8, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x45, 0x00, 0x65, 0x00, 0x81, 0x00, 0x99, 0x00, 0xAE, 0x00, 0xC1), _INIT_DCS_CMD(0xB9, 0x00, 0xD2, 0x01, 0x0B, 0x01, 0x34, 0x01, 0x76, 0x01, 0xA3, 0x01, 0xEF, 0x02, 0x27, 0x02, 0x29), _INIT_DCS_CMD(0xBA, 0x02, 0x5F, 0x02, 0x9E, 0x02, 0xC9, 0x03, 0x00, 0x03, 0x26, 0x03, 0x53, 0x03, 0x63, 0x03, 0x73), - _INIT_DCS_CMD(0xBB, 0x03, 0x86, 0x03, 0x9A, 0x03, 0xA7, 0x03, 0xCF, 0x03, 0xDE, 0x03, 0xE0), + _INIT_DCS_CMD(0xBB, 0x03, 0x86, 0x03, 0x9A, 0x03, 0xAF, 0x03, 0xDF, 0x03, 0xF5, 0x03, 0xE0), _INIT_DCS_CMD(0xFF, 0x24), _INIT_DCS_CMD(0xFB, 0x01), -- cgit From 59a39fcc21c56845e74bf60c1177925bb0255de4 Mon Sep 17 00:00:00 2001 From: KuoHsiang Chou Date: Wed, 29 Dec 2021 16:27:49 +0800 Subject: drm/ast: Enable the supporting of wide screen on AST2600 Enable the supporting of wide sscreen on AST2600, so that the resolution of 16:9 and 16:10 are able to be selected on Display Settings. Signed-off-by: KuoHsiang Chou Signed-off-by: Thomas Zimmermann Link: https://patchwork.freedesktop.org/patch/msgid/20211229082749.5415-1-kuohsiang_chou@aspeedtech.com --- drivers/gpu/drm/ast/ast_main.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/ast/ast_main.c b/drivers/gpu/drm/ast/ast_main.c index 79a361867955..22e9e2d3c71a 100644 --- a/drivers/gpu/drm/ast/ast_main.c +++ b/drivers/gpu/drm/ast/ast_main.c @@ -209,6 +209,8 @@ static int ast_detect_chip(struct drm_device *dev, bool *need_post) if (ast->chip == AST2500 && scu_rev == 0x100) /* ast2510 */ ast->support_wide_screen = true; + if (ast->chip == AST2600) /* ast2600 */ + ast->support_wide_screen = true; } break; } -- cgit From 36e195467ecca6a55fd1a9374d3109692f381fd7 Mon Sep 17 00:00:00 2001 From: Ivaylo Dimitrov Date: Wed, 5 Jan 2022 17:36:58 +0200 Subject: drm: omapdrm: Fix implicit dma_buf fencing Currently omapdrm driver does not initialize dma_buf_export_info resv member, which leads to a new dma_resv being allocated and attached to the exported dma_buf. This leads to the issue that fences created on dma_buf objects imported by other drivers are ignored by omapdrm, as only fences in gem object resv are waited on. This leads to various issues like displaying incomplete frames. Fix that by initializing dma_buf resv to the resv of the gem object being exported. Signed-off-by: Ivaylo Dimitrov Tested-by: Merlijn Wajer Tested-by: Tony Lindgren Signed-off-by: Tomi Valkeinen Link: https://patchwork.freedesktop.org/patch/msgid/1641397018-29872-1-git-send-email-ivo.g.dimitrov.75@gmail.com --- drivers/gpu/drm/omapdrm/omap_gem_dmabuf.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/omapdrm/omap_gem_dmabuf.c b/drivers/gpu/drm/omapdrm/omap_gem_dmabuf.c index 57af3d97be77..ab3fc86e7256 100644 --- a/drivers/gpu/drm/omapdrm/omap_gem_dmabuf.c +++ b/drivers/gpu/drm/omapdrm/omap_gem_dmabuf.c @@ -93,6 +93,7 @@ struct dma_buf *omap_gem_prime_export(struct drm_gem_object *obj, int flags) exp_info.size = omap_gem_mmap_size(obj); exp_info.flags = flags; exp_info.priv = obj; + exp_info.resv = obj->resv; return drm_gem_dmabuf_export(obj->dev, &exp_info); } -- cgit From 5313fb2c779f74bc5083e9d3738d9b2c2ebe0aa4 Mon Sep 17 00:00:00 2001 From: Jiri Vanek Date: Thu, 6 Jan 2022 20:00:27 +0100 Subject: drm/bridge/tc358775: Fix for dual-link LVDS Fixed wrong register shift for single/dual link LVDS output. Tested-by: Jiri Vanek Signed-off-by: Jiri Vanek Reviewed-by: Vinay Simha BN Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20220106190027.1498-1-jirivanek1@gmail.com --- drivers/gpu/drm/bridge/tc358775.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/tc358775.c b/drivers/gpu/drm/bridge/tc358775.c index 2c76331b251d..4ad4fc251b93 100644 --- a/drivers/gpu/drm/bridge/tc358775.c +++ b/drivers/gpu/drm/bridge/tc358775.c @@ -241,7 +241,7 @@ static inline u32 TC358775_LVCFG_PCLKDIV(uint32_t val) } #define TC358775_LVCFG_LVDLINK__MASK 0x00000002 -#define TC358775_LVCFG_LVDLINK__SHIFT 0 +#define TC358775_LVCFG_LVDLINK__SHIFT 1 static inline u32 TC358775_LVCFG_LVDLINK(uint32_t val) { return ((val) << TC358775_LVCFG_LVDLINK__SHIFT) & -- cgit From 57ded5fc98b11d76dae505ca3591b61c9dbbbda7 Mon Sep 17 00:00:00 2001 From: Juston Li Date: Thu, 6 Jan 2022 12:02:36 -0800 Subject: drm/i915/pxp: Hold RPM wakelock during PXP unbind Similar to commit b8d8436840ca ("drm/i915/gt: Hold RPM wakelock during PXP suspend") but to fix the same warning for unbind during shutdown: ------------[ cut here ]------------ RPM wakelock ref not held during HW access WARNING: CPU: 0 PID: 4139 at drivers/gpu/drm/i915/intel_runtime_pm.h:115 gen12_fwtable_write32+0x1b7/0 Modules linked in: 8021q ccm rfcomm cmac algif_hash algif_skcipher af_alg uinput snd_hda_codec_hdmi vf industrialio iwl7000_mac80211 cros_ec_sensorhub lzo_rle lzo_compress zram iwlwifi cfg80211 joydev CPU: 0 PID: 4139 Comm: halt Tainted: G U W 5.10.84 #13 344e11e079c4a03940d949e537eab645f6 RIP: 0010:gen12_fwtable_write32+0x1b7/0x200 Code: 48 c7 c7 fc b3 b5 89 31 c0 e8 2c f3 ad ff 0f 0b e9 04 ff ff ff c6 05 71 e9 1d 01 01 48 c7 c7 d67 RSP: 0018:ffffa09ec0bb3bb0 EFLAGS: 00010246 RAX: 12dde97bbd260300 RBX: 00000000000320f0 RCX: ffffffff89e60ea0 RDX: 0000000000000000 RSI: 00000000ffffdfff RDI: ffffffff89e60e70 RBP: ffffa09ec0bb3bd8 R08: 0000000000000000 R09: ffffa09ec0bb3950 R10: 00000000ffffdfff R11: ffffffff89e91160 R12: 0000000000000000 R13: 0000000028121969 R14: ffff9515c32f0990 R15: 0000000040000000 FS: 0000790dcf225740(0000) GS:ffff951737800000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 000058b25efae147 CR3: 0000000133ea6001 CR4: 0000000000770ef0 DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 DR3: 0000000000000000 DR6: 00000000ffff07f0 DR7: 0000000000000400 PKRU: 55555554 Call Trace: intel_pxp_fini_hw+0x2f/0x39 i915_pxp_tee_component_unbind+0x1c/0x42 component_unbind+0x32/0x48 component_unbind_all+0x80/0x9d take_down_master+0x24/0x36 component_master_del+0x56/0x70 mei_pxp_remove+0x2c/0x68 mei_cl_device_remove+0x35/0x68 device_release_driver_internal+0x100/0x1a1 mei_cl_bus_remove_device+0x21/0x79 mei_cl_bus_remove_devices+0x3b/0x51 mei_stop+0x3b/0xae mei_me_shutdown+0x23/0x58 device_shutdown+0x144/0x1d3 kernel_power_off+0x13/0x4c __se_sys_reboot+0x1d4/0x1e9 do_syscall_64+0x43/0x55 entry_SYSCALL_64_after_hwframe+0x44/0xa9 RIP: 0033:0x790dcf316273 Code: 64 89 01 48 83 c8 ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 89 fa be 69 19 12 28 bf ad8 RSP: 002b:00007ffca0df9198 EFLAGS: 00000202 ORIG_RAX: 00000000000000a9 RAX: ffffffffffffffda RBX: 000000004321fedc RCX: 0000790dcf316273 RDX: 000000004321fedc RSI: 0000000028121969 RDI: 00000000fee1dead RBP: 00007ffca0df9200 R08: 0000000000000007 R09: 0000563ce8cd8970 R10: 0000000000000000 R11: 0000000000000202 R12: 00007ffca0df9308 R13: 0000000000000001 R14: 0000000000000000 R15: 0000000000000003 ---[ end trace 2f501b01b348f114 ]--- ACPI: Preparing to enter system sleep state S5 reboot: Power down Changes since v1: - Rebase to latest drm-tip Fixes: 0cfab4cb3c4e ("drm/i915/pxp: Enable PXP power management") Suggested-by: Lee Shawn C Signed-off-by: Juston Li Reviewed-by: Daniele Ceraolo Spurio Signed-off-by: John Harrison Link: https://patchwork.freedesktop.org/patch/msgid/20220106200236.489656-2-juston.li@intel.com --- drivers/gpu/drm/i915/pxp/intel_pxp_tee.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/pxp/intel_pxp_tee.c b/drivers/gpu/drm/i915/pxp/intel_pxp_tee.c index 7408d2f93d01..20c3ce0c2e19 100644 --- a/drivers/gpu/drm/i915/pxp/intel_pxp_tee.c +++ b/drivers/gpu/drm/i915/pxp/intel_pxp_tee.c @@ -105,9 +105,12 @@ static int i915_pxp_tee_component_bind(struct device *i915_kdev, static void i915_pxp_tee_component_unbind(struct device *i915_kdev, struct device *tee_kdev, void *data) { + struct drm_i915_private *i915 = kdev_to_i915(i915_kdev); struct intel_pxp *pxp = i915_dev_to_pxp(i915_kdev); + intel_wakeref_t wakeref; - intel_pxp_fini_hw(pxp); + with_intel_runtime_pm_if_in_use(&i915->runtime_pm, wakeref) + intel_pxp_fini_hw(pxp); mutex_lock(&pxp->tee_mutex); pxp->pxp_component = NULL; -- cgit From 5edaa2b9748979b08fcdb2f65781aac39e6ae011 Mon Sep 17 00:00:00 2001 From: José Expósito Date: Fri, 7 Jan 2022 19:28:07 +0100 Subject: drm/vkms: refactor overlay plane creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the logic to create an overlay plane to its own function. Refactor, no functional changes. Signed-off-by: José Expósito Reviewed-by: Melissa Wen Signed-off-by: Melissa Wen Link: https://patchwork.freedesktop.org/patch/msgid/20220107182809.141003-1-jose.exposito89@gmail.com --- drivers/gpu/drm/vkms/vkms_output.c | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c index 04406bd3ff02..2e805b2d36ae 100644 --- a/drivers/gpu/drm/vkms/vkms_output.c +++ b/drivers/gpu/drm/vkms/vkms_output.c @@ -32,6 +32,21 @@ static const struct drm_connector_helper_funcs vkms_conn_helper_funcs = { .get_modes = vkms_conn_get_modes, }; +static int vkms_add_overlay_plane(struct vkms_device *vkmsdev, int index, + struct drm_crtc *crtc) +{ + struct vkms_plane *overlay; + + overlay = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_OVERLAY, index); + if (IS_ERR(overlay)) + return PTR_ERR(overlay); + + if (!overlay->base.possible_crtcs) + overlay->base.possible_crtcs = drm_crtc_mask(crtc); + + return 0; +} + int vkms_output_init(struct vkms_device *vkmsdev, int index) { struct vkms_output *output = &vkmsdev->output; @@ -39,7 +54,7 @@ int vkms_output_init(struct vkms_device *vkmsdev, int index) struct drm_connector *connector = &output->connector; struct drm_encoder *encoder = &output->encoder; struct drm_crtc *crtc = &output->crtc; - struct vkms_plane *primary, *cursor = NULL, *overlay = NULL; + struct vkms_plane *primary, *cursor = NULL; int ret; int writeback; @@ -48,12 +63,9 @@ int vkms_output_init(struct vkms_device *vkmsdev, int index) return PTR_ERR(primary); if (vkmsdev->config->overlay) { - overlay = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_OVERLAY, index); - if (IS_ERR(overlay)) - return PTR_ERR(overlay); - - if (!overlay->base.possible_crtcs) - overlay->base.possible_crtcs = drm_crtc_mask(crtc); + ret = vkms_add_overlay_plane(vkmsdev, index, crtc); + if (ret) + return ret; } if (vkmsdev->config->cursor) { -- cgit From df2d385cb4132e945d5bc17d387d0fb6f5d4d66e Mon Sep 17 00:00:00 2001 From: José Expósito Date: Fri, 7 Jan 2022 19:28:08 +0100 Subject: drm/vkms: add support for multiple overlay planes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create 8 overlay planes instead of 1 when the "enable_overlay" module parameter is set. The following igt-gpu-tools tests were executed without finding regressions. Notice than the numbers are identical: | master branch | this patch | | SUCCESS | SKIP | FAIL | SUCCESS | SKIP | FAIL | kms_atomic | 10 | 02 | 00 | 10 | 02 | 00 | kms_plane_cursor | 09 | 45 | 00 | 09 | 45 | 00 | kms_plane_multiple | 01 | 23 | 00 | 01 | 23 | 00 | kms_writeback | 04 | 00 | 00 | 04 | 00 | 00 | Signed-off-by: José Expósito Reviewed-by: Melissa Wen Signed-off-by: Melissa Wen Link: https://patchwork.freedesktop.org/patch/msgid/20220107182809.141003-2-jose.exposito89@gmail.com --- drivers/gpu/drm/vkms/vkms_drv.h | 2 ++ drivers/gpu/drm/vkms/vkms_output.c | 9 ++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/vkms/vkms_drv.h b/drivers/gpu/drm/vkms/vkms_drv.h index d48c23d40ce5..9496fdc900b8 100644 --- a/drivers/gpu/drm/vkms/vkms_drv.h +++ b/drivers/gpu/drm/vkms/vkms_drv.h @@ -20,6 +20,8 @@ #define XRES_MAX 8192 #define YRES_MAX 8192 +#define NUM_OVERLAY_PLANES 8 + struct vkms_writeback_job { struct dma_buf_map map[DRM_FORMAT_MAX_PLANES]; struct dma_buf_map data[DRM_FORMAT_MAX_PLANES]; diff --git a/drivers/gpu/drm/vkms/vkms_output.c b/drivers/gpu/drm/vkms/vkms_output.c index 2e805b2d36ae..ba0e82ae549a 100644 --- a/drivers/gpu/drm/vkms/vkms_output.c +++ b/drivers/gpu/drm/vkms/vkms_output.c @@ -57,15 +57,18 @@ int vkms_output_init(struct vkms_device *vkmsdev, int index) struct vkms_plane *primary, *cursor = NULL; int ret; int writeback; + unsigned int n; primary = vkms_plane_init(vkmsdev, DRM_PLANE_TYPE_PRIMARY, index); if (IS_ERR(primary)) return PTR_ERR(primary); if (vkmsdev->config->overlay) { - ret = vkms_add_overlay_plane(vkmsdev, index, crtc); - if (ret) - return ret; + for (n = 0; n < NUM_OVERLAY_PLANES; n++) { + ret = vkms_add_overlay_plane(vkmsdev, index, crtc); + if (ret) + return ret; + } } if (vkmsdev->config->cursor) { -- cgit From 9354417750e51c3d120cb6040b1b69f70d9ef43f Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Wed, 15 Dec 2021 11:07:45 +0000 Subject: drm/i915: remove writeback hook Ditch the writeback hook and drop i915_gem_object_writeback(). We already support the shrinker_release_pages hook which can just call shmem_writeback directly. Suggested-by: Tvrtko Ursulin Signed-off-by: Matthew Auld Reviewed-by: Tvrtko Ursulin Link: https://patchwork.freedesktop.org/patch/msgid/20211215110746.865-1-matthew.auld@intel.com --- drivers/gpu/drm/i915/gem/i915_gem_object.h | 1 - drivers/gpu/drm/i915/gem/i915_gem_object_types.h | 1 - drivers/gpu/drm/i915/gem/i915_gem_pages.c | 10 ---------- drivers/gpu/drm/i915/gem/i915_gem_shmem.c | 19 ++++++++++++++++++- drivers/gpu/drm/i915/gem/i915_gem_shrinker.c | 12 ------------ 5 files changed, 18 insertions(+), 25 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gem/i915_gem_object.h b/drivers/gpu/drm/i915/gem/i915_gem_object.h index f66d46882ea7..bc448f895ae8 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_object.h +++ b/drivers/gpu/drm/i915/gem/i915_gem_object.h @@ -459,7 +459,6 @@ i915_gem_object_unpin_pages(struct drm_i915_gem_object *obj) int __i915_gem_object_put_pages(struct drm_i915_gem_object *obj); int i915_gem_object_truncate(struct drm_i915_gem_object *obj); -void i915_gem_object_writeback(struct drm_i915_gem_object *obj); /** * i915_gem_object_pin_map - return a contiguous mapping of the entire object diff --git a/drivers/gpu/drm/i915/gem/i915_gem_object_types.h b/drivers/gpu/drm/i915/gem/i915_gem_object_types.h index f9f7e44099fe..00c844caeabd 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_object_types.h +++ b/drivers/gpu/drm/i915/gem/i915_gem_object_types.h @@ -57,7 +57,6 @@ struct drm_i915_gem_object_ops { void (*put_pages)(struct drm_i915_gem_object *obj, struct sg_table *pages); int (*truncate)(struct drm_i915_gem_object *obj); - void (*writeback)(struct drm_i915_gem_object *obj); int (*shrinker_release_pages)(struct drm_i915_gem_object *obj, bool no_gpu_wait, bool should_writeback); diff --git a/drivers/gpu/drm/i915/gem/i915_gem_pages.c b/drivers/gpu/drm/i915/gem/i915_gem_pages.c index 89b70f5cde7a..820eee5e954e 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_pages.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_pages.c @@ -168,16 +168,6 @@ int i915_gem_object_truncate(struct drm_i915_gem_object *obj) return 0; } -/* Try to discard unwanted pages */ -void i915_gem_object_writeback(struct drm_i915_gem_object *obj) -{ - assert_object_held_shared(obj); - GEM_BUG_ON(i915_gem_object_has_pages(obj)); - - if (obj->ops->writeback) - obj->ops->writeback(obj); -} - static void __i915_gem_object_reset_page_iter(struct drm_i915_gem_object *obj) { struct radix_tree_iter iter; diff --git a/drivers/gpu/drm/i915/gem/i915_gem_shmem.c b/drivers/gpu/drm/i915/gem/i915_gem_shmem.c index cc9fe258fba7..7fdf4fa10b0e 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_shmem.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_shmem.c @@ -331,6 +331,23 @@ shmem_writeback(struct drm_i915_gem_object *obj) __shmem_writeback(obj->base.size, obj->base.filp->f_mapping); } +static int shmem_shrinker_release_pages(struct drm_i915_gem_object *obj, + bool no_gpu_wait, + bool writeback) +{ + switch (obj->mm.madv) { + case I915_MADV_DONTNEED: + return i915_gem_object_truncate(obj); + case __I915_MADV_PURGED: + return 0; + } + + if (writeback) + shmem_writeback(obj); + + return 0; +} + void __i915_gem_object_release_shmem(struct drm_i915_gem_object *obj, struct sg_table *pages, @@ -503,7 +520,7 @@ const struct drm_i915_gem_object_ops i915_gem_shmem_ops = { .get_pages = shmem_get_pages, .put_pages = shmem_put_pages, .truncate = shmem_truncate, - .writeback = shmem_writeback, + .shrinker_release_pages = shmem_shrinker_release_pages, .pwrite = shmem_pwrite, .pread = shmem_pread, diff --git a/drivers/gpu/drm/i915/gem/i915_gem_shrinker.c b/drivers/gpu/drm/i915/gem/i915_gem_shrinker.c index f9f68830c084..60b35e4a1394 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_shrinker.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_shrinker.c @@ -61,18 +61,6 @@ static int try_to_writeback(struct drm_i915_gem_object *obj, unsigned int flags) return obj->ops->shrinker_release_pages(obj, !(flags & I915_SHRINK_ACTIVE), flags & I915_SHRINK_WRITEBACK); - - switch (obj->mm.madv) { - case I915_MADV_DONTNEED: - i915_gem_object_truncate(obj); - return 0; - case __I915_MADV_PURGED: - return 0; - } - - if (flags & I915_SHRINK_WRITEBACK) - i915_gem_object_writeback(obj); - return 0; } -- cgit From ffa3fe080c77630f87436a9410475fed1e684612 Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Wed, 15 Dec 2021 11:07:46 +0000 Subject: drm/i915: clean up shrinker_release_pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add some proper flags for the different modes, and shorten the name to something more snappy. Suggested-by: Tvrtko Ursulin Signed-off-by: Matthew Auld Reviewed-by: Thomas Hellström Link: https://patchwork.freedesktop.org/patch/msgid/20211215110746.865-2-matthew.auld@intel.com --- drivers/gpu/drm/i915/gem/i915_gem_object_types.h | 23 ++++++++++++++++++++--- drivers/gpu/drm/i915/gem/i915_gem_shmem.c | 8 +++----- drivers/gpu/drm/i915/gem/i915_gem_shrinker.c | 16 ++++++++++++---- drivers/gpu/drm/i915/gem/i915_gem_ttm.c | 10 ++++------ 4 files changed, 39 insertions(+), 18 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gem/i915_gem_object_types.h b/drivers/gpu/drm/i915/gem/i915_gem_object_types.h index 00c844caeabd..6f446cca4322 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_object_types.h +++ b/drivers/gpu/drm/i915/gem/i915_gem_object_types.h @@ -57,9 +57,26 @@ struct drm_i915_gem_object_ops { void (*put_pages)(struct drm_i915_gem_object *obj, struct sg_table *pages); int (*truncate)(struct drm_i915_gem_object *obj); - int (*shrinker_release_pages)(struct drm_i915_gem_object *obj, - bool no_gpu_wait, - bool should_writeback); + /** + * shrink - Perform further backend specific actions to facilate + * shrinking. + * @obj: The gem object + * @flags: Extra flags to control shrinking behaviour in the backend + * + * Possible values for @flags: + * + * I915_GEM_OBJECT_SHRINK_WRITEBACK - Try to perform writeback of the + * backing pages, if supported. + * + * I915_GEM_OBJECT_SHRINK_NO_GPU_WAIT - Don't wait for the object to + * idle. Active objects can be considered later. The TTM backend for + * example might have aync migrations going on, which don't use any + * i915_vma to track the active GTT binding, and hence having an unbound + * object might not be enough. + */ +#define I915_GEM_OBJECT_SHRINK_WRITEBACK BIT(0) +#define I915_GEM_OBJECT_SHRINK_NO_GPU_WAIT BIT(1) + int (*shrink)(struct drm_i915_gem_object *obj, unsigned int flags); int (*pread)(struct drm_i915_gem_object *obj, const struct drm_i915_gem_pread *arg); diff --git a/drivers/gpu/drm/i915/gem/i915_gem_shmem.c b/drivers/gpu/drm/i915/gem/i915_gem_shmem.c index 7fdf4fa10b0e..6c57b0a79c8a 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_shmem.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_shmem.c @@ -331,9 +331,7 @@ shmem_writeback(struct drm_i915_gem_object *obj) __shmem_writeback(obj->base.size, obj->base.filp->f_mapping); } -static int shmem_shrinker_release_pages(struct drm_i915_gem_object *obj, - bool no_gpu_wait, - bool writeback) +static int shmem_shrink(struct drm_i915_gem_object *obj, unsigned int flags) { switch (obj->mm.madv) { case I915_MADV_DONTNEED: @@ -342,7 +340,7 @@ static int shmem_shrinker_release_pages(struct drm_i915_gem_object *obj, return 0; } - if (writeback) + if (flags & I915_GEM_OBJECT_SHRINK_WRITEBACK) shmem_writeback(obj); return 0; @@ -520,7 +518,7 @@ const struct drm_i915_gem_object_ops i915_gem_shmem_ops = { .get_pages = shmem_get_pages, .put_pages = shmem_put_pages, .truncate = shmem_truncate, - .shrinker_release_pages = shmem_shrinker_release_pages, + .shrink = shmem_shrink, .pwrite = shmem_pwrite, .pread = shmem_pread, diff --git a/drivers/gpu/drm/i915/gem/i915_gem_shrinker.c b/drivers/gpu/drm/i915/gem/i915_gem_shrinker.c index 60b35e4a1394..6a6ff98a8746 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_shrinker.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_shrinker.c @@ -57,10 +57,18 @@ static int drop_pages(struct drm_i915_gem_object *obj, static int try_to_writeback(struct drm_i915_gem_object *obj, unsigned int flags) { - if (obj->ops->shrinker_release_pages) - return obj->ops->shrinker_release_pages(obj, - !(flags & I915_SHRINK_ACTIVE), - flags & I915_SHRINK_WRITEBACK); + if (obj->ops->shrink) { + unsigned int shrink_flags = 0; + + if (!(flags & I915_SHRINK_ACTIVE)) + shrink_flags |= I915_GEM_OBJECT_SHRINK_NO_GPU_WAIT; + + if (flags & I915_SHRINK_WRITEBACK) + shrink_flags |= I915_GEM_OBJECT_SHRINK_WRITEBACK; + + return obj->ops->shrink(obj, shrink_flags); + } + return 0; } diff --git a/drivers/gpu/drm/i915/gem/i915_gem_ttm.c b/drivers/gpu/drm/i915/gem/i915_gem_ttm.c index 923cc7ad8d70..21277f3c64e7 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_ttm.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_ttm.c @@ -424,16 +424,14 @@ int i915_ttm_purge(struct drm_i915_gem_object *obj) return 0; } -static int i915_ttm_shrinker_release_pages(struct drm_i915_gem_object *obj, - bool no_wait_gpu, - bool should_writeback) +static int i915_ttm_shrink(struct drm_i915_gem_object *obj, unsigned int flags) { struct ttm_buffer_object *bo = i915_gem_to_ttm(obj); struct i915_ttm_tt *i915_tt = container_of(bo->ttm, typeof(*i915_tt), ttm); struct ttm_operation_ctx ctx = { .interruptible = true, - .no_wait_gpu = no_wait_gpu, + .no_wait_gpu = flags & I915_GEM_OBJECT_SHRINK_NO_GPU_WAIT, }; struct ttm_placement place = {}; int ret; @@ -467,7 +465,7 @@ static int i915_ttm_shrinker_release_pages(struct drm_i915_gem_object *obj, return ret; } - if (should_writeback) + if (flags & I915_GEM_OBJECT_SHRINK_WRITEBACK) __shmem_writeback(obj->base.size, i915_tt->filp->f_mapping); return 0; @@ -953,7 +951,7 @@ static const struct drm_i915_gem_object_ops i915_gem_ttm_obj_ops = { .get_pages = i915_ttm_get_pages, .put_pages = i915_ttm_put_pages, .truncate = i915_ttm_purge, - .shrinker_release_pages = i915_ttm_shrinker_release_pages, + .shrink = i915_ttm_shrink, .adjust_lru = i915_ttm_adjust_lru, .delayed_free = i915_ttm_delayed_free, -- cgit From 191be00229ef61f9ca0cfa39a09dc71eaf90e78d Mon Sep 17 00:00:00 2001 From: Xin Ji Date: Thu, 6 Jan 2022 18:01:24 +0800 Subject: drm/bridge: anx7625: Add bridge helper atomic conversion Add bridge helper atomic conversion. Reviewed-by: Robert Foss Signed-off-by: Xin Ji Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20220106100127.1862702-1-xji@analogixsemi.com --- drivers/gpu/drm/bridge/analogix/anx7625.c | 46 ++++++++++++++++++++++++++----- drivers/gpu/drm/bridge/analogix/anx7625.h | 1 + 2 files changed, 40 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c index 7b24213f7b13..6d5ae52f4142 100644 --- a/drivers/gpu/drm/bridge/analogix/anx7625.c +++ b/drivers/gpu/drm/bridge/analogix/anx7625.c @@ -1911,25 +1911,54 @@ static bool anx7625_bridge_mode_fixup(struct drm_bridge *bridge, return true; } -static void anx7625_bridge_enable(struct drm_bridge *bridge) +static int anx7625_bridge_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) { struct anx7625_data *ctx = bridge_to_anx7625(bridge); struct device *dev = &ctx->client->dev; - DRM_DEV_DEBUG_DRIVER(dev, "drm enable\n"); + dev_dbg(dev, "drm bridge atomic check\n"); + return anx7625_bridge_mode_fixup(bridge, &crtc_state->mode, + &crtc_state->adjusted_mode); +} + +static void anx7625_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *state) +{ + struct anx7625_data *ctx = bridge_to_anx7625(bridge); + struct device *dev = &ctx->client->dev; + struct drm_connector *connector; + + dev_dbg(dev, "drm atomic enable\n"); + + if (!bridge->encoder) { + dev_err(dev, "Parent encoder object not found"); + return; + } + + connector = drm_atomic_get_new_connector_for_encoder(state->base.state, + bridge->encoder); + if (!connector) + return; + + ctx->connector = connector; pm_runtime_get_sync(dev); anx7625_dp_start(ctx); } -static void anx7625_bridge_disable(struct drm_bridge *bridge) +static void anx7625_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old) { struct anx7625_data *ctx = bridge_to_anx7625(bridge); struct device *dev = &ctx->client->dev; - DRM_DEV_DEBUG_DRIVER(dev, "drm disable\n"); + dev_dbg(dev, "drm atomic disable\n"); + ctx->connector = NULL; anx7625_dp_stop(ctx); pm_runtime_put_sync(dev); @@ -1959,11 +1988,14 @@ static struct edid *anx7625_bridge_get_edid(struct drm_bridge *bridge, static const struct drm_bridge_funcs anx7625_bridge_funcs = { .attach = anx7625_bridge_attach, - .disable = anx7625_bridge_disable, .mode_valid = anx7625_bridge_mode_valid, .mode_set = anx7625_bridge_mode_set, - .mode_fixup = anx7625_bridge_mode_fixup, - .enable = anx7625_bridge_enable, + .atomic_check = anx7625_bridge_atomic_check, + .atomic_enable = anx7625_bridge_atomic_enable, + .atomic_disable = anx7625_bridge_atomic_disable, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, .detect = anx7625_bridge_detect, .get_edid = anx7625_bridge_get_edid, }; diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.h b/drivers/gpu/drm/bridge/analogix/anx7625.h index 3d79b6fb13c8..6f9d5f0e377c 100644 --- a/drivers/gpu/drm/bridge/analogix/anx7625.h +++ b/drivers/gpu/drm/bridge/analogix/anx7625.h @@ -407,6 +407,7 @@ struct anx7625_data { u8 display_timing_valid; struct drm_bridge bridge; u8 bridge_attached; + struct drm_connector *connector; struct mipi_dsi_device *dsi; }; -- cgit From cd1637c7e48043503f8ca6d441568e3889998965 Mon Sep 17 00:00:00 2001 From: Xin Ji Date: Thu, 6 Jan 2022 18:01:25 +0800 Subject: drm/bridge: anx7625: add HDCP support This patch provides HDCP setting interface for userspace to dynamic enable/disable HDCP function. Reported-by: kernel test robot Reviewed-by: Robert Foss Signed-off-by: Xin Ji Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20220106100127.1862702-2-xji@analogixsemi.com --- drivers/gpu/drm/bridge/analogix/anx7625.c | 335 +++++++++++++++++++++++++++++- drivers/gpu/drm/bridge/analogix/anx7625.h | 79 ++++++- 2 files changed, 404 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c index 6d5ae52f4142..001460f62b0c 100644 --- a/drivers/gpu/drm/bridge/analogix/anx7625.c +++ b/drivers/gpu/drm/bridge/analogix/anx7625.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -213,6 +214,65 @@ static int wait_aux_op_finish(struct anx7625_data *ctx) return 0; } +static int anx7625_aux_dpcd_read(struct anx7625_data *ctx, + u32 address, u8 len, u8 *buf) +{ + struct device *dev = &ctx->client->dev; + int ret; + u8 addrh, addrm, addrl; + u8 cmd; + + if (len > MAX_DPCD_BUFFER_SIZE) { + dev_err(dev, "exceed aux buffer len.\n"); + return -EINVAL; + } + + addrl = address & 0xFF; + addrm = (address >> 8) & 0xFF; + addrh = (address >> 16) & 0xFF; + + cmd = DPCD_CMD(len, DPCD_READ); + cmd = ((len - 1) << 4) | 0x09; + + /* Set command and length */ + ret = anx7625_reg_write(ctx, ctx->i2c.rx_p0_client, + AP_AUX_COMMAND, cmd); + + /* Set aux access address */ + ret |= anx7625_reg_write(ctx, ctx->i2c.rx_p0_client, + AP_AUX_ADDR_7_0, addrl); + ret |= anx7625_reg_write(ctx, ctx->i2c.rx_p0_client, + AP_AUX_ADDR_15_8, addrm); + ret |= anx7625_reg_write(ctx, ctx->i2c.rx_p0_client, + AP_AUX_ADDR_19_16, addrh); + + /* Enable aux access */ + ret |= anx7625_write_or(ctx, ctx->i2c.rx_p0_client, + AP_AUX_CTRL_STATUS, AP_AUX_CTRL_OP_EN); + + if (ret < 0) { + dev_err(dev, "cannot access aux related register.\n"); + return -EIO; + } + + usleep_range(2000, 2100); + + ret = wait_aux_op_finish(ctx); + if (ret) { + dev_err(dev, "aux IO error: wait aux op finish.\n"); + return ret; + } + + ret = anx7625_reg_block_read(ctx, ctx->i2c.rx_p0_client, + AP_AUX_BUFF_START, len, buf); + if (ret < 0) { + dev_err(dev, "read dpcd register failed\n"); + return -EIO; + } + + return 0; +} + static int anx7625_video_mute_control(struct anx7625_data *ctx, u8 status) { @@ -669,6 +729,165 @@ static int anx7625_dpi_config(struct anx7625_data *ctx) return ret; } +static int anx7625_read_flash_status(struct anx7625_data *ctx) +{ + return anx7625_reg_read(ctx, ctx->i2c.rx_p0_client, R_RAM_CTRL); +} + +static int anx7625_hdcp_key_probe(struct anx7625_data *ctx) +{ + int ret, val; + struct device *dev = &ctx->client->dev; + u8 ident[FLASH_BUF_LEN]; + + ret = anx7625_reg_write(ctx, ctx->i2c.rx_p0_client, + FLASH_ADDR_HIGH, 0x91); + ret |= anx7625_reg_write(ctx, ctx->i2c.rx_p0_client, + FLASH_ADDR_LOW, 0xA0); + if (ret < 0) { + dev_err(dev, "IO error : set key flash address.\n"); + return ret; + } + + ret = anx7625_reg_write(ctx, ctx->i2c.rx_p0_client, + FLASH_LEN_HIGH, (FLASH_BUF_LEN - 1) >> 8); + ret |= anx7625_reg_write(ctx, ctx->i2c.rx_p0_client, + FLASH_LEN_LOW, (FLASH_BUF_LEN - 1) & 0xFF); + if (ret < 0) { + dev_err(dev, "IO error : set key flash len.\n"); + return ret; + } + + ret = anx7625_reg_write(ctx, ctx->i2c.rx_p0_client, + R_FLASH_RW_CTRL, FLASH_READ); + ret |= readx_poll_timeout(anx7625_read_flash_status, + ctx, val, + ((val & FLASH_DONE) || (val < 0)), + 2000, + 2000 * 150); + if (ret) { + dev_err(dev, "flash read access fail!\n"); + return -EIO; + } + + ret = anx7625_reg_block_read(ctx, ctx->i2c.rx_p0_client, + FLASH_BUF_BASE_ADDR, + FLASH_BUF_LEN, ident); + if (ret < 0) { + dev_err(dev, "read flash data fail!\n"); + return -EIO; + } + + if (ident[29] == 0xFF && ident[30] == 0xFF && ident[31] == 0xFF) + return -EINVAL; + + return 0; +} + +static int anx7625_hdcp_key_load(struct anx7625_data *ctx) +{ + int ret; + struct device *dev = &ctx->client->dev; + + /* Select HDCP 1.4 KEY */ + ret = anx7625_reg_write(ctx, ctx->i2c.rx_p0_client, + R_BOOT_RETRY, 0x12); + ret |= anx7625_reg_write(ctx, ctx->i2c.rx_p0_client, + FLASH_ADDR_HIGH, HDCP14KEY_START_ADDR >> 8); + ret |= anx7625_reg_write(ctx, ctx->i2c.rx_p0_client, + FLASH_ADDR_LOW, HDCP14KEY_START_ADDR & 0xFF); + ret |= anx7625_reg_write(ctx, ctx->i2c.rx_p0_client, + R_RAM_LEN_H, HDCP14KEY_SIZE >> 12); + ret |= anx7625_reg_write(ctx, ctx->i2c.rx_p0_client, + R_RAM_LEN_L, HDCP14KEY_SIZE >> 4); + + ret |= anx7625_reg_write(ctx, ctx->i2c.rx_p0_client, + R_RAM_ADDR_H, 0); + ret |= anx7625_reg_write(ctx, ctx->i2c.rx_p0_client, + R_RAM_ADDR_L, 0); + /* Enable HDCP 1.4 KEY load */ + ret |= anx7625_reg_write(ctx, ctx->i2c.rx_p0_client, + R_RAM_CTRL, DECRYPT_EN | LOAD_START); + dev_dbg(dev, "load HDCP 1.4 key done\n"); + return ret; +} + +static int anx7625_hdcp_disable(struct anx7625_data *ctx) +{ + int ret; + struct device *dev = &ctx->client->dev; + + dev_dbg(dev, "disable HDCP 1.4\n"); + + /* Disable HDCP */ + ret = anx7625_write_and(ctx, ctx->i2c.rx_p1_client, 0xee, 0x9f); + /* Try auth flag */ + ret |= anx7625_write_or(ctx, ctx->i2c.rx_p1_client, 0xec, 0x10); + /* Interrupt for DRM */ + ret |= anx7625_write_or(ctx, ctx->i2c.rx_p1_client, 0xff, 0x01); + if (ret < 0) + dev_err(dev, "fail to disable HDCP\n"); + + return anx7625_write_and(ctx, ctx->i2c.tx_p0_client, + TX_HDCP_CTRL0, ~HARD_AUTH_EN & 0xFF); +} + +static int anx7625_hdcp_enable(struct anx7625_data *ctx) +{ + u8 bcap; + int ret; + struct device *dev = &ctx->client->dev; + + ret = anx7625_hdcp_key_probe(ctx); + if (ret) { + dev_dbg(dev, "no key found, not to do hdcp\n"); + return ret; + } + + /* Read downstream capability */ + anx7625_aux_dpcd_read(ctx, 0x68028, 1, &bcap); + if (!(bcap & 0x01)) { + pr_warn("downstream not support HDCP 1.4, cap(%x).\n", bcap); + return 0; + } + + dev_dbg(dev, "enable HDCP 1.4\n"); + + /* First clear HDCP state */ + ret = anx7625_reg_write(ctx, ctx->i2c.tx_p0_client, + TX_HDCP_CTRL0, + KSVLIST_VLD | BKSV_SRM_PASS | RE_AUTHEN); + usleep_range(1000, 1100); + /* Second clear HDCP state */ + ret |= anx7625_reg_write(ctx, ctx->i2c.tx_p0_client, + TX_HDCP_CTRL0, + KSVLIST_VLD | BKSV_SRM_PASS | RE_AUTHEN); + + /* Set time for waiting KSVR */ + ret |= anx7625_reg_write(ctx, ctx->i2c.tx_p0_client, + SP_TX_WAIT_KSVR_TIME, 0xc8); + /* Set time for waiting R0 */ + ret |= anx7625_reg_write(ctx, ctx->i2c.tx_p0_client, + SP_TX_WAIT_R0_TIME, 0xb0); + ret |= anx7625_hdcp_key_load(ctx); + if (ret) { + pr_warn("prepare HDCP key failed.\n"); + return ret; + } + + ret = anx7625_write_or(ctx, ctx->i2c.rx_p1_client, 0xee, 0x20); + + /* Try auth flag */ + ret |= anx7625_write_or(ctx, ctx->i2c.rx_p1_client, 0xec, 0x10); + /* Interrupt for DRM */ + ret |= anx7625_write_or(ctx, ctx->i2c.rx_p1_client, 0xff, 0x01); + if (ret < 0) + dev_err(dev, "fail to enable HDCP\n"); + + return anx7625_write_or(ctx, ctx->i2c.tx_p0_client, + TX_HDCP_CTRL0, HARD_AUTH_EN); +} + static void anx7625_dp_start(struct anx7625_data *ctx) { int ret; @@ -679,6 +898,9 @@ static void anx7625_dp_start(struct anx7625_data *ctx) return; } + /* Disable HDCP */ + anx7625_write_and(ctx, ctx->i2c.rx_p1_client, 0xee, 0x9f); + if (ctx->pdata.is_dpi) ret = anx7625_dpi_config(ctx); else @@ -686,6 +908,10 @@ static void anx7625_dp_start(struct anx7625_data *ctx) if (ret < 0) DRM_DEV_ERROR(dev, "MIPI phy setup error.\n"); + + ctx->hdcp_cp = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + + ctx->dp_en = 1; } static void anx7625_dp_stop(struct anx7625_data *ctx) @@ -705,6 +931,10 @@ static void anx7625_dp_stop(struct anx7625_data *ctx) ret |= anx7625_video_mute_control(ctx, 1); if (ret < 0) DRM_DEV_ERROR(dev, "IO error : mute video fail\n"); + + ctx->hdcp_cp = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + + ctx->dp_en = 0; } static int sp_tx_rst_aux(struct anx7625_data *ctx) @@ -1697,6 +1927,83 @@ static int anx7625_attach_dsi(struct anx7625_data *ctx) return 0; } +static void hdcp_check_work_func(struct work_struct *work) +{ + u8 status; + struct delayed_work *dwork; + struct anx7625_data *ctx; + struct device *dev; + struct drm_device *drm_dev; + + dwork = to_delayed_work(work); + ctx = container_of(dwork, struct anx7625_data, hdcp_work); + dev = &ctx->client->dev; + + if (!ctx->connector) { + dev_err(dev, "HDCP connector is null!"); + return; + } + + drm_dev = ctx->connector->dev; + drm_modeset_lock(&drm_dev->mode_config.connection_mutex, NULL); + mutex_lock(&ctx->hdcp_wq_lock); + + status = anx7625_reg_read(ctx, ctx->i2c.tx_p0_client, 0); + dev_dbg(dev, "sink HDCP status check: %.02x\n", status); + if (status & BIT(1)) { + ctx->hdcp_cp = DRM_MODE_CONTENT_PROTECTION_ENABLED; + drm_hdcp_update_content_protection(ctx->connector, + ctx->hdcp_cp); + dev_dbg(dev, "update CP to ENABLE\n"); + } + + mutex_unlock(&ctx->hdcp_wq_lock); + drm_modeset_unlock(&drm_dev->mode_config.connection_mutex); +} + +static int anx7625_connector_atomic_check(struct anx7625_data *ctx, + struct drm_connector_state *state) +{ + struct device *dev = &ctx->client->dev; + int cp; + + dev_dbg(dev, "hdcp state check\n"); + cp = state->content_protection; + + if (cp == ctx->hdcp_cp) + return 0; + + if (cp == DRM_MODE_CONTENT_PROTECTION_DESIRED) { + if (ctx->dp_en) { + dev_dbg(dev, "enable HDCP\n"); + anx7625_hdcp_enable(ctx); + + queue_delayed_work(ctx->hdcp_workqueue, + &ctx->hdcp_work, + msecs_to_jiffies(2000)); + } + } + + if (cp == DRM_MODE_CONTENT_PROTECTION_UNDESIRED) { + if (ctx->hdcp_cp != DRM_MODE_CONTENT_PROTECTION_ENABLED) { + dev_err(dev, "current CP is not ENABLED\n"); + return -EINVAL; + } + anx7625_hdcp_disable(ctx); + ctx->hdcp_cp = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + drm_hdcp_update_content_protection(ctx->connector, + ctx->hdcp_cp); + dev_dbg(dev, "update CP to UNDESIRE\n"); + } + + if (cp == DRM_MODE_CONTENT_PROTECTION_ENABLED) { + dev_err(dev, "Userspace illegal set to PROTECTION ENABLE\n"); + return -EINVAL; + } + + return 0; +} + static int anx7625_bridge_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags) { @@ -1920,8 +2227,11 @@ static int anx7625_bridge_atomic_check(struct drm_bridge *bridge, struct device *dev = &ctx->client->dev; dev_dbg(dev, "drm bridge atomic check\n"); - return anx7625_bridge_mode_fixup(bridge, &crtc_state->mode, - &crtc_state->adjusted_mode); + + anx7625_bridge_mode_fixup(bridge, &crtc_state->mode, + &crtc_state->adjusted_mode); + + return anx7625_connector_atomic_check(ctx, conn_state); } static void anx7625_bridge_atomic_enable(struct drm_bridge *bridge, @@ -2189,6 +2499,15 @@ static int anx7625_i2c_probe(struct i2c_client *client, anx7625_init_gpio(platform); mutex_init(&platform->lock); + mutex_init(&platform->hdcp_wq_lock); + + INIT_DELAYED_WORK(&platform->hdcp_work, hdcp_check_work_func); + platform->hdcp_workqueue = create_workqueue("hdcp workqueue"); + if (!platform->hdcp_workqueue) { + dev_err(dev, "fail to create work queue\n"); + ret = -ENOMEM; + goto free_platform; + } platform->pdata.intp_irq = client->irq; if (platform->pdata.intp_irq) { @@ -2198,7 +2517,7 @@ static int anx7625_i2c_probe(struct i2c_client *client, if (!platform->workqueue) { DRM_DEV_ERROR(dev, "fail to create work queue\n"); ret = -ENOMEM; - goto free_platform; + goto free_hdcp_wq; } ret = devm_request_threaded_irq(dev, platform->pdata.intp_irq, @@ -2268,6 +2587,10 @@ free_wq: if (platform->workqueue) destroy_workqueue(platform->workqueue); +free_hdcp_wq: + if (platform->hdcp_workqueue) + destroy_workqueue(platform->hdcp_workqueue); + free_platform: kfree(platform); @@ -2283,6 +2606,12 @@ static int anx7625_i2c_remove(struct i2c_client *client) if (platform->pdata.intp_irq) destroy_workqueue(platform->workqueue); + if (platform->hdcp_workqueue) { + cancel_delayed_work(&platform->hdcp_work); + flush_workqueue(platform->workqueue); + destroy_workqueue(platform->workqueue); + } + if (!platform->pdata.low_power_mode) pm_runtime_put_sync_suspend(&client->dev); diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.h b/drivers/gpu/drm/bridge/analogix/anx7625.h index 6f9d5f0e377c..56165f5b254c 100644 --- a/drivers/gpu/drm/bridge/analogix/anx7625.h +++ b/drivers/gpu/drm/bridge/analogix/anx7625.h @@ -59,10 +59,23 @@ /***************************************************************/ /* Register definition of device address 0x70 */ -#define I2C_ADDR_70_DPTX 0x70 - -#define SP_TX_LINK_BW_SET_REG 0xA0 -#define SP_TX_LANE_COUNT_SET_REG 0xA1 +#define TX_HDCP_CTRL0 0x01 +#define STORE_AN BIT(7) +#define RX_REPEATER BIT(6) +#define RE_AUTHEN BIT(5) +#define SW_AUTH_OK BIT(4) +#define HARD_AUTH_EN BIT(3) +#define ENC_EN BIT(2) +#define BKSV_SRM_PASS BIT(1) +#define KSVLIST_VLD BIT(0) + +#define SP_TX_WAIT_R0_TIME 0x40 +#define SP_TX_WAIT_KSVR_TIME 0x42 +#define SP_TX_SYS_CTRL1_REG 0x80 +#define HDCP2TX_FW_EN BIT(4) + +#define SP_TX_LINK_BW_SET_REG 0xA0 +#define SP_TX_LANE_COUNT_SET_REG 0xA1 #define M_VID_0 0xC0 #define M_VID_1 0xC1 @@ -71,6 +84,12 @@ #define N_VID_1 0xC4 #define N_VID_2 0xC5 +#define KEY_START_ADDR 0x9000 +#define KEY_RESERVED 416 + +#define HDCP14KEY_START_ADDR (KEY_START_ADDR + KEY_RESERVED) +#define HDCP14KEY_SIZE 624 + /***************************************************************/ /* Register definition of device address 0x72 */ #define AUX_RST 0x04 @@ -155,9 +174,43 @@ #define I2C_ADDR_7E_FLASH_CONTROLLER 0x7E +#define R_BOOT_RETRY 0x00 +#define R_RAM_ADDR_H 0x01 +#define R_RAM_ADDR_L 0x02 +#define R_RAM_LEN_H 0x03 +#define R_RAM_LEN_L 0x04 #define FLASH_LOAD_STA 0x05 #define FLASH_LOAD_STA_CHK BIT(7) +#define R_RAM_CTRL 0x05 +/* bit positions */ +#define FLASH_DONE BIT(7) +#define BOOT_LOAD_DONE BIT(6) +#define CRC_OK BIT(5) +#define LOAD_DONE BIT(4) +#define O_RW_DONE BIT(3) +#define FUSE_BUSY BIT(2) +#define DECRYPT_EN BIT(1) +#define LOAD_START BIT(0) + +#define FLASH_ADDR_HIGH 0x0F +#define FLASH_ADDR_LOW 0x10 +#define FLASH_LEN_HIGH 0x31 +#define FLASH_LEN_LOW 0x32 +#define R_FLASH_RW_CTRL 0x33 +/* bit positions */ +#define READ_DELAY_SELECT BIT(7) +#define GENERAL_INSTRUCTION_EN BIT(6) +#define FLASH_ERASE_EN BIT(5) +#define RDID_READ_EN BIT(4) +#define REMS_READ_EN BIT(3) +#define WRITE_STATUS_EN BIT(2) +#define FLASH_READ BIT(1) +#define FLASH_WRITE BIT(0) + +#define FLASH_BUF_BASE_ADDR 0x60 +#define FLASH_BUF_LEN 0x20 + #define XTAL_FRQ_SEL 0x3F /* bit field positions */ #define XTAL_FRQ_SEL_POS 5 @@ -184,10 +237,15 @@ #define AP_AUX_CTRL_ADDRONLY 0x20 #define AP_AUX_BUFF_START 0x15 -#define PIXEL_CLOCK_L 0x25 -#define PIXEL_CLOCK_H 0x26 +#define PIXEL_CLOCK_L 0x25 +#define PIXEL_CLOCK_H 0x26 + +#define AP_AUX_COMMAND 0x27 /* com+len */ +#define LENGTH_SHIFT 4 +#define DPCD_READ 0x09 +#define DPCD_WRITE 0x08 +#define DPCD_CMD(len, cmd) ((((len) - 1) << LENGTH_SHIFT) | (cmd)) -#define AP_AUX_COMMAND 0x27 /* com+len */ /* Bit 0&1: 3D video structure */ /* 0x01: frame packing, 0x02:Line alternative, 0x03:Side-by-side(full) */ #define AP_AV_STATUS 0x28 @@ -392,16 +450,23 @@ struct anx7625_data { struct platform_device *audio_pdev; int hpd_status; int hpd_high_cnt; + int dp_en; + int hdcp_cp; /* Lock for work queue */ struct mutex lock; struct i2c_client *client; struct anx7625_i2c_client i2c; struct i2c_client *last_client; + struct timer_list hdcp_timer; struct s_edid_data slimport_edid_p; struct device *codec_dev; hdmi_codec_plugged_cb plugged_cb; struct work_struct work; struct workqueue_struct *workqueue; + struct delayed_work hdcp_work; + struct workqueue_struct *hdcp_workqueue; + /* Lock for hdcp work queue */ + struct mutex hdcp_wq_lock; char edid_block; struct display_timing dt; u8 display_timing_valid; -- cgit From 607a264ea7016b0f811f82c33094a3c2eda5968c Mon Sep 17 00:00:00 2001 From: Xin Ji Date: Thu, 6 Jan 2022 18:01:26 +0800 Subject: drm/bridge: anx7625: add audio codec .get_eld support Provide .get_eld interface in hdmi_codec_ops for hdmi-codec driver. Reviewed-by: Robert Foss Signed-off-by: Xin Ji Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20220106100127.1862702-3-xji@analogixsemi.com --- drivers/gpu/drm/bridge/analogix/anx7625.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c index 001460f62b0c..33383f83255d 100644 --- a/drivers/gpu/drm/bridge/analogix/anx7625.c +++ b/drivers/gpu/drm/bridge/analogix/anx7625.c @@ -1840,9 +1840,27 @@ static int anx7625_audio_hook_plugged_cb(struct device *dev, void *data, return 0; } +static int anx7625_audio_get_eld(struct device *dev, void *data, + u8 *buf, size_t len) +{ + struct anx7625_data *ctx = dev_get_drvdata(dev); + + if (!ctx->connector) { + dev_err(dev, "connector not initial\n"); + return -EINVAL; + } + + dev_dbg(dev, "audio copy eld\n"); + memcpy(buf, ctx->connector->eld, + min(sizeof(ctx->connector->eld), len)); + + return 0; +} + static const struct hdmi_codec_ops anx7625_codec_ops = { .hw_params = anx7625_audio_hw_params, .audio_shutdown = anx7625_audio_shutdown, + .get_eld = anx7625_audio_get_eld, .get_dai_id = anx7625_hdmi_i2s_get_dai_id, .hook_plugged_cb = anx7625_audio_hook_plugged_cb, }; -- cgit From 3dbc84a595d17f64f14fcea00120d31e33e98880 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Sun, 9 Jan 2022 22:59:49 +0530 Subject: drm: bridge: adv7511: Fix ADV7535 HPD enablement Existing HPD enablement logic is not compatible with ADV7535 bridge, thus any runtime plug-in of HDMI cable is not working on these bridge designs. Unlike other ADV7511 family of bridges, the ADV7535 require HPD_OVERRIDE bit to set and reset for proper handling of HPD functionality. Fix it. Fixes: 8501fe4b14a3 ("drm: bridge: adv7511: Add support for ADV7535") Signed-off-by: Jagan Teki Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20220109172949.168167-1-jagan@amarulasolutions.com Reviewed-by: Robert Foss --- drivers/gpu/drm/bridge/adv7511/adv7511.h | 1 + drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 29 +++++++++++++++++++++------- 2 files changed, 23 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h index 592ecfcf00ca..6a882891d91c 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511.h +++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h @@ -169,6 +169,7 @@ #define ADV7511_PACKET_ENABLE_SPARE2 BIT(1) #define ADV7511_PACKET_ENABLE_SPARE1 BIT(0) +#define ADV7535_REG_POWER2_HPD_OVERRIDE BIT(6) #define ADV7511_REG_POWER2_HPD_SRC_MASK 0xc0 #define ADV7511_REG_POWER2_HPD_SRC_BOTH 0x00 #define ADV7511_REG_POWER2_HPD_SRC_HPD 0x40 diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c index f8e5da148599..77118c3395bf 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -351,11 +351,17 @@ static void __adv7511_power_on(struct adv7511 *adv7511) * from standby or are enabled. When the HPD goes low the adv7511 is * reset and the outputs are disabled which might cause the monitor to * go to standby again. To avoid this we ignore the HPD pin for the - * first few seconds after enabling the output. + * first few seconds after enabling the output. On the other hand + * adv7535 require to enable HPD Override bit for proper HPD. */ - regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, - ADV7511_REG_POWER2_HPD_SRC_MASK, - ADV7511_REG_POWER2_HPD_SRC_NONE); + if (adv7511->type == ADV7535) + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, + ADV7535_REG_POWER2_HPD_OVERRIDE, + ADV7535_REG_POWER2_HPD_OVERRIDE); + else + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, + ADV7511_REG_POWER2_HPD_SRC_MASK, + ADV7511_REG_POWER2_HPD_SRC_NONE); } static void adv7511_power_on(struct adv7511 *adv7511) @@ -375,6 +381,10 @@ static void adv7511_power_on(struct adv7511 *adv7511) static void __adv7511_power_off(struct adv7511 *adv7511) { /* TODO: setup additional power down modes */ + if (adv7511->type == ADV7535) + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, + ADV7535_REG_POWER2_HPD_OVERRIDE, 0); + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER, ADV7511_POWER_POWER_DOWN, ADV7511_POWER_POWER_DOWN); @@ -672,9 +682,14 @@ adv7511_detect(struct adv7511 *adv7511, struct drm_connector *connector) status = connector_status_disconnected; } else { /* Renable HPD sensing */ - regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, - ADV7511_REG_POWER2_HPD_SRC_MASK, - ADV7511_REG_POWER2_HPD_SRC_BOTH); + if (adv7511->type == ADV7535) + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, + ADV7535_REG_POWER2_HPD_OVERRIDE, + ADV7535_REG_POWER2_HPD_OVERRIDE); + else + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, + ADV7511_REG_POWER2_HPD_SRC_MASK, + ADV7511_REG_POWER2_HPD_SRC_BOTH); } adv7511->status = status; -- cgit From 658a0c632625e1db51837ff754fe18a6a7f2ccf8 Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Thu, 6 Jan 2022 17:49:07 +0000 Subject: drm/i915: don't call free_mmap_offset when purging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TTM backend is in theory the only user here(also purge should only be called once we have dropped the pages), where it is setup at object creation and is only removed once the object is destroyed. Also resetting the node here might be iffy since the ttm fault handler uses the stored fake offset to determine the page offset within the pages array. This also blows up in the dontneed-before-mmap test, since the expectation is that the vma_node will live on, until the object is destroyed: <2> [749.062902] kernel BUG at drivers/gpu/drm/i915/gem/i915_gem_ttm.c:943! <4> [749.062923] invalid opcode: 0000 [#1] PREEMPT SMP NOPTI <4> [749.062928] CPU: 0 PID: 1643 Comm: gem_madvise Tainted: G U W 5.16.0-rc8-CI-CI_DRM_11046+ #1 <4> [749.062933] Hardware name: Gigabyte Technology Co., Ltd. GB-Z390 Garuda/GB-Z390 Garuda-CF, BIOS IG1c 11/19/2019 <4> [749.062937] RIP: 0010:i915_ttm_mmap_offset.cold.35+0x5b/0x5d [i915] <4> [749.063044] Code: 00 48 c7 c2 a0 23 4e a0 48 c7 c7 26 df 4a a0 e8 95 1d d0 e0 bf 01 00 00 00 e8 8b ec cf e0 31 f6 bf 09 00 00 00 e8 5f 30 c0 e0 <0f> 0b 48 c7 c1 24 4b 56 a0 ba 5b 03 00 00 48 c7 c6 c0 23 4e a0 48 <4> [749.063052] RSP: 0018:ffffc90002ab7d38 EFLAGS: 00010246 <4> [749.063056] RAX: 0000000000000240 RBX: ffff88811f2e61c0 RCX: 0000000000000006 <4> [749.063060] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000009 <4> [749.063063] RBP: ffffc90002ab7e58 R08: 0000000000000001 R09: 0000000000000001 <4> [749.063067] R10: 000000000123d0f8 R11: ffffc90002ab7b20 R12: ffff888112a1a000 <4> [749.063071] R13: 0000000000000004 R14: ffff88811f2e61c0 R15: ffff888112a1a000 <4> [749.063074] FS: 00007f6e5fcad500(0000) GS:ffff8884ad600000(0000) knlGS:0000000000000000 <4> [749.063078] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 <4> [749.063081] CR2: 00007efd264e39f0 CR3: 0000000115fd6005 CR4: 00000000003706f0 <4> [749.063085] Call Trace: <4> [749.063087] <4> [749.063089] __assign_mmap_offset+0x41/0x300 [i915] <4> [749.063171] __assign_mmap_offset_handle+0x159/0x270 [i915] <4> [749.063248] ? i915_gem_dumb_mmap_offset+0x70/0x70 [i915] <4> [749.063325] drm_ioctl_kernel+0xae/0x140 <4> [749.063330] drm_ioctl+0x201/0x3d0 <4> [749.063333] ? i915_gem_dumb_mmap_offset+0x70/0x70 [i915] <4> [749.063409] ? do_user_addr_fault+0x200/0x670 <4> [749.063415] __x64_sys_ioctl+0x6d/0xa0 <4> [749.063419] do_syscall_64+0x3a/0xb0 <4> [749.063423] entry_SYSCALL_64_after_hwframe+0x44/0xae <4> [749.063428] RIP: 0033:0x7f6e5f100317 Testcase: igt/gem_madvise/dontneed-before-mmap Fixes: cf3e3e86d779 ("drm/i915: Use ttm mmap handling for ttm bo's.") Signed-off-by: Matthew Auld Cc: Thomas Hellström Reviewed-by: Thomas Hellström Link: https://patchwork.freedesktop.org/patch/msgid/20220106174910.280616-1-matthew.auld@intel.com --- drivers/gpu/drm/i915/gem/i915_gem_pages.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gem/i915_gem_pages.c b/drivers/gpu/drm/i915/gem/i915_gem_pages.c index 820eee5e954e..7d2211fbe548 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_pages.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_pages.c @@ -161,7 +161,6 @@ retry: /* Immediately discard the backing storage */ int i915_gem_object_truncate(struct drm_i915_gem_object *obj) { - drm_gem_free_mmap_offset(&obj->base); if (obj->ops->truncate) return obj->ops->truncate(obj); -- cgit From f3cb4a2de5410147b53e53416a3af0ffe26b5f4e Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Thu, 6 Jan 2022 17:49:08 +0000 Subject: drm/i915/ttm: only fault WILLNEED objects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Don't attempt to fault and re-populate purged objects. By some fluke this passes the dontneed-after-mmap IGT, but for the wrong reasons. Fixes: cf3e3e86d779 ("drm/i915: Use ttm mmap handling for ttm bo's.") Signed-off-by: Matthew Auld Cc: Thomas Hellström Reviewed-by: Thomas Hellström Link: https://patchwork.freedesktop.org/patch/msgid/20220106174910.280616-2-matthew.auld@intel.com --- drivers/gpu/drm/i915/gem/i915_gem_ttm.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gem/i915_gem_ttm.c b/drivers/gpu/drm/i915/gem/i915_gem_ttm.c index 21277f3c64e7..f2af76577e22 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_ttm.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_ttm.c @@ -881,6 +881,11 @@ static vm_fault_t vm_fault_ttm(struct vm_fault *vmf) if (ret) return ret; + if (obj->mm.madv != I915_MADV_WILLNEED) { + dma_resv_unlock(bo->base.resv); + return VM_FAULT_SIGBUS; + } + if (drm_dev_enter(dev, &idx)) { ret = ttm_bo_vm_fault_reserved(vmf, vmf->vma->vm_page_prot, TTM_BO_VM_NUM_PREFAULT); -- cgit From 903e0387270eef14a711c0feb23b7bf62d2480df Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Thu, 6 Jan 2022 17:49:09 +0000 Subject: drm/i915/ttm: add unmap_virtual callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ensure we call ttm_bo_unmap_virtual when releasing the pages. Importantly this should now handle the ttm swapping case, and all other places that already call into i915_ttm_move_notify(). v2: fix up the selftest Fixes: cf3e3e86d779 ("drm/i915: Use ttm mmap handling for ttm bo's.") Signed-off-by: Matthew Auld Cc: Thomas Hellström Reviewed-by: Thomas Hellström Link: https://patchwork.freedesktop.org/patch/msgid/20220106174910.280616-3-matthew.auld@intel.com --- drivers/gpu/drm/i915/gem/i915_gem_mman.c | 3 +++ drivers/gpu/drm/i915/gem/i915_gem_object_types.h | 1 + drivers/gpu/drm/i915/gem/i915_gem_ttm.c | 6 ++++++ drivers/gpu/drm/i915/gem/selftests/i915_gem_mman.c | 18 ++++-------------- 4 files changed, 14 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gem/i915_gem_mman.c b/drivers/gpu/drm/i915/gem/i915_gem_mman.c index fae458019172..55f256298d90 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_mman.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_mman.c @@ -537,6 +537,9 @@ void i915_gem_object_release_mmap_offset(struct drm_i915_gem_object *obj) { struct i915_mmap_offset *mmo, *mn; + if (obj->ops->unmap_virtual) + obj->ops->unmap_virtual(obj); + spin_lock(&obj->mmo.lock); rbtree_postorder_for_each_entry_safe(mmo, mn, &obj->mmo.offsets, offset) { diff --git a/drivers/gpu/drm/i915/gem/i915_gem_object_types.h b/drivers/gpu/drm/i915/gem/i915_gem_object_types.h index 6f446cca4322..1c903d813097 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_object_types.h +++ b/drivers/gpu/drm/i915/gem/i915_gem_object_types.h @@ -83,6 +83,7 @@ struct drm_i915_gem_object_ops { int (*pwrite)(struct drm_i915_gem_object *obj, const struct drm_i915_gem_pwrite *arg); u64 (*mmap_offset)(struct drm_i915_gem_object *obj); + void (*unmap_virtual)(struct drm_i915_gem_object *obj); int (*dmabuf_export)(struct drm_i915_gem_object *obj); diff --git a/drivers/gpu/drm/i915/gem/i915_gem_ttm.c b/drivers/gpu/drm/i915/gem/i915_gem_ttm.c index f2af76577e22..d6d6878d2414 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_ttm.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_ttm.c @@ -948,6 +948,11 @@ static u64 i915_ttm_mmap_offset(struct drm_i915_gem_object *obj) return drm_vma_node_offset_addr(&obj->base.vma_node); } +static void i915_ttm_unmap_virtual(struct drm_i915_gem_object *obj) +{ + ttm_bo_unmap_virtual(i915_gem_to_ttm(obj)); +} + static const struct drm_i915_gem_object_ops i915_gem_ttm_obj_ops = { .name = "i915_gem_object_ttm", .flags = I915_GEM_OBJECT_IS_SHRINKABLE | @@ -963,6 +968,7 @@ static const struct drm_i915_gem_object_ops i915_gem_ttm_obj_ops = { .migrate = i915_ttm_migrate, .mmap_offset = i915_ttm_mmap_offset, + .unmap_virtual = i915_ttm_unmap_virtual, .mmap_ops = &vm_ops_ttm, }; diff --git a/drivers/gpu/drm/i915/gem/selftests/i915_gem_mman.c b/drivers/gpu/drm/i915/gem/selftests/i915_gem_mman.c index 743a098facf2..f61356b72b1c 100644 --- a/drivers/gpu/drm/i915/gem/selftests/i915_gem_mman.c +++ b/drivers/gpu/drm/i915/gem/selftests/i915_gem_mman.c @@ -1369,20 +1369,10 @@ static int __igt_mmap_revoke(struct drm_i915_private *i915, } } - if (!obj->ops->mmap_ops) { - err = check_absent(addr, obj->base.size); - if (err) { - pr_err("%s: was not absent\n", obj->mm.region->name); - goto out_unmap; - } - } else { - /* ttm allows access to evicted regions by design */ - - err = check_present(addr, obj->base.size); - if (err) { - pr_err("%s: was not present\n", obj->mm.region->name); - goto out_unmap; - } + err = check_absent(addr, obj->base.size); + if (err) { + pr_err("%s: was not absent\n", obj->mm.region->name); + goto out_unmap; } out_unmap: -- cgit From ab4911b7d411ab2ef3b38322178b9138e156c393 Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Thu, 6 Jan 2022 17:49:10 +0000 Subject: drm/i915/ttm: ensure we unmap when purging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Purging can happen during swapping out, or directly invoked with the madvise ioctl. In such cases this doesn't involve a ttm move, which skips umapping the object. v2(Thomas): - add ttm_truncate helper, and just call into i915_ttm_move_notify() to handle the unmapping step Fixes: 213d50927763 ("drm/i915/ttm: Introduce a TTM i915 gem object backend") Signed-off-by: Matthew Auld Cc: Thomas Hellström Reviewed-by: Thomas Hellström Link: https://patchwork.freedesktop.org/patch/msgid/20220106174910.280616-4-matthew.auld@intel.com --- drivers/gpu/drm/i915/gem/i915_gem_ttm.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gem/i915_gem_ttm.c b/drivers/gpu/drm/i915/gem/i915_gem_ttm.c index d6d6878d2414..84cae740b4a5 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_ttm.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_ttm.c @@ -554,6 +554,20 @@ i915_ttm_resource_get_st(struct drm_i915_gem_object *obj, return intel_region_ttm_resource_to_rsgt(obj->mm.region, res); } +static int i915_ttm_truncate(struct drm_i915_gem_object *obj) +{ + struct ttm_buffer_object *bo = i915_gem_to_ttm(obj); + int err; + + WARN_ON_ONCE(obj->mm.madv == I915_MADV_WILLNEED); + + err = i915_ttm_move_notify(bo); + if (err) + return err; + + return i915_ttm_purge(obj); +} + static void i915_ttm_swap_notify(struct ttm_buffer_object *bo) { struct drm_i915_gem_object *obj = i915_ttm_to_gem(bo); @@ -960,7 +974,7 @@ static const struct drm_i915_gem_object_ops i915_gem_ttm_obj_ops = { .get_pages = i915_ttm_get_pages, .put_pages = i915_ttm_put_pages, - .truncate = i915_ttm_purge, + .truncate = i915_ttm_truncate, .shrink = i915_ttm_shrink, .adjust_lru = i915_ttm_adjust_lru, -- cgit From 30598d925d466c53c4c218a58f80e043cfe9b085 Mon Sep 17 00:00:00 2001 From: Rajat Jain Date: Fri, 7 Jan 2022 11:02:06 -0800 Subject: drm/privacy_screen: Add drvdata in drm_privacy_screen Allow a privacy screen provider to stash its private data pointer in the drm_privacy_screen, and update the drm_privacy_screen_register() call to accept that. Also introduce a *_get_drvdata() so that it can retrieved back when needed. This also touches the IBM Thinkpad platform driver, the only user of privacy screen today, to pass NULL for now to the updated API. Signed-off-by: Rajat Jain Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede Link: https://patchwork.freedesktop.org/patch/msgid/20220107190208.95479-1-rajatja@google.com --- drivers/gpu/drm/drm_privacy_screen.c | 5 ++++- drivers/platform/x86/thinkpad_acpi.c | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/drm_privacy_screen.c b/drivers/gpu/drm/drm_privacy_screen.c index beaf99e9120a..03b149cc455b 100644 --- a/drivers/gpu/drm/drm_privacy_screen.c +++ b/drivers/gpu/drm/drm_privacy_screen.c @@ -387,7 +387,8 @@ static void drm_privacy_screen_device_release(struct device *dev) * * An ERR_PTR(errno) on failure. */ struct drm_privacy_screen *drm_privacy_screen_register( - struct device *parent, const struct drm_privacy_screen_ops *ops) + struct device *parent, const struct drm_privacy_screen_ops *ops, + void *data) { struct drm_privacy_screen *priv; int ret; @@ -404,6 +405,7 @@ struct drm_privacy_screen *drm_privacy_screen_register( priv->dev.parent = parent; priv->dev.release = drm_privacy_screen_device_release; dev_set_name(&priv->dev, "privacy_screen-%s", dev_name(parent)); + priv->drvdata = data; priv->ops = ops; priv->ops->get_hw_state(priv); @@ -439,6 +441,7 @@ void drm_privacy_screen_unregister(struct drm_privacy_screen *priv) mutex_unlock(&drm_privacy_screen_devs_lock); mutex_lock(&priv->lock); + priv->drvdata = NULL; priv->ops = NULL; mutex_unlock(&priv->lock); diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 341655d711ce..ccbfda2b0095 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -9782,7 +9782,7 @@ static int tpacpi_lcdshadow_init(struct ibm_init_struct *iibm) return 0; lcdshadow_dev = drm_privacy_screen_register(&tpacpi_pdev->dev, - &lcdshadow_ops); + &lcdshadow_ops, NULL); if (IS_ERR(lcdshadow_dev)) return PTR_ERR(lcdshadow_dev); -- cgit From 3fb57847f6ec30e9aa27af7b37aed7a7419d50aa Mon Sep 17 00:00:00 2001 From: Rajat Jain Date: Fri, 7 Jan 2022 11:02:07 -0800 Subject: platform/chrome: Add driver for ChromeOS privacy-screen This adds the ACPI driver for the ChromeOS privacy screen that is present on some chromeos devices. Note that ideally, we'd want this privacy screen driver to be probed BEFORE the drm probe in order to avoid a drm probe deferral: https://hansdegoede.livejournal.com/25948.html In practise, I found that ACPI drivers are bound to their devices AFTER the drm probe on chromebooks. So on chromebooks with privacy-screen, this patch along with the other one in this series results in a probe deferral of about 250ms for i915 driver. However, it did not result in any user noticeable delay of splash screen in my personal experience. In future if this probe deferral turns out to be an issue, we can consider turning this ACPI driver into something that is probed earlier than the drm drivers. Signed-off-by: Rajat Jain Reviewed-by: Dmitry Torokhov Acked-by: Benson Leung Signed-off-by: Hans de Goede Link: https://patchwork.freedesktop.org/patch/msgid/20220107190208.95479-2-rajatja@google.com --- drivers/platform/chrome/Kconfig | 11 ++ drivers/platform/chrome/Makefile | 1 + drivers/platform/chrome/chromeos_privacy_screen.c | 153 ++++++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 drivers/platform/chrome/chromeos_privacy_screen.c (limited to 'drivers') diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index ccc23d8686e8..75e93efd669f 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -243,6 +243,17 @@ config CROS_USBPD_NOTIFY To compile this driver as a module, choose M here: the module will be called cros_usbpd_notify. +config CHROMEOS_PRIVACY_SCREEN + tristate "ChromeOS Privacy Screen support" + depends on ACPI + depends on DRM + select DRM_PRIVACY_SCREEN + help + This driver provides the support needed for the in-built electronic + privacy screen that is present on some ChromeOS devices. When enabled, + this should probably always be built into the kernel to avoid or + minimize drm probe deferral. + source "drivers/platform/chrome/wilco_ec/Kconfig" endif # CHROMEOS_PLATFORMS diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index f901d2e43166..5d4be9735d9d 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -4,6 +4,7 @@ CFLAGS_cros_ec_trace.o:= -I$(src) obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o +obj-$(CONFIG_CHROMEOS_PRIVACY_SCREEN) += chromeos_privacy_screen.o obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o obj-$(CONFIG_CHROMEOS_TBMC) += chromeos_tbmc.o obj-$(CONFIG_CROS_EC) += cros_ec.o diff --git a/drivers/platform/chrome/chromeos_privacy_screen.c b/drivers/platform/chrome/chromeos_privacy_screen.c new file mode 100644 index 000000000000..77e9f5ee8e33 --- /dev/null +++ b/drivers/platform/chrome/chromeos_privacy_screen.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * ChromeOS Privacy Screen support + * + * Copyright (C) 2022 Google LLC + * + * This is the Chromeos privacy screen provider, present on certain chromebooks, + * represented by a GOOG0010 device in the ACPI. This ACPI device, if present, + * will cause the i915 drm driver to probe defer until this driver registers + * the privacy-screen. + */ + +#include +#include + +/* + * The DSM (Device Specific Method) constants below are the agreed API with + * the firmware team, on how to control privacy screen using ACPI methods. + */ +#define PRIV_SCRN_DSM_REVID 1 /* DSM version */ +#define PRIV_SCRN_DSM_FN_GET_STATUS 1 /* Get privacy screen status */ +#define PRIV_SCRN_DSM_FN_ENABLE 2 /* Enable privacy screen */ +#define PRIV_SCRN_DSM_FN_DISABLE 3 /* Disable privacy screen */ + +static const guid_t chromeos_privacy_screen_dsm_guid = + GUID_INIT(0xc7033113, 0x8720, 0x4ceb, + 0x90, 0x90, 0x9d, 0x52, 0xb3, 0xe5, 0x2d, 0x73); + +static void +chromeos_privacy_screen_get_hw_state(struct drm_privacy_screen + *drm_privacy_screen) +{ + union acpi_object *obj; + acpi_handle handle; + struct device *privacy_screen = + drm_privacy_screen_get_drvdata(drm_privacy_screen); + + handle = acpi_device_handle(to_acpi_device(privacy_screen)); + obj = acpi_evaluate_dsm(handle, &chromeos_privacy_screen_dsm_guid, + PRIV_SCRN_DSM_REVID, + PRIV_SCRN_DSM_FN_GET_STATUS, NULL); + if (!obj) { + dev_err(privacy_screen, + "_DSM failed to get privacy-screen state\n"); + return; + } + + if (obj->type != ACPI_TYPE_INTEGER) + dev_err(privacy_screen, + "Bad _DSM to get privacy-screen state\n"); + else if (obj->integer.value == 1) + drm_privacy_screen->hw_state = drm_privacy_screen->sw_state = + PRIVACY_SCREEN_ENABLED; + else + drm_privacy_screen->hw_state = drm_privacy_screen->sw_state = + PRIVACY_SCREEN_DISABLED; + + ACPI_FREE(obj); +} + +static int +chromeos_privacy_screen_set_sw_state(struct drm_privacy_screen + *drm_privacy_screen, + enum drm_privacy_screen_status state) +{ + union acpi_object *obj = NULL; + acpi_handle handle; + struct device *privacy_screen = + drm_privacy_screen_get_drvdata(drm_privacy_screen); + + handle = acpi_device_handle(to_acpi_device(privacy_screen)); + + if (state == PRIVACY_SCREEN_DISABLED) { + obj = acpi_evaluate_dsm(handle, + &chromeos_privacy_screen_dsm_guid, + PRIV_SCRN_DSM_REVID, + PRIV_SCRN_DSM_FN_DISABLE, NULL); + } else if (state == PRIVACY_SCREEN_ENABLED) { + obj = acpi_evaluate_dsm(handle, + &chromeos_privacy_screen_dsm_guid, + PRIV_SCRN_DSM_REVID, + PRIV_SCRN_DSM_FN_ENABLE, NULL); + } else { + dev_err(privacy_screen, + "Bad attempt to set privacy-screen status to %u\n", + state); + return -EINVAL; + } + + if (!obj) { + dev_err(privacy_screen, + "_DSM failed to set privacy-screen state\n"); + return -EIO; + } + + drm_privacy_screen->hw_state = drm_privacy_screen->sw_state = state; + ACPI_FREE(obj); + return 0; +} + +static const struct drm_privacy_screen_ops chromeos_privacy_screen_ops = { + .get_hw_state = chromeos_privacy_screen_get_hw_state, + .set_sw_state = chromeos_privacy_screen_set_sw_state, +}; + +static int chromeos_privacy_screen_add(struct acpi_device *adev) +{ + struct drm_privacy_screen *drm_privacy_screen = + drm_privacy_screen_register(&adev->dev, + &chromeos_privacy_screen_ops, + &adev->dev); + + if (IS_ERR(drm_privacy_screen)) { + dev_err(&adev->dev, "Error registering privacy-screen\n"); + return PTR_ERR(drm_privacy_screen); + } + + adev->driver_data = drm_privacy_screen; + dev_info(&adev->dev, "registered privacy-screen '%s'\n", + dev_name(&drm_privacy_screen->dev)); + + return 0; +} + +static int chromeos_privacy_screen_remove(struct acpi_device *adev) +{ + struct drm_privacy_screen *drm_privacy_screen = acpi_driver_data(adev); + + drm_privacy_screen_unregister(drm_privacy_screen); + return 0; +} + +static const struct acpi_device_id chromeos_privacy_screen_device_ids[] = { + {"GOOG0010", 0}, /* Google's electronic privacy screen for eDP-1 */ + {} +}; +MODULE_DEVICE_TABLE(acpi, chromeos_privacy_screen_device_ids); + +static struct acpi_driver chromeos_privacy_screen_driver = { + .name = "chromeos_privacy_screen_driver", + .class = "ChromeOS", + .ids = chromeos_privacy_screen_device_ids, + .ops = { + .add = chromeos_privacy_screen_add, + .remove = chromeos_privacy_screen_remove, + }, +}; + +module_acpi_driver(chromeos_privacy_screen_driver); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ChromeOS ACPI Privacy Screen driver"); +MODULE_AUTHOR("Rajat Jain "); -- cgit From 0af2e827b6acfd22b7794c148943a3ddfe5fbb65 Mon Sep 17 00:00:00 2001 From: Rajat Jain Date: Fri, 7 Jan 2022 11:02:08 -0800 Subject: drm/privacy_screen_x86: Add entry for ChromeOS privacy-screen Add a static entry in the x86 table, to detect and wait for privacy-screen on some ChromeOS platforms. Please note that this means that if CONFIG_CHROMEOS_PRIVACY_SCREEN is enabled, and if "GOOG0010" device is found in ACPI, then the i915 probe shall return EPROBE_DEFER until a platform driver actually registers the privacy-screen: https://hansdegoede.livejournal.com/25948.html Signed-off-by: Rajat Jain Reviewed-by: Hans de Goede Acked-by: Benson Leung Signed-off-by: Hans de Goede Link: https://patchwork.freedesktop.org/patch/msgid/20220107190208.95479-3-rajatja@google.com --- drivers/gpu/drm/drm_privacy_screen_x86.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/drm_privacy_screen_x86.c b/drivers/gpu/drm/drm_privacy_screen_x86.c index a2cafb294ca6..88802cd7a1ee 100644 --- a/drivers/gpu/drm/drm_privacy_screen_x86.c +++ b/drivers/gpu/drm/drm_privacy_screen_x86.c @@ -47,6 +47,13 @@ static bool __init detect_thinkpad_privacy_screen(void) } #endif +#if IS_ENABLED(CONFIG_CHROMEOS_PRIVACY_SCREEN) +static bool __init detect_chromeos_privacy_screen(void) +{ + return acpi_dev_present("GOOG0010", NULL, -1); +} +#endif + static const struct arch_init_data arch_init_data[] __initconst = { #if IS_ENABLED(CONFIG_THINKPAD_ACPI) { @@ -58,6 +65,16 @@ static const struct arch_init_data arch_init_data[] __initconst = { .detect = detect_thinkpad_privacy_screen, }, #endif +#if IS_ENABLED(CONFIG_CHROMEOS_PRIVACY_SCREEN) + { + .lookup = { + .dev_id = NULL, + .con_id = NULL, + .provider = "privacy_screen-GOOG0010:00", + }, + .detect = detect_chromeos_privacy_screen, + }, +#endif }; void __init drm_privacy_screen_lookup_init(void) -- cgit From e1a4bbb6e837d4f4605dffa9eccce722fc59b9cc Mon Sep 17 00:00:00 2001 From: Thomas Hellström Date: Mon, 10 Jan 2022 18:22:14 +0100 Subject: drm/i915: Initial introduction of vma resources MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce vma resources, sort of similar to TTM resources, needed for asynchronous bind management. Initially we will use them to hold completion of unbinding when we capture data from a vma, but they will be used extensively in upcoming patches for asynchronous vma unbinding. v6: - Some documentation updates Signed-off-by: Thomas Hellström Reviewed-by: Matthew Auld Link: https://patchwork.freedesktop.org/patch/msgid/20220110172219.107131-2-thomas.hellstrom@linux.intel.com --- drivers/gpu/drm/i915/Makefile | 1 + drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c | 2 +- drivers/gpu/drm/i915/i915_vma.c | 55 +++++++++-- drivers/gpu/drm/i915/i915_vma.h | 19 +++- drivers/gpu/drm/i915/i915_vma_resource.c | 124 +++++++++++++++++++++++++ drivers/gpu/drm/i915/i915_vma_resource.h | 69 ++++++++++++++ drivers/gpu/drm/i915/i915_vma_snapshot.c | 15 +-- drivers/gpu/drm/i915/i915_vma_snapshot.h | 7 +- drivers/gpu/drm/i915/i915_vma_types.h | 5 + drivers/gpu/drm/i915/selftests/i915_gem_gtt.c | 99 ++++++++++++-------- 10 files changed, 333 insertions(+), 63 deletions(-) create mode 100644 drivers/gpu/drm/i915/i915_vma_resource.c create mode 100644 drivers/gpu/drm/i915/i915_vma_resource.h (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile index bef31c64a110..34ad92c9dbef 100644 --- a/drivers/gpu/drm/i915/Makefile +++ b/drivers/gpu/drm/i915/Makefile @@ -174,6 +174,7 @@ i915-y += \ i915_trace_points.o \ i915_ttm_buddy_manager.o \ i915_vma.o \ + i915_vma_resource.o \ i915_vma_snapshot.o \ intel_wopcm.o diff --git a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c index c0e6712651f3..934d085c791b 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c @@ -1411,7 +1411,7 @@ eb_relocate_entry(struct i915_execbuffer *eb, mutex_lock(&vma->vm->mutex); err = i915_vma_bind(target->vma, target->vma->obj->cache_level, - PIN_GLOBAL, NULL); + PIN_GLOBAL, NULL, NULL); mutex_unlock(&vma->vm->mutex); reloc_cache_remap(&eb->reloc_cache, ev->vma->obj); if (err) diff --git a/drivers/gpu/drm/i915/i915_vma.c b/drivers/gpu/drm/i915/i915_vma.c index 29a858c53bdd..b3a10c003dca 100644 --- a/drivers/gpu/drm/i915/i915_vma.c +++ b/drivers/gpu/drm/i915/i915_vma.c @@ -37,6 +37,7 @@ #include "i915_sw_fence_work.h" #include "i915_trace.h" #include "i915_vma.h" +#include "i915_vma_resource.h" static struct kmem_cache *slab_vmas; @@ -380,6 +381,8 @@ static int i915_vma_verify_bind_complete(struct i915_vma *vma) * @cache_level: mapping cache level * @flags: flags like global or local mapping * @work: preallocated worker for allocating and binding the PTE + * @vma_res: pointer to a preallocated vma resource. The resource is either + * consumed or freed. * * DMA addresses are taken from the scatter-gather table of this object (or of * this VMA in case of non-default GGTT views) and PTE entries set up. @@ -388,7 +391,8 @@ static int i915_vma_verify_bind_complete(struct i915_vma *vma) int i915_vma_bind(struct i915_vma *vma, enum i915_cache_level cache_level, u32 flags, - struct i915_vma_work *work) + struct i915_vma_work *work, + struct i915_vma_resource *vma_res) { u32 bind_flags; u32 vma_flags; @@ -399,11 +403,15 @@ int i915_vma_bind(struct i915_vma *vma, if (GEM_DEBUG_WARN_ON(range_overflows(vma->node.start, vma->node.size, - vma->vm->total))) + vma->vm->total))) { + kfree(vma_res); return -ENODEV; + } - if (GEM_DEBUG_WARN_ON(!flags)) + if (GEM_DEBUG_WARN_ON(!flags)) { + kfree(vma_res); return -EINVAL; + } bind_flags = flags; bind_flags &= I915_VMA_GLOBAL_BIND | I915_VMA_LOCAL_BIND; @@ -412,11 +420,21 @@ int i915_vma_bind(struct i915_vma *vma, vma_flags &= I915_VMA_GLOBAL_BIND | I915_VMA_LOCAL_BIND; bind_flags &= ~vma_flags; - if (bind_flags == 0) + if (bind_flags == 0) { + kfree(vma_res); return 0; + } GEM_BUG_ON(!atomic_read(&vma->pages_count)); + if (vma->resource || !vma_res) { + /* Rebinding with an additional I915_VMA_*_BIND */ + GEM_WARN_ON(!vma_flags); + kfree(vma_res); + } else { + i915_vma_resource_init(vma_res); + vma->resource = vma_res; + } trace_i915_vma_bind(vma, bind_flags); if (work && bind_flags & vma->vm->bind_async_flags) { struct dma_fence *prev; @@ -1224,6 +1242,7 @@ int i915_vma_pin_ww(struct i915_vma *vma, struct i915_gem_ww_ctx *ww, { struct i915_vma_work *work = NULL; struct dma_fence *moving = NULL; + struct i915_vma_resource *vma_res = NULL; intel_wakeref_t wakeref = 0; unsigned int bound; int err; @@ -1278,6 +1297,12 @@ int i915_vma_pin_ww(struct i915_vma *vma, struct i915_gem_ww_ctx *ww, } } + vma_res = i915_vma_resource_alloc(); + if (IS_ERR(vma_res)) { + err = PTR_ERR(vma_res); + goto err_fence; + } + /* * Differentiate between user/kernel vma inside the aliasing-ppgtt. * @@ -1298,7 +1323,7 @@ int i915_vma_pin_ww(struct i915_vma *vma, struct i915_gem_ww_ctx *ww, err = mutex_lock_interruptible_nested(&vma->vm->mutex, !(flags & PIN_GLOBAL)); if (err) - goto err_fence; + goto err_vma_res; /* No more allocations allowed now we hold vm->mutex */ @@ -1339,7 +1364,8 @@ int i915_vma_pin_ww(struct i915_vma *vma, struct i915_gem_ww_ctx *ww, GEM_BUG_ON(!vma->pages); err = i915_vma_bind(vma, vma->obj->cache_level, - flags, work); + flags, work, vma_res); + vma_res = NULL; if (err) goto err_remove; @@ -1362,6 +1388,8 @@ err_active: i915_active_release(&vma->active); err_unlock: mutex_unlock(&vma->vm->mutex); +err_vma_res: + kfree(vma_res); err_fence: if (work) dma_fence_work_commit_imm(&work->base); @@ -1512,6 +1540,7 @@ void i915_vma_release(struct kref *ref) i915_vm_put(vma->vm); i915_active_fini(&vma->active); + GEM_WARN_ON(vma->resource); i915_vma_free(vma); } @@ -1660,6 +1689,8 @@ int _i915_vma_move_to_active(struct i915_vma *vma, void __i915_vma_evict(struct i915_vma *vma) { + struct dma_fence *unbind_fence; + GEM_BUG_ON(i915_vma_is_pinned(vma)); if (i915_vma_is_map_and_fenceable(vma)) { @@ -1697,8 +1728,20 @@ void __i915_vma_evict(struct i915_vma *vma) atomic_and(~(I915_VMA_BIND_MASK | I915_VMA_ERROR | I915_VMA_GGTT_WRITE), &vma->flags); + unbind_fence = i915_vma_resource_unbind(vma->resource); + i915_vma_resource_put(vma->resource); + vma->resource = NULL; + i915_vma_detach(vma); vma_unbind_pages(vma); + + /* + * This uninterruptible wait under the vm mutex is currently + * only ever blocking while the vma is being captured from. + * With async unbinding, this wait here will be removed. + */ + dma_fence_wait(unbind_fence, false); + dma_fence_put(unbind_fence); } int __i915_vma_unbind(struct i915_vma *vma) diff --git a/drivers/gpu/drm/i915/i915_vma.h b/drivers/gpu/drm/i915/i915_vma.h index 32719431b3df..de0f3e44cdfa 100644 --- a/drivers/gpu/drm/i915/i915_vma.h +++ b/drivers/gpu/drm/i915/i915_vma.h @@ -37,6 +37,7 @@ #include "i915_active.h" #include "i915_request.h" +#include "i915_vma_resource.h" #include "i915_vma_types.h" struct i915_vma * @@ -204,7 +205,8 @@ struct i915_vma_work *i915_vma_work(void); int i915_vma_bind(struct i915_vma *vma, enum i915_cache_level cache_level, u32 flags, - struct i915_vma_work *work); + struct i915_vma_work *work, + struct i915_vma_resource *vma_res); bool i915_gem_valid_gtt_space(struct i915_vma *vma, unsigned long color); bool i915_vma_misplaced(const struct i915_vma *vma, @@ -428,6 +430,21 @@ static inline int i915_vma_sync(struct i915_vma *vma) return i915_active_wait(&vma->active); } +/** + * i915_vma_get_current_resource - Get the current resource of the vma + * @vma: The vma to get the current resource from. + * + * It's illegal to call this function if the vma is not bound. + * + * Return: A refcounted pointer to the current vma resource + * of the vma, assuming the vma is bound. + */ +static inline struct i915_vma_resource * +i915_vma_get_current_resource(struct i915_vma *vma) +{ + return i915_vma_resource_get(vma->resource); +} + void i915_vma_module_exit(void); int i915_vma_module_init(void); diff --git a/drivers/gpu/drm/i915/i915_vma_resource.c b/drivers/gpu/drm/i915/i915_vma_resource.c new file mode 100644 index 000000000000..0b4eaafe47d7 --- /dev/null +++ b/drivers/gpu/drm/i915/i915_vma_resource.c @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2021 Intel Corporation + */ +#include + +#include "i915_vma_resource.h" + +/* Callbacks for the unbind dma-fence. */ +static const char *get_driver_name(struct dma_fence *fence) +{ + return "vma unbind fence"; +} + +static const char *get_timeline_name(struct dma_fence *fence) +{ + return "unbound"; +} + +static struct dma_fence_ops unbind_fence_ops = { + .get_driver_name = get_driver_name, + .get_timeline_name = get_timeline_name, +}; + +/** + * i915_vma_resource_init - Initialize a vma resource. + * @vma_res: The vma resource to initialize + * + * Initializes a vma resource allocated using i915_vma_resource_alloc(). + * The reason for having separate allocate and initialize function is that + * initialization may need to be performed from under a lock where + * allocation is not allowed. + */ +void i915_vma_resource_init(struct i915_vma_resource *vma_res) +{ + spin_lock_init(&vma_res->lock); + dma_fence_init(&vma_res->unbind_fence, &unbind_fence_ops, + &vma_res->lock, 0, 0); + refcount_set(&vma_res->hold_count, 1); +} + +/** + * i915_vma_resource_alloc - Allocate a vma resource + * + * Return: A pointer to a cleared struct i915_vma_resource or + * a -ENOMEM error pointer if allocation fails. + */ +struct i915_vma_resource *i915_vma_resource_alloc(void) +{ + struct i915_vma_resource *vma_res = + kzalloc(sizeof(*vma_res), GFP_KERNEL); + + return vma_res ? vma_res : ERR_PTR(-ENOMEM); +} + +static void __i915_vma_resource_unhold(struct i915_vma_resource *vma_res) +{ + if (refcount_dec_and_test(&vma_res->hold_count)) + dma_fence_signal(&vma_res->unbind_fence); +} + +/** + * i915_vma_resource_unhold - Unhold the signaling of the vma resource unbind + * fence. + * @vma_res: The vma resource. + * @lockdep_cookie: The lockdep cookie returned from i915_vma_resource_hold. + * + * The function may leave a dma_fence critical section. + */ +void i915_vma_resource_unhold(struct i915_vma_resource *vma_res, + bool lockdep_cookie) +{ + dma_fence_end_signalling(lockdep_cookie); + + if (IS_ENABLED(CONFIG_PROVE_LOCKING)) { + unsigned long irq_flags; + + /* Inefficient open-coded might_lock_irqsave() */ + spin_lock_irqsave(&vma_res->lock, irq_flags); + spin_unlock_irqrestore(&vma_res->lock, irq_flags); + } + + __i915_vma_resource_unhold(vma_res); +} + +/** + * i915_vma_resource_hold - Hold the signaling of the vma resource unbind fence. + * @vma_res: The vma resource. + * @lockdep_cookie: Pointer to a bool serving as a lockdep cooke that should + * be given as an argument to the pairing i915_vma_resource_unhold. + * + * If returning true, the function enters a dma_fence signalling critical + * section if not in one already. + * + * Return: true if holding successful, false if not. + */ +bool i915_vma_resource_hold(struct i915_vma_resource *vma_res, + bool *lockdep_cookie) +{ + bool held = refcount_inc_not_zero(&vma_res->hold_count); + + if (held) + *lockdep_cookie = dma_fence_begin_signalling(); + + return held; +} + +/** + * i915_vma_resource_unbind - Unbind a vma resource + * @vma_res: The vma resource to unbind. + * + * At this point this function does little more than publish a fence that + * signals immediately unless signaling is held back. + * + * Return: A refcounted pointer to a dma-fence that signals when unbinding is + * complete. + */ +struct dma_fence * +i915_vma_resource_unbind(struct i915_vma_resource *vma_res) +{ + __i915_vma_resource_unhold(vma_res); + dma_fence_get(&vma_res->unbind_fence); + return &vma_res->unbind_fence; +} diff --git a/drivers/gpu/drm/i915/i915_vma_resource.h b/drivers/gpu/drm/i915/i915_vma_resource.h new file mode 100644 index 000000000000..3353d32ceda8 --- /dev/null +++ b/drivers/gpu/drm/i915/i915_vma_resource.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2021 Intel Corporation + */ + +#ifndef __I915_VMA_RESOURCE_H__ +#define __I915_VMA_RESOURCE_H__ + +#include +#include + +/** + * struct i915_vma_resource - Snapshotted unbind information. + * @unbind_fence: Fence to mark unbinding complete. Note that this fence + * is not considered published until unbind is scheduled, and as such it + * is illegal to access this fence before scheduled unbind other than + * for refcounting. + * @lock: The @unbind_fence lock. + * @hold_count: Number of holders blocking the fence from finishing. + * The vma itself is keeping a hold, which is released when unbind + * is scheduled. + * + * The lifetime of a struct i915_vma_resource is from a binding request to + * the actual possible asynchronous unbind has completed. + */ +struct i915_vma_resource { + struct dma_fence unbind_fence; + /* See above for description of the lock. */ + spinlock_t lock; + refcount_t hold_count; +}; + +bool i915_vma_resource_hold(struct i915_vma_resource *vma_res, + bool *lockdep_cookie); + +void i915_vma_resource_unhold(struct i915_vma_resource *vma_res, + bool lockdep_cookie); + +struct i915_vma_resource *i915_vma_resource_alloc(void); + +struct dma_fence *i915_vma_resource_unbind(struct i915_vma_resource *vma_res); + +/** + * i915_vma_resource_get - Take a reference on a vma resource + * @vma_res: The vma resource on which to take a reference. + * + * Return: The @vma_res pointer + */ +static inline struct i915_vma_resource +*i915_vma_resource_get(struct i915_vma_resource *vma_res) +{ + dma_fence_get(&vma_res->unbind_fence); + return vma_res; +} + +/** + * i915_vma_resource_put - Release a reference to a struct i915_vma_resource + * @vma_res: The resource + */ +static inline void i915_vma_resource_put(struct i915_vma_resource *vma_res) +{ + dma_fence_put(&vma_res->unbind_fence); +} + +#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) +void i915_vma_resource_init(struct i915_vma_resource *vma_res); +#endif + +#endif diff --git a/drivers/gpu/drm/i915/i915_vma_snapshot.c b/drivers/gpu/drm/i915/i915_vma_snapshot.c index 2949ceea9884..f7333c7a2f5e 100644 --- a/drivers/gpu/drm/i915/i915_vma_snapshot.c +++ b/drivers/gpu/drm/i915/i915_vma_snapshot.c @@ -3,6 +3,7 @@ * Copyright © 2021 Intel Corporation */ +#include "i915_vma_resource.h" #include "i915_vma_snapshot.h" #include "i915_vma_types.h" #include "i915_vma.h" @@ -35,7 +36,7 @@ void i915_vma_snapshot_init(struct i915_vma_snapshot *vsnap, vsnap->pages_rsgt = i915_refct_sgt_get(vma->obj->mm.rsgt); vsnap->mr = vma->obj->mm.region; kref_init(&vsnap->kref); - vsnap->vma_resource = &vma->active; + vsnap->vma_resource = i915_vma_get_current_resource(vma); vsnap->onstack = false; vsnap->present = true; } @@ -62,6 +63,7 @@ static void vma_snapshot_release(struct kref *ref) container_of(ref, typeof(*vsnap), kref); vsnap->present = false; + i915_vma_resource_put(vsnap->vma_resource); if (vsnap->pages_rsgt) i915_refct_sgt_put(vsnap->pages_rsgt); if (!vsnap->onstack) @@ -109,12 +111,7 @@ void i915_vma_snapshot_put_onstack(struct i915_vma_snapshot *vsnap) bool i915_vma_snapshot_resource_pin(struct i915_vma_snapshot *vsnap, bool *lockdep_cookie) { - bool pinned = i915_active_acquire_if_busy(vsnap->vma_resource); - - if (pinned) - *lockdep_cookie = dma_fence_begin_signalling(); - - return pinned; + return i915_vma_resource_hold(vsnap->vma_resource, lockdep_cookie); } /** @@ -128,7 +125,5 @@ bool i915_vma_snapshot_resource_pin(struct i915_vma_snapshot *vsnap, void i915_vma_snapshot_resource_unpin(struct i915_vma_snapshot *vsnap, bool lockdep_cookie) { - dma_fence_end_signalling(lockdep_cookie); - - return i915_active_release(vsnap->vma_resource); + i915_vma_resource_unhold(vsnap->vma_resource, lockdep_cookie); } diff --git a/drivers/gpu/drm/i915/i915_vma_snapshot.h b/drivers/gpu/drm/i915/i915_vma_snapshot.h index 940581df4622..e74588dd676b 100644 --- a/drivers/gpu/drm/i915/i915_vma_snapshot.h +++ b/drivers/gpu/drm/i915/i915_vma_snapshot.h @@ -31,10 +31,7 @@ struct sg_table; * @pages_rsgt: The refcounted sg_table holding the reference for @pages if any. * @mr: The memory region pointed for the pages bound. * @kref: Reference for this structure. - * @vma_resource: FIXME: A means to keep the unbind fence from signaling. - * Temporarily while we have only sync unbinds, and still use the vma - * active, we use that. With async unbinding we need a signaling refcount - * for the unbind fence. + * @vma_resource: Pointer to the vma resource representing the vma binding. * @page_sizes: The vma GTT page sizes information. * @onstack: Whether the structure shouldn't be freed on final put. * @present: Whether the structure is present and initialized. @@ -49,7 +46,7 @@ struct i915_vma_snapshot { struct i915_refct_sgt *pages_rsgt; struct intel_memory_region *mr; struct kref kref; - struct i915_active *vma_resource; + struct i915_vma_resource *vma_resource; u32 page_sizes; bool onstack:1; bool present:1; diff --git a/drivers/gpu/drm/i915/i915_vma_types.h b/drivers/gpu/drm/i915/i915_vma_types.h index 412d3e78a30d..370601282a16 100644 --- a/drivers/gpu/drm/i915/i915_vma_types.h +++ b/drivers/gpu/drm/i915/i915_vma_types.h @@ -95,6 +95,8 @@ enum i915_cache_level; * */ +struct i915_vma_resource; + struct intel_remapped_plane_info { /* in gtt pages */ u32 offset; @@ -282,6 +284,9 @@ struct i915_vma { struct list_head evict_link; struct list_head closed_link; + + /** The async vma resource. Protected by the vm_mutex */ + struct i915_vma_resource *resource; }; #endif diff --git a/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c b/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c index 357ced0b88e7..19d4defad95c 100644 --- a/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c +++ b/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c @@ -32,6 +32,7 @@ #include "i915_random.h" #include "i915_selftest.h" +#include "i915_vma_resource.h" #include "mock_drm.h" #include "mock_gem_device.h" @@ -1336,6 +1337,33 @@ static int igt_mock_drunk(void *arg) return exercise_mock(ggtt->vm.i915, drunk_hole); } +static int reserve_gtt_with_resource(struct i915_vma *vma, u64 offset) +{ + struct i915_address_space *vm = vma->vm; + struct i915_vma_resource *vma_res; + struct drm_i915_gem_object *obj = vma->obj; + int err; + + vma_res = i915_vma_resource_alloc(); + if (IS_ERR(vma_res)) + return PTR_ERR(vma_res); + + mutex_lock(&vm->mutex); + err = i915_gem_gtt_reserve(vm, &vma->node, obj->base.size, + offset, + obj->cache_level, + 0); + if (!err) { + i915_vma_resource_init(vma_res); + vma->resource = vma_res; + } else { + kfree(vma_res); + } + mutex_unlock(&vm->mutex); + + return err; +} + static int igt_gtt_reserve(void *arg) { struct i915_ggtt *ggtt = arg; @@ -1370,20 +1398,13 @@ static int igt_gtt_reserve(void *arg) } list_add(&obj->st_link, &objects); - vma = i915_vma_instance(obj, &ggtt->vm, NULL); if (IS_ERR(vma)) { err = PTR_ERR(vma); goto out; } - mutex_lock(&ggtt->vm.mutex); - err = i915_gem_gtt_reserve(&ggtt->vm, &vma->node, - obj->base.size, - total, - obj->cache_level, - 0); - mutex_unlock(&ggtt->vm.mutex); + err = reserve_gtt_with_resource(vma, total); if (err) { pr_err("i915_gem_gtt_reserve (pass 1) failed at %llu/%llu with err=%d\n", total, ggtt->vm.total, err); @@ -1429,13 +1450,7 @@ static int igt_gtt_reserve(void *arg) goto out; } - mutex_lock(&ggtt->vm.mutex); - err = i915_gem_gtt_reserve(&ggtt->vm, &vma->node, - obj->base.size, - total, - obj->cache_level, - 0); - mutex_unlock(&ggtt->vm.mutex); + err = reserve_gtt_with_resource(vma, total); if (err) { pr_err("i915_gem_gtt_reserve (pass 2) failed at %llu/%llu with err=%d\n", total, ggtt->vm.total, err); @@ -1476,13 +1491,7 @@ static int igt_gtt_reserve(void *arg) 2 * I915_GTT_PAGE_SIZE, I915_GTT_MIN_ALIGNMENT); - mutex_lock(&ggtt->vm.mutex); - err = i915_gem_gtt_reserve(&ggtt->vm, &vma->node, - obj->base.size, - offset, - obj->cache_level, - 0); - mutex_unlock(&ggtt->vm.mutex); + err = reserve_gtt_with_resource(vma, offset); if (err) { pr_err("i915_gem_gtt_reserve (pass 3) failed at %llu/%llu with err=%d\n", total, ggtt->vm.total, err); @@ -1509,6 +1518,31 @@ out: return err; } +static int insert_gtt_with_resource(struct i915_vma *vma) +{ + struct i915_address_space *vm = vma->vm; + struct i915_vma_resource *vma_res; + struct drm_i915_gem_object *obj = vma->obj; + int err; + + vma_res = i915_vma_resource_alloc(); + if (IS_ERR(vma_res)) + return PTR_ERR(vma_res); + + mutex_lock(&vm->mutex); + err = i915_gem_gtt_insert(vm, &vma->node, obj->base.size, 0, + obj->cache_level, 0, vm->total, 0); + if (!err) { + i915_vma_resource_init(vma_res); + vma->resource = vma_res; + } else { + kfree(vma_res); + } + mutex_unlock(&vm->mutex); + + return err; +} + static int igt_gtt_insert(void *arg) { struct i915_ggtt *ggtt = arg; @@ -1593,12 +1627,7 @@ static int igt_gtt_insert(void *arg) goto out; } - mutex_lock(&ggtt->vm.mutex); - err = i915_gem_gtt_insert(&ggtt->vm, &vma->node, - obj->base.size, 0, obj->cache_level, - 0, ggtt->vm.total, - 0); - mutex_unlock(&ggtt->vm.mutex); + err = insert_gtt_with_resource(vma); if (err == -ENOSPC) { /* maxed out the GGTT space */ i915_gem_object_put(obj); @@ -1653,12 +1682,7 @@ static int igt_gtt_insert(void *arg) goto out; } - mutex_lock(&ggtt->vm.mutex); - err = i915_gem_gtt_insert(&ggtt->vm, &vma->node, - obj->base.size, 0, obj->cache_level, - 0, ggtt->vm.total, - 0); - mutex_unlock(&ggtt->vm.mutex); + err = insert_gtt_with_resource(vma); if (err) { pr_err("i915_gem_gtt_insert (pass 2) failed at %llu/%llu with err=%d\n", total, ggtt->vm.total, err); @@ -1702,12 +1726,7 @@ static int igt_gtt_insert(void *arg) goto out; } - mutex_lock(&ggtt->vm.mutex); - err = i915_gem_gtt_insert(&ggtt->vm, &vma->node, - obj->base.size, 0, obj->cache_level, - 0, ggtt->vm.total, - 0); - mutex_unlock(&ggtt->vm.mutex); + err = insert_gtt_with_resource(vma); if (err) { pr_err("i915_gem_gtt_insert (pass 3) failed at %llu/%llu with err=%d\n", total, ggtt->vm.total, err); -- cgit From 39a2bd34c933b00f7c7ada923c212b3ff826fb5d Mon Sep 17 00:00:00 2001 From: Thomas Hellström Date: Mon, 10 Jan 2022 18:22:15 +0100 Subject: drm/i915: Use the vma resource as argument for gtt binding / unbinding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When introducing asynchronous unbinding, the vma itself may no longer be alive when the actual binding or unbinding takes place. Update the gtt i915_vma_ops accordingly to take a struct i915_vma_resource instead of a struct i915_vma for the bind_vma() and unbind_vma() ops. Similarly change the insert_entries() op for struct i915_address_space. Replace a couple of i915_vma_snapshot members with their newly introduced i915_vma_resource counterparts, since they have the same lifetime. Also make sure to avoid changing the struct i915_vma_flags (in particular the bind flags) async. That should now only be done sync under the vm mutex. v2: - Update the vma_res::bound_flags when binding to the aliased ggtt v6: - Remove I915_VMA_ALLOC_BIT (Matthew Auld) - Change some members of struct i915_vma_resource from unsigned long to u64 (Matthew Auld) v7: - Fix vma resource size parameters to be u64 rather than unsigned long (Matthew Auld) Signed-off-by: Thomas Hellström Reviewed-by: Matthew Auld Link: https://patchwork.freedesktop.org/patch/msgid/20220110172219.107131-3-thomas.hellstrom@linux.intel.com --- drivers/gpu/drm/i915/display/intel_dpt.c | 27 ++++--- drivers/gpu/drm/i915/gem/i915_gem_object_types.h | 27 +------ drivers/gpu/drm/i915/gem/selftests/huge_pages.c | 37 ++++----- drivers/gpu/drm/i915/gt/gen6_ppgtt.c | 19 ++--- drivers/gpu/drm/i915/gt/gen8_ppgtt.c | 37 ++++----- drivers/gpu/drm/i915/gt/intel_engine_cs.c | 4 +- drivers/gpu/drm/i915/gt/intel_ggtt.c | 70 +++++++++-------- drivers/gpu/drm/i915/gt/intel_gtt.h | 16 ++-- drivers/gpu/drm/i915/gt/intel_ppgtt.c | 22 +++--- drivers/gpu/drm/i915/gt/uc/intel_uc_fw.c | 13 ++-- drivers/gpu/drm/i915/gt/uc/intel_uc_fw.h | 2 +- drivers/gpu/drm/i915/i915_debugfs.c | 3 +- drivers/gpu/drm/i915/i915_gpu_error.c | 6 +- drivers/gpu/drm/i915/i915_vma.c | 24 +++++- drivers/gpu/drm/i915/i915_vma.h | 11 ++- drivers/gpu/drm/i915/i915_vma_resource.c | 9 +-- drivers/gpu/drm/i915/i915_vma_resource.h | 99 +++++++++++++++++++++++- drivers/gpu/drm/i915/i915_vma_snapshot.c | 4 - drivers/gpu/drm/i915/i915_vma_snapshot.h | 8 -- drivers/gpu/drm/i915/i915_vma_types.h | 14 ++-- drivers/gpu/drm/i915/selftests/i915_gem_gtt.c | 64 ++++++++++----- drivers/gpu/drm/i915/selftests/mock_gtt.c | 12 +-- 22 files changed, 314 insertions(+), 214 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/display/intel_dpt.c b/drivers/gpu/drm/i915/display/intel_dpt.c index 6fcf08c7ac66..d723655e4596 100644 --- a/drivers/gpu/drm/i915/display/intel_dpt.c +++ b/drivers/gpu/drm/i915/display/intel_dpt.c @@ -48,7 +48,7 @@ static void dpt_insert_page(struct i915_address_space *vm, } static void dpt_insert_entries(struct i915_address_space *vm, - struct i915_vma *vma, + struct i915_vma_resource *vma_res, enum i915_cache_level level, u32 flags) { @@ -64,8 +64,8 @@ static void dpt_insert_entries(struct i915_address_space *vm, * not to allow the user to override access to a read only page. */ - i = vma->node.start / I915_GTT_PAGE_SIZE; - for_each_sgt_daddr(addr, sgt_iter, vma->pages) + i = vma_res->start / I915_GTT_PAGE_SIZE; + for_each_sgt_daddr(addr, sgt_iter, vma_res->bi.pages) gen8_set_pte(&base[i++], pte_encode | addr); } @@ -76,35 +76,38 @@ static void dpt_clear_range(struct i915_address_space *vm, static void dpt_bind_vma(struct i915_address_space *vm, struct i915_vm_pt_stash *stash, - struct i915_vma *vma, + struct i915_vma_resource *vma_res, enum i915_cache_level cache_level, u32 flags) { - struct drm_i915_gem_object *obj = vma->obj; u32 pte_flags; + if (vma_res->bound_flags) + return; + /* Applicable to VLV (gen8+ do not support RO in the GGTT) */ pte_flags = 0; - if (vma->vm->has_read_only && i915_gem_object_is_readonly(obj)) + if (vm->has_read_only && vma_res->bi.readonly) pte_flags |= PTE_READ_ONLY; - if (i915_gem_object_is_lmem(obj)) + if (vma_res->bi.lmem) pte_flags |= PTE_LM; - vma->vm->insert_entries(vma->vm, vma, cache_level, pte_flags); + vm->insert_entries(vm, vma_res, cache_level, pte_flags); - vma->page_sizes.gtt = I915_GTT_PAGE_SIZE; + vma_res->page_sizes_gtt = I915_GTT_PAGE_SIZE; /* * Without aliasing PPGTT there's no difference between * GLOBAL/LOCAL_BIND, it's all the same ptes. Hence unconditionally * upgrade to both bound if we bind either to avoid double-binding. */ - atomic_or(I915_VMA_GLOBAL_BIND | I915_VMA_LOCAL_BIND, &vma->flags); + vma_res->bound_flags = I915_VMA_GLOBAL_BIND | I915_VMA_LOCAL_BIND; } -static void dpt_unbind_vma(struct i915_address_space *vm, struct i915_vma *vma) +static void dpt_unbind_vma(struct i915_address_space *vm, + struct i915_vma_resource *vma_res) { - vm->clear_range(vm, vma->node.start, vma->size); + vm->clear_range(vm, vma_res->start, vma_res->vma_size); } static void dpt_cleanup(struct i915_address_space *vm) diff --git a/drivers/gpu/drm/i915/gem/i915_gem_object_types.h b/drivers/gpu/drm/i915/gem/i915_gem_object_types.h index 1c903d813097..71e778ecaeb8 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_object_types.h +++ b/drivers/gpu/drm/i915/gem/i915_gem_object_types.h @@ -15,6 +15,7 @@ #include "i915_active.h" #include "i915_selftest.h" +#include "i915_vma_resource.h" struct drm_i915_gem_object; struct intel_fronbuffer; @@ -566,31 +567,7 @@ struct drm_i915_gem_object { struct sg_table *pages; void *mapping; - struct i915_page_sizes { - /** - * The sg mask of the pages sg_table. i.e the mask of - * of the lengths for each sg entry. - */ - unsigned int phys; - - /** - * The gtt page sizes we are allowed to use given the - * sg mask and the supported page sizes. This will - * express the smallest unit we can use for the whole - * object, as well as the larger sizes we may be able - * to use opportunistically. - */ - unsigned int sg; - - /** - * The actual gtt page size usage. Since we can have - * multiple vma associated with this object we need to - * prevent any trampling of state, hence a copy of this - * struct also lives in each vma, therefore the gtt - * value here should only be read/write through the vma. - */ - unsigned int gtt; - } page_sizes; + struct i915_page_sizes page_sizes; I915_SELFTEST_DECLARE(unsigned int page_mask); diff --git a/drivers/gpu/drm/i915/gem/selftests/huge_pages.c b/drivers/gpu/drm/i915/gem/selftests/huge_pages.c index 11f0aa65f8a3..26f997c376a2 100644 --- a/drivers/gpu/drm/i915/gem/selftests/huge_pages.c +++ b/drivers/gpu/drm/i915/gem/selftests/huge_pages.c @@ -370,9 +370,9 @@ static int igt_check_page_sizes(struct i915_vma *vma) err = -EINVAL; } - if (!HAS_PAGE_SIZES(i915, vma->page_sizes.gtt)) { + if (!HAS_PAGE_SIZES(i915, vma->resource->page_sizes_gtt)) { pr_err("unsupported page_sizes.gtt=%u, supported=%u\n", - vma->page_sizes.gtt & ~supported, supported); + vma->resource->page_sizes_gtt & ~supported, supported); err = -EINVAL; } @@ -403,15 +403,9 @@ static int igt_check_page_sizes(struct i915_vma *vma) if (i915_gem_object_is_lmem(obj) && IS_ALIGNED(vma->node.start, SZ_2M) && vma->page_sizes.sg & SZ_2M && - vma->page_sizes.gtt < SZ_2M) { + vma->resource->page_sizes_gtt < SZ_2M) { pr_err("gtt pages mismatch for LMEM, expected 2M GTT pages, sg(%u), gtt(%u)\n", - vma->page_sizes.sg, vma->page_sizes.gtt); - err = -EINVAL; - } - - if (obj->mm.page_sizes.gtt) { - pr_err("obj->page_sizes.gtt(%u) should never be set\n", - obj->mm.page_sizes.gtt); + vma->page_sizes.sg, vma->resource->page_sizes_gtt); err = -EINVAL; } @@ -547,9 +541,9 @@ static int igt_mock_memory_region_huge_pages(void *arg) goto out_unpin; } - if (vma->page_sizes.gtt != page_size) { + if (vma->resource->page_sizes_gtt != page_size) { pr_err("%s page_sizes.gtt=%u, expected=%u\n", - __func__, vma->page_sizes.gtt, + __func__, vma->resource->page_sizes_gtt, page_size); err = -EINVAL; goto out_unpin; @@ -630,9 +624,9 @@ static int igt_mock_ppgtt_misaligned_dma(void *arg) err = igt_check_page_sizes(vma); - if (vma->page_sizes.gtt != page_size) { + if (vma->resource->page_sizes_gtt != page_size) { pr_err("page_sizes.gtt=%u, expected %u\n", - vma->page_sizes.gtt, page_size); + vma->resource->page_sizes_gtt, page_size); err = -EINVAL; } @@ -657,9 +651,10 @@ static int igt_mock_ppgtt_misaligned_dma(void *arg) err = igt_check_page_sizes(vma); - if (vma->page_sizes.gtt != I915_GTT_PAGE_SIZE_4K) { + if (vma->resource->page_sizes_gtt != I915_GTT_PAGE_SIZE_4K) { pr_err("page_sizes.gtt=%u, expected %llu\n", - vma->page_sizes.gtt, I915_GTT_PAGE_SIZE_4K); + vma->resource->page_sizes_gtt, + I915_GTT_PAGE_SIZE_4K); err = -EINVAL; } @@ -805,9 +800,9 @@ static int igt_mock_ppgtt_huge_fill(void *arg) } } - if (vma->page_sizes.gtt != expected_gtt) { + if (vma->resource->page_sizes_gtt != expected_gtt) { pr_err("gtt=%u, expected=%u, size=%zd, single=%s\n", - vma->page_sizes.gtt, expected_gtt, + vma->resource->page_sizes_gtt, expected_gtt, obj->base.size, yesno(!!single)); err = -EINVAL; break; @@ -961,10 +956,10 @@ static int igt_mock_ppgtt_64K(void *arg) } } - if (vma->page_sizes.gtt != expected_gtt) { + if (vma->resource->page_sizes_gtt != expected_gtt) { pr_err("gtt=%u, expected=%u, i=%d, single=%s\n", - vma->page_sizes.gtt, expected_gtt, i, - yesno(!!single)); + vma->resource->page_sizes_gtt, + expected_gtt, i, yesno(!!single)); err = -EINVAL; goto out_vma_unpin; } diff --git a/drivers/gpu/drm/i915/gt/gen6_ppgtt.c b/drivers/gpu/drm/i915/gt/gen6_ppgtt.c index 6e9292918bfc..d657ffd6c86a 100644 --- a/drivers/gpu/drm/i915/gt/gen6_ppgtt.c +++ b/drivers/gpu/drm/i915/gt/gen6_ppgtt.c @@ -104,17 +104,17 @@ static void gen6_ppgtt_clear_range(struct i915_address_space *vm, } static void gen6_ppgtt_insert_entries(struct i915_address_space *vm, - struct i915_vma *vma, + struct i915_vma_resource *vma_res, enum i915_cache_level cache_level, u32 flags) { struct i915_ppgtt *ppgtt = i915_vm_to_ppgtt(vm); struct i915_page_directory * const pd = ppgtt->pd; - unsigned int first_entry = vma->node.start / I915_GTT_PAGE_SIZE; + unsigned int first_entry = vma_res->start / I915_GTT_PAGE_SIZE; unsigned int act_pt = first_entry / GEN6_PTES; unsigned int act_pte = first_entry % GEN6_PTES; const u32 pte_encode = vm->pte_encode(0, cache_level, flags); - struct sgt_dma iter = sgt_dma(vma); + struct sgt_dma iter = sgt_dma(vma_res); gen6_pte_t *vaddr; GEM_BUG_ON(!pd->entry[act_pt]); @@ -140,7 +140,7 @@ static void gen6_ppgtt_insert_entries(struct i915_address_space *vm, } } while (1); - vma->page_sizes.gtt = I915_GTT_PAGE_SIZE; + vma_res->page_sizes_gtt = I915_GTT_PAGE_SIZE; } static void gen6_flush_pd(struct gen6_ppgtt *ppgtt, u64 start, u64 end) @@ -271,13 +271,13 @@ static void gen6_ppgtt_cleanup(struct i915_address_space *vm) static void pd_vma_bind(struct i915_address_space *vm, struct i915_vm_pt_stash *stash, - struct i915_vma *vma, + struct i915_vma_resource *vma_res, enum i915_cache_level cache_level, u32 unused) { struct i915_ggtt *ggtt = i915_vm_to_ggtt(vm); - struct gen6_ppgtt *ppgtt = vma->private; - u32 ggtt_offset = i915_ggtt_offset(vma) / I915_GTT_PAGE_SIZE; + struct gen6_ppgtt *ppgtt = vma_res->private; + u32 ggtt_offset = vma_res->start / I915_GTT_PAGE_SIZE; ppgtt->pp_dir = ggtt_offset * sizeof(gen6_pte_t) << 10; ppgtt->pd_addr = (gen6_pte_t __iomem *)ggtt->gsm + ggtt_offset; @@ -285,9 +285,10 @@ static void pd_vma_bind(struct i915_address_space *vm, gen6_flush_pd(ppgtt, 0, ppgtt->base.vm.total); } -static void pd_vma_unbind(struct i915_address_space *vm, struct i915_vma *vma) +static void pd_vma_unbind(struct i915_address_space *vm, + struct i915_vma_resource *vma_res) { - struct gen6_ppgtt *ppgtt = vma->private; + struct gen6_ppgtt *ppgtt = vma_res->private; struct i915_page_directory * const pd = ppgtt->base.pd; struct i915_page_table *pt; unsigned int pde; diff --git a/drivers/gpu/drm/i915/gt/gen8_ppgtt.c b/drivers/gpu/drm/i915/gt/gen8_ppgtt.c index b012c50f7ce7..c43e724afa9f 100644 --- a/drivers/gpu/drm/i915/gt/gen8_ppgtt.c +++ b/drivers/gpu/drm/i915/gt/gen8_ppgtt.c @@ -453,20 +453,21 @@ gen8_ppgtt_insert_pte(struct i915_ppgtt *ppgtt, return idx; } -static void gen8_ppgtt_insert_huge(struct i915_vma *vma, +static void gen8_ppgtt_insert_huge(struct i915_address_space *vm, + struct i915_vma_resource *vma_res, struct sgt_dma *iter, enum i915_cache_level cache_level, u32 flags) { const gen8_pte_t pte_encode = gen8_pte_encode(0, cache_level, flags); unsigned int rem = sg_dma_len(iter->sg); - u64 start = vma->node.start; + u64 start = vma_res->start; - GEM_BUG_ON(!i915_vm_is_4lvl(vma->vm)); + GEM_BUG_ON(!i915_vm_is_4lvl(vm)); do { struct i915_page_directory * const pdp = - gen8_pdp_for_page_address(vma->vm, start); + gen8_pdp_for_page_address(vm, start); struct i915_page_directory * const pd = i915_pd_entry(pdp, __gen8_pte_index(start, 2)); gen8_pte_t encode = pte_encode; @@ -475,7 +476,7 @@ static void gen8_ppgtt_insert_huge(struct i915_vma *vma, gen8_pte_t *vaddr; u16 index; - if (vma->page_sizes.sg & I915_GTT_PAGE_SIZE_2M && + if (vma_res->bi.page_sizes.sg & I915_GTT_PAGE_SIZE_2M && IS_ALIGNED(iter->dma, I915_GTT_PAGE_SIZE_2M) && rem >= I915_GTT_PAGE_SIZE_2M && !__gen8_pte_index(start, 0)) { @@ -492,7 +493,7 @@ static void gen8_ppgtt_insert_huge(struct i915_vma *vma, page_size = I915_GTT_PAGE_SIZE; if (!index && - vma->page_sizes.sg & I915_GTT_PAGE_SIZE_64K && + vma_res->bi.page_sizes.sg & I915_GTT_PAGE_SIZE_64K && IS_ALIGNED(iter->dma, I915_GTT_PAGE_SIZE_64K) && (IS_ALIGNED(rem, I915_GTT_PAGE_SIZE_64K) || rem >= (I915_PDES - index) * I915_GTT_PAGE_SIZE)) @@ -541,9 +542,9 @@ static void gen8_ppgtt_insert_huge(struct i915_vma *vma, */ if (maybe_64K != -1 && (index == I915_PDES || - (i915_vm_has_scratch_64K(vma->vm) && - !iter->sg && IS_ALIGNED(vma->node.start + - vma->node.size, + (i915_vm_has_scratch_64K(vm) && + !iter->sg && IS_ALIGNED(vma_res->start + + vma_res->node_size, I915_GTT_PAGE_SIZE_2M)))) { vaddr = px_vaddr(pd); vaddr[maybe_64K] |= GEN8_PDE_IPS_64K; @@ -559,10 +560,10 @@ static void gen8_ppgtt_insert_huge(struct i915_vma *vma, * instead - which we detect as missing results during * selftests. */ - if (I915_SELFTEST_ONLY(vma->vm->scrub_64K)) { + if (I915_SELFTEST_ONLY(vm->scrub_64K)) { u16 i; - encode = vma->vm->scratch[0]->encode; + encode = vm->scratch[0]->encode; vaddr = px_vaddr(i915_pt_entry(pd, maybe_64K)); for (i = 1; i < index; i += 16) @@ -572,22 +573,22 @@ static void gen8_ppgtt_insert_huge(struct i915_vma *vma, } } - vma->page_sizes.gtt |= page_size; + vma_res->page_sizes_gtt |= page_size; } while (iter->sg && sg_dma_len(iter->sg)); } static void gen8_ppgtt_insert(struct i915_address_space *vm, - struct i915_vma *vma, + struct i915_vma_resource *vma_res, enum i915_cache_level cache_level, u32 flags) { struct i915_ppgtt * const ppgtt = i915_vm_to_ppgtt(vm); - struct sgt_dma iter = sgt_dma(vma); + struct sgt_dma iter = sgt_dma(vma_res); - if (vma->page_sizes.sg > I915_GTT_PAGE_SIZE) { - gen8_ppgtt_insert_huge(vma, &iter, cache_level, flags); + if (vma_res->bi.page_sizes.sg > I915_GTT_PAGE_SIZE) { + gen8_ppgtt_insert_huge(vm, vma_res, &iter, cache_level, flags); } else { - u64 idx = vma->node.start >> GEN8_PTE_SHIFT; + u64 idx = vma_res->start >> GEN8_PTE_SHIFT; do { struct i915_page_directory * const pdp = @@ -597,7 +598,7 @@ static void gen8_ppgtt_insert(struct i915_address_space *vm, cache_level, flags); } while (idx); - vma->page_sizes.gtt = I915_GTT_PAGE_SIZE; + vma_res->page_sizes_gtt = I915_GTT_PAGE_SIZE; } } diff --git a/drivers/gpu/drm/i915/gt/intel_engine_cs.c b/drivers/gpu/drm/i915/gt/intel_engine_cs.c index 352254e001b4..74aa90587061 100644 --- a/drivers/gpu/drm/i915/gt/intel_engine_cs.c +++ b/drivers/gpu/drm/i915/gt/intel_engine_cs.c @@ -1718,8 +1718,8 @@ static void print_request_ring(struct drm_printer *m, struct i915_request *rq) drm_printf(m, "[head %04x, postfix %04x, tail %04x, batch 0x%08x_%08x]:\n", rq->head, rq->postfix, rq->tail, - vsnap ? upper_32_bits(vsnap->gtt_offset) : ~0u, - vsnap ? lower_32_bits(vsnap->gtt_offset) : ~0u); + vsnap ? upper_32_bits(vsnap->vma_resource->start) : ~0u, + vsnap ? lower_32_bits(vsnap->vma_resource->start) : ~0u); size = rq->tail - rq->head; if (rq->tail < rq->head) diff --git a/drivers/gpu/drm/i915/gt/intel_ggtt.c b/drivers/gpu/drm/i915/gt/intel_ggtt.c index ab5e5a097b03..bb885d862ec4 100644 --- a/drivers/gpu/drm/i915/gt/intel_ggtt.c +++ b/drivers/gpu/drm/i915/gt/intel_ggtt.c @@ -218,7 +218,7 @@ static void gen8_ggtt_insert_page(struct i915_address_space *vm, } static void gen8_ggtt_insert_entries(struct i915_address_space *vm, - struct i915_vma *vma, + struct i915_vma_resource *vma_res, enum i915_cache_level level, u32 flags) { @@ -235,10 +235,10 @@ static void gen8_ggtt_insert_entries(struct i915_address_space *vm, */ gte = (gen8_pte_t __iomem *)ggtt->gsm; - gte += vma->node.start / I915_GTT_PAGE_SIZE; - end = gte + vma->node.size / I915_GTT_PAGE_SIZE; + gte += vma_res->start / I915_GTT_PAGE_SIZE; + end = gte + vma_res->node_size / I915_GTT_PAGE_SIZE; - for_each_sgt_daddr(addr, iter, vma->pages) + for_each_sgt_daddr(addr, iter, vma_res->bi.pages) gen8_set_pte(gte++, pte_encode | addr); GEM_BUG_ON(gte > end); @@ -275,7 +275,7 @@ static void gen6_ggtt_insert_page(struct i915_address_space *vm, * through the GMADR mapped BAR (i915->mm.gtt->gtt). */ static void gen6_ggtt_insert_entries(struct i915_address_space *vm, - struct i915_vma *vma, + struct i915_vma_resource *vma_res, enum i915_cache_level level, u32 flags) { @@ -286,10 +286,10 @@ static void gen6_ggtt_insert_entries(struct i915_address_space *vm, dma_addr_t addr; gte = (gen6_pte_t __iomem *)ggtt->gsm; - gte += vma->node.start / I915_GTT_PAGE_SIZE; - end = gte + vma->node.size / I915_GTT_PAGE_SIZE; + gte += vma_res->start / I915_GTT_PAGE_SIZE; + end = gte + vma_res->node_size / I915_GTT_PAGE_SIZE; - for_each_sgt_daddr(addr, iter, vma->pages) + for_each_sgt_daddr(addr, iter, vma_res->bi.pages) iowrite32(vm->pte_encode(addr, level, flags), gte++); GEM_BUG_ON(gte > end); @@ -372,7 +372,7 @@ static void bxt_vtd_ggtt_insert_page__BKL(struct i915_address_space *vm, struct insert_entries { struct i915_address_space *vm; - struct i915_vma *vma; + struct i915_vma_resource *vma_res; enum i915_cache_level level; u32 flags; }; @@ -381,18 +381,18 @@ static int bxt_vtd_ggtt_insert_entries__cb(void *_arg) { struct insert_entries *arg = _arg; - gen8_ggtt_insert_entries(arg->vm, arg->vma, arg->level, arg->flags); + gen8_ggtt_insert_entries(arg->vm, arg->vma_res, arg->level, arg->flags); bxt_vtd_ggtt_wa(arg->vm); return 0; } static void bxt_vtd_ggtt_insert_entries__BKL(struct i915_address_space *vm, - struct i915_vma *vma, + struct i915_vma_resource *vma_res, enum i915_cache_level level, u32 flags) { - struct insert_entries arg = { vm, vma, level, flags }; + struct insert_entries arg = { vm, vma_res, level, flags }; stop_machine(bxt_vtd_ggtt_insert_entries__cb, &arg, NULL); } @@ -431,14 +431,14 @@ static void i915_ggtt_insert_page(struct i915_address_space *vm, } static void i915_ggtt_insert_entries(struct i915_address_space *vm, - struct i915_vma *vma, + struct i915_vma_resource *vma_res, enum i915_cache_level cache_level, u32 unused) { unsigned int flags = (cache_level == I915_CACHE_NONE) ? AGP_USER_MEMORY : AGP_USER_CACHED_MEMORY; - intel_gtt_insert_sg_entries(vma->pages, vma->node.start >> PAGE_SHIFT, + intel_gtt_insert_sg_entries(vma_res->bi.pages, vma_res->start >> PAGE_SHIFT, flags); } @@ -450,30 +450,32 @@ static void i915_ggtt_clear_range(struct i915_address_space *vm, static void ggtt_bind_vma(struct i915_address_space *vm, struct i915_vm_pt_stash *stash, - struct i915_vma *vma, + struct i915_vma_resource *vma_res, enum i915_cache_level cache_level, u32 flags) { - struct drm_i915_gem_object *obj = vma->obj; u32 pte_flags; - if (i915_vma_is_bound(vma, ~flags & I915_VMA_BIND_MASK)) + if (vma_res->bound_flags & (~flags & I915_VMA_BIND_MASK)) return; + vma_res->bound_flags |= flags; + /* Applicable to VLV (gen8+ do not support RO in the GGTT) */ pte_flags = 0; - if (i915_gem_object_is_readonly(obj)) + if (vma_res->bi.readonly) pte_flags |= PTE_READ_ONLY; - if (i915_gem_object_is_lmem(obj)) + if (vma_res->bi.lmem) pte_flags |= PTE_LM; - vm->insert_entries(vm, vma, cache_level, pte_flags); - vma->page_sizes.gtt = I915_GTT_PAGE_SIZE; + vm->insert_entries(vm, vma_res, cache_level, pte_flags); + vma_res->page_sizes_gtt = I915_GTT_PAGE_SIZE; } -static void ggtt_unbind_vma(struct i915_address_space *vm, struct i915_vma *vma) +static void ggtt_unbind_vma(struct i915_address_space *vm, + struct i915_vma_resource *vma_res) { - vm->clear_range(vm, vma->node.start, vma->size); + vm->clear_range(vm, vma_res->start, vma_res->vma_size); } static int ggtt_reserve_guc_top(struct i915_ggtt *ggtt) @@ -606,7 +608,7 @@ err: static void aliasing_gtt_bind_vma(struct i915_address_space *vm, struct i915_vm_pt_stash *stash, - struct i915_vma *vma, + struct i915_vma_resource *vma_res, enum i915_cache_level cache_level, u32 flags) { @@ -614,25 +616,27 @@ static void aliasing_gtt_bind_vma(struct i915_address_space *vm, /* Currently applicable only to VLV */ pte_flags = 0; - if (i915_gem_object_is_readonly(vma->obj)) + if (vma_res->bi.readonly) pte_flags |= PTE_READ_ONLY; if (flags & I915_VMA_LOCAL_BIND) ppgtt_bind_vma(&i915_vm_to_ggtt(vm)->alias->vm, - stash, vma, cache_level, flags); + stash, vma_res, cache_level, flags); if (flags & I915_VMA_GLOBAL_BIND) - vm->insert_entries(vm, vma, cache_level, pte_flags); + vm->insert_entries(vm, vma_res, cache_level, pte_flags); + + vma_res->bound_flags |= flags; } static void aliasing_gtt_unbind_vma(struct i915_address_space *vm, - struct i915_vma *vma) + struct i915_vma_resource *vma_res) { - if (i915_vma_is_bound(vma, I915_VMA_GLOBAL_BIND)) - vm->clear_range(vm, vma->node.start, vma->size); + if (vma_res->bound_flags & I915_VMA_GLOBAL_BIND) + vm->clear_range(vm, vma_res->start, vma_res->vma_size); - if (i915_vma_is_bound(vma, I915_VMA_LOCAL_BIND)) - ppgtt_unbind_vma(&i915_vm_to_ggtt(vm)->alias->vm, vma); + if (vma_res->bound_flags & I915_VMA_LOCAL_BIND) + ppgtt_unbind_vma(&i915_vm_to_ggtt(vm)->alias->vm, vma_res); } static int init_aliasing_ppgtt(struct i915_ggtt *ggtt) @@ -1253,7 +1257,7 @@ void i915_ggtt_resume(struct i915_ggtt *ggtt) atomic_read(&vma->flags) & I915_VMA_BIND_MASK; GEM_BUG_ON(!was_bound); - vma->ops->bind_vma(&ggtt->vm, NULL, vma, + vma->ops->bind_vma(&ggtt->vm, NULL, vma->resource, obj ? obj->cache_level : 0, was_bound); if (obj) { /* only used during resume => exclusive access */ diff --git a/drivers/gpu/drm/i915/gt/intel_gtt.h b/drivers/gpu/drm/i915/gt/intel_gtt.h index fa1bdec2473c..01519e1d1651 100644 --- a/drivers/gpu/drm/i915/gt/intel_gtt.h +++ b/drivers/gpu/drm/i915/gt/intel_gtt.h @@ -27,6 +27,7 @@ #include "gt/intel_reset.h" #include "i915_selftest.h" +#include "i915_vma_resource.h" #include "i915_vma_types.h" #define I915_GFP_ALLOW_FAIL (GFP_KERNEL | __GFP_RETRY_MAYFAIL | __GFP_NOWARN) @@ -200,7 +201,7 @@ struct i915_vma_ops { /* Map an object into an address space with the given cache flags. */ void (*bind_vma)(struct i915_address_space *vm, struct i915_vm_pt_stash *stash, - struct i915_vma *vma, + struct i915_vma_resource *vma_res, enum i915_cache_level cache_level, u32 flags); /* @@ -208,7 +209,8 @@ struct i915_vma_ops { * setting the valid PTE entries to a reserved scratch page. */ void (*unbind_vma)(struct i915_address_space *vm, - struct i915_vma *vma); + struct i915_vma_resource *vma_res); + }; struct i915_address_space { @@ -285,7 +287,7 @@ struct i915_address_space { enum i915_cache_level cache_level, u32 flags); void (*insert_entries)(struct i915_address_space *vm, - struct i915_vma *vma, + struct i915_vma_resource *vma_res, enum i915_cache_level cache_level, u32 flags); void (*cleanup)(struct i915_address_space *vm); @@ -598,11 +600,11 @@ void gen6_ggtt_invalidate(struct i915_ggtt *ggtt); void ppgtt_bind_vma(struct i915_address_space *vm, struct i915_vm_pt_stash *stash, - struct i915_vma *vma, + struct i915_vma_resource *vma_res, enum i915_cache_level cache_level, u32 flags); void ppgtt_unbind_vma(struct i915_address_space *vm, - struct i915_vma *vma); + struct i915_vma_resource *vma_res); void gtt_write_workarounds(struct intel_gt *gt); @@ -625,8 +627,8 @@ __vm_create_scratch_for_read_pinned(struct i915_address_space *vm, unsigned long static inline struct sgt_dma { struct scatterlist *sg; dma_addr_t dma, max; -} sgt_dma(struct i915_vma *vma) { - struct scatterlist *sg = vma->pages->sgl; +} sgt_dma(struct i915_vma_resource *vma_res) { + struct scatterlist *sg = vma_res->bi.pages->sgl; dma_addr_t addr = sg_dma_address(sg); return (struct sgt_dma){ sg, addr, addr + sg_dma_len(sg) }; diff --git a/drivers/gpu/drm/i915/gt/intel_ppgtt.c b/drivers/gpu/drm/i915/gt/intel_ppgtt.c index 083b3090c69c..48e6e2f87700 100644 --- a/drivers/gpu/drm/i915/gt/intel_ppgtt.c +++ b/drivers/gpu/drm/i915/gt/intel_ppgtt.c @@ -179,32 +179,34 @@ struct i915_ppgtt *i915_ppgtt_create(struct intel_gt *gt, void ppgtt_bind_vma(struct i915_address_space *vm, struct i915_vm_pt_stash *stash, - struct i915_vma *vma, + struct i915_vma_resource *vma_res, enum i915_cache_level cache_level, u32 flags) { u32 pte_flags; - if (!test_bit(I915_VMA_ALLOC_BIT, __i915_vma_flags(vma))) { - vm->allocate_va_range(vm, stash, vma->node.start, vma->size); - set_bit(I915_VMA_ALLOC_BIT, __i915_vma_flags(vma)); + if (!vma_res->allocated) { + vm->allocate_va_range(vm, stash, vma_res->start, + vma_res->vma_size); + vma_res->allocated = true; } /* Applicable to VLV, and gen8+ */ pte_flags = 0; - if (i915_gem_object_is_readonly(vma->obj)) + if (vma_res->bi.readonly) pte_flags |= PTE_READ_ONLY; - if (i915_gem_object_is_lmem(vma->obj)) + if (vma_res->bi.lmem) pte_flags |= PTE_LM; - vm->insert_entries(vm, vma, cache_level, pte_flags); + vm->insert_entries(vm, vma_res, cache_level, pte_flags); wmb(); } -void ppgtt_unbind_vma(struct i915_address_space *vm, struct i915_vma *vma) +void ppgtt_unbind_vma(struct i915_address_space *vm, + struct i915_vma_resource *vma_res) { - if (test_and_clear_bit(I915_VMA_ALLOC_BIT, __i915_vma_flags(vma))) - vm->clear_range(vm, vma->node.start, vma->size); + if (vma_res->allocated) + vm->clear_range(vm, vma_res->start, vma_res->vma_size); } static unsigned long pd_count(u64 size, int shift) diff --git a/drivers/gpu/drm/i915/gt/uc/intel_uc_fw.c b/drivers/gpu/drm/i915/gt/uc/intel_uc_fw.c index a5af05bde6f2..777fc6f0ceff 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_uc_fw.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_uc_fw.c @@ -448,20 +448,19 @@ static void uc_fw_bind_ggtt(struct intel_uc_fw *uc_fw) { struct drm_i915_gem_object *obj = uc_fw->obj; struct i915_ggtt *ggtt = __uc_fw_to_gt(uc_fw)->ggtt; - struct i915_vma *dummy = &uc_fw->dummy; + struct i915_vma_resource *dummy = &uc_fw->dummy; u32 pte_flags = 0; - dummy->node.start = uc_fw_ggtt_offset(uc_fw); - dummy->node.size = obj->base.size; - dummy->pages = obj->mm.pages; - dummy->vm = &ggtt->vm; + dummy->start = uc_fw_ggtt_offset(uc_fw); + dummy->node_size = obj->base.size; + dummy->bi.pages = obj->mm.pages; GEM_BUG_ON(!i915_gem_object_has_pinned_pages(obj)); - GEM_BUG_ON(dummy->node.size > ggtt->uc_fw.size); + GEM_BUG_ON(dummy->node_size > ggtt->uc_fw.size); /* uc_fw->obj cache domains were not controlled across suspend */ if (i915_gem_object_has_struct_page(obj)) - drm_clflush_sg(dummy->pages); + drm_clflush_sg(dummy->bi.pages); if (i915_gem_object_is_lmem(obj)) pte_flags |= PTE_LM; diff --git a/drivers/gpu/drm/i915/gt/uc/intel_uc_fw.h b/drivers/gpu/drm/i915/gt/uc/intel_uc_fw.h index d9d1dc0b4cbb..3229018877d3 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_uc_fw.h +++ b/drivers/gpu/drm/i915/gt/uc/intel_uc_fw.h @@ -85,7 +85,7 @@ struct intel_uc_fw { * threaded as it done during driver load (inherently single threaded) * or during a GT reset (mutex guarantees single threaded). */ - struct i915_vma dummy; + struct i915_vma_resource dummy; struct i915_vma *rsa_data; /* diff --git a/drivers/gpu/drm/i915/i915_debugfs.c b/drivers/gpu/drm/i915/i915_debugfs.c index 110720bbe8cf..3ee2cf3a578f 100644 --- a/drivers/gpu/drm/i915/i915_debugfs.c +++ b/drivers/gpu/drm/i915/i915_debugfs.c @@ -171,7 +171,8 @@ i915_debugfs_describe_obj(struct seq_file *m, struct drm_i915_gem_object *obj) seq_printf(m, " (%s offset: %08llx, size: %08llx, pages: %s", stringify_vma_type(vma), vma->node.start, vma->node.size, - stringify_page_sizes(vma->page_sizes.gtt, NULL, 0)); + stringify_page_sizes(vma->resource->page_sizes_gtt, + NULL, 0)); if (i915_vma_is_ggtt(vma) || i915_vma_is_dpt(vma)) { switch (vma->ggtt_view.type) { case I915_GGTT_VIEW_NORMAL: diff --git a/drivers/gpu/drm/i915/i915_gpu_error.c b/drivers/gpu/drm/i915/i915_gpu_error.c index 5ae812d60abe..1af54ff374f9 100644 --- a/drivers/gpu/drm/i915/i915_gpu_error.c +++ b/drivers/gpu/drm/i915/i915_gpu_error.c @@ -1040,9 +1040,9 @@ i915_vma_coredump_create(const struct intel_gt *gt, strcpy(dst->name, vsnap->name); dst->next = NULL; - dst->gtt_offset = vsnap->gtt_offset; - dst->gtt_size = vsnap->gtt_size; - dst->gtt_page_sizes = vsnap->page_sizes; + dst->gtt_offset = vsnap->vma_resource->start; + dst->gtt_size = vsnap->vma_resource->node_size; + dst->gtt_page_sizes = vsnap->vma_resource->page_sizes_gtt; dst->unused = 0; ret = -EINVAL; diff --git a/drivers/gpu/drm/i915/i915_vma.c b/drivers/gpu/drm/i915/i915_vma.c index b3a10c003dca..4884264f3015 100644 --- a/drivers/gpu/drm/i915/i915_vma.c +++ b/drivers/gpu/drm/i915/i915_vma.c @@ -298,7 +298,7 @@ static void __vma_bind(struct dma_fence_work *work) struct i915_vma *vma = vw->vma; vma->ops->bind_vma(vw->vm, &vw->stash, - vma, vw->cache_level, vw->flags); + vma->resource, vw->cache_level, vw->flags); } static void __vma_release(struct dma_fence_work *work) @@ -375,6 +375,21 @@ static int i915_vma_verify_bind_complete(struct i915_vma *vma) #define i915_vma_verify_bind_complete(_vma) 0 #endif +I915_SELFTEST_EXPORT void +i915_vma_resource_init_from_vma(struct i915_vma_resource *vma_res, + struct i915_vma *vma) +{ + struct drm_i915_gem_object *obj = vma->obj; + + i915_vma_resource_init(vma_res, vma->pages, &vma->page_sizes, + i915_gem_object_is_readonly(obj), + i915_gem_object_is_lmem(obj), + vma->private, + vma->node.start, + vma->node.size, + vma->size); +} + /** * i915_vma_bind - Sets up PTEs for an VMA in it's corresponding address space. * @vma: VMA to map @@ -432,7 +447,7 @@ int i915_vma_bind(struct i915_vma *vma, GEM_WARN_ON(!vma_flags); kfree(vma_res); } else { - i915_vma_resource_init(vma_res); + i915_vma_resource_init_from_vma(vma_res, vma); vma->resource = vma_res; } trace_i915_vma_bind(vma, bind_flags); @@ -472,7 +487,8 @@ int i915_vma_bind(struct i915_vma *vma, if (ret) return ret; } - vma->ops->bind_vma(vma->vm, NULL, vma, cache_level, bind_flags); + vma->ops->bind_vma(vma->vm, NULL, vma->resource, cache_level, + bind_flags); } atomic_or(bind_flags, &vma->flags); @@ -1723,7 +1739,7 @@ void __i915_vma_evict(struct i915_vma *vma) if (likely(atomic_read(&vma->vm->open))) { trace_i915_vma_unbind(vma); - vma->ops->unbind_vma(vma->vm, vma); + vma->ops->unbind_vma(vma->vm, vma->resource); } atomic_and(~(I915_VMA_BIND_MASK | I915_VMA_ERROR | I915_VMA_GGTT_WRITE), &vma->flags); diff --git a/drivers/gpu/drm/i915/i915_vma.h b/drivers/gpu/drm/i915/i915_vma.h index de0f3e44cdfa..1df57ec832bd 100644 --- a/drivers/gpu/drm/i915/i915_vma.h +++ b/drivers/gpu/drm/i915/i915_vma.h @@ -339,12 +339,6 @@ void __iomem *i915_vma_pin_iomap(struct i915_vma *vma); */ void i915_vma_unpin_iomap(struct i915_vma *vma); -static inline struct page *i915_vma_first_page(struct i915_vma *vma) -{ - GEM_BUG_ON(!vma->pages); - return sg_page(vma->pages->sgl); -} - /** * i915_vma_pin_fence - pin fencing state * @vma: vma to pin fencing for @@ -445,6 +439,11 @@ i915_vma_get_current_resource(struct i915_vma *vma) return i915_vma_resource_get(vma->resource); } +#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) +void i915_vma_resource_init_from_vma(struct i915_vma_resource *vma_res, + struct i915_vma *vma); +#endif + void i915_vma_module_exit(void); int i915_vma_module_init(void); diff --git a/drivers/gpu/drm/i915/i915_vma_resource.c b/drivers/gpu/drm/i915/i915_vma_resource.c index 0b4eaafe47d7..b50e67035d15 100644 --- a/drivers/gpu/drm/i915/i915_vma_resource.c +++ b/drivers/gpu/drm/i915/i915_vma_resource.c @@ -23,15 +23,12 @@ static struct dma_fence_ops unbind_fence_ops = { }; /** - * i915_vma_resource_init - Initialize a vma resource. + * __i915_vma_resource_init - Initialize a vma resource. * @vma_res: The vma resource to initialize * - * Initializes a vma resource allocated using i915_vma_resource_alloc(). - * The reason for having separate allocate and initialize function is that - * initialization may need to be performed from under a lock where - * allocation is not allowed. + * Initializes the private members of a vma resource. */ -void i915_vma_resource_init(struct i915_vma_resource *vma_res) +void __i915_vma_resource_init(struct i915_vma_resource *vma_res) { spin_lock_init(&vma_res->lock); dma_fence_init(&vma_res->unbind_fence, &unbind_fence_ops, diff --git a/drivers/gpu/drm/i915/i915_vma_resource.h b/drivers/gpu/drm/i915/i915_vma_resource.h index 3353d32ceda8..8071b40f4f9c 100644 --- a/drivers/gpu/drm/i915/i915_vma_resource.h +++ b/drivers/gpu/drm/i915/i915_vma_resource.h @@ -9,6 +9,25 @@ #include #include +#include "i915_gem.h" + +struct i915_page_sizes { + /** + * The sg mask of the pages sg_table. i.e the mask of + * the lengths for each sg entry. + */ + unsigned int phys; + + /** + * The gtt page sizes we are allowed to use given the + * sg mask and the supported page sizes. This will + * express the smallest unit we can use for the whole + * object, as well as the larger sizes we may be able + * to use opportunistically. + */ + unsigned int sg; +}; + /** * struct i915_vma_resource - Snapshotted unbind information. * @unbind_fence: Fence to mark unbinding complete. Note that this fence @@ -19,6 +38,13 @@ * @hold_count: Number of holders blocking the fence from finishing. * The vma itself is keeping a hold, which is released when unbind * is scheduled. + * @private: Bind backend private info. + * @start: Offset into the address space of bind range start. + * @node_size: Size of the allocated range manager node. + * @vma_size: Bind size. + * @page_sizes_gtt: Resulting page sizes from the bind operation. + * @bound_flags: Flags indicating binding status. + * @allocated: Backend private data. TODO: Should move into @private. * * The lifetime of a struct i915_vma_resource is from a binding request to * the actual possible asynchronous unbind has completed. @@ -28,6 +54,32 @@ struct i915_vma_resource { /* See above for description of the lock. */ spinlock_t lock; refcount_t hold_count; + + /** + * struct i915_vma_bindinfo - Information needed for async bind + * only but that can be dropped after the bind has taken place. + * Consider making this a separate argument to the bind_vma + * op, coalescing with other arguments like vm, stash, cache_level + * and flags + * @pages: The pages sg-table. + * @page_sizes: Page sizes of the pages. + * @readonly: Whether the vma should be bound read-only. + * @lmem: Whether the vma points to lmem. + */ + struct i915_vma_bindinfo { + struct sg_table *pages; + struct i915_page_sizes page_sizes; + bool readonly:1; + bool lmem:1; + } bi; + + void *private; + u64 start; + u64 node_size; + u64 vma_size; + u32 page_sizes_gtt; + u32 bound_flags; + bool allocated:1; }; bool i915_vma_resource_hold(struct i915_vma_resource *vma_res, @@ -40,6 +92,8 @@ struct i915_vma_resource *i915_vma_resource_alloc(void); struct dma_fence *i915_vma_resource_unbind(struct i915_vma_resource *vma_res); +void __i915_vma_resource_init(struct i915_vma_resource *vma_res); + /** * i915_vma_resource_get - Take a reference on a vma resource * @vma_res: The vma resource on which to take a reference. @@ -62,8 +116,47 @@ static inline void i915_vma_resource_put(struct i915_vma_resource *vma_res) dma_fence_put(&vma_res->unbind_fence); } -#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) -void i915_vma_resource_init(struct i915_vma_resource *vma_res); -#endif +/** + * i915_vma_resource_init - Initialize a vma resource. + * @vma_res: The vma resource to initialize + * @pages: The pages sg-table. + * @page_sizes: Page sizes of the pages. + * @readonly: Whether the vma should be bound read-only. + * @lmem: Whether the vma points to lmem. + * @private: Bind backend private info. + * @start: Offset into the address space of bind range start. + * @node_size: Size of the allocated range manager node. + * @size: Bind size. + * + * Initializes a vma resource allocated using i915_vma_resource_alloc(). + * The reason for having separate allocate and initialize function is that + * initialization may need to be performed from under a lock where + * allocation is not allowed. + */ +static inline void i915_vma_resource_init(struct i915_vma_resource *vma_res, + struct sg_table *pages, + const struct i915_page_sizes *page_sizes, + bool readonly, + bool lmem, + void *private, + u64 start, + u64 node_size, + u64 size) +{ + __i915_vma_resource_init(vma_res); + vma_res->bi.pages = pages; + vma_res->bi.page_sizes = *page_sizes; + vma_res->bi.readonly = readonly; + vma_res->bi.lmem = lmem; + vma_res->private = private; + vma_res->start = start; + vma_res->node_size = node_size; + vma_res->vma_size = size; +} + +static inline void i915_vma_resource_fini(struct i915_vma_resource *vma_res) +{ + GEM_BUG_ON(refcount_read(&vma_res->hold_count) != 1); +} #endif diff --git a/drivers/gpu/drm/i915/i915_vma_snapshot.c b/drivers/gpu/drm/i915/i915_vma_snapshot.c index f7333c7a2f5e..69f62c1ca967 100644 --- a/drivers/gpu/drm/i915/i915_vma_snapshot.c +++ b/drivers/gpu/drm/i915/i915_vma_snapshot.c @@ -24,11 +24,7 @@ void i915_vma_snapshot_init(struct i915_vma_snapshot *vsnap, assert_object_held(vma->obj); vsnap->name = name; - vsnap->size = vma->size; vsnap->obj_size = vma->obj->base.size; - vsnap->gtt_offset = vma->node.start; - vsnap->gtt_size = vma->node.size; - vsnap->page_sizes = vma->page_sizes.gtt; vsnap->pages = vma->pages; vsnap->pages_rsgt = NULL; vsnap->mr = NULL; diff --git a/drivers/gpu/drm/i915/i915_vma_snapshot.h b/drivers/gpu/drm/i915/i915_vma_snapshot.h index e74588dd676b..1b08ce9f8576 100644 --- a/drivers/gpu/drm/i915/i915_vma_snapshot.h +++ b/drivers/gpu/drm/i915/i915_vma_snapshot.h @@ -23,31 +23,23 @@ struct sg_table; /** * struct i915_vma_snapshot - Snapshot of vma metadata. - * @size: The vma size in bytes. * @obj_size: The size of the underlying object in bytes. - * @gtt_offset: The gtt offset the vma is bound to. - * @gtt_size: The size in bytes allocated for the vma in the GTT. * @pages: The struct sg_table pointing to the pages bound. * @pages_rsgt: The refcounted sg_table holding the reference for @pages if any. * @mr: The memory region pointed for the pages bound. * @kref: Reference for this structure. * @vma_resource: Pointer to the vma resource representing the vma binding. - * @page_sizes: The vma GTT page sizes information. * @onstack: Whether the structure shouldn't be freed on final put. * @present: Whether the structure is present and initialized. */ struct i915_vma_snapshot { const char *name; - size_t size; size_t obj_size; - size_t gtt_offset; - size_t gtt_size; struct sg_table *pages; struct i915_refct_sgt *pages_rsgt; struct intel_memory_region *mr; struct kref kref; struct i915_vma_resource *vma_resource; - u32 page_sizes; bool onstack:1; bool present:1; }; diff --git a/drivers/gpu/drm/i915/i915_vma_types.h b/drivers/gpu/drm/i915/i915_vma_types.h index 370601282a16..5b7da0ee5d64 100644 --- a/drivers/gpu/drm/i915/i915_vma_types.h +++ b/drivers/gpu/drm/i915/i915_vma_types.h @@ -240,22 +240,20 @@ struct i915_vma { #define I915_VMA_BIND_MASK (I915_VMA_GLOBAL_BIND | I915_VMA_LOCAL_BIND) -#define I915_VMA_ALLOC_BIT 12 - -#define I915_VMA_ERROR_BIT 13 +#define I915_VMA_ERROR_BIT 12 #define I915_VMA_ERROR ((int)BIT(I915_VMA_ERROR_BIT)) -#define I915_VMA_GGTT_BIT 14 -#define I915_VMA_CAN_FENCE_BIT 15 -#define I915_VMA_USERFAULT_BIT 16 -#define I915_VMA_GGTT_WRITE_BIT 17 +#define I915_VMA_GGTT_BIT 13 +#define I915_VMA_CAN_FENCE_BIT 14 +#define I915_VMA_USERFAULT_BIT 15 +#define I915_VMA_GGTT_WRITE_BIT 16 #define I915_VMA_GGTT ((int)BIT(I915_VMA_GGTT_BIT)) #define I915_VMA_CAN_FENCE ((int)BIT(I915_VMA_CAN_FENCE_BIT)) #define I915_VMA_USERFAULT ((int)BIT(I915_VMA_USERFAULT_BIT)) #define I915_VMA_GGTT_WRITE ((int)BIT(I915_VMA_GGTT_WRITE_BIT)) -#define I915_VMA_SCANOUT_BIT 18 +#define I915_VMA_SCANOUT_BIT 17 #define I915_VMA_SCANOUT ((int)BIT(I915_VMA_SCANOUT_BIT)) struct i915_active active; diff --git a/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c b/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c index 19d4defad95c..076d860ce01a 100644 --- a/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c +++ b/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c @@ -239,11 +239,11 @@ static int lowlevel_hole(struct i915_address_space *vm, unsigned long end_time) { I915_RND_STATE(seed_prng); - struct i915_vma *mock_vma; + struct i915_vma_resource *mock_vma_res; unsigned int size; - mock_vma = kzalloc(sizeof(*mock_vma), GFP_KERNEL); - if (!mock_vma) + mock_vma_res = kzalloc(sizeof(*mock_vma_res), GFP_KERNEL); + if (!mock_vma_res) return -ENOMEM; /* Keep creating larger objects until one cannot fit into the hole */ @@ -269,7 +269,7 @@ static int lowlevel_hole(struct i915_address_space *vm, break; } while (count >>= 1); if (!count) { - kfree(mock_vma); + kfree(mock_vma_res); return -ENOMEM; } GEM_BUG_ON(!order); @@ -343,12 +343,12 @@ alloc_vm_end: break; } - mock_vma->pages = obj->mm.pages; - mock_vma->node.size = BIT_ULL(size); - mock_vma->node.start = addr; + mock_vma_res->bi.pages = obj->mm.pages; + mock_vma_res->node_size = BIT_ULL(size); + mock_vma_res->start = addr; with_intel_runtime_pm(vm->gt->uncore->rpm, wakeref) - vm->insert_entries(vm, mock_vma, + vm->insert_entries(vm, mock_vma_res, I915_CACHE_NONE, 0); } count = n; @@ -371,7 +371,7 @@ alloc_vm_end: cleanup_freed_objects(vm->i915); } - kfree(mock_vma); + kfree(mock_vma_res); return 0; } @@ -1280,6 +1280,7 @@ static void track_vma_bind(struct i915_vma *vma) atomic_set(&vma->pages_count, I915_VMA_PAGES_ACTIVE); __i915_gem_object_pin_pages(obj); vma->pages = obj->mm.pages; + vma->resource->bi.pages = vma->pages; mutex_lock(&vma->vm->mutex); list_add_tail(&vma->vm_link, &vma->vm->bound_list); @@ -1354,7 +1355,7 @@ static int reserve_gtt_with_resource(struct i915_vma *vma, u64 offset) obj->cache_level, 0); if (!err) { - i915_vma_resource_init(vma_res); + i915_vma_resource_init_from_vma(vma_res, vma); vma->resource = vma_res; } else { kfree(vma_res); @@ -1533,7 +1534,7 @@ static int insert_gtt_with_resource(struct i915_vma *vma) err = i915_gem_gtt_insert(vm, &vma->node, obj->base.size, 0, obj->cache_level, 0, vm->total, 0); if (!err) { - i915_vma_resource_init(vma_res); + i915_vma_resource_init_from_vma(vma_res, vma); vma->resource = vma_res; } else { kfree(vma_res); @@ -1960,6 +1961,7 @@ static int igt_cs_tlb(void *arg) struct i915_vm_pt_stash stash = {}; struct i915_request *rq; struct i915_gem_ww_ctx ww; + struct i915_vma_resource *vma_res; u64 offset; offset = igt_random_offset(&prng, @@ -1980,6 +1982,13 @@ static int igt_cs_tlb(void *arg) if (err) goto end; + vma_res = i915_vma_resource_alloc(); + if (IS_ERR(vma_res)) { + i915_vma_put_pages(vma); + err = PTR_ERR(vma_res); + goto end; + } + i915_gem_ww_ctx_init(&ww, false); retry: err = i915_vm_lock_objects(vm, &ww); @@ -2001,33 +2010,41 @@ end_ww: goto retry; } i915_gem_ww_ctx_fini(&ww); - if (err) + if (err) { + kfree(vma_res); goto end; + } + i915_vma_resource_init_from_vma(vma_res, vma); /* Prime the TLB with the dummy pages */ for (i = 0; i < count; i++) { - vma->node.start = offset + i * PAGE_SIZE; - vm->insert_entries(vm, vma, I915_CACHE_NONE, 0); + vma_res->start = offset + i * PAGE_SIZE; + vm->insert_entries(vm, vma_res, I915_CACHE_NONE, + 0); - rq = submit_batch(ce, vma->node.start); + rq = submit_batch(ce, vma_res->start); if (IS_ERR(rq)) { err = PTR_ERR(rq); + i915_vma_resource_fini(vma_res); + kfree(vma_res); goto end; } i915_request_put(rq); } - + i915_vma_resource_fini(vma_res); i915_vma_put_pages(vma); err = context_sync(ce); if (err) { pr_err("%s: dummy setup timed out\n", ce->engine->name); + kfree(vma_res); goto end; } vma = i915_vma_instance(act, vm, NULL); if (IS_ERR(vma)) { + kfree(vma_res); err = PTR_ERR(vma); goto end; } @@ -2035,19 +2052,22 @@ end_ww: i915_gem_object_lock(act, NULL); err = i915_vma_get_pages(vma); i915_gem_object_unlock(act); - if (err) + if (err) { + kfree(vma_res); goto end; + } + i915_vma_resource_init_from_vma(vma_res, vma); /* Replace the TLB with target batches */ for (i = 0; i < count; i++) { struct i915_request *rq; u32 *cs = batch + i * 64 / sizeof(*cs); u64 addr; - vma->node.start = offset + i * PAGE_SIZE; - vm->insert_entries(vm, vma, I915_CACHE_NONE, 0); + vma_res->start = offset + i * PAGE_SIZE; + vm->insert_entries(vm, vma_res, I915_CACHE_NONE, 0); - addr = vma->node.start + i * 64; + addr = vma_res->start + i * 64; cs[4] = MI_NOOP; cs[6] = lower_32_bits(addr); cs[7] = upper_32_bits(addr); @@ -2056,6 +2076,8 @@ end_ww: rq = submit_batch(ce, addr); if (IS_ERR(rq)) { err = PTR_ERR(rq); + i915_vma_resource_fini(vma_res); + kfree(vma_res); goto end; } @@ -2072,6 +2094,8 @@ end_ww: } end_spin(batch, count - 1); + i915_vma_resource_fini(vma_res); + kfree(vma_res); i915_vma_put_pages(vma); err = context_sync(ce); diff --git a/drivers/gpu/drm/i915/selftests/mock_gtt.c b/drivers/gpu/drm/i915/selftests/mock_gtt.c index 7863d0f4bbd7..568840e7ca66 100644 --- a/drivers/gpu/drm/i915/selftests/mock_gtt.c +++ b/drivers/gpu/drm/i915/selftests/mock_gtt.c @@ -33,23 +33,23 @@ static void mock_insert_page(struct i915_address_space *vm, } static void mock_insert_entries(struct i915_address_space *vm, - struct i915_vma *vma, + struct i915_vma_resource *vma_res, enum i915_cache_level level, u32 flags) { } static void mock_bind_ppgtt(struct i915_address_space *vm, struct i915_vm_pt_stash *stash, - struct i915_vma *vma, + struct i915_vma_resource *vma_res, enum i915_cache_level cache_level, u32 flags) { GEM_BUG_ON(flags & I915_VMA_GLOBAL_BIND); - set_bit(I915_VMA_LOCAL_BIND_BIT, __i915_vma_flags(vma)); + vma_res->bound_flags |= flags; } static void mock_unbind_ppgtt(struct i915_address_space *vm, - struct i915_vma *vma) + struct i915_vma_resource *vma_res) { } @@ -93,14 +93,14 @@ struct i915_ppgtt *mock_ppgtt(struct drm_i915_private *i915, const char *name) static void mock_bind_ggtt(struct i915_address_space *vm, struct i915_vm_pt_stash *stash, - struct i915_vma *vma, + struct i915_vma_resource *vma_res, enum i915_cache_level cache_level, u32 flags) { } static void mock_unbind_ggtt(struct i915_address_space *vm, - struct i915_vma *vma) + struct i915_vma_resource *vma_res) { } -- cgit From ebf3c361f43b1c1fd254be5a6d12bd37b922e6b7 Mon Sep 17 00:00:00 2001 From: Thomas Hellström Date: Mon, 10 Jan 2022 18:22:16 +0100 Subject: drm/i915: Don't pin the object pages during pending vma binds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A pin-count is already held by vma->pages so taking an additional pin during async binds is not necessary. When we introduce async unbinding we have other means of keeping the object pages alive. Signed-off-by: Thomas Hellström Reviewed-by: Matthew Auld Link: https://patchwork.freedesktop.org/patch/msgid/20220110172219.107131-4-thomas.hellstrom@linux.intel.com --- drivers/gpu/drm/i915/i915_vma.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/i915_vma.c b/drivers/gpu/drm/i915/i915_vma.c index 4884264f3015..9f00dfa5e022 100644 --- a/drivers/gpu/drm/i915/i915_vma.c +++ b/drivers/gpu/drm/i915/i915_vma.c @@ -305,10 +305,8 @@ static void __vma_release(struct dma_fence_work *work) { struct i915_vma_work *vw = container_of(work, typeof(*vw), base); - if (vw->pinned) { - __i915_gem_object_unpin_pages(vw->pinned); + if (vw->pinned) i915_gem_object_put(vw->pinned); - } i915_vm_free_pt_stash(vw->vm, &vw->stash); i915_vm_put(vw->vm); @@ -477,7 +475,6 @@ int i915_vma_bind(struct i915_vma *vma, work->base.dma.error = 0; /* enable the queue_work() */ - __i915_gem_object_pin_pages(vma->obj); work->pinned = i915_gem_object_get(vma->obj); } else { if (vma->obj) { -- cgit From 2f6b90da919208621725d1703c0391f63724fa62 Mon Sep 17 00:00:00 2001 From: Thomas Hellström Date: Mon, 10 Jan 2022 18:22:17 +0100 Subject: drm/i915: Use vma resources for async unbinding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement async (non-blocking) unbinding by not syncing the vma before calling unbind on the vma_resource. Add the resulting unbind fence to the object's dma_resv from where it is picked up by the ttm migration code. Ideally these unbind fences should be coalesced with the migration blit fence to avoid stalling the migration blit waiting for unbind, as they can certainly go on in parallel, but since we don't yet have a reasonable data structure to use to coalesce fences and attach the resulting fence to a timeline, we defer that for now. Note that with async unbinding, even while the unbind waits for the preceding bind to complete before unbinding, the vma itself might have been destroyed in the process, clearing the vma pages. Therefore we can only allow async unbinding if we have a refcounted sg-list and keep a refcount on that for the vma resource pages to stay intact until binding occurs. If this condition is not met, a request for an async unbind is diverted to a sync unbind. v2: - Use a separate kmem_cache for vma resources for now to isolate their memory allocation and aid debugging. - Move the check for vm closed to the actual unbinding thread. Regardless of whether the vm is closed, we need the unbind fence to properly wait for capture. - Clear vma_res::vm on unbind and update its documentation. v4: - Take cache coloring into account when searching for vma resources pending unbind. (Matthew Auld) v5: - Fix timeout and error check in i915_vma_resource_bind_dep_await(). - Avoid taking a reference on the object for async binding if async unbind capable. - Fix braces around a single-line if statement. v6: - Fix up the cache coloring adjustment. (Kernel test robot ) - Don't allow async unbinding if the vma_res pages are not the same as the object pages. (Matthew Auld) v7: - s/unsigned long/u64/ in a number of places (Matthew Auld) Signed-off-by: Thomas Hellström Reviewed-by: Matthew Auld Link: https://patchwork.freedesktop.org/patch/msgid/20220110172219.107131-5-thomas.hellstrom@linux.intel.com --- drivers/gpu/drm/i915/gem/i915_gem_ttm_move.c | 11 +- drivers/gpu/drm/i915/gt/intel_ggtt.c | 2 +- drivers/gpu/drm/i915/gt/intel_gtt.c | 4 + drivers/gpu/drm/i915/gt/intel_gtt.h | 3 + drivers/gpu/drm/i915/i915_drv.h | 1 + drivers/gpu/drm/i915/i915_gem.c | 12 +- drivers/gpu/drm/i915/i915_module.c | 3 + drivers/gpu/drm/i915/i915_vma.c | 205 +++++++++++++--- drivers/gpu/drm/i915/i915_vma.h | 3 +- drivers/gpu/drm/i915/i915_vma_resource.c | 354 ++++++++++++++++++++++++--- drivers/gpu/drm/i915/i915_vma_resource.h | 48 ++++ 11 files changed, 579 insertions(+), 67 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gem/i915_gem_ttm_move.c b/drivers/gpu/drm/i915/gem/i915_gem_ttm_move.c index 8653855d808b..1de306c03aaf 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_ttm_move.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_ttm_move.c @@ -142,7 +142,16 @@ int i915_ttm_move_notify(struct ttm_buffer_object *bo) struct drm_i915_gem_object *obj = i915_ttm_to_gem(bo); int ret; - ret = i915_gem_object_unbind(obj, I915_GEM_OBJECT_UNBIND_ACTIVE); + /* + * Note: The async unbinding here will actually transform the + * blocking wait for unbind into a wait before finally submitting + * evict / migration blit and thus stall the migration timeline + * which may not be good for overall throughput. We should make + * sure we await the unbind fences *after* the migration blit + * instead of *before* as we currently do. + */ + ret = i915_gem_object_unbind(obj, I915_GEM_OBJECT_UNBIND_ACTIVE | + I915_GEM_OBJECT_UNBIND_ASYNC); if (ret) return ret; diff --git a/drivers/gpu/drm/i915/gt/intel_ggtt.c b/drivers/gpu/drm/i915/gt/intel_ggtt.c index bb885d862ec4..91ea2882efda 100644 --- a/drivers/gpu/drm/i915/gt/intel_ggtt.c +++ b/drivers/gpu/drm/i915/gt/intel_ggtt.c @@ -131,7 +131,7 @@ void i915_ggtt_suspend(struct i915_ggtt *ggtt) continue; if (!i915_vma_is_bound(vma, I915_VMA_GLOBAL_BIND)) { - __i915_vma_evict(vma); + __i915_vma_evict(vma, false); drm_mm_remove_node(&vma->node); } } diff --git a/drivers/gpu/drm/i915/gt/intel_gtt.c b/drivers/gpu/drm/i915/gt/intel_gtt.c index c3846ff33118..058c88682970 100644 --- a/drivers/gpu/drm/i915/gt/intel_gtt.c +++ b/drivers/gpu/drm/i915/gt/intel_gtt.c @@ -160,6 +160,9 @@ static void __i915_vm_release(struct work_struct *work) struct i915_address_space *vm = container_of(work, struct i915_address_space, release_work); + /* Synchronize async unbinds. */ + i915_vma_resource_bind_dep_sync_all(vm); + vm->cleanup(vm); i915_address_space_fini(vm); @@ -188,6 +191,7 @@ void i915_address_space_init(struct i915_address_space *vm, int subclass) if (!kref_read(&vm->resv_ref)) kref_init(&vm->resv_ref); + vm->pending_unbind = RB_ROOT_CACHED; INIT_WORK(&vm->release_work, __i915_vm_release); atomic_set(&vm->open, 1); diff --git a/drivers/gpu/drm/i915/gt/intel_gtt.h b/drivers/gpu/drm/i915/gt/intel_gtt.h index 01519e1d1651..ac1c4479e697 100644 --- a/drivers/gpu/drm/i915/gt/intel_gtt.h +++ b/drivers/gpu/drm/i915/gt/intel_gtt.h @@ -265,6 +265,9 @@ struct i915_address_space { /* Flags used when creating page-table objects for this vm */ unsigned long lmem_pt_obj_flags; + /* Interval tree for pending unbind vma resources */ + struct rb_root_cached pending_unbind; + struct drm_i915_gem_object * (*alloc_pt_dma)(struct i915_address_space *vm, int sz); struct drm_i915_gem_object * diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index 63a579e952b9..b38810f84181 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -1878,6 +1878,7 @@ int i915_gem_object_unbind(struct drm_i915_gem_object *obj, #define I915_GEM_OBJECT_UNBIND_BARRIER BIT(1) #define I915_GEM_OBJECT_UNBIND_TEST BIT(2) #define I915_GEM_OBJECT_UNBIND_VM_TRYLOCK BIT(3) +#define I915_GEM_OBJECT_UNBIND_ASYNC BIT(4) void i915_gem_runtime_suspend(struct drm_i915_private *dev_priv); diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c index e3730096abd9..3d6c00f845a3 100644 --- a/drivers/gpu/drm/i915/i915_gem.c +++ b/drivers/gpu/drm/i915/i915_gem.c @@ -156,10 +156,16 @@ try_again: spin_unlock(&obj->vma.lock); if (vma) { + bool vm_trylock = !!(flags & I915_GEM_OBJECT_UNBIND_VM_TRYLOCK); ret = -EBUSY; - if (flags & I915_GEM_OBJECT_UNBIND_ACTIVE || - !i915_vma_is_active(vma)) { - if (flags & I915_GEM_OBJECT_UNBIND_VM_TRYLOCK) { + if (flags & I915_GEM_OBJECT_UNBIND_ASYNC) { + assert_object_held(vma->obj); + ret = i915_vma_unbind_async(vma, vm_trylock); + } + + if (ret == -EBUSY && (flags & I915_GEM_OBJECT_UNBIND_ACTIVE || + !i915_vma_is_active(vma))) { + if (vm_trylock) { if (mutex_trylock(&vma->vm->mutex)) { ret = __i915_vma_unbind(vma); mutex_unlock(&vma->vm->mutex); diff --git a/drivers/gpu/drm/i915/i915_module.c b/drivers/gpu/drm/i915/i915_module.c index ab2295dd4500..8d18714cf75f 100644 --- a/drivers/gpu/drm/i915/i915_module.c +++ b/drivers/gpu/drm/i915/i915_module.c @@ -17,6 +17,7 @@ #include "i915_scheduler.h" #include "i915_selftest.h" #include "i915_vma.h" +#include "i915_vma_resource.h" static int i915_check_nomodeset(void) { @@ -64,6 +65,8 @@ static const struct { .exit = i915_scheduler_module_exit }, { .init = i915_vma_module_init, .exit = i915_vma_module_exit }, + { .init = i915_vma_resource_module_init, + .exit = i915_vma_resource_module_exit }, { .init = i915_mock_selftests }, { .init = i915_pmu_init, .exit = i915_pmu_exit }, diff --git a/drivers/gpu/drm/i915/i915_vma.c b/drivers/gpu/drm/i915/i915_vma.c index 9f00dfa5e022..b86666f653ca 100644 --- a/drivers/gpu/drm/i915/i915_vma.c +++ b/drivers/gpu/drm/i915/i915_vma.c @@ -285,9 +285,10 @@ struct i915_vma_work { struct dma_fence_work base; struct i915_address_space *vm; struct i915_vm_pt_stash stash; - struct i915_vma *vma; + struct i915_vma_resource *vma_res; struct drm_i915_gem_object *pinned; struct i915_sw_dma_fence_cb cb; + struct i915_refct_sgt *rsgt; enum i915_cache_level cache_level; unsigned int flags; }; @@ -295,10 +296,11 @@ struct i915_vma_work { static void __vma_bind(struct dma_fence_work *work) { struct i915_vma_work *vw = container_of(work, typeof(*vw), base); - struct i915_vma *vma = vw->vma; + struct i915_vma_resource *vma_res = vw->vma_res; + + vma_res->ops->bind_vma(vma_res->vm, &vw->stash, + vma_res, vw->cache_level, vw->flags); - vma->ops->bind_vma(vw->vm, &vw->stash, - vma->resource, vw->cache_level, vw->flags); } static void __vma_release(struct dma_fence_work *work) @@ -310,6 +312,10 @@ static void __vma_release(struct dma_fence_work *work) i915_vm_free_pt_stash(vw->vm, &vw->stash); i915_vm_put(vw->vm); + if (vw->vma_res) + i915_vma_resource_put(vw->vma_res); + if (vw->rsgt) + i915_refct_sgt_put(vw->rsgt); } static const struct dma_fence_work_ops bind_ops = { @@ -379,13 +385,11 @@ i915_vma_resource_init_from_vma(struct i915_vma_resource *vma_res, { struct drm_i915_gem_object *obj = vma->obj; - i915_vma_resource_init(vma_res, vma->pages, &vma->page_sizes, + i915_vma_resource_init(vma_res, vma->vm, vma->pages, &vma->page_sizes, i915_gem_object_is_readonly(obj), i915_gem_object_is_lmem(obj), - vma->private, - vma->node.start, - vma->node.size, - vma->size); + vma->ops, vma->private, vma->node.start, + vma->node.size, vma->size); } /** @@ -409,6 +413,7 @@ int i915_vma_bind(struct i915_vma *vma, { u32 bind_flags; u32 vma_flags; + int ret; lockdep_assert_held(&vma->vm->mutex); GEM_BUG_ON(!drm_mm_node_allocated(&vma->node)); @@ -417,12 +422,12 @@ int i915_vma_bind(struct i915_vma *vma, if (GEM_DEBUG_WARN_ON(range_overflows(vma->node.start, vma->node.size, vma->vm->total))) { - kfree(vma_res); + i915_vma_resource_free(vma_res); return -ENODEV; } if (GEM_DEBUG_WARN_ON(!flags)) { - kfree(vma_res); + i915_vma_resource_free(vma_res); return -EINVAL; } @@ -434,12 +439,30 @@ int i915_vma_bind(struct i915_vma *vma, bind_flags &= ~vma_flags; if (bind_flags == 0) { - kfree(vma_res); + i915_vma_resource_free(vma_res); return 0; } GEM_BUG_ON(!atomic_read(&vma->pages_count)); + /* Wait for or await async unbinds touching our range */ + if (work && bind_flags & vma->vm->bind_async_flags) + ret = i915_vma_resource_bind_dep_await(vma->vm, + &work->base.chain, + vma->node.start, + vma->node.size, + true, + GFP_NOWAIT | + __GFP_RETRY_MAYFAIL | + __GFP_NOWARN); + else + ret = i915_vma_resource_bind_dep_sync(vma->vm, vma->node.start, + vma->node.size, true); + if (ret) { + i915_vma_resource_free(vma_res); + return ret; + } + if (vma->resource || !vma_res) { /* Rebinding with an additional I915_VMA_*_BIND */ GEM_WARN_ON(!vma_flags); @@ -452,9 +475,11 @@ int i915_vma_bind(struct i915_vma *vma, if (work && bind_flags & vma->vm->bind_async_flags) { struct dma_fence *prev; - work->vma = vma; + work->vma_res = i915_vma_resource_get(vma->resource); work->cache_level = cache_level; work->flags = bind_flags; + if (vma->obj->mm.rsgt) + work->rsgt = i915_refct_sgt_get(vma->obj->mm.rsgt); /* * Note we only want to chain up to the migration fence on @@ -475,14 +500,24 @@ int i915_vma_bind(struct i915_vma *vma, work->base.dma.error = 0; /* enable the queue_work() */ - work->pinned = i915_gem_object_get(vma->obj); + /* + * If we don't have the refcounted pages list, keep a reference + * on the object to avoid waiting for the async bind to + * complete in the object destruction path. + */ + if (!work->rsgt) + work->pinned = i915_gem_object_get(vma->obj); } else { if (vma->obj) { int ret; ret = i915_gem_object_wait_moving_fence(vma->obj, true); - if (ret) + if (ret) { + i915_vma_resource_free(vma->resource); + vma->resource = NULL; + return ret; + } } vma->ops->bind_vma(vma->vm, NULL, vma->resource, cache_level, bind_flags); @@ -1700,8 +1735,9 @@ int _i915_vma_move_to_active(struct i915_vma *vma, return 0; } -void __i915_vma_evict(struct i915_vma *vma) +struct dma_fence *__i915_vma_evict(struct i915_vma *vma, bool async) { + struct i915_vma_resource *vma_res = vma->resource; struct dma_fence *unbind_fence; GEM_BUG_ON(i915_vma_is_pinned(vma)); @@ -1734,27 +1770,39 @@ void __i915_vma_evict(struct i915_vma *vma) GEM_BUG_ON(vma->fence); GEM_BUG_ON(i915_vma_has_userfault(vma)); - if (likely(atomic_read(&vma->vm->open))) { - trace_i915_vma_unbind(vma); - vma->ops->unbind_vma(vma->vm, vma->resource); - } + /* Object backend must be async capable. */ + GEM_WARN_ON(async && !vma->obj->mm.rsgt); + + /* If vm is not open, unbind is a nop. */ + vma_res->needs_wakeref = i915_vma_is_bound(vma, I915_VMA_GLOBAL_BIND) && + atomic_read(&vma->vm->open); + trace_i915_vma_unbind(vma); + + unbind_fence = i915_vma_resource_unbind(vma_res); + vma->resource = NULL; + atomic_and(~(I915_VMA_BIND_MASK | I915_VMA_ERROR | I915_VMA_GGTT_WRITE), &vma->flags); - unbind_fence = i915_vma_resource_unbind(vma->resource); - i915_vma_resource_put(vma->resource); - vma->resource = NULL; + /* Object backend must be async capable. */ + GEM_WARN_ON(async && !vma->obj->mm.rsgt); i915_vma_detach(vma); - vma_unbind_pages(vma); + + if (!async && unbind_fence) { + dma_fence_wait(unbind_fence, false); + dma_fence_put(unbind_fence); + unbind_fence = NULL; + } /* - * This uninterruptible wait under the vm mutex is currently - * only ever blocking while the vma is being captured from. - * With async unbinding, this wait here will be removed. + * Binding itself may not have completed until the unbind fence signals, + * so don't drop the pages until that happens, unless the resource is + * async_capable. */ - dma_fence_wait(unbind_fence, false); - dma_fence_put(unbind_fence); + + vma_unbind_pages(vma); + return unbind_fence; } int __i915_vma_unbind(struct i915_vma *vma) @@ -1781,12 +1829,47 @@ int __i915_vma_unbind(struct i915_vma *vma) return ret; GEM_BUG_ON(i915_vma_is_active(vma)); - __i915_vma_evict(vma); + __i915_vma_evict(vma, false); drm_mm_remove_node(&vma->node); /* pairs with i915_vma_release() */ return 0; } +static struct dma_fence *__i915_vma_unbind_async(struct i915_vma *vma) +{ + struct dma_fence *fence; + + lockdep_assert_held(&vma->vm->mutex); + + if (!drm_mm_node_allocated(&vma->node)) + return NULL; + + if (i915_vma_is_pinned(vma) || + &vma->obj->mm.rsgt->table != vma->resource->bi.pages) + return ERR_PTR(-EAGAIN); + + /* + * We probably need to replace this with awaiting the fences of the + * object's dma_resv when the vma active goes away. When doing that + * we need to be careful to not add the vma_resource unbind fence + * immediately to the object's dma_resv, because then unbinding + * the next vma from the object, in case there are many, will + * actually await the unbinding of the previous vmas, which is + * undesirable. + */ + if (i915_sw_fence_await_active(&vma->resource->chain, &vma->active, + I915_ACTIVE_AWAIT_EXCL | + I915_ACTIVE_AWAIT_ACTIVE) < 0) { + return ERR_PTR(-EBUSY); + } + + fence = __i915_vma_evict(vma, true); + + drm_mm_remove_node(&vma->node); /* pairs with i915_vma_release() */ + + return fence; +} + int i915_vma_unbind(struct i915_vma *vma) { struct i915_address_space *vm = vma->vm; @@ -1823,6 +1906,68 @@ out_rpm: return err; } +int i915_vma_unbind_async(struct i915_vma *vma, bool trylock_vm) +{ + struct drm_i915_gem_object *obj = vma->obj; + struct i915_address_space *vm = vma->vm; + intel_wakeref_t wakeref = 0; + struct dma_fence *fence; + int err; + + /* + * We need the dma-resv lock since we add the + * unbind fence to the dma-resv object. + */ + assert_object_held(obj); + + if (!drm_mm_node_allocated(&vma->node)) + return 0; + + if (i915_vma_is_pinned(vma)) { + vma_print_allocator(vma, "is pinned"); + return -EAGAIN; + } + + if (!obj->mm.rsgt) + return -EBUSY; + + err = dma_resv_reserve_shared(obj->base.resv, 1); + if (err) + return -EBUSY; + + /* + * It would be great if we could grab this wakeref from the + * async unbind work if needed, but we can't because it uses + * kmalloc and it's in the dma-fence signalling critical path. + */ + if (i915_vma_is_bound(vma, I915_VMA_GLOBAL_BIND)) + wakeref = intel_runtime_pm_get(&vm->i915->runtime_pm); + + if (trylock_vm && !mutex_trylock(&vm->mutex)) { + err = -EBUSY; + goto out_rpm; + } else if (!trylock_vm) { + err = mutex_lock_interruptible_nested(&vm->mutex, !wakeref); + if (err) + goto out_rpm; + } + + fence = __i915_vma_unbind_async(vma); + mutex_unlock(&vm->mutex); + if (IS_ERR_OR_NULL(fence)) { + err = PTR_ERR_OR_ZERO(fence); + goto out_rpm; + } + + dma_resv_add_shared_fence(obj->base.resv, fence); + dma_fence_put(fence); + +out_rpm: + if (wakeref) + intel_runtime_pm_put(&vm->i915->runtime_pm, wakeref); + return err; +} + struct i915_vma *i915_vma_make_unshrinkable(struct i915_vma *vma) { i915_gem_object_make_unshrinkable(vma->obj); diff --git a/drivers/gpu/drm/i915/i915_vma.h b/drivers/gpu/drm/i915/i915_vma.h index 1df57ec832bd..a560bae04e7e 100644 --- a/drivers/gpu/drm/i915/i915_vma.h +++ b/drivers/gpu/drm/i915/i915_vma.h @@ -213,9 +213,10 @@ bool i915_vma_misplaced(const struct i915_vma *vma, u64 size, u64 alignment, u64 flags); void __i915_vma_set_map_and_fenceable(struct i915_vma *vma); void i915_vma_revoke_mmap(struct i915_vma *vma); -void __i915_vma_evict(struct i915_vma *vma); +struct dma_fence *__i915_vma_evict(struct i915_vma *vma, bool async); int __i915_vma_unbind(struct i915_vma *vma); int __must_check i915_vma_unbind(struct i915_vma *vma); +int __must_check i915_vma_unbind_async(struct i915_vma *vma, bool trylock_vm); void i915_vma_unlink_ctx(struct i915_vma *vma); void i915_vma_close(struct i915_vma *vma); void i915_vma_reopen(struct i915_vma *vma); diff --git a/drivers/gpu/drm/i915/i915_vma_resource.c b/drivers/gpu/drm/i915/i915_vma_resource.c index b50e67035d15..3e55a30b2da7 100644 --- a/drivers/gpu/drm/i915/i915_vma_resource.c +++ b/drivers/gpu/drm/i915/i915_vma_resource.c @@ -2,39 +2,44 @@ /* * Copyright © 2021 Intel Corporation */ + +#include #include +#include "i915_sw_fence.h" #include "i915_vma_resource.h" +#include "i915_drv.h" -/* Callbacks for the unbind dma-fence. */ -static const char *get_driver_name(struct dma_fence *fence) -{ - return "vma unbind fence"; -} +#include "gt/intel_gtt.h" -static const char *get_timeline_name(struct dma_fence *fence) -{ - return "unbound"; -} - -static struct dma_fence_ops unbind_fence_ops = { - .get_driver_name = get_driver_name, - .get_timeline_name = get_timeline_name, -}; +static struct kmem_cache *slab_vma_resources; /** - * __i915_vma_resource_init - Initialize a vma resource. - * @vma_res: The vma resource to initialize + * DOC: + * We use a per-vm interval tree to keep track of vma_resources + * scheduled for unbind but not yet unbound. The tree is protected by + * the vm mutex, and nodes are removed just after the unbind fence signals. + * The removal takes the vm mutex from a kernel thread which we need to + * keep in mind so that we don't grab the mutex and try to wait for all + * pending unbinds to complete, because that will temporaryily block many + * of the workqueue threads, and people will get angry. * - * Initializes the private members of a vma resource. + * We should consider using a single ordered fence per VM instead but that + * requires ordering the unbinds and might introduce unnecessary waiting + * for unrelated unbinds. Amount of code will probably be roughly the same + * due to the simplicity of using the interval tree interface. + * + * Another drawback of this interval tree is that the complexity of insertion + * and removal of fences increases as O(ln(pending_unbinds)) instead of + * O(1) for a single fence without interval tree. */ -void __i915_vma_resource_init(struct i915_vma_resource *vma_res) -{ - spin_lock_init(&vma_res->lock); - dma_fence_init(&vma_res->unbind_fence, &unbind_fence_ops, - &vma_res->lock, 0, 0); - refcount_set(&vma_res->hold_count, 1); -} +#define VMA_RES_START(_node) ((_node)->start) +#define VMA_RES_LAST(_node) ((_node)->start + (_node)->node_size - 1) +INTERVAL_TREE_DEFINE(struct i915_vma_resource, rb, + u64, __subtree_last, + VMA_RES_START, VMA_RES_LAST, static, vma_res_itree); + +/* Callbacks for the unbind dma-fence. */ /** * i915_vma_resource_alloc - Allocate a vma resource @@ -45,15 +50,73 @@ void __i915_vma_resource_init(struct i915_vma_resource *vma_res) struct i915_vma_resource *i915_vma_resource_alloc(void) { struct i915_vma_resource *vma_res = - kzalloc(sizeof(*vma_res), GFP_KERNEL); + kmem_cache_zalloc(slab_vma_resources, GFP_KERNEL); return vma_res ? vma_res : ERR_PTR(-ENOMEM); } +/** + * i915_vma_resource_free - Free a vma resource + * @vma_res: The vma resource to free. + */ +void i915_vma_resource_free(struct i915_vma_resource *vma_res) +{ + kmem_cache_free(slab_vma_resources, vma_res); +} + +static const char *get_driver_name(struct dma_fence *fence) +{ + return "vma unbind fence"; +} + +static const char *get_timeline_name(struct dma_fence *fence) +{ + return "unbound"; +} + +static void unbind_fence_free_rcu(struct rcu_head *head) +{ + struct i915_vma_resource *vma_res = + container_of(head, typeof(*vma_res), unbind_fence.rcu); + + i915_vma_resource_free(vma_res); +} + +static void unbind_fence_release(struct dma_fence *fence) +{ + struct i915_vma_resource *vma_res = + container_of(fence, typeof(*vma_res), unbind_fence); + + i915_sw_fence_fini(&vma_res->chain); + + call_rcu(&fence->rcu, unbind_fence_free_rcu); +} + +static struct dma_fence_ops unbind_fence_ops = { + .get_driver_name = get_driver_name, + .get_timeline_name = get_timeline_name, + .release = unbind_fence_release, +}; + static void __i915_vma_resource_unhold(struct i915_vma_resource *vma_res) { - if (refcount_dec_and_test(&vma_res->hold_count)) - dma_fence_signal(&vma_res->unbind_fence); + struct i915_address_space *vm; + + if (!refcount_dec_and_test(&vma_res->hold_count)) + return; + + dma_fence_signal(&vma_res->unbind_fence); + + vm = vma_res->vm; + if (vma_res->wakeref) + intel_runtime_pm_put(&vm->i915->runtime_pm, vma_res->wakeref); + + vma_res->vm = NULL; + if (!RB_EMPTY_NODE(&vma_res->rb)) { + mutex_lock(&vm->mutex); + vma_res_itree_remove(vma_res, &vm->pending_unbind); + mutex_unlock(&vm->mutex); + } } /** @@ -102,6 +165,49 @@ bool i915_vma_resource_hold(struct i915_vma_resource *vma_res, return held; } +static void i915_vma_resource_unbind_work(struct work_struct *work) +{ + struct i915_vma_resource *vma_res = + container_of(work, typeof(*vma_res), work); + struct i915_address_space *vm = vma_res->vm; + bool lockdep_cookie; + + lockdep_cookie = dma_fence_begin_signalling(); + if (likely(atomic_read(&vm->open))) + vma_res->ops->unbind_vma(vm, vma_res); + + dma_fence_end_signalling(lockdep_cookie); + __i915_vma_resource_unhold(vma_res); + i915_vma_resource_put(vma_res); +} + +static int +i915_vma_resource_fence_notify(struct i915_sw_fence *fence, + enum i915_sw_fence_notify state) +{ + struct i915_vma_resource *vma_res = + container_of(fence, typeof(*vma_res), chain); + struct dma_fence *unbind_fence = + &vma_res->unbind_fence; + + switch (state) { + case FENCE_COMPLETE: + dma_fence_get(unbind_fence); + if (vma_res->immediate_unbind) { + i915_vma_resource_unbind_work(&vma_res->work); + } else { + INIT_WORK(&vma_res->work, i915_vma_resource_unbind_work); + queue_work(system_unbound_wq, &vma_res->work); + } + break; + case FENCE_FREE: + i915_vma_resource_put(vma_res); + break; + } + + return NOTIFY_DONE; +} + /** * i915_vma_resource_unbind - Unbind a vma resource * @vma_res: The vma resource to unbind. @@ -112,10 +218,196 @@ bool i915_vma_resource_hold(struct i915_vma_resource *vma_res, * Return: A refcounted pointer to a dma-fence that signals when unbinding is * complete. */ -struct dma_fence * -i915_vma_resource_unbind(struct i915_vma_resource *vma_res) +struct dma_fence *i915_vma_resource_unbind(struct i915_vma_resource *vma_res) { - __i915_vma_resource_unhold(vma_res); - dma_fence_get(&vma_res->unbind_fence); + struct i915_address_space *vm = vma_res->vm; + + /* Reference for the sw fence */ + i915_vma_resource_get(vma_res); + + /* Caller must already have a wakeref in this case. */ + if (vma_res->needs_wakeref) + vma_res->wakeref = intel_runtime_pm_get_if_in_use(&vm->i915->runtime_pm); + + if (atomic_read(&vma_res->chain.pending) <= 1) { + RB_CLEAR_NODE(&vma_res->rb); + vma_res->immediate_unbind = 1; + } else { + vma_res_itree_insert(vma_res, &vma_res->vm->pending_unbind); + } + + i915_sw_fence_commit(&vma_res->chain); + return &vma_res->unbind_fence; } + +/** + * __i915_vma_resource_init - Initialize a vma resource. + * @vma_res: The vma resource to initialize + * + * Initializes the private members of a vma resource. + */ +void __i915_vma_resource_init(struct i915_vma_resource *vma_res) +{ + spin_lock_init(&vma_res->lock); + dma_fence_init(&vma_res->unbind_fence, &unbind_fence_ops, + &vma_res->lock, 0, 0); + refcount_set(&vma_res->hold_count, 1); + i915_sw_fence_init(&vma_res->chain, i915_vma_resource_fence_notify); +} + +static void +i915_vma_resource_color_adjust_range(struct i915_address_space *vm, + u64 *start, + u64 *end) +{ + if (i915_vm_has_cache_coloring(vm)) { + if (*start) + *start -= I915_GTT_PAGE_SIZE; + *end += I915_GTT_PAGE_SIZE; + } +} + +/** + * i915_vma_resource_bind_dep_sync - Wait for / sync all unbinds touching a + * certain vm range. + * @vm: The vm to look at. + * @offset: The range start. + * @size: The range size. + * @intr: Whether to wait interrubtible. + * + * The function needs to be called with the vm lock held. + * + * Return: Zero on success, -ERESTARTSYS if interrupted and @intr==true + */ +int i915_vma_resource_bind_dep_sync(struct i915_address_space *vm, + u64 offset, + u64 size, + bool intr) +{ + struct i915_vma_resource *node; + u64 last = offset + size - 1; + + lockdep_assert_held(&vm->mutex); + might_sleep(); + + i915_vma_resource_color_adjust_range(vm, &offset, &last); + node = vma_res_itree_iter_first(&vm->pending_unbind, offset, last); + while (node) { + int ret = dma_fence_wait(&node->unbind_fence, intr); + + if (ret) + return ret; + + node = vma_res_itree_iter_next(node, offset, last); + } + + return 0; +} + +/** + * i915_vma_resource_bind_dep_sync_all - Wait for / sync all unbinds of a vm, + * releasing the vm lock while waiting. + * @vm: The vm to look at. + * + * The function may not be called with the vm lock held. + * Typically this is called at vm destruction to finish any pending + * unbind operations. The vm mutex is released while waiting to avoid + * stalling kernel workqueues trying to grab the mutex. + */ +void i915_vma_resource_bind_dep_sync_all(struct i915_address_space *vm) +{ + struct i915_vma_resource *node; + struct dma_fence *fence; + + do { + fence = NULL; + mutex_lock(&vm->mutex); + node = vma_res_itree_iter_first(&vm->pending_unbind, 0, + U64_MAX); + if (node) + fence = dma_fence_get_rcu(&node->unbind_fence); + mutex_unlock(&vm->mutex); + + if (fence) { + /* + * The wait makes sure the node eventually removes + * itself from the tree. + */ + dma_fence_wait(fence, false); + dma_fence_put(fence); + } + } while (node); +} + +/** + * i915_vma_resource_bind_dep_await - Have a struct i915_sw_fence await all + * pending unbinds in a certain range of a vm. + * @vm: The vm to look at. + * @sw_fence: The struct i915_sw_fence that will be awaiting the unbinds. + * @offset: The range start. + * @size: The range size. + * @intr: Whether to wait interrubtible. + * @gfp: Allocation mode for memory allocations. + * + * The function makes @sw_fence await all pending unbinds in a certain + * vm range before calling the complete notifier. To be able to await + * each individual unbind, the function needs to allocate memory using + * the @gpf allocation mode. If that fails, the function will instead + * wait for the unbind fence to signal, using @intr to judge whether to + * wait interruptible or not. Note that @gfp should ideally be selected so + * as to avoid any expensive memory allocation stalls and rather fail and + * synchronize itself. For now the vm mutex is required when calling this + * function with means that @gfp can't call into direct reclaim. In reality + * this means that during heavy memory pressure, we will sync in this + * function. + * + * Return: Zero on success, -ERESTARTSYS if interrupted and @intr==true + */ +int i915_vma_resource_bind_dep_await(struct i915_address_space *vm, + struct i915_sw_fence *sw_fence, + u64 offset, + u64 size, + bool intr, + gfp_t gfp) +{ + struct i915_vma_resource *node; + u64 last = offset + size - 1; + + lockdep_assert_held(&vm->mutex); + might_alloc(gfp); + might_sleep(); + + i915_vma_resource_color_adjust_range(vm, &offset, &last); + node = vma_res_itree_iter_first(&vm->pending_unbind, offset, last); + while (node) { + int ret; + + ret = i915_sw_fence_await_dma_fence(sw_fence, + &node->unbind_fence, + 0, gfp); + if (ret < 0) { + ret = dma_fence_wait(&node->unbind_fence, intr); + if (ret) + return ret; + } + + node = vma_res_itree_iter_next(node, offset, last); + } + + return 0; +} + +void i915_vma_resource_module_exit(void) +{ + kmem_cache_destroy(slab_vma_resources); +} + +int __init i915_vma_resource_module_init(void) +{ + slab_vma_resources = KMEM_CACHE(i915_vma_resource, SLAB_HWCACHE_ALIGN); + if (!slab_vma_resources) + return -ENOMEM; + + return 0; +} diff --git a/drivers/gpu/drm/i915/i915_vma_resource.h b/drivers/gpu/drm/i915/i915_vma_resource.h index 8071b40f4f9c..cb3f4e1e8457 100644 --- a/drivers/gpu/drm/i915/i915_vma_resource.h +++ b/drivers/gpu/drm/i915/i915_vma_resource.h @@ -10,6 +10,8 @@ #include #include "i915_gem.h" +#include "i915_sw_fence.h" +#include "intel_runtime_pm.h" struct i915_page_sizes { /** @@ -38,6 +40,13 @@ struct i915_page_sizes { * @hold_count: Number of holders blocking the fence from finishing. * The vma itself is keeping a hold, which is released when unbind * is scheduled. + * @work: Work struct for deferred unbind work. + * @chain: Pointer to struct i915_sw_fence used to await dependencies. + * @rb: Rb node for the vm's pending unbind interval tree. + * @__subtree_last: Interval tree private member. + * @vm: non-refcounted pointer to the vm. This is for internal use only and + * this member is cleared after vm_resource unbind. + * @ops: Pointer to the backend i915_vma_ops. * @private: Bind backend private info. * @start: Offset into the address space of bind range start. * @node_size: Size of the allocated range manager node. @@ -45,6 +54,8 @@ struct i915_page_sizes { * @page_sizes_gtt: Resulting page sizes from the bind operation. * @bound_flags: Flags indicating binding status. * @allocated: Backend private data. TODO: Should move into @private. + * @immediate_unbind: Unbind can be done immediately and don't need to be + * deferred to a work item awaiting unsignaled fences. * * The lifetime of a struct i915_vma_resource is from a binding request to * the actual possible asynchronous unbind has completed. @@ -54,6 +65,12 @@ struct i915_vma_resource { /* See above for description of the lock. */ spinlock_t lock; refcount_t hold_count; + struct work_struct work; + struct i915_sw_fence chain; + struct rb_node rb; + u64 __subtree_last; + struct i915_address_space *vm; + intel_wakeref_t wakeref; /** * struct i915_vma_bindinfo - Information needed for async bind @@ -73,13 +90,17 @@ struct i915_vma_resource { bool lmem:1; } bi; + const struct i915_vma_ops *ops; void *private; u64 start; u64 node_size; u64 vma_size; u32 page_sizes_gtt; + u32 bound_flags; bool allocated:1; + bool immediate_unbind:1; + bool needs_wakeref:1; }; bool i915_vma_resource_hold(struct i915_vma_resource *vma_res, @@ -90,6 +111,8 @@ void i915_vma_resource_unhold(struct i915_vma_resource *vma_res, struct i915_vma_resource *i915_vma_resource_alloc(void); +void i915_vma_resource_free(struct i915_vma_resource *vma_res); + struct dma_fence *i915_vma_resource_unbind(struct i915_vma_resource *vma_res); void __i915_vma_resource_init(struct i915_vma_resource *vma_res); @@ -119,10 +142,12 @@ static inline void i915_vma_resource_put(struct i915_vma_resource *vma_res) /** * i915_vma_resource_init - Initialize a vma resource. * @vma_res: The vma resource to initialize + * @vm: Pointer to the vm. * @pages: The pages sg-table. * @page_sizes: Page sizes of the pages. * @readonly: Whether the vma should be bound read-only. * @lmem: Whether the vma points to lmem. + * @ops: The backend ops. * @private: Bind backend private info. * @start: Offset into the address space of bind range start. * @node_size: Size of the allocated range manager node. @@ -134,20 +159,24 @@ static inline void i915_vma_resource_put(struct i915_vma_resource *vma_res) * allocation is not allowed. */ static inline void i915_vma_resource_init(struct i915_vma_resource *vma_res, + struct i915_address_space *vm, struct sg_table *pages, const struct i915_page_sizes *page_sizes, bool readonly, bool lmem, + const struct i915_vma_ops *ops, void *private, u64 start, u64 node_size, u64 size) { __i915_vma_resource_init(vma_res); + vma_res->vm = vm; vma_res->bi.pages = pages; vma_res->bi.page_sizes = *page_sizes; vma_res->bi.readonly = readonly; vma_res->bi.lmem = lmem; + vma_res->ops = ops; vma_res->private = private; vma_res->start = start; vma_res->node_size = node_size; @@ -157,6 +186,25 @@ static inline void i915_vma_resource_init(struct i915_vma_resource *vma_res, static inline void i915_vma_resource_fini(struct i915_vma_resource *vma_res) { GEM_BUG_ON(refcount_read(&vma_res->hold_count) != 1); + i915_sw_fence_fini(&vma_res->chain); } +int i915_vma_resource_bind_dep_sync(struct i915_address_space *vm, + u64 first, + u64 last, + bool intr); + +int i915_vma_resource_bind_dep_await(struct i915_address_space *vm, + struct i915_sw_fence *sw_fence, + u64 first, + u64 last, + bool intr, + gfp_t gfp); + +void i915_vma_resource_bind_dep_sync_all(struct i915_address_space *vm); + +void i915_vma_resource_module_exit(void); + +int i915_vma_resource_module_init(void); + #endif -- cgit From 950505cabe517ad40759cae6f88f33f0bdfbb7c8 Mon Sep 17 00:00:00 2001 From: Thomas Hellström Date: Mon, 10 Jan 2022 18:22:18 +0100 Subject: drm/i915: Asynchronous migration selftest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a selftest to exercise asynchronous migration and -unbining. Extend the gem_migrate selftest to perform the migrations while depending on a spinner and a bound vma set up on the migrated buffer object. Signed-off-by: Thomas Hellström Reviewed-by: Matthew Auld Link: https://patchwork.freedesktop.org/patch/msgid/20220110172219.107131-6-thomas.hellstrom@linux.intel.com --- drivers/gpu/drm/i915/gem/i915_gem_object.c | 12 ++ drivers/gpu/drm/i915/gem/i915_gem_object.h | 3 + .../gpu/drm/i915/gem/selftests/i915_gem_migrate.c | 192 +++++++++++++++++++-- 3 files changed, 192 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gem/i915_gem_object.c b/drivers/gpu/drm/i915/gem/i915_gem_object.c index d87b508b59b1..1a9e1f940a7d 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_object.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_object.c @@ -756,6 +756,18 @@ i915_gem_object_get_moving_fence(struct drm_i915_gem_object *obj) return dma_fence_get(i915_gem_to_ttm(obj)->moving); } +void i915_gem_object_set_moving_fence(struct drm_i915_gem_object *obj, + struct dma_fence *fence) +{ + struct dma_fence **moving = &i915_gem_to_ttm(obj)->moving; + + if (*moving == fence) + return; + + dma_fence_put(*moving); + *moving = dma_fence_get(fence); +} + /** * i915_gem_object_wait_moving_fence - Wait for the object's moving fence if any * @obj: The object whose moving fence to wait for. diff --git a/drivers/gpu/drm/i915/gem/i915_gem_object.h b/drivers/gpu/drm/i915/gem/i915_gem_object.h index bc448f895ae8..02c37fe4a535 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_object.h +++ b/drivers/gpu/drm/i915/gem/i915_gem_object.h @@ -523,6 +523,9 @@ i915_gem_object_finish_access(struct drm_i915_gem_object *obj) struct dma_fence * i915_gem_object_get_moving_fence(struct drm_i915_gem_object *obj); +void i915_gem_object_set_moving_fence(struct drm_i915_gem_object *obj, + struct dma_fence *fence); + int i915_gem_object_wait_moving_fence(struct drm_i915_gem_object *obj, bool intr); diff --git a/drivers/gpu/drm/i915/gem/selftests/i915_gem_migrate.c b/drivers/gpu/drm/i915/gem/selftests/i915_gem_migrate.c index ecb691c81d1e..d534141b2cf7 100644 --- a/drivers/gpu/drm/i915/gem/selftests/i915_gem_migrate.c +++ b/drivers/gpu/drm/i915/gem/selftests/i915_gem_migrate.c @@ -4,8 +4,13 @@ */ #include "gt/intel_migrate.h" +#include "gt/intel_gpu_commands.h" #include "gem/i915_gem_ttm_move.h" +#include "i915_deps.h" + +#include "selftests/igt_spinner.h" + static int igt_fill_check_buffer(struct drm_i915_gem_object *obj, bool fill) { @@ -101,7 +106,8 @@ static int igt_same_create_migrate(void *arg) } static int lmem_pages_migrate_one(struct i915_gem_ww_ctx *ww, - struct drm_i915_gem_object *obj) + struct drm_i915_gem_object *obj, + struct i915_vma *vma) { int err; @@ -109,6 +115,24 @@ static int lmem_pages_migrate_one(struct i915_gem_ww_ctx *ww, if (err) return err; + if (vma) { + err = i915_vma_pin_ww(vma, ww, obj->base.size, 0, + 0UL | PIN_OFFSET_FIXED | + PIN_USER); + if (err) { + if (err != -EINTR && err != ERESTARTSYS && + err != -EDEADLK) + pr_err("Failed to pin vma.\n"); + return err; + } + + i915_vma_unpin(vma); + } + + /* + * Migration will implicitly unbind (asynchronously) any bound + * vmas. + */ if (i915_gem_object_is_lmem(obj)) { err = i915_gem_object_migrate(obj, ww, INTEL_REGION_SMEM); if (err) { @@ -149,11 +173,15 @@ static int lmem_pages_migrate_one(struct i915_gem_ww_ctx *ww, return err; } -static int igt_lmem_pages_migrate(void *arg) +static int __igt_lmem_pages_migrate(struct intel_gt *gt, + struct i915_address_space *vm, + struct i915_deps *deps, + struct igt_spinner *spin, + struct dma_fence *spin_fence) { - struct intel_gt *gt = arg; struct drm_i915_private *i915 = gt->i915; struct drm_i915_gem_object *obj; + struct i915_vma *vma = NULL; struct i915_gem_ww_ctx ww; struct i915_request *rq; int err; @@ -165,6 +193,14 @@ static int igt_lmem_pages_migrate(void *arg) if (IS_ERR(obj)) return PTR_ERR(obj); + if (vm) { + vma = i915_vma_instance(obj, vm, NULL); + if (IS_ERR(vma)) { + err = PTR_ERR(vma); + goto out_put; + } + } + /* Initial GPU fill, sync, CPU initialization. */ for_i915_gem_ww(&ww, err, true) { err = i915_gem_object_lock(obj, &ww); @@ -175,25 +211,23 @@ static int igt_lmem_pages_migrate(void *arg) if (err) continue; - err = intel_migrate_clear(>->migrate, &ww, NULL, + err = intel_migrate_clear(>->migrate, &ww, deps, obj->mm.pages->sgl, obj->cache_level, i915_gem_object_is_lmem(obj), 0xdeadbeaf, &rq); if (rq) { dma_resv_add_excl_fence(obj->base.resv, &rq->fence); + i915_gem_object_set_moving_fence(obj, &rq->fence); i915_request_put(rq); } if (err) continue; - err = i915_gem_object_wait(obj, I915_WAIT_INTERRUPTIBLE, - 5 * HZ); - if (err) - continue; - - err = igt_fill_check_buffer(obj, true); - if (err) - continue; + if (!vma) { + err = igt_fill_check_buffer(obj, true); + if (err) + continue; + } } if (err) goto out_put; @@ -204,7 +238,7 @@ static int igt_lmem_pages_migrate(void *arg) */ for (i = 1; i <= 5; ++i) { for_i915_gem_ww(&ww, err, true) - err = lmem_pages_migrate_one(&ww, obj); + err = lmem_pages_migrate_one(&ww, obj, vma); if (err) goto out_put; } @@ -213,12 +247,27 @@ static int igt_lmem_pages_migrate(void *arg) if (err) goto out_put; + if (spin) { + if (dma_fence_is_signaled(spin_fence)) { + pr_err("Spinner was terminated by hangcheck.\n"); + err = -EBUSY; + goto out_unlock; + } + igt_spinner_end(spin); + } + /* Finally sync migration and check content. */ err = i915_gem_object_wait_migration(obj, true); if (err) goto out_unlock; - err = igt_fill_check_buffer(obj, false); + if (vma) { + err = i915_vma_wait_for_bind(vma); + if (err) + goto out_unlock; + } else { + err = igt_fill_check_buffer(obj, false); + } out_unlock: i915_gem_object_unlock(obj); @@ -231,6 +280,7 @@ out_put: static int igt_lmem_pages_failsafe_migrate(void *arg) { int fail_gpu, fail_alloc, ret; + struct intel_gt *gt = arg; for (fail_gpu = 0; fail_gpu < 2; ++fail_gpu) { for (fail_alloc = 0; fail_alloc < 2; ++fail_alloc) { @@ -238,7 +288,118 @@ static int igt_lmem_pages_failsafe_migrate(void *arg) fail_gpu, fail_alloc); i915_ttm_migrate_set_failure_modes(fail_gpu, fail_alloc); - ret = igt_lmem_pages_migrate(arg); + ret = __igt_lmem_pages_migrate(gt, NULL, NULL, NULL, NULL); + if (ret) + goto out_err; + } + } + +out_err: + i915_ttm_migrate_set_failure_modes(false, false); + return ret; +} + +/* + * This subtest tests that unbinding at migration is indeed performed + * async. We launch a spinner and a number of migrations depending on + * that spinner to have terminated. Before each migration we bind a + * vma, which should then be async unbound by the migration operation. + * If we are able to schedule migrations without blocking while the + * spinner is still running, those unbinds are indeed async and non- + * blocking. + * + * Note that each async bind operation is awaiting the previous migration + * due to the moving fence resulting from the migration. + */ +static int igt_async_migrate(struct intel_gt *gt) +{ + struct intel_engine_cs *engine; + enum intel_engine_id id; + struct i915_ppgtt *ppgtt; + struct igt_spinner spin; + int err; + + ppgtt = i915_ppgtt_create(gt, 0); + if (IS_ERR(ppgtt)) + return PTR_ERR(ppgtt); + + if (igt_spinner_init(&spin, gt)) { + err = -ENOMEM; + goto out_spin; + } + + for_each_engine(engine, gt, id) { + struct ttm_operation_ctx ctx = { + .interruptible = true + }; + struct dma_fence *spin_fence; + struct intel_context *ce; + struct i915_request *rq; + struct i915_deps deps; + + ce = intel_context_create(engine); + if (IS_ERR(ce)) { + err = PTR_ERR(ce); + goto out_ce; + } + + /* + * Use MI_NOOP, making the spinner non-preemptible. If there + * is a code path where we fail async operation due to the + * running spinner, we will block and fail to end the + * spinner resulting in a deadlock. But with a non- + * preemptible spinner, hangcheck will terminate the spinner + * for us, and we will later detect that and fail the test. + */ + rq = igt_spinner_create_request(&spin, ce, MI_NOOP); + intel_context_put(ce); + if (IS_ERR(rq)) { + err = PTR_ERR(rq); + goto out_ce; + } + + i915_deps_init(&deps, GFP_KERNEL); + err = i915_deps_add_dependency(&deps, &rq->fence, &ctx); + spin_fence = dma_fence_get(&rq->fence); + i915_request_add(rq); + if (err) + goto out_ce; + + err = __igt_lmem_pages_migrate(gt, &ppgtt->vm, &deps, &spin, + spin_fence); + i915_deps_fini(&deps); + dma_fence_put(spin_fence); + if (err) + goto out_ce; + } + +out_ce: + igt_spinner_fini(&spin); +out_spin: + i915_vm_put(&ppgtt->vm); + + return err; +} + +/* + * Setting ASYNC_FAIL_ALLOC to 2 will simulate memory allocation failure while + * arming the migration error check and block async migration. This + * will cause us to deadlock and hangcheck will terminate the spinner + * causing the test to fail. + */ +#define ASYNC_FAIL_ALLOC 1 +static int igt_lmem_async_migrate(void *arg) +{ + int fail_gpu, fail_alloc, ret; + struct intel_gt *gt = arg; + + for (fail_gpu = 0; fail_gpu < 2; ++fail_gpu) { + for (fail_alloc = 0; fail_alloc < ASYNC_FAIL_ALLOC; ++fail_alloc) { + pr_info("Simulated failure modes: gpu: %d, alloc: %d\n", + fail_gpu, fail_alloc); + i915_ttm_migrate_set_failure_modes(fail_gpu, + fail_alloc); + ret = igt_async_migrate(gt); if (ret) goto out_err; } @@ -256,6 +417,7 @@ int i915_gem_migrate_live_selftests(struct drm_i915_private *i915) SUBTEST(igt_lmem_create_migrate), SUBTEST(igt_same_create_migrate), SUBTEST(igt_lmem_pages_failsafe_migrate), + SUBTEST(igt_lmem_async_migrate), }; if (!HAS_LMEM(i915)) -- cgit From 60dc43d1190db1bf80c696ab4561ed53f8f42f33 Mon Sep 17 00:00:00 2001 From: Thomas Hellström Date: Mon, 10 Jan 2022 18:22:19 +0100 Subject: drm/i915: Use struct vma_resource instead of struct vma_snapshot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is always a struct vma_resource guaranteed to be alive when we access a corresponding struct vma_snapshot. So ditch the latter and instead of allocating vma_snapshots, reference the already existning vma_resource. This requires a couple of extra members in struct vma_resource but that's a small price to pay for the simplification. v2: - Fix a missing include and declaration (kernel test robot ) Signed-off-by: Thomas Hellström Reviewed-by: Matthew Auld Link: https://patchwork.freedesktop.org/patch/msgid/20220110172219.107131-7-thomas.hellstrom@linux.intel.com --- drivers/gpu/drm/i915/Makefile | 1 - drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c | 15 +-- drivers/gpu/drm/i915/gt/intel_engine_cs.c | 9 +- drivers/gpu/drm/i915/i915_gpu_error.c | 87 ++++++++--------- drivers/gpu/drm/i915/i915_request.c | 12 ++- drivers/gpu/drm/i915/i915_request.h | 6 +- drivers/gpu/drm/i915/i915_vma.c | 16 +--- drivers/gpu/drm/i915/i915_vma_resource.c | 4 + drivers/gpu/drm/i915/i915_vma_resource.h | 28 +++++- drivers/gpu/drm/i915/i915_vma_snapshot.c | 125 ------------------------- drivers/gpu/drm/i915/i915_vma_snapshot.h | 101 -------------------- 11 files changed, 90 insertions(+), 314 deletions(-) delete mode 100644 drivers/gpu/drm/i915/i915_vma_snapshot.c delete mode 100644 drivers/gpu/drm/i915/i915_vma_snapshot.h (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile index 34ad92c9dbef..559e57414c4a 100644 --- a/drivers/gpu/drm/i915/Makefile +++ b/drivers/gpu/drm/i915/Makefile @@ -175,7 +175,6 @@ i915-y += \ i915_ttm_buddy_manager.o \ i915_vma.o \ i915_vma_resource.o \ - i915_vma_snapshot.o \ intel_wopcm.o # general-purpose microcontroller (GuC) support diff --git a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c index 934d085c791b..2065e5e44fac 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c @@ -29,7 +29,6 @@ #include "i915_gem_ioctls.h" #include "i915_trace.h" #include "i915_user_extensions.h" -#include "i915_vma_snapshot.h" struct eb_vma { struct i915_vma *vma; @@ -1941,7 +1940,6 @@ static void eb_capture_stage(struct i915_execbuffer *eb) { const unsigned int count = eb->buffer_count; unsigned int i = count, j; - struct i915_vma_snapshot *vsnap; while (i--) { struct eb_vma *ev = &eb->vma[i]; @@ -1951,11 +1949,6 @@ static void eb_capture_stage(struct i915_execbuffer *eb) if (!(flags & EXEC_OBJECT_CAPTURE)) continue; - vsnap = i915_vma_snapshot_alloc(GFP_KERNEL); - if (!vsnap) - continue; - - i915_vma_snapshot_init(vsnap, vma, "user"); for_each_batch_create_order(eb, j) { struct i915_capture_list *capture; @@ -1964,10 +1957,9 @@ static void eb_capture_stage(struct i915_execbuffer *eb) continue; capture->next = eb->capture_lists[j]; - capture->vma_snapshot = i915_vma_snapshot_get(vsnap); + capture->vma_res = i915_vma_resource_get(vma->resource); eb->capture_lists[j] = capture; } - i915_vma_snapshot_put(vsnap); } } @@ -3270,9 +3262,8 @@ eb_requests_create(struct i915_execbuffer *eb, struct dma_fence *in_fence, * _onstack interface. */ if (eb->batches[i]->vma) - i915_vma_snapshot_init_onstack(&eb->requests[i]->batch_snapshot, - eb->batches[i]->vma, - "batch"); + eb->requests[i]->batch_res = + i915_vma_resource_get(eb->batches[i]->vma->resource); if (eb->batch_pool) { GEM_BUG_ON(intel_context_is_parallel(eb->context)); intel_gt_buffer_pool_mark_active(eb->batch_pool, diff --git a/drivers/gpu/drm/i915/gt/intel_engine_cs.c b/drivers/gpu/drm/i915/gt/intel_engine_cs.c index 74aa90587061..d1daa4cc2895 100644 --- a/drivers/gpu/drm/i915/gt/intel_engine_cs.c +++ b/drivers/gpu/drm/i915/gt/intel_engine_cs.c @@ -1708,18 +1708,15 @@ static void intel_engine_print_registers(struct intel_engine_cs *engine, static void print_request_ring(struct drm_printer *m, struct i915_request *rq) { - struct i915_vma_snapshot *vsnap = &rq->batch_snapshot; + struct i915_vma_resource *vma_res = rq->batch_res; void *ring; int size; - if (!i915_vma_snapshot_present(vsnap)) - vsnap = NULL; - drm_printf(m, "[head %04x, postfix %04x, tail %04x, batch 0x%08x_%08x]:\n", rq->head, rq->postfix, rq->tail, - vsnap ? upper_32_bits(vsnap->vma_resource->start) : ~0u, - vsnap ? lower_32_bits(vsnap->vma_resource->start) : ~0u); + vma_res ? upper_32_bits(vma_res->start) : ~0u, + vma_res ? lower_32_bits(vma_res->start) : ~0u); size = rq->tail - rq->head; if (rq->tail < rq->head) diff --git a/drivers/gpu/drm/i915/i915_gpu_error.c b/drivers/gpu/drm/i915/i915_gpu_error.c index 1af54ff374f9..f8c4336cba89 100644 --- a/drivers/gpu/drm/i915/i915_gpu_error.c +++ b/drivers/gpu/drm/i915/i915_gpu_error.c @@ -48,7 +48,6 @@ #include "i915_gpu_error.h" #include "i915_memcpy.h" #include "i915_scatterlist.h" -#include "i915_vma_snapshot.h" #define ALLOW_FAIL (__GFP_KSWAPD_RECLAIM | __GFP_RETRY_MAYFAIL | __GFP_NOWARN) #define ATOMIC_MAYFAIL (GFP_ATOMIC | __GFP_NOWARN) @@ -1013,8 +1012,10 @@ void __i915_gpu_coredump_free(struct kref *error_ref) static struct i915_vma_coredump * i915_vma_coredump_create(const struct intel_gt *gt, - const struct i915_vma_snapshot *vsnap, - struct i915_vma_compress *compress) + const struct i915_vma_resource *vma_res, + struct i915_vma_compress *compress, + const char *name) + { struct i915_ggtt *ggtt = gt->ggtt; const u64 slot = ggtt->error_capture.start; @@ -1024,7 +1025,7 @@ i915_vma_coredump_create(const struct intel_gt *gt, might_sleep(); - if (!vsnap || !vsnap->pages || !compress) + if (!vma_res || !vma_res->bi.pages || !compress) return NULL; dst = kmalloc(sizeof(*dst), ALLOW_FAIL); @@ -1037,12 +1038,12 @@ i915_vma_coredump_create(const struct intel_gt *gt, } INIT_LIST_HEAD(&dst->page_list); - strcpy(dst->name, vsnap->name); + strcpy(dst->name, name); dst->next = NULL; - dst->gtt_offset = vsnap->vma_resource->start; - dst->gtt_size = vsnap->vma_resource->node_size; - dst->gtt_page_sizes = vsnap->vma_resource->page_sizes_gtt; + dst->gtt_offset = vma_res->start; + dst->gtt_size = vma_res->node_size; + dst->gtt_page_sizes = vma_res->page_sizes_gtt; dst->unused = 0; ret = -EINVAL; @@ -1050,7 +1051,7 @@ i915_vma_coredump_create(const struct intel_gt *gt, void __iomem *s; dma_addr_t dma; - for_each_sgt_daddr(dma, iter, vsnap->pages) { + for_each_sgt_daddr(dma, iter, vma_res->bi.pages) { mutex_lock(&ggtt->error_mutex); ggtt->vm.insert_page(&ggtt->vm, dma, slot, I915_CACHE_NONE, 0); @@ -1068,11 +1069,11 @@ i915_vma_coredump_create(const struct intel_gt *gt, if (ret) break; } - } else if (vsnap->mr && vsnap->mr->type != INTEL_MEMORY_SYSTEM) { - struct intel_memory_region *mem = vsnap->mr; + } else if (vma_res->bi.lmem) { + struct intel_memory_region *mem = vma_res->mr; dma_addr_t dma; - for_each_sgt_daddr(dma, iter, vsnap->pages) { + for_each_sgt_daddr(dma, iter, vma_res->bi.pages) { void __iomem *s; s = io_mapping_map_wc(&mem->iomap, @@ -1088,7 +1089,7 @@ i915_vma_coredump_create(const struct intel_gt *gt, } else { struct page *page; - for_each_sgt_page(page, iter, vsnap->pages) { + for_each_sgt_page(page, iter, vma_res->bi.pages) { void *s; drm_clflush_pages(&page, 1); @@ -1324,33 +1325,32 @@ static bool record_context(struct i915_gem_context_coredump *e, struct intel_engine_capture_vma { struct intel_engine_capture_vma *next; - struct i915_vma_snapshot *vsnap; + struct i915_vma_resource *vma_res; char name[16]; bool lockdep_cookie; }; static struct intel_engine_capture_vma * capture_vma_snapshot(struct intel_engine_capture_vma *next, - struct i915_vma_snapshot *vsnap, - gfp_t gfp) + struct i915_vma_resource *vma_res, + gfp_t gfp, const char *name) { struct intel_engine_capture_vma *c; - if (!i915_vma_snapshot_present(vsnap)) + if (!vma_res) return next; c = kmalloc(sizeof(*c), gfp); if (!c) return next; - if (!i915_vma_snapshot_resource_pin(vsnap, &c->lockdep_cookie)) { + if (!i915_vma_resource_hold(vma_res, &c->lockdep_cookie)) { kfree(c); return next; } - strcpy(c->name, vsnap->name); - c->vsnap = vsnap; - i915_vma_snapshot_get(vsnap); + strcpy(c->name, name); + c->vma_res = i915_vma_resource_get(vma_res); c->next = next; return c; @@ -1362,8 +1362,6 @@ capture_vma(struct intel_engine_capture_vma *next, const char *name, gfp_t gfp) { - struct i915_vma_snapshot *vsnap; - if (!vma) return next; @@ -1372,19 +1370,10 @@ capture_vma(struct intel_engine_capture_vma *next, * to a struct i915_vma_snapshot at command submission time. * Not here. */ - GEM_WARN_ON(!i915_vma_is_pinned(vma)); - if (!i915_vma_is_pinned(vma)) - return next; - - vsnap = i915_vma_snapshot_alloc(gfp); - if (!vsnap) + if (GEM_WARN_ON(!i915_vma_is_pinned(vma))) return next; - i915_vma_snapshot_init(vsnap, vma, name); - next = capture_vma_snapshot(next, vsnap, gfp); - - /* FIXME: Replace on async unbind. */ - i915_vma_snapshot_put(vsnap); + next = capture_vma_snapshot(next, vma->resource, gfp, name); return next; } @@ -1397,7 +1386,8 @@ capture_user(struct intel_engine_capture_vma *capture, struct i915_capture_list *c; for (c = rq->capture_list; c; c = c->next) - capture = capture_vma_snapshot(capture, c->vma_snapshot, gfp); + capture = capture_vma_snapshot(capture, c->vma_res, gfp, + "user"); return capture; } @@ -1415,16 +1405,19 @@ static struct i915_vma_coredump * create_vma_coredump(const struct intel_gt *gt, struct i915_vma *vma, const char *name, struct i915_vma_compress *compress) { - struct i915_vma_coredump *ret; - struct i915_vma_snapshot tmp; + struct i915_vma_coredump *ret = NULL; + struct i915_vma_resource *vma_res; + bool lockdep_cookie; if (!vma) return NULL; - GEM_WARN_ON(!i915_vma_is_pinned(vma)); - i915_vma_snapshot_init_onstack(&tmp, vma, name); - ret = i915_vma_coredump_create(gt, &tmp, compress); - i915_vma_snapshot_put_onstack(&tmp); + vma_res = vma->resource; + + if (i915_vma_resource_hold(vma_res, &lockdep_cookie)) { + ret = i915_vma_coredump_create(gt, vma_res, compress, name); + i915_vma_resource_unhold(vma_res, lockdep_cookie); + } return ret; } @@ -1471,7 +1464,7 @@ intel_engine_coredump_add_request(struct intel_engine_coredump *ee, * as the simplest method to avoid being overwritten * by userspace. */ - vma = capture_vma_snapshot(vma, &rq->batch_snapshot, gfp); + vma = capture_vma_snapshot(vma, rq->batch_res, gfp, "batch"); vma = capture_user(vma, rq, gfp); vma = capture_vma(vma, rq->ring->vma, "ring", gfp); vma = capture_vma(vma, rq->context->state, "HW context", gfp); @@ -1492,14 +1485,14 @@ intel_engine_coredump_add_vma(struct intel_engine_coredump *ee, while (capture) { struct intel_engine_capture_vma *this = capture; - struct i915_vma_snapshot *vsnap = this->vsnap; + struct i915_vma_resource *vma_res = this->vma_res; add_vma(ee, - i915_vma_coredump_create(engine->gt, - vsnap, compress)); + i915_vma_coredump_create(engine->gt, vma_res, + compress, this->name)); - i915_vma_snapshot_resource_unpin(vsnap, this->lockdep_cookie); - i915_vma_snapshot_put(vsnap); + i915_vma_resource_unhold(vma_res, this->lockdep_cookie); + i915_vma_resource_put(vma_res); capture = this->next; kfree(this); diff --git a/drivers/gpu/drm/i915/i915_request.c b/drivers/gpu/drm/i915/i915_request.c index 3a03aa64a7a4..041a5ac7eeba 100644 --- a/drivers/gpu/drm/i915/i915_request.c +++ b/drivers/gpu/drm/i915/i915_request.c @@ -115,8 +115,10 @@ static void i915_fence_release(struct dma_fence *fence) rq->guc_prio != GUC_PRIO_FINI); i915_request_free_capture_list(fetch_and_zero(&rq->capture_list)); - if (i915_vma_snapshot_present(&rq->batch_snapshot)) - i915_vma_snapshot_put_onstack(&rq->batch_snapshot); + if (rq->batch_res) { + i915_vma_resource_put(rq->batch_res); + rq->batch_res = NULL; + } /* * The request is put onto a RCU freelist (i.e. the address @@ -307,7 +309,7 @@ void i915_request_free_capture_list(struct i915_capture_list *capture) while (capture) { struct i915_capture_list *next = capture->next; - i915_vma_snapshot_put(capture->vma_snapshot); + i915_vma_resource_put(capture->vma_res); kfree(capture); capture = next; } @@ -853,7 +855,7 @@ static void __i915_request_ctor(void *arg) i915_sw_fence_init(&rq->semaphore, semaphore_notify); clear_capture_list(rq); - rq->batch_snapshot.present = false; + rq->batch_res = NULL; init_llist_head(&rq->execute_cb); } @@ -959,7 +961,7 @@ __i915_request_create(struct intel_context *ce, gfp_t gfp) __rq_init_watchdog(rq); assert_capture_list_is_null(rq); GEM_BUG_ON(!llist_empty(&rq->execute_cb)); - GEM_BUG_ON(i915_vma_snapshot_present(&rq->batch_snapshot)); + GEM_BUG_ON(rq->batch_res); /* * Reserve space in the ring buffer for all the commands required to diff --git a/drivers/gpu/drm/i915/i915_request.h b/drivers/gpu/drm/i915/i915_request.h index 170ee78c2858..28b1f9db5487 100644 --- a/drivers/gpu/drm/i915/i915_request.h +++ b/drivers/gpu/drm/i915/i915_request.h @@ -40,7 +40,7 @@ #include "i915_scheduler.h" #include "i915_selftest.h" #include "i915_sw_fence.h" -#include "i915_vma_snapshot.h" +#include "i915_vma_resource.h" #include @@ -52,7 +52,7 @@ struct i915_request; #if IS_ENABLED(CONFIG_DRM_I915_CAPTURE_ERROR) struct i915_capture_list { - struct i915_vma_snapshot *vma_snapshot; + struct i915_vma_resource *vma_res; struct i915_capture_list *next; }; @@ -300,7 +300,7 @@ struct i915_request { /** Batch buffer pointer for selftest internal use. */ I915_SELFTEST_DECLARE(struct i915_vma *batch); - struct i915_vma_snapshot batch_snapshot; + struct i915_vma_resource *batch_res; #if IS_ENABLED(CONFIG_DRM_I915_CAPTURE_ERROR) /** diff --git a/drivers/gpu/drm/i915/i915_vma.c b/drivers/gpu/drm/i915/i915_vma.c index b86666f653ca..9d859b0a3fbe 100644 --- a/drivers/gpu/drm/i915/i915_vma.c +++ b/drivers/gpu/drm/i915/i915_vma.c @@ -288,7 +288,6 @@ struct i915_vma_work { struct i915_vma_resource *vma_res; struct drm_i915_gem_object *pinned; struct i915_sw_dma_fence_cb cb; - struct i915_refct_sgt *rsgt; enum i915_cache_level cache_level; unsigned int flags; }; @@ -314,8 +313,6 @@ static void __vma_release(struct dma_fence_work *work) i915_vm_put(vw->vm); if (vw->vma_res) i915_vma_resource_put(vw->vma_res); - if (vw->rsgt) - i915_refct_sgt_put(vw->rsgt); } static const struct dma_fence_work_ops bind_ops = { @@ -386,8 +383,8 @@ i915_vma_resource_init_from_vma(struct i915_vma_resource *vma_res, struct drm_i915_gem_object *obj = vma->obj; i915_vma_resource_init(vma_res, vma->vm, vma->pages, &vma->page_sizes, - i915_gem_object_is_readonly(obj), - i915_gem_object_is_lmem(obj), + obj->mm.rsgt, i915_gem_object_is_readonly(obj), + i915_gem_object_is_lmem(obj), obj->mm.region, vma->ops, vma->private, vma->node.start, vma->node.size, vma->size); } @@ -478,8 +475,6 @@ int i915_vma_bind(struct i915_vma *vma, work->vma_res = i915_vma_resource_get(vma->resource); work->cache_level = cache_level; work->flags = bind_flags; - if (vma->obj->mm.rsgt) - work->rsgt = i915_refct_sgt_get(vma->obj->mm.rsgt); /* * Note we only want to chain up to the migration fence on @@ -505,7 +500,7 @@ int i915_vma_bind(struct i915_vma *vma, * on the object to avoid waiting for the async bind to * complete in the object destruction path. */ - if (!work->rsgt) + if (!work->vma_res->bi.pages_rsgt) work->pinned = i915_gem_object_get(vma->obj); } else { if (vma->obj) { @@ -1771,7 +1766,7 @@ struct dma_fence *__i915_vma_evict(struct i915_vma *vma, bool async) GEM_BUG_ON(i915_vma_has_userfault(vma)); /* Object backend must be async capable. */ - GEM_WARN_ON(async && !vma->obj->mm.rsgt); + GEM_WARN_ON(async && !vma->resource->bi.pages_rsgt); /* If vm is not open, unbind is a nop. */ vma_res->needs_wakeref = i915_vma_is_bound(vma, I915_VMA_GLOBAL_BIND) && @@ -1784,9 +1779,6 @@ struct dma_fence *__i915_vma_evict(struct i915_vma *vma, bool async) atomic_and(~(I915_VMA_BIND_MASK | I915_VMA_ERROR | I915_VMA_GGTT_WRITE), &vma->flags); - /* Object backend must be async capable. */ - GEM_WARN_ON(async && !vma->obj->mm.rsgt); - i915_vma_detach(vma); if (!async && unbind_fence) { diff --git a/drivers/gpu/drm/i915/i915_vma_resource.c b/drivers/gpu/drm/i915/i915_vma_resource.c index 3e55a30b2da7..1f41c0c699eb 100644 --- a/drivers/gpu/drm/i915/i915_vma_resource.c +++ b/drivers/gpu/drm/i915/i915_vma_resource.c @@ -9,6 +9,7 @@ #include "i915_sw_fence.h" #include "i915_vma_resource.h" #include "i915_drv.h" +#include "intel_memory_region.h" #include "gt/intel_gtt.h" @@ -117,6 +118,9 @@ static void __i915_vma_resource_unhold(struct i915_vma_resource *vma_res) vma_res_itree_remove(vma_res, &vm->pending_unbind); mutex_unlock(&vm->mutex); } + + if (vma_res->bi.pages_rsgt) + i915_refct_sgt_put(vma_res->bi.pages_rsgt); } /** diff --git a/drivers/gpu/drm/i915/i915_vma_resource.h b/drivers/gpu/drm/i915/i915_vma_resource.h index cb3f4e1e8457..25913913baa6 100644 --- a/drivers/gpu/drm/i915/i915_vma_resource.h +++ b/drivers/gpu/drm/i915/i915_vma_resource.h @@ -10,9 +10,12 @@ #include #include "i915_gem.h" +#include "i915_scatterlist.h" #include "i915_sw_fence.h" #include "intel_runtime_pm.h" +struct intel_memory_region; + struct i915_page_sizes { /** * The sg mask of the pages sg_table. i.e the mask of @@ -46,6 +49,7 @@ struct i915_page_sizes { * @__subtree_last: Interval tree private member. * @vm: non-refcounted pointer to the vm. This is for internal use only and * this member is cleared after vm_resource unbind. + * @mr: The memory region of the object pointed to by the vma. * @ops: Pointer to the backend i915_vma_ops. * @private: Bind backend private info. * @start: Offset into the address space of bind range start. @@ -54,8 +58,10 @@ struct i915_page_sizes { * @page_sizes_gtt: Resulting page sizes from the bind operation. * @bound_flags: Flags indicating binding status. * @allocated: Backend private data. TODO: Should move into @private. - * @immediate_unbind: Unbind can be done immediately and don't need to be - * deferred to a work item awaiting unsignaled fences. + * @immediate_unbind: Unbind can be done immediately and doesn't need to be + * deferred to a work item awaiting unsignaled fences. This is a hack. + * (dma_fence_work uses a fence flag for this, but this seems slightly + * cleaner). * * The lifetime of a struct i915_vma_resource is from a binding request to * the actual possible asynchronous unbind has completed. @@ -80,16 +86,22 @@ struct i915_vma_resource { * and flags * @pages: The pages sg-table. * @page_sizes: Page sizes of the pages. + * @pages_rsgt: Refcounted sg-table when delayed object destruction + * is supported. May be NULL. * @readonly: Whether the vma should be bound read-only. * @lmem: Whether the vma points to lmem. */ struct i915_vma_bindinfo { struct sg_table *pages; struct i915_page_sizes page_sizes; + struct i915_refct_sgt *pages_rsgt; bool readonly:1; bool lmem:1; } bi; +#if IS_ENABLED(CONFIG_DRM_I915_CAPTURE_ERROR) + struct intel_memory_region *mr; +#endif const struct i915_vma_ops *ops; void *private; u64 start; @@ -145,8 +157,11 @@ static inline void i915_vma_resource_put(struct i915_vma_resource *vma_res) * @vm: Pointer to the vm. * @pages: The pages sg-table. * @page_sizes: Page sizes of the pages. + * @pages_rsgt: Pointer to a struct i915_refct_sgt of an object with + * delayed destruction. * @readonly: Whether the vma should be bound read-only. * @lmem: Whether the vma points to lmem. + * @mr: The memory region of the object the vma points to. * @ops: The backend ops. * @private: Bind backend private info. * @start: Offset into the address space of bind range start. @@ -162,8 +177,10 @@ static inline void i915_vma_resource_init(struct i915_vma_resource *vma_res, struct i915_address_space *vm, struct sg_table *pages, const struct i915_page_sizes *page_sizes, + struct i915_refct_sgt *pages_rsgt, bool readonly, bool lmem, + struct intel_memory_region *mr, const struct i915_vma_ops *ops, void *private, u64 start, @@ -174,8 +191,13 @@ static inline void i915_vma_resource_init(struct i915_vma_resource *vma_res, vma_res->vm = vm; vma_res->bi.pages = pages; vma_res->bi.page_sizes = *page_sizes; + if (pages_rsgt) + vma_res->bi.pages_rsgt = i915_refct_sgt_get(pages_rsgt); vma_res->bi.readonly = readonly; vma_res->bi.lmem = lmem; +#if IS_ENABLED(CONFIG_DRM_I915_CAPTURE_ERROR) + vma_res->mr = mr; +#endif vma_res->ops = ops; vma_res->private = private; vma_res->start = start; @@ -186,6 +208,8 @@ static inline void i915_vma_resource_init(struct i915_vma_resource *vma_res, static inline void i915_vma_resource_fini(struct i915_vma_resource *vma_res) { GEM_BUG_ON(refcount_read(&vma_res->hold_count) != 1); + if (vma_res->bi.pages_rsgt) + i915_refct_sgt_put(vma_res->bi.pages_rsgt); i915_sw_fence_fini(&vma_res->chain); } diff --git a/drivers/gpu/drm/i915/i915_vma_snapshot.c b/drivers/gpu/drm/i915/i915_vma_snapshot.c deleted file mode 100644 index 69f62c1ca967..000000000000 --- a/drivers/gpu/drm/i915/i915_vma_snapshot.c +++ /dev/null @@ -1,125 +0,0 @@ -// SPDX-License-Identifier: MIT -/* - * Copyright © 2021 Intel Corporation - */ - -#include "i915_vma_resource.h" -#include "i915_vma_snapshot.h" -#include "i915_vma_types.h" -#include "i915_vma.h" - -/** - * i915_vma_snapshot_init - Initialize a struct i915_vma_snapshot from - * a struct i915_vma. - * @vsnap: The i915_vma_snapshot to init. - * @vma: A struct i915_vma used to initialize @vsnap. - * @name: Name associated with the snapshot. The character pointer needs to - * stay alive over the lifitime of the shapsot - */ -void i915_vma_snapshot_init(struct i915_vma_snapshot *vsnap, - struct i915_vma *vma, - const char *name) -{ - if (!i915_vma_is_pinned(vma)) - assert_object_held(vma->obj); - - vsnap->name = name; - vsnap->obj_size = vma->obj->base.size; - vsnap->pages = vma->pages; - vsnap->pages_rsgt = NULL; - vsnap->mr = NULL; - if (vma->obj->mm.rsgt) - vsnap->pages_rsgt = i915_refct_sgt_get(vma->obj->mm.rsgt); - vsnap->mr = vma->obj->mm.region; - kref_init(&vsnap->kref); - vsnap->vma_resource = i915_vma_get_current_resource(vma); - vsnap->onstack = false; - vsnap->present = true; -} - -/** - * i915_vma_snapshot_init_onstack - Initialize a struct i915_vma_snapshot from - * a struct i915_vma, but avoid kfreeing it on last put. - * @vsnap: The i915_vma_snapshot to init. - * @vma: A struct i915_vma used to initialize @vsnap. - * @name: Name associated with the snapshot. The character pointer needs to - * stay alive over the lifitime of the shapsot - */ -void i915_vma_snapshot_init_onstack(struct i915_vma_snapshot *vsnap, - struct i915_vma *vma, - const char *name) -{ - i915_vma_snapshot_init(vsnap, vma, name); - vsnap->onstack = true; -} - -static void vma_snapshot_release(struct kref *ref) -{ - struct i915_vma_snapshot *vsnap = - container_of(ref, typeof(*vsnap), kref); - - vsnap->present = false; - i915_vma_resource_put(vsnap->vma_resource); - if (vsnap->pages_rsgt) - i915_refct_sgt_put(vsnap->pages_rsgt); - if (!vsnap->onstack) - kfree(vsnap); -} - -/** - * i915_vma_snapshot_put - Put an i915_vma_snapshot pointer reference - * @vsnap: The pointer reference - */ -void i915_vma_snapshot_put(struct i915_vma_snapshot *vsnap) -{ - kref_put(&vsnap->kref, vma_snapshot_release); -} - -/** - * i915_vma_snapshot_put_onstack - Put an onstcak i915_vma_snapshot pointer - * reference and varify that the structure is released - * @vsnap: The pointer reference - * - * This function is intended to be paired with a i915_vma_init_onstack() - * and should be called before exiting the scope that declared or - * freeing the structure that embedded @vsnap to verify that all references - * have been released. - */ -void i915_vma_snapshot_put_onstack(struct i915_vma_snapshot *vsnap) -{ - if (!kref_put(&vsnap->kref, vma_snapshot_release)) - GEM_BUG_ON(1); -} - -/** - * i915_vma_snapshot_resource_pin - Temporarily block the memory the - * vma snapshot is pointing to from being released. - * @vsnap: The vma snapshot. - * @lockdep_cookie: Pointer to bool needed for lockdep support. This needs - * to be passed to the paired i915_vma_snapshot_resource_unpin. - * - * This function will temporarily try to hold up a fence or similar structure - * and will therefore enter a fence signaling critical section. - * - * Return: true if we succeeded in blocking the memory from being released, - * false otherwise. - */ -bool i915_vma_snapshot_resource_pin(struct i915_vma_snapshot *vsnap, - bool *lockdep_cookie) -{ - return i915_vma_resource_hold(vsnap->vma_resource, lockdep_cookie); -} - -/** - * i915_vma_snapshot_resource_unpin - Unblock vma snapshot memory from - * being released. - * @vsnap: The vma snapshot. - * @lockdep_cookie: Cookie returned from matching i915_vma_resource_pin(). - * - * Might leave a fence signalling critical section and signal a fence. - */ -void i915_vma_snapshot_resource_unpin(struct i915_vma_snapshot *vsnap, - bool lockdep_cookie) -{ - i915_vma_resource_unhold(vsnap->vma_resource, lockdep_cookie); -} diff --git a/drivers/gpu/drm/i915/i915_vma_snapshot.h b/drivers/gpu/drm/i915/i915_vma_snapshot.h deleted file mode 100644 index 1b08ce9f8576..000000000000 --- a/drivers/gpu/drm/i915/i915_vma_snapshot.h +++ /dev/null @@ -1,101 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* - * Copyright © 2021 Intel Corporation - */ -#ifndef _I915_VMA_SNAPSHOT_H_ -#define _I915_VMA_SNAPSHOT_H_ - -#include -#include -#include - -struct i915_active; -struct i915_refct_sgt; -struct i915_vma; -struct intel_memory_region; -struct sg_table; - -/** - * DOC: Simple utilities for snapshotting GPU vma metadata, later used for - * error capture. Vi use a separate header for this to avoid issues due to - * recursive header includes. - */ - -/** - * struct i915_vma_snapshot - Snapshot of vma metadata. - * @obj_size: The size of the underlying object in bytes. - * @pages: The struct sg_table pointing to the pages bound. - * @pages_rsgt: The refcounted sg_table holding the reference for @pages if any. - * @mr: The memory region pointed for the pages bound. - * @kref: Reference for this structure. - * @vma_resource: Pointer to the vma resource representing the vma binding. - * @onstack: Whether the structure shouldn't be freed on final put. - * @present: Whether the structure is present and initialized. - */ -struct i915_vma_snapshot { - const char *name; - size_t obj_size; - struct sg_table *pages; - struct i915_refct_sgt *pages_rsgt; - struct intel_memory_region *mr; - struct kref kref; - struct i915_vma_resource *vma_resource; - bool onstack:1; - bool present:1; -}; - -void i915_vma_snapshot_init(struct i915_vma_snapshot *vsnap, - struct i915_vma *vma, - const char *name); - -void i915_vma_snapshot_init_onstack(struct i915_vma_snapshot *vsnap, - struct i915_vma *vma, - const char *name); - -void i915_vma_snapshot_put(struct i915_vma_snapshot *vsnap); - -void i915_vma_snapshot_put_onstack(struct i915_vma_snapshot *vsnap); - -bool i915_vma_snapshot_resource_pin(struct i915_vma_snapshot *vsnap, - bool *lockdep_cookie); - -void i915_vma_snapshot_resource_unpin(struct i915_vma_snapshot *vsnap, - bool lockdep_cookie); - -/** - * i915_vma_snapshot_alloc - Allocate a struct i915_vma_snapshot - * @gfp: Allocation mode. - * - * Return: A pointer to a struct i915_vma_snapshot if successful. - * NULL otherwise. - */ -static inline struct i915_vma_snapshot *i915_vma_snapshot_alloc(gfp_t gfp) -{ - return kmalloc(sizeof(struct i915_vma_snapshot), gfp); -} - -/** - * i915_vma_snapshot_get - Take a reference on a struct i915_vma_snapshot - * - * Return: A pointer to a struct i915_vma_snapshot. - */ -static inline struct i915_vma_snapshot * -i915_vma_snapshot_get(struct i915_vma_snapshot *vsnap) -{ - kref_get(&vsnap->kref); - return vsnap; -} - -/** - * i915_vma_snapshot_present - Whether a struct i915_vma_snapshot is - * present and initialized. - * - * Return: true if present and initialized; false otherwise. - */ -static inline bool -i915_vma_snapshot_present(const struct i915_vma_snapshot *vsnap) -{ - return vsnap && vsnap->present; -} - -#endif -- cgit From d62b9bee52dc887f9bf7d13cac120b7caf75079e Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Wed, 15 Dec 2021 10:51:15 +0100 Subject: drm/vc4: Support nomodeset If we have nomodeset on the kernel command line we should have the firmware framebuffer driver kept as is and not try to load the full-blown KMS driver. In this case, let's just register the v3d driver. Signed-off-by: Maxime Ripard Reviewed-by: Thomas Zimmermann Link: https://patchwork.freedesktop.org/patch/msgid/20211215095117.176435-3-maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_drv.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/vc4/vc4_drv.c b/drivers/gpu/drm/vc4/vc4_drv.c index 16abc3a3d601..d3cae84a4c4e 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.c +++ b/drivers/gpu/drm/vc4/vc4_drv.c @@ -357,6 +357,9 @@ static int __init vc4_drm_register(void) { int ret; + if (drm_firmware_drivers_only()) + return -ENODEV; + ret = platform_register_drivers(component_drivers, ARRAY_SIZE(component_drivers)); if (ret) -- cgit From a7e6f3d8a41e6b396bf06ad8f7ea5bca46cb2101 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Wed, 15 Dec 2021 10:51:16 +0100 Subject: drm/vc4: Remove conflicting framebuffers before callind bind_all The bind hooks will modify their controller registers, so simplefb is going to be unusable anyway. Let's avoid any transient state where it could still be in the system but no longer functionnal. Acked-by: Nicolas Saenz Julienne Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20211215095117.176435-4-maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_drv.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/vc4/vc4_drv.c b/drivers/gpu/drm/vc4/vc4_drv.c index d3cae84a4c4e..86c61ee120b7 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.c +++ b/drivers/gpu/drm/vc4/vc4_drv.c @@ -251,15 +251,15 @@ static int vc4_drm_bind(struct device *dev) if (ret) return ret; - ret = component_bind_all(dev, drm); + ret = drm_aperture_remove_framebuffers(false, &vc4_drm_driver); if (ret) return ret; - ret = vc4_plane_create_additional_planes(drm); + ret = component_bind_all(dev, drm); if (ret) - goto unbind_all; + return ret; - ret = drm_aperture_remove_framebuffers(false, &vc4_drm_driver); + ret = vc4_plane_create_additional_planes(drm); if (ret) goto unbind_all; -- cgit From c406ad5e4a851c510c94189d608f255796122047 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Wed, 15 Dec 2021 10:51:17 +0100 Subject: drm/vc4: Notify the firmware when DRM is in charge Once the call to drm_fb_helper_remove_conflicting_framebuffers() has been made, simplefb has been unregistered and the KMS driver is entirely in charge of the display. Thus, we can notify the firmware it can free whatever resource it was using to maintain simplefb functional. Signed-off-by: Maxime Ripard Reviewed-by: Javier Martinez Canillas Acked-by: Thomas Zimmermann Link: https://patchwork.freedesktop.org/patch/msgid/20211215095117.176435-5-maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_drv.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/vc4/vc4_drv.c b/drivers/gpu/drm/vc4/vc4_drv.c index 86c61ee120b7..a03053c8e22c 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.c +++ b/drivers/gpu/drm/vc4/vc4_drv.c @@ -37,6 +37,8 @@ #include #include +#include + #include "uapi/drm/vc4_drm.h" #include "vc4_drv.h" @@ -215,6 +217,7 @@ static void vc4_match_add_drivers(struct device *dev, static int vc4_drm_bind(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); + struct rpi_firmware *firmware = NULL; struct drm_device *drm; struct vc4_dev *vc4; struct device_node *node; @@ -251,10 +254,29 @@ static int vc4_drm_bind(struct device *dev) if (ret) return ret; + node = of_find_compatible_node(NULL, NULL, "raspberrypi,bcm2835-firmware"); + if (node) { + firmware = rpi_firmware_get(node); + of_node_put(node); + + if (!firmware) + return -EPROBE_DEFER; + } + ret = drm_aperture_remove_framebuffers(false, &vc4_drm_driver); if (ret) return ret; + if (firmware) { + ret = rpi_firmware_property(firmware, + RPI_FIRMWARE_NOTIFY_DISPLAY_DONE, + NULL, 0); + if (ret) + drm_warn(drm, "Couldn't stop firmware display driver: %d\n", ret); + + rpi_firmware_put(firmware); + } + ret = component_bind_all(dev, drm); if (ret) return ret; -- cgit From f199f71f20916ddaf2bfc4dceea33c776d414428 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Tue, 4 Jan 2022 20:48:57 +0200 Subject: drm/mst: use DP_GET_SINK_COUNT() for sink count in ESI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Take bit 7 into account when reading sink count from DP_DEVICE_SERVICE_IRQ_VECTOR_ESI0. Signed-off-by: Jani Nikula Reviewed-by: Ville Syrjälä Link: https://patchwork.freedesktop.org/patch/msgid/20220104184857.784563-2-jani.nikula@intel.com --- drivers/gpu/drm/drm_dp_mst_topology.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c b/drivers/gpu/drm/drm_dp_mst_topology.c index f3d79eda94bb..ab4372e9fe43 100644 --- a/drivers/gpu/drm/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/drm_dp_mst_topology.c @@ -4196,7 +4196,7 @@ int drm_dp_mst_hpd_irq(struct drm_dp_mst_topology_mgr *mgr, u8 *esi, bool *handl int ret = 0; int sc; *handled = false; - sc = esi[0] & 0x3f; + sc = DP_GET_SINK_COUNT(esi[0]); if (sc != mgr->sink_count) { mgr->sink_count = sc; -- cgit From 53c8283ba9b46559c12fb9fd679cbaf72424649e Mon Sep 17 00:00:00 2001 From: John Harrison Date: Thu, 6 Jan 2022 16:06:20 -0800 Subject: drm/i915/guc: Temporarily bump the GuC load timeout There is a known (but exceedingly unlikely) race condition where the asynchronous frequency management code could reduce the GT clock while a GuC reload is in progress (during a full GT reset). A fix is in progress but there are complex locking issues to be resolved. In the meantime bump the timeout to 200ms. Even at slowest clock, this should be sufficient. And in the working case, a larger timeout makes no difference. Signed-off-by: John Harrison Reviewed-by: Matthew Brost Link: https://patchwork.freedesktop.org/patch/msgid/20220107000622.292081-2-John.C.Harrison@Intel.com --- drivers/gpu/drm/i915/gt/uc/intel_guc_fw.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_fw.c b/drivers/gpu/drm/i915/gt/uc/intel_guc_fw.c index 31420ce1ce6b..d09c205b2beb 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_fw.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_fw.c @@ -105,12 +105,21 @@ static int guc_wait_ucode(struct intel_uncore *uncore) /* * Wait for the GuC to start up. * NB: Docs recommend not using the interrupt for completion. - * Measurements indicate this should take no more than 20ms, so a + * Measurements indicate this should take no more than 20ms + * (assuming the GT clock is at maximum frequency). So, a * timeout here indicates that the GuC has failed and is unusable. * (Higher levels of the driver may decide to reset the GuC and * attempt the ucode load again if this happens.) + * + * FIXME: There is a known (but exceedingly unlikely) race condition + * where the asynchronous frequency management code could reduce + * the GT clock while a GuC reload is in progress (during a full + * GT reset). A fix is in progress but there are complex locking + * issues to be resolved. In the meantime bump the timeout to + * 200ms. Even at slowest clock, this should be sufficient. And + * in the working case, a larger timeout makes no difference. */ - ret = wait_for(guc_ready(uncore, &status), 100); + ret = wait_for(guc_ready(uncore, &status), 200); if (ret) { struct drm_device *drm = &uncore->i915->drm; -- cgit From 77b6f79df66ed2919dedb834edea630f40079f12 Mon Sep 17 00:00:00 2001 From: John Harrison Date: Thu, 6 Jan 2022 16:06:21 -0800 Subject: drm/i915/guc: Update to GuC version 69.0.3 Update to the latest GuC release. The latest GuC firmware introduces a number of interface changes: GuC may return NO_RESPONSE_RETRY message for requests sent over CTB. Add support for this reply and try resending the request again as a new CTB message. A KLV (key-length-value) mechanism is now used for passing configuration data such as CTB management. With the new KLV scheme, the old CTB management actions are no longer used and are removed. Register capture on hang is now supported by GuC. Full i915 support for this will be added by a later patch. A minimum support of providing capture memory and register lists is required though, so add that in. The device id of the current platform needs to be provided at init time. The 'poll CS' w/a (Wa_22012773006) was blanket enabled by previous versions of GuC. It must now be explicitly requested by the KMD. So, add in the code to turn it on when relevant. The GuC log entry format has changed. This requires adding a new field to the log header structure to mark the wrap point at the end of the buffer (as the buffer size is no longer a multiple of the log entry size). New CTB notification messages are now sent for some things that were previously only sent via MMIO notifications. Of these, the crash dump notification was not really being handled by i915. It called the log flush code but that only flushed the regular debug log and then only if relay logging was enabled. So just report an error message instead. The 'exception' notification was just being ignored completely. So add an error message for that as well. Note that in either the crash dump or the exception case, the GuC is basically dead. The KMD will detect this via the heartbeat and trigger both an error log (which will include the crash dump as part of the GuC log) and a GT reset. So no other processing is really required. Signed-off-by: John Harrison Signed-off-by: Michal Wajdeczko Reviewed-by: Matthew Brost Link: https://patchwork.freedesktop.org/patch/msgid/20220107000622.292081-3-John.C.Harrison@Intel.com --- drivers/gpu/drm/i915/gt/uc/abi/guc_actions_abi.h | 80 ++++++------ drivers/gpu/drm/i915/gt/uc/abi/guc_klvs_abi.h | 82 +++++++++++++ drivers/gpu/drm/i915/gt/uc/intel_guc.c | 126 ++++++++++++++++--- drivers/gpu/drm/i915/gt/uc/intel_guc.h | 4 + drivers/gpu/drm/i915/gt/uc/intel_guc_ads.c | 45 ++++++- drivers/gpu/drm/i915/gt/uc/intel_guc_ct.c | 141 ++++++++++++---------- drivers/gpu/drm/i915/gt/uc/intel_guc_fwif.h | 37 ++++-- drivers/gpu/drm/i915/gt/uc/intel_guc_log.c | 31 ++--- drivers/gpu/drm/i915/gt/uc/intel_guc_log.h | 3 + drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c | 18 +++ drivers/gpu/drm/i915/gt/uc/intel_uc_fw.c | 30 ++--- 11 files changed, 433 insertions(+), 164 deletions(-) create mode 100644 drivers/gpu/drm/i915/gt/uc/abi/guc_klvs_abi.h (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/uc/abi/guc_actions_abi.h b/drivers/gpu/drm/i915/gt/uc/abi/guc_actions_abi.h index fe5d7d261797..7afdadc7656f 100644 --- a/drivers/gpu/drm/i915/gt/uc/abi/guc_actions_abi.h +++ b/drivers/gpu/drm/i915/gt/uc/abi/guc_actions_abi.h @@ -7,9 +7,9 @@ #define _ABI_GUC_ACTIONS_ABI_H /** - * DOC: HOST2GUC_REGISTER_CTB + * DOC: HOST2GUC_SELF_CFG * - * This message is used as part of the `CTB based communication`_ setup. + * This message is used by Host KMD to setup of the `GuC Self Config KLVs`_. * * This message must be sent as `MMIO HXG Message`_. * @@ -22,20 +22,18 @@ * | +-------+--------------------------------------------------------------+ * | | 27:16 | DATA0 = MBZ | * | +-------+--------------------------------------------------------------+ - * | | 15:0 | ACTION = _`GUC_ACTION_HOST2GUC_REGISTER_CTB` = 0x4505 | + * | | 15:0 | ACTION = _`GUC_ACTION_HOST2GUC_SELF_CFG` = 0x0508 | * +---+-------+--------------------------------------------------------------+ - * | 1 | 31:12 | RESERVED = MBZ | + * | 1 | 31:16 | **KLV_KEY** - KLV key, see `GuC Self Config KLVs`_ | * | +-------+--------------------------------------------------------------+ - * | | 11:8 | **TYPE** - type for the `CT Buffer`_ | + * | | 15:0 | **KLV_LEN** - KLV length | * | | | | - * | | | - _`GUC_CTB_TYPE_HOST2GUC` = 0 | - * | | | - _`GUC_CTB_TYPE_GUC2HOST` = 1 | - * | +-------+--------------------------------------------------------------+ - * | | 7:0 | **SIZE** - size of the `CT Buffer`_ in 4K units minus 1 | + * | | | - 32 bit KLV = 1 | + * | | | - 64 bit KLV = 2 | * +---+-------+--------------------------------------------------------------+ - * | 2 | 31:0 | **DESC_ADDR** - GGTT address of the `CTB Descriptor`_ | + * | 2 | 31:0 | **VALUE32** - Bits 31-0 of the KLV value | * +---+-------+--------------------------------------------------------------+ - * | 3 | 31:0 | **BUFF_ADDF** - GGTT address of the `CT Buffer`_ | + * | 3 | 31:0 | **VALUE64** - Bits 63-32 of the KLV value (**KLV_LEN** = 2) | * +---+-------+--------------------------------------------------------------+ * * +---+-------+--------------------------------------------------------------+ @@ -45,28 +43,25 @@ * | +-------+--------------------------------------------------------------+ * | | 30:28 | TYPE = GUC_HXG_TYPE_RESPONSE_SUCCESS_ | * | +-------+--------------------------------------------------------------+ - * | | 27:0 | DATA0 = MBZ | + * | | 27:0 | DATA0 = **NUM** - 1 if KLV was parsed, 0 if not recognized | * +---+-------+--------------------------------------------------------------+ */ -#define GUC_ACTION_HOST2GUC_REGISTER_CTB 0x4505 +#define GUC_ACTION_HOST2GUC_SELF_CFG 0x0508 -#define HOST2GUC_REGISTER_CTB_REQUEST_MSG_LEN (GUC_HXG_REQUEST_MSG_MIN_LEN + 3u) -#define HOST2GUC_REGISTER_CTB_REQUEST_MSG_0_MBZ GUC_HXG_REQUEST_MSG_0_DATA0 -#define HOST2GUC_REGISTER_CTB_REQUEST_MSG_1_MBZ (0xfffff << 12) -#define HOST2GUC_REGISTER_CTB_REQUEST_MSG_1_TYPE (0xf << 8) -#define GUC_CTB_TYPE_HOST2GUC 0u -#define GUC_CTB_TYPE_GUC2HOST 1u -#define HOST2GUC_REGISTER_CTB_REQUEST_MSG_1_SIZE (0xff << 0) -#define HOST2GUC_REGISTER_CTB_REQUEST_MSG_2_DESC_ADDR GUC_HXG_REQUEST_MSG_n_DATAn -#define HOST2GUC_REGISTER_CTB_REQUEST_MSG_3_BUFF_ADDR GUC_HXG_REQUEST_MSG_n_DATAn +#define HOST2GUC_SELF_CFG_REQUEST_MSG_LEN (GUC_HXG_REQUEST_MSG_MIN_LEN + 3u) +#define HOST2GUC_SELF_CFG_REQUEST_MSG_0_MBZ GUC_HXG_REQUEST_MSG_0_DATA0 +#define HOST2GUC_SELF_CFG_REQUEST_MSG_1_KLV_KEY (0xffff << 16) +#define HOST2GUC_SELF_CFG_REQUEST_MSG_1_KLV_LEN (0xffff << 0) +#define HOST2GUC_SELF_CFG_REQUEST_MSG_2_VALUE32 GUC_HXG_REQUEST_MSG_n_DATAn +#define HOST2GUC_SELF_CFG_REQUEST_MSG_3_VALUE64 GUC_HXG_REQUEST_MSG_n_DATAn -#define HOST2GUC_REGISTER_CTB_RESPONSE_MSG_LEN GUC_HXG_RESPONSE_MSG_MIN_LEN -#define HOST2GUC_REGISTER_CTB_RESPONSE_MSG_0_MBZ GUC_HXG_RESPONSE_MSG_0_DATA0 +#define HOST2GUC_SELF_CFG_RESPONSE_MSG_LEN GUC_HXG_RESPONSE_MSG_MIN_LEN +#define HOST2GUC_SELF_CFG_RESPONSE_MSG_0_NUM GUC_HXG_RESPONSE_MSG_0_DATA0 /** - * DOC: HOST2GUC_DEREGISTER_CTB + * DOC: HOST2GUC_CONTROL_CTB * - * This message is used as part of the `CTB based communication`_ teardown. + * This H2G action allows Vf Host to enable or disable H2G and G2H `CT Buffer`_. * * This message must be sent as `MMIO HXG Message`_. * @@ -79,15 +74,12 @@ * | +-------+--------------------------------------------------------------+ * | | 27:16 | DATA0 = MBZ | * | +-------+--------------------------------------------------------------+ - * | | 15:0 | ACTION = _`GUC_ACTION_HOST2GUC_DEREGISTER_CTB` = 0x4506 | + * | | 15:0 | ACTION = _`GUC_ACTION_HOST2GUC_CONTROL_CTB` = 0x4509 | * +---+-------+--------------------------------------------------------------+ - * | 1 | 31:12 | RESERVED = MBZ | - * | +-------+--------------------------------------------------------------+ - * | | 11:8 | **TYPE** - type of the `CT Buffer`_ | + * | 1 | 31:0 | **CONTROL** - control `CTB based communication`_ | * | | | | - * | | | see `GUC_ACTION_HOST2GUC_REGISTER_CTB`_ | - * | +-------+--------------------------------------------------------------+ - * | | 7:0 | RESERVED = MBZ | + * | | | - _`GUC_CTB_CONTROL_DISABLE` = 0 | + * | | | - _`GUC_CTB_CONTROL_ENABLE` = 1 | * +---+-------+--------------------------------------------------------------+ * * +---+-------+--------------------------------------------------------------+ @@ -100,16 +92,16 @@ * | | 27:0 | DATA0 = MBZ | * +---+-------+--------------------------------------------------------------+ */ -#define GUC_ACTION_HOST2GUC_DEREGISTER_CTB 0x4506 +#define GUC_ACTION_HOST2GUC_CONTROL_CTB 0x4509 -#define HOST2GUC_DEREGISTER_CTB_REQUEST_MSG_LEN (GUC_HXG_REQUEST_MSG_MIN_LEN + 1u) -#define HOST2GUC_DEREGISTER_CTB_REQUEST_MSG_0_MBZ GUC_HXG_REQUEST_MSG_0_DATA0 -#define HOST2GUC_DEREGISTER_CTB_REQUEST_MSG_1_MBZ (0xfffff << 12) -#define HOST2GUC_DEREGISTER_CTB_REQUEST_MSG_1_TYPE (0xf << 8) -#define HOST2GUC_DEREGISTER_CTB_REQUEST_MSG_1_MBZ2 (0xff << 0) +#define HOST2GUC_CONTROL_CTB_REQUEST_MSG_LEN (GUC_HXG_REQUEST_MSG_MIN_LEN + 1u) +#define HOST2GUC_CONTROL_CTB_REQUEST_MSG_0_MBZ GUC_HXG_REQUEST_MSG_0_DATA0 +#define HOST2GUC_CONTROL_CTB_REQUEST_MSG_1_CONTROL GUC_HXG_REQUEST_MSG_n_DATAn +#define GUC_CTB_CONTROL_DISABLE 0u +#define GUC_CTB_CONTROL_ENABLE 1u -#define HOST2GUC_DEREGISTER_CTB_RESPONSE_MSG_LEN GUC_HXG_RESPONSE_MSG_MIN_LEN -#define HOST2GUC_DEREGISTER_CTB_RESPONSE_MSG_0_MBZ GUC_HXG_RESPONSE_MSG_0_DATA0 +#define HOST2GUC_CONTROL_CTB_RESPONSE_MSG_LEN GUC_HXG_RESPONSE_MSG_MIN_LEN +#define HOST2GUC_CONTROL_CTB_RESPONSE_MSG_0_MBZ GUC_HXG_RESPONSE_MSG_0_DATA0 /* legacy definitions */ @@ -143,8 +135,12 @@ enum intel_guc_action { INTEL_GUC_ACTION_DEREGISTER_COMMAND_TRANSPORT_BUFFER = 0x4506, INTEL_GUC_ACTION_DEREGISTER_CONTEXT_DONE = 0x4600, INTEL_GUC_ACTION_REGISTER_CONTEXT_MULTI_LRC = 0x4601, - INTEL_GUC_ACTION_RESET_CLIENT = 0x5507, + INTEL_GUC_ACTION_CLIENT_SOFT_RESET = 0x5507, INTEL_GUC_ACTION_SET_ENG_UTIL_BUFF = 0x550A, + INTEL_GUC_ACTION_STATE_CAPTURE_NOTIFICATION = 0x8002, + INTEL_GUC_ACTION_NOTIFY_FLUSH_LOG_BUFFER_TO_FILE = 0x8003, + INTEL_GUC_ACTION_NOTIFY_CRASH_DUMP_POSTED = 0x8004, + INTEL_GUC_ACTION_NOTIFY_EXCEPTION = 0x8005, INTEL_GUC_ACTION_LIMIT }; diff --git a/drivers/gpu/drm/i915/gt/uc/abi/guc_klvs_abi.h b/drivers/gpu/drm/i915/gt/uc/abi/guc_klvs_abi.h new file mode 100644 index 000000000000..f0814a57c191 --- /dev/null +++ b/drivers/gpu/drm/i915/gt/uc/abi/guc_klvs_abi.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2021 Intel Corporation + */ + +#ifndef _ABI_GUC_KLVS_ABI_H +#define _ABI_GUC_KLVS_ABI_H + +/** + * DOC: GuC KLV + * + * +---+-------+--------------------------------------------------------------+ + * | | Bits | Description | + * +===+=======+==============================================================+ + * | 0 | 31:16 | **KEY** - KLV key identifier | + * | | | - `GuC Self Config KLVs`_ | + * | | | | + * | +-------+--------------------------------------------------------------+ + * | | 15:0 | **LEN** - length of VALUE (in 32bit dwords) | + * +---+-------+--------------------------------------------------------------+ + * | 1 | 31:0 | **VALUE** - actual value of the KLV (format depends on KEY) | + * +---+-------+ | + * |...| | | + * +---+-------+ | + * | n | 31:0 | | + * +---+-------+--------------------------------------------------------------+ + */ + +#define GUC_KLV_LEN_MIN 1u +#define GUC_KLV_0_KEY (0xffff << 16) +#define GUC_KLV_0_LEN (0xffff << 0) +#define GUC_KLV_n_VALUE (0xffffffff << 0) + +/** + * DOC: GuC Self Config KLVs + * + * `GuC KLV`_ keys available for use with HOST2GUC_SELF_CFG_. + * + * _`GUC_KLV_SELF_CFG_H2G_CTB_ADDR` : 0x0902 + * Refers to 64 bit Global Gfx address of H2G `CT Buffer`_. + * Should be above WOPCM address but below APIC base address for native mode. + * + * _`GUC_KLV_SELF_CFG_H2G_CTB_DESCRIPTOR_ADDR` : 0x0903 + * Refers to 64 bit Global Gfx address of H2G `CTB Descriptor`_. + * Should be above WOPCM address but below APIC base address for native mode. + * + * _`GUC_KLV_SELF_CFG_H2G_CTB_SIZE` : 0x0904 + * Refers to size of H2G `CT Buffer`_ in bytes. + * Should be a multiple of 4K. + * + * _`GUC_KLV_SELF_CFG_G2H_CTB_ADDR` : 0x0905 + * Refers to 64 bit Global Gfx address of G2H `CT Buffer`_. + * Should be above WOPCM address but below APIC base address for native mode. + * + * _`GUC_KLV_SELF_CFG_G2H_CTB_DESCRIPTOR_ADDR` : 0x0906 + * Refers to 64 bit Global Gfx address of G2H `CTB Descriptor`_. + * Should be above WOPCM address but below APIC base address for native mode. + * + * _`GUC_KLV_SELF_CFG_G2H_CTB_SIZE` : 0x0907 + * Refers to size of G2H `CT Buffer`_ in bytes. + * Should be a multiple of 4K. + */ + +#define GUC_KLV_SELF_CFG_H2G_CTB_ADDR_KEY 0x0902 +#define GUC_KLV_SELF_CFG_H2G_CTB_ADDR_LEN 2u + +#define GUC_KLV_SELF_CFG_H2G_CTB_DESCRIPTOR_ADDR_KEY 0x0903 +#define GUC_KLV_SELF_CFG_H2G_CTB_DESCRIPTOR_ADDR_LEN 2u + +#define GUC_KLV_SELF_CFG_H2G_CTB_SIZE_KEY 0x0904 +#define GUC_KLV_SELF_CFG_H2G_CTB_SIZE_LEN 1u + +#define GUC_KLV_SELF_CFG_G2H_CTB_ADDR_KEY 0x0905 +#define GUC_KLV_SELF_CFG_G2H_CTB_ADDR_LEN 2u + +#define GUC_KLV_SELF_CFG_G2H_CTB_DESCRIPTOR_ADDR_KEY 0x0906 +#define GUC_KLV_SELF_CFG_G2H_CTB_DESCRIPTOR_ADDR_LEN 2u + +#define GUC_KLV_SELF_CFG_G2H_CTB_SIZE_KEY 0x0907 +#define GUC_KLV_SELF_CFG_G2H_CTB_SIZE_LEN 1u + +#endif /* _ABI_GUC_KLVS_ABI_H */ diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc.c b/drivers/gpu/drm/i915/gt/uc/intel_guc.c index 6e228343e8cb..cbec51f4dec3 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc.c @@ -182,6 +182,9 @@ void intel_guc_init_early(struct intel_guc *guc) guc->send_regs.count = GUC_MAX_MMIO_MSG_LEN; BUILD_BUG_ON(GUC_MAX_MMIO_MSG_LEN > SOFT_SCRATCH_COUNT); } + + intel_guc_enable_msg(guc, INTEL_GUC_RECV_MSG_EXCEPTION | + INTEL_GUC_RECV_MSG_CRASH_DUMP_POSTED); } void intel_guc_init_late(struct intel_guc *guc) @@ -222,32 +225,48 @@ static u32 guc_ctl_log_params_flags(struct intel_guc *guc) u32 flags; #if (((CRASH_BUFFER_SIZE) % SZ_1M) == 0) - #define UNIT SZ_1M - #define FLAG GUC_LOG_ALLOC_IN_MEGABYTE + #define LOG_UNIT SZ_1M + #define LOG_FLAG GUC_LOG_LOG_ALLOC_UNITS #else - #define UNIT SZ_4K - #define FLAG 0 + #define LOG_UNIT SZ_4K + #define LOG_FLAG 0 + #endif + + #if (((CAPTURE_BUFFER_SIZE) % SZ_1M) == 0) + #define CAPTURE_UNIT SZ_1M + #define CAPTURE_FLAG GUC_LOG_CAPTURE_ALLOC_UNITS + #else + #define CAPTURE_UNIT SZ_4K + #define CAPTURE_FLAG 0 #endif BUILD_BUG_ON(!CRASH_BUFFER_SIZE); - BUILD_BUG_ON(!IS_ALIGNED(CRASH_BUFFER_SIZE, UNIT)); + BUILD_BUG_ON(!IS_ALIGNED(CRASH_BUFFER_SIZE, LOG_UNIT)); BUILD_BUG_ON(!DEBUG_BUFFER_SIZE); - BUILD_BUG_ON(!IS_ALIGNED(DEBUG_BUFFER_SIZE, UNIT)); + BUILD_BUG_ON(!IS_ALIGNED(DEBUG_BUFFER_SIZE, LOG_UNIT)); + BUILD_BUG_ON(!CAPTURE_BUFFER_SIZE); + BUILD_BUG_ON(!IS_ALIGNED(CAPTURE_BUFFER_SIZE, CAPTURE_UNIT)); - BUILD_BUG_ON((CRASH_BUFFER_SIZE / UNIT - 1) > + BUILD_BUG_ON((CRASH_BUFFER_SIZE / LOG_UNIT - 1) > (GUC_LOG_CRASH_MASK >> GUC_LOG_CRASH_SHIFT)); - BUILD_BUG_ON((DEBUG_BUFFER_SIZE / UNIT - 1) > + BUILD_BUG_ON((DEBUG_BUFFER_SIZE / LOG_UNIT - 1) > (GUC_LOG_DEBUG_MASK >> GUC_LOG_DEBUG_SHIFT)); + BUILD_BUG_ON((CAPTURE_BUFFER_SIZE / CAPTURE_UNIT - 1) > + (GUC_LOG_CAPTURE_MASK >> GUC_LOG_CAPTURE_SHIFT)); flags = GUC_LOG_VALID | GUC_LOG_NOTIFY_ON_HALF_FULL | - FLAG | - ((CRASH_BUFFER_SIZE / UNIT - 1) << GUC_LOG_CRASH_SHIFT) | - ((DEBUG_BUFFER_SIZE / UNIT - 1) << GUC_LOG_DEBUG_SHIFT) | + CAPTURE_FLAG | + LOG_FLAG | + ((CRASH_BUFFER_SIZE / LOG_UNIT - 1) << GUC_LOG_CRASH_SHIFT) | + ((DEBUG_BUFFER_SIZE / LOG_UNIT - 1) << GUC_LOG_DEBUG_SHIFT) | + ((CAPTURE_BUFFER_SIZE / CAPTURE_UNIT - 1) << GUC_LOG_CAPTURE_SHIFT) | (offset << GUC_LOG_BUF_ADDR_SHIFT); - #undef UNIT - #undef FLAG + #undef LOG_UNIT + #undef LOG_FLAG + #undef CAPTURE_UNIT + #undef CAPTURE_FLAG return flags; } @@ -260,6 +279,26 @@ static u32 guc_ctl_ads_flags(struct intel_guc *guc) return flags; } +static u32 guc_ctl_wa_flags(struct intel_guc *guc) +{ + struct intel_gt *gt = guc_to_gt(guc); + u32 flags = 0; + + /* Wa_22012773006:gen11,gen12 < XeHP */ + if (GRAPHICS_VER(gt->i915) >= 11 && + GRAPHICS_VER_FULL(gt->i915) < IP_VER(12, 50)) + flags |= GUC_WA_POLLCS; + + return flags; +} + +static u32 guc_ctl_devid(struct intel_guc *guc) +{ + struct drm_i915_private *i915 = guc_to_gt(guc)->i915; + + return (INTEL_DEVID(i915) << 16) | INTEL_REVID(i915); +} + /* * Initialise the GuC parameter block before starting the firmware * transfer. These parameters are read by the firmware on startup @@ -276,6 +315,8 @@ static void guc_init_params(struct intel_guc *guc) params[GUC_CTL_FEATURE] = guc_ctl_feature_flags(guc); params[GUC_CTL_DEBUG] = guc_ctl_debug_flags(guc); params[GUC_CTL_ADS] = guc_ctl_ads_flags(guc); + params[GUC_CTL_WA] = guc_ctl_wa_flags(guc); + params[GUC_CTL_DEVID] = guc_ctl_devid(guc); for (i = 0; i < GUC_CTL_MAX_DWORDS; i++) DRM_DEBUG_DRIVER("param[%2d] = %#x\n", i, params[i]); @@ -513,9 +554,10 @@ int intel_guc_to_host_process_recv_msg(struct intel_guc *guc, /* Make sure to handle only enabled messages */ msg = payload[0] & guc->msg_enabled_mask; - if (msg & (INTEL_GUC_RECV_MSG_FLUSH_LOG_BUFFER | - INTEL_GUC_RECV_MSG_CRASH_DUMP_POSTED)) - intel_guc_log_handle_flush_event(&guc->log); + if (msg & INTEL_GUC_RECV_MSG_CRASH_DUMP_POSTED) + drm_err(&guc_to_gt(guc)->i915->drm, "Received early GuC crash dump notification!\n"); + if (msg & INTEL_GUC_RECV_MSG_EXCEPTION) + drm_err(&guc_to_gt(guc)->i915->drm, "Received early GuC exception notification!\n"); return 0; } @@ -549,7 +591,7 @@ int intel_guc_suspend(struct intel_guc *guc) { int ret; u32 action[] = { - INTEL_GUC_ACTION_RESET_CLIENT, + INTEL_GUC_ACTION_CLIENT_SOFT_RESET, }; if (!intel_guc_is_ready(guc)) @@ -713,6 +755,56 @@ int intel_guc_allocate_and_map_vma(struct intel_guc *guc, u32 size, return 0; } +static int __guc_action_self_cfg(struct intel_guc *guc, u16 key, u16 len, u64 value) +{ + u32 request[HOST2GUC_SELF_CFG_REQUEST_MSG_LEN] = { + FIELD_PREP(GUC_HXG_MSG_0_ORIGIN, GUC_HXG_ORIGIN_HOST) | + FIELD_PREP(GUC_HXG_MSG_0_TYPE, GUC_HXG_TYPE_REQUEST) | + FIELD_PREP(GUC_HXG_REQUEST_MSG_0_ACTION, GUC_ACTION_HOST2GUC_SELF_CFG), + FIELD_PREP(HOST2GUC_SELF_CFG_REQUEST_MSG_1_KLV_KEY, key) | + FIELD_PREP(HOST2GUC_SELF_CFG_REQUEST_MSG_1_KLV_LEN, len), + FIELD_PREP(HOST2GUC_SELF_CFG_REQUEST_MSG_2_VALUE32, lower_32_bits(value)), + FIELD_PREP(HOST2GUC_SELF_CFG_REQUEST_MSG_3_VALUE64, upper_32_bits(value)), + }; + int ret; + + GEM_BUG_ON(len > 2); + GEM_BUG_ON(len == 1 && upper_32_bits(value)); + + /* Self config must go over MMIO */ + ret = intel_guc_send_mmio(guc, request, ARRAY_SIZE(request), NULL, 0); + + if (unlikely(ret < 0)) + return ret; + if (unlikely(ret > 1)) + return -EPROTO; + if (unlikely(!ret)) + return -ENOKEY; + + return 0; +} + +static int __guc_self_cfg(struct intel_guc *guc, u16 key, u16 len, u64 value) +{ + struct drm_i915_private *i915 = guc_to_gt(guc)->i915; + int err = __guc_action_self_cfg(guc, key, len, value); + + if (unlikely(err)) + i915_probe_error(i915, "Unsuccessful self-config (%pe) key %#hx value %#llx\n", + ERR_PTR(err), key, value); + return err; +} + +int intel_guc_self_cfg32(struct intel_guc *guc, u16 key, u32 value) +{ + return __guc_self_cfg(guc, key, 1, value); +} + +int intel_guc_self_cfg64(struct intel_guc *guc, u16 key, u64 value) +{ + return __guc_self_cfg(guc, key, 2, value); +} + /** * intel_guc_load_status - dump information about GuC load status * @guc: the GuC diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc.h b/drivers/gpu/drm/i915/gt/uc/intel_guc.h index f9240d4baa69..9d26a86fe557 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc.h +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc.h @@ -328,6 +328,8 @@ int intel_guc_resume(struct intel_guc *guc); struct i915_vma *intel_guc_allocate_vma(struct intel_guc *guc, u32 size); int intel_guc_allocate_and_map_vma(struct intel_guc *guc, u32 size, struct i915_vma **out_vma, void **out_vaddr); +int intel_guc_self_cfg32(struct intel_guc *guc, u16 key, u32 value); +int intel_guc_self_cfg64(struct intel_guc *guc, u16 key, u64 value); static inline bool intel_guc_is_supported(struct intel_guc *guc) { @@ -404,6 +406,8 @@ int intel_guc_context_reset_process_msg(struct intel_guc *guc, const u32 *msg, u32 len); int intel_guc_engine_failure_process_msg(struct intel_guc *guc, const u32 *msg, u32 len); +int intel_guc_error_capture_process_msg(struct intel_guc *guc, + const u32 *msg, u32 len); void intel_guc_find_hung_context(struct intel_engine_cs *engine); diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_ads.c b/drivers/gpu/drm/i915/gt/uc/intel_guc_ads.c index 1a1edae67e4e..aa767540ca2a 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_ads.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_ads.c @@ -40,6 +40,10 @@ * +---------------------------------------+ * | padding | * +---------------------------------------+ <== 4K aligned + * | capture lists | + * +---------------------------------------+ + * | padding | + * +---------------------------------------+ <== 4K aligned * | private data | * +---------------------------------------+ * | padding | @@ -65,6 +69,12 @@ static u32 guc_ads_golden_ctxt_size(struct intel_guc *guc) return PAGE_ALIGN(guc->ads_golden_ctxt_size); } +static u32 guc_ads_capture_size(struct intel_guc *guc) +{ + /* FIXME: Allocate a proper capture list */ + return PAGE_ALIGN(PAGE_SIZE); +} + static u32 guc_ads_private_data_size(struct intel_guc *guc) { return PAGE_ALIGN(guc->fw.private_data_size); @@ -85,7 +95,7 @@ static u32 guc_ads_golden_ctxt_offset(struct intel_guc *guc) return PAGE_ALIGN(offset); } -static u32 guc_ads_private_data_offset(struct intel_guc *guc) +static u32 guc_ads_capture_offset(struct intel_guc *guc) { u32 offset; @@ -95,6 +105,16 @@ static u32 guc_ads_private_data_offset(struct intel_guc *guc) return PAGE_ALIGN(offset); } +static u32 guc_ads_private_data_offset(struct intel_guc *guc) +{ + u32 offset; + + offset = guc_ads_capture_offset(guc) + + guc_ads_capture_size(guc); + + return PAGE_ALIGN(offset); +} + static u32 guc_ads_blob_size(struct intel_guc *guc) { return guc_ads_private_data_offset(guc) + @@ -499,6 +519,26 @@ static void guc_init_golden_context(struct intel_guc *guc) GEM_BUG_ON(guc->ads_golden_ctxt_size != total_size); } +static void guc_capture_list_init(struct intel_guc *guc, struct __guc_ads_blob *blob) +{ + int i, j; + u32 addr_ggtt, offset; + + offset = guc_ads_capture_offset(guc); + addr_ggtt = intel_guc_ggtt_offset(guc, guc->ads_vma) + offset; + + /* FIXME: Populate a proper capture list */ + + for (i = 0; i < GUC_CAPTURE_LIST_INDEX_MAX; i++) { + for (j = 0; j < GUC_MAX_ENGINE_CLASSES; j++) { + blob->ads.capture_instance[i][j] = addr_ggtt; + blob->ads.capture_class[i][j] = addr_ggtt; + } + + blob->ads.capture_global[i] = addr_ggtt; + } +} + static void __guc_ads_init(struct intel_guc *guc) { struct intel_gt *gt = guc_to_gt(guc); @@ -532,6 +572,9 @@ static void __guc_ads_init(struct intel_guc *guc) base = intel_guc_ggtt_offset(guc, guc->ads_vma); + /* Capture list for hang debug */ + guc_capture_list_init(guc, blob); + /* ADS */ blob->ads.scheduler_policies = base + ptr_offset(blob, policies); blob->ads.gt_system_info = base + ptr_offset(blob, system_info); diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_ct.c b/drivers/gpu/drm/i915/gt/uc/intel_guc_ct.c index aa6dd6415202..de89d40abd38 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_ct.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_ct.c @@ -112,18 +112,6 @@ void intel_guc_ct_init_early(struct intel_guc_ct *ct) init_waitqueue_head(&ct->wq); } -static inline const char *guc_ct_buffer_type_to_str(u32 type) -{ - switch (type) { - case GUC_CTB_TYPE_HOST2GUC: - return "SEND"; - case GUC_CTB_TYPE_GUC2HOST: - return "RECV"; - default: - return ""; - } -} - static void guc_ct_buffer_desc_init(struct guc_ct_buffer_desc *desc) { memset(desc, 0, sizeof(*desc)); @@ -156,71 +144,65 @@ static void guc_ct_buffer_init(struct intel_guc_ct_buffer *ctb, guc_ct_buffer_reset(ctb); } -static int guc_action_register_ct_buffer(struct intel_guc *guc, u32 type, - u32 desc_addr, u32 buff_addr, u32 size) +static int guc_action_control_ctb(struct intel_guc *guc, u32 control) { - u32 request[HOST2GUC_REGISTER_CTB_REQUEST_MSG_LEN] = { + u32 request[HOST2GUC_CONTROL_CTB_REQUEST_MSG_LEN] = { FIELD_PREP(GUC_HXG_MSG_0_ORIGIN, GUC_HXG_ORIGIN_HOST) | FIELD_PREP(GUC_HXG_MSG_0_TYPE, GUC_HXG_TYPE_REQUEST) | - FIELD_PREP(GUC_HXG_REQUEST_MSG_0_ACTION, GUC_ACTION_HOST2GUC_REGISTER_CTB), - FIELD_PREP(HOST2GUC_REGISTER_CTB_REQUEST_MSG_1_SIZE, size / SZ_4K - 1) | - FIELD_PREP(HOST2GUC_REGISTER_CTB_REQUEST_MSG_1_TYPE, type), - FIELD_PREP(HOST2GUC_REGISTER_CTB_REQUEST_MSG_2_DESC_ADDR, desc_addr), - FIELD_PREP(HOST2GUC_REGISTER_CTB_REQUEST_MSG_3_BUFF_ADDR, buff_addr), + FIELD_PREP(GUC_HXG_REQUEST_MSG_0_ACTION, GUC_ACTION_HOST2GUC_CONTROL_CTB), + FIELD_PREP(HOST2GUC_CONTROL_CTB_REQUEST_MSG_1_CONTROL, control), }; int ret; - GEM_BUG_ON(type != GUC_CTB_TYPE_HOST2GUC && type != GUC_CTB_TYPE_GUC2HOST); - GEM_BUG_ON(size % SZ_4K); + GEM_BUG_ON(control != GUC_CTB_CONTROL_DISABLE && control != GUC_CTB_CONTROL_ENABLE); - /* CT registration must go over MMIO */ + /* CT control must go over MMIO */ ret = intel_guc_send_mmio(guc, request, ARRAY_SIZE(request), NULL, 0); return ret > 0 ? -EPROTO : ret; } -static int ct_register_buffer(struct intel_guc_ct *ct, u32 type, - u32 desc_addr, u32 buff_addr, u32 size) +static int ct_control_enable(struct intel_guc_ct *ct, bool enable) { int err; - err = i915_inject_probe_error(guc_to_gt(ct_to_guc(ct))->i915, -ENXIO); + err = guc_action_control_ctb(ct_to_guc(ct), enable ? + GUC_CTB_CONTROL_ENABLE : GUC_CTB_CONTROL_DISABLE); if (unlikely(err)) - return err; + CT_PROBE_ERROR(ct, "Failed to control/%s CTB (%pe)\n", + enabledisable(enable), ERR_PTR(err)); - err = guc_action_register_ct_buffer(ct_to_guc(ct), type, - desc_addr, buff_addr, size); - if (unlikely(err)) - CT_ERROR(ct, "Failed to register %s buffer (%pe)\n", - guc_ct_buffer_type_to_str(type), ERR_PTR(err)); return err; } -static int guc_action_deregister_ct_buffer(struct intel_guc *guc, u32 type) +static int ct_register_buffer(struct intel_guc_ct *ct, bool send, + u32 desc_addr, u32 buff_addr, u32 size) { - u32 request[HOST2GUC_DEREGISTER_CTB_REQUEST_MSG_LEN] = { - FIELD_PREP(GUC_HXG_MSG_0_ORIGIN, GUC_HXG_ORIGIN_HOST) | - FIELD_PREP(GUC_HXG_MSG_0_TYPE, GUC_HXG_TYPE_REQUEST) | - FIELD_PREP(GUC_HXG_REQUEST_MSG_0_ACTION, GUC_ACTION_HOST2GUC_DEREGISTER_CTB), - FIELD_PREP(HOST2GUC_DEREGISTER_CTB_REQUEST_MSG_1_TYPE, type), - }; - int ret; - - GEM_BUG_ON(type != GUC_CTB_TYPE_HOST2GUC && type != GUC_CTB_TYPE_GUC2HOST); - - /* CT deregistration must go over MMIO */ - ret = intel_guc_send_mmio(guc, request, ARRAY_SIZE(request), NULL, 0); + int err; - return ret > 0 ? -EPROTO : ret; -} + err = intel_guc_self_cfg64(ct_to_guc(ct), send ? + GUC_KLV_SELF_CFG_H2G_CTB_DESCRIPTOR_ADDR_KEY : + GUC_KLV_SELF_CFG_G2H_CTB_DESCRIPTOR_ADDR_KEY, + desc_addr); + if (unlikely(err)) + goto failed; -static int ct_deregister_buffer(struct intel_guc_ct *ct, u32 type) -{ - int err = guc_action_deregister_ct_buffer(ct_to_guc(ct), type); + err = intel_guc_self_cfg64(ct_to_guc(ct), send ? + GUC_KLV_SELF_CFG_H2G_CTB_ADDR_KEY : + GUC_KLV_SELF_CFG_G2H_CTB_ADDR_KEY, + buff_addr); + if (unlikely(err)) + goto failed; + err = intel_guc_self_cfg32(ct_to_guc(ct), send ? + GUC_KLV_SELF_CFG_H2G_CTB_SIZE_KEY : + GUC_KLV_SELF_CFG_G2H_CTB_SIZE_KEY, + size); if (unlikely(err)) - CT_ERROR(ct, "Failed to deregister %s buffer (%pe)\n", - guc_ct_buffer_type_to_str(type), ERR_PTR(err)); +failed: + CT_PROBE_ERROR(ct, "Failed to register %s buffer (%pe)\n", + send ? "SEND" : "RECV", ERR_PTR(err)); + return err; } @@ -308,7 +290,7 @@ void intel_guc_ct_fini(struct intel_guc_ct *ct) int intel_guc_ct_enable(struct intel_guc_ct *ct) { struct intel_guc *guc = ct_to_guc(ct); - u32 base, desc, cmds; + u32 base, desc, cmds, size; void *blob; int err; @@ -333,27 +315,27 @@ int intel_guc_ct_enable(struct intel_guc_ct *ct) */ desc = base + ptrdiff(ct->ctbs.recv.desc, blob); cmds = base + ptrdiff(ct->ctbs.recv.cmds, blob); - err = ct_register_buffer(ct, GUC_CTB_TYPE_GUC2HOST, - desc, cmds, ct->ctbs.recv.size * 4); - + size = ct->ctbs.recv.size * 4; + err = ct_register_buffer(ct, false, desc, cmds, size); if (unlikely(err)) goto err_out; desc = base + ptrdiff(ct->ctbs.send.desc, blob); cmds = base + ptrdiff(ct->ctbs.send.cmds, blob); - err = ct_register_buffer(ct, GUC_CTB_TYPE_HOST2GUC, - desc, cmds, ct->ctbs.send.size * 4); + size = ct->ctbs.send.size * 4; + err = ct_register_buffer(ct, true, desc, cmds, size); + if (unlikely(err)) + goto err_out; + err = ct_control_enable(ct, true); if (unlikely(err)) - goto err_deregister; + goto err_out; ct->enabled = true; ct->stall_time = KTIME_MAX; return 0; -err_deregister: - ct_deregister_buffer(ct, GUC_CTB_TYPE_GUC2HOST); err_out: CT_PROBE_ERROR(ct, "Failed to enable CTB (%pe)\n", ERR_PTR(err)); return err; @@ -372,8 +354,7 @@ void intel_guc_ct_disable(struct intel_guc_ct *ct) ct->enabled = false; if (intel_guc_is_fw_running(guc)) { - ct_deregister_buffer(ct, GUC_CTB_TYPE_HOST2GUC); - ct_deregister_buffer(ct, GUC_CTB_TYPE_GUC2HOST); + ct_control_enable(ct, false); } } @@ -662,6 +643,7 @@ static int ct_send(struct intel_guc_ct *ct, struct ct_request request; unsigned long flags; unsigned int sleep_period_ms = 1; + bool send_again; u32 fence; int err; @@ -671,6 +653,9 @@ static int ct_send(struct intel_guc_ct *ct, GEM_BUG_ON(!response_buf && response_buf_size); might_sleep(); +resend: + send_again = false; + /* * We use a lazy spin wait loop here as we believe that if the CT * buffers are sized correctly the flow control condition should be @@ -725,6 +710,13 @@ retry: goto unlink; } + if (FIELD_GET(GUC_HXG_MSG_0_TYPE, *status) == GUC_HXG_TYPE_NO_RESPONSE_RETRY) { + CT_DEBUG(ct, "retrying request %#x (%u)\n", *action, + FIELD_GET(GUC_HXG_RETRY_MSG_0_REASON, *status)); + send_again = true; + goto unlink; + } + if (FIELD_GET(GUC_HXG_MSG_0_TYPE, *status) != GUC_HXG_TYPE_RESPONSE_SUCCESS) { err = -EIO; goto unlink; @@ -747,6 +739,9 @@ unlink: list_del(&request.link); spin_unlock_irqrestore(&ct->requests.lock, flags); + if (unlikely(send_again)) + goto resend; + return err; } @@ -918,6 +913,7 @@ static int ct_handle_response(struct intel_guc_ct *ct, struct ct_incoming_msg *r GEM_BUG_ON(len < GUC_HXG_MSG_MIN_LEN); GEM_BUG_ON(FIELD_GET(GUC_HXG_MSG_0_ORIGIN, hxg[0]) != GUC_HXG_ORIGIN_GUC); GEM_BUG_ON(FIELD_GET(GUC_HXG_MSG_0_TYPE, hxg[0]) != GUC_HXG_TYPE_RESPONSE_SUCCESS && + FIELD_GET(GUC_HXG_MSG_0_TYPE, hxg[0]) != GUC_HXG_TYPE_NO_RESPONSE_RETRY && FIELD_GET(GUC_HXG_MSG_0_TYPE, hxg[0]) != GUC_HXG_TYPE_RESPONSE_FAILURE); CT_DEBUG(ct, "response fence %u status %#x\n", fence, hxg[0]); @@ -990,9 +986,27 @@ static int ct_process_request(struct intel_guc_ct *ct, struct ct_incoming_msg *r case INTEL_GUC_ACTION_CONTEXT_RESET_NOTIFICATION: ret = intel_guc_context_reset_process_msg(guc, payload, len); break; + case INTEL_GUC_ACTION_STATE_CAPTURE_NOTIFICATION: + ret = intel_guc_error_capture_process_msg(guc, payload, len); + if (unlikely(ret)) + CT_ERROR(ct, "error capture notification failed %x %*ph\n", + action, 4 * len, payload); + break; case INTEL_GUC_ACTION_ENGINE_FAILURE_NOTIFICATION: ret = intel_guc_engine_failure_process_msg(guc, payload, len); break; + case INTEL_GUC_ACTION_NOTIFY_FLUSH_LOG_BUFFER_TO_FILE: + intel_guc_log_handle_flush_event(&guc->log); + ret = 0; + break; + case INTEL_GUC_ACTION_NOTIFY_CRASH_DUMP_POSTED: + CT_ERROR(ct, "Received GuC crash dump notification!\n"); + ret = 0; + break; + case INTEL_GUC_ACTION_NOTIFY_EXCEPTION: + CT_ERROR(ct, "Received GuC exception notification!\n"); + ret = 0; + break; default: ret = -EOPNOTSUPP; break; @@ -1098,6 +1112,7 @@ static int ct_handle_hxg(struct intel_guc_ct *ct, struct ct_incoming_msg *msg) break; case GUC_HXG_TYPE_RESPONSE_SUCCESS: case GUC_HXG_TYPE_RESPONSE_FAILURE: + case GUC_HXG_TYPE_NO_RESPONSE_RETRY: err = ct_handle_response(ct, msg); break; default: diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_fwif.h b/drivers/gpu/drm/i915/gt/uc/intel_guc_fwif.h index 7072e30e99f4..6a4612a852e2 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_fwif.h +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_fwif.h @@ -16,6 +16,7 @@ #include "abi/guc_errors_abi.h" #include "abi/guc_communication_mmio_abi.h" #include "abi/guc_communication_ctb_abi.h" +#include "abi/guc_klvs_abi.h" #include "abi/guc_messages_abi.h" /* Payload length only i.e. don't include G2H header length */ @@ -84,19 +85,24 @@ #define GUC_STAGE_DESC_ATTR_TERMINATED BIT(7) #define GUC_CTL_LOG_PARAMS 0 -#define GUC_LOG_VALID (1 << 0) -#define GUC_LOG_NOTIFY_ON_HALF_FULL (1 << 1) -#define GUC_LOG_ALLOC_IN_MEGABYTE (1 << 3) +#define GUC_LOG_VALID BIT(0) +#define GUC_LOG_NOTIFY_ON_HALF_FULL BIT(1) +#define GUC_LOG_CAPTURE_ALLOC_UNITS BIT(2) +#define GUC_LOG_LOG_ALLOC_UNITS BIT(3) #define GUC_LOG_CRASH_SHIFT 4 #define GUC_LOG_CRASH_MASK (0x3 << GUC_LOG_CRASH_SHIFT) #define GUC_LOG_DEBUG_SHIFT 6 #define GUC_LOG_DEBUG_MASK (0xF << GUC_LOG_DEBUG_SHIFT) +#define GUC_LOG_CAPTURE_SHIFT 10 +#define GUC_LOG_CAPTURE_MASK (0x3 << GUC_LOG_CAPTURE_SHIFT) #define GUC_LOG_BUF_ADDR_SHIFT 12 #define GUC_CTL_WA 1 +#define GUC_WA_POLLCS BIT(18) + #define GUC_CTL_FEATURE 2 -#define GUC_CTL_DISABLE_SCHEDULER (1 << 14) #define GUC_CTL_ENABLE_SLPC BIT(2) +#define GUC_CTL_DISABLE_SCHEDULER BIT(14) #define GUC_CTL_DEBUG 3 #define GUC_LOG_VERBOSITY_SHIFT 0 @@ -116,6 +122,8 @@ #define GUC_ADS_ADDR_SHIFT 1 #define GUC_ADS_ADDR_MASK (0xFFFFF << GUC_ADS_ADDR_SHIFT) +#define GUC_CTL_DEVID 5 + #define GUC_CTL_MAX_DWORDS (SOFT_SCRATCH_COUNT - 2) /* [1..14] */ /* Generic GT SysInfo data types */ @@ -263,7 +271,10 @@ struct guc_mmio_reg { u32 offset; u32 value; u32 flags; -#define GUC_REGSET_MASKED (1 << 0) + u32 mask; +#define GUC_REGSET_MASKED BIT(0) +#define GUC_REGSET_MASKED_WITH_VALUE BIT(2) +#define GUC_REGSET_RESTORE_ONLY BIT(3) } __packed; /* GuC register sets */ @@ -280,6 +291,12 @@ struct guc_gt_system_info { u32 generic_gt_sysinfo[GUC_GENERIC_GT_SYSINFO_MAX]; } __packed; +enum { + GUC_CAPTURE_LIST_INDEX_PF = 0, + GUC_CAPTURE_LIST_INDEX_VF = 1, + GUC_CAPTURE_LIST_INDEX_MAX = 2, +}; + /* GuC Additional Data Struct */ struct guc_ads { struct guc_mmio_reg_set reg_state_list[GUC_MAX_ENGINE_CLASSES][GUC_MAX_INSTANCES_PER_CLASS]; @@ -291,7 +308,11 @@ struct guc_ads { u32 golden_context_lrca[GUC_MAX_ENGINE_CLASSES]; u32 eng_state_size[GUC_MAX_ENGINE_CLASSES]; u32 private_data; - u32 reserved[15]; + u32 reserved2; + u32 capture_instance[GUC_CAPTURE_LIST_INDEX_MAX][GUC_MAX_ENGINE_CLASSES]; + u32 capture_class[GUC_CAPTURE_LIST_INDEX_MAX][GUC_MAX_ENGINE_CLASSES]; + u32 capture_global[GUC_CAPTURE_LIST_INDEX_MAX]; + u32 reserved[14]; } __packed; /* Engine usage stats */ @@ -312,6 +333,7 @@ struct guc_engine_usage { enum guc_log_buffer_type { GUC_DEBUG_LOG_BUFFER, GUC_CRASH_DUMP_LOG_BUFFER, + GUC_CAPTURE_LOG_BUFFER, GUC_MAX_LOG_BUFFER }; @@ -342,6 +364,7 @@ struct guc_log_buffer_state { u32 write_ptr; u32 size; u32 sampled_write_ptr; + u32 wrap_offset; union { struct { u32 flush_to_file:1; @@ -382,7 +405,7 @@ struct guc_shared_ctx_data { /* This action will be programmed in C1BC - SOFT_SCRATCH_15_REG */ enum intel_guc_recv_message { INTEL_GUC_RECV_MSG_CRASH_DUMP_POSTED = BIT(1), - INTEL_GUC_RECV_MSG_FLUSH_LOG_BUFFER = BIT(3) + INTEL_GUC_RECV_MSG_EXCEPTION = BIT(30), }; #endif diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_log.c b/drivers/gpu/drm/i915/gt/uc/intel_guc_log.c index ac0931f0374b..be35f0570396 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_log.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_log.c @@ -55,20 +55,6 @@ static int guc_action_control_log(struct intel_guc *guc, bool enable, return intel_guc_send(guc, action, ARRAY_SIZE(action)); } -static void guc_log_enable_flush_events(struct intel_guc_log *log) -{ - intel_guc_enable_msg(log_to_guc(log), - INTEL_GUC_RECV_MSG_FLUSH_LOG_BUFFER | - INTEL_GUC_RECV_MSG_CRASH_DUMP_POSTED); -} - -static void guc_log_disable_flush_events(struct intel_guc_log *log) -{ - intel_guc_disable_msg(log_to_guc(log), - INTEL_GUC_RECV_MSG_FLUSH_LOG_BUFFER | - INTEL_GUC_RECV_MSG_CRASH_DUMP_POSTED); -} - /* * Sub buffer switch callback. Called whenever relay has to switch to a new * sub buffer, relay stays on the same sub buffer if 0 is returned. @@ -201,6 +187,8 @@ static unsigned int guc_get_log_buffer_size(enum guc_log_buffer_type type) return DEBUG_BUFFER_SIZE; case GUC_CRASH_DUMP_LOG_BUFFER: return CRASH_BUFFER_SIZE; + case GUC_CAPTURE_LOG_BUFFER: + return CAPTURE_BUFFER_SIZE; default: MISSING_CASE(type); } @@ -463,14 +451,19 @@ int intel_guc_log_create(struct intel_guc_log *log) * +-------------------------------+ 32B * | Debug state header | * +-------------------------------+ 64B + * | Capture state header | + * +-------------------------------+ 96B * | | * +===============================+ PAGE_SIZE (4KB) * | Crash Dump logs | * +===============================+ + CRASH_SIZE * | Debug logs | * +===============================+ + DEBUG_SIZE + * | Capture logs | + * +===============================+ + CAPTURE_SIZE */ - guc_log_size = PAGE_SIZE + CRASH_BUFFER_SIZE + DEBUG_BUFFER_SIZE; + guc_log_size = PAGE_SIZE + CRASH_BUFFER_SIZE + DEBUG_BUFFER_SIZE + + CAPTURE_BUFFER_SIZE; vma = intel_guc_allocate_vma(guc, guc_log_size); if (IS_ERR(vma)) { @@ -592,8 +585,6 @@ int intel_guc_log_relay_start(struct intel_guc_log *log) if (log->relay.started) return -EEXIST; - guc_log_enable_flush_events(log); - /* * When GuC is logging without us relaying to userspace, we're ignoring * the flush notification. This means that we need to unconditionally @@ -640,7 +631,6 @@ static void guc_log_relay_stop(struct intel_guc_log *log) if (!log->relay.started) return; - guc_log_disable_flush_events(log); intel_synchronize_irq(i915); flush_work(&log->relay.flush_work); @@ -661,7 +651,8 @@ void intel_guc_log_relay_close(struct intel_guc_log *log) void intel_guc_log_handle_flush_event(struct intel_guc_log *log) { - queue_work(system_highpri_wq, &log->relay.flush_work); + if (log->relay.started) + queue_work(system_highpri_wq, &log->relay.flush_work); } static const char * @@ -672,6 +663,8 @@ stringify_guc_log_type(enum guc_log_buffer_type type) return "DEBUG"; case GUC_CRASH_DUMP_LOG_BUFFER: return "CRASH"; + case GUC_CAPTURE_LOG_BUFFER: + return "CAPTURE"; default: MISSING_CASE(type); } diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_log.h b/drivers/gpu/drm/i915/gt/uc/intel_guc_log.h index fe6ab7550a14..d7e1b6471fed 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_log.h +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_log.h @@ -18,12 +18,15 @@ struct intel_guc; #if defined(CONFIG_DRM_I915_DEBUG_GUC) #define CRASH_BUFFER_SIZE SZ_2M #define DEBUG_BUFFER_SIZE SZ_16M +#define CAPTURE_BUFFER_SIZE SZ_4M #elif defined(CONFIG_DRM_I915_DEBUG_GEM) #define CRASH_BUFFER_SIZE SZ_1M #define DEBUG_BUFFER_SIZE SZ_2M +#define CAPTURE_BUFFER_SIZE SZ_1M #else #define CRASH_BUFFER_SIZE SZ_8K #define DEBUG_BUFFER_SIZE SZ_64K +#define CAPTURE_BUFFER_SIZE SZ_16K #endif /* diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c index 9989d121127d..c13f123ae16e 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c @@ -4021,6 +4021,24 @@ int intel_guc_context_reset_process_msg(struct intel_guc *guc, return 0; } +int intel_guc_error_capture_process_msg(struct intel_guc *guc, + const u32 *msg, u32 len) +{ + int status; + + if (unlikely(len != 1)) { + drm_dbg(&guc_to_gt(guc)->i915->drm, "Invalid length %u", len); + return -EPROTO; + } + + status = msg[0]; + drm_info(&guc_to_gt(guc)->i915->drm, "Got error capture: status = %d", status); + + /* FIXME: Do something with the capture */ + + return 0; +} + static struct intel_engine_cs * guc_lookup_engine(struct intel_guc *guc, u8 guc_class, u8 instance) { diff --git a/drivers/gpu/drm/i915/gt/uc/intel_uc_fw.c b/drivers/gpu/drm/i915/gt/uc/intel_uc_fw.c index 777fc6f0ceff..62db14d41042 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_uc_fw.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_uc_fw.c @@ -49,21 +49,21 @@ void intel_uc_fw_change_status(struct intel_uc_fw *uc_fw, * firmware as TGL. */ #define INTEL_GUC_FIRMWARE_DEFS(fw_def, guc_def) \ - fw_def(ALDERLAKE_P, 0, guc_def(adlp, 62, 0, 3)) \ - fw_def(ALDERLAKE_S, 0, guc_def(tgl, 62, 0, 0)) \ - fw_def(DG1, 0, guc_def(dg1, 62, 0, 0)) \ - fw_def(ROCKETLAKE, 0, guc_def(tgl, 62, 0, 0)) \ - fw_def(TIGERLAKE, 0, guc_def(tgl, 62, 0, 0)) \ - fw_def(JASPERLAKE, 0, guc_def(ehl, 62, 0, 0)) \ - fw_def(ELKHARTLAKE, 0, guc_def(ehl, 62, 0, 0)) \ - fw_def(ICELAKE, 0, guc_def(icl, 62, 0, 0)) \ - fw_def(COMETLAKE, 5, guc_def(cml, 62, 0, 0)) \ - fw_def(COMETLAKE, 0, guc_def(kbl, 62, 0, 0)) \ - fw_def(COFFEELAKE, 0, guc_def(kbl, 62, 0, 0)) \ - fw_def(GEMINILAKE, 0, guc_def(glk, 62, 0, 0)) \ - fw_def(KABYLAKE, 0, guc_def(kbl, 62, 0, 0)) \ - fw_def(BROXTON, 0, guc_def(bxt, 62, 0, 0)) \ - fw_def(SKYLAKE, 0, guc_def(skl, 62, 0, 0)) + fw_def(ALDERLAKE_P, 0, guc_def(adlp, 69, 0, 3)) \ + fw_def(ALDERLAKE_S, 0, guc_def(tgl, 69, 0, 3)) \ + fw_def(DG1, 0, guc_def(dg1, 69, 0, 3)) \ + fw_def(ROCKETLAKE, 0, guc_def(tgl, 69, 0, 3)) \ + fw_def(TIGERLAKE, 0, guc_def(tgl, 69, 0, 3)) \ + fw_def(JASPERLAKE, 0, guc_def(ehl, 69, 0, 3)) \ + fw_def(ELKHARTLAKE, 0, guc_def(ehl, 69, 0, 3)) \ + fw_def(ICELAKE, 0, guc_def(icl, 69, 0, 3)) \ + fw_def(COMETLAKE, 5, guc_def(cml, 69, 0, 3)) \ + fw_def(COMETLAKE, 0, guc_def(kbl, 69, 0, 3)) \ + fw_def(COFFEELAKE, 0, guc_def(kbl, 69, 0, 3)) \ + fw_def(GEMINILAKE, 0, guc_def(glk, 69, 0, 3)) \ + fw_def(KABYLAKE, 0, guc_def(kbl, 69, 0, 3)) \ + fw_def(BROXTON, 0, guc_def(bxt, 69, 0, 3)) \ + fw_def(SKYLAKE, 0, guc_def(skl, 69, 0, 3)) #define INTEL_HUC_FIRMWARE_DEFS(fw_def, huc_def) \ fw_def(ALDERLAKE_P, 0, huc_def(tgl, 7, 9, 3)) \ -- cgit From afd088ac05f120d22e06f8404b2672f5841d0da0 Mon Sep 17 00:00:00 2001 From: John Harrison Date: Thu, 6 Jan 2022 16:06:22 -0800 Subject: drm/i915/guc: Improve GuC loading status check/error reports If the GuC fails to load, it is useful to know what firmware file / version was attempted. So move the version info report to before the load attempt rather than only after a successful load. If the GuC does fail to load, then make the error messages visible rather than being 'debug' prints that do not appears in dmesg output by default. When waiting for the GuC to load, it used to be necessary to check for two different states - READY and (LAPIC_DONE | MIA_CORE). Apparently the second signified init complete on RC6 exit. However, in more recent GuC versions the RC6 exit sequence now finishes with status READY as well. So the test can be simplified. Also, add an enum giving all the current status codes that GuC loading can report as a reference without having to pull and search through the GuC source files. Signed-off-by: John Harrison Reviewed-by: Matthew Brost Link: https://patchwork.freedesktop.org/patch/msgid/20220107000622.292081-4-John.C.Harrison@Intel.com --- drivers/gpu/drm/i915/gt/uc/abi/guc_errors_abi.h | 23 ++++++++++++++++++ drivers/gpu/drm/i915/gt/uc/intel_guc_fw.c | 17 +++++++------- drivers/gpu/drm/i915/gt/uc/intel_guc_reg.h | 4 ---- drivers/gpu/drm/i915/gt/uc/intel_huc.c | 1 + drivers/gpu/drm/i915/gt/uc/intel_uc.c | 31 +++++++++++++------------ 5 files changed, 48 insertions(+), 28 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/uc/abi/guc_errors_abi.h b/drivers/gpu/drm/i915/gt/uc/abi/guc_errors_abi.h index 488b6061ee89..c20658ee85a5 100644 --- a/drivers/gpu/drm/i915/gt/uc/abi/guc_errors_abi.h +++ b/drivers/gpu/drm/i915/gt/uc/abi/guc_errors_abi.h @@ -11,4 +11,27 @@ enum intel_guc_response_status { INTEL_GUC_RESPONSE_STATUS_GENERIC_FAIL = 0xF000, }; +enum intel_guc_load_status { + INTEL_GUC_LOAD_STATUS_DEFAULT = 0x00, + INTEL_GUC_LOAD_STATUS_START = 0x01, + INTEL_GUC_LOAD_STATUS_ERROR_DEVID_BUILD_MISMATCH = 0x02, + INTEL_GUC_LOAD_STATUS_GUC_PREPROD_BUILD_MISMATCH = 0x03, + INTEL_GUC_LOAD_STATUS_ERROR_DEVID_INVALID_GUCTYPE = 0x04, + INTEL_GUC_LOAD_STATUS_GDT_DONE = 0x10, + INTEL_GUC_LOAD_STATUS_IDT_DONE = 0x20, + INTEL_GUC_LOAD_STATUS_LAPIC_DONE = 0x30, + INTEL_GUC_LOAD_STATUS_GUCINT_DONE = 0x40, + INTEL_GUC_LOAD_STATUS_DPC_READY = 0x50, + INTEL_GUC_LOAD_STATUS_DPC_ERROR = 0x60, + INTEL_GUC_LOAD_STATUS_EXCEPTION = 0x70, + INTEL_GUC_LOAD_STATUS_INIT_DATA_INVALID = 0x71, + INTEL_GUC_LOAD_STATUS_PXP_TEARDOWN_CTRL_ENABLED = 0x72, + INTEL_GUC_LOAD_STATUS_INVALID_INIT_DATA_RANGE_START, + INTEL_GUC_LOAD_STATUS_MPU_DATA_INVALID = 0x73, + INTEL_GUC_LOAD_STATUS_INIT_MMIO_SAVE_RESTORE_INVALID = 0x74, + INTEL_GUC_LOAD_STATUS_INVALID_INIT_DATA_RANGE_END, + + INTEL_GUC_LOAD_STATUS_READY = 0xF0, +}; + #endif /* _ABI_GUC_ERRORS_ABI_H */ diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_fw.c b/drivers/gpu/drm/i915/gt/uc/intel_guc_fw.c index d09c205b2beb..f773e7f35bc1 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_fw.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_fw.c @@ -90,11 +90,10 @@ static int guc_xfer_rsa(struct intel_uc_fw *guc_fw, static inline bool guc_ready(struct intel_uncore *uncore, u32 *status) { u32 val = intel_uncore_read(uncore, GUC_STATUS); - u32 uk_val = val & GS_UKERNEL_MASK; + u32 uk_val = REG_FIELD_GET(GS_UKERNEL_MASK, val); *status = val; - return (uk_val == GS_UKERNEL_READY) || - ((val & GS_MIA_CORE_STATE) && (uk_val == GS_UKERNEL_LAPIC_DONE)); + return uk_val == INTEL_GUC_LOAD_STATUS_READY; } static int guc_wait_ucode(struct intel_uncore *uncore) @@ -123,8 +122,8 @@ static int guc_wait_ucode(struct intel_uncore *uncore) if (ret) { struct drm_device *drm = &uncore->i915->drm; - drm_dbg(drm, "GuC load failed: status = 0x%08X\n", status); - drm_dbg(drm, "GuC load failed: status: Reset = %d, " + drm_info(drm, "GuC load failed: status = 0x%08X\n", status); + drm_info(drm, "GuC load failed: status: Reset = %d, " "BootROM = 0x%02X, UKernel = 0x%02X, " "MIA = 0x%02X, Auth = 0x%02X\n", REG_FIELD_GET(GS_MIA_IN_RESET, status), @@ -134,13 +133,13 @@ static int guc_wait_ucode(struct intel_uncore *uncore) REG_FIELD_GET(GS_AUTH_STATUS_MASK, status)); if ((status & GS_BOOTROM_MASK) == GS_BOOTROM_RSA_FAILED) { - drm_dbg(drm, "GuC firmware signature verification failed\n"); + drm_info(drm, "GuC firmware signature verification failed\n"); ret = -ENOEXEC; } - if ((status & GS_UKERNEL_MASK) == GS_UKERNEL_EXCEPTION) { - drm_dbg(drm, "GuC firmware exception. EIP: %#x\n", - intel_uncore_read(uncore, SOFT_SCRATCH(13))); + if (REG_FIELD_GET(GS_UKERNEL_MASK, status) == INTEL_GUC_LOAD_STATUS_EXCEPTION) { + drm_info(drm, "GuC firmware exception. EIP: %#x\n", + intel_uncore_read(uncore, SOFT_SCRATCH(13))); ret = -ENXIO; } } diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_reg.h b/drivers/gpu/drm/i915/gt/uc/intel_guc_reg.h index b37fc2ffaef2..e6bd66d6ce5a 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_reg.h +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_reg.h @@ -22,10 +22,6 @@ #define GS_BOOTROM_JUMP_PASSED (0x76 << GS_BOOTROM_SHIFT) #define GS_UKERNEL_SHIFT 8 #define GS_UKERNEL_MASK (0xFF << GS_UKERNEL_SHIFT) -#define GS_UKERNEL_LAPIC_DONE (0x30 << GS_UKERNEL_SHIFT) -#define GS_UKERNEL_DPC_ERROR (0x60 << GS_UKERNEL_SHIFT) -#define GS_UKERNEL_EXCEPTION (0x70 << GS_UKERNEL_SHIFT) -#define GS_UKERNEL_READY (0xF0 << GS_UKERNEL_SHIFT) #define GS_MIA_SHIFT 16 #define GS_MIA_MASK (0x07 << GS_MIA_SHIFT) #define GS_MIA_CORE_STATE (0x01 << GS_MIA_SHIFT) diff --git a/drivers/gpu/drm/i915/gt/uc/intel_huc.c b/drivers/gpu/drm/i915/gt/uc/intel_huc.c index d10b227ac4aa..556829de9c17 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_huc.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_huc.c @@ -124,6 +124,7 @@ int intel_huc_auth(struct intel_huc *huc) } intel_uc_fw_change_status(&huc->fw, INTEL_UC_FIRMWARE_RUNNING); + drm_info(>->i915->drm, "HuC authenticated\n"); return 0; fail: diff --git a/drivers/gpu/drm/i915/gt/uc/intel_uc.c b/drivers/gpu/drm/i915/gt/uc/intel_uc.c index 3693c4e7dad0..8a188232b6aa 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_uc.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_uc.c @@ -432,6 +432,15 @@ static int __uc_check_hw(struct intel_uc *uc) return 0; } +static void print_fw_ver(struct intel_uc *uc, struct intel_uc_fw *fw) +{ + struct drm_i915_private *i915 = uc_to_gt(uc)->i915; + + drm_info(&i915->drm, "%s firmware %s version %u.%u\n", + intel_uc_fw_type_repr(fw->type), fw->path, + fw->major_ver_found, fw->minor_ver_found); +} + static int __uc_init_hw(struct intel_uc *uc) { struct drm_i915_private *i915 = uc_to_gt(uc)->i915; @@ -442,6 +451,11 @@ static int __uc_init_hw(struct intel_uc *uc) GEM_BUG_ON(!intel_uc_supports_guc(uc)); GEM_BUG_ON(!intel_uc_wants_guc(uc)); + print_fw_ver(uc, &guc->fw); + + if (intel_uc_uses_huc(uc)) + print_fw_ver(uc, &huc->fw); + if (!intel_uc_fw_is_loadable(&guc->fw)) { ret = __uc_check_hw(uc) || intel_uc_fw_is_overridden(&guc->fw) || @@ -507,24 +521,11 @@ static int __uc_init_hw(struct intel_uc *uc) intel_rps_lower_unslice(&uc_to_gt(uc)->rps); } - drm_info(&i915->drm, "%s firmware %s version %u.%u %s:%s\n", - intel_uc_fw_type_repr(INTEL_UC_FW_TYPE_GUC), guc->fw.path, - guc->fw.major_ver_found, guc->fw.minor_ver_found, - "submission", + drm_info(&i915->drm, "GuC submission %s\n", enableddisabled(intel_uc_uses_guc_submission(uc))); - - drm_info(&i915->drm, "GuC SLPC: %s\n", + drm_info(&i915->drm, "GuC SLPC %s\n", enableddisabled(intel_uc_uses_guc_slpc(uc))); - if (intel_uc_uses_huc(uc)) { - drm_info(&i915->drm, "%s firmware %s version %u.%u %s:%s\n", - intel_uc_fw_type_repr(INTEL_UC_FW_TYPE_HUC), - huc->fw.path, - huc->fw.major_ver_found, huc->fw.minor_ver_found, - "authenticated", - yesno(intel_huc_is_authenticated(huc))); - } - return 0; /* -- cgit From 4a39156166b90465da0f9a33b3442d63b5651bec Mon Sep 17 00:00:00 2001 From: Jiasheng Jiang Date: Mon, 10 Jan 2022 09:38:07 +0800 Subject: drm/v3d/v3d_drv: Check for error num after setting mask Because of the possible failure of the dma_supported(), the dma_set_mask_and_coherent() may return error num. Therefore, it should be better to check it and return the error if fails. Also, we can create a variable for the mask to solve the alignment issue. Fixes: 334dd38a3878 ("drm/v3d: Set dma_mask as well as coherent_dma_mask") Signed-off-by: Jiasheng Jiang Reviewed-by: Melissa Wen Signed-off-by: Melissa Wen Link: https://patchwork.freedesktop.org/patch/msgid/20220110013807.4105270-1-jiasheng@iscas.ac.cn --- drivers/gpu/drm/v3d/v3d_drv.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/v3d/v3d_drv.c b/drivers/gpu/drm/v3d/v3d_drv.c index bd46396a1ae0..1afcd54fbbd5 100644 --- a/drivers/gpu/drm/v3d/v3d_drv.c +++ b/drivers/gpu/drm/v3d/v3d_drv.c @@ -219,6 +219,7 @@ static int v3d_platform_drm_probe(struct platform_device *pdev) int ret; u32 mmu_debug; u32 ident1; + u64 mask; v3d = devm_drm_dev_alloc(dev, &v3d_drm_driver, struct v3d_dev, drm); if (IS_ERR(v3d)) @@ -237,8 +238,11 @@ static int v3d_platform_drm_probe(struct platform_device *pdev) return ret; mmu_debug = V3D_READ(V3D_MMU_DEBUG_INFO); - dma_set_mask_and_coherent(dev, - DMA_BIT_MASK(30 + V3D_GET_FIELD(mmu_debug, V3D_MMU_PA_WIDTH))); + mask = DMA_BIT_MASK(30 + V3D_GET_FIELD(mmu_debug, V3D_MMU_PA_WIDTH)); + ret = dma_set_mask_and_coherent(dev, mask); + if (ret) + return ret; + v3d->va_width = 30 + V3D_GET_FIELD(mmu_debug, V3D_MMU_VA_WIDTH); ident1 = V3D_READ(V3D_HUB_IDENT1); -- cgit From e592dc320559ebc8166c1dce609faa7e4f3d6da1 Mon Sep 17 00:00:00 2001 From: Minghao Chi Date: Wed, 12 Jan 2022 08:25:24 +0000 Subject: drm/gma500: remove redundant ret variable Return value directly instead of taking this in another redundant variable. Reported-by: Zeal Robot Signed-off-by: Minghao Chi Signed-off-by: CGEL ZTE Signed-off-by: Patrik Jakobsson Link: https://patchwork.freedesktop.org/patch/msgid/20220112082524.667552-1-chi.minghao@zte.com.cn --- drivers/gpu/drm/gma500/cdv_intel_dp.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/gma500/cdv_intel_dp.c b/drivers/gpu/drm/gma500/cdv_intel_dp.c index ba6ad1466374..74e67679714e 100644 --- a/drivers/gpu/drm/gma500/cdv_intel_dp.c +++ b/drivers/gpu/drm/gma500/cdv_intel_dp.c @@ -82,7 +82,6 @@ i2c_algo_dp_aux_address(struct i2c_adapter *adapter, u16 address, bool reading) { struct i2c_algo_dp_aux_data *algo_data = adapter->algo_data; int mode = MODE_I2C_START; - int ret; if (reading) mode |= MODE_I2C_READ; @@ -90,8 +89,7 @@ i2c_algo_dp_aux_address(struct i2c_adapter *adapter, u16 address, bool reading) mode |= MODE_I2C_WRITE; algo_data->address = address; algo_data->running = true; - ret = i2c_algo_dp_aux_transaction(adapter, mode, 0, NULL); - return ret; + return i2c_algo_dp_aux_transaction(adapter, mode, 0, NULL); } /* @@ -122,13 +120,11 @@ static int i2c_algo_dp_aux_put_byte(struct i2c_adapter *adapter, u8 byte) { struct i2c_algo_dp_aux_data *algo_data = adapter->algo_data; - int ret; if (!algo_data->running) return -EIO; - ret = i2c_algo_dp_aux_transaction(adapter, MODE_I2C_WRITE, byte, NULL); - return ret; + return i2c_algo_dp_aux_transaction(adapter, MODE_I2C_WRITE, byte, NULL); } /* @@ -139,13 +135,11 @@ static int i2c_algo_dp_aux_get_byte(struct i2c_adapter *adapter, u8 *byte_ret) { struct i2c_algo_dp_aux_data *algo_data = adapter->algo_data; - int ret; if (!algo_data->running) return -EIO; - ret = i2c_algo_dp_aux_transaction(adapter, MODE_I2C_READ, 0, byte_ret); - return ret; + return i2c_algo_dp_aux_transaction(adapter, MODE_I2C_READ, 0, byte_ret); } static int -- cgit From 9d31993451f6bb4059a9b9eec4856b2225e36df0 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Tue, 11 Jan 2022 14:26:34 +0100 Subject: drm/mipi-dbi: Fix source-buffer address in mipi_dbi_buf_copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Set the source-buffer address after mapping the buffer into the kernel's address space. Makes MIPI DBI helpers work again. Signed-off-by: Thomas Zimmermann Fixes: c47160d8edcd ("drm/mipi-dbi: Remove dependency on GEM CMA helper library") Reviewed-by: Noralf Trønnes Reported-by: Noralf Trønnes Cc: Thomas Zimmermann Cc: Daniel Vetter Cc: Maarten Lankhorst Cc: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20220111132634.18302-1-tzimmermann@suse.de --- drivers/gpu/drm/drm_mipi_dbi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/drm_mipi_dbi.c b/drivers/gpu/drm/drm_mipi_dbi.c index ded8968b3e8a..0327d595e028 100644 --- a/drivers/gpu/drm/drm_mipi_dbi.c +++ b/drivers/gpu/drm/drm_mipi_dbi.c @@ -209,11 +209,11 @@ int mipi_dbi_buf_copy(void *dst, struct drm_framebuffer *fb, ret = drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE); if (ret) return ret; - src = data[0].vaddr; /* TODO: Use mapping abstraction properly */ ret = drm_gem_fb_vmap(fb, map, data); if (ret) goto out_drm_gem_fb_end_cpu_access; + src = data[0].vaddr; /* TODO: Use mapping abstraction properly */ switch (fb->format->format) { case DRM_FORMAT_RGB565: -- cgit From bd29823e05ac5ba81e9f6c8ca51f040c8b47a741 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Wed, 12 Jan 2022 00:13:47 +0530 Subject: Revert "drm: exynos: dsi: Convert to bridge driver" This reverts commit 92e794fab87af0793403d5e4a547f0be94a0e656. It is merged by accident, the actual patch series on this bridge conversion is still under review. Revert this as it breaks the exynos DSI. Signed-off-by: Jagan Teki Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20220111184347.502471-1-jagan@amarulasolutions.com Reviewed-by: Robert Foss --- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 93 ++++++++++++--------------------- 1 file changed, 32 insertions(+), 61 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index 3d4713346949..bce5331ed1e6 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -257,7 +257,6 @@ struct exynos_dsi { struct drm_connector connector; struct drm_panel *panel; struct list_head bridge_chain; - struct drm_bridge bridge; struct drm_bridge *out_bridge; struct device *dev; struct drm_display_mode mode; @@ -289,9 +288,9 @@ struct exynos_dsi { #define host_to_dsi(host) container_of(host, struct exynos_dsi, dsi_host) #define connector_to_dsi(c) container_of(c, struct exynos_dsi, connector) -static inline struct exynos_dsi *bridge_to_dsi(struct drm_bridge *b) +static inline struct exynos_dsi *encoder_to_dsi(struct drm_encoder *e) { - return container_of(b, struct exynos_dsi, bridge); + return container_of(e, struct exynos_dsi, encoder); } enum reg_idx { @@ -1376,10 +1375,9 @@ static void exynos_dsi_unregister_te_irq(struct exynos_dsi *dsi) } } -static void exynos_dsi_atomic_enable(struct drm_bridge *bridge, - struct drm_bridge_state *old_bridge_state) +static void exynos_dsi_enable(struct drm_encoder *encoder) { - struct exynos_dsi *dsi = bridge_to_dsi(bridge); + struct exynos_dsi *dsi = encoder_to_dsi(encoder); struct drm_bridge *iter; int ret; @@ -1402,8 +1400,7 @@ static void exynos_dsi_atomic_enable(struct drm_bridge *bridge, list_for_each_entry_reverse(iter, &dsi->bridge_chain, chain_node) { if (iter->funcs->pre_enable) - iter->funcs->atomic_pre_enable(iter, - old_bridge_state); + iter->funcs->pre_enable(iter); } } @@ -1417,7 +1414,7 @@ static void exynos_dsi_atomic_enable(struct drm_bridge *bridge, } else { list_for_each_entry(iter, &dsi->bridge_chain, chain_node) { if (iter->funcs->enable) - iter->funcs->atomic_enable(iter, old_bridge_state); + iter->funcs->enable(iter); } } @@ -1433,10 +1430,9 @@ err_put_sync: pm_runtime_put(dsi->dev); } -static void exynos_dsi_atomic_disable(struct drm_bridge *bridge, - struct drm_bridge_state *old_bridge_state) +static void exynos_dsi_disable(struct drm_encoder *encoder) { - struct exynos_dsi *dsi = bridge_to_dsi(bridge); + struct exynos_dsi *dsi = encoder_to_dsi(encoder); struct drm_bridge *iter; if (!(dsi->state & DSIM_STATE_ENABLED)) @@ -1448,7 +1444,7 @@ static void exynos_dsi_atomic_disable(struct drm_bridge *bridge, list_for_each_entry_reverse(iter, &dsi->bridge_chain, chain_node) { if (iter->funcs->disable) - iter->funcs->atomic_disable(iter, old_bridge_state); + iter->funcs->disable(iter); } exynos_dsi_set_display_enable(dsi, false); @@ -1456,13 +1452,22 @@ static void exynos_dsi_atomic_disable(struct drm_bridge *bridge, list_for_each_entry(iter, &dsi->bridge_chain, chain_node) { if (iter->funcs->post_disable) - iter->funcs->atomic_post_disable(iter, old_bridge_state); + iter->funcs->post_disable(iter); } dsi->state &= ~DSIM_STATE_ENABLED; pm_runtime_put_sync(dsi->dev); } +static void exynos_dsi_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct exynos_dsi *dsi = encoder_to_dsi(encoder); + + drm_mode_copy(&dsi->mode, adjusted_mode); +} + static enum drm_connector_status exynos_dsi_detect(struct drm_connector *connector, bool force) { @@ -1499,9 +1504,9 @@ static const struct drm_connector_helper_funcs exynos_dsi_connector_helper_funcs .get_modes = exynos_dsi_get_modes, }; -static int exynos_dsi_create_connector(struct exynos_dsi *dsi) +static int exynos_dsi_create_connector(struct drm_encoder *encoder) { - struct drm_encoder *encoder = &dsi->encoder; + struct exynos_dsi *dsi = encoder_to_dsi(encoder); struct drm_connector *connector = &dsi->connector; struct drm_device *drm = encoder->dev; int ret; @@ -1527,31 +1532,10 @@ static int exynos_dsi_create_connector(struct exynos_dsi *dsi) return 0; } -static void exynos_dsi_mode_set(struct drm_bridge *bridge, - const struct drm_display_mode *mode, - const struct drm_display_mode *adjusted_mode) -{ - struct exynos_dsi *dsi = bridge_to_dsi(bridge); - - drm_mode_copy(&dsi->mode, adjusted_mode); -} - -static int exynos_dsi_attach(struct drm_bridge *bridge, - enum drm_bridge_attach_flags flags) -{ - struct exynos_dsi *dsi = bridge_to_dsi(bridge); - - return drm_bridge_attach(bridge->encoder, dsi->out_bridge, NULL, 0); -} - -static const struct drm_bridge_funcs exynos_dsi_bridge_funcs = { - .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, - .atomic_reset = drm_atomic_helper_bridge_reset, - .atomic_enable = exynos_dsi_atomic_enable, - .atomic_disable = exynos_dsi_atomic_disable, - .mode_set = exynos_dsi_mode_set, - .attach = exynos_dsi_attach, +static const struct drm_encoder_helper_funcs exynos_dsi_encoder_helper_funcs = { + .enable = exynos_dsi_enable, + .disable = exynos_dsi_disable, + .mode_set = exynos_dsi_mode_set, }; MODULE_DEVICE_TABLE(of, exynos_dsi_of_match); @@ -1570,7 +1554,7 @@ static int exynos_dsi_host_attach(struct mipi_dsi_host *host, dsi->out_bridge = out_bridge; list_splice_init(&encoder->bridge_chain, &dsi->bridge_chain); } else { - int ret = exynos_dsi_create_connector(dsi); + int ret = exynos_dsi_create_connector(encoder); if (ret) { DRM_DEV_ERROR(dsi->dev, @@ -1623,7 +1607,7 @@ static int exynos_dsi_host_detach(struct mipi_dsi_host *host, if (dsi->panel) { mutex_lock(&drm->mode_config.mutex); - exynos_dsi_atomic_disable(&dsi->bridge, NULL); + exynos_dsi_disable(&dsi->encoder); dsi->panel = NULL; dsi->connector.status = connector_status_disconnected; mutex_unlock(&drm->mode_config.mutex); @@ -1729,16 +1713,12 @@ static int exynos_dsi_bind(struct device *dev, struct device *master, drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS); + drm_encoder_helper_add(encoder, &exynos_dsi_encoder_helper_funcs); + ret = exynos_drm_set_possible_crtcs(encoder, EXYNOS_DISPLAY_TYPE_LCD); if (ret < 0) return ret; - ret = drm_bridge_attach(&dsi->encoder, &dsi->bridge, NULL, 0); - if (ret) { - drm_encoder_cleanup(&dsi->encoder); - return ret; - } - in_bridge_node = of_graph_get_remote_node(dev->of_node, DSI_PORT_IN, 0); if (in_bridge_node) { in_bridge = of_drm_find_bridge(in_bridge_node); @@ -1754,9 +1734,10 @@ static void exynos_dsi_unbind(struct device *dev, struct device *master, void *data) { struct exynos_dsi *dsi = dev_get_drvdata(dev); + struct drm_encoder *encoder = &dsi->encoder; + + exynos_dsi_disable(encoder); - exynos_dsi_atomic_disable(&dsi->bridge, NULL); - drm_encoder_cleanup(&dsi->encoder); mipi_dsi_host_unregister(&dsi->dsi_host); } @@ -1849,12 +1830,6 @@ static int exynos_dsi_probe(struct platform_device *pdev) pm_runtime_enable(dev); - dsi->bridge.funcs = &exynos_dsi_bridge_funcs; - dsi->bridge.of_node = dev->of_node; - dsi->bridge.type = DRM_MODE_CONNECTOR_DSI; - - drm_bridge_add(&dsi->bridge); - ret = component_add(dev, &exynos_dsi_component_ops); if (ret) goto err_disable_runtime; @@ -1869,10 +1844,6 @@ err_disable_runtime: static int exynos_dsi_remove(struct platform_device *pdev) { - struct exynos_dsi *dsi = platform_get_drvdata(pdev); - - drm_bridge_remove(&dsi->bridge); - pm_runtime_disable(&pdev->dev); component_del(&pdev->dev, &exynos_dsi_component_ops); -- cgit From 0f5d32617246810d00dae08c51069873a77b60d1 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Sun, 9 Jan 2022 20:41:05 +0000 Subject: drm/bridge: adv7533: make array clock_div_by_lanes static const Don't populate the read-only array clock_div_by_lanes on the stack but instead it static const. Also makes the object code a little smaller. Signed-off-by: Colin Ian King Reviewed-by: Laurent Pinchart Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20220109204105.51878-1-colin.i.king@gmail.com --- drivers/gpu/drm/bridge/adv7511/adv7533.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/adv7511/adv7533.c b/drivers/gpu/drm/bridge/adv7511/adv7533.c index eb7579dec40a..ef6270806d1d 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7533.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7533.c @@ -29,7 +29,7 @@ static void adv7511_dsi_config_timing_gen(struct adv7511 *adv) struct mipi_dsi_device *dsi = adv->dsi; struct drm_display_mode *mode = &adv->curr_mode; unsigned int hsw, hfp, hbp, vsw, vfp, vbp; - u8 clock_div_by_lanes[] = { 6, 4, 3 }; /* 2, 3, 4 lanes */ + static const u8 clock_div_by_lanes[] = { 6, 4, 3 }; /* 2, 3, 4 lanes */ hsw = mode->hsync_end - mode->hsync_start; hfp = mode->hsync_start - mode->hdisplay; -- cgit From 7b1534188c25948f4ce56dc4af9d8e9234e97592 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Mon, 10 Jan 2022 22:55:33 +0530 Subject: drm: bridge: nwl-dsi: Drop panel_bridge from nwl_dsi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit panel_bridge pointer never used anywhere except the one it looked up at nwl_dsi_bridge_attach. Drop it from the nwl_dsi structure. Reviewed-by: Guido Günther Signed-off-by: Jagan Teki Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20220110172533.66614-1-jagan@amarulasolutions.com --- drivers/gpu/drm/bridge/nwl-dsi.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/nwl-dsi.c b/drivers/gpu/drm/bridge/nwl-dsi.c index fc3ad9fab867..9282e61dfbf0 100644 --- a/drivers/gpu/drm/bridge/nwl-dsi.c +++ b/drivers/gpu/drm/bridge/nwl-dsi.c @@ -65,7 +65,6 @@ struct nwl_dsi_transfer { struct nwl_dsi { struct drm_bridge bridge; struct mipi_dsi_host dsi_host; - struct drm_bridge *panel_bridge; struct device *dev; struct phy *phy; union phy_configure_opts phy_cfg; @@ -924,13 +923,11 @@ static int nwl_dsi_bridge_attach(struct drm_bridge *bridge, if (IS_ERR(panel_bridge)) return PTR_ERR(panel_bridge); } - dsi->panel_bridge = panel_bridge; - if (!dsi->panel_bridge) + if (!panel_bridge) return -EPROBE_DEFER; - return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge, - flags); + return drm_bridge_attach(bridge->encoder, panel_bridge, bridge, flags); } static void nwl_dsi_bridge_detach(struct drm_bridge *bridge) -- cgit From a11d854f072d520c065e1fb9484d868490f1cce8 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Mon, 10 Jan 2022 10:56:24 +0100 Subject: video: vga16fb: Fix logic that checks for the display standard The vga16fb framebuffer driver supports both Enhanced Graphics Adapter (EGA) and Video Graphics Array (VGA) 16 color graphic cards. But the logic to check whether the EGA or VGA standard are used is not correct. It just checks if screen_info.orig_video_isVGA is set, but it should check if is set to VIDEO_TYPE_VGAC instead. This means that it assumes to be VGA even if is set to VIDEO_TYPE_EGAC. All non-x86 architectures though treat orig_video_isVGA as a boolean so only do the change for x86 and keep the old logic for the other arches. Signed-off-by: Javier Martinez Canillas Tested-by: Kris Karas Acked-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20220110095625.278836-2-javierm@redhat.com --- drivers/video/fbdev/vga16fb.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers') diff --git a/drivers/video/fbdev/vga16fb.c b/drivers/video/fbdev/vga16fb.c index e2757ff1c23d..8c464409eb99 100644 --- a/drivers/video/fbdev/vga16fb.c +++ b/drivers/video/fbdev/vga16fb.c @@ -1332,7 +1332,12 @@ static int vga16fb_probe(struct platform_device *dev) printk(KERN_INFO "vga16fb: mapped to 0x%p\n", info->screen_base); par = info->par; +#if defined(CONFIG_X86) + par->isVGA = screen_info.orig_video_isVGA == VIDEO_TYPE_VGAC; +#else + /* non-x86 architectures treat orig_video_isVGA as a boolean flag */ par->isVGA = screen_info.orig_video_isVGA; +#endif par->palette_blanked = 0; par->vesa_blanked = 0; -- cgit From 44ab30b056149bd59dd7989a593dd25ead6007fd Mon Sep 17 00:00:00 2001 From: Jiasheng Jiang Date: Thu, 6 Jan 2022 11:03:26 +0800 Subject: drm/panfrost: Check for error num after setting mask Because of the possible failure of the dma_supported(), the dma_set_mask_and_coherent() may return error num. Therefore, it should be better to check it and return the error if fails. Fixes: f3ba91228e8e ("drm/panfrost: Add initial panfrost driver") Signed-off-by: Jiasheng Jiang [Steve: fix Fixes: line] Reviewed-by: Steven Price Signed-off-by: Steven Price Link: https://patchwork.freedesktop.org/patch/msgid/20220106030326.2620942-1-jiasheng@iscas.ac.cn --- drivers/gpu/drm/panfrost/panfrost_gpu.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/panfrost/panfrost_gpu.c b/drivers/gpu/drm/panfrost/panfrost_gpu.c index bbe628b306ee..f8355de6e335 100644 --- a/drivers/gpu/drm/panfrost/panfrost_gpu.c +++ b/drivers/gpu/drm/panfrost/panfrost_gpu.c @@ -360,8 +360,11 @@ int panfrost_gpu_init(struct panfrost_device *pfdev) panfrost_gpu_init_features(pfdev); - dma_set_mask_and_coherent(pfdev->dev, + err = dma_set_mask_and_coherent(pfdev->dev, DMA_BIT_MASK(FIELD_GET(0xff00, pfdev->features.mmu_features))); + if (err) + return err; + dma_set_max_seg_size(pfdev->dev, UINT_MAX); irq = platform_get_irq_byname(to_platform_device(pfdev->dev), "gpu"); -- cgit From 16a89697a3a0f793ad707f0bd32969419392bbe4 Mon Sep 17 00:00:00 2001 From: Alyssa Rosenzweig Date: Sun, 9 Jan 2022 12:09:18 -0500 Subject: drm/panfrost: Remove features meant for userspace Early versions of the legacy kernel driver included comprehensive feature lists for every GPU, even though most of the enumerated features only matter to userspace. For example, HW_FEATURE_INTERPIPE_REG_ALIASING was a feature bit indicating that a GPU had "interpipe register aliasing": arithmetic, load/store, and texture instruction all use common general-purpose registers. GPUs without this feature bit have dedicated load/store and texture "registers". Whether a GPU has this feature or not is irrelevant to the kernel; it only matters in the userspace compiler's register allocator. It's silly to enumerate it in kernel space, and the information is understandably unused. To underscore the point, this feature only makes sense in the context of the Midgard instruction set. Bifrost never had dedicated load/store or texture registers, so the feature bit was vacuously set for all Bifrost hardware, even though this conveys no useful information. To clean up the feature list, delete feature bits which could not possibly matter to the kernel, leaving only those which do affect the register-level operation of the chip. Signed-off-by: Alyssa Rosenzweig Reviewed-by: Steven Price Signed-off-by: Steven Price Link: https://patchwork.freedesktop.org/patch/msgid/20220109170920.2921-2-alyssa.rosenzweig@collabora.com --- drivers/gpu/drm/panfrost/panfrost_features.h | 172 --------------------------- 1 file changed, 172 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/panfrost/panfrost_features.h b/drivers/gpu/drm/panfrost/panfrost_features.h index 5056777c7744..f557fad5d5ff 100644 --- a/drivers/gpu/drm/panfrost/panfrost_features.h +++ b/drivers/gpu/drm/panfrost/panfrost_features.h @@ -12,24 +12,6 @@ enum panfrost_hw_feature { HW_FEATURE_JOBCHAIN_DISAMBIGUATION, HW_FEATURE_PWRON_DURING_PWROFF_TRANS, HW_FEATURE_XAFFINITY, - HW_FEATURE_OUT_OF_ORDER_EXEC, - HW_FEATURE_MRT, - HW_FEATURE_BRNDOUT_CC, - HW_FEATURE_INTERPIPE_REG_ALIASING, - HW_FEATURE_LD_ST_TILEBUFFER, - HW_FEATURE_MSAA_16X, - HW_FEATURE_32_BIT_UNIFORM_ADDRESS, - HW_FEATURE_ATTR_AUTO_TYPE_INFERRAL, - HW_FEATURE_OPTIMIZED_COVERAGE_MASK, - HW_FEATURE_T7XX_PAIRING_RULES, - HW_FEATURE_LD_ST_LEA_TEX, - HW_FEATURE_LINEAR_FILTER_FLOAT, - HW_FEATURE_WORKGROUP_ROUND_MULTIPLE_OF_4, - HW_FEATURE_IMAGES_IN_FRAGMENT_SHADERS, - HW_FEATURE_TEST4_DATUM_MODE, - HW_FEATURE_NEXT_INSTRUCTION_TYPE, - HW_FEATURE_BRNDOUT_KILL, - HW_FEATURE_WARPING, HW_FEATURE_V4, HW_FEATURE_FLUSH_REDUCTION, HW_FEATURE_PROTECTED_MODE, @@ -42,27 +24,15 @@ enum panfrost_hw_feature { }; #define hw_features_t600 (\ - BIT_ULL(HW_FEATURE_LD_ST_LEA_TEX) | \ - BIT_ULL(HW_FEATURE_LINEAR_FILTER_FLOAT) | \ BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT) | \ BIT_ULL(HW_FEATURE_V4)) #define hw_features_t620 (\ - BIT_ULL(HW_FEATURE_LD_ST_LEA_TEX) | \ - BIT_ULL(HW_FEATURE_LINEAR_FILTER_FLOAT) | \ - BIT_ULL(HW_FEATURE_ATTR_AUTO_TYPE_INFERRAL) | \ BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT) | \ BIT_ULL(HW_FEATURE_V4)) #define hw_features_t720 (\ - BIT_ULL(HW_FEATURE_32_BIT_UNIFORM_ADDRESS) | \ - BIT_ULL(HW_FEATURE_ATTR_AUTO_TYPE_INFERRAL) | \ - BIT_ULL(HW_FEATURE_INTERPIPE_REG_ALIASING) | \ - BIT_ULL(HW_FEATURE_OPTIMIZED_COVERAGE_MASK) | \ - BIT_ULL(HW_FEATURE_T7XX_PAIRING_RULES) | \ BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT) | \ - BIT_ULL(HW_FEATURE_WORKGROUP_ROUND_MULTIPLE_OF_4) | \ - BIT_ULL(HW_FEATURE_WARPING) | \ BIT_ULL(HW_FEATURE_V4)) @@ -70,17 +40,6 @@ enum panfrost_hw_feature { BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \ BIT_ULL(HW_FEATURE_PWRON_DURING_PWROFF_TRANS) | \ BIT_ULL(HW_FEATURE_XAFFINITY) | \ - BIT_ULL(HW_FEATURE_32_BIT_UNIFORM_ADDRESS) | \ - BIT_ULL(HW_FEATURE_ATTR_AUTO_TYPE_INFERRAL) | \ - BIT_ULL(HW_FEATURE_BRNDOUT_CC) | \ - BIT_ULL(HW_FEATURE_LD_ST_LEA_TEX) | \ - BIT_ULL(HW_FEATURE_LD_ST_TILEBUFFER) | \ - BIT_ULL(HW_FEATURE_LINEAR_FILTER_FLOAT) | \ - BIT_ULL(HW_FEATURE_MRT) | \ - BIT_ULL(HW_FEATURE_MSAA_16X) | \ - BIT_ULL(HW_FEATURE_OUT_OF_ORDER_EXEC) | \ - BIT_ULL(HW_FEATURE_T7XX_PAIRING_RULES) | \ - BIT_ULL(HW_FEATURE_TEST4_DATUM_MODE) | \ BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT)) // T860 @@ -88,19 +47,6 @@ enum panfrost_hw_feature { BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \ BIT_ULL(HW_FEATURE_PWRON_DURING_PWROFF_TRANS) | \ BIT_ULL(HW_FEATURE_XAFFINITY) | \ - BIT_ULL(HW_FEATURE_32_BIT_UNIFORM_ADDRESS) | \ - BIT_ULL(HW_FEATURE_ATTR_AUTO_TYPE_INFERRAL) | \ - BIT_ULL(HW_FEATURE_BRNDOUT_CC) | \ - BIT_ULL(HW_FEATURE_BRNDOUT_KILL) | \ - BIT_ULL(HW_FEATURE_LD_ST_LEA_TEX) | \ - BIT_ULL(HW_FEATURE_LD_ST_TILEBUFFER) | \ - BIT_ULL(HW_FEATURE_LINEAR_FILTER_FLOAT) | \ - BIT_ULL(HW_FEATURE_MRT) | \ - BIT_ULL(HW_FEATURE_MSAA_16X) | \ - BIT_ULL(HW_FEATURE_NEXT_INSTRUCTION_TYPE) | \ - BIT_ULL(HW_FEATURE_OUT_OF_ORDER_EXEC) | \ - BIT_ULL(HW_FEATURE_T7XX_PAIRING_RULES) | \ - BIT_ULL(HW_FEATURE_TEST4_DATUM_MODE) | \ BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT)) #define hw_features_t880 hw_features_t860 @@ -109,61 +55,18 @@ enum panfrost_hw_feature { BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \ BIT_ULL(HW_FEATURE_PWRON_DURING_PWROFF_TRANS) | \ BIT_ULL(HW_FEATURE_XAFFINITY) | \ - BIT_ULL(HW_FEATURE_WARPING) | \ - BIT_ULL(HW_FEATURE_INTERPIPE_REG_ALIASING) | \ - BIT_ULL(HW_FEATURE_32_BIT_UNIFORM_ADDRESS) | \ - BIT_ULL(HW_FEATURE_ATTR_AUTO_TYPE_INFERRAL) | \ - BIT_ULL(HW_FEATURE_BRNDOUT_CC) | \ - BIT_ULL(HW_FEATURE_BRNDOUT_KILL) | \ - BIT_ULL(HW_FEATURE_LD_ST_LEA_TEX) | \ - BIT_ULL(HW_FEATURE_LD_ST_TILEBUFFER) | \ - BIT_ULL(HW_FEATURE_LINEAR_FILTER_FLOAT) | \ - BIT_ULL(HW_FEATURE_MRT) | \ - BIT_ULL(HW_FEATURE_NEXT_INSTRUCTION_TYPE) | \ - BIT_ULL(HW_FEATURE_OUT_OF_ORDER_EXEC) | \ - BIT_ULL(HW_FEATURE_T7XX_PAIRING_RULES) | \ - BIT_ULL(HW_FEATURE_TEST4_DATUM_MODE) | \ BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT)) #define hw_features_t820 (\ BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \ BIT_ULL(HW_FEATURE_PWRON_DURING_PWROFF_TRANS) | \ BIT_ULL(HW_FEATURE_XAFFINITY) | \ - BIT_ULL(HW_FEATURE_WARPING) | \ - BIT_ULL(HW_FEATURE_INTERPIPE_REG_ALIASING) | \ - BIT_ULL(HW_FEATURE_32_BIT_UNIFORM_ADDRESS) | \ - BIT_ULL(HW_FEATURE_ATTR_AUTO_TYPE_INFERRAL) | \ - BIT_ULL(HW_FEATURE_BRNDOUT_CC) | \ - BIT_ULL(HW_FEATURE_BRNDOUT_KILL) | \ - BIT_ULL(HW_FEATURE_LD_ST_LEA_TEX) | \ - BIT_ULL(HW_FEATURE_LD_ST_TILEBUFFER) | \ - BIT_ULL(HW_FEATURE_LINEAR_FILTER_FLOAT) | \ - BIT_ULL(HW_FEATURE_MRT) | \ - BIT_ULL(HW_FEATURE_NEXT_INSTRUCTION_TYPE) | \ - BIT_ULL(HW_FEATURE_OUT_OF_ORDER_EXEC) | \ - BIT_ULL(HW_FEATURE_T7XX_PAIRING_RULES) | \ - BIT_ULL(HW_FEATURE_TEST4_DATUM_MODE) | \ BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT)) #define hw_features_g71 (\ BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \ BIT_ULL(HW_FEATURE_PWRON_DURING_PWROFF_TRANS) | \ BIT_ULL(HW_FEATURE_XAFFINITY) | \ - BIT_ULL(HW_FEATURE_WARPING) | \ - BIT_ULL(HW_FEATURE_INTERPIPE_REG_ALIASING) | \ - BIT_ULL(HW_FEATURE_32_BIT_UNIFORM_ADDRESS) | \ - BIT_ULL(HW_FEATURE_ATTR_AUTO_TYPE_INFERRAL) | \ - BIT_ULL(HW_FEATURE_BRNDOUT_CC) | \ - BIT_ULL(HW_FEATURE_BRNDOUT_KILL) | \ - BIT_ULL(HW_FEATURE_LD_ST_LEA_TEX) | \ - BIT_ULL(HW_FEATURE_LD_ST_TILEBUFFER) | \ - BIT_ULL(HW_FEATURE_LINEAR_FILTER_FLOAT) | \ - BIT_ULL(HW_FEATURE_MRT) | \ - BIT_ULL(HW_FEATURE_MSAA_16X) | \ - BIT_ULL(HW_FEATURE_NEXT_INSTRUCTION_TYPE) | \ - BIT_ULL(HW_FEATURE_OUT_OF_ORDER_EXEC) | \ - BIT_ULL(HW_FEATURE_T7XX_PAIRING_RULES) | \ - BIT_ULL(HW_FEATURE_TEST4_DATUM_MODE) | \ BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT) | \ BIT_ULL(HW_FEATURE_FLUSH_REDUCTION) | \ BIT_ULL(HW_FEATURE_PROTECTED_MODE) | \ @@ -173,21 +76,6 @@ enum panfrost_hw_feature { BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \ BIT_ULL(HW_FEATURE_PWRON_DURING_PWROFF_TRANS) | \ BIT_ULL(HW_FEATURE_XAFFINITY) | \ - BIT_ULL(HW_FEATURE_WARPING) | \ - BIT_ULL(HW_FEATURE_INTERPIPE_REG_ALIASING) | \ - BIT_ULL(HW_FEATURE_32_BIT_UNIFORM_ADDRESS) | \ - BIT_ULL(HW_FEATURE_ATTR_AUTO_TYPE_INFERRAL) | \ - BIT_ULL(HW_FEATURE_BRNDOUT_CC) | \ - BIT_ULL(HW_FEATURE_BRNDOUT_KILL) | \ - BIT_ULL(HW_FEATURE_LD_ST_LEA_TEX) | \ - BIT_ULL(HW_FEATURE_LD_ST_TILEBUFFER) | \ - BIT_ULL(HW_FEATURE_LINEAR_FILTER_FLOAT) | \ - BIT_ULL(HW_FEATURE_MRT) | \ - BIT_ULL(HW_FEATURE_MSAA_16X) | \ - BIT_ULL(HW_FEATURE_NEXT_INSTRUCTION_TYPE) | \ - BIT_ULL(HW_FEATURE_OUT_OF_ORDER_EXEC) | \ - BIT_ULL(HW_FEATURE_T7XX_PAIRING_RULES) | \ - BIT_ULL(HW_FEATURE_TEST4_DATUM_MODE) | \ BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT) | \ BIT_ULL(HW_FEATURE_FLUSH_REDUCTION) | \ BIT_ULL(HW_FEATURE_PROTECTED_MODE) | \ @@ -198,21 +86,6 @@ enum panfrost_hw_feature { BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \ BIT_ULL(HW_FEATURE_PWRON_DURING_PWROFF_TRANS) | \ BIT_ULL(HW_FEATURE_XAFFINITY) | \ - BIT_ULL(HW_FEATURE_WARPING) | \ - BIT_ULL(HW_FEATURE_INTERPIPE_REG_ALIASING) | \ - BIT_ULL(HW_FEATURE_32_BIT_UNIFORM_ADDRESS) | \ - BIT_ULL(HW_FEATURE_ATTR_AUTO_TYPE_INFERRAL) | \ - BIT_ULL(HW_FEATURE_BRNDOUT_CC) | \ - BIT_ULL(HW_FEATURE_BRNDOUT_KILL) | \ - BIT_ULL(HW_FEATURE_LD_ST_LEA_TEX) | \ - BIT_ULL(HW_FEATURE_LD_ST_TILEBUFFER) | \ - BIT_ULL(HW_FEATURE_LINEAR_FILTER_FLOAT) | \ - BIT_ULL(HW_FEATURE_MRT) | \ - BIT_ULL(HW_FEATURE_MSAA_16X) | \ - BIT_ULL(HW_FEATURE_NEXT_INSTRUCTION_TYPE) | \ - BIT_ULL(HW_FEATURE_OUT_OF_ORDER_EXEC) | \ - BIT_ULL(HW_FEATURE_T7XX_PAIRING_RULES) | \ - BIT_ULL(HW_FEATURE_TEST4_DATUM_MODE) | \ BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT) | \ BIT_ULL(HW_FEATURE_FLUSH_REDUCTION) | \ BIT_ULL(HW_FEATURE_PROTECTED_MODE) | \ @@ -223,21 +96,6 @@ enum panfrost_hw_feature { BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \ BIT_ULL(HW_FEATURE_PWRON_DURING_PWROFF_TRANS) | \ BIT_ULL(HW_FEATURE_XAFFINITY) | \ - BIT_ULL(HW_FEATURE_WARPING) | \ - BIT_ULL(HW_FEATURE_INTERPIPE_REG_ALIASING) | \ - BIT_ULL(HW_FEATURE_32_BIT_UNIFORM_ADDRESS) | \ - BIT_ULL(HW_FEATURE_ATTR_AUTO_TYPE_INFERRAL) | \ - BIT_ULL(HW_FEATURE_BRNDOUT_CC) | \ - BIT_ULL(HW_FEATURE_BRNDOUT_KILL) | \ - BIT_ULL(HW_FEATURE_LD_ST_LEA_TEX) | \ - BIT_ULL(HW_FEATURE_LD_ST_TILEBUFFER) | \ - BIT_ULL(HW_FEATURE_LINEAR_FILTER_FLOAT) | \ - BIT_ULL(HW_FEATURE_MRT) | \ - BIT_ULL(HW_FEATURE_MSAA_16X) | \ - BIT_ULL(HW_FEATURE_NEXT_INSTRUCTION_TYPE) | \ - BIT_ULL(HW_FEATURE_OUT_OF_ORDER_EXEC) | \ - BIT_ULL(HW_FEATURE_T7XX_PAIRING_RULES) | \ - BIT_ULL(HW_FEATURE_TEST4_DATUM_MODE) | \ BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT) | \ BIT_ULL(HW_FEATURE_FLUSH_REDUCTION) | \ BIT_ULL(HW_FEATURE_PROTECTED_MODE) | \ @@ -248,21 +106,6 @@ enum panfrost_hw_feature { BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \ BIT_ULL(HW_FEATURE_PWRON_DURING_PWROFF_TRANS) | \ BIT_ULL(HW_FEATURE_XAFFINITY) | \ - BIT_ULL(HW_FEATURE_WARPING) | \ - BIT_ULL(HW_FEATURE_INTERPIPE_REG_ALIASING) | \ - BIT_ULL(HW_FEATURE_32_BIT_UNIFORM_ADDRESS) | \ - BIT_ULL(HW_FEATURE_ATTR_AUTO_TYPE_INFERRAL) | \ - BIT_ULL(HW_FEATURE_BRNDOUT_CC) | \ - BIT_ULL(HW_FEATURE_BRNDOUT_KILL) | \ - BIT_ULL(HW_FEATURE_LD_ST_LEA_TEX) | \ - BIT_ULL(HW_FEATURE_LD_ST_TILEBUFFER) | \ - BIT_ULL(HW_FEATURE_LINEAR_FILTER_FLOAT) | \ - BIT_ULL(HW_FEATURE_MRT) | \ - BIT_ULL(HW_FEATURE_MSAA_16X) | \ - BIT_ULL(HW_FEATURE_NEXT_INSTRUCTION_TYPE) | \ - BIT_ULL(HW_FEATURE_OUT_OF_ORDER_EXEC) | \ - BIT_ULL(HW_FEATURE_T7XX_PAIRING_RULES) | \ - BIT_ULL(HW_FEATURE_TEST4_DATUM_MODE) | \ BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT) | \ BIT_ULL(HW_FEATURE_FLUSH_REDUCTION) | \ BIT_ULL(HW_FEATURE_PROTECTED_MODE) | \ @@ -276,21 +119,6 @@ enum panfrost_hw_feature { BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \ BIT_ULL(HW_FEATURE_PWRON_DURING_PWROFF_TRANS) | \ BIT_ULL(HW_FEATURE_XAFFINITY) | \ - BIT_ULL(HW_FEATURE_WARPING) | \ - BIT_ULL(HW_FEATURE_INTERPIPE_REG_ALIASING) | \ - BIT_ULL(HW_FEATURE_32_BIT_UNIFORM_ADDRESS) | \ - BIT_ULL(HW_FEATURE_ATTR_AUTO_TYPE_INFERRAL) | \ - BIT_ULL(HW_FEATURE_BRNDOUT_CC) | \ - BIT_ULL(HW_FEATURE_BRNDOUT_KILL) | \ - BIT_ULL(HW_FEATURE_LD_ST_LEA_TEX) | \ - BIT_ULL(HW_FEATURE_LD_ST_TILEBUFFER) | \ - BIT_ULL(HW_FEATURE_LINEAR_FILTER_FLOAT) | \ - BIT_ULL(HW_FEATURE_MRT) | \ - BIT_ULL(HW_FEATURE_MSAA_16X) | \ - BIT_ULL(HW_FEATURE_NEXT_INSTRUCTION_TYPE) | \ - BIT_ULL(HW_FEATURE_OUT_OF_ORDER_EXEC) | \ - BIT_ULL(HW_FEATURE_T7XX_PAIRING_RULES) | \ - BIT_ULL(HW_FEATURE_TEST4_DATUM_MODE) | \ BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT) | \ BIT_ULL(HW_FEATURE_FLUSH_REDUCTION) | \ BIT_ULL(HW_FEATURE_PROTECTED_MODE) | \ -- cgit From 8f54eab0efd55f7e906597d72bf01c6c6f6de510 Mon Sep 17 00:00:00 2001 From: Alyssa Rosenzweig Date: Sun, 9 Jan 2022 12:09:19 -0500 Subject: drm/panfrost: Merge some feature lists Now that we only list features of interest to kernel space, lots of GPUs have the same feature bits. To cut down on the repetition in the file, merge feature lists that are identical between similar GPUs. Note that this leaves some unmerged identical Bifrost feature lists, as there are more features affecting Bifrost kernel space that we do not yet handle. Signed-off-by: Alyssa Rosenzweig [Steve: fix typo in commit message] Reviewed-by: Steven Price Signed-off-by: Steven Price Link: https://patchwork.freedesktop.org/patch/msgid/20220109170920.2921-3-alyssa.rosenzweig@collabora.com --- drivers/gpu/drm/panfrost/panfrost_features.h | 40 +++++----------------------- 1 file changed, 7 insertions(+), 33 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/panfrost/panfrost_features.h b/drivers/gpu/drm/panfrost/panfrost_features.h index f557fad5d5ff..34f2bae1ec8c 100644 --- a/drivers/gpu/drm/panfrost/panfrost_features.h +++ b/drivers/gpu/drm/panfrost/panfrost_features.h @@ -27,14 +27,9 @@ enum panfrost_hw_feature { BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT) | \ BIT_ULL(HW_FEATURE_V4)) -#define hw_features_t620 (\ - BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT) | \ - BIT_ULL(HW_FEATURE_V4)) - -#define hw_features_t720 (\ - BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT) | \ - BIT_ULL(HW_FEATURE_V4)) +#define hw_features_t620 hw_features_t600 +#define hw_features_t720 hw_features_t600 #define hw_features_t760 (\ BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \ @@ -42,26 +37,13 @@ enum panfrost_hw_feature { BIT_ULL(HW_FEATURE_XAFFINITY) | \ BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT)) -// T860 -#define hw_features_t860 (\ - BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \ - BIT_ULL(HW_FEATURE_PWRON_DURING_PWROFF_TRANS) | \ - BIT_ULL(HW_FEATURE_XAFFINITY) | \ - BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT)) +#define hw_features_t860 hw_features_t760 -#define hw_features_t880 hw_features_t860 +#define hw_features_t880 hw_features_t760 -#define hw_features_t830 (\ - BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \ - BIT_ULL(HW_FEATURE_PWRON_DURING_PWROFF_TRANS) | \ - BIT_ULL(HW_FEATURE_XAFFINITY) | \ - BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT)) +#define hw_features_t830 hw_features_t760 -#define hw_features_t820 (\ - BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \ - BIT_ULL(HW_FEATURE_PWRON_DURING_PWROFF_TRANS) | \ - BIT_ULL(HW_FEATURE_XAFFINITY) | \ - BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT)) +#define hw_features_t820 hw_features_t760 #define hw_features_g71 (\ BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \ @@ -82,15 +64,7 @@ enum panfrost_hw_feature { BIT_ULL(HW_FEATURE_PROTECTED_DEBUG_MODE) | \ BIT_ULL(HW_FEATURE_COHERENCY_REG)) -#define hw_features_g51 (\ - BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \ - BIT_ULL(HW_FEATURE_PWRON_DURING_PWROFF_TRANS) | \ - BIT_ULL(HW_FEATURE_XAFFINITY) | \ - BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT) | \ - BIT_ULL(HW_FEATURE_FLUSH_REDUCTION) | \ - BIT_ULL(HW_FEATURE_PROTECTED_MODE) | \ - BIT_ULL(HW_FEATURE_PROTECTED_DEBUG_MODE) | \ - BIT_ULL(HW_FEATURE_COHERENCY_REG)) +#define hw_features_g51 hw_features_g72 #define hw_features_g52 (\ BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \ -- cgit From 734c26450aefaadb839fe4ba5f7463b53ad2de88 Mon Sep 17 00:00:00 2001 From: Yannick Fertre Date: Wed, 15 Dec 2021 22:47:38 +0100 Subject: drm/stm: ltdc: switch to regmap Replace the legacy register access by regmap API. Signed-off-by: Yannick Fertre Acked-by: Philippe Cornu Reviewed-by: Philippe Cornu Reviewed-by: Raphael Gallais-Pou Tested-by: Raphael Gallais-Pou Signed-off-by: Philippe Cornu Link: https://patchwork.freedesktop.org/patch/msgid/20211215214738.19946-1-yannick.fertre@foss.st.com --- drivers/gpu/drm/stm/ltdc.c | 138 ++++++++++++++++++++++----------------------- drivers/gpu/drm/stm/ltdc.h | 1 + 2 files changed, 68 insertions(+), 71 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/stm/ltdc.c b/drivers/gpu/drm/stm/ltdc.c index c0619f372630..8dad3d00aa5c 100644 --- a/drivers/gpu/drm/stm/ltdc.c +++ b/drivers/gpu/drm/stm/ltdc.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -343,31 +344,14 @@ static const u64 ltdc_format_modifiers[] = { DRM_FORMAT_MOD_INVALID }; -static inline u32 reg_read(void __iomem *base, u32 reg) -{ - return readl_relaxed(base + reg); -} - -static inline void reg_write(void __iomem *base, u32 reg, u32 val) -{ - writel_relaxed(val, base + reg); -} - -static inline void reg_set(void __iomem *base, u32 reg, u32 mask) -{ - reg_write(base, reg, reg_read(base, reg) | mask); -} - -static inline void reg_clear(void __iomem *base, u32 reg, u32 mask) -{ - reg_write(base, reg, reg_read(base, reg) & ~mask); -} - -static inline void reg_update_bits(void __iomem *base, u32 reg, u32 mask, - u32 val) -{ - reg_write(base, reg, (reg_read(base, reg) & ~mask) | val); -} +static const struct regmap_config stm32_ltdc_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = sizeof(u32), + .max_register = 0x400, + .use_relaxed_mmio = true, + .cache_type = REGCACHE_NONE, +}; static inline struct ltdc_device *crtc_to_ltdc(struct drm_crtc *crtc) { @@ -494,9 +478,13 @@ static irqreturn_t ltdc_irq(int irq, void *arg) struct drm_device *ddev = arg; struct ltdc_device *ldev = ddev->dev_private; - /* Read & Clear the interrupt status */ - ldev->irq_status = reg_read(ldev->regs, LTDC_ISR); - reg_write(ldev->regs, LTDC_ICR, ldev->irq_status); + /* + * Read & Clear the interrupt status + * In order to write / read registers in this critical section + * very quickly, the regmap functions are not used. + */ + ldev->irq_status = readl_relaxed(ldev->regs + LTDC_ISR); + writel_relaxed(ldev->irq_status, ldev->regs + LTDC_ICR); return IRQ_WAKE_THREAD; } @@ -520,7 +508,7 @@ static void ltdc_crtc_update_clut(struct drm_crtc *crtc) for (i = 0; i < CLUT_SIZE; i++, lut++) { val = ((lut->red << 8) & 0xff0000) | (lut->green & 0xff00) | (lut->blue >> 8) | (i << 24); - reg_write(ldev->regs, LTDC_L1CLUTWR, val); + regmap_write(ldev->regmap, LTDC_L1CLUTWR, val); } } @@ -535,13 +523,13 @@ static void ltdc_crtc_atomic_enable(struct drm_crtc *crtc, pm_runtime_get_sync(ddev->dev); /* Sets the background color value */ - reg_write(ldev->regs, LTDC_BCCR, BCCR_BCBLACK); + regmap_write(ldev->regmap, LTDC_BCCR, BCCR_BCBLACK); /* Enable IRQ */ - reg_set(ldev->regs, LTDC_IER, IER_RRIE | IER_FUIE | IER_TERRIE); + regmap_set_bits(ldev->regmap, LTDC_IER, IER_RRIE | IER_FUIE | IER_TERRIE); /* Commit shadow registers = update planes at next vblank */ - reg_set(ldev->regs, LTDC_SRCR, SRCR_VBR); + regmap_set_bits(ldev->regmap, LTDC_SRCR, SRCR_VBR); drm_crtc_vblank_on(crtc); } @@ -557,10 +545,10 @@ static void ltdc_crtc_atomic_disable(struct drm_crtc *crtc, drm_crtc_vblank_off(crtc); /* disable IRQ */ - reg_clear(ldev->regs, LTDC_IER, IER_RRIE | IER_FUIE | IER_TERRIE); + regmap_clear_bits(ldev->regmap, LTDC_IER, IER_RRIE | IER_FUIE | IER_TERRIE); /* immediately commit disable of layers before switching off LTDC */ - reg_set(ldev->regs, LTDC_SRCR, SRCR_IMR); + regmap_set_bits(ldev->regmap, LTDC_SRCR, SRCR_IMR); pm_runtime_put_sync(ddev->dev); } @@ -708,26 +696,26 @@ static void ltdc_crtc_mode_set_nofb(struct drm_crtc *crtc) if (bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE) val |= GCR_PCPOL; - reg_update_bits(ldev->regs, LTDC_GCR, - GCR_HSPOL | GCR_VSPOL | GCR_DEPOL | GCR_PCPOL, val); + regmap_update_bits(ldev->regmap, LTDC_GCR, + GCR_HSPOL | GCR_VSPOL | GCR_DEPOL | GCR_PCPOL, val); /* Set Synchronization size */ val = (hsync << 16) | vsync; - reg_update_bits(ldev->regs, LTDC_SSCR, SSCR_VSH | SSCR_HSW, val); + regmap_update_bits(ldev->regmap, LTDC_SSCR, SSCR_VSH | SSCR_HSW, val); /* Set Accumulated Back porch */ val = (accum_hbp << 16) | accum_vbp; - reg_update_bits(ldev->regs, LTDC_BPCR, BPCR_AVBP | BPCR_AHBP, val); + regmap_update_bits(ldev->regmap, LTDC_BPCR, BPCR_AVBP | BPCR_AHBP, val); /* Set Accumulated Active Width */ val = (accum_act_w << 16) | accum_act_h; - reg_update_bits(ldev->regs, LTDC_AWCR, AWCR_AAW | AWCR_AAH, val); + regmap_update_bits(ldev->regmap, LTDC_AWCR, AWCR_AAW | AWCR_AAH, val); /* Set total width & height */ val = (total_width << 16) | total_height; - reg_update_bits(ldev->regs, LTDC_TWCR, TWCR_TOTALH | TWCR_TOTALW, val); + regmap_update_bits(ldev->regmap, LTDC_TWCR, TWCR_TOTALH | TWCR_TOTALW, val); - reg_write(ldev->regs, LTDC_LIPCR, (accum_act_h + 1)); + regmap_write(ldev->regmap, LTDC_LIPCR, (accum_act_h + 1)); } static void ltdc_crtc_atomic_flush(struct drm_crtc *crtc, @@ -742,7 +730,7 @@ static void ltdc_crtc_atomic_flush(struct drm_crtc *crtc, ltdc_crtc_update_clut(crtc); /* Commit shadow registers = update planes at next vblank */ - reg_set(ldev->regs, LTDC_SRCR, SRCR_VBR); + regmap_set_bits(ldev->regmap, LTDC_SRCR, SRCR_VBR); if (event) { crtc->state->event = NULL; @@ -784,10 +772,14 @@ static bool ltdc_crtc_get_scanout_position(struct drm_crtc *crtc, * simplify the code and only test if line > vactive_end */ if (pm_runtime_active(ddev->dev)) { - line = reg_read(ldev->regs, LTDC_CPSR) & CPSR_CYPOS; - vactive_start = reg_read(ldev->regs, LTDC_BPCR) & BPCR_AVBP; - vactive_end = reg_read(ldev->regs, LTDC_AWCR) & AWCR_AAH; - vtotal = reg_read(ldev->regs, LTDC_TWCR) & TWCR_TOTALH; + regmap_read(ldev->regmap, LTDC_CPSR, &line); + line &= CPSR_CYPOS; + regmap_read(ldev->regmap, LTDC_BPCR, &vactive_start); + vactive_start &= BPCR_AVBP; + regmap_read(ldev->regmap, LTDC_AWCR, &vactive_end); + vactive_end &= AWCR_AAH; + regmap_read(ldev->regmap, LTDC_TWCR, &vtotal); + vtotal &= TWCR_TOTALH; if (line > vactive_end) *vpos = line - vtotal - vactive_start; @@ -823,7 +815,7 @@ static int ltdc_crtc_enable_vblank(struct drm_crtc *crtc) DRM_DEBUG_DRIVER("\n"); if (state->enable) - reg_set(ldev->regs, LTDC_IER, IER_LIE); + regmap_set_bits(ldev->regmap, LTDC_IER, IER_LIE); else return -EPERM; @@ -835,7 +827,7 @@ static void ltdc_crtc_disable_vblank(struct drm_crtc *crtc) struct ltdc_device *ldev = crtc_to_ltdc(crtc); DRM_DEBUG_DRIVER("\n"); - reg_clear(ldev->regs, LTDC_IER, IER_LIE); + regmap_clear_bits(ldev->regmap, LTDC_IER, IER_LIE); } static const struct drm_crtc_funcs ltdc_crtc_funcs = { @@ -913,19 +905,20 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane, newstate->crtc_w, newstate->crtc_h, newstate->crtc_x, newstate->crtc_y); - bpcr = reg_read(ldev->regs, LTDC_BPCR); + regmap_read(ldev->regmap, LTDC_BPCR, &bpcr); + ahbp = (bpcr & BPCR_AHBP) >> 16; avbp = bpcr & BPCR_AVBP; /* Configures the horizontal start and stop position */ val = ((x1 + 1 + ahbp) << 16) + (x0 + 1 + ahbp); - reg_update_bits(ldev->regs, LTDC_L1WHPCR + lofs, - LXWHPCR_WHSTPOS | LXWHPCR_WHSPPOS, val); + regmap_write_bits(ldev->regmap, LTDC_L1WHPCR + lofs, + LXWHPCR_WHSTPOS | LXWHPCR_WHSPPOS, val); /* Configures the vertical start and stop position */ val = ((y1 + 1 + avbp) << 16) + (y0 + 1 + avbp); - reg_update_bits(ldev->regs, LTDC_L1WVPCR + lofs, - LXWVPCR_WVSTPOS | LXWVPCR_WVSPPOS, val); + regmap_write_bits(ldev->regmap, LTDC_L1WVPCR + lofs, + LXWVPCR_WVSTPOS | LXWVPCR_WVSPPOS, val); /* Specifies the pixel format */ pf = to_ltdc_pixelformat(fb->format->format); @@ -938,19 +931,18 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane, (char *)&fb->format->format); val = 0; /* set by default ARGB 32 bits */ } - reg_update_bits(ldev->regs, LTDC_L1PFCR + lofs, LXPFCR_PF, val); + regmap_write_bits(ldev->regmap, LTDC_L1PFCR + lofs, LXPFCR_PF, val); /* Configures the color frame buffer pitch in bytes & line length */ pitch_in_bytes = fb->pitches[0]; line_length = fb->format->cpp[0] * (x1 - x0 + 1) + (ldev->caps.bus_width >> 3) - 1; val = ((pitch_in_bytes << 16) | line_length); - reg_update_bits(ldev->regs, LTDC_L1CFBLR + lofs, - LXCFBLR_CFBLL | LXCFBLR_CFBP, val); + regmap_write_bits(ldev->regmap, LTDC_L1CFBLR + lofs, LXCFBLR_CFBLL | LXCFBLR_CFBP, val); /* Specifies the constant alpha value */ val = newstate->alpha >> 8; - reg_update_bits(ldev->regs, LTDC_L1CACR + lofs, LXCACR_CONSTA, val); + regmap_write_bits(ldev->regmap, LTDC_L1CACR + lofs, LXCACR_CONSTA, val); /* Specifies the blending factors */ val = BF1_PAXCA | BF2_1PAXCA; @@ -962,24 +954,22 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane, plane->type != DRM_PLANE_TYPE_PRIMARY) val = BF1_PAXCA | BF2_1PAXCA; - reg_update_bits(ldev->regs, LTDC_L1BFCR + lofs, - LXBFCR_BF2 | LXBFCR_BF1, val); + regmap_write_bits(ldev->regmap, LTDC_L1BFCR + lofs, LXBFCR_BF2 | LXBFCR_BF1, val); /* Configures the frame buffer line number */ val = y1 - y0 + 1; - reg_update_bits(ldev->regs, LTDC_L1CFBLNR + lofs, LXCFBLNR_CFBLN, val); + regmap_write_bits(ldev->regmap, LTDC_L1CFBLNR + lofs, LXCFBLNR_CFBLN, val); /* Sets the FB address */ paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 0); DRM_DEBUG_DRIVER("fb: phys 0x%08x", paddr); - reg_write(ldev->regs, LTDC_L1CFBAR + lofs, paddr); + regmap_write(ldev->regmap, LTDC_L1CFBAR + lofs, paddr); /* Enable layer and CLUT if needed */ val = fb->format->format == DRM_FORMAT_C8 ? LXCR_CLUTEN : 0; val |= LXCR_LEN; - reg_update_bits(ldev->regs, LTDC_L1CR + lofs, - LXCR_LEN | LXCR_CLUTEN, val); + regmap_write_bits(ldev->regmap, LTDC_L1CR + lofs, LXCR_LEN | LXCR_CLUTEN, val); ldev->plane_fpsi[plane->index].counter++; @@ -1004,7 +994,7 @@ static void ltdc_plane_atomic_disable(struct drm_plane *plane, u32 lofs = plane->index * LAY_OFS; /* disable layer */ - reg_clear(ldev->regs, LTDC_L1CR + lofs, LXCR_LEN); + regmap_write_bits(ldev->regmap, LTDC_L1CR + lofs, LXCR_LEN, 0); DRM_DEBUG_DRIVER("CRTC:%d plane:%d\n", oldstate->crtc->base.id, plane->base.id); @@ -1172,7 +1162,7 @@ static void ltdc_encoder_disable(struct drm_encoder *encoder) DRM_DEBUG_DRIVER("\n"); /* Disable LTDC */ - reg_clear(ldev->regs, LTDC_GCR, GCR_LTDCEN); + regmap_clear_bits(ldev->regmap, LTDC_GCR, GCR_LTDCEN); /* Set to sleep state the pinctrl whatever type of encoder */ pinctrl_pm_select_sleep_state(ddev->dev); @@ -1186,7 +1176,7 @@ static void ltdc_encoder_enable(struct drm_encoder *encoder) DRM_DEBUG_DRIVER("\n"); /* Enable LTDC */ - reg_set(ldev->regs, LTDC_GCR, GCR_LTDCEN); + regmap_set_bits(ldev->regmap, LTDC_GCR, GCR_LTDCEN); } static void ltdc_encoder_mode_set(struct drm_encoder *encoder, @@ -1249,15 +1239,15 @@ static int ltdc_get_caps(struct drm_device *ddev) * at least 1 layer must be managed & the number of layers * must not exceed LTDC_MAX_LAYER */ - lcr = reg_read(ldev->regs, LTDC_LCR); + regmap_read(ldev->regmap, LTDC_LCR, &lcr); ldev->caps.nb_layers = clamp((int)lcr, 1, LTDC_MAX_LAYER); /* set data bus width */ - gc2r = reg_read(ldev->regs, LTDC_GC2R); + regmap_read(ldev->regmap, LTDC_GC2R, &gc2r); bus_width_log2 = (gc2r & GC2R_BW) >> 4; ldev->caps.bus_width = 8 << bus_width_log2; - ldev->caps.hw_version = reg_read(ldev->regs, LTDC_IDR); + regmap_read(ldev->regmap, LTDC_IDR, &ldev->caps.hw_version); switch (ldev->caps.hw_version) { case HWVER_10200: @@ -1410,9 +1400,15 @@ int ltdc_load(struct drm_device *ddev) goto err; } + ldev->regmap = devm_regmap_init_mmio(&pdev->dev, ldev->regs, &stm32_ltdc_regmap_cfg); + if (IS_ERR(ldev->regmap)) { + DRM_ERROR("Unable to regmap ltdc registers\n"); + ret = PTR_ERR(ldev->regmap); + goto err; + } + /* Disable interrupts */ - reg_clear(ldev->regs, LTDC_IER, - IER_LIE | IER_RRIE | IER_FUIE | IER_TERRIE); + regmap_clear_bits(ldev->regmap, LTDC_IER, IER_LIE | IER_RRIE | IER_FUIE | IER_TERRIE); ret = ltdc_get_caps(ddev); if (ret) { diff --git a/drivers/gpu/drm/stm/ltdc.h b/drivers/gpu/drm/stm/ltdc.h index 55a125f89af6..20b3dcc7817b 100644 --- a/drivers/gpu/drm/stm/ltdc.h +++ b/drivers/gpu/drm/stm/ltdc.h @@ -32,6 +32,7 @@ struct fps_info { struct ltdc_device { void __iomem *regs; + struct regmap *regmap; struct clk *pixel_clk; /* lcd pixel clock */ struct mutex err_lock; /* protecting error_status */ struct ltdc_caps caps; -- cgit From fb998edf9edc21c9925560860cc36136814a707a Mon Sep 17 00:00:00 2001 From: Yannick Fertre Date: Wed, 15 Dec 2021 22:47:50 +0100 Subject: drm/stm: ltdc: add YCbCr 422 output support LTDC 40100 hw version supports the YCbCr 422 output, reducing the output pins from 24 to 16. This feature is useful for some external devices like HDMI bridges. Both ITU-R BT.601 & ITU-R BT.709 are supported. It is also possible to choose the chrominance order between * Cb is output first (Y0Cb, then Y1Cr, Y2Cb and so on). * Cr is output first (Y0Cr, then Y1Cb, Y2Cr and so on). Signed-off-by: Yannick Fertre Acked-by: Philippe Cornu Reviewed-by: Philippe Cornu Reviewed-by: Raphael Gallais-Pou Signed-off-by: Philippe Cornu Link: https://patchwork.freedesktop.org/patch/msgid/20211215214750.20105-1-yannick.fertre@foss.st.com --- drivers/gpu/drm/stm/ltdc.c | 44 +++++++++++++++++++++++++++++++++++++++++++- drivers/gpu/drm/stm/ltdc.h | 1 + 2 files changed, 44 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/stm/ltdc.c b/drivers/gpu/drm/stm/ltdc.c index 8dad3d00aa5c..b819f4cbcc3d 100644 --- a/drivers/gpu/drm/stm/ltdc.c +++ b/drivers/gpu/drm/stm/ltdc.c @@ -76,6 +76,7 @@ #define LTDC_LIPCR 0x0040 /* Line Interrupt Position Conf. */ #define LTDC_CPSR 0x0044 /* Current Position Status */ #define LTDC_CDSR 0x0048 /* Current Display Status */ +#define LTDC_EDCR 0x0060 /* External Display Control */ #define LTDC_FUT 0x0090 /* Fifo underrun Threshold */ /* Layer register offsets */ @@ -170,6 +171,10 @@ #define ISR_TERRIF BIT(2) /* Transfer ERRor Interrupt Flag */ #define ISR_RRIF BIT(3) /* Register Reload Interrupt Flag */ +#define EDCR_OCYEN BIT(25) /* Output Conversion to YCbCr 422: ENable */ +#define EDCR_OCYSEL BIT(26) /* Output Conversion to YCbCr 422: SELection of the CCIR */ +#define EDCR_OCYCO BIT(27) /* Output Conversion to YCbCr 422: Chrominance Order */ + #define LXCR_LEN BIT(0) /* Layer ENable */ #define LXCR_COLKEN BIT(1) /* Color Keying Enable */ #define LXCR_CLUTEN BIT(4) /* Color Look-Up Table ENable */ @@ -625,6 +630,7 @@ static void ltdc_crtc_mode_set_nofb(struct drm_crtc *crtc) struct drm_display_mode *mode = &crtc->state->adjusted_mode; u32 hsync, vsync, accum_hbp, accum_vbp, accum_act_w, accum_act_h; u32 total_width, total_height; + u32 bus_formats = MEDIA_BUS_FMT_RGB888_1X24; u32 bus_flags = 0; u32 val; int ret; @@ -650,8 +656,11 @@ static void ltdc_crtc_mode_set_nofb(struct drm_crtc *crtc) if (bridge && bridge->timings) bus_flags = bridge->timings->input_bus_flags; - else if (connector) + else if (connector) { bus_flags = connector->display_info.bus_flags; + if (connector->display_info.num_bus_formats) + bus_formats = connector->display_info.bus_formats[0]; + } if (!pm_runtime_active(ddev->dev)) { ret = pm_runtime_get_sync(ddev->dev); @@ -716,6 +725,36 @@ static void ltdc_crtc_mode_set_nofb(struct drm_crtc *crtc) regmap_update_bits(ldev->regmap, LTDC_TWCR, TWCR_TOTALH | TWCR_TOTALW, val); regmap_write(ldev->regmap, LTDC_LIPCR, (accum_act_h + 1)); + + /* Configure the output format (hw version dependent) */ + if (ldev->caps.ycbcr_output) { + /* Input video dynamic_range & colorimetry */ + int vic = drm_match_cea_mode(mode); + u32 val; + + if (vic == 6 || vic == 7 || vic == 21 || vic == 22 || + vic == 2 || vic == 3 || vic == 17 || vic == 18) + /* ITU-R BT.601 */ + val = 0; + else + /* ITU-R BT.709 */ + val = EDCR_OCYSEL; + + switch (bus_formats) { + case MEDIA_BUS_FMT_YUYV8_1X16: + /* enable ycbcr output converter */ + regmap_write(ldev->regmap, LTDC_EDCR, EDCR_OCYEN | val); + break; + case MEDIA_BUS_FMT_YVYU8_1X16: + /* enable ycbcr output converter & invert chrominance order */ + regmap_write(ldev->regmap, LTDC_EDCR, EDCR_OCYEN | EDCR_OCYCO | val); + break; + default: + /* disable ycbcr output converter */ + regmap_write(ldev->regmap, LTDC_EDCR, 0); + break; + } + } } static void ltdc_crtc_atomic_flush(struct drm_crtc *crtc, @@ -1267,6 +1306,7 @@ static int ltdc_get_caps(struct drm_device *ddev) if (ldev->caps.hw_version == HWVER_10200) ldev->caps.pad_max_freq_hz = 65000000; ldev->caps.nb_irq = 2; + ldev->caps.ycbcr_output = false; break; case HWVER_20101: ldev->caps.layer_ofs = LAY_OFS_0; @@ -1275,6 +1315,7 @@ static int ltdc_get_caps(struct drm_device *ddev) ldev->caps.non_alpha_only_l1 = false; ldev->caps.pad_max_freq_hz = 150000000; ldev->caps.nb_irq = 4; + ldev->caps.ycbcr_output = false; break; case HWVER_40100: ldev->caps.layer_ofs = LAY_OFS_1; @@ -1283,6 +1324,7 @@ static int ltdc_get_caps(struct drm_device *ddev) ldev->caps.non_alpha_only_l1 = false; ldev->caps.pad_max_freq_hz = 90000000; ldev->caps.nb_irq = 2; + ldev->caps.ycbcr_output = true; break; default: return -ENODEV; diff --git a/drivers/gpu/drm/stm/ltdc.h b/drivers/gpu/drm/stm/ltdc.h index 20b3dcc7817b..f04fcebb5223 100644 --- a/drivers/gpu/drm/stm/ltdc.h +++ b/drivers/gpu/drm/stm/ltdc.h @@ -21,6 +21,7 @@ struct ltdc_caps { bool non_alpha_only_l1; /* non-native no-alpha formats on layer 1 */ int pad_max_freq_hz; /* max frequency supported by pad */ int nb_irq; /* number of hardware interrupts */ + bool ycbcr_output; /* ycbcr output converter supported */ }; #define LTDC_MAX_LAYER 4 -- cgit From a55d08e0d4945b0374c6dd0e8b3f9b28ce5720ed Mon Sep 17 00:00:00 2001 From: Yannick Fertre Date: Wed, 15 Dec 2021 22:48:17 +0100 Subject: drm/stm: ltdc: add per plane update support Recent ltdc hardware versions offer the ability to update a plane independently of others planes. This is could be useful especially if a plane is assigned to another OS. Signed-off-by: Yannick Fertre Acked-by: Philippe Cornu Reviewed-by: Philippe Cornu Reviewed-by: Raphael Gallais-Pou Signed-off-by: Philippe Cornu Link: https://patchwork.freedesktop.org/patch/msgid/20211215214817.20310-1-yannick.fertre@foss.st.com --- drivers/gpu/drm/stm/ltdc.c | 26 +++++++++++++++++++++++--- drivers/gpu/drm/stm/ltdc.h | 1 + 2 files changed, 24 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/stm/ltdc.c b/drivers/gpu/drm/stm/ltdc.c index b819f4cbcc3d..862d43fe3087 100644 --- a/drivers/gpu/drm/stm/ltdc.c +++ b/drivers/gpu/drm/stm/ltdc.c @@ -197,6 +197,10 @@ #define LXCFBLNR_CFBLN GENMASK(10, 0) /* Color Frame Buffer Line Number */ +#define LXRCR_IMR BIT(0) /* IMmediate Reload */ +#define LXRCR_VBR BIT(1) /* Vertical Blanking Reload */ +#define LXRCR_GRMSK BIT(2) /* Global (centralized) Reload MaSKed */ + #define CLUT_SIZE 256 #define CONSTA_MAX 0xFF /* CONSTant Alpha MAX= 1.0 */ @@ -534,7 +538,8 @@ static void ltdc_crtc_atomic_enable(struct drm_crtc *crtc, regmap_set_bits(ldev->regmap, LTDC_IER, IER_RRIE | IER_FUIE | IER_TERRIE); /* Commit shadow registers = update planes at next vblank */ - regmap_set_bits(ldev->regmap, LTDC_SRCR, SRCR_VBR); + if (!ldev->caps.plane_reg_shadow) + regmap_set_bits(ldev->regmap, LTDC_SRCR, SRCR_VBR); drm_crtc_vblank_on(crtc); } @@ -553,7 +558,8 @@ static void ltdc_crtc_atomic_disable(struct drm_crtc *crtc, regmap_clear_bits(ldev->regmap, LTDC_IER, IER_RRIE | IER_FUIE | IER_TERRIE); /* immediately commit disable of layers before switching off LTDC */ - regmap_set_bits(ldev->regmap, LTDC_SRCR, SRCR_IMR); + if (!ldev->caps.plane_reg_shadow) + regmap_set_bits(ldev->regmap, LTDC_SRCR, SRCR_IMR); pm_runtime_put_sync(ddev->dev); } @@ -769,7 +775,8 @@ static void ltdc_crtc_atomic_flush(struct drm_crtc *crtc, ltdc_crtc_update_clut(crtc); /* Commit shadow registers = update planes at next vblank */ - regmap_set_bits(ldev->regmap, LTDC_SRCR, SRCR_VBR); + if (!ldev->caps.plane_reg_shadow) + regmap_set_bits(ldev->regmap, LTDC_SRCR, SRCR_VBR); if (event) { crtc->state->event = NULL; @@ -1010,6 +1017,11 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane, val |= LXCR_LEN; regmap_write_bits(ldev->regmap, LTDC_L1CR + lofs, LXCR_LEN | LXCR_CLUTEN, val); + /* Commit shadow registers = update plane at next vblank */ + if (ldev->caps.plane_reg_shadow) + regmap_write_bits(ldev->regmap, LTDC_L1RCR + lofs, + LXRCR_IMR | LXRCR_VBR | LXRCR_GRMSK, LXRCR_VBR); + ldev->plane_fpsi[plane->index].counter++; mutex_lock(&ldev->err_lock); @@ -1035,6 +1047,11 @@ static void ltdc_plane_atomic_disable(struct drm_plane *plane, /* disable layer */ regmap_write_bits(ldev->regmap, LTDC_L1CR + lofs, LXCR_LEN, 0); + /* Commit shadow registers = update plane at next vblank */ + if (ldev->caps.plane_reg_shadow) + regmap_write_bits(ldev->regmap, LTDC_L1RCR + lofs, + LXRCR_IMR | LXRCR_VBR | LXRCR_GRMSK, LXRCR_VBR); + DRM_DEBUG_DRIVER("CRTC:%d plane:%d\n", oldstate->crtc->base.id, plane->base.id); } @@ -1307,6 +1324,7 @@ static int ltdc_get_caps(struct drm_device *ddev) ldev->caps.pad_max_freq_hz = 65000000; ldev->caps.nb_irq = 2; ldev->caps.ycbcr_output = false; + ldev->caps.plane_reg_shadow = false; break; case HWVER_20101: ldev->caps.layer_ofs = LAY_OFS_0; @@ -1316,6 +1334,7 @@ static int ltdc_get_caps(struct drm_device *ddev) ldev->caps.pad_max_freq_hz = 150000000; ldev->caps.nb_irq = 4; ldev->caps.ycbcr_output = false; + ldev->caps.plane_reg_shadow = false; break; case HWVER_40100: ldev->caps.layer_ofs = LAY_OFS_1; @@ -1325,6 +1344,7 @@ static int ltdc_get_caps(struct drm_device *ddev) ldev->caps.pad_max_freq_hz = 90000000; ldev->caps.nb_irq = 2; ldev->caps.ycbcr_output = true; + ldev->caps.plane_reg_shadow = true; break; default: return -ENODEV; diff --git a/drivers/gpu/drm/stm/ltdc.h b/drivers/gpu/drm/stm/ltdc.h index f04fcebb5223..68a5a199e320 100644 --- a/drivers/gpu/drm/stm/ltdc.h +++ b/drivers/gpu/drm/stm/ltdc.h @@ -22,6 +22,7 @@ struct ltdc_caps { int pad_max_freq_hz; /* max frequency supported by pad */ int nb_irq; /* number of hardware interrupts */ bool ycbcr_output; /* ycbcr output converter supported */ + bool plane_reg_shadow; /* plane shadow registers ability */ }; #define LTDC_MAX_LAYER 4 -- cgit From 8f2b5f6dcbdaa507e4c4456e6c1b41691117e1d0 Mon Sep 17 00:00:00 2001 From: Yannick Fertre Date: Wed, 15 Dec 2021 22:48:35 +0100 Subject: drm/stm: ltdc: add support of flexible pixel formats This feature allows the generation of any RGB pixel format. The list of supported formats is no longer linked to the register LXPFCR_PF, that the reason why a list of drm formats is defined for each display controller version. Signed-off-by: Yannick Fertre Acked-by: Philippe Cornu Reviewed-by: Philippe Cornu Reviewed-by: Raphael Gallais-Pou Tested-by: Raphael Gallais-Pou Signed-off-by: Philippe Cornu Link: https://patchwork.freedesktop.org/patch/msgid/20211215214835.20593-1-yannick.fertre@foss.st.com --- drivers/gpu/drm/stm/ltdc.c | 196 ++++++++++++++++++++++++++++++++------------- drivers/gpu/drm/stm/ltdc.h | 5 +- 2 files changed, 145 insertions(+), 56 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/stm/ltdc.c b/drivers/gpu/drm/stm/ltdc.c index 862d43fe3087..4d249bc99894 100644 --- a/drivers/gpu/drm/stm/ltdc.c +++ b/drivers/gpu/drm/stm/ltdc.c @@ -186,6 +186,7 @@ #define LXWVPCR_WVSPPOS GENMASK(26, 16) /* Window Vertical StoP POSition */ #define LXPFCR_PF GENMASK(2, 0) /* Pixel Format */ +#define PF_FLEXIBLE 0x7 /* Flexible Pixel Format selected */ #define LXCACR_CONSTA GENMASK(7, 0) /* CONSTant Alpha */ @@ -216,17 +217,18 @@ enum ltdc_pix_fmt { /* RGB formats */ PF_ARGB8888, /* ARGB [32 bits] */ PF_RGBA8888, /* RGBA [32 bits] */ + PF_ABGR8888, /* ABGR [32 bits] */ + PF_BGRA8888, /* BGRA [32 bits] */ PF_RGB888, /* RGB [24 bits] */ + PF_BGR888, /* BGR [24 bits] */ PF_RGB565, /* RGB [16 bits] */ + PF_BGR565, /* BGR [16 bits] */ PF_ARGB1555, /* ARGB A:1 bit RGB:15 bits [16 bits] */ PF_ARGB4444, /* ARGB A:4 bits R/G/B: 4 bits each [16 bits] */ /* Indexed formats */ PF_L8, /* Indexed 8 bits [8 bits] */ PF_AL44, /* Alpha:4 bits + indexed 4 bits [8 bits] */ - PF_AL88, /* Alpha:8 bits + indexed 8 bits [16 bits] */ - PF_ABGR8888, /* ABGR [32 bits] */ - PF_BGRA8888, /* BGRA [32 bits] */ - PF_BGR565 /* RGB [16 bits] */ + PF_AL88 /* Alpha:8 bits + indexed 8 bits [16 bits] */ }; /* The index gives the encoding of the pixel format for an HW version */ @@ -260,7 +262,53 @@ static const enum ltdc_pix_fmt ltdc_pix_fmt_a2[NB_PF] = { PF_RGB565, /* 0x04 */ PF_BGR565, /* 0x05 */ PF_RGB888, /* 0x06 */ - PF_ARGB1555 /* 0x07 */ + PF_NONE /* 0x07 */ +}; + +static const u32 ltdc_drm_fmt_a0[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_RGB565, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_XRGB4444, + DRM_FORMAT_C8 +}; + +static const u32 ltdc_drm_fmt_a1[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_RGB565, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_XRGB4444, + DRM_FORMAT_C8 +}; + +static const u32 ltdc_drm_fmt_a2[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR565, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_XRGB4444, + DRM_FORMAT_C8 }; /* Layer register offsets */ @@ -386,16 +434,30 @@ static inline enum ltdc_pix_fmt to_ltdc_pixelformat(u32 drm_fmt) case DRM_FORMAT_XRGB8888: pf = PF_ARGB8888; break; + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + pf = PF_ABGR8888; + break; case DRM_FORMAT_RGBA8888: case DRM_FORMAT_RGBX8888: pf = PF_RGBA8888; break; + case DRM_FORMAT_BGRA8888: + case DRM_FORMAT_BGRX8888: + pf = PF_BGRA8888; + break; case DRM_FORMAT_RGB888: pf = PF_RGB888; break; + case DRM_FORMAT_BGR888: + pf = PF_BGR888; + break; case DRM_FORMAT_RGB565: pf = PF_RGB565; break; + case DRM_FORMAT_BGR565: + pf = PF_BGR565; + break; case DRM_FORMAT_ARGB1555: case DRM_FORMAT_XRGB1555: pf = PF_ARGB1555; @@ -416,49 +478,66 @@ static inline enum ltdc_pix_fmt to_ltdc_pixelformat(u32 drm_fmt) return pf; } -static inline u32 to_drm_pixelformat(enum ltdc_pix_fmt pf) +static inline u32 ltdc_set_flexible_pixel_format(struct drm_plane *plane, enum ltdc_pix_fmt pix_fmt) { - switch (pf) { - case PF_ARGB8888: - return DRM_FORMAT_ARGB8888; - case PF_RGBA8888: - return DRM_FORMAT_RGBA8888; - case PF_RGB888: - return DRM_FORMAT_RGB888; - case PF_RGB565: - return DRM_FORMAT_RGB565; + struct ltdc_device *ldev = plane_to_ltdc(plane); + u32 lofs = plane->index * LAY_OFS, ret = PF_FLEXIBLE; + int psize, alen, apos, rlen, rpos, glen, gpos, blen, bpos; + + switch (pix_fmt) { + case PF_BGR888: + psize = 3; + alen = 0; apos = 0; rlen = 8; rpos = 0; + glen = 8; gpos = 8; blen = 8; bpos = 16; + break; case PF_ARGB1555: - return DRM_FORMAT_ARGB1555; + psize = 2; + alen = 1; apos = 15; rlen = 5; rpos = 10; + glen = 5; gpos = 5; blen = 5; bpos = 0; + break; case PF_ARGB4444: - return DRM_FORMAT_ARGB4444; + psize = 2; + alen = 4; apos = 12; rlen = 4; rpos = 8; + glen = 4; gpos = 4; blen = 4; bpos = 0; + break; case PF_L8: - return DRM_FORMAT_C8; - case PF_AL44: /* No DRM support */ - case PF_AL88: /* No DRM support */ - case PF_NONE: + psize = 1; + alen = 0; apos = 0; rlen = 8; rpos = 0; + glen = 8; gpos = 0; blen = 8; bpos = 0; + break; + case PF_AL44: + psize = 1; + alen = 4; apos = 4; rlen = 4; rpos = 0; + glen = 4; gpos = 0; blen = 4; bpos = 0; + break; + case PF_AL88: + psize = 2; + alen = 8; apos = 8; rlen = 8; rpos = 0; + glen = 8; gpos = 0; blen = 8; bpos = 0; + break; default: - return 0; + ret = NB_PF; /* error case, trace msg is handled by the caller */ + break; + } + + if (ret == PF_FLEXIBLE) { + regmap_write(ldev->regmap, LTDC_L1FPF0R + lofs, + (rlen << 14) + (rpos << 9) + (alen << 5) + apos); + + regmap_write(ldev->regmap, LTDC_L1FPF1R + lofs, + (psize << 18) + (blen << 14) + (bpos << 9) + (glen << 5) + gpos); } + + return ret; } -static inline u32 get_pixelformat_without_alpha(u32 drm) +/* + * All non-alpha color formats derived from native alpha color formats are + * either characterized by a FourCC format code + */ +static inline u32 is_xrgb(u32 drm) { - switch (drm) { - case DRM_FORMAT_ARGB4444: - return DRM_FORMAT_XRGB4444; - case DRM_FORMAT_RGBA4444: - return DRM_FORMAT_RGBX4444; - case DRM_FORMAT_ARGB1555: - return DRM_FORMAT_XRGB1555; - case DRM_FORMAT_RGBA5551: - return DRM_FORMAT_RGBX5551; - case DRM_FORMAT_ARGB8888: - return DRM_FORMAT_XRGB8888; - case DRM_FORMAT_RGBA8888: - return DRM_FORMAT_RGBX8888; - default: - return 0; - } + return ((drm & 0xFF) == 'X' || ((drm >> 8) & 0xFF) == 'X'); } static irqreturn_t ltdc_irq_thread(int irq, void *arg) @@ -972,6 +1051,10 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane, if (ldev->caps.pix_fmt_hw[val] == pf) break; + /* Use the flexible color format feature if necessary and available */ + if (ldev->caps.pix_fmt_flex && val == NB_PF) + val = ltdc_set_flexible_pixel_format(plane, pf); + if (val == NB_PF) { DRM_ERROR("Pixel format %.4s not supported\n", (char *)&fb->format->format); @@ -1110,29 +1193,23 @@ static struct drm_plane *ltdc_plane_create(struct drm_device *ddev, struct device *dev = ddev->dev; struct drm_plane *plane; unsigned int i, nb_fmt = 0; - u32 formats[NB_PF * 2]; - u32 drm_fmt, drm_fmt_no_alpha; + u32 *formats; + u32 drm_fmt; const u64 *modifiers = ltdc_format_modifiers; int ret; - /* Get supported pixel formats */ - for (i = 0; i < NB_PF; i++) { - drm_fmt = to_drm_pixelformat(ldev->caps.pix_fmt_hw[i]); - if (!drm_fmt) - continue; - formats[nb_fmt++] = drm_fmt; + formats = devm_kzalloc(dev, ldev->caps.pix_fmt_nb * sizeof(*formats), GFP_KERNEL); - /* Add the no-alpha related format if any & supported */ - drm_fmt_no_alpha = get_pixelformat_without_alpha(drm_fmt); - if (!drm_fmt_no_alpha) - continue; + for (i = 0; i < ldev->caps.pix_fmt_nb; i++) { + drm_fmt = ldev->caps.pix_fmt_drm[i]; /* Manage hw-specific capabilities */ - if (ldev->caps.non_alpha_only_l1 && - type != DRM_PLANE_TYPE_PRIMARY) - continue; + if (ldev->caps.non_alpha_only_l1) + /* XR24 & RX24 like formats supported only on primary layer */ + if (type != DRM_PLANE_TYPE_PRIMARY && is_xrgb(drm_fmt)) + continue; - formats[nb_fmt++] = drm_fmt_no_alpha; + formats[nb_fmt++] = drm_fmt; } plane = devm_kzalloc(dev, sizeof(*plane), GFP_KERNEL); @@ -1311,6 +1388,9 @@ static int ltdc_get_caps(struct drm_device *ddev) ldev->caps.layer_ofs = LAY_OFS_0; ldev->caps.layer_regs = ltdc_layer_regs_a0; ldev->caps.pix_fmt_hw = ltdc_pix_fmt_a0; + ldev->caps.pix_fmt_drm = ltdc_drm_fmt_a0; + ldev->caps.pix_fmt_nb = ARRAY_SIZE(ltdc_drm_fmt_a0); + ldev->caps.pix_fmt_flex = false; /* * Hw older versions support non-alpha color formats derived * from native alpha color formats only on the primary layer. @@ -1330,6 +1410,9 @@ static int ltdc_get_caps(struct drm_device *ddev) ldev->caps.layer_ofs = LAY_OFS_0; ldev->caps.layer_regs = ltdc_layer_regs_a1; ldev->caps.pix_fmt_hw = ltdc_pix_fmt_a1; + ldev->caps.pix_fmt_drm = ltdc_drm_fmt_a1; + ldev->caps.pix_fmt_nb = ARRAY_SIZE(ltdc_drm_fmt_a1); + ldev->caps.pix_fmt_flex = false; ldev->caps.non_alpha_only_l1 = false; ldev->caps.pad_max_freq_hz = 150000000; ldev->caps.nb_irq = 4; @@ -1340,6 +1423,9 @@ static int ltdc_get_caps(struct drm_device *ddev) ldev->caps.layer_ofs = LAY_OFS_1; ldev->caps.layer_regs = ltdc_layer_regs_a2; ldev->caps.pix_fmt_hw = ltdc_pix_fmt_a2; + ldev->caps.pix_fmt_drm = ltdc_drm_fmt_a2; + ldev->caps.pix_fmt_nb = ARRAY_SIZE(ltdc_drm_fmt_a2); + ldev->caps.pix_fmt_flex = true; ldev->caps.non_alpha_only_l1 = false; ldev->caps.pad_max_freq_hz = 90000000; ldev->caps.nb_irq = 2; diff --git a/drivers/gpu/drm/stm/ltdc.h b/drivers/gpu/drm/stm/ltdc.h index 68a5a199e320..adc4f9cf7f95 100644 --- a/drivers/gpu/drm/stm/ltdc.h +++ b/drivers/gpu/drm/stm/ltdc.h @@ -17,7 +17,10 @@ struct ltdc_caps { u32 layer_ofs; /* layer offset for applicable regs */ const u32 *layer_regs; /* layer register offset */ u32 bus_width; /* bus width (32 or 64 bits) */ - const u32 *pix_fmt_hw; /* supported pixel formats */ + const u32 *pix_fmt_hw; /* supported hw pixel formats */ + const u32 *pix_fmt_drm; /* supported drm pixel formats */ + int pix_fmt_nb; /* number of pixel format */ + bool pix_fmt_flex; /* pixel format flexibility supported */ bool non_alpha_only_l1; /* non-native no-alpha formats on layer 1 */ int pad_max_freq_hz; /* max frequency supported by pad */ int nb_irq; /* number of hardware interrupts */ -- cgit From 484e72d3146bd3000a785f4709d5d44835ea8bbe Mon Sep 17 00:00:00 2001 From: Yannick Fertre Date: Wed, 15 Dec 2021 22:48:43 +0100 Subject: drm/stm: ltdc: add support of ycbcr pixel formats This patch adds the following YCbCr input pixel formats on the latest LTDC hardware version: 1 plane (co-planar) : YUYV, YVYU, UYVY, VYUY 2 planes (semi-planar): NV12, NV21 3 planes (full-planar): YU12=I420=DRM YUV420, YV12=DRM YVU420 Signed-off-by: Yannick Fertre Acked-by: Philippe Cornu Reviewed-by: Philippe Cornu Signed-off-by: Philippe Cornu Link: https://patchwork.freedesktop.org/patch/msgid/20211215214843.20703-1-yannick.fertre@foss.st.com --- drivers/gpu/drm/stm/ltdc.c | 251 +++++++++++++++++++++++++++++++++++++++++++-- drivers/gpu/drm/stm/ltdc.h | 1 + 2 files changed, 245 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/stm/ltdc.c b/drivers/gpu/drm/stm/ltdc.c index 4d249bc99894..7fd173390b9f 100644 --- a/drivers/gpu/drm/stm/ltdc.c +++ b/drivers/gpu/drm/stm/ltdc.c @@ -198,6 +198,21 @@ #define LXCFBLNR_CFBLN GENMASK(10, 0) /* Color Frame Buffer Line Number */ +#define LXCR_C1R_YIA BIT(0) /* Ycbcr 422 Interleaved Ability */ +#define LXCR_C1R_YSPA BIT(1) /* Ycbcr 420 Semi-Planar Ability */ +#define LXCR_C1R_YFPA BIT(2) /* Ycbcr 420 Full-Planar Ability */ +#define LXCR_C1R_SCA BIT(31) /* SCaling Ability*/ + +#define LxPCR_YREN BIT(9) /* Y Rescale Enable for the color dynamic range */ +#define LxPCR_OF BIT(8) /* Odd pixel First */ +#define LxPCR_CBF BIT(7) /* CB component First */ +#define LxPCR_YF BIT(6) /* Y component First */ +#define LxPCR_YCM GENMASK(5, 4) /* Ycbcr Conversion Mode */ +#define YCM_I 0x0 /* Interleaved 422 */ +#define YCM_SP 0x1 /* Semi-Planar 420 */ +#define YCM_FP 0x2 /* Full-Planar 420 */ +#define LxPCR_YCEN BIT(3) /* YCbCr-to-RGB Conversion Enable */ + #define LXRCR_IMR BIT(0) /* IMmediate Reload */ #define LXRCR_VBR BIT(1) /* Vertical Blanking Reload */ #define LXRCR_GRMSK BIT(2) /* Global (centralized) Reload MaSKed */ @@ -311,6 +326,23 @@ static const u32 ltdc_drm_fmt_a2[] = { DRM_FORMAT_C8 }; +static const u32 ltdc_drm_fmt_ycbcr_cp[] = { + DRM_FORMAT_YUYV, + DRM_FORMAT_YVYU, + DRM_FORMAT_UYVY, + DRM_FORMAT_VYUY +}; + +static const u32 ltdc_drm_fmt_ycbcr_sp[] = { + DRM_FORMAT_NV12, + DRM_FORMAT_NV21 +}; + +static const u32 ltdc_drm_fmt_ycbcr_fp[] = { + DRM_FORMAT_YUV420, + DRM_FORMAT_YVU420 +}; + /* Layer register offsets */ static const u32 ltdc_layer_regs_a0[] = { 0x80, /* L1 configuration 0 */ @@ -410,6 +442,26 @@ static const struct regmap_config stm32_ltdc_regmap_cfg = { .cache_type = REGCACHE_NONE, }; +static const u32 ltdc_ycbcr2rgb_coeffs[DRM_COLOR_ENCODING_MAX][DRM_COLOR_RANGE_MAX][2] = { + [DRM_COLOR_YCBCR_BT601][DRM_COLOR_YCBCR_LIMITED_RANGE] = { + 0x02040199, /* (b_cb = 516 / r_cr = 409) */ + 0x006400D0 /* (g_cb = 100 / g_cr = 208) */ + }, + [DRM_COLOR_YCBCR_BT601][DRM_COLOR_YCBCR_FULL_RANGE] = { + 0x01C60167, /* (b_cb = 454 / r_cr = 359) */ + 0x005800B7 /* (g_cb = 88 / g_cr = 183) */ + }, + [DRM_COLOR_YCBCR_BT709][DRM_COLOR_YCBCR_LIMITED_RANGE] = { + 0x021D01CB, /* (b_cb = 541 / r_cr = 459) */ + 0x00370089 /* (g_cb = 55 / g_cr = 137) */ + }, + [DRM_COLOR_YCBCR_BT709][DRM_COLOR_YCBCR_FULL_RANGE] = { + 0x01DB0193, /* (b_cb = 475 / r_cr = 403) */ + 0x00300078 /* (g_cb = 48 / g_cr = 120) */ + } + /* BT2020 not supported */ +}; + static inline struct ltdc_device *crtc_to_ltdc(struct drm_crtc *crtc) { return (struct ltdc_device *)crtc->dev->dev_private; @@ -540,6 +592,78 @@ static inline u32 is_xrgb(u32 drm) return ((drm & 0xFF) == 'X' || ((drm >> 8) & 0xFF) == 'X'); } +static inline void ltdc_set_ycbcr_config(struct drm_plane *plane, u32 drm_pix_fmt) +{ + struct ltdc_device *ldev = plane_to_ltdc(plane); + struct drm_plane_state *state = plane->state; + u32 lofs = plane->index * LAY_OFS; + u32 val; + + switch (drm_pix_fmt) { + case DRM_FORMAT_YUYV: + val = (YCM_I << 4) | LxPCR_YF | LxPCR_CBF; + break; + case DRM_FORMAT_YVYU: + val = (YCM_I << 4) | LxPCR_YF; + break; + case DRM_FORMAT_UYVY: + val = (YCM_I << 4) | LxPCR_CBF; + break; + case DRM_FORMAT_VYUY: + val = (YCM_I << 4); + break; + case DRM_FORMAT_NV12: + val = (YCM_SP << 4) | LxPCR_CBF; + break; + case DRM_FORMAT_NV21: + val = (YCM_SP << 4); + break; + case DRM_FORMAT_YUV420: + case DRM_FORMAT_YVU420: + val = (YCM_FP << 4); + break; + default: + /* RGB or not a YCbCr supported format */ + break; + } + + /* Enable limited range */ + if (state->color_range == DRM_COLOR_YCBCR_LIMITED_RANGE) + val |= LxPCR_YREN; + + /* enable ycbcr conversion */ + val |= LxPCR_YCEN; + + regmap_write(ldev->regmap, LTDC_L1PCR + lofs, val); +} + +static inline void ltdc_set_ycbcr_coeffs(struct drm_plane *plane) +{ + struct ltdc_device *ldev = plane_to_ltdc(plane); + struct drm_plane_state *state = plane->state; + enum drm_color_encoding enc = state->color_encoding; + enum drm_color_range ran = state->color_range; + u32 lofs = plane->index * LAY_OFS; + + if (enc != DRM_COLOR_YCBCR_BT601 && enc != DRM_COLOR_YCBCR_BT709) { + DRM_ERROR("color encoding %d not supported, use bt601 by default\n", enc); + /* set by default color encoding to DRM_COLOR_YCBCR_BT601 */ + enc = DRM_COLOR_YCBCR_BT601; + } + + if (ran != DRM_COLOR_YCBCR_LIMITED_RANGE && ran != DRM_COLOR_YCBCR_FULL_RANGE) { + DRM_ERROR("color range %d not supported, use limited range by default\n", ran); + /* set by default color range to DRM_COLOR_YCBCR_LIMITED_RANGE */ + ran = DRM_COLOR_YCBCR_LIMITED_RANGE; + } + + DRM_DEBUG_DRIVER("Color encoding=%d, range=%d\n", enc, ran); + regmap_write(ldev->regmap, LTDC_L1CYR0R + lofs, + ltdc_ycbcr2rgb_coeffs[enc][ran][0]); + regmap_write(ldev->regmap, LTDC_L1CYR1R + lofs, + ltdc_ycbcr2rgb_coeffs[enc][ran][1]); +} + static irqreturn_t ltdc_irq_thread(int irq, void *arg) { struct drm_device *ddev = arg; @@ -1010,7 +1134,7 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane, u32 y0 = newstate->crtc_y; u32 y1 = newstate->crtc_y + newstate->crtc_h - 1; u32 src_x, src_y, src_w, src_h; - u32 val, pitch_in_bytes, line_length, paddr, ahbp, avbp, bpcr; + u32 val, pitch_in_bytes, line_length, line_number, paddr, ahbp, avbp, bpcr; enum ltdc_pix_fmt pf; if (!newstate->crtc || !fb) { @@ -1086,8 +1210,8 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane, regmap_write_bits(ldev->regmap, LTDC_L1BFCR + lofs, LXBFCR_BF2 | LXBFCR_BF1, val); /* Configures the frame buffer line number */ - val = y1 - y0 + 1; - regmap_write_bits(ldev->regmap, LTDC_L1CFBLNR + lofs, LXCFBLNR_CFBLN, val); + line_number = y1 - y0 + 1; + regmap_write_bits(ldev->regmap, LTDC_L1CFBLNR + lofs, LXCFBLNR_CFBLN, line_number); /* Sets the FB address */ paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 0); @@ -1095,6 +1219,77 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane, DRM_DEBUG_DRIVER("fb: phys 0x%08x", paddr); regmap_write(ldev->regmap, LTDC_L1CFBAR + lofs, paddr); + if (ldev->caps.ycbcr_input) { + if (fb->format->is_yuv) { + switch (fb->format->format) { + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + /* Configure the auxiliary frame buffer address 0 & 1 */ + paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 1); + regmap_write(ldev->regmap, LTDC_L1AFBA0R + lofs, paddr); + regmap_write(ldev->regmap, LTDC_L1AFBA1R + lofs, paddr + 1); + + /* Configure the buffer length */ + val = ((pitch_in_bytes << 16) | line_length); + regmap_write(ldev->regmap, LTDC_L1AFBLR + lofs, val); + + /* Configure the frame buffer line number */ + val = (line_number >> 1); + regmap_write(ldev->regmap, LTDC_L1AFBLNR + lofs, val); + break; + case DRM_FORMAT_YUV420: + /* Configure the auxiliary frame buffer address 0 */ + paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 1); + regmap_write(ldev->regmap, LTDC_L1AFBA0R + lofs, paddr); + + /* Configure the auxiliary frame buffer address 1 */ + paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 2); + regmap_write(ldev->regmap, LTDC_L1AFBA1R + lofs, paddr); + + line_length = ((fb->format->cpp[0] * (x1 - x0 + 1)) >> 1) + + (ldev->caps.bus_width >> 3) - 1; + + /* Configure the buffer length */ + val = (((pitch_in_bytes >> 1) << 16) | line_length); + regmap_write(ldev->regmap, LTDC_L1AFBLR + lofs, val); + + /* Configure the frame buffer line number */ + val = (line_number >> 1); + regmap_write(ldev->regmap, LTDC_L1AFBLNR + lofs, val); + break; + case DRM_FORMAT_YVU420: + /* Configure the auxiliary frame buffer address 0 */ + paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 2); + regmap_write(ldev->regmap, LTDC_L1AFBA0R + lofs, paddr); + + /* Configure the auxiliary frame buffer address 1 */ + paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 1); + regmap_write(ldev->regmap, LTDC_L1AFBA1R + lofs, paddr); + + line_length = ((fb->format->cpp[0] * (x1 - x0 + 1)) >> 1) + + (ldev->caps.bus_width >> 3) - 1; + + /* Configure the buffer length */ + val = (((pitch_in_bytes >> 1) << 16) | line_length); + regmap_write(ldev->regmap, LTDC_L1AFBLR + lofs, val); + + /* Configure the frame buffer line number */ + val = (line_number >> 1); + regmap_write(ldev->regmap, LTDC_L1AFBLNR + lofs, val); + break; + } + + /* Configure YCbC conversion coefficient */ + ltdc_set_ycbcr_coeffs(plane); + + /* Configure YCbCr format and enable/disable conversion */ + ltdc_set_ycbcr_config(plane, fb->format->format); + } else { + /* disable ycbcr conversion */ + regmap_write(ldev->regmap, LTDC_L1PCR + lofs, 0); + } + } + /* Enable layer and CLUT if needed */ val = fb->format->format == DRM_FORMAT_C8 ? LXCR_CLUTEN : 0; val |= LXCR_LEN; @@ -1186,7 +1381,8 @@ static const struct drm_plane_helper_funcs ltdc_plane_helper_funcs = { }; static struct drm_plane *ltdc_plane_create(struct drm_device *ddev, - enum drm_plane_type type) + enum drm_plane_type type, + int index) { unsigned long possible_crtcs = CRTC_MASK; struct ltdc_device *ldev = ddev->dev_private; @@ -1196,9 +1392,16 @@ static struct drm_plane *ltdc_plane_create(struct drm_device *ddev, u32 *formats; u32 drm_fmt; const u64 *modifiers = ltdc_format_modifiers; + u32 lofs = index * LAY_OFS; + u32 val; int ret; - formats = devm_kzalloc(dev, ldev->caps.pix_fmt_nb * sizeof(*formats), GFP_KERNEL); + /* Allocate the biggest size according to supported color formats */ + formats = devm_kzalloc(dev, (ldev->caps.pix_fmt_nb + + ARRAY_SIZE(ltdc_drm_fmt_ycbcr_cp) + + ARRAY_SIZE(ltdc_drm_fmt_ycbcr_sp) + + ARRAY_SIZE(ltdc_drm_fmt_ycbcr_fp)) * + sizeof(*formats), GFP_KERNEL); for (i = 0; i < ldev->caps.pix_fmt_nb; i++) { drm_fmt = ldev->caps.pix_fmt_drm[i]; @@ -1212,6 +1415,26 @@ static struct drm_plane *ltdc_plane_create(struct drm_device *ddev, formats[nb_fmt++] = drm_fmt; } + /* Add YCbCr supported pixel formats */ + if (ldev->caps.ycbcr_input) { + regmap_read(ldev->regmap, LTDC_L1C1R + lofs, &val); + if (val & LXCR_C1R_YIA) { + memcpy(&formats[nb_fmt], ltdc_drm_fmt_ycbcr_cp, + ARRAY_SIZE(ltdc_drm_fmt_ycbcr_cp) * sizeof(*formats)); + nb_fmt += ARRAY_SIZE(ltdc_drm_fmt_ycbcr_cp); + } + if (val & LXCR_C1R_YSPA) { + memcpy(&formats[nb_fmt], ltdc_drm_fmt_ycbcr_sp, + ARRAY_SIZE(ltdc_drm_fmt_ycbcr_sp) * sizeof(*formats)); + nb_fmt += ARRAY_SIZE(ltdc_drm_fmt_ycbcr_sp); + } + if (val & LXCR_C1R_YFPA) { + memcpy(&formats[nb_fmt], ltdc_drm_fmt_ycbcr_fp, + ARRAY_SIZE(ltdc_drm_fmt_ycbcr_fp) * sizeof(*formats)); + nb_fmt += ARRAY_SIZE(ltdc_drm_fmt_ycbcr_fp); + } + } + plane = devm_kzalloc(dev, sizeof(*plane), GFP_KERNEL); if (!plane) return NULL; @@ -1222,6 +1445,17 @@ static struct drm_plane *ltdc_plane_create(struct drm_device *ddev, if (ret < 0) return NULL; + if (ldev->caps.ycbcr_input) { + if (val & (LXCR_C1R_YIA | LXCR_C1R_YSPA | LXCR_C1R_YFPA)) + drm_plane_create_color_properties(plane, + BIT(DRM_COLOR_YCBCR_BT601) | + BIT(DRM_COLOR_YCBCR_BT709), + BIT(DRM_COLOR_YCBCR_LIMITED_RANGE) | + BIT(DRM_COLOR_YCBCR_FULL_RANGE), + DRM_COLOR_YCBCR_BT601, + DRM_COLOR_YCBCR_LIMITED_RANGE); + } + drm_plane_helper_add(plane, <dc_plane_helper_funcs); drm_plane_create_alpha_property(plane); @@ -1247,7 +1481,7 @@ static int ltdc_crtc_init(struct drm_device *ddev, struct drm_crtc *crtc) unsigned int i; int ret; - primary = ltdc_plane_create(ddev, DRM_PLANE_TYPE_PRIMARY); + primary = ltdc_plane_create(ddev, DRM_PLANE_TYPE_PRIMARY, 0); if (!primary) { DRM_ERROR("Can not create primary plane\n"); return -EINVAL; @@ -1271,7 +1505,7 @@ static int ltdc_crtc_init(struct drm_device *ddev, struct drm_crtc *crtc) /* Add planes. Note : the first layer is used by primary plane */ for (i = 1; i < ldev->caps.nb_layers; i++) { - overlay = ltdc_plane_create(ddev, DRM_PLANE_TYPE_OVERLAY); + overlay = ltdc_plane_create(ddev, DRM_PLANE_TYPE_OVERLAY, i); if (!overlay) { ret = -ENOMEM; DRM_ERROR("Can not create overlay plane %d\n", i); @@ -1403,6 +1637,7 @@ static int ltdc_get_caps(struct drm_device *ddev) if (ldev->caps.hw_version == HWVER_10200) ldev->caps.pad_max_freq_hz = 65000000; ldev->caps.nb_irq = 2; + ldev->caps.ycbcr_input = false; ldev->caps.ycbcr_output = false; ldev->caps.plane_reg_shadow = false; break; @@ -1416,6 +1651,7 @@ static int ltdc_get_caps(struct drm_device *ddev) ldev->caps.non_alpha_only_l1 = false; ldev->caps.pad_max_freq_hz = 150000000; ldev->caps.nb_irq = 4; + ldev->caps.ycbcr_input = false; ldev->caps.ycbcr_output = false; ldev->caps.plane_reg_shadow = false; break; @@ -1429,6 +1665,7 @@ static int ltdc_get_caps(struct drm_device *ddev) ldev->caps.non_alpha_only_l1 = false; ldev->caps.pad_max_freq_hz = 90000000; ldev->caps.nb_irq = 2; + ldev->caps.ycbcr_input = true; ldev->caps.ycbcr_output = true; ldev->caps.plane_reg_shadow = true; break; diff --git a/drivers/gpu/drm/stm/ltdc.h b/drivers/gpu/drm/stm/ltdc.h index adc4f9cf7f95..6968d1ca5149 100644 --- a/drivers/gpu/drm/stm/ltdc.h +++ b/drivers/gpu/drm/stm/ltdc.h @@ -24,6 +24,7 @@ struct ltdc_caps { bool non_alpha_only_l1; /* non-native no-alpha formats on layer 1 */ int pad_max_freq_hz; /* max frequency supported by pad */ int nb_irq; /* number of hardware interrupts */ + bool ycbcr_input; /* ycbcr input converter supported */ bool ycbcr_output; /* ycbcr output converter supported */ bool plane_reg_shadow; /* plane shadow registers ability */ }; -- cgit From d9168aa3291116169b8251e31b034e277b7439ab Mon Sep 17 00:00:00 2001 From: José Expósito Date: Wed, 22 Dec 2021 10:05:52 +0100 Subject: drm/stm: ltdc: Drop format_mod_supported function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "drm_plane_funcs.format_mod_supported" can be removed in favor of the default implementation. Signed-off-by: José Expósito Reviewed-by: Yannick Fertre Tested-by: Yannick Fertre Reviewed-by: Jagan Teki Tested-by: Jagan Teki # i.Core STM32MP1 Signed-off-by: Philippe Cornu Link: https://patchwork.freedesktop.org/patch/msgid/20211222090552.25972-7-jose.exposito89@gmail.com --- drivers/gpu/drm/stm/ltdc.c | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/stm/ltdc.c b/drivers/gpu/drm/stm/ltdc.c index 7fd173390b9f..5eeb32c9c9ce 100644 --- a/drivers/gpu/drm/stm/ltdc.c +++ b/drivers/gpu/drm/stm/ltdc.c @@ -1353,16 +1353,6 @@ static void ltdc_plane_atomic_print_state(struct drm_printer *p, fpsi->counter = 0; } -static bool ltdc_plane_format_mod_supported(struct drm_plane *plane, - u32 format, - u64 modifier) -{ - if (modifier == DRM_FORMAT_MOD_LINEAR) - return true; - - return false; -} - static const struct drm_plane_funcs ltdc_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, @@ -1371,7 +1361,6 @@ static const struct drm_plane_funcs ltdc_plane_funcs = { .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, .atomic_print_state = ltdc_plane_atomic_print_state, - .format_mod_supported = ltdc_plane_format_mod_supported, }; static const struct drm_plane_helper_funcs ltdc_plane_helper_funcs = { -- cgit From 93e97b05d5816ec96bf209a50023cdf9fa750a55 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Fri, 12 Nov 2021 11:47:41 +0530 Subject: drm/bridge: dw-mipi-dsi: Switch to atomic operations Replace atomic version of the enable/disable operations to continue the transition to the atomic API. Also added default drm atomic operations for duplicate, destroy and reset state API's in order to have smooth transition on atomic API's. Tested on Engicam i.Core STM32MP1 SoM. Signed-off-by: Jagan Teki Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20211112061741.120898-1-jagan@amarulasolutions.com Reviewed-by: Robert Foss --- drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c index 2a58b0b7ace5..11d20b8638cd 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c @@ -871,7 +871,8 @@ static void dw_mipi_dsi_clear_err(struct dw_mipi_dsi *dsi) dsi_write(dsi, DSI_INT_MSK1, 0); } -static void dw_mipi_dsi_bridge_post_disable(struct drm_bridge *bridge) +static void dw_mipi_dsi_bridge_post_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) { struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge); const struct dw_mipi_dsi_phy_ops *phy_ops = dsi->plat_data->phy_ops; @@ -978,7 +979,8 @@ static void dw_mipi_dsi_bridge_mode_set(struct drm_bridge *bridge, dw_mipi_dsi_mode_set(dsi->slave, adjusted_mode); } -static void dw_mipi_dsi_bridge_enable(struct drm_bridge *bridge) +static void dw_mipi_dsi_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) { struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge); @@ -1035,11 +1037,14 @@ static int dw_mipi_dsi_bridge_attach(struct drm_bridge *bridge, } static const struct drm_bridge_funcs dw_mipi_dsi_bridge_funcs = { - .mode_set = dw_mipi_dsi_bridge_mode_set, - .enable = dw_mipi_dsi_bridge_enable, - .post_disable = dw_mipi_dsi_bridge_post_disable, - .mode_valid = dw_mipi_dsi_bridge_mode_valid, - .attach = dw_mipi_dsi_bridge_attach, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_enable = dw_mipi_dsi_bridge_atomic_enable, + .atomic_post_disable = dw_mipi_dsi_bridge_post_atomic_disable, + .mode_set = dw_mipi_dsi_bridge_mode_set, + .mode_valid = dw_mipi_dsi_bridge_mode_valid, + .attach = dw_mipi_dsi_bridge_attach, }; #ifdef CONFIG_DEBUG_FS -- cgit From 1500296576464dd4f8f7ba409069591648f4e98c Mon Sep 17 00:00:00 2001 From: Drew Davenport Date: Thu, 13 Jan 2022 23:40:12 -0700 Subject: drm: bridge: it66121: Remove redundant check ctx->next_bridge is checked for NULL twice in a row. The second conditional is redundant, so remove it. Signed-off-by: Drew Davenport Acked-by: Neil Armstrong Signed-off-by: Neil Armstrong Link: https://patchwork.freedesktop.org/patch/msgid/20220114064012.1670612-1-ddavenport@chromium.org --- drivers/gpu/drm/bridge/ite-it66121.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/ite-it66121.c b/drivers/gpu/drm/bridge/ite-it66121.c index 06b59b422c69..69288cf894b9 100644 --- a/drivers/gpu/drm/bridge/ite-it66121.c +++ b/drivers/gpu/drm/bridge/ite-it66121.c @@ -936,9 +936,6 @@ static int it66121_probe(struct i2c_client *client, return -EPROBE_DEFER; } - if (!ctx->next_bridge) - return -EPROBE_DEFER; - i2c_set_clientdata(client, ctx); mutex_init(&ctx->lock); -- cgit From 7f0fec39c9ee3d0f82aa6c5895029e4ab5ec08d0 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Wed, 12 Jan 2022 23:20:36 +0000 Subject: drm/vc4: remove redundant assignment of variable size The variable 'size' is being assigned a value that is never read, the assignment is redundant and can be removed. Cleans up clang-scan warning: drivers/gpu/drm/vc4/vc4_bo.c:358:2: warning: Value stored to 'size' is never read [deadcode.DeadStores] size = roundup(size, PAGE_SIZE); Signed-off-by: Colin Ian King Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20220112232036.1182846-1-colin.i.king@gmail.com --- drivers/gpu/drm/vc4/vc4_bo.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/vc4/vc4_bo.c b/drivers/gpu/drm/vc4/vc4_bo.c index 6d1281a343e9..e451cc5bcfac 100644 --- a/drivers/gpu/drm/vc4/vc4_bo.c +++ b/drivers/gpu/drm/vc4/vc4_bo.c @@ -355,8 +355,6 @@ static struct vc4_bo *vc4_bo_get_from_cache(struct drm_device *dev, uint32_t page_index = bo_page_index(size); struct vc4_bo *bo = NULL; - size = roundup(size, PAGE_SIZE); - mutex_lock(&vc4->bo_lock); if (page_index >= vc4->bo_cache.size_list_size) goto out; -- cgit From 938fb517d4f817c06102e307dcac2fc02b6484b3 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 1 Dec 2021 12:46:39 +0100 Subject: agp: Remove trailing whitespaces Trivial coding-style fix. Signed-off-by: Thomas Zimmermann Acked-by: Helge Deller Link: https://patchwork.freedesktop.org/patch/msgid/20211201114645.15384-2-tzimmermann@suse.de --- drivers/char/agp/ati-agp.c | 2 +- drivers/char/agp/frontend.c | 2 +- drivers/char/agp/sworks-agp.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/char/agp/ati-agp.c b/drivers/char/agp/ati-agp.c index 20bf5f78a362..857b37141a07 100644 --- a/drivers/char/agp/ati-agp.c +++ b/drivers/char/agp/ati-agp.c @@ -303,7 +303,7 @@ static int ati_insert_memory(struct agp_memory * mem, for (i = 0, j = pg_start; i < mem->page_count; i++, j++) { addr = (j * PAGE_SIZE) + agp_bridge->gart_bus_addr; cur_gatt = GET_GATT(addr); - writel(agp_bridge->driver->mask_memory(agp_bridge, + writel(agp_bridge->driver->mask_memory(agp_bridge, page_to_phys(mem->pages[i]), mem->type), cur_gatt+GET_GATT_OFF(addr)); diff --git a/drivers/char/agp/frontend.c b/drivers/char/agp/frontend.c index 00ff5fcb808a..6802a6bbf0f2 100644 --- a/drivers/char/agp/frontend.c +++ b/drivers/char/agp/frontend.c @@ -1017,7 +1017,7 @@ static long agp_ioctl(struct file *file, case AGPIOC_UNBIND: ret_val = agpioc_unbind_wrap(curr_priv, (void __user *) arg); break; - + case AGPIOC_CHIPSET_FLUSH: break; } diff --git a/drivers/char/agp/sworks-agp.c b/drivers/char/agp/sworks-agp.c index f875970bda65..b15d3d4f71d5 100644 --- a/drivers/char/agp/sworks-agp.c +++ b/drivers/char/agp/sworks-agp.c @@ -350,7 +350,7 @@ static int serverworks_insert_memory(struct agp_memory *mem, for (i = 0, j = pg_start; i < mem->page_count; i++, j++) { addr = (j * PAGE_SIZE) + agp_bridge->gart_bus_addr; cur_gatt = SVRWRKS_GET_GATT(addr); - writel(agp_bridge->driver->mask_memory(agp_bridge, + writel(agp_bridge->driver->mask_memory(agp_bridge, page_to_phys(mem->pages[i]), mem->type), cur_gatt+GET_GATT_OFF(addr)); } -- cgit From 9175cb2c83c82fea9f234203ad3a02027372f929 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 1 Dec 2021 12:46:40 +0100 Subject: agp: Include "compat_ioctl.h" where necessary Fix compiler warnings like drivers/char/agp/frontend.c:46:20: warning: no previous prototype for 'agp_find_mem_by_key' [-Wmissing-prototypes] 46 | struct agp_memory *agp_find_mem_by_key(int key) by including the compat_ioctl.h in the source file. Signed-off-by: Thomas Zimmermann Acked-by: Helge Deller Link: https://patchwork.freedesktop.org/patch/msgid/20211201114645.15384-3-tzimmermann@suse.de --- drivers/char/agp/frontend.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/char/agp/frontend.c b/drivers/char/agp/frontend.c index 6802a6bbf0f2..321118a9cfa5 100644 --- a/drivers/char/agp/frontend.c +++ b/drivers/char/agp/frontend.c @@ -39,7 +39,9 @@ #include #include #include + #include "agp.h" +#include "compat_ioctl.h" struct agp_front_data agp_fe; -- cgit From ffe9d02e4152ab653652717a1907d0058b764d58 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 1 Dec 2021 12:46:41 +0100 Subject: agp: Documentation fixes Fix compiler warnings drivers/char/agp/backend.c:68: warning: Function parameter or member 'pdev' not described in 'agp_backend_acquire' drivers/char/agp/backend.c:93: warning: Function parameter or member 'bridge' not described in 'agp_backend_release' by adding the necessary documentation. Signed-off-by: Thomas Zimmermann Acked-by: Helge Deller Link: https://patchwork.freedesktop.org/patch/msgid/20211201114645.15384-4-tzimmermann@suse.de --- drivers/char/agp/backend.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/char/agp/backend.c b/drivers/char/agp/backend.c index 004a3ce8ba72..0e19c600db53 100644 --- a/drivers/char/agp/backend.c +++ b/drivers/char/agp/backend.c @@ -62,6 +62,7 @@ EXPORT_SYMBOL(agp_find_bridge); /** * agp_backend_acquire - attempt to acquire an agp backend. + * @pdev: the PCI device * */ struct agp_bridge_data *agp_backend_acquire(struct pci_dev *pdev) @@ -83,6 +84,7 @@ EXPORT_SYMBOL(agp_backend_acquire); /** * agp_backend_release - release the lock on the agp backend. + * @bridge: the AGP backend to release * * The caller must insure that the graphics aperture translation table * is read for use by another entity. -- cgit From ba7e3fd19a6731747da4d213566e4239b28d3a7d Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 1 Dec 2021 12:46:42 +0100 Subject: agp/ati: Return error from ati_create_page_map() Fix the compiler warning drivers/char/agp/ati-agp.c: In function 'ati_create_page_map': drivers/char/agp/ati-agp.c:58:16: warning: variable 'err' set but not used [-Wunused-but-set-variable] 58 | int i, err = 0; by returing the error to the caller. v2: * free page in error branch (Helge) Signed-off-by: Thomas Zimmermann Acked-by: Helge Deller Link: https://patchwork.freedesktop.org/patch/msgid/20211201114645.15384-5-tzimmermann@suse.de --- drivers/char/agp/ati-agp.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/char/agp/ati-agp.c b/drivers/char/agp/ati-agp.c index 857b37141a07..6f5530482d83 100644 --- a/drivers/char/agp/ati-agp.c +++ b/drivers/char/agp/ati-agp.c @@ -55,7 +55,7 @@ static struct _ati_generic_private { static int ati_create_page_map(struct ati_page_map *page_map) { - int i, err = 0; + int i, err; page_map->real = (unsigned long *) __get_free_page(GFP_KERNEL); if (page_map->real == NULL) @@ -63,6 +63,10 @@ static int ati_create_page_map(struct ati_page_map *page_map) set_memory_uc((unsigned long)page_map->real, 1); err = map_page_into_agp(virt_to_page(page_map->real)); + if (err) { + free_page((unsigned long)page_map->real); + return err; + } page_map->remapped = page_map->real; for (i = 0; i < PAGE_SIZE / sizeof(unsigned long); i++) { -- cgit From c4f7f3117e80fd12f898f24630f146316c809189 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 1 Dec 2021 12:46:43 +0100 Subject: agp/nvidia: Declare value returned by readl() as unused Fix the compiler warning drivers/char/agp/nvidia-agp.c: In function 'nvidia_tlbflush': drivers/char/agp/nvidia-agp.c:264:22: warning: variable 'temp' set but not used [-Wunused-but-set-variable] 264 | u32 wbc_reg, temp; by marking the temp variable with __maybe_unused. The affected readl() is only required for flushing caches, but the returned value is not of interest. v2: * declare temp as __maybe_unused (Helge) Signed-off-by: Thomas Zimmermann Acked-by: Helge Deller Link: https://patchwork.freedesktop.org/patch/msgid/20211201114645.15384-6-tzimmermann@suse.de --- drivers/char/agp/nvidia-agp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/char/agp/nvidia-agp.c b/drivers/char/agp/nvidia-agp.c index f78e756157db..826dbd06f6bb 100644 --- a/drivers/char/agp/nvidia-agp.c +++ b/drivers/char/agp/nvidia-agp.c @@ -261,7 +261,8 @@ static int nvidia_remove_memory(struct agp_memory *mem, off_t pg_start, int type static void nvidia_tlbflush(struct agp_memory *mem) { unsigned long end; - u32 wbc_reg, temp; + u32 wbc_reg; + u32 __maybe_unused temp; int i; /* flush chipset */ -- cgit From a41af4c80a933831b13cfc166257517abec7ab2f Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 1 Dec 2021 12:46:44 +0100 Subject: agp/sworks: Remove unused variable 'current_size' Fix the compiler warning drivers/char/agp/sworks-agp.c: In function 'serverworks_configure': drivers/char/agp/sworks-agp.c:265:37: warning: variable 'current_size' set but not used [-Wunused-but-set-variable] 265 | struct aper_size_info_lvl2 *current_size; by removing the variable. Signed-off-by: Thomas Zimmermann Acked-by: Helge Deller Link: https://patchwork.freedesktop.org/patch/msgid/20211201114645.15384-7-tzimmermann@suse.de --- drivers/char/agp/sworks-agp.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers') diff --git a/drivers/char/agp/sworks-agp.c b/drivers/char/agp/sworks-agp.c index b15d3d4f71d5..b91da5998dd7 100644 --- a/drivers/char/agp/sworks-agp.c +++ b/drivers/char/agp/sworks-agp.c @@ -262,13 +262,10 @@ static void serverworks_tlbflush(struct agp_memory *temp) static int serverworks_configure(void) { - struct aper_size_info_lvl2 *current_size; u32 temp; u8 enable_reg; u16 cap_reg; - current_size = A_SIZE_LVL2(agp_bridge->current_size); - /* Get the memory mapped registers */ pci_read_config_dword(agp_bridge->dev, serverworks_private.mm_addr_ofs, &temp); temp = (temp & PCI_BASE_ADDRESS_MEM_MASK); -- cgit From a320c3a3280b0765ffaa5a15a8e6f5d7909d8df9 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 1 Dec 2021 12:46:45 +0100 Subject: agp/via: Remove unused variable 'current_size' Fix the compiler warning drivers/char/agp/via-agp.c: In function 'via_configure_agp3': drivers/char/agp/via-agp.c:131:35: warning: variable 'current_size' set but not used [-Wunused-but-set-variable] 131 | struct aper_size_info_16 *current_size; by removing the variable. Signed-off-by: Thomas Zimmermann Acked-by: Helge Deller Link: https://patchwork.freedesktop.org/patch/msgid/20211201114645.15384-8-tzimmermann@suse.de --- drivers/char/agp/via-agp.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers') diff --git a/drivers/char/agp/via-agp.c b/drivers/char/agp/via-agp.c index 87a92a044570..dc594f4eca38 100644 --- a/drivers/char/agp/via-agp.c +++ b/drivers/char/agp/via-agp.c @@ -128,9 +128,6 @@ static int via_fetch_size_agp3(void) static int via_configure_agp3(void) { u32 temp; - struct aper_size_info_16 *current_size; - - current_size = A_SIZE_16(agp_bridge->current_size); /* address to map to */ agp_bridge->gart_bus_addr = pci_bus_address(agp_bridge->dev, -- cgit From 3a5d604f90f90a09e35ccfdeeedbadfd06b606db Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Wed, 12 Jan 2022 22:34:35 +0000 Subject: i915: make array flex_regs static const Don't populate the read-only array flex_regs on the stack but instead it static const. Also makes the object code a little smaller. Signed-off-by: Colin Ian King Signed-off-by: Tvrtko Ursulin Link: https://patchwork.freedesktop.org/patch/msgid/20220112223435.949071-1-colin.i.king@gmail.com --- drivers/gpu/drm/i915/i915_perf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/i915_perf.c b/drivers/gpu/drm/i915/i915_perf.c index 298857b69180..f9faf7697453 100644 --- a/drivers/gpu/drm/i915/i915_perf.c +++ b/drivers/gpu/drm/i915/i915_perf.c @@ -2114,7 +2114,7 @@ gen8_update_reg_state_unlocked(const struct intel_context *ce, u32 ctx_oactxctrl = stream->perf->ctx_oactxctrl_offset; u32 ctx_flexeu0 = stream->perf->ctx_flexeu0_offset; /* The MMIO offsets for Flex EU registers aren't contiguous */ - i915_reg_t flex_regs[] = { + static const i915_reg_t flex_regs[] = { EU_PERF_CNTL0, EU_PERF_CNTL1, EU_PERF_CNTL2, -- cgit From fdb9fb6c1302c112dd2ef5056edda2cef6d73bd4 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Thu, 30 Dec 2021 15:52:01 +0000 Subject: video: fbdev: asiliantfb: remove redundant assignment to variable Ftarget Variable Ftarget is being initialized with a value that is never read, it is being re-assigned a different value a little later on. The assignment is redundant and can be removed. Signed-off-by: Colin Ian King Signed-off-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20211230155202.355336-1-colin.i.king@gmail.com --- drivers/video/fbdev/asiliantfb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/video/fbdev/asiliantfb.c b/drivers/video/fbdev/asiliantfb.c index 84c56f525889..f8ef62542f7f 100644 --- a/drivers/video/fbdev/asiliantfb.c +++ b/drivers/video/fbdev/asiliantfb.c @@ -110,7 +110,7 @@ static const struct fb_ops asiliantfb_ops = { static void asiliant_calc_dclk2(u32 *ppixclock, u8 *dclk2_m, u8 *dclk2_n, u8 *dclk2_div) { unsigned pixclock = *ppixclock; - unsigned Ftarget = 1000000 * (1000000 / pixclock); + unsigned Ftarget; unsigned n; unsigned best_error = 0xffffffff; unsigned best_m = 0xffffffff, -- cgit From 61cc48163a4f061dcb0ef84cde228b5f75ae8594 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Thu, 30 Dec 2021 16:06:26 +0000 Subject: video: fbdev: s3c-fb: remove redundant initialization of pointer bufs Pointer bufs is being initialized with a value that is never read, it is being re-assigned with a different value later on. The assignment is redundant and can be removed. Cleans up clang-scan warning: drivers/video/fbdev/s3c-fb.c:492:16: warning: Value stored to 'buf' during its initialization is never read [deadcode.DeadStores] void __iomem *buf = regs; Signed-off-by: Colin Ian King Signed-off-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20211230160626.404072-1-colin.i.king@gmail.com --- drivers/video/fbdev/s3c-fb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/video/fbdev/s3c-fb.c b/drivers/video/fbdev/s3c-fb.c index 3b134e1bbc38..68408c499627 100644 --- a/drivers/video/fbdev/s3c-fb.c +++ b/drivers/video/fbdev/s3c-fb.c @@ -489,7 +489,7 @@ static int s3c_fb_set_par(struct fb_info *info) struct s3c_fb_win *win = info->par; struct s3c_fb *sfb = win->parent; void __iomem *regs = sfb->regs; - void __iomem *buf = regs; + void __iomem *buf; int win_no = win->index; u32 alpha = 0; u32 data; -- cgit From 803abfd8dda540b94ea3c754a70cba69d3825995 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Mon, 17 Jan 2022 00:21:46 -0500 Subject: drm: bridge: fix unmet dependency on DRM_KMS_HELPER for DRM_PANEL_BRIDGE When DRM_CHIPONE_ICN6211 is selected, and DRM_KMS_HELPER is not selected, Kbuild gives the following warning: WARNING: unmet direct dependencies detected for DRM_PANEL_BRIDGE Depends on [n]: HAS_IOMEM [=y] && DRM_BRIDGE [=y] && DRM_KMS_HELPER [=n] Selected by [y]: - DRM_CHIPONE_ICN6211 [=y] && HAS_IOMEM [=y] && DRM [=y] && DRM_BRIDGE [=y] && OF [=y] This is because DRM_CHIPONE_ICN6211 selects DRM_PANEL_BRIDGE without depending on or selecting DRM_KMS_HELPER, despite DRM_PANEL_BRIDGE depending on DRM_KMS_HELPER. This unmet dependency bug was detected by Kismet, a static analysis tool for Kconfig. Fixes: ce517f18944e ("drm: bridge: Add Chipone ICN6211 MIPI-DSI to RGB bridge") Reviewed-by: Robert Foss Reviewed-by: Laurent Pinchart Signed-off-by: Julian Braha Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20220117052146.75811-1-julianbraha@gmail.com --- drivers/gpu/drm/bridge/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 61db5a66b493..a1b52eaf26e0 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -30,6 +30,7 @@ config DRM_CDNS_DSI config DRM_CHIPONE_ICN6211 tristate "Chipone ICN6211 MIPI-DSI/RGB Converter bridge" depends on OF + depends on DRM_KMS_HELPER select DRM_MIPI_DSI select DRM_PANEL_BRIDGE help -- cgit From 6e55d273708b2a9485eb7c035efd2757fdae1869 Mon Sep 17 00:00:00 2001 From: Alexey Sheplyakov Date: Sat, 15 Jan 2022 20:06:58 +0400 Subject: drm/panfrost: initial dual core group GPUs support On a dual core group GPUs (such as T628) fragment shading can be performed over all cores (because a fragment shader job doesn't need coherency between threads), however vertex shading requires to be run on the same core group as the tiler (which always lives in core group 0). As a first step to support T628 power on only the first core group (so no jobs are scheduled on the second one). This makes T628 look like every other Midgard GPU (and throws away up to half the cores). With this patch panfrost is able to drive T628 (r1p0) GPU on some armv8 SoCs (in particular BE-M1000). Without the patch rendering is horribly broken (desktop is completely unusable) and eventually the GPU locks up (it takes from a few seconds to a couple of minutes). Using the second core group requires support in Mesa (and an UABI change): the userspace should 1) set PANFROST_JD_DOESNT_NEED_COHERENCY_ON_GPU flag to opt-in to allowing the job to run across all cores. 2) set PANFROST_RUN_ON_SECOND_CORE_GROUP flag to allow compute jobs to be run on the second core group (at the moment Mesa does not advertise compute support on anything older than Mali T760) But there's little point adding such flags until someone (myself) steps up to do the Mesa work. Signed-off-by: Alexey Sheplyakov Signed-off-by: Vadim V. Vlasov Tested-by: Alexey Sheplyakov Co-developed-by: Steven Price Signed-off-by: Steven Price Link: https://patchwork.freedesktop.org/patch/msgid/20220115160658.582646-1-asheplyakov@basealt.ru --- drivers/gpu/drm/panfrost/panfrost_gpu.c | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/panfrost/panfrost_gpu.c b/drivers/gpu/drm/panfrost/panfrost_gpu.c index f8355de6e335..15cec831a99a 100644 --- a/drivers/gpu/drm/panfrost/panfrost_gpu.c +++ b/drivers/gpu/drm/panfrost/panfrost_gpu.c @@ -320,19 +320,36 @@ void panfrost_gpu_power_on(struct panfrost_device *pfdev) { int ret; u32 val; + u64 core_mask = U64_MAX; panfrost_gpu_init_quirks(pfdev); - /* Just turn on everything for now */ - gpu_write(pfdev, L2_PWRON_LO, pfdev->features.l2_present); + if (pfdev->features.l2_present != 1) { + /* + * Only support one core group now. + * ~(l2_present - 1) unsets all bits in l2_present except + * the bottom bit. (l2_present - 2) has all the bits in + * the first core group set. AND them together to generate + * a mask of cores in the first core group. + */ + core_mask = ~(pfdev->features.l2_present - 1) & + (pfdev->features.l2_present - 2); + dev_info_once(pfdev->dev, "using only 1st core group (%lu cores from %lu)\n", + hweight64(core_mask), + hweight64(pfdev->features.shader_present)); + } + gpu_write(pfdev, L2_PWRON_LO, pfdev->features.l2_present & core_mask); ret = readl_relaxed_poll_timeout(pfdev->iomem + L2_READY_LO, - val, val == pfdev->features.l2_present, 100, 20000); + val, val == (pfdev->features.l2_present & core_mask), + 100, 20000); if (ret) dev_err(pfdev->dev, "error powering up gpu L2"); - gpu_write(pfdev, SHADER_PWRON_LO, pfdev->features.shader_present); + gpu_write(pfdev, SHADER_PWRON_LO, + pfdev->features.shader_present & core_mask); ret = readl_relaxed_poll_timeout(pfdev->iomem + SHADER_READY_LO, - val, val == pfdev->features.shader_present, 100, 20000); + val, val == (pfdev->features.shader_present & core_mask), + 100, 20000); if (ret) dev_err(pfdev->dev, "error powering up gpu shader"); -- cgit From f4319f72a9bc37c948832c0ef121460ad7c1573f Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Thu, 13 Jan 2022 15:43:05 +0100 Subject: drm/bridge: sii902x: add support for DRM_BRIDGE_ATTACH_NO_CONNECTOR This adds support for DRM_BRIDGE_ATTACH_NO_CONNECTOR by adding the bridge get_edid() and detect() callbacks after refactoring the connector get_modes() and connector_detect() callbacks. In order to keep the bridge working, extra code in get_modes() has been moved to more logical places. Signed-off-by: Neil Armstrong Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20220113144305.1074389-1-narmstrong@baylibre.com Reviewed-by: Robert Foss --- drivers/gpu/drm/bridge/sii902x.c | 129 ++++++++++++++++++++++++++++++--------- 1 file changed, 99 insertions(+), 30 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/sii902x.c b/drivers/gpu/drm/bridge/sii902x.c index 89558e581530..65549fbfdc87 100644 --- a/drivers/gpu/drm/bridge/sii902x.c +++ b/drivers/gpu/drm/bridge/sii902x.c @@ -166,10 +166,12 @@ struct sii902x { struct i2c_client *i2c; struct regmap *regmap; struct drm_bridge bridge; + struct drm_bridge *next_bridge; struct drm_connector connector; struct gpio_desc *reset_gpio; struct i2c_mux_core *i2cmux; struct regulator_bulk_data supplies[2]; + bool sink_is_hdmi; /* * Mutex protects audio and video functions from interfering * each other, by keeping their i2c command sequences atomic. @@ -245,10 +247,8 @@ static void sii902x_reset(struct sii902x *sii902x) gpiod_set_value(sii902x->reset_gpio, 0); } -static enum drm_connector_status -sii902x_connector_detect(struct drm_connector *connector, bool force) +static enum drm_connector_status sii902x_detect(struct sii902x *sii902x) { - struct sii902x *sii902x = connector_to_sii902x(connector); unsigned int status; mutex_lock(&sii902x->mutex); @@ -261,6 +261,14 @@ sii902x_connector_detect(struct drm_connector *connector, bool force) connector_status_connected : connector_status_disconnected; } +static enum drm_connector_status +sii902x_connector_detect(struct drm_connector *connector, bool force) +{ + struct sii902x *sii902x = connector_to_sii902x(connector); + + return sii902x_detect(sii902x); +} + static const struct drm_connector_funcs sii902x_connector_funcs = { .detect = sii902x_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, @@ -270,42 +278,40 @@ static const struct drm_connector_funcs sii902x_connector_funcs = { .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; -static int sii902x_get_modes(struct drm_connector *connector) +static struct edid *sii902x_get_edid(struct sii902x *sii902x, + struct drm_connector *connector) { - struct sii902x *sii902x = connector_to_sii902x(connector); - u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; - u8 output_mode = SII902X_SYS_CTRL_OUTPUT_DVI; struct edid *edid; - int num = 0, ret; mutex_lock(&sii902x->mutex); edid = drm_get_edid(connector, sii902x->i2cmux->adapter[0]); - drm_connector_update_edid_property(connector, edid); if (edid) { if (drm_detect_hdmi_monitor(edid)) - output_mode = SII902X_SYS_CTRL_OUTPUT_HDMI; - - num = drm_add_edid_modes(connector, edid); - kfree(edid); + sii902x->sink_is_hdmi = true; + else + sii902x->sink_is_hdmi = false; } - ret = drm_display_info_set_bus_formats(&connector->display_info, - &bus_format, 1); - if (ret) - goto error_out; + mutex_unlock(&sii902x->mutex); - ret = regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA, - SII902X_SYS_CTRL_OUTPUT_MODE, output_mode); - if (ret) - goto error_out; + return edid; +} - ret = num; +static int sii902x_get_modes(struct drm_connector *connector) +{ + struct sii902x *sii902x = connector_to_sii902x(connector); + struct edid *edid; + int num = 0; -error_out: - mutex_unlock(&sii902x->mutex); + edid = sii902x_get_edid(sii902x, connector); + drm_connector_update_edid_property(connector, edid); + if (edid) { + num = drm_add_edid_modes(connector, edid); + kfree(edid); + } - return ret; + return num; } static enum drm_mode_status sii902x_mode_valid(struct drm_connector *connector, @@ -354,12 +360,16 @@ static void sii902x_bridge_mode_set(struct drm_bridge *bridge, const struct drm_display_mode *adj) { struct sii902x *sii902x = bridge_to_sii902x(bridge); + u8 output_mode = SII902X_SYS_CTRL_OUTPUT_DVI; struct regmap *regmap = sii902x->regmap; u8 buf[HDMI_INFOFRAME_SIZE(AVI)]; struct hdmi_avi_infoframe frame; u16 pixel_clock_10kHz = adj->clock / 10; int ret; + if (sii902x->sink_is_hdmi) + output_mode = SII902X_SYS_CTRL_OUTPUT_HDMI; + buf[0] = pixel_clock_10kHz & 0xff; buf[1] = pixel_clock_10kHz >> 8; buf[2] = drm_mode_vrefresh(adj); @@ -375,6 +385,11 @@ static void sii902x_bridge_mode_set(struct drm_bridge *bridge, mutex_lock(&sii902x->mutex); + ret = regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA, + SII902X_SYS_CTRL_OUTPUT_MODE, output_mode); + if (ret) + goto out; + ret = regmap_bulk_write(regmap, SII902X_TPI_VIDEO_DATA, buf, 10); if (ret) goto out; @@ -405,13 +420,13 @@ static int sii902x_bridge_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags) { struct sii902x *sii902x = bridge_to_sii902x(bridge); + u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; struct drm_device *drm = bridge->dev; int ret; - if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) { - DRM_ERROR("Fix bridge driver to make connector optional!"); - return -EINVAL; - } + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) + return drm_bridge_attach(bridge->encoder, sii902x->next_bridge, + bridge, flags); drm_connector_helper_add(&sii902x->connector, &sii902x_connector_helper_funcs); @@ -433,16 +448,38 @@ static int sii902x_bridge_attach(struct drm_bridge *bridge, else sii902x->connector.polled = DRM_CONNECTOR_POLL_CONNECT; + ret = drm_display_info_set_bus_formats(&sii902x->connector.display_info, + &bus_format, 1); + if (ret) + return ret; + drm_connector_attach_encoder(&sii902x->connector, bridge->encoder); return 0; } +static enum drm_connector_status sii902x_bridge_detect(struct drm_bridge *bridge) +{ + struct sii902x *sii902x = bridge_to_sii902x(bridge); + + return sii902x_detect(sii902x); +} + +static struct edid *sii902x_bridge_get_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct sii902x *sii902x = bridge_to_sii902x(bridge); + + return sii902x_get_edid(sii902x, connector); +} + static const struct drm_bridge_funcs sii902x_bridge_funcs = { .attach = sii902x_bridge_attach, .mode_set = sii902x_bridge_mode_set, .disable = sii902x_bridge_disable, .enable = sii902x_bridge_enable, + .detect = sii902x_bridge_detect, + .get_edid = sii902x_bridge_get_edid, }; static int sii902x_mute(struct sii902x *sii902x, bool mute) @@ -829,8 +866,12 @@ static irqreturn_t sii902x_interrupt(int irq, void *data) mutex_unlock(&sii902x->mutex); - if ((status & SII902X_HOTPLUG_EVENT) && sii902x->bridge.dev) + if ((status & SII902X_HOTPLUG_EVENT) && sii902x->bridge.dev) { drm_helper_hpd_irq_event(sii902x->bridge.dev); + drm_bridge_hpd_notify(&sii902x->bridge, (status & SII902X_PLUGGED_STATUS) + ? connector_status_connected + : connector_status_disconnected); + } return IRQ_HANDLED; } @@ -1001,6 +1042,11 @@ static int sii902x_init(struct sii902x *sii902x) sii902x->bridge.funcs = &sii902x_bridge_funcs; sii902x->bridge.of_node = dev->of_node; sii902x->bridge.timings = &default_sii902x_timings; + sii902x->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID; + + if (sii902x->i2c->irq > 0) + sii902x->bridge.ops |= DRM_BRIDGE_OP_HPD; + drm_bridge_add(&sii902x->bridge); sii902x_audio_codec_init(sii902x, dev); @@ -1022,6 +1068,7 @@ static int sii902x_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct device *dev = &client->dev; + struct device_node *endpoint; struct sii902x *sii902x; int ret; @@ -1049,6 +1096,28 @@ static int sii902x_probe(struct i2c_client *client, return PTR_ERR(sii902x->reset_gpio); } + endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 1, -1); + if (endpoint) { + struct device_node *remote = of_graph_get_remote_port_parent(endpoint); + + of_node_put(endpoint); + if (!remote) { + dev_err(dev, "Endpoint in port@1 unconnected\n"); + return -ENODEV; + } + + if (!of_device_is_available(remote)) { + dev_err(dev, "port@1 remote device is disabled\n"); + of_node_put(remote); + return -ENODEV; + } + + sii902x->next_bridge = of_drm_find_bridge(remote); + of_node_put(remote); + if (!sii902x->next_bridge) + return -EPROBE_DEFER; + } + mutex_init(&sii902x->mutex); sii902x->supplies[0].supply = "iovcc"; -- cgit From dfacce59553b2a65f4357ba6019827eb4b9a0eed Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 14 Jan 2022 12:45:31 +0100 Subject: drm/dp_mst: Remove trailing whitespace. Fix coding style. Signed-off-by: Thomas Zimmermann Acked-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20220114114535.29157-2-tzimmermann@suse.de --- drivers/gpu/drm/drm_dp_mst_topology.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c b/drivers/gpu/drm/drm_dp_mst_topology.c index ab4372e9fe43..3110df7e682e 100644 --- a/drivers/gpu/drm/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/drm_dp_mst_topology.c @@ -4811,7 +4811,7 @@ static void drm_dp_mst_dump_mstb(struct seq_file *m, seq_printf(m, "%smstb - [%p]: num_ports: %d\n", prefix, mstb, mstb->num_ports); list_for_each_entry(port, &mstb->ports, next) { - seq_printf(m, "%sport %d - [%p] (%s - %s): ddps: %d, ldps: %d, sdp: %d/%d, fec: %s, conn: %p\n", + seq_printf(m, "%sport %d - [%p] (%s - %s): ddps: %d, ldps: %d, sdp: %d/%d, fec: %s, conn: %p\n", prefix, port->port_num, port, -- cgit From 3681eb24a9f14e001d05f8d15d2e07a96abef8b3 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 14 Jan 2022 12:45:32 +0100 Subject: drm/dp: Move DP declarations into separate header file Split the DP declarations from other helpers before moving the DP functions into a separate module. v2: * forward-declare struct drm_dp_aux (Jani) * add include guards in drm_dp_helper_internal.h Signed-off-by: Thomas Zimmermann Acked-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20220114114535.29157-3-tzimmermann@suse.de --- drivers/gpu/drm/drm_crtc_helper_internal.h | 27 ------------------------ drivers/gpu/drm/drm_dp_aux_dev.c | 2 +- drivers/gpu/drm/drm_dp_helper.c | 2 +- drivers/gpu/drm/drm_dp_helper_internal.h | 33 ++++++++++++++++++++++++++++++ drivers/gpu/drm/drm_dp_mst_topology.c | 2 +- drivers/gpu/drm/drm_kms_helper_common.c | 1 + 6 files changed, 37 insertions(+), 30 deletions(-) create mode 100644 drivers/gpu/drm/drm_dp_helper_internal.h (limited to 'drivers') diff --git a/drivers/gpu/drm/drm_crtc_helper_internal.h b/drivers/gpu/drm/drm_crtc_helper_internal.h index 61e09f8a8d0f..28e04e750130 100644 --- a/drivers/gpu/drm/drm_crtc_helper_internal.h +++ b/drivers/gpu/drm/drm_crtc_helper_internal.h @@ -28,36 +28,9 @@ #include #include -#include #include #include -/* drm_dp_aux_dev.c */ -#ifdef CONFIG_DRM_DP_AUX_CHARDEV -int drm_dp_aux_dev_init(void); -void drm_dp_aux_dev_exit(void); -int drm_dp_aux_register_devnode(struct drm_dp_aux *aux); -void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux); -#else -static inline int drm_dp_aux_dev_init(void) -{ - return 0; -} - -static inline void drm_dp_aux_dev_exit(void) -{ -} - -static inline int drm_dp_aux_register_devnode(struct drm_dp_aux *aux) -{ - return 0; -} - -static inline void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux) -{ -} -#endif - /* drm_probe_helper.c */ enum drm_mode_status drm_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode); diff --git a/drivers/gpu/drm/drm_dp_aux_dev.c b/drivers/gpu/drm/drm_dp_aux_dev.c index 06b374cae956..0618dfe16660 100644 --- a/drivers/gpu/drm/drm_dp_aux_dev.c +++ b/drivers/gpu/drm/drm_dp_aux_dev.c @@ -40,7 +40,7 @@ #include #include -#include "drm_crtc_helper_internal.h" +#include "drm_dp_helper_internal.h" struct drm_dp_aux_dev { unsigned index; diff --git a/drivers/gpu/drm/drm_dp_helper.c b/drivers/gpu/drm/drm_dp_helper.c index 23f9073bc473..e995a0262ed7 100644 --- a/drivers/gpu/drm/drm_dp_helper.c +++ b/drivers/gpu/drm/drm_dp_helper.c @@ -35,7 +35,7 @@ #include #include -#include "drm_crtc_helper_internal.h" +#include "drm_dp_helper_internal.h" struct dp_aux_backlight { struct backlight_device *base; diff --git a/drivers/gpu/drm/drm_dp_helper_internal.h b/drivers/gpu/drm/drm_dp_helper_internal.h new file mode 100644 index 000000000000..8917fc3af9ec --- /dev/null +++ b/drivers/gpu/drm/drm_dp_helper_internal.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: MIT */ + +#ifndef DRM_DP_HELPER_INTERNAL_H +#define DRM_DP_HELPER_INTERNAL_H + +struct drm_dp_aux; + +#ifdef CONFIG_DRM_DP_AUX_CHARDEV +int drm_dp_aux_dev_init(void); +void drm_dp_aux_dev_exit(void); +int drm_dp_aux_register_devnode(struct drm_dp_aux *aux); +void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux); +#else +static inline int drm_dp_aux_dev_init(void) +{ + return 0; +} + +static inline void drm_dp_aux_dev_exit(void) +{ +} + +static inline int drm_dp_aux_register_devnode(struct drm_dp_aux *aux) +{ + return 0; +} + +static inline void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux) +{ +} +#endif + +#endif diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c b/drivers/gpu/drm/drm_dp_mst_topology.c index 3110df7e682e..bc3237a09c06 100644 --- a/drivers/gpu/drm/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/drm_dp_mst_topology.c @@ -45,7 +45,7 @@ #include #include -#include "drm_crtc_helper_internal.h" +#include "drm_dp_helper_internal.h" #include "drm_dp_mst_topology_internal.h" /** diff --git a/drivers/gpu/drm/drm_kms_helper_common.c b/drivers/gpu/drm/drm_kms_helper_common.c index 47e92400548d..88260d26409c 100644 --- a/drivers/gpu/drm/drm_kms_helper_common.c +++ b/drivers/gpu/drm/drm_kms_helper_common.c @@ -29,6 +29,7 @@ #include +#include "drm_dp_helper_internal.h" #include "drm_crtc_helper_internal.h" MODULE_AUTHOR("David Airlie, Jesse Barnes"); -- cgit From adb9d5a2cc77e8aefe98fe4c11656c5b7025c248 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 14 Jan 2022 12:45:33 +0100 Subject: drm/dp: Move DisplayPort helpers into separate helper module Move DisplayPort functions into a separate module to reduce the size of the KMS helpers. Select DRM_DP_HELPER for all users of the code. To avoid naming conflicts, rename drm_dp_helper.c to drm_dp.c This change can help to reduce the size of the kernel binary. Some numbers from a x86-64 test build: Before: drm_kms_helper.ko: 447480 bytes After: drm_dp_helper.ko: 216632 bytes drm_kms_helper.ko: 239424 bytes For early-boot graphics, generic DRM drivers, such as simpledrm, require DRM KMS helpers to be built into the kernel. Generic helper functions for DisplayPort take up a significant portion of DRM KMS helper library. These functions are not used by generic drivers and can be loaded as a module. v3: * fix include statement in DRM selftests v2: * move DP helper code into dp/ (Jani) Signed-off-by: Thomas Zimmermann Acked-by: Lyude Paul Acked-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20220114114535.29157-4-tzimmermann@suse.de --- drivers/gpu/drm/Kconfig | 8 + drivers/gpu/drm/Makefile | 10 +- drivers/gpu/drm/bridge/Kconfig | 4 + drivers/gpu/drm/bridge/analogix/Kconfig | 2 + drivers/gpu/drm/bridge/cadence/Kconfig | 1 + drivers/gpu/drm/dp/Makefile | 7 + drivers/gpu/drm/dp/drm_dp.c | 3744 ++++++++++++ drivers/gpu/drm/dp/drm_dp_aux_dev.c | 354 ++ drivers/gpu/drm/dp/drm_dp_cec.c | 451 ++ drivers/gpu/drm/dp/drm_dp_dual_mode_helper.c | 530 ++ drivers/gpu/drm/dp/drm_dp_helper_internal.h | 33 + drivers/gpu/drm/dp/drm_dp_helper_mod.c | 22 + drivers/gpu/drm/dp/drm_dp_mst_topology.c | 5977 ++++++++++++++++++++ drivers/gpu/drm/dp/drm_dp_mst_topology_internal.h | 24 + drivers/gpu/drm/drm_dp_aux_dev.c | 354 -- drivers/gpu/drm/drm_dp_cec.c | 451 -- drivers/gpu/drm/drm_dp_dual_mode_helper.c | 530 -- drivers/gpu/drm/drm_dp_helper.c | 3744 ------------ drivers/gpu/drm/drm_dp_helper_internal.h | 33 - drivers/gpu/drm/drm_dp_mst_topology.c | 5977 -------------------- drivers/gpu/drm/drm_dp_mst_topology_internal.h | 24 - drivers/gpu/drm/drm_kms_helper_common.c | 15 - drivers/gpu/drm/i915/Kconfig | 1 + drivers/gpu/drm/msm/Kconfig | 1 + drivers/gpu/drm/nouveau/Kconfig | 1 + drivers/gpu/drm/rockchip/Kconfig | 1 + drivers/gpu/drm/selftests/test-drm_dp_mst_helper.c | 2 +- drivers/gpu/drm/tegra/Kconfig | 1 + drivers/gpu/drm/xlnx/Kconfig | 1 + 29 files changed, 11168 insertions(+), 11135 deletions(-) create mode 100644 drivers/gpu/drm/dp/Makefile create mode 100644 drivers/gpu/drm/dp/drm_dp.c create mode 100644 drivers/gpu/drm/dp/drm_dp_aux_dev.c create mode 100644 drivers/gpu/drm/dp/drm_dp_cec.c create mode 100644 drivers/gpu/drm/dp/drm_dp_dual_mode_helper.c create mode 100644 drivers/gpu/drm/dp/drm_dp_helper_internal.h create mode 100644 drivers/gpu/drm/dp/drm_dp_helper_mod.c create mode 100644 drivers/gpu/drm/dp/drm_dp_mst_topology.c create mode 100644 drivers/gpu/drm/dp/drm_dp_mst_topology_internal.h delete mode 100644 drivers/gpu/drm/drm_dp_aux_dev.c delete mode 100644 drivers/gpu/drm/drm_dp_cec.c delete mode 100644 drivers/gpu/drm/drm_dp_dual_mode_helper.c delete mode 100644 drivers/gpu/drm/drm_dp_helper.c delete mode 100644 drivers/gpu/drm/drm_dp_helper_internal.h delete mode 100644 drivers/gpu/drm/drm_dp_mst_topology.c delete mode 100644 drivers/gpu/drm/drm_dp_mst_topology_internal.h (limited to 'drivers') diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index b1f22e457fd0..91f54aeb0b7c 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -80,6 +80,12 @@ config DRM_DEBUG_SELFTEST If in doubt, say "N". +config DRM_DP_HELPER + tristate + depends on DRM + help + DRM helpers for DisplayPort. + config DRM_KMS_HELPER tristate depends on DRM @@ -236,6 +242,7 @@ config DRM_RADEON depends on DRM && PCI && MMU depends on AGP || !AGP select FW_LOADER + select DRM_DP_HELPER select DRM_KMS_HELPER select DRM_TTM select DRM_TTM_HELPER @@ -256,6 +263,7 @@ config DRM_AMDGPU tristate "AMD GPU" depends on DRM && PCI && MMU select FW_LOADER + select DRM_DP_HELPER select DRM_KMS_HELPER select DRM_SCHED select DRM_TTM diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 301a44dc18e3..69be80ef1d31 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -48,21 +48,18 @@ obj-$(CONFIG_DRM_VRAM_HELPER) += drm_vram_helper.o drm_ttm_helper-y := drm_gem_ttm_helper.o obj-$(CONFIG_DRM_TTM_HELPER) += drm_ttm_helper.o -drm_kms_helper-y := drm_bridge_connector.o drm_crtc_helper.o drm_dp_helper.o \ +drm_kms_helper-y := drm_bridge_connector.o drm_crtc_helper.o \ drm_dsc.o drm_encoder_slave.o drm_flip_work.o drm_hdcp.o \ drm_probe_helper.o \ - drm_plane_helper.o drm_dp_mst_topology.o drm_atomic_helper.o \ - drm_kms_helper_common.o drm_dp_dual_mode_helper.o \ + drm_plane_helper.o drm_atomic_helper.o \ + drm_kms_helper_common.o \ drm_simple_kms_helper.o drm_modeset_helper.o \ drm_scdc_helper.o drm_gem_atomic_helper.o \ drm_gem_framebuffer_helper.o \ drm_atomic_state_helper.o drm_damage_helper.o \ drm_format_helper.o drm_self_refresh_helper.o drm_rect.o - drm_kms_helper-$(CONFIG_DRM_PANEL_BRIDGE) += bridge/panel.o drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o -drm_kms_helper-$(CONFIG_DRM_DP_AUX_CHARDEV) += drm_dp_aux_dev.o -drm_kms_helper-$(CONFIG_DRM_DP_CEC) += drm_dp_cec.o obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o obj-$(CONFIG_DRM_DEBUG_SELFTEST) += selftests/ @@ -72,6 +69,7 @@ obj-$(CONFIG_DRM_MIPI_DBI) += drm_mipi_dbi.o obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o obj-$(CONFIG_DRM_PANEL_ORIENTATION_QUIRKS) += drm_panel_orientation_quirks.o obj-y += arm/ +obj-y += dp/ obj-$(CONFIG_DRM_TTM) += ttm/ obj-$(CONFIG_DRM_SCHED) += scheduler/ obj-$(CONFIG_DRM_TDFX) += tdfx/ diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index a1b52eaf26e0..fcd93f1aec90 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -184,6 +184,7 @@ config DRM_PARADE_PS8640 tristate "Parade PS8640 MIPI DSI to eDP Converter" depends on OF select DRM_DP_AUX_BUS + select DRM_DP_HELPER select DRM_KMS_HELPER select DRM_MIPI_DSI select DRM_PANEL @@ -254,6 +255,7 @@ config DRM_TOSHIBA_TC358764 config DRM_TOSHIBA_TC358767 tristate "Toshiba TC358767 eDP bridge" depends on OF + select DRM_DP_HELPER select DRM_KMS_HELPER select REGMAP_I2C select DRM_PANEL @@ -273,6 +275,7 @@ config DRM_TOSHIBA_TC358768 config DRM_TOSHIBA_TC358775 tristate "Toshiba TC358775 DSI/LVDS bridge" depends on OF + select DRM_DP_HELPER select DRM_KMS_HELPER select REGMAP_I2C select DRM_PANEL @@ -300,6 +303,7 @@ config DRM_TI_SN65DSI83 config DRM_TI_SN65DSI86 tristate "TI SN65DSI86 DSI to eDP bridge" depends on OF + select DRM_DP_HELPER select DRM_KMS_HELPER select REGMAP_I2C select DRM_PANEL diff --git a/drivers/gpu/drm/bridge/analogix/Kconfig b/drivers/gpu/drm/bridge/analogix/Kconfig index 2ef6eb2b786c..319ba0df57be 100644 --- a/drivers/gpu/drm/bridge/analogix/Kconfig +++ b/drivers/gpu/drm/bridge/analogix/Kconfig @@ -3,6 +3,7 @@ config DRM_ANALOGIX_ANX6345 tristate "Analogix ANX6345 bridge" depends on OF select DRM_ANALOGIX_DP + select DRM_DP_HELPER select DRM_KMS_HELPER select REGMAP_I2C help @@ -14,6 +15,7 @@ config DRM_ANALOGIX_ANX6345 config DRM_ANALOGIX_ANX78XX tristate "Analogix ANX78XX bridge" select DRM_ANALOGIX_DP + select DRM_DP_HELPER select DRM_KMS_HELPER select REGMAP_I2C help diff --git a/drivers/gpu/drm/bridge/cadence/Kconfig b/drivers/gpu/drm/bridge/cadence/Kconfig index ef8c230e0f62..de697bade05e 100644 --- a/drivers/gpu/drm/bridge/cadence/Kconfig +++ b/drivers/gpu/drm/bridge/cadence/Kconfig @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only config DRM_CDNS_MHDP8546 tristate "Cadence DPI/DP bridge" + select DRM_DP_HELPER select DRM_KMS_HELPER select DRM_PANEL_BRIDGE depends on OF diff --git a/drivers/gpu/drm/dp/Makefile b/drivers/gpu/drm/dp/Makefile new file mode 100644 index 000000000000..5b892aeff5ab --- /dev/null +++ b/drivers/gpu/drm/dp/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: MIT + +drm_dp_helper-y := drm_dp.o drm_dp_dual_mode_helper.o drm_dp_helper_mod.o drm_dp_mst_topology.o +drm_dp_helper-$(CONFIG_DRM_DP_AUX_CHARDEV) += drm_dp_aux_dev.o +drm_dp_helper-$(CONFIG_DRM_DP_CEC) += drm_dp_cec.o + +obj-$(CONFIG_DRM_DP_HELPER) += drm_dp_helper.o diff --git a/drivers/gpu/drm/dp/drm_dp.c b/drivers/gpu/drm/dp/drm_dp.c new file mode 100644 index 000000000000..e995a0262ed7 --- /dev/null +++ b/drivers/gpu/drm/dp/drm_dp.c @@ -0,0 +1,3744 @@ +/* + * Copyright © 2009 Keith Packard + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "drm_dp_helper_internal.h" + +struct dp_aux_backlight { + struct backlight_device *base; + struct drm_dp_aux *aux; + struct drm_edp_backlight_info info; + bool enabled; +}; + +/** + * DOC: dp helpers + * + * These functions contain some common logic and helpers at various abstraction + * levels to deal with Display Port sink devices and related things like DP aux + * channel transfers, EDID reading over DP aux channels, decoding certain DPCD + * blocks, ... + */ + +/* Helpers for DP link training */ +static u8 dp_link_status(const u8 link_status[DP_LINK_STATUS_SIZE], int r) +{ + return link_status[r - DP_LANE0_1_STATUS]; +} + +static u8 dp_get_lane_status(const u8 link_status[DP_LINK_STATUS_SIZE], + int lane) +{ + int i = DP_LANE0_1_STATUS + (lane >> 1); + int s = (lane & 1) * 4; + u8 l = dp_link_status(link_status, i); + + return (l >> s) & 0xf; +} + +bool drm_dp_channel_eq_ok(const u8 link_status[DP_LINK_STATUS_SIZE], + int lane_count) +{ + u8 lane_align; + u8 lane_status; + int lane; + + lane_align = dp_link_status(link_status, + DP_LANE_ALIGN_STATUS_UPDATED); + if ((lane_align & DP_INTERLANE_ALIGN_DONE) == 0) + return false; + for (lane = 0; lane < lane_count; lane++) { + lane_status = dp_get_lane_status(link_status, lane); + if ((lane_status & DP_CHANNEL_EQ_BITS) != DP_CHANNEL_EQ_BITS) + return false; + } + return true; +} +EXPORT_SYMBOL(drm_dp_channel_eq_ok); + +bool drm_dp_clock_recovery_ok(const u8 link_status[DP_LINK_STATUS_SIZE], + int lane_count) +{ + int lane; + u8 lane_status; + + for (lane = 0; lane < lane_count; lane++) { + lane_status = dp_get_lane_status(link_status, lane); + if ((lane_status & DP_LANE_CR_DONE) == 0) + return false; + } + return true; +} +EXPORT_SYMBOL(drm_dp_clock_recovery_ok); + +u8 drm_dp_get_adjust_request_voltage(const u8 link_status[DP_LINK_STATUS_SIZE], + int lane) +{ + int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1); + int s = ((lane & 1) ? + DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT : + DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT); + u8 l = dp_link_status(link_status, i); + + return ((l >> s) & 0x3) << DP_TRAIN_VOLTAGE_SWING_SHIFT; +} +EXPORT_SYMBOL(drm_dp_get_adjust_request_voltage); + +u8 drm_dp_get_adjust_request_pre_emphasis(const u8 link_status[DP_LINK_STATUS_SIZE], + int lane) +{ + int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1); + int s = ((lane & 1) ? + DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT : + DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT); + u8 l = dp_link_status(link_status, i); + + return ((l >> s) & 0x3) << DP_TRAIN_PRE_EMPHASIS_SHIFT; +} +EXPORT_SYMBOL(drm_dp_get_adjust_request_pre_emphasis); + +/* DP 2.0 128b/132b */ +u8 drm_dp_get_adjust_tx_ffe_preset(const u8 link_status[DP_LINK_STATUS_SIZE], + int lane) +{ + int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1); + int s = ((lane & 1) ? + DP_ADJUST_TX_FFE_PRESET_LANE1_SHIFT : + DP_ADJUST_TX_FFE_PRESET_LANE0_SHIFT); + u8 l = dp_link_status(link_status, i); + + return (l >> s) & 0xf; +} +EXPORT_SYMBOL(drm_dp_get_adjust_tx_ffe_preset); + +u8 drm_dp_get_adjust_request_post_cursor(const u8 link_status[DP_LINK_STATUS_SIZE], + unsigned int lane) +{ + unsigned int offset = DP_ADJUST_REQUEST_POST_CURSOR2; + u8 value = dp_link_status(link_status, offset); + + return (value >> (lane << 1)) & 0x3; +} +EXPORT_SYMBOL(drm_dp_get_adjust_request_post_cursor); + +static int __8b10b_clock_recovery_delay_us(const struct drm_dp_aux *aux, u8 rd_interval) +{ + if (rd_interval > 4) + drm_dbg_kms(aux->drm_dev, "%s: invalid AUX interval 0x%02x (max 4)\n", + aux->name, rd_interval); + + if (rd_interval == 0) + return 100; + + return rd_interval * 4 * USEC_PER_MSEC; +} + +static int __8b10b_channel_eq_delay_us(const struct drm_dp_aux *aux, u8 rd_interval) +{ + if (rd_interval > 4) + drm_dbg_kms(aux->drm_dev, "%s: invalid AUX interval 0x%02x (max 4)\n", + aux->name, rd_interval); + + if (rd_interval == 0) + return 400; + + return rd_interval * 4 * USEC_PER_MSEC; +} + +static int __128b132b_channel_eq_delay_us(const struct drm_dp_aux *aux, u8 rd_interval) +{ + switch (rd_interval) { + default: + drm_dbg_kms(aux->drm_dev, "%s: invalid AUX interval 0x%02x\n", + aux->name, rd_interval); + fallthrough; + case DP_128B132B_TRAINING_AUX_RD_INTERVAL_400_US: + return 400; + case DP_128B132B_TRAINING_AUX_RD_INTERVAL_4_MS: + return 4000; + case DP_128B132B_TRAINING_AUX_RD_INTERVAL_8_MS: + return 8000; + case DP_128B132B_TRAINING_AUX_RD_INTERVAL_12_MS: + return 12000; + case DP_128B132B_TRAINING_AUX_RD_INTERVAL_16_MS: + return 16000; + case DP_128B132B_TRAINING_AUX_RD_INTERVAL_32_MS: + return 32000; + case DP_128B132B_TRAINING_AUX_RD_INTERVAL_64_MS: + return 64000; + } +} + +/* + * The link training delays are different for: + * + * - Clock recovery vs. channel equalization + * - DPRX vs. LTTPR + * - 128b/132b vs. 8b/10b + * - DPCD rev 1.3 vs. later + * + * Get the correct delay in us, reading DPCD if necessary. + */ +static int __read_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE], + enum drm_dp_phy dp_phy, bool uhbr, bool cr) +{ + int (*parse)(const struct drm_dp_aux *aux, u8 rd_interval); + unsigned int offset; + u8 rd_interval, mask; + + if (dp_phy == DP_PHY_DPRX) { + if (uhbr) { + if (cr) + return 100; + + offset = DP_128B132B_TRAINING_AUX_RD_INTERVAL; + mask = DP_128B132B_TRAINING_AUX_RD_INTERVAL_MASK; + parse = __128b132b_channel_eq_delay_us; + } else { + if (cr && dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14) + return 100; + + offset = DP_TRAINING_AUX_RD_INTERVAL; + mask = DP_TRAINING_AUX_RD_MASK; + if (cr) + parse = __8b10b_clock_recovery_delay_us; + else + parse = __8b10b_channel_eq_delay_us; + } + } else { + if (uhbr) { + offset = DP_128B132B_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER(dp_phy); + mask = DP_128B132B_TRAINING_AUX_RD_INTERVAL_MASK; + parse = __128b132b_channel_eq_delay_us; + } else { + if (cr) + return 100; + + offset = DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER(dp_phy); + mask = DP_TRAINING_AUX_RD_MASK; + parse = __8b10b_channel_eq_delay_us; + } + } + + if (offset < DP_RECEIVER_CAP_SIZE) { + rd_interval = dpcd[offset]; + } else { + if (drm_dp_dpcd_readb(aux, offset, &rd_interval) != 1) { + drm_dbg_kms(aux->drm_dev, "%s: failed rd interval read\n", + aux->name); + /* arbitrary default delay */ + return 400; + } + } + + return parse(aux, rd_interval & mask); +} + +int drm_dp_read_clock_recovery_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE], + enum drm_dp_phy dp_phy, bool uhbr) +{ + return __read_delay(aux, dpcd, dp_phy, uhbr, true); +} +EXPORT_SYMBOL(drm_dp_read_clock_recovery_delay); + +int drm_dp_read_channel_eq_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE], + enum drm_dp_phy dp_phy, bool uhbr) +{ + return __read_delay(aux, dpcd, dp_phy, uhbr, false); +} +EXPORT_SYMBOL(drm_dp_read_channel_eq_delay); + +void drm_dp_link_train_clock_recovery_delay(const struct drm_dp_aux *aux, + const u8 dpcd[DP_RECEIVER_CAP_SIZE]) +{ + u8 rd_interval = dpcd[DP_TRAINING_AUX_RD_INTERVAL] & + DP_TRAINING_AUX_RD_MASK; + int delay_us; + + if (dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14) + delay_us = 100; + else + delay_us = __8b10b_clock_recovery_delay_us(aux, rd_interval); + + usleep_range(delay_us, delay_us * 2); +} +EXPORT_SYMBOL(drm_dp_link_train_clock_recovery_delay); + +static void __drm_dp_link_train_channel_eq_delay(const struct drm_dp_aux *aux, + u8 rd_interval) +{ + int delay_us = __8b10b_channel_eq_delay_us(aux, rd_interval); + + usleep_range(delay_us, delay_us * 2); +} + +void drm_dp_link_train_channel_eq_delay(const struct drm_dp_aux *aux, + const u8 dpcd[DP_RECEIVER_CAP_SIZE]) +{ + __drm_dp_link_train_channel_eq_delay(aux, + dpcd[DP_TRAINING_AUX_RD_INTERVAL] & + DP_TRAINING_AUX_RD_MASK); +} +EXPORT_SYMBOL(drm_dp_link_train_channel_eq_delay); + +void drm_dp_lttpr_link_train_clock_recovery_delay(void) +{ + usleep_range(100, 200); +} +EXPORT_SYMBOL(drm_dp_lttpr_link_train_clock_recovery_delay); + +static u8 dp_lttpr_phy_cap(const u8 phy_cap[DP_LTTPR_PHY_CAP_SIZE], int r) +{ + return phy_cap[r - DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER1]; +} + +void drm_dp_lttpr_link_train_channel_eq_delay(const struct drm_dp_aux *aux, + const u8 phy_cap[DP_LTTPR_PHY_CAP_SIZE]) +{ + u8 interval = dp_lttpr_phy_cap(phy_cap, + DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER1) & + DP_TRAINING_AUX_RD_MASK; + + __drm_dp_link_train_channel_eq_delay(aux, interval); +} +EXPORT_SYMBOL(drm_dp_lttpr_link_train_channel_eq_delay); + +u8 drm_dp_link_rate_to_bw_code(int link_rate) +{ + switch (link_rate) { + case 1000000: + return DP_LINK_BW_10; + case 1350000: + return DP_LINK_BW_13_5; + case 2000000: + return DP_LINK_BW_20; + default: + /* Spec says link_bw = link_rate / 0.27Gbps */ + return link_rate / 27000; + } +} +EXPORT_SYMBOL(drm_dp_link_rate_to_bw_code); + +int drm_dp_bw_code_to_link_rate(u8 link_bw) +{ + switch (link_bw) { + case DP_LINK_BW_10: + return 1000000; + case DP_LINK_BW_13_5: + return 1350000; + case DP_LINK_BW_20: + return 2000000; + default: + /* Spec says link_rate = link_bw * 0.27Gbps */ + return link_bw * 27000; + } +} +EXPORT_SYMBOL(drm_dp_bw_code_to_link_rate); + +#define AUX_RETRY_INTERVAL 500 /* us */ + +static inline void +drm_dp_dump_access(const struct drm_dp_aux *aux, + u8 request, uint offset, void *buffer, int ret) +{ + const char *arrow = request == DP_AUX_NATIVE_READ ? "->" : "<-"; + + if (ret > 0) + drm_dbg_dp(aux->drm_dev, "%s: 0x%05x AUX %s (ret=%3d) %*ph\n", + aux->name, offset, arrow, ret, min(ret, 20), buffer); + else + drm_dbg_dp(aux->drm_dev, "%s: 0x%05x AUX %s (ret=%3d)\n", + aux->name, offset, arrow, ret); +} + +/** + * DOC: dp helpers + * + * The DisplayPort AUX channel is an abstraction to allow generic, driver- + * independent access to AUX functionality. Drivers can take advantage of + * this by filling in the fields of the drm_dp_aux structure. + * + * Transactions are described using a hardware-independent drm_dp_aux_msg + * structure, which is passed into a driver's .transfer() implementation. + * Both native and I2C-over-AUX transactions are supported. + */ + +static int drm_dp_dpcd_access(struct drm_dp_aux *aux, u8 request, + unsigned int offset, void *buffer, size_t size) +{ + struct drm_dp_aux_msg msg; + unsigned int retry, native_reply; + int err = 0, ret = 0; + + memset(&msg, 0, sizeof(msg)); + msg.address = offset; + msg.request = request; + msg.buffer = buffer; + msg.size = size; + + mutex_lock(&aux->hw_mutex); + + /* + * The specification doesn't give any recommendation on how often to + * retry native transactions. We used to retry 7 times like for + * aux i2c transactions but real world devices this wasn't + * sufficient, bump to 32 which makes Dell 4k monitors happier. + */ + for (retry = 0; retry < 32; retry++) { + if (ret != 0 && ret != -ETIMEDOUT) { + usleep_range(AUX_RETRY_INTERVAL, + AUX_RETRY_INTERVAL + 100); + } + + ret = aux->transfer(aux, &msg); + if (ret >= 0) { + native_reply = msg.reply & DP_AUX_NATIVE_REPLY_MASK; + if (native_reply == DP_AUX_NATIVE_REPLY_ACK) { + if (ret == size) + goto unlock; + + ret = -EPROTO; + } else + ret = -EIO; + } + + /* + * We want the error we return to be the error we received on + * the first transaction, since we may get a different error the + * next time we retry + */ + if (!err) + err = ret; + } + + drm_dbg_kms(aux->drm_dev, "%s: Too many retries, giving up. First error: %d\n", + aux->name, err); + ret = err; + +unlock: + mutex_unlock(&aux->hw_mutex); + return ret; +} + +/** + * drm_dp_dpcd_read() - read a series of bytes from the DPCD + * @aux: DisplayPort AUX channel (SST or MST) + * @offset: address of the (first) register to read + * @buffer: buffer to store the register values + * @size: number of bytes in @buffer + * + * Returns the number of bytes transferred on success, or a negative error + * code on failure. -EIO is returned if the request was NAKed by the sink or + * if the retry count was exceeded. If not all bytes were transferred, this + * function returns -EPROTO. Errors from the underlying AUX channel transfer + * function, with the exception of -EBUSY (which causes the transaction to + * be retried), are propagated to the caller. + */ +ssize_t drm_dp_dpcd_read(struct drm_dp_aux *aux, unsigned int offset, + void *buffer, size_t size) +{ + int ret; + + /* + * HP ZR24w corrupts the first DPCD access after entering power save + * mode. Eg. on a read, the entire buffer will be filled with the same + * byte. Do a throw away read to avoid corrupting anything we care + * about. Afterwards things will work correctly until the monitor + * gets woken up and subsequently re-enters power save mode. + * + * The user pressing any button on the monitor is enough to wake it + * up, so there is no particularly good place to do the workaround. + * We just have to do it before any DPCD access and hope that the + * monitor doesn't power down exactly after the throw away read. + */ + if (!aux->is_remote) { + ret = drm_dp_dpcd_access(aux, DP_AUX_NATIVE_READ, DP_DPCD_REV, + buffer, 1); + if (ret != 1) + goto out; + } + + if (aux->is_remote) + ret = drm_dp_mst_dpcd_read(aux, offset, buffer, size); + else + ret = drm_dp_dpcd_access(aux, DP_AUX_NATIVE_READ, offset, + buffer, size); + +out: + drm_dp_dump_access(aux, DP_AUX_NATIVE_READ, offset, buffer, ret); + return ret; +} +EXPORT_SYMBOL(drm_dp_dpcd_read); + +/** + * drm_dp_dpcd_write() - write a series of bytes to the DPCD + * @aux: DisplayPort AUX channel (SST or MST) + * @offset: address of the (first) register to write + * @buffer: buffer containing the values to write + * @size: number of bytes in @buffer + * + * Returns the number of bytes transferred on success, or a negative error + * code on failure. -EIO is returned if the request was NAKed by the sink or + * if the retry count was exceeded. If not all bytes were transferred, this + * function returns -EPROTO. Errors from the underlying AUX channel transfer + * function, with the exception of -EBUSY (which causes the transaction to + * be retried), are propagated to the caller. + */ +ssize_t drm_dp_dpcd_write(struct drm_dp_aux *aux, unsigned int offset, + void *buffer, size_t size) +{ + int ret; + + if (aux->is_remote) + ret = drm_dp_mst_dpcd_write(aux, offset, buffer, size); + else + ret = drm_dp_dpcd_access(aux, DP_AUX_NATIVE_WRITE, offset, + buffer, size); + + drm_dp_dump_access(aux, DP_AUX_NATIVE_WRITE, offset, buffer, ret); + return ret; +} +EXPORT_SYMBOL(drm_dp_dpcd_write); + +/** + * drm_dp_dpcd_read_link_status() - read DPCD link status (bytes 0x202-0x207) + * @aux: DisplayPort AUX channel + * @status: buffer to store the link status in (must be at least 6 bytes) + * + * Returns the number of bytes transferred on success or a negative error + * code on failure. + */ +int drm_dp_dpcd_read_link_status(struct drm_dp_aux *aux, + u8 status[DP_LINK_STATUS_SIZE]) +{ + return drm_dp_dpcd_read(aux, DP_LANE0_1_STATUS, status, + DP_LINK_STATUS_SIZE); +} +EXPORT_SYMBOL(drm_dp_dpcd_read_link_status); + +/** + * drm_dp_dpcd_read_phy_link_status - get the link status information for a DP PHY + * @aux: DisplayPort AUX channel + * @dp_phy: the DP PHY to get the link status for + * @link_status: buffer to return the status in + * + * Fetch the AUX DPCD registers for the DPRX or an LTTPR PHY link status. The + * layout of the returned @link_status matches the DPCD register layout of the + * DPRX PHY link status. + * + * Returns 0 if the information was read successfully or a negative error code + * on failure. + */ +int drm_dp_dpcd_read_phy_link_status(struct drm_dp_aux *aux, + enum drm_dp_phy dp_phy, + u8 link_status[DP_LINK_STATUS_SIZE]) +{ + int ret; + + if (dp_phy == DP_PHY_DPRX) { + ret = drm_dp_dpcd_read(aux, + DP_LANE0_1_STATUS, + link_status, + DP_LINK_STATUS_SIZE); + + if (ret < 0) + return ret; + + WARN_ON(ret != DP_LINK_STATUS_SIZE); + + return 0; + } + + ret = drm_dp_dpcd_read(aux, + DP_LANE0_1_STATUS_PHY_REPEATER(dp_phy), + link_status, + DP_LINK_STATUS_SIZE - 1); + + if (ret < 0) + return ret; + + WARN_ON(ret != DP_LINK_STATUS_SIZE - 1); + + /* Convert the LTTPR to the sink PHY link status layout */ + memmove(&link_status[DP_SINK_STATUS - DP_LANE0_1_STATUS + 1], + &link_status[DP_SINK_STATUS - DP_LANE0_1_STATUS], + DP_LINK_STATUS_SIZE - (DP_SINK_STATUS - DP_LANE0_1_STATUS) - 1); + link_status[DP_SINK_STATUS - DP_LANE0_1_STATUS] = 0; + + return 0; +} +EXPORT_SYMBOL(drm_dp_dpcd_read_phy_link_status); + +static bool is_edid_digital_input_dp(const struct edid *edid) +{ + return edid && edid->revision >= 4 && + edid->input & DRM_EDID_INPUT_DIGITAL && + (edid->input & DRM_EDID_DIGITAL_TYPE_MASK) == DRM_EDID_DIGITAL_TYPE_DP; +} + +/** + * drm_dp_downstream_is_type() - is the downstream facing port of certain type? + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + * @type: port type to be checked. Can be: + * %DP_DS_PORT_TYPE_DP, %DP_DS_PORT_TYPE_VGA, %DP_DS_PORT_TYPE_DVI, + * %DP_DS_PORT_TYPE_HDMI, %DP_DS_PORT_TYPE_NON_EDID, + * %DP_DS_PORT_TYPE_DP_DUALMODE or %DP_DS_PORT_TYPE_WIRELESS. + * + * Caveat: Only works with DPCD 1.1+ port caps. + * + * Returns: whether the downstream facing port matches the type. + */ +bool drm_dp_downstream_is_type(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4], u8 type) +{ + return drm_dp_is_branch(dpcd) && + dpcd[DP_DPCD_REV] >= 0x11 && + (port_cap[0] & DP_DS_PORT_TYPE_MASK) == type; +} +EXPORT_SYMBOL(drm_dp_downstream_is_type); + +/** + * drm_dp_downstream_is_tmds() - is the downstream facing port TMDS? + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + * @edid: EDID + * + * Returns: whether the downstream facing port is TMDS (HDMI/DVI). + */ +bool drm_dp_downstream_is_tmds(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4], + const struct edid *edid) +{ + if (dpcd[DP_DPCD_REV] < 0x11) { + switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) { + case DP_DWN_STRM_PORT_TYPE_TMDS: + return true; + default: + return false; + } + } + + switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { + case DP_DS_PORT_TYPE_DP_DUALMODE: + if (is_edid_digital_input_dp(edid)) + return false; + fallthrough; + case DP_DS_PORT_TYPE_DVI: + case DP_DS_PORT_TYPE_HDMI: + return true; + default: + return false; + } +} +EXPORT_SYMBOL(drm_dp_downstream_is_tmds); + +/** + * drm_dp_send_real_edid_checksum() - send back real edid checksum value + * @aux: DisplayPort AUX channel + * @real_edid_checksum: real edid checksum for the last block + * + * Returns: + * True on success + */ +bool drm_dp_send_real_edid_checksum(struct drm_dp_aux *aux, + u8 real_edid_checksum) +{ + u8 link_edid_read = 0, auto_test_req = 0, test_resp = 0; + + if (drm_dp_dpcd_read(aux, DP_DEVICE_SERVICE_IRQ_VECTOR, + &auto_test_req, 1) < 1) { + drm_err(aux->drm_dev, "%s: DPCD failed read at register 0x%x\n", + aux->name, DP_DEVICE_SERVICE_IRQ_VECTOR); + return false; + } + auto_test_req &= DP_AUTOMATED_TEST_REQUEST; + + if (drm_dp_dpcd_read(aux, DP_TEST_REQUEST, &link_edid_read, 1) < 1) { + drm_err(aux->drm_dev, "%s: DPCD failed read at register 0x%x\n", + aux->name, DP_TEST_REQUEST); + return false; + } + link_edid_read &= DP_TEST_LINK_EDID_READ; + + if (!auto_test_req || !link_edid_read) { + drm_dbg_kms(aux->drm_dev, "%s: Source DUT does not support TEST_EDID_READ\n", + aux->name); + return false; + } + + if (drm_dp_dpcd_write(aux, DP_DEVICE_SERVICE_IRQ_VECTOR, + &auto_test_req, 1) < 1) { + drm_err(aux->drm_dev, "%s: DPCD failed write at register 0x%x\n", + aux->name, DP_DEVICE_SERVICE_IRQ_VECTOR); + return false; + } + + /* send back checksum for the last edid extension block data */ + if (drm_dp_dpcd_write(aux, DP_TEST_EDID_CHECKSUM, + &real_edid_checksum, 1) < 1) { + drm_err(aux->drm_dev, "%s: DPCD failed write at register 0x%x\n", + aux->name, DP_TEST_EDID_CHECKSUM); + return false; + } + + test_resp |= DP_TEST_EDID_CHECKSUM_WRITE; + if (drm_dp_dpcd_write(aux, DP_TEST_RESPONSE, &test_resp, 1) < 1) { + drm_err(aux->drm_dev, "%s: DPCD failed write at register 0x%x\n", + aux->name, DP_TEST_RESPONSE); + return false; + } + + return true; +} +EXPORT_SYMBOL(drm_dp_send_real_edid_checksum); + +static u8 drm_dp_downstream_port_count(const u8 dpcd[DP_RECEIVER_CAP_SIZE]) +{ + u8 port_count = dpcd[DP_DOWN_STREAM_PORT_COUNT] & DP_PORT_COUNT_MASK; + + if (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE && port_count > 4) + port_count = 4; + + return port_count; +} + +static int drm_dp_read_extended_dpcd_caps(struct drm_dp_aux *aux, + u8 dpcd[DP_RECEIVER_CAP_SIZE]) +{ + u8 dpcd_ext[DP_RECEIVER_CAP_SIZE]; + int ret; + + /* + * Prior to DP1.3 the bit represented by + * DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT was reserved. + * If it is set DP_DPCD_REV at 0000h could be at a value less than + * the true capability of the panel. The only way to check is to + * then compare 0000h and 2200h. + */ + if (!(dpcd[DP_TRAINING_AUX_RD_INTERVAL] & + DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT)) + return 0; + + ret = drm_dp_dpcd_read(aux, DP_DP13_DPCD_REV, &dpcd_ext, + sizeof(dpcd_ext)); + if (ret < 0) + return ret; + if (ret != sizeof(dpcd_ext)) + return -EIO; + + if (dpcd[DP_DPCD_REV] > dpcd_ext[DP_DPCD_REV]) { + drm_dbg_kms(aux->drm_dev, + "%s: Extended DPCD rev less than base DPCD rev (%d > %d)\n", + aux->name, dpcd[DP_DPCD_REV], dpcd_ext[DP_DPCD_REV]); + return 0; + } + + if (!memcmp(dpcd, dpcd_ext, sizeof(dpcd_ext))) + return 0; + + drm_dbg_kms(aux->drm_dev, "%s: Base DPCD: %*ph\n", aux->name, DP_RECEIVER_CAP_SIZE, dpcd); + + memcpy(dpcd, dpcd_ext, sizeof(dpcd_ext)); + + return 0; +} + +/** + * drm_dp_read_dpcd_caps() - read DPCD caps and extended DPCD caps if + * available + * @aux: DisplayPort AUX channel + * @dpcd: Buffer to store the resulting DPCD in + * + * Attempts to read the base DPCD caps for @aux. Additionally, this function + * checks for and reads the extended DPRX caps (%DP_DP13_DPCD_REV) if + * present. + * + * Returns: %0 if the DPCD was read successfully, negative error code + * otherwise. + */ +int drm_dp_read_dpcd_caps(struct drm_dp_aux *aux, + u8 dpcd[DP_RECEIVER_CAP_SIZE]) +{ + int ret; + + ret = drm_dp_dpcd_read(aux, DP_DPCD_REV, dpcd, DP_RECEIVER_CAP_SIZE); + if (ret < 0) + return ret; + if (ret != DP_RECEIVER_CAP_SIZE || dpcd[DP_DPCD_REV] == 0) + return -EIO; + + ret = drm_dp_read_extended_dpcd_caps(aux, dpcd); + if (ret < 0) + return ret; + + drm_dbg_kms(aux->drm_dev, "%s: DPCD: %*ph\n", aux->name, DP_RECEIVER_CAP_SIZE, dpcd); + + return ret; +} +EXPORT_SYMBOL(drm_dp_read_dpcd_caps); + +/** + * drm_dp_read_downstream_info() - read DPCD downstream port info if available + * @aux: DisplayPort AUX channel + * @dpcd: A cached copy of the port's DPCD + * @downstream_ports: buffer to store the downstream port info in + * + * See also: + * drm_dp_downstream_max_clock() + * drm_dp_downstream_max_bpc() + * + * Returns: 0 if either the downstream port info was read successfully or + * there was no downstream info to read, or a negative error code otherwise. + */ +int drm_dp_read_downstream_info(struct drm_dp_aux *aux, + const u8 dpcd[DP_RECEIVER_CAP_SIZE], + u8 downstream_ports[DP_MAX_DOWNSTREAM_PORTS]) +{ + int ret; + u8 len; + + memset(downstream_ports, 0, DP_MAX_DOWNSTREAM_PORTS); + + /* No downstream info to read */ + if (!drm_dp_is_branch(dpcd) || dpcd[DP_DPCD_REV] == DP_DPCD_REV_10) + return 0; + + /* Some branches advertise having 0 downstream ports, despite also advertising they have a + * downstream port present. The DP spec isn't clear on if this is allowed or not, but since + * some branches do it we need to handle it regardless. + */ + len = drm_dp_downstream_port_count(dpcd); + if (!len) + return 0; + + if (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) + len *= 4; + + ret = drm_dp_dpcd_read(aux, DP_DOWNSTREAM_PORT_0, downstream_ports, len); + if (ret < 0) + return ret; + if (ret != len) + return -EIO; + + drm_dbg_kms(aux->drm_dev, "%s: DPCD DFP: %*ph\n", aux->name, len, downstream_ports); + + return 0; +} +EXPORT_SYMBOL(drm_dp_read_downstream_info); + +/** + * drm_dp_downstream_max_dotclock() - extract downstream facing port max dot clock + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + * + * Returns: Downstream facing port max dot clock in kHz on success, + * or 0 if max clock not defined + */ +int drm_dp_downstream_max_dotclock(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4]) +{ + if (!drm_dp_is_branch(dpcd)) + return 0; + + if (dpcd[DP_DPCD_REV] < 0x11) + return 0; + + switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { + case DP_DS_PORT_TYPE_VGA: + if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) + return 0; + return port_cap[1] * 8000; + default: + return 0; + } +} +EXPORT_SYMBOL(drm_dp_downstream_max_dotclock); + +/** + * drm_dp_downstream_max_tmds_clock() - extract downstream facing port max TMDS clock + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + * @edid: EDID + * + * Returns: HDMI/DVI downstream facing port max TMDS clock in kHz on success, + * or 0 if max TMDS clock not defined + */ +int drm_dp_downstream_max_tmds_clock(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4], + const struct edid *edid) +{ + if (!drm_dp_is_branch(dpcd)) + return 0; + + if (dpcd[DP_DPCD_REV] < 0x11) { + switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) { + case DP_DWN_STRM_PORT_TYPE_TMDS: + return 165000; + default: + return 0; + } + } + + switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { + case DP_DS_PORT_TYPE_DP_DUALMODE: + if (is_edid_digital_input_dp(edid)) + return 0; + /* + * It's left up to the driver to check the + * DP dual mode adapter's max TMDS clock. + * + * Unfortunately it looks like branch devices + * may not fordward that the DP dual mode i2c + * access so we just usually get i2c nak :( + */ + fallthrough; + case DP_DS_PORT_TYPE_HDMI: + /* + * We should perhaps assume 165 MHz when detailed cap + * info is not available. But looks like many typical + * branch devices fall into that category and so we'd + * probably end up with users complaining that they can't + * get high resolution modes with their favorite dongle. + * + * So let's limit to 300 MHz instead since DPCD 1.4 + * HDMI 2.0 DFPs are required to have the detailed cap + * info. So it's more likely we're dealing with a HDMI 1.4 + * compatible* device here. + */ + if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) + return 300000; + return port_cap[1] * 2500; + case DP_DS_PORT_TYPE_DVI: + if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) + return 165000; + /* FIXME what to do about DVI dual link? */ + return port_cap[1] * 2500; + default: + return 0; + } +} +EXPORT_SYMBOL(drm_dp_downstream_max_tmds_clock); + +/** + * drm_dp_downstream_min_tmds_clock() - extract downstream facing port min TMDS clock + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + * @edid: EDID + * + * Returns: HDMI/DVI downstream facing port min TMDS clock in kHz on success, + * or 0 if max TMDS clock not defined + */ +int drm_dp_downstream_min_tmds_clock(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4], + const struct edid *edid) +{ + if (!drm_dp_is_branch(dpcd)) + return 0; + + if (dpcd[DP_DPCD_REV] < 0x11) { + switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) { + case DP_DWN_STRM_PORT_TYPE_TMDS: + return 25000; + default: + return 0; + } + } + + switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { + case DP_DS_PORT_TYPE_DP_DUALMODE: + if (is_edid_digital_input_dp(edid)) + return 0; + fallthrough; + case DP_DS_PORT_TYPE_DVI: + case DP_DS_PORT_TYPE_HDMI: + /* + * Unclear whether the protocol converter could + * utilize pixel replication. Assume it won't. + */ + return 25000; + default: + return 0; + } +} +EXPORT_SYMBOL(drm_dp_downstream_min_tmds_clock); + +/** + * drm_dp_downstream_max_bpc() - extract downstream facing port max + * bits per component + * @dpcd: DisplayPort configuration data + * @port_cap: downstream facing port capabilities + * @edid: EDID + * + * Returns: Max bpc on success or 0 if max bpc not defined + */ +int drm_dp_downstream_max_bpc(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4], + const struct edid *edid) +{ + if (!drm_dp_is_branch(dpcd)) + return 0; + + if (dpcd[DP_DPCD_REV] < 0x11) { + switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) { + case DP_DWN_STRM_PORT_TYPE_DP: + return 0; + default: + return 8; + } + } + + switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { + case DP_DS_PORT_TYPE_DP: + return 0; + case DP_DS_PORT_TYPE_DP_DUALMODE: + if (is_edid_digital_input_dp(edid)) + return 0; + fallthrough; + case DP_DS_PORT_TYPE_HDMI: + case DP_DS_PORT_TYPE_DVI: + case DP_DS_PORT_TYPE_VGA: + if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) + return 8; + + switch (port_cap[2] & DP_DS_MAX_BPC_MASK) { + case DP_DS_8BPC: + return 8; + case DP_DS_10BPC: + return 10; + case DP_DS_12BPC: + return 12; + case DP_DS_16BPC: + return 16; + default: + return 8; + } + break; + default: + return 8; + } +} +EXPORT_SYMBOL(drm_dp_downstream_max_bpc); + +/** + * drm_dp_downstream_420_passthrough() - determine downstream facing port + * YCbCr 4:2:0 pass-through capability + * @dpcd: DisplayPort configuration data + * @port_cap: downstream facing port capabilities + * + * Returns: whether the downstream facing port can pass through YCbCr 4:2:0 + */ +bool drm_dp_downstream_420_passthrough(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4]) +{ + if (!drm_dp_is_branch(dpcd)) + return false; + + if (dpcd[DP_DPCD_REV] < 0x13) + return false; + + switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { + case DP_DS_PORT_TYPE_DP: + return true; + case DP_DS_PORT_TYPE_HDMI: + if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) + return false; + + return port_cap[3] & DP_DS_HDMI_YCBCR420_PASS_THROUGH; + default: + return false; + } +} +EXPORT_SYMBOL(drm_dp_downstream_420_passthrough); + +/** + * drm_dp_downstream_444_to_420_conversion() - determine downstream facing port + * YCbCr 4:4:4->4:2:0 conversion capability + * @dpcd: DisplayPort configuration data + * @port_cap: downstream facing port capabilities + * + * Returns: whether the downstream facing port can convert YCbCr 4:4:4 to 4:2:0 + */ +bool drm_dp_downstream_444_to_420_conversion(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4]) +{ + if (!drm_dp_is_branch(dpcd)) + return false; + + if (dpcd[DP_DPCD_REV] < 0x13) + return false; + + switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { + case DP_DS_PORT_TYPE_HDMI: + if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) + return false; + + return port_cap[3] & DP_DS_HDMI_YCBCR444_TO_420_CONV; + default: + return false; + } +} +EXPORT_SYMBOL(drm_dp_downstream_444_to_420_conversion); + +/** + * drm_dp_downstream_rgb_to_ycbcr_conversion() - determine downstream facing port + * RGB->YCbCr conversion capability + * @dpcd: DisplayPort configuration data + * @port_cap: downstream facing port capabilities + * @color_spc: Colorspace for which conversion cap is sought + * + * Returns: whether the downstream facing port can convert RGB->YCbCr for a given + * colorspace. + */ +bool drm_dp_downstream_rgb_to_ycbcr_conversion(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4], + u8 color_spc) +{ + if (!drm_dp_is_branch(dpcd)) + return false; + + if (dpcd[DP_DPCD_REV] < 0x13) + return false; + + switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { + case DP_DS_PORT_TYPE_HDMI: + if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) + return false; + + return port_cap[3] & color_spc; + default: + return false; + } +} +EXPORT_SYMBOL(drm_dp_downstream_rgb_to_ycbcr_conversion); + +/** + * drm_dp_downstream_mode() - return a mode for downstream facing port + * @dev: DRM device + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + * + * Provides a suitable mode for downstream facing ports without EDID. + * + * Returns: A new drm_display_mode on success or NULL on failure + */ +struct drm_display_mode * +drm_dp_downstream_mode(struct drm_device *dev, + const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4]) + +{ + u8 vic; + + if (!drm_dp_is_branch(dpcd)) + return NULL; + + if (dpcd[DP_DPCD_REV] < 0x11) + return NULL; + + switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { + case DP_DS_PORT_TYPE_NON_EDID: + switch (port_cap[0] & DP_DS_NON_EDID_MASK) { + case DP_DS_NON_EDID_720x480i_60: + vic = 6; + break; + case DP_DS_NON_EDID_720x480i_50: + vic = 21; + break; + case DP_DS_NON_EDID_1920x1080i_60: + vic = 5; + break; + case DP_DS_NON_EDID_1920x1080i_50: + vic = 20; + break; + case DP_DS_NON_EDID_1280x720_60: + vic = 4; + break; + case DP_DS_NON_EDID_1280x720_50: + vic = 19; + break; + default: + return NULL; + } + return drm_display_mode_from_cea_vic(dev, vic); + default: + return NULL; + } +} +EXPORT_SYMBOL(drm_dp_downstream_mode); + +/** + * drm_dp_downstream_id() - identify branch device + * @aux: DisplayPort AUX channel + * @id: DisplayPort branch device id + * + * Returns branch device id on success or NULL on failure + */ +int drm_dp_downstream_id(struct drm_dp_aux *aux, char id[6]) +{ + return drm_dp_dpcd_read(aux, DP_BRANCH_ID, id, 6); +} +EXPORT_SYMBOL(drm_dp_downstream_id); + +/** + * drm_dp_downstream_debug() - debug DP branch devices + * @m: pointer for debugfs file + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + * @edid: EDID + * @aux: DisplayPort AUX channel + * + */ +void drm_dp_downstream_debug(struct seq_file *m, + const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4], + const struct edid *edid, + struct drm_dp_aux *aux) +{ + bool detailed_cap_info = dpcd[DP_DOWNSTREAMPORT_PRESENT] & + DP_DETAILED_CAP_INFO_AVAILABLE; + int clk; + int bpc; + char id[7]; + int len; + uint8_t rev[2]; + int type = port_cap[0] & DP_DS_PORT_TYPE_MASK; + bool branch_device = drm_dp_is_branch(dpcd); + + seq_printf(m, "\tDP branch device present: %s\n", + branch_device ? "yes" : "no"); + + if (!branch_device) + return; + + switch (type) { + case DP_DS_PORT_TYPE_DP: + seq_puts(m, "\t\tType: DisplayPort\n"); + break; + case DP_DS_PORT_TYPE_VGA: + seq_puts(m, "\t\tType: VGA\n"); + break; + case DP_DS_PORT_TYPE_DVI: + seq_puts(m, "\t\tType: DVI\n"); + break; + case DP_DS_PORT_TYPE_HDMI: + seq_puts(m, "\t\tType: HDMI\n"); + break; + case DP_DS_PORT_TYPE_NON_EDID: + seq_puts(m, "\t\tType: others without EDID support\n"); + break; + case DP_DS_PORT_TYPE_DP_DUALMODE: + seq_puts(m, "\t\tType: DP++\n"); + break; + case DP_DS_PORT_TYPE_WIRELESS: + seq_puts(m, "\t\tType: Wireless\n"); + break; + default: + seq_puts(m, "\t\tType: N/A\n"); + } + + memset(id, 0, sizeof(id)); + drm_dp_downstream_id(aux, id); + seq_printf(m, "\t\tID: %s\n", id); + + len = drm_dp_dpcd_read(aux, DP_BRANCH_HW_REV, &rev[0], 1); + if (len > 0) + seq_printf(m, "\t\tHW: %d.%d\n", + (rev[0] & 0xf0) >> 4, rev[0] & 0xf); + + len = drm_dp_dpcd_read(aux, DP_BRANCH_SW_REV, rev, 2); + if (len > 0) + seq_printf(m, "\t\tSW: %d.%d\n", rev[0], rev[1]); + + if (detailed_cap_info) { + clk = drm_dp_downstream_max_dotclock(dpcd, port_cap); + if (clk > 0) + seq_printf(m, "\t\tMax dot clock: %d kHz\n", clk); + + clk = drm_dp_downstream_max_tmds_clock(dpcd, port_cap, edid); + if (clk > 0) + seq_printf(m, "\t\tMax TMDS clock: %d kHz\n", clk); + + clk = drm_dp_downstream_min_tmds_clock(dpcd, port_cap, edid); + if (clk > 0) + seq_printf(m, "\t\tMin TMDS clock: %d kHz\n", clk); + + bpc = drm_dp_downstream_max_bpc(dpcd, port_cap, edid); + + if (bpc > 0) + seq_printf(m, "\t\tMax bpc: %d\n", bpc); + } +} +EXPORT_SYMBOL(drm_dp_downstream_debug); + +/** + * drm_dp_subconnector_type() - get DP branch device type + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + */ +enum drm_mode_subconnector +drm_dp_subconnector_type(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4]) +{ + int type; + if (!drm_dp_is_branch(dpcd)) + return DRM_MODE_SUBCONNECTOR_Native; + /* DP 1.0 approach */ + if (dpcd[DP_DPCD_REV] == DP_DPCD_REV_10) { + type = dpcd[DP_DOWNSTREAMPORT_PRESENT] & + DP_DWN_STRM_PORT_TYPE_MASK; + + switch (type) { + case DP_DWN_STRM_PORT_TYPE_TMDS: + /* Can be HDMI or DVI-D, DVI-D is a safer option */ + return DRM_MODE_SUBCONNECTOR_DVID; + case DP_DWN_STRM_PORT_TYPE_ANALOG: + /* Can be VGA or DVI-A, VGA is more popular */ + return DRM_MODE_SUBCONNECTOR_VGA; + case DP_DWN_STRM_PORT_TYPE_DP: + return DRM_MODE_SUBCONNECTOR_DisplayPort; + case DP_DWN_STRM_PORT_TYPE_OTHER: + default: + return DRM_MODE_SUBCONNECTOR_Unknown; + } + } + type = port_cap[0] & DP_DS_PORT_TYPE_MASK; + + switch (type) { + case DP_DS_PORT_TYPE_DP: + case DP_DS_PORT_TYPE_DP_DUALMODE: + return DRM_MODE_SUBCONNECTOR_DisplayPort; + case DP_DS_PORT_TYPE_VGA: + return DRM_MODE_SUBCONNECTOR_VGA; + case DP_DS_PORT_TYPE_DVI: + return DRM_MODE_SUBCONNECTOR_DVID; + case DP_DS_PORT_TYPE_HDMI: + return DRM_MODE_SUBCONNECTOR_HDMIA; + case DP_DS_PORT_TYPE_WIRELESS: + return DRM_MODE_SUBCONNECTOR_Wireless; + case DP_DS_PORT_TYPE_NON_EDID: + default: + return DRM_MODE_SUBCONNECTOR_Unknown; + } +} +EXPORT_SYMBOL(drm_dp_subconnector_type); + +/** + * drm_dp_set_subconnector_property - set subconnector for DP connector + * @connector: connector to set property on + * @status: connector status + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + * + * Called by a driver on every detect event. + */ +void drm_dp_set_subconnector_property(struct drm_connector *connector, + enum drm_connector_status status, + const u8 *dpcd, + const u8 port_cap[4]) +{ + enum drm_mode_subconnector subconnector = DRM_MODE_SUBCONNECTOR_Unknown; + + if (status == connector_status_connected) + subconnector = drm_dp_subconnector_type(dpcd, port_cap); + drm_object_property_set_value(&connector->base, + connector->dev->mode_config.dp_subconnector_property, + subconnector); +} +EXPORT_SYMBOL(drm_dp_set_subconnector_property); + +/** + * drm_dp_read_sink_count_cap() - Check whether a given connector has a valid sink + * count + * @connector: The DRM connector to check + * @dpcd: A cached copy of the connector's DPCD RX capabilities + * @desc: A cached copy of the connector's DP descriptor + * + * See also: drm_dp_read_sink_count() + * + * Returns: %True if the (e)DP connector has a valid sink count that should + * be probed, %false otherwise. + */ +bool drm_dp_read_sink_count_cap(struct drm_connector *connector, + const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const struct drm_dp_desc *desc) +{ + /* Some eDP panels don't set a valid value for the sink count */ + return connector->connector_type != DRM_MODE_CONNECTOR_eDP && + dpcd[DP_DPCD_REV] >= DP_DPCD_REV_11 && + dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT && + !drm_dp_has_quirk(desc, DP_DPCD_QUIRK_NO_SINK_COUNT); +} +EXPORT_SYMBOL(drm_dp_read_sink_count_cap); + +/** + * drm_dp_read_sink_count() - Retrieve the sink count for a given sink + * @aux: The DP AUX channel to use + * + * See also: drm_dp_read_sink_count_cap() + * + * Returns: The current sink count reported by @aux, or a negative error code + * otherwise. + */ +int drm_dp_read_sink_count(struct drm_dp_aux *aux) +{ + u8 count; + int ret; + + ret = drm_dp_dpcd_readb(aux, DP_SINK_COUNT, &count); + if (ret < 0) + return ret; + if (ret != 1) + return -EIO; + + return DP_GET_SINK_COUNT(count); +} +EXPORT_SYMBOL(drm_dp_read_sink_count); + +/* + * I2C-over-AUX implementation + */ + +static u32 drm_dp_i2c_functionality(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | + I2C_FUNC_SMBUS_READ_BLOCK_DATA | + I2C_FUNC_SMBUS_BLOCK_PROC_CALL | + I2C_FUNC_10BIT_ADDR; +} + +static void drm_dp_i2c_msg_write_status_update(struct drm_dp_aux_msg *msg) +{ + /* + * In case of i2c defer or short i2c ack reply to a write, + * we need to switch to WRITE_STATUS_UPDATE to drain the + * rest of the message + */ + if ((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_I2C_WRITE) { + msg->request &= DP_AUX_I2C_MOT; + msg->request |= DP_AUX_I2C_WRITE_STATUS_UPDATE; + } +} + +#define AUX_PRECHARGE_LEN 10 /* 10 to 16 */ +#define AUX_SYNC_LEN (16 + 4) /* preamble + AUX_SYNC_END */ +#define AUX_STOP_LEN 4 +#define AUX_CMD_LEN 4 +#define AUX_ADDRESS_LEN 20 +#define AUX_REPLY_PAD_LEN 4 +#define AUX_LENGTH_LEN 8 + +/* + * Calculate the duration of the AUX request/reply in usec. Gives the + * "best" case estimate, ie. successful while as short as possible. + */ +static int drm_dp_aux_req_duration(const struct drm_dp_aux_msg *msg) +{ + int len = AUX_PRECHARGE_LEN + AUX_SYNC_LEN + AUX_STOP_LEN + + AUX_CMD_LEN + AUX_ADDRESS_LEN + AUX_LENGTH_LEN; + + if ((msg->request & DP_AUX_I2C_READ) == 0) + len += msg->size * 8; + + return len; +} + +static int drm_dp_aux_reply_duration(const struct drm_dp_aux_msg *msg) +{ + int len = AUX_PRECHARGE_LEN + AUX_SYNC_LEN + AUX_STOP_LEN + + AUX_CMD_LEN + AUX_REPLY_PAD_LEN; + + /* + * For read we expect what was asked. For writes there will + * be 0 or 1 data bytes. Assume 0 for the "best" case. + */ + if (msg->request & DP_AUX_I2C_READ) + len += msg->size * 8; + + return len; +} + +#define I2C_START_LEN 1 +#define I2C_STOP_LEN 1 +#define I2C_ADDR_LEN 9 /* ADDRESS + R/W + ACK/NACK */ +#define I2C_DATA_LEN 9 /* DATA + ACK/NACK */ + +/* + * Calculate the length of the i2c transfer in usec, assuming + * the i2c bus speed is as specified. Gives the the "worst" + * case estimate, ie. successful while as long as possible. + * Doesn't account the the "MOT" bit, and instead assumes each + * message includes a START, ADDRESS and STOP. Neither does it + * account for additional random variables such as clock stretching. + */ +static int drm_dp_i2c_msg_duration(const struct drm_dp_aux_msg *msg, + int i2c_speed_khz) +{ + /* AUX bitrate is 1MHz, i2c bitrate as specified */ + return DIV_ROUND_UP((I2C_START_LEN + I2C_ADDR_LEN + + msg->size * I2C_DATA_LEN + + I2C_STOP_LEN) * 1000, i2c_speed_khz); +} + +/* + * Determine how many retries should be attempted to successfully transfer + * the specified message, based on the estimated durations of the + * i2c and AUX transfers. + */ +static int drm_dp_i2c_retry_count(const struct drm_dp_aux_msg *msg, + int i2c_speed_khz) +{ + int aux_time_us = drm_dp_aux_req_duration(msg) + + drm_dp_aux_reply_duration(msg); + int i2c_time_us = drm_dp_i2c_msg_duration(msg, i2c_speed_khz); + + return DIV_ROUND_UP(i2c_time_us, aux_time_us + AUX_RETRY_INTERVAL); +} + +/* + * FIXME currently assumes 10 kHz as some real world devices seem + * to require it. We should query/set the speed via DPCD if supported. + */ +static int dp_aux_i2c_speed_khz __read_mostly = 10; +module_param_unsafe(dp_aux_i2c_speed_khz, int, 0644); +MODULE_PARM_DESC(dp_aux_i2c_speed_khz, + "Assumed speed of the i2c bus in kHz, (1-400, default 10)"); + +/* + * Transfer a single I2C-over-AUX message and handle various error conditions, + * retrying the transaction as appropriate. It is assumed that the + * &drm_dp_aux.transfer function does not modify anything in the msg other than the + * reply field. + * + * Returns bytes transferred on success, or a negative error code on failure. + */ +static int drm_dp_i2c_do_msg(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) +{ + unsigned int retry, defer_i2c; + int ret; + /* + * DP1.2 sections 2.7.7.1.5.6.1 and 2.7.7.1.6.6.1: A DP Source device + * is required to retry at least seven times upon receiving AUX_DEFER + * before giving up the AUX transaction. + * + * We also try to account for the i2c bus speed. + */ + int max_retries = max(7, drm_dp_i2c_retry_count(msg, dp_aux_i2c_speed_khz)); + + for (retry = 0, defer_i2c = 0; retry < (max_retries + defer_i2c); retry++) { + ret = aux->transfer(aux, msg); + if (ret < 0) { + if (ret == -EBUSY) + continue; + + /* + * While timeouts can be errors, they're usually normal + * behavior (for instance, when a driver tries to + * communicate with a non-existent DisplayPort device). + * Avoid spamming the kernel log with timeout errors. + */ + if (ret == -ETIMEDOUT) + drm_dbg_kms_ratelimited(aux->drm_dev, "%s: transaction timed out\n", + aux->name); + else + drm_dbg_kms(aux->drm_dev, "%s: transaction failed: %d\n", + aux->name, ret); + return ret; + } + + + switch (msg->reply & DP_AUX_NATIVE_REPLY_MASK) { + case DP_AUX_NATIVE_REPLY_ACK: + /* + * For I2C-over-AUX transactions this isn't enough, we + * need to check for the I2C ACK reply. + */ + break; + + case DP_AUX_NATIVE_REPLY_NACK: + drm_dbg_kms(aux->drm_dev, "%s: native nack (result=%d, size=%zu)\n", + aux->name, ret, msg->size); + return -EREMOTEIO; + + case DP_AUX_NATIVE_REPLY_DEFER: + drm_dbg_kms(aux->drm_dev, "%s: native defer\n", aux->name); + /* + * We could check for I2C bit rate capabilities and if + * available adjust this interval. We could also be + * more careful with DP-to-legacy adapters where a + * long legacy cable may force very low I2C bit rates. + * + * For now just defer for long enough to hopefully be + * safe for all use-cases. + */ + usleep_range(AUX_RETRY_INTERVAL, AUX_RETRY_INTERVAL + 100); + continue; + + default: + drm_err(aux->drm_dev, "%s: invalid native reply %#04x\n", + aux->name, msg->reply); + return -EREMOTEIO; + } + + switch (msg->reply & DP_AUX_I2C_REPLY_MASK) { + case DP_AUX_I2C_REPLY_ACK: + /* + * Both native ACK and I2C ACK replies received. We + * can assume the transfer was successful. + */ + if (ret != msg->size) + drm_dp_i2c_msg_write_status_update(msg); + return ret; + + case DP_AUX_I2C_REPLY_NACK: + drm_dbg_kms(aux->drm_dev, "%s: I2C nack (result=%d, size=%zu)\n", + aux->name, ret, msg->size); + aux->i2c_nack_count++; + return -EREMOTEIO; + + case DP_AUX_I2C_REPLY_DEFER: + drm_dbg_kms(aux->drm_dev, "%s: I2C defer\n", aux->name); + /* DP Compliance Test 4.2.2.5 Requirement: + * Must have at least 7 retries for I2C defers on the + * transaction to pass this test + */ + aux->i2c_defer_count++; + if (defer_i2c < 7) + defer_i2c++; + usleep_range(AUX_RETRY_INTERVAL, AUX_RETRY_INTERVAL + 100); + drm_dp_i2c_msg_write_status_update(msg); + + continue; + + default: + drm_err(aux->drm_dev, "%s: invalid I2C reply %#04x\n", + aux->name, msg->reply); + return -EREMOTEIO; + } + } + + drm_dbg_kms(aux->drm_dev, "%s: Too many retries, giving up\n", aux->name); + return -EREMOTEIO; +} + +static void drm_dp_i2c_msg_set_request(struct drm_dp_aux_msg *msg, + const struct i2c_msg *i2c_msg) +{ + msg->request = (i2c_msg->flags & I2C_M_RD) ? + DP_AUX_I2C_READ : DP_AUX_I2C_WRITE; + if (!(i2c_msg->flags & I2C_M_STOP)) + msg->request |= DP_AUX_I2C_MOT; +} + +/* + * Keep retrying drm_dp_i2c_do_msg until all data has been transferred. + * + * Returns an error code on failure, or a recommended transfer size on success. + */ +static int drm_dp_i2c_drain_msg(struct drm_dp_aux *aux, struct drm_dp_aux_msg *orig_msg) +{ + int err, ret = orig_msg->size; + struct drm_dp_aux_msg msg = *orig_msg; + + while (msg.size > 0) { + err = drm_dp_i2c_do_msg(aux, &msg); + if (err <= 0) + return err == 0 ? -EPROTO : err; + + if (err < msg.size && err < ret) { + drm_dbg_kms(aux->drm_dev, + "%s: Partial I2C reply: requested %zu bytes got %d bytes\n", + aux->name, msg.size, err); + ret = err; + } + + msg.size -= err; + msg.buffer += err; + } + + return ret; +} + +/* + * Bizlink designed DP->DVI-D Dual Link adapters require the I2C over AUX + * packets to be as large as possible. If not, the I2C transactions never + * succeed. Hence the default is maximum. + */ +static int dp_aux_i2c_transfer_size __read_mostly = DP_AUX_MAX_PAYLOAD_BYTES; +module_param_unsafe(dp_aux_i2c_transfer_size, int, 0644); +MODULE_PARM_DESC(dp_aux_i2c_transfer_size, + "Number of bytes to transfer in a single I2C over DP AUX CH message, (1-16, default 16)"); + +static int drm_dp_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, + int num) +{ + struct drm_dp_aux *aux = adapter->algo_data; + unsigned int i, j; + unsigned transfer_size; + struct drm_dp_aux_msg msg; + int err = 0; + + dp_aux_i2c_transfer_size = clamp(dp_aux_i2c_transfer_size, 1, DP_AUX_MAX_PAYLOAD_BYTES); + + memset(&msg, 0, sizeof(msg)); + + for (i = 0; i < num; i++) { + msg.address = msgs[i].addr; + drm_dp_i2c_msg_set_request(&msg, &msgs[i]); + /* Send a bare address packet to start the transaction. + * Zero sized messages specify an address only (bare + * address) transaction. + */ + msg.buffer = NULL; + msg.size = 0; + err = drm_dp_i2c_do_msg(aux, &msg); + + /* + * Reset msg.request in case in case it got + * changed into a WRITE_STATUS_UPDATE. + */ + drm_dp_i2c_msg_set_request(&msg, &msgs[i]); + + if (err < 0) + break; + /* We want each transaction to be as large as possible, but + * we'll go to smaller sizes if the hardware gives us a + * short reply. + */ + transfer_size = dp_aux_i2c_transfer_size; + for (j = 0; j < msgs[i].len; j += msg.size) { + msg.buffer = msgs[i].buf + j; + msg.size = min(transfer_size, msgs[i].len - j); + + err = drm_dp_i2c_drain_msg(aux, &msg); + + /* + * Reset msg.request in case in case it got + * changed into a WRITE_STATUS_UPDATE. + */ + drm_dp_i2c_msg_set_request(&msg, &msgs[i]); + + if (err < 0) + break; + transfer_size = err; + } + if (err < 0) + break; + } + if (err >= 0) + err = num; + /* Send a bare address packet to close out the transaction. + * Zero sized messages specify an address only (bare + * address) transaction. + */ + msg.request &= ~DP_AUX_I2C_MOT; + msg.buffer = NULL; + msg.size = 0; + (void)drm_dp_i2c_do_msg(aux, &msg); + + return err; +} + +static const struct i2c_algorithm drm_dp_i2c_algo = { + .functionality = drm_dp_i2c_functionality, + .master_xfer = drm_dp_i2c_xfer, +}; + +static struct drm_dp_aux *i2c_to_aux(struct i2c_adapter *i2c) +{ + return container_of(i2c, struct drm_dp_aux, ddc); +} + +static void lock_bus(struct i2c_adapter *i2c, unsigned int flags) +{ + mutex_lock(&i2c_to_aux(i2c)->hw_mutex); +} + +static int trylock_bus(struct i2c_adapter *i2c, unsigned int flags) +{ + return mutex_trylock(&i2c_to_aux(i2c)->hw_mutex); +} + +static void unlock_bus(struct i2c_adapter *i2c, unsigned int flags) +{ + mutex_unlock(&i2c_to_aux(i2c)->hw_mutex); +} + +static const struct i2c_lock_operations drm_dp_i2c_lock_ops = { + .lock_bus = lock_bus, + .trylock_bus = trylock_bus, + .unlock_bus = unlock_bus, +}; + +static int drm_dp_aux_get_crc(struct drm_dp_aux *aux, u8 *crc) +{ + u8 buf, count; + int ret; + + ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK, &buf); + if (ret < 0) + return ret; + + WARN_ON(!(buf & DP_TEST_SINK_START)); + + ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK_MISC, &buf); + if (ret < 0) + return ret; + + count = buf & DP_TEST_COUNT_MASK; + if (count == aux->crc_count) + return -EAGAIN; /* No CRC yet */ + + aux->crc_count = count; + + /* + * At DP_TEST_CRC_R_CR, there's 6 bytes containing CRC data, 2 bytes + * per component (RGB or CrYCb). + */ + ret = drm_dp_dpcd_read(aux, DP_TEST_CRC_R_CR, crc, 6); + if (ret < 0) + return ret; + + return 0; +} + +static void drm_dp_aux_crc_work(struct work_struct *work) +{ + struct drm_dp_aux *aux = container_of(work, struct drm_dp_aux, + crc_work); + struct drm_crtc *crtc; + u8 crc_bytes[6]; + uint32_t crcs[3]; + int ret; + + if (WARN_ON(!aux->crtc)) + return; + + crtc = aux->crtc; + while (crtc->crc.opened) { + drm_crtc_wait_one_vblank(crtc); + if (!crtc->crc.opened) + break; + + ret = drm_dp_aux_get_crc(aux, crc_bytes); + if (ret == -EAGAIN) { + usleep_range(1000, 2000); + ret = drm_dp_aux_get_crc(aux, crc_bytes); + } + + if (ret == -EAGAIN) { + drm_dbg_kms(aux->drm_dev, "%s: Get CRC failed after retrying: %d\n", + aux->name, ret); + continue; + } else if (ret) { + drm_dbg_kms(aux->drm_dev, "%s: Failed to get a CRC: %d\n", aux->name, ret); + continue; + } + + crcs[0] = crc_bytes[0] | crc_bytes[1] << 8; + crcs[1] = crc_bytes[2] | crc_bytes[3] << 8; + crcs[2] = crc_bytes[4] | crc_bytes[5] << 8; + drm_crtc_add_crc_entry(crtc, false, 0, crcs); + } +} + +/** + * drm_dp_remote_aux_init() - minimally initialise a remote aux channel + * @aux: DisplayPort AUX channel + * + * Used for remote aux channel in general. Merely initialize the crc work + * struct. + */ +void drm_dp_remote_aux_init(struct drm_dp_aux *aux) +{ + INIT_WORK(&aux->crc_work, drm_dp_aux_crc_work); +} +EXPORT_SYMBOL(drm_dp_remote_aux_init); + +/** + * drm_dp_aux_init() - minimally initialise an aux channel + * @aux: DisplayPort AUX channel + * + * If you need to use the drm_dp_aux's i2c adapter prior to registering it with + * the outside world, call drm_dp_aux_init() first. For drivers which are + * grandparents to their AUX adapters (e.g. the AUX adapter is parented by a + * &drm_connector), you must still call drm_dp_aux_register() once the connector + * has been registered to allow userspace access to the auxiliary DP channel. + * Likewise, for such drivers you should also assign &drm_dp_aux.drm_dev as + * early as possible so that the &drm_device that corresponds to the AUX adapter + * may be mentioned in debugging output from the DRM DP helpers. + * + * For devices which use a separate platform device for their AUX adapters, this + * may be called as early as required by the driver. + * + */ +void drm_dp_aux_init(struct drm_dp_aux *aux) +{ + mutex_init(&aux->hw_mutex); + mutex_init(&aux->cec.lock); + INIT_WORK(&aux->crc_work, drm_dp_aux_crc_work); + + aux->ddc.algo = &drm_dp_i2c_algo; + aux->ddc.algo_data = aux; + aux->ddc.retries = 3; + + aux->ddc.lock_ops = &drm_dp_i2c_lock_ops; +} +EXPORT_SYMBOL(drm_dp_aux_init); + +/** + * drm_dp_aux_register() - initialise and register aux channel + * @aux: DisplayPort AUX channel + * + * Automatically calls drm_dp_aux_init() if this hasn't been done yet. This + * should only be called once the parent of @aux, &drm_dp_aux.dev, is + * initialized. For devices which are grandparents of their AUX channels, + * &drm_dp_aux.dev will typically be the &drm_connector &device which + * corresponds to @aux. For these devices, it's advised to call + * drm_dp_aux_register() in &drm_connector_funcs.late_register, and likewise to + * call drm_dp_aux_unregister() in &drm_connector_funcs.early_unregister. + * Functions which don't follow this will likely Oops when + * %CONFIG_DRM_DP_AUX_CHARDEV is enabled. + * + * For devices where the AUX channel is a device that exists independently of + * the &drm_device that uses it, such as SoCs and bridge devices, it is + * recommended to call drm_dp_aux_register() after a &drm_device has been + * assigned to &drm_dp_aux.drm_dev, and likewise to call + * drm_dp_aux_unregister() once the &drm_device should no longer be associated + * with the AUX channel (e.g. on bridge detach). + * + * Drivers which need to use the aux channel before either of the two points + * mentioned above need to call drm_dp_aux_init() in order to use the AUX + * channel before registration. + * + * Returns 0 on success or a negative error code on failure. + */ +int drm_dp_aux_register(struct drm_dp_aux *aux) +{ + int ret; + + WARN_ON_ONCE(!aux->drm_dev); + + if (!aux->ddc.algo) + drm_dp_aux_init(aux); + + aux->ddc.class = I2C_CLASS_DDC; + aux->ddc.owner = THIS_MODULE; + aux->ddc.dev.parent = aux->dev; + + strlcpy(aux->ddc.name, aux->name ? aux->name : dev_name(aux->dev), + sizeof(aux->ddc.name)); + + ret = drm_dp_aux_register_devnode(aux); + if (ret) + return ret; + + ret = i2c_add_adapter(&aux->ddc); + if (ret) { + drm_dp_aux_unregister_devnode(aux); + return ret; + } + + return 0; +} +EXPORT_SYMBOL(drm_dp_aux_register); + +/** + * drm_dp_aux_unregister() - unregister an AUX adapter + * @aux: DisplayPort AUX channel + */ +void drm_dp_aux_unregister(struct drm_dp_aux *aux) +{ + drm_dp_aux_unregister_devnode(aux); + i2c_del_adapter(&aux->ddc); +} +EXPORT_SYMBOL(drm_dp_aux_unregister); + +#define PSR_SETUP_TIME(x) [DP_PSR_SETUP_TIME_ ## x >> DP_PSR_SETUP_TIME_SHIFT] = (x) + +/** + * drm_dp_psr_setup_time() - PSR setup in time usec + * @psr_cap: PSR capabilities from DPCD + * + * Returns: + * PSR setup time for the panel in microseconds, negative + * error code on failure. + */ +int drm_dp_psr_setup_time(const u8 psr_cap[EDP_PSR_RECEIVER_CAP_SIZE]) +{ + static const u16 psr_setup_time_us[] = { + PSR_SETUP_TIME(330), + PSR_SETUP_TIME(275), + PSR_SETUP_TIME(220), + PSR_SETUP_TIME(165), + PSR_SETUP_TIME(110), + PSR_SETUP_TIME(55), + PSR_SETUP_TIME(0), + }; + int i; + + i = (psr_cap[1] & DP_PSR_SETUP_TIME_MASK) >> DP_PSR_SETUP_TIME_SHIFT; + if (i >= ARRAY_SIZE(psr_setup_time_us)) + return -EINVAL; + + return psr_setup_time_us[i]; +} +EXPORT_SYMBOL(drm_dp_psr_setup_time); + +#undef PSR_SETUP_TIME + +/** + * drm_dp_start_crc() - start capture of frame CRCs + * @aux: DisplayPort AUX channel + * @crtc: CRTC displaying the frames whose CRCs are to be captured + * + * Returns 0 on success or a negative error code on failure. + */ +int drm_dp_start_crc(struct drm_dp_aux *aux, struct drm_crtc *crtc) +{ + u8 buf; + int ret; + + ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK, &buf); + if (ret < 0) + return ret; + + ret = drm_dp_dpcd_writeb(aux, DP_TEST_SINK, buf | DP_TEST_SINK_START); + if (ret < 0) + return ret; + + aux->crc_count = 0; + aux->crtc = crtc; + schedule_work(&aux->crc_work); + + return 0; +} +EXPORT_SYMBOL(drm_dp_start_crc); + +/** + * drm_dp_stop_crc() - stop capture of frame CRCs + * @aux: DisplayPort AUX channel + * + * Returns 0 on success or a negative error code on failure. + */ +int drm_dp_stop_crc(struct drm_dp_aux *aux) +{ + u8 buf; + int ret; + + ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK, &buf); + if (ret < 0) + return ret; + + ret = drm_dp_dpcd_writeb(aux, DP_TEST_SINK, buf & ~DP_TEST_SINK_START); + if (ret < 0) + return ret; + + flush_work(&aux->crc_work); + aux->crtc = NULL; + + return 0; +} +EXPORT_SYMBOL(drm_dp_stop_crc); + +struct dpcd_quirk { + u8 oui[3]; + u8 device_id[6]; + bool is_branch; + u32 quirks; +}; + +#define OUI(first, second, third) { (first), (second), (third) } +#define DEVICE_ID(first, second, third, fourth, fifth, sixth) \ + { (first), (second), (third), (fourth), (fifth), (sixth) } + +#define DEVICE_ID_ANY DEVICE_ID(0, 0, 0, 0, 0, 0) + +static const struct dpcd_quirk dpcd_quirk_list[] = { + /* Analogix 7737 needs reduced M and N at HBR2 link rates */ + { OUI(0x00, 0x22, 0xb9), DEVICE_ID_ANY, true, BIT(DP_DPCD_QUIRK_CONSTANT_N) }, + /* LG LP140WF6-SPM1 eDP panel */ + { OUI(0x00, 0x22, 0xb9), DEVICE_ID('s', 'i', 'v', 'a', 'r', 'T'), false, BIT(DP_DPCD_QUIRK_CONSTANT_N) }, + /* Apple panels need some additional handling to support PSR */ + { OUI(0x00, 0x10, 0xfa), DEVICE_ID_ANY, false, BIT(DP_DPCD_QUIRK_NO_PSR) }, + /* CH7511 seems to leave SINK_COUNT zeroed */ + { OUI(0x00, 0x00, 0x00), DEVICE_ID('C', 'H', '7', '5', '1', '1'), false, BIT(DP_DPCD_QUIRK_NO_SINK_COUNT) }, + /* Synaptics DP1.4 MST hubs can support DSC without virtual DPCD */ + { OUI(0x90, 0xCC, 0x24), DEVICE_ID_ANY, true, BIT(DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD) }, + /* Apple MacBookPro 2017 15 inch eDP Retina panel reports too low DP_MAX_LINK_RATE */ + { OUI(0x00, 0x10, 0xfa), DEVICE_ID(101, 68, 21, 101, 98, 97), false, BIT(DP_DPCD_QUIRK_CAN_DO_MAX_LINK_RATE_3_24_GBPS) }, +}; + +#undef OUI + +/* + * Get a bit mask of DPCD quirks for the sink/branch device identified by + * ident. The quirk data is shared but it's up to the drivers to act on the + * data. + * + * For now, only the OUI (first three bytes) is used, but this may be extended + * to device identification string and hardware/firmware revisions later. + */ +static u32 +drm_dp_get_quirks(const struct drm_dp_dpcd_ident *ident, bool is_branch) +{ + const struct dpcd_quirk *quirk; + u32 quirks = 0; + int i; + u8 any_device[] = DEVICE_ID_ANY; + + for (i = 0; i < ARRAY_SIZE(dpcd_quirk_list); i++) { + quirk = &dpcd_quirk_list[i]; + + if (quirk->is_branch != is_branch) + continue; + + if (memcmp(quirk->oui, ident->oui, sizeof(ident->oui)) != 0) + continue; + + if (memcmp(quirk->device_id, any_device, sizeof(any_device)) != 0 && + memcmp(quirk->device_id, ident->device_id, sizeof(ident->device_id)) != 0) + continue; + + quirks |= quirk->quirks; + } + + return quirks; +} + +#undef DEVICE_ID_ANY +#undef DEVICE_ID + +/** + * drm_dp_read_desc - read sink/branch descriptor from DPCD + * @aux: DisplayPort AUX channel + * @desc: Device descriptor to fill from DPCD + * @is_branch: true for branch devices, false for sink devices + * + * Read DPCD 0x400 (sink) or 0x500 (branch) into @desc. Also debug log the + * identification. + * + * Returns 0 on success or a negative error code on failure. + */ +int drm_dp_read_desc(struct drm_dp_aux *aux, struct drm_dp_desc *desc, + bool is_branch) +{ + struct drm_dp_dpcd_ident *ident = &desc->ident; + unsigned int offset = is_branch ? DP_BRANCH_OUI : DP_SINK_OUI; + int ret, dev_id_len; + + ret = drm_dp_dpcd_read(aux, offset, ident, sizeof(*ident)); + if (ret < 0) + return ret; + + desc->quirks = drm_dp_get_quirks(ident, is_branch); + + dev_id_len = strnlen(ident->device_id, sizeof(ident->device_id)); + + drm_dbg_kms(aux->drm_dev, + "%s: DP %s: OUI %*phD dev-ID %*pE HW-rev %d.%d SW-rev %d.%d quirks 0x%04x\n", + aux->name, is_branch ? "branch" : "sink", + (int)sizeof(ident->oui), ident->oui, dev_id_len, + ident->device_id, ident->hw_rev >> 4, ident->hw_rev & 0xf, + ident->sw_major_rev, ident->sw_minor_rev, desc->quirks); + + return 0; +} +EXPORT_SYMBOL(drm_dp_read_desc); + +/** + * drm_dp_dsc_sink_max_slice_count() - Get the max slice count + * supported by the DSC sink. + * @dsc_dpcd: DSC capabilities from DPCD + * @is_edp: true if its eDP, false for DP + * + * Read the slice capabilities DPCD register from DSC sink to get + * the maximum slice count supported. This is used to populate + * the DSC parameters in the &struct drm_dsc_config by the driver. + * Driver creates an infoframe using these parameters to populate + * &struct drm_dsc_pps_infoframe. These are sent to the sink using DSC + * infoframe using the helper function drm_dsc_pps_infoframe_pack() + * + * Returns: + * Maximum slice count supported by DSC sink or 0 its invalid + */ +u8 drm_dp_dsc_sink_max_slice_count(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE], + bool is_edp) +{ + u8 slice_cap1 = dsc_dpcd[DP_DSC_SLICE_CAP_1 - DP_DSC_SUPPORT]; + + if (is_edp) { + /* For eDP, register DSC_SLICE_CAPABILITIES_1 gives slice count */ + if (slice_cap1 & DP_DSC_4_PER_DP_DSC_SINK) + return 4; + if (slice_cap1 & DP_DSC_2_PER_DP_DSC_SINK) + return 2; + if (slice_cap1 & DP_DSC_1_PER_DP_DSC_SINK) + return 1; + } else { + /* For DP, use values from DSC_SLICE_CAP_1 and DSC_SLICE_CAP2 */ + u8 slice_cap2 = dsc_dpcd[DP_DSC_SLICE_CAP_2 - DP_DSC_SUPPORT]; + + if (slice_cap2 & DP_DSC_24_PER_DP_DSC_SINK) + return 24; + if (slice_cap2 & DP_DSC_20_PER_DP_DSC_SINK) + return 20; + if (slice_cap2 & DP_DSC_16_PER_DP_DSC_SINK) + return 16; + if (slice_cap1 & DP_DSC_12_PER_DP_DSC_SINK) + return 12; + if (slice_cap1 & DP_DSC_10_PER_DP_DSC_SINK) + return 10; + if (slice_cap1 & DP_DSC_8_PER_DP_DSC_SINK) + return 8; + if (slice_cap1 & DP_DSC_6_PER_DP_DSC_SINK) + return 6; + if (slice_cap1 & DP_DSC_4_PER_DP_DSC_SINK) + return 4; + if (slice_cap1 & DP_DSC_2_PER_DP_DSC_SINK) + return 2; + if (slice_cap1 & DP_DSC_1_PER_DP_DSC_SINK) + return 1; + } + + return 0; +} +EXPORT_SYMBOL(drm_dp_dsc_sink_max_slice_count); + +/** + * drm_dp_dsc_sink_line_buf_depth() - Get the line buffer depth in bits + * @dsc_dpcd: DSC capabilities from DPCD + * + * Read the DSC DPCD register to parse the line buffer depth in bits which is + * number of bits of precision within the decoder line buffer supported by + * the DSC sink. This is used to populate the DSC parameters in the + * &struct drm_dsc_config by the driver. + * Driver creates an infoframe using these parameters to populate + * &struct drm_dsc_pps_infoframe. These are sent to the sink using DSC + * infoframe using the helper function drm_dsc_pps_infoframe_pack() + * + * Returns: + * Line buffer depth supported by DSC panel or 0 its invalid + */ +u8 drm_dp_dsc_sink_line_buf_depth(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE]) +{ + u8 line_buf_depth = dsc_dpcd[DP_DSC_LINE_BUF_BIT_DEPTH - DP_DSC_SUPPORT]; + + switch (line_buf_depth & DP_DSC_LINE_BUF_BIT_DEPTH_MASK) { + case DP_DSC_LINE_BUF_BIT_DEPTH_9: + return 9; + case DP_DSC_LINE_BUF_BIT_DEPTH_10: + return 10; + case DP_DSC_LINE_BUF_BIT_DEPTH_11: + return 11; + case DP_DSC_LINE_BUF_BIT_DEPTH_12: + return 12; + case DP_DSC_LINE_BUF_BIT_DEPTH_13: + return 13; + case DP_DSC_LINE_BUF_BIT_DEPTH_14: + return 14; + case DP_DSC_LINE_BUF_BIT_DEPTH_15: + return 15; + case DP_DSC_LINE_BUF_BIT_DEPTH_16: + return 16; + case DP_DSC_LINE_BUF_BIT_DEPTH_8: + return 8; + } + + return 0; +} +EXPORT_SYMBOL(drm_dp_dsc_sink_line_buf_depth); + +/** + * drm_dp_dsc_sink_supported_input_bpcs() - Get all the input bits per component + * values supported by the DSC sink. + * @dsc_dpcd: DSC capabilities from DPCD + * @dsc_bpc: An array to be filled by this helper with supported + * input bpcs. + * + * Read the DSC DPCD from the sink device to parse the supported bits per + * component values. This is used to populate the DSC parameters + * in the &struct drm_dsc_config by the driver. + * Driver creates an infoframe using these parameters to populate + * &struct drm_dsc_pps_infoframe. These are sent to the sink using DSC + * infoframe using the helper function drm_dsc_pps_infoframe_pack() + * + * Returns: + * Number of input BPC values parsed from the DPCD + */ +int drm_dp_dsc_sink_supported_input_bpcs(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE], + u8 dsc_bpc[3]) +{ + int num_bpc = 0; + u8 color_depth = dsc_dpcd[DP_DSC_DEC_COLOR_DEPTH_CAP - DP_DSC_SUPPORT]; + + if (color_depth & DP_DSC_12_BPC) + dsc_bpc[num_bpc++] = 12; + if (color_depth & DP_DSC_10_BPC) + dsc_bpc[num_bpc++] = 10; + if (color_depth & DP_DSC_8_BPC) + dsc_bpc[num_bpc++] = 8; + + return num_bpc; +} +EXPORT_SYMBOL(drm_dp_dsc_sink_supported_input_bpcs); + +/** + * drm_dp_read_lttpr_common_caps - read the LTTPR common capabilities + * @aux: DisplayPort AUX channel + * @caps: buffer to return the capability info in + * + * Read capabilities common to all LTTPRs. + * + * Returns 0 on success or a negative error code on failure. + */ +int drm_dp_read_lttpr_common_caps(struct drm_dp_aux *aux, + u8 caps[DP_LTTPR_COMMON_CAP_SIZE]) +{ + int ret; + + ret = drm_dp_dpcd_read(aux, + DP_LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV, + caps, DP_LTTPR_COMMON_CAP_SIZE); + if (ret < 0) + return ret; + + WARN_ON(ret != DP_LTTPR_COMMON_CAP_SIZE); + + return 0; +} +EXPORT_SYMBOL(drm_dp_read_lttpr_common_caps); + +/** + * drm_dp_read_lttpr_phy_caps - read the capabilities for a given LTTPR PHY + * @aux: DisplayPort AUX channel + * @dp_phy: LTTPR PHY to read the capabilities for + * @caps: buffer to return the capability info in + * + * Read the capabilities for the given LTTPR PHY. + * + * Returns 0 on success or a negative error code on failure. + */ +int drm_dp_read_lttpr_phy_caps(struct drm_dp_aux *aux, + enum drm_dp_phy dp_phy, + u8 caps[DP_LTTPR_PHY_CAP_SIZE]) +{ + int ret; + + ret = drm_dp_dpcd_read(aux, + DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER(dp_phy), + caps, DP_LTTPR_PHY_CAP_SIZE); + if (ret < 0) + return ret; + + WARN_ON(ret != DP_LTTPR_PHY_CAP_SIZE); + + return 0; +} +EXPORT_SYMBOL(drm_dp_read_lttpr_phy_caps); + +static u8 dp_lttpr_common_cap(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE], int r) +{ + return caps[r - DP_LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV]; +} + +/** + * drm_dp_lttpr_count - get the number of detected LTTPRs + * @caps: LTTPR common capabilities + * + * Get the number of detected LTTPRs from the LTTPR common capabilities info. + * + * Returns: + * -ERANGE if more than supported number (8) of LTTPRs are detected + * -EINVAL if the DP_PHY_REPEATER_CNT register contains an invalid value + * otherwise the number of detected LTTPRs + */ +int drm_dp_lttpr_count(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE]) +{ + u8 count = dp_lttpr_common_cap(caps, DP_PHY_REPEATER_CNT); + + switch (hweight8(count)) { + case 0: + return 0; + case 1: + return 8 - ilog2(count); + case 8: + return -ERANGE; + default: + return -EINVAL; + } +} +EXPORT_SYMBOL(drm_dp_lttpr_count); + +/** + * drm_dp_lttpr_max_link_rate - get the maximum link rate supported by all LTTPRs + * @caps: LTTPR common capabilities + * + * Returns the maximum link rate supported by all detected LTTPRs. + */ +int drm_dp_lttpr_max_link_rate(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE]) +{ + u8 rate = dp_lttpr_common_cap(caps, DP_MAX_LINK_RATE_PHY_REPEATER); + + return drm_dp_bw_code_to_link_rate(rate); +} +EXPORT_SYMBOL(drm_dp_lttpr_max_link_rate); + +/** + * drm_dp_lttpr_max_lane_count - get the maximum lane count supported by all LTTPRs + * @caps: LTTPR common capabilities + * + * Returns the maximum lane count supported by all detected LTTPRs. + */ +int drm_dp_lttpr_max_lane_count(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE]) +{ + u8 max_lanes = dp_lttpr_common_cap(caps, DP_MAX_LANE_COUNT_PHY_REPEATER); + + return max_lanes & DP_MAX_LANE_COUNT_MASK; +} +EXPORT_SYMBOL(drm_dp_lttpr_max_lane_count); + +/** + * drm_dp_lttpr_voltage_swing_level_3_supported - check for LTTPR vswing3 support + * @caps: LTTPR PHY capabilities + * + * Returns true if the @caps for an LTTPR TX PHY indicate support for + * voltage swing level 3. + */ +bool +drm_dp_lttpr_voltage_swing_level_3_supported(const u8 caps[DP_LTTPR_PHY_CAP_SIZE]) +{ + u8 txcap = dp_lttpr_phy_cap(caps, DP_TRANSMITTER_CAPABILITY_PHY_REPEATER1); + + return txcap & DP_VOLTAGE_SWING_LEVEL_3_SUPPORTED; +} +EXPORT_SYMBOL(drm_dp_lttpr_voltage_swing_level_3_supported); + +/** + * drm_dp_lttpr_pre_emphasis_level_3_supported - check for LTTPR preemph3 support + * @caps: LTTPR PHY capabilities + * + * Returns true if the @caps for an LTTPR TX PHY indicate support for + * pre-emphasis level 3. + */ +bool +drm_dp_lttpr_pre_emphasis_level_3_supported(const u8 caps[DP_LTTPR_PHY_CAP_SIZE]) +{ + u8 txcap = dp_lttpr_phy_cap(caps, DP_TRANSMITTER_CAPABILITY_PHY_REPEATER1); + + return txcap & DP_PRE_EMPHASIS_LEVEL_3_SUPPORTED; +} +EXPORT_SYMBOL(drm_dp_lttpr_pre_emphasis_level_3_supported); + +/** + * drm_dp_get_phy_test_pattern() - get the requested pattern from the sink. + * @aux: DisplayPort AUX channel + * @data: DP phy compliance test parameters. + * + * Returns 0 on success or a negative error code on failure. + */ +int drm_dp_get_phy_test_pattern(struct drm_dp_aux *aux, + struct drm_dp_phy_test_params *data) +{ + int err; + u8 rate, lanes; + + err = drm_dp_dpcd_readb(aux, DP_TEST_LINK_RATE, &rate); + if (err < 0) + return err; + data->link_rate = drm_dp_bw_code_to_link_rate(rate); + + err = drm_dp_dpcd_readb(aux, DP_TEST_LANE_COUNT, &lanes); + if (err < 0) + return err; + data->num_lanes = lanes & DP_MAX_LANE_COUNT_MASK; + + if (lanes & DP_ENHANCED_FRAME_CAP) + data->enhanced_frame_cap = true; + + err = drm_dp_dpcd_readb(aux, DP_PHY_TEST_PATTERN, &data->phy_pattern); + if (err < 0) + return err; + + switch (data->phy_pattern) { + case DP_PHY_TEST_PATTERN_80BIT_CUSTOM: + err = drm_dp_dpcd_read(aux, DP_TEST_80BIT_CUSTOM_PATTERN_7_0, + &data->custom80, sizeof(data->custom80)); + if (err < 0) + return err; + + break; + case DP_PHY_TEST_PATTERN_CP2520: + err = drm_dp_dpcd_read(aux, DP_TEST_HBR2_SCRAMBLER_RESET, + &data->hbr2_reset, + sizeof(data->hbr2_reset)); + if (err < 0) + return err; + } + + return 0; +} +EXPORT_SYMBOL(drm_dp_get_phy_test_pattern); + +/** + * drm_dp_set_phy_test_pattern() - set the pattern to the sink. + * @aux: DisplayPort AUX channel + * @data: DP phy compliance test parameters. + * @dp_rev: DP revision to use for compliance testing + * + * Returns 0 on success or a negative error code on failure. + */ +int drm_dp_set_phy_test_pattern(struct drm_dp_aux *aux, + struct drm_dp_phy_test_params *data, u8 dp_rev) +{ + int err, i; + u8 link_config[2]; + u8 test_pattern; + + link_config[0] = drm_dp_link_rate_to_bw_code(data->link_rate); + link_config[1] = data->num_lanes; + if (data->enhanced_frame_cap) + link_config[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; + err = drm_dp_dpcd_write(aux, DP_LINK_BW_SET, link_config, 2); + if (err < 0) + return err; + + test_pattern = data->phy_pattern; + if (dp_rev < 0x12) { + test_pattern = (test_pattern << 2) & + DP_LINK_QUAL_PATTERN_11_MASK; + err = drm_dp_dpcd_writeb(aux, DP_TRAINING_PATTERN_SET, + test_pattern); + if (err < 0) + return err; + } else { + for (i = 0; i < data->num_lanes; i++) { + err = drm_dp_dpcd_writeb(aux, + DP_LINK_QUAL_LANE0_SET + i, + test_pattern); + if (err < 0) + return err; + } + } + + return 0; +} +EXPORT_SYMBOL(drm_dp_set_phy_test_pattern); + +static const char *dp_pixelformat_get_name(enum dp_pixelformat pixelformat) +{ + if (pixelformat < 0 || pixelformat > DP_PIXELFORMAT_RESERVED) + return "Invalid"; + + switch (pixelformat) { + case DP_PIXELFORMAT_RGB: + return "RGB"; + case DP_PIXELFORMAT_YUV444: + return "YUV444"; + case DP_PIXELFORMAT_YUV422: + return "YUV422"; + case DP_PIXELFORMAT_YUV420: + return "YUV420"; + case DP_PIXELFORMAT_Y_ONLY: + return "Y_ONLY"; + case DP_PIXELFORMAT_RAW: + return "RAW"; + default: + return "Reserved"; + } +} + +static const char *dp_colorimetry_get_name(enum dp_pixelformat pixelformat, + enum dp_colorimetry colorimetry) +{ + if (pixelformat < 0 || pixelformat > DP_PIXELFORMAT_RESERVED) + return "Invalid"; + + switch (colorimetry) { + case DP_COLORIMETRY_DEFAULT: + switch (pixelformat) { + case DP_PIXELFORMAT_RGB: + return "sRGB"; + case DP_PIXELFORMAT_YUV444: + case DP_PIXELFORMAT_YUV422: + case DP_PIXELFORMAT_YUV420: + return "BT.601"; + case DP_PIXELFORMAT_Y_ONLY: + return "DICOM PS3.14"; + case DP_PIXELFORMAT_RAW: + return "Custom Color Profile"; + default: + return "Reserved"; + } + case DP_COLORIMETRY_RGB_WIDE_FIXED: /* and DP_COLORIMETRY_BT709_YCC */ + switch (pixelformat) { + case DP_PIXELFORMAT_RGB: + return "Wide Fixed"; + case DP_PIXELFORMAT_YUV444: + case DP_PIXELFORMAT_YUV422: + case DP_PIXELFORMAT_YUV420: + return "BT.709"; + default: + return "Reserved"; + } + case DP_COLORIMETRY_RGB_WIDE_FLOAT: /* and DP_COLORIMETRY_XVYCC_601 */ + switch (pixelformat) { + case DP_PIXELFORMAT_RGB: + return "Wide Float"; + case DP_PIXELFORMAT_YUV444: + case DP_PIXELFORMAT_YUV422: + case DP_PIXELFORMAT_YUV420: + return "xvYCC 601"; + default: + return "Reserved"; + } + case DP_COLORIMETRY_OPRGB: /* and DP_COLORIMETRY_XVYCC_709 */ + switch (pixelformat) { + case DP_PIXELFORMAT_RGB: + return "OpRGB"; + case DP_PIXELFORMAT_YUV444: + case DP_PIXELFORMAT_YUV422: + case DP_PIXELFORMAT_YUV420: + return "xvYCC 709"; + default: + return "Reserved"; + } + case DP_COLORIMETRY_DCI_P3_RGB: /* and DP_COLORIMETRY_SYCC_601 */ + switch (pixelformat) { + case DP_PIXELFORMAT_RGB: + return "DCI-P3"; + case DP_PIXELFORMAT_YUV444: + case DP_PIXELFORMAT_YUV422: + case DP_PIXELFORMAT_YUV420: + return "sYCC 601"; + default: + return "Reserved"; + } + case DP_COLORIMETRY_RGB_CUSTOM: /* and DP_COLORIMETRY_OPYCC_601 */ + switch (pixelformat) { + case DP_PIXELFORMAT_RGB: + return "Custom Profile"; + case DP_PIXELFORMAT_YUV444: + case DP_PIXELFORMAT_YUV422: + case DP_PIXELFORMAT_YUV420: + return "OpYCC 601"; + default: + return "Reserved"; + } + case DP_COLORIMETRY_BT2020_RGB: /* and DP_COLORIMETRY_BT2020_CYCC */ + switch (pixelformat) { + case DP_PIXELFORMAT_RGB: + return "BT.2020 RGB"; + case DP_PIXELFORMAT_YUV444: + case DP_PIXELFORMAT_YUV422: + case DP_PIXELFORMAT_YUV420: + return "BT.2020 CYCC"; + default: + return "Reserved"; + } + case DP_COLORIMETRY_BT2020_YCC: + switch (pixelformat) { + case DP_PIXELFORMAT_YUV444: + case DP_PIXELFORMAT_YUV422: + case DP_PIXELFORMAT_YUV420: + return "BT.2020 YCC"; + default: + return "Reserved"; + } + default: + return "Invalid"; + } +} + +static const char *dp_dynamic_range_get_name(enum dp_dynamic_range dynamic_range) +{ + switch (dynamic_range) { + case DP_DYNAMIC_RANGE_VESA: + return "VESA range"; + case DP_DYNAMIC_RANGE_CTA: + return "CTA range"; + default: + return "Invalid"; + } +} + +static const char *dp_content_type_get_name(enum dp_content_type content_type) +{ + switch (content_type) { + case DP_CONTENT_TYPE_NOT_DEFINED: + return "Not defined"; + case DP_CONTENT_TYPE_GRAPHICS: + return "Graphics"; + case DP_CONTENT_TYPE_PHOTO: + return "Photo"; + case DP_CONTENT_TYPE_VIDEO: + return "Video"; + case DP_CONTENT_TYPE_GAME: + return "Game"; + default: + return "Reserved"; + } +} + +void drm_dp_vsc_sdp_log(const char *level, struct device *dev, + const struct drm_dp_vsc_sdp *vsc) +{ +#define DP_SDP_LOG(fmt, ...) dev_printk(level, dev, fmt, ##__VA_ARGS__) + DP_SDP_LOG("DP SDP: %s, revision %u, length %u\n", "VSC", + vsc->revision, vsc->length); + DP_SDP_LOG(" pixelformat: %s\n", + dp_pixelformat_get_name(vsc->pixelformat)); + DP_SDP_LOG(" colorimetry: %s\n", + dp_colorimetry_get_name(vsc->pixelformat, vsc->colorimetry)); + DP_SDP_LOG(" bpc: %u\n", vsc->bpc); + DP_SDP_LOG(" dynamic range: %s\n", + dp_dynamic_range_get_name(vsc->dynamic_range)); + DP_SDP_LOG(" content type: %s\n", + dp_content_type_get_name(vsc->content_type)); +#undef DP_SDP_LOG +} +EXPORT_SYMBOL(drm_dp_vsc_sdp_log); + +/** + * drm_dp_get_pcon_max_frl_bw() - maximum frl supported by PCON + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + * + * Returns maximum frl bandwidth supported by PCON in GBPS, + * returns 0 if not supported. + */ +int drm_dp_get_pcon_max_frl_bw(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4]) +{ + int bw; + u8 buf; + + buf = port_cap[2]; + bw = buf & DP_PCON_MAX_FRL_BW; + + switch (bw) { + case DP_PCON_MAX_9GBPS: + return 9; + case DP_PCON_MAX_18GBPS: + return 18; + case DP_PCON_MAX_24GBPS: + return 24; + case DP_PCON_MAX_32GBPS: + return 32; + case DP_PCON_MAX_40GBPS: + return 40; + case DP_PCON_MAX_48GBPS: + return 48; + case DP_PCON_MAX_0GBPS: + default: + return 0; + } + + return 0; +} +EXPORT_SYMBOL(drm_dp_get_pcon_max_frl_bw); + +/** + * drm_dp_pcon_frl_prepare() - Prepare PCON for FRL. + * @aux: DisplayPort AUX channel + * @enable_frl_ready_hpd: Configure DP_PCON_ENABLE_HPD_READY. + * + * Returns 0 if success, else returns negative error code. + */ +int drm_dp_pcon_frl_prepare(struct drm_dp_aux *aux, bool enable_frl_ready_hpd) +{ + int ret; + u8 buf = DP_PCON_ENABLE_SOURCE_CTL_MODE | + DP_PCON_ENABLE_LINK_FRL_MODE; + + if (enable_frl_ready_hpd) + buf |= DP_PCON_ENABLE_HPD_READY; + + ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_1, buf); + + return ret; +} +EXPORT_SYMBOL(drm_dp_pcon_frl_prepare); + +/** + * drm_dp_pcon_is_frl_ready() - Is PCON ready for FRL + * @aux: DisplayPort AUX channel + * + * Returns true if success, else returns false. + */ +bool drm_dp_pcon_is_frl_ready(struct drm_dp_aux *aux) +{ + int ret; + u8 buf; + + ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_TX_LINK_STATUS, &buf); + if (ret < 0) + return false; + + if (buf & DP_PCON_FRL_READY) + return true; + + return false; +} +EXPORT_SYMBOL(drm_dp_pcon_is_frl_ready); + +/** + * drm_dp_pcon_frl_configure_1() - Set HDMI LINK Configuration-Step1 + * @aux: DisplayPort AUX channel + * @max_frl_gbps: maximum frl bw to be configured between PCON and HDMI sink + * @frl_mode: FRL Training mode, it can be either Concurrent or Sequential. + * In Concurrent Mode, the FRL link bring up can be done along with + * DP Link training. In Sequential mode, the FRL link bring up is done prior to + * the DP Link training. + * + * Returns 0 if success, else returns negative error code. + */ + +int drm_dp_pcon_frl_configure_1(struct drm_dp_aux *aux, int max_frl_gbps, + u8 frl_mode) +{ + int ret; + u8 buf; + + ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_LINK_CONFIG_1, &buf); + if (ret < 0) + return ret; + + if (frl_mode == DP_PCON_ENABLE_CONCURRENT_LINK) + buf |= DP_PCON_ENABLE_CONCURRENT_LINK; + else + buf &= ~DP_PCON_ENABLE_CONCURRENT_LINK; + + switch (max_frl_gbps) { + case 9: + buf |= DP_PCON_ENABLE_MAX_BW_9GBPS; + break; + case 18: + buf |= DP_PCON_ENABLE_MAX_BW_18GBPS; + break; + case 24: + buf |= DP_PCON_ENABLE_MAX_BW_24GBPS; + break; + case 32: + buf |= DP_PCON_ENABLE_MAX_BW_32GBPS; + break; + case 40: + buf |= DP_PCON_ENABLE_MAX_BW_40GBPS; + break; + case 48: + buf |= DP_PCON_ENABLE_MAX_BW_48GBPS; + break; + case 0: + buf |= DP_PCON_ENABLE_MAX_BW_0GBPS; + break; + default: + return -EINVAL; + } + + ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_1, buf); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(drm_dp_pcon_frl_configure_1); + +/** + * drm_dp_pcon_frl_configure_2() - Set HDMI Link configuration Step-2 + * @aux: DisplayPort AUX channel + * @max_frl_mask : Max FRL BW to be tried by the PCON with HDMI Sink + * @frl_type : FRL training type, can be Extended, or Normal. + * In Normal FRL training, the PCON tries each frl bw from the max_frl_mask + * starting from min, and stops when link training is successful. In Extended + * FRL training, all frl bw selected in the mask are trained by the PCON. + * + * Returns 0 if success, else returns negative error code. + */ +int drm_dp_pcon_frl_configure_2(struct drm_dp_aux *aux, int max_frl_mask, + u8 frl_type) +{ + int ret; + u8 buf = max_frl_mask; + + if (frl_type == DP_PCON_FRL_LINK_TRAIN_EXTENDED) + buf |= DP_PCON_FRL_LINK_TRAIN_EXTENDED; + else + buf &= ~DP_PCON_FRL_LINK_TRAIN_EXTENDED; + + ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_2, buf); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(drm_dp_pcon_frl_configure_2); + +/** + * drm_dp_pcon_reset_frl_config() - Re-Set HDMI Link configuration. + * @aux: DisplayPort AUX channel + * + * Returns 0 if success, else returns negative error code. + */ +int drm_dp_pcon_reset_frl_config(struct drm_dp_aux *aux) +{ + int ret; + + ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_1, 0x0); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(drm_dp_pcon_reset_frl_config); + +/** + * drm_dp_pcon_frl_enable() - Enable HDMI link through FRL + * @aux: DisplayPort AUX channel + * + * Returns 0 if success, else returns negative error code. + */ +int drm_dp_pcon_frl_enable(struct drm_dp_aux *aux) +{ + int ret; + u8 buf = 0; + + ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_LINK_CONFIG_1, &buf); + if (ret < 0) + return ret; + if (!(buf & DP_PCON_ENABLE_SOURCE_CTL_MODE)) { + drm_dbg_kms(aux->drm_dev, "%s: PCON in Autonomous mode, can't enable FRL\n", + aux->name); + return -EINVAL; + } + buf |= DP_PCON_ENABLE_HDMI_LINK; + ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_1, buf); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(drm_dp_pcon_frl_enable); + +/** + * drm_dp_pcon_hdmi_link_active() - check if the PCON HDMI LINK status is active. + * @aux: DisplayPort AUX channel + * + * Returns true if link is active else returns false. + */ +bool drm_dp_pcon_hdmi_link_active(struct drm_dp_aux *aux) +{ + u8 buf; + int ret; + + ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_TX_LINK_STATUS, &buf); + if (ret < 0) + return false; + + return buf & DP_PCON_HDMI_TX_LINK_ACTIVE; +} +EXPORT_SYMBOL(drm_dp_pcon_hdmi_link_active); + +/** + * drm_dp_pcon_hdmi_link_mode() - get the PCON HDMI LINK MODE + * @aux: DisplayPort AUX channel + * @frl_trained_mask: pointer to store bitmask of the trained bw configuration. + * Valid only if the MODE returned is FRL. For Normal Link training mode + * only 1 of the bits will be set, but in case of Extended mode, more than + * one bits can be set. + * + * Returns the link mode : TMDS or FRL on success, else returns negative error + * code. + */ +int drm_dp_pcon_hdmi_link_mode(struct drm_dp_aux *aux, u8 *frl_trained_mask) +{ + u8 buf; + int mode; + int ret; + + ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_POST_FRL_STATUS, &buf); + if (ret < 0) + return ret; + + mode = buf & DP_PCON_HDMI_LINK_MODE; + + if (frl_trained_mask && DP_PCON_HDMI_MODE_FRL == mode) + *frl_trained_mask = (buf & DP_PCON_HDMI_FRL_TRAINED_BW) >> 1; + + return mode; +} +EXPORT_SYMBOL(drm_dp_pcon_hdmi_link_mode); + +/** + * drm_dp_pcon_hdmi_frl_link_error_count() - print the error count per lane + * during link failure between PCON and HDMI sink + * @aux: DisplayPort AUX channel + * @connector: DRM connector + * code. + **/ + +void drm_dp_pcon_hdmi_frl_link_error_count(struct drm_dp_aux *aux, + struct drm_connector *connector) +{ + u8 buf, error_count; + int i, num_error; + struct drm_hdmi_info *hdmi = &connector->display_info.hdmi; + + for (i = 0; i < hdmi->max_lanes; i++) { + if (drm_dp_dpcd_readb(aux, DP_PCON_HDMI_ERROR_STATUS_LN0 + i, &buf) < 0) + return; + + error_count = buf & DP_PCON_HDMI_ERROR_COUNT_MASK; + switch (error_count) { + case DP_PCON_HDMI_ERROR_COUNT_HUNDRED_PLUS: + num_error = 100; + break; + case DP_PCON_HDMI_ERROR_COUNT_TEN_PLUS: + num_error = 10; + break; + case DP_PCON_HDMI_ERROR_COUNT_THREE_PLUS: + num_error = 3; + break; + default: + num_error = 0; + } + + drm_err(aux->drm_dev, "%s: More than %d errors since the last read for lane %d", + aux->name, num_error, i); + } +} +EXPORT_SYMBOL(drm_dp_pcon_hdmi_frl_link_error_count); + +/* + * drm_dp_pcon_enc_is_dsc_1_2 - Does PCON Encoder supports DSC 1.2 + * @pcon_dsc_dpcd: DSC capabilities of the PCON DSC Encoder + * + * Returns true is PCON encoder is DSC 1.2 else returns false. + */ +bool drm_dp_pcon_enc_is_dsc_1_2(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]) +{ + u8 buf; + u8 major_v, minor_v; + + buf = pcon_dsc_dpcd[DP_PCON_DSC_VERSION - DP_PCON_DSC_ENCODER]; + major_v = (buf & DP_PCON_DSC_MAJOR_MASK) >> DP_PCON_DSC_MAJOR_SHIFT; + minor_v = (buf & DP_PCON_DSC_MINOR_MASK) >> DP_PCON_DSC_MINOR_SHIFT; + + if (major_v == 1 && minor_v == 2) + return true; + + return false; +} +EXPORT_SYMBOL(drm_dp_pcon_enc_is_dsc_1_2); + +/* + * drm_dp_pcon_dsc_max_slices - Get max slices supported by PCON DSC Encoder + * @pcon_dsc_dpcd: DSC capabilities of the PCON DSC Encoder + * + * Returns maximum no. of slices supported by the PCON DSC Encoder. + */ +int drm_dp_pcon_dsc_max_slices(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]) +{ + u8 slice_cap1, slice_cap2; + + slice_cap1 = pcon_dsc_dpcd[DP_PCON_DSC_SLICE_CAP_1 - DP_PCON_DSC_ENCODER]; + slice_cap2 = pcon_dsc_dpcd[DP_PCON_DSC_SLICE_CAP_2 - DP_PCON_DSC_ENCODER]; + + if (slice_cap2 & DP_PCON_DSC_24_PER_DSC_ENC) + return 24; + if (slice_cap2 & DP_PCON_DSC_20_PER_DSC_ENC) + return 20; + if (slice_cap2 & DP_PCON_DSC_16_PER_DSC_ENC) + return 16; + if (slice_cap1 & DP_PCON_DSC_12_PER_DSC_ENC) + return 12; + if (slice_cap1 & DP_PCON_DSC_10_PER_DSC_ENC) + return 10; + if (slice_cap1 & DP_PCON_DSC_8_PER_DSC_ENC) + return 8; + if (slice_cap1 & DP_PCON_DSC_6_PER_DSC_ENC) + return 6; + if (slice_cap1 & DP_PCON_DSC_4_PER_DSC_ENC) + return 4; + if (slice_cap1 & DP_PCON_DSC_2_PER_DSC_ENC) + return 2; + if (slice_cap1 & DP_PCON_DSC_1_PER_DSC_ENC) + return 1; + + return 0; +} +EXPORT_SYMBOL(drm_dp_pcon_dsc_max_slices); + +/* + * drm_dp_pcon_dsc_max_slice_width() - Get max slice width for Pcon DSC encoder + * @pcon_dsc_dpcd: DSC capabilities of the PCON DSC Encoder + * + * Returns maximum width of the slices in pixel width i.e. no. of pixels x 320. + */ +int drm_dp_pcon_dsc_max_slice_width(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]) +{ + u8 buf; + + buf = pcon_dsc_dpcd[DP_PCON_DSC_MAX_SLICE_WIDTH - DP_PCON_DSC_ENCODER]; + + return buf * DP_DSC_SLICE_WIDTH_MULTIPLIER; +} +EXPORT_SYMBOL(drm_dp_pcon_dsc_max_slice_width); + +/* + * drm_dp_pcon_dsc_bpp_incr() - Get bits per pixel increment for PCON DSC encoder + * @pcon_dsc_dpcd: DSC capabilities of the PCON DSC Encoder + * + * Returns the bpp precision supported by the PCON encoder. + */ +int drm_dp_pcon_dsc_bpp_incr(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]) +{ + u8 buf; + + buf = pcon_dsc_dpcd[DP_PCON_DSC_BPP_INCR - DP_PCON_DSC_ENCODER]; + + switch (buf & DP_PCON_DSC_BPP_INCR_MASK) { + case DP_PCON_DSC_ONE_16TH_BPP: + return 16; + case DP_PCON_DSC_ONE_8TH_BPP: + return 8; + case DP_PCON_DSC_ONE_4TH_BPP: + return 4; + case DP_PCON_DSC_ONE_HALF_BPP: + return 2; + case DP_PCON_DSC_ONE_BPP: + return 1; + } + + return 0; +} +EXPORT_SYMBOL(drm_dp_pcon_dsc_bpp_incr); + +static +int drm_dp_pcon_configure_dsc_enc(struct drm_dp_aux *aux, u8 pps_buf_config) +{ + u8 buf; + int ret; + + ret = drm_dp_dpcd_readb(aux, DP_PROTOCOL_CONVERTER_CONTROL_2, &buf); + if (ret < 0) + return ret; + + buf |= DP_PCON_ENABLE_DSC_ENCODER; + + if (pps_buf_config <= DP_PCON_ENC_PPS_OVERRIDE_EN_BUFFER) { + buf &= ~DP_PCON_ENCODER_PPS_OVERRIDE_MASK; + buf |= pps_buf_config << 2; + } + + ret = drm_dp_dpcd_writeb(aux, DP_PROTOCOL_CONVERTER_CONTROL_2, buf); + if (ret < 0) + return ret; + + return 0; +} + +/** + * drm_dp_pcon_pps_default() - Let PCON fill the default pps parameters + * for DSC1.2 between PCON & HDMI2.1 sink + * @aux: DisplayPort AUX channel + * + * Returns 0 on success, else returns negative error code. + */ +int drm_dp_pcon_pps_default(struct drm_dp_aux *aux) +{ + int ret; + + ret = drm_dp_pcon_configure_dsc_enc(aux, DP_PCON_ENC_PPS_OVERRIDE_DISABLED); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(drm_dp_pcon_pps_default); + +/** + * drm_dp_pcon_pps_override_buf() - Configure PPS encoder override buffer for + * HDMI sink + * @aux: DisplayPort AUX channel + * @pps_buf: 128 bytes to be written into PPS buffer for HDMI sink by PCON. + * + * Returns 0 on success, else returns negative error code. + */ +int drm_dp_pcon_pps_override_buf(struct drm_dp_aux *aux, u8 pps_buf[128]) +{ + int ret; + + ret = drm_dp_dpcd_write(aux, DP_PCON_HDMI_PPS_OVERRIDE_BASE, &pps_buf, 128); + if (ret < 0) + return ret; + + ret = drm_dp_pcon_configure_dsc_enc(aux, DP_PCON_ENC_PPS_OVERRIDE_EN_BUFFER); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(drm_dp_pcon_pps_override_buf); + +/* + * drm_dp_pcon_pps_override_param() - Write PPS parameters to DSC encoder + * override registers + * @aux: DisplayPort AUX channel + * @pps_param: 3 Parameters (2 Bytes each) : Slice Width, Slice Height, + * bits_per_pixel. + * + * Returns 0 on success, else returns negative error code. + */ +int drm_dp_pcon_pps_override_param(struct drm_dp_aux *aux, u8 pps_param[6]) +{ + int ret; + + ret = drm_dp_dpcd_write(aux, DP_PCON_HDMI_PPS_OVRD_SLICE_HEIGHT, &pps_param[0], 2); + if (ret < 0) + return ret; + ret = drm_dp_dpcd_write(aux, DP_PCON_HDMI_PPS_OVRD_SLICE_WIDTH, &pps_param[2], 2); + if (ret < 0) + return ret; + ret = drm_dp_dpcd_write(aux, DP_PCON_HDMI_PPS_OVRD_BPP, &pps_param[4], 2); + if (ret < 0) + return ret; + + ret = drm_dp_pcon_configure_dsc_enc(aux, DP_PCON_ENC_PPS_OVERRIDE_EN_BUFFER); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(drm_dp_pcon_pps_override_param); + +/* + * drm_dp_pcon_convert_rgb_to_ycbcr() - Configure the PCon to convert RGB to Ycbcr + * @aux: displayPort AUX channel + * @color_spc: Color-space/s for which conversion is to be enabled, 0 for disable. + * + * Returns 0 on success, else returns negative error code. + */ +int drm_dp_pcon_convert_rgb_to_ycbcr(struct drm_dp_aux *aux, u8 color_spc) +{ + int ret; + u8 buf; + + ret = drm_dp_dpcd_readb(aux, DP_PROTOCOL_CONVERTER_CONTROL_2, &buf); + if (ret < 0) + return ret; + + if (color_spc & DP_CONVERSION_RGB_YCBCR_MASK) + buf |= (color_spc & DP_CONVERSION_RGB_YCBCR_MASK); + else + buf &= ~DP_CONVERSION_RGB_YCBCR_MASK; + + ret = drm_dp_dpcd_writeb(aux, DP_PROTOCOL_CONVERTER_CONTROL_2, buf); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(drm_dp_pcon_convert_rgb_to_ycbcr); + +/** + * drm_edp_backlight_set_level() - Set the backlight level of an eDP panel via AUX + * @aux: The DP AUX channel to use + * @bl: Backlight capability info from drm_edp_backlight_init() + * @level: The brightness level to set + * + * Sets the brightness level of an eDP panel's backlight. Note that the panel's backlight must + * already have been enabled by the driver by calling drm_edp_backlight_enable(). + * + * Returns: %0 on success, negative error code on failure + */ +int drm_edp_backlight_set_level(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl, + u16 level) +{ + int ret; + u8 buf[2] = { 0 }; + + /* The panel uses the PWM for controlling brightness levels */ + if (!bl->aux_set) + return 0; + + if (bl->lsb_reg_used) { + buf[0] = (level & 0xff00) >> 8; + buf[1] = (level & 0x00ff); + } else { + buf[0] = level; + } + + ret = drm_dp_dpcd_write(aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB, buf, sizeof(buf)); + if (ret != sizeof(buf)) { + drm_err(aux->drm_dev, + "%s: Failed to write aux backlight level: %d\n", + aux->name, ret); + return ret < 0 ? ret : -EIO; + } + + return 0; +} +EXPORT_SYMBOL(drm_edp_backlight_set_level); + +static int +drm_edp_backlight_set_enable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl, + bool enable) +{ + int ret; + u8 buf; + + /* This panel uses the EDP_BL_PWR GPIO for enablement */ + if (!bl->aux_enable) + return 0; + + ret = drm_dp_dpcd_readb(aux, DP_EDP_DISPLAY_CONTROL_REGISTER, &buf); + if (ret != 1) { + drm_err(aux->drm_dev, "%s: Failed to read eDP display control register: %d\n", + aux->name, ret); + return ret < 0 ? ret : -EIO; + } + if (enable) + buf |= DP_EDP_BACKLIGHT_ENABLE; + else + buf &= ~DP_EDP_BACKLIGHT_ENABLE; + + ret = drm_dp_dpcd_writeb(aux, DP_EDP_DISPLAY_CONTROL_REGISTER, buf); + if (ret != 1) { + drm_err(aux->drm_dev, "%s: Failed to write eDP display control register: %d\n", + aux->name, ret); + return ret < 0 ? ret : -EIO; + } + + return 0; +} + +/** + * drm_edp_backlight_enable() - Enable an eDP panel's backlight using DPCD + * @aux: The DP AUX channel to use + * @bl: Backlight capability info from drm_edp_backlight_init() + * @level: The initial backlight level to set via AUX, if there is one + * + * This function handles enabling DPCD backlight controls on a panel over DPCD, while additionally + * restoring any important backlight state such as the given backlight level, the brightness byte + * count, backlight frequency, etc. + * + * Note that certain panels do not support being enabled or disabled via DPCD, but instead require + * that the driver handle enabling/disabling the panel through implementation-specific means using + * the EDP_BL_PWR GPIO. For such panels, &drm_edp_backlight_info.aux_enable will be set to %false, + * this function becomes a no-op, and the driver is expected to handle powering the panel on using + * the EDP_BL_PWR GPIO. + * + * Returns: %0 on success, negative error code on failure. + */ +int drm_edp_backlight_enable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl, + const u16 level) +{ + int ret; + u8 dpcd_buf; + + if (bl->aux_set) + dpcd_buf = DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD; + else + dpcd_buf = DP_EDP_BACKLIGHT_CONTROL_MODE_PWM; + + if (bl->pwmgen_bit_count) { + ret = drm_dp_dpcd_writeb(aux, DP_EDP_PWMGEN_BIT_COUNT, bl->pwmgen_bit_count); + if (ret != 1) + drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux pwmgen bit count: %d\n", + aux->name, ret); + } + + if (bl->pwm_freq_pre_divider) { + ret = drm_dp_dpcd_writeb(aux, DP_EDP_BACKLIGHT_FREQ_SET, bl->pwm_freq_pre_divider); + if (ret != 1) + drm_dbg_kms(aux->drm_dev, + "%s: Failed to write aux backlight frequency: %d\n", + aux->name, ret); + else + dpcd_buf |= DP_EDP_BACKLIGHT_FREQ_AUX_SET_ENABLE; + } + + ret = drm_dp_dpcd_writeb(aux, DP_EDP_BACKLIGHT_MODE_SET_REGISTER, dpcd_buf); + if (ret != 1) { + drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux backlight mode: %d\n", + aux->name, ret); + return ret < 0 ? ret : -EIO; + } + + ret = drm_edp_backlight_set_level(aux, bl, level); + if (ret < 0) + return ret; + ret = drm_edp_backlight_set_enable(aux, bl, true); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(drm_edp_backlight_enable); + +/** + * drm_edp_backlight_disable() - Disable an eDP backlight using DPCD, if supported + * @aux: The DP AUX channel to use + * @bl: Backlight capability info from drm_edp_backlight_init() + * + * This function handles disabling DPCD backlight controls on a panel over AUX. + * + * Note that certain panels do not support being enabled or disabled via DPCD, but instead require + * that the driver handle enabling/disabling the panel through implementation-specific means using + * the EDP_BL_PWR GPIO. For such panels, &drm_edp_backlight_info.aux_enable will be set to %false, + * this function becomes a no-op, and the driver is expected to handle powering the panel off using + * the EDP_BL_PWR GPIO. + * + * Returns: %0 on success or no-op, negative error code on failure. + */ +int drm_edp_backlight_disable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl) +{ + int ret; + + ret = drm_edp_backlight_set_enable(aux, bl, false); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(drm_edp_backlight_disable); + +static inline int +drm_edp_backlight_probe_max(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl, + u16 driver_pwm_freq_hz, const u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE]) +{ + int fxp, fxp_min, fxp_max, fxp_actual, f = 1; + int ret; + u8 pn, pn_min, pn_max; + + if (!bl->aux_set) + return 0; + + ret = drm_dp_dpcd_readb(aux, DP_EDP_PWMGEN_BIT_COUNT, &pn); + if (ret != 1) { + drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap: %d\n", + aux->name, ret); + return -ENODEV; + } + + pn &= DP_EDP_PWMGEN_BIT_COUNT_MASK; + bl->max = (1 << pn) - 1; + if (!driver_pwm_freq_hz) + return 0; + + /* + * Set PWM Frequency divider to match desired frequency provided by the driver. + * The PWM Frequency is calculated as 27Mhz / (F x P). + * - Where F = PWM Frequency Pre-Divider value programmed by field 7:0 of the + * EDP_BACKLIGHT_FREQ_SET register (DPCD Address 00728h) + * - Where P = 2^Pn, where Pn is the value programmed by field 4:0 of the + * EDP_PWMGEN_BIT_COUNT register (DPCD Address 00724h) + */ + + /* Find desired value of (F x P) + * Note that, if F x P is out of supported range, the maximum value or minimum value will + * applied automatically. So no need to check that. + */ + fxp = DIV_ROUND_CLOSEST(1000 * DP_EDP_BACKLIGHT_FREQ_BASE_KHZ, driver_pwm_freq_hz); + + /* Use highest possible value of Pn for more granularity of brightness adjustment while + * satisfying the conditions below. + * - Pn is in the range of Pn_min and Pn_max + * - F is in the range of 1 and 255 + * - FxP is within 25% of desired value. + * Note: 25% is arbitrary value and may need some tweak. + */ + ret = drm_dp_dpcd_readb(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, &pn_min); + if (ret != 1) { + drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap min: %d\n", + aux->name, ret); + return 0; + } + ret = drm_dp_dpcd_readb(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MAX, &pn_max); + if (ret != 1) { + drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap max: %d\n", + aux->name, ret); + return 0; + } + pn_min &= DP_EDP_PWMGEN_BIT_COUNT_MASK; + pn_max &= DP_EDP_PWMGEN_BIT_COUNT_MASK; + + /* Ensure frequency is within 25% of desired value */ + fxp_min = DIV_ROUND_CLOSEST(fxp * 3, 4); + fxp_max = DIV_ROUND_CLOSEST(fxp * 5, 4); + if (fxp_min < (1 << pn_min) || (255 << pn_max) < fxp_max) { + drm_dbg_kms(aux->drm_dev, + "%s: Driver defined backlight frequency (%d) out of range\n", + aux->name, driver_pwm_freq_hz); + return 0; + } + + for (pn = pn_max; pn >= pn_min; pn--) { + f = clamp(DIV_ROUND_CLOSEST(fxp, 1 << pn), 1, 255); + fxp_actual = f << pn; + if (fxp_min <= fxp_actual && fxp_actual <= fxp_max) + break; + } + + ret = drm_dp_dpcd_writeb(aux, DP_EDP_PWMGEN_BIT_COUNT, pn); + if (ret != 1) { + drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux pwmgen bit count: %d\n", + aux->name, ret); + return 0; + } + bl->pwmgen_bit_count = pn; + bl->max = (1 << pn) - 1; + + if (edp_dpcd[2] & DP_EDP_BACKLIGHT_FREQ_AUX_SET_CAP) { + bl->pwm_freq_pre_divider = f; + drm_dbg_kms(aux->drm_dev, "%s: Using backlight frequency from driver (%dHz)\n", + aux->name, driver_pwm_freq_hz); + } + + return 0; +} + +static inline int +drm_edp_backlight_probe_state(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl, + u8 *current_mode) +{ + int ret; + u8 buf[2]; + u8 mode_reg; + + ret = drm_dp_dpcd_readb(aux, DP_EDP_BACKLIGHT_MODE_SET_REGISTER, &mode_reg); + if (ret != 1) { + drm_dbg_kms(aux->drm_dev, "%s: Failed to read backlight mode: %d\n", + aux->name, ret); + return ret < 0 ? ret : -EIO; + } + + *current_mode = (mode_reg & DP_EDP_BACKLIGHT_CONTROL_MODE_MASK); + if (!bl->aux_set) + return 0; + + if (*current_mode == DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD) { + int size = 1 + bl->lsb_reg_used; + + ret = drm_dp_dpcd_read(aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB, buf, size); + if (ret != size) { + drm_dbg_kms(aux->drm_dev, "%s: Failed to read backlight level: %d\n", + aux->name, ret); + return ret < 0 ? ret : -EIO; + } + + if (bl->lsb_reg_used) + return (buf[0] << 8) | buf[1]; + else + return buf[0]; + } + + /* + * If we're not in DPCD control mode yet, the programmed brightness value is meaningless and + * the driver should assume max brightness + */ + return bl->max; +} + +/** + * drm_edp_backlight_init() - Probe a display panel's TCON using the standard VESA eDP backlight + * interface. + * @aux: The DP aux device to use for probing + * @bl: The &drm_edp_backlight_info struct to fill out with information on the backlight + * @driver_pwm_freq_hz: Optional PWM frequency from the driver in hz + * @edp_dpcd: A cached copy of the eDP DPCD + * @current_level: Where to store the probed brightness level, if any + * @current_mode: Where to store the currently set backlight control mode + * + * Initializes a &drm_edp_backlight_info struct by probing @aux for it's backlight capabilities, + * along with also probing the current and maximum supported brightness levels. + * + * If @driver_pwm_freq_hz is non-zero, this will be used as the backlight frequency. Otherwise, the + * default frequency from the panel is used. + * + * Returns: %0 on success, negative error code on failure. + */ +int +drm_edp_backlight_init(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl, + u16 driver_pwm_freq_hz, const u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE], + u16 *current_level, u8 *current_mode) +{ + int ret; + + if (edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP) + bl->aux_enable = true; + if (edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP) + bl->aux_set = true; + if (edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT) + bl->lsb_reg_used = true; + + /* Sanity check caps */ + if (!bl->aux_set && !(edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_PWM_PIN_CAP)) { + drm_dbg_kms(aux->drm_dev, + "%s: Panel supports neither AUX or PWM brightness control? Aborting\n", + aux->name); + return -EINVAL; + } + + ret = drm_edp_backlight_probe_max(aux, bl, driver_pwm_freq_hz, edp_dpcd); + if (ret < 0) + return ret; + + ret = drm_edp_backlight_probe_state(aux, bl, current_mode); + if (ret < 0) + return ret; + *current_level = ret; + + drm_dbg_kms(aux->drm_dev, + "%s: Found backlight: aux_set=%d aux_enable=%d mode=%d\n", + aux->name, bl->aux_set, bl->aux_enable, *current_mode); + if (bl->aux_set) { + drm_dbg_kms(aux->drm_dev, + "%s: Backlight caps: level=%d/%d pwm_freq_pre_divider=%d lsb_reg_used=%d\n", + aux->name, *current_level, bl->max, bl->pwm_freq_pre_divider, + bl->lsb_reg_used); + } + + return 0; +} +EXPORT_SYMBOL(drm_edp_backlight_init); + +#if IS_BUILTIN(CONFIG_BACKLIGHT_CLASS_DEVICE) || \ + (IS_MODULE(CONFIG_DRM_KMS_HELPER) && IS_MODULE(CONFIG_BACKLIGHT_CLASS_DEVICE)) + +static int dp_aux_backlight_update_status(struct backlight_device *bd) +{ + struct dp_aux_backlight *bl = bl_get_data(bd); + u16 brightness = backlight_get_brightness(bd); + int ret = 0; + + if (!backlight_is_blank(bd)) { + if (!bl->enabled) { + drm_edp_backlight_enable(bl->aux, &bl->info, brightness); + bl->enabled = true; + return 0; + } + ret = drm_edp_backlight_set_level(bl->aux, &bl->info, brightness); + } else { + if (bl->enabled) { + drm_edp_backlight_disable(bl->aux, &bl->info); + bl->enabled = false; + } + } + + return ret; +} + +static const struct backlight_ops dp_aux_bl_ops = { + .update_status = dp_aux_backlight_update_status, +}; + +/** + * drm_panel_dp_aux_backlight - create and use DP AUX backlight + * @panel: DRM panel + * @aux: The DP AUX channel to use + * + * Use this function to create and handle backlight if your panel + * supports backlight control over DP AUX channel using DPCD + * registers as per VESA's standard backlight control interface. + * + * When the panel is enabled backlight will be enabled after a + * successful call to &drm_panel_funcs.enable() + * + * When the panel is disabled backlight will be disabled before the + * call to &drm_panel_funcs.disable(). + * + * A typical implementation for a panel driver supporting backlight + * control over DP AUX will call this function at probe time. + * Backlight will then be handled transparently without requiring + * any intervention from the driver. + * + * drm_panel_dp_aux_backlight() must be called after the call to drm_panel_init(). + * + * Return: 0 on success or a negative error code on failure. + */ +int drm_panel_dp_aux_backlight(struct drm_panel *panel, struct drm_dp_aux *aux) +{ + struct dp_aux_backlight *bl; + struct backlight_properties props = { 0 }; + u16 current_level; + u8 current_mode; + u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE]; + int ret; + + if (!panel || !panel->dev || !aux) + return -EINVAL; + + ret = drm_dp_dpcd_read(aux, DP_EDP_DPCD_REV, edp_dpcd, + EDP_DISPLAY_CTL_CAP_SIZE); + if (ret < 0) + return ret; + + if (!drm_edp_backlight_supported(edp_dpcd)) { + DRM_DEV_INFO(panel->dev, "DP AUX backlight is not supported\n"); + return 0; + } + + bl = devm_kzalloc(panel->dev, sizeof(*bl), GFP_KERNEL); + if (!bl) + return -ENOMEM; + + bl->aux = aux; + + ret = drm_edp_backlight_init(aux, &bl->info, 0, edp_dpcd, + ¤t_level, ¤t_mode); + if (ret < 0) + return ret; + + props.type = BACKLIGHT_RAW; + props.brightness = current_level; + props.max_brightness = bl->info.max; + + bl->base = devm_backlight_device_register(panel->dev, "dp_aux_backlight", + panel->dev, bl, + &dp_aux_bl_ops, &props); + if (IS_ERR(bl->base)) + return PTR_ERR(bl->base); + + backlight_disable(bl->base); + + panel->backlight = bl->base; + + return 0; +} +EXPORT_SYMBOL(drm_panel_dp_aux_backlight); + +#endif diff --git a/drivers/gpu/drm/dp/drm_dp_aux_dev.c b/drivers/gpu/drm/dp/drm_dp_aux_dev.c new file mode 100644 index 000000000000..0618dfe16660 --- /dev/null +++ b/drivers/gpu/drm/dp/drm_dp_aux_dev.c @@ -0,0 +1,354 @@ +/* + * Copyright © 2015 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Authors: + * Rafael Antognolli + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "drm_dp_helper_internal.h" + +struct drm_dp_aux_dev { + unsigned index; + struct drm_dp_aux *aux; + struct device *dev; + struct kref refcount; + atomic_t usecount; +}; + +#define DRM_AUX_MINORS 256 +#define AUX_MAX_OFFSET (1 << 20) +static DEFINE_IDR(aux_idr); +static DEFINE_MUTEX(aux_idr_mutex); +static struct class *drm_dp_aux_dev_class; +static int drm_dev_major = -1; + +static struct drm_dp_aux_dev *drm_dp_aux_dev_get_by_minor(unsigned index) +{ + struct drm_dp_aux_dev *aux_dev = NULL; + + mutex_lock(&aux_idr_mutex); + aux_dev = idr_find(&aux_idr, index); + if (aux_dev && !kref_get_unless_zero(&aux_dev->refcount)) + aux_dev = NULL; + mutex_unlock(&aux_idr_mutex); + + return aux_dev; +} + +static struct drm_dp_aux_dev *alloc_drm_dp_aux_dev(struct drm_dp_aux *aux) +{ + struct drm_dp_aux_dev *aux_dev; + int index; + + aux_dev = kzalloc(sizeof(*aux_dev), GFP_KERNEL); + if (!aux_dev) + return ERR_PTR(-ENOMEM); + aux_dev->aux = aux; + atomic_set(&aux_dev->usecount, 1); + kref_init(&aux_dev->refcount); + + mutex_lock(&aux_idr_mutex); + index = idr_alloc(&aux_idr, aux_dev, 0, DRM_AUX_MINORS, GFP_KERNEL); + mutex_unlock(&aux_idr_mutex); + if (index < 0) { + kfree(aux_dev); + return ERR_PTR(index); + } + aux_dev->index = index; + + return aux_dev; +} + +static void release_drm_dp_aux_dev(struct kref *ref) +{ + struct drm_dp_aux_dev *aux_dev = + container_of(ref, struct drm_dp_aux_dev, refcount); + + kfree(aux_dev); +} + +static ssize_t name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t res; + struct drm_dp_aux_dev *aux_dev = + drm_dp_aux_dev_get_by_minor(MINOR(dev->devt)); + + if (!aux_dev) + return -ENODEV; + + res = sprintf(buf, "%s\n", aux_dev->aux->name); + kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); + + return res; +} +static DEVICE_ATTR_RO(name); + +static struct attribute *drm_dp_aux_attrs[] = { + &dev_attr_name.attr, + NULL, +}; +ATTRIBUTE_GROUPS(drm_dp_aux); + +static int auxdev_open(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + struct drm_dp_aux_dev *aux_dev; + + aux_dev = drm_dp_aux_dev_get_by_minor(minor); + if (!aux_dev) + return -ENODEV; + + file->private_data = aux_dev; + return 0; +} + +static loff_t auxdev_llseek(struct file *file, loff_t offset, int whence) +{ + return fixed_size_llseek(file, offset, whence, AUX_MAX_OFFSET); +} + +static ssize_t auxdev_read_iter(struct kiocb *iocb, struct iov_iter *to) +{ + struct drm_dp_aux_dev *aux_dev = iocb->ki_filp->private_data; + loff_t pos = iocb->ki_pos; + ssize_t res = 0; + + if (!atomic_inc_not_zero(&aux_dev->usecount)) + return -ENODEV; + + iov_iter_truncate(to, AUX_MAX_OFFSET - pos); + + while (iov_iter_count(to)) { + uint8_t buf[DP_AUX_MAX_PAYLOAD_BYTES]; + ssize_t todo = min(iov_iter_count(to), sizeof(buf)); + + if (signal_pending(current)) { + res = -ERESTARTSYS; + break; + } + + res = drm_dp_dpcd_read(aux_dev->aux, pos, buf, todo); + + if (res <= 0) + break; + + if (copy_to_iter(buf, res, to) != res) { + res = -EFAULT; + break; + } + + pos += res; + } + + if (pos != iocb->ki_pos) + res = pos - iocb->ki_pos; + iocb->ki_pos = pos; + + if (atomic_dec_and_test(&aux_dev->usecount)) + wake_up_var(&aux_dev->usecount); + + return res; +} + +static ssize_t auxdev_write_iter(struct kiocb *iocb, struct iov_iter *from) +{ + struct drm_dp_aux_dev *aux_dev = iocb->ki_filp->private_data; + loff_t pos = iocb->ki_pos; + ssize_t res = 0; + + if (!atomic_inc_not_zero(&aux_dev->usecount)) + return -ENODEV; + + iov_iter_truncate(from, AUX_MAX_OFFSET - pos); + + while (iov_iter_count(from)) { + uint8_t buf[DP_AUX_MAX_PAYLOAD_BYTES]; + ssize_t todo = min(iov_iter_count(from), sizeof(buf)); + + if (signal_pending(current)) { + res = -ERESTARTSYS; + break; + } + + if (!copy_from_iter_full(buf, todo, from)) { + res = -EFAULT; + break; + } + + res = drm_dp_dpcd_write(aux_dev->aux, pos, buf, todo); + + if (res <= 0) + break; + + pos += res; + } + + if (pos != iocb->ki_pos) + res = pos - iocb->ki_pos; + iocb->ki_pos = pos; + + if (atomic_dec_and_test(&aux_dev->usecount)) + wake_up_var(&aux_dev->usecount); + + return res; +} + +static int auxdev_release(struct inode *inode, struct file *file) +{ + struct drm_dp_aux_dev *aux_dev = file->private_data; + + kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); + return 0; +} + +static const struct file_operations auxdev_fops = { + .owner = THIS_MODULE, + .llseek = auxdev_llseek, + .read_iter = auxdev_read_iter, + .write_iter = auxdev_write_iter, + .open = auxdev_open, + .release = auxdev_release, +}; + +#define to_auxdev(d) container_of(d, struct drm_dp_aux_dev, aux) + +static struct drm_dp_aux_dev *drm_dp_aux_dev_get_by_aux(struct drm_dp_aux *aux) +{ + struct drm_dp_aux_dev *iter, *aux_dev = NULL; + int id; + + /* don't increase kref count here because this function should only be + * used by drm_dp_aux_unregister_devnode. Thus, it will always have at + * least one reference - the one that drm_dp_aux_register_devnode + * created + */ + mutex_lock(&aux_idr_mutex); + idr_for_each_entry(&aux_idr, iter, id) { + if (iter->aux == aux) { + aux_dev = iter; + break; + } + } + mutex_unlock(&aux_idr_mutex); + return aux_dev; +} + +void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux) +{ + struct drm_dp_aux_dev *aux_dev; + unsigned int minor; + + aux_dev = drm_dp_aux_dev_get_by_aux(aux); + if (!aux_dev) /* attach must have failed */ + return; + + /* + * As some AUX adapters may exist as platform devices which outlive their respective DRM + * devices, we clear drm_dev to ensure that we never accidentally reference a stale pointer + */ + aux->drm_dev = NULL; + + mutex_lock(&aux_idr_mutex); + idr_remove(&aux_idr, aux_dev->index); + mutex_unlock(&aux_idr_mutex); + + atomic_dec(&aux_dev->usecount); + wait_var_event(&aux_dev->usecount, !atomic_read(&aux_dev->usecount)); + + minor = aux_dev->index; + if (aux_dev->dev) + device_destroy(drm_dp_aux_dev_class, + MKDEV(drm_dev_major, minor)); + + DRM_DEBUG("drm_dp_aux_dev: aux [%s] unregistering\n", aux->name); + kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); +} + +int drm_dp_aux_register_devnode(struct drm_dp_aux *aux) +{ + struct drm_dp_aux_dev *aux_dev; + int res; + + aux_dev = alloc_drm_dp_aux_dev(aux); + if (IS_ERR(aux_dev)) + return PTR_ERR(aux_dev); + + aux_dev->dev = device_create(drm_dp_aux_dev_class, aux->dev, + MKDEV(drm_dev_major, aux_dev->index), NULL, + "drm_dp_aux%d", aux_dev->index); + if (IS_ERR(aux_dev->dev)) { + res = PTR_ERR(aux_dev->dev); + aux_dev->dev = NULL; + goto error; + } + + DRM_DEBUG("drm_dp_aux_dev: aux [%s] registered as minor %d\n", + aux->name, aux_dev->index); + return 0; +error: + drm_dp_aux_unregister_devnode(aux); + return res; +} + +int drm_dp_aux_dev_init(void) +{ + int res; + + drm_dp_aux_dev_class = class_create(THIS_MODULE, "drm_dp_aux_dev"); + if (IS_ERR(drm_dp_aux_dev_class)) { + return PTR_ERR(drm_dp_aux_dev_class); + } + drm_dp_aux_dev_class->dev_groups = drm_dp_aux_groups; + + res = register_chrdev(0, "aux", &auxdev_fops); + if (res < 0) + goto out; + drm_dev_major = res; + + return 0; +out: + class_destroy(drm_dp_aux_dev_class); + return res; +} + +void drm_dp_aux_dev_exit(void) +{ + unregister_chrdev(drm_dev_major, "aux"); + class_destroy(drm_dp_aux_dev_class); +} diff --git a/drivers/gpu/drm/dp/drm_dp_cec.c b/drivers/gpu/drm/dp/drm_dp_cec.c new file mode 100644 index 000000000000..3ab2609f9ec7 --- /dev/null +++ b/drivers/gpu/drm/dp/drm_dp_cec.c @@ -0,0 +1,451 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DisplayPort CEC-Tunneling-over-AUX support + * + * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + */ + +#include +#include +#include + +#include + +#include +#include +#include + +/* + * Unfortunately it turns out that we have a chicken-and-egg situation + * here. Quite a few active (mini-)DP-to-HDMI or USB-C-to-HDMI adapters + * have a converter chip that supports CEC-Tunneling-over-AUX (usually the + * Parade PS176), but they do not wire up the CEC pin, thus making CEC + * useless. Note that MegaChips 2900-based adapters appear to have good + * support for CEC tunneling. Those adapters that I have tested using + * this chipset all have the CEC line connected. + * + * Sadly there is no way for this driver to know this. What happens is + * that a /dev/cecX device is created that is isolated and unable to see + * any of the other CEC devices. Quite literally the CEC wire is cut + * (or in this case, never connected in the first place). + * + * The reason so few adapters support this is that this tunneling protocol + * was never supported by any OS. So there was no easy way of testing it, + * and no incentive to correctly wire up the CEC pin. + * + * Hopefully by creating this driver it will be easier for vendors to + * finally fix their adapters and test the CEC functionality. + * + * I keep a list of known working adapters here: + * + * https://hverkuil.home.xs4all.nl/cec-status.txt + * + * Please mail me (hverkuil@xs4all.nl) if you find an adapter that works + * and is not yet listed there. + * + * Note that the current implementation does not support CEC over an MST hub. + * As far as I can see there is no mechanism defined in the DisplayPort + * standard to transport CEC interrupts over an MST device. It might be + * possible to do this through polling, but I have not been able to get that + * to work. + */ + +/** + * DOC: dp cec helpers + * + * These functions take care of supporting the CEC-Tunneling-over-AUX + * feature of DisplayPort-to-HDMI adapters. + */ + +/* + * When the EDID is unset because the HPD went low, then the CEC DPCD registers + * typically can no longer be read (true for a DP-to-HDMI adapter since it is + * powered by the HPD). However, some displays toggle the HPD off and on for a + * short period for one reason or another, and that would cause the CEC adapter + * to be removed and added again, even though nothing else changed. + * + * This module parameter sets a delay in seconds before the CEC adapter is + * actually unregistered. Only if the HPD does not return within that time will + * the CEC adapter be unregistered. + * + * If it is set to a value >= NEVER_UNREG_DELAY, then the CEC adapter will never + * be unregistered for as long as the connector remains registered. + * + * If it is set to 0, then the CEC adapter will be unregistered immediately as + * soon as the HPD disappears. + * + * The default is one second to prevent short HPD glitches from unregistering + * the CEC adapter. + * + * Note that for integrated HDMI branch devices that support CEC the DPCD + * registers remain available even if the HPD goes low since it is not powered + * by the HPD. In that case the CEC adapter will never be unregistered during + * the life time of the connector. At least, this is the theory since I do not + * have hardware with an integrated HDMI branch device that supports CEC. + */ +#define NEVER_UNREG_DELAY 1000 +static unsigned int drm_dp_cec_unregister_delay = 1; +module_param(drm_dp_cec_unregister_delay, uint, 0600); +MODULE_PARM_DESC(drm_dp_cec_unregister_delay, + "CEC unregister delay in seconds, 0: no delay, >= 1000: never unregister"); + +static int drm_dp_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct drm_dp_aux *aux = cec_get_drvdata(adap); + u32 val = enable ? DP_CEC_TUNNELING_ENABLE : 0; + ssize_t err = 0; + + err = drm_dp_dpcd_writeb(aux, DP_CEC_TUNNELING_CONTROL, val); + return (enable && err < 0) ? err : 0; +} + +static int drm_dp_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) +{ + struct drm_dp_aux *aux = cec_get_drvdata(adap); + /* Bit 15 (logical address 15) should always be set */ + u16 la_mask = 1 << CEC_LOG_ADDR_BROADCAST; + u8 mask[2]; + ssize_t err; + + if (addr != CEC_LOG_ADDR_INVALID) + la_mask |= adap->log_addrs.log_addr_mask | (1 << addr); + mask[0] = la_mask & 0xff; + mask[1] = la_mask >> 8; + err = drm_dp_dpcd_write(aux, DP_CEC_LOGICAL_ADDRESS_MASK, mask, 2); + return (addr != CEC_LOG_ADDR_INVALID && err < 0) ? err : 0; +} + +static int drm_dp_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct drm_dp_aux *aux = cec_get_drvdata(adap); + unsigned int retries = min(5, attempts - 1); + ssize_t err; + + err = drm_dp_dpcd_write(aux, DP_CEC_TX_MESSAGE_BUFFER, + msg->msg, msg->len); + if (err < 0) + return err; + + err = drm_dp_dpcd_writeb(aux, DP_CEC_TX_MESSAGE_INFO, + (msg->len - 1) | (retries << 4) | + DP_CEC_TX_MESSAGE_SEND); + return err < 0 ? err : 0; +} + +static int drm_dp_cec_adap_monitor_all_enable(struct cec_adapter *adap, + bool enable) +{ + struct drm_dp_aux *aux = cec_get_drvdata(adap); + ssize_t err; + u8 val; + + if (!(adap->capabilities & CEC_CAP_MONITOR_ALL)) + return 0; + + err = drm_dp_dpcd_readb(aux, DP_CEC_TUNNELING_CONTROL, &val); + if (err >= 0) { + if (enable) + val |= DP_CEC_SNOOPING_ENABLE; + else + val &= ~DP_CEC_SNOOPING_ENABLE; + err = drm_dp_dpcd_writeb(aux, DP_CEC_TUNNELING_CONTROL, val); + } + return (enable && err < 0) ? err : 0; +} + +static void drm_dp_cec_adap_status(struct cec_adapter *adap, + struct seq_file *file) +{ + struct drm_dp_aux *aux = cec_get_drvdata(adap); + struct drm_dp_desc desc; + struct drm_dp_dpcd_ident *id = &desc.ident; + + if (drm_dp_read_desc(aux, &desc, true)) + return; + seq_printf(file, "OUI: %*phD\n", + (int)sizeof(id->oui), id->oui); + seq_printf(file, "ID: %*pE\n", + (int)strnlen(id->device_id, sizeof(id->device_id)), + id->device_id); + seq_printf(file, "HW Rev: %d.%d\n", id->hw_rev >> 4, id->hw_rev & 0xf); + /* + * Show this both in decimal and hex: at least one vendor + * always reports this in hex. + */ + seq_printf(file, "FW/SW Rev: %d.%d (0x%02x.0x%02x)\n", + id->sw_major_rev, id->sw_minor_rev, + id->sw_major_rev, id->sw_minor_rev); +} + +static const struct cec_adap_ops drm_dp_cec_adap_ops = { + .adap_enable = drm_dp_cec_adap_enable, + .adap_log_addr = drm_dp_cec_adap_log_addr, + .adap_transmit = drm_dp_cec_adap_transmit, + .adap_monitor_all_enable = drm_dp_cec_adap_monitor_all_enable, + .adap_status = drm_dp_cec_adap_status, +}; + +static int drm_dp_cec_received(struct drm_dp_aux *aux) +{ + struct cec_adapter *adap = aux->cec.adap; + struct cec_msg msg; + u8 rx_msg_info; + ssize_t err; + + err = drm_dp_dpcd_readb(aux, DP_CEC_RX_MESSAGE_INFO, &rx_msg_info); + if (err < 0) + return err; + + if (!(rx_msg_info & DP_CEC_RX_MESSAGE_ENDED)) + return 0; + + msg.len = (rx_msg_info & DP_CEC_RX_MESSAGE_LEN_MASK) + 1; + err = drm_dp_dpcd_read(aux, DP_CEC_RX_MESSAGE_BUFFER, msg.msg, msg.len); + if (err < 0) + return err; + + cec_received_msg(adap, &msg); + return 0; +} + +static void drm_dp_cec_handle_irq(struct drm_dp_aux *aux) +{ + struct cec_adapter *adap = aux->cec.adap; + u8 flags; + + if (drm_dp_dpcd_readb(aux, DP_CEC_TUNNELING_IRQ_FLAGS, &flags) < 0) + return; + + if (flags & DP_CEC_RX_MESSAGE_INFO_VALID) + drm_dp_cec_received(aux); + + if (flags & DP_CEC_TX_MESSAGE_SENT) + cec_transmit_attempt_done(adap, CEC_TX_STATUS_OK); + else if (flags & DP_CEC_TX_LINE_ERROR) + cec_transmit_attempt_done(adap, CEC_TX_STATUS_ERROR | + CEC_TX_STATUS_MAX_RETRIES); + else if (flags & + (DP_CEC_TX_ADDRESS_NACK_ERROR | DP_CEC_TX_DATA_NACK_ERROR)) + cec_transmit_attempt_done(adap, CEC_TX_STATUS_NACK | + CEC_TX_STATUS_MAX_RETRIES); + drm_dp_dpcd_writeb(aux, DP_CEC_TUNNELING_IRQ_FLAGS, flags); +} + +/** + * drm_dp_cec_irq() - handle CEC interrupt, if any + * @aux: DisplayPort AUX channel + * + * Should be called when handling an IRQ_HPD request. If CEC-tunneling-over-AUX + * is present, then it will check for a CEC_IRQ and handle it accordingly. + */ +void drm_dp_cec_irq(struct drm_dp_aux *aux) +{ + u8 cec_irq; + int ret; + + /* No transfer function was set, so not a DP connector */ + if (!aux->transfer) + return; + + mutex_lock(&aux->cec.lock); + if (!aux->cec.adap) + goto unlock; + + ret = drm_dp_dpcd_readb(aux, DP_DEVICE_SERVICE_IRQ_VECTOR_ESI1, + &cec_irq); + if (ret < 0 || !(cec_irq & DP_CEC_IRQ)) + goto unlock; + + drm_dp_cec_handle_irq(aux); + drm_dp_dpcd_writeb(aux, DP_DEVICE_SERVICE_IRQ_VECTOR_ESI1, DP_CEC_IRQ); +unlock: + mutex_unlock(&aux->cec.lock); +} +EXPORT_SYMBOL(drm_dp_cec_irq); + +static bool drm_dp_cec_cap(struct drm_dp_aux *aux, u8 *cec_cap) +{ + u8 cap = 0; + + if (drm_dp_dpcd_readb(aux, DP_CEC_TUNNELING_CAPABILITY, &cap) != 1 || + !(cap & DP_CEC_TUNNELING_CAPABLE)) + return false; + if (cec_cap) + *cec_cap = cap; + return true; +} + +/* + * Called if the HPD was low for more than drm_dp_cec_unregister_delay + * seconds. This unregisters the CEC adapter. + */ +static void drm_dp_cec_unregister_work(struct work_struct *work) +{ + struct drm_dp_aux *aux = container_of(work, struct drm_dp_aux, + cec.unregister_work.work); + + mutex_lock(&aux->cec.lock); + cec_unregister_adapter(aux->cec.adap); + aux->cec.adap = NULL; + mutex_unlock(&aux->cec.lock); +} + +/* + * A new EDID is set. If there is no CEC adapter, then create one. If + * there was a CEC adapter, then check if the CEC adapter properties + * were unchanged and just update the CEC physical address. Otherwise + * unregister the old CEC adapter and create a new one. + */ +void drm_dp_cec_set_edid(struct drm_dp_aux *aux, const struct edid *edid) +{ + struct drm_connector *connector = aux->cec.connector; + u32 cec_caps = CEC_CAP_DEFAULTS | CEC_CAP_NEEDS_HPD | + CEC_CAP_CONNECTOR_INFO; + struct cec_connector_info conn_info; + unsigned int num_las = 1; + u8 cap; + + /* No transfer function was set, so not a DP connector */ + if (!aux->transfer) + return; + +#ifndef CONFIG_MEDIA_CEC_RC + /* + * CEC_CAP_RC is part of CEC_CAP_DEFAULTS, but it is stripped by + * cec_allocate_adapter() if CONFIG_MEDIA_CEC_RC is undefined. + * + * Do this here as well to ensure the tests against cec_caps are + * correct. + */ + cec_caps &= ~CEC_CAP_RC; +#endif + cancel_delayed_work_sync(&aux->cec.unregister_work); + + mutex_lock(&aux->cec.lock); + if (!drm_dp_cec_cap(aux, &cap)) { + /* CEC is not supported, unregister any existing adapter */ + cec_unregister_adapter(aux->cec.adap); + aux->cec.adap = NULL; + goto unlock; + } + + if (cap & DP_CEC_SNOOPING_CAPABLE) + cec_caps |= CEC_CAP_MONITOR_ALL; + if (cap & DP_CEC_MULTIPLE_LA_CAPABLE) + num_las = CEC_MAX_LOG_ADDRS; + + if (aux->cec.adap) { + if (aux->cec.adap->capabilities == cec_caps && + aux->cec.adap->available_log_addrs == num_las) { + /* Unchanged, so just set the phys addr */ + cec_s_phys_addr_from_edid(aux->cec.adap, edid); + goto unlock; + } + /* + * The capabilities changed, so unregister the old + * adapter first. + */ + cec_unregister_adapter(aux->cec.adap); + } + + /* Create a new adapter */ + aux->cec.adap = cec_allocate_adapter(&drm_dp_cec_adap_ops, + aux, connector->name, cec_caps, + num_las); + if (IS_ERR(aux->cec.adap)) { + aux->cec.adap = NULL; + goto unlock; + } + + cec_fill_conn_info_from_drm(&conn_info, connector); + cec_s_conn_info(aux->cec.adap, &conn_info); + + if (cec_register_adapter(aux->cec.adap, connector->dev->dev)) { + cec_delete_adapter(aux->cec.adap); + aux->cec.adap = NULL; + } else { + /* + * Update the phys addr for the new CEC adapter. When called + * from drm_dp_cec_register_connector() edid == NULL, so in + * that case the phys addr is just invalidated. + */ + cec_s_phys_addr_from_edid(aux->cec.adap, edid); + } +unlock: + mutex_unlock(&aux->cec.lock); +} +EXPORT_SYMBOL(drm_dp_cec_set_edid); + +/* + * The EDID disappeared (likely because of the HPD going down). + */ +void drm_dp_cec_unset_edid(struct drm_dp_aux *aux) +{ + /* No transfer function was set, so not a DP connector */ + if (!aux->transfer) + return; + + cancel_delayed_work_sync(&aux->cec.unregister_work); + + mutex_lock(&aux->cec.lock); + if (!aux->cec.adap) + goto unlock; + + cec_phys_addr_invalidate(aux->cec.adap); + /* + * We're done if we want to keep the CEC device + * (drm_dp_cec_unregister_delay is >= NEVER_UNREG_DELAY) or if the + * DPCD still indicates the CEC capability (expected for an integrated + * HDMI branch device). + */ + if (drm_dp_cec_unregister_delay < NEVER_UNREG_DELAY && + !drm_dp_cec_cap(aux, NULL)) { + /* + * Unregister the CEC adapter after drm_dp_cec_unregister_delay + * seconds. This to debounce short HPD off-and-on cycles from + * displays. + */ + schedule_delayed_work(&aux->cec.unregister_work, + drm_dp_cec_unregister_delay * HZ); + } +unlock: + mutex_unlock(&aux->cec.lock); +} +EXPORT_SYMBOL(drm_dp_cec_unset_edid); + +/** + * drm_dp_cec_register_connector() - register a new connector + * @aux: DisplayPort AUX channel + * @connector: drm connector + * + * A new connector was registered with associated CEC adapter name and + * CEC adapter parent device. After registering the name and parent + * drm_dp_cec_set_edid() is called to check if the connector supports + * CEC and to register a CEC adapter if that is the case. + */ +void drm_dp_cec_register_connector(struct drm_dp_aux *aux, + struct drm_connector *connector) +{ + WARN_ON(aux->cec.adap); + if (WARN_ON(!aux->transfer)) + return; + aux->cec.connector = connector; + INIT_DELAYED_WORK(&aux->cec.unregister_work, + drm_dp_cec_unregister_work); +} +EXPORT_SYMBOL(drm_dp_cec_register_connector); + +/** + * drm_dp_cec_unregister_connector() - unregister the CEC adapter, if any + * @aux: DisplayPort AUX channel + */ +void drm_dp_cec_unregister_connector(struct drm_dp_aux *aux) +{ + if (!aux->cec.adap) + return; + cancel_delayed_work_sync(&aux->cec.unregister_work); + cec_unregister_adapter(aux->cec.adap); + aux->cec.adap = NULL; +} +EXPORT_SYMBOL(drm_dp_cec_unregister_connector); diff --git a/drivers/gpu/drm/dp/drm_dp_dual_mode_helper.c b/drivers/gpu/drm/dp/drm_dp_dual_mode_helper.c new file mode 100644 index 000000000000..9faf49354cab --- /dev/null +++ b/drivers/gpu/drm/dp/drm_dp_dual_mode_helper.c @@ -0,0 +1,530 @@ +/* + * Copyright © 2016 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/** + * DOC: dp dual mode helpers + * + * Helper functions to deal with DP dual mode (aka. DP++) adaptors. + * + * Type 1: + * Adaptor registers (if any) and the sink DDC bus may be accessed via I2C. + * + * Type 2: + * Adaptor registers and sink DDC bus can be accessed either via I2C or + * I2C-over-AUX. Source devices may choose to implement either of these + * access methods. + */ + +#define DP_DUAL_MODE_SLAVE_ADDRESS 0x40 + +/** + * drm_dp_dual_mode_read - Read from the DP dual mode adaptor register(s) + * @adapter: I2C adapter for the DDC bus + * @offset: register offset + * @buffer: buffer for return data + * @size: sizo of the buffer + * + * Reads @size bytes from the DP dual mode adaptor registers + * starting at @offset. + * + * Returns: + * 0 on success, negative error code on failure + */ +ssize_t drm_dp_dual_mode_read(struct i2c_adapter *adapter, + u8 offset, void *buffer, size_t size) +{ + struct i2c_msg msgs[] = { + { + .addr = DP_DUAL_MODE_SLAVE_ADDRESS, + .flags = 0, + .len = 1, + .buf = &offset, + }, + { + .addr = DP_DUAL_MODE_SLAVE_ADDRESS, + .flags = I2C_M_RD, + .len = size, + .buf = buffer, + }, + }; + int ret; + + ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + return ret; + if (ret != ARRAY_SIZE(msgs)) + return -EPROTO; + + return 0; +} +EXPORT_SYMBOL(drm_dp_dual_mode_read); + +/** + * drm_dp_dual_mode_write - Write to the DP dual mode adaptor register(s) + * @adapter: I2C adapter for the DDC bus + * @offset: register offset + * @buffer: buffer for write data + * @size: sizo of the buffer + * + * Writes @size bytes to the DP dual mode adaptor registers + * starting at @offset. + * + * Returns: + * 0 on success, negative error code on failure + */ +ssize_t drm_dp_dual_mode_write(struct i2c_adapter *adapter, + u8 offset, const void *buffer, size_t size) +{ + struct i2c_msg msg = { + .addr = DP_DUAL_MODE_SLAVE_ADDRESS, + .flags = 0, + .len = 1 + size, + .buf = NULL, + }; + void *data; + int ret; + + data = kmalloc(msg.len, GFP_KERNEL); + if (!data) + return -ENOMEM; + + msg.buf = data; + + memcpy(data, &offset, 1); + memcpy(data + 1, buffer, size); + + ret = i2c_transfer(adapter, &msg, 1); + + kfree(data); + + if (ret < 0) + return ret; + if (ret != 1) + return -EPROTO; + + return 0; +} +EXPORT_SYMBOL(drm_dp_dual_mode_write); + +static bool is_hdmi_adaptor(const char hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN]) +{ + static const char dp_dual_mode_hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN] = + "DP-HDMI ADAPTOR\x04"; + + return memcmp(hdmi_id, dp_dual_mode_hdmi_id, + sizeof(dp_dual_mode_hdmi_id)) == 0; +} + +static bool is_type1_adaptor(uint8_t adaptor_id) +{ + return adaptor_id == 0 || adaptor_id == 0xff; +} + +static bool is_type2_adaptor(uint8_t adaptor_id) +{ + return adaptor_id == (DP_DUAL_MODE_TYPE_TYPE2 | + DP_DUAL_MODE_REV_TYPE2); +} + +static bool is_lspcon_adaptor(const char hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN], + const uint8_t adaptor_id) +{ + return is_hdmi_adaptor(hdmi_id) && + (adaptor_id == (DP_DUAL_MODE_TYPE_TYPE2 | + DP_DUAL_MODE_TYPE_HAS_DPCD)); +} + +/** + * drm_dp_dual_mode_detect - Identify the DP dual mode adaptor + * @dev: &drm_device to use + * @adapter: I2C adapter for the DDC bus + * + * Attempt to identify the type of the DP dual mode adaptor used. + * + * Note that when the answer is @DRM_DP_DUAL_MODE_UNKNOWN it's not + * certain whether we're dealing with a native HDMI port or + * a type 1 DVI dual mode adaptor. The driver will have to use + * some other hardware/driver specific mechanism to make that + * distinction. + * + * Returns: + * The type of the DP dual mode adaptor used + */ +enum drm_dp_dual_mode_type drm_dp_dual_mode_detect(const struct drm_device *dev, + struct i2c_adapter *adapter) +{ + char hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN] = {}; + uint8_t adaptor_id = 0x00; + ssize_t ret; + + /* + * Let's see if the adaptor is there the by reading the + * HDMI ID registers. + * + * Note that type 1 DVI adaptors are not required to implemnt + * any registers, and that presents a problem for detection. + * If the i2c transfer is nacked, we may or may not be dealing + * with a type 1 DVI adaptor. Some other mechanism of detecting + * the presence of the adaptor is required. One way would be + * to check the state of the CONFIG1 pin, Another method would + * simply require the driver to know whether the port is a DP++ + * port or a native HDMI port. Both of these methods are entirely + * hardware/driver specific so we can't deal with them here. + */ + ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_HDMI_ID, + hdmi_id, sizeof(hdmi_id)); + drm_dbg_kms(dev, "DP dual mode HDMI ID: %*pE (err %zd)\n", + ret ? 0 : (int)sizeof(hdmi_id), hdmi_id, ret); + if (ret) + return DRM_DP_DUAL_MODE_UNKNOWN; + + /* + * Sigh. Some (maybe all?) type 1 adaptors are broken and ack + * the offset but ignore it, and instead they just always return + * data from the start of the HDMI ID buffer. So for a broken + * type 1 HDMI adaptor a single byte read will always give us + * 0x44, and for a type 1 DVI adaptor it should give 0x00 + * (assuming it implements any registers). Fortunately neither + * of those values will match the type 2 signature of the + * DP_DUAL_MODE_ADAPTOR_ID register so we can proceed with + * the type 2 adaptor detection safely even in the presence + * of broken type 1 adaptors. + */ + ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_ADAPTOR_ID, + &adaptor_id, sizeof(adaptor_id)); + drm_dbg_kms(dev, "DP dual mode adaptor ID: %02x (err %zd)\n", adaptor_id, ret); + if (ret == 0) { + if (is_lspcon_adaptor(hdmi_id, adaptor_id)) + return DRM_DP_DUAL_MODE_LSPCON; + if (is_type2_adaptor(adaptor_id)) { + if (is_hdmi_adaptor(hdmi_id)) + return DRM_DP_DUAL_MODE_TYPE2_HDMI; + else + return DRM_DP_DUAL_MODE_TYPE2_DVI; + } + /* + * If neither a proper type 1 ID nor a broken type 1 adaptor + * as described above, assume type 1, but let the user know + * that we may have misdetected the type. + */ + if (!is_type1_adaptor(adaptor_id) && adaptor_id != hdmi_id[0]) + drm_err(dev, "Unexpected DP dual mode adaptor ID %02x\n", adaptor_id); + + } + + if (is_hdmi_adaptor(hdmi_id)) + return DRM_DP_DUAL_MODE_TYPE1_HDMI; + else + return DRM_DP_DUAL_MODE_TYPE1_DVI; +} +EXPORT_SYMBOL(drm_dp_dual_mode_detect); + +/** + * drm_dp_dual_mode_max_tmds_clock - Max TMDS clock for DP dual mode adaptor + * @dev: &drm_device to use + * @type: DP dual mode adaptor type + * @adapter: I2C adapter for the DDC bus + * + * Determine the max TMDS clock the adaptor supports based on the + * type of the dual mode adaptor and the DP_DUAL_MODE_MAX_TMDS_CLOCK + * register (on type2 adaptors). As some type 1 adaptors have + * problems with registers (see comments in drm_dp_dual_mode_detect()) + * we don't read the register on those, instead we simply assume + * a 165 MHz limit based on the specification. + * + * Returns: + * Maximum supported TMDS clock rate for the DP dual mode adaptor in kHz. + */ +int drm_dp_dual_mode_max_tmds_clock(const struct drm_device *dev, enum drm_dp_dual_mode_type type, + struct i2c_adapter *adapter) +{ + uint8_t max_tmds_clock; + ssize_t ret; + + /* native HDMI so no limit */ + if (type == DRM_DP_DUAL_MODE_NONE) + return 0; + + /* + * Type 1 adaptors are limited to 165MHz + * Type 2 adaptors can tells us their limit + */ + if (type < DRM_DP_DUAL_MODE_TYPE2_DVI) + return 165000; + + ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_MAX_TMDS_CLOCK, + &max_tmds_clock, sizeof(max_tmds_clock)); + if (ret || max_tmds_clock == 0x00 || max_tmds_clock == 0xff) { + drm_dbg_kms(dev, "Failed to query max TMDS clock\n"); + return 165000; + } + + return max_tmds_clock * 5000 / 2; +} +EXPORT_SYMBOL(drm_dp_dual_mode_max_tmds_clock); + +/** + * drm_dp_dual_mode_get_tmds_output - Get the state of the TMDS output buffers in the DP dual mode adaptor + * @dev: &drm_device to use + * @type: DP dual mode adaptor type + * @adapter: I2C adapter for the DDC bus + * @enabled: current state of the TMDS output buffers + * + * Get the state of the TMDS output buffers in the adaptor. For + * type2 adaptors this is queried from the DP_DUAL_MODE_TMDS_OEN + * register. As some type 1 adaptors have problems with registers + * (see comments in drm_dp_dual_mode_detect()) we don't read the + * register on those, instead we simply assume that the buffers + * are always enabled. + * + * Returns: + * 0 on success, negative error code on failure + */ +int drm_dp_dual_mode_get_tmds_output(const struct drm_device *dev, + enum drm_dp_dual_mode_type type, struct i2c_adapter *adapter, + bool *enabled) +{ + uint8_t tmds_oen; + ssize_t ret; + + if (type < DRM_DP_DUAL_MODE_TYPE2_DVI) { + *enabled = true; + return 0; + } + + ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_TMDS_OEN, + &tmds_oen, sizeof(tmds_oen)); + if (ret) { + drm_dbg_kms(dev, "Failed to query state of TMDS output buffers\n"); + return ret; + } + + *enabled = !(tmds_oen & DP_DUAL_MODE_TMDS_DISABLE); + + return 0; +} +EXPORT_SYMBOL(drm_dp_dual_mode_get_tmds_output); + +/** + * drm_dp_dual_mode_set_tmds_output - Enable/disable TMDS output buffers in the DP dual mode adaptor + * @dev: &drm_device to use + * @type: DP dual mode adaptor type + * @adapter: I2C adapter for the DDC bus + * @enable: enable (as opposed to disable) the TMDS output buffers + * + * Set the state of the TMDS output buffers in the adaptor. For + * type2 this is set via the DP_DUAL_MODE_TMDS_OEN register. As + * some type 1 adaptors have problems with registers (see comments + * in drm_dp_dual_mode_detect()) we avoid touching the register, + * making this function a no-op on type 1 adaptors. + * + * Returns: + * 0 on success, negative error code on failure + */ +int drm_dp_dual_mode_set_tmds_output(const struct drm_device *dev, enum drm_dp_dual_mode_type type, + struct i2c_adapter *adapter, bool enable) +{ + uint8_t tmds_oen = enable ? 0 : DP_DUAL_MODE_TMDS_DISABLE; + ssize_t ret; + int retry; + + if (type < DRM_DP_DUAL_MODE_TYPE2_DVI) + return 0; + + /* + * LSPCON adapters in low-power state may ignore the first write, so + * read back and verify the written value a few times. + */ + for (retry = 0; retry < 3; retry++) { + uint8_t tmp; + + ret = drm_dp_dual_mode_write(adapter, DP_DUAL_MODE_TMDS_OEN, + &tmds_oen, sizeof(tmds_oen)); + if (ret) { + drm_dbg_kms(dev, "Failed to %s TMDS output buffers (%d attempts)\n", + enable ? "enable" : "disable", retry + 1); + return ret; + } + + ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_TMDS_OEN, + &tmp, sizeof(tmp)); + if (ret) { + drm_dbg_kms(dev, + "I2C read failed during TMDS output buffer %s (%d attempts)\n", + enable ? "enabling" : "disabling", retry + 1); + return ret; + } + + if (tmp == tmds_oen) + return 0; + } + + drm_dbg_kms(dev, "I2C write value mismatch during TMDS output buffer %s\n", + enable ? "enabling" : "disabling"); + + return -EIO; +} +EXPORT_SYMBOL(drm_dp_dual_mode_set_tmds_output); + +/** + * drm_dp_get_dual_mode_type_name - Get the name of the DP dual mode adaptor type as a string + * @type: DP dual mode adaptor type + * + * Returns: + * String representation of the DP dual mode adaptor type + */ +const char *drm_dp_get_dual_mode_type_name(enum drm_dp_dual_mode_type type) +{ + switch (type) { + case DRM_DP_DUAL_MODE_NONE: + return "none"; + case DRM_DP_DUAL_MODE_TYPE1_DVI: + return "type 1 DVI"; + case DRM_DP_DUAL_MODE_TYPE1_HDMI: + return "type 1 HDMI"; + case DRM_DP_DUAL_MODE_TYPE2_DVI: + return "type 2 DVI"; + case DRM_DP_DUAL_MODE_TYPE2_HDMI: + return "type 2 HDMI"; + case DRM_DP_DUAL_MODE_LSPCON: + return "lspcon"; + default: + WARN_ON(type != DRM_DP_DUAL_MODE_UNKNOWN); + return "unknown"; + } +} +EXPORT_SYMBOL(drm_dp_get_dual_mode_type_name); + +/** + * drm_lspcon_get_mode: Get LSPCON's current mode of operation by + * reading offset (0x80, 0x41) + * @dev: &drm_device to use + * @adapter: I2C-over-aux adapter + * @mode: current lspcon mode of operation output variable + * + * Returns: + * 0 on success, sets the current_mode value to appropriate mode + * -error on failure + */ +int drm_lspcon_get_mode(const struct drm_device *dev, struct i2c_adapter *adapter, + enum drm_lspcon_mode *mode) +{ + u8 data; + int ret = 0; + int retry; + + if (!mode) { + drm_err(dev, "NULL input\n"); + return -EINVAL; + } + + /* Read Status: i2c over aux */ + for (retry = 0; retry < 6; retry++) { + if (retry) + usleep_range(500, 1000); + + ret = drm_dp_dual_mode_read(adapter, + DP_DUAL_MODE_LSPCON_CURRENT_MODE, + &data, sizeof(data)); + if (!ret) + break; + } + + if (ret < 0) { + drm_dbg_kms(dev, "LSPCON read(0x80, 0x41) failed\n"); + return -EFAULT; + } + + if (data & DP_DUAL_MODE_LSPCON_MODE_PCON) + *mode = DRM_LSPCON_MODE_PCON; + else + *mode = DRM_LSPCON_MODE_LS; + return 0; +} +EXPORT_SYMBOL(drm_lspcon_get_mode); + +/** + * drm_lspcon_set_mode: Change LSPCON's mode of operation by + * writing offset (0x80, 0x40) + * @dev: &drm_device to use + * @adapter: I2C-over-aux adapter + * @mode: required mode of operation + * + * Returns: + * 0 on success, -error on failure/timeout + */ +int drm_lspcon_set_mode(const struct drm_device *dev, struct i2c_adapter *adapter, + enum drm_lspcon_mode mode) +{ + u8 data = 0; + int ret; + int time_out = 200; + enum drm_lspcon_mode current_mode; + + if (mode == DRM_LSPCON_MODE_PCON) + data = DP_DUAL_MODE_LSPCON_MODE_PCON; + + /* Change mode */ + ret = drm_dp_dual_mode_write(adapter, DP_DUAL_MODE_LSPCON_MODE_CHANGE, + &data, sizeof(data)); + if (ret < 0) { + drm_err(dev, "LSPCON mode change failed\n"); + return ret; + } + + /* + * Confirm mode change by reading the status bit. + * Sometimes, it takes a while to change the mode, + * so wait and retry until time out or done. + */ + do { + ret = drm_lspcon_get_mode(dev, adapter, ¤t_mode); + if (ret) { + drm_err(dev, "can't confirm LSPCON mode change\n"); + return ret; + } else { + if (current_mode != mode) { + msleep(10); + time_out -= 10; + } else { + drm_dbg_kms(dev, "LSPCON mode changed to %s\n", + mode == DRM_LSPCON_MODE_LS ? "LS" : "PCON"); + return 0; + } + } + } while (time_out); + + drm_err(dev, "LSPCON mode change timed out\n"); + return -ETIMEDOUT; +} +EXPORT_SYMBOL(drm_lspcon_set_mode); diff --git a/drivers/gpu/drm/dp/drm_dp_helper_internal.h b/drivers/gpu/drm/dp/drm_dp_helper_internal.h new file mode 100644 index 000000000000..8917fc3af9ec --- /dev/null +++ b/drivers/gpu/drm/dp/drm_dp_helper_internal.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: MIT */ + +#ifndef DRM_DP_HELPER_INTERNAL_H +#define DRM_DP_HELPER_INTERNAL_H + +struct drm_dp_aux; + +#ifdef CONFIG_DRM_DP_AUX_CHARDEV +int drm_dp_aux_dev_init(void); +void drm_dp_aux_dev_exit(void); +int drm_dp_aux_register_devnode(struct drm_dp_aux *aux); +void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux); +#else +static inline int drm_dp_aux_dev_init(void) +{ + return 0; +} + +static inline void drm_dp_aux_dev_exit(void) +{ +} + +static inline int drm_dp_aux_register_devnode(struct drm_dp_aux *aux) +{ + return 0; +} + +static inline void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux) +{ +} +#endif + +#endif diff --git a/drivers/gpu/drm/dp/drm_dp_helper_mod.c b/drivers/gpu/drm/dp/drm_dp_helper_mod.c new file mode 100644 index 000000000000..db753de24000 --- /dev/null +++ b/drivers/gpu/drm/dp/drm_dp_helper_mod.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +#include + +#include "drm_dp_helper_internal.h" + +MODULE_DESCRIPTION("DRM DisplayPort helper"); +MODULE_LICENSE("GPL and additional rights"); + +static int __init drm_dp_helper_module_init(void) +{ + return drm_dp_aux_dev_init(); +} + +static void __exit drm_dp_helper_module_exit(void) +{ + /* Call exit functions from specific dp helpers here */ + drm_dp_aux_dev_exit(); +} + +module_init(drm_dp_helper_module_init); +module_exit(drm_dp_helper_module_exit); diff --git a/drivers/gpu/drm/dp/drm_dp_mst_topology.c b/drivers/gpu/drm/dp/drm_dp_mst_topology.c new file mode 100644 index 000000000000..bc3237a09c06 --- /dev/null +++ b/drivers/gpu/drm/dp/drm_dp_mst_topology.c @@ -0,0 +1,5977 @@ +/* + * Copyright © 2014 Red Hat + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS) +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "drm_dp_helper_internal.h" +#include "drm_dp_mst_topology_internal.h" + +/** + * DOC: dp mst helper + * + * These functions contain parts of the DisplayPort 1.2a MultiStream Transport + * protocol. The helpers contain a topology manager and bandwidth manager. + * The helpers encapsulate the sending and received of sideband msgs. + */ +struct drm_dp_pending_up_req { + struct drm_dp_sideband_msg_hdr hdr; + struct drm_dp_sideband_msg_req_body msg; + struct list_head next; +}; + +static bool dump_dp_payload_table(struct drm_dp_mst_topology_mgr *mgr, + char *buf); + +static void drm_dp_mst_topology_put_port(struct drm_dp_mst_port *port); + +static int drm_dp_dpcd_write_payload(struct drm_dp_mst_topology_mgr *mgr, + int id, + struct drm_dp_payload *payload); + +static int drm_dp_send_dpcd_read(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int offset, int size, u8 *bytes); +static int drm_dp_send_dpcd_write(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int offset, int size, u8 *bytes); + +static int drm_dp_send_link_address(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb); + +static void +drm_dp_send_clear_payload_id_table(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb); + +static int drm_dp_send_enum_path_resources(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_port *port); +static bool drm_dp_validate_guid(struct drm_dp_mst_topology_mgr *mgr, + u8 *guid); + +static int drm_dp_mst_register_i2c_bus(struct drm_dp_mst_port *port); +static void drm_dp_mst_unregister_i2c_bus(struct drm_dp_mst_port *port); +static void drm_dp_mst_kick_tx(struct drm_dp_mst_topology_mgr *mgr); + +static bool drm_dp_mst_port_downstream_of_branch(struct drm_dp_mst_port *port, + struct drm_dp_mst_branch *branch); + +#define DBG_PREFIX "[dp_mst]" + +#define DP_STR(x) [DP_ ## x] = #x + +static const char *drm_dp_mst_req_type_str(u8 req_type) +{ + static const char * const req_type_str[] = { + DP_STR(GET_MSG_TRANSACTION_VERSION), + DP_STR(LINK_ADDRESS), + DP_STR(CONNECTION_STATUS_NOTIFY), + DP_STR(ENUM_PATH_RESOURCES), + DP_STR(ALLOCATE_PAYLOAD), + DP_STR(QUERY_PAYLOAD), + DP_STR(RESOURCE_STATUS_NOTIFY), + DP_STR(CLEAR_PAYLOAD_ID_TABLE), + DP_STR(REMOTE_DPCD_READ), + DP_STR(REMOTE_DPCD_WRITE), + DP_STR(REMOTE_I2C_READ), + DP_STR(REMOTE_I2C_WRITE), + DP_STR(POWER_UP_PHY), + DP_STR(POWER_DOWN_PHY), + DP_STR(SINK_EVENT_NOTIFY), + DP_STR(QUERY_STREAM_ENC_STATUS), + }; + + if (req_type >= ARRAY_SIZE(req_type_str) || + !req_type_str[req_type]) + return "unknown"; + + return req_type_str[req_type]; +} + +#undef DP_STR +#define DP_STR(x) [DP_NAK_ ## x] = #x + +static const char *drm_dp_mst_nak_reason_str(u8 nak_reason) +{ + static const char * const nak_reason_str[] = { + DP_STR(WRITE_FAILURE), + DP_STR(INVALID_READ), + DP_STR(CRC_FAILURE), + DP_STR(BAD_PARAM), + DP_STR(DEFER), + DP_STR(LINK_FAILURE), + DP_STR(NO_RESOURCES), + DP_STR(DPCD_FAIL), + DP_STR(I2C_NAK), + DP_STR(ALLOCATE_FAIL), + }; + + if (nak_reason >= ARRAY_SIZE(nak_reason_str) || + !nak_reason_str[nak_reason]) + return "unknown"; + + return nak_reason_str[nak_reason]; +} + +#undef DP_STR +#define DP_STR(x) [DRM_DP_SIDEBAND_TX_ ## x] = #x + +static const char *drm_dp_mst_sideband_tx_state_str(int state) +{ + static const char * const sideband_reason_str[] = { + DP_STR(QUEUED), + DP_STR(START_SEND), + DP_STR(SENT), + DP_STR(RX), + DP_STR(TIMEOUT), + }; + + if (state >= ARRAY_SIZE(sideband_reason_str) || + !sideband_reason_str[state]) + return "unknown"; + + return sideband_reason_str[state]; +} + +static int +drm_dp_mst_rad_to_str(const u8 rad[8], u8 lct, char *out, size_t len) +{ + int i; + u8 unpacked_rad[16]; + + for (i = 0; i < lct; i++) { + if (i % 2) + unpacked_rad[i] = rad[i / 2] >> 4; + else + unpacked_rad[i] = rad[i / 2] & BIT_MASK(4); + } + + /* TODO: Eventually add something to printk so we can format the rad + * like this: 1.2.3 + */ + return snprintf(out, len, "%*phC", lct, unpacked_rad); +} + +/* sideband msg handling */ +static u8 drm_dp_msg_header_crc4(const uint8_t *data, size_t num_nibbles) +{ + u8 bitmask = 0x80; + u8 bitshift = 7; + u8 array_index = 0; + int number_of_bits = num_nibbles * 4; + u8 remainder = 0; + + while (number_of_bits != 0) { + number_of_bits--; + remainder <<= 1; + remainder |= (data[array_index] & bitmask) >> bitshift; + bitmask >>= 1; + bitshift--; + if (bitmask == 0) { + bitmask = 0x80; + bitshift = 7; + array_index++; + } + if ((remainder & 0x10) == 0x10) + remainder ^= 0x13; + } + + number_of_bits = 4; + while (number_of_bits != 0) { + number_of_bits--; + remainder <<= 1; + if ((remainder & 0x10) != 0) + remainder ^= 0x13; + } + + return remainder; +} + +static u8 drm_dp_msg_data_crc4(const uint8_t *data, u8 number_of_bytes) +{ + u8 bitmask = 0x80; + u8 bitshift = 7; + u8 array_index = 0; + int number_of_bits = number_of_bytes * 8; + u16 remainder = 0; + + while (number_of_bits != 0) { + number_of_bits--; + remainder <<= 1; + remainder |= (data[array_index] & bitmask) >> bitshift; + bitmask >>= 1; + bitshift--; + if (bitmask == 0) { + bitmask = 0x80; + bitshift = 7; + array_index++; + } + if ((remainder & 0x100) == 0x100) + remainder ^= 0xd5; + } + + number_of_bits = 8; + while (number_of_bits != 0) { + number_of_bits--; + remainder <<= 1; + if ((remainder & 0x100) != 0) + remainder ^= 0xd5; + } + + return remainder & 0xff; +} +static inline u8 drm_dp_calc_sb_hdr_size(struct drm_dp_sideband_msg_hdr *hdr) +{ + u8 size = 3; + + size += (hdr->lct / 2); + return size; +} + +static void drm_dp_encode_sideband_msg_hdr(struct drm_dp_sideband_msg_hdr *hdr, + u8 *buf, int *len) +{ + int idx = 0; + int i; + u8 crc4; + + buf[idx++] = ((hdr->lct & 0xf) << 4) | (hdr->lcr & 0xf); + for (i = 0; i < (hdr->lct / 2); i++) + buf[idx++] = hdr->rad[i]; + buf[idx++] = (hdr->broadcast << 7) | (hdr->path_msg << 6) | + (hdr->msg_len & 0x3f); + buf[idx++] = (hdr->somt << 7) | (hdr->eomt << 6) | (hdr->seqno << 4); + + crc4 = drm_dp_msg_header_crc4(buf, (idx * 2) - 1); + buf[idx - 1] |= (crc4 & 0xf); + + *len = idx; +} + +static bool drm_dp_decode_sideband_msg_hdr(const struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_hdr *hdr, + u8 *buf, int buflen, u8 *hdrlen) +{ + u8 crc4; + u8 len; + int i; + u8 idx; + + if (buf[0] == 0) + return false; + len = 3; + len += ((buf[0] & 0xf0) >> 4) / 2; + if (len > buflen) + return false; + crc4 = drm_dp_msg_header_crc4(buf, (len * 2) - 1); + + if ((crc4 & 0xf) != (buf[len - 1] & 0xf)) { + drm_dbg_kms(mgr->dev, "crc4 mismatch 0x%x 0x%x\n", crc4, buf[len - 1]); + return false; + } + + hdr->lct = (buf[0] & 0xf0) >> 4; + hdr->lcr = (buf[0] & 0xf); + idx = 1; + for (i = 0; i < (hdr->lct / 2); i++) + hdr->rad[i] = buf[idx++]; + hdr->broadcast = (buf[idx] >> 7) & 0x1; + hdr->path_msg = (buf[idx] >> 6) & 0x1; + hdr->msg_len = buf[idx] & 0x3f; + idx++; + hdr->somt = (buf[idx] >> 7) & 0x1; + hdr->eomt = (buf[idx] >> 6) & 0x1; + hdr->seqno = (buf[idx] >> 4) & 0x1; + idx++; + *hdrlen = idx; + return true; +} + +void +drm_dp_encode_sideband_req(const struct drm_dp_sideband_msg_req_body *req, + struct drm_dp_sideband_msg_tx *raw) +{ + int idx = 0; + int i; + u8 *buf = raw->msg; + + buf[idx++] = req->req_type & 0x7f; + + switch (req->req_type) { + case DP_ENUM_PATH_RESOURCES: + case DP_POWER_DOWN_PHY: + case DP_POWER_UP_PHY: + buf[idx] = (req->u.port_num.port_number & 0xf) << 4; + idx++; + break; + case DP_ALLOCATE_PAYLOAD: + buf[idx] = (req->u.allocate_payload.port_number & 0xf) << 4 | + (req->u.allocate_payload.number_sdp_streams & 0xf); + idx++; + buf[idx] = (req->u.allocate_payload.vcpi & 0x7f); + idx++; + buf[idx] = (req->u.allocate_payload.pbn >> 8); + idx++; + buf[idx] = (req->u.allocate_payload.pbn & 0xff); + idx++; + for (i = 0; i < req->u.allocate_payload.number_sdp_streams / 2; i++) { + buf[idx] = ((req->u.allocate_payload.sdp_stream_sink[i * 2] & 0xf) << 4) | + (req->u.allocate_payload.sdp_stream_sink[i * 2 + 1] & 0xf); + idx++; + } + if (req->u.allocate_payload.number_sdp_streams & 1) { + i = req->u.allocate_payload.number_sdp_streams - 1; + buf[idx] = (req->u.allocate_payload.sdp_stream_sink[i] & 0xf) << 4; + idx++; + } + break; + case DP_QUERY_PAYLOAD: + buf[idx] = (req->u.query_payload.port_number & 0xf) << 4; + idx++; + buf[idx] = (req->u.query_payload.vcpi & 0x7f); + idx++; + break; + case DP_REMOTE_DPCD_READ: + buf[idx] = (req->u.dpcd_read.port_number & 0xf) << 4; + buf[idx] |= ((req->u.dpcd_read.dpcd_address & 0xf0000) >> 16) & 0xf; + idx++; + buf[idx] = (req->u.dpcd_read.dpcd_address & 0xff00) >> 8; + idx++; + buf[idx] = (req->u.dpcd_read.dpcd_address & 0xff); + idx++; + buf[idx] = (req->u.dpcd_read.num_bytes); + idx++; + break; + + case DP_REMOTE_DPCD_WRITE: + buf[idx] = (req->u.dpcd_write.port_number & 0xf) << 4; + buf[idx] |= ((req->u.dpcd_write.dpcd_address & 0xf0000) >> 16) & 0xf; + idx++; + buf[idx] = (req->u.dpcd_write.dpcd_address & 0xff00) >> 8; + idx++; + buf[idx] = (req->u.dpcd_write.dpcd_address & 0xff); + idx++; + buf[idx] = (req->u.dpcd_write.num_bytes); + idx++; + memcpy(&buf[idx], req->u.dpcd_write.bytes, req->u.dpcd_write.num_bytes); + idx += req->u.dpcd_write.num_bytes; + break; + case DP_REMOTE_I2C_READ: + buf[idx] = (req->u.i2c_read.port_number & 0xf) << 4; + buf[idx] |= (req->u.i2c_read.num_transactions & 0x3); + idx++; + for (i = 0; i < (req->u.i2c_read.num_transactions & 0x3); i++) { + buf[idx] = req->u.i2c_read.transactions[i].i2c_dev_id & 0x7f; + idx++; + buf[idx] = req->u.i2c_read.transactions[i].num_bytes; + idx++; + memcpy(&buf[idx], req->u.i2c_read.transactions[i].bytes, req->u.i2c_read.transactions[i].num_bytes); + idx += req->u.i2c_read.transactions[i].num_bytes; + + buf[idx] = (req->u.i2c_read.transactions[i].no_stop_bit & 0x1) << 4; + buf[idx] |= (req->u.i2c_read.transactions[i].i2c_transaction_delay & 0xf); + idx++; + } + buf[idx] = (req->u.i2c_read.read_i2c_device_id) & 0x7f; + idx++; + buf[idx] = (req->u.i2c_read.num_bytes_read); + idx++; + break; + + case DP_REMOTE_I2C_WRITE: + buf[idx] = (req->u.i2c_write.port_number & 0xf) << 4; + idx++; + buf[idx] = (req->u.i2c_write.write_i2c_device_id) & 0x7f; + idx++; + buf[idx] = (req->u.i2c_write.num_bytes); + idx++; + memcpy(&buf[idx], req->u.i2c_write.bytes, req->u.i2c_write.num_bytes); + idx += req->u.i2c_write.num_bytes; + break; + case DP_QUERY_STREAM_ENC_STATUS: { + const struct drm_dp_query_stream_enc_status *msg; + + msg = &req->u.enc_status; + buf[idx] = msg->stream_id; + idx++; + memcpy(&buf[idx], msg->client_id, sizeof(msg->client_id)); + idx += sizeof(msg->client_id); + buf[idx] = 0; + buf[idx] |= FIELD_PREP(GENMASK(1, 0), msg->stream_event); + buf[idx] |= msg->valid_stream_event ? BIT(2) : 0; + buf[idx] |= FIELD_PREP(GENMASK(4, 3), msg->stream_behavior); + buf[idx] |= msg->valid_stream_behavior ? BIT(5) : 0; + idx++; + } + break; + } + raw->cur_len = idx; +} +EXPORT_SYMBOL_FOR_TESTS_ONLY(drm_dp_encode_sideband_req); + +/* Decode a sideband request we've encoded, mainly used for debugging */ +int +drm_dp_decode_sideband_req(const struct drm_dp_sideband_msg_tx *raw, + struct drm_dp_sideband_msg_req_body *req) +{ + const u8 *buf = raw->msg; + int i, idx = 0; + + req->req_type = buf[idx++] & 0x7f; + switch (req->req_type) { + case DP_ENUM_PATH_RESOURCES: + case DP_POWER_DOWN_PHY: + case DP_POWER_UP_PHY: + req->u.port_num.port_number = (buf[idx] >> 4) & 0xf; + break; + case DP_ALLOCATE_PAYLOAD: + { + struct drm_dp_allocate_payload *a = + &req->u.allocate_payload; + + a->number_sdp_streams = buf[idx] & 0xf; + a->port_number = (buf[idx] >> 4) & 0xf; + + WARN_ON(buf[++idx] & 0x80); + a->vcpi = buf[idx] & 0x7f; + + a->pbn = buf[++idx] << 8; + a->pbn |= buf[++idx]; + + idx++; + for (i = 0; i < a->number_sdp_streams; i++) { + a->sdp_stream_sink[i] = + (buf[idx + (i / 2)] >> ((i % 2) ? 0 : 4)) & 0xf; + } + } + break; + case DP_QUERY_PAYLOAD: + req->u.query_payload.port_number = (buf[idx] >> 4) & 0xf; + WARN_ON(buf[++idx] & 0x80); + req->u.query_payload.vcpi = buf[idx] & 0x7f; + break; + case DP_REMOTE_DPCD_READ: + { + struct drm_dp_remote_dpcd_read *r = &req->u.dpcd_read; + + r->port_number = (buf[idx] >> 4) & 0xf; + + r->dpcd_address = (buf[idx] << 16) & 0xf0000; + r->dpcd_address |= (buf[++idx] << 8) & 0xff00; + r->dpcd_address |= buf[++idx] & 0xff; + + r->num_bytes = buf[++idx]; + } + break; + case DP_REMOTE_DPCD_WRITE: + { + struct drm_dp_remote_dpcd_write *w = + &req->u.dpcd_write; + + w->port_number = (buf[idx] >> 4) & 0xf; + + w->dpcd_address = (buf[idx] << 16) & 0xf0000; + w->dpcd_address |= (buf[++idx] << 8) & 0xff00; + w->dpcd_address |= buf[++idx] & 0xff; + + w->num_bytes = buf[++idx]; + + w->bytes = kmemdup(&buf[++idx], w->num_bytes, + GFP_KERNEL); + if (!w->bytes) + return -ENOMEM; + } + break; + case DP_REMOTE_I2C_READ: + { + struct drm_dp_remote_i2c_read *r = &req->u.i2c_read; + struct drm_dp_remote_i2c_read_tx *tx; + bool failed = false; + + r->num_transactions = buf[idx] & 0x3; + r->port_number = (buf[idx] >> 4) & 0xf; + for (i = 0; i < r->num_transactions; i++) { + tx = &r->transactions[i]; + + tx->i2c_dev_id = buf[++idx] & 0x7f; + tx->num_bytes = buf[++idx]; + tx->bytes = kmemdup(&buf[++idx], + tx->num_bytes, + GFP_KERNEL); + if (!tx->bytes) { + failed = true; + break; + } + idx += tx->num_bytes; + tx->no_stop_bit = (buf[idx] >> 5) & 0x1; + tx->i2c_transaction_delay = buf[idx] & 0xf; + } + + if (failed) { + for (i = 0; i < r->num_transactions; i++) { + tx = &r->transactions[i]; + kfree(tx->bytes); + } + return -ENOMEM; + } + + r->read_i2c_device_id = buf[++idx] & 0x7f; + r->num_bytes_read = buf[++idx]; + } + break; + case DP_REMOTE_I2C_WRITE: + { + struct drm_dp_remote_i2c_write *w = &req->u.i2c_write; + + w->port_number = (buf[idx] >> 4) & 0xf; + w->write_i2c_device_id = buf[++idx] & 0x7f; + w->num_bytes = buf[++idx]; + w->bytes = kmemdup(&buf[++idx], w->num_bytes, + GFP_KERNEL); + if (!w->bytes) + return -ENOMEM; + } + break; + case DP_QUERY_STREAM_ENC_STATUS: + req->u.enc_status.stream_id = buf[idx++]; + for (i = 0; i < sizeof(req->u.enc_status.client_id); i++) + req->u.enc_status.client_id[i] = buf[idx++]; + + req->u.enc_status.stream_event = FIELD_GET(GENMASK(1, 0), + buf[idx]); + req->u.enc_status.valid_stream_event = FIELD_GET(BIT(2), + buf[idx]); + req->u.enc_status.stream_behavior = FIELD_GET(GENMASK(4, 3), + buf[idx]); + req->u.enc_status.valid_stream_behavior = FIELD_GET(BIT(5), + buf[idx]); + break; + } + + return 0; +} +EXPORT_SYMBOL_FOR_TESTS_ONLY(drm_dp_decode_sideband_req); + +void +drm_dp_dump_sideband_msg_req_body(const struct drm_dp_sideband_msg_req_body *req, + int indent, struct drm_printer *printer) +{ + int i; + +#define P(f, ...) drm_printf_indent(printer, indent, f, ##__VA_ARGS__) + if (req->req_type == DP_LINK_ADDRESS) { + /* No contents to print */ + P("type=%s\n", drm_dp_mst_req_type_str(req->req_type)); + return; + } + + P("type=%s contents:\n", drm_dp_mst_req_type_str(req->req_type)); + indent++; + + switch (req->req_type) { + case DP_ENUM_PATH_RESOURCES: + case DP_POWER_DOWN_PHY: + case DP_POWER_UP_PHY: + P("port=%d\n", req->u.port_num.port_number); + break; + case DP_ALLOCATE_PAYLOAD: + P("port=%d vcpi=%d pbn=%d sdp_streams=%d %*ph\n", + req->u.allocate_payload.port_number, + req->u.allocate_payload.vcpi, req->u.allocate_payload.pbn, + req->u.allocate_payload.number_sdp_streams, + req->u.allocate_payload.number_sdp_streams, + req->u.allocate_payload.sdp_stream_sink); + break; + case DP_QUERY_PAYLOAD: + P("port=%d vcpi=%d\n", + req->u.query_payload.port_number, + req->u.query_payload.vcpi); + break; + case DP_REMOTE_DPCD_READ: + P("port=%d dpcd_addr=%05x len=%d\n", + req->u.dpcd_read.port_number, req->u.dpcd_read.dpcd_address, + req->u.dpcd_read.num_bytes); + break; + case DP_REMOTE_DPCD_WRITE: + P("port=%d addr=%05x len=%d: %*ph\n", + req->u.dpcd_write.port_number, + req->u.dpcd_write.dpcd_address, + req->u.dpcd_write.num_bytes, req->u.dpcd_write.num_bytes, + req->u.dpcd_write.bytes); + break; + case DP_REMOTE_I2C_READ: + P("port=%d num_tx=%d id=%d size=%d:\n", + req->u.i2c_read.port_number, + req->u.i2c_read.num_transactions, + req->u.i2c_read.read_i2c_device_id, + req->u.i2c_read.num_bytes_read); + + indent++; + for (i = 0; i < req->u.i2c_read.num_transactions; i++) { + const struct drm_dp_remote_i2c_read_tx *rtx = + &req->u.i2c_read.transactions[i]; + + P("%d: id=%03d size=%03d no_stop_bit=%d tx_delay=%03d: %*ph\n", + i, rtx->i2c_dev_id, rtx->num_bytes, + rtx->no_stop_bit, rtx->i2c_transaction_delay, + rtx->num_bytes, rtx->bytes); + } + break; + case DP_REMOTE_I2C_WRITE: + P("port=%d id=%d size=%d: %*ph\n", + req->u.i2c_write.port_number, + req->u.i2c_write.write_i2c_device_id, + req->u.i2c_write.num_bytes, req->u.i2c_write.num_bytes, + req->u.i2c_write.bytes); + break; + case DP_QUERY_STREAM_ENC_STATUS: + P("stream_id=%u client_id=%*ph stream_event=%x " + "valid_event=%d stream_behavior=%x valid_behavior=%d", + req->u.enc_status.stream_id, + (int)ARRAY_SIZE(req->u.enc_status.client_id), + req->u.enc_status.client_id, req->u.enc_status.stream_event, + req->u.enc_status.valid_stream_event, + req->u.enc_status.stream_behavior, + req->u.enc_status.valid_stream_behavior); + break; + default: + P("???\n"); + break; + } +#undef P +} +EXPORT_SYMBOL_FOR_TESTS_ONLY(drm_dp_dump_sideband_msg_req_body); + +static inline void +drm_dp_mst_dump_sideband_msg_tx(struct drm_printer *p, + const struct drm_dp_sideband_msg_tx *txmsg) +{ + struct drm_dp_sideband_msg_req_body req; + char buf[64]; + int ret; + int i; + + drm_dp_mst_rad_to_str(txmsg->dst->rad, txmsg->dst->lct, buf, + sizeof(buf)); + drm_printf(p, "txmsg cur_offset=%x cur_len=%x seqno=%x state=%s path_msg=%d dst=%s\n", + txmsg->cur_offset, txmsg->cur_len, txmsg->seqno, + drm_dp_mst_sideband_tx_state_str(txmsg->state), + txmsg->path_msg, buf); + + ret = drm_dp_decode_sideband_req(txmsg, &req); + if (ret) { + drm_printf(p, "\n", ret); + return; + } + drm_dp_dump_sideband_msg_req_body(&req, 1, p); + + switch (req.req_type) { + case DP_REMOTE_DPCD_WRITE: + kfree(req.u.dpcd_write.bytes); + break; + case DP_REMOTE_I2C_READ: + for (i = 0; i < req.u.i2c_read.num_transactions; i++) + kfree(req.u.i2c_read.transactions[i].bytes); + break; + case DP_REMOTE_I2C_WRITE: + kfree(req.u.i2c_write.bytes); + break; + } +} + +static void drm_dp_crc_sideband_chunk_req(u8 *msg, u8 len) +{ + u8 crc4; + + crc4 = drm_dp_msg_data_crc4(msg, len); + msg[len] = crc4; +} + +static void drm_dp_encode_sideband_reply(struct drm_dp_sideband_msg_reply_body *rep, + struct drm_dp_sideband_msg_tx *raw) +{ + int idx = 0; + u8 *buf = raw->msg; + + buf[idx++] = (rep->reply_type & 0x1) << 7 | (rep->req_type & 0x7f); + + raw->cur_len = idx; +} + +static int drm_dp_sideband_msg_set_header(struct drm_dp_sideband_msg_rx *msg, + struct drm_dp_sideband_msg_hdr *hdr, + u8 hdrlen) +{ + /* + * ignore out-of-order messages or messages that are part of a + * failed transaction + */ + if (!hdr->somt && !msg->have_somt) + return false; + + /* get length contained in this portion */ + msg->curchunk_idx = 0; + msg->curchunk_len = hdr->msg_len; + msg->curchunk_hdrlen = hdrlen; + + /* we have already gotten an somt - don't bother parsing */ + if (hdr->somt && msg->have_somt) + return false; + + if (hdr->somt) { + memcpy(&msg->initial_hdr, hdr, + sizeof(struct drm_dp_sideband_msg_hdr)); + msg->have_somt = true; + } + if (hdr->eomt) + msg->have_eomt = true; + + return true; +} + +/* this adds a chunk of msg to the builder to get the final msg */ +static bool drm_dp_sideband_append_payload(struct drm_dp_sideband_msg_rx *msg, + u8 *replybuf, u8 replybuflen) +{ + u8 crc4; + + memcpy(&msg->chunk[msg->curchunk_idx], replybuf, replybuflen); + msg->curchunk_idx += replybuflen; + + if (msg->curchunk_idx >= msg->curchunk_len) { + /* do CRC */ + crc4 = drm_dp_msg_data_crc4(msg->chunk, msg->curchunk_len - 1); + if (crc4 != msg->chunk[msg->curchunk_len - 1]) + print_hex_dump(KERN_DEBUG, "wrong crc", + DUMP_PREFIX_NONE, 16, 1, + msg->chunk, msg->curchunk_len, false); + /* copy chunk into bigger msg */ + memcpy(&msg->msg[msg->curlen], msg->chunk, msg->curchunk_len - 1); + msg->curlen += msg->curchunk_len - 1; + } + return true; +} + +static bool drm_dp_sideband_parse_link_address(const struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) +{ + int idx = 1; + int i; + + memcpy(repmsg->u.link_addr.guid, &raw->msg[idx], 16); + idx += 16; + repmsg->u.link_addr.nports = raw->msg[idx] & 0xf; + idx++; + if (idx > raw->curlen) + goto fail_len; + for (i = 0; i < repmsg->u.link_addr.nports; i++) { + if (raw->msg[idx] & 0x80) + repmsg->u.link_addr.ports[i].input_port = 1; + + repmsg->u.link_addr.ports[i].peer_device_type = (raw->msg[idx] >> 4) & 0x7; + repmsg->u.link_addr.ports[i].port_number = (raw->msg[idx] & 0xf); + + idx++; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.link_addr.ports[i].mcs = (raw->msg[idx] >> 7) & 0x1; + repmsg->u.link_addr.ports[i].ddps = (raw->msg[idx] >> 6) & 0x1; + if (repmsg->u.link_addr.ports[i].input_port == 0) + repmsg->u.link_addr.ports[i].legacy_device_plug_status = (raw->msg[idx] >> 5) & 0x1; + idx++; + if (idx > raw->curlen) + goto fail_len; + if (repmsg->u.link_addr.ports[i].input_port == 0) { + repmsg->u.link_addr.ports[i].dpcd_revision = (raw->msg[idx]); + idx++; + if (idx > raw->curlen) + goto fail_len; + memcpy(repmsg->u.link_addr.ports[i].peer_guid, &raw->msg[idx], 16); + idx += 16; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.link_addr.ports[i].num_sdp_streams = (raw->msg[idx] >> 4) & 0xf; + repmsg->u.link_addr.ports[i].num_sdp_stream_sinks = (raw->msg[idx] & 0xf); + idx++; + + } + if (idx > raw->curlen) + goto fail_len; + } + + return true; +fail_len: + DRM_DEBUG_KMS("link address reply parse length fail %d %d\n", idx, raw->curlen); + return false; +} + +static bool drm_dp_sideband_parse_remote_dpcd_read(struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) +{ + int idx = 1; + + repmsg->u.remote_dpcd_read_ack.port_number = raw->msg[idx] & 0xf; + idx++; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.remote_dpcd_read_ack.num_bytes = raw->msg[idx]; + idx++; + if (idx > raw->curlen) + goto fail_len; + + memcpy(repmsg->u.remote_dpcd_read_ack.bytes, &raw->msg[idx], repmsg->u.remote_dpcd_read_ack.num_bytes); + return true; +fail_len: + DRM_DEBUG_KMS("link address reply parse length fail %d %d\n", idx, raw->curlen); + return false; +} + +static bool drm_dp_sideband_parse_remote_dpcd_write(struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) +{ + int idx = 1; + + repmsg->u.remote_dpcd_write_ack.port_number = raw->msg[idx] & 0xf; + idx++; + if (idx > raw->curlen) + goto fail_len; + return true; +fail_len: + DRM_DEBUG_KMS("parse length fail %d %d\n", idx, raw->curlen); + return false; +} + +static bool drm_dp_sideband_parse_remote_i2c_read_ack(struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) +{ + int idx = 1; + + repmsg->u.remote_i2c_read_ack.port_number = (raw->msg[idx] & 0xf); + idx++; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.remote_i2c_read_ack.num_bytes = raw->msg[idx]; + idx++; + /* TODO check */ + memcpy(repmsg->u.remote_i2c_read_ack.bytes, &raw->msg[idx], repmsg->u.remote_i2c_read_ack.num_bytes); + return true; +fail_len: + DRM_DEBUG_KMS("remote i2c reply parse length fail %d %d\n", idx, raw->curlen); + return false; +} + +static bool drm_dp_sideband_parse_enum_path_resources_ack(struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) +{ + int idx = 1; + + repmsg->u.path_resources.port_number = (raw->msg[idx] >> 4) & 0xf; + repmsg->u.path_resources.fec_capable = raw->msg[idx] & 0x1; + idx++; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.path_resources.full_payload_bw_number = (raw->msg[idx] << 8) | (raw->msg[idx+1]); + idx += 2; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.path_resources.avail_payload_bw_number = (raw->msg[idx] << 8) | (raw->msg[idx+1]); + idx += 2; + if (idx > raw->curlen) + goto fail_len; + return true; +fail_len: + DRM_DEBUG_KMS("enum resource parse length fail %d %d\n", idx, raw->curlen); + return false; +} + +static bool drm_dp_sideband_parse_allocate_payload_ack(struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) +{ + int idx = 1; + + repmsg->u.allocate_payload.port_number = (raw->msg[idx] >> 4) & 0xf; + idx++; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.allocate_payload.vcpi = raw->msg[idx]; + idx++; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.allocate_payload.allocated_pbn = (raw->msg[idx] << 8) | (raw->msg[idx+1]); + idx += 2; + if (idx > raw->curlen) + goto fail_len; + return true; +fail_len: + DRM_DEBUG_KMS("allocate payload parse length fail %d %d\n", idx, raw->curlen); + return false; +} + +static bool drm_dp_sideband_parse_query_payload_ack(struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) +{ + int idx = 1; + + repmsg->u.query_payload.port_number = (raw->msg[idx] >> 4) & 0xf; + idx++; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.query_payload.allocated_pbn = (raw->msg[idx] << 8) | (raw->msg[idx + 1]); + idx += 2; + if (idx > raw->curlen) + goto fail_len; + return true; +fail_len: + DRM_DEBUG_KMS("query payload parse length fail %d %d\n", idx, raw->curlen); + return false; +} + +static bool drm_dp_sideband_parse_power_updown_phy_ack(struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) +{ + int idx = 1; + + repmsg->u.port_number.port_number = (raw->msg[idx] >> 4) & 0xf; + idx++; + if (idx > raw->curlen) { + DRM_DEBUG_KMS("power up/down phy parse length fail %d %d\n", + idx, raw->curlen); + return false; + } + return true; +} + +static bool +drm_dp_sideband_parse_query_stream_enc_status( + struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) +{ + struct drm_dp_query_stream_enc_status_ack_reply *reply; + + reply = &repmsg->u.enc_status; + + reply->stream_id = raw->msg[3]; + + reply->reply_signed = raw->msg[2] & BIT(0); + + /* + * NOTE: It's my impression from reading the spec that the below parsing + * is correct. However I noticed while testing with an HDCP 1.4 display + * through an HDCP 2.2 hub that only bit 3 was set. In that case, I + * would expect both bits to be set. So keep the parsing following the + * spec, but beware reality might not match the spec (at least for some + * configurations). + */ + reply->hdcp_1x_device_present = raw->msg[2] & BIT(4); + reply->hdcp_2x_device_present = raw->msg[2] & BIT(3); + + reply->query_capable_device_present = raw->msg[2] & BIT(5); + reply->legacy_device_present = raw->msg[2] & BIT(6); + reply->unauthorizable_device_present = raw->msg[2] & BIT(7); + + reply->auth_completed = !!(raw->msg[1] & BIT(3)); + reply->encryption_enabled = !!(raw->msg[1] & BIT(4)); + reply->repeater_present = !!(raw->msg[1] & BIT(5)); + reply->state = (raw->msg[1] & GENMASK(7, 6)) >> 6; + + return true; +} + +static bool drm_dp_sideband_parse_reply(const struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *msg) +{ + memset(msg, 0, sizeof(*msg)); + msg->reply_type = (raw->msg[0] & 0x80) >> 7; + msg->req_type = (raw->msg[0] & 0x7f); + + if (msg->reply_type == DP_SIDEBAND_REPLY_NAK) { + memcpy(msg->u.nak.guid, &raw->msg[1], 16); + msg->u.nak.reason = raw->msg[17]; + msg->u.nak.nak_data = raw->msg[18]; + return false; + } + + switch (msg->req_type) { + case DP_LINK_ADDRESS: + return drm_dp_sideband_parse_link_address(mgr, raw, msg); + case DP_QUERY_PAYLOAD: + return drm_dp_sideband_parse_query_payload_ack(raw, msg); + case DP_REMOTE_DPCD_READ: + return drm_dp_sideband_parse_remote_dpcd_read(raw, msg); + case DP_REMOTE_DPCD_WRITE: + return drm_dp_sideband_parse_remote_dpcd_write(raw, msg); + case DP_REMOTE_I2C_READ: + return drm_dp_sideband_parse_remote_i2c_read_ack(raw, msg); + case DP_REMOTE_I2C_WRITE: + return true; /* since there's nothing to parse */ + case DP_ENUM_PATH_RESOURCES: + return drm_dp_sideband_parse_enum_path_resources_ack(raw, msg); + case DP_ALLOCATE_PAYLOAD: + return drm_dp_sideband_parse_allocate_payload_ack(raw, msg); + case DP_POWER_DOWN_PHY: + case DP_POWER_UP_PHY: + return drm_dp_sideband_parse_power_updown_phy_ack(raw, msg); + case DP_CLEAR_PAYLOAD_ID_TABLE: + return true; /* since there's nothing to parse */ + case DP_QUERY_STREAM_ENC_STATUS: + return drm_dp_sideband_parse_query_stream_enc_status(raw, msg); + default: + drm_err(mgr->dev, "Got unknown reply 0x%02x (%s)\n", + msg->req_type, drm_dp_mst_req_type_str(msg->req_type)); + return false; + } +} + +static bool +drm_dp_sideband_parse_connection_status_notify(const struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_req_body *msg) +{ + int idx = 1; + + msg->u.conn_stat.port_number = (raw->msg[idx] & 0xf0) >> 4; + idx++; + if (idx > raw->curlen) + goto fail_len; + + memcpy(msg->u.conn_stat.guid, &raw->msg[idx], 16); + idx += 16; + if (idx > raw->curlen) + goto fail_len; + + msg->u.conn_stat.legacy_device_plug_status = (raw->msg[idx] >> 6) & 0x1; + msg->u.conn_stat.displayport_device_plug_status = (raw->msg[idx] >> 5) & 0x1; + msg->u.conn_stat.message_capability_status = (raw->msg[idx] >> 4) & 0x1; + msg->u.conn_stat.input_port = (raw->msg[idx] >> 3) & 0x1; + msg->u.conn_stat.peer_device_type = (raw->msg[idx] & 0x7); + idx++; + return true; +fail_len: + drm_dbg_kms(mgr->dev, "connection status reply parse length fail %d %d\n", + idx, raw->curlen); + return false; +} + +static bool drm_dp_sideband_parse_resource_status_notify(const struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_req_body *msg) +{ + int idx = 1; + + msg->u.resource_stat.port_number = (raw->msg[idx] & 0xf0) >> 4; + idx++; + if (idx > raw->curlen) + goto fail_len; + + memcpy(msg->u.resource_stat.guid, &raw->msg[idx], 16); + idx += 16; + if (idx > raw->curlen) + goto fail_len; + + msg->u.resource_stat.available_pbn = (raw->msg[idx] << 8) | (raw->msg[idx + 1]); + idx++; + return true; +fail_len: + drm_dbg_kms(mgr->dev, "resource status reply parse length fail %d %d\n", idx, raw->curlen); + return false; +} + +static bool drm_dp_sideband_parse_req(const struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_req_body *msg) +{ + memset(msg, 0, sizeof(*msg)); + msg->req_type = (raw->msg[0] & 0x7f); + + switch (msg->req_type) { + case DP_CONNECTION_STATUS_NOTIFY: + return drm_dp_sideband_parse_connection_status_notify(mgr, raw, msg); + case DP_RESOURCE_STATUS_NOTIFY: + return drm_dp_sideband_parse_resource_status_notify(mgr, raw, msg); + default: + drm_err(mgr->dev, "Got unknown request 0x%02x (%s)\n", + msg->req_type, drm_dp_mst_req_type_str(msg->req_type)); + return false; + } +} + +static void build_dpcd_write(struct drm_dp_sideband_msg_tx *msg, + u8 port_num, u32 offset, u8 num_bytes, u8 *bytes) +{ + struct drm_dp_sideband_msg_req_body req; + + req.req_type = DP_REMOTE_DPCD_WRITE; + req.u.dpcd_write.port_number = port_num; + req.u.dpcd_write.dpcd_address = offset; + req.u.dpcd_write.num_bytes = num_bytes; + req.u.dpcd_write.bytes = bytes; + drm_dp_encode_sideband_req(&req, msg); +} + +static void build_link_address(struct drm_dp_sideband_msg_tx *msg) +{ + struct drm_dp_sideband_msg_req_body req; + + req.req_type = DP_LINK_ADDRESS; + drm_dp_encode_sideband_req(&req, msg); +} + +static void build_clear_payload_id_table(struct drm_dp_sideband_msg_tx *msg) +{ + struct drm_dp_sideband_msg_req_body req; + + req.req_type = DP_CLEAR_PAYLOAD_ID_TABLE; + drm_dp_encode_sideband_req(&req, msg); + msg->path_msg = true; +} + +static int build_enum_path_resources(struct drm_dp_sideband_msg_tx *msg, + int port_num) +{ + struct drm_dp_sideband_msg_req_body req; + + req.req_type = DP_ENUM_PATH_RESOURCES; + req.u.port_num.port_number = port_num; + drm_dp_encode_sideband_req(&req, msg); + msg->path_msg = true; + return 0; +} + +static void build_allocate_payload(struct drm_dp_sideband_msg_tx *msg, + int port_num, + u8 vcpi, uint16_t pbn, + u8 number_sdp_streams, + u8 *sdp_stream_sink) +{ + struct drm_dp_sideband_msg_req_body req; + + memset(&req, 0, sizeof(req)); + req.req_type = DP_ALLOCATE_PAYLOAD; + req.u.allocate_payload.port_number = port_num; + req.u.allocate_payload.vcpi = vcpi; + req.u.allocate_payload.pbn = pbn; + req.u.allocate_payload.number_sdp_streams = number_sdp_streams; + memcpy(req.u.allocate_payload.sdp_stream_sink, sdp_stream_sink, + number_sdp_streams); + drm_dp_encode_sideband_req(&req, msg); + msg->path_msg = true; +} + +static void build_power_updown_phy(struct drm_dp_sideband_msg_tx *msg, + int port_num, bool power_up) +{ + struct drm_dp_sideband_msg_req_body req; + + if (power_up) + req.req_type = DP_POWER_UP_PHY; + else + req.req_type = DP_POWER_DOWN_PHY; + + req.u.port_num.port_number = port_num; + drm_dp_encode_sideband_req(&req, msg); + msg->path_msg = true; +} + +static int +build_query_stream_enc_status(struct drm_dp_sideband_msg_tx *msg, u8 stream_id, + u8 *q_id) +{ + struct drm_dp_sideband_msg_req_body req; + + req.req_type = DP_QUERY_STREAM_ENC_STATUS; + req.u.enc_status.stream_id = stream_id; + memcpy(req.u.enc_status.client_id, q_id, + sizeof(req.u.enc_status.client_id)); + req.u.enc_status.stream_event = 0; + req.u.enc_status.valid_stream_event = false; + req.u.enc_status.stream_behavior = 0; + req.u.enc_status.valid_stream_behavior = false; + + drm_dp_encode_sideband_req(&req, msg); + return 0; +} + +static int drm_dp_mst_assign_payload_id(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_vcpi *vcpi) +{ + int ret, vcpi_ret; + + mutex_lock(&mgr->payload_lock); + ret = find_first_zero_bit(&mgr->payload_mask, mgr->max_payloads + 1); + if (ret > mgr->max_payloads) { + ret = -EINVAL; + drm_dbg_kms(mgr->dev, "out of payload ids %d\n", ret); + goto out_unlock; + } + + vcpi_ret = find_first_zero_bit(&mgr->vcpi_mask, mgr->max_payloads + 1); + if (vcpi_ret > mgr->max_payloads) { + ret = -EINVAL; + drm_dbg_kms(mgr->dev, "out of vcpi ids %d\n", ret); + goto out_unlock; + } + + set_bit(ret, &mgr->payload_mask); + set_bit(vcpi_ret, &mgr->vcpi_mask); + vcpi->vcpi = vcpi_ret + 1; + mgr->proposed_vcpis[ret - 1] = vcpi; +out_unlock: + mutex_unlock(&mgr->payload_lock); + return ret; +} + +static void drm_dp_mst_put_payload_id(struct drm_dp_mst_topology_mgr *mgr, + int vcpi) +{ + int i; + + if (vcpi == 0) + return; + + mutex_lock(&mgr->payload_lock); + drm_dbg_kms(mgr->dev, "putting payload %d\n", vcpi); + clear_bit(vcpi - 1, &mgr->vcpi_mask); + + for (i = 0; i < mgr->max_payloads; i++) { + if (mgr->proposed_vcpis[i] && + mgr->proposed_vcpis[i]->vcpi == vcpi) { + mgr->proposed_vcpis[i] = NULL; + clear_bit(i + 1, &mgr->payload_mask); + } + } + mutex_unlock(&mgr->payload_lock); +} + +static bool check_txmsg_state(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_tx *txmsg) +{ + unsigned int state; + + /* + * All updates to txmsg->state are protected by mgr->qlock, and the two + * cases we check here are terminal states. For those the barriers + * provided by the wake_up/wait_event pair are enough. + */ + state = READ_ONCE(txmsg->state); + return (state == DRM_DP_SIDEBAND_TX_RX || + state == DRM_DP_SIDEBAND_TX_TIMEOUT); +} + +static int drm_dp_mst_wait_tx_reply(struct drm_dp_mst_branch *mstb, + struct drm_dp_sideband_msg_tx *txmsg) +{ + struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; + unsigned long wait_timeout = msecs_to_jiffies(4000); + unsigned long wait_expires = jiffies + wait_timeout; + int ret; + + for (;;) { + /* + * If the driver provides a way for this, change to + * poll-waiting for the MST reply interrupt if we didn't receive + * it for 50 msec. This would cater for cases where the HPD + * pulse signal got lost somewhere, even though the sink raised + * the corresponding MST interrupt correctly. One example is the + * Club 3D CAC-1557 TypeC -> DP adapter which for some reason + * filters out short pulses with a duration less than ~540 usec. + * + * The poll period is 50 msec to avoid missing an interrupt + * after the sink has cleared it (after a 110msec timeout + * since it raised the interrupt). + */ + ret = wait_event_timeout(mgr->tx_waitq, + check_txmsg_state(mgr, txmsg), + mgr->cbs->poll_hpd_irq ? + msecs_to_jiffies(50) : + wait_timeout); + + if (ret || !mgr->cbs->poll_hpd_irq || + time_after(jiffies, wait_expires)) + break; + + mgr->cbs->poll_hpd_irq(mgr); + } + + mutex_lock(&mgr->qlock); + if (ret > 0) { + if (txmsg->state == DRM_DP_SIDEBAND_TX_TIMEOUT) { + ret = -EIO; + goto out; + } + } else { + drm_dbg_kms(mgr->dev, "timedout msg send %p %d %d\n", + txmsg, txmsg->state, txmsg->seqno); + + /* dump some state */ + ret = -EIO; + + /* remove from q */ + if (txmsg->state == DRM_DP_SIDEBAND_TX_QUEUED || + txmsg->state == DRM_DP_SIDEBAND_TX_START_SEND || + txmsg->state == DRM_DP_SIDEBAND_TX_SENT) + list_del(&txmsg->next); + } +out: + if (unlikely(ret == -EIO) && drm_debug_enabled(DRM_UT_DP)) { + struct drm_printer p = drm_debug_printer(DBG_PREFIX); + + drm_dp_mst_dump_sideband_msg_tx(&p, txmsg); + } + mutex_unlock(&mgr->qlock); + + drm_dp_mst_kick_tx(mgr); + return ret; +} + +static struct drm_dp_mst_branch *drm_dp_add_mst_branch_device(u8 lct, u8 *rad) +{ + struct drm_dp_mst_branch *mstb; + + mstb = kzalloc(sizeof(*mstb), GFP_KERNEL); + if (!mstb) + return NULL; + + mstb->lct = lct; + if (lct > 1) + memcpy(mstb->rad, rad, lct / 2); + INIT_LIST_HEAD(&mstb->ports); + kref_init(&mstb->topology_kref); + kref_init(&mstb->malloc_kref); + return mstb; +} + +static void drm_dp_free_mst_branch_device(struct kref *kref) +{ + struct drm_dp_mst_branch *mstb = + container_of(kref, struct drm_dp_mst_branch, malloc_kref); + + if (mstb->port_parent) + drm_dp_mst_put_port_malloc(mstb->port_parent); + + kfree(mstb); +} + +/** + * DOC: Branch device and port refcounting + * + * Topology refcount overview + * ~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * The refcounting schemes for &struct drm_dp_mst_branch and &struct + * drm_dp_mst_port are somewhat unusual. Both ports and branch devices have + * two different kinds of refcounts: topology refcounts, and malloc refcounts. + * + * Topology refcounts are not exposed to drivers, and are handled internally + * by the DP MST helpers. The helpers use them in order to prevent the + * in-memory topology state from being changed in the middle of critical + * operations like changing the internal state of payload allocations. This + * means each branch and port will be considered to be connected to the rest + * of the topology until its topology refcount reaches zero. Additionally, + * for ports this means that their associated &struct drm_connector will stay + * registered with userspace until the port's refcount reaches 0. + * + * Malloc refcount overview + * ~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Malloc references are used to keep a &struct drm_dp_mst_port or &struct + * drm_dp_mst_branch allocated even after all of its topology references have + * been dropped, so that the driver or MST helpers can safely access each + * branch's last known state before it was disconnected from the topology. + * When the malloc refcount of a port or branch reaches 0, the memory + * allocation containing the &struct drm_dp_mst_branch or &struct + * drm_dp_mst_port respectively will be freed. + * + * For &struct drm_dp_mst_branch, malloc refcounts are not currently exposed + * to drivers. As of writing this documentation, there are no drivers that + * have a usecase for accessing &struct drm_dp_mst_branch outside of the MST + * helpers. Exposing this API to drivers in a race-free manner would take more + * tweaking of the refcounting scheme, however patches are welcome provided + * there is a legitimate driver usecase for this. + * + * Refcount relationships in a topology + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Let's take a look at why the relationship between topology and malloc + * refcounts is designed the way it is. + * + * .. kernel-figure:: dp-mst/topology-figure-1.dot + * + * An example of topology and malloc refs in a DP MST topology with two + * active payloads. Topology refcount increments are indicated by solid + * lines, and malloc refcount increments are indicated by dashed lines. + * Each starts from the branch which incremented the refcount, and ends at + * the branch to which the refcount belongs to, i.e. the arrow points the + * same way as the C pointers used to reference a structure. + * + * As you can see in the above figure, every branch increments the topology + * refcount of its children, and increments the malloc refcount of its + * parent. Additionally, every payload increments the malloc refcount of its + * assigned port by 1. + * + * So, what would happen if MSTB #3 from the above figure was unplugged from + * the system, but the driver hadn't yet removed payload #2 from port #3? The + * topology would start to look like the figure below. + * + * .. kernel-figure:: dp-mst/topology-figure-2.dot + * + * Ports and branch devices which have been released from memory are + * colored grey, and references which have been removed are colored red. + * + * Whenever a port or branch device's topology refcount reaches zero, it will + * decrement the topology refcounts of all its children, the malloc refcount + * of its parent, and finally its own malloc refcount. For MSTB #4 and port + * #4, this means they both have been disconnected from the topology and freed + * from memory. But, because payload #2 is still holding a reference to port + * #3, port #3 is removed from the topology but its &struct drm_dp_mst_port + * is still accessible from memory. This also means port #3 has not yet + * decremented the malloc refcount of MSTB #3, so its &struct + * drm_dp_mst_branch will also stay allocated in memory until port #3's + * malloc refcount reaches 0. + * + * This relationship is necessary because in order to release payload #2, we + * need to be able to figure out the last relative of port #3 that's still + * connected to the topology. In this case, we would travel up the topology as + * shown below. + * + * .. kernel-figure:: dp-mst/topology-figure-3.dot + * + * And finally, remove payload #2 by communicating with port #2 through + * sideband transactions. + */ + +/** + * drm_dp_mst_get_mstb_malloc() - Increment the malloc refcount of a branch + * device + * @mstb: The &struct drm_dp_mst_branch to increment the malloc refcount of + * + * Increments &drm_dp_mst_branch.malloc_kref. When + * &drm_dp_mst_branch.malloc_kref reaches 0, the memory allocation for @mstb + * will be released and @mstb may no longer be used. + * + * See also: drm_dp_mst_put_mstb_malloc() + */ +static void +drm_dp_mst_get_mstb_malloc(struct drm_dp_mst_branch *mstb) +{ + kref_get(&mstb->malloc_kref); + drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->malloc_kref)); +} + +/** + * drm_dp_mst_put_mstb_malloc() - Decrement the malloc refcount of a branch + * device + * @mstb: The &struct drm_dp_mst_branch to decrement the malloc refcount of + * + * Decrements &drm_dp_mst_branch.malloc_kref. When + * &drm_dp_mst_branch.malloc_kref reaches 0, the memory allocation for @mstb + * will be released and @mstb may no longer be used. + * + * See also: drm_dp_mst_get_mstb_malloc() + */ +static void +drm_dp_mst_put_mstb_malloc(struct drm_dp_mst_branch *mstb) +{ + drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->malloc_kref) - 1); + kref_put(&mstb->malloc_kref, drm_dp_free_mst_branch_device); +} + +static void drm_dp_free_mst_port(struct kref *kref) +{ + struct drm_dp_mst_port *port = + container_of(kref, struct drm_dp_mst_port, malloc_kref); + + drm_dp_mst_put_mstb_malloc(port->parent); + kfree(port); +} + +/** + * drm_dp_mst_get_port_malloc() - Increment the malloc refcount of an MST port + * @port: The &struct drm_dp_mst_port to increment the malloc refcount of + * + * Increments &drm_dp_mst_port.malloc_kref. When &drm_dp_mst_port.malloc_kref + * reaches 0, the memory allocation for @port will be released and @port may + * no longer be used. + * + * Because @port could potentially be freed at any time by the DP MST helpers + * if &drm_dp_mst_port.malloc_kref reaches 0, including during a call to this + * function, drivers that which to make use of &struct drm_dp_mst_port should + * ensure that they grab at least one main malloc reference to their MST ports + * in &drm_dp_mst_topology_cbs.add_connector. This callback is called before + * there is any chance for &drm_dp_mst_port.malloc_kref to reach 0. + * + * See also: drm_dp_mst_put_port_malloc() + */ +void +drm_dp_mst_get_port_malloc(struct drm_dp_mst_port *port) +{ + kref_get(&port->malloc_kref); + drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->malloc_kref)); +} +EXPORT_SYMBOL(drm_dp_mst_get_port_malloc); + +/** + * drm_dp_mst_put_port_malloc() - Decrement the malloc refcount of an MST port + * @port: The &struct drm_dp_mst_port to decrement the malloc refcount of + * + * Decrements &drm_dp_mst_port.malloc_kref. When &drm_dp_mst_port.malloc_kref + * reaches 0, the memory allocation for @port will be released and @port may + * no longer be used. + * + * See also: drm_dp_mst_get_port_malloc() + */ +void +drm_dp_mst_put_port_malloc(struct drm_dp_mst_port *port) +{ + drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->malloc_kref) - 1); + kref_put(&port->malloc_kref, drm_dp_free_mst_port); +} +EXPORT_SYMBOL(drm_dp_mst_put_port_malloc); + +#if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS) + +#define STACK_DEPTH 8 + +static noinline void +__topology_ref_save(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_topology_ref_history *history, + enum drm_dp_mst_topology_ref_type type) +{ + struct drm_dp_mst_topology_ref_entry *entry = NULL; + depot_stack_handle_t backtrace; + ulong stack_entries[STACK_DEPTH]; + uint n; + int i; + + n = stack_trace_save(stack_entries, ARRAY_SIZE(stack_entries), 1); + backtrace = stack_depot_save(stack_entries, n, GFP_KERNEL); + if (!backtrace) + return; + + /* Try to find an existing entry for this backtrace */ + for (i = 0; i < history->len; i++) { + if (history->entries[i].backtrace == backtrace) { + entry = &history->entries[i]; + break; + } + } + + /* Otherwise add one */ + if (!entry) { + struct drm_dp_mst_topology_ref_entry *new; + int new_len = history->len + 1; + + new = krealloc(history->entries, sizeof(*new) * new_len, + GFP_KERNEL); + if (!new) + return; + + entry = &new[history->len]; + history->len = new_len; + history->entries = new; + + entry->backtrace = backtrace; + entry->type = type; + entry->count = 0; + } + entry->count++; + entry->ts_nsec = ktime_get_ns(); +} + +static int +topology_ref_history_cmp(const void *a, const void *b) +{ + const struct drm_dp_mst_topology_ref_entry *entry_a = a, *entry_b = b; + + if (entry_a->ts_nsec > entry_b->ts_nsec) + return 1; + else if (entry_a->ts_nsec < entry_b->ts_nsec) + return -1; + else + return 0; +} + +static inline const char * +topology_ref_type_to_str(enum drm_dp_mst_topology_ref_type type) +{ + if (type == DRM_DP_MST_TOPOLOGY_REF_GET) + return "get"; + else + return "put"; +} + +static void +__dump_topology_ref_history(struct drm_dp_mst_topology_ref_history *history, + void *ptr, const char *type_str) +{ + struct drm_printer p = drm_debug_printer(DBG_PREFIX); + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + int i; + + if (!buf) + return; + + if (!history->len) + goto out; + + /* First, sort the list so that it goes from oldest to newest + * reference entry + */ + sort(history->entries, history->len, sizeof(*history->entries), + topology_ref_history_cmp, NULL); + + drm_printf(&p, "%s (%p) topology count reached 0, dumping history:\n", + type_str, ptr); + + for (i = 0; i < history->len; i++) { + const struct drm_dp_mst_topology_ref_entry *entry = + &history->entries[i]; + u64 ts_nsec = entry->ts_nsec; + u32 rem_nsec = do_div(ts_nsec, 1000000000); + + stack_depot_snprint(entry->backtrace, buf, PAGE_SIZE, 4); + + drm_printf(&p, " %d %ss (last at %5llu.%06u):\n%s", + entry->count, + topology_ref_type_to_str(entry->type), + ts_nsec, rem_nsec / 1000, buf); + } + + /* Now free the history, since this is the only time we expose it */ + kfree(history->entries); +out: + kfree(buf); +} + +static __always_inline void +drm_dp_mst_dump_mstb_topology_history(struct drm_dp_mst_branch *mstb) +{ + __dump_topology_ref_history(&mstb->topology_ref_history, mstb, + "MSTB"); +} + +static __always_inline void +drm_dp_mst_dump_port_topology_history(struct drm_dp_mst_port *port) +{ + __dump_topology_ref_history(&port->topology_ref_history, port, + "Port"); +} + +static __always_inline void +save_mstb_topology_ref(struct drm_dp_mst_branch *mstb, + enum drm_dp_mst_topology_ref_type type) +{ + __topology_ref_save(mstb->mgr, &mstb->topology_ref_history, type); +} + +static __always_inline void +save_port_topology_ref(struct drm_dp_mst_port *port, + enum drm_dp_mst_topology_ref_type type) +{ + __topology_ref_save(port->mgr, &port->topology_ref_history, type); +} + +static inline void +topology_ref_history_lock(struct drm_dp_mst_topology_mgr *mgr) +{ + mutex_lock(&mgr->topology_ref_history_lock); +} + +static inline void +topology_ref_history_unlock(struct drm_dp_mst_topology_mgr *mgr) +{ + mutex_unlock(&mgr->topology_ref_history_lock); +} +#else +static inline void +topology_ref_history_lock(struct drm_dp_mst_topology_mgr *mgr) {} +static inline void +topology_ref_history_unlock(struct drm_dp_mst_topology_mgr *mgr) {} +static inline void +drm_dp_mst_dump_mstb_topology_history(struct drm_dp_mst_branch *mstb) {} +static inline void +drm_dp_mst_dump_port_topology_history(struct drm_dp_mst_port *port) {} +#define save_mstb_topology_ref(mstb, type) +#define save_port_topology_ref(port, type) +#endif + +static void drm_dp_destroy_mst_branch_device(struct kref *kref) +{ + struct drm_dp_mst_branch *mstb = + container_of(kref, struct drm_dp_mst_branch, topology_kref); + struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; + + drm_dp_mst_dump_mstb_topology_history(mstb); + + INIT_LIST_HEAD(&mstb->destroy_next); + + /* + * This can get called under mgr->mutex, so we need to perform the + * actual destruction of the mstb in another worker + */ + mutex_lock(&mgr->delayed_destroy_lock); + list_add(&mstb->destroy_next, &mgr->destroy_branch_device_list); + mutex_unlock(&mgr->delayed_destroy_lock); + queue_work(mgr->delayed_destroy_wq, &mgr->delayed_destroy_work); +} + +/** + * drm_dp_mst_topology_try_get_mstb() - Increment the topology refcount of a + * branch device unless it's zero + * @mstb: &struct drm_dp_mst_branch to increment the topology refcount of + * + * Attempts to grab a topology reference to @mstb, if it hasn't yet been + * removed from the topology (e.g. &drm_dp_mst_branch.topology_kref has + * reached 0). Holding a topology reference implies that a malloc reference + * will be held to @mstb as long as the user holds the topology reference. + * + * Care should be taken to ensure that the user has at least one malloc + * reference to @mstb. If you already have a topology reference to @mstb, you + * should use drm_dp_mst_topology_get_mstb() instead. + * + * See also: + * drm_dp_mst_topology_get_mstb() + * drm_dp_mst_topology_put_mstb() + * + * Returns: + * * 1: A topology reference was grabbed successfully + * * 0: @port is no longer in the topology, no reference was grabbed + */ +static int __must_check +drm_dp_mst_topology_try_get_mstb(struct drm_dp_mst_branch *mstb) +{ + int ret; + + topology_ref_history_lock(mstb->mgr); + ret = kref_get_unless_zero(&mstb->topology_kref); + if (ret) { + drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->topology_kref)); + save_mstb_topology_ref(mstb, DRM_DP_MST_TOPOLOGY_REF_GET); + } + + topology_ref_history_unlock(mstb->mgr); + + return ret; +} + +/** + * drm_dp_mst_topology_get_mstb() - Increment the topology refcount of a + * branch device + * @mstb: The &struct drm_dp_mst_branch to increment the topology refcount of + * + * Increments &drm_dp_mst_branch.topology_refcount without checking whether or + * not it's already reached 0. This is only valid to use in scenarios where + * you are already guaranteed to have at least one active topology reference + * to @mstb. Otherwise, drm_dp_mst_topology_try_get_mstb() must be used. + * + * See also: + * drm_dp_mst_topology_try_get_mstb() + * drm_dp_mst_topology_put_mstb() + */ +static void drm_dp_mst_topology_get_mstb(struct drm_dp_mst_branch *mstb) +{ + topology_ref_history_lock(mstb->mgr); + + save_mstb_topology_ref(mstb, DRM_DP_MST_TOPOLOGY_REF_GET); + WARN_ON(kref_read(&mstb->topology_kref) == 0); + kref_get(&mstb->topology_kref); + drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->topology_kref)); + + topology_ref_history_unlock(mstb->mgr); +} + +/** + * drm_dp_mst_topology_put_mstb() - release a topology reference to a branch + * device + * @mstb: The &struct drm_dp_mst_branch to release the topology reference from + * + * Releases a topology reference from @mstb by decrementing + * &drm_dp_mst_branch.topology_kref. + * + * See also: + * drm_dp_mst_topology_try_get_mstb() + * drm_dp_mst_topology_get_mstb() + */ +static void +drm_dp_mst_topology_put_mstb(struct drm_dp_mst_branch *mstb) +{ + topology_ref_history_lock(mstb->mgr); + + drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->topology_kref) - 1); + save_mstb_topology_ref(mstb, DRM_DP_MST_TOPOLOGY_REF_PUT); + + topology_ref_history_unlock(mstb->mgr); + kref_put(&mstb->topology_kref, drm_dp_destroy_mst_branch_device); +} + +static void drm_dp_destroy_port(struct kref *kref) +{ + struct drm_dp_mst_port *port = + container_of(kref, struct drm_dp_mst_port, topology_kref); + struct drm_dp_mst_topology_mgr *mgr = port->mgr; + + drm_dp_mst_dump_port_topology_history(port); + + /* There's nothing that needs locking to destroy an input port yet */ + if (port->input) { + drm_dp_mst_put_port_malloc(port); + return; + } + + kfree(port->cached_edid); + + /* + * we can't destroy the connector here, as we might be holding the + * mode_config.mutex from an EDID retrieval + */ + mutex_lock(&mgr->delayed_destroy_lock); + list_add(&port->next, &mgr->destroy_port_list); + mutex_unlock(&mgr->delayed_destroy_lock); + queue_work(mgr->delayed_destroy_wq, &mgr->delayed_destroy_work); +} + +/** + * drm_dp_mst_topology_try_get_port() - Increment the topology refcount of a + * port unless it's zero + * @port: &struct drm_dp_mst_port to increment the topology refcount of + * + * Attempts to grab a topology reference to @port, if it hasn't yet been + * removed from the topology (e.g. &drm_dp_mst_port.topology_kref has reached + * 0). Holding a topology reference implies that a malloc reference will be + * held to @port as long as the user holds the topology reference. + * + * Care should be taken to ensure that the user has at least one malloc + * reference to @port. If you already have a topology reference to @port, you + * should use drm_dp_mst_topology_get_port() instead. + * + * See also: + * drm_dp_mst_topology_get_port() + * drm_dp_mst_topology_put_port() + * + * Returns: + * * 1: A topology reference was grabbed successfully + * * 0: @port is no longer in the topology, no reference was grabbed + */ +static int __must_check +drm_dp_mst_topology_try_get_port(struct drm_dp_mst_port *port) +{ + int ret; + + topology_ref_history_lock(port->mgr); + ret = kref_get_unless_zero(&port->topology_kref); + if (ret) { + drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->topology_kref)); + save_port_topology_ref(port, DRM_DP_MST_TOPOLOGY_REF_GET); + } + + topology_ref_history_unlock(port->mgr); + return ret; +} + +/** + * drm_dp_mst_topology_get_port() - Increment the topology refcount of a port + * @port: The &struct drm_dp_mst_port to increment the topology refcount of + * + * Increments &drm_dp_mst_port.topology_refcount without checking whether or + * not it's already reached 0. This is only valid to use in scenarios where + * you are already guaranteed to have at least one active topology reference + * to @port. Otherwise, drm_dp_mst_topology_try_get_port() must be used. + * + * See also: + * drm_dp_mst_topology_try_get_port() + * drm_dp_mst_topology_put_port() + */ +static void drm_dp_mst_topology_get_port(struct drm_dp_mst_port *port) +{ + topology_ref_history_lock(port->mgr); + + WARN_ON(kref_read(&port->topology_kref) == 0); + kref_get(&port->topology_kref); + drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->topology_kref)); + save_port_topology_ref(port, DRM_DP_MST_TOPOLOGY_REF_GET); + + topology_ref_history_unlock(port->mgr); +} + +/** + * drm_dp_mst_topology_put_port() - release a topology reference to a port + * @port: The &struct drm_dp_mst_port to release the topology reference from + * + * Releases a topology reference from @port by decrementing + * &drm_dp_mst_port.topology_kref. + * + * See also: + * drm_dp_mst_topology_try_get_port() + * drm_dp_mst_topology_get_port() + */ +static void drm_dp_mst_topology_put_port(struct drm_dp_mst_port *port) +{ + topology_ref_history_lock(port->mgr); + + drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->topology_kref) - 1); + save_port_topology_ref(port, DRM_DP_MST_TOPOLOGY_REF_PUT); + + topology_ref_history_unlock(port->mgr); + kref_put(&port->topology_kref, drm_dp_destroy_port); +} + +static struct drm_dp_mst_branch * +drm_dp_mst_topology_get_mstb_validated_locked(struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_branch *to_find) +{ + struct drm_dp_mst_port *port; + struct drm_dp_mst_branch *rmstb; + + if (to_find == mstb) + return mstb; + + list_for_each_entry(port, &mstb->ports, next) { + if (port->mstb) { + rmstb = drm_dp_mst_topology_get_mstb_validated_locked( + port->mstb, to_find); + if (rmstb) + return rmstb; + } + } + return NULL; +} + +static struct drm_dp_mst_branch * +drm_dp_mst_topology_get_mstb_validated(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb) +{ + struct drm_dp_mst_branch *rmstb = NULL; + + mutex_lock(&mgr->lock); + if (mgr->mst_primary) { + rmstb = drm_dp_mst_topology_get_mstb_validated_locked( + mgr->mst_primary, mstb); + + if (rmstb && !drm_dp_mst_topology_try_get_mstb(rmstb)) + rmstb = NULL; + } + mutex_unlock(&mgr->lock); + return rmstb; +} + +static struct drm_dp_mst_port * +drm_dp_mst_topology_get_port_validated_locked(struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_port *to_find) +{ + struct drm_dp_mst_port *port, *mport; + + list_for_each_entry(port, &mstb->ports, next) { + if (port == to_find) + return port; + + if (port->mstb) { + mport = drm_dp_mst_topology_get_port_validated_locked( + port->mstb, to_find); + if (mport) + return mport; + } + } + return NULL; +} + +static struct drm_dp_mst_port * +drm_dp_mst_topology_get_port_validated(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port) +{ + struct drm_dp_mst_port *rport = NULL; + + mutex_lock(&mgr->lock); + if (mgr->mst_primary) { + rport = drm_dp_mst_topology_get_port_validated_locked( + mgr->mst_primary, port); + + if (rport && !drm_dp_mst_topology_try_get_port(rport)) + rport = NULL; + } + mutex_unlock(&mgr->lock); + return rport; +} + +static struct drm_dp_mst_port *drm_dp_get_port(struct drm_dp_mst_branch *mstb, u8 port_num) +{ + struct drm_dp_mst_port *port; + int ret; + + list_for_each_entry(port, &mstb->ports, next) { + if (port->port_num == port_num) { + ret = drm_dp_mst_topology_try_get_port(port); + return ret ? port : NULL; + } + } + + return NULL; +} + +/* + * calculate a new RAD for this MST branch device + * if parent has an LCT of 2 then it has 1 nibble of RAD, + * if parent has an LCT of 3 then it has 2 nibbles of RAD, + */ +static u8 drm_dp_calculate_rad(struct drm_dp_mst_port *port, + u8 *rad) +{ + int parent_lct = port->parent->lct; + int shift = 4; + int idx = (parent_lct - 1) / 2; + + if (parent_lct > 1) { + memcpy(rad, port->parent->rad, idx + 1); + shift = (parent_lct % 2) ? 4 : 0; + } else + rad[0] = 0; + + rad[idx] |= port->port_num << shift; + return parent_lct + 1; +} + +static bool drm_dp_mst_is_end_device(u8 pdt, bool mcs) +{ + switch (pdt) { + case DP_PEER_DEVICE_DP_LEGACY_CONV: + case DP_PEER_DEVICE_SST_SINK: + return true; + case DP_PEER_DEVICE_MST_BRANCHING: + /* For sst branch device */ + if (!mcs) + return true; + + return false; + } + return true; +} + +static int +drm_dp_port_set_pdt(struct drm_dp_mst_port *port, u8 new_pdt, + bool new_mcs) +{ + struct drm_dp_mst_topology_mgr *mgr = port->mgr; + struct drm_dp_mst_branch *mstb; + u8 rad[8], lct; + int ret = 0; + + if (port->pdt == new_pdt && port->mcs == new_mcs) + return 0; + + /* Teardown the old pdt, if there is one */ + if (port->pdt != DP_PEER_DEVICE_NONE) { + if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) { + /* + * If the new PDT would also have an i2c bus, + * don't bother with reregistering it + */ + if (new_pdt != DP_PEER_DEVICE_NONE && + drm_dp_mst_is_end_device(new_pdt, new_mcs)) { + port->pdt = new_pdt; + port->mcs = new_mcs; + return 0; + } + + /* remove i2c over sideband */ + drm_dp_mst_unregister_i2c_bus(port); + } else { + mutex_lock(&mgr->lock); + drm_dp_mst_topology_put_mstb(port->mstb); + port->mstb = NULL; + mutex_unlock(&mgr->lock); + } + } + + port->pdt = new_pdt; + port->mcs = new_mcs; + + if (port->pdt != DP_PEER_DEVICE_NONE) { + if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) { + /* add i2c over sideband */ + ret = drm_dp_mst_register_i2c_bus(port); + } else { + lct = drm_dp_calculate_rad(port, rad); + mstb = drm_dp_add_mst_branch_device(lct, rad); + if (!mstb) { + ret = -ENOMEM; + drm_err(mgr->dev, "Failed to create MSTB for port %p", port); + goto out; + } + + mutex_lock(&mgr->lock); + port->mstb = mstb; + mstb->mgr = port->mgr; + mstb->port_parent = port; + + /* + * Make sure this port's memory allocation stays + * around until its child MSTB releases it + */ + drm_dp_mst_get_port_malloc(port); + mutex_unlock(&mgr->lock); + + /* And make sure we send a link address for this */ + ret = 1; + } + } + +out: + if (ret < 0) + port->pdt = DP_PEER_DEVICE_NONE; + return ret; +} + +/** + * drm_dp_mst_dpcd_read() - read a series of bytes from the DPCD via sideband + * @aux: Fake sideband AUX CH + * @offset: address of the (first) register to read + * @buffer: buffer to store the register values + * @size: number of bytes in @buffer + * + * Performs the same functionality for remote devices via + * sideband messaging as drm_dp_dpcd_read() does for local + * devices via actual AUX CH. + * + * Return: Number of bytes read, or negative error code on failure. + */ +ssize_t drm_dp_mst_dpcd_read(struct drm_dp_aux *aux, + unsigned int offset, void *buffer, size_t size) +{ + struct drm_dp_mst_port *port = container_of(aux, struct drm_dp_mst_port, + aux); + + return drm_dp_send_dpcd_read(port->mgr, port, + offset, size, buffer); +} + +/** + * drm_dp_mst_dpcd_write() - write a series of bytes to the DPCD via sideband + * @aux: Fake sideband AUX CH + * @offset: address of the (first) register to write + * @buffer: buffer containing the values to write + * @size: number of bytes in @buffer + * + * Performs the same functionality for remote devices via + * sideband messaging as drm_dp_dpcd_write() does for local + * devices via actual AUX CH. + * + * Return: number of bytes written on success, negative error code on failure. + */ +ssize_t drm_dp_mst_dpcd_write(struct drm_dp_aux *aux, + unsigned int offset, void *buffer, size_t size) +{ + struct drm_dp_mst_port *port = container_of(aux, struct drm_dp_mst_port, + aux); + + return drm_dp_send_dpcd_write(port->mgr, port, + offset, size, buffer); +} + +static int drm_dp_check_mstb_guid(struct drm_dp_mst_branch *mstb, u8 *guid) +{ + int ret = 0; + + memcpy(mstb->guid, guid, 16); + + if (!drm_dp_validate_guid(mstb->mgr, mstb->guid)) { + if (mstb->port_parent) { + ret = drm_dp_send_dpcd_write(mstb->mgr, + mstb->port_parent, + DP_GUID, 16, mstb->guid); + } else { + ret = drm_dp_dpcd_write(mstb->mgr->aux, + DP_GUID, mstb->guid, 16); + } + } + + if (ret < 16 && ret > 0) + return -EPROTO; + + return ret == 16 ? 0 : ret; +} + +static void build_mst_prop_path(const struct drm_dp_mst_branch *mstb, + int pnum, + char *proppath, + size_t proppath_size) +{ + int i; + char temp[8]; + + snprintf(proppath, proppath_size, "mst:%d", mstb->mgr->conn_base_id); + for (i = 0; i < (mstb->lct - 1); i++) { + int shift = (i % 2) ? 0 : 4; + int port_num = (mstb->rad[i / 2] >> shift) & 0xf; + + snprintf(temp, sizeof(temp), "-%d", port_num); + strlcat(proppath, temp, proppath_size); + } + snprintf(temp, sizeof(temp), "-%d", pnum); + strlcat(proppath, temp, proppath_size); +} + +/** + * drm_dp_mst_connector_late_register() - Late MST connector registration + * @connector: The MST connector + * @port: The MST port for this connector + * + * Helper to register the remote aux device for this MST port. Drivers should + * call this from their mst connector's late_register hook to enable MST aux + * devices. + * + * Return: 0 on success, negative error code on failure. + */ +int drm_dp_mst_connector_late_register(struct drm_connector *connector, + struct drm_dp_mst_port *port) +{ + drm_dbg_kms(port->mgr->dev, "registering %s remote bus for %s\n", + port->aux.name, connector->kdev->kobj.name); + + port->aux.dev = connector->kdev; + return drm_dp_aux_register_devnode(&port->aux); +} +EXPORT_SYMBOL(drm_dp_mst_connector_late_register); + +/** + * drm_dp_mst_connector_early_unregister() - Early MST connector unregistration + * @connector: The MST connector + * @port: The MST port for this connector + * + * Helper to unregister the remote aux device for this MST port, registered by + * drm_dp_mst_connector_late_register(). Drivers should call this from their mst + * connector's early_unregister hook. + */ +void drm_dp_mst_connector_early_unregister(struct drm_connector *connector, + struct drm_dp_mst_port *port) +{ + drm_dbg_kms(port->mgr->dev, "unregistering %s remote bus for %s\n", + port->aux.name, connector->kdev->kobj.name); + drm_dp_aux_unregister_devnode(&port->aux); +} +EXPORT_SYMBOL(drm_dp_mst_connector_early_unregister); + +static void +drm_dp_mst_port_add_connector(struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_port *port) +{ + struct drm_dp_mst_topology_mgr *mgr = port->mgr; + char proppath[255]; + int ret; + + build_mst_prop_path(mstb, port->port_num, proppath, sizeof(proppath)); + port->connector = mgr->cbs->add_connector(mgr, port, proppath); + if (!port->connector) { + ret = -ENOMEM; + goto error; + } + + if (port->pdt != DP_PEER_DEVICE_NONE && + drm_dp_mst_is_end_device(port->pdt, port->mcs) && + port->port_num >= DP_MST_LOGICAL_PORT_0) + port->cached_edid = drm_get_edid(port->connector, + &port->aux.ddc); + + drm_connector_register(port->connector); + return; + +error: + drm_err(mgr->dev, "Failed to create connector for port %p: %d\n", port, ret); +} + +/* + * Drop a topology reference, and unlink the port from the in-memory topology + * layout + */ +static void +drm_dp_mst_topology_unlink_port(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port) +{ + mutex_lock(&mgr->lock); + port->parent->num_ports--; + list_del(&port->next); + mutex_unlock(&mgr->lock); + drm_dp_mst_topology_put_port(port); +} + +static struct drm_dp_mst_port * +drm_dp_mst_add_port(struct drm_device *dev, + struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb, u8 port_number) +{ + struct drm_dp_mst_port *port = kzalloc(sizeof(*port), GFP_KERNEL); + + if (!port) + return NULL; + + kref_init(&port->topology_kref); + kref_init(&port->malloc_kref); + port->parent = mstb; + port->port_num = port_number; + port->mgr = mgr; + port->aux.name = "DPMST"; + port->aux.dev = dev->dev; + port->aux.is_remote = true; + + /* initialize the MST downstream port's AUX crc work queue */ + port->aux.drm_dev = dev; + drm_dp_remote_aux_init(&port->aux); + + /* + * Make sure the memory allocation for our parent branch stays + * around until our own memory allocation is released + */ + drm_dp_mst_get_mstb_malloc(mstb); + + return port; +} + +static int +drm_dp_mst_handle_link_address_port(struct drm_dp_mst_branch *mstb, + struct drm_device *dev, + struct drm_dp_link_addr_reply_port *port_msg) +{ + struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; + struct drm_dp_mst_port *port; + int old_ddps = 0, ret; + u8 new_pdt = DP_PEER_DEVICE_NONE; + bool new_mcs = 0; + bool created = false, send_link_addr = false, changed = false; + + port = drm_dp_get_port(mstb, port_msg->port_number); + if (!port) { + port = drm_dp_mst_add_port(dev, mgr, mstb, + port_msg->port_number); + if (!port) + return -ENOMEM; + created = true; + changed = true; + } else if (!port->input && port_msg->input_port && port->connector) { + /* Since port->connector can't be changed here, we create a + * new port if input_port changes from 0 to 1 + */ + drm_dp_mst_topology_unlink_port(mgr, port); + drm_dp_mst_topology_put_port(port); + port = drm_dp_mst_add_port(dev, mgr, mstb, + port_msg->port_number); + if (!port) + return -ENOMEM; + changed = true; + created = true; + } else if (port->input && !port_msg->input_port) { + changed = true; + } else if (port->connector) { + /* We're updating a port that's exposed to userspace, so do it + * under lock + */ + drm_modeset_lock(&mgr->base.lock, NULL); + + old_ddps = port->ddps; + changed = port->ddps != port_msg->ddps || + (port->ddps && + (port->ldps != port_msg->legacy_device_plug_status || + port->dpcd_rev != port_msg->dpcd_revision || + port->mcs != port_msg->mcs || + port->pdt != port_msg->peer_device_type || + port->num_sdp_stream_sinks != + port_msg->num_sdp_stream_sinks)); + } + + port->input = port_msg->input_port; + if (!port->input) + new_pdt = port_msg->peer_device_type; + new_mcs = port_msg->mcs; + port->ddps = port_msg->ddps; + port->ldps = port_msg->legacy_device_plug_status; + port->dpcd_rev = port_msg->dpcd_revision; + port->num_sdp_streams = port_msg->num_sdp_streams; + port->num_sdp_stream_sinks = port_msg->num_sdp_stream_sinks; + + /* manage mstb port lists with mgr lock - take a reference + for this list */ + if (created) { + mutex_lock(&mgr->lock); + drm_dp_mst_topology_get_port(port); + list_add(&port->next, &mstb->ports); + mstb->num_ports++; + mutex_unlock(&mgr->lock); + } + + /* + * Reprobe PBN caps on both hotplug, and when re-probing the link + * for our parent mstb + */ + if (old_ddps != port->ddps || !created) { + if (port->ddps && !port->input) { + ret = drm_dp_send_enum_path_resources(mgr, mstb, + port); + if (ret == 1) + changed = true; + } else { + port->full_pbn = 0; + } + } + + ret = drm_dp_port_set_pdt(port, new_pdt, new_mcs); + if (ret == 1) { + send_link_addr = true; + } else if (ret < 0) { + drm_err(dev, "Failed to change PDT on port %p: %d\n", port, ret); + goto fail; + } + + /* + * If this port wasn't just created, then we're reprobing because + * we're coming out of suspend. In this case, always resend the link + * address if there's an MSTB on this port + */ + if (!created && port->pdt == DP_PEER_DEVICE_MST_BRANCHING && + port->mcs) + send_link_addr = true; + + if (port->connector) + drm_modeset_unlock(&mgr->base.lock); + else if (!port->input) + drm_dp_mst_port_add_connector(mstb, port); + + if (send_link_addr && port->mstb) { + ret = drm_dp_send_link_address(mgr, port->mstb); + if (ret == 1) /* MSTB below us changed */ + changed = true; + else if (ret < 0) + goto fail_put; + } + + /* put reference to this port */ + drm_dp_mst_topology_put_port(port); + return changed; + +fail: + drm_dp_mst_topology_unlink_port(mgr, port); + if (port->connector) + drm_modeset_unlock(&mgr->base.lock); +fail_put: + drm_dp_mst_topology_put_port(port); + return ret; +} + +static void +drm_dp_mst_handle_conn_stat(struct drm_dp_mst_branch *mstb, + struct drm_dp_connection_status_notify *conn_stat) +{ + struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; + struct drm_dp_mst_port *port; + int old_ddps, ret; + u8 new_pdt; + bool new_mcs; + bool dowork = false, create_connector = false; + + port = drm_dp_get_port(mstb, conn_stat->port_number); + if (!port) + return; + + if (port->connector) { + if (!port->input && conn_stat->input_port) { + /* + * We can't remove a connector from an already exposed + * port, so just throw the port out and make sure we + * reprobe the link address of it's parent MSTB + */ + drm_dp_mst_topology_unlink_port(mgr, port); + mstb->link_address_sent = false; + dowork = true; + goto out; + } + + /* Locking is only needed if the port's exposed to userspace */ + drm_modeset_lock(&mgr->base.lock, NULL); + } else if (port->input && !conn_stat->input_port) { + create_connector = true; + /* Reprobe link address so we get num_sdp_streams */ + mstb->link_address_sent = false; + dowork = true; + } + + old_ddps = port->ddps; + port->input = conn_stat->input_port; + port->ldps = conn_stat->legacy_device_plug_status; + port->ddps = conn_stat->displayport_device_plug_status; + + if (old_ddps != port->ddps) { + if (port->ddps && !port->input) + drm_dp_send_enum_path_resources(mgr, mstb, port); + else + port->full_pbn = 0; + } + + new_pdt = port->input ? DP_PEER_DEVICE_NONE : conn_stat->peer_device_type; + new_mcs = conn_stat->message_capability_status; + ret = drm_dp_port_set_pdt(port, new_pdt, new_mcs); + if (ret == 1) { + dowork = true; + } else if (ret < 0) { + drm_err(mgr->dev, "Failed to change PDT for port %p: %d\n", port, ret); + dowork = false; + } + + if (port->connector) + drm_modeset_unlock(&mgr->base.lock); + else if (create_connector) + drm_dp_mst_port_add_connector(mstb, port); + +out: + drm_dp_mst_topology_put_port(port); + if (dowork) + queue_work(system_long_wq, &mstb->mgr->work); +} + +static struct drm_dp_mst_branch *drm_dp_get_mst_branch_device(struct drm_dp_mst_topology_mgr *mgr, + u8 lct, u8 *rad) +{ + struct drm_dp_mst_branch *mstb; + struct drm_dp_mst_port *port; + int i, ret; + /* find the port by iterating down */ + + mutex_lock(&mgr->lock); + mstb = mgr->mst_primary; + + if (!mstb) + goto out; + + for (i = 0; i < lct - 1; i++) { + int shift = (i % 2) ? 0 : 4; + int port_num = (rad[i / 2] >> shift) & 0xf; + + list_for_each_entry(port, &mstb->ports, next) { + if (port->port_num == port_num) { + mstb = port->mstb; + if (!mstb) { + drm_err(mgr->dev, + "failed to lookup MSTB with lct %d, rad %02x\n", + lct, rad[0]); + goto out; + } + + break; + } + } + } + ret = drm_dp_mst_topology_try_get_mstb(mstb); + if (!ret) + mstb = NULL; +out: + mutex_unlock(&mgr->lock); + return mstb; +} + +static struct drm_dp_mst_branch *get_mst_branch_device_by_guid_helper( + struct drm_dp_mst_branch *mstb, + const uint8_t *guid) +{ + struct drm_dp_mst_branch *found_mstb; + struct drm_dp_mst_port *port; + + if (memcmp(mstb->guid, guid, 16) == 0) + return mstb; + + + list_for_each_entry(port, &mstb->ports, next) { + if (!port->mstb) + continue; + + found_mstb = get_mst_branch_device_by_guid_helper(port->mstb, guid); + + if (found_mstb) + return found_mstb; + } + + return NULL; +} + +static struct drm_dp_mst_branch * +drm_dp_get_mst_branch_device_by_guid(struct drm_dp_mst_topology_mgr *mgr, + const uint8_t *guid) +{ + struct drm_dp_mst_branch *mstb; + int ret; + + /* find the port by iterating down */ + mutex_lock(&mgr->lock); + + mstb = get_mst_branch_device_by_guid_helper(mgr->mst_primary, guid); + if (mstb) { + ret = drm_dp_mst_topology_try_get_mstb(mstb); + if (!ret) + mstb = NULL; + } + + mutex_unlock(&mgr->lock); + return mstb; +} + +static int drm_dp_check_and_send_link_address(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb) +{ + struct drm_dp_mst_port *port; + int ret; + bool changed = false; + + if (!mstb->link_address_sent) { + ret = drm_dp_send_link_address(mgr, mstb); + if (ret == 1) + changed = true; + else if (ret < 0) + return ret; + } + + list_for_each_entry(port, &mstb->ports, next) { + struct drm_dp_mst_branch *mstb_child = NULL; + + if (port->input || !port->ddps) + continue; + + if (port->mstb) + mstb_child = drm_dp_mst_topology_get_mstb_validated( + mgr, port->mstb); + + if (mstb_child) { + ret = drm_dp_check_and_send_link_address(mgr, + mstb_child); + drm_dp_mst_topology_put_mstb(mstb_child); + if (ret == 1) + changed = true; + else if (ret < 0) + return ret; + } + } + + return changed; +} + +static void drm_dp_mst_link_probe_work(struct work_struct *work) +{ + struct drm_dp_mst_topology_mgr *mgr = + container_of(work, struct drm_dp_mst_topology_mgr, work); + struct drm_device *dev = mgr->dev; + struct drm_dp_mst_branch *mstb; + int ret; + bool clear_payload_id_table; + + mutex_lock(&mgr->probe_lock); + + mutex_lock(&mgr->lock); + clear_payload_id_table = !mgr->payload_id_table_cleared; + mgr->payload_id_table_cleared = true; + + mstb = mgr->mst_primary; + if (mstb) { + ret = drm_dp_mst_topology_try_get_mstb(mstb); + if (!ret) + mstb = NULL; + } + mutex_unlock(&mgr->lock); + if (!mstb) { + mutex_unlock(&mgr->probe_lock); + return; + } + + /* + * Certain branch devices seem to incorrectly report an available_pbn + * of 0 on downstream sinks, even after clearing the + * DP_PAYLOAD_ALLOCATE_* registers in + * drm_dp_mst_topology_mgr_set_mst(). Namely, the CableMatters USB-C + * 2x DP hub. Sending a CLEAR_PAYLOAD_ID_TABLE message seems to make + * things work again. + */ + if (clear_payload_id_table) { + drm_dbg_kms(dev, "Clearing payload ID table\n"); + drm_dp_send_clear_payload_id_table(mgr, mstb); + } + + ret = drm_dp_check_and_send_link_address(mgr, mstb); + drm_dp_mst_topology_put_mstb(mstb); + + mutex_unlock(&mgr->probe_lock); + if (ret > 0) + drm_kms_helper_hotplug_event(dev); +} + +static bool drm_dp_validate_guid(struct drm_dp_mst_topology_mgr *mgr, + u8 *guid) +{ + u64 salt; + + if (memchr_inv(guid, 0, 16)) + return true; + + salt = get_jiffies_64(); + + memcpy(&guid[0], &salt, sizeof(u64)); + memcpy(&guid[8], &salt, sizeof(u64)); + + return false; +} + +static void build_dpcd_read(struct drm_dp_sideband_msg_tx *msg, + u8 port_num, u32 offset, u8 num_bytes) +{ + struct drm_dp_sideband_msg_req_body req; + + req.req_type = DP_REMOTE_DPCD_READ; + req.u.dpcd_read.port_number = port_num; + req.u.dpcd_read.dpcd_address = offset; + req.u.dpcd_read.num_bytes = num_bytes; + drm_dp_encode_sideband_req(&req, msg); +} + +static int drm_dp_send_sideband_msg(struct drm_dp_mst_topology_mgr *mgr, + bool up, u8 *msg, int len) +{ + int ret; + int regbase = up ? DP_SIDEBAND_MSG_UP_REP_BASE : DP_SIDEBAND_MSG_DOWN_REQ_BASE; + int tosend, total, offset; + int retries = 0; + +retry: + total = len; + offset = 0; + do { + tosend = min3(mgr->max_dpcd_transaction_bytes, 16, total); + + ret = drm_dp_dpcd_write(mgr->aux, regbase + offset, + &msg[offset], + tosend); + if (ret != tosend) { + if (ret == -EIO && retries < 5) { + retries++; + goto retry; + } + drm_dbg_kms(mgr->dev, "failed to dpcd write %d %d\n", tosend, ret); + + return -EIO; + } + offset += tosend; + total -= tosend; + } while (total > 0); + return 0; +} + +static int set_hdr_from_dst_qlock(struct drm_dp_sideband_msg_hdr *hdr, + struct drm_dp_sideband_msg_tx *txmsg) +{ + struct drm_dp_mst_branch *mstb = txmsg->dst; + u8 req_type; + + req_type = txmsg->msg[0] & 0x7f; + if (req_type == DP_CONNECTION_STATUS_NOTIFY || + req_type == DP_RESOURCE_STATUS_NOTIFY || + req_type == DP_CLEAR_PAYLOAD_ID_TABLE) + hdr->broadcast = 1; + else + hdr->broadcast = 0; + hdr->path_msg = txmsg->path_msg; + if (hdr->broadcast) { + hdr->lct = 1; + hdr->lcr = 6; + } else { + hdr->lct = mstb->lct; + hdr->lcr = mstb->lct - 1; + } + + memcpy(hdr->rad, mstb->rad, hdr->lct / 2); + + return 0; +} +/* + * process a single block of the next message in the sideband queue + */ +static int process_single_tx_qlock(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_tx *txmsg, + bool up) +{ + u8 chunk[48]; + struct drm_dp_sideband_msg_hdr hdr; + int len, space, idx, tosend; + int ret; + + if (txmsg->state == DRM_DP_SIDEBAND_TX_SENT) + return 0; + + memset(&hdr, 0, sizeof(struct drm_dp_sideband_msg_hdr)); + + if (txmsg->state == DRM_DP_SIDEBAND_TX_QUEUED) + txmsg->state = DRM_DP_SIDEBAND_TX_START_SEND; + + /* make hdr from dst mst */ + ret = set_hdr_from_dst_qlock(&hdr, txmsg); + if (ret < 0) + return ret; + + /* amount left to send in this message */ + len = txmsg->cur_len - txmsg->cur_offset; + + /* 48 - sideband msg size - 1 byte for data CRC, x header bytes */ + space = 48 - 1 - drm_dp_calc_sb_hdr_size(&hdr); + + tosend = min(len, space); + if (len == txmsg->cur_len) + hdr.somt = 1; + if (space >= len) + hdr.eomt = 1; + + + hdr.msg_len = tosend + 1; + drm_dp_encode_sideband_msg_hdr(&hdr, chunk, &idx); + memcpy(&chunk[idx], &txmsg->msg[txmsg->cur_offset], tosend); + /* add crc at end */ + drm_dp_crc_sideband_chunk_req(&chunk[idx], tosend); + idx += tosend + 1; + + ret = drm_dp_send_sideband_msg(mgr, up, chunk, idx); + if (ret) { + if (drm_debug_enabled(DRM_UT_DP)) { + struct drm_printer p = drm_debug_printer(DBG_PREFIX); + + drm_printf(&p, "sideband msg failed to send\n"); + drm_dp_mst_dump_sideband_msg_tx(&p, txmsg); + } + return ret; + } + + txmsg->cur_offset += tosend; + if (txmsg->cur_offset == txmsg->cur_len) { + txmsg->state = DRM_DP_SIDEBAND_TX_SENT; + return 1; + } + return 0; +} + +static void process_single_down_tx_qlock(struct drm_dp_mst_topology_mgr *mgr) +{ + struct drm_dp_sideband_msg_tx *txmsg; + int ret; + + WARN_ON(!mutex_is_locked(&mgr->qlock)); + + /* construct a chunk from the first msg in the tx_msg queue */ + if (list_empty(&mgr->tx_msg_downq)) + return; + + txmsg = list_first_entry(&mgr->tx_msg_downq, + struct drm_dp_sideband_msg_tx, next); + ret = process_single_tx_qlock(mgr, txmsg, false); + if (ret < 0) { + drm_dbg_kms(mgr->dev, "failed to send msg in q %d\n", ret); + list_del(&txmsg->next); + txmsg->state = DRM_DP_SIDEBAND_TX_TIMEOUT; + wake_up_all(&mgr->tx_waitq); + } +} + +static void drm_dp_queue_down_tx(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_tx *txmsg) +{ + mutex_lock(&mgr->qlock); + list_add_tail(&txmsg->next, &mgr->tx_msg_downq); + + if (drm_debug_enabled(DRM_UT_DP)) { + struct drm_printer p = drm_debug_printer(DBG_PREFIX); + + drm_dp_mst_dump_sideband_msg_tx(&p, txmsg); + } + + if (list_is_singular(&mgr->tx_msg_downq)) + process_single_down_tx_qlock(mgr); + mutex_unlock(&mgr->qlock); +} + +static void +drm_dp_dump_link_address(const struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_link_address_ack_reply *reply) +{ + struct drm_dp_link_addr_reply_port *port_reply; + int i; + + for (i = 0; i < reply->nports; i++) { + port_reply = &reply->ports[i]; + drm_dbg_kms(mgr->dev, + "port %d: input %d, pdt: %d, pn: %d, dpcd_rev: %02x, mcs: %d, ddps: %d, ldps %d, sdp %d/%d\n", + i, + port_reply->input_port, + port_reply->peer_device_type, + port_reply->port_number, + port_reply->dpcd_revision, + port_reply->mcs, + port_reply->ddps, + port_reply->legacy_device_plug_status, + port_reply->num_sdp_streams, + port_reply->num_sdp_stream_sinks); + } +} + +static int drm_dp_send_link_address(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb) +{ + struct drm_dp_sideband_msg_tx *txmsg; + struct drm_dp_link_address_ack_reply *reply; + struct drm_dp_mst_port *port, *tmp; + int i, ret, port_mask = 0; + bool changed = false; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) + return -ENOMEM; + + txmsg->dst = mstb; + build_link_address(txmsg); + + mstb->link_address_sent = true; + drm_dp_queue_down_tx(mgr, txmsg); + + /* FIXME: Actually do some real error handling here */ + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret <= 0) { + drm_err(mgr->dev, "Sending link address failed with %d\n", ret); + goto out; + } + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { + drm_err(mgr->dev, "link address NAK received\n"); + ret = -EIO; + goto out; + } + + reply = &txmsg->reply.u.link_addr; + drm_dbg_kms(mgr->dev, "link address reply: %d\n", reply->nports); + drm_dp_dump_link_address(mgr, reply); + + ret = drm_dp_check_mstb_guid(mstb, reply->guid); + if (ret) { + char buf[64]; + + drm_dp_mst_rad_to_str(mstb->rad, mstb->lct, buf, sizeof(buf)); + drm_err(mgr->dev, "GUID check on %s failed: %d\n", buf, ret); + goto out; + } + + for (i = 0; i < reply->nports; i++) { + port_mask |= BIT(reply->ports[i].port_number); + ret = drm_dp_mst_handle_link_address_port(mstb, mgr->dev, + &reply->ports[i]); + if (ret == 1) + changed = true; + else if (ret < 0) + goto out; + } + + /* Prune any ports that are currently a part of mstb in our in-memory + * topology, but were not seen in this link address. Usually this + * means that they were removed while the topology was out of sync, + * e.g. during suspend/resume + */ + mutex_lock(&mgr->lock); + list_for_each_entry_safe(port, tmp, &mstb->ports, next) { + if (port_mask & BIT(port->port_num)) + continue; + + drm_dbg_kms(mgr->dev, "port %d was not in link address, removing\n", + port->port_num); + list_del(&port->next); + drm_dp_mst_topology_put_port(port); + changed = true; + } + mutex_unlock(&mgr->lock); + +out: + if (ret <= 0) + mstb->link_address_sent = false; + kfree(txmsg); + return ret < 0 ? ret : changed; +} + +static void +drm_dp_send_clear_payload_id_table(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb) +{ + struct drm_dp_sideband_msg_tx *txmsg; + int ret; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) + return; + + txmsg->dst = mstb; + build_clear_payload_id_table(txmsg); + + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret > 0 && txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) + drm_dbg_kms(mgr->dev, "clear payload table id nak received\n"); + + kfree(txmsg); +} + +static int +drm_dp_send_enum_path_resources(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_port *port) +{ + struct drm_dp_enum_path_resources_ack_reply *path_res; + struct drm_dp_sideband_msg_tx *txmsg; + int ret; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) + return -ENOMEM; + + txmsg->dst = mstb; + build_enum_path_resources(txmsg, port->port_num); + + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret > 0) { + ret = 0; + path_res = &txmsg->reply.u.path_resources; + + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { + drm_dbg_kms(mgr->dev, "enum path resources nak received\n"); + } else { + if (port->port_num != path_res->port_number) + DRM_ERROR("got incorrect port in response\n"); + + drm_dbg_kms(mgr->dev, "enum path resources %d: %d %d\n", + path_res->port_number, + path_res->full_payload_bw_number, + path_res->avail_payload_bw_number); + + /* + * If something changed, make sure we send a + * hotplug + */ + if (port->full_pbn != path_res->full_payload_bw_number || + port->fec_capable != path_res->fec_capable) + ret = 1; + + port->full_pbn = path_res->full_payload_bw_number; + port->fec_capable = path_res->fec_capable; + } + } + + kfree(txmsg); + return ret; +} + +static struct drm_dp_mst_port *drm_dp_get_last_connected_port_to_mstb(struct drm_dp_mst_branch *mstb) +{ + if (!mstb->port_parent) + return NULL; + + if (mstb->port_parent->mstb != mstb) + return mstb->port_parent; + + return drm_dp_get_last_connected_port_to_mstb(mstb->port_parent->parent); +} + +/* + * Searches upwards in the topology starting from mstb to try to find the + * closest available parent of mstb that's still connected to the rest of the + * topology. This can be used in order to perform operations like releasing + * payloads, where the branch device which owned the payload may no longer be + * around and thus would require that the payload on the last living relative + * be freed instead. + */ +static struct drm_dp_mst_branch * +drm_dp_get_last_connected_port_and_mstb(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb, + int *port_num) +{ + struct drm_dp_mst_branch *rmstb = NULL; + struct drm_dp_mst_port *found_port; + + mutex_lock(&mgr->lock); + if (!mgr->mst_primary) + goto out; + + do { + found_port = drm_dp_get_last_connected_port_to_mstb(mstb); + if (!found_port) + break; + + if (drm_dp_mst_topology_try_get_mstb(found_port->parent)) { + rmstb = found_port->parent; + *port_num = found_port->port_num; + } else { + /* Search again, starting from this parent */ + mstb = found_port->parent; + } + } while (!rmstb); +out: + mutex_unlock(&mgr->lock); + return rmstb; +} + +static int drm_dp_payload_send_msg(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int id, + int pbn) +{ + struct drm_dp_sideband_msg_tx *txmsg; + struct drm_dp_mst_branch *mstb; + int ret, port_num; + u8 sinks[DRM_DP_MAX_SDP_STREAMS]; + int i; + + port_num = port->port_num; + mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent); + if (!mstb) { + mstb = drm_dp_get_last_connected_port_and_mstb(mgr, + port->parent, + &port_num); + + if (!mstb) + return -EINVAL; + } + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) { + ret = -ENOMEM; + goto fail_put; + } + + for (i = 0; i < port->num_sdp_streams; i++) + sinks[i] = i; + + txmsg->dst = mstb; + build_allocate_payload(txmsg, port_num, + id, + pbn, port->num_sdp_streams, sinks); + + drm_dp_queue_down_tx(mgr, txmsg); + + /* + * FIXME: there is a small chance that between getting the last + * connected mstb and sending the payload message, the last connected + * mstb could also be removed from the topology. In the future, this + * needs to be fixed by restarting the + * drm_dp_get_last_connected_port_and_mstb() search in the event of a + * timeout if the topology is still connected to the system. + */ + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret > 0) { + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) + ret = -EINVAL; + else + ret = 0; + } + kfree(txmsg); +fail_put: + drm_dp_mst_topology_put_mstb(mstb); + return ret; +} + +int drm_dp_send_power_updown_phy(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, bool power_up) +{ + struct drm_dp_sideband_msg_tx *txmsg; + int ret; + + port = drm_dp_mst_topology_get_port_validated(mgr, port); + if (!port) + return -EINVAL; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) { + drm_dp_mst_topology_put_port(port); + return -ENOMEM; + } + + txmsg->dst = port->parent; + build_power_updown_phy(txmsg, port->port_num, power_up); + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(port->parent, txmsg); + if (ret > 0) { + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) + ret = -EINVAL; + else + ret = 0; + } + kfree(txmsg); + drm_dp_mst_topology_put_port(port); + + return ret; +} +EXPORT_SYMBOL(drm_dp_send_power_updown_phy); + +int drm_dp_send_query_stream_enc_status(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + struct drm_dp_query_stream_enc_status_ack_reply *status) +{ + struct drm_dp_sideband_msg_tx *txmsg; + u8 nonce[7]; + int ret; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) + return -ENOMEM; + + port = drm_dp_mst_topology_get_port_validated(mgr, port); + if (!port) { + ret = -EINVAL; + goto out_get_port; + } + + get_random_bytes(nonce, sizeof(nonce)); + + /* + * "Source device targets the QUERY_STREAM_ENCRYPTION_STATUS message + * transaction at the MST Branch device directly connected to the + * Source" + */ + txmsg->dst = mgr->mst_primary; + + build_query_stream_enc_status(txmsg, port->vcpi.vcpi, nonce); + + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(mgr->mst_primary, txmsg); + if (ret < 0) { + goto out; + } else if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { + drm_dbg_kms(mgr->dev, "query encryption status nak received\n"); + ret = -ENXIO; + goto out; + } + + ret = 0; + memcpy(status, &txmsg->reply.u.enc_status, sizeof(*status)); + +out: + drm_dp_mst_topology_put_port(port); +out_get_port: + kfree(txmsg); + return ret; +} +EXPORT_SYMBOL(drm_dp_send_query_stream_enc_status); + +static int drm_dp_create_payload_step1(struct drm_dp_mst_topology_mgr *mgr, + int id, + struct drm_dp_payload *payload) +{ + int ret; + + ret = drm_dp_dpcd_write_payload(mgr, id, payload); + if (ret < 0) { + payload->payload_state = 0; + return ret; + } + payload->payload_state = DP_PAYLOAD_LOCAL; + return 0; +} + +static int drm_dp_create_payload_step2(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int id, + struct drm_dp_payload *payload) +{ + int ret; + + ret = drm_dp_payload_send_msg(mgr, port, id, port->vcpi.pbn); + if (ret < 0) + return ret; + payload->payload_state = DP_PAYLOAD_REMOTE; + return ret; +} + +static int drm_dp_destroy_payload_step1(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int id, + struct drm_dp_payload *payload) +{ + drm_dbg_kms(mgr->dev, "\n"); + /* it's okay for these to fail */ + if (port) { + drm_dp_payload_send_msg(mgr, port, id, 0); + } + + drm_dp_dpcd_write_payload(mgr, id, payload); + payload->payload_state = DP_PAYLOAD_DELETE_LOCAL; + return 0; +} + +static int drm_dp_destroy_payload_step2(struct drm_dp_mst_topology_mgr *mgr, + int id, + struct drm_dp_payload *payload) +{ + payload->payload_state = 0; + return 0; +} + +/** + * drm_dp_update_payload_part1() - Execute payload update part 1 + * @mgr: manager to use. + * @start_slot: this is the cur slot + * + * NOTE: start_slot is a temporary workaround for non-atomic drivers, + * this will be removed when non-atomic mst helpers are moved out of the helper + * + * This iterates over all proposed virtual channels, and tries to + * allocate space in the link for them. For 0->slots transitions, + * this step just writes the VCPI to the MST device. For slots->0 + * transitions, this writes the updated VCPIs and removes the + * remote VC payloads. + * + * after calling this the driver should generate ACT and payload + * packets. + */ +int drm_dp_update_payload_part1(struct drm_dp_mst_topology_mgr *mgr, int start_slot) +{ + struct drm_dp_payload req_payload; + struct drm_dp_mst_port *port; + int i, j; + int cur_slots = start_slot; + bool skip; + + mutex_lock(&mgr->payload_lock); + for (i = 0; i < mgr->max_payloads; i++) { + struct drm_dp_vcpi *vcpi = mgr->proposed_vcpis[i]; + struct drm_dp_payload *payload = &mgr->payloads[i]; + bool put_port = false; + + /* solve the current payloads - compare to the hw ones + - update the hw view */ + req_payload.start_slot = cur_slots; + if (vcpi) { + port = container_of(vcpi, struct drm_dp_mst_port, + vcpi); + + mutex_lock(&mgr->lock); + skip = !drm_dp_mst_port_downstream_of_branch(port, mgr->mst_primary); + mutex_unlock(&mgr->lock); + + if (skip) { + drm_dbg_kms(mgr->dev, + "Virtual channel %d is not in current topology\n", + i); + continue; + } + /* Validated ports don't matter if we're releasing + * VCPI + */ + if (vcpi->num_slots) { + port = drm_dp_mst_topology_get_port_validated( + mgr, port); + if (!port) { + if (vcpi->num_slots == payload->num_slots) { + cur_slots += vcpi->num_slots; + payload->start_slot = req_payload.start_slot; + continue; + } else { + drm_dbg_kms(mgr->dev, + "Fail:set payload to invalid sink"); + mutex_unlock(&mgr->payload_lock); + return -EINVAL; + } + } + put_port = true; + } + + req_payload.num_slots = vcpi->num_slots; + req_payload.vcpi = vcpi->vcpi; + } else { + port = NULL; + req_payload.num_slots = 0; + } + + payload->start_slot = req_payload.start_slot; + /* work out what is required to happen with this payload */ + if (payload->num_slots != req_payload.num_slots) { + + /* need to push an update for this payload */ + if (req_payload.num_slots) { + drm_dp_create_payload_step1(mgr, vcpi->vcpi, + &req_payload); + payload->num_slots = req_payload.num_slots; + payload->vcpi = req_payload.vcpi; + + } else if (payload->num_slots) { + payload->num_slots = 0; + drm_dp_destroy_payload_step1(mgr, port, + payload->vcpi, + payload); + req_payload.payload_state = + payload->payload_state; + payload->start_slot = 0; + } + payload->payload_state = req_payload.payload_state; + } + cur_slots += req_payload.num_slots; + + if (put_port) + drm_dp_mst_topology_put_port(port); + } + + for (i = 0; i < mgr->max_payloads; /* do nothing */) { + if (mgr->payloads[i].payload_state != DP_PAYLOAD_DELETE_LOCAL) { + i++; + continue; + } + + drm_dbg_kms(mgr->dev, "removing payload %d\n", i); + for (j = i; j < mgr->max_payloads - 1; j++) { + mgr->payloads[j] = mgr->payloads[j + 1]; + mgr->proposed_vcpis[j] = mgr->proposed_vcpis[j + 1]; + + if (mgr->proposed_vcpis[j] && + mgr->proposed_vcpis[j]->num_slots) { + set_bit(j + 1, &mgr->payload_mask); + } else { + clear_bit(j + 1, &mgr->payload_mask); + } + } + + memset(&mgr->payloads[mgr->max_payloads - 1], 0, + sizeof(struct drm_dp_payload)); + mgr->proposed_vcpis[mgr->max_payloads - 1] = NULL; + clear_bit(mgr->max_payloads, &mgr->payload_mask); + } + mutex_unlock(&mgr->payload_lock); + + return 0; +} +EXPORT_SYMBOL(drm_dp_update_payload_part1); + +/** + * drm_dp_update_payload_part2() - Execute payload update part 2 + * @mgr: manager to use. + * + * This iterates over all proposed virtual channels, and tries to + * allocate space in the link for them. For 0->slots transitions, + * this step writes the remote VC payload commands. For slots->0 + * this just resets some internal state. + */ +int drm_dp_update_payload_part2(struct drm_dp_mst_topology_mgr *mgr) +{ + struct drm_dp_mst_port *port; + int i; + int ret = 0; + bool skip; + + mutex_lock(&mgr->payload_lock); + for (i = 0; i < mgr->max_payloads; i++) { + + if (!mgr->proposed_vcpis[i]) + continue; + + port = container_of(mgr->proposed_vcpis[i], struct drm_dp_mst_port, vcpi); + + mutex_lock(&mgr->lock); + skip = !drm_dp_mst_port_downstream_of_branch(port, mgr->mst_primary); + mutex_unlock(&mgr->lock); + + if (skip) + continue; + + drm_dbg_kms(mgr->dev, "payload %d %d\n", i, mgr->payloads[i].payload_state); + if (mgr->payloads[i].payload_state == DP_PAYLOAD_LOCAL) { + ret = drm_dp_create_payload_step2(mgr, port, mgr->proposed_vcpis[i]->vcpi, &mgr->payloads[i]); + } else if (mgr->payloads[i].payload_state == DP_PAYLOAD_DELETE_LOCAL) { + ret = drm_dp_destroy_payload_step2(mgr, mgr->proposed_vcpis[i]->vcpi, &mgr->payloads[i]); + } + if (ret) { + mutex_unlock(&mgr->payload_lock); + return ret; + } + } + mutex_unlock(&mgr->payload_lock); + return 0; +} +EXPORT_SYMBOL(drm_dp_update_payload_part2); + +static int drm_dp_send_dpcd_read(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int offset, int size, u8 *bytes) +{ + int ret = 0; + struct drm_dp_sideband_msg_tx *txmsg; + struct drm_dp_mst_branch *mstb; + + mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent); + if (!mstb) + return -EINVAL; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) { + ret = -ENOMEM; + goto fail_put; + } + + build_dpcd_read(txmsg, port->port_num, offset, size); + txmsg->dst = port->parent; + + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret < 0) + goto fail_free; + + /* DPCD read should never be NACKed */ + if (txmsg->reply.reply_type == 1) { + drm_err(mgr->dev, "mstb %p port %d: DPCD read on addr 0x%x for %d bytes NAKed\n", + mstb, port->port_num, offset, size); + ret = -EIO; + goto fail_free; + } + + if (txmsg->reply.u.remote_dpcd_read_ack.num_bytes != size) { + ret = -EPROTO; + goto fail_free; + } + + ret = min_t(size_t, txmsg->reply.u.remote_dpcd_read_ack.num_bytes, + size); + memcpy(bytes, txmsg->reply.u.remote_dpcd_read_ack.bytes, ret); + +fail_free: + kfree(txmsg); +fail_put: + drm_dp_mst_topology_put_mstb(mstb); + + return ret; +} + +static int drm_dp_send_dpcd_write(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int offset, int size, u8 *bytes) +{ + int ret; + struct drm_dp_sideband_msg_tx *txmsg; + struct drm_dp_mst_branch *mstb; + + mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent); + if (!mstb) + return -EINVAL; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) { + ret = -ENOMEM; + goto fail_put; + } + + build_dpcd_write(txmsg, port->port_num, offset, size, bytes); + txmsg->dst = mstb; + + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret > 0) { + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) + ret = -EIO; + else + ret = size; + } + + kfree(txmsg); +fail_put: + drm_dp_mst_topology_put_mstb(mstb); + return ret; +} + +static int drm_dp_encode_up_ack_reply(struct drm_dp_sideband_msg_tx *msg, u8 req_type) +{ + struct drm_dp_sideband_msg_reply_body reply; + + reply.reply_type = DP_SIDEBAND_REPLY_ACK; + reply.req_type = req_type; + drm_dp_encode_sideband_reply(&reply, msg); + return 0; +} + +static int drm_dp_send_up_ack_reply(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb, + int req_type, bool broadcast) +{ + struct drm_dp_sideband_msg_tx *txmsg; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) + return -ENOMEM; + + txmsg->dst = mstb; + drm_dp_encode_up_ack_reply(txmsg, req_type); + + mutex_lock(&mgr->qlock); + /* construct a chunk from the first msg in the tx_msg queue */ + process_single_tx_qlock(mgr, txmsg, true); + mutex_unlock(&mgr->qlock); + + kfree(txmsg); + return 0; +} + +/** + * drm_dp_get_vc_payload_bw - get the VC payload BW for an MST link + * @mgr: The &drm_dp_mst_topology_mgr to use + * @link_rate: link rate in 10kbits/s units + * @link_lane_count: lane count + * + * Calculate the total bandwidth of a MultiStream Transport link. The returned + * value is in units of PBNs/(timeslots/1 MTP). This value can be used to + * convert the number of PBNs required for a given stream to the number of + * timeslots this stream requires in each MTP. + */ +int drm_dp_get_vc_payload_bw(const struct drm_dp_mst_topology_mgr *mgr, + int link_rate, int link_lane_count) +{ + if (link_rate == 0 || link_lane_count == 0) + drm_dbg_kms(mgr->dev, "invalid link rate/lane count: (%d / %d)\n", + link_rate, link_lane_count); + + /* See DP v2.0 2.6.4.2, VCPayload_Bandwidth_for_OneTimeSlotPer_MTP_Allocation */ + return link_rate * link_lane_count / 54000; +} +EXPORT_SYMBOL(drm_dp_get_vc_payload_bw); + +/** + * drm_dp_read_mst_cap() - check whether or not a sink supports MST + * @aux: The DP AUX channel to use + * @dpcd: A cached copy of the DPCD capabilities for this sink + * + * Returns: %True if the sink supports MST, %false otherwise + */ +bool drm_dp_read_mst_cap(struct drm_dp_aux *aux, + const u8 dpcd[DP_RECEIVER_CAP_SIZE]) +{ + u8 mstm_cap; + + if (dpcd[DP_DPCD_REV] < DP_DPCD_REV_12) + return false; + + if (drm_dp_dpcd_readb(aux, DP_MSTM_CAP, &mstm_cap) != 1) + return false; + + return mstm_cap & DP_MST_CAP; +} +EXPORT_SYMBOL(drm_dp_read_mst_cap); + +/** + * drm_dp_mst_topology_mgr_set_mst() - Set the MST state for a topology manager + * @mgr: manager to set state for + * @mst_state: true to enable MST on this connector - false to disable. + * + * This is called by the driver when it detects an MST capable device plugged + * into a DP MST capable port, or when a DP MST capable device is unplugged. + */ +int drm_dp_mst_topology_mgr_set_mst(struct drm_dp_mst_topology_mgr *mgr, bool mst_state) +{ + int ret = 0; + struct drm_dp_mst_branch *mstb = NULL; + + mutex_lock(&mgr->payload_lock); + mutex_lock(&mgr->lock); + if (mst_state == mgr->mst_state) + goto out_unlock; + + mgr->mst_state = mst_state; + /* set the device into MST mode */ + if (mst_state) { + struct drm_dp_payload reset_pay; + int lane_count; + int link_rate; + + WARN_ON(mgr->mst_primary); + + /* get dpcd info */ + ret = drm_dp_read_dpcd_caps(mgr->aux, mgr->dpcd); + if (ret < 0) { + drm_dbg_kms(mgr->dev, "%s: failed to read DPCD, ret %d\n", + mgr->aux->name, ret); + goto out_unlock; + } + + lane_count = min_t(int, mgr->dpcd[2] & DP_MAX_LANE_COUNT_MASK, mgr->max_lane_count); + link_rate = min_t(int, drm_dp_bw_code_to_link_rate(mgr->dpcd[1]), mgr->max_link_rate); + mgr->pbn_div = drm_dp_get_vc_payload_bw(mgr, + link_rate, + lane_count); + if (mgr->pbn_div == 0) { + ret = -EINVAL; + goto out_unlock; + } + + /* add initial branch device at LCT 1 */ + mstb = drm_dp_add_mst_branch_device(1, NULL); + if (mstb == NULL) { + ret = -ENOMEM; + goto out_unlock; + } + mstb->mgr = mgr; + + /* give this the main reference */ + mgr->mst_primary = mstb; + drm_dp_mst_topology_get_mstb(mgr->mst_primary); + + ret = drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, + DP_MST_EN | + DP_UP_REQ_EN | + DP_UPSTREAM_IS_SRC); + if (ret < 0) + goto out_unlock; + + reset_pay.start_slot = 0; + reset_pay.num_slots = 0x3f; + drm_dp_dpcd_write_payload(mgr, 0, &reset_pay); + + queue_work(system_long_wq, &mgr->work); + + ret = 0; + } else { + /* disable MST on the device */ + mstb = mgr->mst_primary; + mgr->mst_primary = NULL; + /* this can fail if the device is gone */ + drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, 0); + ret = 0; + memset(mgr->payloads, 0, + mgr->max_payloads * sizeof(mgr->payloads[0])); + memset(mgr->proposed_vcpis, 0, + mgr->max_payloads * sizeof(mgr->proposed_vcpis[0])); + mgr->payload_mask = 0; + set_bit(0, &mgr->payload_mask); + mgr->vcpi_mask = 0; + mgr->payload_id_table_cleared = false; + } + +out_unlock: + mutex_unlock(&mgr->lock); + mutex_unlock(&mgr->payload_lock); + if (mstb) + drm_dp_mst_topology_put_mstb(mstb); + return ret; + +} +EXPORT_SYMBOL(drm_dp_mst_topology_mgr_set_mst); + +static void +drm_dp_mst_topology_mgr_invalidate_mstb(struct drm_dp_mst_branch *mstb) +{ + struct drm_dp_mst_port *port; + + /* The link address will need to be re-sent on resume */ + mstb->link_address_sent = false; + + list_for_each_entry(port, &mstb->ports, next) + if (port->mstb) + drm_dp_mst_topology_mgr_invalidate_mstb(port->mstb); +} + +/** + * drm_dp_mst_topology_mgr_suspend() - suspend the MST manager + * @mgr: manager to suspend + * + * This function tells the MST device that we can't handle UP messages + * anymore. This should stop it from sending any since we are suspended. + */ +void drm_dp_mst_topology_mgr_suspend(struct drm_dp_mst_topology_mgr *mgr) +{ + mutex_lock(&mgr->lock); + drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, + DP_MST_EN | DP_UPSTREAM_IS_SRC); + mutex_unlock(&mgr->lock); + flush_work(&mgr->up_req_work); + flush_work(&mgr->work); + flush_work(&mgr->delayed_destroy_work); + + mutex_lock(&mgr->lock); + if (mgr->mst_state && mgr->mst_primary) + drm_dp_mst_topology_mgr_invalidate_mstb(mgr->mst_primary); + mutex_unlock(&mgr->lock); +} +EXPORT_SYMBOL(drm_dp_mst_topology_mgr_suspend); + +/** + * drm_dp_mst_topology_mgr_resume() - resume the MST manager + * @mgr: manager to resume + * @sync: whether or not to perform topology reprobing synchronously + * + * This will fetch DPCD and see if the device is still there, + * if it is, it will rewrite the MSTM control bits, and return. + * + * If the device fails this returns -1, and the driver should do + * a full MST reprobe, in case we were undocked. + * + * During system resume (where it is assumed that the driver will be calling + * drm_atomic_helper_resume()) this function should be called beforehand with + * @sync set to true. In contexts like runtime resume where the driver is not + * expected to be calling drm_atomic_helper_resume(), this function should be + * called with @sync set to false in order to avoid deadlocking. + * + * Returns: -1 if the MST topology was removed while we were suspended, 0 + * otherwise. + */ +int drm_dp_mst_topology_mgr_resume(struct drm_dp_mst_topology_mgr *mgr, + bool sync) +{ + int ret; + u8 guid[16]; + + mutex_lock(&mgr->lock); + if (!mgr->mst_primary) + goto out_fail; + + ret = drm_dp_dpcd_read(mgr->aux, DP_DPCD_REV, mgr->dpcd, + DP_RECEIVER_CAP_SIZE); + if (ret != DP_RECEIVER_CAP_SIZE) { + drm_dbg_kms(mgr->dev, "dpcd read failed - undocked during suspend?\n"); + goto out_fail; + } + + ret = drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, + DP_MST_EN | + DP_UP_REQ_EN | + DP_UPSTREAM_IS_SRC); + if (ret < 0) { + drm_dbg_kms(mgr->dev, "mst write failed - undocked during suspend?\n"); + goto out_fail; + } + + /* Some hubs forget their guids after they resume */ + ret = drm_dp_dpcd_read(mgr->aux, DP_GUID, guid, 16); + if (ret != 16) { + drm_dbg_kms(mgr->dev, "dpcd read failed - undocked during suspend?\n"); + goto out_fail; + } + + ret = drm_dp_check_mstb_guid(mgr->mst_primary, guid); + if (ret) { + drm_dbg_kms(mgr->dev, "check mstb failed - undocked during suspend?\n"); + goto out_fail; + } + + /* + * For the final step of resuming the topology, we need to bring the + * state of our in-memory topology back into sync with reality. So, + * restart the probing process as if we're probing a new hub + */ + queue_work(system_long_wq, &mgr->work); + mutex_unlock(&mgr->lock); + + if (sync) { + drm_dbg_kms(mgr->dev, + "Waiting for link probe work to finish re-syncing topology...\n"); + flush_work(&mgr->work); + } + + return 0; + +out_fail: + mutex_unlock(&mgr->lock); + return -1; +} +EXPORT_SYMBOL(drm_dp_mst_topology_mgr_resume); + +static bool +drm_dp_get_one_sb_msg(struct drm_dp_mst_topology_mgr *mgr, bool up, + struct drm_dp_mst_branch **mstb) +{ + int len; + u8 replyblock[32]; + int replylen, curreply; + int ret; + u8 hdrlen; + struct drm_dp_sideband_msg_hdr hdr; + struct drm_dp_sideband_msg_rx *msg = + up ? &mgr->up_req_recv : &mgr->down_rep_recv; + int basereg = up ? DP_SIDEBAND_MSG_UP_REQ_BASE : + DP_SIDEBAND_MSG_DOWN_REP_BASE; + + if (!up) + *mstb = NULL; + + len = min(mgr->max_dpcd_transaction_bytes, 16); + ret = drm_dp_dpcd_read(mgr->aux, basereg, replyblock, len); + if (ret != len) { + drm_dbg_kms(mgr->dev, "failed to read DPCD down rep %d %d\n", len, ret); + return false; + } + + ret = drm_dp_decode_sideband_msg_hdr(mgr, &hdr, replyblock, len, &hdrlen); + if (ret == false) { + print_hex_dump(KERN_DEBUG, "failed hdr", DUMP_PREFIX_NONE, 16, + 1, replyblock, len, false); + drm_dbg_kms(mgr->dev, "ERROR: failed header\n"); + return false; + } + + if (!up) { + /* Caller is responsible for giving back this reference */ + *mstb = drm_dp_get_mst_branch_device(mgr, hdr.lct, hdr.rad); + if (!*mstb) { + drm_dbg_kms(mgr->dev, "Got MST reply from unknown device %d\n", hdr.lct); + return false; + } + } + + if (!drm_dp_sideband_msg_set_header(msg, &hdr, hdrlen)) { + drm_dbg_kms(mgr->dev, "sideband msg set header failed %d\n", replyblock[0]); + return false; + } + + replylen = min(msg->curchunk_len, (u8)(len - hdrlen)); + ret = drm_dp_sideband_append_payload(msg, replyblock + hdrlen, replylen); + if (!ret) { + drm_dbg_kms(mgr->dev, "sideband msg build failed %d\n", replyblock[0]); + return false; + } + + replylen = msg->curchunk_len + msg->curchunk_hdrlen - len; + curreply = len; + while (replylen > 0) { + len = min3(replylen, mgr->max_dpcd_transaction_bytes, 16); + ret = drm_dp_dpcd_read(mgr->aux, basereg + curreply, + replyblock, len); + if (ret != len) { + drm_dbg_kms(mgr->dev, "failed to read a chunk (len %d, ret %d)\n", + len, ret); + return false; + } + + ret = drm_dp_sideband_append_payload(msg, replyblock, len); + if (!ret) { + drm_dbg_kms(mgr->dev, "failed to build sideband msg\n"); + return false; + } + + curreply += len; + replylen -= len; + } + return true; +} + +static int drm_dp_mst_handle_down_rep(struct drm_dp_mst_topology_mgr *mgr) +{ + struct drm_dp_sideband_msg_tx *txmsg; + struct drm_dp_mst_branch *mstb = NULL; + struct drm_dp_sideband_msg_rx *msg = &mgr->down_rep_recv; + + if (!drm_dp_get_one_sb_msg(mgr, false, &mstb)) + goto out; + + /* Multi-packet message transmission, don't clear the reply */ + if (!msg->have_eomt) + goto out; + + /* find the message */ + mutex_lock(&mgr->qlock); + txmsg = list_first_entry_or_null(&mgr->tx_msg_downq, + struct drm_dp_sideband_msg_tx, next); + mutex_unlock(&mgr->qlock); + + /* Were we actually expecting a response, and from this mstb? */ + if (!txmsg || txmsg->dst != mstb) { + struct drm_dp_sideband_msg_hdr *hdr; + + hdr = &msg->initial_hdr; + drm_dbg_kms(mgr->dev, "Got MST reply with no msg %p %d %d %02x %02x\n", + mstb, hdr->seqno, hdr->lct, hdr->rad[0], msg->msg[0]); + goto out_clear_reply; + } + + drm_dp_sideband_parse_reply(mgr, msg, &txmsg->reply); + + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { + drm_dbg_kms(mgr->dev, + "Got NAK reply: req 0x%02x (%s), reason 0x%02x (%s), nak data 0x%02x\n", + txmsg->reply.req_type, + drm_dp_mst_req_type_str(txmsg->reply.req_type), + txmsg->reply.u.nak.reason, + drm_dp_mst_nak_reason_str(txmsg->reply.u.nak.reason), + txmsg->reply.u.nak.nak_data); + } + + memset(msg, 0, sizeof(struct drm_dp_sideband_msg_rx)); + drm_dp_mst_topology_put_mstb(mstb); + + mutex_lock(&mgr->qlock); + txmsg->state = DRM_DP_SIDEBAND_TX_RX; + list_del(&txmsg->next); + mutex_unlock(&mgr->qlock); + + wake_up_all(&mgr->tx_waitq); + + return 0; + +out_clear_reply: + memset(msg, 0, sizeof(struct drm_dp_sideband_msg_rx)); +out: + if (mstb) + drm_dp_mst_topology_put_mstb(mstb); + + return 0; +} + +static inline bool +drm_dp_mst_process_up_req(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_pending_up_req *up_req) +{ + struct drm_dp_mst_branch *mstb = NULL; + struct drm_dp_sideband_msg_req_body *msg = &up_req->msg; + struct drm_dp_sideband_msg_hdr *hdr = &up_req->hdr; + bool hotplug = false; + + if (hdr->broadcast) { + const u8 *guid = NULL; + + if (msg->req_type == DP_CONNECTION_STATUS_NOTIFY) + guid = msg->u.conn_stat.guid; + else if (msg->req_type == DP_RESOURCE_STATUS_NOTIFY) + guid = msg->u.resource_stat.guid; + + if (guid) + mstb = drm_dp_get_mst_branch_device_by_guid(mgr, guid); + } else { + mstb = drm_dp_get_mst_branch_device(mgr, hdr->lct, hdr->rad); + } + + if (!mstb) { + drm_dbg_kms(mgr->dev, "Got MST reply from unknown device %d\n", hdr->lct); + return false; + } + + /* TODO: Add missing handler for DP_RESOURCE_STATUS_NOTIFY events */ + if (msg->req_type == DP_CONNECTION_STATUS_NOTIFY) { + drm_dp_mst_handle_conn_stat(mstb, &msg->u.conn_stat); + hotplug = true; + } + + drm_dp_mst_topology_put_mstb(mstb); + return hotplug; +} + +static void drm_dp_mst_up_req_work(struct work_struct *work) +{ + struct drm_dp_mst_topology_mgr *mgr = + container_of(work, struct drm_dp_mst_topology_mgr, + up_req_work); + struct drm_dp_pending_up_req *up_req; + bool send_hotplug = false; + + mutex_lock(&mgr->probe_lock); + while (true) { + mutex_lock(&mgr->up_req_lock); + up_req = list_first_entry_or_null(&mgr->up_req_list, + struct drm_dp_pending_up_req, + next); + if (up_req) + list_del(&up_req->next); + mutex_unlock(&mgr->up_req_lock); + + if (!up_req) + break; + + send_hotplug |= drm_dp_mst_process_up_req(mgr, up_req); + kfree(up_req); + } + mutex_unlock(&mgr->probe_lock); + + if (send_hotplug) + drm_kms_helper_hotplug_event(mgr->dev); +} + +static int drm_dp_mst_handle_up_req(struct drm_dp_mst_topology_mgr *mgr) +{ + struct drm_dp_pending_up_req *up_req; + + if (!drm_dp_get_one_sb_msg(mgr, true, NULL)) + goto out; + + if (!mgr->up_req_recv.have_eomt) + return 0; + + up_req = kzalloc(sizeof(*up_req), GFP_KERNEL); + if (!up_req) + return -ENOMEM; + + INIT_LIST_HEAD(&up_req->next); + + drm_dp_sideband_parse_req(mgr, &mgr->up_req_recv, &up_req->msg); + + if (up_req->msg.req_type != DP_CONNECTION_STATUS_NOTIFY && + up_req->msg.req_type != DP_RESOURCE_STATUS_NOTIFY) { + drm_dbg_kms(mgr->dev, "Received unknown up req type, ignoring: %x\n", + up_req->msg.req_type); + kfree(up_req); + goto out; + } + + drm_dp_send_up_ack_reply(mgr, mgr->mst_primary, up_req->msg.req_type, + false); + + if (up_req->msg.req_type == DP_CONNECTION_STATUS_NOTIFY) { + const struct drm_dp_connection_status_notify *conn_stat = + &up_req->msg.u.conn_stat; + + drm_dbg_kms(mgr->dev, "Got CSN: pn: %d ldps:%d ddps: %d mcs: %d ip: %d pdt: %d\n", + conn_stat->port_number, + conn_stat->legacy_device_plug_status, + conn_stat->displayport_device_plug_status, + conn_stat->message_capability_status, + conn_stat->input_port, + conn_stat->peer_device_type); + } else if (up_req->msg.req_type == DP_RESOURCE_STATUS_NOTIFY) { + const struct drm_dp_resource_status_notify *res_stat = + &up_req->msg.u.resource_stat; + + drm_dbg_kms(mgr->dev, "Got RSN: pn: %d avail_pbn %d\n", + res_stat->port_number, + res_stat->available_pbn); + } + + up_req->hdr = mgr->up_req_recv.initial_hdr; + mutex_lock(&mgr->up_req_lock); + list_add_tail(&up_req->next, &mgr->up_req_list); + mutex_unlock(&mgr->up_req_lock); + queue_work(system_long_wq, &mgr->up_req_work); + +out: + memset(&mgr->up_req_recv, 0, sizeof(struct drm_dp_sideband_msg_rx)); + return 0; +} + +/** + * drm_dp_mst_hpd_irq() - MST hotplug IRQ notify + * @mgr: manager to notify irq for. + * @esi: 4 bytes from SINK_COUNT_ESI + * @handled: whether the hpd interrupt was consumed or not + * + * This should be called from the driver when it detects a short IRQ, + * along with the value of the DEVICE_SERVICE_IRQ_VECTOR_ESI0. The + * topology manager will process the sideband messages received as a result + * of this. + */ +int drm_dp_mst_hpd_irq(struct drm_dp_mst_topology_mgr *mgr, u8 *esi, bool *handled) +{ + int ret = 0; + int sc; + *handled = false; + sc = DP_GET_SINK_COUNT(esi[0]); + + if (sc != mgr->sink_count) { + mgr->sink_count = sc; + *handled = true; + } + + if (esi[1] & DP_DOWN_REP_MSG_RDY) { + ret = drm_dp_mst_handle_down_rep(mgr); + *handled = true; + } + + if (esi[1] & DP_UP_REQ_MSG_RDY) { + ret |= drm_dp_mst_handle_up_req(mgr); + *handled = true; + } + + drm_dp_mst_kick_tx(mgr); + return ret; +} +EXPORT_SYMBOL(drm_dp_mst_hpd_irq); + +/** + * drm_dp_mst_detect_port() - get connection status for an MST port + * @connector: DRM connector for this port + * @ctx: The acquisition context to use for grabbing locks + * @mgr: manager for this port + * @port: pointer to a port + * + * This returns the current connection state for a port. + */ +int +drm_dp_mst_detect_port(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, + struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port) +{ + int ret; + + /* we need to search for the port in the mgr in case it's gone */ + port = drm_dp_mst_topology_get_port_validated(mgr, port); + if (!port) + return connector_status_disconnected; + + ret = drm_modeset_lock(&mgr->base.lock, ctx); + if (ret) + goto out; + + ret = connector_status_disconnected; + + if (!port->ddps) + goto out; + + switch (port->pdt) { + case DP_PEER_DEVICE_NONE: + break; + case DP_PEER_DEVICE_MST_BRANCHING: + if (!port->mcs) + ret = connector_status_connected; + break; + + case DP_PEER_DEVICE_SST_SINK: + ret = connector_status_connected; + /* for logical ports - cache the EDID */ + if (port->port_num >= DP_MST_LOGICAL_PORT_0 && !port->cached_edid) + port->cached_edid = drm_get_edid(connector, &port->aux.ddc); + break; + case DP_PEER_DEVICE_DP_LEGACY_CONV: + if (port->ldps) + ret = connector_status_connected; + break; + } +out: + drm_dp_mst_topology_put_port(port); + return ret; +} +EXPORT_SYMBOL(drm_dp_mst_detect_port); + +/** + * drm_dp_mst_get_edid() - get EDID for an MST port + * @connector: toplevel connector to get EDID for + * @mgr: manager for this port + * @port: unverified pointer to a port. + * + * This returns an EDID for the port connected to a connector, + * It validates the pointer still exists so the caller doesn't require a + * reference. + */ +struct edid *drm_dp_mst_get_edid(struct drm_connector *connector, struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port) +{ + struct edid *edid = NULL; + + /* we need to search for the port in the mgr in case it's gone */ + port = drm_dp_mst_topology_get_port_validated(mgr, port); + if (!port) + return NULL; + + if (port->cached_edid) + edid = drm_edid_duplicate(port->cached_edid); + else { + edid = drm_get_edid(connector, &port->aux.ddc); + } + port->has_audio = drm_detect_monitor_audio(edid); + drm_dp_mst_topology_put_port(port); + return edid; +} +EXPORT_SYMBOL(drm_dp_mst_get_edid); + +/** + * drm_dp_find_vcpi_slots() - Find VCPI slots for this PBN value + * @mgr: manager to use + * @pbn: payload bandwidth to convert into slots. + * + * Calculate the number of VCPI slots that will be required for the given PBN + * value. This function is deprecated, and should not be used in atomic + * drivers. + * + * RETURNS: + * The total slots required for this port, or error. + */ +int drm_dp_find_vcpi_slots(struct drm_dp_mst_topology_mgr *mgr, + int pbn) +{ + int num_slots; + + num_slots = DIV_ROUND_UP(pbn, mgr->pbn_div); + + /* max. time slots - one slot for MTP header */ + if (num_slots > 63) + return -ENOSPC; + return num_slots; +} +EXPORT_SYMBOL(drm_dp_find_vcpi_slots); + +static int drm_dp_init_vcpi(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_vcpi *vcpi, int pbn, int slots) +{ + int ret; + + vcpi->pbn = pbn; + vcpi->aligned_pbn = slots * mgr->pbn_div; + vcpi->num_slots = slots; + + ret = drm_dp_mst_assign_payload_id(mgr, vcpi); + if (ret < 0) + return ret; + return 0; +} + +/** + * drm_dp_atomic_find_vcpi_slots() - Find and add VCPI slots to the state + * @state: global atomic state + * @mgr: MST topology manager for the port + * @port: port to find vcpi slots for + * @pbn: bandwidth required for the mode in PBN + * @pbn_div: divider for DSC mode that takes FEC into account + * + * Allocates VCPI slots to @port, replacing any previous VCPI allocations it + * may have had. Any atomic drivers which support MST must call this function + * in their &drm_encoder_helper_funcs.atomic_check() callback to change the + * current VCPI allocation for the new state, but only when + * &drm_crtc_state.mode_changed or &drm_crtc_state.connectors_changed is set + * to ensure compatibility with userspace applications that still use the + * legacy modesetting UAPI. + * + * Allocations set by this function are not checked against the bandwidth + * restraints of @mgr until the driver calls drm_dp_mst_atomic_check(). + * + * Additionally, it is OK to call this function multiple times on the same + * @port as needed. It is not OK however, to call this function and + * drm_dp_atomic_release_vcpi_slots() in the same atomic check phase. + * + * See also: + * drm_dp_atomic_release_vcpi_slots() + * drm_dp_mst_atomic_check() + * + * Returns: + * Total slots in the atomic state assigned for this port, or a negative error + * code if the port no longer exists + */ +int drm_dp_atomic_find_vcpi_slots(struct drm_atomic_state *state, + struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, int pbn, + int pbn_div) +{ + struct drm_dp_mst_topology_state *topology_state; + struct drm_dp_vcpi_allocation *pos, *vcpi = NULL; + int prev_slots, prev_bw, req_slots; + + topology_state = drm_atomic_get_mst_topology_state(state, mgr); + if (IS_ERR(topology_state)) + return PTR_ERR(topology_state); + + /* Find the current allocation for this port, if any */ + list_for_each_entry(pos, &topology_state->vcpis, next) { + if (pos->port == port) { + vcpi = pos; + prev_slots = vcpi->vcpi; + prev_bw = vcpi->pbn; + + /* + * This should never happen, unless the driver tries + * releasing and allocating the same VCPI allocation, + * which is an error + */ + if (WARN_ON(!prev_slots)) { + drm_err(mgr->dev, + "cannot allocate and release VCPI on [MST PORT:%p] in the same state\n", + port); + return -EINVAL; + } + + break; + } + } + if (!vcpi) { + prev_slots = 0; + prev_bw = 0; + } + + if (pbn_div <= 0) + pbn_div = mgr->pbn_div; + + req_slots = DIV_ROUND_UP(pbn, pbn_div); + + drm_dbg_atomic(mgr->dev, "[CONNECTOR:%d:%s] [MST PORT:%p] VCPI %d -> %d\n", + port->connector->base.id, port->connector->name, + port, prev_slots, req_slots); + drm_dbg_atomic(mgr->dev, "[CONNECTOR:%d:%s] [MST PORT:%p] PBN %d -> %d\n", + port->connector->base.id, port->connector->name, + port, prev_bw, pbn); + + /* Add the new allocation to the state */ + if (!vcpi) { + vcpi = kzalloc(sizeof(*vcpi), GFP_KERNEL); + if (!vcpi) + return -ENOMEM; + + drm_dp_mst_get_port_malloc(port); + vcpi->port = port; + list_add(&vcpi->next, &topology_state->vcpis); + } + vcpi->vcpi = req_slots; + vcpi->pbn = pbn; + + return req_slots; +} +EXPORT_SYMBOL(drm_dp_atomic_find_vcpi_slots); + +/** + * drm_dp_atomic_release_vcpi_slots() - Release allocated vcpi slots + * @state: global atomic state + * @mgr: MST topology manager for the port + * @port: The port to release the VCPI slots from + * + * Releases any VCPI slots that have been allocated to a port in the atomic + * state. Any atomic drivers which support MST must call this function in + * their &drm_connector_helper_funcs.atomic_check() callback when the + * connector will no longer have VCPI allocated (e.g. because its CRTC was + * removed) when it had VCPI allocated in the previous atomic state. + * + * It is OK to call this even if @port has been removed from the system. + * Additionally, it is OK to call this function multiple times on the same + * @port as needed. It is not OK however, to call this function and + * drm_dp_atomic_find_vcpi_slots() on the same @port in a single atomic check + * phase. + * + * See also: + * drm_dp_atomic_find_vcpi_slots() + * drm_dp_mst_atomic_check() + * + * Returns: + * 0 if all slots for this port were added back to + * &drm_dp_mst_topology_state.avail_slots or negative error code + */ +int drm_dp_atomic_release_vcpi_slots(struct drm_atomic_state *state, + struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port) +{ + struct drm_dp_mst_topology_state *topology_state; + struct drm_dp_vcpi_allocation *pos; + bool found = false; + + topology_state = drm_atomic_get_mst_topology_state(state, mgr); + if (IS_ERR(topology_state)) + return PTR_ERR(topology_state); + + list_for_each_entry(pos, &topology_state->vcpis, next) { + if (pos->port == port) { + found = true; + break; + } + } + if (WARN_ON(!found)) { + drm_err(mgr->dev, "no VCPI for [MST PORT:%p] found in mst state %p\n", + port, &topology_state->base); + return -EINVAL; + } + + drm_dbg_atomic(mgr->dev, "[MST PORT:%p] VCPI %d -> 0\n", port, pos->vcpi); + if (pos->vcpi) { + drm_dp_mst_put_port_malloc(port); + pos->vcpi = 0; + pos->pbn = 0; + } + + return 0; +} +EXPORT_SYMBOL(drm_dp_atomic_release_vcpi_slots); + +/** + * drm_dp_mst_update_slots() - updates the slot info depending on the DP ecoding format + * @mst_state: mst_state to update + * @link_encoding_cap: the ecoding format on the link + */ +void drm_dp_mst_update_slots(struct drm_dp_mst_topology_state *mst_state, uint8_t link_encoding_cap) +{ + if (link_encoding_cap == DP_CAP_ANSI_128B132B) { + mst_state->total_avail_slots = 64; + mst_state->start_slot = 0; + } else { + mst_state->total_avail_slots = 63; + mst_state->start_slot = 1; + } + + DRM_DEBUG_KMS("%s encoding format on mst_state 0x%p\n", + (link_encoding_cap == DP_CAP_ANSI_128B132B) ? "128b/132b":"8b/10b", + mst_state); +} +EXPORT_SYMBOL(drm_dp_mst_update_slots); + +/** + * drm_dp_mst_allocate_vcpi() - Allocate a virtual channel + * @mgr: manager for this port + * @port: port to allocate a virtual channel for. + * @pbn: payload bandwidth number to request + * @slots: returned number of slots for this PBN. + */ +bool drm_dp_mst_allocate_vcpi(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, int pbn, int slots) +{ + int ret; + + if (slots < 0) + return false; + + port = drm_dp_mst_topology_get_port_validated(mgr, port); + if (!port) + return false; + + if (port->vcpi.vcpi > 0) { + drm_dbg_kms(mgr->dev, + "payload: vcpi %d already allocated for pbn %d - requested pbn %d\n", + port->vcpi.vcpi, port->vcpi.pbn, pbn); + if (pbn == port->vcpi.pbn) { + drm_dp_mst_topology_put_port(port); + return true; + } + } + + ret = drm_dp_init_vcpi(mgr, &port->vcpi, pbn, slots); + if (ret) { + drm_dbg_kms(mgr->dev, "failed to init vcpi slots=%d ret=%d\n", + DIV_ROUND_UP(pbn, mgr->pbn_div), ret); + drm_dp_mst_topology_put_port(port); + goto out; + } + drm_dbg_kms(mgr->dev, "initing vcpi for pbn=%d slots=%d\n", pbn, port->vcpi.num_slots); + + /* Keep port allocated until its payload has been removed */ + drm_dp_mst_get_port_malloc(port); + drm_dp_mst_topology_put_port(port); + return true; +out: + return false; +} +EXPORT_SYMBOL(drm_dp_mst_allocate_vcpi); + +int drm_dp_mst_get_vcpi_slots(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port) +{ + int slots = 0; + + port = drm_dp_mst_topology_get_port_validated(mgr, port); + if (!port) + return slots; + + slots = port->vcpi.num_slots; + drm_dp_mst_topology_put_port(port); + return slots; +} +EXPORT_SYMBOL(drm_dp_mst_get_vcpi_slots); + +/** + * drm_dp_mst_reset_vcpi_slots() - Reset number of slots to 0 for VCPI + * @mgr: manager for this port + * @port: unverified pointer to a port. + * + * This just resets the number of slots for the ports VCPI for later programming. + */ +void drm_dp_mst_reset_vcpi_slots(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port) +{ + /* + * A port with VCPI will remain allocated until its VCPI is + * released, no verified ref needed + */ + + port->vcpi.num_slots = 0; +} +EXPORT_SYMBOL(drm_dp_mst_reset_vcpi_slots); + +/** + * drm_dp_mst_deallocate_vcpi() - deallocate a VCPI + * @mgr: manager for this port + * @port: port to deallocate vcpi for + * + * This can be called unconditionally, regardless of whether + * drm_dp_mst_allocate_vcpi() succeeded or not. + */ +void drm_dp_mst_deallocate_vcpi(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port) +{ + bool skip; + + if (!port->vcpi.vcpi) + return; + + mutex_lock(&mgr->lock); + skip = !drm_dp_mst_port_downstream_of_branch(port, mgr->mst_primary); + mutex_unlock(&mgr->lock); + + if (skip) + return; + + drm_dp_mst_put_payload_id(mgr, port->vcpi.vcpi); + port->vcpi.num_slots = 0; + port->vcpi.pbn = 0; + port->vcpi.aligned_pbn = 0; + port->vcpi.vcpi = 0; + drm_dp_mst_put_port_malloc(port); +} +EXPORT_SYMBOL(drm_dp_mst_deallocate_vcpi); + +static int drm_dp_dpcd_write_payload(struct drm_dp_mst_topology_mgr *mgr, + int id, struct drm_dp_payload *payload) +{ + u8 payload_alloc[3], status; + int ret; + int retries = 0; + + drm_dp_dpcd_writeb(mgr->aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, + DP_PAYLOAD_TABLE_UPDATED); + + payload_alloc[0] = id; + payload_alloc[1] = payload->start_slot; + payload_alloc[2] = payload->num_slots; + + ret = drm_dp_dpcd_write(mgr->aux, DP_PAYLOAD_ALLOCATE_SET, payload_alloc, 3); + if (ret != 3) { + drm_dbg_kms(mgr->dev, "failed to write payload allocation %d\n", ret); + goto fail; + } + +retry: + ret = drm_dp_dpcd_readb(mgr->aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, &status); + if (ret < 0) { + drm_dbg_kms(mgr->dev, "failed to read payload table status %d\n", ret); + goto fail; + } + + if (!(status & DP_PAYLOAD_TABLE_UPDATED)) { + retries++; + if (retries < 20) { + usleep_range(10000, 20000); + goto retry; + } + drm_dbg_kms(mgr->dev, "status not set after read payload table status %d\n", + status); + ret = -EINVAL; + goto fail; + } + ret = 0; +fail: + return ret; +} + +static int do_get_act_status(struct drm_dp_aux *aux) +{ + int ret; + u8 status; + + ret = drm_dp_dpcd_readb(aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, &status); + if (ret < 0) + return ret; + + return status; +} + +/** + * drm_dp_check_act_status() - Polls for ACT handled status. + * @mgr: manager to use + * + * Tries waiting for the MST hub to finish updating it's payload table by + * polling for the ACT handled bit for up to 3 seconds (yes-some hubs really + * take that long). + * + * Returns: + * 0 if the ACT was handled in time, negative error code on failure. + */ +int drm_dp_check_act_status(struct drm_dp_mst_topology_mgr *mgr) +{ + /* + * There doesn't seem to be any recommended retry count or timeout in + * the MST specification. Since some hubs have been observed to take + * over 1 second to update their payload allocations under certain + * conditions, we use a rather large timeout value. + */ + const int timeout_ms = 3000; + int ret, status; + + ret = readx_poll_timeout(do_get_act_status, mgr->aux, status, + status & DP_PAYLOAD_ACT_HANDLED || status < 0, + 200, timeout_ms * USEC_PER_MSEC); + if (ret < 0 && status >= 0) { + drm_err(mgr->dev, "Failed to get ACT after %dms, last status: %02x\n", + timeout_ms, status); + return -EINVAL; + } else if (status < 0) { + /* + * Failure here isn't unexpected - the hub may have + * just been unplugged + */ + drm_dbg_kms(mgr->dev, "Failed to read payload table status: %d\n", status); + return status; + } + + return 0; +} +EXPORT_SYMBOL(drm_dp_check_act_status); + +/** + * drm_dp_calc_pbn_mode() - Calculate the PBN for a mode. + * @clock: dot clock for the mode + * @bpp: bpp for the mode. + * @dsc: DSC mode. If true, bpp has units of 1/16 of a bit per pixel + * + * This uses the formula in the spec to calculate the PBN value for a mode. + */ +int drm_dp_calc_pbn_mode(int clock, int bpp, bool dsc) +{ + /* + * margin 5300ppm + 300ppm ~ 0.6% as per spec, factor is 1.006 + * The unit of 54/64Mbytes/sec is an arbitrary unit chosen based on + * common multiplier to render an integer PBN for all link rate/lane + * counts combinations + * calculate + * peak_kbps *= (1006/1000) + * peak_kbps *= (64/54) + * peak_kbps *= 8 convert to bytes + * + * If the bpp is in units of 1/16, further divide by 16. Put this + * factor in the numerator rather than the denominator to avoid + * integer overflow + */ + + if (dsc) + return DIV_ROUND_UP_ULL(mul_u32_u32(clock * (bpp / 16), 64 * 1006), + 8 * 54 * 1000 * 1000); + + return DIV_ROUND_UP_ULL(mul_u32_u32(clock * bpp, 64 * 1006), + 8 * 54 * 1000 * 1000); +} +EXPORT_SYMBOL(drm_dp_calc_pbn_mode); + +/* we want to kick the TX after we've ack the up/down IRQs. */ +static void drm_dp_mst_kick_tx(struct drm_dp_mst_topology_mgr *mgr) +{ + queue_work(system_long_wq, &mgr->tx_work); +} + +/* + * Helper function for parsing DP device types into convenient strings + * for use with dp_mst_topology + */ +static const char *pdt_to_string(u8 pdt) +{ + switch (pdt) { + case DP_PEER_DEVICE_NONE: + return "NONE"; + case DP_PEER_DEVICE_SOURCE_OR_SST: + return "SOURCE OR SST"; + case DP_PEER_DEVICE_MST_BRANCHING: + return "MST BRANCHING"; + case DP_PEER_DEVICE_SST_SINK: + return "SST SINK"; + case DP_PEER_DEVICE_DP_LEGACY_CONV: + return "DP LEGACY CONV"; + default: + return "ERR"; + } +} + +static void drm_dp_mst_dump_mstb(struct seq_file *m, + struct drm_dp_mst_branch *mstb) +{ + struct drm_dp_mst_port *port; + int tabs = mstb->lct; + char prefix[10]; + int i; + + for (i = 0; i < tabs; i++) + prefix[i] = '\t'; + prefix[i] = '\0'; + + seq_printf(m, "%smstb - [%p]: num_ports: %d\n", prefix, mstb, mstb->num_ports); + list_for_each_entry(port, &mstb->ports, next) { + seq_printf(m, "%sport %d - [%p] (%s - %s): ddps: %d, ldps: %d, sdp: %d/%d, fec: %s, conn: %p\n", + prefix, + port->port_num, + port, + port->input ? "input" : "output", + pdt_to_string(port->pdt), + port->ddps, + port->ldps, + port->num_sdp_streams, + port->num_sdp_stream_sinks, + port->fec_capable ? "true" : "false", + port->connector); + if (port->mstb) + drm_dp_mst_dump_mstb(m, port->mstb); + } +} + +#define DP_PAYLOAD_TABLE_SIZE 64 + +static bool dump_dp_payload_table(struct drm_dp_mst_topology_mgr *mgr, + char *buf) +{ + int i; + + for (i = 0; i < DP_PAYLOAD_TABLE_SIZE; i += 16) { + if (drm_dp_dpcd_read(mgr->aux, + DP_PAYLOAD_TABLE_UPDATE_STATUS + i, + &buf[i], 16) != 16) + return false; + } + return true; +} + +static void fetch_monitor_name(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, char *name, + int namelen) +{ + struct edid *mst_edid; + + mst_edid = drm_dp_mst_get_edid(port->connector, mgr, port); + drm_edid_get_monitor_name(mst_edid, name, namelen); +} + +/** + * drm_dp_mst_dump_topology(): dump topology to seq file. + * @m: seq_file to dump output to + * @mgr: manager to dump current topology for. + * + * helper to dump MST topology to a seq file for debugfs. + */ +void drm_dp_mst_dump_topology(struct seq_file *m, + struct drm_dp_mst_topology_mgr *mgr) +{ + int i; + struct drm_dp_mst_port *port; + + mutex_lock(&mgr->lock); + if (mgr->mst_primary) + drm_dp_mst_dump_mstb(m, mgr->mst_primary); + + /* dump VCPIs */ + mutex_unlock(&mgr->lock); + + mutex_lock(&mgr->payload_lock); + seq_printf(m, "\n*** VCPI Info ***\n"); + seq_printf(m, "payload_mask: %lx, vcpi_mask: %lx, max_payloads: %d\n", mgr->payload_mask, mgr->vcpi_mask, mgr->max_payloads); + + seq_printf(m, "\n| idx | port # | vcp_id | # slots | sink name |\n"); + for (i = 0; i < mgr->max_payloads; i++) { + if (mgr->proposed_vcpis[i]) { + char name[14]; + + port = container_of(mgr->proposed_vcpis[i], struct drm_dp_mst_port, vcpi); + fetch_monitor_name(mgr, port, name, sizeof(name)); + seq_printf(m, "%10d%10d%10d%10d%20s\n", + i, + port->port_num, + port->vcpi.vcpi, + port->vcpi.num_slots, + (*name != 0) ? name : "Unknown"); + } else + seq_printf(m, "%6d - Unused\n", i); + } + seq_printf(m, "\n*** Payload Info ***\n"); + seq_printf(m, "| idx | state | start slot | # slots |\n"); + for (i = 0; i < mgr->max_payloads; i++) { + seq_printf(m, "%10d%10d%15d%10d\n", + i, + mgr->payloads[i].payload_state, + mgr->payloads[i].start_slot, + mgr->payloads[i].num_slots); + } + mutex_unlock(&mgr->payload_lock); + + seq_printf(m, "\n*** DPCD Info ***\n"); + mutex_lock(&mgr->lock); + if (mgr->mst_primary) { + u8 buf[DP_PAYLOAD_TABLE_SIZE]; + int ret; + + ret = drm_dp_dpcd_read(mgr->aux, DP_DPCD_REV, buf, DP_RECEIVER_CAP_SIZE); + if (ret) { + seq_printf(m, "dpcd read failed\n"); + goto out; + } + seq_printf(m, "dpcd: %*ph\n", DP_RECEIVER_CAP_SIZE, buf); + + ret = drm_dp_dpcd_read(mgr->aux, DP_FAUX_CAP, buf, 2); + if (ret) { + seq_printf(m, "faux/mst read failed\n"); + goto out; + } + seq_printf(m, "faux/mst: %*ph\n", 2, buf); + + ret = drm_dp_dpcd_read(mgr->aux, DP_MSTM_CTRL, buf, 1); + if (ret) { + seq_printf(m, "mst ctrl read failed\n"); + goto out; + } + seq_printf(m, "mst ctrl: %*ph\n", 1, buf); + + /* dump the standard OUI branch header */ + ret = drm_dp_dpcd_read(mgr->aux, DP_BRANCH_OUI, buf, DP_BRANCH_OUI_HEADER_SIZE); + if (ret) { + seq_printf(m, "branch oui read failed\n"); + goto out; + } + seq_printf(m, "branch oui: %*phN devid: ", 3, buf); + + for (i = 0x3; i < 0x8 && buf[i]; i++) + seq_printf(m, "%c", buf[i]); + seq_printf(m, " revision: hw: %x.%x sw: %x.%x\n", + buf[0x9] >> 4, buf[0x9] & 0xf, buf[0xa], buf[0xb]); + if (dump_dp_payload_table(mgr, buf)) + seq_printf(m, "payload table: %*ph\n", DP_PAYLOAD_TABLE_SIZE, buf); + } + +out: + mutex_unlock(&mgr->lock); + +} +EXPORT_SYMBOL(drm_dp_mst_dump_topology); + +static void drm_dp_tx_work(struct work_struct *work) +{ + struct drm_dp_mst_topology_mgr *mgr = container_of(work, struct drm_dp_mst_topology_mgr, tx_work); + + mutex_lock(&mgr->qlock); + if (!list_empty(&mgr->tx_msg_downq)) + process_single_down_tx_qlock(mgr); + mutex_unlock(&mgr->qlock); +} + +static inline void +drm_dp_delayed_destroy_port(struct drm_dp_mst_port *port) +{ + drm_dp_port_set_pdt(port, DP_PEER_DEVICE_NONE, port->mcs); + + if (port->connector) { + drm_connector_unregister(port->connector); + drm_connector_put(port->connector); + } + + drm_dp_mst_put_port_malloc(port); +} + +static inline void +drm_dp_delayed_destroy_mstb(struct drm_dp_mst_branch *mstb) +{ + struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; + struct drm_dp_mst_port *port, *port_tmp; + struct drm_dp_sideband_msg_tx *txmsg, *txmsg_tmp; + bool wake_tx = false; + + mutex_lock(&mgr->lock); + list_for_each_entry_safe(port, port_tmp, &mstb->ports, next) { + list_del(&port->next); + drm_dp_mst_topology_put_port(port); + } + mutex_unlock(&mgr->lock); + + /* drop any tx slot msg */ + mutex_lock(&mstb->mgr->qlock); + list_for_each_entry_safe(txmsg, txmsg_tmp, &mgr->tx_msg_downq, next) { + if (txmsg->dst != mstb) + continue; + + txmsg->state = DRM_DP_SIDEBAND_TX_TIMEOUT; + list_del(&txmsg->next); + wake_tx = true; + } + mutex_unlock(&mstb->mgr->qlock); + + if (wake_tx) + wake_up_all(&mstb->mgr->tx_waitq); + + drm_dp_mst_put_mstb_malloc(mstb); +} + +static void drm_dp_delayed_destroy_work(struct work_struct *work) +{ + struct drm_dp_mst_topology_mgr *mgr = + container_of(work, struct drm_dp_mst_topology_mgr, + delayed_destroy_work); + bool send_hotplug = false, go_again; + + /* + * Not a regular list traverse as we have to drop the destroy + * connector lock before destroying the mstb/port, to avoid AB->BA + * ordering between this lock and the config mutex. + */ + do { + go_again = false; + + for (;;) { + struct drm_dp_mst_branch *mstb; + + mutex_lock(&mgr->delayed_destroy_lock); + mstb = list_first_entry_or_null(&mgr->destroy_branch_device_list, + struct drm_dp_mst_branch, + destroy_next); + if (mstb) + list_del(&mstb->destroy_next); + mutex_unlock(&mgr->delayed_destroy_lock); + + if (!mstb) + break; + + drm_dp_delayed_destroy_mstb(mstb); + go_again = true; + } + + for (;;) { + struct drm_dp_mst_port *port; + + mutex_lock(&mgr->delayed_destroy_lock); + port = list_first_entry_or_null(&mgr->destroy_port_list, + struct drm_dp_mst_port, + next); + if (port) + list_del(&port->next); + mutex_unlock(&mgr->delayed_destroy_lock); + + if (!port) + break; + + drm_dp_delayed_destroy_port(port); + send_hotplug = true; + go_again = true; + } + } while (go_again); + + if (send_hotplug) + drm_kms_helper_hotplug_event(mgr->dev); +} + +static struct drm_private_state * +drm_dp_mst_duplicate_state(struct drm_private_obj *obj) +{ + struct drm_dp_mst_topology_state *state, *old_state = + to_dp_mst_topology_state(obj->state); + struct drm_dp_vcpi_allocation *pos, *vcpi; + + state = kmemdup(old_state, sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + + __drm_atomic_helper_private_obj_duplicate_state(obj, &state->base); + + INIT_LIST_HEAD(&state->vcpis); + + list_for_each_entry(pos, &old_state->vcpis, next) { + /* Prune leftover freed VCPI allocations */ + if (!pos->vcpi) + continue; + + vcpi = kmemdup(pos, sizeof(*vcpi), GFP_KERNEL); + if (!vcpi) + goto fail; + + drm_dp_mst_get_port_malloc(vcpi->port); + list_add(&vcpi->next, &state->vcpis); + } + + return &state->base; + +fail: + list_for_each_entry_safe(pos, vcpi, &state->vcpis, next) { + drm_dp_mst_put_port_malloc(pos->port); + kfree(pos); + } + kfree(state); + + return NULL; +} + +static void drm_dp_mst_destroy_state(struct drm_private_obj *obj, + struct drm_private_state *state) +{ + struct drm_dp_mst_topology_state *mst_state = + to_dp_mst_topology_state(state); + struct drm_dp_vcpi_allocation *pos, *tmp; + + list_for_each_entry_safe(pos, tmp, &mst_state->vcpis, next) { + /* We only keep references to ports with non-zero VCPIs */ + if (pos->vcpi) + drm_dp_mst_put_port_malloc(pos->port); + kfree(pos); + } + + kfree(mst_state); +} + +static bool drm_dp_mst_port_downstream_of_branch(struct drm_dp_mst_port *port, + struct drm_dp_mst_branch *branch) +{ + while (port->parent) { + if (port->parent == branch) + return true; + + if (port->parent->port_parent) + port = port->parent->port_parent; + else + break; + } + return false; +} + +static int +drm_dp_mst_atomic_check_port_bw_limit(struct drm_dp_mst_port *port, + struct drm_dp_mst_topology_state *state); + +static int +drm_dp_mst_atomic_check_mstb_bw_limit(struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_topology_state *state) +{ + struct drm_dp_vcpi_allocation *vcpi; + struct drm_dp_mst_port *port; + int pbn_used = 0, ret; + bool found = false; + + /* Check that we have at least one port in our state that's downstream + * of this branch, otherwise we can skip this branch + */ + list_for_each_entry(vcpi, &state->vcpis, next) { + if (!vcpi->pbn || + !drm_dp_mst_port_downstream_of_branch(vcpi->port, mstb)) + continue; + + found = true; + break; + } + if (!found) + return 0; + + if (mstb->port_parent) + drm_dbg_atomic(mstb->mgr->dev, + "[MSTB:%p] [MST PORT:%p] Checking bandwidth limits on [MSTB:%p]\n", + mstb->port_parent->parent, mstb->port_parent, mstb); + else + drm_dbg_atomic(mstb->mgr->dev, "[MSTB:%p] Checking bandwidth limits\n", mstb); + + list_for_each_entry(port, &mstb->ports, next) { + ret = drm_dp_mst_atomic_check_port_bw_limit(port, state); + if (ret < 0) + return ret; + + pbn_used += ret; + } + + return pbn_used; +} + +static int +drm_dp_mst_atomic_check_port_bw_limit(struct drm_dp_mst_port *port, + struct drm_dp_mst_topology_state *state) +{ + struct drm_dp_vcpi_allocation *vcpi; + int pbn_used = 0; + + if (port->pdt == DP_PEER_DEVICE_NONE) + return 0; + + if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) { + bool found = false; + + list_for_each_entry(vcpi, &state->vcpis, next) { + if (vcpi->port != port) + continue; + if (!vcpi->pbn) + return 0; + + found = true; + break; + } + if (!found) + return 0; + + /* + * This could happen if the sink deasserted its HPD line, but + * the branch device still reports it as attached (PDT != NONE). + */ + if (!port->full_pbn) { + drm_dbg_atomic(port->mgr->dev, + "[MSTB:%p] [MST PORT:%p] no BW available for the port\n", + port->parent, port); + return -EINVAL; + } + + pbn_used = vcpi->pbn; + } else { + pbn_used = drm_dp_mst_atomic_check_mstb_bw_limit(port->mstb, + state); + if (pbn_used <= 0) + return pbn_used; + } + + if (pbn_used > port->full_pbn) { + drm_dbg_atomic(port->mgr->dev, + "[MSTB:%p] [MST PORT:%p] required PBN of %d exceeds port limit of %d\n", + port->parent, port, pbn_used, port->full_pbn); + return -ENOSPC; + } + + drm_dbg_atomic(port->mgr->dev, "[MSTB:%p] [MST PORT:%p] uses %d out of %d PBN\n", + port->parent, port, pbn_used, port->full_pbn); + + return pbn_used; +} + +static inline int +drm_dp_mst_atomic_check_vcpi_alloc_limit(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_topology_state *mst_state) +{ + struct drm_dp_vcpi_allocation *vcpi; + int avail_slots = mst_state->total_avail_slots, payload_count = 0; + + list_for_each_entry(vcpi, &mst_state->vcpis, next) { + /* Releasing VCPI is always OK-even if the port is gone */ + if (!vcpi->vcpi) { + drm_dbg_atomic(mgr->dev, "[MST PORT:%p] releases all VCPI slots\n", + vcpi->port); + continue; + } + + drm_dbg_atomic(mgr->dev, "[MST PORT:%p] requires %d vcpi slots\n", + vcpi->port, vcpi->vcpi); + + avail_slots -= vcpi->vcpi; + if (avail_slots < 0) { + drm_dbg_atomic(mgr->dev, + "[MST PORT:%p] not enough VCPI slots in mst state %p (avail=%d)\n", + vcpi->port, mst_state, avail_slots + vcpi->vcpi); + return -ENOSPC; + } + + if (++payload_count > mgr->max_payloads) { + drm_dbg_atomic(mgr->dev, + "[MST MGR:%p] state %p has too many payloads (max=%d)\n", + mgr, mst_state, mgr->max_payloads); + return -EINVAL; + } + } + drm_dbg_atomic(mgr->dev, "[MST MGR:%p] mst state %p VCPI avail=%d used=%d\n", + mgr, mst_state, avail_slots, mst_state->total_avail_slots - avail_slots); + + return 0; +} + +/** + * drm_dp_mst_add_affected_dsc_crtcs + * @state: Pointer to the new struct drm_dp_mst_topology_state + * @mgr: MST topology manager + * + * Whenever there is a change in mst topology + * DSC configuration would have to be recalculated + * therefore we need to trigger modeset on all affected + * CRTCs in that topology + * + * See also: + * drm_dp_mst_atomic_enable_dsc() + */ +int drm_dp_mst_add_affected_dsc_crtcs(struct drm_atomic_state *state, struct drm_dp_mst_topology_mgr *mgr) +{ + struct drm_dp_mst_topology_state *mst_state; + struct drm_dp_vcpi_allocation *pos; + struct drm_connector *connector; + struct drm_connector_state *conn_state; + struct drm_crtc *crtc; + struct drm_crtc_state *crtc_state; + + mst_state = drm_atomic_get_mst_topology_state(state, mgr); + + if (IS_ERR(mst_state)) + return -EINVAL; + + list_for_each_entry(pos, &mst_state->vcpis, next) { + + connector = pos->port->connector; + + if (!connector) + return -EINVAL; + + conn_state = drm_atomic_get_connector_state(state, connector); + + if (IS_ERR(conn_state)) + return PTR_ERR(conn_state); + + crtc = conn_state->crtc; + + if (!crtc) + continue; + + if (!drm_dp_mst_dsc_aux_for_port(pos->port)) + continue; + + crtc_state = drm_atomic_get_crtc_state(mst_state->base.state, crtc); + + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + drm_dbg_atomic(mgr->dev, "[MST MGR:%p] Setting mode_changed flag on CRTC %p\n", + mgr, crtc); + + crtc_state->mode_changed = true; + } + return 0; +} +EXPORT_SYMBOL(drm_dp_mst_add_affected_dsc_crtcs); + +/** + * drm_dp_mst_atomic_enable_dsc - Set DSC Enable Flag to On/Off + * @state: Pointer to the new drm_atomic_state + * @port: Pointer to the affected MST Port + * @pbn: Newly recalculated bw required for link with DSC enabled + * @pbn_div: Divider to calculate correct number of pbn per slot + * @enable: Boolean flag to enable or disable DSC on the port + * + * This function enables DSC on the given Port + * by recalculating its vcpi from pbn provided + * and sets dsc_enable flag to keep track of which + * ports have DSC enabled + * + */ +int drm_dp_mst_atomic_enable_dsc(struct drm_atomic_state *state, + struct drm_dp_mst_port *port, + int pbn, int pbn_div, + bool enable) +{ + struct drm_dp_mst_topology_state *mst_state; + struct drm_dp_vcpi_allocation *pos; + bool found = false; + int vcpi = 0; + + mst_state = drm_atomic_get_mst_topology_state(state, port->mgr); + + if (IS_ERR(mst_state)) + return PTR_ERR(mst_state); + + list_for_each_entry(pos, &mst_state->vcpis, next) { + if (pos->port == port) { + found = true; + break; + } + } + + if (!found) { + drm_dbg_atomic(state->dev, + "[MST PORT:%p] Couldn't find VCPI allocation in mst state %p\n", + port, mst_state); + return -EINVAL; + } + + if (pos->dsc_enabled == enable) { + drm_dbg_atomic(state->dev, + "[MST PORT:%p] DSC flag is already set to %d, returning %d VCPI slots\n", + port, enable, pos->vcpi); + vcpi = pos->vcpi; + } + + if (enable) { + vcpi = drm_dp_atomic_find_vcpi_slots(state, port->mgr, port, pbn, pbn_div); + drm_dbg_atomic(state->dev, + "[MST PORT:%p] Enabling DSC flag, reallocating %d VCPI slots on the port\n", + port, vcpi); + if (vcpi < 0) + return -EINVAL; + } + + pos->dsc_enabled = enable; + + return vcpi; +} +EXPORT_SYMBOL(drm_dp_mst_atomic_enable_dsc); +/** + * drm_dp_mst_atomic_check - Check that the new state of an MST topology in an + * atomic update is valid + * @state: Pointer to the new &struct drm_dp_mst_topology_state + * + * Checks the given topology state for an atomic update to ensure that it's + * valid. This includes checking whether there's enough bandwidth to support + * the new VCPI allocations in the atomic update. + * + * Any atomic drivers supporting DP MST must make sure to call this after + * checking the rest of their state in their + * &drm_mode_config_funcs.atomic_check() callback. + * + * See also: + * drm_dp_atomic_find_vcpi_slots() + * drm_dp_atomic_release_vcpi_slots() + * + * Returns: + * + * 0 if the new state is valid, negative error code otherwise. + */ +int drm_dp_mst_atomic_check(struct drm_atomic_state *state) +{ + struct drm_dp_mst_topology_mgr *mgr; + struct drm_dp_mst_topology_state *mst_state; + int i, ret = 0; + + for_each_new_mst_mgr_in_state(state, mgr, mst_state, i) { + if (!mgr->mst_state) + continue; + + ret = drm_dp_mst_atomic_check_vcpi_alloc_limit(mgr, mst_state); + if (ret) + break; + + mutex_lock(&mgr->lock); + ret = drm_dp_mst_atomic_check_mstb_bw_limit(mgr->mst_primary, + mst_state); + mutex_unlock(&mgr->lock); + if (ret < 0) + break; + else + ret = 0; + } + + return ret; +} +EXPORT_SYMBOL(drm_dp_mst_atomic_check); + +const struct drm_private_state_funcs drm_dp_mst_topology_state_funcs = { + .atomic_duplicate_state = drm_dp_mst_duplicate_state, + .atomic_destroy_state = drm_dp_mst_destroy_state, +}; +EXPORT_SYMBOL(drm_dp_mst_topology_state_funcs); + +/** + * drm_atomic_get_mst_topology_state: get MST topology state + * + * @state: global atomic state + * @mgr: MST topology manager, also the private object in this case + * + * This function wraps drm_atomic_get_priv_obj_state() passing in the MST atomic + * state vtable so that the private object state returned is that of a MST + * topology object. Also, drm_atomic_get_private_obj_state() expects the caller + * to care of the locking, so warn if don't hold the connection_mutex. + * + * RETURNS: + * + * The MST topology state or error pointer. + */ +struct drm_dp_mst_topology_state *drm_atomic_get_mst_topology_state(struct drm_atomic_state *state, + struct drm_dp_mst_topology_mgr *mgr) +{ + return to_dp_mst_topology_state(drm_atomic_get_private_obj_state(state, &mgr->base)); +} +EXPORT_SYMBOL(drm_atomic_get_mst_topology_state); + +/** + * drm_dp_mst_topology_mgr_init - initialise a topology manager + * @mgr: manager struct to initialise + * @dev: device providing this structure - for i2c addition. + * @aux: DP helper aux channel to talk to this device + * @max_dpcd_transaction_bytes: hw specific DPCD transaction limit + * @max_payloads: maximum number of payloads this GPU can source + * @max_lane_count: maximum number of lanes this GPU supports + * @max_link_rate: maximum link rate per lane this GPU supports in kHz + * @conn_base_id: the connector object ID the MST device is connected to. + * + * Return 0 for success, or negative error code on failure + */ +int drm_dp_mst_topology_mgr_init(struct drm_dp_mst_topology_mgr *mgr, + struct drm_device *dev, struct drm_dp_aux *aux, + int max_dpcd_transaction_bytes, int max_payloads, + int max_lane_count, int max_link_rate, + int conn_base_id) +{ + struct drm_dp_mst_topology_state *mst_state; + + mutex_init(&mgr->lock); + mutex_init(&mgr->qlock); + mutex_init(&mgr->payload_lock); + mutex_init(&mgr->delayed_destroy_lock); + mutex_init(&mgr->up_req_lock); + mutex_init(&mgr->probe_lock); +#if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS) + mutex_init(&mgr->topology_ref_history_lock); +#endif + INIT_LIST_HEAD(&mgr->tx_msg_downq); + INIT_LIST_HEAD(&mgr->destroy_port_list); + INIT_LIST_HEAD(&mgr->destroy_branch_device_list); + INIT_LIST_HEAD(&mgr->up_req_list); + + /* + * delayed_destroy_work will be queued on a dedicated WQ, so that any + * requeuing will be also flushed when deiniting the topology manager. + */ + mgr->delayed_destroy_wq = alloc_ordered_workqueue("drm_dp_mst_wq", 0); + if (mgr->delayed_destroy_wq == NULL) + return -ENOMEM; + + INIT_WORK(&mgr->work, drm_dp_mst_link_probe_work); + INIT_WORK(&mgr->tx_work, drm_dp_tx_work); + INIT_WORK(&mgr->delayed_destroy_work, drm_dp_delayed_destroy_work); + INIT_WORK(&mgr->up_req_work, drm_dp_mst_up_req_work); + init_waitqueue_head(&mgr->tx_waitq); + mgr->dev = dev; + mgr->aux = aux; + mgr->max_dpcd_transaction_bytes = max_dpcd_transaction_bytes; + mgr->max_payloads = max_payloads; + mgr->max_lane_count = max_lane_count; + mgr->max_link_rate = max_link_rate; + mgr->conn_base_id = conn_base_id; + if (max_payloads + 1 > sizeof(mgr->payload_mask) * 8 || + max_payloads + 1 > sizeof(mgr->vcpi_mask) * 8) + return -EINVAL; + mgr->payloads = kcalloc(max_payloads, sizeof(struct drm_dp_payload), GFP_KERNEL); + if (!mgr->payloads) + return -ENOMEM; + mgr->proposed_vcpis = kcalloc(max_payloads, sizeof(struct drm_dp_vcpi *), GFP_KERNEL); + if (!mgr->proposed_vcpis) + return -ENOMEM; + set_bit(0, &mgr->payload_mask); + + mst_state = kzalloc(sizeof(*mst_state), GFP_KERNEL); + if (mst_state == NULL) + return -ENOMEM; + + mst_state->total_avail_slots = 63; + mst_state->start_slot = 1; + + mst_state->mgr = mgr; + INIT_LIST_HEAD(&mst_state->vcpis); + + drm_atomic_private_obj_init(dev, &mgr->base, + &mst_state->base, + &drm_dp_mst_topology_state_funcs); + + return 0; +} +EXPORT_SYMBOL(drm_dp_mst_topology_mgr_init); + +/** + * drm_dp_mst_topology_mgr_destroy() - destroy topology manager. + * @mgr: manager to destroy + */ +void drm_dp_mst_topology_mgr_destroy(struct drm_dp_mst_topology_mgr *mgr) +{ + drm_dp_mst_topology_mgr_set_mst(mgr, false); + flush_work(&mgr->work); + /* The following will also drain any requeued work on the WQ. */ + if (mgr->delayed_destroy_wq) { + destroy_workqueue(mgr->delayed_destroy_wq); + mgr->delayed_destroy_wq = NULL; + } + mutex_lock(&mgr->payload_lock); + kfree(mgr->payloads); + mgr->payloads = NULL; + kfree(mgr->proposed_vcpis); + mgr->proposed_vcpis = NULL; + mutex_unlock(&mgr->payload_lock); + mgr->dev = NULL; + mgr->aux = NULL; + drm_atomic_private_obj_fini(&mgr->base); + mgr->funcs = NULL; + + mutex_destroy(&mgr->delayed_destroy_lock); + mutex_destroy(&mgr->payload_lock); + mutex_destroy(&mgr->qlock); + mutex_destroy(&mgr->lock); + mutex_destroy(&mgr->up_req_lock); + mutex_destroy(&mgr->probe_lock); +#if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS) + mutex_destroy(&mgr->topology_ref_history_lock); +#endif +} +EXPORT_SYMBOL(drm_dp_mst_topology_mgr_destroy); + +static bool remote_i2c_read_ok(const struct i2c_msg msgs[], int num) +{ + int i; + + if (num - 1 > DP_REMOTE_I2C_READ_MAX_TRANSACTIONS) + return false; + + for (i = 0; i < num - 1; i++) { + if (msgs[i].flags & I2C_M_RD || + msgs[i].len > 0xff) + return false; + } + + return msgs[num - 1].flags & I2C_M_RD && + msgs[num - 1].len <= 0xff; +} + +static bool remote_i2c_write_ok(const struct i2c_msg msgs[], int num) +{ + int i; + + for (i = 0; i < num - 1; i++) { + if (msgs[i].flags & I2C_M_RD || !(msgs[i].flags & I2C_M_STOP) || + msgs[i].len > 0xff) + return false; + } + + return !(msgs[num - 1].flags & I2C_M_RD) && msgs[num - 1].len <= 0xff; +} + +static int drm_dp_mst_i2c_read(struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_port *port, + struct i2c_msg *msgs, int num) +{ + struct drm_dp_mst_topology_mgr *mgr = port->mgr; + unsigned int i; + struct drm_dp_sideband_msg_req_body msg; + struct drm_dp_sideband_msg_tx *txmsg = NULL; + int ret; + + memset(&msg, 0, sizeof(msg)); + msg.req_type = DP_REMOTE_I2C_READ; + msg.u.i2c_read.num_transactions = num - 1; + msg.u.i2c_read.port_number = port->port_num; + for (i = 0; i < num - 1; i++) { + msg.u.i2c_read.transactions[i].i2c_dev_id = msgs[i].addr; + msg.u.i2c_read.transactions[i].num_bytes = msgs[i].len; + msg.u.i2c_read.transactions[i].bytes = msgs[i].buf; + msg.u.i2c_read.transactions[i].no_stop_bit = !(msgs[i].flags & I2C_M_STOP); + } + msg.u.i2c_read.read_i2c_device_id = msgs[num - 1].addr; + msg.u.i2c_read.num_bytes_read = msgs[num - 1].len; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) { + ret = -ENOMEM; + goto out; + } + + txmsg->dst = mstb; + drm_dp_encode_sideband_req(&msg, txmsg); + + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret > 0) { + + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { + ret = -EREMOTEIO; + goto out; + } + if (txmsg->reply.u.remote_i2c_read_ack.num_bytes != msgs[num - 1].len) { + ret = -EIO; + goto out; + } + memcpy(msgs[num - 1].buf, txmsg->reply.u.remote_i2c_read_ack.bytes, msgs[num - 1].len); + ret = num; + } +out: + kfree(txmsg); + return ret; +} + +static int drm_dp_mst_i2c_write(struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_port *port, + struct i2c_msg *msgs, int num) +{ + struct drm_dp_mst_topology_mgr *mgr = port->mgr; + unsigned int i; + struct drm_dp_sideband_msg_req_body msg; + struct drm_dp_sideband_msg_tx *txmsg = NULL; + int ret; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) { + ret = -ENOMEM; + goto out; + } + for (i = 0; i < num; i++) { + memset(&msg, 0, sizeof(msg)); + msg.req_type = DP_REMOTE_I2C_WRITE; + msg.u.i2c_write.port_number = port->port_num; + msg.u.i2c_write.write_i2c_device_id = msgs[i].addr; + msg.u.i2c_write.num_bytes = msgs[i].len; + msg.u.i2c_write.bytes = msgs[i].buf; + + memset(txmsg, 0, sizeof(*txmsg)); + txmsg->dst = mstb; + + drm_dp_encode_sideband_req(&msg, txmsg); + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret > 0) { + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { + ret = -EREMOTEIO; + goto out; + } + } else { + goto out; + } + } + ret = num; +out: + kfree(txmsg); + return ret; +} + +/* I2C device */ +static int drm_dp_mst_i2c_xfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, int num) +{ + struct drm_dp_aux *aux = adapter->algo_data; + struct drm_dp_mst_port *port = + container_of(aux, struct drm_dp_mst_port, aux); + struct drm_dp_mst_branch *mstb; + struct drm_dp_mst_topology_mgr *mgr = port->mgr; + int ret; + + mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent); + if (!mstb) + return -EREMOTEIO; + + if (remote_i2c_read_ok(msgs, num)) { + ret = drm_dp_mst_i2c_read(mstb, port, msgs, num); + } else if (remote_i2c_write_ok(msgs, num)) { + ret = drm_dp_mst_i2c_write(mstb, port, msgs, num); + } else { + drm_dbg_kms(mgr->dev, "Unsupported I2C transaction for MST device\n"); + ret = -EIO; + } + + drm_dp_mst_topology_put_mstb(mstb); + return ret; +} + +static u32 drm_dp_mst_i2c_functionality(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | + I2C_FUNC_SMBUS_READ_BLOCK_DATA | + I2C_FUNC_SMBUS_BLOCK_PROC_CALL | + I2C_FUNC_10BIT_ADDR; +} + +static const struct i2c_algorithm drm_dp_mst_i2c_algo = { + .functionality = drm_dp_mst_i2c_functionality, + .master_xfer = drm_dp_mst_i2c_xfer, +}; + +/** + * drm_dp_mst_register_i2c_bus() - register an I2C adapter for I2C-over-AUX + * @port: The port to add the I2C bus on + * + * Returns 0 on success or a negative error code on failure. + */ +static int drm_dp_mst_register_i2c_bus(struct drm_dp_mst_port *port) +{ + struct drm_dp_aux *aux = &port->aux; + struct device *parent_dev = port->mgr->dev->dev; + + aux->ddc.algo = &drm_dp_mst_i2c_algo; + aux->ddc.algo_data = aux; + aux->ddc.retries = 3; + + aux->ddc.class = I2C_CLASS_DDC; + aux->ddc.owner = THIS_MODULE; + /* FIXME: set the kdev of the port's connector as parent */ + aux->ddc.dev.parent = parent_dev; + aux->ddc.dev.of_node = parent_dev->of_node; + + strlcpy(aux->ddc.name, aux->name ? aux->name : dev_name(parent_dev), + sizeof(aux->ddc.name)); + + return i2c_add_adapter(&aux->ddc); +} + +/** + * drm_dp_mst_unregister_i2c_bus() - unregister an I2C-over-AUX adapter + * @port: The port to remove the I2C bus from + */ +static void drm_dp_mst_unregister_i2c_bus(struct drm_dp_mst_port *port) +{ + i2c_del_adapter(&port->aux.ddc); +} + +/** + * drm_dp_mst_is_virtual_dpcd() - Is the given port a virtual DP Peer Device + * @port: The port to check + * + * A single physical MST hub object can be represented in the topology + * by multiple branches, with virtual ports between those branches. + * + * As of DP1.4, An MST hub with internal (virtual) ports must expose + * certain DPCD registers over those ports. See sections 2.6.1.1.1 + * and 2.6.1.1.2 of Display Port specification v1.4 for details. + * + * May acquire mgr->lock + * + * Returns: + * true if the port is a virtual DP peer device, false otherwise + */ +static bool drm_dp_mst_is_virtual_dpcd(struct drm_dp_mst_port *port) +{ + struct drm_dp_mst_port *downstream_port; + + if (!port || port->dpcd_rev < DP_DPCD_REV_14) + return false; + + /* Virtual DP Sink (Internal Display Panel) */ + if (port->port_num >= 8) + return true; + + /* DP-to-HDMI Protocol Converter */ + if (port->pdt == DP_PEER_DEVICE_DP_LEGACY_CONV && + !port->mcs && + port->ldps) + return true; + + /* DP-to-DP */ + mutex_lock(&port->mgr->lock); + if (port->pdt == DP_PEER_DEVICE_MST_BRANCHING && + port->mstb && + port->mstb->num_ports == 2) { + list_for_each_entry(downstream_port, &port->mstb->ports, next) { + if (downstream_port->pdt == DP_PEER_DEVICE_SST_SINK && + !downstream_port->input) { + mutex_unlock(&port->mgr->lock); + return true; + } + } + } + mutex_unlock(&port->mgr->lock); + + return false; +} + +/** + * drm_dp_mst_dsc_aux_for_port() - Find the correct aux for DSC + * @port: The port to check. A leaf of the MST tree with an attached display. + * + * Depending on the situation, DSC may be enabled via the endpoint aux, + * the immediately upstream aux, or the connector's physical aux. + * + * This is both the correct aux to read DSC_CAPABILITY and the + * correct aux to write DSC_ENABLED. + * + * This operation can be expensive (up to four aux reads), so + * the caller should cache the return. + * + * Returns: + * NULL if DSC cannot be enabled on this port, otherwise the aux device + */ +struct drm_dp_aux *drm_dp_mst_dsc_aux_for_port(struct drm_dp_mst_port *port) +{ + struct drm_dp_mst_port *immediate_upstream_port; + struct drm_dp_mst_port *fec_port; + struct drm_dp_desc desc = {}; + u8 endpoint_fec; + u8 endpoint_dsc; + + if (!port) + return NULL; + + if (port->parent->port_parent) + immediate_upstream_port = port->parent->port_parent; + else + immediate_upstream_port = NULL; + + fec_port = immediate_upstream_port; + while (fec_port) { + /* + * Each physical link (i.e. not a virtual port) between the + * output and the primary device must support FEC + */ + if (!drm_dp_mst_is_virtual_dpcd(fec_port) && + !fec_port->fec_capable) + return NULL; + + fec_port = fec_port->parent->port_parent; + } + + /* DP-to-DP peer device */ + if (drm_dp_mst_is_virtual_dpcd(immediate_upstream_port)) { + u8 upstream_dsc; + + if (drm_dp_dpcd_read(&port->aux, + DP_DSC_SUPPORT, &endpoint_dsc, 1) != 1) + return NULL; + if (drm_dp_dpcd_read(&port->aux, + DP_FEC_CAPABILITY, &endpoint_fec, 1) != 1) + return NULL; + if (drm_dp_dpcd_read(&immediate_upstream_port->aux, + DP_DSC_SUPPORT, &upstream_dsc, 1) != 1) + return NULL; + + /* Enpoint decompression with DP-to-DP peer device */ + if ((endpoint_dsc & DP_DSC_DECOMPRESSION_IS_SUPPORTED) && + (endpoint_fec & DP_FEC_CAPABLE) && + (upstream_dsc & 0x2) /* DSC passthrough */) + return &port->aux; + + /* Virtual DPCD decompression with DP-to-DP peer device */ + return &immediate_upstream_port->aux; + } + + /* Virtual DPCD decompression with DP-to-HDMI or Virtual DP Sink */ + if (drm_dp_mst_is_virtual_dpcd(port)) + return &port->aux; + + /* + * Synaptics quirk + * Applies to ports for which: + * - Physical aux has Synaptics OUI + * - DPv1.4 or higher + * - Port is on primary branch device + * - Not a VGA adapter (DP_DWN_STRM_PORT_TYPE_ANALOG) + */ + if (drm_dp_read_desc(port->mgr->aux, &desc, true)) + return NULL; + + if (drm_dp_has_quirk(&desc, DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD) && + port->mgr->dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14 && + port->parent == port->mgr->mst_primary) { + u8 dpcd_ext[DP_RECEIVER_CAP_SIZE]; + + if (drm_dp_read_dpcd_caps(port->mgr->aux, dpcd_ext) < 0) + return NULL; + + if ((dpcd_ext[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT) && + ((dpcd_ext[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) + != DP_DWN_STRM_PORT_TYPE_ANALOG)) + return port->mgr->aux; + } + + /* + * The check below verifies if the MST sink + * connected to the GPU is capable of DSC - + * therefore the endpoint needs to be + * both DSC and FEC capable. + */ + if (drm_dp_dpcd_read(&port->aux, + DP_DSC_SUPPORT, &endpoint_dsc, 1) != 1) + return NULL; + if (drm_dp_dpcd_read(&port->aux, + DP_FEC_CAPABILITY, &endpoint_fec, 1) != 1) + return NULL; + if ((endpoint_dsc & DP_DSC_DECOMPRESSION_IS_SUPPORTED) && + (endpoint_fec & DP_FEC_CAPABLE)) + return &port->aux; + + return NULL; +} +EXPORT_SYMBOL(drm_dp_mst_dsc_aux_for_port); diff --git a/drivers/gpu/drm/dp/drm_dp_mst_topology_internal.h b/drivers/gpu/drm/dp/drm_dp_mst_topology_internal.h new file mode 100644 index 000000000000..eeda9a61c657 --- /dev/null +++ b/drivers/gpu/drm/dp/drm_dp_mst_topology_internal.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Declarations for DP MST related functions which are only used in selftests + * + * Copyright © 2018 Red Hat + * Authors: + * Lyude Paul + */ + +#ifndef _DRM_DP_MST_HELPER_INTERNAL_H_ +#define _DRM_DP_MST_HELPER_INTERNAL_H_ + +#include + +void +drm_dp_encode_sideband_req(const struct drm_dp_sideband_msg_req_body *req, + struct drm_dp_sideband_msg_tx *raw); +int drm_dp_decode_sideband_req(const struct drm_dp_sideband_msg_tx *raw, + struct drm_dp_sideband_msg_req_body *req); +void +drm_dp_dump_sideband_msg_req_body(const struct drm_dp_sideband_msg_req_body *req, + int indent, struct drm_printer *printer); + +#endif /* !_DRM_DP_MST_HELPER_INTERNAL_H_ */ diff --git a/drivers/gpu/drm/drm_dp_aux_dev.c b/drivers/gpu/drm/drm_dp_aux_dev.c deleted file mode 100644 index 0618dfe16660..000000000000 --- a/drivers/gpu/drm/drm_dp_aux_dev.c +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Copyright © 2015 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - * - * Authors: - * Rafael Antognolli - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "drm_dp_helper_internal.h" - -struct drm_dp_aux_dev { - unsigned index; - struct drm_dp_aux *aux; - struct device *dev; - struct kref refcount; - atomic_t usecount; -}; - -#define DRM_AUX_MINORS 256 -#define AUX_MAX_OFFSET (1 << 20) -static DEFINE_IDR(aux_idr); -static DEFINE_MUTEX(aux_idr_mutex); -static struct class *drm_dp_aux_dev_class; -static int drm_dev_major = -1; - -static struct drm_dp_aux_dev *drm_dp_aux_dev_get_by_minor(unsigned index) -{ - struct drm_dp_aux_dev *aux_dev = NULL; - - mutex_lock(&aux_idr_mutex); - aux_dev = idr_find(&aux_idr, index); - if (aux_dev && !kref_get_unless_zero(&aux_dev->refcount)) - aux_dev = NULL; - mutex_unlock(&aux_idr_mutex); - - return aux_dev; -} - -static struct drm_dp_aux_dev *alloc_drm_dp_aux_dev(struct drm_dp_aux *aux) -{ - struct drm_dp_aux_dev *aux_dev; - int index; - - aux_dev = kzalloc(sizeof(*aux_dev), GFP_KERNEL); - if (!aux_dev) - return ERR_PTR(-ENOMEM); - aux_dev->aux = aux; - atomic_set(&aux_dev->usecount, 1); - kref_init(&aux_dev->refcount); - - mutex_lock(&aux_idr_mutex); - index = idr_alloc(&aux_idr, aux_dev, 0, DRM_AUX_MINORS, GFP_KERNEL); - mutex_unlock(&aux_idr_mutex); - if (index < 0) { - kfree(aux_dev); - return ERR_PTR(index); - } - aux_dev->index = index; - - return aux_dev; -} - -static void release_drm_dp_aux_dev(struct kref *ref) -{ - struct drm_dp_aux_dev *aux_dev = - container_of(ref, struct drm_dp_aux_dev, refcount); - - kfree(aux_dev); -} - -static ssize_t name_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - ssize_t res; - struct drm_dp_aux_dev *aux_dev = - drm_dp_aux_dev_get_by_minor(MINOR(dev->devt)); - - if (!aux_dev) - return -ENODEV; - - res = sprintf(buf, "%s\n", aux_dev->aux->name); - kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); - - return res; -} -static DEVICE_ATTR_RO(name); - -static struct attribute *drm_dp_aux_attrs[] = { - &dev_attr_name.attr, - NULL, -}; -ATTRIBUTE_GROUPS(drm_dp_aux); - -static int auxdev_open(struct inode *inode, struct file *file) -{ - unsigned int minor = iminor(inode); - struct drm_dp_aux_dev *aux_dev; - - aux_dev = drm_dp_aux_dev_get_by_minor(minor); - if (!aux_dev) - return -ENODEV; - - file->private_data = aux_dev; - return 0; -} - -static loff_t auxdev_llseek(struct file *file, loff_t offset, int whence) -{ - return fixed_size_llseek(file, offset, whence, AUX_MAX_OFFSET); -} - -static ssize_t auxdev_read_iter(struct kiocb *iocb, struct iov_iter *to) -{ - struct drm_dp_aux_dev *aux_dev = iocb->ki_filp->private_data; - loff_t pos = iocb->ki_pos; - ssize_t res = 0; - - if (!atomic_inc_not_zero(&aux_dev->usecount)) - return -ENODEV; - - iov_iter_truncate(to, AUX_MAX_OFFSET - pos); - - while (iov_iter_count(to)) { - uint8_t buf[DP_AUX_MAX_PAYLOAD_BYTES]; - ssize_t todo = min(iov_iter_count(to), sizeof(buf)); - - if (signal_pending(current)) { - res = -ERESTARTSYS; - break; - } - - res = drm_dp_dpcd_read(aux_dev->aux, pos, buf, todo); - - if (res <= 0) - break; - - if (copy_to_iter(buf, res, to) != res) { - res = -EFAULT; - break; - } - - pos += res; - } - - if (pos != iocb->ki_pos) - res = pos - iocb->ki_pos; - iocb->ki_pos = pos; - - if (atomic_dec_and_test(&aux_dev->usecount)) - wake_up_var(&aux_dev->usecount); - - return res; -} - -static ssize_t auxdev_write_iter(struct kiocb *iocb, struct iov_iter *from) -{ - struct drm_dp_aux_dev *aux_dev = iocb->ki_filp->private_data; - loff_t pos = iocb->ki_pos; - ssize_t res = 0; - - if (!atomic_inc_not_zero(&aux_dev->usecount)) - return -ENODEV; - - iov_iter_truncate(from, AUX_MAX_OFFSET - pos); - - while (iov_iter_count(from)) { - uint8_t buf[DP_AUX_MAX_PAYLOAD_BYTES]; - ssize_t todo = min(iov_iter_count(from), sizeof(buf)); - - if (signal_pending(current)) { - res = -ERESTARTSYS; - break; - } - - if (!copy_from_iter_full(buf, todo, from)) { - res = -EFAULT; - break; - } - - res = drm_dp_dpcd_write(aux_dev->aux, pos, buf, todo); - - if (res <= 0) - break; - - pos += res; - } - - if (pos != iocb->ki_pos) - res = pos - iocb->ki_pos; - iocb->ki_pos = pos; - - if (atomic_dec_and_test(&aux_dev->usecount)) - wake_up_var(&aux_dev->usecount); - - return res; -} - -static int auxdev_release(struct inode *inode, struct file *file) -{ - struct drm_dp_aux_dev *aux_dev = file->private_data; - - kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); - return 0; -} - -static const struct file_operations auxdev_fops = { - .owner = THIS_MODULE, - .llseek = auxdev_llseek, - .read_iter = auxdev_read_iter, - .write_iter = auxdev_write_iter, - .open = auxdev_open, - .release = auxdev_release, -}; - -#define to_auxdev(d) container_of(d, struct drm_dp_aux_dev, aux) - -static struct drm_dp_aux_dev *drm_dp_aux_dev_get_by_aux(struct drm_dp_aux *aux) -{ - struct drm_dp_aux_dev *iter, *aux_dev = NULL; - int id; - - /* don't increase kref count here because this function should only be - * used by drm_dp_aux_unregister_devnode. Thus, it will always have at - * least one reference - the one that drm_dp_aux_register_devnode - * created - */ - mutex_lock(&aux_idr_mutex); - idr_for_each_entry(&aux_idr, iter, id) { - if (iter->aux == aux) { - aux_dev = iter; - break; - } - } - mutex_unlock(&aux_idr_mutex); - return aux_dev; -} - -void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux) -{ - struct drm_dp_aux_dev *aux_dev; - unsigned int minor; - - aux_dev = drm_dp_aux_dev_get_by_aux(aux); - if (!aux_dev) /* attach must have failed */ - return; - - /* - * As some AUX adapters may exist as platform devices which outlive their respective DRM - * devices, we clear drm_dev to ensure that we never accidentally reference a stale pointer - */ - aux->drm_dev = NULL; - - mutex_lock(&aux_idr_mutex); - idr_remove(&aux_idr, aux_dev->index); - mutex_unlock(&aux_idr_mutex); - - atomic_dec(&aux_dev->usecount); - wait_var_event(&aux_dev->usecount, !atomic_read(&aux_dev->usecount)); - - minor = aux_dev->index; - if (aux_dev->dev) - device_destroy(drm_dp_aux_dev_class, - MKDEV(drm_dev_major, minor)); - - DRM_DEBUG("drm_dp_aux_dev: aux [%s] unregistering\n", aux->name); - kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); -} - -int drm_dp_aux_register_devnode(struct drm_dp_aux *aux) -{ - struct drm_dp_aux_dev *aux_dev; - int res; - - aux_dev = alloc_drm_dp_aux_dev(aux); - if (IS_ERR(aux_dev)) - return PTR_ERR(aux_dev); - - aux_dev->dev = device_create(drm_dp_aux_dev_class, aux->dev, - MKDEV(drm_dev_major, aux_dev->index), NULL, - "drm_dp_aux%d", aux_dev->index); - if (IS_ERR(aux_dev->dev)) { - res = PTR_ERR(aux_dev->dev); - aux_dev->dev = NULL; - goto error; - } - - DRM_DEBUG("drm_dp_aux_dev: aux [%s] registered as minor %d\n", - aux->name, aux_dev->index); - return 0; -error: - drm_dp_aux_unregister_devnode(aux); - return res; -} - -int drm_dp_aux_dev_init(void) -{ - int res; - - drm_dp_aux_dev_class = class_create(THIS_MODULE, "drm_dp_aux_dev"); - if (IS_ERR(drm_dp_aux_dev_class)) { - return PTR_ERR(drm_dp_aux_dev_class); - } - drm_dp_aux_dev_class->dev_groups = drm_dp_aux_groups; - - res = register_chrdev(0, "aux", &auxdev_fops); - if (res < 0) - goto out; - drm_dev_major = res; - - return 0; -out: - class_destroy(drm_dp_aux_dev_class); - return res; -} - -void drm_dp_aux_dev_exit(void) -{ - unregister_chrdev(drm_dev_major, "aux"); - class_destroy(drm_dp_aux_dev_class); -} diff --git a/drivers/gpu/drm/drm_dp_cec.c b/drivers/gpu/drm/drm_dp_cec.c deleted file mode 100644 index 3ab2609f9ec7..000000000000 --- a/drivers/gpu/drm/drm_dp_cec.c +++ /dev/null @@ -1,451 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * DisplayPort CEC-Tunneling-over-AUX support - * - * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved. - */ - -#include -#include -#include - -#include - -#include -#include -#include - -/* - * Unfortunately it turns out that we have a chicken-and-egg situation - * here. Quite a few active (mini-)DP-to-HDMI or USB-C-to-HDMI adapters - * have a converter chip that supports CEC-Tunneling-over-AUX (usually the - * Parade PS176), but they do not wire up the CEC pin, thus making CEC - * useless. Note that MegaChips 2900-based adapters appear to have good - * support for CEC tunneling. Those adapters that I have tested using - * this chipset all have the CEC line connected. - * - * Sadly there is no way for this driver to know this. What happens is - * that a /dev/cecX device is created that is isolated and unable to see - * any of the other CEC devices. Quite literally the CEC wire is cut - * (or in this case, never connected in the first place). - * - * The reason so few adapters support this is that this tunneling protocol - * was never supported by any OS. So there was no easy way of testing it, - * and no incentive to correctly wire up the CEC pin. - * - * Hopefully by creating this driver it will be easier for vendors to - * finally fix their adapters and test the CEC functionality. - * - * I keep a list of known working adapters here: - * - * https://hverkuil.home.xs4all.nl/cec-status.txt - * - * Please mail me (hverkuil@xs4all.nl) if you find an adapter that works - * and is not yet listed there. - * - * Note that the current implementation does not support CEC over an MST hub. - * As far as I can see there is no mechanism defined in the DisplayPort - * standard to transport CEC interrupts over an MST device. It might be - * possible to do this through polling, but I have not been able to get that - * to work. - */ - -/** - * DOC: dp cec helpers - * - * These functions take care of supporting the CEC-Tunneling-over-AUX - * feature of DisplayPort-to-HDMI adapters. - */ - -/* - * When the EDID is unset because the HPD went low, then the CEC DPCD registers - * typically can no longer be read (true for a DP-to-HDMI adapter since it is - * powered by the HPD). However, some displays toggle the HPD off and on for a - * short period for one reason or another, and that would cause the CEC adapter - * to be removed and added again, even though nothing else changed. - * - * This module parameter sets a delay in seconds before the CEC adapter is - * actually unregistered. Only if the HPD does not return within that time will - * the CEC adapter be unregistered. - * - * If it is set to a value >= NEVER_UNREG_DELAY, then the CEC adapter will never - * be unregistered for as long as the connector remains registered. - * - * If it is set to 0, then the CEC adapter will be unregistered immediately as - * soon as the HPD disappears. - * - * The default is one second to prevent short HPD glitches from unregistering - * the CEC adapter. - * - * Note that for integrated HDMI branch devices that support CEC the DPCD - * registers remain available even if the HPD goes low since it is not powered - * by the HPD. In that case the CEC adapter will never be unregistered during - * the life time of the connector. At least, this is the theory since I do not - * have hardware with an integrated HDMI branch device that supports CEC. - */ -#define NEVER_UNREG_DELAY 1000 -static unsigned int drm_dp_cec_unregister_delay = 1; -module_param(drm_dp_cec_unregister_delay, uint, 0600); -MODULE_PARM_DESC(drm_dp_cec_unregister_delay, - "CEC unregister delay in seconds, 0: no delay, >= 1000: never unregister"); - -static int drm_dp_cec_adap_enable(struct cec_adapter *adap, bool enable) -{ - struct drm_dp_aux *aux = cec_get_drvdata(adap); - u32 val = enable ? DP_CEC_TUNNELING_ENABLE : 0; - ssize_t err = 0; - - err = drm_dp_dpcd_writeb(aux, DP_CEC_TUNNELING_CONTROL, val); - return (enable && err < 0) ? err : 0; -} - -static int drm_dp_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) -{ - struct drm_dp_aux *aux = cec_get_drvdata(adap); - /* Bit 15 (logical address 15) should always be set */ - u16 la_mask = 1 << CEC_LOG_ADDR_BROADCAST; - u8 mask[2]; - ssize_t err; - - if (addr != CEC_LOG_ADDR_INVALID) - la_mask |= adap->log_addrs.log_addr_mask | (1 << addr); - mask[0] = la_mask & 0xff; - mask[1] = la_mask >> 8; - err = drm_dp_dpcd_write(aux, DP_CEC_LOGICAL_ADDRESS_MASK, mask, 2); - return (addr != CEC_LOG_ADDR_INVALID && err < 0) ? err : 0; -} - -static int drm_dp_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, - u32 signal_free_time, struct cec_msg *msg) -{ - struct drm_dp_aux *aux = cec_get_drvdata(adap); - unsigned int retries = min(5, attempts - 1); - ssize_t err; - - err = drm_dp_dpcd_write(aux, DP_CEC_TX_MESSAGE_BUFFER, - msg->msg, msg->len); - if (err < 0) - return err; - - err = drm_dp_dpcd_writeb(aux, DP_CEC_TX_MESSAGE_INFO, - (msg->len - 1) | (retries << 4) | - DP_CEC_TX_MESSAGE_SEND); - return err < 0 ? err : 0; -} - -static int drm_dp_cec_adap_monitor_all_enable(struct cec_adapter *adap, - bool enable) -{ - struct drm_dp_aux *aux = cec_get_drvdata(adap); - ssize_t err; - u8 val; - - if (!(adap->capabilities & CEC_CAP_MONITOR_ALL)) - return 0; - - err = drm_dp_dpcd_readb(aux, DP_CEC_TUNNELING_CONTROL, &val); - if (err >= 0) { - if (enable) - val |= DP_CEC_SNOOPING_ENABLE; - else - val &= ~DP_CEC_SNOOPING_ENABLE; - err = drm_dp_dpcd_writeb(aux, DP_CEC_TUNNELING_CONTROL, val); - } - return (enable && err < 0) ? err : 0; -} - -static void drm_dp_cec_adap_status(struct cec_adapter *adap, - struct seq_file *file) -{ - struct drm_dp_aux *aux = cec_get_drvdata(adap); - struct drm_dp_desc desc; - struct drm_dp_dpcd_ident *id = &desc.ident; - - if (drm_dp_read_desc(aux, &desc, true)) - return; - seq_printf(file, "OUI: %*phD\n", - (int)sizeof(id->oui), id->oui); - seq_printf(file, "ID: %*pE\n", - (int)strnlen(id->device_id, sizeof(id->device_id)), - id->device_id); - seq_printf(file, "HW Rev: %d.%d\n", id->hw_rev >> 4, id->hw_rev & 0xf); - /* - * Show this both in decimal and hex: at least one vendor - * always reports this in hex. - */ - seq_printf(file, "FW/SW Rev: %d.%d (0x%02x.0x%02x)\n", - id->sw_major_rev, id->sw_minor_rev, - id->sw_major_rev, id->sw_minor_rev); -} - -static const struct cec_adap_ops drm_dp_cec_adap_ops = { - .adap_enable = drm_dp_cec_adap_enable, - .adap_log_addr = drm_dp_cec_adap_log_addr, - .adap_transmit = drm_dp_cec_adap_transmit, - .adap_monitor_all_enable = drm_dp_cec_adap_monitor_all_enable, - .adap_status = drm_dp_cec_adap_status, -}; - -static int drm_dp_cec_received(struct drm_dp_aux *aux) -{ - struct cec_adapter *adap = aux->cec.adap; - struct cec_msg msg; - u8 rx_msg_info; - ssize_t err; - - err = drm_dp_dpcd_readb(aux, DP_CEC_RX_MESSAGE_INFO, &rx_msg_info); - if (err < 0) - return err; - - if (!(rx_msg_info & DP_CEC_RX_MESSAGE_ENDED)) - return 0; - - msg.len = (rx_msg_info & DP_CEC_RX_MESSAGE_LEN_MASK) + 1; - err = drm_dp_dpcd_read(aux, DP_CEC_RX_MESSAGE_BUFFER, msg.msg, msg.len); - if (err < 0) - return err; - - cec_received_msg(adap, &msg); - return 0; -} - -static void drm_dp_cec_handle_irq(struct drm_dp_aux *aux) -{ - struct cec_adapter *adap = aux->cec.adap; - u8 flags; - - if (drm_dp_dpcd_readb(aux, DP_CEC_TUNNELING_IRQ_FLAGS, &flags) < 0) - return; - - if (flags & DP_CEC_RX_MESSAGE_INFO_VALID) - drm_dp_cec_received(aux); - - if (flags & DP_CEC_TX_MESSAGE_SENT) - cec_transmit_attempt_done(adap, CEC_TX_STATUS_OK); - else if (flags & DP_CEC_TX_LINE_ERROR) - cec_transmit_attempt_done(adap, CEC_TX_STATUS_ERROR | - CEC_TX_STATUS_MAX_RETRIES); - else if (flags & - (DP_CEC_TX_ADDRESS_NACK_ERROR | DP_CEC_TX_DATA_NACK_ERROR)) - cec_transmit_attempt_done(adap, CEC_TX_STATUS_NACK | - CEC_TX_STATUS_MAX_RETRIES); - drm_dp_dpcd_writeb(aux, DP_CEC_TUNNELING_IRQ_FLAGS, flags); -} - -/** - * drm_dp_cec_irq() - handle CEC interrupt, if any - * @aux: DisplayPort AUX channel - * - * Should be called when handling an IRQ_HPD request. If CEC-tunneling-over-AUX - * is present, then it will check for a CEC_IRQ and handle it accordingly. - */ -void drm_dp_cec_irq(struct drm_dp_aux *aux) -{ - u8 cec_irq; - int ret; - - /* No transfer function was set, so not a DP connector */ - if (!aux->transfer) - return; - - mutex_lock(&aux->cec.lock); - if (!aux->cec.adap) - goto unlock; - - ret = drm_dp_dpcd_readb(aux, DP_DEVICE_SERVICE_IRQ_VECTOR_ESI1, - &cec_irq); - if (ret < 0 || !(cec_irq & DP_CEC_IRQ)) - goto unlock; - - drm_dp_cec_handle_irq(aux); - drm_dp_dpcd_writeb(aux, DP_DEVICE_SERVICE_IRQ_VECTOR_ESI1, DP_CEC_IRQ); -unlock: - mutex_unlock(&aux->cec.lock); -} -EXPORT_SYMBOL(drm_dp_cec_irq); - -static bool drm_dp_cec_cap(struct drm_dp_aux *aux, u8 *cec_cap) -{ - u8 cap = 0; - - if (drm_dp_dpcd_readb(aux, DP_CEC_TUNNELING_CAPABILITY, &cap) != 1 || - !(cap & DP_CEC_TUNNELING_CAPABLE)) - return false; - if (cec_cap) - *cec_cap = cap; - return true; -} - -/* - * Called if the HPD was low for more than drm_dp_cec_unregister_delay - * seconds. This unregisters the CEC adapter. - */ -static void drm_dp_cec_unregister_work(struct work_struct *work) -{ - struct drm_dp_aux *aux = container_of(work, struct drm_dp_aux, - cec.unregister_work.work); - - mutex_lock(&aux->cec.lock); - cec_unregister_adapter(aux->cec.adap); - aux->cec.adap = NULL; - mutex_unlock(&aux->cec.lock); -} - -/* - * A new EDID is set. If there is no CEC adapter, then create one. If - * there was a CEC adapter, then check if the CEC adapter properties - * were unchanged and just update the CEC physical address. Otherwise - * unregister the old CEC adapter and create a new one. - */ -void drm_dp_cec_set_edid(struct drm_dp_aux *aux, const struct edid *edid) -{ - struct drm_connector *connector = aux->cec.connector; - u32 cec_caps = CEC_CAP_DEFAULTS | CEC_CAP_NEEDS_HPD | - CEC_CAP_CONNECTOR_INFO; - struct cec_connector_info conn_info; - unsigned int num_las = 1; - u8 cap; - - /* No transfer function was set, so not a DP connector */ - if (!aux->transfer) - return; - -#ifndef CONFIG_MEDIA_CEC_RC - /* - * CEC_CAP_RC is part of CEC_CAP_DEFAULTS, but it is stripped by - * cec_allocate_adapter() if CONFIG_MEDIA_CEC_RC is undefined. - * - * Do this here as well to ensure the tests against cec_caps are - * correct. - */ - cec_caps &= ~CEC_CAP_RC; -#endif - cancel_delayed_work_sync(&aux->cec.unregister_work); - - mutex_lock(&aux->cec.lock); - if (!drm_dp_cec_cap(aux, &cap)) { - /* CEC is not supported, unregister any existing adapter */ - cec_unregister_adapter(aux->cec.adap); - aux->cec.adap = NULL; - goto unlock; - } - - if (cap & DP_CEC_SNOOPING_CAPABLE) - cec_caps |= CEC_CAP_MONITOR_ALL; - if (cap & DP_CEC_MULTIPLE_LA_CAPABLE) - num_las = CEC_MAX_LOG_ADDRS; - - if (aux->cec.adap) { - if (aux->cec.adap->capabilities == cec_caps && - aux->cec.adap->available_log_addrs == num_las) { - /* Unchanged, so just set the phys addr */ - cec_s_phys_addr_from_edid(aux->cec.adap, edid); - goto unlock; - } - /* - * The capabilities changed, so unregister the old - * adapter first. - */ - cec_unregister_adapter(aux->cec.adap); - } - - /* Create a new adapter */ - aux->cec.adap = cec_allocate_adapter(&drm_dp_cec_adap_ops, - aux, connector->name, cec_caps, - num_las); - if (IS_ERR(aux->cec.adap)) { - aux->cec.adap = NULL; - goto unlock; - } - - cec_fill_conn_info_from_drm(&conn_info, connector); - cec_s_conn_info(aux->cec.adap, &conn_info); - - if (cec_register_adapter(aux->cec.adap, connector->dev->dev)) { - cec_delete_adapter(aux->cec.adap); - aux->cec.adap = NULL; - } else { - /* - * Update the phys addr for the new CEC adapter. When called - * from drm_dp_cec_register_connector() edid == NULL, so in - * that case the phys addr is just invalidated. - */ - cec_s_phys_addr_from_edid(aux->cec.adap, edid); - } -unlock: - mutex_unlock(&aux->cec.lock); -} -EXPORT_SYMBOL(drm_dp_cec_set_edid); - -/* - * The EDID disappeared (likely because of the HPD going down). - */ -void drm_dp_cec_unset_edid(struct drm_dp_aux *aux) -{ - /* No transfer function was set, so not a DP connector */ - if (!aux->transfer) - return; - - cancel_delayed_work_sync(&aux->cec.unregister_work); - - mutex_lock(&aux->cec.lock); - if (!aux->cec.adap) - goto unlock; - - cec_phys_addr_invalidate(aux->cec.adap); - /* - * We're done if we want to keep the CEC device - * (drm_dp_cec_unregister_delay is >= NEVER_UNREG_DELAY) or if the - * DPCD still indicates the CEC capability (expected for an integrated - * HDMI branch device). - */ - if (drm_dp_cec_unregister_delay < NEVER_UNREG_DELAY && - !drm_dp_cec_cap(aux, NULL)) { - /* - * Unregister the CEC adapter after drm_dp_cec_unregister_delay - * seconds. This to debounce short HPD off-and-on cycles from - * displays. - */ - schedule_delayed_work(&aux->cec.unregister_work, - drm_dp_cec_unregister_delay * HZ); - } -unlock: - mutex_unlock(&aux->cec.lock); -} -EXPORT_SYMBOL(drm_dp_cec_unset_edid); - -/** - * drm_dp_cec_register_connector() - register a new connector - * @aux: DisplayPort AUX channel - * @connector: drm connector - * - * A new connector was registered with associated CEC adapter name and - * CEC adapter parent device. After registering the name and parent - * drm_dp_cec_set_edid() is called to check if the connector supports - * CEC and to register a CEC adapter if that is the case. - */ -void drm_dp_cec_register_connector(struct drm_dp_aux *aux, - struct drm_connector *connector) -{ - WARN_ON(aux->cec.adap); - if (WARN_ON(!aux->transfer)) - return; - aux->cec.connector = connector; - INIT_DELAYED_WORK(&aux->cec.unregister_work, - drm_dp_cec_unregister_work); -} -EXPORT_SYMBOL(drm_dp_cec_register_connector); - -/** - * drm_dp_cec_unregister_connector() - unregister the CEC adapter, if any - * @aux: DisplayPort AUX channel - */ -void drm_dp_cec_unregister_connector(struct drm_dp_aux *aux) -{ - if (!aux->cec.adap) - return; - cancel_delayed_work_sync(&aux->cec.unregister_work); - cec_unregister_adapter(aux->cec.adap); - aux->cec.adap = NULL; -} -EXPORT_SYMBOL(drm_dp_cec_unregister_connector); diff --git a/drivers/gpu/drm/drm_dp_dual_mode_helper.c b/drivers/gpu/drm/drm_dp_dual_mode_helper.c deleted file mode 100644 index 9faf49354cab..000000000000 --- a/drivers/gpu/drm/drm_dp_dual_mode_helper.c +++ /dev/null @@ -1,530 +0,0 @@ -/* - * Copyright © 2016 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR - * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -/** - * DOC: dp dual mode helpers - * - * Helper functions to deal with DP dual mode (aka. DP++) adaptors. - * - * Type 1: - * Adaptor registers (if any) and the sink DDC bus may be accessed via I2C. - * - * Type 2: - * Adaptor registers and sink DDC bus can be accessed either via I2C or - * I2C-over-AUX. Source devices may choose to implement either of these - * access methods. - */ - -#define DP_DUAL_MODE_SLAVE_ADDRESS 0x40 - -/** - * drm_dp_dual_mode_read - Read from the DP dual mode adaptor register(s) - * @adapter: I2C adapter for the DDC bus - * @offset: register offset - * @buffer: buffer for return data - * @size: sizo of the buffer - * - * Reads @size bytes from the DP dual mode adaptor registers - * starting at @offset. - * - * Returns: - * 0 on success, negative error code on failure - */ -ssize_t drm_dp_dual_mode_read(struct i2c_adapter *adapter, - u8 offset, void *buffer, size_t size) -{ - struct i2c_msg msgs[] = { - { - .addr = DP_DUAL_MODE_SLAVE_ADDRESS, - .flags = 0, - .len = 1, - .buf = &offset, - }, - { - .addr = DP_DUAL_MODE_SLAVE_ADDRESS, - .flags = I2C_M_RD, - .len = size, - .buf = buffer, - }, - }; - int ret; - - ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs)); - if (ret < 0) - return ret; - if (ret != ARRAY_SIZE(msgs)) - return -EPROTO; - - return 0; -} -EXPORT_SYMBOL(drm_dp_dual_mode_read); - -/** - * drm_dp_dual_mode_write - Write to the DP dual mode adaptor register(s) - * @adapter: I2C adapter for the DDC bus - * @offset: register offset - * @buffer: buffer for write data - * @size: sizo of the buffer - * - * Writes @size bytes to the DP dual mode adaptor registers - * starting at @offset. - * - * Returns: - * 0 on success, negative error code on failure - */ -ssize_t drm_dp_dual_mode_write(struct i2c_adapter *adapter, - u8 offset, const void *buffer, size_t size) -{ - struct i2c_msg msg = { - .addr = DP_DUAL_MODE_SLAVE_ADDRESS, - .flags = 0, - .len = 1 + size, - .buf = NULL, - }; - void *data; - int ret; - - data = kmalloc(msg.len, GFP_KERNEL); - if (!data) - return -ENOMEM; - - msg.buf = data; - - memcpy(data, &offset, 1); - memcpy(data + 1, buffer, size); - - ret = i2c_transfer(adapter, &msg, 1); - - kfree(data); - - if (ret < 0) - return ret; - if (ret != 1) - return -EPROTO; - - return 0; -} -EXPORT_SYMBOL(drm_dp_dual_mode_write); - -static bool is_hdmi_adaptor(const char hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN]) -{ - static const char dp_dual_mode_hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN] = - "DP-HDMI ADAPTOR\x04"; - - return memcmp(hdmi_id, dp_dual_mode_hdmi_id, - sizeof(dp_dual_mode_hdmi_id)) == 0; -} - -static bool is_type1_adaptor(uint8_t adaptor_id) -{ - return adaptor_id == 0 || adaptor_id == 0xff; -} - -static bool is_type2_adaptor(uint8_t adaptor_id) -{ - return adaptor_id == (DP_DUAL_MODE_TYPE_TYPE2 | - DP_DUAL_MODE_REV_TYPE2); -} - -static bool is_lspcon_adaptor(const char hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN], - const uint8_t adaptor_id) -{ - return is_hdmi_adaptor(hdmi_id) && - (adaptor_id == (DP_DUAL_MODE_TYPE_TYPE2 | - DP_DUAL_MODE_TYPE_HAS_DPCD)); -} - -/** - * drm_dp_dual_mode_detect - Identify the DP dual mode adaptor - * @dev: &drm_device to use - * @adapter: I2C adapter for the DDC bus - * - * Attempt to identify the type of the DP dual mode adaptor used. - * - * Note that when the answer is @DRM_DP_DUAL_MODE_UNKNOWN it's not - * certain whether we're dealing with a native HDMI port or - * a type 1 DVI dual mode adaptor. The driver will have to use - * some other hardware/driver specific mechanism to make that - * distinction. - * - * Returns: - * The type of the DP dual mode adaptor used - */ -enum drm_dp_dual_mode_type drm_dp_dual_mode_detect(const struct drm_device *dev, - struct i2c_adapter *adapter) -{ - char hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN] = {}; - uint8_t adaptor_id = 0x00; - ssize_t ret; - - /* - * Let's see if the adaptor is there the by reading the - * HDMI ID registers. - * - * Note that type 1 DVI adaptors are not required to implemnt - * any registers, and that presents a problem for detection. - * If the i2c transfer is nacked, we may or may not be dealing - * with a type 1 DVI adaptor. Some other mechanism of detecting - * the presence of the adaptor is required. One way would be - * to check the state of the CONFIG1 pin, Another method would - * simply require the driver to know whether the port is a DP++ - * port or a native HDMI port. Both of these methods are entirely - * hardware/driver specific so we can't deal with them here. - */ - ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_HDMI_ID, - hdmi_id, sizeof(hdmi_id)); - drm_dbg_kms(dev, "DP dual mode HDMI ID: %*pE (err %zd)\n", - ret ? 0 : (int)sizeof(hdmi_id), hdmi_id, ret); - if (ret) - return DRM_DP_DUAL_MODE_UNKNOWN; - - /* - * Sigh. Some (maybe all?) type 1 adaptors are broken and ack - * the offset but ignore it, and instead they just always return - * data from the start of the HDMI ID buffer. So for a broken - * type 1 HDMI adaptor a single byte read will always give us - * 0x44, and for a type 1 DVI adaptor it should give 0x00 - * (assuming it implements any registers). Fortunately neither - * of those values will match the type 2 signature of the - * DP_DUAL_MODE_ADAPTOR_ID register so we can proceed with - * the type 2 adaptor detection safely even in the presence - * of broken type 1 adaptors. - */ - ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_ADAPTOR_ID, - &adaptor_id, sizeof(adaptor_id)); - drm_dbg_kms(dev, "DP dual mode adaptor ID: %02x (err %zd)\n", adaptor_id, ret); - if (ret == 0) { - if (is_lspcon_adaptor(hdmi_id, adaptor_id)) - return DRM_DP_DUAL_MODE_LSPCON; - if (is_type2_adaptor(adaptor_id)) { - if (is_hdmi_adaptor(hdmi_id)) - return DRM_DP_DUAL_MODE_TYPE2_HDMI; - else - return DRM_DP_DUAL_MODE_TYPE2_DVI; - } - /* - * If neither a proper type 1 ID nor a broken type 1 adaptor - * as described above, assume type 1, but let the user know - * that we may have misdetected the type. - */ - if (!is_type1_adaptor(adaptor_id) && adaptor_id != hdmi_id[0]) - drm_err(dev, "Unexpected DP dual mode adaptor ID %02x\n", adaptor_id); - - } - - if (is_hdmi_adaptor(hdmi_id)) - return DRM_DP_DUAL_MODE_TYPE1_HDMI; - else - return DRM_DP_DUAL_MODE_TYPE1_DVI; -} -EXPORT_SYMBOL(drm_dp_dual_mode_detect); - -/** - * drm_dp_dual_mode_max_tmds_clock - Max TMDS clock for DP dual mode adaptor - * @dev: &drm_device to use - * @type: DP dual mode adaptor type - * @adapter: I2C adapter for the DDC bus - * - * Determine the max TMDS clock the adaptor supports based on the - * type of the dual mode adaptor and the DP_DUAL_MODE_MAX_TMDS_CLOCK - * register (on type2 adaptors). As some type 1 adaptors have - * problems with registers (see comments in drm_dp_dual_mode_detect()) - * we don't read the register on those, instead we simply assume - * a 165 MHz limit based on the specification. - * - * Returns: - * Maximum supported TMDS clock rate for the DP dual mode adaptor in kHz. - */ -int drm_dp_dual_mode_max_tmds_clock(const struct drm_device *dev, enum drm_dp_dual_mode_type type, - struct i2c_adapter *adapter) -{ - uint8_t max_tmds_clock; - ssize_t ret; - - /* native HDMI so no limit */ - if (type == DRM_DP_DUAL_MODE_NONE) - return 0; - - /* - * Type 1 adaptors are limited to 165MHz - * Type 2 adaptors can tells us their limit - */ - if (type < DRM_DP_DUAL_MODE_TYPE2_DVI) - return 165000; - - ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_MAX_TMDS_CLOCK, - &max_tmds_clock, sizeof(max_tmds_clock)); - if (ret || max_tmds_clock == 0x00 || max_tmds_clock == 0xff) { - drm_dbg_kms(dev, "Failed to query max TMDS clock\n"); - return 165000; - } - - return max_tmds_clock * 5000 / 2; -} -EXPORT_SYMBOL(drm_dp_dual_mode_max_tmds_clock); - -/** - * drm_dp_dual_mode_get_tmds_output - Get the state of the TMDS output buffers in the DP dual mode adaptor - * @dev: &drm_device to use - * @type: DP dual mode adaptor type - * @adapter: I2C adapter for the DDC bus - * @enabled: current state of the TMDS output buffers - * - * Get the state of the TMDS output buffers in the adaptor. For - * type2 adaptors this is queried from the DP_DUAL_MODE_TMDS_OEN - * register. As some type 1 adaptors have problems with registers - * (see comments in drm_dp_dual_mode_detect()) we don't read the - * register on those, instead we simply assume that the buffers - * are always enabled. - * - * Returns: - * 0 on success, negative error code on failure - */ -int drm_dp_dual_mode_get_tmds_output(const struct drm_device *dev, - enum drm_dp_dual_mode_type type, struct i2c_adapter *adapter, - bool *enabled) -{ - uint8_t tmds_oen; - ssize_t ret; - - if (type < DRM_DP_DUAL_MODE_TYPE2_DVI) { - *enabled = true; - return 0; - } - - ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_TMDS_OEN, - &tmds_oen, sizeof(tmds_oen)); - if (ret) { - drm_dbg_kms(dev, "Failed to query state of TMDS output buffers\n"); - return ret; - } - - *enabled = !(tmds_oen & DP_DUAL_MODE_TMDS_DISABLE); - - return 0; -} -EXPORT_SYMBOL(drm_dp_dual_mode_get_tmds_output); - -/** - * drm_dp_dual_mode_set_tmds_output - Enable/disable TMDS output buffers in the DP dual mode adaptor - * @dev: &drm_device to use - * @type: DP dual mode adaptor type - * @adapter: I2C adapter for the DDC bus - * @enable: enable (as opposed to disable) the TMDS output buffers - * - * Set the state of the TMDS output buffers in the adaptor. For - * type2 this is set via the DP_DUAL_MODE_TMDS_OEN register. As - * some type 1 adaptors have problems with registers (see comments - * in drm_dp_dual_mode_detect()) we avoid touching the register, - * making this function a no-op on type 1 adaptors. - * - * Returns: - * 0 on success, negative error code on failure - */ -int drm_dp_dual_mode_set_tmds_output(const struct drm_device *dev, enum drm_dp_dual_mode_type type, - struct i2c_adapter *adapter, bool enable) -{ - uint8_t tmds_oen = enable ? 0 : DP_DUAL_MODE_TMDS_DISABLE; - ssize_t ret; - int retry; - - if (type < DRM_DP_DUAL_MODE_TYPE2_DVI) - return 0; - - /* - * LSPCON adapters in low-power state may ignore the first write, so - * read back and verify the written value a few times. - */ - for (retry = 0; retry < 3; retry++) { - uint8_t tmp; - - ret = drm_dp_dual_mode_write(adapter, DP_DUAL_MODE_TMDS_OEN, - &tmds_oen, sizeof(tmds_oen)); - if (ret) { - drm_dbg_kms(dev, "Failed to %s TMDS output buffers (%d attempts)\n", - enable ? "enable" : "disable", retry + 1); - return ret; - } - - ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_TMDS_OEN, - &tmp, sizeof(tmp)); - if (ret) { - drm_dbg_kms(dev, - "I2C read failed during TMDS output buffer %s (%d attempts)\n", - enable ? "enabling" : "disabling", retry + 1); - return ret; - } - - if (tmp == tmds_oen) - return 0; - } - - drm_dbg_kms(dev, "I2C write value mismatch during TMDS output buffer %s\n", - enable ? "enabling" : "disabling"); - - return -EIO; -} -EXPORT_SYMBOL(drm_dp_dual_mode_set_tmds_output); - -/** - * drm_dp_get_dual_mode_type_name - Get the name of the DP dual mode adaptor type as a string - * @type: DP dual mode adaptor type - * - * Returns: - * String representation of the DP dual mode adaptor type - */ -const char *drm_dp_get_dual_mode_type_name(enum drm_dp_dual_mode_type type) -{ - switch (type) { - case DRM_DP_DUAL_MODE_NONE: - return "none"; - case DRM_DP_DUAL_MODE_TYPE1_DVI: - return "type 1 DVI"; - case DRM_DP_DUAL_MODE_TYPE1_HDMI: - return "type 1 HDMI"; - case DRM_DP_DUAL_MODE_TYPE2_DVI: - return "type 2 DVI"; - case DRM_DP_DUAL_MODE_TYPE2_HDMI: - return "type 2 HDMI"; - case DRM_DP_DUAL_MODE_LSPCON: - return "lspcon"; - default: - WARN_ON(type != DRM_DP_DUAL_MODE_UNKNOWN); - return "unknown"; - } -} -EXPORT_SYMBOL(drm_dp_get_dual_mode_type_name); - -/** - * drm_lspcon_get_mode: Get LSPCON's current mode of operation by - * reading offset (0x80, 0x41) - * @dev: &drm_device to use - * @adapter: I2C-over-aux adapter - * @mode: current lspcon mode of operation output variable - * - * Returns: - * 0 on success, sets the current_mode value to appropriate mode - * -error on failure - */ -int drm_lspcon_get_mode(const struct drm_device *dev, struct i2c_adapter *adapter, - enum drm_lspcon_mode *mode) -{ - u8 data; - int ret = 0; - int retry; - - if (!mode) { - drm_err(dev, "NULL input\n"); - return -EINVAL; - } - - /* Read Status: i2c over aux */ - for (retry = 0; retry < 6; retry++) { - if (retry) - usleep_range(500, 1000); - - ret = drm_dp_dual_mode_read(adapter, - DP_DUAL_MODE_LSPCON_CURRENT_MODE, - &data, sizeof(data)); - if (!ret) - break; - } - - if (ret < 0) { - drm_dbg_kms(dev, "LSPCON read(0x80, 0x41) failed\n"); - return -EFAULT; - } - - if (data & DP_DUAL_MODE_LSPCON_MODE_PCON) - *mode = DRM_LSPCON_MODE_PCON; - else - *mode = DRM_LSPCON_MODE_LS; - return 0; -} -EXPORT_SYMBOL(drm_lspcon_get_mode); - -/** - * drm_lspcon_set_mode: Change LSPCON's mode of operation by - * writing offset (0x80, 0x40) - * @dev: &drm_device to use - * @adapter: I2C-over-aux adapter - * @mode: required mode of operation - * - * Returns: - * 0 on success, -error on failure/timeout - */ -int drm_lspcon_set_mode(const struct drm_device *dev, struct i2c_adapter *adapter, - enum drm_lspcon_mode mode) -{ - u8 data = 0; - int ret; - int time_out = 200; - enum drm_lspcon_mode current_mode; - - if (mode == DRM_LSPCON_MODE_PCON) - data = DP_DUAL_MODE_LSPCON_MODE_PCON; - - /* Change mode */ - ret = drm_dp_dual_mode_write(adapter, DP_DUAL_MODE_LSPCON_MODE_CHANGE, - &data, sizeof(data)); - if (ret < 0) { - drm_err(dev, "LSPCON mode change failed\n"); - return ret; - } - - /* - * Confirm mode change by reading the status bit. - * Sometimes, it takes a while to change the mode, - * so wait and retry until time out or done. - */ - do { - ret = drm_lspcon_get_mode(dev, adapter, ¤t_mode); - if (ret) { - drm_err(dev, "can't confirm LSPCON mode change\n"); - return ret; - } else { - if (current_mode != mode) { - msleep(10); - time_out -= 10; - } else { - drm_dbg_kms(dev, "LSPCON mode changed to %s\n", - mode == DRM_LSPCON_MODE_LS ? "LS" : "PCON"); - return 0; - } - } - } while (time_out); - - drm_err(dev, "LSPCON mode change timed out\n"); - return -ETIMEDOUT; -} -EXPORT_SYMBOL(drm_lspcon_set_mode); diff --git a/drivers/gpu/drm/drm_dp_helper.c b/drivers/gpu/drm/drm_dp_helper.c deleted file mode 100644 index e995a0262ed7..000000000000 --- a/drivers/gpu/drm/drm_dp_helper.c +++ /dev/null @@ -1,3744 +0,0 @@ -/* - * Copyright © 2009 Keith Packard - * - * Permission to use, copy, modify, distribute, and sell this software and its - * documentation for any purpose is hereby granted without fee, provided that - * the above copyright notice appear in all copies and that both that copyright - * notice and this permission notice appear in supporting documentation, and - * that the name of the copyright holders not be used in advertising or - * publicity pertaining to distribution of the software without specific, - * written prior permission. The copyright holders make no representations - * about the suitability of this software for any purpose. It is provided "as - * is" without express or implied warranty. - * - * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, - * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO - * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR - * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, - * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER - * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE - * OF THIS SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "drm_dp_helper_internal.h" - -struct dp_aux_backlight { - struct backlight_device *base; - struct drm_dp_aux *aux; - struct drm_edp_backlight_info info; - bool enabled; -}; - -/** - * DOC: dp helpers - * - * These functions contain some common logic and helpers at various abstraction - * levels to deal with Display Port sink devices and related things like DP aux - * channel transfers, EDID reading over DP aux channels, decoding certain DPCD - * blocks, ... - */ - -/* Helpers for DP link training */ -static u8 dp_link_status(const u8 link_status[DP_LINK_STATUS_SIZE], int r) -{ - return link_status[r - DP_LANE0_1_STATUS]; -} - -static u8 dp_get_lane_status(const u8 link_status[DP_LINK_STATUS_SIZE], - int lane) -{ - int i = DP_LANE0_1_STATUS + (lane >> 1); - int s = (lane & 1) * 4; - u8 l = dp_link_status(link_status, i); - - return (l >> s) & 0xf; -} - -bool drm_dp_channel_eq_ok(const u8 link_status[DP_LINK_STATUS_SIZE], - int lane_count) -{ - u8 lane_align; - u8 lane_status; - int lane; - - lane_align = dp_link_status(link_status, - DP_LANE_ALIGN_STATUS_UPDATED); - if ((lane_align & DP_INTERLANE_ALIGN_DONE) == 0) - return false; - for (lane = 0; lane < lane_count; lane++) { - lane_status = dp_get_lane_status(link_status, lane); - if ((lane_status & DP_CHANNEL_EQ_BITS) != DP_CHANNEL_EQ_BITS) - return false; - } - return true; -} -EXPORT_SYMBOL(drm_dp_channel_eq_ok); - -bool drm_dp_clock_recovery_ok(const u8 link_status[DP_LINK_STATUS_SIZE], - int lane_count) -{ - int lane; - u8 lane_status; - - for (lane = 0; lane < lane_count; lane++) { - lane_status = dp_get_lane_status(link_status, lane); - if ((lane_status & DP_LANE_CR_DONE) == 0) - return false; - } - return true; -} -EXPORT_SYMBOL(drm_dp_clock_recovery_ok); - -u8 drm_dp_get_adjust_request_voltage(const u8 link_status[DP_LINK_STATUS_SIZE], - int lane) -{ - int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1); - int s = ((lane & 1) ? - DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT : - DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT); - u8 l = dp_link_status(link_status, i); - - return ((l >> s) & 0x3) << DP_TRAIN_VOLTAGE_SWING_SHIFT; -} -EXPORT_SYMBOL(drm_dp_get_adjust_request_voltage); - -u8 drm_dp_get_adjust_request_pre_emphasis(const u8 link_status[DP_LINK_STATUS_SIZE], - int lane) -{ - int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1); - int s = ((lane & 1) ? - DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT : - DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT); - u8 l = dp_link_status(link_status, i); - - return ((l >> s) & 0x3) << DP_TRAIN_PRE_EMPHASIS_SHIFT; -} -EXPORT_SYMBOL(drm_dp_get_adjust_request_pre_emphasis); - -/* DP 2.0 128b/132b */ -u8 drm_dp_get_adjust_tx_ffe_preset(const u8 link_status[DP_LINK_STATUS_SIZE], - int lane) -{ - int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1); - int s = ((lane & 1) ? - DP_ADJUST_TX_FFE_PRESET_LANE1_SHIFT : - DP_ADJUST_TX_FFE_PRESET_LANE0_SHIFT); - u8 l = dp_link_status(link_status, i); - - return (l >> s) & 0xf; -} -EXPORT_SYMBOL(drm_dp_get_adjust_tx_ffe_preset); - -u8 drm_dp_get_adjust_request_post_cursor(const u8 link_status[DP_LINK_STATUS_SIZE], - unsigned int lane) -{ - unsigned int offset = DP_ADJUST_REQUEST_POST_CURSOR2; - u8 value = dp_link_status(link_status, offset); - - return (value >> (lane << 1)) & 0x3; -} -EXPORT_SYMBOL(drm_dp_get_adjust_request_post_cursor); - -static int __8b10b_clock_recovery_delay_us(const struct drm_dp_aux *aux, u8 rd_interval) -{ - if (rd_interval > 4) - drm_dbg_kms(aux->drm_dev, "%s: invalid AUX interval 0x%02x (max 4)\n", - aux->name, rd_interval); - - if (rd_interval == 0) - return 100; - - return rd_interval * 4 * USEC_PER_MSEC; -} - -static int __8b10b_channel_eq_delay_us(const struct drm_dp_aux *aux, u8 rd_interval) -{ - if (rd_interval > 4) - drm_dbg_kms(aux->drm_dev, "%s: invalid AUX interval 0x%02x (max 4)\n", - aux->name, rd_interval); - - if (rd_interval == 0) - return 400; - - return rd_interval * 4 * USEC_PER_MSEC; -} - -static int __128b132b_channel_eq_delay_us(const struct drm_dp_aux *aux, u8 rd_interval) -{ - switch (rd_interval) { - default: - drm_dbg_kms(aux->drm_dev, "%s: invalid AUX interval 0x%02x\n", - aux->name, rd_interval); - fallthrough; - case DP_128B132B_TRAINING_AUX_RD_INTERVAL_400_US: - return 400; - case DP_128B132B_TRAINING_AUX_RD_INTERVAL_4_MS: - return 4000; - case DP_128B132B_TRAINING_AUX_RD_INTERVAL_8_MS: - return 8000; - case DP_128B132B_TRAINING_AUX_RD_INTERVAL_12_MS: - return 12000; - case DP_128B132B_TRAINING_AUX_RD_INTERVAL_16_MS: - return 16000; - case DP_128B132B_TRAINING_AUX_RD_INTERVAL_32_MS: - return 32000; - case DP_128B132B_TRAINING_AUX_RD_INTERVAL_64_MS: - return 64000; - } -} - -/* - * The link training delays are different for: - * - * - Clock recovery vs. channel equalization - * - DPRX vs. LTTPR - * - 128b/132b vs. 8b/10b - * - DPCD rev 1.3 vs. later - * - * Get the correct delay in us, reading DPCD if necessary. - */ -static int __read_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE], - enum drm_dp_phy dp_phy, bool uhbr, bool cr) -{ - int (*parse)(const struct drm_dp_aux *aux, u8 rd_interval); - unsigned int offset; - u8 rd_interval, mask; - - if (dp_phy == DP_PHY_DPRX) { - if (uhbr) { - if (cr) - return 100; - - offset = DP_128B132B_TRAINING_AUX_RD_INTERVAL; - mask = DP_128B132B_TRAINING_AUX_RD_INTERVAL_MASK; - parse = __128b132b_channel_eq_delay_us; - } else { - if (cr && dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14) - return 100; - - offset = DP_TRAINING_AUX_RD_INTERVAL; - mask = DP_TRAINING_AUX_RD_MASK; - if (cr) - parse = __8b10b_clock_recovery_delay_us; - else - parse = __8b10b_channel_eq_delay_us; - } - } else { - if (uhbr) { - offset = DP_128B132B_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER(dp_phy); - mask = DP_128B132B_TRAINING_AUX_RD_INTERVAL_MASK; - parse = __128b132b_channel_eq_delay_us; - } else { - if (cr) - return 100; - - offset = DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER(dp_phy); - mask = DP_TRAINING_AUX_RD_MASK; - parse = __8b10b_channel_eq_delay_us; - } - } - - if (offset < DP_RECEIVER_CAP_SIZE) { - rd_interval = dpcd[offset]; - } else { - if (drm_dp_dpcd_readb(aux, offset, &rd_interval) != 1) { - drm_dbg_kms(aux->drm_dev, "%s: failed rd interval read\n", - aux->name); - /* arbitrary default delay */ - return 400; - } - } - - return parse(aux, rd_interval & mask); -} - -int drm_dp_read_clock_recovery_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE], - enum drm_dp_phy dp_phy, bool uhbr) -{ - return __read_delay(aux, dpcd, dp_phy, uhbr, true); -} -EXPORT_SYMBOL(drm_dp_read_clock_recovery_delay); - -int drm_dp_read_channel_eq_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE], - enum drm_dp_phy dp_phy, bool uhbr) -{ - return __read_delay(aux, dpcd, dp_phy, uhbr, false); -} -EXPORT_SYMBOL(drm_dp_read_channel_eq_delay); - -void drm_dp_link_train_clock_recovery_delay(const struct drm_dp_aux *aux, - const u8 dpcd[DP_RECEIVER_CAP_SIZE]) -{ - u8 rd_interval = dpcd[DP_TRAINING_AUX_RD_INTERVAL] & - DP_TRAINING_AUX_RD_MASK; - int delay_us; - - if (dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14) - delay_us = 100; - else - delay_us = __8b10b_clock_recovery_delay_us(aux, rd_interval); - - usleep_range(delay_us, delay_us * 2); -} -EXPORT_SYMBOL(drm_dp_link_train_clock_recovery_delay); - -static void __drm_dp_link_train_channel_eq_delay(const struct drm_dp_aux *aux, - u8 rd_interval) -{ - int delay_us = __8b10b_channel_eq_delay_us(aux, rd_interval); - - usleep_range(delay_us, delay_us * 2); -} - -void drm_dp_link_train_channel_eq_delay(const struct drm_dp_aux *aux, - const u8 dpcd[DP_RECEIVER_CAP_SIZE]) -{ - __drm_dp_link_train_channel_eq_delay(aux, - dpcd[DP_TRAINING_AUX_RD_INTERVAL] & - DP_TRAINING_AUX_RD_MASK); -} -EXPORT_SYMBOL(drm_dp_link_train_channel_eq_delay); - -void drm_dp_lttpr_link_train_clock_recovery_delay(void) -{ - usleep_range(100, 200); -} -EXPORT_SYMBOL(drm_dp_lttpr_link_train_clock_recovery_delay); - -static u8 dp_lttpr_phy_cap(const u8 phy_cap[DP_LTTPR_PHY_CAP_SIZE], int r) -{ - return phy_cap[r - DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER1]; -} - -void drm_dp_lttpr_link_train_channel_eq_delay(const struct drm_dp_aux *aux, - const u8 phy_cap[DP_LTTPR_PHY_CAP_SIZE]) -{ - u8 interval = dp_lttpr_phy_cap(phy_cap, - DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER1) & - DP_TRAINING_AUX_RD_MASK; - - __drm_dp_link_train_channel_eq_delay(aux, interval); -} -EXPORT_SYMBOL(drm_dp_lttpr_link_train_channel_eq_delay); - -u8 drm_dp_link_rate_to_bw_code(int link_rate) -{ - switch (link_rate) { - case 1000000: - return DP_LINK_BW_10; - case 1350000: - return DP_LINK_BW_13_5; - case 2000000: - return DP_LINK_BW_20; - default: - /* Spec says link_bw = link_rate / 0.27Gbps */ - return link_rate / 27000; - } -} -EXPORT_SYMBOL(drm_dp_link_rate_to_bw_code); - -int drm_dp_bw_code_to_link_rate(u8 link_bw) -{ - switch (link_bw) { - case DP_LINK_BW_10: - return 1000000; - case DP_LINK_BW_13_5: - return 1350000; - case DP_LINK_BW_20: - return 2000000; - default: - /* Spec says link_rate = link_bw * 0.27Gbps */ - return link_bw * 27000; - } -} -EXPORT_SYMBOL(drm_dp_bw_code_to_link_rate); - -#define AUX_RETRY_INTERVAL 500 /* us */ - -static inline void -drm_dp_dump_access(const struct drm_dp_aux *aux, - u8 request, uint offset, void *buffer, int ret) -{ - const char *arrow = request == DP_AUX_NATIVE_READ ? "->" : "<-"; - - if (ret > 0) - drm_dbg_dp(aux->drm_dev, "%s: 0x%05x AUX %s (ret=%3d) %*ph\n", - aux->name, offset, arrow, ret, min(ret, 20), buffer); - else - drm_dbg_dp(aux->drm_dev, "%s: 0x%05x AUX %s (ret=%3d)\n", - aux->name, offset, arrow, ret); -} - -/** - * DOC: dp helpers - * - * The DisplayPort AUX channel is an abstraction to allow generic, driver- - * independent access to AUX functionality. Drivers can take advantage of - * this by filling in the fields of the drm_dp_aux structure. - * - * Transactions are described using a hardware-independent drm_dp_aux_msg - * structure, which is passed into a driver's .transfer() implementation. - * Both native and I2C-over-AUX transactions are supported. - */ - -static int drm_dp_dpcd_access(struct drm_dp_aux *aux, u8 request, - unsigned int offset, void *buffer, size_t size) -{ - struct drm_dp_aux_msg msg; - unsigned int retry, native_reply; - int err = 0, ret = 0; - - memset(&msg, 0, sizeof(msg)); - msg.address = offset; - msg.request = request; - msg.buffer = buffer; - msg.size = size; - - mutex_lock(&aux->hw_mutex); - - /* - * The specification doesn't give any recommendation on how often to - * retry native transactions. We used to retry 7 times like for - * aux i2c transactions but real world devices this wasn't - * sufficient, bump to 32 which makes Dell 4k monitors happier. - */ - for (retry = 0; retry < 32; retry++) { - if (ret != 0 && ret != -ETIMEDOUT) { - usleep_range(AUX_RETRY_INTERVAL, - AUX_RETRY_INTERVAL + 100); - } - - ret = aux->transfer(aux, &msg); - if (ret >= 0) { - native_reply = msg.reply & DP_AUX_NATIVE_REPLY_MASK; - if (native_reply == DP_AUX_NATIVE_REPLY_ACK) { - if (ret == size) - goto unlock; - - ret = -EPROTO; - } else - ret = -EIO; - } - - /* - * We want the error we return to be the error we received on - * the first transaction, since we may get a different error the - * next time we retry - */ - if (!err) - err = ret; - } - - drm_dbg_kms(aux->drm_dev, "%s: Too many retries, giving up. First error: %d\n", - aux->name, err); - ret = err; - -unlock: - mutex_unlock(&aux->hw_mutex); - return ret; -} - -/** - * drm_dp_dpcd_read() - read a series of bytes from the DPCD - * @aux: DisplayPort AUX channel (SST or MST) - * @offset: address of the (first) register to read - * @buffer: buffer to store the register values - * @size: number of bytes in @buffer - * - * Returns the number of bytes transferred on success, or a negative error - * code on failure. -EIO is returned if the request was NAKed by the sink or - * if the retry count was exceeded. If not all bytes were transferred, this - * function returns -EPROTO. Errors from the underlying AUX channel transfer - * function, with the exception of -EBUSY (which causes the transaction to - * be retried), are propagated to the caller. - */ -ssize_t drm_dp_dpcd_read(struct drm_dp_aux *aux, unsigned int offset, - void *buffer, size_t size) -{ - int ret; - - /* - * HP ZR24w corrupts the first DPCD access after entering power save - * mode. Eg. on a read, the entire buffer will be filled with the same - * byte. Do a throw away read to avoid corrupting anything we care - * about. Afterwards things will work correctly until the monitor - * gets woken up and subsequently re-enters power save mode. - * - * The user pressing any button on the monitor is enough to wake it - * up, so there is no particularly good place to do the workaround. - * We just have to do it before any DPCD access and hope that the - * monitor doesn't power down exactly after the throw away read. - */ - if (!aux->is_remote) { - ret = drm_dp_dpcd_access(aux, DP_AUX_NATIVE_READ, DP_DPCD_REV, - buffer, 1); - if (ret != 1) - goto out; - } - - if (aux->is_remote) - ret = drm_dp_mst_dpcd_read(aux, offset, buffer, size); - else - ret = drm_dp_dpcd_access(aux, DP_AUX_NATIVE_READ, offset, - buffer, size); - -out: - drm_dp_dump_access(aux, DP_AUX_NATIVE_READ, offset, buffer, ret); - return ret; -} -EXPORT_SYMBOL(drm_dp_dpcd_read); - -/** - * drm_dp_dpcd_write() - write a series of bytes to the DPCD - * @aux: DisplayPort AUX channel (SST or MST) - * @offset: address of the (first) register to write - * @buffer: buffer containing the values to write - * @size: number of bytes in @buffer - * - * Returns the number of bytes transferred on success, or a negative error - * code on failure. -EIO is returned if the request was NAKed by the sink or - * if the retry count was exceeded. If not all bytes were transferred, this - * function returns -EPROTO. Errors from the underlying AUX channel transfer - * function, with the exception of -EBUSY (which causes the transaction to - * be retried), are propagated to the caller. - */ -ssize_t drm_dp_dpcd_write(struct drm_dp_aux *aux, unsigned int offset, - void *buffer, size_t size) -{ - int ret; - - if (aux->is_remote) - ret = drm_dp_mst_dpcd_write(aux, offset, buffer, size); - else - ret = drm_dp_dpcd_access(aux, DP_AUX_NATIVE_WRITE, offset, - buffer, size); - - drm_dp_dump_access(aux, DP_AUX_NATIVE_WRITE, offset, buffer, ret); - return ret; -} -EXPORT_SYMBOL(drm_dp_dpcd_write); - -/** - * drm_dp_dpcd_read_link_status() - read DPCD link status (bytes 0x202-0x207) - * @aux: DisplayPort AUX channel - * @status: buffer to store the link status in (must be at least 6 bytes) - * - * Returns the number of bytes transferred on success or a negative error - * code on failure. - */ -int drm_dp_dpcd_read_link_status(struct drm_dp_aux *aux, - u8 status[DP_LINK_STATUS_SIZE]) -{ - return drm_dp_dpcd_read(aux, DP_LANE0_1_STATUS, status, - DP_LINK_STATUS_SIZE); -} -EXPORT_SYMBOL(drm_dp_dpcd_read_link_status); - -/** - * drm_dp_dpcd_read_phy_link_status - get the link status information for a DP PHY - * @aux: DisplayPort AUX channel - * @dp_phy: the DP PHY to get the link status for - * @link_status: buffer to return the status in - * - * Fetch the AUX DPCD registers for the DPRX or an LTTPR PHY link status. The - * layout of the returned @link_status matches the DPCD register layout of the - * DPRX PHY link status. - * - * Returns 0 if the information was read successfully or a negative error code - * on failure. - */ -int drm_dp_dpcd_read_phy_link_status(struct drm_dp_aux *aux, - enum drm_dp_phy dp_phy, - u8 link_status[DP_LINK_STATUS_SIZE]) -{ - int ret; - - if (dp_phy == DP_PHY_DPRX) { - ret = drm_dp_dpcd_read(aux, - DP_LANE0_1_STATUS, - link_status, - DP_LINK_STATUS_SIZE); - - if (ret < 0) - return ret; - - WARN_ON(ret != DP_LINK_STATUS_SIZE); - - return 0; - } - - ret = drm_dp_dpcd_read(aux, - DP_LANE0_1_STATUS_PHY_REPEATER(dp_phy), - link_status, - DP_LINK_STATUS_SIZE - 1); - - if (ret < 0) - return ret; - - WARN_ON(ret != DP_LINK_STATUS_SIZE - 1); - - /* Convert the LTTPR to the sink PHY link status layout */ - memmove(&link_status[DP_SINK_STATUS - DP_LANE0_1_STATUS + 1], - &link_status[DP_SINK_STATUS - DP_LANE0_1_STATUS], - DP_LINK_STATUS_SIZE - (DP_SINK_STATUS - DP_LANE0_1_STATUS) - 1); - link_status[DP_SINK_STATUS - DP_LANE0_1_STATUS] = 0; - - return 0; -} -EXPORT_SYMBOL(drm_dp_dpcd_read_phy_link_status); - -static bool is_edid_digital_input_dp(const struct edid *edid) -{ - return edid && edid->revision >= 4 && - edid->input & DRM_EDID_INPUT_DIGITAL && - (edid->input & DRM_EDID_DIGITAL_TYPE_MASK) == DRM_EDID_DIGITAL_TYPE_DP; -} - -/** - * drm_dp_downstream_is_type() - is the downstream facing port of certain type? - * @dpcd: DisplayPort configuration data - * @port_cap: port capabilities - * @type: port type to be checked. Can be: - * %DP_DS_PORT_TYPE_DP, %DP_DS_PORT_TYPE_VGA, %DP_DS_PORT_TYPE_DVI, - * %DP_DS_PORT_TYPE_HDMI, %DP_DS_PORT_TYPE_NON_EDID, - * %DP_DS_PORT_TYPE_DP_DUALMODE or %DP_DS_PORT_TYPE_WIRELESS. - * - * Caveat: Only works with DPCD 1.1+ port caps. - * - * Returns: whether the downstream facing port matches the type. - */ -bool drm_dp_downstream_is_type(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4], u8 type) -{ - return drm_dp_is_branch(dpcd) && - dpcd[DP_DPCD_REV] >= 0x11 && - (port_cap[0] & DP_DS_PORT_TYPE_MASK) == type; -} -EXPORT_SYMBOL(drm_dp_downstream_is_type); - -/** - * drm_dp_downstream_is_tmds() - is the downstream facing port TMDS? - * @dpcd: DisplayPort configuration data - * @port_cap: port capabilities - * @edid: EDID - * - * Returns: whether the downstream facing port is TMDS (HDMI/DVI). - */ -bool drm_dp_downstream_is_tmds(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4], - const struct edid *edid) -{ - if (dpcd[DP_DPCD_REV] < 0x11) { - switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) { - case DP_DWN_STRM_PORT_TYPE_TMDS: - return true; - default: - return false; - } - } - - switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { - case DP_DS_PORT_TYPE_DP_DUALMODE: - if (is_edid_digital_input_dp(edid)) - return false; - fallthrough; - case DP_DS_PORT_TYPE_DVI: - case DP_DS_PORT_TYPE_HDMI: - return true; - default: - return false; - } -} -EXPORT_SYMBOL(drm_dp_downstream_is_tmds); - -/** - * drm_dp_send_real_edid_checksum() - send back real edid checksum value - * @aux: DisplayPort AUX channel - * @real_edid_checksum: real edid checksum for the last block - * - * Returns: - * True on success - */ -bool drm_dp_send_real_edid_checksum(struct drm_dp_aux *aux, - u8 real_edid_checksum) -{ - u8 link_edid_read = 0, auto_test_req = 0, test_resp = 0; - - if (drm_dp_dpcd_read(aux, DP_DEVICE_SERVICE_IRQ_VECTOR, - &auto_test_req, 1) < 1) { - drm_err(aux->drm_dev, "%s: DPCD failed read at register 0x%x\n", - aux->name, DP_DEVICE_SERVICE_IRQ_VECTOR); - return false; - } - auto_test_req &= DP_AUTOMATED_TEST_REQUEST; - - if (drm_dp_dpcd_read(aux, DP_TEST_REQUEST, &link_edid_read, 1) < 1) { - drm_err(aux->drm_dev, "%s: DPCD failed read at register 0x%x\n", - aux->name, DP_TEST_REQUEST); - return false; - } - link_edid_read &= DP_TEST_LINK_EDID_READ; - - if (!auto_test_req || !link_edid_read) { - drm_dbg_kms(aux->drm_dev, "%s: Source DUT does not support TEST_EDID_READ\n", - aux->name); - return false; - } - - if (drm_dp_dpcd_write(aux, DP_DEVICE_SERVICE_IRQ_VECTOR, - &auto_test_req, 1) < 1) { - drm_err(aux->drm_dev, "%s: DPCD failed write at register 0x%x\n", - aux->name, DP_DEVICE_SERVICE_IRQ_VECTOR); - return false; - } - - /* send back checksum for the last edid extension block data */ - if (drm_dp_dpcd_write(aux, DP_TEST_EDID_CHECKSUM, - &real_edid_checksum, 1) < 1) { - drm_err(aux->drm_dev, "%s: DPCD failed write at register 0x%x\n", - aux->name, DP_TEST_EDID_CHECKSUM); - return false; - } - - test_resp |= DP_TEST_EDID_CHECKSUM_WRITE; - if (drm_dp_dpcd_write(aux, DP_TEST_RESPONSE, &test_resp, 1) < 1) { - drm_err(aux->drm_dev, "%s: DPCD failed write at register 0x%x\n", - aux->name, DP_TEST_RESPONSE); - return false; - } - - return true; -} -EXPORT_SYMBOL(drm_dp_send_real_edid_checksum); - -static u8 drm_dp_downstream_port_count(const u8 dpcd[DP_RECEIVER_CAP_SIZE]) -{ - u8 port_count = dpcd[DP_DOWN_STREAM_PORT_COUNT] & DP_PORT_COUNT_MASK; - - if (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE && port_count > 4) - port_count = 4; - - return port_count; -} - -static int drm_dp_read_extended_dpcd_caps(struct drm_dp_aux *aux, - u8 dpcd[DP_RECEIVER_CAP_SIZE]) -{ - u8 dpcd_ext[DP_RECEIVER_CAP_SIZE]; - int ret; - - /* - * Prior to DP1.3 the bit represented by - * DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT was reserved. - * If it is set DP_DPCD_REV at 0000h could be at a value less than - * the true capability of the panel. The only way to check is to - * then compare 0000h and 2200h. - */ - if (!(dpcd[DP_TRAINING_AUX_RD_INTERVAL] & - DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT)) - return 0; - - ret = drm_dp_dpcd_read(aux, DP_DP13_DPCD_REV, &dpcd_ext, - sizeof(dpcd_ext)); - if (ret < 0) - return ret; - if (ret != sizeof(dpcd_ext)) - return -EIO; - - if (dpcd[DP_DPCD_REV] > dpcd_ext[DP_DPCD_REV]) { - drm_dbg_kms(aux->drm_dev, - "%s: Extended DPCD rev less than base DPCD rev (%d > %d)\n", - aux->name, dpcd[DP_DPCD_REV], dpcd_ext[DP_DPCD_REV]); - return 0; - } - - if (!memcmp(dpcd, dpcd_ext, sizeof(dpcd_ext))) - return 0; - - drm_dbg_kms(aux->drm_dev, "%s: Base DPCD: %*ph\n", aux->name, DP_RECEIVER_CAP_SIZE, dpcd); - - memcpy(dpcd, dpcd_ext, sizeof(dpcd_ext)); - - return 0; -} - -/** - * drm_dp_read_dpcd_caps() - read DPCD caps and extended DPCD caps if - * available - * @aux: DisplayPort AUX channel - * @dpcd: Buffer to store the resulting DPCD in - * - * Attempts to read the base DPCD caps for @aux. Additionally, this function - * checks for and reads the extended DPRX caps (%DP_DP13_DPCD_REV) if - * present. - * - * Returns: %0 if the DPCD was read successfully, negative error code - * otherwise. - */ -int drm_dp_read_dpcd_caps(struct drm_dp_aux *aux, - u8 dpcd[DP_RECEIVER_CAP_SIZE]) -{ - int ret; - - ret = drm_dp_dpcd_read(aux, DP_DPCD_REV, dpcd, DP_RECEIVER_CAP_SIZE); - if (ret < 0) - return ret; - if (ret != DP_RECEIVER_CAP_SIZE || dpcd[DP_DPCD_REV] == 0) - return -EIO; - - ret = drm_dp_read_extended_dpcd_caps(aux, dpcd); - if (ret < 0) - return ret; - - drm_dbg_kms(aux->drm_dev, "%s: DPCD: %*ph\n", aux->name, DP_RECEIVER_CAP_SIZE, dpcd); - - return ret; -} -EXPORT_SYMBOL(drm_dp_read_dpcd_caps); - -/** - * drm_dp_read_downstream_info() - read DPCD downstream port info if available - * @aux: DisplayPort AUX channel - * @dpcd: A cached copy of the port's DPCD - * @downstream_ports: buffer to store the downstream port info in - * - * See also: - * drm_dp_downstream_max_clock() - * drm_dp_downstream_max_bpc() - * - * Returns: 0 if either the downstream port info was read successfully or - * there was no downstream info to read, or a negative error code otherwise. - */ -int drm_dp_read_downstream_info(struct drm_dp_aux *aux, - const u8 dpcd[DP_RECEIVER_CAP_SIZE], - u8 downstream_ports[DP_MAX_DOWNSTREAM_PORTS]) -{ - int ret; - u8 len; - - memset(downstream_ports, 0, DP_MAX_DOWNSTREAM_PORTS); - - /* No downstream info to read */ - if (!drm_dp_is_branch(dpcd) || dpcd[DP_DPCD_REV] == DP_DPCD_REV_10) - return 0; - - /* Some branches advertise having 0 downstream ports, despite also advertising they have a - * downstream port present. The DP spec isn't clear on if this is allowed or not, but since - * some branches do it we need to handle it regardless. - */ - len = drm_dp_downstream_port_count(dpcd); - if (!len) - return 0; - - if (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) - len *= 4; - - ret = drm_dp_dpcd_read(aux, DP_DOWNSTREAM_PORT_0, downstream_ports, len); - if (ret < 0) - return ret; - if (ret != len) - return -EIO; - - drm_dbg_kms(aux->drm_dev, "%s: DPCD DFP: %*ph\n", aux->name, len, downstream_ports); - - return 0; -} -EXPORT_SYMBOL(drm_dp_read_downstream_info); - -/** - * drm_dp_downstream_max_dotclock() - extract downstream facing port max dot clock - * @dpcd: DisplayPort configuration data - * @port_cap: port capabilities - * - * Returns: Downstream facing port max dot clock in kHz on success, - * or 0 if max clock not defined - */ -int drm_dp_downstream_max_dotclock(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4]) -{ - if (!drm_dp_is_branch(dpcd)) - return 0; - - if (dpcd[DP_DPCD_REV] < 0x11) - return 0; - - switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { - case DP_DS_PORT_TYPE_VGA: - if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) - return 0; - return port_cap[1] * 8000; - default: - return 0; - } -} -EXPORT_SYMBOL(drm_dp_downstream_max_dotclock); - -/** - * drm_dp_downstream_max_tmds_clock() - extract downstream facing port max TMDS clock - * @dpcd: DisplayPort configuration data - * @port_cap: port capabilities - * @edid: EDID - * - * Returns: HDMI/DVI downstream facing port max TMDS clock in kHz on success, - * or 0 if max TMDS clock not defined - */ -int drm_dp_downstream_max_tmds_clock(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4], - const struct edid *edid) -{ - if (!drm_dp_is_branch(dpcd)) - return 0; - - if (dpcd[DP_DPCD_REV] < 0x11) { - switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) { - case DP_DWN_STRM_PORT_TYPE_TMDS: - return 165000; - default: - return 0; - } - } - - switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { - case DP_DS_PORT_TYPE_DP_DUALMODE: - if (is_edid_digital_input_dp(edid)) - return 0; - /* - * It's left up to the driver to check the - * DP dual mode adapter's max TMDS clock. - * - * Unfortunately it looks like branch devices - * may not fordward that the DP dual mode i2c - * access so we just usually get i2c nak :( - */ - fallthrough; - case DP_DS_PORT_TYPE_HDMI: - /* - * We should perhaps assume 165 MHz when detailed cap - * info is not available. But looks like many typical - * branch devices fall into that category and so we'd - * probably end up with users complaining that they can't - * get high resolution modes with their favorite dongle. - * - * So let's limit to 300 MHz instead since DPCD 1.4 - * HDMI 2.0 DFPs are required to have the detailed cap - * info. So it's more likely we're dealing with a HDMI 1.4 - * compatible* device here. - */ - if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) - return 300000; - return port_cap[1] * 2500; - case DP_DS_PORT_TYPE_DVI: - if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) - return 165000; - /* FIXME what to do about DVI dual link? */ - return port_cap[1] * 2500; - default: - return 0; - } -} -EXPORT_SYMBOL(drm_dp_downstream_max_tmds_clock); - -/** - * drm_dp_downstream_min_tmds_clock() - extract downstream facing port min TMDS clock - * @dpcd: DisplayPort configuration data - * @port_cap: port capabilities - * @edid: EDID - * - * Returns: HDMI/DVI downstream facing port min TMDS clock in kHz on success, - * or 0 if max TMDS clock not defined - */ -int drm_dp_downstream_min_tmds_clock(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4], - const struct edid *edid) -{ - if (!drm_dp_is_branch(dpcd)) - return 0; - - if (dpcd[DP_DPCD_REV] < 0x11) { - switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) { - case DP_DWN_STRM_PORT_TYPE_TMDS: - return 25000; - default: - return 0; - } - } - - switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { - case DP_DS_PORT_TYPE_DP_DUALMODE: - if (is_edid_digital_input_dp(edid)) - return 0; - fallthrough; - case DP_DS_PORT_TYPE_DVI: - case DP_DS_PORT_TYPE_HDMI: - /* - * Unclear whether the protocol converter could - * utilize pixel replication. Assume it won't. - */ - return 25000; - default: - return 0; - } -} -EXPORT_SYMBOL(drm_dp_downstream_min_tmds_clock); - -/** - * drm_dp_downstream_max_bpc() - extract downstream facing port max - * bits per component - * @dpcd: DisplayPort configuration data - * @port_cap: downstream facing port capabilities - * @edid: EDID - * - * Returns: Max bpc on success or 0 if max bpc not defined - */ -int drm_dp_downstream_max_bpc(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4], - const struct edid *edid) -{ - if (!drm_dp_is_branch(dpcd)) - return 0; - - if (dpcd[DP_DPCD_REV] < 0x11) { - switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) { - case DP_DWN_STRM_PORT_TYPE_DP: - return 0; - default: - return 8; - } - } - - switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { - case DP_DS_PORT_TYPE_DP: - return 0; - case DP_DS_PORT_TYPE_DP_DUALMODE: - if (is_edid_digital_input_dp(edid)) - return 0; - fallthrough; - case DP_DS_PORT_TYPE_HDMI: - case DP_DS_PORT_TYPE_DVI: - case DP_DS_PORT_TYPE_VGA: - if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) - return 8; - - switch (port_cap[2] & DP_DS_MAX_BPC_MASK) { - case DP_DS_8BPC: - return 8; - case DP_DS_10BPC: - return 10; - case DP_DS_12BPC: - return 12; - case DP_DS_16BPC: - return 16; - default: - return 8; - } - break; - default: - return 8; - } -} -EXPORT_SYMBOL(drm_dp_downstream_max_bpc); - -/** - * drm_dp_downstream_420_passthrough() - determine downstream facing port - * YCbCr 4:2:0 pass-through capability - * @dpcd: DisplayPort configuration data - * @port_cap: downstream facing port capabilities - * - * Returns: whether the downstream facing port can pass through YCbCr 4:2:0 - */ -bool drm_dp_downstream_420_passthrough(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4]) -{ - if (!drm_dp_is_branch(dpcd)) - return false; - - if (dpcd[DP_DPCD_REV] < 0x13) - return false; - - switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { - case DP_DS_PORT_TYPE_DP: - return true; - case DP_DS_PORT_TYPE_HDMI: - if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) - return false; - - return port_cap[3] & DP_DS_HDMI_YCBCR420_PASS_THROUGH; - default: - return false; - } -} -EXPORT_SYMBOL(drm_dp_downstream_420_passthrough); - -/** - * drm_dp_downstream_444_to_420_conversion() - determine downstream facing port - * YCbCr 4:4:4->4:2:0 conversion capability - * @dpcd: DisplayPort configuration data - * @port_cap: downstream facing port capabilities - * - * Returns: whether the downstream facing port can convert YCbCr 4:4:4 to 4:2:0 - */ -bool drm_dp_downstream_444_to_420_conversion(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4]) -{ - if (!drm_dp_is_branch(dpcd)) - return false; - - if (dpcd[DP_DPCD_REV] < 0x13) - return false; - - switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { - case DP_DS_PORT_TYPE_HDMI: - if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) - return false; - - return port_cap[3] & DP_DS_HDMI_YCBCR444_TO_420_CONV; - default: - return false; - } -} -EXPORT_SYMBOL(drm_dp_downstream_444_to_420_conversion); - -/** - * drm_dp_downstream_rgb_to_ycbcr_conversion() - determine downstream facing port - * RGB->YCbCr conversion capability - * @dpcd: DisplayPort configuration data - * @port_cap: downstream facing port capabilities - * @color_spc: Colorspace for which conversion cap is sought - * - * Returns: whether the downstream facing port can convert RGB->YCbCr for a given - * colorspace. - */ -bool drm_dp_downstream_rgb_to_ycbcr_conversion(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4], - u8 color_spc) -{ - if (!drm_dp_is_branch(dpcd)) - return false; - - if (dpcd[DP_DPCD_REV] < 0x13) - return false; - - switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { - case DP_DS_PORT_TYPE_HDMI: - if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) - return false; - - return port_cap[3] & color_spc; - default: - return false; - } -} -EXPORT_SYMBOL(drm_dp_downstream_rgb_to_ycbcr_conversion); - -/** - * drm_dp_downstream_mode() - return a mode for downstream facing port - * @dev: DRM device - * @dpcd: DisplayPort configuration data - * @port_cap: port capabilities - * - * Provides a suitable mode for downstream facing ports without EDID. - * - * Returns: A new drm_display_mode on success or NULL on failure - */ -struct drm_display_mode * -drm_dp_downstream_mode(struct drm_device *dev, - const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4]) - -{ - u8 vic; - - if (!drm_dp_is_branch(dpcd)) - return NULL; - - if (dpcd[DP_DPCD_REV] < 0x11) - return NULL; - - switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { - case DP_DS_PORT_TYPE_NON_EDID: - switch (port_cap[0] & DP_DS_NON_EDID_MASK) { - case DP_DS_NON_EDID_720x480i_60: - vic = 6; - break; - case DP_DS_NON_EDID_720x480i_50: - vic = 21; - break; - case DP_DS_NON_EDID_1920x1080i_60: - vic = 5; - break; - case DP_DS_NON_EDID_1920x1080i_50: - vic = 20; - break; - case DP_DS_NON_EDID_1280x720_60: - vic = 4; - break; - case DP_DS_NON_EDID_1280x720_50: - vic = 19; - break; - default: - return NULL; - } - return drm_display_mode_from_cea_vic(dev, vic); - default: - return NULL; - } -} -EXPORT_SYMBOL(drm_dp_downstream_mode); - -/** - * drm_dp_downstream_id() - identify branch device - * @aux: DisplayPort AUX channel - * @id: DisplayPort branch device id - * - * Returns branch device id on success or NULL on failure - */ -int drm_dp_downstream_id(struct drm_dp_aux *aux, char id[6]) -{ - return drm_dp_dpcd_read(aux, DP_BRANCH_ID, id, 6); -} -EXPORT_SYMBOL(drm_dp_downstream_id); - -/** - * drm_dp_downstream_debug() - debug DP branch devices - * @m: pointer for debugfs file - * @dpcd: DisplayPort configuration data - * @port_cap: port capabilities - * @edid: EDID - * @aux: DisplayPort AUX channel - * - */ -void drm_dp_downstream_debug(struct seq_file *m, - const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4], - const struct edid *edid, - struct drm_dp_aux *aux) -{ - bool detailed_cap_info = dpcd[DP_DOWNSTREAMPORT_PRESENT] & - DP_DETAILED_CAP_INFO_AVAILABLE; - int clk; - int bpc; - char id[7]; - int len; - uint8_t rev[2]; - int type = port_cap[0] & DP_DS_PORT_TYPE_MASK; - bool branch_device = drm_dp_is_branch(dpcd); - - seq_printf(m, "\tDP branch device present: %s\n", - branch_device ? "yes" : "no"); - - if (!branch_device) - return; - - switch (type) { - case DP_DS_PORT_TYPE_DP: - seq_puts(m, "\t\tType: DisplayPort\n"); - break; - case DP_DS_PORT_TYPE_VGA: - seq_puts(m, "\t\tType: VGA\n"); - break; - case DP_DS_PORT_TYPE_DVI: - seq_puts(m, "\t\tType: DVI\n"); - break; - case DP_DS_PORT_TYPE_HDMI: - seq_puts(m, "\t\tType: HDMI\n"); - break; - case DP_DS_PORT_TYPE_NON_EDID: - seq_puts(m, "\t\tType: others without EDID support\n"); - break; - case DP_DS_PORT_TYPE_DP_DUALMODE: - seq_puts(m, "\t\tType: DP++\n"); - break; - case DP_DS_PORT_TYPE_WIRELESS: - seq_puts(m, "\t\tType: Wireless\n"); - break; - default: - seq_puts(m, "\t\tType: N/A\n"); - } - - memset(id, 0, sizeof(id)); - drm_dp_downstream_id(aux, id); - seq_printf(m, "\t\tID: %s\n", id); - - len = drm_dp_dpcd_read(aux, DP_BRANCH_HW_REV, &rev[0], 1); - if (len > 0) - seq_printf(m, "\t\tHW: %d.%d\n", - (rev[0] & 0xf0) >> 4, rev[0] & 0xf); - - len = drm_dp_dpcd_read(aux, DP_BRANCH_SW_REV, rev, 2); - if (len > 0) - seq_printf(m, "\t\tSW: %d.%d\n", rev[0], rev[1]); - - if (detailed_cap_info) { - clk = drm_dp_downstream_max_dotclock(dpcd, port_cap); - if (clk > 0) - seq_printf(m, "\t\tMax dot clock: %d kHz\n", clk); - - clk = drm_dp_downstream_max_tmds_clock(dpcd, port_cap, edid); - if (clk > 0) - seq_printf(m, "\t\tMax TMDS clock: %d kHz\n", clk); - - clk = drm_dp_downstream_min_tmds_clock(dpcd, port_cap, edid); - if (clk > 0) - seq_printf(m, "\t\tMin TMDS clock: %d kHz\n", clk); - - bpc = drm_dp_downstream_max_bpc(dpcd, port_cap, edid); - - if (bpc > 0) - seq_printf(m, "\t\tMax bpc: %d\n", bpc); - } -} -EXPORT_SYMBOL(drm_dp_downstream_debug); - -/** - * drm_dp_subconnector_type() - get DP branch device type - * @dpcd: DisplayPort configuration data - * @port_cap: port capabilities - */ -enum drm_mode_subconnector -drm_dp_subconnector_type(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4]) -{ - int type; - if (!drm_dp_is_branch(dpcd)) - return DRM_MODE_SUBCONNECTOR_Native; - /* DP 1.0 approach */ - if (dpcd[DP_DPCD_REV] == DP_DPCD_REV_10) { - type = dpcd[DP_DOWNSTREAMPORT_PRESENT] & - DP_DWN_STRM_PORT_TYPE_MASK; - - switch (type) { - case DP_DWN_STRM_PORT_TYPE_TMDS: - /* Can be HDMI or DVI-D, DVI-D is a safer option */ - return DRM_MODE_SUBCONNECTOR_DVID; - case DP_DWN_STRM_PORT_TYPE_ANALOG: - /* Can be VGA or DVI-A, VGA is more popular */ - return DRM_MODE_SUBCONNECTOR_VGA; - case DP_DWN_STRM_PORT_TYPE_DP: - return DRM_MODE_SUBCONNECTOR_DisplayPort; - case DP_DWN_STRM_PORT_TYPE_OTHER: - default: - return DRM_MODE_SUBCONNECTOR_Unknown; - } - } - type = port_cap[0] & DP_DS_PORT_TYPE_MASK; - - switch (type) { - case DP_DS_PORT_TYPE_DP: - case DP_DS_PORT_TYPE_DP_DUALMODE: - return DRM_MODE_SUBCONNECTOR_DisplayPort; - case DP_DS_PORT_TYPE_VGA: - return DRM_MODE_SUBCONNECTOR_VGA; - case DP_DS_PORT_TYPE_DVI: - return DRM_MODE_SUBCONNECTOR_DVID; - case DP_DS_PORT_TYPE_HDMI: - return DRM_MODE_SUBCONNECTOR_HDMIA; - case DP_DS_PORT_TYPE_WIRELESS: - return DRM_MODE_SUBCONNECTOR_Wireless; - case DP_DS_PORT_TYPE_NON_EDID: - default: - return DRM_MODE_SUBCONNECTOR_Unknown; - } -} -EXPORT_SYMBOL(drm_dp_subconnector_type); - -/** - * drm_dp_set_subconnector_property - set subconnector for DP connector - * @connector: connector to set property on - * @status: connector status - * @dpcd: DisplayPort configuration data - * @port_cap: port capabilities - * - * Called by a driver on every detect event. - */ -void drm_dp_set_subconnector_property(struct drm_connector *connector, - enum drm_connector_status status, - const u8 *dpcd, - const u8 port_cap[4]) -{ - enum drm_mode_subconnector subconnector = DRM_MODE_SUBCONNECTOR_Unknown; - - if (status == connector_status_connected) - subconnector = drm_dp_subconnector_type(dpcd, port_cap); - drm_object_property_set_value(&connector->base, - connector->dev->mode_config.dp_subconnector_property, - subconnector); -} -EXPORT_SYMBOL(drm_dp_set_subconnector_property); - -/** - * drm_dp_read_sink_count_cap() - Check whether a given connector has a valid sink - * count - * @connector: The DRM connector to check - * @dpcd: A cached copy of the connector's DPCD RX capabilities - * @desc: A cached copy of the connector's DP descriptor - * - * See also: drm_dp_read_sink_count() - * - * Returns: %True if the (e)DP connector has a valid sink count that should - * be probed, %false otherwise. - */ -bool drm_dp_read_sink_count_cap(struct drm_connector *connector, - const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const struct drm_dp_desc *desc) -{ - /* Some eDP panels don't set a valid value for the sink count */ - return connector->connector_type != DRM_MODE_CONNECTOR_eDP && - dpcd[DP_DPCD_REV] >= DP_DPCD_REV_11 && - dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT && - !drm_dp_has_quirk(desc, DP_DPCD_QUIRK_NO_SINK_COUNT); -} -EXPORT_SYMBOL(drm_dp_read_sink_count_cap); - -/** - * drm_dp_read_sink_count() - Retrieve the sink count for a given sink - * @aux: The DP AUX channel to use - * - * See also: drm_dp_read_sink_count_cap() - * - * Returns: The current sink count reported by @aux, or a negative error code - * otherwise. - */ -int drm_dp_read_sink_count(struct drm_dp_aux *aux) -{ - u8 count; - int ret; - - ret = drm_dp_dpcd_readb(aux, DP_SINK_COUNT, &count); - if (ret < 0) - return ret; - if (ret != 1) - return -EIO; - - return DP_GET_SINK_COUNT(count); -} -EXPORT_SYMBOL(drm_dp_read_sink_count); - -/* - * I2C-over-AUX implementation - */ - -static u32 drm_dp_i2c_functionality(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | - I2C_FUNC_SMBUS_READ_BLOCK_DATA | - I2C_FUNC_SMBUS_BLOCK_PROC_CALL | - I2C_FUNC_10BIT_ADDR; -} - -static void drm_dp_i2c_msg_write_status_update(struct drm_dp_aux_msg *msg) -{ - /* - * In case of i2c defer or short i2c ack reply to a write, - * we need to switch to WRITE_STATUS_UPDATE to drain the - * rest of the message - */ - if ((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_I2C_WRITE) { - msg->request &= DP_AUX_I2C_MOT; - msg->request |= DP_AUX_I2C_WRITE_STATUS_UPDATE; - } -} - -#define AUX_PRECHARGE_LEN 10 /* 10 to 16 */ -#define AUX_SYNC_LEN (16 + 4) /* preamble + AUX_SYNC_END */ -#define AUX_STOP_LEN 4 -#define AUX_CMD_LEN 4 -#define AUX_ADDRESS_LEN 20 -#define AUX_REPLY_PAD_LEN 4 -#define AUX_LENGTH_LEN 8 - -/* - * Calculate the duration of the AUX request/reply in usec. Gives the - * "best" case estimate, ie. successful while as short as possible. - */ -static int drm_dp_aux_req_duration(const struct drm_dp_aux_msg *msg) -{ - int len = AUX_PRECHARGE_LEN + AUX_SYNC_LEN + AUX_STOP_LEN + - AUX_CMD_LEN + AUX_ADDRESS_LEN + AUX_LENGTH_LEN; - - if ((msg->request & DP_AUX_I2C_READ) == 0) - len += msg->size * 8; - - return len; -} - -static int drm_dp_aux_reply_duration(const struct drm_dp_aux_msg *msg) -{ - int len = AUX_PRECHARGE_LEN + AUX_SYNC_LEN + AUX_STOP_LEN + - AUX_CMD_LEN + AUX_REPLY_PAD_LEN; - - /* - * For read we expect what was asked. For writes there will - * be 0 or 1 data bytes. Assume 0 for the "best" case. - */ - if (msg->request & DP_AUX_I2C_READ) - len += msg->size * 8; - - return len; -} - -#define I2C_START_LEN 1 -#define I2C_STOP_LEN 1 -#define I2C_ADDR_LEN 9 /* ADDRESS + R/W + ACK/NACK */ -#define I2C_DATA_LEN 9 /* DATA + ACK/NACK */ - -/* - * Calculate the length of the i2c transfer in usec, assuming - * the i2c bus speed is as specified. Gives the the "worst" - * case estimate, ie. successful while as long as possible. - * Doesn't account the the "MOT" bit, and instead assumes each - * message includes a START, ADDRESS and STOP. Neither does it - * account for additional random variables such as clock stretching. - */ -static int drm_dp_i2c_msg_duration(const struct drm_dp_aux_msg *msg, - int i2c_speed_khz) -{ - /* AUX bitrate is 1MHz, i2c bitrate as specified */ - return DIV_ROUND_UP((I2C_START_LEN + I2C_ADDR_LEN + - msg->size * I2C_DATA_LEN + - I2C_STOP_LEN) * 1000, i2c_speed_khz); -} - -/* - * Determine how many retries should be attempted to successfully transfer - * the specified message, based on the estimated durations of the - * i2c and AUX transfers. - */ -static int drm_dp_i2c_retry_count(const struct drm_dp_aux_msg *msg, - int i2c_speed_khz) -{ - int aux_time_us = drm_dp_aux_req_duration(msg) + - drm_dp_aux_reply_duration(msg); - int i2c_time_us = drm_dp_i2c_msg_duration(msg, i2c_speed_khz); - - return DIV_ROUND_UP(i2c_time_us, aux_time_us + AUX_RETRY_INTERVAL); -} - -/* - * FIXME currently assumes 10 kHz as some real world devices seem - * to require it. We should query/set the speed via DPCD if supported. - */ -static int dp_aux_i2c_speed_khz __read_mostly = 10; -module_param_unsafe(dp_aux_i2c_speed_khz, int, 0644); -MODULE_PARM_DESC(dp_aux_i2c_speed_khz, - "Assumed speed of the i2c bus in kHz, (1-400, default 10)"); - -/* - * Transfer a single I2C-over-AUX message and handle various error conditions, - * retrying the transaction as appropriate. It is assumed that the - * &drm_dp_aux.transfer function does not modify anything in the msg other than the - * reply field. - * - * Returns bytes transferred on success, or a negative error code on failure. - */ -static int drm_dp_i2c_do_msg(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) -{ - unsigned int retry, defer_i2c; - int ret; - /* - * DP1.2 sections 2.7.7.1.5.6.1 and 2.7.7.1.6.6.1: A DP Source device - * is required to retry at least seven times upon receiving AUX_DEFER - * before giving up the AUX transaction. - * - * We also try to account for the i2c bus speed. - */ - int max_retries = max(7, drm_dp_i2c_retry_count(msg, dp_aux_i2c_speed_khz)); - - for (retry = 0, defer_i2c = 0; retry < (max_retries + defer_i2c); retry++) { - ret = aux->transfer(aux, msg); - if (ret < 0) { - if (ret == -EBUSY) - continue; - - /* - * While timeouts can be errors, they're usually normal - * behavior (for instance, when a driver tries to - * communicate with a non-existent DisplayPort device). - * Avoid spamming the kernel log with timeout errors. - */ - if (ret == -ETIMEDOUT) - drm_dbg_kms_ratelimited(aux->drm_dev, "%s: transaction timed out\n", - aux->name); - else - drm_dbg_kms(aux->drm_dev, "%s: transaction failed: %d\n", - aux->name, ret); - return ret; - } - - - switch (msg->reply & DP_AUX_NATIVE_REPLY_MASK) { - case DP_AUX_NATIVE_REPLY_ACK: - /* - * For I2C-over-AUX transactions this isn't enough, we - * need to check for the I2C ACK reply. - */ - break; - - case DP_AUX_NATIVE_REPLY_NACK: - drm_dbg_kms(aux->drm_dev, "%s: native nack (result=%d, size=%zu)\n", - aux->name, ret, msg->size); - return -EREMOTEIO; - - case DP_AUX_NATIVE_REPLY_DEFER: - drm_dbg_kms(aux->drm_dev, "%s: native defer\n", aux->name); - /* - * We could check for I2C bit rate capabilities and if - * available adjust this interval. We could also be - * more careful with DP-to-legacy adapters where a - * long legacy cable may force very low I2C bit rates. - * - * For now just defer for long enough to hopefully be - * safe for all use-cases. - */ - usleep_range(AUX_RETRY_INTERVAL, AUX_RETRY_INTERVAL + 100); - continue; - - default: - drm_err(aux->drm_dev, "%s: invalid native reply %#04x\n", - aux->name, msg->reply); - return -EREMOTEIO; - } - - switch (msg->reply & DP_AUX_I2C_REPLY_MASK) { - case DP_AUX_I2C_REPLY_ACK: - /* - * Both native ACK and I2C ACK replies received. We - * can assume the transfer was successful. - */ - if (ret != msg->size) - drm_dp_i2c_msg_write_status_update(msg); - return ret; - - case DP_AUX_I2C_REPLY_NACK: - drm_dbg_kms(aux->drm_dev, "%s: I2C nack (result=%d, size=%zu)\n", - aux->name, ret, msg->size); - aux->i2c_nack_count++; - return -EREMOTEIO; - - case DP_AUX_I2C_REPLY_DEFER: - drm_dbg_kms(aux->drm_dev, "%s: I2C defer\n", aux->name); - /* DP Compliance Test 4.2.2.5 Requirement: - * Must have at least 7 retries for I2C defers on the - * transaction to pass this test - */ - aux->i2c_defer_count++; - if (defer_i2c < 7) - defer_i2c++; - usleep_range(AUX_RETRY_INTERVAL, AUX_RETRY_INTERVAL + 100); - drm_dp_i2c_msg_write_status_update(msg); - - continue; - - default: - drm_err(aux->drm_dev, "%s: invalid I2C reply %#04x\n", - aux->name, msg->reply); - return -EREMOTEIO; - } - } - - drm_dbg_kms(aux->drm_dev, "%s: Too many retries, giving up\n", aux->name); - return -EREMOTEIO; -} - -static void drm_dp_i2c_msg_set_request(struct drm_dp_aux_msg *msg, - const struct i2c_msg *i2c_msg) -{ - msg->request = (i2c_msg->flags & I2C_M_RD) ? - DP_AUX_I2C_READ : DP_AUX_I2C_WRITE; - if (!(i2c_msg->flags & I2C_M_STOP)) - msg->request |= DP_AUX_I2C_MOT; -} - -/* - * Keep retrying drm_dp_i2c_do_msg until all data has been transferred. - * - * Returns an error code on failure, or a recommended transfer size on success. - */ -static int drm_dp_i2c_drain_msg(struct drm_dp_aux *aux, struct drm_dp_aux_msg *orig_msg) -{ - int err, ret = orig_msg->size; - struct drm_dp_aux_msg msg = *orig_msg; - - while (msg.size > 0) { - err = drm_dp_i2c_do_msg(aux, &msg); - if (err <= 0) - return err == 0 ? -EPROTO : err; - - if (err < msg.size && err < ret) { - drm_dbg_kms(aux->drm_dev, - "%s: Partial I2C reply: requested %zu bytes got %d bytes\n", - aux->name, msg.size, err); - ret = err; - } - - msg.size -= err; - msg.buffer += err; - } - - return ret; -} - -/* - * Bizlink designed DP->DVI-D Dual Link adapters require the I2C over AUX - * packets to be as large as possible. If not, the I2C transactions never - * succeed. Hence the default is maximum. - */ -static int dp_aux_i2c_transfer_size __read_mostly = DP_AUX_MAX_PAYLOAD_BYTES; -module_param_unsafe(dp_aux_i2c_transfer_size, int, 0644); -MODULE_PARM_DESC(dp_aux_i2c_transfer_size, - "Number of bytes to transfer in a single I2C over DP AUX CH message, (1-16, default 16)"); - -static int drm_dp_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, - int num) -{ - struct drm_dp_aux *aux = adapter->algo_data; - unsigned int i, j; - unsigned transfer_size; - struct drm_dp_aux_msg msg; - int err = 0; - - dp_aux_i2c_transfer_size = clamp(dp_aux_i2c_transfer_size, 1, DP_AUX_MAX_PAYLOAD_BYTES); - - memset(&msg, 0, sizeof(msg)); - - for (i = 0; i < num; i++) { - msg.address = msgs[i].addr; - drm_dp_i2c_msg_set_request(&msg, &msgs[i]); - /* Send a bare address packet to start the transaction. - * Zero sized messages specify an address only (bare - * address) transaction. - */ - msg.buffer = NULL; - msg.size = 0; - err = drm_dp_i2c_do_msg(aux, &msg); - - /* - * Reset msg.request in case in case it got - * changed into a WRITE_STATUS_UPDATE. - */ - drm_dp_i2c_msg_set_request(&msg, &msgs[i]); - - if (err < 0) - break; - /* We want each transaction to be as large as possible, but - * we'll go to smaller sizes if the hardware gives us a - * short reply. - */ - transfer_size = dp_aux_i2c_transfer_size; - for (j = 0; j < msgs[i].len; j += msg.size) { - msg.buffer = msgs[i].buf + j; - msg.size = min(transfer_size, msgs[i].len - j); - - err = drm_dp_i2c_drain_msg(aux, &msg); - - /* - * Reset msg.request in case in case it got - * changed into a WRITE_STATUS_UPDATE. - */ - drm_dp_i2c_msg_set_request(&msg, &msgs[i]); - - if (err < 0) - break; - transfer_size = err; - } - if (err < 0) - break; - } - if (err >= 0) - err = num; - /* Send a bare address packet to close out the transaction. - * Zero sized messages specify an address only (bare - * address) transaction. - */ - msg.request &= ~DP_AUX_I2C_MOT; - msg.buffer = NULL; - msg.size = 0; - (void)drm_dp_i2c_do_msg(aux, &msg); - - return err; -} - -static const struct i2c_algorithm drm_dp_i2c_algo = { - .functionality = drm_dp_i2c_functionality, - .master_xfer = drm_dp_i2c_xfer, -}; - -static struct drm_dp_aux *i2c_to_aux(struct i2c_adapter *i2c) -{ - return container_of(i2c, struct drm_dp_aux, ddc); -} - -static void lock_bus(struct i2c_adapter *i2c, unsigned int flags) -{ - mutex_lock(&i2c_to_aux(i2c)->hw_mutex); -} - -static int trylock_bus(struct i2c_adapter *i2c, unsigned int flags) -{ - return mutex_trylock(&i2c_to_aux(i2c)->hw_mutex); -} - -static void unlock_bus(struct i2c_adapter *i2c, unsigned int flags) -{ - mutex_unlock(&i2c_to_aux(i2c)->hw_mutex); -} - -static const struct i2c_lock_operations drm_dp_i2c_lock_ops = { - .lock_bus = lock_bus, - .trylock_bus = trylock_bus, - .unlock_bus = unlock_bus, -}; - -static int drm_dp_aux_get_crc(struct drm_dp_aux *aux, u8 *crc) -{ - u8 buf, count; - int ret; - - ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK, &buf); - if (ret < 0) - return ret; - - WARN_ON(!(buf & DP_TEST_SINK_START)); - - ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK_MISC, &buf); - if (ret < 0) - return ret; - - count = buf & DP_TEST_COUNT_MASK; - if (count == aux->crc_count) - return -EAGAIN; /* No CRC yet */ - - aux->crc_count = count; - - /* - * At DP_TEST_CRC_R_CR, there's 6 bytes containing CRC data, 2 bytes - * per component (RGB or CrYCb). - */ - ret = drm_dp_dpcd_read(aux, DP_TEST_CRC_R_CR, crc, 6); - if (ret < 0) - return ret; - - return 0; -} - -static void drm_dp_aux_crc_work(struct work_struct *work) -{ - struct drm_dp_aux *aux = container_of(work, struct drm_dp_aux, - crc_work); - struct drm_crtc *crtc; - u8 crc_bytes[6]; - uint32_t crcs[3]; - int ret; - - if (WARN_ON(!aux->crtc)) - return; - - crtc = aux->crtc; - while (crtc->crc.opened) { - drm_crtc_wait_one_vblank(crtc); - if (!crtc->crc.opened) - break; - - ret = drm_dp_aux_get_crc(aux, crc_bytes); - if (ret == -EAGAIN) { - usleep_range(1000, 2000); - ret = drm_dp_aux_get_crc(aux, crc_bytes); - } - - if (ret == -EAGAIN) { - drm_dbg_kms(aux->drm_dev, "%s: Get CRC failed after retrying: %d\n", - aux->name, ret); - continue; - } else if (ret) { - drm_dbg_kms(aux->drm_dev, "%s: Failed to get a CRC: %d\n", aux->name, ret); - continue; - } - - crcs[0] = crc_bytes[0] | crc_bytes[1] << 8; - crcs[1] = crc_bytes[2] | crc_bytes[3] << 8; - crcs[2] = crc_bytes[4] | crc_bytes[5] << 8; - drm_crtc_add_crc_entry(crtc, false, 0, crcs); - } -} - -/** - * drm_dp_remote_aux_init() - minimally initialise a remote aux channel - * @aux: DisplayPort AUX channel - * - * Used for remote aux channel in general. Merely initialize the crc work - * struct. - */ -void drm_dp_remote_aux_init(struct drm_dp_aux *aux) -{ - INIT_WORK(&aux->crc_work, drm_dp_aux_crc_work); -} -EXPORT_SYMBOL(drm_dp_remote_aux_init); - -/** - * drm_dp_aux_init() - minimally initialise an aux channel - * @aux: DisplayPort AUX channel - * - * If you need to use the drm_dp_aux's i2c adapter prior to registering it with - * the outside world, call drm_dp_aux_init() first. For drivers which are - * grandparents to their AUX adapters (e.g. the AUX adapter is parented by a - * &drm_connector), you must still call drm_dp_aux_register() once the connector - * has been registered to allow userspace access to the auxiliary DP channel. - * Likewise, for such drivers you should also assign &drm_dp_aux.drm_dev as - * early as possible so that the &drm_device that corresponds to the AUX adapter - * may be mentioned in debugging output from the DRM DP helpers. - * - * For devices which use a separate platform device for their AUX adapters, this - * may be called as early as required by the driver. - * - */ -void drm_dp_aux_init(struct drm_dp_aux *aux) -{ - mutex_init(&aux->hw_mutex); - mutex_init(&aux->cec.lock); - INIT_WORK(&aux->crc_work, drm_dp_aux_crc_work); - - aux->ddc.algo = &drm_dp_i2c_algo; - aux->ddc.algo_data = aux; - aux->ddc.retries = 3; - - aux->ddc.lock_ops = &drm_dp_i2c_lock_ops; -} -EXPORT_SYMBOL(drm_dp_aux_init); - -/** - * drm_dp_aux_register() - initialise and register aux channel - * @aux: DisplayPort AUX channel - * - * Automatically calls drm_dp_aux_init() if this hasn't been done yet. This - * should only be called once the parent of @aux, &drm_dp_aux.dev, is - * initialized. For devices which are grandparents of their AUX channels, - * &drm_dp_aux.dev will typically be the &drm_connector &device which - * corresponds to @aux. For these devices, it's advised to call - * drm_dp_aux_register() in &drm_connector_funcs.late_register, and likewise to - * call drm_dp_aux_unregister() in &drm_connector_funcs.early_unregister. - * Functions which don't follow this will likely Oops when - * %CONFIG_DRM_DP_AUX_CHARDEV is enabled. - * - * For devices where the AUX channel is a device that exists independently of - * the &drm_device that uses it, such as SoCs and bridge devices, it is - * recommended to call drm_dp_aux_register() after a &drm_device has been - * assigned to &drm_dp_aux.drm_dev, and likewise to call - * drm_dp_aux_unregister() once the &drm_device should no longer be associated - * with the AUX channel (e.g. on bridge detach). - * - * Drivers which need to use the aux channel before either of the two points - * mentioned above need to call drm_dp_aux_init() in order to use the AUX - * channel before registration. - * - * Returns 0 on success or a negative error code on failure. - */ -int drm_dp_aux_register(struct drm_dp_aux *aux) -{ - int ret; - - WARN_ON_ONCE(!aux->drm_dev); - - if (!aux->ddc.algo) - drm_dp_aux_init(aux); - - aux->ddc.class = I2C_CLASS_DDC; - aux->ddc.owner = THIS_MODULE; - aux->ddc.dev.parent = aux->dev; - - strlcpy(aux->ddc.name, aux->name ? aux->name : dev_name(aux->dev), - sizeof(aux->ddc.name)); - - ret = drm_dp_aux_register_devnode(aux); - if (ret) - return ret; - - ret = i2c_add_adapter(&aux->ddc); - if (ret) { - drm_dp_aux_unregister_devnode(aux); - return ret; - } - - return 0; -} -EXPORT_SYMBOL(drm_dp_aux_register); - -/** - * drm_dp_aux_unregister() - unregister an AUX adapter - * @aux: DisplayPort AUX channel - */ -void drm_dp_aux_unregister(struct drm_dp_aux *aux) -{ - drm_dp_aux_unregister_devnode(aux); - i2c_del_adapter(&aux->ddc); -} -EXPORT_SYMBOL(drm_dp_aux_unregister); - -#define PSR_SETUP_TIME(x) [DP_PSR_SETUP_TIME_ ## x >> DP_PSR_SETUP_TIME_SHIFT] = (x) - -/** - * drm_dp_psr_setup_time() - PSR setup in time usec - * @psr_cap: PSR capabilities from DPCD - * - * Returns: - * PSR setup time for the panel in microseconds, negative - * error code on failure. - */ -int drm_dp_psr_setup_time(const u8 psr_cap[EDP_PSR_RECEIVER_CAP_SIZE]) -{ - static const u16 psr_setup_time_us[] = { - PSR_SETUP_TIME(330), - PSR_SETUP_TIME(275), - PSR_SETUP_TIME(220), - PSR_SETUP_TIME(165), - PSR_SETUP_TIME(110), - PSR_SETUP_TIME(55), - PSR_SETUP_TIME(0), - }; - int i; - - i = (psr_cap[1] & DP_PSR_SETUP_TIME_MASK) >> DP_PSR_SETUP_TIME_SHIFT; - if (i >= ARRAY_SIZE(psr_setup_time_us)) - return -EINVAL; - - return psr_setup_time_us[i]; -} -EXPORT_SYMBOL(drm_dp_psr_setup_time); - -#undef PSR_SETUP_TIME - -/** - * drm_dp_start_crc() - start capture of frame CRCs - * @aux: DisplayPort AUX channel - * @crtc: CRTC displaying the frames whose CRCs are to be captured - * - * Returns 0 on success or a negative error code on failure. - */ -int drm_dp_start_crc(struct drm_dp_aux *aux, struct drm_crtc *crtc) -{ - u8 buf; - int ret; - - ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK, &buf); - if (ret < 0) - return ret; - - ret = drm_dp_dpcd_writeb(aux, DP_TEST_SINK, buf | DP_TEST_SINK_START); - if (ret < 0) - return ret; - - aux->crc_count = 0; - aux->crtc = crtc; - schedule_work(&aux->crc_work); - - return 0; -} -EXPORT_SYMBOL(drm_dp_start_crc); - -/** - * drm_dp_stop_crc() - stop capture of frame CRCs - * @aux: DisplayPort AUX channel - * - * Returns 0 on success or a negative error code on failure. - */ -int drm_dp_stop_crc(struct drm_dp_aux *aux) -{ - u8 buf; - int ret; - - ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK, &buf); - if (ret < 0) - return ret; - - ret = drm_dp_dpcd_writeb(aux, DP_TEST_SINK, buf & ~DP_TEST_SINK_START); - if (ret < 0) - return ret; - - flush_work(&aux->crc_work); - aux->crtc = NULL; - - return 0; -} -EXPORT_SYMBOL(drm_dp_stop_crc); - -struct dpcd_quirk { - u8 oui[3]; - u8 device_id[6]; - bool is_branch; - u32 quirks; -}; - -#define OUI(first, second, third) { (first), (second), (third) } -#define DEVICE_ID(first, second, third, fourth, fifth, sixth) \ - { (first), (second), (third), (fourth), (fifth), (sixth) } - -#define DEVICE_ID_ANY DEVICE_ID(0, 0, 0, 0, 0, 0) - -static const struct dpcd_quirk dpcd_quirk_list[] = { - /* Analogix 7737 needs reduced M and N at HBR2 link rates */ - { OUI(0x00, 0x22, 0xb9), DEVICE_ID_ANY, true, BIT(DP_DPCD_QUIRK_CONSTANT_N) }, - /* LG LP140WF6-SPM1 eDP panel */ - { OUI(0x00, 0x22, 0xb9), DEVICE_ID('s', 'i', 'v', 'a', 'r', 'T'), false, BIT(DP_DPCD_QUIRK_CONSTANT_N) }, - /* Apple panels need some additional handling to support PSR */ - { OUI(0x00, 0x10, 0xfa), DEVICE_ID_ANY, false, BIT(DP_DPCD_QUIRK_NO_PSR) }, - /* CH7511 seems to leave SINK_COUNT zeroed */ - { OUI(0x00, 0x00, 0x00), DEVICE_ID('C', 'H', '7', '5', '1', '1'), false, BIT(DP_DPCD_QUIRK_NO_SINK_COUNT) }, - /* Synaptics DP1.4 MST hubs can support DSC without virtual DPCD */ - { OUI(0x90, 0xCC, 0x24), DEVICE_ID_ANY, true, BIT(DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD) }, - /* Apple MacBookPro 2017 15 inch eDP Retina panel reports too low DP_MAX_LINK_RATE */ - { OUI(0x00, 0x10, 0xfa), DEVICE_ID(101, 68, 21, 101, 98, 97), false, BIT(DP_DPCD_QUIRK_CAN_DO_MAX_LINK_RATE_3_24_GBPS) }, -}; - -#undef OUI - -/* - * Get a bit mask of DPCD quirks for the sink/branch device identified by - * ident. The quirk data is shared but it's up to the drivers to act on the - * data. - * - * For now, only the OUI (first three bytes) is used, but this may be extended - * to device identification string and hardware/firmware revisions later. - */ -static u32 -drm_dp_get_quirks(const struct drm_dp_dpcd_ident *ident, bool is_branch) -{ - const struct dpcd_quirk *quirk; - u32 quirks = 0; - int i; - u8 any_device[] = DEVICE_ID_ANY; - - for (i = 0; i < ARRAY_SIZE(dpcd_quirk_list); i++) { - quirk = &dpcd_quirk_list[i]; - - if (quirk->is_branch != is_branch) - continue; - - if (memcmp(quirk->oui, ident->oui, sizeof(ident->oui)) != 0) - continue; - - if (memcmp(quirk->device_id, any_device, sizeof(any_device)) != 0 && - memcmp(quirk->device_id, ident->device_id, sizeof(ident->device_id)) != 0) - continue; - - quirks |= quirk->quirks; - } - - return quirks; -} - -#undef DEVICE_ID_ANY -#undef DEVICE_ID - -/** - * drm_dp_read_desc - read sink/branch descriptor from DPCD - * @aux: DisplayPort AUX channel - * @desc: Device descriptor to fill from DPCD - * @is_branch: true for branch devices, false for sink devices - * - * Read DPCD 0x400 (sink) or 0x500 (branch) into @desc. Also debug log the - * identification. - * - * Returns 0 on success or a negative error code on failure. - */ -int drm_dp_read_desc(struct drm_dp_aux *aux, struct drm_dp_desc *desc, - bool is_branch) -{ - struct drm_dp_dpcd_ident *ident = &desc->ident; - unsigned int offset = is_branch ? DP_BRANCH_OUI : DP_SINK_OUI; - int ret, dev_id_len; - - ret = drm_dp_dpcd_read(aux, offset, ident, sizeof(*ident)); - if (ret < 0) - return ret; - - desc->quirks = drm_dp_get_quirks(ident, is_branch); - - dev_id_len = strnlen(ident->device_id, sizeof(ident->device_id)); - - drm_dbg_kms(aux->drm_dev, - "%s: DP %s: OUI %*phD dev-ID %*pE HW-rev %d.%d SW-rev %d.%d quirks 0x%04x\n", - aux->name, is_branch ? "branch" : "sink", - (int)sizeof(ident->oui), ident->oui, dev_id_len, - ident->device_id, ident->hw_rev >> 4, ident->hw_rev & 0xf, - ident->sw_major_rev, ident->sw_minor_rev, desc->quirks); - - return 0; -} -EXPORT_SYMBOL(drm_dp_read_desc); - -/** - * drm_dp_dsc_sink_max_slice_count() - Get the max slice count - * supported by the DSC sink. - * @dsc_dpcd: DSC capabilities from DPCD - * @is_edp: true if its eDP, false for DP - * - * Read the slice capabilities DPCD register from DSC sink to get - * the maximum slice count supported. This is used to populate - * the DSC parameters in the &struct drm_dsc_config by the driver. - * Driver creates an infoframe using these parameters to populate - * &struct drm_dsc_pps_infoframe. These are sent to the sink using DSC - * infoframe using the helper function drm_dsc_pps_infoframe_pack() - * - * Returns: - * Maximum slice count supported by DSC sink or 0 its invalid - */ -u8 drm_dp_dsc_sink_max_slice_count(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE], - bool is_edp) -{ - u8 slice_cap1 = dsc_dpcd[DP_DSC_SLICE_CAP_1 - DP_DSC_SUPPORT]; - - if (is_edp) { - /* For eDP, register DSC_SLICE_CAPABILITIES_1 gives slice count */ - if (slice_cap1 & DP_DSC_4_PER_DP_DSC_SINK) - return 4; - if (slice_cap1 & DP_DSC_2_PER_DP_DSC_SINK) - return 2; - if (slice_cap1 & DP_DSC_1_PER_DP_DSC_SINK) - return 1; - } else { - /* For DP, use values from DSC_SLICE_CAP_1 and DSC_SLICE_CAP2 */ - u8 slice_cap2 = dsc_dpcd[DP_DSC_SLICE_CAP_2 - DP_DSC_SUPPORT]; - - if (slice_cap2 & DP_DSC_24_PER_DP_DSC_SINK) - return 24; - if (slice_cap2 & DP_DSC_20_PER_DP_DSC_SINK) - return 20; - if (slice_cap2 & DP_DSC_16_PER_DP_DSC_SINK) - return 16; - if (slice_cap1 & DP_DSC_12_PER_DP_DSC_SINK) - return 12; - if (slice_cap1 & DP_DSC_10_PER_DP_DSC_SINK) - return 10; - if (slice_cap1 & DP_DSC_8_PER_DP_DSC_SINK) - return 8; - if (slice_cap1 & DP_DSC_6_PER_DP_DSC_SINK) - return 6; - if (slice_cap1 & DP_DSC_4_PER_DP_DSC_SINK) - return 4; - if (slice_cap1 & DP_DSC_2_PER_DP_DSC_SINK) - return 2; - if (slice_cap1 & DP_DSC_1_PER_DP_DSC_SINK) - return 1; - } - - return 0; -} -EXPORT_SYMBOL(drm_dp_dsc_sink_max_slice_count); - -/** - * drm_dp_dsc_sink_line_buf_depth() - Get the line buffer depth in bits - * @dsc_dpcd: DSC capabilities from DPCD - * - * Read the DSC DPCD register to parse the line buffer depth in bits which is - * number of bits of precision within the decoder line buffer supported by - * the DSC sink. This is used to populate the DSC parameters in the - * &struct drm_dsc_config by the driver. - * Driver creates an infoframe using these parameters to populate - * &struct drm_dsc_pps_infoframe. These are sent to the sink using DSC - * infoframe using the helper function drm_dsc_pps_infoframe_pack() - * - * Returns: - * Line buffer depth supported by DSC panel or 0 its invalid - */ -u8 drm_dp_dsc_sink_line_buf_depth(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE]) -{ - u8 line_buf_depth = dsc_dpcd[DP_DSC_LINE_BUF_BIT_DEPTH - DP_DSC_SUPPORT]; - - switch (line_buf_depth & DP_DSC_LINE_BUF_BIT_DEPTH_MASK) { - case DP_DSC_LINE_BUF_BIT_DEPTH_9: - return 9; - case DP_DSC_LINE_BUF_BIT_DEPTH_10: - return 10; - case DP_DSC_LINE_BUF_BIT_DEPTH_11: - return 11; - case DP_DSC_LINE_BUF_BIT_DEPTH_12: - return 12; - case DP_DSC_LINE_BUF_BIT_DEPTH_13: - return 13; - case DP_DSC_LINE_BUF_BIT_DEPTH_14: - return 14; - case DP_DSC_LINE_BUF_BIT_DEPTH_15: - return 15; - case DP_DSC_LINE_BUF_BIT_DEPTH_16: - return 16; - case DP_DSC_LINE_BUF_BIT_DEPTH_8: - return 8; - } - - return 0; -} -EXPORT_SYMBOL(drm_dp_dsc_sink_line_buf_depth); - -/** - * drm_dp_dsc_sink_supported_input_bpcs() - Get all the input bits per component - * values supported by the DSC sink. - * @dsc_dpcd: DSC capabilities from DPCD - * @dsc_bpc: An array to be filled by this helper with supported - * input bpcs. - * - * Read the DSC DPCD from the sink device to parse the supported bits per - * component values. This is used to populate the DSC parameters - * in the &struct drm_dsc_config by the driver. - * Driver creates an infoframe using these parameters to populate - * &struct drm_dsc_pps_infoframe. These are sent to the sink using DSC - * infoframe using the helper function drm_dsc_pps_infoframe_pack() - * - * Returns: - * Number of input BPC values parsed from the DPCD - */ -int drm_dp_dsc_sink_supported_input_bpcs(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE], - u8 dsc_bpc[3]) -{ - int num_bpc = 0; - u8 color_depth = dsc_dpcd[DP_DSC_DEC_COLOR_DEPTH_CAP - DP_DSC_SUPPORT]; - - if (color_depth & DP_DSC_12_BPC) - dsc_bpc[num_bpc++] = 12; - if (color_depth & DP_DSC_10_BPC) - dsc_bpc[num_bpc++] = 10; - if (color_depth & DP_DSC_8_BPC) - dsc_bpc[num_bpc++] = 8; - - return num_bpc; -} -EXPORT_SYMBOL(drm_dp_dsc_sink_supported_input_bpcs); - -/** - * drm_dp_read_lttpr_common_caps - read the LTTPR common capabilities - * @aux: DisplayPort AUX channel - * @caps: buffer to return the capability info in - * - * Read capabilities common to all LTTPRs. - * - * Returns 0 on success or a negative error code on failure. - */ -int drm_dp_read_lttpr_common_caps(struct drm_dp_aux *aux, - u8 caps[DP_LTTPR_COMMON_CAP_SIZE]) -{ - int ret; - - ret = drm_dp_dpcd_read(aux, - DP_LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV, - caps, DP_LTTPR_COMMON_CAP_SIZE); - if (ret < 0) - return ret; - - WARN_ON(ret != DP_LTTPR_COMMON_CAP_SIZE); - - return 0; -} -EXPORT_SYMBOL(drm_dp_read_lttpr_common_caps); - -/** - * drm_dp_read_lttpr_phy_caps - read the capabilities for a given LTTPR PHY - * @aux: DisplayPort AUX channel - * @dp_phy: LTTPR PHY to read the capabilities for - * @caps: buffer to return the capability info in - * - * Read the capabilities for the given LTTPR PHY. - * - * Returns 0 on success or a negative error code on failure. - */ -int drm_dp_read_lttpr_phy_caps(struct drm_dp_aux *aux, - enum drm_dp_phy dp_phy, - u8 caps[DP_LTTPR_PHY_CAP_SIZE]) -{ - int ret; - - ret = drm_dp_dpcd_read(aux, - DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER(dp_phy), - caps, DP_LTTPR_PHY_CAP_SIZE); - if (ret < 0) - return ret; - - WARN_ON(ret != DP_LTTPR_PHY_CAP_SIZE); - - return 0; -} -EXPORT_SYMBOL(drm_dp_read_lttpr_phy_caps); - -static u8 dp_lttpr_common_cap(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE], int r) -{ - return caps[r - DP_LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV]; -} - -/** - * drm_dp_lttpr_count - get the number of detected LTTPRs - * @caps: LTTPR common capabilities - * - * Get the number of detected LTTPRs from the LTTPR common capabilities info. - * - * Returns: - * -ERANGE if more than supported number (8) of LTTPRs are detected - * -EINVAL if the DP_PHY_REPEATER_CNT register contains an invalid value - * otherwise the number of detected LTTPRs - */ -int drm_dp_lttpr_count(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE]) -{ - u8 count = dp_lttpr_common_cap(caps, DP_PHY_REPEATER_CNT); - - switch (hweight8(count)) { - case 0: - return 0; - case 1: - return 8 - ilog2(count); - case 8: - return -ERANGE; - default: - return -EINVAL; - } -} -EXPORT_SYMBOL(drm_dp_lttpr_count); - -/** - * drm_dp_lttpr_max_link_rate - get the maximum link rate supported by all LTTPRs - * @caps: LTTPR common capabilities - * - * Returns the maximum link rate supported by all detected LTTPRs. - */ -int drm_dp_lttpr_max_link_rate(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE]) -{ - u8 rate = dp_lttpr_common_cap(caps, DP_MAX_LINK_RATE_PHY_REPEATER); - - return drm_dp_bw_code_to_link_rate(rate); -} -EXPORT_SYMBOL(drm_dp_lttpr_max_link_rate); - -/** - * drm_dp_lttpr_max_lane_count - get the maximum lane count supported by all LTTPRs - * @caps: LTTPR common capabilities - * - * Returns the maximum lane count supported by all detected LTTPRs. - */ -int drm_dp_lttpr_max_lane_count(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE]) -{ - u8 max_lanes = dp_lttpr_common_cap(caps, DP_MAX_LANE_COUNT_PHY_REPEATER); - - return max_lanes & DP_MAX_LANE_COUNT_MASK; -} -EXPORT_SYMBOL(drm_dp_lttpr_max_lane_count); - -/** - * drm_dp_lttpr_voltage_swing_level_3_supported - check for LTTPR vswing3 support - * @caps: LTTPR PHY capabilities - * - * Returns true if the @caps for an LTTPR TX PHY indicate support for - * voltage swing level 3. - */ -bool -drm_dp_lttpr_voltage_swing_level_3_supported(const u8 caps[DP_LTTPR_PHY_CAP_SIZE]) -{ - u8 txcap = dp_lttpr_phy_cap(caps, DP_TRANSMITTER_CAPABILITY_PHY_REPEATER1); - - return txcap & DP_VOLTAGE_SWING_LEVEL_3_SUPPORTED; -} -EXPORT_SYMBOL(drm_dp_lttpr_voltage_swing_level_3_supported); - -/** - * drm_dp_lttpr_pre_emphasis_level_3_supported - check for LTTPR preemph3 support - * @caps: LTTPR PHY capabilities - * - * Returns true if the @caps for an LTTPR TX PHY indicate support for - * pre-emphasis level 3. - */ -bool -drm_dp_lttpr_pre_emphasis_level_3_supported(const u8 caps[DP_LTTPR_PHY_CAP_SIZE]) -{ - u8 txcap = dp_lttpr_phy_cap(caps, DP_TRANSMITTER_CAPABILITY_PHY_REPEATER1); - - return txcap & DP_PRE_EMPHASIS_LEVEL_3_SUPPORTED; -} -EXPORT_SYMBOL(drm_dp_lttpr_pre_emphasis_level_3_supported); - -/** - * drm_dp_get_phy_test_pattern() - get the requested pattern from the sink. - * @aux: DisplayPort AUX channel - * @data: DP phy compliance test parameters. - * - * Returns 0 on success or a negative error code on failure. - */ -int drm_dp_get_phy_test_pattern(struct drm_dp_aux *aux, - struct drm_dp_phy_test_params *data) -{ - int err; - u8 rate, lanes; - - err = drm_dp_dpcd_readb(aux, DP_TEST_LINK_RATE, &rate); - if (err < 0) - return err; - data->link_rate = drm_dp_bw_code_to_link_rate(rate); - - err = drm_dp_dpcd_readb(aux, DP_TEST_LANE_COUNT, &lanes); - if (err < 0) - return err; - data->num_lanes = lanes & DP_MAX_LANE_COUNT_MASK; - - if (lanes & DP_ENHANCED_FRAME_CAP) - data->enhanced_frame_cap = true; - - err = drm_dp_dpcd_readb(aux, DP_PHY_TEST_PATTERN, &data->phy_pattern); - if (err < 0) - return err; - - switch (data->phy_pattern) { - case DP_PHY_TEST_PATTERN_80BIT_CUSTOM: - err = drm_dp_dpcd_read(aux, DP_TEST_80BIT_CUSTOM_PATTERN_7_0, - &data->custom80, sizeof(data->custom80)); - if (err < 0) - return err; - - break; - case DP_PHY_TEST_PATTERN_CP2520: - err = drm_dp_dpcd_read(aux, DP_TEST_HBR2_SCRAMBLER_RESET, - &data->hbr2_reset, - sizeof(data->hbr2_reset)); - if (err < 0) - return err; - } - - return 0; -} -EXPORT_SYMBOL(drm_dp_get_phy_test_pattern); - -/** - * drm_dp_set_phy_test_pattern() - set the pattern to the sink. - * @aux: DisplayPort AUX channel - * @data: DP phy compliance test parameters. - * @dp_rev: DP revision to use for compliance testing - * - * Returns 0 on success or a negative error code on failure. - */ -int drm_dp_set_phy_test_pattern(struct drm_dp_aux *aux, - struct drm_dp_phy_test_params *data, u8 dp_rev) -{ - int err, i; - u8 link_config[2]; - u8 test_pattern; - - link_config[0] = drm_dp_link_rate_to_bw_code(data->link_rate); - link_config[1] = data->num_lanes; - if (data->enhanced_frame_cap) - link_config[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; - err = drm_dp_dpcd_write(aux, DP_LINK_BW_SET, link_config, 2); - if (err < 0) - return err; - - test_pattern = data->phy_pattern; - if (dp_rev < 0x12) { - test_pattern = (test_pattern << 2) & - DP_LINK_QUAL_PATTERN_11_MASK; - err = drm_dp_dpcd_writeb(aux, DP_TRAINING_PATTERN_SET, - test_pattern); - if (err < 0) - return err; - } else { - for (i = 0; i < data->num_lanes; i++) { - err = drm_dp_dpcd_writeb(aux, - DP_LINK_QUAL_LANE0_SET + i, - test_pattern); - if (err < 0) - return err; - } - } - - return 0; -} -EXPORT_SYMBOL(drm_dp_set_phy_test_pattern); - -static const char *dp_pixelformat_get_name(enum dp_pixelformat pixelformat) -{ - if (pixelformat < 0 || pixelformat > DP_PIXELFORMAT_RESERVED) - return "Invalid"; - - switch (pixelformat) { - case DP_PIXELFORMAT_RGB: - return "RGB"; - case DP_PIXELFORMAT_YUV444: - return "YUV444"; - case DP_PIXELFORMAT_YUV422: - return "YUV422"; - case DP_PIXELFORMAT_YUV420: - return "YUV420"; - case DP_PIXELFORMAT_Y_ONLY: - return "Y_ONLY"; - case DP_PIXELFORMAT_RAW: - return "RAW"; - default: - return "Reserved"; - } -} - -static const char *dp_colorimetry_get_name(enum dp_pixelformat pixelformat, - enum dp_colorimetry colorimetry) -{ - if (pixelformat < 0 || pixelformat > DP_PIXELFORMAT_RESERVED) - return "Invalid"; - - switch (colorimetry) { - case DP_COLORIMETRY_DEFAULT: - switch (pixelformat) { - case DP_PIXELFORMAT_RGB: - return "sRGB"; - case DP_PIXELFORMAT_YUV444: - case DP_PIXELFORMAT_YUV422: - case DP_PIXELFORMAT_YUV420: - return "BT.601"; - case DP_PIXELFORMAT_Y_ONLY: - return "DICOM PS3.14"; - case DP_PIXELFORMAT_RAW: - return "Custom Color Profile"; - default: - return "Reserved"; - } - case DP_COLORIMETRY_RGB_WIDE_FIXED: /* and DP_COLORIMETRY_BT709_YCC */ - switch (pixelformat) { - case DP_PIXELFORMAT_RGB: - return "Wide Fixed"; - case DP_PIXELFORMAT_YUV444: - case DP_PIXELFORMAT_YUV422: - case DP_PIXELFORMAT_YUV420: - return "BT.709"; - default: - return "Reserved"; - } - case DP_COLORIMETRY_RGB_WIDE_FLOAT: /* and DP_COLORIMETRY_XVYCC_601 */ - switch (pixelformat) { - case DP_PIXELFORMAT_RGB: - return "Wide Float"; - case DP_PIXELFORMAT_YUV444: - case DP_PIXELFORMAT_YUV422: - case DP_PIXELFORMAT_YUV420: - return "xvYCC 601"; - default: - return "Reserved"; - } - case DP_COLORIMETRY_OPRGB: /* and DP_COLORIMETRY_XVYCC_709 */ - switch (pixelformat) { - case DP_PIXELFORMAT_RGB: - return "OpRGB"; - case DP_PIXELFORMAT_YUV444: - case DP_PIXELFORMAT_YUV422: - case DP_PIXELFORMAT_YUV420: - return "xvYCC 709"; - default: - return "Reserved"; - } - case DP_COLORIMETRY_DCI_P3_RGB: /* and DP_COLORIMETRY_SYCC_601 */ - switch (pixelformat) { - case DP_PIXELFORMAT_RGB: - return "DCI-P3"; - case DP_PIXELFORMAT_YUV444: - case DP_PIXELFORMAT_YUV422: - case DP_PIXELFORMAT_YUV420: - return "sYCC 601"; - default: - return "Reserved"; - } - case DP_COLORIMETRY_RGB_CUSTOM: /* and DP_COLORIMETRY_OPYCC_601 */ - switch (pixelformat) { - case DP_PIXELFORMAT_RGB: - return "Custom Profile"; - case DP_PIXELFORMAT_YUV444: - case DP_PIXELFORMAT_YUV422: - case DP_PIXELFORMAT_YUV420: - return "OpYCC 601"; - default: - return "Reserved"; - } - case DP_COLORIMETRY_BT2020_RGB: /* and DP_COLORIMETRY_BT2020_CYCC */ - switch (pixelformat) { - case DP_PIXELFORMAT_RGB: - return "BT.2020 RGB"; - case DP_PIXELFORMAT_YUV444: - case DP_PIXELFORMAT_YUV422: - case DP_PIXELFORMAT_YUV420: - return "BT.2020 CYCC"; - default: - return "Reserved"; - } - case DP_COLORIMETRY_BT2020_YCC: - switch (pixelformat) { - case DP_PIXELFORMAT_YUV444: - case DP_PIXELFORMAT_YUV422: - case DP_PIXELFORMAT_YUV420: - return "BT.2020 YCC"; - default: - return "Reserved"; - } - default: - return "Invalid"; - } -} - -static const char *dp_dynamic_range_get_name(enum dp_dynamic_range dynamic_range) -{ - switch (dynamic_range) { - case DP_DYNAMIC_RANGE_VESA: - return "VESA range"; - case DP_DYNAMIC_RANGE_CTA: - return "CTA range"; - default: - return "Invalid"; - } -} - -static const char *dp_content_type_get_name(enum dp_content_type content_type) -{ - switch (content_type) { - case DP_CONTENT_TYPE_NOT_DEFINED: - return "Not defined"; - case DP_CONTENT_TYPE_GRAPHICS: - return "Graphics"; - case DP_CONTENT_TYPE_PHOTO: - return "Photo"; - case DP_CONTENT_TYPE_VIDEO: - return "Video"; - case DP_CONTENT_TYPE_GAME: - return "Game"; - default: - return "Reserved"; - } -} - -void drm_dp_vsc_sdp_log(const char *level, struct device *dev, - const struct drm_dp_vsc_sdp *vsc) -{ -#define DP_SDP_LOG(fmt, ...) dev_printk(level, dev, fmt, ##__VA_ARGS__) - DP_SDP_LOG("DP SDP: %s, revision %u, length %u\n", "VSC", - vsc->revision, vsc->length); - DP_SDP_LOG(" pixelformat: %s\n", - dp_pixelformat_get_name(vsc->pixelformat)); - DP_SDP_LOG(" colorimetry: %s\n", - dp_colorimetry_get_name(vsc->pixelformat, vsc->colorimetry)); - DP_SDP_LOG(" bpc: %u\n", vsc->bpc); - DP_SDP_LOG(" dynamic range: %s\n", - dp_dynamic_range_get_name(vsc->dynamic_range)); - DP_SDP_LOG(" content type: %s\n", - dp_content_type_get_name(vsc->content_type)); -#undef DP_SDP_LOG -} -EXPORT_SYMBOL(drm_dp_vsc_sdp_log); - -/** - * drm_dp_get_pcon_max_frl_bw() - maximum frl supported by PCON - * @dpcd: DisplayPort configuration data - * @port_cap: port capabilities - * - * Returns maximum frl bandwidth supported by PCON in GBPS, - * returns 0 if not supported. - */ -int drm_dp_get_pcon_max_frl_bw(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4]) -{ - int bw; - u8 buf; - - buf = port_cap[2]; - bw = buf & DP_PCON_MAX_FRL_BW; - - switch (bw) { - case DP_PCON_MAX_9GBPS: - return 9; - case DP_PCON_MAX_18GBPS: - return 18; - case DP_PCON_MAX_24GBPS: - return 24; - case DP_PCON_MAX_32GBPS: - return 32; - case DP_PCON_MAX_40GBPS: - return 40; - case DP_PCON_MAX_48GBPS: - return 48; - case DP_PCON_MAX_0GBPS: - default: - return 0; - } - - return 0; -} -EXPORT_SYMBOL(drm_dp_get_pcon_max_frl_bw); - -/** - * drm_dp_pcon_frl_prepare() - Prepare PCON for FRL. - * @aux: DisplayPort AUX channel - * @enable_frl_ready_hpd: Configure DP_PCON_ENABLE_HPD_READY. - * - * Returns 0 if success, else returns negative error code. - */ -int drm_dp_pcon_frl_prepare(struct drm_dp_aux *aux, bool enable_frl_ready_hpd) -{ - int ret; - u8 buf = DP_PCON_ENABLE_SOURCE_CTL_MODE | - DP_PCON_ENABLE_LINK_FRL_MODE; - - if (enable_frl_ready_hpd) - buf |= DP_PCON_ENABLE_HPD_READY; - - ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_1, buf); - - return ret; -} -EXPORT_SYMBOL(drm_dp_pcon_frl_prepare); - -/** - * drm_dp_pcon_is_frl_ready() - Is PCON ready for FRL - * @aux: DisplayPort AUX channel - * - * Returns true if success, else returns false. - */ -bool drm_dp_pcon_is_frl_ready(struct drm_dp_aux *aux) -{ - int ret; - u8 buf; - - ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_TX_LINK_STATUS, &buf); - if (ret < 0) - return false; - - if (buf & DP_PCON_FRL_READY) - return true; - - return false; -} -EXPORT_SYMBOL(drm_dp_pcon_is_frl_ready); - -/** - * drm_dp_pcon_frl_configure_1() - Set HDMI LINK Configuration-Step1 - * @aux: DisplayPort AUX channel - * @max_frl_gbps: maximum frl bw to be configured between PCON and HDMI sink - * @frl_mode: FRL Training mode, it can be either Concurrent or Sequential. - * In Concurrent Mode, the FRL link bring up can be done along with - * DP Link training. In Sequential mode, the FRL link bring up is done prior to - * the DP Link training. - * - * Returns 0 if success, else returns negative error code. - */ - -int drm_dp_pcon_frl_configure_1(struct drm_dp_aux *aux, int max_frl_gbps, - u8 frl_mode) -{ - int ret; - u8 buf; - - ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_LINK_CONFIG_1, &buf); - if (ret < 0) - return ret; - - if (frl_mode == DP_PCON_ENABLE_CONCURRENT_LINK) - buf |= DP_PCON_ENABLE_CONCURRENT_LINK; - else - buf &= ~DP_PCON_ENABLE_CONCURRENT_LINK; - - switch (max_frl_gbps) { - case 9: - buf |= DP_PCON_ENABLE_MAX_BW_9GBPS; - break; - case 18: - buf |= DP_PCON_ENABLE_MAX_BW_18GBPS; - break; - case 24: - buf |= DP_PCON_ENABLE_MAX_BW_24GBPS; - break; - case 32: - buf |= DP_PCON_ENABLE_MAX_BW_32GBPS; - break; - case 40: - buf |= DP_PCON_ENABLE_MAX_BW_40GBPS; - break; - case 48: - buf |= DP_PCON_ENABLE_MAX_BW_48GBPS; - break; - case 0: - buf |= DP_PCON_ENABLE_MAX_BW_0GBPS; - break; - default: - return -EINVAL; - } - - ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_1, buf); - if (ret < 0) - return ret; - - return 0; -} -EXPORT_SYMBOL(drm_dp_pcon_frl_configure_1); - -/** - * drm_dp_pcon_frl_configure_2() - Set HDMI Link configuration Step-2 - * @aux: DisplayPort AUX channel - * @max_frl_mask : Max FRL BW to be tried by the PCON with HDMI Sink - * @frl_type : FRL training type, can be Extended, or Normal. - * In Normal FRL training, the PCON tries each frl bw from the max_frl_mask - * starting from min, and stops when link training is successful. In Extended - * FRL training, all frl bw selected in the mask are trained by the PCON. - * - * Returns 0 if success, else returns negative error code. - */ -int drm_dp_pcon_frl_configure_2(struct drm_dp_aux *aux, int max_frl_mask, - u8 frl_type) -{ - int ret; - u8 buf = max_frl_mask; - - if (frl_type == DP_PCON_FRL_LINK_TRAIN_EXTENDED) - buf |= DP_PCON_FRL_LINK_TRAIN_EXTENDED; - else - buf &= ~DP_PCON_FRL_LINK_TRAIN_EXTENDED; - - ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_2, buf); - if (ret < 0) - return ret; - - return 0; -} -EXPORT_SYMBOL(drm_dp_pcon_frl_configure_2); - -/** - * drm_dp_pcon_reset_frl_config() - Re-Set HDMI Link configuration. - * @aux: DisplayPort AUX channel - * - * Returns 0 if success, else returns negative error code. - */ -int drm_dp_pcon_reset_frl_config(struct drm_dp_aux *aux) -{ - int ret; - - ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_1, 0x0); - if (ret < 0) - return ret; - - return 0; -} -EXPORT_SYMBOL(drm_dp_pcon_reset_frl_config); - -/** - * drm_dp_pcon_frl_enable() - Enable HDMI link through FRL - * @aux: DisplayPort AUX channel - * - * Returns 0 if success, else returns negative error code. - */ -int drm_dp_pcon_frl_enable(struct drm_dp_aux *aux) -{ - int ret; - u8 buf = 0; - - ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_LINK_CONFIG_1, &buf); - if (ret < 0) - return ret; - if (!(buf & DP_PCON_ENABLE_SOURCE_CTL_MODE)) { - drm_dbg_kms(aux->drm_dev, "%s: PCON in Autonomous mode, can't enable FRL\n", - aux->name); - return -EINVAL; - } - buf |= DP_PCON_ENABLE_HDMI_LINK; - ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_1, buf); - if (ret < 0) - return ret; - - return 0; -} -EXPORT_SYMBOL(drm_dp_pcon_frl_enable); - -/** - * drm_dp_pcon_hdmi_link_active() - check if the PCON HDMI LINK status is active. - * @aux: DisplayPort AUX channel - * - * Returns true if link is active else returns false. - */ -bool drm_dp_pcon_hdmi_link_active(struct drm_dp_aux *aux) -{ - u8 buf; - int ret; - - ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_TX_LINK_STATUS, &buf); - if (ret < 0) - return false; - - return buf & DP_PCON_HDMI_TX_LINK_ACTIVE; -} -EXPORT_SYMBOL(drm_dp_pcon_hdmi_link_active); - -/** - * drm_dp_pcon_hdmi_link_mode() - get the PCON HDMI LINK MODE - * @aux: DisplayPort AUX channel - * @frl_trained_mask: pointer to store bitmask of the trained bw configuration. - * Valid only if the MODE returned is FRL. For Normal Link training mode - * only 1 of the bits will be set, but in case of Extended mode, more than - * one bits can be set. - * - * Returns the link mode : TMDS or FRL on success, else returns negative error - * code. - */ -int drm_dp_pcon_hdmi_link_mode(struct drm_dp_aux *aux, u8 *frl_trained_mask) -{ - u8 buf; - int mode; - int ret; - - ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_POST_FRL_STATUS, &buf); - if (ret < 0) - return ret; - - mode = buf & DP_PCON_HDMI_LINK_MODE; - - if (frl_trained_mask && DP_PCON_HDMI_MODE_FRL == mode) - *frl_trained_mask = (buf & DP_PCON_HDMI_FRL_TRAINED_BW) >> 1; - - return mode; -} -EXPORT_SYMBOL(drm_dp_pcon_hdmi_link_mode); - -/** - * drm_dp_pcon_hdmi_frl_link_error_count() - print the error count per lane - * during link failure between PCON and HDMI sink - * @aux: DisplayPort AUX channel - * @connector: DRM connector - * code. - **/ - -void drm_dp_pcon_hdmi_frl_link_error_count(struct drm_dp_aux *aux, - struct drm_connector *connector) -{ - u8 buf, error_count; - int i, num_error; - struct drm_hdmi_info *hdmi = &connector->display_info.hdmi; - - for (i = 0; i < hdmi->max_lanes; i++) { - if (drm_dp_dpcd_readb(aux, DP_PCON_HDMI_ERROR_STATUS_LN0 + i, &buf) < 0) - return; - - error_count = buf & DP_PCON_HDMI_ERROR_COUNT_MASK; - switch (error_count) { - case DP_PCON_HDMI_ERROR_COUNT_HUNDRED_PLUS: - num_error = 100; - break; - case DP_PCON_HDMI_ERROR_COUNT_TEN_PLUS: - num_error = 10; - break; - case DP_PCON_HDMI_ERROR_COUNT_THREE_PLUS: - num_error = 3; - break; - default: - num_error = 0; - } - - drm_err(aux->drm_dev, "%s: More than %d errors since the last read for lane %d", - aux->name, num_error, i); - } -} -EXPORT_SYMBOL(drm_dp_pcon_hdmi_frl_link_error_count); - -/* - * drm_dp_pcon_enc_is_dsc_1_2 - Does PCON Encoder supports DSC 1.2 - * @pcon_dsc_dpcd: DSC capabilities of the PCON DSC Encoder - * - * Returns true is PCON encoder is DSC 1.2 else returns false. - */ -bool drm_dp_pcon_enc_is_dsc_1_2(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]) -{ - u8 buf; - u8 major_v, minor_v; - - buf = pcon_dsc_dpcd[DP_PCON_DSC_VERSION - DP_PCON_DSC_ENCODER]; - major_v = (buf & DP_PCON_DSC_MAJOR_MASK) >> DP_PCON_DSC_MAJOR_SHIFT; - minor_v = (buf & DP_PCON_DSC_MINOR_MASK) >> DP_PCON_DSC_MINOR_SHIFT; - - if (major_v == 1 && minor_v == 2) - return true; - - return false; -} -EXPORT_SYMBOL(drm_dp_pcon_enc_is_dsc_1_2); - -/* - * drm_dp_pcon_dsc_max_slices - Get max slices supported by PCON DSC Encoder - * @pcon_dsc_dpcd: DSC capabilities of the PCON DSC Encoder - * - * Returns maximum no. of slices supported by the PCON DSC Encoder. - */ -int drm_dp_pcon_dsc_max_slices(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]) -{ - u8 slice_cap1, slice_cap2; - - slice_cap1 = pcon_dsc_dpcd[DP_PCON_DSC_SLICE_CAP_1 - DP_PCON_DSC_ENCODER]; - slice_cap2 = pcon_dsc_dpcd[DP_PCON_DSC_SLICE_CAP_2 - DP_PCON_DSC_ENCODER]; - - if (slice_cap2 & DP_PCON_DSC_24_PER_DSC_ENC) - return 24; - if (slice_cap2 & DP_PCON_DSC_20_PER_DSC_ENC) - return 20; - if (slice_cap2 & DP_PCON_DSC_16_PER_DSC_ENC) - return 16; - if (slice_cap1 & DP_PCON_DSC_12_PER_DSC_ENC) - return 12; - if (slice_cap1 & DP_PCON_DSC_10_PER_DSC_ENC) - return 10; - if (slice_cap1 & DP_PCON_DSC_8_PER_DSC_ENC) - return 8; - if (slice_cap1 & DP_PCON_DSC_6_PER_DSC_ENC) - return 6; - if (slice_cap1 & DP_PCON_DSC_4_PER_DSC_ENC) - return 4; - if (slice_cap1 & DP_PCON_DSC_2_PER_DSC_ENC) - return 2; - if (slice_cap1 & DP_PCON_DSC_1_PER_DSC_ENC) - return 1; - - return 0; -} -EXPORT_SYMBOL(drm_dp_pcon_dsc_max_slices); - -/* - * drm_dp_pcon_dsc_max_slice_width() - Get max slice width for Pcon DSC encoder - * @pcon_dsc_dpcd: DSC capabilities of the PCON DSC Encoder - * - * Returns maximum width of the slices in pixel width i.e. no. of pixels x 320. - */ -int drm_dp_pcon_dsc_max_slice_width(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]) -{ - u8 buf; - - buf = pcon_dsc_dpcd[DP_PCON_DSC_MAX_SLICE_WIDTH - DP_PCON_DSC_ENCODER]; - - return buf * DP_DSC_SLICE_WIDTH_MULTIPLIER; -} -EXPORT_SYMBOL(drm_dp_pcon_dsc_max_slice_width); - -/* - * drm_dp_pcon_dsc_bpp_incr() - Get bits per pixel increment for PCON DSC encoder - * @pcon_dsc_dpcd: DSC capabilities of the PCON DSC Encoder - * - * Returns the bpp precision supported by the PCON encoder. - */ -int drm_dp_pcon_dsc_bpp_incr(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]) -{ - u8 buf; - - buf = pcon_dsc_dpcd[DP_PCON_DSC_BPP_INCR - DP_PCON_DSC_ENCODER]; - - switch (buf & DP_PCON_DSC_BPP_INCR_MASK) { - case DP_PCON_DSC_ONE_16TH_BPP: - return 16; - case DP_PCON_DSC_ONE_8TH_BPP: - return 8; - case DP_PCON_DSC_ONE_4TH_BPP: - return 4; - case DP_PCON_DSC_ONE_HALF_BPP: - return 2; - case DP_PCON_DSC_ONE_BPP: - return 1; - } - - return 0; -} -EXPORT_SYMBOL(drm_dp_pcon_dsc_bpp_incr); - -static -int drm_dp_pcon_configure_dsc_enc(struct drm_dp_aux *aux, u8 pps_buf_config) -{ - u8 buf; - int ret; - - ret = drm_dp_dpcd_readb(aux, DP_PROTOCOL_CONVERTER_CONTROL_2, &buf); - if (ret < 0) - return ret; - - buf |= DP_PCON_ENABLE_DSC_ENCODER; - - if (pps_buf_config <= DP_PCON_ENC_PPS_OVERRIDE_EN_BUFFER) { - buf &= ~DP_PCON_ENCODER_PPS_OVERRIDE_MASK; - buf |= pps_buf_config << 2; - } - - ret = drm_dp_dpcd_writeb(aux, DP_PROTOCOL_CONVERTER_CONTROL_2, buf); - if (ret < 0) - return ret; - - return 0; -} - -/** - * drm_dp_pcon_pps_default() - Let PCON fill the default pps parameters - * for DSC1.2 between PCON & HDMI2.1 sink - * @aux: DisplayPort AUX channel - * - * Returns 0 on success, else returns negative error code. - */ -int drm_dp_pcon_pps_default(struct drm_dp_aux *aux) -{ - int ret; - - ret = drm_dp_pcon_configure_dsc_enc(aux, DP_PCON_ENC_PPS_OVERRIDE_DISABLED); - if (ret < 0) - return ret; - - return 0; -} -EXPORT_SYMBOL(drm_dp_pcon_pps_default); - -/** - * drm_dp_pcon_pps_override_buf() - Configure PPS encoder override buffer for - * HDMI sink - * @aux: DisplayPort AUX channel - * @pps_buf: 128 bytes to be written into PPS buffer for HDMI sink by PCON. - * - * Returns 0 on success, else returns negative error code. - */ -int drm_dp_pcon_pps_override_buf(struct drm_dp_aux *aux, u8 pps_buf[128]) -{ - int ret; - - ret = drm_dp_dpcd_write(aux, DP_PCON_HDMI_PPS_OVERRIDE_BASE, &pps_buf, 128); - if (ret < 0) - return ret; - - ret = drm_dp_pcon_configure_dsc_enc(aux, DP_PCON_ENC_PPS_OVERRIDE_EN_BUFFER); - if (ret < 0) - return ret; - - return 0; -} -EXPORT_SYMBOL(drm_dp_pcon_pps_override_buf); - -/* - * drm_dp_pcon_pps_override_param() - Write PPS parameters to DSC encoder - * override registers - * @aux: DisplayPort AUX channel - * @pps_param: 3 Parameters (2 Bytes each) : Slice Width, Slice Height, - * bits_per_pixel. - * - * Returns 0 on success, else returns negative error code. - */ -int drm_dp_pcon_pps_override_param(struct drm_dp_aux *aux, u8 pps_param[6]) -{ - int ret; - - ret = drm_dp_dpcd_write(aux, DP_PCON_HDMI_PPS_OVRD_SLICE_HEIGHT, &pps_param[0], 2); - if (ret < 0) - return ret; - ret = drm_dp_dpcd_write(aux, DP_PCON_HDMI_PPS_OVRD_SLICE_WIDTH, &pps_param[2], 2); - if (ret < 0) - return ret; - ret = drm_dp_dpcd_write(aux, DP_PCON_HDMI_PPS_OVRD_BPP, &pps_param[4], 2); - if (ret < 0) - return ret; - - ret = drm_dp_pcon_configure_dsc_enc(aux, DP_PCON_ENC_PPS_OVERRIDE_EN_BUFFER); - if (ret < 0) - return ret; - - return 0; -} -EXPORT_SYMBOL(drm_dp_pcon_pps_override_param); - -/* - * drm_dp_pcon_convert_rgb_to_ycbcr() - Configure the PCon to convert RGB to Ycbcr - * @aux: displayPort AUX channel - * @color_spc: Color-space/s for which conversion is to be enabled, 0 for disable. - * - * Returns 0 on success, else returns negative error code. - */ -int drm_dp_pcon_convert_rgb_to_ycbcr(struct drm_dp_aux *aux, u8 color_spc) -{ - int ret; - u8 buf; - - ret = drm_dp_dpcd_readb(aux, DP_PROTOCOL_CONVERTER_CONTROL_2, &buf); - if (ret < 0) - return ret; - - if (color_spc & DP_CONVERSION_RGB_YCBCR_MASK) - buf |= (color_spc & DP_CONVERSION_RGB_YCBCR_MASK); - else - buf &= ~DP_CONVERSION_RGB_YCBCR_MASK; - - ret = drm_dp_dpcd_writeb(aux, DP_PROTOCOL_CONVERTER_CONTROL_2, buf); - if (ret < 0) - return ret; - - return 0; -} -EXPORT_SYMBOL(drm_dp_pcon_convert_rgb_to_ycbcr); - -/** - * drm_edp_backlight_set_level() - Set the backlight level of an eDP panel via AUX - * @aux: The DP AUX channel to use - * @bl: Backlight capability info from drm_edp_backlight_init() - * @level: The brightness level to set - * - * Sets the brightness level of an eDP panel's backlight. Note that the panel's backlight must - * already have been enabled by the driver by calling drm_edp_backlight_enable(). - * - * Returns: %0 on success, negative error code on failure - */ -int drm_edp_backlight_set_level(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl, - u16 level) -{ - int ret; - u8 buf[2] = { 0 }; - - /* The panel uses the PWM for controlling brightness levels */ - if (!bl->aux_set) - return 0; - - if (bl->lsb_reg_used) { - buf[0] = (level & 0xff00) >> 8; - buf[1] = (level & 0x00ff); - } else { - buf[0] = level; - } - - ret = drm_dp_dpcd_write(aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB, buf, sizeof(buf)); - if (ret != sizeof(buf)) { - drm_err(aux->drm_dev, - "%s: Failed to write aux backlight level: %d\n", - aux->name, ret); - return ret < 0 ? ret : -EIO; - } - - return 0; -} -EXPORT_SYMBOL(drm_edp_backlight_set_level); - -static int -drm_edp_backlight_set_enable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl, - bool enable) -{ - int ret; - u8 buf; - - /* This panel uses the EDP_BL_PWR GPIO for enablement */ - if (!bl->aux_enable) - return 0; - - ret = drm_dp_dpcd_readb(aux, DP_EDP_DISPLAY_CONTROL_REGISTER, &buf); - if (ret != 1) { - drm_err(aux->drm_dev, "%s: Failed to read eDP display control register: %d\n", - aux->name, ret); - return ret < 0 ? ret : -EIO; - } - if (enable) - buf |= DP_EDP_BACKLIGHT_ENABLE; - else - buf &= ~DP_EDP_BACKLIGHT_ENABLE; - - ret = drm_dp_dpcd_writeb(aux, DP_EDP_DISPLAY_CONTROL_REGISTER, buf); - if (ret != 1) { - drm_err(aux->drm_dev, "%s: Failed to write eDP display control register: %d\n", - aux->name, ret); - return ret < 0 ? ret : -EIO; - } - - return 0; -} - -/** - * drm_edp_backlight_enable() - Enable an eDP panel's backlight using DPCD - * @aux: The DP AUX channel to use - * @bl: Backlight capability info from drm_edp_backlight_init() - * @level: The initial backlight level to set via AUX, if there is one - * - * This function handles enabling DPCD backlight controls on a panel over DPCD, while additionally - * restoring any important backlight state such as the given backlight level, the brightness byte - * count, backlight frequency, etc. - * - * Note that certain panels do not support being enabled or disabled via DPCD, but instead require - * that the driver handle enabling/disabling the panel through implementation-specific means using - * the EDP_BL_PWR GPIO. For such panels, &drm_edp_backlight_info.aux_enable will be set to %false, - * this function becomes a no-op, and the driver is expected to handle powering the panel on using - * the EDP_BL_PWR GPIO. - * - * Returns: %0 on success, negative error code on failure. - */ -int drm_edp_backlight_enable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl, - const u16 level) -{ - int ret; - u8 dpcd_buf; - - if (bl->aux_set) - dpcd_buf = DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD; - else - dpcd_buf = DP_EDP_BACKLIGHT_CONTROL_MODE_PWM; - - if (bl->pwmgen_bit_count) { - ret = drm_dp_dpcd_writeb(aux, DP_EDP_PWMGEN_BIT_COUNT, bl->pwmgen_bit_count); - if (ret != 1) - drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux pwmgen bit count: %d\n", - aux->name, ret); - } - - if (bl->pwm_freq_pre_divider) { - ret = drm_dp_dpcd_writeb(aux, DP_EDP_BACKLIGHT_FREQ_SET, bl->pwm_freq_pre_divider); - if (ret != 1) - drm_dbg_kms(aux->drm_dev, - "%s: Failed to write aux backlight frequency: %d\n", - aux->name, ret); - else - dpcd_buf |= DP_EDP_BACKLIGHT_FREQ_AUX_SET_ENABLE; - } - - ret = drm_dp_dpcd_writeb(aux, DP_EDP_BACKLIGHT_MODE_SET_REGISTER, dpcd_buf); - if (ret != 1) { - drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux backlight mode: %d\n", - aux->name, ret); - return ret < 0 ? ret : -EIO; - } - - ret = drm_edp_backlight_set_level(aux, bl, level); - if (ret < 0) - return ret; - ret = drm_edp_backlight_set_enable(aux, bl, true); - if (ret < 0) - return ret; - - return 0; -} -EXPORT_SYMBOL(drm_edp_backlight_enable); - -/** - * drm_edp_backlight_disable() - Disable an eDP backlight using DPCD, if supported - * @aux: The DP AUX channel to use - * @bl: Backlight capability info from drm_edp_backlight_init() - * - * This function handles disabling DPCD backlight controls on a panel over AUX. - * - * Note that certain panels do not support being enabled or disabled via DPCD, but instead require - * that the driver handle enabling/disabling the panel through implementation-specific means using - * the EDP_BL_PWR GPIO. For such panels, &drm_edp_backlight_info.aux_enable will be set to %false, - * this function becomes a no-op, and the driver is expected to handle powering the panel off using - * the EDP_BL_PWR GPIO. - * - * Returns: %0 on success or no-op, negative error code on failure. - */ -int drm_edp_backlight_disable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl) -{ - int ret; - - ret = drm_edp_backlight_set_enable(aux, bl, false); - if (ret < 0) - return ret; - - return 0; -} -EXPORT_SYMBOL(drm_edp_backlight_disable); - -static inline int -drm_edp_backlight_probe_max(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl, - u16 driver_pwm_freq_hz, const u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE]) -{ - int fxp, fxp_min, fxp_max, fxp_actual, f = 1; - int ret; - u8 pn, pn_min, pn_max; - - if (!bl->aux_set) - return 0; - - ret = drm_dp_dpcd_readb(aux, DP_EDP_PWMGEN_BIT_COUNT, &pn); - if (ret != 1) { - drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap: %d\n", - aux->name, ret); - return -ENODEV; - } - - pn &= DP_EDP_PWMGEN_BIT_COUNT_MASK; - bl->max = (1 << pn) - 1; - if (!driver_pwm_freq_hz) - return 0; - - /* - * Set PWM Frequency divider to match desired frequency provided by the driver. - * The PWM Frequency is calculated as 27Mhz / (F x P). - * - Where F = PWM Frequency Pre-Divider value programmed by field 7:0 of the - * EDP_BACKLIGHT_FREQ_SET register (DPCD Address 00728h) - * - Where P = 2^Pn, where Pn is the value programmed by field 4:0 of the - * EDP_PWMGEN_BIT_COUNT register (DPCD Address 00724h) - */ - - /* Find desired value of (F x P) - * Note that, if F x P is out of supported range, the maximum value or minimum value will - * applied automatically. So no need to check that. - */ - fxp = DIV_ROUND_CLOSEST(1000 * DP_EDP_BACKLIGHT_FREQ_BASE_KHZ, driver_pwm_freq_hz); - - /* Use highest possible value of Pn for more granularity of brightness adjustment while - * satisfying the conditions below. - * - Pn is in the range of Pn_min and Pn_max - * - F is in the range of 1 and 255 - * - FxP is within 25% of desired value. - * Note: 25% is arbitrary value and may need some tweak. - */ - ret = drm_dp_dpcd_readb(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, &pn_min); - if (ret != 1) { - drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap min: %d\n", - aux->name, ret); - return 0; - } - ret = drm_dp_dpcd_readb(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MAX, &pn_max); - if (ret != 1) { - drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap max: %d\n", - aux->name, ret); - return 0; - } - pn_min &= DP_EDP_PWMGEN_BIT_COUNT_MASK; - pn_max &= DP_EDP_PWMGEN_BIT_COUNT_MASK; - - /* Ensure frequency is within 25% of desired value */ - fxp_min = DIV_ROUND_CLOSEST(fxp * 3, 4); - fxp_max = DIV_ROUND_CLOSEST(fxp * 5, 4); - if (fxp_min < (1 << pn_min) || (255 << pn_max) < fxp_max) { - drm_dbg_kms(aux->drm_dev, - "%s: Driver defined backlight frequency (%d) out of range\n", - aux->name, driver_pwm_freq_hz); - return 0; - } - - for (pn = pn_max; pn >= pn_min; pn--) { - f = clamp(DIV_ROUND_CLOSEST(fxp, 1 << pn), 1, 255); - fxp_actual = f << pn; - if (fxp_min <= fxp_actual && fxp_actual <= fxp_max) - break; - } - - ret = drm_dp_dpcd_writeb(aux, DP_EDP_PWMGEN_BIT_COUNT, pn); - if (ret != 1) { - drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux pwmgen bit count: %d\n", - aux->name, ret); - return 0; - } - bl->pwmgen_bit_count = pn; - bl->max = (1 << pn) - 1; - - if (edp_dpcd[2] & DP_EDP_BACKLIGHT_FREQ_AUX_SET_CAP) { - bl->pwm_freq_pre_divider = f; - drm_dbg_kms(aux->drm_dev, "%s: Using backlight frequency from driver (%dHz)\n", - aux->name, driver_pwm_freq_hz); - } - - return 0; -} - -static inline int -drm_edp_backlight_probe_state(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl, - u8 *current_mode) -{ - int ret; - u8 buf[2]; - u8 mode_reg; - - ret = drm_dp_dpcd_readb(aux, DP_EDP_BACKLIGHT_MODE_SET_REGISTER, &mode_reg); - if (ret != 1) { - drm_dbg_kms(aux->drm_dev, "%s: Failed to read backlight mode: %d\n", - aux->name, ret); - return ret < 0 ? ret : -EIO; - } - - *current_mode = (mode_reg & DP_EDP_BACKLIGHT_CONTROL_MODE_MASK); - if (!bl->aux_set) - return 0; - - if (*current_mode == DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD) { - int size = 1 + bl->lsb_reg_used; - - ret = drm_dp_dpcd_read(aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB, buf, size); - if (ret != size) { - drm_dbg_kms(aux->drm_dev, "%s: Failed to read backlight level: %d\n", - aux->name, ret); - return ret < 0 ? ret : -EIO; - } - - if (bl->lsb_reg_used) - return (buf[0] << 8) | buf[1]; - else - return buf[0]; - } - - /* - * If we're not in DPCD control mode yet, the programmed brightness value is meaningless and - * the driver should assume max brightness - */ - return bl->max; -} - -/** - * drm_edp_backlight_init() - Probe a display panel's TCON using the standard VESA eDP backlight - * interface. - * @aux: The DP aux device to use for probing - * @bl: The &drm_edp_backlight_info struct to fill out with information on the backlight - * @driver_pwm_freq_hz: Optional PWM frequency from the driver in hz - * @edp_dpcd: A cached copy of the eDP DPCD - * @current_level: Where to store the probed brightness level, if any - * @current_mode: Where to store the currently set backlight control mode - * - * Initializes a &drm_edp_backlight_info struct by probing @aux for it's backlight capabilities, - * along with also probing the current and maximum supported brightness levels. - * - * If @driver_pwm_freq_hz is non-zero, this will be used as the backlight frequency. Otherwise, the - * default frequency from the panel is used. - * - * Returns: %0 on success, negative error code on failure. - */ -int -drm_edp_backlight_init(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl, - u16 driver_pwm_freq_hz, const u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE], - u16 *current_level, u8 *current_mode) -{ - int ret; - - if (edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP) - bl->aux_enable = true; - if (edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP) - bl->aux_set = true; - if (edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT) - bl->lsb_reg_used = true; - - /* Sanity check caps */ - if (!bl->aux_set && !(edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_PWM_PIN_CAP)) { - drm_dbg_kms(aux->drm_dev, - "%s: Panel supports neither AUX or PWM brightness control? Aborting\n", - aux->name); - return -EINVAL; - } - - ret = drm_edp_backlight_probe_max(aux, bl, driver_pwm_freq_hz, edp_dpcd); - if (ret < 0) - return ret; - - ret = drm_edp_backlight_probe_state(aux, bl, current_mode); - if (ret < 0) - return ret; - *current_level = ret; - - drm_dbg_kms(aux->drm_dev, - "%s: Found backlight: aux_set=%d aux_enable=%d mode=%d\n", - aux->name, bl->aux_set, bl->aux_enable, *current_mode); - if (bl->aux_set) { - drm_dbg_kms(aux->drm_dev, - "%s: Backlight caps: level=%d/%d pwm_freq_pre_divider=%d lsb_reg_used=%d\n", - aux->name, *current_level, bl->max, bl->pwm_freq_pre_divider, - bl->lsb_reg_used); - } - - return 0; -} -EXPORT_SYMBOL(drm_edp_backlight_init); - -#if IS_BUILTIN(CONFIG_BACKLIGHT_CLASS_DEVICE) || \ - (IS_MODULE(CONFIG_DRM_KMS_HELPER) && IS_MODULE(CONFIG_BACKLIGHT_CLASS_DEVICE)) - -static int dp_aux_backlight_update_status(struct backlight_device *bd) -{ - struct dp_aux_backlight *bl = bl_get_data(bd); - u16 brightness = backlight_get_brightness(bd); - int ret = 0; - - if (!backlight_is_blank(bd)) { - if (!bl->enabled) { - drm_edp_backlight_enable(bl->aux, &bl->info, brightness); - bl->enabled = true; - return 0; - } - ret = drm_edp_backlight_set_level(bl->aux, &bl->info, brightness); - } else { - if (bl->enabled) { - drm_edp_backlight_disable(bl->aux, &bl->info); - bl->enabled = false; - } - } - - return ret; -} - -static const struct backlight_ops dp_aux_bl_ops = { - .update_status = dp_aux_backlight_update_status, -}; - -/** - * drm_panel_dp_aux_backlight - create and use DP AUX backlight - * @panel: DRM panel - * @aux: The DP AUX channel to use - * - * Use this function to create and handle backlight if your panel - * supports backlight control over DP AUX channel using DPCD - * registers as per VESA's standard backlight control interface. - * - * When the panel is enabled backlight will be enabled after a - * successful call to &drm_panel_funcs.enable() - * - * When the panel is disabled backlight will be disabled before the - * call to &drm_panel_funcs.disable(). - * - * A typical implementation for a panel driver supporting backlight - * control over DP AUX will call this function at probe time. - * Backlight will then be handled transparently without requiring - * any intervention from the driver. - * - * drm_panel_dp_aux_backlight() must be called after the call to drm_panel_init(). - * - * Return: 0 on success or a negative error code on failure. - */ -int drm_panel_dp_aux_backlight(struct drm_panel *panel, struct drm_dp_aux *aux) -{ - struct dp_aux_backlight *bl; - struct backlight_properties props = { 0 }; - u16 current_level; - u8 current_mode; - u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE]; - int ret; - - if (!panel || !panel->dev || !aux) - return -EINVAL; - - ret = drm_dp_dpcd_read(aux, DP_EDP_DPCD_REV, edp_dpcd, - EDP_DISPLAY_CTL_CAP_SIZE); - if (ret < 0) - return ret; - - if (!drm_edp_backlight_supported(edp_dpcd)) { - DRM_DEV_INFO(panel->dev, "DP AUX backlight is not supported\n"); - return 0; - } - - bl = devm_kzalloc(panel->dev, sizeof(*bl), GFP_KERNEL); - if (!bl) - return -ENOMEM; - - bl->aux = aux; - - ret = drm_edp_backlight_init(aux, &bl->info, 0, edp_dpcd, - ¤t_level, ¤t_mode); - if (ret < 0) - return ret; - - props.type = BACKLIGHT_RAW; - props.brightness = current_level; - props.max_brightness = bl->info.max; - - bl->base = devm_backlight_device_register(panel->dev, "dp_aux_backlight", - panel->dev, bl, - &dp_aux_bl_ops, &props); - if (IS_ERR(bl->base)) - return PTR_ERR(bl->base); - - backlight_disable(bl->base); - - panel->backlight = bl->base; - - return 0; -} -EXPORT_SYMBOL(drm_panel_dp_aux_backlight); - -#endif diff --git a/drivers/gpu/drm/drm_dp_helper_internal.h b/drivers/gpu/drm/drm_dp_helper_internal.h deleted file mode 100644 index 8917fc3af9ec..000000000000 --- a/drivers/gpu/drm/drm_dp_helper_internal.h +++ /dev/null @@ -1,33 +0,0 @@ -/* SPDX-License-Identifier: MIT */ - -#ifndef DRM_DP_HELPER_INTERNAL_H -#define DRM_DP_HELPER_INTERNAL_H - -struct drm_dp_aux; - -#ifdef CONFIG_DRM_DP_AUX_CHARDEV -int drm_dp_aux_dev_init(void); -void drm_dp_aux_dev_exit(void); -int drm_dp_aux_register_devnode(struct drm_dp_aux *aux); -void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux); -#else -static inline int drm_dp_aux_dev_init(void) -{ - return 0; -} - -static inline void drm_dp_aux_dev_exit(void) -{ -} - -static inline int drm_dp_aux_register_devnode(struct drm_dp_aux *aux) -{ - return 0; -} - -static inline void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux) -{ -} -#endif - -#endif diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c b/drivers/gpu/drm/drm_dp_mst_topology.c deleted file mode 100644 index bc3237a09c06..000000000000 --- a/drivers/gpu/drm/drm_dp_mst_topology.c +++ /dev/null @@ -1,5977 +0,0 @@ -/* - * Copyright © 2014 Red Hat - * - * Permission to use, copy, modify, distribute, and sell this software and its - * documentation for any purpose is hereby granted without fee, provided that - * the above copyright notice appear in all copies and that both that copyright - * notice and this permission notice appear in supporting documentation, and - * that the name of the copyright holders not be used in advertising or - * publicity pertaining to distribution of the software without specific, - * written prior permission. The copyright holders make no representations - * about the suitability of this software for any purpose. It is provided "as - * is" without express or implied warranty. - * - * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, - * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO - * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR - * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, - * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER - * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE - * OF THIS SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS) -#include -#include -#include -#include -#endif - -#include -#include -#include -#include -#include -#include - -#include "drm_dp_helper_internal.h" -#include "drm_dp_mst_topology_internal.h" - -/** - * DOC: dp mst helper - * - * These functions contain parts of the DisplayPort 1.2a MultiStream Transport - * protocol. The helpers contain a topology manager and bandwidth manager. - * The helpers encapsulate the sending and received of sideband msgs. - */ -struct drm_dp_pending_up_req { - struct drm_dp_sideband_msg_hdr hdr; - struct drm_dp_sideband_msg_req_body msg; - struct list_head next; -}; - -static bool dump_dp_payload_table(struct drm_dp_mst_topology_mgr *mgr, - char *buf); - -static void drm_dp_mst_topology_put_port(struct drm_dp_mst_port *port); - -static int drm_dp_dpcd_write_payload(struct drm_dp_mst_topology_mgr *mgr, - int id, - struct drm_dp_payload *payload); - -static int drm_dp_send_dpcd_read(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, - int offset, int size, u8 *bytes); -static int drm_dp_send_dpcd_write(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, - int offset, int size, u8 *bytes); - -static int drm_dp_send_link_address(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb); - -static void -drm_dp_send_clear_payload_id_table(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb); - -static int drm_dp_send_enum_path_resources(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb, - struct drm_dp_mst_port *port); -static bool drm_dp_validate_guid(struct drm_dp_mst_topology_mgr *mgr, - u8 *guid); - -static int drm_dp_mst_register_i2c_bus(struct drm_dp_mst_port *port); -static void drm_dp_mst_unregister_i2c_bus(struct drm_dp_mst_port *port); -static void drm_dp_mst_kick_tx(struct drm_dp_mst_topology_mgr *mgr); - -static bool drm_dp_mst_port_downstream_of_branch(struct drm_dp_mst_port *port, - struct drm_dp_mst_branch *branch); - -#define DBG_PREFIX "[dp_mst]" - -#define DP_STR(x) [DP_ ## x] = #x - -static const char *drm_dp_mst_req_type_str(u8 req_type) -{ - static const char * const req_type_str[] = { - DP_STR(GET_MSG_TRANSACTION_VERSION), - DP_STR(LINK_ADDRESS), - DP_STR(CONNECTION_STATUS_NOTIFY), - DP_STR(ENUM_PATH_RESOURCES), - DP_STR(ALLOCATE_PAYLOAD), - DP_STR(QUERY_PAYLOAD), - DP_STR(RESOURCE_STATUS_NOTIFY), - DP_STR(CLEAR_PAYLOAD_ID_TABLE), - DP_STR(REMOTE_DPCD_READ), - DP_STR(REMOTE_DPCD_WRITE), - DP_STR(REMOTE_I2C_READ), - DP_STR(REMOTE_I2C_WRITE), - DP_STR(POWER_UP_PHY), - DP_STR(POWER_DOWN_PHY), - DP_STR(SINK_EVENT_NOTIFY), - DP_STR(QUERY_STREAM_ENC_STATUS), - }; - - if (req_type >= ARRAY_SIZE(req_type_str) || - !req_type_str[req_type]) - return "unknown"; - - return req_type_str[req_type]; -} - -#undef DP_STR -#define DP_STR(x) [DP_NAK_ ## x] = #x - -static const char *drm_dp_mst_nak_reason_str(u8 nak_reason) -{ - static const char * const nak_reason_str[] = { - DP_STR(WRITE_FAILURE), - DP_STR(INVALID_READ), - DP_STR(CRC_FAILURE), - DP_STR(BAD_PARAM), - DP_STR(DEFER), - DP_STR(LINK_FAILURE), - DP_STR(NO_RESOURCES), - DP_STR(DPCD_FAIL), - DP_STR(I2C_NAK), - DP_STR(ALLOCATE_FAIL), - }; - - if (nak_reason >= ARRAY_SIZE(nak_reason_str) || - !nak_reason_str[nak_reason]) - return "unknown"; - - return nak_reason_str[nak_reason]; -} - -#undef DP_STR -#define DP_STR(x) [DRM_DP_SIDEBAND_TX_ ## x] = #x - -static const char *drm_dp_mst_sideband_tx_state_str(int state) -{ - static const char * const sideband_reason_str[] = { - DP_STR(QUEUED), - DP_STR(START_SEND), - DP_STR(SENT), - DP_STR(RX), - DP_STR(TIMEOUT), - }; - - if (state >= ARRAY_SIZE(sideband_reason_str) || - !sideband_reason_str[state]) - return "unknown"; - - return sideband_reason_str[state]; -} - -static int -drm_dp_mst_rad_to_str(const u8 rad[8], u8 lct, char *out, size_t len) -{ - int i; - u8 unpacked_rad[16]; - - for (i = 0; i < lct; i++) { - if (i % 2) - unpacked_rad[i] = rad[i / 2] >> 4; - else - unpacked_rad[i] = rad[i / 2] & BIT_MASK(4); - } - - /* TODO: Eventually add something to printk so we can format the rad - * like this: 1.2.3 - */ - return snprintf(out, len, "%*phC", lct, unpacked_rad); -} - -/* sideband msg handling */ -static u8 drm_dp_msg_header_crc4(const uint8_t *data, size_t num_nibbles) -{ - u8 bitmask = 0x80; - u8 bitshift = 7; - u8 array_index = 0; - int number_of_bits = num_nibbles * 4; - u8 remainder = 0; - - while (number_of_bits != 0) { - number_of_bits--; - remainder <<= 1; - remainder |= (data[array_index] & bitmask) >> bitshift; - bitmask >>= 1; - bitshift--; - if (bitmask == 0) { - bitmask = 0x80; - bitshift = 7; - array_index++; - } - if ((remainder & 0x10) == 0x10) - remainder ^= 0x13; - } - - number_of_bits = 4; - while (number_of_bits != 0) { - number_of_bits--; - remainder <<= 1; - if ((remainder & 0x10) != 0) - remainder ^= 0x13; - } - - return remainder; -} - -static u8 drm_dp_msg_data_crc4(const uint8_t *data, u8 number_of_bytes) -{ - u8 bitmask = 0x80; - u8 bitshift = 7; - u8 array_index = 0; - int number_of_bits = number_of_bytes * 8; - u16 remainder = 0; - - while (number_of_bits != 0) { - number_of_bits--; - remainder <<= 1; - remainder |= (data[array_index] & bitmask) >> bitshift; - bitmask >>= 1; - bitshift--; - if (bitmask == 0) { - bitmask = 0x80; - bitshift = 7; - array_index++; - } - if ((remainder & 0x100) == 0x100) - remainder ^= 0xd5; - } - - number_of_bits = 8; - while (number_of_bits != 0) { - number_of_bits--; - remainder <<= 1; - if ((remainder & 0x100) != 0) - remainder ^= 0xd5; - } - - return remainder & 0xff; -} -static inline u8 drm_dp_calc_sb_hdr_size(struct drm_dp_sideband_msg_hdr *hdr) -{ - u8 size = 3; - - size += (hdr->lct / 2); - return size; -} - -static void drm_dp_encode_sideband_msg_hdr(struct drm_dp_sideband_msg_hdr *hdr, - u8 *buf, int *len) -{ - int idx = 0; - int i; - u8 crc4; - - buf[idx++] = ((hdr->lct & 0xf) << 4) | (hdr->lcr & 0xf); - for (i = 0; i < (hdr->lct / 2); i++) - buf[idx++] = hdr->rad[i]; - buf[idx++] = (hdr->broadcast << 7) | (hdr->path_msg << 6) | - (hdr->msg_len & 0x3f); - buf[idx++] = (hdr->somt << 7) | (hdr->eomt << 6) | (hdr->seqno << 4); - - crc4 = drm_dp_msg_header_crc4(buf, (idx * 2) - 1); - buf[idx - 1] |= (crc4 & 0xf); - - *len = idx; -} - -static bool drm_dp_decode_sideband_msg_hdr(const struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_sideband_msg_hdr *hdr, - u8 *buf, int buflen, u8 *hdrlen) -{ - u8 crc4; - u8 len; - int i; - u8 idx; - - if (buf[0] == 0) - return false; - len = 3; - len += ((buf[0] & 0xf0) >> 4) / 2; - if (len > buflen) - return false; - crc4 = drm_dp_msg_header_crc4(buf, (len * 2) - 1); - - if ((crc4 & 0xf) != (buf[len - 1] & 0xf)) { - drm_dbg_kms(mgr->dev, "crc4 mismatch 0x%x 0x%x\n", crc4, buf[len - 1]); - return false; - } - - hdr->lct = (buf[0] & 0xf0) >> 4; - hdr->lcr = (buf[0] & 0xf); - idx = 1; - for (i = 0; i < (hdr->lct / 2); i++) - hdr->rad[i] = buf[idx++]; - hdr->broadcast = (buf[idx] >> 7) & 0x1; - hdr->path_msg = (buf[idx] >> 6) & 0x1; - hdr->msg_len = buf[idx] & 0x3f; - idx++; - hdr->somt = (buf[idx] >> 7) & 0x1; - hdr->eomt = (buf[idx] >> 6) & 0x1; - hdr->seqno = (buf[idx] >> 4) & 0x1; - idx++; - *hdrlen = idx; - return true; -} - -void -drm_dp_encode_sideband_req(const struct drm_dp_sideband_msg_req_body *req, - struct drm_dp_sideband_msg_tx *raw) -{ - int idx = 0; - int i; - u8 *buf = raw->msg; - - buf[idx++] = req->req_type & 0x7f; - - switch (req->req_type) { - case DP_ENUM_PATH_RESOURCES: - case DP_POWER_DOWN_PHY: - case DP_POWER_UP_PHY: - buf[idx] = (req->u.port_num.port_number & 0xf) << 4; - idx++; - break; - case DP_ALLOCATE_PAYLOAD: - buf[idx] = (req->u.allocate_payload.port_number & 0xf) << 4 | - (req->u.allocate_payload.number_sdp_streams & 0xf); - idx++; - buf[idx] = (req->u.allocate_payload.vcpi & 0x7f); - idx++; - buf[idx] = (req->u.allocate_payload.pbn >> 8); - idx++; - buf[idx] = (req->u.allocate_payload.pbn & 0xff); - idx++; - for (i = 0; i < req->u.allocate_payload.number_sdp_streams / 2; i++) { - buf[idx] = ((req->u.allocate_payload.sdp_stream_sink[i * 2] & 0xf) << 4) | - (req->u.allocate_payload.sdp_stream_sink[i * 2 + 1] & 0xf); - idx++; - } - if (req->u.allocate_payload.number_sdp_streams & 1) { - i = req->u.allocate_payload.number_sdp_streams - 1; - buf[idx] = (req->u.allocate_payload.sdp_stream_sink[i] & 0xf) << 4; - idx++; - } - break; - case DP_QUERY_PAYLOAD: - buf[idx] = (req->u.query_payload.port_number & 0xf) << 4; - idx++; - buf[idx] = (req->u.query_payload.vcpi & 0x7f); - idx++; - break; - case DP_REMOTE_DPCD_READ: - buf[idx] = (req->u.dpcd_read.port_number & 0xf) << 4; - buf[idx] |= ((req->u.dpcd_read.dpcd_address & 0xf0000) >> 16) & 0xf; - idx++; - buf[idx] = (req->u.dpcd_read.dpcd_address & 0xff00) >> 8; - idx++; - buf[idx] = (req->u.dpcd_read.dpcd_address & 0xff); - idx++; - buf[idx] = (req->u.dpcd_read.num_bytes); - idx++; - break; - - case DP_REMOTE_DPCD_WRITE: - buf[idx] = (req->u.dpcd_write.port_number & 0xf) << 4; - buf[idx] |= ((req->u.dpcd_write.dpcd_address & 0xf0000) >> 16) & 0xf; - idx++; - buf[idx] = (req->u.dpcd_write.dpcd_address & 0xff00) >> 8; - idx++; - buf[idx] = (req->u.dpcd_write.dpcd_address & 0xff); - idx++; - buf[idx] = (req->u.dpcd_write.num_bytes); - idx++; - memcpy(&buf[idx], req->u.dpcd_write.bytes, req->u.dpcd_write.num_bytes); - idx += req->u.dpcd_write.num_bytes; - break; - case DP_REMOTE_I2C_READ: - buf[idx] = (req->u.i2c_read.port_number & 0xf) << 4; - buf[idx] |= (req->u.i2c_read.num_transactions & 0x3); - idx++; - for (i = 0; i < (req->u.i2c_read.num_transactions & 0x3); i++) { - buf[idx] = req->u.i2c_read.transactions[i].i2c_dev_id & 0x7f; - idx++; - buf[idx] = req->u.i2c_read.transactions[i].num_bytes; - idx++; - memcpy(&buf[idx], req->u.i2c_read.transactions[i].bytes, req->u.i2c_read.transactions[i].num_bytes); - idx += req->u.i2c_read.transactions[i].num_bytes; - - buf[idx] = (req->u.i2c_read.transactions[i].no_stop_bit & 0x1) << 4; - buf[idx] |= (req->u.i2c_read.transactions[i].i2c_transaction_delay & 0xf); - idx++; - } - buf[idx] = (req->u.i2c_read.read_i2c_device_id) & 0x7f; - idx++; - buf[idx] = (req->u.i2c_read.num_bytes_read); - idx++; - break; - - case DP_REMOTE_I2C_WRITE: - buf[idx] = (req->u.i2c_write.port_number & 0xf) << 4; - idx++; - buf[idx] = (req->u.i2c_write.write_i2c_device_id) & 0x7f; - idx++; - buf[idx] = (req->u.i2c_write.num_bytes); - idx++; - memcpy(&buf[idx], req->u.i2c_write.bytes, req->u.i2c_write.num_bytes); - idx += req->u.i2c_write.num_bytes; - break; - case DP_QUERY_STREAM_ENC_STATUS: { - const struct drm_dp_query_stream_enc_status *msg; - - msg = &req->u.enc_status; - buf[idx] = msg->stream_id; - idx++; - memcpy(&buf[idx], msg->client_id, sizeof(msg->client_id)); - idx += sizeof(msg->client_id); - buf[idx] = 0; - buf[idx] |= FIELD_PREP(GENMASK(1, 0), msg->stream_event); - buf[idx] |= msg->valid_stream_event ? BIT(2) : 0; - buf[idx] |= FIELD_PREP(GENMASK(4, 3), msg->stream_behavior); - buf[idx] |= msg->valid_stream_behavior ? BIT(5) : 0; - idx++; - } - break; - } - raw->cur_len = idx; -} -EXPORT_SYMBOL_FOR_TESTS_ONLY(drm_dp_encode_sideband_req); - -/* Decode a sideband request we've encoded, mainly used for debugging */ -int -drm_dp_decode_sideband_req(const struct drm_dp_sideband_msg_tx *raw, - struct drm_dp_sideband_msg_req_body *req) -{ - const u8 *buf = raw->msg; - int i, idx = 0; - - req->req_type = buf[idx++] & 0x7f; - switch (req->req_type) { - case DP_ENUM_PATH_RESOURCES: - case DP_POWER_DOWN_PHY: - case DP_POWER_UP_PHY: - req->u.port_num.port_number = (buf[idx] >> 4) & 0xf; - break; - case DP_ALLOCATE_PAYLOAD: - { - struct drm_dp_allocate_payload *a = - &req->u.allocate_payload; - - a->number_sdp_streams = buf[idx] & 0xf; - a->port_number = (buf[idx] >> 4) & 0xf; - - WARN_ON(buf[++idx] & 0x80); - a->vcpi = buf[idx] & 0x7f; - - a->pbn = buf[++idx] << 8; - a->pbn |= buf[++idx]; - - idx++; - for (i = 0; i < a->number_sdp_streams; i++) { - a->sdp_stream_sink[i] = - (buf[idx + (i / 2)] >> ((i % 2) ? 0 : 4)) & 0xf; - } - } - break; - case DP_QUERY_PAYLOAD: - req->u.query_payload.port_number = (buf[idx] >> 4) & 0xf; - WARN_ON(buf[++idx] & 0x80); - req->u.query_payload.vcpi = buf[idx] & 0x7f; - break; - case DP_REMOTE_DPCD_READ: - { - struct drm_dp_remote_dpcd_read *r = &req->u.dpcd_read; - - r->port_number = (buf[idx] >> 4) & 0xf; - - r->dpcd_address = (buf[idx] << 16) & 0xf0000; - r->dpcd_address |= (buf[++idx] << 8) & 0xff00; - r->dpcd_address |= buf[++idx] & 0xff; - - r->num_bytes = buf[++idx]; - } - break; - case DP_REMOTE_DPCD_WRITE: - { - struct drm_dp_remote_dpcd_write *w = - &req->u.dpcd_write; - - w->port_number = (buf[idx] >> 4) & 0xf; - - w->dpcd_address = (buf[idx] << 16) & 0xf0000; - w->dpcd_address |= (buf[++idx] << 8) & 0xff00; - w->dpcd_address |= buf[++idx] & 0xff; - - w->num_bytes = buf[++idx]; - - w->bytes = kmemdup(&buf[++idx], w->num_bytes, - GFP_KERNEL); - if (!w->bytes) - return -ENOMEM; - } - break; - case DP_REMOTE_I2C_READ: - { - struct drm_dp_remote_i2c_read *r = &req->u.i2c_read; - struct drm_dp_remote_i2c_read_tx *tx; - bool failed = false; - - r->num_transactions = buf[idx] & 0x3; - r->port_number = (buf[idx] >> 4) & 0xf; - for (i = 0; i < r->num_transactions; i++) { - tx = &r->transactions[i]; - - tx->i2c_dev_id = buf[++idx] & 0x7f; - tx->num_bytes = buf[++idx]; - tx->bytes = kmemdup(&buf[++idx], - tx->num_bytes, - GFP_KERNEL); - if (!tx->bytes) { - failed = true; - break; - } - idx += tx->num_bytes; - tx->no_stop_bit = (buf[idx] >> 5) & 0x1; - tx->i2c_transaction_delay = buf[idx] & 0xf; - } - - if (failed) { - for (i = 0; i < r->num_transactions; i++) { - tx = &r->transactions[i]; - kfree(tx->bytes); - } - return -ENOMEM; - } - - r->read_i2c_device_id = buf[++idx] & 0x7f; - r->num_bytes_read = buf[++idx]; - } - break; - case DP_REMOTE_I2C_WRITE: - { - struct drm_dp_remote_i2c_write *w = &req->u.i2c_write; - - w->port_number = (buf[idx] >> 4) & 0xf; - w->write_i2c_device_id = buf[++idx] & 0x7f; - w->num_bytes = buf[++idx]; - w->bytes = kmemdup(&buf[++idx], w->num_bytes, - GFP_KERNEL); - if (!w->bytes) - return -ENOMEM; - } - break; - case DP_QUERY_STREAM_ENC_STATUS: - req->u.enc_status.stream_id = buf[idx++]; - for (i = 0; i < sizeof(req->u.enc_status.client_id); i++) - req->u.enc_status.client_id[i] = buf[idx++]; - - req->u.enc_status.stream_event = FIELD_GET(GENMASK(1, 0), - buf[idx]); - req->u.enc_status.valid_stream_event = FIELD_GET(BIT(2), - buf[idx]); - req->u.enc_status.stream_behavior = FIELD_GET(GENMASK(4, 3), - buf[idx]); - req->u.enc_status.valid_stream_behavior = FIELD_GET(BIT(5), - buf[idx]); - break; - } - - return 0; -} -EXPORT_SYMBOL_FOR_TESTS_ONLY(drm_dp_decode_sideband_req); - -void -drm_dp_dump_sideband_msg_req_body(const struct drm_dp_sideband_msg_req_body *req, - int indent, struct drm_printer *printer) -{ - int i; - -#define P(f, ...) drm_printf_indent(printer, indent, f, ##__VA_ARGS__) - if (req->req_type == DP_LINK_ADDRESS) { - /* No contents to print */ - P("type=%s\n", drm_dp_mst_req_type_str(req->req_type)); - return; - } - - P("type=%s contents:\n", drm_dp_mst_req_type_str(req->req_type)); - indent++; - - switch (req->req_type) { - case DP_ENUM_PATH_RESOURCES: - case DP_POWER_DOWN_PHY: - case DP_POWER_UP_PHY: - P("port=%d\n", req->u.port_num.port_number); - break; - case DP_ALLOCATE_PAYLOAD: - P("port=%d vcpi=%d pbn=%d sdp_streams=%d %*ph\n", - req->u.allocate_payload.port_number, - req->u.allocate_payload.vcpi, req->u.allocate_payload.pbn, - req->u.allocate_payload.number_sdp_streams, - req->u.allocate_payload.number_sdp_streams, - req->u.allocate_payload.sdp_stream_sink); - break; - case DP_QUERY_PAYLOAD: - P("port=%d vcpi=%d\n", - req->u.query_payload.port_number, - req->u.query_payload.vcpi); - break; - case DP_REMOTE_DPCD_READ: - P("port=%d dpcd_addr=%05x len=%d\n", - req->u.dpcd_read.port_number, req->u.dpcd_read.dpcd_address, - req->u.dpcd_read.num_bytes); - break; - case DP_REMOTE_DPCD_WRITE: - P("port=%d addr=%05x len=%d: %*ph\n", - req->u.dpcd_write.port_number, - req->u.dpcd_write.dpcd_address, - req->u.dpcd_write.num_bytes, req->u.dpcd_write.num_bytes, - req->u.dpcd_write.bytes); - break; - case DP_REMOTE_I2C_READ: - P("port=%d num_tx=%d id=%d size=%d:\n", - req->u.i2c_read.port_number, - req->u.i2c_read.num_transactions, - req->u.i2c_read.read_i2c_device_id, - req->u.i2c_read.num_bytes_read); - - indent++; - for (i = 0; i < req->u.i2c_read.num_transactions; i++) { - const struct drm_dp_remote_i2c_read_tx *rtx = - &req->u.i2c_read.transactions[i]; - - P("%d: id=%03d size=%03d no_stop_bit=%d tx_delay=%03d: %*ph\n", - i, rtx->i2c_dev_id, rtx->num_bytes, - rtx->no_stop_bit, rtx->i2c_transaction_delay, - rtx->num_bytes, rtx->bytes); - } - break; - case DP_REMOTE_I2C_WRITE: - P("port=%d id=%d size=%d: %*ph\n", - req->u.i2c_write.port_number, - req->u.i2c_write.write_i2c_device_id, - req->u.i2c_write.num_bytes, req->u.i2c_write.num_bytes, - req->u.i2c_write.bytes); - break; - case DP_QUERY_STREAM_ENC_STATUS: - P("stream_id=%u client_id=%*ph stream_event=%x " - "valid_event=%d stream_behavior=%x valid_behavior=%d", - req->u.enc_status.stream_id, - (int)ARRAY_SIZE(req->u.enc_status.client_id), - req->u.enc_status.client_id, req->u.enc_status.stream_event, - req->u.enc_status.valid_stream_event, - req->u.enc_status.stream_behavior, - req->u.enc_status.valid_stream_behavior); - break; - default: - P("???\n"); - break; - } -#undef P -} -EXPORT_SYMBOL_FOR_TESTS_ONLY(drm_dp_dump_sideband_msg_req_body); - -static inline void -drm_dp_mst_dump_sideband_msg_tx(struct drm_printer *p, - const struct drm_dp_sideband_msg_tx *txmsg) -{ - struct drm_dp_sideband_msg_req_body req; - char buf[64]; - int ret; - int i; - - drm_dp_mst_rad_to_str(txmsg->dst->rad, txmsg->dst->lct, buf, - sizeof(buf)); - drm_printf(p, "txmsg cur_offset=%x cur_len=%x seqno=%x state=%s path_msg=%d dst=%s\n", - txmsg->cur_offset, txmsg->cur_len, txmsg->seqno, - drm_dp_mst_sideband_tx_state_str(txmsg->state), - txmsg->path_msg, buf); - - ret = drm_dp_decode_sideband_req(txmsg, &req); - if (ret) { - drm_printf(p, "\n", ret); - return; - } - drm_dp_dump_sideband_msg_req_body(&req, 1, p); - - switch (req.req_type) { - case DP_REMOTE_DPCD_WRITE: - kfree(req.u.dpcd_write.bytes); - break; - case DP_REMOTE_I2C_READ: - for (i = 0; i < req.u.i2c_read.num_transactions; i++) - kfree(req.u.i2c_read.transactions[i].bytes); - break; - case DP_REMOTE_I2C_WRITE: - kfree(req.u.i2c_write.bytes); - break; - } -} - -static void drm_dp_crc_sideband_chunk_req(u8 *msg, u8 len) -{ - u8 crc4; - - crc4 = drm_dp_msg_data_crc4(msg, len); - msg[len] = crc4; -} - -static void drm_dp_encode_sideband_reply(struct drm_dp_sideband_msg_reply_body *rep, - struct drm_dp_sideband_msg_tx *raw) -{ - int idx = 0; - u8 *buf = raw->msg; - - buf[idx++] = (rep->reply_type & 0x1) << 7 | (rep->req_type & 0x7f); - - raw->cur_len = idx; -} - -static int drm_dp_sideband_msg_set_header(struct drm_dp_sideband_msg_rx *msg, - struct drm_dp_sideband_msg_hdr *hdr, - u8 hdrlen) -{ - /* - * ignore out-of-order messages or messages that are part of a - * failed transaction - */ - if (!hdr->somt && !msg->have_somt) - return false; - - /* get length contained in this portion */ - msg->curchunk_idx = 0; - msg->curchunk_len = hdr->msg_len; - msg->curchunk_hdrlen = hdrlen; - - /* we have already gotten an somt - don't bother parsing */ - if (hdr->somt && msg->have_somt) - return false; - - if (hdr->somt) { - memcpy(&msg->initial_hdr, hdr, - sizeof(struct drm_dp_sideband_msg_hdr)); - msg->have_somt = true; - } - if (hdr->eomt) - msg->have_eomt = true; - - return true; -} - -/* this adds a chunk of msg to the builder to get the final msg */ -static bool drm_dp_sideband_append_payload(struct drm_dp_sideband_msg_rx *msg, - u8 *replybuf, u8 replybuflen) -{ - u8 crc4; - - memcpy(&msg->chunk[msg->curchunk_idx], replybuf, replybuflen); - msg->curchunk_idx += replybuflen; - - if (msg->curchunk_idx >= msg->curchunk_len) { - /* do CRC */ - crc4 = drm_dp_msg_data_crc4(msg->chunk, msg->curchunk_len - 1); - if (crc4 != msg->chunk[msg->curchunk_len - 1]) - print_hex_dump(KERN_DEBUG, "wrong crc", - DUMP_PREFIX_NONE, 16, 1, - msg->chunk, msg->curchunk_len, false); - /* copy chunk into bigger msg */ - memcpy(&msg->msg[msg->curlen], msg->chunk, msg->curchunk_len - 1); - msg->curlen += msg->curchunk_len - 1; - } - return true; -} - -static bool drm_dp_sideband_parse_link_address(const struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_reply_body *repmsg) -{ - int idx = 1; - int i; - - memcpy(repmsg->u.link_addr.guid, &raw->msg[idx], 16); - idx += 16; - repmsg->u.link_addr.nports = raw->msg[idx] & 0xf; - idx++; - if (idx > raw->curlen) - goto fail_len; - for (i = 0; i < repmsg->u.link_addr.nports; i++) { - if (raw->msg[idx] & 0x80) - repmsg->u.link_addr.ports[i].input_port = 1; - - repmsg->u.link_addr.ports[i].peer_device_type = (raw->msg[idx] >> 4) & 0x7; - repmsg->u.link_addr.ports[i].port_number = (raw->msg[idx] & 0xf); - - idx++; - if (idx > raw->curlen) - goto fail_len; - repmsg->u.link_addr.ports[i].mcs = (raw->msg[idx] >> 7) & 0x1; - repmsg->u.link_addr.ports[i].ddps = (raw->msg[idx] >> 6) & 0x1; - if (repmsg->u.link_addr.ports[i].input_port == 0) - repmsg->u.link_addr.ports[i].legacy_device_plug_status = (raw->msg[idx] >> 5) & 0x1; - idx++; - if (idx > raw->curlen) - goto fail_len; - if (repmsg->u.link_addr.ports[i].input_port == 0) { - repmsg->u.link_addr.ports[i].dpcd_revision = (raw->msg[idx]); - idx++; - if (idx > raw->curlen) - goto fail_len; - memcpy(repmsg->u.link_addr.ports[i].peer_guid, &raw->msg[idx], 16); - idx += 16; - if (idx > raw->curlen) - goto fail_len; - repmsg->u.link_addr.ports[i].num_sdp_streams = (raw->msg[idx] >> 4) & 0xf; - repmsg->u.link_addr.ports[i].num_sdp_stream_sinks = (raw->msg[idx] & 0xf); - idx++; - - } - if (idx > raw->curlen) - goto fail_len; - } - - return true; -fail_len: - DRM_DEBUG_KMS("link address reply parse length fail %d %d\n", idx, raw->curlen); - return false; -} - -static bool drm_dp_sideband_parse_remote_dpcd_read(struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_reply_body *repmsg) -{ - int idx = 1; - - repmsg->u.remote_dpcd_read_ack.port_number = raw->msg[idx] & 0xf; - idx++; - if (idx > raw->curlen) - goto fail_len; - repmsg->u.remote_dpcd_read_ack.num_bytes = raw->msg[idx]; - idx++; - if (idx > raw->curlen) - goto fail_len; - - memcpy(repmsg->u.remote_dpcd_read_ack.bytes, &raw->msg[idx], repmsg->u.remote_dpcd_read_ack.num_bytes); - return true; -fail_len: - DRM_DEBUG_KMS("link address reply parse length fail %d %d\n", idx, raw->curlen); - return false; -} - -static bool drm_dp_sideband_parse_remote_dpcd_write(struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_reply_body *repmsg) -{ - int idx = 1; - - repmsg->u.remote_dpcd_write_ack.port_number = raw->msg[idx] & 0xf; - idx++; - if (idx > raw->curlen) - goto fail_len; - return true; -fail_len: - DRM_DEBUG_KMS("parse length fail %d %d\n", idx, raw->curlen); - return false; -} - -static bool drm_dp_sideband_parse_remote_i2c_read_ack(struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_reply_body *repmsg) -{ - int idx = 1; - - repmsg->u.remote_i2c_read_ack.port_number = (raw->msg[idx] & 0xf); - idx++; - if (idx > raw->curlen) - goto fail_len; - repmsg->u.remote_i2c_read_ack.num_bytes = raw->msg[idx]; - idx++; - /* TODO check */ - memcpy(repmsg->u.remote_i2c_read_ack.bytes, &raw->msg[idx], repmsg->u.remote_i2c_read_ack.num_bytes); - return true; -fail_len: - DRM_DEBUG_KMS("remote i2c reply parse length fail %d %d\n", idx, raw->curlen); - return false; -} - -static bool drm_dp_sideband_parse_enum_path_resources_ack(struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_reply_body *repmsg) -{ - int idx = 1; - - repmsg->u.path_resources.port_number = (raw->msg[idx] >> 4) & 0xf; - repmsg->u.path_resources.fec_capable = raw->msg[idx] & 0x1; - idx++; - if (idx > raw->curlen) - goto fail_len; - repmsg->u.path_resources.full_payload_bw_number = (raw->msg[idx] << 8) | (raw->msg[idx+1]); - idx += 2; - if (idx > raw->curlen) - goto fail_len; - repmsg->u.path_resources.avail_payload_bw_number = (raw->msg[idx] << 8) | (raw->msg[idx+1]); - idx += 2; - if (idx > raw->curlen) - goto fail_len; - return true; -fail_len: - DRM_DEBUG_KMS("enum resource parse length fail %d %d\n", idx, raw->curlen); - return false; -} - -static bool drm_dp_sideband_parse_allocate_payload_ack(struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_reply_body *repmsg) -{ - int idx = 1; - - repmsg->u.allocate_payload.port_number = (raw->msg[idx] >> 4) & 0xf; - idx++; - if (idx > raw->curlen) - goto fail_len; - repmsg->u.allocate_payload.vcpi = raw->msg[idx]; - idx++; - if (idx > raw->curlen) - goto fail_len; - repmsg->u.allocate_payload.allocated_pbn = (raw->msg[idx] << 8) | (raw->msg[idx+1]); - idx += 2; - if (idx > raw->curlen) - goto fail_len; - return true; -fail_len: - DRM_DEBUG_KMS("allocate payload parse length fail %d %d\n", idx, raw->curlen); - return false; -} - -static bool drm_dp_sideband_parse_query_payload_ack(struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_reply_body *repmsg) -{ - int idx = 1; - - repmsg->u.query_payload.port_number = (raw->msg[idx] >> 4) & 0xf; - idx++; - if (idx > raw->curlen) - goto fail_len; - repmsg->u.query_payload.allocated_pbn = (raw->msg[idx] << 8) | (raw->msg[idx + 1]); - idx += 2; - if (idx > raw->curlen) - goto fail_len; - return true; -fail_len: - DRM_DEBUG_KMS("query payload parse length fail %d %d\n", idx, raw->curlen); - return false; -} - -static bool drm_dp_sideband_parse_power_updown_phy_ack(struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_reply_body *repmsg) -{ - int idx = 1; - - repmsg->u.port_number.port_number = (raw->msg[idx] >> 4) & 0xf; - idx++; - if (idx > raw->curlen) { - DRM_DEBUG_KMS("power up/down phy parse length fail %d %d\n", - idx, raw->curlen); - return false; - } - return true; -} - -static bool -drm_dp_sideband_parse_query_stream_enc_status( - struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_reply_body *repmsg) -{ - struct drm_dp_query_stream_enc_status_ack_reply *reply; - - reply = &repmsg->u.enc_status; - - reply->stream_id = raw->msg[3]; - - reply->reply_signed = raw->msg[2] & BIT(0); - - /* - * NOTE: It's my impression from reading the spec that the below parsing - * is correct. However I noticed while testing with an HDCP 1.4 display - * through an HDCP 2.2 hub that only bit 3 was set. In that case, I - * would expect both bits to be set. So keep the parsing following the - * spec, but beware reality might not match the spec (at least for some - * configurations). - */ - reply->hdcp_1x_device_present = raw->msg[2] & BIT(4); - reply->hdcp_2x_device_present = raw->msg[2] & BIT(3); - - reply->query_capable_device_present = raw->msg[2] & BIT(5); - reply->legacy_device_present = raw->msg[2] & BIT(6); - reply->unauthorizable_device_present = raw->msg[2] & BIT(7); - - reply->auth_completed = !!(raw->msg[1] & BIT(3)); - reply->encryption_enabled = !!(raw->msg[1] & BIT(4)); - reply->repeater_present = !!(raw->msg[1] & BIT(5)); - reply->state = (raw->msg[1] & GENMASK(7, 6)) >> 6; - - return true; -} - -static bool drm_dp_sideband_parse_reply(const struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_reply_body *msg) -{ - memset(msg, 0, sizeof(*msg)); - msg->reply_type = (raw->msg[0] & 0x80) >> 7; - msg->req_type = (raw->msg[0] & 0x7f); - - if (msg->reply_type == DP_SIDEBAND_REPLY_NAK) { - memcpy(msg->u.nak.guid, &raw->msg[1], 16); - msg->u.nak.reason = raw->msg[17]; - msg->u.nak.nak_data = raw->msg[18]; - return false; - } - - switch (msg->req_type) { - case DP_LINK_ADDRESS: - return drm_dp_sideband_parse_link_address(mgr, raw, msg); - case DP_QUERY_PAYLOAD: - return drm_dp_sideband_parse_query_payload_ack(raw, msg); - case DP_REMOTE_DPCD_READ: - return drm_dp_sideband_parse_remote_dpcd_read(raw, msg); - case DP_REMOTE_DPCD_WRITE: - return drm_dp_sideband_parse_remote_dpcd_write(raw, msg); - case DP_REMOTE_I2C_READ: - return drm_dp_sideband_parse_remote_i2c_read_ack(raw, msg); - case DP_REMOTE_I2C_WRITE: - return true; /* since there's nothing to parse */ - case DP_ENUM_PATH_RESOURCES: - return drm_dp_sideband_parse_enum_path_resources_ack(raw, msg); - case DP_ALLOCATE_PAYLOAD: - return drm_dp_sideband_parse_allocate_payload_ack(raw, msg); - case DP_POWER_DOWN_PHY: - case DP_POWER_UP_PHY: - return drm_dp_sideband_parse_power_updown_phy_ack(raw, msg); - case DP_CLEAR_PAYLOAD_ID_TABLE: - return true; /* since there's nothing to parse */ - case DP_QUERY_STREAM_ENC_STATUS: - return drm_dp_sideband_parse_query_stream_enc_status(raw, msg); - default: - drm_err(mgr->dev, "Got unknown reply 0x%02x (%s)\n", - msg->req_type, drm_dp_mst_req_type_str(msg->req_type)); - return false; - } -} - -static bool -drm_dp_sideband_parse_connection_status_notify(const struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_req_body *msg) -{ - int idx = 1; - - msg->u.conn_stat.port_number = (raw->msg[idx] & 0xf0) >> 4; - idx++; - if (idx > raw->curlen) - goto fail_len; - - memcpy(msg->u.conn_stat.guid, &raw->msg[idx], 16); - idx += 16; - if (idx > raw->curlen) - goto fail_len; - - msg->u.conn_stat.legacy_device_plug_status = (raw->msg[idx] >> 6) & 0x1; - msg->u.conn_stat.displayport_device_plug_status = (raw->msg[idx] >> 5) & 0x1; - msg->u.conn_stat.message_capability_status = (raw->msg[idx] >> 4) & 0x1; - msg->u.conn_stat.input_port = (raw->msg[idx] >> 3) & 0x1; - msg->u.conn_stat.peer_device_type = (raw->msg[idx] & 0x7); - idx++; - return true; -fail_len: - drm_dbg_kms(mgr->dev, "connection status reply parse length fail %d %d\n", - idx, raw->curlen); - return false; -} - -static bool drm_dp_sideband_parse_resource_status_notify(const struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_req_body *msg) -{ - int idx = 1; - - msg->u.resource_stat.port_number = (raw->msg[idx] & 0xf0) >> 4; - idx++; - if (idx > raw->curlen) - goto fail_len; - - memcpy(msg->u.resource_stat.guid, &raw->msg[idx], 16); - idx += 16; - if (idx > raw->curlen) - goto fail_len; - - msg->u.resource_stat.available_pbn = (raw->msg[idx] << 8) | (raw->msg[idx + 1]); - idx++; - return true; -fail_len: - drm_dbg_kms(mgr->dev, "resource status reply parse length fail %d %d\n", idx, raw->curlen); - return false; -} - -static bool drm_dp_sideband_parse_req(const struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_req_body *msg) -{ - memset(msg, 0, sizeof(*msg)); - msg->req_type = (raw->msg[0] & 0x7f); - - switch (msg->req_type) { - case DP_CONNECTION_STATUS_NOTIFY: - return drm_dp_sideband_parse_connection_status_notify(mgr, raw, msg); - case DP_RESOURCE_STATUS_NOTIFY: - return drm_dp_sideband_parse_resource_status_notify(mgr, raw, msg); - default: - drm_err(mgr->dev, "Got unknown request 0x%02x (%s)\n", - msg->req_type, drm_dp_mst_req_type_str(msg->req_type)); - return false; - } -} - -static void build_dpcd_write(struct drm_dp_sideband_msg_tx *msg, - u8 port_num, u32 offset, u8 num_bytes, u8 *bytes) -{ - struct drm_dp_sideband_msg_req_body req; - - req.req_type = DP_REMOTE_DPCD_WRITE; - req.u.dpcd_write.port_number = port_num; - req.u.dpcd_write.dpcd_address = offset; - req.u.dpcd_write.num_bytes = num_bytes; - req.u.dpcd_write.bytes = bytes; - drm_dp_encode_sideband_req(&req, msg); -} - -static void build_link_address(struct drm_dp_sideband_msg_tx *msg) -{ - struct drm_dp_sideband_msg_req_body req; - - req.req_type = DP_LINK_ADDRESS; - drm_dp_encode_sideband_req(&req, msg); -} - -static void build_clear_payload_id_table(struct drm_dp_sideband_msg_tx *msg) -{ - struct drm_dp_sideband_msg_req_body req; - - req.req_type = DP_CLEAR_PAYLOAD_ID_TABLE; - drm_dp_encode_sideband_req(&req, msg); - msg->path_msg = true; -} - -static int build_enum_path_resources(struct drm_dp_sideband_msg_tx *msg, - int port_num) -{ - struct drm_dp_sideband_msg_req_body req; - - req.req_type = DP_ENUM_PATH_RESOURCES; - req.u.port_num.port_number = port_num; - drm_dp_encode_sideband_req(&req, msg); - msg->path_msg = true; - return 0; -} - -static void build_allocate_payload(struct drm_dp_sideband_msg_tx *msg, - int port_num, - u8 vcpi, uint16_t pbn, - u8 number_sdp_streams, - u8 *sdp_stream_sink) -{ - struct drm_dp_sideband_msg_req_body req; - - memset(&req, 0, sizeof(req)); - req.req_type = DP_ALLOCATE_PAYLOAD; - req.u.allocate_payload.port_number = port_num; - req.u.allocate_payload.vcpi = vcpi; - req.u.allocate_payload.pbn = pbn; - req.u.allocate_payload.number_sdp_streams = number_sdp_streams; - memcpy(req.u.allocate_payload.sdp_stream_sink, sdp_stream_sink, - number_sdp_streams); - drm_dp_encode_sideband_req(&req, msg); - msg->path_msg = true; -} - -static void build_power_updown_phy(struct drm_dp_sideband_msg_tx *msg, - int port_num, bool power_up) -{ - struct drm_dp_sideband_msg_req_body req; - - if (power_up) - req.req_type = DP_POWER_UP_PHY; - else - req.req_type = DP_POWER_DOWN_PHY; - - req.u.port_num.port_number = port_num; - drm_dp_encode_sideband_req(&req, msg); - msg->path_msg = true; -} - -static int -build_query_stream_enc_status(struct drm_dp_sideband_msg_tx *msg, u8 stream_id, - u8 *q_id) -{ - struct drm_dp_sideband_msg_req_body req; - - req.req_type = DP_QUERY_STREAM_ENC_STATUS; - req.u.enc_status.stream_id = stream_id; - memcpy(req.u.enc_status.client_id, q_id, - sizeof(req.u.enc_status.client_id)); - req.u.enc_status.stream_event = 0; - req.u.enc_status.valid_stream_event = false; - req.u.enc_status.stream_behavior = 0; - req.u.enc_status.valid_stream_behavior = false; - - drm_dp_encode_sideband_req(&req, msg); - return 0; -} - -static int drm_dp_mst_assign_payload_id(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_vcpi *vcpi) -{ - int ret, vcpi_ret; - - mutex_lock(&mgr->payload_lock); - ret = find_first_zero_bit(&mgr->payload_mask, mgr->max_payloads + 1); - if (ret > mgr->max_payloads) { - ret = -EINVAL; - drm_dbg_kms(mgr->dev, "out of payload ids %d\n", ret); - goto out_unlock; - } - - vcpi_ret = find_first_zero_bit(&mgr->vcpi_mask, mgr->max_payloads + 1); - if (vcpi_ret > mgr->max_payloads) { - ret = -EINVAL; - drm_dbg_kms(mgr->dev, "out of vcpi ids %d\n", ret); - goto out_unlock; - } - - set_bit(ret, &mgr->payload_mask); - set_bit(vcpi_ret, &mgr->vcpi_mask); - vcpi->vcpi = vcpi_ret + 1; - mgr->proposed_vcpis[ret - 1] = vcpi; -out_unlock: - mutex_unlock(&mgr->payload_lock); - return ret; -} - -static void drm_dp_mst_put_payload_id(struct drm_dp_mst_topology_mgr *mgr, - int vcpi) -{ - int i; - - if (vcpi == 0) - return; - - mutex_lock(&mgr->payload_lock); - drm_dbg_kms(mgr->dev, "putting payload %d\n", vcpi); - clear_bit(vcpi - 1, &mgr->vcpi_mask); - - for (i = 0; i < mgr->max_payloads; i++) { - if (mgr->proposed_vcpis[i] && - mgr->proposed_vcpis[i]->vcpi == vcpi) { - mgr->proposed_vcpis[i] = NULL; - clear_bit(i + 1, &mgr->payload_mask); - } - } - mutex_unlock(&mgr->payload_lock); -} - -static bool check_txmsg_state(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_sideband_msg_tx *txmsg) -{ - unsigned int state; - - /* - * All updates to txmsg->state are protected by mgr->qlock, and the two - * cases we check here are terminal states. For those the barriers - * provided by the wake_up/wait_event pair are enough. - */ - state = READ_ONCE(txmsg->state); - return (state == DRM_DP_SIDEBAND_TX_RX || - state == DRM_DP_SIDEBAND_TX_TIMEOUT); -} - -static int drm_dp_mst_wait_tx_reply(struct drm_dp_mst_branch *mstb, - struct drm_dp_sideband_msg_tx *txmsg) -{ - struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; - unsigned long wait_timeout = msecs_to_jiffies(4000); - unsigned long wait_expires = jiffies + wait_timeout; - int ret; - - for (;;) { - /* - * If the driver provides a way for this, change to - * poll-waiting for the MST reply interrupt if we didn't receive - * it for 50 msec. This would cater for cases where the HPD - * pulse signal got lost somewhere, even though the sink raised - * the corresponding MST interrupt correctly. One example is the - * Club 3D CAC-1557 TypeC -> DP adapter which for some reason - * filters out short pulses with a duration less than ~540 usec. - * - * The poll period is 50 msec to avoid missing an interrupt - * after the sink has cleared it (after a 110msec timeout - * since it raised the interrupt). - */ - ret = wait_event_timeout(mgr->tx_waitq, - check_txmsg_state(mgr, txmsg), - mgr->cbs->poll_hpd_irq ? - msecs_to_jiffies(50) : - wait_timeout); - - if (ret || !mgr->cbs->poll_hpd_irq || - time_after(jiffies, wait_expires)) - break; - - mgr->cbs->poll_hpd_irq(mgr); - } - - mutex_lock(&mgr->qlock); - if (ret > 0) { - if (txmsg->state == DRM_DP_SIDEBAND_TX_TIMEOUT) { - ret = -EIO; - goto out; - } - } else { - drm_dbg_kms(mgr->dev, "timedout msg send %p %d %d\n", - txmsg, txmsg->state, txmsg->seqno); - - /* dump some state */ - ret = -EIO; - - /* remove from q */ - if (txmsg->state == DRM_DP_SIDEBAND_TX_QUEUED || - txmsg->state == DRM_DP_SIDEBAND_TX_START_SEND || - txmsg->state == DRM_DP_SIDEBAND_TX_SENT) - list_del(&txmsg->next); - } -out: - if (unlikely(ret == -EIO) && drm_debug_enabled(DRM_UT_DP)) { - struct drm_printer p = drm_debug_printer(DBG_PREFIX); - - drm_dp_mst_dump_sideband_msg_tx(&p, txmsg); - } - mutex_unlock(&mgr->qlock); - - drm_dp_mst_kick_tx(mgr); - return ret; -} - -static struct drm_dp_mst_branch *drm_dp_add_mst_branch_device(u8 lct, u8 *rad) -{ - struct drm_dp_mst_branch *mstb; - - mstb = kzalloc(sizeof(*mstb), GFP_KERNEL); - if (!mstb) - return NULL; - - mstb->lct = lct; - if (lct > 1) - memcpy(mstb->rad, rad, lct / 2); - INIT_LIST_HEAD(&mstb->ports); - kref_init(&mstb->topology_kref); - kref_init(&mstb->malloc_kref); - return mstb; -} - -static void drm_dp_free_mst_branch_device(struct kref *kref) -{ - struct drm_dp_mst_branch *mstb = - container_of(kref, struct drm_dp_mst_branch, malloc_kref); - - if (mstb->port_parent) - drm_dp_mst_put_port_malloc(mstb->port_parent); - - kfree(mstb); -} - -/** - * DOC: Branch device and port refcounting - * - * Topology refcount overview - * ~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * The refcounting schemes for &struct drm_dp_mst_branch and &struct - * drm_dp_mst_port are somewhat unusual. Both ports and branch devices have - * two different kinds of refcounts: topology refcounts, and malloc refcounts. - * - * Topology refcounts are not exposed to drivers, and are handled internally - * by the DP MST helpers. The helpers use them in order to prevent the - * in-memory topology state from being changed in the middle of critical - * operations like changing the internal state of payload allocations. This - * means each branch and port will be considered to be connected to the rest - * of the topology until its topology refcount reaches zero. Additionally, - * for ports this means that their associated &struct drm_connector will stay - * registered with userspace until the port's refcount reaches 0. - * - * Malloc refcount overview - * ~~~~~~~~~~~~~~~~~~~~~~~~ - * - * Malloc references are used to keep a &struct drm_dp_mst_port or &struct - * drm_dp_mst_branch allocated even after all of its topology references have - * been dropped, so that the driver or MST helpers can safely access each - * branch's last known state before it was disconnected from the topology. - * When the malloc refcount of a port or branch reaches 0, the memory - * allocation containing the &struct drm_dp_mst_branch or &struct - * drm_dp_mst_port respectively will be freed. - * - * For &struct drm_dp_mst_branch, malloc refcounts are not currently exposed - * to drivers. As of writing this documentation, there are no drivers that - * have a usecase for accessing &struct drm_dp_mst_branch outside of the MST - * helpers. Exposing this API to drivers in a race-free manner would take more - * tweaking of the refcounting scheme, however patches are welcome provided - * there is a legitimate driver usecase for this. - * - * Refcount relationships in a topology - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * Let's take a look at why the relationship between topology and malloc - * refcounts is designed the way it is. - * - * .. kernel-figure:: dp-mst/topology-figure-1.dot - * - * An example of topology and malloc refs in a DP MST topology with two - * active payloads. Topology refcount increments are indicated by solid - * lines, and malloc refcount increments are indicated by dashed lines. - * Each starts from the branch which incremented the refcount, and ends at - * the branch to which the refcount belongs to, i.e. the arrow points the - * same way as the C pointers used to reference a structure. - * - * As you can see in the above figure, every branch increments the topology - * refcount of its children, and increments the malloc refcount of its - * parent. Additionally, every payload increments the malloc refcount of its - * assigned port by 1. - * - * So, what would happen if MSTB #3 from the above figure was unplugged from - * the system, but the driver hadn't yet removed payload #2 from port #3? The - * topology would start to look like the figure below. - * - * .. kernel-figure:: dp-mst/topology-figure-2.dot - * - * Ports and branch devices which have been released from memory are - * colored grey, and references which have been removed are colored red. - * - * Whenever a port or branch device's topology refcount reaches zero, it will - * decrement the topology refcounts of all its children, the malloc refcount - * of its parent, and finally its own malloc refcount. For MSTB #4 and port - * #4, this means they both have been disconnected from the topology and freed - * from memory. But, because payload #2 is still holding a reference to port - * #3, port #3 is removed from the topology but its &struct drm_dp_mst_port - * is still accessible from memory. This also means port #3 has not yet - * decremented the malloc refcount of MSTB #3, so its &struct - * drm_dp_mst_branch will also stay allocated in memory until port #3's - * malloc refcount reaches 0. - * - * This relationship is necessary because in order to release payload #2, we - * need to be able to figure out the last relative of port #3 that's still - * connected to the topology. In this case, we would travel up the topology as - * shown below. - * - * .. kernel-figure:: dp-mst/topology-figure-3.dot - * - * And finally, remove payload #2 by communicating with port #2 through - * sideband transactions. - */ - -/** - * drm_dp_mst_get_mstb_malloc() - Increment the malloc refcount of a branch - * device - * @mstb: The &struct drm_dp_mst_branch to increment the malloc refcount of - * - * Increments &drm_dp_mst_branch.malloc_kref. When - * &drm_dp_mst_branch.malloc_kref reaches 0, the memory allocation for @mstb - * will be released and @mstb may no longer be used. - * - * See also: drm_dp_mst_put_mstb_malloc() - */ -static void -drm_dp_mst_get_mstb_malloc(struct drm_dp_mst_branch *mstb) -{ - kref_get(&mstb->malloc_kref); - drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->malloc_kref)); -} - -/** - * drm_dp_mst_put_mstb_malloc() - Decrement the malloc refcount of a branch - * device - * @mstb: The &struct drm_dp_mst_branch to decrement the malloc refcount of - * - * Decrements &drm_dp_mst_branch.malloc_kref. When - * &drm_dp_mst_branch.malloc_kref reaches 0, the memory allocation for @mstb - * will be released and @mstb may no longer be used. - * - * See also: drm_dp_mst_get_mstb_malloc() - */ -static void -drm_dp_mst_put_mstb_malloc(struct drm_dp_mst_branch *mstb) -{ - drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->malloc_kref) - 1); - kref_put(&mstb->malloc_kref, drm_dp_free_mst_branch_device); -} - -static void drm_dp_free_mst_port(struct kref *kref) -{ - struct drm_dp_mst_port *port = - container_of(kref, struct drm_dp_mst_port, malloc_kref); - - drm_dp_mst_put_mstb_malloc(port->parent); - kfree(port); -} - -/** - * drm_dp_mst_get_port_malloc() - Increment the malloc refcount of an MST port - * @port: The &struct drm_dp_mst_port to increment the malloc refcount of - * - * Increments &drm_dp_mst_port.malloc_kref. When &drm_dp_mst_port.malloc_kref - * reaches 0, the memory allocation for @port will be released and @port may - * no longer be used. - * - * Because @port could potentially be freed at any time by the DP MST helpers - * if &drm_dp_mst_port.malloc_kref reaches 0, including during a call to this - * function, drivers that which to make use of &struct drm_dp_mst_port should - * ensure that they grab at least one main malloc reference to their MST ports - * in &drm_dp_mst_topology_cbs.add_connector. This callback is called before - * there is any chance for &drm_dp_mst_port.malloc_kref to reach 0. - * - * See also: drm_dp_mst_put_port_malloc() - */ -void -drm_dp_mst_get_port_malloc(struct drm_dp_mst_port *port) -{ - kref_get(&port->malloc_kref); - drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->malloc_kref)); -} -EXPORT_SYMBOL(drm_dp_mst_get_port_malloc); - -/** - * drm_dp_mst_put_port_malloc() - Decrement the malloc refcount of an MST port - * @port: The &struct drm_dp_mst_port to decrement the malloc refcount of - * - * Decrements &drm_dp_mst_port.malloc_kref. When &drm_dp_mst_port.malloc_kref - * reaches 0, the memory allocation for @port will be released and @port may - * no longer be used. - * - * See also: drm_dp_mst_get_port_malloc() - */ -void -drm_dp_mst_put_port_malloc(struct drm_dp_mst_port *port) -{ - drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->malloc_kref) - 1); - kref_put(&port->malloc_kref, drm_dp_free_mst_port); -} -EXPORT_SYMBOL(drm_dp_mst_put_port_malloc); - -#if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS) - -#define STACK_DEPTH 8 - -static noinline void -__topology_ref_save(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_topology_ref_history *history, - enum drm_dp_mst_topology_ref_type type) -{ - struct drm_dp_mst_topology_ref_entry *entry = NULL; - depot_stack_handle_t backtrace; - ulong stack_entries[STACK_DEPTH]; - uint n; - int i; - - n = stack_trace_save(stack_entries, ARRAY_SIZE(stack_entries), 1); - backtrace = stack_depot_save(stack_entries, n, GFP_KERNEL); - if (!backtrace) - return; - - /* Try to find an existing entry for this backtrace */ - for (i = 0; i < history->len; i++) { - if (history->entries[i].backtrace == backtrace) { - entry = &history->entries[i]; - break; - } - } - - /* Otherwise add one */ - if (!entry) { - struct drm_dp_mst_topology_ref_entry *new; - int new_len = history->len + 1; - - new = krealloc(history->entries, sizeof(*new) * new_len, - GFP_KERNEL); - if (!new) - return; - - entry = &new[history->len]; - history->len = new_len; - history->entries = new; - - entry->backtrace = backtrace; - entry->type = type; - entry->count = 0; - } - entry->count++; - entry->ts_nsec = ktime_get_ns(); -} - -static int -topology_ref_history_cmp(const void *a, const void *b) -{ - const struct drm_dp_mst_topology_ref_entry *entry_a = a, *entry_b = b; - - if (entry_a->ts_nsec > entry_b->ts_nsec) - return 1; - else if (entry_a->ts_nsec < entry_b->ts_nsec) - return -1; - else - return 0; -} - -static inline const char * -topology_ref_type_to_str(enum drm_dp_mst_topology_ref_type type) -{ - if (type == DRM_DP_MST_TOPOLOGY_REF_GET) - return "get"; - else - return "put"; -} - -static void -__dump_topology_ref_history(struct drm_dp_mst_topology_ref_history *history, - void *ptr, const char *type_str) -{ - struct drm_printer p = drm_debug_printer(DBG_PREFIX); - char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); - int i; - - if (!buf) - return; - - if (!history->len) - goto out; - - /* First, sort the list so that it goes from oldest to newest - * reference entry - */ - sort(history->entries, history->len, sizeof(*history->entries), - topology_ref_history_cmp, NULL); - - drm_printf(&p, "%s (%p) topology count reached 0, dumping history:\n", - type_str, ptr); - - for (i = 0; i < history->len; i++) { - const struct drm_dp_mst_topology_ref_entry *entry = - &history->entries[i]; - u64 ts_nsec = entry->ts_nsec; - u32 rem_nsec = do_div(ts_nsec, 1000000000); - - stack_depot_snprint(entry->backtrace, buf, PAGE_SIZE, 4); - - drm_printf(&p, " %d %ss (last at %5llu.%06u):\n%s", - entry->count, - topology_ref_type_to_str(entry->type), - ts_nsec, rem_nsec / 1000, buf); - } - - /* Now free the history, since this is the only time we expose it */ - kfree(history->entries); -out: - kfree(buf); -} - -static __always_inline void -drm_dp_mst_dump_mstb_topology_history(struct drm_dp_mst_branch *mstb) -{ - __dump_topology_ref_history(&mstb->topology_ref_history, mstb, - "MSTB"); -} - -static __always_inline void -drm_dp_mst_dump_port_topology_history(struct drm_dp_mst_port *port) -{ - __dump_topology_ref_history(&port->topology_ref_history, port, - "Port"); -} - -static __always_inline void -save_mstb_topology_ref(struct drm_dp_mst_branch *mstb, - enum drm_dp_mst_topology_ref_type type) -{ - __topology_ref_save(mstb->mgr, &mstb->topology_ref_history, type); -} - -static __always_inline void -save_port_topology_ref(struct drm_dp_mst_port *port, - enum drm_dp_mst_topology_ref_type type) -{ - __topology_ref_save(port->mgr, &port->topology_ref_history, type); -} - -static inline void -topology_ref_history_lock(struct drm_dp_mst_topology_mgr *mgr) -{ - mutex_lock(&mgr->topology_ref_history_lock); -} - -static inline void -topology_ref_history_unlock(struct drm_dp_mst_topology_mgr *mgr) -{ - mutex_unlock(&mgr->topology_ref_history_lock); -} -#else -static inline void -topology_ref_history_lock(struct drm_dp_mst_topology_mgr *mgr) {} -static inline void -topology_ref_history_unlock(struct drm_dp_mst_topology_mgr *mgr) {} -static inline void -drm_dp_mst_dump_mstb_topology_history(struct drm_dp_mst_branch *mstb) {} -static inline void -drm_dp_mst_dump_port_topology_history(struct drm_dp_mst_port *port) {} -#define save_mstb_topology_ref(mstb, type) -#define save_port_topology_ref(port, type) -#endif - -static void drm_dp_destroy_mst_branch_device(struct kref *kref) -{ - struct drm_dp_mst_branch *mstb = - container_of(kref, struct drm_dp_mst_branch, topology_kref); - struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; - - drm_dp_mst_dump_mstb_topology_history(mstb); - - INIT_LIST_HEAD(&mstb->destroy_next); - - /* - * This can get called under mgr->mutex, so we need to perform the - * actual destruction of the mstb in another worker - */ - mutex_lock(&mgr->delayed_destroy_lock); - list_add(&mstb->destroy_next, &mgr->destroy_branch_device_list); - mutex_unlock(&mgr->delayed_destroy_lock); - queue_work(mgr->delayed_destroy_wq, &mgr->delayed_destroy_work); -} - -/** - * drm_dp_mst_topology_try_get_mstb() - Increment the topology refcount of a - * branch device unless it's zero - * @mstb: &struct drm_dp_mst_branch to increment the topology refcount of - * - * Attempts to grab a topology reference to @mstb, if it hasn't yet been - * removed from the topology (e.g. &drm_dp_mst_branch.topology_kref has - * reached 0). Holding a topology reference implies that a malloc reference - * will be held to @mstb as long as the user holds the topology reference. - * - * Care should be taken to ensure that the user has at least one malloc - * reference to @mstb. If you already have a topology reference to @mstb, you - * should use drm_dp_mst_topology_get_mstb() instead. - * - * See also: - * drm_dp_mst_topology_get_mstb() - * drm_dp_mst_topology_put_mstb() - * - * Returns: - * * 1: A topology reference was grabbed successfully - * * 0: @port is no longer in the topology, no reference was grabbed - */ -static int __must_check -drm_dp_mst_topology_try_get_mstb(struct drm_dp_mst_branch *mstb) -{ - int ret; - - topology_ref_history_lock(mstb->mgr); - ret = kref_get_unless_zero(&mstb->topology_kref); - if (ret) { - drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->topology_kref)); - save_mstb_topology_ref(mstb, DRM_DP_MST_TOPOLOGY_REF_GET); - } - - topology_ref_history_unlock(mstb->mgr); - - return ret; -} - -/** - * drm_dp_mst_topology_get_mstb() - Increment the topology refcount of a - * branch device - * @mstb: The &struct drm_dp_mst_branch to increment the topology refcount of - * - * Increments &drm_dp_mst_branch.topology_refcount without checking whether or - * not it's already reached 0. This is only valid to use in scenarios where - * you are already guaranteed to have at least one active topology reference - * to @mstb. Otherwise, drm_dp_mst_topology_try_get_mstb() must be used. - * - * See also: - * drm_dp_mst_topology_try_get_mstb() - * drm_dp_mst_topology_put_mstb() - */ -static void drm_dp_mst_topology_get_mstb(struct drm_dp_mst_branch *mstb) -{ - topology_ref_history_lock(mstb->mgr); - - save_mstb_topology_ref(mstb, DRM_DP_MST_TOPOLOGY_REF_GET); - WARN_ON(kref_read(&mstb->topology_kref) == 0); - kref_get(&mstb->topology_kref); - drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->topology_kref)); - - topology_ref_history_unlock(mstb->mgr); -} - -/** - * drm_dp_mst_topology_put_mstb() - release a topology reference to a branch - * device - * @mstb: The &struct drm_dp_mst_branch to release the topology reference from - * - * Releases a topology reference from @mstb by decrementing - * &drm_dp_mst_branch.topology_kref. - * - * See also: - * drm_dp_mst_topology_try_get_mstb() - * drm_dp_mst_topology_get_mstb() - */ -static void -drm_dp_mst_topology_put_mstb(struct drm_dp_mst_branch *mstb) -{ - topology_ref_history_lock(mstb->mgr); - - drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->topology_kref) - 1); - save_mstb_topology_ref(mstb, DRM_DP_MST_TOPOLOGY_REF_PUT); - - topology_ref_history_unlock(mstb->mgr); - kref_put(&mstb->topology_kref, drm_dp_destroy_mst_branch_device); -} - -static void drm_dp_destroy_port(struct kref *kref) -{ - struct drm_dp_mst_port *port = - container_of(kref, struct drm_dp_mst_port, topology_kref); - struct drm_dp_mst_topology_mgr *mgr = port->mgr; - - drm_dp_mst_dump_port_topology_history(port); - - /* There's nothing that needs locking to destroy an input port yet */ - if (port->input) { - drm_dp_mst_put_port_malloc(port); - return; - } - - kfree(port->cached_edid); - - /* - * we can't destroy the connector here, as we might be holding the - * mode_config.mutex from an EDID retrieval - */ - mutex_lock(&mgr->delayed_destroy_lock); - list_add(&port->next, &mgr->destroy_port_list); - mutex_unlock(&mgr->delayed_destroy_lock); - queue_work(mgr->delayed_destroy_wq, &mgr->delayed_destroy_work); -} - -/** - * drm_dp_mst_topology_try_get_port() - Increment the topology refcount of a - * port unless it's zero - * @port: &struct drm_dp_mst_port to increment the topology refcount of - * - * Attempts to grab a topology reference to @port, if it hasn't yet been - * removed from the topology (e.g. &drm_dp_mst_port.topology_kref has reached - * 0). Holding a topology reference implies that a malloc reference will be - * held to @port as long as the user holds the topology reference. - * - * Care should be taken to ensure that the user has at least one malloc - * reference to @port. If you already have a topology reference to @port, you - * should use drm_dp_mst_topology_get_port() instead. - * - * See also: - * drm_dp_mst_topology_get_port() - * drm_dp_mst_topology_put_port() - * - * Returns: - * * 1: A topology reference was grabbed successfully - * * 0: @port is no longer in the topology, no reference was grabbed - */ -static int __must_check -drm_dp_mst_topology_try_get_port(struct drm_dp_mst_port *port) -{ - int ret; - - topology_ref_history_lock(port->mgr); - ret = kref_get_unless_zero(&port->topology_kref); - if (ret) { - drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->topology_kref)); - save_port_topology_ref(port, DRM_DP_MST_TOPOLOGY_REF_GET); - } - - topology_ref_history_unlock(port->mgr); - return ret; -} - -/** - * drm_dp_mst_topology_get_port() - Increment the topology refcount of a port - * @port: The &struct drm_dp_mst_port to increment the topology refcount of - * - * Increments &drm_dp_mst_port.topology_refcount without checking whether or - * not it's already reached 0. This is only valid to use in scenarios where - * you are already guaranteed to have at least one active topology reference - * to @port. Otherwise, drm_dp_mst_topology_try_get_port() must be used. - * - * See also: - * drm_dp_mst_topology_try_get_port() - * drm_dp_mst_topology_put_port() - */ -static void drm_dp_mst_topology_get_port(struct drm_dp_mst_port *port) -{ - topology_ref_history_lock(port->mgr); - - WARN_ON(kref_read(&port->topology_kref) == 0); - kref_get(&port->topology_kref); - drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->topology_kref)); - save_port_topology_ref(port, DRM_DP_MST_TOPOLOGY_REF_GET); - - topology_ref_history_unlock(port->mgr); -} - -/** - * drm_dp_mst_topology_put_port() - release a topology reference to a port - * @port: The &struct drm_dp_mst_port to release the topology reference from - * - * Releases a topology reference from @port by decrementing - * &drm_dp_mst_port.topology_kref. - * - * See also: - * drm_dp_mst_topology_try_get_port() - * drm_dp_mst_topology_get_port() - */ -static void drm_dp_mst_topology_put_port(struct drm_dp_mst_port *port) -{ - topology_ref_history_lock(port->mgr); - - drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->topology_kref) - 1); - save_port_topology_ref(port, DRM_DP_MST_TOPOLOGY_REF_PUT); - - topology_ref_history_unlock(port->mgr); - kref_put(&port->topology_kref, drm_dp_destroy_port); -} - -static struct drm_dp_mst_branch * -drm_dp_mst_topology_get_mstb_validated_locked(struct drm_dp_mst_branch *mstb, - struct drm_dp_mst_branch *to_find) -{ - struct drm_dp_mst_port *port; - struct drm_dp_mst_branch *rmstb; - - if (to_find == mstb) - return mstb; - - list_for_each_entry(port, &mstb->ports, next) { - if (port->mstb) { - rmstb = drm_dp_mst_topology_get_mstb_validated_locked( - port->mstb, to_find); - if (rmstb) - return rmstb; - } - } - return NULL; -} - -static struct drm_dp_mst_branch * -drm_dp_mst_topology_get_mstb_validated(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb) -{ - struct drm_dp_mst_branch *rmstb = NULL; - - mutex_lock(&mgr->lock); - if (mgr->mst_primary) { - rmstb = drm_dp_mst_topology_get_mstb_validated_locked( - mgr->mst_primary, mstb); - - if (rmstb && !drm_dp_mst_topology_try_get_mstb(rmstb)) - rmstb = NULL; - } - mutex_unlock(&mgr->lock); - return rmstb; -} - -static struct drm_dp_mst_port * -drm_dp_mst_topology_get_port_validated_locked(struct drm_dp_mst_branch *mstb, - struct drm_dp_mst_port *to_find) -{ - struct drm_dp_mst_port *port, *mport; - - list_for_each_entry(port, &mstb->ports, next) { - if (port == to_find) - return port; - - if (port->mstb) { - mport = drm_dp_mst_topology_get_port_validated_locked( - port->mstb, to_find); - if (mport) - return mport; - } - } - return NULL; -} - -static struct drm_dp_mst_port * -drm_dp_mst_topology_get_port_validated(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port) -{ - struct drm_dp_mst_port *rport = NULL; - - mutex_lock(&mgr->lock); - if (mgr->mst_primary) { - rport = drm_dp_mst_topology_get_port_validated_locked( - mgr->mst_primary, port); - - if (rport && !drm_dp_mst_topology_try_get_port(rport)) - rport = NULL; - } - mutex_unlock(&mgr->lock); - return rport; -} - -static struct drm_dp_mst_port *drm_dp_get_port(struct drm_dp_mst_branch *mstb, u8 port_num) -{ - struct drm_dp_mst_port *port; - int ret; - - list_for_each_entry(port, &mstb->ports, next) { - if (port->port_num == port_num) { - ret = drm_dp_mst_topology_try_get_port(port); - return ret ? port : NULL; - } - } - - return NULL; -} - -/* - * calculate a new RAD for this MST branch device - * if parent has an LCT of 2 then it has 1 nibble of RAD, - * if parent has an LCT of 3 then it has 2 nibbles of RAD, - */ -static u8 drm_dp_calculate_rad(struct drm_dp_mst_port *port, - u8 *rad) -{ - int parent_lct = port->parent->lct; - int shift = 4; - int idx = (parent_lct - 1) / 2; - - if (parent_lct > 1) { - memcpy(rad, port->parent->rad, idx + 1); - shift = (parent_lct % 2) ? 4 : 0; - } else - rad[0] = 0; - - rad[idx] |= port->port_num << shift; - return parent_lct + 1; -} - -static bool drm_dp_mst_is_end_device(u8 pdt, bool mcs) -{ - switch (pdt) { - case DP_PEER_DEVICE_DP_LEGACY_CONV: - case DP_PEER_DEVICE_SST_SINK: - return true; - case DP_PEER_DEVICE_MST_BRANCHING: - /* For sst branch device */ - if (!mcs) - return true; - - return false; - } - return true; -} - -static int -drm_dp_port_set_pdt(struct drm_dp_mst_port *port, u8 new_pdt, - bool new_mcs) -{ - struct drm_dp_mst_topology_mgr *mgr = port->mgr; - struct drm_dp_mst_branch *mstb; - u8 rad[8], lct; - int ret = 0; - - if (port->pdt == new_pdt && port->mcs == new_mcs) - return 0; - - /* Teardown the old pdt, if there is one */ - if (port->pdt != DP_PEER_DEVICE_NONE) { - if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) { - /* - * If the new PDT would also have an i2c bus, - * don't bother with reregistering it - */ - if (new_pdt != DP_PEER_DEVICE_NONE && - drm_dp_mst_is_end_device(new_pdt, new_mcs)) { - port->pdt = new_pdt; - port->mcs = new_mcs; - return 0; - } - - /* remove i2c over sideband */ - drm_dp_mst_unregister_i2c_bus(port); - } else { - mutex_lock(&mgr->lock); - drm_dp_mst_topology_put_mstb(port->mstb); - port->mstb = NULL; - mutex_unlock(&mgr->lock); - } - } - - port->pdt = new_pdt; - port->mcs = new_mcs; - - if (port->pdt != DP_PEER_DEVICE_NONE) { - if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) { - /* add i2c over sideband */ - ret = drm_dp_mst_register_i2c_bus(port); - } else { - lct = drm_dp_calculate_rad(port, rad); - mstb = drm_dp_add_mst_branch_device(lct, rad); - if (!mstb) { - ret = -ENOMEM; - drm_err(mgr->dev, "Failed to create MSTB for port %p", port); - goto out; - } - - mutex_lock(&mgr->lock); - port->mstb = mstb; - mstb->mgr = port->mgr; - mstb->port_parent = port; - - /* - * Make sure this port's memory allocation stays - * around until its child MSTB releases it - */ - drm_dp_mst_get_port_malloc(port); - mutex_unlock(&mgr->lock); - - /* And make sure we send a link address for this */ - ret = 1; - } - } - -out: - if (ret < 0) - port->pdt = DP_PEER_DEVICE_NONE; - return ret; -} - -/** - * drm_dp_mst_dpcd_read() - read a series of bytes from the DPCD via sideband - * @aux: Fake sideband AUX CH - * @offset: address of the (first) register to read - * @buffer: buffer to store the register values - * @size: number of bytes in @buffer - * - * Performs the same functionality for remote devices via - * sideband messaging as drm_dp_dpcd_read() does for local - * devices via actual AUX CH. - * - * Return: Number of bytes read, or negative error code on failure. - */ -ssize_t drm_dp_mst_dpcd_read(struct drm_dp_aux *aux, - unsigned int offset, void *buffer, size_t size) -{ - struct drm_dp_mst_port *port = container_of(aux, struct drm_dp_mst_port, - aux); - - return drm_dp_send_dpcd_read(port->mgr, port, - offset, size, buffer); -} - -/** - * drm_dp_mst_dpcd_write() - write a series of bytes to the DPCD via sideband - * @aux: Fake sideband AUX CH - * @offset: address of the (first) register to write - * @buffer: buffer containing the values to write - * @size: number of bytes in @buffer - * - * Performs the same functionality for remote devices via - * sideband messaging as drm_dp_dpcd_write() does for local - * devices via actual AUX CH. - * - * Return: number of bytes written on success, negative error code on failure. - */ -ssize_t drm_dp_mst_dpcd_write(struct drm_dp_aux *aux, - unsigned int offset, void *buffer, size_t size) -{ - struct drm_dp_mst_port *port = container_of(aux, struct drm_dp_mst_port, - aux); - - return drm_dp_send_dpcd_write(port->mgr, port, - offset, size, buffer); -} - -static int drm_dp_check_mstb_guid(struct drm_dp_mst_branch *mstb, u8 *guid) -{ - int ret = 0; - - memcpy(mstb->guid, guid, 16); - - if (!drm_dp_validate_guid(mstb->mgr, mstb->guid)) { - if (mstb->port_parent) { - ret = drm_dp_send_dpcd_write(mstb->mgr, - mstb->port_parent, - DP_GUID, 16, mstb->guid); - } else { - ret = drm_dp_dpcd_write(mstb->mgr->aux, - DP_GUID, mstb->guid, 16); - } - } - - if (ret < 16 && ret > 0) - return -EPROTO; - - return ret == 16 ? 0 : ret; -} - -static void build_mst_prop_path(const struct drm_dp_mst_branch *mstb, - int pnum, - char *proppath, - size_t proppath_size) -{ - int i; - char temp[8]; - - snprintf(proppath, proppath_size, "mst:%d", mstb->mgr->conn_base_id); - for (i = 0; i < (mstb->lct - 1); i++) { - int shift = (i % 2) ? 0 : 4; - int port_num = (mstb->rad[i / 2] >> shift) & 0xf; - - snprintf(temp, sizeof(temp), "-%d", port_num); - strlcat(proppath, temp, proppath_size); - } - snprintf(temp, sizeof(temp), "-%d", pnum); - strlcat(proppath, temp, proppath_size); -} - -/** - * drm_dp_mst_connector_late_register() - Late MST connector registration - * @connector: The MST connector - * @port: The MST port for this connector - * - * Helper to register the remote aux device for this MST port. Drivers should - * call this from their mst connector's late_register hook to enable MST aux - * devices. - * - * Return: 0 on success, negative error code on failure. - */ -int drm_dp_mst_connector_late_register(struct drm_connector *connector, - struct drm_dp_mst_port *port) -{ - drm_dbg_kms(port->mgr->dev, "registering %s remote bus for %s\n", - port->aux.name, connector->kdev->kobj.name); - - port->aux.dev = connector->kdev; - return drm_dp_aux_register_devnode(&port->aux); -} -EXPORT_SYMBOL(drm_dp_mst_connector_late_register); - -/** - * drm_dp_mst_connector_early_unregister() - Early MST connector unregistration - * @connector: The MST connector - * @port: The MST port for this connector - * - * Helper to unregister the remote aux device for this MST port, registered by - * drm_dp_mst_connector_late_register(). Drivers should call this from their mst - * connector's early_unregister hook. - */ -void drm_dp_mst_connector_early_unregister(struct drm_connector *connector, - struct drm_dp_mst_port *port) -{ - drm_dbg_kms(port->mgr->dev, "unregistering %s remote bus for %s\n", - port->aux.name, connector->kdev->kobj.name); - drm_dp_aux_unregister_devnode(&port->aux); -} -EXPORT_SYMBOL(drm_dp_mst_connector_early_unregister); - -static void -drm_dp_mst_port_add_connector(struct drm_dp_mst_branch *mstb, - struct drm_dp_mst_port *port) -{ - struct drm_dp_mst_topology_mgr *mgr = port->mgr; - char proppath[255]; - int ret; - - build_mst_prop_path(mstb, port->port_num, proppath, sizeof(proppath)); - port->connector = mgr->cbs->add_connector(mgr, port, proppath); - if (!port->connector) { - ret = -ENOMEM; - goto error; - } - - if (port->pdt != DP_PEER_DEVICE_NONE && - drm_dp_mst_is_end_device(port->pdt, port->mcs) && - port->port_num >= DP_MST_LOGICAL_PORT_0) - port->cached_edid = drm_get_edid(port->connector, - &port->aux.ddc); - - drm_connector_register(port->connector); - return; - -error: - drm_err(mgr->dev, "Failed to create connector for port %p: %d\n", port, ret); -} - -/* - * Drop a topology reference, and unlink the port from the in-memory topology - * layout - */ -static void -drm_dp_mst_topology_unlink_port(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port) -{ - mutex_lock(&mgr->lock); - port->parent->num_ports--; - list_del(&port->next); - mutex_unlock(&mgr->lock); - drm_dp_mst_topology_put_port(port); -} - -static struct drm_dp_mst_port * -drm_dp_mst_add_port(struct drm_device *dev, - struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb, u8 port_number) -{ - struct drm_dp_mst_port *port = kzalloc(sizeof(*port), GFP_KERNEL); - - if (!port) - return NULL; - - kref_init(&port->topology_kref); - kref_init(&port->malloc_kref); - port->parent = mstb; - port->port_num = port_number; - port->mgr = mgr; - port->aux.name = "DPMST"; - port->aux.dev = dev->dev; - port->aux.is_remote = true; - - /* initialize the MST downstream port's AUX crc work queue */ - port->aux.drm_dev = dev; - drm_dp_remote_aux_init(&port->aux); - - /* - * Make sure the memory allocation for our parent branch stays - * around until our own memory allocation is released - */ - drm_dp_mst_get_mstb_malloc(mstb); - - return port; -} - -static int -drm_dp_mst_handle_link_address_port(struct drm_dp_mst_branch *mstb, - struct drm_device *dev, - struct drm_dp_link_addr_reply_port *port_msg) -{ - struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; - struct drm_dp_mst_port *port; - int old_ddps = 0, ret; - u8 new_pdt = DP_PEER_DEVICE_NONE; - bool new_mcs = 0; - bool created = false, send_link_addr = false, changed = false; - - port = drm_dp_get_port(mstb, port_msg->port_number); - if (!port) { - port = drm_dp_mst_add_port(dev, mgr, mstb, - port_msg->port_number); - if (!port) - return -ENOMEM; - created = true; - changed = true; - } else if (!port->input && port_msg->input_port && port->connector) { - /* Since port->connector can't be changed here, we create a - * new port if input_port changes from 0 to 1 - */ - drm_dp_mst_topology_unlink_port(mgr, port); - drm_dp_mst_topology_put_port(port); - port = drm_dp_mst_add_port(dev, mgr, mstb, - port_msg->port_number); - if (!port) - return -ENOMEM; - changed = true; - created = true; - } else if (port->input && !port_msg->input_port) { - changed = true; - } else if (port->connector) { - /* We're updating a port that's exposed to userspace, so do it - * under lock - */ - drm_modeset_lock(&mgr->base.lock, NULL); - - old_ddps = port->ddps; - changed = port->ddps != port_msg->ddps || - (port->ddps && - (port->ldps != port_msg->legacy_device_plug_status || - port->dpcd_rev != port_msg->dpcd_revision || - port->mcs != port_msg->mcs || - port->pdt != port_msg->peer_device_type || - port->num_sdp_stream_sinks != - port_msg->num_sdp_stream_sinks)); - } - - port->input = port_msg->input_port; - if (!port->input) - new_pdt = port_msg->peer_device_type; - new_mcs = port_msg->mcs; - port->ddps = port_msg->ddps; - port->ldps = port_msg->legacy_device_plug_status; - port->dpcd_rev = port_msg->dpcd_revision; - port->num_sdp_streams = port_msg->num_sdp_streams; - port->num_sdp_stream_sinks = port_msg->num_sdp_stream_sinks; - - /* manage mstb port lists with mgr lock - take a reference - for this list */ - if (created) { - mutex_lock(&mgr->lock); - drm_dp_mst_topology_get_port(port); - list_add(&port->next, &mstb->ports); - mstb->num_ports++; - mutex_unlock(&mgr->lock); - } - - /* - * Reprobe PBN caps on both hotplug, and when re-probing the link - * for our parent mstb - */ - if (old_ddps != port->ddps || !created) { - if (port->ddps && !port->input) { - ret = drm_dp_send_enum_path_resources(mgr, mstb, - port); - if (ret == 1) - changed = true; - } else { - port->full_pbn = 0; - } - } - - ret = drm_dp_port_set_pdt(port, new_pdt, new_mcs); - if (ret == 1) { - send_link_addr = true; - } else if (ret < 0) { - drm_err(dev, "Failed to change PDT on port %p: %d\n", port, ret); - goto fail; - } - - /* - * If this port wasn't just created, then we're reprobing because - * we're coming out of suspend. In this case, always resend the link - * address if there's an MSTB on this port - */ - if (!created && port->pdt == DP_PEER_DEVICE_MST_BRANCHING && - port->mcs) - send_link_addr = true; - - if (port->connector) - drm_modeset_unlock(&mgr->base.lock); - else if (!port->input) - drm_dp_mst_port_add_connector(mstb, port); - - if (send_link_addr && port->mstb) { - ret = drm_dp_send_link_address(mgr, port->mstb); - if (ret == 1) /* MSTB below us changed */ - changed = true; - else if (ret < 0) - goto fail_put; - } - - /* put reference to this port */ - drm_dp_mst_topology_put_port(port); - return changed; - -fail: - drm_dp_mst_topology_unlink_port(mgr, port); - if (port->connector) - drm_modeset_unlock(&mgr->base.lock); -fail_put: - drm_dp_mst_topology_put_port(port); - return ret; -} - -static void -drm_dp_mst_handle_conn_stat(struct drm_dp_mst_branch *mstb, - struct drm_dp_connection_status_notify *conn_stat) -{ - struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; - struct drm_dp_mst_port *port; - int old_ddps, ret; - u8 new_pdt; - bool new_mcs; - bool dowork = false, create_connector = false; - - port = drm_dp_get_port(mstb, conn_stat->port_number); - if (!port) - return; - - if (port->connector) { - if (!port->input && conn_stat->input_port) { - /* - * We can't remove a connector from an already exposed - * port, so just throw the port out and make sure we - * reprobe the link address of it's parent MSTB - */ - drm_dp_mst_topology_unlink_port(mgr, port); - mstb->link_address_sent = false; - dowork = true; - goto out; - } - - /* Locking is only needed if the port's exposed to userspace */ - drm_modeset_lock(&mgr->base.lock, NULL); - } else if (port->input && !conn_stat->input_port) { - create_connector = true; - /* Reprobe link address so we get num_sdp_streams */ - mstb->link_address_sent = false; - dowork = true; - } - - old_ddps = port->ddps; - port->input = conn_stat->input_port; - port->ldps = conn_stat->legacy_device_plug_status; - port->ddps = conn_stat->displayport_device_plug_status; - - if (old_ddps != port->ddps) { - if (port->ddps && !port->input) - drm_dp_send_enum_path_resources(mgr, mstb, port); - else - port->full_pbn = 0; - } - - new_pdt = port->input ? DP_PEER_DEVICE_NONE : conn_stat->peer_device_type; - new_mcs = conn_stat->message_capability_status; - ret = drm_dp_port_set_pdt(port, new_pdt, new_mcs); - if (ret == 1) { - dowork = true; - } else if (ret < 0) { - drm_err(mgr->dev, "Failed to change PDT for port %p: %d\n", port, ret); - dowork = false; - } - - if (port->connector) - drm_modeset_unlock(&mgr->base.lock); - else if (create_connector) - drm_dp_mst_port_add_connector(mstb, port); - -out: - drm_dp_mst_topology_put_port(port); - if (dowork) - queue_work(system_long_wq, &mstb->mgr->work); -} - -static struct drm_dp_mst_branch *drm_dp_get_mst_branch_device(struct drm_dp_mst_topology_mgr *mgr, - u8 lct, u8 *rad) -{ - struct drm_dp_mst_branch *mstb; - struct drm_dp_mst_port *port; - int i, ret; - /* find the port by iterating down */ - - mutex_lock(&mgr->lock); - mstb = mgr->mst_primary; - - if (!mstb) - goto out; - - for (i = 0; i < lct - 1; i++) { - int shift = (i % 2) ? 0 : 4; - int port_num = (rad[i / 2] >> shift) & 0xf; - - list_for_each_entry(port, &mstb->ports, next) { - if (port->port_num == port_num) { - mstb = port->mstb; - if (!mstb) { - drm_err(mgr->dev, - "failed to lookup MSTB with lct %d, rad %02x\n", - lct, rad[0]); - goto out; - } - - break; - } - } - } - ret = drm_dp_mst_topology_try_get_mstb(mstb); - if (!ret) - mstb = NULL; -out: - mutex_unlock(&mgr->lock); - return mstb; -} - -static struct drm_dp_mst_branch *get_mst_branch_device_by_guid_helper( - struct drm_dp_mst_branch *mstb, - const uint8_t *guid) -{ - struct drm_dp_mst_branch *found_mstb; - struct drm_dp_mst_port *port; - - if (memcmp(mstb->guid, guid, 16) == 0) - return mstb; - - - list_for_each_entry(port, &mstb->ports, next) { - if (!port->mstb) - continue; - - found_mstb = get_mst_branch_device_by_guid_helper(port->mstb, guid); - - if (found_mstb) - return found_mstb; - } - - return NULL; -} - -static struct drm_dp_mst_branch * -drm_dp_get_mst_branch_device_by_guid(struct drm_dp_mst_topology_mgr *mgr, - const uint8_t *guid) -{ - struct drm_dp_mst_branch *mstb; - int ret; - - /* find the port by iterating down */ - mutex_lock(&mgr->lock); - - mstb = get_mst_branch_device_by_guid_helper(mgr->mst_primary, guid); - if (mstb) { - ret = drm_dp_mst_topology_try_get_mstb(mstb); - if (!ret) - mstb = NULL; - } - - mutex_unlock(&mgr->lock); - return mstb; -} - -static int drm_dp_check_and_send_link_address(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb) -{ - struct drm_dp_mst_port *port; - int ret; - bool changed = false; - - if (!mstb->link_address_sent) { - ret = drm_dp_send_link_address(mgr, mstb); - if (ret == 1) - changed = true; - else if (ret < 0) - return ret; - } - - list_for_each_entry(port, &mstb->ports, next) { - struct drm_dp_mst_branch *mstb_child = NULL; - - if (port->input || !port->ddps) - continue; - - if (port->mstb) - mstb_child = drm_dp_mst_topology_get_mstb_validated( - mgr, port->mstb); - - if (mstb_child) { - ret = drm_dp_check_and_send_link_address(mgr, - mstb_child); - drm_dp_mst_topology_put_mstb(mstb_child); - if (ret == 1) - changed = true; - else if (ret < 0) - return ret; - } - } - - return changed; -} - -static void drm_dp_mst_link_probe_work(struct work_struct *work) -{ - struct drm_dp_mst_topology_mgr *mgr = - container_of(work, struct drm_dp_mst_topology_mgr, work); - struct drm_device *dev = mgr->dev; - struct drm_dp_mst_branch *mstb; - int ret; - bool clear_payload_id_table; - - mutex_lock(&mgr->probe_lock); - - mutex_lock(&mgr->lock); - clear_payload_id_table = !mgr->payload_id_table_cleared; - mgr->payload_id_table_cleared = true; - - mstb = mgr->mst_primary; - if (mstb) { - ret = drm_dp_mst_topology_try_get_mstb(mstb); - if (!ret) - mstb = NULL; - } - mutex_unlock(&mgr->lock); - if (!mstb) { - mutex_unlock(&mgr->probe_lock); - return; - } - - /* - * Certain branch devices seem to incorrectly report an available_pbn - * of 0 on downstream sinks, even after clearing the - * DP_PAYLOAD_ALLOCATE_* registers in - * drm_dp_mst_topology_mgr_set_mst(). Namely, the CableMatters USB-C - * 2x DP hub. Sending a CLEAR_PAYLOAD_ID_TABLE message seems to make - * things work again. - */ - if (clear_payload_id_table) { - drm_dbg_kms(dev, "Clearing payload ID table\n"); - drm_dp_send_clear_payload_id_table(mgr, mstb); - } - - ret = drm_dp_check_and_send_link_address(mgr, mstb); - drm_dp_mst_topology_put_mstb(mstb); - - mutex_unlock(&mgr->probe_lock); - if (ret > 0) - drm_kms_helper_hotplug_event(dev); -} - -static bool drm_dp_validate_guid(struct drm_dp_mst_topology_mgr *mgr, - u8 *guid) -{ - u64 salt; - - if (memchr_inv(guid, 0, 16)) - return true; - - salt = get_jiffies_64(); - - memcpy(&guid[0], &salt, sizeof(u64)); - memcpy(&guid[8], &salt, sizeof(u64)); - - return false; -} - -static void build_dpcd_read(struct drm_dp_sideband_msg_tx *msg, - u8 port_num, u32 offset, u8 num_bytes) -{ - struct drm_dp_sideband_msg_req_body req; - - req.req_type = DP_REMOTE_DPCD_READ; - req.u.dpcd_read.port_number = port_num; - req.u.dpcd_read.dpcd_address = offset; - req.u.dpcd_read.num_bytes = num_bytes; - drm_dp_encode_sideband_req(&req, msg); -} - -static int drm_dp_send_sideband_msg(struct drm_dp_mst_topology_mgr *mgr, - bool up, u8 *msg, int len) -{ - int ret; - int regbase = up ? DP_SIDEBAND_MSG_UP_REP_BASE : DP_SIDEBAND_MSG_DOWN_REQ_BASE; - int tosend, total, offset; - int retries = 0; - -retry: - total = len; - offset = 0; - do { - tosend = min3(mgr->max_dpcd_transaction_bytes, 16, total); - - ret = drm_dp_dpcd_write(mgr->aux, regbase + offset, - &msg[offset], - tosend); - if (ret != tosend) { - if (ret == -EIO && retries < 5) { - retries++; - goto retry; - } - drm_dbg_kms(mgr->dev, "failed to dpcd write %d %d\n", tosend, ret); - - return -EIO; - } - offset += tosend; - total -= tosend; - } while (total > 0); - return 0; -} - -static int set_hdr_from_dst_qlock(struct drm_dp_sideband_msg_hdr *hdr, - struct drm_dp_sideband_msg_tx *txmsg) -{ - struct drm_dp_mst_branch *mstb = txmsg->dst; - u8 req_type; - - req_type = txmsg->msg[0] & 0x7f; - if (req_type == DP_CONNECTION_STATUS_NOTIFY || - req_type == DP_RESOURCE_STATUS_NOTIFY || - req_type == DP_CLEAR_PAYLOAD_ID_TABLE) - hdr->broadcast = 1; - else - hdr->broadcast = 0; - hdr->path_msg = txmsg->path_msg; - if (hdr->broadcast) { - hdr->lct = 1; - hdr->lcr = 6; - } else { - hdr->lct = mstb->lct; - hdr->lcr = mstb->lct - 1; - } - - memcpy(hdr->rad, mstb->rad, hdr->lct / 2); - - return 0; -} -/* - * process a single block of the next message in the sideband queue - */ -static int process_single_tx_qlock(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_sideband_msg_tx *txmsg, - bool up) -{ - u8 chunk[48]; - struct drm_dp_sideband_msg_hdr hdr; - int len, space, idx, tosend; - int ret; - - if (txmsg->state == DRM_DP_SIDEBAND_TX_SENT) - return 0; - - memset(&hdr, 0, sizeof(struct drm_dp_sideband_msg_hdr)); - - if (txmsg->state == DRM_DP_SIDEBAND_TX_QUEUED) - txmsg->state = DRM_DP_SIDEBAND_TX_START_SEND; - - /* make hdr from dst mst */ - ret = set_hdr_from_dst_qlock(&hdr, txmsg); - if (ret < 0) - return ret; - - /* amount left to send in this message */ - len = txmsg->cur_len - txmsg->cur_offset; - - /* 48 - sideband msg size - 1 byte for data CRC, x header bytes */ - space = 48 - 1 - drm_dp_calc_sb_hdr_size(&hdr); - - tosend = min(len, space); - if (len == txmsg->cur_len) - hdr.somt = 1; - if (space >= len) - hdr.eomt = 1; - - - hdr.msg_len = tosend + 1; - drm_dp_encode_sideband_msg_hdr(&hdr, chunk, &idx); - memcpy(&chunk[idx], &txmsg->msg[txmsg->cur_offset], tosend); - /* add crc at end */ - drm_dp_crc_sideband_chunk_req(&chunk[idx], tosend); - idx += tosend + 1; - - ret = drm_dp_send_sideband_msg(mgr, up, chunk, idx); - if (ret) { - if (drm_debug_enabled(DRM_UT_DP)) { - struct drm_printer p = drm_debug_printer(DBG_PREFIX); - - drm_printf(&p, "sideband msg failed to send\n"); - drm_dp_mst_dump_sideband_msg_tx(&p, txmsg); - } - return ret; - } - - txmsg->cur_offset += tosend; - if (txmsg->cur_offset == txmsg->cur_len) { - txmsg->state = DRM_DP_SIDEBAND_TX_SENT; - return 1; - } - return 0; -} - -static void process_single_down_tx_qlock(struct drm_dp_mst_topology_mgr *mgr) -{ - struct drm_dp_sideband_msg_tx *txmsg; - int ret; - - WARN_ON(!mutex_is_locked(&mgr->qlock)); - - /* construct a chunk from the first msg in the tx_msg queue */ - if (list_empty(&mgr->tx_msg_downq)) - return; - - txmsg = list_first_entry(&mgr->tx_msg_downq, - struct drm_dp_sideband_msg_tx, next); - ret = process_single_tx_qlock(mgr, txmsg, false); - if (ret < 0) { - drm_dbg_kms(mgr->dev, "failed to send msg in q %d\n", ret); - list_del(&txmsg->next); - txmsg->state = DRM_DP_SIDEBAND_TX_TIMEOUT; - wake_up_all(&mgr->tx_waitq); - } -} - -static void drm_dp_queue_down_tx(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_sideband_msg_tx *txmsg) -{ - mutex_lock(&mgr->qlock); - list_add_tail(&txmsg->next, &mgr->tx_msg_downq); - - if (drm_debug_enabled(DRM_UT_DP)) { - struct drm_printer p = drm_debug_printer(DBG_PREFIX); - - drm_dp_mst_dump_sideband_msg_tx(&p, txmsg); - } - - if (list_is_singular(&mgr->tx_msg_downq)) - process_single_down_tx_qlock(mgr); - mutex_unlock(&mgr->qlock); -} - -static void -drm_dp_dump_link_address(const struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_link_address_ack_reply *reply) -{ - struct drm_dp_link_addr_reply_port *port_reply; - int i; - - for (i = 0; i < reply->nports; i++) { - port_reply = &reply->ports[i]; - drm_dbg_kms(mgr->dev, - "port %d: input %d, pdt: %d, pn: %d, dpcd_rev: %02x, mcs: %d, ddps: %d, ldps %d, sdp %d/%d\n", - i, - port_reply->input_port, - port_reply->peer_device_type, - port_reply->port_number, - port_reply->dpcd_revision, - port_reply->mcs, - port_reply->ddps, - port_reply->legacy_device_plug_status, - port_reply->num_sdp_streams, - port_reply->num_sdp_stream_sinks); - } -} - -static int drm_dp_send_link_address(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb) -{ - struct drm_dp_sideband_msg_tx *txmsg; - struct drm_dp_link_address_ack_reply *reply; - struct drm_dp_mst_port *port, *tmp; - int i, ret, port_mask = 0; - bool changed = false; - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) - return -ENOMEM; - - txmsg->dst = mstb; - build_link_address(txmsg); - - mstb->link_address_sent = true; - drm_dp_queue_down_tx(mgr, txmsg); - - /* FIXME: Actually do some real error handling here */ - ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); - if (ret <= 0) { - drm_err(mgr->dev, "Sending link address failed with %d\n", ret); - goto out; - } - if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { - drm_err(mgr->dev, "link address NAK received\n"); - ret = -EIO; - goto out; - } - - reply = &txmsg->reply.u.link_addr; - drm_dbg_kms(mgr->dev, "link address reply: %d\n", reply->nports); - drm_dp_dump_link_address(mgr, reply); - - ret = drm_dp_check_mstb_guid(mstb, reply->guid); - if (ret) { - char buf[64]; - - drm_dp_mst_rad_to_str(mstb->rad, mstb->lct, buf, sizeof(buf)); - drm_err(mgr->dev, "GUID check on %s failed: %d\n", buf, ret); - goto out; - } - - for (i = 0; i < reply->nports; i++) { - port_mask |= BIT(reply->ports[i].port_number); - ret = drm_dp_mst_handle_link_address_port(mstb, mgr->dev, - &reply->ports[i]); - if (ret == 1) - changed = true; - else if (ret < 0) - goto out; - } - - /* Prune any ports that are currently a part of mstb in our in-memory - * topology, but were not seen in this link address. Usually this - * means that they were removed while the topology was out of sync, - * e.g. during suspend/resume - */ - mutex_lock(&mgr->lock); - list_for_each_entry_safe(port, tmp, &mstb->ports, next) { - if (port_mask & BIT(port->port_num)) - continue; - - drm_dbg_kms(mgr->dev, "port %d was not in link address, removing\n", - port->port_num); - list_del(&port->next); - drm_dp_mst_topology_put_port(port); - changed = true; - } - mutex_unlock(&mgr->lock); - -out: - if (ret <= 0) - mstb->link_address_sent = false; - kfree(txmsg); - return ret < 0 ? ret : changed; -} - -static void -drm_dp_send_clear_payload_id_table(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb) -{ - struct drm_dp_sideband_msg_tx *txmsg; - int ret; - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) - return; - - txmsg->dst = mstb; - build_clear_payload_id_table(txmsg); - - drm_dp_queue_down_tx(mgr, txmsg); - - ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); - if (ret > 0 && txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) - drm_dbg_kms(mgr->dev, "clear payload table id nak received\n"); - - kfree(txmsg); -} - -static int -drm_dp_send_enum_path_resources(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb, - struct drm_dp_mst_port *port) -{ - struct drm_dp_enum_path_resources_ack_reply *path_res; - struct drm_dp_sideband_msg_tx *txmsg; - int ret; - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) - return -ENOMEM; - - txmsg->dst = mstb; - build_enum_path_resources(txmsg, port->port_num); - - drm_dp_queue_down_tx(mgr, txmsg); - - ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); - if (ret > 0) { - ret = 0; - path_res = &txmsg->reply.u.path_resources; - - if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { - drm_dbg_kms(mgr->dev, "enum path resources nak received\n"); - } else { - if (port->port_num != path_res->port_number) - DRM_ERROR("got incorrect port in response\n"); - - drm_dbg_kms(mgr->dev, "enum path resources %d: %d %d\n", - path_res->port_number, - path_res->full_payload_bw_number, - path_res->avail_payload_bw_number); - - /* - * If something changed, make sure we send a - * hotplug - */ - if (port->full_pbn != path_res->full_payload_bw_number || - port->fec_capable != path_res->fec_capable) - ret = 1; - - port->full_pbn = path_res->full_payload_bw_number; - port->fec_capable = path_res->fec_capable; - } - } - - kfree(txmsg); - return ret; -} - -static struct drm_dp_mst_port *drm_dp_get_last_connected_port_to_mstb(struct drm_dp_mst_branch *mstb) -{ - if (!mstb->port_parent) - return NULL; - - if (mstb->port_parent->mstb != mstb) - return mstb->port_parent; - - return drm_dp_get_last_connected_port_to_mstb(mstb->port_parent->parent); -} - -/* - * Searches upwards in the topology starting from mstb to try to find the - * closest available parent of mstb that's still connected to the rest of the - * topology. This can be used in order to perform operations like releasing - * payloads, where the branch device which owned the payload may no longer be - * around and thus would require that the payload on the last living relative - * be freed instead. - */ -static struct drm_dp_mst_branch * -drm_dp_get_last_connected_port_and_mstb(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb, - int *port_num) -{ - struct drm_dp_mst_branch *rmstb = NULL; - struct drm_dp_mst_port *found_port; - - mutex_lock(&mgr->lock); - if (!mgr->mst_primary) - goto out; - - do { - found_port = drm_dp_get_last_connected_port_to_mstb(mstb); - if (!found_port) - break; - - if (drm_dp_mst_topology_try_get_mstb(found_port->parent)) { - rmstb = found_port->parent; - *port_num = found_port->port_num; - } else { - /* Search again, starting from this parent */ - mstb = found_port->parent; - } - } while (!rmstb); -out: - mutex_unlock(&mgr->lock); - return rmstb; -} - -static int drm_dp_payload_send_msg(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, - int id, - int pbn) -{ - struct drm_dp_sideband_msg_tx *txmsg; - struct drm_dp_mst_branch *mstb; - int ret, port_num; - u8 sinks[DRM_DP_MAX_SDP_STREAMS]; - int i; - - port_num = port->port_num; - mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent); - if (!mstb) { - mstb = drm_dp_get_last_connected_port_and_mstb(mgr, - port->parent, - &port_num); - - if (!mstb) - return -EINVAL; - } - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) { - ret = -ENOMEM; - goto fail_put; - } - - for (i = 0; i < port->num_sdp_streams; i++) - sinks[i] = i; - - txmsg->dst = mstb; - build_allocate_payload(txmsg, port_num, - id, - pbn, port->num_sdp_streams, sinks); - - drm_dp_queue_down_tx(mgr, txmsg); - - /* - * FIXME: there is a small chance that between getting the last - * connected mstb and sending the payload message, the last connected - * mstb could also be removed from the topology. In the future, this - * needs to be fixed by restarting the - * drm_dp_get_last_connected_port_and_mstb() search in the event of a - * timeout if the topology is still connected to the system. - */ - ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); - if (ret > 0) { - if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) - ret = -EINVAL; - else - ret = 0; - } - kfree(txmsg); -fail_put: - drm_dp_mst_topology_put_mstb(mstb); - return ret; -} - -int drm_dp_send_power_updown_phy(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, bool power_up) -{ - struct drm_dp_sideband_msg_tx *txmsg; - int ret; - - port = drm_dp_mst_topology_get_port_validated(mgr, port); - if (!port) - return -EINVAL; - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) { - drm_dp_mst_topology_put_port(port); - return -ENOMEM; - } - - txmsg->dst = port->parent; - build_power_updown_phy(txmsg, port->port_num, power_up); - drm_dp_queue_down_tx(mgr, txmsg); - - ret = drm_dp_mst_wait_tx_reply(port->parent, txmsg); - if (ret > 0) { - if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) - ret = -EINVAL; - else - ret = 0; - } - kfree(txmsg); - drm_dp_mst_topology_put_port(port); - - return ret; -} -EXPORT_SYMBOL(drm_dp_send_power_updown_phy); - -int drm_dp_send_query_stream_enc_status(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, - struct drm_dp_query_stream_enc_status_ack_reply *status) -{ - struct drm_dp_sideband_msg_tx *txmsg; - u8 nonce[7]; - int ret; - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) - return -ENOMEM; - - port = drm_dp_mst_topology_get_port_validated(mgr, port); - if (!port) { - ret = -EINVAL; - goto out_get_port; - } - - get_random_bytes(nonce, sizeof(nonce)); - - /* - * "Source device targets the QUERY_STREAM_ENCRYPTION_STATUS message - * transaction at the MST Branch device directly connected to the - * Source" - */ - txmsg->dst = mgr->mst_primary; - - build_query_stream_enc_status(txmsg, port->vcpi.vcpi, nonce); - - drm_dp_queue_down_tx(mgr, txmsg); - - ret = drm_dp_mst_wait_tx_reply(mgr->mst_primary, txmsg); - if (ret < 0) { - goto out; - } else if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { - drm_dbg_kms(mgr->dev, "query encryption status nak received\n"); - ret = -ENXIO; - goto out; - } - - ret = 0; - memcpy(status, &txmsg->reply.u.enc_status, sizeof(*status)); - -out: - drm_dp_mst_topology_put_port(port); -out_get_port: - kfree(txmsg); - return ret; -} -EXPORT_SYMBOL(drm_dp_send_query_stream_enc_status); - -static int drm_dp_create_payload_step1(struct drm_dp_mst_topology_mgr *mgr, - int id, - struct drm_dp_payload *payload) -{ - int ret; - - ret = drm_dp_dpcd_write_payload(mgr, id, payload); - if (ret < 0) { - payload->payload_state = 0; - return ret; - } - payload->payload_state = DP_PAYLOAD_LOCAL; - return 0; -} - -static int drm_dp_create_payload_step2(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, - int id, - struct drm_dp_payload *payload) -{ - int ret; - - ret = drm_dp_payload_send_msg(mgr, port, id, port->vcpi.pbn); - if (ret < 0) - return ret; - payload->payload_state = DP_PAYLOAD_REMOTE; - return ret; -} - -static int drm_dp_destroy_payload_step1(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, - int id, - struct drm_dp_payload *payload) -{ - drm_dbg_kms(mgr->dev, "\n"); - /* it's okay for these to fail */ - if (port) { - drm_dp_payload_send_msg(mgr, port, id, 0); - } - - drm_dp_dpcd_write_payload(mgr, id, payload); - payload->payload_state = DP_PAYLOAD_DELETE_LOCAL; - return 0; -} - -static int drm_dp_destroy_payload_step2(struct drm_dp_mst_topology_mgr *mgr, - int id, - struct drm_dp_payload *payload) -{ - payload->payload_state = 0; - return 0; -} - -/** - * drm_dp_update_payload_part1() - Execute payload update part 1 - * @mgr: manager to use. - * @start_slot: this is the cur slot - * - * NOTE: start_slot is a temporary workaround for non-atomic drivers, - * this will be removed when non-atomic mst helpers are moved out of the helper - * - * This iterates over all proposed virtual channels, and tries to - * allocate space in the link for them. For 0->slots transitions, - * this step just writes the VCPI to the MST device. For slots->0 - * transitions, this writes the updated VCPIs and removes the - * remote VC payloads. - * - * after calling this the driver should generate ACT and payload - * packets. - */ -int drm_dp_update_payload_part1(struct drm_dp_mst_topology_mgr *mgr, int start_slot) -{ - struct drm_dp_payload req_payload; - struct drm_dp_mst_port *port; - int i, j; - int cur_slots = start_slot; - bool skip; - - mutex_lock(&mgr->payload_lock); - for (i = 0; i < mgr->max_payloads; i++) { - struct drm_dp_vcpi *vcpi = mgr->proposed_vcpis[i]; - struct drm_dp_payload *payload = &mgr->payloads[i]; - bool put_port = false; - - /* solve the current payloads - compare to the hw ones - - update the hw view */ - req_payload.start_slot = cur_slots; - if (vcpi) { - port = container_of(vcpi, struct drm_dp_mst_port, - vcpi); - - mutex_lock(&mgr->lock); - skip = !drm_dp_mst_port_downstream_of_branch(port, mgr->mst_primary); - mutex_unlock(&mgr->lock); - - if (skip) { - drm_dbg_kms(mgr->dev, - "Virtual channel %d is not in current topology\n", - i); - continue; - } - /* Validated ports don't matter if we're releasing - * VCPI - */ - if (vcpi->num_slots) { - port = drm_dp_mst_topology_get_port_validated( - mgr, port); - if (!port) { - if (vcpi->num_slots == payload->num_slots) { - cur_slots += vcpi->num_slots; - payload->start_slot = req_payload.start_slot; - continue; - } else { - drm_dbg_kms(mgr->dev, - "Fail:set payload to invalid sink"); - mutex_unlock(&mgr->payload_lock); - return -EINVAL; - } - } - put_port = true; - } - - req_payload.num_slots = vcpi->num_slots; - req_payload.vcpi = vcpi->vcpi; - } else { - port = NULL; - req_payload.num_slots = 0; - } - - payload->start_slot = req_payload.start_slot; - /* work out what is required to happen with this payload */ - if (payload->num_slots != req_payload.num_slots) { - - /* need to push an update for this payload */ - if (req_payload.num_slots) { - drm_dp_create_payload_step1(mgr, vcpi->vcpi, - &req_payload); - payload->num_slots = req_payload.num_slots; - payload->vcpi = req_payload.vcpi; - - } else if (payload->num_slots) { - payload->num_slots = 0; - drm_dp_destroy_payload_step1(mgr, port, - payload->vcpi, - payload); - req_payload.payload_state = - payload->payload_state; - payload->start_slot = 0; - } - payload->payload_state = req_payload.payload_state; - } - cur_slots += req_payload.num_slots; - - if (put_port) - drm_dp_mst_topology_put_port(port); - } - - for (i = 0; i < mgr->max_payloads; /* do nothing */) { - if (mgr->payloads[i].payload_state != DP_PAYLOAD_DELETE_LOCAL) { - i++; - continue; - } - - drm_dbg_kms(mgr->dev, "removing payload %d\n", i); - for (j = i; j < mgr->max_payloads - 1; j++) { - mgr->payloads[j] = mgr->payloads[j + 1]; - mgr->proposed_vcpis[j] = mgr->proposed_vcpis[j + 1]; - - if (mgr->proposed_vcpis[j] && - mgr->proposed_vcpis[j]->num_slots) { - set_bit(j + 1, &mgr->payload_mask); - } else { - clear_bit(j + 1, &mgr->payload_mask); - } - } - - memset(&mgr->payloads[mgr->max_payloads - 1], 0, - sizeof(struct drm_dp_payload)); - mgr->proposed_vcpis[mgr->max_payloads - 1] = NULL; - clear_bit(mgr->max_payloads, &mgr->payload_mask); - } - mutex_unlock(&mgr->payload_lock); - - return 0; -} -EXPORT_SYMBOL(drm_dp_update_payload_part1); - -/** - * drm_dp_update_payload_part2() - Execute payload update part 2 - * @mgr: manager to use. - * - * This iterates over all proposed virtual channels, and tries to - * allocate space in the link for them. For 0->slots transitions, - * this step writes the remote VC payload commands. For slots->0 - * this just resets some internal state. - */ -int drm_dp_update_payload_part2(struct drm_dp_mst_topology_mgr *mgr) -{ - struct drm_dp_mst_port *port; - int i; - int ret = 0; - bool skip; - - mutex_lock(&mgr->payload_lock); - for (i = 0; i < mgr->max_payloads; i++) { - - if (!mgr->proposed_vcpis[i]) - continue; - - port = container_of(mgr->proposed_vcpis[i], struct drm_dp_mst_port, vcpi); - - mutex_lock(&mgr->lock); - skip = !drm_dp_mst_port_downstream_of_branch(port, mgr->mst_primary); - mutex_unlock(&mgr->lock); - - if (skip) - continue; - - drm_dbg_kms(mgr->dev, "payload %d %d\n", i, mgr->payloads[i].payload_state); - if (mgr->payloads[i].payload_state == DP_PAYLOAD_LOCAL) { - ret = drm_dp_create_payload_step2(mgr, port, mgr->proposed_vcpis[i]->vcpi, &mgr->payloads[i]); - } else if (mgr->payloads[i].payload_state == DP_PAYLOAD_DELETE_LOCAL) { - ret = drm_dp_destroy_payload_step2(mgr, mgr->proposed_vcpis[i]->vcpi, &mgr->payloads[i]); - } - if (ret) { - mutex_unlock(&mgr->payload_lock); - return ret; - } - } - mutex_unlock(&mgr->payload_lock); - return 0; -} -EXPORT_SYMBOL(drm_dp_update_payload_part2); - -static int drm_dp_send_dpcd_read(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, - int offset, int size, u8 *bytes) -{ - int ret = 0; - struct drm_dp_sideband_msg_tx *txmsg; - struct drm_dp_mst_branch *mstb; - - mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent); - if (!mstb) - return -EINVAL; - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) { - ret = -ENOMEM; - goto fail_put; - } - - build_dpcd_read(txmsg, port->port_num, offset, size); - txmsg->dst = port->parent; - - drm_dp_queue_down_tx(mgr, txmsg); - - ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); - if (ret < 0) - goto fail_free; - - /* DPCD read should never be NACKed */ - if (txmsg->reply.reply_type == 1) { - drm_err(mgr->dev, "mstb %p port %d: DPCD read on addr 0x%x for %d bytes NAKed\n", - mstb, port->port_num, offset, size); - ret = -EIO; - goto fail_free; - } - - if (txmsg->reply.u.remote_dpcd_read_ack.num_bytes != size) { - ret = -EPROTO; - goto fail_free; - } - - ret = min_t(size_t, txmsg->reply.u.remote_dpcd_read_ack.num_bytes, - size); - memcpy(bytes, txmsg->reply.u.remote_dpcd_read_ack.bytes, ret); - -fail_free: - kfree(txmsg); -fail_put: - drm_dp_mst_topology_put_mstb(mstb); - - return ret; -} - -static int drm_dp_send_dpcd_write(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, - int offset, int size, u8 *bytes) -{ - int ret; - struct drm_dp_sideband_msg_tx *txmsg; - struct drm_dp_mst_branch *mstb; - - mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent); - if (!mstb) - return -EINVAL; - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) { - ret = -ENOMEM; - goto fail_put; - } - - build_dpcd_write(txmsg, port->port_num, offset, size, bytes); - txmsg->dst = mstb; - - drm_dp_queue_down_tx(mgr, txmsg); - - ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); - if (ret > 0) { - if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) - ret = -EIO; - else - ret = size; - } - - kfree(txmsg); -fail_put: - drm_dp_mst_topology_put_mstb(mstb); - return ret; -} - -static int drm_dp_encode_up_ack_reply(struct drm_dp_sideband_msg_tx *msg, u8 req_type) -{ - struct drm_dp_sideband_msg_reply_body reply; - - reply.reply_type = DP_SIDEBAND_REPLY_ACK; - reply.req_type = req_type; - drm_dp_encode_sideband_reply(&reply, msg); - return 0; -} - -static int drm_dp_send_up_ack_reply(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb, - int req_type, bool broadcast) -{ - struct drm_dp_sideband_msg_tx *txmsg; - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) - return -ENOMEM; - - txmsg->dst = mstb; - drm_dp_encode_up_ack_reply(txmsg, req_type); - - mutex_lock(&mgr->qlock); - /* construct a chunk from the first msg in the tx_msg queue */ - process_single_tx_qlock(mgr, txmsg, true); - mutex_unlock(&mgr->qlock); - - kfree(txmsg); - return 0; -} - -/** - * drm_dp_get_vc_payload_bw - get the VC payload BW for an MST link - * @mgr: The &drm_dp_mst_topology_mgr to use - * @link_rate: link rate in 10kbits/s units - * @link_lane_count: lane count - * - * Calculate the total bandwidth of a MultiStream Transport link. The returned - * value is in units of PBNs/(timeslots/1 MTP). This value can be used to - * convert the number of PBNs required for a given stream to the number of - * timeslots this stream requires in each MTP. - */ -int drm_dp_get_vc_payload_bw(const struct drm_dp_mst_topology_mgr *mgr, - int link_rate, int link_lane_count) -{ - if (link_rate == 0 || link_lane_count == 0) - drm_dbg_kms(mgr->dev, "invalid link rate/lane count: (%d / %d)\n", - link_rate, link_lane_count); - - /* See DP v2.0 2.6.4.2, VCPayload_Bandwidth_for_OneTimeSlotPer_MTP_Allocation */ - return link_rate * link_lane_count / 54000; -} -EXPORT_SYMBOL(drm_dp_get_vc_payload_bw); - -/** - * drm_dp_read_mst_cap() - check whether or not a sink supports MST - * @aux: The DP AUX channel to use - * @dpcd: A cached copy of the DPCD capabilities for this sink - * - * Returns: %True if the sink supports MST, %false otherwise - */ -bool drm_dp_read_mst_cap(struct drm_dp_aux *aux, - const u8 dpcd[DP_RECEIVER_CAP_SIZE]) -{ - u8 mstm_cap; - - if (dpcd[DP_DPCD_REV] < DP_DPCD_REV_12) - return false; - - if (drm_dp_dpcd_readb(aux, DP_MSTM_CAP, &mstm_cap) != 1) - return false; - - return mstm_cap & DP_MST_CAP; -} -EXPORT_SYMBOL(drm_dp_read_mst_cap); - -/** - * drm_dp_mst_topology_mgr_set_mst() - Set the MST state for a topology manager - * @mgr: manager to set state for - * @mst_state: true to enable MST on this connector - false to disable. - * - * This is called by the driver when it detects an MST capable device plugged - * into a DP MST capable port, or when a DP MST capable device is unplugged. - */ -int drm_dp_mst_topology_mgr_set_mst(struct drm_dp_mst_topology_mgr *mgr, bool mst_state) -{ - int ret = 0; - struct drm_dp_mst_branch *mstb = NULL; - - mutex_lock(&mgr->payload_lock); - mutex_lock(&mgr->lock); - if (mst_state == mgr->mst_state) - goto out_unlock; - - mgr->mst_state = mst_state; - /* set the device into MST mode */ - if (mst_state) { - struct drm_dp_payload reset_pay; - int lane_count; - int link_rate; - - WARN_ON(mgr->mst_primary); - - /* get dpcd info */ - ret = drm_dp_read_dpcd_caps(mgr->aux, mgr->dpcd); - if (ret < 0) { - drm_dbg_kms(mgr->dev, "%s: failed to read DPCD, ret %d\n", - mgr->aux->name, ret); - goto out_unlock; - } - - lane_count = min_t(int, mgr->dpcd[2] & DP_MAX_LANE_COUNT_MASK, mgr->max_lane_count); - link_rate = min_t(int, drm_dp_bw_code_to_link_rate(mgr->dpcd[1]), mgr->max_link_rate); - mgr->pbn_div = drm_dp_get_vc_payload_bw(mgr, - link_rate, - lane_count); - if (mgr->pbn_div == 0) { - ret = -EINVAL; - goto out_unlock; - } - - /* add initial branch device at LCT 1 */ - mstb = drm_dp_add_mst_branch_device(1, NULL); - if (mstb == NULL) { - ret = -ENOMEM; - goto out_unlock; - } - mstb->mgr = mgr; - - /* give this the main reference */ - mgr->mst_primary = mstb; - drm_dp_mst_topology_get_mstb(mgr->mst_primary); - - ret = drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, - DP_MST_EN | - DP_UP_REQ_EN | - DP_UPSTREAM_IS_SRC); - if (ret < 0) - goto out_unlock; - - reset_pay.start_slot = 0; - reset_pay.num_slots = 0x3f; - drm_dp_dpcd_write_payload(mgr, 0, &reset_pay); - - queue_work(system_long_wq, &mgr->work); - - ret = 0; - } else { - /* disable MST on the device */ - mstb = mgr->mst_primary; - mgr->mst_primary = NULL; - /* this can fail if the device is gone */ - drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, 0); - ret = 0; - memset(mgr->payloads, 0, - mgr->max_payloads * sizeof(mgr->payloads[0])); - memset(mgr->proposed_vcpis, 0, - mgr->max_payloads * sizeof(mgr->proposed_vcpis[0])); - mgr->payload_mask = 0; - set_bit(0, &mgr->payload_mask); - mgr->vcpi_mask = 0; - mgr->payload_id_table_cleared = false; - } - -out_unlock: - mutex_unlock(&mgr->lock); - mutex_unlock(&mgr->payload_lock); - if (mstb) - drm_dp_mst_topology_put_mstb(mstb); - return ret; - -} -EXPORT_SYMBOL(drm_dp_mst_topology_mgr_set_mst); - -static void -drm_dp_mst_topology_mgr_invalidate_mstb(struct drm_dp_mst_branch *mstb) -{ - struct drm_dp_mst_port *port; - - /* The link address will need to be re-sent on resume */ - mstb->link_address_sent = false; - - list_for_each_entry(port, &mstb->ports, next) - if (port->mstb) - drm_dp_mst_topology_mgr_invalidate_mstb(port->mstb); -} - -/** - * drm_dp_mst_topology_mgr_suspend() - suspend the MST manager - * @mgr: manager to suspend - * - * This function tells the MST device that we can't handle UP messages - * anymore. This should stop it from sending any since we are suspended. - */ -void drm_dp_mst_topology_mgr_suspend(struct drm_dp_mst_topology_mgr *mgr) -{ - mutex_lock(&mgr->lock); - drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, - DP_MST_EN | DP_UPSTREAM_IS_SRC); - mutex_unlock(&mgr->lock); - flush_work(&mgr->up_req_work); - flush_work(&mgr->work); - flush_work(&mgr->delayed_destroy_work); - - mutex_lock(&mgr->lock); - if (mgr->mst_state && mgr->mst_primary) - drm_dp_mst_topology_mgr_invalidate_mstb(mgr->mst_primary); - mutex_unlock(&mgr->lock); -} -EXPORT_SYMBOL(drm_dp_mst_topology_mgr_suspend); - -/** - * drm_dp_mst_topology_mgr_resume() - resume the MST manager - * @mgr: manager to resume - * @sync: whether or not to perform topology reprobing synchronously - * - * This will fetch DPCD and see if the device is still there, - * if it is, it will rewrite the MSTM control bits, and return. - * - * If the device fails this returns -1, and the driver should do - * a full MST reprobe, in case we were undocked. - * - * During system resume (where it is assumed that the driver will be calling - * drm_atomic_helper_resume()) this function should be called beforehand with - * @sync set to true. In contexts like runtime resume where the driver is not - * expected to be calling drm_atomic_helper_resume(), this function should be - * called with @sync set to false in order to avoid deadlocking. - * - * Returns: -1 if the MST topology was removed while we were suspended, 0 - * otherwise. - */ -int drm_dp_mst_topology_mgr_resume(struct drm_dp_mst_topology_mgr *mgr, - bool sync) -{ - int ret; - u8 guid[16]; - - mutex_lock(&mgr->lock); - if (!mgr->mst_primary) - goto out_fail; - - ret = drm_dp_dpcd_read(mgr->aux, DP_DPCD_REV, mgr->dpcd, - DP_RECEIVER_CAP_SIZE); - if (ret != DP_RECEIVER_CAP_SIZE) { - drm_dbg_kms(mgr->dev, "dpcd read failed - undocked during suspend?\n"); - goto out_fail; - } - - ret = drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, - DP_MST_EN | - DP_UP_REQ_EN | - DP_UPSTREAM_IS_SRC); - if (ret < 0) { - drm_dbg_kms(mgr->dev, "mst write failed - undocked during suspend?\n"); - goto out_fail; - } - - /* Some hubs forget their guids after they resume */ - ret = drm_dp_dpcd_read(mgr->aux, DP_GUID, guid, 16); - if (ret != 16) { - drm_dbg_kms(mgr->dev, "dpcd read failed - undocked during suspend?\n"); - goto out_fail; - } - - ret = drm_dp_check_mstb_guid(mgr->mst_primary, guid); - if (ret) { - drm_dbg_kms(mgr->dev, "check mstb failed - undocked during suspend?\n"); - goto out_fail; - } - - /* - * For the final step of resuming the topology, we need to bring the - * state of our in-memory topology back into sync with reality. So, - * restart the probing process as if we're probing a new hub - */ - queue_work(system_long_wq, &mgr->work); - mutex_unlock(&mgr->lock); - - if (sync) { - drm_dbg_kms(mgr->dev, - "Waiting for link probe work to finish re-syncing topology...\n"); - flush_work(&mgr->work); - } - - return 0; - -out_fail: - mutex_unlock(&mgr->lock); - return -1; -} -EXPORT_SYMBOL(drm_dp_mst_topology_mgr_resume); - -static bool -drm_dp_get_one_sb_msg(struct drm_dp_mst_topology_mgr *mgr, bool up, - struct drm_dp_mst_branch **mstb) -{ - int len; - u8 replyblock[32]; - int replylen, curreply; - int ret; - u8 hdrlen; - struct drm_dp_sideband_msg_hdr hdr; - struct drm_dp_sideband_msg_rx *msg = - up ? &mgr->up_req_recv : &mgr->down_rep_recv; - int basereg = up ? DP_SIDEBAND_MSG_UP_REQ_BASE : - DP_SIDEBAND_MSG_DOWN_REP_BASE; - - if (!up) - *mstb = NULL; - - len = min(mgr->max_dpcd_transaction_bytes, 16); - ret = drm_dp_dpcd_read(mgr->aux, basereg, replyblock, len); - if (ret != len) { - drm_dbg_kms(mgr->dev, "failed to read DPCD down rep %d %d\n", len, ret); - return false; - } - - ret = drm_dp_decode_sideband_msg_hdr(mgr, &hdr, replyblock, len, &hdrlen); - if (ret == false) { - print_hex_dump(KERN_DEBUG, "failed hdr", DUMP_PREFIX_NONE, 16, - 1, replyblock, len, false); - drm_dbg_kms(mgr->dev, "ERROR: failed header\n"); - return false; - } - - if (!up) { - /* Caller is responsible for giving back this reference */ - *mstb = drm_dp_get_mst_branch_device(mgr, hdr.lct, hdr.rad); - if (!*mstb) { - drm_dbg_kms(mgr->dev, "Got MST reply from unknown device %d\n", hdr.lct); - return false; - } - } - - if (!drm_dp_sideband_msg_set_header(msg, &hdr, hdrlen)) { - drm_dbg_kms(mgr->dev, "sideband msg set header failed %d\n", replyblock[0]); - return false; - } - - replylen = min(msg->curchunk_len, (u8)(len - hdrlen)); - ret = drm_dp_sideband_append_payload(msg, replyblock + hdrlen, replylen); - if (!ret) { - drm_dbg_kms(mgr->dev, "sideband msg build failed %d\n", replyblock[0]); - return false; - } - - replylen = msg->curchunk_len + msg->curchunk_hdrlen - len; - curreply = len; - while (replylen > 0) { - len = min3(replylen, mgr->max_dpcd_transaction_bytes, 16); - ret = drm_dp_dpcd_read(mgr->aux, basereg + curreply, - replyblock, len); - if (ret != len) { - drm_dbg_kms(mgr->dev, "failed to read a chunk (len %d, ret %d)\n", - len, ret); - return false; - } - - ret = drm_dp_sideband_append_payload(msg, replyblock, len); - if (!ret) { - drm_dbg_kms(mgr->dev, "failed to build sideband msg\n"); - return false; - } - - curreply += len; - replylen -= len; - } - return true; -} - -static int drm_dp_mst_handle_down_rep(struct drm_dp_mst_topology_mgr *mgr) -{ - struct drm_dp_sideband_msg_tx *txmsg; - struct drm_dp_mst_branch *mstb = NULL; - struct drm_dp_sideband_msg_rx *msg = &mgr->down_rep_recv; - - if (!drm_dp_get_one_sb_msg(mgr, false, &mstb)) - goto out; - - /* Multi-packet message transmission, don't clear the reply */ - if (!msg->have_eomt) - goto out; - - /* find the message */ - mutex_lock(&mgr->qlock); - txmsg = list_first_entry_or_null(&mgr->tx_msg_downq, - struct drm_dp_sideband_msg_tx, next); - mutex_unlock(&mgr->qlock); - - /* Were we actually expecting a response, and from this mstb? */ - if (!txmsg || txmsg->dst != mstb) { - struct drm_dp_sideband_msg_hdr *hdr; - - hdr = &msg->initial_hdr; - drm_dbg_kms(mgr->dev, "Got MST reply with no msg %p %d %d %02x %02x\n", - mstb, hdr->seqno, hdr->lct, hdr->rad[0], msg->msg[0]); - goto out_clear_reply; - } - - drm_dp_sideband_parse_reply(mgr, msg, &txmsg->reply); - - if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { - drm_dbg_kms(mgr->dev, - "Got NAK reply: req 0x%02x (%s), reason 0x%02x (%s), nak data 0x%02x\n", - txmsg->reply.req_type, - drm_dp_mst_req_type_str(txmsg->reply.req_type), - txmsg->reply.u.nak.reason, - drm_dp_mst_nak_reason_str(txmsg->reply.u.nak.reason), - txmsg->reply.u.nak.nak_data); - } - - memset(msg, 0, sizeof(struct drm_dp_sideband_msg_rx)); - drm_dp_mst_topology_put_mstb(mstb); - - mutex_lock(&mgr->qlock); - txmsg->state = DRM_DP_SIDEBAND_TX_RX; - list_del(&txmsg->next); - mutex_unlock(&mgr->qlock); - - wake_up_all(&mgr->tx_waitq); - - return 0; - -out_clear_reply: - memset(msg, 0, sizeof(struct drm_dp_sideband_msg_rx)); -out: - if (mstb) - drm_dp_mst_topology_put_mstb(mstb); - - return 0; -} - -static inline bool -drm_dp_mst_process_up_req(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_pending_up_req *up_req) -{ - struct drm_dp_mst_branch *mstb = NULL; - struct drm_dp_sideband_msg_req_body *msg = &up_req->msg; - struct drm_dp_sideband_msg_hdr *hdr = &up_req->hdr; - bool hotplug = false; - - if (hdr->broadcast) { - const u8 *guid = NULL; - - if (msg->req_type == DP_CONNECTION_STATUS_NOTIFY) - guid = msg->u.conn_stat.guid; - else if (msg->req_type == DP_RESOURCE_STATUS_NOTIFY) - guid = msg->u.resource_stat.guid; - - if (guid) - mstb = drm_dp_get_mst_branch_device_by_guid(mgr, guid); - } else { - mstb = drm_dp_get_mst_branch_device(mgr, hdr->lct, hdr->rad); - } - - if (!mstb) { - drm_dbg_kms(mgr->dev, "Got MST reply from unknown device %d\n", hdr->lct); - return false; - } - - /* TODO: Add missing handler for DP_RESOURCE_STATUS_NOTIFY events */ - if (msg->req_type == DP_CONNECTION_STATUS_NOTIFY) { - drm_dp_mst_handle_conn_stat(mstb, &msg->u.conn_stat); - hotplug = true; - } - - drm_dp_mst_topology_put_mstb(mstb); - return hotplug; -} - -static void drm_dp_mst_up_req_work(struct work_struct *work) -{ - struct drm_dp_mst_topology_mgr *mgr = - container_of(work, struct drm_dp_mst_topology_mgr, - up_req_work); - struct drm_dp_pending_up_req *up_req; - bool send_hotplug = false; - - mutex_lock(&mgr->probe_lock); - while (true) { - mutex_lock(&mgr->up_req_lock); - up_req = list_first_entry_or_null(&mgr->up_req_list, - struct drm_dp_pending_up_req, - next); - if (up_req) - list_del(&up_req->next); - mutex_unlock(&mgr->up_req_lock); - - if (!up_req) - break; - - send_hotplug |= drm_dp_mst_process_up_req(mgr, up_req); - kfree(up_req); - } - mutex_unlock(&mgr->probe_lock); - - if (send_hotplug) - drm_kms_helper_hotplug_event(mgr->dev); -} - -static int drm_dp_mst_handle_up_req(struct drm_dp_mst_topology_mgr *mgr) -{ - struct drm_dp_pending_up_req *up_req; - - if (!drm_dp_get_one_sb_msg(mgr, true, NULL)) - goto out; - - if (!mgr->up_req_recv.have_eomt) - return 0; - - up_req = kzalloc(sizeof(*up_req), GFP_KERNEL); - if (!up_req) - return -ENOMEM; - - INIT_LIST_HEAD(&up_req->next); - - drm_dp_sideband_parse_req(mgr, &mgr->up_req_recv, &up_req->msg); - - if (up_req->msg.req_type != DP_CONNECTION_STATUS_NOTIFY && - up_req->msg.req_type != DP_RESOURCE_STATUS_NOTIFY) { - drm_dbg_kms(mgr->dev, "Received unknown up req type, ignoring: %x\n", - up_req->msg.req_type); - kfree(up_req); - goto out; - } - - drm_dp_send_up_ack_reply(mgr, mgr->mst_primary, up_req->msg.req_type, - false); - - if (up_req->msg.req_type == DP_CONNECTION_STATUS_NOTIFY) { - const struct drm_dp_connection_status_notify *conn_stat = - &up_req->msg.u.conn_stat; - - drm_dbg_kms(mgr->dev, "Got CSN: pn: %d ldps:%d ddps: %d mcs: %d ip: %d pdt: %d\n", - conn_stat->port_number, - conn_stat->legacy_device_plug_status, - conn_stat->displayport_device_plug_status, - conn_stat->message_capability_status, - conn_stat->input_port, - conn_stat->peer_device_type); - } else if (up_req->msg.req_type == DP_RESOURCE_STATUS_NOTIFY) { - const struct drm_dp_resource_status_notify *res_stat = - &up_req->msg.u.resource_stat; - - drm_dbg_kms(mgr->dev, "Got RSN: pn: %d avail_pbn %d\n", - res_stat->port_number, - res_stat->available_pbn); - } - - up_req->hdr = mgr->up_req_recv.initial_hdr; - mutex_lock(&mgr->up_req_lock); - list_add_tail(&up_req->next, &mgr->up_req_list); - mutex_unlock(&mgr->up_req_lock); - queue_work(system_long_wq, &mgr->up_req_work); - -out: - memset(&mgr->up_req_recv, 0, sizeof(struct drm_dp_sideband_msg_rx)); - return 0; -} - -/** - * drm_dp_mst_hpd_irq() - MST hotplug IRQ notify - * @mgr: manager to notify irq for. - * @esi: 4 bytes from SINK_COUNT_ESI - * @handled: whether the hpd interrupt was consumed or not - * - * This should be called from the driver when it detects a short IRQ, - * along with the value of the DEVICE_SERVICE_IRQ_VECTOR_ESI0. The - * topology manager will process the sideband messages received as a result - * of this. - */ -int drm_dp_mst_hpd_irq(struct drm_dp_mst_topology_mgr *mgr, u8 *esi, bool *handled) -{ - int ret = 0; - int sc; - *handled = false; - sc = DP_GET_SINK_COUNT(esi[0]); - - if (sc != mgr->sink_count) { - mgr->sink_count = sc; - *handled = true; - } - - if (esi[1] & DP_DOWN_REP_MSG_RDY) { - ret = drm_dp_mst_handle_down_rep(mgr); - *handled = true; - } - - if (esi[1] & DP_UP_REQ_MSG_RDY) { - ret |= drm_dp_mst_handle_up_req(mgr); - *handled = true; - } - - drm_dp_mst_kick_tx(mgr); - return ret; -} -EXPORT_SYMBOL(drm_dp_mst_hpd_irq); - -/** - * drm_dp_mst_detect_port() - get connection status for an MST port - * @connector: DRM connector for this port - * @ctx: The acquisition context to use for grabbing locks - * @mgr: manager for this port - * @port: pointer to a port - * - * This returns the current connection state for a port. - */ -int -drm_dp_mst_detect_port(struct drm_connector *connector, - struct drm_modeset_acquire_ctx *ctx, - struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port) -{ - int ret; - - /* we need to search for the port in the mgr in case it's gone */ - port = drm_dp_mst_topology_get_port_validated(mgr, port); - if (!port) - return connector_status_disconnected; - - ret = drm_modeset_lock(&mgr->base.lock, ctx); - if (ret) - goto out; - - ret = connector_status_disconnected; - - if (!port->ddps) - goto out; - - switch (port->pdt) { - case DP_PEER_DEVICE_NONE: - break; - case DP_PEER_DEVICE_MST_BRANCHING: - if (!port->mcs) - ret = connector_status_connected; - break; - - case DP_PEER_DEVICE_SST_SINK: - ret = connector_status_connected; - /* for logical ports - cache the EDID */ - if (port->port_num >= DP_MST_LOGICAL_PORT_0 && !port->cached_edid) - port->cached_edid = drm_get_edid(connector, &port->aux.ddc); - break; - case DP_PEER_DEVICE_DP_LEGACY_CONV: - if (port->ldps) - ret = connector_status_connected; - break; - } -out: - drm_dp_mst_topology_put_port(port); - return ret; -} -EXPORT_SYMBOL(drm_dp_mst_detect_port); - -/** - * drm_dp_mst_get_edid() - get EDID for an MST port - * @connector: toplevel connector to get EDID for - * @mgr: manager for this port - * @port: unverified pointer to a port. - * - * This returns an EDID for the port connected to a connector, - * It validates the pointer still exists so the caller doesn't require a - * reference. - */ -struct edid *drm_dp_mst_get_edid(struct drm_connector *connector, struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port) -{ - struct edid *edid = NULL; - - /* we need to search for the port in the mgr in case it's gone */ - port = drm_dp_mst_topology_get_port_validated(mgr, port); - if (!port) - return NULL; - - if (port->cached_edid) - edid = drm_edid_duplicate(port->cached_edid); - else { - edid = drm_get_edid(connector, &port->aux.ddc); - } - port->has_audio = drm_detect_monitor_audio(edid); - drm_dp_mst_topology_put_port(port); - return edid; -} -EXPORT_SYMBOL(drm_dp_mst_get_edid); - -/** - * drm_dp_find_vcpi_slots() - Find VCPI slots for this PBN value - * @mgr: manager to use - * @pbn: payload bandwidth to convert into slots. - * - * Calculate the number of VCPI slots that will be required for the given PBN - * value. This function is deprecated, and should not be used in atomic - * drivers. - * - * RETURNS: - * The total slots required for this port, or error. - */ -int drm_dp_find_vcpi_slots(struct drm_dp_mst_topology_mgr *mgr, - int pbn) -{ - int num_slots; - - num_slots = DIV_ROUND_UP(pbn, mgr->pbn_div); - - /* max. time slots - one slot for MTP header */ - if (num_slots > 63) - return -ENOSPC; - return num_slots; -} -EXPORT_SYMBOL(drm_dp_find_vcpi_slots); - -static int drm_dp_init_vcpi(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_vcpi *vcpi, int pbn, int slots) -{ - int ret; - - vcpi->pbn = pbn; - vcpi->aligned_pbn = slots * mgr->pbn_div; - vcpi->num_slots = slots; - - ret = drm_dp_mst_assign_payload_id(mgr, vcpi); - if (ret < 0) - return ret; - return 0; -} - -/** - * drm_dp_atomic_find_vcpi_slots() - Find and add VCPI slots to the state - * @state: global atomic state - * @mgr: MST topology manager for the port - * @port: port to find vcpi slots for - * @pbn: bandwidth required for the mode in PBN - * @pbn_div: divider for DSC mode that takes FEC into account - * - * Allocates VCPI slots to @port, replacing any previous VCPI allocations it - * may have had. Any atomic drivers which support MST must call this function - * in their &drm_encoder_helper_funcs.atomic_check() callback to change the - * current VCPI allocation for the new state, but only when - * &drm_crtc_state.mode_changed or &drm_crtc_state.connectors_changed is set - * to ensure compatibility with userspace applications that still use the - * legacy modesetting UAPI. - * - * Allocations set by this function are not checked against the bandwidth - * restraints of @mgr until the driver calls drm_dp_mst_atomic_check(). - * - * Additionally, it is OK to call this function multiple times on the same - * @port as needed. It is not OK however, to call this function and - * drm_dp_atomic_release_vcpi_slots() in the same atomic check phase. - * - * See also: - * drm_dp_atomic_release_vcpi_slots() - * drm_dp_mst_atomic_check() - * - * Returns: - * Total slots in the atomic state assigned for this port, or a negative error - * code if the port no longer exists - */ -int drm_dp_atomic_find_vcpi_slots(struct drm_atomic_state *state, - struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, int pbn, - int pbn_div) -{ - struct drm_dp_mst_topology_state *topology_state; - struct drm_dp_vcpi_allocation *pos, *vcpi = NULL; - int prev_slots, prev_bw, req_slots; - - topology_state = drm_atomic_get_mst_topology_state(state, mgr); - if (IS_ERR(topology_state)) - return PTR_ERR(topology_state); - - /* Find the current allocation for this port, if any */ - list_for_each_entry(pos, &topology_state->vcpis, next) { - if (pos->port == port) { - vcpi = pos; - prev_slots = vcpi->vcpi; - prev_bw = vcpi->pbn; - - /* - * This should never happen, unless the driver tries - * releasing and allocating the same VCPI allocation, - * which is an error - */ - if (WARN_ON(!prev_slots)) { - drm_err(mgr->dev, - "cannot allocate and release VCPI on [MST PORT:%p] in the same state\n", - port); - return -EINVAL; - } - - break; - } - } - if (!vcpi) { - prev_slots = 0; - prev_bw = 0; - } - - if (pbn_div <= 0) - pbn_div = mgr->pbn_div; - - req_slots = DIV_ROUND_UP(pbn, pbn_div); - - drm_dbg_atomic(mgr->dev, "[CONNECTOR:%d:%s] [MST PORT:%p] VCPI %d -> %d\n", - port->connector->base.id, port->connector->name, - port, prev_slots, req_slots); - drm_dbg_atomic(mgr->dev, "[CONNECTOR:%d:%s] [MST PORT:%p] PBN %d -> %d\n", - port->connector->base.id, port->connector->name, - port, prev_bw, pbn); - - /* Add the new allocation to the state */ - if (!vcpi) { - vcpi = kzalloc(sizeof(*vcpi), GFP_KERNEL); - if (!vcpi) - return -ENOMEM; - - drm_dp_mst_get_port_malloc(port); - vcpi->port = port; - list_add(&vcpi->next, &topology_state->vcpis); - } - vcpi->vcpi = req_slots; - vcpi->pbn = pbn; - - return req_slots; -} -EXPORT_SYMBOL(drm_dp_atomic_find_vcpi_slots); - -/** - * drm_dp_atomic_release_vcpi_slots() - Release allocated vcpi slots - * @state: global atomic state - * @mgr: MST topology manager for the port - * @port: The port to release the VCPI slots from - * - * Releases any VCPI slots that have been allocated to a port in the atomic - * state. Any atomic drivers which support MST must call this function in - * their &drm_connector_helper_funcs.atomic_check() callback when the - * connector will no longer have VCPI allocated (e.g. because its CRTC was - * removed) when it had VCPI allocated in the previous atomic state. - * - * It is OK to call this even if @port has been removed from the system. - * Additionally, it is OK to call this function multiple times on the same - * @port as needed. It is not OK however, to call this function and - * drm_dp_atomic_find_vcpi_slots() on the same @port in a single atomic check - * phase. - * - * See also: - * drm_dp_atomic_find_vcpi_slots() - * drm_dp_mst_atomic_check() - * - * Returns: - * 0 if all slots for this port were added back to - * &drm_dp_mst_topology_state.avail_slots or negative error code - */ -int drm_dp_atomic_release_vcpi_slots(struct drm_atomic_state *state, - struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port) -{ - struct drm_dp_mst_topology_state *topology_state; - struct drm_dp_vcpi_allocation *pos; - bool found = false; - - topology_state = drm_atomic_get_mst_topology_state(state, mgr); - if (IS_ERR(topology_state)) - return PTR_ERR(topology_state); - - list_for_each_entry(pos, &topology_state->vcpis, next) { - if (pos->port == port) { - found = true; - break; - } - } - if (WARN_ON(!found)) { - drm_err(mgr->dev, "no VCPI for [MST PORT:%p] found in mst state %p\n", - port, &topology_state->base); - return -EINVAL; - } - - drm_dbg_atomic(mgr->dev, "[MST PORT:%p] VCPI %d -> 0\n", port, pos->vcpi); - if (pos->vcpi) { - drm_dp_mst_put_port_malloc(port); - pos->vcpi = 0; - pos->pbn = 0; - } - - return 0; -} -EXPORT_SYMBOL(drm_dp_atomic_release_vcpi_slots); - -/** - * drm_dp_mst_update_slots() - updates the slot info depending on the DP ecoding format - * @mst_state: mst_state to update - * @link_encoding_cap: the ecoding format on the link - */ -void drm_dp_mst_update_slots(struct drm_dp_mst_topology_state *mst_state, uint8_t link_encoding_cap) -{ - if (link_encoding_cap == DP_CAP_ANSI_128B132B) { - mst_state->total_avail_slots = 64; - mst_state->start_slot = 0; - } else { - mst_state->total_avail_slots = 63; - mst_state->start_slot = 1; - } - - DRM_DEBUG_KMS("%s encoding format on mst_state 0x%p\n", - (link_encoding_cap == DP_CAP_ANSI_128B132B) ? "128b/132b":"8b/10b", - mst_state); -} -EXPORT_SYMBOL(drm_dp_mst_update_slots); - -/** - * drm_dp_mst_allocate_vcpi() - Allocate a virtual channel - * @mgr: manager for this port - * @port: port to allocate a virtual channel for. - * @pbn: payload bandwidth number to request - * @slots: returned number of slots for this PBN. - */ -bool drm_dp_mst_allocate_vcpi(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, int pbn, int slots) -{ - int ret; - - if (slots < 0) - return false; - - port = drm_dp_mst_topology_get_port_validated(mgr, port); - if (!port) - return false; - - if (port->vcpi.vcpi > 0) { - drm_dbg_kms(mgr->dev, - "payload: vcpi %d already allocated for pbn %d - requested pbn %d\n", - port->vcpi.vcpi, port->vcpi.pbn, pbn); - if (pbn == port->vcpi.pbn) { - drm_dp_mst_topology_put_port(port); - return true; - } - } - - ret = drm_dp_init_vcpi(mgr, &port->vcpi, pbn, slots); - if (ret) { - drm_dbg_kms(mgr->dev, "failed to init vcpi slots=%d ret=%d\n", - DIV_ROUND_UP(pbn, mgr->pbn_div), ret); - drm_dp_mst_topology_put_port(port); - goto out; - } - drm_dbg_kms(mgr->dev, "initing vcpi for pbn=%d slots=%d\n", pbn, port->vcpi.num_slots); - - /* Keep port allocated until its payload has been removed */ - drm_dp_mst_get_port_malloc(port); - drm_dp_mst_topology_put_port(port); - return true; -out: - return false; -} -EXPORT_SYMBOL(drm_dp_mst_allocate_vcpi); - -int drm_dp_mst_get_vcpi_slots(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port) -{ - int slots = 0; - - port = drm_dp_mst_topology_get_port_validated(mgr, port); - if (!port) - return slots; - - slots = port->vcpi.num_slots; - drm_dp_mst_topology_put_port(port); - return slots; -} -EXPORT_SYMBOL(drm_dp_mst_get_vcpi_slots); - -/** - * drm_dp_mst_reset_vcpi_slots() - Reset number of slots to 0 for VCPI - * @mgr: manager for this port - * @port: unverified pointer to a port. - * - * This just resets the number of slots for the ports VCPI for later programming. - */ -void drm_dp_mst_reset_vcpi_slots(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port) -{ - /* - * A port with VCPI will remain allocated until its VCPI is - * released, no verified ref needed - */ - - port->vcpi.num_slots = 0; -} -EXPORT_SYMBOL(drm_dp_mst_reset_vcpi_slots); - -/** - * drm_dp_mst_deallocate_vcpi() - deallocate a VCPI - * @mgr: manager for this port - * @port: port to deallocate vcpi for - * - * This can be called unconditionally, regardless of whether - * drm_dp_mst_allocate_vcpi() succeeded or not. - */ -void drm_dp_mst_deallocate_vcpi(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port) -{ - bool skip; - - if (!port->vcpi.vcpi) - return; - - mutex_lock(&mgr->lock); - skip = !drm_dp_mst_port_downstream_of_branch(port, mgr->mst_primary); - mutex_unlock(&mgr->lock); - - if (skip) - return; - - drm_dp_mst_put_payload_id(mgr, port->vcpi.vcpi); - port->vcpi.num_slots = 0; - port->vcpi.pbn = 0; - port->vcpi.aligned_pbn = 0; - port->vcpi.vcpi = 0; - drm_dp_mst_put_port_malloc(port); -} -EXPORT_SYMBOL(drm_dp_mst_deallocate_vcpi); - -static int drm_dp_dpcd_write_payload(struct drm_dp_mst_topology_mgr *mgr, - int id, struct drm_dp_payload *payload) -{ - u8 payload_alloc[3], status; - int ret; - int retries = 0; - - drm_dp_dpcd_writeb(mgr->aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, - DP_PAYLOAD_TABLE_UPDATED); - - payload_alloc[0] = id; - payload_alloc[1] = payload->start_slot; - payload_alloc[2] = payload->num_slots; - - ret = drm_dp_dpcd_write(mgr->aux, DP_PAYLOAD_ALLOCATE_SET, payload_alloc, 3); - if (ret != 3) { - drm_dbg_kms(mgr->dev, "failed to write payload allocation %d\n", ret); - goto fail; - } - -retry: - ret = drm_dp_dpcd_readb(mgr->aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, &status); - if (ret < 0) { - drm_dbg_kms(mgr->dev, "failed to read payload table status %d\n", ret); - goto fail; - } - - if (!(status & DP_PAYLOAD_TABLE_UPDATED)) { - retries++; - if (retries < 20) { - usleep_range(10000, 20000); - goto retry; - } - drm_dbg_kms(mgr->dev, "status not set after read payload table status %d\n", - status); - ret = -EINVAL; - goto fail; - } - ret = 0; -fail: - return ret; -} - -static int do_get_act_status(struct drm_dp_aux *aux) -{ - int ret; - u8 status; - - ret = drm_dp_dpcd_readb(aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, &status); - if (ret < 0) - return ret; - - return status; -} - -/** - * drm_dp_check_act_status() - Polls for ACT handled status. - * @mgr: manager to use - * - * Tries waiting for the MST hub to finish updating it's payload table by - * polling for the ACT handled bit for up to 3 seconds (yes-some hubs really - * take that long). - * - * Returns: - * 0 if the ACT was handled in time, negative error code on failure. - */ -int drm_dp_check_act_status(struct drm_dp_mst_topology_mgr *mgr) -{ - /* - * There doesn't seem to be any recommended retry count or timeout in - * the MST specification. Since some hubs have been observed to take - * over 1 second to update their payload allocations under certain - * conditions, we use a rather large timeout value. - */ - const int timeout_ms = 3000; - int ret, status; - - ret = readx_poll_timeout(do_get_act_status, mgr->aux, status, - status & DP_PAYLOAD_ACT_HANDLED || status < 0, - 200, timeout_ms * USEC_PER_MSEC); - if (ret < 0 && status >= 0) { - drm_err(mgr->dev, "Failed to get ACT after %dms, last status: %02x\n", - timeout_ms, status); - return -EINVAL; - } else if (status < 0) { - /* - * Failure here isn't unexpected - the hub may have - * just been unplugged - */ - drm_dbg_kms(mgr->dev, "Failed to read payload table status: %d\n", status); - return status; - } - - return 0; -} -EXPORT_SYMBOL(drm_dp_check_act_status); - -/** - * drm_dp_calc_pbn_mode() - Calculate the PBN for a mode. - * @clock: dot clock for the mode - * @bpp: bpp for the mode. - * @dsc: DSC mode. If true, bpp has units of 1/16 of a bit per pixel - * - * This uses the formula in the spec to calculate the PBN value for a mode. - */ -int drm_dp_calc_pbn_mode(int clock, int bpp, bool dsc) -{ - /* - * margin 5300ppm + 300ppm ~ 0.6% as per spec, factor is 1.006 - * The unit of 54/64Mbytes/sec is an arbitrary unit chosen based on - * common multiplier to render an integer PBN for all link rate/lane - * counts combinations - * calculate - * peak_kbps *= (1006/1000) - * peak_kbps *= (64/54) - * peak_kbps *= 8 convert to bytes - * - * If the bpp is in units of 1/16, further divide by 16. Put this - * factor in the numerator rather than the denominator to avoid - * integer overflow - */ - - if (dsc) - return DIV_ROUND_UP_ULL(mul_u32_u32(clock * (bpp / 16), 64 * 1006), - 8 * 54 * 1000 * 1000); - - return DIV_ROUND_UP_ULL(mul_u32_u32(clock * bpp, 64 * 1006), - 8 * 54 * 1000 * 1000); -} -EXPORT_SYMBOL(drm_dp_calc_pbn_mode); - -/* we want to kick the TX after we've ack the up/down IRQs. */ -static void drm_dp_mst_kick_tx(struct drm_dp_mst_topology_mgr *mgr) -{ - queue_work(system_long_wq, &mgr->tx_work); -} - -/* - * Helper function for parsing DP device types into convenient strings - * for use with dp_mst_topology - */ -static const char *pdt_to_string(u8 pdt) -{ - switch (pdt) { - case DP_PEER_DEVICE_NONE: - return "NONE"; - case DP_PEER_DEVICE_SOURCE_OR_SST: - return "SOURCE OR SST"; - case DP_PEER_DEVICE_MST_BRANCHING: - return "MST BRANCHING"; - case DP_PEER_DEVICE_SST_SINK: - return "SST SINK"; - case DP_PEER_DEVICE_DP_LEGACY_CONV: - return "DP LEGACY CONV"; - default: - return "ERR"; - } -} - -static void drm_dp_mst_dump_mstb(struct seq_file *m, - struct drm_dp_mst_branch *mstb) -{ - struct drm_dp_mst_port *port; - int tabs = mstb->lct; - char prefix[10]; - int i; - - for (i = 0; i < tabs; i++) - prefix[i] = '\t'; - prefix[i] = '\0'; - - seq_printf(m, "%smstb - [%p]: num_ports: %d\n", prefix, mstb, mstb->num_ports); - list_for_each_entry(port, &mstb->ports, next) { - seq_printf(m, "%sport %d - [%p] (%s - %s): ddps: %d, ldps: %d, sdp: %d/%d, fec: %s, conn: %p\n", - prefix, - port->port_num, - port, - port->input ? "input" : "output", - pdt_to_string(port->pdt), - port->ddps, - port->ldps, - port->num_sdp_streams, - port->num_sdp_stream_sinks, - port->fec_capable ? "true" : "false", - port->connector); - if (port->mstb) - drm_dp_mst_dump_mstb(m, port->mstb); - } -} - -#define DP_PAYLOAD_TABLE_SIZE 64 - -static bool dump_dp_payload_table(struct drm_dp_mst_topology_mgr *mgr, - char *buf) -{ - int i; - - for (i = 0; i < DP_PAYLOAD_TABLE_SIZE; i += 16) { - if (drm_dp_dpcd_read(mgr->aux, - DP_PAYLOAD_TABLE_UPDATE_STATUS + i, - &buf[i], 16) != 16) - return false; - } - return true; -} - -static void fetch_monitor_name(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, char *name, - int namelen) -{ - struct edid *mst_edid; - - mst_edid = drm_dp_mst_get_edid(port->connector, mgr, port); - drm_edid_get_monitor_name(mst_edid, name, namelen); -} - -/** - * drm_dp_mst_dump_topology(): dump topology to seq file. - * @m: seq_file to dump output to - * @mgr: manager to dump current topology for. - * - * helper to dump MST topology to a seq file for debugfs. - */ -void drm_dp_mst_dump_topology(struct seq_file *m, - struct drm_dp_mst_topology_mgr *mgr) -{ - int i; - struct drm_dp_mst_port *port; - - mutex_lock(&mgr->lock); - if (mgr->mst_primary) - drm_dp_mst_dump_mstb(m, mgr->mst_primary); - - /* dump VCPIs */ - mutex_unlock(&mgr->lock); - - mutex_lock(&mgr->payload_lock); - seq_printf(m, "\n*** VCPI Info ***\n"); - seq_printf(m, "payload_mask: %lx, vcpi_mask: %lx, max_payloads: %d\n", mgr->payload_mask, mgr->vcpi_mask, mgr->max_payloads); - - seq_printf(m, "\n| idx | port # | vcp_id | # slots | sink name |\n"); - for (i = 0; i < mgr->max_payloads; i++) { - if (mgr->proposed_vcpis[i]) { - char name[14]; - - port = container_of(mgr->proposed_vcpis[i], struct drm_dp_mst_port, vcpi); - fetch_monitor_name(mgr, port, name, sizeof(name)); - seq_printf(m, "%10d%10d%10d%10d%20s\n", - i, - port->port_num, - port->vcpi.vcpi, - port->vcpi.num_slots, - (*name != 0) ? name : "Unknown"); - } else - seq_printf(m, "%6d - Unused\n", i); - } - seq_printf(m, "\n*** Payload Info ***\n"); - seq_printf(m, "| idx | state | start slot | # slots |\n"); - for (i = 0; i < mgr->max_payloads; i++) { - seq_printf(m, "%10d%10d%15d%10d\n", - i, - mgr->payloads[i].payload_state, - mgr->payloads[i].start_slot, - mgr->payloads[i].num_slots); - } - mutex_unlock(&mgr->payload_lock); - - seq_printf(m, "\n*** DPCD Info ***\n"); - mutex_lock(&mgr->lock); - if (mgr->mst_primary) { - u8 buf[DP_PAYLOAD_TABLE_SIZE]; - int ret; - - ret = drm_dp_dpcd_read(mgr->aux, DP_DPCD_REV, buf, DP_RECEIVER_CAP_SIZE); - if (ret) { - seq_printf(m, "dpcd read failed\n"); - goto out; - } - seq_printf(m, "dpcd: %*ph\n", DP_RECEIVER_CAP_SIZE, buf); - - ret = drm_dp_dpcd_read(mgr->aux, DP_FAUX_CAP, buf, 2); - if (ret) { - seq_printf(m, "faux/mst read failed\n"); - goto out; - } - seq_printf(m, "faux/mst: %*ph\n", 2, buf); - - ret = drm_dp_dpcd_read(mgr->aux, DP_MSTM_CTRL, buf, 1); - if (ret) { - seq_printf(m, "mst ctrl read failed\n"); - goto out; - } - seq_printf(m, "mst ctrl: %*ph\n", 1, buf); - - /* dump the standard OUI branch header */ - ret = drm_dp_dpcd_read(mgr->aux, DP_BRANCH_OUI, buf, DP_BRANCH_OUI_HEADER_SIZE); - if (ret) { - seq_printf(m, "branch oui read failed\n"); - goto out; - } - seq_printf(m, "branch oui: %*phN devid: ", 3, buf); - - for (i = 0x3; i < 0x8 && buf[i]; i++) - seq_printf(m, "%c", buf[i]); - seq_printf(m, " revision: hw: %x.%x sw: %x.%x\n", - buf[0x9] >> 4, buf[0x9] & 0xf, buf[0xa], buf[0xb]); - if (dump_dp_payload_table(mgr, buf)) - seq_printf(m, "payload table: %*ph\n", DP_PAYLOAD_TABLE_SIZE, buf); - } - -out: - mutex_unlock(&mgr->lock); - -} -EXPORT_SYMBOL(drm_dp_mst_dump_topology); - -static void drm_dp_tx_work(struct work_struct *work) -{ - struct drm_dp_mst_topology_mgr *mgr = container_of(work, struct drm_dp_mst_topology_mgr, tx_work); - - mutex_lock(&mgr->qlock); - if (!list_empty(&mgr->tx_msg_downq)) - process_single_down_tx_qlock(mgr); - mutex_unlock(&mgr->qlock); -} - -static inline void -drm_dp_delayed_destroy_port(struct drm_dp_mst_port *port) -{ - drm_dp_port_set_pdt(port, DP_PEER_DEVICE_NONE, port->mcs); - - if (port->connector) { - drm_connector_unregister(port->connector); - drm_connector_put(port->connector); - } - - drm_dp_mst_put_port_malloc(port); -} - -static inline void -drm_dp_delayed_destroy_mstb(struct drm_dp_mst_branch *mstb) -{ - struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; - struct drm_dp_mst_port *port, *port_tmp; - struct drm_dp_sideband_msg_tx *txmsg, *txmsg_tmp; - bool wake_tx = false; - - mutex_lock(&mgr->lock); - list_for_each_entry_safe(port, port_tmp, &mstb->ports, next) { - list_del(&port->next); - drm_dp_mst_topology_put_port(port); - } - mutex_unlock(&mgr->lock); - - /* drop any tx slot msg */ - mutex_lock(&mstb->mgr->qlock); - list_for_each_entry_safe(txmsg, txmsg_tmp, &mgr->tx_msg_downq, next) { - if (txmsg->dst != mstb) - continue; - - txmsg->state = DRM_DP_SIDEBAND_TX_TIMEOUT; - list_del(&txmsg->next); - wake_tx = true; - } - mutex_unlock(&mstb->mgr->qlock); - - if (wake_tx) - wake_up_all(&mstb->mgr->tx_waitq); - - drm_dp_mst_put_mstb_malloc(mstb); -} - -static void drm_dp_delayed_destroy_work(struct work_struct *work) -{ - struct drm_dp_mst_topology_mgr *mgr = - container_of(work, struct drm_dp_mst_topology_mgr, - delayed_destroy_work); - bool send_hotplug = false, go_again; - - /* - * Not a regular list traverse as we have to drop the destroy - * connector lock before destroying the mstb/port, to avoid AB->BA - * ordering between this lock and the config mutex. - */ - do { - go_again = false; - - for (;;) { - struct drm_dp_mst_branch *mstb; - - mutex_lock(&mgr->delayed_destroy_lock); - mstb = list_first_entry_or_null(&mgr->destroy_branch_device_list, - struct drm_dp_mst_branch, - destroy_next); - if (mstb) - list_del(&mstb->destroy_next); - mutex_unlock(&mgr->delayed_destroy_lock); - - if (!mstb) - break; - - drm_dp_delayed_destroy_mstb(mstb); - go_again = true; - } - - for (;;) { - struct drm_dp_mst_port *port; - - mutex_lock(&mgr->delayed_destroy_lock); - port = list_first_entry_or_null(&mgr->destroy_port_list, - struct drm_dp_mst_port, - next); - if (port) - list_del(&port->next); - mutex_unlock(&mgr->delayed_destroy_lock); - - if (!port) - break; - - drm_dp_delayed_destroy_port(port); - send_hotplug = true; - go_again = true; - } - } while (go_again); - - if (send_hotplug) - drm_kms_helper_hotplug_event(mgr->dev); -} - -static struct drm_private_state * -drm_dp_mst_duplicate_state(struct drm_private_obj *obj) -{ - struct drm_dp_mst_topology_state *state, *old_state = - to_dp_mst_topology_state(obj->state); - struct drm_dp_vcpi_allocation *pos, *vcpi; - - state = kmemdup(old_state, sizeof(*state), GFP_KERNEL); - if (!state) - return NULL; - - __drm_atomic_helper_private_obj_duplicate_state(obj, &state->base); - - INIT_LIST_HEAD(&state->vcpis); - - list_for_each_entry(pos, &old_state->vcpis, next) { - /* Prune leftover freed VCPI allocations */ - if (!pos->vcpi) - continue; - - vcpi = kmemdup(pos, sizeof(*vcpi), GFP_KERNEL); - if (!vcpi) - goto fail; - - drm_dp_mst_get_port_malloc(vcpi->port); - list_add(&vcpi->next, &state->vcpis); - } - - return &state->base; - -fail: - list_for_each_entry_safe(pos, vcpi, &state->vcpis, next) { - drm_dp_mst_put_port_malloc(pos->port); - kfree(pos); - } - kfree(state); - - return NULL; -} - -static void drm_dp_mst_destroy_state(struct drm_private_obj *obj, - struct drm_private_state *state) -{ - struct drm_dp_mst_topology_state *mst_state = - to_dp_mst_topology_state(state); - struct drm_dp_vcpi_allocation *pos, *tmp; - - list_for_each_entry_safe(pos, tmp, &mst_state->vcpis, next) { - /* We only keep references to ports with non-zero VCPIs */ - if (pos->vcpi) - drm_dp_mst_put_port_malloc(pos->port); - kfree(pos); - } - - kfree(mst_state); -} - -static bool drm_dp_mst_port_downstream_of_branch(struct drm_dp_mst_port *port, - struct drm_dp_mst_branch *branch) -{ - while (port->parent) { - if (port->parent == branch) - return true; - - if (port->parent->port_parent) - port = port->parent->port_parent; - else - break; - } - return false; -} - -static int -drm_dp_mst_atomic_check_port_bw_limit(struct drm_dp_mst_port *port, - struct drm_dp_mst_topology_state *state); - -static int -drm_dp_mst_atomic_check_mstb_bw_limit(struct drm_dp_mst_branch *mstb, - struct drm_dp_mst_topology_state *state) -{ - struct drm_dp_vcpi_allocation *vcpi; - struct drm_dp_mst_port *port; - int pbn_used = 0, ret; - bool found = false; - - /* Check that we have at least one port in our state that's downstream - * of this branch, otherwise we can skip this branch - */ - list_for_each_entry(vcpi, &state->vcpis, next) { - if (!vcpi->pbn || - !drm_dp_mst_port_downstream_of_branch(vcpi->port, mstb)) - continue; - - found = true; - break; - } - if (!found) - return 0; - - if (mstb->port_parent) - drm_dbg_atomic(mstb->mgr->dev, - "[MSTB:%p] [MST PORT:%p] Checking bandwidth limits on [MSTB:%p]\n", - mstb->port_parent->parent, mstb->port_parent, mstb); - else - drm_dbg_atomic(mstb->mgr->dev, "[MSTB:%p] Checking bandwidth limits\n", mstb); - - list_for_each_entry(port, &mstb->ports, next) { - ret = drm_dp_mst_atomic_check_port_bw_limit(port, state); - if (ret < 0) - return ret; - - pbn_used += ret; - } - - return pbn_used; -} - -static int -drm_dp_mst_atomic_check_port_bw_limit(struct drm_dp_mst_port *port, - struct drm_dp_mst_topology_state *state) -{ - struct drm_dp_vcpi_allocation *vcpi; - int pbn_used = 0; - - if (port->pdt == DP_PEER_DEVICE_NONE) - return 0; - - if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) { - bool found = false; - - list_for_each_entry(vcpi, &state->vcpis, next) { - if (vcpi->port != port) - continue; - if (!vcpi->pbn) - return 0; - - found = true; - break; - } - if (!found) - return 0; - - /* - * This could happen if the sink deasserted its HPD line, but - * the branch device still reports it as attached (PDT != NONE). - */ - if (!port->full_pbn) { - drm_dbg_atomic(port->mgr->dev, - "[MSTB:%p] [MST PORT:%p] no BW available for the port\n", - port->parent, port); - return -EINVAL; - } - - pbn_used = vcpi->pbn; - } else { - pbn_used = drm_dp_mst_atomic_check_mstb_bw_limit(port->mstb, - state); - if (pbn_used <= 0) - return pbn_used; - } - - if (pbn_used > port->full_pbn) { - drm_dbg_atomic(port->mgr->dev, - "[MSTB:%p] [MST PORT:%p] required PBN of %d exceeds port limit of %d\n", - port->parent, port, pbn_used, port->full_pbn); - return -ENOSPC; - } - - drm_dbg_atomic(port->mgr->dev, "[MSTB:%p] [MST PORT:%p] uses %d out of %d PBN\n", - port->parent, port, pbn_used, port->full_pbn); - - return pbn_used; -} - -static inline int -drm_dp_mst_atomic_check_vcpi_alloc_limit(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_topology_state *mst_state) -{ - struct drm_dp_vcpi_allocation *vcpi; - int avail_slots = mst_state->total_avail_slots, payload_count = 0; - - list_for_each_entry(vcpi, &mst_state->vcpis, next) { - /* Releasing VCPI is always OK-even if the port is gone */ - if (!vcpi->vcpi) { - drm_dbg_atomic(mgr->dev, "[MST PORT:%p] releases all VCPI slots\n", - vcpi->port); - continue; - } - - drm_dbg_atomic(mgr->dev, "[MST PORT:%p] requires %d vcpi slots\n", - vcpi->port, vcpi->vcpi); - - avail_slots -= vcpi->vcpi; - if (avail_slots < 0) { - drm_dbg_atomic(mgr->dev, - "[MST PORT:%p] not enough VCPI slots in mst state %p (avail=%d)\n", - vcpi->port, mst_state, avail_slots + vcpi->vcpi); - return -ENOSPC; - } - - if (++payload_count > mgr->max_payloads) { - drm_dbg_atomic(mgr->dev, - "[MST MGR:%p] state %p has too many payloads (max=%d)\n", - mgr, mst_state, mgr->max_payloads); - return -EINVAL; - } - } - drm_dbg_atomic(mgr->dev, "[MST MGR:%p] mst state %p VCPI avail=%d used=%d\n", - mgr, mst_state, avail_slots, mst_state->total_avail_slots - avail_slots); - - return 0; -} - -/** - * drm_dp_mst_add_affected_dsc_crtcs - * @state: Pointer to the new struct drm_dp_mst_topology_state - * @mgr: MST topology manager - * - * Whenever there is a change in mst topology - * DSC configuration would have to be recalculated - * therefore we need to trigger modeset on all affected - * CRTCs in that topology - * - * See also: - * drm_dp_mst_atomic_enable_dsc() - */ -int drm_dp_mst_add_affected_dsc_crtcs(struct drm_atomic_state *state, struct drm_dp_mst_topology_mgr *mgr) -{ - struct drm_dp_mst_topology_state *mst_state; - struct drm_dp_vcpi_allocation *pos; - struct drm_connector *connector; - struct drm_connector_state *conn_state; - struct drm_crtc *crtc; - struct drm_crtc_state *crtc_state; - - mst_state = drm_atomic_get_mst_topology_state(state, mgr); - - if (IS_ERR(mst_state)) - return -EINVAL; - - list_for_each_entry(pos, &mst_state->vcpis, next) { - - connector = pos->port->connector; - - if (!connector) - return -EINVAL; - - conn_state = drm_atomic_get_connector_state(state, connector); - - if (IS_ERR(conn_state)) - return PTR_ERR(conn_state); - - crtc = conn_state->crtc; - - if (!crtc) - continue; - - if (!drm_dp_mst_dsc_aux_for_port(pos->port)) - continue; - - crtc_state = drm_atomic_get_crtc_state(mst_state->base.state, crtc); - - if (IS_ERR(crtc_state)) - return PTR_ERR(crtc_state); - - drm_dbg_atomic(mgr->dev, "[MST MGR:%p] Setting mode_changed flag on CRTC %p\n", - mgr, crtc); - - crtc_state->mode_changed = true; - } - return 0; -} -EXPORT_SYMBOL(drm_dp_mst_add_affected_dsc_crtcs); - -/** - * drm_dp_mst_atomic_enable_dsc - Set DSC Enable Flag to On/Off - * @state: Pointer to the new drm_atomic_state - * @port: Pointer to the affected MST Port - * @pbn: Newly recalculated bw required for link with DSC enabled - * @pbn_div: Divider to calculate correct number of pbn per slot - * @enable: Boolean flag to enable or disable DSC on the port - * - * This function enables DSC on the given Port - * by recalculating its vcpi from pbn provided - * and sets dsc_enable flag to keep track of which - * ports have DSC enabled - * - */ -int drm_dp_mst_atomic_enable_dsc(struct drm_atomic_state *state, - struct drm_dp_mst_port *port, - int pbn, int pbn_div, - bool enable) -{ - struct drm_dp_mst_topology_state *mst_state; - struct drm_dp_vcpi_allocation *pos; - bool found = false; - int vcpi = 0; - - mst_state = drm_atomic_get_mst_topology_state(state, port->mgr); - - if (IS_ERR(mst_state)) - return PTR_ERR(mst_state); - - list_for_each_entry(pos, &mst_state->vcpis, next) { - if (pos->port == port) { - found = true; - break; - } - } - - if (!found) { - drm_dbg_atomic(state->dev, - "[MST PORT:%p] Couldn't find VCPI allocation in mst state %p\n", - port, mst_state); - return -EINVAL; - } - - if (pos->dsc_enabled == enable) { - drm_dbg_atomic(state->dev, - "[MST PORT:%p] DSC flag is already set to %d, returning %d VCPI slots\n", - port, enable, pos->vcpi); - vcpi = pos->vcpi; - } - - if (enable) { - vcpi = drm_dp_atomic_find_vcpi_slots(state, port->mgr, port, pbn, pbn_div); - drm_dbg_atomic(state->dev, - "[MST PORT:%p] Enabling DSC flag, reallocating %d VCPI slots on the port\n", - port, vcpi); - if (vcpi < 0) - return -EINVAL; - } - - pos->dsc_enabled = enable; - - return vcpi; -} -EXPORT_SYMBOL(drm_dp_mst_atomic_enable_dsc); -/** - * drm_dp_mst_atomic_check - Check that the new state of an MST topology in an - * atomic update is valid - * @state: Pointer to the new &struct drm_dp_mst_topology_state - * - * Checks the given topology state for an atomic update to ensure that it's - * valid. This includes checking whether there's enough bandwidth to support - * the new VCPI allocations in the atomic update. - * - * Any atomic drivers supporting DP MST must make sure to call this after - * checking the rest of their state in their - * &drm_mode_config_funcs.atomic_check() callback. - * - * See also: - * drm_dp_atomic_find_vcpi_slots() - * drm_dp_atomic_release_vcpi_slots() - * - * Returns: - * - * 0 if the new state is valid, negative error code otherwise. - */ -int drm_dp_mst_atomic_check(struct drm_atomic_state *state) -{ - struct drm_dp_mst_topology_mgr *mgr; - struct drm_dp_mst_topology_state *mst_state; - int i, ret = 0; - - for_each_new_mst_mgr_in_state(state, mgr, mst_state, i) { - if (!mgr->mst_state) - continue; - - ret = drm_dp_mst_atomic_check_vcpi_alloc_limit(mgr, mst_state); - if (ret) - break; - - mutex_lock(&mgr->lock); - ret = drm_dp_mst_atomic_check_mstb_bw_limit(mgr->mst_primary, - mst_state); - mutex_unlock(&mgr->lock); - if (ret < 0) - break; - else - ret = 0; - } - - return ret; -} -EXPORT_SYMBOL(drm_dp_mst_atomic_check); - -const struct drm_private_state_funcs drm_dp_mst_topology_state_funcs = { - .atomic_duplicate_state = drm_dp_mst_duplicate_state, - .atomic_destroy_state = drm_dp_mst_destroy_state, -}; -EXPORT_SYMBOL(drm_dp_mst_topology_state_funcs); - -/** - * drm_atomic_get_mst_topology_state: get MST topology state - * - * @state: global atomic state - * @mgr: MST topology manager, also the private object in this case - * - * This function wraps drm_atomic_get_priv_obj_state() passing in the MST atomic - * state vtable so that the private object state returned is that of a MST - * topology object. Also, drm_atomic_get_private_obj_state() expects the caller - * to care of the locking, so warn if don't hold the connection_mutex. - * - * RETURNS: - * - * The MST topology state or error pointer. - */ -struct drm_dp_mst_topology_state *drm_atomic_get_mst_topology_state(struct drm_atomic_state *state, - struct drm_dp_mst_topology_mgr *mgr) -{ - return to_dp_mst_topology_state(drm_atomic_get_private_obj_state(state, &mgr->base)); -} -EXPORT_SYMBOL(drm_atomic_get_mst_topology_state); - -/** - * drm_dp_mst_topology_mgr_init - initialise a topology manager - * @mgr: manager struct to initialise - * @dev: device providing this structure - for i2c addition. - * @aux: DP helper aux channel to talk to this device - * @max_dpcd_transaction_bytes: hw specific DPCD transaction limit - * @max_payloads: maximum number of payloads this GPU can source - * @max_lane_count: maximum number of lanes this GPU supports - * @max_link_rate: maximum link rate per lane this GPU supports in kHz - * @conn_base_id: the connector object ID the MST device is connected to. - * - * Return 0 for success, or negative error code on failure - */ -int drm_dp_mst_topology_mgr_init(struct drm_dp_mst_topology_mgr *mgr, - struct drm_device *dev, struct drm_dp_aux *aux, - int max_dpcd_transaction_bytes, int max_payloads, - int max_lane_count, int max_link_rate, - int conn_base_id) -{ - struct drm_dp_mst_topology_state *mst_state; - - mutex_init(&mgr->lock); - mutex_init(&mgr->qlock); - mutex_init(&mgr->payload_lock); - mutex_init(&mgr->delayed_destroy_lock); - mutex_init(&mgr->up_req_lock); - mutex_init(&mgr->probe_lock); -#if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS) - mutex_init(&mgr->topology_ref_history_lock); -#endif - INIT_LIST_HEAD(&mgr->tx_msg_downq); - INIT_LIST_HEAD(&mgr->destroy_port_list); - INIT_LIST_HEAD(&mgr->destroy_branch_device_list); - INIT_LIST_HEAD(&mgr->up_req_list); - - /* - * delayed_destroy_work will be queued on a dedicated WQ, so that any - * requeuing will be also flushed when deiniting the topology manager. - */ - mgr->delayed_destroy_wq = alloc_ordered_workqueue("drm_dp_mst_wq", 0); - if (mgr->delayed_destroy_wq == NULL) - return -ENOMEM; - - INIT_WORK(&mgr->work, drm_dp_mst_link_probe_work); - INIT_WORK(&mgr->tx_work, drm_dp_tx_work); - INIT_WORK(&mgr->delayed_destroy_work, drm_dp_delayed_destroy_work); - INIT_WORK(&mgr->up_req_work, drm_dp_mst_up_req_work); - init_waitqueue_head(&mgr->tx_waitq); - mgr->dev = dev; - mgr->aux = aux; - mgr->max_dpcd_transaction_bytes = max_dpcd_transaction_bytes; - mgr->max_payloads = max_payloads; - mgr->max_lane_count = max_lane_count; - mgr->max_link_rate = max_link_rate; - mgr->conn_base_id = conn_base_id; - if (max_payloads + 1 > sizeof(mgr->payload_mask) * 8 || - max_payloads + 1 > sizeof(mgr->vcpi_mask) * 8) - return -EINVAL; - mgr->payloads = kcalloc(max_payloads, sizeof(struct drm_dp_payload), GFP_KERNEL); - if (!mgr->payloads) - return -ENOMEM; - mgr->proposed_vcpis = kcalloc(max_payloads, sizeof(struct drm_dp_vcpi *), GFP_KERNEL); - if (!mgr->proposed_vcpis) - return -ENOMEM; - set_bit(0, &mgr->payload_mask); - - mst_state = kzalloc(sizeof(*mst_state), GFP_KERNEL); - if (mst_state == NULL) - return -ENOMEM; - - mst_state->total_avail_slots = 63; - mst_state->start_slot = 1; - - mst_state->mgr = mgr; - INIT_LIST_HEAD(&mst_state->vcpis); - - drm_atomic_private_obj_init(dev, &mgr->base, - &mst_state->base, - &drm_dp_mst_topology_state_funcs); - - return 0; -} -EXPORT_SYMBOL(drm_dp_mst_topology_mgr_init); - -/** - * drm_dp_mst_topology_mgr_destroy() - destroy topology manager. - * @mgr: manager to destroy - */ -void drm_dp_mst_topology_mgr_destroy(struct drm_dp_mst_topology_mgr *mgr) -{ - drm_dp_mst_topology_mgr_set_mst(mgr, false); - flush_work(&mgr->work); - /* The following will also drain any requeued work on the WQ. */ - if (mgr->delayed_destroy_wq) { - destroy_workqueue(mgr->delayed_destroy_wq); - mgr->delayed_destroy_wq = NULL; - } - mutex_lock(&mgr->payload_lock); - kfree(mgr->payloads); - mgr->payloads = NULL; - kfree(mgr->proposed_vcpis); - mgr->proposed_vcpis = NULL; - mutex_unlock(&mgr->payload_lock); - mgr->dev = NULL; - mgr->aux = NULL; - drm_atomic_private_obj_fini(&mgr->base); - mgr->funcs = NULL; - - mutex_destroy(&mgr->delayed_destroy_lock); - mutex_destroy(&mgr->payload_lock); - mutex_destroy(&mgr->qlock); - mutex_destroy(&mgr->lock); - mutex_destroy(&mgr->up_req_lock); - mutex_destroy(&mgr->probe_lock); -#if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS) - mutex_destroy(&mgr->topology_ref_history_lock); -#endif -} -EXPORT_SYMBOL(drm_dp_mst_topology_mgr_destroy); - -static bool remote_i2c_read_ok(const struct i2c_msg msgs[], int num) -{ - int i; - - if (num - 1 > DP_REMOTE_I2C_READ_MAX_TRANSACTIONS) - return false; - - for (i = 0; i < num - 1; i++) { - if (msgs[i].flags & I2C_M_RD || - msgs[i].len > 0xff) - return false; - } - - return msgs[num - 1].flags & I2C_M_RD && - msgs[num - 1].len <= 0xff; -} - -static bool remote_i2c_write_ok(const struct i2c_msg msgs[], int num) -{ - int i; - - for (i = 0; i < num - 1; i++) { - if (msgs[i].flags & I2C_M_RD || !(msgs[i].flags & I2C_M_STOP) || - msgs[i].len > 0xff) - return false; - } - - return !(msgs[num - 1].flags & I2C_M_RD) && msgs[num - 1].len <= 0xff; -} - -static int drm_dp_mst_i2c_read(struct drm_dp_mst_branch *mstb, - struct drm_dp_mst_port *port, - struct i2c_msg *msgs, int num) -{ - struct drm_dp_mst_topology_mgr *mgr = port->mgr; - unsigned int i; - struct drm_dp_sideband_msg_req_body msg; - struct drm_dp_sideband_msg_tx *txmsg = NULL; - int ret; - - memset(&msg, 0, sizeof(msg)); - msg.req_type = DP_REMOTE_I2C_READ; - msg.u.i2c_read.num_transactions = num - 1; - msg.u.i2c_read.port_number = port->port_num; - for (i = 0; i < num - 1; i++) { - msg.u.i2c_read.transactions[i].i2c_dev_id = msgs[i].addr; - msg.u.i2c_read.transactions[i].num_bytes = msgs[i].len; - msg.u.i2c_read.transactions[i].bytes = msgs[i].buf; - msg.u.i2c_read.transactions[i].no_stop_bit = !(msgs[i].flags & I2C_M_STOP); - } - msg.u.i2c_read.read_i2c_device_id = msgs[num - 1].addr; - msg.u.i2c_read.num_bytes_read = msgs[num - 1].len; - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) { - ret = -ENOMEM; - goto out; - } - - txmsg->dst = mstb; - drm_dp_encode_sideband_req(&msg, txmsg); - - drm_dp_queue_down_tx(mgr, txmsg); - - ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); - if (ret > 0) { - - if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { - ret = -EREMOTEIO; - goto out; - } - if (txmsg->reply.u.remote_i2c_read_ack.num_bytes != msgs[num - 1].len) { - ret = -EIO; - goto out; - } - memcpy(msgs[num - 1].buf, txmsg->reply.u.remote_i2c_read_ack.bytes, msgs[num - 1].len); - ret = num; - } -out: - kfree(txmsg); - return ret; -} - -static int drm_dp_mst_i2c_write(struct drm_dp_mst_branch *mstb, - struct drm_dp_mst_port *port, - struct i2c_msg *msgs, int num) -{ - struct drm_dp_mst_topology_mgr *mgr = port->mgr; - unsigned int i; - struct drm_dp_sideband_msg_req_body msg; - struct drm_dp_sideband_msg_tx *txmsg = NULL; - int ret; - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) { - ret = -ENOMEM; - goto out; - } - for (i = 0; i < num; i++) { - memset(&msg, 0, sizeof(msg)); - msg.req_type = DP_REMOTE_I2C_WRITE; - msg.u.i2c_write.port_number = port->port_num; - msg.u.i2c_write.write_i2c_device_id = msgs[i].addr; - msg.u.i2c_write.num_bytes = msgs[i].len; - msg.u.i2c_write.bytes = msgs[i].buf; - - memset(txmsg, 0, sizeof(*txmsg)); - txmsg->dst = mstb; - - drm_dp_encode_sideband_req(&msg, txmsg); - drm_dp_queue_down_tx(mgr, txmsg); - - ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); - if (ret > 0) { - if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { - ret = -EREMOTEIO; - goto out; - } - } else { - goto out; - } - } - ret = num; -out: - kfree(txmsg); - return ret; -} - -/* I2C device */ -static int drm_dp_mst_i2c_xfer(struct i2c_adapter *adapter, - struct i2c_msg *msgs, int num) -{ - struct drm_dp_aux *aux = adapter->algo_data; - struct drm_dp_mst_port *port = - container_of(aux, struct drm_dp_mst_port, aux); - struct drm_dp_mst_branch *mstb; - struct drm_dp_mst_topology_mgr *mgr = port->mgr; - int ret; - - mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent); - if (!mstb) - return -EREMOTEIO; - - if (remote_i2c_read_ok(msgs, num)) { - ret = drm_dp_mst_i2c_read(mstb, port, msgs, num); - } else if (remote_i2c_write_ok(msgs, num)) { - ret = drm_dp_mst_i2c_write(mstb, port, msgs, num); - } else { - drm_dbg_kms(mgr->dev, "Unsupported I2C transaction for MST device\n"); - ret = -EIO; - } - - drm_dp_mst_topology_put_mstb(mstb); - return ret; -} - -static u32 drm_dp_mst_i2c_functionality(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | - I2C_FUNC_SMBUS_READ_BLOCK_DATA | - I2C_FUNC_SMBUS_BLOCK_PROC_CALL | - I2C_FUNC_10BIT_ADDR; -} - -static const struct i2c_algorithm drm_dp_mst_i2c_algo = { - .functionality = drm_dp_mst_i2c_functionality, - .master_xfer = drm_dp_mst_i2c_xfer, -}; - -/** - * drm_dp_mst_register_i2c_bus() - register an I2C adapter for I2C-over-AUX - * @port: The port to add the I2C bus on - * - * Returns 0 on success or a negative error code on failure. - */ -static int drm_dp_mst_register_i2c_bus(struct drm_dp_mst_port *port) -{ - struct drm_dp_aux *aux = &port->aux; - struct device *parent_dev = port->mgr->dev->dev; - - aux->ddc.algo = &drm_dp_mst_i2c_algo; - aux->ddc.algo_data = aux; - aux->ddc.retries = 3; - - aux->ddc.class = I2C_CLASS_DDC; - aux->ddc.owner = THIS_MODULE; - /* FIXME: set the kdev of the port's connector as parent */ - aux->ddc.dev.parent = parent_dev; - aux->ddc.dev.of_node = parent_dev->of_node; - - strlcpy(aux->ddc.name, aux->name ? aux->name : dev_name(parent_dev), - sizeof(aux->ddc.name)); - - return i2c_add_adapter(&aux->ddc); -} - -/** - * drm_dp_mst_unregister_i2c_bus() - unregister an I2C-over-AUX adapter - * @port: The port to remove the I2C bus from - */ -static void drm_dp_mst_unregister_i2c_bus(struct drm_dp_mst_port *port) -{ - i2c_del_adapter(&port->aux.ddc); -} - -/** - * drm_dp_mst_is_virtual_dpcd() - Is the given port a virtual DP Peer Device - * @port: The port to check - * - * A single physical MST hub object can be represented in the topology - * by multiple branches, with virtual ports between those branches. - * - * As of DP1.4, An MST hub with internal (virtual) ports must expose - * certain DPCD registers over those ports. See sections 2.6.1.1.1 - * and 2.6.1.1.2 of Display Port specification v1.4 for details. - * - * May acquire mgr->lock - * - * Returns: - * true if the port is a virtual DP peer device, false otherwise - */ -static bool drm_dp_mst_is_virtual_dpcd(struct drm_dp_mst_port *port) -{ - struct drm_dp_mst_port *downstream_port; - - if (!port || port->dpcd_rev < DP_DPCD_REV_14) - return false; - - /* Virtual DP Sink (Internal Display Panel) */ - if (port->port_num >= 8) - return true; - - /* DP-to-HDMI Protocol Converter */ - if (port->pdt == DP_PEER_DEVICE_DP_LEGACY_CONV && - !port->mcs && - port->ldps) - return true; - - /* DP-to-DP */ - mutex_lock(&port->mgr->lock); - if (port->pdt == DP_PEER_DEVICE_MST_BRANCHING && - port->mstb && - port->mstb->num_ports == 2) { - list_for_each_entry(downstream_port, &port->mstb->ports, next) { - if (downstream_port->pdt == DP_PEER_DEVICE_SST_SINK && - !downstream_port->input) { - mutex_unlock(&port->mgr->lock); - return true; - } - } - } - mutex_unlock(&port->mgr->lock); - - return false; -} - -/** - * drm_dp_mst_dsc_aux_for_port() - Find the correct aux for DSC - * @port: The port to check. A leaf of the MST tree with an attached display. - * - * Depending on the situation, DSC may be enabled via the endpoint aux, - * the immediately upstream aux, or the connector's physical aux. - * - * This is both the correct aux to read DSC_CAPABILITY and the - * correct aux to write DSC_ENABLED. - * - * This operation can be expensive (up to four aux reads), so - * the caller should cache the return. - * - * Returns: - * NULL if DSC cannot be enabled on this port, otherwise the aux device - */ -struct drm_dp_aux *drm_dp_mst_dsc_aux_for_port(struct drm_dp_mst_port *port) -{ - struct drm_dp_mst_port *immediate_upstream_port; - struct drm_dp_mst_port *fec_port; - struct drm_dp_desc desc = {}; - u8 endpoint_fec; - u8 endpoint_dsc; - - if (!port) - return NULL; - - if (port->parent->port_parent) - immediate_upstream_port = port->parent->port_parent; - else - immediate_upstream_port = NULL; - - fec_port = immediate_upstream_port; - while (fec_port) { - /* - * Each physical link (i.e. not a virtual port) between the - * output and the primary device must support FEC - */ - if (!drm_dp_mst_is_virtual_dpcd(fec_port) && - !fec_port->fec_capable) - return NULL; - - fec_port = fec_port->parent->port_parent; - } - - /* DP-to-DP peer device */ - if (drm_dp_mst_is_virtual_dpcd(immediate_upstream_port)) { - u8 upstream_dsc; - - if (drm_dp_dpcd_read(&port->aux, - DP_DSC_SUPPORT, &endpoint_dsc, 1) != 1) - return NULL; - if (drm_dp_dpcd_read(&port->aux, - DP_FEC_CAPABILITY, &endpoint_fec, 1) != 1) - return NULL; - if (drm_dp_dpcd_read(&immediate_upstream_port->aux, - DP_DSC_SUPPORT, &upstream_dsc, 1) != 1) - return NULL; - - /* Enpoint decompression with DP-to-DP peer device */ - if ((endpoint_dsc & DP_DSC_DECOMPRESSION_IS_SUPPORTED) && - (endpoint_fec & DP_FEC_CAPABLE) && - (upstream_dsc & 0x2) /* DSC passthrough */) - return &port->aux; - - /* Virtual DPCD decompression with DP-to-DP peer device */ - return &immediate_upstream_port->aux; - } - - /* Virtual DPCD decompression with DP-to-HDMI or Virtual DP Sink */ - if (drm_dp_mst_is_virtual_dpcd(port)) - return &port->aux; - - /* - * Synaptics quirk - * Applies to ports for which: - * - Physical aux has Synaptics OUI - * - DPv1.4 or higher - * - Port is on primary branch device - * - Not a VGA adapter (DP_DWN_STRM_PORT_TYPE_ANALOG) - */ - if (drm_dp_read_desc(port->mgr->aux, &desc, true)) - return NULL; - - if (drm_dp_has_quirk(&desc, DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD) && - port->mgr->dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14 && - port->parent == port->mgr->mst_primary) { - u8 dpcd_ext[DP_RECEIVER_CAP_SIZE]; - - if (drm_dp_read_dpcd_caps(port->mgr->aux, dpcd_ext) < 0) - return NULL; - - if ((dpcd_ext[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT) && - ((dpcd_ext[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) - != DP_DWN_STRM_PORT_TYPE_ANALOG)) - return port->mgr->aux; - } - - /* - * The check below verifies if the MST sink - * connected to the GPU is capable of DSC - - * therefore the endpoint needs to be - * both DSC and FEC capable. - */ - if (drm_dp_dpcd_read(&port->aux, - DP_DSC_SUPPORT, &endpoint_dsc, 1) != 1) - return NULL; - if (drm_dp_dpcd_read(&port->aux, - DP_FEC_CAPABILITY, &endpoint_fec, 1) != 1) - return NULL; - if ((endpoint_dsc & DP_DSC_DECOMPRESSION_IS_SUPPORTED) && - (endpoint_fec & DP_FEC_CAPABLE)) - return &port->aux; - - return NULL; -} -EXPORT_SYMBOL(drm_dp_mst_dsc_aux_for_port); diff --git a/drivers/gpu/drm/drm_dp_mst_topology_internal.h b/drivers/gpu/drm/drm_dp_mst_topology_internal.h deleted file mode 100644 index eeda9a61c657..000000000000 --- a/drivers/gpu/drm/drm_dp_mst_topology_internal.h +++ /dev/null @@ -1,24 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only - * - * Declarations for DP MST related functions which are only used in selftests - * - * Copyright © 2018 Red Hat - * Authors: - * Lyude Paul - */ - -#ifndef _DRM_DP_MST_HELPER_INTERNAL_H_ -#define _DRM_DP_MST_HELPER_INTERNAL_H_ - -#include - -void -drm_dp_encode_sideband_req(const struct drm_dp_sideband_msg_req_body *req, - struct drm_dp_sideband_msg_tx *raw); -int drm_dp_decode_sideband_req(const struct drm_dp_sideband_msg_tx *raw, - struct drm_dp_sideband_msg_req_body *req); -void -drm_dp_dump_sideband_msg_req_body(const struct drm_dp_sideband_msg_req_body *req, - int indent, struct drm_printer *printer); - -#endif /* !_DRM_DP_MST_HELPER_INTERNAL_H_ */ diff --git a/drivers/gpu/drm/drm_kms_helper_common.c b/drivers/gpu/drm/drm_kms_helper_common.c index 88260d26409c..8be20080cd8d 100644 --- a/drivers/gpu/drm/drm_kms_helper_common.c +++ b/drivers/gpu/drm/drm_kms_helper_common.c @@ -29,7 +29,6 @@ #include -#include "drm_dp_helper_internal.h" #include "drm_crtc_helper_internal.h" MODULE_AUTHOR("David Airlie, Jesse Barnes"); @@ -62,17 +61,3 @@ MODULE_PARM_DESC(edid_firmware, "DEPRECATED. Use drm.edid_firmware module parameter instead."); #endif - -static int __init drm_kms_helper_init(void) -{ - return drm_dp_aux_dev_init(); -} - -static void __exit drm_kms_helper_exit(void) -{ - /* Call exit functions from specific kms helpers here */ - drm_dp_aux_dev_exit(); -} - -module_init(drm_kms_helper_init); -module_exit(drm_kms_helper_exit); diff --git a/drivers/gpu/drm/i915/Kconfig b/drivers/gpu/drm/i915/Kconfig index a4c94dc2e216..b68e8b551b83 100644 --- a/drivers/gpu/drm/i915/Kconfig +++ b/drivers/gpu/drm/i915/Kconfig @@ -9,6 +9,7 @@ config DRM_I915 # the shmem_readpage() which depends upon tmpfs select SHMEM select TMPFS + select DRM_DP_HELPER select DRM_KMS_HELPER select DRM_PANEL select DRM_MIPI_DSI diff --git a/drivers/gpu/drm/msm/Kconfig b/drivers/gpu/drm/msm/Kconfig index 39197b4beea7..75015b0e165e 100644 --- a/drivers/gpu/drm/msm/Kconfig +++ b/drivers/gpu/drm/msm/Kconfig @@ -12,6 +12,7 @@ config DRM_MSM select IOMMU_IO_PGTABLE select QCOM_MDT_LOADER if ARCH_QCOM select REGULATOR + select DRM_DP_HELPER select DRM_KMS_HELPER select DRM_PANEL select DRM_BRIDGE diff --git a/drivers/gpu/drm/nouveau/Kconfig b/drivers/gpu/drm/nouveau/Kconfig index 9436310d0854..3ec690b6f0b4 100644 --- a/drivers/gpu/drm/nouveau/Kconfig +++ b/drivers/gpu/drm/nouveau/Kconfig @@ -4,6 +4,7 @@ config DRM_NOUVEAU depends on DRM && PCI && MMU select IOMMU_API select FW_LOADER + select DRM_DP_HELPER select DRM_KMS_HELPER select DRM_TTM select DRM_TTM_HELPER diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index 9f1ecefc3933..d59dca5efb52 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -2,6 +2,7 @@ config DRM_ROCKCHIP tristate "DRM Support for Rockchip" depends on DRM && ROCKCHIP_IOMMU + select DRM_DP_HELPER select DRM_GEM_CMA_HELPER select DRM_KMS_HELPER select DRM_PANEL diff --git a/drivers/gpu/drm/selftests/test-drm_dp_mst_helper.c b/drivers/gpu/drm/selftests/test-drm_dp_mst_helper.c index 6b4759ed6bfd..784048cb3c61 100644 --- a/drivers/gpu/drm/selftests/test-drm_dp_mst_helper.c +++ b/drivers/gpu/drm/selftests/test-drm_dp_mst_helper.c @@ -10,7 +10,7 @@ #include #include -#include "../drm_dp_mst_topology_internal.h" +#include "../dp/drm_dp_mst_topology_internal.h" #include "test-drm_modeset_common.h" int igt_dp_mst_calc_pbn_mode(void *ignored) diff --git a/drivers/gpu/drm/tegra/Kconfig b/drivers/gpu/drm/tegra/Kconfig index 1650a448eabd..dc88adc7ba40 100644 --- a/drivers/gpu/drm/tegra/Kconfig +++ b/drivers/gpu/drm/tegra/Kconfig @@ -5,6 +5,7 @@ config DRM_TEGRA depends on COMMON_CLK depends on DRM depends on OF + select DRM_DP_HELPER select DRM_KMS_HELPER select DRM_MIPI_DSI select DRM_PANEL diff --git a/drivers/gpu/drm/xlnx/Kconfig b/drivers/gpu/drm/xlnx/Kconfig index d8d38d86d5c6..06cf477dbcdd 100644 --- a/drivers/gpu/drm/xlnx/Kconfig +++ b/drivers/gpu/drm/xlnx/Kconfig @@ -6,6 +6,7 @@ config DRM_ZYNQMP_DPSUB depends on PHY_XILINX_ZYNQMP depends on XILINX_ZYNQMP_DPDMA select DMA_ENGINE + select DRM_DP_HELPER select DRM_GEM_CMA_HELPER select DRM_KMS_HELPER select GENERIC_PHY -- cgit From 5b529e8d9c387a34ca2b8008dc65f55d539b3ef6 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 14 Jan 2022 12:45:34 +0100 Subject: drm/dp: Move public DisplayPort headers into dp/ Move all public DisplayPort headers into dp/ and update users. No functional changes. v3: * rebased onto latest drm-tip Signed-off-by: Thomas Zimmermann Acked-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20220114114535.29157-5-tzimmermann@suse.de --- drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.c | 2 +- drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h | 4 ++-- drivers/gpu/drm/amd/amdgpu/atombios_dp.c | 2 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 2 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 2 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c | 4 ++-- drivers/gpu/drm/amd/display/dc/core/dc_link_dpcd.c | 2 +- drivers/gpu/drm/amd/display/dc/dsc/dc_dsc.c | 2 +- drivers/gpu/drm/amd/display/dc/os_types.h | 2 +- drivers/gpu/drm/amd/display/include/dpcd_defs.h | 2 +- drivers/gpu/drm/amd/display/modules/hdcp/hdcp.h | 2 +- drivers/gpu/drm/bridge/analogix/analogix-anx6345.c | 2 +- drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c | 2 +- drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.c | 2 +- drivers/gpu/drm/bridge/analogix/analogix_dp_core.h | 2 +- drivers/gpu/drm/bridge/analogix/anx7625.c | 2 +- drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c | 2 +- drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h | 2 +- drivers/gpu/drm/bridge/parade-ps8640.c | 2 +- drivers/gpu/drm/bridge/tc358767.c | 2 +- drivers/gpu/drm/bridge/tc358775.c | 2 +- drivers/gpu/drm/bridge/ti-sn65dsi86.c | 2 +- drivers/gpu/drm/dp/drm_dp.c | 4 ++-- drivers/gpu/drm/dp/drm_dp_aux_dev.c | 4 ++-- drivers/gpu/drm/dp/drm_dp_cec.c | 2 +- drivers/gpu/drm/dp/drm_dp_dual_mode_helper.c | 2 +- drivers/gpu/drm/dp/drm_dp_mst_topology.c | 2 +- drivers/gpu/drm/dp/drm_dp_mst_topology_internal.h | 2 +- drivers/gpu/drm/drm_dp_aux_bus.c | 2 +- drivers/gpu/drm/drm_dsc.c | 2 +- drivers/gpu/drm/gma500/cdv_intel_dp.c | 2 +- drivers/gpu/drm/gma500/intel_bios.c | 2 +- drivers/gpu/drm/i915/display/intel_bios.c | 2 +- drivers/gpu/drm/i915/display/intel_display.c | 2 +- drivers/gpu/drm/i915/display/intel_display_types.h | 4 ++-- drivers/gpu/drm/i915/display/intel_dp.c | 2 +- drivers/gpu/drm/i915/display/intel_dp_hdcp.c | 4 ++-- drivers/gpu/drm/i915/display/intel_dp_link_training.h | 2 +- drivers/gpu/drm/i915/display/intel_lspcon.c | 2 +- drivers/gpu/drm/msm/dp/dp_audio.c | 2 +- drivers/gpu/drm/msm/dp/dp_aux.h | 2 +- drivers/gpu/drm/msm/dp/dp_catalog.c | 2 +- drivers/gpu/drm/msm/dp/dp_ctrl.c | 2 +- drivers/gpu/drm/nouveau/dispnv50/disp.c | 2 +- drivers/gpu/drm/nouveau/nouveau_connector.h | 2 +- drivers/gpu/drm/nouveau/nouveau_dp.c | 2 +- drivers/gpu/drm/nouveau/nouveau_encoder.h | 4 ++-- drivers/gpu/drm/panel/panel-edp.c | 2 +- drivers/gpu/drm/panel/panel-samsung-atna33xc20.c | 2 +- drivers/gpu/drm/radeon/atombios_dp.c | 2 +- drivers/gpu/drm/radeon/radeon_connectors.c | 2 +- drivers/gpu/drm/radeon/radeon_dp_mst.c | 2 +- drivers/gpu/drm/radeon/radeon_mode.h | 4 ++-- drivers/gpu/drm/rockchip/analogix_dp-rockchip.c | 2 +- drivers/gpu/drm/rockchip/cdn-dp-core.c | 2 +- drivers/gpu/drm/rockchip/cdn-dp-core.h | 2 +- drivers/gpu/drm/rockchip/rockchip_lvds.c | 2 +- drivers/gpu/drm/rockchip/rockchip_rgb.c | 2 +- drivers/gpu/drm/selftests/test-drm_dp_mst_helper.c | 2 +- drivers/gpu/drm/tegra/dp.c | 2 +- drivers/gpu/drm/tegra/dpaux.c | 2 +- drivers/gpu/drm/tegra/sor.c | 2 +- drivers/gpu/drm/xlnx/zynqmp_dp.c | 2 +- 63 files changed, 71 insertions(+), 71 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.c index 0de66f59adb8..19c4d9225197 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.c @@ -26,7 +26,7 @@ #include #include -#include +#include #include #include #include "amdgpu.h" diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h index 6043bf6fd414..902235fae4cd 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h @@ -33,7 +33,7 @@ #include #include #include -#include +#include #include #include #include @@ -44,7 +44,7 @@ #include #include "amdgpu_irq.h" -#include +#include #include "modules/inc/mod_freesync.h" #include "amdgpu_dm_irq_params.h" diff --git a/drivers/gpu/drm/amd/amdgpu/atombios_dp.c b/drivers/gpu/drm/amd/amdgpu/atombios_dp.c index f327becb022f..49a2f594fb2c 100644 --- a/drivers/gpu/drm/amd/amdgpu/atombios_dp.c +++ b/drivers/gpu/drm/amd/amdgpu/atombios_dp.c @@ -34,7 +34,7 @@ #include "atombios_dp.h" #include "amdgpu_connectors.h" #include "amdgpu_atombios.h" -#include +#include /* move these to drm_dp_helper.c/h */ #define DP_LINK_CONFIGURATION_SIZE 9 diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 116a280d8a20..046506ff0532 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -76,7 +76,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index bb65f41d1a59..bec1de86e2ef 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include /* diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c index cc34a35d0bcb..35c944a8e74d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c @@ -25,8 +25,8 @@ #include #include -#include -#include +#include +#include #include "dm_services.h" #include "amdgpu.h" #include "amdgpu_dm.h" diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_link_dpcd.c b/drivers/gpu/drm/amd/display/dc/core/dc_link_dpcd.c index 7f25c11f4248..48a18766f002 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_link_dpcd.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_link_dpcd.c @@ -27,7 +27,7 @@ #include #include #include -#include "drm/drm_dp_helper.h" +#include #include #include "dm_helpers.h" diff --git a/drivers/gpu/drm/amd/display/dc/dsc/dc_dsc.c b/drivers/gpu/drm/amd/display/dc/dsc/dc_dsc.c index 9c74564cbd8d..efc2339f1fa0 100644 --- a/drivers/gpu/drm/amd/display/dc/dsc/dc_dsc.c +++ b/drivers/gpu/drm/amd/display/dc/dsc/dc_dsc.c @@ -25,7 +25,7 @@ #include #include "dc_hw_types.h" #include "dsc.h" -#include +#include #include "dc.h" #include "rc_calc.h" #include "fixed31_32.h" diff --git a/drivers/gpu/drm/amd/display/dc/os_types.h b/drivers/gpu/drm/amd/display/dc/os_types.h index 5df1d80c8341..17d05071b809 100644 --- a/drivers/gpu/drm/amd/display/dc/os_types.h +++ b/drivers/gpu/drm/amd/display/dc/os_types.h @@ -36,7 +36,7 @@ #include #include -#include +#include #include "cgs_common.h" diff --git a/drivers/gpu/drm/amd/display/include/dpcd_defs.h b/drivers/gpu/drm/amd/display/include/dpcd_defs.h index ffd0df1701e6..270260e82b61 100644 --- a/drivers/gpu/drm/amd/display/include/dpcd_defs.h +++ b/drivers/gpu/drm/amd/display/include/dpcd_defs.h @@ -26,7 +26,7 @@ #ifndef __DAL_DPCD_DEFS_H__ #define __DAL_DPCD_DEFS_H__ -#include +#include #ifndef DP_SINK_HW_REVISION_START // can remove this once the define gets into linux drm_dp_helper.h #define DP_SINK_HW_REVISION_START 0x409 #endif diff --git a/drivers/gpu/drm/amd/display/modules/hdcp/hdcp.h b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp.h index 399fbca8947b..8502263d2968 100644 --- a/drivers/gpu/drm/amd/display/modules/hdcp/hdcp.h +++ b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp.h @@ -30,7 +30,7 @@ #include "hdcp_log.h" #include -#include +#include enum mod_hdcp_trans_input_result { UNKNOWN = 0, diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c index e33cd077595a..94e56a2e91f2 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c +++ b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c b/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c index 5e6a0ed39199..2768b41c48e9 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c +++ b/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.c b/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.c index fe40bab21530..e8297168bfef 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.c +++ b/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.c @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include "analogix-i2c-dptx.h" diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h index c051502d7fbf..32665203a6ae 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h @@ -10,7 +10,7 @@ #define _ANALOGIX_DP_CORE_H #include -#include +#include #define DP_TIMEOUT_LOOP_COUNT 100 #define MAX_CR_LOOP 5 diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c index 33383f83255d..0528c3c6168d 100644 --- a/drivers/gpu/drm/bridge/analogix/anx7625.c +++ b/drivers/gpu/drm/bridge/analogix/anx7625.c @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c index 5530fbf64f1e..ac9e64169c6f 100644 --- a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c +++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c @@ -41,7 +41,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h index c74439d0b1a7..fc77f987c835 100644 --- a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h +++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h @@ -17,7 +17,7 @@ #include #include -#include +#include struct clk; struct device; diff --git a/drivers/gpu/drm/bridge/parade-ps8640.c b/drivers/gpu/drm/bridge/parade-ps8640.c index 54723f068884..3e0c7436f407 100644 --- a/drivers/gpu/drm/bridge/parade-ps8640.c +++ b/drivers/gpu/drm/bridge/parade-ps8640.c @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include #include diff --git a/drivers/gpu/drm/bridge/tc358767.c b/drivers/gpu/drm/bridge/tc358767.c index 23a6f90b694b..c23e0abc65e8 100644 --- a/drivers/gpu/drm/bridge/tc358767.c +++ b/drivers/gpu/drm/bridge/tc358767.c @@ -27,7 +27,7 @@ #include #include -#include +#include #include #include #include diff --git a/drivers/gpu/drm/bridge/tc358775.c b/drivers/gpu/drm/bridge/tc358775.c index 4ad4fc251b93..695af3badcc7 100644 --- a/drivers/gpu/drm/bridge/tc358775.c +++ b/drivers/gpu/drm/bridge/tc358775.c @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index dab8f76618f3..65d25ce30ce5 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/drivers/gpu/drm/dp/drm_dp.c b/drivers/gpu/drm/dp/drm_dp.c index e995a0262ed7..6d43325acca5 100644 --- a/drivers/gpu/drm/dp/drm_dp.c +++ b/drivers/gpu/drm/dp/drm_dp.c @@ -29,10 +29,10 @@ #include #include -#include +#include #include #include -#include +#include #include #include "drm_dp_helper_internal.h" diff --git a/drivers/gpu/drm/dp/drm_dp_aux_dev.c b/drivers/gpu/drm/dp/drm_dp_aux_dev.c index 0618dfe16660..53ad4e72790b 100644 --- a/drivers/gpu/drm/dp/drm_dp_aux_dev.c +++ b/drivers/gpu/drm/dp/drm_dp_aux_dev.c @@ -36,8 +36,8 @@ #include #include -#include -#include +#include +#include #include #include "drm_dp_helper_internal.h" diff --git a/drivers/gpu/drm/dp/drm_dp_cec.c b/drivers/gpu/drm/dp/drm_dp_cec.c index 3ab2609f9ec7..f9e927355879 100644 --- a/drivers/gpu/drm/dp/drm_dp_cec.c +++ b/drivers/gpu/drm/dp/drm_dp_cec.c @@ -13,7 +13,7 @@ #include #include -#include +#include /* * Unfortunately it turns out that we have a chicken-and-egg situation diff --git a/drivers/gpu/drm/dp/drm_dp_dual_mode_helper.c b/drivers/gpu/drm/dp/drm_dp_dual_mode_helper.c index 9faf49354cab..2049cb0f7ed0 100644 --- a/drivers/gpu/drm/dp/drm_dp_dual_mode_helper.c +++ b/drivers/gpu/drm/dp/drm_dp_dual_mode_helper.c @@ -28,7 +28,7 @@ #include #include -#include +#include #include /** diff --git a/drivers/gpu/drm/dp/drm_dp_mst_topology.c b/drivers/gpu/drm/dp/drm_dp_mst_topology.c index bc3237a09c06..ddb9aa051288 100644 --- a/drivers/gpu/drm/dp/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/dp/drm_dp_mst_topology.c @@ -38,9 +38,9 @@ #include #endif +#include #include #include -#include #include #include #include diff --git a/drivers/gpu/drm/dp/drm_dp_mst_topology_internal.h b/drivers/gpu/drm/dp/drm_dp_mst_topology_internal.h index eeda9a61c657..401953b59d45 100644 --- a/drivers/gpu/drm/dp/drm_dp_mst_topology_internal.h +++ b/drivers/gpu/drm/dp/drm_dp_mst_topology_internal.h @@ -10,7 +10,7 @@ #ifndef _DRM_DP_MST_HELPER_INTERNAL_H_ #define _DRM_DP_MST_HELPER_INTERNAL_H_ -#include +#include void drm_dp_encode_sideband_req(const struct drm_dp_sideband_msg_req_body *req, diff --git a/drivers/gpu/drm/drm_dp_aux_bus.c b/drivers/gpu/drm/drm_dp_aux_bus.c index 298ea7a49591..565edf6b1732 100644 --- a/drivers/gpu/drm/drm_dp_aux_bus.c +++ b/drivers/gpu/drm/drm_dp_aux_bus.c @@ -20,7 +20,7 @@ #include #include -#include +#include /** * dp_aux_ep_match() - The match function for the dp_aux_bus. diff --git a/drivers/gpu/drm/drm_dsc.c b/drivers/gpu/drm/drm_dsc.c index 46a3c1b62463..fdd8d5f42622 100644 --- a/drivers/gpu/drm/drm_dsc.c +++ b/drivers/gpu/drm/drm_dsc.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include /** diff --git a/drivers/gpu/drm/gma500/cdv_intel_dp.c b/drivers/gpu/drm/gma500/cdv_intel_dp.c index 74e67679714e..f562e91337c7 100644 --- a/drivers/gpu/drm/gma500/cdv_intel_dp.c +++ b/drivers/gpu/drm/gma500/cdv_intel_dp.c @@ -31,7 +31,7 @@ #include #include -#include +#include #include #include "gma_display.h" diff --git a/drivers/gpu/drm/gma500/intel_bios.c b/drivers/gpu/drm/gma500/intel_bios.c index d5ca5f241974..ea7c16f33a0e 100644 --- a/drivers/gpu/drm/gma500/intel_bios.c +++ b/drivers/gpu/drm/gma500/intel_bios.c @@ -6,7 +6,7 @@ * Eric Anholt */ #include -#include +#include #include "intel_bios.h" #include "psb_drv.h" diff --git a/drivers/gpu/drm/i915/display/intel_bios.c b/drivers/gpu/drm/i915/display/intel_bios.c index 2b1423a43437..41d37a8c5c8f 100644 --- a/drivers/gpu/drm/i915/display/intel_bios.c +++ b/drivers/gpu/drm/i915/display/intel_bios.c @@ -25,7 +25,7 @@ * */ -#include +#include #include "display/intel_display.h" #include "display/intel_display_types.h" diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c index badf035efaeb..df4c1a17ba5f 100644 --- a/drivers/gpu/drm/i915/display/intel_display.c +++ b/drivers/gpu/drm/i915/display/intel_display.c @@ -37,7 +37,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h index b9c967837872..0d68dec6ac83 100644 --- a/drivers/gpu/drm/i915/display/intel_display_types.h +++ b/drivers/gpu/drm/i915/display/intel_display_types.h @@ -32,10 +32,10 @@ #include #include +#include +#include #include #include -#include -#include #include #include #include diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index 5a8206298691..d8a0f7a2b939 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -36,7 +36,7 @@ #include #include -#include +#include #include #include diff --git a/drivers/gpu/drm/i915/display/intel_dp_hdcp.c b/drivers/gpu/drm/i915/display/intel_dp_hdcp.c index 540a669e01dd..82d024dafe7b 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_hdcp.c +++ b/drivers/gpu/drm/i915/display/intel_dp_hdcp.c @@ -6,8 +6,8 @@ * Sean Paul */ -#include -#include +#include +#include #include #include diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_training.h b/drivers/gpu/drm/i915/display/intel_dp_link_training.h index 6a3a7b37349a..dbfb15705aaa 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_training.h +++ b/drivers/gpu/drm/i915/display/intel_dp_link_training.h @@ -6,7 +6,7 @@ #ifndef __INTEL_DP_LINK_TRAINING_H__ #define __INTEL_DP_LINK_TRAINING_H__ -#include +#include struct intel_crtc_state; struct intel_dp; diff --git a/drivers/gpu/drm/i915/display/intel_lspcon.c b/drivers/gpu/drm/i915/display/intel_lspcon.c index 05d2d750fa53..e879d36c31ad 100644 --- a/drivers/gpu/drm/i915/display/intel_lspcon.c +++ b/drivers/gpu/drm/i915/display/intel_lspcon.c @@ -24,7 +24,7 @@ */ #include -#include +#include #include #include "intel_de.h" diff --git a/drivers/gpu/drm/msm/dp/dp_audio.c b/drivers/gpu/drm/msm/dp/dp_audio.c index d7e4a39a904e..4553f4985434 100644 --- a/drivers/gpu/drm/msm/dp/dp_audio.c +++ b/drivers/gpu/drm/msm/dp/dp_audio.c @@ -8,7 +8,7 @@ #include -#include +#include #include #include "dp_catalog.h" diff --git a/drivers/gpu/drm/msm/dp/dp_aux.h b/drivers/gpu/drm/msm/dp/dp_aux.h index 0728cc09c9ec..82afc8d5210f 100644 --- a/drivers/gpu/drm/msm/dp/dp_aux.h +++ b/drivers/gpu/drm/msm/dp/dp_aux.h @@ -7,7 +7,7 @@ #define _DP_AUX_H_ #include "dp_catalog.h" -#include +#include int dp_aux_register(struct drm_dp_aux *dp_aux); void dp_aux_unregister(struct drm_dp_aux *dp_aux); diff --git a/drivers/gpu/drm/msm/dp/dp_catalog.c b/drivers/gpu/drm/msm/dp/dp_catalog.c index 6ae9b29044b6..8a6d3ead3440 100644 --- a/drivers/gpu/drm/msm/dp/dp_catalog.c +++ b/drivers/gpu/drm/msm/dp/dp_catalog.c @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include "dp_catalog.h" diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 62e75dc8afc6..ecf20458c75e 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include "dp_reg.h" diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c b/drivers/gpu/drm/nouveau/dispnv50/disp.c index ae1f41205520..df58c6445c51 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/disp.c +++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c @@ -35,7 +35,7 @@ #include #include -#include +#include #include #include #include diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.h b/drivers/gpu/drm/nouveau/nouveau_connector.h index 40f90e353540..1b173191cc41 100644 --- a/drivers/gpu/drm/nouveau/nouveau_connector.h +++ b/drivers/gpu/drm/nouveau/nouveau_connector.h @@ -36,7 +36,7 @@ #include #include #include -#include +#include #include #include "nouveau_crtc.h" diff --git a/drivers/gpu/drm/nouveau/nouveau_dp.c b/drivers/gpu/drm/nouveau/nouveau_dp.c index 3a288d0b848f..724d40ddd452 100644 --- a/drivers/gpu/drm/nouveau/nouveau_dp.c +++ b/drivers/gpu/drm/nouveau/nouveau_dp.c @@ -22,7 +22,7 @@ * Authors: Ben Skeggs */ -#include +#include #include "nouveau_drv.h" #include "nouveau_connector.h" diff --git a/drivers/gpu/drm/nouveau/nouveau_encoder.h b/drivers/gpu/drm/nouveau/nouveau_encoder.h index 77c2fed76e8b..65ed84f88cca 100644 --- a/drivers/gpu/drm/nouveau/nouveau_encoder.h +++ b/drivers/gpu/drm/nouveau/nouveau_encoder.h @@ -30,8 +30,8 @@ #include #include -#include -#include +#include +#include #include "dispnv04/disp.h" struct nv50_head_atom; struct nouveau_connector; diff --git a/drivers/gpu/drm/panel/panel-edp.c b/drivers/gpu/drm/panel/panel-edp.c index 176ef0c3cc1d..6a6ca891ee2e 100644 --- a/drivers/gpu/drm/panel/panel-edp.c +++ b/drivers/gpu/drm/panel/panel-edp.c @@ -37,7 +37,7 @@ #include #include #include -#include +#include #include /** diff --git a/drivers/gpu/drm/panel/panel-samsung-atna33xc20.c b/drivers/gpu/drm/panel/panel-samsung-atna33xc20.c index 221db6512859..bffeadaaf9a2 100644 --- a/drivers/gpu/drm/panel/panel-samsung-atna33xc20.c +++ b/drivers/gpu/drm/panel/panel-samsung-atna33xc20.c @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include diff --git a/drivers/gpu/drm/radeon/atombios_dp.c b/drivers/gpu/drm/radeon/atombios_dp.c index 4c1e551d9714..4798cf23d251 100644 --- a/drivers/gpu/drm/radeon/atombios_dp.c +++ b/drivers/gpu/drm/radeon/atombios_dp.c @@ -30,7 +30,7 @@ #include "atom.h" #include "atom-bits.h" -#include +#include /* move these to drm_dp_helper.c/h */ #define DP_LINK_CONFIGURATION_SIZE 9 diff --git a/drivers/gpu/drm/radeon/radeon_connectors.c b/drivers/gpu/drm/radeon/radeon_connectors.c index 607ad5620bd9..82d37a85bfc8 100644 --- a/drivers/gpu/drm/radeon/radeon_connectors.c +++ b/drivers/gpu/drm/radeon/radeon_connectors.c @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #include #include "radeon.h" diff --git a/drivers/gpu/drm/radeon/radeon_dp_mst.c b/drivers/gpu/drm/radeon/radeon_dp_mst.c index 751c2c075e09..9f26baf7adb0 100644 --- a/drivers/gpu/drm/radeon/radeon_dp_mst.c +++ b/drivers/gpu/drm/radeon/radeon_dp_mst.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -#include +#include #include #include #include diff --git a/drivers/gpu/drm/radeon/radeon_mode.h b/drivers/gpu/drm/radeon/radeon_mode.h index fe16f140a6b4..5288dc7a4897 100644 --- a/drivers/gpu/drm/radeon/radeon_mode.h +++ b/drivers/gpu/drm/radeon/radeon_mode.h @@ -33,8 +33,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c index 8abb5ac26807..c61d37f02af7 100644 --- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c index 16497c31d9f9..4740cc14beb8 100644 --- a/drivers/gpu/drm/rockchip/cdn-dp-core.c +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c @@ -16,7 +16,7 @@ #include #include -#include +#include #include #include #include diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.h b/drivers/gpu/drm/rockchip/cdn-dp-core.h index 81ac9b658a70..0d044146f4e9 100644 --- a/drivers/gpu/drm/rockchip/cdn-dp-core.h +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.h @@ -7,7 +7,7 @@ #ifndef _CDN_DP_CORE_H #define _CDN_DP_CORE_H -#include +#include #include #include diff --git a/drivers/gpu/drm/rockchip/rockchip_lvds.c b/drivers/gpu/drm/rockchip/rockchip_lvds.c index be74c87a8be4..0b972418067e 100644 --- a/drivers/gpu/drm/rockchip/rockchip_lvds.c +++ b/drivers/gpu/drm/rockchip/rockchip_lvds.c @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/drivers/gpu/drm/rockchip/rockchip_rgb.c b/drivers/gpu/drm/rockchip/rockchip_rgb.c index 09be9678f2bd..2494b079489d 100644 --- a/drivers/gpu/drm/rockchip/rockchip_rgb.c +++ b/drivers/gpu/drm/rockchip/rockchip_rgb.c @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/drivers/gpu/drm/selftests/test-drm_dp_mst_helper.c b/drivers/gpu/drm/selftests/test-drm_dp_mst_helper.c index 784048cb3c61..4004e6dc14de 100644 --- a/drivers/gpu/drm/selftests/test-drm_dp_mst_helper.c +++ b/drivers/gpu/drm/selftests/test-drm_dp_mst_helper.c @@ -7,7 +7,7 @@ #include -#include +#include #include #include "../dp/drm_dp_mst_topology_internal.h" diff --git a/drivers/gpu/drm/tegra/dp.c b/drivers/gpu/drm/tegra/dp.c index 70dfb7d1dec5..e4369e5b2943 100644 --- a/drivers/gpu/drm/tegra/dp.c +++ b/drivers/gpu/drm/tegra/dp.c @@ -5,7 +5,7 @@ */ #include -#include +#include #include #include "dp.h" diff --git a/drivers/gpu/drm/tegra/dpaux.c b/drivers/gpu/drm/tegra/dpaux.c index 1f96e416fa08..8ca500977a46 100644 --- a/drivers/gpu/drm/tegra/dpaux.c +++ b/drivers/gpu/drm/tegra/dpaux.c @@ -18,7 +18,7 @@ #include #include -#include +#include #include #include "dp.h" diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 0ea320c1092b..b125572feb84 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -18,7 +18,7 @@ #include #include -#include +#include #include #include #include diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c index 6f588dc09ba6..b1bbbb1d0a54 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include -- cgit From 032a125904995985334766911de9e26ee2bbd646 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 14 Jan 2022 12:45:35 +0100 Subject: drm/dp: Move DisplayPort AUX bus helpers into dp/ Move drm_dp_aux_bus.c and its header file into the DP subdirectory and update all users. No functional changes. Signed-off-by: Thomas Zimmermann Acked-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20220114114535.29157-6-tzimmermann@suse.de --- drivers/gpu/drm/Makefile | 2 - drivers/gpu/drm/bridge/parade-ps8640.c | 2 +- drivers/gpu/drm/bridge/ti-sn65dsi86.c | 2 +- drivers/gpu/drm/dp/Makefile | 2 + drivers/gpu/drm/dp/drm_dp_aux_bus.c | 323 +++++++++++++++++++++++ drivers/gpu/drm/drm_dp_aux_bus.c | 323 ----------------------- drivers/gpu/drm/panel/panel-edp.c | 2 +- drivers/gpu/drm/panel/panel-samsung-atna33xc20.c | 2 +- 8 files changed, 329 insertions(+), 329 deletions(-) create mode 100644 drivers/gpu/drm/dp/drm_dp_aux_bus.c delete mode 100644 drivers/gpu/drm/drm_dp_aux_bus.c (limited to 'drivers') diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 69be80ef1d31..700abeb4945e 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -31,8 +31,6 @@ drm-$(CONFIG_DEBUG_FS) += drm_debugfs.o drm_debugfs_crc.o drm-$(CONFIG_DRM_LOAD_EDID_FIRMWARE) += drm_edid_load.o drm-$(CONFIG_DRM_PRIVACY_SCREEN) += drm_privacy_screen.o drm_privacy_screen_x86.o -obj-$(CONFIG_DRM_DP_AUX_BUS) += drm_dp_aux_bus.o - obj-$(CONFIG_DRM_NOMODESET) += drm_nomodeset.o drm_cma_helper-y := drm_gem_cma_helper.o diff --git a/drivers/gpu/drm/bridge/parade-ps8640.c b/drivers/gpu/drm/bridge/parade-ps8640.c index 3e0c7436f407..3f17337ee389 100644 --- a/drivers/gpu/drm/bridge/parade-ps8640.c +++ b/drivers/gpu/drm/bridge/parade-ps8640.c @@ -14,7 +14,7 @@ #include #include -#include +#include #include #include #include diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index 65d25ce30ce5..ba136a188be7 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/drivers/gpu/drm/dp/Makefile b/drivers/gpu/drm/dp/Makefile index 5b892aeff5ab..75faffc706b1 100644 --- a/drivers/gpu/drm/dp/Makefile +++ b/drivers/gpu/drm/dp/Makefile @@ -1,5 +1,7 @@ # SPDX-License-Identifier: MIT +obj-$(CONFIG_DRM_DP_AUX_BUS) += drm_dp_aux_bus.o + drm_dp_helper-y := drm_dp.o drm_dp_dual_mode_helper.o drm_dp_helper_mod.o drm_dp_mst_topology.o drm_dp_helper-$(CONFIG_DRM_DP_AUX_CHARDEV) += drm_dp_aux_dev.o drm_dp_helper-$(CONFIG_DRM_DP_CEC) += drm_dp_cec.o diff --git a/drivers/gpu/drm/dp/drm_dp_aux_bus.c b/drivers/gpu/drm/dp/drm_dp_aux_bus.c new file mode 100644 index 000000000000..415afce3cf96 --- /dev/null +++ b/drivers/gpu/drm/dp/drm_dp_aux_bus.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2021 Google Inc. + * + * The DP AUX bus is used for devices that are connected over a DisplayPort + * AUX bus. The devices on the far side of the bus are referred to as + * endpoints in this code. + * + * Commonly there is only one device connected to the DP AUX bus: a panel. + * Though historically panels (even DP panels) have been modeled as simple + * platform devices, putting them under the DP AUX bus allows the panel driver + * to perform transactions on that bus. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +/** + * dp_aux_ep_match() - The match function for the dp_aux_bus. + * @dev: The device to match. + * @drv: The driver to try to match against. + * + * At the moment, we just match on device tree. + * + * Return: True if this driver matches this device; false otherwise. + */ +static int dp_aux_ep_match(struct device *dev, struct device_driver *drv) +{ + return !!of_match_device(drv->of_match_table, dev); +} + +/** + * dp_aux_ep_probe() - The probe function for the dp_aux_bus. + * @dev: The device to probe. + * + * Calls through to the endpoint driver probe. + * + * Return: 0 if no error or negative error code. + */ +static int dp_aux_ep_probe(struct device *dev) +{ + struct dp_aux_ep_driver *aux_ep_drv = to_dp_aux_ep_drv(dev->driver); + struct dp_aux_ep_device *aux_ep = to_dp_aux_ep_dev(dev); + int ret; + + ret = dev_pm_domain_attach(dev, true); + if (ret) + return dev_err_probe(dev, ret, "Failed to attach to PM Domain\n"); + + ret = aux_ep_drv->probe(aux_ep); + if (ret) + dev_pm_domain_detach(dev, true); + + return ret; +} + +/** + * dp_aux_ep_remove() - The remove function for the dp_aux_bus. + * @dev: The device to remove. + * + * Calls through to the endpoint driver remove. + * + */ +static void dp_aux_ep_remove(struct device *dev) +{ + struct dp_aux_ep_driver *aux_ep_drv = to_dp_aux_ep_drv(dev->driver); + struct dp_aux_ep_device *aux_ep = to_dp_aux_ep_dev(dev); + + if (aux_ep_drv->remove) + aux_ep_drv->remove(aux_ep); + dev_pm_domain_detach(dev, true); +} + +/** + * dp_aux_ep_shutdown() - The shutdown function for the dp_aux_bus. + * @dev: The device to shutdown. + * + * Calls through to the endpoint driver shutdown. + */ +static void dp_aux_ep_shutdown(struct device *dev) +{ + struct dp_aux_ep_driver *aux_ep_drv; + + if (!dev->driver) + return; + + aux_ep_drv = to_dp_aux_ep_drv(dev->driver); + if (aux_ep_drv->shutdown) + aux_ep_drv->shutdown(to_dp_aux_ep_dev(dev)); +} + +static struct bus_type dp_aux_bus_type = { + .name = "dp-aux", + .match = dp_aux_ep_match, + .probe = dp_aux_ep_probe, + .remove = dp_aux_ep_remove, + .shutdown = dp_aux_ep_shutdown, +}; + +static ssize_t modalias_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return of_device_modalias(dev, buf, PAGE_SIZE); +} +static DEVICE_ATTR_RO(modalias); + +static struct attribute *dp_aux_ep_dev_attrs[] = { + &dev_attr_modalias.attr, + NULL, +}; +ATTRIBUTE_GROUPS(dp_aux_ep_dev); + +/** + * dp_aux_ep_dev_release() - Free memory for the dp_aux_ep device + * @dev: The device to free. + * + * Return: 0 if no error or negative error code. + */ +static void dp_aux_ep_dev_release(struct device *dev) +{ + kfree(to_dp_aux_ep_dev(dev)); +} + +static struct device_type dp_aux_device_type_type = { + .groups = dp_aux_ep_dev_groups, + .uevent = of_device_uevent_modalias, + .release = dp_aux_ep_dev_release, +}; + +/** + * of_dp_aux_ep_destroy() - Destroy an DP AUX endpoint device + * @dev: The device to destroy. + * @data: Not used + * + * This is just used as a callback by of_dp_aux_depopulate_ep_devices() and + * is called for _all_ of the child devices of the device providing the AUX bus. + * We'll only act on those that are of type "dp_aux_bus_type". + * + * This function is effectively an inverse of what's in the loop + * in of_dp_aux_populate_ep_devices(). + * + * Return: 0 if no error or negative error code. + */ +static int of_dp_aux_ep_destroy(struct device *dev, void *data) +{ + struct device_node *np = dev->of_node; + + if (dev->bus != &dp_aux_bus_type) + return 0; + + if (!of_node_check_flag(np, OF_POPULATED)) + return 0; + + of_node_clear_flag(np, OF_POPULATED); + of_node_put(np); + + device_unregister(dev); + + return 0; +} + +/** + * of_dp_aux_depopulate_ep_devices() - Undo of_dp_aux_populate_ep_devices + * @aux: The AUX channel whose devices we want to depopulate + * + * This will destroy all devices that were created + * by of_dp_aux_populate_ep_devices(). + */ +void of_dp_aux_depopulate_ep_devices(struct drm_dp_aux *aux) +{ + device_for_each_child_reverse(aux->dev, NULL, of_dp_aux_ep_destroy); +} +EXPORT_SYMBOL_GPL(of_dp_aux_depopulate_ep_devices); + +/** + * of_dp_aux_populate_ep_devices() - Populate the endpoint devices on the DP AUX + * @aux: The AUX channel whose devices we want to populate. It is required that + * drm_dp_aux_init() has already been called for this AUX channel. + * + * This will populate all the devices under the "aux-bus" node of the device + * providing the AUX channel (AKA aux->dev). + * + * When this function finishes, it is _possible_ (but not guaranteed) that + * our sub-devices will have finished probing. It should be noted that if our + * sub-devices return -EPROBE_DEFER that we will not return any error codes + * ourselves but our sub-devices will _not_ have actually probed successfully + * yet. There may be other cases (maybe added in the future?) where sub-devices + * won't have been probed yet when this function returns, so it's best not to + * rely on that. + * + * If this function succeeds you should later make sure you call + * of_dp_aux_depopulate_ep_devices() to undo it, or just use the devm version + * of this function. + * + * Return: 0 if no error or negative error code. + */ +int of_dp_aux_populate_ep_devices(struct drm_dp_aux *aux) +{ + struct device_node *bus, *np; + struct dp_aux_ep_device *aux_ep; + int ret; + + /* drm_dp_aux_init() should have been called already; warn if not */ + WARN_ON_ONCE(!aux->ddc.algo); + + if (!aux->dev->of_node) + return 0; + + bus = of_get_child_by_name(aux->dev->of_node, "aux-bus"); + if (!bus) + return 0; + + for_each_available_child_of_node(bus, np) { + if (of_node_test_and_set_flag(np, OF_POPULATED)) + continue; + + aux_ep = kzalloc(sizeof(*aux_ep), GFP_KERNEL); + if (!aux_ep) + continue; + aux_ep->aux = aux; + + aux_ep->dev.parent = aux->dev; + aux_ep->dev.bus = &dp_aux_bus_type; + aux_ep->dev.type = &dp_aux_device_type_type; + aux_ep->dev.of_node = of_node_get(np); + dev_set_name(&aux_ep->dev, "aux-%s", dev_name(aux->dev)); + + ret = device_register(&aux_ep->dev); + if (ret) { + dev_err(aux->dev, "Failed to create AUX EP for %pOF: %d\n", np, ret); + of_node_clear_flag(np, OF_POPULATED); + of_node_put(np); + + /* + * As per docs of device_register(), call this instead + * of kfree() directly for error cases. + */ + put_device(&aux_ep->dev); + + /* + * Following in the footsteps of of_i2c_register_devices(), + * we won't fail the whole function here--we'll just + * continue registering any other devices we find. + */ + } + } + + of_node_put(bus); + + return 0; +} + +static void of_dp_aux_depopulate_ep_devices_void(void *data) +{ + of_dp_aux_depopulate_ep_devices(data); +} + +/** + * devm_of_dp_aux_populate_ep_devices() - devm wrapper for of_dp_aux_populate_ep_devices() + * @aux: The AUX channel whose devices we want to populate + * + * Handles freeing w/ devm on the device "aux->dev". + * + * Return: 0 if no error or negative error code. + */ +int devm_of_dp_aux_populate_ep_devices(struct drm_dp_aux *aux) +{ + int ret; + + ret = of_dp_aux_populate_ep_devices(aux); + if (ret) + return ret; + + return devm_add_action_or_reset(aux->dev, + of_dp_aux_depopulate_ep_devices_void, + aux); +} +EXPORT_SYMBOL_GPL(devm_of_dp_aux_populate_ep_devices); + +int __dp_aux_dp_driver_register(struct dp_aux_ep_driver *drv, struct module *owner) +{ + drv->driver.owner = owner; + drv->driver.bus = &dp_aux_bus_type; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(__dp_aux_dp_driver_register); + +void dp_aux_dp_driver_unregister(struct dp_aux_ep_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(dp_aux_dp_driver_unregister); + +static int __init dp_aux_bus_init(void) +{ + int ret; + + ret = bus_register(&dp_aux_bus_type); + if (ret) + return ret; + + return 0; +} + +static void __exit dp_aux_bus_exit(void) +{ + bus_unregister(&dp_aux_bus_type); +} + +subsys_initcall(dp_aux_bus_init); +module_exit(dp_aux_bus_exit); + +MODULE_AUTHOR("Douglas Anderson "); +MODULE_DESCRIPTION("DRM DisplayPort AUX bus"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/drm_dp_aux_bus.c b/drivers/gpu/drm/drm_dp_aux_bus.c deleted file mode 100644 index 565edf6b1732..000000000000 --- a/drivers/gpu/drm/drm_dp_aux_bus.c +++ /dev/null @@ -1,323 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright 2021 Google Inc. - * - * The DP AUX bus is used for devices that are connected over a DisplayPort - * AUX bus. The devices on the far side of the bus are referred to as - * endpoints in this code. - * - * Commonly there is only one device connected to the DP AUX bus: a panel. - * Though historically panels (even DP panels) have been modeled as simple - * platform devices, putting them under the DP AUX bus allows the panel driver - * to perform transactions on that bus. - */ - -#include -#include -#include -#include -#include -#include - -#include -#include - -/** - * dp_aux_ep_match() - The match function for the dp_aux_bus. - * @dev: The device to match. - * @drv: The driver to try to match against. - * - * At the moment, we just match on device tree. - * - * Return: True if this driver matches this device; false otherwise. - */ -static int dp_aux_ep_match(struct device *dev, struct device_driver *drv) -{ - return !!of_match_device(drv->of_match_table, dev); -} - -/** - * dp_aux_ep_probe() - The probe function for the dp_aux_bus. - * @dev: The device to probe. - * - * Calls through to the endpoint driver probe. - * - * Return: 0 if no error or negative error code. - */ -static int dp_aux_ep_probe(struct device *dev) -{ - struct dp_aux_ep_driver *aux_ep_drv = to_dp_aux_ep_drv(dev->driver); - struct dp_aux_ep_device *aux_ep = to_dp_aux_ep_dev(dev); - int ret; - - ret = dev_pm_domain_attach(dev, true); - if (ret) - return dev_err_probe(dev, ret, "Failed to attach to PM Domain\n"); - - ret = aux_ep_drv->probe(aux_ep); - if (ret) - dev_pm_domain_detach(dev, true); - - return ret; -} - -/** - * dp_aux_ep_remove() - The remove function for the dp_aux_bus. - * @dev: The device to remove. - * - * Calls through to the endpoint driver remove. - * - */ -static void dp_aux_ep_remove(struct device *dev) -{ - struct dp_aux_ep_driver *aux_ep_drv = to_dp_aux_ep_drv(dev->driver); - struct dp_aux_ep_device *aux_ep = to_dp_aux_ep_dev(dev); - - if (aux_ep_drv->remove) - aux_ep_drv->remove(aux_ep); - dev_pm_domain_detach(dev, true); -} - -/** - * dp_aux_ep_shutdown() - The shutdown function for the dp_aux_bus. - * @dev: The device to shutdown. - * - * Calls through to the endpoint driver shutdown. - */ -static void dp_aux_ep_shutdown(struct device *dev) -{ - struct dp_aux_ep_driver *aux_ep_drv; - - if (!dev->driver) - return; - - aux_ep_drv = to_dp_aux_ep_drv(dev->driver); - if (aux_ep_drv->shutdown) - aux_ep_drv->shutdown(to_dp_aux_ep_dev(dev)); -} - -static struct bus_type dp_aux_bus_type = { - .name = "dp-aux", - .match = dp_aux_ep_match, - .probe = dp_aux_ep_probe, - .remove = dp_aux_ep_remove, - .shutdown = dp_aux_ep_shutdown, -}; - -static ssize_t modalias_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - return of_device_modalias(dev, buf, PAGE_SIZE); -} -static DEVICE_ATTR_RO(modalias); - -static struct attribute *dp_aux_ep_dev_attrs[] = { - &dev_attr_modalias.attr, - NULL, -}; -ATTRIBUTE_GROUPS(dp_aux_ep_dev); - -/** - * dp_aux_ep_dev_release() - Free memory for the dp_aux_ep device - * @dev: The device to free. - * - * Return: 0 if no error or negative error code. - */ -static void dp_aux_ep_dev_release(struct device *dev) -{ - kfree(to_dp_aux_ep_dev(dev)); -} - -static struct device_type dp_aux_device_type_type = { - .groups = dp_aux_ep_dev_groups, - .uevent = of_device_uevent_modalias, - .release = dp_aux_ep_dev_release, -}; - -/** - * of_dp_aux_ep_destroy() - Destroy an DP AUX endpoint device - * @dev: The device to destroy. - * @data: Not used - * - * This is just used as a callback by of_dp_aux_depopulate_ep_devices() and - * is called for _all_ of the child devices of the device providing the AUX bus. - * We'll only act on those that are of type "dp_aux_bus_type". - * - * This function is effectively an inverse of what's in the loop - * in of_dp_aux_populate_ep_devices(). - * - * Return: 0 if no error or negative error code. - */ -static int of_dp_aux_ep_destroy(struct device *dev, void *data) -{ - struct device_node *np = dev->of_node; - - if (dev->bus != &dp_aux_bus_type) - return 0; - - if (!of_node_check_flag(np, OF_POPULATED)) - return 0; - - of_node_clear_flag(np, OF_POPULATED); - of_node_put(np); - - device_unregister(dev); - - return 0; -} - -/** - * of_dp_aux_depopulate_ep_devices() - Undo of_dp_aux_populate_ep_devices - * @aux: The AUX channel whose devices we want to depopulate - * - * This will destroy all devices that were created - * by of_dp_aux_populate_ep_devices(). - */ -void of_dp_aux_depopulate_ep_devices(struct drm_dp_aux *aux) -{ - device_for_each_child_reverse(aux->dev, NULL, of_dp_aux_ep_destroy); -} -EXPORT_SYMBOL_GPL(of_dp_aux_depopulate_ep_devices); - -/** - * of_dp_aux_populate_ep_devices() - Populate the endpoint devices on the DP AUX - * @aux: The AUX channel whose devices we want to populate. It is required that - * drm_dp_aux_init() has already been called for this AUX channel. - * - * This will populate all the devices under the "aux-bus" node of the device - * providing the AUX channel (AKA aux->dev). - * - * When this function finishes, it is _possible_ (but not guaranteed) that - * our sub-devices will have finished probing. It should be noted that if our - * sub-devices return -EPROBE_DEFER that we will not return any error codes - * ourselves but our sub-devices will _not_ have actually probed successfully - * yet. There may be other cases (maybe added in the future?) where sub-devices - * won't have been probed yet when this function returns, so it's best not to - * rely on that. - * - * If this function succeeds you should later make sure you call - * of_dp_aux_depopulate_ep_devices() to undo it, or just use the devm version - * of this function. - * - * Return: 0 if no error or negative error code. - */ -int of_dp_aux_populate_ep_devices(struct drm_dp_aux *aux) -{ - struct device_node *bus, *np; - struct dp_aux_ep_device *aux_ep; - int ret; - - /* drm_dp_aux_init() should have been called already; warn if not */ - WARN_ON_ONCE(!aux->ddc.algo); - - if (!aux->dev->of_node) - return 0; - - bus = of_get_child_by_name(aux->dev->of_node, "aux-bus"); - if (!bus) - return 0; - - for_each_available_child_of_node(bus, np) { - if (of_node_test_and_set_flag(np, OF_POPULATED)) - continue; - - aux_ep = kzalloc(sizeof(*aux_ep), GFP_KERNEL); - if (!aux_ep) - continue; - aux_ep->aux = aux; - - aux_ep->dev.parent = aux->dev; - aux_ep->dev.bus = &dp_aux_bus_type; - aux_ep->dev.type = &dp_aux_device_type_type; - aux_ep->dev.of_node = of_node_get(np); - dev_set_name(&aux_ep->dev, "aux-%s", dev_name(aux->dev)); - - ret = device_register(&aux_ep->dev); - if (ret) { - dev_err(aux->dev, "Failed to create AUX EP for %pOF: %d\n", np, ret); - of_node_clear_flag(np, OF_POPULATED); - of_node_put(np); - - /* - * As per docs of device_register(), call this instead - * of kfree() directly for error cases. - */ - put_device(&aux_ep->dev); - - /* - * Following in the footsteps of of_i2c_register_devices(), - * we won't fail the whole function here--we'll just - * continue registering any other devices we find. - */ - } - } - - of_node_put(bus); - - return 0; -} - -static void of_dp_aux_depopulate_ep_devices_void(void *data) -{ - of_dp_aux_depopulate_ep_devices(data); -} - -/** - * devm_of_dp_aux_populate_ep_devices() - devm wrapper for of_dp_aux_populate_ep_devices() - * @aux: The AUX channel whose devices we want to populate - * - * Handles freeing w/ devm on the device "aux->dev". - * - * Return: 0 if no error or negative error code. - */ -int devm_of_dp_aux_populate_ep_devices(struct drm_dp_aux *aux) -{ - int ret; - - ret = of_dp_aux_populate_ep_devices(aux); - if (ret) - return ret; - - return devm_add_action_or_reset(aux->dev, - of_dp_aux_depopulate_ep_devices_void, - aux); -} -EXPORT_SYMBOL_GPL(devm_of_dp_aux_populate_ep_devices); - -int __dp_aux_dp_driver_register(struct dp_aux_ep_driver *drv, struct module *owner) -{ - drv->driver.owner = owner; - drv->driver.bus = &dp_aux_bus_type; - - return driver_register(&drv->driver); -} -EXPORT_SYMBOL_GPL(__dp_aux_dp_driver_register); - -void dp_aux_dp_driver_unregister(struct dp_aux_ep_driver *drv) -{ - driver_unregister(&drv->driver); -} -EXPORT_SYMBOL_GPL(dp_aux_dp_driver_unregister); - -static int __init dp_aux_bus_init(void) -{ - int ret; - - ret = bus_register(&dp_aux_bus_type); - if (ret) - return ret; - - return 0; -} - -static void __exit dp_aux_bus_exit(void) -{ - bus_unregister(&dp_aux_bus_type); -} - -subsys_initcall(dp_aux_bus_init); -module_exit(dp_aux_bus_exit); - -MODULE_AUTHOR("Douglas Anderson "); -MODULE_DESCRIPTION("DRM DisplayPort AUX bus"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-edp.c b/drivers/gpu/drm/panel/panel-edp.c index 6a6ca891ee2e..99ca1bd0091c 100644 --- a/drivers/gpu/drm/panel/panel-edp.c +++ b/drivers/gpu/drm/panel/panel-edp.c @@ -36,7 +36,7 @@ #include #include -#include +#include #include #include diff --git a/drivers/gpu/drm/panel/panel-samsung-atna33xc20.c b/drivers/gpu/drm/panel/panel-samsung-atna33xc20.c index bffeadaaf9a2..20666b6217e7 100644 --- a/drivers/gpu/drm/panel/panel-samsung-atna33xc20.c +++ b/drivers/gpu/drm/panel/panel-samsung-atna33xc20.c @@ -14,7 +14,7 @@ #include #include -#include +#include #include #include #include -- cgit From 269332997a160b3785690a32d2c5496bce7dae51 Mon Sep 17 00:00:00 2001 From: owen Date: Mon, 17 Jan 2022 18:09:49 +0800 Subject: drm/bridge: anx7625: Return -EPROBE_DEFER if the dsi host was not found It will connect to the mipi dsi host and find the corresponding mipi dsi host node, but the node registered by the mipi dsi host has not been loaded yet. of_find_mipi_dsi_host_by_node() returns -EINVAL which causes the calling driver to fail. If the anx7625 driver is loaded afterwards the driver requesting the mipi dsi host will not notice this. Better approach is to return -EPROBE_DEFER in such case. Then when the anx7625 driver appears the driver requesting the mipi dsi host will be probed again. Signed-off-by: owen Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20220117100949.9542-1-qwt9588@gamil.com Reviewed-by: Robert Foss --- drivers/gpu/drm/bridge/analogix/anx7625.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c index 0528c3c6168d..76662fce4ce6 100644 --- a/drivers/gpu/drm/bridge/analogix/anx7625.c +++ b/drivers/gpu/drm/bridge/analogix/anx7625.c @@ -1917,7 +1917,7 @@ static int anx7625_attach_dsi(struct anx7625_data *ctx) host = of_find_mipi_dsi_host_by_node(ctx->pdata.mipi_host_node); if (!host) { DRM_DEV_ERROR(dev, "fail to find dsi host.\n"); - return -EINVAL; + return -EPROBE_DEFER; } dsi = devm_mipi_dsi_device_register_full(dev, host, &info); -- cgit From 2b6dd600dd72573c23ea180b5b0b2f1813405882 Mon Sep 17 00:00:00 2001 From: Pavel Skripkin Date: Thu, 30 Dec 2021 17:26:49 +0300 Subject: udmabuf: validate ubuf->pagecount Syzbot has reported GPF in sg_alloc_append_table_from_pages(). The problem was in ubuf->pages == ZERO_PTR. ubuf->pagecount is calculated from arguments passed from user-space. If user creates udmabuf with list.size == 0 then ubuf->pagecount will be also equal to zero; it causes kmalloc_array() to return ZERO_PTR. Fix it by validating ubuf->pagecount before passing it to kmalloc_array(). Fixes: fbb0de795078 ("Add udmabuf misc device") Reported-and-tested-by: syzbot+2c56b725ec547fa9cb29@syzkaller.appspotmail.com Signed-off-by: Pavel Skripkin Link: http://patchwork.freedesktop.org/patch/msgid/20211230142649.23022-1-paskripkin@gmail.com Signed-off-by: Gerd Hoffmann --- drivers/dma-buf/udmabuf.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/dma-buf/udmabuf.c b/drivers/dma-buf/udmabuf.c index c57a609db75b..e7330684d3b8 100644 --- a/drivers/dma-buf/udmabuf.c +++ b/drivers/dma-buf/udmabuf.c @@ -190,6 +190,10 @@ static long udmabuf_create(struct miscdevice *device, if (ubuf->pagecount > pglimit) goto err; } + + if (!ubuf->pagecount) + goto err; + ubuf->pages = kmalloc_array(ubuf->pagecount, sizeof(*ubuf->pages), GFP_KERNEL); if (!ubuf->pages) { -- cgit From 6b79f96f4a23846516e5e6e4dd37fc06f43a60dd Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Mon, 13 Dec 2021 19:31:22 +0100 Subject: drm/virtio: Ensure that objs is not NULL in virtio_gpu_array_put_free() If virtio_gpu_object_shmem_init() fails (e.g. due to fault injection, as it happened in the bug report by syzbot), virtio_gpu_array_put_free() could be called with objs equal to NULL. Ensure that objs is not NULL in virtio_gpu_array_put_free(), or otherwise return from the function. Cc: stable@vger.kernel.org # 5.13.x Signed-off-by: Roberto Sassu Reported-by: syzbot+e9072e90624a31dfa85f@syzkaller.appspotmail.com Fixes: 377f8331d0565 ("drm/virtio: fix possible leak/unlock virtio_gpu_object_array") Link: http://patchwork.freedesktop.org/patch/msgid/20211213183122.838119-1-roberto.sassu@huawei.com Signed-off-by: Gerd Hoffmann --- drivers/gpu/drm/virtio/virtgpu_gem.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/virtio/virtgpu_gem.c b/drivers/gpu/drm/virtio/virtgpu_gem.c index 2de61b63ef91..48d3c9955f0d 100644 --- a/drivers/gpu/drm/virtio/virtgpu_gem.c +++ b/drivers/gpu/drm/virtio/virtgpu_gem.c @@ -248,6 +248,9 @@ void virtio_gpu_array_put_free(struct virtio_gpu_object_array *objs) { u32 i; + if (!objs) + return; + for (i = 0; i < objs->nents; i++) drm_gem_object_put(objs->objs[i]); virtio_gpu_array_free(objs); -- cgit From e849f7e708600a9d7567ae22f945b5b01d7f7401 Mon Sep 17 00:00:00 2001 From: Maarten Lankhorst Date: Fri, 14 Jan 2022 14:23:15 +0100 Subject: drm/i915: Call i915_gem_evict_vm in vm_fault_gtt to prevent new ENOSPC errors, v2. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that we cannot unbind kill the currently locked object directly because we're removing short term pinning, we may have to unbind the object from gtt manually, using a i915_gem_evict_vm() call. Changes since v1: - Remove -ENOSPC warning, can still happen with concurrent mmaps where we can't unbind the other mmap because of the lock held. This fixes the gem_mmap_gtt@cpuset tests. Signed-off-by: Maarten Lankhorst Reviewed-by: Thomas Hellström Link: https://patchwork.freedesktop.org/patch/msgid/20220114132320.109030-2-maarten.lankhorst@linux.intel.com --- drivers/gpu/drm/i915/gem/i915_gem_mman.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gem/i915_gem_mman.c b/drivers/gpu/drm/i915/gem/i915_gem_mman.c index 5ac2506f4ee8..fafd158e5313 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_mman.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_mman.c @@ -21,6 +21,7 @@ #include "i915_trace.h" #include "i915_user_extensions.h" #include "i915_gem_ttm.h" +#include "i915_gem_evict.h" #include "i915_vma.h" static inline bool @@ -358,8 +359,21 @@ retry: vma = i915_gem_object_ggtt_pin_ww(obj, &ww, &view, 0, 0, flags); } - /* The entire mappable GGTT is pinned? Unexpected! */ - GEM_BUG_ON(vma == ERR_PTR(-ENOSPC)); + /* + * The entire mappable GGTT is pinned? Unexpected! + * Try to evict the object we locked too, as normally we skip it + * due to lack of short term pinning inside execbuf. + */ + if (vma == ERR_PTR(-ENOSPC)) { + ret = mutex_lock_interruptible(&ggtt->vm.mutex); + if (!ret) { + ret = i915_gem_evict_vm(&ggtt->vm); + mutex_unlock(&ggtt->vm.mutex); + } + if (ret) + goto err_reset; + vma = i915_gem_object_ggtt_pin_ww(obj, &ww, &view, 0, 0, flags); + } } if (IS_ERR(vma)) { ret = PTR_ERR(vma); -- cgit From 6945c53bc712cf4a28a46fe46c2bd8526ea261d1 Mon Sep 17 00:00:00 2001 From: Maarten Lankhorst Date: Mon, 17 Jan 2022 08:56:04 +0100 Subject: drm/i915: Add locking to i915_gem_evict_vm(), v3. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit i915_gem_evict_vm will need to be able to evict objects that are locked by the current ctx. By testing if the current context already locked the object, we can do this correctly. This allows us to evict the entire vm even if we already hold some objects' locks. Previously, this was spread over several commits, but it makes more sense to commit the changes to i915_gem_evict_vm separately from the changes to i915_gem_evict_something() and i915_gem_evict_for_node(). Changes since v1: - Handle evicting dead objects better. Changes since v2: - Use for_i915_gem_ww in igt_evict_vm. (Thomas) Signed-off-by: Maarten Lankhorst Reviewed-by: Thomas Hellström [mlankhorst: Fix up doc warning.] Link: https://patchwork.freedesktop.org/patch/msgid/20220117075604.131477-1-maarten.lankhorst@linux.intel.com --- drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c | 2 +- drivers/gpu/drm/i915/gem/i915_gem_mman.c | 3 +-- drivers/gpu/drm/i915/i915_drv.h | 3 ++- drivers/gpu/drm/i915/i915_gem_evict.c | 34 +++++++++++++++++++++++-- drivers/gpu/drm/i915/i915_vma.c | 7 ++++- drivers/gpu/drm/i915/selftests/i915_gem_evict.c | 12 ++++++--- 6 files changed, 50 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c index 2065e5e44fac..97b9fd7afbef 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c @@ -754,7 +754,7 @@ static int eb_reserve(struct i915_execbuffer *eb) case 1: /* Too fragmented, unbind everything and retry */ mutex_lock(&eb->context->vm->mutex); - err = i915_gem_evict_vm(eb->context->vm); + err = i915_gem_evict_vm(eb->context->vm, &eb->ww); mutex_unlock(&eb->context->vm->mutex); if (err) return err; diff --git a/drivers/gpu/drm/i915/gem/i915_gem_mman.c b/drivers/gpu/drm/i915/gem/i915_gem_mman.c index fafd158e5313..4afad1604a6a 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_mman.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_mman.c @@ -21,7 +21,6 @@ #include "i915_trace.h" #include "i915_user_extensions.h" #include "i915_gem_ttm.h" -#include "i915_gem_evict.h" #include "i915_vma.h" static inline bool @@ -367,7 +366,7 @@ retry: if (vma == ERR_PTR(-ENOSPC)) { ret = mutex_lock_interruptible(&ggtt->vm.mutex); if (!ret) { - ret = i915_gem_evict_vm(&ggtt->vm); + ret = i915_gem_evict_vm(&ggtt->vm, &ww); mutex_unlock(&ggtt->vm.mutex); } if (ret) diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index b61a1c785739..deb7b82a7cf1 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -1742,7 +1742,8 @@ int __must_check i915_gem_evict_something(struct i915_address_space *vm, int __must_check i915_gem_evict_for_node(struct i915_address_space *vm, struct drm_mm_node *node, unsigned int flags); -int i915_gem_evict_vm(struct i915_address_space *vm); +int i915_gem_evict_vm(struct i915_address_space *vm, + struct i915_gem_ww_ctx *ww); /* i915_gem_internal.c */ struct drm_i915_gem_object * diff --git a/drivers/gpu/drm/i915/i915_gem_evict.c b/drivers/gpu/drm/i915/i915_gem_evict.c index 2b73ddb11c66..670dceaa9b24 100644 --- a/drivers/gpu/drm/i915/i915_gem_evict.c +++ b/drivers/gpu/drm/i915/i915_gem_evict.c @@ -358,6 +358,8 @@ int i915_gem_evict_for_node(struct i915_address_space *vm, /** * i915_gem_evict_vm - Evict all idle vmas from a vm * @vm: Address space to cleanse + * @ww: An optional struct i915_gem_ww_ctx. If not NULL, i915_gem_evict_vm + * will be able to evict vma's locked by the ww as well. * * This function evicts all vmas from a vm. * @@ -367,7 +369,7 @@ int i915_gem_evict_for_node(struct i915_address_space *vm, * To clarify: This is for freeing up virtual address space, not for freeing * memory in e.g. the shrinker. */ -int i915_gem_evict_vm(struct i915_address_space *vm) +int i915_gem_evict_vm(struct i915_address_space *vm, struct i915_gem_ww_ctx *ww) { int ret = 0; @@ -388,24 +390,52 @@ int i915_gem_evict_vm(struct i915_address_space *vm) do { struct i915_vma *vma, *vn; LIST_HEAD(eviction_list); + LIST_HEAD(locked_eviction_list); list_for_each_entry(vma, &vm->bound_list, vm_link) { if (i915_vma_is_pinned(vma)) continue; + /* + * If we already own the lock, trylock fails. In case + * the resv is shared among multiple objects, we still + * need the object ref. + */ + if (!kref_read(&vma->obj->base.refcount) || + (ww && (dma_resv_locking_ctx(vma->obj->base.resv) == &ww->ctx))) { + __i915_vma_pin(vma); + list_add(&vma->evict_link, &locked_eviction_list); + continue; + } + + if (!i915_gem_object_trylock(vma->obj, ww)) + continue; + __i915_vma_pin(vma); list_add(&vma->evict_link, &eviction_list); } - if (list_empty(&eviction_list)) + if (list_empty(&eviction_list) && list_empty(&locked_eviction_list)) break; ret = 0; + /* Unbind locked objects first, before unlocking the eviction_list */ + list_for_each_entry_safe(vma, vn, &locked_eviction_list, evict_link) { + __i915_vma_unpin(vma); + + if (ret == 0) + ret = __i915_vma_unbind(vma); + if (ret != -EINTR) /* "Get me out of here!" */ + ret = 0; + } + list_for_each_entry_safe(vma, vn, &eviction_list, evict_link) { __i915_vma_unpin(vma); if (ret == 0) ret = __i915_vma_unbind(vma); if (ret != -EINTR) /* "Get me out of here!" */ ret = 0; + + i915_gem_object_unlock(vma->obj); } } while (ret == 0); diff --git a/drivers/gpu/drm/i915/i915_vma.c b/drivers/gpu/drm/i915/i915_vma.c index 9d859b0a3fbe..8bbc08a0e88f 100644 --- a/drivers/gpu/drm/i915/i915_vma.c +++ b/drivers/gpu/drm/i915/i915_vma.c @@ -1479,7 +1479,12 @@ static int __i915_ggtt_pin(struct i915_vma *vma, struct i915_gem_ww_ctx *ww, /* Unlike i915_vma_pin, we don't take no for an answer! */ flush_idle_contexts(vm->gt); if (mutex_lock_interruptible(&vm->mutex) == 0) { - i915_gem_evict_vm(vm); + /* + * We pass NULL ww here, as we don't want to unbind + * locked objects when called from execbuf when pinning + * is removed. This would probably regress badly. + */ + i915_gem_evict_vm(vm, NULL); mutex_unlock(&vm->mutex); } } while (1); diff --git a/drivers/gpu/drm/i915/selftests/i915_gem_evict.c b/drivers/gpu/drm/i915/selftests/i915_gem_evict.c index 75b709c26dd3..19a348546b73 100644 --- a/drivers/gpu/drm/i915/selftests/i915_gem_evict.c +++ b/drivers/gpu/drm/i915/selftests/i915_gem_evict.c @@ -331,6 +331,7 @@ static int igt_evict_vm(void *arg) { struct intel_gt *gt = arg; struct i915_ggtt *ggtt = gt->ggtt; + struct i915_gem_ww_ctx ww; LIST_HEAD(objects); int err; @@ -342,7 +343,7 @@ static int igt_evict_vm(void *arg) /* Everything is pinned, nothing should happen */ mutex_lock(&ggtt->vm.mutex); - err = i915_gem_evict_vm(&ggtt->vm); + err = i915_gem_evict_vm(&ggtt->vm, NULL); mutex_unlock(&ggtt->vm.mutex); if (err) { pr_err("i915_gem_evict_vm on a full GGTT returned err=%d]\n", @@ -352,9 +353,12 @@ static int igt_evict_vm(void *arg) unpin_ggtt(ggtt); - mutex_lock(&ggtt->vm.mutex); - err = i915_gem_evict_vm(&ggtt->vm); - mutex_unlock(&ggtt->vm.mutex); + for_i915_gem_ww(&ww, err, false) { + mutex_lock(&ggtt->vm.mutex); + err = i915_gem_evict_vm(&ggtt->vm, &ww); + mutex_unlock(&ggtt->vm.mutex); + } + if (err) { pr_err("i915_gem_evict_vm on a full GGTT returned err=%d]\n", err); -- cgit From 7e00897be8bf13ef9c68c95a8e386b714c29ad95 Mon Sep 17 00:00:00 2001 From: Maarten Lankhorst Date: Fri, 14 Jan 2022 14:23:17 +0100 Subject: drm/i915: Add object locking to i915_gem_evict_for_node and i915_gem_evict_something, v2. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because we will start to require the obj->resv lock for unbinding, ensure these vma eviction utility functions also take the lock. This requires some function signature changes, to ensure that the ww context is passed around, but is mostly straightforward. Previously this was split up into several patches, but reworking should allow for easier bisection. Changes since v1: - Handle evicting dead objects better. Signed-off-by: Maarten Lankhorst Reviewed-by: Thomas Hellström Link: https://patchwork.freedesktop.org/patch/msgid/20220114132320.109030-4-maarten.lankhorst@linux.intel.com --- drivers/gpu/drm/i915/gt/intel_ggtt.c | 2 +- drivers/gpu/drm/i915/gt/selftest_hangcheck.c | 2 +- drivers/gpu/drm/i915/gvt/aperture_gm.c | 2 +- drivers/gpu/drm/i915/i915_drv.h | 2 + drivers/gpu/drm/i915/i915_gem_evict.c | 69 ++++++++++++++++++++++--- drivers/gpu/drm/i915/i915_gem_gtt.c | 10 ++-- drivers/gpu/drm/i915/i915_gem_gtt.h | 3 ++ drivers/gpu/drm/i915/i915_vgpu.c | 2 +- drivers/gpu/drm/i915/i915_vma.c | 9 ++-- drivers/gpu/drm/i915/selftests/i915_gem_evict.c | 16 +++--- drivers/gpu/drm/i915/selftests/i915_gem_gtt.c | 6 +-- 11 files changed, 94 insertions(+), 29 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/intel_ggtt.c b/drivers/gpu/drm/i915/gt/intel_ggtt.c index a1b2761bc16e..da7f54b6fa38 100644 --- a/drivers/gpu/drm/i915/gt/intel_ggtt.c +++ b/drivers/gpu/drm/i915/gt/intel_ggtt.c @@ -506,7 +506,7 @@ static int ggtt_reserve_guc_top(struct i915_ggtt *ggtt) GEM_BUG_ON(ggtt->vm.total <= GUC_GGTT_TOP); size = ggtt->vm.total - GUC_GGTT_TOP; - ret = i915_gem_gtt_reserve(&ggtt->vm, &ggtt->uc_fw, size, + ret = i915_gem_gtt_reserve(&ggtt->vm, NULL, &ggtt->uc_fw, size, GUC_GGTT_TOP, I915_COLOR_UNEVICTABLE, PIN_NOEVICT); if (ret) diff --git a/drivers/gpu/drm/i915/gt/selftest_hangcheck.c b/drivers/gpu/drm/i915/gt/selftest_hangcheck.c index 15d63435ec4d..9c21b55b927b 100644 --- a/drivers/gpu/drm/i915/gt/selftest_hangcheck.c +++ b/drivers/gpu/drm/i915/gt/selftest_hangcheck.c @@ -1382,7 +1382,7 @@ static int evict_vma(void *data) complete(&arg->completion); mutex_lock(&vm->mutex); - err = i915_gem_evict_for_node(vm, &evict, 0); + err = i915_gem_evict_for_node(vm, NULL, &evict, 0); mutex_unlock(&vm->mutex); return err; diff --git a/drivers/gpu/drm/i915/gvt/aperture_gm.c b/drivers/gpu/drm/i915/gvt/aperture_gm.c index 0d6d59871308..c08098a167e9 100644 --- a/drivers/gpu/drm/i915/gvt/aperture_gm.c +++ b/drivers/gpu/drm/i915/gvt/aperture_gm.c @@ -63,7 +63,7 @@ static int alloc_gm(struct intel_vgpu *vgpu, bool high_gm) mutex_lock(>->ggtt->vm.mutex); mmio_hw_access_pre(gt); - ret = i915_gem_gtt_insert(>->ggtt->vm, node, + ret = i915_gem_gtt_insert(>->ggtt->vm, NULL, node, size, I915_GTT_PAGE_SIZE, I915_COLOR_UNEVICTABLE, start, end, flags); diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index deb7b82a7cf1..ac68d9c16e8e 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -1735,11 +1735,13 @@ i915_gem_vm_lookup(struct drm_i915_file_private *file_priv, u32 id) /* i915_gem_evict.c */ int __must_check i915_gem_evict_something(struct i915_address_space *vm, + struct i915_gem_ww_ctx *ww, u64 min_size, u64 alignment, unsigned long color, u64 start, u64 end, unsigned flags); int __must_check i915_gem_evict_for_node(struct i915_address_space *vm, + struct i915_gem_ww_ctx *ww, struct drm_mm_node *node, unsigned int flags); int i915_gem_evict_vm(struct i915_address_space *vm, diff --git a/drivers/gpu/drm/i915/i915_gem_evict.c b/drivers/gpu/drm/i915/i915_gem_evict.c index 670dceaa9b24..3483d4ee235b 100644 --- a/drivers/gpu/drm/i915/i915_gem_evict.c +++ b/drivers/gpu/drm/i915/i915_gem_evict.c @@ -37,6 +37,11 @@ I915_SELFTEST_DECLARE(static struct igt_evict_ctl { bool fail_if_busy:1; } igt_evict_ctl;) +static bool dying_vma(struct i915_vma *vma) +{ + return !kref_read(&vma->obj->base.refcount); +} + static int ggtt_flush(struct intel_gt *gt) { /* @@ -49,8 +54,37 @@ static int ggtt_flush(struct intel_gt *gt) return intel_gt_wait_for_idle(gt, MAX_SCHEDULE_TIMEOUT); } +static bool grab_vma(struct i915_vma *vma, struct i915_gem_ww_ctx *ww) +{ + /* + * We add the extra refcount so the object doesn't drop to zero until + * after ungrab_vma(), this way trylock is always paired with unlock. + */ + if (i915_gem_object_get_rcu(vma->obj)) { + if (!i915_gem_object_trylock(vma->obj, ww)) { + i915_gem_object_put(vma->obj); + return false; + } + } else { + /* Dead objects don't need pins */ + atomic_and(~I915_VMA_PIN_MASK, &vma->flags); + } + + return true; +} + +static void ungrab_vma(struct i915_vma *vma) +{ + if (dying_vma(vma)) + return; + + i915_gem_object_unlock(vma->obj); + i915_gem_object_put(vma->obj); +} + static bool mark_free(struct drm_mm_scan *scan, + struct i915_gem_ww_ctx *ww, struct i915_vma *vma, unsigned int flags, struct list_head *unwind) @@ -58,6 +92,9 @@ mark_free(struct drm_mm_scan *scan, if (i915_vma_is_pinned(vma)) return false; + if (!grab_vma(vma, ww)) + return false; + list_add(&vma->evict_link, unwind); return drm_mm_scan_add_block(scan, &vma->node); } @@ -76,6 +113,7 @@ static bool defer_evict(struct i915_vma *vma) /** * i915_gem_evict_something - Evict vmas to make room for binding a new one * @vm: address space to evict from + * @ww: An optional struct i915_gem_ww_ctx. * @min_size: size of the desired free space * @alignment: alignment constraint of the desired free space * @color: color for the desired space @@ -98,6 +136,7 @@ static bool defer_evict(struct i915_vma *vma) */ int i915_gem_evict_something(struct i915_address_space *vm, + struct i915_gem_ww_ctx *ww, u64 min_size, u64 alignment, unsigned long color, u64 start, u64 end, @@ -170,7 +209,7 @@ search_again: continue; } - if (mark_free(&scan, vma, flags, &eviction_list)) + if (mark_free(&scan, ww, vma, flags, &eviction_list)) goto found; } @@ -178,6 +217,7 @@ search_again: list_for_each_entry_safe(vma, next, &eviction_list, evict_link) { ret = drm_mm_scan_remove_block(&scan, &vma->node); BUG_ON(ret); + ungrab_vma(vma); } /* @@ -222,10 +262,12 @@ found: * of any of our objects, thus corrupting the list). */ list_for_each_entry_safe(vma, next, &eviction_list, evict_link) { - if (drm_mm_scan_remove_block(&scan, &vma->node)) + if (drm_mm_scan_remove_block(&scan, &vma->node)) { __i915_vma_pin(vma); - else + } else { list_del(&vma->evict_link); + ungrab_vma(vma); + } } /* Unbinding will emit any required flushes */ @@ -234,16 +276,20 @@ found: __i915_vma_unpin(vma); if (ret == 0) ret = __i915_vma_unbind(vma); + ungrab_vma(vma); } while (ret == 0 && (node = drm_mm_scan_color_evict(&scan))) { vma = container_of(node, struct i915_vma, node); /* If we find any non-objects (!vma), we cannot evict them */ - if (vma->node.color != I915_COLOR_UNEVICTABLE) + if (vma->node.color != I915_COLOR_UNEVICTABLE && + grab_vma(vma, ww)) { ret = __i915_vma_unbind(vma); - else - ret = -ENOSPC; /* XXX search failed, try again? */ + ungrab_vma(vma); + } else { + ret = -ENOSPC; + } } return ret; @@ -252,6 +298,7 @@ found: /** * i915_gem_evict_for_node - Evict vmas to make room for binding a new one * @vm: address space to evict from + * @ww: An optional struct i915_gem_ww_ctx. * @target: range (and color) to evict for * @flags: additional flags to control the eviction algorithm * @@ -261,6 +308,7 @@ found: * memory in e.g. the shrinker. */ int i915_gem_evict_for_node(struct i915_address_space *vm, + struct i915_gem_ww_ctx *ww, struct drm_mm_node *target, unsigned int flags) { @@ -333,6 +381,11 @@ int i915_gem_evict_for_node(struct i915_address_space *vm, break; } + if (!grab_vma(vma, ww)) { + ret = -ENOSPC; + break; + } + /* * Never show fear in the face of dragons! * @@ -350,6 +403,8 @@ int i915_gem_evict_for_node(struct i915_address_space *vm, __i915_vma_unpin(vma); if (ret == 0) ret = __i915_vma_unbind(vma); + + ungrab_vma(vma); } return ret; @@ -401,7 +456,7 @@ int i915_gem_evict_vm(struct i915_address_space *vm, struct i915_gem_ww_ctx *ww) * the resv is shared among multiple objects, we still * need the object ref. */ - if (!kref_read(&vma->obj->base.refcount) || + if (dying_vma(vma) || (ww && (dma_resv_locking_ctx(vma->obj->base.resv) == &ww->ctx))) { __i915_vma_pin(vma); list_add(&vma->evict_link, &locked_eviction_list); diff --git a/drivers/gpu/drm/i915/i915_gem_gtt.c b/drivers/gpu/drm/i915/i915_gem_gtt.c index 2f2ba7a2955d..048ad921f952 100644 --- a/drivers/gpu/drm/i915/i915_gem_gtt.c +++ b/drivers/gpu/drm/i915/i915_gem_gtt.c @@ -70,6 +70,7 @@ void i915_gem_gtt_finish_pages(struct drm_i915_gem_object *obj, /** * i915_gem_gtt_reserve - reserve a node in an address_space (GTT) * @vm: the &struct i915_address_space + * @ww: An optional struct i915_gem_ww_ctx. * @node: the &struct drm_mm_node (typically i915_vma.mode) * @size: how much space to allocate inside the GTT, * must be #I915_GTT_PAGE_SIZE aligned @@ -93,6 +94,7 @@ void i915_gem_gtt_finish_pages(struct drm_i915_gem_object *obj, * asked to wait for eviction and interrupted. */ int i915_gem_gtt_reserve(struct i915_address_space *vm, + struct i915_gem_ww_ctx *ww, struct drm_mm_node *node, u64 size, u64 offset, unsigned long color, unsigned int flags) @@ -117,7 +119,7 @@ int i915_gem_gtt_reserve(struct i915_address_space *vm, if (flags & PIN_NOEVICT) return -ENOSPC; - err = i915_gem_evict_for_node(vm, node, flags); + err = i915_gem_evict_for_node(vm, ww, node, flags); if (err == 0) err = drm_mm_reserve_node(&vm->mm, node); @@ -152,6 +154,7 @@ static u64 random_offset(u64 start, u64 end, u64 len, u64 align) /** * i915_gem_gtt_insert - insert a node into an address_space (GTT) * @vm: the &struct i915_address_space + * @ww: An optional struct i915_gem_ww_ctx. * @node: the &struct drm_mm_node (typically i915_vma.node) * @size: how much space to allocate inside the GTT, * must be #I915_GTT_PAGE_SIZE aligned @@ -184,6 +187,7 @@ static u64 random_offset(u64 start, u64 end, u64 len, u64 align) * asked to wait for eviction and interrupted. */ int i915_gem_gtt_insert(struct i915_address_space *vm, + struct i915_gem_ww_ctx *ww, struct drm_mm_node *node, u64 size, u64 alignment, unsigned long color, u64 start, u64 end, unsigned int flags) @@ -269,7 +273,7 @@ int i915_gem_gtt_insert(struct i915_address_space *vm, */ offset = random_offset(start, end, size, alignment ?: I915_GTT_MIN_ALIGNMENT); - err = i915_gem_gtt_reserve(vm, node, size, offset, color, flags); + err = i915_gem_gtt_reserve(vm, ww, node, size, offset, color, flags); if (err != -ENOSPC) return err; @@ -277,7 +281,7 @@ int i915_gem_gtt_insert(struct i915_address_space *vm, return -ENOSPC; /* Randomly selected placement is pinned, do a search */ - err = i915_gem_evict_something(vm, size, alignment, color, + err = i915_gem_evict_something(vm, ww, size, alignment, color, start, end, flags); if (err) return err; diff --git a/drivers/gpu/drm/i915/i915_gem_gtt.h b/drivers/gpu/drm/i915/i915_gem_gtt.h index c9b0ee5e1d23..e4938aba3fe9 100644 --- a/drivers/gpu/drm/i915/i915_gem_gtt.h +++ b/drivers/gpu/drm/i915/i915_gem_gtt.h @@ -16,6 +16,7 @@ struct drm_i915_gem_object; struct i915_address_space; +struct i915_gem_ww_ctx; int __must_check i915_gem_gtt_prepare_pages(struct drm_i915_gem_object *obj, struct sg_table *pages); @@ -23,11 +24,13 @@ void i915_gem_gtt_finish_pages(struct drm_i915_gem_object *obj, struct sg_table *pages); int i915_gem_gtt_reserve(struct i915_address_space *vm, + struct i915_gem_ww_ctx *ww, struct drm_mm_node *node, u64 size, u64 offset, unsigned long color, unsigned int flags); int i915_gem_gtt_insert(struct i915_address_space *vm, + struct i915_gem_ww_ctx *ww, struct drm_mm_node *node, u64 size, u64 alignment, unsigned long color, u64 start, u64 end, unsigned int flags); diff --git a/drivers/gpu/drm/i915/i915_vgpu.c b/drivers/gpu/drm/i915/i915_vgpu.c index 31a105bc1792..c97323973f9b 100644 --- a/drivers/gpu/drm/i915/i915_vgpu.c +++ b/drivers/gpu/drm/i915/i915_vgpu.c @@ -197,7 +197,7 @@ static int vgt_balloon_space(struct i915_ggtt *ggtt, drm_info(&dev_priv->drm, "balloon space: range [ 0x%lx - 0x%lx ] %lu KiB.\n", start, end, size / 1024); - ret = i915_gem_gtt_reserve(&ggtt->vm, node, + ret = i915_gem_gtt_reserve(&ggtt->vm, NULL, node, size, start, I915_COLOR_UNEVICTABLE, 0); if (!ret) diff --git a/drivers/gpu/drm/i915/i915_vma.c b/drivers/gpu/drm/i915/i915_vma.c index 8bbc08a0e88f..9aa651d919f2 100644 --- a/drivers/gpu/drm/i915/i915_vma.c +++ b/drivers/gpu/drm/i915/i915_vma.c @@ -712,7 +712,8 @@ bool i915_gem_valid_gtt_space(struct i915_vma *vma, unsigned long color) * 0 on success, negative error code otherwise. */ static int -i915_vma_insert(struct i915_vma *vma, u64 size, u64 alignment, u64 flags) +i915_vma_insert(struct i915_vma *vma, struct i915_gem_ww_ctx *ww, + u64 size, u64 alignment, u64 flags) { unsigned long color; u64 start, end; @@ -764,7 +765,7 @@ i915_vma_insert(struct i915_vma *vma, u64 size, u64 alignment, u64 flags) range_overflows(offset, size, end)) return -EINVAL; - ret = i915_gem_gtt_reserve(vma->vm, &vma->node, + ret = i915_gem_gtt_reserve(vma->vm, ww, &vma->node, size, offset, color, flags); if (ret) @@ -803,7 +804,7 @@ i915_vma_insert(struct i915_vma *vma, u64 size, u64 alignment, u64 flags) size = round_up(size, I915_GTT_PAGE_SIZE_2M); } - ret = i915_gem_gtt_insert(vma->vm, &vma->node, + ret = i915_gem_gtt_insert(vma->vm, ww, &vma->node, size, alignment, color, start, end, flags); if (ret) @@ -1396,7 +1397,7 @@ int i915_vma_pin_ww(struct i915_vma *vma, struct i915_gem_ww_ctx *ww, goto err_unlock; if (!(bound & I915_VMA_BIND_MASK)) { - err = i915_vma_insert(vma, size, alignment, flags); + err = i915_vma_insert(vma, ww, size, alignment, flags); if (err) goto err_active; diff --git a/drivers/gpu/drm/i915/selftests/i915_gem_evict.c b/drivers/gpu/drm/i915/selftests/i915_gem_evict.c index 19a348546b73..1d57b355e9a7 100644 --- a/drivers/gpu/drm/i915/selftests/i915_gem_evict.c +++ b/drivers/gpu/drm/i915/selftests/i915_gem_evict.c @@ -117,7 +117,7 @@ static int igt_evict_something(void *arg) /* Everything is pinned, nothing should happen */ mutex_lock(&ggtt->vm.mutex); - err = i915_gem_evict_something(&ggtt->vm, + err = i915_gem_evict_something(&ggtt->vm, NULL, I915_GTT_PAGE_SIZE, 0, 0, 0, U64_MAX, 0); @@ -132,7 +132,7 @@ static int igt_evict_something(void *arg) /* Everything is unpinned, we should be able to evict something */ mutex_lock(&ggtt->vm.mutex); - err = i915_gem_evict_something(&ggtt->vm, + err = i915_gem_evict_something(&ggtt->vm, NULL, I915_GTT_PAGE_SIZE, 0, 0, 0, U64_MAX, 0); @@ -204,7 +204,7 @@ static int igt_evict_for_vma(void *arg) /* Everything is pinned, nothing should happen */ mutex_lock(&ggtt->vm.mutex); - err = i915_gem_evict_for_node(&ggtt->vm, &target, 0); + err = i915_gem_evict_for_node(&ggtt->vm, NULL, &target, 0); mutex_unlock(&ggtt->vm.mutex); if (err != -ENOSPC) { pr_err("i915_gem_evict_for_node on a full GGTT returned err=%d\n", @@ -216,7 +216,7 @@ static int igt_evict_for_vma(void *arg) /* Everything is unpinned, we should be able to evict the node */ mutex_lock(&ggtt->vm.mutex); - err = i915_gem_evict_for_node(&ggtt->vm, &target, 0); + err = i915_gem_evict_for_node(&ggtt->vm, NULL, &target, 0); mutex_unlock(&ggtt->vm.mutex); if (err) { pr_err("i915_gem_evict_for_node returned err=%d\n", @@ -297,7 +297,7 @@ static int igt_evict_for_cache_color(void *arg) /* Remove just the second vma */ mutex_lock(&ggtt->vm.mutex); - err = i915_gem_evict_for_node(&ggtt->vm, &target, 0); + err = i915_gem_evict_for_node(&ggtt->vm, NULL, &target, 0); mutex_unlock(&ggtt->vm.mutex); if (err) { pr_err("[0]i915_gem_evict_for_node returned err=%d\n", err); @@ -310,7 +310,7 @@ static int igt_evict_for_cache_color(void *arg) target.color = I915_CACHE_L3_LLC; mutex_lock(&ggtt->vm.mutex); - err = i915_gem_evict_for_node(&ggtt->vm, &target, 0); + err = i915_gem_evict_for_node(&ggtt->vm, NULL, &target, 0); mutex_unlock(&ggtt->vm.mutex); if (!err) { pr_err("[1]i915_gem_evict_for_node returned err=%d\n", err); @@ -406,7 +406,7 @@ static int igt_evict_contexts(void *arg) /* Reserve a block so that we know we have enough to fit a few rq */ memset(&hole, 0, sizeof(hole)); mutex_lock(&ggtt->vm.mutex); - err = i915_gem_gtt_insert(&ggtt->vm, &hole, + err = i915_gem_gtt_insert(&ggtt->vm, NULL, &hole, PRETEND_GGTT_SIZE, 0, I915_COLOR_UNEVICTABLE, 0, ggtt->vm.total, PIN_NOEVICT); @@ -426,7 +426,7 @@ static int igt_evict_contexts(void *arg) goto out_locked; } - if (i915_gem_gtt_insert(&ggtt->vm, &r->node, + if (i915_gem_gtt_insert(&ggtt->vm, NULL, &r->node, 1ul << 20, 0, I915_COLOR_UNEVICTABLE, 0, ggtt->vm.total, PIN_NOEVICT)) { diff --git a/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c b/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c index 076d860ce01a..b91ec3d2d66a 100644 --- a/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c +++ b/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c @@ -1350,7 +1350,7 @@ static int reserve_gtt_with_resource(struct i915_vma *vma, u64 offset) return PTR_ERR(vma_res); mutex_lock(&vm->mutex); - err = i915_gem_gtt_reserve(vm, &vma->node, obj->base.size, + err = i915_gem_gtt_reserve(vm, NULL, &vma->node, obj->base.size, offset, obj->cache_level, 0); @@ -1531,7 +1531,7 @@ static int insert_gtt_with_resource(struct i915_vma *vma) return PTR_ERR(vma_res); mutex_lock(&vm->mutex); - err = i915_gem_gtt_insert(vm, &vma->node, obj->base.size, 0, + err = i915_gem_gtt_insert(vm, NULL, &vma->node, obj->base.size, 0, obj->cache_level, 0, vm->total, 0); if (!err) { i915_vma_resource_init_from_vma(vma_res, vma); @@ -1587,7 +1587,7 @@ static int igt_gtt_insert(void *arg) /* Check a couple of obviously invalid requests */ for (ii = invalid_insert; ii->size; ii++) { mutex_lock(&ggtt->vm.mutex); - err = i915_gem_gtt_insert(&ggtt->vm, &tmp, + err = i915_gem_gtt_insert(&ggtt->vm, NULL, &tmp, ii->size, ii->alignment, I915_COLOR_UNEVICTABLE, ii->start, ii->end, -- cgit From 0f341974cbc2a4efe074dd24c153e439b8430afe Mon Sep 17 00:00:00 2001 From: Maarten Lankhorst Date: Fri, 14 Jan 2022 14:23:18 +0100 Subject: drm/i915: Add i915_vma_unbind_unlocked, and take obj lock for i915_vma_unbind, v2. We want to remove more members of i915_vma, which requires the locking to be held more often. Start requiring gem object lock for i915_vma_unbind, as it's one of the callers that may unpin pages. Some special care is needed when evicting, because the last reference to the object may be held by the VMA, so after __i915_vma_unbind, vma may be garbage, and we need to cache vma->obj before unlocking. Changes since v1: - Make trylock failing a WARN. (Matt) - Remove double i915_vma_wait_for_bind() (Matt) - Move atomic_set to right before mutex_unlock(), to make it more clear they belong together. (Matt) Signed-off-by: Maarten Lankhorst Reviewed-by: Matthew Auld Link: https://patchwork.freedesktop.org/patch/msgid/20220114132320.109030-5-maarten.lankhorst@linux.intel.com --- drivers/gpu/drm/i915/display/intel_fb_pin.c | 2 +- drivers/gpu/drm/i915/gem/selftests/huge_pages.c | 2 +- .../drm/i915/gem/selftests/i915_gem_client_blt.c | 2 +- drivers/gpu/drm/i915/gem/selftests/i915_gem_mman.c | 6 +++ drivers/gpu/drm/i915/gt/intel_ggtt.c | 45 ++++++++++++++++++++-- drivers/gpu/drm/i915/i915_gem.c | 2 + drivers/gpu/drm/i915/i915_vma.c | 27 ++++++++++++- drivers/gpu/drm/i915/i915_vma.h | 1 + drivers/gpu/drm/i915/selftests/i915_gem_gtt.c | 22 +++++------ drivers/gpu/drm/i915/selftests/i915_vma.c | 8 ++-- 10 files changed, 94 insertions(+), 23 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/display/intel_fb_pin.c b/drivers/gpu/drm/i915/display/intel_fb_pin.c index 31c15e5fca95..9c555f6d1958 100644 --- a/drivers/gpu/drm/i915/display/intel_fb_pin.c +++ b/drivers/gpu/drm/i915/display/intel_fb_pin.c @@ -47,7 +47,7 @@ intel_pin_fb_obj_dpt(struct drm_framebuffer *fb, goto err; if (i915_vma_misplaced(vma, 0, alignment, 0)) { - ret = i915_vma_unbind(vma); + ret = i915_vma_unbind_unlocked(vma); if (ret) { vma = ERR_PTR(ret); goto err; diff --git a/drivers/gpu/drm/i915/gem/selftests/huge_pages.c b/drivers/gpu/drm/i915/gem/selftests/huge_pages.c index 26f997c376a2..f36191ebf964 100644 --- a/drivers/gpu/drm/i915/gem/selftests/huge_pages.c +++ b/drivers/gpu/drm/i915/gem/selftests/huge_pages.c @@ -641,7 +641,7 @@ static int igt_mock_ppgtt_misaligned_dma(void *arg) * pages. */ for (offset = 4096; offset < page_size; offset += 4096) { - err = i915_vma_unbind(vma); + err = i915_vma_unbind_unlocked(vma); if (err) goto out_unpin; diff --git a/drivers/gpu/drm/i915/gem/selftests/i915_gem_client_blt.c b/drivers/gpu/drm/i915/gem/selftests/i915_gem_client_blt.c index c08f766e6e15..c8ff8bf0986d 100644 --- a/drivers/gpu/drm/i915/gem/selftests/i915_gem_client_blt.c +++ b/drivers/gpu/drm/i915/gem/selftests/i915_gem_client_blt.c @@ -318,7 +318,7 @@ static int pin_buffer(struct i915_vma *vma, u64 addr) int err; if (drm_mm_node_allocated(&vma->node) && vma->node.start != addr) { - err = i915_vma_unbind(vma); + err = i915_vma_unbind_unlocked(vma); if (err) return err; } diff --git a/drivers/gpu/drm/i915/gem/selftests/i915_gem_mman.c b/drivers/gpu/drm/i915/gem/selftests/i915_gem_mman.c index f61356b72b1c..ba29767348be 100644 --- a/drivers/gpu/drm/i915/gem/selftests/i915_gem_mman.c +++ b/drivers/gpu/drm/i915/gem/selftests/i915_gem_mman.c @@ -166,7 +166,9 @@ static int check_partial_mapping(struct drm_i915_gem_object *obj, kunmap(p); out: + i915_gem_object_lock(obj, NULL); __i915_vma_put(vma); + i915_gem_object_unlock(obj); return err; } @@ -261,7 +263,9 @@ static int check_partial_mappings(struct drm_i915_gem_object *obj, if (err) return err; + i915_gem_object_lock(obj, NULL); __i915_vma_put(vma); + i915_gem_object_unlock(obj); if (igt_timeout(end_time, "%s: timed out after tiling=%d stride=%d\n", @@ -1352,7 +1356,9 @@ static int __igt_mmap_revoke(struct drm_i915_private *i915, * for other objects. Ergo we have to revoke the previous mmap PTE * access as it no longer points to the same object. */ + i915_gem_object_lock(obj, NULL); err = i915_gem_object_unbind(obj, I915_GEM_OBJECT_UNBIND_ACTIVE); + i915_gem_object_unlock(obj); if (err) { pr_err("Failed to unbind object!\n"); goto out_unmap; diff --git a/drivers/gpu/drm/i915/gt/intel_ggtt.c b/drivers/gpu/drm/i915/gt/intel_ggtt.c index da7f54b6fa38..536b0995b595 100644 --- a/drivers/gpu/drm/i915/gt/intel_ggtt.c +++ b/drivers/gpu/drm/i915/gt/intel_ggtt.c @@ -129,22 +129,51 @@ void i915_ggtt_suspend_vm(struct i915_address_space *vm) drm_WARN_ON(&vm->i915->drm, !vm->is_ggtt && !vm->is_dpt); +retry: + i915_gem_drain_freed_objects(vm->i915); + mutex_lock(&vm->mutex); /* Skip rewriting PTE on VMA unbind. */ open = atomic_xchg(&vm->open, 0); list_for_each_entry_safe(vma, vn, &vm->bound_list, vm_link) { + struct drm_i915_gem_object *obj = vma->obj; + GEM_BUG_ON(!drm_mm_node_allocated(&vma->node)); - i915_vma_wait_for_bind(vma); - if (i915_vma_is_pinned(vma)) + if (i915_vma_is_pinned(vma) || !i915_vma_is_bound(vma, I915_VMA_GLOBAL_BIND)) continue; + /* unlikely to race when GPU is idle, so no worry about slowpath.. */ + if (WARN_ON(!i915_gem_object_trylock(obj, NULL))) { + /* + * No dead objects should appear here, GPU should be + * completely idle, and userspace suspended + */ + i915_gem_object_get(obj); + + atomic_set(&vm->open, open); + mutex_unlock(&vm->mutex); + + i915_gem_object_lock(obj, NULL); + open = i915_vma_unbind(vma); + i915_gem_object_unlock(obj); + + GEM_WARN_ON(open); + + i915_gem_object_put(obj); + goto retry; + } + if (!i915_vma_is_bound(vma, I915_VMA_GLOBAL_BIND)) { + i915_vma_wait_for_bind(vma); + __i915_vma_evict(vma, false); drm_mm_remove_node(&vma->node); } + + i915_gem_object_unlock(obj); } vm->clear_range(vm, 0, vm->total); @@ -746,11 +775,21 @@ static void ggtt_cleanup_hw(struct i915_ggtt *ggtt) atomic_set(&ggtt->vm.open, 0); flush_workqueue(ggtt->vm.i915->wq); + i915_gem_drain_freed_objects(ggtt->vm.i915); mutex_lock(&ggtt->vm.mutex); - list_for_each_entry_safe(vma, vn, &ggtt->vm.bound_list, vm_link) + list_for_each_entry_safe(vma, vn, &ggtt->vm.bound_list, vm_link) { + struct drm_i915_gem_object *obj = vma->obj; + bool trylock; + + trylock = i915_gem_object_trylock(obj, NULL); + WARN_ON(!trylock); + WARN_ON(__i915_vma_unbind(vma)); + if (trylock) + i915_gem_object_unlock(obj); + } if (drm_mm_node_allocated(&ggtt->error_capture)) drm_mm_remove_node(&ggtt->error_capture); diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c index 3d6c00f845a3..bb65563296b5 100644 --- a/drivers/gpu/drm/i915/i915_gem.c +++ b/drivers/gpu/drm/i915/i915_gem.c @@ -119,6 +119,8 @@ int i915_gem_object_unbind(struct drm_i915_gem_object *obj, struct i915_vma *vma; int ret; + assert_object_held(obj); + if (list_empty(&obj->vma.list)) return 0; diff --git a/drivers/gpu/drm/i915/i915_vma.c b/drivers/gpu/drm/i915/i915_vma.c index 9aa651d919f2..2a24986861e3 100644 --- a/drivers/gpu/drm/i915/i915_vma.c +++ b/drivers/gpu/drm/i915/i915_vma.c @@ -1622,8 +1622,16 @@ void i915_vma_parked(struct intel_gt *gt) struct drm_i915_gem_object *obj = vma->obj; struct i915_address_space *vm = vma->vm; - INIT_LIST_HEAD(&vma->closed_link); - __i915_vma_put(vma); + if (i915_gem_object_trylock(obj, NULL)) { + INIT_LIST_HEAD(&vma->closed_link); + __i915_vma_put(vma); + i915_gem_object_unlock(obj); + } else { + /* back you go.. */ + spin_lock_irq(>->closed_lock); + list_add(&vma->closed_link, >->closed_vma); + spin_unlock_irq(>->closed_lock); + } i915_gem_object_put(obj); i915_vm_close(vm); @@ -1742,6 +1750,7 @@ struct dma_fence *__i915_vma_evict(struct i915_vma *vma, bool async) struct dma_fence *unbind_fence; GEM_BUG_ON(i915_vma_is_pinned(vma)); + assert_object_held_shared(vma->obj); if (i915_vma_is_map_and_fenceable(vma)) { /* Force a pagefault for domain tracking on next user access */ @@ -1808,6 +1817,7 @@ int __i915_vma_unbind(struct i915_vma *vma) int ret; lockdep_assert_held(&vma->vm->mutex); + assert_object_held_shared(vma->obj); if (!drm_mm_node_allocated(&vma->node)) return 0; @@ -1874,6 +1884,8 @@ int i915_vma_unbind(struct i915_vma *vma) intel_wakeref_t wakeref = 0; int err; + assert_object_held_shared(vma->obj); + /* Optimistic wait before taking the mutex */ err = i915_vma_sync(vma); if (err) @@ -1966,6 +1978,17 @@ out_rpm: return err; } +int i915_vma_unbind_unlocked(struct i915_vma *vma) +{ + int err; + + i915_gem_object_lock(vma->obj, NULL); + err = i915_vma_unbind(vma); + i915_gem_object_unlock(vma->obj); + + return err; +} + struct i915_vma *i915_vma_make_unshrinkable(struct i915_vma *vma) { i915_gem_object_make_unshrinkable(vma->obj); diff --git a/drivers/gpu/drm/i915/i915_vma.h b/drivers/gpu/drm/i915/i915_vma.h index a560bae04e7e..011af044ad4f 100644 --- a/drivers/gpu/drm/i915/i915_vma.h +++ b/drivers/gpu/drm/i915/i915_vma.h @@ -217,6 +217,7 @@ struct dma_fence *__i915_vma_evict(struct i915_vma *vma, bool async); int __i915_vma_unbind(struct i915_vma *vma); int __must_check i915_vma_unbind(struct i915_vma *vma); int __must_check i915_vma_unbind_async(struct i915_vma *vma, bool trylock_vm); +int __must_check i915_vma_unbind_unlocked(struct i915_vma *vma); void i915_vma_unlink_ctx(struct i915_vma *vma); void i915_vma_close(struct i915_vma *vma); void i915_vma_reopen(struct i915_vma *vma); diff --git a/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c b/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c index b91ec3d2d66a..fba1c8be1649 100644 --- a/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c +++ b/drivers/gpu/drm/i915/selftests/i915_gem_gtt.c @@ -386,7 +386,7 @@ static void close_object_list(struct list_head *objects, vma = i915_vma_instance(obj, vm, NULL); if (!IS_ERR(vma)) - ignored = i915_vma_unbind(vma); + ignored = i915_vma_unbind_unlocked(vma); list_del(&obj->st_link); i915_gem_object_put(obj); @@ -497,7 +497,7 @@ static int fill_hole(struct i915_address_space *vm, goto err; } - err = i915_vma_unbind(vma); + err = i915_vma_unbind_unlocked(vma); if (err) { pr_err("%s(%s) (forward) unbind of vma.node=%llx + %llx failed with err=%d\n", __func__, p->name, vma->node.start, vma->node.size, @@ -570,7 +570,7 @@ static int fill_hole(struct i915_address_space *vm, goto err; } - err = i915_vma_unbind(vma); + err = i915_vma_unbind_unlocked(vma); if (err) { pr_err("%s(%s) (backward) unbind of vma.node=%llx + %llx failed with err=%d\n", __func__, p->name, vma->node.start, vma->node.size, @@ -656,7 +656,7 @@ static int walk_hole(struct i915_address_space *vm, goto err_put; } - err = i915_vma_unbind(vma); + err = i915_vma_unbind_unlocked(vma); if (err) { pr_err("%s unbind failed at %llx + %llx with err=%d\n", __func__, addr, vma->size, err); @@ -733,13 +733,13 @@ static int pot_hole(struct i915_address_space *vm, pr_err("%s incorrect at %llx + %llx\n", __func__, addr, vma->size); i915_vma_unpin(vma); - err = i915_vma_unbind(vma); + err = i915_vma_unbind_unlocked(vma); err = -EINVAL; goto err_obj; } i915_vma_unpin(vma); - err = i915_vma_unbind(vma); + err = i915_vma_unbind_unlocked(vma); GEM_BUG_ON(err); } @@ -833,13 +833,13 @@ static int drunk_hole(struct i915_address_space *vm, pr_err("%s incorrect at %llx + %llx\n", __func__, addr, BIT_ULL(size)); i915_vma_unpin(vma); - err = i915_vma_unbind(vma); + err = i915_vma_unbind_unlocked(vma); err = -EINVAL; goto err_obj; } i915_vma_unpin(vma); - err = i915_vma_unbind(vma); + err = i915_vma_unbind_unlocked(vma); GEM_BUG_ON(err); if (igt_timeout(end_time, @@ -907,7 +907,7 @@ static int __shrink_hole(struct i915_address_space *vm, pr_err("%s incorrect at %llx + %llx\n", __func__, addr, size); i915_vma_unpin(vma); - err = i915_vma_unbind(vma); + err = i915_vma_unbind_unlocked(vma); err = -EINVAL; break; } @@ -1481,7 +1481,7 @@ static int igt_gtt_reserve(void *arg) goto out; } - err = i915_vma_unbind(vma); + err = i915_vma_unbind_unlocked(vma); if (err) { pr_err("i915_vma_unbind failed with err=%d!\n", err); goto out; @@ -1677,7 +1677,7 @@ static int igt_gtt_insert(void *arg) GEM_BUG_ON(!drm_mm_node_allocated(&vma->node)); offset = vma->node.start; - err = i915_vma_unbind(vma); + err = i915_vma_unbind_unlocked(vma); if (err) { pr_err("i915_vma_unbind failed with err=%d!\n", err); goto out; diff --git a/drivers/gpu/drm/i915/selftests/i915_vma.c b/drivers/gpu/drm/i915/selftests/i915_vma.c index de37cfa4c65f..0280605a2673 100644 --- a/drivers/gpu/drm/i915/selftests/i915_vma.c +++ b/drivers/gpu/drm/i915/selftests/i915_vma.c @@ -340,7 +340,7 @@ static int igt_vma_pin1(void *arg) if (!err) { i915_vma_unpin(vma); - err = i915_vma_unbind(vma); + err = i915_vma_unbind_unlocked(vma); if (err) { pr_err("Failed to unbind single page from GGTT, err=%d\n", err); goto out; @@ -691,7 +691,7 @@ static int igt_vma_rotate_remap(void *arg) } i915_vma_unpin(vma); - err = i915_vma_unbind(vma); + err = i915_vma_unbind_unlocked(vma); if (err) { pr_err("Unbinding returned %i\n", err); goto out_object; @@ -852,7 +852,7 @@ static int igt_vma_partial(void *arg) i915_vma_unpin(vma); nvma++; - err = i915_vma_unbind(vma); + err = i915_vma_unbind_unlocked(vma); if (err) { pr_err("Unbinding returned %i\n", err); goto out_object; @@ -891,7 +891,7 @@ static int igt_vma_partial(void *arg) i915_vma_unpin(vma); - err = i915_vma_unbind(vma); + err = i915_vma_unbind_unlocked(vma); if (err) { pr_err("Unbinding returned %i\n", err); goto out_object; -- cgit From 294996a98a641254b72732f3cb36d51f12a6f3d9 Mon Sep 17 00:00:00 2001 From: Maarten Lankhorst Date: Fri, 14 Jan 2022 14:23:19 +0100 Subject: drm/i915: Remove support for unlocked i915_vma unbind MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that we require the object lock for all ops, some code handling race conditions can be removed. This is required to not take short-term pins inside execbuf. Signed-off-by: Maarten Lankhorst Acked-by: Niranjana Vishwanathapura Reviewed-by: Thomas Hellström Link: https://patchwork.freedesktop.org/patch/msgid/20220114132320.109030-6-maarten.lankhorst@linux.intel.com --- drivers/gpu/drm/i915/i915_vma.c | 55 ++++++----------------------------------- 1 file changed, 8 insertions(+), 47 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/i915_vma.c b/drivers/gpu/drm/i915/i915_vma.c index 2a24986861e3..4403a93eb164 100644 --- a/drivers/gpu/drm/i915/i915_vma.c +++ b/drivers/gpu/drm/i915/i915_vma.c @@ -838,7 +838,6 @@ i915_vma_detach(struct i915_vma *vma) static bool try_qad_pin(struct i915_vma *vma, unsigned int flags) { unsigned int bound; - bool pinned = true; bound = atomic_read(&vma->flags); do { @@ -848,34 +847,10 @@ static bool try_qad_pin(struct i915_vma *vma, unsigned int flags) if (unlikely(bound & (I915_VMA_OVERFLOW | I915_VMA_ERROR))) return false; - if (!(bound & I915_VMA_PIN_MASK)) - goto unpinned; - GEM_BUG_ON(((bound + 1) & I915_VMA_PIN_MASK) == 0); } while (!atomic_try_cmpxchg(&vma->flags, &bound, bound + 1)); return true; - -unpinned: - /* - * If pin_count==0, but we are bound, check under the lock to avoid - * racing with a concurrent i915_vma_unbind(). - */ - mutex_lock(&vma->vm->mutex); - do { - if (unlikely(bound & (I915_VMA_OVERFLOW | I915_VMA_ERROR))) { - pinned = false; - break; - } - - if (unlikely(flags & ~bound)) { - pinned = false; - break; - } - } while (!atomic_try_cmpxchg(&vma->flags, &bound, bound + 1)); - mutex_unlock(&vma->vm->mutex); - - return pinned; } static struct scatterlist * @@ -1159,7 +1134,6 @@ static int __i915_vma_get_pages(struct i915_vma *vma) { struct sg_table *pages; - int ret; /* * The vma->pages are only valid within the lifespan of the borrowed @@ -1192,18 +1166,16 @@ __i915_vma_get_pages(struct i915_vma *vma) break; } - ret = 0; if (IS_ERR(pages)) { - ret = PTR_ERR(pages); - pages = NULL; drm_err(&vma->vm->i915->drm, - "Failed to get pages for VMA view type %u (%d)!\n", - vma->ggtt_view.type, ret); + "Failed to get pages for VMA view type %u (%ld)!\n", + vma->ggtt_view.type, PTR_ERR(pages)); + return PTR_ERR(pages); } vma->pages = pages; - return ret; + return 0; } I915_SELFTEST_EXPORT int i915_vma_get_pages(struct i915_vma *vma) @@ -1235,25 +1207,14 @@ err_unpin: static void __vma_put_pages(struct i915_vma *vma, unsigned int count) { /* We allocate under vma_get_pages, so beware the shrinker */ - struct sg_table *pages = READ_ONCE(vma->pages); - GEM_BUG_ON(atomic_read(&vma->pages_count) < count); if (atomic_sub_return(count, &vma->pages_count) == 0) { - /* - * The atomic_sub_return is a read barrier for the READ_ONCE of - * vma->pages above. - * - * READ_ONCE is safe because this is either called from the same - * function (i915_vma_pin_ww), or guarded by vma->vm->mutex. - * - * TODO: We're leaving vma->pages dangling, until vma->obj->resv - * lock is required. - */ - if (pages != vma->obj->mm.pages) { - sg_free_table(pages); - kfree(pages); + if (vma->pages != vma->obj->mm.pages) { + sg_free_table(vma->pages); + kfree(vma->pages); } + vma->pages = NULL; i915_gem_object_unpin_pages(vma->obj); } -- cgit From b5cfe6f7a6e17c6bc7c802d382cf22c4dc405477 Mon Sep 17 00:00:00 2001 From: Maarten Lankhorst Date: Fri, 14 Jan 2022 14:23:20 +0100 Subject: drm/i915: Remove short-term pins from execbuf, v6. Add a flag PIN_VALIDATE, to indicate we don't need to pin and only protected by the object lock. This removes the need to unpin, which is done by just releasing the lock. eb_reserve is slightly reworked for readability, but the same steps are still done: - First pass pins with NONBLOCK. - Second pass unbinds all objects first, then pins. - Third pass is only called when not all objects are softpinned, and unbinds all objects, then calls i915_gem_evict_vm(), then pins. Changes since v1: - Split out eb_reserve() into separate functions for readability. Changes since v2: - Make batch buffer mappable on platforms where only GGTT is available, to prevent moving the batch buffer during relocations. Changes since v3: - Preserve current behavior for batch buffer, instead be cautious when calling i915_gem_object_ggtt_pin_ww, and re-use the current batch vma if it's inside ggtt and map-and-fenceable. - Remove impossible condition check from eb_reserve. (Matt) Changes since v5: - Do not even temporarily pin, just call i915_gem_evict_vm() and mark all vma's as unpinned. Signed-off-by: Maarten Lankhorst Reviewed-by: Matthew Auld Link: https://patchwork.freedesktop.org/patch/msgid/20220114132320.109030-7-maarten.lankhorst@linux.intel.com --- drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c | 220 ++++++++++++------------- drivers/gpu/drm/i915/gt/intel_ggtt_fencing.c | 1 - drivers/gpu/drm/i915/i915_gem_gtt.h | 1 + drivers/gpu/drm/i915/i915_vma.c | 24 ++- 4 files changed, 128 insertions(+), 118 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c index 97b9fd7afbef..0103f39b5c77 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c @@ -440,7 +440,7 @@ eb_pin_vma(struct i915_execbuffer *eb, else pin_flags = entry->offset & PIN_OFFSET_MASK; - pin_flags |= PIN_USER | PIN_NOEVICT | PIN_OFFSET_FIXED; + pin_flags |= PIN_USER | PIN_NOEVICT | PIN_OFFSET_FIXED | PIN_VALIDATE; if (unlikely(ev->flags & EXEC_OBJECT_NEEDS_GTT)) pin_flags |= PIN_GLOBAL; @@ -458,17 +458,15 @@ eb_pin_vma(struct i915_execbuffer *eb, entry->pad_to_size, entry->alignment, eb_pin_flags(entry, ev->flags) | - PIN_USER | PIN_NOEVICT); + PIN_USER | PIN_NOEVICT | PIN_VALIDATE); if (unlikely(err)) return err; } if (unlikely(ev->flags & EXEC_OBJECT_NEEDS_FENCE)) { err = i915_vma_pin_fence(vma); - if (unlikely(err)) { - i915_vma_unpin(vma); + if (unlikely(err)) return err; - } if (vma->fence) ev->flags |= __EXEC_OBJECT_HAS_FENCE; @@ -484,13 +482,9 @@ eb_pin_vma(struct i915_execbuffer *eb, static inline void eb_unreserve_vma(struct eb_vma *ev) { - if (!(ev->flags & __EXEC_OBJECT_HAS_PIN)) - return; - if (unlikely(ev->flags & __EXEC_OBJECT_HAS_FENCE)) __i915_vma_unpin_fence(ev->vma); - __i915_vma_unpin(ev->vma); ev->flags &= ~__EXEC_OBJECT_RESERVED; } @@ -672,10 +666,8 @@ static int eb_reserve_vma(struct i915_execbuffer *eb, if (unlikely(ev->flags & EXEC_OBJECT_NEEDS_FENCE)) { err = i915_vma_pin_fence(vma); - if (unlikely(err)) { - i915_vma_unpin(vma); + if (unlikely(err)) return err; - } if (vma->fence) ev->flags |= __EXEC_OBJECT_HAS_FENCE; @@ -687,85 +679,95 @@ static int eb_reserve_vma(struct i915_execbuffer *eb, return 0; } -static int eb_reserve(struct i915_execbuffer *eb) +static bool eb_unbind(struct i915_execbuffer *eb, bool force) { const unsigned int count = eb->buffer_count; - unsigned int pin_flags = PIN_USER | PIN_NONBLOCK; + unsigned int i; struct list_head last; + bool unpinned = false; + + /* Resort *all* the objects into priority order */ + INIT_LIST_HEAD(&eb->unbound); + INIT_LIST_HEAD(&last); + + for (i = 0; i < count; i++) { + struct eb_vma *ev = &eb->vma[i]; + unsigned int flags = ev->flags; + + if (!force && flags & EXEC_OBJECT_PINNED && + flags & __EXEC_OBJECT_HAS_PIN) + continue; + + unpinned = true; + eb_unreserve_vma(ev); + + if (flags & EXEC_OBJECT_PINNED) + /* Pinned must have their slot */ + list_add(&ev->bind_link, &eb->unbound); + else if (flags & __EXEC_OBJECT_NEEDS_MAP) + /* Map require the lowest 256MiB (aperture) */ + list_add_tail(&ev->bind_link, &eb->unbound); + else if (!(flags & EXEC_OBJECT_SUPPORTS_48B_ADDRESS)) + /* Prioritise 4GiB region for restricted bo */ + list_add(&ev->bind_link, &last); + else + list_add_tail(&ev->bind_link, &last); + } + + list_splice_tail(&last, &eb->unbound); + return unpinned; +} + +static int eb_reserve(struct i915_execbuffer *eb) +{ struct eb_vma *ev; - unsigned int i, pass; + unsigned int pass; int err = 0; + bool unpinned; /* * Attempt to pin all of the buffers into the GTT. - * This is done in 3 phases: + * This is done in 2 phases: * - * 1a. Unbind all objects that do not match the GTT constraints for - * the execbuffer (fenceable, mappable, alignment etc). - * 1b. Increment pin count for already bound objects. - * 2. Bind new objects. - * 3. Decrement pin count. + * 1. Unbind all objects that do not match the GTT constraints for + * the execbuffer (fenceable, mappable, alignment etc). + * 2. Bind new objects. * * This avoid unnecessary unbinding of later objects in order to make * room for the earlier objects *unless* we need to defragment. + * + * Defragmenting is skipped if all objects are pinned at a fixed location. */ - pass = 0; - do { - list_for_each_entry(ev, &eb->unbound, bind_link) { - err = eb_reserve_vma(eb, ev, pin_flags); - if (err) - break; - } - if (err != -ENOSPC) - return err; + for (pass = 0; pass <= 2; pass++) { + int pin_flags = PIN_USER | PIN_VALIDATE; - /* Resort *all* the objects into priority order */ - INIT_LIST_HEAD(&eb->unbound); - INIT_LIST_HEAD(&last); - for (i = 0; i < count; i++) { - unsigned int flags; + if (pass == 0) + pin_flags |= PIN_NONBLOCK; - ev = &eb->vma[i]; - flags = ev->flags; - if (flags & EXEC_OBJECT_PINNED && - flags & __EXEC_OBJECT_HAS_PIN) - continue; + if (pass >= 1) + unpinned = eb_unbind(eb, pass == 2); - eb_unreserve_vma(ev); - - if (flags & EXEC_OBJECT_PINNED) - /* Pinned must have their slot */ - list_add(&ev->bind_link, &eb->unbound); - else if (flags & __EXEC_OBJECT_NEEDS_MAP) - /* Map require the lowest 256MiB (aperture) */ - list_add_tail(&ev->bind_link, &eb->unbound); - else if (!(flags & EXEC_OBJECT_SUPPORTS_48B_ADDRESS)) - /* Prioritise 4GiB region for restricted bo */ - list_add(&ev->bind_link, &last); - else - list_add_tail(&ev->bind_link, &last); - } - list_splice_tail(&last, &eb->unbound); - - switch (pass++) { - case 0: - break; - - case 1: - /* Too fragmented, unbind everything and retry */ - mutex_lock(&eb->context->vm->mutex); - err = i915_gem_evict_vm(eb->context->vm, &eb->ww); - mutex_unlock(&eb->context->vm->mutex); + if (pass == 2) { + err = mutex_lock_interruptible(&eb->context->vm->mutex); + if (!err) { + err = i915_gem_evict_vm(eb->context->vm, &eb->ww); + mutex_unlock(&eb->context->vm->mutex); + } if (err) return err; - break; + } - default: - return -ENOSPC; + list_for_each_entry(ev, &eb->unbound, bind_link) { + err = eb_reserve_vma(eb, ev, pin_flags); + if (err) + break; } - pin_flags = PIN_USER; - } while (1); + if (err != -ENOSPC) + break; + } + + return err; } static int eb_select_context(struct i915_execbuffer *eb) @@ -1213,10 +1215,11 @@ static void *reloc_kmap(struct drm_i915_gem_object *obj, return vaddr; } -static void *reloc_iomap(struct drm_i915_gem_object *obj, +static void *reloc_iomap(struct i915_vma *batch, struct i915_execbuffer *eb, unsigned long page) { + struct drm_i915_gem_object *obj = batch->obj; struct reloc_cache *cache = &eb->reloc_cache; struct i915_ggtt *ggtt = cache_to_ggtt(cache); unsigned long offset; @@ -1226,7 +1229,7 @@ static void *reloc_iomap(struct drm_i915_gem_object *obj, intel_gt_flush_ggtt_writes(ggtt->vm.gt); io_mapping_unmap_atomic((void __force __iomem *) unmask_page(cache->vaddr)); } else { - struct i915_vma *vma; + struct i915_vma *vma = ERR_PTR(-ENODEV); int err; if (i915_gem_object_is_tiled(obj)) @@ -1239,10 +1242,23 @@ static void *reloc_iomap(struct drm_i915_gem_object *obj, if (err) return ERR_PTR(err); - vma = i915_gem_object_ggtt_pin_ww(obj, &eb->ww, NULL, 0, 0, - PIN_MAPPABLE | - PIN_NONBLOCK /* NOWARN */ | - PIN_NOEVICT); + /* + * i915_gem_object_ggtt_pin_ww may attempt to remove the batch + * VMA from the object list because we no longer pin. + * + * Only attempt to pin the batch buffer to ggtt if the current batch + * is not inside ggtt, or the batch buffer is not misplaced. + */ + if (!i915_is_ggtt(batch->vm)) { + vma = i915_gem_object_ggtt_pin_ww(obj, &eb->ww, NULL, 0, 0, + PIN_MAPPABLE | + PIN_NONBLOCK /* NOWARN */ | + PIN_NOEVICT); + } else if (i915_vma_is_map_and_fenceable(batch)) { + __i915_vma_pin(batch); + vma = batch; + } + if (vma == ERR_PTR(-EDEADLK)) return vma; @@ -1280,7 +1296,7 @@ static void *reloc_iomap(struct drm_i915_gem_object *obj, return vaddr; } -static void *reloc_vaddr(struct drm_i915_gem_object *obj, +static void *reloc_vaddr(struct i915_vma *vma, struct i915_execbuffer *eb, unsigned long page) { @@ -1292,9 +1308,9 @@ static void *reloc_vaddr(struct drm_i915_gem_object *obj, } else { vaddr = NULL; if ((cache->vaddr & KMAP) == 0) - vaddr = reloc_iomap(obj, eb, page); + vaddr = reloc_iomap(vma, eb, page); if (!vaddr) - vaddr = reloc_kmap(obj, cache, page); + vaddr = reloc_kmap(vma->obj, cache, page); } return vaddr; @@ -1335,7 +1351,7 @@ relocate_entry(struct i915_vma *vma, void *vaddr; repeat: - vaddr = reloc_vaddr(vma->obj, eb, + vaddr = reloc_vaddr(vma, eb, offset >> PAGE_SHIFT); if (IS_ERR(vaddr)) return PTR_ERR(vaddr); @@ -2190,7 +2206,7 @@ shadow_batch_pin(struct i915_execbuffer *eb, if (IS_ERR(vma)) return vma; - err = i915_vma_pin_ww(vma, &eb->ww, 0, 0, flags); + err = i915_vma_pin_ww(vma, &eb->ww, 0, 0, flags | PIN_VALIDATE); if (err) return ERR_PTR(err); @@ -2204,7 +2220,7 @@ static struct i915_vma *eb_dispatch_secure(struct i915_execbuffer *eb, struct i9 * batch" bit. Hence we need to pin secure batches into the global gtt. * hsw should have this fixed, but bdw mucks it up again. */ if (eb->batch_flags & I915_DISPATCH_SECURE) - return i915_gem_object_ggtt_pin_ww(vma->obj, &eb->ww, NULL, 0, 0, 0); + return i915_gem_object_ggtt_pin_ww(vma->obj, &eb->ww, NULL, 0, 0, PIN_VALIDATE); return NULL; } @@ -2255,13 +2271,12 @@ static int eb_parse(struct i915_execbuffer *eb) err = i915_gem_object_lock(pool->obj, &eb->ww); if (err) - goto err; + return err; shadow = shadow_batch_pin(eb, pool->obj, eb->context->vm, PIN_USER); - if (IS_ERR(shadow)) { - err = PTR_ERR(shadow); - goto err; - } + if (IS_ERR(shadow)) + return PTR_ERR(shadow); + intel_gt_buffer_pool_mark_used(pool); i915_gem_object_set_readonly(shadow->obj); shadow->private = pool; @@ -2273,25 +2288,21 @@ static int eb_parse(struct i915_execbuffer *eb) shadow = shadow_batch_pin(eb, pool->obj, &eb->gt->ggtt->vm, PIN_GLOBAL); - if (IS_ERR(shadow)) { - err = PTR_ERR(shadow); - shadow = trampoline; - goto err_shadow; - } + if (IS_ERR(shadow)) + return PTR_ERR(shadow); + shadow->private = pool; eb->batch_flags |= I915_DISPATCH_SECURE; } batch = eb_dispatch_secure(eb, shadow); - if (IS_ERR(batch)) { - err = PTR_ERR(batch); - goto err_trampoline; - } + if (IS_ERR(batch)) + return PTR_ERR(batch); err = dma_resv_reserve_shared(shadow->obj->base.resv, 1); if (err) - goto err_trampoline; + return err; err = intel_engine_cmd_parser(eb->context->engine, eb->batches[0]->vma, @@ -2299,7 +2310,7 @@ static int eb_parse(struct i915_execbuffer *eb) eb->batch_len[0], shadow, trampoline); if (err) - goto err_unpin_batch; + return err; eb->batches[0] = &eb->vma[eb->buffer_count++]; eb->batches[0]->vma = i915_vma_get(shadow); @@ -2318,17 +2329,6 @@ secure_batch: eb->batches[0]->vma = i915_vma_get(batch); } return 0; - -err_unpin_batch: - if (batch) - i915_vma_unpin(batch); -err_trampoline: - if (trampoline) - i915_vma_unpin(trampoline); -err_shadow: - i915_vma_unpin(shadow); -err: - return err; } static int eb_request_submit(struct i915_execbuffer *eb, @@ -3448,8 +3448,6 @@ err_request: err_vma: eb_release_vmas(&eb, true); - if (eb.trampoline) - i915_vma_unpin(eb.trampoline); WARN_ON(err == -EDEADLK); i915_gem_ww_ctx_fini(&eb.ww); diff --git a/drivers/gpu/drm/i915/gt/intel_ggtt_fencing.c b/drivers/gpu/drm/i915/gt/intel_ggtt_fencing.c index beabf3bc9b75..c52d255e8ef3 100644 --- a/drivers/gpu/drm/i915/gt/intel_ggtt_fencing.c +++ b/drivers/gpu/drm/i915/gt/intel_ggtt_fencing.c @@ -425,7 +425,6 @@ int i915_vma_pin_fence(struct i915_vma *vma) * must keep the device awake whilst using the fence. */ assert_rpm_wakelock_held(vma->vm->gt->uncore->rpm); - GEM_BUG_ON(!i915_vma_is_pinned(vma)); GEM_BUG_ON(!i915_vma_is_ggtt(vma)); err = mutex_lock_interruptible(&vma->vm->mutex); diff --git a/drivers/gpu/drm/i915/i915_gem_gtt.h b/drivers/gpu/drm/i915/i915_gem_gtt.h index e4938aba3fe9..8c2f57eb5dda 100644 --- a/drivers/gpu/drm/i915/i915_gem_gtt.h +++ b/drivers/gpu/drm/i915/i915_gem_gtt.h @@ -44,6 +44,7 @@ int i915_gem_gtt_insert(struct i915_address_space *vm, #define PIN_HIGH BIT_ULL(5) #define PIN_OFFSET_BIAS BIT_ULL(6) #define PIN_OFFSET_FIXED BIT_ULL(7) +#define PIN_VALIDATE BIT_ULL(8) /* validate placement only, no need to call unpin() */ #define PIN_GLOBAL BIT_ULL(10) /* I915_VMA_GLOBAL_BIND */ #define PIN_USER BIT_ULL(11) /* I915_VMA_LOCAL_BIND */ diff --git a/drivers/gpu/drm/i915/i915_vma.c b/drivers/gpu/drm/i915/i915_vma.c index 4403a93eb164..426ed08b15ad 100644 --- a/drivers/gpu/drm/i915/i915_vma.c +++ b/drivers/gpu/drm/i915/i915_vma.c @@ -840,6 +840,15 @@ static bool try_qad_pin(struct i915_vma *vma, unsigned int flags) unsigned int bound; bound = atomic_read(&vma->flags); + + if (flags & PIN_VALIDATE) { + flags &= I915_VMA_BIND_MASK; + + return (flags & bound) == flags; + } + + /* with the lock mandatory for unbind, we don't race here */ + flags &= I915_VMA_BIND_MASK; do { if (unlikely(flags & ~bound)) return false; @@ -1261,7 +1270,7 @@ int i915_vma_pin_ww(struct i915_vma *vma, struct i915_gem_ww_ctx *ww, GEM_BUG_ON(!(flags & (PIN_USER | PIN_GLOBAL))); /* First try and grab the pin without rebinding the vma */ - if (try_qad_pin(vma, flags & I915_VMA_BIND_MASK)) + if (try_qad_pin(vma, flags)) return 0; err = i915_vma_get_pages(vma); @@ -1349,7 +1358,8 @@ int i915_vma_pin_ww(struct i915_vma *vma, struct i915_gem_ww_ctx *ww, } if (unlikely(!(flags & ~bound & I915_VMA_BIND_MASK))) { - __i915_vma_pin(vma); + if (!(flags & PIN_VALIDATE)) + __i915_vma_pin(vma); goto err_unlock; } @@ -1379,8 +1389,10 @@ int i915_vma_pin_ww(struct i915_vma *vma, struct i915_gem_ww_ctx *ww, atomic_add(I915_VMA_PAGES_ACTIVE, &vma->pages_count); list_move_tail(&vma->vm_link, &vma->vm->bound_list); - __i915_vma_pin(vma); - GEM_BUG_ON(!i915_vma_is_pinned(vma)); + if (!(flags & PIN_VALIDATE)) { + __i915_vma_pin(vma); + GEM_BUG_ON(!i915_vma_is_pinned(vma)); + } GEM_BUG_ON(!i915_vma_is_bound(vma, flags)); GEM_BUG_ON(i915_vma_misplaced(vma, size, alignment, flags)); @@ -1643,8 +1655,6 @@ static int __i915_vma_move_to_active(struct i915_vma *vma, struct i915_request * { int err; - GEM_BUG_ON(!i915_vma_is_pinned(vma)); - /* Wait for the vma to be bound before we start! */ err = __i915_request_await_bind(rq, vma); if (err) @@ -1663,6 +1673,8 @@ int _i915_vma_move_to_active(struct i915_vma *vma, assert_object_held(obj); + GEM_BUG_ON(!vma->pages); + err = __i915_vma_move_to_active(vma, rq); if (unlikely(err)) return err; -- cgit From 4e68354667716e62089ce3032a3c7e1b6c07e669 Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Thu, 13 Jan 2022 10:13:50 -0800 Subject: drm/i915/selftests: Add a cancel request selftest that triggers a reset Add a cancel request selftest that results in an engine reset to cancel the request as it is non-preemptable. Also insert a NOP request after the cancelled request and confirm that it completes successfully. v2: (Tvrtko) - Skip test if preemption timeout compiled out - Skip test if engine reset isn't supported - Update debug prints to be more descriptive v3: - Add comment explaining test v4: (John Harrison) - Fix typos in comment explaining test - goto out_rq is NOP creation fails Signed-off-by: Matthew Brost Reviewed-by: John Harrison Signed-off-by: John Harrison Link: https://patchwork.freedesktop.org/patch/msgid/20220113181351.21296-2-matthew.brost@intel.com --- drivers/gpu/drm/i915/selftests/i915_request.c | 117 ++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/selftests/i915_request.c b/drivers/gpu/drm/i915/selftests/i915_request.c index 7f66f6d299b2..2a99dd7c2fe8 100644 --- a/drivers/gpu/drm/i915/selftests/i915_request.c +++ b/drivers/gpu/drm/i915/selftests/i915_request.c @@ -782,6 +782,115 @@ out_spin: return err; } +/* + * Test to prove a non-preemptable request can be cancelled and a subsequent + * request on the same context can successfully complete after cancellation. + * + * Testing methodology is to create a non-preemptible request and submit it, + * wait for spinner to start, create a NOP request and submit it, cancel the + * spinner, wait for spinner to complete and verify it failed with an error, + * finally wait for NOP request to complete verify it succeeded without an + * error. Preemption timeout also reduced / restored so test runs in a timely + * maner. + */ +static int __cancel_reset(struct drm_i915_private *i915, + struct intel_engine_cs *engine) +{ + struct intel_context *ce; + struct igt_spinner spin; + struct i915_request *rq, *nop; + unsigned long preempt_timeout_ms; + int err = 0; + + if (!CONFIG_DRM_I915_PREEMPT_TIMEOUT || + !intel_has_reset_engine(engine->gt)) + return 0; + + preempt_timeout_ms = engine->props.preempt_timeout_ms; + engine->props.preempt_timeout_ms = 100; + + if (igt_spinner_init(&spin, engine->gt)) + goto out_restore; + + ce = intel_context_create(engine); + if (IS_ERR(ce)) { + err = PTR_ERR(ce); + goto out_spin; + } + + rq = igt_spinner_create_request(&spin, ce, MI_NOOP); + if (IS_ERR(rq)) { + err = PTR_ERR(rq); + goto out_ce; + } + + pr_debug("%s: Cancelling active non-preemptable request\n", + engine->name); + i915_request_get(rq); + i915_request_add(rq); + if (!igt_wait_for_spinner(&spin, rq)) { + struct drm_printer p = drm_info_printer(engine->i915->drm.dev); + + pr_err("Failed to start spinner on %s\n", engine->name); + intel_engine_dump(engine, &p, "%s\n", engine->name); + err = -ETIME; + goto out_rq; + } + + nop = intel_context_create_request(ce); + if (IS_ERR(nop)) + goto out_rq; + i915_request_get(nop); + i915_request_add(nop); + + i915_request_cancel(rq, -EINTR); + + if (i915_request_wait(rq, 0, HZ) < 0) { + struct drm_printer p = drm_info_printer(engine->i915->drm.dev); + + pr_err("%s: Failed to cancel hung request\n", engine->name); + intel_engine_dump(engine, &p, "%s\n", engine->name); + err = -ETIME; + goto out_nop; + } + + if (rq->fence.error != -EINTR) { + pr_err("%s: fence not cancelled (%u)\n", + engine->name, rq->fence.error); + err = -EINVAL; + goto out_nop; + } + + if (i915_request_wait(nop, 0, HZ) < 0) { + struct drm_printer p = drm_info_printer(engine->i915->drm.dev); + + pr_err("%s: Failed to complete nop request\n", engine->name); + intel_engine_dump(engine, &p, "%s\n", engine->name); + err = -ETIME; + goto out_nop; + } + + if (nop->fence.error != 0) { + pr_err("%s: Nop request errored (%u)\n", + engine->name, nop->fence.error); + err = -EINVAL; + } + +out_nop: + i915_request_put(nop); +out_rq: + i915_request_put(rq); +out_ce: + intel_context_put(ce); +out_spin: + igt_spinner_fini(&spin); +out_restore: + engine->props.preempt_timeout_ms = preempt_timeout_ms; + if (err) + pr_err("%s: %s error %d\n", __func__, engine->name, err); + return err; +} + static int live_cancel_request(void *arg) { struct drm_i915_private *i915 = arg; @@ -814,6 +923,14 @@ static int live_cancel_request(void *arg) return err; if (err2) return err2; + + /* Expects reset so call outside of igt_live_test_* */ + err = __cancel_reset(i915, engine); + if (err) + return err; + + if (igt_flush_test(i915)) + return -EIO; } return 0; -- cgit From 41f8aa5d6a8aace5d33e2d908bf5fdc833456a5f Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Thu, 13 Jan 2022 10:13:51 -0800 Subject: drm/i915/guc: Remove hacks for reset and schedule disable G2H being received out of order In the i915 there are several hacks in place to make request cancellation work with an old version of the GuC which delivered the G2H indicating schedule disable is done before G2H indicating a context reset. Version 69 fixes this, so we can remove these hacks. v2: (Checkpatch) - s/cancelation/cancellation Reviewed-by: John Harrison Signed-off-by: Matthew Brost Signed-off-by: John Harrison Link: https://patchwork.freedesktop.org/patch/msgid/20220113181351.21296-3-matthew.brost@intel.com --- drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c | 30 ++--------------------- 1 file changed, 2 insertions(+), 28 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c index c13f123ae16e..411054f77f33 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c @@ -1532,7 +1532,6 @@ static void __guc_reset_context(struct intel_context *ce, bool stalled) unsigned long flags; u32 head; int i, number_children = ce->parallel.number_children; - bool skip = false; struct intel_context *parent = ce; GEM_BUG_ON(intel_context_is_child(ce)); @@ -1543,23 +1542,10 @@ static void __guc_reset_context(struct intel_context *ce, bool stalled) * GuC will implicitly mark the context as non-schedulable when it sends * the reset notification. Make sure our state reflects this change. The * context will be marked enabled on resubmission. - * - * XXX: If the context is reset as a result of the request cancellation - * this G2H is received after the schedule disable complete G2H which is - * wrong as this creates a race between the request cancellation code - * re-submitting the context and this G2H handler. This is a bug in the - * GuC but can be worked around in the meantime but converting this to a - * NOP if a pending enable is in flight as this indicates that a request - * cancellation has occurred. */ spin_lock_irqsave(&ce->guc_state.lock, flags); - if (likely(!context_pending_enable(ce))) - clr_context_enabled(ce); - else - skip = true; + clr_context_enabled(ce); spin_unlock_irqrestore(&ce->guc_state.lock, flags); - if (unlikely(skip)) - goto out_put; /* * For each context in the relationship find the hanging request @@ -1591,7 +1577,6 @@ next_context: } __unwind_incomplete_requests(parent); -out_put: intel_context_put(parent); } @@ -2530,12 +2515,6 @@ static void guc_context_cancel_request(struct intel_context *ce, true); } - /* - * XXX: Racey if context is reset, see comment in - * __guc_reset_context(). - */ - flush_work(&ce_to_guc(ce)->ct.requests.worker); - guc_context_unblock(block_context); intel_context_put(ce); } @@ -3970,12 +3949,7 @@ static void guc_handle_context_reset(struct intel_guc *guc, { trace_intel_context_reset(ce); - /* - * XXX: Racey if request cancellation has occurred, see comment in - * __guc_reset_context(). - */ - if (likely(!intel_context_is_banned(ce) && - !context_blocked(ce))) { + if (likely(!intel_context_is_banned(ce))) { capture_error_state(guc, ce); guc_context_replay(ce); } else { -- cgit From 6387a3c4b0c45a3932cc06daaf15727e7f83be43 Mon Sep 17 00:00:00 2001 From: Arunpravin Date: Tue, 18 Jan 2022 16:14:59 +0530 Subject: drm: move the buddy allocator from i915 into common drm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the base i915 buddy allocator code into drm - Move i915_buddy.h to include/drm - Move i915_buddy.c to drm root folder - Rename "i915" string with "drm" string wherever applicable - Rename "I915" string with "DRM" string wherever applicable - Fix header file dependencies - Fix alignment issues - add Makefile support for drm buddy - export functions and write kerneldoc description - Remove i915 selftest config check condition as buddy selftest will be moved to drm selftest folder cleanup i915 buddy references in i915 driver module and replace with drm buddy v2: - include header file in alphabetical order(Thomas) - merged changes listed in the body section into a single patch to keep the build intact(Christian, Jani) v3: - make drm buddy a separate module(Thomas, Christian) v4: - Fix build error reported by kernel test robot - removed i915 buddy selftest from i915_mock_selftests.h to avoid build error - removed selftests/i915_buddy.c file as we create a new set of buddy test cases in drm/selftests folder v5: - Fix merge conflict issue v6: - replace drm_buddy_mm structure name as drm_buddy(Thomas, Christian) - replace drm_buddy_alloc() function name as drm_buddy_alloc_blocks() (Thomas) - replace drm_buddy_free() function name as drm_buddy_free_block() (Thomas) - export drm_buddy_free_block() function - fix multiple instances of KMEM_CACHE() entry v7: - fix warnings reported by kernel test robot - modify the license(Christian) v8: - fix warnings reported by kernel test robot Signed-off-by: Arunpravin Acked-by: Christian König Link: https://patchwork.freedesktop.org/patch/msgid/20220118104504.2349-1-Arunpravin.PaneerSelvam@amd.com Signed-off-by: Christian König --- drivers/gpu/drm/Kconfig | 6 + drivers/gpu/drm/Makefile | 2 + drivers/gpu/drm/drm_buddy.c | 535 ++++++++++++++ drivers/gpu/drm/i915/Kconfig | 1 + drivers/gpu/drm/i915/Makefile | 1 - drivers/gpu/drm/i915/i915_buddy.c | 466 ------------ drivers/gpu/drm/i915/i915_buddy.h | 143 ---- drivers/gpu/drm/i915/i915_module.c | 3 - drivers/gpu/drm/i915/i915_scatterlist.c | 11 +- drivers/gpu/drm/i915/i915_ttm_buddy_manager.c | 33 +- drivers/gpu/drm/i915/i915_ttm_buddy_manager.h | 4 +- drivers/gpu/drm/i915/selftests/i915_buddy.c | 787 --------------------- .../gpu/drm/i915/selftests/i915_mock_selftests.h | 1 - .../gpu/drm/i915/selftests/intel_memory_region.c | 13 +- 14 files changed, 575 insertions(+), 1431 deletions(-) create mode 100644 drivers/gpu/drm/drm_buddy.c delete mode 100644 drivers/gpu/drm/i915/i915_buddy.c delete mode 100644 drivers/gpu/drm/i915/i915_buddy.h delete mode 100644 drivers/gpu/drm/i915/selftests/i915_buddy.c (limited to 'drivers') diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 91f54aeb0b7c..cc3e979c9c9d 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -204,6 +204,12 @@ config DRM_TTM GPU memory types. Will be enabled automatically if a device driver uses it. +config DRM_BUDDY + tristate + depends on DRM + help + A page based buddy allocator + config DRM_VRAM_HELPER tristate depends on DRM diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 700abeb4945e..8675c2af7ae1 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -40,6 +40,8 @@ obj-$(CONFIG_DRM_GEM_CMA_HELPER) += drm_cma_helper.o drm_shmem_helper-y := drm_gem_shmem_helper.o obj-$(CONFIG_DRM_GEM_SHMEM_HELPER) += drm_shmem_helper.o +obj-$(CONFIG_DRM_BUDDY) += drm_buddy.o + drm_vram_helper-y := drm_gem_vram_helper.o obj-$(CONFIG_DRM_VRAM_HELPER) += drm_vram_helper.o diff --git a/drivers/gpu/drm/drm_buddy.c b/drivers/gpu/drm/drm_buddy.c new file mode 100644 index 000000000000..d60878bc9c20 --- /dev/null +++ b/drivers/gpu/drm/drm_buddy.c @@ -0,0 +1,535 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2021 Intel Corporation + */ + +#include +#include +#include + +#include + +static struct kmem_cache *slab_blocks; + +static struct drm_buddy_block *drm_block_alloc(struct drm_buddy *mm, + struct drm_buddy_block *parent, + unsigned int order, + u64 offset) +{ + struct drm_buddy_block *block; + + BUG_ON(order > DRM_BUDDY_MAX_ORDER); + + block = kmem_cache_zalloc(slab_blocks, GFP_KERNEL); + if (!block) + return NULL; + + block->header = offset; + block->header |= order; + block->parent = parent; + + BUG_ON(block->header & DRM_BUDDY_HEADER_UNUSED); + return block; +} + +static void drm_block_free(struct drm_buddy *mm, + struct drm_buddy_block *block) +{ + kmem_cache_free(slab_blocks, block); +} + +static void mark_allocated(struct drm_buddy_block *block) +{ + block->header &= ~DRM_BUDDY_HEADER_STATE; + block->header |= DRM_BUDDY_ALLOCATED; + + list_del(&block->link); +} + +static void mark_free(struct drm_buddy *mm, + struct drm_buddy_block *block) +{ + block->header &= ~DRM_BUDDY_HEADER_STATE; + block->header |= DRM_BUDDY_FREE; + + list_add(&block->link, + &mm->free_list[drm_buddy_block_order(block)]); +} + +static void mark_split(struct drm_buddy_block *block) +{ + block->header &= ~DRM_BUDDY_HEADER_STATE; + block->header |= DRM_BUDDY_SPLIT; + + list_del(&block->link); +} + +/** + * drm_buddy_init - init memory manager + * + * @mm: DRM buddy manager to initialize + * @size: size in bytes to manage + * @chunk_size: minimum page size in bytes for our allocations + * + * Initializes the memory manager and its resources. + * + * Returns: + * 0 on success, error code on failure. + */ +int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size) +{ + unsigned int i; + u64 offset; + + if (size < chunk_size) + return -EINVAL; + + if (chunk_size < PAGE_SIZE) + return -EINVAL; + + if (!is_power_of_2(chunk_size)) + return -EINVAL; + + size = round_down(size, chunk_size); + + mm->size = size; + mm->avail = size; + mm->chunk_size = chunk_size; + mm->max_order = ilog2(size) - ilog2(chunk_size); + + BUG_ON(mm->max_order > DRM_BUDDY_MAX_ORDER); + + mm->free_list = kmalloc_array(mm->max_order + 1, + sizeof(struct list_head), + GFP_KERNEL); + if (!mm->free_list) + return -ENOMEM; + + for (i = 0; i <= mm->max_order; ++i) + INIT_LIST_HEAD(&mm->free_list[i]); + + mm->n_roots = hweight64(size); + + mm->roots = kmalloc_array(mm->n_roots, + sizeof(struct drm_buddy_block *), + GFP_KERNEL); + if (!mm->roots) + goto out_free_list; + + offset = 0; + i = 0; + + /* + * Split into power-of-two blocks, in case we are given a size that is + * not itself a power-of-two. + */ + do { + struct drm_buddy_block *root; + unsigned int order; + u64 root_size; + + root_size = rounddown_pow_of_two(size); + order = ilog2(root_size) - ilog2(chunk_size); + + root = drm_block_alloc(mm, NULL, order, offset); + if (!root) + goto out_free_roots; + + mark_free(mm, root); + + BUG_ON(i > mm->max_order); + BUG_ON(drm_buddy_block_size(mm, root) < chunk_size); + + mm->roots[i] = root; + + offset += root_size; + size -= root_size; + i++; + } while (size); + + return 0; + +out_free_roots: + while (i--) + drm_block_free(mm, mm->roots[i]); + kfree(mm->roots); +out_free_list: + kfree(mm->free_list); + return -ENOMEM; +} +EXPORT_SYMBOL(drm_buddy_init); + +/** + * drm_buddy_fini - tear down the memory manager + * + * @mm: DRM buddy manager to free + * + * Cleanup memory manager resources and the freelist + */ +void drm_buddy_fini(struct drm_buddy *mm) +{ + int i; + + for (i = 0; i < mm->n_roots; ++i) { + WARN_ON(!drm_buddy_block_is_free(mm->roots[i])); + drm_block_free(mm, mm->roots[i]); + } + + WARN_ON(mm->avail != mm->size); + + kfree(mm->roots); + kfree(mm->free_list); +} +EXPORT_SYMBOL(drm_buddy_fini); + +static int split_block(struct drm_buddy *mm, + struct drm_buddy_block *block) +{ + unsigned int block_order = drm_buddy_block_order(block) - 1; + u64 offset = drm_buddy_block_offset(block); + + BUG_ON(!drm_buddy_block_is_free(block)); + BUG_ON(!drm_buddy_block_order(block)); + + block->left = drm_block_alloc(mm, block, block_order, offset); + if (!block->left) + return -ENOMEM; + + block->right = drm_block_alloc(mm, block, block_order, + offset + (mm->chunk_size << block_order)); + if (!block->right) { + drm_block_free(mm, block->left); + return -ENOMEM; + } + + mark_free(mm, block->left); + mark_free(mm, block->right); + + mark_split(block); + + return 0; +} + +static struct drm_buddy_block * +get_buddy(struct drm_buddy_block *block) +{ + struct drm_buddy_block *parent; + + parent = block->parent; + if (!parent) + return NULL; + + if (parent->left == block) + return parent->right; + + return parent->left; +} + +static void __drm_buddy_free(struct drm_buddy *mm, + struct drm_buddy_block *block) +{ + struct drm_buddy_block *parent; + + while ((parent = block->parent)) { + struct drm_buddy_block *buddy; + + buddy = get_buddy(block); + + if (!drm_buddy_block_is_free(buddy)) + break; + + list_del(&buddy->link); + + drm_block_free(mm, block); + drm_block_free(mm, buddy); + + block = parent; + } + + mark_free(mm, block); +} + +/** + * drm_buddy_free_block - free a block + * + * @mm: DRM buddy manager + * @block: block to be freed + */ +void drm_buddy_free_block(struct drm_buddy *mm, + struct drm_buddy_block *block) +{ + BUG_ON(!drm_buddy_block_is_allocated(block)); + mm->avail += drm_buddy_block_size(mm, block); + __drm_buddy_free(mm, block); +} +EXPORT_SYMBOL(drm_buddy_free_block); + +/** + * drm_buddy_free_list - free blocks + * + * @mm: DRM buddy manager + * @objects: input list head to free blocks + */ +void drm_buddy_free_list(struct drm_buddy *mm, struct list_head *objects) +{ + struct drm_buddy_block *block, *on; + + list_for_each_entry_safe(block, on, objects, link) { + drm_buddy_free_block(mm, block); + cond_resched(); + } + INIT_LIST_HEAD(objects); +} +EXPORT_SYMBOL(drm_buddy_free_list); + +/** + * drm_buddy_alloc_blocks - allocate power-of-two blocks + * + * @mm: DRM buddy manager to allocate from + * @order: size of the allocation + * + * The order value here translates to: + * + * 0 = 2^0 * mm->chunk_size + * 1 = 2^1 * mm->chunk_size + * 2 = 2^2 * mm->chunk_size + * + * Returns: + * allocated ptr to the &drm_buddy_block on success + */ +struct drm_buddy_block * +drm_buddy_alloc_blocks(struct drm_buddy *mm, unsigned int order) +{ + struct drm_buddy_block *block = NULL; + unsigned int i; + int err; + + for (i = order; i <= mm->max_order; ++i) { + block = list_first_entry_or_null(&mm->free_list[i], + struct drm_buddy_block, + link); + if (block) + break; + } + + if (!block) + return ERR_PTR(-ENOSPC); + + BUG_ON(!drm_buddy_block_is_free(block)); + + while (i != order) { + err = split_block(mm, block); + if (unlikely(err)) + goto out_free; + + /* Go low */ + block = block->left; + i--; + } + + mark_allocated(block); + mm->avail -= drm_buddy_block_size(mm, block); + kmemleak_update_trace(block); + return block; + +out_free: + if (i != order) + __drm_buddy_free(mm, block); + return ERR_PTR(err); +} +EXPORT_SYMBOL(drm_buddy_alloc_blocks); + +static inline bool overlaps(u64 s1, u64 e1, u64 s2, u64 e2) +{ + return s1 <= e2 && e1 >= s2; +} + +static inline bool contains(u64 s1, u64 e1, u64 s2, u64 e2) +{ + return s1 <= s2 && e1 >= e2; +} + +/** + * drm_buddy_alloc_range - allocate range + * + * @mm: DRM buddy manager to allocate from + * @blocks: output list head to add allocated blocks + * @start: start of the allowed range for this block + * @size: size of the allocation + * + * Intended for pre-allocating portions of the address space, for example to + * reserve a block for the initial framebuffer or similar, hence the expectation + * here is that drm_buddy_alloc_blocks() is still the main vehicle for + * allocations, so if that's not the case then the drm_mm range allocator is + * probably a much better fit, and so you should probably go use that instead. + * + * Note that it's safe to chain together multiple alloc_ranges + * with the same blocks list + * + * Returns: + * 0 on success, error code on failure. + */ +int drm_buddy_alloc_range(struct drm_buddy *mm, + struct list_head *blocks, + u64 start, u64 size) +{ + struct drm_buddy_block *block; + struct drm_buddy_block *buddy; + LIST_HEAD(allocated); + LIST_HEAD(dfs); + u64 end; + int err; + int i; + + if (size < mm->chunk_size) + return -EINVAL; + + if (!IS_ALIGNED(size | start, mm->chunk_size)) + return -EINVAL; + + if (range_overflows(start, size, mm->size)) + return -EINVAL; + + for (i = 0; i < mm->n_roots; ++i) + list_add_tail(&mm->roots[i]->tmp_link, &dfs); + + end = start + size - 1; + + do { + u64 block_start; + u64 block_end; + + block = list_first_entry_or_null(&dfs, + struct drm_buddy_block, + tmp_link); + if (!block) + break; + + list_del(&block->tmp_link); + + block_start = drm_buddy_block_offset(block); + block_end = block_start + drm_buddy_block_size(mm, block) - 1; + + if (!overlaps(start, end, block_start, block_end)) + continue; + + if (drm_buddy_block_is_allocated(block)) { + err = -ENOSPC; + goto err_free; + } + + if (contains(start, end, block_start, block_end)) { + if (!drm_buddy_block_is_free(block)) { + err = -ENOSPC; + goto err_free; + } + + mark_allocated(block); + mm->avail -= drm_buddy_block_size(mm, block); + list_add_tail(&block->link, &allocated); + continue; + } + + if (!drm_buddy_block_is_split(block)) { + err = split_block(mm, block); + if (unlikely(err)) + goto err_undo; + } + + list_add(&block->right->tmp_link, &dfs); + list_add(&block->left->tmp_link, &dfs); + } while (1); + + list_splice_tail(&allocated, blocks); + return 0; + +err_undo: + /* + * We really don't want to leave around a bunch of split blocks, since + * bigger is better, so make sure we merge everything back before we + * free the allocated blocks. + */ + buddy = get_buddy(block); + if (buddy && + (drm_buddy_block_is_free(block) && + drm_buddy_block_is_free(buddy))) + __drm_buddy_free(mm, block); + +err_free: + drm_buddy_free_list(mm, &allocated); + return err; +} +EXPORT_SYMBOL(drm_buddy_alloc_range); + +/** + * drm_buddy_block_print - print block information + * + * @mm: DRM buddy manager + * @block: DRM buddy block + * @p: DRM printer to use + */ +void drm_buddy_block_print(struct drm_buddy *mm, + struct drm_buddy_block *block, + struct drm_printer *p) +{ + u64 start = drm_buddy_block_offset(block); + u64 size = drm_buddy_block_size(mm, block); + + drm_printf(p, "%#018llx-%#018llx: %llu\n", start, start + size, size); +} +EXPORT_SYMBOL(drm_buddy_block_print); + +/** + * drm_buddy_print - print allocator state + * + * @mm: DRM buddy manager + * @p: DRM printer to use + */ +void drm_buddy_print(struct drm_buddy *mm, struct drm_printer *p) +{ + int order; + + drm_printf(p, "chunk_size: %lluKiB, total: %lluMiB, free: %lluMiB\n", + mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20); + + for (order = mm->max_order; order >= 0; order--) { + struct drm_buddy_block *block; + u64 count = 0, free; + + list_for_each_entry(block, &mm->free_list[order], link) { + BUG_ON(!drm_buddy_block_is_free(block)); + count++; + } + + drm_printf(p, "order-%d ", order); + + free = count * (mm->chunk_size << order); + if (free < SZ_1M) + drm_printf(p, "free: %lluKiB", free >> 10); + else + drm_printf(p, "free: %lluMiB", free >> 20); + + drm_printf(p, ", pages: %llu\n", count); + } +} +EXPORT_SYMBOL(drm_buddy_print); + +static void drm_buddy_module_exit(void) +{ + kmem_cache_destroy(slab_blocks); +} + +static int __init drm_buddy_module_init(void) +{ + slab_blocks = KMEM_CACHE(drm_buddy_block, 0); + if (!slab_blocks) + return -ENOMEM; + + return 0; +} + +module_init(drm_buddy_module_init); +module_exit(drm_buddy_module_exit); + +MODULE_DESCRIPTION("DRM Buddy Allocator"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/gpu/drm/i915/Kconfig b/drivers/gpu/drm/i915/Kconfig index b68e8b551b83..2ac220bfd0ed 100644 --- a/drivers/gpu/drm/i915/Kconfig +++ b/drivers/gpu/drm/i915/Kconfig @@ -28,6 +28,7 @@ config DRM_I915 select CEC_CORE if CEC_NOTIFIER select VMAP_PFN select DRM_TTM + select DRM_BUDDY help Choose this option if you have a system that has "Intel Graphics Media Accelerator" or "HD Graphics" integrated graphics, diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile index 3b5857da4123..72c2e9c5e0b3 100644 --- a/drivers/gpu/drm/i915/Makefile +++ b/drivers/gpu/drm/i915/Makefile @@ -161,7 +161,6 @@ gem-y += \ i915-y += \ $(gem-y) \ i915_active.o \ - i915_buddy.o \ i915_cmd_parser.o \ i915_gem_evict.o \ i915_gem_gtt.o \ diff --git a/drivers/gpu/drm/i915/i915_buddy.c b/drivers/gpu/drm/i915/i915_buddy.c deleted file mode 100644 index 6e2ad68f8f3f..000000000000 --- a/drivers/gpu/drm/i915/i915_buddy.c +++ /dev/null @@ -1,466 +0,0 @@ -// SPDX-License-Identifier: MIT -/* - * Copyright © 2021 Intel Corporation - */ - -#include -#include - -#include "i915_buddy.h" - -#include "i915_gem.h" -#include "i915_utils.h" - -static struct kmem_cache *slab_blocks; - -static struct i915_buddy_block *i915_block_alloc(struct i915_buddy_mm *mm, - struct i915_buddy_block *parent, - unsigned int order, - u64 offset) -{ - struct i915_buddy_block *block; - - GEM_BUG_ON(order > I915_BUDDY_MAX_ORDER); - - block = kmem_cache_zalloc(slab_blocks, GFP_KERNEL); - if (!block) - return NULL; - - block->header = offset; - block->header |= order; - block->parent = parent; - - GEM_BUG_ON(block->header & I915_BUDDY_HEADER_UNUSED); - return block; -} - -static void i915_block_free(struct i915_buddy_mm *mm, - struct i915_buddy_block *block) -{ - kmem_cache_free(slab_blocks, block); -} - -static void mark_allocated(struct i915_buddy_block *block) -{ - block->header &= ~I915_BUDDY_HEADER_STATE; - block->header |= I915_BUDDY_ALLOCATED; - - list_del(&block->link); -} - -static void mark_free(struct i915_buddy_mm *mm, - struct i915_buddy_block *block) -{ - block->header &= ~I915_BUDDY_HEADER_STATE; - block->header |= I915_BUDDY_FREE; - - list_add(&block->link, - &mm->free_list[i915_buddy_block_order(block)]); -} - -static void mark_split(struct i915_buddy_block *block) -{ - block->header &= ~I915_BUDDY_HEADER_STATE; - block->header |= I915_BUDDY_SPLIT; - - list_del(&block->link); -} - -int i915_buddy_init(struct i915_buddy_mm *mm, u64 size, u64 chunk_size) -{ - unsigned int i; - u64 offset; - - if (size < chunk_size) - return -EINVAL; - - if (chunk_size < PAGE_SIZE) - return -EINVAL; - - if (!is_power_of_2(chunk_size)) - return -EINVAL; - - size = round_down(size, chunk_size); - - mm->size = size; - mm->avail = size; - mm->chunk_size = chunk_size; - mm->max_order = ilog2(size) - ilog2(chunk_size); - - GEM_BUG_ON(mm->max_order > I915_BUDDY_MAX_ORDER); - - mm->free_list = kmalloc_array(mm->max_order + 1, - sizeof(struct list_head), - GFP_KERNEL); - if (!mm->free_list) - return -ENOMEM; - - for (i = 0; i <= mm->max_order; ++i) - INIT_LIST_HEAD(&mm->free_list[i]); - - mm->n_roots = hweight64(size); - - mm->roots = kmalloc_array(mm->n_roots, - sizeof(struct i915_buddy_block *), - GFP_KERNEL); - if (!mm->roots) - goto out_free_list; - - offset = 0; - i = 0; - - /* - * Split into power-of-two blocks, in case we are given a size that is - * not itself a power-of-two. - */ - do { - struct i915_buddy_block *root; - unsigned int order; - u64 root_size; - - root_size = rounddown_pow_of_two(size); - order = ilog2(root_size) - ilog2(chunk_size); - - root = i915_block_alloc(mm, NULL, order, offset); - if (!root) - goto out_free_roots; - - mark_free(mm, root); - - GEM_BUG_ON(i > mm->max_order); - GEM_BUG_ON(i915_buddy_block_size(mm, root) < chunk_size); - - mm->roots[i] = root; - - offset += root_size; - size -= root_size; - i++; - } while (size); - - return 0; - -out_free_roots: - while (i--) - i915_block_free(mm, mm->roots[i]); - kfree(mm->roots); -out_free_list: - kfree(mm->free_list); - return -ENOMEM; -} - -void i915_buddy_fini(struct i915_buddy_mm *mm) -{ - int i; - - for (i = 0; i < mm->n_roots; ++i) { - GEM_WARN_ON(!i915_buddy_block_is_free(mm->roots[i])); - i915_block_free(mm, mm->roots[i]); - } - - GEM_WARN_ON(mm->avail != mm->size); - - kfree(mm->roots); - kfree(mm->free_list); -} - -static int split_block(struct i915_buddy_mm *mm, - struct i915_buddy_block *block) -{ - unsigned int block_order = i915_buddy_block_order(block) - 1; - u64 offset = i915_buddy_block_offset(block); - - GEM_BUG_ON(!i915_buddy_block_is_free(block)); - GEM_BUG_ON(!i915_buddy_block_order(block)); - - block->left = i915_block_alloc(mm, block, block_order, offset); - if (!block->left) - return -ENOMEM; - - block->right = i915_block_alloc(mm, block, block_order, - offset + (mm->chunk_size << block_order)); - if (!block->right) { - i915_block_free(mm, block->left); - return -ENOMEM; - } - - mark_free(mm, block->left); - mark_free(mm, block->right); - - mark_split(block); - - return 0; -} - -static struct i915_buddy_block * -get_buddy(struct i915_buddy_block *block) -{ - struct i915_buddy_block *parent; - - parent = block->parent; - if (!parent) - return NULL; - - if (parent->left == block) - return parent->right; - - return parent->left; -} - -static void __i915_buddy_free(struct i915_buddy_mm *mm, - struct i915_buddy_block *block) -{ - struct i915_buddy_block *parent; - - while ((parent = block->parent)) { - struct i915_buddy_block *buddy; - - buddy = get_buddy(block); - - if (!i915_buddy_block_is_free(buddy)) - break; - - list_del(&buddy->link); - - i915_block_free(mm, block); - i915_block_free(mm, buddy); - - block = parent; - } - - mark_free(mm, block); -} - -void i915_buddy_free(struct i915_buddy_mm *mm, - struct i915_buddy_block *block) -{ - GEM_BUG_ON(!i915_buddy_block_is_allocated(block)); - mm->avail += i915_buddy_block_size(mm, block); - __i915_buddy_free(mm, block); -} - -void i915_buddy_free_list(struct i915_buddy_mm *mm, struct list_head *objects) -{ - struct i915_buddy_block *block, *on; - - list_for_each_entry_safe(block, on, objects, link) { - i915_buddy_free(mm, block); - cond_resched(); - } - INIT_LIST_HEAD(objects); -} - -/* - * Allocate power-of-two block. The order value here translates to: - * - * 0 = 2^0 * mm->chunk_size - * 1 = 2^1 * mm->chunk_size - * 2 = 2^2 * mm->chunk_size - * ... - */ -struct i915_buddy_block * -i915_buddy_alloc(struct i915_buddy_mm *mm, unsigned int order) -{ - struct i915_buddy_block *block = NULL; - unsigned int i; - int err; - - for (i = order; i <= mm->max_order; ++i) { - block = list_first_entry_or_null(&mm->free_list[i], - struct i915_buddy_block, - link); - if (block) - break; - } - - if (!block) - return ERR_PTR(-ENOSPC); - - GEM_BUG_ON(!i915_buddy_block_is_free(block)); - - while (i != order) { - err = split_block(mm, block); - if (unlikely(err)) - goto out_free; - - /* Go low */ - block = block->left; - i--; - } - - mark_allocated(block); - mm->avail -= i915_buddy_block_size(mm, block); - kmemleak_update_trace(block); - return block; - -out_free: - if (i != order) - __i915_buddy_free(mm, block); - return ERR_PTR(err); -} - -static inline bool overlaps(u64 s1, u64 e1, u64 s2, u64 e2) -{ - return s1 <= e2 && e1 >= s2; -} - -static inline bool contains(u64 s1, u64 e1, u64 s2, u64 e2) -{ - return s1 <= s2 && e1 >= e2; -} - -/* - * Allocate range. Note that it's safe to chain together multiple alloc_ranges - * with the same blocks list. - * - * Intended for pre-allocating portions of the address space, for example to - * reserve a block for the initial framebuffer or similar, hence the expectation - * here is that i915_buddy_alloc() is still the main vehicle for - * allocations, so if that's not the case then the drm_mm range allocator is - * probably a much better fit, and so you should probably go use that instead. - */ -int i915_buddy_alloc_range(struct i915_buddy_mm *mm, - struct list_head *blocks, - u64 start, u64 size) -{ - struct i915_buddy_block *block; - struct i915_buddy_block *buddy; - LIST_HEAD(allocated); - LIST_HEAD(dfs); - u64 end; - int err; - int i; - - if (size < mm->chunk_size) - return -EINVAL; - - if (!IS_ALIGNED(size | start, mm->chunk_size)) - return -EINVAL; - - if (range_overflows(start, size, mm->size)) - return -EINVAL; - - for (i = 0; i < mm->n_roots; ++i) - list_add_tail(&mm->roots[i]->tmp_link, &dfs); - - end = start + size - 1; - - do { - u64 block_start; - u64 block_end; - - block = list_first_entry_or_null(&dfs, - struct i915_buddy_block, - tmp_link); - if (!block) - break; - - list_del(&block->tmp_link); - - block_start = i915_buddy_block_offset(block); - block_end = block_start + i915_buddy_block_size(mm, block) - 1; - - if (!overlaps(start, end, block_start, block_end)) - continue; - - if (i915_buddy_block_is_allocated(block)) { - err = -ENOSPC; - goto err_free; - } - - if (contains(start, end, block_start, block_end)) { - if (!i915_buddy_block_is_free(block)) { - err = -ENOSPC; - goto err_free; - } - - mark_allocated(block); - mm->avail -= i915_buddy_block_size(mm, block); - list_add_tail(&block->link, &allocated); - continue; - } - - if (!i915_buddy_block_is_split(block)) { - err = split_block(mm, block); - if (unlikely(err)) - goto err_undo; - } - - list_add(&block->right->tmp_link, &dfs); - list_add(&block->left->tmp_link, &dfs); - } while (1); - - list_splice_tail(&allocated, blocks); - return 0; - -err_undo: - /* - * We really don't want to leave around a bunch of split blocks, since - * bigger is better, so make sure we merge everything back before we - * free the allocated blocks. - */ - buddy = get_buddy(block); - if (buddy && - (i915_buddy_block_is_free(block) && - i915_buddy_block_is_free(buddy))) - __i915_buddy_free(mm, block); - -err_free: - i915_buddy_free_list(mm, &allocated); - return err; -} - -void i915_buddy_block_print(struct i915_buddy_mm *mm, - struct i915_buddy_block *block, - struct drm_printer *p) -{ - u64 start = i915_buddy_block_offset(block); - u64 size = i915_buddy_block_size(mm, block); - - drm_printf(p, "%#018llx-%#018llx: %llu\n", start, start + size, size); -} - -void i915_buddy_print(struct i915_buddy_mm *mm, struct drm_printer *p) -{ - int order; - - drm_printf(p, "chunk_size: %lluKiB, total: %lluMiB, free: %lluMiB\n", - mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20); - - for (order = mm->max_order; order >= 0; order--) { - struct i915_buddy_block *block; - u64 count = 0, free; - - list_for_each_entry(block, &mm->free_list[order], link) { - GEM_BUG_ON(!i915_buddy_block_is_free(block)); - count++; - } - - drm_printf(p, "order-%d ", order); - - free = count * (mm->chunk_size << order); - if (free < SZ_1M) - drm_printf(p, "free: %lluKiB", free >> 10); - else - drm_printf(p, "free: %lluMiB", free >> 20); - - drm_printf(p, ", pages: %llu\n", count); - } -} - -#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) -#include "selftests/i915_buddy.c" -#endif - -void i915_buddy_module_exit(void) -{ - kmem_cache_destroy(slab_blocks); -} - -int __init i915_buddy_module_init(void) -{ - slab_blocks = KMEM_CACHE(i915_buddy_block, 0); - if (!slab_blocks) - return -ENOMEM; - - return 0; -} diff --git a/drivers/gpu/drm/i915/i915_buddy.h b/drivers/gpu/drm/i915/i915_buddy.h deleted file mode 100644 index 7077742112ac..000000000000 --- a/drivers/gpu/drm/i915/i915_buddy.h +++ /dev/null @@ -1,143 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* - * Copyright © 2021 Intel Corporation - */ - -#ifndef __I915_BUDDY_H__ -#define __I915_BUDDY_H__ - -#include -#include -#include - -#include - -struct i915_buddy_block { -#define I915_BUDDY_HEADER_OFFSET GENMASK_ULL(63, 12) -#define I915_BUDDY_HEADER_STATE GENMASK_ULL(11, 10) -#define I915_BUDDY_ALLOCATED (1 << 10) -#define I915_BUDDY_FREE (2 << 10) -#define I915_BUDDY_SPLIT (3 << 10) -/* Free to be used, if needed in the future */ -#define I915_BUDDY_HEADER_UNUSED GENMASK_ULL(9, 6) -#define I915_BUDDY_HEADER_ORDER GENMASK_ULL(5, 0) - u64 header; - - struct i915_buddy_block *left; - struct i915_buddy_block *right; - struct i915_buddy_block *parent; - - void *private; /* owned by creator */ - - /* - * While the block is allocated by the user through i915_buddy_alloc*, - * the user has ownership of the link, for example to maintain within - * a list, if so desired. As soon as the block is freed with - * i915_buddy_free* ownership is given back to the mm. - */ - struct list_head link; - struct list_head tmp_link; -}; - -/* Order-zero must be at least PAGE_SIZE */ -#define I915_BUDDY_MAX_ORDER (63 - PAGE_SHIFT) - -/* - * Binary Buddy System. - * - * Locking should be handled by the user, a simple mutex around - * i915_buddy_alloc* and i915_buddy_free* should suffice. - */ -struct i915_buddy_mm { - /* Maintain a free list for each order. */ - struct list_head *free_list; - - /* - * Maintain explicit binary tree(s) to track the allocation of the - * address space. This gives us a simple way of finding a buddy block - * and performing the potentially recursive merge step when freeing a - * block. Nodes are either allocated or free, in which case they will - * also exist on the respective free list. - */ - struct i915_buddy_block **roots; - - /* - * Anything from here is public, and remains static for the lifetime of - * the mm. Everything above is considered do-not-touch. - */ - unsigned int n_roots; - unsigned int max_order; - - /* Must be at least PAGE_SIZE */ - u64 chunk_size; - u64 size; - u64 avail; -}; - -static inline u64 -i915_buddy_block_offset(struct i915_buddy_block *block) -{ - return block->header & I915_BUDDY_HEADER_OFFSET; -} - -static inline unsigned int -i915_buddy_block_order(struct i915_buddy_block *block) -{ - return block->header & I915_BUDDY_HEADER_ORDER; -} - -static inline unsigned int -i915_buddy_block_state(struct i915_buddy_block *block) -{ - return block->header & I915_BUDDY_HEADER_STATE; -} - -static inline bool -i915_buddy_block_is_allocated(struct i915_buddy_block *block) -{ - return i915_buddy_block_state(block) == I915_BUDDY_ALLOCATED; -} - -static inline bool -i915_buddy_block_is_free(struct i915_buddy_block *block) -{ - return i915_buddy_block_state(block) == I915_BUDDY_FREE; -} - -static inline bool -i915_buddy_block_is_split(struct i915_buddy_block *block) -{ - return i915_buddy_block_state(block) == I915_BUDDY_SPLIT; -} - -static inline u64 -i915_buddy_block_size(struct i915_buddy_mm *mm, - struct i915_buddy_block *block) -{ - return mm->chunk_size << i915_buddy_block_order(block); -} - -int i915_buddy_init(struct i915_buddy_mm *mm, u64 size, u64 chunk_size); - -void i915_buddy_fini(struct i915_buddy_mm *mm); - -struct i915_buddy_block * -i915_buddy_alloc(struct i915_buddy_mm *mm, unsigned int order); - -int i915_buddy_alloc_range(struct i915_buddy_mm *mm, - struct list_head *blocks, - u64 start, u64 size); - -void i915_buddy_free(struct i915_buddy_mm *mm, struct i915_buddy_block *block); - -void i915_buddy_free_list(struct i915_buddy_mm *mm, struct list_head *objects); - -void i915_buddy_print(struct i915_buddy_mm *mm, struct drm_printer *p); -void i915_buddy_block_print(struct i915_buddy_mm *mm, - struct i915_buddy_block *block, - struct drm_printer *p); - -void i915_buddy_module_exit(void); -int i915_buddy_module_init(void); - -#endif diff --git a/drivers/gpu/drm/i915/i915_module.c b/drivers/gpu/drm/i915/i915_module.c index f6bcd2f89257..8451822637f0 100644 --- a/drivers/gpu/drm/i915/i915_module.c +++ b/drivers/gpu/drm/i915/i915_module.c @@ -9,7 +9,6 @@ #include "gem/i915_gem_context.h" #include "gem/i915_gem_object.h" #include "i915_active.h" -#include "i915_buddy.h" #include "i915_params.h" #include "i915_pci.h" #include "i915_perf.h" @@ -50,8 +49,6 @@ static const struct { { .init = i915_check_nomodeset }, { .init = i915_active_module_init, .exit = i915_active_module_exit }, - { .init = i915_buddy_module_init, - .exit = i915_buddy_module_exit }, { .init = i915_context_module_init, .exit = i915_context_module_exit }, { .init = i915_gem_context_module_init, diff --git a/drivers/gpu/drm/i915/i915_scatterlist.c b/drivers/gpu/drm/i915/i915_scatterlist.c index 41f2adb6a583..159571b9bd24 100644 --- a/drivers/gpu/drm/i915/i915_scatterlist.c +++ b/drivers/gpu/drm/i915/i915_scatterlist.c @@ -5,10 +5,9 @@ */ #include "i915_scatterlist.h" - -#include "i915_buddy.h" #include "i915_ttm_buddy_manager.h" +#include #include #include @@ -153,9 +152,9 @@ struct i915_refct_sgt *i915_rsgt_from_buddy_resource(struct ttm_resource *res, struct i915_ttm_buddy_resource *bman_res = to_ttm_buddy_resource(res); const u64 size = res->num_pages << PAGE_SHIFT; const u64 max_segment = rounddown(UINT_MAX, PAGE_SIZE); - struct i915_buddy_mm *mm = bman_res->mm; + struct drm_buddy *mm = bman_res->mm; struct list_head *blocks = &bman_res->blocks; - struct i915_buddy_block *block; + struct drm_buddy_block *block; struct i915_refct_sgt *rsgt; struct scatterlist *sg; struct sg_table *st; @@ -181,8 +180,8 @@ struct i915_refct_sgt *i915_rsgt_from_buddy_resource(struct ttm_resource *res, list_for_each_entry(block, blocks, link) { u64 block_size, offset; - block_size = min_t(u64, size, i915_buddy_block_size(mm, block)); - offset = i915_buddy_block_offset(block); + block_size = min_t(u64, size, drm_buddy_block_size(mm, block)); + offset = drm_buddy_block_offset(block); while (block_size) { u64 len; diff --git a/drivers/gpu/drm/i915/i915_ttm_buddy_manager.c b/drivers/gpu/drm/i915/i915_ttm_buddy_manager.c index d59fbb019032..6ba314f9836a 100644 --- a/drivers/gpu/drm/i915/i915_ttm_buddy_manager.c +++ b/drivers/gpu/drm/i915/i915_ttm_buddy_manager.c @@ -8,14 +8,15 @@ #include #include +#include + #include "i915_ttm_buddy_manager.h" -#include "i915_buddy.h" #include "i915_gem.h" struct i915_ttm_buddy_manager { struct ttm_resource_manager manager; - struct i915_buddy_mm mm; + struct drm_buddy mm; struct list_head reserved; struct mutex lock; u64 default_page_size; @@ -34,7 +35,7 @@ static int i915_ttm_buddy_man_alloc(struct ttm_resource_manager *man, { struct i915_ttm_buddy_manager *bman = to_buddy_manager(man); struct i915_ttm_buddy_resource *bman_res; - struct i915_buddy_mm *mm = &bman->mm; + struct drm_buddy *mm = &bman->mm; unsigned long n_pages; unsigned int min_order; u64 min_page_size; @@ -73,7 +74,7 @@ static int i915_ttm_buddy_man_alloc(struct ttm_resource_manager *man, n_pages = size >> ilog2(mm->chunk_size); do { - struct i915_buddy_block *block; + struct drm_buddy_block *block; unsigned int order; order = fls(n_pages) - 1; @@ -82,7 +83,7 @@ static int i915_ttm_buddy_man_alloc(struct ttm_resource_manager *man, do { mutex_lock(&bman->lock); - block = i915_buddy_alloc(mm, order); + block = drm_buddy_alloc_blocks(mm, order); mutex_unlock(&bman->lock); if (!IS_ERR(block)) break; @@ -106,7 +107,7 @@ static int i915_ttm_buddy_man_alloc(struct ttm_resource_manager *man, err_free_blocks: mutex_lock(&bman->lock); - i915_buddy_free_list(mm, &bman_res->blocks); + drm_buddy_free_list(mm, &bman_res->blocks); mutex_unlock(&bman->lock); err_free_res: kfree(bman_res); @@ -120,7 +121,7 @@ static void i915_ttm_buddy_man_free(struct ttm_resource_manager *man, struct i915_ttm_buddy_manager *bman = to_buddy_manager(man); mutex_lock(&bman->lock); - i915_buddy_free_list(&bman->mm, &bman_res->blocks); + drm_buddy_free_list(&bman->mm, &bman_res->blocks); mutex_unlock(&bman->lock); kfree(bman_res); @@ -130,17 +131,17 @@ static void i915_ttm_buddy_man_debug(struct ttm_resource_manager *man, struct drm_printer *printer) { struct i915_ttm_buddy_manager *bman = to_buddy_manager(man); - struct i915_buddy_block *block; + struct drm_buddy_block *block; mutex_lock(&bman->lock); drm_printf(printer, "default_page_size: %lluKiB\n", bman->default_page_size >> 10); - i915_buddy_print(&bman->mm, printer); + drm_buddy_print(&bman->mm, printer); drm_printf(printer, "reserved:\n"); list_for_each_entry(block, &bman->reserved, link) - i915_buddy_block_print(&bman->mm, block, printer); + drm_buddy_block_print(&bman->mm, block, printer); mutex_unlock(&bman->lock); } @@ -190,7 +191,7 @@ int i915_ttm_buddy_man_init(struct ttm_device *bdev, if (!bman) return -ENOMEM; - err = i915_buddy_init(&bman->mm, size, chunk_size); + err = drm_buddy_init(&bman->mm, size, chunk_size); if (err) goto err_free_bman; @@ -228,7 +229,7 @@ int i915_ttm_buddy_man_fini(struct ttm_device *bdev, unsigned int type) { struct ttm_resource_manager *man = ttm_manager_type(bdev, type); struct i915_ttm_buddy_manager *bman = to_buddy_manager(man); - struct i915_buddy_mm *mm = &bman->mm; + struct drm_buddy *mm = &bman->mm; int ret; ttm_resource_manager_set_used(man, false); @@ -240,8 +241,8 @@ int i915_ttm_buddy_man_fini(struct ttm_device *bdev, unsigned int type) ttm_set_driver_manager(bdev, type, NULL); mutex_lock(&bman->lock); - i915_buddy_free_list(mm, &bman->reserved); - i915_buddy_fini(mm); + drm_buddy_free_list(mm, &bman->reserved); + drm_buddy_fini(mm); mutex_unlock(&bman->lock); ttm_resource_manager_cleanup(man); @@ -264,11 +265,11 @@ int i915_ttm_buddy_man_reserve(struct ttm_resource_manager *man, u64 start, u64 size) { struct i915_ttm_buddy_manager *bman = to_buddy_manager(man); - struct i915_buddy_mm *mm = &bman->mm; + struct drm_buddy *mm = &bman->mm; int ret; mutex_lock(&bman->lock); - ret = i915_buddy_alloc_range(mm, &bman->reserved, start, size); + ret = drm_buddy_alloc_range(mm, &bman->reserved, start, size); mutex_unlock(&bman->lock); return ret; diff --git a/drivers/gpu/drm/i915/i915_ttm_buddy_manager.h b/drivers/gpu/drm/i915/i915_ttm_buddy_manager.h index 0722d33f3e14..312077941411 100644 --- a/drivers/gpu/drm/i915/i915_ttm_buddy_manager.h +++ b/drivers/gpu/drm/i915/i915_ttm_buddy_manager.h @@ -13,7 +13,7 @@ struct ttm_device; struct ttm_resource_manager; -struct i915_buddy_mm; +struct drm_buddy; /** * struct i915_ttm_buddy_resource @@ -28,7 +28,7 @@ struct i915_buddy_mm; struct i915_ttm_buddy_resource { struct ttm_resource base; struct list_head blocks; - struct i915_buddy_mm *mm; + struct drm_buddy *mm; }; /** diff --git a/drivers/gpu/drm/i915/selftests/i915_buddy.c b/drivers/gpu/drm/i915/selftests/i915_buddy.c deleted file mode 100644 index d61ec9c951bf..000000000000 --- a/drivers/gpu/drm/i915/selftests/i915_buddy.c +++ /dev/null @@ -1,787 +0,0 @@ -// SPDX-License-Identifier: MIT -/* - * Copyright © 2019 Intel Corporation - */ - -#include - -#include "../i915_selftest.h" -#include "i915_random.h" - -static void __igt_dump_block(struct i915_buddy_mm *mm, - struct i915_buddy_block *block, - bool buddy) -{ - pr_err("block info: header=%llx, state=%u, order=%d, offset=%llx size=%llx root=%s buddy=%s\n", - block->header, - i915_buddy_block_state(block), - i915_buddy_block_order(block), - i915_buddy_block_offset(block), - i915_buddy_block_size(mm, block), - yesno(!block->parent), - yesno(buddy)); -} - -static void igt_dump_block(struct i915_buddy_mm *mm, - struct i915_buddy_block *block) -{ - struct i915_buddy_block *buddy; - - __igt_dump_block(mm, block, false); - - buddy = get_buddy(block); - if (buddy) - __igt_dump_block(mm, buddy, true); -} - -static int igt_check_block(struct i915_buddy_mm *mm, - struct i915_buddy_block *block) -{ - struct i915_buddy_block *buddy; - unsigned int block_state; - u64 block_size; - u64 offset; - int err = 0; - - block_state = i915_buddy_block_state(block); - - if (block_state != I915_BUDDY_ALLOCATED && - block_state != I915_BUDDY_FREE && - block_state != I915_BUDDY_SPLIT) { - pr_err("block state mismatch\n"); - err = -EINVAL; - } - - block_size = i915_buddy_block_size(mm, block); - offset = i915_buddy_block_offset(block); - - if (block_size < mm->chunk_size) { - pr_err("block size smaller than min size\n"); - err = -EINVAL; - } - - if (!is_power_of_2(block_size)) { - pr_err("block size not power of two\n"); - err = -EINVAL; - } - - if (!IS_ALIGNED(block_size, mm->chunk_size)) { - pr_err("block size not aligned to min size\n"); - err = -EINVAL; - } - - if (!IS_ALIGNED(offset, mm->chunk_size)) { - pr_err("block offset not aligned to min size\n"); - err = -EINVAL; - } - - if (!IS_ALIGNED(offset, block_size)) { - pr_err("block offset not aligned to block size\n"); - err = -EINVAL; - } - - buddy = get_buddy(block); - - if (!buddy && block->parent) { - pr_err("buddy has gone fishing\n"); - err = -EINVAL; - } - - if (buddy) { - if (i915_buddy_block_offset(buddy) != (offset ^ block_size)) { - pr_err("buddy has wrong offset\n"); - err = -EINVAL; - } - - if (i915_buddy_block_size(mm, buddy) != block_size) { - pr_err("buddy size mismatch\n"); - err = -EINVAL; - } - - if (i915_buddy_block_state(buddy) == block_state && - block_state == I915_BUDDY_FREE) { - pr_err("block and its buddy are free\n"); - err = -EINVAL; - } - } - - return err; -} - -static int igt_check_blocks(struct i915_buddy_mm *mm, - struct list_head *blocks, - u64 expected_size, - bool is_contiguous) -{ - struct i915_buddy_block *block; - struct i915_buddy_block *prev; - u64 total; - int err = 0; - - block = NULL; - prev = NULL; - total = 0; - - list_for_each_entry(block, blocks, link) { - err = igt_check_block(mm, block); - - if (!i915_buddy_block_is_allocated(block)) { - pr_err("block not allocated\n"), - err = -EINVAL; - } - - if (is_contiguous && prev) { - u64 prev_block_size; - u64 prev_offset; - u64 offset; - - prev_offset = i915_buddy_block_offset(prev); - prev_block_size = i915_buddy_block_size(mm, prev); - offset = i915_buddy_block_offset(block); - - if (offset != (prev_offset + prev_block_size)) { - pr_err("block offset mismatch\n"); - err = -EINVAL; - } - } - - if (err) - break; - - total += i915_buddy_block_size(mm, block); - prev = block; - } - - if (!err) { - if (total != expected_size) { - pr_err("size mismatch, expected=%llx, found=%llx\n", - expected_size, total); - err = -EINVAL; - } - return err; - } - - if (prev) { - pr_err("prev block, dump:\n"); - igt_dump_block(mm, prev); - } - - pr_err("bad block, dump:\n"); - igt_dump_block(mm, block); - - return err; -} - -static int igt_check_mm(struct i915_buddy_mm *mm) -{ - struct i915_buddy_block *root; - struct i915_buddy_block *prev; - unsigned int i; - u64 total; - int err = 0; - - if (!mm->n_roots) { - pr_err("n_roots is zero\n"); - return -EINVAL; - } - - if (mm->n_roots != hweight64(mm->size)) { - pr_err("n_roots mismatch, n_roots=%u, expected=%lu\n", - mm->n_roots, hweight64(mm->size)); - return -EINVAL; - } - - root = NULL; - prev = NULL; - total = 0; - - for (i = 0; i < mm->n_roots; ++i) { - struct i915_buddy_block *block; - unsigned int order; - - root = mm->roots[i]; - if (!root) { - pr_err("root(%u) is NULL\n", i); - err = -EINVAL; - break; - } - - err = igt_check_block(mm, root); - - if (!i915_buddy_block_is_free(root)) { - pr_err("root not free\n"); - err = -EINVAL; - } - - order = i915_buddy_block_order(root); - - if (!i) { - if (order != mm->max_order) { - pr_err("max order root missing\n"); - err = -EINVAL; - } - } - - if (prev) { - u64 prev_block_size; - u64 prev_offset; - u64 offset; - - prev_offset = i915_buddy_block_offset(prev); - prev_block_size = i915_buddy_block_size(mm, prev); - offset = i915_buddy_block_offset(root); - - if (offset != (prev_offset + prev_block_size)) { - pr_err("root offset mismatch\n"); - err = -EINVAL; - } - } - - block = list_first_entry_or_null(&mm->free_list[order], - struct i915_buddy_block, - link); - if (block != root) { - pr_err("root mismatch at order=%u\n", order); - err = -EINVAL; - } - - if (err) - break; - - prev = root; - total += i915_buddy_block_size(mm, root); - } - - if (!err) { - if (total != mm->size) { - pr_err("expected mm size=%llx, found=%llx\n", mm->size, - total); - err = -EINVAL; - } - return err; - } - - if (prev) { - pr_err("prev root(%u), dump:\n", i - 1); - igt_dump_block(mm, prev); - } - - if (root) { - pr_err("bad root(%u), dump:\n", i); - igt_dump_block(mm, root); - } - - return err; -} - -static void igt_mm_config(u64 *size, u64 *chunk_size) -{ - I915_RND_STATE(prng); - u32 s, ms; - - /* Nothing fancy, just try to get an interesting bit pattern */ - - prandom_seed_state(&prng, i915_selftest.random_seed); - - /* Let size be a random number of pages up to 8 GB (2M pages) */ - s = 1 + i915_prandom_u32_max_state((BIT(33 - 12)) - 1, &prng); - /* Let the chunk size be a random power of 2 less than size */ - ms = BIT(i915_prandom_u32_max_state(ilog2(s), &prng)); - /* Round size down to the chunk size */ - s &= -ms; - - /* Convert from pages to bytes */ - *chunk_size = (u64)ms << 12; - *size = (u64)s << 12; -} - -static int igt_buddy_alloc_smoke(void *arg) -{ - struct i915_buddy_mm mm; - IGT_TIMEOUT(end_time); - I915_RND_STATE(prng); - u64 chunk_size; - u64 mm_size; - int *order; - int err, i; - - igt_mm_config(&mm_size, &chunk_size); - - pr_info("buddy_init with size=%llx, chunk_size=%llx\n", mm_size, chunk_size); - - err = i915_buddy_init(&mm, mm_size, chunk_size); - if (err) { - pr_err("buddy_init failed(%d)\n", err); - return err; - } - - order = i915_random_order(mm.max_order + 1, &prng); - if (!order) - goto out_fini; - - for (i = 0; i <= mm.max_order; ++i) { - struct i915_buddy_block *block; - int max_order = order[i]; - bool timeout = false; - LIST_HEAD(blocks); - int order; - u64 total; - - err = igt_check_mm(&mm); - if (err) { - pr_err("pre-mm check failed, abort\n"); - break; - } - - pr_info("filling from max_order=%u\n", max_order); - - order = max_order; - total = 0; - - do { -retry: - block = i915_buddy_alloc(&mm, order); - if (IS_ERR(block)) { - err = PTR_ERR(block); - if (err == -ENOMEM) { - pr_info("buddy_alloc hit -ENOMEM with order=%d\n", - order); - } else { - if (order--) { - err = 0; - goto retry; - } - - pr_err("buddy_alloc with order=%d failed(%d)\n", - order, err); - } - - break; - } - - list_add_tail(&block->link, &blocks); - - if (i915_buddy_block_order(block) != order) { - pr_err("buddy_alloc order mismatch\n"); - err = -EINVAL; - break; - } - - total += i915_buddy_block_size(&mm, block); - - if (__igt_timeout(end_time, NULL)) { - timeout = true; - break; - } - } while (total < mm.size); - - if (!err) - err = igt_check_blocks(&mm, &blocks, total, false); - - i915_buddy_free_list(&mm, &blocks); - - if (!err) { - err = igt_check_mm(&mm); - if (err) - pr_err("post-mm check failed\n"); - } - - if (err || timeout) - break; - - cond_resched(); - } - - if (err == -ENOMEM) - err = 0; - - kfree(order); -out_fini: - i915_buddy_fini(&mm); - - return err; -} - -static int igt_buddy_alloc_pessimistic(void *arg) -{ - const unsigned int max_order = 16; - struct i915_buddy_block *block, *bn; - struct i915_buddy_mm mm; - unsigned int order; - LIST_HEAD(blocks); - int err; - - /* - * Create a pot-sized mm, then allocate one of each possible - * order within. This should leave the mm with exactly one - * page left. - */ - - err = i915_buddy_init(&mm, PAGE_SIZE << max_order, PAGE_SIZE); - if (err) { - pr_err("buddy_init failed(%d)\n", err); - return err; - } - GEM_BUG_ON(mm.max_order != max_order); - - for (order = 0; order < max_order; order++) { - block = i915_buddy_alloc(&mm, order); - if (IS_ERR(block)) { - pr_info("buddy_alloc hit -ENOMEM with order=%d\n", - order); - err = PTR_ERR(block); - goto err; - } - - list_add_tail(&block->link, &blocks); - } - - /* And now the last remaining block available */ - block = i915_buddy_alloc(&mm, 0); - if (IS_ERR(block)) { - pr_info("buddy_alloc hit -ENOMEM on final alloc\n"); - err = PTR_ERR(block); - goto err; - } - list_add_tail(&block->link, &blocks); - - /* Should be completely full! */ - for (order = max_order; order--; ) { - block = i915_buddy_alloc(&mm, order); - if (!IS_ERR(block)) { - pr_info("buddy_alloc unexpectedly succeeded at order %d, it should be full!", - order); - list_add_tail(&block->link, &blocks); - err = -EINVAL; - goto err; - } - } - - block = list_last_entry(&blocks, typeof(*block), link); - list_del(&block->link); - i915_buddy_free(&mm, block); - - /* As we free in increasing size, we make available larger blocks */ - order = 1; - list_for_each_entry_safe(block, bn, &blocks, link) { - list_del(&block->link); - i915_buddy_free(&mm, block); - - block = i915_buddy_alloc(&mm, order); - if (IS_ERR(block)) { - pr_info("buddy_alloc (realloc) hit -ENOMEM with order=%d\n", - order); - err = PTR_ERR(block); - goto err; - } - i915_buddy_free(&mm, block); - order++; - } - - /* To confirm, now the whole mm should be available */ - block = i915_buddy_alloc(&mm, max_order); - if (IS_ERR(block)) { - pr_info("buddy_alloc (realloc) hit -ENOMEM with order=%d\n", - max_order); - err = PTR_ERR(block); - goto err; - } - i915_buddy_free(&mm, block); - -err: - i915_buddy_free_list(&mm, &blocks); - i915_buddy_fini(&mm); - return err; -} - -static int igt_buddy_alloc_optimistic(void *arg) -{ - const int max_order = 16; - struct i915_buddy_block *block; - struct i915_buddy_mm mm; - LIST_HEAD(blocks); - int order; - int err; - - /* - * Create a mm with one block of each order available, and - * try to allocate them all. - */ - - err = i915_buddy_init(&mm, - PAGE_SIZE * ((1 << (max_order + 1)) - 1), - PAGE_SIZE); - if (err) { - pr_err("buddy_init failed(%d)\n", err); - return err; - } - GEM_BUG_ON(mm.max_order != max_order); - - for (order = 0; order <= max_order; order++) { - block = i915_buddy_alloc(&mm, order); - if (IS_ERR(block)) { - pr_info("buddy_alloc hit -ENOMEM with order=%d\n", - order); - err = PTR_ERR(block); - goto err; - } - - list_add_tail(&block->link, &blocks); - } - - /* Should be completely full! */ - block = i915_buddy_alloc(&mm, 0); - if (!IS_ERR(block)) { - pr_info("buddy_alloc unexpectedly succeeded, it should be full!"); - list_add_tail(&block->link, &blocks); - err = -EINVAL; - goto err; - } - -err: - i915_buddy_free_list(&mm, &blocks); - i915_buddy_fini(&mm); - return err; -} - -static int igt_buddy_alloc_pathological(void *arg) -{ - const int max_order = 16; - struct i915_buddy_block *block; - struct i915_buddy_mm mm; - LIST_HEAD(blocks); - LIST_HEAD(holes); - int order, top; - int err; - - /* - * Create a pot-sized mm, then allocate one of each possible - * order within. This should leave the mm with exactly one - * page left. Free the largest block, then whittle down again. - * Eventually we will have a fully 50% fragmented mm. - */ - - err = i915_buddy_init(&mm, PAGE_SIZE << max_order, PAGE_SIZE); - if (err) { - pr_err("buddy_init failed(%d)\n", err); - return err; - } - GEM_BUG_ON(mm.max_order != max_order); - - for (top = max_order; top; top--) { - /* Make room by freeing the largest allocated block */ - block = list_first_entry_or_null(&blocks, typeof(*block), link); - if (block) { - list_del(&block->link); - i915_buddy_free(&mm, block); - } - - for (order = top; order--; ) { - block = i915_buddy_alloc(&mm, order); - if (IS_ERR(block)) { - pr_info("buddy_alloc hit -ENOMEM with order=%d, top=%d\n", - order, top); - err = PTR_ERR(block); - goto err; - } - list_add_tail(&block->link, &blocks); - } - - /* There should be one final page for this sub-allocation */ - block = i915_buddy_alloc(&mm, 0); - if (IS_ERR(block)) { - pr_info("buddy_alloc hit -ENOMEM for hole\n"); - err = PTR_ERR(block); - goto err; - } - list_add_tail(&block->link, &holes); - - block = i915_buddy_alloc(&mm, top); - if (!IS_ERR(block)) { - pr_info("buddy_alloc unexpectedly succeeded at top-order %d/%d, it should be full!", - top, max_order); - list_add_tail(&block->link, &blocks); - err = -EINVAL; - goto err; - } - } - - i915_buddy_free_list(&mm, &holes); - - /* Nothing larger than blocks of chunk_size now available */ - for (order = 1; order <= max_order; order++) { - block = i915_buddy_alloc(&mm, order); - if (!IS_ERR(block)) { - pr_info("buddy_alloc unexpectedly succeeded at order %d, it should be full!", - order); - list_add_tail(&block->link, &blocks); - err = -EINVAL; - goto err; - } - } - -err: - list_splice_tail(&holes, &blocks); - i915_buddy_free_list(&mm, &blocks); - i915_buddy_fini(&mm); - return err; -} - -static int igt_buddy_alloc_range(void *arg) -{ - struct i915_buddy_mm mm; - unsigned long page_num; - LIST_HEAD(blocks); - u64 chunk_size; - u64 offset; - u64 size; - u64 rem; - int err; - - igt_mm_config(&size, &chunk_size); - - pr_info("buddy_init with size=%llx, chunk_size=%llx\n", size, chunk_size); - - err = i915_buddy_init(&mm, size, chunk_size); - if (err) { - pr_err("buddy_init failed(%d)\n", err); - return err; - } - - err = igt_check_mm(&mm); - if (err) { - pr_err("pre-mm check failed, abort, abort, abort!\n"); - goto err_fini; - } - - rem = mm.size; - offset = 0; - - for_each_prime_number_from(page_num, 1, ULONG_MAX - 1) { - struct i915_buddy_block *block; - LIST_HEAD(tmp); - - size = min(page_num * mm.chunk_size, rem); - - err = i915_buddy_alloc_range(&mm, &tmp, offset, size); - if (err) { - if (err == -ENOMEM) { - pr_info("alloc_range hit -ENOMEM with size=%llx\n", - size); - } else { - pr_err("alloc_range with offset=%llx, size=%llx failed(%d)\n", - offset, size, err); - } - - break; - } - - block = list_first_entry_or_null(&tmp, - struct i915_buddy_block, - link); - if (!block) { - pr_err("alloc_range has no blocks\n"); - err = -EINVAL; - break; - } - - if (i915_buddy_block_offset(block) != offset) { - pr_err("alloc_range start offset mismatch, found=%llx, expected=%llx\n", - i915_buddy_block_offset(block), offset); - err = -EINVAL; - } - - if (!err) - err = igt_check_blocks(&mm, &tmp, size, true); - - list_splice_tail(&tmp, &blocks); - - if (err) - break; - - offset += size; - - rem -= size; - if (!rem) - break; - - cond_resched(); - } - - if (err == -ENOMEM) - err = 0; - - i915_buddy_free_list(&mm, &blocks); - - if (!err) { - err = igt_check_mm(&mm); - if (err) - pr_err("post-mm check failed\n"); - } - -err_fini: - i915_buddy_fini(&mm); - - return err; -} - -static int igt_buddy_alloc_limit(void *arg) -{ - struct i915_buddy_block *block; - struct i915_buddy_mm mm; - const u64 size = U64_MAX; - int err; - - err = i915_buddy_init(&mm, size, PAGE_SIZE); - if (err) - return err; - - if (mm.max_order != I915_BUDDY_MAX_ORDER) { - pr_err("mm.max_order(%d) != %d\n", - mm.max_order, I915_BUDDY_MAX_ORDER); - err = -EINVAL; - goto out_fini; - } - - block = i915_buddy_alloc(&mm, mm.max_order); - if (IS_ERR(block)) { - err = PTR_ERR(block); - goto out_fini; - } - - if (i915_buddy_block_order(block) != mm.max_order) { - pr_err("block order(%d) != %d\n", - i915_buddy_block_order(block), mm.max_order); - err = -EINVAL; - goto out_free; - } - - if (i915_buddy_block_size(&mm, block) != - BIT_ULL(mm.max_order) * PAGE_SIZE) { - pr_err("block size(%llu) != %llu\n", - i915_buddy_block_size(&mm, block), - BIT_ULL(mm.max_order) * PAGE_SIZE); - err = -EINVAL; - goto out_free; - } - -out_free: - i915_buddy_free(&mm, block); -out_fini: - i915_buddy_fini(&mm); - return err; -} - -int i915_buddy_mock_selftests(void) -{ - static const struct i915_subtest tests[] = { - SUBTEST(igt_buddy_alloc_pessimistic), - SUBTEST(igt_buddy_alloc_optimistic), - SUBTEST(igt_buddy_alloc_pathological), - SUBTEST(igt_buddy_alloc_smoke), - SUBTEST(igt_buddy_alloc_range), - SUBTEST(igt_buddy_alloc_limit), - }; - - return i915_subtests(tests, NULL); -} diff --git a/drivers/gpu/drm/i915/selftests/i915_mock_selftests.h b/drivers/gpu/drm/i915/selftests/i915_mock_selftests.h index 793fb28a770d..0c22e0fc9059 100644 --- a/drivers/gpu/drm/i915/selftests/i915_mock_selftests.h +++ b/drivers/gpu/drm/i915/selftests/i915_mock_selftests.h @@ -33,4 +33,3 @@ selftest(evict, i915_gem_evict_mock_selftests) selftest(gtt, i915_gem_gtt_mock_selftests) selftest(hugepages, i915_gem_huge_page_mock_selftests) selftest(memory_region, intel_memory_region_mock_selftests) -selftest(buddy, i915_buddy_mock_selftests) diff --git a/drivers/gpu/drm/i915/selftests/intel_memory_region.c b/drivers/gpu/drm/i915/selftests/intel_memory_region.c index 0d5df0dc7212..d3b7fb4d52d1 100644 --- a/drivers/gpu/drm/i915/selftests/intel_memory_region.c +++ b/drivers/gpu/drm/i915/selftests/intel_memory_region.c @@ -6,6 +6,8 @@ #include #include +#include + #include "../i915_selftest.h" #include "mock_drm.h" @@ -20,7 +22,6 @@ #include "gt/intel_engine_pm.h" #include "gt/intel_engine_user.h" #include "gt/intel_gt.h" -#include "i915_buddy.h" #include "gt/intel_migrate.h" #include "i915_memcpy.h" #include "i915_ttm_buddy_manager.h" @@ -369,7 +370,7 @@ static int igt_mock_splintered_region(void *arg) struct drm_i915_private *i915 = mem->i915; struct i915_ttm_buddy_resource *res; struct drm_i915_gem_object *obj; - struct i915_buddy_mm *mm; + struct drm_buddy *mm; unsigned int expected_order; LIST_HEAD(objects); u64 size; @@ -454,8 +455,8 @@ static int igt_mock_max_segment(void *arg) struct drm_i915_private *i915 = mem->i915; struct i915_ttm_buddy_resource *res; struct drm_i915_gem_object *obj; - struct i915_buddy_block *block; - struct i915_buddy_mm *mm; + struct drm_buddy_block *block; + struct drm_buddy *mm; struct list_head *blocks; struct scatterlist *sg; LIST_HEAD(objects); @@ -485,8 +486,8 @@ static int igt_mock_max_segment(void *arg) mm = res->mm; size = 0; list_for_each_entry(block, blocks, link) { - if (i915_buddy_block_size(mm, block) > size) - size = i915_buddy_block_size(mm, block); + if (drm_buddy_block_size(mm, block) > size) + size = drm_buddy_block_size(mm, block); } if (size < max_segment) { pr_err("%s: Failed to create a huge contiguous block [> %u], largest block %lld\n", -- cgit From acde6234f65bad89a2e27d3e8dd2daf680862545 Mon Sep 17 00:00:00 2001 From: Christian König Date: Wed, 3 Nov 2021 10:37:06 +0100 Subject: drm/amdgpu: remove excl as shared workarounds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was added because of the now dropped shared on excl dependency. Signed-off-by: Christian König Reviewed-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20211123142111.3885-15-christian.koenig@amd.com --- drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c | 5 +---- drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c | 6 ------ 2 files changed, 1 insertion(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c index 0311d799a010..53e407ea4c89 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c @@ -1275,14 +1275,11 @@ static int amdgpu_cs_submit(struct amdgpu_cs_parser *p, /* * Work around dma_resv shortcommings by wrapping up the * submission in a dma_fence_chain and add it as exclusive - * fence, but first add the submission as shared fence to make - * sure that shared fences never signal before the exclusive - * one. + * fence. */ dma_fence_chain_init(chain, dma_resv_excl_fence(resv), dma_fence_get(p->fence), 1); - dma_resv_add_shared_fence(resv, p->fence); rcu_assign_pointer(resv->fence_excl, &chain->base); e->chain = NULL; } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c index c0d8f40a5b45..9b12cab5e606 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c @@ -226,12 +226,6 @@ static void amdgpu_gem_object_close(struct drm_gem_object *obj, if (!amdgpu_vm_ready(vm)) goto out_unlock; - fence = dma_resv_excl_fence(bo->tbo.base.resv); - if (fence) { - amdgpu_bo_fence(bo, fence, true); - fence = NULL; - } - r = amdgpu_vm_clear_freed(adev, vm, &fence); if (r || !fence) goto out_unlock; -- cgit From 75ab2b3633ccddd8f7bdf6c76f9ab3f9b2fc5d9d Mon Sep 17 00:00:00 2001 From: Christian König Date: Thu, 28 Oct 2021 13:19:22 +0200 Subject: dma-buf: drop excl_fence parameter from dma_resv_get_fences MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Returning the exclusive fence separately is no longer used. Instead add a write parameter to indicate the use case. Signed-off-by: Christian König Reviewed-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20211207123411.167006-4-christian.koenig@amd.com --- drivers/dma-buf/dma-resv.c | 46 ++++++++++------------------ drivers/dma-buf/st-dma-resv.c | 26 +++------------- drivers/gpu/drm/amd/amdgpu/amdgpu_display.c | 6 ++-- drivers/gpu/drm/amd/amdgpu/amdgpu_ids.c | 2 +- drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c | 3 +- 5 files changed, 28 insertions(+), 55 deletions(-) (limited to 'drivers') diff --git a/drivers/dma-buf/dma-resv.c b/drivers/dma-buf/dma-resv.c index 4deea75c0b9c..6dd9a40b55d4 100644 --- a/drivers/dma-buf/dma-resv.c +++ b/drivers/dma-buf/dma-resv.c @@ -542,57 +542,45 @@ EXPORT_SYMBOL(dma_resv_copy_fences); * dma_resv_get_fences - Get an object's shared and exclusive * fences without update side lock held * @obj: the reservation object - * @fence_excl: the returned exclusive fence (or NULL) - * @shared_count: the number of shared fences returned - * @shared: the array of shared fence ptrs returned (array is krealloc'd to - * the required size, and must be freed by caller) + * @write: true if we should return all fences + * @num_fences: the number of fences returned + * @fences: the array of fence ptrs returned (array is krealloc'd to the + * required size, and must be freed by caller) * - * Retrieve all fences from the reservation object. If the pointer for the - * exclusive fence is not specified the fence is put into the array of the - * shared fences as well. Returns either zero or -ENOMEM. + * Retrieve all fences from the reservation object. + * Returns either zero or -ENOMEM. */ -int dma_resv_get_fences(struct dma_resv *obj, struct dma_fence **fence_excl, - unsigned int *shared_count, struct dma_fence ***shared) +int dma_resv_get_fences(struct dma_resv *obj, bool write, + unsigned int *num_fences, struct dma_fence ***fences) { struct dma_resv_iter cursor; struct dma_fence *fence; - *shared_count = 0; - *shared = NULL; + *num_fences = 0; + *fences = NULL; - if (fence_excl) - *fence_excl = NULL; - - dma_resv_iter_begin(&cursor, obj, true); + dma_resv_iter_begin(&cursor, obj, write); dma_resv_for_each_fence_unlocked(&cursor, fence) { if (dma_resv_iter_is_restarted(&cursor)) { unsigned int count; - while (*shared_count) - dma_fence_put((*shared)[--(*shared_count)]); - - if (fence_excl) - dma_fence_put(*fence_excl); + while (*num_fences) + dma_fence_put((*fences)[--(*num_fences)]); - count = cursor.shared_count; - count += fence_excl ? 0 : 1; + count = cursor.shared_count + 1; /* Eventually re-allocate the array */ - *shared = krealloc_array(*shared, count, + *fences = krealloc_array(*fences, count, sizeof(void *), GFP_KERNEL); - if (count && !*shared) { + if (count && !*fences) { dma_resv_iter_end(&cursor); return -ENOMEM; } } - dma_fence_get(fence); - if (dma_resv_iter_is_exclusive(&cursor) && fence_excl) - *fence_excl = fence; - else - (*shared)[(*shared_count)++] = fence; + (*fences)[(*num_fences)++] = dma_fence_get(fence); } dma_resv_iter_end(&cursor); diff --git a/drivers/dma-buf/st-dma-resv.c b/drivers/dma-buf/st-dma-resv.c index bc32b3eedcb6..cbe999c6e7a6 100644 --- a/drivers/dma-buf/st-dma-resv.c +++ b/drivers/dma-buf/st-dma-resv.c @@ -275,7 +275,7 @@ static int test_shared_for_each_unlocked(void *arg) static int test_get_fences(void *arg, bool shared) { - struct dma_fence *f, *excl = NULL, **fences = NULL; + struct dma_fence *f, **fences = NULL; struct dma_resv resv; int r, i; @@ -304,35 +304,19 @@ static int test_get_fences(void *arg, bool shared) } dma_resv_unlock(&resv); - r = dma_resv_get_fences(&resv, &excl, &i, &fences); + r = dma_resv_get_fences(&resv, shared, &i, &fences); if (r) { pr_err("get_fences failed\n"); goto err_free; } - if (shared) { - if (excl != NULL) { - pr_err("get_fences returned unexpected excl fence\n"); - goto err_free; - } - if (i != 1 || fences[0] != f) { - pr_err("get_fences returned unexpected shared fence\n"); - goto err_free; - } - } else { - if (excl != f) { - pr_err("get_fences returned unexpected excl fence\n"); - goto err_free; - } - if (i != 0) { - pr_err("get_fences returned unexpected shared fence\n"); - goto err_free; - } + if (i != 1 || fences[0] != f) { + pr_err("get_fences returned unexpected fence\n"); + goto err_free; } dma_fence_signal(f); err_free: - dma_fence_put(excl); while (i--) dma_fence_put(fences[i]); kfree(fences); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c index 18cc7155e667..9a273420a67a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c @@ -200,8 +200,10 @@ int amdgpu_display_crtc_page_flip_target(struct drm_crtc *crtc, goto unpin; } - r = dma_resv_get_fences(new_abo->tbo.base.resv, NULL, - &work->shared_count, &work->shared); + /* TODO: Unify this with other drivers */ + r = dma_resv_get_fences(new_abo->tbo.base.resv, true, + &work->shared_count, + &work->shared); if (unlikely(r != 0)) { DRM_ERROR("failed to get fences for buffer\n"); goto unpin; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.c index b7fb72bff2c1..be48487e2ca7 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.c @@ -112,7 +112,7 @@ void amdgpu_pasid_free_delayed(struct dma_resv *resv, unsigned count; int r; - r = dma_resv_get_fences(resv, NULL, &count, &fences); + r = dma_resv_get_fences(resv, true, &count, &fences); if (r) goto fallback; diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c b/drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c index b5e8ce86dbe7..64c90ff348f2 100644 --- a/drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c +++ b/drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c @@ -189,8 +189,7 @@ static int submit_fence_sync(struct etnaviv_gem_submit *submit) continue; if (bo->flags & ETNA_SUBMIT_BO_WRITE) { - ret = dma_resv_get_fences(robj, NULL, - &bo->nr_shared, + ret = dma_resv_get_fences(robj, true, &bo->nr_shared, &bo->shared); if (ret) return ret; -- cgit From d7e3ea83bba81d6d9c8f80c62a99b018165f5808 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Tue, 18 Jan 2022 16:44:18 +0100 Subject: drm/selftests: Select DRM_DP_HELPER Resolve warnings about non-existing symbols by selecting DRM_DP_HELPER. Signed-off-by: Thomas Zimmermann Fixes: adb9d5a2cc77 ("drm/dp: Move DisplayPort helpers into separate helper module") Reviewed-by: Javier Martinez Canillas Reported-by: kernel test robot Cc: Lyude Paul Cc: Daniel Vetter Cc: Maarten Lankhorst Cc: Maxime Ripard Cc: dri-devel@lists.freedesktop.org Link: https://patchwork.freedesktop.org/patch/msgid/20220118154418.25932-1-tzimmermann@suse.de --- drivers/gpu/drm/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index cc3e979c9c9d..dfdd3ec5f793 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -68,6 +68,7 @@ config DRM_DEBUG_SELFTEST depends on DRM depends on DEBUG_KERNEL select PRIME_NUMBERS + select DRM_DP_HELPER select DRM_LIB_RANDOM select DRM_KMS_HELPER select DRM_EXPORT_FOR_TESTS if m -- cgit From 4ce2ca4b374a384257187a4418250734518210d7 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Tue, 18 Jan 2022 16:44:50 +0100 Subject: drm/msm: Fix include statements for DisplayPort Update the include statements for DisplayPort helpers. The header files are in the dp/ subdirectory. Signed-off-by: Thomas Zimmermann Fixes: 5b529e8d9c38 ("drm/dp: Move public DisplayPort headers into dp/") Reviewed-by: Javier Martinez Canillas Reported-by: kernel test robot Cc: Lyude Paul Cc: Daniel Vetter Cc: Maarten Lankhorst Cc: Maxime Ripard Cc: dri-devel@lists.freedesktop.org Link: https://patchwork.freedesktop.org/patch/msgid/20220118154450.25947-1-tzimmermann@suse.de --- drivers/gpu/drm/msm/edp/edp.h | 2 +- drivers/gpu/drm/msm/edp/edp_ctrl.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/msm/edp/edp.h b/drivers/gpu/drm/msm/edp/edp.h index 8590f2ce274d..1a82d7a4af9f 100644 --- a/drivers/gpu/drm/msm/edp/edp.h +++ b/drivers/gpu/drm/msm/edp/edp.h @@ -10,9 +10,9 @@ #include #include #include +#include #include #include -#include #include "msm_drv.h" diff --git a/drivers/gpu/drm/msm/edp/edp_ctrl.c b/drivers/gpu/drm/msm/edp/edp_ctrl.c index a68a4a1867c1..9f537b1fd849 100644 --- a/drivers/gpu/drm/msm/edp/edp_ctrl.c +++ b/drivers/gpu/drm/msm/edp/edp_ctrl.c @@ -6,8 +6,8 @@ #include #include #include +#include #include -#include #include #include "edp.h" -- cgit From cc37b88b02fb590d08dd922ef1c0f7ef3125bba8 Mon Sep 17 00:00:00 2001 From: Zhiming Liu Date: Mon, 17 Jan 2022 23:41:50 +0800 Subject: drm/bridge: Remove extra device acquisition method of i2c client in lt9611 driver We have got the device of i2c client in probe function. So we should remove extra device acquisition method of i2c client. Signed-off-by: Zhiming Liu Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20220117154150.5145-1-lzmlzmhh@gmail.com Reviewed-by: Robert Foss --- drivers/gpu/drm/bridge/lontium-lt9611.c | 4 ++-- drivers/gpu/drm/bridge/lontium-lt9611uxc.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/lontium-lt9611.c b/drivers/gpu/drm/bridge/lontium-lt9611.c index dafb1b47c15f..feb128a4557d 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9611.c +++ b/drivers/gpu/drm/bridge/lontium-lt9611.c @@ -1090,7 +1090,7 @@ static int lt9611_probe(struct i2c_client *client, if (!lt9611) return -ENOMEM; - lt9611->dev = &client->dev; + lt9611->dev = dev; lt9611->client = client; lt9611->sleep = false; @@ -1100,7 +1100,7 @@ static int lt9611_probe(struct i2c_client *client, return PTR_ERR(lt9611->regmap); } - ret = lt9611_parse_dt(&client->dev, lt9611); + ret = lt9611_parse_dt(dev, lt9611); if (ret) { dev_err(dev, "failed to parse device tree\n"); return ret; diff --git a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c index 33f9716da0ee..3d62e6bf6892 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c +++ b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c @@ -860,7 +860,7 @@ static int lt9611uxc_probe(struct i2c_client *client, if (!lt9611uxc) return -ENOMEM; - lt9611uxc->dev = &client->dev; + lt9611uxc->dev = dev; lt9611uxc->client = client; mutex_init(<9611uxc->ocm_lock); @@ -870,7 +870,7 @@ static int lt9611uxc_probe(struct i2c_client *client, return PTR_ERR(lt9611uxc->regmap); } - ret = lt9611uxc_parse_dt(&client->dev, lt9611uxc); + ret = lt9611uxc_parse_dt(dev, lt9611uxc); if (ret) { dev_err(dev, "failed to parse device tree\n"); return ret; -- cgit From 028a73e10705af1ffd51f2537460f616dc58680e Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Wed, 19 Jan 2022 11:29:05 +0100 Subject: mgag200 fix memmapsl configuration in GCTL6 register MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On some servers with MGA G200_SE_A (rev 42), booting with Legacy BIOS, the hardware hangs when using kdump and kexec into the kdump kernel. This happens when the uncompress code tries to write "Decompressing Linux" to the VGA Console. It can be reproduced by writing to the VGA console (0xB8000) after booting to graphic mode, it generates the following error: kernel:NMI: PCI system error (SERR) for reason a0 on CPU 0. kernel:Dazed and confused, but trying to continue The root cause is the configuration of the MGA GCTL6 register According to the GCTL6 register documentation: bit 0 is gcgrmode: 0: Enables alpha mode, and the character generator addressing system is activated. 1: Enables graphics mode, and the character addressing system is not used. bit 1 is chainodd even: 0: The A0 signal of the memory address bus is used during system memory addressing. 1: Allows A0 to be replaced by either the A16 signal of the system address (ifmemmapsl is ‘00’), or by the hpgoddev (MISC<5>, odd/even page select) field, described on page 3-294). bit 3-2 are memmapsl: Memory map select bits 1 and 0. VGA. These bits select where the video memory is mapped, as shown below: 00 => A0000h - BFFFFh 01 => A0000h - AFFFFh 10 => B0000h - B7FFFh 11 => B8000h - BFFFFh bit 7-4 are reserved. Current code set it to 0x05 => memmapsl to b01 => 0xa0000 (graphic mode) But on x86, the VGA console is at 0xb8000 (text mode) In arch/x86/boot/compressed/misc.c debug strings are written to 0xb8000 As the driver doesn't use this mapping at 0xa0000, it is safe to set it to 0xb8000 instead, to avoid kernel hang on G200_SE_A rev42, with kexec/kdump. Thus changing the value 0x05 to 0x0d Signed-off-by: Jocelyn Falempe Reviewed-by: Javier Martinez Canillas Acked-by: Lyude Paul Cc: stable@vger.kernel.org Signed-off-by: Thomas Zimmermann Link: https://patchwork.freedesktop.org/patch/msgid/20220119102905.1194787-1-jfalempe@redhat.com --- drivers/gpu/drm/mgag200/mgag200_mode.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/mgag200/mgag200_mode.c b/drivers/gpu/drm/mgag200/mgag200_mode.c index b983541a4c53..cd9ba13ad5fc 100644 --- a/drivers/gpu/drm/mgag200/mgag200_mode.c +++ b/drivers/gpu/drm/mgag200/mgag200_mode.c @@ -529,7 +529,10 @@ static void mgag200_set_format_regs(struct mga_device *mdev, WREG_GFX(3, 0x00); WREG_GFX(4, 0x00); WREG_GFX(5, 0x40); - WREG_GFX(6, 0x05); + /* GCTL6 should be 0x05, but we configure memmapsl to 0xb8000 (text mode), + * so that it doesn't hang when running kexec/kdump on G200_SE rev42. + */ + WREG_GFX(6, 0x0d); WREG_GFX(7, 0x0f); WREG_GFX(8, 0x0f); -- cgit From bcc77411e8a65929655cef7b63a36000724cdc4b Mon Sep 17 00:00:00 2001 From: KuoHsiang Chou Date: Mon, 17 Jan 2022 16:36:43 +0800 Subject: drm/ast: Create threshold values for AST2600 The threshold value is used for AST2600 only. Signed-off-by: KuoHsiang Chou Signed-off-by: Thomas Zimmermann Link: https://patchwork.freedesktop.org/patch/msgid/20220117083643.41493-1-kuohsiang_chou@aspeedtech.com --- drivers/gpu/drm/ast/ast_mode.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/ast/ast_mode.c b/drivers/gpu/drm/ast/ast_mode.c index 44c2aafcb7c2..6fa8042a0dfd 100644 --- a/drivers/gpu/drm/ast/ast_mode.c +++ b/drivers/gpu/drm/ast/ast_mode.c @@ -471,7 +471,10 @@ static void ast_set_color_reg(struct ast_private *ast, static void ast_set_crtthd_reg(struct ast_private *ast) { /* Set Threshold */ - if (ast->chip == AST2300 || ast->chip == AST2400 || + if (ast->chip == AST2600) { + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xa7, 0xe0); + ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xa6, 0xa0); + } else if (ast->chip == AST2300 || ast->chip == AST2400 || ast->chip == AST2500) { ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xa7, 0x78); ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xa6, 0x60); -- cgit From ba3a5ddcf1e5df31f2291006d5297ca62035584f Mon Sep 17 00:00:00 2001 From: José Expósito Date: Sat, 8 Jan 2022 17:58:12 +0100 Subject: drm/selftests/test-drm_dp_mst_helper: Fix memory leak in sideband_msg_req_encode_decode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avoid leaking the "out" variable if it is not possible to allocate the "txmsg" variable. Fixes: 09234b88ef55 ("drm/selftests/test-drm_dp_mst_helper: Move 'sideband_msg_req_encode_decode' onto the heap") Addresses-Coverity-ID: 1475685 ("Resource leak") Signed-off-by: José Expósito Signed-off-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20220108165812.46797-1-jose.exposito89@gmail.com --- drivers/gpu/drm/selftests/test-drm_dp_mst_helper.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/selftests/test-drm_dp_mst_helper.c b/drivers/gpu/drm/selftests/test-drm_dp_mst_helper.c index 4004e6dc14de..fc1deb1231a2 100644 --- a/drivers/gpu/drm/selftests/test-drm_dp_mst_helper.c +++ b/drivers/gpu/drm/selftests/test-drm_dp_mst_helper.c @@ -131,8 +131,10 @@ sideband_msg_req_encode_decode(struct drm_dp_sideband_msg_req_body *in) return false; txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) + if (!txmsg) { + kfree(out); return false; + } drm_dp_encode_sideband_req(in, txmsg); ret = drm_dp_decode_sideband_req(txmsg, out); -- cgit From 8638b4d8ddc944ef3c1899cb774cb07de0b416f6 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 22 Dec 2021 09:28:23 +0100 Subject: drm/ast: Replace module-init boiler-plate code with DRM helpers Remove custom ast_init() and ast_exit() functions and initialize the module with DRM module helpers. Signed-off-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Signed-off-by: Javier Martinez Canillas Link: https://patchwork.freedesktop.org/patch/msgid/20211222082831.196562-3-javierm@redhat.com --- drivers/gpu/drm/ast/ast_drv.c | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/ast/ast_drv.c b/drivers/gpu/drm/ast/ast_drv.c index 6d8613f6fe1c..7465c4f0156a 100644 --- a/drivers/gpu/drm/ast/ast_drv.c +++ b/drivers/gpu/drm/ast/ast_drv.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include "ast_drv.h" @@ -230,22 +231,7 @@ static struct pci_driver ast_pci_driver = { .driver.pm = &ast_pm_ops, }; -static int __init ast_init(void) -{ - if (drm_firmware_drivers_only() && ast_modeset == -1) - return -EINVAL; - - if (ast_modeset == 0) - return -EINVAL; - return pci_register_driver(&ast_pci_driver); -} -static void __exit ast_exit(void) -{ - pci_unregister_driver(&ast_pci_driver); -} - -module_init(ast_init); -module_exit(ast_exit); +drm_module_pci_driver_if_modeset(ast_pci_driver, ast_modeset); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); -- cgit From 54e67e5a2326b99c1461e29eb022e9d07a419abd Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 22 Dec 2021 09:28:24 +0100 Subject: drm/bochs: Replace module-init boiler-plate code with DRM helpers Remove custom bochs_init() and bochs_exit() functions and initialize the module with DRM module helpers. Signed-off-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Signed-off-by: Javier Martinez Canillas Link: https://patchwork.freedesktop.org/patch/msgid/20211222082831.196562-4-javierm@redhat.com --- drivers/gpu/drm/tiny/bochs.c | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tiny/bochs.c b/drivers/gpu/drm/tiny/bochs.c index fc26a1ce11ee..ed971c8bb446 100644 --- a/drivers/gpu/drm/tiny/bochs.c +++ b/drivers/gpu/drm/tiny/bochs.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -716,24 +717,7 @@ static struct pci_driver bochs_pci_driver = { /* ---------------------------------------------------------------------- */ /* module init/exit */ -static int __init bochs_init(void) -{ - if (drm_firmware_drivers_only() && bochs_modeset == -1) - return -EINVAL; - - if (bochs_modeset == 0) - return -EINVAL; - - return pci_register_driver(&bochs_pci_driver); -} - -static void __exit bochs_exit(void) -{ - pci_unregister_driver(&bochs_pci_driver); -} - -module_init(bochs_init); -module_exit(bochs_exit); +drm_module_pci_driver_if_modeset(bochs_pci_driver, bochs_modeset); MODULE_DEVICE_TABLE(pci, bochs_pci_tbl); MODULE_AUTHOR("Gerd Hoffmann "); -- cgit From a32762d1db4c13db53ab9005a24fe5465ab511f1 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 22 Dec 2021 09:28:25 +0100 Subject: drm/cirrus: Replace module-init boiler-plate code with DRM helpers Remove custom cirrus_init() and cirrus_exit() functions and initialize the module with DRM module helpers. Signed-off-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Signed-off-by: Javier Martinez Canillas Link: https://patchwork.freedesktop.org/patch/msgid/20211222082831.196562-5-javierm@redhat.com --- drivers/gpu/drm/tiny/cirrus.c | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tiny/cirrus.c b/drivers/gpu/drm/tiny/cirrus.c index c95d9ff7d600..ecf2475d0f16 100644 --- a/drivers/gpu/drm/tiny/cirrus.c +++ b/drivers/gpu/drm/tiny/cirrus.c @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -633,21 +634,7 @@ static struct pci_driver cirrus_pci_driver = { .remove = cirrus_pci_remove, }; -static int __init cirrus_init(void) -{ - if (drm_firmware_drivers_only()) - return -EINVAL; - - return pci_register_driver(&cirrus_pci_driver); -} - -static void __exit cirrus_exit(void) -{ - pci_unregister_driver(&cirrus_pci_driver); -} - -module_init(cirrus_init); -module_exit(cirrus_exit); +drm_module_pci_driver(cirrus_pci_driver) MODULE_DEVICE_TABLE(pci, pciidlist); MODULE_LICENSE("GPL"); -- cgit From 2075a734ed990c50b6c544c6256b0dc0b74b82ea Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 22 Dec 2021 09:28:26 +0100 Subject: drm/hisilicon/hibmc: Replace module initialization with DRM helpers Replace module_pci_driver() with drm_module_pci_driver(). The DRM macro respects drm_firmware_drivers_only() and fails if the flag has been set. Signed-off-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Signed-off-by: Javier Martinez Canillas Link: https://patchwork.freedesktop.org/patch/msgid/20211222082831.196562-6-javierm@redhat.com --- drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c index 610fc8e135f9..fe4269c5aa0a 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include "hibmc_drm_drv.h" @@ -379,7 +380,7 @@ static struct pci_driver hibmc_pci_driver = { .driver.pm = &hibmc_pm_ops, }; -module_pci_driver(hibmc_pci_driver); +drm_module_pci_driver(hibmc_pci_driver); MODULE_DEVICE_TABLE(pci, hibmc_pci_table); MODULE_AUTHOR("RongrongZou "); -- cgit From 7a90a846ad3fc58edde357d2723c457510ba58c2 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Wed, 22 Dec 2021 09:28:28 +0100 Subject: drm/imx/dcss: Replace module initialization with DRM helpers Replace module_platform_driver() with drm_module_platform_driver(). The DRM macro respects drm_firmware_drivers_only() and fails if the flag has been set. Signed-off-by: Javier Martinez Canillas Reviewed-by: Lucas Stach Acked-by: Thomas Zimmermann Link: https://patchwork.freedesktop.org/patch/msgid/20211222082831.196562-8-javierm@redhat.com --- drivers/gpu/drm/imx/dcss/dcss-drv.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/imx/dcss/dcss-drv.c b/drivers/gpu/drm/imx/dcss/dcss-drv.c index 8dc2f85c514b..24147ee7080e 100644 --- a/drivers/gpu/drm/imx/dcss/dcss-drv.c +++ b/drivers/gpu/drm/imx/dcss/dcss-drv.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include "dcss-dev.h" @@ -131,7 +132,7 @@ static struct platform_driver dcss_platform_driver = { }, }; -module_platform_driver(dcss_platform_driver); +drm_module_platform_driver(dcss_platform_driver); MODULE_AUTHOR("Laurentiu Palcu "); MODULE_DESCRIPTION("DCSS driver for i.MX8MQ"); -- cgit From e0f8cd2343f889b210266e28f07484efd527a977 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Wed, 22 Dec 2021 09:28:29 +0100 Subject: drm/komeda: Replace module initialization with DRM helpers Replace module_platform_driver() with drm_module_platform_driver(). The DRM macro respects drm_firmware_drivers_only() and fails if the flag has been set. Signed-off-by: Javier Martinez Canillas Acked-by: Thomas Zimmermann Link: https://patchwork.freedesktop.org/patch/msgid/20211222082831.196562-9-javierm@redhat.com --- drivers/gpu/drm/arm/display/komeda/komeda_drv.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_drv.c b/drivers/gpu/drm/arm/display/komeda/komeda_drv.c index e7933930a657..51e51ff299b7 100644 --- a/drivers/gpu/drm/arm/display/komeda/komeda_drv.c +++ b/drivers/gpu/drm/arm/display/komeda/komeda_drv.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "komeda_dev.h" #include "komeda_kms.h" @@ -198,7 +199,7 @@ static struct platform_driver komeda_platform_driver = { }, }; -module_platform_driver(komeda_platform_driver); +drm_module_platform_driver(komeda_platform_driver); MODULE_AUTHOR("James.Qian.Wang "); MODULE_DESCRIPTION("Komeda KMS driver"); -- cgit From 6aef229365f460bd386023ca5f08abc29cbe6555 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Wed, 22 Dec 2021 09:28:30 +0100 Subject: drm/arm/hdlcd: Replace module initialization with DRM helpers Replace module_platform_driver() with drm_module_platform_driver(). The DRM macro respects drm_firmware_drivers_only() and fails if the flag has been set. Signed-off-by: Javier Martinez Canillas Acked-by: Thomas Zimmermann Link: https://patchwork.freedesktop.org/patch/msgid/20211222082831.196562-10-javierm@redhat.com --- drivers/gpu/drm/arm/hdlcd_drv.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/arm/hdlcd_drv.c b/drivers/gpu/drm/arm/hdlcd_drv.c index 479c2422a2e0..e89ae0ec60eb 100644 --- a/drivers/gpu/drm/arm/hdlcd_drv.c +++ b/drivers/gpu/drm/arm/hdlcd_drv.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -434,7 +435,7 @@ static struct platform_driver hdlcd_platform_driver = { }, }; -module_platform_driver(hdlcd_platform_driver); +drm_module_platform_driver(hdlcd_platform_driver); MODULE_AUTHOR("Liviu Dudau"); MODULE_DESCRIPTION("ARM HDLCD DRM driver"); -- cgit From 37e0321ab2569b44f8a94339bf47653493ac864e Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Wed, 22 Dec 2021 09:28:31 +0100 Subject: drm/malidp: Replace module initialization with DRM helpers Replace module_platform_driver() with drm_module_platform_driver(). The DRM macro respects drm_firmware_drivers_only() and fails if the flag has been set. Signed-off-by: Javier Martinez Canillas Acked-by: Thomas Zimmermann Link: https://patchwork.freedesktop.org/patch/msgid/20211222082831.196562-11-javierm@redhat.com --- drivers/gpu/drm/arm/malidp_drv.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/arm/malidp_drv.c b/drivers/gpu/drm/arm/malidp_drv.c index 78d15b04b105..d5aef21426cf 100644 --- a/drivers/gpu/drm/arm/malidp_drv.c +++ b/drivers/gpu/drm/arm/malidp_drv.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -1008,7 +1009,7 @@ static struct platform_driver malidp_platform_driver = { }, }; -module_platform_driver(malidp_platform_driver); +drm_module_platform_driver(malidp_platform_driver); MODULE_AUTHOR("Liviu Dudau "); MODULE_DESCRIPTION("ARM Mali DP DRM driver"); -- cgit From 4f72fc3c7f3d9f29a438bb0e17c7773f2fc8242a Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Thu, 20 Jan 2022 20:31:16 -0800 Subject: drm/i915: Allocate intel_engine_coredump_alloc with ALLOW_FAIL Allocate intel_engine_coredump_alloc with ALLOW_FAIL rather than GFP_KERNEL to fully decouple the error capture from fence signalling. v2: (John Harrison) - Fix typo in commit message (s/do/to) Fixes: 8b91cdd4f8649 ("drm/i915: Use __GFP_KSWAPD_RECLAIM in the capture code") Signed-off-by: Matthew Brost Reviewed-by: John Harrison Signed-off-by: John Harrison Link: https://patchwork.freedesktop.org/patch/msgid/20220121043118.24886-2-matthew.brost@intel.com --- drivers/gpu/drm/i915/i915_gpu_error.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/i915_gpu_error.c b/drivers/gpu/drm/i915/i915_gpu_error.c index f8c4336cba89..9c3f53579fe9 100644 --- a/drivers/gpu/drm/i915/i915_gpu_error.c +++ b/drivers/gpu/drm/i915/i915_gpu_error.c @@ -1515,7 +1515,7 @@ capture_engine(struct intel_engine_cs *engine, struct i915_request *rq = NULL; unsigned long flags; - ee = intel_engine_coredump_alloc(engine, GFP_KERNEL); + ee = intel_engine_coredump_alloc(engine, ALLOW_FAIL); if (!ee) return NULL; -- cgit From 1f73a367420c954f5cae2f899ebb9515be093645 Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Thu, 20 Jan 2022 20:31:17 -0800 Subject: drm/i915/guc: Add work queue to trigger a GT reset The G2H handler needs to be flushed during a GT reset but a G2H indicating engine reset failure can trigger a GT reset. Add a worker to trigger the GT rest when an engine reset failure is received to break this circular dependency. v2: (John Harrison) - Store engine reset mask - Fix typo in commit message v3: (John Harrison) - Fix another typo in commit message - s/reset_*/reset_fail_*/ Signed-off-by: Matthew Brost Reviewed-by: John Harrison Signed-off-by: John Harrison Link: https://patchwork.freedesktop.org/patch/msgid/20220121043118.24886-3-matthew.brost@intel.com --- drivers/gpu/drm/i915/gt/uc/intel_guc.h | 9 ++++++ drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c | 37 ++++++++++++++++++++--- 2 files changed, 42 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc.h b/drivers/gpu/drm/i915/gt/uc/intel_guc.h index 9d26a86fe557..d59bbf49d1c2 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc.h +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc.h @@ -119,6 +119,15 @@ struct intel_guc { * function as it might be in an atomic context (no sleeping) */ struct work_struct destroyed_worker; + /** + * @reset_fail_worker: worker to trigger a GT reset after an + * engine reset fails + */ + struct work_struct reset_fail_worker; + /** + * @reset_fail_mask: mask of engines that failed to reset + */ + intel_engine_mask_t reset_fail_mask; } submission_state; /** diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c index 411054f77f33..6fc73f277a44 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c @@ -1730,6 +1730,7 @@ void intel_guc_submission_reset_finish(struct intel_guc *guc) } static void destroyed_worker_func(struct work_struct *w); +static void reset_fail_worker_func(struct work_struct *w); /* * Set up the memory resources to be shared with the GuC (via the GGTT) @@ -1760,6 +1761,8 @@ int intel_guc_submission_init(struct intel_guc *guc) INIT_LIST_HEAD(&guc->submission_state.destroyed_contexts); INIT_WORK(&guc->submission_state.destroyed_worker, destroyed_worker_func); + INIT_WORK(&guc->submission_state.reset_fail_worker, + reset_fail_worker_func); guc->submission_state.guc_ids_bitmap = bitmap_zalloc(NUMBER_MULTI_LRC_GUC_ID(guc), GFP_KERNEL); @@ -4025,6 +4028,26 @@ guc_lookup_engine(struct intel_guc *guc, u8 guc_class, u8 instance) return gt->engine_class[engine_class][instance]; } +static void reset_fail_worker_func(struct work_struct *w) +{ + struct intel_guc *guc = container_of(w, struct intel_guc, + submission_state.reset_fail_worker); + struct intel_gt *gt = guc_to_gt(guc); + intel_engine_mask_t reset_fail_mask; + unsigned long flags; + + spin_lock_irqsave(&guc->submission_state.lock, flags); + reset_fail_mask = guc->submission_state.reset_fail_mask; + guc->submission_state.reset_fail_mask = 0; + spin_unlock_irqrestore(&guc->submission_state.lock, flags); + + if (likely(reset_fail_mask)) + intel_gt_handle_error(gt, reset_fail_mask, + I915_ERROR_CAPTURE, + "GuC failed to reset engine mask=0x%x\n", + reset_fail_mask); +} + int intel_guc_engine_failure_process_msg(struct intel_guc *guc, const u32 *msg, u32 len) { @@ -4032,6 +4055,7 @@ int intel_guc_engine_failure_process_msg(struct intel_guc *guc, struct intel_gt *gt = guc_to_gt(guc); u8 guc_class, instance; u32 reason; + unsigned long flags; if (unlikely(len != 3)) { drm_err(>->i915->drm, "Invalid length %u", len); @@ -4056,10 +4080,15 @@ int intel_guc_engine_failure_process_msg(struct intel_guc *guc, drm_err(>->i915->drm, "GuC engine reset request failed on %d:%d (%s) because 0x%08X", guc_class, instance, engine->name, reason); - intel_gt_handle_error(gt, engine->mask, - I915_ERROR_CAPTURE, - "GuC failed to reset %s (reason=0x%08x)\n", - engine->name, reason); + spin_lock_irqsave(&guc->submission_state.lock, flags); + guc->submission_state.reset_fail_mask |= engine->mask; + spin_unlock_irqrestore(&guc->submission_state.lock, flags); + + /* + * A GT reset flushes this worker queue (G2H handler) so we must use + * another worker to trigger a GT reset. + */ + queue_work(system_unbound_wq, &guc->submission_state.reset_fail_worker); return 0; } -- cgit From 5fe0fdd23e3379833f4346b49fa791733c22ed29 Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Thu, 20 Jan 2022 20:31:18 -0800 Subject: drm/i915/guc: Flush G2H handler during a GT reset Now that the error capture is fully decoupled from fence signalling (request retirement to free memory, which in turn depends on resets) we can safely flush the G2H handler during a GT reset. This eliminates corner cases where GuC generated G2H (e.g. engine resets) race with a GT reset. v2: (John Harrison) - Fix typo in commit message (s/is/in) Signed-off-by: Matthew Brost Reviewed-by: John Harrison Signed-off-by: John Harrison Link: https://patchwork.freedesktop.org/patch/msgid/20220121043118.24886-4-matthew.brost@intel.com --- drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c index 6fc73f277a44..343bd4804e73 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c @@ -1395,8 +1395,6 @@ static void guc_flush_destroyed_contexts(struct intel_guc *guc); void intel_guc_submission_reset_prepare(struct intel_guc *guc) { - int i; - if (unlikely(!guc_submission_initialized(guc))) { /* Reset called during driver load? GuC not yet initialised! */ return; @@ -1413,21 +1411,7 @@ void intel_guc_submission_reset_prepare(struct intel_guc *guc) guc_flush_submissions(guc); guc_flush_destroyed_contexts(guc); - - /* - * Handle any outstanding G2Hs before reset. Call IRQ handler directly - * each pass as interrupt have been disabled. We always scrub for - * outstanding G2H as it is possible for outstanding_submission_g2h to - * be incremented after the context state update. - */ - for (i = 0; i < 4 && atomic_read(&guc->outstanding_submission_g2h); ++i) { - intel_guc_to_host_event_handler(guc); -#define wait_for_reset(guc, wait_var) \ - intel_guc_wait_for_pending_msg(guc, wait_var, false, (HZ / 20)) - do { - wait_for_reset(guc, &guc->outstanding_submission_g2h); - } while (!list_empty(&guc->ct.requests.incoming)); - } + flush_work(&guc->ct.requests.worker); scrub_guc_desc_for_outstanding_g2h(guc); } -- cgit From cb935c4618bd2ff9058feee4af7088446da6a763 Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Tue, 11 Jan 2022 08:39:29 -0800 Subject: drm/i915: Lock timeline mutex directly in error path of eb_pin_timeline Don't use the interruptable version of the timeline mutex lock in the error path of eb_pin_timeline as the cleanup must always happen. v2: (John Harrison) - Don't check for interrupt during mutex lock v3: (Tvrtko) - A comment explaining why lock helper isn't used Fixes: 544460c33821 ("drm/i915: Multi-BB execbuf") Signed-off-by: Matthew Brost Reviewed-by: John Harrison Signed-off-by: John Harrison Link: https://patchwork.freedesktop.org/patch/msgid/20220111163929.14017-1-matthew.brost@intel.com --- drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c index 0103f39b5c77..ae6805b37806 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c @@ -2497,9 +2497,14 @@ static int eb_pin_timeline(struct i915_execbuffer *eb, struct intel_context *ce, timeout) < 0) { i915_request_put(rq); - tl = intel_context_timeline_lock(ce); + /* + * Error path, cannot use intel_context_timeline_lock as + * that is user interruptable and this clean up step + * must be done. + */ + mutex_lock(&ce->timeline->mutex); intel_context_exit(ce); - intel_context_timeline_unlock(tl); + mutex_unlock(&ce->timeline->mutex); if (nonblock) return -EWOULDBLOCK; -- cgit From 7305f5b3a5d62d1ce1405e91fa367e6a9126090c Mon Sep 17 00:00:00 2001 From: Christian König Date: Tue, 20 Jul 2021 10:56:44 +0200 Subject: drm/radeon: use ttm_resource_manager_debug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of calling the debug operation directly. Signed-off-by: Christian König Reviewed-by: Huang Rui Link: https://patchwork.freedesktop.org/patch/msgid/20211124124430.20859-8-christian.koenig@amd.com --- drivers/gpu/drm/radeon/radeon_ttm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/radeon/radeon_ttm.c b/drivers/gpu/drm/radeon/radeon_ttm.c index 11b21d605584..0d1283cdc8fb 100644 --- a/drivers/gpu/drm/radeon/radeon_ttm.c +++ b/drivers/gpu/drm/radeon/radeon_ttm.c @@ -802,7 +802,7 @@ static int radeon_mm_vram_dump_table_show(struct seq_file *m, void *unused) TTM_PL_VRAM); struct drm_printer p = drm_seq_file_printer(m); - man->func->debug(man, &p); + ttm_resource_manager_debug(man, &p); return 0; } @@ -820,7 +820,7 @@ static int radeon_mm_gtt_dump_table_show(struct seq_file *m, void *unused) TTM_PL_TT); struct drm_printer p = drm_seq_file_printer(m); - man->func->debug(man, &p); + ttm_resource_manager_debug(man, &p); return 0; } -- cgit From b3bddb7a3806f26ba3eacb4f77834102ed344b9d Mon Sep 17 00:00:00 2001 From: Christian König Date: Tue, 20 Jul 2021 11:04:50 +0200 Subject: drm/amdgpu: use ttm_resource_manager_debug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of calling the debug operation directly. Signed-off-by: Christian König Reviewed-by: Huang Rui Link: https://patchwork.freedesktop.org/patch/msgid/20211124124430.20859-10-christian.koenig@amd.com --- drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index fb0d8bffdce2..eac2ff4647e3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -2076,7 +2076,7 @@ static int amdgpu_mm_vram_table_show(struct seq_file *m, void *unused) TTM_PL_VRAM); struct drm_printer p = drm_seq_file_printer(m); - man->func->debug(man, &p); + ttm_resource_manager_debug(man, &p); return 0; } @@ -2094,7 +2094,7 @@ static int amdgpu_mm_tt_table_show(struct seq_file *m, void *unused) TTM_PL_TT); struct drm_printer p = drm_seq_file_printer(m); - man->func->debug(man, &p); + ttm_resource_manager_debug(man, &p); return 0; } @@ -2105,7 +2105,7 @@ static int amdgpu_mm_gds_table_show(struct seq_file *m, void *unused) AMDGPU_PL_GDS); struct drm_printer p = drm_seq_file_printer(m); - man->func->debug(man, &p); + ttm_resource_manager_debug(man, &p); return 0; } @@ -2116,7 +2116,7 @@ static int amdgpu_mm_gws_table_show(struct seq_file *m, void *unused) AMDGPU_PL_GWS); struct drm_printer p = drm_seq_file_printer(m); - man->func->debug(man, &p); + ttm_resource_manager_debug(man, &p); return 0; } @@ -2127,7 +2127,7 @@ static int amdgpu_mm_oa_table_show(struct seq_file *m, void *unused) AMDGPU_PL_OA); struct drm_printer p = drm_seq_file_printer(m); - man->func->debug(man, &p); + ttm_resource_manager_debug(man, &p); return 0; } -- cgit From 70b42b5845c84f33ed2b79a0fa96134710c652f2 Mon Sep 17 00:00:00 2001 From: Andi Shyti Date: Mon, 24 Jan 2022 11:44:18 +0200 Subject: drm/i915: fix header file inclusion for might_alloc() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace "linux/slab.h" with "linux/sched/mm.h" header inclusion as the first is not required, while the second, if not included, prodouces the following error: drivers/gpu/drm/i915/i915_vma_resource.c: In function ‘i915_vma_resource_bind_dep_await’: drivers/gpu/drm/i915/i915_vma_resource.c:381:9: error: implicit declaration of function ‘might_alloc’; did you mean ‘might_lock’? [-Werror=implicit-function-declaration] 381 | might_alloc(gfp); | ^~~~~~~~~~~ | might_lock Fixes: 2f6b90da9192 ("drm/i915: Use vma resources for async unbinding") Signed-off-by: Andi Shyti Cc: Thomas Hellström Reviewed-by: Thomas Hellström Signed-off-by: Maarten Lankhorst Link: https://patchwork.freedesktop.org/patch/msgid/20220124094418.2661-1-andi.shyti@linux.intel.com --- drivers/gpu/drm/i915/i915_vma_resource.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/i915_vma_resource.c b/drivers/gpu/drm/i915/i915_vma_resource.c index 1f41c0c699eb..bbb0ff14272f 100644 --- a/drivers/gpu/drm/i915/i915_vma_resource.c +++ b/drivers/gpu/drm/i915/i915_vma_resource.c @@ -4,7 +4,7 @@ */ #include -#include +#include #include "i915_sw_fence.h" #include "i915_vma_resource.h" -- cgit From 2869f599c0d8c9c6492bec40d062951be8caae04 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Sun, 23 Jan 2022 11:16:52 +0100 Subject: drm/edid: support Microsoft extension for HMDs and specialized monitors Add minimal support for parsing VSDBs documented in Microsoft's "EDID extension for head-mounted and specialized monitors" [1]. The version field and the desktop usage flag can be used to set the non_desktop connector property. [1] https://docs.microsoft.com/en-us/windows-hardware/drivers/display/specialized-monitors-edid-extension Tested with HPN-36C1 and LEN-B800. Signed-off-by: Philipp Zabel Reviewed-by: Jani Nikula Signed-off-by: Jani Nikula Link: https://patchwork.freedesktop.org/patch/msgid/20220123101653.147333-1-philipp.zabel@gmail.com --- drivers/gpu/drm/drm_edid.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 12893e7be89b..271b5616cfaf 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -93,6 +93,8 @@ static int oui(u8 first, u8 second, u8 third) /* Non desktop display (i.e. HMD) */ #define EDID_QUIRK_NON_DESKTOP (1 << 12) +#define MICROSOFT_IEEE_OUI 0xca125c + struct detailed_mode_closure { struct drm_connector *connector; struct edid *edid; @@ -4222,6 +4224,17 @@ static bool cea_db_is_hdmi_forum_vsdb(const u8 *db) return oui(db[3], db[2], db[1]) == HDMI_FORUM_IEEE_OUI; } +static bool cea_db_is_microsoft_vsdb(const u8 *db) +{ + if (cea_db_tag(db) != VENDOR_BLOCK) + return false; + + if (cea_db_payload_len(db) != 21) + return false; + + return oui(db[3], db[2], db[1]) == MICROSOFT_IEEE_OUI; +} + static bool cea_db_is_vcdb(const u8 *db) { if (cea_db_tag(db) != USE_EXTENDED_TAG) @@ -5149,6 +5162,25 @@ drm_parse_hdmi_vsdb_video(struct drm_connector *connector, const u8 *db) drm_parse_hdmi_deep_color_info(connector, db); } +/* + * See EDID extension for head-mounted and specialized monitors, specified at: + * https://docs.microsoft.com/en-us/windows-hardware/drivers/display/specialized-monitors-edid-extension + */ +static void drm_parse_microsoft_vsdb(struct drm_connector *connector, + const u8 *db) +{ + struct drm_display_info *info = &connector->display_info; + u8 version = db[4]; + bool desktop_usage = db[5] & BIT(6); + + /* Version 1 and 2 for HMDs, version 3 flags desktop usage explicitly */ + if (version == 1 || version == 2 || (version == 3 && !desktop_usage)) + info->non_desktop = true; + + drm_dbg_kms(connector->dev, "HMD or specialized display VSDB version %u: 0x%02x\n", + version, db[5]); +} + static void drm_parse_cea_ext(struct drm_connector *connector, const struct edid *edid) { @@ -5179,6 +5211,8 @@ static void drm_parse_cea_ext(struct drm_connector *connector, drm_parse_hdmi_vsdb_video(connector, db); if (cea_db_is_hdmi_forum_vsdb(db)) drm_parse_hdmi_forum_vsdb(connector, db); + if (cea_db_is_microsoft_vsdb(db)) + drm_parse_microsoft_vsdb(connector, db); if (cea_db_is_y420cmdb(db)) drm_parse_y420cmdb_bitmap(connector, db); if (cea_db_is_vcdb(db)) -- cgit From 50dc95d561a2552b0d76a9f91b38005195bf2974 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Sun, 23 Jan 2022 11:16:53 +0100 Subject: drm/edid: remove non_desktop quirk for HPN-3515 and LEN-B800. Now that there is support for the Microsoft VSDB for HMDs, remove the non-desktop quirk for two devices that are verified to contain it in their EDID: HPN-3515 and LEN-B800. Presumably most of the other Windows Mixed Reality headsets contain it as well, but there are ACR-7FCE and SEC-5194 devices without it. Tested with LEN-B800. Signed-off-by: Philipp Zabel Reviewed-by: Jani Nikula Signed-off-by: Jani Nikula Link: https://patchwork.freedesktop.org/patch/msgid/20220123101653.147333-2-philipp.zabel@gmail.com --- drivers/gpu/drm/drm_edid.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 271b5616cfaf..c23ad8f0b3e9 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -214,9 +214,7 @@ static const struct edid_quirk { /* Windows Mixed Reality Headsets */ EDID_QUIRK('A', 'C', 'R', 0x7fce, EDID_QUIRK_NON_DESKTOP), - EDID_QUIRK('H', 'P', 'N', 0x3515, EDID_QUIRK_NON_DESKTOP), EDID_QUIRK('L', 'E', 'N', 0x0408, EDID_QUIRK_NON_DESKTOP), - EDID_QUIRK('L', 'E', 'N', 0xb800, EDID_QUIRK_NON_DESKTOP), EDID_QUIRK('F', 'U', 'J', 0x1970, EDID_QUIRK_NON_DESKTOP), EDID_QUIRK('D', 'E', 'L', 0x7fce, EDID_QUIRK_NON_DESKTOP), EDID_QUIRK('S', 'E', 'C', 0x144a, EDID_QUIRK_NON_DESKTOP), -- cgit From ce99534e978d4a36787dbe5e5c57749d12e6bf4a Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Tue, 28 Dec 2021 12:10:51 +0200 Subject: drm/edid: improve non-desktop quirk logging Improve non-desktop quirk logging if the EDID indicates non-desktop. If both are set, note about redundant quirk. If there's no quirk but the EDID indicates non-desktop, don't log non-desktop is set to 0. Cc: Philipp Zabel Signed-off-by: Jani Nikula Reviewed-by: Philipp Zabel Tested-by: Philipp Zabel Link: https://patchwork.freedesktop.org/patch/msgid/20211228101051.317989-1-jani.nikula@intel.com --- drivers/gpu/drm/drm_edid.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index c23ad8f0b3e9..eb61a1a92dc0 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -5365,17 +5365,13 @@ u32 drm_add_display_info(struct drm_connector *connector, const struct edid *edi info->width_mm = edid->width_cm * 10; info->height_mm = edid->height_cm * 10; - info->non_desktop = !!(quirks & EDID_QUIRK_NON_DESKTOP); - drm_get_monitor_range(connector, edid); - DRM_DEBUG_KMS("non_desktop set to %d\n", info->non_desktop); - if (edid->revision < 3) - return quirks; + goto out; if (!(edid->input & DRM_EDID_INPUT_DIGITAL)) - return quirks; + goto out; drm_parse_cea_ext(connector, edid); @@ -5395,7 +5391,7 @@ u32 drm_add_display_info(struct drm_connector *connector, const struct edid *edi /* Only defined for 1.4 with digital displays */ if (edid->revision < 4) - return quirks; + goto out; switch (edid->input & DRM_EDID_DIGITAL_DEPTH_MASK) { case DRM_EDID_DIGITAL_DEPTH_6: @@ -5433,6 +5429,13 @@ u32 drm_add_display_info(struct drm_connector *connector, const struct edid *edi drm_update_mso(connector, edid); +out: + if (quirks & EDID_QUIRK_NON_DESKTOP) { + drm_dbg_kms(connector->dev, "Non-desktop display%s\n", + info->non_desktop ? " (redundant quirk)" : ""); + info->non_desktop = true; + } + return quirks; } -- cgit From 8b250cd3b5da18bd90f34ecb04c01661213b65ba Mon Sep 17 00:00:00 2001 From: Ira Weiny Date: Sun, 23 Jan 2022 17:54:05 -0800 Subject: drm/gma: Remove calls to kmap() kmap() is being deprecated and these instances are easy to convert to kmap_local_page(). Furthermore, in gma_crtc_cursor_set() use the memcpy_from_page() helper instead of an open coded use of kmap_local_page(). Signed-off-by: Ira Weiny Signed-off-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20220124015409.807587-4-ira.weiny@intel.com --- drivers/gpu/drm/gma500/gma_display.c | 6 ++---- drivers/gpu/drm/gma500/mmu.c | 8 ++++---- 2 files changed, 6 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/gma500/gma_display.c b/drivers/gpu/drm/gma500/gma_display.c index 99da3118131a..60ba7de59139 100644 --- a/drivers/gpu/drm/gma500/gma_display.c +++ b/drivers/gpu/drm/gma500/gma_display.c @@ -335,7 +335,7 @@ int gma_crtc_cursor_set(struct drm_crtc *crtc, struct psb_gem_object *pobj; struct psb_gem_object *cursor_pobj = gma_crtc->cursor_pobj; struct drm_gem_object *obj; - void *tmp_dst, *tmp_src; + void *tmp_dst; int ret = 0, i, cursor_pages; /* If we didn't get a handle then turn the cursor off */ @@ -400,9 +400,7 @@ int gma_crtc_cursor_set(struct drm_crtc *crtc, /* Copy the cursor to cursor mem */ tmp_dst = dev_priv->vram_addr + cursor_pobj->offset; for (i = 0; i < cursor_pages; i++) { - tmp_src = kmap(pobj->pages[i]); - memcpy(tmp_dst, tmp_src, PAGE_SIZE); - kunmap(pobj->pages[i]); + memcpy_from_page(tmp_dst, pobj->pages[i], 0, PAGE_SIZE); tmp_dst += PAGE_SIZE; } diff --git a/drivers/gpu/drm/gma500/mmu.c b/drivers/gpu/drm/gma500/mmu.c index fe9ace2a7967..a70b01ccdf70 100644 --- a/drivers/gpu/drm/gma500/mmu.c +++ b/drivers/gpu/drm/gma500/mmu.c @@ -184,17 +184,17 @@ struct psb_mmu_pd *psb_mmu_alloc_pd(struct psb_mmu_driver *driver, pd->invalid_pte = 0; } - v = kmap(pd->dummy_pt); + v = kmap_local_page(pd->dummy_pt); for (i = 0; i < (PAGE_SIZE / sizeof(uint32_t)); ++i) v[i] = pd->invalid_pte; - kunmap(pd->dummy_pt); + kunmap_local(v); - v = kmap(pd->p); + v = kmap_local_page(pd->p); for (i = 0; i < (PAGE_SIZE / sizeof(uint32_t)); ++i) v[i] = pd->invalid_pde; - kunmap(pd->p); + kunmap_local(v); clear_page(kmap(pd->dummy_page)); kunmap(pd->dummy_page); -- cgit From ff3aeb34deb24f40626c171ce1cfd447a2edafc8 Mon Sep 17 00:00:00 2001 From: Matt Roper Date: Thu, 20 Jan 2022 15:41:47 -0800 Subject: drm/i915/dg2: Add Wa_18018781329 A few more MOD registers need to be programmed on DG2. Signed-off-by: Matt Roper Link: https://patchwork.freedesktop.org/patch/msgid/20220120234147.1200574-1-matthew.d.roper@intel.com Reviewed-by: Swathi Dhanavanthri --- drivers/gpu/drm/i915/gt/intel_workarounds.c | 6 ++++++ drivers/gpu/drm/i915/i915_reg.h | 4 ++++ 2 files changed, 10 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/intel_workarounds.c b/drivers/gpu/drm/i915/gt/intel_workarounds.c index ab3277a3d593..930da3b4e0eb 100644 --- a/drivers/gpu/drm/i915/gt/intel_workarounds.c +++ b/drivers/gpu/drm/i915/gt/intel_workarounds.c @@ -1508,6 +1508,12 @@ dg2_gt_workarounds_init(struct intel_gt *gt, struct i915_wa_list *wal) */ wa_write_or(wal, XEHP_L3SCQREG7, BLEND_FILL_CACHING_OPT_DIS); wa_write_or(wal, GEN12_SQCM, EN_32B_ACCESS); + + /* Wa_18018781329:dg2 */ + wa_write_or(wal, RENDER_MOD_CTRL, FORCE_MISS_FTLB); + wa_write_or(wal, COMP_MOD_CTRL, FORCE_MISS_FTLB); + wa_write_or(wal, VDBX_MOD_CTRL, FORCE_MISS_FTLB); + wa_write_or(wal, VEBX_MOD_CTRL, FORCE_MISS_FTLB); } static void diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h index 4c28dadf8d69..5a7c9ae8001d 100644 --- a/drivers/gpu/drm/i915/i915_reg.h +++ b/drivers/gpu/drm/i915/i915_reg.h @@ -511,6 +511,10 @@ static inline bool i915_mmio_reg_valid(i915_reg_t reg) #define TAG_BLOCK_CLKGATE_DIS REG_BIT(7) #define GEN12_MERT_MOD_CTRL _MMIO(0xcf28) +#define RENDER_MOD_CTRL _MMIO(0xcf2c) +#define COMP_MOD_CTRL _MMIO(0xcf30) +#define VDBX_MOD_CTRL _MMIO(0xcf34) +#define VEBX_MOD_CTRL _MMIO(0xcf38) #define FORCE_MISS_FTLB REG_BIT(3) #define GAB_CTL _MMIO(0x24000) -- cgit From 4fd5e720b709b87f67809a871fde9fb6cb910f28 Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Mon, 24 Jan 2022 23:16:33 +0100 Subject: drm/docs: Document where the C8 color lut is stored MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also add notes that for atomic drivers it's really somewhere else and no longer in struct drm_crtc. Maybe we should put a bigger warning here that this is confusing, since the pixel format is a plane property, but the GAMMA_LUT property is on the crtc. But I think we can fix this if/when someone finds a need for a per-plane CLUT, since I'm not sure such hw even exists. I'm also not sure whether even hardware with a CLUT and a full color correction pipeline with degamm/cgm/gamma exists. Motivated by comments from Geert that we have a gap here. v2: More names for color luts (Laurent). Reviewed-by: Alex Deucher Cc: Laurent Pinchart Reviewed-by: Laurent Pinchart Cc: Geert Uytterhoeven Cc: Ville Syrjälä Cc: Maarten Lankhorst Cc: Maxime Ripard Cc: Thomas Zimmermann Cc: David Airlie Cc: Daniel Vetter Signed-off-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20220124221633.952374-1-daniel.vetter@ffwll.ch --- drivers/gpu/drm/drm_color_mgmt.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/drm_color_mgmt.c b/drivers/gpu/drm/drm_color_mgmt.c index bb14f488c8f6..9079fbe21d2f 100644 --- a/drivers/gpu/drm/drm_color_mgmt.c +++ b/drivers/gpu/drm/drm_color_mgmt.c @@ -82,6 +82,10 @@ * driver boot-up state too. Drivers can access this blob through * &drm_crtc_state.gamma_lut. * + * Note that for mostly historical reasons stemming from Xorg heritage, + * this is also used to store the color map (also sometimes color lut, CLUT + * or color palette) for indexed formats like DRM_FORMAT_C8. + * * “GAMMA_LUT_SIZE”: * Unsigned range property to give the size of the lookup table to be set * on the GAMMA_LUT property (the size depends on the underlying hardware). -- cgit From 4a46e5d251a39e7c10493743ac89a31f6f3ce5b5 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Thu, 20 Jan 2022 16:16:10 +0100 Subject: drm/edid: Rename drm_hdmi_avi_infoframe_colorspace to _colorimetry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The drm_hdmi_avi_infoframe_colorspace() function actually sets the colorimetry and extended_colorimetry fields in the hdmi_avi_infoframe structure with DRM_MODE_COLORIMETRY_* values. To make things worse, the hdmi_avi_infoframe structure also has a colorspace field used to signal whether an RGB or YUV output is being used. Let's remove the inconsistency and allow for the colorspace usage by renaming the function. Reviewed-by: Ville Syrjälä Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20220120151625.594595-2-maxime@cerno.tech --- drivers/gpu/drm/drm_edid.c | 8 ++++---- drivers/gpu/drm/i915/display/intel_hdmi.c | 2 +- drivers/gpu/drm/i915/display/intel_lspcon.c | 2 +- drivers/gpu/drm/vc4/vc4_hdmi.c | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index eb61a1a92dc0..d8df2e7b8786 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -5926,13 +5926,13 @@ static const u32 hdmi_colorimetry_val[] = { #undef ACE /** - * drm_hdmi_avi_infoframe_colorspace() - fill the HDMI AVI infoframe - * colorspace information + * drm_hdmi_avi_infoframe_colorimetry() - fill the HDMI AVI infoframe + * colorimetry information * @frame: HDMI AVI infoframe * @conn_state: connector state */ void -drm_hdmi_avi_infoframe_colorspace(struct hdmi_avi_infoframe *frame, +drm_hdmi_avi_infoframe_colorimetry(struct hdmi_avi_infoframe *frame, const struct drm_connector_state *conn_state) { u32 colorimetry_val; @@ -5951,7 +5951,7 @@ drm_hdmi_avi_infoframe_colorspace(struct hdmi_avi_infoframe *frame, frame->extended_colorimetry = (colorimetry_val >> 2) & EXTENDED_COLORIMETRY_MASK; } -EXPORT_SYMBOL(drm_hdmi_avi_infoframe_colorspace); +EXPORT_SYMBOL(drm_hdmi_avi_infoframe_colorimetry); /** * drm_hdmi_avi_infoframe_quant_range() - fill the HDMI AVI infoframe diff --git a/drivers/gpu/drm/i915/display/intel_hdmi.c b/drivers/gpu/drm/i915/display/intel_hdmi.c index 3b5b9e7b05b7..96e508ddc4af 100644 --- a/drivers/gpu/drm/i915/display/intel_hdmi.c +++ b/drivers/gpu/drm/i915/display/intel_hdmi.c @@ -730,7 +730,7 @@ intel_hdmi_compute_avi_infoframe(struct intel_encoder *encoder, else frame->colorspace = HDMI_COLORSPACE_RGB; - drm_hdmi_avi_infoframe_colorspace(frame, conn_state); + drm_hdmi_avi_infoframe_colorimetry(frame, conn_state); /* nonsense combination */ drm_WARN_ON(encoder->base.dev, crtc_state->limited_color_range && diff --git a/drivers/gpu/drm/i915/display/intel_lspcon.c b/drivers/gpu/drm/i915/display/intel_lspcon.c index e879d36c31ad..012f13e034bf 100644 --- a/drivers/gpu/drm/i915/display/intel_lspcon.c +++ b/drivers/gpu/drm/i915/display/intel_lspcon.c @@ -537,7 +537,7 @@ void lspcon_set_infoframes(struct intel_encoder *encoder, frame.avi.colorspace = HDMI_COLORSPACE_RGB; /* Set the Colorspace as per the HDMI spec */ - drm_hdmi_avi_infoframe_colorspace(&frame.avi, conn_state); + drm_hdmi_avi_infoframe_colorimetry(&frame.avi, conn_state); /* nonsense combination */ drm_WARN_ON(encoder->base.dev, crtc_state->limited_color_range && diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 053fbaf765ca..be39e55ae113 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -502,7 +502,7 @@ static void vc4_hdmi_set_avi_infoframe(struct drm_encoder *encoder) vc4_encoder->limited_rgb_range ? HDMI_QUANTIZATION_RANGE_LIMITED : HDMI_QUANTIZATION_RANGE_FULL); - drm_hdmi_avi_infoframe_colorspace(&frame.avi, cstate); + drm_hdmi_avi_infoframe_colorimetry(&frame.avi, cstate); drm_hdmi_avi_infoframe_bars(&frame.avi, cstate); vc4_hdmi_write_infoframe(encoder, &frame); -- cgit From 75478b3b393bcbdca4e6da76fe3a9f1a4133ec5d Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Thu, 20 Jan 2022 16:16:11 +0100 Subject: drm/edid: Don't clear formats if using deep color MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current code, when parsing the EDID Deep Color depths, that the YUV422 cannot be used, referring to the HDMI 1.3 Specification. This specification, in its section 6.2.4, indeed states: For each supported Deep Color mode, RGB 4:4:4 shall be supported and optionally YCBCR 4:4:4 may be supported. YCBCR 4:2:2 is not permitted for any Deep Color mode. This indeed can be interpreted like the code does, but the HDMI 1.4 specification further clarifies that statement in its section 6.2.4: For each supported Deep Color mode, RGB 4:4:4 shall be supported and optionally YCBCR 4:4:4 may be supported. YCBCR 4:2:2 is also 36-bit mode but does not require the further use of the Deep Color modes described in section 6.5.2 and 6.5.3. This means that, even though YUV422 can be used with 12 bit per color, it shouldn't be treated as a deep color mode. This is also broken with YUV444 if it's supported by the display, but DRM_EDID_HDMI_DC_Y444 isn't set. In such a case, the code will clear color_formats of the YUV444 support set previously in drm_parse_cea_ext(), but will not set it back. Since the formats supported are already setup properly in drm_parse_cea_ext(), let's just remove the code modifying the formats in drm_parse_hdmi_deep_color_info() Fixes: d0c94692e0a3 ("drm/edid: Parse and handle HDMI deep color modes.") Signed-off-by: Maxime Ripard Reviewed-by: Ville Syrjälä Link: https://patchwork.freedesktop.org/patch/msgid/20220120151625.594595-3-maxime@cerno.tech --- drivers/gpu/drm/drm_edid.c | 8 -------- 1 file changed, 8 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index d8df2e7b8786..d00cac879bd5 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -5115,16 +5115,8 @@ static void drm_parse_hdmi_deep_color_info(struct drm_connector *connector, connector->name, dc_bpc); info->bpc = dc_bpc; - /* - * Deep color support mandates RGB444 support for all video - * modes and forbids YCRCB422 support for all video modes per - * HDMI 1.3 spec. - */ - info->color_formats = DRM_COLOR_FORMAT_RGB444; - /* YCRCB444 is optional according to spec. */ if (hdmi[6] & DRM_EDID_HDMI_DC_Y444) { - info->color_formats |= DRM_COLOR_FORMAT_YCRCB444; DRM_DEBUG("%s: HDMI sink does YCRCB444 in deep color.\n", connector->name); } -- cgit From 4adc33f36d80489339f1b43dfeee96bb9ea8e459 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Thu, 20 Jan 2022 16:16:12 +0100 Subject: drm/edid: Split deep color modes between RGB and YUV444 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current code assumes that the RGB444 and YUV444 formats are the same, but the HDMI 2.0 specification states that: The three DC_XXbit bits above only indicate support for RGB 4:4:4 at that pixel size. Support for YCBCR 4:4:4 in Deep Color modes is indicated with the DC_Y444 bit. If DC_Y444 is set, then YCBCR 4:4:4 is supported for all modes indicated by the DC_XXbit flags. So if we have YUV444 support and any DC_XXbit flag set but the DC_Y444 flag isn't, we'll assume that we support that deep colour mode for YUV444 which breaks the specification. In order to fix this, let's split the edid_hdmi_dc_modes field in struct drm_display_info into two fields, one for RGB444 and one for YUV444. Suggested-by: Ville Syrjälä Fixes: d0c94692e0a3 ("drm/edid: Parse and handle HDMI deep color modes.") Signed-off-by: Maxime Ripard Reviewed-by: Ville Syrjälä Link: https://patchwork.freedesktop.org/patch/msgid/20220120151625.594595-4-maxime@cerno.tech --- drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.c | 2 +- drivers/gpu/drm/drm_edid.c | 7 ++++--- drivers/gpu/drm/i915/display/intel_hdmi.c | 4 ++-- drivers/gpu/drm/radeon/radeon_connectors.c | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.c index 19c4d9225197..82d847e4e7d8 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.c @@ -175,7 +175,7 @@ int amdgpu_connector_get_monitor_bpc(struct drm_connector *connector) /* Check if bpc is within clock limit. Try to degrade gracefully otherwise */ if ((bpc == 12) && (mode_clock * 3/2 > max_tmds_clock)) { - if ((connector->display_info.edid_hdmi_dc_modes & DRM_EDID_HDMI_DC_30) && + if ((connector->display_info.edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_30) && (mode_clock * 5/4 <= max_tmds_clock)) bpc = 10; else diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index d00cac879bd5..7ce28f1df136 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -5086,21 +5086,21 @@ static void drm_parse_hdmi_deep_color_info(struct drm_connector *connector, if (hdmi[6] & DRM_EDID_HDMI_DC_30) { dc_bpc = 10; - info->edid_hdmi_dc_modes |= DRM_EDID_HDMI_DC_30; + info->edid_hdmi_rgb444_dc_modes |= DRM_EDID_HDMI_DC_30; DRM_DEBUG("%s: HDMI sink does deep color 30.\n", connector->name); } if (hdmi[6] & DRM_EDID_HDMI_DC_36) { dc_bpc = 12; - info->edid_hdmi_dc_modes |= DRM_EDID_HDMI_DC_36; + info->edid_hdmi_rgb444_dc_modes |= DRM_EDID_HDMI_DC_36; DRM_DEBUG("%s: HDMI sink does deep color 36.\n", connector->name); } if (hdmi[6] & DRM_EDID_HDMI_DC_48) { dc_bpc = 16; - info->edid_hdmi_dc_modes |= DRM_EDID_HDMI_DC_48; + info->edid_hdmi_rgb444_dc_modes |= DRM_EDID_HDMI_DC_48; DRM_DEBUG("%s: HDMI sink does deep color 48.\n", connector->name); } @@ -5117,6 +5117,7 @@ static void drm_parse_hdmi_deep_color_info(struct drm_connector *connector, /* YCRCB444 is optional according to spec. */ if (hdmi[6] & DRM_EDID_HDMI_DC_Y444) { + info->edid_hdmi_ycbcr444_dc_modes = info->edid_hdmi_rgb444_dc_modes; DRM_DEBUG("%s: HDMI sink does YCRCB444 in deep color.\n", connector->name); } diff --git a/drivers/gpu/drm/i915/display/intel_hdmi.c b/drivers/gpu/drm/i915/display/intel_hdmi.c index 96e508ddc4af..52f6dc248453 100644 --- a/drivers/gpu/drm/i915/display/intel_hdmi.c +++ b/drivers/gpu/drm/i915/display/intel_hdmi.c @@ -1912,7 +1912,7 @@ static bool intel_hdmi_sink_bpc_possible(struct drm_connector *connector, if (ycbcr420_output) return hdmi->y420_dc_modes & DRM_EDID_YCBCR420_DC_36; else - return info->edid_hdmi_dc_modes & DRM_EDID_HDMI_DC_36; + return info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_36; case 10: if (!has_hdmi_sink) return false; @@ -1920,7 +1920,7 @@ static bool intel_hdmi_sink_bpc_possible(struct drm_connector *connector, if (ycbcr420_output) return hdmi->y420_dc_modes & DRM_EDID_YCBCR420_DC_30; else - return info->edid_hdmi_dc_modes & DRM_EDID_HDMI_DC_30; + return info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_30; case 8: return true; default: diff --git a/drivers/gpu/drm/radeon/radeon_connectors.c b/drivers/gpu/drm/radeon/radeon_connectors.c index 82d37a85bfc8..a7925a8290b2 100644 --- a/drivers/gpu/drm/radeon/radeon_connectors.c +++ b/drivers/gpu/drm/radeon/radeon_connectors.c @@ -204,7 +204,7 @@ int radeon_get_monitor_bpc(struct drm_connector *connector) /* Check if bpc is within clock limit. Try to degrade gracefully otherwise */ if ((bpc == 12) && (mode_clock * 3/2 > max_tmds_clock)) { - if ((connector->display_info.edid_hdmi_dc_modes & DRM_EDID_HDMI_DC_30) && + if ((connector->display_info.edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_30) && (mode_clock * 5/4 <= max_tmds_clock)) bpc = 10; else -- cgit From c03d0b52ff71d580ee235463c7ca9eac31351dcd Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Thu, 20 Jan 2022 16:16:13 +0100 Subject: drm/connector: Fix typo in output format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HDMI specification mentions YCbCr everywhere, but our enums have YCrCb. Let's rename it to match. Signed-off-by: Maxime Ripard Reviewed-by: Ville Syrjälä Link: https://patchwork.freedesktop.org/patch/msgid/20220120151625.594595-5-maxime@cerno.tech --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 2 +- drivers/gpu/drm/arm/display/komeda/d71/d71_component.c | 12 ++++++------ drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 2 +- drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 4 ++-- drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c | 18 +++++++++--------- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 16 ++++++++-------- drivers/gpu/drm/drm_edid.c | 16 ++++++++-------- drivers/gpu/drm/rockchip/analogix_dp-rockchip.c | 2 +- 8 files changed, 36 insertions(+), 36 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 046506ff0532..39812daeb7c5 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -5816,7 +5816,7 @@ static void fill_stream_properties_from_drm_display_mode( else if (drm_mode_is_420_also(info, mode_in) && aconnector->force_yuv420_output) timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; - else if ((connector->display_info.color_formats & DRM_COLOR_FORMAT_YCRCB444) + else if ((connector->display_info.color_formats & DRM_COLOR_FORMAT_YCBCR444) && stream->signal == SIGNAL_TYPE_HDMI_TYPE_A) timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR444; else diff --git a/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c b/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c index 8a02ade369db..42510fdea27e 100644 --- a/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c +++ b/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c @@ -1078,11 +1078,11 @@ static void d71_improc_update(struct komeda_component *c, mask |= IPS_CTRL_YUV | IPS_CTRL_CHD422 | IPS_CTRL_CHD420; /* config color format */ - if (st->color_format == DRM_COLOR_FORMAT_YCRCB420) + if (st->color_format == DRM_COLOR_FORMAT_YCBCR420) ctrl |= IPS_CTRL_YUV | IPS_CTRL_CHD422 | IPS_CTRL_CHD420; - else if (st->color_format == DRM_COLOR_FORMAT_YCRCB422) + else if (st->color_format == DRM_COLOR_FORMAT_YCBCR422) ctrl |= IPS_CTRL_YUV | IPS_CTRL_CHD422; - else if (st->color_format == DRM_COLOR_FORMAT_YCRCB444) + else if (st->color_format == DRM_COLOR_FORMAT_YCBCR444) ctrl |= IPS_CTRL_YUV; malidp_write32_mask(reg, BLK_CONTROL, mask, ctrl); @@ -1144,11 +1144,11 @@ static int d71_improc_init(struct d71_dev *d71, improc = to_improc(c); improc->supported_color_depths = BIT(8) | BIT(10); improc->supported_color_formats = DRM_COLOR_FORMAT_RGB444 | - DRM_COLOR_FORMAT_YCRCB444 | - DRM_COLOR_FORMAT_YCRCB422; + DRM_COLOR_FORMAT_YCBCR444 | + DRM_COLOR_FORMAT_YCBCR422; value = malidp_read32(reg, BLK_INFO); if (value & IPS_INFO_CHD420) - improc->supported_color_formats |= DRM_COLOR_FORMAT_YCRCB420; + improc->supported_color_formats |= DRM_COLOR_FORMAT_YCBCR420; improc->supports_csc = true; improc->supports_gamma = true; diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c index 77118c3395bf..005bf18682ff 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -223,7 +223,7 @@ static void adv7511_set_config_csc(struct adv7511 *adv7511, config.csc_coefficents = adv7511_csc_ycbcr_to_rgb; if ((connector->display_info.color_formats & - DRM_COLOR_FORMAT_YCRCB422) && + DRM_COLOR_FORMAT_YCBCR422) && config.hdmi_mode) { config.csc_enable = false; config.avi_infoframe.colorspace = diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c index b7d2e4449cfa..eb590fb8e8d0 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c @@ -1537,9 +1537,9 @@ static void analogix_dp_bridge_mode_set(struct drm_bridge *bridge, video->color_depth = COLOR_8; break; } - if (display_info->color_formats & DRM_COLOR_FORMAT_YCRCB444) + if (display_info->color_formats & DRM_COLOR_FORMAT_YCBCR444) video->color_space = COLOR_YCBCR444; - else if (display_info->color_formats & DRM_COLOR_FORMAT_YCRCB422) + else if (display_info->color_formats & DRM_COLOR_FORMAT_YCBCR422) video->color_space = COLOR_YCBCR422; else video->color_space = COLOR_RGB; diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c index ac9e64169c6f..ac18e15aa167 100644 --- a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c +++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c @@ -1553,13 +1553,13 @@ static u32 cdns_mhdp_get_bpp(struct cdns_mhdp_display_fmt *fmt) switch (fmt->color_format) { case DRM_COLOR_FORMAT_RGB444: - case DRM_COLOR_FORMAT_YCRCB444: + case DRM_COLOR_FORMAT_YCBCR444: bpp = fmt->bpc * 3; break; - case DRM_COLOR_FORMAT_YCRCB422: + case DRM_COLOR_FORMAT_YCBCR422: bpp = fmt->bpc * 2; break; - case DRM_COLOR_FORMAT_YCRCB420: + case DRM_COLOR_FORMAT_YCBCR420: bpp = fmt->bpc * 3 / 2; break; default: @@ -1767,8 +1767,8 @@ static void cdns_mhdp_configure_video(struct cdns_mhdp_device *mhdp, * If YCBCR supported and stream not SD, use ITU709 * Need to handle ITU version with YCBCR420 when supported */ - if ((pxlfmt == DRM_COLOR_FORMAT_YCRCB444 || - pxlfmt == DRM_COLOR_FORMAT_YCRCB422) && mode->crtc_vdisplay >= 720) + if ((pxlfmt == DRM_COLOR_FORMAT_YCBCR444 || + pxlfmt == DRM_COLOR_FORMAT_YCBCR422) && mode->crtc_vdisplay >= 720) misc0 = DP_YCBCR_COEFFICIENTS_ITU709; bpp = cdns_mhdp_get_bpp(&mhdp->display_fmt); @@ -1778,15 +1778,15 @@ static void cdns_mhdp_configure_video(struct cdns_mhdp_device *mhdp, pxl_repr = CDNS_DP_FRAMER_RGB << CDNS_DP_FRAMER_PXL_FORMAT; misc0 |= DP_COLOR_FORMAT_RGB; break; - case DRM_COLOR_FORMAT_YCRCB444: + case DRM_COLOR_FORMAT_YCBCR444: pxl_repr = CDNS_DP_FRAMER_YCBCR444 << CDNS_DP_FRAMER_PXL_FORMAT; misc0 |= DP_COLOR_FORMAT_YCbCr444 | DP_TEST_DYNAMIC_RANGE_CEA; break; - case DRM_COLOR_FORMAT_YCRCB422: + case DRM_COLOR_FORMAT_YCBCR422: pxl_repr = CDNS_DP_FRAMER_YCBCR422 << CDNS_DP_FRAMER_PXL_FORMAT; misc0 |= DP_COLOR_FORMAT_YCbCr422 | DP_TEST_DYNAMIC_RANGE_CEA; break; - case DRM_COLOR_FORMAT_YCRCB420: + case DRM_COLOR_FORMAT_YCBCR420: pxl_repr = CDNS_DP_FRAMER_YCBCR420 << CDNS_DP_FRAMER_PXL_FORMAT; break; default: @@ -1882,7 +1882,7 @@ static void cdns_mhdp_configure_video(struct cdns_mhdp_device *mhdp, if (mhdp->display_fmt.y_only) misc1 |= CDNS_DP_TEST_COLOR_FORMAT_RAW_Y_ONLY; /* Use VSC SDP for Y420 */ - if (pxlfmt == DRM_COLOR_FORMAT_YCRCB420) + if (pxlfmt == DRM_COLOR_FORMAT_YCBCR420) misc1 = CDNS_DP_TEST_VSC_SDP; cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_MISC(stream_id), diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 54d8fdad395f..b0d8110dd412 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -2540,7 +2540,7 @@ static u32 *dw_hdmi_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge, struct drm_display_mode *mode = &crtc_state->mode; u8 max_bpc = conn_state->max_requested_bpc; bool is_hdmi2_sink = info->hdmi.scdc.supported || - (info->color_formats & DRM_COLOR_FORMAT_YCRCB420); + (info->color_formats & DRM_COLOR_FORMAT_YCBCR420); u32 *output_fmts; unsigned int i = 0; @@ -2594,36 +2594,36 @@ static u32 *dw_hdmi_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge, */ if (max_bpc >= 16 && info->bpc == 16) { - if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444) output_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48; output_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48; } if (max_bpc >= 12 && info->bpc >= 12) { - if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422) output_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24; - if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444) output_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36; output_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36; } if (max_bpc >= 10 && info->bpc >= 10) { - if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422) output_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20; - if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444) output_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30; output_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30; } - if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422) output_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16; - if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444) output_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24; /* Default 8bit RGB fallback */ diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 7ce28f1df136..5251925e3b92 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -3776,7 +3776,7 @@ static int do_y420vdb_modes(struct drm_connector *connector, } if (modes > 0) - info->color_formats |= DRM_COLOR_FORMAT_YCRCB420; + info->color_formats |= DRM_COLOR_FORMAT_YCBCR420; return modes; } @@ -4290,7 +4290,7 @@ static void drm_parse_y420cmdb_bitmap(struct drm_connector *connector, if (map_len == 0) { /* All CEA modes support ycbcr420 sampling also.*/ hdmi->y420_cmdb_map = U64_MAX; - info->color_formats |= DRM_COLOR_FORMAT_YCRCB420; + info->color_formats |= DRM_COLOR_FORMAT_YCBCR420; return; } @@ -4313,7 +4313,7 @@ static void drm_parse_y420cmdb_bitmap(struct drm_connector *connector, map |= (u64)db[2 + count] << (8 * count); if (map) - info->color_formats |= DRM_COLOR_FORMAT_YCRCB420; + info->color_formats |= DRM_COLOR_FORMAT_YCBCR420; hdmi->y420_cmdb_map = map; } @@ -5188,9 +5188,9 @@ static void drm_parse_cea_ext(struct drm_connector *connector, /* The existence of a CEA block should imply RGB support */ info->color_formats = DRM_COLOR_FORMAT_RGB444; if (edid_ext[3] & EDID_CEA_YCRCB444) - info->color_formats |= DRM_COLOR_FORMAT_YCRCB444; + info->color_formats |= DRM_COLOR_FORMAT_YCBCR444; if (edid_ext[3] & EDID_CEA_YCRCB422) - info->color_formats |= DRM_COLOR_FORMAT_YCRCB422; + info->color_formats |= DRM_COLOR_FORMAT_YCBCR422; if (cea_db_offsets(edid_ext, &start, &end)) return; @@ -5416,9 +5416,9 @@ u32 drm_add_display_info(struct drm_connector *connector, const struct edid *edi info->color_formats |= DRM_COLOR_FORMAT_RGB444; if (edid->features & DRM_EDID_FEATURE_RGB_YCRCB444) - info->color_formats |= DRM_COLOR_FORMAT_YCRCB444; + info->color_formats |= DRM_COLOR_FORMAT_YCBCR444; if (edid->features & DRM_EDID_FEATURE_RGB_YCRCB422) - info->color_formats |= DRM_COLOR_FORMAT_YCRCB422; + info->color_formats |= DRM_COLOR_FORMAT_YCBCR422; drm_update_mso(connector, edid); @@ -5680,7 +5680,7 @@ static bool is_hdmi2_sink(const struct drm_connector *connector) return true; return connector->display_info.hdmi.scdc.supported || - connector->display_info.color_formats & DRM_COLOR_FORMAT_YCRCB420; + connector->display_info.color_formats & DRM_COLOR_FORMAT_YCBCR420; } static inline bool is_eotf_supported(u8 output_eotf, u8 sink_eotf) diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c index c61d37f02af7..c82901d9a9cc 100644 --- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c @@ -117,7 +117,7 @@ static int rockchip_dp_get_modes(struct analogix_dp_plat_data *plat_data, { struct drm_display_info *di = &connector->display_info; /* VOP couldn't output YUV video format for eDP rightly */ - u32 mask = DRM_COLOR_FORMAT_YCRCB444 | DRM_COLOR_FORMAT_YCRCB422; + u32 mask = DRM_COLOR_FORMAT_YCBCR444 | DRM_COLOR_FORMAT_YCBCR422; if ((di->color_formats & mask)) { DRM_DEBUG_KMS("Swapping display color format from YUV to RGB\n"); -- cgit From a649cc821a2a2ee2e38d93426e92d6e88f7e4cbe Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Thu, 20 Jan 2022 16:16:14 +0100 Subject: drm/vc4: hdmi: Add full range RGB helper We're going to need to tell whether we want to run with a full or limited range RGB output in multiple places in the code, so let's create a helper that will return whether we need with full range or not. Acked-by: Thomas Zimmermann Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20220120151625.594595-6-maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index be39e55ae113..7966e3b00332 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -104,6 +104,15 @@ static bool vc4_hdmi_mode_needs_scrambling(const struct drm_display_mode *mode) return (mode->clock * 1000) > HDMI_14_MAX_TMDS_CLK; } +static bool vc4_hdmi_is_full_range_rgb(struct vc4_hdmi *vc4_hdmi, + const struct drm_display_mode *mode) +{ + struct vc4_hdmi_encoder *vc4_encoder = &vc4_hdmi->encoder; + + return !vc4_encoder->hdmi_monitor || + drm_default_rgb_quant_range(mode) == HDMI_QUANTIZATION_RANGE_FULL; +} + static int vc4_hdmi_debugfs_regs(struct seq_file *m, void *unused) { struct drm_info_node *node = (struct drm_info_node *)m->private; @@ -1119,8 +1128,7 @@ static void vc4_hdmi_encoder_pre_crtc_enable(struct drm_encoder *encoder, mutex_lock(&vc4_hdmi->mutex); - if (vc4_encoder->hdmi_monitor && - drm_default_rgb_quant_range(mode) == HDMI_QUANTIZATION_RANGE_LIMITED) { + if (!vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode)) { if (vc4_hdmi->variant->csc_setup) vc4_hdmi->variant->csc_setup(vc4_hdmi, true); -- cgit From dccb4d74e06dd7ef758e8dd69a5420d0f3a9fb9f Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Thu, 20 Jan 2022 16:16:15 +0100 Subject: drm/vc4: hdmi: Use full range helper in csc functions The CSC callbacks takes a boolean as an argument to tell whether we're using the full range or limited range RGB. However, with the upcoming YUV support, the logic will be a bit more complex. In order to address this, let's make the callbacks take the entire mode, and call our new helper to tell whether the full or limited range RGB should be used. Acked-by: Thomas Zimmermann Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20220120151625.594595-7-maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 31 +++++++++++-------------------- drivers/gpu/drm/vc4/vc4_hdmi.h | 4 ++-- 2 files changed, 13 insertions(+), 22 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 7966e3b00332..47ff4507f017 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -490,7 +490,6 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder, static void vc4_hdmi_set_avi_infoframe(struct drm_encoder *encoder) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); - struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder); struct drm_connector *connector = &vc4_hdmi->connector; struct drm_connector_state *cstate = connector->state; const struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode; @@ -508,9 +507,9 @@ static void vc4_hdmi_set_avi_infoframe(struct drm_encoder *encoder) drm_hdmi_avi_infoframe_quant_range(&frame.avi, connector, mode, - vc4_encoder->limited_rgb_range ? - HDMI_QUANTIZATION_RANGE_LIMITED : - HDMI_QUANTIZATION_RANGE_FULL); + vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode) ? + HDMI_QUANTIZATION_RANGE_FULL : + HDMI_QUANTIZATION_RANGE_LIMITED); drm_hdmi_avi_infoframe_colorimetry(&frame.avi, cstate); drm_hdmi_avi_infoframe_bars(&frame.avi, cstate); @@ -735,7 +734,8 @@ static void vc4_hdmi_encoder_disable(struct drm_encoder *encoder) mutex_unlock(&vc4_hdmi->mutex); } -static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, bool enable) +static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, + const struct drm_display_mode *mode) { unsigned long flags; u32 csc_ctl; @@ -745,7 +745,7 @@ static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, bool enable) csc_ctl = VC4_SET_FIELD(VC4_HD_CSC_CTL_ORDER_BGR, VC4_HD_CSC_CTL_ORDER); - if (enable) { + if (!vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode)) { /* CEA VICs other than #1 requre limited range RGB * output unless overridden by an AVI infoframe. * Apply a colorspace conversion to squash 0-255 down @@ -775,7 +775,8 @@ static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, bool enable) spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); } -static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, bool enable) +static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, + const struct drm_display_mode *mode) { unsigned long flags; u32 csc_ctl; @@ -784,7 +785,7 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, bool enable) spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); - if (enable) { + if (!vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode)) { /* CEA VICs other than #1 requre limited range RGB * output unless overridden by an AVI infoframe. * Apply a colorspace conversion to squash 0-255 down @@ -1123,22 +1124,12 @@ static void vc4_hdmi_encoder_pre_crtc_enable(struct drm_encoder *encoder, { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode; - struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder); unsigned long flags; mutex_lock(&vc4_hdmi->mutex); - if (!vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode)) { - if (vc4_hdmi->variant->csc_setup) - vc4_hdmi->variant->csc_setup(vc4_hdmi, true); - - vc4_encoder->limited_rgb_range = true; - } else { - if (vc4_hdmi->variant->csc_setup) - vc4_hdmi->variant->csc_setup(vc4_hdmi, false); - - vc4_encoder->limited_rgb_range = false; - } + if (vc4_hdmi->variant->csc_setup) + vc4_hdmi->variant->csc_setup(vc4_hdmi, mode); spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_FIFO_CTL, VC4_HDMI_FIFO_CTL_MASTER_SLAVE_N); diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.h b/drivers/gpu/drm/vc4/vc4_hdmi.h index 36c0b082a43b..4a5536975bf6 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.h +++ b/drivers/gpu/drm/vc4/vc4_hdmi.h @@ -12,7 +12,6 @@ struct vc4_hdmi_encoder { struct vc4_encoder base; bool hdmi_monitor; - bool limited_rgb_range; }; static inline struct vc4_hdmi_encoder * @@ -77,7 +76,8 @@ struct vc4_hdmi_variant { void (*reset)(struct vc4_hdmi *vc4_hdmi); /* Callback to enable / disable the CSC */ - void (*csc_setup)(struct vc4_hdmi *vc4_hdmi, bool enable); + void (*csc_setup)(struct vc4_hdmi *vc4_hdmi, + const struct drm_display_mode *mode); /* Callback to configure the video timings in the HDMI block */ void (*set_timings)(struct vc4_hdmi *vc4_hdmi, -- cgit From 0cbb53b283d325fb90c3ab39c300c085bb7ac2f8 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Thu, 20 Jan 2022 16:16:16 +0100 Subject: drm/vc4: hdmi: Move XBAR setup to csc_setup On the BCM2711, the HDMI_VEC_INTERFACE_XBAR register configuration depends on whether we're using an RGB or YUV output. Let's move that configuration to the CSC setup. Acked-by: Thomas Zimmermann Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20220120151625.594595-8-maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 47ff4507f017..0f8b1e907fae 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -785,6 +785,8 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); + HDMI_WRITE(HDMI_VEC_INTERFACE_XBAR, 0x354021); + if (!vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode)) { /* CEA VICs other than #1 requre limited range RGB * output unless overridden by an AVI infoframe. @@ -899,7 +901,6 @@ static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); - HDMI_WRITE(HDMI_VEC_INTERFACE_XBAR, 0x354021); HDMI_WRITE(HDMI_HORZA, (vsync_pos ? VC5_HDMI_HORZA_VPOS : 0) | (hsync_pos ? VC5_HDMI_HORZA_HPOS : 0) | -- cgit From b718d8478e1c0bd9c2deb1579eb2953caeea5e8c Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Thu, 20 Jan 2022 16:16:17 +0100 Subject: drm/vc4: hdmi: Replace CSC_CTL hardcoded value by defines On BCM2711, the HDMI_CSC_CTL register value has been hardcoded to an opaque value. Let's replace it with properly defined values. Acked-by: Thomas Zimmermann Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20220120151625.594595-9-maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 5 ++--- drivers/gpu/drm/vc4/vc4_regs.h | 3 +++ 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 0f8b1e907fae..682c3c907cbe 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -779,9 +779,8 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, const struct drm_display_mode *mode) { unsigned long flags; - u32 csc_ctl; - - csc_ctl = 0x07; /* RGB_CONVERT_MODE = custom matrix, || USE_RGB_TO_YCBCR */ + u32 csc_ctl = VC5_MT_CP_CSC_CTL_ENABLE | VC4_SET_FIELD(VC4_HD_CSC_CTL_MODE_CUSTOM, + VC5_MT_CP_CSC_CTL_MODE); spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); diff --git a/drivers/gpu/drm/vc4/vc4_regs.h b/drivers/gpu/drm/vc4/vc4_regs.h index 7538b84a6dca..33410718089e 100644 --- a/drivers/gpu/drm/vc4/vc4_regs.h +++ b/drivers/gpu/drm/vc4/vc4_regs.h @@ -774,6 +774,9 @@ enum { # define VC4_HD_CSC_CTL_RGB2YCC BIT(1) # define VC4_HD_CSC_CTL_ENABLE BIT(0) +# define VC5_MT_CP_CSC_CTL_ENABLE BIT(2) +# define VC5_MT_CP_CSC_CTL_MODE_MASK VC4_MASK(1, 0) + # define VC4_DVP_HT_CLOCK_STOP_PIXEL BIT(1) /* HVS display list information. */ -- cgit From 2034fc12dd258931f06f5e320d87ea8776e01bc6 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Thu, 20 Jan 2022 16:16:18 +0100 Subject: drm/vc4: hdmi: Define colorspace matrices The current CSC setup code for the BCM2711 uses a sequence of register writes to configure the CSC depending on whether we output using a full or limited range. However, with the upcoming introduction of the YUV output, we're going to add new matrices to perform the conversions, so we should switch to something a bit more flexible that takes the matrix as an argument and programs the CSC accordingly. Acked-by: Thomas Zimmermann Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20220120151625.594595-10-maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 78 ++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 29 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 682c3c907cbe..3a3c8201c660 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -775,6 +775,51 @@ static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); } +/* + * If we need to output Full Range RGB, then use the unity matrix + * + * [ 1 0 0 0] + * [ 0 1 0 0] + * [ 0 0 1 0] + * + * Matrix is signed 2p13 fixed point, with signed 9p6 offsets + */ +static const u16 vc5_hdmi_csc_full_rgb_unity[3][4] = { + { 0x2000, 0x0000, 0x0000, 0x0000 }, + { 0x0000, 0x2000, 0x0000, 0x0000 }, + { 0x0000, 0x0000, 0x2000, 0x0000 }, +}; + +/* + * CEA VICs other than #1 require limited range RGB output unless + * overridden by an AVI infoframe. Apply a colorspace conversion to + * squash 0-255 down to 16-235. The matrix here is: + * + * [ 0.8594 0 0 16] + * [ 0 0.8594 0 16] + * [ 0 0 0.8594 16] + * + * Matrix is signed 2p13 fixed point, with signed 9p6 offsets + */ +static const u16 vc5_hdmi_csc_full_rgb_to_limited_rgb[3][4] = { + { 0x1b80, 0x0000, 0x0000, 0x0400 }, + { 0x0000, 0x1b80, 0x0000, 0x0400 }, + { 0x0000, 0x0000, 0x1b80, 0x0400 }, +}; + +static void vc5_hdmi_set_csc_coeffs(struct vc4_hdmi *vc4_hdmi, + const u16 coeffs[3][4]) +{ + lockdep_assert_held(&vc4_hdmi->hw_lock); + + HDMI_WRITE(HDMI_CSC_12_11, (coeffs[0][1] << 16) | coeffs[0][0]); + HDMI_WRITE(HDMI_CSC_14_13, (coeffs[0][3] << 16) | coeffs[0][2]); + HDMI_WRITE(HDMI_CSC_22_21, (coeffs[1][1] << 16) | coeffs[1][0]); + HDMI_WRITE(HDMI_CSC_24_23, (coeffs[1][3] << 16) | coeffs[1][2]); + HDMI_WRITE(HDMI_CSC_32_31, (coeffs[2][1] << 16) | coeffs[2][0]); + HDMI_WRITE(HDMI_CSC_34_33, (coeffs[2][3] << 16) | coeffs[2][2]); +} + static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, const struct drm_display_mode *mode) { @@ -786,35 +831,10 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, HDMI_WRITE(HDMI_VEC_INTERFACE_XBAR, 0x354021); - if (!vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode)) { - /* CEA VICs other than #1 requre limited range RGB - * output unless overridden by an AVI infoframe. - * Apply a colorspace conversion to squash 0-255 down - * to 16-235. The matrix here is: - * - * [ 0.8594 0 0 16] - * [ 0 0.8594 0 16] - * [ 0 0 0.8594 16] - * [ 0 0 0 1] - * Matrix is signed 2p13 fixed point, with signed 9p6 offsets - */ - HDMI_WRITE(HDMI_CSC_12_11, (0x0000 << 16) | 0x1b80); - HDMI_WRITE(HDMI_CSC_14_13, (0x0400 << 16) | 0x0000); - HDMI_WRITE(HDMI_CSC_22_21, (0x1b80 << 16) | 0x0000); - HDMI_WRITE(HDMI_CSC_24_23, (0x0400 << 16) | 0x0000); - HDMI_WRITE(HDMI_CSC_32_31, (0x0000 << 16) | 0x0000); - HDMI_WRITE(HDMI_CSC_34_33, (0x0400 << 16) | 0x1b80); - } else { - /* Still use the matrix for full range, but make it unity. - * Matrix is signed 2p13 fixed point, with signed 9p6 offsets - */ - HDMI_WRITE(HDMI_CSC_12_11, (0x0000 << 16) | 0x2000); - HDMI_WRITE(HDMI_CSC_14_13, (0x0000 << 16) | 0x0000); - HDMI_WRITE(HDMI_CSC_22_21, (0x2000 << 16) | 0x0000); - HDMI_WRITE(HDMI_CSC_24_23, (0x0000 << 16) | 0x0000); - HDMI_WRITE(HDMI_CSC_32_31, (0x0000 << 16) | 0x0000); - HDMI_WRITE(HDMI_CSC_34_33, (0x0000 << 16) | 0x2000); - } + if (!vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode)) + vc5_hdmi_set_csc_coeffs(vc4_hdmi, vc5_hdmi_csc_full_rgb_to_limited_rgb); + else + vc5_hdmi_set_csc_coeffs(vc4_hdmi, vc5_hdmi_csc_full_rgb_unity); HDMI_WRITE(HDMI_CSC_CTL, csc_ctl); -- cgit From a34b14a29d96db74876d2cbb466bbc2b73002f3d Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Thu, 20 Jan 2022 16:16:19 +0100 Subject: drm/vc4: hdmi: Change CSC callback prototype In order to support the YUV output, we'll need the atomic state to know what is the state of the associated property in the CSC setup callback. Let's change the prototype of that callback to allow us to access it. Acked-by: Thomas Zimmermann Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20220120151625.594595-11-maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 7 ++++++- drivers/gpu/drm/vc4/vc4_hdmi.h | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 3a3c8201c660..e3121eb5f605 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -735,6 +735,7 @@ static void vc4_hdmi_encoder_disable(struct drm_encoder *encoder) } static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, + struct drm_connector_state *state, const struct drm_display_mode *mode) { unsigned long flags; @@ -821,6 +822,7 @@ static void vc5_hdmi_set_csc_coeffs(struct vc4_hdmi *vc4_hdmi, } static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, + struct drm_connector_state *state, const struct drm_display_mode *mode) { unsigned long flags; @@ -1143,13 +1145,16 @@ static void vc4_hdmi_encoder_pre_crtc_enable(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + struct drm_connector *connector = &vc4_hdmi->connector; struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode; + struct drm_connector_state *conn_state = + drm_atomic_get_new_connector_state(state, connector); unsigned long flags; mutex_lock(&vc4_hdmi->mutex); if (vc4_hdmi->variant->csc_setup) - vc4_hdmi->variant->csc_setup(vc4_hdmi, mode); + vc4_hdmi->variant->csc_setup(vc4_hdmi, conn_state, mode); spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_FIFO_CTL, VC4_HDMI_FIFO_CTL_MASTER_SLAVE_N); diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.h b/drivers/gpu/drm/vc4/vc4_hdmi.h index 4a5536975bf6..2b6aaafc020a 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.h +++ b/drivers/gpu/drm/vc4/vc4_hdmi.h @@ -77,6 +77,7 @@ struct vc4_hdmi_variant { /* Callback to enable / disable the CSC */ void (*csc_setup)(struct vc4_hdmi *vc4_hdmi, + struct drm_connector_state *state, const struct drm_display_mode *mode); /* Callback to configure the video timings in the HDMI block */ -- cgit From 80ecb5d7c0f224218fdf956faec0ebe73d79f53d Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Sun, 23 Jan 2022 22:19:55 +0300 Subject: drm/edid: Support type 7 timings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per VESA DisplayID Standard v2.0: Type VII Timing – Detailed Timing Data Definitions were already provided as type I, but not used Signed-off-by: Yaroslav Bolyukin Reviewed-by: Jani Nikula Signed-off-by: Jani Nikula Link: https://patchwork.freedesktop.org/patch/msgid/20220123191955.57994-1-iam@lach.pw --- drivers/gpu/drm/drm_edid.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 5251925e3b92..a504542238ed 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -5433,7 +5433,8 @@ out: } static struct drm_display_mode *drm_mode_displayid_detailed(struct drm_device *dev, - struct displayid_detailed_timings_1 *timings) + struct displayid_detailed_timings_1 *timings, + bool type_7) { struct drm_display_mode *mode; unsigned pixel_clock = (timings->pixel_clock[0] | @@ -5454,7 +5455,8 @@ static struct drm_display_mode *drm_mode_displayid_detailed(struct drm_device *d if (!mode) return NULL; - mode->clock = pixel_clock * 10; + /* resolution is kHz for type VII, and 10 kHz for type I */ + mode->clock = type_7 ? pixel_clock : pixel_clock * 10; mode->hdisplay = hactive; mode->hsync_start = mode->hdisplay + hsync; mode->hsync_end = mode->hsync_start + hsync_width; @@ -5485,6 +5487,7 @@ static int add_displayid_detailed_1_modes(struct drm_connector *connector, int num_timings; struct drm_display_mode *newmode; int num_modes = 0; + bool type_7 = block->tag == DATA_BLOCK_2_TYPE_7_DETAILED_TIMING; /* blocks must be multiple of 20 bytes length */ if (block->num_bytes % 20) return 0; @@ -5493,7 +5496,7 @@ static int add_displayid_detailed_1_modes(struct drm_connector *connector, for (i = 0; i < num_timings; i++) { struct displayid_detailed_timings_1 *timings = &det->timings[i]; - newmode = drm_mode_displayid_detailed(connector->dev, timings); + newmode = drm_mode_displayid_detailed(connector->dev, timings, type_7); if (!newmode) continue; @@ -5512,7 +5515,8 @@ static int add_displayid_detailed_modes(struct drm_connector *connector, displayid_iter_edid_begin(edid, &iter); displayid_iter_for_each(block, &iter) { - if (block->tag == DATA_BLOCK_TYPE_1_DETAILED_TIMING) + if (block->tag == DATA_BLOCK_TYPE_1_DETAILED_TIMING || + block->tag == DATA_BLOCK_2_TYPE_7_DETAILED_TIMING) num_modes += add_displayid_detailed_1_modes(connector, block); } displayid_iter_end(&iter); -- cgit From 85e97b1dd5d29b66a249406624734843b71c0a0c Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Wed, 19 Jan 2022 13:06:39 -0800 Subject: drm/i915/guc: Ensure multi-lrc fini breadcrumb math is correct Realized that the GuC multi-lrc fini breadcrumb emit code is very delicate as the math this code does relies on functions it calls to emit a certain number of DWs. Add a few GEM_BUG_ONs to assert the math is correct. v2: - Rebase + resend for CI (Checkpatch) - Fix blank line warning Signed-off-by: Matthew Brost Reviewed-by: John Harrison Signed-off-by: John Harrison Link: https://patchwork.freedesktop.org/patch/msgid/20220119210639.33053-1-matthew.brost@intel.com --- drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c | 33 ++++++++++++++++++----- 1 file changed, 27 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c index 343bd4804e73..422353a70d5d 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c @@ -4441,27 +4441,31 @@ static inline bool skip_handshake(struct i915_request *rq) return test_bit(I915_FENCE_FLAG_SKIP_PARALLEL, &rq->fence.flags); } +#define NON_SKIP_LEN 6 static u32 * emit_fini_breadcrumb_parent_no_preempt_mid_batch(struct i915_request *rq, u32 *cs) { struct intel_context *ce = rq->context; + __maybe_unused u32 *before_fini_breadcrumb_user_interrupt_cs; + __maybe_unused u32 *start_fini_breadcrumb_cs = cs; GEM_BUG_ON(!intel_context_is_parent(ce)); if (unlikely(skip_handshake(rq))) { /* * NOP everything in __emit_fini_breadcrumb_parent_no_preempt_mid_batch, - * the -6 comes from the length of the emits below. + * the NON_SKIP_LEN comes from the length of the emits below. */ memset(cs, 0, sizeof(u32) * - (ce->engine->emit_fini_breadcrumb_dw - 6)); - cs += ce->engine->emit_fini_breadcrumb_dw - 6; + (ce->engine->emit_fini_breadcrumb_dw - NON_SKIP_LEN)); + cs += ce->engine->emit_fini_breadcrumb_dw - NON_SKIP_LEN; } else { cs = __emit_fini_breadcrumb_parent_no_preempt_mid_batch(rq, cs); } /* Emit fini breadcrumb */ + before_fini_breadcrumb_user_interrupt_cs = cs; cs = gen8_emit_ggtt_write(cs, rq->fence.seqno, i915_request_active_timeline(rq)->hwsp_offset, @@ -4471,6 +4475,12 @@ emit_fini_breadcrumb_parent_no_preempt_mid_batch(struct i915_request *rq, *cs++ = MI_USER_INTERRUPT; *cs++ = MI_NOOP; + /* Ensure our math for skip + emit is correct */ + GEM_BUG_ON(before_fini_breadcrumb_user_interrupt_cs + NON_SKIP_LEN != + cs); + GEM_BUG_ON(start_fini_breadcrumb_cs + + ce->engine->emit_fini_breadcrumb_dw != cs); + rq->tail = intel_ring_offset(rq, cs); return cs; @@ -4513,22 +4523,25 @@ emit_fini_breadcrumb_child_no_preempt_mid_batch(struct i915_request *rq, u32 *cs) { struct intel_context *ce = rq->context; + __maybe_unused u32 *before_fini_breadcrumb_user_interrupt_cs; + __maybe_unused u32 *start_fini_breadcrumb_cs = cs; GEM_BUG_ON(!intel_context_is_child(ce)); if (unlikely(skip_handshake(rq))) { /* * NOP everything in __emit_fini_breadcrumb_child_no_preempt_mid_batch, - * the -6 comes from the length of the emits below. + * the NON_SKIP_LEN comes from the length of the emits below. */ memset(cs, 0, sizeof(u32) * - (ce->engine->emit_fini_breadcrumb_dw - 6)); - cs += ce->engine->emit_fini_breadcrumb_dw - 6; + (ce->engine->emit_fini_breadcrumb_dw - NON_SKIP_LEN)); + cs += ce->engine->emit_fini_breadcrumb_dw - NON_SKIP_LEN; } else { cs = __emit_fini_breadcrumb_child_no_preempt_mid_batch(rq, cs); } /* Emit fini breadcrumb */ + before_fini_breadcrumb_user_interrupt_cs = cs; cs = gen8_emit_ggtt_write(cs, rq->fence.seqno, i915_request_active_timeline(rq)->hwsp_offset, @@ -4538,11 +4551,19 @@ emit_fini_breadcrumb_child_no_preempt_mid_batch(struct i915_request *rq, *cs++ = MI_USER_INTERRUPT; *cs++ = MI_NOOP; + /* Ensure our math for skip + emit is correct */ + GEM_BUG_ON(before_fini_breadcrumb_user_interrupt_cs + NON_SKIP_LEN != + cs); + GEM_BUG_ON(start_fini_breadcrumb_cs + + ce->engine->emit_fini_breadcrumb_dw != cs); + rq->tail = intel_ring_offset(rq, cs); return cs; } +#undef NON_SKIP_LEN + static struct intel_context * guc_create_virtual(struct intel_engine_cs **siblings, unsigned int count, unsigned long flags) -- cgit From 2343bcdb4747d4f418a4daf2e898b94f86c24a59 Mon Sep 17 00:00:00 2001 From: Zhou Qingyang Date: Tue, 25 Jan 2022 00:58:55 +0800 Subject: drm/nouveau/acr: Fix undefined behavior in nvkm_acr_hsfw_load_bl() In nvkm_acr_hsfw_load_bl(), the return value of kmalloc() is directly passed to memcpy(), which could lead to undefined behavior on failure of kmalloc(). Fix this bug by using kmemdup() instead of kmalloc()+memcpy(). This bug was found by a static analyzer. Builds with 'make allyesconfig' show no new warnings, and our static analyzer no longer warns about this code. Fixes: 22dcda45a3d1 ("drm/nouveau/acr: implement new subdev to replace "secure boot"") Signed-off-by: Zhou Qingyang Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patchwork.freedesktop.org/patch/msgid/20220124165856.57022-1-zhou1615@umn.edu --- drivers/gpu/drm/nouveau/nvkm/subdev/acr/hsfw.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/acr/hsfw.c b/drivers/gpu/drm/nouveau/nvkm/subdev/acr/hsfw.c index 667fa016496e..a6ea89a5d51a 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/acr/hsfw.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/acr/hsfw.c @@ -142,11 +142,12 @@ nvkm_acr_hsfw_load_bl(struct nvkm_acr *acr, const char *name, int ver, hsfw->imem_size = desc->code_size; hsfw->imem_tag = desc->start_tag; - hsfw->imem = kmalloc(desc->code_size, GFP_KERNEL); - memcpy(hsfw->imem, data + desc->code_off, desc->code_size); - + hsfw->imem = kmemdup(data + desc->code_off, desc->code_size, GFP_KERNEL); nvkm_firmware_put(fw); - return 0; + if (!hsfw->imem) + return -ENOMEM; + else + return 0; } int -- cgit From 29c52ab2711f0e8ce506c120fbecb30e83f83339 Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Fri, 21 Jan 2022 16:08:22 -0800 Subject: drm/i915/selftests: Use less in contexts steal guc id test Using more guc_ids in the stealing guc id test has no real benefit. Tearing down lots of contexts all at the same time takes a bit of time due to the H2G / G2H ping-pong with the GuC. On some slower platforms this can cause timeous when flushing the test as the GT isn't idle when this ping-pong is happening. Reduce the number of guc ids to speed up the flushing of the test. Link: https://gitlab.freedesktop.org/drm/intel/-/issues/4821 Signed-off-by: Matthew Brost Reviewed-by: Matthew Auld Signed-off-by: John Harrison Link: https://patchwork.freedesktop.org/patch/msgid/20220122000822.25616-1-matthew.brost@intel.com --- drivers/gpu/drm/i915/gt/uc/selftest_guc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/uc/selftest_guc.c b/drivers/gpu/drm/i915/gt/uc/selftest_guc.c index d3327b802b76..a115894d5896 100644 --- a/drivers/gpu/drm/i915/gt/uc/selftest_guc.c +++ b/drivers/gpu/drm/i915/gt/uc/selftest_guc.c @@ -157,7 +157,7 @@ static int intel_guc_steal_guc_ids(void *arg) wakeref = intel_runtime_pm_get(gt->uncore->rpm); engine = intel_selftest_find_any_engine(gt); sv = guc->submission_state.num_guc_ids; - guc->submission_state.num_guc_ids = 4096; + guc->submission_state.num_guc_ids = 512; /* Create spinner to block requests in below loop */ ce[context_index] = intel_context_create(engine); -- cgit From 721fd84ea1fe957453587efad5fdc44dfba58e04 Mon Sep 17 00:00:00 2001 From: Umesh Nerlige Ramappa Date: Mon, 10 Jan 2022 17:55:23 -0800 Subject: drm/i915/pmu: Use PM timestamp instead of RING TIMESTAMP for reference All timestamps returned by GuC for GuC PMU busyness are captured from GUC PM TIMESTAMP. Since this timestamp does not tick when GuC goes idle, kmd uses RING_TIMESTAMP to measure busyness of an engine with an active context. In further stress testing, the MMIO read of the RING_TIMESTAMP is seen to cause a rare hang. Resolve the issue by using gt specific timestamp from PM which is in sync with the GuC PM timestamp. Fixes: 77cdd054dd2c ("drm/i915/pmu: Connect engine busyness stats from GuC to pmu") Signed-off-by: Umesh Nerlige Ramappa Reviewed-by: Alan Previn Signed-off-by: John Harrison Link: https://patchwork.freedesktop.org/patch/msgid/20220111015523.225562-1-umesh.nerlige.ramappa@intel.com --- drivers/gpu/drm/i915/gt/uc/intel_guc.h | 5 ++ drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c | 56 +++++++++++++++++------ drivers/gpu/drm/i915/i915_reg.h | 3 +- 3 files changed, 50 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc.h b/drivers/gpu/drm/i915/gt/uc/intel_guc.h index d59bbf49d1c2..697d9d66acef 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc.h +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc.h @@ -215,6 +215,11 @@ struct intel_guc { * context usage for overflows. */ struct delayed_work work; + + /** + * @shift: Right shift value for the gpm timestamp + */ + u32 shift; } timestamp; #ifdef CONFIG_DRM_I915_SELFTEST diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c index 422353a70d5d..c3fa7f4b38db 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c @@ -1149,23 +1149,51 @@ static void guc_update_engine_gt_clks(struct intel_engine_cs *engine) } } -static void guc_update_pm_timestamp(struct intel_guc *guc, - struct intel_engine_cs *engine, - ktime_t *now) +static u32 gpm_timestamp_shift(struct intel_gt *gt) { - u32 gt_stamp_now, gt_stamp_hi; + intel_wakeref_t wakeref; + u32 reg, shift; + + with_intel_runtime_pm(gt->uncore->rpm, wakeref) + reg = intel_uncore_read(gt->uncore, RPM_CONFIG0); + + shift = (reg & GEN10_RPM_CONFIG0_CTC_SHIFT_PARAMETER_MASK) >> + GEN10_RPM_CONFIG0_CTC_SHIFT_PARAMETER_SHIFT; + + return 3 - shift; +} + +static u64 gpm_timestamp(struct intel_gt *gt) +{ + u32 lo, hi, old_hi, loop = 0; + + hi = intel_uncore_read(gt->uncore, MISC_STATUS1); + do { + lo = intel_uncore_read(gt->uncore, MISC_STATUS0); + old_hi = hi; + hi = intel_uncore_read(gt->uncore, MISC_STATUS1); + } while (old_hi != hi && loop++ < 2); + + return ((u64)hi << 32) | lo; +} + +static void guc_update_pm_timestamp(struct intel_guc *guc, ktime_t *now) +{ + struct intel_gt *gt = guc_to_gt(guc); + u32 gt_stamp_lo, gt_stamp_hi; + u64 gpm_ts; lockdep_assert_held(&guc->timestamp.lock); gt_stamp_hi = upper_32_bits(guc->timestamp.gt_stamp); - gt_stamp_now = intel_uncore_read(engine->uncore, - RING_TIMESTAMP(engine->mmio_base)); + gpm_ts = gpm_timestamp(gt) >> guc->timestamp.shift; + gt_stamp_lo = lower_32_bits(gpm_ts); *now = ktime_get(); - if (gt_stamp_now < lower_32_bits(guc->timestamp.gt_stamp)) + if (gt_stamp_lo < lower_32_bits(guc->timestamp.gt_stamp)) gt_stamp_hi++; - guc->timestamp.gt_stamp = ((u64)gt_stamp_hi << 32) | gt_stamp_now; + guc->timestamp.gt_stamp = ((u64)gt_stamp_hi << 32) | gt_stamp_lo; } /* @@ -1209,7 +1237,7 @@ static ktime_t guc_engine_busyness(struct intel_engine_cs *engine, ktime_t *now) stats_saved = *stats; gt_stamp_saved = guc->timestamp.gt_stamp; guc_update_engine_gt_clks(engine); - guc_update_pm_timestamp(guc, engine, now); + guc_update_pm_timestamp(guc, now); intel_gt_pm_put_async(gt); if (i915_reset_count(gpu_error) != reset_count) { *stats = stats_saved; @@ -1241,8 +1269,8 @@ static void __reset_guc_busyness_stats(struct intel_guc *guc) spin_lock_irqsave(&guc->timestamp.lock, flags); + guc_update_pm_timestamp(guc, &unused); for_each_engine(engine, gt, id) { - guc_update_pm_timestamp(guc, engine, &unused); guc_update_engine_gt_clks(engine); engine->stats.guc.prev_total = 0; } @@ -1259,10 +1287,11 @@ static void __update_guc_busyness_stats(struct intel_guc *guc) ktime_t unused; spin_lock_irqsave(&guc->timestamp.lock, flags); - for_each_engine(engine, gt, id) { - guc_update_pm_timestamp(guc, engine, &unused); + + guc_update_pm_timestamp(guc, &unused); + for_each_engine(engine, gt, id) guc_update_engine_gt_clks(engine); - } + spin_unlock_irqrestore(&guc->timestamp.lock, flags); } @@ -1756,6 +1785,7 @@ int intel_guc_submission_init(struct intel_guc *guc) spin_lock_init(&guc->timestamp.lock); INIT_DELAYED_WORK(&guc->timestamp.work, guc_timestamp_ping); guc->timestamp.ping_delay = (POLL_TIME_CLKS / gt->clock_frequency + 1) * HZ; + guc->timestamp.shift = gpm_timestamp_shift(gt); return 0; } diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h index 5a7c9ae8001d..bcbb163dc99d 100644 --- a/drivers/gpu/drm/i915/i915_reg.h +++ b/drivers/gpu/drm/i915/i915_reg.h @@ -2688,7 +2688,8 @@ static inline bool i915_mmio_reg_valid(i915_reg_t reg) #define RING_WAIT (1 << 11) /* gen3+, PRBx_CTL */ #define RING_WAIT_SEMAPHORE (1 << 10) /* gen6+ */ -#define GUCPMTIMESTAMP _MMIO(0xC3E8) +#define MISC_STATUS0 _MMIO(0xA500) +#define MISC_STATUS1 _MMIO(0xA504) /* There are 16 64-bit CS General Purpose Registers per-engine on Gen8+ */ #define GEN8_RING_CS_GPR(base, n) _MMIO((base) + 0x600 + (n) * 8) -- cgit From cec49bce6e0cdf916433a248402bfdecbf6589b0 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Tue, 25 Jan 2022 12:07:26 -0600 Subject: drm/i915/guc: Use struct_size() helper in kmalloc() Make use of the struct_size() helper instead of an open-coded version, in order to avoid any potential type mistakes or integer overflows that, in the worst scenario, could lead to heap overflows. Also, address the following sparse warnings: drivers/gpu/drm/i915/gt/uc/intel_guc_ct.c:792:23: warning: using sizeof on a flexible structure Link: https://github.com/KSPP/linux/issues/174 Signed-off-by: Gustavo A. R. Silva Reviewed-by: Tvrtko Ursulin Signed-off-by: Tvrtko Ursulin Link: https://patchwork.freedesktop.org/patch/msgid/20220125180726.GA68646@embeddedor --- drivers/gpu/drm/i915/gt/uc/intel_guc_ct.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_ct.c b/drivers/gpu/drm/i915/gt/uc/intel_guc_ct.c index de89d40abd38..2f7fc87a78e1 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_ct.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_ct.c @@ -784,7 +784,7 @@ static struct ct_incoming_msg *ct_alloc_msg(u32 num_dwords) { struct ct_incoming_msg *msg; - msg = kmalloc(sizeof(*msg) + sizeof(u32) * num_dwords, GFP_ATOMIC); + msg = kmalloc(struct_size(msg, msg, num_dwords), GFP_ATOMIC); if (msg) msg->size = num_dwords; return msg; -- cgit From 8f4f9a3b3a0b733f8fd102f9e0843cad8d055c83 Mon Sep 17 00:00:00 2001 From: Thomas Hellström Date: Wed, 19 Jan 2022 18:47:34 +0100 Subject: drm/i915: Fix vma resource freeing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In some cases we use leftover kfree() instead of i915_vma_resource_free(). Fix this. Fixes: 2f6b90da9192 ("drm/i915: Use vma resources for async unbinding") Reported-by: Robert Beckett Cc: Matthew Auld Signed-off-by: Thomas Hellström Reviewed-by: Matthew Auld Link: https://patchwork.freedesktop.org/patch/msgid/20220119174734.213552-1-thomas.hellstrom@linux.intel.com --- drivers/gpu/drm/i915/i915_vma.c | 4 ++-- drivers/gpu/drm/i915/i915_vma_resource.c | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/i915_vma.c b/drivers/gpu/drm/i915/i915_vma.c index 426ed08b15ad..cdf1f9066c4f 100644 --- a/drivers/gpu/drm/i915/i915_vma.c +++ b/drivers/gpu/drm/i915/i915_vma.c @@ -463,7 +463,7 @@ int i915_vma_bind(struct i915_vma *vma, if (vma->resource || !vma_res) { /* Rebinding with an additional I915_VMA_*_BIND */ GEM_WARN_ON(!vma_flags); - kfree(vma_res); + i915_vma_resource_free(vma_res); } else { i915_vma_resource_init_from_vma(vma_res, vma); vma->resource = vma_res; @@ -1406,7 +1406,7 @@ err_active: err_unlock: mutex_unlock(&vma->vm->mutex); err_vma_res: - kfree(vma_res); + i915_vma_resource_free(vma_res); err_fence: if (work) dma_fence_work_commit_imm(&work->base); diff --git a/drivers/gpu/drm/i915/i915_vma_resource.c b/drivers/gpu/drm/i915/i915_vma_resource.c index bbb0ff14272f..57ae92ba8af1 100644 --- a/drivers/gpu/drm/i915/i915_vma_resource.c +++ b/drivers/gpu/drm/i915/i915_vma_resource.c @@ -62,7 +62,8 @@ struct i915_vma_resource *i915_vma_resource_alloc(void) */ void i915_vma_resource_free(struct i915_vma_resource *vma_res) { - kmem_cache_free(slab_vma_resources, vma_res); + if (vma_res) + kmem_cache_free(slab_vma_resources, vma_res); } static const char *get_driver_name(struct dma_fence *fence) -- cgit From de3688e469b08be958914674e8b01cb0cea42388 Mon Sep 17 00:00:00 2001 From: Christian König Date: Fri, 9 Jul 2021 15:18:39 +0200 Subject: drm/ttm: add ttm_resource_fini v2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make sure we call the common cleanup function in all implementations of the resource manager. v2: fix missing case in i915, rudimentary kerneldoc, should be filled in more when we add more functionality Signed-off-by: Christian König Reviewed-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20220124122514.1832-2-christian.koenig@amd.com --- drivers/gpu/drm/amd/amdgpu/amdgpu_gtt_mgr.c | 2 ++ drivers/gpu/drm/amd/amdgpu/amdgpu_preempt_mgr.c | 1 + drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c | 2 ++ drivers/gpu/drm/i915/i915_ttm_buddy_manager.c | 2 ++ drivers/gpu/drm/nouveau/nouveau_mem.c | 3 ++- drivers/gpu/drm/nouveau/nouveau_mem.h | 3 ++- drivers/gpu/drm/nouveau/nouveau_ttm.c | 9 +++++---- drivers/gpu/drm/ttm/ttm_range_manager.c | 2 ++ drivers/gpu/drm/ttm/ttm_resource.c | 23 +++++++++++++++++++++++ drivers/gpu/drm/ttm/ttm_sys_manager.c | 1 + drivers/gpu/drm/vmwgfx/vmwgfx_gmrid_manager.c | 2 ++ drivers/gpu/drm/vmwgfx/vmwgfx_system_manager.c | 1 + 12 files changed, 45 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gtt_mgr.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gtt_mgr.c index 675a72ef305d..ea5470c8c921 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gtt_mgr.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gtt_mgr.c @@ -169,6 +169,7 @@ static int amdgpu_gtt_mgr_new(struct ttm_resource_manager *man, return 0; err_free: + ttm_resource_fini(man, &node->base.base); kfree(node); err_out: @@ -200,6 +201,7 @@ static void amdgpu_gtt_mgr_del(struct ttm_resource_manager *man, if (!(res->placement & TTM_PL_FLAG_TEMPORARY)) atomic64_sub(res->num_pages, &mgr->used); + ttm_resource_fini(man, res); kfree(node); } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_preempt_mgr.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_preempt_mgr.c index d02c8637f909..ffddec08e931 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_preempt_mgr.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_preempt_mgr.c @@ -95,6 +95,7 @@ static void amdgpu_preempt_mgr_del(struct ttm_resource_manager *man, struct amdgpu_preempt_mgr *mgr = to_preempt_mgr(man); atomic64_sub(res->num_pages, &mgr->used); + ttm_resource_fini(man, res); kfree(res); } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c index 7b2b0980ec41..55d68408951d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c @@ -476,6 +476,7 @@ error_free: while (i--) drm_mm_remove_node(&node->mm_nodes[i]); spin_unlock(&mgr->lock); + ttm_resource_fini(man, &node->base); kvfree(node); error_sub: @@ -515,6 +516,7 @@ static void amdgpu_vram_mgr_del(struct ttm_resource_manager *man, atomic64_sub(usage, &mgr->usage); atomic64_sub(vis_usage, &mgr->vis_usage); + ttm_resource_fini(man, res); kvfree(node); } diff --git a/drivers/gpu/drm/i915/i915_ttm_buddy_manager.c b/drivers/gpu/drm/i915/i915_ttm_buddy_manager.c index 6ba314f9836a..e275b386886d 100644 --- a/drivers/gpu/drm/i915/i915_ttm_buddy_manager.c +++ b/drivers/gpu/drm/i915/i915_ttm_buddy_manager.c @@ -110,6 +110,7 @@ err_free_blocks: drm_buddy_free_list(mm, &bman_res->blocks); mutex_unlock(&bman->lock); err_free_res: + ttm_resource_fini(man, &bman_res->base); kfree(bman_res); return err; } @@ -124,6 +125,7 @@ static void i915_ttm_buddy_man_free(struct ttm_resource_manager *man, drm_buddy_free_list(&bman->mm, &bman_res->blocks); mutex_unlock(&bman->lock); + ttm_resource_fini(man, res); kfree(bman_res); } diff --git a/drivers/gpu/drm/nouveau/nouveau_mem.c b/drivers/gpu/drm/nouveau/nouveau_mem.c index 2ca3207c13fc..2e517cdc24c9 100644 --- a/drivers/gpu/drm/nouveau/nouveau_mem.c +++ b/drivers/gpu/drm/nouveau/nouveau_mem.c @@ -162,11 +162,12 @@ nouveau_mem_vram(struct ttm_resource *reg, bool contig, u8 page) } void -nouveau_mem_del(struct ttm_resource *reg) +nouveau_mem_del(struct ttm_resource_manager *man, struct ttm_resource *reg) { struct nouveau_mem *mem = nouveau_mem(reg); nouveau_mem_fini(mem); + ttm_resource_fini(man, reg); kfree(mem); } diff --git a/drivers/gpu/drm/nouveau/nouveau_mem.h b/drivers/gpu/drm/nouveau/nouveau_mem.h index 2c01166a90f2..325551eba5cd 100644 --- a/drivers/gpu/drm/nouveau/nouveau_mem.h +++ b/drivers/gpu/drm/nouveau/nouveau_mem.h @@ -23,7 +23,8 @@ nouveau_mem(struct ttm_resource *reg) int nouveau_mem_new(struct nouveau_cli *, u8 kind, u8 comp, struct ttm_resource **); -void nouveau_mem_del(struct ttm_resource *); +void nouveau_mem_del(struct ttm_resource_manager *man, + struct ttm_resource *); int nouveau_mem_vram(struct ttm_resource *, bool contig, u8 page); int nouveau_mem_host(struct ttm_resource *, struct ttm_tt *); void nouveau_mem_fini(struct nouveau_mem *); diff --git a/drivers/gpu/drm/nouveau/nouveau_ttm.c b/drivers/gpu/drm/nouveau/nouveau_ttm.c index 2ca9d9a9e5d5..91ef33f8f22c 100644 --- a/drivers/gpu/drm/nouveau/nouveau_ttm.c +++ b/drivers/gpu/drm/nouveau/nouveau_ttm.c @@ -36,9 +36,10 @@ #include static void -nouveau_manager_del(struct ttm_resource_manager *man, struct ttm_resource *reg) +nouveau_manager_del(struct ttm_resource_manager *man, + struct ttm_resource *reg) { - nouveau_mem_del(reg); + nouveau_mem_del(man, reg); } static int @@ -62,7 +63,7 @@ nouveau_vram_manager_new(struct ttm_resource_manager *man, ret = nouveau_mem_vram(*res, nvbo->contig, nvbo->page); if (ret) { - nouveau_mem_del(*res); + nouveau_mem_del(man, *res); return ret; } @@ -118,7 +119,7 @@ nv04_gart_manager_new(struct ttm_resource_manager *man, ret = nvif_vmm_get(&mem->cli->vmm.vmm, PTES, false, 12, 0, (long)(*res)->num_pages << PAGE_SHIFT, &mem->vma[0]); if (ret) { - nouveau_mem_del(*res); + nouveau_mem_del(man, *res); return ret; } diff --git a/drivers/gpu/drm/ttm/ttm_range_manager.c b/drivers/gpu/drm/ttm/ttm_range_manager.c index 072e0baf2ab4..55a41355f684 100644 --- a/drivers/gpu/drm/ttm/ttm_range_manager.c +++ b/drivers/gpu/drm/ttm/ttm_range_manager.c @@ -89,6 +89,7 @@ static int ttm_range_man_alloc(struct ttm_resource_manager *man, spin_unlock(&rman->lock); if (unlikely(ret)) { + ttm_resource_fini(man, *res); kfree(node); return ret; } @@ -108,6 +109,7 @@ static void ttm_range_man_free(struct ttm_resource_manager *man, drm_mm_remove_node(&node->mm_nodes[0]); spin_unlock(&rman->lock); + ttm_resource_fini(man, res); kfree(node); } diff --git a/drivers/gpu/drm/ttm/ttm_resource.c b/drivers/gpu/drm/ttm/ttm_resource.c index 035d71332d18..7e5e32d2ef12 100644 --- a/drivers/gpu/drm/ttm/ttm_resource.c +++ b/drivers/gpu/drm/ttm/ttm_resource.c @@ -29,6 +29,14 @@ #include #include +/** + * ttm_resource_init - resource object constructure + * @bo: buffer object this resources is allocated for + * @place: placement of the resource + * @res: the resource object to inistilize + * + * Initialize a new resource object. Counterpart of &ttm_resource_fini. + */ void ttm_resource_init(struct ttm_buffer_object *bo, const struct ttm_place *place, struct ttm_resource *res) @@ -44,6 +52,21 @@ void ttm_resource_init(struct ttm_buffer_object *bo, } EXPORT_SYMBOL(ttm_resource_init); +/** + * ttm_resource_fini - resource destructor + * @man: the resource manager this resource belongs to + * @res: the resource to clean up + * + * Should be used by resource manager backends to clean up the TTM resource + * objects before freeing the underlying structure. Counterpart of + * &ttm_resource_init + */ +void ttm_resource_fini(struct ttm_resource_manager *man, + struct ttm_resource *res) +{ +} +EXPORT_SYMBOL(ttm_resource_fini); + int ttm_resource_alloc(struct ttm_buffer_object *bo, const struct ttm_place *place, struct ttm_resource **res_ptr) diff --git a/drivers/gpu/drm/ttm/ttm_sys_manager.c b/drivers/gpu/drm/ttm/ttm_sys_manager.c index 63aca52f75e1..135394dcca95 100644 --- a/drivers/gpu/drm/ttm/ttm_sys_manager.c +++ b/drivers/gpu/drm/ttm/ttm_sys_manager.c @@ -23,6 +23,7 @@ static int ttm_sys_man_alloc(struct ttm_resource_manager *man, static void ttm_sys_man_free(struct ttm_resource_manager *man, struct ttm_resource *res) { + ttm_resource_fini(man, res); kfree(res); } diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_gmrid_manager.c b/drivers/gpu/drm/vmwgfx/vmwgfx_gmrid_manager.c index ebb4505a31a3..99ccf690f8a1 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_gmrid_manager.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_gmrid_manager.c @@ -117,6 +117,7 @@ nospace: gman->used_gmr_pages -= (*res)->num_pages; spin_unlock(&gman->lock); ida_free(&gman->gmr_ida, id); + ttm_resource_fini(man, *res); kfree(*res); return -ENOSPC; } @@ -130,6 +131,7 @@ static void vmw_gmrid_man_put_node(struct ttm_resource_manager *man, spin_lock(&gman->lock); gman->used_gmr_pages -= res->num_pages; spin_unlock(&gman->lock); + ttm_resource_fini(man, res); kfree(res); } diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_system_manager.c b/drivers/gpu/drm/vmwgfx/vmwgfx_system_manager.c index b0005b03a617..a64188c7268d 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_system_manager.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_system_manager.c @@ -49,6 +49,7 @@ static int vmw_sys_man_alloc(struct ttm_resource_manager *man, static void vmw_sys_man_free(struct ttm_resource_manager *man, struct ttm_resource *res) { + ttm_resource_fini(man, res); kfree(res); } -- cgit From 3f268ef06f8cf3c481dbd5843d564f5170c6df54 Mon Sep 17 00:00:00 2001 From: Christian König Date: Mon, 30 Aug 2021 09:22:06 +0200 Subject: drm/ttm: add back a reference to the bdev to the res manager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is simply a lot cleaner to have this around instead of adding the device throughout the call chain. Signed-off-by: Christian König Reviewed-by: Huang Rui Acked-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20220124122514.1832-3-christian.koenig@amd.com --- drivers/gpu/drm/amd/amdgpu/amdgpu_gtt_mgr.c | 3 ++- drivers/gpu/drm/amd/amdgpu/amdgpu_preempt_mgr.c | 2 +- drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c | 3 ++- drivers/gpu/drm/i915/i915_ttm_buddy_manager.c | 2 +- drivers/gpu/drm/nouveau/nouveau_ttm.c | 4 ++-- drivers/gpu/drm/ttm/ttm_range_manager.c | 2 +- drivers/gpu/drm/ttm/ttm_resource.c | 3 +++ drivers/gpu/drm/ttm/ttm_sys_manager.c | 2 +- drivers/gpu/drm/vmwgfx/vmwgfx_gmrid_manager.c | 2 +- drivers/gpu/drm/vmwgfx/vmwgfx_system_manager.c | 2 +- 10 files changed, 15 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gtt_mgr.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gtt_mgr.c index ea5470c8c921..9e7685a4878c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gtt_mgr.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gtt_mgr.c @@ -293,7 +293,8 @@ int amdgpu_gtt_mgr_init(struct amdgpu_device *adev, uint64_t gtt_size) man->use_tt = true; man->func = &amdgpu_gtt_mgr_func; - ttm_resource_manager_init(man, gtt_size >> PAGE_SHIFT); + ttm_resource_manager_init(man, &adev->mman.bdev, + gtt_size >> PAGE_SHIFT); start = AMDGPU_GTT_MAX_TRANSFER_SIZE * AMDGPU_GTT_NUM_TRANSFER_WINDOWS; size = (adev->gmc.gart_size >> PAGE_SHIFT) - start; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_preempt_mgr.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_preempt_mgr.c index ffddec08e931..6f7189d32f0a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_preempt_mgr.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_preempt_mgr.c @@ -153,7 +153,7 @@ int amdgpu_preempt_mgr_init(struct amdgpu_device *adev) man->use_tt = true; man->func = &amdgpu_preempt_mgr_func; - ttm_resource_manager_init(man, (1 << 30)); + ttm_resource_manager_init(man, &adev->mman.bdev, (1 << 30)); atomic64_set(&mgr->used, 0); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c index 55d68408951d..ddd0b6d74070 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c @@ -699,7 +699,8 @@ int amdgpu_vram_mgr_init(struct amdgpu_device *adev) struct amdgpu_vram_mgr *mgr = &adev->mman.vram_mgr; struct ttm_resource_manager *man = &mgr->manager; - ttm_resource_manager_init(man, adev->gmc.real_vram_size >> PAGE_SHIFT); + ttm_resource_manager_init(man, &adev->mman.bdev, + adev->gmc.real_vram_size >> PAGE_SHIFT); man->func = &amdgpu_vram_mgr_func; diff --git a/drivers/gpu/drm/i915/i915_ttm_buddy_manager.c b/drivers/gpu/drm/i915/i915_ttm_buddy_manager.c index e275b386886d..247714bab044 100644 --- a/drivers/gpu/drm/i915/i915_ttm_buddy_manager.c +++ b/drivers/gpu/drm/i915/i915_ttm_buddy_manager.c @@ -205,7 +205,7 @@ int i915_ttm_buddy_man_init(struct ttm_device *bdev, man = &bman->manager; man->use_tt = use_tt; man->func = &i915_ttm_buddy_manager_func; - ttm_resource_manager_init(man, bman->mm.size >> PAGE_SHIFT); + ttm_resource_manager_init(man, bdev, bman->mm.size >> PAGE_SHIFT); ttm_resource_manager_set_used(man, true); ttm_set_driver_manager(bdev, type, man); diff --git a/drivers/gpu/drm/nouveau/nouveau_ttm.c b/drivers/gpu/drm/nouveau/nouveau_ttm.c index 91ef33f8f22c..85f1f5a0fe5d 100644 --- a/drivers/gpu/drm/nouveau/nouveau_ttm.c +++ b/drivers/gpu/drm/nouveau/nouveau_ttm.c @@ -164,7 +164,7 @@ nouveau_ttm_init_vram(struct nouveau_drm *drm) man->func = &nouveau_vram_manager; - ttm_resource_manager_init(man, + ttm_resource_manager_init(man, &drm->ttm.bdev, drm->gem.vram_available >> PAGE_SHIFT); ttm_set_driver_manager(&drm->ttm.bdev, TTM_PL_VRAM, man); ttm_resource_manager_set_used(man, true); @@ -211,7 +211,7 @@ nouveau_ttm_init_gtt(struct nouveau_drm *drm) man->func = func; man->use_tt = true; - ttm_resource_manager_init(man, size_pages); + ttm_resource_manager_init(man, &drm->ttm.bdev, size_pages); ttm_set_driver_manager(&drm->ttm.bdev, TTM_PL_TT, man); ttm_resource_manager_set_used(man, true); return 0; diff --git a/drivers/gpu/drm/ttm/ttm_range_manager.c b/drivers/gpu/drm/ttm/ttm_range_manager.c index 55a41355f684..8cd4f3fb9f79 100644 --- a/drivers/gpu/drm/ttm/ttm_range_manager.c +++ b/drivers/gpu/drm/ttm/ttm_range_manager.c @@ -158,7 +158,7 @@ int ttm_range_man_init_nocheck(struct ttm_device *bdev, man->func = &ttm_range_manager_func; - ttm_resource_manager_init(man, p_size); + ttm_resource_manager_init(man, bdev, p_size); drm_mm_init(&rman->mm, 0, p_size); spin_lock_init(&rman->lock); diff --git a/drivers/gpu/drm/ttm/ttm_resource.c b/drivers/gpu/drm/ttm/ttm_resource.c index 7e5e32d2ef12..4717128633e9 100644 --- a/drivers/gpu/drm/ttm/ttm_resource.c +++ b/drivers/gpu/drm/ttm/ttm_resource.c @@ -143,16 +143,19 @@ EXPORT_SYMBOL(ttm_resource_compat); * ttm_resource_manager_init * * @man: memory manager object to init + * @bdev: ttm device this manager belongs to * @p_size: size managed area in pages. * * Initialise core parts of a manager object. */ void ttm_resource_manager_init(struct ttm_resource_manager *man, + struct ttm_device *bdev, unsigned long p_size) { unsigned i; spin_lock_init(&man->move_lock); + man->bdev = bdev; man->size = p_size; for (i = 0; i < TTM_MAX_BO_PRIORITY; ++i) diff --git a/drivers/gpu/drm/ttm/ttm_sys_manager.c b/drivers/gpu/drm/ttm/ttm_sys_manager.c index 135394dcca95..2ced169513cb 100644 --- a/drivers/gpu/drm/ttm/ttm_sys_manager.c +++ b/drivers/gpu/drm/ttm/ttm_sys_manager.c @@ -43,7 +43,7 @@ void ttm_sys_man_init(struct ttm_device *bdev) man->use_tt = true; man->func = &ttm_sys_manager_func; - ttm_resource_manager_init(man, 0); + ttm_resource_manager_init(man, bdev, 0); ttm_set_driver_manager(bdev, TTM_PL_SYSTEM, man); ttm_resource_manager_set_used(man, true); } diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_gmrid_manager.c b/drivers/gpu/drm/vmwgfx/vmwgfx_gmrid_manager.c index 99ccf690f8a1..60e3cc537f36 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_gmrid_manager.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_gmrid_manager.c @@ -162,7 +162,7 @@ int vmw_gmrid_man_init(struct vmw_private *dev_priv, int type) man->func = &vmw_gmrid_manager_func; man->use_tt = true; - ttm_resource_manager_init(man, 0); + ttm_resource_manager_init(man, &dev_priv->bdev, 0); spin_lock_init(&gman->lock); gman->used_gmr_pages = 0; ida_init(&gman->gmr_ida); diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_system_manager.c b/drivers/gpu/drm/vmwgfx/vmwgfx_system_manager.c index a64188c7268d..d3007bf1b8f5 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_system_manager.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_system_manager.c @@ -70,7 +70,7 @@ int vmw_sys_man_init(struct vmw_private *dev_priv) man->use_tt = true; man->func = &vmw_sys_manager_func; - ttm_resource_manager_init(man, 0); + ttm_resource_manager_init(man, bdev, 0); ttm_set_driver_manager(bdev, VMW_PL_SYSTEM, man); ttm_resource_manager_set_used(man, true); return 0; -- cgit From fda8d552c9c7783d4b29aeb6350d7404b31cdbff Mon Sep 17 00:00:00 2001 From: Christian König Date: Thu, 15 Jul 2021 14:17:19 +0200 Subject: drm/ttm: add a weak BO reference to the resource v3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep track for which BO a resource was allocated. This is necessary to move the LRU handling into the resources. A bit problematic is i915 since it tries to use the resource interface without a BO which is illegal from the conceptional point of view. v2: Document that this is a weak reference and add a workaround for i915 v3: further document that this is protected by ttm_device::lru_lock and clarify the i915 workaround Signed-off-by: Christian König Acked-by: Huang Rui Link: https://patchwork.freedesktop.org/patch/msgid/20220124122514.1832-4-christian.koenig@amd.com --- drivers/gpu/drm/ttm/ttm_bo_util.c | 7 +++++-- drivers/gpu/drm/ttm/ttm_resource.c | 9 +++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/ttm/ttm_bo_util.c b/drivers/gpu/drm/ttm/ttm_bo_util.c index 72a94301bc95..544a84fa6589 100644 --- a/drivers/gpu/drm/ttm/ttm_bo_util.c +++ b/drivers/gpu/drm/ttm/ttm_bo_util.c @@ -241,6 +241,11 @@ static int ttm_buffer_object_transfer(struct ttm_buffer_object *bo, if (bo->type != ttm_bo_type_sg) fbo->base.base.resv = &fbo->base.base._resv; + if (fbo->base.resource) { + ttm_resource_set_bo(fbo->base.resource, &fbo->base); + bo->resource = NULL; + } + dma_resv_init(&fbo->base.base._resv); fbo->base.base.dev = NULL; ret = dma_resv_trylock(&fbo->base.base._resv); @@ -509,7 +514,6 @@ static int ttm_bo_move_to_ghost(struct ttm_buffer_object *bo, ghost_obj->ttm = NULL; else bo->ttm = NULL; - bo->resource = NULL; dma_resv_unlock(&ghost_obj->base._resv); ttm_bo_put(ghost_obj); @@ -637,7 +641,6 @@ int ttm_bo_pipeline_gutting(struct ttm_buffer_object *bo) dma_resv_unlock(&ghost->base._resv); ttm_bo_put(ghost); bo->ttm = ttm; - bo->resource = NULL; ttm_bo_assign_mem(bo, sys_res); return 0; diff --git a/drivers/gpu/drm/ttm/ttm_resource.c b/drivers/gpu/drm/ttm/ttm_resource.c index 4717128633e9..68344c90549b 100644 --- a/drivers/gpu/drm/ttm/ttm_resource.c +++ b/drivers/gpu/drm/ttm/ttm_resource.c @@ -49,6 +49,7 @@ void ttm_resource_init(struct ttm_buffer_object *bo, res->bus.offset = 0; res->bus.is_iomem = false; res->bus.caching = ttm_cached; + res->bo = bo; } EXPORT_SYMBOL(ttm_resource_init); @@ -139,6 +140,14 @@ bool ttm_resource_compat(struct ttm_resource *res, } EXPORT_SYMBOL(ttm_resource_compat); +void ttm_resource_set_bo(struct ttm_resource *res, + struct ttm_buffer_object *bo) +{ + spin_lock(&bo->bdev->lru_lock); + res->bo = bo; + spin_unlock(&bo->bdev->lru_lock); +} + /** * ttm_resource_manager_init * -- cgit From b889d89ad45f9957ea3eac8f61cb8884c2010940 Mon Sep 17 00:00:00 2001 From: Yunlong Jia Date: Thu, 20 Jan 2022 06:45:10 +0000 Subject: gpu: drm: panel-edp: Add panels planned for sc7180-trogdor-pazquel We have added corresponding information: [BOE]NV116WHM-N45 use delay_200_500_e50 [KDB]116N29-30NK-C007 use delay_200_500_e80_d50 [STA]2081116HHD028001-51D use delay_100_500_e200 Add 3 panels & 2 delay. Signed-off-by: Yunlong Jia Signed-off-by: Grace Mi Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patchwork.freedesktop.org/patch/msgid/20220120064457.1.I337b8db7efaba8eb9c0ffd4da0d8c8133faf6f19@changeid --- drivers/gpu/drm/panel/panel-edp.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/panel/panel-edp.c b/drivers/gpu/drm/panel/panel-edp.c index 99ca1bd0091c..a394a15dc3fb 100644 --- a/drivers/gpu/drm/panel/panel-edp.c +++ b/drivers/gpu/drm/panel/panel-edp.c @@ -1745,6 +1745,19 @@ static const struct panel_delay delay_200_500_e50 = { .enable = 50, }; +static const struct panel_delay delay_200_500_e80_d50 = { + .hpd_absent = 200, + .unprepare = 500, + .enable = 80, + .disable = 50, +}; + +static const struct panel_delay delay_100_500_e200 = { + .hpd_absent = 100, + .unprepare = 500, + .enable = 200, +}; + #define EDP_PANEL_ENTRY(vend_chr_0, vend_chr_1, vend_chr_2, product_id, _delay, _name) \ { \ .name = _name, \ @@ -1768,13 +1781,17 @@ static const struct edp_panel_entry edp_panels[] = { EDP_PANEL_ENTRY('B', 'O', 'E', 0x07d1, &boe_nv133fhm_n61.delay, "NV133FHM-N61"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x082d, &boe_nv133fhm_n61.delay, "NV133FHM-N62"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x098d, &boe_nv110wtm_n61.delay, "NV110WTM-N61"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0a5d, &delay_200_500_e50, "NV116WHM-N45"), EDP_PANEL_ENTRY('C', 'M', 'N', 0x114c, &innolux_n116bca_ea1.delay, "N116BCA-EA1"), EDP_PANEL_ENTRY('K', 'D', 'B', 0x0624, &kingdisplay_kd116n21_30nv_a010.delay, "116N21-30NV-A010"), + EDP_PANEL_ENTRY('K', 'D', 'B', 0x1120, &delay_200_500_e80_d50, "116N29-30NK-C007"), EDP_PANEL_ENTRY('S', 'H', 'P', 0x154c, &delay_200_500_p2e100, "LQ116M1JW10"), + EDP_PANEL_ENTRY('S', 'T', 'A', 0x0100, &delay_100_500_e200, "2081116HHD028001-51D"), + { /* sentinal */ } }; -- cgit From 8295524a2d5550b56e800ac779a183b7f4d8c33c Mon Sep 17 00:00:00 2001 From: Anusha Srivatsa Date: Tue, 25 Jan 2022 14:36:25 -0800 Subject: drm/i915/rpl-s: Add stepping info Add stepping-substepping info in accordance to BSpec changes. Though it looks weird, the revision ID for the newer stepping is indeed backwards and is in accordance to the spec. v2: Rearrange the platforms in logical order (Matt) Bspec: 53655 Cc: Matt Roper Signed-off-by: Anusha Srivatsa Reviewed-by: Matt Roper Signed-off-by: Matt Roper Link: https://patchwork.freedesktop.org/patch/msgid/20220125223625.4139326-1-anusha.srivatsa@intel.com --- drivers/gpu/drm/i915/intel_step.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/intel_step.c b/drivers/gpu/drm/i915/intel_step.c index a4b16b9e2e55..453c04472545 100644 --- a/drivers/gpu/drm/i915/intel_step.c +++ b/drivers/gpu/drm/i915/intel_step.c @@ -122,6 +122,11 @@ static const struct intel_step_info dg2_g11_revid_step_tbl[] = { [0x5] = { COMMON_GT_MEDIA_STEP(B1), .display_step = STEP_C0 }, }; +static const struct intel_step_info adls_rpls_revids[] = { + [0x4] = { COMMON_GT_MEDIA_STEP(D0), .display_step = STEP_D0 }, + [0xC] = { COMMON_GT_MEDIA_STEP(D0), .display_step = STEP_C0 }, +}; + void intel_step_init(struct drm_i915_private *i915) { const struct intel_step_info *revids = NULL; @@ -141,6 +146,9 @@ void intel_step_init(struct drm_i915_private *i915) } else if (IS_ALDERLAKE_P(i915)) { revids = adlp_revids; size = ARRAY_SIZE(adlp_revids); + } else if (IS_ADLS_RPLS(i915)) { + revids = adls_rpls_revids; + size = ARRAY_SIZE(adls_rpls_revids); } else if (IS_ALDERLAKE_S(i915)) { revids = adls_revids; size = ARRAY_SIZE(adls_revids); -- cgit From db3b3f3e62279b914e1958e93e057fc4d8dee263 Mon Sep 17 00:00:00 2001 From: Daniele Ceraolo Spurio Date: Thu, 20 Jan 2022 13:29:47 -0800 Subject: drm/i915/wopcm: Handle pre-programmed WOPCM registers Starting from DG2, some of the programming previously done by i915 and the GuC has been moved to the GSC and the relevant registers are no longer writable by either CPU or GuC. This is also referred to as GuC deprivilege. On the i915 side, this affects the WOPCM registers: these are no longer programmed by the driver and we do instead expect to find them already set. This can lead to verification failures because in i915 we cheat a bit with the WOPCM size defines, to keep the code common across platforms, by sometimes using a smaller WOPCM size that the actual HW support (which isn't a problem because the extra size is not needed if the FW fits in the smaller chunk), while the pre-programmed values can use the actual size. Given tha the new programming entity is trusted, relax the amount of the checks done on the pre-programmed values by not limiting the max programmed size. In the extremely unlikely scenario that the registers have been misprogrammed, we will still fail later at DMA time. v2: drop special case for DG2 G10 A0 (Alan) Signed-off-by: Daniele Ceraolo Spurio Cc: Stuart Summers Cc: John Harrison Cc: Alan Previn Reviewed-by: Alan Previn Link: https://patchwork.freedesktop.org/patch/msgid/20220120212947.3440448-1-daniele.ceraolospurio@intel.com --- drivers/gpu/drm/i915/gt/uc/intel_guc_reg.h | 3 +++ drivers/gpu/drm/i915/i915_drv.h | 3 +++ drivers/gpu/drm/i915/i915_pci.c | 1 + drivers/gpu/drm/i915/intel_device_info.h | 1 + drivers/gpu/drm/i915/intel_wopcm.c | 42 +++++++++++++++++++++++++----- 5 files changed, 43 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_reg.h b/drivers/gpu/drm/i915/gt/uc/intel_guc_reg.h index e6bd66d6ce5a..cdb47c2291c8 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_reg.h +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_reg.h @@ -94,6 +94,9 @@ #define GUC_ENABLE_MIA_CLOCK_GATING (1<<15) #define GUC_GEN10_SHIM_WC_ENABLE (1<<21) +#define GUC_SHIM_CONTROL2 _MMIO(0xc068) +#define GUC_IS_PRIVILEGED (1<<29) + #define GUC_SEND_INTERRUPT _MMIO(0xc4c8) #define GUC_SEND_TRIGGER (1<<0) #define GEN11_GUC_HOST_INTERRUPT _MMIO(0x1901f0) diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index ac68d9c16e8e..28374fd5e0a1 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -1577,6 +1577,9 @@ IS_SUBPLATFORM(const struct drm_i915_private *i915, #define INTEL_DISPLAY_ENABLED(dev_priv) \ (drm_WARN_ON(&(dev_priv)->drm, !HAS_DISPLAY(dev_priv)), !(dev_priv)->params.disable_display) +#define HAS_GUC_DEPRIVILEGE(dev_priv) \ + (INTEL_INFO(dev_priv)->has_guc_deprivilege) + static inline bool run_as_guest(void) { return !hypervisor_is_type(X86_HYPER_NATIVE); diff --git a/drivers/gpu/drm/i915/i915_pci.c b/drivers/gpu/drm/i915/i915_pci.c index 261294df535c..55333ccd1e6d 100644 --- a/drivers/gpu/drm/i915/i915_pci.c +++ b/drivers/gpu/drm/i915/i915_pci.c @@ -1045,6 +1045,7 @@ static const struct intel_device_info dg2_info = { .graphics.rel = 55, .media.rel = 55, PLATFORM(INTEL_DG2), + .has_guc_deprivilege = 1, .has_64k_pages = 1, .platform_engine_mask = BIT(RCS0) | BIT(BCS0) | diff --git a/drivers/gpu/drm/i915/intel_device_info.h b/drivers/gpu/drm/i915/intel_device_info.h index 78597d382445..8b994ddc374e 100644 --- a/drivers/gpu/drm/i915/intel_device_info.h +++ b/drivers/gpu/drm/i915/intel_device_info.h @@ -131,6 +131,7 @@ enum intel_ppgtt_type { func(has_reset_engine); \ func(has_global_mocs); \ func(has_gt_uc); \ + func(has_guc_deprivilege); \ func(has_l3_dpf); \ func(has_llc); \ func(has_logical_ring_contexts); \ diff --git a/drivers/gpu/drm/i915/intel_wopcm.c b/drivers/gpu/drm/i915/intel_wopcm.c index f06d21005106..322fb9eeb880 100644 --- a/drivers/gpu/drm/i915/intel_wopcm.c +++ b/drivers/gpu/drm/i915/intel_wopcm.c @@ -43,6 +43,7 @@ /* Default WOPCM size is 2MB from Gen11, 1MB on previous platforms */ #define GEN11_WOPCM_SIZE SZ_2M #define GEN9_WOPCM_SIZE SZ_1M +#define MAX_WOPCM_SIZE SZ_8M /* 16KB WOPCM (RSVD WOPCM) is reserved from HuC firmware top. */ #define WOPCM_RESERVED_SIZE SZ_16K @@ -207,6 +208,14 @@ static bool __wopcm_regs_locked(struct intel_uncore *uncore, return true; } +static bool __wopcm_regs_writable(struct intel_uncore *uncore) +{ + if (!HAS_GUC_DEPRIVILEGE(uncore->i915)) + return true; + + return intel_uncore_read(uncore, GUC_SHIM_CONTROL2) & GUC_IS_PRIVILEGED; +} + /** * intel_wopcm_init() - Initialize the WOPCM structure. * @wopcm: pointer to intel_wopcm. @@ -224,18 +233,19 @@ void intel_wopcm_init(struct intel_wopcm *wopcm) u32 guc_fw_size = intel_uc_fw_get_upload_size(>->uc.guc.fw); u32 huc_fw_size = intel_uc_fw_get_upload_size(>->uc.huc.fw); u32 ctx_rsvd = context_reserved_size(i915); + u32 wopcm_size = wopcm->size; u32 guc_wopcm_base; u32 guc_wopcm_size; if (!guc_fw_size) return; - GEM_BUG_ON(!wopcm->size); + GEM_BUG_ON(!wopcm_size); GEM_BUG_ON(wopcm->guc.base); GEM_BUG_ON(wopcm->guc.size); - GEM_BUG_ON(guc_fw_size >= wopcm->size); - GEM_BUG_ON(huc_fw_size >= wopcm->size); - GEM_BUG_ON(ctx_rsvd + WOPCM_RESERVED_SIZE >= wopcm->size); + GEM_BUG_ON(guc_fw_size >= wopcm_size); + GEM_BUG_ON(huc_fw_size >= wopcm_size); + GEM_BUG_ON(ctx_rsvd + WOPCM_RESERVED_SIZE >= wopcm_size); if (i915_inject_probe_failure(i915)) return; @@ -243,6 +253,24 @@ void intel_wopcm_init(struct intel_wopcm *wopcm) if (__wopcm_regs_locked(gt->uncore, &guc_wopcm_base, &guc_wopcm_size)) { drm_dbg(&i915->drm, "GuC WOPCM is already locked [%uK, %uK)\n", guc_wopcm_base / SZ_1K, guc_wopcm_size / SZ_1K); + /* + * Note that to keep things simple (i.e. avoid different + * defines per platform) our WOPCM math doesn't always use the + * actual WOPCM size, but a value that is less or equal to it. + * This is perfectly fine when i915 programs the registers, but + * on platforms with GuC deprivilege the registers are not + * writable from i915 and are instead pre-programmed by the + * bios/IFWI, so there might be a mismatch of sizes. + * Instead of handling the size difference, we trust that the + * programmed values make sense and disable the relevant check + * by using the maximum possible WOPCM size in the verification + * math. In the extremely unlikely case that the registers + * were pre-programmed with an invalid value, we will still + * gracefully fail later during the GuC/HuC dma. + */ + if (!__wopcm_regs_writable(gt->uncore)) + wopcm_size = MAX_WOPCM_SIZE; + goto check; } @@ -257,17 +285,17 @@ void intel_wopcm_init(struct intel_wopcm *wopcm) * Need to clamp guc_wopcm_base now to make sure the following math is * correct. Formal check of whole WOPCM layout will be done below. */ - guc_wopcm_base = min(guc_wopcm_base, wopcm->size - ctx_rsvd); + guc_wopcm_base = min(guc_wopcm_base, wopcm_size - ctx_rsvd); /* Aligned remainings of usable WOPCM space can be assigned to GuC. */ - guc_wopcm_size = wopcm->size - ctx_rsvd - guc_wopcm_base; + guc_wopcm_size = wopcm_size - ctx_rsvd - guc_wopcm_base; guc_wopcm_size &= GUC_WOPCM_SIZE_MASK; drm_dbg(&i915->drm, "Calculated GuC WOPCM [%uK, %uK)\n", guc_wopcm_base / SZ_1K, guc_wopcm_size / SZ_1K); check: - if (__check_layout(i915, wopcm->size, guc_wopcm_base, guc_wopcm_size, + if (__check_layout(i915, wopcm_size, guc_wopcm_base, guc_wopcm_size, guc_fw_size, huc_fw_size)) { wopcm->guc.base = guc_wopcm_base; wopcm->guc.size = guc_wopcm_size; -- cgit From b229712b26b58d043cd7386827f41ab022a4d109 Mon Sep 17 00:00:00 2001 From: Daniele Ceraolo Spurio Date: Thu, 20 Jan 2022 14:24:36 -0800 Subject: drm/i915/guc: Update guc shim control programming on newer platforms Starting from xehpsdv, bit 0 of the GuC shim control register has been repurposed, while bit 2 is now reserved, so we need to avoid setting those for their old meaning on newer platforms. Cc: Vinay Belgaumkar Cc: Stuart Summers Signed-off-by: Daniele Ceraolo Spurio Reviewed-by: Vinay Belgaumkar Link: https://patchwork.freedesktop.org/patch/msgid/20220120222436.3449778-1-daniele.ceraolospurio@intel.com --- drivers/gpu/drm/i915/gt/uc/intel_guc_fw.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_fw.c b/drivers/gpu/drm/i915/gt/uc/intel_guc_fw.c index f773e7f35bc1..40f7d4779c9e 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_fw.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_fw.c @@ -15,13 +15,15 @@ static void guc_prepare_xfer(struct intel_uncore *uncore) { - u32 shim_flags = GUC_DISABLE_SRAM_INIT_TO_ZEROES | - GUC_ENABLE_READ_CACHE_LOGIC | - GUC_ENABLE_MIA_CACHING | + u32 shim_flags = GUC_ENABLE_READ_CACHE_LOGIC | GUC_ENABLE_READ_CACHE_FOR_SRAM_DATA | GUC_ENABLE_READ_CACHE_FOR_WOPCM_DATA | GUC_ENABLE_MIA_CLOCK_GATING; + if (GRAPHICS_VER_FULL(uncore->i915) < IP_VER(12, 50)) + shim_flags |= GUC_DISABLE_SRAM_INIT_TO_ZEROES | + GUC_ENABLE_MIA_CACHING; + /* Must program this register before loading the ucode with DMA */ intel_uncore_write(uncore, GUC_SHIM_CONTROL, shim_flags); -- cgit From 27599aacbaefcbf2af7b06b0029459bbf682000d Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Tue, 25 Jan 2022 10:12:18 +0100 Subject: fbdev: Hot-unplug firmware fb devices on forced removal Hot-unplug all firmware-framebuffer devices as part of removing them via remove_conflicting_framebuffers() et al. Releases all memory regions to be acquired by native drivers. Firmware, such as EFI, install a framebuffer while posting the computer. After removing the firmware-framebuffer device from fbdev, a native driver takes over the hardware and the firmware framebuffer becomes invalid. Firmware-framebuffer drivers, specifically simplefb, don't release their device from Linux' device hierarchy. It still owns the firmware framebuffer and blocks the native drivers from loading. This has been observed in the vmwgfx driver. [1] Initiating a device removal (i.e., hot unplug) as part of remove_conflicting_framebuffers() removes the underlying device and returns the memory range to the system. [1] https://lore.kernel.org/dri-devel/20220117180359.18114-1-zack@kde.org/ v2: * rename variable 'dev' to 'device' (Javier) Signed-off-by: Thomas Zimmermann Reported-by: Zack Rusin Reviewed-by: Javier Martinez Canillas Reviewed-by: Zack Rusin Reviewed-by: Hans de Goede CC: stable@vger.kernel.org # v5.11+ Link: https://patchwork.freedesktop.org/patch/msgid/20220125091222.21457-2-tzimmermann@suse.de --- drivers/video/fbdev/core/fbmem.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/video/fbdev/core/fbmem.c b/drivers/video/fbdev/core/fbmem.c index 826175ad88a2..45329dbfe617 100644 --- a/drivers/video/fbdev/core/fbmem.c +++ b/drivers/video/fbdev/core/fbmem.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -1557,18 +1558,36 @@ static void do_remove_conflicting_framebuffers(struct apertures_struct *a, /* check all firmware fbs and kick off if the base addr overlaps */ for_each_registered_fb(i) { struct apertures_struct *gen_aper; + struct device *device; if (!(registered_fb[i]->flags & FBINFO_MISC_FIRMWARE)) continue; gen_aper = registered_fb[i]->apertures; + device = registered_fb[i]->device; if (fb_do_apertures_overlap(gen_aper, a) || (primary && gen_aper && gen_aper->count && gen_aper->ranges[0].base == VGA_FB_PHYS)) { printk(KERN_INFO "fb%d: switching to %s from %s\n", i, name, registered_fb[i]->fix.id); - do_unregister_framebuffer(registered_fb[i]); + + /* + * If we kick-out a firmware driver, we also want to remove + * the underlying platform device, such as simple-framebuffer, + * VESA, EFI, etc. A native driver will then be able to + * allocate the memory range. + * + * If it's not a platform device, at least print a warning. A + * fix would add code to remove the device from the system. + */ + if (dev_is_platform(device)) { + registered_fb[i]->forced_out = true; + platform_device_unregister(to_platform_device(device)); + } else { + pr_warn("fb%d: cannot remove device\n", i); + do_unregister_framebuffer(registered_fb[i]); + } } } } @@ -1851,9 +1870,13 @@ EXPORT_SYMBOL(register_framebuffer); void unregister_framebuffer(struct fb_info *fb_info) { - mutex_lock(®istration_lock); + bool forced_out = fb_info->forced_out; + + if (!forced_out) + mutex_lock(®istration_lock); do_unregister_framebuffer(fb_info); - mutex_unlock(®istration_lock); + if (!forced_out) + mutex_unlock(®istration_lock); } EXPORT_SYMBOL(unregister_framebuffer); -- cgit From c96898342c3813fa7de6a47904dea46538873b3b Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Tue, 25 Jan 2022 10:12:19 +0100 Subject: drivers/firmware: Don't mark as busy the simple-framebuffer IO resource The sysfb_create_simplefb() function requests a IO memory resource for the simple-framebuffer platform device, but it also marks it as busy which can lead to drivers requesting the same memory resource to fail. Let's drop the IORESOURCE_BUSY flag and let drivers to request it as busy instead. Signed-off-by: Javier Martinez Canillas Signed-off-by: Thomas Zimmermann Reviewed-by: Zack Rusin Reviewed-by: Hans de Goede Link: https://patchwork.freedesktop.org/patch/msgid/20220125091222.21457-3-tzimmermann@suse.de --- drivers/firmware/sysfb_simplefb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/firmware/sysfb_simplefb.c b/drivers/firmware/sysfb_simplefb.c index b86761904949..179e9d0ef3e9 100644 --- a/drivers/firmware/sysfb_simplefb.c +++ b/drivers/firmware/sysfb_simplefb.c @@ -99,7 +99,7 @@ __init int sysfb_create_simplefb(const struct screen_info *si, /* setup IORESOURCE_MEM as framebuffer memory */ memset(&res, 0, sizeof(res)); - res.flags = IORESOURCE_MEM | IORESOURCE_BUSY; + res.flags = IORESOURCE_MEM; res.name = simplefb_resname; res.start = base; res.end = res.start + length - 1; -- cgit From 8ec6a72da65bbc4d341253e5a641a51bb1c0d967 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Tue, 25 Jan 2022 10:12:20 +0100 Subject: drm/simpledrm: Request memory region in driver Requesting the framebuffer memory in simpledrm marks the memory range as busy. This used to be done by the firmware sysfb code, but the driver is the correct place. v2: * use I/O memory if request_mem_region() fails (Jocelyn) Signed-off-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Reviewed-by: Jocelyn Falempe Reviewed-by: Hans de Goede Link: https://patchwork.freedesktop.org/patch/msgid/20220125091222.21457-4-tzimmermann@suse.de --- drivers/gpu/drm/tiny/simpledrm.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tiny/simpledrm.c b/drivers/gpu/drm/tiny/simpledrm.c index b977f5c94562..0b7a8be37a9f 100644 --- a/drivers/gpu/drm/tiny/simpledrm.c +++ b/drivers/gpu/drm/tiny/simpledrm.c @@ -526,21 +526,33 @@ static int simpledrm_device_init_mm(struct simpledrm_device *sdev) { struct drm_device *dev = &sdev->dev; struct platform_device *pdev = sdev->pdev; - struct resource *mem; + struct resource *res, *mem; void __iomem *screen_base; int ret; - mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!mem) + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) return -EINVAL; - ret = devm_aperture_acquire_from_firmware(dev, mem->start, resource_size(mem)); + ret = devm_aperture_acquire_from_firmware(dev, res->start, resource_size(res)); if (ret) { drm_err(dev, "could not acquire memory range %pr: error %d\n", - mem, ret); + res, ret); return ret; } + mem = devm_request_mem_region(&pdev->dev, res->start, resource_size(res), + sdev->dev.driver->name); + if (!mem) { + /* + * We cannot make this fatal. Sometimes this comes from magic + * spaces our resource handlers simply don't know about. Use + * the I/O-memory resource as-is and try to map that instead. + */ + drm_warn(dev, "could not acquire memory region %pr\n", res); + mem = res; + } + screen_base = devm_ioremap_wc(&pdev->dev, mem->start, resource_size(mem)); if (!screen_base) -- cgit From 748bd5873d1a6f61d1d5fc8bbd6dc20eb513528b Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Tue, 25 Jan 2022 10:12:21 +0100 Subject: fbdev/simplefb: Request memory region in driver Requesting the framebuffer memory in simpledrm marks the memory range as busy. This used to be done by the firmware sysfb code, but the driver is the correct place. v2: * store memory region in struct for later cleanup (Javier) Signed-off-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Reviewed-by: Hans de Goede Link: https://patchwork.freedesktop.org/patch/msgid/20220125091222.21457-5-tzimmermann@suse.de --- drivers/video/fbdev/simplefb.c | 65 +++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 20 deletions(-) (limited to 'drivers') diff --git a/drivers/video/fbdev/simplefb.c b/drivers/video/fbdev/simplefb.c index 57541887188b..94fc9c6d0411 100644 --- a/drivers/video/fbdev/simplefb.c +++ b/drivers/video/fbdev/simplefb.c @@ -66,16 +66,36 @@ static int simplefb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, return 0; } -struct simplefb_par; +struct simplefb_par { + u32 palette[PSEUDO_PALETTE_SIZE]; + struct resource *mem; +#if defined CONFIG_OF && defined CONFIG_COMMON_CLK + bool clks_enabled; + unsigned int clk_count; + struct clk **clks; +#endif +#if defined CONFIG_OF && defined CONFIG_REGULATOR + bool regulators_enabled; + u32 regulator_count; + struct regulator **regulators; +#endif +}; + static void simplefb_clocks_destroy(struct simplefb_par *par); static void simplefb_regulators_destroy(struct simplefb_par *par); static void simplefb_destroy(struct fb_info *info) { + struct simplefb_par *par = info->par; + struct resource *mem = par->mem; + simplefb_regulators_destroy(info->par); simplefb_clocks_destroy(info->par); if (info->screen_base) iounmap(info->screen_base); + + if (mem) + release_mem_region(mem->start, resource_size(mem)); } static const struct fb_ops simplefb_ops = { @@ -169,20 +189,6 @@ static int simplefb_parse_pd(struct platform_device *pdev, return 0; } -struct simplefb_par { - u32 palette[PSEUDO_PALETTE_SIZE]; -#if defined CONFIG_OF && defined CONFIG_COMMON_CLK - bool clks_enabled; - unsigned int clk_count; - struct clk **clks; -#endif -#if defined CONFIG_OF && defined CONFIG_REGULATOR - bool regulators_enabled; - u32 regulator_count; - struct regulator **regulators; -#endif -}; - #if defined CONFIG_OF && defined CONFIG_COMMON_CLK /* * Clock handling code. @@ -405,7 +411,7 @@ static int simplefb_probe(struct platform_device *pdev) struct simplefb_params params; struct fb_info *info; struct simplefb_par *par; - struct resource *mem; + struct resource *res, *mem; /* * Generic drivers must not be registered if a framebuffer exists. @@ -430,15 +436,28 @@ static int simplefb_probe(struct platform_device *pdev) if (ret) return ret; - mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!mem) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { dev_err(&pdev->dev, "No memory resource\n"); return -EINVAL; } + mem = request_mem_region(res->start, resource_size(res), "simplefb"); + if (!mem) { + /* + * We cannot make this fatal. Sometimes this comes from magic + * spaces our resource handlers simply don't know about. Use + * the I/O-memory resource as-is and try to map that instead. + */ + dev_warn(&pdev->dev, "simplefb: cannot reserve video memory at %pR\n", res); + mem = res; + } + info = framebuffer_alloc(sizeof(struct simplefb_par), &pdev->dev); - if (!info) - return -ENOMEM; + if (!info) { + ret = -ENOMEM; + goto error_release_mem_region; + } platform_set_drvdata(pdev, info); par = info->par; @@ -495,6 +514,9 @@ static int simplefb_probe(struct platform_device *pdev) info->var.xres, info->var.yres, info->var.bits_per_pixel, info->fix.line_length); + if (mem != res) + par->mem = mem; /* release in clean-up handler */ + ret = register_framebuffer(info); if (ret < 0) { dev_err(&pdev->dev, "Unable to register simplefb: %d\n", ret); @@ -513,6 +535,9 @@ error_unmap: iounmap(info->screen_base); error_fb_release: framebuffer_release(info); +error_release_mem_region: + if (mem != res) + release_mem_region(mem->start, resource_size(mem)); return ret; } -- cgit From 72f6c03336b8ee8f02086311444f94de99822b42 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 31 Dec 2021 17:00:56 +0100 Subject: drm: mxsfb: Shutdown the display on remove When the device is unbound from the driver, the display may be active. Make sure it gets shut down. Signed-off-by: Marek Vasut Cc: Daniel Vetter Cc: Laurent Pinchart Cc: Lucas Stach Cc: Sam Ravnborg Cc: Stefan Agner Cc: Thomas Zimmermann Reviewed-by: Laurent Pinchart Signed-off-by: Thomas Zimmermann Link: https://patchwork.freedesktop.org/patch/msgid/20211231160056.302276-1-marex@denx.de --- drivers/gpu/drm/mxsfb/mxsfb_drv.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/mxsfb/mxsfb_drv.c b/drivers/gpu/drm/mxsfb/mxsfb_drv.c index 86d78634a979..6d7a3aeff50b 100644 --- a/drivers/gpu/drm/mxsfb/mxsfb_drv.c +++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.c @@ -374,6 +374,7 @@ static int mxsfb_remove(struct platform_device *pdev) struct drm_device *drm = platform_get_drvdata(pdev); drm_dev_unregister(drm); + drm_atomic_helper_shutdown(drm); mxsfb_unload(drm); drm_dev_put(drm); -- cgit From 653af51ca6d8047403d6a1ab76928e12505779e3 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Sat, 1 Jan 2022 22:37:55 +0100 Subject: drm: mxsfb: Shutdown the display on system shutdown When the system shuts down or warm reboots, the display may be active. Implement the platform_driver .shutdown() operation and shut down the display. Signed-off-by: Marek Vasut Cc: Daniel Vetter Cc: Laurent Pinchart Cc: Lucas Stach Cc: Sam Ravnborg Cc: Stefan Agner Cc: Thomas Zimmermann Reviewed-by: Laurent Pinchart Signed-off-by: Thomas Zimmermann Link: https://patchwork.freedesktop.org/patch/msgid/20220101213755.506404-1-marex@denx.de --- drivers/gpu/drm/mxsfb/mxsfb_drv.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/mxsfb/mxsfb_drv.c b/drivers/gpu/drm/mxsfb/mxsfb_drv.c index 6d7a3aeff50b..375f26d4a417 100644 --- a/drivers/gpu/drm/mxsfb/mxsfb_drv.c +++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.c @@ -381,6 +381,13 @@ static int mxsfb_remove(struct platform_device *pdev) return 0; } +static void mxsfb_shutdown(struct platform_device *pdev) +{ + struct drm_device *drm = platform_get_drvdata(pdev); + + drm_atomic_helper_shutdown(drm); +} + #ifdef CONFIG_PM_SLEEP static int mxsfb_suspend(struct device *dev) { @@ -404,6 +411,7 @@ static const struct dev_pm_ops mxsfb_pm_ops = { static struct platform_driver mxsfb_platform_driver = { .probe = mxsfb_probe, .remove = mxsfb_remove, + .shutdown = mxsfb_shutdown, .driver = { .name = "mxsfb", .of_match_table = mxsfb_dt_ids, -- cgit From a5d092d37eb5d25520d283985082e977bda68eb7 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Wed, 22 Dec 2021 14:32:50 +0100 Subject: drm/panel: simple: add Multi-Inno Technology MI0700S4T-6 Add Multi-Inno Technology MI0700S4T-6 7" 800x480 DPI panel support. Signed-off-by: Marek Vasut Cc: Sam Ravnborg To: dri-devel@lists.freedesktop.org Signed-off-by: Thomas Zimmermann Link: https://patchwork.freedesktop.org/patch/msgid/20211222133250.6628-1-marex@denx.de --- drivers/gpu/drm/panel/panel-simple.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 9e46db5e359c..0c8786ebffd1 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -2525,6 +2525,36 @@ static const struct panel_desc mitsubishi_aa070mc01 = { .bus_flags = DRM_BUS_FLAG_DE_HIGH, }; +static const struct display_timing multi_inno_mi0700s4t_6_timing = { + .pixelclock = { 29000000, 33000000, 38000000 }, + .hactive = { 800, 800, 800 }, + .hfront_porch = { 180, 210, 240 }, + .hback_porch = { 16, 16, 16 }, + .hsync_len = { 30, 30, 30 }, + .vactive = { 480, 480, 480 }, + .vfront_porch = { 12, 22, 32 }, + .vback_porch = { 10, 10, 10 }, + .vsync_len = { 13, 13, 13 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE | + DISPLAY_FLAGS_SYNC_POSEDGE, +}; + +static const struct panel_desc multi_inno_mi0700s4t_6 = { + .timings = &multi_inno_mi0700s4t_6_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 154, + .height = 86, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE | + DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + static const struct display_timing multi_inno_mi1010ait_1cp_timing = { .pixelclock = { 68900000, 70000000, 73400000 }, .hactive = { 1280, 1280, 1280 }, @@ -3871,6 +3901,9 @@ static const struct of_device_id platform_of_match[] = { }, { .compatible = "mitsubishi,aa070mc01-ca1", .data = &mitsubishi_aa070mc01, + }, { + .compatible = "multi-inno,mi0700s4t-6", + .data = &multi_inno_mi0700s4t_6, }, { .compatible = "multi-inno,mi1010ait-1cp", .data = &multi_inno_mi1010ait_1cp, -- cgit From c36846f3917962a1f7586a3d39a423e6679df3d7 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 27 Jan 2022 11:51:15 +0300 Subject: drm/i915: delete shadow "ret" variable This "ret" declaration shadows an existing "ret" variable at the top of the function. Delete it. Signed-off-by: Dan Carpenter Reviewed-by: Rodrigo Vivi Signed-off-by: Rodrigo Vivi Link: https://patchwork.freedesktop.org/patch/msgid/20220127085115.GD25644@kili Fixes: f6c466b84cfa ("drm/i915: Add support for moving fence waiting") --- drivers/gpu/drm/i915/i915_vma.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/i915_vma.c b/drivers/gpu/drm/i915/i915_vma.c index cdf1f9066c4f..2a14a4e8b0bc 100644 --- a/drivers/gpu/drm/i915/i915_vma.c +++ b/drivers/gpu/drm/i915/i915_vma.c @@ -504,8 +504,6 @@ int i915_vma_bind(struct i915_vma *vma, work->pinned = i915_gem_object_get(vma->obj); } else { if (vma->obj) { - int ret; - ret = i915_gem_object_wait_moving_fence(vma->obj, true); if (ret) { i915_vma_resource_free(vma->resource); -- cgit From 512712a824de9b856a4e61343e3e4390eba2c391 Mon Sep 17 00:00:00 2001 From: Umesh Nerlige Ramappa Date: Mon, 24 Jan 2022 18:01:24 -0800 Subject: drm/i915/pmu: Fix KMD and GuC race on accessing busyness GuC updates shared memory and KMD reads it. Since this is not synchronized, we run into a race where the value read is inconsistent. Sometimes the inconsistency is in reading the upper MSB bytes of the last_switch_in value. 2 types of cases are seen - upper 8 bits are zero and upper 24 bits are zero. Since these are non-zero values, it is not trivial to determine validity of these values. Instead we read the values multiple times until they are consistent. In test runs, 3 attempts results in consistent values. The upper bound is set to 6 attempts and may need to be tuned as per any new occurences. Since the duration that gt is parked can vary, the patch also updates the gt timestamp on unpark before starting the worker. v2: - Initialize i - Use READ_ONCE to access engine record Fixes: 77cdd054dd2c ("drm/i915/pmu: Connect engine busyness stats from GuC to pmu") Signed-off-by: Umesh Nerlige Ramappa Reviewed-by: Alan Previn Signed-off-by: John Harrison Link: https://patchwork.freedesktop.org/patch/msgid/20220125020124.788679-2-umesh.nerlige.ramappa@intel.com --- drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c | 58 +++++++++++++++++++++-- 1 file changed, 54 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c index c3fa7f4b38db..9c0c2f433b9b 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c @@ -1113,6 +1113,19 @@ __extend_last_switch(struct intel_guc *guc, u64 *prev_start, u32 new_start) if (new_start == lower_32_bits(*prev_start)) return; + /* + * When gt is unparked, we update the gt timestamp and start the ping + * worker that updates the gt_stamp every POLL_TIME_CLKS. As long as gt + * is unparked, all switched in contexts will have a start time that is + * within +/- POLL_TIME_CLKS of the most recent gt_stamp. + * + * If neither gt_stamp nor new_start has rolled over, then the + * gt_stamp_hi does not need to be adjusted, however if one of them has + * rolled over, we need to adjust gt_stamp_hi accordingly. + * + * The below conditions address the cases of new_start rollover and + * gt_stamp_last rollover respectively. + */ if (new_start < gt_stamp_last && (new_start - gt_stamp_last) <= POLL_TIME_CLKS) gt_stamp_hi++; @@ -1124,17 +1137,45 @@ __extend_last_switch(struct intel_guc *guc, u64 *prev_start, u32 new_start) *prev_start = ((u64)gt_stamp_hi << 32) | new_start; } -static void guc_update_engine_gt_clks(struct intel_engine_cs *engine) +/* + * GuC updates shared memory and KMD reads it. Since this is not synchronized, + * we run into a race where the value read is inconsistent. Sometimes the + * inconsistency is in reading the upper MSB bytes of the last_in value when + * this race occurs. 2 types of cases are seen - upper 8 bits are zero and upper + * 24 bits are zero. Since these are non-zero values, it is non-trivial to + * determine validity of these values. Instead we read the values multiple times + * until they are consistent. In test runs, 3 attempts results in consistent + * values. The upper bound is set to 6 attempts and may need to be tuned as per + * any new occurences. + */ +static void __get_engine_usage_record(struct intel_engine_cs *engine, + u32 *last_in, u32 *id, u32 *total) { struct guc_engine_usage_record *rec = intel_guc_engine_usage(engine); + int i = 0; + + do { + *last_in = READ_ONCE(rec->last_switch_in_stamp); + *id = READ_ONCE(rec->current_context_index); + *total = READ_ONCE(rec->total_runtime); + + if (READ_ONCE(rec->last_switch_in_stamp) == *last_in && + READ_ONCE(rec->current_context_index) == *id && + READ_ONCE(rec->total_runtime) == *total) + break; + } while (++i < 6); +} + +static void guc_update_engine_gt_clks(struct intel_engine_cs *engine) +{ struct intel_engine_guc_stats *stats = &engine->stats.guc; struct intel_guc *guc = &engine->gt->uc.guc; - u32 last_switch = rec->last_switch_in_stamp; - u32 ctx_id = rec->current_context_index; - u32 total = rec->total_runtime; + u32 last_switch, ctx_id, total; lockdep_assert_held(&guc->timestamp.lock); + __get_engine_usage_record(engine, &last_switch, &ctx_id, &total); + stats->running = ctx_id != ~0U && last_switch; if (stats->running) __extend_last_switch(guc, &stats->start_gt_clk, last_switch); @@ -1236,6 +1277,10 @@ static ktime_t guc_engine_busyness(struct intel_engine_cs *engine, ktime_t *now) if (!in_reset && intel_gt_pm_get_if_awake(gt)) { stats_saved = *stats; gt_stamp_saved = guc->timestamp.gt_stamp; + /* + * Update gt_clks, then gt timestamp to simplify the 'gt_stamp - + * start_gt_clk' calculation below for active engines. + */ guc_update_engine_gt_clks(engine); guc_update_pm_timestamp(guc, now); intel_gt_pm_put_async(gt); @@ -1364,10 +1409,15 @@ void intel_guc_busyness_park(struct intel_gt *gt) void intel_guc_busyness_unpark(struct intel_gt *gt) { struct intel_guc *guc = >->uc.guc; + unsigned long flags; + ktime_t unused; if (!guc_submission_initialized(guc)) return; + spin_lock_irqsave(&guc->timestamp.lock, flags); + guc_update_pm_timestamp(guc, &unused); + spin_unlock_irqrestore(&guc->timestamp.lock, flags); mod_delayed_work(system_highpri_wq, &guc->timestamp.work, guc->timestamp.ping_delay); } -- cgit From bc1922e5d349db4be14c55513102c024c2ae8a50 Mon Sep 17 00:00:00 2001 From: Thomas Hellström Date: Thu, 27 Jan 2022 12:56:22 +0100 Subject: drm/i915: Fix a race between vma / object destruction and unbinding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The vma destruction code was using an unlocked advisory check for drm_mm_node_allocated() to avoid racing with eviction code unbinding the vma. This is very fragile and prohibits the dereference of non-refcounted pointers of dying vmas after a call to __i915_vma_unbind(). It also prohibits the dereference of vma->obj of refcounted pointers of dying vmas after a call to __i915_vma_unbind(), since even if a refcount is held on the vma, that won't guarantee that its backing object doesn't get destroyed. So introduce an unbind under the vm mutex at object destroy time, removing all weak references of the vma and its object from the object vma list and from the vm bound list. Signed-off-by: Thomas Hellström Reviewed-by: Maarten Lankhorst Link: https://patchwork.freedesktop.org/patch/msgid/20220127115622.302970-1-thomas.hellstrom@linux.intel.com --- drivers/gpu/drm/i915/gem/i915_gem_object.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gem/i915_gem_object.c b/drivers/gpu/drm/i915/gem/i915_gem_object.c index 1a9e1f940a7d..e03e362d320b 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_object.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_object.c @@ -280,6 +280,12 @@ void __i915_gem_object_pages_fini(struct drm_i915_gem_object *obj) GEM_BUG_ON(vma->obj != obj); spin_unlock(&obj->vma.lock); + /* Verify that the vma is unbound under the vm mutex. */ + mutex_lock(&vma->vm->mutex); + atomic_and(~I915_VMA_PIN_MASK, &vma->flags); + __i915_vma_unbind(vma); + mutex_unlock(&vma->vm->mutex); + __i915_vma_put(vma); spin_lock(&obj->vma.lock); -- cgit From 7a05c5a0c883ed6353f82699dff8d789dae6b673 Mon Sep 17 00:00:00 2001 From: Maarten Lankhorst Date: Wed, 26 Jan 2022 08:37:03 +0100 Subject: drm/i915: Lock dpt_obj around set_cache_level, v2. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit set_cache_level may unbind the object, which will result in the below lockdep splat: <6> [184.578145] [IGT] kms_addfb_basic: starting subtest addfb25-framebuffer-vs-set-tiling <4> [184.578220] ------------[ cut here ]------------ <4> [184.578221] WARN_ON(debug_locks && !(lock_is_held(&(&((obj)->base.resv)->lock.base)->dep_map) != 0)) <4> [184.578237] WARNING: CPU: 6 PID: 5544 at drivers/gpu/drm/i915/i915_gem.c:123 i915_gem_object_unbind+0x4a9/0x510 [i915] <4> [184.578323] Modules linked in: vgem drm_shmem_helper snd_hda_codec_hdmi i915 mei_hdcp x86_pkg_temp_thermal snd_hda_intel coretemp crct10dif_pclmul snd_intel_dspcfg crc32_pclmul ttm snd_hda_codec ghash_clmulni_intel snd_hwdep drm_kms_helper snd_hda_core e1000e mei_me syscopyarea ptp snd_pcm sysfillrect mei pps_core sysimgblt fb_sys_fops prime_numbers intel_lpss_pci smsc75xx usbnet mii <4> [184.578349] CPU: 6 PID: 5544 Comm: kms_addfb_basic Not tainted 5.16.0-CI-Patchwork_22006+ #1 <4> [184.578351] Hardware name: Intel Corporation Alder Lake Client Platform/AlderLake-P DDR4 RVP, BIOS ADLPFWI1.R00.2422.A00.2110131104 10/13/2021 <4> [184.578352] RIP: 0010:i915_gem_object_unbind+0x4a9/0x510 [i915] <4> [184.578424] Code: 00 be ff ff ff ff 48 8d 78 68 e8 a2 6e 2b e1 85 c0 0f 85 b1 fb ff ff 48 c7 c6 48 37 9e a0 48 c7 c7 d9 fc a1 a0 e8 a3 54 26 e1 <0f> 0b e9 97 fb ff ff 31 ed 48 8b 5c 24 58 65 48 33 1c 25 28 00 00 <4> [184.578426] RSP: 0018:ffffc900013b3b68 EFLAGS: 00010286 <4> [184.578428] RAX: 0000000000000000 RBX: ffffc900013b3bb0 RCX: 0000000000000001 <4> [184.578429] RDX: 0000000080000001 RSI: ffffffff8230b42d RDI: 00000000ffffffff <4> [184.578430] RBP: ffff888120e10000 R08: 0000000000000000 R09: c0000000ffff7fff <4> [184.578431] R10: 0000000000000001 R11: ffffc900013b3980 R12: ffff8881176ea740 <4> [184.578432] R13: ffff888120e10000 R14: 0000000000000000 R15: 0000000000000001 <4> [184.578433] FS: 00007f65074f5e40(0000) GS:ffff88888f300000(0000) knlGS:0000000000000000 <4> [184.578435] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 <4> [184.578436] CR2: 00007fff4420ede8 CR3: 000000010c2f2005 CR4: 0000000000770ee0 <4> [184.578437] PKRU: 55555554 <4> [184.578438] Call Trace: <4> [184.578439] <4> [184.578440] ? dma_resv_iter_first_unlocked+0x78/0xf0 <4> [184.578447] intel_dpt_create+0x88/0x220 [i915] <4> [184.578530] intel_framebuffer_init+0x5b8/0x620 [i915] <4> [184.578612] intel_framebuffer_create+0x3d/0x60 [i915] <4> [184.578691] intel_user_framebuffer_create+0x18f/0x2c0 [i915] <4> [184.578775] drm_internal_framebuffer_create+0x36d/0x4c0 <4> [184.578779] drm_mode_addfb2+0x2f/0xd0 <4> [184.578781] ? drm_mode_addfb_ioctl+0x10/0x10 <4> [184.578784] drm_ioctl_kernel+0xac/0x140 <4> [184.578787] drm_ioctl+0x201/0x3d0 <4> [184.578789] ? drm_mode_addfb_ioctl+0x10/0x10 <4> [184.578796] __x64_sys_ioctl+0x6a/0xa0 <4> [184.578800] do_syscall_64+0x37/0xb0 <4> [184.578803] entry_SYSCALL_64_after_hwframe+0x44/0xae <4> [184.578805] RIP: 0033:0x7f6506736317 <4> [184.578807] Code: b3 66 90 48 8b 05 71 4b 2d 00 64 c7 00 26 00 00 00 48 c7 c0 ff ff ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 b8 10 00 00 00 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d 41 4b 2d 00 f7 d8 64 89 01 48 <4> [184.578808] RSP: 002b:00007fff44211a98 EFLAGS: 00000246 ORIG_RAX: 0000000000000010 <4> [184.578810] RAX: ffffffffffffffda RBX: 0000000000000006 RCX: 00007f6506736317 <4> [184.578811] RDX: 00007fff44211b30 RSI: 00000000c06864b8 RDI: 0000000000000006 <4> [184.578812] RBP: 00007fff44211b30 R08: 00007fff44311170 R09: 0000000000000000 <4> [184.578813] R10: 0000000000000008 R11: 0000000000000246 R12: 00000000c06864b8 <4> [184.578813] R13: 0000000000000006 R14: 0000000000000000 R15: 0000000000000000 <4> [184.578819] <4> [184.578820] irq event stamp: 47931 <4> [184.578821] hardirqs last enabled at (47937): [] __up_console_sem+0x62/0x70 <4> [184.578824] hardirqs last disabled at (47942): [] __up_console_sem+0x47/0x70 <4> [184.578826] softirqs last enabled at (47340): [] __do_softirq+0x32d/0x493 <4> [184.578828] softirqs last disabled at (47335): [] irq_exit_rcu+0xa6/0xe0 <4> [184.578830] ---[ end trace f17ec219f892c7d4 ]--- Changes since v1: - Fix intel_pin_fb_obj_dpt too. Fixes: 0f341974cbc2 ("drm/i915: Add i915_vma_unbind_unlocked, and take obj lock for i915_vma_unbind, v2.") Signed-off-by: Maarten Lankhorst Testcase: kms_addfb_basic Link: https://patchwork.freedesktop.org/patch/msgid/20220126073703.1215696-1-maarten.lankhorst@linux.intel.com Reviewed-by: Thomas Hellström --- drivers/gpu/drm/i915/display/intel_dpt.c | 6 +++++- drivers/gpu/drm/i915/display/intel_fb_pin.c | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/display/intel_dpt.c b/drivers/gpu/drm/i915/display/intel_dpt.c index 63a83d5f85a1..c2f8f853db90 100644 --- a/drivers/gpu/drm/i915/display/intel_dpt.c +++ b/drivers/gpu/drm/i915/display/intel_dpt.c @@ -253,7 +253,11 @@ intel_dpt_create(struct intel_framebuffer *fb) if (IS_ERR(dpt_obj)) return ERR_CAST(dpt_obj); - ret = i915_gem_object_set_cache_level(dpt_obj, I915_CACHE_NONE); + ret = i915_gem_object_lock_interruptible(dpt_obj, NULL); + if (!ret) { + ret = i915_gem_object_set_cache_level(dpt_obj, I915_CACHE_NONE); + i915_gem_object_unlock(dpt_obj); + } if (ret) { i915_gem_object_put(dpt_obj); return ERR_PTR(ret); diff --git a/drivers/gpu/drm/i915/display/intel_fb_pin.c b/drivers/gpu/drm/i915/display/intel_fb_pin.c index 9c555f6d1958..c4b3d76341f3 100644 --- a/drivers/gpu/drm/i915/display/intel_fb_pin.c +++ b/drivers/gpu/drm/i915/display/intel_fb_pin.c @@ -36,7 +36,11 @@ intel_pin_fb_obj_dpt(struct drm_framebuffer *fb, atomic_inc(&dev_priv->gpu_error.pending_fb_pin); - ret = i915_gem_object_set_cache_level(obj, I915_CACHE_NONE); + ret = i915_gem_object_lock_interruptible(obj, NULL); + if (!ret) { + ret = i915_gem_object_set_cache_level(obj, I915_CACHE_NONE); + i915_gem_object_unlock(obj); + } if (ret) { vma = ERR_PTR(ret); goto err; -- cgit From a594525c82e0b8d677a7e5fd13c7c115d41e9722 Mon Sep 17 00:00:00 2001 From: Maarten Lankhorst Date: Fri, 28 Jan 2022 09:57:39 +0100 Subject: drm/i915: Allow dead vm to unbind vma's without lock. i915_gem_vm_close may take the lock, and we currently have no better way of handling this. At least for now, allow a path in which holding vm->mutex is sufficient. This is the case, because the object destroy path will forcefully take vm->mutex now. Signed-off-by: Maarten Lankhorst Link: https://patchwork.freedesktop.org/patch/msgid/20220128085739.1464568-1-maarten.lankhorst@linux.intel.com Reviewed-by: Thomas Hellstrom --- drivers/gpu/drm/i915/i915_vma.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/i915_vma.c b/drivers/gpu/drm/i915/i915_vma.c index 2a14a4e8b0bc..22cdc55c4863 100644 --- a/drivers/gpu/drm/i915/i915_vma.c +++ b/drivers/gpu/drm/i915/i915_vma.c @@ -39,6 +39,17 @@ #include "i915_vma.h" #include "i915_vma_resource.h" +static inline void assert_vma_held_evict(const struct i915_vma *vma) +{ + /* + * We may be forced to unbind when the vm is dead, to clean it up. + * This is the only exception to the requirement of the object lock + * being held. + */ + if (atomic_read(&vma->vm->open)) + assert_object_held_shared(vma->obj); +} + static struct kmem_cache *slab_vmas; static struct i915_vma *i915_vma_alloc(void) @@ -1721,7 +1732,7 @@ struct dma_fence *__i915_vma_evict(struct i915_vma *vma, bool async) struct dma_fence *unbind_fence; GEM_BUG_ON(i915_vma_is_pinned(vma)); - assert_object_held_shared(vma->obj); + assert_vma_held_evict(vma); if (i915_vma_is_map_and_fenceable(vma)) { /* Force a pagefault for domain tracking on next user access */ @@ -1788,7 +1799,7 @@ int __i915_vma_unbind(struct i915_vma *vma) int ret; lockdep_assert_held(&vma->vm->mutex); - assert_object_held_shared(vma->obj); + assert_vma_held_evict(vma); if (!drm_mm_node_allocated(&vma->node)) return 0; -- cgit From 270677026261ad8d1ea8ed60c3327d37aeb5f372 Mon Sep 17 00:00:00 2001 From: Matt Roper Date: Thu, 27 Jan 2022 11:48:55 -0800 Subject: drm/i915/dg2: Add Wa_14015227452 Note that the bspec doesn't list the bit we're programming here (bit 11) as being present on DG2, but we've confirmed with the hardware team that this is a documentation mistake and the bit does indeed exist on all Xe_HP-based platforms. Signed-off-by: Matt Roper Link: https://patchwork.freedesktop.org/patch/msgid/20220127194855.3963296-1-matthew.d.roper@intel.com Reviewed-by: Swathi Dhanavanthri --- drivers/gpu/drm/i915/gt/intel_workarounds.c | 5 +++++ drivers/gpu/drm/i915/i915_reg.h | 1 + 2 files changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/intel_workarounds.c b/drivers/gpu/drm/i915/gt/intel_workarounds.c index 930da3b4e0eb..379311aad020 100644 --- a/drivers/gpu/drm/i915/gt/intel_workarounds.c +++ b/drivers/gpu/drm/i915/gt/intel_workarounds.c @@ -2044,6 +2044,11 @@ rcs_engine_wa_init(struct intel_engine_cs *engine, struct i915_wa_list *wal) { struct drm_i915_private *i915 = engine->i915; + if (IS_DG2(engine->i915)) { + /* Wa_14015227452:dg2 */ + wa_masked_en(wal, GEN9_ROW_CHICKEN4, XEHP_DIS_BBL_SYSPIPE); + } + if (IS_DG2_GRAPHICS_STEP(engine->i915, G11, STEP_A0, STEP_B0)) { /* Wa_14013392000:dg2_g11 */ wa_masked_en(wal, GEN7_ROW_CHICKEN2, GEN12_ENABLE_LARGE_GRF_MODE); diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h index bcbb163dc99d..be096a46ce72 100644 --- a/drivers/gpu/drm/i915/i915_reg.h +++ b/drivers/gpu/drm/i915/i915_reg.h @@ -9762,6 +9762,7 @@ enum { #define GEN9_ROW_CHICKEN4 _MMIO(0xe48c) #define GEN12_DISABLE_GRF_CLEAR REG_BIT(13) +#define XEHP_DIS_BBL_SYSPIPE REG_BIT(11) #define GEN12_DISABLE_TDL_PUSH REG_BIT(9) #define GEN11_DIS_PICK_2ND_EU REG_BIT(7) #define GEN12_DISABLE_HDR_PAST_PAYLOAD_HOLD_FIX REG_BIT(4) -- cgit From ef6e871af3126850b429f68e8bd5b657042139f0 Mon Sep 17 00:00:00 2001 From: Matt Roper Date: Fri, 28 Jan 2022 09:01:25 -0800 Subject: drm/i915/dg2: s/engine->i915/i915/ for engine workarounds rcs_engine_wa_init() has a local 'i915' variable; we should use that rather than 'engine->i915' for consistency with how we handle other platforms. Suggested-by: Tvrtko Ursulin Signed-off-by: Matt Roper Reviewed-by: Tvrtko Ursulin Link: https://patchwork.freedesktop.org/patch/msgid/20220128170125.4121819-1-matthew.d.roper@intel.com --- drivers/gpu/drm/i915/gt/intel_workarounds.c | 30 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/intel_workarounds.c b/drivers/gpu/drm/i915/gt/intel_workarounds.c index 379311aad020..e18ed63b7b5f 100644 --- a/drivers/gpu/drm/i915/gt/intel_workarounds.c +++ b/drivers/gpu/drm/i915/gt/intel_workarounds.c @@ -2044,12 +2044,12 @@ rcs_engine_wa_init(struct intel_engine_cs *engine, struct i915_wa_list *wal) { struct drm_i915_private *i915 = engine->i915; - if (IS_DG2(engine->i915)) { + if (IS_DG2(i915)) { /* Wa_14015227452:dg2 */ wa_masked_en(wal, GEN9_ROW_CHICKEN4, XEHP_DIS_BBL_SYSPIPE); } - if (IS_DG2_GRAPHICS_STEP(engine->i915, G11, STEP_A0, STEP_B0)) { + if (IS_DG2_GRAPHICS_STEP(i915, G11, STEP_A0, STEP_B0)) { /* Wa_14013392000:dg2_g11 */ wa_masked_en(wal, GEN7_ROW_CHICKEN2, GEN12_ENABLE_LARGE_GRF_MODE); @@ -2057,15 +2057,15 @@ rcs_engine_wa_init(struct intel_engine_cs *engine, struct i915_wa_list *wal) wa_write_or(wal, LSC_CHICKEN_BIT_0_UDW, DIS_CHAIN_2XSIMD8); } - if (IS_DG2_GRAPHICS_STEP(engine->i915, G10, STEP_A0, STEP_B0) || - IS_DG2_GRAPHICS_STEP(engine->i915, G11, STEP_A0, STEP_B0)) { + if (IS_DG2_GRAPHICS_STEP(i915, G10, STEP_A0, STEP_B0) || + IS_DG2_GRAPHICS_STEP(i915, G11, STEP_A0, STEP_B0)) { /* Wa_14012419201:dg2 */ wa_masked_en(wal, GEN9_ROW_CHICKEN4, GEN12_DISABLE_HDR_PAST_PAYLOAD_HOLD_FIX); } - if (IS_DG2_GRAPHICS_STEP(engine->i915, G10, STEP_B0, STEP_C0) || - IS_DG2_G11(engine->i915)) { + if (IS_DG2_GRAPHICS_STEP(i915, G10, STEP_B0, STEP_C0) || + IS_DG2_G11(i915)) { /* * Wa_22012826095:dg2 * Wa_22013059131:dg2 @@ -2080,14 +2080,14 @@ rcs_engine_wa_init(struct intel_engine_cs *engine, struct i915_wa_list *wal) } /* Wa_1308578152:dg2_g10 when first gslice is fused off */ - if (IS_DG2_GRAPHICS_STEP(engine->i915, G10, STEP_B0, STEP_C0) && + if (IS_DG2_GRAPHICS_STEP(i915, G10, STEP_B0, STEP_C0) && needs_wa_1308578152(engine)) { wa_masked_dis(wal, GEN12_CS_DEBUG_MODE1_CCCSUNIT_BE_COMMON, GEN12_REPLAY_MODE_GRANULARITY); } - if (IS_DG2_GRAPHICS_STEP(engine->i915, G10, STEP_B0, STEP_FOREVER) || - IS_DG2_G11(engine->i915)) { + if (IS_DG2_GRAPHICS_STEP(i915, G10, STEP_B0, STEP_FOREVER) || + IS_DG2_G11(i915)) { /* Wa_22013037850:dg2 */ wa_write_or(wal, LSC_CHICKEN_BIT_0_UDW, DISABLE_128B_EVICTION_COMMAND_UDW); @@ -2104,7 +2104,7 @@ rcs_engine_wa_init(struct intel_engine_cs *engine, struct i915_wa_list *wal) LSC_L1_FLUSH_CTL_3D_DATAPORT_FLUSH_EVENTS_MASK); } - if (IS_DG2_GRAPHICS_STEP(engine->i915, G10, STEP_A0, STEP_B0)) { + if (IS_DG2_GRAPHICS_STEP(i915, G10, STEP_A0, STEP_B0)) { /* * Wa_1608949956:dg2_g10 * Wa_14010198302:dg2_g10 @@ -2123,7 +2123,7 @@ rcs_engine_wa_init(struct intel_engine_cs *engine, struct i915_wa_list *wal) 0, false); } - if (IS_DG2_GRAPHICS_STEP(engine->i915, G10, STEP_A0, STEP_B0)) { + if (IS_DG2_GRAPHICS_STEP(i915, G10, STEP_A0, STEP_B0)) { /* Wa_22010430635:dg2 */ wa_masked_en(wal, GEN9_ROW_CHICKEN4, @@ -2133,8 +2133,8 @@ rcs_engine_wa_init(struct intel_engine_cs *engine, struct i915_wa_list *wal) wa_write_or(wal, XEHP_L3NODEARBCFG, XEHP_LNESPARE); } - if (IS_DG2_GRAPHICS_STEP(engine->i915, G10, STEP_A0, STEP_C0) || - IS_DG2_G11(engine->i915)) { + if (IS_DG2_GRAPHICS_STEP(i915, G10, STEP_A0, STEP_C0) || + IS_DG2_G11(i915)) { /* Wa_22012654132:dg2 */ wa_add(wal, GEN10_CACHE_MODE_SS, 0, _MASKED_BIT_ENABLE(ENABLE_PREFETCH_INTO_IC), @@ -2143,8 +2143,8 @@ rcs_engine_wa_init(struct intel_engine_cs *engine, struct i915_wa_list *wal) } /* Wa_14013202645:dg2 */ - if (IS_DG2_GRAPHICS_STEP(engine->i915, G10, STEP_B0, STEP_C0) || - IS_DG2_GRAPHICS_STEP(engine->i915, G11, STEP_A0, STEP_B0)) + if (IS_DG2_GRAPHICS_STEP(i915, G10, STEP_B0, STEP_C0) || + IS_DG2_GRAPHICS_STEP(i915, G11, STEP_A0, STEP_B0)) wa_write_or(wal, RT_CTRL, DIS_NULL_QUERY); if (IS_DG1_GRAPHICS_STEP(i915, STEP_A0, STEP_B0) || -- cgit From 29b9702ffe70d83b9970abbccaeb287dfda4409f Mon Sep 17 00:00:00 2001 From: Thomas Hellström Date: Tue, 1 Feb 2022 08:03:40 +0100 Subject: drm/i915/ttm: Return some errors instead of trying memcpy move MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The i915_ttm_accel_move() function may return error codes that should be propagated further up the stack rather than consumed assuming that the accel move failed and could be replaced with a memcpy move. For -EINTR, -ERESTARTSYS and -EAGAIN, just propagate those codes, rather than retrying with a memcpy move. Fixes: 2b0a750caf33 ("drm/i915/ttm: Failsafe migration blits") Cc: Matthew Auld Signed-off-by: Thomas Hellström Reviewed-by: Matthew Auld Link: https://patchwork.freedesktop.org/patch/msgid/20220201070340.16457-1-thomas.hellstrom@linux.intel.com --- drivers/gpu/drm/i915/gem/i915_gem_ttm_move.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gem/i915_gem_ttm_move.c b/drivers/gpu/drm/i915/gem/i915_gem_ttm_move.c index 1de306c03aaf..1ebe6e4086a1 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_ttm_move.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_ttm_move.c @@ -436,11 +436,17 @@ __i915_ttm_move(struct ttm_buffer_object *bo, if (!IS_ERR(fence)) goto out; - } else if (move_deps) { - int err = i915_deps_sync(move_deps, ctx); + } else { + int err = PTR_ERR(fence); + + if (err == -EINTR || err == -ERESTARTSYS || err == -EAGAIN) + return fence; - if (err) - return ERR_PTR(err); + if (move_deps) { + err = i915_deps_sync(move_deps, ctx); + if (err) + return ERR_PTR(err); + } } /* Error intercept failed or no accelerated migration to start with */ -- cgit From 86df4141869350edaa53fb994b3db2c2cca5065d Mon Sep 17 00:00:00 2001 From: Matt Roper Date: Thu, 20 Jan 2022 15:50:16 -0800 Subject: drm/i915: Introduce G12 subplatform of DG2 Another fork of the DG2 design has appeared, known as "DG2-G12;" let's add it as a new subplatform. As with G11, the GT stepping resets back to A0 (so a DG2-G12 A0 is similar, but not identical, to a DG2-G10 C0) but the display steppings continue to use the same numbering scheme as G10 and G11. Some existing DG2 workarounds are starting to be extended to the DG2-G12 subplatform. So far only workarounds that were "permanent" for both DG2-G10 and DG2-G11 have been tagged for DG2-G12, but more stepping-specific workarounds are likely to show up in the future. Bspec: 44477 Signed-off-by: Matt Roper Reviewed-by: Radhakrishna Sripada Link: https://patchwork.freedesktop.org/patch/msgid/20220120235016.1209326-1-matthew.d.roper@intel.com --- drivers/gpu/drm/i915/gt/intel_workarounds.c | 2 +- drivers/gpu/drm/i915/i915_drv.h | 19 +++++++++++-------- drivers/gpu/drm/i915/intel_device_info.h | 3 ++- drivers/gpu/drm/i915/intel_step.c | 7 +++++++ 4 files changed, 21 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/intel_workarounds.c b/drivers/gpu/drm/i915/gt/intel_workarounds.c index e18ed63b7b5f..77ac294acc9d 100644 --- a/drivers/gpu/drm/i915/gt/intel_workarounds.c +++ b/drivers/gpu/drm/i915/gt/intel_workarounds.c @@ -2087,7 +2087,7 @@ rcs_engine_wa_init(struct intel_engine_cs *engine, struct i915_wa_list *wal) } if (IS_DG2_GRAPHICS_STEP(i915, G10, STEP_B0, STEP_FOREVER) || - IS_DG2_G11(i915)) { + IS_DG2_G11(i915) || IS_DG2_G12(i915)) { /* Wa_22013037850:dg2 */ wa_write_or(wal, LSC_CHICKEN_BIT_0_UDW, DISABLE_128B_EVICTION_COMMAND_UDW); diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index 28374fd5e0a1..9b48dd9ccacd 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -1284,6 +1284,8 @@ IS_SUBPLATFORM(const struct drm_i915_private *i915, IS_SUBPLATFORM(dev_priv, INTEL_DG2, INTEL_SUBPLATFORM_G10) #define IS_DG2_G11(dev_priv) \ IS_SUBPLATFORM(dev_priv, INTEL_DG2, INTEL_SUBPLATFORM_G11) +#define IS_DG2_G12(dev_priv) \ + IS_SUBPLATFORM(dev_priv, INTEL_DG2, INTEL_SUBPLATFORM_G12) #define IS_ADLS_RPLS(dev_priv) \ IS_SUBPLATFORM(dev_priv, INTEL_ALDERLAKE_S, INTEL_SUBPLATFORM_RPL_S) #define IS_HSW_EARLY_SDV(dev_priv) (IS_HASWELL(dev_priv) && \ @@ -1398,16 +1400,17 @@ IS_SUBPLATFORM(const struct drm_i915_private *i915, (IS_XEHPSDV(__i915) && IS_GRAPHICS_STEP(__i915, since, until)) /* - * DG2 hardware steppings are a bit unusual. The hardware design was forked - * to create two variants (G10 and G11) which have distinct workaround sets. - * The G11 fork of the DG2 design resets the GT stepping back to "A0" for its - * first iteration, even though it's more similar to a G10 B0 stepping in terms - * of functionality and workarounds. However the display stepping does not - * reset in the same manner --- a specific stepping like "B0" has a consistent - * meaning regardless of whether it belongs to a G10 or G11 DG2. + * DG2 hardware steppings are a bit unusual. The hardware design was forked to + * create three variants (G10, G11, and G12) which each have distinct + * workaround sets. The G11 and G12 forks of the DG2 design reset the GT + * stepping back to "A0" for their first iterations, even though they're more + * similar to a G10 B0 stepping and G10 C0 stepping respectively in terms of + * functionality and workarounds. However the display stepping does not reset + * in the same manner --- a specific stepping like "B0" has a consistent + * meaning regardless of whether it belongs to a G10, G11, or G12 DG2. * * TLDR: All GT workarounds and stepping-specific logic must be applied in - * relation to a specific subplatform (G10 or G11), whereas display workarounds + * relation to a specific subplatform (G10/G11/G12), whereas display workarounds * and stepping-specific logic will be applied with a general DG2-wide stepping * number. */ diff --git a/drivers/gpu/drm/i915/intel_device_info.h b/drivers/gpu/drm/i915/intel_device_info.h index 8b994ddc374e..09ce63c8f78f 100644 --- a/drivers/gpu/drm/i915/intel_device_info.h +++ b/drivers/gpu/drm/i915/intel_device_info.h @@ -96,7 +96,7 @@ enum intel_platform { * it is fine for the same bit to be used on multiple parent platforms. */ -#define INTEL_SUBPLATFORM_BITS (2) +#define INTEL_SUBPLATFORM_BITS (3) #define INTEL_SUBPLATFORM_MASK (BIT(INTEL_SUBPLATFORM_BITS) - 1) /* HSW/BDW/SKL/KBL/CFL */ @@ -109,6 +109,7 @@ enum intel_platform { /* DG2 */ #define INTEL_SUBPLATFORM_G10 0 #define INTEL_SUBPLATFORM_G11 1 +#define INTEL_SUBPLATFORM_G12 2 /* ADL-S */ #define INTEL_SUBPLATFORM_RPL_S 0 diff --git a/drivers/gpu/drm/i915/intel_step.c b/drivers/gpu/drm/i915/intel_step.c index 453c04472545..ac1a796b2808 100644 --- a/drivers/gpu/drm/i915/intel_step.c +++ b/drivers/gpu/drm/i915/intel_step.c @@ -122,6 +122,10 @@ static const struct intel_step_info dg2_g11_revid_step_tbl[] = { [0x5] = { COMMON_GT_MEDIA_STEP(B1), .display_step = STEP_C0 }, }; +static const struct intel_step_info dg2_g12_revid_step_tbl[] = { + [0x0] = { COMMON_GT_MEDIA_STEP(A0), .display_step = STEP_C0 }, +}; + static const struct intel_step_info adls_rpls_revids[] = { [0x4] = { COMMON_GT_MEDIA_STEP(D0), .display_step = STEP_D0 }, [0xC] = { COMMON_GT_MEDIA_STEP(D0), .display_step = STEP_C0 }, @@ -140,6 +144,9 @@ void intel_step_init(struct drm_i915_private *i915) } else if (IS_DG2_G11(i915)) { revids = dg2_g11_revid_step_tbl; size = ARRAY_SIZE(dg2_g11_revid_step_tbl); + } else if (IS_DG2_G12(i915)) { + revids = dg2_g12_revid_step_tbl; + size = ARRAY_SIZE(dg2_g12_revid_step_tbl); } else if (IS_XEHPSDV(i915)) { revids = xehpsdv_revids; size = ARRAY_SIZE(xehpsdv_revids); -- cgit From f4044ca1967098b44bd2d569ddb99352b599551e Mon Sep 17 00:00:00 2001 From: Lucas De Marchi Date: Mon, 7 Feb 2022 23:01:40 -0800 Subject: drm/i915/guc: Prepare for error propagation Currently guc_mmio_reg_add() relies on having enough memory available in the array to add a new slot. It uses `GEM_BUG_ON(count >= regset->size);` to protect going above the threshold. In order to allow guc_mmio_reg_add() to handle the memory allocation by itself, it must return an error in case of failures. Adjust return code so this error can be propagated to the callers of guc_mmio_reg_add() and guc_mmio_regset_init(). No intended change in behavior. Cc: Matt Roper Cc: John Harrison Cc: Matthew Brost Cc: Daniele Ceraolo Spurio Signed-off-by: Lucas De Marchi Reviewed-by: Daniele Ceraolo Spurio Link: https://patchwork.freedesktop.org/patch/msgid/20220208070141.2095177-2-lucas.demarchi@intel.com --- drivers/gpu/drm/i915/gt/uc/intel_guc_ads.c | 31 +++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_ads.c b/drivers/gpu/drm/i915/gt/uc/intel_guc_ads.c index aa767540ca2a..f163c929a6b3 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_ads.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_ads.c @@ -224,8 +224,8 @@ static int guc_mmio_reg_cmp(const void *a, const void *b) return (int)ra->offset - (int)rb->offset; } -static void guc_mmio_reg_add(struct temp_regset *regset, - u32 offset, u32 flags) +static long __must_check guc_mmio_reg_add(struct temp_regset *regset, + u32 offset, u32 flags) { u32 count = regset->used; struct guc_mmio_reg reg = { @@ -244,7 +244,7 @@ static void guc_mmio_reg_add(struct temp_regset *regset, */ if (bsearch(®, regset->registers, count, sizeof(reg), guc_mmio_reg_cmp)) - return; + return 0; slot = ®set->registers[count]; regset->used++; @@ -257,6 +257,8 @@ static void guc_mmio_reg_add(struct temp_regset *regset, swap(slot[1], slot[0]); } + + return 0; } #define GUC_MMIO_REG_ADD(regset, reg, masked) \ @@ -264,32 +266,35 @@ static void guc_mmio_reg_add(struct temp_regset *regset, i915_mmio_reg_offset((reg)), \ (masked) ? GUC_REGSET_MASKED : 0) -static void guc_mmio_regset_init(struct temp_regset *regset, - struct intel_engine_cs *engine) +static int guc_mmio_regset_init(struct temp_regset *regset, + struct intel_engine_cs *engine) { const u32 base = engine->mmio_base; struct i915_wa_list *wal = &engine->wa_list; struct i915_wa *wa; unsigned int i; + int ret = 0; regset->used = 0; - GUC_MMIO_REG_ADD(regset, RING_MODE_GEN7(base), true); - GUC_MMIO_REG_ADD(regset, RING_HWS_PGA(base), false); - GUC_MMIO_REG_ADD(regset, RING_IMR(base), false); + ret |= GUC_MMIO_REG_ADD(regset, RING_MODE_GEN7(base), true); + ret |= GUC_MMIO_REG_ADD(regset, RING_HWS_PGA(base), false); + ret |= GUC_MMIO_REG_ADD(regset, RING_IMR(base), false); for (i = 0, wa = wal->list; i < wal->count; i++, wa++) - GUC_MMIO_REG_ADD(regset, wa->reg, wa->masked_reg); + ret |= GUC_MMIO_REG_ADD(regset, wa->reg, wa->masked_reg); /* Be extra paranoid and include all whitelist registers. */ for (i = 0; i < RING_MAX_NONPRIV_SLOTS; i++) - GUC_MMIO_REG_ADD(regset, - RING_FORCE_TO_NONPRIV(base, i), - false); + ret |= GUC_MMIO_REG_ADD(regset, + RING_FORCE_TO_NONPRIV(base, i), + false); /* add in local MOCS registers */ for (i = 0; i < GEN9_LNCFCMOCS_REG_COUNT; i++) - GUC_MMIO_REG_ADD(regset, GEN9_LNCFCMOCS(i), false); + ret |= GUC_MMIO_REG_ADD(regset, GEN9_LNCFCMOCS(i), false); + + return ret ? -1 : 0; } static int guc_mmio_reg_state_query(struct intel_guc *guc) -- cgit From bf8900406e9e5c928ba578c8fd932b4bc72556be Mon Sep 17 00:00:00 2001 From: Lucas De Marchi Date: Mon, 7 Feb 2022 23:01:41 -0800 Subject: drm/i915/guc: Use a single pass to calculate regset The ADS initialitazion was using 2 passes to calculate the regset sent to GuC to initialize each engine: the first pass to just have the final object size and the second to set each register in place in the final gem object. However in order to maintain an ordered set of registers to pass to guc, each register needs to be added and moved in the final array. The second phase may actually happen in IO memory rather than system memory and accessing IO memory by simply dereferencing the pointer doesn't work on all architectures. Other places of the ADS initializaition were converted to use the iosys_map API, but here there may be a lot more accesses to IO memory. So, instead of following that same approach, convert the regset initialization to calculate the final array in 1 pass and in the second pass that array is just copied to its final location, updating the pointers for each engine written to the ADS blob. One important thing is that struct temp_regset now have different semantics: `registers` continues to track the registers of a single engine, however the other fields are updated together, according to the newly added `storage`, which tracks the memory allocated for all the registers. So rename some of these fields and add a __mmio_reg_add(): this function (possibly) allocates memory and operates on the storage pointer while guc_mmio_reg_add() continues to manage the registers pointer. On a Tiger Lake system using enable_guc=3, the following log message is now seen: [ 187.334310] i915 0000:00:02.0: [drm:intel_guc_ads_create [i915]] Used 4 KB for temporary ADS regset This change has also been tested on an ARM64 host with DG2 and other discrete graphics cards. v2 (Daniele): - Fix leaking tempset on error path - Add comments on struct temp_regset to document the meaning of each field Cc: Matt Roper Cc: John Harrison Cc: Matthew Brost Cc: Daniele Ceraolo Spurio Signed-off-by: Lucas De Marchi Reviewed-by: Daniele Ceraolo Spurio Link: https://patchwork.freedesktop.org/patch/msgid/20220208070141.2095177-3-lucas.demarchi@intel.com --- drivers/gpu/drm/i915/gt/uc/intel_guc.h | 7 ++ drivers/gpu/drm/i915/gt/uc/intel_guc_ads.c | 128 +++++++++++++++++++---------- 2 files changed, 90 insertions(+), 45 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc.h b/drivers/gpu/drm/i915/gt/uc/intel_guc.h index 697d9d66acef..9d779de16613 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc.h +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc.h @@ -150,6 +150,13 @@ struct intel_guc { struct __guc_ads_blob *ads_blob; /** @ads_regset_size: size of the save/restore regsets in the ADS */ u32 ads_regset_size; + /** + * @ads_regset_count: number of save/restore registers in the ADS for + * each engine + */ + u32 ads_regset_count[I915_NUM_ENGINES]; + /** @ads_regset: save/restore regsets in the ADS */ + struct guc_mmio_reg *ads_regset; /** @ads_golden_ctxt_size: size of the golden contexts in the ADS */ u32 ads_golden_ctxt_size; /** @ads_engine_usage_size: size of engine usage in the ADS */ diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_ads.c b/drivers/gpu/drm/i915/gt/uc/intel_guc_ads.c index f163c929a6b3..542bada48674 100644 --- a/drivers/gpu/drm/i915/gt/uc/intel_guc_ads.c +++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_ads.c @@ -206,14 +206,18 @@ static void guc_mapping_table_init(struct intel_gt *gt, /* * The save/restore register list must be pre-calculated to a temporary - * buffer of driver defined size before it can be generated in place - * inside the ADS. + * buffer before it can be copied inside the ADS. */ -#define MAX_MMIO_REGS 128 /* Arbitrary size, increase as needed */ struct temp_regset { + /* + * ptr to the section of the storage for the engine currently being + * worked on + */ struct guc_mmio_reg *registers; - u32 used; - u32 size; + /* ptr to the base of the allocated storage for all engines */ + struct guc_mmio_reg *storage; + u32 storage_used; + u32 storage_max; }; static int guc_mmio_reg_cmp(const void *a, const void *b) @@ -224,18 +228,44 @@ static int guc_mmio_reg_cmp(const void *a, const void *b) return (int)ra->offset - (int)rb->offset; } +static struct guc_mmio_reg * __must_check +__mmio_reg_add(struct temp_regset *regset, struct guc_mmio_reg *reg) +{ + u32 pos = regset->storage_used; + struct guc_mmio_reg *slot; + + if (pos >= regset->storage_max) { + size_t size = ALIGN((pos + 1) * sizeof(*slot), PAGE_SIZE); + struct guc_mmio_reg *r = krealloc(regset->storage, + size, GFP_KERNEL); + if (!r) { + WARN_ONCE(1, "Incomplete regset list: can't add register (%d)\n", + -ENOMEM); + return ERR_PTR(-ENOMEM); + } + + regset->registers = r + (regset->registers - regset->storage); + regset->storage = r; + regset->storage_max = size / sizeof(*slot); + } + + slot = ®set->storage[pos]; + regset->storage_used++; + *slot = *reg; + + return slot; +} + static long __must_check guc_mmio_reg_add(struct temp_regset *regset, u32 offset, u32 flags) { - u32 count = regset->used; + u32 count = regset->storage_used - (regset->registers - regset->storage); struct guc_mmio_reg reg = { .offset = offset, .flags = flags, }; struct guc_mmio_reg *slot; - GEM_BUG_ON(count >= regset->size); - /* * The mmio list is built using separate lists within the driver. * It's possible that at some point we may attempt to add the same @@ -246,9 +276,9 @@ static long __must_check guc_mmio_reg_add(struct temp_regset *regset, sizeof(reg), guc_mmio_reg_cmp)) return 0; - slot = ®set->registers[count]; - regset->used++; - *slot = reg; + slot = __mmio_reg_add(regset, ®); + if (IS_ERR(slot)) + return PTR_ERR(slot); while (slot-- > regset->registers) { GEM_BUG_ON(slot[0].offset == slot[1].offset); @@ -275,7 +305,11 @@ static int guc_mmio_regset_init(struct temp_regset *regset, unsigned int i; int ret = 0; - regset->used = 0; + /* + * Each engine's registers point to a new start relative to + * storage + */ + regset->registers = regset->storage + regset->storage_used; ret |= GUC_MMIO_REG_ADD(regset, RING_MODE_GEN7(base), true); ret |= GUC_MMIO_REG_ADD(regset, RING_HWS_PGA(base), false); @@ -297,34 +331,36 @@ static int guc_mmio_regset_init(struct temp_regset *regset, return ret ? -1 : 0; } -static int guc_mmio_reg_state_query(struct intel_guc *guc) +static long guc_mmio_reg_state_create(struct intel_guc *guc) { struct intel_gt *gt = guc_to_gt(guc); struct intel_engine_cs *engine; enum intel_engine_id id; - struct temp_regset temp_set; - u32 total; + struct temp_regset temp_set = {}; + long total = 0; + long ret; - /* - * Need to actually build the list in order to filter out - * duplicates and other such data dependent constructions. - */ - temp_set.size = MAX_MMIO_REGS; - temp_set.registers = kmalloc_array(temp_set.size, - sizeof(*temp_set.registers), - GFP_KERNEL); - if (!temp_set.registers) - return -ENOMEM; - - total = 0; for_each_engine(engine, gt, id) { - guc_mmio_regset_init(&temp_set, engine); - total += temp_set.used; + u32 used = temp_set.storage_used; + + ret = guc_mmio_regset_init(&temp_set, engine); + if (ret < 0) + goto fail_regset_init; + + guc->ads_regset_count[id] = temp_set.storage_used - used; + total += guc->ads_regset_count[id]; } - kfree(temp_set.registers); + guc->ads_regset = temp_set.storage; + + drm_dbg(&guc_to_gt(guc)->i915->drm, "Used %zu KB for temporary ADS regset\n", + (temp_set.storage_max * sizeof(struct guc_mmio_reg)) >> 10); return total * sizeof(struct guc_mmio_reg); + +fail_regset_init: + kfree(temp_set.storage); + return ret; } static void guc_mmio_reg_state_init(struct intel_guc *guc, @@ -332,40 +368,38 @@ static void guc_mmio_reg_state_init(struct intel_guc *guc, { struct intel_gt *gt = guc_to_gt(guc); struct intel_engine_cs *engine; + struct guc_mmio_reg *ads_registers; enum intel_engine_id id; - struct temp_regset temp_set; - struct guc_mmio_reg_set *ads_reg_set; u32 addr_ggtt, offset; - u8 guc_class; offset = guc_ads_regset_offset(guc); addr_ggtt = intel_guc_ggtt_offset(guc, guc->ads_vma) + offset; - temp_set.registers = (struct guc_mmio_reg *)(((u8 *)blob) + offset); - temp_set.size = guc->ads_regset_size / sizeof(temp_set.registers[0]); + ads_registers = (struct guc_mmio_reg *)(((u8 *)blob) + offset); + + memcpy(ads_registers, guc->ads_regset, guc->ads_regset_size); for_each_engine(engine, gt, id) { + u32 count = guc->ads_regset_count[id]; + struct guc_mmio_reg_set *ads_reg_set; + u8 guc_class; + /* Class index is checked in class converter */ GEM_BUG_ON(engine->instance >= GUC_MAX_INSTANCES_PER_CLASS); guc_class = engine_class_to_guc_class(engine->class); ads_reg_set = &blob->ads.reg_state_list[guc_class][engine->instance]; - guc_mmio_regset_init(&temp_set, engine); - if (!temp_set.used) { + if (!count) { ads_reg_set->address = 0; ads_reg_set->count = 0; continue; } ads_reg_set->address = addr_ggtt; - ads_reg_set->count = temp_set.used; + ads_reg_set->count = count; - temp_set.size -= temp_set.used; - temp_set.registers += temp_set.used; - addr_ggtt += temp_set.used * sizeof(struct guc_mmio_reg); + addr_ggtt += count * sizeof(struct guc_mmio_reg); } - - GEM_BUG_ON(temp_set.size); } static void fill_engine_enable_masks(struct intel_gt *gt, @@ -607,8 +641,11 @@ int intel_guc_ads_create(struct intel_guc *guc) GEM_BUG_ON(guc->ads_vma); - /* Need to calculate the reg state size dynamically: */ - ret = guc_mmio_reg_state_query(guc); + /* + * Create reg state size dynamically on system memory to be copied to + * the final ads blob on gt init/reset + */ + ret = guc_mmio_reg_state_create(guc); if (ret < 0) return ret; guc->ads_regset_size = ret; @@ -648,6 +685,7 @@ void intel_guc_ads_destroy(struct intel_guc *guc) { i915_vma_unpin_and_release(&guc->ads_vma, I915_VMA_RELEASE_MAP); guc->ads_blob = NULL; + kfree(guc->ads_regset); } static void guc_ads_private_data_reset(struct intel_guc *guc) -- cgit From ba2c5d15022a565da187d90e2fe44768e33e5034 Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Wed, 9 Feb 2022 11:16:52 +0000 Subject: drm/i915/ttm: tweak priority hint selection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For some reason we are selecting PRIO_HAS_PAGES when we don't have mm.pages, and vice versa. v2(Thomas): - Add missing fixes tag Fixes: 213d50927763 ("drm/i915/ttm: Introduce a TTM i915 gem object backend") Signed-off-by: Matthew Auld Cc: Thomas Hellström Reviewed-by: Thomas Hellström Link: https://patchwork.freedesktop.org/patch/msgid/20220209111652.468762-1-matthew.auld@intel.com --- drivers/gpu/drm/i915/gem/i915_gem_ttm.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gem/i915_gem_ttm.c b/drivers/gpu/drm/i915/gem/i915_gem_ttm.c index 84cae740b4a5..1eb2fd81c5b6 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_ttm.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_ttm.c @@ -840,11 +840,9 @@ void i915_ttm_adjust_lru(struct drm_i915_gem_object *obj) } else if (obj->mm.madv != I915_MADV_WILLNEED) { bo->priority = I915_TTM_PRIO_PURGE; } else if (!i915_gem_object_has_pages(obj)) { - if (bo->priority < I915_TTM_PRIO_HAS_PAGES) - bo->priority = I915_TTM_PRIO_HAS_PAGES; + bo->priority = I915_TTM_PRIO_NO_PAGES; } else { - if (bo->priority > I915_TTM_PRIO_NO_PAGES) - bo->priority = I915_TTM_PRIO_NO_PAGES; + bo->priority = I915_TTM_PRIO_HAS_PAGES; } ttm_bo_move_to_lru_tail(bo, bo->resource, NULL); -- cgit From 88d23eda3c7f8bb560ae93b00d03688ff7920cdd Mon Sep 17 00:00:00 2001 From: Ramalingam C Date: Sat, 29 Jan 2022 00:22:05 +0530 Subject: drm/i915/dg2: Add Wa_22011450934 An indirect ctx wabb is implemented as per Wa_22011450934 to avoid rcs restore hang during context restore of a preempted context in GPGPU mode Signed-off-by: Ramalingam C cc: Chris Wilson Reviewed-by: Matthew Auld Link: https://patchwork.freedesktop.org/patch/msgid/20220128185209.18077-2-ramalingam.c@intel.com --- drivers/gpu/drm/i915/gt/intel_lrc.c | 28 ++++++++++++++++++++++++++++ drivers/gpu/drm/i915/i915_reg.h | 4 ++++ 2 files changed, 32 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/intel_lrc.c b/drivers/gpu/drm/i915/gt/intel_lrc.c index 84456ffeb4cd..b4b7453554d5 100644 --- a/drivers/gpu/drm/i915/gt/intel_lrc.c +++ b/drivers/gpu/drm/i915/gt/intel_lrc.c @@ -1164,6 +1164,29 @@ gen12_emit_cmd_buf_wa(const struct intel_context *ce, u32 *cs) return cs; } +/* + * On DG2 during context restore of a preempted context in GPGPU mode, + * RCS restore hang is detected. This is extremely timing dependent. + * To address this below sw wabb is implemented for DG2 A steppings. + */ +static u32 * +dg2_emit_rcs_hang_wabb(const struct intel_context *ce, u32 *cs) +{ + *cs++ = MI_LOAD_REGISTER_IMM(1); + *cs++ = i915_mmio_reg_offset(GEN12_STATE_ACK_DEBUG); + *cs++ = 0x21; + + *cs++ = MI_LOAD_REGISTER_REG; + *cs++ = i915_mmio_reg_offset(RING_NOPID(ce->engine->mmio_base)); + *cs++ = i915_mmio_reg_offset(GEN12_CULLBIT1); + + *cs++ = MI_LOAD_REGISTER_REG; + *cs++ = i915_mmio_reg_offset(RING_NOPID(ce->engine->mmio_base)); + *cs++ = i915_mmio_reg_offset(GEN12_CULLBIT2); + + return cs; +} + static u32 * gen12_emit_indirect_ctx_rcs(const struct intel_context *ce, u32 *cs) { @@ -1171,6 +1194,11 @@ gen12_emit_indirect_ctx_rcs(const struct intel_context *ce, u32 *cs) cs = gen12_emit_cmd_buf_wa(ce, cs); cs = gen12_emit_restore_scratch(ce, cs); + /* Wa_22011450934:dg2 */ + if (IS_DG2_GRAPHICS_STEP(ce->engine->i915, G10, STEP_A0, STEP_B0) || + IS_DG2_GRAPHICS_STEP(ce->engine->i915, G11, STEP_A0, STEP_B0)) + cs = dg2_emit_rcs_hang_wabb(ce, cs); + /* Wa_16013000631:dg2 */ if (IS_DG2_GRAPHICS_STEP(ce->engine->i915, G10, STEP_B0, STEP_C0) || IS_DG2_G11(ce->engine->i915)) diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h index a19504ac86b1..f95bbb10b6f4 100644 --- a/drivers/gpu/drm/i915/i915_reg.h +++ b/drivers/gpu/drm/i915/i915_reg.h @@ -13024,4 +13024,8 @@ enum skl_power_gate { #define SLICE_COMMON_ECO_CHICKEN1 _MMIO(0x731C) #define MSC_MSAA_REODER_BUF_BYPASS_DISABLE REG_BIT(14) +#define GEN12_CULLBIT1 _MMIO(0x6100) +#define GEN12_CULLBIT2 _MMIO(0x7030) +#define GEN12_STATE_ACK_DEBUG _MMIO(0x20BC) + #endif /* _I915_REG_H_ */ -- cgit From 165bbfba34cb4c8f31d1e643abd2d5cb6eba1b90 Mon Sep 17 00:00:00 2001 From: Ramalingam C Date: Sat, 29 Jan 2022 00:22:06 +0530 Subject: drm/i915: align the plane_vma to min_page_size of stolen mem Align the plane vma size to the stolem memory regions' min_page_size. Signed-off-by: Ramalingam C cc: Matthew Auld cc: Chris P Wilson Reviewed-by: Matthew Auld Link: https://patchwork.freedesktop.org/patch/msgid/20220128185209.18077-3-ramalingam.c@intel.com --- drivers/gpu/drm/i915/display/intel_plane_initial.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/display/intel_plane_initial.c b/drivers/gpu/drm/i915/display/intel_plane_initial.c index e4186a0b8edb..543877998078 100644 --- a/drivers/gpu/drm/i915/display/intel_plane_initial.c +++ b/drivers/gpu/drm/i915/display/intel_plane_initial.c @@ -46,17 +46,18 @@ static struct i915_vma * initial_plane_vma(struct drm_i915_private *i915, struct intel_initial_plane_config *plane_config) { + struct intel_memory_region *mem = i915->mm.stolen_region; struct drm_i915_gem_object *obj; struct i915_vma *vma; u32 base, size; - if (plane_config->size == 0) + if (!mem || plane_config->size == 0) return NULL; base = round_down(plane_config->base, I915_GTT_MIN_ALIGNMENT); size = round_up(plane_config->base + plane_config->size, - I915_GTT_MIN_ALIGNMENT); + mem->min_page_size); size -= base; /* -- cgit From 2528b396d5304a711d28f08047a8fe60daebc3ae Mon Sep 17 00:00:00 2001 From: Ramalingam C Date: Sat, 29 Jan 2022 00:22:07 +0530 Subject: drm/i915: More gt idling time with guc submission On i915_selftest@live@gt_timelines, we create many contexts in loop and create and submit request and then destoy contexts. Destroying the context needs to disable scheduling, wait for G2H, deregister context and wait for G2H to destroy each context. Idling of the gt has to wait for all this to complete which is taking ~3sec for this test. Hence we are increasing the igt_flush_test's timeout for gt idling to 3Sec. Signed-off-by: Ramalingam C cc: Matthew Brost Reviewed-by: Matthew Auld Link: https://patchwork.freedesktop.org/patch/msgid/20220128185209.18077-4-ramalingam.c@intel.com --- drivers/gpu/drm/i915/selftests/igt_flush_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/selftests/igt_flush_test.c b/drivers/gpu/drm/i915/selftests/igt_flush_test.c index b84594601d30..b484e12df417 100644 --- a/drivers/gpu/drm/i915/selftests/igt_flush_test.c +++ b/drivers/gpu/drm/i915/selftests/igt_flush_test.c @@ -19,7 +19,7 @@ int igt_flush_test(struct drm_i915_private *i915) cond_resched(); - if (intel_gt_wait_for_idle(gt, HZ) == -ETIME) { + if (intel_gt_wait_for_idle(gt, HZ * 3) == -ETIME) { pr_err("%pS timed out, cancelling all further testing.\n", __builtin_return_address(0)); -- cgit From 154cfae6158141b18d65abb0db679bb51a8294e7 Mon Sep 17 00:00:00 2001 From: Bruce Chang Date: Sat, 29 Jan 2022 00:22:08 +0530 Subject: drm/i915/dg2: Add Wa_22011100796 Whenever Full soft reset is required, reset all individual engines first, and then do a full soft reset. Signed-off-by: Bruce Chang cc: Matt Roper Cc: Rodrigo Vivi Signed-off-by: Ramalingam C Reviewed-by: Matthew Auld Link: https://patchwork.freedesktop.org/patch/msgid/20220128185209.18077-5-ramalingam.c@intel.com --- drivers/gpu/drm/i915/gt/intel_reset.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/gt/intel_reset.c b/drivers/gpu/drm/i915/gt/intel_reset.c index 7be0002d9d70..cf1f8a382c45 100644 --- a/drivers/gpu/drm/i915/gt/intel_reset.c +++ b/drivers/gpu/drm/i915/gt/intel_reset.c @@ -598,6 +598,15 @@ static int gen8_reset_engines(struct intel_gt *gt, */ } + /* + * Wa_22011100796:dg2, whenever Full soft reset is required, + * reset all individual engines firstly, and then do a full soft reset. + * + * This is best effort, so ignore any error from the initial reset. + */ + if (IS_DG2(gt->i915) && engine_mask == ALL_ENGINES) + gen11_reset_engines(gt, gt->info.engine_mask, 0); + if (GRAPHICS_VER(gt->i915) >= 11) ret = gen11_reset_engines(gt, engine_mask, retry); else -- cgit