* [PATCH 7/8] iommu/arm-smmu-v3: Change how the tlbi describes the invalidation
From: Jason Gunthorpe @ 2026-05-18 19:43 UTC (permalink / raw)
To: iommu, Joerg Roedel, Jean-Philippe Brucker, linux-arm-kernel,
Robin Murphy, Will Deacon
Cc: David Matlack, Pasha Tatashin, patches, Pranjal Shrivastava,
Samiullah Khawaja, Mostafa Saleh
In-Reply-To: <0-v1-5b1ac97a5403+6588f-smmu_tlbi_jgg@nvidia.com>
The RIL logic has long had a FIXME that there is not enough
information to properly compute the RIL. There is also subtly not
enough information to properly compute the single stride either.
Change tlbi to use the information format that iommupt is going to
use for ARM. This prepares the invalidation code to support iommupt
and fixes two small limitations with the current code.
iommupt is designed to accumulate all invalidation into a single
gather, then the iommu driver should issue a small number of commands
to execute the gather to control invalidation latency. This is in
contrast to io-pgtable-arm.c which generates many gather flushes and
direct walk cache flushes as it progresses.
To accommodate this the gather will accumulate "damage" in bitmaps,
one for leaf changes and one for table changes. This is enough
information for SMMUv3 to compute the proper stride for single
invalidation and to generate ideal hints for range invalidation.
Change the inner workings of the tlbi process to directly use this
new-style gather description with the idea that the iommupt
conversion will just direct assign the gather fields to the tlbi.
Rework the three places creating the tlbi to express their needs in
terms of the new bitmaps.
1) Simple iotlb invalidation always gets a single range of leaf
levels, so it can set a single leaf bit
2) Walk invalidation always gets a single table level so it can set a
single table bit.
This corrects a weakness in the existing design where single
invalidation would walk the entire table level issuing 4k
invalidations, now it will just push a single invalidation.
3) SVA invalidation has no idea what the MM did, so it will set all
the bits in the bitmaps.
This corrects another weakness where the RIL invalidation logic
was generating hints assuming the #2 rules which isn't correct
for SVA.
Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
---
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c | 28 ++-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 163 +++++++++++++-----
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 20 ++-
3 files changed, 157 insertions(+), 54 deletions(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c
index c708fefb053771..d7f88866469846 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c
@@ -129,17 +129,33 @@ static void arm_smmu_mm_arch_invalidate_secondary_tlbs(struct mmu_notifier *mn,
{
struct arm_smmu_domain *smmu_domain =
container_of(mn, struct arm_smmu_domain, mmu_notifier);
+ unsigned int tg_lg2 = smmu_domain->tgsz_lg2;
struct arm_smmu_tlbi tlbi = {
.smmu_domain = smmu_domain,
- .iova = start,
+ .start = start,
+ .last = end - 1,
/*
- * The mm_types defines vm_end as the first byte after the end
- * address, different from IOMMU subsystem using the last
- * address of an address range.
+ * No information comes from the mm, assume the worst case that
+ * it changed every table level. The way this is hooked into the
+ * mm is tricky, the range won't be expanded to include an
+ * entire table level if one was removed like the iommu gather
+ * does. Thus even if this is a 4k invalidation it may be
+ * including any table level too.
*/
- .size = end - start,
- .iopte_granule = PAGE_SIZE,
+ .table_levels_bitmap = 0xfe,
};
+ unsigned int pmd_lg2sz = (tg_lg2 - 3) * 1 + tg_lg2;
+
+ /*
+ * If the size is small then we can infer the invalidation is PTE only
+ * and set the PTE level only. Otherwise it could be some other
+ * combination so just set them all. This allows RIL to use TTL=3 in
+ * cases of PTE only changes.
+ */
+ if (end - start < BIT_U64(pmd_lg2sz))
+ tlbi.leaf_levels_bitmap = 1;
+ else
+ tlbi.leaf_levels_bitmap = 0xff;
arm_smmu_domain_tlbi(&tlbi);
}
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 94f742de90330c..0841ab053f903e 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -2312,8 +2312,8 @@ static struct arm_smmu_cmd arm_smmu_atc_inv_to_cmd(u32 sid, int ssid,
* This has the unpleasant side-effect of invalidating all PASID-tagged
* ATC entries within the address range.
*/
- page_start = tlbi->iova >> inval_grain_shift;
- page_end = (tlbi->iova + tlbi->size - 1) >> inval_grain_shift;
+ page_start = tlbi->start >> inval_grain_shift;
+ page_end = tlbi->last >> inval_grain_shift;
/*
* In an ATS Invalidate Request, the address must be aligned on the
@@ -2399,7 +2399,49 @@ static bool arm_smmu_ttl_addr_aligned(u64 address, unsigned int tg,
}
/*
- * Generate a single range TLBI command covering [iova, iova+size). Sets
+ * Compute the TTL hint from leaf/table level bitmaps. 0 ttlt means no hint
+ * invalidate all levels.
+ */
+static unsigned int arm_smmu_compute_ttl(u8 leaf_bitmap, u8 table_bitmap,
+ unsigned int tg)
+{
+ int ttl;
+
+ if (leaf_bitmap) {
+ if (is_power_of_2(leaf_bitmap))
+ ttl = 3 - (int)__ffs(leaf_bitmap);
+ else
+ ttl = 0;
+
+ if (table_bitmap) {
+ int table_ttl = 3 - (int)__ffs(table_bitmap) + 1;
+
+ /*
+ * A RIL invalidation with !leaf_only clears out all
+ * table levels above the leaf level ttl only.
+ */
+ if (table_ttl > ttl)
+ ttl = 0;
+ }
+ } else if (table_bitmap) {
+ ttl = 3 - (int)__ffs(table_bitmap) + 1;
+ } else {
+ /* Both bitmaps zero is not allowed */
+ return 0;
+ }
+
+ /* 16K granule, ARM TTL=1 is reserved (SMMUv3 F.b Section 4.4.1) */
+ if (tg == 14 && ttl == 1)
+ return 0;
+
+ /* ARM levels -1 and 0 cannot be hinted */
+ if (ttl <= 0 || ttl > 3)
+ return 0;
+ return ttl;
+}
+
+/*
+ * Generate a single range TLBI command covering [start, last]. Sets
* use_full_inv if the range is too large for a single command.
*
* The algorithm finds the smallest SCALE where the range (in tg-sized pages)
@@ -2409,20 +2451,13 @@ static bool arm_smmu_ttl_addr_aligned(u64 address, unsigned int tg,
static void arm_smmu_tlbi_calc_range(struct arm_smmu_tlbi *tlbi)
{
unsigned int tg_lg2 = tlbi->smmu_domain->tgsz_lg2;
- u64 cur_tg = tlbi->iova >> tg_lg2;
- u64 last_tg = (tlbi->iova + tlbi->size - 1) >> tg_lg2;
+ unsigned int ttl = arm_smmu_compute_ttl(
+ tlbi->leaf_levels_bitmap, tlbi->table_levels_bitmap, tg_lg2);
+ u64 cur_tg = tlbi->start >> tg_lg2;
+ u64 last_tg = tlbi->last >> tg_lg2;
u64 num_tg = last_tg - cur_tg + 1;
u8 tg_enc = (tg_lg2 - 10) / 2;
unsigned int scale;
- u8 ttl = 0;
-
- /*
- * Determine what level the granule is at. For non-leaf, both
- * io-pgtable and SVA pass a nominal last-level granule because they
- * don't know what level(s) actually apply, so leave TTL=0.
- */
- if (tlbi->leaf_only)
- ttl = 4 - ((ilog2(tlbi->iopte_granule) - 3) / (tg_lg2 - 3));
/*
* SMMUv3 F.b Section 4.4.1: TG!=0, NUM==0, SCALE==0, TTL==0 is
@@ -2430,14 +2465,18 @@ static void arm_smmu_tlbi_calc_range(struct arm_smmu_tlbi *tlbi)
* a TTL hint to target only the exact leaf entry.
*/
if (num_tg == 1) {
- if (!ttl)
+ /*
+ * The two io-pgtable ops filling the tlbi won't generate ttl=0.
+ * sva sets constants for single page that give ttl=3
+ */
+ if (WARN_ON(!ttl))
ttl = 3;
tlbi->range.data0 = 0;
- tlbi->range.data1 =
- FIELD_PREP(CMDQ_TLBI_1_LEAF, tlbi->leaf_only) |
- FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
- FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) |
- (cur_tg << tg_lg2);
+ tlbi->range.data1 = FIELD_PREP(CMDQ_TLBI_1_LEAF,
+ !tlbi->table_levels_bitmap) |
+ FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
+ FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) |
+ (cur_tg << tg_lg2);
return;
}
@@ -2461,10 +2500,6 @@ static void arm_smmu_tlbi_calc_range(struct arm_smmu_tlbi *tlbi)
return;
}
- /* 16K granule TTL=1 is reserved (Section 4.4.1) */
- if (tg_lg2 == 14 && ttl == 1)
- ttl = 0;
-
/* Verify address alignment for the TTL hint */
if (ttl && !arm_smmu_ttl_addr_aligned(cur_tg << tg_lg2, tg_lg2, ttl))
ttl = 0;
@@ -2473,27 +2508,52 @@ static void arm_smmu_tlbi_calc_range(struct arm_smmu_tlbi *tlbi)
FIELD_PREP(CMDQ_TLBI_0_NUM,
DIV_ROUND_UP_ULL(num_tg, 1ULL << scale) - 1) |
FIELD_PREP(CMDQ_TLBI_0_SCALE, scale);
- tlbi->range.data1 = FIELD_PREP(CMDQ_TLBI_1_LEAF, tlbi->leaf_only) |
- FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
- FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) |
- (cur_tg << tg_lg2);
+ tlbi->range.data1 =
+ FIELD_PREP(CMDQ_TLBI_1_LEAF, !tlbi->table_levels_bitmap) |
+ FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
+ FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) |
+ (cur_tg << tg_lg2);
}
/*
- * One TLBI command per IOTLB entry, assuming the entries are all at least
- * iopte_granule sized. Sets use_full_inv if too many commands would be needed
- * which indicates too high a latency. The threshold is similar to MAX_DVM_OPS
- * in arch/arm64/include/asm/tlbflush.h for the 4k PAGE_SIZE.
+ * Compute the stride for non-RIL single-page invalidation. Returns the log2
+ * stride of the lowest affected level. Single invalidation removes all IOPTEs
+ * that contain the IOVA invalidated, and we can reliably assume that the
+ * architected page size and table sizes (not contiguous!) are reflected in the
+ * IOTLB. Thus if there is a 2M leaf entry we only need to issue a single IOTLB
+ * invalidation within that 2M IOVA.
+ */
+static u8 arm_smmu_tlbi_calc_stride(struct arm_smmu_tlbi *tlbi)
+{
+ unsigned int tg_lg2 = tlbi->smmu_domain->tgsz_lg2;
+ u8 combined = tlbi->table_levels_bitmap | tlbi->leaf_levels_bitmap;
+
+ if (!combined)
+ return U8_MAX;
+ return (tg_lg2 - 3) * __ffs(combined) + tg_lg2;
+}
+
+/*
+ * One TLBI command per stride-sized entry. Sets use_full_inv if too many
+ * commands would be needed. The threshold is similar to MAX_DVM_OPS in
+ * arch/arm64/include/asm/tlbflush.h.
*/
static void arm_smmu_tlbi_calc_single(struct arm_smmu_tlbi *tlbi)
{
- unsigned long num_ops = tlbi->size / tlbi->iopte_granule;
+ u8 stride_lg2 = arm_smmu_tlbi_calc_stride(tlbi);
+ unsigned long num_ops;
+ if (stride_lg2 == U8_MAX) {
+ tlbi->single.use_full_inv = true;
+ return;
+ }
+ num_ops = (tlbi->last - tlbi->start + 1) >> stride_lg2;
if (!num_ops || num_ops > 512) {
tlbi->single.use_full_inv = true;
return;
}
tlbi->single.num = num_ops;
+ tlbi->single.stride_lg2 = stride_lg2;
}
static void arm_smmu_inv_all_cmd(struct arm_smmu_inv *inv,
@@ -2513,7 +2573,7 @@ static bool arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
struct arm_smmu_cmd *cmd,
struct arm_smmu_tlbi *tlbi)
{
- u64 iova = tlbi->iova;
+ u64 iova = tlbi->start;
unsigned int i;
if (inv->smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
@@ -2533,9 +2593,10 @@ static bool arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
}
for (i = 0; i < tlbi->single.num; i++) {
- cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, tlbi->leaf_only) |
+ cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF,
+ !tlbi->table_levels_bitmap) |
(iova & ~GENMASK_U64(11, 0));
- iova += tlbi->iopte_granule;
+ iova += BIT_U64(tlbi->single.stride_lg2);
arm_smmu_cmdq_batch_add_cmd_p(inv->smmu, cmds, cmd);
}
return false;
@@ -2714,15 +2775,21 @@ static void arm_smmu_tlb_inv_page_nosync(struct iommu_iotlb_gather *gather,
iommu_iotlb_gather_add_page(domain, gather, iova, granule);
}
+/*
+ * Called by io-pgtable-arm.c for each single table level it wants to remove.
+ * size is the size of the table level and granule is the tg in bytes.
+ */
static void arm_smmu_tlb_inv_walk(unsigned long iova, size_t size,
size_t granule, void *cookie)
{
struct arm_smmu_domain *smmu_domain = cookie;
+ unsigned int tg_lg2 = smmu_domain->tgsz_lg2;
struct arm_smmu_tlbi tlbi = {
.smmu_domain = smmu_domain,
- .iova = iova,
- .size = size,
- .iopte_granule = granule,
+ .start = iova,
+ .last = iova + size - 1,
+ .table_levels_bitmap =
+ BIT((ilog2(size) - tg_lg2) / (tg_lg2 - 3)),
};
arm_smmu_domain_tlbi(&tlbi);
@@ -3984,15 +4051,23 @@ static void arm_smmu_flush_iotlb_all(struct iommu_domain *domain)
arm_smmu_tlb_inv_context(smmu_domain);
}
+/*
+ * Called by io-pgtable-arm.c for each run of same pgsize leaf only
+ * invalidation. If it has to change to a different leaf level then it flushes
+ * the gather and starts a fresh one. Thus this always targets only a single
+ * leaf level.
+ */
static void arm_smmu_iotlb_sync(struct iommu_domain *domain,
struct iommu_iotlb_gather *gather)
{
+ struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
+ unsigned int tg = smmu_domain->tgsz_lg2;
struct arm_smmu_tlbi tlbi = {
- .smmu_domain = to_smmu_domain(domain),
- .iova = gather->start,
- .size = gather->end - gather->start + 1,
- .iopte_granule = gather->pgsize,
- .leaf_only = true,
+ .smmu_domain = smmu_domain,
+ .start = gather->start,
+ .last = gather->end,
+ .leaf_levels_bitmap =
+ BIT((ilog2(gather->pgsize) - tg) / (tg - 3)),
};
if (!gather->pgsize)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
index 95da62d64df171..d6c548ade41f01 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -805,14 +805,26 @@ static inline struct arm_smmu_invs *arm_smmu_invs_alloc(size_t num_invs)
struct arm_smmu_tlbi {
struct arm_smmu_domain *smmu_domain;
- unsigned long iova;
- size_t size;
- unsigned int iopte_granule;
- bool leaf_only;
+ unsigned long start;
+ unsigned long last;
+ /*
+ * Level bitmaps use iommupt numbering: bit 0 is the leaf-only level
+ * (ARM level 3), bit 1 is the next level up (ARM level 2), etc. These
+ * match the iommu_iotlb_gather.pt fields. Each set bit indicates a
+ * change at that level. The contiguous hint has no effect on
+ * invalidation processing because HW can ignore the hint.
+ *
+ * If leaf_levels_bitmap is 0 then this is a walk cache only
+ * invalidation. If table_levels_bitmap is 0 then this is a leaf only
+ * invalidation.
+ */
+ u8 leaf_levels_bitmap;
+ u8 table_levels_bitmap;
struct {
bool use_full_inv;
u16 num;
+ u8 stride_lg2;
} single;
struct {
--
2.43.0
^ permalink raw reply related
* [PATCH 1/8] iommu/arm-smmu-v3: Pass the parameters for the invalidation in a struct
From: Jason Gunthorpe @ 2026-05-18 19:43 UTC (permalink / raw)
To: iommu, Joerg Roedel, Jean-Philippe Brucker, linux-arm-kernel,
Robin Murphy, Will Deacon
Cc: David Matlack, Pasha Tatashin, patches, Pranjal Shrivastava,
Samiullah Khawaja, Mostafa Saleh
In-Reply-To: <0-v1-5b1ac97a5403+6588f-smmu_tlbi_jgg@nvidia.com>
These parameters go to a lot of different functions and the next
patches will add more. Put them into a struct to keep things tidy.
Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 75 +++++++++++----------
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 7 ++
2 files changed, 46 insertions(+), 36 deletions(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 9be589d14a3bd4..f7881d21eb06cd 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -2279,8 +2279,8 @@ static irqreturn_t arm_smmu_combined_irq_handler(int irq, void *dev)
return IRQ_WAKE_THREAD;
}
-static struct arm_smmu_cmd
-arm_smmu_atc_inv_to_cmd(u32 sid, int ssid, unsigned long iova, size_t size)
+static struct arm_smmu_cmd arm_smmu_atc_inv_to_cmd(u32 sid, int ssid,
+ struct arm_smmu_tlbi *tlbi)
{
size_t log2_span;
size_t span_mask;
@@ -2302,8 +2302,8 @@ arm_smmu_atc_inv_to_cmd(u32 sid, int ssid, unsigned long iova, size_t size)
* This has the unpleasant side-effect of invalidating all PASID-tagged
* ATC entries within the address range.
*/
- page_start = iova >> inval_grain_shift;
- page_end = (iova + size - 1) >> inval_grain_shift;
+ page_start = tlbi->iova >> inval_grain_shift;
+ page_end = (tlbi->iova + tlbi->size - 1) >> inval_grain_shift;
/*
* In an ATS Invalidate Request, the address must be aligned on the
@@ -2378,20 +2378,23 @@ static void arm_smmu_tlb_inv_context(void *cookie)
static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
struct arm_smmu_cmdq_batch *cmds,
- struct arm_smmu_cmd *cmd, bool leaf,
- unsigned long iova, size_t size,
- size_t granule, size_t pgsize)
+ struct arm_smmu_cmd *cmd,
+ struct arm_smmu_tlbi *tlbi,
+ size_t pgsize)
{
- unsigned long end = iova + size, num_pages = 0, tg = pgsize;
+ size_t inv_range = tlbi->iopte_granule;
+ unsigned long iova = tlbi->iova;
+ unsigned long end = iova + tlbi->size;
+ unsigned long num_pages = 0;
+ unsigned int tg = pgsize;
u64 orig_data0 = cmd->data[0];
- size_t inv_range = granule;
u8 ttl = 0, tg_enc = 0;
- if (WARN_ON_ONCE(!size))
+ if (WARN_ON_ONCE(!tlbi->size))
return;
if (smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
- num_pages = size >> tg;
+ num_pages = tlbi->size >> tg;
/* Convert page size of 12,14,16 (log2) to 1,2,3 */
tg_enc = (tg - 10) / 2;
@@ -2404,8 +2407,8 @@ static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
* want to use a range command, so avoid the SVA corner case
* where both scale and num could be 0 as well.
*/
- if (leaf)
- ttl = 4 - ((ilog2(granule) - 3) / (tg - 3));
+ if (tlbi->leaf_only)
+ ttl = 4 - ((ilog2(tlbi->iopte_granule) - 3) / (tg - 3));
else if ((num_pages & CMDQ_TLBI_RANGE_NUM_MAX) == 1)
num_pages++;
}
@@ -2443,7 +2446,7 @@ static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
* command and something would be very broken if iova had them
* set.
*/
- cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, leaf) |
+ cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, tlbi->leaf_only) |
FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) |
(iova & ~GENMASK_U64(11, 0));
@@ -2453,13 +2456,13 @@ static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
}
}
-static bool arm_smmu_inv_size_too_big(struct arm_smmu_device *smmu, size_t size,
- size_t granule)
+static bool arm_smmu_inv_size_too_big(struct arm_smmu_device *smmu,
+ struct arm_smmu_tlbi *tlbi)
{
size_t max_tlbi_ops;
/* 0 size means invalidate all */
- if (!size || size == SIZE_MAX)
+ if (!tlbi->size || tlbi->size == SIZE_MAX)
return true;
if (smmu->features & ARM_SMMU_FEAT_RANGE_INV)
@@ -2472,19 +2475,17 @@ static bool arm_smmu_inv_size_too_big(struct arm_smmu_device *smmu, size_t size,
* invalidation feature, where there can be too many per-granule TLBIs,
* resulting in a soft lockup.
*/
- max_tlbi_ops = 1 << (ilog2(granule) - 3);
- return size >= max_tlbi_ops * granule;
+ max_tlbi_ops = 1 << (ilog2(tlbi->iopte_granule) - 3);
+ return tlbi->size >= max_tlbi_ops * tlbi->iopte_granule;
}
/* Used by non INV_TYPE_ATS* invalidations */
static void arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
struct arm_smmu_cmdq_batch *cmds,
struct arm_smmu_cmd *cmd,
- bool leaf,
- unsigned long iova, size_t size,
- unsigned int granule)
+ struct arm_smmu_tlbi *tlbi)
{
- if (arm_smmu_inv_size_too_big(inv->smmu, size, granule)) {
+ if (arm_smmu_inv_size_too_big(inv->smmu, tlbi)) {
struct arm_smmu_cmd nsize_cmd = *cmd;
u64p_replace_bits(&nsize_cmd.data[0], inv->nsize_opcode,
@@ -2493,8 +2494,7 @@ static void arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
return;
}
- arm_smmu_cmdq_batch_add_range(inv->smmu, cmds, cmd, leaf,
- iova, size, granule, inv->pgsize);
+ arm_smmu_cmdq_batch_add_range(inv->smmu, cmds, cmd, tlbi, inv->pgsize);
}
static inline bool arm_smmu_invs_end_batch(struct arm_smmu_inv *cur,
@@ -2513,9 +2513,8 @@ static inline bool arm_smmu_invs_end_batch(struct arm_smmu_inv *cur,
return false;
}
-static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,
- unsigned long iova, size_t size,
- unsigned int granule, bool leaf)
+static void __arm_smmu_domain_inv_range(struct arm_smmu_tlbi *tlbi,
+ struct arm_smmu_invs *invs)
{
struct arm_smmu_cmdq_batch cmds = {};
struct arm_smmu_inv *cur;
@@ -2545,18 +2544,16 @@ static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,
case INV_TYPE_S1_ASID:
cmd = arm_smmu_make_cmd_tlbi(cur->size_opcode,
cur->id, 0);
- arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, leaf,
- iova, size, granule);
+ arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, tlbi);
break;
case INV_TYPE_S2_VMID:
cmd = arm_smmu_make_cmd_tlbi(cur->size_opcode,
0, cur->id);
- arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, leaf,
- iova, size, granule);
+ arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, tlbi);
break;
case INV_TYPE_S2_VMID_S1_CLEAR:
/* CMDQ_OP_TLBI_S12_VMALL already flushed S1 entries */
- if (arm_smmu_inv_size_too_big(cur->smmu, size, granule))
+ if (arm_smmu_inv_size_too_big(cur->smmu, tlbi))
break;
arm_smmu_cmdq_batch_add_cmd(
smmu, &cmds,
@@ -2567,7 +2564,7 @@ static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,
arm_smmu_cmdq_batch_add_cmd(
smmu, &cmds,
arm_smmu_atc_inv_to_cmd(cur->id, cur->ssid,
- iova, size));
+ tlbi));
break;
case INV_TYPE_ATS_FULL:
arm_smmu_cmdq_batch_add_cmd(
@@ -2598,6 +2595,12 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
unsigned long iova, size_t size,
unsigned int granule, bool leaf)
{
+ struct arm_smmu_tlbi tlbi = {
+ .iova = iova,
+ .size = size,
+ .iopte_granule = granule,
+ .leaf_only = leaf,
+ };
struct arm_smmu_invs *invs;
/*
@@ -2638,10 +2641,10 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
unsigned long flags;
read_lock_irqsave(&invs->rwlock, flags);
- __arm_smmu_domain_inv_range(invs, iova, size, granule, leaf);
+ __arm_smmu_domain_inv_range(&tlbi, invs);
read_unlock_irqrestore(&invs->rwlock, flags);
} else {
- __arm_smmu_domain_inv_range(invs, iova, size, granule, leaf);
+ __arm_smmu_domain_inv_range(&tlbi, invs);
}
rcu_read_unlock();
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
index 16353596e08ad8..4e97aa5d011a98 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -802,6 +802,13 @@ static inline struct arm_smmu_invs *arm_smmu_invs_alloc(size_t num_invs)
return new_invs;
}
+struct arm_smmu_tlbi {
+ unsigned long iova;
+ size_t size;
+ unsigned int iopte_granule;
+ bool leaf_only;
+};
+
struct arm_smmu_evtq {
struct arm_smmu_queue q;
struct iopf_queue *iopf;
--
2.43.0
^ permalink raw reply related
* [PATCH 3/8] iommu/arm-smmu-v3: Optimize range invalidation for latency
From: Jason Gunthorpe @ 2026-05-18 19:43 UTC (permalink / raw)
To: iommu, Joerg Roedel, Jean-Philippe Brucker, linux-arm-kernel,
Robin Murphy, Will Deacon
Cc: David Matlack, Pasha Tatashin, patches, Pranjal Shrivastava,
Samiullah Khawaja, Mostafa Saleh
In-Reply-To: <0-v1-5b1ac97a5403+6588f-smmu_tlbi_jgg@nvidia.com>
The server IOMMU drivers focus on invalidation latency by default,
over-invalidating if necessary, to round the invalidation range up to a
single command. I think this represents a trade off for DMA non-FQ and SVA
where stalling the operation is overall worse than re-loading the IOTLB.
For instance AMD and VT-d both round the range up to the largest aligned
power of two and invalidate that. This causes over-invalidation but that
is preferred on real HW over trying to issue a number of smaller
range invalidations.
Only if a para-virtualizating hypervisor is detected do they switch to
using more accurate invalidation. This also triggers using
iommu_iotlb_gather_is_disjoint() (ie PT_FEAT_FLUSH_RANGE_NO_GAPS) to
remove over invalidation from the gather. A pvIOMMU has a hypervisor that
will walk the IOPTEs and resync them. Over invalidation, especially
significant over invalidation, can incur a big latency cost reloading alot
of page table. x86 IOMMUs have aligned range restrictions so there are
some pretty nasty corner cases that can trigger huge over invalidation.
Currently SMMUv3 doesn't support detecting a hypervisor, and it
unconditionally runs in a NO_GAPS mode. This makes some sense for the
single invalidation flow where there is little reason to push single
commands across a gap.
When we get to RIL hardware, this doesn't look so good. On real HW the
best option is the same as x86: issue a single RIL per gather and optimize
for latency. SMMUv3 has a significant advantage as its RIL does not have
alignment limitations so it's single-command over-invalidation is capped
at < 1/32 of the gather's size, making it much more suitable for a
pvIOMMU.
However even with RIL SMMUv3 still uses NO_GAPS and it breaks down the
gather into several exactly sized RILs to avoid any over-invalidation,
costing latency on real HW.
When the HW has RIL support follow the x86 approach in SMMUv3 and
calculate a single RIL per gather that will cover the required
invalidation.
Calculate the smallest SCALE such that NUM can cover the range to minimize
over-invalidation. Always use a RIL command if RIL is possible working
around the spec limitations to form a valid one. If RIL is not possible
then do full invalidation.
At least one invalidation errata is avoided by 'always use RIL'.
Since the normal path is now the only one with a loop, split them into two
functions and fold a simplified version of arm_smmu_inv_size_too_big()
directly into the normal flow in a way that directly limits the number of
single invalidation commands generated, again focusing on controlling
latency.
The end result is any gather is converted into either:
- One invalidate all
- One range invalidate op
- At most 512 single invalidation ops
Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 257 ++++++++++++--------
1 file changed, 153 insertions(+), 104 deletions(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index cd0ab518712cd6..59634a5a5c0640 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -2376,124 +2376,166 @@ static void arm_smmu_tlb_inv_context(void *cookie)
arm_smmu_domain_inv(smmu_domain);
}
-static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
+/*
+ * Check address alignment for TTL hint per SMMUv3 F.b Section 4.4.1.
+ * Address bits below the alignment must be zero, otherwise UNPREDICTABLE.
+ */
+static bool arm_smmu_ttl_addr_aligned(u64 address, unsigned int tg,
+ unsigned int ttl)
+{
+ unsigned int pgsz_lg2 = (tg - 3) * (3 - ttl) + tg;
+
+ return !(address & GENMASK_U64(pgsz_lg2 - 1, 0));
+}
+
+static void arm_smmu_cmdq_batch_add_ril(struct arm_smmu_device *smmu,
+ struct arm_smmu_cmdq_batch *cmds,
+ struct arm_smmu_cmd *cmd, bool leaf,
+ u64 address, unsigned int num,
+ unsigned int scale, u8 ttl, u8 tg_enc)
+{
+ cmd->data[0] |= FIELD_PREP(CMDQ_TLBI_0_NUM, num) |
+ FIELD_PREP(CMDQ_TLBI_0_SCALE, scale);
+ cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, leaf) |
+ FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
+ FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) | address;
+ arm_smmu_cmdq_batch_add_cmd_p(smmu, cmds, cmd);
+}
+
+/*
+ * Issue a single range TLBI command covering [iova, iova+size). Returns true if
+ * successful, false if the range is too large for a single command.
+ *
+ * The algorithm finds the smallest SCALE where the range (in tg-sized pages)
+ * fits in the 5-bit NUM field (max 32 units of 2^SCALE pages). This may widen
+ * the invalidation range.
+ */
+static bool arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
struct arm_smmu_cmdq_batch *cmds,
struct arm_smmu_cmd *cmd,
struct arm_smmu_tlbi *tlbi)
{
- size_t inv_range = tlbi->iopte_granule;
- unsigned long iova = tlbi->iova;
- unsigned long end = iova + tlbi->size;
- unsigned long num_pages = 0;
- unsigned int tg = tlbi->smmu_domain->tgsz_lg2;
- u64 orig_data0 = cmd->data[0];
- u8 ttl = 0, tg_enc = 0;
+ unsigned int tg_lg2 = tlbi->smmu_domain->tgsz_lg2;
+ u64 cur_tg = tlbi->iova >> tg_lg2;
+ u64 last_tg = (tlbi->iova + tlbi->size - 1) >> tg_lg2;
+ u64 num_tg = last_tg - cur_tg + 1;
+ u8 tg_enc = (tg_lg2 - 10) / 2;
+ unsigned int scale;
+ u8 ttl = 0;
- if (WARN_ON_ONCE(!tlbi->size))
- return;
-
- if (smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
- num_pages = tlbi->size >> tg;
-
- /* Convert page size of 12,14,16 (log2) to 1,2,3 */
- tg_enc = (tg - 10) / 2;
-
- /*
- * Determine what level the granule is at. For non-leaf, both
- * io-pgtable and SVA pass a nominal last-level granule because
- * they don't know what level(s) actually apply, so ignore that
- * and leave TTL=0. However for various errata reasons we still
- * want to use a range command, so avoid the SVA corner case
- * where both scale and num could be 0 as well.
- */
- if (tlbi->leaf_only)
- ttl = 4 - ((ilog2(tlbi->iopte_granule) - 3) / (tg - 3));
- else if ((num_pages & CMDQ_TLBI_RANGE_NUM_MAX) == 1)
- num_pages++;
- }
-
- while (iova < end) {
- if (smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
- /*
- * On each iteration of the loop, the range is 5 bits
- * worth of the aligned size remaining.
- * The range in pages is:
- *
- * range = (num_pages & (0x1f << __ffs(num_pages)))
- */
- unsigned long scale, num;
-
- /* Determine the power of 2 multiple number of pages */
- scale = __ffs(num_pages);
-
- /* Determine how many chunks of 2^scale size we have */
- num = (num_pages >> scale) & CMDQ_TLBI_RANGE_NUM_MAX;
-
- cmd->data[0] = orig_data0 |
- FIELD_PREP(CMDQ_TLBI_0_NUM, num - 1) |
- FIELD_PREP(CMDQ_TLBI_0_SCALE, scale);
-
- /* range is num * 2^scale * pgsize */
- inv_range = num << (scale + tg);
-
- /* Clear out the lower order bits for the next iteration */
- num_pages -= num << scale;
- }
-
- /*
- * IPA has fewer bits than VA, but they are reserved in the
- * command and something would be very broken if iova had them
- * set.
- */
- cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, tlbi->leaf_only) |
- FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
- FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) |
- (iova & ~GENMASK_U64(11, 0));
-
- arm_smmu_cmdq_batch_add_cmd_p(smmu, cmds, cmd);
- iova += inv_range;
- }
-}
-
-static bool arm_smmu_inv_size_too_big(struct arm_smmu_device *smmu,
- struct arm_smmu_tlbi *tlbi)
-{
- size_t max_tlbi_ops;
-
- /* 0 size means invalidate all */
- if (!tlbi->size || tlbi->size == SIZE_MAX)
- return true;
-
- if (smmu->features & ARM_SMMU_FEAT_RANGE_INV)
+ if (!tlbi->size)
return false;
/*
- * Borrowed from the MAX_TLBI_OPS in arch/arm64/include/asm/tlbflush.h,
- * this is used as a threshold to replace "size_opcode" commands with a
- * single "nsize_opcode" command, when SMMU doesn't implement the range
- * invalidation feature, where there can be too many per-granule TLBIs,
- * resulting in a soft lockup.
+ * Determine what level the granule is at. For non-leaf, both
+ * io-pgtable and SVA pass a nominal last-level granule because they
+ * don't know what level(s) actually apply, so leave TTL=0.
*/
- max_tlbi_ops = 1 << (ilog2(tlbi->iopte_granule) - 3);
- return tlbi->size >= max_tlbi_ops * tlbi->iopte_granule;
+ if (tlbi->leaf_only)
+ ttl = 4 - ((ilog2(tlbi->iopte_granule) - 3) / (tg_lg2 - 3));
+
+ /*
+ * SMMUv3 F.b Section 4.4.1: TG!=0, NUM==0, SCALE==0, TTL==0 is
+ * Reserved and causes CERROR_ILL. Single page uses NUM=0, SCALE=0 with
+ * a TTL hint to target only the exact leaf entry.
+ */
+ if (num_tg == 1) {
+ if (!ttl)
+ ttl = 3;
+ arm_smmu_cmdq_batch_add_ril(smmu, cmds, cmd, tlbi->leaf_only,
+ cur_tg << tg_lg2, 0, 0, ttl,
+ tg_enc);
+ return true;
+ }
+
+ /*
+ * There are at most 5 possible values for NUM based on SCALE. The
+ * highest NUM is at the lowest SCALE where:
+ * ceil(num_tg / 2^SCALE) <= 32
+ * scale >= ceil(log2(num_tg / 32))
+ * The lowest value is 1 where 2^SCALE covers the whole range. Pick the
+ * highest since it trivially also gives the tightest range.
+ *
+ * Unlike other IOMMUs the spec doesn't have any alignment requirements
+ * on the address beyond it must be aligned to tg (so long as TTL=0)
+ */
+ scale = fls64((num_tg - 1) / 32);
+ if (scale > 31) {
+ /*
+ * Range too large for a single command, use full invalidation.
+ */
+ return false;
+ }
+
+ /* 16K granule TTL=1 is reserved (Section 4.4.1) */
+ if (tg_lg2 == 14 && ttl == 1)
+ ttl = 0;
+
+ /* Verify address alignment for the TTL hint */
+ if (ttl && !arm_smmu_ttl_addr_aligned(cur_tg << tg_lg2, tg_lg2, ttl))
+ ttl = 0;
+
+ arm_smmu_cmdq_batch_add_ril(smmu, cmds, cmd, tlbi->leaf_only,
+ cur_tg << tg_lg2,
+ DIV_ROUND_UP_ULL(num_tg, 1ULL << scale) - 1,
+ scale, ttl, tg_enc);
+ return true;
}
-/* Used by non INV_TYPE_ATS* invalidations */
-static void arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
+/*
+ * One TLBI command per IOTLB entry, assuming the entries are all at least
+ * iopte_granule sized. Returns false if too many commands would be needed which
+ * indicates too high a latency. The threshold is similar to MAX_DVM_OPS in
+ * arch/arm64/include/asm/tlbflush.h for the 4k PAGE_SIZE.
+ */
+static bool arm_smmu_cmdq_batch_add_single(struct arm_smmu_device *smmu,
+ struct arm_smmu_cmdq_batch *cmds,
+ struct arm_smmu_cmd *cmd,
+ struct arm_smmu_tlbi *tlbi)
+{
+ unsigned long num_ops = tlbi->size / tlbi->iopte_granule;
+ unsigned long iova = tlbi->iova;
+ unsigned long i;
+
+ if (!num_ops || num_ops > 512)
+ return false;
+
+ for (i = 0; i < num_ops; i++) {
+ cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, tlbi->leaf_only) |
+ (iova & ~GENMASK_U64(11, 0));
+ arm_smmu_cmdq_batch_add_cmd_p(smmu, cmds, cmd);
+ iova += tlbi->iopte_granule;
+ }
+ return true;
+}
+
+static void arm_smmu_inv_all_cmd(struct arm_smmu_inv *inv,
+ struct arm_smmu_cmdq_batch *cmds,
+ struct arm_smmu_cmd *cmd)
+{
+ u64p_replace_bits(&cmd->data[0], inv->nsize_opcode, CMDQ_0_OP);
+ arm_smmu_cmdq_batch_add_cmd_p(inv->smmu, cmds, cmd);
+}
+
+/*
+ * Used by non INV_TYPE_ATS* invalidations. Returns true if it fell back to
+ * full invalidation using nsize_opcode.
+ */
+static bool arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
struct arm_smmu_cmdq_batch *cmds,
struct arm_smmu_cmd *cmd,
struct arm_smmu_tlbi *tlbi)
{
- if (arm_smmu_inv_size_too_big(inv->smmu, tlbi)) {
- struct arm_smmu_cmd nsize_cmd = *cmd;
-
- u64p_replace_bits(&nsize_cmd.data[0], inv->nsize_opcode,
- CMDQ_0_OP);
- arm_smmu_cmdq_batch_add_cmd_p(inv->smmu, cmds, &nsize_cmd);
- return;
+ if (inv->smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
+ if (arm_smmu_cmdq_batch_add_range(inv->smmu, cmds, cmd, tlbi))
+ return false;
+ } else {
+ if (arm_smmu_cmdq_batch_add_single(inv->smmu, cmds, cmd, tlbi))
+ return false;
}
- arm_smmu_cmdq_batch_add_range(inv->smmu, cmds, cmd, tlbi);
+ arm_smmu_inv_all_cmd(inv, cmds, cmd);
+ return true;
}
static inline bool arm_smmu_invs_end_batch(struct arm_smmu_inv *cur,
@@ -2516,6 +2558,7 @@ static void __arm_smmu_domain_inv_range(struct arm_smmu_tlbi *tlbi,
struct arm_smmu_invs *invs)
{
struct arm_smmu_cmdq_batch cmds = {};
+ bool used_s12_vmall = false;
struct arm_smmu_inv *cur;
struct arm_smmu_inv *end;
@@ -2548,11 +2591,17 @@ static void __arm_smmu_domain_inv_range(struct arm_smmu_tlbi *tlbi,
case INV_TYPE_S2_VMID:
cmd = arm_smmu_make_cmd_tlbi(cur->size_opcode,
0, cur->id);
- arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, tlbi);
+ used_s12_vmall = arm_smmu_inv_to_cmdq_batch(cur, &cmds,
+ &cmd, tlbi);
break;
case INV_TYPE_S2_VMID_S1_CLEAR:
- /* CMDQ_OP_TLBI_S12_VMALL already flushed S1 entries */
- if (arm_smmu_inv_size_too_big(cur->smmu, tlbi))
+ /*
+ * S2_VMID used CMDQ_OP_TLBI_S12_VMALL which already
+ * flushed S1 entries. These two types always come in
+ * pairs and arm_smmu_inv_cmp() ensures that they are
+ * consecutive in the list.
+ */
+ if (used_s12_vmall)
break;
arm_smmu_cmdq_batch_add_cmd(
smmu, &cmds,
--
2.43.0
^ permalink raw reply related
* [PATCH 4/8] iommu/arm-smmu-v3: Keep track in the arm_smmu_invs if RIL is used
From: Jason Gunthorpe @ 2026-05-18 19:43 UTC (permalink / raw)
To: iommu, Joerg Roedel, Jean-Philippe Brucker, linux-arm-kernel,
Robin Murphy, Will Deacon
Cc: David Matlack, Pasha Tatashin, patches, Pranjal Shrivastava,
Samiullah Khawaja, Mostafa Saleh
In-Reply-To: <0-v1-5b1ac97a5403+6588f-smmu_tlbi_jgg@nvidia.com>
Summarize if any of the inv entries will use RIL. The next patch will use
this to avoid RIL pre-calculations unless RIL is being used by the
invalidation.
Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
---
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c | 30 +++++++++----------
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 18 ++++++++---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 2 ++
3 files changed, 31 insertions(+), 19 deletions(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c
index add671363c828c..785dd21bd68b7a 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c
@@ -655,37 +655,37 @@ static void arm_smmu_v3_invs_test_verify(struct kunit *test,
static struct arm_smmu_invs invs1 = {
.num_invs = 3,
- .inv = { { .type = INV_TYPE_S2_VMID, .id = 1, },
- { .type = INV_TYPE_S2_VMID_S1_CLEAR, .id = 1, },
- { .type = INV_TYPE_ATS, .id = 3, }, },
+ .inv = { { .smmu = &smmu, .type = INV_TYPE_S2_VMID, .id = 1, },
+ { .smmu = &smmu, .type = INV_TYPE_S2_VMID_S1_CLEAR, .id = 1, },
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 3, }, },
};
static struct arm_smmu_invs invs2 = {
.num_invs = 3,
- .inv = { { .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
- { .type = INV_TYPE_ATS, .id = 4, },
- { .type = INV_TYPE_ATS, .id = 5, }, },
+ .inv = { { .smmu = &smmu, .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 4, },
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 5, }, },
};
static struct arm_smmu_invs invs3 = {
.num_invs = 3,
- .inv = { { .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
- { .type = INV_TYPE_ATS, .id = 5, }, /* recover a trash */
- { .type = INV_TYPE_ATS, .id = 6, }, },
+ .inv = { { .smmu = &smmu, .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 5, }, /* recover a trash */
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 6, }, },
};
static struct arm_smmu_invs invs4 = {
.num_invs = 3,
- .inv = { { .type = INV_TYPE_ATS, .id = 10, .ssid = 1 },
- { .type = INV_TYPE_ATS, .id = 10, .ssid = 3 },
- { .type = INV_TYPE_ATS, .id = 12, .ssid = 1 }, },
+ .inv = { { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 1 },
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 3 },
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 12, .ssid = 1 }, },
};
static struct arm_smmu_invs invs5 = {
.num_invs = 3,
- .inv = { { .type = INV_TYPE_ATS, .id = 10, .ssid = 2 },
- { .type = INV_TYPE_ATS, .id = 10, .ssid = 3 }, /* duplicate */
- { .type = INV_TYPE_ATS, .id = 12, .ssid = 2 }, },
+ .inv = { { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 2 },
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 3 }, /* duplicate */
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 12, .ssid = 2 }, },
};
static void arm_smmu_v3_invs_test(struct kunit *test)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 59634a5a5c0640..3b0b273fcde829 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -988,6 +988,18 @@ static inline int arm_smmu_invs_iter_next_cmp(struct arm_smmu_invs *invs_l,
return arm_smmu_inv_cmp(cur_l, &invs_r->inv[next_r]);
}
+static void arm_smmu_invs_update_caps(struct arm_smmu_invs *invs,
+ const struct arm_smmu_inv *inv)
+{
+ if (arm_smmu_inv_is_ats(inv))
+ invs->has_ats = true;
+
+ if (!(inv->smmu->features & ARM_SMMU_FEAT_RANGE_INV))
+ return;
+
+ invs->has_range_inv = true;
+}
+
/**
* arm_smmu_invs_for_each_cmp - Iterate over two sorted arrays computing for
* arm_smmu_invs_merge() or arm_smmu_invs_unref()
@@ -1058,8 +1070,7 @@ struct arm_smmu_invs *arm_smmu_invs_merge(struct arm_smmu_invs *invs,
*/
if (new != new_invs->inv)
WARN_ON_ONCE(arm_smmu_inv_cmp(new - 1, new) == 1);
- if (arm_smmu_inv_is_ats(new))
- new_invs->has_ats = true;
+ arm_smmu_invs_update_caps(new_invs, new);
new++;
}
@@ -1169,8 +1180,7 @@ struct arm_smmu_invs *arm_smmu_invs_purge(struct arm_smmu_invs *invs)
arm_smmu_invs_for_each_entry(invs, i, inv) {
new_invs->inv[num_invs] = *inv;
- if (arm_smmu_inv_is_ats(inv))
- new_invs->has_ats = true;
+ arm_smmu_invs_update_caps(new_invs, inv);
num_invs++;
}
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
index 0c63069400d22d..d58fe91a96325f 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -755,6 +755,7 @@ static inline bool arm_smmu_inv_is_ats(const struct arm_smmu_inv *inv)
* Must not be greater than @num_invs
* @rwlock: optional rwlock to fence ATS operations
* @has_ats: flag if the array contains an INV_TYPE_ATS or INV_TYPE_ATS_FULL
+ * @has_range_inv: flag if any entry's SMMU supports range invalidation
* @rcu: rcu head for kfree_rcu()
* @inv: flexible invalidation array
*
@@ -784,6 +785,7 @@ struct arm_smmu_invs {
size_t num_trashes;
rwlock_t rwlock;
bool has_ats;
+ bool has_range_inv;
struct rcu_head rcu;
struct arm_smmu_inv inv[] __counted_by(max_invs);
};
--
2.43.0
^ permalink raw reply related
* [PATCH v2 08/11] drm/rockchip: dw_hdmi: Set output_port for RK3568/RK3566
From: Jonas Karlman @ 2026-05-18 19:37 UTC (permalink / raw)
To: Heiko Stübner, Sandy Huang, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel,
Jonas Karlman
In-Reply-To: <20260518193748.2482823-1-jonas@kwiboo.se>
All in-tree RK3568/RK3566 device trees using HDMI also include the
required hdmi-connector node at port@1 since their introduction.
Define the output_port for RK3568 so that dw-hdmi bridge driver can pick
up the display-connector bridge once the dw-hdmi connector is replaced
with a bridge connector in a future change.
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v2: No change
---
drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
index 02ee606c648e..6c4923b9b659 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -459,6 +459,7 @@ static const struct dw_hdmi_plat_data rk3568_hdmi_drv_data = {
.phy_config = rockchip_phy_config,
.phy_data = &rk3568_chip_data,
.use_drm_infoframe = true,
+ .output_port = 1,
};
static const struct of_device_id dw_hdmi_rockchip_dt_ids[] = {
--
2.54.0
^ permalink raw reply related
* [PATCH v2 07/11] drm/rockchip: dw_hdmi: Clean up whitespace
From: Jonas Karlman @ 2026-05-18 19:37 UTC (permalink / raw)
To: Heiko Stübner, Sandy Huang, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel,
Jonas Karlman
In-Reply-To: <20260518193748.2482823-1-jonas@kwiboo.se>
Move the blank line before the RK3328 definitions for readability and
make the phy_config table spacing consistent with other tables.
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v2: No change
---
drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
index bd530321b49c..02ee606c648e 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -31,8 +31,8 @@
#define RK3288_GRF_SOC_CON6 0x025C
#define RK3288_HDMI_LCDC_SEL BIT(4)
-#define RK3328_GRF_SOC_CON2 0x0408
+#define RK3328_GRF_SOC_CON2 0x0408
#define RK3328_HDMI_SDAIN_MSK BIT(11)
#define RK3328_HDMI_SCLIN_MSK BIT(10)
#define RK3328_HDMI_HPD_IOE BIT(2)
@@ -190,11 +190,11 @@ static const struct dw_hdmi_curr_ctrl rockchip_cur_ctr[] = {
static const struct dw_hdmi_phy_config rockchip_phy_config[] = {
/*pixelclk symbol term vlev*/
- { 74250000, 0x8009, 0x0004, 0x0272},
- { 165000000, 0x802b, 0x0004, 0x0209},
- { 297000000, 0x8039, 0x0005, 0x028d},
- { 594000000, 0x8039, 0x0000, 0x019d},
- { ~0UL, 0x0000, 0x0000, 0x0000}
+ { 74250000, 0x8009, 0x0004, 0x0272 },
+ { 165000000, 0x802b, 0x0004, 0x0209 },
+ { 297000000, 0x8039, 0x0005, 0x028d },
+ { 594000000, 0x8039, 0x0000, 0x019d },
+ { ~0UL, 0x0000, 0x0000, 0x0000 },
};
static enum drm_mode_status
--
2.54.0
^ permalink raw reply related
* [PATCH v2 11/11] drm/rockchip: dw_hdmi: Use resume_early pm ops for system suspend
From: Jonas Karlman @ 2026-05-18 19:37 UTC (permalink / raw)
To: Heiko Stübner, Sandy Huang, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel,
Jonas Karlman
In-Reply-To: <20260518193748.2482823-1-jonas@kwiboo.se>
rockchip_drm_sys_resume()/drm_mode_config_helper_resume() is called
before the resume pm ops of dw-hdmi. This result in an atomic_enable()
before dw_hdmi_rockchip_resume()/dw_hdmi_resume() is called.
Resume (without changes):
- rockchip_drm_sys_resume()
- drm_mode_config_helper_resume()
- atomic_enable()
- dw_hdmi_rockchip_resume()
- dw_hdmi_resume()
- dw_hdmi_init_hw()
Change to use resume_early pm ops for system suspend to ensure pm ops
for dw-hdmi is run before rockchip-drm pm ops. Also fix a possible NULL
pointer dereference timing issue while at it.
Resume (with changes):
- dw_hdmi_rockchip_resume_early()
- dw_hdmi_resume()
- dw_hdmi_init_hw()
- rockchip_drm_sys_resume()
- drm_mode_config_helper_resume()
- atomic_enable()
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v2: New patch
This can be tested using CONFIG_PM_DEBUG with a simulated suspend:
echo N > /sys/module/printk/parameters/console_suspend
echo 1 > /sys/power/pm_debug_messages
echo platform > /sys/power/pm_test
echo mem > /sys/power/state
or using something like following for real suspend/resume:
echo N > /sys/module/printk/parameters/console_suspend
rtcwake -m mem -s 5
Note that RK356x has an issue related to EHCI/OHCI and a missing USBPHY
clock reference during suspend, and GPU during resume (mainline TF-A).
A separate patch to address the ECHI/OHCI issue will be sent.
---
drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
index 0133a4b67b3b..ac6a48ce8642 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -692,17 +692,18 @@ static void dw_hdmi_rockchip_remove(struct platform_device *pdev)
component_del(&pdev->dev, &dw_hdmi_rockchip_ops);
}
-static int __maybe_unused dw_hdmi_rockchip_resume(struct device *dev)
+static int __maybe_unused dw_hdmi_rockchip_resume_early(struct device *dev)
{
struct rockchip_hdmi *hdmi = dev_get_drvdata(dev);
- dw_hdmi_resume(hdmi->hdmi);
+ if (hdmi)
+ dw_hdmi_resume(hdmi->hdmi);
return 0;
}
static const struct dev_pm_ops dw_hdmi_rockchip_pm = {
- SET_SYSTEM_SLEEP_PM_OPS(NULL, dw_hdmi_rockchip_resume)
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(NULL, dw_hdmi_rockchip_resume_early)
};
struct platform_driver dw_hdmi_rockchip_pltfm_driver = {
--
2.54.0
^ permalink raw reply related
* [PATCH v2 10/11] drm/rockchip: dw_hdmi: Propagate bus format to display driver
From: Jonas Karlman @ 2026-05-18 19:37 UTC (permalink / raw)
To: Heiko Stübner, Sandy Huang, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel,
Jonas Karlman
In-Reply-To: <20260518193748.2482823-1-jonas@kwiboo.se>
The HDMI block is currently hardcoded to expect RGB output from the
display controller. However, the VOP in some SoCs are capable of YCbCr
output to the HDMI block.
Read the negotiated bus format from the bridge state and propagate it to
the CRCT state in form of output mode and bus format. Treat the format
MEDIA_BUS_FMT_FIXED as RGB888 and reject any unsupported formats.
This has no inpact until dw-hdmi bridge is fully converted to a HDMI
bridge and also adds support for the "color format" connector property.
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v2: No change
---
drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 44 ++++++++++++++++++++-
1 file changed, 43 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
index 4e7fd4b80d76..0133a4b67b3b 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -5,6 +5,7 @@
#include <linux/clk.h>
#include <linux/hw_bitfield.h>
+#include <linux/media-bus-format.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/platform_device.h>
@@ -275,6 +276,26 @@ static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder)
dev_dbg(hdmi->dev, "vop %s output to hdmi\n", ret ? "LIT" : "BIG");
}
+static u32 dw_hdmi_rockchip_get_bus_format(struct drm_encoder *encoder,
+ struct drm_connector_state *conn_state)
+{
+ struct drm_bridge *bridge __free(drm_bridge_put) = NULL;
+ struct drm_bridge_state *bridge_state;
+
+ bridge = drm_bridge_chain_get_first_bridge(encoder);
+ if (!bridge)
+ return 0;
+
+ bridge_state = drm_atomic_get_bridge_state(conn_state->state, bridge);
+ if (!bridge_state)
+ return 0;
+
+ if (bridge_state->input_bus_cfg.format != MEDIA_BUS_FMT_FIXED)
+ return bridge_state->input_bus_cfg.format;
+
+ return bridge_state->output_bus_cfg.format;
+}
+
static int
dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
@@ -283,9 +304,30 @@ dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder,
struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
union phy_configure_opts opts = {};
+ u32 bus_format;
+
+ bus_format = dw_hdmi_rockchip_get_bus_format(encoder, conn_state);
+
+ switch (bus_format) {
+ case MEDIA_BUS_FMT_FIXED:
+ bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ fallthrough;
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ case MEDIA_BUS_FMT_RGB101010_1X30:
+ case MEDIA_BUS_FMT_YUV8_1X24:
+ case MEDIA_BUS_FMT_YUV10_1X30:
+ s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
+ break;
+ case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
+ case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
+ s->output_mode = ROCKCHIP_OUT_MODE_YUV420;
+ break;
+ default:
+ return -EINVAL;
+ }
- s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
s->output_type = DRM_MODE_CONNECTOR_HDMIA;
+ s->bus_format = bus_format;
if (!hdmi->phy || !conn_state->hdmi.tmds_char_rate)
return 0;
--
2.54.0
^ permalink raw reply related
* [PATCH v2 06/11] drm/rockchip: dw_hdmi: Remove empty encoder helper funcs
From: Jonas Karlman @ 2026-05-18 19:37 UTC (permalink / raw)
To: Heiko Stübner, Sandy Huang, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel,
Jonas Karlman
In-Reply-To: <20260518193748.2482823-1-jonas@kwiboo.se>
Remove the empty disable() and static return true mode_fixup() encoder
helper funcs as they do not provide any useful functionality.
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v2: No change
---
drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 18 ++----------------
1 file changed, 2 insertions(+), 16 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
index 538906e342d2..bd530321b49c 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -226,18 +226,6 @@ dw_hdmi_rockchip_mode_valid(struct dw_hdmi *dw_hdmi, void *data,
return MODE_OK;
}
-static void dw_hdmi_rockchip_encoder_disable(struct drm_encoder *encoder)
-{
-}
-
-static bool
-dw_hdmi_rockchip_encoder_mode_fixup(struct drm_encoder *encoder,
- const struct drm_display_mode *mode,
- struct drm_display_mode *adj_mode)
-{
- return true;
-}
-
static void dw_hdmi_rockchip_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adj_mode)
@@ -290,10 +278,8 @@ dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder,
}
static const struct drm_encoder_helper_funcs dw_hdmi_rockchip_encoder_helper_funcs = {
- .mode_fixup = dw_hdmi_rockchip_encoder_mode_fixup,
- .mode_set = dw_hdmi_rockchip_encoder_mode_set,
- .enable = dw_hdmi_rockchip_encoder_enable,
- .disable = dw_hdmi_rockchip_encoder_disable,
+ .mode_set = dw_hdmi_rockchip_encoder_mode_set,
+ .enable = dw_hdmi_rockchip_encoder_enable,
.atomic_check = dw_hdmi_rockchip_encoder_atomic_check,
};
--
2.54.0
^ permalink raw reply related
* [PATCH v2 04/11] drm/rockchip: dw_hdmi: Inline resource lookup into bind()
From: Jonas Karlman @ 2026-05-18 19:37 UTC (permalink / raw)
To: Heiko Stübner, Sandy Huang, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel,
Jonas Karlman
In-Reply-To: <20260518193748.2482823-1-jonas@kwiboo.se>
Inline rockchip_hdmi_parse_dt() into dw_hdmi_rockchip_bind() so the
probe path is easier to follow in one place. Also ensure failures in
bind() use dev_err_probe() so probe deferrals and errors are reported
consistently.
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v2: No change
---
drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 74 +++++++++------------
1 file changed, 30 insertions(+), 44 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
index 12a7c989c4ee..8c26223b70b5 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -196,41 +196,6 @@ static const struct dw_hdmi_phy_config rockchip_phy_config[] = {
{ ~0UL, 0x0000, 0x0000, 0x0000}
};
-static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
-{
- struct device_node *np = hdmi->dev->of_node;
- int ret;
-
- hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
- if (IS_ERR(hdmi->regmap)) {
- dev_err(hdmi->dev, "Unable to get rockchip,grf\n");
- return PTR_ERR(hdmi->regmap);
- }
-
- hdmi->ref_clk = devm_clk_get_optional_enabled(hdmi->dev, "ref");
- if (!hdmi->ref_clk)
- hdmi->ref_clk = devm_clk_get_optional_enabled(hdmi->dev, "vpll");
-
- if (IS_ERR(hdmi->ref_clk)) {
- ret = PTR_ERR(hdmi->ref_clk);
- return dev_err_probe(hdmi->dev, ret, "failed to get reference clock\n");
- }
-
- hdmi->grf_clk = devm_clk_get_optional(hdmi->dev, "grf");
- if (IS_ERR(hdmi->grf_clk)) {
- ret = PTR_ERR(hdmi->grf_clk);
- return dev_err_probe(hdmi->dev, ret, "failed to get grf clock\n");
- }
-
- ret = devm_regulator_get_enable(hdmi->dev, "avdd-0v9");
- if (ret)
- return ret;
-
- ret = devm_regulator_get_enable(hdmi->dev, "avdd-1v8");
-
- return ret;
-}
-
static enum drm_mode_status
dw_hdmi_rockchip_mode_valid(struct dw_hdmi *dw_hdmi, void *data,
const struct drm_display_info *info,
@@ -578,18 +543,39 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
* the required CRTC is added later.
*/
if (encoder->possible_crtcs == 0)
- return -EPROBE_DEFER;
+ return dev_err_probe(dev, -EPROBE_DEFER,
+ "failed to find possible crtcs\n");
- ret = rockchip_hdmi_parse_dt(hdmi);
- if (ret) {
- return dev_err_probe(dev, ret, "Unable to parse OF data\n");
- }
+ hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
+ if (IS_ERR(hdmi->regmap))
+ return dev_err_probe(dev, PTR_ERR(hdmi->regmap),
+ "failed to get rockchip,grf\n");
+
+ hdmi->ref_clk = devm_clk_get_optional_enabled(dev, "ref");
+ if (!hdmi->ref_clk)
+ hdmi->ref_clk = devm_clk_get_optional_enabled(dev, "vpll");
+
+ if (IS_ERR(hdmi->ref_clk))
+ return dev_err_probe(dev, PTR_ERR(hdmi->ref_clk),
+ "failed to get reference clock\n");
+
+ hdmi->grf_clk = devm_clk_get_optional(dev, "grf");
+ if (IS_ERR(hdmi->grf_clk))
+ return dev_err_probe(dev, PTR_ERR(hdmi->grf_clk),
+ "failed to get grf clock\n");
+
+ ret = devm_regulator_get_enable(dev, "avdd-0v9");
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable avdd-0v9\n");
+
+ ret = devm_regulator_get_enable(dev, "avdd-1v8");
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable avdd-1v8\n");
hdmi->phy = devm_phy_optional_get(dev, "hdmi");
- if (IS_ERR(hdmi->phy)) {
- ret = PTR_ERR(hdmi->phy);
- return dev_err_probe(dev, ret, "failed to get phy\n");
- }
+ if (IS_ERR(hdmi->phy))
+ return dev_err_probe(dev, PTR_ERR(hdmi->phy),
+ "failed to get phy\n");
index = of_property_match_string(np, "phy-names", "hdmi");
if (index >= 0) {
--
2.54.0
^ permalink raw reply related
* [PATCH v2 03/11] drm/rockchip: dw_hdmi: Use drmres helpers for encoder resources
From: Jonas Karlman @ 2026-05-18 19:37 UTC (permalink / raw)
To: Heiko Stübner, Sandy Huang, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel,
Jonas Karlman
In-Reply-To: <20260518193748.2482823-1-jonas@kwiboo.se>
Change to use drmres helpers drmm_kzalloc() to allocate driver data
and drmm_encoder_init() to initialize the encoder. With use of drmres
the manual encoder cleanup in failure path and unbind is also removed.
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v2: Adjust error message related to dw-hdmi bridge
---
drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 29 ++++++++-------------
1 file changed, 11 insertions(+), 18 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
index 0d5d60c315c6..12a7c989c4ee 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -14,6 +14,7 @@
#include <drm/bridge/dw_hdmi.h>
#include <drm/drm_edid.h>
+#include <drm/drm_managed.h>
#include <drm/drm_of.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
@@ -550,13 +551,14 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
if (!drv_data)
return -ENODEV;
- hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
+ hdmi = drmm_kzalloc(drm, sizeof(*hdmi), GFP_KERNEL);
if (!hdmi)
return -ENOMEM;
- plat_data = devm_kmemdup(dev, drv_data, sizeof(*drv_data), GFP_KERNEL);
+ plat_data = drmm_kzalloc(drm, sizeof(*drv_data), GFP_KERNEL);
if (!plat_data)
return -ENOMEM;
+ memcpy(plat_data, drv_data, sizeof(*drv_data));
hdmi->dev = dev;
hdmi->plat_data = plat_data;
@@ -608,28 +610,20 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
FIELD_PREP_WM16(RK3568_HDMI_SCLIN_MSK, 1));
}
+ ret = drmm_encoder_init(drm, encoder, NULL, DRM_MODE_ENCODER_TMDS, NULL);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to init encoder\n");
+
drm_encoder_helper_add(encoder, &dw_hdmi_rockchip_encoder_helper_funcs);
- drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS);
platform_set_drvdata(pdev, hdmi);
hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data);
-
- /*
- * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(),
- * which would have called the encoder cleanup. Do it manually.
- */
- if (IS_ERR(hdmi->hdmi)) {
- ret = PTR_ERR(hdmi->hdmi);
- goto err_bind;
- }
+ if (IS_ERR(hdmi->hdmi))
+ return dev_err_probe(dev, PTR_ERR(hdmi->hdmi),
+ "failed to probe dw-hdmi bridge\n");
return 0;
-
-err_bind:
- drm_encoder_cleanup(encoder);
-
- return ret;
}
static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master,
@@ -638,7 +632,6 @@ static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master,
struct rockchip_hdmi *hdmi = dev_get_drvdata(dev);
dw_hdmi_unbind(hdmi->hdmi);
- drm_encoder_cleanup(&hdmi->encoder.encoder);
}
static const struct component_ops dw_hdmi_rockchip_ops = {
--
2.54.0
^ permalink raw reply related
* [PATCH v2 05/11] drm/rockchip: dw_hdmi: Hold a reference to the dw-hdmi bridge
From: Jonas Karlman @ 2026-05-18 19:37 UTC (permalink / raw)
To: Heiko Stübner, Sandy Huang, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel,
Jonas Karlman
In-Reply-To: <20260518193748.2482823-1-jonas@kwiboo.se>
Take a reference on the dw-hdmi bridge during bind and drop it again
from unbind to ensure the bridge is kept alive for the lifetime of the
encoder component.
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v2: Use dw_hdmi_unbind() and adjust the error message
---
drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
index 8c26223b70b5..538906e342d2 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -79,6 +79,7 @@ struct rockchip_hdmi {
struct clk *hdmiphy_clk;
struct clk *ref_clk;
struct clk *grf_clk;
+ struct drm_bridge *bridge;
struct dw_hdmi *hdmi;
struct phy *phy;
};
@@ -609,6 +610,13 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
return dev_err_probe(dev, PTR_ERR(hdmi->hdmi),
"failed to probe dw-hdmi bridge\n");
+ hdmi->bridge = of_drm_find_and_get_bridge(np);
+ if (!hdmi->bridge) {
+ dw_hdmi_unbind(hdmi->hdmi);
+ return dev_err_probe(dev, -ENODEV,
+ "failed to find dw-hdmi bridge\n");
+ }
+
return 0;
}
@@ -617,6 +625,7 @@ static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master,
{
struct rockchip_hdmi *hdmi = dev_get_drvdata(dev);
+ drm_bridge_put(hdmi->bridge);
dw_hdmi_unbind(hdmi->hdmi);
}
--
2.54.0
^ permalink raw reply related
* [PATCH v2 00/11] drm/rockchip: dw_hdmi: Misc cleanup and propagate bus format
From: Jonas Karlman @ 2026-05-18 19:37 UTC (permalink / raw)
To: Heiko Stübner, Sandy Huang, Andy Yan
Cc: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, dri-devel, linux-rockchip, linux-arm-kernel,
linux-kernel, Jonas Karlman
This series include misc cleanup of the dwhdmi-rockchip driver and
prepares for future support of YCbCr output and Deep Color modes.
Patch 1-7 cleanup and changes to use drmres helpers for the encoder.
Patch 8 prepare for use of a display-connector bridge for RK3568/RK3566.
Patch 9-10 prepares for future support of YCbCr and Deep Color modes.
Patch 11 changes to use resume_early pm ops for system suspend.
This series depends on the patch "drm/rockchip: dw_hdmi: avoid direct
dereference of phy->dev.of_node" [1] from the series "Split Generic PHY
consumer and provider API" [2].
[1] https://lore.kernel.org/linux-phy/20260505100523.1922388-16-vladimir.oltean@nxp.com/
[2] https://lore.kernel.org/linux-phy/20260505100523.1922388-1-vladimir.oltean@nxp.com/
This series is part of a multi series effort to:
- phy: rockchip: inno-hdmi: Change TMDS rate handling to configure() ops [v4]
- drm/rockchip: dw_hdmi: Misc cleanup and propagate bus format [v2]
- drm: bridge: dw_hdmi: Misc enable/disable, CEC and EDID cleanup [v7]
- drm/meson: hdmi: Misc cleanup and use CEC notifier helpers [v1]
- drm/bridge: dw-hdmi: Improve input/output bus format handling
- drm/bridge: dw-hdmi: Convert to a HDMI bridge and use of bridge connector
- drm/bridge: dw-hdmi: Add and use tmds_char_rate_valid() plat data ops
- drm/rockchip: dw_hdmi: Enable YCbCr and Deep Color modes
Link to snapshot: https://github.com/Kwiboo/linux-rockchip/commits/next-20260518-rk-hdmi-v5/
Changes in v2:
- Add patch to use resume_early pm ops for system suspend
- Adjust error messages related to the dw-hdmi bridge
- Use dw_hdmi_unbind() instead of dw_hdmi_remove()
Link to v1: https://lore.kernel.org/dri-devel/20260510183114.1248840-1-jonas@kwiboo.se/
Jonas Karlman (11):
drm/rockchip: dw_hdmi: Use of_device_get_match_data() to get match
data
drm/rockchip: dw_hdmi: Use local dev variable consistently in bind()
drm/rockchip: dw_hdmi: Use drmres helpers for encoder resources
drm/rockchip: dw_hdmi: Inline resource lookup into bind()
drm/rockchip: dw_hdmi: Hold a reference to the dw-hdmi bridge
drm/rockchip: dw_hdmi: Remove empty encoder helper funcs
drm/rockchip: dw_hdmi: Clean up whitespace
drm/rockchip: dw_hdmi: Set output_port for RK3568/RK3566
drm/rockchip: dw_hdmi: Configure HDMI PHY in atomic_mode_set()
drm/rockchip: dw_hdmi: Propagate bus format to display driver
drm/rockchip: dw_hdmi: Use resume_early pm ops for system suspend
drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 227 ++++++++++++--------
1 file changed, 133 insertions(+), 94 deletions(-)
--
2.54.0
^ permalink raw reply
* [PATCH v2 02/11] drm/rockchip: dw_hdmi: Use local dev variable consistently in bind()
From: Jonas Karlman @ 2026-05-18 19:37 UTC (permalink / raw)
To: Heiko Stübner, Sandy Huang, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel,
Jonas Karlman
In-Reply-To: <20260518193748.2482823-1-jonas@kwiboo.se>
Replace indirect struct device accesses via hdmi->dev and pdev->dev with
the local dev parameter already available in dw_hdmi_rockchip_bind(),
for consistency and readability.
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v2: No change
---
drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
index fd7cc1572a40..0d5d60c315c6 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -550,16 +550,15 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
if (!drv_data)
return -ENODEV;
- hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
+ hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
if (!hdmi)
return -ENOMEM;
- plat_data = devm_kmemdup(&pdev->dev, drv_data,
- sizeof(*drv_data), GFP_KERNEL);
+ plat_data = devm_kmemdup(dev, drv_data, sizeof(*drv_data), GFP_KERNEL);
if (!plat_data)
return -ENOMEM;
- hdmi->dev = &pdev->dev;
+ hdmi->dev = dev;
hdmi->plat_data = plat_data;
hdmi->chip_data = plat_data->phy_data;
plat_data->phy_data = hdmi;
@@ -581,13 +580,13 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
ret = rockchip_hdmi_parse_dt(hdmi);
if (ret) {
- return dev_err_probe(hdmi->dev, ret, "Unable to parse OF data\n");
+ return dev_err_probe(dev, ret, "Unable to parse OF data\n");
}
hdmi->phy = devm_phy_optional_get(dev, "hdmi");
if (IS_ERR(hdmi->phy)) {
ret = PTR_ERR(hdmi->phy);
- return dev_err_probe(hdmi->dev, ret, "failed to get phy\n");
+ return dev_err_probe(dev, ret, "failed to get phy\n");
}
index = of_property_match_string(np, "phy-names", "hdmi");
--
2.54.0
^ permalink raw reply related
* [PATCH v2 01/11] drm/rockchip: dw_hdmi: Use of_device_get_match_data() to get match data
From: Jonas Karlman @ 2026-05-18 19:37 UTC (permalink / raw)
To: Heiko Stübner, Sandy Huang, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel,
Jonas Karlman
In-Reply-To: <20260518193748.2482823-1-jonas@kwiboo.se>
Change to use of_device_get_match_data() to get match data prior to
allocating private data. All current entries in the of_device_id match
table provide match data, so no functional change is intended.
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v2: No change
---
drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
index 40df6d1be2bf..fd7cc1572a40 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -536,8 +536,8 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
{
struct platform_device *pdev = to_platform_device(dev);
struct device_node *np = dev_of_node(dev);
+ const struct dw_hdmi_plat_data *drv_data;
struct dw_hdmi_plat_data *plat_data;
- const struct of_device_id *match;
struct drm_device *drm = data;
struct drm_encoder *encoder;
struct rockchip_hdmi *hdmi;
@@ -546,13 +546,16 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
if (!np)
return -ENODEV;
+ drv_data = of_device_get_match_data(dev);
+ if (!drv_data)
+ return -ENODEV;
+
hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
if (!hdmi)
return -ENOMEM;
- match = of_match_node(dw_hdmi_rockchip_dt_ids, np);
- plat_data = devm_kmemdup(&pdev->dev, match->data,
- sizeof(*plat_data), GFP_KERNEL);
+ plat_data = devm_kmemdup(&pdev->dev, drv_data,
+ sizeof(*drv_data), GFP_KERNEL);
if (!plat_data)
return -ENOMEM;
--
2.54.0
^ permalink raw reply related
* [PATCH v02] mailbox: pcc: report errors for PCC clients
From: Adam Young @ 2026-05-18 19:30 UTC (permalink / raw)
To: Sudeep Holla, Jassi Brar
Cc: linux-kernel, linux-hwmon, Rafael J . Wysocki, Len Brown,
linux-acpi, Andi Shyti, Guenter Roeck, Huisong Li, MyungJoo Ham,
Kyungmin Park, Chanwoo Choi, linux-arm-kernel
The tx_done callback function has a return code (rc) parameter
that the tx_done callback can use to determine how to handle an error.
However the IRQ handler was not setting that value if there is an error.
The following clients are affected:
drivers/acpi/cppc_acpi.c
drivers/i2c/busses/i2c-xgene-slimpro.c
drivers/hwmon/xgene-hwmon.c
drivers/soc/hisilicon/kunpeng_hccs.c
drivers/devfreq/hisi_uncore_freq.c
All of these only use the error code to report, so they
are expecting an error code to come thorugh, but they
do not modify behavior based on this code.
In the case of an error code in the IRQ, the handler was returning
IRQ_NONE which is not correct: the IRQ handler was matched
to the IRQ. This mean that multiple error codes returned from
a PCC triggered interrupt would end up disabling the device.
In addition, if the error code IRQ was coming from a Type4 Device that was
expecting an IRQ response, that device would then be hung.
Fixes: c45ded7e1135 ("mailbox: pcc: Add support for PCCT extended PCC subspaces(type 3/4)")
Signed-off-by: Adam Young <admiyo@os.amperecomputing.com>
---
---
drivers/mailbox/pcc.c | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/drivers/mailbox/pcc.c b/drivers/mailbox/pcc.c
index 636879ae1db7..16b9ce087b9e 100644
--- a/drivers/mailbox/pcc.c
+++ b/drivers/mailbox/pcc.c
@@ -314,6 +314,7 @@ static irqreturn_t pcc_mbox_irq(int irq, void *p)
{
struct pcc_chan_info *pchan;
struct mbox_chan *chan = p;
+ int rc;
pchan = chan->con_priv;
@@ -327,8 +328,7 @@ static irqreturn_t pcc_mbox_irq(int irq, void *p)
if (!pcc_mbox_cmd_complete_check(pchan))
return IRQ_NONE;
- if (pcc_mbox_error_check_and_clear(pchan))
- return IRQ_NONE;
+ rc = pcc_mbox_error_check_and_clear(pchan);
/*
* Clear this flag after updating interrupt ack register and just
@@ -337,8 +337,9 @@ static irqreturn_t pcc_mbox_irq(int irq, void *p)
* required to avoid any possible race in updatation of this flag.
*/
pchan->chan_in_use = false;
- mbox_chan_received_data(chan, NULL);
- mbox_chan_txdone(chan, 0);
+ if (!rc)
+ mbox_chan_received_data(chan, NULL);
+ mbox_chan_txdone(chan, rc);
pcc_chan_acknowledge(pchan);
--
2.43.0
^ permalink raw reply related
* Re: [PATCH] mailbox: mtk-adsp: fix UAF during device teardown
From: Jassi Brar @ 2026-05-18 19:04 UTC (permalink / raw)
To: Sergey Senozhatsky
Cc: Matthias Brugger, AngeloGioacchino Del Regno, Allen-KH Cheng,
YC Hung, Tzung-Bi Shih, linux-kernel, linux-arm-kernel,
linux-mediatek
In-Reply-To: <20260428025614.1094085-1-senozhatsky@chromium.org>
On Mon, Apr 27, 2026 at 9:56 PM Sergey Senozhatsky
<senozhatsky@chromium.org> wrote:
>
> When the SOF audio driver fails to initialize (e.g. firmware boot
> timeout), its devres unwind frees the snd_sof_dev object that the
> mailbox client (mtk-adsp-ipc) reaches via chan->cl->rx_callback.
> The mtk-adsp-mailbox shutdown clears the mailbox command registers
> but leaves the IRQ line unmasked, so a late interrupt can still
> queue a threaded handler after mbox_free_channel() had cleared
> chan->cl, and mbox_chan_received_data() would then trigger UAF:
>
> BUG: KASAN: slab-use-after-free in sof_ipc3_validate_fw_version
> sof_ipc3_validate_fw_version
> sof_ipc3_do_rx_work
> sof_ipc3_rx_msg
> mt8196_dsp_handle_request
> mtk_adsp_ipc_recv
> mbox_chan_received_data
> mtk_adsp_mbox_isr
> irq_thread_fn
> Freed by task ...:
> kfree
> devres_release_all
> really_probe
> ... (sof-audio-of-mt8196 probe failure)
>
> The crash was observed roughly three seconds after the failed probe.
>
> disable_irq() in shutdown and enable_irq() in startup. disable_irq()
> also waits for any in-flight interrupts, so by the time
> mbox_free_channel() proceeds to clear chan->cl no rx_callback can run.
>
> In addition, request the IRQ with IRQF_NO_AUTOEN so it stays masked
> between probe and the first client bind — otherwise an early interrupt
> can crash on chan->cl == NULL in mbox_chan_received_data().
>
> Fixes: af2dfa96c52d ("mailbox: mediatek: add support for adsp mailbox controller")
> Signed-off-by: Sergey Senozhatsky <senozhatsky@chromium.org>
> ---
> drivers/mailbox/mtk-adsp-mailbox.c | 9 ++++++++-
> 1 file changed, 8 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/mailbox/mtk-adsp-mailbox.c b/drivers/mailbox/mtk-adsp-mailbox.c
> index 91487aa4d7da..8bcecddee0eb 100644
> --- a/drivers/mailbox/mtk-adsp-mailbox.c
> +++ b/drivers/mailbox/mtk-adsp-mailbox.c
> @@ -19,6 +19,7 @@ struct mtk_adsp_mbox_priv {
> struct mbox_controller mbox;
> void __iomem *va_mboxreg;
> const struct mtk_adsp_mbox_cfg *cfg;
> + int irq;
> };
>
> struct mtk_adsp_mbox_cfg {
> @@ -67,6 +68,8 @@ static int mtk_adsp_mbox_startup(struct mbox_chan *chan)
> writel(0xFFFFFFFF, priv->va_mboxreg + priv->cfg->clr_in);
> writel(0xFFFFFFFF, priv->va_mboxreg + priv->cfg->clr_out);
>
> + enable_irq(priv->irq);
> +
> return 0;
> }
>
> @@ -74,6 +77,8 @@ static void mtk_adsp_mbox_shutdown(struct mbox_chan *chan)
> {
> struct mtk_adsp_mbox_priv *priv = get_mtk_adsp_mbox_priv(chan->mbox);
>
> + disable_irq(priv->irq);
> +
> /* Clear ADSP mbox command */
> writel(0xFFFFFFFF, priv->va_mboxreg + priv->cfg->clr_in);
> writel(0xFFFFFFFF, priv->va_mboxreg + priv->cfg->clr_out);
> @@ -139,8 +144,10 @@ static int mtk_adsp_mbox_probe(struct platform_device *pdev)
> if (irq < 0)
> return irq;
>
> + priv->irq = irq;
> ret = devm_request_threaded_irq(dev, irq, mtk_adsp_mbox_irq,
> - mtk_adsp_mbox_isr, IRQF_TRIGGER_NONE,
> + mtk_adsp_mbox_isr,
> + IRQF_TRIGGER_NONE | IRQF_NO_AUTOEN,
> dev_name(dev), mbox->chans);
> if (ret < 0)
> return ret;
> --
> 2.54.0.545.g6539524ca2-goog
>
Applied to mailbox/for-next
Thanks
Jassi
^ permalink raw reply
* Re: [PATCH] mailbox: exynos: Drop unused register definitions
From: Jassi Brar @ 2026-05-18 19:00 UTC (permalink / raw)
To: Tudor Ambarus
Cc: Krzysztof Kozlowski, Alim Akhtar, alexey.klimov, linux-kernel,
linux-samsung-soc, linux-arm-kernel
In-Reply-To: <20260430-exynos-mbox-dead-def-v1-1-a69176b7d0f0@linaro.org>
On Thu, Apr 30, 2026 at 6:13 AM Tudor Ambarus <tudor.ambarus@linaro.org> wrote:
>
> Leaving these dead definitions in place hides which registers are
> actually being used by the hardware, making the driver harder to read
> and maintain. Remove them to clean up the file.
>
> Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
> ---
> drivers/mailbox/exynos-mailbox.c | 7 -------
> 1 file changed, 7 deletions(-)
>
> diff --git a/drivers/mailbox/exynos-mailbox.c b/drivers/mailbox/exynos-mailbox.c
> index 5f2d3b81c1db..953e715a89b1 100644
> --- a/drivers/mailbox/exynos-mailbox.c
> +++ b/drivers/mailbox/exynos-mailbox.c
> @@ -16,15 +16,8 @@
> #include <linux/platform_device.h>
> #include <linux/slab.h>
>
> -#define EXYNOS_MBOX_MCUCTRL 0x0 /* Mailbox Control Register */
> -#define EXYNOS_MBOX_INTCR0 0x24 /* Interrupt Clear Register 0 */
> #define EXYNOS_MBOX_INTMR0 0x28 /* Interrupt Mask Register 0 */
> -#define EXYNOS_MBOX_INTSR0 0x2c /* Interrupt Status Register 0 */
> -#define EXYNOS_MBOX_INTMSR0 0x30 /* Interrupt Mask Status Register 0 */
> #define EXYNOS_MBOX_INTGR1 0x40 /* Interrupt Generation Register 1 */
> -#define EXYNOS_MBOX_INTMR1 0x48 /* Interrupt Mask Register 1 */
> -#define EXYNOS_MBOX_INTSR1 0x4c /* Interrupt Status Register 1 */
> -#define EXYNOS_MBOX_INTMSR1 0x50 /* Interrupt Mask Status Register 1 */
>
> #define EXYNOS_MBOX_INTMR0_MASK GENMASK(15, 0)
> #define EXYNOS_MBOX_INTGR1_MASK GENMASK(15, 0)
>
> ---
Applied to mailbox/for-next
Thanks
Jassi
^ permalink raw reply
* Re: [PATCH v6 0/4] Update the thermal support for imx93
From: Daniel Lezcano @ 2026-05-18 18:50 UTC (permalink / raw)
To: Jacky Bai, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
Lukasz Luba, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Shawn Guo, Sascha Hauer, Fabio Estevam, Pengutronix Kernel Team,
Frank Li
Cc: linux-pm, devicetree, imx, linux-arm-kernel, Conor Dooley,
Alice Guo
In-Reply-To: <20260430-imx93_tmu-v6-0-485459d7b54f@nxp.com>
On 4/30/26 04:53, Jacky Bai wrote:
> The TMU (Thermal Monitoring Unit) on the i.MX93 requires specific
> configurations and workarounds that differ from previous implementations.
> So, using the 'fsl,qoriq-tmu' compatible string is not appropriate.
> To address this, a dedicated compatible string and corresponding driver
> changes need to be introduced to properly support the i.MX93 TMU.
>
> Signed-off-by: Jacky Bai <ping.bai@nxp.com>
> ---
> Changes in v6:
> - Drop the unnecessary local variable
> - Drop the first errata check in get_temp function
> - Link to v5: https://lore.kernel.org/r/20260421-imx93_tmu-v5-0-05ea1969bb9f@nxp.com
>
> Changes in v5:
> - Drop the unnecessary macro defines in patch 2/3
> - Add the drvdata info for each of the platform as suggested by Daniel
> - Link to v4: https://lore.kernel.org/r/20250821-imx93_tmu-v4-0-6cf5688bf016@nxp.com
>
> Changes in v4:
> - Include bitfield.h to fix the build error for RISC-V
> - Use macro to define temp rate threshold related settings
> - Link to v3: https://lore.kernel.org/r/20250818-imx93_tmu-v3-0-35f79a86c072@nxp.com
>
> ---
Applied patches 1,2 & 3
Thanks
^ permalink raw reply
* [PATCH v7 16/23] drm: bridge: dw_hdmi: Update EDID and CEC phys addr in bridge detect()
From: Jonas Karlman @ 2026-05-18 18:01 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se>
Update EDID and CEC phys addr in the bridge detect() func to closely
match the behavior of a bridge connector with a HDMI bridge attached
and the dw-hdmi connector.
This change introduce a slight delay to the bridge connector detect()
and get_modes() funcs due to multiple EDID reads. This is an acceptable
added delay to help ensure EDID and CEC phys addr always are correct.
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v7: Update commit message
v6: New patch
This is a temporary change until dw-hdmi is fully converted into a
HDMI bridge in a future part of this multi-series effort.
The patch "drm/bridge-connector: Use cached connector status in
.get_modes()" [1] can help remove one unnecessary EDID read until
dw-hdmi is fully converted into a HDMI bridge.
[1] https://lore.kernel.org/dri-devel/20260426-dw-hdmi-qp-scramb-v5-3-d778e70c317b@collabora.com/
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 37406555af7b..0c4388e7aa5e 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -2947,8 +2947,17 @@ static enum drm_connector_status
dw_hdmi_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector)
{
struct dw_hdmi *hdmi = bridge->driver_private;
+ enum drm_connector_status status;
- return dw_hdmi_detect(hdmi);
+ status = dw_hdmi_detect(hdmi);
+
+ /*
+ * Update EDID and CEC phys addr to match the behavior of a bridge
+ * connector with a HDMI bridge attached and the dw-hdmi connector.
+ */
+ dw_hdmi_connector_status_update(hdmi, connector, status);
+
+ return status;
}
static const struct drm_edid *dw_hdmi_bridge_edid_read(struct drm_bridge *bridge,
--
2.54.0
^ permalink raw reply related
* [PATCH v4 2/2] phy: rockchip: inno-hdmi: Remove deprecated way to configure TMDS rate
From: Jonas Karlman @ 2026-05-18 18:07 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner
Cc: linux-phy, linux-rockchip, linux-arm-kernel, linux-kernel,
Jonas Karlman
In-Reply-To: <20260518180722.2480799-1-jonas@kwiboo.se>
The TMDS character rate of this PHY is configured using PHY bus width
in downstream vendor kernel and out-of-tree patches, however no in-tree
consumer of this PHY has ever called phy_set_bus_width() to change the
TMDS character rate as currently only 8-bit RGB output is supported by
the HDMI display driver.
The series "Split Generic PHY consumer and provider" clarifies that
phy_set_bus_width() is intended as a provider-only function.
Remove the deprecated unused fallback way to configure TMDS character
rate now that this HDMI PHY support using phy_configure() to configure
the TMDS character rate.
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v4: No change
v3: No change
v2: New patch, split from original patch
---
drivers/phy/rockchip/phy-rockchip-inno-hdmi.c | 17 +----------------
1 file changed, 1 insertion(+), 16 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c b/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c
index 1c43ea700f0e..c3d257a0f4a6 100644
--- a/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c
+++ b/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c
@@ -555,24 +555,10 @@ static inline void inno_update_bits(struct inno_hdmi_phy *inno, u8 reg,
static unsigned long inno_hdmi_phy_get_tmdsclk(struct inno_hdmi_phy *inno,
unsigned long rate)
{
- int bus_width;
-
if (inno->opts_tmds_char_rate)
return inno->opts_tmds_char_rate;
- bus_width = phy_get_bus_width(inno->phy);
-
- switch (bus_width) {
- case 4:
- case 5:
- case 6:
- case 10:
- case 12:
- case 16:
- return (u64)rate * bus_width / 8;
- default:
- return rate;
- }
+ return rate;
}
static irqreturn_t inno_hdmi_phy_rk3328_hardirq(int irq, void *dev_id)
@@ -1452,7 +1438,6 @@ static int inno_hdmi_phy_probe(struct platform_device *pdev)
phy_set_drvdata(inno->phy, inno);
phy_set_mode_ext(inno->phy, PHY_MODE_HDMI, PHY_HDMI_MODE_TMDS);
- phy_set_bus_width(inno->phy, 8);
if (inno->plat_data->ops->init) {
ret = inno->plat_data->ops->init(inno);
--
2.54.0
^ permalink raw reply related
* [PATCH v4 0/2] phy: rockchip: inno-hdmi: Change TMDS rate handling to configure() ops
From: Jonas Karlman @ 2026-05-18 18:07 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner
Cc: linux-phy, linux-rockchip, linux-arm-kernel, linux-kernel,
Jonas Karlman
This series adds support for using phy_validate() and phy_configure()
with this HDMI PHY as an alternative to current in-tree unused way of
using PHY bus width to configure the TMDS character rate.
The only known users that calls phy_set_bus_width() on this PHY are my
out-of-tree HDMI 2.0 patches for Rockchip RK3228/RK3328, i.e. those
originating from LibreELEC (also carried by other distros), the
downstream vendor kernel uses a different implementation that also calls
phy_set_bus_width() on this PHY.
Patch "drm/rockchip: dw_hdmi: Configure HDMI PHY in atomic_mode_set()"
that calls phy_validate() and phy_configure() on this PHY can be found
at [1].
[1] https://lore.kernel.org/dri-devel/20260510183114.1248840-10-jonas@kwiboo.se/
This series is part of a larger multi series effort to:
- phy: rockchip: inno-hdmi: Change TMDS rate handling to configure() ops [v4]
- drm/rockchip: dw_hdmi: Misc cleanup and propagate bus format [v2]
- drm: bridge: dw_hdmi: Misc enable/disable, CEC and EDID cleanup [v7]
- drm/bridge: dw-hdmi: Improve input/output bus format handling
- drm/bridge: dw-hdmi: Convert to a HDMI bridge and use of bridge connector
- drm/bridge: dw-hdmi: Add and use tmds_char_rate_valid() plat data ops
- drm/meson: hdmi: Misc cleanup and use CEC notifier helpers [v1]
- drm/rockchip: dw_hdmi: Enable YCbCr and Deep Color modes
Link to snapshot: https://github.com/Kwiboo/linux-rockchip/commits/next-20260518-rk-hdmi-v5/
Changes in v4:
- Add NULL opts check in validate()
- Only store the opts->hdmi.tmds_char_rate value for later use
- Move comments about expected consumer usage from inline to above the
functions
Link to v3: https://lore.kernel.org/linux-phy/20260515195512.1757363-1-jonas@kwiboo.se/
Changes in v3:
- Change validate() ops to only validate tmdsclock
- Add comments about expected consumer usage
- Update commit message with a typical call chain
Link to v2: https://lore.kernel.org/linux-phy/20260510095731.1222705-1-jonas@kwiboo.se/
Changes in v2:
- Split into two patches, one that adds new ops and a second that remove
the old and unused workaround
- Add validate() ops to validate that the TMDS rate is supported
Link to v1: https://lore.kernel.org/linux-phy/20260503172936.194003-1-jonas@kwiboo.se/
Jonas Karlman (2):
phy: rockchip: inno-hdmi: Add configure() and validate() ops
phy: rockchip: inno-hdmi: Remove deprecated way to configure TMDS rate
drivers/phy/rockchip/phy-rockchip-inno-hdmi.c | 71 +++++++++++++++----
1 file changed, 58 insertions(+), 13 deletions(-)
--
2.54.0
^ permalink raw reply
* [PATCH v4 1/2] phy: rockchip: inno-hdmi: Add configure() and validate() ops
From: Jonas Karlman @ 2026-05-18 18:07 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner
Cc: linux-phy, linux-rockchip, linux-arm-kernel, linux-kernel,
Jonas Karlman
In-Reply-To: <20260518180722.2480799-1-jonas@kwiboo.se>
The commit 10ed34d6eaaf ("phy: Add HDMI configuration options")
introduced a way for HDMI PHYs to be configured through the generic
phy_configure() function.
This driver derives the TMDS character rate from the pixel clock and the
PHY bus width setting. However, no in-tree consumer of this PHY has ever
called phy_set_bus_width() to change the TMDS character rate as only
8-bit RGB output is supported by the HDMI display driver.
Add configure() and validate() ops to allow consumers to configure the
TMDS character rate using phy_configure(). Fallback to the deprecated
way of using the PHY bus width to configure the TMDS character rate.
A typical call chain during DRM modeset on a RK3328 device:
dw_hdmi_rockchip_encoder_atomic_check():
- inno_hdmi_phy_validate(): pixclock 148500000 tmdsclock 594000000
dw_hdmi_rockchip_encoder_atomic_mode_set():
- inno_hdmi_phy_configure(): pixclock 148500000
- inno_hdmi_phy_validate(): pixclock 148500000 tmdsclock 594000000
vop_crtc_atomic_enable():
- inno_hdmi_phy_rk3328_clk_set_rate(): rate 594000000 tmdsclk 594000000
inno_hdmi_phy_rk3328_clk_set_rate(): pixclock 594000000 tmdsclock 594000000
- inno_hdmi_phy_rk3328_clk_recalc_rate(): pixclock 594000000 vco 594000000
dw_hdmi_rockchip_encoder_enable():
- inno_hdmi_phy_power_on(): Inno HDMI PHY Power On
- inno_hdmi_phy_rk3328_clk_set_rate(): rate 594000000 tmdsclk 594000000
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
Changes in v4:
- Add NULL opts check in validate()
- Only store the opts->hdmi.tmds_char_rate value for later use
- Move comments about expected consumer usage from inline to above the
functions
- Indent a set_rate() sub-call in the commit message call chain
Changes in v3:
- Change validate() ops to only validate tmdsclock
- Add comments about expected consumer usage
- Update commit message with a typical call chain
Changes in v2:
- Add validate() ops to validate that the TMDS rate is supported
- Split out parts that remove the old workaround into a separate patch
Patch "drm/rockchip: dw_hdmi: Configure HDMI PHY in atomic_mode_set()"
at [1] adds phy_validate() and phy_configure() calls for this HDMI PHY.
[1] https://lore.kernel.org/dri-devel/20260510183114.1248840-10-jonas@kwiboo.se/
---
drivers/phy/rockchip/phy-rockchip-inno-hdmi.c | 62 ++++++++++++++++++-
1 file changed, 61 insertions(+), 1 deletion(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c b/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c
index 1483907413fa..1c43ea700f0e 100644
--- a/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c
+++ b/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c
@@ -245,6 +245,7 @@ struct inno_hdmi_phy {
struct clk *phyclk;
unsigned long pixclock;
unsigned long tmdsclock;
+ unsigned long opts_tmds_char_rate;
};
struct pre_pll_config {
@@ -554,7 +555,12 @@ static inline void inno_update_bits(struct inno_hdmi_phy *inno, u8 reg,
static unsigned long inno_hdmi_phy_get_tmdsclk(struct inno_hdmi_phy *inno,
unsigned long rate)
{
- int bus_width = phy_get_bus_width(inno->phy);
+ int bus_width;
+
+ if (inno->opts_tmds_char_rate)
+ return inno->opts_tmds_char_rate;
+
+ bus_width = phy_get_bus_width(inno->phy);
switch (bus_width) {
case 4:
@@ -602,6 +608,57 @@ static irqreturn_t inno_hdmi_phy_rk3328_irq(int irq, void *dev_id)
return IRQ_HANDLED;
}
+/*
+ * phy_validate() is expected to be called from encoder atomic_check(), before
+ * the hdmiphy pixel clock is known. Without knowing the actual pixel clock, we
+ * cannot do full validation of the configuration. Instead, we do a simple check
+ * that the pre-pll table contains an entry for the requested TMDS char rate.
+ */
+static int inno_hdmi_phy_validate(struct phy *phy, enum phy_mode mode,
+ int submode, union phy_configure_opts *opts)
+{
+ const struct pre_pll_config *cfg = pre_pll_cfg_table;
+ unsigned long tmdsclock;
+
+ if (!(mode == PHY_MODE_HDMI && submode == PHY_HDMI_MODE_TMDS))
+ return -EINVAL;
+
+ if (!opts)
+ return -EINVAL;
+
+ if (!opts->hdmi.tmds_char_rate || opts->hdmi.tmds_char_rate > 594000000)
+ return -EINVAL;
+
+ tmdsclock = opts->hdmi.tmds_char_rate;
+ for (; cfg->pixclock != 0; cfg++)
+ if (cfg->tmdsclock == tmdsclock)
+ return 0;
+
+ return -EINVAL;
+}
+
+/*
+ * phy_configure() is expected to be called from encoder atomic_set_mode(),
+ * before the hdmiphy pixel clock is known. Store the requested TMDS character
+ * rate, so that it can be used later in power_on() and/or set_rate() when the
+ * pixel clock is known.
+ */
+static int inno_hdmi_phy_configure(struct phy *phy,
+ union phy_configure_opts *opts)
+{
+ struct inno_hdmi_phy *inno = phy_get_drvdata(phy);
+ int ret;
+
+ ret = inno_hdmi_phy_validate(phy, phy_get_mode(phy),
+ PHY_HDMI_MODE_TMDS, opts);
+ if (ret)
+ return ret;
+
+ inno->opts_tmds_char_rate = opts->hdmi.tmds_char_rate;
+
+ return 0;
+}
+
static int inno_hdmi_phy_power_on(struct phy *phy)
{
struct inno_hdmi_phy *inno = phy_get_drvdata(phy);
@@ -670,6 +727,8 @@ static const struct phy_ops inno_hdmi_phy_ops = {
.owner = THIS_MODULE,
.power_on = inno_hdmi_phy_power_on,
.power_off = inno_hdmi_phy_power_off,
+ .configure = inno_hdmi_phy_configure,
+ .validate = inno_hdmi_phy_validate,
};
static const
@@ -1392,6 +1451,7 @@ static int inno_hdmi_phy_probe(struct platform_device *pdev)
}
phy_set_drvdata(inno->phy, inno);
+ phy_set_mode_ext(inno->phy, PHY_MODE_HDMI, PHY_HDMI_MODE_TMDS);
phy_set_bus_width(inno->phy, 8);
if (inno->plat_data->ops->init) {
--
2.54.0
^ permalink raw reply related
* [PATCH v7 23/23] drm: bridge: dw_hdmi: Merge top and bottom half IRQ handlers
From: Jonas Karlman @ 2026-05-18 18:01 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se>
The bottom half IRQ handler only modify delay of or queue a delayed work
used for HPD handling. The mod_delayed_work() called is documented as
being safe to call from any context including IRQ handler.
Merge top and bottom half IRQ handlers to simplify IRQ handling now that
HPD event is handled using a delayed work.
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v7: No change
v6: Collect r-b tag
v5: No change
v4: New patch
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 34 ++++++++---------------
1 file changed, 11 insertions(+), 23 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index c596534510da..99dd62b6becf 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -2993,30 +2993,18 @@ static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id)
if (hdmi->i2c)
ret = dw_hdmi_i2c_irq(hdmi);
- intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0);
- if (intr_stat) {
- hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0);
- return IRQ_WAKE_THREAD;
- }
-
- return ret;
-}
-
-static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
-{
- struct dw_hdmi *hdmi = dev_id;
- u8 intr_stat;
-
/*
* Interrupt generation is accomplished in the following way:
* interrupt = (mask == 0) && (polarity == status)
* All interrupts are forwarded to the Interrupt Handler sticky bit
* register ih_phy_stat0 and muted using the register ih_mute_phy_stat0.
*/
- intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0);
- if (intr_stat & HDMI_IH_PHY_STAT0_HPD) {
+ intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0) & HDMI_IH_PHY_STAT0_HPD;
+ if (intr_stat) {
enum drm_connector_status status;
+ hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0);
+
/* Set HPD interrupt polarity based on current HPD status. */
status = dw_hdmi_phy_read_hpd(hdmi, hdmi->phy.data);
hdmi_modb(hdmi, status == connector_status_connected ?
@@ -3028,12 +3016,13 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
mod_delayed_work(system_percpu_wq, &hdmi->hpd_work,
msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS));
+
+ hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0);
+ hdmi_writeb(hdmi, ~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0);
+ ret = IRQ_HANDLED;
}
- hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0);
- hdmi_writeb(hdmi, ~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0);
-
- return IRQ_HANDLED;
+ return ret;
}
static void dw_hdmi_hpd_work(struct work_struct *work)
@@ -3343,9 +3332,8 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
INIT_DELAYED_WORK(&hdmi->hpd_work, dw_hdmi_hpd_work);
disable_delayed_work(&hdmi->hpd_work);
- ret = devm_request_threaded_irq(dev, irq, dw_hdmi_hardirq,
- dw_hdmi_irq, IRQF_SHARED,
- dev_name(dev), hdmi);
+ ret = devm_request_irq(dev, irq, dw_hdmi_hardirq, IRQF_SHARED,
+ dev_name(dev), hdmi);
if (ret)
goto err_res;
--
2.54.0
^ permalink raw reply related
* [PATCH v7 22/23] drm: bridge: dw_hdmi: Remove the empty dw_hdmi_phy_update_hpd()
From: Jonas Karlman @ 2026-05-18 18:01 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Liu Ying, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Luca Ceresoli, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Sandy Huang,
Andy Yan, Chen-Yu Tsai, Samuel Holland
Cc: Christian Hewitt, Diederik de Haas, Nicolas Frattaroli,
Dmitry Baryshkov, dri-devel, linux-arm-kernel, linux-rockchip,
linux-amlogic, linux-sunxi, imx, linux-kernel
In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se>
The dw_hdmi_phy_update_hpd() helper is empty and no longer needed after
recent RXSENSE and HPD rework, remove it.
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v7: No change
v6: Collect t-b tag
v5: No change
v4: New patch
---
drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c | 1 -
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 7 -------
drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 2 --
drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c | 2 --
include/drm/bridge/dw_hdmi.h | 4 ----
5 files changed, 16 deletions(-)
diff --git a/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c
index 8e8cfd66f23b..20d389dbfdc5 100644
--- a/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c
+++ b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c
@@ -78,7 +78,6 @@ static const struct dw_hdmi_phy_ops imx8mp_hdmi_phy_ops = {
.disable = imx8mp_hdmi_phy_disable,
.setup_hpd = im8mp_hdmi_phy_setup_hpd,
.read_hpd = dw_hdmi_phy_read_hpd,
- .update_hpd = dw_hdmi_phy_update_hpd,
};
static int imx8mp_dw_hdmi_bind(struct device *dev)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 42d630efb875..c596534510da 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -1687,12 +1687,6 @@ enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi,
}
EXPORT_SYMBOL_GPL(dw_hdmi_phy_read_hpd);
-void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data,
- bool force, bool disabled, bool rxsense)
-{
-}
-EXPORT_SYMBOL_GPL(dw_hdmi_phy_update_hpd);
-
void dw_hdmi_phy_setup_hpd(struct dw_hdmi *hdmi, void *data)
{
/*
@@ -1716,7 +1710,6 @@ static const struct dw_hdmi_phy_ops dw_hdmi_synopsys_phy_ops = {
.init = dw_hdmi_phy_init,
.disable = dw_hdmi_phy_disable,
.read_hpd = dw_hdmi_phy_read_hpd,
- .update_hpd = dw_hdmi_phy_update_hpd,
.setup_hpd = dw_hdmi_phy_setup_hpd,
};
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
index 0dc1eb5d2ae3..7136e713df2e 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -413,7 +413,6 @@ static const struct dw_hdmi_phy_ops rk3228_hdmi_phy_ops = {
.init = dw_hdmi_rockchip_genphy_init,
.disable = dw_hdmi_rockchip_genphy_disable,
.read_hpd = dw_hdmi_phy_read_hpd,
- .update_hpd = dw_hdmi_phy_update_hpd,
.setup_hpd = dw_hdmi_rk3228_setup_hpd,
};
@@ -449,7 +448,6 @@ static const struct dw_hdmi_phy_ops rk3328_hdmi_phy_ops = {
.init = dw_hdmi_rockchip_genphy_init,
.disable = dw_hdmi_rockchip_genphy_disable,
.read_hpd = dw_hdmi_rk3328_read_hpd,
- .update_hpd = dw_hdmi_phy_update_hpd,
.setup_hpd = dw_hdmi_rk3328_setup_hpd,
};
diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
index 4fa69c463dc4..2ac99b8ce8c4 100644
--- a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
+++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
@@ -221,7 +221,6 @@ static const struct dw_hdmi_phy_ops sun8i_a83t_hdmi_phy_ops = {
.init = sun8i_a83t_hdmi_phy_config,
.disable = sun8i_a83t_hdmi_phy_disable,
.read_hpd = dw_hdmi_phy_read_hpd,
- .update_hpd = dw_hdmi_phy_update_hpd,
.setup_hpd = dw_hdmi_phy_setup_hpd,
};
@@ -395,7 +394,6 @@ static const struct dw_hdmi_phy_ops sun8i_h3_hdmi_phy_ops = {
.init = sun8i_h3_hdmi_phy_config,
.disable = sun8i_h3_hdmi_phy_disable,
.read_hpd = dw_hdmi_phy_read_hpd,
- .update_hpd = dw_hdmi_phy_update_hpd,
.setup_hpd = dw_hdmi_phy_setup_hpd,
};
diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h
index a612b9fa6dbb..10013b8d3adb 100644
--- a/include/drm/bridge/dw_hdmi.h
+++ b/include/drm/bridge/dw_hdmi.h
@@ -118,8 +118,6 @@ struct dw_hdmi_phy_ops {
const struct drm_display_mode *mode);
void (*disable)(struct dw_hdmi *hdmi, void *data);
enum drm_connector_status (*read_hpd)(struct dw_hdmi *hdmi, void *data);
- void (*update_hpd)(struct dw_hdmi *hdmi, void *data,
- bool force, bool disabled, bool rxsense);
void (*setup_hpd)(struct dw_hdmi *hdmi, void *data);
};
@@ -213,8 +211,6 @@ void dw_hdmi_phy_gen2_reset(struct dw_hdmi *hdmi);
enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi,
void *data);
-void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data,
- bool force, bool disabled, bool rxsense);
void dw_hdmi_phy_setup_hpd(struct dw_hdmi *hdmi, void *data);
bool dw_hdmi_bus_fmt_is_420(struct dw_hdmi *hdmi);
--
2.54.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox