| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright(c) 2007 - 2011 Realtek Corporation. */ |
| |
| #include <linux/firmware.h> |
| #include "../include/rtw_fw.h" |
| |
| #define MAX_REG_BLOCK_SIZE 196 |
| #define FW_8188E_START_ADDRESS 0x1000 |
| #define MAX_PAGE_SIZE 4096 |
| |
| #define IS_FW_HEADER_EXIST(_fwhdr) \ |
| ((le16_to_cpu(_fwhdr->signature) & 0xFFF0) == 0x92C0 || \ |
| (le16_to_cpu(_fwhdr->signature) & 0xFFF0) == 0x88C0 || \ |
| (le16_to_cpu(_fwhdr->signature) & 0xFFF0) == 0x2300 || \ |
| (le16_to_cpu(_fwhdr->signature) & 0xFFF0) == 0x88E0) |
| |
| struct rt_firmware_hdr { |
| __le16 signature; /* 92C0: test chip; 92C, |
| * 88C0: test chip; 88C1: MP A-cut; |
| * 92C1: MP A-cut */ |
| u8 category; /* AP/NIC and USB/PCI */ |
| u8 function; /* Reserved for different FW function |
| * indcation, for further use when |
| * driver needs to download different |
| * FW for different conditions */ |
| __le16 version; /* FW Version */ |
| u8 subversion; /* FW Subversion, default 0x00 */ |
| u8 rsvd1; |
| u8 month; /* Release time Month field */ |
| u8 date; /* Release time Date field */ |
| u8 hour; /* Release time Hour field */ |
| u8 minute; /* Release time Minute field */ |
| __le16 ramcodesize; /* The size of RAM code */ |
| u8 foundry; |
| u8 rsvd2; |
| __le32 svnidx; /* The SVN entry index */ |
| __le32 rsvd3; |
| __le32 rsvd4; |
| __le32 rsvd5; |
| }; |
| |
| static_assert(sizeof(struct rt_firmware_hdr) == 32); |
| |
| static void fw_download_enable(struct adapter *padapter, bool enable) |
| { |
| u8 tmp; |
| int res; |
| |
| if (enable) { |
| /* MCU firmware download enable. */ |
| res = rtw_read8(padapter, REG_MCUFWDL, &tmp); |
| if (res) |
| return; |
| |
| rtw_write8(padapter, REG_MCUFWDL, tmp | 0x01); |
| |
| /* 8051 reset */ |
| res = rtw_read8(padapter, REG_MCUFWDL + 2, &tmp); |
| if (res) |
| return; |
| |
| rtw_write8(padapter, REG_MCUFWDL + 2, tmp & 0xf7); |
| } else { |
| /* MCU firmware download disable. */ |
| res = rtw_read8(padapter, REG_MCUFWDL, &tmp); |
| if (res) |
| return; |
| |
| rtw_write8(padapter, REG_MCUFWDL, tmp & 0xfe); |
| |
| /* Reserved for fw extension. */ |
| rtw_write8(padapter, REG_MCUFWDL + 1, 0x00); |
| } |
| } |
| |
| static int block_write(struct adapter *padapter, u8 *buffer, u32 size) |
| { |
| int ret = _SUCCESS; |
| u32 blocks, block_size, remain; |
| u32 i, offset, addr; |
| u8 *data; |
| |
| block_size = MAX_REG_BLOCK_SIZE; |
| |
| blocks = size / block_size; |
| remain = size % block_size; |
| |
| for (i = 0; i < blocks; i++) { |
| addr = FW_8188E_START_ADDRESS + i * block_size; |
| data = buffer + i * block_size; |
| |
| ret = rtw_writeN(padapter, addr, block_size, data); |
| if (ret == _FAIL) |
| goto exit; |
| } |
| |
| if (remain) { |
| offset = blocks * block_size; |
| block_size = 8; |
| |
| blocks = remain / block_size; |
| remain = remain % block_size; |
| |
| for (i = 0; i < blocks; i++) { |
| addr = FW_8188E_START_ADDRESS + offset + i * block_size; |
| data = buffer + offset + i * block_size; |
| |
| ret = rtw_writeN(padapter, addr, block_size, data); |
| if (ret == _FAIL) |
| goto exit; |
| } |
| } |
| |
| if (remain) { |
| offset += blocks * block_size; |
| |
| /* block size 1 */ |
| blocks = remain; |
| |
| for (i = 0; i < blocks; i++) { |
| addr = FW_8188E_START_ADDRESS + offset + i; |
| data = buffer + offset + i; |
| |
| ret = rtw_write8(padapter, addr, *data); |
| if (ret == _FAIL) |
| goto exit; |
| } |
| } |
| |
| exit: |
| return ret; |
| } |
| |
| static int page_write(struct adapter *padapter, u32 page, u8 *buffer, u32 size) |
| { |
| u8 value8; |
| u8 u8Page = (u8)(page & 0x07); |
| int res; |
| |
| res = rtw_read8(padapter, REG_MCUFWDL + 2, &value8); |
| if (res) |
| return _FAIL; |
| |
| value8 = (value8 & 0xF8) | u8Page; |
| rtw_write8(padapter, REG_MCUFWDL + 2, value8); |
| |
| return block_write(padapter, buffer, size); |
| } |
| |
| static int write_fw(struct adapter *padapter, u8 *buffer, u32 size) |
| { |
| /* Since we need dynamic decide method of dwonload fw, so we call this function to get chip version. */ |
| /* We can remove _ReadChipVersion from ReadpadapterInfo8192C later. */ |
| int ret = _SUCCESS; |
| u32 pageNums, remainSize; |
| u32 page, offset; |
| |
| pageNums = size / MAX_PAGE_SIZE; |
| remainSize = size % MAX_PAGE_SIZE; |
| |
| for (page = 0; page < pageNums; page++) { |
| offset = page * MAX_PAGE_SIZE; |
| ret = page_write(padapter, page, buffer + offset, MAX_PAGE_SIZE); |
| |
| if (ret == _FAIL) |
| goto exit; |
| } |
| if (remainSize) { |
| offset = pageNums * MAX_PAGE_SIZE; |
| page = pageNums; |
| ret = page_write(padapter, page, buffer + offset, remainSize); |
| |
| if (ret == _FAIL) |
| goto exit; |
| } |
| exit: |
| return ret; |
| } |
| |
| void rtw_reset_8051(struct adapter *padapter) |
| { |
| u8 val8; |
| int res; |
| |
| res = rtw_read8(padapter, REG_SYS_FUNC_EN + 1, &val8); |
| if (res) |
| return; |
| |
| rtw_write8(padapter, REG_SYS_FUNC_EN + 1, val8 & (~BIT(2))); |
| rtw_write8(padapter, REG_SYS_FUNC_EN + 1, val8 | (BIT(2))); |
| } |
| |
| static int fw_free_to_go(struct adapter *padapter) |
| { |
| u32 counter = 0; |
| u32 value32; |
| int res; |
| |
| /* polling CheckSum report */ |
| do { |
| res = rtw_read32(padapter, REG_MCUFWDL, &value32); |
| if (res) |
| continue; |
| |
| if (value32 & FWDL_CHKSUM_RPT) |
| break; |
| } while (counter++ < POLLING_READY_TIMEOUT_COUNT); |
| |
| if (counter >= POLLING_READY_TIMEOUT_COUNT) |
| return _FAIL; |
| |
| res = rtw_read32(padapter, REG_MCUFWDL, &value32); |
| if (res) |
| return _FAIL; |
| |
| value32 |= MCUFWDL_RDY; |
| value32 &= ~WINTINI_RDY; |
| rtw_write32(padapter, REG_MCUFWDL, value32); |
| |
| rtw_reset_8051(padapter); |
| |
| /* polling for FW ready */ |
| counter = 0; |
| do { |
| res = rtw_read32(padapter, REG_MCUFWDL, &value32); |
| if (!res && value32 & WINTINI_RDY) |
| return _SUCCESS; |
| |
| udelay(5); |
| } while (counter++ < POLLING_READY_TIMEOUT_COUNT); |
| |
| return _FAIL; |
| } |
| |
| static int load_firmware(struct rt_firmware *rtfw, struct device *device) |
| { |
| int ret = _SUCCESS; |
| const struct firmware *fw; |
| const char *fw_name = FW_RTL8188EU; |
| int err = request_firmware(&fw, fw_name, device); |
| |
| if (err) { |
| pr_err("Request firmware failed with error 0x%x\n", err); |
| ret = _FAIL; |
| goto exit; |
| } |
| if (!fw) { |
| pr_err("Firmware %s not available\n", fw_name); |
| ret = _FAIL; |
| goto exit; |
| } |
| |
| rtfw->data = kmemdup(fw->data, fw->size, GFP_KERNEL); |
| if (!rtfw->data) { |
| pr_err("Failed to allocate rtfw->data\n"); |
| ret = _FAIL; |
| goto exit; |
| } |
| rtfw->size = fw->size; |
| |
| exit: |
| release_firmware(fw); |
| return ret; |
| } |
| |
| int rtl8188e_firmware_download(struct adapter *padapter) |
| { |
| int ret = _SUCCESS; |
| u8 reg; |
| unsigned long fwdl_timeout; |
| struct dvobj_priv *dvobj = adapter_to_dvobj(padapter); |
| struct device *device = dvobj_to_dev(dvobj); |
| struct rt_firmware_hdr *fwhdr = NULL; |
| u8 *fw_data; |
| u32 fw_size; |
| |
| if (!dvobj->firmware.data) |
| ret = load_firmware(&dvobj->firmware, device); |
| if (ret == _FAIL) { |
| dvobj->firmware.data = NULL; |
| goto exit; |
| } |
| fw_data = dvobj->firmware.data; |
| fw_size = dvobj->firmware.size; |
| |
| fwhdr = (struct rt_firmware_hdr *)dvobj->firmware.data; |
| |
| if (IS_FW_HEADER_EXIST(fwhdr)) { |
| dev_info_once(device, "Firmware Version %d, SubVersion %d, Signature 0x%x\n", |
| le16_to_cpu(fwhdr->version), fwhdr->subversion, |
| le16_to_cpu(fwhdr->signature)); |
| |
| fw_data = fw_data + sizeof(struct rt_firmware_hdr); |
| fw_size = fw_size - sizeof(struct rt_firmware_hdr); |
| } |
| |
| /* Suggested by Filen. If 8051 is running in RAM code, driver should inform Fw to reset by itself, */ |
| /* or it will cause download Fw fail. 2010.02.01. by tynli. */ |
| ret = rtw_read8(padapter, REG_MCUFWDL, ®); |
| if (ret) { |
| ret = _FAIL; |
| goto exit; |
| } |
| |
| if (reg & RAM_DL_SEL) { /* 8051 RAM code */ |
| rtw_write8(padapter, REG_MCUFWDL, 0x00); |
| rtw_reset_8051(padapter); |
| } |
| |
| fw_download_enable(padapter, true); |
| fwdl_timeout = jiffies + msecs_to_jiffies(500); |
| do { |
| /* reset the FWDL chksum */ |
| ret = rtw_read8(padapter, REG_MCUFWDL, ®); |
| if (ret) { |
| ret = _FAIL; |
| continue; |
| } |
| |
| rtw_write8(padapter, REG_MCUFWDL, reg | FWDL_CHKSUM_RPT); |
| |
| ret = write_fw(padapter, fw_data, fw_size); |
| if (ret == _SUCCESS) |
| break; |
| } while (!time_after(jiffies, fwdl_timeout)); |
| |
| fw_download_enable(padapter, false); |
| if (ret != _SUCCESS) |
| goto exit; |
| |
| ret = fw_free_to_go(padapter); |
| if (ret != _SUCCESS) |
| goto exit; |
| |
| exit: |
| return ret; |
| } |