| /* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */ |
| /* |
| * common/rtk_fw.c |
| * |
| * Copyright (C) 2020 Amlogic, Inc. All rights reserved. |
| * |
| */ |
| |
| /****************************************************************************** |
| * Copyright (c) 2009-2015 by Hisi. |
| * All rights reserved. |
| * |
| * Create by Chenzetian. 2015-11-16 |
| * |
| ******************************************************************************/ |
| |
| #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 <malloc.h> |
| #include <usb.h> |
| #include <rtk_fw.h> |
| |
| #define USB_ERROR(fmt, args...) printf(fmt, ##args) |
| #define USB_DEBUG(fmt, args...) printf(fmt, ##args) |
| #define CHIP_TYPE_RTL8723BU "fw_8723b_b.txt" |
| #define CHIP_TYPE_RTL8723DU "fw_8723du_c.txt" |
| #define CHIP_TYPE_RTL8822BU "fw_8822b_b.txt" |
| #define CODE_MAXSIZE_24K (1024*24) //24K |
| #define CODE_MAXSIZE_40K (1024*40) //40K |
| #define code_maxsize 23639//56 * 10K |
| int fw_8723bu=0; |
| int fw_8723du=0; |
| int fw_8822bu=0; |
| |
| //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] = { |
| #include CHIP_TYPE_RTL8822BU |
| }; |
| |
| |
| 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; |
| |
| if (fw_8723bu) { |
| fw_info->fw_data = malloc(CODE_MAXSIZE_24K); |
| memset(fw_info->fw_data,0,CODE_MAXSIZE_24K); |
| } |
| |
| if (fw_8723du) { |
| fw_info->fw_data = malloc(CODE_MAXSIZE_40K); |
| memset(fw_info->fw_data,0,CODE_MAXSIZE_40K); |
| } |
| if (fw_8822bu) { |
| fw_info->fw_data = malloc(code_maxsize); |
| memset(fw_info->fw_data,0,code_maxsize); |
| //USB_DEBUG("zengqiu %s[%d]: start.\n", __func__, __LINE__); |
| } |
| |
| 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; |
| |
| if (fw_8723bu) { |
| size = lu8Code_RTL8723BU[0] | (lu8Code_RTL8723BU[1] << 8); |
| USB_DEBUG("%s,size is %d\n",__func__, size); |
| |
| if (size >= CODE_MAXSIZE_24K) { |
| USB_ERROR("%s,size is %d firmware is larger than %d\n",__func__, size, CODE_MAXSIZE_24K); |
| return -1; |
| } |
| |
| memcpy(*buff, &lu8Code_RTL8723BU[2],size); |
| fw_8723bu = 0; |
| return size; |
| } |
| |
| if (fw_8723du) { |
| size = lu8Code_RTL8723DU[0] | (lu8Code_RTL8723DU[1] << 8); |
| USB_DEBUG("%s,size is %d\n",__func__, size); |
| |
| if (size >= CODE_MAXSIZE_40K) { |
| USB_ERROR("%s,size is %d firmware is larger than %d\n",__func__, size, CODE_MAXSIZE_40K); |
| return -1; |
| } |
| |
| memcpy(*buff, &lu8Code_RTL8723DU[2],size); |
| fw_8723du= 0; |
| return size; |
| } |
| if (fw_8822bu) { |
| size = lu8Code_RTL8822BU[0] | (lu8Code_RTL8822BU[1] << 8); |
| USB_DEBUG("%s,size is %d\n",__func__, size); |
| |
| memcpy(*buff, &lu8Code_RTL8822BU[2],size); |
| fw_8822bu = 0; |
| return size; |
| } |
| return 0; |
| } |
| 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 (CMD_CMP_EVT == fw_info->evt_hdr->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("reset_controller \n"); |
| 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); |
| } |
| 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; |
| } else |
| //USB_DEBUG("%s: Send frag num %d\n", __func__, cmd_para->index); |
| |
| 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; |
| } else { |
| //USB_DEBUG("%s: Receive acked frag num %d\n", __func__, evt_para->index); |
| ncmd--; |
| } |
| |
| if (0 != evt_para->status) { |
| //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 = 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", __func__, ever_evt->status, ever_evt->version); |
| |
| 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_VERISION); |
| 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 0 // 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 == NULL) |
| 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 |
| struct usb_device * get_rtl_dev(void){ |
| int i; |
| struct usb_device *dev; |
| /* scan all USB Devices */ |
| dev = NULL; |
| for (i = 0; i < USB_MAX_DEVICE; i++) { |
| dev=usb_get_dev_index(i); /* get device */ |
| if (dev == NULL) { |
| 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); |
| printf("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) { |
| if (le16_to_cpu(dev->descriptor.idVendor) == 0x0bda |
| && le16_to_cpu(dev->descriptor.idProduct) == 0xb720){ |
| fw_8723bu = 1; |
| return dev; |
| } |
| if (le16_to_cpu(dev->descriptor.idVendor) == 0x0bda |
| && le16_to_cpu(dev->descriptor.idProduct) == 0xd723){ |
| fw_8723du = 1; |
| return dev; |
| } |
| if (le16_to_cpu(dev->descriptor.idVendor) == 0x0bda |
| && le16_to_cpu(dev->descriptor.idProduct) == 0xb82c){ |
| fw_8822bu = 1; |
| return dev; |
| } |
| } |
| } |
| return NULL; |
| } |
| int load_rtl_firmware_dev(struct usb_device *dev) |
| { |
| #if 0 |
| char *reboot_mode = getenv("reboot_mode"); |
| if (reboot_mode == NULL) |
| 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 20181220i_1)\n"); |
| |
| fw_info = firmware_info_init(dev); |
| if (NULL == fw_info) |
| return -1; |
| |
| ret_val = get_firmware(fw_info); |
| printf("++++MB:{%s}[%d]...(get_firmware = [%d]).\n", __FUNCTION__, __LINE__, ret_val); |
| if (-1 == ret_val) { |
| printf("++++MB:{%s}[%d]...get firmware, errno %d \n", __FUNCTION__, __LINE__, ret_val); |
| return ret_val; |
| } |
| |
| ret_val = check_chip_fw_version(fw_info); |
| printf("++++MB:{%s}[%d]...(check_chip_fw_version = [%d]).\n", __FUNCTION__, __LINE__, ret_val); |
| if (ret_val < 0) { |
| printf("++++MB:{%s}[%d]...Failed to check chip fw version, errno %d \n", __FUNCTION__, __LINE__, ret_val); |
| return ret_val; |
| } |
| |
| ret_val = reset_controller(fw_info); |
| printf("++++MB:{%s}[%d]...(reset_controller = [%d]).\n", __FUNCTION__, __LINE__, ret_val); |
| if (ret_val < 0) { |
| printf("++++MB:{%s}[%d]...(reset controller error = [%d]).\n", __FUNCTION__, __LINE__, ret_val); |
| return ret_val; |
| } |
| |
| ret_val = download_data(fw_info); |
| printf("++++MB:{%s}[%d]...(download_data = [%d]).\n", __FUNCTION__, __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); |
| printf("++++MB:{%s}[%d]...(set_wakeup_device = [%d]).\n", __FUNCTION__, __LINE__, ret_val); |
| //set_bt_wakeup(fw_info); |
| firmware_info_destroy(fw_info); |
| return ret_val; |
| } |
| |