Devicetree
 help / color / mirror / Atom feed
* [RFC PATCH bluetooth-next 2/3] dt-bindings: net: bluetooth: Add AIC8800D80
  2026-09-16  8:12 [RFC PATCH bluetooth-next 0/3] Bluetooth: Add AIC8800D80 SDIO firmware loader and UART HCI Yanli Yang
  2026-09-16  8:12 ` [RFC PATCH bluetooth-next 1/3] dt-bindings: vendor-prefixes: Add AIC Semiconductor Yanli Yang
@ 2026-09-16  8:12 ` Yanli Yang
  2026-09-16  8:12 ` [RFC PATCH bluetooth-next 3/3] Bluetooth: btaic: Add AIC8800D80 SDIO loader and UART transport Yanli Yang
  2026-09-20  9:50 ` [RFC PATCH v2 0/3] Bluetooth: Add AIC8800D80 SDIO firmware loader and UART HCI Yanli Yang
  3 siblings, 0 replies; 18+ messages in thread
From: Yanli Yang @ 2026-09-16  8:12 UTC (permalink / raw)
  To: linux-bluetooth
  Cc: marcel, luiz.dentz, devicetree, robh, krzk+dt, conor+dt,
	linux-kernel, zhirunliu, dijiaxu, chunqiuliu, liheng.wei,
	yanli.yang

Describe the AIC8800D80 Bluetooth SDIO firmware-loading function and
UART HCI interface. Link the UART node to its SDIO firmware provider
with the aic,firmware-sdio phandle.

The UART interface uses H4 at 1500000 baud with hardware flow control.
Include an example showing both nodes and their association.
Signed-off-by: Zhirun Liu <zhirunliu@aicsemi.com>
Signed-off-by: Dijia Xu <dijiaxu@aicsemi.com>
Signed-off-by: Chunqiu Liu <chunqiuliu@aicsemi.com>
Signed-off-by: Liheng Wei <liheng.wei@bedmex.com>
Signed-off-by: Yanli Yang <yanli.yang@bedmex.com>
---
 .../net/bluetooth/aic,aic8800d80-bt.yaml      | 87 +++++++++++++++++++
 1 file changed, 87 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml

diff --git a/Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml b/Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml
new file mode 100644
index 000000000000..6fabb226cf15
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml
@@ -0,0 +1,87 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/bluetooth/aic,aic8800d80-bt.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: AIC AIC8800D80 Bluetooth
+
+maintainers:
+  - Zhirun Liu <zhirunliu@aicsemi.com>
+  - Dijia Xu <dijiaxu@aicsemi.com>
+  - Chunqiu Liu <chunqiuliu@aicsemi.com>
+  - Liheng Wei <liheng.wei@bedmex.com>
+  - Yanli Yang <yanli.yang@bedmex.com>
+
+description:
+  The AIC8800D80 is a Wi-Fi and Bluetooth combination chip. The Bluetooth
+  firmware is loaded through SDIO function 1, while Bluetooth HCI traffic uses
+  the H4 protocol over a UART interface with hardware flow control.
+
+properties:
+  compatible:
+    enum:
+      - aic,aic8800d80-bt
+      - aic,aic8800d80-bt-sdio
+
+  reg:
+    maxItems: 1
+
+  max-speed: true
+
+  aic,firmware-sdio:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description:
+      Phandle to the SDIO function used to load the Bluetooth firmware.
+
+required:
+  - compatible
+
+allOf:
+  - $ref: bluetooth-controller.yaml#
+  - if:
+      properties:
+        compatible:
+          const: aic,aic8800d80-bt-sdio
+    then:
+      properties:
+        reg:
+          const: 1
+        max-speed: false
+        aic,firmware-sdio: false
+      required:
+        - reg
+    else:
+      properties:
+        reg: false
+        max-speed:
+          const: 1500000
+      required:
+        - max-speed
+        - aic,firmware-sdio
+      allOf:
+        - $ref: /schemas/serial/serial-peripheral-props.yaml#
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    mmc {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        bt_sdio: bluetooth@1 {
+            compatible = "aic,aic8800d80-bt-sdio";
+            reg = <1>;
+        };
+    };
+
+    serial {
+        uart-has-rtscts;
+
+        bluetooth {
+            compatible = "aic,aic8800d80-bt";
+            max-speed = <1500000>;
+            aic,firmware-sdio = <&bt_sdio>;
+        };
+    };
-- 
2.34.1

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH bluetooth-next 1/3] dt-bindings: vendor-prefixes: Add AIC Semiconductor
  2026-09-16  8:12 [RFC PATCH bluetooth-next 0/3] Bluetooth: Add AIC8800D80 SDIO firmware loader and UART HCI Yanli Yang
@ 2026-09-16  8:12 ` Yanli Yang
  2026-09-16  8:12 ` [RFC PATCH bluetooth-next 2/3] dt-bindings: net: bluetooth: Add AIC8800D80 Yanli Yang
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 18+ messages in thread
From: Yanli Yang @ 2026-09-16  8:12 UTC (permalink / raw)
  To: linux-bluetooth
  Cc: marcel, luiz.dentz, devicetree, robh, krzk+dt, conor+dt,
	linux-kernel, zhirunliu, dijiaxu, chunqiuliu, liheng.wei,
	yanli.yang

Add the vendor prefix for AIC Semiconductor (Shanghai) Co., Ltd.,
used by the AIC8800D80 Bluetooth bindings.
Signed-off-by: Zhirun Liu <zhirunliu@aicsemi.com>
Signed-off-by: Dijia Xu <dijiaxu@aicsemi.com>
Signed-off-by: Chunqiu Liu <chunqiuliu@aicsemi.com>
Signed-off-by: Liheng Wei <liheng.wei@bedmex.com>
Signed-off-by: Yanli Yang <yanli.yang@bedmex.com>
---
 Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index 396044f368e7..a55d0d974cab 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -76,6 +76,8 @@ patternProperties:
     description: Aeroflex Gaisler AB
   "^aesop,.*":
     description: AESOP Embedded Forum
+  "^aic,.*":
+    description: AIC Semiconductor (Shanghai) Co., Ltd.
   "^airoha,.*":
     description: Airoha
   "^al,.*":
-- 
2.34.1

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH bluetooth-next 0/3] Bluetooth: Add AIC8800D80 SDIO firmware loader and UART HCI
@ 2026-09-16  8:12 Yanli Yang
  2026-09-16  8:12 ` [RFC PATCH bluetooth-next 1/3] dt-bindings: vendor-prefixes: Add AIC Semiconductor Yanli Yang
                   ` (3 more replies)
  0 siblings, 4 replies; 18+ messages in thread
From: Yanli Yang @ 2026-09-16  8:12 UTC (permalink / raw)
  To: linux-bluetooth
  Cc: marcel, luiz.dentz, devicetree, robh, krzk+dt, conor+dt,
	linux-kernel, zhirunliu, dijiaxu, chunqiuliu, liheng.wei,
	yanli.yang

Hi,

This RFC series adds initial Bluetooth support for the AIC8800D80.
SDIO function 1 loads the Bluetooth firmware; HCI traffic uses a separate
UART interface with H4 framing at 1500000 baud and hardware flow control.

The series adds the AIC vendor prefix, Device Tree bindings for the two
interfaces, and the btaic driver with build and maintainer integration.
The UART node references its SDIO firmware provider through the
aic,firmware-sdio property.

Validation status:
- The vendor has confirmed that SDIO firmware loading works.
- UART HCI communication, controller initialization, scanning, pairing,
  connections and data transfer have not yet been validated.
- Suspend/resume and Wi-Fi/Bluetooth coexistence have not been validated.
- No hardware functionality beyond firmware loading is claimed.

This is an RFC for implementation and binding review, not a request for
merging at this stage. Feedback is particularly welcome on the SDIO/UART
split, the firmware-provider association and the device lifetime handling.

The driver requests the following external firmware files:
  aic/aic8800d80/fw_adid_8800d80_u02.bin
  aic/aic8800d80/fw_patch_8800d80_u02.bin
  aic/aic8800d80/fw_patch_table_8800d80_u02.bin
Firmware binaries are not included in this kernel patch series.

This copy is sent to my own mailbox for private review.

Thanks,
Yanli

Yanli Yang (3):
  dt-bindings: vendor-prefixes: Add AIC Semiconductor
  dt-bindings: net: bluetooth: Add AIC8800D80
  Bluetooth: btaic: Add AIC8800D80 SDIO loader and UART transport

 .../net/bluetooth/aic,aic8800d80-bt.yaml      |  87 ++
 .../devicetree/bindings/vendor-prefixes.yaml  |   2 +
 MAINTAINERS                                   |  11 +
 drivers/bluetooth/Kconfig                     |  16 +
 drivers/bluetooth/Makefile                    |   3 +
 drivers/bluetooth/btaic.h                     |  26 +
 drivers/bluetooth/btaic_core.c                | 180 ++++
 drivers/bluetooth/btaic_sdio.c                | 886 ++++++++++++++++++
 drivers/bluetooth/btaic_uart.c                | 331 +++++++
 9 files changed, 1542 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml
 create mode 100644 drivers/bluetooth/btaic.h
 create mode 100644 drivers/bluetooth/btaic_core.c
 create mode 100644 drivers/bluetooth/btaic_sdio.c
 create mode 100644 drivers/bluetooth/btaic_uart.c


base-commit: 6696072ffe07205255cf83621a95a1aa2f9f6e62
-- 
2.34.1

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [RFC PATCH bluetooth-next 3/3] Bluetooth: btaic: Add AIC8800D80 SDIO loader and UART transport
  2026-09-16  8:12 [RFC PATCH bluetooth-next 0/3] Bluetooth: Add AIC8800D80 SDIO firmware loader and UART HCI Yanli Yang
  2026-09-16  8:12 ` [RFC PATCH bluetooth-next 1/3] dt-bindings: vendor-prefixes: Add AIC Semiconductor Yanli Yang
  2026-09-16  8:12 ` [RFC PATCH bluetooth-next 2/3] dt-bindings: net: bluetooth: Add AIC8800D80 Yanli Yang
