// SPDX-License-Identifier: GPL-2.0 /* Copyright(c) 2007 - 2011 Realtek Corporation. */ #include #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; }