| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * common/rtk_fw.c |
| * |
| * Copyright (C) 2020 Amlogic, Inc. All rights reserved. |
| * |
| */ |
| |
| /****************************************************************************** |
| * Copyright (c) 2020-2022 by Hisi. |
| * All rights reserved. |
| * |
| * Create by zengqiu. 2022-1-10 |
| * |
| ******************************************************************************/ |
| #include <common.h> |
| #include <command.h> |
| #include <asm/processor.h> |
| #include <linux/compiler.h> |
| #include <linux/ctype.h> |
| #include <asm/byteorder.h> |
| #include <asm/unaligned.h> |
| #include <dm/uclass.h> |
| #include <dm/device.h> |
| #include <dm/uclass-internal.h> |
| |
| #include <malloc.h> |
| #include <usb.h> |
| #include <rtk_fw.h> |
| |
| #define USB_ERROR(fmt, args...) {if (log_level) printf(fmt, ##args); } |
| #define USB_DEBUG(fmt, args...) {if (log_level) printf(fmt, ##args); } |
| |
| //patch txt formate: total length(2 bytes) || fw patch || config patch |
| static unsigned char lu8Code_RTL8723BU[CODE_MAXSIZE_24K] = { |
| #include CHIP_TYPE_RTL8723BU |
| }; |
| |
| static unsigned char lu8Code_RTL8723DU[CODE_MAXSIZE_40K] = { |
| #include CHIP_TYPE_RTL8723DU |
| }; |
| |
| static unsigned char lu8Code_RTL8822BU[CODE_MAXSIZE_23K] = { |
| #include CHIP_TYPE_RTL8822BU |
| }; |
| |
| static unsigned char lu8Code_RTL88X2CU[CODE_MAXSIZE_40K] = { |
| #include CHIP_TYPE_RTL88X2CU |
| }; |
| |
| static const bluetooth_dongle usb_dongle[] = { |
| {0x0bda, 0xb720, "8723bu", CHIP_TYPE_RTL8723BU, CODE_MAXSIZE_24K, lu8Code_RTL8723BU}, |
| {0x0bda, 0xd723, "8723du", CHIP_TYPE_RTL8723DU, CODE_MAXSIZE_40K, lu8Code_RTL8723DU}, |
| {0x0bda, 0xb82c, "8822bu", CHIP_TYPE_RTL8822BU, CODE_MAXSIZE_23K, lu8Code_RTL8822BU}, |
| {0x0bda, 0xc82c, "88x2cu", CHIP_TYPE_RTL88X2CU, CODE_MAXSIZE_40K, lu8Code_RTL88X2CU} |
| }; |
| |
| static int num = -1; |
| |
| firmware_info *firmware_info_init(struct usb_device *dev) |
| { |
| struct usb_device *udev = dev; |
| firmware_info *fw_info; |
| |
| USB_DEBUG("%s[%d]: start.\n", __func__, __LINE__); |
| |
| fw_info = malloc(sizeof(*fw_info)); |
| memset(fw_info, 0, sizeof(*fw_info)); |
| if (!fw_info) |
| return NULL; |
| |
| fw_info->fw_data = malloc(usb_dongle[num].fw_size); |
| memset(fw_info->fw_data, 0, usb_dongle[num].fw_size); |
| if (!fw_info->fw_data) { |
| free(fw_info); |
| return NULL; |
| } |
| |
| fw_info->send_pkt = malloc(PKT_LEN); |
| memset(fw_info->send_pkt, 0, PKT_LEN); |
| if (!fw_info->send_pkt) { |
| free(fw_info->fw_data); |
| free(fw_info); |
| return NULL; |
| } |
| |
| fw_info->rcv_pkt = malloc(PKT_LEN); |
| memset(fw_info->rcv_pkt, 0, PKT_LEN); |
| if (!fw_info->rcv_pkt) { |
| free(fw_info->fw_data); |
| free(fw_info->send_pkt); |
| free(fw_info); |
| return NULL; |
| } |
| |
| fw_info->patch_entry = NULL; |
| fw_info->intf = NULL; |
| fw_info->udev = udev; |
| fw_info->pipe_in = usb_rcvintpipe(fw_info->udev, INTR_EP); |
| fw_info->pipe_out = usb_sndctrlpipe(fw_info->udev, CTRL_EP); |
| fw_info->cmd_hdr = (struct hci_command_hdr *)(fw_info->send_pkt); |
| fw_info->evt_hdr = (struct hci_event_hdr *)(fw_info->rcv_pkt); |
| fw_info->cmd_cmp = (struct hci_ev_cmd_complete *)(fw_info->rcv_pkt + EVT_HDR_LEN); |
| fw_info->req_para = fw_info->send_pkt + CMD_HDR_LEN; |
| fw_info->rsp_para = fw_info->rcv_pkt + EVT_HDR_LEN + CMD_CMP_LEN; |
| |
| return fw_info; |
| } |
| |
| void firmware_info_destroy(firmware_info *fw_info) |
| { |
| free(fw_info->fw_data); |
| free(fw_info->rcv_pkt); |
| free(fw_info->send_pkt); |
| free(fw_info); |
| } |
| |
| int load_firmware(firmware_info *fw_info, unsigned char **buff) |
| { |
| unsigned int size; |
| |
| size = usb_dongle[num].fw_buf[0] | (usb_dongle[num].fw_buf[1] << 8); |
| USB_DEBUG("%s,%s size is %d\n", __func__, usb_dongle[num].fw_name, size); |
| |
| if (size > usb_dongle[num].fw_size) { |
| USB_ERROR("%s,size is %d firmware is larger than %d\n", |
| __func__, size, usb_dongle[num].fw_size); |
| num = -1; |
| return -1; |
| } |
| memcpy(*buff, &usb_dongle[num].fw_buf[2], size); |
| return size; |
| } |
| |
| int send_hci_cmd(firmware_info *fw_info) |
| { |
| int ret_val; |
| |
| ret_val = usb_control_msg(fw_info->udev, fw_info->pipe_out, |
| 0, USB_TYPE_CLASS, 0, 0, (void *)(fw_info->send_pkt), |
| fw_info->pkt_len, MSG_TO); |
| |
| return ret_val; |
| } |
| |
| int rcv_hci_evt(firmware_info *fw_info) |
| { |
| int /*ret_len = 0,*/ ret_val = 0; |
| int i; |
| |
| while (1) { |
| for (i = 0; i < 5; i++) { |
| ret_val = usb_submit_int_msg(fw_info->udev, |
| fw_info->pipe_in, |
| (void *)(fw_info->rcv_pkt), PKT_LEN, |
| MSG_TO); |
| if (ret_val >= 0) |
| break; |
| } |
| |
| //USB_DEBUG("fw_info->rcv_pkt:0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", |
| // fw_info->rcv_pkt[0], fw_info->rcv_pkt[1], |
| // fw_info->rcv_pkt[2], fw_info->rcv_pkt[3], |
| // fw_info->rcv_pkt[4], fw_info->rcv_pkt[5], |
| // fw_info->rcv_pkt[6]); |
| |
| if (ret_val < 0) |
| return ret_val; |
| |
| if (fw_info->evt_hdr->evt == CMD_CMP_EVT) { |
| if (fw_info->cmd_hdr->opcode == fw_info->cmd_cmp->opcode) |
| return 0; |
| } |
| } |
| } |
| |
| int get_firmware(firmware_info *fw_info) |
| { |
| /* patch_info *patch_entry = fw_info->patch_entry; */ |
| |
| fw_info->fw_len = load_firmware(fw_info, &fw_info->fw_data); |
| USB_DEBUG("%s[%d]: fw_len=%d\n", __func__, __LINE__, fw_info->fw_len); |
| if (fw_info->fw_len <= 0) |
| return -1; |
| |
| return 0; |
| } |
| |
| int reset_controller(firmware_info *fw_info) |
| { |
| int ret_val; |
| |
| USB_DEBUG("%s\n", __func__); |
| if (!fw_info) |
| return -ENODEV; |
| |
| fw_info->cmd_hdr->opcode = cpu_to_le16(HCI_VENDOR_FORCE_RESET_AND_PATCHABLE); |
| fw_info->cmd_hdr->plen = 0; |
| fw_info->pkt_len = CMD_HDR_LEN; |
| ret_val = send_hci_cmd(fw_info); |
| if (ret_val < 0) { |
| USB_DEBUG("%s: Failed to send hci cmd 0x%04x, errno %d\n", |
| __func__, fw_info->cmd_hdr->opcode, ret_val); |
| return ret_val; |
| } |
| |
| mdelay(200); |
| USB_DEBUG("%s: Wait fw reset for 200ms\n", __func__); |
| return ret_val; |
| } |
| |
| int download_data(firmware_info *fw_info) |
| { |
| download_cp *cmd_para; |
| download_rp *evt_para; |
| unsigned char *pcur; |
| int pkt_len, frag_num, frag_len; |
| int i, ret_val; |
| int ncmd = 1, step = 1; |
| |
| USB_DEBUG("%s: start\n", __func__); |
| |
| cmd_para = (download_cp *)fw_info->req_para; |
| evt_para = (download_rp *)fw_info->rsp_para; |
| pcur = fw_info->fw_data; |
| pkt_len = CMD_HDR_LEN + sizeof(download_cp); |
| frag_num = fw_info->fw_len / PATCH_SEG_MAX + 1; |
| frag_len = PATCH_SEG_MAX; |
| |
| cmd_para->index = 0; |
| for (i = 0; i < frag_num; i++) { |
| if (i == (frag_num - 1)) { |
| cmd_para->index |= DATA_END; |
| frag_len = fw_info->fw_len % PATCH_SEG_MAX; |
| pkt_len -= (PATCH_SEG_MAX - frag_len); |
| if (pkt_len == (CMD_HDR_LEN + sizeof(unsigned char))) |
| goto end; |
| } |
| fw_info->cmd_hdr->opcode = cpu_to_le16(DOWNLOAD_OPCODE); |
| fw_info->cmd_hdr->plen = sizeof(unsigned char) + frag_len; |
| fw_info->pkt_len = pkt_len; |
| memcpy(cmd_para->data, pcur, frag_len); |
| |
| if (step > 0) { |
| ret_val = send_hci_cmd(fw_info); |
| if (ret_val < 0) |
| //USB_ERROR("%s: Failed to send frag num %d\n", |
| // __func__, cmd_para->index); |
| return ret_val; |
| |
| if (--step > 0 && i < frag_num - 1) { |
| //USB_DEBUG("%s: Continue to send frag num %d\n", |
| // __func__, cmd_para->index + 1); |
| pcur += PATCH_SEG_MAX; |
| continue; |
| } |
| } |
| |
| while (ncmd > 0) { |
| ret_val = rcv_hci_evt(fw_info); |
| if (ret_val < 0) { |
| //USB_ERROR("%s: rcv_hci_evt err %d\n", __func__, ret_val); |
| return ret_val; |
| } |
| |
| //USB_DEBUG("%s: Receive acked frag num %d\n", __func__, evt_para->index); |
| ncmd--; |
| |
| if (evt_para->status != 0) { |
| //USB_ERROR("%s: Receive acked frag num %d, err status %d\n", |
| // __func__, ret_val, evt_para->status); |
| return -1; |
| } |
| |
| if ((evt_para->index & DATA_END) || (evt_para->index == frag_num - 1)) { |
| //USB_DEBUG("%s: Receive last acked index %d\n", |
| // __func__, evt_para->index); |
| goto end; |
| } |
| } |
| |
| cmd_para->index++; |
| if (cmd_para->index == 0x80) |
| cmd_para->index = 1; |
| ncmd = fw_info->cmd_cmp->ncmd; |
| step = fw_info->cmd_cmp->ncmd; |
| pcur += PATCH_SEG_MAX; |
| //USB_DEBUG("%s: HCI command packet num %d\n", __func__, ncmd); |
| } |
| |
| /* |
| * It is tricky that Host cannot receive DATA_END index from BT |
| * controller, at least for 8723au. We are doomed if failed. |
| */ |
| |
| end: |
| // USB_DEBUG("%s: done, sent %d frag pkts, received %d frag events\n", |
| // __func__, cmd_para->index, evt_para->index); |
| return fw_info->fw_len; |
| } |
| |
| int set_wakeup_device(firmware_info *fw_info) |
| { |
| struct rtk_eversion_evt *ever_evt; |
| int ret_val; |
| static unsigned char bdaddr[7] = {0x00, 0xd7, 0xe3, 0x3e, 0xf8, 0xe6, 0xa0}; |
| |
| if (!fw_info) |
| return -ENODEV; |
| |
| fw_info->cmd_hdr->opcode = cpu_to_le16(HCI_VENDOR_SET_WAKE_UP_DEVICE); |
| fw_info->cmd_hdr->plen = 7; |
| memcpy(fw_info->req_para, bdaddr, 7); |
| fw_info->pkt_len = CMD_HDR_LEN + 7; |
| |
| ret_val = send_hci_cmd(fw_info); |
| if (ret_val < 0) { |
| USB_ERROR("%s: Failed to send hci cmd 0x%04x, errno %d\n", |
| __func__, fw_info->cmd_hdr->opcode, ret_val); |
| return ret_val; |
| } |
| |
| ret_val = rcv_hci_evt(fw_info); |
| if (ret_val < 0) { |
| USB_ERROR("%s: Failed to receive hci event, errno %d\n", |
| __func__, ret_val); |
| return ret_val; |
| } |
| |
| ever_evt = (struct rtk_eversion_evt *)(fw_info->rsp_para); |
| |
| USB_DEBUG("%s: status %d, eversion %d\n", __func__, ever_evt->status, ever_evt->version); |
| |
| return ret_val; |
| } |
| |
| int reset_chip_dev(firmware_info *fw_info) |
| { |
| int ret_val; |
| |
| if (!fw_info) |
| return -ENODEV; |
| |
| fw_info->cmd_hdr->opcode = cpu_to_le16(HCI_VENDOR_CHIP_RESET); |
| fw_info->cmd_hdr->plen = 0; |
| fw_info->pkt_len = CMD_HDR_LEN; |
| |
| ret_val = send_hci_cmd(fw_info); |
| if (ret_val < 0) { |
| USB_ERROR("%s: Failed to send hci cmd 0x%04x, errno %d\n", |
| __func__, fw_info->cmd_hdr->opcode, ret_val); |
| return ret_val; |
| } |
| |
| ret_val = rcv_hci_evt(fw_info); |
| if (ret_val < 0) { |
| USB_ERROR("%s: Failed to receive hci event, errno %d\n", |
| __func__, ret_val); |
| return ret_val; |
| } |
| |
| if (!*fw_info->rsp_para) |
| printf("%s success\n", __func__); |
| |
| return ret_val; |
| } |
| |
| int check_chip_fw_version(firmware_info *fw_info) |
| { |
| struct rtk_localversion_evt *ever_evt; |
| int ret_val; |
| |
| if (!fw_info) |
| return -ENODEV; |
| |
| fw_info->cmd_hdr->opcode = cpu_to_le16(HCI_VENDOR_READ_LMP_VERSION); |
| fw_info->cmd_hdr->plen = 0; |
| fw_info->pkt_len = CMD_HDR_LEN; |
| |
| ret_val = send_hci_cmd(fw_info); |
| if (ret_val < 0) { |
| USB_ERROR("%s: Failed to send hci cmd 0x%04x, errno %d\n", |
| __func__, fw_info->cmd_hdr->opcode, ret_val); |
| return ret_val; |
| } |
| |
| ret_val = rcv_hci_evt(fw_info); |
| if (ret_val < 0) { |
| USB_ERROR("%s: Failed to receive hci event, errno %d\n", |
| __func__, ret_val); |
| return ret_val; |
| } |
| |
| ever_evt = (struct rtk_localversion_evt *)(fw_info->rsp_para); |
| |
| USB_DEBUG("%s: status %x\n", __func__, ever_evt->status); |
| USB_DEBUG("%s: hci_version %x\n", __func__, ever_evt->hci_version); |
| USB_DEBUG("%s: hci_revision %x\n", __func__, ever_evt->hci_revision); |
| USB_DEBUG("%s: lmp_version %x\n", __func__, ever_evt->lmp_version); |
| USB_DEBUG("%s: lmp_subversion %x\n", __func__, ever_evt->lmp_subversion); |
| USB_DEBUG("%s: lmp_manufacture %x\n", __func__, ever_evt->lmp_manufacture); |
| |
| return ret_val; |
| } |
| |
| #if DEL_CODE // just fro reference |
| void load_rtl_firmware(void) |
| { |
| firmware_info *fw_info; |
| int ret_val, i; |
| struct usb_device *dev; |
| |
| usb_init(); |
| |
| /* scan all USB Devices */ |
| dev = NULL; |
| for (i = 0; i < USB_MAX_DEVICE; i++) { |
| dev = usb_get_dev_index(i); /* get device */ |
| if (!dev) |
| return; |
| if (dev->devnum != -1) { |
| if (!strcmp(dev->mf, "Realtek") && !strcmp(dev->prod, "Bluetooth Radio")) { |
| /* Download BT Fireware */ |
| printf("Now Download BT Firmware.\n"); |
| fw_info = firmware_info_init(dev); |
| get_firmware(fw_info); |
| ret_val = download_data(fw_info); |
| if (ret_val > 0) |
| printf("Download fw patch done, fw len %d\n", ret_val); |
| mdelay(10); |
| set_wakeup_device(fw_info); |
| //set_bt_wakeup(fw_info); |
| firmware_info_destroy(fw_info); |
| } |
| } |
| } |
| } |
| #endif |
| |
| static struct usb_device *qiu_usb_find_device(int devnum) |
| { |
| #ifdef CONFIG_DM_USB |
| struct usb_device *udev; |
| struct udevice *hub; |
| struct uclass *uc; |
| int ret; |
| |
| /* Device addresses start at 1 */ |
| devnum++; |
| ret = uclass_get(UCLASS_USB_HUB, &uc); |
| if (ret) |
| return NULL; |
| |
| uclass_foreach_dev(hub, uc) { |
| struct udevice *dev; |
| |
| if (!device_active(hub)) |
| continue; |
| udev = dev_get_parent_priv(hub); |
| if (udev->devnum == devnum) |
| return udev; |
| |
| for (device_find_first_child(hub, &dev); |
| dev; |
| device_find_next_child(&dev)) { |
| if (!device_active(hub)) |
| continue; |
| |
| udev = dev_get_parent_priv(dev); |
| if (udev->devnum == devnum) |
| return udev; |
| } |
| } |
| #else |
| struct usb_device *udev; |
| int d; |
| |
| for (d = 0; d < USB_MAX_DEVICE; d++) { |
| udev = usb_get_dev_index(d); |
| if (!udev) |
| return NULL; |
| if (udev->devnum == devnum) |
| return udev; |
| } |
| #endif |
| |
| return NULL; |
| } |
| |
| struct usb_device *get_rtl_dev(void) |
| { |
| int i = 0; |
| int size, usb_size; |
| struct usb_device *dev; |
| /* scan all USB Devices */ |
| dev = NULL; |
| |
| for (i = 0; i < USB_MAX_DEVICE; i++) { |
| dev = qiu_usb_find_device(i); |
| if (!dev) { |
| printf("usb dev is null!!!!\n"); |
| return NULL; |
| } |
| |
| printf("i=%d dev->devnum:%d dev->mf:%s dev->prod:%s\n", |
| i, dev->devnum, dev->mf, dev->prod); |
| USB_DEBUG("vid is %x, pid is %x\n", |
| le16_to_cpu(dev->descriptor.idVendor), |
| le16_to_cpu(dev->descriptor.idProduct)); |
| /* AML change for haier to match 8723bu bt */ |
| if (dev->devnum != -1) { |
| size = sizeof(usb_dongle) / sizeof(bluetooth_dongle); |
| for (usb_size = 0; usb_size < size; usb_size++) { |
| if (le16_to_cpu(dev->descriptor.idVendor == |
| usb_dongle[usb_size].idVendor) && |
| le16_to_cpu(dev->descriptor.idProduct) == |
| usb_dongle[usb_size].idProduct) { |
| num = usb_size; |
| return dev; |
| } |
| } |
| } |
| } |
| printf("dou't find device\n"); |
| return NULL; |
| } |
| |
| int load_rtl_firmware_dev(struct usb_device *dev) |
| { |
| #if DEL_CODE |
| char *reboot_mode = getenv("reboot_mode"); |
| |
| if (!reboot_mode) |
| return 0; |
| |
| if (reboot_mode && strcmp("cold_boot", reboot_mode)) |
| return 0; |
| #endif |
| firmware_info *fw_info; |
| int ret_val = -1; |
| |
| /* Download BT Fireware */ |
| USB_DEBUG("Now Download RTK BT Firmware. (version 20221220i_1)\n"); |
| |
| fw_info = firmware_info_init(dev); |
| if (!fw_info) |
| return -1; |
| |
| ret_val = get_firmware(fw_info); |
| printf("++++ZQ:{%s}[%d]...(get_firmware = [%d]).\n", |
| __func__, __LINE__, fw_info->fw_len); |
| if (-1 == ret_val) { |
| printf("++++ZQ:{%s}[%d]...get firmware, errno %d\n", |
| __func__, __LINE__, ret_val); |
| return ret_val; |
| } |
| |
| ret_val = reset_chip_dev(fw_info); |
| if (ret_val < 0) { |
| printf("++++ZQ:{%s}[%d]...chip fail", __func__, __LINE__); |
| return ret_val; |
| } |
| |
| ret_val = check_chip_fw_version(fw_info); |
| USB_DEBUG("++++ZQ:{%s}[%d]...(check_chip_fw_version = [%d]).\n", |
| __func__, __LINE__, ret_val); |
| |
| if (ret_val < 0) { |
| printf("++++ZQ:{%s}[%d]...Failed to check chip fw version, errno %d\n", |
| __func__, __LINE__, ret_val); |
| return ret_val; |
| } |
| |
| ret_val = reset_controller(fw_info); |
| USB_DEBUG("++++ZQ:{%s}[%d]...(reset_controller = [%d]).\n", |
| __func__, __LINE__, ret_val); |
| |
| if (ret_val < 0) { |
| printf("++++ZQ:{%s}[%d]...(reset controller error = [%d]).\n", |
| __func__, __LINE__, ret_val); |
| return ret_val; |
| } |
| |
| ret_val = download_data(fw_info); |
| USB_DEBUG("++++ZQ:{%s}[%d]...(download_data = [%d]).\n", |
| __func__, __LINE__, ret_val); |
| |
| if (ret_val > 0) { |
| printf("Download fw patch done, fw len %d\n", ret_val); |
| } else { |
| firmware_info_destroy(fw_info); |
| return ret_val; |
| } |
| mdelay(10); |
| ret_val = set_wakeup_device(fw_info); |
| USB_DEBUG("++++ZQ:{%s}[%d]...(set_wakeup_device = [%d]).\n", |
| __func__, __LINE__, ret_val); |
| //set_bt_wakeup(fw_info); |
| firmware_info_destroy(fw_info); |
| return ret_val; |
| } |
| |