@ 2026-09-16  8:12 ` Yanli Yang
  2026-09-17  2:33   ` sashiko-bot
  2026-09-20  9:50 ` [RFC PATCH v2 0/3] Bluetooth: Add AIC8800D80 SDIO firmware loader and UART HCI Yanli Yang
  3 siblings, 1 reply; 18+ messages in thread
From: Yanli Yang @ 2026-09-16  8:12 UTC (permalink / raw)
  To: linux-bluetooth
  Cc: marcel, luiz.dentz, devicetree, robh, krzk+dt, conor+dt,
	linux-kernel, zhirunliu, dijiaxu, chunqiuliu, liheng.wei,
	yanli.yang

Add a driver for the AIC8800D80 combination chip, using SDIO function 1
for Bluetooth firmware loading and a serdev UART for H4 HCI traffic.

Load the ADID image, patch image and patch table through
request_firmware(), validate the patch-table layout, and transfer firmware
using the vendor SDIO command protocol. Recognize the U02 and U03 chip
revisions.

Coordinate the SDIO firmware provider and UART consumer through a
reference-counted boot state and a device link. Wait for firmware readiness
before opening the UART, and use hardware flow control at 1500000 baud.

Add CONFIG_BT_AIC, build integration and a MAINTAINERS entry.

The vendor has confirmed SDIO firmware loading only. UART HCI operation
and other Bluetooth functionality have not yet been validated. Submit
this implementation as RFC for review before further functional testing.
Signed-off-by: Zhirun Liu <zhirunliu@aicsemi.com>
Signed-off-by: Dijia Xu <dijiaxu@aicsemi.com>
Signed-off-by: Chunqiu Liu <chunqiuliu@aicsemi.com>
Signed-off-by: Liheng Wei <liheng.wei@bedmex.com>
Signed-off-by: Yanli Yang <yanli.yang@bedmex.com>
---
 MAINTAINERS                    |  11 +
 drivers/bluetooth/Kconfig      |  16 +
 drivers/bluetooth/Makefile     |   3 +
 drivers/bluetooth/btaic.h      |  26 +
 drivers/bluetooth/btaic_core.c | 180 +++++++
 drivers/bluetooth/btaic_sdio.c | 886 +++++++++++++++++++++++++++++++++
 drivers/bluetooth/btaic_uart.c | 331 ++++++++++++
 7 files changed, 1453 insertions(+)
 create mode 100644 drivers/bluetooth/btaic.h
 create mode 100644 drivers/bluetooth/btaic_core.c
 create mode 100644 drivers/bluetooth/btaic_sdio.c
 create mode 100644 drivers/bluetooth/btaic_uart.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 891c064a881b..83b618662d86 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -730,6 +730,17 @@ S:	Maintained
 F:	drivers/scsi/aha152x*
 F:	drivers/scsi/pcmcia/aha152x*
 
+AIC BLUETOOTH DRIVER
+M:	Zhirun Liu <zhirunliu@aicsemi.com>
+M:	Dijia Xu <dijiaxu@aicsemi.com>
+M:	Chunqiu Liu <chunqiuliu@aicsemi.com>
+M:	Liheng Wei <liheng.wei@bedmex.com>
+M:	Yanli Yang <yanli.yang@bedmex.com>
+L:	linux-bluetooth@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml
+F:	drivers/bluetooth/btaic*
+
 AIC7XXX / AIC79XX SCSI DRIVER
 M:	Hannes Reinecke <hare@suse.com>
 L:	linux-scsi@vger.kernel.org
diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
index 4e8c24d757e9..c8f66742f98b 100644
--- a/drivers/bluetooth/Kconfig
+++ b/drivers/bluetooth/Kconfig
@@ -472,4 +472,20 @@ config BT_INTEL_PCIE
 
 	  Say Y here to compiler support for Intel Bluetooth PCIe device into
 	  the kernel or say M to compile it as module (btintel_pcie)
+
+config BT_AIC
+	tristate "AIC8800D80 Bluetooth support"
+	depends on MMC
+	depends on SERIAL_DEV_BUS
+	depends on BT_HCIUART
+	select BT_HCIUART_H4
+	select FW_LOADER
+	help
+	  Bluetooth support for the AIC8800D80 combination chip, using an
+	  SDIO function for firmware loading and a UART interface for the
+	  HCI transport.
+
+	  Say Y here to compile support into the kernel or M to build it as
+	  a module named btaic.
+
 endmenu
diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
index e6b1c1180d1d..d9569d6563f1 100644
--- a/drivers/bluetooth/Makefile
+++ b/drivers/bluetooth/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_BT_MRVL)		+= btmrvl.o
 obj-$(CONFIG_BT_MRVL_SDIO)	+= btmrvl_sdio.o
 obj-$(CONFIG_BT_MTKSDIO)	+= btmtksdio.o
 obj-$(CONFIG_BT_MTKUART)	+= btmtkuart.o
+obj-$(CONFIG_BT_AIC)		+= btaic.o
 obj-$(CONFIG_BT_QCOMSMD)	+= btqcomsmd.o
 obj-$(CONFIG_BT_BCM)		+= btbcm.o
 obj-$(CONFIG_BT_RTL)		+= btrtl.o
@@ -51,4 +52,6 @@ hci_uart-$(CONFIG_BT_HCIUART_MRVL)	+= hci_mrvl.o
 hci_uart-$(CONFIG_BT_HCIUART_AML)	+= hci_aml.o
 hci_uart-objs				:= $(hci_uart-y)
 
+btaic-y				:= btaic_core.o btaic_sdio.o btaic_uart.o
+
 CONTEXT_ANALYSIS := y
diff --git a/drivers/bluetooth/btaic.h b/drivers/bluetooth/btaic.h
new file mode 100644
index 000000000000..dc631b495697
--- /dev/null
+++ b/drivers/bluetooth/btaic.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __BTAIC_H
+#define __BTAIC_H
+
+#include <linux/types.h>
+
+struct device;
+struct fwnode_handle;
+
+struct aic_bt_boot;
+
+struct aic_bt_boot *aic_bt_boot_register(struct device *dev);
+void aic_bt_boot_ready(struct aic_bt_boot *boot, int status);
+void aic_bt_boot_unregister(struct aic_bt_boot *boot);
+
+struct aic_bt_boot *aic_bt_boot_get(const struct fwnode_handle *fwnode);
+void aic_bt_boot_put(struct aic_bt_boot *boot);
+int aic_bt_boot_wait(struct aic_bt_boot *boot, unsigned long timeout);
+struct device *aic_bt_boot_device(struct aic_bt_boot *boot);
+
+int aic_bt_sdio_register(void);
+void aic_bt_sdio_unregister(void);
+int aic_bt_uart_register(void);
+void aic_bt_uart_unregister(void);
+
+#endif
diff --git a/drivers/bluetooth/btaic_core.c b/drivers/bluetooth/btaic_core.c
new file mode 100644
index 000000000000..35644b9b1209
--- /dev/null
+++ b/drivers/bluetooth/btaic_core.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief AIC8800D80 Bluetooth driver core
+ *
+ ******************************************************************************
+ */
+
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+
+#include "btaic.h"
+
+struct aic_bt_boot {
+	struct kref ref;
+	struct list_head node;
+	struct completion ready;
+	struct device *dev;
+	int status;
+	bool present;
+};
+
+static DEFINE_MUTEX(aic_bt_boot_lock);
+static LIST_HEAD(aic_bt_boot_list);
+
+static void aic_bt_boot_release(struct kref *ref)
+{
+	struct aic_bt_boot *boot = container_of(ref, struct aic_bt_boot, ref);
+
+	put_device(boot->dev);
+	kfree(boot);
+}
+
+struct aic_bt_boot *aic_bt_boot_register(struct device *dev)
+{
+	struct aic_bt_boot *boot;
+
+	boot = kzalloc_obj(*boot);
+	if (!boot)
+		return ERR_PTR(-ENOMEM);
+
+	kref_init(&boot->ref);
+	INIT_LIST_HEAD(&boot->node);
+	init_completion(&boot->ready);
+	boot->dev = get_device(dev);
+	boot->status = -EINPROGRESS;
+	boot->present = true;
+
+	mutex_lock(&aic_bt_boot_lock);
+	list_add_tail(&boot->node, &aic_bt_boot_list);
+	mutex_unlock(&aic_bt_boot_lock);
+
+	return boot;
+}
+
+void aic_bt_boot_ready(struct aic_bt_boot *boot, int status)
+{
+	mutex_lock(&aic_bt_boot_lock);
+	if (boot->present)
+		boot->status = status;
+	mutex_unlock(&aic_bt_boot_lock);
+
+	complete_all(&boot->ready);
+}
+
+void aic_bt_boot_unregister(struct aic_bt_boot *boot)
+{
+	if (!boot)
+		return;
+
+	mutex_lock(&aic_bt_boot_lock);
+	if (boot->present) {
+		list_del_init(&boot->node);
+		boot->present = false;
+		boot->status = -ENODEV;
+	}
+	mutex_unlock(&aic_bt_boot_lock);
+
+	complete_all(&boot->ready);
+	kref_put(&boot->ref, aic_bt_boot_release);
+}
+
+struct aic_bt_boot *aic_bt_boot_get(const struct fwnode_handle *fwnode)
+{
+	struct aic_bt_boot *boot;
+	struct aic_bt_boot *found = NULL;
+	unsigned int count = 0;
+
+	mutex_lock(&aic_bt_boot_lock);
+	list_for_each_entry(boot, &aic_bt_boot_list, node) {
+		if (!boot->present)
+			continue;
+
+		if (fwnode) {
+			if (dev_fwnode(boot->dev) == fwnode) {
+				found = boot;
+				break;
+			}
+			continue;
+		}
+
+		found = boot;
+		count++;
+	}
+
+	if (found && (fwnode || count == 1))
+		kref_get(&found->ref);
+	else if (count > 1)
+		found = ERR_PTR(-EINVAL);
+	else
+		found = ERR_PTR(-EPROBE_DEFER);
+	mutex_unlock(&aic_bt_boot_lock);
+
+	return found;
+}
+
+void aic_bt_boot_put(struct aic_bt_boot *boot)
+{
+	if (!IS_ERR_OR_NULL(boot))
+		kref_put(&boot->ref, aic_bt_boot_release);
+}
+
+int aic_bt_boot_wait(struct aic_bt_boot *boot, unsigned long timeout)
+{
+	int status;
+
+	if (!wait_for_completion_timeout(&boot->ready, timeout))
+		return -ETIMEDOUT;
+
+	mutex_lock(&aic_bt_boot_lock);
+	status = boot->present ? boot->status : -ENODEV;
+	mutex_unlock(&aic_bt_boot_lock);
+
+	return status;
+}
+
+struct device *aic_bt_boot_device(struct aic_bt_boot *boot)
+{
+	return boot->dev;
+}
+
+static int __init aic_bt_init(void)
+{
+	int err;
+
+	err = aic_bt_sdio_register();
+	if (err)
+		return err;
+
+	err = aic_bt_uart_register();
+	if (err) {
+		aic_bt_sdio_unregister();
+		return err;
+	}
+
+	return 0;
+}
+
+static void __exit aic_bt_exit(void)
+{
+	aic_bt_uart_unregister();
+	aic_bt_sdio_unregister();
+}
+
+module_init(aic_bt_init);
+module_exit(aic_bt_exit);
+
+MODULE_AUTHOR("AIC Semiconductor");
+MODULE_DESCRIPTION("AIC8800D80 Bluetooth driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/bluetooth/btaic_sdio.c b/drivers/bluetooth/btaic_sdio.c
new file mode 100644
index 000000000000..87b6b1c03a6e
--- /dev/null
+++ b/drivers/bluetooth/btaic_sdio.c
@@ -0,0 +1,886 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief AIC8800D80 Bluetooth SDIO firmware loader
+ *
+ ******************************************************************************
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/minmax.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/sdio_ids.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/overflow.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/unaligned.h>
+
+#include "btaic.h"
+
+#define AIC_SDIO_VENDOR_ID			0xc8a1
+#define AIC_SDIO_DEVICE_ID_8800D80		0x0082
+
+#define AIC_SDIO_BLOCK_SIZE			512
+#define AIC_SDIO_BUFFER_SIZE			1536
+#define AIC_SDIO_TX_BUFFER_SIZE			1536
+#define AIC_SDIO_RX_MAX_SIZE			(127 * AIC_SDIO_BLOCK_SIZE)
+
+#define AIC_SDIO_INTR_ENABLE			0x00
+#define AIC_SDIO_INTR_PENDING			0x01
+#define AIC_SDIO_FLOW_CTRL_Q1			0x03
+#define AIC_SDIO_MISC_INT_STATUS		0x04
+#define AIC_SDIO_BYTEMODE_LEN			0x05
+#define AIC_SDIO_BYTEMODE_ENABLE		0x07
+#define AIC_SDIO_RD_FIFO			0x0f
+#define AIC_SDIO_WR_FIFO			0x10
+
+#define AIC_SDIO_OTHER_INTERRUPT		BIT(7)
+#define AIC_SDIO_BYTE_MODE_BLOCKS		120
+#define AIC_SDIO_FLOW_RETRIES			50
+#define AIC_SDIO_FRAME_TAIL_LEN			4
+
+#define AIC_CMD_TIMEOUT_MS			6000
+#define AIC_CMD_TYPE				0x11
+#define AIC_TASK_DBG				1
+#define AIC_DRIVER_TASK				100
+#define AIC_FIRST_MSG(task)			((u16)(task) << 10)
+
+#define AIC_DBG_MEM_READ_REQ			(AIC_FIRST_MSG(AIC_TASK_DBG) + 0)
+#define AIC_DBG_MEM_READ_CFM			(AIC_FIRST_MSG(AIC_TASK_DBG) + 1)
+#define AIC_DBG_MEM_WRITE_REQ			(AIC_FIRST_MSG(AIC_TASK_DBG) + 2)
+#define AIC_DBG_MEM_WRITE_CFM			(AIC_FIRST_MSG(AIC_TASK_DBG) + 3)
+#define AIC_DBG_MEM_BLOCK_WRITE_REQ		(AIC_FIRST_MSG(AIC_TASK_DBG) + 11)
+#define AIC_DBG_MEM_BLOCK_WRITE_CFM		(AIC_FIRST_MSG(AIC_TASK_DBG) + 12)
+
+#define AIC_BT_CHIP_ID_ADDR			0x40500000
+#define AIC_BT_CHIP_REV_U02			3
+#define AIC_BT_CHIP_REV_U03			7
+
+#define AIC_BT_FW_ADID				"aic/aic8800d80/fw_adid_8800d80_u02.bin"
+#define AIC_BT_FW_PATCH				"aic/aic8800d80/fw_patch_8800d80_u02.bin"
+#define AIC_BT_FW_TABLE				"aic/aic8800d80/fw_patch_table_8800d80_u02.bin"
+
+#define AIC_BT_PATCH_TAG			"AICBT_PT_TAG"
+#define AIC_BT_PATCH_TAG_SIZE			16
+#define AIC_BT_PATCH_RECORD_HEADER_SIZE		24
+#define AIC_BT_PATCH_PAIR_SIZE			8
+#define AIC_BT_PATCH_BLOCK_SIZE			1024
+
+#define AIC_BT_MODE_ONLY_COANT			5
+#define AIC_BT_PORT_UART			2
+#define AIC_BT_UART_BAUD			1500000
+#define AIC_BT_UART_FLOW_CTRL			1
+#define AIC_BT_LOW_POWER_ENABLE			1
+#define AIC_BT_TX_POWER_LEVEL			0x00006f2f
+
+enum aic_bt_patch_type {
+	AIC_BT_PATCH_INFO,
+	AIC_BT_PATCH_TRAP,
+	AIC_BT_PATCH_B4,
+	AIC_BT_PATCH_MODE,
+	AIC_BT_PATCH_POWER_ON,
+	AIC_BT_PATCH_AF,
+	AIC_BT_PATCH_VERSION,
+};
+
+struct aic_bt_e2a_header {
+	__le16 id;
+	__le16 dest_id;
+	__le16 src_id;
+	__le16 param_len;
+	__le32 pattern;
+	u8 param[];
+} __packed;
+
+struct aic_bt_mem_read_cfm {
+	__le32 address;
+	__le32 value;
+} __packed;
+
+struct aic_bt_sdio {
+	struct sdio_func *func;
+	struct aic_bt_boot *boot;
+
+	/* Serializes commands because the firmware accepts one at a time. */
+	struct mutex command_mutex;
+	/* Protects response state shared with the SDIO IRQ handler. */
+	spinlock_t response_lock;
+	struct completion command_done;
+	u16 expected_response;
+	void *response;
+	size_t response_size;
+	int command_result;
+	bool waiting_response;
+
+	u8 *tx_buf;
+	bool function_enabled;
+	bool irq_claimed;
+};
+
+static u8 aic_bt_crc8(const u8 *buffer, size_t len)
+{
+	u8 crc = 0;
+	size_t byte;
+	int bit;
+
+	for (byte = 0; byte < len; byte++) {
+		for (bit = 0x80; bit; bit >>= 1) {
+			if (crc & 0x80)
+				crc = (crc << 1) ^ 0x07;
+			else
+				crc <<= 1;
+
+			if (buffer[byte] & bit)
+				crc ^= 0x07;
+		}
+	}
+
+	return crc;
+}
+
+static void aic_bt_complete_response(struct aic_bt_sdio *btdev,
+				     const struct aic_bt_e2a_header *message,
+				     size_t message_len)
+{
+	unsigned long flags;
+	size_t param_len;
+	u16 id;
+	bool complete_command = false;
+
+	if (message_len < sizeof(*message))
+		return;
+
+	id = get_unaligned_le16(&message->id);
+	param_len = get_unaligned_le16(&message->param_len);
+	if (param_len > message_len - sizeof(*message))
+		return;
+
+	spin_lock_irqsave(&btdev->response_lock, flags);
+	if (!btdev->waiting_response || id != btdev->expected_response)
+		goto unlock;
+
+	if (btdev->response && param_len < btdev->response_size) {
+		btdev->command_result = -EMSGSIZE;
+	} else {
+		if (btdev->response)
+			memcpy(btdev->response, message->param,
+			       btdev->response_size);
+		btdev->command_result = 0;
+	}
+
+	btdev->waiting_response = false;
+	complete_command = true;
+
+unlock:
+	spin_unlock_irqrestore(&btdev->response_lock, flags);
+
+	if (complete_command)
+		complete(&btdev->command_done);
+}
+
+static void aic_bt_parse_rx(struct aic_bt_sdio *btdev, const u8 *data,
+			    size_t data_len)
+{
+	size_t offset = 0;
+
+	while (data_len - offset >= 4) {
+		const struct aic_bt_e2a_header *message;
+		size_t frame_len;
+		size_t frame_size;
+
+		frame_len = get_unaligned_le16(data + offset);
+		if (!frame_len)
+			break;
+
+		if (check_add_overflow(frame_len, (size_t)4, &frame_size) ||
+		    frame_size > data_len - offset)
+			break;
+
+		if ((data[offset + 2] & 0x7f) == AIC_CMD_TYPE) {
+			message = (const void *)(data + offset + 4);
+			aic_bt_complete_response(btdev, message, frame_len);
+		}
+
+		frame_size = roundup(frame_len, 4) + 4;
+		if (frame_size > data_len - offset)
+			break;
+		offset += frame_size;
+	}
+}
+
+static void aic_bt_sdio_irq(struct sdio_func *func)
+{
+	struct aic_bt_sdio *btdev = sdio_get_drvdata(func);
+	u8 *data;
+	u8 blocks;
+	u8 status;
+	size_t data_len;
+	int err;
+
+	status = sdio_readb(func, AIC_SDIO_MISC_INT_STATUS, &err);
+	if (err) {
+		dev_err_ratelimited(&func->dev,
+				    "failed to read interrupt status: %d\n", err);
+		return;
+	}
+
+	if (status & AIC_SDIO_OTHER_INTERRUPT) {
+		u8 pending;
+
+		pending = sdio_readb(func, AIC_SDIO_INTR_PENDING, &err);
+		if (!err) {
+			pending &= ~BIT(0);
+			sdio_writeb(func, pending, AIC_SDIO_INTR_PENDING, &err);
+		}
+		if (err)
+			dev_err_ratelimited(&func->dev,
+					    "failed to clear soft interrupt: %d\n",
+					    err);
+	}
+
+	blocks = status & 0x7f;
+	if (!blocks)
+		return;
+
+	if (blocks == AIC_SDIO_BYTE_MODE_BLOCKS) {
+		u8 words;
+
+		words = sdio_readb(func, AIC_SDIO_BYTEMODE_LEN, &err);
+		if (err)
+			return;
+		data_len = (size_t)words * 4;
+	} else {
+		data_len = (size_t)blocks * AIC_SDIO_BLOCK_SIZE;
+	}
+
+	if (!data_len || data_len > AIC_SDIO_RX_MAX_SIZE) {
+		dev_err_ratelimited(&func->dev,
+				    "invalid SDIO response length %zu\n", data_len);
+		return;
+	}
+
+	data = kmalloc(data_len, GFP_KERNEL);
+	if (!data)
+		return;
+
+	err = sdio_readsb(func, data, AIC_SDIO_RD_FIFO, data_len);
+	if (err)
+		dev_err_ratelimited(&func->dev,
+				    "failed to read response: %d\n", err);
+	else
+		aic_bt_parse_rx(btdev, data, data_len);
+
+	kfree(data);
+}
+
+static int aic_bt_wait_for_credits(struct aic_bt_sdio *btdev, size_t tx_len)
+{
+	unsigned int retry;
+	int err;
+
+	for (retry = 0; retry < AIC_SDIO_FLOW_RETRIES; retry++) {
+		u8 credits;
+
+		credits = sdio_readb(btdev->func, AIC_SDIO_FLOW_CTRL_Q1, &err);
+		if (err)
+			return err;
+
+		if (credits && tx_len < (size_t)credits * AIC_SDIO_BUFFER_SIZE)
+			return 0;
+
+		if (retry < 30)
+			usleep_range(30, 50);
+		else if (retry < 40)
+			usleep_range(1000, 1500);
+		else
+			usleep_range(10000, 12000);
+	}
+
+	return -ETIMEDOUT;
+}
+
+static int aic_bt_command(struct aic_bt_sdio *btdev, u16 request_id,
+			  const void *param, size_t param_len, u16 response_id,
+			  void *response, size_t response_size)
+{
+	unsigned long flags;
+	size_t frame_len;
+	size_t message_len;
+	size_t tx_len;
+	long timeout;
+	int err;
+
+	if (param_len > U16_MAX)
+		return -EMSGSIZE;
+
+	message_len = 8 + param_len;
+	frame_len = 8 + message_len;
+	if (frame_len > AIC_SDIO_TX_BUFFER_SIZE)
+		return -EMSGSIZE;
+
+	if (IS_ALIGNED(frame_len, AIC_SDIO_BLOCK_SIZE))
+		tx_len = frame_len;
+	else
+		tx_len = roundup(frame_len + AIC_SDIO_FRAME_TAIL_LEN,
+				 AIC_SDIO_BLOCK_SIZE);
+
+	if (tx_len > AIC_SDIO_TX_BUFFER_SIZE)
+		return -EMSGSIZE;
+
+	mutex_lock(&btdev->command_mutex);
+
+	memset(btdev->tx_buf, 0, tx_len);
+	put_unaligned_le16(message_len + 4, btdev->tx_buf);
+	btdev->tx_buf[2] = AIC_CMD_TYPE;
+	btdev->tx_buf[3] = aic_bt_crc8(btdev->tx_buf, 3);
+
+	put_unaligned_le16(request_id, btdev->tx_buf + 8);
+	put_unaligned_le16(AIC_TASK_DBG, btdev->tx_buf + 10);
+	put_unaligned_le16(AIC_DRIVER_TASK, btdev->tx_buf + 12);
+	put_unaligned_le16(param_len, btdev->tx_buf + 14);
+	if (param_len)
+		memcpy(btdev->tx_buf + 16, param, param_len);
+
+	reinit_completion(&btdev->command_done);
+	spin_lock_irqsave(&btdev->response_lock, flags);
+	btdev->expected_response = response_id;
+	btdev->response = response;
+	btdev->response_size = response_size;
+	btdev->command_result = -EINPROGRESS;
+	btdev->waiting_response = true;
+	spin_unlock_irqrestore(&btdev->response_lock, flags);
+
+	sdio_claim_host(btdev->func);
+	err = aic_bt_wait_for_credits(btdev, tx_len);
+	if (!err)
+		err = sdio_writesb(btdev->func, AIC_SDIO_WR_FIFO,
+				   btdev->tx_buf, tx_len);
+	sdio_release_host(btdev->func);
+	if (err)
+		goto clear_response;
+
+	timeout = wait_for_completion_timeout(&btdev->command_done,
+					      msecs_to_jiffies(AIC_CMD_TIMEOUT_MS));
+	if (!timeout) {
+		dev_err(&btdev->func->dev,
+			"command 0x%04x timed out waiting for 0x%04x\n",
+			request_id, response_id);
+		err = -ETIMEDOUT;
+		goto clear_response;
+	}
+
+	spin_lock_irqsave(&btdev->response_lock, flags);
+	err = btdev->command_result;
+	btdev->response = NULL;
+	btdev->response_size = 0;
+	spin_unlock_irqrestore(&btdev->response_lock, flags);
+	mutex_unlock(&btdev->command_mutex);
+
+	return err;
+
+clear_response:
+	spin_lock_irqsave(&btdev->response_lock, flags);
+	btdev->waiting_response = false;
+	btdev->response = NULL;
+	btdev->response_size = 0;
+	spin_unlock_irqrestore(&btdev->response_lock, flags);
+	mutex_unlock(&btdev->command_mutex);
+
+	return err;
+}
+
+static int aic_bt_mem_read(struct aic_bt_sdio *btdev, u32 address, u32 *value)
+{
+	struct aic_bt_mem_read_cfm cfm;
+	__le32 request = cpu_to_le32(address);
+	int err;
+
+	err = aic_bt_command(btdev, AIC_DBG_MEM_READ_REQ,
+			     &request, sizeof(request), AIC_DBG_MEM_READ_CFM,
+			     &cfm, sizeof(cfm));
+	if (!err)
+		*value = le32_to_cpu(cfm.value);
+
+	return err;
+}
+
+static int aic_bt_mem_write(struct aic_bt_sdio *btdev, u32 address, u32 value)
+{
+	__le32 request[2] = {
+		cpu_to_le32(address),
+		cpu_to_le32(value),
+	};
+
+	return aic_bt_command(btdev, AIC_DBG_MEM_WRITE_REQ,
+			      request, sizeof(request), AIC_DBG_MEM_WRITE_CFM,
+			      NULL, 0);
+}
+
+static int aic_bt_mem_block_write(struct aic_bt_sdio *btdev, u32 address,
+				  const u8 *data, size_t data_len)
+{
+	__le32 status;
+	u8 *request;
+	int err;
+
+	if (data_len > AIC_BT_PATCH_BLOCK_SIZE)
+		return -EINVAL;
+
+	request = kzalloc(8 + AIC_BT_PATCH_BLOCK_SIZE, GFP_KERNEL);
+	if (!request)
+		return -ENOMEM;
+
+	put_unaligned_le32(address, request);
+	put_unaligned_le32(data_len, request + 4);
+	memcpy(request + 8, data, data_len);
+
+	err = aic_bt_command(btdev, AIC_DBG_MEM_BLOCK_WRITE_REQ,
+			     request, 8 + AIC_BT_PATCH_BLOCK_SIZE,
+			     AIC_DBG_MEM_BLOCK_WRITE_CFM,
+			     &status, sizeof(status));
+	kfree(request);
+	if (err)
+		return err;
+
+	if (le32_to_cpu(status)) {
+		dev_err(&btdev->func->dev,
+			"firmware rejected block write at 0x%08x\n", address);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int aic_bt_upload_firmware(struct aic_bt_sdio *btdev, const char *name,
+				  u32 address)
+{
+	const struct firmware *firmware;
+	size_t offset = 0;
+	int err;
+
+	err = request_firmware(&firmware, name, &btdev->func->dev);
+	if (err)
+		return dev_err_probe(&btdev->func->dev, err,
+				     "failed to load %s\n", name);
+
+	if (firmware->size > U32_MAX - address) {
+		err = -EFBIG;
+		goto release;
+	}
+
+	while (offset < firmware->size) {
+		size_t len = min_t(size_t, AIC_BT_PATCH_BLOCK_SIZE,
+				   firmware->size - offset);
+
+		err = aic_bt_mem_block_write(btdev, address + offset,
+					     firmware->data + offset, len);
+		if (err)
+			goto release;
+		offset += len;
+	}
+
+	dev_dbg(&btdev->func->dev, "loaded %s (%zu bytes) at 0x%08x\n",
+		name, firmware->size, address);
+
+release:
+	release_firmware(firmware);
+	return err;
+}
+
+static u32 aic_bt_mode_value(unsigned int pair, u32 firmware_value)
+{
+	switch (pair) {
+	case 0:
+		return 1;
+	case 1:
+		return U32_MAX;
+	case 2:
+		return 0;
+	case 3:
+		return AIC_BT_MODE_ONLY_COANT;
+	case 4:
+		return AIC_BT_PORT_UART;
+	case 5:
+		return AIC_BT_UART_BAUD;
+	case 6:
+		return AIC_BT_UART_FLOW_CTRL;
+	case 7:
+		return AIC_BT_LOW_POWER_ENABLE;
+	case 8:
+		return AIC_BT_TX_POWER_LEVEL;
+	default:
+		return firmware_value;
+	}
+}
+
+static int aic_bt_process_patch_table(struct aic_bt_sdio *btdev,
+				      const struct firmware *firmware,
+				      bool apply, u32 *adid_addr,
+				      u32 *patch_addr)
+{
+	const u8 *data = firmware->data;
+	size_t offset = AIC_BT_PATCH_TAG_SIZE;
+	bool have_record = false;
+	bool have_info = false;
+
+	if (firmware->size < AIC_BT_PATCH_TAG_SIZE ||
+	    memcmp(data, AIC_BT_PATCH_TAG, sizeof(AIC_BT_PATCH_TAG)))
+		return -EBADMSG;
+
+	while (offset < firmware->size) {
+		const u8 *record;
+		const u8 *pairs_data;
+		size_t pairs_size;
+		unsigned int pair;
+		u32 pair_count;
+		u32 type;
+		int err;
+
+		if (firmware->size - offset <
+		    AIC_BT_PATCH_RECORD_HEADER_SIZE)
+			return -EBADMSG;
+
+		record = data + offset;
+		type = get_unaligned_le32(record + 16);
+		pair_count = get_unaligned_le32(record + 20);
+		offset += AIC_BT_PATCH_RECORD_HEADER_SIZE;
+
+		if (type >= 1000) {
+			pairs_size = 0;
+			pair_count = 0;
+		} else if (check_mul_overflow((size_t)pair_count,
+					      (size_t)AIC_BT_PATCH_PAIR_SIZE,
+					      &pairs_size)) {
+			return -EOVERFLOW;
+		}
+
+		if (pairs_size > firmware->size - offset)
+			return -EBADMSG;
+
+		pairs_data = data + offset;
+		have_record = true;
+
+		if (type == AIC_BT_PATCH_INFO) {
+			if (pair_count < 2)
+				return -EBADMSG;
+
+			if (!have_info) {
+				if (adid_addr)
+					*adid_addr = get_unaligned_le32(pairs_data + 4);
+				if (patch_addr)
+					*patch_addr = get_unaligned_le32(pairs_data + 12);
+				have_info = true;
+			}
+		}
+
+		if (!apply)
+			goto next_record;
+
+		if (type == AIC_BT_PATCH_VERSION) {
+			dev_info(&btdev->func->dev, "BT patch version: %.*s\n",
+				 (int)min_t(size_t, pairs_size, 80),
+				 pairs_data);
+			goto next_record;
+		}
+
+		if (type == AIC_BT_PATCH_MODE && pair_count < 9)
+			return -EBADMSG;
+
+		for (pair = 0; pair < pair_count; pair++) {
+			u32 address;
+			u32 value;
+
+			address = get_unaligned_le32(pairs_data +
+						    pair * AIC_BT_PATCH_PAIR_SIZE);
+			value = get_unaligned_le32(pairs_data +
+						  pair * AIC_BT_PATCH_PAIR_SIZE + 4);
+			if (type == AIC_BT_PATCH_MODE)
+				value = aic_bt_mode_value(pair, value);
+
+			err = aic_bt_mem_write(btdev, address, value);
+			if (err)
+				return err;
+		}
+
+		if (type == AIC_BT_PATCH_POWER_ON)
+			usleep_range(50, 100);
+
+next_record:
+		offset += pairs_size;
+	}
+
+	if (!have_record || ((adid_addr || patch_addr) && !have_info))
+		return -EBADMSG;
+
+	return 0;
+}
+
+static int aic_bt_download_firmware(struct aic_bt_sdio *btdev)
+{
+	const struct firmware *table;
+	u32 chip_id;
+	u32 adid_addr;
+	u32 patch_addr;
+	u8 revision;
+	int err;
+
+	err = aic_bt_mem_read(btdev, AIC_BT_CHIP_ID_ADDR, &chip_id);
+	if (err)
+		return err;
+
+	revision = (chip_id >> 16) & 0x3f;
+	if (revision != AIC_BT_CHIP_REV_U02 &&
+	    revision != AIC_BT_CHIP_REV_U03) {
+		dev_err(&btdev->func->dev,
+			"unsupported AIC8800D80 revision %u\n", revision);
+		return -ENODEV;
+	}
+
+	err = request_firmware(&table, AIC_BT_FW_TABLE, &btdev->func->dev);
+	if (err)
+		return dev_err_probe(&btdev->func->dev, err,
+				     "failed to load %s\n", AIC_BT_FW_TABLE);
+
+	err = aic_bt_process_patch_table(btdev, table, false,
+					 &adid_addr, &patch_addr);
+	if (err) {
+		dev_err(&btdev->func->dev, "invalid BT patch table: %d\n", err);
+		goto release_table;
+	}
+
+	err = aic_bt_upload_firmware(btdev, AIC_BT_FW_ADID, adid_addr);
+	if (err)
+		goto release_table;
+
+	err = aic_bt_upload_firmware(btdev, AIC_BT_FW_PATCH, patch_addr);
+	if (err)
+		goto release_table;
+
+	err = aic_bt_process_patch_table(btdev, table, true, NULL, NULL);
+	if (!err)
+		dev_info(&btdev->func->dev,
+			 "AIC8800D80 revision %u Bluetooth firmware ready\n",
+			 revision);
+
+release_table:
+	release_firmware(table);
+	return err;
+}
+
+static int aic_bt_sdio_hw_init(struct aic_bt_sdio *btdev)
+{
+	struct sdio_func *func = btdev->func;
+	struct mmc_host *host = func->card->host;
+	u8 io_control;
+	int err;
+
+	sdio_claim_host(func);
+	func->card->quirks |= MMC_QUIRK_LENIENT_FN0;
+
+	err = sdio_set_block_size(func, AIC_SDIO_BLOCK_SIZE);
+	if (err)
+		goto release_host;
+
+	err = sdio_enable_func(func);
+	if (err)
+		goto release_host;
+	btdev->function_enabled = true;
+
+	sdio_f0_writeb(func, 0x7f, 0xf2, &err);
+	if (err)
+		goto disable_func;
+
+	io_control = host->ios.timing == MMC_TIMING_UHS_DDR50 ? 0x20 : 0x00;
+	io_control |= BIT(6);
+	sdio_f0_writeb(func, io_control, 0xf0, &err);
+	if (err)
+		goto disable_func;
+
+	sdio_f0_writeb(func, 0x00, 0xf8, &err);
+	if (err)
+		goto disable_func;
+
+	sdio_f0_writeb(func, 0x00, 0xf1, &err);
+	if (err)
+		goto disable_func;
+
+	sdio_writeb(func, 1, AIC_SDIO_BYTEMODE_ENABLE, &err);
+	if (err)
+		goto disable_func;
+
+	err = sdio_claim_irq(func, aic_bt_sdio_irq);
+	if (err)
+		goto disable_func;
+	btdev->irq_claimed = true;
+
+	sdio_f0_writeb(func, 0x07, 0x04, &err);
+	if (err)
+		goto release_irq;
+
+	sdio_writeb(func, 0x07, AIC_SDIO_INTR_ENABLE, &err);
+	if (err)
+		goto release_irq;
+
+	sdio_release_host(func);
+	return 0;
+
+release_irq:
+	sdio_release_irq(func);
+	btdev->irq_claimed = false;
+disable_func:
+	sdio_disable_func(func);
+	btdev->function_enabled = false;
+release_host:
+	sdio_release_host(func);
+	return err;
+}
+
+static void aic_bt_sdio_hw_deinit(struct aic_bt_sdio *btdev)
+{
+	sdio_claim_host(btdev->func);
+
+	if (btdev->irq_claimed) {
+		int err;
+
+		sdio_writeb(btdev->func, 0, AIC_SDIO_INTR_ENABLE, &err);
+		sdio_release_irq(btdev->func);
+		btdev->irq_claimed = false;
+	}
+
+	if (btdev->function_enabled) {
+		sdio_disable_func(btdev->func);
+		btdev->function_enabled = false;
+	}
+
+	sdio_release_host(btdev->func);
+}
+
+static int aic_bt_sdio_probe(struct sdio_func *func,
+			     const struct sdio_device_id *id)
+{
+	struct aic_bt_sdio *btdev;
+	int err;
+
+	if (func->num != 1)
+		return -ENODEV;
+
+	btdev = devm_kzalloc(&func->dev, sizeof(*btdev), GFP_KERNEL);
+	if (!btdev)
+		return -ENOMEM;
+
+	btdev->tx_buf = devm_kmalloc(&func->dev, AIC_SDIO_TX_BUFFER_SIZE,
+				     GFP_KERNEL);
+	if (!btdev->tx_buf)
+		return -ENOMEM;
+
+	btdev->func = func;
+	mutex_init(&btdev->command_mutex);
+	spin_lock_init(&btdev->response_lock);
+	init_completion(&btdev->command_done);
+	sdio_set_drvdata(func, btdev);
+
+	err = aic_bt_sdio_hw_init(btdev);
+	if (err)
+		goto clear_drvdata;
+
+	btdev->boot = aic_bt_boot_register(&func->dev);
+	if (IS_ERR(btdev->boot)) {
+		err = PTR_ERR(btdev->boot);
+		btdev->boot = NULL;
+		goto deinit_hw;
+	}
+
+	err = aic_bt_download_firmware(btdev);
+	aic_bt_boot_ready(btdev->boot, err);
+	if (err)
+		goto unregister_boot;
+
+	return 0;
+
+unregister_boot:
+	aic_bt_boot_unregister(btdev->boot);
+	btdev->boot = NULL;
+deinit_hw:
+	aic_bt_sdio_hw_deinit(btdev);
+clear_drvdata:
+	sdio_set_drvdata(func, NULL);
+	return err;
+}
+
+static void aic_bt_sdio_remove(struct sdio_func *func)
+{
+	struct aic_bt_sdio *btdev = sdio_get_drvdata(func);
+
+	aic_bt_boot_unregister(btdev->boot);
+	btdev->boot = NULL;
+	aic_bt_sdio_hw_deinit(btdev);
+	sdio_set_drvdata(func, NULL);
+}
+
+static int aic_bt_sdio_suspend(struct device *dev)
+{
+	struct sdio_func *func = dev_to_sdio_func(dev);
+	mmc_pm_flag_t caps;
+
+	caps = sdio_get_host_pm_caps(func);
+	if (!(caps & MMC_PM_KEEP_POWER))
+		return -EOPNOTSUPP;
+
+	return sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
+}
+
+static int aic_bt_sdio_resume(struct device *dev)
+{
+	return 0;
+}
+
+static const struct dev_pm_ops aic_bt_sdio_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(aic_bt_sdio_suspend, aic_bt_sdio_resume)
+};
+
+static const struct sdio_device_id aic_bt_sdio_ids[] = {
+	{ SDIO_DEVICE(AIC_SDIO_VENDOR_ID, AIC_SDIO_DEVICE_ID_8800D80) },
+	{ }
+};
+MODULE_DEVICE_TABLE(sdio, aic_bt_sdio_ids);
+
+static const struct of_device_id aic_bt_sdio_of_match[] = {
+	{ .compatible = "aic,aic8800d80-bt-sdio" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, aic_bt_sdio_of_match);
+
+static struct sdio_driver aic_bt_sdio_driver = {
+	.name = "aic_bt_sdio",
+	.id_table = aic_bt_sdio_ids,
+	.probe = aic_bt_sdio_probe,
+	.remove = aic_bt_sdio_remove,
+	.drv = {
+		.of_match_table = aic_bt_sdio_of_match,
+		.pm = &aic_bt_sdio_pm_ops,
+	},
+};
+
+int aic_bt_sdio_register(void)
+{
+	return sdio_register_driver(&aic_bt_sdio_driver);
+}
+
+void aic_bt_sdio_unregister(void)
+{
+	sdio_unregister_driver(&aic_bt_sdio_driver);
+}
+
+MODULE_FIRMWARE(AIC_BT_FW_ADID);
+MODULE_FIRMWARE(AIC_BT_FW_PATCH);
+MODULE_FIRMWARE(AIC_BT_FW_TABLE);
diff --git a/drivers/bluetooth/btaic_uart.c b/drivers/bluetooth/btaic_uart.c
new file mode 100644
index 000000000000..4a38fccfe7af
--- /dev/null
+++ b/drivers/bluetooth/btaic_uart.c
@@ -0,0 +1,331 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief AIC8800D80 Bluetooth UART transport
+ *
+ ******************************************************************************
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/property.h>
+#include <linux/serdev.h>
+#include <linux/skbuff.h>
+#include <linux/workqueue.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+#include "btaic.h"
+#include "hci_uart.h"
+
+#define AIC_BT_UART_DEFAULT_SPEED	1500000
+#define AIC_BT_BOOT_TIMEOUT_MS		10000
+
+#define AIC_BT_TX_ACTIVE		0
+#define AIC_BT_TX_WAKEUP		1
+
+struct aic_bt_uart {
+	struct serdev_device *serdev;
+	struct hci_dev *hdev;
+	struct hci_uart hu;
+	struct aic_bt_boot *boot;
+
+	struct sk_buff *rx_skb;
+	struct sk_buff_head txq;
+	struct work_struct tx_work;
+	unsigned long tx_state;
+
+	u32 speed;
+};
+
+static const struct h4_recv_pkt aic_bt_recv_pkts[] = {
+	{ H4_RECV_ACL,   .recv = hci_recv_frame },
+	{ H4_RECV_SCO,   .recv = hci_recv_frame },
+	{ H4_RECV_EVENT, .recv = hci_recv_frame },
+	{ H4_RECV_ISO,   .recv = hci_recv_frame },
+};
+
+static void aic_bt_tx_work(struct work_struct *work)
+{
+	struct aic_bt_uart *uart = container_of(work, struct aic_bt_uart,
+						 tx_work);
+
+	for (;;) {
+		struct sk_buff *skb;
+
+		clear_bit(AIC_BT_TX_WAKEUP, &uart->tx_state);
+
+		while ((skb = skb_dequeue(&uart->txq))) {
+			int len;
+
+			len = serdev_device_write_buf(uart->serdev, skb->data,
+						      skb->len);
+			if (len <= 0) {
+				skb_queue_head(&uart->txq, skb);
+				if (len < 0)
+					bt_dev_err(uart->hdev,
+						   "UART transmit failed (%d)", len);
+				break;
+			}
+
+			uart->hdev->stat.byte_tx += len;
+			skb_pull(skb, len);
+			if (skb->len) {
+				skb_queue_head(&uart->txq, skb);
+				break;
+			}
+
+			switch (hci_skb_pkt_type(skb)) {
+			case HCI_COMMAND_PKT:
+				uart->hdev->stat.cmd_tx++;
+				break;
+			case HCI_ACLDATA_PKT:
+				uart->hdev->stat.acl_tx++;
+				break;
+			case HCI_SCODATA_PKT:
+				uart->hdev->stat.sco_tx++;
+				break;
+			}
+
+			kfree_skb(skb);
+		}
+
+		if (!test_bit(AIC_BT_TX_WAKEUP, &uart->tx_state))
+			break;
+	}
+
+	clear_bit(AIC_BT_TX_ACTIVE, &uart->tx_state);
+}
+
+static void aic_bt_tx_wakeup(struct aic_bt_uart *uart)
+{
+	if (test_and_set_bit(AIC_BT_TX_ACTIVE, &uart->tx_state))
+		set_bit(AIC_BT_TX_WAKEUP, &uart->tx_state);
+
+	schedule_work(&uart->tx_work);
+}
+
+static size_t aic_bt_receive_buf(struct serdev_device *serdev,
+				 const u8 *data, size_t count)
+{
+	struct aic_bt_uart *uart = serdev_device_get_drvdata(serdev);
+
+	uart->rx_skb = h4_recv_buf(&uart->hu, uart->rx_skb, data, count,
+				   aic_bt_recv_pkts,
+				   ARRAY_SIZE(aic_bt_recv_pkts));
+	if (IS_ERR(uart->rx_skb)) {
+		bt_dev_err(uart->hdev, "Frame reassembly failed (%ld)",
+			   PTR_ERR(uart->rx_skb));
+		uart->rx_skb = NULL;
+	}
+
+	uart->hdev->stat.byte_rx += count;
+	return count;
+}
+
+static void aic_bt_write_wakeup(struct serdev_device *serdev)
+{
+	struct aic_bt_uart *uart = serdev_device_get_drvdata(serdev);
+
+	aic_bt_tx_wakeup(uart);
+}
+
+static const struct serdev_device_ops aic_bt_serdev_ops = {
+	.receive_buf = aic_bt_receive_buf,
+	.write_wakeup = aic_bt_write_wakeup,
+};
+
+static int aic_bt_open(struct hci_dev *hdev)
+{
+	struct aic_bt_uart *uart = hci_get_drvdata(hdev);
+	unsigned int actual_speed;
+	int err;
+
+	err = aic_bt_boot_wait(uart->boot,
+			       msecs_to_jiffies(AIC_BT_BOOT_TIMEOUT_MS));
+	if (err) {
+		bt_dev_err(hdev, "Bluetooth firmware is not ready (%d)", err);
+		return err;
+	}
+
+	err = serdev_device_open(uart->serdev);
+	if (err)
+		return err;
+
+	actual_speed = serdev_device_set_baudrate(uart->serdev, uart->speed);
+	if (!actual_speed) {
+		serdev_device_close(uart->serdev);
+		return -EIO;
+	}
+
+	serdev_device_set_flow_control(uart->serdev, true);
+	return 0;
+}
+
+static int aic_bt_close(struct hci_dev *hdev)
+{
+	struct aic_bt_uart *uart = hci_get_drvdata(hdev);
+
+	serdev_device_close(uart->serdev);
+	return 0;
+}
+
+static int aic_bt_flush(struct hci_dev *hdev)
+{
+	struct aic_bt_uart *uart = hci_get_drvdata(hdev);
+
+	serdev_device_write_flush(uart->serdev);
+	cancel_work_sync(&uart->tx_work);
+	skb_queue_purge(&uart->txq);
+	kfree_skb(uart->rx_skb);
+	uart->rx_skb = NULL;
+	clear_bit(AIC_BT_TX_ACTIVE, &uart->tx_state);
+	clear_bit(AIC_BT_TX_WAKEUP, &uart->tx_state);
+
+	return 0;
+}
+
+static int aic_bt_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
+{
+	struct aic_bt_uart *uart = hci_get_drvdata(hdev);
+	u8 pkt_type = hci_skb_pkt_type(skb);
+	int err;
+
+	err = skb_cow_head(skb, 1);
+	if (err)
+		return err;
+
+	memcpy(skb_push(skb, 1), &pkt_type, sizeof(pkt_type));
+	skb_queue_tail(&uart->txq, skb);
+	aic_bt_tx_wakeup(uart);
+
+	return 0;
+}
+
+static void aic_bt_boot_put_action(void *data)
+{
+	aic_bt_boot_put(data);
+}
+
+static int aic_bt_find_boot_provider(struct device *dev,
+				     struct aic_bt_uart *uart)
+{
+	struct fwnode_handle *fwnode = NULL;
+	int err;
+
+	if (device_property_present(dev, "aic,firmware-sdio")) {
+		fwnode = fwnode_find_reference(dev_fwnode(dev),
+					       "aic,firmware-sdio", 0);
+		if (IS_ERR(fwnode))
+			return PTR_ERR(fwnode);
+	}
+
+	uart->boot = aic_bt_boot_get(fwnode);
+	fwnode_handle_put(fwnode);
+	if (IS_ERR(uart->boot))
+		return dev_err_probe(dev, PTR_ERR(uart->boot),
+				     "failed to find Bluetooth SDIO firmware loader\n");
+
+	err = devm_add_action_or_reset(dev, aic_bt_boot_put_action,
+				       uart->boot);
+	if (err)
+		return err;
+
+	if (!device_link_add(dev, aic_bt_boot_device(uart->boot),
+			     DL_FLAG_AUTOREMOVE_CONSUMER))
+		return -EINVAL;
+
+	return 0;
+}
+
+static int aic_bt_uart_probe(struct serdev_device *serdev)
+{
+	struct device *dev = &serdev->dev;
+	struct aic_bt_uart *uart;
+	struct hci_dev *hdev;
+	int err;
+
+	uart = devm_kzalloc(dev, sizeof(*uart), GFP_KERNEL);
+	if (!uart)
+		return -ENOMEM;
+
+	uart->serdev = serdev;
+	uart->speed = AIC_BT_UART_DEFAULT_SPEED;
+	device_property_read_u32(dev, "max-speed", &uart->speed);
+	serdev_device_set_drvdata(serdev, uart);
+	serdev_device_set_client_ops(serdev, &aic_bt_serdev_ops);
+
+	err = aic_bt_find_boot_provider(dev, uart);
+	if (err)
+		return err;
+
+	INIT_WORK(&uart->tx_work, aic_bt_tx_work);
+	skb_queue_head_init(&uart->txq);
+
+	hdev = hci_alloc_dev();
+	if (!hdev)
+		return -ENOMEM;
+
+	uart->hdev = hdev;
+	uart->hu.hdev = hdev;
+	hdev->bus = HCI_UART;
+	hci_set_drvdata(hdev, uart);
+	SET_HCIDEV_DEV(hdev, dev);
+
+	hdev->open = aic_bt_open;
+	hdev->close = aic_bt_close;
+	hdev->flush = aic_bt_flush;
+	hdev->send = aic_bt_send_frame;
+
+	err = hci_register_dev(hdev);
+	if (err) {
+		hci_free_dev(hdev);
+		return err;
+	}
+
+	return 0;
+}
+
+static void aic_bt_uart_remove(struct serdev_device *serdev)
+{
+	struct aic_bt_uart *uart = serdev_device_get_drvdata(serdev);
+
+	hci_unregister_dev(uart->hdev);
+	cancel_work_sync(&uart->tx_work);
+	skb_queue_purge(&uart->txq);
+	kfree_skb(uart->rx_skb);
+	uart->rx_skb = NULL;
+	hci_free_dev(uart->hdev);
+}
+
+static const struct of_device_id aic_bt_uart_of_match[] = {
+	{ .compatible = "aic,aic8800d80-bt" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, aic_bt_uart_of_match);
+
+static struct serdev_device_driver aic_bt_uart_driver = {
+	.probe = aic_bt_uart_probe,
+	.remove = aic_bt_uart_remove,
+	.driver = {
+		.name = "bt_aic_uart",
+		.of_match_table = aic_bt_uart_of_match,
+	},
+};
+
+int aic_bt_uart_register(void)
+{
+	return serdev_device_driver_register(&aic_bt_uart_driver);
+}
+
+void aic_bt_uart_unregister(void)
+{
+	serdev_device_driver_unregister(&aic_bt_uart_driver);
+}
-- 
2.34.1

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH bluetooth-next 3/3] Bluetooth: btaic: Add AIC8800D80 SDIO loader and UART transport
  2026-09-16  8:35 [RFC PATCH bluetooth-next 0/3] Bluetooth: Add AIC8800D80 SDIO firmware loader and UART HCI Yanli Yang
@ 2026-09-16  8:35 ` Yanli Yang
  2026-09-17  2:43   ` sashiko-bot
  0 siblings, 1 reply; 18+ messages in thread
From: Yanli Yang @ 2026-09-16  8:35 UTC (permalink / raw)
  To: linux-bluetooth
  Cc: marcel, luiz.dentz, devicetree, robh, krzk+dt, conor+dt,
	linux-kernel, zhirunliu, dijiaxu, chunqiuliu, liheng.wei,
	yanli.yang

Add a driver for the AIC8800D80 combination chip, using SDIO function 1
for Bluetooth firmware loading and a serdev UART for H4 HCI traffic.

Load the ADID image, patch image and patch table through
request_firmware(), validate the patch-table layout, and transfer firmware
using the vendor SDIO command protocol. Recognize the U02 and U03 chip
revisions.

Coordinate the SDIO firmware provider and UART consumer through a
reference-counted boot state and a device link. Wait for firmware readiness
before opening the UART, and use hardware flow control at 1500000 baud.

Add CONFIG_BT_AIC, build integration and a MAINTAINERS entry.

The vendor has confirmed SDIO firmware loading only. UART HCI operation
and other Bluetooth functionality have not yet been validated. Submit
this implementation as RFC for review before further functional testing.

Signed-off-by: Zhirun Liu <zhirunliu@aicsemi.com>
Signed-off-by: Dijia Xu <dijiaxu@aicsemi.com>
Signed-off-by: Chunqiu Liu <chunqiuliu@aicsemi.com>
Signed-off-by: Liheng Wei <liheng.wei@bedmex.com>
Signed-off-by: Yanli Yang <yanli.yang@bedmex.com>
---
 MAINTAINERS                    |  11 +
 drivers/bluetooth/Kconfig      |  16 +
 drivers/bluetooth/Makefile     |   3 +
 drivers/bluetooth/btaic.h      |  26 +
 drivers/bluetooth/btaic_core.c | 180 +++++++
 drivers/bluetooth/btaic_sdio.c | 886 +++++++++++++++++++++++++++++++++
 drivers/bluetooth/btaic_uart.c | 331 ++++++++++++
 7 files changed, 1453 insertions(+)
 create mode 100644 drivers/bluetooth/btaic.h
 create mode 100644 drivers/bluetooth/btaic_core.c
 create mode 100644 drivers/bluetooth/btaic_sdio.c
 create mode 100644 drivers/bluetooth/btaic_uart.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 891c064a881b..83b618662d86 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -730,6 +730,17 @@ S:	Maintained
 F:	drivers/scsi/aha152x*
 F:	drivers/scsi/pcmcia/aha152x*
 
+AIC BLUETOOTH DRIVER
+M:	Zhirun Liu <zhirunliu@aicsemi.com>
+M:	Dijia Xu <dijiaxu@aicsemi.com>
+M:	Chunqiu Liu <chunqiuliu@aicsemi.com>
+M:	Liheng Wei <liheng.wei@bedmex.com>
+M:	Yanli Yang <yanli.yang@bedmex.com>
+L:	linux-bluetooth@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml
+F:	drivers/bluetooth/btaic*
+
 AIC7XXX / AIC79XX SCSI DRIVER
 M:	Hannes Reinecke <hare@suse.com>
 L:	linux-scsi@vger.kernel.org
diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
index 4e8c24d757e9..c8f66742f98b 100644
--- a/drivers/bluetooth/Kconfig
+++ b/drivers/bluetooth/Kconfig
@@ -472,4 +472,20 @@ config BT_INTEL_PCIE
 
 	  Say Y here to compiler support for Intel Bluetooth PCIe device into
 	  the kernel or say M to compile it as module (btintel_pcie)
+
+config BT_AIC
+	tristate "AIC8800D80 Bluetooth support"
+	depends on MMC
+	depends on SERIAL_DEV_BUS
+	depends on BT_HCIUART
+	select BT_HCIUART_H4
+	select FW_LOADER
+	help
+	  Bluetooth support for the AIC8800D80 combination chip, using an
+	  SDIO function for firmware loading and a UART interface for the
+	  HCI transport.
+
+	  Say Y here to compile support into the kernel or M to build it as
+	  a module named btaic.
+
 endmenu
diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
index e6b1c1180d1d..d9569d6563f1 100644
--- a/drivers/bluetooth/Makefile
+++ b/drivers/bluetooth/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_BT_MRVL)		+= btmrvl.o
 obj-$(CONFIG_BT_MRVL_SDIO)	+= btmrvl_sdio.o
 obj-$(CONFIG_BT_MTKSDIO)	+= btmtksdio.o
 obj-$(CONFIG_BT_MTKUART)	+= btmtkuart.o
+obj-$(CONFIG_BT_AIC)		+= btaic.o
 obj-$(CONFIG_BT_QCOMSMD)	+= btqcomsmd.o
 obj-$(CONFIG_BT_BCM)		+= btbcm.o
 obj-$(CONFIG_BT_RTL)		+= btrtl.o
@@ -51,4 +52,6 @@ hci_uart-$(CONFIG_BT_HCIUART_MRVL)	+= hci_mrvl.o
 hci_uart-$(CONFIG_BT_HCIUART_AML)	+= hci_aml.o
 hci_uart-objs				:= $(hci_uart-y)
 
+btaic-y				:= btaic_core.o btaic_sdio.o btaic_uart.o
+
 CONTEXT_ANALYSIS := y
diff --git a/drivers/bluetooth/btaic.h b/drivers/bluetooth/btaic.h
new file mode 100644
index 000000000000..dc631b495697
--- /dev/null
+++ b/drivers/bluetooth/btaic.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __BTAIC_H
+#define __BTAIC_H
+
+#include <linux/types.h>
+
+struct device;
+struct fwnode_handle;
+
+struct aic_bt_boot;
+
+struct aic_bt_boot *aic_bt_boot_register(struct device *dev);
+void aic_bt_boot_ready(struct aic_bt_boot *boot, int status);
+void aic_bt_boot_unregister(struct aic_bt_boot *boot);
+
+struct aic_bt_boot *aic_bt_boot_get(const struct fwnode_handle *fwnode);
+void aic_bt_boot_put(struct aic_bt_boot *boot);
+int aic_bt_boot_wait(struct aic_bt_boot *boot, unsigned long timeout);
+struct device *aic_bt_boot_device(struct aic_bt_boot *boot);
+
+int aic_bt_sdio_register(void);
+void aic_bt_sdio_unregister(void);
+int aic_bt_uart_register(void);
+void aic_bt_uart_unregister(void);
+
+#endif
diff --git a/drivers/bluetooth/btaic_core.c b/drivers/bluetooth/btaic_core.c
new file mode 100644
index 000000000000..35644b9b1209
--- /dev/null
+++ b/drivers/bluetooth/btaic_core.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief AIC8800D80 Bluetooth driver core
+ *
+ ******************************************************************************
+ */
+
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+
+#include "btaic.h"
+
+struct aic_bt_boot {
+	struct kref ref;
+	struct list_head node;
+	struct completion ready;
+	struct device *dev;
+	int status;
+	bool present;
+};
+
+static DEFINE_MUTEX(aic_bt_boot_lock);
+static LIST_HEAD(aic_bt_boot_list);
+
+static void aic_bt_boot_release(struct kref *ref)
+{
+	struct aic_bt_boot *boot = container_of(ref, struct aic_bt_boot, ref);
+
+	put_device(boot->dev);
+	kfree(boot);
+}
+
+struct aic_bt_boot *aic_bt_boot_register(struct device *dev)
+{
+	struct aic_bt_boot *boot;
+
+	boot = kzalloc_obj(*boot);
+	if (!boot)
+		return ERR_PTR(-ENOMEM);
+
+	kref_init(&boot->ref);
+	INIT_LIST_HEAD(&boot->node);
+	init_completion(&boot->ready);
+	boot->dev = get_device(dev);
+	boot->status = -EINPROGRESS;
+	boot->present = true;
+
+	mutex_lock(&aic_bt_boot_lock);
+	list_add_tail(&boot->node, &aic_bt_boot_list);
+	mutex_unlock(&aic_bt_boot_lock);
+
+	return boot;
+}
+
+void aic_bt_boot_ready(struct aic_bt_boot *boot, int status)
+{
+	mutex_lock(&aic_bt_boot_lock);
+	if (boot->present)
+		boot->status = status;
+	mutex_unlock(&aic_bt_boot_lock);
+
+	complete_all(&boot->ready);
+}
+
+void aic_bt_boot_unregister(struct aic_bt_boot *boot)
+{
+	if (!boot)
+		return;
+
+	mutex_lock(&aic_bt_boot_lock);
+	if (boot->present) {
+		list_del_init(&boot->node);
+		boot->present = false;
+		boot->status = -ENODEV;
+	}
+	mutex_unlock(&aic_bt_boot_lock);
+
+	complete_all(&boot->ready);
+	kref_put(&boot->ref, aic_bt_boot_release);
+}
+
+struct aic_bt_boot *aic_bt_boot_get(const struct fwnode_handle *fwnode)
+{
+	struct aic_bt_boot *boot;
+	struct aic_bt_boot *found = NULL;
+	unsigned int count = 0;
+
+	mutex_lock(&aic_bt_boot_lock);
+	list_for_each_entry(boot, &aic_bt_boot_list, node) {
+		if (!boot->present)
+			continue;
+
+		if (fwnode) {
+			if (dev_fwnode(boot->dev) == fwnode) {
+				found = boot;
+				break;
+			}
+			continue;
+		}
+
+		found = boot;
+		count++;
+	}
+
+	if (found && (fwnode || count == 1))
+		kref_get(&found->ref);
+	else if (count > 1)
+		found = ERR_PTR(-EINVAL);
+	else
+		found = ERR_PTR(-EPROBE_DEFER);
+	mutex_unlock(&aic_bt_boot_lock);
+
+	return found;
+}
+
+void aic_bt_boot_put(struct aic_bt_boot *boot)
+{
+	if (!IS_ERR_OR_NULL(boot))
+		kref_put(&boot->ref, aic_bt_boot_release);
+}
+
+int aic_bt_boot_wait(struct aic_bt_boot *boot, unsigned long timeout)
+{
+	int status;
+
+	if (!wait_for_completion_timeout(&boot->ready, timeout))
+		return -ETIMEDOUT;
+
+	mutex_lock(&aic_bt_boot_lock);
+	status = boot->present ? boot->status : -ENODEV;
+	mutex_unlock(&aic_bt_boot_lock);
+
+	return status;
+}
+
+struct device *aic_bt_boot_device(struct aic_bt_boot *boot)
+{
+	return boot->dev;
+}
+
+static int __init aic_bt_init(void)
+{
+	int err;
+
+	err = aic_bt_sdio_register();
+	if (err)
+		return err;
+
+	err = aic_bt_uart_register();
+	if (err) {
+		aic_bt_sdio_unregister();
+		return err;
+	}
+
+	return 0;
+}
+
+static void __exit aic_bt_exit(void)
+{
+	aic_bt_uart_unregister();
+	aic_bt_sdio_unregister();
+}
+
+module_init(aic_bt_init);
+module_exit(aic_bt_exit);
+
+MODULE_AUTHOR("AIC Semiconductor");
+MODULE_DESCRIPTION("AIC8800D80 Bluetooth driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/bluetooth/btaic_sdio.c b/drivers/bluetooth/btaic_sdio.c
new file mode 100644
index 000000000000..87b6b1c03a6e
--- /dev/null
+++ b/drivers/bluetooth/btaic_sdio.c
@@ -0,0 +1,886 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief AIC8800D80 Bluetooth SDIO firmware loader
+ *
+ ******************************************************************************
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/minmax.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/sdio_ids.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/overflow.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/unaligned.h>
+
+#include "btaic.h"
+
+#define AIC_SDIO_VENDOR_ID			0xc8a1
+#define AIC_SDIO_DEVICE_ID_8800D80		0x0082
+
+#define AIC_SDIO_BLOCK_SIZE			512
+#define AIC_SDIO_BUFFER_SIZE			1536
+#define AIC_SDIO_TX_BUFFER_SIZE			1536
+#define AIC_SDIO_RX_MAX_SIZE			(127 * AIC_SDIO_BLOCK_SIZE)
+
+#define AIC_SDIO_INTR_ENABLE			0x00
+#define AIC_SDIO_INTR_PENDING			0x01
+#define AIC_SDIO_FLOW_CTRL_Q1			0x03
+#define AIC_SDIO_MISC_INT_STATUS		0x04
+#define AIC_SDIO_BYTEMODE_LEN			0x05
+#define AIC_SDIO_BYTEMODE_ENABLE		0x07
+#define AIC_SDIO_RD_FIFO			0x0f
+#define AIC_SDIO_WR_FIFO			0x10
+
+#define AIC_SDIO_OTHER_INTERRUPT		BIT(7)
+#define AIC_SDIO_BYTE_MODE_BLOCKS		120
+#define AIC_SDIO_FLOW_RETRIES			50
+#define AIC_SDIO_FRAME_TAIL_LEN			4
+
+#define AIC_CMD_TIMEOUT_MS			6000
+#define AIC_CMD_TYPE				0x11
+#define AIC_TASK_DBG				1
+#define AIC_DRIVER_TASK				100
+#define AIC_FIRST_MSG(task)			((u16)(task) << 10)
+
+#define AIC_DBG_MEM_READ_REQ			(AIC_FIRST_MSG(AIC_TASK_DBG) + 0)
+#define AIC_DBG_MEM_READ_CFM			(AIC_FIRST_MSG(AIC_TASK_DBG) + 1)
+#define AIC_DBG_MEM_WRITE_REQ			(AIC_FIRST_MSG(AIC_TASK_DBG) + 2)
+#define AIC_DBG_MEM_WRITE_CFM			(AIC_FIRST_MSG(AIC_TASK_DBG) + 3)
+#define AIC_DBG_MEM_BLOCK_WRITE_REQ		(AIC_FIRST_MSG(AIC_TASK_DBG) + 11)
+#define AIC_DBG_MEM_BLOCK_WRITE_CFM		(AIC_FIRST_MSG(AIC_TASK_DBG) + 12)
+
+#define AIC_BT_CHIP_ID_ADDR			0x40500000
+#define AIC_BT_CHIP_REV_U02			3
+#define AIC_BT_CHIP_REV_U03			7
+
+#define AIC_BT_FW_ADID				"aic/aic8800d80/fw_adid_8800d80_u02.bin"
+#define AIC_BT_FW_PATCH				"aic/aic8800d80/fw_patch_8800d80_u02.bin"
+#define AIC_BT_FW_TABLE				"aic/aic8800d80/fw_patch_table_8800d80_u02.bin"
+
+#define AIC_BT_PATCH_TAG			"AICBT_PT_TAG"
+#define AIC_BT_PATCH_TAG_SIZE			16
+#define AIC_BT_PATCH_RECORD_HEADER_SIZE		24
+#define AIC_BT_PATCH_PAIR_SIZE			8
+#define AIC_BT_PATCH_BLOCK_SIZE			1024
+
+#define AIC_BT_MODE_ONLY_COANT			5
+#define AIC_BT_PORT_UART			2
+#define AIC_BT_UART_BAUD			1500000
+#define AIC_BT_UART_FLOW_CTRL			1
+#define AIC_BT_LOW_POWER_ENABLE			1
+#define AIC_BT_TX_POWER_LEVEL			0x00006f2f
+
+enum aic_bt_patch_type {
+	AIC_BT_PATCH_INFO,
+	AIC_BT_PATCH_TRAP,
+	AIC_BT_PATCH_B4,
+	AIC_BT_PATCH_MODE,
+	AIC_BT_PATCH_POWER_ON,
+	AIC_BT_PATCH_AF,
+	AIC_BT_PATCH_VERSION,
+};
+
+struct aic_bt_e2a_header {
+	__le16 id;
+	__le16 dest_id;
+	__le16 src_id;
+	__le16 param_len;
+	__le32 pattern;
+	u8 param[];
+} __packed;
+
+struct aic_bt_mem_read_cfm {
+	__le32 address;
+	__le32 value;
+} __packed;
+
+struct aic_bt_sdio {
+	struct sdio_func *func;
+	struct aic_bt_boot *boot;
+
+	/* Serializes commands because the firmware accepts one at a time. */
+	struct mutex command_mutex;
+	/* Protects response state shared with the SDIO IRQ handler. */
+	spinlock_t response_lock;
+	struct completion command_done;
+	u16 expected_response;
+	void *response;
+	size_t response_size;
+	int command_result;
+	bool waiting_response;
+
+	u8 *tx_buf;
+	bool function_enabled;
+	bool irq_claimed;
+};
+
+static u8 aic_bt_crc8(const u8 *buffer, size_t len)
+{
+	u8 crc = 0;
+	size_t byte;
+	int bit;
+
+	for (byte = 0; byte < len; byte++) {
+		for (bit = 0x80; bit; bit >>= 1) {
+			if (crc & 0x80)
+				crc = (crc << 1) ^ 0x07;
+			else
+				crc <<= 1;
+
+			if (buffer[byte] & bit)
+				crc ^= 0x07;
+		}
+	}
+
+	return crc;
+}
+
+static void aic_bt_complete_response(struct aic_bt_sdio *btdev,
+				     const struct aic_bt_e2a_header *message,
+				     size_t message_len)
+{
+	unsigned long flags;
+	size_t param_len;
+	u16 id;
+	bool complete_command = false;
+
+	if (message_len < sizeof(*message))
+		return;
+
+	id = get_unaligned_le16(&message->id);
+	param_len = get_unaligned_le16(&message->param_len);
+	if (param_len > message_len - sizeof(*message))
+		return;
+
+	spin_lock_irqsave(&btdev->response_lock, flags);
+	if (!btdev->waiting_response || id != btdev->expected_response)
+		goto unlock;
+
+	if (btdev->response && param_len < btdev->response_size) {
+		btdev->command_result = -EMSGSIZE;
+	} else {
+		if (btdev->response)
+			memcpy(btdev->response, message->param,
+			       btdev->response_size);
+		btdev->command_result = 0;
+	}
+
+	btdev->waiting_response = false;
+	complete_command = true;
+
+unlock:
+	spin_unlock_irqrestore(&btdev->response_lock, flags);
+
+	if (complete_command)
+		complete(&btdev->command_done);
+}
+
+static void aic_bt_parse_rx(struct aic_bt_sdio *btdev, const u8 *data,
+			    size_t data_len)
+{
+	size_t offset = 0;
+
+	while (data_len - offset >= 4) {
+		const struct aic_bt_e2a_header *message;
+		size_t frame_len;
+		size_t frame_size;
+
+		frame_len = get_unaligned_le16(data + offset);
+		if (!frame_len)
+			break;
+
+		if (check_add_overflow(frame_len, (size_t)4, &frame_size) ||
+		    frame_size > data_len - offset)
+			break;
+
+		if ((data[offset + 2] & 0x7f) == AIC_CMD_TYPE) {
+			message = (const void *)(data + offset + 4);
+			aic_bt_complete_response(btdev, message, frame_len);
+		}
+
+		frame_size = roundup(frame_len, 4) + 4;
+		if (frame_size > data_len - offset)
+			break;
+		offset += frame_size;
+	}
+}
+
+static void aic_bt_sdio_irq(struct sdio_func *func)
+{
+	struct aic_bt_sdio *btdev = sdio_get_drvdata(func);
+	u8 *data;
+	u8 blocks;
+	u8 status;
+	size_t data_len;
+	int err;
+
+	status = sdio_readb(func, AIC_SDIO_MISC_INT_STATUS, &err);
+	if (err) {
+		dev_err_ratelimited(&func->dev,
+				    "failed to read interrupt status: %d\n", err);
+		return;
+	}
+
+	if (status & AIC_SDIO_OTHER_INTERRUPT) {
+		u8 pending;
+
+		pending = sdio_readb(func, AIC_SDIO_INTR_PENDING, &err);
+		if (!err) {
+			pending &= ~BIT(0);
+			sdio_writeb(func, pending, AIC_SDIO_INTR_PENDING, &err);
+		}
+		if (err)
+			dev_err_ratelimited(&func->dev,
+					    "failed to clear soft interrupt: %d\n",
+					    err);
+	}
+
+	blocks = status & 0x7f;
+	if (!blocks)
+		return;
+
+	if (blocks == AIC_SDIO_BYTE_MODE_BLOCKS) {
+		u8 words;
+
+		words = sdio_readb(func, AIC_SDIO_BYTEMODE_LEN, &err);
+		if (err)
+			return;
+		data_len = (size_t)words * 4;
+	} else {
+		data_len = (size_t)blocks * AIC_SDIO_BLOCK_SIZE;
+	}
+
+	if (!data_len || data_len > AIC_SDIO_RX_MAX_SIZE) {
+		dev_err_ratelimited(&func->dev,
+				    "invalid SDIO response length %zu\n", data_len);
+		return;
+	}
+
+	data = kmalloc(data_len, GFP_KERNEL);
+	if (!data)
+		return;
+
+	err = sdio_readsb(func, data, AIC_SDIO_RD_FIFO, data_len);
+	if (err)
+		dev_err_ratelimited(&func->dev,
+				    "failed to read response: %d\n", err);
+	else
+		aic_bt_parse_rx(btdev, data, data_len);
+
+	kfree(data);
+}
+
+static int aic_bt_wait_for_credits(struct aic_bt_sdio *btdev, size_t tx_len)
+{
+	unsigned int retry;
+	int err;
+
+	for (retry = 0; retry < AIC_SDIO_FLOW_RETRIES; retry++) {
+		u8 credits;
+
+		credits = sdio_readb(btdev->func, AIC_SDIO_FLOW_CTRL_Q1, &err);
+		if (err)
+			return err;
+
+		if (credits && tx_len < (size_t)credits * AIC_SDIO_BUFFER_SIZE)
+			return 0;
+
+		if (retry < 30)
+			usleep_range(30, 50);
+		else if (retry < 40)
+			usleep_range(1000, 1500);
+		else
+			usleep_range(10000, 12000);
+	}
+
+	return -ETIMEDOUT;
+}
+
+static int aic_bt_command(struct aic_bt_sdio *btdev, u16 request_id,
+			  const void *param, size_t param_len, u16 response_id,
+			  void *response, size_t response_size)
+{
+	unsigned long flags;
+	size_t frame_len;
+	size_t message_len;
+	size_t tx_len;
+	long timeout;
+	int err;
+
+	if (param_len > U16_MAX)
+		return -EMSGSIZE;
+
+	message_len = 8 + param_len;
+	frame_len = 8 + message_len;
+	if (frame_len > AIC_SDIO_TX_BUFFER_SIZE)
+		return -EMSGSIZE;
+
+	if (IS_ALIGNED(frame_len, AIC_SDIO_BLOCK_SIZE))
+		tx_len = frame_len;
+	else
+		tx_len = roundup(frame_len + AIC_SDIO_FRAME_TAIL_LEN,
+				 AIC_SDIO_BLOCK_SIZE);
+
+	if (tx_len > AIC_SDIO_TX_BUFFER_SIZE)
+		return -EMSGSIZE;
+
+	mutex_lock(&btdev->command_mutex);
+
+	memset(btdev->tx_buf, 0, tx_len);
+	put_unaligned_le16(message_len + 4, btdev->tx_buf);
+	btdev->tx_buf[2] = AIC_CMD_TYPE;
+	btdev->tx_buf[3] = aic_bt_crc8(btdev->tx_buf, 3);
+
+	put_unaligned_le16(request_id, btdev->tx_buf + 8);
+	put_unaligned_le16(AIC_TASK_DBG, btdev->tx_buf + 10);
+	put_unaligned_le16(AIC_DRIVER_TASK, btdev->tx_buf + 12);
+	put_unaligned_le16(param_len, btdev->tx_buf + 14);
+	if (param_len)
+		memcpy(btdev->tx_buf + 16, param, param_len);
+
+	reinit_completion(&btdev->command_done);
+	spin_lock_irqsave(&btdev->response_lock, flags);
+	btdev->expected_response = response_id;
+	btdev->response = response;
+	btdev->response_size = response_size;
+	btdev->command_result = -EINPROGRESS;
+	btdev->waiting_response = true;
+	spin_unlock_irqrestore(&btdev->response_lock, flags);
+
+	sdio_claim_host(btdev->func);
+	err = aic_bt_wait_for_credits(btdev, tx_len);
+	if (!err)
+		err = sdio_writesb(btdev->func, AIC_SDIO_WR_FIFO,
+				   btdev->tx_buf, tx_len);
+	sdio_release_host(btdev->func);
+	if (err)
+		goto clear_response;
+
+	timeout = wait_for_completion_timeout(&btdev->command_done,
+					      msecs_to_jiffies(AIC_CMD_TIMEOUT_MS));
+	if (!timeout) {
+		dev_err(&btdev->func->dev,
+			"command 0x%04x timed out waiting for 0x%04x\n",
+			request_id, response_id);
+		err = -ETIMEDOUT;
+		goto clear_response;
+	}
+
+	spin_lock_irqsave(&btdev->response_lock, flags);
+	err = btdev->command_result;
+	btdev->response = NULL;
+	btdev->response_size = 0;
+	spin_unlock_irqrestore(&btdev->response_lock, flags);
+	mutex_unlock(&btdev->command_mutex);
+
+	return err;
+
+clear_response:
+	spin_lock_irqsave(&btdev->response_lock, flags);
+	btdev->waiting_response = false;
+	btdev->response = NULL;
+	btdev->response_size = 0;
+	spin_unlock_irqrestore(&btdev->response_lock, flags);
+	mutex_unlock(&btdev->command_mutex);
+
+	return err;
+}
+
+static int aic_bt_mem_read(struct aic_bt_sdio *btdev, u32 address, u32 *value)
+{
+	struct aic_bt_mem_read_cfm cfm;
+	__le32 request = cpu_to_le32(address);
+	int err;
+
+	err = aic_bt_command(btdev, AIC_DBG_MEM_READ_REQ,
+			     &request, sizeof(request), AIC_DBG_MEM_READ_CFM,
+			     &cfm, sizeof(cfm));
+	if (!err)
+		*value = le32_to_cpu(cfm.value);
+
+	return err;
+}
+
+static int aic_bt_mem_write(struct aic_bt_sdio *btdev, u32 address, u32 value)
+{
+	__le32 request[2] = {
+		cpu_to_le32(address),
+		cpu_to_le32(value),
+	};
+
+	return aic_bt_command(btdev, AIC_DBG_MEM_WRITE_REQ,
+			      request, sizeof(request), AIC_DBG_MEM_WRITE_CFM,
+			      NULL, 0);
+}
+
+static int aic_bt_mem_block_write(struct aic_bt_sdio *btdev, u32 address,
+				  const u8 *data, size_t data_len)
+{
+	__le32 status;
+	u8 *request;
+	int err;
+
+	if (data_len > AIC_BT_PATCH_BLOCK_SIZE)
+		return -EINVAL;
+
+	request = kzalloc(8 + AIC_BT_PATCH_BLOCK_SIZE, GFP_KERNEL);
+	if (!request)
+		return -ENOMEM;
+
+	put_unaligned_le32(address, request);
+	put_unaligned_le32(data_len, request + 4);
+	memcpy(request + 8, data, data_len);
+
+	err = aic_bt_command(btdev, AIC_DBG_MEM_BLOCK_WRITE_REQ,
+			     request, 8 + AIC_BT_PATCH_BLOCK_SIZE,
+			     AIC_DBG_MEM_BLOCK_WRITE_CFM,
+			     &status, sizeof(status));
+	kfree(request);
+	if (err)
+		return err;
+
+	if (le32_to_cpu(status)) {
+		dev_err(&btdev->func->dev,
+			"firmware rejected block write at 0x%08x\n", address);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int aic_bt_upload_firmware(struct aic_bt_sdio *btdev, const char *name,
+				  u32 address)
+{
+	const struct firmware *firmware;
+	size_t offset = 0;
+	int err;
+
+	err = request_firmware(&firmware, name, &btdev->func->dev);
+	if (err)
+		return dev_err_probe(&btdev->func->dev, err,
+				     "failed to load %s\n", name);
+
+	if (firmware->size > U32_MAX - address) {
+		err = -EFBIG;
+		goto release;
+	}
+
+	while (offset < firmware->size) {
+		size_t len = min_t(size_t, AIC_BT_PATCH_BLOCK_SIZE,
+				   firmware->size - offset);
+
+		err = aic_bt_mem_block_write(btdev, address + offset,
+					     firmware->data + offset, len);
+		if (err)
+			goto release;
+		offset += len;
+	}
+
+	dev_dbg(&btdev->func->dev, "loaded %s (%zu bytes) at 0x%08x\n",
+		name, firmware->size, address);
+
+release:
+	release_firmware(firmware);
+	return err;
+}
+
+static u32 aic_bt_mode_value(unsigned int pair, u32 firmware_value)
+{
+	switch (pair) {
+	case 0:
+		return 1;
+	case 1:
+		return U32_MAX;
+	case 2:
+		return 0;
+	case 3:
+		return AIC_BT_MODE_ONLY_COANT;
+	case 4:
+		return AIC_BT_PORT_UART;
+	case 5:
+		return AIC_BT_UART_BAUD;
+	case 6:
+		return AIC_BT_UART_FLOW_CTRL;
+	case 7:
+		return AIC_BT_LOW_POWER_ENABLE;
+	case 8:
+		return AIC_BT_TX_POWER_LEVEL;
+	default:
+		return firmware_value;
+	}
+}
+
+static int aic_bt_process_patch_table(struct aic_bt_sdio *btdev,
+				      const struct firmware *firmware,
+				      bool apply, u32 *adid_addr,
+				      u32 *patch_addr)
+{
+	const u8 *data = firmware->data;
+	size_t offset = AIC_BT_PATCH_TAG_SIZE;
+	bool have_record = false;
+	bool have_info = false;
+
+	if (firmware->size < AIC_BT_PATCH_TAG_SIZE ||
+	    memcmp(data, AIC_BT_PATCH_TAG, sizeof(AIC_BT_PATCH_TAG)))
+		return -EBADMSG;
+
+	while (offset < firmware->size) {
+		const u8 *record;
+		const u8 *pairs_data;
+		size_t pairs_size;
+		unsigned int pair;
+		u32 pair_count;
+		u32 type;
+		int err;
+
+		if (firmware->size - offset <
+		    AIC_BT_PATCH_RECORD_HEADER_SIZE)
+			return -EBADMSG;
+
+		record = data + offset;
+		type = get_unaligned_le32(record + 16);
+		pair_count = get_unaligned_le32(record + 20);
+		offset += AIC_BT_PATCH_RECORD_HEADER_SIZE;
+
+		if (type >= 1000) {
+			pairs_size = 0;
+			pair_count = 0;
+		} else if (check_mul_overflow((size_t)pair_count,
+					      (size_t)AIC_BT_PATCH_PAIR_SIZE,
+					      &pairs_size)) {
+			return -EOVERFLOW;
+		}
+
+		if (pairs_size > firmware->size - offset)
+			return -EBADMSG;
+
+		pairs_data = data + offset;
+		have_record = true;
+
+		if (type == AIC_BT_PATCH_INFO) {
+			if (pair_count < 2)
+				return -EBADMSG;
+
+			if (!have_info) {
+				if (adid_addr)
+					*adid_addr = get_unaligned_le32(pairs_data + 4);
+				if (patch_addr)
+					*patch_addr = get_unaligned_le32(pairs_data + 12);
+				have_info = true;
+			}
+		}
+
+		if (!apply)
+			goto next_record;
+
+		if (type == AIC_BT_PATCH_VERSION) {
+			dev_info(&btdev->func->dev, "BT patch version: %.*s\n",
+				 (int)min_t(size_t, pairs_size, 80),
+				 pairs_data);
+			goto next_record;
+		}
+
+		if (type == AIC_BT_PATCH_MODE && pair_count < 9)
+			return -EBADMSG;
+
+		for (pair = 0; pair < pair_count; pair++) {
+			u32 address;
+			u32 value;
+
+			address = get_unaligned_le32(pairs_data +
+						    pair * AIC_BT_PATCH_PAIR_SIZE);
+			value = get_unaligned_le32(pairs_data +
+						  pair * AIC_BT_PATCH_PAIR_SIZE + 4);
+			if (type == AIC_BT_PATCH_MODE)
+				value = aic_bt_mode_value(pair, value);
+
+			err = aic_bt_mem_write(btdev, address, value);
+			if (err)
+				return err;
+		}
+
+		if (type == AIC_BT_PATCH_POWER_ON)
+			usleep_range(50, 100);
+
+next_record:
+		offset += pairs_size;
+	}
+
+	if (!have_record || ((adid_addr || patch_addr) && !have_info))
+		return -EBADMSG;
+
+	return 0;
+}
+
+static int aic_bt_download_firmware(struct aic_bt_sdio *btdev)
+{
+	const struct firmware *table;
+	u32 chip_id;
+	u32 adid_addr;
+	u32 patch_addr;
+	u8 revision;
+	int err;
+
+	err = aic_bt_mem_read(btdev, AIC_BT_CHIP_ID_ADDR, &chip_id);
+	if (err)
+		return err;
+
+	revision = (chip_id >> 16) & 0x3f;
+	if (revision != AIC_BT_CHIP_REV_U02 &&
+	    revision != AIC_BT_CHIP_REV_U03) {
+		dev_err(&btdev->func->dev,
+			"unsupported AIC8800D80 revision %u\n", revision);
+		return -ENODEV;
+	}
+
+	err = request_firmware(&table, AIC_BT_FW_TABLE, &btdev->func->dev);
+	if (err)
+		return dev_err_probe(&btdev->func->dev, err,
+				     "failed to load %s\n", AIC_BT_FW_TABLE);
+
+	err = aic_bt_process_patch_table(btdev, table, false,
+					 &adid_addr, &patch_addr);
+	if (err) {
+		dev_err(&btdev->func->dev, "invalid BT patch table: %d\n", err);
+		goto release_table;
+	}
+
+	err = aic_bt_upload_firmware(btdev, AIC_BT_FW_ADID, adid_addr);
+	if (err)
+		goto release_table;
+
+	err = aic_bt_upload_firmware(btdev, AIC_BT_FW_PATCH, patch_addr);
+	if (err)
+		goto release_table;
+
+	err = aic_bt_process_patch_table(btdev, table, true, NULL, NULL);
+	if (!err)
+		dev_info(&btdev->func->dev,
+			 "AIC8800D80 revision %u Bluetooth firmware ready\n",
+			 revision);
+
+release_table:
+	release_firmware(table);
+	return err;
+}
+
+static int aic_bt_sdio_hw_init(struct aic_bt_sdio *btdev)
+{
+	struct sdio_func *func = btdev->func;
+	struct mmc_host *host = func->card->host;
+	u8 io_control;
+	int err;
+
+	sdio_claim_host(func);
+	func->card->quirks |= MMC_QUIRK_LENIENT_FN0;
+
+	err = sdio_set_block_size(func, AIC_SDIO_BLOCK_SIZE);
+	if (err)
+		goto release_host;
+
+	err = sdio_enable_func(func);
+	if (err)
+		goto release_host;
+	btdev->function_enabled = true;
+
+	sdio_f0_writeb(func, 0x7f, 0xf2, &err);
+	if (err)
+		goto disable_func;
+
+	io_control = host->ios.timing == MMC_TIMING_UHS_DDR50 ? 0x20 : 0x00;
+	io_control |= BIT(6);
+	sdio_f0_writeb(func, io_control, 0xf0, &err);
+	if (err)
+		goto disable_func;
+
+	sdio_f0_writeb(func, 0x00, 0xf8, &err);
+	if (err)
+		goto disable_func;
+
+	sdio_f0_writeb(func, 0x00, 0xf1, &err);
+	if (err)
+		goto disable_func;
+
+	sdio_writeb(func, 1, AIC_SDIO_BYTEMODE_ENABLE, &err);
+	if (err)
+		goto disable_func;
+
+	err = sdio_claim_irq(func, aic_bt_sdio_irq);
+	if (err)
+		goto disable_func;
+	btdev->irq_claimed = true;
+
+	sdio_f0_writeb(func, 0x07, 0x04, &err);
+	if (err)
+		goto release_irq;
+
+	sdio_writeb(func, 0x07, AIC_SDIO_INTR_ENABLE, &err);
+	if (err)
+		goto release_irq;
+
+	sdio_release_host(func);
+	return 0;
+
+release_irq:
+	sdio_release_irq(func);
+	btdev->irq_claimed = false;
+disable_func:
+	sdio_disable_func(func);
+	btdev->function_enabled = false;
+release_host:
+	sdio_release_host(func);
+	return err;
+}
+
+static void aic_bt_sdio_hw_deinit(struct aic_bt_sdio *btdev)
+{
+	sdio_claim_host(btdev->func);
+
+	if (btdev->irq_claimed) {
+		int err;
+
+		sdio_writeb(btdev->func, 0, AIC_SDIO_INTR_ENABLE, &err);
+		sdio_release_irq(btdev->func);
+		btdev->irq_claimed = false;
+	}
+
+	if (btdev->function_enabled) {
+		sdio_disable_func(btdev->func);
+		btdev->function_enabled = false;
+	}
+
+	sdio_release_host(btdev->func);
+}
+
+static int aic_bt_sdio_probe(struct sdio_func *func,
+			     const struct sdio_device_id *id)
+{
+	struct aic_bt_sdio *btdev;
+	int err;
+
+	if (func->num != 1)
+		return -ENODEV;
+
+	btdev = devm_kzalloc(&func->dev, sizeof(*btdev), GFP_KERNEL);
+	if (!btdev)
+		return -ENOMEM;
+
+	btdev->tx_buf = devm_kmalloc(&func->dev, AIC_SDIO_TX_BUFFER_SIZE,
+				     GFP_KERNEL);
+	if (!btdev->tx_buf)
+		return -ENOMEM;
+
+	btdev->func = func;
+	mutex_init(&btdev->command_mutex);
+	spin_lock_init(&btdev->response_lock);
+	init_completion(&btdev->command_done);
+	sdio_set_drvdata(func, btdev);
+
+	err = aic_bt_sdio_hw_init(btdev);
+	if (err)
+		goto clear_drvdata;
+
+	btdev->boot = aic_bt_boot_register(&func->dev);
+	if (IS_ERR(btdev->boot)) {
+		err = PTR_ERR(btdev->boot);
+		btdev->boot = NULL;
+		goto deinit_hw;
+	}
+
+	err = aic_bt_download_firmware(btdev);
+	aic_bt_boot_ready(btdev->boot, err);
+	if (err)
+		goto unregister_boot;
+
+	return 0;
+
+unregister_boot:
+	aic_bt_boot_unregister(btdev->boot);
+	btdev->boot = NULL;
+deinit_hw:
+	aic_bt_sdio_hw_deinit(btdev);
+clear_drvdata:
+	sdio_set_drvdata(func, NULL);
+	return err;
+}
+
+static void aic_bt_sdio_remove(struct sdio_func *func)
+{
+	struct aic_bt_sdio *btdev = sdio_get_drvdata(func);
+
+	aic_bt_boot_unregister(btdev->boot);
+	btdev->boot = NULL;
+	aic_bt_sdio_hw_deinit(btdev);
+	sdio_set_drvdata(func, NULL);
+}
+
+static int aic_bt_sdio_suspend(struct device *dev)
+{
+	struct sdio_func *func = dev_to_sdio_func(dev);
+	mmc_pm_flag_t caps;
+
+	caps = sdio_get_host_pm_caps(func);
+	if (!(caps & MMC_PM_KEEP_POWER))
+		return -EOPNOTSUPP;
+
+	return sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
+}
+
+static int aic_bt_sdio_resume(struct device *dev)
+{
+	return 0;
+}
+
+static const struct dev_pm_ops aic_bt_sdio_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(aic_bt_sdio_suspend, aic_bt_sdio_resume)
+};
+
+static const struct sdio_device_id aic_bt_sdio_ids[] = {
+	{ SDIO_DEVICE(AIC_SDIO_VENDOR_ID, AIC_SDIO_DEVICE_ID_8800D80) },
+	{ }
+};
+MODULE_DEVICE_TABLE(sdio, aic_bt_sdio_ids);
+
+static const struct of_device_id aic_bt_sdio_of_match[] = {
+	{ .compatible = "aic,aic8800d80-bt-sdio" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, aic_bt_sdio_of_match);
+
+static struct sdio_driver aic_bt_sdio_driver = {
+	.name = "aic_bt_sdio",
+	.id_table = aic_bt_sdio_ids,
+	.probe = aic_bt_sdio_probe,
+	.remove = aic_bt_sdio_remove,
+	.drv = {
+		.of_match_table = aic_bt_sdio_of_match,
+		.pm = &aic_bt_sdio_pm_ops,
+	},
+};
+
+int aic_bt_sdio_register(void)
+{
+	return sdio_register_driver(&aic_bt_sdio_driver);
+}
+
+void aic_bt_sdio_unregister(void)
+{
+	sdio_unregister_driver(&aic_bt_sdio_driver);
+}
+
+MODULE_FIRMWARE(AIC_BT_FW_ADID);
+MODULE_FIRMWARE(AIC_BT_FW_PATCH);
+MODULE_FIRMWARE(AIC_BT_FW_TABLE);
diff --git a/drivers/bluetooth/btaic_uart.c b/drivers/bluetooth/btaic_uart.c
new file mode 100644
index 000000000000..4a38fccfe7af
--- /dev/null
+++ b/drivers/bluetooth/btaic_uart.c
@@ -0,0 +1,331 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief AIC8800D80 Bluetooth UART transport
+ *
+ ******************************************************************************
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/property.h>
+#include <linux/serdev.h>
+#include <linux/skbuff.h>
+#include <linux/workqueue.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+#include "btaic.h"
+#include "hci_uart.h"
+
+#define AIC_BT_UART_DEFAULT_SPEED	1500000
+#define AIC_BT_BOOT_TIMEOUT_MS		10000
+
+#define AIC_BT_TX_ACTIVE		0
+#define AIC_BT_TX_WAKEUP		1
+
+struct aic_bt_uart {
+	struct serdev_device *serdev;
+	struct hci_dev *hdev;
+	struct hci_uart hu;
+	struct aic_bt_boot *boot;
+
+	struct sk_buff *rx_skb;
+	struct sk_buff_head txq;
+	struct work_struct tx_work;
+	unsigned long tx_state;
+
+	u32 speed;
+};
+
+static const struct h4_recv_pkt aic_bt_recv_pkts[] = {
+	{ H4_RECV_ACL,   .recv = hci_recv_frame },
+	{ H4_RECV_SCO,   .recv = hci_recv_frame },
+	{ H4_RECV_EVENT, .recv = hci_recv_frame },
+	{ H4_RECV_ISO,   .recv = hci_recv_frame },
+};
+
+static void aic_bt_tx_work(struct work_struct *work)
+{
+	struct aic_bt_uart *uart = container_of(work, struct aic_bt_uart,
+						 tx_work);
+
+	for (;;) {
+		struct sk_buff *skb;
+
+		clear_bit(AIC_BT_TX_WAKEUP, &uart->tx_state);
+
+		while ((skb = skb_dequeue(&uart->txq))) {
+			int len;
+
+			len = serdev_device_write_buf(uart->serdev, skb->data,
+						      skb->len);
+			if (len <= 0) {
+				skb_queue_head(&uart->txq, skb);
+				if (len < 0)
+					bt_dev_err(uart->hdev,
+						   "UART transmit failed (%d)", len);
+				break;
+			}
+
+			uart->hdev->stat.byte_tx += len;
+			skb_pull(skb, len);
+			if (skb->len) {
+				skb_queue_head(&uart->txq, skb);
+				break;
+			}
+
+			switch (hci_skb_pkt_type(skb)) {
+			case HCI_COMMAND_PKT:
+				uart->hdev->stat.cmd_tx++;
+				break;
+			case HCI_ACLDATA_PKT:
+				uart->hdev->stat.acl_tx++;
+				break;
+			case HCI_SCODATA_PKT:
+				uart->hdev->stat.sco_tx++;
+				break;
+			}
+
+			kfree_skb(skb);
+		}
+
+		if (!test_bit(AIC_BT_TX_WAKEUP, &uart->tx_state))
+			break;
+	}
+
+	clear_bit(AIC_BT_TX_ACTIVE, &uart->tx_state);
+}
+
+static void aic_bt_tx_wakeup(struct aic_bt_uart *uart)
+{
+	if (test_and_set_bit(AIC_BT_TX_ACTIVE, &uart->tx_state))
+		set_bit(AIC_BT_TX_WAKEUP, &uart->tx_state);
+
+	schedule_work(&uart->tx_work);
+}
+
+static size_t aic_bt_receive_buf(struct serdev_device *serdev,
+				 const u8 *data, size_t count)
+{
+	struct aic_bt_uart *uart = serdev_device_get_drvdata(serdev);
+
+	uart->rx_skb = h4_recv_buf(&uart->hu, uart->rx_skb, data, count,
+				   aic_bt_recv_pkts,
+				   ARRAY_SIZE(aic_bt_recv_pkts));
+	if (IS_ERR(uart->rx_skb)) {
+		bt_dev_err(uart->hdev, "Frame reassembly failed (%ld)",
+			   PTR_ERR(uart->rx_skb));
+		uart->rx_skb = NULL;
+	}
+
+	uart->hdev->stat.byte_rx += count;
+	return count;
+}
+
+static void aic_bt_write_wakeup(struct serdev_device *serdev)
+{
+	struct aic_bt_uart *uart = serdev_device_get_drvdata(serdev);
+
+	aic_bt_tx_wakeup(uart);
+}
+
+static const struct serdev_device_ops aic_bt_serdev_ops = {
+	.receive_buf = aic_bt_receive_buf,
+	.write_wakeup = aic_bt_write_wakeup,
+};
+
+static int aic_bt_open(struct hci_dev *hdev)
+{
+	struct aic_bt_uart *uart = hci_get_drvdata(hdev);
+	unsigned int actual_speed;
+	int err;
+
+	err = aic_bt_boot_wait(uart->boot,
+			       msecs_to_jiffies(AIC_BT_BOOT_TIMEOUT_MS));
+	if (err) {
+		bt_dev_err(hdev, "Bluetooth firmware is not ready (%d)", err);
+		return err;
+	}
+
+	err = serdev_device_open(uart->serdev);
+	if (err)
+		return err;
+
+	actual_speed = serdev_device_set_baudrate(uart->serdev, uart->speed);
+	if (!actual_speed) {
+		serdev_device_close(uart->serdev);
+		return -EIO;
+	}
+
+	serdev_device_set_flow_control(uart->serdev, true);
+	return 0;
+}
+
+static int aic_bt_close(struct hci_dev *hdev)
+{
+	struct aic_bt_uart *uart = hci_get_drvdata(hdev);
+
+	serdev_device_close(uart->serdev);
+	return 0;
+}
+
+static int aic_bt_flush(struct hci_dev *hdev)
+{
+	struct aic_bt_uart *uart = hci_get_drvdata(hdev);
+
+	serdev_device_write_flush(uart->serdev);
+	cancel_work_sync(&uart->tx_work);
+	skb_queue_purge(&uart->txq);
+	kfree_skb(uart->rx_skb);
+	uart->rx_skb = NULL;
+	clear_bit(AIC_BT_TX_ACTIVE, &uart->tx_state);
+	clear_bit(AIC_BT_TX_WAKEUP, &uart->tx_state);
+
+	return 0;
+}
+
+static int aic_bt_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
+{
+	struct aic_bt_uart *uart = hci_get_drvdata(hdev);
+	u8 pkt_type = hci_skb_pkt_type(skb);
+	int err;
+
+	err = skb_cow_head(skb, 1);
+	if (err)
+		return err;
+
+	memcpy(skb_push(skb, 1), &pkt_type, sizeof(pkt_type));
+	skb_queue_tail(&uart->txq, skb);
+	aic_bt_tx_wakeup(uart);
+
+	return 0;
+}
+
+static void aic_bt_boot_put_action(void *data)
+{
+	aic_bt_boot_put(data);
+}
+
+static int aic_bt_find_boot_provider(struct device *dev,
+				     struct aic_bt_uart *uart)
+{
+	struct fwnode_handle *fwnode = NULL;
+	int err;
+
+	if (device_property_present(dev, "aic,firmware-sdio")) {
+		fwnode = fwnode_find_reference(dev_fwnode(dev),
+					       "aic,firmware-sdio", 0);
+		if (IS_ERR(fwnode))
+			return PTR_ERR(fwnode);
+	}
+
+	uart->boot = aic_bt_boot_get(fwnode);
+	fwnode_handle_put(fwnode);
+	if (IS_ERR(uart->boot))
+		return dev_err_probe(dev, PTR_ERR(uart->boot),
+				     "failed to find Bluetooth SDIO firmware loader\n");
+
+	err = devm_add_action_or_reset(dev, aic_bt_boot_put_action,
+				       uart->boot);
+	if (err)
+		return err;
+
+	if (!device_link_add(dev, aic_bt_boot_device(uart->boot),
+			     DL_FLAG_AUTOREMOVE_CONSUMER))
+		return -EINVAL;
+
+	return 0;
+}
+
+static int aic_bt_uart_probe(struct serdev_device *serdev)
+{
+	struct device *dev = &serdev->dev;
+	struct aic_bt_uart *uart;
+	struct hci_dev *hdev;
+	int err;
+
+	uart = devm_kzalloc(dev, sizeof(*uart), GFP_KERNEL);
+	if (!uart)
+		return -ENOMEM;
+
+	uart->serdev = serdev;
+	uart->speed = AIC_BT_UART_DEFAULT_SPEED;
+	device_property_read_u32(dev, "max-speed", &uart->speed);
+	serdev_device_set_drvdata(serdev, uart);
+	serdev_device_set_client_ops(serdev, &aic_bt_serdev_ops);
+
+	err = aic_bt_find_boot_provider(dev, uart);
+	if (err)
+		return err;
+
+	INIT_WORK(&uart->tx_work, aic_bt_tx_work);
+	skb_queue_head_init(&uart->txq);
+
+	hdev = hci_alloc_dev();
+	if (!hdev)
+		return -ENOMEM;
+
+	uart->hdev = hdev;
+	uart->hu.hdev = hdev;
+	hdev->bus = HCI_UART;
+	hci_set_drvdata(hdev, uart);
+	SET_HCIDEV_DEV(hdev, dev);
+
+	hdev->open = aic_bt_open;
+	hdev->close = aic_bt_close;
+	hdev->flush = aic_bt_flush;
+	hdev->send = aic_bt_send_frame;
+
+	err = hci_register_dev(hdev);
+	if (err) {
+		hci_free_dev(hdev);
+		return err;
+	}
+
+	return 0;
+}
+
+static void aic_bt_uart_remove(struct serdev_device *serdev)
+{
+	struct aic_bt_uart *uart = serdev_device_get_drvdata(serdev);
+
+	hci_unregister_dev(uart->hdev);
+	cancel_work_sync(&uart->tx_work);
+	skb_queue_purge(&uart->txq);
+	kfree_skb(uart->rx_skb);
+	uart->rx_skb = NULL;
+	hci_free_dev(uart->hdev);
+}
+
+static const struct of_device_id aic_bt_uart_of_match[] = {
+	{ .compatible = "aic,aic8800d80-bt" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, aic_bt_uart_of_match);
+
+static struct serdev_device_driver aic_bt_uart_driver = {
+	.probe = aic_bt_uart_probe,
+	.remove = aic_bt_uart_remove,
+	.driver = {
+		.name = "bt_aic_uart",
+		.of_match_table = aic_bt_uart_of_match,
+	},
+};
+
+int aic_bt_uart_register(void)
+{
+	return serdev_device_driver_register(&aic_bt_uart_driver);
+}
+
+void aic_bt_uart_unregister(void)
+{
+	serdev_device_driver_unregister(&aic_bt_uart_driver);
+}
-- 
2.34.1

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH bluetooth-next 3/3] Bluetooth: btaic: Add AIC8800D80 SDIO loader and UART transport
  2026-09-16  8:12 ` [RFC PATCH bluetooth-next 3/3] Bluetooth: btaic: Add AIC8800D80 SDIO loader and UART transport Yanli Yang
@ 2026-09-17  2:33   ` sashiko-bot
  0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-09-17  2:33 UTC (permalink / raw)
  To: Yanli Yang; +Cc: devicetree, conor+dt, robh

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] bluetooth: btaic: use-after-free of rx_skb during concurrent flush and receive
- [Medium] bluetooth: btaic: off-by-one in credits check causes timeout for exact-fit payloads
- [Medium] bluetooth: btaic: spurious command failure due to delayed completion signal
--

commit d0fbf93152e71e54fc5cd6389eb4e023f7adbfc3
Author: Yanli Yang <yanli.yang@bedmex.com>
Subject: Bluetooth: btaic: Add AIC8800D80 SDIO loader and UART transport

This commit adds a driver for the AIC8800D80 combination chip. It utilizes 
SDIO function 1 for loading Bluetooth firmware and a serdev UART interface 
for handling the H4 HCI traffic. The firmware loading process uses a vendor 
protocol to validate patch-table layouts and transfer the necessary images.

> --- /dev/null
> +++ b/drivers/bluetooth/btaic_sdio.c
> @@ -0,0 +1,886 @@

[ ... ]

> +static void aic_bt_complete_response(struct aic_bt_sdio *btdev,
> +				     const struct aic_bt_e2a_header *message,
> +				     size_t message_len)
> +{
> +	unsigned long flags;
> +	size_t param_len;
> +	u16 id;
> +	bool complete_command = false;
> +
> +	if (message_len < sizeof(*message))
> +		return;
> +
> +	id = get_unaligned_le16(&message->id);
> +	param_len = get_unaligned_le16(&message->param_len);
> +	if (param_len > message_len - sizeof(*message))
> +		return;
> +
> +	spin_lock_irqsave(&btdev->response_lock, flags);
> +	if (!btdev->waiting_response || id != btdev->expected_response)
> +		goto unlock;
> +
> +	if (btdev->response && param_len < btdev->response_size) {
> +		btdev->command_result = -EMSGSIZE;
> +	} else {
> +		if (btdev->response)
> +			memcpy(btdev->response, message->param,
> +			       btdev->response_size);
> +		btdev->command_result = 0;
> +	}
> +
> +	btdev->waiting_response = false;
> +	complete_command = true;
> +
> +unlock:
> +	spin_unlock_irqrestore(&btdev->response_lock, flags);
> +
> +	if (complete_command)
> +		complete(&btdev->command_done);
> +}

[Severity: Medium]
Could this delayed completion signal cause a race condition with a subsequent
command?

If a command times out, aic_bt_command() aborts and unlocks command_mutex.
If the SDIO IRQ handler processes the timed-out command's delayed response
concurrently, it releases response_lock just before calling complete().

If another thread executing aic_bt_command() acquires command_mutex and calls
reinit_completion() in this narrow window, the delayed complete() might satisfy
the new command's wait immediately. The new command would then incorrectly
return its initial command_result (-EINPROGRESS) instead of waiting for its
own response.

[ ... ]

> +static int aic_bt_wait_for_credits(struct aic_bt_sdio *btdev, size_t tx_len)
> +{
> +	unsigned int retry;
> +	int err;
> +
> +	for (retry = 0; retry < AIC_SDIO_FLOW_RETRIES; retry++) {
> +		u8 credits;
> +
> +		credits = sdio_readb(btdev->func, AIC_SDIO_FLOW_CTRL_Q1, &err);
> +		if (err)
> +			return err;
> +
> +		if (credits && tx_len < (size_t)credits * AIC_SDIO_BUFFER_SIZE)
> +			return 0;

[Severity: Medium]
Does this strict inequality incorrectly reject a transmission that exactly fits
the available credit buffers?

For example, when aic_bt_mem_block_write() sends firmware chunks of 1024
bytes, the total tx_len (including headers and padding) is calculated as
1536 bytes. If the hardware advertises exactly 1 credit (1536 bytes of
capacity), the check (1536 < 1536) evaluates to false, causing the driver
to unnecessarily spin until it times out.

> +
> +		if (retry < 30)
> +			usleep_range(30, 50);
> +		else if (retry < 40)
> +			usleep_range(1000, 1500);
> +		else
> +			usleep_range(10000, 12000);
> +	}
> +
> +	return -ETIMEDOUT;
> +}
> +
> +static int aic_bt_command(struct aic_bt_sdio *btdev, u16 request_id,
> +			  const void *param, size_t param_len, u16 response_id,
> +			  void *response, size_t response_size)
> +{
> +	unsigned long flags;
> +	size_t frame_len;
> +	size_t message_len;
> +	size_t tx_len;
> +	long timeout;
> +	int err;

[ ... ]

> +	reinit_completion(&btdev->command_done);
> +	spin_lock_irqsave(&btdev->response_lock, flags);
> +	btdev->expected_response = response_id;
> +	btdev->response = response;
> +	btdev->response_size = response_size;
> +	btdev->command_result = -EINPROGRESS;
> +	btdev->waiting_response = true;
> +	spin_unlock_irqrestore(&btdev->response_lock, flags);

[ ... ]

> --- /dev/null
> +++ b/drivers/bluetooth/btaic_uart.c
> @@ -0,0 +1,331 @@

[ ... ]

> +static size_t aic_bt_receive_buf(struct serdev_device *serdev,
> +				 const u8 *data, size_t count)
> +{
> +	struct aic_bt_uart *uart = serdev_device_get_drvdata(serdev);
> +
> +	uart->rx_skb = h4_recv_buf(&uart->hu, uart->rx_skb, data, count,
> +				   aic_bt_recv_pkts,
> +				   ARRAY_SIZE(aic_bt_recv_pkts));

[ ... ]

> +static int aic_bt_close(struct hci_dev *hdev)
> +{
> +	struct aic_bt_uart *uart = hci_get_drvdata(hdev);
> +
> +	serdev_device_close(uart->serdev);
> +	return 0;
> +}
> +
> +static int aic_bt_flush(struct hci_dev *hdev)
> +{
> +	struct aic_bt_uart *uart = hci_get_drvdata(hdev);
> +
> +	serdev_device_write_flush(uart->serdev);
> +	cancel_work_sync(&uart->tx_work);
> +	skb_queue_purge(&uart->txq);
> +	kfree_skb(uart->rx_skb);
> +	uart->rx_skb = NULL;

[Severity: High]
Can this lead to a use-after-free of uart->rx_skb if data is received
concurrently during teardown?

During interface teardown, the HCI stack calls hdev->flush() before it calls
hdev->close(). This means aic_bt_flush() will free uart->rx_skb while the
UART serdev is still open.

If a UART receive interrupt fires before aic_bt_close() runs,
aic_bt_receive_buf() could be called and pass the freed uart->rx_skb
pointer into h4_recv_buf(), causing memory corruption.

> +	clear_bit(AIC_BT_TX_ACTIVE, &uart->tx_state);
> +	clear_bit(AIC_BT_TX_WAKEUP, &uart->tx_state);
> +
> +	return 0;
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1789546358.git.yanli.yang@bedmex.com?part=3

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH bluetooth-next 3/3] Bluetooth: btaic: Add AIC8800D80 SDIO loader and UART transport
  2026-09-16  8:35 ` [RFC PATCH bluetooth-next 3/3] Bluetooth: btaic: Add AIC8800D80 SDIO loader and UART transport Yanli Yang
@ 2026-09-17  2:43   ` sashiko-bot
  0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-09-17  2:43 UTC (permalink / raw)
  To: Yanli Yang; +Cc: conor+dt, robh, devicetree

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] bluetooth: btaic: off-by-one in SDIO credit check causes timeout
- [High] bluetooth: btaic: missing SDIO transaction sequence numbers
- [High] bluetooth: btaic: UAF race in aic_bt_flush with RX path
- [High] bluetooth: btaic: deadlock in SDIO IRQ due to GFP_KERNEL allocation
- [Medium] bluetooth: btaic: UART max-speed DT property breaks firmware baud rate sync
--

commit c8b56eafa02e3f8c8946ccb2113f1c30e8b1cd4f
Author: Yanli Yang <yanli.yang@bedmex.com>
Subject: Bluetooth: btaic: Add AIC8800D80 SDIO loader and UART transport

Adds a driver for the AIC8800D80 combination chip. It implements an SDIO
firmware loader and a serdev UART for H4 HCI traffic.

> diff --git a/drivers/bluetooth/btaic_sdio.c b/drivers/bluetooth/btaic_sdio.c
> --- a/drivers/bluetooth/btaic_sdio.c
> +++ b/drivers/bluetooth/btaic_sdio.c
[ ... ]
> static void aic_bt_complete_response(struct aic_bt_sdio *btdev,
> 				     const struct aic_bt_e2a_header *message,
> 				     size_t message_len)
> {
[ ... ]
> 	spin_lock_irqsave(&btdev->response_lock, flags);
> 	if (!btdev->waiting_response || id != btdev->expected_response)
> 		goto unlock;

[Severity: High]
Is there a risk of mismatched responses if a command times out?

In aic_bt_command(), a timeout clears waiting_response and aborts, leaving
the hardware state machine active:

    spin_lock_irqsave(&btdev->response_lock, flags);
    btdev->waiting_response = false;
    btdev->response = NULL;

If a subsequent command of the same type is sent, and the hardware belatedly
responds to the timed-out request, aic_bt_complete_response() would accept
the late response since the expected_response ID matches. Could this corrupt
the initialization sequence?

[ ... ]
> static void aic_bt_sdio_irq(struct sdio_func *func)
> {
[ ... ]
> 	data = kmalloc(data_len, GFP_KERNEL);

[Severity: High]
Could this allocation deadlock under memory pressure?

The mmc core's sdio_irq_thread calls aic_bt_sdio_irq() with the mmc host lock
held. If GFP_KERNEL triggers direct reclaim, and reclaim tries to flush dirty
pages to a block device on the same mmc host, it would attempt to acquire the
host lock again, hanging the system.

[ ... ]
> static int aic_bt_wait_for_credits(struct aic_bt_sdio *btdev, size_t tx_len)
> {
[ ... ]
> 	for (retry = 0; retry < AIC_SDIO_FLOW_RETRIES; retry++) {
> 		u8 credits;
> 
> 		credits = sdio_readb(btdev->func, AIC_SDIO_FLOW_CTRL_Q1, &err);
> 		if (err)
> 			return err;
> 
> 		if (credits && tx_len < (size_t)credits * AIC_SDIO_BUFFER_SIZE)
> 			return 0;

[Severity: High]
Can this condition prevent transmission when exactly the right amount of
credits are available?

If the hardware grants exactly the required number of credits, this strictly
less-than check evaluates to false, causing the initialization to time out.
Should this be a <= check?

[ ... ]
> static u32 aic_bt_mode_value(unsigned int pair, u32 firmware_value)
> {
> 	switch (pair) {
[ ... ]
> 	case 5:
> 		return AIC_BT_UART_BAUD;

[Severity: Medium]
Does this firmware configuration get out of sync with the device tree speed?

Here, aic_bt_mode_value() hardcodes the firmware's baud rate patch to
1,500,000. However, in aic_bt_uart_probe(), the max-speed DT property is
read and configures the host UART speed. If the device tree specifies a
different speed, the host and controller will operate at different baud rates,
causing HCI communication to fail.

> diff --git a/drivers/bluetooth/btaic_uart.c b/drivers/bluetooth/btaic_uart.c
> --- a/drivers/bluetooth/btaic_uart.c
> +++ b/drivers/bluetooth/btaic_uart.c
[ ... ]
> static size_t aic_bt_receive_buf(struct serdev_device *serdev,
> 				 const u8 *data, size_t count)
> {
> 	struct aic_bt_uart *uart = serdev_device_get_drvdata(serdev);
> 
> 	uart->rx_skb = h4_recv_buf(&uart->hu, uart->rx_skb, data, count,
[ ... ]
> static int aic_bt_flush(struct hci_dev *hdev)
> {
[ ... ]
> 	kfree_skb(uart->rx_skb);
> 	uart->rx_skb = NULL;

[Severity: High]
Could this race with concurrent UART receives and cause a use-after-free?

When the HCI core invokes aic_bt_flush(), it frees uart->rx_skb. But the
serdev device is not closed until aic_bt_close(), meaning aic_bt_receive_buf()
could be executing simultaneously on another CPU, calling h4_recv_buf() on the
freed socket buffer.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/178954773705.2.3446471521233620446@bedmex.com?part=3

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [RFC PATCH v2 0/3] Bluetooth: Add AIC8800D80 SDIO firmware loader and UART HCI
  2026-09-16  8:12 [RFC PATCH bluetooth-next 0/3] Bluetooth: Add AIC8800D80 SDIO firmware loader and UART HCI Yanli Yang
                   ` (2 preceding siblings ...)
  2026-09-16  8:12 ` [RFC PATCH bluetooth-next 3/3] Bluetooth: btaic: Add AIC8800D80 SDIO loader and UART transport Yanli Yang
@ 2026-09-20  9:50 ` Yanli Yang
  2026-09-20  9:50   ` [RFC PATCH v2 1/3] dt-bindings: vendor-prefixes: Add AIC Semiconductor Yanli Yang
                     ` (3 more replies)
  3 siblings, 4 replies; 18+ messages in thread
From: Yanli Yang @ 2026-09-20  9:50 UTC (permalink / raw)
  To: linux-bluetooth
  Cc: marcel, devicetree, robh, krzk+dt, conor+dt, linux-kernel,
	zhirunliu, dijiaxu, chunqiuliu, liheng.wei, yanli.yang

Hi,

This RFC series adds initial Bluetooth support for the AIC8800D80.
SDIO function 1 loads the Bluetooth firmware; HCI traffic uses a separate
UART interface with H4 framing at 1500000 baud and hardware flow control.

The series adds the AIC vendor prefix, Device Tree bindings for the two
interfaces, and the btaic driver with build and maintainer integration.
The UART node references its SDIO firmware provider through the
aic,firmware-sdio property.

All issues reported by the automated checks on v1 have been addressed.
This version is also regenerated against the latest bluetooth-next master.

Validation status:
- The vendor has confirmed that SDIO firmware loading works.
- UART HCI communication, controller initialization, scanning, pairing,
  connections and data transfer have not yet been validated.
- Suspend/resume and Wi-Fi/Bluetooth coexistence have not been validated.
- No hardware functionality beyond firmware loading is claimed.

This is an RFC for implementation and binding review, not a request for
merging at this stage. Feedback is particularly welcome on the SDIO/UART
split, the firmware-provider association and the device lifetime handling.

The driver requests the following external firmware files:
  aic/aic8800d80/fw_adid_8800d80_u02.bin
  aic/aic8800d80/fw_patch_8800d80_u02.bin
  aic/aic8800d80/fw_patch_table_8800d80_u02.bin
Firmware binaries are not included in this kernel patch series.

Changes in v2:
- Fix the Device Tree schema encoding of the SDIO reg constant.
- Preallocate the SDIO receive buffer used by the interrupt handler.
- Prevent late responses from timed-out SDIO commands from racing the next
  command and its completion state.
- Accept transfers that exactly consume the available SDIO credits.
- Free the UART receive skb only after receive callbacks have stopped.
- Use the fixed baud rate required by the binding.
- Sync the series to bluetooth-next commit 019debf20bfd.

v1: https://lore.kernel.org/linux-bluetooth/cover.1789546358.git.yanli.yang@bedmex.com/

Thanks,
Yanli

Yanli Yang (3):
  dt-bindings: vendor-prefixes: Add AIC Semiconductor
  dt-bindings: net: bluetooth: Add AIC8800D80
  Bluetooth: btaic: Add AIC8800D80 SDIO loader and UART transport

 .../net/bluetooth/aic,aic8800d80-bt.yaml      |  87 ++
 .../devicetree/bindings/vendor-prefixes.yaml  |   2 +
 MAINTAINERS                                   |  11 +
 drivers/bluetooth/Kconfig                     |  16 +
 drivers/bluetooth/Makefile                    |   3 +
 drivers/bluetooth/btaic.h                     |  26 +
 drivers/bluetooth/btaic_core.c                | 180 ++++
 drivers/bluetooth/btaic_sdio.c                | 901 ++++++++++++++++++
 drivers/bluetooth/btaic_uart.c                | 328 +++++++
 9 files changed, 1554 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml
 create mode 100644 drivers/bluetooth/btaic.h
 create mode 100644 drivers/bluetooth/btaic_core.c
 create mode 100644 drivers/bluetooth/btaic_sdio.c
 create mode 100644 drivers/bluetooth/btaic_uart.c


base-commit: 019debf20bfd648b40ba10377ee0168db5eb241e
-- 
2.34.1

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [RFC PATCH v2 1/3] dt-bindings: vendor-prefixes: Add AIC Semiconductor
  2026-09-20  9:50 ` [RFC PATCH v2 0/3] Bluetooth: Add AIC8800D80 SDIO firmware loader and UART HCI Yanli Yang
@ 2026-09-20  9:50   ` Yanli Yang
  2026-09-20  9:50   ` [RFC PATCH v2 2/3] dt-bindings: net: bluetooth: Add AIC8800D80 Yanli Yang
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 18+ messages in thread
From: Yanli Yang @ 2026-09-20  9:50 UTC (permalink / raw)
  To: linux-bluetooth
  Cc: marcel, devicetree, robh, krzk+dt, conor+dt, linux-kernel,
	zhirunliu, dijiaxu, chunqiuliu, liheng.wei, yanli.yang

Add the vendor prefix for AIC Semiconductor (Shanghai) Co., Ltd.,
used by the AIC8800D80 Bluetooth bindings.

Signed-off-by: Zhirun Liu <zhirunliu@aicsemi.com>
Signed-off-by: Dijia Xu <dijiaxu@aicsemi.com>
Signed-off-by: Chunqiu Liu <chunqiuliu@aicsemi.com>
Signed-off-by: Liheng Wei <liheng.wei@bedmex.com>
Signed-off-by: Yanli Yang <yanli.yang@bedmex.com>
---
 Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index ba2002969373..8d35a4b7d8ed 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -76,6 +76,8 @@ patternProperties:
     description: Aeroflex Gaisler AB
   "^aesop,.*":
     description: AESOP Embedded Forum
+  "^aic,.*":
+    description: AIC Semiconductor (Shanghai) Co., Ltd.
   "^airoha,.*":
     description: Airoha
   "^al,.*":
-- 
2.34.1

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH v2 2/3] dt-bindings: net: bluetooth: Add AIC8800D80
  2026-09-20  9:50 ` [RFC PATCH v2 0/3] Bluetooth: Add AIC8800D80 SDIO firmware loader and UART HCI Yanli Yang
  2026-09-20  9:50   ` [RFC PATCH v2 1/3] dt-bindings: vendor-prefixes: Add AIC Semiconductor Yanli Yang
@ 2026-09-20  9:50   ` Yanli Yang
  2026-09-20 10:01     ` sashiko-bot
  2026-09-20  9:50   ` [RFC PATCH v2 3/3] Bluetooth: btaic: Add AIC8800D80 SDIO loader and UART transport Yanli Yang
  2026-09-21  7:25   ` [RFC PATCH v3 0/3] Bluetooth: Add AIC8800D80 support Yanli Yang
  3 siblings, 1 reply; 18+ messages in thread
From: Yanli Yang @ 2026-09-20  9:50 UTC (permalink / raw)
  To: linux-bluetooth
  Cc: marcel, devicetree, robh, krzk+dt, conor+dt, linux-kernel,
	zhirunliu, dijiaxu, chunqiuliu, liheng.wei, yanli.yang

Describe the AIC8800D80 Bluetooth SDIO firmware-loading function and
UART HCI interface. Link the UART node to its SDIO firmware provider
with the aic,firmware-sdio phandle.

The UART interface uses H4 at 1500000 baud with hardware flow control.
Include an example showing both nodes and their association.

Signed-off-by: Zhirun Liu <zhirunliu@aicsemi.com>
Signed-off-by: Dijia Xu <dijiaxu@aicsemi.com>
Signed-off-by: Chunqiu Liu <chunqiuliu@aicsemi.com>
Signed-off-by: Liheng Wei <liheng.wei@bedmex.com>
Signed-off-by: Yanli Yang <yanli.yang@bedmex.com>
---
 .../net/bluetooth/aic,aic8800d80-bt.yaml      | 87 +++++++++++++++++++
 1 file changed, 87 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml

diff --git a/Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml b/Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml
new file mode 100644
index 000000000000..d3ea780e85da
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml
@@ -0,0 +1,87 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/bluetooth/aic,aic8800d80-bt.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: AIC AIC8800D80 Bluetooth
+
+maintainers:
+  - Zhirun Liu <zhirunliu@aicsemi.com>
+  - Dijia Xu <dijiaxu@aicsemi.com>
+  - Chunqiu Liu <chunqiuliu@aicsemi.com>
+  - Liheng Wei <liheng.wei@bedmex.com>
+  - Yanli Yang <yanli.yang@bedmex.com>
+
+description:
+  The AIC8800D80 is a Wi-Fi and Bluetooth combination chip. The Bluetooth
+  firmware is loaded through SDIO function 1, while Bluetooth HCI traffic uses
+  the H4 protocol over a UART interface with hardware flow control.
+
+properties:
+  compatible:
+    enum:
+      - aic,aic8800d80-bt
+      - aic,aic8800d80-bt-sdio
+
+  reg:
+    maxItems: 1
+
+  max-speed: true
+
+  aic,firmware-sdio:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description:
+      Phandle to the SDIO function used to load the Bluetooth firmware.
+
+required:
+  - compatible
+
+allOf:
+  - $ref: bluetooth-controller.yaml#
+  - if:
+      properties:
+        compatible:
+          const: aic,aic8800d80-bt-sdio
+    then:
+      properties:
+        reg:
+          const: [[1]]
+        max-speed: false
+        aic,firmware-sdio: false
+      required:
+        - reg
+    else:
+      properties:
+        reg: false
+        max-speed:
+          const: 1500000
+      required:
+        - max-speed
+        - aic,firmware-sdio
+      allOf:
+        - $ref: /schemas/serial/serial-peripheral-props.yaml#
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    mmc {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        bt_sdio: bluetooth@1 {
+            compatible = "aic,aic8800d80-bt-sdio";
+            reg = <1>;
+        };
+    };
+
+    serial {
+        uart-has-rtscts;
+
+        bluetooth {
+            compatible = "aic,aic8800d80-bt";
+            max-speed = <1500000>;
+            aic,firmware-sdio = <&bt_sdio>;
+        };
+    };
-- 
2.34.1

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH v2 3/3] Bluetooth: btaic: Add AIC8800D80 SDIO loader and UART transport
  2026-09-20  9:50 ` [RFC PATCH v2 0/3] Bluetooth: Add AIC8800D80 SDIO firmware loader and UART HCI Yanli Yang
  2026-09-20  9:50   ` [RFC PATCH v2 1/3] dt-bindings: vendor-prefixes: Add AIC Semiconductor Yanli Yang
  2026-09-20  9:50   ` [RFC PATCH v2 2/3] dt-bindings: net: bluetooth: Add AIC8800D80 Yanli Yang
@ 2026-09-20  9:50   ` Yanli Yang
  2026-09-21  7:25   ` [RFC PATCH v3 0/3] Bluetooth: Add AIC8800D80 support Yanli Yang
  3 siblings, 0 replies; 18+ messages in thread
From: Yanli Yang @ 2026-09-20  9:50 UTC (permalink / raw)
  To: linux-bluetooth
  Cc: marcel, devicetree, robh, krzk+dt, conor+dt, linux-kernel,
	zhirunliu, dijiaxu, chunqiuliu, liheng.wei, yanli.yang

Add a driver for the AIC8800D80 combination chip, using SDIO function 1
for Bluetooth firmware loading and a serdev UART for H4 HCI traffic.

Load the ADID image, patch image and patch table through
request_firmware(), validate the patch-table layout, and transfer firmware
using the vendor SDIO command protocol. Recognize the U02 and U03 chip
revisions.

Coordinate the SDIO firmware provider and UART consumer through a
reference-counted boot state and a device link. Wait for firmware readiness
before opening the UART, and use hardware flow control at 1500000 baud.

Add CONFIG_BT_AIC, build integration and a MAINTAINERS entry.

The vendor has confirmed SDIO firmware loading only. UART HCI operation
and other Bluetooth functionality have not yet been validated. Submit
this implementation as RFC for review before further functional testing.

Signed-off-by: Zhirun Liu <zhirunliu@aicsemi.com>
Signed-off-by: Dijia Xu <dijiaxu@aicsemi.com>
Signed-off-by: Chunqiu Liu <chunqiuliu@aicsemi.com>
Signed-off-by: Liheng Wei <liheng.wei@bedmex.com>
Signed-off-by: Yanli Yang <yanli.yang@bedmex.com>
---
 MAINTAINERS                    |  11 +
 drivers/bluetooth/Kconfig      |  16 +
 drivers/bluetooth/Makefile     |   3 +
 drivers/bluetooth/btaic.h      |  26 +
 drivers/bluetooth/btaic_core.c | 180 +++++++
 drivers/bluetooth/btaic_sdio.c | 901 +++++++++++++++++++++++++++++++++
 drivers/bluetooth/btaic_uart.c | 328 ++++++++++++
 7 files changed, 1465 insertions(+)
 create mode 100644 drivers/bluetooth/btaic.h
 create mode 100644 drivers/bluetooth/btaic_core.c
 create mode 100644 drivers/bluetooth/btaic_sdio.c
 create mode 100644 drivers/bluetooth/btaic_uart.c

diff --git a/MAINTAINERS b/MAINTAINERS
index af5a8b4b0bb5..a5c6889fbccc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -729,6 +729,17 @@ S:	Maintained
 F:	drivers/scsi/aha152x*
 F:	drivers/scsi/pcmcia/aha152x*
 
+AIC BLUETOOTH DRIVER
+M:	Zhirun Liu <zhirunliu@aicsemi.com>
+M:	Dijia Xu <dijiaxu@aicsemi.com>
+M:	Chunqiu Liu <chunqiuliu@aicsemi.com>
+M:	Liheng Wei <liheng.wei@bedmex.com>
+M:	Yanli Yang <yanli.yang@bedmex.com>
+L:	linux-bluetooth@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml
+F:	drivers/bluetooth/btaic*
+
 AIC7XXX / AIC79XX SCSI DRIVER
 M:	Hannes Reinecke <hare@suse.com>
 L:	linux-scsi@vger.kernel.org
diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
index db0189ca3cbe..2b01f3838363 100644
--- a/drivers/bluetooth/Kconfig
+++ b/drivers/bluetooth/Kconfig
@@ -487,4 +487,20 @@ config BT_INTEL_PCIE
 
 	  Say Y here to compiler support for Intel Bluetooth PCIe device into
 	  the kernel or say M to compile it as module (btintel_pcie)
+
+config BT_AIC
+	tristate "AIC8800D80 Bluetooth support"
+	depends on MMC
+	depends on SERIAL_DEV_BUS
+	depends on BT_HCIUART
+	select BT_HCIUART_H4
+	select FW_LOADER
+	help
+	  Bluetooth support for the AIC8800D80 combination chip, using an
+	  SDIO function for firmware loading and a UART interface for the
+	  HCI transport.
+
+	  Say Y here to compile support into the kernel or M to build it as
+	  a module named btaic.
+
 endmenu
diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
index d5eb2568af2e..19abc5252676 100644
--- a/drivers/bluetooth/Makefile
+++ b/drivers/bluetooth/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_BT_MRVL)		+= btmrvl.o
 obj-$(CONFIG_BT_MRVL_SDIO)	+= btmrvl_sdio.o
 obj-$(CONFIG_BT_MTKSDIO)	+= btmtksdio.o
 obj-$(CONFIG_BT_MTKUART)	+= btmtkuart.o
+obj-$(CONFIG_BT_AIC)		+= btaic.o
 obj-$(CONFIG_BT_QCOMSMD)	+= btqcomsmd.o
 obj-$(CONFIG_BT_BCM)		+= btbcm.o
 obj-$(CONFIG_BT_RTL)		+= btrtl.o
@@ -55,3 +56,5 @@ btusb-y				:= btusb_main.o
 btusb-$(CONFIG_BT_HCIBTUSB_QCOM)	+= btusb_qcom.o
 
 CONTEXT_ANALYSIS := y
