There are two modes PDC irqchip supports pass through mode and secondary
controller mode.
All PDC irqchip supports pass through mode in which both Direct SPIs and
GPIO IRQs (as SPIs) are sent to GIC without latching at PDC.
Newer PDCs (v3.0 onwards) also support additional secondary controller mode
where PDC latches GPIO IRQs and sends to GIC as level type IRQ. Direct SPIs
still works same as pass through mode without latching at PDC even in
secondary controller mode.
All the SoCs so far default uses pass through mode with the exception of
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.
There is no way to read which current mode it is set to and make PDC work
in respective mode as the read access is not opened up for non secure
world. There is though write access opened up via SCM write API to set the
mode.
Configure PDC mode to pass through mode for all x1e based boards via SCM
write.
Co-developed-by: Sneh Mankad <sneh.mankad@oss.qualcomm.com>
Signed-off-by: Sneh Mankad <sneh.mankad@oss.qualcomm.com>
Signed-off-by: Maulik Shah <maulik.shah@oss.qualcomm.com>
---
drivers/irqchip/Kconfig | 1 +
drivers/irqchip/qcom-pdc.c | 119 +++++++++++++++++++++++++++++++++++++++++----
2 files changed, 111 insertions(+), 9 deletions(-)
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 83d333f8bf63d78827800e0de724f81e6aa2f1df..89caddf6e5c569a0e867cda1838c870b967fb13d 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -505,6 +505,7 @@ config GOLDFISH_PIC
config QCOM_PDC
tristate "QCOM PDC"
depends on ARCH_QCOM
+ depends on QCOM_AOSS_QMP
select IRQ_DOMAIN_HIERARCHY
help
Power Domain Controller driver to manage and configure wakeup
diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c
index 32b77fa93f730416edf120710bcdcdce33fa39a7..051700d672471c092e8cda4d7f5aa6d2032157f7 100644
--- a/drivers/irqchip/qcom-pdc.c
+++ b/drivers/irqchip/qcom-pdc.c
@@ -19,6 +19,8 @@
#include <linux/spinlock.h>
#include <linux/slab.h>
#include <linux/types.h>
+#include <linux/firmware/qcom/qcom_scm.h>
+#include <linux/soc/qcom/qcom_aoss.h>
#define PDC_MAX_GPIO_IRQS 256
#define PDC_DRV_OFFSET 0x10000
@@ -26,9 +28,11 @@
/* 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_IRQ_MASK_3_0 3
#define IRQ_i_CFG 0x110
/* Valid only on HW version >= 3.2 */
+#define IRQ_i_CFG_IRQ_MASK_3_2 4
#define IRQ_i_CFG_IRQ_ENABLE 3
#define IRQ_i_CFG_TYPE_MASK GENMASK(2, 0)
@@ -36,8 +40,11 @@
#define PDC_VERSION_REG 0x1000
/* Notable PDC versions */
+#define PDC_VERSION_3_0 0x30000
#define PDC_VERSION_3_2 0x30200
+#define PDC_PASS_THROUGH_MODE 0
+
struct pdc_pin_region {
u32 pin_base;
u32 parent_base;
@@ -97,6 +104,33 @@ static void pdc_x1e_irq_enable_write(u32 bank, u32 enable)
pdc_base_reg_write(base, IRQ_ENABLE_BANK, bank, enable);
}
+/*
+ * The new mask bit controls whether the interrupt is to be forwarded to the
+ * parent GIC in secondary controller mode. Writing the mask is do not care
+ * when the PDC is set to pass through mode.
+ *
+ * As linux only makes so far make use of pass through mode set all IRQs
+ * masked during probe.
+ */
+static void __pdc_mask_intr(int pin_out, bool mask)
+{
+ unsigned long irq_cfg;
+ int mask_bit;
+
+ /* Mask bit available from v3.0 */
+ if (pdc_version < PDC_VERSION_3_0)
+ return;
+
+ if (pdc_version < PDC_VERSION_3_2)
+ mask_bit = IRQ_i_CFG_IRQ_MASK_3_0;
+ else
+ mask_bit = IRQ_i_CFG_IRQ_MASK_3_2;
+
+ irq_cfg = pdc_reg_read(IRQ_i_CFG, pin_out);
+ __assign_bit(mask_bit, &irq_cfg, mask);
+ pdc_reg_write(IRQ_i_CFG, pin_out, irq_cfg);
+}
+
static void __pdc_enable_intr(int pin_out, bool on)
{
unsigned long enable;
@@ -312,7 +346,6 @@ static const struct irq_domain_ops qcom_pdc_ops = {
static int pdc_setup_pin_mapping(struct device_node *np)
{
int ret, n, i;
-
n = of_property_count_elems_of_size(np, "qcom,pdc-ranges", sizeof(u32));
if (n <= 0 || n % 3)
return -EINVAL;
@@ -341,8 +374,10 @@ static int pdc_setup_pin_mapping(struct device_node *np)
if (ret)
return ret;
- for (i = 0; i < pdc_region[n].cnt; i++)
+ for (i = 0; i < pdc_region[n].cnt; i++) {
__pdc_enable_intr(i + pdc_region[n].pin_base, 0);
+ __pdc_mask_intr(i + pdc_region[n].pin_base, true);
+ }
}
return 0;
@@ -352,10 +387,13 @@ static int pdc_setup_pin_mapping(struct device_node *np)
static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *parent)
{
+ static const char buf[64] = "{class: cx_mol, res: cx, val: mol}";
+ unsigned int domain_flag = IRQ_DOMAIN_FLAG_QCOM_PDC_WAKEUP;
struct irq_domain *parent_domain, *pdc_domain;
struct device_node *node = pdev->dev.of_node;
resource_size_t res_size;
struct resource res;
+ struct qmp *pdc_qmp;
int ret;
/* compat with old sm8150 DT which had very small region for PDC */
@@ -366,6 +404,13 @@ 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_base = ioremap(res.start, res_size);
+ if (!pdc_base) {
+ pr_err("%pOF: unable to map PDC registers\n", node);
+ ret = -ENXIO;
+ goto fail;
+ }
+
/*
* PDC has multiple DRV regions, each one provides the same set of
* registers for a particular client in the system. Due to a hardware
@@ -382,15 +427,71 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare
}
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;
+ /*
+ * There are two modes PDC irqchip can work in
+ * - pass through mode
+ * - secondary controller mode
+ *
+ * All PDC irqchip supports pass through mode in which both
+ * Direct SPIs and GPIO IRQs (as SPIs) are sent to GIC
+ * without latching at PDC.
+ *
+ * Newer PDCs (v3.0 onwards) also support additional
+ * secondary controller mode where PDC latches GPIO IRQs
+ * and sends to GIC as level type IRQ. Direct SPIs still
+ * works same as pass through mode without latching at PDC
+ * even in secondary controller mode.
+ *
+ * All the SoCs so far default uses pass through mode with
+ * the exception of x1e.
+ *
+ * x1e modes:
+ *
+ * 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.
+ *
+ * There is no way to read which current mode it is set to
+ * and make PDC work in respective mode as the read access
+ * is not opened up for non secure world. There is though
+ * write access opened up via SCM write API to set the mode.
+ *
+ * Configure PDC mode to pass through mode for all x1e based
+ * boards.
+ *
+ * For successful write:
+ * - Nothing more to be done
+ *
+ * For unsuccessful write:
+ * - Inform TLMM to monitor GPIO IRQs (same as MPM)
+ * - Prevent SoC low power mode (CxPC) as PDC is not
+ * monitoring GPIO IRQs which may be needed to wake
+ * the SoC from low power mode.
+ */
+ ret = of_address_to_resource(node, 2, &res);
+ if (ret) {
+ domain_flag = IRQ_DOMAIN_FLAG_QCOM_MPM_WAKEUP;
+ goto skip_scm_write;
+ }
+
+ ret = qcom_scm_io_writel(res.start, PDC_PASS_THROUGH_MODE);
+ if (ret) {
+ pdc_qmp = qmp_get(&pdev->dev);
+ if (IS_ERR(pdc_qmp)) {
+ ret = PTR_ERR(pdc_qmp);
+ goto fail;
+ } else {
+ ret = qmp_send(pdc_qmp, buf, sizeof(buf));
+ qmp_put(pdc_qmp);
+ if (ret)
+ goto fail;
+ }
+ domain_flag = IRQ_DOMAIN_FLAG_QCOM_MPM_WAKEUP;
+ }
}
+skip_scm_write:
pdc_version = pdc_reg_read(PDC_VERSION_REG, 0);
parent_domain = irq_find_host(parent);
@@ -407,7 +508,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,
+ domain_flag,
PDC_MAX_GPIO_IRQS,
of_fwnode_handle(node),
&qcom_pdc_ops, NULL);
--
2.34.1
On Thu, Mar 12, 2026 at 09:26:37PM +0530, Maulik Shah wrote:
> There are two modes PDC irqchip supports pass through mode and secondary
> controller mode.
>
> All PDC irqchip supports pass through mode in which both Direct SPIs and
> GPIO IRQs (as SPIs) are sent to GIC without latching at PDC.
>
> Newer PDCs (v3.0 onwards) also support additional secondary controller mode
> where PDC latches GPIO IRQs and sends to GIC as level type IRQ. Direct SPIs
> still works same as pass through mode without latching at PDC even in
> secondary controller mode.
>
> All the SoCs so far default uses pass through mode with the exception of
> 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.
>
> There is no way to read which current mode it is set to and make PDC work
> in respective mode as the read access is not opened up for non secure
> world. There is though write access opened up via SCM write API to set the
> mode.
>
> Configure PDC mode to pass through mode for all x1e based boards via SCM
> write.
>
You're failing to mention that the SCM interface was not present in
initially shipping Windows firmware, which would result in you breaking
those devices.
If you're certain that this change is available to all users, you can
argue that this is acceptable - but omitting this from the commit
message isn't.
Regards,
Bjorn
> Co-developed-by: Sneh Mankad <sneh.mankad@oss.qualcomm.com>
> Signed-off-by: Sneh Mankad <sneh.mankad@oss.qualcomm.com>
> Signed-off-by: Maulik Shah <maulik.shah@oss.qualcomm.com>
> ---
> drivers/irqchip/Kconfig | 1 +
> drivers/irqchip/qcom-pdc.c | 119 +++++++++++++++++++++++++++++++++++++++++----
> 2 files changed, 111 insertions(+), 9 deletions(-)
>
> diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> index 83d333f8bf63d78827800e0de724f81e6aa2f1df..89caddf6e5c569a0e867cda1838c870b967fb13d 100644
> --- a/drivers/irqchip/Kconfig
> +++ b/drivers/irqchip/Kconfig
> @@ -505,6 +505,7 @@ config GOLDFISH_PIC
> config QCOM_PDC
> tristate "QCOM PDC"
> depends on ARCH_QCOM
> + depends on QCOM_AOSS_QMP
> select IRQ_DOMAIN_HIERARCHY
> help
> Power Domain Controller driver to manage and configure wakeup
> diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c
> index 32b77fa93f730416edf120710bcdcdce33fa39a7..051700d672471c092e8cda4d7f5aa6d2032157f7 100644
> --- a/drivers/irqchip/qcom-pdc.c
> +++ b/drivers/irqchip/qcom-pdc.c
> @@ -19,6 +19,8 @@
> #include <linux/spinlock.h>
> #include <linux/slab.h>
> #include <linux/types.h>
> +#include <linux/firmware/qcom/qcom_scm.h>
> +#include <linux/soc/qcom/qcom_aoss.h>
>
> #define PDC_MAX_GPIO_IRQS 256
> #define PDC_DRV_OFFSET 0x10000
> @@ -26,9 +28,11 @@
> /* 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_IRQ_MASK_3_0 3
> #define IRQ_i_CFG 0x110
>
> /* Valid only on HW version >= 3.2 */
> +#define IRQ_i_CFG_IRQ_MASK_3_2 4
> #define IRQ_i_CFG_IRQ_ENABLE 3
>
> #define IRQ_i_CFG_TYPE_MASK GENMASK(2, 0)
> @@ -36,8 +40,11 @@
> #define PDC_VERSION_REG 0x1000
>
> /* Notable PDC versions */
> +#define PDC_VERSION_3_0 0x30000
> #define PDC_VERSION_3_2 0x30200
>
> +#define PDC_PASS_THROUGH_MODE 0
> +
> struct pdc_pin_region {
> u32 pin_base;
> u32 parent_base;
> @@ -97,6 +104,33 @@ static void pdc_x1e_irq_enable_write(u32 bank, u32 enable)
> pdc_base_reg_write(base, IRQ_ENABLE_BANK, bank, enable);
> }
>
> +/*
> + * The new mask bit controls whether the interrupt is to be forwarded to the
> + * parent GIC in secondary controller mode. Writing the mask is do not care
> + * when the PDC is set to pass through mode.
> + *
> + * As linux only makes so far make use of pass through mode set all IRQs
> + * masked during probe.
> + */
> +static void __pdc_mask_intr(int pin_out, bool mask)
> +{
> + unsigned long irq_cfg;
> + int mask_bit;
> +
> + /* Mask bit available from v3.0 */
> + if (pdc_version < PDC_VERSION_3_0)
> + return;
> +
> + if (pdc_version < PDC_VERSION_3_2)
> + mask_bit = IRQ_i_CFG_IRQ_MASK_3_0;
> + else
> + mask_bit = IRQ_i_CFG_IRQ_MASK_3_2;
> +
> + irq_cfg = pdc_reg_read(IRQ_i_CFG, pin_out);
> + __assign_bit(mask_bit, &irq_cfg, mask);
> + pdc_reg_write(IRQ_i_CFG, pin_out, irq_cfg);
> +}
> +
> static void __pdc_enable_intr(int pin_out, bool on)
> {
> unsigned long enable;
> @@ -312,7 +346,6 @@ static const struct irq_domain_ops qcom_pdc_ops = {
> static int pdc_setup_pin_mapping(struct device_node *np)
> {
> int ret, n, i;
> -
> n = of_property_count_elems_of_size(np, "qcom,pdc-ranges", sizeof(u32));
> if (n <= 0 || n % 3)
> return -EINVAL;
> @@ -341,8 +374,10 @@ static int pdc_setup_pin_mapping(struct device_node *np)
> if (ret)
> return ret;
>
> - for (i = 0; i < pdc_region[n].cnt; i++)
> + for (i = 0; i < pdc_region[n].cnt; i++) {
> __pdc_enable_intr(i + pdc_region[n].pin_base, 0);
> + __pdc_mask_intr(i + pdc_region[n].pin_base, true);
> + }
> }
>
> return 0;
> @@ -352,10 +387,13 @@ static int pdc_setup_pin_mapping(struct device_node *np)
>
> static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *parent)
> {
> + static const char buf[64] = "{class: cx_mol, res: cx, val: mol}";
> + unsigned int domain_flag = IRQ_DOMAIN_FLAG_QCOM_PDC_WAKEUP;
> struct irq_domain *parent_domain, *pdc_domain;
> struct device_node *node = pdev->dev.of_node;
> resource_size_t res_size;
> struct resource res;
> + struct qmp *pdc_qmp;
> int ret;
>
> /* compat with old sm8150 DT which had very small region for PDC */
> @@ -366,6 +404,13 @@ 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_base = ioremap(res.start, res_size);
> + if (!pdc_base) {
> + pr_err("%pOF: unable to map PDC registers\n", node);
> + ret = -ENXIO;
> + goto fail;
> + }
> +
> /*
> * PDC has multiple DRV regions, each one provides the same set of
> * registers for a particular client in the system. Due to a hardware
> @@ -382,15 +427,71 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare
> }
>
> 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;
> + /*
> + * There are two modes PDC irqchip can work in
> + * - pass through mode
> + * - secondary controller mode
> + *
> + * All PDC irqchip supports pass through mode in which both
> + * Direct SPIs and GPIO IRQs (as SPIs) are sent to GIC
> + * without latching at PDC.
> + *
> + * Newer PDCs (v3.0 onwards) also support additional
> + * secondary controller mode where PDC latches GPIO IRQs
> + * and sends to GIC as level type IRQ. Direct SPIs still
> + * works same as pass through mode without latching at PDC
> + * even in secondary controller mode.
> + *
> + * All the SoCs so far default uses pass through mode with
> + * the exception of x1e.
> + *
> + * x1e modes:
> + *
> + * 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.
> + *
> + * There is no way to read which current mode it is set to
> + * and make PDC work in respective mode as the read access
> + * is not opened up for non secure world. There is though
> + * write access opened up via SCM write API to set the mode.
> + *
> + * Configure PDC mode to pass through mode for all x1e based
> + * boards.
> + *
> + * For successful write:
> + * - Nothing more to be done
> + *
> + * For unsuccessful write:
> + * - Inform TLMM to monitor GPIO IRQs (same as MPM)
> + * - Prevent SoC low power mode (CxPC) as PDC is not
> + * monitoring GPIO IRQs which may be needed to wake
> + * the SoC from low power mode.
> + */
> + ret = of_address_to_resource(node, 2, &res);
> + if (ret) {
> + domain_flag = IRQ_DOMAIN_FLAG_QCOM_MPM_WAKEUP;
> + goto skip_scm_write;
> + }
> +
> + ret = qcom_scm_io_writel(res.start, PDC_PASS_THROUGH_MODE);
> + if (ret) {
> + pdc_qmp = qmp_get(&pdev->dev);
> + if (IS_ERR(pdc_qmp)) {
> + ret = PTR_ERR(pdc_qmp);
> + goto fail;
> + } else {
> + ret = qmp_send(pdc_qmp, buf, sizeof(buf));
> + qmp_put(pdc_qmp);
> + if (ret)
> + goto fail;
> + }
> + domain_flag = IRQ_DOMAIN_FLAG_QCOM_MPM_WAKEUP;
> + }
> }
>
> +skip_scm_write:
> pdc_version = pdc_reg_read(PDC_VERSION_REG, 0);
>
> parent_domain = irq_find_host(parent);
> @@ -407,7 +508,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,
> + domain_flag,
> PDC_MAX_GPIO_IRQS,
> of_fwnode_handle(node),
> &qcom_pdc_ops, NULL);
>
> --
> 2.34.1
>
On Thu, Mar 12, 2026 at 09:26:37PM +0530, Maulik Shah wrote:
> There are two modes PDC irqchip supports pass through mode and secondary
> controller mode.
Can't parse this, excuse me.
>
> All PDC irqchip supports pass through mode in which both Direct SPIs and
> GPIO IRQs (as SPIs) are sent to GIC without latching at PDC.
>
> Newer PDCs (v3.0 onwards) also support additional secondary controller mode
It would help to mention the platforms, not everybody has the core docs.
> where PDC latches GPIO IRQs and sends to GIC as level type IRQ. Direct SPIs
> still works same as pass through mode without latching at PDC even in
> secondary controller mode.
>
> All the SoCs so far default uses pass through mode with the exception of
Is it something that must be configured by the bootloaders?
> 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.
>
> There is no way to read which current mode it is set to and make PDC work
> in respective mode as the read access is not opened up for non secure
> world. There is though write access opened up via SCM write API to set the
> mode.
What are going to loose? The ability to latch the wakeup sources on the
CRD?
> Configure PDC mode to pass through mode for all x1e based boards via SCM
> write.
Would it make sense to always use the secondary mode instead?
>
> Co-developed-by: Sneh Mankad <sneh.mankad@oss.qualcomm.com>
> Signed-off-by: Sneh Mankad <sneh.mankad@oss.qualcomm.com>
> Signed-off-by: Maulik Shah <maulik.shah@oss.qualcomm.com>
> ---
> drivers/irqchip/Kconfig | 1 +
> drivers/irqchip/qcom-pdc.c | 119 +++++++++++++++++++++++++++++++++++++++++----
> 2 files changed, 111 insertions(+), 9 deletions(-)
>
> diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> index 83d333f8bf63d78827800e0de724f81e6aa2f1df..89caddf6e5c569a0e867cda1838c870b967fb13d 100644
> --- a/drivers/irqchip/Kconfig
> +++ b/drivers/irqchip/Kconfig
> @@ -505,6 +505,7 @@ config GOLDFISH_PIC
> config QCOM_PDC
> tristate "QCOM PDC"
> depends on ARCH_QCOM
> + depends on QCOM_AOSS_QMP
> select IRQ_DOMAIN_HIERARCHY
> help
> Power Domain Controller driver to manage and configure wakeup
> diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c
> index 32b77fa93f730416edf120710bcdcdce33fa39a7..051700d672471c092e8cda4d7f5aa6d2032157f7 100644
> --- a/drivers/irqchip/qcom-pdc.c
> +++ b/drivers/irqchip/qcom-pdc.c
> @@ -19,6 +19,8 @@
> #include <linux/spinlock.h>
> #include <linux/slab.h>
> #include <linux/types.h>
> +#include <linux/firmware/qcom/qcom_scm.h>
> +#include <linux/soc/qcom/qcom_aoss.h>
>
> #define PDC_MAX_GPIO_IRQS 256
> #define PDC_DRV_OFFSET 0x10000
> @@ -26,9 +28,11 @@
> /* 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_IRQ_MASK_3_0 3
> #define IRQ_i_CFG 0x110
>
> /* Valid only on HW version >= 3.2 */
> +#define IRQ_i_CFG_IRQ_MASK_3_2 4
> #define IRQ_i_CFG_IRQ_ENABLE 3
>
> #define IRQ_i_CFG_TYPE_MASK GENMASK(2, 0)
> @@ -36,8 +40,11 @@
> #define PDC_VERSION_REG 0x1000
>
> /* Notable PDC versions */
> +#define PDC_VERSION_3_0 0x30000
> #define PDC_VERSION_3_2 0x30200
>
> +#define PDC_PASS_THROUGH_MODE 0
> +
> struct pdc_pin_region {
> u32 pin_base;
> u32 parent_base;
> @@ -97,6 +104,33 @@ static void pdc_x1e_irq_enable_write(u32 bank, u32 enable)
> pdc_base_reg_write(base, IRQ_ENABLE_BANK, bank, enable);
> }
>
> +/*
> + * The new mask bit controls whether the interrupt is to be forwarded to the
> + * parent GIC in secondary controller mode. Writing the mask is do not care
> + * when the PDC is set to pass through mode.
> + *
> + * As linux only makes so far make use of pass through mode set all IRQs
> + * masked during probe.
> + */
> +static void __pdc_mask_intr(int pin_out, bool mask)
> +{
> + unsigned long irq_cfg;
> + int mask_bit;
> +
> + /* Mask bit available from v3.0 */
> + if (pdc_version < PDC_VERSION_3_0)
> + return;
> +
> + if (pdc_version < PDC_VERSION_3_2)
> + mask_bit = IRQ_i_CFG_IRQ_MASK_3_0;
> + else
> + mask_bit = IRQ_i_CFG_IRQ_MASK_3_2;
> +
> + irq_cfg = pdc_reg_read(IRQ_i_CFG, pin_out);
> + __assign_bit(mask_bit, &irq_cfg, mask);
> + pdc_reg_write(IRQ_i_CFG, pin_out, irq_cfg);
> +}
> +
> static void __pdc_enable_intr(int pin_out, bool on)
> {
> unsigned long enable;
> @@ -312,7 +346,6 @@ static const struct irq_domain_ops qcom_pdc_ops = {
> static int pdc_setup_pin_mapping(struct device_node *np)
> {
> int ret, n, i;
> -
> n = of_property_count_elems_of_size(np, "qcom,pdc-ranges", sizeof(u32));
> if (n <= 0 || n % 3)
> return -EINVAL;
> @@ -341,8 +374,10 @@ static int pdc_setup_pin_mapping(struct device_node *np)
> if (ret)
> return ret;
>
> - for (i = 0; i < pdc_region[n].cnt; i++)
> + for (i = 0; i < pdc_region[n].cnt; i++) {
> __pdc_enable_intr(i + pdc_region[n].pin_base, 0);
> + __pdc_mask_intr(i + pdc_region[n].pin_base, true);
> + }
> }
>
> return 0;
> @@ -352,10 +387,13 @@ static int pdc_setup_pin_mapping(struct device_node *np)
>
> static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *parent)
> {
> + static const char buf[64] = "{class: cx_mol, res: cx, val: mol}";
> + unsigned int domain_flag = IRQ_DOMAIN_FLAG_QCOM_PDC_WAKEUP;
> struct irq_domain *parent_domain, *pdc_domain;
> struct device_node *node = pdev->dev.of_node;
> resource_size_t res_size;
> struct resource res;
> + struct qmp *pdc_qmp;
> int ret;
>
> /* compat with old sm8150 DT which had very small region for PDC */
> @@ -366,6 +404,13 @@ 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_base = ioremap(res.start, res_size);
> + if (!pdc_base) {
> + pr_err("%pOF: unable to map PDC registers\n", node);
> + ret = -ENXIO;
> + goto fail;
> + }
> +
> /*
> * PDC has multiple DRV regions, each one provides the same set of
> * registers for a particular client in the system. Due to a hardware
> @@ -382,15 +427,71 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare
> }
>
> 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;
> + /*
> + * There are two modes PDC irqchip can work in
> + * - pass through mode
> + * - secondary controller mode
> + *
> + * All PDC irqchip supports pass through mode in which both
> + * Direct SPIs and GPIO IRQs (as SPIs) are sent to GIC
> + * without latching at PDC.
> + *
> + * Newer PDCs (v3.0 onwards) also support additional
> + * secondary controller mode where PDC latches GPIO IRQs
> + * and sends to GIC as level type IRQ. Direct SPIs still
> + * works same as pass through mode without latching at PDC
> + * even in secondary controller mode.
I'd say, there is no need to duplicate the commit message.
> + *
> + * All the SoCs so far default uses pass through mode with
> + * the exception of x1e.
> + *
> + * x1e modes:
> + *
> + * 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.
> + *
> + * There is no way to read which current mode it is set to
> + * and make PDC work in respective mode as the read access
> + * is not opened up for non secure world. There is though
> + * write access opened up via SCM write API to set the mode.
> + *
> + * Configure PDC mode to pass through mode for all x1e based
> + * boards.
> + *
> + * For successful write:
> + * - Nothing more to be done
> + *
> + * For unsuccessful write:
Why would it fail?
> + * - Inform TLMM to monitor GPIO IRQs (same as MPM)
> + * - Prevent SoC low power mode (CxPC) as PDC is not
> + * monitoring GPIO IRQs which may be needed to wake
> + * the SoC from low power mode.
This doesn't quite match the description of "latches the GPIO IRQs".
> + */
> + ret = of_address_to_resource(node, 2, &res);
> + if (ret) {
> + domain_flag = IRQ_DOMAIN_FLAG_QCOM_MPM_WAKEUP;
> + goto skip_scm_write;
> + }
> +
> + ret = qcom_scm_io_writel(res.start, PDC_PASS_THROUGH_MODE);
> + if (ret) {
> + pdc_qmp = qmp_get(&pdev->dev);
> + if (IS_ERR(pdc_qmp)) {
> + ret = PTR_ERR(pdc_qmp);
> + goto fail;
> + } else {
> + ret = qmp_send(pdc_qmp, buf, sizeof(buf));
> + qmp_put(pdc_qmp);
> + if (ret)
> + goto fail;
> + }
> + domain_flag = IRQ_DOMAIN_FLAG_QCOM_MPM_WAKEUP;
> + }
> }
>
> +skip_scm_write:
> pdc_version = pdc_reg_read(PDC_VERSION_REG, 0);
>
> parent_domain = irq_find_host(parent);
> @@ -407,7 +508,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,
> + domain_flag,
> PDC_MAX_GPIO_IRQS,
> of_fwnode_handle(node),
> &qcom_pdc_ops, NULL);
>
> --
> 2.34.1
>
--
With best wishes
Dmitry
On 3/13/2026 7:52 AM, Dmitry Baryshkov wrote:
> On Thu, Mar 12, 2026 at 09:26:37PM +0530, Maulik Shah wrote:
>> There are two modes PDC irqchip supports pass through mode and secondary
>> controller mode.
>
> Can't parse this, excuse me.
Ok, I can drop this in v2.
>
>>
>> All PDC irqchip supports pass through mode in which both Direct SPIs and
>> GPIO IRQs (as SPIs) are sent to GIC without latching at PDC.
>>
>> Newer PDCs (v3.0 onwards) also support additional secondary controller mode
>
> It would help to mention the platforms, not everybody has the core docs.
Sure, i can update the platforms which are v3.0 or higher.
>
>> where PDC latches GPIO IRQs and sends to GIC as level type IRQ. Direct SPIs
>> still works same as pass through mode without latching at PDC even in
>> secondary controller mode.
>>
>> All the SoCs so far default uses pass through mode with the exception of
>
> Is it something that must be configured by the bootloaders?
yes, currently changing the the mode can be done from secure world either at boot
or after boot via scm write.
>
>> 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.
>>
>> There is no way to read which current mode it is set to and make PDC work
>> in respective mode as the read access is not opened up for non secure
>> world. There is though write access opened up via SCM write API to set the
>> mode.
>
> What are going to loose? The ability to latch the wakeup sources on the
> CRD?
CXPC (SoC level low power mode) would be lost if the device can not wake up from GPIO wakeup sources.
>
>> Configure PDC mode to pass through mode for all x1e based boards via SCM
>> write.
>
> Would it make sense to always use the secondary mode instead?
No, it would not make sense to support the secondary mode in Linux.
>
..
..
>>
>> - pdc_base = ioremap(res.start, res_size);
>> - if (!pdc_base) {
>> - pr_err("%pOF: unable to map PDC registers\n", node);
>> - ret = -ENXIO;
>> - goto fail;
>> + /*
>> + * There are two modes PDC irqchip can work in
>> + * - pass through mode
>> + * - secondary controller mode
>> + *
>> + * All PDC irqchip supports pass through mode in which both
>> + * Direct SPIs and GPIO IRQs (as SPIs) are sent to GIC
>> + * without latching at PDC.
>> + *
>> + * Newer PDCs (v3.0 onwards) also support additional
>> + * secondary controller mode where PDC latches GPIO IRQs
>> + * and sends to GIC as level type IRQ. Direct SPIs still
>> + * works same as pass through mode without latching at PDC
>> + * even in secondary controller mode.
>
> I'd say, there is no need to duplicate the commit message.
Sure, i can remove from comments.
>
>> + *
>> + * All the SoCs so far default uses pass through mode with
>> + * the exception of x1e.
>> + *
>> + * x1e modes:
>> + *
>> + * 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.
>> + *
>> + * There is no way to read which current mode it is set to
>> + * and make PDC work in respective mode as the read access
>> + * is not opened up for non secure world. There is though
>> + * write access opened up via SCM write API to set the mode.
>> + *
>> + * Configure PDC mode to pass through mode for all x1e based
>> + * boards.
>> + *
>> + * For successful write:
>> + * - Nothing more to be done
>> + *
>> + * For unsuccessful write:
>
> Why would it fail?
It can fail if the write is denied by firmware.
As i understand the older firmware had neither read/write as such firmware
was meant to be used for non-linux (windows only and not for the dual boot).
>
>> + * - Inform TLMM to monitor GPIO IRQs (same as MPM)
>> + * - Prevent SoC low power mode (CxPC) as PDC is not
>> + * monitoring GPIO IRQs which may be needed to wake
>> + * the SoC from low power mode.
>
> This doesn't quite match the description of "latches the GPIO IRQs".
It does, PDC would continue to still latch the GPIO IRQs (as the mode change failed)
but PDC won't forward them to parent GIC as they are masked at PDC with __pdc_mask_intr().
In summary,
Below is what x1e users get today if they boot up Linux:
- All the GPIO interrupts works fine for them as they don't get forwarded to PDC
due to commit 602cb14e310a ("pinctrl: qcom: x1e80100: Bypass PDC wakeup parent for now")
- SS3 idle state (CPU level deepest low power mode) not added in device tree due to
above commit.
- This prevents CXPC (SoC level low power mode) as the CPU subsystem cannot hit deepest low power mode.
Below is what would x1e users would get from this series,
- GPIO interrupts continue to work fine after reverting commit 602cb14e310a
("pinctrl: qcom: x1e80100: Bypass PDC wakeup parent for now"), PATCH 5/5 of this series.
- SS3 idle state (CPU level deepest idle state) is added, PATCH 4/4 of this series.
Adding the SS3 idle states opens up the path for the SoC to achieve CXPC (SoC level low power mode)
(This again depends on drivers removing all the global resources vote)
While all global resources votes can get removed, if the PDC still could not wake the SoC from GPIO
interrupts, the CX is kept at MoL (minimum operating level) and TLMM (which is on Cx rail) would then
wake up the CPUs from SS3 CPUidle / suspend (s2idle) state with GPIO interrupts.
Thanks,
Maulik
On 3/13/26 7:40 AM, Maulik Shah (mkshah) wrote:
>
>
> On 3/13/2026 7:52 AM, Dmitry Baryshkov wrote:
>> On Thu, Mar 12, 2026 at 09:26:37PM +0530, Maulik Shah wrote:
[...]
>>> All the SoCs so far default uses pass through mode with the exception of
>>
>> Is it something that must be configured by the bootloaders?
>
> yes, currently changing the the mode can be done from secure world either at boot
> or after boot via scm write.
..which won't work on almost any X1E devices, except CRD and IOT..
>>> 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.
>>>
>>> There is no way to read which current mode it is set to and make PDC work
>>> in respective mode as the read access is not opened up for non secure
>>> world. There is though write access opened up via SCM write API to set the
>>> mode.
>>
>> What are going to loose? The ability to latch the wakeup sources on the
>> CRD?
>
> CXPC (SoC level low power mode) would be lost if the device can not wake up from GPIO wakeup sources.
To the best of my understanding, that's only because your approach chooses
to ignore supporting the secondary controller mode and force-reconfigure,
since GPIO wakeup functionality is otherwise available regardless of the
mode.
>>> Configure PDC mode to pass through mode for all x1e based boards via SCM
>>> write.
>>
>> Would it make sense to always use the secondary mode instead?
>
> No, it would not make sense to support the secondary mode in Linux.
Why?
[...]
>>> + * - Inform TLMM to monitor GPIO IRQs (same as MPM)
>>> + * - Prevent SoC low power mode (CxPC) as PDC is not
>>> + * monitoring GPIO IRQs which may be needed to wake
>>> + * the SoC from low power mode.
>>
>> This doesn't quite match the description of "latches the GPIO IRQs".
>
> It does, PDC would continue to still latch the GPIO IRQs (as the mode change failed)
> but PDC won't forward them to parent GIC as they are masked at PDC with __pdc_mask_intr().
Can you not refrain from masking them then, and clear them upon reception,
with a write to IRQ_i_CFG[IRQ_STATUS]?
The HPG states that this mechanism is only engaged for GPIO IRQs and that
the forwarded interrupt will be of LEVEL_HIGH type (which is what TLMM
accepts anyway)
FWIW, some related work:
c7984dc0a2b9 ("pinctrl: qcom: Add test case for TLMM interrupt handling")
Konrad
© 2016 - 2026 Red Hat, Inc.