]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
gpio: Add Intel Nova Lake ACPI GPIO events driver
authorAlan Borzeszkowski <alan.borzeszkowski@linux.intel.com>
Wed, 1 Apr 2026 17:45:26 +0000 (19:45 +0200)
committerBartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Tue, 7 Apr 2026 10:32:21 +0000 (12:32 +0200)
This driver provides support for new way of handling platform events,
through the use of GPIO-signaled ACPI events. This mechanism is used on
Intel client platforms released in 2026 and later, starting with Intel
Nova Lake.

Signed-off-by: Alan Borzeszkowski <alan.borzeszkowski@linux.intel.com>
Reviewed-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Link: https://patch.msgid.link/20260401174526.60881-1-alan.borzeszkowski@linux.intel.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
MAINTAINERS
drivers/gpio/Kconfig
drivers/gpio/Makefile
drivers/gpio/gpio-novalake-events.c [new file with mode: 0644]

index 24b3f8d2a64c94ff0fef469506949e0c36e9c056..dbe2cad0c947c70a4b0935b39275f74849cfa25e 100644 (file)
@@ -12851,6 +12851,13 @@ F:     drivers/gpio/gpio-sodaville.c
 F:     drivers/gpio/gpio-tangier.c
 F:     drivers/gpio/gpio-tangier.h
 
+INTEL GPIO GPE DRIVER
+M:     Alan Borzeszkowski <alan.borzeszkowski@linux.intel.com>
+R:     Mika Westerberg <westeri@kernel.org>
+L:     linux-gpio@vger.kernel.org
+S:     Supported
+F:     drivers/gpio/gpio-novalake-events.c
+
 INTEL GVT-g DRIVERS (Intel GPU Virtualization)
 R:     Zhenyu Wang <zhenyuw.linux@gmail.com>
 R:     Zhi Wang <zhi.wang.linux@gmail.com>
index 4c3f6ec336c16129301613aadc8b22587b217005..5c62a00633510ca4bb3c514eda0c515d2cb33140 100644 (file)
@@ -1057,6 +1057,32 @@ config GPIO_SCH
          The Intel Quark X1000 SoC has 2 GPIOs powered by the core
          power well and 6 from the suspend power well.
 
+config GPIO_NOVALAKE
+       tristate "Intel Nova Lake GPIO-signaled ACPI events support"
+       depends on (X86 || COMPILE_TEST) && ACPI
+       select GPIOLIB_IRQCHIP
+       help
+         Select this to enable GPIO-signaled ACPI events support on platforms
+         with the following SoCs:
+
+         - Intel Nova Lake
+
+         This driver adds support for new mode of handling platform events,
+         through the use of GPIO-signaled ACPI events. Main purpose is to
+         handle platform IRQs that originate in PCH components, for example
+         interrupt triggered by Power Management Event (PME).
+
+         This driver, at this time, is not required to handle platform events.
+         Listed platform(s) will stay in legacy mode, handling ACPI events as
+         in previous generations. However, future platforms will eventually
+         switch to new handling mode, requiring this driver to run events
+         properly.
+
+         Driver supports up to 128 GPIO pins per GPE block.
+
+         To compile this driver as a module, choose M here: the module will
+         be called gpio-novalake-events.
+
 config GPIO_SCH311X
        tristate "SMSC SCH311x SuperI/O GPIO"
        help
index 20d4a57afdaa6db0d01cd7e107a2e22004641ecb..13ff2a129354889414ff0b2323644ba0ec9f1220 100644 (file)
@@ -135,6 +135,7 @@ obj-$(CONFIG_GPIO_MXC)                      += gpio-mxc.o
 obj-$(CONFIG_GPIO_MXS)                 += gpio-mxs.o
 obj-$(CONFIG_GPIO_NCT6694)             += gpio-nct6694.o
 obj-$(CONFIG_GPIO_NOMADIK)             += gpio-nomadik.o
