1 // SPDX-License-Identifier: GPL-2.0
2 /******************************************************************************
3  *
4  * Copyright(c) 2009-2013  Realtek Corporation.
5  *
6  * Contact Information:
7  * wlanfae <wlanfae@realtek.com>
8  * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park,
9  * Hsinchu 300, Taiwan.
10  *
11  * Larry Finger <Larry.Finger@lwfinger.net>
12  *
13  *****************************************************************************/
14 
15 #include "fw.h"
16 #include "drv_types.h"
17 #include "usb_ops_linux.h"
18 #include "rtl8188e_spec.h"
19 #include "rtl8188e_hal.h"
20 
21 #include <linux/firmware.h>
22 #include <linux/slab.h>
23 
_rtl88e_enable_fw_download(struct adapter * adapt,bool enable)24 static void _rtl88e_enable_fw_download(struct adapter *adapt, bool enable)
25 {
26 	u8 tmp;
27 
28 	if (enable) {
29 		tmp = usb_read8(adapt, REG_MCUFWDL);
30 		usb_write8(adapt, REG_MCUFWDL, tmp | 0x01);
31 
32 		tmp = usb_read8(adapt, REG_MCUFWDL + 2);
33 		usb_write8(adapt, REG_MCUFWDL + 2, tmp & 0xf7);
34 	} else {
35 		tmp = usb_read8(adapt, REG_MCUFWDL);
36 		usb_write8(adapt, REG_MCUFWDL, tmp & 0xfe);
37 
38 		usb_write8(adapt, REG_MCUFWDL + 1, 0x00);
39 	}
40 }
41 
_rtl88e_fw_block_write(struct adapter * adapt,const u8 * buffer,u32 size)42 static void _rtl88e_fw_block_write(struct adapter *adapt,
43 				   const u8 *buffer, u32 size)
44 {
45 	u32 blk_sz = sizeof(u32);
46 	const u8 *byte_buffer;
47 	const u32 *dword_buffer = (u32 *)buffer;
48 	u32 i, write_address, blk_cnt, remain;
49 
50 	blk_cnt = size / blk_sz;
51 	remain = size % blk_sz;
52 
53 	write_address = FW_8192C_START_ADDRESS;
54 
55 	for (i = 0; i < blk_cnt; i++, write_address += blk_sz)
56 		usb_write32(adapt, write_address, dword_buffer[i]);
57 
58 	byte_buffer = buffer + blk_cnt * blk_sz;
59 	for (i = 0; i < remain; i++, write_address++)
60 		usb_write8(adapt, write_address, byte_buffer[i]);
61 }
62 
_rtl88e_fw_page_write(struct adapter * adapt,u32 page,const u8 * buffer,u32 size)63 static void _rtl88e_fw_page_write(struct adapter *adapt,
64 				  u32 page, const u8 *buffer, u32 size)
65 {
66 	u8 value8;
67 	u8 u8page = (u8)(page & 0x07);
68 
69 	value8 = (usb_read8(adapt, REG_MCUFWDL + 2) & 0xF8) | u8page;
70 
71 	usb_write8(adapt, (REG_MCUFWDL + 2), value8);
72 	_rtl88e_fw_block_write(adapt, buffer, size);
73 }
74 
_rtl88e_write_fw(struct adapter * adapt,u8 * buffer,u32 size)75 static void _rtl88e_write_fw(struct adapter *adapt, u8 *buffer, u32 size)
76 {
77 	u8 *buf_ptr = buffer;
78 	u32 page_no, remain;
79 	u32 page, offset;
80 
81 	page_no = size / FW_8192C_PAGE_SIZE;
82 	remain = size % FW_8192C_PAGE_SIZE;
83 
84 	for (page = 0; page < page_no; page++) {
85 		offset = page * FW_8192C_PAGE_SIZE;
86 		_rtl88e_fw_page_write(adapt, page, (buf_ptr + offset),
87 				      FW_8192C_PAGE_SIZE);
88 	}
89 
90 	if (remain) {
91 		offset = page_no * FW_8192C_PAGE_SIZE;
92 		page = page_no;
93 		_rtl88e_fw_page_write(adapt, page, (buf_ptr + offset), remain);
94 	}
95 }
96 
rtl88e_firmware_selfreset(struct adapter * adapt)97 static void rtl88e_firmware_selfreset(struct adapter *adapt)
98 {
99 	u8 u1b_tmp;
100 
101 	u1b_tmp = usb_read8(adapt, REG_SYS_FUNC_EN + 1);
102 	usb_write8(adapt, REG_SYS_FUNC_EN + 1, (u1b_tmp & (~BIT(2))));
103 	usb_write8(adapt, REG_SYS_FUNC_EN + 1, (u1b_tmp | BIT(2)));
104 }
105 
_rtl88e_fw_free_to_go(struct adapter * adapt)106 static int _rtl88e_fw_free_to_go(struct adapter *adapt)
107 {
108 	int err = -EIO;
109 	u32 counter = 0;
110 	u32 value32;
111 
112 	do {
113 		value32 = usb_read32(adapt, REG_MCUFWDL);
114 		if (value32 & FWDL_CHKSUM_RPT)
115 			break;
116 	} while (counter++ < POLLING_READY_TIMEOUT_COUNT);
117 
118 	if (counter >= POLLING_READY_TIMEOUT_COUNT)
119 		goto exit;
120 
121 	value32 = usb_read32(adapt, REG_MCUFWDL);
122 	value32 |= MCUFWDL_RDY;
123 	value32 &= ~WINTINI_RDY;
124 	usb_write32(adapt, REG_MCUFWDL, value32);
125 
126 	rtl88e_firmware_selfreset(adapt);
127 	counter = 0;
128 
129 	do {
130 		value32 = usb_read32(adapt, REG_MCUFWDL);
131 		if (value32 & WINTINI_RDY) {
132 			err = 0;
133 			goto exit;
134 		}
135 
136 		udelay(FW_8192C_POLLING_DELAY);
137 
138 	} while (counter++ < POLLING_READY_TIMEOUT_COUNT);
139 
140 exit:
141 	return err;
142 }
143 
rtl88eu_download_fw(struct adapter * adapt)144 int rtl88eu_download_fw(struct adapter *adapt)
145 {
146 	struct dvobj_priv *dvobj = adapter_to_dvobj(adapt);
147 	struct device *device = dvobj_to_dev(dvobj);
148 	const struct firmware *fw;
149 	static const char fw_name[] = "rtlwifi/rtl8188eufw.bin";
150 	struct rtl92c_firmware_header *pfwheader = NULL;
151 	u8 *download_data, *fw_data;
152 	size_t download_size;
153 	unsigned int trailing_zeros_length;
154 
155 	if (request_firmware(&fw, fw_name, device)) {
156 		dev_err(device, "Firmware %s not available\n", fw_name);
157 		return -ENOENT;
158 	}
159 
160 	if (fw->size > FW_8188E_SIZE) {
161 		dev_err(device, "Firmware size exceed 0x%X. Check it.\n",
162 			FW_8188E_SIZE);
163 		release_firmware(fw);
164 		return -1;
165 	}
166 
167 	trailing_zeros_length = (4 - fw->size % 4) % 4;
168 
169 	fw_data = kmalloc(fw->size + trailing_zeros_length, GFP_KERNEL);
170 	if (!fw_data) {
171 		release_firmware(fw);
172 		return -ENOMEM;
173 	}
174 
175 	memcpy(fw_data, fw->data, fw->size);
176 	memset(fw_data + fw->size, 0, trailing_zeros_length);
177 
178 	pfwheader = (struct rtl92c_firmware_header *)fw_data;
179 
180 	if (IS_FW_HEADER_EXIST(pfwheader)) {
181 		download_data = fw_data + 32;
182 		download_size = fw->size + trailing_zeros_length - 32;
183 	} else {
184 		download_data = fw_data;
185 		download_size = fw->size + trailing_zeros_length;
186 	}
187 
188 	release_firmware(fw);
189 
190 	if (usb_read8(adapt, REG_MCUFWDL) & RAM_DL_SEL) {
191 		usb_write8(adapt, REG_MCUFWDL, 0);
192 		rtl88e_firmware_selfreset(adapt);
193 	}
194 	_rtl88e_enable_fw_download(adapt, true);
195 	usb_write8(adapt, REG_MCUFWDL,
196 		   usb_read8(adapt, REG_MCUFWDL) | FWDL_CHKSUM_RPT);
197 	_rtl88e_write_fw(adapt, download_data, download_size);
198 	_rtl88e_enable_fw_download(adapt, false);
199 
200 	kfree(fw_data);
201 	return _rtl88e_fw_free_to_go(adapt);
202 }
203