From: Svyatoslav Ryhel <clamor95@gmail.com>
To: Andrew Lunn <andrew+netdev@lunn.ch>,
"David S. Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Svyatoslav Ryhel <clamor95@gmail.com>
Cc: netdev@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org
Subject: [PATCH v2 2/2] net: usb: Add Infineon XMM6260 Baseband modem support
Date: Sat, 23 May 2026 11:44:07 +0300 [thread overview]
Message-ID: <20260523084408.50346-3-clamor95@gmail.com> (raw)
In-Reply-To: <20260523084408.50346-1-clamor95@gmail.com>
The Infineon/Intel XMM6260 is a 3G-focused, slim modem platform designed
for smartphones, data cards, and Machine-to-Machine (M2M) applications.
The modem is usually connected via the application processor's USB line
in HSIC mode; however, to work properly, the modem must control this line
Dmesg with modem appearing on LG Optimus Vu (P895):
[ 9.427014] ci_hdrc ci_hdrc.1: EHCI Host Controller
[ 9.431488] ci_hdrc ci_hdrc.1: new USB bus registered, assigned bus number 1
[ 9.457197] ci_hdrc ci_hdrc.1: USB 2.0 started, EHCI 1.00
[ 9.460370] usb usb1: New USB device found, idVendor=1d6b, idProduct=0002, bcdDevice= 6.16
[ 9.468470] usb usb1: New USB device strings: Mfr=3, Product=2, SerialNumber=1
[ 9.475597] usb usb1: Product: EHCI Host Controller
[ 9.480508] usb usb1: Manufacturer: Linux 6.16.0+ ehci_hcd
[ 9.485913] usb usb1: SerialNumber: ci_hdrc.1
[ 9.490862] hub 1-0:1.0: USB hub found
[ 9.494005] hub 1-0:1.0: 1 port detected
[ 9.657191] usb 1-1: new high-speed USB device number 2 using ci_hdrc
[ 9.844726] usb 1-1: New USB device found, idVendor=1519, idProduct=0020, bcdDevice=12.74
[ 9.850530] usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 9.857594] usb 1-1: Product: HSIC Device
[ 9.861606] usb 1-1: Manufacturer: Comneon
[ 9.865627] usb 1-1: SerialNumber: 0123456789
[ 9.908739] cdc_acm 1-1:1.0: ttyACM0: USB ACM device
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
---
drivers/net/usb/Kconfig | 15 ++
drivers/net/usb/Makefile | 1 +
drivers/net/usb/baseband-xmm6260.c | 378 +++++++++++++++++++++++++++++
3 files changed, 394 insertions(+)
create mode 100644 drivers/net/usb/baseband-xmm6260.c
diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig
index 52a5c0922c79..d54d8db752df 100644
--- a/drivers/net/usb/Kconfig
+++ b/drivers/net/usb/Kconfig
@@ -642,4 +642,19 @@ config USB_RTL8153_ECM
CONFIG_USB_RTL8152 is not set, or the RTL8153 device is not
supported by r8152 driver.
+config USB_NET_XMM6260
+ tristate "Infineon XMM626X Baseband HSPA/HSUPA modem"
+ depends on USB_CHIPIDEA && (RFKILL || RFKILL=n)
+ help
+ Select this if you want to use an Infineon XMM626X modem, found in
+ devices such as the LG Optimus 4X P880, LG Optimus Vu P895, Samsung
+ Galaxy S II (GT-I9100), and Galaxy Nexus (GT-I9250). This driver
+ handles the modem configuration and provides a stable way to expose
+ the modem's USB interface. To establish a connection, you will first
+ need a userspace program to send the correct commands to the modem
+ through its CDC ACM port, as well as a DHCP client.
+
+ To compile this driver as a module, choose M here: the module will
+ be called baseband-xmm6260.
+
endif # USB_NET_DRIVERS
diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile
index 4964f7b326fb..ffa532c7d7d6 100644
--- a/drivers/net/usb/Makefile
+++ b/drivers/net/usb/Makefile
@@ -42,3 +42,4 @@ obj-$(CONFIG_USB_NET_CDC_MBIM) += cdc_mbim.o
obj-$(CONFIG_USB_NET_CH9200) += ch9200.o
obj-$(CONFIG_USB_NET_AQC111) += aqc111.o
obj-$(CONFIG_USB_RTL8153_ECM) += r8153_ecm.o
+obj-$(CONFIG_USB_NET_XMM6260) += baseband-xmm6260.o
diff --git a/drivers/net/usb/baseband-xmm6260.c b/drivers/net/usb/baseband-xmm6260.c
new file mode 100644
index 000000000000..557ec79f5e2a
--- /dev/null
+++ b/drivers/net/usb/baseband-xmm6260.c
@@ -0,0 +1,378 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2011 NVIDIA Corporation
+ * Copyright (C) 2023 Svyatoslav Ryhel <clamor95@gmail.com>
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/pwrseq/consumer.h>
+#include <linux/regulator/consumer.h>
+#include <linux/rfkill.h>
+#include <linux/usb.h>
+
+#define BASEBAND_XMM_INIT_DELAY 5000
+
+#define BASEBAND_PRODUCT_ID_XMM6260 0x0020
+#define BASEBAND_VENDOR_ID_COMNEON 0x1519
+
+/*
+ * Starting from ver 1145 modem starts in the IPC_AP_WAKE_IRQ_READY state.
+ * AP wake interrupt keeps low util CP starts to initiate its HSIC hw. AP wake
+ * interrupt goes up during CP HSIC init (BASEBAND_XMM_IPC_AP_WAKE_INIT1 state)
+ * at this point Host USB bus must be configured in order for modem to set
+ * properly. Then interrupt goes down when CP HSIC is ready
+ * (BASEBAND_XMM_IPC_AP_WAKE_INIT2 state) and at this point XMM6260 USB device
+ * should appear and be accessible for further work. In case it does not, the
+ * cycle must repeat.
+ */
+
+/* bits [0:2] */
+enum baseband_xmm_ipc_ap_wake_state {
+ BASEBAND_XMM_IPC_AP_WAKE_IRQ_READY,
+ BASEBAND_XMM_IPC_AP_WAKE_INIT1,
+ BASEBAND_XMM_IPC_AP_WAKE_INIT2,
+ BASEBAND_XMM_IPC_AP_WAKE_L,
+ BASEBAND_XMM_IPC_AP_WAKE_H,
+ BASEBAND_XMM_IPC_AP_WAKE_UNINIT,
+ BASEBAND_XMM_IPC_AP_WAKE_MASK = 7,
+};
+
+#define BASEBAND_XMM_IPC_AP_WAKE_MAX 3
+
+#define BASEBAND_XMM_STATE_POWERED 3 /* Tracks regulator state */
+#define BASEBAND_XMM_STATE_PROTECTED 4 /* Prevents rfkill from access */
+#define BASEBAND_XMM_STATE_PRESENT 5 /* Tracks USB device presence */
+#define BASEBAND_XMM_STATE_POWEROFF 6 /* Prevents poweroff recursive call */
+#define BASEBAND_XMM_STATE_MAX 8
+
+struct baseband_xmm_data {
+ struct device *dev;
+ struct rfkill *rfkill_dev;
+ struct pwrseq_desc *pwrseq;
+
+ DECLARE_BITMAP(state, BASEBAND_XMM_STATE_MAX);
+ int irq;
+
+ struct gpio_desc *reset_gpio;
+ struct gpio_desc *enable_gpio;
+
+ struct gpio_desc *ipc_cp_gpio;
+ struct gpio_desc *ipc_ap_gpio;
+
+ struct regulator *vbat_supply;
+
+ struct delayed_work modem_work;
+ struct notifier_block nb;
+};
+
+static int get_ipc_ap_wake(struct baseband_xmm_data *priv)
+{
+ int i, ret = 0;
+
+ for (i = 0; i < BASEBAND_XMM_IPC_AP_WAKE_MAX; i++)
+ ret |= (test_bit(i, priv->state) << i);
+
+ return ret;
+}
+
+static void set_ipc_ap_wake(struct baseband_xmm_data *priv,
+ enum baseband_xmm_ipc_ap_wake_state state)
+{
+ for (int i = 0; i < BASEBAND_XMM_IPC_AP_WAKE_MAX; i++)
+ if (state & BIT(i))
+ set_bit(i, priv->state);
+ else
+ clear_bit(i, priv->state);
+}
+
+static void baseband_xmm_reset(struct baseband_xmm_data *priv)
+{
+ int ret;
+
+ set_bit(BASEBAND_XMM_STATE_PROTECTED, priv->state);
+
+ if (!test_bit(BASEBAND_XMM_STATE_POWERED, priv->state)) {
+ ret = regulator_enable(priv->vbat_supply);
+ if (ret)
+ dev_err(priv->dev,
+ "failed to enable vbat power supply\n");
+
+ set_bit(BASEBAND_XMM_STATE_POWERED, priv->state);
+ }
+
+ gpiod_set_value_cansleep(priv->enable_gpio, 0);
+ msleep(50);
+
+ gpiod_set_value_cansleep(priv->reset_gpio, 1);
+ msleep(200);
+ gpiod_set_value_cansleep(priv->reset_gpio, 0);
+
+ msleep(50);
+
+ /* falling edge trigger to CP */
+ gpiod_set_value_cansleep(priv->enable_gpio, 1);
+ usleep_range(50, 100);
+ gpiod_set_value_cansleep(priv->enable_gpio, 0);
+ msleep(20);
+}
+
+static void baseband_xmm_poweroff(struct baseband_xmm_data *priv)
+{
+ /*
+ * The test_bit check prevents poweroff from being recursively
+ * called during USB device deregistration. USB device
+ * deregistration can be triggered by the driver by calling this
+ * function or by some external event. The first case will cause
+ * a recursive call by the notifier if not handled, while the
+ * second case requires this call to handle the USB controller
+ * properly.
+ */
+ if (test_bit(BASEBAND_XMM_STATE_POWEROFF, priv->state))
+ return;
+
+ set_bit(BASEBAND_XMM_STATE_PROTECTED, priv->state);
+ set_bit(BASEBAND_XMM_STATE_POWEROFF, priv->state);
+
+ pwrseq_power_off(priv->pwrseq);
+ gpiod_set_value_cansleep(priv->reset_gpio, 1);
+
+ if (test_bit(BASEBAND_XMM_STATE_POWERED, priv->state)) {
+ regulator_disable(priv->vbat_supply);
+ clear_bit(BASEBAND_XMM_STATE_POWERED, priv->state);
+ }
+ set_ipc_ap_wake(priv, BASEBAND_XMM_IPC_AP_WAKE_IRQ_READY);
+
+ clear_bit(BASEBAND_XMM_STATE_PROTECTED, priv->state);
+ clear_bit(BASEBAND_XMM_STATE_PRESENT, priv->state);
+ clear_bit(BASEBAND_XMM_STATE_POWEROFF, priv->state);
+}
+
+static int baseband_xmm_usb_notifier_call(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct baseband_xmm_data *priv =
+ container_of(nb, struct baseband_xmm_data, nb);
+ struct usb_device *udev;
+ u16 product, vendor;
+
+ if (action == USB_BUS_ADD || action == USB_BUS_REMOVE)
+ return NOTIFY_OK;
+
+ udev = data;
+ product = le16_to_cpu(udev->descriptor.idProduct);
+ vendor = le16_to_cpu(udev->descriptor.idVendor);
+
+ switch (action) {
+ case USB_DEVICE_ADD:
+ /* Infineon XMM6260 ID 1519:0020 */
+ if (vendor == BASEBAND_VENDOR_ID_COMNEON &&
+ product == BASEBAND_PRODUCT_ID_XMM6260) {
+ cancel_delayed_work_sync(&priv->modem_work);
+ clear_bit(BASEBAND_XMM_STATE_PROTECTED, priv->state);
+ set_bit(BASEBAND_XMM_STATE_PRESENT, priv->state);
+ }
+ break;
+
+ case USB_DEVICE_REMOVE:
+ /* Infineon XMM6260 ID 1519:0020 */
+ if (vendor == BASEBAND_VENDOR_ID_COMNEON &&
+ product == BASEBAND_PRODUCT_ID_XMM6260)
+ baseband_xmm_poweroff(priv);
+ break;
+
+ default:
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+static int baseband_xmm_set_block(void *data, bool blocked)
+{
+ struct baseband_xmm_data *priv = data;
+
+ if (test_bit(BASEBAND_XMM_STATE_PROTECTED, priv->state))
+ return -EBUSY;
+
+ if (blocked && test_bit(BASEBAND_XMM_STATE_PRESENT, priv->state))
+ baseband_xmm_poweroff(priv);
+ else if (!blocked && !test_bit(BASEBAND_XMM_STATE_PRESENT, priv->state))
+ baseband_xmm_reset(priv);
+
+ return 0;
+}
+
+static const struct rfkill_ops baseband_xmm_rfkill_ops = {
+ .set_block = baseband_xmm_set_block,
+};
+
+static void baseband_xmm_work(struct work_struct *work)
+{
+ struct baseband_xmm_data *priv =
+ container_of(work, struct baseband_xmm_data, modem_work.work);
+
+ baseband_xmm_poweroff(priv);
+};
+
+static irqreturn_t baseband_hostwake_interrupt(int irq, void *dev_id)
+{
+ struct baseband_xmm_data *priv = dev_id;
+ int state = gpiod_get_value(priv->ipc_ap_gpio);
+
+ switch (get_ipc_ap_wake(priv)) {
+ case BASEBAND_XMM_IPC_AP_WAKE_IRQ_READY:
+ if (!state) {
+ set_ipc_ap_wake(priv, BASEBAND_XMM_IPC_AP_WAKE_INIT1);
+ pwrseq_power_on(priv->pwrseq);
+ }
+ break;
+
+ case BASEBAND_XMM_IPC_AP_WAKE_INIT1:
+ if (state) {
+ set_ipc_ap_wake(priv, BASEBAND_XMM_IPC_AP_WAKE_INIT2);
+ schedule_delayed_work(&priv->modem_work,
+ msecs_to_jiffies(BASEBAND_XMM_INIT_DELAY));
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int baseband_xmm_probe(struct platform_device *pdev)
+{
+ struct baseband_xmm_data *priv;
+ struct device *dev = &pdev->dev;
+ unsigned long irqflags;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ platform_set_drvdata(pdev, priv);
+
+ priv->vbat_supply = devm_regulator_get(dev, "vbat");
+ if (IS_ERR(priv->vbat_supply))
+ return dev_err_probe(dev, PTR_ERR(priv->vbat_supply),
+ "failed to get vbat regulator\n");
+
+ /* Own modem gpios */
+ priv->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(priv->reset_gpio))
+ return dev_err_probe(dev, PTR_ERR(priv->reset_gpio),
+ "failed to get reset GPIO\n");
+
+ priv->enable_gpio = devm_gpiod_get_optional(dev, "enable",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(priv->enable_gpio))
+ return dev_err_probe(dev, PTR_ERR(priv->enable_gpio),
+ "failed to get enable GPIO\n");
+
+ /* CP - AP connections */
+ priv->ipc_cp_gpio = devm_gpiod_get_optional(dev, "cp-wake",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(priv->ipc_cp_gpio))
+ return dev_err_probe(dev, PTR_ERR(priv->ipc_cp_gpio),
+ "failed to get CP wake GPIO\n");
+
+ priv->ipc_ap_gpio = devm_gpiod_get_optional(dev, "ap-wake", GPIOD_IN);
+ if (IS_ERR(priv->ipc_ap_gpio))
+ return dev_err_probe(dev, PTR_ERR(priv->ipc_ap_gpio),
+ "failed to get AP wake GPIO\n");
+
+ /* Modem power sequence */
+ priv->pwrseq = devm_pwrseq_get(dev, "modem-power");
+ if (IS_ERR(priv->pwrseq))
+ return dev_err_probe(dev, PTR_ERR(priv->pwrseq),
+ "failed to get modem pwrseq");
+
+ bitmap_zero(priv->state, BASEBAND_XMM_STATE_MAX);
+ INIT_DELAYED_WORK(&priv->modem_work, baseband_xmm_work);
+
+ priv->irq = platform_get_irq(pdev, 0);
+ if (priv->irq < 0)
+ return dev_err_probe(dev, priv->irq, "failed to get IRQ\n");
+
+ /*
+ * Systems using device tree should set up interrupt via DT,
+ * the rest will use the default edge both interrupt.
+ */
+ irqflags = dev->of_node ? 0 : IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
+
+ ret = devm_request_threaded_irq(dev, priv->irq, NULL,
+ &baseband_hostwake_interrupt,
+ IRQF_ONESHOT | irqflags,
+ "modem-hostwake", priv);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to register IRQ %d\n", priv->irq);
+
+ priv->rfkill_dev = rfkill_alloc("xmm-modem", dev, RFKILL_TYPE_WWAN,
+ &baseband_xmm_rfkill_ops, priv);
+ if (!priv->rfkill_dev)
+ return -ENOMEM;
+
+ ret = rfkill_register(priv->rfkill_dev);
+ if (ret) {
+ rfkill_destroy(priv->rfkill_dev);
+ return dev_err_probe(dev, ret,
+ "failed to register WWAN rfkill\n");
+ }
+
+ priv->nb.notifier_call = baseband_xmm_usb_notifier_call;
+ usb_register_notify(&priv->nb);
+
+ return 0;
+}
+
+static void baseband_xmm_remove(struct platform_device *pdev)
+{
+ struct baseband_xmm_data *priv = platform_get_drvdata(pdev);
+
+ rfkill_unregister(priv->rfkill_dev);
+ rfkill_destroy(priv->rfkill_dev);
+
+ disable_irq(priv->irq);
+ cancel_delayed_work_sync(&priv->modem_work);
+
+ usb_unregister_notify(&priv->nb);
+ baseband_xmm_poweroff(priv);
+}
+
+static const struct of_device_id baseband_xmm_match[] = {
+ { .compatible = "infineon,xmm6260" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, baseband_xmm_match);
+
+static struct platform_driver baseband_xmm_driver = {
+ .driver = {
+ .name = "baseband-xmm6260",
+ .of_match_table = baseband_xmm_match,
+ },
+ .probe = baseband_xmm_probe,
+ .remove = baseband_xmm_remove,
+};
+module_platform_driver(baseband_xmm_driver);
+
+MODULE_AUTHOR("Svyatolsav Ryhel <clamor95@gmail.com>");
+MODULE_DESCRIPTION("Baseband Infineon XMM6260 driver");
+MODULE_LICENSE("GPL");
--
2.51.0
next prev parent reply other threads:[~2026-05-23 8:44 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-23 8:44 [PATCH v2 0/2] Add support for Infineon/Intel XMM6260 modem Svyatoslav Ryhel
2026-05-23 8:44 ` [PATCH v2 1/2] dt-bindings: net: Document " Svyatoslav Ryhel
2026-05-27 8:23 ` Krzysztof Kozlowski
2026-05-27 8:54 ` Svyatoslav Ryhel
2026-05-23 8:44 ` Svyatoslav Ryhel [this message]
2026-05-27 0:38 ` [PATCH v2 2/2] net: usb: Add Infineon XMM6260 Baseband modem support Jakub Kicinski
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260523084408.50346-3-clamor95@gmail.com \
--to=clamor95@gmail.com \
--cc=andrew+netdev@lunn.ch \
--cc=conor+dt@kernel.org \
--cc=davem@davemloft.net \
--cc=devicetree@vger.kernel.org \
--cc=edumazet@google.com \
--cc=krzk+dt@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-usb@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=robh@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox