[PATCH 06/16] usb: typec: add FUSB340 SuperSpeed switch driver

Fabrice Gasnier <[email protected]>
Newsgroups org.infradead.lists.linux-phy,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel,org.kernel.vger.linux-usb
Message-ID <20260821-ucpd-host-fusb340-v7-2-rfc-v1-6-c5e27cbc0795@foss.st.com>
Add a platform driver for the ON Semiconductor FUSB340 SuperSpeed switch.
The driver uses enable-gpios and select-gpios to control the switch and
registers Type-C switch and mux providers for Type-C applications where a
reversible cable requires a switch.

Assisted-by: GitHub-Copilot:GPT-5.4-mini
Signed-off-by: Fabrice Gasnier <[email protected]>
---
 drivers/usb/typec/mux/Kconfig   |   8 ++
 drivers/usb/typec/mux/Makefile  |   1 +
 drivers/usb/typec/mux/fusb340.c | 169 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 178 insertions(+)

diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig
index 6dd8f961b593..6a1da48a2a8f 100644
--- a/drivers/usb/typec/mux/Kconfig
+++ b/drivers/usb/typec/mux/Kconfig
@@ -12,6 +12,14 @@ config TYPEC_MUX_FSA4480
 	  common USB Type-C connector.
 	  If compiled as a module, the module will be named fsa4480.
 
+config TYPEC_MUX_FUSB340
+	tristate "ON Semi FUSB340 SuperSpeed switch driver"
+	help
+	  Driver for the ON Semiconductor FUSB340 SuperSpeed switch, which
+	  provides support for routing SuperSpeed pairs in Type-C applications
+	  where a reversible cable requires a switch.
+	  If compiled as a module, the module will be named fusb340.
+
 config TYPEC_MUX_GPIO_SBU
 	tristate "Generic GPIO based SBU mux for USB Type-C applications"
 	help
diff --git a/drivers/usb/typec/mux/Makefile b/drivers/usb/typec/mux/Makefile
index b4f599eb5053..ef8d2b05ff7f 100644
--- a/drivers/usb/typec/mux/Makefile
+++ b/drivers/usb/typec/mux/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 
 obj-$(CONFIG_TYPEC_MUX_FSA4480)		+= fsa4480.o
+obj-$(CONFIG_TYPEC_MUX_FUSB340)		+= fusb340.o
 obj-$(CONFIG_TYPEC_MUX_GPIO_SBU)	+= gpio-sbu-mux.o
 obj-$(CONFIG_TYPEC_MUX_PI3USB30532)	+= pi3usb30532.o
 obj-$(CONFIG_TYPEC_MUX_INTEL_PMC)	+= intel_pmc_mux.o