+
+btaic-y				:= btaic_core.o btaic_sdio.o btaic_uart.o
diff --git a/drivers/bluetooth/btaic.h b/drivers/bluetooth/btaic.h
new file mode 100644
index 000000000000..dc631b495697
--- /dev/null
+++ b/drivers/bluetooth/btaic.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __BTAIC_H
+#define __BTAIC_H
+
+#include <linux/types.h>
+
+struct device;
+struct fwnode_handle;
+
+struct aic_bt_boot;
+
+struct aic_bt_boot *aic_bt_boot_register(struct device *dev);
+void aic_bt_boot_ready(struct aic_bt_boot *boot, int status);
+void aic_bt_boot_unregister(struct aic_bt_boot *boot);
+
+struct aic_bt_boot *aic_bt_boot_get(const struct fwnode_handle *fwnode);
+void aic_bt_boot_put(struct aic_bt_boot *boot);
+int aic_bt_boot_wait(struct aic_bt_boot *boot, unsigned long timeout);
+struct device *aic_bt_boot_device(struct aic_bt_boot *boot);
+
+int aic_bt_sdio_register(void);
+void aic_bt_sdio_unregister(void);
+int aic_bt_uart_register(void);
+void aic_bt_uart_unregister(void);
+
+#endif
diff --git a/drivers/bluetooth/btaic_core.c b/drivers/bluetooth/btaic_core.c
new file mode 100644
index 000000000000..35644b9b1209
--- /dev/null
+++ b/drivers/bluetooth/btaic_core.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief AIC8800D80 Bluetooth driver core
+ *
+ ******************************************************************************
+ */
+
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+
+#include "btaic.h"
+
+struct aic_bt_boot {
+	struct kref ref;
+	struct list_head node;
+	struct completion ready;
+	struct device *dev;
+	int status;
+	bool present;
+};
+
+static DEFINE_MUTEX(aic_bt_boot_lock);
+static LIST_HEAD(aic_bt_boot_list);
+
+static void aic_bt_boot_release(struct kref *ref)
+{
+	struct aic_bt_boot *boot = container_of(ref, struct aic_bt_boot, ref);
+
+	put_device(boot->dev);
+	kfree(boot);
+}
+
+struct aic_bt_boot *aic_bt_boot_register(struct device *dev)
+{
+	struct aic_bt_boot *boot;
+
+	boot = kzalloc_obj(*boot);
+	if (!boot)
+		return ERR_PTR(-ENOMEM);
+
+	kref_init(&boot->ref);
+	INIT_LIST_HEAD(&boot->node);
+	init_completion(&boot->ready);
+	boot->dev = get_device(dev);
+	boot->status = -EINPROGRESS;
+	boot->present = true;
+
+	mutex_lock(&aic_bt_boot_lock);
+	list_add_tail(&boot->node, &aic_bt_boot_list);
+	mutex_unlock(&aic_bt_boot_lock);
+
+	return boot;
+}
+
+void aic_bt_boot_ready(struct aic_bt_boot *boot, int status)
+{
+	mutex_lock(&aic_bt_boot_lock);
+	if (boot->present)
+		boot->status = status;
+	mutex_unlock(&aic_bt_boot_lock);
+
+	complete_all(&boot->ready);
+}
+
+void aic_bt_boot_unregister(struct aic_bt_boot *boot)
+{
+	if (!boot)
+		return;
+
+	mutex_lock(&aic_bt_boot_lock);
+	if (boot->present) {
+		list_del_init(&boot->node);
+		boot->present = false;
+		boot->status = -ENODEV;
+	}
+	mutex_unlock(&aic_bt_boot_lock);
+
+	complete_all(&boot->ready);
+	kref_put(&boot->ref, aic_bt_boot_release);
+}
+
+struct aic_bt_boot *aic_bt_boot_get(const struct fwnode_handle *fwnode)
+{
+	struct aic_bt_boot *boot;
+	struct aic_bt_boot *found = NULL;
+	unsigned int count = 0;
+
+	mutex_lock(&aic_bt_boot_lock);
+	list_for_each_entry(boot, &aic_bt_boot_list, node) {
+		if (!boot->present)
+			continue;
+
+		if (fwnode) {
+			if (dev_fwnode(boot->dev) == fwnode) {
+				found = boot;
+				break;
+			}
+			continue;
+		}
+
+		found = boot;
+		count++;
+	}
+
+	if (found && (fwnode || count == 1))
+		kref_get(&found->ref);
+	else if (count > 1)
+		found = ERR_PTR(-EINVAL);
+	else
+		found = ERR_PTR(-EPROBE_DEFER);
+	mutex_unlock(&aic_bt_boot_lock);
+
+	return found;
+}
+
+void aic_bt_boot_put(struct aic_bt_boot *boot)
+{
+	if (!IS_ERR_OR_NULL(boot))
+		kref_put(&boot->ref, aic_bt_boot_release);
+}
+
+int aic_bt_boot_wait(struct aic_bt_boot *boot, unsigned long timeout)
+{
+	int status;
+
+	if (!wait_for_completion_timeout(&boot->ready, timeout))
+		return -ETIMEDOUT;
+
+	mutex_lock(&aic_bt_boot_lock);
+	status = boot->present ? boot->status : -ENODEV;
+	mutex_unlock(&aic_bt_boot_lock);
+
+	return status;
+}
+
+struct device *aic_bt_boot_device(struct aic_bt_boot *boot)
+{
+	return boot->dev;
+}
+
+static int __init aic_bt_init(void)
+{
+	int err;
+
+	err = aic_bt_sdio_register();
+	if (err)
+		return err;
+
+	err = aic_bt_uart_register();
+	if (err) {
+		aic_bt_sdio_unregister();
+		return err;
+	}
+
+	return 0;
+}
+
+static void __exit aic_bt_exit(void)
+{
+	aic_bt_uart_unregister();
+	aic_bt_sdio_unregister();
+}
+
+module_init(aic_bt_init);
+module_exit(aic_bt_exit);
+
+MODULE_AUTHOR("AIC Semiconductor");
+MODULE_DESCRIPTION("AIC8800D80 Bluetooth driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/bluetooth/btaic_sdio.c b/drivers/bluetooth/btaic_sdio.c
new file mode 100644
index 000000000000..18d58f0832a5
--- /dev/null
+++ b/drivers/bluetooth/btaic_sdio.c
@@ -0,0 +1,901 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief AIC8800D80 Bluetooth SDIO firmware loader
+ *
+ ******************************************************************************
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/minmax.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/sdio_ids.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/overflow.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/unaligned.h>
+
+#include "btaic.h"
+
+#define AIC_SDIO_VENDOR_ID			0xc8a1
+#define AIC_SDIO_DEVICE_ID_8800D80		0x0082
+
+#define AIC_SDIO_BLOCK_SIZE			512
+#define AIC_SDIO_BUFFER_SIZE			1536
+#define AIC_SDIO_TX_BUFFER_SIZE			1536
+#define AIC_SDIO_RX_MAX_SIZE			(127 * AIC_SDIO_BLOCK_SIZE)
+
+#define AIC_SDIO_INTR_ENABLE			0x00
+#define AIC_SDIO_INTR_PENDING			0x01
+#define AIC_SDIO_FLOW_CTRL_Q1			0x03
+#define AIC_SDIO_MISC_INT_STATUS		0x04
+#define AIC_SDIO_BYTEMODE_LEN			0x05
+#define AIC_SDIO_BYTEMODE_ENABLE		0x07
+#define AIC_SDIO_RD_FIFO			0x0f
+#define AIC_SDIO_WR_FIFO			0x10
+
+#define AIC_SDIO_OTHER_INTERRUPT		BIT(7)
+#define AIC_SDIO_BYTE_MODE_BLOCKS		120
+#define AIC_SDIO_FLOW_RETRIES			50
+#define AIC_SDIO_FRAME_TAIL_LEN			4
+
+#define AIC_CMD_TIMEOUT_MS			6000
+#define AIC_CMD_TYPE				0x11
+#define AIC_TASK_DBG				1
+#define AIC_DRIVER_TASK				100
+#define AIC_FIRST_MSG(task)			((u16)(task) << 10)
+
+#define AIC_DBG_MEM_READ_REQ			(AIC_FIRST_MSG(AIC_TASK_DBG) + 0)
+#define AIC_DBG_MEM_READ_CFM			(AIC_FIRST_MSG(AIC_TASK_DBG) + 1)
+#define AIC_DBG_MEM_WRITE_REQ			(AIC_FIRST_MSG(AIC_TASK_DBG) + 2)
+#define AIC_DBG_MEM_WRITE_CFM			(AIC_FIRST_MSG(AIC_TASK_DBG) + 3)
+#define AIC_DBG_MEM_BLOCK_WRITE_REQ		(AIC_FIRST_MSG(AIC_TASK_DBG) + 11)
+#define AIC_DBG_MEM_BLOCK_WRITE_CFM		(AIC_FIRST_MSG(AIC_TASK_DBG) + 12)
+
+#define AIC_BT_CHIP_ID_ADDR			0x40500000
+#define AIC_BT_CHIP_REV_U02			3
+#define AIC_BT_CHIP_REV_U03			7
+
+#define AIC_BT_FW_ADID				"aic/aic8800d80/fw_adid_8800d80_u02.bin"
+#define AIC_BT_FW_PATCH				"aic/aic8800d80/fw_patch_8800d80_u02.bin"
+#define AIC_BT_FW_TABLE				"aic/aic8800d80/fw_patch_table_8800d80_u02.bin"
+
+#define AIC_BT_PATCH_TAG			"AICBT_PT_TAG"
+#define AIC_BT_PATCH_TAG_SIZE			16
+#define AIC_BT_PATCH_RECORD_HEADER_SIZE		24
+#define AIC_BT_PATCH_PAIR_SIZE			8
+#define AIC_BT_PATCH_BLOCK_SIZE			1024
+
+#define AIC_BT_MODE_ONLY_COANT			5
+#define AIC_BT_PORT_UART			2
+#define AIC_BT_UART_BAUD			1500000
+#define AIC_BT_UART_FLOW_CTRL			1
+#define AIC_BT_LOW_POWER_ENABLE			1
+#define AIC_BT_TX_POWER_LEVEL			0x00006f2f
+
+enum aic_bt_patch_type {
+	AIC_BT_PATCH_INFO,
+	AIC_BT_PATCH_TRAP,
+	AIC_BT_PATCH_B4,
+	AIC_BT_PATCH_MODE,
+	AIC_BT_PATCH_POWER_ON,
+	AIC_BT_PATCH_AF,
+	AIC_BT_PATCH_VERSION,
+};
+
+struct aic_bt_e2a_header {
+	__le16 id;
+	__le16 dest_id;
+	__le16 src_id;
+	__le16 param_len;
+	__le32 pattern;
+	u8 param[];
+} __packed;
+
+struct aic_bt_mem_read_cfm {
+	__le32 address;
+	__le32 value;
+} __packed;
+
+struct aic_bt_sdio {
+	struct sdio_func *func;
+	struct aic_bt_boot *boot;
+
+	/* Serializes commands because the firmware accepts one at a time. */
+	struct mutex command_mutex;
+	/* Protects response state shared with the SDIO IRQ handler. */
+	spinlock_t response_lock;
+	struct completion command_done;
+	u16 expected_response;
+	void *response;
+	size_t response_size;
+	int command_result;
+	bool waiting_response;
+	/* The firmware protocol has no transaction sequence number. */
+	bool command_timed_out;
+
+	u8 *tx_buf;
+	u8 *rx_buf;
+	bool function_enabled;
+	bool irq_claimed;
+};
+
+static u8 aic_bt_crc8(const u8 *buffer, size_t len)
+{
+	u8 crc = 0;
+	size_t byte;
+	int bit;
+
+	for (byte = 0; byte < len; byte++) {
+		for (bit = 0x80; bit; bit >>= 1) {
+			if (crc & 0x80)
+				crc = (crc << 1) ^ 0x07;
+			else
+				crc <<= 1;
+
+			if (buffer[byte] & bit)
+				crc ^= 0x07;
+		}
+	}
+
+	return crc;
+}
+
+static void aic_bt_complete_response(struct aic_bt_sdio *btdev,
+				     const struct aic_bt_e2a_header *message,
+				     size_t message_len)
+{
+	unsigned long flags;
+	size_t param_len;
+	u16 id;
+
+	if (message_len < sizeof(*message))
+		return;
+
+	id = get_unaligned_le16(&message->id);
+	param_len = get_unaligned_le16(&message->param_len);
+	if (param_len > message_len - sizeof(*message))
+		return;
+
+	spin_lock_irqsave(&btdev->response_lock, flags);
+	if (!btdev->waiting_response || id != btdev->expected_response)
+		goto unlock;
+
+	if (btdev->response && param_len < btdev->response_size) {
+		btdev->command_result = -EMSGSIZE;
+	} else {
+		if (btdev->response)
+			memcpy(btdev->response, message->param,
+			       btdev->response_size);
+		btdev->command_result = 0;
+	}
+
+	btdev->waiting_response = false;
+	/*
+	 * Serialize completion with command setup.  In particular, do not
+	 * allow a timed out command's completion to race a following command's
+	 * reinit_completion().
+	 */
+	complete(&btdev->command_done);
+
+unlock:
+	spin_unlock_irqrestore(&btdev->response_lock, flags);
+}
+
+static void aic_bt_parse_rx(struct aic_bt_sdio *btdev, const u8 *data,
+			    size_t data_len)
+{
+	size_t offset = 0;
+
+	while (data_len - offset >= 4) {
+		const struct aic_bt_e2a_header *message;
+		size_t frame_len;
+		size_t frame_size;
+
+		frame_len = get_unaligned_le16(data + offset);
+		if (!frame_len)
+			break;
+
+		if (check_add_overflow(frame_len, (size_t)4, &frame_size) ||
+		    frame_size > data_len - offset)
+			break;
+
+		if ((data[offset + 2] & 0x7f) == AIC_CMD_TYPE) {
+			message = (const void *)(data + offset + 4);
+			aic_bt_complete_response(btdev, message, frame_len);
+		}
+
+		frame_size = roundup(frame_len, 4) + 4;
+		if (frame_size > data_len - offset)
+			break;
+		offset += frame_size;
+	}
+}
+
+static void aic_bt_sdio_irq(struct sdio_func *func)
+{
+	struct aic_bt_sdio *btdev = sdio_get_drvdata(func);
+	u8 *data;
+	u8 blocks;
+	u8 status;
+	size_t data_len;
+	int err;
+
+	status = sdio_readb(func, AIC_SDIO_MISC_INT_STATUS, &err);
+	if (err) {
+		dev_err_ratelimited(&func->dev,
+				    "failed to read interrupt status: %d\n", err);
+		return;
+	}
+
+	if (status & AIC_SDIO_OTHER_INTERRUPT) {
+		u8 pending;
+
+		pending = sdio_readb(func, AIC_SDIO_INTR_PENDING, &err);
+		if (!err) {
+			pending &= ~BIT(0);
+			sdio_writeb(func, pending, AIC_SDIO_INTR_PENDING, &err);
+		}
+		if (err)
+			dev_err_ratelimited(&func->dev,
+					    "failed to clear soft interrupt: %d\n",
+					    err);
+	}
+
+	blocks = status & 0x7f;
+	if (!blocks)
+		return;
+
+	if (blocks == AIC_SDIO_BYTE_MODE_BLOCKS) {
+		u8 words;
+
+		words = sdio_readb(func, AIC_SDIO_BYTEMODE_LEN, &err);
+		if (err)
+			return;
+		data_len = (size_t)words * 4;
+	} else {
+		data_len = (size_t)blocks * AIC_SDIO_BLOCK_SIZE;
+	}
+
+	if (!data_len || data_len > AIC_SDIO_RX_MAX_SIZE) {
+		dev_err_ratelimited(&func->dev,
+				    "invalid SDIO response length %zu\n", data_len);
+		return;
+	}
+
+	data = btdev->rx_buf;
+	err = sdio_readsb(func, data, AIC_SDIO_RD_FIFO, data_len);
+	if (err)
+		dev_err_ratelimited(&func->dev,
+				    "failed to read response: %d\n", err);
+	else
+		aic_bt_parse_rx(btdev, data, data_len);
+}
+
+static int aic_bt_wait_for_credits(struct aic_bt_sdio *btdev, size_t tx_len)
+{
+	unsigned int retry;
+	int err;
+
+	for (retry = 0; retry < AIC_SDIO_FLOW_RETRIES; retry++) {
+		u8 credits;
+
+		credits = sdio_readb(btdev->func, AIC_SDIO_FLOW_CTRL_Q1, &err);
+		if (err)
+			return err;
+
+		if (credits && tx_len <= (size_t)credits * AIC_SDIO_BUFFER_SIZE)
+			return 0;
+
+		if (retry < 30)
+			usleep_range(30, 50);
+		else if (retry < 40)
+			usleep_range(1000, 1500);
+		else
+			usleep_range(10000, 12000);
+	}
+
+	return -ETIMEDOUT;
+}
+
+static int aic_bt_command(struct aic_bt_sdio *btdev, u16 request_id,
+			  const void *param, size_t param_len, u16 response_id,
+			  void *response, size_t response_size)
+{
+	unsigned long flags;
+	size_t frame_len;
+	size_t message_len;
+	size_t tx_len;
+	long timeout;
+	int err;
+
+	if (param_len > U16_MAX)
+		return -EMSGSIZE;
+
+	message_len = 8 + param_len;
+	frame_len = 8 + message_len;
+	if (frame_len > AIC_SDIO_TX_BUFFER_SIZE)
+		return -EMSGSIZE;
+
+	if (IS_ALIGNED(frame_len, AIC_SDIO_BLOCK_SIZE))
+		tx_len = frame_len;
+	else
+		tx_len = roundup(frame_len + AIC_SDIO_FRAME_TAIL_LEN,
+				 AIC_SDIO_BLOCK_SIZE);
+
+	if (tx_len > AIC_SDIO_TX_BUFFER_SIZE)
+		return -EMSGSIZE;
+
+	mutex_lock(&btdev->command_mutex);
+	spin_lock_irqsave(&btdev->response_lock, flags);
+	if (btdev->command_timed_out) {
+		spin_unlock_irqrestore(&btdev->response_lock, flags);
+		err = -ETIMEDOUT;
+		goto unlock_command;
+	}
+	spin_unlock_irqrestore(&btdev->response_lock, flags);
+
+	memset(btdev->tx_buf, 0, tx_len);
+	put_unaligned_le16(message_len + 4, btdev->tx_buf);
+	btdev->tx_buf[2] = AIC_CMD_TYPE;
+	btdev->tx_buf[3] = aic_bt_crc8(btdev->tx_buf, 3);
+
+	put_unaligned_le16(request_id, btdev->tx_buf + 8);
+	put_unaligned_le16(AIC_TASK_DBG, btdev->tx_buf + 10);
+	put_unaligned_le16(AIC_DRIVER_TASK, btdev->tx_buf + 12);
+	put_unaligned_le16(param_len, btdev->tx_buf + 14);
+	if (param_len)
+		memcpy(btdev->tx_buf + 16, param, param_len);
+
+	spin_lock_irqsave(&btdev->response_lock, flags);
+	reinit_completion(&btdev->command_done);
+	btdev->expected_response = response_id;
+	btdev->response = response;
+	btdev->response_size = response_size;
+	btdev->command_result = -EINPROGRESS;
+	btdev->waiting_response = true;
+	spin_unlock_irqrestore(&btdev->response_lock, flags);
+
+	sdio_claim_host(btdev->func);
+	err = aic_bt_wait_for_credits(btdev, tx_len);
+	if (!err)
+		err = sdio_writesb(btdev->func, AIC_SDIO_WR_FIFO,
+				   btdev->tx_buf, tx_len);
+	sdio_release_host(btdev->func);
+	if (err)
+		goto clear_response;
+
+	timeout = wait_for_completion_timeout(&btdev->command_done,
+					      msecs_to_jiffies(AIC_CMD_TIMEOUT_MS));
+	if (!timeout) {
+		dev_err(&btdev->func->dev,
+			"command 0x%04x timed out waiting for 0x%04x\n",
+			request_id, response_id);
+		err = -ETIMEDOUT;
+		spin_lock_irqsave(&btdev->response_lock, flags);
+		btdev->command_timed_out = true;
+		spin_unlock_irqrestore(&btdev->response_lock, flags);
+		goto clear_response;
+	}
+
+	spin_lock_irqsave(&btdev->response_lock, flags);
+	err = btdev->command_result;
+	btdev->response = NULL;
+	btdev->response_size = 0;
+	spin_unlock_irqrestore(&btdev->response_lock, flags);
+	mutex_unlock(&btdev->command_mutex);
+
+	return err;
+
+clear_response:
+	spin_lock_irqsave(&btdev->response_lock, flags);
+	btdev->waiting_response = false;
+	btdev->response = NULL;
+	btdev->response_size = 0;
+	spin_unlock_irqrestore(&btdev->response_lock, flags);
+unlock_command:
+	mutex_unlock(&btdev->command_mutex);
+
+	return err;
+}
+
+static int aic_bt_mem_read(struct aic_bt_sdio *btdev, u32 address, u32 *value)
+{
+	struct aic_bt_mem_read_cfm cfm;
+	__le32 request = cpu_to_le32(address);
+	int err;
+
+	err = aic_bt_command(btdev, AIC_DBG_MEM_READ_REQ,
+			     &request, sizeof(request), AIC_DBG_MEM_READ_CFM,
+			     &cfm, sizeof(cfm));
+	if (!err)
+		*value = le32_to_cpu(cfm.value);
+
+	return err;
+}
+
+static int aic_bt_mem_write(struct aic_bt_sdio *btdev, u32 address, u32 value)
+{
+	__le32 request[2] = {
+		cpu_to_le32(address),
+		cpu_to_le32(value),
+	};
+
+	return aic_bt_command(btdev, AIC_DBG_MEM_WRITE_REQ,
+			      request, sizeof(request), AIC_DBG_MEM_WRITE_CFM,
+			      NULL, 0);
+}
+
+static int aic_bt_mem_block_write(struct aic_bt_sdio *btdev, u32 address,
+				  const u8 *data, size_t data_len)
+{
+	__le32 status;
+	u8 *request;
+	int err;
+
+	if (data_len > AIC_BT_PATCH_BLOCK_SIZE)
+		return -EINVAL;
+
+	request = kzalloc(8 + AIC_BT_PATCH_BLOCK_SIZE, GFP_KERNEL);
+	if (!request)
+		return -ENOMEM;
+
+	put_unaligned_le32(address, request);
+	put_unaligned_le32(data_len, request + 4);
+	memcpy(request + 8, data, data_len);
+
+	err = aic_bt_command(btdev, AIC_DBG_MEM_BLOCK_WRITE_REQ,
+			     request, 8 + AIC_BT_PATCH_BLOCK_SIZE,
+			     AIC_DBG_MEM_BLOCK_WRITE_CFM,
+			     &status, sizeof(status));
+	kfree(request);
+	if (err)
+		return err;
+
+	if (le32_to_cpu(status)) {
+		dev_err(&btdev->func->dev,
+			"firmware rejected block write at 0x%08x\n", address);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int aic_bt_upload_firmware(struct aic_bt_sdio *btdev, const char *name,
+				  u32 address)
+{
+	const struct firmware *firmware;
+	size_t offset = 0;
+	int err;
+
+	err = request_firmware(&firmware, name, &btdev->func->dev);
+	if (err)
+		return dev_err_probe(&btdev->func->dev, err,
+				     "failed to load %s\n", name);
+
+	if (firmware->size > U32_MAX - address) {
+		err = -EFBIG;
+		goto release;
+	}
+
+	while (offset < firmware->size) {
+		size_t len = min_t(size_t, AIC_BT_PATCH_BLOCK_SIZE,
+				   firmware->size - offset);
+
+		err = aic_bt_mem_block_write(btdev, address + offset,
+					     firmware->data + offset, len);
+		if (err)
+			goto release;
+		offset += len;
+	}
+
+	dev_dbg(&btdev->func->dev, "loaded %s (%zu bytes) at 0x%08x\n",
+		name, firmware->size, address);
+
+release:
+	release_firmware(firmware);
+	return err;
+}
+
+static u32 aic_bt_mode_value(unsigned int pair, u32 firmware_value)
+{
+	switch (pair) {
+	case 0:
+		return 1;
+	case 1:
+		return U32_MAX;
+	case 2:
+		return 0;
+	case 3:
+		return AIC_BT_MODE_ONLY_COANT;
+	case 4:
+		return AIC_BT_PORT_UART;
+	case 5:
+		return AIC_BT_UART_BAUD;
+	case 6:
+		return AIC_BT_UART_FLOW_CTRL;
+	case 7:
+		return AIC_BT_LOW_POWER_ENABLE;
+	case 8:
+		return AIC_BT_TX_POWER_LEVEL;
+	default:
+		return firmware_value;
+	}
+}
+
+static int aic_bt_process_patch_table(struct aic_bt_sdio *btdev,
+				      const struct firmware *firmware,
+				      bool apply, u32 *adid_addr,
+				      u32 *patch_addr)
+{
+	const u8 *data = firmware->data;
+	size_t offset = AIC_BT_PATCH_TAG_SIZE;
+	bool have_record = false;
+	bool have_info = false;
+
+	if (firmware->size < AIC_BT_PATCH_TAG_SIZE ||
+	    memcmp(data, AIC_BT_PATCH_TAG, sizeof(AIC_BT_PATCH_TAG)))
+		return -EBADMSG;
+
+	while (offset < firmware->size) {
+		const u8 *record;
+		const u8 *pairs_data;
+		size_t pairs_size;
+		unsigned int pair;
+		u32 pair_count;
+		u32 type;
+		int err;
+
+		if (firmware->size - offset <
+		    AIC_BT_PATCH_RECORD_HEADER_SIZE)
+			return -EBADMSG;
+
+		record = data + offset;
+		type = get_unaligned_le32(record + 16);
+		pair_count = get_unaligned_le32(record + 20);
+		offset += AIC_BT_PATCH_RECORD_HEADER_SIZE;
+
+		if (type >= 1000) {
+			pairs_size = 0;
+			pair_count = 0;
+		} else if (check_mul_overflow((size_t)pair_count,
+					      (size_t)AIC_BT_PATCH_PAIR_SIZE,
+					      &pairs_size)) {
+			return -EOVERFLOW;
+		}
+
+		if (pairs_size > firmware->size - offset)
+			return -EBADMSG;
+
+		pairs_data = data + offset;
+		have_record = true;
+
+		if (type == AIC_BT_PATCH_INFO) {
+			if (pair_count < 2)
+				return -EBADMSG;
+
+			if (!have_info) {
+				if (adid_addr)
+					*adid_addr = get_unaligned_le32(pairs_data + 4);
+				if (patch_addr)
+					*patch_addr = get_unaligned_le32(pairs_data + 12);
+				have_info = true;
+			}
+		}
+
+		if (!apply)
+			goto next_record;
+
+		if (type == AIC_BT_PATCH_VERSION) {
+			dev_info(&btdev->func->dev, "BT patch version: %.*s\n",
+				 (int)min_t(size_t, pairs_size, 80),
+				 pairs_data);
+			goto next_record;
+		}
+
+		if (type == AIC_BT_PATCH_MODE && pair_count < 9)
+			return -EBADMSG;
+
+		for (pair = 0; pair < pair_count; pair++) {
+			u32 address;
+			u32 value;
+
+			address = get_unaligned_le32(pairs_data +
+						    pair * AIC_BT_PATCH_PAIR_SIZE);
+			value = get_unaligned_le32(pairs_data +
+						  pair * AIC_BT_PATCH_PAIR_SIZE + 4);
+			if (type == AIC_BT_PATCH_MODE)
+				value = aic_bt_mode_value(pair, value);
+
+			err = aic_bt_mem_write(btdev, address, value);
+			if (err)
+				return err;
+		}
+
+		if (type == AIC_BT_PATCH_POWER_ON)
+			usleep_range(50, 100);
+
+next_record:
+		offset += pairs_size;
+	}
+
+	if (!have_record || ((adid_addr || patch_addr) && !have_info))
+		return -EBADMSG;
+
+	return 0;
+}
+
+static int aic_bt_download_firmware(struct aic_bt_sdio *btdev)
+{
+	const struct firmware *table;
+	u32 chip_id;
+	u32 adid_addr;
+	u32 patch_addr;
+	u8 revision;
+	int err;
+
+	err = aic_bt_mem_read(btdev, AIC_BT_CHIP_ID_ADDR, &chip_id);
+	if (err)
+		return err;
+
+	revision = (chip_id >> 16) & 0x3f;
+	if (revision != AIC_BT_CHIP_REV_U02 &&
+	    revision != AIC_BT_CHIP_REV_U03) {
+		dev_err(&btdev->func->dev,
+			"unsupported AIC8800D80 revision %u\n", revision);
+		return -ENODEV;
+	}
+
+	err = request_firmware(&table, AIC_BT_FW_TABLE, &btdev->func->dev);
+	if (err)
+		return dev_err_probe(&btdev->func->dev, err,
+				     "failed to load %s\n", AIC_BT_FW_TABLE);
+
+	err = aic_bt_process_patch_table(btdev, table, false,
+					 &adid_addr, &patch_addr);
+	if (err) {
+		dev_err(&btdev->func->dev, "invalid BT patch table: %d\n", err);
+		goto release_table;
+	}
+
+	err = aic_bt_upload_firmware(btdev, AIC_BT_FW_ADID, adid_addr);
+	if (err)
+		goto release_table;
+
+	err = aic_bt_upload_firmware(btdev, AIC_BT_FW_PATCH, patch_addr);
+	if (err)
+		goto release_table;
+
+	err = aic_bt_process_patch_table(btdev, table, true, NULL, NULL);
+	if (!err)
+		dev_info(&btdev->func->dev,
+			 "AIC8800D80 revision %u Bluetooth firmware ready\n",
+			 revision);
+
+release_table:
+	release_firmware(table);
+	return err;
+}
+
+static int aic_bt_sdio_hw_init(struct aic_bt_sdio *btdev)
+{
+	struct sdio_func *func = btdev->func;
+	struct mmc_host *host = func->card->host;
+	u8 io_control;
+	int err;
+
+	sdio_claim_host(func);
+	func->card->quirks |= MMC_QUIRK_LENIENT_FN0;
+
+	err = sdio_set_block_size(func, AIC_SDIO_BLOCK_SIZE);
+	if (err)
+		goto release_host;
+
+	err = sdio_enable_func(func);
+	if (err)
+		goto release_host;
+	btdev->function_enabled = true;
+
+	sdio_f0_writeb(func, 0x7f, 0xf2, &err);
+	if (err)
+		goto disable_func;
+
+	io_control = host->ios.timing == MMC_TIMING_UHS_DDR50 ? 0x20 : 0x00;
+	io_control |= BIT(6);
+	sdio_f0_writeb(func, io_control, 0xf0, &err);
+	if (err)
+		goto disable_func;
+
+	sdio_f0_writeb(func, 0x00, 0xf8, &err);
+	if (err)
+		goto disable_func;
+
+	sdio_f0_writeb(func, 0x00, 0xf1, &err);
+	if (err)
+		goto disable_func;
+
+	sdio_writeb(func, 1, AIC_SDIO_BYTEMODE_ENABLE, &err);
+	if (err)
+		goto disable_func;
+
+	err = sdio_claim_irq(func, aic_bt_sdio_irq);
+	if (err)
+		goto disable_func;
+	btdev->irq_claimed = true;
+
+	sdio_f0_writeb(func, 0x07, 0x04, &err);
+	if (err)
+		goto release_irq;
+
+	sdio_writeb(func, 0x07, AIC_SDIO_INTR_ENABLE, &err);
+	if (err)
+		goto release_irq;
+
+	sdio_release_host(func);
+	return 0;
+
+release_irq:
+	sdio_release_irq(func);
+	btdev->irq_claimed = false;
+disable_func:
+	sdio_disable_func(func);
+	btdev->function_enabled = false;
+release_host:
+	sdio_release_host(func);
+	return err;
+}
+
+static void aic_bt_sdio_hw_deinit(struct aic_bt_sdio *btdev)
+{
+	sdio_claim_host(btdev->func);
+
+	if (btdev->irq_claimed) {
+		int err;
+
+		sdio_writeb(btdev->func, 0, AIC_SDIO_INTR_ENABLE, &err);
+		sdio_release_irq(btdev->func);
+		btdev->irq_claimed = false;
+	}
+
+	if (btdev->function_enabled) {
+		sdio_disable_func(btdev->func);
+		btdev->function_enabled = false;
+	}
+
+	sdio_release_host(btdev->func);
+}
+
+static int aic_bt_sdio_probe(struct sdio_func *func,
+			     const struct sdio_device_id *id)
+{
+	struct aic_bt_sdio *btdev;
+	int err;
+
+	if (func->num != 1)
+		return -ENODEV;
+
+	btdev = devm_kzalloc(&func->dev, sizeof(*btdev), GFP_KERNEL);
+	if (!btdev)
+		return -ENOMEM;
+
+	btdev->tx_buf = devm_kmalloc(&func->dev, AIC_SDIO_TX_BUFFER_SIZE,
+				     GFP_KERNEL);
+	if (!btdev->tx_buf)
+		return -ENOMEM;
+
+	btdev->rx_buf = devm_kmalloc(&func->dev, AIC_SDIO_RX_MAX_SIZE,
+				     GFP_KERNEL);
+	if (!btdev->rx_buf)
+		return -ENOMEM;
+
+	btdev->func = func;
+	mutex_init(&btdev->command_mutex);
+	spin_lock_init(&btdev->response_lock);
+	init_completion(&btdev->command_done);
+	sdio_set_drvdata(func, btdev);
+
+	err = aic_bt_sdio_hw_init(btdev);
+	if (err)
+		goto clear_drvdata;
+
+	btdev->boot = aic_bt_boot_register(&func->dev);
+	if (IS_ERR(btdev->boot)) {
+		err = PTR_ERR(btdev->boot);
+		btdev->boot = NULL;
+		goto deinit_hw;
+	}
+
+	err = aic_bt_download_firmware(btdev);
+	aic_bt_boot_ready(btdev->boot, err);
+	if (err)
+		goto unregister_boot;
+
+	return 0;
+
+unregister_boot:
+	aic_bt_boot_unregister(btdev->boot);
+	btdev->boot = NULL;
+deinit_hw:
+	aic_bt_sdio_hw_deinit(btdev);
+clear_drvdata:
+	sdio_set_drvdata(func, NULL);
+	return err;
+}
+
+static void aic_bt_sdio_remove(struct sdio_func *func)
+{
+	struct aic_bt_sdio *btdev = sdio_get_drvdata(func);
+
+	aic_bt_boot_unregister(btdev->boot);
+	btdev->boot = NULL;
+	aic_bt_sdio_hw_deinit(btdev);
+	sdio_set_drvdata(func, NULL);
+}
+
+static int aic_bt_sdio_suspend(struct device *dev)
+{
+	struct sdio_func *func = dev_to_sdio_func(dev);
+	mmc_pm_flag_t caps;
+
+	caps = sdio_get_host_pm_caps(func);
+	if (!(caps & MMC_PM_KEEP_POWER))
+		return -EOPNOTSUPP;
+
+	return sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
+}
+
+static int aic_bt_sdio_resume(struct device *dev)
+{
+	return 0;
+}
+
+static const struct dev_pm_ops aic_bt_sdio_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(aic_bt_sdio_suspend, aic_bt_sdio_resume)
+};
+
+static const struct sdio_device_id aic_bt_sdio_ids[] = {
+	{ SDIO_DEVICE(AIC_SDIO_VENDOR_ID, AIC_SDIO_DEVICE_ID_8800D80) },
+	{ }
+};
+MODULE_DEVICE_TABLE(sdio, aic_bt_sdio_ids);
+
+static const struct of_device_id aic_bt_sdio_of_match[] = {
+	{ .compatible = "aic,aic8800d80-bt-sdio" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, aic_bt_sdio_of_match);
+
+static struct sdio_driver aic_bt_sdio_driver = {
+	.name = "aic_bt_sdio",
+	.id_table = aic_bt_sdio_ids,
+	.probe = aic_bt_sdio_probe,
+	.remove = aic_bt_sdio_remove,
+	.drv = {
+		.of_match_table = aic_bt_sdio_of_match,
+		.pm = &aic_bt_sdio_pm_ops,
+	},
+};
+
+int aic_bt_sdio_register(void)
+{
+	return sdio_register_driver(&aic_bt_sdio_driver);
+}
+
+void aic_bt_sdio_unregister(void)
+{
+	sdio_unregister_driver(&aic_bt_sdio_driver);
+}
+
+MODULE_FIRMWARE(AIC_BT_FW_ADID);
+MODULE_FIRMWARE(AIC_BT_FW_PATCH);
+MODULE_FIRMWARE(AIC_BT_FW_TABLE);
diff --git a/drivers/bluetooth/btaic_uart.c b/drivers/bluetooth/btaic_uart.c
new file mode 100644
index 000000000000..224ec8fef864
--- /dev/null
+++ b/drivers/bluetooth/btaic_uart.c
@@ -0,0 +1,328 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief AIC8800D80 Bluetooth UART transport
+ *
+ ******************************************************************************
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/property.h>
+#include <linux/serdev.h>
+#include <linux/skbuff.h>
+#include <linux/workqueue.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+#include "btaic.h"
+#include "hci_uart.h"
+
+#define AIC_BT_UART_DEFAULT_SPEED	1500000
+#define AIC_BT_BOOT_TIMEOUT_MS		10000
+
+#define AIC_BT_TX_ACTIVE		0
+#define AIC_BT_TX_WAKEUP		1
+
+struct aic_bt_uart {
+	struct serdev_device *serdev;
+	struct hci_dev *hdev;
+	struct hci_uart hu;
+	struct aic_bt_boot *boot;
+
+	struct sk_buff *rx_skb;
+	struct sk_buff_head txq;
+	struct work_struct tx_work;
+	unsigned long tx_state;
+};
+
+static const struct h4_recv_pkt aic_bt_recv_pkts[] = {
+	{ H4_RECV_ACL,   .recv = hci_recv_frame },
+	{ H4_RECV_SCO,   .recv = hci_recv_frame },
+	{ H4_RECV_EVENT, .recv = hci_recv_frame },
+	{ H4_RECV_ISO,   .recv = hci_recv_frame },
+};
+
+static void aic_bt_tx_work(struct work_struct *work)
+{
+	struct aic_bt_uart *uart = container_of(work, struct aic_bt_uart,
+						 tx_work);
+
+	for (;;) {
+		struct sk_buff *skb;
+
+		clear_bit(AIC_BT_TX_WAKEUP, &uart->tx_state);
+
+		while ((skb = skb_dequeue(&uart->txq))) {
+			int len;
+
+			len = serdev_device_write_buf(uart->serdev, skb->data,
+						      skb->len);
+			if (len <= 0) {
+				skb_queue_head(&uart->txq, skb);
+				if (len < 0)
+					bt_dev_err(uart->hdev,
+						   "UART transmit failed (%d)", len);
+				break;
+			}
+
+			uart->hdev->stat.byte_tx += len;
+			skb_pull(skb, len);
+			if (skb->len) {
+				skb_queue_head(&uart->txq, skb);
+				break;
+			}
+
+			switch (hci_skb_pkt_type(skb)) {
+			case HCI_COMMAND_PKT:
+				uart->hdev->stat.cmd_tx++;
+				break;
+			case HCI_ACLDATA_PKT:
+				uart->hdev->stat.acl_tx++;
+				break;
+			case HCI_SCODATA_PKT:
+				uart->hdev->stat.sco_tx++;
+				break;
+			}
+
+			kfree_skb(skb);
+		}
+
+		if (!test_bit(AIC_BT_TX_WAKEUP, &uart->tx_state))
+			break;
+	}
+
+	clear_bit(AIC_BT_TX_ACTIVE, &uart->tx_state);
+}
+
+static void aic_bt_tx_wakeup(struct aic_bt_uart *uart)
+{
+	if (test_and_set_bit(AIC_BT_TX_ACTIVE, &uart->tx_state))
+		set_bit(AIC_BT_TX_WAKEUP, &uart->tx_state);
+
+	schedule_work(&uart->tx_work);
+}
+
+static size_t aic_bt_receive_buf(struct serdev_device *serdev,
+				 const u8 *data, size_t count)
+{
+	struct aic_bt_uart *uart = serdev_device_get_drvdata(serdev);
+
+	uart->rx_skb = h4_recv_buf(&uart->hu, uart->rx_skb, data, count,
+				   aic_bt_recv_pkts,
+				   ARRAY_SIZE(aic_bt_recv_pkts));
+	if (IS_ERR(uart->rx_skb)) {
+		bt_dev_err(uart->hdev, "Frame reassembly failed (%ld)",
+			   PTR_ERR(uart->rx_skb));
+		uart->rx_skb = NULL;
+	}
+
+	uart->hdev->stat.byte_rx += count;
+	return count;
+}
+
+static void aic_bt_write_wakeup(struct serdev_device *serdev)
+{
+	struct aic_bt_uart *uart = serdev_device_get_drvdata(serdev);
+
+	aic_bt_tx_wakeup(uart);
+}
+
+static const struct serdev_device_ops aic_bt_serdev_ops = {
+	.receive_buf = aic_bt_receive_buf,
+	.write_wakeup = aic_bt_write_wakeup,
+};
+
+static int aic_bt_open(struct hci_dev *hdev)
+{
+	struct aic_bt_uart *uart = hci_get_drvdata(hdev);
+	unsigned int actual_speed;
+	int err;
+
+	err = aic_bt_boot_wait(uart->boot,
+			       msecs_to_jiffies(AIC_BT_BOOT_TIMEOUT_MS));
+	if (err) {
+		bt_dev_err(hdev, "Bluetooth firmware is not ready (%d)", err);
+		return err;
+	}
+
+	err = serdev_device_open(uart->serdev);
+	if (err)
+		return err;
+
+	actual_speed = serdev_device_set_baudrate(uart->serdev, AIC_BT_UART_DEFAULT_SPEED);
+	if (!actual_speed) {
+		serdev_device_close(uart->serdev);
+		return -EIO;
+	}
+
+	serdev_device_set_flow_control(uart->serdev, true);
+	return 0;
+}
+
+static int aic_bt_close(struct hci_dev *hdev)
+{
+	struct aic_bt_uart *uart = hci_get_drvdata(hdev);
+
+	serdev_device_close(uart->serdev);
+	/* serdev_device_close() stops receive callbacks before freeing rx_skb. */
+	kfree_skb(uart->rx_skb);
+	uart->rx_skb = NULL;
+	return 0;
+}
+
+static int aic_bt_flush(struct hci_dev *hdev)
+{
+	struct aic_bt_uart *uart = hci_get_drvdata(hdev);
+
+	serdev_device_write_flush(uart->serdev);
+	cancel_work_sync(&uart->tx_work);
+	skb_queue_purge(&uart->txq);
+	clear_bit(AIC_BT_TX_ACTIVE, &uart->tx_state);
+	clear_bit(AIC_BT_TX_WAKEUP, &uart->tx_state);
+
+	return 0;
+}
+
+static int aic_bt_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
+{
+	struct aic_bt_uart *uart = hci_get_drvdata(hdev);
+	u8 pkt_type = hci_skb_pkt_type(skb);
+	int err;
+
+	err = skb_cow_head(skb, 1);
+	if (err)
+		return err;
+
+	memcpy(skb_push(skb, 1), &pkt_type, sizeof(pkt_type));
+	skb_queue_tail(&uart->txq, skb);
+	aic_bt_tx_wakeup(uart);
+
+	return 0;
+}
+
+static void aic_bt_boot_put_action(void *data)
+{
+	aic_bt_boot_put(data);
+}
+
+static int aic_bt_find_boot_provider(struct device *dev,
+				     struct aic_bt_uart *uart)
+{
+	struct fwnode_handle *fwnode = NULL;
+	int err;
+
+	if (device_property_present(dev, "aic,firmware-sdio")) {
+		fwnode = fwnode_find_reference(dev_fwnode(dev),
+					       "aic,firmware-sdio", 0);
+		if (IS_ERR(fwnode))
+			return PTR_ERR(fwnode);
+	}
+
+	uart->boot = aic_bt_boot_get(fwnode);
+	fwnode_handle_put(fwnode);
+	if (IS_ERR(uart->boot))
+		return dev_err_probe(dev, PTR_ERR(uart->boot),
+				     "failed to find Bluetooth SDIO firmware loader\n");
+
+	err = devm_add_action_or_reset(dev, aic_bt_boot_put_action,
+				       uart->boot);
+	if (err)
+		return err;
+
+	if (!device_link_add(dev, aic_bt_boot_device(uart->boot),
+			     DL_FLAG_AUTOREMOVE_CONSUMER))
+		return -EINVAL;
+
+	return 0;
+}
+
+static int aic_bt_uart_probe(struct serdev_device *serdev)
+{
+	struct device *dev = &serdev->dev;
+	struct aic_bt_uart *uart;
+	struct hci_dev *hdev;
+	int err;
+
+	uart = devm_kzalloc(dev, sizeof(*uart), GFP_KERNEL);
+	if (!uart)
+		return -ENOMEM;
+
+	uart->serdev = serdev;
+	serdev_device_set_drvdata(serdev, uart);
+	serdev_device_set_client_ops(serdev, &aic_bt_serdev_ops);
+
+	err = aic_bt_find_boot_provider(dev, uart);
+	if (err)
+		return err;
+
+	INIT_WORK(&uart->tx_work, aic_bt_tx_work);
+	skb_queue_head_init(&uart->txq);
+
+	hdev = hci_alloc_dev();
+	if (!hdev)
+		return -ENOMEM;
+
+	uart->hdev = hdev;
+	uart->hu.hdev = hdev;
+	hdev->bus = HCI_UART;
+	hci_set_drvdata(hdev, uart);
+	SET_HCIDEV_DEV(hdev, dev);
+
+	hdev->open = aic_bt_open;
+	hdev->close = aic_bt_close;
+	hdev->flush = aic_bt_flush;
+	hdev->send = aic_bt_send_frame;
+
+	err = hci_register_dev(hdev);
+	if (err) {
+		hci_free_dev(hdev);
+		return err;
+	}
+
+	return 0;
+}
+
+static void aic_bt_uart_remove(struct serdev_device *serdev)
+{
+	struct aic_bt_uart *uart = serdev_device_get_drvdata(serdev);
+
+	hci_unregister_dev(uart->hdev);
+	cancel_work_sync(&uart->tx_work);
+	skb_queue_purge(&uart->txq);
+	kfree_skb(uart->rx_skb);
+	uart->rx_skb = NULL;
+	hci_free_dev(uart->hdev);
+}
+
+static const struct of_device_id aic_bt_uart_of_match[] = {
+	{ .compatible = "aic,aic8800d80-bt" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, aic_bt_uart_of_match);
+
+static struct serdev_device_driver aic_bt_uart_driver = {
+	.probe = aic_bt_uart_probe,
+	.remove = aic_bt_uart_remove,
+	.driver = {
+		.name = "bt_aic_uart",
+		.of_match_table = aic_bt_uart_of_match,
+	},
+};
+
+int aic_bt_uart_register(void)
+{
+	return serdev_device_driver_register(&aic_bt_uart_driver);
+}
+
+void aic_bt_uart_unregister(void)
+{
+	serdev_device_driver_unregister(&aic_bt_uart_driver);
+}
-- 
2.34.1

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH v2 2/3] dt-bindings: net: bluetooth: Add AIC8800D80
  2026-09-20  9:50   ` [RFC PATCH v2 2/3] dt-bindings: net: bluetooth: Add AIC8800D80 Yanli Yang
@ 2026-09-20 10:01     ` sashiko-bot
  0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-09-20 10:01 UTC (permalink / raw)
  To: Yanli Yang; +Cc: devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] dt-bindings: net: bluetooth: Incorrect max-speed property constraint
- [Low] dt-bindings: net: bluetooth: Unidiomatic reg property constraint syntax
--

commit 8cfd6d94bf45715d77abf03541df961e63f18ae5
Author: Yanli Yang <yanli.yang@bedmex.com>

dt-bindings: net: bluetooth: Add AIC8800D80

This commit adds a devicetree binding for the AIC8800D80 Bluetooth chip.
It describes the SDIO firmware-loading function and the UART HCI interface
which uses the H4 protocol with hardware flow control.

> diff --git a/Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml b/Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml
> new file mode 100644
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml

[ ... ]

> +allOf:
> +  - $ref: bluetooth-controller.yaml#
> +  - if:
> +      properties:
> +        compatible:
> +          const: aic,aic8800d80-bt-sdio
> +    then:
> +      properties:
> +        reg:
> +          const: [[1]]

[Severity: Low]
Is this the correct syntax for constraining the reg property?

In devicetree YAML bindings, reg constraints are typically expressed using
standard json-schema properties like minimum/maximum or an items list
definition. Does using a 2D array [[1]] expose the internal parsing
representation of dt-schema, making it unidiomatic? Could this fail
validation in future tooling updates or confuse other developers?

> +        max-speed: false
> +        aic,firmware-sdio: false
> +      required:
> +        - reg
> +    else:
> +      properties:
> +        reg: false
> +        max-speed:
> +          const: 1500000
> +      required:
> +        - max-speed

[Severity: Medium]
Does this incorrectly mandate a fixed max-speed?

According to standard devicetree principles and the core
serial-peripheral-props.yaml specification, max-speed represents a
board-specific hardware limitation and should only be present if the maximum
is less than the slave device can support. By requiring it and forcing it to
const: 1500000, does this force redundant, fixed configuration into every
device tree using this compatible string?

Would this also prevent developers from specifying a lower speed if their
particular board design has signal quality issues? If the device always
operates at 1.5 Mbaud by default, should the kernel driver handle that based
on the compatible string rather than hardcoding a required static property in
the DT?

> +        - aic,firmware-sdio
> +      allOf:
> +        - $ref: /schemas/serial/serial-peripheral-props.yaml#

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1789897503.git.yanli.yang@bedmex.com?part=2

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [RFC PATCH v3 0/3] Bluetooth: Add AIC8800D80 support
  2026-09-20  9:50 ` [RFC PATCH v2 0/3] Bluetooth: Add AIC8800D80 SDIO firmware loader and UART HCI Yanli Yang
                     ` (2 preceding siblings ...)
  2026-09-20  9:50   ` [RFC PATCH v2 3/3] Bluetooth: btaic: Add AIC8800D80 SDIO loader and UART transport Yanli Yang
@ 2026-09-21  7:25   ` Yanli Yang
  2026-09-21  7:25     ` [RFC PATCH v3 1/3] Bluetooth: dt-bindings: Add AIC vendor prefix Yanli Yang
                       ` (2 more replies)
  3 siblings, 3 replies; 18+ messages in thread
From: Yanli Yang @ 2026-09-21  7:25 UTC (permalink / raw)
  To: linux-bluetooth
  Cc: marcel, devicetree, robh, krzk+dt, conor+dt, linux-kernel,
	zhirunliu, dijiaxu, chunqiuliu, liheng.wei, yanli.yang

Hi,

This RFC series adds initial Bluetooth support for the AIC8800D80.
SDIO function 1 loads the Bluetooth firmware; HCI traffic uses a separate
UART interface with H4 framing at 1500000 baud and hardware flow control.

The series adds the AIC vendor prefix, Device Tree bindings for the two
interfaces, and the btaic driver with build and maintainer integration.
The UART node references its SDIO firmware provider through the
aic,firmware-sdio property.

Validation status:
- The vendor has confirmed that SDIO firmware loading works.
- UART HCI communication, controller initialization, scanning, pairing,
  connections and data transfer have not yet been validated.
- Suspend/resume and Wi-Fi/Bluetooth coexistence have not been validated.
- No hardware functionality beyond firmware loading is claimed.

This is an RFC for implementation and binding review, not a request for
merging at this stage. Feedback is particularly welcome on the SDIO/UART
split, the firmware-provider association and the device lifetime handling.

The driver requests the following external firmware files:
  aic/aic8800d80/fw_adid_8800d80_u02.bin
  aic/aic8800d80/fw_patch_8800d80_u02.bin
  aic/aic8800d80/fw_patch_table_8800d80_u02.bin
Firmware binaries are not included in this kernel patch series.

Changes in v3:
- Express the SDIO reg constraint using an items list.
- Drop the max-speed property because the driver selects the fixed
  1500000 baud rate based on the compatible string.
- Update MAINTAINERS in each patch that adds new files.
- Use the Bluetooth subject prefix for all patches.

The BlueZ GitLint check may flag the auto-generated "create mode" summary
line for the binding path as exceeding 80 columns. No source or commit
message line is involved.

Changes in v2:
- Preallocate the SDIO receive buffer used by the interrupt handler.
- Prevent late responses from timed-out SDIO commands from racing the next
  command and its completion state.
- Accept transfers that exactly consume the available SDIO credits.
- Free the UART receive skb only after receive callbacks have stopped.
- Use the fixed baud rate required by the controller.
- Sync the series to bluetooth-next commit 019debf20bfd.

v2: https://lore.kernel.org/linux-bluetooth/cover.1789897503.git.yanli.yang@bedmex.com/
v1: https://lore.kernel.org/linux-bluetooth/cover.1789546358.git.yanli.yang@bedmex.com/

Thanks,
Yanli

Yanli Yang (3):
  Bluetooth: dt-bindings: Add AIC vendor prefix
  Bluetooth: dt-bindings: Add AIC8800D80
  Bluetooth: btaic: Add AIC8800D80 support

 .../net/bluetooth/aic,aic8800d80-bt.yaml      |  83 ++
 .../devicetree/bindings/vendor-prefixes.yaml  |   2 +
 MAINTAINERS                                   |  11 +
 drivers/bluetooth/Kconfig                     |  16 +
 drivers/bluetooth/Makefile                    |   3 +
 drivers/bluetooth/btaic.h                     |  26 +
 drivers/bluetooth/btaic_core.c                | 180 ++++
 drivers/bluetooth/btaic_sdio.c                | 901 ++++++++++++++++++
 drivers/bluetooth/btaic_uart.c                | 328 +++++++
 9 files changed, 1550 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml
 create mode 100644 drivers/bluetooth/btaic.h
 create mode 100644 drivers/bluetooth/btaic_core.c
 create mode 100644 drivers/bluetooth/btaic_sdio.c
 create mode 100644 drivers/bluetooth/btaic_uart.c


base-commit: 019debf20bfd648b40ba10377ee0168db5eb241e
-- 
2.34.1

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [RFC PATCH v3 1/3] Bluetooth: dt-bindings: Add AIC vendor prefix
  2026-09-21  7:25   ` [RFC PATCH v3 0/3] Bluetooth: Add AIC8800D80 support Yanli Yang
@ 2026-09-21  7:25     ` Yanli Yang
  2026-09-21  7:25     ` [RFC PATCH v3 2/3] Bluetooth: dt-bindings: Add AIC8800D80 Yanli Yang
  2026-09-21  7:25     ` [RFC PATCH v3 3/3] Bluetooth: btaic: Add AIC8800D80 support Yanli Yang
  2 siblings, 0 replies; 18+ messages in thread
From: Yanli Yang @ 2026-09-21  7:25 UTC (permalink / raw)
  To: linux-bluetooth
  Cc: marcel, devicetree, robh, krzk+dt, conor+dt, linux-kernel,
	zhirunliu, dijiaxu, chunqiuliu, liheng.wei, yanli.yang

Add the vendor prefix for AIC Semiconductor (Shanghai) Co., Ltd.,
used by the AIC8800D80 Bluetooth bindings.

Signed-off-by: Zhirun Liu <zhirunliu@aicsemi.com>
Signed-off-by: Dijia Xu <dijiaxu@aicsemi.com>
Signed-off-by: Chunqiu Liu <chunqiuliu@aicsemi.com>
Signed-off-by: Liheng Wei <liheng.wei@bedmex.com>
Signed-off-by: Yanli Yang <yanli.yang@bedmex.com>
---
 Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index ba2002969373..8d35a4b7d8ed 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -76,6 +76,8 @@ patternProperties:
     description: Aeroflex Gaisler AB
   "^aesop,.*":
     description: AESOP Embedded Forum
+  "^aic,.*":
+    description: AIC Semiconductor (Shanghai) Co., Ltd.
   "^airoha,.*":
     description: Airoha
   "^al,.*":
-- 
2.34.1

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH v3 2/3] Bluetooth: dt-bindings: Add AIC8800D80
  2026-09-21  7:25   ` [RFC PATCH v3 0/3] Bluetooth: Add AIC8800D80 support Yanli Yang
  2026-09-21  7:25     ` [RFC PATCH v3 1/3] Bluetooth: dt-bindings: Add AIC vendor prefix Yanli Yang
@ 2026-09-21  7:25     ` Yanli Yang
  2026-09-21  7:34       ` sashiko-bot
  2026-09-21  7:25     ` [RFC PATCH v3 3/3] Bluetooth: btaic: Add AIC8800D80 support Yanli Yang
  2 siblings, 1 reply; 18+ messages in thread
From: Yanli Yang @ 2026-09-21  7:25 UTC (permalink / raw)
  To: linux-bluetooth
  Cc: marcel, devicetree, robh, krzk+dt, conor+dt, linux-kernel,
	zhirunliu, dijiaxu, chunqiuliu, liheng.wei, yanli.yang

Describe the AIC8800D80 Bluetooth SDIO firmware-loading function and
UART HCI interface. Link the UART node to its SDIO firmware provider
with the aic,firmware-sdio phandle.

Add the binding file to the AIC Bluetooth MAINTAINERS entry.

Signed-off-by: Zhirun Liu <zhirunliu@aicsemi.com>
Signed-off-by: Dijia Xu <dijiaxu@aicsemi.com>
Signed-off-by: Chunqiu Liu <chunqiuliu@aicsemi.com>
Signed-off-by: Liheng Wei <liheng.wei@bedmex.com>
Signed-off-by: Yanli Yang <yanli.yang@bedmex.com>
---
 .../net/bluetooth/aic,aic8800d80-bt.yaml      | 83 +++++++++++++++++++
 MAINTAINERS                                   | 10 +++
 2 files changed, 93 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml

diff --git a/Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml b/Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml
new file mode 100644
index 000000000000..9c04ba62ecef
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml
@@ -0,0 +1,83 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/bluetooth/aic,aic8800d80-bt.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: AIC AIC8800D80 Bluetooth
+
+maintainers:
+  - Zhirun Liu <zhirunliu@aicsemi.com>
+  - Dijia Xu <dijiaxu@aicsemi.com>
+  - Chunqiu Liu <chunqiuliu@aicsemi.com>
+  - Liheng Wei <liheng.wei@bedmex.com>
+  - Yanli Yang <yanli.yang@bedmex.com>
+
+description:
+  The AIC8800D80 is a Wi-Fi and Bluetooth combination chip. The Bluetooth
+  firmware is loaded through SDIO function 1, while Bluetooth HCI traffic uses
+  the H4 protocol over a UART interface with hardware flow control.
+
+properties:
+  compatible:
+    enum:
+      - aic,aic8800d80-bt
+      - aic,aic8800d80-bt-sdio
+
+  reg:
+    maxItems: 1
+
+  max-speed: false
+
+  aic,firmware-sdio:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description:
+      Phandle to the SDIO function used to load the Bluetooth firmware.
+
+required:
+  - compatible
+
+allOf:
+  - $ref: bluetooth-controller.yaml#
+  - if:
+      properties:
+        compatible:
+          const: aic,aic8800d80-bt-sdio
+    then:
+      properties:
+        reg:
+          items:
+            - const: 1
+        aic,firmware-sdio: false
+      required:
+        - reg
+    else:
+      properties:
+        reg: false
+      required:
+        - aic,firmware-sdio
+      allOf:
+        - $ref: /schemas/serial/serial-peripheral-props.yaml#
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    mmc {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        bt_sdio: bluetooth@1 {
+            compatible = "aic,aic8800d80-bt-sdio";
+            reg = <1>;
+        };
+    };
+
+    serial {
+        uart-has-rtscts;
+
+        bluetooth {
+            compatible = "aic,aic8800d80-bt";
+            aic,firmware-sdio = <&bt_sdio>;
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index af5a8b4b0bb5..20662fe78632 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -729,6 +729,16 @@ S:	Maintained
 F:	drivers/scsi/aha152x*
 F:	drivers/scsi/pcmcia/aha152x*
 
+AIC BLUETOOTH DRIVER
+M:	Zhirun Liu <zhirunliu@aicsemi.com>
+M:	Dijia Xu <dijiaxu@aicsemi.com>
+M:	Chunqiu Liu <chunqiuliu@aicsemi.com>
+M:	Liheng Wei <liheng.wei@bedmex.com>
+M:	Yanli Yang <yanli.yang@bedmex.com>
+L:	linux-bluetooth@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml
+
 AIC7XXX / AIC79XX SCSI DRIVER
 M:	Hannes Reinecke <hare@suse.com>
 L:	linux-scsi@vger.kernel.org
-- 
2.34.1

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH v3 3/3] Bluetooth: btaic: Add AIC8800D80 support
  2026-09-21  7:25   ` [RFC PATCH v3 0/3] Bluetooth: Add AIC8800D80 support Yanli Yang
  2026-09-21  7:25     ` [RFC PATCH v3 1/3] Bluetooth: dt-bindings: Add AIC vendor prefix Yanli Yang
  2026-09-21  7:25     ` [RFC PATCH v3 2/3] Bluetooth: dt-bindings: Add AIC8800D80 Yanli Yang
@ 2026-09-21  7:25     ` Yanli Yang
  2026-09-21  7:39       ` sashiko-bot
  2 siblings, 1 reply; 18+ messages in thread
From: Yanli Yang @ 2026-09-21  7:25 UTC (permalink / raw)
  To: linux-bluetooth
  Cc: marcel, devicetree, robh, krzk+dt, conor+dt, linux-kernel,
	zhirunliu, dijiaxu, chunqiuliu, liheng.wei, yanli.yang

Add a driver for the AIC8800D80 combination chip, using SDIO function 1
for Bluetooth firmware loading and a serdev UART for H4 HCI traffic.

Load the ADID image, patch image and patch table through
request_firmware(), validate the patch-table layout, and transfer firmware
using the vendor SDIO command protocol. Recognize the U02 and U03 chip
revisions.

Coordinate the SDIO firmware provider and UART consumer through a
reference-counted boot state and a device link. Wait for firmware readiness
before opening the UART, and use hardware flow control at 1500000 baud.

Add CONFIG_BT_AIC, build integration and the driver file pattern to the
AIC Bluetooth MAINTAINERS entry.

The vendor has confirmed SDIO firmware loading only. UART HCI operation
and other Bluetooth functionality have not yet been validated. Submit
this implementation as RFC for review before further functional testing.

Signed-off-by: Zhirun Liu <zhirunliu@aicsemi.com>
Signed-off-by: Dijia Xu <dijiaxu@aicsemi.com>
Signed-off-by: Chunqiu Liu <chunqiuliu@aicsemi.com>
Signed-off-by: Liheng Wei <liheng.wei@bedmex.com>
Signed-off-by: Yanli Yang <yanli.yang@bedmex.com>
---
 MAINTAINERS                    |   1 +
 drivers/bluetooth/Kconfig      |  16 +
 drivers/bluetooth/Makefile     |   3 +
 drivers/bluetooth/btaic.h      |  26 +
 drivers/bluetooth/btaic_core.c | 180 +++++++
 drivers/bluetooth/btaic_sdio.c | 901 +++++++++++++++++++++++++++++++++
 drivers/bluetooth/btaic_uart.c | 328 ++++++++++++
 7 files changed, 1455 insertions(+)
 create mode 100644 drivers/bluetooth/btaic.h
 create mode 100644 drivers/bluetooth/btaic_core.c
 create mode 100644 drivers/bluetooth/btaic_sdio.c
 create mode 100644 drivers/bluetooth/btaic_uart.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 20662fe78632..a5c6889fbccc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -738,6 +738,7 @@ M:	Yanli Yang <yanli.yang@bedmex.com>
 L:	linux-bluetooth@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml
+F:	drivers/bluetooth/btaic*
 
 AIC7XXX / AIC79XX SCSI DRIVER
 M:	Hannes Reinecke <hare@suse.com>
diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
index db0189ca3cbe..2b01f3838363 100644
--- a/drivers/bluetooth/Kconfig
+++ b/drivers/bluetooth/Kconfig
@@ -487,4 +487,20 @@ config BT_INTEL_PCIE
 
 	  Say Y here to compiler support for Intel Bluetooth PCIe device into
 	  the kernel or say M to compile it as module (btintel_pcie)
+
+config BT_AIC
+	tristate "AIC8800D80 Bluetooth support"
+	depends on MMC
+	depends on SERIAL_DEV_BUS
+	depends on BT_HCIUART
+	select BT_HCIUART_H4
+	select FW_LOADER
+	help
+	  Bluetooth support for the AIC8800D80 combination chip, using an
+	  SDIO function for firmware loading and a UART interface for the
+	  HCI transport.
+
+	  Say Y here to compile support into the kernel or M to build it as
+	  a module named btaic.
+
 endmenu
diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
index d5eb2568af2e..19abc5252676 100644
--- a/drivers/bluetooth/Makefile
+++ b/drivers/bluetooth/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_BT_MRVL)		+= btmrvl.o
 obj-$(CONFIG_BT_MRVL_SDIO)	+= btmrvl_sdio.o
 obj-$(CONFIG_BT_MTKSDIO)	+= btmtksdio.o
 obj-$(CONFIG_BT_MTKUART)	+= btmtkuart.o
+obj-$(CONFIG_BT_AIC)		+= btaic.o
 obj-$(CONFIG_BT_QCOMSMD)	+= btqcomsmd.o
 obj-$(CONFIG_BT_BCM)		+= btbcm.o
 obj-$(CONFIG_BT_RTL)		+= btrtl.o
@@ -55,3 +56,5 @@ btusb-y				:= btusb_main.o
 btusb-$(CONFIG_BT_HCIBTUSB_QCOM)	+= btusb_qcom.o
 
 CONTEXT_ANALYSIS := y
+
+btaic-y				:= btaic_core.o btaic_sdio.o btaic_uart.o
diff --git a/drivers/bluetooth/btaic.h b/drivers/bluetooth/btaic.h
new file mode 100644
index 000000000000..dc631b495697
--- /dev/null
+++ b/drivers/bluetooth/btaic.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __BTAIC_H
+#define __BTAIC_H
+
+#include <linux/types.h>
+
+struct device;
+struct fwnode_handle;
+
+struct aic_bt_boot;
+
+struct aic_bt_boot *aic_bt_boot_register(struct device *dev);
+void aic_bt_boot_ready(struct aic_bt_boot *boot, int status);
+void aic_bt_boot_unregister(struct aic_bt_boot *boot);
+
+struct aic_bt_boot *aic_bt_boot_get(const struct fwnode_handle *fwnode);
+void aic_bt_boot_put(struct aic_bt_boot *boot);
+int aic_bt_boot_wait(struct aic_bt_boot *boot, unsigned long timeout);
+struct device *aic_bt_boot_device(struct aic_bt_boot *boot);
+
+int aic_bt_sdio_register(void);
+void aic_bt_sdio_unregister(void);
+int aic_bt_uart_register(void);
+void aic_bt_uart_unregister(void);
+
+#endif
diff --git a/drivers/bluetooth/btaic_core.c b/drivers/bluetooth/btaic_core.c
new file mode 100644
index 000000000000..35644b9b1209
--- /dev/null
+++ b/drivers/bluetooth/btaic_core.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief AIC8800D80 Bluetooth driver core
+ *
+ ******************************************************************************
+ */
+
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+
+#include "btaic.h"
+
+struct aic_bt_boot {
+	struct kref ref;
+	struct list_head node;
+	struct completion ready;
+	struct device *dev;
+	int status;
+	bool present;
+};
+
+static DEFINE_MUTEX(aic_bt_boot_lock);
+static LIST_HEAD(aic_bt_boot_list);
+
+static void aic_bt_boot_release(struct kref *ref)
+{
+	struct aic_bt_boot *boot = container_of(ref, struct aic_bt_boot, ref);
+
+	put_device(boot->dev);
+	kfree(boot);
+}
+
+struct aic_bt_boot *aic_bt_boot_register(struct device *dev)
+{
+	struct aic_bt_boot *boot;
+
+	boot = kzalloc_obj(*boot);
+	if (!boot)
+		return ERR_PTR(-ENOMEM);
+
+	kref_init(&boot->ref);
+	INIT_LIST_HEAD(&boot->node);
+	init_completion(&boot->ready);
+	boot->dev = get_device(dev);
+	boot->status = -EINPROGRESS;
+	boot->present = true;
+
+	mutex_lock(&aic_bt_boot_lock);
+	list_add_tail(&boot->node, &aic_bt_boot_list);
+	mutex_unlock(&aic_bt_boot_lock);
+
+	return boot;
+}
+
+void aic_bt_boot_ready(struct aic_bt_boot *boot, int status)
+{
+	mutex_lock(&aic_bt_boot_lock);
+	if (boot->present)
+		boot->status = status;
+	mutex_unlock(&aic_bt_boot_lock);
+
+	complete_all(&boot->ready);
+}
+
+void aic_bt_boot_unregister(struct aic_bt_boot *boot)
+{
+	if (!boot)
+		return;
+
+	mutex_lock(&aic_bt_boot_lock);
+	if (boot->present) {
+		list_del_init(&boot->node);
+		boot->present = false;
+		boot->status = -ENODEV;
+	}
+	mutex_unlock(&aic_bt_boot_lock);
+
+	complete_all(&boot->ready);
+	kref_put(&boot->ref, aic_bt_boot_release);
+}
+
+struct aic_bt_boot *aic_bt_boot_get(const struct fwnode_handle *fwnode)
+{
+	struct aic_bt_boot *boot;
+	struct aic_bt_boot *found = NULL;
+	unsigned int count = 0;
+
+	mutex_lock(&aic_bt_boot_lock);
+	list_for_each_entry(boot, &aic_bt_boot_list, node) {
+		if (!boot->present)
+			continue;
+
+		if (fwnode) {
+			if (dev_fwnode(boot->dev) == fwnode) {
+				found = boot;
+				break;
+			}
+			continue;
+		}
+
+		found = boot;
+		count++;
+	}
+
+	if (found && (fwnode || count == 1))
+		kref_get(&found->ref);
+	else if (count > 1)
+		found = ERR_PTR(-EINVAL);
+	else
+		found = ERR_PTR(-EPROBE_DEFER);
+	mutex_unlock(&aic_bt_boot_lock);
+
+	return found;
+}
+
+void aic_bt_boot_put(struct aic_bt_boot *boot)
+{
+	if (!IS_ERR_OR_NULL(boot))
+		kref_put(&boot->ref, aic_bt_boot_release);
+}
+
+int aic_bt_boot_wait(struct aic_bt_boot *boot, unsigned long timeout)
+{
+	int status;
+
+	if (!wait_for_completion_timeout(&boot->ready, timeout))
+		return -ETIMEDOUT;
+
+	mutex_lock(&aic_bt_boot_lock);
+	status = boot->present ? boot->status : -ENODEV;
+	mutex_unlock(&aic_bt_boot_lock);
+
+	return status;
+}
+
+struct device *aic_bt_boot_device(struct aic_bt_boot *boot)
+{
+	return boot->dev;
+}
+
+static int __init aic_bt_init(void)
+{
+	int err;
+
+	err = aic_bt_sdio_register();
+	if (err)
+		return err;
+
+	err = aic_bt_uart_register();
+	if (err) {
+		aic_bt_sdio_unregister();
+		return err;
+	}
+
+	return 0;
+}
+
+static void __exit aic_bt_exit(void)
+{
+	aic_bt_uart_unregister();
+	aic_bt_sdio_unregister();
+}
+
+module_init(aic_bt_init);
+module_exit(aic_bt_exit);
+
+MODULE_AUTHOR("AIC Semiconductor");
+MODULE_DESCRIPTION("AIC8800D80 Bluetooth driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/bluetooth/btaic_sdio.c b/drivers/bluetooth/btaic_sdio.c
new file mode 100644
index 000000000000..18d58f0832a5
--- /dev/null
+++ b/drivers/bluetooth/btaic_sdio.c
@@ -0,0 +1,901 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief AIC8800D80 Bluetooth SDIO firmware loader
+ *
+ ******************************************************************************
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/minmax.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/sdio_ids.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/overflow.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/unaligned.h>
+
+#include "btaic.h"
+
+#define AIC_SDIO_VENDOR_ID			0xc8a1
+#define AIC_SDIO_DEVICE_ID_8800D80		0x0082
+
+#define AIC_SDIO_BLOCK_SIZE			512
+#define AIC_SDIO_BUFFER_SIZE			1536
+#define AIC_SDIO_TX_BUFFER_SIZE			1536
+#define AIC_SDIO_RX_MAX_SIZE			(127 * AIC_SDIO_BLOCK_SIZE)
+
+#define AIC_SDIO_INTR_ENABLE			0x00
+#define AIC_SDIO_INTR_PENDING			0x01
+#define AIC_SDIO_FLOW_CTRL_Q1			0x03
+#define AIC_SDIO_MISC_INT_STATUS		0x04
+#define AIC_SDIO_BYTEMODE_LEN			0x05
+#define AIC_SDIO_BYTEMODE_ENABLE		0x07
+#define AIC_SDIO_RD_FIFO			0x0f
+#define AIC_SDIO_WR_FIFO			0x10
+
+#define AIC_SDIO_OTHER_INTERRUPT		BIT(7)
+#define AIC_SDIO_BYTE_MODE_BLOCKS		120
+#define AIC_SDIO_FLOW_RETRIES			50
+#define AIC_SDIO_FRAME_TAIL_LEN			4
+
+#define AIC_CMD_TIMEOUT_MS			6000
+#define AIC_CMD_TYPE				0x11
+#define AIC_TASK_DBG				1
+#define AIC_DRIVER_TASK				100
+#define AIC_FIRST_MSG(task)			((u16)(task) << 10)
+
+#define AIC_DBG_MEM_READ_REQ			(AIC_FIRST_MSG(AIC_TASK_DBG) + 0)
+#define AIC_DBG_MEM_READ_CFM			(AIC_FIRST_MSG(AIC_TASK_DBG) + 1)
+#define AIC_DBG_MEM_WRITE_REQ			(AIC_FIRST_MSG(AIC_TASK_DBG) + 2)
+#define AIC_DBG_MEM_WRITE_CFM			(AIC_FIRST_MSG(AIC_TASK_DBG) + 3)
+#define AIC_DBG_MEM_BLOCK_WRITE_REQ		(AIC_FIRST_MSG(AIC_TASK_DBG) + 11)
+#define AIC_DBG_MEM_BLOCK_WRITE_CFM		(AIC_FIRST_MSG(AIC_TASK_DBG) + 12)
+
+#define AIC_BT_CHIP_ID_ADDR			0x40500000
+#define AIC_BT_CHIP_REV_U02			3
+#define AIC_BT_CHIP_REV_U03			7
+
+#define AIC_BT_FW_ADID				"aic/aic8800d80/fw_adid_8800d80_u02.bin"
+#define AIC_BT_FW_PATCH				"aic/aic8800d80/fw_patch_8800d80_u02.bin"
+#define AIC_BT_FW_TABLE				"aic/aic8800d80/fw_patch_table_8800d80_u02.bin"
+
+#define AIC_BT_PATCH_TAG			"AICBT_PT_TAG"
+#define AIC_BT_PATCH_TAG_SIZE			16
+#define AIC_BT_PATCH_RECORD_HEADER_SIZE		24
+#define AIC_BT_PATCH_PAIR_SIZE			8
+#define AIC_BT_PATCH_BLOCK_SIZE			1024
+
+#define AIC_BT_MODE_ONLY_COANT			5
+#define AIC_BT_PORT_UART			2
+#define AIC_BT_UART_BAUD			1500000
+#define AIC_BT_UART_FLOW_CTRL			1
+#define AIC_BT_LOW_POWER_ENABLE			1
+#define AIC_BT_TX_POWER_LEVEL			0x00006f2f
+
+enum aic_bt_patch_type {
+	AIC_BT_PATCH_INFO,
+	AIC_BT_PATCH_TRAP,
+	AIC_BT_PATCH_B4,
+	AIC_BT_PATCH_MODE,
+	AIC_BT_PATCH_POWER_ON,
+	AIC_BT_PATCH_AF,
+	AIC_BT_PATCH_VERSION,
+};
+
+struct aic_bt_e2a_header {
+	__le16 id;
+	__le16 dest_id;
+	__le16 src_id;
+	__le16 param_len;
+	__le32 pattern;
+	u8 param[];
+} __packed;
+
+struct aic_bt_mem_read_cfm {
+	__le32 address;
+	__le32 value;
+} __packed;
+
+struct aic_bt_sdio {
+	struct sdio_func *func;
+	struct aic_bt_boot *boot;
+
+	/* Serializes commands because the firmware accepts one at a time. */
+	struct mutex command_mutex;
+	/* Protects response state shared with the SDIO IRQ handler. */
+	spinlock_t response_lock;
+	struct completion command_done;
+	u16 expected_response;
+	void *response;
+	size_t response_size;
+	int command_result;
+	bool waiting_response;
+	/* The firmware protocol has no transaction sequence number. */
+	bool command_timed_out;
+
+	u8 *tx_buf;
+	u8 *rx_buf;
+	bool function_enabled;
+	bool irq_claimed;
+};
+
+static u8 aic_bt_crc8(const u8 *buffer, size_t len)
+{
+	u8 crc = 0;
+	size_t byte;
+	int bit;
+
+	for (byte = 0; byte < len; byte++) {
+		for (bit = 0x80; bit; bit >>= 1) {
+			if (crc & 0x80)
+				crc = (crc << 1) ^ 0x07;
+			else
+				crc <<= 1;
+
+			if (buffer[byte] & bit)
+				crc ^= 0x07;
+		}
+	}
+
+	return crc;
+}
+
+static void aic_bt_complete_response(struct aic_bt_sdio *btdev,
+				     const struct aic_bt_e2a_header *message,
+				     size_t message_len)
+{
+	unsigned long flags;
+	size_t param_len;
+	u16 id;
+
+	if (message_len < sizeof(*message))
+		return;
+
+	id = get_unaligned_le16(&message->id);
+	param_len = get_unaligned_le16(&message->param_len);
+	if (param_len > message_len - sizeof(*message))
+		return;
+
+	spin_lock_irqsave(&btdev->response_lock, flags);
+	if (!btdev->waiting_response || id != btdev->expected_response)
+		goto unlock;
+
+	if (btdev->response && param_len < btdev->response_size) {
+		btdev->command_result = -EMSGSIZE;
+	} else {
+		if (btdev->response)
+			memcpy(btdev->response, message->param,
+			       btdev->response_size);
+		btdev->command_result = 0;
+	}
+
+	btdev->waiting_response = false;
+	/*
+	 * Serialize completion with command setup.  In particular, do not
+	 * allow a timed out command's completion to race a following command's
+	 * reinit_completion().
+	 */
+	complete(&btdev->command_done);
+
+unlock:
+	spin_unlock_irqrestore(&btdev->response_lock, flags);
+}
+
+static void aic_bt_parse_rx(struct aic_bt_sdio *btdev, const u8 *data,
+			    size_t data_len)
+{
+	size_t offset = 0;
+
+	while (data_len - offset >= 4) {
+		const struct aic_bt_e2a_header *message;
+		size_t frame_len;
+		size_t frame_size;
+
+		frame_len = get_unaligned_le16(data + offset);
+		if (!frame_len)
+			break;
+
+		if (check_add_overflow(frame_len, (size_t)4, &frame_size) ||
+		    frame_size > data_len - offset)
+			break;
+
+		if ((data[offset + 2] & 0x7f) == AIC_CMD_TYPE) {
+			message = (const void *)(data + offset + 4);
+			aic_bt_complete_response(btdev, message, frame_len);
+		}
+
+		frame_size = roundup(frame_len, 4) + 4;
+		if (frame_size > data_len - offset)
+			break;
+		offset += frame_size;
+	}
+}
+
+static void aic_bt_sdio_irq(struct sdio_func *func)
+{
+	struct aic_bt_sdio *btdev = sdio_get_drvdata(func);
+	u8 *data;
+	u8 blocks;
+	u8 status;
+	size_t data_len;
+	int err;
+
+	status = sdio_readb(func, AIC_SDIO_MISC_INT_STATUS, &err);
+	if (err) {
+		dev_err_ratelimited(&func->dev,
+				    "failed to read interrupt status: %d\n", err);
+		return;
+	}
+
+	if (status & AIC_SDIO_OTHER_INTERRUPT) {
+		u8 pending;
+
+		pending = sdio_readb(func, AIC_SDIO_INTR_PENDING, &err);
+		if (!err) {
+			pending &= ~BIT(0);
+			sdio_writeb(func, pending, AIC_SDIO_INTR_PENDING, &err);
+		}
+		if (err)
+			dev_err_ratelimited(&func->dev,
+					    "failed to clear soft interrupt: %d\n",
+					    err);
+	}
+
+	blocks = status & 0x7f;
+	if (!blocks)
+		return;
+
+	if (blocks == AIC_SDIO_BYTE_MODE_BLOCKS) {
+		u8 words;
+
+		words = sdio_readb(func, AIC_SDIO_BYTEMODE_LEN, &err);
+		if (err)
+			return;
+		data_len = (size_t)words * 4;
+	} else {
+		data_len = (size_t)blocks * AIC_SDIO_BLOCK_SIZE;
+	}
+
+	if (!data_len || data_len > AIC_SDIO_RX_MAX_SIZE) {
+		dev_err_ratelimited(&func->dev,
+				    "invalid SDIO response length %zu\n", data_len);
+		return;
+	}
+
+	data = btdev->rx_buf;
+	err = sdio_readsb(func, data, AIC_SDIO_RD_FIFO, data_len);
+	if (err)
+		dev_err_ratelimited(&func->dev,
+				    "failed to read response: %d\n", err);
+	else
+		aic_bt_parse_rx(btdev, data, data_len);
+}
+
+static int aic_bt_wait_for_credits(struct aic_bt_sdio *btdev, size_t tx_len)
+{
+	unsigned int retry;
+	int err;
+
+	for (retry = 0; retry < AIC_SDIO_FLOW_RETRIES; retry++) {
+		u8 credits;
+
+		credits = sdio_readb(btdev->func, AIC_SDIO_FLOW_CTRL_Q1, &err);
+		if (err)
+			return err;
+
+		if (credits && tx_len <= (size_t)credits * AIC_SDIO_BUFFER_SIZE)
+			return 0;
+
+		if (retry < 30)
+			usleep_range(30, 50);
+		else if (retry < 40)
+			usleep_range(1000, 1500);
+		else
+			usleep_range(10000, 12000);
+	}
+
+	return -ETIMEDOUT;
+}
+
+static int aic_bt_command(struct aic_bt_sdio *btdev, u16 request_id,
+			  const void *param, size_t param_len, u16 response_id,
+			  void *response, size_t response_size)
+{
+	unsigned long flags;
+	size_t frame_len;
+	size_t message_len;
+	size_t tx_len;
+	long timeout;
+	int err;
+
+	if (param_len > U16_MAX)
+		return -EMSGSIZE;
+
+	message_len = 8 + param_len;
+	frame_len = 8 + message_len;
+	if (frame_len > AIC_SDIO_TX_BUFFER_SIZE)
+		return -EMSGSIZE;
+
+	if (IS_ALIGNED(frame_len, AIC_SDIO_BLOCK_SIZE))
+		tx_len = frame_len;
+	else
+		tx_len = roundup(frame_len + AIC_SDIO_FRAME_TAIL_LEN,
+				 AIC_SDIO_BLOCK_SIZE);
+
+	if (tx_len > AIC_SDIO_TX_BUFFER_SIZE)
+		return -EMSGSIZE;
+
+	mutex_lock(&btdev->command_mutex);
+	spin_lock_irqsave(&btdev->response_lock, flags);
+	if (btdev->command_timed_out) {
+		spin_unlock_irqrestore(&btdev->response_lock, flags);
+		err = -ETIMEDOUT;
+		goto unlock_command;
+	}
+	spin_unlock_irqrestore(&btdev->response_lock, flags);
+
+	memset(btdev->tx_buf, 0, tx_len);
+	put_unaligned_le16(message_len + 4, btdev->tx_buf);
+	btdev->tx_buf[2] = AIC_CMD_TYPE;
+	btdev->tx_buf[3] = aic_bt_crc8(btdev->tx_buf, 3);
+
+	put_unaligned_le16(request_id, btdev->tx_buf + 8);
+	put_unaligned_le16(AIC_TASK_DBG, btdev->tx_buf + 10);
+	put_unaligned_le16(AIC_DRIVER_TASK, btdev->tx_buf + 12);
+	put_unaligned_le16(param_len, btdev->tx_buf + 14);
+	if (param_len)
+		memcpy(btdev->tx_buf + 16, param, param_len);
+
+	spin_lock_irqsave(&btdev->response_lock, flags);
+	reinit_completion(&btdev->command_done);
+	btdev->expected_response = response_id;
+	btdev->response = response;
+	btdev->response_size = response_size;
+	btdev->command_result = -EINPROGRESS;
+	btdev->waiting_response = true;
+	spin_unlock_irqrestore(&btdev->response_lock, flags);
+
+	sdio_claim_host(btdev->func);
+	err = aic_bt_wait_for_credits(btdev, tx_len);
+	if (!err)
+		err = sdio_writesb(btdev->func, AIC_SDIO_WR_FIFO,
+				   btdev->tx_buf, tx_len);
+	sdio_release_host(btdev->func);
+	if (err)
+		goto clear_response;
+
+	timeout = wait_for_completion_timeout(&btdev->command_done,
+					      msecs_to_jiffies(AIC_CMD_TIMEOUT_MS));
+	if (!timeout) {
+		dev_err(&btdev->func->dev,
+			"command 0x%04x timed out waiting for 0x%04x\n",
+			request_id, response_id);
+		err = -ETIMEDOUT;
+		spin_lock_irqsave(&btdev->response_lock, flags);
+		btdev->command_timed_out = true;
+		spin_unlock_irqrestore(&btdev->response_lock, flags);
+		goto clear_response;
+	}
+
+	spin_lock_irqsave(&btdev->response_lock, flags);
+	err = btdev->command_result;
+	btdev->response = NULL;
+	btdev->response_size = 0;
+	spin_unlock_irqrestore(&btdev->response_lock, flags);
+	mutex_unlock(&btdev->command_mutex);
+
+	return err;
+
+clear_response:
+	spin_lock_irqsave(&btdev->response_lock, flags);
+	btdev->waiting_response = false;
+	btdev->response = NULL;
+	btdev->response_size = 0;
+	spin_unlock_irqrestore(&btdev->response_lock, flags);
+unlock_command:
+	mutex_unlock(&btdev->command_mutex);
+
+	return err;
+}
+
+static int aic_bt_mem_read(struct aic_bt_sdio *btdev, u32 address, u32 *value)
+{
+	struct aic_bt_mem_read_cfm cfm;
+	__le32 request = cpu_to_le32(address);
+	int err;
+
+	err = aic_bt_command(btdev, AIC_DBG_MEM_READ_REQ,
+			     &request, sizeof(request), AIC_DBG_MEM_READ_CFM,
+			     &cfm, sizeof(cfm));
+	if (!err)
+		*value = le32_to_cpu(cfm.value);
+
+	return err;
+}
+
+static int aic_bt_mem_write(struct aic_bt_sdio *btdev, u32 address, u32 value)
+{
+	__le32 request[2] = {
+		cpu_to_le32(address),
+		cpu_to_le32(value),
+	};
+
+	return aic_bt_command(btdev, AIC_DBG_MEM_WRITE_REQ,
+			      request, sizeof(request), AIC_DBG_MEM_WRITE_CFM,
+			      NULL, 0);
+}
+
+static int aic_bt_mem_block_write(struct aic_bt_sdio *btdev, u32 address,
+				  const u8 *data, size_t data_len)
+{
+	__le32 status;
+	u8 *request;
+	int err;
+
+	if (data_len > AIC_BT_PATCH_BLOCK_SIZE)
+		return -EINVAL;
+
+	request = kzalloc(8 + AIC_BT_PATCH_BLOCK_SIZE, GFP_KERNEL);
+	if (!request)
+		return -ENOMEM;
+
+	put_unaligned_le32(address, request);
+	put_unaligned_le32(data_len, request + 4);
+	memcpy(request + 8, data, data_len);
+
+	err = aic_bt_command(btdev, AIC_DBG_MEM_BLOCK_WRITE_REQ,
+			     request, 8 + AIC_BT_PATCH_BLOCK_SIZE,
+			     AIC_DBG_MEM_BLOCK_WRITE_CFM,
+			     &status, sizeof(status));
+	kfree(request);
+	if (err)
+		return err;
+
+	if (le32_to_cpu(status)) {
+		dev_err(&btdev->func->dev,
+			"firmware rejected block write at 0x%08x\n", address);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int aic_bt_upload_firmware(struct aic_bt_sdio *btdev, const char *name,
+				  u32 address)
+{
+	const struct firmware *firmware;
+	size_t offset = 0;
+	int err;
+
+	err = request_firmware(&firmware, name, &btdev->func->dev);
+	if (err)
+		return dev_err_probe(&btdev->func->dev, err,
+				     "failed to load %s\n", name);
+
+	if (firmware->size > U32_MAX - address) {
+		err = -EFBIG;
+		goto release;
+	}
+
+	while (offset < firmware->size) {
+		size_t len = min_t(size_t, AIC_BT_PATCH_BLOCK_SIZE,
+				   firmware->size - offset);
+
+		err = aic_bt_mem_block_write(btdev, address + offset,
+					     firmware->data + offset, len);
+		if (err)
+			goto release;
+		offset += len;
+	}
+
+	dev_dbg(&btdev->func->dev, "loaded %s (%zu bytes) at 0x%08x\n",
+		name, firmware->size, address);
+
+release:
+	release_firmware(firmware);
+	return err;
+}
+
+static u32 aic_bt_mode_value(unsigned int pair, u32 firmware_value)
+{
+	switch (pair) {
+	case 0:
+		return 1;
+	case 1:
+		return U32_MAX;
+	case 2:
+		return 0;
+	case 3:
+		return AIC_BT_MODE_ONLY_COANT;
+	case 4:
+		return AIC_BT_PORT_UART;
+	case 5:
+		return AIC_BT_UART_BAUD;
+	case 6:
+		return AIC_BT_UART_FLOW_CTRL;
+	case 7:
+		return AIC_BT_LOW_POWER_ENABLE;
+	case 8:
+		return AIC_BT_TX_POWER_LEVEL;
+	default:
+		return firmware_value;
+	}
+}
+
+static int aic_bt_process_patch_table(struct aic_bt_sdio *btdev,
+				      const struct firmware *firmware,
+				      bool apply, u32 *adid_addr,
+				      u32 *patch_addr)
+{
+	const u8 *data = firmware->data;
+	size_t offset = AIC_BT_PATCH_TAG_SIZE;
+	bool have_record = false;
+	bool have_info = false;
+
+	if (firmware->size < AIC_BT_PATCH_TAG_SIZE ||
+	    memcmp(data, AIC_BT_PATCH_TAG, sizeof(AIC_BT_PATCH_TAG)))
+		return -EBADMSG;
+
+	while (offset < firmware->size) {
+		const u8 *record;
+		const u8 *pairs_data;
+		size_t pairs_size;
+		unsigned int pair;
+		u32 pair_count;
+		u32 type;
+		int err;
+
+		if (firmware->size - offset <
+		    AIC_BT_PATCH_RECORD_HEADER_SIZE)
+			return -EBADMSG;
+
+		record = data + offset;
+		type = get_unaligned_le32(record + 16);
+		pair_count = get_unaligned_le32(record + 20);
+		offset += AIC_BT_PATCH_RECORD_HEADER_SIZE;
+
+		if (type >= 1000) {
+			pairs_size = 0;
+			pair_count = 0;
+		} else if (check_mul_overflow((size_t)pair_count,
+					      (size_t)AIC_BT_PATCH_PAIR_SIZE,
+					      &pairs_size)) {
+			return -EOVERFLOW;
+		}
+
+		if (pairs_size > firmware->size - offset)
+			return -EBADMSG;
+
+		pairs_data = data + offset;
+		have_record = true;
+
+		if (type == AIC_BT_PATCH_INFO) {
+			if (pair_count < 2)
+				return -EBADMSG;
+
+			if (!have_info) {
+				if (adid_addr)
+					*adid_addr = get_unaligned_le32(pairs_data + 4);
+				if (patch_addr)
+					*patch_addr = get_unaligned_le32(pairs_data + 12);
+				have_info = true;
+			}
+		}
+
+		if (!apply)
+			goto next_record;
+
+		if (type == AIC_BT_PATCH_VERSION) {
+			dev_info(&btdev->func->dev, "BT patch version: %.*s\n",
+				 (int)min_t(size_t, pairs_size, 80),
+				 pairs_data);
+			goto next_record;
+		}
+
+		if (type == AIC_BT_PATCH_MODE && pair_count < 9)
+			return -EBADMSG;
+
+		for (pair = 0; pair < pair_count; pair++) {
+			u32 address;
+			u32 value;
+
+			address = get_unaligned_le32(pairs_data +
+						    pair * AIC_BT_PATCH_PAIR_SIZE);
+			value = get_unaligned_le32(pairs_data +
+						  pair * AIC_BT_PATCH_PAIR_SIZE + 4);
+			if (type == AIC_BT_PATCH_MODE)
+				value = aic_bt_mode_value(pair, value);
+
+			err = aic_bt_mem_write(btdev, address, value);
+			if (err)
+				return err;
+		}
+
+		if (type == AIC_BT_PATCH_POWER_ON)
+			usleep_range(50, 100);
+
+next_record:
+		offset += pairs_size;
+	}
+
+	if (!have_record || ((adid_addr || patch_addr) && !have_info))
+		return -EBADMSG;
+
+	return 0;
+}
+
+static int aic_bt_download_firmware(struct aic_bt_sdio *btdev)
+{
+	const struct firmware *table;
+	u32 chip_id;
+	u32 adid_addr;
+	u32 patch_addr;
+	u8 revision;
+	int err;
+
+	err = aic_bt_mem_read(btdev, AIC_BT_CHIP_ID_ADDR, &chip_id);
+	if (err)
+		return err;
+
+	revision = (chip_id >> 16) & 0x3f;
+	if (revision != AIC_BT_CHIP_REV_U02 &&
+	    revision != AIC_BT_CHIP_REV_U03) {
+		dev_err(&btdev->func->dev,
+			"unsupported AIC8800D80 revision %u\n", revision);
+		return -ENODEV;
+	}
+
+	err = request_firmware(&table, AIC_BT_FW_TABLE, &btdev->func->dev);
+	if (err)
+		return dev_err_probe(&btdev->func->dev, err,
+				     "failed to load %s\n", AIC_BT_FW_TABLE);
+
+	err = aic_bt_process_patch_table(btdev, table, false,
+					 &adid_addr, &patch_addr);
+	if (err) {
+		dev_err(&btdev->func->dev, "invalid BT patch table: %d\n", err);
+		goto release_table;
+	}
+
+	err = aic_bt_upload_firmware(btdev, AIC_BT_FW_ADID, adid_addr);
+	if (err)
+		goto release_table;
+
+	err = aic_bt_upload_firmware(btdev, AIC_BT_FW_PATCH, patch_addr);
+	if (err)
+		goto release_table;
+
+	err = aic_bt_process_patch_table(btdev, table, true, NULL, NULL);
+	if (!err)
+		dev_info(&btdev->func->dev,
+			 "AIC8800D80 revision %u Bluetooth firmware ready\n",
+			 revision);
+
+release_table:
+	release_firmware(table);
+	return err;
+}
+
+static int aic_bt_sdio_hw_init(struct aic_bt_sdio *btdev)
+{
+	struct sdio_func *func = btdev->func;
+	struct mmc_host *host = func->card->host;
+	u8 io_control;
+	int err;
+
+	sdio_claim_host(func);
+	func->card->quirks |= MMC_QUIRK_LENIENT_FN0;
+
+	err = sdio_set_block_size(func, AIC_SDIO_BLOCK_SIZE);
+	if (err)
+		goto release_host;
+
+	err = sdio_enable_func(func);
+	if (err)
+		goto release_host;
+	btdev->function_enabled = true;
+
+	sdio_f0_writeb(func, 0x7f, 0xf2, &err);
+	if (err)
+		goto disable_func;
+
+	io_control = host->ios.timing == MMC_TIMING_UHS_DDR50 ? 0x20 : 0x00;
+	io_control |= BIT(6);
+	sdio_f0_writeb(func, io_control, 0xf0, &err);
+	if (err)
+		goto disable_func;
+
+	sdio_f0_writeb(func, 0x00, 0xf8, &err);
+	if (err)
+		goto disable_func;
+
+	sdio_f0_writeb(func, 0x00, 0xf1, &err);
+	if (err)
+		goto disable_func;
+
+	sdio_writeb(func, 1, AIC_SDIO_BYTEMODE_ENABLE, &err);
+	if (err)
+		goto disable_func;
+
+	err = sdio_claim_irq(func, aic_bt_sdio_irq);
+	if (err)
+		goto disable_func;
+	btdev->irq_claimed = true;
+
+	sdio_f0_writeb(func, 0x07, 0x04, &err);
+	if (err)
+		goto release_irq;
+
+	sdio_writeb(func, 0x07, AIC_SDIO_INTR_ENABLE, &err);
+	if (err)
+		goto release_irq;
+
+	sdio_release_host(func);
+	return 0;
+
+release_irq:
+	sdio_release_irq(func);
+	btdev->irq_claimed = false;
+disable_func:
+	sdio_disable_func(func);
+	btdev->function_enabled = false;
+release_host:
+	sdio_release_host(func);
+	return err;
+}
+
+static void aic_bt_sdio_hw_deinit(struct aic_bt_sdio *btdev)
+{
+	sdio_claim_host(btdev->func);
+
+	if (btdev->irq_claimed) {
+		int err;
+
+		sdio_writeb(btdev->func, 0, AIC_SDIO_INTR_ENABLE, &err);
+		sdio_release_irq(btdev->func);
+		btdev->irq_claimed = false;
+	}
+
+	if (btdev->function_enabled) {
+		sdio_disable_func(btdev->func);
+		btdev->function_enabled = false;
+	}
+
+	sdio_release_host(btdev->func);
+}
+
+static int aic_bt_sdio_probe(struct sdio_func *func,
+			     const struct sdio_device_id *id)
+{
+	struct aic_bt_sdio *btdev;
+	int err;
+
+	if (func->num != 1)
+		return -ENODEV;
+
+	btdev = devm_kzalloc(&func->dev, sizeof(*btdev), GFP_KERNEL);
+	if (!btdev)
+		return -ENOMEM;
+
+	btdev->tx_buf = devm_kmalloc(&func->dev, AIC_SDIO_TX_BUFFER_SIZE,
+				     GFP_KERNEL);
+	if (!btdev->tx_buf)
+		return -ENOMEM;
+
+	btdev->rx_buf = devm_kmalloc(&func->dev, AIC_SDIO_RX_MAX_SIZE,
+				     GFP_KERNEL);
+	if (!btdev->rx_buf)
+		return -ENOMEM;
+
+	btdev->func = func;
+	mutex_init(&btdev->command_mutex);
+	spin_lock_init(&btdev->response_lock);
+	init_completion(&btdev->command_done);
+	sdio_set_drvdata(func, btdev);
+
+	err = aic_bt_sdio_hw_init(btdev);
+	if (err)
+		goto clear_drvdata;
+
+	btdev->boot = aic_bt_boot_register(&func->dev);
+	if (IS_ERR(btdev->boot)) {
+		err = PTR_ERR(btdev->boot);
+		btdev->boot = NULL;
+		goto deinit_hw;
+	}
+
+	err = aic_bt_download_firmware(btdev);
+	aic_bt_boot_ready(btdev->boot, err);
+	if (err)
+		goto unregister_boot;
+
+	return 0;
+
+unregister_boot:
+	aic_bt_boot_unregister(btdev->boot);
+	btdev->boot = NULL;
+deinit_hw:
+	aic_bt_sdio_hw_deinit(btdev);
+clear_drvdata:
+	sdio_set_drvdata(func, NULL);
+	return err;
+}
+
+static void aic_bt_sdio_remove(struct sdio_func *func)
+{
+	struct aic_bt_sdio *btdev = sdio_get_drvdata(func);
+
+	aic_bt_boot_unregister(btdev->boot);
+	btdev->boot = NULL;
+	aic_bt_sdio_hw_deinit(btdev);
+	sdio_set_drvdata(func, NULL);
+}
+
+static int aic_bt_sdio_suspend(struct device *dev)
+{
+	struct sdio_func *func = dev_to_sdio_func(dev);
+	mmc_pm_flag_t caps;
+
+	caps = sdio_get_host_pm_caps(func);
+	if (!(caps & MMC_PM_KEEP_POWER))
+		return -EOPNOTSUPP;
+
+	return sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
+}
+
+static int aic_bt_sdio_resume(struct device *dev)
+{
+	return 0;
+}
+
+static const struct dev_pm_ops aic_bt_sdio_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(aic_bt_sdio_suspend, aic_bt_sdio_resume)
+};
+
+static const struct sdio_device_id aic_bt_sdio_ids[] = {
+	{ SDIO_DEVICE(AIC_SDIO_VENDOR_ID, AIC_SDIO_DEVICE_ID_8800D80) },
+	{ }
+};
+MODULE_DEVICE_TABLE(sdio, aic_bt_sdio_ids);
+
+static const struct of_device_id aic_bt_sdio_of_match[] = {
+	{ .compatible = "aic,aic8800d80-bt-sdio" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, aic_bt_sdio_of_match);
+
+static struct sdio_driver aic_bt_sdio_driver = {
+	.name = "aic_bt_sdio",
+	.id_table = aic_bt_sdio_ids,
+	.probe = aic_bt_sdio_probe,
+	.remove = aic_bt_sdio_remove,
+	.drv = {
+		.of_match_table = aic_bt_sdio_of_match,
+		.pm = &aic_bt_sdio_pm_ops,
+	},
+};
+
+int aic_bt_sdio_register(void)
+{
+	return sdio_register_driver(&aic_bt_sdio_driver);
+}
+
+void aic_bt_sdio_unregister(void)
+{
+	sdio_unregister_driver(&aic_bt_sdio_driver);
+}
+
+MODULE_FIRMWARE(AIC_BT_FW_ADID);
+MODULE_FIRMWARE(AIC_BT_FW_PATCH);
+MODULE_FIRMWARE(AIC_BT_FW_TABLE);
diff --git a/drivers/bluetooth/btaic_uart.c b/drivers/bluetooth/btaic_uart.c
new file mode 100644
index 000000000000..224ec8fef864
--- /dev/null
+++ b/drivers/bluetooth/btaic_uart.c
@@ -0,0 +1,328 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ ******************************************************************************
+ *
+ * Copyright (C) 2020 AIC semiconductor.
+ *
+ * @brief AIC8800D80 Bluetooth UART transport
+ *
+ ******************************************************************************
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/property.h>
+#include <linux/serdev.h>
+#include <linux/skbuff.h>
+#include <linux/workqueue.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+#include "btaic.h"
+#include "hci_uart.h"
+
+#define AIC_BT_UART_DEFAULT_SPEED	1500000
+#define AIC_BT_BOOT_TIMEOUT_MS		10000
+
+#define AIC_BT_TX_ACTIVE		0
+#define AIC_BT_TX_WAKEUP		1
+
+struct aic_bt_uart {
+	struct serdev_device *serdev;
+	struct hci_dev *hdev;
+	struct hci_uart hu;
+	struct aic_bt_boot *boot;
+
+	struct sk_buff *rx_skb;
+	struct sk_buff_head txq;
+	struct work_struct tx_work;
+	unsigned long tx_state;
+};
+
+static const struct h4_recv_pkt aic_bt_recv_pkts[] = {
+	{ H4_RECV_ACL,   .recv = hci_recv_frame },
+	{ H4_RECV_SCO,   .recv = hci_recv_frame },
+	{ H4_RECV_EVENT, .recv = hci_recv_frame },
+	{ H4_RECV_ISO,   .recv = hci_recv_frame },
+};
+
+static void aic_bt_tx_work(struct work_struct *work)
+{
+	struct aic_bt_uart *uart = container_of(work, struct aic_bt_uart,
+						 tx_work);
+
+	for (;;) {
+		struct sk_buff *skb;
+
+		clear_bit(AIC_BT_TX_WAKEUP, &uart->tx_state);
+
+		while ((skb = skb_dequeue(&uart->txq))) {
+			int len;
+
+			len = serdev_device_write_buf(uart->serdev, skb->data,
+						      skb->len);
+			if (len <= 0) {
+				skb_queue_head(&uart->txq, skb);
+				if (len < 0)
+					bt_dev_err(uart->hdev,
+						   "UART transmit failed (%d)", len);
+				break;
+			}
+
+			uart->hdev->stat.byte_tx += len;
+			skb_pull(skb, len);
+			if (skb->len) {
+				skb_queue_head(&uart->txq, skb);
+				break;
+			}
+
+			switch (hci_skb_pkt_type(skb)) {
+			case HCI_COMMAND_PKT:
+				uart->hdev->stat.cmd_tx++;
+				break;
+			case HCI_ACLDATA_PKT:
+				uart->hdev->stat.acl_tx++;
+				break;
+			case HCI_SCODATA_PKT:
+				uart->hdev->stat.sco_tx++;
+				break;
+			}
+
+			kfree_skb(skb);
+		}
+
+		if (!test_bit(AIC_BT_TX_WAKEUP, &uart->tx_state))
+			break;
+	}
+
+	clear_bit(AIC_BT_TX_ACTIVE, &uart->tx_state);
+}
+
+static void aic_bt_tx_wakeup(struct aic_bt_uart *uart)
+{
+	if (test_and_set_bit(AIC_BT_TX_ACTIVE, &uart->tx_state))
+		set_bit(AIC_BT_TX_WAKEUP, &uart->tx_state);
+
+	schedule_work(&uart->tx_work);
+}
+
+static size_t aic_bt_receive_buf(struct serdev_device *serdev,
+				 const u8 *data, size_t count)
+{
+	struct aic_bt_uart *uart = serdev_device_get_drvdata(serdev);
+
+	uart->rx_skb = h4_recv_buf(&uart->hu, uart->rx_skb, data, count,
+				   aic_bt_recv_pkts,
+				   ARRAY_SIZE(aic_bt_recv_pkts));
+	if (IS_ERR(uart->rx_skb)) {
+		bt_dev_err(uart->hdev, "Frame reassembly failed (%ld)",
+			   PTR_ERR(uart->rx_skb));
+		uart->rx_skb = NULL;
+	}
+
+	uart->hdev->stat.byte_rx += count;
+	return count;
+}
+
+static void aic_bt_write_wakeup(struct serdev_device *serdev)
+{
+	struct aic_bt_uart *uart = serdev_device_get_drvdata(serdev);
+
+	aic_bt_tx_wakeup(uart);
+}
+
+static const struct serdev_device_ops aic_bt_serdev_ops = {
+	.receive_buf = aic_bt_receive_buf,
+	.write_wakeup = aic_bt_write_wakeup,
+};
+
+static int aic_bt_open(struct hci_dev *hdev)
+{
+	struct aic_bt_uart *uart = hci_get_drvdata(hdev);
+	unsigned int actual_speed;
+	int err;
+
+	err = aic_bt_boot_wait(uart->boot,
+			       msecs_to_jiffies(AIC_BT_BOOT_TIMEOUT_MS));
+	if (err) {
+		bt_dev_err(hdev, "Bluetooth firmware is not ready (%d)", err);
+		return err;
+	}
+
+	err = serdev_device_open(uart->serdev);
+	if (err)
+		return err;
+
+	actual_speed = serdev_device_set_baudrate(uart->serdev, AIC_BT_UART_DEFAULT_SPEED);
+	if (!actual_speed) {
+		serdev_device_close(uart->serdev);
+		return -EIO;
+	}
+
+	serdev_device_set_flow_control(uart->serdev, true);
+	return 0;
+}
+
+static int aic_bt_close(struct hci_dev *hdev)
+{
+	struct aic_bt_uart *uart = hci_get_drvdata(hdev);
+
+	serdev_device_close(uart->serdev);
+	/* serdev_device_close() stops receive callbacks before freeing rx_skb. */
+	kfree_skb(uart->rx_skb);
+	uart->rx_skb = NULL;
+	return 0;
+}
+
+static int aic_bt_flush(struct hci_dev *hdev)
+{
+	struct aic_bt_uart *uart = hci_get_drvdata(hdev);
+
+	serdev_device_write_flush(uart->serdev);
+	cancel_work_sync(&uart->tx_work);
+	skb_queue_purge(&uart->txq);
+	clear_bit(AIC_BT_TX_ACTIVE, &uart->tx_state);
+	clear_bit(AIC_BT_TX_WAKEUP, &uart->tx_state);
+
+	return 0;
+}
+
+static int aic_bt_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
+{
+	struct aic_bt_uart *uart = hci_get_drvdata(hdev);
+	u8 pkt_type = hci_skb_pkt_type(skb);
+	int err;
+
+	err = skb_cow_head(skb, 1);
+	if (err)
+		return err;
+
+	memcpy(skb_push(skb, 1), &pkt_type, sizeof(pkt_type));
+	skb_queue_tail(&uart->txq, skb);
+	aic_bt_tx_wakeup(uart);
+
+	return 0;
+}
+
+static void aic_bt_boot_put_action(void *data)
+{
+	aic_bt_boot_put(data);
+}
+
+static int aic_bt_find_boot_provider(struct device *dev,
+				     struct aic_bt_uart *uart)
+{
+	struct fwnode_handle *fwnode = NULL;
+	int err;
+
+	if (device_property_present(dev, "aic,firmware-sdio")) {
+		fwnode = fwnode_find_reference(dev_fwnode(dev),
+					       "aic,firmware-sdio", 0);
+		if (IS_ERR(fwnode))
+			return PTR_ERR(fwnode);
+	}
+
+	uart->boot = aic_bt_boot_get(fwnode);
+	fwnode_handle_put(fwnode);
+	if (IS_ERR(uart->boot))
+		return dev_err_probe(dev, PTR_ERR(uart->boot),
+				     "failed to find Bluetooth SDIO firmware loader\n");
+
+	err = devm_add_action_or_reset(dev, aic_bt_boot_put_action,
+				       uart->boot);
+	if (err)
+		return err;
+
+	if (!device_link_add(dev, aic_bt_boot_device(uart->boot),
+			     DL_FLAG_AUTOREMOVE_CONSUMER))
+		return -EINVAL;
+
+	return 0;
+}
+
+static int aic_bt_uart_probe(struct serdev_device *serdev)
+{
+	struct device *dev = &serdev->dev;
+	struct aic_bt_uart *uart;
+	struct hci_dev *hdev;
+	int err;
+
+	uart = devm_kzalloc(dev, sizeof(*uart), GFP_KERNEL);
+	if (!uart)
+		return -ENOMEM;
+
+	uart->serdev = serdev;
+	serdev_device_set_drvdata(serdev, uart);
+	serdev_device_set_client_ops(serdev, &aic_bt_serdev_ops);
+
+	err = aic_bt_find_boot_provider(dev, uart);
+	if (err)
+		return err;
+
+	INIT_WORK(&uart->tx_work, aic_bt_tx_work);
+	skb_queue_head_init(&uart->txq);
+
+	hdev = hci_alloc_dev();
+	if (!hdev)
+		return -ENOMEM;
+
+	uart->hdev = hdev;
+	uart->hu.hdev = hdev;
+	hdev->bus = HCI_UART;
+	hci_set_drvdata(hdev, uart);
+	SET_HCIDEV_DEV(hdev, dev);
+
+	hdev->open = aic_bt_open;
+	hdev->close = aic_bt_close;
+	hdev->flush = aic_bt_flush;
+	hdev->send = aic_bt_send_frame;
+
+	err = hci_register_dev(hdev);
+	if (err) {
+		hci_free_dev(hdev);
+		return err;
+	}
+
+	return 0;
+}
+
+static void aic_bt_uart_remove(struct serdev_device *serdev)
+{
+	struct aic_bt_uart *uart = serdev_device_get_drvdata(serdev);
+
+	hci_unregister_dev(uart->hdev);
+	cancel_work_sync(&uart->tx_work);
+	skb_queue_purge(&uart->txq);
+	kfree_skb(uart->rx_skb);
+	uart->rx_skb = NULL;
+	hci_free_dev(uart->hdev);
+}
+
+static const struct of_device_id aic_bt_uart_of_match[] = {
+	{ .compatible = "aic,aic8800d80-bt" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, aic_bt_uart_of_match);
+
+static struct serdev_device_driver aic_bt_uart_driver = {
+	.probe = aic_bt_uart_probe,
+	.remove = aic_bt_uart_remove,
+	.driver = {
+		.name = "bt_aic_uart",
+		.of_match_table = aic_bt_uart_of_match,
+	},
+};
+
+int aic_bt_uart_register(void)
+{
+	return serdev_device_driver_register(&aic_bt_uart_driver);
+}
+
+void aic_bt_uart_unregister(void)
+{
+	serdev_device_driver_unregister(&aic_bt_uart_driver);
+}
-- 
2.34.1

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH v3 2/3] Bluetooth: dt-bindings: Add AIC8800D80
  2026-09-21  7:25     ` [RFC PATCH v3 2/3] Bluetooth: dt-bindings: Add AIC8800D80 Yanli Yang
@ 2026-09-21  7:34       ` sashiko-bot
  0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-09-21  7:34 UTC (permalink / raw)
  To: Yanli Yang; +Cc: devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] dt-bindings: unsatisfiable constraint on reg property causes validation failure
--

commit c2a5ac7736c6234041d0e5fd1d5e6d9742fc0653
Author: Yanli Yang <yanli.yang@bedmex.com>

Bluetooth: dt-bindings: Add AIC8800D80

This commit adds the device tree binding for the AIC8800D80 Bluetooth SDIO
and UART HCI interface. It describes the firmware-loading function and links
the UART node to the SDIO firmware provider. It also updates the MAINTAINERS
file.

> diff --git a/Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml b/Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml
> new file mode 100644
> index 0000000000000..9c04ba62ecefd
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/bluetooth/aic,aic8800d80-bt.yaml

[ ... ]

> +allOf:
> +  - $ref: bluetooth-controller.yaml#
> +  - if:
> +      properties:
> +        compatible:
> +          const: aic,aic8800d80-bt-sdio
> +    then:
> +      properties:
> +        reg:
> +          items:
> +            - const: 1

[Severity: Low]
Does this constraint on the reg property cause a validation failure?

In dt-schema, the reg property is always decoded as a matrix (an array of
arrays), even for single-cell values. For example, reg = <1> in the provided
example is parsed internally as [[1]].

Because JSON Schema evaluates this by checking if the first element of the
outer array exactly matches the literal integer 1, this constraint is
impossible to satisfy and causes make dt_binding_check to unconditionally
fail.

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1789974593.git.yanli.yang@bedmex.com?part=2

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH v3 3/3] Bluetooth: btaic: Add AIC8800D80 support
  2026-09-21  7:25     ` [RFC PATCH v3 3/3] Bluetooth: btaic: Add AIC8800D80 support Yanli Yang
@ 2026-09-21  7:39       ` sashiko-bot
  0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-09-21  7:39 UTC (permalink / raw)
  To: Yanli Yang; +Cc: robh, conor+dt, devicetree

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] bt: aic: struct layout mismatch breaks firmware loading
- [High] bt: aic: synchronous request_firmware in probe deadlocks SDIO bus
- [High] bt: aic: type mismatch in h4_recv_buf() causes out-of-bounds access
- [Medium] bt: aic: rx_skb memory leak and state corruption on open failure
- [Medium] bt: aic: memory leak of skb on skb_cow_head failure
- [Low] bt: aic: build failure due to undefined kzalloc_obj macro
--

