All of lore.kernel.org
 help / color / mirror / Atom feed
From: Cai Yu <caiyu7372@gmail.com>
To: Marcel Holtmann <marcel@holtmann.org>,
	Luiz Augusto von Dentz <luiz.dentz@gmail.com>
Cc: linux-bluetooth@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [RFC PATCH 3/4] Bluetooth: hci_intel: add serdev support for the CcP controller
Date: Sat, 26 Sep 2026 07:40:42 +0800	[thread overview]
Message-ID: <20260925234043.707679-4-caiyu7372@gmail.com> (raw)
In-Reply-To: <20260925234043.707679-1-caiyu7372@gmail.com>

The Intel Bluetooth controller on the ThinkPad X1 Fold Gen1 (Lakefield,
ACPI INT33E4) is described as a serdev child of its LPSS UART: the port
has no tty device at all, so the line discipline path of this driver can
never be used and nothing binds to the controller today.

Add a serdev driver next to the platform driver, in the same way hci_bcm
supports controllers of both kinds:

 - the ACPI id INT33E4 is matched in a separate table, so the platform
   driver keeps its INT33E1/INT33E3 set;
 - the probe power cycles the controller through the "reset" GPIO (it
   does not keep the firmware across a power cycle, and this also clears
   a controller left in an unknown state) and waits for the bootloader to
   come up before the first command.  On this board 500 ms of reset pulse
   plus 2 s of boot delay are needed, otherwise the first command is lost;
 - this UART is described with FlowControlHardware, so the serial core
   enables CTS/RTS.  The Intel handshake has to transmit freely before
   the controller answers, so flow control is turned off;
 - the serdev path uses a protocol struct with oper_speed = 0.  The
   generic baudrate change in hci_serdev.c runs before intel_setup(), but
   the controller is in bootloader mode at that point: it only answers at
   init_speed until the firmware has been downloaded, and it is not even
   listening yet right after the reset pulse.  Switching the host to
   oper_speed there makes the setup fail with a -110 timeout on the
   version read; intel_setup() changes the baudrate itself.

Tested on a Lenovo ThinkPad X1 Fold Gen1 (20RKA000CD): the controller
comes up as hci0, the Intel firmware is downloaded, the DDC parameters
are applied and the (sole) HOG keyboard connects.

Signed-off-by: Cai Yu <caiyu7372@gmail.com>
---
 drivers/bluetooth/hci_intel.c | 132 +++++++++++++++++++++++++++++++++-
 1 file changed, 131 insertions(+), 1 deletion(-)

diff --git a/drivers/bluetooth/hci_intel.c b/drivers/bluetooth/hci_intel.c
index d25d029..5926d1f 100644
--- a/drivers/bluetooth/hci_intel.c
+++ b/drivers/bluetooth/hci_intel.c
@@ -544,6 +544,14 @@ static int intel_setup(struct hci_uart *hu)
 
 	bt_dev_dbg(hdev, "");
 
+	/* ACPI describes the UART of this controller with FlowControlHardware,
+	 * so the serial core enables CTS/RTS.  The Intel handshake has to
+	 * transmit freely at init_speed before the controller starts answering,
+	 * otherwise the first commands time out; keep flow control off.
+	 */
+	if (hu->serdev)
+		serdev_device_set_flow_control(hu->serdev, false);
+
 	hu->hdev->set_diag = btintel_set_diag;
 	hu->hdev->set_bdaddr = btintel_set_bdaddr;
 
@@ -1067,6 +1075,33 @@ static const struct hci_uart_proto intel_proto = {
 	.dequeue	= intel_dequeue,
 };
 
+/* Do not let the generic baudrate change in hci_serdev.c run for this path.
+ *
+ * It is executed before intel_setup() (and right after the controller has been
+ * power cycled by the probe), but the controller is still in bootloader mode at
+ * that point: it only answers at init_speed until the firmware has been
+ * downloaded, and it needs a few seconds after the reset pulse before it
+ * answers at all.  Switching the host to oper_speed while the controller is
+ * silent leaves the two sides at different baudrates and the setup never
+ * recovers.  intel_setup() changes the baudrate itself, once the bootloader is
+ * talking.
+ */
+static const struct hci_uart_proto intel_serdev_proto = {
+	.id		= HCI_UART_INTEL,
+	.name		= "Intel",
+	.manufacturer	= 2,
+	.init_speed	= 115200,
+	.oper_speed	= 0,
+	.open		= intel_open,
+	.close		= intel_close,
+	.flush		= intel_flush,
+	.setup		= intel_setup,
+	.set_baudrate	= intel_set_baudrate,
+	.recv		= intel_recv,
+	.enqueue	= intel_enqueue,
+	.dequeue	= intel_dequeue,
+};
+
 #ifdef CONFIG_ACPI
 static const struct acpi_device_id intel_acpi_match[] = {
 	{ .id = "INT33E1" },
@@ -1074,6 +1109,16 @@ static const struct acpi_device_id intel_acpi_match[] = {
 	{ }
 };
 MODULE_DEVICE_TABLE(acpi, intel_acpi_match);
+
+/* Controllers which the firmware describes as a serdev child of their UART
+ * instead of as an LPSS platform device.  The CcP controller (Lakefield,
+ * Jasper Lake) is one of them.
+ */
+static const struct acpi_device_id intel_serdev_acpi_match[] = {
+	{ .id = "INT33E4" },
+	{ }
+};
+MODULE_DEVICE_TABLE(acpi, intel_serdev_acpi_match);
 #endif
 
 static int intel_suspend_device(struct device *dev)
@@ -1217,6 +1262,67 @@ static struct platform_driver intel_driver = {
 	},
 };
 
