162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * This file is part of wl1251
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2008 Nokia Corporation
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/slab.h>
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include "reg.h"
1162306a36Sopenharmony_ci#include "boot.h"
1262306a36Sopenharmony_ci#include "io.h"
1362306a36Sopenharmony_ci#include "spi.h"
1462306a36Sopenharmony_ci#include "event.h"
1562306a36Sopenharmony_ci#include "acx.h"
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_civoid wl1251_boot_target_enable_interrupts(struct wl1251 *wl)
1862306a36Sopenharmony_ci{
1962306a36Sopenharmony_ci	wl1251_reg_write32(wl, ACX_REG_INTERRUPT_MASK, ~(wl->intr_mask));
2062306a36Sopenharmony_ci	wl1251_reg_write32(wl, HI_CFG, HI_CFG_DEF_VAL);
2162306a36Sopenharmony_ci}
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ciint wl1251_boot_soft_reset(struct wl1251 *wl)
2462306a36Sopenharmony_ci{
2562306a36Sopenharmony_ci	unsigned long timeout;
2662306a36Sopenharmony_ci	u32 boot_data;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	/* perform soft reset */
2962306a36Sopenharmony_ci	wl1251_reg_write32(wl, ACX_REG_SLV_SOFT_RESET, ACX_SLV_SOFT_RESET_BIT);
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci	/* SOFT_RESET is self clearing */
3262306a36Sopenharmony_ci	timeout = jiffies + usecs_to_jiffies(SOFT_RESET_MAX_TIME);
3362306a36Sopenharmony_ci	while (1) {
3462306a36Sopenharmony_ci		boot_data = wl1251_reg_read32(wl, ACX_REG_SLV_SOFT_RESET);
3562306a36Sopenharmony_ci		wl1251_debug(DEBUG_BOOT, "soft reset bootdata 0x%x", boot_data);
3662306a36Sopenharmony_ci		if ((boot_data & ACX_SLV_SOFT_RESET_BIT) == 0)
3762306a36Sopenharmony_ci			break;
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci		if (time_after(jiffies, timeout)) {
4062306a36Sopenharmony_ci			/* 1.2 check pWhalBus->uSelfClearTime if the
4162306a36Sopenharmony_ci			 * timeout was reached */
4262306a36Sopenharmony_ci			wl1251_error("soft reset timeout");
4362306a36Sopenharmony_ci			return -1;
4462306a36Sopenharmony_ci		}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci		udelay(SOFT_RESET_STALL_TIME);
4762306a36Sopenharmony_ci	}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	/* disable Rx/Tx */
5062306a36Sopenharmony_ci	wl1251_reg_write32(wl, ENABLE, 0x0);
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	/* disable auto calibration on start*/
5362306a36Sopenharmony_ci	wl1251_reg_write32(wl, SPARE_A2, 0xffff);
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	return 0;
5662306a36Sopenharmony_ci}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ciint wl1251_boot_init_seq(struct wl1251 *wl)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	u32 scr_pad6, init_data, tmp, elp_cmd, ref_freq;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	/*
6362306a36Sopenharmony_ci	 * col #1: INTEGER_DIVIDER
6462306a36Sopenharmony_ci	 * col #2: FRACTIONAL_DIVIDER
6562306a36Sopenharmony_ci	 * col #3: ATTN_BB
6662306a36Sopenharmony_ci	 * col #4: ALPHA_BB
6762306a36Sopenharmony_ci	 * col #5: STOP_TIME_BB
6862306a36Sopenharmony_ci	 * col #6: BB_PLL_LOOP_FILTER
6962306a36Sopenharmony_ci	 */
7062306a36Sopenharmony_ci	static const u32 LUT[REF_FREQ_NUM][LUT_PARAM_NUM] = {
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci		{   83, 87381,  0xB, 5, 0xF00,  3}, /* REF_FREQ_19_2*/
7362306a36Sopenharmony_ci		{   61, 141154, 0xB, 5, 0x1450, 2}, /* REF_FREQ_26_0*/
7462306a36Sopenharmony_ci		{   41, 174763, 0xC, 6, 0x2D00, 1}, /* REF_FREQ_38_4*/
7562306a36Sopenharmony_ci		{   40, 0,      0xC, 6, 0x2EE0, 1}, /* REF_FREQ_40_0*/
7662306a36Sopenharmony_ci		{   47, 162280, 0xC, 6, 0x2760, 1}  /* REF_FREQ_33_6        */
7762306a36Sopenharmony_ci	};
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	/* read NVS params */
8062306a36Sopenharmony_ci	scr_pad6 = wl1251_reg_read32(wl, SCR_PAD6);
8162306a36Sopenharmony_ci	wl1251_debug(DEBUG_BOOT, "scr_pad6 0x%x", scr_pad6);
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	/* read ELP_CMD */
8462306a36Sopenharmony_ci	elp_cmd = wl1251_reg_read32(wl, ELP_CMD);
8562306a36Sopenharmony_ci	wl1251_debug(DEBUG_BOOT, "elp_cmd 0x%x", elp_cmd);
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	/* set the BB calibration time to be 300 usec (PLL_CAL_TIME) */
8862306a36Sopenharmony_ci	ref_freq = scr_pad6 & 0x000000FF;
8962306a36Sopenharmony_ci	wl1251_debug(DEBUG_BOOT, "ref_freq 0x%x", ref_freq);
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	wl1251_reg_write32(wl, PLL_CAL_TIME, 0x9);
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	/*
9462306a36Sopenharmony_ci	 * PG 1.2: set the clock buffer time to be 210 usec (CLK_BUF_TIME)
9562306a36Sopenharmony_ci	 */
9662306a36Sopenharmony_ci	wl1251_reg_write32(wl, CLK_BUF_TIME, 0x6);
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	/*
9962306a36Sopenharmony_ci	 * set the clock detect feature to work in the restart wu procedure
10062306a36Sopenharmony_ci	 * (ELP_CFG_MODE[14]) and Select the clock source type
10162306a36Sopenharmony_ci	 * (ELP_CFG_MODE[13:12])
10262306a36Sopenharmony_ci	 */
10362306a36Sopenharmony_ci	tmp = ((scr_pad6 & 0x0000FF00) << 4) | 0x00004000;
10462306a36Sopenharmony_ci	wl1251_reg_write32(wl, ELP_CFG_MODE, tmp);
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	/* PG 1.2: enable the BB PLL fix. Enable the PLL_LIMP_CLK_EN_CMD */
10762306a36Sopenharmony_ci	elp_cmd |= 0x00000040;
10862306a36Sopenharmony_ci	wl1251_reg_write32(wl, ELP_CMD, elp_cmd);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	/* PG 1.2: Set the BB PLL stable time to be 1000usec
11162306a36Sopenharmony_ci	 * (PLL_STABLE_TIME) */
11262306a36Sopenharmony_ci	wl1251_reg_write32(wl, CFG_PLL_SYNC_CNT, 0x20);
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	/* PG 1.2: read clock request time */
11562306a36Sopenharmony_ci	init_data = wl1251_reg_read32(wl, CLK_REQ_TIME);
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	/*
11862306a36Sopenharmony_ci	 * PG 1.2: set the clock request time to be ref_clk_settling_time -
11962306a36Sopenharmony_ci	 * 1ms = 4ms
12062306a36Sopenharmony_ci	 */
12162306a36Sopenharmony_ci	if (init_data > 0x21)
12262306a36Sopenharmony_ci		tmp = init_data - 0x21;
12362306a36Sopenharmony_ci	else
12462306a36Sopenharmony_ci		tmp = 0;
12562306a36Sopenharmony_ci	wl1251_reg_write32(wl, CLK_REQ_TIME, tmp);
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	/* set BB PLL configurations in RF AFE */
12862306a36Sopenharmony_ci	wl1251_reg_write32(wl, 0x003058cc, 0x4B5);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	/* set RF_AFE_REG_5 */
13162306a36Sopenharmony_ci	wl1251_reg_write32(wl, 0x003058d4, 0x50);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	/* set RF_AFE_CTRL_REG_2 */
13462306a36Sopenharmony_ci	wl1251_reg_write32(wl, 0x00305948, 0x11c001);
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	/*
13762306a36Sopenharmony_ci	 * change RF PLL and BB PLL divider for VCO clock and adjust VCO
13862306a36Sopenharmony_ci	 * bais current(RF_AFE_REG_13)
13962306a36Sopenharmony_ci	 */
14062306a36Sopenharmony_ci	wl1251_reg_write32(wl, 0x003058f4, 0x1e);
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	/* set BB PLL configurations */
14362306a36Sopenharmony_ci	tmp = LUT[ref_freq][LUT_PARAM_INTEGER_DIVIDER] | 0x00017000;
14462306a36Sopenharmony_ci	wl1251_reg_write32(wl, 0x00305840, tmp);
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	/* set fractional divider according to Appendix C-BB PLL
14762306a36Sopenharmony_ci	 * Calculations
14862306a36Sopenharmony_ci	 */
14962306a36Sopenharmony_ci	tmp = LUT[ref_freq][LUT_PARAM_FRACTIONAL_DIVIDER];
15062306a36Sopenharmony_ci	wl1251_reg_write32(wl, 0x00305844, tmp);
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	/* set the initial data for the sigma delta */
15362306a36Sopenharmony_ci	wl1251_reg_write32(wl, 0x00305848, 0x3039);
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	/*
15662306a36Sopenharmony_ci	 * set the accumulator attenuation value, calibration loop1
15762306a36Sopenharmony_ci	 * (alpha), calibration loop2 (beta), calibration loop3 (gamma) and
15862306a36Sopenharmony_ci	 * the VCO gain
15962306a36Sopenharmony_ci	 */
16062306a36Sopenharmony_ci	tmp = (LUT[ref_freq][LUT_PARAM_ATTN_BB] << 16) |
16162306a36Sopenharmony_ci		(LUT[ref_freq][LUT_PARAM_ALPHA_BB] << 12) | 0x1;
16262306a36Sopenharmony_ci	wl1251_reg_write32(wl, 0x00305854, tmp);
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	/*
16562306a36Sopenharmony_ci	 * set the calibration stop time after holdoff time expires and set
16662306a36Sopenharmony_ci	 * settling time HOLD_OFF_TIME_BB
16762306a36Sopenharmony_ci	 */
16862306a36Sopenharmony_ci	tmp = LUT[ref_freq][LUT_PARAM_STOP_TIME_BB] | 0x000A0000;
16962306a36Sopenharmony_ci	wl1251_reg_write32(wl, 0x00305858, tmp);
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	/*
17262306a36Sopenharmony_ci	 * set BB PLL Loop filter capacitor3- BB_C3[2:0] and set BB PLL
17362306a36Sopenharmony_ci	 * constant leakage current to linearize PFD to 0uA -
17462306a36Sopenharmony_ci	 * BB_ILOOPF[7:3]
17562306a36Sopenharmony_ci	 */
17662306a36Sopenharmony_ci	tmp = LUT[ref_freq][LUT_PARAM_BB_PLL_LOOP_FILTER] | 0x00000030;
17762306a36Sopenharmony_ci	wl1251_reg_write32(wl, 0x003058f8, tmp);
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	/*
18062306a36Sopenharmony_ci	 * set regulator output voltage for n divider to
18162306a36Sopenharmony_ci	 * 1.35-BB_REFDIV[1:0], set charge pump current- BB_CPGAIN[4:2],
18262306a36Sopenharmony_ci	 * set BB PLL Loop filter capacitor2- BB_C2[7:5], set gain of BB
18362306a36Sopenharmony_ci	 * PLL auto-call to normal mode- BB_CALGAIN_3DB[8]
18462306a36Sopenharmony_ci	 */
18562306a36Sopenharmony_ci	wl1251_reg_write32(wl, 0x003058f0, 0x29);
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	/* enable restart wakeup sequence (ELP_CMD[0]) */
18862306a36Sopenharmony_ci	wl1251_reg_write32(wl, ELP_CMD, elp_cmd | 0x1);
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	/* restart sequence completed */
19162306a36Sopenharmony_ci	udelay(2000);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	return 0;
19462306a36Sopenharmony_ci}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_cistatic void wl1251_boot_set_ecpu_ctrl(struct wl1251 *wl, u32 flag)
19762306a36Sopenharmony_ci{
19862306a36Sopenharmony_ci	u32 cpu_ctrl;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	/* 10.5.0 run the firmware (I) */
20162306a36Sopenharmony_ci	cpu_ctrl = wl1251_reg_read32(wl, ACX_REG_ECPU_CONTROL);
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	/* 10.5.1 run the firmware (II) */
20462306a36Sopenharmony_ci	cpu_ctrl &= ~flag;
20562306a36Sopenharmony_ci	wl1251_reg_write32(wl, ACX_REG_ECPU_CONTROL, cpu_ctrl);
20662306a36Sopenharmony_ci}
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ciint wl1251_boot_run_firmware(struct wl1251 *wl)
20962306a36Sopenharmony_ci{
21062306a36Sopenharmony_ci	int loop, ret;
21162306a36Sopenharmony_ci	u32 chip_id, acx_intr;
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	wl1251_boot_set_ecpu_ctrl(wl, ECPU_CONTROL_HALT);
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	chip_id = wl1251_reg_read32(wl, CHIP_ID_B);
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	wl1251_debug(DEBUG_BOOT, "chip id after firmware boot: 0x%x", chip_id);
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	if (chip_id != wl->chip_id) {
22062306a36Sopenharmony_ci		wl1251_error("chip id doesn't match after firmware boot");
22162306a36Sopenharmony_ci		return -EIO;
22262306a36Sopenharmony_ci	}
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	/* wait for init to complete */
22562306a36Sopenharmony_ci	loop = 0;
22662306a36Sopenharmony_ci	while (loop++ < INIT_LOOP) {
22762306a36Sopenharmony_ci		udelay(INIT_LOOP_DELAY);
22862306a36Sopenharmony_ci		acx_intr = wl1251_reg_read32(wl, ACX_REG_INTERRUPT_NO_CLEAR);
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci		if (acx_intr == 0xffffffff) {
23162306a36Sopenharmony_ci			wl1251_error("error reading hardware complete "
23262306a36Sopenharmony_ci				     "init indication");
23362306a36Sopenharmony_ci			return -EIO;
23462306a36Sopenharmony_ci		}
23562306a36Sopenharmony_ci		/* check that ACX_INTR_INIT_COMPLETE is enabled */
23662306a36Sopenharmony_ci		else if (acx_intr & WL1251_ACX_INTR_INIT_COMPLETE) {
23762306a36Sopenharmony_ci			wl1251_reg_write32(wl, ACX_REG_INTERRUPT_ACK,
23862306a36Sopenharmony_ci					   WL1251_ACX_INTR_INIT_COMPLETE);
23962306a36Sopenharmony_ci			break;
24062306a36Sopenharmony_ci		}
24162306a36Sopenharmony_ci	}
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	if (loop > INIT_LOOP) {
24462306a36Sopenharmony_ci		wl1251_error("timeout waiting for the hardware to "
24562306a36Sopenharmony_ci			     "complete initialization");
24662306a36Sopenharmony_ci		return -EIO;
24762306a36Sopenharmony_ci	}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	/* get hardware config command mail box */
25062306a36Sopenharmony_ci	wl->cmd_box_addr = wl1251_reg_read32(wl, REG_COMMAND_MAILBOX_PTR);
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	/* get hardware config event mail box */
25362306a36Sopenharmony_ci	wl->event_box_addr = wl1251_reg_read32(wl, REG_EVENT_MAILBOX_PTR);
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	/* set the working partition to its "running" mode offset */
25662306a36Sopenharmony_ci	wl1251_set_partition(wl, WL1251_PART_WORK_MEM_START,
25762306a36Sopenharmony_ci			     WL1251_PART_WORK_MEM_SIZE,
25862306a36Sopenharmony_ci			     WL1251_PART_WORK_REG_START,
25962306a36Sopenharmony_ci			     WL1251_PART_WORK_REG_SIZE);
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	wl1251_debug(DEBUG_MAILBOX, "cmd_box_addr 0x%x event_box_addr 0x%x",
26262306a36Sopenharmony_ci		     wl->cmd_box_addr, wl->event_box_addr);
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	wl1251_acx_fw_version(wl, wl->fw_ver, sizeof(wl->fw_ver));
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	/*
26762306a36Sopenharmony_ci	 * in case of full asynchronous mode the firmware event must be
26862306a36Sopenharmony_ci	 * ready to receive event from the command mailbox
26962306a36Sopenharmony_ci	 */
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	/* enable gpio interrupts */
27262306a36Sopenharmony_ci	wl1251_enable_interrupts(wl);
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	/* Enable target's interrupts */
27562306a36Sopenharmony_ci	wl->intr_mask = WL1251_ACX_INTR_RX0_DATA |
27662306a36Sopenharmony_ci		WL1251_ACX_INTR_RX1_DATA |
27762306a36Sopenharmony_ci		WL1251_ACX_INTR_TX_RESULT |
27862306a36Sopenharmony_ci		WL1251_ACX_INTR_EVENT_A |
27962306a36Sopenharmony_ci		WL1251_ACX_INTR_EVENT_B |
28062306a36Sopenharmony_ci		WL1251_ACX_INTR_INIT_COMPLETE;
28162306a36Sopenharmony_ci	wl1251_boot_target_enable_interrupts(wl);
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	wl->event_mask = SCAN_COMPLETE_EVENT_ID | BSS_LOSE_EVENT_ID |
28462306a36Sopenharmony_ci		SYNCHRONIZATION_TIMEOUT_EVENT_ID |
28562306a36Sopenharmony_ci		ROAMING_TRIGGER_LOW_RSSI_EVENT_ID |
28662306a36Sopenharmony_ci		ROAMING_TRIGGER_REGAINED_RSSI_EVENT_ID |
28762306a36Sopenharmony_ci		REGAINED_BSS_EVENT_ID | BT_PTA_SENSE_EVENT_ID |
28862306a36Sopenharmony_ci		BT_PTA_PREDICTION_EVENT_ID | JOIN_EVENT_COMPLETE_ID |
28962306a36Sopenharmony_ci		PS_REPORT_EVENT_ID;
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	ret = wl1251_event_unmask(wl);
29262306a36Sopenharmony_ci	if (ret < 0) {
29362306a36Sopenharmony_ci		wl1251_error("EVENT mask setting failed");
29462306a36Sopenharmony_ci		return ret;
29562306a36Sopenharmony_ci	}
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	wl1251_event_mbox_config(wl);
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	/* firmware startup completed */
30062306a36Sopenharmony_ci	return 0;
30162306a36Sopenharmony_ci}
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_cistatic int wl1251_boot_upload_firmware(struct wl1251 *wl)
30462306a36Sopenharmony_ci{
30562306a36Sopenharmony_ci	int addr, chunk_num, partition_limit;
30662306a36Sopenharmony_ci	size_t fw_data_len, len;
30762306a36Sopenharmony_ci	u8 *p, *buf;
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	/* whal_FwCtrl_LoadFwImageSm() */
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	wl1251_debug(DEBUG_BOOT, "chip id before fw upload: 0x%x",
31262306a36Sopenharmony_ci		     wl1251_reg_read32(wl, CHIP_ID_B));
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	/* 10.0 check firmware length and set partition */
31562306a36Sopenharmony_ci	fw_data_len =  (wl->fw[4] << 24) | (wl->fw[5] << 16) |
31662306a36Sopenharmony_ci		(wl->fw[6] << 8) | (wl->fw[7]);
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	wl1251_debug(DEBUG_BOOT, "fw_data_len %zu chunk_size %d", fw_data_len,
31962306a36Sopenharmony_ci		CHUNK_SIZE);
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	if ((fw_data_len % 4) != 0) {
32262306a36Sopenharmony_ci		wl1251_error("firmware length not multiple of four");
32362306a36Sopenharmony_ci		return -EIO;
32462306a36Sopenharmony_ci	}
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	buf = kmalloc(CHUNK_SIZE, GFP_KERNEL);
32762306a36Sopenharmony_ci	if (!buf) {
32862306a36Sopenharmony_ci		wl1251_error("allocation for firmware upload chunk failed");
32962306a36Sopenharmony_ci		return -ENOMEM;
33062306a36Sopenharmony_ci	}
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci	wl1251_set_partition(wl, WL1251_PART_DOWN_MEM_START,
33362306a36Sopenharmony_ci			     WL1251_PART_DOWN_MEM_SIZE,
33462306a36Sopenharmony_ci			     WL1251_PART_DOWN_REG_START,
33562306a36Sopenharmony_ci			     WL1251_PART_DOWN_REG_SIZE);
33662306a36Sopenharmony_ci
33762306a36Sopenharmony_ci	/* 10.1 set partition limit and chunk num */
33862306a36Sopenharmony_ci	chunk_num = 0;
33962306a36Sopenharmony_ci	partition_limit = WL1251_PART_DOWN_MEM_SIZE;
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci	while (chunk_num < fw_data_len / CHUNK_SIZE) {
34262306a36Sopenharmony_ci		/* 10.2 update partition, if needed */
34362306a36Sopenharmony_ci		addr = WL1251_PART_DOWN_MEM_START +
34462306a36Sopenharmony_ci			(chunk_num + 2) * CHUNK_SIZE;
34562306a36Sopenharmony_ci		if (addr > partition_limit) {
34662306a36Sopenharmony_ci			addr = WL1251_PART_DOWN_MEM_START +
34762306a36Sopenharmony_ci				chunk_num * CHUNK_SIZE;
34862306a36Sopenharmony_ci			partition_limit = chunk_num * CHUNK_SIZE +
34962306a36Sopenharmony_ci				WL1251_PART_DOWN_MEM_SIZE;
35062306a36Sopenharmony_ci			wl1251_set_partition(wl,
35162306a36Sopenharmony_ci					     addr,
35262306a36Sopenharmony_ci					     WL1251_PART_DOWN_MEM_SIZE,
35362306a36Sopenharmony_ci					     WL1251_PART_DOWN_REG_START,
35462306a36Sopenharmony_ci					     WL1251_PART_DOWN_REG_SIZE);
35562306a36Sopenharmony_ci		}
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci		/* 10.3 upload the chunk */
35862306a36Sopenharmony_ci		addr = WL1251_PART_DOWN_MEM_START + chunk_num * CHUNK_SIZE;
35962306a36Sopenharmony_ci		p = wl->fw + FW_HDR_SIZE + chunk_num * CHUNK_SIZE;
36062306a36Sopenharmony_ci		wl1251_debug(DEBUG_BOOT, "uploading fw chunk 0x%p to 0x%x",
36162306a36Sopenharmony_ci			     p, addr);
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci		/* need to copy the chunk for dma */
36462306a36Sopenharmony_ci		len = CHUNK_SIZE;
36562306a36Sopenharmony_ci		memcpy(buf, p, len);
36662306a36Sopenharmony_ci		wl1251_mem_write(wl, addr, buf, len);
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci		chunk_num++;
36962306a36Sopenharmony_ci	}
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	/* 10.4 upload the last chunk */
37262306a36Sopenharmony_ci	addr = WL1251_PART_DOWN_MEM_START + chunk_num * CHUNK_SIZE;
37362306a36Sopenharmony_ci	p = wl->fw + FW_HDR_SIZE + chunk_num * CHUNK_SIZE;
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci	/* need to copy the chunk for dma */
37662306a36Sopenharmony_ci	len = fw_data_len % CHUNK_SIZE;
37762306a36Sopenharmony_ci	memcpy(buf, p, len);
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	wl1251_debug(DEBUG_BOOT, "uploading fw last chunk (%zu B) 0x%p to 0x%x",
38062306a36Sopenharmony_ci		     len, p, addr);
38162306a36Sopenharmony_ci	wl1251_mem_write(wl, addr, buf, len);
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci	kfree(buf);
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	return 0;
38662306a36Sopenharmony_ci}
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_cistatic int wl1251_boot_upload_nvs(struct wl1251 *wl)
38962306a36Sopenharmony_ci{
39062306a36Sopenharmony_ci	size_t nvs_len, nvs_bytes_written, burst_len;
39162306a36Sopenharmony_ci	int nvs_start, i;
39262306a36Sopenharmony_ci	u32 dest_addr, val;
39362306a36Sopenharmony_ci	u8 *nvs_ptr, *nvs;
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci	nvs = wl->nvs;
39662306a36Sopenharmony_ci	if (nvs == NULL)
39762306a36Sopenharmony_ci		return -ENODEV;
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci	nvs_ptr = nvs;
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci	nvs_len = wl->nvs_len;
40262306a36Sopenharmony_ci	nvs_start = wl->fw_len;
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci	/*
40562306a36Sopenharmony_ci	 * Layout before the actual NVS tables:
40662306a36Sopenharmony_ci	 * 1 byte : burst length.
40762306a36Sopenharmony_ci	 * 2 bytes: destination address.
40862306a36Sopenharmony_ci	 * n bytes: data to burst copy.
40962306a36Sopenharmony_ci	 *
41062306a36Sopenharmony_ci	 * This is ended by a 0 length, then the NVS tables.
41162306a36Sopenharmony_ci	 */
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci	while (nvs_ptr[0]) {
41462306a36Sopenharmony_ci		burst_len = nvs_ptr[0];
41562306a36Sopenharmony_ci		dest_addr = (nvs_ptr[1] & 0xfe) | ((u32)(nvs_ptr[2] << 8));
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci		/* We move our pointer to the data */
41862306a36Sopenharmony_ci		nvs_ptr += 3;
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci		for (i = 0; i < burst_len; i++) {
42162306a36Sopenharmony_ci			val = (nvs_ptr[0] | (nvs_ptr[1] << 8)
42262306a36Sopenharmony_ci			       | (nvs_ptr[2] << 16) | (nvs_ptr[3] << 24));
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci			wl1251_debug(DEBUG_BOOT,
42562306a36Sopenharmony_ci				     "nvs burst write 0x%x: 0x%x",
42662306a36Sopenharmony_ci				     dest_addr, val);
42762306a36Sopenharmony_ci			wl1251_mem_write32(wl, dest_addr, val);
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci			nvs_ptr += 4;
43062306a36Sopenharmony_ci			dest_addr += 4;
43162306a36Sopenharmony_ci		}
43262306a36Sopenharmony_ci	}
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci	/*
43562306a36Sopenharmony_ci	 * We've reached the first zero length, the first NVS table
43662306a36Sopenharmony_ci	 * is 7 bytes further.
43762306a36Sopenharmony_ci	 */
43862306a36Sopenharmony_ci	nvs_ptr += 7;
43962306a36Sopenharmony_ci	nvs_len -= nvs_ptr - nvs;
44062306a36Sopenharmony_ci	nvs_len = ALIGN(nvs_len, 4);
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci	/* Now we must set the partition correctly */
44362306a36Sopenharmony_ci	wl1251_set_partition(wl, nvs_start,
44462306a36Sopenharmony_ci			     WL1251_PART_DOWN_MEM_SIZE,
44562306a36Sopenharmony_ci			     WL1251_PART_DOWN_REG_START,
44662306a36Sopenharmony_ci			     WL1251_PART_DOWN_REG_SIZE);
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci	/* And finally we upload the NVS tables */
44962306a36Sopenharmony_ci	nvs_bytes_written = 0;
45062306a36Sopenharmony_ci	while (nvs_bytes_written < nvs_len) {
45162306a36Sopenharmony_ci		val = (nvs_ptr[0] | (nvs_ptr[1] << 8)
45262306a36Sopenharmony_ci		       | (nvs_ptr[2] << 16) | (nvs_ptr[3] << 24));
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ci		wl1251_debug(DEBUG_BOOT,
45562306a36Sopenharmony_ci			     "nvs write table 0x%x: 0x%x",
45662306a36Sopenharmony_ci			     nvs_start, val);
45762306a36Sopenharmony_ci		wl1251_mem_write32(wl, nvs_start, val);
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_ci		nvs_ptr += 4;
46062306a36Sopenharmony_ci		nvs_bytes_written += 4;
46162306a36Sopenharmony_ci		nvs_start += 4;
46262306a36Sopenharmony_ci	}
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	return 0;
46562306a36Sopenharmony_ci}
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ciint wl1251_boot(struct wl1251 *wl)
46862306a36Sopenharmony_ci{
46962306a36Sopenharmony_ci	int ret = 0, minor_minor_e2_ver;
47062306a36Sopenharmony_ci	u32 tmp, boot_data;
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci	/* halt embedded ARM CPU while loading firmware */
47362306a36Sopenharmony_ci	wl1251_reg_write32(wl, ACX_REG_ECPU_CONTROL, ECPU_CONTROL_HALT);
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci	ret = wl1251_boot_soft_reset(wl);
47662306a36Sopenharmony_ci	if (ret < 0)
47762306a36Sopenharmony_ci		goto out;
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_ci	/* 2. start processing NVS file */
48062306a36Sopenharmony_ci	if (wl->use_eeprom) {
48162306a36Sopenharmony_ci		wl1251_reg_write32(wl, ACX_REG_EE_START, START_EEPROM_MGR);
48262306a36Sopenharmony_ci		/* Wait for EEPROM NVS burst read to complete */
48362306a36Sopenharmony_ci		msleep(40);
48462306a36Sopenharmony_ci		wl1251_reg_write32(wl, ACX_EEPROMLESS_IND_REG, USE_EEPROM);
48562306a36Sopenharmony_ci	} else {
48662306a36Sopenharmony_ci		ret = wl1251_boot_upload_nvs(wl);
48762306a36Sopenharmony_ci		if (ret < 0)
48862306a36Sopenharmony_ci			goto out;
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_ci		/* write firmware's last address (ie. it's length) to
49162306a36Sopenharmony_ci		 * ACX_EEPROMLESS_IND_REG */
49262306a36Sopenharmony_ci		wl1251_reg_write32(wl, ACX_EEPROMLESS_IND_REG, wl->fw_len);
49362306a36Sopenharmony_ci	}
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_ci	/* 6. read the EEPROM parameters */
49662306a36Sopenharmony_ci	tmp = wl1251_reg_read32(wl, SCR_PAD2);
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci	/* 7. read bootdata */
49962306a36Sopenharmony_ci	wl->boot_attr.radio_type = (tmp & 0x0000FF00) >> 8;
50062306a36Sopenharmony_ci	wl->boot_attr.major = (tmp & 0x00FF0000) >> 16;
50162306a36Sopenharmony_ci	tmp = wl1251_reg_read32(wl, SCR_PAD3);
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_ci	/* 8. check bootdata and call restart sequence */
50462306a36Sopenharmony_ci	wl->boot_attr.minor = (tmp & 0x00FF0000) >> 16;
50562306a36Sopenharmony_ci	minor_minor_e2_ver = (tmp & 0xFF000000) >> 24;
50662306a36Sopenharmony_ci
50762306a36Sopenharmony_ci	wl1251_debug(DEBUG_BOOT, "radioType 0x%x majorE2Ver 0x%x "
50862306a36Sopenharmony_ci		     "minorE2Ver 0x%x minor_minor_e2_ver 0x%x",
50962306a36Sopenharmony_ci		     wl->boot_attr.radio_type, wl->boot_attr.major,
51062306a36Sopenharmony_ci		     wl->boot_attr.minor, minor_minor_e2_ver);
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci	ret = wl1251_boot_init_seq(wl);
51362306a36Sopenharmony_ci	if (ret < 0)
51462306a36Sopenharmony_ci		goto out;
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_ci	/* 9. NVS processing done */
51762306a36Sopenharmony_ci	boot_data = wl1251_reg_read32(wl, ACX_REG_ECPU_CONTROL);
51862306a36Sopenharmony_ci
51962306a36Sopenharmony_ci	wl1251_debug(DEBUG_BOOT, "halt boot_data 0x%x", boot_data);
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci	/* 10. check that ECPU_CONTROL_HALT bits are set in
52262306a36Sopenharmony_ci	 * pWhalBus->uBootData and start uploading firmware
52362306a36Sopenharmony_ci	 */
52462306a36Sopenharmony_ci	if ((boot_data & ECPU_CONTROL_HALT) == 0) {
52562306a36Sopenharmony_ci		wl1251_error("boot failed, ECPU_CONTROL_HALT not set");
52662306a36Sopenharmony_ci		ret = -EIO;
52762306a36Sopenharmony_ci		goto out;
52862306a36Sopenharmony_ci	}
52962306a36Sopenharmony_ci
53062306a36Sopenharmony_ci	ret = wl1251_boot_upload_firmware(wl);
53162306a36Sopenharmony_ci	if (ret < 0)
53262306a36Sopenharmony_ci		goto out;
53362306a36Sopenharmony_ci
53462306a36Sopenharmony_ci	/* 10.5 start firmware */
53562306a36Sopenharmony_ci	ret = wl1251_boot_run_firmware(wl);
53662306a36Sopenharmony_ci	if (ret < 0)
53762306a36Sopenharmony_ci		goto out;
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_ciout:
54062306a36Sopenharmony_ci	return ret;
54162306a36Sopenharmony_ci}
542