From shawn.lin at rock-chips.com Wed Apr 1 00:11:16 2026 From: shawn.lin at rock-chips.com (Shawn Lin) Date: Wed, 1 Apr 2026 15:11:16 +0800 Subject: [PATCH 1/2] pmdomain/rockchip: skip QoS operations for idle-only domains In-Reply-To: References: <20260331180223.1682283-1-daniel@orb.net> <5c8732a3-6cc9-5d02-f56b-fc7ba29c7ce0@rock-chips.com> Message-ID: + Finley ? 2026/04/01 ??? 14:13, Daniel Bozeman ??: > > > I ran additional tests to gather evidence: > > Test 1: Patch 2 only (skip EPROBE_DEFER), no patch 1. > Result: kernel panic. The idle-only domains register > successfully, but genpd_power_off_work_fn attempts to power > them off and crashes in rockchip_pmu_set_idle_request: > > Internal error: synchronous external abort: 0000000096000010 > CPU: 0 PID: 59 Comm: kworker/0:3 > Workqueue: pm genpd_power_off_work_fn > pc : regmap_mmio_read32le+0x8/0x20 > Call trace: > regmap_mmio_read32le+0x8/0x20 > _regmap_bus_reg_read+0x6c/0xac > _regmap_read+0x60/0xd8 > regmap_read+0x4c/0x7c > rockchip_pmu_set_idle_request.isra.0+0x94/0x1b4 > rockchip_pd_power+0x378/0x604 > rockchip_pd_power_off+0x14/0x34 > genpd_power_off.isra.0+0x1f0/0x2f0 > genpd_power_off_work_fn+0x34/0x54 > > Test 2: No kernel patches, PD_GPU disabled via > status = "disabled" in DTS to avoid EPROBE_DEFER entirely. > Result: same kernel panic. The idle-only domains register > but crash identically when genpd tries to power them off. > Same call trace as above. > > So the crash is not caused by probe ordering or > EPROBE_DEFER -- it happens whenever idle-only domains > (pwr_mask == 0) are registered and genpd attempts to > power them off. The QoS register access in > rockchip_pmu_set_idle_request faults on these domains. > > Regarding S2R: you raise a valid concern about skipping > QoS save/restore. However, these idle-only domains cannot > actually be powered off (pwr_mask == 0), so > rockchip_do_pmu_set_power_domain already returns early for > them. The QoS save/idle/restore cycle in rockchip_pd_power > has no effect on these domains since the power state never > changes -- the save and restore are paired around a no-op. > Skipping the entire sequence for pwr_mask == 0 should be > safe for S2R as well. > Thanks for these details but I think it explains the phenomenon and work around it but didn't explain the root cause. RK3528 SoC can't power down these PDs but only support to idle them. Right, idle these PDs could still make QoS registers inaccessable. But from the code, rockchip_pmu_save_qos() and rockchip_pmu_restore_qos() both are called under idle-free state. One possible guess is it's clk related. Could you please help test your environment with "clk_ignore_unused" set in cmdline? Another test is to print out genpd->name in the entry of rockchip_pd_power_on() and rockchip_pd_power_off() to see which one is inaccessable. > Both patches are needed: > - Patch 1: prevents the QoS crash on idle-only domains > - Patch 2: prevents probe teardown from making things worse > > Tested on NanoPi Zero2 (RK3528) with all four scenarios: > both patches (boots), patch 2 only (panic), DTS workaround > (panic), both patches + E20C regression test (no issues). > > The board DTS that triggers this (GPIO-controlled USB VBUS > regulator on GPIO4/PD_RKVENC) can be seen at: > https://github.com/dboze/openwrt/blob/add-nanopi-zero2-clean/target/linux/rockchip/patches-6.12/102-arm64-dts-rockchip-Add-FriendlyElec-NanoPi-Zero2.patch > From xxm at rock-chips.com Wed Apr 1 00:48:30 2026 From: xxm at rock-chips.com (Simon) Date: Wed, 1 Apr 2026 15:48:30 +0800 Subject: [PATCH] iommu/rockchip: fix page table allocation flags for v2 IOMMU In-Reply-To: <20260331075010.1463-1-midgy971@gmail.com> References: <20260331075010.1463-1-midgy971@gmail.com> Message-ID: <0f285782-b12a-4abd-bca7-b6c549bed59f@rock-chips.com> Hi Midgy, ? 2026/3/31 15:50, Midgy BALON ??: > commit 2a7e6400f72b ("iommu: rockchip: Allocate tables from all > available memory for IOMMU v2") removed GFP_DMA32 from > iommu_data_ops_v2, reasoning that RK356x and RK3588 IOMMU v2 hardware > supports up to 40-bit physical addresses for page tables. However, the > RK3568 IOMMU page-table walker uses a 32-bit AXI bus: it cannot access > physical addresses above 4 GB regardless of the address encoding range. > > On boards with more than 4 GB of RAM (e.g. 8 GB LPDDR4X), removing > GFP_DMA32 causes two distinct failure modes: > > 1. Direct allocation above 4 GB: iommu_alloc_pages_sz() may return > memory above 0x100000000. The hardware page-table walker issues a > bus error trying to dereference those addresses, causing an IOMMU > fault on the first DMA transaction. Which IP block is hitting this? We'd like to take a look on our end. > 2. SWIOTLB bounce-buffer poisoning: without GFP_DMA32, page tables land > above the SWIOTLB window. dma_map_single() with DMA_BIT_MASK(32) > then bounces them into a buffer below 4 GB. rk_dte_get_page_table() > returns phys_to_virt() of the bounce buffer address; PTEs are written > there; the next dma_sync_single_for_device(DMA_TO_DEVICE) copies the > original (zero) data back over the bounce buffer, silently erasing the > freshly written PTEs. The IOMMU faults because every PTE reads as zero. This probably need a separate patch. One way to fix it would be to track the original L2 page table base addresses in struct rk_iommu_domain, then have rk_dte_get_page_table() return the tracked address instead of deriving it from the DTE. > Restore GFP_DMA32 (and DMA_BIT_MASK(32)) for iommu_data_ops_v2, which > currently only serves "rockchip,rk3568-iommu" in mainline. > > Tested on Radxa ROCK 3B (RK3568, 8 GB LPDDR4X): > - MobileNetV1 via RKNN: 5.8 ms/inference (IOMMU mode) > - YOLOv5s 640x640 via RKNN: ~57 ms/inference (IOMMU mode) > - No IOMMU faults, correct inference results > > Fixes: 2a7e6400f72b ("iommu: rockchip: Allocate tables from all available memory for IOMMU v2") > Cc: stable at vger.kernel.org > Cc: Jonas Karlman > Signed-off-by: Midgy BALON > --- > drivers/iommu/rockchip-iommu.c | 4 ++-- > 1 file changed, 2 insertions(+), 2 deletions(-) > > diff --git a/drivers/iommu/rockchip-iommu.c b/drivers/iommu/rockchip-iommu.c > index 85f3667e797..8b45db29471 100644 > --- a/drivers/iommu/rockchip-iommu.c > +++ b/drivers/iommu/rockchip-iommu.c > @@ -1358,8 +1358,8 @@ static struct rk_iommu_ops iommu_data_ops_v2 = { > .pt_address = &rk_dte_pt_address_v2, > .mk_dtentries = &rk_mk_dte_v2, > .mk_ptentries = &rk_mk_pte_v2, > - .dma_bit_mask = DMA_BIT_MASK(40), > - .gfp_flags = 0, > + .dma_bit_mask = DMA_BIT_MASK(32), > + .gfp_flags = GFP_DMA32, > }; > > static const struct of_device_id rk_iommu_dt_ids[] = { From xxm at rock-chips.com Wed Apr 1 00:59:52 2026 From: xxm at rock-chips.com (Simon Xue) Date: Wed, 1 Apr 2026 15:59:52 +0800 Subject: [PATCH v2] iommu/rockchip: Drop global rk_ops in favor of per-device ops In-Reply-To: References: <20260310084812.126082-1-xxm@rock-chips.com> <20260310105303.128859-1-xxm@rock-chips.com> Message-ID: Hi all, A gentle ping on this patch. ? 2026/3/13 17:32, Shawn Lin ??: > ? 2026/03/10 ??? 18:53, Simon Xue ??: >> The driver currently uses a global rk_ops pointer, forcing all IOMMU >> instances to share the same operations. This restricts the driver from >> supporting SoCs that might integrate different versions of IOMMU >> hardware. >> >> Since the IOMMU framework passes the master device information to >> iommu_paging_domain_alloc(), the global variable is no longer needed. >> >> Fix this by moving rk_ops into struct rk_iommu and struct >> rk_iommu_domain. >> Initialize it per-device during probe via of_device_get_match_data(), >> and replace all global references with the instance-specific pointers. >> > > Thanks for the patch, Simon. I've tested it on the RK3576 EVB1 with > PCIe1 + IOMMU. NVMe works fine on it, and I also verified the IOVA > allocated in the NVMe driver, they look correct as I manually limited > the memblock to under 2GB, so here it is: > > nvme 0001:21:00.0: cq_dma_addr: 0x00000000f7fc7000 > > Tested-by: Shawn Lin > Reviewed-by: Shawn Lin > >> Signed-off-by: Simon Xue >> --- >> v2: >> ? - Remove the one-time-used 'ops' variable in rk_iommu_probe() >> >> ? drivers/iommu/rockchip-iommu.c | 71 ++++++++++++++++------------------ >> ? 1 file changed, 33 insertions(+), 38 deletions(-) >> >> diff --git a/drivers/iommu/rockchip-iommu.c >> b/drivers/iommu/rockchip-iommu.c >> index 0013cf196c57..4da80136933c 100644 >> --- a/drivers/iommu/rockchip-iommu.c >> +++ b/drivers/iommu/rockchip-iommu.c >> @@ -82,6 +82,14 @@ >> ??? */ >> ? #define RK_IOMMU_PGSIZE_BITMAP 0x007ff000 >> ? +struct rk_iommu_ops { >> +??? phys_addr_t (*pt_address)(u32 dte); >> +??? u32 (*mk_dtentries)(dma_addr_t pt_dma); >> +??? u32 (*mk_ptentries)(phys_addr_t page, int prot); >> +??? u64 dma_bit_mask; >> +??? gfp_t gfp_flags; >> +}; >> + >> ? struct rk_iommu_domain { >> ????? struct list_head iommus; >> ????? u32 *dt; /* page directory table */ >> @@ -89,6 +97,7 @@ struct rk_iommu_domain { >> ????? spinlock_t iommus_lock; /* lock for iommus list */ >> ????? spinlock_t dt_lock; /* lock for modifying page directory table */ >> ????? struct device *dma_dev; >> +??? const struct rk_iommu_ops *rk_ops; >> ? ????? struct iommu_domain domain; >> ? }; >> @@ -98,14 +107,6 @@ static const char * const rk_iommu_clocks[] = { >> ????? "aclk", "iface", >> ? }; >> ? -struct rk_iommu_ops { >> -??? phys_addr_t (*pt_address)(u32 dte); >> -??? u32 (*mk_dtentries)(dma_addr_t pt_dma); >> -??? u32 (*mk_ptentries)(phys_addr_t page, int prot); >> -??? u64 dma_bit_mask; >> -??? gfp_t gfp_flags; >> -}; >> - >> ? struct rk_iommu { >> ????? struct device *dev; >> ????? void __iomem **bases; >> @@ -117,6 +118,7 @@ struct rk_iommu { >> ????? struct iommu_device iommu; >> ????? struct list_head node; /* entry in rk_iommu_domain.iommus */ >> ????? struct iommu_domain *domain; /* domain to which iommu is >> attached */ >> +??? const struct rk_iommu_ops *rk_ops; >> ? }; >> ? ? struct rk_iommudata { >> @@ -124,7 +126,6 @@ struct rk_iommudata { >> ????? struct rk_iommu *iommu; >> ? }; >> ? -static const struct rk_iommu_ops *rk_ops; >> ? static struct iommu_domain rk_identity_domain; >> ? ? static inline void rk_table_flush(struct rk_iommu_domain *dom, >> dma_addr_t dma, >> @@ -510,7 +511,7 @@ static int rk_iommu_force_reset(struct rk_iommu >> *iommu) >> ?????? * and verifying that upper 5 (v1) or 7 (v2) nybbles are read >> back. >> ?????? */ >> ????? for (i = 0; i < iommu->num_mmu; i++) { >> -??????? dte_addr = rk_ops->pt_address(DTE_ADDR_DUMMY); >> +??????? dte_addr = iommu->rk_ops->pt_address(DTE_ADDR_DUMMY); >> ????????? rk_iommu_write(iommu->bases[i], RK_MMU_DTE_ADDR, dte_addr); >> ? ????????? if (dte_addr != rk_iommu_read(iommu->bases[i], >> RK_MMU_DTE_ADDR)) { >> @@ -551,7 +552,7 @@ static void log_iova(struct rk_iommu *iommu, int >> index, dma_addr_t iova) >> ????? page_offset = rk_iova_page_offset(iova); >> ? ????? mmu_dte_addr = rk_iommu_read(base, RK_MMU_DTE_ADDR); >> -??? mmu_dte_addr_phys = rk_ops->pt_address(mmu_dte_addr); >> +??? mmu_dte_addr_phys = iommu->rk_ops->pt_address(mmu_dte_addr); >> ? ????? dte_addr_phys = mmu_dte_addr_phys + (4 * dte_index); >> ????? dte_addr = phys_to_virt(dte_addr_phys); >> @@ -560,14 +561,14 @@ static void log_iova(struct rk_iommu *iommu, >> int index, dma_addr_t iova) >> ????? if (!rk_dte_is_pt_valid(dte)) >> ????????? goto print_it; >> ? -??? pte_addr_phys = rk_ops->pt_address(dte) + (pte_index * 4); >> +??? pte_addr_phys = iommu->rk_ops->pt_address(dte) + (pte_index * 4); >> ????? pte_addr = phys_to_virt(pte_addr_phys); >> ????? pte = *pte_addr; >> ? ????? if (!rk_pte_is_page_valid(pte)) >> ????????? goto print_it; >> ? -??? page_addr_phys = rk_ops->pt_address(pte) + page_offset; >> +??? page_addr_phys = iommu->rk_ops->pt_address(pte) + page_offset; >> ????? page_flags = pte & RK_PTE_PAGE_FLAGS_MASK; >> ? ? print_it: >> @@ -663,13 +664,13 @@ static phys_addr_t rk_iommu_iova_to_phys(struct >> iommu_domain *domain, >> ????? if (!rk_dte_is_pt_valid(dte)) >> ????????? goto out; >> ? -??? pt_phys = rk_ops->pt_address(dte); >> +??? pt_phys = rk_domain->rk_ops->pt_address(dte); >> ????? page_table = (u32 *)phys_to_virt(pt_phys); >> ????? pte = page_table[rk_iova_pte_index(iova)]; >> ????? if (!rk_pte_is_page_valid(pte)) >> ????????? goto out; >> ? -??? phys = rk_ops->pt_address(pte) + rk_iova_page_offset(iova); >> +??? phys = rk_domain->rk_ops->pt_address(pte) + >> rk_iova_page_offset(iova); >> ? out: >> ????? spin_unlock_irqrestore(&rk_domain->dt_lock, flags); >> ? @@ -730,7 +731,7 @@ static u32 *rk_dte_get_page_table(struct >> rk_iommu_domain *rk_domain, >> ????? if (rk_dte_is_pt_valid(dte)) >> ????????? goto done; >> ? -??? page_table = iommu_alloc_pages_sz(GFP_ATOMIC | rk_ops->gfp_flags, >> +??? page_table = iommu_alloc_pages_sz(GFP_ATOMIC | >> rk_domain->rk_ops->gfp_flags, >> ??????????????????????? SPAGE_SIZE); >> ????? if (!page_table) >> ????????? return ERR_PTR(-ENOMEM); >> @@ -742,13 +743,13 @@ static u32 *rk_dte_get_page_table(struct >> rk_iommu_domain *rk_domain, >> ????????? return ERR_PTR(-ENOMEM); >> ????? } >> ? -??? dte = rk_ops->mk_dtentries(pt_dma); >> +??? dte = rk_domain->rk_ops->mk_dtentries(pt_dma); >> ????? *dte_addr = dte; >> ? ????? rk_table_flush(rk_domain, >> ???????????????? rk_domain->dt_dma + dte_index * sizeof(u32), 1); >> ? done: >> -??? pt_phys = rk_ops->pt_address(dte); >> +??? pt_phys = rk_domain->rk_ops->pt_address(dte); >> ????? return (u32 *)phys_to_virt(pt_phys); >> ? } >> ? @@ -790,7 +791,7 @@ static int rk_iommu_map_iova(struct >> rk_iommu_domain *rk_domain, u32 *pte_addr, >> ????????? if (rk_pte_is_page_valid(pte)) >> ????????????? goto unwind; >> ? -??????? pte_addr[pte_count] = rk_ops->mk_ptentries(paddr, prot); >> +??????? pte_addr[pte_count] = rk_domain->rk_ops->mk_ptentries(paddr, >> prot); >> ? ????????? paddr += SPAGE_SIZE; >> ????? } >> @@ -812,7 +813,7 @@ static int rk_iommu_map_iova(struct >> rk_iommu_domain *rk_domain, u32 *pte_addr, >> ????????????????? pte_count * SPAGE_SIZE); >> ? ????? iova += pte_count * SPAGE_SIZE; >> -??? page_phys = rk_ops->pt_address(pte_addr[pte_count]); >> +??? page_phys = rk_domain->rk_ops->pt_address(pte_addr[pte_count]); >> ????? pr_err("iova: %pad already mapped to %pa cannot remap to phys: >> %pa prot: %#x\n", >> ???????????? &iova, &page_phys, &paddr, prot); >> ? @@ -849,7 +850,7 @@ static int rk_iommu_map(struct iommu_domain >> *domain, unsigned long _iova, >> ????? pte_index = rk_iova_pte_index(iova); >> ????? pte_addr = &page_table[pte_index]; >> ? -??? pte_dma = rk_ops->pt_address(dte_index) + pte_index * >> sizeof(u32); >> +??? pte_dma = rk_domain->rk_ops->pt_address(dte_index) + pte_index * >> sizeof(u32); >> ????? ret = rk_iommu_map_iova(rk_domain, pte_addr, pte_dma, iova, >> ????????????????? paddr, size, prot); >> ? @@ -887,7 +888,7 @@ static size_t rk_iommu_unmap(struct >> iommu_domain *domain, unsigned long _iova, >> ????????? return 0; >> ????? } >> ? -??? pt_phys = rk_ops->pt_address(dte); >> +??? pt_phys = rk_domain->rk_ops->pt_address(dte); >> ????? pte_addr = (u32 *)phys_to_virt(pt_phys) + rk_iova_pte_index(iova); >> ????? pte_dma = pt_phys + rk_iova_pte_index(iova) * sizeof(u32); >> ????? unmap_size = rk_iommu_unmap_iova(rk_domain, pte_addr, pte_dma, >> size); >> @@ -945,7 +946,7 @@ static int rk_iommu_enable(struct rk_iommu *iommu) >> ? ????? for (i = 0; i < iommu->num_mmu; i++) { >> ????????? rk_iommu_write(iommu->bases[i], RK_MMU_DTE_ADDR, >> - rk_ops->mk_dtentries(rk_domain->dt_dma)); >> + iommu->rk_ops->mk_dtentries(rk_domain->dt_dma)); >> ????????? rk_iommu_base_command(iommu->bases[i], RK_MMU_CMD_ZAP_CACHE); >> ????????? rk_iommu_write(iommu->bases[i], RK_MMU_INT_MASK, >> RK_MMU_IRQ_MASK); >> ????? } >> @@ -1068,17 +1069,19 @@ static struct iommu_domain >> *rk_iommu_domain_alloc_paging(struct device *dev) >> ????? if (!rk_domain) >> ????????? return NULL; >> ? +??? iommu = rk_iommu_from_dev(dev); >> +??? rk_domain->rk_ops = iommu->rk_ops; >> + >> ????? /* >> ?????? * rk32xx iommus use a 2 level pagetable. >> ?????? * Each level1 (dt) and level2 (pt) table has 1024 4-byte entries. >> ?????? * Allocate one 4 KiB page for each table. >> ?????? */ >> -??? rk_domain->dt = iommu_alloc_pages_sz(GFP_KERNEL | >> rk_ops->gfp_flags, >> +??? rk_domain->dt = iommu_alloc_pages_sz(GFP_KERNEL | >> rk_domain->rk_ops->gfp_flags, >> ?????????????????????????? SPAGE_SIZE); >> ????? if (!rk_domain->dt) >> ????????? goto err_free_domain; >> ? -??? iommu = rk_iommu_from_dev(dev); >> ????? rk_domain->dma_dev = iommu->dev; >> ????? rk_domain->dt_dma = dma_map_single(rk_domain->dma_dev, >> rk_domain->dt, >> ???????????????????????? SPAGE_SIZE, DMA_TO_DEVICE); >> @@ -1117,7 +1120,7 @@ static void rk_iommu_domain_free(struct >> iommu_domain *domain) >> ????? for (i = 0; i < NUM_DT_ENTRIES; i++) { >> ????????? u32 dte = rk_domain->dt[i]; >> ????????? if (rk_dte_is_pt_valid(dte)) { >> -??????????? phys_addr_t pt_phys = rk_ops->pt_address(dte); >> +??????????? phys_addr_t pt_phys = rk_domain->rk_ops->pt_address(dte); >> ????????????? u32 *page_table = phys_to_virt(pt_phys); >> ????????????? dma_unmap_single(rk_domain->dma_dev, pt_phys, >> ?????????????????????? SPAGE_SIZE, DMA_TO_DEVICE); >> @@ -1197,7 +1200,6 @@ static int rk_iommu_probe(struct >> platform_device *pdev) >> ????? struct device *dev = &pdev->dev; >> ????? struct rk_iommu *iommu; >> ????? struct resource *res; >> -??? const struct rk_iommu_ops *ops; >> ????? int num_res = pdev->num_resources; >> ????? int err, i; >> ? @@ -1211,16 +1213,9 @@ static int rk_iommu_probe(struct >> platform_device *pdev) >> ????? iommu->dev = dev; >> ????? iommu->num_mmu = 0; >> ? -??? ops = of_device_get_match_data(dev); >> -??? if (!rk_ops) >> -??????? rk_ops = ops; >> - >> -??? /* >> -???? * That should not happen unless different versions of the >> -???? * hardware block are embedded the same SoC >> -???? */ >> -??? if (WARN_ON(rk_ops != ops)) >> -??????? return -EINVAL; >> +??? iommu->rk_ops = of_device_get_match_data(dev); >> +??? if (!iommu->rk_ops) >> +??????? return -ENOENT; >> ? ????? iommu->bases = devm_kcalloc(dev, num_res, sizeof(*iommu->bases), >> ????????????????????? GFP_KERNEL); >> @@ -1286,7 +1281,7 @@ static int rk_iommu_probe(struct >> platform_device *pdev) >> ????????????? goto err_pm_disable; >> ????? } >> ? -??? dma_set_mask_and_coherent(dev, rk_ops->dma_bit_mask); >> +??? dma_set_mask_and_coherent(dev, iommu->rk_ops->dma_bit_mask); >> ? ????? err = iommu_device_sysfs_add(&iommu->iommu, dev, NULL, >> dev_name(dev)); >> ????? if (err) >> > From jonas at kwiboo.se Wed Apr 1 01:25:58 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Wed, 1 Apr 2026 10:25:58 +0200 Subject: [PATCH v2] iommu/rockchip: Drop global rk_ops in favor of per-device ops In-Reply-To: References: <20260310084812.126082-1-xxm@rock-chips.com> <20260310105303.128859-1-xxm@rock-chips.com> Message-ID: <8ca0a8d3-aed9-4c2a-89dd-e9029afdd279@kwiboo.se> Hi, On 4/1/2026 9:59 AM, Simon Xue wrote: > Hi all, > > A gentle ping on this patch. > > ? 2026/3/13 17:32, Shawn Lin ??: >> ? 2026/03/10 ??? 18:53, Simon Xue ??: >>> The driver currently uses a global rk_ops pointer, forcing all IOMMU >>> instances to share the same operations. This restricts the driver from >>> supporting SoCs that might integrate different versions of IOMMU >>> hardware. >>> >>> Since the IOMMU framework passes the master device information to >>> iommu_paging_domain_alloc(), the global variable is no longer needed. >>> >>> Fix this by moving rk_ops into struct rk_iommu and struct >>> rk_iommu_domain. >>> Initialize it per-device during probe via of_device_get_match_data(), >>> and replace all global references with the instance-specific pointers. >>> >> >> Thanks for the patch, Simon. I've tested it on the RK3576 EVB1 with >> PCIe1 + IOMMU. NVMe works fine on it, and I also verified the IOVA >> allocated in the NVMe driver, they look correct as I manually limited >> the memblock to under 2GB, so here it is: >> >> nvme 0001:21:00.0: cq_dma_addr: 0x00000000f7fc7000 >> >> Tested-by: Shawn Lin >> Reviewed-by: Shawn Lin >> >>> Signed-off-by: Simon Xue >>> --- >>> v2: >>> ? - Remove the one-time-used 'ops' variable in rk_iommu_probe() >>> >>> ? drivers/iommu/rockchip-iommu.c | 71 ++++++++++++++++------------------ >>> ? 1 file changed, 33 insertions(+), 38 deletions(-) >>> >>> diff --git a/drivers/iommu/rockchip-iommu.c >>> b/drivers/iommu/rockchip-iommu.c >>> index 0013cf196c57..4da80136933c 100644 >>> --- a/drivers/iommu/rockchip-iommu.c >>> +++ b/drivers/iommu/rockchip-iommu.c >>> @@ -82,6 +82,14 @@ >>> ??? */ >>> ? #define RK_IOMMU_PGSIZE_BITMAP 0x007ff000 >>> ? +struct rk_iommu_ops { >>> +??? phys_addr_t (*pt_address)(u32 dte); >>> +??? u32 (*mk_dtentries)(dma_addr_t pt_dma); >>> +??? u32 (*mk_ptentries)(phys_addr_t page, int prot); >>> +??? u64 dma_bit_mask; >>> +??? gfp_t gfp_flags; >>> +}; >>> + >>> ? struct rk_iommu_domain { >>> ????? struct list_head iommus; >>> ????? u32 *dt; /* page directory table */ >>> @@ -89,6 +97,7 @@ struct rk_iommu_domain { >>> ????? spinlock_t iommus_lock; /* lock for iommus list */ >>> ????? spinlock_t dt_lock; /* lock for modifying page directory table */ >>> ????? struct device *dma_dev; >>> +??? const struct rk_iommu_ops *rk_ops; >>> ? ????? struct iommu_domain domain; >>> ? }; >>> @@ -98,14 +107,6 @@ static const char * const rk_iommu_clocks[] = { >>> ????? "aclk", "iface", >>> ? }; >>> ? -struct rk_iommu_ops { >>> -??? phys_addr_t (*pt_address)(u32 dte); >>> -??? u32 (*mk_dtentries)(dma_addr_t pt_dma); >>> -??? u32 (*mk_ptentries)(phys_addr_t page, int prot); >>> -??? u64 dma_bit_mask; >>> -??? gfp_t gfp_flags; >>> -}; >>> - >>> ? struct rk_iommu { >>> ????? struct device *dev; >>> ????? void __iomem **bases; >>> @@ -117,6 +118,7 @@ struct rk_iommu { >>> ????? struct iommu_device iommu; >>> ????? struct list_head node; /* entry in rk_iommu_domain.iommus */ >>> ????? struct iommu_domain *domain; /* domain to which iommu is >>> attached */ >>> +??? const struct rk_iommu_ops *rk_ops; Do we really need the rk_ops on both the rk_iommu and rk_iommu_domain? >>> ? }; >>> ? ? struct rk_iommudata { >>> @@ -124,7 +126,6 @@ struct rk_iommudata { >>> ????? struct rk_iommu *iommu; >>> ? }; >>> ? -static const struct rk_iommu_ops *rk_ops; >>> ? static struct iommu_domain rk_identity_domain; >>> ? ? static inline void rk_table_flush(struct rk_iommu_domain *dom, >>> dma_addr_t dma, >>> @@ -510,7 +511,7 @@ static int rk_iommu_force_reset(struct rk_iommu >>> *iommu) >>> ?????? * and verifying that upper 5 (v1) or 7 (v2) nybbles are read >>> back. >>> ?????? */ >>> ????? for (i = 0; i < iommu->num_mmu; i++) { >>> -??????? dte_addr = rk_ops->pt_address(DTE_ADDR_DUMMY); >>> +??????? dte_addr = iommu->rk_ops->pt_address(DTE_ADDR_DUMMY); Maybe this patch it trying to do too much at once that makes it harder to review? To simplify review maybe one patch just drops the global rk_ops and a assigns a local version based on domain/iommu, and a second patch changes to use the iommu->rk_ops directly? Just a thought. >>> ????????? rk_iommu_write(iommu->bases[i], RK_MMU_DTE_ADDR, dte_addr); >>> ? ????????? if (dte_addr != rk_iommu_read(iommu->bases[i], >>> RK_MMU_DTE_ADDR)) { >>> @@ -551,7 +552,7 @@ static void log_iova(struct rk_iommu *iommu, int >>> index, dma_addr_t iova) >>> ????? page_offset = rk_iova_page_offset(iova); >>> ? ????? mmu_dte_addr = rk_iommu_read(base, RK_MMU_DTE_ADDR); >>> -??? mmu_dte_addr_phys = rk_ops->pt_address(mmu_dte_addr); >>> +??? mmu_dte_addr_phys = iommu->rk_ops->pt_address(mmu_dte_addr); >>> ? ????? dte_addr_phys = mmu_dte_addr_phys + (4 * dte_index); >>> ????? dte_addr = phys_to_virt(dte_addr_phys); >>> @@ -560,14 +561,14 @@ static void log_iova(struct rk_iommu *iommu, >>> int index, dma_addr_t iova) >>> ????? if (!rk_dte_is_pt_valid(dte)) >>> ????????? goto print_it; >>> ? -??? pte_addr_phys = rk_ops->pt_address(dte) + (pte_index * 4); >>> +??? pte_addr_phys = iommu->rk_ops->pt_address(dte) + (pte_index * 4); >>> ????? pte_addr = phys_to_virt(pte_addr_phys); >>> ????? pte = *pte_addr; >>> ? ????? if (!rk_pte_is_page_valid(pte)) >>> ????????? goto print_it; >>> ? -??? page_addr_phys = rk_ops->pt_address(pte) + page_offset; >>> +??? page_addr_phys = iommu->rk_ops->pt_address(pte) + page_offset; >>> ????? page_flags = pte & RK_PTE_PAGE_FLAGS_MASK; >>> ? ? print_it: >>> @@ -663,13 +664,13 @@ static phys_addr_t rk_iommu_iova_to_phys(struct >>> iommu_domain *domain, >>> ????? if (!rk_dte_is_pt_valid(dte)) >>> ????????? goto out; >>> ? -??? pt_phys = rk_ops->pt_address(dte); >>> +??? pt_phys = rk_domain->rk_ops->pt_address(dte); >>> ????? page_table = (u32 *)phys_to_virt(pt_phys); >>> ????? pte = page_table[rk_iova_pte_index(iova)]; >>> ????? if (!rk_pte_is_page_valid(pte)) >>> ????????? goto out; >>> ? -??? phys = rk_ops->pt_address(pte) + rk_iova_page_offset(iova); >>> +??? phys = rk_domain->rk_ops->pt_address(pte) + >>> rk_iova_page_offset(iova); >>> ? out: >>> ????? spin_unlock_irqrestore(&rk_domain->dt_lock, flags); >>> ? @@ -730,7 +731,7 @@ static u32 *rk_dte_get_page_table(struct >>> rk_iommu_domain *rk_domain, >>> ????? if (rk_dte_is_pt_valid(dte)) >>> ????????? goto done; >>> ? -??? page_table = iommu_alloc_pages_sz(GFP_ATOMIC | rk_ops->gfp_flags, >>> +??? page_table = iommu_alloc_pages_sz(GFP_ATOMIC | >>> rk_domain->rk_ops->gfp_flags, >>> ??????????????????????? SPAGE_SIZE); >>> ????? if (!page_table) >>> ????????? return ERR_PTR(-ENOMEM); >>> @@ -742,13 +743,13 @@ static u32 *rk_dte_get_page_table(struct >>> rk_iommu_domain *rk_domain, >>> ????????? return ERR_PTR(-ENOMEM); >>> ????? } >>> ? -??? dte = rk_ops->mk_dtentries(pt_dma); >>> +??? dte = rk_domain->rk_ops->mk_dtentries(pt_dma); >>> ????? *dte_addr = dte; >>> ? ????? rk_table_flush(rk_domain, >>> ???????????????? rk_domain->dt_dma + dte_index * sizeof(u32), 1); >>> ? done: >>> -??? pt_phys = rk_ops->pt_address(dte); >>> +??? pt_phys = rk_domain->rk_ops->pt_address(dte); >>> ????? return (u32 *)phys_to_virt(pt_phys); >>> ? } >>> ? @@ -790,7 +791,7 @@ static int rk_iommu_map_iova(struct >>> rk_iommu_domain *rk_domain, u32 *pte_addr, >>> ????????? if (rk_pte_is_page_valid(pte)) >>> ????????????? goto unwind; >>> ? -??????? pte_addr[pte_count] = rk_ops->mk_ptentries(paddr, prot); >>> +??????? pte_addr[pte_count] = rk_domain->rk_ops->mk_ptentries(paddr, >>> prot); >>> ? ????????? paddr += SPAGE_SIZE; >>> ????? } >>> @@ -812,7 +813,7 @@ static int rk_iommu_map_iova(struct >>> rk_iommu_domain *rk_domain, u32 *pte_addr, >>> ????????????????? pte_count * SPAGE_SIZE); >>> ? ????? iova += pte_count * SPAGE_SIZE; >>> -??? page_phys = rk_ops->pt_address(pte_addr[pte_count]); >>> +??? page_phys = rk_domain->rk_ops->pt_address(pte_addr[pte_count]); >>> ????? pr_err("iova: %pad already mapped to %pa cannot remap to phys: >>> %pa prot: %#x\n", >>> ???????????? &iova, &page_phys, &paddr, prot); >>> ? @@ -849,7 +850,7 @@ static int rk_iommu_map(struct iommu_domain >>> *domain, unsigned long _iova, >>> ????? pte_index = rk_iova_pte_index(iova); >>> ????? pte_addr = &page_table[pte_index]; >>> ? -??? pte_dma = rk_ops->pt_address(dte_index) + pte_index * >>> sizeof(u32); >>> +??? pte_dma = rk_domain->rk_ops->pt_address(dte_index) + pte_index * >>> sizeof(u32); >>> ????? ret = rk_iommu_map_iova(rk_domain, pte_addr, pte_dma, iova, >>> ????????????????? paddr, size, prot); >>> ? @@ -887,7 +888,7 @@ static size_t rk_iommu_unmap(struct >>> iommu_domain *domain, unsigned long _iova, >>> ????????? return 0; >>> ????? } >>> ? -??? pt_phys = rk_ops->pt_address(dte); >>> +??? pt_phys = rk_domain->rk_ops->pt_address(dte); >>> ????? pte_addr = (u32 *)phys_to_virt(pt_phys) + rk_iova_pte_index(iova); >>> ????? pte_dma = pt_phys + rk_iova_pte_index(iova) * sizeof(u32); >>> ????? unmap_size = rk_iommu_unmap_iova(rk_domain, pte_addr, pte_dma, >>> size); >>> @@ -945,7 +946,7 @@ static int rk_iommu_enable(struct rk_iommu *iommu) >>> ? ????? for (i = 0; i < iommu->num_mmu; i++) { >>> ????????? rk_iommu_write(iommu->bases[i], RK_MMU_DTE_ADDR, >>> - rk_ops->mk_dtentries(rk_domain->dt_dma)); >>> + iommu->rk_ops->mk_dtentries(rk_domain->dt_dma)); >>> ????????? rk_iommu_base_command(iommu->bases[i], RK_MMU_CMD_ZAP_CACHE); >>> ????????? rk_iommu_write(iommu->bases[i], RK_MMU_INT_MASK, >>> RK_MMU_IRQ_MASK); >>> ????? } >>> @@ -1068,17 +1069,19 @@ static struct iommu_domain >>> *rk_iommu_domain_alloc_paging(struct device *dev) >>> ????? if (!rk_domain) >>> ????????? return NULL; >>> ? +??? iommu = rk_iommu_from_dev(dev); >>> +??? rk_domain->rk_ops = iommu->rk_ops; As mentioned above, this seem strange and is possible just a shortcut due to current call-paths? Why do we assign "iommu ops" to the domain? Regards, Jonas >>> + >>> ????? /* >>> ?????? * rk32xx iommus use a 2 level pagetable. >>> ?????? * Each level1 (dt) and level2 (pt) table has 1024 4-byte entries. >>> ?????? * Allocate one 4 KiB page for each table. >>> ?????? */ >>> -??? rk_domain->dt = iommu_alloc_pages_sz(GFP_KERNEL | >>> rk_ops->gfp_flags, >>> +??? rk_domain->dt = iommu_alloc_pages_sz(GFP_KERNEL | >>> rk_domain->rk_ops->gfp_flags, >>> ?????????????????????????? SPAGE_SIZE); >>> ????? if (!rk_domain->dt) >>> ????????? goto err_free_domain; >>> ? -??? iommu = rk_iommu_from_dev(dev); >>> ????? rk_domain->dma_dev = iommu->dev; >>> ????? rk_domain->dt_dma = dma_map_single(rk_domain->dma_dev, >>> rk_domain->dt, >>> ???????????????????????? SPAGE_SIZE, DMA_TO_DEVICE); >>> @@ -1117,7 +1120,7 @@ static void rk_iommu_domain_free(struct >>> iommu_domain *domain) >>> ????? for (i = 0; i < NUM_DT_ENTRIES; i++) { >>> ????????? u32 dte = rk_domain->dt[i]; >>> ????????? if (rk_dte_is_pt_valid(dte)) { >>> -??????????? phys_addr_t pt_phys = rk_ops->pt_address(dte); >>> +??????????? phys_addr_t pt_phys = rk_domain->rk_ops->pt_address(dte); >>> ????????????? u32 *page_table = phys_to_virt(pt_phys); >>> ????????????? dma_unmap_single(rk_domain->dma_dev, pt_phys, >>> ?????????????????????? SPAGE_SIZE, DMA_TO_DEVICE); >>> @@ -1197,7 +1200,6 @@ static int rk_iommu_probe(struct >>> platform_device *pdev) >>> ????? struct device *dev = &pdev->dev; >>> ????? struct rk_iommu *iommu; >>> ????? struct resource *res; >>> -??? const struct rk_iommu_ops *ops; >>> ????? int num_res = pdev->num_resources; >>> ????? int err, i; >>> ? @@ -1211,16 +1213,9 @@ static int rk_iommu_probe(struct >>> platform_device *pdev) >>> ????? iommu->dev = dev; >>> ????? iommu->num_mmu = 0; >>> ? -??? ops = of_device_get_match_data(dev); >>> -??? if (!rk_ops) >>> -??????? rk_ops = ops; >>> - >>> -??? /* >>> -???? * That should not happen unless different versions of the >>> -???? * hardware block are embedded the same SoC >>> -???? */ >>> -??? if (WARN_ON(rk_ops != ops)) >>> -??????? return -EINVAL; >>> +??? iommu->rk_ops = of_device_get_match_data(dev); >>> +??? if (!iommu->rk_ops) >>> +??????? return -ENOENT; >>> ? ????? iommu->bases = devm_kcalloc(dev, num_res, sizeof(*iommu->bases), >>> ????????????????????? GFP_KERNEL); >>> @@ -1286,7 +1281,7 @@ static int rk_iommu_probe(struct >>> platform_device *pdev) >>> ????????????? goto err_pm_disable; >>> ????? } >>> ? -??? dma_set_mask_and_coherent(dev, rk_ops->dma_bit_mask); >>> +??? dma_set_mask_and_coherent(dev, iommu->rk_ops->dma_bit_mask); >>> ? ????? err = iommu_device_sysfs_add(&iommu->iommu, dev, NULL, >>> dev_name(dev)); >>> ????? if (err) >>> >> > > _______________________________________________ > Linux-rockchip mailing list > Linux-rockchip at lists.infradead.org > http://lists.infradead.org/mailman/listinfo/linux-rockchip From michel.daenzer at mailbox.org Wed Apr 1 01:27:46 2026 From: michel.daenzer at mailbox.org (=?UTF-8?Q?Michel_D=C3=A4nzer?=) Date: Wed, 1 Apr 2026 10:27:46 +0200 Subject: [PATCH v11 03/22] drm: Add new general DRM property "color format" In-Reply-To: <7991520.DvuYhMxLoT@workhorse> References: <20260324-color-format-v11-0-605559af4fb4@collabora.com> <7991520.DvuYhMxLoT@workhorse> Message-ID: <824bf7a3-44ad-4da3-b019-08e43a4cc192@mailbox.org> On 3/26/26 13:02, Nicolas Frattaroli wrote: > On Thursday, 26 March 2026 12:16:12 Central European Standard Time Dave Stevenson wrote: >> On Wed, 25 Mar 2026 at 13:43, Ville Syrj?l? >> wrote: >>> On Wed, Mar 25, 2026 at 12:49:19PM +0000, Dave Stevenson wrote: >>>> On Tue, 24 Mar 2026 at 16:02, Nicolas Frattaroli >>>> wrote: >>>>> >>>>> +/** >>>>> + * enum drm_connector_color_format - Connector Color Format Request >>>>> + * >>>>> + * This enum, unlike &enum drm_output_color_format, is used to specify requests >>>>> + * for a specific color format on a connector through the DRM "color format" >>>>> + * property. The difference is that it has an "AUTO" value to specify that >>>>> + * no specific choice has been made. >>>>> + */ >>>>> +enum drm_connector_color_format { >>>>> + /** >>>>> + * @DRM_CONNECTOR_COLOR_FORMAT_AUTO: The driver or display protocol >>>>> + * helpers should pick a suitable color format. All implementations of a >>>>> + * specific display protocol must behave the same way with "AUTO", but >>>>> + * different display protocols do not necessarily have the same "AUTO" >>>>> + * semantics. >>>>> + * >>>>> + * For HDMI, "AUTO" picks RGB, but falls back to YCbCr 4:2:0 if the >>>>> + * bandwidth required for full-scale RGB is not available, or the mode >>>>> + * is YCbCr 4:2:0-only, as long as the mode and output both support >>>>> + * YCbCr 4:2:0. >>>> >>>> Is there a reason you propose dropping back to YCbCr 4:2:0 without >>>> trying YCbCr 4:2:2 first? Minimising the subsampling is surely >>>> beneficial, and vc4 for one can do 4:2:2 but not 4:2:0. >>> >>> On HDMI 4:2:2 is always 12bpc, so it doesn't save any bandwidth >>> compared to 8bpc 4:4:4. >> >> It does save bandwidth against 10 or 12bpc RGB 4:4:4. >> >> Or is the implication that max_bpc = 12 and >> DRM_CONNECTOR_COLOR_FORMAT_AUTO should drop bpc down to 8 and select >> RGB in preference to selecting 4:2:2? > > Yes. Some people consider max-bpc to not be a legitimate way of requesting > an actual bpc, and don't think drivers will choose the highest bpc <= max-bpc, > and instead may negotiate a fantasy number anywhere below or equal to max-bpc. Ridiculing others like this for disagreeing with you is uncalled for. Is there any evidence for your claim that the driver must always use the highest possible bpc <= max-bpc? > Of course this logic could be done in userspace which knows whether the > less chroma for more bit depth trade-off is worth it, but userspace does > not know the negotiated link bpc, and my attempts at adding a property for > it are being blocked. Assuming you're referring to the concerns I raised there, I don't have the power or intent to block it. -- Earthling Michel D?nzer \ GNOME / Xwayland / Mesa developer https://redhat.com \ Libre software enthusiast From jonas at kwiboo.se Wed Apr 1 01:41:54 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Wed, 1 Apr 2026 10:41:54 +0200 Subject: [PATCH] iommu/rockchip: fix page table allocation flags for v2 IOMMU In-Reply-To: <0f285782-b12a-4abd-bca7-b6c549bed59f@rock-chips.com> References: <20260331075010.1463-1-midgy971@gmail.com> <0f285782-b12a-4abd-bca7-b6c549bed59f@rock-chips.com> Message-ID: Hi Simon, On 4/1/2026 9:48 AM, Simon wrote: > Hi Midgy, > > ? 2026/3/31 15:50, Midgy BALON ??: >> commit 2a7e6400f72b ("iommu: rockchip: Allocate tables from all >> available memory for IOMMU v2") removed GFP_DMA32 from >> iommu_data_ops_v2, reasoning that RK356x and RK3588 IOMMU v2 hardware >> supports up to 40-bit physical addresses for page tables. However, the >> RK3568 IOMMU page-table walker uses a 32-bit AXI bus: it cannot access >> physical addresses above 4 GB regardless of the address encoding range. >> >> On boards with more than 4 GB of RAM (e.g. 8 GB LPDDR4X), removing >> GFP_DMA32 causes two distinct failure modes: >> >> 1. Direct allocation above 4 GB: iommu_alloc_pages_sz() may return >> memory above 0x100000000. The hardware page-table walker issues a >> bus error trying to dereference those addresses, causing an IOMMU >> fault on the first DMA transaction. > > Which IP block is hitting this? We'd like to take a look on our end. I have seen reports that the NPU MMU on RK3568/RK3566 is having some issue using DTE/PTE with >32-bit addresses, maybe it uses a different MMU hw revision or has some hw errata? >From my own testing at least the VOP2 MMU on RK3568 (and RK3588) was able to handle 40-bit addressable DTE/PTE, hence the original commit 2a7e6400f72b ("iommu: rockchip: Allocate tables from all available memory for IOMMU v2"). As also mentioned in my reply at [1], maybe the NPU MMU has some hw limitation or errata and may need to use a different compatible. [1] https://lore.kernel.org/r/3cd63b3d-1c5e-4a11-856e-c4aeb5d97d55 at kwiboo.se/ Regards, Jonas > >> 2. SWIOTLB bounce-buffer poisoning: without GFP_DMA32, page tables land >> above the SWIOTLB window. dma_map_single() with DMA_BIT_MASK(32) >> then bounces them into a buffer below 4 GB. rk_dte_get_page_table() >> returns phys_to_virt() of the bounce buffer address; PTEs are written >> there; the next dma_sync_single_for_device(DMA_TO_DEVICE) copies the >> original (zero) data back over the bounce buffer, silently erasing the >> freshly written PTEs. The IOMMU faults because every PTE reads as zero. > > This probably need a separate patch. One way to fix it would be to track the > original L2 page table base addresses in struct rk_iommu_domain, > then have rk_dte_get_page_table() return the tracked address instead of > deriving it from the DTE. > >> Restore GFP_DMA32 (and DMA_BIT_MASK(32)) for iommu_data_ops_v2, which >> currently only serves "rockchip,rk3568-iommu" in mainline. >> >> Tested on Radxa ROCK 3B (RK3568, 8 GB LPDDR4X): >> - MobileNetV1 via RKNN: 5.8 ms/inference (IOMMU mode) >> - YOLOv5s 640x640 via RKNN: ~57 ms/inference (IOMMU mode) >> - No IOMMU faults, correct inference results >> >> Fixes: 2a7e6400f72b ("iommu: rockchip: Allocate tables from all available memory for IOMMU v2") >> Cc: stable at vger.kernel.org >> Cc: Jonas Karlman >> Signed-off-by: Midgy BALON >> --- >> drivers/iommu/rockchip-iommu.c | 4 ++-- >> 1 file changed, 2 insertions(+), 2 deletions(-) >> >> diff --git a/drivers/iommu/rockchip-iommu.c b/drivers/iommu/rockchip-iommu.c >> index 85f3667e797..8b45db29471 100644 >> --- a/drivers/iommu/rockchip-iommu.c >> +++ b/drivers/iommu/rockchip-iommu.c >> @@ -1358,8 +1358,8 @@ static struct rk_iommu_ops iommu_data_ops_v2 = { >> .pt_address = &rk_dte_pt_address_v2, >> .mk_dtentries = &rk_mk_dte_v2, >> .mk_ptentries = &rk_mk_pte_v2, >> - .dma_bit_mask = DMA_BIT_MASK(40), >> - .gfp_flags = 0, >> + .dma_bit_mask = DMA_BIT_MASK(32), >> + .gfp_flags = GFP_DMA32, >> }; >> >> static const struct of_device_id rk_iommu_dt_ids[] = { From damon.ding at rock-chips.com Wed Apr 1 02:14:40 2026 From: damon.ding at rock-chips.com (Damon Ding) Date: Wed, 1 Apr 2026 17:14:40 +0800 Subject: [PATCH v12 03/17] drm/exynos: exynos_dp: Remove &exynos_dp_device.ptn_bridge In-Reply-To: <20260401091454.25730-1-damon.ding@rock-chips.com> References: <20260401091454.25730-1-damon.ding@rock-chips.com> Message-ID: <20260401091454.25730-4-damon.ding@rock-chips.com> Use &analogix_dp_plat_data.bridge instead of &exynos_dp_device.ptn_bridge directly. Signed-off-by: Damon Ding Reviewed-by: Dmitry Baryshkov Reviewed-by: Luca Ceresoli Tested-by: Marek Szyprowski Tested-by: Heiko Stuebner (on rk3588) ------ Changes in v3: - Fix the typographical error for &dp->plat_data.bridge. Changes in v4: - Rename the &analogix_dp_plat_data.bridge to &analogix_dp_plat_data.next_bridge. Changes in v9: - Add Reviewed-by and Tested-by tags. --- drivers/gpu/drm/exynos/exynos_dp.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c index 5bcf41e0bd04..f469ac5b3c2a 100644 --- a/drivers/gpu/drm/exynos/exynos_dp.c +++ b/drivers/gpu/drm/exynos/exynos_dp.c @@ -36,7 +36,6 @@ struct exynos_dp_device { struct drm_encoder encoder; struct drm_connector *connector; - struct drm_bridge *ptn_bridge; struct drm_device *drm_dev; struct device *dev; @@ -106,8 +105,8 @@ static int exynos_dp_bridge_attach(struct analogix_dp_plat_data *plat_data, dp->connector = connector; /* Pre-empt DP connector creation if there's a bridge */ - if (dp->ptn_bridge) { - ret = drm_bridge_attach(&dp->encoder, dp->ptn_bridge, bridge, + if (plat_data->next_bridge) { + ret = drm_bridge_attach(&dp->encoder, plat_data->next_bridge, bridge, 0); if (ret) return ret; @@ -155,7 +154,7 @@ static int exynos_dp_bind(struct device *dev, struct device *master, void *data) dp->drm_dev = drm_dev; - if (!dp->plat_data.panel && !dp->ptn_bridge) { + if (!dp->plat_data.panel && !dp->plat_data.next_bridge) { ret = exynos_dp_dt_parse_panel(dp); if (ret) return ret; @@ -232,6 +231,7 @@ static int exynos_dp_probe(struct platform_device *pdev) /* The remote port can be either a panel or a bridge */ dp->plat_data.panel = panel; + dp->plat_data.next_bridge = bridge; dp->plat_data.dev_type = EXYNOS_DP; dp->plat_data.power_on = exynos_dp_poweron; dp->plat_data.power_off = exynos_dp_poweroff; @@ -239,8 +239,6 @@ static int exynos_dp_probe(struct platform_device *pdev) dp->plat_data.get_modes = exynos_dp_get_modes; dp->plat_data.skip_connector = !!bridge; - dp->ptn_bridge = bridge; - out: dp->adp = analogix_dp_probe(dev, &dp->plat_data); if (IS_ERR(dp->adp)) -- 2.34.1 From damon.ding at rock-chips.com Wed Apr 1 02:14:41 2026 From: damon.ding at rock-chips.com (Damon Ding) Date: Wed, 1 Apr 2026 17:14:41 +0800 Subject: [PATCH v12 04/17] drm/exynos: exynos_dp: Remove unused &exynos_dp_device.connector In-Reply-To: <20260401091454.25730-1-damon.ding@rock-chips.com> References: <20260401091454.25730-1-damon.ding@rock-chips.com> Message-ID: <20260401091454.25730-5-damon.ding@rock-chips.com> The &exynos_dp_device.connector is assigned in exynos_dp_bridge_attach() but never used. It should make sense to remove it. Signed-off-by: Damon Ding Reviewed-by: Dmitry Baryshkov Reviewed-by: Luca Ceresoli Tested-by: Marek Szyprowski Tested-by: Heiko Stuebner (on rk3588) --- Changes in v5: - Fix the 'drm/bridge' to 'drm/exynos' in commit message. Changes in v9: - Add Reviewed-by and Tested-by tags. --- drivers/gpu/drm/exynos/exynos_dp.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c index f469ac5b3c2a..e20513164032 100644 --- a/drivers/gpu/drm/exynos/exynos_dp.c +++ b/drivers/gpu/drm/exynos/exynos_dp.c @@ -35,7 +35,6 @@ struct exynos_dp_device { struct drm_encoder encoder; - struct drm_connector *connector; struct drm_device *drm_dev; struct device *dev; @@ -102,8 +101,6 @@ static int exynos_dp_bridge_attach(struct analogix_dp_plat_data *plat_data, struct exynos_dp_device *dp = to_dp(plat_data); int ret; - dp->connector = connector; - /* Pre-empt DP connector creation if there's a bridge */ if (plat_data->next_bridge) { ret = drm_bridge_attach(&dp->encoder, plat_data->next_bridge, bridge, -- 2.34.1 From damon.ding at rock-chips.com Wed Apr 1 02:14:38 2026 From: damon.ding at rock-chips.com (Damon Ding) Date: Wed, 1 Apr 2026 17:14:38 +0800 Subject: [PATCH v12 01/17] drm/bridge: analogix_dp: Add &analogix_dp_plat_data.next_bridge In-Reply-To: <20260401091454.25730-1-damon.ding@rock-chips.com> References: <20260401091454.25730-1-damon.ding@rock-chips.com> Message-ID: <20260401091454.25730-2-damon.ding@rock-chips.com> In order to move the panel/bridge parsing and attachmenet to the Analogix side, add component struct drm_bridge *next_bridge to platform data struct analogix_dp_plat_data. The movement makes sense because the panel/bridge should logically be positioned behind the Analogix bridge in the display pipeline. Signed-off-by: Damon Ding Reviewed-by: Dmitry Baryshkov Reviewed-by: Luca Ceresoli Tested-by: Marek Szyprowski Tested-by: Heiko Stuebner (on rk3588) --- Changes in v4: - Rename the &analogix_dp_plat_data.bridge to &analogix_dp_plat_data.next_bridge Changes in v9: - Add Reviewed-by and Tested-by tags. --- include/drm/bridge/analogix_dp.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/drm/bridge/analogix_dp.h b/include/drm/bridge/analogix_dp.h index cf17646c1310..582357c20640 100644 --- a/include/drm/bridge/analogix_dp.h +++ b/include/drm/bridge/analogix_dp.h @@ -27,6 +27,7 @@ static inline bool is_rockchip(enum analogix_dp_devtype type) struct analogix_dp_plat_data { enum analogix_dp_devtype dev_type; struct drm_panel *panel; + struct drm_bridge *next_bridge; struct drm_encoder *encoder; struct drm_connector *connector; bool skip_connector; -- 2.34.1 From damon.ding at rock-chips.com Wed Apr 1 02:14:37 2026 From: damon.ding at rock-chips.com (Damon Ding) Date: Wed, 1 Apr 2026 17:14:37 +0800 Subject: [PATCH v12 00/17] Apply drm_bridge_connector and panel_bridge helper for the Analogix DP driver Message-ID: <20260401091454.25730-1-damon.ding@rock-chips.com> PATCH 1 is to add a new parameter to store the point of next bridge. PATCH 2 is to make legacy bridge driver more universal. PATCH 3-10 are preparations for apply drm_bridge_connector helper. PATCH 11 is to apply the drm_bridge_connector helper. PATCH 12-14 are to move the panel/bridge parsing to the Analogix side. PATCH 15 is to attach the next bridge on Analogix side uniformly. PATCH 16-17 are to apply the panel_bridge helper. Damon Ding (17): drm/bridge: analogix_dp: Add &analogix_dp_plat_data.next_bridge drm/bridge: Move legacy bridge driver out of imx directory for multi-platform use drm/exynos: exynos_dp: Remove &exynos_dp_device.ptn_bridge drm/exynos: exynos_dp: Remove unused &exynos_dp_device.connector drm/exynos: exynos_dp: Apply of-display-mode-bridge to parse the display-timings node drm/bridge: analogix_dp: Remove redundant &analogix_dp_plat_data.skip_connector drm/bridge: analogix_dp: Move the color format check to .atomic_check() for Rockchip platforms drm/bridge: analogix_dp: Remove unused &analogix_dp_plat_data.get_modes() drm/bridge: analogix_dp: Remove unused struct drm_connector* for &analogix_dp_plat_data.attach() drm/bridge: analogix_dp: Pass struct drm_atomic_state* for analogix_dp_bridge_mode_set() drm/bridge: analogix_dp: Apply drm_bridge_connector helper drm/bridge: analogix_dp: Add new API analogix_dp_finish_probe() drm/rockchip: analogix_dp: Apply analogix_dp_finish_probe() drm/exynos: exynos_dp: Apply analogix_dp_finish_probe() drm/bridge: analogix_dp: Attach the next bridge in analogix_dp_bridge_attach() drm/bridge: analogix_dp: Remove bridge disabing and panel unpreparing in analogix_dp_unbind() drm/bridge: analogix_dp: Apply panel_bridge helper drivers/gpu/drm/bridge/Kconfig | 10 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/analogix/Kconfig | 1 + .../drm/bridge/analogix/analogix_dp_core.c | 236 +++++++++--------- .../drm/bridge/analogix/analogix_dp_core.h | 1 - drivers/gpu/drm/bridge/imx/Kconfig | 10 - drivers/gpu/drm/bridge/imx/Makefile | 1 - .../gpu/drm/bridge/imx/imx-legacy-bridge.c | 91 ------- .../gpu/drm/bridge/of-display-mode-bridge.c | 93 +++++++ drivers/gpu/drm/exynos/Kconfig | 2 + drivers/gpu/drm/exynos/exynos_dp.c | 110 ++------ drivers/gpu/drm/imx/ipuv3/Kconfig | 4 +- drivers/gpu/drm/imx/ipuv3/imx-ldb.c | 6 +- drivers/gpu/drm/imx/ipuv3/parallel-display.c | 5 +- drivers/gpu/drm/rockchip/Kconfig | 1 + .../gpu/drm/rockchip/analogix_dp-rockchip.c | 67 +---- include/drm/bridge/analogix_dp.h | 8 +- include/drm/bridge/imx.h | 17 -- include/drm/bridge/of-display-mode-bridge.h | 17 ++ 19 files changed, 295 insertions(+), 386 deletions(-) delete mode 100644 drivers/gpu/drm/bridge/imx/imx-legacy-bridge.c create mode 100644 drivers/gpu/drm/bridge/of-display-mode-bridge.c delete mode 100644 include/drm/bridge/imx.h create mode 100644 include/drm/bridge/of-display-mode-bridge.h --- Changes in v2: - Update Exynos DP driver synchronously. - Move the panel/bridge parsing to the Analogix side. Changes in v3: - Rebase for the existing devm_drm_bridge_alloc() applying commit. - Fix the typographical error of panel/bridge check in exynos_dp_bind(). - Squash all commits related to skip_connector deletion in both Exynos and Analogix code into one. - Apply panel_bridge helper to make the codes more concise. - Fix the handing of bridge in analogix_dp_bridge_get_modes(). - Remove unnecessary parameter struct drm_connector* for callback &analogix_dp_plat_data.attach(). - In order to decouple the connector driver and the bridge driver, move the bridge connector initilization to the Rockchip and Exynos sides. Changes in v4: - Rebase for the applied &drm_bridge_funcs.detect() modification commit. - Rename analogix_dp_find_panel_or_bridge() to analogix_dp_finish_probe(). - Drop the drmm_encoder_init() modification commit. - Rename the &analogix_dp_plat_data.bridge to &analogix_dp_plat_data.next_bridge. Changes in v5: - Add legacy bridge to parse the display-timings node under the dp node for Exynos side. - Move color format check to &drm_connector_helper_funcs.atomic_check() in order to get rid of &analogix_dp_plat_data.get_modes(). - Remove unused callback &analogix_dp_plat_data.get_modes(). - Distinguish the &drm_bridge->ops of Analogix bridge based on whether the downstream device is a panel, a bridge or neither. - Select DRM_DISPLAY_DP_AUX_BUS for DRM_ANALOGIX_DP, and remove it for ROCKCHIP_ANALOGIX_DP. - Apply rockchip_dp_attach() to support the next bridge attachment for the Rockchip side. - Move next_bridge attachment from Analogix side to Rockchip/Exynos sides. Changes in v6: - Move legacy bridge driver out of imx directory for multi-platform use. - Apply DRM legacy bridge to parse display timings intead of implementing the same codes only for Exynos DP. - Ensure last bridge determines EDID/modes detection capabilities in DRM bridge_connector driver. - Remove unnecessary drm_bridge_get_modes() in analogix_dp_bridge_get_modes(). - Simplify analogix_dp_bridge_edid_read(). - If the next is a bridge, set DRM_BRIDGE_OP_DETECT and return connector_status_connected in analogix_dp_bridge_detect(). - Set flag DRM_BRIDGE_ATTACH_NO_CONNECTOR for bridge attachment while binding. Meanwhile, make DRM_BRIDGE_ATTACH_NO_CONNECTOR unsuppported in analogix_dp_bridge_attach(). - Move the next bridge attachment to the Analogix side rather than scattered on Rockchip and Exynos sides. - Remove the unnecessary analogix_dp_bridge_get_modes(). - Squash [PATCH v5 15/17] into [PATCH v5 17/17]. - Fix the &drm_bridge->ops to DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_DETECT. Changes in v7: - As Luca suggested, simplify the code and related comment for bridge_connector modifications. Additionally, move the commit related to bridge_connector to the top of this patch series. - Rename legacy-bridge driver to of-display-mode-bridge driver. - Remove unnecessary API drm_bridge_is_legacy() and apply a temporary flag &exynos_dp_device.has_of_bridge instead, which will be removed finally. - Remove exynos_dp_legacy_bridge_init() and inline API devm_drm_of_display_mode_bridge(). Changes in v8: - Adapt the related modifications to the newest bridge_connector driver. Changes in v9: - Fix the Kconfig help text for CONFIG_DRM_OF_DISPLAY_MODE_BRIDGE. - Add Tested-by tag from Heiko. Changes in v10: - Fix to use dev_err_probe() in newly added API analogix_dp_finish_probe(). - Expaned commit message for [PATCH v9 9/15] and [PATCH v9 10/15]. - Split [PATCH v9 9/15] into serval smaller commits. - Add Reviewed-by tags from Luca. Changes in v11: - Merge [PATCH v10 12/18] into [PATCH v10 11/18]. - Fix the bridge flag to 'flags | DRM_BRIDGE_ATTACH_NO_CONNECTOR' in [PATCH v10 11/18]. - Add Reviewed-by tags from Luca. Changes in v12: - Restore accidentally removed DRM_BRIDGE_CONNECTOR Kconfig in v10. -- 2.34.1 From damon.ding at rock-chips.com Wed Apr 1 02:14:39 2026 From: damon.ding at rock-chips.com (Damon Ding) Date: Wed, 1 Apr 2026 17:14:39 +0800 Subject: [PATCH v12 02/17] drm/bridge: Move legacy bridge driver out of imx directory for multi-platform use In-Reply-To: <20260401091454.25730-1-damon.ding@rock-chips.com> References: <20260401091454.25730-1-damon.ding@rock-chips.com> Message-ID: <20260401091454.25730-3-damon.ding@rock-chips.com> As suggested by Dmitry, the DRM legacy bridge driver can be pulled out of imx/ subdir for multi-platform use. The driver is also renamed to make it more generic and suitable for platforms other than i.MX. Signed-off-by: Damon Ding Suggested-by: Dmitry Baryshkov Reviewed-by: Luca Ceresoli Tested-by: Marek Szyprowski Tested-by: Heiko Stuebner (on rk3588) --- Changes in v7: - Rename legacy-bridge to of-display-mode-bridge. - Remove unnecessary API drm_bridge_is_legacy(). Changes in v9: - Fix the Kconfig help text. - Add Tested-by tag. Changes in v10: - Add Reviewed-by tag. --- drivers/gpu/drm/bridge/Kconfig | 10 ++ drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/imx/Kconfig | 10 -- drivers/gpu/drm/bridge/imx/Makefile | 1 - .../gpu/drm/bridge/imx/imx-legacy-bridge.c | 91 ------------------ .../gpu/drm/bridge/of-display-mode-bridge.c | 93 +++++++++++++++++++ drivers/gpu/drm/imx/ipuv3/Kconfig | 4 +- drivers/gpu/drm/imx/ipuv3/imx-ldb.c | 6 +- drivers/gpu/drm/imx/ipuv3/parallel-display.c | 5 +- include/drm/bridge/imx.h | 17 ---- include/drm/bridge/of-display-mode-bridge.h | 17 ++++ 11 files changed, 129 insertions(+), 126 deletions(-) delete mode 100644 drivers/gpu/drm/bridge/imx/imx-legacy-bridge.c create mode 100644 drivers/gpu/drm/bridge/of-display-mode-bridge.c delete mode 100644 include/drm/bridge/imx.h create mode 100644 include/drm/bridge/of-display-mode-bridge.h diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 39385deafc68..6563d367cad8 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -251,6 +251,16 @@ config DRM_NXP_PTN3460 help NXP PTN3460 eDP-LVDS bridge chip driver. +config DRM_OF_DISPLAY_MODE_BRIDGE + tristate + depends on DRM_BRIDGE && OF + help + This is a DRM bridge implementation that uses of_get_drm_display_mode + to acquire display mode. + + It exists for compatibility with legacy display mode parsing, in order + to conform to the panel-bridge framework. + config DRM_PARADE_PS8622 tristate "Parade eDP/LVDS bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index 909c21cc3acd..c54018a014d3 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o obj-$(CONFIG_DRM_MICROCHIP_LVDS_SERIALIZER) += microchip-lvds.o obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o +obj-$(CONFIG_DRM_OF_DISPLAY_MODE_BRIDGE) += of-display-mode-bridge.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o obj-$(CONFIG_DRM_PARADE_PS8640) += parade-ps8640.o obj-$(CONFIG_DRM_SAMSUNG_DSIM) += samsung-dsim.o diff --git a/drivers/gpu/drm/bridge/imx/Kconfig b/drivers/gpu/drm/bridge/imx/Kconfig index b9028a5e5a06..8877b9789868 100644 --- a/drivers/gpu/drm/bridge/imx/Kconfig +++ b/drivers/gpu/drm/bridge/imx/Kconfig @@ -3,16 +3,6 @@ if ARCH_MXC || COMPILE_TEST config DRM_IMX_LDB_HELPER tristate -config DRM_IMX_LEGACY_BRIDGE - tristate - depends on DRM_IMX - help - This is a DRM bridge implementation for the DRM i.MX IPUv3 driver, - that uses of_get_drm_display_mode to acquire display mode. - - Newer designs should not use this bridge and should use proper panel - driver instead. - config DRM_IMX8MP_DW_HDMI_BRIDGE tristate "Freescale i.MX8MP HDMI-TX bridge support" depends on OF diff --git a/drivers/gpu/drm/bridge/imx/Makefile b/drivers/gpu/drm/bridge/imx/Makefile index 8d01fda25451..69d9f9abbe36 100644 --- a/drivers/gpu/drm/bridge/imx/Makefile +++ b/drivers/gpu/drm/bridge/imx/Makefile @@ -1,5 +1,4 @@ obj-$(CONFIG_DRM_IMX_LDB_HELPER) += imx-ldb-helper.o -obj-$(CONFIG_DRM_IMX_LEGACY_BRIDGE) += imx-legacy-bridge.o obj-$(CONFIG_DRM_IMX8MP_DW_HDMI_BRIDGE) += imx8mp-hdmi-tx.o obj-$(CONFIG_DRM_IMX8MP_HDMI_PAI) += imx8mp-hdmi-pai.o obj-$(CONFIG_DRM_IMX8MP_HDMI_PVI) += imx8mp-hdmi-pvi.o diff --git a/drivers/gpu/drm/bridge/imx/imx-legacy-bridge.c b/drivers/gpu/drm/bridge/imx/imx-legacy-bridge.c deleted file mode 100644 index 0e31d5000e7c..000000000000 --- a/drivers/gpu/drm/bridge/imx/imx-legacy-bridge.c +++ /dev/null @@ -1,91 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Freescale i.MX drm driver - * - * bridge driver for legacy DT bindings, utilizing display-timings node - */ - -#include - -#include -#include -#include -#include - -#include