diff options
author | Jakub Kicinski <kuba@kernel.org> | 2024-07-15 17:27:40 +0200 |
---|---|---|
committer | Jakub Kicinski <kuba@kernel.org> | 2024-07-15 17:27:41 +0200 |
commit | cd9b6f4795e7002fa2fb51e6d172294e28453df3 (patch) | |
tree | ec3b31e1eeed21db658aa837990a4e9f0880608d /drivers | |
parent | Merge branch 'net-make-timestamping-selectable' (diff) | |
parent | Bluetooth: btmtk: Mark all stub functions as inline (diff) | |
download | linux-cd9b6f4795e7002fa2fb51e6d172294e28453df3.tar.xz linux-cd9b6f4795e7002fa2fb51e6d172294e28453df3.zip |
Merge tag 'for-net-next-2024-07-15' of git://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next
Luiz Augusto von Dentz says:
====================
bluetooth-next pull request for net-next:
- qca: use the power sequencer for QCA6390
- btusb: mediatek: add ISO data transmission functions
- hci_bcm4377: Add BCM4388 support
- btintel: Add support for BlazarU core
- btintel: Add support for Whale Peak2
- btnxpuart: Add support for AW693 A1 chipset
- btnxpuart: Add support for IW615 chipset
- btusb: Add Realtek RTL8852BE support ID 0x13d3:0x3591
* tag 'for-net-next-2024-07-15' of git://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next: (71 commits)
Bluetooth: btmtk: Mark all stub functions as inline
Bluetooth: hci_qca: Fix build error
Bluetooth: hci_qca: use the power sequencer for wcn7850 and wcn6855
Bluetooth: hci_qca: make pwrseq calls the default if available
Bluetooth: hci_qca: unduplicate calls to hci_uart_register_device()
Bluetooth: hci_qca: schedule a devm action for disabling the clock
dt-bindings: bluetooth: qualcomm: describe the inputs from PMU for wcn7850
Bluetooth: btnxpuart: Fix warnings for suspend and resume functions
Bluetooth: btnxpuart: Add system suspend and resume handlers
Bluetooth: btnxpuart: Add support for IW615 chipset
Bluetooth: btnxpuart: Add support for AW693 A1 chipset
Bluetooth: btintel: Add support for Whale Peak2
Bluetooth: btintel: Add support for BlazarU core
Bluetooth: btusb: mediatek: add ISO data transmission functions
Bluetooth: btmtk: move btusb_recv_acl_mtk to btmtk.c
Bluetooth: btmtk: move btusb_mtk_[setup, shutdown] to btmtk.c
Bluetooth: btmtk: move btusb_mtk_hci_wmt_sync to btmtk.c
Bluetooth: btusb: add callback function in btusb suspend/resume
Bluetooth: btmtk: rename btmediatek_data
Bluetooth: btusb: mediatek: return error for failed reg access
...
====================
Link: https://patch.msgid.link/20240715142543.303944-1-luiz.dentz@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/bluetooth/Kconfig | 7 | ||||
-rw-r--r-- | drivers/bluetooth/btintel.c | 240 | ||||
-rw-r--r-- | drivers/bluetooth/btintel.h | 11 | ||||
-rw-r--r-- | drivers/bluetooth/btintel_pcie.c | 10 | ||||
-rw-r--r-- | drivers/bluetooth/btmtk.c | 1085 | ||||
-rw-r--r-- | drivers/bluetooth/btmtk.h | 118 | ||||
-rw-r--r-- | drivers/bluetooth/btmtksdio.c | 4 | ||||
-rw-r--r-- | drivers/bluetooth/btmtkuart.c | 1 | ||||
-rw-r--r-- | drivers/bluetooth/btnxpuart.c | 242 | ||||
-rw-r--r-- | drivers/bluetooth/btrtl.c | 2 | ||||
-rw-r--r-- | drivers/bluetooth/btusb.c | 735 | ||||
-rw-r--r-- | drivers/bluetooth/hci_bcm4377.c | 66 | ||||
-rw-r--r-- | drivers/bluetooth/hci_ldisc.c | 2 | ||||
-rw-r--r-- | drivers/bluetooth/hci_nokia.c | 5 | ||||
-rw-r--r-- | drivers/bluetooth/hci_qca.c | 133 | ||||
-rw-r--r-- | drivers/bluetooth/hci_vhci.c | 2 | ||||
-rw-r--r-- | drivers/power/Kconfig | 1 | ||||
-rw-r--r-- | drivers/power/Makefile | 1 | ||||
-rw-r--r-- | drivers/power/sequencing/Kconfig | 29 | ||||
-rw-r--r-- | drivers/power/sequencing/Makefile | 6 | ||||
-rw-r--r-- | drivers/power/sequencing/core.c | 1105 | ||||
-rw-r--r-- | drivers/power/sequencing/pwrseq-qcom-wcn.c | 336 |
22 files changed, 3250 insertions, 891 deletions
diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig index 0b5f218ac505..90a94a111e67 100644 --- a/drivers/bluetooth/Kconfig +++ b/drivers/bluetooth/Kconfig @@ -105,6 +105,7 @@ config BT_HCIUART tristate "HCI UART driver" depends on SERIAL_DEV_BUS || !SERIAL_DEV_BUS depends on NVMEM || !NVMEM + depends on POWER_SEQUENCING || !POWER_SEQUENCING depends on TTY help Bluetooth HCI UART driver. @@ -287,12 +288,12 @@ config BT_HCIBCM203X config BT_HCIBCM4377 - tristate "HCI BCM4377/4378/4387 PCIe driver" + tristate "HCI BCM4377/4378/4387/4388 PCIe driver" depends on PCI select FW_LOADER help - Support for Broadcom BCM4377/4378/4387 Bluetooth chipsets attached via - PCIe. These are usually found in Apple machines. + Support for Broadcom BCM4377/4378/4387/4388 Bluetooth chipsets + attached via PCIe. These are usually found in Apple machines. Say Y here to compile support for HCI BCM4377 family devices into the kernel or say M to compile it as module (hci_bcm4377). diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index 0c855c3ee1c1..e7a612504ab1 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -26,21 +26,11 @@ #define ECDSA_OFFSET 644 #define ECDSA_HEADER_LEN 320 -#define BTINTEL_PPAG_NAME "PPAG" - enum { DSM_SET_WDISABLE2_DELAY = 1, DSM_SET_RESET_METHOD = 3, }; -/* structure to store the PPAG data read from ACPI table */ -struct btintel_ppag { - u32 domain; - u32 mode; - acpi_status status; - struct hci_dev *hdev; -}; - #define CMD_WRITE_BOOT_PARAMS 0xfc0e struct cmd_write_boot_params { __le32 boot_addr; @@ -482,6 +472,7 @@ int btintel_version_info_tlv(struct hci_dev *hdev, case 0x19: /* Slr-F */ case 0x1b: /* Mgr */ case 0x1c: /* Gale Peak (GaP) */ + case 0x1d: /* BlazarU (BzrU) */ case 0x1e: /* BlazarI (Bzr) */ break; default: @@ -641,6 +632,10 @@ int btintel_parse_version_tlv(struct hci_dev *hdev, case INTEL_TLV_GIT_SHA1: version->git_sha1 = get_unaligned_le32(tlv->val); break; + case INTEL_TLV_FW_ID: + snprintf(version->fw_id, sizeof(version->fw_id), + "%s", tlv->val); + break; default: /* Ignore rest of information */ break; @@ -1324,65 +1319,6 @@ static int btintel_read_debug_features(struct hci_dev *hdev, return 0; } -static acpi_status btintel_ppag_callback(acpi_handle handle, u32 lvl, void *data, - void **ret) -{ - acpi_status status; - size_t len; - struct btintel_ppag *ppag = data; - union acpi_object *p, *elements; - struct acpi_buffer string = {ACPI_ALLOCATE_BUFFER, NULL}; - struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; - struct hci_dev *hdev = ppag->hdev; - - status = acpi_get_name(handle, ACPI_FULL_PATHNAME, &string); - if (ACPI_FAILURE(status)) { - bt_dev_warn(hdev, "PPAG-BT: ACPI Failure: %s", acpi_format_exception(status)); - return status; - } - - len = strlen(string.pointer); - if (len < strlen(BTINTEL_PPAG_NAME)) { - kfree(string.pointer); - return AE_OK; - } - - if (strncmp((char *)string.pointer + len - 4, BTINTEL_PPAG_NAME, 4)) { - kfree(string.pointer); - return AE_OK; - } - kfree(string.pointer); - - status = acpi_evaluate_object(handle, NULL, NULL, &buffer); - if (ACPI_FAILURE(status)) { - ppag->status = status; - bt_dev_warn(hdev, "PPAG-BT: ACPI Failure: %s", acpi_format_exception(status)); - return status; - } - - p = buffer.pointer; - ppag = (struct btintel_ppag *)data; - - if (p->type != ACPI_TYPE_PACKAGE || p->package.count != 2) { - kfree(buffer.pointer); - bt_dev_warn(hdev, "PPAG-BT: Invalid object type: %d or package count: %d", - p->type, p->package.count); - ppag->status = AE_ERROR; - return AE_ERROR; - } - - elements = p->package.elements; - - /* PPAG table is located at element[1] */ - p = &elements[1]; - - ppag->domain = (u32)p->package.elements[0].integer.value; - ppag->mode = (u32)p->package.elements[1].integer.value; - ppag->status = AE_OK; - kfree(buffer.pointer); - return AE_CTRL_TERMINATE; -} - static int btintel_set_debug_features(struct hci_dev *hdev, const struct intel_debug_features *features) { @@ -2202,30 +2138,61 @@ static void btintel_get_fw_name_tlv(const struct intel_version_tlv *ver, const char *suffix) { const char *format; - /* The firmware file name for new generation controllers will be - * ibt-<cnvi_top type+cnvi_top step>-<cnvr_top type+cnvr_top step> - */ - switch (ver->cnvi_top & 0xfff) { + u32 cnvi, cnvr; + + cnvi = INTEL_CNVX_TOP_PACK_SWAB(INTEL_CNVX_TOP_TYPE(ver->cnvi_top), + INTEL_CNVX_TOP_STEP(ver->cnvi_top)); + + cnvr = INTEL_CNVX_TOP_PACK_SWAB(INTEL_CNVX_TOP_TYPE(ver->cnvr_top), + INTEL_CNVX_TOP_STEP(ver->cnvr_top)); + /* Only Blazar product supports downloading of intermediate loader * image */ - case BTINTEL_CNVI_BLAZARI: - if (ver->img_type == BTINTEL_IMG_BOOTLOADER) + if (INTEL_HW_VARIANT(ver->cnvi_bt) >= 0x1e) { + u8 zero[BTINTEL_FWID_MAXLEN]; + + if (ver->img_type == BTINTEL_IMG_BOOTLOADER) { format = "intel/ibt-%04x-%04x-iml.%s"; - else - format = "intel/ibt-%04x-%04x.%s"; - break; - default: - format = "intel/ibt-%04x-%04x.%s"; - break; + snprintf(fw_name, len, format, cnvi, cnvr, suffix); + return; + } + + memset(zero, 0, sizeof(zero)); + + /* ibt-<cnvi_top type+cnvi_top step>-<cnvr_top type+cnvr_top step-fw_id> */ + if (memcmp(ver->fw_id, zero, sizeof(zero))) { + format = "intel/ibt-%04x-%04x-%s.%s"; + snprintf(fw_name, len, format, cnvi, cnvr, + ver->fw_id, suffix); + return; + } + /* If firmware id is not present, fallback to legacy naming + * convention + */ } + /* Fallback to legacy naming convention for other controllers + * ibt-<cnvi_top type+cnvi_top step>-<cnvr_top type+cnvr_top step> + */ + format = "intel/ibt-%04x-%04x.%s"; + snprintf(fw_name, len, format, cnvi, cnvr, suffix); +} - snprintf(fw_name, len, format, - INTEL_CNVX_TOP_PACK_SWAB(INTEL_CNVX_TOP_TYPE(ver->cnvi_top), - INTEL_CNVX_TOP_STEP(ver->cnvi_top)), - INTEL_CNVX_TOP_PACK_SWAB(INTEL_CNVX_TOP_TYPE(ver->cnvr_top), - INTEL_CNVX_TOP_STEP(ver->cnvr_top)), - suffix); +static void btintel_get_iml_tlv(const struct intel_version_tlv *ver, + char *fw_name, size_t len, + const char *suffix) +{ + const char *format; + u32 cnvi, cnvr; + + cnvi = INTEL_CNVX_TOP_PACK_SWAB(INTEL_CNVX_TOP_TYPE(ver->cnvi_top), + INTEL_CNVX_TOP_STEP(ver->cnvi_top)); + + cnvr = INTEL_CNVX_TOP_PACK_SWAB(INTEL_CNVX_TOP_TYPE(ver->cnvr_top), + INTEL_CNVX_TOP_STEP(ver->cnvr_top)); + + format = "intel/ibt-%04x-%04x-iml.%s"; + snprintf(fw_name, len, format, cnvi, cnvr, suffix); } static int btintel_prepare_fw_download_tlv(struct hci_dev *hdev, @@ -2233,7 +2200,7 @@ static int btintel_prepare_fw_download_tlv(struct hci_dev *hdev, u32 *boot_param) { const struct firmware *fw; - char fwname[64]; + char fwname[128]; int err; ktime_t calltime; @@ -2268,7 +2235,20 @@ static int btintel_prepare_fw_download_tlv(struct hci_dev *hdev, } } - btintel_get_fw_name_tlv(ver, fwname, sizeof(fwname), "sfi"); + if (ver->img_type == BTINTEL_IMG_OP) { + /* Controller running OP image. In case of FW downgrade, + * FWID TLV may not be present and driver may attempt to load + * firmware image which doesn't exist. Lets compare the version + * of IML image + */ + if (INTEL_HW_VARIANT(ver->cnvi_bt) >= 0x1e) + btintel_get_iml_tlv(ver, fwname, sizeof(fwname), "sfi"); + else + btintel_get_fw_name_tlv(ver, fwname, sizeof(fwname), "sfi"); + } else { + btintel_get_fw_name_tlv(ver, fwname, sizeof(fwname), "sfi"); + } + err = firmware_request_nowarn(&fw, fwname, &hdev->dev); if (err < 0) { if (!btintel_test_flag(hdev, INTEL_BOOTLOADER)) { @@ -2427,10 +2407,13 @@ error: static void btintel_set_ppag(struct hci_dev *hdev, struct intel_version_tlv *ver) { - struct btintel_ppag ppag; struct sk_buff *skb; struct hci_ppag_enable_cmd ppag_cmd; acpi_handle handle; + struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; + union acpi_object *p, *elements; + u32 domain, mode; + acpi_status status; /* PPAG is not supported if CRF is HrP2, Jfp2, JfP1 */ switch (ver->cnvr_top & 0xFFF) { @@ -2448,22 +2431,34 @@ static void btintel_set_ppag(struct hci_dev *hdev, struct intel_version_tlv *ver return; } - memset(&ppag, 0, sizeof(ppag)); - - ppag.hdev = hdev; - ppag.status = AE_NOT_FOUND; - acpi_walk_namespace(ACPI_TYPE_PACKAGE, handle, 1, NULL, - btintel_ppag_callback, &ppag, NULL); - - if (ACPI_FAILURE(ppag.status)) { - if (ppag.status == AE_NOT_FOUND) { + status = acpi_evaluate_object(handle, "PPAG", NULL, &buffer); + if (ACPI_FAILURE(status)) { + if (status == AE_NOT_FOUND) { bt_dev_dbg(hdev, "PPAG-BT: ACPI entry not found"); return; } + bt_dev_warn(hdev, "PPAG-BT: ACPI Failure: %s", acpi_format_exception(status)); return; } - if (ppag.domain != 0x12) { + p = buffer.pointer; + if (p->type != ACPI_TYPE_PACKAGE || p->package.count != 2) { + bt_dev_warn(hdev, "PPAG-BT: Invalid object type: %d or package count: %d", + p->type, p->package.count); + kfree(buffer.pointer); + return; + } + + elements = p->package.elements; + + /* PPAG table is located at element[1] */ + p = &elements[1]; + + domain = (u32)p->package.elements[0].integer.value; + mode = (u32)p->package.elements[1].integer.value; + kfree(buffer.pointer); + + if (domain != 0x12) { bt_dev_dbg(hdev, "PPAG-BT: Bluetooth domain is disabled in ACPI firmware"); return; } @@ -2474,19 +2469,22 @@ static void btintel_set_ppag(struct hci_dev *hdev, struct intel_version_tlv *ver * BIT 1 : 0 Disabled in China * 1 Enabled in China */ - if ((ppag.mode & 0x01) != BIT(0) && (ppag.mode & 0x02) != BIT(1)) { - bt_dev_dbg(hdev, "PPAG-BT: EU, China mode are disabled in CB/BIOS"); + mode &= 0x03; + + if (!mode) { + bt_dev_dbg(hdev, "PPAG-BT: EU, China mode are disabled in BIOS"); return; } - ppag_cmd.ppag_enable_flags = cpu_to_le32(ppag.mode); + ppag_cmd.ppag_enable_flags = cpu_to_le32(mode); - skb = __hci_cmd_sync(hdev, INTEL_OP_PPAG_CMD, sizeof(ppag_cmd), &ppag_cmd, HCI_CMD_TIMEOUT); + skb = __hci_cmd_sync(hdev, INTEL_OP_PPAG_CMD, sizeof(ppag_cmd), + &ppag_cmd, HCI_CMD_TIMEOUT); if (IS_ERR(skb)) { bt_dev_warn(hdev, "Failed to send PPAG Enable (%ld)", PTR_ERR(skb)); return; } - bt_dev_info(hdev, "PPAG-BT: Enabled (Mode %d)", ppag.mode); + bt_dev_info(hdev, "PPAG-BT: Enabled (Mode %d)", mode); kfree_skb(skb); } @@ -2600,6 +2598,24 @@ static void btintel_set_dsm_reset_method(struct hci_dev *hdev, data->acpi_reset_method = btintel_acpi_reset_method; } +#define BTINTEL_ISODATA_HANDLE_BASE 0x900 + +static u8 btintel_classify_pkt_type(struct hci_dev *hdev, struct sk_buff *skb) +{ + /* + * Distinguish ISO data packets form ACL data packets + * based on their connection handle value range. + */ + if (hci_skb_pkt_type(skb) == HCI_ACLDATA_PKT) { + __u16 handle = __le16_to_cpu(hci_acl_hdr(skb)->handle); + + if (hci_handle(handle) >= BTINTEL_ISODATA_HANDLE_BASE) + return HCI_ISODATA_PKT; + } + + return hci_skb_pkt_type(skb); +} + int btintel_bootloader_setup_tlv(struct hci_dev *hdev, struct intel_version_tlv *ver) { @@ -2635,7 +2651,7 @@ int btintel_bootloader_setup_tlv(struct hci_dev *hdev, return err; /* If image type returned is BTINTEL_IMG_IML, then controller supports - * intermediae loader image + * intermediate loader image */ if (ver->img_type == BTINTEL_IMG_IML) { err = btintel_prepare_fw_download_tlv(hdev, ver, &boot_param); @@ -2703,6 +2719,7 @@ void btintel_set_msft_opcode(struct hci_dev *hdev, u8 hw_variant) case 0x19: case 0x1b: case 0x1c: + case 0x1d: case 0x1e: hci_set_msft_opcode(hdev, 0xFC1E); break; @@ -2713,7 +2730,7 @@ void btintel_set_msft_opcode(struct hci_dev *hdev, u8 hw_variant) } EXPORT_SYMBOL_GPL(btintel_set_msft_opcode); -static void btintel_print_fseq_info(struct hci_dev *hdev) +void btintel_print_fseq_info(struct hci_dev *hdev) { struct sk_buff *skb; u8 *p; @@ -2825,6 +2842,7 @@ static void btintel_print_fseq_info(struct hci_dev *hdev) kfree_skb(skb); } +EXPORT_SYMBOL_GPL(btintel_print_fseq_info); static int btintel_setup_combined(struct hci_dev *hdev) { @@ -3039,11 +3057,15 @@ static int btintel_setup_combined(struct hci_dev *hdev) err = btintel_bootloader_setup(hdev, &ver); btintel_register_devcoredump_support(hdev); break; + case 0x18: /* GfP2 */ + case 0x1c: /* GaP */ + /* Re-classify packet type for controllers with LE audio */ + hdev->classify_pkt_type = btintel_classify_pkt_type; + fallthrough; case 0x17: - case 0x18: case 0x19: case 0x1b: - case 0x1c: + case 0x1d: case 0x1e: /* Display version information of TLV type */ btintel_version_info_tlv(hdev, &ver_tlv); diff --git a/drivers/bluetooth/btintel.h b/drivers/bluetooth/btintel.h index b5fea735e260..aa70e4c27416 100644 --- a/drivers/bluetooth/btintel.h +++ b/drivers/bluetooth/btintel.h @@ -42,7 +42,8 @@ enum { INTEL_TLV_SBE_TYPE, INTEL_TLV_OTP_BDADDR, INTEL_TLV_UNLOCKED_STATE, - INTEL_TLV_GIT_SHA1 + INTEL_TLV_GIT_SHA1, + INTEL_TLV_FW_ID = 0x50 }; struct intel_tlv { @@ -57,6 +58,8 @@ struct intel_tlv { #define BTINTEL_IMG_IML 0x02 /* Intermediate image */ #define BTINTEL_IMG_OP 0x03 /* Operational image */ +#define BTINTEL_FWID_MAXLEN 64 + struct intel_version_tlv { u32 cnvi_top; u32 cnvr_top; @@ -77,6 +80,7 @@ struct intel_version_tlv { u8 limited_cce; u8 sbe_type; u32 git_sha1; + u8 fw_id[BTINTEL_FWID_MAXLEN]; bdaddr_t otp_bd_addr; }; @@ -244,6 +248,7 @@ int btintel_bootloader_setup_tlv(struct hci_dev *hdev, struct intel_version_tlv *ver); int btintel_shutdown_combined(struct hci_dev *hdev); void btintel_hw_error(struct hci_dev *hdev, u8 code); +void btintel_print_fseq_info(struct hci_dev *hdev); #else static inline int btintel_check_bdaddr(struct hci_dev *hdev) @@ -373,4 +378,8 @@ static inline int btintel_shutdown_combined(struct hci_dev *hdev) static inline void btintel_hw_error(struct hci_dev *hdev, u8 code) { } + +static inline void btintel_print_fseq_info(struct hci_dev *hdev) +{ +} #endif diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index dd3c0626c72d..0d1a0415557b 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -797,7 +797,6 @@ static int btintel_pcie_setup_txq_bufs(struct btintel_pcie_data *data, kfree(txq->bufs); return -ENOMEM; } - memset(txq->buf_v_addr, 0, txq->count * BTINTEL_PCIE_BUFFER_SIZE); /* Setup the allocated DMA buffer to bufs. Each data_buf should * have virtual address and physical address @@ -842,7 +841,6 @@ static int btintel_pcie_setup_rxq_bufs(struct btintel_pcie_data *data, kfree(rxq->bufs); return -ENOMEM; } - memset(rxq->buf_v_addr, 0, rxq->count * BTINTEL_PCIE_BUFFER_SIZE); /* Setup the allocated DMA buffer to bufs. Each data_buf should * have virtual address and physical address @@ -1197,9 +1195,11 @@ static int btintel_pcie_setup(struct hci_dev *hdev) bt_dev_err(hdev, "Unsupported Intel hw variant (%u)", INTEL_HW_VARIANT(ver_tlv.cnvi_bt)); err = -EINVAL; + goto exit_error; break; } + btintel_print_fseq_info(hdev); exit_error: kfree_skb(skb); @@ -1327,6 +1327,12 @@ static void btintel_pcie_remove(struct pci_dev *pdev) data = pci_get_drvdata(pdev); btintel_pcie_reset_bt(data); + for (int i = 0; i < data->alloc_vecs; i++) { + struct msix_entry *msix_entry; + + msix_entry = &data->msix_entries[i]; + free_irq(msix_entry->vector, msix_entry); + } pci_free_irq_vectors(pdev); diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c index 812fd2a8f853..b7c348687a77 100644 --- a/drivers/bluetooth/btmtk.c +++ b/drivers/bluetooth/btmtk.c @@ -4,6 +4,9 @@ */ #include <linux/module.h> #include <linux/firmware.h> +#include <linux/usb.h> +#include <linux/iopoll.h> +#include <asm/unaligned.h> #include <net/bluetooth/bluetooth.h> #include <net/bluetooth/hci_core.h> @@ -19,6 +22,9 @@ #define MTK_SEC_MAP_COMMON_SIZE 12 #define MTK_SEC_MAP_NEED_SEND_SIZE 52 +/* It is for mt79xx iso data transmission setting */ +#define MTK_ISO_THRESHOLD 264 + struct btmtk_patch_header { u8 datetime[16]; u8 platform[4]; @@ -64,7 +70,7 @@ static void btmtk_coredump(struct hci_dev *hdev) static void btmtk_coredump_hdr(struct hci_dev *hdev, struct sk_buff *skb) { - struct btmediatek_data *data = hci_get_priv(hdev); + struct btmtk_data *data = hci_get_priv(hdev); char buf[80]; snprintf(buf, sizeof(buf), "Controller Name: 0x%X\n", @@ -85,7 +91,7 @@ static void btmtk_coredump_hdr(struct hci_dev *hdev, struct sk_buff *skb) static void btmtk_coredump_notify(struct hci_dev *hdev, int state) { - struct btmediatek_data *data = hci_get_priv(hdev); + struct btmtk_data *data = hci_get_priv(hdev); switch (state) { case HCI_DEVCOREDUMP_IDLE: @@ -103,6 +109,24 @@ static void btmtk_coredump_notify(struct hci_dev *hdev, int state) } } +void btmtk_fw_get_filename(char *buf, size_t size, u32 dev_id, u32 fw_ver, + u32 fw_flavor) +{ + if (dev_id == 0x7925) + snprintf(buf, size, + "mediatek/mt%04x/BT_RAM_CODE_MT%04x_1_%x_hdr.bin", + dev_id & 0xffff, dev_id & 0xffff, (fw_ver & 0xff) + 1); + else if (dev_id == 0x7961 && fw_flavor) + snprintf(buf, size, + "mediatek/BT_RAM_CODE_MT%04x_1a_%x_hdr.bin", + dev_id & 0xffff, (fw_ver & 0xff) + 1); + else + snprintf(buf, size, + "mediatek/BT_RAM_CODE_MT%04x_1_%x_hdr.bin", + dev_id & 0xffff, (fw_ver & 0xff) + 1); +} +EXPORT_SYMBOL_GPL(btmtk_fw_get_filename); + int btmtk_setup_firmware_79xx(struct hci_dev *hdev, const char *fwname, wmt_cmd_sync_func_t wmt_cmd_sync) { @@ -337,7 +361,7 @@ EXPORT_SYMBOL_GPL(btmtk_set_bdaddr); void btmtk_reset_sync(struct hci_dev *hdev) { - struct btmediatek_data *reset_work = hci_get_priv(hdev); + struct btmtk_data *reset_work = hci_get_priv(hdev); int err; hci_dev_lock(hdev); @@ -353,7 +377,7 @@ EXPORT_SYMBOL_GPL(btmtk_reset_sync); int btmtk_register_coredump(struct hci_dev *hdev, const char *name, u32 fw_version) { - struct btmediatek_data *data = hci_get_priv(hdev); + struct btmtk_data *data = hci_get_priv(hdev); if (!IS_ENABLED(CONFIG_DEV_COREDUMP)) return -EOPNOTSUPP; @@ -369,7 +393,7 @@ EXPORT_SYMBOL_GPL(btmtk_register_coredump); int btmtk_process_coredump(struct hci_dev *hdev, struct sk_buff *skb) { - struct btmediatek_data *data = hci_get_priv(hdev); + struct btmtk_data *data = hci_get_priv(hdev); int err; if (!IS_ENABLED(CONFIG_DEV_COREDUMP)) { @@ -413,6 +437,1057 @@ int btmtk_process_coredump(struct hci_dev *hdev, struct sk_buff *skb) } EXPORT_SYMBOL_GPL(btmtk_process_coredump); +static void btmtk_usb_wmt_recv(struct urb *urb) +{ + struct hci_dev *hdev = urb->context; + struct btmtk_data *data = hci_get_priv(hdev); + struct sk_buff *skb; + int err; + + if (urb->status == 0 && urb->actual_length > 0) { + hdev->stat.byte_rx += urb->actual_length; + + /* WMT event shouldn't be fragmented and the size should be + * less than HCI_WMT_MAX_EVENT_SIZE. + */ + skb = bt_skb_alloc(HCI_WMT_MAX_EVENT_SIZE, GFP_ATOMIC); + if (!skb) { + hdev->stat.err_rx++; + kfree(urb->setup_packet); + return; + } + + hci_skb_pkt_type(skb) = HCI_EVENT_PKT; + skb_put_data(skb, urb->transfer_buffer, urb->actual_length); + + /* When someone waits for the WMT event, the skb is being cloned + * and being processed the events from there then. + */ + if (test_bit(BTMTK_TX_WAIT_VND_EVT, &data->flags)) { + data->evt_skb = skb_clone(skb, GFP_ATOMIC); + if (!data->evt_skb) { + kfree_skb(skb); + kfree(urb->setup_packet); + return; + } + } + + err = hci_recv_frame(hdev, skb); + if (err < 0) { + kfree_skb(data->evt_skb); + data->evt_skb = NULL; + kfree(urb->setup_packet); + return; + } + + if (test_and_clear_bit(BTMTK_TX_WAIT_VND_EVT, + &data->flags)) { + /* Barrier to sync with other CPUs */ + smp_mb__after_atomic(); + wake_up_bit(&data->flags, + BTMTK_TX_WAIT_VND_EVT); + } + kfree(urb->setup_packet); + return; + } else if (urb->status == -ENOENT) { + /* Avoid suspend failed when usb_kill_urb */ + return; + } + + usb_mark_last_busy(data->udev); + + /* The URB complete handler is still called with urb->actual_length = 0 + * when the event is not available, so we should keep re-submitting + * URB until WMT event returns, Also, It's necessary to wait some time + * between the two consecutive control URBs to relax the target device + * to generate the event. Otherwise, the WMT event cannot return from + * the device successfully. + */ + udelay(500); + + usb_anchor_urb(urb, data->ctrl_anchor); + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { + kfree(urb->setup_packet); + /* -EPERM: urb is being killed; + * -ENODEV: device got disconnected + */ + if (err != -EPERM && err != -ENODEV) + bt_dev_err(hdev, "urb %p failed to resubmit (%d)", + urb, -err); + usb_unanchor_urb(urb); + } +} + +static int btmtk_usb_submit_wmt_recv_urb(struct hci_dev *hdev) +{ + struct btmtk_data *data = hci_get_priv(hdev); + struct usb_ctrlrequest *dr; + unsigned char *buf; + int err, size = 64; + unsigned int pipe; + struct urb *urb; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return -ENOMEM; + + dr = kmalloc(sizeof(*dr), GFP_KERNEL); + if (!dr) { + usb_free_urb(urb); + return -ENOMEM; + } + + dr->bRequestType = USB_TYPE_VENDOR | USB_DIR_IN; + dr->bRequest = 1; + dr->wIndex = cpu_to_le16(0); + dr->wValue = cpu_to_le16(48); + dr->wLength = cpu_to_le16(size); + + buf = kmalloc(size, GFP_KERNEL); + if (!buf) { + kfree(dr); + usb_free_urb(urb); + return -ENOMEM; + } + + pipe = usb_rcvctrlpipe(data->udev, 0); + + usb_fill_control_urb(urb, data->udev, pipe, (void *)dr, + buf, size, btmtk_usb_wmt_recv, hdev); + + urb->transfer_flags |= URB_FREE_BUFFER; + + usb_anchor_urb(urb, data->ctrl_anchor); + err = usb_submit_urb(urb, GFP_KERNEL); + if (err < 0) { + if (err != -EPERM && err != -ENODEV) + bt_dev_err(hdev, "urb %p submission failed (%d)", + urb, -err); + usb_unanchor_urb(urb); + } + + usb_free_urb(urb); + + return err; +} + +static int btmtk_usb_hci_wmt_sync(struct hci_dev *hdev, + struct btmtk_hci_wmt_params *wmt_params) +{ + struct btmtk_data *data = hci_get_priv(hdev); + struct btmtk_hci_wmt_evt_funcc *wmt_evt_funcc; + u32 hlen, status = BTMTK_WMT_INVALID; + struct btmtk_hci_wmt_evt *wmt_evt; + struct btmtk_hci_wmt_cmd *wc; + struct btmtk_wmt_hdr *hdr; + int err; + + /* Send the WMT command and wait until the WMT event returns */ + hlen = sizeof(*hdr) + wmt_params->dlen; + if (hlen > 255) + return -EINVAL; + + wc = kzalloc(hlen, GFP_KERNEL); + if (!wc) + return -ENOMEM; + + hdr = &wc->hdr; + hdr->dir = 1; + hdr->op = wmt_params->op; + hdr->dlen = cpu_to_le16(wmt_params->dlen + 1); + hdr->flag = wmt_params->flag; + memcpy(wc->data, wmt_params->data, wmt_params->dlen); + + set_bit(BTMTK_TX_WAIT_VND_EVT, &data->flags); + + /* WMT cmd/event doesn't follow up the generic HCI cmd/event handling, + * it needs constantly polling control pipe until the host received the + * WMT event, thus, we should require to specifically acquire PM counter + * on the USB to prevent the interface from entering auto suspended + * while WMT cmd/event in progress. + */ + err = usb_autopm_get_interface(data->intf); + if (err < 0) + goto err_free_wc; + + err = __hci_cmd_send(hdev, 0xfc6f, hlen, wc); + + if (err < 0) { + clear_bit(BTMTK_TX_WAIT_VND_EVT, &data->flags); + usb_autopm_put_interface(data->intf); + goto err_free_wc; + } + + /* Submit control IN URB on demand to process the WMT event */ + err = btmtk_usb_submit_wmt_recv_urb(hdev); + + usb_autopm_put_interface(data->intf); + + if (err < 0) + goto err_free_wc; + + /* The vendor specific WMT commands are all answered by a vendor + * specific event and will have the Command Status or Command + * Complete as with usual HCI command flow control. + * + * After sending the command, wait for BTUSB_TX_WAIT_VND_EVT + * state to be cleared. The driver specific event receive routine + * will clear that state and with that indicate completion of the + * WMT command. + */ + err = wait_on_bit_timeout(&data->flags, BTMTK_TX_WAIT_VND_EVT, + TASK_INTERRUPTIBLE, HCI_INIT_TIMEOUT); + if (err == -EINTR) { + bt_dev_err(hdev, "Execution of wmt command interrupted"); + clear_bit(BTMTK_TX_WAIT_VND_EVT, &data->flags); + goto err_free_wc; + } + + if (err) { + bt_dev_err(hdev, "Execution of wmt command timed out"); + clear_bit(BTMTK_TX_WAIT_VND_EVT, &data->flags); + err = -ETIMEDOUT; + goto err_free_wc; + } + + if (data->evt_skb == NULL) + goto err_free_wc; + + /* Parse and handle the return WMT event */ + wmt_evt = (struct btmtk_hci_wmt_evt *)data->evt_skb->data; + if (wmt_evt->whdr.op != hdr->op) { + bt_dev_err(hdev, "Wrong op received %d expected %d", + wmt_evt->whdr.op, hdr->op); + err = -EIO; + goto err_free_skb; + } + + switch (wmt_evt->whdr.op) { + case BTMTK_WMT_SEMAPHORE: + if (wmt_evt->whdr.flag == 2) + status = BTMTK_WMT_PATCH_UNDONE; + else + status = BTMTK_WMT_PATCH_DONE; + break; + case BTMTK_WMT_FUNC_CTRL: + wmt_evt_funcc = (struct btmtk_hci_wmt_evt_funcc *)wmt_evt; + if (be16_to_cpu(wmt_evt_funcc->status) == 0x404) + status = BTMTK_WMT_ON_DONE; + else if (be16_to_cpu(wmt_evt_funcc->status) == 0x420) + status = BTMTK_WMT_ON_PROGRESS; + else + status = BTMTK_WMT_ON_UNDONE; + break; + case BTMTK_WMT_PATCH_DWNLD: + if (wmt_evt->whdr.flag == 2) + status = BTMTK_WMT_PATCH_DONE; + else if (wmt_evt->whdr.flag == 1) + status = BTMTK_WMT_PATCH_PROGRESS; + else + status = BTMTK_WMT_PATCH_UNDONE; + break; + } + + if (wmt_params->status) + *wmt_params->status = status; + +err_free_skb: + kfree_skb(data->evt_skb); + data->evt_skb = NULL; +err_free_wc: + kfree(wc); + return err; +} + +static int btmtk_usb_func_query(struct hci_dev *hdev) +{ + struct btmtk_hci_wmt_params wmt_params; + int status, err; + u8 param = 0; + + /* Query whether the function is enabled */ + wmt_params.op = BTMTK_WMT_FUNC_CTRL; + wmt_params.flag = 4; + wmt_params.dlen = sizeof(param); + wmt_params.data = ¶m; + wmt_params.status = &status; + + err = btmtk_usb_hci_wmt_sync(hdev, &wmt_params); + if (err < 0) { + bt_dev_err(hdev, "Failed to query function status (%d)", err); + return err; + } + + return status; +} + +static int btmtk_usb_uhw_reg_write(struct hci_dev *hdev, u32 reg, u32 val) +{ + struct btmtk_data *data = hci_get_priv(hdev); + int pipe, err; + void *buf; + + buf = kzalloc(4, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + put_unaligned_le32(val, buf); + + pipe = usb_sndctrlpipe(data->udev, 0); + err = usb_control_msg(data->udev, pipe, 0x02, + 0x5E, + reg >> 16, reg & 0xffff, + buf, 4, USB_CTRL_SET_TIMEOUT); + if (err < 0) + bt_dev_err(hdev, "Failed to write uhw reg(%d)", err); + + kfree(buf); + + return err; +} + +static int btmtk_usb_uhw_reg_read(struct hci_dev *hdev, u32 reg, u32 *val) +{ + struct btmtk_data *data = hci_get_priv(hdev); + int pipe, err; + void *buf; + + buf = kzalloc(4, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pipe = usb_rcvctrlpipe(data->udev, 0); + err = usb_control_msg(data->udev, pipe, 0x01, + 0xDE, + reg >> 16, reg & 0xffff, + buf, 4, USB_CTRL_GET_TIMEOUT); + if (err < 0) { + bt_dev_err(hdev, "Failed to read uhw reg(%d)", err); + goto err_free_buf; + } + + *val = get_unaligned_le32(buf); + bt_dev_dbg(hdev, "reg=%x, value=0x%08x", reg, *val); + +err_free_buf: + kfree(buf); + + return err; +} + +static int btmtk_usb_reg_read(struct hci_dev *hdev, u32 reg, u32 *val) +{ + struct btmtk_data *data = hci_get_priv(hdev); + int pipe, err, size = sizeof(u32); + void *buf; + + buf = kzalloc(size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pipe = usb_rcvctrlpipe(data->udev, 0); + err = usb_control_msg(data->udev, pipe, 0x63, + USB_TYPE_VENDOR | USB_DIR_IN, + reg >> 16, reg & 0xffff, + buf, size, USB_CTRL_GET_TIMEOUT); + if (err < 0) + goto err_free_buf; + + *val = get_unaligned_le32(buf); + +err_free_buf: + kfree(buf); + + return err; +} + +static int btmtk_usb_id_get(struct hci_dev *hdev, u32 reg, u32 *id) +{ + return btmtk_usb_reg_read(hdev, reg, id); +} + +static u32 btmtk_usb_reset_done(struct hci_dev *hdev) +{ + u32 val = 0; + + btmtk_usb_uhw_reg_read(hdev, MTK_BT_MISC, &val); + + return val & MTK_BT_RST_DONE; +} + +int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id) +{ + u32 val; + int err; + + if (dev_id == 0x7922) { + err = btmtk_usb_uhw_reg_read(hdev, MTK_BT_SUBSYS_RST, &val); + if (err < 0) + return err; + val |= 0x00002020; + err = btmtk_usb_uhw_reg_write(hdev, MTK_BT_SUBSYS_RST, val); + if (err < 0) + return err; + err = btmtk_usb_uhw_reg_write(hdev, MTK_EP_RST_OPT, 0x00010001); + if (err < 0) + return err; + err = btmtk_usb_uhw_reg_read(hdev, MTK_BT_SUBSYS_RST, &val); + if (err < 0) + return err; + val |= BIT(0); + err = btmtk_usb_uhw_reg_write(hdev, MTK_BT_SUBSYS_RST, val); + if (err < 0) + return err; + msleep(100); + } else if (dev_id == 0x7925) { + err = btmtk_usb_uhw_reg_read(hdev, MTK_BT_RESET_REG_CONNV3, &val); + if (err < 0) + return err; + val |= (1 << 5); + err = btmtk_usb_uhw_reg_write(hdev, MTK_BT_RESET_REG_CONNV3, val); + if (err < 0) + return err; + err = btmtk_usb_uhw_reg_read(hdev, MTK_BT_RESET_REG_CONNV3, &val); + if (err < 0) + return err; + val &= 0xFFFF00FF; + val |= (1 << 13); + err = btmtk_usb_uhw_reg_write(hdev, MTK_BT_RESET_REG_CONNV3, val); + if (err < 0) + return err; + err = btmtk_usb_uhw_reg_write(hdev, MTK_EP_RST_OPT, 0x00010001); + if (err < 0) + return err; + err = btmtk_usb_uhw_reg_read(hdev, MTK_BT_RESET_REG_CONNV3, &val); + if (err < 0) + return err; + val |= (1 << 0); + err = btmtk_usb_uhw_reg_write(hdev, MTK_BT_RESET_REG_CONNV3, val); + if (err < 0) + return err; + err = btmtk_usb_uhw_reg_write(hdev, MTK_UDMA_INT_STA_BT, 0x000000FF); + if (err < 0) + return err; + err = btmtk_usb_uhw_reg_read(hdev, MTK_UDMA_INT_STA_BT, &val); + if (err < 0) + return err; + err = btmtk_usb_uhw_reg_write(hdev, MTK_UDMA_INT_STA_BT1, 0x000000FF); + if (err < 0) + return err; + err = btmtk_usb_uhw_reg_read(hdev, MTK_UDMA_INT_STA_BT1, &val); + if (err < 0) + return err; + msleep(100); + } else { + /* It's Device EndPoint Reset Option Register */ + bt_dev_dbg(hdev, "Initiating reset mechanism via uhw"); + err = btmtk_usb_uhw_reg_write(hdev, MTK_EP_RST_OPT, MTK_EP_RST_IN_OUT_OPT); + if (err < 0) + return err; + err = btmtk_usb_uhw_reg_read(hdev, MTK_BT_WDT_STATUS, &val); + if (err < 0) + return err; + /* Reset the bluetooth chip via USB interface. */ + err = btmtk_usb_uhw_reg_write(hdev, MTK_BT_SUBSYS_RST, 1); + if (err < 0) + return err; + err = btmtk_usb_uhw_reg_write(hdev, MTK_UDMA_INT_STA_BT, 0x000000FF); + if (err < 0) + return err; + err = btmtk_usb_uhw_reg_read(hdev, MTK_UDMA_INT_STA_BT, &val); + if (err < 0) + return err; + err = btmtk_usb_uhw_reg_write(hdev, MTK_UDMA_INT_STA_BT1, 0x000000FF); + if (err < 0) + return err; + err = btmtk_usb_uhw_reg_read(hdev, MTK_UDMA_INT_STA_BT1, &val); + if (err < 0) + return err; + /* MT7921 need to delay 20ms between toggle reset bit */ + msleep(20); + err = btmtk_usb_uhw_reg_write(hdev, MTK_BT_SUBSYS_RST, 0); + if (err < 0) + return err; + err = btmtk_usb_uhw_reg_read(hdev, MTK_BT_SUBSYS_RST, &val); + if (err < 0) + return err; + } + + err = readx_poll_timeout(btmtk_usb_reset_done, hdev, val, + val & MTK_BT_RST_DONE, 20000, 1000000); + if (err < 0) + bt_dev_err(hdev, "Reset timeout"); + + if (dev_id == 0x7922) { + err = btmtk_usb_uhw_reg_write(hdev, MTK_UDMA_INT_STA_BT, 0x000000FF); + if (err < 0) + return err; + } + + err = btmtk_usb_id_get(hdev, 0x70010200, &val); + if (err < 0 || !val) + bt_dev_err(hdev, "Can't get device id, subsys reset fail."); + + return err; +} +EXPORT_SYMBOL_GPL(btmtk_usb_subsys_reset); + +int btmtk_usb_recv_acl(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct btmtk_data *data = hci_get_priv(hdev); + u16 handle = le16_to_cpu(hci_acl_hdr(skb)->handle); + + switch (handle) { + case 0xfc6f: /* Firmware dump from device */ + /* When the firmware hangs, the device can no longer + * suspend and thus disable auto-suspend. + */ + usb_disable_autosuspend(data->udev); + + /* We need to forward the diagnostic packet to userspace daemon + * for backward compatibility, so we have to clone the packet + * extraly for the in-kernel coredump support. + */ + if (IS_ENABLED(CONFIG_DEV_COREDUMP)) { + struct sk_buff *skb_cd = skb_clone(skb, GFP_ATOMIC); + + if (skb_cd) + btmtk_process_coredump(hdev, skb_cd); + } + + fallthrough; + case 0x05ff: /* Firmware debug logging 1 */ + case 0x05fe: /* Firmware debug logging 2 */ + return hci_recv_diag(hdev, skb); + } + + return hci_recv_frame(hdev, skb); +} +EXPORT_SYMBOL_GPL(btmtk_usb_recv_acl); + +static int btmtk_isopkt_pad(struct hci_dev *hdev, struct sk_buff *skb) +{ + if (skb->len > MTK_ISO_THRESHOLD) + return -EINVAL; + + if (skb_pad(skb, MTK_ISO_THRESHOLD - skb->len)) + return -ENOMEM; + + __skb_put(skb, MTK_ISO_THRESHOLD - skb->len); + + return 0; +} + +static int __set_mtk_intr_interface(struct hci_dev *hdev) +{ + struct btmtk_data *btmtk_data = hci_get_priv(hdev); + struct usb_interface *intf = btmtk_data->isopkt_intf; + int i, err; + + if (!btmtk_data->isopkt_intf) + return -ENODEV; + + err = usb_set_interface(btmtk_data->udev, MTK_ISO_IFNUM, 1); + if (err < 0) { + bt_dev_err(hdev, "setting interface failed (%d)", -err); + return err; + } + + btmtk_data->isopkt_tx_ep = NULL; + btmtk_data->isopkt_rx_ep = NULL; + + for (i = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++) { + struct usb_endpoint_descriptor *ep_desc; + + ep_desc = &intf->cur_altsetting->endpoint[i].desc; + + if (!btmtk_data->isopkt_tx_ep && + usb_endpoint_is_int_out(ep_desc)) { + btmtk_data->isopkt_tx_ep = ep_desc; + continue; + } + + if (!btmtk_data->isopkt_rx_ep && + usb_endpoint_is_int_in(ep_desc)) { + btmtk_data->isopkt_rx_ep = ep_desc; + continue; + } + } + + if (!btmtk_data->isopkt_tx_ep || + !btmtk_data->isopkt_rx_ep) { + bt_dev_err(hdev, "invalid interrupt descriptors"); + return -ENODEV; + } + + return 0; +} + +struct urb *alloc_mtk_intr_urb(struct hci_dev *hdev, struct sk_buff *skb, + usb_complete_t tx_complete) +{ + struct btmtk_data *btmtk_data = hci_get_priv(hdev); + struct urb *urb; + unsigned int pipe; + + if (!btmtk_data->isopkt_tx_ep) + return ERR_PTR(-ENODEV); + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return ERR_PTR(-ENOMEM); + + if (btmtk_isopkt_pad(hdev, skb)) + return ERR_PTR(-EINVAL); + + pipe = usb_sndintpipe(btmtk_data->udev, + btmtk_data->isopkt_tx_ep->bEndpointAddress); + + usb_fill_int_urb(urb, btmtk_data->udev, pipe, + skb->data, skb->len, tx_complete, + skb, btmtk_data->isopkt_tx_ep->bInterval); + + skb->dev = (void *)hdev; + + return urb; +} +EXPORT_SYMBOL_GPL(alloc_mtk_intr_urb); + +static int btmtk_recv_isopkt(struct hci_dev *hdev, void *buffer, int count) +{ + struct btmtk_data *btmtk_data = hci_get_priv(hdev); + struct sk_buff *skb; + unsigned long flags; + int err = 0; + + spin_lock_irqsave(&btmtk_data->isorxlock, flags); + skb = btmtk_data->isopkt_skb; + + while (count) { + int len; + + if (!skb) { + skb = bt_skb_alloc(HCI_MAX_ISO_SIZE, GFP_ATOMIC); + if (!skb) { + err = -ENOMEM; + break; + } + + hci_skb_pkt_type(skb) = HCI_ISODATA_PKT; + hci_skb_expect(skb) = HCI_ISO_HDR_SIZE; + } + + len = min_t(uint, hci_skb_expect(skb), count); + skb_put_data(skb, buffer, len); + + count -= len; + buffer += len; + hci_skb_expect(skb) -= len; + + if (skb->len == HCI_ISO_HDR_SIZE) { + __le16 dlen = ((struct hci_iso_hdr *)skb->data)->dlen; + + /* Complete ISO header */ + hci_skb_expect(skb) = __le16_to_cpu(dlen); + + if (skb_tailroom(skb) < hci_skb_expect(skb)) { + kfree_skb(skb); + skb = NULL; + + err = -EILSEQ; + break; + } + } + + if (!hci_skb_expect(skb)) { + /* Complete frame */ + hci_recv_frame(hdev, skb); + skb = NULL; + } + } + + btmtk_data->isopkt_skb = skb; + spin_unlock_irqrestore(&btmtk_data->isorxlock, flags); + + return err; +} + +static void btmtk_intr_complete(struct urb *urb) +{ + struct hci_dev *hdev = urb->context; + struct btmtk_data *btmtk_data = hci_get_priv(hdev); + int err; + + BT_DBG("%s urb %p status %d count %d", hdev->name, urb, urb->status, + urb->actual_length); + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return; + + if (hdev->suspended) + return; + + if (urb->status == 0) { + hdev->stat.byte_rx += urb->actual_length; + + if (btmtk_recv_isopkt(hdev, urb->transfer_buffer, + urb->actual_length) < 0) { + bt_dev_err(hdev, "corrupted iso packet"); + hdev->stat.err_rx++; + } + } else if (urb->status == -ENOENT) { + /* Avoid suspend failed when usb_kill_urb */ + return; + } + + usb_mark_last_busy(btmtk_data->udev); + usb_anchor_urb(urb, &btmtk_data->isopkt_anchor); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { + /* -EPERM: urb is being killed; + * -ENODEV: device got disconnected + */ + if (err != -EPERM && err != -ENODEV) + bt_dev_err(hdev, "urb %p failed to resubmit (%d)", + urb, -err); + if (err != -EPERM) + hci_cmd_sync_cancel(hdev, -err); + usb_unanchor_urb(urb); + } +} + +static int btmtk_submit_intr_urb(struct hci_dev *hdev, gfp_t mem_flags) +{ + struct btmtk_data *btmtk_data = hci_get_priv(hdev); + unsigned char *buf; + unsigned int pipe; + struct urb *urb; + int err, size; + + BT_DBG("%s", hdev->name); + + if (!btmtk_data->isopkt_rx_ep) + return -ENODEV; + + urb = usb_alloc_urb(0, mem_flags); + if (!urb) + return -ENOMEM; + size = le16_to_cpu(btmtk_data->isopkt_rx_ep->wMaxPacketSize); + + buf = kmalloc(size, mem_flags); + if (!buf) { + usb_free_urb(urb); + return -ENOMEM; + } + + pipe = usb_rcvintpipe(btmtk_data->udev, + btmtk_data->isopkt_rx_ep->bEndpointAddress); + + usb_fill_int_urb(urb, btmtk_data->udev, pipe, buf, size, + btmtk_intr_complete, hdev, + btmtk_data->isopkt_rx_ep->bInterval); + + urb->transfer_flags |= URB_FREE_BUFFER; + + usb_mark_last_busy(btmtk_data->udev); + usb_anchor_urb(urb, &btmtk_data->isopkt_anchor); + + err = usb_submit_urb(urb, mem_flags); + if (err < 0) { + if (err != -EPERM && err != -ENODEV) + bt_dev_err(hdev, "urb %p submission failed (%d)", + urb, -err); + usb_unanchor_urb(urb); + } + + usb_free_urb(urb); + + return err; +} + +static int btmtk_usb_isointf_init(struct hci_dev *hdev) +{ + struct btmtk_data *btmtk_data = hci_get_priv(hdev); + u8 iso_param[2] = { 0x08, 0x01 }; + struct sk_buff *skb; + int err; + + init_usb_anchor(&btmtk_data->isopkt_anchor); + spin_lock_init(&btmtk_data->isorxlock); + + __set_mtk_intr_interface(hdev); + + err = btmtk_submit_intr_urb(hdev, GFP_KERNEL); + if (err < 0) { + usb_kill_anchored_urbs(&btmtk_data->isopkt_anchor); + bt_dev_err(hdev, "ISO intf not support (%d)", err); + return err; + } + + skb = __hci_cmd_sync(hdev, 0xfd98, sizeof(iso_param), iso_param, + HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + bt_dev_err(hdev, "Failed to apply iso setting (%ld)", PTR_ERR(skb)); + return PTR_ERR(skb); + } + kfree_skb(skb); + + return 0; +} + +int btmtk_usb_resume(struct hci_dev *hdev) +{ + /* This function describes the specific additional steps taken by MediaTek + * when Bluetooth usb driver's resume function is called. + */ + struct btmtk_data *btmtk_data = hci_get_priv(hdev); + + /* Resubmit urb for iso data transmission */ + if (test_bit(BTMTK_ISOPKT_RUNNING, &btmtk_data->flags)) { + if (btmtk_submit_intr_urb(hdev, GFP_NOIO) < 0) + clear_bit(BTMTK_ISOPKT_RUNNING, &btmtk_data->flags); + } + + return 0; +} +EXPORT_SYMBOL_GPL(btmtk_usb_resume); + +int btmtk_usb_suspend(struct hci_dev *hdev) +{ + /* This function describes the specific additional steps taken by MediaTek + * when Bluetooth usb driver's suspend function is called. + */ + struct btmtk_data *btmtk_data = hci_get_priv(hdev); + + /* Stop urb anchor for iso data transmission */ + usb_kill_anchored_urbs(&btmtk_data->isopkt_anchor); + + return 0; +} +EXPORT_SYMBOL_GPL(btmtk_usb_suspend); + +int btmtk_usb_setup(struct hci_dev *hdev) +{ + struct btmtk_data *btmtk_data = hci_get_priv(hdev); + struct btmtk_hci_wmt_params wmt_params; + ktime_t calltime, delta, rettime; + struct btmtk_tci_sleep tci_sleep; + unsigned long long duration; + struct sk_buff *skb; + const char *fwname; + int err, status; + u32 dev_id = 0; + char fw_bin_name[64]; + u32 fw_version = 0, fw_flavor = 0; + u8 param; + + calltime = ktime_get(); + + err = btmtk_usb_id_get(hdev, 0x80000008, &dev_id); + if (err < 0) { + bt_dev_err(hdev, "Failed to get device id (%d)", err); + return err; + } + + if (!dev_id || dev_id != 0x7663) { + err = btmtk_usb_id_get(hdev, 0x70010200, &dev_id); + if (err < 0) { + bt_dev_err(hdev, "Failed to get device id (%d)", err); + return err; + } + err = btmtk_usb_id_get(hdev, 0x80021004, &fw_version); + if (err < 0) { + bt_dev_err(hdev, "Failed to get fw version (%d)", err); + return err; + } + err = btmtk_usb_id_get(hdev, 0x70010020, &fw_flavor); + if (err < 0) { + bt_dev_err(hdev, "Failed to get fw flavor (%d)", err); + return err; + } + fw_flavor = (fw_flavor & 0x00000080) >> 7; + } + + btmtk_data->dev_id = dev_id; + + err = btmtk_register_coredump(hdev, btmtk_data->drv_name, fw_version); + if (err < 0) + bt_dev_err(hdev, "Failed to register coredump (%d)", err); + + switch (dev_id) { + case 0x7663: + fwname = FIRMWARE_MT7663; + break; + case 0x7668: + fwname = FIRMWARE_MT7668; + break; + case 0x7922: + case 0x7961: + case 0x7925: + /* Reset the device to ensure it's in the initial state before + * downloading the firmware to ensure. + */ + + if (!test_bit(BTMTK_FIRMWARE_LOADED, &btmtk_data->flags)) + btmtk_usb_subsys_reset(hdev, dev_id); + + btmtk_fw_get_filename(fw_bin_name, sizeof(fw_bin_name), dev_id, + fw_version, fw_flavor); + + err = btmtk_setup_firmware_79xx(hdev, fw_bin_name, + btmtk_usb_hci_wmt_sync); + if (err < 0) { + bt_dev_err(hdev, "Failed to set up firmware (%d)", err); + clear_bit(BTMTK_FIRMWARE_LOADED, &btmtk_data->flags); + return err; + } + + set_bit(BTMTK_FIRMWARE_LOADED, &btmtk_data->flags); + + /* It's Device EndPoint Reset Option Register */ + err = btmtk_usb_uhw_reg_write(hdev, MTK_EP_RST_OPT, + MTK_EP_RST_IN_OUT_OPT); + if (err < 0) + return err; + + /* Enable Bluetooth protocol */ + param = 1; + wmt_params.op = BTMTK_WMT_FUNC_CTRL; + wmt_params.flag = 0; + wmt_params.dlen = sizeof(param); + wmt_params.data = ¶m; + wmt_params.status = NULL; + + err = btmtk_usb_hci_wmt_sync(hdev, &wmt_params); + if (err < 0) { + bt_dev_err(hdev, "Failed to send wmt func ctrl (%d)", err); + return err; + } + + hci_set_msft_opcode(hdev, 0xFD30); + hci_set_aosp_capable(hdev); + + /* Set up ISO interface after protocol enabled */ + if (test_bit(BTMTK_ISOPKT_OVER_INTR, &btmtk_data->flags)) { + if (!btmtk_usb_isointf_init(hdev)) + set_bit(BTMTK_ISOPKT_RUNNING, &btmtk_data->flags); + } + + goto done; + default: + bt_dev_err(hdev, "Unsupported hardware variant (%08x)", + dev_id); + return -ENODEV; + } + + /* Query whether the firmware is already download */ + wmt_params.op = BTMTK_WMT_SEMAPHORE; + wmt_params.flag = 1; + wmt_params.dlen = 0; + wmt_params.data = NULL; + wmt_params.status = &status; + + err = btmtk_usb_hci_wmt_sync(hdev, &wmt_params); + if (err < 0) { + bt_dev_err(hdev, "Failed to query firmware status (%d)", err); + return err; + } + + if (status == BTMTK_WMT_PATCH_DONE) { + bt_dev_info(hdev, "firmware already downloaded"); + goto ignore_setup_fw; + } + + /* Setup a firmware which the device definitely requires */ + err = btmtk_setup_firmware(hdev, fwname, + btmtk_usb_hci_wmt_sync); + if (err < 0) + return err; + +ignore_setup_fw: + err = readx_poll_timeout(btmtk_usb_func_query, hdev, status, + status < 0 || status != BTMTK_WMT_ON_PROGRESS, + 2000, 5000000); + /* -ETIMEDOUT happens */ + if (err < 0) + return err; + + /* The other errors happen in btmtk_usb_func_query */ + if (status < 0) + return status; + + if (status == BTMTK_WMT_ON_DONE) { + bt_dev_info(hdev, "function already on"); + goto ignore_func_on; + } + + /* Enable Bluetooth protocol */ + param = 1; + wmt_params.op = BTMTK_WMT_FUNC_CTRL; + wmt_params.flag = 0; + wmt_params.dlen = sizeof(param); + wmt_params.data = ¶m; + wmt_params.status = NULL; + + err = btmtk_usb_hci_wmt_sync(hdev, &wmt_params); + if (err < 0) { + bt_dev_err(hdev, "Failed to send wmt func ctrl (%d)", err); + return err; + } + +ignore_func_on: + /* Apply the low power environment setup */ + tci_sleep.mode = 0x5; + tci_sleep.duration = cpu_to_le16(0x640); + tci_sleep.host_duration = cpu_to_le16(0x640); + tci_sleep.host_wakeup_pin = 0; + tci_sleep.time_compensation = 0; + + skb = __hci_cmd_sync(hdev, 0xfc7a, sizeof(tci_sleep), &tci_sleep, + HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + err = PTR_ERR(skb); + bt_dev_err(hdev, "Failed to apply low power setting (%d)", err); + return err; + } + kfree_skb(skb); + +done: + rettime = ktime_get(); + delta = ktime_sub(rettime, calltime); + duration = (unsigned long long)ktime_to_ns(delta) >> 10; + + bt_dev_info(hdev, "Device setup in %llu usecs", duration); + + return 0; +} +EXPORT_SYMBOL_GPL(btmtk_usb_setup); + +int btmtk_usb_shutdown(struct hci_dev *hdev) +{ + struct btmtk_hci_wmt_params wmt_params; + u8 param = 0; + int err; + + /* Disable the device */ + wmt_params.op = BTMTK_WMT_FUNC_CTRL; + wmt_params.flag = 0; + wmt_params.dlen = sizeof(param); + wmt_params.data = ¶m; + wmt_params.status = NULL; + + err = btmtk_usb_hci_wmt_sync(hdev, &wmt_params); + if (err < 0) { + bt_dev_err(hdev, "Failed to send wmt func ctrl (%d)", err); + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(btmtk_usb_shutdown); + MODULE_AUTHOR("Sean Wang <sean.wang@mediatek.com>"); MODULE_AUTHOR("Mark Chen <mark-yw.chen@mediatek.com>"); MODULE_DESCRIPTION("Bluetooth support for MediaTek devices ver " VERSION); diff --git a/drivers/bluetooth/btmtk.h b/drivers/bluetooth/btmtk.h index cbcdb99a22e6..5df7c3296624 100644 --- a/drivers/bluetooth/btmtk.h +++ b/drivers/bluetooth/btmtk.h @@ -28,6 +28,21 @@ #define MTK_COREDUMP_END_LEN (sizeof(MTK_COREDUMP_END)) #define MTK_COREDUMP_NUM 255 +/* UHW CR mapping */ +#define MTK_BT_MISC 0x70002510 +#define MTK_BT_SUBSYS_RST 0x70002610 +#define MTK_UDMA_INT_STA_BT 0x74000024 +#define MTK_UDMA_INT_STA_BT1 0x74000308 +#define MTK_BT_WDT_STATUS 0x740003A0 +#define MTK_EP_RST_OPT 0x74011890 +#define MTK_EP_RST_IN_OUT_OPT 0x00010001 +#define MTK_BT_RST_DONE 0x00000100 +#define MTK_BT_RESET_REG_CONNV3 0x70028610 +#define MTK_BT_READ_DEV_ID 0x70010200 + +/* MediaTek ISO Interface */ +#define MTK_ISO_IFNUM 2 + enum { BTMTK_WMT_PATCH_DWNLD = 0x1, BTMTK_WMT_TEST = 0x2, @@ -126,6 +141,14 @@ struct btmtk_hci_wmt_params { u32 *status; }; +enum { + BTMTK_TX_WAIT_VND_EVT, + BTMTK_FIRMWARE_LOADED, + BTMTK_HW_RESET_ACTIVE, + BTMTK_ISOPKT_OVER_INTR, + BTMTK_ISOPKT_RUNNING, +}; + typedef int (*btmtk_reset_sync_func_t)(struct hci_dev *, void *); struct btmtk_coredump_info { @@ -135,10 +158,25 @@ struct btmtk_coredump_info { int state; }; -struct btmediatek_data { +struct btmtk_data { + const char *drv_name; + unsigned long flags; u32 dev_id; btmtk_reset_sync_func_t reset_sync; struct btmtk_coredump_info cd_info; + + struct usb_device *udev; + struct usb_interface *intf; + struct usb_anchor *ctrl_anchor; + struct sk_buff *evt_skb; + struct usb_endpoint_descriptor *isopkt_tx_ep; + struct usb_endpoint_descriptor *isopkt_rx_ep; + struct usb_interface *isopkt_intf; + struct usb_anchor isopkt_anchor; + struct sk_buff *isopkt_skb; + + /* spinlock for ISO data transmission */ + spinlock_t isorxlock; }; typedef int (*wmt_cmd_sync_func_t)(struct hci_dev *, @@ -160,6 +198,24 @@ int btmtk_register_coredump(struct hci_dev *hdev, const char *name, u32 fw_version); int btmtk_process_coredump(struct hci_dev *hdev, struct sk_buff *skb); + +void btmtk_fw_get_filename(char *buf, size_t size, u32 dev_id, u32 fw_ver, + u32 fw_flavor); + +int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id); + +int btmtk_usb_recv_acl(struct hci_dev *hdev, struct sk_buff *skb); + +struct urb *alloc_mtk_intr_urb(struct hci_dev *hdev, struct sk_buff *skb, + usb_complete_t tx_complete); + +int btmtk_usb_resume(struct hci_dev *hdev); + +int btmtk_usb_suspend(struct hci_dev *hdev); + +int btmtk_usb_setup(struct hci_dev *hdev); + +int btmtk_usb_shutdown(struct hci_dev *hdev); #else static inline int btmtk_set_bdaddr(struct hci_dev *hdev, @@ -168,29 +224,73 @@ static inline int btmtk_set_bdaddr(struct hci_dev *hdev, return -EOPNOTSUPP; } -static int btmtk_setup_firmware_79xx(struct hci_dev *hdev, const char *fwname, - wmt_cmd_sync_func_t wmt_cmd_sync) +static inline int btmtk_setup_firmware_79xx(struct hci_dev *hdev, + const char *fwname, + wmt_cmd_sync_func_t wmt_cmd_sync) { return -EOPNOTSUPP; } -static int btmtk_setup_firmware(struct hci_dev *hdev, const char *fwname, - wmt_cmd_sync_func_t wmt_cmd_sync) +static inline int btmtk_setup_firmware(struct hci_dev *hdev, const char *fwname, + wmt_cmd_sync_func_t wmt_cmd_sync) { return -EOPNOTSUPP; } -static void btmtk_reset_sync(struct hci_dev *hdev) +static inline void btmtk_reset_sync(struct hci_dev *hdev) +{ +} + +static inline int btmtk_register_coredump(struct hci_dev *hdev, + const char *name, u32 fw_version) { + return -EOPNOTSUPP; +} + +static inline int btmtk_process_coredump(struct hci_dev *hdev, + struct sk_buff *skb) +{ + return -EOPNOTSUPP; +} + +static inline void btmtk_fw_get_filename(char *buf, size_t size, u32 dev_id, + u32 fw_ver, u32 fw_flavor) +{ +} + +static inline int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id) +{ + return -EOPNOTSUPP; +} + +static inline int btmtk_usb_recv_acl(struct hci_dev *hdev, struct sk_buff *skb) +{ + return -EOPNOTSUPP; +} + +static inline struct urb *alloc_mtk_intr_urb(struct hci_dev *hdev, + struct sk_buff *skb, + usb_complete_t tx_complete) +{ + return ERR_PTR(-EOPNOTSUPP); +} + +static inline int btmtk_usb_resume(struct hci_dev *hdev) +{ + return -EOPNOTSUPP; +} + +static inline int btmtk_usb_suspend(struct hci_dev *hdev) +{ + return -EOPNOTSUPP; } -static int btmtk_register_coredump(struct hci_dev *hdev, const char *name, - u32 fw_version) +static inline int btmtk_usb_setup(struct hci_dev *hdev) { return -EOPNOTSUPP; } -static int btmtk_process_coredump(struct hci_dev *hdev, struct sk_buff *skb) +static inline int btmtk_usb_shutdown(struct hci_dev *hdev) { return -EOPNOTSUPP; } diff --git a/drivers/bluetooth/btmtksdio.c b/drivers/bluetooth/btmtksdio.c index 8ded9ef8089a..39d6898497a4 100644 --- a/drivers/bluetooth/btmtksdio.c +++ b/drivers/bluetooth/btmtksdio.c @@ -20,6 +20,7 @@ #include <linux/of.h> #include <linux/pm_runtime.h> #include <linux/skbuff.h> +#include <linux/usb.h> #include <linux/mmc/host.h> #include <linux/mmc/sdio_ids.h> @@ -1117,6 +1118,9 @@ static int btmtksdio_setup(struct hci_dev *hdev) return err; } + btmtk_fw_get_filename(fwname, sizeof(fwname), dev_id, + fw_version, 0); + snprintf(fwname, sizeof(fwname), "mediatek/BT_RAM_CODE_MT%04x_1_%x_hdr.bin", dev_id & 0xffff, (fw_version & 0xff) + 1); diff --git a/drivers/bluetooth/btmtkuart.c b/drivers/bluetooth/btmtkuart.c index e6bc4a73c9fc..aa87c3e78871 100644 --- a/drivers/bluetooth/btmtkuart.c +++ b/drivers/bluetooth/btmtkuart.c @@ -22,6 +22,7 @@ #include <linux/regulator/consumer.h> #include <linux/serdev.h> #include <linux/skbuff.h> +#include <linux/usb.h> #include <net/bluetooth/bluetooth.h> #include <net/bluetooth/hci_core.h> diff --git a/drivers/bluetooth/btnxpuart.c b/drivers/bluetooth/btnxpuart.c index 9bfa9a6ad56c..31d3dd90b672 100644 --- a/drivers/bluetooth/btnxpuart.c +++ b/drivers/bluetooth/btnxpuart.c @@ -29,27 +29,38 @@ #define BTNXPUART_CHECK_BOOT_SIGNATURE 3 #define BTNXPUART_SERDEV_OPEN 4 #define BTNXPUART_IR_IN_PROGRESS 5 +#define BTNXPUART_FW_DOWNLOAD_ABORT 6 /* NXP HW err codes */ #define BTNXPUART_IR_HW_ERR 0xb0 -#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_IW624 "nxp/uartiw624_bt.bin" -#define FIRMWARE_SECURE_IW624 "nxp/uartiw624_bt.bin.se" -#define FIRMWARE_AW693 "nxp/uartaw693_bt.bin" -#define FIRMWARE_SECURE_AW693 "nxp/uartaw693_bt.bin.se" -#define FIRMWARE_HELPER "nxp/helper_uart_3000000.bin" +#define FIRMWARE_W8987 "uart8987_bt_v0.bin" +#define FIRMWARE_W8987_OLD "uartuart8987_bt.bin" +#define FIRMWARE_W8997 "uart8997_bt_v4.bin" +#define FIRMWARE_W8997_OLD "uartuart8997_bt_v4.bin" +#define FIRMWARE_W9098 "uart9098_bt_v1.bin" +#define FIRMWARE_W9098_OLD "uartuart9098_bt_v1.bin" +#define FIRMWARE_IW416 "uartiw416_bt_v0.bin" +#define FIRMWARE_IW612 "uartspi_n61x_v1.bin.se" +#define FIRMWARE_IW615 "uartspi_iw610_v0.bin" +#define FIRMWARE_SECURE_IW615 "uartspi_iw610_v0.bin.se" +#define FIRMWARE_IW624 "uartiw624_bt.bin" +#define FIRMWARE_SECURE_IW624 "uartiw624_bt.bin.se" +#define FIRMWARE_AW693 "uartaw693_bt.bin" +#define FIRMWARE_SECURE_AW693 "uartaw693_bt.bin.se" +#define FIRMWARE_AW693_A1 "uartaw693_bt_v1.bin" +#define FIRMWARE_SECURE_AW693_A1 "uartaw693_bt_v1.bin.se" +#define FIRMWARE_HELPER "helper_uart_3000000.bin" #define CHIP_ID_W9098 0x5c03 #define CHIP_ID_IW416 0x7201 #define CHIP_ID_IW612 0x7601 #define CHIP_ID_IW624a 0x8000 #define CHIP_ID_IW624c 0x8001 -#define CHIP_ID_AW693 0x8200 +#define CHIP_ID_AW693a0 0x8200 +#define CHIP_ID_AW693a1 0x8201 +#define CHIP_ID_IW615a0 0x8800 +#define CHIP_ID_IW615a1 0x8801 #define FW_SECURE_MASK 0xc0 #define FW_OPEN 0x00 @@ -144,6 +155,7 @@ struct psmode_cmd_payload { struct btnxpuart_data { const char *helper_fw_name; const char *fw_name; + const char *fw_name_old; }; struct btnxpuart_dev { @@ -159,6 +171,7 @@ struct btnxpuart_dev { u8 fw_name[MAX_FW_FILE_NAME_LEN]; u32 fw_dnld_v1_offset; u32 fw_v1_sent_bytes; + u32 fw_dnld_v3_offset; u32 fw_v3_offset_correction; u32 fw_v1_expected_len; u32 boot_reg_offset; @@ -187,6 +200,11 @@ struct btnxpuart_dev { #define NXP_NAK_V3 0x7b #define NXP_CRC_ERROR_V3 0x7c +/* Bootloader signature error codes */ +#define NXP_ACK_RX_TIMEOUT 0x0002 /* ACK not received from host */ +#define NXP_HDR_RX_TIMEOUT 0x0003 /* FW Header chunk not received */ +#define NXP_DATA_RX_TIMEOUT 0x0004 /* FW Data chunk not received */ + #define HDR_LEN 16 #define NXP_RECV_CHIP_VER_V1 \ @@ -277,6 +295,17 @@ struct nxp_bootloader_cmd { __be32 crc; } __packed; +struct nxp_v3_rx_timeout_nak { + u8 nak; + __le32 offset; + u8 crc; +} __packed; + +union nxp_v3_rx_timeout_nak_u { + struct nxp_v3_rx_timeout_nak pkt; + u8 buf[6]; +}; + static u8 crc8_table[CRC8_TABLE_SIZE]; /* Default configurations */ @@ -328,7 +357,7 @@ 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); + timer_shutdown_sync(&psdata->ps_timer); } static void ps_control(struct hci_dev *hdev, u8 ps_state) @@ -550,6 +579,7 @@ static int nxp_download_firmware(struct hci_dev *hdev) nxpdev->fw_v1_sent_bytes = 0; nxpdev->fw_v1_expected_len = HDR_LEN; nxpdev->boot_reg_offset = 0; + nxpdev->fw_dnld_v3_offset = 0; nxpdev->fw_v3_offset_correction = 0; nxpdev->baudrate_changed = false; nxpdev->timeout_changed = false; @@ -564,14 +594,23 @@ static int nxp_download_firmware(struct hci_dev *hdev) !test_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state), msecs_to_jiffies(60000)); + + release_firmware(nxpdev->fw); + memset(nxpdev->fw_name, 0, sizeof(nxpdev->fw_name)); + if (err == 0) { - bt_dev_err(hdev, "FW Download Timeout."); + bt_dev_err(hdev, "FW Download Timeout. offset: %d", + nxpdev->fw_dnld_v1_offset ? + nxpdev->fw_dnld_v1_offset : + nxpdev->fw_dnld_v3_offset); return -ETIMEDOUT; } + if (test_bit(BTNXPUART_FW_DOWNLOAD_ABORT, &nxpdev->tx_state)) { + bt_dev_err(hdev, "FW Download Aborted"); + return -EINTR; + } serdev_device_set_flow_control(nxpdev->serdev, true); - release_firmware(nxpdev->fw); - memset(nxpdev->fw_name, 0, sizeof(nxpdev->fw_name)); /* Allow the downloaded FW to initialize */ msleep(1200); @@ -682,19 +721,30 @@ static bool process_boot_signature(struct btnxpuart_dev *nxpdev) return is_fw_downloading(nxpdev); } -static int nxp_request_firmware(struct hci_dev *hdev, const char *fw_name) +static int nxp_request_firmware(struct hci_dev *hdev, const char *fw_name, + const char *fw_name_old) { struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + const char *fw_name_dt; int err = 0; if (!fw_name) return -ENOENT; if (!strlen(nxpdev->fw_name)) { - snprintf(nxpdev->fw_name, MAX_FW_FILE_NAME_LEN, "%s", fw_name); + if (strcmp(fw_name, FIRMWARE_HELPER) && + !device_property_read_string(&nxpdev->serdev->dev, + "firmware-name", + &fw_name_dt)) + fw_name = fw_name_dt; + snprintf(nxpdev->fw_name, MAX_FW_FILE_NAME_LEN, "nxp/%s", fw_name); + err = request_firmware_direct(&nxpdev->fw, nxpdev->fw_name, &hdev->dev); + if (err < 0 && fw_name_old) { + snprintf(nxpdev->fw_name, MAX_FW_FILE_NAME_LEN, "nxp/%s", fw_name_old); + err = request_firmware_direct(&nxpdev->fw, nxpdev->fw_name, &hdev->dev); + } - bt_dev_dbg(hdev, "Request Firmware: %s", nxpdev->fw_name); - err = request_firmware(&nxpdev->fw, nxpdev->fw_name, &hdev->dev); + bt_dev_info(hdev, "Request Firmware: %s", nxpdev->fw_name); if (err < 0) { bt_dev_err(hdev, "Firmware file %s not found", nxpdev->fw_name); clear_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state); @@ -773,15 +823,15 @@ static int nxp_recv_fw_req_v1(struct hci_dev *hdev, struct sk_buff *skb) } if (!nxp_data->helper_fw_name || nxpdev->helper_downloaded) { - if (nxp_request_firmware(hdev, nxp_data->fw_name)) + if (nxp_request_firmware(hdev, nxp_data->fw_name, nxp_data->fw_name_old)) goto free_skb; } else if (nxp_data->helper_fw_name && !nxpdev->helper_downloaded) { - if (nxp_request_firmware(hdev, nxp_data->helper_fw_name)) + if (nxp_request_firmware(hdev, nxp_data->helper_fw_name, NULL)) goto free_skb; } if (!len) { - bt_dev_dbg(hdev, "FW Downloaded Successfully: %zu bytes", + bt_dev_info(hdev, "FW Download Complete: %zu bytes", nxpdev->fw->size); if (nxp_data->helper_fw_name && !nxpdev->helper_downloaded) { nxpdev->helper_downloaded = true; @@ -863,7 +913,7 @@ static char *nxp_get_fw_name_from_chipid(struct hci_dev *hdev, u16 chipid, else bt_dev_err(hdev, "Illegal loader version %02x", loader_ver); break; - case CHIP_ID_AW693: + case CHIP_ID_AW693a0: if ((loader_ver & FW_SECURE_MASK) == FW_OPEN) fw_name = FIRMWARE_AW693; else if ((loader_ver & FW_SECURE_MASK) != FW_AUTH_ILLEGAL) @@ -871,6 +921,23 @@ static char *nxp_get_fw_name_from_chipid(struct hci_dev *hdev, u16 chipid, else bt_dev_err(hdev, "Illegal loader version %02x", loader_ver); break; + case CHIP_ID_AW693a1: + if ((loader_ver & FW_SECURE_MASK) == FW_OPEN) + fw_name = FIRMWARE_AW693_A1; + else if ((loader_ver & FW_SECURE_MASK) != FW_AUTH_ILLEGAL) + fw_name = FIRMWARE_SECURE_AW693_A1; + else + bt_dev_err(hdev, "Illegal loader version %02x", loader_ver); + break; + case CHIP_ID_IW615a0: + case CHIP_ID_IW615a1: + if ((loader_ver & FW_SECURE_MASK) == FW_OPEN) + fw_name = FIRMWARE_IW615; + else if ((loader_ver & FW_SECURE_MASK) != FW_AUTH_ILLEGAL) + fw_name = FIRMWARE_SECURE_IW615; + else + bt_dev_err(hdev, "Illegal loader version %02x", loader_ver); + break; default: bt_dev_err(hdev, "Unknown chip signature %04x", chipid); break; @@ -878,10 +945,25 @@ static char *nxp_get_fw_name_from_chipid(struct hci_dev *hdev, u16 chipid, return fw_name; } +static char *nxp_get_old_fw_name_from_chipid(struct hci_dev *hdev, u16 chipid, + u8 loader_ver) +{ + char *fw_name_old = NULL; + + switch (chipid) { + case CHIP_ID_W9098: + fw_name_old = FIRMWARE_W9098_OLD; + break; + } + return fw_name_old; +} + static int nxp_recv_chip_ver_v3(struct hci_dev *hdev, struct sk_buff *skb) { struct v3_start_ind *req = skb_pull_data(skb, sizeof(*req)); struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + const char *fw_name; + const char *fw_name_old; u16 chip_id; u8 loader_ver; @@ -890,8 +972,10 @@ static int nxp_recv_chip_ver_v3(struct hci_dev *hdev, struct sk_buff *skb) chip_id = le16_to_cpu(req->chip_id); loader_ver = req->loader_ver; - if (!nxp_request_firmware(hdev, nxp_get_fw_name_from_chipid(hdev, - chip_id, loader_ver))) + bt_dev_info(hdev, "ChipID: %04x, Version: %d", chip_id, loader_ver); + fw_name = nxp_get_fw_name_from_chipid(hdev, chip_id, loader_ver); + fw_name_old = nxp_get_old_fw_name_from_chipid(hdev, chip_id, loader_ver); + if (!nxp_request_firmware(hdev, fw_name, fw_name_old)) nxp_send_ack(NXP_ACK_V3, hdev); free_skb: @@ -899,6 +983,32 @@ free_skb: return 0; } +static void nxp_handle_fw_download_error(struct hci_dev *hdev, struct v3_data_req *req) +{ + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); + __u32 offset = __le32_to_cpu(req->offset); + __u16 err = __le16_to_cpu(req->error); + union nxp_v3_rx_timeout_nak_u nak_tx_buf; + + switch (err) { + case NXP_ACK_RX_TIMEOUT: + case NXP_HDR_RX_TIMEOUT: + case NXP_DATA_RX_TIMEOUT: + nak_tx_buf.pkt.nak = NXP_NAK_V3; + nak_tx_buf.pkt.offset = __cpu_to_le32(offset); + nak_tx_buf.pkt.crc = crc8(crc8_table, nak_tx_buf.buf, + sizeof(nak_tx_buf) - 1, 0xff); + serdev_device_write_buf(nxpdev->serdev, nak_tx_buf.buf, + sizeof(nak_tx_buf)); + break; + default: + bt_dev_dbg(hdev, "Unknown bootloader error code: %d", err); + break; + + } + +} + static int nxp_recv_fw_req_v3(struct hci_dev *hdev, struct sk_buff *skb) { struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); @@ -913,7 +1023,12 @@ static int nxp_recv_fw_req_v3(struct hci_dev *hdev, struct sk_buff *skb) if (!req || !nxpdev->fw) goto free_skb; - nxp_send_ack(NXP_ACK_V3, hdev); + if (!req->error) { + nxp_send_ack(NXP_ACK_V3, hdev); + } else { + nxp_handle_fw_download_error(hdev, req); + goto free_skb; + } len = __le16_to_cpu(req->len); @@ -934,15 +1049,12 @@ static int nxp_recv_fw_req_v3(struct hci_dev *hdev, struct sk_buff *skb) } if (req->len == 0) { - bt_dev_dbg(hdev, "FW Downloaded Successfully: %zu bytes", + bt_dev_info(hdev, "FW Download Complete: %zu bytes", nxpdev->fw->size); clear_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state); wake_up_interruptible(&nxpdev->fw_dnld_done_wait_q); goto free_skb; } - if (req->error) - bt_dev_dbg(hdev, "FW Download received err 0x%02x from chip", - req->error); offset = __le32_to_cpu(req->offset); if (offset < nxpdev->fw_v3_offset_correction) { @@ -954,8 +1066,9 @@ static int nxp_recv_fw_req_v3(struct hci_dev *hdev, struct sk_buff *skb) goto free_skb; } - serdev_device_write_buf(nxpdev->serdev, nxpdev->fw->data + offset - - nxpdev->fw_v3_offset_correction, len); + nxpdev->fw_dnld_v3_offset = offset - nxpdev->fw_v3_offset_correction; + serdev_device_write_buf(nxpdev->serdev, nxpdev->fw->data + + nxpdev->fw_dnld_v3_offset, len); free_skb: kfree_skb(skb); @@ -1037,7 +1150,7 @@ static int nxp_setup(struct hci_dev *hdev) if (err < 0) return err; } else { - bt_dev_dbg(hdev, "FW already running."); + bt_dev_info(hdev, "FW already running."); clear_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state); } @@ -1253,8 +1366,10 @@ static int btnxpuart_close(struct hci_dev *hdev) ps_wakeup(nxpdev); serdev_device_close(nxpdev->serdev); skb_queue_purge(&nxpdev->txq); - kfree_skb(nxpdev->rx_skb); - nxpdev->rx_skb = NULL; + if (!IS_ERR_OR_NULL(nxpdev->rx_skb)) { + kfree_skb(nxpdev->rx_skb); + nxpdev->rx_skb = NULL; + } clear_bit(BTNXPUART_SERDEV_OPEN, &nxpdev->tx_state); return 0; } @@ -1269,8 +1384,10 @@ static int btnxpuart_flush(struct hci_dev *hdev) cancel_work_sync(&nxpdev->tx_work); - kfree_skb(nxpdev->rx_skb); - nxpdev->rx_skb = NULL; + if (!IS_ERR_OR_NULL(nxpdev->rx_skb)) { + kfree_skb(nxpdev->rx_skb); + nxpdev->rx_skb = NULL; + } return 0; } @@ -1385,28 +1502,56 @@ static void nxp_serdev_remove(struct serdev_device *serdev) struct btnxpuart_dev *nxpdev = serdev_device_get_drvdata(serdev); struct hci_dev *hdev = nxpdev->hdev; - /* Restore FW baudrate to fw_init_baudrate if changed. - * This will ensure FW baudrate is in sync with - * driver baudrate in case this driver is re-inserted. - */ - if (nxpdev->current_baudrate != nxpdev->fw_init_baudrate) { - nxpdev->new_baudrate = nxpdev->fw_init_baudrate; - nxp_set_baudrate_cmd(hdev, NULL); + if (is_fw_downloading(nxpdev)) { + set_bit(BTNXPUART_FW_DOWNLOAD_ABORT, &nxpdev->tx_state); + clear_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state); + wake_up_interruptible(&nxpdev->check_boot_sign_wait_q); + wake_up_interruptible(&nxpdev->fw_dnld_done_wait_q); + } else { + /* Restore FW baudrate to fw_init_baudrate if changed. + * This will ensure FW baudrate is in sync with + * driver baudrate in case this driver is re-inserted. + */ + if (nxpdev->current_baudrate != nxpdev->fw_init_baudrate) { + nxpdev->new_baudrate = nxpdev->fw_init_baudrate; + nxp_set_baudrate_cmd(hdev, NULL); + } + ps_cancel_timer(nxpdev); } - - ps_cancel_timer(nxpdev); hci_unregister_dev(hdev); hci_free_dev(hdev); } +#ifdef CONFIG_PM_SLEEP +static int nxp_serdev_suspend(struct device *dev) +{ + struct btnxpuart_dev *nxpdev = dev_get_drvdata(dev); + struct ps_data *psdata = &nxpdev->psdata; + + ps_control(psdata->hdev, PS_STATE_SLEEP); + return 0; +} + +static int nxp_serdev_resume(struct device *dev) +{ + struct btnxpuart_dev *nxpdev = dev_get_drvdata(dev); + struct ps_data *psdata = &nxpdev->psdata; + + ps_control(psdata->hdev, PS_STATE_AWAKE); + return 0; +} +#endif + static struct btnxpuart_data w8987_data __maybe_unused = { .helper_fw_name = NULL, .fw_name = FIRMWARE_W8987, + .fw_name_old = FIRMWARE_W8987_OLD, }; static struct btnxpuart_data w8997_data __maybe_unused = { .helper_fw_name = FIRMWARE_HELPER, .fw_name = FIRMWARE_W8997, + .fw_name_old = FIRMWARE_W8997_OLD, }; static const struct of_device_id nxpuart_of_match_table[] __maybe_unused = { @@ -1416,12 +1561,17 @@ static const struct of_device_id nxpuart_of_match_table[] __maybe_unused = { }; MODULE_DEVICE_TABLE(of, nxpuart_of_match_table); +static const struct dev_pm_ops nxp_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(nxp_serdev_suspend, nxp_serdev_resume) +}; + static struct serdev_device_driver nxp_serdev_driver = { .probe = nxp_serdev_probe, .remove = nxp_serdev_remove, .driver = { .name = "btnxpuart", .of_match_table = of_match_ptr(nxpuart_of_match_table), + .pm = &nxp_pm_ops, }, }; diff --git a/drivers/bluetooth/btrtl.c b/drivers/bluetooth/btrtl.c index 4f1e37b4f780..f2f37143c454 100644 --- a/drivers/bluetooth/btrtl.c +++ b/drivers/bluetooth/btrtl.c @@ -811,7 +811,7 @@ static int rtl_download_firmware(struct hci_dev *hdev, struct sk_buff *skb; struct hci_rp_read_local_version *rp; - dl_cmd = kmalloc(sizeof(struct rtl_download_cmd), GFP_KERNEL); + dl_cmd = kmalloc(sizeof(*dl_cmd), GFP_KERNEL); if (!dl_cmd) return -ENOMEM; diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index e384ef6ff050..acdba5d77694 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -479,6 +479,7 @@ static const struct usb_device_id quirks_table[] = { { USB_DEVICE(0x8087, 0x0036), .driver_info = BTUSB_INTEL_COMBINED }, { USB_DEVICE(0x8087, 0x0037), .driver_info = BTUSB_INTEL_COMBINED }, { USB_DEVICE(0x8087, 0x0038), .driver_info = BTUSB_INTEL_COMBINED }, + { USB_DEVICE(0x8087, 0x0039), .driver_info = BTUSB_INTEL_COMBINED }, { USB_DEVICE(0x8087, 0x07da), .driver_info = BTUSB_CSR }, { USB_DEVICE(0x8087, 0x07dc), .driver_info = BTUSB_INTEL_COMBINED | BTUSB_INTEL_NO_WBS_SUPPORT | @@ -555,6 +556,10 @@ static const struct usb_device_id quirks_table[] = { BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x13d3, 0x3572), .driver_info = BTUSB_REALTEK | BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x13d3, 0x3591), .driver_info = BTUSB_REALTEK | + BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x0489, 0xe125), .driver_info = BTUSB_REALTEK | + BTUSB_WIDEBAND_SPEECH }, /* Realtek 8852BT/8852BE-VT Bluetooth devices */ { USB_DEVICE(0x0bda, 0x8520), .driver_info = BTUSB_REALTEK | @@ -891,6 +896,9 @@ struct btusb_data { int (*setup_on_usb)(struct hci_dev *hdev); + int (*suspend)(struct hci_dev *hdev); + int (*resume)(struct hci_dev *hdev); + int oob_wake_irq; /* irq for out-of-band wake-on-bt */ unsigned cmd_timeout_cnt; @@ -2638,410 +2646,48 @@ static int btusb_recv_event_realtek(struct hci_dev *hdev, struct sk_buff *skb) return hci_recv_frame(hdev, skb); } -/* UHW CR mapping */ -#define MTK_BT_MISC 0x70002510 -#define MTK_BT_SUBSYS_RST 0x70002610 -#define MTK_UDMA_INT_STA_BT 0x74000024 -#define MTK_UDMA_INT_STA_BT1 0x74000308 -#define MTK_BT_WDT_STATUS 0x740003A0 -#define MTK_EP_RST_OPT 0x74011890 -#define MTK_EP_RST_IN_OUT_OPT 0x00010001 -#define MTK_BT_RST_DONE 0x00000100 -#define MTK_BT_RESET_REG_CONNV3 0x70028610 -#define MTK_BT_READ_DEV_ID 0x70010200 - - -static void btusb_mtk_wmt_recv(struct urb *urb) -{ - struct hci_dev *hdev = urb->context; - struct btusb_data *data = hci_get_drvdata(hdev); - struct sk_buff *skb; - int err; - - if (urb->status == 0 && urb->actual_length > 0) { - hdev->stat.byte_rx += urb->actual_length; - - /* WMT event shouldn't be fragmented and the size should be - * less than HCI_WMT_MAX_EVENT_SIZE. - */ - skb = bt_skb_alloc(HCI_WMT_MAX_EVENT_SIZE, GFP_ATOMIC); - if (!skb) { - hdev->stat.err_rx++; - kfree(urb->setup_packet); - return; - } - - hci_skb_pkt_type(skb) = HCI_EVENT_PKT; - skb_put_data(skb, urb->transfer_buffer, urb->actual_length); - - /* When someone waits for the WMT event, the skb is being cloned - * and being processed the events from there then. - */ - if (test_bit(BTUSB_TX_WAIT_VND_EVT, &data->flags)) { - data->evt_skb = skb_clone(skb, GFP_ATOMIC); - if (!data->evt_skb) { - kfree_skb(skb); - kfree(urb->setup_packet); - return; - } - } - - err = hci_recv_frame(hdev, skb); - if (err < 0) { - kfree_skb(data->evt_skb); - data->evt_skb = NULL; - kfree(urb->setup_packet); - return; - } - - if (test_and_clear_bit(BTUSB_TX_WAIT_VND_EVT, - &data->flags)) { - /* Barrier to sync with other CPUs */ - smp_mb__after_atomic(); - wake_up_bit(&data->flags, - BTUSB_TX_WAIT_VND_EVT); - } - kfree(urb->setup_packet); - return; - } else if (urb->status == -ENOENT) { - /* Avoid suspend failed when usb_kill_urb */ - return; - } - - usb_mark_last_busy(data->udev); - - /* The URB complete handler is still called with urb->actual_length = 0 - * when the event is not available, so we should keep re-submitting - * URB until WMT event returns, Also, It's necessary to wait some time - * between the two consecutive control URBs to relax the target device - * to generate the event. Otherwise, the WMT event cannot return from - * the device successfully. - */ - udelay(500); - - usb_anchor_urb(urb, &data->ctrl_anchor); - err = usb_submit_urb(urb, GFP_ATOMIC); - if (err < 0) { - kfree(urb->setup_packet); - /* -EPERM: urb is being killed; - * -ENODEV: device got disconnected - */ - if (err != -EPERM && err != -ENODEV) - bt_dev_err(hdev, "urb %p failed to resubmit (%d)", - urb, -err); - usb_unanchor_urb(urb); - } -} - -static int btusb_mtk_submit_wmt_recv_urb(struct hci_dev *hdev) -{ - struct btusb_data *data = hci_get_drvdata(hdev); - struct usb_ctrlrequest *dr; - unsigned char *buf; - int err, size = 64; - unsigned int pipe; - struct urb *urb; - - urb = usb_alloc_urb(0, GFP_KERNEL); - if (!urb) - return -ENOMEM; - - dr = kmalloc(sizeof(*dr), GFP_KERNEL); - if (!dr) { - usb_free_urb(urb); - return -ENOMEM; - } - - dr->bRequestType = USB_TYPE_VENDOR | USB_DIR_IN; - dr->bRequest = 1; - dr->wIndex = cpu_to_le16(0); - dr->wValue = cpu_to_le16(48); - dr->wLength = cpu_to_le16(size); - - buf = kmalloc(size, GFP_KERNEL); - if (!buf) { - kfree(dr); - usb_free_urb(urb); - return -ENOMEM; - } - - pipe = usb_rcvctrlpipe(data->udev, 0); - - usb_fill_control_urb(urb, data->udev, pipe, (void *)dr, - buf, size, btusb_mtk_wmt_recv, hdev); - - urb->transfer_flags |= URB_FREE_BUFFER; - - usb_anchor_urb(urb, &data->ctrl_anchor); - err = usb_submit_urb(urb, GFP_KERNEL); - if (err < 0) { - if (err != -EPERM && err != -ENODEV) - bt_dev_err(hdev, "urb %p submission failed (%d)", - urb, -err); - usb_unanchor_urb(urb); - } - - usb_free_urb(urb); - - return err; -} - -static int btusb_mtk_hci_wmt_sync(struct hci_dev *hdev, - struct btmtk_hci_wmt_params *wmt_params) +static void btusb_mtk_claim_iso_intf(struct btusb_data *data) { - struct btusb_data *data = hci_get_drvdata(hdev); - struct btmtk_hci_wmt_evt_funcc *wmt_evt_funcc; - u32 hlen, status = BTMTK_WMT_INVALID; - struct btmtk_hci_wmt_evt *wmt_evt; - struct btmtk_hci_wmt_cmd *wc; - struct btmtk_wmt_hdr *hdr; + struct btmtk_data *btmtk_data = hci_get_priv(data->hdev); int err; - /* Send the WMT command and wait until the WMT event returns */ - hlen = sizeof(*hdr) + wmt_params->dlen; - if (hlen > 255) - return -EINVAL; - - wc = kzalloc(hlen, GFP_KERNEL); - if (!wc) - return -ENOMEM; - - hdr = &wc->hdr; - hdr->dir = 1; - hdr->op = wmt_params->op; - hdr->dlen = cpu_to_le16(wmt_params->dlen + 1); - hdr->flag = wmt_params->flag; - memcpy(wc->data, wmt_params->data, wmt_params->dlen); - - set_bit(BTUSB_TX_WAIT_VND_EVT, &data->flags); - - /* WMT cmd/event doesn't follow up the generic HCI cmd/event handling, - * it needs constantly polling control pipe until the host received the - * WMT event, thus, we should require to specifically acquire PM counter - * on the USB to prevent the interface from entering auto suspended - * while WMT cmd/event in progress. - */ - err = usb_autopm_get_interface(data->intf); - if (err < 0) - goto err_free_wc; - - err = __hci_cmd_send(hdev, 0xfc6f, hlen, wc); - - if (err < 0) { - clear_bit(BTUSB_TX_WAIT_VND_EVT, &data->flags); - usb_autopm_put_interface(data->intf); - goto err_free_wc; - } - - /* Submit control IN URB on demand to process the WMT event */ - err = btusb_mtk_submit_wmt_recv_urb(hdev); - - usb_autopm_put_interface(data->intf); - - if (err < 0) - goto err_free_wc; - - /* The vendor specific WMT commands are all answered by a vendor - * specific event and will have the Command Status or Command - * Complete as with usual HCI command flow control. - * - * After sending the command, wait for BTUSB_TX_WAIT_VND_EVT - * state to be cleared. The driver specific event receive routine - * will clear that state and with that indicate completion of the - * WMT command. - */ - err = wait_on_bit_timeout(&data->flags, BTUSB_TX_WAIT_VND_EVT, - TASK_INTERRUPTIBLE, HCI_INIT_TIMEOUT); - if (err == -EINTR) { - bt_dev_err(hdev, "Execution of wmt command interrupted"); - clear_bit(BTUSB_TX_WAIT_VND_EVT, &data->flags); - goto err_free_wc; - } - - if (err) { - bt_dev_err(hdev, "Execution of wmt command timed out"); - clear_bit(BTUSB_TX_WAIT_VND_EVT, &data->flags); - err = -ETIMEDOUT; - goto err_free_wc; - } - - if (data->evt_skb == NULL) - goto err_free_wc; - - /* Parse and handle the return WMT event */ - wmt_evt = (struct btmtk_hci_wmt_evt *)data->evt_skb->data; - if (wmt_evt->whdr.op != hdr->op) { - bt_dev_err(hdev, "Wrong op received %d expected %d", - wmt_evt->whdr.op, hdr->op); - err = -EIO; - goto err_free_skb; - } - - switch (wmt_evt->whdr.op) { - case BTMTK_WMT_SEMAPHORE: - if (wmt_evt->whdr.flag == 2) - status = BTMTK_WMT_PATCH_UNDONE; - else - status = BTMTK_WMT_PATCH_DONE; - break; - case BTMTK_WMT_FUNC_CTRL: - wmt_evt_funcc = (struct btmtk_hci_wmt_evt_funcc *)wmt_evt; - if (be16_to_cpu(wmt_evt_funcc->status) == 0x404) - status = BTMTK_WMT_ON_DONE; - else if (be16_to_cpu(wmt_evt_funcc->status) == 0x420) - status = BTMTK_WMT_ON_PROGRESS; - else - status = BTMTK_WMT_ON_UNDONE; - break; - case BTMTK_WMT_PATCH_DWNLD: - if (wmt_evt->whdr.flag == 2) - status = BTMTK_WMT_PATCH_DONE; - else if (wmt_evt->whdr.flag == 1) - status = BTMTK_WMT_PATCH_PROGRESS; - else - status = BTMTK_WMT_PATCH_UNDONE; - break; - } - - if (wmt_params->status) - *wmt_params->status = status; - -err_free_skb: - kfree_skb(data->evt_skb); - data->evt_skb = NULL; -err_free_wc: - kfree(wc); - return err; -} - -static int btusb_mtk_func_query(struct hci_dev *hdev) -{ - struct btmtk_hci_wmt_params wmt_params; - int status, err; - u8 param = 0; - - /* Query whether the function is enabled */ - wmt_params.op = BTMTK_WMT_FUNC_CTRL; - wmt_params.flag = 4; - wmt_params.dlen = sizeof(param); - wmt_params.data = ¶m; - wmt_params.status = &status; - - err = btusb_mtk_hci_wmt_sync(hdev, &wmt_params); - if (err < 0) { - bt_dev_err(hdev, "Failed to query function status (%d)", err); - return err; - } - - return status; -} - -static int btusb_mtk_uhw_reg_write(struct btusb_data *data, u32 reg, u32 val) -{ - struct hci_dev *hdev = data->hdev; - int pipe, err; - void *buf; - - buf = kzalloc(4, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - put_unaligned_le32(val, buf); - - pipe = usb_sndctrlpipe(data->udev, 0); - err = usb_control_msg(data->udev, pipe, 0x02, - 0x5E, - reg >> 16, reg & 0xffff, - buf, 4, USB_CTRL_SET_TIMEOUT); + err = usb_driver_claim_interface(&btusb_driver, + btmtk_data->isopkt_intf, data); if (err < 0) { - bt_dev_err(hdev, "Failed to write uhw reg(%d)", err); - goto err_free_buf; + btmtk_data->isopkt_intf = NULL; + bt_dev_err(data->hdev, "Failed to claim iso interface"); + return; } -err_free_buf: - kfree(buf); - - return err; + set_bit(BTMTK_ISOPKT_OVER_INTR, &btmtk_data->flags); } -static int btusb_mtk_uhw_reg_read(struct btusb_data *data, u32 reg, u32 *val) +static void btusb_mtk_release_iso_intf(struct btusb_data *data) { - struct hci_dev *hdev = data->hdev; - int pipe, err; - void *buf; + struct btmtk_data *btmtk_data = hci_get_priv(data->hdev); - buf = kzalloc(4, GFP_KERNEL); - if (!buf) - return -ENOMEM; + if (btmtk_data->isopkt_intf) { + usb_kill_anchored_urbs(&btmtk_data->isopkt_anchor); + clear_bit(BTMTK_ISOPKT_RUNNING, &btmtk_data->flags); - pipe = usb_rcvctrlpipe(data->udev, 0); - err = usb_control_msg(data->udev, pipe, 0x01, - 0xDE, - reg >> 16, reg & 0xffff, - buf, 4, USB_CTRL_GET_TIMEOUT); - if (err < 0) { - bt_dev_err(hdev, "Failed to read uhw reg(%d)", err); - goto err_free_buf; + dev_kfree_skb_irq(btmtk_data->isopkt_skb); + btmtk_data->isopkt_skb = NULL; + usb_set_intfdata(btmtk_data->isopkt_intf, NULL); + usb_driver_release_interface(&btusb_driver, + btmtk_data->isopkt_intf); } - *val = get_unaligned_le32(buf); - bt_dev_dbg(hdev, "reg=%x, value=0x%08x", reg, *val); - -err_free_buf: - kfree(buf); - - return err; -} - -static int btusb_mtk_reg_read(struct btusb_data *data, u32 reg, u32 *val) -{ - int pipe, err, size = sizeof(u32); - void *buf; - - buf = kzalloc(size, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - pipe = usb_rcvctrlpipe(data->udev, 0); - err = usb_control_msg(data->udev, pipe, 0x63, - USB_TYPE_VENDOR | USB_DIR_IN, - reg >> 16, reg & 0xffff, - buf, size, USB_CTRL_GET_TIMEOUT); - if (err < 0) - goto err_free_buf; - - *val = get_unaligned_le32(buf); - -err_free_buf: - kfree(buf); - - return err; -} - -static int btusb_mtk_id_get(struct btusb_data *data, u32 reg, u32 *id) -{ - return btusb_mtk_reg_read(data, reg, id); -} - -static u32 btusb_mtk_reset_done(struct hci_dev *hdev) -{ - struct btusb_data *data = hci_get_drvdata(hdev); - u32 val = 0; - - btusb_mtk_uhw_reg_read(data, MTK_BT_MISC, &val); - - return val & MTK_BT_RST_DONE; + clear_bit(BTMTK_ISOPKT_OVER_INTR, &btmtk_data->flags); } static int btusb_mtk_reset(struct hci_dev *hdev, void *rst_data) { struct btusb_data *data = hci_get_drvdata(hdev); - struct btmediatek_data *mediatek; - u32 val; + struct btmtk_data *btmtk_data = hci_get_priv(hdev); int err; /* It's MediaTek specific bluetooth reset mechanism via USB */ - if (test_and_set_bit(BTUSB_HW_RESET_ACTIVE, &data->flags)) { + if (test_and_set_bit(BTMTK_HW_RESET_ACTIVE, &btmtk_data->flags)) { bt_dev_err(hdev, "last reset failed? Not resetting again"); return -EBUSY; } @@ -3050,302 +2696,68 @@ static int btusb_mtk_reset(struct hci_dev *hdev, void *rst_data) if (err < 0) return err; + if (test_bit(BTMTK_ISOPKT_RUNNING, &btmtk_data->flags)) + btusb_mtk_release_iso_intf(data); + btusb_stop_traffic(data); usb_kill_anchored_urbs(&data->tx_anchor); - mediatek = hci_get_priv(hdev); - - if (mediatek->dev_id == 0x7925) { - btusb_mtk_uhw_reg_read(data, MTK_BT_RESET_REG_CONNV3, &val); - val |= (1 << 5); - btusb_mtk_uhw_reg_write(data, MTK_BT_RESET_REG_CONNV3, val); - btusb_mtk_uhw_reg_read(data, MTK_BT_RESET_REG_CONNV3, &val); - val &= 0xFFFF00FF; - val |= (1 << 13); - btusb_mtk_uhw_reg_write(data, MTK_BT_RESET_REG_CONNV3, val); - btusb_mtk_uhw_reg_write(data, MTK_EP_RST_OPT, 0x00010001); - btusb_mtk_uhw_reg_read(data, MTK_BT_RESET_REG_CONNV3, &val); - val |= (1 << 0); - btusb_mtk_uhw_reg_write(data, MTK_BT_RESET_REG_CONNV3, val); - btusb_mtk_uhw_reg_write(data, MTK_UDMA_INT_STA_BT, 0x000000FF); - btusb_mtk_uhw_reg_read(data, MTK_UDMA_INT_STA_BT, &val); - btusb_mtk_uhw_reg_write(data, MTK_UDMA_INT_STA_BT1, 0x000000FF); - btusb_mtk_uhw_reg_read(data, MTK_UDMA_INT_STA_BT1, &val); - msleep(100); - } else { - /* It's Device EndPoint Reset Option Register */ - bt_dev_dbg(hdev, "Initiating reset mechanism via uhw"); - btusb_mtk_uhw_reg_write(data, MTK_EP_RST_OPT, MTK_EP_RST_IN_OUT_OPT); - btusb_mtk_uhw_reg_read(data, MTK_BT_WDT_STATUS, &val); - - /* Reset the bluetooth chip via USB interface. */ - btusb_mtk_uhw_reg_write(data, MTK_BT_SUBSYS_RST, 1); - btusb_mtk_uhw_reg_write(data, MTK_UDMA_INT_STA_BT, 0x000000FF); - btusb_mtk_uhw_reg_read(data, MTK_UDMA_INT_STA_BT, &val); - btusb_mtk_uhw_reg_write(data, MTK_UDMA_INT_STA_BT1, 0x000000FF); - btusb_mtk_uhw_reg_read(data, MTK_UDMA_INT_STA_BT1, &val); - /* MT7921 need to delay 20ms between toggle reset bit */ - msleep(20); - btusb_mtk_uhw_reg_write(data, MTK_BT_SUBSYS_RST, 0); - btusb_mtk_uhw_reg_read(data, MTK_BT_SUBSYS_RST, &val); - } - err = readx_poll_timeout(btusb_mtk_reset_done, hdev, val, - val & MTK_BT_RST_DONE, 20000, 1000000); - if (err < 0) - bt_dev_err(hdev, "Reset timeout"); - - btusb_mtk_id_get(data, 0x70010200, &val); - if (!val) - bt_dev_err(hdev, "Can't get device id, subsys reset fail."); + err = btmtk_usb_subsys_reset(hdev, btmtk_data->dev_id); usb_queue_reset_device(data->intf); - - clear_bit(BTUSB_HW_RESET_ACTIVE, &data->flags); + clear_bit(BTMTK_HW_RESET_ACTIVE, &btmtk_data->flags); return err; } -static int btusb_mtk_setup(struct hci_dev *hdev) +static int btusb_send_frame_mtk(struct hci_dev *hdev, struct sk_buff *skb) { - struct btusb_data *data = hci_get_drvdata(hdev); - struct btmtk_hci_wmt_params wmt_params; - ktime_t calltime, delta, rettime; - struct btmtk_tci_sleep tci_sleep; - unsigned long long duration; - struct sk_buff *skb; - const char *fwname; - int err, status; - u32 dev_id = 0; - char fw_bin_name[64]; - u32 fw_version = 0, fw_flavor = 0; - u8 param; - struct btmediatek_data *mediatek; - - calltime = ktime_get(); - - err = btusb_mtk_id_get(data, 0x80000008, &dev_id); - if (err < 0) { - bt_dev_err(hdev, "Failed to get device id (%d)", err); - return err; - } - - if (!dev_id || dev_id != 0x7663) { - err = btusb_mtk_id_get(data, 0x70010200, &dev_id); - if (err < 0) { - bt_dev_err(hdev, "Failed to get device id (%d)", err); - return err; - } - err = btusb_mtk_id_get(data, 0x80021004, &fw_version); - if (err < 0) { - bt_dev_err(hdev, "Failed to get fw version (%d)", err); - return err; - } - err = btusb_mtk_id_get(data, 0x70010020, &fw_flavor); - if (err < 0) { - bt_dev_err(hdev, "Failed to get fw flavor (%d)", err); - return err; - } - fw_flavor = (fw_flavor & 0x00000080) >> 7; - } - - mediatek = hci_get_priv(hdev); - mediatek->dev_id = dev_id; - mediatek->reset_sync = btusb_mtk_reset; - - err = btmtk_register_coredump(hdev, btusb_driver.name, fw_version); - if (err < 0) - bt_dev_err(hdev, "Failed to register coredump (%d)", err); - - switch (dev_id) { - case 0x7663: - fwname = FIRMWARE_MT7663; - break; - case 0x7668: - fwname = FIRMWARE_MT7668; - break; - case 0x7922: - case 0x7961: - case 0x7925: - if (dev_id == 0x7925) - snprintf(fw_bin_name, sizeof(fw_bin_name), - "mediatek/mt%04x/BT_RAM_CODE_MT%04x_1_%x_hdr.bin", - dev_id & 0xffff, dev_id & 0xffff, (fw_version & 0xff) + 1); - else if (dev_id == 0x7961 && fw_flavor) - snprintf(fw_bin_name, sizeof(fw_bin_name), - "mediatek/BT_RAM_CODE_MT%04x_1a_%x_hdr.bin", - dev_id & 0xffff, (fw_version & 0xff) + 1); - else - snprintf(fw_bin_name, sizeof(fw_bin_name), - "mediatek/BT_RAM_CODE_MT%04x_1_%x_hdr.bin", - dev_id & 0xffff, (fw_version & 0xff) + 1); - - err = btmtk_setup_firmware_79xx(hdev, fw_bin_name, - btusb_mtk_hci_wmt_sync); - if (err < 0) { - bt_dev_err(hdev, "Failed to set up firmware (%d)", err); - return err; - } - - /* It's Device EndPoint Reset Option Register */ - btusb_mtk_uhw_reg_write(data, MTK_EP_RST_OPT, MTK_EP_RST_IN_OUT_OPT); - - /* Enable Bluetooth protocol */ - param = 1; - wmt_params.op = BTMTK_WMT_FUNC_CTRL; - wmt_params.flag = 0; - wmt_params.dlen = sizeof(param); - wmt_params.data = ¶m; - wmt_params.status = NULL; - - err = btusb_mtk_hci_wmt_sync(hdev, &wmt_params); - if (err < 0) { - bt_dev_err(hdev, "Failed to send wmt func ctrl (%d)", err); - return err; - } - - hci_set_msft_opcode(hdev, 0xFD30); - hci_set_aosp_capable(hdev); - goto done; - default: - bt_dev_err(hdev, "Unsupported hardware variant (%08x)", - dev_id); - return -ENODEV; - } - - /* Query whether the firmware is already download */ - wmt_params.op = BTMTK_WMT_SEMAPHORE; - wmt_params.flag = 1; - wmt_params.dlen = 0; - wmt_params.data = NULL; - wmt_params.status = &status; - - err = btusb_mtk_hci_wmt_sync(hdev, &wmt_params); - if (err < 0) { - bt_dev_err(hdev, "Failed to query firmware status (%d)", err); - return err; - } - - if (status == BTMTK_WMT_PATCH_DONE) { - bt_dev_info(hdev, "firmware already downloaded"); - goto ignore_setup_fw; - } - - /* Setup a firmware which the device definitely requires */ - err = btmtk_setup_firmware(hdev, fwname, - btusb_mtk_hci_wmt_sync); - if (err < 0) - return err; - -ignore_setup_fw: - err = readx_poll_timeout(btusb_mtk_func_query, hdev, status, - status < 0 || status != BTMTK_WMT_ON_PROGRESS, - 2000, 5000000); - /* -ETIMEDOUT happens */ - if (err < 0) - return err; - - /* The other errors happen in btusb_mtk_func_query */ - if (status < 0) - return status; - - if (status == BTMTK_WMT_ON_DONE) { - bt_dev_info(hdev, "function already on"); - goto ignore_func_on; - } - - /* Enable Bluetooth protocol */ - param = 1; - wmt_params.op = BTMTK_WMT_FUNC_CTRL; - wmt_params.flag = 0; - wmt_params.dlen = sizeof(param); - wmt_params.data = ¶m; - wmt_params.status = NULL; + struct urb *urb; - err = btusb_mtk_hci_wmt_sync(hdev, &wmt_params); - if (err < 0) { - bt_dev_err(hdev, "Failed to send wmt func ctrl (%d)", err); - return err; - } + BT_DBG("%s", hdev->name); -ignore_func_on: - /* Apply the low power environment setup */ - tci_sleep.mode = 0x5; - tci_sleep.duration = cpu_to_le16(0x640); - tci_sleep.host_duration = cpu_to_le16(0x640); - tci_sleep.host_wakeup_pin = 0; - tci_sleep.time_compensation = 0; + if (hci_skb_pkt_type(skb) == HCI_ISODATA_PKT) { + urb = alloc_mtk_intr_urb(hdev, skb, btusb_tx_complete); + if (IS_ERR(urb)) + return PTR_ERR(urb); - skb = __hci_cmd_sync(hdev, 0xfc7a, sizeof(tci_sleep), &tci_sleep, - HCI_INIT_TIMEOUT); - if (IS_ERR(skb)) { - err = PTR_ERR(skb); - bt_dev_err(hdev, "Failed to apply low power setting (%d)", err); - return err; + return submit_or_queue_tx_urb(hdev, urb); + } else { + return btusb_send_frame(hdev, skb); } - kfree_skb(skb); - -done: - rettime = ktime_get(); - delta = ktime_sub(rettime, calltime); - duration = (unsigned long long)ktime_to_ns(delta) >> 10; - - bt_dev_info(hdev, "Device setup in %llu usecs", duration); - - return 0; } -static int btusb_mtk_shutdown(struct hci_dev *hdev) +static int btusb_mtk_setup(struct hci_dev *hdev) { - struct btmtk_hci_wmt_params wmt_params; - u8 param = 0; - int err; - - /* Disable the device */ - wmt_params.op = BTMTK_WMT_FUNC_CTRL; - wmt_params.flag = 0; - wmt_params.dlen = sizeof(param); - wmt_params.data = ¶m; - wmt_params.status = NULL; - - err = btusb_mtk_hci_wmt_sync(hdev, &wmt_params); - if (err < 0) { - bt_dev_err(hdev, "Failed to send wmt func ctrl (%d)", err); - return err; - } + struct btusb_data *data = hci_get_drvdata(hdev); + struct btmtk_data *btmtk_data = hci_get_priv(hdev); - return 0; + /* MediaTek WMT vendor cmd requiring below USB resources to + * complete the handshake. + */ + btmtk_data->drv_name = btusb_driver.name; + btmtk_data->intf = data->intf; + btmtk_data->udev = data->udev; + btmtk_data->ctrl_anchor = &data->ctrl_anchor; + btmtk_data->reset_sync = btusb_mtk_reset; + + /* Claim ISO data interface and endpoint */ + btmtk_data->isopkt_intf = usb_ifnum_to_if(data->udev, MTK_ISO_IFNUM); + if (btmtk_data->isopkt_intf) + btusb_mtk_claim_iso_intf(data); + + return btmtk_usb_setup(hdev); } -static int btusb_recv_acl_mtk(struct hci_dev *hdev, struct sk_buff *skb) +static int btusb_mtk_shutdown(struct hci_dev *hdev) { struct btusb_data *data = hci_get_drvdata(hdev); - u16 handle = le16_to_cpu(hci_acl_hdr(skb)->handle); - - switch (handle) { - case 0xfc6f: /* Firmware dump from device */ - /* When the firmware hangs, the device can no longer - * suspend and thus disable auto-suspend. - */ - usb_disable_autosuspend(data->udev); + struct btmtk_data *btmtk_data = hci_get_priv(hdev); - /* We need to forward the diagnostic packet to userspace daemon - * for backward compatibility, so we have to clone the packet - * extraly for the in-kernel coredump support. - */ - if (IS_ENABLED(CONFIG_DEV_COREDUMP)) { - struct sk_buff *skb_cd = skb_clone(skb, GFP_ATOMIC); + if (test_bit(BTMTK_ISOPKT_RUNNING, &btmtk_data->flags)) + btusb_mtk_release_iso_intf(data); - if (skb_cd) - btmtk_process_coredump(hdev, skb_cd); - } - - fallthrough; - case 0x05ff: /* Firmware debug logging 1 */ - case 0x05fe: /* Firmware debug logging 2 */ - return hci_recv_diag(hdev, skb); - } - - return hci_recv_frame(hdev, skb); + return btmtk_usb_shutdown(hdev); } #ifdef CONFIG_PM @@ -4347,7 +3759,7 @@ static int btusb_probe(struct usb_interface *intf, data->recv_event = btusb_recv_event_realtek; } else if (id->driver_info & BTUSB_MEDIATEK) { /* Allocate extra space for Mediatek device */ - priv_size += sizeof(struct btmediatek_data); + priv_size += sizeof(struct btmtk_data); } data->recv_acl = hci_recv_frame; @@ -4451,9 +3863,12 @@ static int btusb_probe(struct usb_interface *intf, hdev->manufacturer = 70; hdev->cmd_timeout = btmtk_reset_sync; hdev->set_bdaddr = btmtk_set_bdaddr; + hdev->send = btusb_send_frame_mtk; set_bit(HCI_QUIRK_BROKEN_ENHANCED_SETUP_SYNC_CONN, &hdev->quirks); set_bit(HCI_QUIRK_NON_PERSISTENT_SETUP, &hdev->quirks); - data->recv_acl = btusb_recv_acl_mtk; + data->recv_acl = btmtk_usb_recv_acl; + data->suspend = btmtk_usb_suspend; + data->resume = btmtk_usb_resume; } if (id->driver_info & BTUSB_SWAVE) { @@ -4694,6 +4109,9 @@ static int btusb_suspend(struct usb_interface *intf, pm_message_t message) cancel_work_sync(&data->work); + if (data->suspend) + data->suspend(data->hdev); + btusb_stop_traffic(data); usb_kill_anchored_urbs(&data->tx_anchor); @@ -4797,6 +4215,9 @@ static int btusb_resume(struct usb_interface *intf) btusb_submit_isoc_urb(hdev, GFP_NOIO); } + if (data->resume) + data->resume(hdev); + spin_lock_irq(&data->txlock); play_deferred(data); clear_bit(BTUSB_SUSPENDING, &data->flags); diff --git a/drivers/bluetooth/hci_bcm4377.c b/drivers/bluetooth/hci_bcm4377.c index d90858ea2fe5..77a5454a8721 100644 --- a/drivers/bluetooth/hci_bcm4377.c +++ b/drivers/bluetooth/hci_bcm4377.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT /* - * Bluetooth HCI driver for Broadcom 4377/4378/4387 devices attached via PCIe + * Bluetooth HCI driver for Broadcom 4377/4378/4387/4388 devices attached via PCIe * * Copyright (C) The Asahi Linux Contributors */ @@ -26,13 +26,16 @@ enum bcm4377_chip { BCM4377 = 0, BCM4378, BCM4387, + BCM4388, }; #define BCM4377_DEVICE_ID 0x5fa0 #define BCM4378_DEVICE_ID 0x5f69 #define BCM4387_DEVICE_ID 0x5f71 +#define BCM4388_DEVICE_ID 0x5f72 -#define BCM4377_TIMEOUT 1000 +#define BCM4377_TIMEOUT msecs_to_jiffies(1000) +#define BCM4377_BOOT_TIMEOUT msecs_to_jiffies(5000) /* * These devices only support DMA transactions inside a 32bit window @@ -487,6 +490,7 @@ struct bcm4377_data; * second window in BAR0 * has_bar0_core2_window2: Set to true if this chip requires the second core's * second window to be configured + * bar2_offset: Offset to the start of the variables in BAR2 * clear_pciecfg_subsystem_ctrl_bit19: Set to true if bit 19 in the * vendor-specific subsystem control * register has to be cleared @@ -510,6 +514,7 @@ struct bcm4377_hw { u32 bar0_window1; u32 bar0_window2; u32 bar0_core2_window2; + u32 bar2_offset; unsigned long has_bar0_core2_window2 : 1; unsigned long clear_pciecfg_subsystem_ctrl_bit19 : 1; @@ -835,8 +840,8 @@ static irqreturn_t bcm4377_irq(int irq, void *data) struct bcm4377_data *bcm4377 = data; u32 bootstage, rti_status; - bootstage = ioread32(bcm4377->bar2 + BCM4377_BAR2_BOOTSTAGE); - rti_status = ioread32(bcm4377->bar2 + BCM4377_BAR2_RTI_STATUS); + bootstage = ioread32(bcm4377->bar2 + bcm4377->hw->bar2_offset + BCM4377_BAR2_BOOTSTAGE); + rti_status = ioread32(bcm4377->bar2 + bcm4377->hw->bar2_offset + BCM4377_BAR2_RTI_STATUS); if (bootstage != bcm4377->bootstage || rti_status != bcm4377->rti_status) { @@ -1196,6 +1201,14 @@ static int bcm4387_send_calibration(struct bcm4377_data *bcm4377) bcm4377->taurus_cal_size); } +static int bcm4388_send_calibration(struct bcm4377_data *bcm4377) +{ + /* BCM4388 always uses beamforming */ + return __bcm4378_send_calibration( + bcm4377, bcm4377->taurus_beamforming_cal_blob, + bcm4377->taurus_beamforming_cal_size); +} + static const struct firmware *bcm4377_request_blob(struct bcm4377_data *bcm4377, const char *suffix) { @@ -1819,8 +1832,8 @@ static int bcm4377_boot(struct bcm4377_data *bcm4377) int ret = 0; u32 bootstage, rti_status; - bootstage = ioread32(bcm4377->bar2 + BCM4377_BAR2_BOOTSTAGE); - rti_status = ioread32(bcm4377->bar2 + BCM4377_BAR2_RTI_STATUS); + bootstage = ioread32(bcm4377->bar2 + bcm4377->hw->bar2_offset + BCM4377_BAR2_BOOTSTAGE); + rti_status = ioread32(bcm4377->bar2 + bcm4377->hw->bar2_offset + BCM4377_BAR2_RTI_STATUS); if (bootstage != 0) { dev_err(&bcm4377->pdev->dev, "bootstage is %d and not 0\n", @@ -1854,15 +1867,18 @@ static int bcm4377_boot(struct bcm4377_data *bcm4377) iowrite32(BCM4377_DMA_MASK, bcm4377->bar0 + BCM4377_BAR0_HOST_WINDOW_SIZE); - iowrite32(lower_32_bits(fw_dma), bcm4377->bar2 + BCM4377_BAR2_FW_LO); - iowrite32(upper_32_bits(fw_dma), bcm4377->bar2 + BCM4377_BAR2_FW_HI); - iowrite32(fw->size, bcm4377->bar2 + BCM4377_BAR2_FW_SIZE); + iowrite32(lower_32_bits(fw_dma), + bcm4377->bar2 + bcm4377->hw->bar2_offset + BCM4377_BAR2_FW_LO); + iowrite32(upper_32_bits(fw_dma), + bcm4377->bar2 + bcm4377->hw->bar2_offset + BCM4377_BAR2_FW_HI); + iowrite32(fw->size, + bcm4377->bar2 + bcm4377->hw->bar2_offset + BCM4377_BAR2_FW_SIZE); iowrite32(0, bcm4377->bar0 + BCM4377_BAR0_FW_DOORBELL); dev_dbg(&bcm4377->pdev->dev, "waiting for firmware to boot\n"); ret = wait_for_completion_interruptible_timeout(&bcm4377->event, - BCM4377_TIMEOUT); + BCM4377_BOOT_TIMEOUT); if (ret == 0) { ret = -ETIMEDOUT; goto out_dma_free; @@ -1913,16 +1929,16 @@ static int bcm4377_setup_rti(struct bcm4377_data *bcm4377) dev_dbg(&bcm4377->pdev->dev, "RTI is in state 1\n"); /* allow access to the entire IOVA space again */ - iowrite32(0, bcm4377->bar2 + BCM4377_BAR2_RTI_WINDOW_LO); - iowrite32(0, bcm4377->bar2 + BCM4377_BAR2_RTI_WINDOW_HI); + iowrite32(0, bcm4377->bar2 + bcm4377->hw->bar2_offset + BCM4377_BAR2_RTI_WINDOW_LO); + iowrite32(0, bcm4377->bar2 + bcm4377->hw->bar2_offset + BCM4377_BAR2_RTI_WINDOW_HI); iowrite32(BCM4377_DMA_MASK, - bcm4377->bar2 + BCM4377_BAR2_RTI_WINDOW_SIZE); + bcm4377->bar2 + bcm4377->hw->bar2_offset + BCM4377_BAR2_RTI_WINDOW_SIZE); /* setup "Converged IPC" context */ iowrite32(lower_32_bits(bcm4377->ctx_dma), - bcm4377->bar2 + BCM4377_BAR2_CONTEXT_ADDR_LO); + bcm4377->bar2 + bcm4377->hw->bar2_offset + BCM4377_BAR2_CONTEXT_ADDR_LO); iowrite32(upper_32_bits(bcm4377->ctx_dma), - bcm4377->bar2 + BCM4377_BAR2_CONTEXT_ADDR_HI); + bcm4377->bar2 + bcm4377->hw->bar2_offset + BCM4377_BAR2_CONTEXT_ADDR_HI); iowrite32(2, bcm4377->bar0 + BCM4377_BAR0_RTI_CONTROL); ret = wait_for_completion_interruptible_timeout(&bcm4377->event, @@ -2488,6 +2504,21 @@ static const struct bcm4377_hw bcm4377_hw_variants[] = { .send_calibration = bcm4387_send_calibration, .send_ptb = bcm4378_send_ptb, }, + + [BCM4388] = { + .id = 0x4388, + .otp_offset = 0x415c, + .bar2_offset = 0x200000, + .bar0_window1 = 0x18002000, + .bar0_window2 = 0x18109000, + .bar0_core2_window2 = 0x18106000, + .has_bar0_core2_window2 = true, + .broken_mws_transport_config = true, + .broken_le_coded = true, + .broken_le_ext_adv_report_phy = true, + .send_calibration = bcm4388_send_calibration, + .send_ptb = bcm4378_send_ptb, + }, }; #define BCM4377_DEVID_ENTRY(id) \ @@ -2501,6 +2532,7 @@ static const struct pci_device_id bcm4377_devid_table[] = { BCM4377_DEVID_ENTRY(4377), BCM4377_DEVID_ENTRY(4378), BCM4377_DEVID_ENTRY(4387), + BCM4377_DEVID_ENTRY(4388), {}, }; MODULE_DEVICE_TABLE(pci, bcm4377_devid_table); @@ -2515,7 +2547,7 @@ static struct pci_driver bcm4377_pci_driver = { module_pci_driver(bcm4377_pci_driver); MODULE_AUTHOR("Sven Peter <sven@svenpeter.dev>"); -MODULE_DESCRIPTION("Bluetooth support for Broadcom 4377/4378/4387 devices"); +MODULE_DESCRIPTION("Bluetooth support for Broadcom 4377/4378/4387/4388 devices"); MODULE_LICENSE("Dual MIT/GPL"); MODULE_FIRMWARE("brcm/brcmbt4377*.bin"); MODULE_FIRMWARE("brcm/brcmbt4377*.ptb"); @@ -2523,3 +2555,5 @@ MODULE_FIRMWARE("brcm/brcmbt4378*.bin"); MODULE_FIRMWARE("brcm/brcmbt4378*.ptb"); MODULE_FIRMWARE("brcm/brcmbt4387*.bin"); MODULE_FIRMWARE("brcm/brcmbt4387*.ptb"); +MODULE_FIRMWARE("brcm/brcmbt4388*.bin"); +MODULE_FIRMWARE("brcm/brcmbt4388*.ptb"); diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c index 17a2f158a0df..30192bb08354 100644 --- a/drivers/bluetooth/hci_ldisc.c +++ b/drivers/bluetooth/hci_ldisc.c @@ -488,7 +488,7 @@ static int hci_uart_tty_open(struct tty_struct *tty) if (tty->ops->write == NULL) return -EOPNOTSUPP; - hu = kzalloc(sizeof(struct hci_uart), GFP_KERNEL); + hu = kzalloc(sizeof(*hu), GFP_KERNEL); if (!hu) { BT_ERR("Can't allocate control structure"); return -ENFILE; diff --git a/drivers/bluetooth/hci_nokia.c b/drivers/bluetooth/hci_nokia.c index 97da0b2bfd17..62633d9ba7c4 100644 --- a/drivers/bluetooth/hci_nokia.c +++ b/drivers/bluetooth/hci_nokia.c @@ -116,11 +116,6 @@ struct hci_nokia_neg_evt { #define SETUP_BAUD_RATE 921600 #define INIT_BAUD_RATE 120000 -struct hci_nokia_radio_hdr { - u8 evt; - u8 dlen; -} __packed; - struct nokia_bt_dev { struct hci_uart hu; struct serdev_device *serdev; diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c index 9a0bc86f9aac..ca6466676902 100644 --- a/drivers/bluetooth/hci_qca.c +++ b/drivers/bluetooth/hci_qca.c @@ -28,6 +28,7 @@ #include <linux/of.h> #include <linux/acpi.h> #include <linux/platform_device.h> +#include <linux/pwrseq/consumer.h> #include <linux/regulator/consumer.h> #include <linux/serdev.h> #include <linux/mutex.h> @@ -214,6 +215,7 @@ struct qca_power { struct regulator_bulk_data *vreg_bulk; int num_vregs; bool vregs_on; + struct pwrseq_desc *pwrseq; }; struct qca_serdev { @@ -569,7 +571,7 @@ static int qca_open(struct hci_uart *hu) if (!hci_uart_has_flow_control(hu)) return -EOPNOTSUPP; - qca = kzalloc(sizeof(struct qca_data), GFP_KERNEL); + qca = kzalloc(sizeof(*qca), GFP_KERNEL); if (!qca) return -ENOMEM; @@ -1040,8 +1042,7 @@ static void qca_controller_memdump(struct work_struct *work) } if (!qca_memdump) { - qca_memdump = kzalloc(sizeof(struct qca_memdump_info), - GFP_ATOMIC); + qca_memdump = kzalloc(sizeof(*qca_memdump), GFP_ATOMIC); if (!qca_memdump) { mutex_unlock(&qca->hci_memdump_lock); return; @@ -1685,6 +1686,27 @@ static bool qca_wakeup(struct hci_dev *hdev) return wakeup; } +static int qca_port_reopen(struct hci_uart *hu) +{ + int ret; + + /* Now the device is in ready state to communicate with host. + * To sync host with device we need to reopen port. + * Without this, we will have RTS and CTS synchronization + * issues. + */ + serdev_device_close(hu->serdev); + ret = serdev_device_open(hu->serdev); + if (ret) { + bt_dev_err(hu->hdev, "failed to open port"); + return ret; + } + + hci_uart_set_flow_control(hu, false); + + return 0; +} + static int qca_regulator_init(struct hci_uart *hu) { enum qca_btsoc_type soc_type = qca_soc_type(hu); @@ -1696,6 +1718,7 @@ static int qca_regulator_init(struct hci_uart *hu) * off the voltage regulator. */ qcadev = serdev_device_get_drvdata(hu->serdev); + if (!qcadev->bt_power->vregs_on) { serdev_device_close(hu->serdev); ret = qca_regulator_enable(qcadev); @@ -1753,21 +1776,7 @@ static int qca_regulator_init(struct hci_uart *hu) break; } - /* Now the device is in ready state to communicate with host. - * To sync host with device we need to reopen port. - * Without this, we will have RTS and CTS synchronization - * issues. - */ - serdev_device_close(hu->serdev); - ret = serdev_device_open(hu->serdev); - if (ret) { - bt_dev_err(hu->hdev, "failed to open port"); - return ret; - } - - hci_uart_set_flow_control(hu, false); - - return 0; + return qca_port_reopen(hu); } static int qca_power_on(struct hci_dev *hdev) @@ -1792,6 +1801,7 @@ static int qca_power_on(struct hci_dev *hdev) case QCA_WCN6750: case QCA_WCN6855: case QCA_WCN7850: + case QCA_QCA6390: ret = qca_regulator_init(hu); break; @@ -2130,6 +2140,7 @@ static void qca_power_shutdown(struct hci_uart *hu) unsigned long flags; enum qca_btsoc_type soc_type = qca_soc_type(hu); bool sw_ctrl_state; + struct qca_power *power; /* From this point we go into power off state. But serial port is * still open, stop queueing the IBS data and flush all the buffered @@ -2147,6 +2158,13 @@ static void qca_power_shutdown(struct hci_uart *hu) return; qcadev = serdev_device_get_drvdata(hu->serdev); + power = qcadev->bt_power; + + if (power->pwrseq) { + pwrseq_power_off(power->pwrseq); + set_bit(QCA_BT_OFF, &qca->flags); + return; + } switch (soc_type) { case QCA_WCN3988: @@ -2169,6 +2187,10 @@ static void qca_power_shutdown(struct hci_uart *hu) } break; + case QCA_QCA6390: + pwrseq_power_off(qcadev->bt_power->pwrseq); + break; + default: gpiod_set_value_cansleep(qcadev->bt_en, 0); } @@ -2204,6 +2226,9 @@ static int qca_regulator_enable(struct qca_serdev *qcadev) struct qca_power *power = qcadev->bt_power; int ret; + if (power->pwrseq) + return pwrseq_power_on(power->pwrseq); + /* Already enabled */ if (power->vregs_on) return 0; @@ -2272,6 +2297,13 @@ static int qca_init_regulators(struct qca_power *qca, return 0; } +static void qca_clk_disable_unprepare(void *data) +{ + struct clk *clk = data; + + clk_disable_unprepare(clk); +} + static int qca_serdev_probe(struct serdev_device *serdev) { struct qca_serdev *qcadev; @@ -2310,12 +2342,40 @@ static int qca_serdev_probe(struct serdev_device *serdev) case QCA_WCN6750: case QCA_WCN6855: case QCA_WCN7850: + case QCA_QCA6390: qcadev->bt_power = devm_kzalloc(&serdev->dev, sizeof(struct qca_power), GFP_KERNEL); if (!qcadev->bt_power) return -ENOMEM; + break; + default: + break; + } + + switch (qcadev->btsoc_type) { + case QCA_WCN6855: + case QCA_WCN7850: + if (!device_property_present(&serdev->dev, "enable-gpios")) { + /* + * Backward compatibility with old DT sources. If the + * node doesn't have the 'enable-gpios' property then + * let's use the power sequencer. Otherwise, let's + * drive everything outselves. + */ + qcadev->bt_power->pwrseq = devm_pwrseq_get(&serdev->dev, + "bluetooth"); + if (IS_ERR(qcadev->bt_power->pwrseq)) + return PTR_ERR(qcadev->bt_power->pwrseq); + break; + } + fallthrough; + case QCA_WCN3988: + case QCA_WCN3990: + case QCA_WCN3991: + case QCA_WCN3998: + case QCA_WCN6750: qcadev->bt_power->dev = &serdev->dev; err = qca_init_regulators(qcadev->bt_power, data->vregs, data->num_vregs); @@ -2353,12 +2413,13 @@ static int qca_serdev_probe(struct serdev_device *serdev) dev_err(&serdev->dev, "failed to acquire clk\n"); return PTR_ERR(qcadev->susclk); } + break; - err = hci_uart_register_device(&qcadev->serdev_hu, &qca_proto); - if (err) { - BT_ERR("wcn3990 serdev registration failed"); - return err; - } + case QCA_QCA6390: + qcadev->bt_power->pwrseq = devm_pwrseq_get(&serdev->dev, + "bluetooth"); + if (IS_ERR(qcadev->bt_power->pwrseq)) + return PTR_ERR(qcadev->bt_power->pwrseq); break; default: @@ -2385,12 +2446,18 @@ static int qca_serdev_probe(struct serdev_device *serdev) if (err) return err; - err = hci_uart_register_device(&qcadev->serdev_hu, &qca_proto); - if (err) { - BT_ERR("Rome serdev registration failed"); - clk_disable_unprepare(qcadev->susclk); + err = devm_add_action_or_reset(&serdev->dev, + qca_clk_disable_unprepare, + qcadev->susclk); + if (err) return err; - } + + } + + err = hci_uart_register_device(&qcadev->serdev_hu, &qca_proto); + if (err) { + BT_ERR("serdev registration failed"); + return err; } hdev = qcadev->serdev_hu.hdev; @@ -2428,15 +2495,11 @@ static void qca_serdev_remove(struct serdev_device *serdev) case QCA_WCN6750: case QCA_WCN6855: case QCA_WCN7850: - if (power->vregs_on) { + if (power->vregs_on) qca_power_shutdown(&qcadev->serdev_hu); - break; - } - fallthrough; - + break; default: - if (qcadev->susclk) - clk_disable_unprepare(qcadev->susclk); + break; } hci_uart_unregister_device(&qcadev->serdev_hu); diff --git a/drivers/bluetooth/hci_vhci.c b/drivers/bluetooth/hci_vhci.c index 28750a40f0ed..c4046f8f1985 100644 --- a/drivers/bluetooth/hci_vhci.c +++ b/drivers/bluetooth/hci_vhci.c @@ -633,7 +633,7 @@ static int vhci_open(struct inode *inode, struct file *file) { struct vhci_data *data; - data = kzalloc(sizeof(struct vhci_data), GFP_KERNEL); + data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 696bf77a7042..9a8e44ca9ae4 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only source "drivers/power/reset/Kconfig" +source "drivers/power/sequencing/Kconfig" source "drivers/power/supply/Kconfig" diff --git a/drivers/power/Makefile b/drivers/power/Makefile index effbf0377f32..962a2cd30a51 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_POWER_RESET) += reset/ +obj-$(CONFIG_POWER_SEQUENCING) += sequencing/ obj-$(CONFIG_POWER_SUPPLY) += supply/ diff --git a/drivers/power/sequencing/Kconfig b/drivers/power/sequencing/Kconfig new file mode 100644 index 000000000000..c9f1cdb66524 --- /dev/null +++ b/drivers/power/sequencing/Kconfig @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only + +menuconfig POWER_SEQUENCING + tristate "Power Sequencing support" + help + Say Y here to enable the Power Sequencing subsystem. + + This subsystem is designed to control power to devices that share + complex resources and/or require specific power sequences to be run + during power-up. + + If unsure, say no. + +if POWER_SEQUENCING + +config POWER_SEQUENCING_QCOM_WCN + tristate "Qualcomm WCN family PMU driver" + default m if ARCH_QCOM + help + Say Y here to enable the power sequencing driver for Qualcomm + WCN Bluetooth/WLAN chipsets. + + Typically, a package from the Qualcomm WCN family contains the BT + and WLAN modules whose power is controlled by the PMU module. As the + former two share the power-up sequence which is executed by the PMU, + this driver is needed for correct power control or else we'd risk not + respecting the required delays between enabling Bluetooth and WLAN. + +endif diff --git a/drivers/power/sequencing/Makefile b/drivers/power/sequencing/Makefile new file mode 100644 index 000000000000..2eec2df7912d --- /dev/null +++ b/drivers/power/sequencing/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_POWER_SEQUENCING) += pwrseq-core.o +pwrseq-core-y := core.o + +obj-$(CONFIG_POWER_SEQUENCING_QCOM_WCN) += pwrseq-qcom-wcn.o diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c new file mode 100644 index 000000000000..9e606c9f6f04 --- /dev/null +++ b/drivers/power/sequencing/core.c @@ -0,0 +1,1105 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 Linaro Ltd. + */ + +#include <linux/bug.h> +#include <linux/cleanup.h> +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/export.h> +#include <linux/idr.h> +#include <linux/kernel.h> +#include <linux/kref.h> +#include <linux/list.h> +#include <linux/lockdep.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/property.h> +#include <linux/pwrseq/consumer.h> +#include <linux/pwrseq/provider.h> +#include <linux/radix-tree.h> +#include <linux/rwsem.h> +#include <linux/slab.h> + +/* + * Power-sequencing framework for linux. + * + * This subsystem allows power sequence providers to register a set of targets + * that consumers may request and power-up/down. + * + * Glossary: + * + * Unit - a unit is a discreet chunk of a power sequence. For instance one unit + * may enable a set of regulators, another may enable a specific GPIO. Units + * can define dependencies in the form of other units that must be enabled + * before it itself can be. + * + * Target - a target is a set of units (composed of the "final" unit and its + * dependencies) that a consumer selects by its name when requesting a handle + * to the power sequencer. Via the dependency system, multiple targets may + * share the same parts of a power sequence but ignore parts that are + * irrelevant. + * + * Descriptor - a handle passed by the pwrseq core to every consumer that + * serves as the entry point to the provider layer. It ensures coherence + * between different users and keeps reference counting consistent. + * + * Each provider must define a .match() callback whose role is to determine + * whether a potential consumer is in fact associated with this sequencer. + * This allows creating abstraction layers on top of regular device-tree + * resources like regulators, clocks and other nodes connected to the consumer + * via phandle. + */ + +static DEFINE_IDA(pwrseq_ida); + +/* + * Protects the device list on the pwrseq bus from concurrent modifications + * but allows simultaneous read-only access. + */ +static DECLARE_RWSEM(pwrseq_sem); + +/** + * struct pwrseq_unit - Private power-sequence unit data. + * @ref: Reference count for this object. When it goes to 0, the object is + * destroyed. + * @name: Name of this target. + * @list: Link to siblings on the list of all units of a single sequencer. + * @deps: List of units on which this unit depends. + * @enable: Callback running the part of the power-on sequence provided by + * this unit. + * @disable: Callback running the part of the power-off sequence provided + * by this unit. + * @enable_count: Current number of users that enabled this unit. May be the + * consumer of the power sequencer or other units that depend + * on this one. + */ +struct pwrseq_unit { + struct kref ref; + const char *name; + struct list_head list; + struct list_head deps; + pwrseq_power_state_func enable; + pwrseq_power_state_func disable; + unsigned int enable_count; +}; + +static struct pwrseq_unit *pwrseq_unit_new(const struct pwrseq_unit_data *data) +{ + struct pwrseq_unit *unit; + + unit = kzalloc(sizeof(*unit), GFP_KERNEL); + if (!unit) + return NULL; + + unit->name = kstrdup_const(data->name, GFP_KERNEL); + if (!unit->name) { + kfree(unit); + return NULL; + } + + kref_init(&unit->ref); + INIT_LIST_HEAD(&unit->deps); + unit->enable = data->enable; + unit->disable = data->disable; + + return unit; +} + +static struct pwrseq_unit *pwrseq_unit_get(struct pwrseq_unit *unit) +{ + kref_get(&unit->ref); + + return unit; +} + +static void pwrseq_unit_release(struct kref *ref); + +static void pwrseq_unit_put(struct pwrseq_unit *unit) +{ + kref_put(&unit->ref, pwrseq_unit_release); +} + +/** + * struct pwrseq_unit_dep - Wrapper around a reference to the unit structure + * allowing to keep it on multiple dependency lists + * in different units. + * @list: Siblings on the list. + * @unit: Address of the referenced unit. + */ +struct pwrseq_unit_dep { + struct list_head list; + struct pwrseq_unit *unit; +}; + +static struct pwrseq_unit_dep *pwrseq_unit_dep_new(struct pwrseq_unit *unit) +{ + struct pwrseq_unit_dep *dep; + + dep = kzalloc(sizeof(*dep), GFP_KERNEL); + if (!dep) + return NULL; + + dep->unit = unit; + + return dep; +} + +static void pwrseq_unit_dep_free(struct pwrseq_unit_dep *ref) +{ + pwrseq_unit_put(ref->unit); + kfree(ref); +} + +static void pwrseq_unit_free_deps(struct list_head *list) +{ + struct pwrseq_unit_dep *dep, *next; + + list_for_each_entry_safe(dep, next, list, list) { + list_del(&dep->list); + pwrseq_unit_dep_free(dep); + } +} + +static void pwrseq_unit_release(struct kref *ref) +{ + struct pwrseq_unit *unit = container_of(ref, struct pwrseq_unit, ref); + + pwrseq_unit_free_deps(&unit->deps); + list_del(&unit->list); + kfree_const(unit->name); + kfree(unit); +} + +/** + * struct pwrseq_target - Private power-sequence target data. + * @list: Siblings on the list of all targets exposed by a power sequencer. + * @name: Name of the target. + * @unit: Final unit for this target. + * @post_enable: Callback run after the target unit has been enabled, *after* + * the state lock has been released. It's useful for implementing + * boot-up delays without blocking other users from powering up + * using the same power sequencer. + */ +struct pwrseq_target { + struct list_head list; + const char *name; + struct pwrseq_unit *unit; + pwrseq_power_state_func post_enable; +}; + +static struct pwrseq_target * +pwrseq_target_new(const struct pwrseq_target_data *data) +{ + struct pwrseq_target *target; + + target = kzalloc(sizeof(*target), GFP_KERNEL); + if (!target) + return NULL; + + target->name = kstrdup_const(data->name, GFP_KERNEL); + if (!target->name) { + kfree(target); + return NULL; + } + + target->post_enable = data->post_enable; + + return target; +} + +static void pwrseq_target_free(struct pwrseq_target *target) +{ + pwrseq_unit_put(target->unit); + kfree_const(target->name); + kfree(target); +} + +/** + * struct pwrseq_device - Private power sequencing data. + * @dev: Device struct associated with this sequencer. + * @id: Device ID. + * @owner: Prevents removal of active power sequencing providers. + * @rw_lock: Protects the device from being unregistered while in use. + * @state_lock: Prevents multiple users running the power sequence at the same + * time. + * @match: Power sequencer matching callback. + * @targets: List of targets exposed by this sequencer. + * @units: List of all units supported by this sequencer. + */ +struct pwrseq_device { + struct device dev; + int id; + struct module *owner; + struct rw_semaphore rw_lock; + struct mutex state_lock; + pwrseq_match_func match; + struct list_head targets; + struct list_head units; +}; + +static struct pwrseq_device *to_pwrseq_device(struct device *dev) +{ + return container_of(dev, struct pwrseq_device, dev); +} + +static struct pwrseq_device *pwrseq_device_get(struct pwrseq_device *pwrseq) +{ + get_device(&pwrseq->dev); + + return pwrseq; +} + +static void pwrseq_device_put(struct pwrseq_device *pwrseq) +{ + put_device(&pwrseq->dev); +} + +/** + * struct pwrseq_desc - Wraps access to the pwrseq_device and ensures that one + * user cannot break the reference counting for others. + * @pwrseq: Reference to the power sequencing device. + * @target: Reference to the target this descriptor allows to control. + * @powered_on: Power state set by the holder of the descriptor (not necessarily + * corresponding to the actual power state of the device). + */ +struct pwrseq_desc { + struct pwrseq_device *pwrseq; + struct pwrseq_target *target; + bool powered_on; +}; + +static const struct bus_type pwrseq_bus = { + .name = "pwrseq", +}; + +static void pwrseq_release(struct device *dev) +{ + struct pwrseq_device *pwrseq = to_pwrseq_device(dev); + struct pwrseq_target *target, *pos; + + list_for_each_entry_safe(target, pos, &pwrseq->targets, list) { + list_del(&target->list); + pwrseq_target_free(target); + } + + mutex_destroy(&pwrseq->state_lock); + ida_free(&pwrseq_ida, pwrseq->id); + kfree(pwrseq); +} + +static const struct device_type pwrseq_device_type = { + .name = "power_sequencer", + .release = pwrseq_release, +}; + +static int pwrseq_check_unit_deps(const struct pwrseq_unit_data *data, + struct radix_tree_root *visited_units) +{ + const struct pwrseq_unit_data *tmp, **cur; + int ret; + + ret = radix_tree_insert(visited_units, (unsigned long)data, + (void *)data); + if (ret) + return ret; + + for (cur = data->deps; cur && *cur; cur++) { + tmp = radix_tree_lookup(visited_units, (unsigned long)*cur); + if (tmp) { + WARN(1, "Circular dependency in power sequencing flow detected!\n"); + return -EINVAL; + } + + ret = pwrseq_check_unit_deps(*cur, visited_units); + if (ret) + return ret; + } + + return 0; +} + +static int pwrseq_check_target_deps(const struct pwrseq_target_data *data) +{ + struct radix_tree_root visited_units; + struct radix_tree_iter iter; + void __rcu **slot; + int ret; + + if (!data->unit) + return -EINVAL; + + INIT_RADIX_TREE(&visited_units, GFP_KERNEL); + ret = pwrseq_check_unit_deps(data->unit, &visited_units); + radix_tree_for_each_slot(slot, &visited_units, &iter, 0) + radix_tree_delete(&visited_units, iter.index); + + return ret; +} + +static int pwrseq_unit_setup_deps(const struct pwrseq_unit_data **data, + struct list_head *dep_list, + struct list_head *unit_list, + struct radix_tree_root *processed_units); + +static struct pwrseq_unit * +pwrseq_unit_setup(const struct pwrseq_unit_data *data, + struct list_head *unit_list, + struct radix_tree_root *processed_units) +{ + struct pwrseq_unit *unit; + int ret; + + unit = radix_tree_lookup(processed_units, (unsigned long)data); + if (unit) + return pwrseq_unit_get(unit); + + unit = pwrseq_unit_new(data); + if (!unit) + return ERR_PTR(-ENOMEM); + + if (data->deps) { + ret = pwrseq_unit_setup_deps(data->deps, &unit->deps, + unit_list, processed_units); + if (ret) { + pwrseq_unit_put(unit); + return ERR_PTR(ret); + } + } + + ret = radix_tree_insert(processed_units, (unsigned long)data, unit); + if (ret) { + pwrseq_unit_put(unit); + return ERR_PTR(ret); + } + + list_add_tail(&unit->list, unit_list); + + return unit; +} + +static int pwrseq_unit_setup_deps(const struct pwrseq_unit_data **data, + struct list_head *dep_list, + struct list_head *unit_list, + struct radix_tree_root *processed_units) +{ + const struct pwrseq_unit_data *pos; + struct pwrseq_unit_dep *dep; + struct pwrseq_unit *unit; + int i; + + for (i = 0; data[i]; i++) { + pos = data[i]; + + unit = pwrseq_unit_setup(pos, unit_list, processed_units); + if (IS_ERR(unit)) + return PTR_ERR(unit); + + dep = pwrseq_unit_dep_new(unit); + if (!dep) { + pwrseq_unit_put(unit); + return -ENOMEM; + } + + list_add_tail(&dep->list, dep_list); + } + + return 0; +} + +static int pwrseq_do_setup_targets(const struct pwrseq_target_data **data, + struct pwrseq_device *pwrseq, + struct radix_tree_root *processed_units) +{ + const struct pwrseq_target_data *pos; + struct pwrseq_target *target; + int ret, i; + + for (i = 0; data[i]; i++) { + pos = data[i]; + + ret = pwrseq_check_target_deps(pos); + if (ret) + return ret; + + target = pwrseq_target_new(pos); + if (!target) + return -ENOMEM; + + target->unit = pwrseq_unit_setup(pos->unit, &pwrseq->units, + processed_units); + if (IS_ERR(target->unit)) { + ret = PTR_ERR(target->unit); + pwrseq_target_free(target); + return ret; + } + + list_add_tail(&target->list, &pwrseq->targets); + } + + return 0; +} + +static int pwrseq_setup_targets(const struct pwrseq_target_data **targets, + struct pwrseq_device *pwrseq) +{ + struct radix_tree_root processed_units; + struct radix_tree_iter iter; + void __rcu **slot; + int ret; + + INIT_RADIX_TREE(&processed_units, GFP_KERNEL); + ret = pwrseq_do_setup_targets(targets, pwrseq, &processed_units); + radix_tree_for_each_slot(slot, &processed_units, &iter, 0) + radix_tree_delete(&processed_units, iter.index); + + return ret; +} + +/** + * pwrseq_device_register() - Register a new power sequencer. + * @config: Configuration of the new power sequencing device. + * + * The config structure is only used during the call and can be freed after + * the function returns. The config structure *must* have the parent device + * as well as the match() callback and at least one target set. + * + * Returns: + * Returns the address of the new pwrseq device or ERR_PTR() on failure. + */ +struct pwrseq_device * +pwrseq_device_register(const struct pwrseq_config *config) +{ + struct pwrseq_device *pwrseq; + int ret, id; + + if (!config->parent || !config->match || !config->targets || + !config->targets[0]) + return ERR_PTR(-EINVAL); + + pwrseq = kzalloc(sizeof(*pwrseq), GFP_KERNEL); + if (!pwrseq) + return ERR_PTR(-ENOMEM); + + pwrseq->dev.type = &pwrseq_device_type; + pwrseq->dev.bus = &pwrseq_bus; + pwrseq->dev.parent = config->parent; + device_set_node(&pwrseq->dev, dev_fwnode(config->parent)); + dev_set_drvdata(&pwrseq->dev, config->drvdata); + + id = ida_alloc(&pwrseq_ida, GFP_KERNEL); + if (id < 0) { + kfree(pwrseq); + return ERR_PTR(id); + } + + pwrseq->id = id; + + /* + * From this point onwards the device's release() callback is + * responsible for freeing resources. + */ + device_initialize(&pwrseq->dev); + + ret = dev_set_name(&pwrseq->dev, "pwrseq.%d", pwrseq->id); + if (ret) + goto err_put_pwrseq; + + pwrseq->owner = config->owner ?: THIS_MODULE; + pwrseq->match = config->match; + + init_rwsem(&pwrseq->rw_lock); + mutex_init(&pwrseq->state_lock); + INIT_LIST_HEAD(&pwrseq->targets); + INIT_LIST_HEAD(&pwrseq->units); + + ret = pwrseq_setup_targets(config->targets, pwrseq); + if (ret) + goto err_put_pwrseq; + + scoped_guard(rwsem_write, &pwrseq_sem) { + ret = device_add(&pwrseq->dev); + if (ret) + goto err_put_pwrseq; + } + + return pwrseq; + +err_put_pwrseq: + pwrseq_device_put(pwrseq); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(pwrseq_device_register); + +/** + * pwrseq_device_unregister() - Unregister the power sequencer. + * @pwrseq: Power sequencer to unregister. + */ +void pwrseq_device_unregister(struct pwrseq_device *pwrseq) +{ + struct device *dev = &pwrseq->dev; + struct pwrseq_target *target; + + scoped_guard(mutex, &pwrseq->state_lock) { + guard(rwsem_write)(&pwrseq->rw_lock); + + list_for_each_entry(target, &pwrseq->targets, list) + WARN(target->unit->enable_count, + "REMOVING POWER SEQUENCER WITH ACTIVE USERS\n"); + + guard(rwsem_write)(&pwrseq_sem); + + device_del(dev); + } + + pwrseq_device_put(pwrseq); +} +EXPORT_SYMBOL_GPL(pwrseq_device_unregister); + +static void devm_pwrseq_device_unregister(void *data) +{ + struct pwrseq_device *pwrseq = data; + + pwrseq_device_unregister(pwrseq); +} + +/** + * devm_pwrseq_device_register() - Managed variant of pwrseq_device_register(). + * @dev: Managing device. + * @config: Configuration of the new power sequencing device. + * + * Returns: + * Returns the address of the new pwrseq device or ERR_PTR() on failure. + */ +struct pwrseq_device * +devm_pwrseq_device_register(struct device *dev, + const struct pwrseq_config *config) +{ + struct pwrseq_device *pwrseq; + int ret; + + pwrseq = pwrseq_device_register(config); + if (IS_ERR(pwrseq)) + return pwrseq; + + ret = devm_add_action_or_reset(dev, devm_pwrseq_device_unregister, + pwrseq); + if (ret) + return ERR_PTR(ret); + + return pwrseq; +} +EXPORT_SYMBOL_GPL(devm_pwrseq_device_register); + +/** + * pwrseq_device_get_drvdata() - Get the driver private data associated with + * this sequencer. + * @pwrseq: Power sequencer object. + * + * Returns: + * Address of the private driver data. + */ +void *pwrseq_device_get_drvdata(struct pwrseq_device *pwrseq) +{ + return dev_get_drvdata(&pwrseq->dev); +} +EXPORT_SYMBOL_GPL(pwrseq_device_get_drvdata); + +struct pwrseq_match_data { + struct pwrseq_desc *desc; + struct device *dev; + const char *target; +}; + +static int pwrseq_match_device(struct device *pwrseq_dev, void *data) +{ + struct pwrseq_device *pwrseq = to_pwrseq_device(pwrseq_dev); + struct pwrseq_match_data *match_data = data; + struct pwrseq_target *target; + int ret; + + lockdep_assert_held_read(&pwrseq_sem); + + guard(rwsem_read)(&pwrseq->rw_lock); + if (!device_is_registered(&pwrseq->dev)) + return 0; + + ret = pwrseq->match(pwrseq, match_data->dev); + if (ret <= 0) + return ret; + + /* We got the matching device, let's find the right target. */ + list_for_each_entry(target, &pwrseq->targets, list) { + if (strcmp(target->name, match_data->target)) + continue; + + match_data->desc->target = target; + } + + /* + * This device does not have this target. No point in deferring as it + * will not get a new target dynamically later. + */ + if (!match_data->desc->target) + return -ENOENT; + + if (!try_module_get(pwrseq->owner)) + return -EPROBE_DEFER; + + match_data->desc->pwrseq = pwrseq_device_get(pwrseq); + + return 1; +} + +/** + * pwrseq_get() - Get the power sequencer associated with this device. + * @dev: Device for which to get the sequencer. + * @target: Name of the target exposed by the sequencer this device wants to + * reach. + * + * Returns: + * New power sequencer descriptor for use by the consumer driver or ERR_PTR() + * on failure. + */ +struct pwrseq_desc *pwrseq_get(struct device *dev, const char *target) +{ + struct pwrseq_match_data match_data; + int ret; + + struct pwrseq_desc *desc __free(kfree) = kzalloc(sizeof(*desc), + GFP_KERNEL); + if (!desc) + return ERR_PTR(-ENOMEM); + + match_data.desc = desc; + match_data.dev = dev; + match_data.target = target; + + guard(rwsem_read)(&pwrseq_sem); + + ret = bus_for_each_dev(&pwrseq_bus, NULL, &match_data, + pwrseq_match_device); + if (ret < 0) + return ERR_PTR(ret); + if (ret == 0) + /* No device matched. */ + return ERR_PTR(-EPROBE_DEFER); + + return no_free_ptr(desc); +} +EXPORT_SYMBOL_GPL(pwrseq_get); + +/** + * pwrseq_put() - Release the power sequencer descriptor. + * @desc: Descriptor to release. + */ +void pwrseq_put(struct pwrseq_desc *desc) +{ + struct pwrseq_device *pwrseq; + + if (!desc) + return; + + pwrseq = desc->pwrseq; + + if (desc->powered_on) + pwrseq_power_off(desc); + + kfree(desc); + module_put(pwrseq->owner); + pwrseq_device_put(pwrseq); +} +EXPORT_SYMBOL_GPL(pwrseq_put); + +static void devm_pwrseq_put(void *data) +{ + struct pwrseq_desc *desc = data; + + pwrseq_put(desc); +} + +/** + * devm_pwrseq_get() - Managed variant of pwrseq_get(). + * @dev: Device for which to get the sequencer and which also manages its + * lifetime. + * @target: Name of the target exposed by the sequencer this device wants to + * reach. + * + * Returns: + * New power sequencer descriptor for use by the consumer driver or ERR_PTR() + * on failure. + */ +struct pwrseq_desc *devm_pwrseq_get(struct device *dev, const char *target) +{ + struct pwrseq_desc *desc; + int ret; + + desc = pwrseq_get(dev, target); + if (IS_ERR(desc)) + return desc; + + ret = devm_add_action_or_reset(dev, devm_pwrseq_put, desc); + if (ret) + return ERR_PTR(ret); + + return desc; +} +EXPORT_SYMBOL_GPL(devm_pwrseq_get); + +static int pwrseq_unit_enable(struct pwrseq_device *pwrseq, + struct pwrseq_unit *target); +static int pwrseq_unit_disable(struct pwrseq_device *pwrseq, + struct pwrseq_unit *target); + +static int pwrseq_unit_enable_deps(struct pwrseq_device *pwrseq, + struct list_head *list) +{ + struct pwrseq_unit_dep *pos; + int ret = 0; + + list_for_each_entry(pos, list, list) { + ret = pwrseq_unit_enable(pwrseq, pos->unit); + if (ret) { + list_for_each_entry_continue_reverse(pos, list, list) + pwrseq_unit_disable(pwrseq, pos->unit); + break; + } + } + + return ret; +} + +static int pwrseq_unit_disable_deps(struct pwrseq_device *pwrseq, + struct list_head *list) +{ + struct pwrseq_unit_dep *pos; + int ret = 0; + + list_for_each_entry_reverse(pos, list, list) { + ret = pwrseq_unit_disable(pwrseq, pos->unit); + if (ret) { + list_for_each_entry_continue(pos, list, list) + pwrseq_unit_enable(pwrseq, pos->unit); + break; + } + } + + return ret; +} + +static int pwrseq_unit_enable(struct pwrseq_device *pwrseq, + struct pwrseq_unit *unit) +{ + int ret; + + lockdep_assert_held_read(&pwrseq->rw_lock); + lockdep_assert_held(&pwrseq->state_lock); + + if (unit->enable_count != 0) { + unit->enable_count++; + return 0; + } + + ret = pwrseq_unit_enable_deps(pwrseq, &unit->deps); + if (ret) { + dev_err(&pwrseq->dev, + "Failed to enable dependencies before power-on for target '%s': %d\n", + unit->name, ret); + return ret; + } + + if (unit->enable) { + ret = unit->enable(pwrseq); + if (ret) { + dev_err(&pwrseq->dev, + "Failed to enable target '%s': %d\n", + unit->name, ret); + pwrseq_unit_disable_deps(pwrseq, &unit->deps); + return ret; + } + } + + unit->enable_count++; + + return 0; +} + +static int pwrseq_unit_disable(struct pwrseq_device *pwrseq, + struct pwrseq_unit *unit) +{ + int ret; + + lockdep_assert_held_read(&pwrseq->rw_lock); + lockdep_assert_held(&pwrseq->state_lock); + + if (unit->enable_count == 0) { + WARN(1, "Unmatched power-off for target '%s'\n", + unit->name); + return -EBUSY; + } + + if (unit->enable_count != 1) { + unit->enable_count--; + return 0; + } + + if (unit->disable) { + ret = unit->disable(pwrseq); + if (ret) { + dev_err(&pwrseq->dev, + "Failed to disable target '%s': %d\n", + unit->name, ret); + return ret; + } + } + + ret = pwrseq_unit_disable_deps(pwrseq, &unit->deps); + if (ret) { + dev_err(&pwrseq->dev, + "Failed to disable dependencies after power-off for target '%s': %d\n", + unit->name, ret); + if (unit->enable) + unit->enable(pwrseq); + return ret; + } + + unit->enable_count--; + + return 0; +} + +/** + * pwrseq_power_on() - Issue a power-on request on behalf of the consumer + * device. + * @desc: Descriptor referencing the power sequencer. + * + * This function tells the power sequencer that the consumer wants to be + * powered-up. The sequencer may already have powered-up the device in which + * case the function returns 0. If the power-up sequence is already in + * progress, the function will block until it's done and return 0. If this is + * the first request, the device will be powered up. + * + * Returns: + * 0 on success, negative error number on failure. + */ +int pwrseq_power_on(struct pwrseq_desc *desc) +{ + struct pwrseq_device *pwrseq; + struct pwrseq_target *target; + struct pwrseq_unit *unit; + int ret; + + might_sleep(); + + if (!desc || desc->powered_on) + return 0; + + pwrseq = desc->pwrseq; + target = desc->target; + unit = target->unit; + + guard(rwsem_read)(&pwrseq->rw_lock); + if (!device_is_registered(&pwrseq->dev)) + return -ENODEV; + + scoped_guard(mutex, &pwrseq->state_lock) { + ret = pwrseq_unit_enable(pwrseq, unit); + if (!ret) + desc->powered_on = true; + } + + if (target->post_enable) { + ret = target->post_enable(pwrseq); + if (ret) { + pwrseq_unit_disable(pwrseq, unit); + desc->powered_on = false; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(pwrseq_power_on); + +/** + * pwrseq_power_off() - Issue a power-off request on behalf of the consumer + * device. + * @desc: Descriptor referencing the power sequencer. + * + * This undoes the effects of pwrseq_power_on(). It issues a power-off request + * on behalf of the consumer and when the last remaining user does so, the + * power-down sequence will be started. If one is in progress, the function + * will block until it's complete and then return. + * + * Returns: + * 0 on success, negative error number on failure. + */ +int pwrseq_power_off(struct pwrseq_desc *desc) +{ + struct pwrseq_device *pwrseq; + struct pwrseq_unit *unit; + int ret; + + might_sleep(); + + if (!desc || !desc->powered_on) + return 0; + + pwrseq = desc->pwrseq; + unit = desc->target->unit; + + guard(rwsem_read)(&pwrseq->rw_lock); + if (!device_is_registered(&pwrseq->dev)) + return -ENODEV; + + guard(mutex)(&pwrseq->state_lock); + + ret = pwrseq_unit_disable(pwrseq, unit); + if (!ret) + desc->powered_on = false; + + return ret; +} +EXPORT_SYMBOL_GPL(pwrseq_power_off); + +#if IS_ENABLED(CONFIG_DEBUG_FS) + +struct pwrseq_debugfs_count_ctx { + struct device *dev; + loff_t index; +}; + +static int pwrseq_debugfs_seq_count(struct device *dev, void *data) +{ + struct pwrseq_debugfs_count_ctx *ctx = data; + + ctx->dev = dev; + + return ctx->index-- ? 0 : 1; +} + +static void *pwrseq_debugfs_seq_start(struct seq_file *seq, loff_t *pos) +{ + struct pwrseq_debugfs_count_ctx ctx; + + ctx.dev = NULL; + ctx.index = *pos; + + /* + * We're holding the lock for the entire printout so no need to fiddle + * with device reference count. + */ + down_read(&pwrseq_sem); + + bus_for_each_dev(&pwrseq_bus, NULL, &ctx, pwrseq_debugfs_seq_count); + if (!ctx.index) + return NULL; + + return ctx.dev; +} + +static void *pwrseq_debugfs_seq_next(struct seq_file *seq, void *data, + loff_t *pos) +{ + struct device *curr = data; + + ++*pos; + + struct device *next __free(put_device) = + bus_find_next_device(&pwrseq_bus, curr); + return next; +} + +static void pwrseq_debugfs_seq_show_target(struct seq_file *seq, + struct pwrseq_target *target) +{ + seq_printf(seq, " target: [%s] (target unit: [%s])\n", + target->name, target->unit->name); +} + +static void pwrseq_debugfs_seq_show_unit(struct seq_file *seq, + struct pwrseq_unit *unit) +{ + struct pwrseq_unit_dep *ref; + + seq_printf(seq, " unit: [%s] - enable count: %u\n", + unit->name, unit->enable_count); + + if (list_empty(&unit->deps)) + return; + + seq_puts(seq, " dependencies:\n"); + list_for_each_entry(ref, &unit->deps, list) + seq_printf(seq, " [%s]\n", ref->unit->name); +} + +static int pwrseq_debugfs_seq_show(struct seq_file *seq, void *data) +{ + struct device *dev = data; + struct pwrseq_device *pwrseq = to_pwrseq_device(dev); + struct pwrseq_target *target; + struct pwrseq_unit *unit; + + seq_printf(seq, "%s:\n", dev_name(dev)); + + seq_puts(seq, " targets:\n"); + list_for_each_entry(target, &pwrseq->targets, list) + pwrseq_debugfs_seq_show_target(seq, target); + + seq_puts(seq, " units:\n"); + list_for_each_entry(unit, &pwrseq->units, list) + pwrseq_debugfs_seq_show_unit(seq, unit); + + return 0; +} + +static void pwrseq_debugfs_seq_stop(struct seq_file *seq, void *data) +{ + up_read(&pwrseq_sem); +} + +static const struct seq_operations pwrseq_debugfs_sops = { + .start = pwrseq_debugfs_seq_start, + .next = pwrseq_debugfs_seq_next, + .show = pwrseq_debugfs_seq_show, + .stop = pwrseq_debugfs_seq_stop, +}; +DEFINE_SEQ_ATTRIBUTE(pwrseq_debugfs); + +static struct dentry *pwrseq_debugfs_dentry; + +#endif /* CONFIG_DEBUG_FS */ + +static int __init pwrseq_init(void) +{ + int ret; + + ret = bus_register(&pwrseq_bus); + if (ret) { + pr_err("Failed to register the power sequencer bus\n"); + return ret; + } + +#if IS_ENABLED(CONFIG_DEBUG_FS) + pwrseq_debugfs_dentry = debugfs_create_file("pwrseq", 0444, NULL, NULL, + &pwrseq_debugfs_fops); +#endif /* CONFIG_DEBUG_FS */ + + return 0; +} +subsys_initcall(pwrseq_init); + +static void __exit pwrseq_exit(void) +{ +#if IS_ENABLED(CONFIG_DEBUG_FS) + debugfs_remove_recursive(pwrseq_debugfs_dentry); +#endif /* CONFIG_DEBUG_FS */ + + bus_unregister(&pwrseq_bus); +} +module_exit(pwrseq_exit); + +MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@linaro.org>"); +MODULE_DESCRIPTION("Power Sequencing subsystem core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/sequencing/pwrseq-qcom-wcn.c b/drivers/power/sequencing/pwrseq-qcom-wcn.c new file mode 100644 index 000000000000..42dacfda745e --- /dev/null +++ b/drivers/power/sequencing/pwrseq-qcom-wcn.c @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 Linaro Ltd. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/jiffies.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/pwrseq/provider.h> +#include <linux/string.h> +#include <linux/types.h> + +struct pwrseq_qcom_wcn_pdata { + const char *const *vregs; + size_t num_vregs; + unsigned int pwup_delay_ms; + unsigned int gpio_enable_delay_ms; +}; + +struct pwrseq_qcom_wcn_ctx { + struct pwrseq_device *pwrseq; + struct device_node *of_node; + const struct pwrseq_qcom_wcn_pdata *pdata; + struct regulator_bulk_data *regs; + struct gpio_desc *bt_gpio; + struct gpio_desc *wlan_gpio; + struct clk *clk; + unsigned long last_gpio_enable_jf; +}; + +static void pwrseq_qcom_wcn_ensure_gpio_delay(struct pwrseq_qcom_wcn_ctx *ctx) +{ + unsigned long diff_jiffies; + unsigned int diff_msecs; + + if (!ctx->pdata->gpio_enable_delay_ms) + return; + + diff_jiffies = jiffies - ctx->last_gpio_enable_jf; + diff_msecs = jiffies_to_msecs(diff_jiffies); + + if (diff_msecs < ctx->pdata->gpio_enable_delay_ms) + msleep(ctx->pdata->gpio_enable_delay_ms - diff_msecs); +} + +static int pwrseq_qcom_wcn_vregs_enable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return regulator_bulk_enable(ctx->pdata->num_vregs, ctx->regs); +} + +static int pwrseq_qcom_wcn_vregs_disable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return regulator_bulk_disable(ctx->pdata->num_vregs, ctx->regs); +} + +static const struct pwrseq_unit_data pwrseq_qcom_wcn_vregs_unit_data = { + .name = "regulators-enable", + .enable = pwrseq_qcom_wcn_vregs_enable, + .disable = pwrseq_qcom_wcn_vregs_disable, +}; + +static int pwrseq_qcom_wcn_clk_enable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return clk_prepare_enable(ctx->clk); +} + +static int pwrseq_qcom_wcn_clk_disable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + clk_disable_unprepare(ctx->clk); + + return 0; +} + +static const struct pwrseq_unit_data pwrseq_qcom_wcn_clk_unit_data = { + .name = "clock-enable", + .enable = pwrseq_qcom_wcn_clk_enable, + .disable = pwrseq_qcom_wcn_clk_disable, +}; + +static const struct pwrseq_unit_data *pwrseq_qcom_wcn_unit_deps[] = { + &pwrseq_qcom_wcn_vregs_unit_data, + &pwrseq_qcom_wcn_clk_unit_data, + NULL +}; + +static int pwrseq_qcom_wcn_bt_enable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + pwrseq_qcom_wcn_ensure_gpio_delay(ctx); + gpiod_set_value_cansleep(ctx->bt_gpio, 1); + ctx->last_gpio_enable_jf = jiffies; + + return 0; +} + +static int pwrseq_qcom_wcn_bt_disable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + gpiod_set_value_cansleep(ctx->bt_gpio, 0); + + return 0; +} + +static const struct pwrseq_unit_data pwrseq_qcom_wcn_bt_unit_data = { + .name = "bluetooth-enable", + .deps = pwrseq_qcom_wcn_unit_deps, + .enable = pwrseq_qcom_wcn_bt_enable, + .disable = pwrseq_qcom_wcn_bt_disable, +}; + +static int pwrseq_qcom_wcn_wlan_enable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + pwrseq_qcom_wcn_ensure_gpio_delay(ctx); + gpiod_set_value_cansleep(ctx->wlan_gpio, 1); + ctx->last_gpio_enable_jf = jiffies; + + return 0; +} + +static int pwrseq_qcom_wcn_wlan_disable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + gpiod_set_value_cansleep(ctx->wlan_gpio, 0); + + return 0; +} + +static const struct pwrseq_unit_data pwrseq_qcom_wcn_wlan_unit_data = { + .name = "wlan-enable", + .deps = pwrseq_qcom_wcn_unit_deps, + .enable = pwrseq_qcom_wcn_wlan_enable, + .disable = pwrseq_qcom_wcn_wlan_disable, +}; + +static int pwrseq_qcom_wcn_pwup_delay(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + if (ctx->pdata->pwup_delay_ms) + msleep(ctx->pdata->pwup_delay_ms); + + return 0; +} + +static const struct pwrseq_target_data pwrseq_qcom_wcn_bt_target_data = { + .name = "bluetooth", + .unit = &pwrseq_qcom_wcn_bt_unit_data, + .post_enable = pwrseq_qcom_wcn_pwup_delay, +}; + +static const struct pwrseq_target_data pwrseq_qcom_wcn_wlan_target_data = { + .name = "wlan", + .unit = &pwrseq_qcom_wcn_wlan_unit_data, + .post_enable = pwrseq_qcom_wcn_pwup_delay, +}; + +static const struct pwrseq_target_data *pwrseq_qcom_wcn_targets[] = { + &pwrseq_qcom_wcn_bt_target_data, + &pwrseq_qcom_wcn_wlan_target_data, + NULL +}; + +static const char *const pwrseq_qca6390_vregs[] = { + "vddio", + "vddaon", + "vddpmu", + "vddrfa0p95", + "vddrfa1p3", + "vddrfa1p9", + "vddpcie1p3", + "vddpcie1p9", +}; + +static const struct pwrseq_qcom_wcn_pdata pwrseq_qca6390_of_data = { + .vregs = pwrseq_qca6390_vregs, + .num_vregs = ARRAY_SIZE(pwrseq_qca6390_vregs), + .pwup_delay_ms = 60, + .gpio_enable_delay_ms = 100, +}; + +static const char *const pwrseq_wcn7850_vregs[] = { + "vdd", + "vddio", + "vddio1p2", + "vddaon", + "vdddig", + "vddrfa1p2", + "vddrfa1p8", +}; + +static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn7850_of_data = { + .vregs = pwrseq_wcn7850_vregs, + .num_vregs = ARRAY_SIZE(pwrseq_wcn7850_vregs), + .pwup_delay_ms = 50, +}; + +static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq, + struct device *dev) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + struct device_node *dev_node = dev->of_node; + + /* + * The PMU supplies power to the Bluetooth and WLAN modules. both + * consume the PMU AON output so check the presence of the + * 'vddaon-supply' property and whether it leads us to the right + * device. + */ + if (!of_property_present(dev_node, "vddaon-supply")) + return 0; + + struct device_node *reg_node __free(device_node) = + of_parse_phandle(dev_node, "vddaon-supply", 0); + if (!reg_node) + return 0; + + /* + * `reg_node` is the PMU AON regulator, its parent is the `regulators` + * node and finally its grandparent is the PMU device node that we're + * looking for. + */ + if (!reg_node->parent || !reg_node->parent->parent || + reg_node->parent->parent != ctx->of_node) + return 0; + + return 1; +} + +static int pwrseq_qcom_wcn_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pwrseq_qcom_wcn_ctx *ctx; + struct pwrseq_config config; + int i, ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->of_node = dev->of_node; + + ctx->pdata = of_device_get_match_data(dev); + if (!ctx->pdata) + return dev_err_probe(dev, -ENODEV, + "Failed to obtain platform data\n"); + + ctx->regs = devm_kcalloc(dev, ctx->pdata->num_vregs, + sizeof(*ctx->regs), GFP_KERNEL); + if (!ctx->regs) + return -ENOMEM; + + for (i = 0; i < ctx->pdata->num_vregs; i++) + ctx->regs[i].supply = ctx->pdata->vregs[i]; + + ret = devm_regulator_bulk_get(dev, ctx->pdata->num_vregs, ctx->regs); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to get all regulators\n"); + + ctx->bt_gpio = devm_gpiod_get_optional(dev, "bt-enable", GPIOD_OUT_LOW); + if (IS_ERR(ctx->bt_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->bt_gpio), + "Failed to get the Bluetooth enable GPIO\n"); + + ctx->wlan_gpio = devm_gpiod_get_optional(dev, "wlan-enable", + GPIOD_OUT_LOW); + if (IS_ERR(ctx->wlan_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->wlan_gpio), + "Failed to get the WLAN enable GPIO\n"); + + ctx->clk = devm_clk_get_optional(dev, NULL); + if (IS_ERR(ctx->clk)) + return dev_err_probe(dev, PTR_ERR(ctx->clk), + "Failed to get the reference clock\n"); + + memset(&config, 0, sizeof(config)); + + config.parent = dev; + config.owner = THIS_MODULE; + config.drvdata = ctx; + config.match = pwrseq_qcom_wcn_match; + config.targets = pwrseq_qcom_wcn_targets; + + ctx->pwrseq = devm_pwrseq_device_register(dev, &config); + if (IS_ERR(ctx->pwrseq)) + return dev_err_probe(dev, PTR_ERR(ctx->pwrseq), + "Failed to register the power sequencer\n"); + + return 0; +} + +static const struct of_device_id pwrseq_qcom_wcn_of_match[] = { + { + .compatible = "qcom,qca6390-pmu", + .data = &pwrseq_qca6390_of_data, + }, + { + .compatible = "qcom,wcn7850-pmu", + .data = &pwrseq_wcn7850_of_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, pwrseq_qcom_wcn_of_match); + +static struct platform_driver pwrseq_qcom_wcn_driver = { + .driver = { + .name = "pwrseq-qcom_wcn", + .of_match_table = pwrseq_qcom_wcn_of_match, + }, + .probe = pwrseq_qcom_wcn_probe, +}; +module_platform_driver(pwrseq_qcom_wcn_driver); + +MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@linaro.org>"); +MODULE_DESCRIPTION("Qualcomm WCN PMU power sequencing driver"); +MODULE_LICENSE("GPL"); |