diff --git a/drivers/usb/typec/mux/fusb340.c b/drivers/usb/typec/mux/fusb340.c
new file mode 100644
index 000000000000..0f0e45743784
--- /dev/null
+++ b/drivers/usb/typec/mux/fusb340.c
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ON Semiconductor FUSB340 SuperSpeed switch driver
+ *
+ * Copyright (C) 2026 STMicroelectronics
+ */
+
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/usb/typec_altmode.h>
+#include <linux/usb/typec_mux.h>
+
+struct fusb340 {
+	struct gpio_desc *enable_gpio;
+	struct gpio_desc *select_gpio;
+	struct mutex lock; /* serialize orientation and mode updates */
+	struct typec_switch_dev *sw;
+	struct typec_mux_dev *mux;
+	enum typec_orientation orientation;
+	unsigned long mode;
+};
+
+static void fusb340_update_locked(struct fusb340 *fusb)
+{
+	bool enable = fusb->mode == TYPEC_STATE_USB &&
+		      fusb->orientation != TYPEC_ORIENTATION_NONE;
+	bool select = fusb->orientation == TYPEC_ORIENTATION_REVERSE;
+
+	gpiod_set_value_cansleep(fusb->enable_gpio, enable);
+	gpiod_set_value_cansleep(fusb->select_gpio, select);
+}
+
+static int fusb340_switch_set(struct typec_switch_dev *sw,
+			      enum typec_orientation orientation)
+{
+	struct fusb340 *fusb = typec_switch_get_drvdata(sw);
+
+	mutex_lock(&fusb->lock);
+
+	if (fusb->orientation != orientation) {
+		fusb->orientation = orientation;
+		fusb340_update_locked(fusb);
+	}
+
+	mutex_unlock(&fusb->lock);
+
+	return 0;
+}
+
+static int fusb340_mux_set(struct typec_mux_dev *mux,
+			   struct typec_mux_state *state)
+{
+	struct fusb340 *fusb = typec_mux_get_drvdata(mux);
+	int ret = 0;
+
+	mutex_lock(&fusb->lock);
+
+	if (state->mode != TYPEC_STATE_SAFE && state->mode != TYPEC_STATE_USB) {
+		ret = -EOPNOTSUPP;
+		goto out_unlock;
+	}
+
+	if (fusb->mode != state->mode) {
+		fusb->mode = state->mode;
+		fusb340_update_locked(fusb);
+	}
+
+out_unlock:
+	mutex_unlock(&fusb->lock);
+
+	return ret;
+}
+
+static int fusb340_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct typec_switch_desc sw_desc = { };
+	struct typec_mux_desc mux_desc = { };
+	struct fusb340 *fusb;
+	int ret;
+
+	fusb = devm_kzalloc(dev, sizeof(*fusb), GFP_KERNEL);
+	if (!fusb)
+		return -ENOMEM;
+
+	fusb->orientation = TYPEC_ORIENTATION_NONE;
+	fusb->mode = TYPEC_STATE_SAFE;
+
+	fusb->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
+	if (IS_ERR(fusb->enable_gpio))
+		return dev_err_probe(dev, PTR_ERR(fusb->enable_gpio),
+				     "failed to get enable gpio\n");
+
+	fusb->select_gpio = devm_gpiod_get(dev, "select", GPIOD_OUT_LOW);
+	if (IS_ERR(fusb->select_gpio))
+		return dev_err_probe(dev, PTR_ERR(fusb->select_gpio),
+				     "failed to get select gpio\n");
+
+	mutex_init(&fusb->lock);
+
+	sw_desc.drvdata = fusb;
+	sw_desc.fwnode = dev_fwnode(dev);
+	sw_desc.set = fusb340_switch_set;
+
+	fusb->sw = typec_switch_register(dev, &sw_desc);
+	if (IS_ERR(fusb->sw)) {
+		ret = dev_err_probe(dev, PTR_ERR(fusb->sw),
+				    "failed to register typec switch\n");
+		goto err_mutex_destroy;
+	}
+
+	mux_desc.drvdata = fusb;
+	mux_desc.fwnode = dev_fwnode(dev);
+	mux_desc.set = fusb340_mux_set;
+
+	fusb->mux = typec_mux_register(dev, &mux_desc);
+	if (IS_ERR(fusb->mux)) {
+		ret = dev_err_probe(dev, PTR_ERR(fusb->mux),
+				    "failed to register typec mux\n");
+		goto err_unregister_switch;
+	}
+
+	platform_set_drvdata(pdev, fusb);
+
+	return 0;
+
+err_unregister_switch:
+	typec_switch_unregister(fusb->sw);
+err_mutex_destroy:
+	mutex_destroy(&fusb->lock);
+	return ret;
+}
+
+static void fusb340_remove(struct platform_device *pdev)
+{
+	struct fusb340 *fusb = platform_get_drvdata(pdev);
+
+	gpiod_set_value_cansleep(fusb->enable_gpio, 0);
+	gpiod_set_value_cansleep(fusb->select_gpio, 0);
+
+	typec_mux_unregister(fusb->mux);
+	typec_switch_unregister(fusb->sw);
+	mutex_destroy(&fusb->lock);
+}
+
+static const struct of_device_id fusb340_of_match[] = {
+	{ .compatible = "onnn,fusb340" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, fusb340_of_match);
+
+static struct platform_driver fusb340_driver = {
+	.probe = fusb340_probe,
+	.remove = fusb340_remove,
+	.driver = {
+		.name = "fusb340",
+		.of_match_table = fusb340_of_match,
+	},
+};
+module_platform_driver(fusb340_driver);
+
+MODULE_DESCRIPTION("ON Semiconductor FUSB340 SuperSpeed switch driver");
+MODULE_AUTHOR("Fabrice Gasnier <[email protected]>");
+MODULE_LICENSE("GPL");

-- 
2.43.0


-- 
linux-phy mailing list
[email protected]
https://lists.infradead.org/mailman/listinfo/linux-phy
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.