commit 0701d7f81641131610e34be895b933ef2ec43de9
Author: Yanli Yang <yanli.yang@bedmex.com>

Bluetooth: btaic: Add AIC8800D80 support

Add a driver for the AIC8800D80 combination chip, using SDIO function 1
for Bluetooth firmware loading and a serdev UART for H4 HCI traffic.

> diff --git a/drivers/bluetooth/btaic_core.c b/drivers/bluetooth/btaic_core.c
> --- /dev/null
> +++ b/drivers/bluetooth/btaic_core.c
> @@ -0,0 +1,180 @@

[ ... ]

> +struct aic_bt_boot *aic_bt_boot_register(struct device *dev)
> +{
> +	struct aic_bt_boot *boot;
> +
> +	boot = kzalloc_obj(*boot);
> +	if (!boot)
> +		return ERR_PTR(-ENOMEM);

[Severity: Low]
Is kzalloc_obj() defined in the kernel?

In aic_bt_boot_register(), the code attempts to allocate memory using
kzalloc_obj(). This macro is not a standard Linux kernel macro and does not
appear to be defined anywhere in the kernel source or driver headers. Will
this cause a compilation failure?

> diff --git a/drivers/bluetooth/btaic_sdio.c b/drivers/bluetooth/btaic_sdio.c
> --- /dev/null
> +++ b/drivers/bluetooth/btaic_sdio.c
> @@ -0,0 +1,901 @@

[ ... ]

> +struct aic_bt_e2a_header {
> +	__le16 id;
> +	__le16 dest_id;
> +	__le16 src_id;
> +	__le16 param_len;
> +	__le32 pattern;
> +	u8 param[];
> +} __packed;

[Severity: High]
Does this struct layout match the hardware format constructed during TX?

The driver manually constructs SDIO TX messages with a 4-byte zero padding
(pattern) starting at offset 4 of the buffer, followed by request_id at
offset 8, dest_id at 10, etc., in aic_bt_command():

	put_unaligned_le16(request_id, btdev->tx_buf + 8);
	put_unaligned_le16(AIC_TASK_DBG, btdev->tx_buf + 10);
	put_unaligned_le16(AIC_DRIVER_TASK, btdev->tx_buf + 12);
	put_unaligned_le16(param_len, btdev->tx_buf + 14);
	if (param_len)
		memcpy(btdev->tx_buf + 16, param, param_len);

However, the RX parser receives the payload starting at offset 4 and casts
it to struct aic_bt_e2a_header. This struct defines __le16 id at offset 0
and __le32 pattern at offset 8, which reverses the TX layout.

Due to this layout mismatch, message->id reads the zero padding instead of
the ID, and message->param_len reads the dest_id. Does this cause all
received responses to fail validation in aic_bt_complete_response() and
prevent firmware loading?

[ ... ]

> +static int aic_bt_download_firmware(struct aic_bt_sdio *btdev)
> +{
> +	const struct firmware *table;
> +	u32 chip_id;
> +	u32 adid_addr;
> +	u32 patch_addr;
> +	u8 revision;
> +	int err;
> +
> +	err = aic_bt_mem_read(btdev, AIC_BT_CHIP_ID_ADDR, &chip_id);
> +	if (err)
> +		return err;
> +
> +	revision = (chip_id >> 16) & 0x3f;
> +	if (revision != AIC_BT_CHIP_REV_U02 &&
> +	    revision != AIC_BT_CHIP_REV_U03) {
> +		dev_err(&btdev->func->dev,
> +			"unsupported AIC8800D80 revision %u\n", revision);
> +		return -ENODEV;
> +	}
> +
> +	err = request_firmware(&table, AIC_BT_FW_TABLE, &btdev->func->dev);

[Severity: High]
Can calling request_firmware() synchronously from the SDIO probe path
deadlock the bus?

The SDIO probe function aic_bt_sdio_probe() calls aic_bt_download_firmware(),
which initiates synchronous request_firmware() calls.

SDIO probes execute in the context of the MMC host worker thread. Blocking
the MMC host worker thread prevents the MMC subsystem from servicing other
requests.

If the requested firmware resides on a filesystem mounted via the same MMC
host, this could create an unresolvable deadlock. Even if it does not, a
missing firmware file might block the MMC bus for 60 seconds waiting for
the user-mode helper timeout.

> diff --git a/drivers/bluetooth/btaic_uart.c b/drivers/bluetooth/btaic_uart.c
> --- /dev/null
> +++ b/drivers/bluetooth/btaic_uart.c
> @@ -0,0 +1,328 @@

[ ... ]

> +static size_t aic_bt_receive_buf(struct serdev_device *serdev,
> +				 const u8 *data, size_t count)
> +{
> +	struct aic_bt_uart *uart = serdev_device_get_drvdata(serdev);
> +
> +	uart->rx_skb = h4_recv_buf(&uart->hu, uart->rx_skb, data, count,
> +				   aic_bt_recv_pkts,
> +				   ARRAY_SIZE(aic_bt_recv_pkts));

[Severity: High]
Does this pass the correct pointer type to h4_recv_buf()?

The generic Bluetooth core function h4_recv_buf() requires a
struct hci_dev *hdev as its first parameter. However, aic_bt_receive_buf()
passes &uart->hu, which is a pointer to struct hci_uart.

If h4_recv_buf() encounters a framing error or an internal issue, it logs
the error using bt_dev_err(hdev, ...). Passing the incorrect struct pointer
could cause the generic core to read out-of-bounds memory when evaluating
hdev->name, potentially leading to garbage logs, memory corruption, or
kernel panics.

[ ... ]

> +static int aic_bt_open(struct hci_dev *hdev)
> +{
> +	struct aic_bt_uart *uart = hci_get_drvdata(hdev);
> +	unsigned int actual_speed;
> +	int err;
> +
> +	err = aic_bt_boot_wait(uart->boot,
> +			       msecs_to_jiffies(AIC_BT_BOOT_TIMEOUT_MS));
> +	if (err) {
> +		bt_dev_err(hdev, "Bluetooth firmware is not ready (%d)", err);
> +		return err;
> +	}
> +
> +	err = serdev_device_open(uart->serdev);
> +	if (err)
> +		return err;
> +
> +	actual_speed = serdev_device_set_baudrate(uart->serdev, AIC_BT_UART_DEFAULT_SPEED);
> +	if (!actual_speed) {
> +		serdev_device_close(uart->serdev);
> +		return -EIO;
> +	}

[Severity: Medium]
Does this error path leak uart->rx_skb?

In aic_bt_open(), the serdev is opened before the baudrate is set. RX data
may arrive immediately and allocate uart->rx_skb via the RX callback.

If setting the baudrate fails, the function returns -EIO and closes the
serdev, but uart->rx_skb is not freed or set to NULL on this error path.

Since the HCI core considers the device unopened on failure, close() is never
called. If the interface is subsequently brought up again, the stale rx_skb
will corrupt the next received packet sequence, and the original allocation is
leaked.

[ ... ]

> +static int aic_bt_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
> +{
> +	struct aic_bt_uart *uart = hci_get_drvdata(hdev);
> +	u8 pkt_type = hci_skb_pkt_type(skb);
> +	int err;
> +
> +	err = skb_cow_head(skb, 1);
> +	if (err)
> +		return err;

[Severity: Medium]
Should this error path free the skb?

The Bluetooth HCI core transfers ownership of the skb to the driver's
hdev->send() callback. If the driver's send callback drops the packet (even
if it returns an error), it must free the skb.

In aic_bt_send_frame(), if skb_cow_head() fails, the function returns -ENOMEM
but does not invoke kfree_skb(skb). Could this silently drop the reference
and create a memory leak?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1789974593.git.yanli.yang@bedmex.com?part=3

^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2026-09-21  7:39 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-16  8:12 [RFC PATCH bluetooth-next 0/3] Bluetooth: Add AIC8800D80 SDIO firmware loader and UART HCI Yanli Yang
2026-09-16  8:12 ` [RFC PATCH bluetooth-next 1/3] dt-bindings: vendor-prefixes: Add AIC Semiconductor Yanli Yang
2026-09-16  8:12 ` [RFC PATCH bluetooth-next 2/3] dt-bindings: net: bluetooth: Add AIC8800D80 Yanli Yang
2026-09-16  8:12 ` [RFC PATCH bluetooth-next 3/3] Bluetooth: btaic: Add AIC8800D80 SDIO loader and UART transport Yanli Yang
2026-09-17  2:33   ` sashiko-bot
2026-09-20  9:50 ` [RFC PATCH v2 0/3] Bluetooth: Add AIC8800D80 SDIO firmware loader and UART HCI Yanli Yang
2026-09-20  9:50   ` [RFC PATCH v2 1/3] dt-bindings: vendor-prefixes: Add AIC Semiconductor Yanli Yang
2026-09-20  9:50   ` [RFC PATCH v2 2/3] dt-bindings: net: bluetooth: Add AIC8800D80 Yanli Yang
2026-09-20 10:01     ` sashiko-bot
2026-09-20  9:50   ` [RFC PATCH v2 3/3] Bluetooth: btaic: Add AIC8800D80 SDIO loader and UART transport Yanli Yang
2026-09-21  7:25   ` [RFC PATCH v3 0/3] Bluetooth: Add AIC8800D80 support Yanli Yang
2026-09-21  7:25     ` [RFC PATCH v3 1/3] Bluetooth: dt-bindings: Add AIC vendor prefix Yanli Yang
2026-09-21  7:25     ` [RFC PATCH v3 2/3] Bluetooth: dt-bindings: Add AIC8800D80 Yanli Yang
2026-09-21  7:34       ` sashiko-bot
2026-09-21  7:25     ` [RFC PATCH v3 3/3] Bluetooth: btaic: Add AIC8800D80 support Yanli Yang
2026-09-21  7:39       ` sashiko-bot
  -- strict thread matches above, loose matches on Subject: below --
2026-09-16  8:35 [RFC PATCH bluetooth-next 0/3] Bluetooth: Add AIC8800D80 SDIO firmware loader and UART HCI Yanli Yang
2026-09-16  8:35 ` [RFC PATCH bluetooth-next 3/3] Bluetooth: btaic: Add AIC8800D80 SDIO loader and UART transport Yanli Yang
2026-09-17  2:43   ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox