diff options
Diffstat (limited to 'drivers/bluetooth')
-rw-r--r-- | drivers/bluetooth/Kconfig | 14 | ||||
-rw-r--r-- | drivers/bluetooth/Makefile | 1 | ||||
-rw-r--r-- | drivers/bluetooth/btbcm.c | 49 | ||||
-rw-r--r-- | drivers/bluetooth/btintel.c | 77 | ||||
-rw-r--r-- | drivers/bluetooth/btintel.h | 12 | ||||
-rw-r--r-- | drivers/bluetooth/btmrvl_sdio.c | 2 | ||||
-rw-r--r-- | drivers/bluetooth/btmtkuart.c | 6 | ||||
-rw-r--r-- | drivers/bluetooth/btnxpuart.c | 1352 | ||||
-rw-r--r-- | drivers/bluetooth/btqca.c | 14 | ||||
-rw-r--r-- | drivers/bluetooth/btqca.h | 10 | ||||
-rw-r--r-- | drivers/bluetooth/btrtl.c | 502 | ||||
-rw-r--r-- | drivers/bluetooth/btrtl.h | 58 | ||||
-rw-r--r-- | drivers/bluetooth/btsdio.c | 2 | ||||
-rw-r--r-- | drivers/bluetooth/btusb.c | 318 | ||||
-rw-r--r-- | drivers/bluetooth/hci_bcm.c | 60 | ||||
-rw-r--r-- | drivers/bluetooth/hci_h5.c | 6 | ||||
-rw-r--r-- | drivers/bluetooth/hci_ldisc.c | 8 | ||||
-rw-r--r-- | drivers/bluetooth/hci_ll.c | 2 | ||||
-rw-r--r-- | drivers/bluetooth/hci_mrvl.c | 90 | ||||
-rw-r--r-- | drivers/bluetooth/hci_qca.c | 67 | ||||
-rw-r--r-- | drivers/bluetooth/hci_vhci.c | 101 |
21 files changed, 2629 insertions, 122 deletions
diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig index 5a1a7bec3c42..bc211c324206 100644 --- a/drivers/bluetooth/Kconfig +++ b/drivers/bluetooth/Kconfig @@ -363,6 +363,7 @@ config BT_HCIBLUECARD config BT_HCIVHCI tristate "HCI VHCI (Virtual HCI device) driver" + select WANT_DEV_COREDUMP help Bluetooth Virtual HCI device driver. This driver is required if you want to use HCI Emulation software. @@ -465,4 +466,17 @@ config BT_VIRTIO Say Y here to compile support for HCI over Virtio into the kernel or say M to compile as a module. +config BT_NXPUART + tristate "NXP protocol support" + depends on SERIAL_DEV_BUS + select CRC32 + select CRC8 + help + NXP is serial driver required for NXP Bluetooth + devices with UART interface. + + Say Y here to compile support for NXP Bluetooth UART device into + the kernel, or say M here to compile as a module (btnxpuart). + + endmenu diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile index e0b261f24fc9..7a5967e9ac48 100644 --- a/drivers/bluetooth/Makefile +++ b/drivers/bluetooth/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_BT_QCA) += btqca.o obj-$(CONFIG_BT_MTK) += btmtk.o obj-$(CONFIG_BT_VIRTIO) += virtio_bt.o +obj-$(CONFIG_BT_NXPUART) += btnxpuart.o obj-$(CONFIG_BT_HCIUART_NOKIA) += hci_nokia.o diff --git a/drivers/bluetooth/btbcm.c b/drivers/bluetooth/btbcm.c index 3006e2a0f37e..de2ea589aa49 100644 --- a/drivers/bluetooth/btbcm.c +++ b/drivers/bluetooth/btbcm.c @@ -6,6 +6,7 @@ * Copyright (C) 2015 Intel Corporation */ +#include <linux/efi.h> #include <linux/module.h> #include <linux/firmware.h> #include <linux/dmi.h> @@ -34,6 +35,43 @@ /* For kmalloc-ing the fw-name array instead of putting it on the stack */ typedef char bcm_fw_name[BCM_FW_NAME_LEN]; +#ifdef CONFIG_EFI +static int btbcm_set_bdaddr_from_efi(struct hci_dev *hdev) +{ + efi_guid_t guid = EFI_GUID(0x74b00bd9, 0x805a, 0x4d61, 0xb5, 0x1f, + 0x43, 0x26, 0x81, 0x23, 0xd1, 0x13); + bdaddr_t efi_bdaddr, bdaddr; + efi_status_t status; + unsigned long len; + int ret; + + if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) + return -EOPNOTSUPP; + + len = sizeof(efi_bdaddr); + status = efi.get_variable(L"BDADDR", &guid, NULL, &len, &efi_bdaddr); + if (status != EFI_SUCCESS) + return -ENXIO; + + if (len != sizeof(efi_bdaddr)) + return -EIO; + + baswap(&bdaddr, &efi_bdaddr); + + ret = btbcm_set_bdaddr(hdev, &bdaddr); + if (ret) + return ret; + + bt_dev_info(hdev, "BCM: Using EFI device address (%pMR)", &bdaddr); + return 0; +} +#else +static int btbcm_set_bdaddr_from_efi(struct hci_dev *hdev) +{ + return -EOPNOTSUPP; +} +#endif + int btbcm_check_bdaddr(struct hci_dev *hdev) { struct hci_rp_read_bd_addr *bda; @@ -87,9 +125,12 @@ int btbcm_check_bdaddr(struct hci_dev *hdev) !bacmp(&bda->bdaddr, BDADDR_BCM4345C5) || !bacmp(&bda->bdaddr, BDADDR_BCM43430A0) || !bacmp(&bda->bdaddr, BDADDR_BCM43341B)) { - bt_dev_info(hdev, "BCM: Using default device address (%pMR)", - &bda->bdaddr); - set_bit(HCI_QUIRK_INVALID_BDADDR, &hdev->quirks); + /* Try falling back to BDADDR EFI variable */ + if (btbcm_set_bdaddr_from_efi(hdev) != 0) { + bt_dev_info(hdev, "BCM: Using default device address (%pMR)", + &bda->bdaddr); + set_bit(HCI_QUIRK_INVALID_BDADDR, &hdev->quirks); + } } kfree_skb(skb); @@ -511,7 +552,7 @@ static const char *btbcm_get_board_name(struct device *dev) len = strlen(tmp) + 1; board_type = devm_kzalloc(dev, len, GFP_KERNEL); strscpy(board_type, tmp, len); - for (i = 0; i < board_type[i]; i++) { + for (i = 0; i < len; i++) { if (board_type[i] == '/') board_type[i] = '-'; } diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index af774688f1c0..d9349ba48281 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -43,6 +43,12 @@ struct cmd_write_boot_params { u8 fw_build_yy; } __packed; +static struct { + const char *driver_name; + u8 hw_variant; + u32 fw_build_num; +} coredump_info; + int btintel_check_bdaddr(struct hci_dev *hdev) { struct hci_rp_read_bd_addr *bda; @@ -315,6 +321,9 @@ int btintel_version_info(struct hci_dev *hdev, struct intel_version *ver) return -EINVAL; } + coredump_info.hw_variant = ver->hw_variant; + coredump_info.fw_build_num = ver->fw_build_num; + bt_dev_info(hdev, "%s revision %u.%u build %u week %u %u", variant, ver->fw_revision >> 4, ver->fw_revision & 0x0f, ver->fw_build_num, ver->fw_build_ww, @@ -509,6 +518,9 @@ static int btintel_version_info_tlv(struct hci_dev *hdev, return -EINVAL; } + coredump_info.hw_variant = INTEL_HW_VARIANT(version->cnvi_bt); + coredump_info.fw_build_num = version->build_num; + bt_dev_info(hdev, "%s timestamp %u.%u buildtype %u build %u", variant, 2000 + (version->timestamp >> 8), version->timestamp & 0xff, version->build_type, version->build_num); @@ -1462,6 +1474,59 @@ int btintel_set_quality_report(struct hci_dev *hdev, bool enable) } EXPORT_SYMBOL_GPL(btintel_set_quality_report); +static void btintel_coredump(struct hci_dev *hdev) +{ + struct sk_buff *skb; + + skb = __hci_cmd_sync(hdev, 0xfc4e, 0, NULL, HCI_CMD_TIMEOUT); + if (IS_ERR(skb)) { + bt_dev_err(hdev, "Coredump failed (%ld)", PTR_ERR(skb)); + return; + } + + kfree_skb(skb); +} + +static void btintel_dmp_hdr(struct hci_dev *hdev, struct sk_buff *skb) +{ + char buf[80]; + + snprintf(buf, sizeof(buf), "Controller Name: 0x%X\n", + coredump_info.hw_variant); + skb_put_data(skb, buf, strlen(buf)); + + snprintf(buf, sizeof(buf), "Firmware Version: 0x%X\n", + coredump_info.fw_build_num); + skb_put_data(skb, buf, strlen(buf)); + + snprintf(buf, sizeof(buf), "Driver: %s\n", coredump_info.driver_name); + skb_put_data(skb, buf, strlen(buf)); + + snprintf(buf, sizeof(buf), "Vendor: Intel\n"); + skb_put_data(skb, buf, strlen(buf)); +} + +static int btintel_register_devcoredump_support(struct hci_dev *hdev) +{ + struct intel_debug_features features; + int err; + + err = btintel_read_debug_features(hdev, &features); + if (err) { + bt_dev_info(hdev, "Error reading debug features"); + return err; + } + + if (!(features.page1[0] & 0x3f)) { + bt_dev_dbg(hdev, "Telemetry exception format not supported"); + return -EOPNOTSUPP; + } + + hci_devcd_register(hdev, btintel_coredump, btintel_dmp_hdr, NULL); + + return err; +} + static const struct firmware *btintel_legacy_rom_get_fw(struct hci_dev *hdev, struct intel_version *ver) { @@ -2597,6 +2662,7 @@ static int btintel_setup_combined(struct hci_dev *hdev) btintel_set_msft_opcode(hdev, ver.hw_variant); err = btintel_bootloader_setup(hdev, &ver); + btintel_register_devcoredump_support(hdev); break; default: bt_dev_err(hdev, "Unsupported Intel hw variant (%u)", @@ -2670,6 +2736,7 @@ static int btintel_setup_combined(struct hci_dev *hdev) btintel_set_msft_opcode(hdev, ver.hw_variant); err = btintel_bootloader_setup(hdev, &ver); + btintel_register_devcoredump_support(hdev); break; case 0x17: case 0x18: @@ -2684,15 +2751,15 @@ static int btintel_setup_combined(struct hci_dev *hdev) */ set_bit(HCI_QUIRK_WIDEBAND_SPEECH_SUPPORTED, &hdev->quirks); - /* Valid LE States quirk for GfP */ - if (INTEL_HW_VARIANT(ver_tlv.cnvi_bt) == 0x18) - set_bit(HCI_QUIRK_VALID_LE_STATES, &hdev->quirks); + /* Apply LE States quirk from solar onwards */ + set_bit(HCI_QUIRK_VALID_LE_STATES, &hdev->quirks); /* Setup MSFT Extension support */ btintel_set_msft_opcode(hdev, INTEL_HW_VARIANT(ver_tlv.cnvi_bt)); err = btintel_bootloader_setup_tlv(hdev, &ver_tlv); + btintel_register_devcoredump_support(hdev); break; default: bt_dev_err(hdev, "Unsupported Intel hw variant (%u)", @@ -2742,7 +2809,7 @@ static int btintel_shutdown_combined(struct hci_dev *hdev) return 0; } -int btintel_configure_setup(struct hci_dev *hdev) +int btintel_configure_setup(struct hci_dev *hdev, const char *driver_name) { hdev->manufacturer = 2; hdev->setup = btintel_setup_combined; @@ -2751,6 +2818,8 @@ int btintel_configure_setup(struct hci_dev *hdev) hdev->set_diag = btintel_set_diag_combined; hdev->set_bdaddr = btintel_set_bdaddr; + coredump_info.driver_name = driver_name; + return 0; } EXPORT_SYMBOL_GPL(btintel_configure_setup); diff --git a/drivers/bluetooth/btintel.h b/drivers/bluetooth/btintel.h index 8fdb65b66315..d6a1dc8d8a82 100644 --- a/drivers/bluetooth/btintel.h +++ b/drivers/bluetooth/btintel.h @@ -143,6 +143,13 @@ struct btintel_loc_aware_reg { __le32 delta; } __packed; +#define INTEL_TLV_TYPE_ID 0x01 + +#define INTEL_TLV_SYSTEM_EXCEPTION 0x00 +#define INTEL_TLV_FATAL_EXCEPTION 0x01 +#define INTEL_TLV_DEBUG_EXCEPTION 0x02 +#define INTEL_TLV_TEST_EXCEPTION 0xDE + #define INTEL_HW_PLATFORM(cnvx_bt) ((u8)(((cnvx_bt) & 0x0000ff00) >> 8)) #define INTEL_HW_VARIANT(cnvx_bt) ((u8)(((cnvx_bt) & 0x003f0000) >> 16)) #define INTEL_CNVX_TOP_TYPE(cnvx_top) ((cnvx_top) & 0x00000fff) @@ -212,7 +219,7 @@ int btintel_read_boot_params(struct hci_dev *hdev, struct intel_boot_params *params); int btintel_download_firmware(struct hci_dev *dev, struct intel_version *ver, const struct firmware *fw, u32 *boot_param); -int btintel_configure_setup(struct hci_dev *hdev); +int btintel_configure_setup(struct hci_dev *hdev, const char *driver_name); void btintel_bootup(struct hci_dev *hdev, const void *ptr, unsigned int len); void btintel_secure_send_result(struct hci_dev *hdev, const void *ptr, unsigned int len); @@ -293,7 +300,8 @@ static inline int btintel_download_firmware(struct hci_dev *dev, return -EOPNOTSUPP; } -static inline int btintel_configure_setup(struct hci_dev *hdev) +static inline int btintel_configure_setup(struct hci_dev *hdev, + const char *driver_name) { return -ENODEV; } diff --git a/drivers/bluetooth/btmrvl_sdio.c b/drivers/bluetooth/btmrvl_sdio.c index ba057ebfda5c..d76c799553aa 100644 --- a/drivers/bluetooth/btmrvl_sdio.c +++ b/drivers/bluetooth/btmrvl_sdio.c @@ -40,7 +40,7 @@ static struct memory_type_mapping mem_type_mapping_tbl[] = { {"EXTLAST", NULL, 0, 0xFE}, }; -static const struct of_device_id btmrvl_sdio_of_match_table[] = { +static const struct of_device_id btmrvl_sdio_of_match_table[] __maybe_unused = { { .compatible = "marvell,sd8897-bt" }, { .compatible = "marvell,sd8997-bt" }, { } diff --git a/drivers/bluetooth/btmtkuart.c b/drivers/bluetooth/btmtkuart.c index c98691cdbbd5..7680c67cdb35 100644 --- a/drivers/bluetooth/btmtkuart.c +++ b/drivers/bluetooth/btmtkuart.c @@ -959,16 +959,16 @@ static void btmtkuart_remove(struct serdev_device *serdev) hci_free_dev(hdev); } -static const struct btmtkuart_data mt7622_data = { +static const struct btmtkuart_data mt7622_data __maybe_unused = { .fwname = FIRMWARE_MT7622, }; -static const struct btmtkuart_data mt7663_data = { +static const struct btmtkuart_data mt7663_data __maybe_unused = { .flags = BTMTKUART_FLAG_STANDALONE_HW, .fwname = FIRMWARE_MT7663, }; -static const struct btmtkuart_data mt7668_data = { +static const struct btmtkuart_data mt7668_data __maybe_unused = { .flags = BTMTKUART_FLAG_STANDALONE_HW, .fwname = FIRMWARE_MT7668, }; diff --git a/drivers/bluetooth/btnxpuart.c b/drivers/bluetooth/btnxpuart.c new file mode 100644 index 000000000000..3a34d7c1475b --- /dev/null +++ b/drivers/bluetooth/btnxpuart.c @@ -0,0 +1,1352 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NXP Bluetooth driver + * Copyright 2023 NXP + */ + +#include <linux/module.h> +#include <linux/kernel.h> + +#include <linux/serdev.h> +#include <linux/of.h> +#include <linux/skbuff.h> +#include <asm/unaligned.h> +#include <linux/firmware.h> +#include <linux/string.h> +#include <linux/crc8.h> +#include <linux/crc32.h> +#include <linux/string_helpers.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#include "h4_recv.h" + +#define MANUFACTURER_NXP 37 + +#define BTNXPUART_TX_STATE_ACTIVE 1 +#define BTNXPUART_FW_DOWNLOADING 2 +#define BTNXPUART_CHECK_BOOT_SIGNATURE 3 +#define BTNXPUART_SERDEV_OPEN 4 + +#define FIRMWARE_W8987 "nxp/uartuart8987_bt.bin" +#define FIRMWARE_W8997 "nxp/uartuart8997_bt_v4.bin" +#define FIRMWARE_W9098 "nxp/uartuart9098_bt_v1.bin" +#define FIRMWARE_IW416 "nxp/uartiw416_bt_v0.bin" +#define FIRMWARE_IW612 "nxp/uartspi_n61x_v1.bin.se" +#define FIRMWARE_HELPER "nxp/helper_uart_3000000.bin" + +#define CHIP_ID_W9098 0x5c03 +#define CHIP_ID_IW416 0x7201 +#define CHIP_ID_IW612 0x7601 + +#define HCI_NXP_PRI_BAUDRATE 115200 +#define HCI_NXP_SEC_BAUDRATE 3000000 + +#define MAX_FW_FILE_NAME_LEN 50 + +/* Default ps timeout period in milliseconds */ +#define PS_DEFAULT_TIMEOUT_PERIOD_MS 2000 + +/* wakeup methods */ +#define WAKEUP_METHOD_DTR 0 +#define WAKEUP_METHOD_BREAK 1 +#define WAKEUP_METHOD_EXT_BREAK 2 +#define WAKEUP_METHOD_RTS 3 +#define WAKEUP_METHOD_INVALID 0xff + +/* power save mode status */ +#define PS_MODE_DISABLE 0 +#define PS_MODE_ENABLE 1 + +/* Power Save Commands to ps_work_func */ +#define PS_CMD_EXIT_PS 1 +#define PS_CMD_ENTER_PS 2 + +/* power save state */ +#define PS_STATE_AWAKE 0 +#define PS_STATE_SLEEP 1 + +/* Bluetooth vendor command : Sleep mode */ +#define HCI_NXP_AUTO_SLEEP_MODE 0xfc23 +/* Bluetooth vendor command : Wakeup method */ +#define HCI_NXP_WAKEUP_METHOD 0xfc53 +/* Bluetooth vendor command : Set operational baudrate */ +#define HCI_NXP_SET_OPER_SPEED 0xfc09 +/* Bluetooth vendor command: Independent Reset */ +#define HCI_NXP_IND_RESET 0xfcfc + +/* Bluetooth Power State : Vendor cmd params */ +#define BT_PS_ENABLE 0x02 +#define BT_PS_DISABLE 0x03 + +/* Bluetooth Host Wakeup Methods */ +#define BT_HOST_WAKEUP_METHOD_NONE 0x00 +#define BT_HOST_WAKEUP_METHOD_DTR 0x01 +#define BT_HOST_WAKEUP_METHOD_BREAK 0x02 +#define BT_HOST_WAKEUP_METHOD_GPIO 0x03 + +/* Bluetooth Chip Wakeup Methods */ +#define BT_CTRL_WAKEUP_METHOD_DSR 0x00 +#define BT_CTRL_WAKEUP_METHOD_BREAK 0x01 +#define BT_CTRL_WAKEUP_METHOD_GPIO 0x02 +#define BT_CTRL_WAKEUP_METHOD_EXT_BREAK 0x04 +#define BT_CTRL_WAKEUP_METHOD_RTS 0x05 + +struct ps_data { + u8 target_ps_mode; /* ps mode to be set */ + u8 cur_psmode; /* current ps_mode */ + u8 ps_state; /* controller's power save state */ + u8 ps_cmd; + u8 h2c_wakeupmode; + u8 cur_h2c_wakeupmode; + u8 c2h_wakeupmode; + u8 c2h_wakeup_gpio; + u8 h2c_wakeup_gpio; + bool driver_sent_cmd; + u16 h2c_ps_interval; + u16 c2h_ps_interval; + struct hci_dev *hdev; + struct work_struct work; + struct timer_list ps_timer; +}; + +struct wakeup_cmd_payload { + u8 c2h_wakeupmode; + u8 c2h_wakeup_gpio; + u8 h2c_wakeupmode; + u8 h2c_wakeup_gpio; +} __packed; + +struct psmode_cmd_payload { + u8 ps_cmd; + __le16 c2h_ps_interval; +} __packed; + +struct btnxpuart_data { + const char *helper_fw_name; + const char *fw_name; +}; + +struct btnxpuart_dev { + struct hci_dev *hdev; + struct serdev_device *serdev; + + struct work_struct tx_work; + unsigned long tx_state; + struct sk_buff_head txq; + struct sk_buff *rx_skb; + + const struct firmware *fw; + u8 fw_name[MAX_FW_FILE_NAME_LEN]; + u32 fw_dnld_v1_offset; + u32 fw_v1_sent_bytes; + u32 fw_v3_offset_correction; + u32 fw_v1_expected_len; + wait_queue_head_t fw_dnld_done_wait_q; + wait_queue_head_t check_boot_sign_wait_q; + + u32 new_baudrate; + u32 current_baudrate; + u32 fw_init_baudrate; + bool timeout_changed; + bool baudrate_changed; + bool helper_downloaded; + + struct ps_data psdata; + struct btnxpuart_data *nxp_data; +}; + +#define NXP_V1_FW_REQ_PKT 0xa5 +#define NXP_V1_CHIP_VER_PKT 0xaa +#define NXP_V3_FW_REQ_PKT 0xa7 +#define NXP_V3_CHIP_VER_PKT 0xab + +#define NXP_ACK_V1 0x5a +#define NXP_NAK_V1 0xbf +#define NXP_ACK_V3 0x7a +#define NXP_NAK_V3 0x7b +#define NXP_CRC_ERROR_V3 0x7c + +#define HDR_LEN 16 + +#define NXP_RECV_CHIP_VER_V1 \ + .type = NXP_V1_CHIP_VER_PKT, \ + .hlen = 4, \ + .loff = 0, \ + .lsize = 0, \ + .maxlen = 4 + +#define NXP_RECV_FW_REQ_V1 \ + .type = NXP_V1_FW_REQ_PKT, \ + .hlen = 4, \ + .loff = 0, \ + .lsize = 0, \ + .maxlen = 4 + +#define NXP_RECV_CHIP_VER_V3 \ + .type = NXP_V3_CHIP_VER_PKT, \ + .hlen = 4, \ + .loff = 0, \ + .lsize = 0, \ + .maxlen = 4 + +#define NXP_RECV_FW_REQ_V3 \ + .type = NXP_V3_FW_REQ_PKT, \ + .hlen = 9, \ + .loff = 0, \ + .lsize = 0, \ + .maxlen = 9 + +struct v1_data_req { + __le16 len; + __le16 len_comp; +} __packed; + +struct v1_start_ind { + __le16 chip_id; + __le16 chip_id_comp; +} __packed; + +struct v3_data_req { + __le16 len; + __le32 offset; + __le16 error; + u8 crc; +} __packed; + +struct v3_start_ind { + __le16 chip_id; + u8 loader_ver; + u8 crc; +} __packed; + +/* UART register addresses of BT chip */ +#define CLKDIVADDR 0x7f00008f +#define UARTDIVADDR 0x7f000090 +#define UARTMCRADDR 0x7f000091 +#define UARTREINITADDR 0x7f000092 +#define UARTICRADDR 0x7f000093 +#define UARTFCRADDR 0x7f000094 + +#define MCR 0x00000022 +#define INIT 0x00000001 +#define ICR 0x000000c7 +#define FCR 0x000000c7 + +#define POLYNOMIAL8 0x07 + +struct uart_reg { + __le32 address; + __le32 value; +} __packed; + +struct uart_config { + struct uart_reg clkdiv; + struct uart_reg uartdiv; + struct uart_reg mcr; + struct uart_reg re_init; + struct uart_reg icr; + struct uart_reg fcr; + __be32 crc; +} __packed; + +struct nxp_bootloader_cmd { + __le32 header; + __le32 arg; + __le32 payload_len; + __be32 crc; +} __packed; + +static u8 crc8_table[CRC8_TABLE_SIZE]; + +/* Default configurations */ +#define DEFAULT_H2C_WAKEUP_MODE WAKEUP_METHOD_BREAK +#define DEFAULT_PS_MODE PS_MODE_DISABLE +#define FW_INIT_BAUDRATE HCI_NXP_PRI_BAUDRATE + +static struct sk_buff *nxp_drv_send_cmd(struct hci_dev *hdev, u16 opcode, + u32 plen, + void *param) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + struct ps_data *psdata = &nxpdev->psdata; + struct sk_buff *skb; + + /* set flag to prevent nxp_enqueue from parsing values from this command and + * calling hci_cmd_sync_queue() again. + */ + psdata->driver_sent_cmd = true; + skb = __hci_cmd_sync(hdev, opcode, plen, param, HCI_CMD_TIMEOUT); + psdata->driver_sent_cmd = false; + + return skb; +} + +static void btnxpuart_tx_wakeup(struct btnxpuart_dev *nxpdev) +{ + if (schedule_work(&nxpdev->tx_work)) + set_bit(BTNXPUART_TX_STATE_ACTIVE, &nxpdev->tx_state); +} + +/* NXP Power Save Feature */ +static void ps_start_timer(struct btnxpuart_dev *nxpdev) +{ + struct ps_data *psdata = &nxpdev->psdata; + + if (!psdata) + return; + + if (psdata->cur_psmode == PS_MODE_ENABLE) + mod_timer(&psdata->ps_timer, jiffies + msecs_to_jiffies(psdata->h2c_ps_interval)); +} + +static void ps_cancel_timer(struct btnxpuart_dev *nxpdev) +{ + struct ps_data *psdata = &nxpdev->psdata; + + flush_work(&psdata->work); + del_timer_sync(&psdata->ps_timer); +} + +static void ps_control(struct hci_dev *hdev, u8 ps_state) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + struct ps_data *psdata = &nxpdev->psdata; + int status; + + if (psdata->ps_state == ps_state || + !test_bit(BTNXPUART_SERDEV_OPEN, &nxpdev->tx_state)) + return; + + switch (psdata->cur_h2c_wakeupmode) { + case WAKEUP_METHOD_DTR: + if (ps_state == PS_STATE_AWAKE) + status = serdev_device_set_tiocm(nxpdev->serdev, TIOCM_DTR, 0); + else + status = serdev_device_set_tiocm(nxpdev->serdev, 0, TIOCM_DTR); + break; + case WAKEUP_METHOD_BREAK: + default: + if (ps_state == PS_STATE_AWAKE) + status = serdev_device_break_ctl(nxpdev->serdev, 0); + else + status = serdev_device_break_ctl(nxpdev->serdev, -1); + bt_dev_dbg(hdev, "Set UART break: %s, status=%d", + str_on_off(ps_state == PS_STATE_SLEEP), status); + break; + } + if (!status) + psdata->ps_state = ps_state; + if (ps_state == PS_STATE_AWAKE) + btnxpuart_tx_wakeup(nxpdev); +} + +static void ps_work_func(struct work_struct *work) +{ + struct ps_data *data = container_of(work, struct ps_data, work); + + if (data->ps_cmd == PS_CMD_ENTER_PS && data->cur_psmode == PS_MODE_ENABLE) + ps_control(data->hdev, PS_STATE_SLEEP); + else if (data->ps_cmd == PS_CMD_EXIT_PS) + ps_control(data->hdev, PS_STATE_AWAKE); +} + +static void ps_timeout_func(struct timer_list *t) +{ + struct ps_data *data = from_timer(data, t, ps_timer); + struct hci_dev *hdev = data->hdev; + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + + if (test_bit(BTNXPUART_TX_STATE_ACTIVE, &nxpdev->tx_state)) { + ps_start_timer(nxpdev); + } else { + data->ps_cmd = PS_CMD_ENTER_PS; + schedule_work(&data->work); + } +} + +static int ps_init_work(struct hci_dev *hdev) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + struct ps_data *psdata = &nxpdev->psdata; + + psdata->h2c_ps_interval = PS_DEFAULT_TIMEOUT_PERIOD_MS; + psdata->ps_state = PS_STATE_AWAKE; + psdata->target_ps_mode = DEFAULT_PS_MODE; + psdata->hdev = hdev; + psdata->c2h_wakeupmode = BT_HOST_WAKEUP_METHOD_NONE; + psdata->c2h_wakeup_gpio = 0xff; + + switch (DEFAULT_H2C_WAKEUP_MODE) { + case WAKEUP_METHOD_DTR: + psdata->h2c_wakeupmode = WAKEUP_METHOD_DTR; + break; + case WAKEUP_METHOD_BREAK: + default: + psdata->h2c_wakeupmode = WAKEUP_METHOD_BREAK; + break; + } + psdata->cur_psmode = PS_MODE_DISABLE; + psdata->cur_h2c_wakeupmode = WAKEUP_METHOD_INVALID; + INIT_WORK(&psdata->work, ps_work_func); + + return 0; +} + +static void ps_init_timer(struct hci_dev *hdev) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + struct ps_data *psdata = &nxpdev->psdata; + + timer_setup(&psdata->ps_timer, ps_timeout_func, 0); +} + +static void ps_wakeup(struct btnxpuart_dev *nxpdev) +{ + struct ps_data *psdata = &nxpdev->psdata; + + if (psdata->ps_state != PS_STATE_AWAKE) { + psdata->ps_cmd = PS_CMD_EXIT_PS; + schedule_work(&psdata->work); + } +} + +static int send_ps_cmd(struct hci_dev *hdev, void *data) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + struct ps_data *psdata = &nxpdev->psdata; + struct psmode_cmd_payload pcmd; + struct sk_buff *skb; + u8 *status; + + if (psdata->target_ps_mode == PS_MODE_ENABLE) + pcmd.ps_cmd = BT_PS_ENABLE; + else + pcmd.ps_cmd = BT_PS_DISABLE; + pcmd.c2h_ps_interval = __cpu_to_le16(psdata->c2h_ps_interval); + + skb = nxp_drv_send_cmd(hdev, HCI_NXP_AUTO_SLEEP_MODE, sizeof(pcmd), &pcmd); + if (IS_ERR(skb)) { + bt_dev_err(hdev, "Setting Power Save mode failed (%ld)", PTR_ERR(skb)); + return PTR_ERR(skb); + } + + status = skb_pull_data(skb, 1); + if (status) { + if (!*status) + psdata->cur_psmode = psdata->target_ps_mode; + else + psdata->target_ps_mode = psdata->cur_psmode; + if (psdata->cur_psmode == PS_MODE_ENABLE) + ps_start_timer(nxpdev); + else + ps_wakeup(nxpdev); + bt_dev_dbg(hdev, "Power Save mode response: status=%d, ps_mode=%d", + *status, psdata->cur_psmode); + } + kfree_skb(skb); + + return 0; +} + +static int send_wakeup_method_cmd(struct hci_dev *hdev, void *data) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + struct ps_data *psdata = &nxpdev->psdata; + struct wakeup_cmd_payload pcmd; + struct sk_buff *skb; + u8 *status; + + pcmd.c2h_wakeupmode = psdata->c2h_wakeupmode; + pcmd.c2h_wakeup_gpio = psdata->c2h_wakeup_gpio; + switch (psdata->h2c_wakeupmode) { + case WAKEUP_METHOD_DTR: + pcmd.h2c_wakeupmode = BT_CTRL_WAKEUP_METHOD_DSR; + break; + case WAKEUP_METHOD_BREAK: + default: + pcmd.h2c_wakeupmode = BT_CTRL_WAKEUP_METHOD_BREAK; + break; + } + pcmd.h2c_wakeup_gpio = 0xff; + + skb = nxp_drv_send_cmd(hdev, HCI_NXP_WAKEUP_METHOD, sizeof(pcmd), &pcmd); + if (IS_ERR(skb)) { + bt_dev_err(hdev, "Setting wake-up method failed (%ld)", PTR_ERR(skb)); + return PTR_ERR(skb); + } + + status = skb_pull_data(skb, 1); + if (status) { + if (*status == 0) + psdata->cur_h2c_wakeupmode = psdata->h2c_wakeupmode; + else + psdata->h2c_wakeupmode = psdata->cur_h2c_wakeupmode; + bt_dev_dbg(hdev, "Set Wakeup Method response: status=%d, h2c_wakeupmode=%d", + *status, psdata->cur_h2c_wakeupmode); + } + kfree_skb(skb); + + return 0; +} + +static void ps_init(struct hci_dev *hdev) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + struct ps_data *psdata = &nxpdev->psdata; + + serdev_device_set_tiocm(nxpdev->serdev, 0, TIOCM_RTS); + usleep_range(5000, 10000); + serdev_device_set_tiocm(nxpdev->serdev, TIOCM_RTS, 0); + usleep_range(5000, 10000); + + switch (psdata->h2c_wakeupmode) { + case WAKEUP_METHOD_DTR: + serdev_device_set_tiocm(nxpdev->serdev, 0, TIOCM_DTR); + serdev_device_set_tiocm(nxpdev->serdev, TIOCM_DTR, 0); + break; + case WAKEUP_METHOD_BREAK: + default: + serdev_device_break_ctl(nxpdev->serdev, -1); + usleep_range(5000, 10000); + serdev_device_break_ctl(nxpdev->serdev, 0); + usleep_range(5000, 10000); + break; + } + if (psdata->cur_h2c_wakeupmode != psdata->h2c_wakeupmode) + hci_cmd_sync_queue(hdev, send_wakeup_method_cmd, NULL, NULL); + if (psdata->cur_psmode != psdata->target_ps_mode) + hci_cmd_sync_queue(hdev, send_ps_cmd, NULL, NULL); +} + +/* NXP Firmware Download Feature */ +static int nxp_download_firmware(struct hci_dev *hdev) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + int err = 0; + + nxpdev->fw_dnld_v1_offset = 0; + nxpdev->fw_v1_sent_bytes = 0; + nxpdev->fw_v1_expected_len = HDR_LEN; + nxpdev->fw_v3_offset_correction = 0; + nxpdev->baudrate_changed = false; + nxpdev->timeout_changed = false; + nxpdev->helper_downloaded = false; + + serdev_device_set_baudrate(nxpdev->serdev, HCI_NXP_PRI_BAUDRATE); + serdev_device_set_flow_control(nxpdev->serdev, false); + nxpdev->current_baudrate = HCI_NXP_PRI_BAUDRATE; + + /* Wait till FW is downloaded and CTS becomes low */ + err = wait_event_interruptible_timeout(nxpdev->fw_dnld_done_wait_q, + !test_bit(BTNXPUART_FW_DOWNLOADING, + &nxpdev->tx_state), + msecs_to_jiffies(60000)); + if (err == 0) { + bt_dev_err(hdev, "FW Download Timeout."); + return -ETIMEDOUT; + } + + serdev_device_set_flow_control(nxpdev->serdev, true); + err = serdev_device_wait_for_cts(nxpdev->serdev, 1, 60000); + if (err < 0) { + bt_dev_err(hdev, "CTS is still high. FW Download failed."); + return err; + } + release_firmware(nxpdev->fw); + memset(nxpdev->fw_name, 0, sizeof(nxpdev->fw_name)); + + /* Allow the downloaded FW to initialize */ + usleep_range(800 * USEC_PER_MSEC, 1 * USEC_PER_SEC); + + return 0; +} + +static void nxp_send_ack(u8 ack, struct hci_dev *hdev) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + u8 ack_nak[2]; + int len = 1; + + ack_nak[0] = ack; + if (ack == NXP_ACK_V3) { + ack_nak[1] = crc8(crc8_table, ack_nak, 1, 0xff); + len = 2; + } + serdev_device_write_buf(nxpdev->serdev, ack_nak, len); +} + +static bool nxp_fw_change_baudrate(struct hci_dev *hdev, u16 req_len) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + struct nxp_bootloader_cmd nxp_cmd5; + struct uart_config uart_config; + + if (req_len == sizeof(nxp_cmd5)) { + nxp_cmd5.header = __cpu_to_le32(5); + nxp_cmd5.arg = 0; + nx |