+#ifdef CONFIG_ACPI
+/* The controller does not keep the firmware across a power cycle, so it starts
+ * in bootloader mode every time; power cycling it here also makes sure a
+ * controller left in an unknown state by a previous boot cannot break the
+ * setup.  Measured on the ThinkPad X1 Fold Gen1: 500 ms of reset pulse, then
+ * 2 s until the bootloader answers the first command.
+ */
+#define INTEL_RESET_PULSE_MS	500
+#define INTEL_BOOT_DELAY_MS	2000
+
+static int intel_serdev_probe(struct serdev_device *serdev)
+{
+	struct hci_uart *hu;
+	struct gpio_desc *reset;
+
+	hu = devm_kzalloc(&serdev->dev, sizeof(*hu), GFP_KERNEL);
+	if (!hu)
+		return -ENOMEM;
+
+	hu->serdev = serdev;
+
+	/* The port is not open yet (hci_uart_register_device() opens it), so
+	 * only the ACPI properties and the reset GPIO can be used here.
+	 */
+	if (devm_acpi_dev_add_driver_gpios(&serdev->dev, acpi_hci_intel_gpios))
+		dev_dbg(&serdev->dev, "No ACPI GPIO mapping table\n");
+
+	reset = devm_gpiod_get_optional(&serdev->dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(reset))
+		return dev_err_probe(&serdev->dev, PTR_ERR(reset),
+				     "Unable to retrieve reset gpio\n");
+
+	if (reset) {
+		gpiod_set_value_cansleep(reset, 0);
+		msleep(INTEL_RESET_PULSE_MS);
+		gpiod_set_value_cansleep(reset, 1);
+		msleep(INTEL_BOOT_DELAY_MS);
+	} else {
+		dev_warn(&serdev->dev, "No reset gpio, relying on the firmware state\n");
+	}
+
+	return hci_uart_register_device(hu, &intel_serdev_proto);
+}
+
+static void intel_serdev_remove(struct serdev_device *serdev)
+{
+	struct hci_uart *hu = serdev_device_get_drvdata(serdev);
+
+	hci_uart_unregister_device(hu);
+}
+
+static struct serdev_device_driver intel_serdev_driver = {
+	.probe = intel_serdev_probe,
+	.remove = intel_serdev_remove,
+	.driver = {
+		.name = "hci_uart_intel",
+		.acpi_match_table = ACPI_PTR(intel_serdev_acpi_match),
+	},
+};
+#endif
+
 int __init intel_init(void)
 {
 	int err;
@@ -1225,12 +1331,36 @@ int __init intel_init(void)
 	if (err)
 		return err;
 
+#ifdef CONFIG_ACPI
+	err = serdev_device_driver_register(&intel_serdev_driver);
+	if (err)
+		goto err_platform;
+
+	err = hci_uart_register_proto(&intel_proto);
+	if (err)
+		goto err_serdev;
+
+	return 0;
+
+err_serdev:
+	serdev_device_driver_unregister(&intel_serdev_driver);
+err_platform:
+	platform_driver_unregister(&intel_driver);
+	return err;
+#else
 	return hci_uart_register_proto(&intel_proto);
+#endif
 }
 
 int __exit intel_deinit(void)
 {
+	hci_uart_unregister_proto(&intel_proto);
+
+#ifdef CONFIG_ACPI
+	serdev_device_driver_unregister(&intel_serdev_driver);
+#endif
+
 	platform_driver_unregister(&intel_driver);
 
-	return hci_uart_unregister_proto(&intel_proto);
+	return 0;
 }

  parent reply	other threads:[~2026-09-25 23:40 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-25 23:40 [RFC PATCH 0/4] Bluetooth: hci_intel: support the CcP controller (X1 Fold Gen1) Cai Yu
2026-09-25 23:40 ` [RFC PATCH 1/4] Bluetooth: hci_intel: fix tty-only assumptions in the LPM paths Cai Yu
2026-09-26  1:11   ` Bluetooth: hci_intel: support the CcP controller (X1 Fold Gen1) bluez.test.bot
2026-09-25 23:40 ` [RFC PATCH 2/4] Bluetooth: hci_intel: add the CcP controller (hardware variant 0x14) Cai Yu
2026-09-25 23:40 ` Cai Yu [this message]
2026-09-25 23:40 ` [RFC PATCH 4/4] Bluetooth: hci_intel: download the CcP firmware at 921.6 kbaud Cai Yu

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=20260925234043.707679-4-caiyu7372@gmail.com \
    --to=caiyu7372@gmail.com \
    --cc=linux-bluetooth@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=luiz.dentz@gmail.com \
    --cc=marcel@holtmann.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.