Re: [PATCH 3/5] irqchip/qcom-pdc: Configure PDC to pass through mode

From: Dmitry Baryshkov

Date: Thu Mar 12 2026 - 22:25:17 EST


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@xxxxxxxxxxxxxxxx>
> Signed-off-by: Sneh Mankad <sneh.mankad@xxxxxxxxxxxxxxxx>
> Signed-off-by: Maulik Shah <maulik.shah@xxxxxxxxxxxxxxxx>
> ---
> 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