From 718ba914a00cde53f5df0c3de369333d0ddcd5a5 Mon Sep 17 00:00:00 2001 From: Samuel Morris Date: Mon, 1 Jun 2026 14:32:17 -0700 Subject: [PATCH 01/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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");