18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * This file is part of wl1251 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2008 Nokia Corporation 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/slab.h> 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include "reg.h" 118c2ecf20Sopenharmony_ci#include "boot.h" 128c2ecf20Sopenharmony_ci#include "io.h" 138c2ecf20Sopenharmony_ci#include "spi.h" 148c2ecf20Sopenharmony_ci#include "event.h" 158c2ecf20Sopenharmony_ci#include "acx.h" 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_civoid wl1251_boot_target_enable_interrupts(struct wl1251 *wl) 188c2ecf20Sopenharmony_ci{ 198c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, ACX_REG_INTERRUPT_MASK, ~(wl->intr_mask)); 208c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, HI_CFG, HI_CFG_DEF_VAL); 218c2ecf20Sopenharmony_ci} 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ciint wl1251_boot_soft_reset(struct wl1251 *wl) 248c2ecf20Sopenharmony_ci{ 258c2ecf20Sopenharmony_ci unsigned long timeout; 268c2ecf20Sopenharmony_ci u32 boot_data; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci /* perform soft reset */ 298c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, ACX_REG_SLV_SOFT_RESET, ACX_SLV_SOFT_RESET_BIT); 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci /* SOFT_RESET is self clearing */ 328c2ecf20Sopenharmony_ci timeout = jiffies + usecs_to_jiffies(SOFT_RESET_MAX_TIME); 338c2ecf20Sopenharmony_ci while (1) { 348c2ecf20Sopenharmony_ci boot_data = wl1251_reg_read32(wl, ACX_REG_SLV_SOFT_RESET); 358c2ecf20Sopenharmony_ci wl1251_debug(DEBUG_BOOT, "soft reset bootdata 0x%x", boot_data); 368c2ecf20Sopenharmony_ci if ((boot_data & ACX_SLV_SOFT_RESET_BIT) == 0) 378c2ecf20Sopenharmony_ci break; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci if (time_after(jiffies, timeout)) { 408c2ecf20Sopenharmony_ci /* 1.2 check pWhalBus->uSelfClearTime if the 418c2ecf20Sopenharmony_ci * timeout was reached */ 428c2ecf20Sopenharmony_ci wl1251_error("soft reset timeout"); 438c2ecf20Sopenharmony_ci return -1; 448c2ecf20Sopenharmony_ci } 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci udelay(SOFT_RESET_STALL_TIME); 478c2ecf20Sopenharmony_ci } 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci /* disable Rx/Tx */ 508c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, ENABLE, 0x0); 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci /* disable auto calibration on start*/ 538c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, SPARE_A2, 0xffff); 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci return 0; 568c2ecf20Sopenharmony_ci} 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ciint wl1251_boot_init_seq(struct wl1251 *wl) 598c2ecf20Sopenharmony_ci{ 608c2ecf20Sopenharmony_ci u32 scr_pad6, init_data, tmp, elp_cmd, ref_freq; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci /* 638c2ecf20Sopenharmony_ci * col #1: INTEGER_DIVIDER 648c2ecf20Sopenharmony_ci * col #2: FRACTIONAL_DIVIDER 658c2ecf20Sopenharmony_ci * col #3: ATTN_BB 668c2ecf20Sopenharmony_ci * col #4: ALPHA_BB 678c2ecf20Sopenharmony_ci * col #5: STOP_TIME_BB 688c2ecf20Sopenharmony_ci * col #6: BB_PLL_LOOP_FILTER 698c2ecf20Sopenharmony_ci */ 708c2ecf20Sopenharmony_ci static const u32 LUT[REF_FREQ_NUM][LUT_PARAM_NUM] = { 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci { 83, 87381, 0xB, 5, 0xF00, 3}, /* REF_FREQ_19_2*/ 738c2ecf20Sopenharmony_ci { 61, 141154, 0xB, 5, 0x1450, 2}, /* REF_FREQ_26_0*/ 748c2ecf20Sopenharmony_ci { 41, 174763, 0xC, 6, 0x2D00, 1}, /* REF_FREQ_38_4*/ 758c2ecf20Sopenharmony_ci { 40, 0, 0xC, 6, 0x2EE0, 1}, /* REF_FREQ_40_0*/ 768c2ecf20Sopenharmony_ci { 47, 162280, 0xC, 6, 0x2760, 1} /* REF_FREQ_33_6 */ 778c2ecf20Sopenharmony_ci }; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci /* read NVS params */ 808c2ecf20Sopenharmony_ci scr_pad6 = wl1251_reg_read32(wl, SCR_PAD6); 818c2ecf20Sopenharmony_ci wl1251_debug(DEBUG_BOOT, "scr_pad6 0x%x", scr_pad6); 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci /* read ELP_CMD */ 848c2ecf20Sopenharmony_ci elp_cmd = wl1251_reg_read32(wl, ELP_CMD); 858c2ecf20Sopenharmony_ci wl1251_debug(DEBUG_BOOT, "elp_cmd 0x%x", elp_cmd); 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci /* set the BB calibration time to be 300 usec (PLL_CAL_TIME) */ 888c2ecf20Sopenharmony_ci ref_freq = scr_pad6 & 0x000000FF; 898c2ecf20Sopenharmony_ci wl1251_debug(DEBUG_BOOT, "ref_freq 0x%x", ref_freq); 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, PLL_CAL_TIME, 0x9); 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci /* 948c2ecf20Sopenharmony_ci * PG 1.2: set the clock buffer time to be 210 usec (CLK_BUF_TIME) 958c2ecf20Sopenharmony_ci */ 968c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, CLK_BUF_TIME, 0x6); 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci /* 998c2ecf20Sopenharmony_ci * set the clock detect feature to work in the restart wu procedure 1008c2ecf20Sopenharmony_ci * (ELP_CFG_MODE[14]) and Select the clock source type 1018c2ecf20Sopenharmony_ci * (ELP_CFG_MODE[13:12]) 1028c2ecf20Sopenharmony_ci */ 1038c2ecf20Sopenharmony_ci tmp = ((scr_pad6 & 0x0000FF00) << 4) | 0x00004000; 1048c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, ELP_CFG_MODE, tmp); 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci /* PG 1.2: enable the BB PLL fix. Enable the PLL_LIMP_CLK_EN_CMD */ 1078c2ecf20Sopenharmony_ci elp_cmd |= 0x00000040; 1088c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, ELP_CMD, elp_cmd); 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci /* PG 1.2: Set the BB PLL stable time to be 1000usec 1118c2ecf20Sopenharmony_ci * (PLL_STABLE_TIME) */ 1128c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, CFG_PLL_SYNC_CNT, 0x20); 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci /* PG 1.2: read clock request time */ 1158c2ecf20Sopenharmony_ci init_data = wl1251_reg_read32(wl, CLK_REQ_TIME); 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci /* 1188c2ecf20Sopenharmony_ci * PG 1.2: set the clock request time to be ref_clk_settling_time - 1198c2ecf20Sopenharmony_ci * 1ms = 4ms 1208c2ecf20Sopenharmony_ci */ 1218c2ecf20Sopenharmony_ci if (init_data > 0x21) 1228c2ecf20Sopenharmony_ci tmp = init_data - 0x21; 1238c2ecf20Sopenharmony_ci else 1248c2ecf20Sopenharmony_ci tmp = 0; 1258c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, CLK_REQ_TIME, tmp); 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci /* set BB PLL configurations in RF AFE */ 1288c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, 0x003058cc, 0x4B5); 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci /* set RF_AFE_REG_5 */ 1318c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, 0x003058d4, 0x50); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci /* set RF_AFE_CTRL_REG_2 */ 1348c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, 0x00305948, 0x11c001); 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci /* 1378c2ecf20Sopenharmony_ci * change RF PLL and BB PLL divider for VCO clock and adjust VCO 1388c2ecf20Sopenharmony_ci * bais current(RF_AFE_REG_13) 1398c2ecf20Sopenharmony_ci */ 1408c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, 0x003058f4, 0x1e); 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci /* set BB PLL configurations */ 1438c2ecf20Sopenharmony_ci tmp = LUT[ref_freq][LUT_PARAM_INTEGER_DIVIDER] | 0x00017000; 1448c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, 0x00305840, tmp); 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci /* set fractional divider according to Appendix C-BB PLL 1478c2ecf20Sopenharmony_ci * Calculations 1488c2ecf20Sopenharmony_ci */ 1498c2ecf20Sopenharmony_ci tmp = LUT[ref_freq][LUT_PARAM_FRACTIONAL_DIVIDER]; 1508c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, 0x00305844, tmp); 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci /* set the initial data for the sigma delta */ 1538c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, 0x00305848, 0x3039); 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci /* 1568c2ecf20Sopenharmony_ci * set the accumulator attenuation value, calibration loop1 1578c2ecf20Sopenharmony_ci * (alpha), calibration loop2 (beta), calibration loop3 (gamma) and 1588c2ecf20Sopenharmony_ci * the VCO gain 1598c2ecf20Sopenharmony_ci */ 1608c2ecf20Sopenharmony_ci tmp = (LUT[ref_freq][LUT_PARAM_ATTN_BB] << 16) | 1618c2ecf20Sopenharmony_ci (LUT[ref_freq][LUT_PARAM_ALPHA_BB] << 12) | 0x1; 1628c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, 0x00305854, tmp); 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci /* 1658c2ecf20Sopenharmony_ci * set the calibration stop time after holdoff time expires and set 1668c2ecf20Sopenharmony_ci * settling time HOLD_OFF_TIME_BB 1678c2ecf20Sopenharmony_ci */ 1688c2ecf20Sopenharmony_ci tmp = LUT[ref_freq][LUT_PARAM_STOP_TIME_BB] | 0x000A0000; 1698c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, 0x00305858, tmp); 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci /* 1728c2ecf20Sopenharmony_ci * set BB PLL Loop filter capacitor3- BB_C3[2:0] and set BB PLL 1738c2ecf20Sopenharmony_ci * constant leakage current to linearize PFD to 0uA - 1748c2ecf20Sopenharmony_ci * BB_ILOOPF[7:3] 1758c2ecf20Sopenharmony_ci */ 1768c2ecf20Sopenharmony_ci tmp = LUT[ref_freq][LUT_PARAM_BB_PLL_LOOP_FILTER] | 0x00000030; 1778c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, 0x003058f8, tmp); 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci /* 1808c2ecf20Sopenharmony_ci * set regulator output voltage for n divider to 1818c2ecf20Sopenharmony_ci * 1.35-BB_REFDIV[1:0], set charge pump current- BB_CPGAIN[4:2], 1828c2ecf20Sopenharmony_ci * set BB PLL Loop filter capacitor2- BB_C2[7:5], set gain of BB 1838c2ecf20Sopenharmony_ci * PLL auto-call to normal mode- BB_CALGAIN_3DB[8] 1848c2ecf20Sopenharmony_ci */ 1858c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, 0x003058f0, 0x29); 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci /* enable restart wakeup sequence (ELP_CMD[0]) */ 1888c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, ELP_CMD, elp_cmd | 0x1); 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci /* restart sequence completed */ 1918c2ecf20Sopenharmony_ci udelay(2000); 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci return 0; 1948c2ecf20Sopenharmony_ci} 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_cistatic void wl1251_boot_set_ecpu_ctrl(struct wl1251 *wl, u32 flag) 1978c2ecf20Sopenharmony_ci{ 1988c2ecf20Sopenharmony_ci u32 cpu_ctrl; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci /* 10.5.0 run the firmware (I) */ 2018c2ecf20Sopenharmony_ci cpu_ctrl = wl1251_reg_read32(wl, ACX_REG_ECPU_CONTROL); 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci /* 10.5.1 run the firmware (II) */ 2048c2ecf20Sopenharmony_ci cpu_ctrl &= ~flag; 2058c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, ACX_REG_ECPU_CONTROL, cpu_ctrl); 2068c2ecf20Sopenharmony_ci} 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ciint wl1251_boot_run_firmware(struct wl1251 *wl) 2098c2ecf20Sopenharmony_ci{ 2108c2ecf20Sopenharmony_ci int loop, ret; 2118c2ecf20Sopenharmony_ci u32 chip_id, acx_intr; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci wl1251_boot_set_ecpu_ctrl(wl, ECPU_CONTROL_HALT); 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci chip_id = wl1251_reg_read32(wl, CHIP_ID_B); 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci wl1251_debug(DEBUG_BOOT, "chip id after firmware boot: 0x%x", chip_id); 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci if (chip_id != wl->chip_id) { 2208c2ecf20Sopenharmony_ci wl1251_error("chip id doesn't match after firmware boot"); 2218c2ecf20Sopenharmony_ci return -EIO; 2228c2ecf20Sopenharmony_ci } 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci /* wait for init to complete */ 2258c2ecf20Sopenharmony_ci loop = 0; 2268c2ecf20Sopenharmony_ci while (loop++ < INIT_LOOP) { 2278c2ecf20Sopenharmony_ci udelay(INIT_LOOP_DELAY); 2288c2ecf20Sopenharmony_ci acx_intr = wl1251_reg_read32(wl, ACX_REG_INTERRUPT_NO_CLEAR); 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci if (acx_intr == 0xffffffff) { 2318c2ecf20Sopenharmony_ci wl1251_error("error reading hardware complete " 2328c2ecf20Sopenharmony_ci "init indication"); 2338c2ecf20Sopenharmony_ci return -EIO; 2348c2ecf20Sopenharmony_ci } 2358c2ecf20Sopenharmony_ci /* check that ACX_INTR_INIT_COMPLETE is enabled */ 2368c2ecf20Sopenharmony_ci else if (acx_intr & WL1251_ACX_INTR_INIT_COMPLETE) { 2378c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, ACX_REG_INTERRUPT_ACK, 2388c2ecf20Sopenharmony_ci WL1251_ACX_INTR_INIT_COMPLETE); 2398c2ecf20Sopenharmony_ci break; 2408c2ecf20Sopenharmony_ci } 2418c2ecf20Sopenharmony_ci } 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci if (loop > INIT_LOOP) { 2448c2ecf20Sopenharmony_ci wl1251_error("timeout waiting for the hardware to " 2458c2ecf20Sopenharmony_ci "complete initialization"); 2468c2ecf20Sopenharmony_ci return -EIO; 2478c2ecf20Sopenharmony_ci } 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci /* get hardware config command mail box */ 2508c2ecf20Sopenharmony_ci wl->cmd_box_addr = wl1251_reg_read32(wl, REG_COMMAND_MAILBOX_PTR); 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci /* get hardware config event mail box */ 2538c2ecf20Sopenharmony_ci wl->event_box_addr = wl1251_reg_read32(wl, REG_EVENT_MAILBOX_PTR); 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci /* set the working partition to its "running" mode offset */ 2568c2ecf20Sopenharmony_ci wl1251_set_partition(wl, WL1251_PART_WORK_MEM_START, 2578c2ecf20Sopenharmony_ci WL1251_PART_WORK_MEM_SIZE, 2588c2ecf20Sopenharmony_ci WL1251_PART_WORK_REG_START, 2598c2ecf20Sopenharmony_ci WL1251_PART_WORK_REG_SIZE); 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci wl1251_debug(DEBUG_MAILBOX, "cmd_box_addr 0x%x event_box_addr 0x%x", 2628c2ecf20Sopenharmony_ci wl->cmd_box_addr, wl->event_box_addr); 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci wl1251_acx_fw_version(wl, wl->fw_ver, sizeof(wl->fw_ver)); 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci /* 2678c2ecf20Sopenharmony_ci * in case of full asynchronous mode the firmware event must be 2688c2ecf20Sopenharmony_ci * ready to receive event from the command mailbox 2698c2ecf20Sopenharmony_ci */ 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci /* enable gpio interrupts */ 2728c2ecf20Sopenharmony_ci wl1251_enable_interrupts(wl); 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci /* Enable target's interrupts */ 2758c2ecf20Sopenharmony_ci wl->intr_mask = WL1251_ACX_INTR_RX0_DATA | 2768c2ecf20Sopenharmony_ci WL1251_ACX_INTR_RX1_DATA | 2778c2ecf20Sopenharmony_ci WL1251_ACX_INTR_TX_RESULT | 2788c2ecf20Sopenharmony_ci WL1251_ACX_INTR_EVENT_A | 2798c2ecf20Sopenharmony_ci WL1251_ACX_INTR_EVENT_B | 2808c2ecf20Sopenharmony_ci WL1251_ACX_INTR_INIT_COMPLETE; 2818c2ecf20Sopenharmony_ci wl1251_boot_target_enable_interrupts(wl); 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci wl->event_mask = SCAN_COMPLETE_EVENT_ID | BSS_LOSE_EVENT_ID | 2848c2ecf20Sopenharmony_ci SYNCHRONIZATION_TIMEOUT_EVENT_ID | 2858c2ecf20Sopenharmony_ci ROAMING_TRIGGER_LOW_RSSI_EVENT_ID | 2868c2ecf20Sopenharmony_ci ROAMING_TRIGGER_REGAINED_RSSI_EVENT_ID | 2878c2ecf20Sopenharmony_ci REGAINED_BSS_EVENT_ID | BT_PTA_SENSE_EVENT_ID | 2888c2ecf20Sopenharmony_ci BT_PTA_PREDICTION_EVENT_ID | JOIN_EVENT_COMPLETE_ID | 2898c2ecf20Sopenharmony_ci PS_REPORT_EVENT_ID; 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_ci ret = wl1251_event_unmask(wl); 2928c2ecf20Sopenharmony_ci if (ret < 0) { 2938c2ecf20Sopenharmony_ci wl1251_error("EVENT mask setting failed"); 2948c2ecf20Sopenharmony_ci return ret; 2958c2ecf20Sopenharmony_ci } 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci wl1251_event_mbox_config(wl); 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_ci /* firmware startup completed */ 3008c2ecf20Sopenharmony_ci return 0; 3018c2ecf20Sopenharmony_ci} 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_cistatic int wl1251_boot_upload_firmware(struct wl1251 *wl) 3048c2ecf20Sopenharmony_ci{ 3058c2ecf20Sopenharmony_ci int addr, chunk_num, partition_limit; 3068c2ecf20Sopenharmony_ci size_t fw_data_len, len; 3078c2ecf20Sopenharmony_ci u8 *p, *buf; 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci /* whal_FwCtrl_LoadFwImageSm() */ 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci wl1251_debug(DEBUG_BOOT, "chip id before fw upload: 0x%x", 3128c2ecf20Sopenharmony_ci wl1251_reg_read32(wl, CHIP_ID_B)); 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_ci /* 10.0 check firmware length and set partition */ 3158c2ecf20Sopenharmony_ci fw_data_len = (wl->fw[4] << 24) | (wl->fw[5] << 16) | 3168c2ecf20Sopenharmony_ci (wl->fw[6] << 8) | (wl->fw[7]); 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci wl1251_debug(DEBUG_BOOT, "fw_data_len %zu chunk_size %d", fw_data_len, 3198c2ecf20Sopenharmony_ci CHUNK_SIZE); 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_ci if ((fw_data_len % 4) != 0) { 3228c2ecf20Sopenharmony_ci wl1251_error("firmware length not multiple of four"); 3238c2ecf20Sopenharmony_ci return -EIO; 3248c2ecf20Sopenharmony_ci } 3258c2ecf20Sopenharmony_ci 3268c2ecf20Sopenharmony_ci buf = kmalloc(CHUNK_SIZE, GFP_KERNEL); 3278c2ecf20Sopenharmony_ci if (!buf) { 3288c2ecf20Sopenharmony_ci wl1251_error("allocation for firmware upload chunk failed"); 3298c2ecf20Sopenharmony_ci return -ENOMEM; 3308c2ecf20Sopenharmony_ci } 3318c2ecf20Sopenharmony_ci 3328c2ecf20Sopenharmony_ci wl1251_set_partition(wl, WL1251_PART_DOWN_MEM_START, 3338c2ecf20Sopenharmony_ci WL1251_PART_DOWN_MEM_SIZE, 3348c2ecf20Sopenharmony_ci WL1251_PART_DOWN_REG_START, 3358c2ecf20Sopenharmony_ci WL1251_PART_DOWN_REG_SIZE); 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci /* 10.1 set partition limit and chunk num */ 3388c2ecf20Sopenharmony_ci chunk_num = 0; 3398c2ecf20Sopenharmony_ci partition_limit = WL1251_PART_DOWN_MEM_SIZE; 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_ci while (chunk_num < fw_data_len / CHUNK_SIZE) { 3428c2ecf20Sopenharmony_ci /* 10.2 update partition, if needed */ 3438c2ecf20Sopenharmony_ci addr = WL1251_PART_DOWN_MEM_START + 3448c2ecf20Sopenharmony_ci (chunk_num + 2) * CHUNK_SIZE; 3458c2ecf20Sopenharmony_ci if (addr > partition_limit) { 3468c2ecf20Sopenharmony_ci addr = WL1251_PART_DOWN_MEM_START + 3478c2ecf20Sopenharmony_ci chunk_num * CHUNK_SIZE; 3488c2ecf20Sopenharmony_ci partition_limit = chunk_num * CHUNK_SIZE + 3498c2ecf20Sopenharmony_ci WL1251_PART_DOWN_MEM_SIZE; 3508c2ecf20Sopenharmony_ci wl1251_set_partition(wl, 3518c2ecf20Sopenharmony_ci addr, 3528c2ecf20Sopenharmony_ci WL1251_PART_DOWN_MEM_SIZE, 3538c2ecf20Sopenharmony_ci WL1251_PART_DOWN_REG_START, 3548c2ecf20Sopenharmony_ci WL1251_PART_DOWN_REG_SIZE); 3558c2ecf20Sopenharmony_ci } 3568c2ecf20Sopenharmony_ci 3578c2ecf20Sopenharmony_ci /* 10.3 upload the chunk */ 3588c2ecf20Sopenharmony_ci addr = WL1251_PART_DOWN_MEM_START + chunk_num * CHUNK_SIZE; 3598c2ecf20Sopenharmony_ci p = wl->fw + FW_HDR_SIZE + chunk_num * CHUNK_SIZE; 3608c2ecf20Sopenharmony_ci wl1251_debug(DEBUG_BOOT, "uploading fw chunk 0x%p to 0x%x", 3618c2ecf20Sopenharmony_ci p, addr); 3628c2ecf20Sopenharmony_ci 3638c2ecf20Sopenharmony_ci /* need to copy the chunk for dma */ 3648c2ecf20Sopenharmony_ci len = CHUNK_SIZE; 3658c2ecf20Sopenharmony_ci memcpy(buf, p, len); 3668c2ecf20Sopenharmony_ci wl1251_mem_write(wl, addr, buf, len); 3678c2ecf20Sopenharmony_ci 3688c2ecf20Sopenharmony_ci chunk_num++; 3698c2ecf20Sopenharmony_ci } 3708c2ecf20Sopenharmony_ci 3718c2ecf20Sopenharmony_ci /* 10.4 upload the last chunk */ 3728c2ecf20Sopenharmony_ci addr = WL1251_PART_DOWN_MEM_START + chunk_num * CHUNK_SIZE; 3738c2ecf20Sopenharmony_ci p = wl->fw + FW_HDR_SIZE + chunk_num * CHUNK_SIZE; 3748c2ecf20Sopenharmony_ci 3758c2ecf20Sopenharmony_ci /* need to copy the chunk for dma */ 3768c2ecf20Sopenharmony_ci len = fw_data_len % CHUNK_SIZE; 3778c2ecf20Sopenharmony_ci memcpy(buf, p, len); 3788c2ecf20Sopenharmony_ci 3798c2ecf20Sopenharmony_ci wl1251_debug(DEBUG_BOOT, "uploading fw last chunk (%zu B) 0x%p to 0x%x", 3808c2ecf20Sopenharmony_ci len, p, addr); 3818c2ecf20Sopenharmony_ci wl1251_mem_write(wl, addr, buf, len); 3828c2ecf20Sopenharmony_ci 3838c2ecf20Sopenharmony_ci kfree(buf); 3848c2ecf20Sopenharmony_ci 3858c2ecf20Sopenharmony_ci return 0; 3868c2ecf20Sopenharmony_ci} 3878c2ecf20Sopenharmony_ci 3888c2ecf20Sopenharmony_cistatic int wl1251_boot_upload_nvs(struct wl1251 *wl) 3898c2ecf20Sopenharmony_ci{ 3908c2ecf20Sopenharmony_ci size_t nvs_len, nvs_bytes_written, burst_len; 3918c2ecf20Sopenharmony_ci int nvs_start, i; 3928c2ecf20Sopenharmony_ci u32 dest_addr, val; 3938c2ecf20Sopenharmony_ci u8 *nvs_ptr, *nvs; 3948c2ecf20Sopenharmony_ci 3958c2ecf20Sopenharmony_ci nvs = wl->nvs; 3968c2ecf20Sopenharmony_ci if (nvs == NULL) 3978c2ecf20Sopenharmony_ci return -ENODEV; 3988c2ecf20Sopenharmony_ci 3998c2ecf20Sopenharmony_ci nvs_ptr = nvs; 4008c2ecf20Sopenharmony_ci 4018c2ecf20Sopenharmony_ci nvs_len = wl->nvs_len; 4028c2ecf20Sopenharmony_ci nvs_start = wl->fw_len; 4038c2ecf20Sopenharmony_ci 4048c2ecf20Sopenharmony_ci /* 4058c2ecf20Sopenharmony_ci * Layout before the actual NVS tables: 4068c2ecf20Sopenharmony_ci * 1 byte : burst length. 4078c2ecf20Sopenharmony_ci * 2 bytes: destination address. 4088c2ecf20Sopenharmony_ci * n bytes: data to burst copy. 4098c2ecf20Sopenharmony_ci * 4108c2ecf20Sopenharmony_ci * This is ended by a 0 length, then the NVS tables. 4118c2ecf20Sopenharmony_ci */ 4128c2ecf20Sopenharmony_ci 4138c2ecf20Sopenharmony_ci while (nvs_ptr[0]) { 4148c2ecf20Sopenharmony_ci burst_len = nvs_ptr[0]; 4158c2ecf20Sopenharmony_ci dest_addr = (nvs_ptr[1] & 0xfe) | ((u32)(nvs_ptr[2] << 8)); 4168c2ecf20Sopenharmony_ci 4178c2ecf20Sopenharmony_ci /* We move our pointer to the data */ 4188c2ecf20Sopenharmony_ci nvs_ptr += 3; 4198c2ecf20Sopenharmony_ci 4208c2ecf20Sopenharmony_ci for (i = 0; i < burst_len; i++) { 4218c2ecf20Sopenharmony_ci val = (nvs_ptr[0] | (nvs_ptr[1] << 8) 4228c2ecf20Sopenharmony_ci | (nvs_ptr[2] << 16) | (nvs_ptr[3] << 24)); 4238c2ecf20Sopenharmony_ci 4248c2ecf20Sopenharmony_ci wl1251_debug(DEBUG_BOOT, 4258c2ecf20Sopenharmony_ci "nvs burst write 0x%x: 0x%x", 4268c2ecf20Sopenharmony_ci dest_addr, val); 4278c2ecf20Sopenharmony_ci wl1251_mem_write32(wl, dest_addr, val); 4288c2ecf20Sopenharmony_ci 4298c2ecf20Sopenharmony_ci nvs_ptr += 4; 4308c2ecf20Sopenharmony_ci dest_addr += 4; 4318c2ecf20Sopenharmony_ci } 4328c2ecf20Sopenharmony_ci } 4338c2ecf20Sopenharmony_ci 4348c2ecf20Sopenharmony_ci /* 4358c2ecf20Sopenharmony_ci * We've reached the first zero length, the first NVS table 4368c2ecf20Sopenharmony_ci * is 7 bytes further. 4378c2ecf20Sopenharmony_ci */ 4388c2ecf20Sopenharmony_ci nvs_ptr += 7; 4398c2ecf20Sopenharmony_ci nvs_len -= nvs_ptr - nvs; 4408c2ecf20Sopenharmony_ci nvs_len = ALIGN(nvs_len, 4); 4418c2ecf20Sopenharmony_ci 4428c2ecf20Sopenharmony_ci /* Now we must set the partition correctly */ 4438c2ecf20Sopenharmony_ci wl1251_set_partition(wl, nvs_start, 4448c2ecf20Sopenharmony_ci WL1251_PART_DOWN_MEM_SIZE, 4458c2ecf20Sopenharmony_ci WL1251_PART_DOWN_REG_START, 4468c2ecf20Sopenharmony_ci WL1251_PART_DOWN_REG_SIZE); 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_ci /* And finally we upload the NVS tables */ 4498c2ecf20Sopenharmony_ci nvs_bytes_written = 0; 4508c2ecf20Sopenharmony_ci while (nvs_bytes_written < nvs_len) { 4518c2ecf20Sopenharmony_ci val = (nvs_ptr[0] | (nvs_ptr[1] << 8) 4528c2ecf20Sopenharmony_ci | (nvs_ptr[2] << 16) | (nvs_ptr[3] << 24)); 4538c2ecf20Sopenharmony_ci 4548c2ecf20Sopenharmony_ci wl1251_debug(DEBUG_BOOT, 4558c2ecf20Sopenharmony_ci "nvs write table 0x%x: 0x%x", 4568c2ecf20Sopenharmony_ci nvs_start, val); 4578c2ecf20Sopenharmony_ci wl1251_mem_write32(wl, nvs_start, val); 4588c2ecf20Sopenharmony_ci 4598c2ecf20Sopenharmony_ci nvs_ptr += 4; 4608c2ecf20Sopenharmony_ci nvs_bytes_written += 4; 4618c2ecf20Sopenharmony_ci nvs_start += 4; 4628c2ecf20Sopenharmony_ci } 4638c2ecf20Sopenharmony_ci 4648c2ecf20Sopenharmony_ci return 0; 4658c2ecf20Sopenharmony_ci} 4668c2ecf20Sopenharmony_ci 4678c2ecf20Sopenharmony_ciint wl1251_boot(struct wl1251 *wl) 4688c2ecf20Sopenharmony_ci{ 4698c2ecf20Sopenharmony_ci int ret = 0, minor_minor_e2_ver; 4708c2ecf20Sopenharmony_ci u32 tmp, boot_data; 4718c2ecf20Sopenharmony_ci 4728c2ecf20Sopenharmony_ci /* halt embedded ARM CPU while loading firmware */ 4738c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, ACX_REG_ECPU_CONTROL, ECPU_CONTROL_HALT); 4748c2ecf20Sopenharmony_ci 4758c2ecf20Sopenharmony_ci ret = wl1251_boot_soft_reset(wl); 4768c2ecf20Sopenharmony_ci if (ret < 0) 4778c2ecf20Sopenharmony_ci goto out; 4788c2ecf20Sopenharmony_ci 4798c2ecf20Sopenharmony_ci /* 2. start processing NVS file */ 4808c2ecf20Sopenharmony_ci if (wl->use_eeprom) { 4818c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, ACX_REG_EE_START, START_EEPROM_MGR); 4828c2ecf20Sopenharmony_ci /* Wait for EEPROM NVS burst read to complete */ 4838c2ecf20Sopenharmony_ci msleep(40); 4848c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, ACX_EEPROMLESS_IND_REG, USE_EEPROM); 4858c2ecf20Sopenharmony_ci } else { 4868c2ecf20Sopenharmony_ci ret = wl1251_boot_upload_nvs(wl); 4878c2ecf20Sopenharmony_ci if (ret < 0) 4888c2ecf20Sopenharmony_ci goto out; 4898c2ecf20Sopenharmony_ci 4908c2ecf20Sopenharmony_ci /* write firmware's last address (ie. it's length) to 4918c2ecf20Sopenharmony_ci * ACX_EEPROMLESS_IND_REG */ 4928c2ecf20Sopenharmony_ci wl1251_reg_write32(wl, ACX_EEPROMLESS_IND_REG, wl->fw_len); 4938c2ecf20Sopenharmony_ci } 4948c2ecf20Sopenharmony_ci 4958c2ecf20Sopenharmony_ci /* 6. read the EEPROM parameters */ 4968c2ecf20Sopenharmony_ci tmp = wl1251_reg_read32(wl, SCR_PAD2); 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci /* 7. read bootdata */ 4998c2ecf20Sopenharmony_ci wl->boot_attr.radio_type = (tmp & 0x0000FF00) >> 8; 5008c2ecf20Sopenharmony_ci wl->boot_attr.major = (tmp & 0x00FF0000) >> 16; 5018c2ecf20Sopenharmony_ci tmp = wl1251_reg_read32(wl, SCR_PAD3); 5028c2ecf20Sopenharmony_ci 5038c2ecf20Sopenharmony_ci /* 8. check bootdata and call restart sequence */ 5048c2ecf20Sopenharmony_ci wl->boot_attr.minor = (tmp & 0x00FF0000) >> 16; 5058c2ecf20Sopenharmony_ci minor_minor_e2_ver = (tmp & 0xFF000000) >> 24; 5068c2ecf20Sopenharmony_ci 5078c2ecf20Sopenharmony_ci wl1251_debug(DEBUG_BOOT, "radioType 0x%x majorE2Ver 0x%x " 5088c2ecf20Sopenharmony_ci "minorE2Ver 0x%x minor_minor_e2_ver 0x%x", 5098c2ecf20Sopenharmony_ci wl->boot_attr.radio_type, wl->boot_attr.major, 5108c2ecf20Sopenharmony_ci wl->boot_attr.minor, minor_minor_e2_ver); 5118c2ecf20Sopenharmony_ci 5128c2ecf20Sopenharmony_ci ret = wl1251_boot_init_seq(wl); 5138c2ecf20Sopenharmony_ci if (ret < 0) 5148c2ecf20Sopenharmony_ci goto out; 5158c2ecf20Sopenharmony_ci 5168c2ecf20Sopenharmony_ci /* 9. NVS processing done */ 5178c2ecf20Sopenharmony_ci boot_data = wl1251_reg_read32(wl, ACX_REG_ECPU_CONTROL); 5188c2ecf20Sopenharmony_ci 5198c2ecf20Sopenharmony_ci wl1251_debug(DEBUG_BOOT, "halt boot_data 0x%x", boot_data); 5208c2ecf20Sopenharmony_ci 5218c2ecf20Sopenharmony_ci /* 10. check that ECPU_CONTROL_HALT bits are set in 5228c2ecf20Sopenharmony_ci * pWhalBus->uBootData and start uploading firmware 5238c2ecf20Sopenharmony_ci */ 5248c2ecf20Sopenharmony_ci if ((boot_data & ECPU_CONTROL_HALT) == 0) { 5258c2ecf20Sopenharmony_ci wl1251_error("boot failed, ECPU_CONTROL_HALT not set"); 5268c2ecf20Sopenharmony_ci ret = -EIO; 5278c2ecf20Sopenharmony_ci goto out; 5288c2ecf20Sopenharmony_ci } 5298c2ecf20Sopenharmony_ci 5308c2ecf20Sopenharmony_ci ret = wl1251_boot_upload_firmware(wl); 5318c2ecf20Sopenharmony_ci if (ret < 0) 5328c2ecf20Sopenharmony_ci goto out; 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_ci /* 10.5 start firmware */ 5358c2ecf20Sopenharmony_ci ret = wl1251_boot_run_firmware(wl); 5368c2ecf20Sopenharmony_ci if (ret < 0) 5378c2ecf20Sopenharmony_ci goto out; 5388c2ecf20Sopenharmony_ci 5398c2ecf20Sopenharmony_ciout: 5408c2ecf20Sopenharmony_ci return ret; 5418c2ecf20Sopenharmony_ci} 542