+obj-$(CONFIG_GPIO_NOVALAKE)            += gpio-novalake-events.o
 obj-$(CONFIG_GPIO_NPCM_SGPIO)          += gpio-npcm-sgpio.o
 obj-$(CONFIG_GPIO_OCTEON)              += gpio-octeon.o
 obj-$(CONFIG_GPIO_OMAP)                        += gpio-omap.o
diff --git a/drivers/gpio/gpio-novalake-events.c b/drivers/gpio/gpio-novalake-events.c
new file mode 100644 (file)
index 0000000..b3bf003
--- /dev/null
@@ -0,0 +1,323 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Nova Lake GPIO-signaled ACPI events driver
+ *
+ * Copyright (c) 2026, Intel Corporation.
+ *
+ * Author: Alan Borzeszkowski <alan.borzeszkowski@linux.intel.com>
+ *
+ * Intel client platforms released in 2026 and later (starting with Intel Nova
+ * Lake) support two modes of handling ACPI General Purpose Events (GPE):
+ * exposed GPIO interrupt mode and legacy mode.
+ *
+ * By default, the platform uses legacy mode, handling GPEs as usual. If this
+ * driver is installed, it signals to the platform (on every boot) that exposed
+ * GPIO interrupt mode is supported. The platform then switches to exposed
+ * mode, which takes effect on next boot. From the user perspective, this
+ * change is transparent.
+ *
+ * However, if driver is uninstalled while in exposed interrupt mode, GPEs will
+ * _not_ be handled until platform falls back to legacy mode. This means that
+ * USB keyboard, mouse might not function properly for the fallback duration.
+ * Fallback requires two reboots to take effect: on first reboot, platform no
+ * longer receives signal from this driver and switches to legacy mode, which
+ * takes effect on second boot.
+ *
+ * Example ACPI event: Power Management Event coming from motherboard PCH,
+ * waking system from sleep following USB mouse hotplug.
+ *
+ * This driver supports up to 128 GPIO pins in each GPE block, per ACPI
+ * specification v6.6 section 5.6.4.
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/gfp_types.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/uuid.h>
+
+#include <linux/gpio/driver.h>
+
+/*
+ * GPE block has two registers, each register takes half the block size.
+ * Convert size to bits to get total GPIO pin count.
+ */
+#define GPE_BLK_REG_SIZE(block_size)   ((block_size) / 2)
+#define GPE_REG_PIN_COUNT(block_size)  BYTES_TO_BITS(GPE_BLK_REG_SIZE(block_size))
+#define GPE_STS_REG_OFFSET             0
+#define GPE_EN_REG_OFFSET(block_size)  GPE_BLK_REG_SIZE(block_size)
+
+/**
+ * struct nvl_gpio - Intel Nova Lake GPIO driver state
+ * @gc: GPIO controller interface
+ * @reg_base: Base address of the GPE registers
+ * @lock: Guard register access
+ * @blk_size: GPE block length
+ */
+struct nvl_gpio {
+       struct gpio_chip gc;
+       void __iomem *reg_base;
+       raw_spinlock_t lock;
+       size_t blk_size;
+};
+
+static void __iomem *nvl_gpio_get_byte_addr(struct nvl_gpio *priv,
+                                           unsigned int reg_offset,
+                                           unsigned long gpio)
+{
+       return priv->reg_base + reg_offset + gpio;
+}
+
+static int nvl_gpio_get(struct gpio_chip *gc, unsigned int gpio)
+{
+       struct nvl_gpio *priv = gpiochip_get_data(gc);
+       unsigned int byte_idx = gpio / BITS_PER_BYTE;
+       unsigned int bit_idx = gpio % BITS_PER_BYTE;
+       void __iomem *addr;
+       u8 reg;
+
+       addr = nvl_gpio_get_byte_addr(priv, GPE_STS_REG_OFFSET, byte_idx);
+
+       guard(raw_spinlock_irqsave)(&priv->lock);
+
+       reg = ioread8(addr);
+
+       return !!(reg & BIT(bit_idx));
+}
+
+static const struct gpio_chip nvl_gpio_chip = {
+       .owner  = THIS_MODULE,
+       .get    = nvl_gpio_get,
+};
+
+static int nvl_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+       if (type & IRQ_TYPE_EDGE_BOTH)
+               irq_set_handler_locked(d, handle_edge_irq);
+       else if (type & IRQ_TYPE_LEVEL_MASK)
+               irq_set_handler_locked(d, handle_level_irq);
+
+       return 0;
+}
+
+static void nvl_gpio_irq_mask_unmask(struct gpio_chip *gc, unsigned long hwirq,
+                                    bool mask)
+{
+       struct nvl_gpio *priv = gpiochip_get_data(gc);
+       unsigned int byte_idx = hwirq / BITS_PER_BYTE;
+       unsigned int bit_idx = hwirq % BITS_PER_BYTE;
+       void __iomem *addr;
+       u8 reg;
+
+       addr = nvl_gpio_get_byte_addr(priv, GPE_EN_REG_OFFSET(priv->blk_size), byte_idx);
+
+       guard(raw_spinlock_irqsave)(&priv->lock);
+
+       reg = ioread8(addr);
+       if (mask)
+               reg &= ~BIT(bit_idx);
+       else
+               reg |= BIT(bit_idx);
+       iowrite8(reg, addr);
+}
+
+static void nvl_gpio_irq_unmask(struct irq_data *d)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+       irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+       gpiochip_enable_irq(gc, hwirq);
+       nvl_gpio_irq_mask_unmask(gc, hwirq, false);
+}
+
+static void nvl_gpio_irq_mask(struct irq_data *d)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+       irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+       nvl_gpio_irq_mask_unmask(gc, hwirq, true);
+       gpiochip_disable_irq(gc, hwirq);
+}
+
+static void nvl_gpio_irq_ack(struct irq_data *d)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+       struct nvl_gpio *priv = gpiochip_get_data(gc);
+       irq_hw_number_t hwirq = irqd_to_hwirq(d);
+       unsigned int byte_idx = hwirq / BITS_PER_BYTE;
+       unsigned int bit_idx = hwirq % BITS_PER_BYTE;
+       void __iomem *addr;
+       u8 reg;
+
+       addr = nvl_gpio_get_byte_addr(priv, GPE_STS_REG_OFFSET, byte_idx);
+
+       guard(raw_spinlock_irqsave)(&priv->lock);
+
+       reg = ioread8(addr);
+       reg |= BIT(bit_idx);
+       iowrite8(reg, addr);
+}
+
+static const struct irq_chip nvl_gpio_irq_chip = {
+       .name           = "gpio-novalake",
+       .irq_ack        = nvl_gpio_irq_ack,
+       .irq_mask       = nvl_gpio_irq_mask,
+       .irq_unmask     = nvl_gpio_irq_unmask,
+       .irq_set_type   = nvl_gpio_irq_set_type,
+       .flags          = IRQCHIP_IMMUTABLE,
+       GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static irqreturn_t nvl_gpio_irq(int irq, void *data)
+{
+       struct nvl_gpio *priv = data;
+       const size_t block_size = priv->blk_size;
+       unsigned int handled = 0;
+
+       for (unsigned int i = 0; i < block_size; i++) {
+               const void __iomem *reg = priv->reg_base + i;
+               unsigned long pending;
+               unsigned long enabled;
+               unsigned int bit_idx;
+
+               scoped_guard(raw_spinlock, &priv->lock) {
+                       pending = ioread8(reg + GPE_STS_REG_OFFSET);
+                       enabled = ioread8(reg + GPE_EN_REG_OFFSET(block_size));
+               }
+               pending &= enabled;
+
+               for_each_set_bit(bit_idx, &pending, BITS_PER_BYTE) {
+                       unsigned int hwirq = i * BITS_PER_BYTE + bit_idx;
+
+                       generic_handle_domain_irq(priv->gc.irq.domain, hwirq);
+               }
+
+               handled += pending ? 1 : 0;
+       }
+
+       return IRQ_RETVAL(handled);
+}
+
+/* UUID for GPE device _DSM: 079406e6-bdea-49cf-8563-03e2811901cb */
+static const guid_t nvl_gpe_dsm_guid =
+       GUID_INIT(0x079406e6, 0xbdea, 0x49cf,
+                 0x85, 0x63, 0x03, 0xe2, 0x81, 0x19, 0x01, 0xcb);
+
+#define DSM_GPE_MODE_REV       1
+#define DSM_GPE_MODE_FN_INDEX  1
+#define DSM_ENABLE_GPE_MODE    1
+
+static int nvl_acpi_enable_gpe_mode(struct device *dev)
+{
+       union acpi_object argv4[2];
+       union acpi_object *obj;
+
+       argv4[0].type = ACPI_TYPE_PACKAGE;
+       argv4[0].package.count = 1;
+       argv4[0].package.elements = &argv4[1];
+       argv4[1].integer.type = ACPI_TYPE_INTEGER;
+       argv4[1].integer.value = DSM_ENABLE_GPE_MODE;
+
+       obj = acpi_evaluate_dsm_typed(ACPI_HANDLE(dev), &nvl_gpe_dsm_guid,
+                                     DSM_GPE_MODE_REV, DSM_GPE_MODE_FN_INDEX,
+                                     argv4, ACPI_TYPE_BUFFER);
+       if (!obj)
+               return -EIO;
+       ACPI_FREE(obj);
+
+       return 0;
+}
+
+static int nvl_gpio_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       resource_size_t ioresource_size;
+       struct gpio_irq_chip *girq;
+       struct nvl_gpio *priv;
+       struct resource *res;
+       void __iomem *regs;
+       int ret, irq;
+
+       res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+       if (!res)
+               return -ENXIO;
+
+       /*
+        * GPE block length should be non-negative multiple of two and allow up
+        * to 128 pins. ACPI v6.6 section 5.2.9 and 5.6.4.
+        */
+       ioresource_size = resource_size(res);
+       if (!ioresource_size || ioresource_size % 2 || ioresource_size > 0x20)
+               return dev_err_probe(dev, -EINVAL,
+                                    "invalid GPE block length, resource: %pR\n",
+                                    res);
+
+       regs = devm_ioport_map(dev, res->start, ioresource_size);
+       if (!regs)
+               return -ENOMEM;
+
+       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+       raw_spin_lock_init(&priv->lock);
+
+       priv->reg_base = regs;
+       priv->blk_size = ioresource_size;
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0)
+               return irq;
+
+       ret = devm_request_irq(dev, irq, nvl_gpio_irq, IRQF_SHARED, dev_name(dev), priv);
+       if (ret)
+               return ret;
+
+       priv->gc        = nvl_gpio_chip;
+       priv->gc.label  = dev_name(dev);
+       priv->gc.parent = dev;
+       priv->gc.ngpio  = GPE_REG_PIN_COUNT(priv->blk_size);
+       priv->gc.base   = -1;
+
+       girq = &priv->gc.irq;
+       gpio_irq_chip_set_chip(girq, &nvl_gpio_irq_chip);
+       girq->parent_handler    = NULL;
+       girq->num_parents       = 0;
+       girq->parents           = NULL;
+       girq->default_type      = IRQ_TYPE_NONE;
+       girq->handler           = handle_bad_irq;
+
+       ret = devm_gpiochip_add_data(dev, &priv->gc, priv);
+       if (ret)
+               return ret;
+
+       return nvl_acpi_enable_gpe_mode(dev);
+}
+
+static const struct acpi_device_id nvl_gpio_acpi_match[] = {
+       { "INTC1114" },
+       {}
+};
+MODULE_DEVICE_TABLE(acpi, nvl_gpio_acpi_match);
+
+static struct platform_driver nvl_gpio_driver = {
+       .driver = {
+               .name             = "gpio-novalake-events",
+               .acpi_match_table = nvl_gpio_acpi_match,
+       },
+       .probe = nvl_gpio_probe,
+};
+module_platform_driver(nvl_gpio_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Alan Borzeszkowski <alan.borzeszkowski@linux.intel.com>");
+MODULE_DESCRIPTION("Intel Nova Lake ACPI GPIO events driver");