From: Yang Li <yang.li@amlogic.com>
To: christian.koever-draxl@student.uibk.ac.at, marcel@holtmann.org,
luiz.dentz@gmail.com, robh@kernel.org, krzk+dt@kernel.org,
conor+dt@kernel.org
Cc: linux-bluetooth@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org,
"Christian Stefan Kövér-Draxl" <ckoever@outlook.de>
Subject: Re: [PATCH 3/7] Bluetooth: hci_aml: cope with a controller that is already running
Date: Fri, 6 Nov 2026 11:28:34 +0800 [thread overview]
Message-ID: <95e5b0bf-a8db-4448-8ca0-a9af76b66e85@amlogic.com> (raw)
In-Reply-To: <20261025192338.209574-4-christian.koever-draxl@student.uibk.ac.at>
Hi,
>
> [ EXTERNAL EMAIL ]
>
> From: Christian Stefan Kövér-Draxl <ckoever@outlook.de>
>
> Several things stop the W1 (W155S2) in an S905W2 TV box from completing
> setup reliably:
>
> - Dropping the enable line does not reset this controller. It keeps
> running whatever firmware it was last given, at the baud rate it was
> last told to use, so a warm reboot or a driver reload finds an HCI
> device at 4 Mbps that no longer understands TCI commands, and the
> download dies on the first TCI write. Read Local Version tells the
> two cases apart, as the ROM does not answer it, so probe for it and
> skip the download when firmware is already running.
>
> - Talking to a running controller first means sending it a TCI command
> at the ROM's 115200 baud, which arrives as noise at 4 Mbps. The
> firmware takes it for the start of a packet and swallows the next
> command as its payload. Retry the probe; every attempt completes
> more of the imagined packet until the controller listens again.
>
> - A single unanswered firmware segment aborted the bring-up. Segments
> are plain writes to controller RAM, so resync and send it again.
>
> - The fixed 60 ms wait after starting the firmware is too short for
> W1, which takes up to half a second. A version request sent too early
> is lost and blocks the command queue for HCI_CMD_TIMEOUT, so wait
> 600 ms, then poll the firmware version until it answers.
>
> - Closing the HCI device dropped the enable line. Setup, which powers
> the controller up, only runs once per registration, and the dropped
> enable line leaves the controller unreachable until the combo chip
> is power cycled, so the adapter was lost after a single down/up
> cycle. Keep it powered in close and power it off on remove instead,
> tracking the state so remove and shutdown stay balanced. Rebinding
> the driver still loses the controller, as the hardware gives no way
> to reset it short of a power cycle.
>
> Tested on an S905W2 board: setup succeeds on cold boot and warm reboot,
> and the adapter survives down/up cycles.
How did you test the warm boot scenario?
Were there any operations involving the Wi-Fi driver? Cause of the
Bluetooth driver and Wi-Fi driver share |chip_en| GPIO.
Could you please share your test steps and system logs to help with the
analysis?
Thanks.
> Signed-off-by: Christian Stefan Kövér-Draxl <ckoever@outlook.de>
> ---
> drivers/bluetooth/hci_aml.c | 186 ++++++++++++++++++++++++++++++------
> 1 file changed, 158 insertions(+), 28 deletions(-)
>
> diff --git a/drivers/bluetooth/hci_aml.c b/drivers/bluetooth/hci_aml.c
> index 5979b114f2e4..8561618867e8 100644
> --- a/drivers/bluetooth/hci_aml.c
> +++ b/drivers/bluetooth/hci_aml.c
> @@ -24,6 +24,29 @@
>
> #define AML_FIRMWARE_OPERATION_SIZE (248)
> #define AML_FIRMWARE_MAX_SIZE (512 * 1024)
> +#define AML_FIRMWARE_SEGMENT_RETRIES (1)
> +
> +/* Probing for a controller that already runs firmware is expected to time
> + * out on a freshly powered one, so do not wait HCI_INIT_TIMEOUT for it.
> + */
> +#define AML_PROBE_TIMEOUT msecs_to_jiffies(500)
> +
> +/* Long enough for the controller to drop a partial frame received at the
> + * wrong rate.
> + */
> +#define AML_RESYNC_MS (100)
> +#define AML_RESYNC_RETRIES (4)
> +
> +/* An HCI command has an 8 bit length, so this always completes a partial one */
> +#define AML_RESYNC_CHUNKS_CMD (2)
> +
> +/* W1 takes up to half a second to start its firmware, and a version request
> + * sent before that is lost and blocks the command queue for HCI_CMD_TIMEOUT,
> + * so wait a little longer than that before asking, then keep asking.
> + */
> +#define AML_STARTUP_DELAY_MS (600)
> +#define AML_STARTUP_POLL_MS (60)
> +#define AML_STARTUP_RETRIES (5)
>
> /* TCI command */
> #define AML_TCI_CMD_READ 0xFEF0
> @@ -100,6 +123,7 @@ struct aml_serdev {
> struct clk *lpo_clk;
> const struct aml_device_data *aml_dev_data;
> const char *firmware_name;
> + bool powered;
> };
>
> struct aml_data {
> @@ -214,11 +238,33 @@ static int aml_send_firmware_segment(struct hci_dev *hdev,
> op_addr, (u32 *)seg, seg_size);
> }
>
> -static int aml_send_firmware(struct hci_dev *hdev, u8 fw_type,
> - u8 *fw, u32 fw_size, u32 offset)
> +static const u8 aml_resync_pattern[256];
> +
> +/* A command that goes unanswered has usually been half received: the
> + * controller took what did arrive for the start of a packet and waits for
> + * the rest, so every following command is swallowed as its payload. Send
> + * enough bytes to complete whatever it is reassembling. Zero is not a valid
> + * H4 packet type, so what is left over afterwards is discarded.
> + */
> +static void aml_resync(struct hci_uart *hu, unsigned int chunks)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < chunks; i++)
> + serdev_device_write(hu->serdev, aml_resync_pattern,
> + sizeof(aml_resync_pattern),
> + MAX_SCHEDULE_TIMEOUT);
> +
> + serdev_device_wait_until_sent(hu->serdev, 0);
> +}
> +
> +static int aml_send_firmware(struct hci_dev *hdev, struct hci_uart *hu,
> + u8 fw_type, u8 *fw, u32 fw_size, u32 offset)
> {
> u32 seg_size = 0;
> u32 seg_off = 0;
> + int err = 0;
> + int retry;
>
> if (fw_size > AML_FIRMWARE_MAX_SIZE) {
> bt_dev_err(hdev,
> @@ -229,8 +275,22 @@ static int aml_send_firmware(struct hci_dev *hdev, u8 fw_type,
> while (fw_size > 0) {
> seg_size = (fw_size > AML_FIRMWARE_OPERATION_SIZE) ?
> AML_FIRMWARE_OPERATION_SIZE : fw_size;
> - if (aml_send_firmware_segment(hdev, fw_type, (fw + seg_off),
> - seg_size, offset)) {
> + /* A segment is a plain write to controller RAM, so one that
> + * goes unanswered can simply be sent again, after resyncing
> + * the controller that is still waiting for its end.
> + */
> + for (retry = 0; retry <= AML_FIRMWARE_SEGMENT_RETRIES; retry++) {
> + err = aml_send_firmware_segment(hdev, fw_type,
> + fw + seg_off,
> + seg_size, offset);
> + if (!err)
> + break;
> +
> + bt_dev_warn(hdev, "Retrying firmware segment, type: %d, offset: 0x%x",
> + fw_type, offset);
> + aml_resync(hu, AML_RESYNC_CHUNKS_CMD);
> + }
> + if (err) {
> bt_dev_err(hdev, "Failed send firmware, type: %d, offset: 0x%x",
> fw_type, offset);
> return -EINVAL;
> @@ -242,6 +302,35 @@ static int aml_send_firmware(struct hci_dev *hdev, u8 fw_type,
> return 0;
> }
>
> +/* Dropping the enable line does not reset these controllers: they keep
> + * running the firmware they were last given, at the baud rate they were last
> + * told to use. A warm reboot or a driver reload therefore finds an HCI device
> + * at the operational rate that no longer understands TCI commands. Read Local
> + * Version tells the two cases apart: the ROM does not answer it, running
> + * firmware does.
> + */
> +static bool aml_fw_already_running(struct hci_dev *hdev, struct hci_uart *hu)
> +{
> + struct sk_buff *skb;
> + int i;
> +
> + serdev_device_set_baudrate(hu->serdev, hu->proto->oper_speed);
> +
> + for (i = 0; i < AML_RESYNC_RETRIES; i++) {
> + skb = __hci_cmd_sync_ev(hdev, HCI_OP_READ_LOCAL_VERSION, 0,
> + NULL, HCI_EV_CMD_COMPLETE,
> + AML_PROBE_TIMEOUT);
> + if (!IS_ERR(skb)) {
> + kfree_skb(skb);
> + return true;
> + }
> + }
> +
> + serdev_device_set_baudrate(hu->serdev, hu->proto->init_speed);
> +
> + return false;
> +}
> +
> static int aml_download_firmware(struct hci_dev *hdev, const char *fw_name)
> {
> struct hci_uart *hu = hci_get_drvdata(hdev);
> @@ -301,7 +390,7 @@ static int aml_download_firmware(struct hci_dev *hdev, const char *fw_name)
> iccm_start = (u8 *)(firmware->data) + sizeof(struct aml_fw_len)
> + amldev->aml_dev_data->iccm_offset;
> iccm_len = fw_len->iccm_len - amldev->aml_dev_data->iccm_offset;
> - ret = aml_send_firmware(hdev, FW_ICCM, iccm_start, iccm_len,
> + ret = aml_send_firmware(hdev, hu, FW_ICCM, iccm_start, iccm_len,
> amldev->aml_dev_data->iccm_offset);
> if (ret) {
> bt_dev_err(hdev, "Failed to send FW_ICCM (%d)", ret);
> @@ -311,7 +400,7 @@ static int aml_download_firmware(struct hci_dev *hdev, const char *fw_name)
> /* Download DCCM */
> dccm_start = (u8 *)(firmware->data) + sizeof(struct aml_fw_len) + fw_len->iccm_len;
> dccm_len = fw_len->dccm_len;
> - ret = aml_send_firmware(hdev, FW_DCCM, dccm_start, dccm_len,
> + ret = aml_send_firmware(hdev, hu, FW_DCCM, dccm_start, dccm_len,
> amldev->aml_dev_data->dccm_offset);
> if (ret) {
> bt_dev_err(hdev, "Failed to send FW_DCCM (%d)", ret);
> @@ -508,6 +597,7 @@ static int aml_power_on(struct aml_serdev *amldev)
> }
>
> gpiod_set_value_cansleep(amldev->bt_en_gpio, 1);
> + amldev->powered = true;
>
> /* Wait 20ms for bluetooth controller power on */
> msleep(20);
> @@ -516,6 +606,10 @@ static int aml_power_on(struct aml_serdev *amldev)
>
> static int aml_power_off(struct aml_serdev *amldev)
> {
> + if (!amldev->powered)
> + return 0;
> +
> + amldev->powered = false;
> gpiod_set_value_cansleep(amldev->bt_en_gpio, 0);
>
> clk_disable_unprepare(amldev->lpo_clk);
> @@ -568,7 +662,6 @@ static int aml_open(struct hci_uart *hu)
>
> static int aml_close(struct hci_uart *hu)
> {
> - struct aml_serdev *amldev = serdev_device_get_drvdata(hu->serdev);
> struct aml_data *aml_data = hu->priv;
>
> skb_queue_purge(&aml_data->txq);
> @@ -577,7 +670,13 @@ static int aml_close(struct hci_uart *hu)
>
> hu->priv = NULL;
>
> - return aml_power_off(amldev);
> + /* The controller is left powered. Setup, which powers it on, only
> + * runs once per registration, and dropping the enable line does not
> + * reset it but leaves it unreachable until the combo chip is power
> + * cycled, so powering off here would lose the controller on the next
> + * open. It is powered off on remove and shutdown.
> + */
> + return 0;
> }
>
> static int aml_flush(struct hci_uart *hu)
> @@ -589,6 +688,55 @@ static int aml_flush(struct hci_uart *hu)
> return 0;
> }
>
> +/* Everything between powering the controller up and a usable HCI device */
> +static int aml_setup_controller(struct hci_uart *hu, struct aml_serdev *amldev,
> + struct hci_dev *hdev)
> +{
> + int err;
> + int i;
> +
> + /* Try the fresh controller first: a ROM that has just been probed at
> + * the wrong rate swallows the command that follows.
> + */
> + err = aml_set_baudrate(hu, hu->proto->oper_speed);
> + if (err) {
> + msleep(AML_RESYNC_MS);
> +
> + if (!aml_fw_already_running(hdev, hu))
> + return err;
> +
> + bt_dev_info(hdev, "Controller is already running firmware");
> + return 0;
> + }
> +
> + err = aml_download_firmware(hdev, amldev->firmware_name);
> + if (err)
> + return err;
> +
> + err = aml_config_rf(hdev, amldev->aml_dev_data->is_coex);
> + if (err)
> + return err;
> +
> + err = aml_start_chip(hdev);
> + if (err)
> + return err;
> +
> + /* There is nothing to poll but the version command itself, and the
> + * time to start varies: W1 takes up to half a second.
> + */
> + msleep(AML_STARTUP_DELAY_MS);
> + for (i = 0; i < AML_STARTUP_RETRIES; i++) {
> + if (i)
> + msleep(AML_STARTUP_POLL_MS);
> +
> + err = aml_dump_fw_version(hdev);
> + if (!err)
> + break;
> + }
> +
> + return err;
> +}
> +
> static int aml_setup(struct hci_uart *hu)
> {
> struct aml_serdev *amldev = serdev_device_get_drvdata(hu->serdev);
> @@ -613,26 +761,7 @@ static int aml_setup(struct hci_uart *hu)
> if (err)
> return err;
>
> - err = aml_set_baudrate(hu, amldev->serdev_hu.proto->oper_speed);
> - if (err)
> - return err;
> -
> - err = aml_download_firmware(hdev, amldev->firmware_name);
> - if (err)
> - return err;
> -
> - err = aml_config_rf(hdev, amldev->aml_dev_data->is_coex);
> - if (err)
> - return err;
> -
> - err = aml_start_chip(hdev);
> - if (err)
> - return err;
> -
> - /* Wait 60ms for controller startup */
> - msleep(60);
> -
> - err = aml_dump_fw_version(hdev);
> + err = aml_setup_controller(hu, amldev, hdev);
> if (err)
> return err;
>
> @@ -730,6 +859,7 @@ static void aml_serdev_remove(struct serdev_device *serdev)
> struct aml_serdev *amldev = serdev_device_get_drvdata(serdev);
>
> hci_uart_unregister_device(&amldev->serdev_hu);
> + aml_power_off(amldev);
> }
>
> static void aml_serdev_shutdown(struct serdev_device *serdev)
> --
> 2.56.0
>
next prev parent reply other threads:[~2026-11-06 3:28 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-25 19:23 [PATCH 0/7] Bluetooth: make the Amlogic W1 (W155S2) usable christian.koever-draxl
2026-10-25 19:23 ` [PATCH 1/7] Bluetooth: add a quirk for controllers that cannot enable Secure Connections christian.koever-draxl
2026-11-05 2:19 ` Yang Li
2026-10-25 19:23 ` [PATCH 2/7] Bluetooth: add a quirk for controllers that hang on Write Current IAC LAP christian.koever-draxl
2026-11-05 2:24 ` Yang Li
2026-10-25 19:23 ` [PATCH 3/7] Bluetooth: hci_aml: cope with a controller that is already running christian.koever-draxl
2026-11-06 3:28 ` Yang Li [this message]
2026-10-25 19:23 ` [PATCH 4/7] dt-bindings: net: bluetooth: amlogic,w155s2-bt: allow local-bd-address christian.koever-draxl
2026-10-30 11:32 ` Krzysztof Kozlowski
2026-10-25 19:23 ` [PATCH 5/7] Bluetooth: hci_aml: take the address from the firmware description christian.koever-draxl
2026-11-06 3:15 ` Yang Li
2026-10-25 19:23 ` [PATCH 6/7] dt-bindings: net: bluetooth: amlogic,w155s2-bt: add amlogic,shared-antenna christian.koever-draxl
2026-10-30 11:40 ` Krzysztof Kozlowski
2026-10-25 19:23 ` [PATCH 7/7] Bluetooth: hci_aml: configure the RF for a shared antenna christian.koever-draxl
2026-11-06 2:57 ` Yang Li
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=95e5b0bf-a8db-4448-8ca0-a9af76b66e85@amlogic.com \
--to=yang.li@amlogic.com \
--cc=christian.koever-draxl@student.uibk.ac.at \
--cc=ckoever@outlook.de \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linux-bluetooth@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=luiz.dentz@gmail.com \
--cc=marcel@holtmann.org \
--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
all inboxes | Powered by JetHome®