[PATCH 7/9] irqchip/google-cpm: Add CPM IRQ driver
From: Brian Norris
Date: Mon Oct 26 2026 - 17:48:04 EST
The Central Power Manager (CPM) on Google Tensor SoCs can signal
events/interrupts over its mailbox communication protocol. Today, the
downstream driver for these chips only supports interrupt notification,
although the CPM firmware may also have mask/unmask support (not
currently used) that could be added later if needed.
In practice, this driver is used for RTC alarms and for events driven by
the Always-on Compute (AoC) subsystem.
Signed-off-by: Brian Norris <briannorris@xxxxxxxxxxxx>
---
drivers/irqchip/Kconfig | 10 +++
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-google-cpm.c | 123 +++++++++++++++++++++++++++++++
3 files changed, 134 insertions(+)
create mode 100644 drivers/irqchip/irq-google-cpm.c
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index ca915fca0ef5..47f9739b1d74 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -556,6 +556,16 @@ config CSKY_APB_INTC
by C-SKY single core SOC system. It uses mmio map apb-bus to visit
the controller's register.
+config GOOGLE_CPM_IRQ
+ tristate "Google Tensor CPM interrupt controller"
+ depends on GOOGLE_CPM_MBA
+ depends on ARCH_GOOGLE || COMPILE_TEST
+ select IRQ_DOMAIN
+ help
+ Support for the Central Power Manager (CPM) interrupt controller
+ found on Google Tensor SoCs. This controller routes interrupts from
+ the CPM to the Application Processor.
+
config IMX_IRQSTEER
bool "i.MX IRQSTEER support"
depends on ARCH_MXC || ARCH_S32 || COMPILE_TEST
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 16a5fd126c9a..ca10888687fd 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -101,6 +101,7 @@ obj-$(CONFIG_QCOM_PDC) += qcom-pdc.o
obj-$(CONFIG_QCOM_MPM) += irq-qcom-mpm.o
obj-$(CONFIG_CSKY_MPINTC) += irq-csky-mpintc.o
obj-$(CONFIG_CSKY_APB_INTC) += irq-csky-apb-intc.o
+obj-$(CONFIG_GOOGLE_CPM_IRQ) += irq-google-cpm.o
obj-$(CONFIG_RISCV_INTC) += irq-riscv-intc.o
obj-$(CONFIG_RISCV_APLIC) += irq-riscv-aplic-main.o irq-riscv-aplic-direct.o
obj-$(CONFIG_RISCV_APLIC_MSI) += irq-riscv-aplic-msi.o
diff --git a/drivers/irqchip/irq-google-cpm.c b/drivers/irqchip/irq-google-cpm.c
new file mode 100644
index 000000000000..a235cc1cfe7c
--- /dev/null
+++ b/drivers/irqchip/irq-google-cpm.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Google CPM IRQ chip driver
+ *
+ * Copyright 2023-2026 Google LLC
+ */
+#include <linux/auxiliary_bus.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <soc/google/goog_cpm_service_ids.h>
+#include <soc/google/google-cpm-mba.h>
+
+#define CPM_IRQ_COUNT 512
+
+struct cpm_irq_info {
+ struct device *dev;
+ struct irq_domain *domain;
+};
+
+static void cpm_irq_process_irq_status(struct cpm_irq_info *info, u32 irq_number)
+{
+ int ret;
+
+ ret = generic_handle_domain_irq_safe(info->domain, irq_number);
+ if (ret)
+ dev_err(info->dev, "Error handling IRQ %u, error code %d\n", irq_number, ret);
+}
+
+static void cpm_irq_rx_callback(u32 context, const struct cpm_payload *msg, void *priv)
+{
+ struct cpm_irq_info *info = priv;
+ u32 irq_number = msg->payload[0];
+
+ cpm_irq_process_irq_status(info, irq_number);
+}
+
+static void cpm_irq_domain_remove_action(void *data)
+{
+ struct irq_domain *domain = data;
+ unsigned int hwirq;
+
+ for (hwirq = 0; hwirq < CPM_IRQ_COUNT; hwirq++)
+ irq_dispose_mapping(irq_find_mapping(domain, hwirq));
+
+ irq_domain_remove(domain);
+}
+
+static int cpm_irq_domain_map(struct irq_domain *d, unsigned int virq,
+ irq_hw_number_t hwirq)
+{
+ struct cpm_irq_info *info = d->host_data;
+
+ if (hwirq >= CPM_IRQ_COUNT)
+ return -EINVAL;
+
+ irq_set_chip_data(virq, info);
+ irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq);
+
+ dev_dbg(info->dev, "Mapped hwirq %lu to virq %u\n", hwirq, virq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops cpm_irq_domain_ops = {
+ .map = cpm_irq_domain_map,
+ .xlate = irq_domain_xlate_onecell,
+};
+
+static int cpm_irq_probe(struct auxiliary_device *adev, const struct auxiliary_device_id *id)
+{
+ struct device *dev = &adev->dev;
+ struct cpm_client *client;
+ struct cpm_irq_info *info;
+ int ret;
+
+ info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->dev = dev;
+ auxiliary_set_drvdata(adev, info);
+
+ info->domain = irq_domain_create_tree(dev_fwnode(dev), &cpm_irq_domain_ops, info);
+ if (!info->domain) {
+ dev_err(dev, "Unable to add irq domain\n");
+ return -ENODEV;
+ }
+
+ ret = devm_add_action_or_reset(dev, cpm_irq_domain_remove_action, info->domain);
+ if (ret) {
+ dev_err(dev, "Failed to add irq domain removal action\n");
+ return ret;
+ }
+
+ client = devm_cpm_request_client(dev, CPM_AP_NS_IRQ_SERVICE,
+ cpm_irq_rx_callback, info);
+ if (IS_ERR(client))
+ return dev_err_probe(dev, PTR_ERR(client),
+ "Failed to request cpm client\n");
+
+ return 0;
+}
+
+static const struct auxiliary_device_id cpm_irq_id_table[] = {
+ { .name = "google_cpm_mba.irq" },
+ { },
+};
+MODULE_DEVICE_TABLE(auxiliary, cpm_irq_id_table);
+
+static struct auxiliary_driver cpm_irq_driver = {
+ .probe = cpm_irq_probe,
+ .name = "cpm-irq-chip",
+ .id_table = cpm_irq_id_table,
+};
+
+module_auxiliary_driver(cpm_irq_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Google CPM IRQ chip driver");
+MODULE_AUTHOR("Jim Wylder <jwylder@xxxxxxxxxx>");
--
2.56.0.307.g5fe7bd680c-goog