From 718ba914a00cde53f5df0c3de369333d0ddcd5a5 Mon Sep 17 00:00:00 2001 From: Samuel Morris Date: Mon, 1 Jun 2026 14:32:17 -0700 Subject: [PATCH 01/32] extcon: Add STM32 SPI IPC driver Add a custom SPI client driver that functions as an extcon event provider. This driver communicates with an external STM32 co-processor over SPI using an attention line (interrupt) to propagate USB cable events. It registers separate virtual extcon sub-devices for each connector, allowing ChipIdea OTG controllers to switch between Host/Peripheral roles. A debugfs simulation interface is also exposed for runtime testing. --- drivers/extcon/Kconfig | 7 + drivers/extcon/Makefile | 1 + drivers/extcon/extcon-stm32-ipc.c | 292 ++++++++++++++++++++++++++++++ 3 files changed, 300 insertions(+) create mode 100644 drivers/extcon/extcon-stm32-ipc.c diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index cf472e44c5ff9..8852425b68fba 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -176,6 +176,13 @@ config EXTCON_USB_GPIO Say Y here to enable GPIO based USB cable detection extcon support. Used typically if GPIO is used for USB ID pin detection. +config EXTCON_STM32_SPI_IPC + tristate "STM32 SPI IPC and Virtual Extcon support" + depends on SPI + help + Say Y here to enable the STM32 to i.MX8MM SPI IPC and Virtual Extcon + driver, which handles USB connection/cable event notifications over SPI. + config EXTCON_USBC_CROS_EC tristate "ChromeOS Embedded Controller EXTCON support" depends on CROS_EC diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 1b390d934ca92..b962d043fd804 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -23,5 +23,6 @@ obj-$(CONFIG_EXTCON_QCOM_SPMI_MISC) += extcon-qcom-spmi-misc.o obj-$(CONFIG_EXTCON_RT8973A) += extcon-rt8973a.o obj-$(CONFIG_EXTCON_SM5502) += extcon-sm5502.o obj-$(CONFIG_EXTCON_USB_GPIO) += extcon-usb-gpio.o +obj-$(CONFIG_EXTCON_STM32_SPI_IPC) += extcon-stm32-ipc.o obj-$(CONFIG_EXTCON_USBC_CROS_EC) += extcon-usbc-cros-ec.o obj-$(CONFIG_EXTCON_USBC_TUSB320) += extcon-usbc-tusb320.o diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c new file mode 100644 index 0000000000000..ad70407cbb0c7 --- /dev/null +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/extcon/extcon-stm32-ipc.c - STM32 SPI IPC and Virtual Extcon Driver + * + * Copyright (C) 2026 MultiTracks + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "stm-spi-ipc" + +/* Protocol constants */ +#define STM_IPC_MSG_MAGIC 0x5A +#define MSG_TYPE_USB_EVENT 0x01 + +struct stm_ipc_packet { + u8 magic; + u8 type; + u8 length; + u8 port; + u8 state; /* 0 = disconnected, 1 = peripheral (VBUS), 2 = host (ID) */ + u8 crc; +} __packed; + +/* Parent SPI controller private structure */ +struct stm_ipc_priv { + struct spi_device *spi; + struct mutex lock; + struct dentry *debugfs_root; +}; + +/* Child connector platform device private structure */ +struct stm_connector_priv { + struct extcon_dev *edev; + u32 port_id; +}; + +static const unsigned int stm_usb_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_NONE, +}; + +/* Helper to update extcon state based on STM32 reporting */ +static void stm_ipc_update_state(struct extcon_dev *edev, u8 state) +{ + switch (state) { + case 0: /* Disconnected */ + extcon_set_state_sync(edev, EXTCON_USB, false); + extcon_set_state_sync(edev, EXTCON_USB_HOST, false); + break; + case 1: /* Peripheral Mode (VBUS present) */ + extcon_set_state_sync(edev, EXTCON_USB_HOST, false); + extcon_set_state_sync(edev, EXTCON_USB, true); + break; + case 2: /* Host Mode (ID ground) */ + extcon_set_state_sync(edev, EXTCON_USB, false); + extcon_set_state_sync(edev, EXTCON_USB_HOST, true); + break; + default: + break; + } +} + +/* Callback used to find and update state of correct child platform device */ +static int match_and_update_state(struct device *dev, void *data) +{ + struct stm_connector_priv *priv = dev_get_drvdata(dev); + u8 *port_info = data; + u8 port_id = port_info[0]; + u8 state = port_info[1]; + + if (priv && priv->port_id == port_id) { + stm_ipc_update_state(priv->edev, state); + return 1; /* Found and processed, stop iteration */ + } + return 0; +} + +/* Threaded IRQ Handler (safe to perform sleeping SPI transfers) */ +static irqreturn_t stm_ipc_threaded_irq(int irq, void *dev_id) +{ + struct stm_ipc_priv *priv = dev_id; + struct stm_ipc_packet tx_buf = {0}; + struct stm_ipc_packet rx_buf = {0}; + struct spi_transfer t = { + .tx_buf = &tx_buf, + .rx_buf = &rx_buf, + .len = sizeof(struct stm_ipc_packet), + }; + struct spi_message m; + int ret; + + mutex_lock(&priv->lock); + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + ret = spi_sync(priv->spi, &m); + if (ret < 0) { + dev_err(&priv->spi->dev, "SPI sync transfer failed: %d\n", ret); + goto out; + } + + /* Validate packet */ + if (rx_buf.magic != STM_IPC_MSG_MAGIC) { + dev_warn_ratelimited(&priv->spi->dev, "Invalid magic byte: 0x%02x\n", rx_buf.magic); + goto out; + } + + /* Process USB event */ + if (rx_buf.type == MSG_TYPE_USB_EVENT) { + u8 port_info[2] = { rx_buf.port, rx_buf.state }; + + dev_info(&priv->spi->dev, "USB Event from STM32 on Port %d: State %d\n", rx_buf.port, rx_buf.state); + device_for_each_child(&priv->spi->dev, port_info, match_and_update_state); + } + +out: + mutex_unlock(&priv->lock); + return IRQ_HANDLED; +} + +/* Debugfs Simulation Entry (for mocking events) */ +static int stm_ipc_sim_write(void *data, u64 val) +{ + struct stm_connector_priv *priv = data; + + if (val > 2) + return -EINVAL; + + stm_ipc_update_state(priv->edev, (u8)val); + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(stm_ipc_sim_fops, NULL, stm_ipc_sim_write, "%llu\n"); + +/* Child Connector Platform Device Probe */ +static int stm_usb_connector_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct stm_connector_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + ret = of_property_read_u32(dev->of_node, "port-id", &priv->port_id); + if (ret) { + dev_err(dev, "Missing 'port-id' property\n"); + return ret; + } + + priv->edev = devm_extcon_dev_allocate(dev, stm_usb_cable); + if (IS_ERR(priv->edev)) + return PTR_ERR(priv->edev); + + ret = devm_extcon_dev_register(dev, priv->edev); + if (ret) { + dev_err(dev, "Failed to register extcon device: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, priv); + + /* Set up DebugFS for this virtual connector for runtime simulation */ + struct stm_ipc_priv *parent_priv = dev_get_drvdata(dev->parent); + if (parent_priv && parent_priv->debugfs_root) { + char name[32]; + snprintf(name, sizeof(name), "usb%d_sim", priv->port_id + 1); + debugfs_create_file(name, 0200, parent_priv->debugfs_root, priv, &stm_ipc_sim_fops); + } + + dev_info(dev, "Registered virtual USB connector on Port %d\n", priv->port_id); + return 0; +} + +static const struct of_device_id stm_usb_connector_of_match[] = { + { .compatible = "multitracks,stm32-usb-connector" }, + { } +}; +MODULE_DEVICE_TABLE(of, stm_usb_connector_of_match); + +static struct platform_driver stm_usb_connector_driver = { + .probe = stm_usb_connector_probe, + .driver = { + .name = "stm32-usb-connector", + .of_match_table = stm_usb_connector_of_match, + }, +}; + +/* Parent SPI Driver Probe */ +static int stm_ipc_probe(struct spi_device *spi) +{ + struct stm_ipc_priv *priv; + int ret; + + priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->spi = spi; + mutex_init(&priv->lock); + spi_set_drvdata(spi, priv); + + /* Initialize DebugFS root directory under /sys/kernel/debug/stm_ipc */ + priv->debugfs_root = debugfs_create_dir("stm_ipc", NULL); + + /* Populate subnodes as platform devices (this probes the connector sub-drivers) */ + ret = devm_of_platform_populate(&spi->dev); + if (ret) { + dev_err(&spi->dev, "Failed to populate child connectors: %d\n", ret); + goto err_debugfs; + } + + /* Request Attention Pin Interrupt */ + if (spi->irq > 0) { + ret = devm_request_threaded_irq(&spi->dev, spi->irq, NULL, + stm_ipc_threaded_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + DRIVER_NAME, priv); + if (ret) { + dev_err(&spi->dev, "Failed to request threaded IRQ: %d\n", ret); + goto err_debugfs; + } + } + + dev_info(&spi->dev, "STM32 SPI IPC Core initialized\n"); + return 0; + +err_debugfs: + debugfs_remove_recursive(priv->debugfs_root); + return ret; +} + +static void stm_ipc_remove(struct spi_device *spi) +{ + struct stm_ipc_priv *priv = spi_get_drvdata(spi); + debugfs_remove_recursive(priv->debugfs_root); +} + +static const struct of_device_id stm_ipc_of_match[] = { + { .compatible = "multitracks,stm32-spi-ipc" }, + { } +}; +MODULE_DEVICE_TABLE(of, stm_ipc_of_match); + +static struct spi_driver stm_ipc_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = stm_ipc_of_match, + }, + .probe = stm_ipc_probe, + .remove = stm_ipc_remove, +}; + +static int __init stm_ipc_init(void) +{ + int ret; + + ret = spi_register_driver(&stm_ipc_driver); + if (ret) + return ret; + + ret = platform_driver_register(&stm_usb_connector_driver); + if (ret) { + spi_unregister_driver(&stm_ipc_driver); + return ret; + } + + return 0; +} +static void __exit stm_ipc_exit(void) +{ + spi_unregister_driver(&stm_ipc_driver); + platform_driver_unregister(&stm_usb_connector_driver); +} + +module_init(stm_ipc_init); +module_exit(stm_ipc_exit); + +MODULE_AUTHOR("Samuel Morris "); +MODULE_DESCRIPTION("STM32 SPI IPC and virtual Extcon Driver"); +MODULE_LICENSE("GPL v2"); From 6fc6a6fc62597eb259eccbb828a861726d22d500 Mon Sep 17 00:00:00 2001 From: Samuel Morris Date: Mon, 1 Jun 2026 14:32:29 -0700 Subject: [PATCH 02/32] arm64: configs: Enable STM32 SPI IPC extcon driver Enable CONFIG_EXTCON_STM32_SPI_IPC in mt_connect_defconfig to compile the custom stm-spi-ipc extcon driver directly into the kernel. --- arch/arm64/configs/mt_connect_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/mt_connect_defconfig b/arch/arm64/configs/mt_connect_defconfig index dd15ba66e2408..31e80d4eeb68d 100644 --- a/arch/arm64/configs/mt_connect_defconfig +++ b/arch/arm64/configs/mt_connect_defconfig @@ -654,6 +654,7 @@ CONFIG_FSL_QIXIS=y CONFIG_SOC_TI=y CONFIG_EXTCON_PTN5150=m CONFIG_EXTCON_USB_GPIO=y +CONFIG_EXTCON_STM32_SPI_IPC=y CONFIG_IIO=y CONFIG_FXLS8962AF_I2C=m CONFIG_IIO_ST_ACCEL_3AXIS=m From 579eca85d33f31287963ac15b3416d2ec931eb0f Mon Sep 17 00:00:00 2001 From: Samuel Morris Date: Mon, 1 Jun 2026 14:32:39 -0700 Subject: [PATCH 03/32] arm64: dts: freescale: Add STM32 SPI IPC and extcon nodes Configure the ECSPI2 controller on mt-connect to support the STM32 SPI IPC slave node and its virtual USB connector child nodes. Bind &usbotg1 and &usbotg2 to their respective virtual extcon connectors and enable role switching. Set up pin control and active-low interrupt for GPIO2_IO11. --- arch/arm64/boot/dts/freescale/mt-connect.dts | 28 +++++++++++++++----- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/arch/arm64/boot/dts/freescale/mt-connect.dts b/arch/arm64/boot/dts/freescale/mt-connect.dts index 49b02c88cba9d..949475cda4227 100644 --- a/arch/arm64/boot/dts/freescale/mt-connect.dts +++ b/arch/arm64/boot/dts/freescale/mt-connect.dts @@ -495,15 +495,29 @@ }; &ecspi2 { - status = "okay"; + #address-cells = <1>; + #size-cells = <0>; pinctrl-names = "default"; - pinctrl-0 = <&pinctrl_ecspi2>; + pinctrl-0 = <&pinctrl_ecspi2 &pinctrl_stm_ipc>; cs-gpios = <&gpio5 13 GPIO_ACTIVE_LOW>; + status = "okay"; - spidev1: spi@0 { + stm_ipc: stm32-ipc@0 { + compatible = "multitracks,stm32-spi-ipc"; reg = <0>; - compatible = "rohm,dh2228fv"; - spi-max-frequency = <500000>; + spi-max-frequency = <10000000>; + interrupt-parent = <&gpio2>; + interrupts = <11 IRQ_TYPE_EDGE_FALLING>; + + usb1_connector: connector-usb1 { + compatible = "multitracks,stm32-usb-connector"; + port-id = <0>; + }; + + usb2_connector: connector-usb2 { + compatible = "multitracks,stm32-usb-connector"; + port-id = <1>; + }; }; }; @@ -520,6 +534,7 @@ }; &usbotg1 { + extcon = <&usb1_connector>; hnp-disable; srp-disable; adp-disable; @@ -533,6 +548,7 @@ }; &usbotg2 { + extcon = <&usb2_connector>; hnp-disable; srp-disable; adp-disable; @@ -710,7 +726,7 @@ >; }; - pinctrl_typec1: typec1grp { + pinctrl_stm_ipc: stmipcgrp { fsl,pins = < MX8MM_IOMUXC_SD1_STROBE_GPIO2_IO11 0x159 >; From ba8eea041b21c24d99d454bafffa207adf5c12cf Mon Sep 17 00:00:00 2001 From: Samuel Morris Date: Wed, 3 Jun 2026 15:34:42 -0700 Subject: [PATCH 04/32] extcon: Kconfig: select CRC8 and improve stm32-ipc help description --- drivers/extcon/Kconfig | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 8852425b68fba..910f1eb15b121 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -179,9 +179,11 @@ config EXTCON_USB_GPIO config EXTCON_STM32_SPI_IPC tristate "STM32 SPI IPC and Virtual Extcon support" depends on SPI + select CRC8 help - Say Y here to enable the STM32 to i.MX8MM SPI IPC and Virtual Extcon - driver, which handles USB connection/cable event notifications over SPI. + Say Y here to enable the STM32 SPI IPC and Virtual Extcon driver, + which handles USB connection/cable event notifications reported + by an external STM32 co-processor over SPI. config EXTCON_USBC_CROS_EC tristate "ChromeOS Embedded Controller EXTCON support" From 2cedf5cbd3f99433300a88e4f090209ce4ec9c47 Mon Sep 17 00:00:00 2001 From: Samuel Morris Date: Wed, 3 Jun 2026 15:35:26 -0700 Subject: [PATCH 05/32] extcon: stm32-ipc: remove unused GPIO header --- drivers/extcon/extcon-stm32-ipc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index ad70407cbb0c7..f899d5824230e 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include From 7ee736b38c2ca202e76425bc997fcdffdd4aa03b Mon Sep 17 00:00:00 2001 From: Samuel Morris Date: Wed, 3 Jun 2026 15:35:27 -0700 Subject: [PATCH 06/32] extcon: stm32-ipc: fallback to safe disconnected state error --- drivers/extcon/extcon-stm32-ipc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index f899d5824230e..8df0c09fee12c 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -66,6 +66,10 @@ static void stm_ipc_update_state(struct extcon_dev *edev, u8 state) extcon_set_state_sync(edev, EXTCON_USB_HOST, true); break; default: + /* Fallback to safe disconnected state on protocol error */ + extcon_set_state_sync(edev, EXTCON_USB, false); + extcon_set_state_sync(edev, EXTCON_USB_HOST, false); + pr_warn_ratelimited("stm-spi-ipc: Invalid USB connector state: %u\n", state); break; } } From d24f351c8c1805af88d90efb7d58ecf5b69d7df2 Mon Sep 17 00:00:00 2001 From: Samuel Morris Date: Wed, 3 Jun 2026 15:35:27 -0700 Subject: [PATCH 07/32] extcon: stm32-ipc: Use dev_dbg in threaded IRQ to avoid log flooding --- drivers/extcon/extcon-stm32-ipc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index 8df0c09fee12c..01c31f82d84a7 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -123,7 +123,7 @@ static irqreturn_t stm_ipc_threaded_irq(int irq, void *dev_id) if (rx_buf.type == MSG_TYPE_USB_EVENT) { u8 port_info[2] = { rx_buf.port, rx_buf.state }; - dev_info(&priv->spi->dev, "USB Event from STM32 on Port %d: State %d\n", rx_buf.port, rx_buf.state); + dev_dbg(&priv->spi->dev, "USB Event from STM32 on Port %d: State %d\n", rx_buf.port, rx_buf.state); device_for_each_child(&priv->spi->dev, port_info, match_and_update_state); } From 97c4e671a68c668c8b630d26371759f5df623c6d Mon Sep 17 00:00:00 2001 From: Samuel Morris Date: Wed, 3 Jun 2026 15:35:27 -0700 Subject: [PATCH 08/32] extcon: stm32-ipc: create per-device debugfs to avoid collisions --- drivers/extcon/extcon-stm32-ipc.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index 01c31f82d84a7..6366d3a85dde9 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -214,8 +214,20 @@ static int stm_ipc_probe(struct spi_device *spi) mutex_init(&priv->lock); spi_set_drvdata(spi, priv); - /* Initialize DebugFS root directory under /sys/kernel/debug/stm_ipc */ - priv->debugfs_root = debugfs_create_dir("stm_ipc", NULL); + struct dentry *parent; + + /* Initialize DebugFS subdirectory under /sys/kernel/debug/stm_ipc/ */ + parent = debugfs_create_dir("stm_ipc", NULL); + if (IS_ERR_OR_NULL(parent)) { + dev_warn(&spi->dev, "Failed to create debugfs parent directory\n"); + priv->debugfs_root = NULL; + } else { + priv->debugfs_root = debugfs_create_dir(dev_name(&spi->dev), parent); + if (IS_ERR_OR_NULL(priv->debugfs_root)) { + dev_warn(&spi->dev, "Failed to create debugfs root directory\n"); + priv->debugfs_root = NULL; + } + } /* Populate subnodes as platform devices (this probes the connector sub-drivers) */ ret = devm_of_platform_populate(&spi->dev); From 2398ab9a5143f1261172497f99d862404318dc22 Mon Sep 17 00:00:00 2001 From: Samuel Morris Date: Wed, 3 Jun 2026 15:35:27 -0700 Subject: [PATCH 09/32] extcon: stm32-ipc: implement CRC-8/length validation on SPI packets --- drivers/extcon/extcon-stm32-ipc.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index 6366d3a85dde9..2ba47f957db1b 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -14,6 +14,7 @@ #include #include #include +#include #define DRIVER_NAME "stm-spi-ipc" @@ -31,6 +32,8 @@ struct stm_ipc_packet { } __packed; /* Parent SPI controller private structure */ +DECLARE_CRC8_TABLE(stm_crc8_table); + struct stm_ipc_priv { struct spi_device *spi; struct mutex lock; @@ -102,6 +105,7 @@ static irqreturn_t stm_ipc_threaded_irq(int irq, void *dev_id) }; struct spi_message m; int ret; + u8 calc_crc; mutex_lock(&priv->lock); @@ -113,12 +117,27 @@ static irqreturn_t stm_ipc_threaded_irq(int irq, void *dev_id) goto out; } - /* Validate packet */ + /* Validate magic byte */ if (rx_buf.magic != STM_IPC_MSG_MAGIC) { dev_warn_ratelimited(&priv->spi->dev, "Invalid magic byte: 0x%02x\n", rx_buf.magic); goto out; } + /* Validate packet CRC */ + calc_crc = crc8(stm_crc8_table, (const u8 *)&rx_buf, offsetof(struct stm_ipc_packet, crc), 0); + if (calc_crc != rx_buf.crc) { + dev_warn_ratelimited(&priv->spi->dev, "CRC mismatch: read 0x%02x, calculated 0x%02x\n", + rx_buf.crc, calc_crc); + goto out; + } + + /* Validate packet payload length for USB events */ + if (rx_buf.type == MSG_TYPE_USB_EVENT && rx_buf.length != 2) { + dev_warn_ratelimited(&priv->spi->dev, "Invalid packet length: %u (expected 2)\n", + rx_buf.length); + goto out; + } + /* Process USB event */ if (rx_buf.type == MSG_TYPE_USB_EVENT) { u8 port_info[2] = { rx_buf.port, rx_buf.state }; @@ -216,6 +235,9 @@ static int stm_ipc_probe(struct spi_device *spi) struct dentry *parent; + /* Setup CRC8 lookup table (polynomial 0x07) */ + crc8_populate_msb(stm_crc8_table, 0x07); + /* Initialize DebugFS subdirectory under /sys/kernel/debug/stm_ipc/ */ parent = debugfs_create_dir("stm_ipc", NULL); if (IS_ERR_OR_NULL(parent)) { From 813a8668e4210c7fd45abed96ff144ead3e7045b Mon Sep 17 00:00:00 2001 From: Samuel Morris Date: Wed, 3 Jun 2026 15:51:19 -0700 Subject: [PATCH 10/32] extcon: stm32-ipc: replace magic numbers with enum and defines --- drivers/extcon/extcon-stm32-ipc.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index 2ba47f957db1b..79298b811fad3 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -19,15 +19,23 @@ #define DRIVER_NAME "stm-spi-ipc" /* Protocol constants */ -#define STM_IPC_MSG_MAGIC 0x5A -#define MSG_TYPE_USB_EVENT 0x01 +#define STM_IPC_MSG_MAGIC 0x5A +#define MSG_TYPE_USB_EVENT 0x01 +#define STM_IPC_MSG_LEN_USB_EVENT 2 +#define STM_IPC_CRC8_POLYNOMIAL 0x07 + +enum stm_ipc_usb_state { + STM_IPC_USB_STATE_DISCONNECTED = 0, + STM_IPC_USB_STATE_PERIPHERAL = 1, + STM_IPC_USB_STATE_HOST = 2, +}; struct stm_ipc_packet { u8 magic; u8 type; u8 length; u8 port; - u8 state; /* 0 = disconnected, 1 = peripheral (VBUS), 2 = host (ID) */ + u8 state; /* enum stm_ipc_usb_state */ u8 crc; } __packed; @@ -56,15 +64,15 @@ static const unsigned int stm_usb_cable[] = { static void stm_ipc_update_state(struct extcon_dev *edev, u8 state) { switch (state) { - case 0: /* Disconnected */ + case STM_IPC_USB_STATE_DISCONNECTED: extcon_set_state_sync(edev, EXTCON_USB, false); extcon_set_state_sync(edev, EXTCON_USB_HOST, false); break; - case 1: /* Peripheral Mode (VBUS present) */ + case STM_IPC_USB_STATE_PERIPHERAL: extcon_set_state_sync(edev, EXTCON_USB_HOST, false); extcon_set_state_sync(edev, EXTCON_USB, true); break; - case 2: /* Host Mode (ID ground) */ + case STM_IPC_USB_STATE_HOST: extcon_set_state_sync(edev, EXTCON_USB, false); extcon_set_state_sync(edev, EXTCON_USB_HOST, true); break; @@ -132,9 +140,9 @@ static irqreturn_t stm_ipc_threaded_irq(int irq, void *dev_id) } /* Validate packet payload length for USB events */ - if (rx_buf.type == MSG_TYPE_USB_EVENT && rx_buf.length != 2) { - dev_warn_ratelimited(&priv->spi->dev, "Invalid packet length: %u (expected 2)\n", - rx_buf.length); + if (rx_buf.type == MSG_TYPE_USB_EVENT && rx_buf.length != STM_IPC_MSG_LEN_USB_EVENT) { + dev_warn_ratelimited(&priv->spi->dev, "Invalid packet length: %u (expected %d)\n", + rx_buf.length, STM_IPC_MSG_LEN_USB_EVENT); goto out; } @@ -156,7 +164,7 @@ static int stm_ipc_sim_write(void *data, u64 val) { struct stm_connector_priv *priv = data; - if (val > 2) + if (val > STM_IPC_USB_STATE_HOST) return -EINVAL; stm_ipc_update_state(priv->edev, (u8)val); @@ -236,7 +244,7 @@ static int stm_ipc_probe(struct spi_device *spi) struct dentry *parent; /* Setup CRC8 lookup table (polynomial 0x07) */ - crc8_populate_msb(stm_crc8_table, 0x07); + crc8_populate_msb(stm_crc8_table, STM_IPC_CRC8_POLYNOMIAL); /* Initialize DebugFS subdirectory under /sys/kernel/debug/stm_ipc/ */ parent = debugfs_create_dir("stm_ipc", NULL); From fa00235fa0d390a6bebed01b39ede8622d79c6dc Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Wed, 8 Jul 2026 17:43:03 -0600 Subject: [PATCH 11/32] extcon: stm32-ipc: add Kconfig dependency on OF Add a dependency on CONFIG_OF for CONFIG_EXTCON_STM32_SPI_IPC to prevent compile/configuration failures on architectures where Device Tree is not supported. --- drivers/extcon/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 910f1eb15b121..2681b3465c1b1 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -178,7 +178,7 @@ config EXTCON_USB_GPIO config EXTCON_STM32_SPI_IPC tristate "STM32 SPI IPC and Virtual Extcon support" - depends on SPI + depends on SPI && OF select CRC8 help Say Y here to enable the STM32 SPI IPC and Virtual Extcon driver, From a76fffd64042f41c6d204a3c37430627929944f1 Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Wed, 8 Jul 2026 17:43:17 -0600 Subject: [PATCH 12/32] extcon: stm32-ipc: fix declarations after statements Move variable declarations in stm_usb_connector_probe() and stm_ipc_probe() to the top of functions. This resolves compilation failures under -Wdeclaration-after-statement. --- drivers/extcon/extcon-stm32-ipc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index 79298b811fad3..baea9ef6ca7c1 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -177,6 +177,8 @@ static int stm_usb_connector_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct stm_connector_priv *priv; + struct stm_ipc_priv *parent_priv; + char name[32]; int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); @@ -202,9 +204,8 @@ static int stm_usb_connector_probe(struct platform_device *pdev) platform_set_drvdata(pdev, priv); /* Set up DebugFS for this virtual connector for runtime simulation */ - struct stm_ipc_priv *parent_priv = dev_get_drvdata(dev->parent); + parent_priv = dev_get_drvdata(dev->parent); if (parent_priv && parent_priv->debugfs_root) { - char name[32]; snprintf(name, sizeof(name), "usb%d_sim", priv->port_id + 1); debugfs_create_file(name, 0200, parent_priv->debugfs_root, priv, &stm_ipc_sim_fops); } @@ -231,6 +232,7 @@ static struct platform_driver stm_usb_connector_driver = { static int stm_ipc_probe(struct spi_device *spi) { struct stm_ipc_priv *priv; + struct dentry *parent; int ret; priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL); @@ -241,8 +243,6 @@ static int stm_ipc_probe(struct spi_device *spi) mutex_init(&priv->lock); spi_set_drvdata(spi, priv); - struct dentry *parent; - /* Setup CRC8 lookup table (polynomial 0x07) */ crc8_populate_msb(stm_crc8_table, STM_IPC_CRC8_POLYNOMIAL); From 4033d4280efe7eef121faa63c36f7cb05ae359b5 Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Wed, 8 Jul 2026 17:43:23 -0600 Subject: [PATCH 13/32] extcon: stm32-ipc: validate port-id DT property Validate the 'port-id' Device Tree property in stm_usb_connector_probe() to ensure it fits within [0, 255] before casting it to u8. Avoids silently ignoring out-of-bounds inputs from the device tree. --- drivers/extcon/extcon-stm32-ipc.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index baea9ef6ca7c1..2593374184af0 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -51,7 +51,7 @@ struct stm_ipc_priv { /* Child connector platform device private structure */ struct stm_connector_priv { struct extcon_dev *edev; - u32 port_id; + u8 port_id; }; static const unsigned int stm_usb_cable[] = { @@ -179,17 +179,23 @@ static int stm_usb_connector_probe(struct platform_device *pdev) struct stm_connector_priv *priv; struct stm_ipc_priv *parent_priv; char name[32]; + u32 val; int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; - ret = of_property_read_u32(dev->of_node, "port-id", &priv->port_id); + ret = of_property_read_u32(dev->of_node, "port-id", &val); if (ret) { dev_err(dev, "Missing 'port-id' property\n"); return ret; } + if (val > 255) { + dev_err(dev, "Invalid 'port-id' value: %u (must be <= 255)\n", val); + return -EINVAL; + } + priv->port_id = (u8)val; priv->edev = devm_extcon_dev_allocate(dev, stm_usb_cable); if (IS_ERR(priv->edev)) From a18f363607f091fe5e4ed4a0dc8e69658ae01f8c Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Wed, 8 Jul 2026 17:43:30 -0600 Subject: [PATCH 14/32] extcon: stm32-ipc: use dev_warn_ratelimited Replace pr_warn_ratelimited() with dev_warn_ratelimited(&edev->dev, ...) in stm_ipc_update_state(). This logs the warning in the context of the specific extcon device instance, making debugging easier. --- drivers/extcon/extcon-stm32-ipc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index 2593374184af0..c1706d5379c8f 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -80,7 +80,7 @@ static void stm_ipc_update_state(struct extcon_dev *edev, u8 state) /* Fallback to safe disconnected state on protocol error */ extcon_set_state_sync(edev, EXTCON_USB, false); extcon_set_state_sync(edev, EXTCON_USB_HOST, false); - pr_warn_ratelimited("stm-spi-ipc: Invalid USB connector state: %u\n", state); + dev_warn_ratelimited(&edev->dev, "Invalid USB connector state: %u\n", state); break; } } From af221468bd7dcee6fd574c1ae1eba8674c9dafc6 Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Wed, 8 Jul 2026 17:43:37 -0600 Subject: [PATCH 15/32] extcon: stm32-ipc: initialize CRC-8 table once Move crc8_populate_msb() from stm_ipc_probe() to stm_ipc_init(). Populating the table during module initialization instead of probe prevents a concurrent write race condition when multiple devices are probed. --- drivers/extcon/extcon-stm32-ipc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index c1706d5379c8f..713b35a566028 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -249,8 +249,7 @@ static int stm_ipc_probe(struct spi_device *spi) mutex_init(&priv->lock); spi_set_drvdata(spi, priv); - /* Setup CRC8 lookup table (polynomial 0x07) */ - crc8_populate_msb(stm_crc8_table, STM_IPC_CRC8_POLYNOMIAL); + /* Initialize DebugFS subdirectory under /sys/kernel/debug/stm_ipc/ */ parent = debugfs_create_dir("stm_ipc", NULL); @@ -317,6 +316,9 @@ static int __init stm_ipc_init(void) { int ret; + /* Setup CRC8 lookup table (polynomial 0x07) once */ + crc8_populate_msb(stm_crc8_table, STM_IPC_CRC8_POLYNOMIAL); + ret = spi_register_driver(&stm_ipc_driver); if (ret) return ret; From 7db84be700f91ebea477be9d177a8fea3e2fad1d Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Wed, 8 Jul 2026 17:43:44 -0600 Subject: [PATCH 16/32] extcon: stm32-ipc: fix debugfs lifetime management Create the top-level 'stm_ipc' debugfs parent directory once globally during module initialization and remove it during module exit. Per-device debugfs subdirectories are created under this parent directory during probe. --- drivers/extcon/extcon-stm32-ipc.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index 713b35a566028..c8e060f63a89b 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -41,6 +41,7 @@ struct stm_ipc_packet { /* Parent SPI controller private structure */ DECLARE_CRC8_TABLE(stm_crc8_table); +static struct dentry *stm_ipc_debugfs_dir; struct stm_ipc_priv { struct spi_device *spi; @@ -238,7 +239,6 @@ static struct platform_driver stm_usb_connector_driver = { static int stm_ipc_probe(struct spi_device *spi) { struct stm_ipc_priv *priv; - struct dentry *parent; int ret; priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL); @@ -252,16 +252,14 @@ static int stm_ipc_probe(struct spi_device *spi) /* Initialize DebugFS subdirectory under /sys/kernel/debug/stm_ipc/ */ - parent = debugfs_create_dir("stm_ipc", NULL); - if (IS_ERR_OR_NULL(parent)) { - dev_warn(&spi->dev, "Failed to create debugfs parent directory\n"); - priv->debugfs_root = NULL; - } else { - priv->debugfs_root = debugfs_create_dir(dev_name(&spi->dev), parent); + if (stm_ipc_debugfs_dir) { + priv->debugfs_root = debugfs_create_dir(dev_name(&spi->dev), stm_ipc_debugfs_dir); if (IS_ERR_OR_NULL(priv->debugfs_root)) { dev_warn(&spi->dev, "Failed to create debugfs root directory\n"); priv->debugfs_root = NULL; } + } else { + priv->debugfs_root = NULL; } /* Populate subnodes as platform devices (this probes the connector sub-drivers) */ @@ -319,22 +317,32 @@ static int __init stm_ipc_init(void) /* Setup CRC8 lookup table (polynomial 0x07) once */ crc8_populate_msb(stm_crc8_table, STM_IPC_CRC8_POLYNOMIAL); + /* Setup global debugfs parent directory */ + stm_ipc_debugfs_dir = debugfs_create_dir("stm_ipc", NULL); + if (IS_ERR_OR_NULL(stm_ipc_debugfs_dir)) + stm_ipc_debugfs_dir = NULL; + ret = spi_register_driver(&stm_ipc_driver); if (ret) - return ret; + goto err_debugfs; ret = platform_driver_register(&stm_usb_connector_driver); if (ret) { spi_unregister_driver(&stm_ipc_driver); - return ret; + goto err_debugfs; } return 0; + +err_debugfs: + debugfs_remove_recursive(stm_ipc_debugfs_dir); + return ret; } static void __exit stm_ipc_exit(void) { spi_unregister_driver(&stm_ipc_driver); platform_driver_unregister(&stm_usb_connector_driver); + debugfs_remove_recursive(stm_ipc_debugfs_dir); } module_init(stm_ipc_init); From 57f6c94ad87bd03f2779056b8771403f7954fde3 Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Wed, 8 Jul 2026 18:14:53 -0600 Subject: [PATCH 17/32] extcon: stm32-ipc: cache states to reduce churn Read the current cable states of EXTCON_USB and EXTCON_USB_HOST first in stm_ipc_update_state(). Only invoke extcon_set_state_sync() when a state bit actually changes, clearing the opposite state first. This avoids unnecessary extcon uevent updates and userspace churn. --- drivers/extcon/extcon-stm32-ipc.c | 39 ++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index c8e060f63a89b..f40874a60f3ab 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -64,26 +64,49 @@ static const unsigned int stm_usb_cable[] = { /* Helper to update extcon state based on STM32 reporting */ static void stm_ipc_update_state(struct extcon_dev *edev, u8 state) { + int usb_state = extcon_get_state(edev, EXTCON_USB); + int host_state = extcon_get_state(edev, EXTCON_USB_HOST); + bool want_usb = false; + bool want_host = false; + + if (usb_state < 0 || host_state < 0) { + dev_err_ratelimited(&edev->dev, "Failed to get current extcon state\n"); + return; + } + switch (state) { case STM_IPC_USB_STATE_DISCONNECTED: - extcon_set_state_sync(edev, EXTCON_USB, false); - extcon_set_state_sync(edev, EXTCON_USB_HOST, false); + want_usb = false; + want_host = false; break; case STM_IPC_USB_STATE_PERIPHERAL: - extcon_set_state_sync(edev, EXTCON_USB_HOST, false); - extcon_set_state_sync(edev, EXTCON_USB, true); + want_usb = true; + want_host = false; break; case STM_IPC_USB_STATE_HOST: - extcon_set_state_sync(edev, EXTCON_USB, false); - extcon_set_state_sync(edev, EXTCON_USB_HOST, true); + want_usb = false; + want_host = true; break; default: /* Fallback to safe disconnected state on protocol error */ - extcon_set_state_sync(edev, EXTCON_USB, false); - extcon_set_state_sync(edev, EXTCON_USB_HOST, false); + want_usb = false; + want_host = false; dev_warn_ratelimited(&edev->dev, "Invalid USB connector state: %u\n", state); break; } + + /* Only update states if they have actually changed, clearing opposite first */ + if (want_usb != usb_state || want_host != host_state) { + if (!want_usb && usb_state) + extcon_set_state_sync(edev, EXTCON_USB, false); + if (!want_host && host_state) + extcon_set_state_sync(edev, EXTCON_USB_HOST, false); + + if (want_usb && !usb_state) + extcon_set_state_sync(edev, EXTCON_USB, true); + if (want_host && !host_state) + extcon_set_state_sync(edev, EXTCON_USB_HOST, true); + } } /* Callback used to find and update state of correct child platform device */ From 9d2588b0ee1efcc0f2937675dae23137b988b489 Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Wed, 8 Jul 2026 18:14:59 -0600 Subject: [PATCH 18/32] extcon: stm32-ipc: rate-limit SPI transfer error logs Replace dev_err() with dev_err_ratelimited() on SPI sync transfer failures in stm_ipc_threaded_irq(). This avoids flooding the kernel log in case the SPI bus becomes misconfigured or experiences noise. --- drivers/extcon/extcon-stm32-ipc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index f40874a60f3ab..36d46d8431967 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -145,7 +145,7 @@ static irqreturn_t stm_ipc_threaded_irq(int irq, void *dev_id) spi_message_add_tail(&t, &m); ret = spi_sync(priv->spi, &m); if (ret < 0) { - dev_err(&priv->spi->dev, "SPI sync transfer failed: %d\n", ret); + dev_err_ratelimited(&priv->spi->dev, "SPI sync transfer failed: %d\n", ret); goto out; } From 39b5db701840241cc397c54bff20179a2045c72b Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Wed, 8 Jul 2026 18:15:05 -0600 Subject: [PATCH 19/32] extcon: stm32-ipc: warn on unmatched connector ports Check the return value of device_for_each_child() when dispatching USB events. If no child platform connector matches the reported port ID, emit a rate-limited warning log to assist in troubleshooting Device Tree or protocol mismatches. --- drivers/extcon/extcon-stm32-ipc.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index 36d46d8431967..59a066b367ffc 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -175,7 +175,10 @@ static irqreturn_t stm_ipc_threaded_irq(int irq, void *dev_id) u8 port_info[2] = { rx_buf.port, rx_buf.state }; dev_dbg(&priv->spi->dev, "USB Event from STM32 on Port %d: State %d\n", rx_buf.port, rx_buf.state); - device_for_each_child(&priv->spi->dev, port_info, match_and_update_state); + ret = device_for_each_child(&priv->spi->dev, port_info, match_and_update_state); + if (ret == 0) { + dev_warn_ratelimited(&priv->spi->dev, "No matching USB connector found for Port %d\n", rx_buf.port); + } } out: From f61e32cd3f20d8d57db9336b0d57eb9f36d0a311 Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Wed, 8 Jul 2026 18:15:12 -0600 Subject: [PATCH 20/32] extcon: stm32-ipc: clean up debugfs to avoid UAF Register a devm device cleanup action in stm_usb_connector_probe() to automatically call debugfs_remove() on the connector's simulation file when the connector device is unbound. This prevents a potential use-after-free (UAF) if userspace interacts with the simulation file after the device is unbound. --- drivers/extcon/extcon-stm32-ipc.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index 59a066b367ffc..25d31877ffa26 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -199,6 +199,13 @@ static int stm_ipc_sim_write(void *data, u64 val) } DEFINE_DEBUGFS_ATTRIBUTE(stm_ipc_sim_fops, NULL, stm_ipc_sim_write, "%llu\n"); +static void stm_ipc_debugfs_cleanup(void *data) +{ + struct dentry *dentry = data; + + debugfs_remove(dentry); +} + /* Child Connector Platform Device Probe */ static int stm_usb_connector_probe(struct platform_device *pdev) { @@ -239,8 +246,17 @@ static int stm_usb_connector_probe(struct platform_device *pdev) /* Set up DebugFS for this virtual connector for runtime simulation */ parent_priv = dev_get_drvdata(dev->parent); if (parent_priv && parent_priv->debugfs_root) { + struct dentry *sim_file; + snprintf(name, sizeof(name), "usb%d_sim", priv->port_id + 1); - debugfs_create_file(name, 0200, parent_priv->debugfs_root, priv, &stm_ipc_sim_fops); + sim_file = debugfs_create_file(name, 0200, parent_priv->debugfs_root, priv, &stm_ipc_sim_fops); + if (!IS_ERR_OR_NULL(sim_file)) { + ret = devm_add_action_or_reset(dev, stm_ipc_debugfs_cleanup, sim_file); + if (ret) { + dev_err(dev, "Failed to register debugfs cleanup action: %d\n", ret); + return ret; + } + } } dev_info(dev, "Registered virtual USB connector on Port %d\n", priv->port_id); From 9ec6b447a26380355605de3d42ad2f34442e03f3 Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Wed, 8 Jul 2026 18:15:20 -0600 Subject: [PATCH 21/32] extcon: stm32-ipc: register platform driver first Register the platform connector driver stm_usb_connector_driver before registering the SPI core driver stm_ipc_driver during module initialization. This ensures child connector devices can bind immediately when devm_of_platform_populate() is called during SPI device probe, preventing events from being dropped. Swap unregistration order in exit. --- drivers/extcon/extcon-stm32-ipc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index 25d31877ffa26..c9a91f4fa7726 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -364,13 +364,13 @@ static int __init stm_ipc_init(void) if (IS_ERR_OR_NULL(stm_ipc_debugfs_dir)) stm_ipc_debugfs_dir = NULL; - ret = spi_register_driver(&stm_ipc_driver); + ret = platform_driver_register(&stm_usb_connector_driver); if (ret) goto err_debugfs; - ret = platform_driver_register(&stm_usb_connector_driver); + ret = spi_register_driver(&stm_ipc_driver); if (ret) { - spi_unregister_driver(&stm_ipc_driver); + platform_driver_unregister(&stm_usb_connector_driver); goto err_debugfs; } From 371883f9befd1edbf443e10ec576107cde2fc1c5 Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Wed, 8 Jul 2026 19:37:36 -0600 Subject: [PATCH 22/32] extcon: stm32-ipc: pass device struct directly Do not dereference edev->dev to log messages in stm_ipc_update_state() since struct extcon_dev is opaque to client drivers. Instead, store the device pointer in struct stm_connector_priv and pass it explicitly to stm_ipc_update_state() from all call sites. --- drivers/extcon/extcon-stm32-ipc.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index c9a91f4fa7726..6f3940402a067 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -51,6 +51,7 @@ struct stm_ipc_priv { /* Child connector platform device private structure */ struct stm_connector_priv { + struct device *dev; struct extcon_dev *edev; u8 port_id; }; @@ -62,7 +63,7 @@ static const unsigned int stm_usb_cable[] = { }; /* Helper to update extcon state based on STM32 reporting */ -static void stm_ipc_update_state(struct extcon_dev *edev, u8 state) +static void stm_ipc_update_state(struct device *dev, struct extcon_dev *edev, u8 state) { int usb_state = extcon_get_state(edev, EXTCON_USB); int host_state = extcon_get_state(edev, EXTCON_USB_HOST); @@ -70,7 +71,7 @@ static void stm_ipc_update_state(struct extcon_dev *edev, u8 state) bool want_host = false; if (usb_state < 0 || host_state < 0) { - dev_err_ratelimited(&edev->dev, "Failed to get current extcon state\n"); + dev_err_ratelimited(dev, "Failed to get current extcon state\n"); return; } @@ -91,7 +92,7 @@ static void stm_ipc_update_state(struct extcon_dev *edev, u8 state) /* Fallback to safe disconnected state on protocol error */ want_usb = false; want_host = false; - dev_warn_ratelimited(&edev->dev, "Invalid USB connector state: %u\n", state); + dev_warn_ratelimited(dev, "Invalid USB connector state: %u\n", state); break; } @@ -118,7 +119,7 @@ static int match_and_update_state(struct device *dev, void *data) u8 state = port_info[1]; if (priv && priv->port_id == port_id) { - stm_ipc_update_state(priv->edev, state); + stm_ipc_update_state(dev, priv->edev, state); return 1; /* Found and processed, stop iteration */ } return 0; @@ -194,7 +195,7 @@ static int stm_ipc_sim_write(void *data, u64 val) if (val > STM_IPC_USB_STATE_HOST) return -EINVAL; - stm_ipc_update_state(priv->edev, (u8)val); + stm_ipc_update_state(priv->dev, priv->edev, (u8)val); return 0; } DEFINE_DEBUGFS_ATTRIBUTE(stm_ipc_sim_fops, NULL, stm_ipc_sim_write, "%llu\n"); @@ -220,6 +221,8 @@ static int stm_usb_connector_probe(struct platform_device *pdev) if (!priv) return -ENOMEM; + priv->dev = dev; + ret = of_property_read_u32(dev->of_node, "port-id", &val); if (ret) { dev_err(dev, "Missing 'port-id' property\n"); From 8e20baae93f134989f0f268fb6740e4028519007 Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Mon, 13 Jul 2026 09:37:10 -0600 Subject: [PATCH 23/32] extcon: stm32-ipc: fix debugfs double-free on unbind Each connector's sim file was torn down by a devres action registered in stm_usb_connector_probe(), while the parent SPI driver *also* removed the whole subtree via debugfs_remove_recursive(priv->debugfs_root) in its .remove() and probe error path. On unbind the recursive removal frees the connectors' sim-file dentries first, then each connector's devres action runs debugfs_remove() on the already-freed dentry -> use-after-free. Keep the per-connector cleanup -- it is what prevents a use-after-free of the debugfs data pointer when a connector is unbound on its own -- and tear the parent directory down through devres as well. Registering that action before devm_of_platform_populate() makes it run *after* the connectors are depopulated (LIFO devres order), so each connector removes its own sim file first and the parent action only reaps the now-empty directory. With teardown fully handled by devres, the manual recursive removal in the probe error path and .remove() is gone, leaving .remove() empty, so drop it. --- drivers/extcon/extcon-stm32-ipc.c | 35 +++++++++++++++++-------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index 6f3940402a067..1cabb3c51d46c 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -204,7 +204,7 @@ static void stm_ipc_debugfs_cleanup(void *data) { struct dentry *dentry = data; - debugfs_remove(dentry); + debugfs_remove_recursive(dentry); } /* Child Connector Platform Device Probe */ @@ -294,14 +294,28 @@ static int stm_ipc_probe(struct spi_device *spi) mutex_init(&priv->lock); spi_set_drvdata(spi, priv); - - /* Initialize DebugFS subdirectory under /sys/kernel/debug/stm_ipc/ */ if (stm_ipc_debugfs_dir) { priv->debugfs_root = debugfs_create_dir(dev_name(&spi->dev), stm_ipc_debugfs_dir); if (IS_ERR_OR_NULL(priv->debugfs_root)) { dev_warn(&spi->dev, "Failed to create debugfs root directory\n"); priv->debugfs_root = NULL; + } else { + /* + * Tear the directory down via devres. Registering the + * action *before* devm_of_platform_populate() means it + * runs *after* the child connectors are depopulated (LIFO + * devres order), so each connector removes its own sim + * file first and this only reaps the now-empty directory. + * That avoids the double-free that a direct + * debugfs_remove_recursive() here would cause against the + * connectors' per-file cleanup. + */ + ret = devm_add_action_or_reset(&spi->dev, + stm_ipc_debugfs_cleanup, + priv->debugfs_root); + if (ret) + return ret; } } else { priv->debugfs_root = NULL; @@ -311,7 +325,7 @@ static int stm_ipc_probe(struct spi_device *spi) ret = devm_of_platform_populate(&spi->dev); if (ret) { dev_err(&spi->dev, "Failed to populate child connectors: %d\n", ret); - goto err_debugfs; + return ret; } /* Request Attention Pin Interrupt */ @@ -322,22 +336,12 @@ static int stm_ipc_probe(struct spi_device *spi) DRIVER_NAME, priv); if (ret) { dev_err(&spi->dev, "Failed to request threaded IRQ: %d\n", ret); - goto err_debugfs; + return ret; } } dev_info(&spi->dev, "STM32 SPI IPC Core initialized\n"); return 0; - -err_debugfs: - debugfs_remove_recursive(priv->debugfs_root); - return ret; -} - -static void stm_ipc_remove(struct spi_device *spi) -{ - struct stm_ipc_priv *priv = spi_get_drvdata(spi); - debugfs_remove_recursive(priv->debugfs_root); } static const struct of_device_id stm_ipc_of_match[] = { @@ -352,7 +356,6 @@ static struct spi_driver stm_ipc_driver = { .of_match_table = stm_ipc_of_match, }, .probe = stm_ipc_probe, - .remove = stm_ipc_remove, }; static int __init stm_ipc_init(void) From 2bfa6ab3e76dc5a951d1b178d8919242c2eb05c0 Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Mon, 13 Jul 2026 09:47:14 -0600 Subject: [PATCH 24/32] dt-bindings: extcon: add multitracks,stm32-spi-ipc Document the STM32 SPI IPC device and its child USB connector nodes. The SPI peripheral talks to an external STM32 co-processor that reports USB cable/role events over SPI (signalled by an attention interrupt); each child connector node is an extcon provider for one i.MX8M USB OTG controller and carries a required port-id. These compatibles ("multitracks,stm32-spi-ipc" and "multitracks,stm32-usb-connector") are used by the mt-connect device tree but were previously undocumented, which triggers dtbs_check warnings. --- .../extcon/multitracks,stm32-spi-ipc.yaml | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 Documentation/devicetree/bindings/extcon/multitracks,stm32-spi-ipc.yaml diff --git a/Documentation/devicetree/bindings/extcon/multitracks,stm32-spi-ipc.yaml b/Documentation/devicetree/bindings/extcon/multitracks,stm32-spi-ipc.yaml new file mode 100644 index 0000000000000..5d23c67d39c12 --- /dev/null +++ b/Documentation/devicetree/bindings/extcon/multitracks,stm32-spi-ipc.yaml @@ -0,0 +1,95 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/extcon/multitracks,stm32-spi-ipc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MultiTracks STM32 SPI IPC USB extcon provider + +maintainers: + - Samuel Morris + +description: | + An SPI client that talks to an external STM32 co-processor. The STM32 reports + USB cable/role events (disconnected, peripheral or host) for each physical USB + port over SPI, signalled by a dedicated attention (interrupt) line. + + Each USB port is described by a child connector node that acts as an extcon + provider (EXTCON_USB / EXTCON_USB_HOST) for the USB OTG controller wired to + that port, allowing it to switch between host and peripheral roles. + +properties: + compatible: + const: multitracks,stm32-spi-ipc + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + description: + Attention line, driven by the STM32 co-processor to signal that a new + event packet is pending on the SPI bus. + +patternProperties: + '^connector-usb[0-9]+$': + type: object + description: + A virtual USB connector exposed as an extcon provider. Reference it from + the "extcon" property of the corresponding USB OTG controller node. + + properties: + compatible: + const: multitracks,stm32-usb-connector + + port-id: + $ref: /schemas/types.yaml#/definitions/uint32 + maximum: 255 + description: + Identifier of the USB port on the STM32 co-processor that this + connector represents. It is matched against the "port" field of the + incoming SPI event packet. + + required: + - compatible + - port-id + + additionalProperties: false + +required: + - compatible + - reg + - interrupts + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + + spi { + #address-cells = <1>; + #size-cells = <0>; + + ipc@0 { + compatible = "multitracks,stm32-spi-ipc"; + reg = <0>; + spi-max-frequency = <10000000>; + interrupt-parent = <&gpio2>; + interrupts = <11 IRQ_TYPE_EDGE_FALLING>; + + connector-usb1 { + compatible = "multitracks,stm32-usb-connector"; + port-id = <0>; + }; + + connector-usb2 { + compatible = "multitracks,stm32-usb-connector"; + port-id = <1>; + }; + }; + }; +... From 49b10804a125ffb7f45b816944c3cb3b4a6718e7 Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Mon, 13 Jul 2026 11:29:19 -0600 Subject: [PATCH 25/32] arm64: dts: freescale: mt-connect: wire both extcon phandles The ChipIdea controller parses the "extcon" property as two phandles: index 0 is the VBUS (EXTCON_USB) source and index 1 is the ID (EXTCON_USB_HOST) source. ci_extcon_register() only registers the ID notifier when phandle 1 resolves, independent of usb-role-switch. With a single phandle the EXTCON_USB_HOST events emitted by the connector for host-role detection were never consumed. Point both indices at the same connector -- it provides both cable types -- so peripheral and host notifications are delivered. --- arch/arm64/boot/dts/freescale/mt-connect.dts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/freescale/mt-connect.dts b/arch/arm64/boot/dts/freescale/mt-connect.dts index 949475cda4227..eabd450fa07dc 100644 --- a/arch/arm64/boot/dts/freescale/mt-connect.dts +++ b/arch/arm64/boot/dts/freescale/mt-connect.dts @@ -534,7 +534,14 @@ }; &usbotg1 { - extcon = <&usb1_connector>; + /* + * ChipIdea reads extcon phandle 0 as the VBUS (EXTCON_USB) source and + * phandle 1 as the ID (EXTCON_USB_HOST) source. The connector provides + * both cable types, so point both indices at it; otherwise the ID/host + * notifier is never registered by ci_extcon_register() and host-role + * events are dropped. + */ + extcon = <&usb1_connector>, <&usb1_connector>; hnp-disable; srp-disable; adp-disable; @@ -548,7 +555,8 @@ }; &usbotg2 { - extcon = <&usb2_connector>; + /* Both extcon indices point at the connector; see &usbotg1. */ + extcon = <&usb2_connector>, <&usb2_connector>; hnp-disable; srp-disable; adp-disable; From 35dd274077abd6d36be566c0e2af6c2623e64e0d Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Mon, 13 Jul 2026 11:29:19 -0600 Subject: [PATCH 26/32] extcon: stm32-ipc: track connector match via a flag match_and_update_state() returned 1 to mean "found" so the caller could test device_for_each_child()'s return value. That overloads the iterator return -- which exists to propagate a callback's error code -- as a found/not-found sentinel. Pass a small stm_ipc_port_event payload instead: the callback records the match in event->handled and always returns 0, leaving the return value free to carry a genuine error. The caller warns on a negative return and, separately, when no connector matched the reported port. --- drivers/extcon/extcon-stm32-ipc.c | 39 ++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index 1cabb3c51d46c..756402453852c 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -56,6 +56,13 @@ struct stm_connector_priv { u8 port_id; }; +/* Payload handed to the device_for_each_child() match callback */ +struct stm_ipc_port_event { + u8 port; + u8 state; + bool handled; +}; + static const unsigned int stm_usb_cable[] = { EXTCON_USB, EXTCON_USB_HOST, @@ -114,14 +121,18 @@ static void stm_ipc_update_state(struct device *dev, struct extcon_dev *edev, u8 static int match_and_update_state(struct device *dev, void *data) { struct stm_connector_priv *priv = dev_get_drvdata(dev); - u8 *port_info = data; - u8 port_id = port_info[0]; - u8 state = port_info[1]; + struct stm_ipc_port_event *event = data; - if (priv && priv->port_id == port_id) { - stm_ipc_update_state(dev, priv->edev, state); - return 1; /* Found and processed, stop iteration */ + if (priv && priv->port_id == event->port) { + stm_ipc_update_state(dev, priv->edev, event->state); + event->handled = true; } + + /* + * Always return 0 so the return value of device_for_each_child() stays + * reserved for genuine errors; "was a matching connector found" is + * reported through event->handled instead. + */ return 0; } @@ -173,13 +184,19 @@ static irqreturn_t stm_ipc_threaded_irq(int irq, void *dev_id) /* Process USB event */ if (rx_buf.type == MSG_TYPE_USB_EVENT) { - u8 port_info[2] = { rx_buf.port, rx_buf.state }; + struct stm_ipc_port_event event = { + .port = rx_buf.port, + .state = rx_buf.state, + }; dev_dbg(&priv->spi->dev, "USB Event from STM32 on Port %d: State %d\n", rx_buf.port, rx_buf.state); - ret = device_for_each_child(&priv->spi->dev, port_info, match_and_update_state); - if (ret == 0) { - dev_warn_ratelimited(&priv->spi->dev, "No matching USB connector found for Port %d\n", rx_buf.port); - } + ret = device_for_each_child(&priv->spi->dev, &event, match_and_update_state); + if (ret < 0) + dev_warn_ratelimited(&priv->spi->dev, "Failed to dispatch event for Port %d: %d\n", + rx_buf.port, ret); + else if (!event.handled) + dev_warn_ratelimited(&priv->spi->dev, "No matching USB connector found for Port %d\n", + rx_buf.port); } out: From e3f6b79d463df11a1326cc37a56981509e181787 Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Mon, 13 Jul 2026 13:03:41 -0600 Subject: [PATCH 27/32] extcon: stm32-ipc: take IRQ trigger type from DT Requesting the attention IRQ with a hard-coded IRQF_TRIGGER_FALLING overrides the trigger type described by the device tree "interrupts" property and can provoke a trigger-type mismatch warning if the two ever disagree. Drop the flag and pass only IRQF_ONESHOT; the falling-edge type is already specified by the DT node (IRQ_TYPE_EDGE_FALLING) and configured by the irqchip, so behaviour is unchanged while the binding stays authoritative. --- drivers/extcon/extcon-stm32-ipc.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index 756402453852c..887a45b23e967 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -345,11 +345,15 @@ static int stm_ipc_probe(struct spi_device *spi) return ret; } - /* Request Attention Pin Interrupt */ + /* + * Request the attention line interrupt. The trigger type is taken from + * the device tree ("interrupts"), so only IRQF_ONESHOT is requested here + * to keep the line masked until the threaded handler completes. + */ if (spi->irq > 0) { ret = devm_request_threaded_irq(&spi->dev, spi->irq, NULL, stm_ipc_threaded_irq, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + IRQF_ONESHOT, DRIVER_NAME, priv); if (ret) { dev_err(&spi->dev, "Failed to request threaded IRQ: %d\n", ret); From 04586ee60062dc850abb0871a1a98b9051ba7e43 Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Mon, 13 Jul 2026 13:52:32 -0600 Subject: [PATCH 28/32] arm64: dts: freescale: mt-connect: fix STM IPC interrupt to GPIO4_IO29 The STM32 SPI IPC attention line was wired to GPIO2_IO11 (SD1_STROBE), which is actually the leftover Type-C controller interrupt pad. The real attention/handshake line for the ECSPI2 co-processor is GPIO4_IO29 (SAI3_RXC): the existing user-space implementation reads it as gpiochip3 line 29 on /dev/spidev1.0 (imx8-a53/connect: spi.c SPI_BUSY_GPIO_PIN=29, gpio.h GPIO_CHIP_NAME="/dev/gpiochip3"), and pinctrl_ecspi2 already muxed GPIO4_IO29 with an "ECSPI2 INT" note that nothing consumed. Point the stm_ipc interrupt at gpio4/29 and move the pad into pinctrl_stm_ipc, dropping the now-duplicate entry from pinctrl_ecspi2 so the muxed pin and the interrupt agree. GPIO2_IO11 is freed. The pad keeps the 0x82 setting proven on this line by the user-space driver. --- arch/arm64/boot/dts/freescale/mt-connect.dts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/arch/arm64/boot/dts/freescale/mt-connect.dts b/arch/arm64/boot/dts/freescale/mt-connect.dts index eabd450fa07dc..e657a73f9fc29 100644 --- a/arch/arm64/boot/dts/freescale/mt-connect.dts +++ b/arch/arm64/boot/dts/freescale/mt-connect.dts @@ -506,8 +506,8 @@ compatible = "multitracks,stm32-spi-ipc"; reg = <0>; spi-max-frequency = <10000000>; - interrupt-parent = <&gpio2>; - interrupts = <11 IRQ_TYPE_EDGE_FALLING>; + interrupt-parent = <&gpio4>; + interrupts = <29 IRQ_TYPE_EDGE_FALLING>; usb1_connector: connector-usb1 { compatible = "multitracks,stm32-usb-connector"; @@ -736,7 +736,7 @@ pinctrl_stm_ipc: stmipcgrp { fsl,pins = < - MX8MM_IOMUXC_SD1_STROBE_GPIO2_IO11 0x159 + MX8MM_IOMUXC_SAI3_RXC_GPIO4_IO29 0x82 /* STM IPC attention (ECSPI2 INT) */ >; }; @@ -761,7 +761,6 @@ MX8MM_IOMUXC_ECSPI2_SCLK_ECSPI2_SCLK 0x82 MX8MM_IOMUXC_ECSPI2_SS0_ECSPI2_SS0 0x82 MX8MM_IOMUXC_ECSPI2_SS0_GPIO5_IO13 0x140 /* ECSPI2 NSS */ - MX8MM_IOMUXC_SAI3_RXC_GPIO4_IO29 0x82 /* ECSPI2 INT */ >; }; From 0715ac69db0daf361621a33ffde14598c1785aa1 Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Mon, 13 Jul 2026 14:25:22 -0600 Subject: [PATCH 29/32] extcon: stm32-ipc: require the attention interrupt The attention line is how the STM32 signals pending events; without it the threaded handler never runs and no USB-role events are ever processed. The probe previously skipped devm_request_threaded_irq() when spi->irq was unset and still returned success, so a device tree missing the (binding- required) "interrupts" property would boot looking healthy while the IPC path was silently dead. Fail probe when no interrupt is provided instead. --- drivers/extcon/extcon-stm32-ipc.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index 887a45b23e967..967b42b479b27 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -346,19 +346,24 @@ static int stm_ipc_probe(struct spi_device *spi) } /* - * Request the attention line interrupt. The trigger type is taken from - * the device tree ("interrupts"), so only IRQF_ONESHOT is requested here - * to keep the line masked until the threaded handler completes. + * The attention line is mandatory: without it no STM32 events are ever + * read and the IPC path is silently dead, so refuse to probe rather than + * come up looking healthy. The trigger type is taken from the device tree + * ("interrupts"); only IRQF_ONESHOT is requested here to keep the line + * masked until the threaded handler completes. */ - if (spi->irq > 0) { - ret = devm_request_threaded_irq(&spi->dev, spi->irq, NULL, - stm_ipc_threaded_irq, - IRQF_ONESHOT, - DRIVER_NAME, priv); - if (ret) { - dev_err(&spi->dev, "Failed to request threaded IRQ: %d\n", ret); - return ret; - } + if (spi->irq <= 0) { + dev_err(&spi->dev, "Missing attention interrupt\n"); + return spi->irq < 0 ? spi->irq : -ENXIO; + } + + ret = devm_request_threaded_irq(&spi->dev, spi->irq, NULL, + stm_ipc_threaded_irq, + IRQF_ONESHOT, + DRIVER_NAME, priv); + if (ret) { + dev_err(&spi->dev, "Failed to request threaded IRQ: %d\n", ret); + return ret; } dev_info(&spi->dev, "STM32 SPI IPC Core initialized\n"); From bc6331c09e63f68413cb6508a9d7a781238af425 Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Mon, 13 Jul 2026 14:26:16 -0600 Subject: [PATCH 30/32] extcon: stm32-ipc: clean up comments and line lengths No functional change. Address checkpatch --strict nits: drop the file name from the header comment, describe what priv->lock protects, move the misplaced "parent SPI controller" comment onto the struct it documents, wrap the lines that exceeded 100 columns, and add the missing blank line between stm_ipc_init() and stm_ipc_exit(). --- drivers/extcon/extcon-stm32-ipc.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index 967b42b479b27..0aba5f13db209 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * drivers/extcon/extcon-stm32-ipc.c - STM32 SPI IPC and Virtual Extcon Driver + * STM32 SPI IPC and virtual extcon driver * * Copyright (C) 2026 MultiTracks */ @@ -39,13 +39,14 @@ struct stm_ipc_packet { u8 crc; } __packed; -/* Parent SPI controller private structure */ +/* Module-wide CRC-8 table and debugfs parent, shared by all instances */ DECLARE_CRC8_TABLE(stm_crc8_table); static struct dentry *stm_ipc_debugfs_dir; +/* Parent SPI controller private structure */ struct stm_ipc_priv { struct spi_device *spi; - struct mutex lock; + struct mutex lock; /* serialises SPI packet transfers */ struct dentry *debugfs_root; }; @@ -168,7 +169,8 @@ static irqreturn_t stm_ipc_threaded_irq(int irq, void *dev_id) } /* Validate packet CRC */ - calc_crc = crc8(stm_crc8_table, (const u8 *)&rx_buf, offsetof(struct stm_ipc_packet, crc), 0); + calc_crc = crc8(stm_crc8_table, (const u8 *)&rx_buf, + offsetof(struct stm_ipc_packet, crc), 0); if (calc_crc != rx_buf.crc) { dev_warn_ratelimited(&priv->spi->dev, "CRC mismatch: read 0x%02x, calculated 0x%02x\n", rx_buf.crc, calc_crc); @@ -189,7 +191,8 @@ static irqreturn_t stm_ipc_threaded_irq(int irq, void *dev_id) .state = rx_buf.state, }; - dev_dbg(&priv->spi->dev, "USB Event from STM32 on Port %d: State %d\n", rx_buf.port, rx_buf.state); + dev_dbg(&priv->spi->dev, "USB Event from STM32 on Port %d: State %d\n", + rx_buf.port, rx_buf.state); ret = device_for_each_child(&priv->spi->dev, &event, match_and_update_state); if (ret < 0) dev_warn_ratelimited(&priv->spi->dev, "Failed to dispatch event for Port %d: %d\n", @@ -269,11 +272,13 @@ static int stm_usb_connector_probe(struct platform_device *pdev) struct dentry *sim_file; snprintf(name, sizeof(name), "usb%d_sim", priv->port_id + 1); - sim_file = debugfs_create_file(name, 0200, parent_priv->debugfs_root, priv, &stm_ipc_sim_fops); + sim_file = debugfs_create_file(name, 0200, parent_priv->debugfs_root, + priv, &stm_ipc_sim_fops); if (!IS_ERR_OR_NULL(sim_file)) { ret = devm_add_action_or_reset(dev, stm_ipc_debugfs_cleanup, sim_file); if (ret) { - dev_err(dev, "Failed to register debugfs cleanup action: %d\n", ret); + dev_err(dev, "Failed to register debugfs cleanup action: %d\n", + ret); return ret; } } @@ -412,6 +417,7 @@ static int __init stm_ipc_init(void) debugfs_remove_recursive(stm_ipc_debugfs_dir); return ret; } + static void __exit stm_ipc_exit(void) { spi_unregister_driver(&stm_ipc_driver); From 0c7d705da3de8f0d85387339e1e9429a2a6483c1 Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Mon, 13 Jul 2026 14:26:16 -0600 Subject: [PATCH 31/32] arm64: dts: freescale: mt-connect: drop redundant ECSPI2 SS0 pinmux pinctrl_ecspi2 muxed the ECSPI2_SS0 pad twice -- once as native ECSPI2_SS0 and once as GPIO5_IO13 -- which are the same pad (IOMUXC mux-ctl 0x210) configured two ways. The controller uses cs-gpios = <&gpio5 13 ...>, so the pad must be a GPIO; the native SS0 entry is redundant and only muddies which mux actually takes effect. Keep the GPIO5_IO13 mux and drop the native one. --- arch/arm64/boot/dts/freescale/mt-connect.dts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/freescale/mt-connect.dts b/arch/arm64/boot/dts/freescale/mt-connect.dts index e657a73f9fc29..9e7dbcf3bfa4d 100644 --- a/arch/arm64/boot/dts/freescale/mt-connect.dts +++ b/arch/arm64/boot/dts/freescale/mt-connect.dts @@ -759,8 +759,7 @@ MX8MM_IOMUXC_ECSPI2_MISO_ECSPI2_MISO 0x82 MX8MM_IOMUXC_ECSPI2_MOSI_ECSPI2_MOSI 0x82 MX8MM_IOMUXC_ECSPI2_SCLK_ECSPI2_SCLK 0x82 - MX8MM_IOMUXC_ECSPI2_SS0_ECSPI2_SS0 0x82 - MX8MM_IOMUXC_ECSPI2_SS0_GPIO5_IO13 0x140 /* ECSPI2 NSS */ + MX8MM_IOMUXC_ECSPI2_SS0_GPIO5_IO13 0x140 /* ECSPI2 NSS (cs-gpios) */ >; }; From 18f98261adea7b5fed735d0f1cf6631f5a51b7a2 Mon Sep 17 00:00:00 2001 From: Overdr0ne Date: Mon, 13 Jul 2026 15:04:48 -0600 Subject: [PATCH 32/32] extcon: stm32-ipc: narrow irq handler lock scope Release the SPI IPC serialization mutex immediately after completing the spi_sync() transfer in the threaded IRQ handler. Holding the lock during packet validation and extcon state updates (which can sleep and trigger userspace uevents) extends the critical section unnecessarily and could block other SPI operations. --- drivers/extcon/extcon-stm32-ipc.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/extcon/extcon-stm32-ipc.c b/drivers/extcon/extcon-stm32-ipc.c index 0aba5f13db209..a43d8a06e3916 100644 --- a/drivers/extcon/extcon-stm32-ipc.c +++ b/drivers/extcon/extcon-stm32-ipc.c @@ -157,15 +157,18 @@ static irqreturn_t stm_ipc_threaded_irq(int irq, void *dev_id) spi_message_init(&m); spi_message_add_tail(&t, &m); ret = spi_sync(priv->spi, &m); + + mutex_unlock(&priv->lock); + if (ret < 0) { dev_err_ratelimited(&priv->spi->dev, "SPI sync transfer failed: %d\n", ret); - goto out; + return IRQ_HANDLED; } /* Validate magic byte */ if (rx_buf.magic != STM_IPC_MSG_MAGIC) { dev_warn_ratelimited(&priv->spi->dev, "Invalid magic byte: 0x%02x\n", rx_buf.magic); - goto out; + return IRQ_HANDLED; } /* Validate packet CRC */ @@ -174,14 +177,14 @@ static irqreturn_t stm_ipc_threaded_irq(int irq, void *dev_id) if (calc_crc != rx_buf.crc) { dev_warn_ratelimited(&priv->spi->dev, "CRC mismatch: read 0x%02x, calculated 0x%02x\n", rx_buf.crc, calc_crc); - goto out; + return IRQ_HANDLED; } /* Validate packet payload length for USB events */ if (rx_buf.type == MSG_TYPE_USB_EVENT && rx_buf.length != STM_IPC_MSG_LEN_USB_EVENT) { dev_warn_ratelimited(&priv->spi->dev, "Invalid packet length: %u (expected %d)\n", rx_buf.length, STM_IPC_MSG_LEN_USB_EVENT); - goto out; + return IRQ_HANDLED; } /* Process USB event */ @@ -202,8 +205,6 @@ static irqreturn_t stm_ipc_threaded_irq(int irq, void *dev_id) rx_buf.port); } -out: - mutex_unlock(&priv->lock); return IRQ_HANDLED; }