[PATCH v2 1/8] irqchip/qcom-pdc: restructure version support

From: Maulik Shah

Date: Tue May 26 2026 - 06:55:25 EST


PDC irqchip updates IRQ_ENABLE and IRQ_CFG and for three different
versions v2.7, v3.0 and v3.2. These registers are organized in H/W
as below on various SoCs.

+---------------------------------------------------------------+
| SM8350, SM8450 | SM8550, Hamoa | SM8650, SM8750 |
|---------------------------------------------------------------|
| v2.7 | v3.0 | v3.2 |
|---------------------------------------------------------------|
| IRQ_ENABLE_BANK | IRQ_ENABLE_BANK | NA |
|---------------------------------------------------------------|
| IRQ_CFG | IRQ_CFG | IRQ_CFG |
| | | |
| | | [31:6] Unused |
| | [31:5] Unused | [5] GPIO_STATUS |
| | [4] GPIO_STATUS| [4] GPIO_MASK |
| [31:3] Unused | [3] GPIO_MASK | [3] IRQ_ENABLE |
| [0:2] Type | [0:2] Type | [0:2] Type |
+---------------------------------------------------------------|

All SoCs PDC irqchip supports "pass through mode" in which all interrupts
are forwarded to the GIC without any latching at PDC H/W.

So far irqchip did not utilize GPIO_STATUS and GPIO_MASK from IRQ_CFG
register for v3.0 and v3.2 since they are only needed to be configured
when PDC runs in specific mode named "second level interrupt controller"
where it can latch the GPIO interrupts in GPIO_STATUS and forward GPIO
interrupts to GIC as LEVEL_HIGH type SPI interrupt.

All the SoCs defaulted to pass through mode with the exception of some
x1e. x1e PDC may be set to secondary controller mode for builds on CRD
boards whereas it may be set to pass through mode for IoT-EVK boards.

Restructure in preparation to add the second level interrupt controller
mode utilizing GPIO_STATUS and GPIO_MASK bits which changed the bit
positions between v3.0 and v3.2.

No functional impact with the change.

Signed-off-by: Maulik Shah <maulik.shah@xxxxxxxxxxxxxxxx>
---
drivers/irqchip/qcom-pdc.c | 193 +++++++++++++++++++++++++++++++++++----------
1 file changed, 150 insertions(+), 43 deletions(-)

diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c
index 638b5d89a141..c5b64649b2a9 100644
--- a/drivers/irqchip/qcom-pdc.c
+++ b/drivers/irqchip/qcom-pdc.c
@@ -21,18 +21,10 @@
#include <linux/slab.h>
#include <linux/types.h>

-#define PDC_MAX_GPIO_IRQS 256
-#define PDC_DRV_SIZE 0x10000
-
-/* Valid only on HW version < 3.2 */
-#define IRQ_ENABLE_BANK 0x10
-#define IRQ_ENABLE_BANK_MAX (IRQ_ENABLE_BANK + BITS_TO_BYTES(PDC_MAX_GPIO_IRQS))
-#define IRQ_i_CFG 0x110
+#define PDC_MAX_IRQS 256
+#define IRQ_ENABLE_BANK_MAX BITS_TO_BYTES(PDC_MAX_IRQS)

-/* Valid only on HW version >= 3.2 */
-#define IRQ_i_CFG_IRQ_ENABLE 3
-
-#define IRQ_i_CFG_TYPE_MASK GENMASK(2, 0)
+#define PDC_DRV_SIZE 0x10000

#define PDC_VERSION_REG 0x1000
#define PDC_VERSION_MAJOR GENMASK(23, 16)
@@ -45,6 +37,98 @@

/* Notable PDC versions */
#define PDC_VERSION_3_2 PDC_VERSION(3, 2, 0)
+#define PDC_VERSION_3_0 PDC_VERSION(3, 0, 0)
+#define PDC_VERSION_2_7 PDC_VERSION(2, 7, 0)
+
+/*
+ * PDC H/W registers layout per version:
+ *
+ * IRQ_ENABLE_BANK[b], b = 0....BITS_TO_BYTES(PDC_MAX_IRQS)
+ * IRQ_CFG[n], n = 0....PDC_MAX_IRQS
+ *
+ * +---------------------------------------------------------------+
+ * | v2.7 | v3.0 | v3.2 |
+ * |---------------------------------------------------------------|
+ * | BASE | BASE | BASE |
+ * |---------------------------------------------------------------|
+ * | |
+ * | IRQ_ENABLE_BANK | IRQ_ENABLE_BANK | NA |
+ * |---------------------------------------------------------------|
+ * | IRQ_CFG | IRQ_CFG | IRQ_CFG |
+ * | | | |
+ * | | | [31:6] Unused |
+ * | | [31:5] Unused | [5] GPIO_STATUS |
+ * | | [4] GPIO_STATUS| [4] GPIO_MASK |
+ * | [31:3] Unused | [3] GPIO_MASK | [3] IRQ_ENABLE |
+ * | [0:2] Type | [0:2] Type | [0:2] Type |
+ * +---------------------------------------------------------------+
+ */
+
+/**
+ * struct pdc_regs: PDC registers location
+ *
+ * @irq_en_reg: IRQ_ENABLE_BANK register location
+ * @irq_cfg_reg: IRQ_CFG register location
+ */
+struct pdc_regs {
+ u32 irq_en_reg;
+ u32 irq_cfg_reg;
+};
+
+/**
+ * struct pdc_cfg: bit fields for PDC IRQ_CFG register
+ *
+ * @irq_enable: bit mask for IRQ_ENABLE
+ * @irq_type: bit mask for IRQ_TYPE
+ */
+struct pdc_cfg {
+ u32 irq_enable;
+ u32 irq_type;
+};
+
+/**
+ * struct pdc_desc: PDC driver state
+ *
+ * @base: PDC base register for DRV2 / HLOS
+ * @prev_base: PDC DRV1 base, applicable only for x1e RTL bug.
+ * @version: PDC version
+ * @regs: PDC regs (IRQ_ENABLE_BANK and IRQ_CFG)
+ * @cfg: bit masks within for IRQ_CFG reg
+ */
+struct pdc_desc {
+ void __iomem *base;
+ void __iomem *prev_base;
+ u32 version;
+ const struct pdc_regs *regs;
+ const struct pdc_cfg *cfg;
+};
+
+static const struct pdc_regs pdc_v3_2 = {
+ .irq_cfg_reg = 0x110,
+};
+
+static const struct pdc_cfg pdc_cfg_v3_2 = {
+ .irq_enable = GENMASK(3, 3),
+ .irq_type = GENMASK(2, 0),
+};
+
+static const struct pdc_regs pdc_v3_0 = {
+ .irq_en_reg = 0x10,
+ .irq_cfg_reg = 0x110,
+};
+
+static const struct pdc_cfg pdc_cfg_v3_0 = {
+ .irq_type = GENMASK(2, 0),
+};
+
+static const struct pdc_regs pdc_v2_7 = {
+ .irq_en_reg = 0x10,
+ .irq_cfg_reg = 0x110,
+};
+
+static const struct pdc_cfg pdc_cfg_v2_7 = {
+ .irq_type = GENMASK(2, 0),
+};

struct pdc_pin_region {
u32 pin_base;
@@ -55,12 +139,11 @@ struct pdc_pin_region {
#define pin_to_hwirq(r, p) ((r)->parent_base + (p) - (r)->pin_base)

static DEFINE_RAW_SPINLOCK(pdc_lock);
-static void __iomem *pdc_base;
-static void __iomem *pdc_prev_base;
static struct pdc_pin_region *pdc_region;
static int pdc_region_cnt;
static void (*__pdc_enable_intr)(int pin_out, bool on);
static bool pdc_x1e_quirk;
+static struct pdc_desc *pdc;

static void pdc_base_reg_write(void __iomem *base, int reg, u32 i, u32 val)
{
@@ -69,12 +152,12 @@ static void pdc_base_reg_write(void __iomem *base, int reg, u32 i, u32 val)

static void pdc_reg_write(int reg, u32 i, u32 val)
{
- pdc_base_reg_write(pdc_base, reg, i, val);
+ pdc_base_reg_write(pdc->base, reg, i, val);
}

static u32 pdc_reg_read(int reg, u32 i)
{
- return readl_relaxed(pdc_base + reg + i * sizeof(u32));
+ return readl_relaxed(pdc->base + reg + i * sizeof(u32));
}

static void pdc_x1e_irq_enable_write(u32 bank, u32 enable)
@@ -85,24 +168,24 @@ static void pdc_x1e_irq_enable_write(u32 bank, u32 enable)
switch (bank) {
case 0 ... 1:
/* Use previous DRV (client) region and shift to bank 3-4 */
- base = pdc_prev_base;
+ base = pdc->prev_base;
bank += 3;
break;
case 2 ... 4:
/* Use our own region and shift to bank 0-2 */
- base = pdc_base;
+ base = pdc->base;
bank -= 2;
break;
case 5:
/* No fixup required for bank 5 */
- base = pdc_base;
+ base = pdc->base;
break;
default:
WARN_ON(1);
return;
}

- pdc_base_reg_write(base, IRQ_ENABLE_BANK, bank, enable);
+ pdc_base_reg_write(base, pdc->regs->irq_en_reg, bank, enable);
}

static void pdc_enable_intr_bank(int pin_out, bool on)
@@ -113,22 +196,25 @@ static void pdc_enable_intr_bank(int pin_out, bool on)
index = FIELD_GET(GENMASK(31, 5), pin_out);
mask = FIELD_GET(GENMASK(4, 0), pin_out);

- enable = pdc_reg_read(IRQ_ENABLE_BANK, index);
+ enable = pdc_reg_read(pdc->regs->irq_en_reg, index);
__assign_bit(mask, &enable, on);

if (pdc_x1e_quirk)
pdc_x1e_irq_enable_write(index, enable);
else
- pdc_reg_write(IRQ_ENABLE_BANK, index, enable);
+ pdc_reg_write(pdc->regs->irq_en_reg, index, enable);
}

static void pdc_enable_intr_cfg(int pin_out, bool on)
{
unsigned long enable;

- enable = pdc_reg_read(IRQ_i_CFG, pin_out);
- __assign_bit(IRQ_i_CFG_IRQ_ENABLE, &enable, on);
- pdc_reg_write(IRQ_i_CFG, pin_out, enable);
+ enable = pdc_reg_read(pdc->regs->irq_cfg_reg, pin_out);
+ if (on)
+ enable |= pdc->cfg->irq_enable;
+ else
+ enable &= ~pdc->cfg->irq_enable;
+ pdc_reg_write(pdc->regs->irq_cfg_reg, pin_out, enable);
}

static void pdc_enable_intr(struct irq_data *d, bool on)
@@ -216,9 +302,9 @@ static int qcom_pdc_gic_set_type(struct irq_data *d, unsigned int type)
return -EINVAL;
}

