diff options
author | Eran Harary <eran.harary@intel.com> | 2013-05-09 01:07:59 -0400 |
---|---|---|
committer | Johannes Berg <johannes.berg@intel.com> | 2013-05-16 17:16:59 -0400 |
commit | 1214755c2bfc74b56093ccae797cb295e89d3400 (patch) | |
tree | e80c3aed583aee75994abaf7b3f7795613b6afea /drivers/net/wireless/iwlwifi | |
parent | 07fd7d284dd01b46dea1986cb3bff20dfffe09bd (diff) |
iwlwifi: support loading NVM data from file
Some newer devices will be integrated into the platform more
deeply and will not have embedded NVM (EEPROM/OTP). To support
such devices the NVM data must be provided by the platform,
allow loading the data via request_firmware() and then send it
to the device as needed.
Signed-off-by: Eran Harary <eran.harary@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Diffstat (limited to 'drivers/net/wireless/iwlwifi')
-rw-r--r-- | drivers/net/wireless/iwlwifi/iwl-drv.c | 3 | ||||
-rw-r--r-- | drivers/net/wireless/iwlwifi/iwl-modparams.h | 1 | ||||
-rw-r--r-- | drivers/net/wireless/iwlwifi/mvm/nvm.c | 198 |
3 files changed, 194 insertions, 8 deletions
diff --git a/drivers/net/wireless/iwlwifi/iwl-drv.c b/drivers/net/wireless/iwlwifi/iwl-drv.c index 39aad9893e0b..4f886133639a 100644 --- a/drivers/net/wireless/iwlwifi/iwl-drv.c +++ b/drivers/net/wireless/iwlwifi/iwl-drv.c | |||
@@ -1234,6 +1234,9 @@ MODULE_PARM_DESC(wd_disable, | |||
1234 | "Disable stuck queue watchdog timer 0=system default, " | 1234 | "Disable stuck queue watchdog timer 0=system default, " |
1235 | "1=disable, 2=enable (default: 0)"); | 1235 | "1=disable, 2=enable (default: 0)"); |
1236 | 1236 | ||
1237 | module_param_named(nvm_file, iwlwifi_mod_params.nvm_file, charp, S_IRUGO); | ||
1238 | MODULE_PARM_DESC(nvm_file, "NVM file name"); | ||
1239 | |||
1237 | /* | 1240 | /* |
1238 | * set bt_coex_active to true, uCode will do kill/defer | 1241 | * set bt_coex_active to true, uCode will do kill/defer |
1239 | * every time the priority line is asserted (BT is sending signals on the | 1242 | * every time the priority line is asserted (BT is sending signals on the |
diff --git a/drivers/net/wireless/iwlwifi/iwl-modparams.h b/drivers/net/wireless/iwlwifi/iwl-modparams.h index d6f6c37c09fd..36dfe0919f6b 100644 --- a/drivers/net/wireless/iwlwifi/iwl-modparams.h +++ b/drivers/net/wireless/iwlwifi/iwl-modparams.h | |||
@@ -119,6 +119,7 @@ struct iwl_mod_params { | |||
119 | int ant_coupling; | 119 | int ant_coupling; |
120 | bool bt_ch_announce; | 120 | bool bt_ch_announce; |
121 | bool auto_agg; | 121 | bool auto_agg; |
122 | char *nvm_file; | ||
122 | }; | 123 | }; |
123 | 124 | ||
124 | #endif /* #__iwl_modparams_h__ */ | 125 | #endif /* #__iwl_modparams_h__ */ |
diff --git a/drivers/net/wireless/iwlwifi/mvm/nvm.c b/drivers/net/wireless/iwlwifi/mvm/nvm.c index ce464a5cca29..3f05c6b23874 100644 --- a/drivers/net/wireless/iwlwifi/mvm/nvm.c +++ b/drivers/net/wireless/iwlwifi/mvm/nvm.c | |||
@@ -60,6 +60,7 @@ | |||
60 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 60 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
61 | * | 61 | * |
62 | *****************************************************************************/ | 62 | *****************************************************************************/ |
63 | #include <linux/firmware.h> | ||
63 | #include "iwl-trans.h" | 64 | #include "iwl-trans.h" |
64 | #include "mvm.h" | 65 | #include "mvm.h" |
65 | #include "iwl-eeprom-parse.h" | 66 | #include "iwl-eeprom-parse.h" |
@@ -75,20 +76,46 @@ static const int nvm_to_read[] = { | |||
75 | }; | 76 | }; |
76 | 77 | ||
77 | /* Default NVM size to read */ | 78 | /* Default NVM size to read */ |
78 | #define IWL_NVM_DEFAULT_CHUNK_SIZE (2*1024); | 79 | #define IWL_NVM_DEFAULT_CHUNK_SIZE (2*1024) |
80 | #define IWL_MAX_NVM_SECTION_SIZE 6000 | ||
79 | 81 | ||
80 | static inline void iwl_nvm_fill_read(struct iwl_nvm_access_cmd *cmd, | 82 | #define NVM_WRITE_OPCODE 1 |
81 | u16 offset, u16 length, u16 section) | 83 | #define NVM_READ_OPCODE 0 |
84 | |||
85 | /* | ||
86 | * prepare the NVM host command w/ the pointers to the nvm buffer | ||
87 | * and send it to fw | ||
88 | */ | ||
89 | static int iwl_nvm_write_chunk(struct iwl_mvm *mvm, u16 section, | ||
90 | u16 offset, u16 length, const u8 *data) | ||
82 | { | 91 | { |
83 | cmd->offset = cpu_to_le16(offset); | 92 | struct iwl_nvm_access_cmd nvm_access_cmd = { |
84 | cmd->length = cpu_to_le16(length); | 93 | .offset = cpu_to_le16(offset), |
85 | cmd->type = cpu_to_le16(section); | 94 | .length = cpu_to_le16(length), |
95 | .type = cpu_to_le16(section), | ||
96 | .op_code = NVM_WRITE_OPCODE, | ||
97 | }; | ||
98 | struct iwl_host_cmd cmd = { | ||
99 | .id = NVM_ACCESS_CMD, | ||
100 | .len = { sizeof(struct iwl_nvm_access_cmd), length }, | ||
101 | .flags = CMD_SYNC, | ||
102 | .data = { &nvm_access_cmd, data }, | ||
103 | /* data may come from vmalloc, so use _DUP */ | ||
104 | .dataflags = { 0, IWL_HCMD_DFL_DUP }, | ||
105 | }; | ||
106 | |||
107 | return iwl_mvm_send_cmd(mvm, &cmd); | ||
86 | } | 108 | } |
87 | 109 | ||
88 | static int iwl_nvm_read_chunk(struct iwl_mvm *mvm, u16 section, | 110 | static int iwl_nvm_read_chunk(struct iwl_mvm *mvm, u16 section, |
89 | u16 offset, u16 length, u8 *data) | 111 | u16 offset, u16 length, u8 *data) |
90 | { | 112 | { |
91 | struct iwl_nvm_access_cmd nvm_access_cmd = {}; | 113 | struct iwl_nvm_access_cmd nvm_access_cmd = { |
114 | .offset = cpu_to_le16(offset), | ||
115 | .length = cpu_to_le16(length), | ||
116 | .type = cpu_to_le16(section), | ||
117 | .op_code = NVM_READ_OPCODE, | ||
118 | }; | ||
92 | struct iwl_nvm_access_resp *nvm_resp; | 119 | struct iwl_nvm_access_resp *nvm_resp; |
93 | struct iwl_rx_packet *pkt; | 120 | struct iwl_rx_packet *pkt; |
94 | struct iwl_host_cmd cmd = { | 121 | struct iwl_host_cmd cmd = { |
@@ -99,7 +126,6 @@ static int iwl_nvm_read_chunk(struct iwl_mvm *mvm, u16 section, | |||
99 | int ret, bytes_read, offset_read; | 126 | int ret, bytes_read, offset_read; |
100 | u8 *resp_data; | 127 | u8 *resp_data; |
101 | 128 | ||
102 | iwl_nvm_fill_read(&nvm_access_cmd, offset, length, section); | ||
103 | cmd.len[0] = sizeof(struct iwl_nvm_access_cmd); | 129 | cmd.len[0] = sizeof(struct iwl_nvm_access_cmd); |
104 | 130 | ||
105 | ret = iwl_mvm_send_cmd(mvm, &cmd); | 131 | ret = iwl_mvm_send_cmd(mvm, &cmd); |
@@ -144,6 +170,30 @@ exit: | |||
144 | return ret; | 170 | return ret; |
145 | } | 171 | } |
146 | 172 | ||
173 | static int iwl_nvm_write_section(struct iwl_mvm *mvm, u16 section, | ||
174 | const u8 *data, u16 length) | ||
175 | { | ||
176 | int offset = 0; | ||
177 | |||
178 | /* copy data in chunks of 2k (and remainder if any) */ | ||
179 | |||
180 | while (offset < length) { | ||
181 | int chunk_size, ret; | ||
182 | |||
183 | chunk_size = min(IWL_NVM_DEFAULT_CHUNK_SIZE, | ||
184 | length - offset); | ||
185 | |||
186 | ret = iwl_nvm_write_chunk(mvm, section, offset, | ||
187 | chunk_size, data + offset); | ||
188 | if (ret < 0) | ||
189 | return ret; | ||
190 | |||
191 | offset += chunk_size; | ||
192 | } | ||
193 | |||
194 | return 0; | ||
195 | } | ||
196 | |||
147 | /* | 197 | /* |
148 | * Reads an NVM section completely. | 198 | * Reads an NVM section completely. |
149 | * NICs prior to 7000 family doesn't have a real NVM, but just read | 199 | * NICs prior to 7000 family doesn't have a real NVM, but just read |
@@ -204,11 +254,143 @@ iwl_parse_nvm_sections(struct iwl_mvm *mvm) | |||
204 | return iwl_parse_nvm_data(mvm->trans->dev, mvm->cfg, hw, sw, calib); | 254 | return iwl_parse_nvm_data(mvm->trans->dev, mvm->cfg, hw, sw, calib); |
205 | } | 255 | } |
206 | 256 | ||
257 | #define MAX_NVM_FILE_LEN 16384 | ||
258 | |||
259 | /* | ||
260 | * HOW TO CREATE THE NVM FILE FORMAT: | ||
261 | * ------------------------------ | ||
262 | * 1. create hex file, format: | ||
263 | * 3800 -> header | ||
264 | * 0000 -> header | ||
265 | * 5a40 -> data | ||
266 | * | ||
267 | * rev - 6 bit (word1) | ||
268 | * len - 10 bit (word1) | ||
269 | * id - 4 bit (word2) | ||
270 | * rsv - 12 bit (word2) | ||
271 | * | ||
272 | * 2. flip 8bits with 8 bits per line to get the right NVM file format | ||
273 | * | ||
274 | * 3. create binary file from the hex file | ||
275 | * | ||
276 | * 4. save as "iNVM_xxx.bin" under /lib/firmware | ||
277 | */ | ||
278 | static int iwl_mvm_load_external_nvm(struct iwl_mvm *mvm) | ||
279 | { | ||
280 | int ret, section_id, section_size; | ||
281 | const struct firmware *fw_entry; | ||
282 | const struct { | ||
283 | __le16 word1; | ||
284 | __le16 word2; | ||
285 | u8 data[]; | ||
286 | } *file_sec; | ||
287 | const u8 *eof; | ||
288 | |||
289 | #define NVM_WORD1_LEN(x) (8 * (x & 0x03FF)) | ||
290 | #define NVM_WORD2_ID(x) (x >> 12) | ||
291 | |||
292 | /* | ||
293 | * Obtain NVM image via request_firmware. Since we already used | ||
294 | * request_firmware_nowait() for the firmware binary load and only | ||
295 | * get here after that we assume the NVM request can be satisfied | ||
296 | * synchronously. | ||
297 | */ | ||
298 | ret = request_firmware(&fw_entry, iwlwifi_mod_params.nvm_file, | ||
299 | mvm->trans->dev); | ||
300 | if (ret) { | ||
301 | IWL_ERR(mvm, "ERROR: %s isn't available %d\n", | ||
302 | iwlwifi_mod_params.nvm_file, ret); | ||
303 | return ret; | ||
304 | } | ||
305 | |||
306 | IWL_INFO(mvm, "Loaded NVM file %s (%zu bytes)\n", | ||
307 | iwlwifi_mod_params.nvm_file, fw_entry->size); | ||
308 | |||
309 | if (fw_entry->size < sizeof(*file_sec)) { | ||
310 | IWL_ERR(mvm, "NVM file too small\n"); | ||
311 | ret = -EINVAL; | ||
312 | goto out; | ||
313 | } | ||
314 | |||
315 | if (fw_entry->size > MAX_NVM_FILE_LEN) { | ||
316 | IWL_ERR(mvm, "NVM file too large\n"); | ||
317 | ret = -EINVAL; | ||
318 | goto out; | ||
319 | } | ||
320 | |||
321 | eof = fw_entry->data + fw_entry->size; | ||
322 | |||
323 | file_sec = (void *)fw_entry->data; | ||
324 | |||
325 | while (true) { | ||
326 | if (file_sec->data > eof) { | ||
327 | IWL_ERR(mvm, | ||
328 | "ERROR - NVM file too short for section header\n"); | ||
329 | ret = -EINVAL; | ||
330 | break; | ||
331 | } | ||
332 | |||
333 | /* check for EOF marker */ | ||
334 | if (!file_sec->word1 && !file_sec->word2) { | ||
335 | ret = 0; | ||
336 | break; | ||
337 | } | ||
338 | |||
339 | section_size = 2 * NVM_WORD1_LEN(le16_to_cpu(file_sec->word1)); | ||
340 | section_id = NVM_WORD2_ID(le16_to_cpu(file_sec->word2)); | ||
341 | |||
342 | if (section_size > IWL_MAX_NVM_SECTION_SIZE) { | ||
343 | IWL_ERR(mvm, "ERROR - section too large (%d)\n", | ||
344 | section_size); | ||
345 | ret = -EINVAL; | ||
346 | break; | ||
347 | } | ||
348 | |||
349 | if (!section_size) { | ||
350 | IWL_ERR(mvm, "ERROR - section empty\n"); | ||
351 | ret = -EINVAL; | ||
352 | break; | ||
353 | } | ||
354 | |||
355 | if (file_sec->data + section_size > eof) { | ||
356 | IWL_ERR(mvm, | ||
357 | "ERROR - NVM file too short for section (%d bytes)\n", | ||
358 | section_size); | ||
359 | ret = -EINVAL; | ||
360 | break; | ||
361 | } | ||
362 | |||
363 | ret = iwl_nvm_write_section(mvm, section_id, file_sec->data, | ||
364 | section_size); | ||
365 | if (ret < 0) { | ||
366 | IWL_ERR(mvm, "iwl_mvm_send_cmd failed: %d\n", ret); | ||
367 | break; | ||
368 | } | ||
369 | |||
370 | /* advance to the next section */ | ||
371 | file_sec = (void *)(file_sec->data + section_size); | ||
372 | } | ||
373 | out: | ||
374 | release_firmware(fw_entry); | ||
375 | return ret; | ||
376 | } | ||
377 | |||
207 | int iwl_nvm_init(struct iwl_mvm *mvm) | 378 | int iwl_nvm_init(struct iwl_mvm *mvm) |
208 | { | 379 | { |
209 | int ret, i, section; | 380 | int ret, i, section; |
210 | u8 *nvm_buffer, *temp; | 381 | u8 *nvm_buffer, *temp; |
211 | 382 | ||
383 | /* load external NVM if configured */ | ||
384 | if (iwlwifi_mod_params.nvm_file) { | ||
385 | /* move to External NVM flow */ | ||
386 | ret = iwl_mvm_load_external_nvm(mvm); | ||
387 | if (ret) | ||
388 | return ret; | ||
389 | } | ||
390 | |||
391 | /* Read From FW NVM */ | ||
392 | IWL_DEBUG_EEPROM(mvm->trans->dev, "Read from NVM\n"); | ||
393 | |||
212 | /* TODO: find correct NVM max size for a section */ | 394 | /* TODO: find correct NVM max size for a section */ |
213 | nvm_buffer = kmalloc(mvm->cfg->base_params->eeprom_size, | 395 | nvm_buffer = kmalloc(mvm->cfg->base_params->eeprom_size, |
214 | GFP_KERNEL); | 396 | GFP_KERNEL); |