|
@@ -24,9 +24,13 @@
|
|
|
|
|
|
#include <linux/err.h>
|
|
|
|
|
|
+#include <linux/wl12xx.h>
|
|
|
+
|
|
|
#include "../wlcore/wlcore.h"
|
|
|
#include "../wlcore/debug.h"
|
|
|
#include "../wlcore/io.h"
|
|
|
+#include "../wlcore/acx.h"
|
|
|
+#include "../wlcore/boot.h"
|
|
|
|
|
|
#include "reg.h"
|
|
|
|
|
@@ -181,21 +185,408 @@ out:
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+static void wl12xx_top_reg_write(struct wl1271 *wl, int addr, u16 val)
|
|
|
+{
|
|
|
+ /* write address >> 1 + 0x30000 to OCP_POR_CTR */
|
|
|
+ addr = (addr >> 1) + 0x30000;
|
|
|
+ wl1271_write32(wl, WL12XX_OCP_POR_CTR, addr);
|
|
|
+
|
|
|
+ /* write value to OCP_POR_WDATA */
|
|
|
+ wl1271_write32(wl, WL12XX_OCP_DATA_WRITE, val);
|
|
|
+
|
|
|
+ /* write 1 to OCP_CMD */
|
|
|
+ wl1271_write32(wl, WL12XX_OCP_CMD, OCP_CMD_WRITE);
|
|
|
+}
|
|
|
+
|
|
|
+static u16 wl12xx_top_reg_read(struct wl1271 *wl, int addr)
|
|
|
+{
|
|
|
+ u32 val;
|
|
|
+ int timeout = OCP_CMD_LOOP;
|
|
|
+
|
|
|
+ /* write address >> 1 + 0x30000 to OCP_POR_CTR */
|
|
|
+ addr = (addr >> 1) + 0x30000;
|
|
|
+ wl1271_write32(wl, WL12XX_OCP_POR_CTR, addr);
|
|
|
+
|
|
|
+ /* write 2 to OCP_CMD */
|
|
|
+ wl1271_write32(wl, WL12XX_OCP_CMD, OCP_CMD_READ);
|
|
|
+
|
|
|
+ /* poll for data ready */
|
|
|
+ do {
|
|
|
+ val = wl1271_read32(wl, WL12XX_OCP_DATA_READ);
|
|
|
+ } while (!(val & OCP_READY_MASK) && --timeout);
|
|
|
+
|
|
|
+ if (!timeout) {
|
|
|
+ wl1271_warning("Top register access timed out.");
|
|
|
+ return 0xffff;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* check data status and return if OK */
|
|
|
+ if ((val & OCP_STATUS_MASK) == OCP_STATUS_OK)
|
|
|
+ return val & 0xffff;
|
|
|
+ else {
|
|
|
+ wl1271_warning("Top register access returned error.");
|
|
|
+ return 0xffff;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static int wl128x_switch_tcxo_to_fref(struct wl1271 *wl)
|
|
|
+{
|
|
|
+ u16 spare_reg;
|
|
|
+
|
|
|
+ /* Mask bits [2] & [8:4] in the sys_clk_cfg register */
|
|
|
+ spare_reg = wl12xx_top_reg_read(wl, WL_SPARE_REG);
|
|
|
+ if (spare_reg == 0xFFFF)
|
|
|
+ return -EFAULT;
|
|
|
+ spare_reg |= (BIT(3) | BIT(5) | BIT(6));
|
|
|
+ wl12xx_top_reg_write(wl, WL_SPARE_REG, spare_reg);
|
|
|
+
|
|
|
+ /* Enable FREF_CLK_REQ & mux MCS and coex PLLs to FREF */
|
|
|
+ wl12xx_top_reg_write(wl, SYS_CLK_CFG_REG,
|
|
|
+ WL_CLK_REQ_TYPE_PG2 | MCS_PLL_CLK_SEL_FREF);
|
|
|
+
|
|
|
+ /* Delay execution for 15msec, to let the HW settle */
|
|
|
+ mdelay(15);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static bool wl128x_is_tcxo_valid(struct wl1271 *wl)
|
|
|
+{
|
|
|
+ u16 tcxo_detection;
|
|
|
+
|
|
|
+ tcxo_detection = wl12xx_top_reg_read(wl, TCXO_CLK_DETECT_REG);
|
|
|
+ if (tcxo_detection & TCXO_DET_FAILED)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+static bool wl128x_is_fref_valid(struct wl1271 *wl)
|
|
|
+{
|
|
|
+ u16 fref_detection;
|
|
|
+
|
|
|
+ fref_detection = wl12xx_top_reg_read(wl, FREF_CLK_DETECT_REG);
|
|
|
+ if (fref_detection & FREF_CLK_DETECT_FAIL)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+static int wl128x_manually_configure_mcs_pll(struct wl1271 *wl)
|
|
|
+{
|
|
|
+ wl12xx_top_reg_write(wl, MCS_PLL_M_REG, MCS_PLL_M_REG_VAL);
|
|
|
+ wl12xx_top_reg_write(wl, MCS_PLL_N_REG, MCS_PLL_N_REG_VAL);
|
|
|
+ wl12xx_top_reg_write(wl, MCS_PLL_CONFIG_REG, MCS_PLL_CONFIG_REG_VAL);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int wl128x_configure_mcs_pll(struct wl1271 *wl, int clk)
|
|
|
+{
|
|
|
+ u16 spare_reg;
|
|
|
+ u16 pll_config;
|
|
|
+ u8 input_freq;
|
|
|
+
|
|
|
+ /* Mask bits [3:1] in the sys_clk_cfg register */
|
|
|
+ spare_reg = wl12xx_top_reg_read(wl, WL_SPARE_REG);
|
|
|
+ if (spare_reg == 0xFFFF)
|
|
|
+ return -EFAULT;
|
|
|
+ spare_reg |= BIT(2);
|
|
|
+ wl12xx_top_reg_write(wl, WL_SPARE_REG, spare_reg);
|
|
|
+
|
|
|
+ /* Handle special cases of the TCXO clock */
|
|
|
+ if (wl->tcxo_clock == WL12XX_TCXOCLOCK_16_8 ||
|
|
|
+ wl->tcxo_clock == WL12XX_TCXOCLOCK_33_6)
|
|
|
+ return wl128x_manually_configure_mcs_pll(wl);
|
|
|
+
|
|
|
+ /* Set the input frequency according to the selected clock source */
|
|
|
+ input_freq = (clk & 1) + 1;
|
|
|
+
|
|
|
+ pll_config = wl12xx_top_reg_read(wl, MCS_PLL_CONFIG_REG);
|
|
|
+ if (pll_config == 0xFFFF)
|
|
|
+ return -EFAULT;
|
|
|
+ pll_config |= (input_freq << MCS_SEL_IN_FREQ_SHIFT);
|
|
|
+ pll_config |= MCS_PLL_ENABLE_HP;
|
|
|
+ wl12xx_top_reg_write(wl, MCS_PLL_CONFIG_REG, pll_config);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * WL128x has two clocks input - TCXO and FREF.
|
|
|
+ * TCXO is the main clock of the device, while FREF is used to sync
|
|
|
+ * between the GPS and the cellular modem.
|
|
|
+ * In cases where TCXO is 32.736MHz or 16.368MHz, the FREF will be used
|
|
|
+ * as the WLAN/BT main clock.
|
|
|
+ */
|
|
|
+static int wl128x_boot_clk(struct wl1271 *wl, int *selected_clock)
|
|
|
+{
|
|
|
+ u16 sys_clk_cfg;
|
|
|
+
|
|
|
+ /* For XTAL-only modes, FREF will be used after switching from TCXO */
|
|
|
+ if (wl->ref_clock == WL12XX_REFCLOCK_26_XTAL ||
|
|
|
+ wl->ref_clock == WL12XX_REFCLOCK_38_XTAL) {
|
|
|
+ if (!wl128x_switch_tcxo_to_fref(wl))
|
|
|
+ return -EINVAL;
|
|
|
+ goto fref_clk;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Query the HW, to determine which clock source we should use */
|
|
|
+ sys_clk_cfg = wl12xx_top_reg_read(wl, SYS_CLK_CFG_REG);
|
|
|
+ if (sys_clk_cfg == 0xFFFF)
|
|
|
+ return -EINVAL;
|
|
|
+ if (sys_clk_cfg & PRCM_CM_EN_MUX_WLAN_FREF)
|
|
|
+ goto fref_clk;
|
|
|
+
|
|
|
+ /* If TCXO is either 32.736MHz or 16.368MHz, switch to FREF */
|
|
|
+ if (wl->tcxo_clock == WL12XX_TCXOCLOCK_16_368 ||
|
|
|
+ wl->tcxo_clock == WL12XX_TCXOCLOCK_32_736) {
|
|
|
+ if (!wl128x_switch_tcxo_to_fref(wl))
|
|
|
+ return -EINVAL;
|
|
|
+ goto fref_clk;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* TCXO clock is selected */
|
|
|
+ if (!wl128x_is_tcxo_valid(wl))
|
|
|
+ return -EINVAL;
|
|
|
+ *selected_clock = wl->tcxo_clock;
|
|
|
+ goto config_mcs_pll;
|
|
|
+
|
|
|
+fref_clk:
|
|
|
+ /* FREF clock is selected */
|
|
|
+ if (!wl128x_is_fref_valid(wl))
|
|
|
+ return -EINVAL;
|
|
|
+ *selected_clock = wl->ref_clock;
|
|
|
+
|
|
|
+config_mcs_pll:
|
|
|
+ return wl128x_configure_mcs_pll(wl, *selected_clock);
|
|
|
+}
|
|
|
+
|
|
|
+static int wl127x_boot_clk(struct wl1271 *wl)
|
|
|
+{
|
|
|
+ u32 pause;
|
|
|
+ u32 clk;
|
|
|
+
|
|
|
+ if (WL127X_PG_GET_MAJOR(wl->hw_pg_ver) < 3)
|
|
|
+ wl->quirks |= WLCORE_QUIRK_END_OF_TRANSACTION;
|
|
|
+
|
|
|
+ if (wl->ref_clock == CONF_REF_CLK_19_2_E ||
|
|
|
+ wl->ref_clock == CONF_REF_CLK_38_4_E ||
|
|
|
+ wl->ref_clock == CONF_REF_CLK_38_4_M_XTAL)
|
|
|
+ /* ref clk: 19.2/38.4/38.4-XTAL */
|
|
|
+ clk = 0x3;
|
|
|
+ else if (wl->ref_clock == CONF_REF_CLK_26_E ||
|
|
|
+ wl->ref_clock == CONF_REF_CLK_52_E)
|
|
|
+ /* ref clk: 26/52 */
|
|
|
+ clk = 0x5;
|
|
|
+ else
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (wl->ref_clock != CONF_REF_CLK_19_2_E) {
|
|
|
+ u16 val;
|
|
|
+ /* Set clock type (open drain) */
|
|
|
+ val = wl12xx_top_reg_read(wl, OCP_REG_CLK_TYPE);
|
|
|
+ val &= FREF_CLK_TYPE_BITS;
|
|
|
+ wl12xx_top_reg_write(wl, OCP_REG_CLK_TYPE, val);
|
|
|
+
|
|
|
+ /* Set clock pull mode (no pull) */
|
|
|
+ val = wl12xx_top_reg_read(wl, OCP_REG_CLK_PULL);
|
|
|
+ val |= NO_PULL;
|
|
|
+ wl12xx_top_reg_write(wl, OCP_REG_CLK_PULL, val);
|
|
|
+ } else {
|
|
|
+ u16 val;
|
|
|
+ /* Set clock polarity */
|
|
|
+ val = wl12xx_top_reg_read(wl, OCP_REG_CLK_POLARITY);
|
|
|
+ val &= FREF_CLK_POLARITY_BITS;
|
|
|
+ val |= CLK_REQ_OUTN_SEL;
|
|
|
+ wl12xx_top_reg_write(wl, OCP_REG_CLK_POLARITY, val);
|
|
|
+ }
|
|
|
+
|
|
|
+ wl1271_write32(wl, WL12XX_PLL_PARAMETERS, clk);
|
|
|
+
|
|
|
+ pause = wl1271_read32(wl, WL12XX_PLL_PARAMETERS);
|
|
|
+
|
|
|
+ wl1271_debug(DEBUG_BOOT, "pause1 0x%x", pause);
|
|
|
+
|
|
|
+ pause &= ~(WU_COUNTER_PAUSE_VAL);
|
|
|
+ pause |= WU_COUNTER_PAUSE_VAL;
|
|
|
+ wl1271_write32(wl, WL12XX_WU_COUNTER_PAUSE, pause);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int wl1271_boot_soft_reset(struct wl1271 *wl)
|
|
|
+{
|
|
|
+ unsigned long timeout;
|
|
|
+ u32 boot_data;
|
|
|
+
|
|
|
+ /* perform soft reset */
|
|
|
+ wl1271_write32(wl, WL12XX_SLV_SOFT_RESET, ACX_SLV_SOFT_RESET_BIT);
|
|
|
+
|
|
|
+ /* SOFT_RESET is self clearing */
|
|
|
+ timeout = jiffies + usecs_to_jiffies(SOFT_RESET_MAX_TIME);
|
|
|
+ while (1) {
|
|
|
+ boot_data = wl1271_read32(wl, WL12XX_SLV_SOFT_RESET);
|
|
|
+ wl1271_debug(DEBUG_BOOT, "soft reset bootdata 0x%x", boot_data);
|
|
|
+ if ((boot_data & ACX_SLV_SOFT_RESET_BIT) == 0)
|
|
|
+ break;
|
|
|
+
|
|
|
+ if (time_after(jiffies, timeout)) {
|
|
|
+ /* 1.2 check pWhalBus->uSelfClearTime if the
|
|
|
+ * timeout was reached */
|
|
|
+ wl1271_error("soft reset timeout");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ udelay(SOFT_RESET_STALL_TIME);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* disable Rx/Tx */
|
|
|
+ wl1271_write32(wl, WL12XX_ENABLE, 0x0);
|
|
|
+
|
|
|
+ /* disable auto calibration on start*/
|
|
|
+ wl1271_write32(wl, WL12XX_SPARE_A2, 0xffff);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int wl12xx_pre_boot(struct wl1271 *wl)
|
|
|
+{
|
|
|
+ int ret = 0;
|
|
|
+ u32 clk;
|
|
|
+ int selected_clock = -1;
|
|
|
+
|
|
|
+ if (wl->chip.id == CHIP_ID_1283_PG20) {
|
|
|
+ ret = wl128x_boot_clk(wl, &selected_clock);
|
|
|
+ if (ret < 0)
|
|
|
+ goto out;
|
|
|
+ } else {
|
|
|
+ ret = wl127x_boot_clk(wl);
|
|
|
+ if (ret < 0)
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Continue the ELP wake up sequence */
|
|
|
+ wl1271_write32(wl, WL12XX_WELP_ARM_COMMAND, WELP_ARM_COMMAND_VAL);
|
|
|
+ udelay(500);
|
|
|
+
|
|
|
+ wlcore_set_partition(wl, &wl->ptable[PART_DRPW]);
|
|
|
+
|
|
|
+ /* Read-modify-write DRPW_SCRATCH_START register (see next state)
|
|
|
+ to be used by DRPw FW. The RTRIM value will be added by the FW
|
|
|
+ before taking DRPw out of reset */
|
|
|
+
|
|
|
+ clk = wl1271_read32(wl, WL12XX_DRPW_SCRATCH_START);
|
|
|
+
|
|
|
+ wl1271_debug(DEBUG_BOOT, "clk2 0x%x", clk);
|
|
|
+
|
|
|
+ if (wl->chip.id == CHIP_ID_1283_PG20)
|
|
|
+ clk |= ((selected_clock & 0x3) << 1) << 4;
|
|
|
+ else
|
|
|
+ clk |= (wl->ref_clock << 1) << 4;
|
|
|
+
|
|
|
+ wl1271_write32(wl, WL12XX_DRPW_SCRATCH_START, clk);
|
|
|
+
|
|
|
+ wlcore_set_partition(wl, &wl->ptable[PART_WORK]);
|
|
|
+
|
|
|
+ /* Disable interrupts */
|
|
|
+ wlcore_write_reg(wl, REG_INTERRUPT_MASK, WL1271_ACX_INTR_ALL);
|
|
|
+
|
|
|
+ ret = wl1271_boot_soft_reset(wl);
|
|
|
+ if (ret < 0)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+out:
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static void wl12xx_pre_upload(struct wl1271 *wl)
|
|
|
+{
|
|
|
+ u32 tmp;
|
|
|
+
|
|
|
+ /* write firmware's last address (ie. it's length) to
|
|
|
+ * ACX_EEPROMLESS_IND_REG */
|
|
|
+ wl1271_debug(DEBUG_BOOT, "ACX_EEPROMLESS_IND_REG");
|
|
|
+
|
|
|
+ wl1271_write32(wl, WL12XX_EEPROMLESS_IND, WL12XX_EEPROMLESS_IND);
|
|
|
+
|
|
|
+ tmp = wlcore_read_reg(wl, REG_CHIP_ID_B);
|
|
|
+
|
|
|
+ wl1271_debug(DEBUG_BOOT, "chip id 0x%x", tmp);
|
|
|
+
|
|
|
+ /* 6. read the EEPROM parameters */
|
|
|
+ tmp = wl1271_read32(wl, WL12XX_SCR_PAD2);
|
|
|
+
|
|
|
+ /* WL1271: The reference driver skips steps 7 to 10 (jumps directly
|
|
|
+ * to upload_fw) */
|
|
|
+
|
|
|
+ if (wl->chip.id == CHIP_ID_1283_PG20)
|
|
|
+ wl12xx_top_reg_write(wl, SDIO_IO_DS, HCI_IO_DS_6MA);
|
|
|
+}
|
|
|
+
|
|
|
+static void wl12xx_enable_interrupts(struct wl1271 *wl)
|
|
|
+{
|
|
|
+ u32 polarity;
|
|
|
+
|
|
|
+ polarity = wl12xx_top_reg_read(wl, OCP_REG_POLARITY);
|
|
|
+
|
|
|
+ /* We use HIGH polarity, so unset the LOW bit */
|
|
|
+ polarity &= ~POLARITY_LOW;
|
|
|
+ wl12xx_top_reg_write(wl, OCP_REG_POLARITY, polarity);
|
|
|
+
|
|
|
+ wlcore_write_reg(wl, REG_INTERRUPT_MASK, WL1271_ACX_ALL_EVENTS_VECTOR);
|
|
|
+
|
|
|
+ wlcore_enable_interrupts(wl);
|
|
|
+ wlcore_write_reg(wl, REG_INTERRUPT_MASK,
|
|
|
+ WL1271_ACX_INTR_ALL & ~(WL1271_INTR_MASK));
|
|
|
+
|
|
|
+ wl1271_write32(wl, WL12XX_HI_CFG, HI_CFG_DEF_VAL);
|
|
|
+}
|
|
|
+
|
|
|
+static int wl12xx_boot(struct wl1271 *wl)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ ret = wl12xx_pre_boot(wl);
|
|
|
+ if (ret < 0)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ ret = wlcore_boot_upload_nvs(wl);
|
|
|
+ if (ret < 0)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ wl12xx_pre_upload(wl);
|
|
|
+
|
|
|
+ ret = wlcore_boot_upload_firmware(wl);
|
|
|
+ if (ret < 0)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ ret = wlcore_boot_run_firmware(wl);
|
|
|
+ if (ret < 0)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ wl12xx_enable_interrupts(wl);
|
|
|
+
|
|
|
+out:
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
static s8 wl12xx_get_pg_ver(struct wl1271 *wl)
|
|
|
{
|
|
|
u32 die_info;
|
|
|
|
|
|
if (wl->chip.id == CHIP_ID_1283_PG20)
|
|
|
- die_info = wl1271_top_reg_read(wl, WL128X_REG_FUSE_DATA_2_1);
|
|
|
+ die_info = wl12xx_top_reg_read(wl, WL128X_REG_FUSE_DATA_2_1);
|
|
|
else
|
|
|
- die_info = wl1271_top_reg_read(wl, WL127X_REG_FUSE_DATA_2_1);
|
|
|
+ die_info = wl12xx_top_reg_read(wl, WL127X_REG_FUSE_DATA_2_1);
|
|
|
|
|
|
return (s8) (die_info & PG_VER_MASK) >> PG_VER_OFFSET;
|
|
|
}
|
|
|
|
|
|
static struct wlcore_ops wl12xx_ops = {
|
|
|
- .identify_chip = wl12xx_identify_chip,
|
|
|
- .get_pg_ver = wl12xx_get_pg_ver,
|
|
|
+ .identify_chip = wl12xx_identify_chip,
|
|
|
+ .boot = wl12xx_boot,
|
|
|
+ .get_pg_ver = wl12xx_get_pg_ver,
|
|
|
};
|
|
|
|
|
|
static int __devinit wl12xx_probe(struct platform_device *pdev)
|