- old_pdc_type = pdc_reg_read(IRQ_i_CFG, d->hwirq);
- pdc_type |= (old_pdc_type & ~IRQ_i_CFG_TYPE_MASK);
- pdc_reg_write(IRQ_i_CFG, d->hwirq, pdc_type);
+ old_pdc_type = pdc_reg_read(pdc->regs->irq_cfg_reg, d->hwirq);
+ pdc_type |= (old_pdc_type & ~pdc->cfg->irq_type);
+ pdc_reg_write(pdc->regs->irq_cfg_reg, d->hwirq, pdc_type);

ret = irq_chip_set_type_parent(d, type);
if (ret)
@@ -375,6 +461,34 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare
if (res_size > resource_size(&res))
pr_warn("%pOF: invalid reg size, please fix DT\n", node);

+ pdc = kzalloc_objs(*pdc, GFP_KERNEL);
+ if (!pdc)
+ return -ENOMEM;
+
+ pdc->base = ioremap(res.start, res_size);
+ if (!pdc->base) {
+ pr_err("%pOF: unable to map PDC registers\n", node);
+ ret = -ENXIO;
+ goto fail;
+ }
+
+ pdc->version = pdc_reg_read(PDC_VERSION_REG, 0);
+
+ if (pdc->version >= PDC_VERSION_3_2) {
+ pdc->cfg = &pdc_cfg_v3_2;
+ pdc->regs = &pdc_v3_2;
+ __pdc_enable_intr = pdc_enable_intr_cfg;
+ } else if (pdc->version < PDC_VERSION_3_2 &&
+ pdc->version >= PDC_VERSION_3_0) {
+ pdc->cfg = &pdc_cfg_v3_0;
+ pdc->regs = &pdc_v3_0;
+ __pdc_enable_intr = pdc_enable_intr_bank;
+ } else {
+ pdc->cfg = &pdc_cfg_v2_7;
+ pdc->regs = &pdc_v2_7;
+ __pdc_enable_intr = pdc_enable_intr_bank;
+ }
+
/*
* PDC has multiple DRV regions, each one provides the same set of
* registers for a particular client in the system. Due to a hardware
@@ -384,25 +498,17 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare
* region with the expected offset to preserve support for old DTs.
*/
if (of_device_is_compatible(node, "qcom,x1e80100-pdc")) {
- pdc_prev_base = ioremap(res.start - PDC_DRV_SIZE, IRQ_ENABLE_BANK_MAX);
- if (!pdc_prev_base) {
+ pdc->prev_base = ioremap(res.start - PDC_DRV_SIZE,
+ pdc->regs->irq_en_reg + IRQ_ENABLE_BANK_MAX);
+ if (!pdc->prev_base) {
pr_err("%pOF: unable to map previous PDC DRV region\n", node);
- return -ENXIO;
+ ret = -ENXIO;
+ goto fail;
}

pdc_x1e_quirk = true;
}

- pdc_base = ioremap(res.start, res_size);
- if (!pdc_base) {
- pr_err("%pOF: unable to map PDC registers\n", node);
- ret = -ENXIO;
- goto fail;
- }
-
- __pdc_enable_intr = (pdc_reg_read(PDC_VERSION_REG, 0) < PDC_VERSION_3_2) ?
- pdc_enable_intr_bank : pdc_enable_intr_cfg;
-
parent_domain = irq_find_host(parent);
if (!parent_domain) {
pr_err("%pOF: unable to find PDC's parent domain\n", node);
@@ -418,7 +524,7 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare

pdc_domain = irq_domain_create_hierarchy(parent_domain,
IRQ_DOMAIN_FLAG_QCOM_PDC_WAKEUP,
- PDC_MAX_GPIO_IRQS,
+ PDC_MAX_IRQS,
of_fwnode_handle(node),
&qcom_pdc_ops, NULL);
if (!pdc_domain) {
@@ -433,8 +539,9 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare

fail:
kfree(pdc_region);
- iounmap(pdc_base);
- iounmap(pdc_prev_base);
+ iounmap(pdc->base);
+ iounmap(pdc->prev_base);
+ kfree(pdc);
return ret;
}


--
2.43.0