* [PATCH v3 3/7] serial: 8250_aspeed_vuart: add aspeed,ast2600-vuart compatible string
From: Grégoire Layet @ 2026-06-23 14:25 UTC (permalink / raw)
To: joel, andrew, lkundrak, devicetree, gregkh, jirislaby, robh,
krzk+dt, conor+dt
Cc: andrew, jacky_chou, yh_chung, ninad, anirudhsriniv, linux-serial,
linux-aspeed, linux-arm-kernel, linux-kernel, Grégoire Layet
In-Reply-To: <cover.1782224059.git.gregoire.layet@9elements.com>
Makes the driver compatible with the ast2600-vuart.
This enables specific configuration for the AST2600.
Signed-off-by: Grégoire Layet <gregoire.layet@9elements.com>
---
drivers/tty/serial/8250/8250_aspeed_vuart.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/tty/serial/8250/8250_aspeed_vuart.c b/drivers/tty/serial/8250/8250_aspeed_vuart.c
index 26fc0464f1cc..6afa2f4057e1 100644
--- a/drivers/tty/serial/8250/8250_aspeed_vuart.c
+++ b/drivers/tty/serial/8250/8250_aspeed_vuart.c
@@ -560,6 +560,7 @@ static void aspeed_vuart_remove(struct platform_device *pdev)
static const struct of_device_id aspeed_vuart_table[] = {
{ .compatible = "aspeed,ast2400-vuart" },
{ .compatible = "aspeed,ast2500-vuart" },
+ { .compatible = "aspeed,ast2600-vuart" },
{ },
};
MODULE_DEVICE_TABLE(of, aspeed_vuart_table);
--
2.54.0
^ permalink raw reply related
* [PATCH v3 4/7] serial: 8250_aspeed_vuart: add VUART over PCI
From: Grégoire Layet @ 2026-06-23 14:25 UTC (permalink / raw)
To: joel, andrew, lkundrak, devicetree, gregkh, jirislaby, robh,
krzk+dt, conor+dt
Cc: andrew, jacky_chou, yh_chung, ninad, anirudhsriniv, linux-serial,
linux-aspeed, linux-arm-kernel, linux-kernel, Grégoire Layet
In-Reply-To: <cover.1782224059.git.gregoire.layet@9elements.com>
This patch enables the VUART over PCI possible for the AST2600. This is
only activated if the 'aspeed,vuart-over-pci' property flag is set on an
'ast2600-vuart' compatible node.
The AST2600 has 2 VUART that are usable over PCI. These are the VUART3
and VUART4 in the 'apseed-g6.dtsi'.
This code sets the BMC PCI device enables
bits, sets the PCI class code to MFD device and configures MSI interrupts.
There is no disable function. Removing this driver should not disable
the BMC PCI device, as other drivers could use it.
However, if all the drivers using it are removed, the
BMC PCI device will still be activated, which is not ideal. Realistically though, this is not a
use case for a BMC, the drivers will never be removed.
This is useful on PCIe BMC expansion cards that use the AST2600, such as the
ASUS Kommando IPMI Expansion Card.
Registers initialisation taken from ASPEED 6.18 Kernel SDK.
Return code checks were added to each register write.
The code has been simplified and macros have been added.
The ASPEED_SCUC24 regmap update is missing a macro for 'BIT(14)'. I was
unable to determine the purpose of this bit. In the AST2600 A3
datasheet it is marked as 'reserved'. It is only used on the other
revision. As I only have the AST2600A3, I was unable to try this code
path.
Signed-off-by: Jacky Chou <jacky_chou@aspeedtech.com>
Signed-off-by: aspeedyh <yh_chung@aspeedtech.com>
Signed-off-by: Grégoire Layet <gregoire.layet@9elements.com>
Tested-by: Grégoire Layet <gregoire.layet@9elements.com>
---
drivers/tty/serial/8250/8250_aspeed_vuart.c | 86 +++++++++++++++++++++
1 file changed, 86 insertions(+)
diff --git a/drivers/tty/serial/8250/8250_aspeed_vuart.c b/drivers/tty/serial/8250/8250_aspeed_vuart.c
index 6afa2f4057e1..e204e26fa173 100644
--- a/drivers/tty/serial/8250/8250_aspeed_vuart.c
+++ b/drivers/tty/serial/8250/8250_aspeed_vuart.c
@@ -32,6 +32,27 @@
#define ASPEED_VUART_DEFAULT_SIRQ 4
#define ASPEED_VUART_DEFAULT_SIRQ_POLARITY IRQ_TYPE_LEVEL_LOW
+#define ASPEED_SCU_SILICON_REVISION_ID 0x04
+#define AST2600A3_REVISION_ID 0x05030303
+
+#define ASPEED_SCUC24 0xC24
+#define ASPEED_SCUC24_MSI_ROUTING_MASK GENMASK(11, 10)
+#define ASPEED_SCUC24_MSI_ROUTING_PCIe2LPC_PCIDEV1 (0x2 << 10)
+#define ASPEED_SCUC24_PCIDEV1_INTX_MSI_HOST2BMC_EN BIT(18)
+#define ASPEED_SCUC24_PCIDEV1_INTX_MSI_SCU560_EN BIT(17)
+
+
+#define ASPEED_SCU_PCIE_CONF_CTRL 0xC20
+#define SCU_PCIE_CONF_BMC_DEV_EN BIT(8)
+#define SCU_PCIE_CONF_BMC_DEV_EN_MMIO BIT(9)
+#define SCU_PCIE_CONF_BMC_DEV_EN_MSI BIT(11)
+#define SCU_PCIE_CONF_BMC_DEV_EN_IRQ BIT(13)
+#define SCU_PCIE_CONF_BMC_DEV_EN_PCIE_BUS_MASTER BIT(14)
+#define SCU_PCIE_CONF_BMC_DEV_EN_E2L BIT(15)
+#define SCU_PCIE_CONF_BMC_DEV_EN_LPC_DECODE BIT(21)
+
+#define ASPEED_SCU_BMC_DEV_CLASS 0xC68
+
struct aspeed_vuart {
struct device *dev;
int line;
@@ -412,6 +433,62 @@ static int aspeed_vuart_map_irq_polarity(u32 dt)
}
}
+static int aspeed_ast2600_vuart_over_pci_set_enabled(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ u32 silicon_revision_id;
+ struct regmap *scu;
+ int rc;
+
+ u32 pcie_config_ctl = SCU_PCIE_CONF_BMC_DEV_EN_IRQ |
+ SCU_PCIE_CONF_BMC_DEV_EN_MMIO |
+ SCU_PCIE_CONF_BMC_DEV_EN_MSI |
+ SCU_PCIE_CONF_BMC_DEV_EN_PCIE_BUS_MASTER |
+ SCU_PCIE_CONF_BMC_DEV_EN_E2L |
+ SCU_PCIE_CONF_BMC_DEV_EN_LPC_DECODE |
+ SCU_PCIE_CONF_BMC_DEV_EN;
+
+ scu = syscon_regmap_lookup_by_phandle(dev->of_node, "clocks");
+ if (IS_ERR(scu)) {
+ dev_err(&pdev->dev, "failed to find SCU regmap\n");
+ return PTR_ERR(scu);
+ }
+
+ /* update class code to be an MFD device */
+ if (regmap_write(scu, ASPEED_SCU_BMC_DEV_CLASS, 0xff000000)) {
+ dev_err(dev, "could not set PCI class code\n");
+ return -EIO;
+ }
+
+ if (regmap_update_bits(scu, ASPEED_SCU_PCIE_CONF_CTRL,
+ pcie_config_ctl, pcie_config_ctl)) {
+ dev_err(dev, "could not set PCIe configuration\n");
+ return -EIO;
+ }
+
+ if (regmap_read(scu, ASPEED_SCU_SILICON_REVISION_ID, &silicon_revision_id)) {
+ dev_err(dev, "could not read silicon revision\n");
+ return -EIO;
+ }
+
+ if (silicon_revision_id == AST2600A3_REVISION_ID)
+ rc = regmap_update_bits(scu, ASPEED_SCUC24,
+ ASPEED_SCUC24_PCIDEV1_INTX_MSI_HOST2BMC_EN | ASPEED_SCUC24_MSI_ROUTING_MASK,
+ ASPEED_SCUC24_PCIDEV1_INTX_MSI_HOST2BMC_EN | ASPEED_SCUC24_MSI_ROUTING_PCIe2LPC_PCIDEV1);
+ else
+ rc = regmap_update_bits(scu, ASPEED_SCUC24,
+ /* The bit 14 is reserved in the Datasheet, so we can't say what it does. This revision has not been tested */
+ ASPEED_SCUC24_PCIDEV1_INTX_MSI_SCU560_EN | BIT(14) | ASPEED_SCUC24_MSI_ROUTING_MASK,
+ ASPEED_SCUC24_PCIDEV1_INTX_MSI_SCU560_EN | BIT(14) | ASPEED_SCUC24_MSI_ROUTING_PCIe2LPC_PCIDEV1);
+ if (rc) {
+ dev_err(dev, "could not set PCI device 1 MSI interrupt routing\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+
static int aspeed_vuart_probe(struct platform_device *pdev)
{
struct of_phandle_args sirq_polarity_sense_args;
@@ -540,6 +617,15 @@ static int aspeed_vuart_probe(struct platform_device *pdev)
aspeed_vuart_set_host_tx_discard(vuart, true);
platform_set_drvdata(pdev, vuart);
+ if (of_device_is_compatible(dev->of_node, "aspeed,ast2600-vuart") &&
+ of_property_read_bool(dev->of_node, "aspeed,vuart-over-pci")) {
+ rc = aspeed_ast2600_vuart_over_pci_set_enabled(pdev);
+ if (rc) {
+ dev_err(dev, "could not enable VUART over PCI\n");
+ return rc;
+ }
+ }
+
return 0;
err_sysfs_remove:
--
2.54.0
^ permalink raw reply related
* [PATCH v3 5/7] soc: aspeed: add host-side PCIe BMC device driver
From: Grégoire Layet @ 2026-06-23 14:25 UTC (permalink / raw)
To: joel, andrew, lkundrak, devicetree, gregkh, jirislaby, robh,
krzk+dt, conor+dt
Cc: andrew, jacky_chou, yh_chung, ninad, anirudhsriniv, linux-serial,
linux-aspeed, linux-arm-kernel, linux-kernel, Grégoire Layet
In-Reply-To: <cover.1782224059.git.gregoire.layet@9elements.com>
Add support for VUART over PCIe between BMC and host.
This add host side driver.
This only support the AST2600.
Taken from ASPEED 6.18 Kernel SDK and trimmed down.
The host can't detect the VUART adresses, they are forced
at 0x3f8 and 0x2f8, similar from the initial ASPEED driver.
The MSI vector index has been changed for the VUART2 from 15 to 17.
The index 15 used in the initial driver was not working.
Data path in both direction is tested on both VUART.
Signed-off-by: Jacky Chou <jacky_chou@aspeedtech.com>
Signed-off-by: aspeedyh <yh_chung@aspeedtech.com>
Signed-off-by: Grégoire Layet <gregoire.layet@9elements.com>
Tested-by: Grégoire Layet <gregoire.layet@9elements.com>
---
drivers/soc/aspeed/Kconfig | 8 +
drivers/soc/aspeed/Makefile | 1 +
drivers/soc/aspeed/aspeed-host-bmc-dev.c | 183 +++++++++++++++++++++++
3 files changed, 192 insertions(+)
create mode 100644 drivers/soc/aspeed/aspeed-host-bmc-dev.c
diff --git a/drivers/soc/aspeed/Kconfig b/drivers/soc/aspeed/Kconfig
index 63a656449a1a..ebd023b10701 100644
--- a/drivers/soc/aspeed/Kconfig
+++ b/drivers/soc/aspeed/Kconfig
@@ -4,6 +4,14 @@ if ARCH_ASPEED || COMPILE_TEST
menu "ASPEED SoC drivers"
+config ASPEED_HOST_BMC_DEV
+ tristate "ASPEED Host BMC Device"
+ depends on PCI
+ depends on SERIAL_8250
+ help
+ Enable support for the ASPEED AST2600 BMC Device on the Host.
+ This configure the PCIe and setup two 8250 compatible VUART ports.
+
config ASPEED_LPC_CTRL
tristate "ASPEED LPC firmware cycle control"
select REGMAP
diff --git a/drivers/soc/aspeed/Makefile b/drivers/soc/aspeed/Makefile
index b35d74592964..c515e163eab7 100644
--- a/drivers/soc/aspeed/Makefile
+++ b/drivers/soc/aspeed/Makefile
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_ASPEED_HOST_BMC_DEV) += aspeed-host-bmc-dev.o
obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o
obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o
obj-$(CONFIG_ASPEED_UART_ROUTING) += aspeed-uart-routing.o
diff --git a/drivers/soc/aspeed/aspeed-host-bmc-dev.c b/drivers/soc/aspeed/aspeed-host-bmc-dev.c
new file mode 100644
index 000000000000..3160b6aedb5b
--- /dev/null
+++ b/drivers/soc/aspeed/aspeed-host-bmc-dev.c
@@ -0,0 +1,183 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// Copyright (C) ASPEED Technology Inc.
+
+#include <linux/init.h>
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/pci.h>
+#include <linux/serial_core.h>
+#include <linux/serial_8250.h>
+
+#define BMC_MULTI_MSI 32
+#define PCI_BMC_DEVICE_ID 0x2402
+
+#define DRIVER_NAME "aspeed-host-bmc-dev"
+
+enum aspeed_platform_id {
+ ASPEED,
+};
+
+static int vuart_msi_index[2] = { 16, 17 };
+static int vuart_port_addr[2] = {0x3f8, 0x2f8};
+
+struct aspeed_pci_bmc_dev {
+ struct device *dev;
+ kernel_ulong_t driver_data;
+ int id;
+
+ unsigned long message_bar_base;
+
+ struct uart_8250_port uart[2];
+ int uart_line[2];
+
+ int *msi_idx_table;
+};
+
+static void aspeed_pci_setup_irq_resource(struct pci_dev *pdev)
+{
+ struct aspeed_pci_bmc_dev *pci_bmc_dev = pci_get_drvdata(pdev);
+
+ pci_bmc_dev->msi_idx_table = vuart_msi_index;
+
+ if (pci_alloc_irq_vectors(pdev, 1, BMC_MULTI_MSI, PCI_IRQ_INTX | PCI_IRQ_MSI) <= 1)
+ /* If pci_alloc fail, set all msi index to the first vector */
+ memset(pci_bmc_dev->msi_idx_table, 0, sizeof(vuart_msi_index));
+}
+
+static int aspeed_pci_bmc_device_setup_vuart(struct pci_dev *pdev, int idx)
+{
+ struct aspeed_pci_bmc_dev *pci_bmc_dev = pci_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+ struct uart_8250_port *uart = &pci_bmc_dev->uart[idx];
+ u16 vuart_ioport;
+ int ret;
+
+ /* Assign the line to non-exist device before everything is setup */
+ pci_bmc_dev->uart_line[idx] = -ENOENT;
+
+ vuart_ioport = vuart_port_addr[idx];
+ /* ASPEED BMC device shift adresses by 2 to the left */
+ vuart_ioport = vuart_ioport << 2;
+
+ uart->port.flags = UPF_SKIP_TEST | UPF_BOOT_AUTOCONF | UPF_SHARE_IRQ;
+ uart->port.uartclk = 115200 * 16;
+ uart->port.irq = pci_irq_vector(pdev, pci_bmc_dev->msi_idx_table[idx]);
+ uart->port.dev = dev;
+ uart->port.iotype = UPIO_MEM32;
+ uart->port.iobase = 0;
+ uart->port.mapbase = pci_bmc_dev->message_bar_base + vuart_ioport;
+ uart->port.membase = 0;
+ uart->port.type = PORT_16550A;
+ uart->port.flags |= (UPF_IOREMAP | UPF_FIXED_PORT | UPF_FIXED_TYPE);
+ uart->port.regshift = 2;
+
+ ret = serial8250_register_8250_port(&pci_bmc_dev->uart[idx]);
+ if (ret < 0) {
+ dev_err_probe(dev, ret, "Can't setup PCIe VUART%d\n", idx);
+ return ret;
+ }
+
+ pci_bmc_dev->uart_line[idx] = ret;
+
+ return 0;
+}
+
+static void aspeed_pci_host_bmc_device_release_vuart(struct pci_dev *pdev, int idx)
+{
+ struct aspeed_pci_bmc_dev *pci_bmc_dev = pci_get_drvdata(pdev);
+
+ if (pci_bmc_dev->uart_line[idx] >= 0)
+ serial8250_unregister_port(pci_bmc_dev->uart_line[idx]);
+}
+
+static int aspeed_pci_host_setup(struct pci_dev *pdev)
+{
+ struct aspeed_pci_bmc_dev *pci_bmc_dev = pci_get_drvdata(pdev);
+ int rc = 0;
+
+ pci_bmc_dev->message_bar_base = pci_resource_start(pdev, 1);
+
+ if (pdev->revision == 0x27) {
+ pr_err("AST2700 detected but not supported");
+ return -ENODEV;
+ }
+
+ rc = aspeed_pci_bmc_device_setup_vuart(pdev, 0);
+ if (rc)
+ return rc;
+
+ rc = aspeed_pci_bmc_device_setup_vuart(pdev, 1);
+ if (rc)
+ goto out_freeVUART1;
+
+ return 0;
+
+out_freeVUART1:
+ aspeed_pci_host_bmc_device_release_vuart(pdev, 0);
+
+ return rc;
+}
+
+static int aspeed_pci_host_bmc_device_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ struct aspeed_pci_bmc_dev *pci_bmc_dev;
+ int rc = 0;
+
+ pci_bmc_dev = devm_kzalloc(&pdev->dev, sizeof(*pci_bmc_dev), GFP_KERNEL);
+ if (!pci_bmc_dev)
+ return -ENOMEM;
+
+ rc = pci_enable_device(pdev);
+ if (rc) {
+ dev_err(&pdev->dev, "pci_enable_device() returned error %d\n", rc);
+ return rc;
+ }
+
+ pci_set_master(pdev);
+ pci_set_drvdata(pdev, pci_bmc_dev);
+
+ aspeed_pci_setup_irq_resource(pdev);
+
+ /* Setup BMC PCI device */
+ rc = aspeed_pci_host_setup(pdev);
+ if (rc) {
+ dev_err(&pdev->dev, "ASPEED PCIe Host device returned error %d\n", rc);
+ pci_free_irq_vectors(pdev);
+ pci_disable_device(pdev);
+ return rc;
+ }
+
+ return 0;
+}
+
+static void aspeed_pci_host_bmc_device_remove(struct pci_dev *pdev)
+{
+ aspeed_pci_host_bmc_device_release_vuart(pdev, 0);
+ aspeed_pci_host_bmc_device_release_vuart(pdev, 1);
+
+ pci_free_irq_vectors(pdev);
+ pci_disable_device(pdev);
+}
+
+static struct pci_device_id aspeed_host_bmc_dev_pci_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ASPEED, PCI_BMC_DEVICE_ID), .class = 0xFF0000, .class_mask = 0xFFFF00,
+ .driver_data = ASPEED },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(pci, aspeed_host_bmc_dev_pci_ids);
+
+static struct pci_driver aspeed_host_bmc_dev_driver = {
+ .name = DRIVER_NAME,
+ .id_table = aspeed_host_bmc_dev_pci_ids,
+ .probe = aspeed_pci_host_bmc_device_probe,
+ .remove = aspeed_pci_host_bmc_device_remove,
+};
+
+module_driver(aspeed_host_bmc_dev_driver, pci_register_driver, pci_unregister_driver);
+
+MODULE_AUTHOR("Ryan Chen <ryan_chen@aspeedtech.com>");
+MODULE_DESCRIPTION("ASPEED Host BMC DEVICE Driver");
+MODULE_LICENSE("GPL");
--
2.54.0
^ permalink raw reply related
* [PATCH v3 6/7] ARM: dts: aspeed: g6: Change vuart compatible string for ast2600
From: Grégoire Layet @ 2026-06-23 14:25 UTC (permalink / raw)
To: joel, andrew, lkundrak, devicetree, gregkh, jirislaby, robh,
krzk+dt, conor+dt
Cc: andrew, jacky_chou, yh_chung, ninad, anirudhsriniv, linux-serial,
linux-aspeed, linux-arm-kernel, linux-kernel, Grégoire Layet
In-Reply-To: <cover.1782224059.git.gregoire.layet@9elements.com>
Use the ast2600 compatible string.
This makes it more precise and enables specific ast2600 properties.
Still use the ast2500 compatible string as a fallback.
Signed-off-by: Grégoire Layet <gregoire.layet@9elements.com>
---
arch/arm/boot/dts/aspeed/aspeed-g6.dtsi | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi b/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
index 56bb3b0444f7..7c02633f2bd6 100644
--- a/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
+++ b/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
@@ -707,7 +707,7 @@ emmc: sdhci@1e750100 {
};
vuart1: serial@1e787000 {
- compatible = "aspeed,ast2500-vuart";
+ compatible = "aspeed,ast2600-vuart", "aspeed,ast2500-vuart";
reg = <0x1e787000 0x40>;
reg-shift = <2>;
interrupts = <GIC_SPI 147 IRQ_TYPE_LEVEL_HIGH>;
@@ -717,7 +717,7 @@ vuart1: serial@1e787000 {
};
vuart3: serial@1e787800 {
- compatible = "aspeed,ast2500-vuart";
+ compatible = "aspeed,ast2600-vuart", "aspeed,ast2500-vuart";
reg = <0x1e787800 0x40>;
reg-shift = <2>;
interrupts = <GIC_SPI 180 IRQ_TYPE_LEVEL_HIGH>;
@@ -727,7 +727,7 @@ vuart3: serial@1e787800 {
};
vuart2: serial@1e788000 {
- compatible = "aspeed,ast2500-vuart";
+ compatible = "aspeed,ast2600-vuart", "aspeed,ast2500-vuart";
reg = <0x1e788000 0x40>;
reg-shift = <2>;
interrupts = <GIC_SPI 148 IRQ_TYPE_LEVEL_HIGH>;
@@ -737,7 +737,7 @@ vuart2: serial@1e788000 {
};
vuart4: serial@1e788800 {
- compatible = "aspeed,ast2500-vuart";
+ compatible = "aspeed,ast2600-vuart", "aspeed,ast2500-vuart";
reg = <0x1e788800 0x40>;
reg-shift = <2>;
interrupts = <GIC_SPI 181 IRQ_TYPE_LEVEL_HIGH>;
--
2.54.0
^ permalink raw reply related
* [PATCH v3 7/7] ARM: dts: aspeed: g6: add aspeed,vuart-over-pci prop to vuart3 and 4
From: Grégoire Layet @ 2026-06-23 14:25 UTC (permalink / raw)
To: joel, andrew, lkundrak, devicetree, gregkh, jirislaby, robh,
krzk+dt, conor+dt
Cc: andrew, jacky_chou, yh_chung, ninad, anirudhsriniv, linux-serial,
linux-aspeed, linux-arm-kernel, linux-kernel, Grégoire Layet
In-Reply-To: <cover.1782224059.git.gregoire.layet@9elements.com>
The VUART 3 and 4 are VUART over PCI.
This flag indicates this information.
Signed-off-by: Grégoire Layet <gregoire.layet@9elements.com>
---
arch/arm/boot/dts/aspeed/aspeed-g6.dtsi | 2 ++
1 file changed, 2 insertions(+)
diff --git a/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi b/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
index 7c02633f2bd6..2a19463b4c21 100644
--- a/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
+++ b/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
@@ -723,6 +723,7 @@ vuart3: serial@1e787800 {
interrupts = <GIC_SPI 180 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&syscon ASPEED_CLK_APB2>;
no-loopback-test;
+ aspeed,vuart-over-pci;
status = "disabled";
};
@@ -743,6 +744,7 @@ vuart4: serial@1e788800 {
interrupts = <GIC_SPI 181 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&syscon ASPEED_CLK_APB2>;
no-loopback-test;
+ aspeed,vuart-over-pci;
status = "disabled";
};
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v2] dt-bindings: pwm: st,pwm: convert to DT schema
From: Charan Pedumuru @ 2026-06-23 14:31 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Pal Singh, linux-pwm, devicetree, linux-kernel
In-Reply-To: <761cb58f-17cd-4a0b-98eb-a38b3b619941@kernel.org>
On 23-06-2026 19:55, Krzysztof Kozlowski wrote:
> On 23/06/2026 16:16, Charan Pedumuru wrote:
>>
>>
>> On 23-06-2026 12:47, Krzysztof Kozlowski wrote:
>>> On Thu, Jun 18, 2026 at 12:42:07PM +0000, Charan Pedumuru wrote:
>>>> +description:
>>>> + The STiH41x PWM controller supports both PWM output and input capture
>>>> + functionality. It provides multiple PWM output channels for generating
>>>> + variable duty-cycle waveforms, and multiple input capture channels for
>>>> + measuring external signal periods and pulse widths. PWM output channels
>>>> + and input capture channels are configured independently via
>>>> + st,pwm-num-chan and st,capture-num-chan respectively.
>>>> +
>>>> +allOf:
>>>> + - $ref: pwm.yaml#
>>>
>>> Where is this compatible used?
>>
>> I will remove this allof and redefine the YAML again
>>
>
>
> Sorry, I think I messed something so comment was incorrectly placed.
>
> The comment should be under:
>
> "+ const: st,pwm"
Okay, this compatible is not used and there is a similar compatible "st,sti-pwm", AI bot detected it and I will rename it and modify the commit message with necessary explanation. Also the maintainer email doesn't exist now. I will change that too with other maintainer. Thank you.
>
> Best regards,
> Krzysztof
--
Best Regards,
Charan.
^ permalink raw reply
* Re: [PATCH V2 1/8] PCI: imx6: Add skip_pwrctrl_off flag support
From: Frank Li @ 2026-06-23 14:32 UTC (permalink / raw)
To: Sherry Sun (OSS)
Cc: robh, krzk+dt, conor+dt, Frank.Li, s.hauer, kernel, festevam,
amitkumar.karwar, neeraj.sanjaykale, marcel, luiz.dentz,
hongxing.zhu, l.stach, lpieralisi, kwilczynski, mani, bhelgaas,
brgl, imx, linux-pci, linux-arm-kernel, devicetree, linux-kernel,
linux-bluetooth, linux-pm, sherry.sun
In-Reply-To: <20260623030736.1421537-2-sherry.sun@oss.nxp.com>
On Tue, Jun 23, 2026 at 11:07:28AM +0800, Sherry Sun (OSS) wrote:
> From: Sherry Sun <sherry.sun@nxp.com>
>
> Use dw_pcie_rp::skip_pwrctrl_off to avoid powering off devices during
> suspend to preserve wakeup capability of the devices and also not to power
> on the devices in the init path.
> This allows controller power-off to be skipped when some devices(e.g. M.2
> cards key E without auxiliary power) required to support PCIe L2 link state
> and wake-up mechanisms.
>
> Signed-off-by: Sherry Sun <sherry.sun@nxp.com>
> ---
> drivers/pci/controller/dwc/pci-imx6.c | 36 +++++++++++++++++----------
> 1 file changed, 23 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c
> index 0fa716d1ed75..ff5a9565dbbf 100644
> --- a/drivers/pci/controller/dwc/pci-imx6.c
> +++ b/drivers/pci/controller/dwc/pci-imx6.c
> @@ -1382,16 +1382,20 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp)
> }
> }
>
> - ret = pci_pwrctrl_create_devices(dev);
> - if (ret) {
> - dev_err(dev, "failed to create pwrctrl devices\n");
> - goto err_reg_disable;
> + if (!pci->suspended) {
> + ret = pci_pwrctrl_create_devices(dev);
Is possible move pci_pwrctrl_create_devices() of pci_pwrctrl_create_devices
and call it direct at probe() function, like other regulator_get function.
Frank
> + if (ret) {
> + dev_err(dev, "failed to create pwrctrl devices\n");
> + goto err_reg_disable;
> + }
> }
>
> - ret = pci_pwrctrl_power_on_devices(dev);
> - if (ret) {
> - dev_err(dev, "failed to power on pwrctrl devices\n");
> - goto err_pwrctrl_destroy;
> + if (!pp->skip_pwrctrl_off) {
> + ret = pci_pwrctrl_power_on_devices(dev);
> + if (ret) {
> + dev_err(dev, "failed to power on pwrctrl devices\n");
> + goto err_pwrctrl_destroy;
> + }
> }
>
> ret = imx_pcie_clk_enable(imx_pcie);
> @@ -1460,9 +1464,10 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp)
> err_clk_disable:
> imx_pcie_clk_disable(imx_pcie);
> err_pwrctrl_power_off:
> - pci_pwrctrl_power_off_devices(dev);
> + if (!pp->skip_pwrctrl_off)
> + pci_pwrctrl_power_off_devices(dev);
> err_pwrctrl_destroy:
> - if (ret != -EPROBE_DEFER)
> + if (ret != -EPROBE_DEFER && !pci->suspended)
> pci_pwrctrl_destroy_devices(dev);
> err_reg_disable:
> if (imx_pcie->vpcie)
> @@ -1482,7 +1487,8 @@ static void imx_pcie_host_exit(struct dw_pcie_rp *pp)
> }
> imx_pcie_clk_disable(imx_pcie);
>
> - pci_pwrctrl_power_off_devices(pci->dev);
> + if (!pci->pp.skip_pwrctrl_off)
> + pci_pwrctrl_power_off_devices(pci->dev);
> if (imx_pcie->vpcie)
> regulator_disable(imx_pcie->vpcie);
> }
> @@ -1990,12 +1996,16 @@ static int imx_pcie_probe(struct platform_device *pdev)
> static void imx_pcie_shutdown(struct platform_device *pdev)
> {
> struct imx_pcie *imx_pcie = platform_get_drvdata(pdev);
> + struct dw_pcie *pci = imx_pcie->pci;
> + struct dw_pcie_rp *pp = &pci->pp;
>
> /* bring down link, so bootloader gets clean state in case of reboot */
> imx_pcie_assert_core_reset(imx_pcie);
> imx_pcie_assert_perst(imx_pcie, true);
> - pci_pwrctrl_power_off_devices(&pdev->dev);
> - pci_pwrctrl_destroy_devices(&pdev->dev);
> + if (!pp->skip_pwrctrl_off)
> + pci_pwrctrl_power_off_devices(&pdev->dev);
> + if (!pci->suspended)
> + pci_pwrctrl_destroy_devices(&pdev->dev);
> }
>
> static const struct imx_pcie_drvdata drvdata[] = {
> --
> 2.50.1
>
>
^ permalink raw reply
* Re: [PATCH v3 1/7] dt-bindings: serial: 8250: aspeed: add compatible string for ast2600
From: sashiko-bot @ 2026-06-23 14:35 UTC (permalink / raw)
To: Grégoire Layet; +Cc: devicetree, robh, conor+dt
In-Reply-To: <80d983887dfdfc7e70a6db95f8cb95b7312f3044.1782224059.git.gregoire.layet@9elements.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The newly introduced compatible string `aspeed,ast2600-vuart` is missing from the top-level `properties: compatible: oneOf:` allowed list. While it was correctly added to the `allOf` conditional blocks, its absence from the main enumeration will cause device tree schema validation (`dt_binding_check` and `dtbs_check`) to fail for any device tree node utilizing this compatible string.
--
commit 6f7f111bc5bb5cca5a6591d60c1457c4bf00cb3e
Author: Grégoire Layet <gregoire.layet@9elements.com>
dt-bindings: serial: 8250: aspeed: add compatible string for ast2600
The ast2600 was using the ast2500 vuart compatible string.
This change makes it possible to have ast2600-specific properties.
> diff --git a/Documentation/devicetree/bindings/serial/8250.yaml b/Documentation/devicetree/bindings/serial/8250.yaml
> index bb7b9c87a8072..3cbd0f532e151 100644
> --- a/Documentation/devicetree/bindings/serial/8250.yaml
> +++ b/Documentation/devicetree/bindings/serial/8250.yaml
> @@ -23,7 +23,9 @@ allOf:
> then:
> properties:
> compatible:
> - const: aspeed,ast2500-vuart
> + anyOf:
> + - const: aspeed,ast2500-vuart
> + - const: aspeed,ast2600-vuart
[Severity: Low]
Does this compatible string also need to be added to the top-level properties
enumeration?
While it is added here in the allOf conditional block, it appears to be missing
from the main allowed list, which currently looks like this:
properties:
compatible:
oneOf:
- const: ns8250
- const: ns16450
- const: ns16550
- const: ns16550a
- const: ns16850
- const: aspeed,ast2400-vuart
- const: aspeed,ast2500-vuart
Will this omission cause a regression during device tree schema validation
for device trees using the new aspeed,ast2600-vuart compatible string?
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1782224059.git.gregoire.layet@9elements.com?part=1
^ permalink raw reply
* Re: [PATCH 1/9] dt-bindings: nvmem: imx-ocotp: Add support for secure-enclave
From: Frank Li @ 2026-06-23 14:36 UTC (permalink / raw)
To: Peng Fan
Cc: Frieder Schrempf, Krzysztof Kozlowski, Frieder Schrempf,
Srinivas Kandagatla, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Shawn Guo, devicetree, imx, linux-arm-kernel,
linux-kernel
In-Reply-To: <ajoJFt6c1cKELlH6@shlinux89>
On Tue, Jun 23, 2026 at 12:18:30PM +0800, Peng Fan wrote:
> On Mon, Jun 22, 2026 at 09:14:43AM -0500, Frank Li wrote:
> >On Wed, Jun 17, 2026 at 01:36:30PM +0200, Frieder Schrempf wrote:
> >> On 17.06.26 12:49, Krzysztof Kozlowski wrote:
> >> > On Tue, Jun 16, 2026 at 01:52:16PM +0200, Frieder Schrempf wrote:
> >> >> From: Frieder Schrempf <frieder.schrempf@kontron.de>
> >> >>
> >> >> Some SoCs like the i.MX9 family allow full access to the fuses only
> >> >> through the secure enclave firmware API. Add a property to reference
> >> >> the secure enclave node and let the driver use the API.
> >> >>
> >> >> Signed-off-by: Frieder Schrempf <frieder.schrempf@kontron.de>
> >> >> ---
> >> >> Documentation/devicetree/bindings/nvmem/imx-ocotp.yaml | 4 ++++
> >> >> 1 file changed, 4 insertions(+)
> >> >>
> >> >> diff --git a/Documentation/devicetree/bindings/nvmem/imx-ocotp.yaml b/Documentation/devicetree/bindings/nvmem/imx-ocotp.yaml
> >> >> index a8076d0e2737..14a6429f4a4c 100644
> >> >> --- a/Documentation/devicetree/bindings/nvmem/imx-ocotp.yaml
> >> >> +++ b/Documentation/devicetree/bindings/nvmem/imx-ocotp.yaml
> >> >> @@ -53,6 +53,10 @@ properties:
> >> >> reg:
> >> >> maxItems: 1
> >> >>
> >> >> + secure-enclave:
> >> >> + $ref: /schemas/types.yaml#/definitions/phandle
> >> >> + description: A phandle to the secure enclave node
> >> >
> >> > Two things here:
> >> > 1. Here you describe what for is that phandle, how it is used by the
> >> > hardware. Currently the description repeats the property name and type,
> >> > so not much useful.
> >>
> >> Ok, agree.
> >>
> >> >
> >> > 2. If you access OTP via firmware, then this is completely different
> >> > interface than MMIO, thus:
> >> > A. reg is not appropriate
> >> > B. Device is very different thus it has different compatible and I even
> >> > claim should be in different binding. Devices having completely
> >> > different SW interface should not be in the same binding, at least
> >> > usually.
> >> >
> >> > If any of above is not accurate, then your commit msg should answer why
> >> > and give some background.
> >>
> >> Thanks for the feedback!
> >>
> >> The driver currently uses the limited MMIO (FSB) interface to access the
> >> OTPs. The intention is to support the firmware interface alongside the
> >> MMIO interface so the driver can pick the interface that is available
> >> (firmware might not be loaded) and fallback to MMIO.
> >
> >Does ELE and MMIO access the same bank of fuse? If access the same bank,
>
> Some fuse banks are only accessible through ELE firmware. Some fuse banks
> are accessible using MMIO. In theory, ELE firmware are able to access all
> fuse banks.
So use two driver for it, one use current MMIO ot access part of fuse box.
use ELE access the left part, which MMIO can't access.
MMIO should be simple and quick than go through ELE.
Frank
>
> Regards
> Peng
>
> >why not always use MMIO. Any beneafit from ELE firmware?
>
> >
> >Frank
> >>
> >> Following your argument would mean a driver deciding by itself which
> >> interface to use at runtime is not something we want to have in general,
> >> right?
> >>
> >> In turn this would mean we need two drivers, or at least two
> >> compatibles/bindings for something that is effectively the same hardware.
> >>
> >> Actually, my first RFC approach [1] was to create a separate driver. But
> >> in the end it seemed very weird to have two drivers and two DT nodes for
> >> the same hardware block. Also I have no idea what happens if both
> >> interfaces are used at the same time.
> >>
> >> The other idea from back then was to replace the MMIO (FSB) interface
> >> with ELE, but this would mean that we rely on the proprietary ELE
> >> firmware to be available for simple things like reading a MAC address,
> >> which is not desirable either, I guess.
> >>
> >> In which direction should I move on with this?
> >>
> >> [1]
> >> https://patchwork.kernel.org/project/linux-arm-kernel/patch/20250416142715.1042363-1-frieder@fris.de/
> >>
^ permalink raw reply
* Re: [PATCH 1/1] iio: health: add MAX86150 ECG and PPG biosensor driver
From: Joshua Crofts @ 2026-06-23 14:38 UTC (permalink / raw)
To: Md Shofiqul Islam
Cc: linux-iio, jic23, dlechner, nuno.sa, robh, krzk+dt, devicetree
In-Reply-To: <20260623140113.12574-2-shofiqtest@gmail.com>
On Tue, 23 Jun 2026 17:01:13 +0300
Md Shofiqul Islam <shofiqtest@gmail.com> wrote:
> The MAX86150 (Maxim/Analog Devices) integrates two PPG optical channels
> (Red/IR LED) and one ECG biopotential channel in a single I2C device.
> Data is captured via a 32-entry hardware FIFO with a configurable
> almost-full interrupt.
>
> The driver exposes three IIO channels:
> - in_intensity_red_raw (PPG Red, 19-bit unsigned)
> - in_intensity_ir_raw (PPG IR, 19-bit unsigned)
> - in_voltage0_raw (ECG, 18-bit signed)
>
> A hardware trigger is registered when an interrupt GPIO is provided in
> the device tree, enabling the IIO triggered buffer path for continuous
> low-overhead capture.
>
> FIFO reads use regmap_noinc_read() to burst-read 9 bytes (3 slots x
> 3 bytes) from the streaming FIFO_DATA register in a single I2C
> transaction.
>
> Signed-off-by: Md Shofiqul Islam <shofiqtest@gmail.com>
> ---
> .../bindings/iio/health/maxim,max86150.yaml | 67 +++
> drivers/iio/health/Kconfig | 23 +
> drivers/iio/health/Makefile | 1 +
> drivers/iio/health/max86150.c | 512 ++++++++++++++++++
> 4 files changed, 603 insertions(+)
> create mode 100755 Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml
> create mode 100755 drivers/iio/health/max86150.c
>
> diff --git a/Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml b/Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml
> new file mode 100755
> index 000000000000..1bf10fd1a3d2
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/health/maxim,max86150.yaml
> @@ -0,0 +1,67 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/health/maxim,max86150.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
Split this into 2 patches - one for the new dt-binding and one for the
actual driver, KConfig and Makefile.
Additionally, since you're adding a brand new driver, a MAINTAINERS entry
is required.
Kind regards
CJD
^ permalink raw reply
* Re: [PATCH v3 2/7] dt-bindings: serial: 8250: aspeed: add aspeed,vuart-over-pci bool prop
From: sashiko-bot @ 2026-06-23 14:38 UTC (permalink / raw)
To: Grégoire Layet; +Cc: conor+dt, devicetree, robh
In-Reply-To: <73b2bd81ce70814612e6d3cb689c3296de742aaf.1782224059.git.gregoire.layet@9elements.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Missing `aspeed,ast2600-vuart` in the top-level `compatible` property schema definition, causing `dt_binding_check` failures.
--
commit 33747bb385812ccb653177817f0ed8cba28dbdec
Author: Grégoire Layet <gregoire.layet@9elements.com>
dt-bindings: serial: 8250: aspeed: add aspeed,vuart-over-pci bool prop
The ASPEED AST2600 has 2 VUART accessible over PCI.
This boolean can be set to specify if the VUART is used over PCI.
> diff --git a/Documentation/devicetree/bindings/serial/8250.yaml b/Documentation/devicetree/bindings/serial/8250.yaml
> index 3cbd0f532e151..b03797f4674d4 100644
> --- a/Documentation/devicetree/bindings/serial/8250.yaml
> +++ b/Documentation/devicetree/bindings/serial/8250.yaml
> @@ -26,6 +26,14 @@ allOf:
> anyOf:
> - const: aspeed,ast2500-vuart
> - const: aspeed,ast2600-vuart
> + - if:
> + anyOf:
> + - required:
> + - aspeed,vuart-over-pci
> + then:
> + properties:
> + compatible:
> + const: aspeed,ast2600-vuart
[Severity: Low]
Should aspeed,ast2600-vuart also be explicitly added to the top-level
properties: compatible: oneOf: block?
Documentation/devicetree/bindings/serial/8250.yaml:properties.compatible.oneOf:
properties:
compatible:
oneOf:
- const: ns8250
- const: ns16450
- const: ns16550
- const: ns16550a
- const: ns16850
- const: aspeed,ast2400-vuart
- const: aspeed,ast2500-vuart
Device tree nodes using compatible = "aspeed,ast2600-vuart" will be rejected by
make dt_binding_check because the string is missing from this top-level
definition.
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1782224059.git.gregoire.layet@9elements.com?part=2
^ permalink raw reply
* Re: [PATCH v3 5/7] soc: aspeed: add host-side PCIe BMC device driver
From: sashiko-bot @ 2026-06-23 14:40 UTC (permalink / raw)
To: Grégoire Layet; +Cc: robh, devicetree, conor+dt
In-Reply-To: <8746971524bf4186c9a393dab637ad4e27ce4563.1782224059.git.gregoire.layet@9elements.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Modifying a shared static global array on IRQ allocation fallback permanently corrupts the driver state for all subsequent devices and creates a data race.
- [High] The driver mishandles `pci_alloc_irq_vectors()` return values, leading to silent probe successes with invalid IRQs or out-of-bounds vector accesses that trigger kernel warnings.
- [Medium] Truncation of a 64-bit PCI physical address due to storing it in an `unsigned long` on 32-bit architectures.
--
commit 64de7243e0e8e8cd9ac72d04be5ef937e8143347
Author: Grégoire Layet <gregoire.layet@9elements.com>
soc: aspeed: add host-side PCIe BMC device driver
Add support for VUART over PCIe between BMC and host.
This configures the PCIe and sets up two 8250 compatible VUART ports.
> diff --git a/drivers/soc/aspeed/aspeed-host-bmc-dev.c b/drivers/soc/aspeed/aspeed-host-bmc-dev.c
> new file mode 100644
> index 0000000000000..3160b6aedb5b6
> --- /dev/null
> +++ b/drivers/soc/aspeed/aspeed-host-bmc-dev.c
[ ... ]
> +static int vuart_msi_index[2] = { 16, 17 };
> +static int vuart_port_addr[2] = {0x3f8, 0x2f8};
> +
> +struct aspeed_pci_bmc_dev {
> + struct device *dev;
> + kernel_ulong_t driver_data;
> + int id;
> +
> + unsigned long message_bar_base;
[Severity: Medium]
Does storing the BAR address in an unsigned long truncate 64-bit physical
addresses on 32-bit architectures with PAE/LPAE?
pci_resource_start() returns a resource_size_t, which could be 64-bit,
potentially leading to incorrect I/O memory mapping here.
> +
> + struct uart_8250_port uart[2];
> + int uart_line[2];
> +
> + int *msi_idx_table;
> +};
> +
> +static void aspeed_pci_setup_irq_resource(struct pci_dev *pdev)
> +{
> + struct aspeed_pci_bmc_dev *pci_bmc_dev = pci_get_drvdata(pdev);
> +
> + pci_bmc_dev->msi_idx_table = vuart_msi_index;
> +
> + if (pci_alloc_irq_vectors(pdev, 1, BMC_MULTI_MSI, PCI_IRQ_INTX | PCI_IRQ_MSI) <= 1)
[Severity: High]
Can this condition mishandle partial allocations and negative error codes?
If pci_alloc_irq_vectors() partially succeeds and returns a value between
2 and 16, the <= 1 check evaluates to false. This leaves the indices at 16
and 17, but pci_irq_vector() will later trigger a WARN_ON_ONCE out-of-bounds
check in the PCI core when it tries to access index 16.
Additionally, if it fails entirely and returns a negative error, the check
passes, but the probe doesn't abort. Does this result in silently registering
the 8250 UART with an invalid IRQ?
> + /* If pci_alloc fail, set all msi index to the first vector */
> + memset(pci_bmc_dev->msi_idx_table, 0, sizeof(vuart_msi_index));
[Severity: High]
Does this memset corrupt the driver state for all subsequent instances?
Because pci_bmc_dev->msi_idx_table points directly to the static global array
vuart_msi_index, writing zeroes here permanently overwrites the shared array.
If multiple ASPEED PCI devices are probed, or if the device is probed again
later, will the indices be permanently stuck at 0?
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1782224059.git.gregoire.layet@9elements.com?part=5
^ permalink raw reply
* Re: [PATCH v3 4/7] serial: 8250_aspeed_vuart: add VUART over PCI
From: sashiko-bot @ 2026-06-23 14:41 UTC (permalink / raw)
To: Grégoire Layet; +Cc: robh, conor+dt, devicetree
In-Reply-To: <c3e1136d3565216c254db138e29299c2a3c6499f.1782224059.git.gregoire.layet@9elements.com>
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
New issues:
- [High] The newly added error path directly returns on failure, bypassing sysfs cleanup and leaving the hardware enabled and port registered.
- [Medium] Brittle lookup of the SCU syscon regmap by parsing the "clocks" property.
- [Medium] Setting untested and reserved bits in SCU registers.
Pre-existing issues:
- [High] Preexisting error paths fail to unregister the 8250 serial port and delete the timer on probe failure, leading to a Use-After-Free.
- [High] Unprotected Read-Modify-Write operations on shared hardware registers.
- [High] Sysfs files and TTY port are exposed to userspace before driver data is initialized.
--
commit 277ed031956064f236c5536027c1f37b4a76d71d
Author: Grégoire Layet <gregoire.layet@9elements.com>
serial: 8250_aspeed_vuart: add VUART over PCI
This commit adds support for VUART over PCI on the AST2600 platform. It
configures the BMC PCI device enables, sets the PCI class code to MFD, and
configures MSI interrupts based on the silicon revision.
> diff --git a/drivers/tty/serial/8250/8250_aspeed_vuart.c b/drivers/tty/serial/8250/8250_aspeed_vuart.c
> index 6afa2f4057e1b..e204e26fa173b 100644
> --- a/drivers/tty/serial/8250/8250_aspeed_vuart.c
> +++ b/drivers/tty/serial/8250/8250_aspeed_vuart.c
[ ... ]
> @@ -412,6 +433,62 @@ static int aspeed_vuart_map_irq_polarity(u32 dt)
> +static int aspeed_ast2600_vuart_over_pci_set_enabled(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + u32 silicon_revision_id;
> + struct regmap *scu;
> + int rc;
> +
> + u32 pcie_config_ctl = SCU_PCIE_CONF_BMC_DEV_EN_IRQ |
> + SCU_PCIE_CONF_BMC_DEV_EN_MMIO |
> + SCU_PCIE_CONF_BMC_DEV_EN_MSI |
> + SCU_PCIE_CONF_BMC_DEV_EN_PCIE_BUS_MASTER |
> + SCU_PCIE_CONF_BMC_DEV_EN_E2L |
> + SCU_PCIE_CONF_BMC_DEV_EN_LPC_DECODE |
> + SCU_PCIE_CONF_BMC_DEV_EN;
> +
> + scu = syscon_regmap_lookup_by_phandle(dev->of_node, "clocks");
> + if (IS_ERR(scu)) {
[Severity: Medium]
Will this break if any clock multiplexer or intermediate clock controller is
inserted between the SCU and the VUART?
This code uses syscon_regmap_lookup_by_phandle() to fetch the SCU regmap by
parsing the "clocks" property. While this may happen to work if the SCU is
the direct clock provider, does this conflate the clock provider bindings
with syscon mappings?
> + dev_err(&pdev->dev, "failed to find SCU regmap\n");
> + return PTR_ERR(scu);
> + }
[ ... ]
> + if (silicon_revision_id == AST2600A3_REVISION_ID)
> + rc = regmap_update_bits(scu, ASPEED_SCUC24,
> + ASPEED_SCUC24_PCIDEV1_INTX_MSI_HOST2BMC_EN | ASPEED_SCUC24_MSI_ROUTING_MASK,
> + ASPEED_SCUC24_PCIDEV1_INTX_MSI_HOST2BMC_EN | ASPEED_SCUC24_MSI_ROUTING_PCIe2LPC_PCIDEV1);
> + else
> + rc = regmap_update_bits(scu, ASPEED_SCUC24,
> + /* The bit 14 is reserved in the Datasheet, so we can't say what it does. This revision has not been tested */
> + ASPEED_SCUC24_PCIDEV1_INTX_MSI_SCU560_EN | BIT(14) | ASPEED_SCUC24_MSI_ROUTING_MASK,
[Severity: Medium]
Is it safe to write BIT(14) into ASPEED_SCUC24 when it is undocumented and
reserved?
The comment indicates the bit's purpose is unknown and this revision is
untested. Can setting this undocumented, reserved bit trigger unpredictable
hardware behavior on other silicon revisions?
> + ASPEED_SCUC24_PCIDEV1_INTX_MSI_SCU560_EN | BIT(14) | ASPEED_SCUC24_MSI_ROUTING_PCIe2LPC_PCIDEV1);
> + if (rc) {
> + dev_err(dev, "could not set PCI device 1 MSI interrupt routing\n");
> + return -EIO;
> + }
> +
> + return 0;
> +}
[ ... ]
> @@ -540,6 +617,15 @@ static int aspeed_vuart_probe(struct platform_device *pdev)
> aspeed_vuart_set_host_tx_discard(vuart, true);
[Severity: High]
This isn't a bug introduced by this patch, but can concurrent access
clobber register modifications?
Functions like aspeed_vuart_set_host_tx_discard() and
aspeed_vuart_set_sirq_polarity() perform read-modify-write operations on
ASPEED_VUART_GCRA. Does a concurrent sysfs write and TTY port open/close
cause a race condition here, since these are invoked from contexts with
different locks (or no locks)?
> platform_set_drvdata(pdev, vuart);
[Severity: High]
This isn't a bug introduced by this patch, but does the delay in calling
platform_set_drvdata() expose userspace to a null pointer dereference?
In aspeed_vuart_probe(), sysfs_create_group() is called very early.
If a concurrent userspace process reads the sysfs files, invoking
lpc_address_show() before platform_set_drvdata() is called here, it will
retrieve a null pointer and immediately dereference it.
>
> + if (of_device_is_compatible(dev->of_node, "aspeed,ast2600-vuart") &&
> + of_property_read_bool(dev->of_node, "aspeed,vuart-over-pci")) {
> + rc = aspeed_ast2600_vuart_over_pci_set_enabled(pdev);
> + if (rc) {
> + dev_err(dev, "could not enable VUART over PCI\n");
> + return rc;
[Severity: High]
Does this direct return bypass necessary cleanup logic and leave the
hardware enabled?
By returning rc directly instead of jumping to a cleanup label, the sysfs
group aspeed_vuart_attr_group is leaked. Furthermore, does this bypass
unregistering the 8250 port and leave the VUART active despite probe
failure, since aspeed_vuart_set_enabled(vuart, true) was called earlier?
> + }
> + }
> +
> return 0;
>
> err_sysfs_remove:
> sysfs_remove_group(&vuart->dev->kobj, &aspeed_vuart_attr_group);
> return rc;
> }
[Severity: High]
This isn't a bug introduced by this patch, but does this error path lead
to a use-after-free?
The err_sysfs_remove block cleans up sysfs attributes but entirely omits
calling serial8250_unregister_port() and timer_delete_sync(). Because the
driver uses devres, the vuart structure will be freed upon probe failure.
Since the port remains actively registered, any subsequent access to the
port via TTY operations will dereference this freed memory.
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1782224059.git.gregoire.layet@9elements.com?part=4
^ permalink raw reply
* Re: [PATCH v3 3/7] serial: 8250_aspeed_vuart: add aspeed,ast2600-vuart compatible string
From: sashiko-bot @ 2026-06-23 14:41 UTC (permalink / raw)
To: Grégoire Layet; +Cc: devicetree, conor+dt, robh
In-Reply-To: <56c378923566ea0be831f58fd7c2f186ab3f5cba.1782224059.git.gregoire.layet@9elements.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] Direct return in the newly added AST2600 PCI initialization logic bypasses all resource cleanup.
Pre-existing issues:
- [High] Missing serial port unregistration and timer cleanup on probe error paths causes resource leaks and a Use-After-Free.
- [High] Sysfs attributes are created prematurely in probe() and removed late in remove(), allowing NULL pointer dereference and unmapped memory access.
- [High] Incorrect teardown order in remove() deletes the timer before unregistering the port, allowing the timer to be re-armed.
--
commit 0b81ae92f1a0f52b8cec821070559651477025dd
Author: Grégoire Layet <gregoire.layet@9elements.com>
serial: 8250_aspeed_vuart: add aspeed,ast2600-vuart compatible string
Makes the driver compatible with the ast2600-vuart.
This enables specific configuration for the AST2600.
> diff --git a/drivers/tty/serial/8250/8250_aspeed_vuart.c b/drivers/tty/serial/8250/8250_aspeed_vuart.c
> index 26fc0464f1cc2..6afa2f4057e1b 100644
> --- a/drivers/tty/serial/8250/8250_aspeed_vuart.c
> +++ b/drivers/tty/serial/8250/8250_aspeed_vuart.c
> @@ -560,6 +560,7 @@ static void aspeed_vuart_remove(struct platform_device *pdev)
> static const struct of_device_id aspeed_vuart_table[] = {
> { .compatible = "aspeed,ast2400-vuart" },
> { .compatible = "aspeed,ast2500-vuart" },
> + { .compatible = "aspeed,ast2600-vuart" },
[Severity: High]
Does the AST2600 PCI initialization logic bypass the error handling labels
in aspeed_vuart_probe()?
If aspeed_ast2600_vuart_over_pci_set_enabled() fails, the code returns
directly:
drivers/tty/serial/8250/8250_aspeed_vuart.c:aspeed_vuart_probe() {
...
if (of_device_is_compatible(dev->of_node, "aspeed,ast2600-vuart") &&
of_property_read_bool(dev->of_node, "aspeed,vuart-over-pci")) {
rc = aspeed_ast2600_vuart_over_pci_set_enabled(pdev);
if (rc) {
dev_err(dev, "could not enable VUART over PCI\n");
return rc;
}
}
return 0;
err_sysfs_remove:
sysfs_remove_group(&vuart->dev->kobj, &aspeed_vuart_attr_group);
return rc;
}
This direct return bypasses the err_sysfs_remove cleanup label, which would
leave the sysfs group leaked and the UART port and timer active with
pointers to the devm-freed vuart structure.
[Severity: High]
This isn't a bug introduced by this patch, but does aspeed_vuart_probe()
leak resources and cause a use-after-free on other error paths?
If the probe fails and jumps to err_sysfs_remove, it removes the sysfs
group but fails to call serial8250_unregister_port() or timer_delete_sync():
drivers/tty/serial/8250/8250_aspeed_vuart.c:aspeed_vuart_probe() {
...
rc = serial8250_register_8250_port(&port);
...
err_sysfs_remove:
sysfs_remove_group(&vuart->dev->kobj, &aspeed_vuart_attr_group);
return rc;
}
Because vuart is devm-allocated, it will be freed upon probe failure,
leaving the UART port registered with private_data pointing to freed memory.
[Severity: High]
This is a pre-existing issue, but are the sysfs attributes exposed too
early in probe() and removed too late in remove()?
In aspeed_vuart_probe(), sysfs_create_group() is called before the
driver data is set via platform_set_drvdata():
drivers/tty/serial/8250/8250_aspeed_vuart.c:aspeed_vuart_probe() {
...
rc = sysfs_create_group(&vuart->dev->kobj, &aspeed_vuart_attr_group);
...
platform_set_drvdata(pdev, vuart);
...
}
If userspace reads attributes like lpc_address during this window,
dev_get_drvdata() returns NULL which is then dereferenced in the callback.
Additionally, in aspeed_vuart_remove(), sysfs_remove_group() is called
after serial8250_unregister_port():
drivers/tty/serial/8250/8250_aspeed_vuart.c:aspeed_vuart_remove() {
...
serial8250_unregister_port(vuart->line);
sysfs_remove_group(&vuart->dev->kobj, &aspeed_vuart_attr_group);
}
Because unregistering the port calls iounmap(), any concurrent sysfs access
in this window could access unmapped I/O memory.
[Severity: High]
This is also a pre-existing issue, but does aspeed_vuart_remove() delete
the unthrottle timer before disabling the interrupt source that re-arms it?
drivers/tty/serial/8250/8250_aspeed_vuart.c:aspeed_vuart_remove() {
...
timer_delete_sync(&vuart->unthrottle_timer);
aspeed_vuart_set_enabled(vuart, false);
serial8250_unregister_port(vuart->line);
...
}
At the point timer_delete_sync() is called, the UART port is still active.
If an RX interrupt triggers concurrently, aspeed_vuart_handle_irq() can
call mod_timer() and re-arm it. The re-armed timer could then fire after
the devm-allocated vuart structure is freed.
> { },
> };
> MODULE_DEVICE_TABLE(of, aspeed_vuart_table);
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1782224059.git.gregoire.layet@9elements.com?part=3
^ permalink raw reply
* Re: [PATCH v3 6/7] ARM: dts: aspeed: g6: Change vuart compatible string for ast2600
From: sashiko-bot @ 2026-06-23 14:42 UTC (permalink / raw)
To: Grégoire Layet; +Cc: robh, conor+dt, devicetree
In-Reply-To: <30aedaa9ffd5ba2d763d8802a07b77ef2d5bfcf0.1782224060.git.gregoire.layet@9elements.com>
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
New issues:
- [High] Newly introduced error path completely bypasses all probe cleanup.
- [High] Modifying a static global array when MSI allocation fails causes state corruption.
- [High] Assuming index 16 and 17 are unconditionally valid for partially allocated MSI vectors.
- [Medium] Device Tree Binding Schema Validation Failure
- [Medium] Using the 'clocks' property as a syscon phandle violates DT semantics.
Pre-existing issues:
- [High] Probe error paths fail to unregister the 8250 serial port, leading to Use-After-Free.
--
commit cd4f0040d97e2d9918c8ecf27108ed2de48871b7
Author: Grégoire Layet <gregoire.layet@9elements.com>
ARM: dts: aspeed: g6: Change vuart compatible string for ast2600
Use the ast2600 compatible string.
This makes it more precise and enables specific ast2600 properties.
Still use the ast2500 compatible string as a fallback.
> diff --git a/Documentation/devicetree/bindings/serial/8250.yaml b/Documentation/devicetree/bindings/serial/8250.yaml
> index bb7b9c87a8072..b03797f4674d4 100644
> --- a/Documentation/devicetree/bindings/serial/8250.yaml
> +++ b/Documentation/devicetree/bindings/serial/8250.yaml
> @@ -23,7 +23,17 @@ allOf:
> then:
> properties:
> compatible:
> - const: aspeed,ast2500-vuart
> + anyOf:
> + - const: aspeed,ast2500-vuart
> + - const: aspeed,ast2600-vuart
[ ... ]
> diff --git a/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi b/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
> index 189bc3bbb47c9..5ff52697e8bb4 100644
> --- a/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
> +++ b/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
> @@ -698,7 +698,7 @@ emmc: sdhci@1e750100 {
> };
>
> vuart1: serial@1e787000 {
> - compatible = "aspeed,ast2500-vuart";
> + compatible = "aspeed,ast2600-vuart", "aspeed,ast2500-vuart";
[Severity: Medium]
Will this pass device tree schema validation (dtbs_check)? The binding in
8250.yaml defines the compatible property as anyOf with single const strings,
meaning it expects exactly one of those strings. The device tree uses an array
of two strings. Should the schema be updated with an items array definition to
accept this combination?
[ ... ]
> diff --git a/drivers/soc/aspeed/aspeed-host-bmc-dev.c b/drivers/soc/aspeed/aspeed-host-bmc-dev.c
> new file mode 100644
> index 0000000000000..3160b6aedb5b6
> --- /dev/null
> +++ b/drivers/soc/aspeed/aspeed-host-bmc-dev.c
[ ... ]
> +static int vuart_msi_index[2] = { 16, 17 };
[ ... ]
> +static void aspeed_pci_setup_irq_resource(struct pci_dev *pdev)
> +{
> + struct aspeed_pci_bmc_dev *pci_bmc_dev = pci_get_drvdata(pdev);
> +
> + pci_bmc_dev->msi_idx_table = vuart_msi_index;
> +
> + if (pci_alloc_irq_vectors(pdev, 1, BMC_MULTI_MSI, PCI_IRQ_INTX | PCI_IRQ_MSI) <= 1)
> + /* If pci_alloc fail, set all msi index to the first vector */
> + memset(pci_bmc_dev->msi_idx_table, 0, sizeof(vuart_msi_index));
[Severity: High]
Does this code inadvertently corrupt global state for all driver instances?
Since pci_bmc_dev->msi_idx_table points to vuart_msi_index (a static global
array), calling memset on it will permanently zero out the MSI indices
system-wide if any device fails to allocate multiple MSI vectors.
Also, does the threshold check protect against partial allocations falling
short of index 17?
If pci_alloc_irq_vectors() successfully allocates an intermediate number of
vectors (e.g., 4 or 8), the '<= 1' check evaluates to false. The driver
retains indices 16 and 17 and attempts to fetch them via pci_irq_vector()
below, which triggers an out-of-bounds warning and returns -EINVAL.
[ ... ]
> +static int aspeed_pci_bmc_device_setup_vuart(struct pci_dev *pdev, int idx)
> +{
> + struct aspeed_pci_bmc_dev *pci_bmc_dev = pci_get_drvdata(pdev);
[ ... ]
> + uart->port.irq = pci_irq_vector(pdev, pci_bmc_dev->msi_idx_table[idx]);
[Severity: High]
If a partial allocation leaves us with fewer vectors than the index stored in
msi_idx_table[idx], pci_irq_vector() returns a negative error code like
-EINVAL. Could this error code be incorrectly passed as the IRQ number here,
causing interrupt registration to fail later?
[ ... ]
> diff --git a/drivers/tty/serial/8250/8250_aspeed_vuart.c b/drivers/tty/serial/8250/8250_aspeed_vuart.c
> index 26fc0464f1cc2..e204e26fa173b 100644
> --- a/drivers/tty/serial/8250/8250_aspeed_vuart.c
> +++ b/drivers/tty/serial/8250/8250_aspeed_vuart.c
[ ... ]
> +static int aspeed_ast2600_vuart_over_pci_set_enabled(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
[ ... ]
> + scu = syscon_regmap_lookup_by_phandle(dev->of_node, "clocks");
[Severity: Medium]
Is it semantically correct to use the "clocks" property as a syscon phandle?
The "clocks" property specifies clock providers and specifiers, not raw syscon
phandles. Piggybacking on this to extract a syscon regmap leverages an
implementation detail of syscon_regmap_lookup_by_phandle() and violates
device tree semantics.
[ ... ]
> @@ -540,6 +617,15 @@ static int aspeed_vuart_probe(struct platform_device *pdev)
> aspeed_vuart_set_host_tx_discard(vuart, true);
> platform_set_drvdata(pdev, vuart);
>
> + if (of_device_is_compatible(dev->of_node, "aspeed,ast2600-vuart") &&
> + of_property_read_bool(dev->of_node, "aspeed,vuart-over-pci")) {
> + rc = aspeed_ast2600_vuart_over_pci_set_enabled(pdev);
> + if (rc) {
> + dev_err(dev, "could not enable VUART over PCI\n");
> + return rc;
[Severity: High]
Does returning directly here skip probe cleanup? Bypassing the err_sysfs_remove
label leaves sysfs groups, timers, and the 8250 serial port registered while
devres frees the underlying vuart structure. Does this leak the sysfs groups
and timers, and potentially cause a use-after-free if the 8250 core accesses
the unregistered port?
> + }
> + }
> +
> return 0;
>
> err_sysfs_remove:
> sysfs_remove_group(&vuart->dev->kobj, &aspeed_vuart_attr_group);
> return rc;
[Severity: High]
This isn't a bug introduced by this patch, but does the err_sysfs_remove path
fail to unregister the 8250 port? If probe fails after port registration,
serial8250_unregister_port() is missed. The 8250 core will later dereference
the freed vuart pointer via port.private_data, causing a use-after-free.
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1782224059.git.gregoire.layet@9elements.com?part=6
^ permalink raw reply
* Re: [PATCH v3 7/7] ARM: dts: aspeed: g6: add aspeed,vuart-over-pci prop to vuart3 and 4
From: sashiko-bot @ 2026-06-23 14:44 UTC (permalink / raw)
To: Grégoire Layet; +Cc: robh, devicetree, conor+dt
In-Reply-To: <86fc0b2a105a16ceb301ba53fadd096877455bb8.1782224060.git.gregoire.layet@9elements.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Low] Adding `aspeed,vuart-over-pci` to nodes with multiple compatible strings violates the `const` constraint in the YAML binding, causing a schema validation failure.
Pre-existing issues:
- [High] Probe error paths fail to unregister the 8250 port and remove the sysfs group, resulting in a use-after-free of the devm-allocated driver state and resource leaks.
--
commit 3ce60a66a65e6d307209da21c88e6e5c758ddf74
Author: Grégoire Layet <gregoire.layet@9elements.com>
Date: Tue Jun 23 14:25:45 2026 +0000
ARM: dts: aspeed: g6: add aspeed,vuart-over-pci prop to vuart3 and 4
The VUART 3 and 4 are VUART over PCI.
This flag indicates this information.
> diff --git a/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi b/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
> index 5ff52697e8bb4..b33468288d4a5 100644
> --- a/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
> +++ b/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
> @@ -714,6 +714,7 @@ vuart3: serial@1e787800 {
> interrupts = <GIC_SPI 180 IRQ_TYPE_LEVEL_HIGH>;
> clocks = <&syscon ASPEED_CLK_APB2>;
> no-loopback-test;
> + aspeed,vuart-over-pci;
[Severity: Low]
Does adding this property cause a device tree schema validation failure?
Looking at Documentation/devicetree/bindings/serial/8250.yaml, the schema
restricts the compatible property to a single string when
aspeed,vuart-over-pci is present:
compatible:
const: aspeed,ast2600-vuart
Since the vuart3 node uses multiple compatible strings
("aspeed,ast2600-vuart", "aspeed,ast2500-vuart"), does this violate the
constraint and cause dtbs_check to fail?
[Severity: High]
This isn't a bug introduced by this patch, but adding this property appears
to trigger a pre-existing resource leak and use-after-free in the driver's
error path.
In drivers/tty/serial/8250/8250_aspeed_vuart.c, if
aspeed_ast2600_vuart_over_pci_set_enabled() fails during
aspeed_vuart_probe(), the function returns directly:
rc = aspeed_ast2600_vuart_over_pci_set_enabled(pdev);
if (rc) {
dev_err(dev, "could not enable VUART over PCI\n");
return rc;
}
Does this bypass calling serial8250_unregister_port() and
sysfs_remove_group() after a successful serial8250_register_8250_port()?
Since the devm-allocated driver state is freed on probe failure, does the
UART core retain a registered port pointing to freed memory?
> status = "disabled";
> };
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1782224059.git.gregoire.layet@9elements.com?part=7
^ permalink raw reply
* [RFC 00/12] RFC: Devicetree-ACPI hybrid mode
From: Hans de Goede @ 2026-06-23 14:52 UTC (permalink / raw)
To: Rafael J . Wysocki, Bjorn Andersson, Konrad Dybcio
Cc: Hans de Goede, Srinivas Kandagatla, Krzysztof Kozlowski,
Dmitry Baryshkov, Bartosz Golaszewski, Abel Vesa, linux-arm-msm,
devicetree, linux-acpi
Hi All,
Currently as soon as the kernel boots with a populated DT provided then
the arch/arm64 code sets acpi_disabled=1 and the complete ACPI subsystem
gets disabled. On WoA Snapdragon laptops where the factory Windows OS
actually boots using these tables this is not necessarily desirable.
It might still be interesting to at least parse the ACPI tables and make
the ACPI fwnodes available for device-drivers to use. I call this DT-ACPI
hybrid mode.
This mainly is an experiment for now and possibly a method for accelerating
the ongoing effort to run Linux on currently available Snapdragon laptops.
On current laptops Linux cannot boot using ACPI due to some information
missing from the ACPI tables. People are working on changing this so that
for future WoA Snapdragon laptops Linux can boot using ACPI only without
requiring Devicetree.
There are a couple of scenarios where DT-ACPI hybrid mode is useful:
a) This leads to a populated /sys/firmware/acpi/tables allowing one to run
acpidump, which is useful to grab info from the ACPI tables when e.g.
creating a DT for a new laptop model. As a bonus /sys/firmware/acpi/bgrt
is also populated allowing the boot-splash to show the vendor logo.
b) It might be useful for device-drivers to be able to access ACPI data
for the device even when running in DT mode. E.g. Srini Kandagatla first
got me thinking about this because he wants to use the ACPI MIPI SDCA
tables for audio codec routing when booting Linux on Windows Qualcomm X2
(Glymur) laptops.
c) It is also possible to go truely hybrid and use ACPI to instantiate
some of the kernel device objects representing the hardware. For example
the last patch in this RFC series switches to using ACPI instantiation for
the I2C clients for the keyboard and touchpad on the Snapdragon X1E Lenovo
ThinkPad T14s gen 6.
d) This may help identify shortcomings in the current ACPI tables which
need to be fixed to allow future laptop generations to use ACPI only.
Upstreaming of these patches (to upstream or not to upstream?).
1. The first couple of patches in this series mainly implement a) + b) from
above. This seems like something genuinely useful to have; and except for
missing DT-bindings for hybrid mode this seems mostly ready to go upstream.
2. I see c) as a way to slowly evolve support for current Snapdragon laptops
to use more and more info from ACPI and get closer to a point where we only
need a single DT describing the SoC and any info related to laptop model
specific bits outside of the SoC can be read from the ACPI tables.
As mentioned above work is being done to have Linux boot on future laptop
generations using ACPI only, so all this applies to currently available
Snapdragon laptop generations only.
The question is what to do wrt upstreaming patches necessary for c) though
(patches 7-12) are we going to allow new Devicetree files for not yet
supported laptop models to partially rely on ACPI?
The current demo ACPI usage in this RFC series just instantiates I2C-HID
devices from ACPI. More interesting would be to hookup the embedded
controller (EC) handling in the ACPI tables instead of having to write
a special EC driver for each laptop model separately. For the EC parts
I believe that it might be worthwhile to implement c).
This new DT-ACPI hybrid mode works as follows:
1. A new global ACPI subsys flag called acpi_dt_hybrid is introduced which
can be set to 1 combined with acpi_disabled=1. When this is done, then
despite acpi_disabled being set the ACPI tables will still get parsed and
/sys/firmware/acpi (tables) and /sys/bus/acpi/devices (fwnodes) will still
get populated. No devices will be instantiated, no fwnodes will get
attached to any other (e.g. PCI, USB) devices and no other actions will
be taken.
2. Add acpi=hybrid kernel-commandline option to the arch/arm64 code.
TODO: Add a DT-binding for selecting hybrid mode from Devicetree.
3. drivers/acpi/glue.c changes to support devices with an of_node as
primary fwnode having a "acpi-path" string property and when present look
up the ACPI fwnode for that path, e.g. "\\_SB.GIO0", "\\_SB.I2C1" and
set that fwnode as secondary node.
4. pinctrl-msm changes to map special WoA ACPI table virtual GPIO numbers
for PDC pins back to regular TLMM GPIO numbers.
5. Tiny drivers/i2c/i2c-core-acpi.c change to make it instantiate ACPI
described I2C clients under a DT instantiated I2C adapter if that
adapter has an ACPI secondary fwnode.
With this I can drop the DT description of the ThinkPad T14s gen 6
keyboard and touchpad and instead have these be instantiated by ACPI,
as shown in patch 12/12.
Comments, thoughts ?
Regards,
Hans
Hans de Goede (12):
ACPI: Introduce DT-ACPI hybrid mode
arm64: acpi: Cleanup acpi=[on|off|force] handling
arm64: acpi: add acpi=hybrid support
ACPI: Add helpers for dealing with ACPI fwnode as secondary fwnode
ACPI: glue: Implement setting secondary-fwnode for DT-ACPI hybrid mode
ACPI: scan: Retry acpi_device_notify() in DT-ACPI hybrid mode
ACPI: Make device_match_acpi_handle() also check the secondary fwnode
irqchip/gic-v3: Always call acpi_set_irq_model()
pinctrl: qcom: Add support for WoA ACPI tables virtual TLMM pin
numbers
i2c: acpi: Also register ACPI i2c_clients for adapters with a
secondary ACPI fwnode
i2c: qcom-geni: Fall back to i2c_acpi_find_bus_speed()
arm64: dts: qcom: x1e78100-thinkpad-t14s: Move keyb and touchpad to
ACPI enumeration
.../qcom/x1e78100-lenovo-thinkpad-t14s.dtsi | 61 +-----
arch/arm64/include/asm/acpi.h | 1 +
arch/arm64/kernel/acpi.c | 47 +++--
arch/loongarch/include/asm/acpi.h | 1 +
arch/riscv/include/asm/acpi.h | 3 +-
arch/x86/include/asm/acpi.h | 1 +
drivers/acpi/acpi_apd.c | 3 +
drivers/acpi/acpi_memhotplug.c | 3 +
drivers/acpi/acpi_processor.c | 3 +
drivers/acpi/arm64/init.c | 2 +
drivers/acpi/bus.c | 6 +-
drivers/acpi/glue.c | 70 ++++++-
drivers/acpi/property.c | 13 ++
drivers/acpi/scan.c | 33 +++
drivers/acpi/tables.c | 4 +-
drivers/base/core.c | 4 +-
drivers/firmware/efi/efi-bgrt.c | 2 +-
drivers/i2c/busses/i2c-qcom-geni.c | 10 +-
drivers/i2c/i2c-core-acpi.c | 8 +-
drivers/irqchip/irq-gic-v3.c | 29 ++-
drivers/pinctrl/qcom/Makefile | 4 +-
drivers/pinctrl/qcom/pinctrl-msm-acpi.c | 196 ++++++++++++++++++
drivers/pinctrl/qcom/pinctrl-msm.c | 47 ++++-
drivers/pinctrl/qcom/pinctrl-msm.h | 35 ++++
include/acpi/acpi_bus.h | 16 ++
include/linux/acpi.h | 10 +
26 files changed, 502 insertions(+), 110 deletions(-)
create mode 100644 drivers/pinctrl/qcom/pinctrl-msm-acpi.c
--
2.54.0
^ permalink raw reply
* [RFC 01/12] ACPI: Introduce DT-ACPI hybrid mode
From: Hans de Goede @ 2026-06-23 14:52 UTC (permalink / raw)
To: Rafael J . Wysocki, Bjorn Andersson, Konrad Dybcio
Cc: Hans de Goede, Srinivas Kandagatla, Krzysztof Kozlowski,
Dmitry Baryshkov, Bartosz Golaszewski, Abel Vesa, linux-arm-msm,
devicetree, linux-acpi
In-Reply-To: <20260623145225.143218-1-johannes.goede@oss.qualcomm.com>
ATM on platforms which support DT or ACPI as firmware interfaces ACPI gets
fully disabled when DT is in use.
In some cases it is interesting to at least parse the ACPI tables and
possibly also use parts of them.
One specific example of this is Windows on Arm laptops with Qualcomm
Snapdragon processors where Windows boots using information from the ACPI
tables but Linux uses a device-tree description of the hardware instead.
Since Windows is the factory OS, these DT descriptions need to be created
by hand.
Having the ACPI tables available at runtime allows the kernel to use some
information from these ACPI tables while still using device-tree as its
main information source.
Having the tables available at runtime can also help with creating and
improving these DT descriptions.
This patch prepares for a new DT-ACPI hybrid mode controlled by a new
acpi_dt_hybrid flag. When enabled this makes the kernel booting in DT
mode still load and parse the ACPI tables, without taking any further
actions like creating devices.
In this case the ACPI subsys will still parse ACPI tables and populate
/sys/firmware/acpi and /sys/bus/acpi/devices.
Note this patch itself is a no-op, since all the arch/*/include/asm/acpi.h
files define the new acpi_dt_hybrid variable to 0. All the added extra
"if (acpi_disabled)" checks are in paths which already check for this
earlier in the code-path. The new checks only come into play when
acpi_dt_hybrid is set to 1.
Signed-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
---
arch/arm64/include/asm/acpi.h | 1 +
arch/loongarch/include/asm/acpi.h | 1 +
arch/riscv/include/asm/acpi.h | 3 ++-
arch/x86/include/asm/acpi.h | 1 +
drivers/acpi/acpi_apd.c | 3 +++
drivers/acpi/acpi_memhotplug.c | 3 +++
drivers/acpi/acpi_processor.c | 3 +++
drivers/acpi/arm64/init.c | 2 ++
drivers/acpi/bus.c | 6 +++---
drivers/acpi/scan.c | 4 ++++
drivers/acpi/tables.c | 4 ++--
drivers/firmware/efi/efi-bgrt.c | 2 +-
12 files changed, 26 insertions(+), 7 deletions(-)
diff --git a/arch/arm64/include/asm/acpi.h b/arch/arm64/include/asm/acpi.h
index 8a54ca6ba602..3116bb872f47 100644
--- a/arch/arm64/include/asm/acpi.h
+++ b/arch/arm64/include/asm/acpi.h
@@ -76,6 +76,7 @@ typedef u64 phys_cpuid_t;
#define PHYS_CPUID_INVALID INVALID_HWID
#define acpi_strict 1 /* No out-of-spec workarounds on ARM64 */
+#define acpi_dt_hybrid 0 /* No DT-ACPI hybrid mode on ARM64 */
extern int acpi_disabled;
extern int acpi_noirq;
extern int acpi_pci_disabled;
diff --git a/arch/loongarch/include/asm/acpi.h b/arch/loongarch/include/asm/acpi.h
index eda9d4d0a493..5f725bba6e87 100644
--- a/arch/loongarch/include/asm/acpi.h
+++ b/arch/loongarch/include/asm/acpi.h
@@ -12,6 +12,7 @@
#include <asm/suspend.h>
#ifdef CONFIG_ACPI
+#define acpi_dt_hybrid 0 /* No DT-ACPI hybrid mode on loongarch */
extern int acpi_strict;
extern int acpi_disabled;
extern int acpi_pci_disabled;
diff --git a/arch/riscv/include/asm/acpi.h b/arch/riscv/include/asm/acpi.h
index 26ab37c171bc..b72586fe650e 100644
--- a/arch/riscv/include/asm/acpi.h
+++ b/arch/riscv/include/asm/acpi.h
@@ -22,7 +22,8 @@ typedef u64 phys_cpuid_t;
void __iomem *acpi_os_ioremap(acpi_physical_address phys, acpi_size size);
#define acpi_os_ioremap acpi_os_ioremap
-#define acpi_strict 1 /* No out-of-spec workarounds on RISC-V */
+#define acpi_strict 1 /* No out-of-spec workarounds on RISC-V */
+#define acpi_dt_hybrid 0 /* No DT-ACPI hybrid mode on RISC-V */
extern int acpi_disabled;
extern int acpi_noirq;
extern int acpi_pci_disabled;
diff --git a/arch/x86/include/asm/acpi.h b/arch/x86/include/asm/acpi.h
index a03aa6f999d1..ae1d3d588c06 100644
--- a/arch/x86/include/asm/acpi.h
+++ b/arch/x86/include/asm/acpi.h
@@ -25,6 +25,7 @@
#endif
#ifdef CONFIG_ACPI
+#define acpi_dt_hybrid 0 /* No DT-ACPI hybrid mode on x86 */
extern int acpi_lapic;
extern int acpi_ioapic;
extern int acpi_noirq;
diff --git a/drivers/acpi/acpi_apd.c b/drivers/acpi/acpi_apd.c
index bed0791c17fc..0f9e22046e0a 100644
--- a/drivers/acpi/acpi_apd.c
+++ b/drivers/acpi/acpi_apd.c
@@ -263,5 +263,8 @@ static struct acpi_scan_handler apd_handler = {
void __init acpi_apd_init(void)
{
+ if (acpi_disabled)
+ return;
+
acpi_scan_add_handler(&apd_handler);
}
diff --git a/drivers/acpi/acpi_memhotplug.c b/drivers/acpi/acpi_memhotplug.c
index 1d7dfe4ee9a6..00469a4b8897 100644
--- a/drivers/acpi/acpi_memhotplug.c
+++ b/drivers/acpi/acpi_memhotplug.c
@@ -361,6 +361,9 @@ static struct acpi_scan_handler memory_device_handler = {
void __init acpi_memory_hotplug_init(void)
{
+ if (acpi_disabled)
+ return;
+
acpi_scan_add_handler(&memory_device_handler);
}
diff --git a/drivers/acpi/acpi_processor.c b/drivers/acpi/acpi_processor.c
index 00775b91bd41..f4ddd5e126e3 100644
--- a/drivers/acpi/acpi_processor.c
+++ b/drivers/acpi/acpi_processor.c
@@ -785,6 +785,9 @@ bool acpi_duplicate_processor_id(int proc_id)
void __init acpi_processor_init(void)
{
+ if (acpi_disabled)
+ return;
+
acpi_processor_check_duplicates();
acpi_scan_add_handler_with_hotplug(&processor_handler, "processor");
acpi_scan_add_handler(&processor_container_handler);
diff --git a/drivers/acpi/arm64/init.c b/drivers/acpi/arm64/init.c
index 7a47d8095a7d..9826c0b35715 100644
--- a/drivers/acpi/arm64/init.c
+++ b/drivers/acpi/arm64/init.c
@@ -4,6 +4,8 @@
void __init acpi_arch_init(void)
{
+ if (acpi_disabled)
+ return;
if (IS_ENABLED(CONFIG_ACPI_AGDI))
acpi_agdi_init();
if (IS_ENABLED(CONFIG_ACPI_APMT))
diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c
index 2ec095e2009e..1d5ea66cb99e 100644
--- a/drivers/acpi/bus.c
+++ b/drivers/acpi/bus.c
@@ -1290,7 +1290,7 @@ void __init acpi_early_init(void)
{
acpi_status status;
- if (acpi_disabled)
+ if (acpi_disabled && !acpi_dt_hybrid)
return;
pr_info("Core revision %08x\n", ACPI_CA_VERSION);
@@ -1361,7 +1361,7 @@ void __init acpi_subsystem_init(void)
{
acpi_status status;
- if (acpi_disabled)
+ if (acpi_disabled && !acpi_dt_hybrid)
return;
status = acpi_enable_subsystem(~ACPI_NO_ACPI_ENABLE);
@@ -1494,7 +1494,7 @@ static int __init acpi_init(void)
{
int result;
- if (acpi_disabled) {
+ if (acpi_disabled && !acpi_dt_hybrid) {
pr_info("Interpreter disabled.\n");
return -ENODEV;
}
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c
index 530547cda8b2..4836286968e8 100644
--- a/drivers/acpi/scan.c
+++ b/drivers/acpi/scan.c
@@ -2253,6 +2253,10 @@ static void acpi_default_enumeration(struct acpi_device *device)
ACPI_RECONFIG_DEVICE_ADD, device);
return;
}
+
+ if (acpi_dt_hybrid)
+ return;
+
if (match_string(acpi_system_dev_ids, -1, acpi_device_hid(device)) >= 0) {
struct acpi_scan_system_dev *sd;
diff --git a/drivers/acpi/tables.c b/drivers/acpi/tables.c
index 4286e4af1092..6aec547c0872 100644
--- a/drivers/acpi/tables.c
+++ b/drivers/acpi/tables.c
@@ -247,7 +247,7 @@ int __init_or_acpilib acpi_table_parse_entries_array(
int count;
u32 instance = 0;
- if (acpi_disabled)
+ if (acpi_disabled && !acpi_dt_hybrid)
return -ENODEV;
if (!id)
@@ -330,7 +330,7 @@ int __init acpi_table_parse(char *id, acpi_tbl_table_handler handler)
{
struct acpi_table_header *table = NULL;
- if (acpi_disabled)
+ if (acpi_disabled && !acpi_dt_hybrid)
return -ENODEV;
if (!id || !handler)
diff --git a/drivers/firmware/efi/efi-bgrt.c b/drivers/firmware/efi/efi-bgrt.c
index 1da451582812..dc69342cdb38 100644
--- a/drivers/firmware/efi/efi-bgrt.c
+++ b/drivers/firmware/efi/efi-bgrt.c
@@ -31,7 +31,7 @@ void __init efi_bgrt_init(struct acpi_table_header *table)
struct acpi_table_bgrt *bgrt = &bgrt_tab;
int mem_type;
- if (acpi_disabled)
+ if (acpi_disabled && !acpi_dt_hybrid)
return;
if (!efi_enabled(EFI_MEMMAP) && !efi_enabled(EFI_PARAVIRT))
--
2.54.0
^ permalink raw reply related
* [RFC 02/12] arm64: acpi: Cleanup acpi=[on|off|force] handling
From: Hans de Goede @ 2026-06-23 14:52 UTC (permalink / raw)
To: Rafael J . Wysocki, Bjorn Andersson, Konrad Dybcio
Cc: Hans de Goede, Srinivas Kandagatla, Krzysztof Kozlowski,
Dmitry Baryshkov, Bartosz Golaszewski, Abel Vesa, linux-arm-msm,
devicetree, linux-acpi
In-Reply-To: <20260623145225.143218-1-johannes.goede@oss.qualcomm.com>
acpi=[on|off|force] are 3 values of a single "should ACPI be enabled"
setting. Instead of storing these in 3 separate booleans, introduce a new
acpi_mode_t enum for this and store these in a single param varialable.
This is a preparation patch for adding acpi=hybrid support to enable the
new DT-ACPI hybrid mode, which makes the kernel booting in DT mode still
load and parse the ACPI tables, without taking any further actions.
Signed-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
---
arch/arm64/kernel/acpi.c | 33 +++++++++++++++++++--------------
1 file changed, 19 insertions(+), 14 deletions(-)
diff --git a/arch/arm64/kernel/acpi.c b/arch/arm64/kernel/acpi.c
index 5891f92c2035..06ab3a9da64b 100644
--- a/arch/arm64/kernel/acpi.c
+++ b/arch/arm64/kernel/acpi.c
@@ -43,9 +43,14 @@ EXPORT_SYMBOL(acpi_disabled);
int acpi_pci_disabled = 1; /* skip ACPI PCI scan and IRQ initialization */
EXPORT_SYMBOL(acpi_pci_disabled);
-static bool param_acpi_off __initdata;
-static bool param_acpi_on __initdata;
-static bool param_acpi_force __initdata;
+enum acpi_mode_t {
+ acpi_mode_unset,
+ acpi_mode_off,
+ acpi_mode_on,
+ acpi_mode_force,
+};
+
+static enum acpi_mode_t param_acpi_mode __initdata;
static bool param_acpi_nospcr __initdata;
static int __init parse_acpi(char *arg)
@@ -55,11 +60,11 @@ static int __init parse_acpi(char *arg)
/* "acpi=off" disables both ACPI table parsing and interpreter */
if (strcmp(arg, "off") == 0)
- param_acpi_off = true;
+ param_acpi_mode = acpi_mode_off;
else if (strcmp(arg, "on") == 0) /* prefer ACPI over DT */
- param_acpi_on = true;
+ param_acpi_mode = acpi_mode_on;
else if (strcmp(arg, "force") == 0) /* force ACPI to be enabled */
- param_acpi_force = true;
+ param_acpi_mode = acpi_mode_force;
else if (strcmp(arg, "nospcr") == 0) /* disable SPCR as default console */
param_acpi_nospcr = true;
else
@@ -198,14 +203,14 @@ static int __init acpi_fadt_sanity_check(void)
void __init acpi_boot_table_init(void)
{
/*
- * Enable ACPI instead of device tree unless
- * - ACPI has been disabled explicitly (acpi=off), or
- * - the device tree is not empty (it has more than just a /chosen node,
- * and a /hypervisor node when running on Xen)
- * and ACPI has not been [force] enabled (acpi=on|force)
+ * When no ACPI mode (acpi=off|on|force) has been specified,
+ * enable ACPI if the device tree is empty (it only has a /chosen
+ * node, and a /hypervisor node when running on Xen).
*/
- if (param_acpi_off ||
- (!param_acpi_on && !param_acpi_force && !dt_is_stub()))
+ if (!param_acpi_mode)
+ param_acpi_mode = dt_is_stub() ? acpi_mode_on : acpi_mode_off;
+
+ if (param_acpi_mode == acpi_mode_off)
goto done;
/*
@@ -223,7 +228,7 @@ void __init acpi_boot_table_init(void)
*/
if (acpi_table_init() || acpi_fadt_sanity_check()) {
pr_err("Failed to init ACPI tables\n");
- if (!param_acpi_force)
+ if (param_acpi_mode != acpi_mode_force)
disable_acpi();
}
--
2.54.0
^ permalink raw reply related
* [RFC 03/12] arm64: acpi: add acpi=hybrid support
From: Hans de Goede @ 2026-06-23 14:52 UTC (permalink / raw)
To: Rafael J . Wysocki, Bjorn Andersson, Konrad Dybcio
Cc: Hans de Goede, Srinivas Kandagatla, Krzysztof Kozlowski,
Dmitry Baryshkov, Bartosz Golaszewski, Abel Vesa, linux-arm-msm,
devicetree, linux-acpi
In-Reply-To: <20260623145225.143218-1-johannes.goede@oss.qualcomm.com>
By default when a non stub DT is given to the kernel when booting, ACPI
gets fully disabled.
Add support for selecting a new APCI-DT hybrid mode where the kernel
booting in DT mode still loads and parses the ACPI tables, without taking
any further actions like creating devices.
This will parse ACPI tables and populate /sys/firmware/acpi and
/sys/bus/acpi/devices, which can be useful to e.g. get an acpidump .
Signed-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
---
arch/arm64/include/asm/acpi.h | 2 +-
arch/arm64/kernel/acpi.c | 16 ++++++++++++----
2 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/arch/arm64/include/asm/acpi.h b/arch/arm64/include/asm/acpi.h
index 3116bb872f47..8f8a0b9e49c3 100644
--- a/arch/arm64/include/asm/acpi.h
+++ b/arch/arm64/include/asm/acpi.h
@@ -76,10 +76,10 @@ typedef u64 phys_cpuid_t;
#define PHYS_CPUID_INVALID INVALID_HWID
#define acpi_strict 1 /* No out-of-spec workarounds on ARM64 */
-#define acpi_dt_hybrid 0 /* No DT-ACPI hybrid mode on ARM64 */
extern int acpi_disabled;
extern int acpi_noirq;
extern int acpi_pci_disabled;
+extern int acpi_dt_hybrid;
static inline void disable_acpi(void)
{
diff --git a/arch/arm64/kernel/acpi.c b/arch/arm64/kernel/acpi.c
index 06ab3a9da64b..9d2c42375afe 100644
--- a/arch/arm64/kernel/acpi.c
+++ b/arch/arm64/kernel/acpi.c
@@ -43,11 +43,14 @@ EXPORT_SYMBOL(acpi_disabled);
int acpi_pci_disabled = 1; /* skip ACPI PCI scan and IRQ initialization */
EXPORT_SYMBOL(acpi_pci_disabled);
+int acpi_dt_hybrid; /* disable ACPI-DT hybrid mode */
+
enum acpi_mode_t {
acpi_mode_unset,
acpi_mode_off,
acpi_mode_on,
acpi_mode_force,
+ acpi_mode_hybrid,
};
static enum acpi_mode_t param_acpi_mode __initdata;
@@ -65,6 +68,8 @@ static int __init parse_acpi(char *arg)
param_acpi_mode = acpi_mode_on;
else if (strcmp(arg, "force") == 0) /* force ACPI to be enabled */
param_acpi_mode = acpi_mode_force;
+ else if (strcmp(arg, "hybrid") == 0) /* ACPI-DT hybrid mode */
+ param_acpi_mode = acpi_mode_hybrid;
else if (strcmp(arg, "nospcr") == 0) /* disable SPCR as default console */
param_acpi_nospcr = true;
else
@@ -203,7 +208,7 @@ static int __init acpi_fadt_sanity_check(void)
void __init acpi_boot_table_init(void)
{
/*
- * When no ACPI mode (acpi=off|on|force) has been specified,
+ * When no ACPI mode (acpi=off|on|force|hybrid) has been specified,
* enable ACPI if the device tree is empty (it only has a /chosen
* node, and a /hypervisor node when running on Xen).
*/
@@ -230,6 +235,9 @@ void __init acpi_boot_table_init(void)
pr_err("Failed to init ACPI tables\n");
if (param_acpi_mode != acpi_mode_force)
disable_acpi();
+ } else if (param_acpi_mode == acpi_mode_hybrid) {
+ acpi_dt_hybrid = 1;
+ disable_acpi();
}
done:
@@ -257,10 +265,10 @@ void __init acpi_boot_table_init(void)
*/
acpi_parse_spcr(earlycon_acpi_spcr_enable,
!param_acpi_nospcr);
-
- if (IS_ENABLED(CONFIG_ACPI_BGRT))
- acpi_table_parse(ACPI_SIG_BGRT, acpi_parse_bgrt);
}
+
+ if ((!acpi_disabled || acpi_dt_hybrid) && IS_ENABLED(CONFIG_ACPI_BGRT))
+ acpi_table_parse(ACPI_SIG_BGRT, acpi_parse_bgrt);
}
static pgprot_t __acpi_get_writethrough_mem_attribute(void)
--
2.54.0
^ permalink raw reply related
* [RFC 04/12] ACPI: Add helpers for dealing with ACPI fwnode as secondary fwnode
From: Hans de Goede @ 2026-06-23 14:52 UTC (permalink / raw)
To: Rafael J . Wysocki, Bjorn Andersson, Konrad Dybcio
Cc: Hans de Goede, Srinivas Kandagatla, Krzysztof Kozlowski,
Dmitry Baryshkov, Bartosz Golaszewski, Abel Vesa, linux-arm-msm,
devicetree, linux-acpi
In-Reply-To: <20260623145225.143218-1-johannes.goede@oss.qualcomm.com>
In DT-ACPI hybrid mode in some cases code calling is_acpi_device_node() or
to_acpi_device_node() may also want the secondary fwnode to be used.
DT-ACPI hybrid mode is something which subsystems / drivers should opt-in
to, so it is undesirable to make existing helpers use the secondary fwnode.
Add new is_acpi_device_node_any() and to_acpi_device_node_any() helpers
which also use the secondary fwnode.
Signed-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
---
drivers/acpi/property.c | 13 +++++++++++++
include/acpi/acpi_bus.h | 16 ++++++++++++++++
include/linux/acpi.h | 10 ++++++++++
3 files changed, 39 insertions(+)
diff --git a/drivers/acpi/property.c b/drivers/acpi/property.c
index 8ee5a1f0eb48..fb555e50ce83 100644
--- a/drivers/acpi/property.c
+++ b/drivers/acpi/property.c
@@ -1775,6 +1775,19 @@ bool is_acpi_device_node(const struct fwnode_handle *fwnode)
}
EXPORT_SYMBOL(is_acpi_device_node);
+/* Like is_acpi_device_node() but also check the secondary fwnode */
+bool is_acpi_device_node_any(const struct fwnode_handle *fwnode)
+{
+ if (IS_ERR_OR_NULL(fwnode))
+ return false;
+
+ if (fwnode->ops == &acpi_device_fwnode_ops)
+ return true;
+
+ return is_acpi_device_node(fwnode->secondary);
+}
+EXPORT_SYMBOL(is_acpi_device_node_any);
+
bool is_acpi_data_node(const struct fwnode_handle *fwnode)
{
return !IS_ERR_OR_NULL(fwnode) && fwnode->ops == &acpi_data_fwnode_ops;
diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h
index c41d9a7565cf..bfc3883765f1 100644
--- a/include/acpi/acpi_bus.h
+++ b/include/acpi/acpi_bus.h
@@ -515,6 +515,7 @@ extern const struct fwnode_operations acpi_data_fwnode_ops;
extern const struct fwnode_operations acpi_static_fwnode_ops;
bool is_acpi_device_node(const struct fwnode_handle *fwnode);
+bool is_acpi_device_node_any(const struct fwnode_handle *fwnode);
bool is_acpi_data_node(const struct fwnode_handle *fwnode);
static inline bool is_acpi_node(const struct fwnode_handle *fwnode)
@@ -532,6 +533,21 @@ static inline bool is_acpi_node(const struct fwnode_handle *fwnode)
NULL; \
})
+/* Like to_acpi_device_node() but also check the secondary fwnode */
+#define to_acpi_device_node_any(__fwnode) \
+ ({ \
+ typeof(__fwnode) __to_acpi_device_node_fwnode = __fwnode; \
+ \
+ IS_ERR_OR_NULL(__to_acpi_device_node_fwnode) ? NULL : \
+ is_acpi_device_node(__to_acpi_device_node_fwnode) ? \
+ container_of(__to_acpi_device_node_fwnode, \
+ struct acpi_device, fwnode) : \
+ is_acpi_device_node(__to_acpi_device_node_fwnode->secondary) ? \
+ container_of(__to_acpi_device_node_fwnode->secondary, \
+ struct acpi_device, fwnode) : \
+ NULL; \
+ })
+
#define to_acpi_data_node(__fwnode) \
({ \
typeof(__fwnode) __to_acpi_data_node_fwnode = __fwnode; \
diff --git a/include/linux/acpi.h b/include/linux/acpi.h
index 67effb91fa98..bb84774f61df 100644
--- a/include/linux/acpi.h
+++ b/include/linux/acpi.h
@@ -865,11 +865,21 @@ static inline bool is_acpi_device_node(const struct fwnode_handle *fwnode)
return false;
}
+static inline bool is_acpi_device_node_any(const struct fwnode_handle *fwnode)
+{
+ return false;
+}
+
static inline struct acpi_device *to_acpi_device_node(const struct fwnode_handle *fwnode)
{
return NULL;
}
+static inline struct acpi_device *to_acpi_device_node_any(const struct fwnode_handle *fwnode)
+{
+ return NULL;
+}
+
static inline bool is_acpi_data_node(const struct fwnode_handle *fwnode)
{
return false;
--
2.54.0
^ permalink raw reply related
* [RFC 05/12] ACPI: glue: Implement setting secondary-fwnode for DT-ACPI hybrid mode
From: Hans de Goede @ 2026-06-23 14:52 UTC (permalink / raw)
To: Rafael J . Wysocki, Bjorn Andersson, Konrad Dybcio
Cc: Hans de Goede, Srinivas Kandagatla, Krzysztof Kozlowski,
Dmitry Baryshkov, Bartosz Golaszewski, Abel Vesa, linux-arm-msm,
devicetree, linux-acpi
In-Reply-To: <20260623145225.143218-1-johannes.goede@oss.qualcomm.com>
In DT-ACPI hybrid mode devices are instantiated from device tree and
a device's primary firmware-node will normally be an of fwnode.
Add support to the ACPI glue code to check the new "acpi-path" DT property
and when this is set lookup the handle for the specified path and set
the secondary-fwnode of the device to point to the handle's ACPI fwnode.
Also create a firmware_node and physical_node links/entries as normal.
When e.g. an "acpi-path" property is specified for e.g. an i2c-adapter
then the normal ACPI i2c-client enumeration will happen for any ACPI
fwnodes with an I2CSerialBus resource pointing to the i2c-adapter, these
ACPI instantiated i2c_clients will have an ACPI fwnode as primary fwnode.
The normal acpi_device_notify[_remove]() paths will be used for these.
Signed-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
---
drivers/acpi/glue.c | 70 ++++++++++++++++++++++++++++++++++++++-------
1 file changed, 60 insertions(+), 10 deletions(-)
diff --git a/drivers/acpi/glue.c b/drivers/acpi/glue.c
index b1776809279d..fef450d5cb7d 100644
--- a/drivers/acpi/glue.c
+++ b/drivers/acpi/glue.c
@@ -225,6 +225,15 @@ static void acpi_physnode_link_name(char *buf, unsigned int node_id)
strcpy(buf, PHYSICAL_NODE_STRING);
}
+/* Like ACPI_COMPANION_SET() but set secondary fwnode if primary is an of_node */
+static void acpi_companion_set_any(struct device *dev, struct acpi_device *adev)
+{
+ if (is_of_node(dev->fwnode))
+ set_secondary_fwnode(dev, adev ? acpi_fwnode_handle(adev) : NULL);
+ else
+ set_primary_fwnode(dev, adev ? acpi_fwnode_handle(adev) : NULL);
+}
+
int acpi_bind_one(struct device *dev, struct acpi_device *acpi_dev)
{
struct acpi_device_physical_node *physical_node, *pn;
@@ -233,12 +242,12 @@ int acpi_bind_one(struct device *dev, struct acpi_device *acpi_dev)
unsigned int node_id;
int retval = -EINVAL;
- if (has_acpi_companion(dev)) {
+ if (is_acpi_device_node_any(dev->fwnode)) {
if (acpi_dev) {
dev_warn(dev, "ACPI companion already set\n");
return -EINVAL;
} else {
- acpi_dev = ACPI_COMPANION(dev);
+ acpi_dev = to_acpi_device_node_any(dev->fwnode);
}
}
if (!acpi_dev)
@@ -267,7 +276,7 @@ int acpi_bind_one(struct device *dev, struct acpi_device *acpi_dev)
dev_warn(dev, "Already associated with ACPI node\n");
kfree(physical_node);
- if (ACPI_COMPANION(dev) != acpi_dev)
+ if (to_acpi_device_node_any(dev->fwnode) != acpi_dev)
goto err;
put_device(dev);
@@ -285,8 +294,8 @@ int acpi_bind_one(struct device *dev, struct acpi_device *acpi_dev)
list_add(&physical_node->node, physnode_list);
acpi_dev->physical_node_count++;
- if (!has_acpi_companion(dev))
- ACPI_COMPANION_SET(dev, acpi_dev);
+ if (!is_acpi_device_node_any(dev->fwnode))
+ acpi_companion_set_any(dev, acpi_dev);
acpi_physnode_link_name(physical_node_name, node_id);
retval = sysfs_create_link(&acpi_dev->dev.kobj, &dev->kobj,
@@ -303,13 +312,14 @@ int acpi_bind_one(struct device *dev, struct acpi_device *acpi_dev)
mutex_unlock(&acpi_dev->physical_node_lock);
- if (acpi_dev->wakeup.flags.valid)
+ /* Don't set wakeup flag for devices where ACPI fwnode is secondary */
+ if (acpi_dev->wakeup.flags.valid && has_acpi_companion(dev))
device_set_wakeup_capable(dev, true);
return 0;
err:
- ACPI_COMPANION_SET(dev, NULL);
+ acpi_companion_set_any(dev, NULL);
put_device(dev);
acpi_dev_put(acpi_dev);
return retval;
@@ -318,7 +328,7 @@ EXPORT_SYMBOL_GPL(acpi_bind_one);
int acpi_unbind_one(struct device *dev)
{
- struct acpi_device *acpi_dev = ACPI_COMPANION(dev);
+ struct acpi_device *acpi_dev = to_acpi_device_node_any(dev->fwnode);
struct acpi_device_physical_node *entry;
if (!acpi_dev)
@@ -336,7 +346,7 @@ int acpi_unbind_one(struct device *dev)
acpi_physnode_link_name(physnode_name, entry->node_id);
sysfs_remove_link(&acpi_dev->dev.kobj, physnode_name);
sysfs_remove_link(&dev->kobj, "firmware_node");
- ACPI_COMPANION_SET(dev, NULL);
+ acpi_companion_set_any(dev, NULL);
/* Drop references taken by acpi_bind_one(). */
put_device(dev);
acpi_dev_put(acpi_dev);
@@ -354,6 +364,40 @@ void acpi_device_notify(struct device *dev)
struct acpi_device *adev;
int ret;
+ if (acpi_dt_hybrid && is_of_node(dev->fwnode)) {
+ const char *acpi_path;
+ acpi_status status;
+ acpi_handle handle;
+
+ ret = of_property_read_string(dev->of_node, "acpi-path", &acpi_path);
+ if (ret)
+ return;
+
+ status = acpi_get_handle(NULL, acpi_path, &handle);
+ if (ACPI_FAILURE(status))
+ goto err_hybrid;
+
+ adev = acpi_fetch_acpi_dev(handle);
+ if (!adev)
+ goto err_hybrid;
+
+ /*
+ * set_secondary_fwnode() + pass NULL to make this work for
+ * child devices which share the fwnode with their parent.
+ */
+ set_secondary_fwnode(dev, acpi_fwnode_handle(adev));
+ ret = acpi_bind_one(dev, NULL);
+ if (ret)
+ goto err_hybrid;
+
+ /* TODO change to dev_dbg() when DT-ACPI hybrid support is stable */
+ dev_info(dev, "Set secondary fwnode to acpi-path '%s'\n", acpi_path);
+ return;
+err_hybrid:
+ dev_warn(dev, "Failed to set ACPI fwnode for acpi-path '%s'\n", acpi_path);
+ return;
+ }
+
ret = acpi_bind_one(dev, NULL);
if (ret) {
struct acpi_bus_type *type = acpi_get_bus_type(dev);
@@ -400,8 +444,14 @@ void acpi_device_notify(struct device *dev)
void acpi_device_notify_remove(struct device *dev)
{
- struct acpi_device *adev = ACPI_COMPANION(dev);
+ struct acpi_device *adev;
+ if (acpi_dt_hybrid && is_of_node(dev->fwnode)) {
+ acpi_unbind_one(dev);
+ return;
+ }
+
+ adev = ACPI_COMPANION(dev);
if (!adev)
return;
--
2.54.0
^ permalink raw reply related
* [RFC 06/12] ACPI: scan: Retry acpi_device_notify() in DT-ACPI hybrid mode
From: Hans de Goede @ 2026-06-23 14:52 UTC (permalink / raw)
To: Rafael J . Wysocki, Bjorn Andersson, Konrad Dybcio
Cc: Hans de Goede, Srinivas Kandagatla, Krzysztof Kozlowski,
Dmitry Baryshkov, Bartosz Golaszewski, Abel Vesa, linux-arm-msm,
devicetree, linux-acpi
In-Reply-To: <20260623145225.143218-1-johannes.goede@oss.qualcomm.com>
of_platform_default_populate_init creates DT platform devices from
an arch_initcall(), so before acpi_scan_init() runs this causing
acpi_device_notify() to be unable to honor "acpi-path" properties
in DT-ACPI hybrid mode.
Re-call acpi_device_notify() at the end of scanning ACPI devices to
fix this up.
Note this relies on the driver which may use the bound ACPI fwnode
to only register after subsys_initcall(acpi_init) has run.
TODO: It is probably better to add an acpi_platform_device_pre_probe()
function and call that from drivers/base/platform.c:platform_probe() and
dot the setting of the secondary fwnode for platform-devices with
an "acpi-path" property there. Basically moving the "if (acpi_dt_hybrid)"
block in acpi_device_notify() to a new acpi_platform_device_pre_probe().
Signed-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
---
drivers/acpi/scan.c | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c
index 4836286968e8..b305c03e8504 100644
--- a/drivers/acpi/scan.c
+++ b/drivers/acpi/scan.c
@@ -21,6 +21,7 @@
#include <linux/dmi.h>
#include <linux/dma-map-ops.h>
#include <linux/platform_data/x86/apple.h>
+#include <linux/platform_device.h>
#include <linux/pgtable.h>
#include <linux/crc32.h>
#include <linux/dma-direct.h>
@@ -2818,6 +2819,22 @@ static void __init acpi_get_spcr_uart_addr(void)
acpi_put_table((struct acpi_table_header *)spcr_ptr);
}
+static int acpi_scan_retry_of_acpi_binding(struct device *dev, void *data)
+{
+ struct fwnode_handle *fwnode = dev_fwnode(dev);
+ const char *acpi_path;
+
+ /* Check primary fwnode is OF and secondary fwnode is not yet ACPI */
+ if (!is_of_node(fwnode) || is_acpi_device_node(fwnode->secondary))
+ return 0;
+
+ /* If there is an "acpi-path" property retry binding ACPI fwnode */
+ if (of_property_read_string(dev->of_node, "acpi-path", &acpi_path) == 0)
+ acpi_device_notify(dev);
+
+ return 0;
+}
+
static bool acpi_scan_initialized;
void __init acpi_scan_init(void)
@@ -2881,6 +2898,18 @@ void __init acpi_scan_init(void)
if (!acpi_gbl_reduced_hardware)
acpi_bus_scan_fixed();
+ /*
+ * of_platform_default_populate_init creates DT platform devices from
+ * an arch_initcall(), so before acpi_scan_init() runs this causing
+ * acpi_device_notify() to be unable to honor "acpi-path" properties
+ * in DT-ACPI hybrid mode. Re-call acpi_device_notify() to fix this up.
+ * Note this relies on the driver which may use the bound ACPI fwnode
+ * to only register after subsys_initcall(acpi_init) has run.
+ */
+ if (acpi_dt_hybrid)
+ bus_for_each_dev(&platform_bus_type, NULL, NULL,
+ acpi_scan_retry_of_acpi_binding);
+
acpi_turn_off_unused_power_resources();
acpi_scan_initialized = true;
--
2.54.0
^ permalink raw reply related
* [RFC 08/12] irqchip/gic-v3: Always call acpi_set_irq_model()
From: Hans de Goede @ 2026-06-23 14:52 UTC (permalink / raw)
To: Rafael J . Wysocki, Bjorn Andersson, Konrad Dybcio
Cc: Hans de Goede, Srinivas Kandagatla, Krzysztof Kozlowski,
Dmitry Baryshkov, Bartosz Golaszewski, Abel Vesa, linux-arm-msm,
devicetree, linux-acpi
In-Reply-To: <20260623145225.143218-1-johannes.goede@oss.qualcomm.com>
The gic-v3 code used to only call acpi_set_irq_model() when the GIC is
enumerated through ACPI.
This causes problems for DT-ACPI hybrid mode, where the GIC might be
described in DT but then a driver using info from the ACPI tables might
call acpi_dev_get_irqresource(), which results in a NULL pointer deref
when acpi_set_irq_model() has not been called.
Always call acpi_set_irq_model() when CONFIG_ACPI is enabled to fix this.
Signed-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
---
drivers/irqchip/irq-gic-v3.c | 29 ++++++++++++++++++++---------
1 file changed, 20 insertions(+), 9 deletions(-)
diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c
index 99444a1b2ffa..2ccd04689a04 100644
--- a/drivers/irqchip/irq-gic-v3.c
+++ b/drivers/irqchip/irq-gic-v3.c
@@ -1974,6 +1974,23 @@ static void gic_enable_nmi_support(void)
gic_chip.flags |= IRQCHIP_SUPPORTS_NMI;
}
+#ifdef CONFIG_ACPI
+static struct fwnode_handle *gsi_domain_handle;
+
+static struct fwnode_handle *gic_v3_get_gsi_domain_id(u32 gsi)
+{
+ return gsi_domain_handle;
+}
+
+static void gic_set_acpi_irq_model(struct fwnode_handle *handle)
+{
+ gsi_domain_handle = handle;
+ acpi_set_irq_model(ACPI_IRQ_MODEL_GIC, gic_v3_get_gsi_domain_id);
+}
+#else
+static void gic_set_acpi_irq_model(struct fwnode_handle *handle) { }
+#endif
+
static int __init gic_init_bases(phys_addr_t dist_phys_base,
void __iomem *dist_base,
struct redist_region *rdist_regs,
@@ -2063,6 +2080,9 @@ static int __init gic_init_bases(phys_addr_t dist_phys_base,
gicv2m_init(handle, gic_data.domain);
}
+ /* Also do this when enumerated through DT for DT-ACPI hybrid mode */
+ gic_set_acpi_irq_model(handle);
+
return 0;
out_free:
@@ -2534,13 +2554,6 @@ static void __init gic_acpi_setup_kvm_info(void)
vgic_set_kvm_info(&gic_v3_kvm_info);
}
-static struct fwnode_handle *gsi_domain_handle;
-
-static struct fwnode_handle *gic_v3_get_gsi_domain_id(u32 gsi)
-{
- return gsi_domain_handle;
-}
-
static int __init
gic_acpi_init(union acpi_subtable_headers *header, const unsigned long end)
{
@@ -2588,8 +2601,6 @@ gic_acpi_init(union acpi_subtable_headers *header, const unsigned long end)
if (err)
goto out_fwhandle_free;
- acpi_set_irq_model(ACPI_IRQ_MODEL_GIC, gic_v3_get_gsi_domain_id);
-
if (static_branch_likely(&supports_deactivate_key))
gic_acpi_setup_kvm_info();
--
2.54.0
^ permalink raw reply related
* [RFC 09/12] pinctrl: qcom: Add support for WoA ACPI tables virtual TLMM pin numbers
From: Hans de Goede @ 2026-06-23 14:52 UTC (permalink / raw)
To: Rafael J . Wysocki, Bjorn Andersson, Konrad Dybcio
Cc: Hans de Goede, Srinivas Kandagatla, Krzysztof Kozlowski,
Dmitry Baryshkov, Bartosz Golaszewski, Abel Vesa, linux-arm-msm,
devicetree, linux-acpi
In-Reply-To: <20260623145225.143218-1-johannes.goede@oss.qualcomm.com>
The ACPI tabled on Windows on ARM laptops use TLMM pin numbers outside of
the actual TLMM pin number range. These are a rather convoluted way to let
the Windows Qualcomm GPIO driver now to use the PDC for some pins because
these are wakeup sources.
This adds support for translating the magic Windows virtual GPIOs for these
back to a regular TLMM GPIO so that ACPI described devices using these
virtual GPIOs can work with Linux.
For now this code only tries to do this mapping when booting in DT-ACPI
hybrid mode which is only used on some WoA devices so this should not
impact any other use-cases.
The new functions use woa_acpi in their name to make clear that these
are for dealing with the ACPI tables found on WoA devices, rather then
ACPI tables found on other devices, like ARM system ready devices which
also use ACPI.
Note that simply mapping these virtual GPIOs back to TLMM pin numbers can
safely be done on Linux, because Linux always uses the PDC for GPIO IRQs
where possible.
Signed-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
---
drivers/pinctrl/qcom/Makefile | 4 +-
drivers/pinctrl/qcom/pinctrl-msm-acpi.c | 196 ++++++++++++++++++++++++
drivers/pinctrl/qcom/pinctrl-msm.c | 47 +++++-
drivers/pinctrl/qcom/pinctrl-msm.h | 35 +++++
4 files changed, 278 insertions(+), 4 deletions(-)
create mode 100644 drivers/pinctrl/qcom/pinctrl-msm-acpi.c
diff --git a/drivers/pinctrl/qcom/Makefile b/drivers/pinctrl/qcom/Makefile
index 84bda3ada874..9029d99190d2 100644
--- a/drivers/pinctrl/qcom/Makefile
+++ b/drivers/pinctrl/qcom/Makefile
@@ -1,6 +1,8 @@
# SPDX-License-Identifier: GPL-2.0
# Qualcomm pin control drivers
-obj-$(CONFIG_PINCTRL_MSM) += pinctrl-msm.o
+obj-$(CONFIG_PINCTRL_MSM) += pinctrl-msm-core.o
+pinctrl-msm-core-y := pinctrl-msm.o
+pinctrl-msm-core-$(CONFIG_ACPI) += pinctrl-msm-acpi.o
obj-$(CONFIG_PINCTRL_APQ8064) += pinctrl-apq8064.o
obj-$(CONFIG_PINCTRL_APQ8084) += pinctrl-apq8084.o
obj-$(CONFIG_PINCTRL_ELIZA) += pinctrl-eliza.o
diff --git a/drivers/pinctrl/qcom/pinctrl-msm-acpi.c b/drivers/pinctrl/qcom/pinctrl-msm-acpi.c
new file mode 100644
index 000000000000..df180fd04749
--- /dev/null
+++ b/drivers/pinctrl/qcom/pinctrl-msm-acpi.c
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ACPI GPIO lookup handling for WoA (Windows on ARM) laptop ACPI tables.
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/gpio/driver.h>
+#include <linux/list.h>
+#include <linux/math.h>
+#include "pinctrl-msm.h"
+
+#define MSM_GPIO_WOA_ACPI_GPIOS_PER_BANK 64
+#define MSM_GPIO_WOA_ACPI_IRQ_OFFSET 32
+#define MSM_GPIO_WOA_ACPI_INVALID_GPIO ~0U
+#define MSM_GPIO_WOA_ACPI_MAX_PDC_RANGES 16
+
+#define PDC_RANGE_PIN_BASE 0
+#define PDC_RANGE_GIC_BASE 1
+#define PDC_RANGE_COUNT 2
+#define PDC_RANGE_ELEMENTS 3
+
+/**
+ * struct msm_gpio_woa_acpi_parse_data - Data for parsing WoA ACPI GPIO ctl resources
+ * @chip: gpiochip handle
+ * @data: Data for mapping virtual WoA ACPI PDC IRQ GPIOs
+ * @soc_data: Reference to soc_data of platform specific data
+ * @pdc_range: PDC GIC to PDC map ranges
+ * @pdc_range_count: PDC GIC to PDC map range-count
+ */
+struct msm_gpio_woa_acpi_parse_data {
+ struct gpio_chip *chip;
+ struct msm_gpio_woa_acpi_data *data;
+ const struct msm_pinctrl_soc_data *soc_data;
+ u32 pdc_range[MSM_GPIO_WOA_ACPI_MAX_PDC_RANGES][PDC_RANGE_ELEMENTS];
+ unsigned int pdc_range_count;
+};
+
+/*
+ * Mapping does not need translating the acpi_resource in to a regular resoure
+ * and adding it to the resource list. Always return 1 to disable this.
+ */
+static int msm_gpio_woa_acpi_resource(struct acpi_resource *ares, void *_parse)
+{
+ struct msm_gpio_woa_acpi_parse_data *parse = _parse;
+ const struct msm_pinctrl_soc_data *soc_data = parse->soc_data;
+ struct msm_gpio_woa_acpi_data *data = parse->data;
+ struct gpio_chip *chip = parse->chip;
+ u32 gic_irq, pdc_pin;
+
+ if (ares->type != ACPI_RESOURCE_TYPE_EXTENDED_IRQ ||
+ ares->data.extended_irq.interrupt_count != 1)
+ return 1;
+
+ if (data->nmap == MSM_GPIO_WOA_ACPI_MAX_VIRT_GPIOS) {
+ dev_err(chip->parent, "ACPI resources contain more than %d IRQs\n",
+ MSM_GPIO_WOA_ACPI_MAX_VIRT_GPIOS);
+ return 1;
+ }
+
+ /*
+ * Windows ACPI tables divide GPIOs into banks of 64 pins with one IRQ
+ * per bank. The resources start with listing the real TLMM IRQ for
+ * as many banks as are necessary to cover the real GPIOs. The Windows
+ * virtual GPIO indexes skip these banks, mark them as unavailable.
+ */
+ if (data->nmap < DIV_ROUND_UP(chip->ngpio, MSM_GPIO_WOA_ACPI_GPIOS_PER_BANK)) {
+ data->map[data->nmap++] = MSM_GPIO_WOA_ACPI_INVALID_GPIO;
+ return 1;
+ }
+
+ /*
+ * Use the "pdc-ranges" property on the PDC to translate the GIC IRQ
+ * from the acpi_resource to a PDC pin.
+ */
+ gic_irq = ares->data.extended_irq.interrupts[0] - MSM_GPIO_WOA_ACPI_IRQ_OFFSET;
+ pdc_pin = MSM_GPIO_WOA_ACPI_INVALID_GPIO;
+ for (unsigned int i = 0; i < parse->pdc_range_count; i++) {
+ u32 gic_base = parse->pdc_range[i][PDC_RANGE_GIC_BASE];
+ u32 count = parse->pdc_range[i][PDC_RANGE_COUNT];
+ if (gic_irq >= gic_base && gic_irq < (gic_base + count)) {
+ pdc_pin = parse->pdc_range[i][PDC_RANGE_PIN_BASE] +
+ gic_irq - gic_base;
+ break;
+ }
+ }
+ if (pdc_pin == MSM_GPIO_WOA_ACPI_INVALID_GPIO)
+ goto no_map;
+
+ /* Use wakeirq-map to map PDC pin to TLMM pin */
+ for (unsigned int i = 0; i < soc_data->nwakeirq_map; i++) {
+ if (soc_data->wakeirq_map[i].wakeirq == pdc_pin) {
+ data->map[data->nmap++] = soc_data->wakeirq_map[i].gpio;
+ return 1;
+ }
+ }
+
+no_map:
+ dev_warn(chip->parent, "Cannot map GIC IRQ %u to TLMM pin\n", gic_irq);
+ data->map[data->nmap++] = MSM_GPIO_WOA_ACPI_INVALID_GPIO;
+ return 1;
+}
+
+int msm_gpio_woa_acpi_init(struct gpio_chip *chip, struct msm_gpio_woa_acpi_data *data,
+ const struct msm_pinctrl_soc_data *soc_data)
+{
+ struct msm_gpio_woa_acpi_parse_data parse;
+ struct fwnode_handle *fwnode;
+ struct device_node *pdc_np;
+ LIST_HEAD(resources);
+ unsigned int ngpio;
+ int ret;
+
+ /* WoA ACPI tables are only used in DT-ACPI hybrid mode */
+ fwnode = chip->parent->fwnode;
+ if (!is_of_node(fwnode) || !is_acpi_device_node(fwnode->secondary))
+ return 0;
+
+ parse.chip = chip;
+ parse.data = data;
+ parse.soc_data = soc_data;
+
+ /* Get PDC ranges, the PDC is the TLMM's wakeup-parent. */
+ pdc_np = of_parse_phandle(chip->parent->of_node, "wakeup-parent", 0);
+ if (!pdc_np)
+ return 0;
+
+ ret = of_property_count_elems_of_size(pdc_np, "qcom,pdc-ranges", sizeof(u32));
+ if (ret <= 0 || ret % PDC_RANGE_ELEMENTS ||
+ ret > (MSM_GPIO_WOA_ACPI_MAX_PDC_RANGES * PDC_RANGE_ELEMENTS))
+ goto err_pdc_ranges;
+
+ parse.pdc_range_count = ret / PDC_RANGE_ELEMENTS;
+ ret = of_property_read_u32_array(pdc_np, "qcom,pdc-ranges",
+ &parse.pdc_range[0][0], ret);
+ if (ret)
+ goto err_pdc_ranges;
+
+ ret = acpi_dev_get_resources(to_acpi_device_node(fwnode->secondary), &resources,
+ msm_gpio_woa_acpi_resource, &parse);
+ if (ret < 0)
+ return ret;
+
+ acpi_dev_free_resource_list(&resources);
+
+ ngpio = data->nmap * MSM_GPIO_WOA_ACPI_GPIOS_PER_BANK + 1;
+ chip->ngpio = max(chip->ngpio, ngpio);
+
+ for (unsigned int i = 0; i < data->nmap; i++) {
+ if (data->map[i] != MSM_GPIO_WOA_ACPI_INVALID_GPIO) {
+ /* TODO lower log level to dev_dbg() */
+ dev_info(chip->parent, "mapped GPIO 0x%03x to TLMM pin %u\n",
+ i * MSM_GPIO_WOA_ACPI_GPIOS_PER_BANK, data->map[i]);
+ }
+ }
+
+ return 0;
+
+err_pdc_ranges:
+ dev_err(chip->parent, "Error invalid pdc-ranges\n");
+ return 0; /* Continue without mapping */
+}
+
+void msm_gpio_woa_acpi_valid_mask(struct gpio_chip *chip,
+ struct msm_gpio_woa_acpi_data *data,
+ unsigned long *valid_mask,
+ unsigned int soc_ngpio)
+{
+ /* First mark all virtual ACPI PDC GPIOs (if any) as invalid */
+ bitmap_clear(valid_mask, soc_ngpio, chip->ngpio - soc_ngpio);
+
+ /*
+ * WoA ACPI tables list 1 Interrupt resource per PDC pin and use
+ * a 1 interrupt per bank model. So each PDC pin gets its own bank,
+ * with only pin 0 of that bank used for that PDC pin.
+ */
+ for (unsigned int i = 0; i < data->nmap; i++) {
+ if (data->map[i] != MSM_GPIO_WOA_ACPI_INVALID_GPIO)
+ set_bit(i * MSM_GPIO_WOA_ACPI_GPIOS_PER_BANK, valid_mask);
+ }
+}
+
+unsigned int msm_gpio_woa_acpi_map(struct msm_gpio_woa_acpi_data *data, unsigned int offset)
+{
+ unsigned int bank = offset / MSM_GPIO_WOA_ACPI_GPIOS_PER_BANK;
+
+ /* msm_gpio_woa_acpi_valid_mask() should ensure this never happens */
+ if (WARN_ON(offset % MSM_GPIO_WOA_ACPI_GPIOS_PER_BANK || bank >= data->nmap ||
+ data->map[bank] == MSM_GPIO_WOA_ACPI_INVALID_GPIO))
+ return 0;
+
+ return data->map[bank];
+}
diff --git a/drivers/pinctrl/qcom/pinctrl-msm.c b/drivers/pinctrl/qcom/pinctrl-msm.c
index f780bab51d9c..60ba0da95634 100644
--- a/drivers/pinctrl/qcom/pinctrl-msm.c
+++ b/drivers/pinctrl/qcom/pinctrl-msm.c
@@ -45,6 +45,7 @@
* @pctrl: pinctrl handle.
* @chip: gpiochip handle.
* @desc: pin controller descriptor
+ * @woa_acpi: data for mapping virtual WoA ACPI PDC IRQ GPIOs
* @irq: parent irq for the TLMM irq_chip.
* @intr_target_use_scm: route irq to application cpu using scm calls
* @lock: Spinlock to protect register resources as well
@@ -64,6 +65,7 @@ struct msm_pinctrl {
struct pinctrl_dev *pctrl;
struct gpio_chip chip;
struct pinctrl_desc desc;
+ struct msm_gpio_woa_acpi_data woa_acpi;
int irq;
@@ -553,6 +555,16 @@ static const struct pinconf_ops msm_pinconf_ops = {
.pin_config_group_set = msm_config_group_set,
};
+static unsigned int msm_gpio_map(struct gpio_chip *chip, unsigned int offset)
+{
+ struct msm_pinctrl *pctrl = gpiochip_get_data(chip);
+
+ if (offset < pctrl->soc->ngpios)
+ return offset;
+
+ return msm_gpio_woa_acpi_map(&pctrl->woa_acpi, offset);
+}
+
static int msm_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
{
const struct msm_pingroup *g;
@@ -560,6 +572,8 @@ static int msm_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
unsigned long flags;
u32 val;
+ offset = msm_gpio_map(chip, offset);
+
g = &pctrl->soc->groups[offset];
raw_spin_lock_irqsave(&pctrl->lock, flags);
@@ -580,6 +594,8 @@ static int msm_gpio_direction_output(struct gpio_chip *chip, unsigned offset, in
unsigned long flags;
u32 val;
+ offset = msm_gpio_map(chip, offset);
+
g = &pctrl->soc->groups[offset];
raw_spin_lock_irqsave(&pctrl->lock, flags);
@@ -606,6 +622,8 @@ static int msm_gpio_get_direction(struct gpio_chip *chip, unsigned int offset)
const struct msm_pingroup *g;
u32 val;
+ offset = msm_gpio_map(chip, offset);
+
g = &pctrl->soc->groups[offset];
val = msm_readl_ctl(pctrl, g);
@@ -620,6 +638,8 @@ static int msm_gpio_get(struct gpio_chip *chip, unsigned offset)
struct msm_pinctrl *pctrl = gpiochip_get_data(chip);
u32 val;
+ offset = msm_gpio_map(chip, offset);
+
g = &pctrl->soc->groups[offset];
val = msm_readl_io(pctrl, g);
@@ -633,6 +653,8 @@ static int msm_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
unsigned long flags;
u32 val;
+ offset = msm_gpio_map(chip, offset);
+
g = &pctrl->soc->groups[offset];
raw_spin_lock_irqsave(&pctrl->lock, flags);
@@ -717,10 +739,11 @@ static void msm_gpio_dbg_show_one(struct seq_file *s,
static void msm_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
{
+ struct msm_pinctrl *pctrl = gpiochip_get_data(chip);
unsigned gpio = chip->base;
unsigned i;
- for (i = 0; i < chip->ngpio; i++, gpio++)
+ for (i = 0; i < pctrl->soc->ngpios; i++, gpio++)
msm_gpio_dbg_show_one(s, NULL, chip, i, gpio);
}
@@ -738,6 +761,10 @@ static int msm_gpio_init_valid_mask(struct gpio_chip *gc,
const int *reserved = pctrl->soc->reserved_gpios;
u16 *tmp;
+ /* WOA ACPI handles virtual PDC GPIO range, only handle real GPIOs here */
+ ngpios = pctrl->soc->ngpios;
+ msm_gpio_woa_acpi_valid_mask(gc, &pctrl->woa_acpi, valid_mask, ngpios);
+
/* Remove driver-provided reserved GPIOs from valid_mask */
if (reserved) {
for (i = 0; reserved[i] >= 0; i++) {
@@ -1046,6 +1073,10 @@ static void msm_gpio_irq_init_valid_mask(struct gpio_chip *gc,
const struct msm_pingroup *g;
int i;
+ /* WOA ACPI handles virtual PDC GPIO range, only handle real GPIOs here */
+ ngpios = pctrl->soc->ngpios;
+ msm_gpio_woa_acpi_valid_mask(gc, &pctrl->woa_acpi, valid_mask, ngpios);
+
for (i = 0; i < ngpios; i++) {
g = &pctrl->soc->groups[i];
@@ -1387,6 +1418,9 @@ static bool msm_gpio_needs_valid_mask(struct msm_pinctrl *pctrl)
if (pctrl->soc->reserved_gpios)
return true;
+ if (pctrl->chip.ngpio != pctrl->soc->ngpios)
+ return true;
+
return device_property_count_u16(pctrl->dev, "gpios") > 0;
}
@@ -1428,8 +1462,6 @@ static int msm_gpio_init(struct msm_pinctrl *pctrl)
chip->label = dev_name(pctrl->dev);
chip->parent = pctrl->dev;
chip->owner = THIS_MODULE;
- if (msm_gpio_needs_valid_mask(pctrl))
- chip->init_valid_mask = msm_gpio_init_valid_mask;
np = of_parse_phandle(pctrl->dev->of_node, "wakeup-parent", 0);
if (np) {
@@ -1450,6 +1482,13 @@ static int msm_gpio_init(struct msm_pinctrl *pctrl)
}
}
+ ret = msm_gpio_woa_acpi_init(chip, &pctrl->woa_acpi, pctrl->soc);
+ if (ret)
+ return ret;
+
+ if (msm_gpio_needs_valid_mask(pctrl))
+ chip->init_valid_mask = msm_gpio_init_valid_mask;
+
girq = &chip->irq;
gpio_irq_chip_set_chip(girq, &msm_gpio_irq_chip);
girq->parent_handler = msm_gpio_irq_handler;
@@ -1463,6 +1502,8 @@ static int msm_gpio_init(struct msm_pinctrl *pctrl)
girq->handler = handle_bad_irq;
girq->parents[0] = pctrl->irq;
girq->init_valid_mask = msm_gpio_irq_init_valid_mask;
+ if (pctrl->chip.ngpio != pctrl->soc->ngpios)
+ girq->child_offset_to_irq = msm_gpio_map;
ret = devm_gpiochip_add_data(pctrl->dev, &pctrl->chip, pctrl);
if (ret) {
diff --git a/drivers/pinctrl/qcom/pinctrl-msm.h b/drivers/pinctrl/qcom/pinctrl-msm.h
index 4fbff61de6bb..3a8a55a6d8c1 100644
--- a/drivers/pinctrl/qcom/pinctrl-msm.h
+++ b/drivers/pinctrl/qcom/pinctrl-msm.h
@@ -10,6 +10,7 @@
#include <linux/pinctrl/pinctrl.h>
+struct gpio_chip;
struct platform_device;
struct pinctrl_pin_desc;
@@ -181,9 +182,43 @@ struct msm_pinctrl_soc_data {
unsigned int egpio_func;
};
+#define MSM_GPIO_WOA_ACPI_MAX_VIRT_GPIOS 32
+
+/**
+ * struct msm_gpio_woa_acpi_data - Data for mapping virtual WoA ACPI PDC IRQ GPIOs
+ * @map: Map to translate virtual ACPI GPIO offsets to TLMM pin offsets
+ * @nmap: Number of valid entried in @map
+ */
+struct msm_gpio_woa_acpi_data {
+ unsigned int map[MSM_GPIO_WOA_ACPI_MAX_VIRT_GPIOS];
+ unsigned int nmap;
+};
+
extern const struct dev_pm_ops msm_pinctrl_dev_pm_ops;
int msm_pinctrl_probe(struct platform_device *pdev,
const struct msm_pinctrl_soc_data *soc_data);
+#ifdef CONFIG_ACPI
+int msm_gpio_woa_acpi_init(struct gpio_chip *chip, struct msm_gpio_woa_acpi_data *data,
+ const struct msm_pinctrl_soc_data *soc_data);
+void msm_gpio_woa_acpi_valid_mask(struct gpio_chip *chip,
+ struct msm_gpio_woa_acpi_data *data,
+ unsigned long *valid_mask,
+ unsigned int soc_ngpio);
+unsigned int msm_gpio_woa_acpi_map(struct msm_gpio_woa_acpi_data *data, unsigned int offset);
+#else
+static inline int
+msm_gpio_woa_acpi_init(struct gpio_chip *chip, struct msm_gpio_woa_acpi_data *data,
+ const struct msm_pinctrl_soc_data *soc_data)
+{ return 0; }
+static inline void msm_gpio_woa_acpi_valid_mask(struct gpio_chip *chip,
+ struct msm_gpio_woa_acpi_data *data,
+ unsigned long *valid_mask,
+ unsigned int soc_ngpio) { }
+static inline unsigned int
+msm_gpio_woa_acpi_map(struct msm_gpio_woa_acpi_data *data, unsigned int offset)
+{ return 0; }
#endif
+
+#endif /* ifndef __PINCTRL_MSM_H__ */
--
2.54.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox