1013 lines
26 KiB
C
Executable File
1013 lines
26 KiB
C
Executable File
/*
|
|
* Firmware I/O implementation for XRadio drivers
|
|
*
|
|
* Copyright (c) 2013
|
|
* Xradio Technology Co., Ltd. <www.xradiotech.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/firmware.h>
|
|
|
|
#include "xradio.h"
|
|
#include "fwio.h"
|
|
#include "hwio.h"
|
|
#include "sbus.h"
|
|
#include "bh.h"
|
|
|
|
/* Macroses are local. */
|
|
#define APB_WRITE(reg, val) \
|
|
do { \
|
|
ret = xradio_apb_write_32(hw_priv, APB_ADDR(reg), (val)); \
|
|
if (ret < 0) { \
|
|
xradio_dbg(XRADIO_DBG_ERROR, \
|
|
"%s: can't write %s at line %d.\n", \
|
|
__func__, #reg, __LINE__); \
|
|
goto error; \
|
|
} \
|
|
} while (0)
|
|
#define APB_READ(reg, val) \
|
|
do { \
|
|
ret = xradio_apb_read_32(hw_priv, APB_ADDR(reg), &(val)); \
|
|
if (ret < 0) { \
|
|
xradio_dbg(XRADIO_DBG_ERROR, \
|
|
"%s: can't read %s at line %d.\n", \
|
|
__func__, #reg, __LINE__); \
|
|
goto error; \
|
|
} \
|
|
} while (0)
|
|
#define REG_WRITE(reg, val) \
|
|
do { \
|
|
ret = xradio_reg_write_32(hw_priv, (reg), (val)); \
|
|
if (ret < 0) { \
|
|
xradio_dbg(XRADIO_DBG_ERROR, \
|
|
"%s: can't write %s at line %d.\n", \
|
|
__func__, #reg, __LINE__); \
|
|
goto error; \
|
|
} \
|
|
} while (0)
|
|
#define REG_READ(reg, val) \
|
|
do { \
|
|
ret = xradio_reg_read_32(hw_priv, (reg), &(val)); \
|
|
if (ret < 0) { \
|
|
xradio_dbg(XRADIO_DBG_ERROR, \
|
|
"%s: can't read %s at line %d.\n", \
|
|
__func__, #reg, __LINE__); \
|
|
goto error; \
|
|
} \
|
|
} while (0)
|
|
|
|
static int xradio_get_hw_type(u32 config_reg_val, int *major_revision)
|
|
{
|
|
int hw_type = -1;
|
|
u32 hif_type = (config_reg_val >> 24) & 0x4;
|
|
/*u32 hif_vers = (config_reg_val >> 31) & 0x1;*/
|
|
|
|
/* Check if we have XRADIO */
|
|
if (hif_type == 0x4) {
|
|
*major_revision = 0x4;
|
|
hw_type = HIF_HW_TYPE_XRADIO;
|
|
} else {
|
|
/*hw type unknown.*/
|
|
*major_revision = 0x0;
|
|
}
|
|
return hw_type;
|
|
}
|
|
|
|
/*
|
|
* This function is called to Parse the SDD file
|
|
* to extract some informations
|
|
*/
|
|
static int xradio_parse_sdd(struct xradio_common *hw_priv, u32 *dpll)
|
|
{
|
|
int ret = 0;
|
|
const char *sdd_path = NULL;
|
|
struct xradio_sdd *pElement = NULL;
|
|
int parsedLength = 0;
|
|
|
|
sta_printk(XRADIO_DBG_TRC, "%s\n", __func__);
|
|
SYS_BUG(hw_priv->sdd != NULL);
|
|
|
|
/* select and load sdd file depend on hardware version. */
|
|
switch (hw_priv->hw_revision) {
|
|
case XR829_HW_REV0:
|
|
sdd_path = XR829_SDD_FILE;
|
|
#ifdef CONFIG_XRADIO_ETF
|
|
if (etf_is_connect())
|
|
sdd_path = etf_get_sddpath();
|
|
#endif
|
|
break;
|
|
default:
|
|
xradio_dbg(XRADIO_DBG_ERROR,
|
|
"%s: unknown hardware version.\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
#ifdef USE_VFS_FIRMWARE
|
|
hw_priv->sdd = xr_request_file(sdd_path);
|
|
if (unlikely(!hw_priv->sdd)) {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: can't load sdd file %s.\n",
|
|
__func__, sdd_path);
|
|
return -ENOENT;
|
|
}
|
|
#else
|
|
ret = request_firmware(&hw_priv->sdd, sdd_path, hw_priv->pdev);
|
|
if (unlikely(ret)) {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: can't load sdd file %s.\n",
|
|
__func__, sdd_path);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/*parse SDD config.*/
|
|
hw_priv->is_BT_Present = false;
|
|
pElement = (struct xradio_sdd *)hw_priv->sdd->data;
|
|
parsedLength += (FIELD_OFFSET(struct xradio_sdd, data) + \
|
|
pElement->length);
|
|
pElement = FIND_NEXT_ELT(pElement);
|
|
|
|
while (parsedLength < hw_priv->sdd->size) {
|
|
switch (pElement->id) {
|
|
case SDD_PTA_CFG_ELT_ID:
|
|
hw_priv->conf_listen_interval =
|
|
(*((u16 *) pElement->data + 1) >> 7) & 0x1F;
|
|
hw_priv->is_BT_Present = true;
|
|
xradio_dbg(XRADIO_DBG_NIY,
|
|
"PTA element found.Listen Interval %d\n",
|
|
hw_priv->conf_listen_interval);
|
|
break;
|
|
case SDD_REFERENCE_FREQUENCY_ELT_ID:
|
|
switch (*((uint16_t *) pElement->data)) {
|
|
case 0x32C8:
|
|
*dpll = 0x1D89D241;
|
|
break;
|
|
case 0x3E80:
|
|
*dpll = 0x1E1;
|
|
break;
|
|
case 0x41A0:
|
|
*dpll = 0x124931C1;
|
|
break;
|
|
case 0x4B00:
|
|
*dpll = 0x191;
|
|
break;
|
|
case 0x5DC0:
|
|
*dpll = 0x141;
|
|
break;
|
|
case 0x6590:
|
|
*dpll = 0x0EC4F121;
|
|
break;
|
|
case 0x8340:
|
|
*dpll = 0x92490E1;
|
|
break;
|
|
case 0x9600:
|
|
*dpll = 0x100010C1;
|
|
break;
|
|
case 0x9C40:
|
|
*dpll = 0xC1;
|
|
break;
|
|
case 0xBB80:
|
|
*dpll = 0xA1;
|
|
break;
|
|
case 0xCB20:
|
|
*dpll = 0x7627091;
|
|
break;
|
|
default:
|
|
*dpll = DPLL_INIT_VAL_XRADIO;
|
|
xradio_dbg(XRADIO_DBG_WARN,
|
|
"Unknown Reference clock frequency."
|
|
"Use default DPLL value=0x%08x.",
|
|
DPLL_INIT_VAL_XRADIO);
|
|
break;
|
|
}
|
|
xradio_dbg(XRADIO_DBG_NIY,
|
|
"Reference clock=%uKHz, DPLL value=0x%08x.\n",
|
|
*((uint16_t *) pElement->data), *dpll);
|
|
default:
|
|
break;
|
|
}
|
|
parsedLength += (FIELD_OFFSET(struct xradio_sdd, data) + \
|
|
pElement->length);
|
|
pElement = FIND_NEXT_ELT(pElement);
|
|
}
|
|
|
|
xradio_dbg(XRADIO_DBG_MSG, "sdd size=%zu parse len=%d.\n",
|
|
hw_priv->sdd->size, parsedLength);
|
|
|
|
|
|
if (hw_priv->is_BT_Present == false) {
|
|
hw_priv->conf_listen_interval = 0;
|
|
xradio_dbg(XRADIO_DBG_NIY, "PTA element NOT found.\n");
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int xradio_update_dpllctrl(struct xradio_common *hw_priv, u32 dpll_update)
|
|
{
|
|
int ret = 0;
|
|
u32 val32 = 0;
|
|
u32 dpll_read = 0;
|
|
int i = 0;
|
|
|
|
xradio_ahb_read_32(hw_priv, PWRCTRL_WLAN_START_CFG, &val32);
|
|
do {
|
|
ret = xradio_ahb_read_32(hw_priv, PWRCTRL_WLAN_COMMON_CFG, &val32);
|
|
/*Check DPLL, return success if sync finish*/
|
|
if (val32 & PWRCTRL_COMMON_REG_DONE) {
|
|
xradio_ahb_read_32(hw_priv, PWRCTRL_WLAN_DPLL_CTRL, &dpll_read);
|
|
if (dpll_update == dpll_read) {
|
|
xradio_dbg(XRADIO_DBG_NIY, "%s: dpll sync ok=0x%08x.\n",
|
|
__func__, dpll_read);
|
|
break;
|
|
} else {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: dpll is incorrect," \
|
|
"dpll_read=0x%08x, dpll_update=0x%08x.\n",
|
|
__func__, dpll_read, dpll_update);
|
|
/*dpll_ctrl need to be corrected it by follow procedure.*/
|
|
}
|
|
}
|
|
|
|
/*correct dpll_ctrl if wlan is accessible.*/
|
|
if ((val32 & PWRCTRL_COMMON_REG_ARBT) && /*wait for arbit end.*/
|
|
!(val32 & PWRCTRL_COMMON_REG_BT)) { /*wlan is accessible.*/
|
|
xradio_ahb_read_32(hw_priv, PWRCTRL_WLAN_DPLL_CTRL, &dpll_read);
|
|
if (dpll_update != dpll_read) {
|
|
xradio_dbg(XRADIO_DBG_WARN,
|
|
"%s:dpll_read=0x%08x, new dpll_ctrl=0x%08x.\n",
|
|
__func__, dpll_read, dpll_update);
|
|
xradio_ahb_write_32(hw_priv,
|
|
PWRCTRL_WLAN_DPLL_CTRL, (dpll_update|0x1));
|
|
msleep(5); /*wait for it stable after change DPLL config. */
|
|
} else {
|
|
xradio_dbg(XRADIO_DBG_ALWY, "%s: DPLL_CTRL Sync=0x%08x.\n",
|
|
__func__, dpll_read);
|
|
}
|
|
xradio_ahb_write_32(hw_priv, PWRCTRL_WLAN_COMMON_CFG,
|
|
PWRCTRL_COMMON_REG_DONE); /*set done*/
|
|
break;
|
|
} else if (i < 100) {
|
|
xradio_dbg(XRADIO_DBG_WARN, "%s: COMMON_REG=0x%08x.\n",
|
|
__func__, val32);
|
|
msleep(i);
|
|
++i;
|
|
} else {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s:DPLL access timeout=0x%08x.\n",
|
|
__func__, val32);
|
|
ret = -ETIMEDOUT;
|
|
break;
|
|
}
|
|
} while (!ret);
|
|
xradio_ahb_write_32(hw_priv, PWRCTRL_WLAN_START_CFG, 0);
|
|
return ret;
|
|
}
|
|
|
|
static int xradio_firmware(struct xradio_common *hw_priv)
|
|
{
|
|
int ret, block, num_blocks;
|
|
unsigned i;
|
|
u32 val32;
|
|
u32 put = 0, get = 0;
|
|
u8 *buf = NULL;
|
|
const char *fw_path;
|
|
#ifdef USE_VFS_FIRMWARE
|
|
const struct xr_file *firmware = NULL;
|
|
#else
|
|
const struct firmware *firmware = NULL;
|
|
#endif
|
|
xradio_dbg(XRADIO_DBG_TRC, "%s\n", __func__);
|
|
|
|
switch (hw_priv->hw_revision) {
|
|
case XR829_HW_REV0:
|
|
fw_path = XR829_FIRMWARE;
|
|
#ifdef CONFIG_XRADIO_ETF
|
|
if (etf_is_connect())
|
|
fw_path = etf_get_fwpath();
|
|
#endif
|
|
break;
|
|
default:
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: invalid silicon revision %d.\n",
|
|
__func__, hw_priv->hw_revision);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Initialize common registers */
|
|
APB_WRITE(DOWNLOAD_IMAGE_SIZE_REG, DOWNLOAD_ARE_YOU_HERE);
|
|
APB_WRITE(DOWNLOAD_PUT_REG, 0);
|
|
APB_WRITE(DOWNLOAD_GET_REG, 0);
|
|
APB_WRITE(DOWNLOAD_STATUS_REG, DOWNLOAD_PENDING);
|
|
APB_WRITE(DOWNLOAD_FLAGS_REG, 0);
|
|
|
|
/* Release CPU from RESET */
|
|
xradio_reg_bit_operate(hw_priv, HIF_CONFIG_REG_ID,
|
|
0, HIF_CONFIG_CPU_RESET_BIT);
|
|
/* Enable Clock */
|
|
xradio_reg_bit_operate(hw_priv, HIF_CONFIG_REG_ID,
|
|
0, HIF_CONFIG_CPU_CLK_DIS_BIT);
|
|
|
|
/* Load a firmware file */
|
|
#ifdef USE_VFS_FIRMWARE
|
|
firmware = xr_fileopen(fw_path, O_RDONLY, 0);
|
|
if (!firmware) {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: can't load firmware file %s.\n",
|
|
__func__, fw_path);
|
|
ret = -1;
|
|
goto error;
|
|
}
|
|
#else
|
|
ret = request_firmware(&firmware, fw_path, hw_priv->pdev);
|
|
if (ret) {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: can't load firmware file %s.\n",
|
|
__func__, fw_path);
|
|
goto error;
|
|
}
|
|
SYS_BUG(!firmware->data);
|
|
#endif
|
|
|
|
buf = xr_kmalloc(DOWNLOAD_BLOCK_SIZE, true);
|
|
if (!buf) {
|
|
xradio_dbg(XRADIO_DBG_ERROR,
|
|
"%s: can't allocate firmware buffer.\n", __func__);
|
|
ret = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
/* Check if the bootloader is ready */
|
|
for (i = 0; i < 100; i++ /*= 1 + i / 2*/) {
|
|
APB_READ(DOWNLOAD_IMAGE_SIZE_REG, val32);
|
|
if (val32 == DOWNLOAD_I_AM_HERE)
|
|
break;
|
|
mdelay(10);
|
|
} /* End of for loop */
|
|
if (val32 != DOWNLOAD_I_AM_HERE) {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: bootloader is not ready.\n",
|
|
__func__);
|
|
#ifdef BOOT_NOT_READY_FIX
|
|
hw_priv->boot_not_ready_cnt++;
|
|
hw_priv->boot_not_ready = 1;
|
|
#endif
|
|
ret = -ETIMEDOUT;
|
|
goto error;
|
|
}
|
|
|
|
/* Calculcate number of download blocks */
|
|
num_blocks = (firmware->size - 1) / DOWNLOAD_BLOCK_SIZE + 1;
|
|
|
|
/* Updating the length in Download Ctrl Area */
|
|
val32 = firmware->size; /* Explicit cast from size_t to u32 */
|
|
APB_WRITE(DOWNLOAD_IMAGE_SIZE_REG, val32);
|
|
|
|
/*
|
|
* DOWNLOAD_BLOCK_SIZE must be divided exactly by sdio blocksize,
|
|
* otherwise it may cause bootloader error.
|
|
*/
|
|
val32 = hw_priv->sbus_ops->get_block_size(hw_priv->sbus_priv);
|
|
if (val32 > DOWNLOAD_BLOCK_SIZE || DOWNLOAD_BLOCK_SIZE%val32) {
|
|
xradio_dbg(XRADIO_DBG_WARN,
|
|
"%s:change blocksize(%d->%d) during download fw.\n",
|
|
__func__, val32, DOWNLOAD_BLOCK_SIZE>>1);
|
|
hw_priv->sbus_ops->lock(hw_priv->sbus_priv);
|
|
ret = hw_priv->sbus_ops->set_block_size(hw_priv->sbus_priv,
|
|
DOWNLOAD_BLOCK_SIZE>>1);
|
|
if (ret)
|
|
xradio_dbg(XRADIO_DBG_ERROR,
|
|
"%s: set blocksize error(%d).\n", __func__, ret);
|
|
hw_priv->sbus_ops->unlock(hw_priv->sbus_priv);
|
|
}
|
|
|
|
/* Firmware downloading loop */
|
|
for (block = 0; block < num_blocks; block++) {
|
|
size_t tx_size;
|
|
size_t block_size;
|
|
|
|
/* check the download status */
|
|
APB_READ(DOWNLOAD_STATUS_REG, val32);
|
|
if (val32 != DOWNLOAD_PENDING) {
|
|
xradio_dbg(XRADIO_DBG_ERROR,
|
|
"%s: bootloader reported error %d.\n",
|
|
__func__, val32);
|
|
ret = -EIO;
|
|
goto error;
|
|
}
|
|
|
|
/* calculate the block size */
|
|
tx_size = block_size = min((size_t)(firmware->size - put),
|
|
(size_t)DOWNLOAD_BLOCK_SIZE);
|
|
#ifdef USE_VFS_FIRMWARE
|
|
ret = xr_fileread(firmware, buf, block_size);
|
|
if (ret < block_size) {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: xr_fileread error %d.\n",
|
|
__func__, ret);
|
|
goto error;
|
|
}
|
|
#else
|
|
memcpy(buf, &firmware->data[put], block_size);
|
|
#endif
|
|
if (block_size < DOWNLOAD_BLOCK_SIZE) {
|
|
memset(&buf[block_size], 0, DOWNLOAD_BLOCK_SIZE - block_size);
|
|
tx_size = DOWNLOAD_BLOCK_SIZE;
|
|
}
|
|
|
|
/* loop until put - get <= 24K */
|
|
for (i = 0; i < 100; i++) {
|
|
APB_READ(DOWNLOAD_GET_REG, get);
|
|
if ((put - get) <= (DOWNLOAD_FIFO_SIZE - DOWNLOAD_BLOCK_SIZE))
|
|
break;
|
|
mdelay(i);
|
|
}
|
|
|
|
if ((put - get) > (DOWNLOAD_FIFO_SIZE - DOWNLOAD_BLOCK_SIZE)) {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: Timeout waiting for FIFO.\n",
|
|
__func__);
|
|
ret = -ETIMEDOUT;
|
|
goto error;
|
|
}
|
|
|
|
/* send the block to sram */
|
|
ret = xradio_apb_write(hw_priv, APB_ADDR(DOWNLOAD_FIFO_OFFSET + \
|
|
(put & (DOWNLOAD_FIFO_SIZE - 1))),
|
|
buf, tx_size);
|
|
if (ret < 0) {
|
|
xradio_dbg(XRADIO_DBG_ERROR,
|
|
"%s: can't write block at line %d.\n",
|
|
__func__, __LINE__);
|
|
goto error;
|
|
}
|
|
|
|
/* update the put register */
|
|
put += block_size;
|
|
APB_WRITE(DOWNLOAD_PUT_REG, put);
|
|
} /* End of firmware download loop */
|
|
|
|
/* Wait for the download completion */
|
|
for (i = 0; i < 300; i += 1 + i / 2) {
|
|
APB_READ(DOWNLOAD_STATUS_REG, val32);
|
|
if (val32 != DOWNLOAD_PENDING)
|
|
break;
|
|
mdelay(i);
|
|
}
|
|
if (val32 != DOWNLOAD_SUCCESS) {
|
|
xradio_dbg(XRADIO_DBG_ERROR,
|
|
"%s: wait for download completion failed. " \
|
|
"Read: 0x%.8X\n", __func__, val32);
|
|
ret = -ETIMEDOUT;
|
|
goto error;
|
|
} else {
|
|
xradio_dbg(XRADIO_DBG_ALWY, "Firmware completed.\n");
|
|
ret = 0;
|
|
}
|
|
|
|
error:
|
|
if (buf)
|
|
kfree(buf);
|
|
if (firmware) {
|
|
#ifdef USE_VFS_FIRMWARE
|
|
xr_fileclose(firmware);
|
|
#else
|
|
release_firmware(firmware);
|
|
#endif
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int xradio_bootloader(struct xradio_common *hw_priv)
|
|
{
|
|
int ret = -1;
|
|
u32 i = 0;
|
|
const char *bl_path = XR829_BOOTLOADER;
|
|
u32 addr = AHB_MEMORY_ADDRESS;
|
|
u32 *data = NULL;
|
|
#ifdef USE_VFS_FIRMWARE
|
|
const struct xr_file *bootloader = NULL;
|
|
#else
|
|
const struct firmware *bootloader = NULL;
|
|
#endif
|
|
xradio_dbg(XRADIO_DBG_TRC, "%s\n", __func__);
|
|
|
|
#ifdef USE_VFS_FIRMWARE
|
|
bootloader = xr_request_file(bl_path);
|
|
if (!bootloader) {
|
|
xradio_dbg(XRADIO_DBG_ERROR,
|
|
"%s: can't load bootloader file %s.\n",
|
|
__func__, bl_path);
|
|
goto error;
|
|
}
|
|
#else
|
|
/* Load a bootloader file */
|
|
ret = request_firmware(&bootloader, bl_path, hw_priv->pdev);
|
|
if (ret) {
|
|
xradio_dbg(XRADIO_DBG_ERROR,
|
|
"%s: can't load bootloader file %s.\n",
|
|
__func__, bl_path);
|
|
goto error;
|
|
}
|
|
#endif
|
|
|
|
xradio_dbg(XRADIO_DBG_NIY, "%s: bootloader size = %zu, loopcount = %zu\n",
|
|
__func__, bootloader->size, (bootloader->size) / 4);
|
|
|
|
/* Down bootloader. */
|
|
data = (u32 *)bootloader->data;
|
|
for (i = 0; i < (bootloader->size)/4; i++) {
|
|
ret = xradio_ahb_write_32(hw_priv, addr, data[i]);
|
|
if (ret < 0) {
|
|
sbus_printk(XRADIO_DBG_ERROR, "%s: xradio_ahb_write failed.\n", __func__);
|
|
goto error;
|
|
}
|
|
if (i == 100 || i == 200 || i == 300 || i == 400 || i == 500 || i == 600)
|
|
xradio_dbg(XRADIO_DBG_NIY, "%s: addr = 0x%x,data = 0x%x\n", __func__, addr, data[i]);
|
|
addr += 4;
|
|
}
|
|
xradio_dbg(XRADIO_DBG_ALWY, "Bootloader complete\n");
|
|
|
|
error:
|
|
if (bootloader) {
|
|
#ifdef USE_VFS_FIRMWARE
|
|
xr_fileclose(bootloader);
|
|
#else
|
|
release_firmware(bootloader);
|
|
#endif
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
#if (DBG_XRADIO_HIF)
|
|
|
|
extern u16 hif_test_rw; /*0: nothing to do; 1: write only; 2: write and read*/
|
|
extern u16 hif_test_data_mode; /* hif test data mode, such as 0x55, 0xff etc*/
|
|
extern u16 hif_test_data_len; /* hif test data len, every data len pre round*/
|
|
extern u16 hif_test_data_round;
|
|
extern u16 hif_test_oper_delta; /* hif test operation delta time, give more time to analyze data tranx*/
|
|
int HIF_R_W_TEST(struct xradio_common *hw_priv)
|
|
{
|
|
int time;
|
|
int i;
|
|
struct timeval start;
|
|
struct timeval end;
|
|
unsigned int addr;
|
|
char *write_buf;
|
|
char *read_buf;
|
|
write_buf = kmalloc(hif_test_data_len * 4, GFP_KERNEL);
|
|
if (!write_buf)
|
|
return 0xff;
|
|
read_buf = kmalloc(hif_test_data_len * 4, GFP_KERNEL);
|
|
if (!read_buf) {
|
|
kfree(write_buf);
|
|
return 0xff;
|
|
}
|
|
|
|
do_gettimeofday(&start);
|
|
printk(KERN_ERR"[HIF test] --- <write> --- begin~~\n");
|
|
addr = PAS_RAM_START_ADDR;
|
|
memset(write_buf, hif_test_data_mode, hif_test_data_len * 4);
|
|
i = 0;
|
|
while (i < hif_test_data_round) {
|
|
xradio_apb_write(hw_priv, addr, write_buf, hif_test_data_len * 4);
|
|
msleep(hif_test_oper_delta);
|
|
i++;
|
|
if (0 == hif_test_rw)
|
|
goto err;
|
|
}
|
|
|
|
if (1 == hif_test_rw) { // means write only
|
|
kfree(write_buf);
|
|
kfree(read_buf);
|
|
printk(KERN_ERR"[HIF test] --- <write> --- end and return~~\n");
|
|
return 0;
|
|
}
|
|
|
|
msleep(hif_test_oper_delta * 5);
|
|
printk(KERN_ERR"[HIF test] --- <read> --- begin~~\n");
|
|
|
|
addr = PAS_RAM_START_ADDR;
|
|
memset(write_buf, hif_test_data_mode, hif_test_data_len * 4);
|
|
i = 0;
|
|
while (i < hif_test_data_round) {
|
|
xradio_apb_read(hw_priv, addr, read_buf, hif_test_data_len * 4);
|
|
msleep(hif_test_oper_delta);
|
|
i++;
|
|
if (0 == hif_test_rw)
|
|
goto err;
|
|
}
|
|
|
|
printk(KERN_ERR"[HIF test] --- <read> --- end~~\n");
|
|
do_gettimeofday(&end);
|
|
time = 1000 * (end.tv_sec - start.tv_sec) + (end.tv_usec - start.tv_usec) / 1000;
|
|
kfree(write_buf);
|
|
kfree(read_buf);
|
|
return 0;
|
|
err:
|
|
kfree(write_buf);
|
|
kfree(read_buf);
|
|
return 1;
|
|
}
|
|
|
|
#endif
|
|
|
|
#if (DBG_PAS_RAM)
|
|
int pas_ram_check(struct xradio_common *hw_priv)
|
|
{
|
|
int ret;
|
|
int count = 0;
|
|
int time;
|
|
int i;
|
|
bool error = false;
|
|
unsigned char write_value[4] = {0x0, 0x55, 0xaa, 0xff};
|
|
struct timeval start;
|
|
struct timeval end;
|
|
unsigned int addr;
|
|
unsigned char *write_buf;
|
|
unsigned char *read_buf;
|
|
write_buf = kmalloc(WRITE_READ_MAX_LEN, GFP_KERNEL);
|
|
if (!write_buf)
|
|
return 0xff;
|
|
read_buf = kmalloc(WRITE_READ_MAX_LEN, GFP_KERNEL);
|
|
if (!read_buf) {
|
|
kfree(write_buf);
|
|
return 0xff;
|
|
}
|
|
|
|
printk(KERN_ERR "[PAS_RAM_TEST begin] start address = 0x%08x, end address = 0x%08x\n", PAS_RAM_START_ADDR, PAS_RAM_END_ADDR);
|
|
do_gettimeofday(&start);
|
|
for (i = 0;i < 4;i++) {
|
|
addr = PAS_RAM_START_ADDR;
|
|
memset(write_buf, write_value[i], WRITE_READ_MAX_LEN);
|
|
while (addr + WRITE_READ_MAX_LEN <= PAS_RAM_END_ADDR + 1) {
|
|
if (xradio_apb_write(hw_priv, addr, write_buf, WRITE_READ_MAX_LEN))
|
|
goto err;
|
|
if (xradio_apb_read(hw_priv, addr, read_buf, WRITE_READ_MAX_LEN))
|
|
goto err;
|
|
ret = memcmp(write_buf, read_buf, WRITE_READ_MAX_LEN);
|
|
count++;
|
|
if (ret) {
|
|
unsigned char *check_addr = read_buf;
|
|
while (check_addr < read_buf + WRITE_READ_MAX_LEN) {
|
|
if (*check_addr != write_value[i]) {
|
|
printk(KERN_ERR "[pas_ram err]:error address:0x%08x, write value:0x%02x, but read value:0x%02x\n",
|
|
addr + check_addr - read_buf, write_value[i], *check_addr);
|
|
}
|
|
check_addr++;
|
|
}
|
|
error = true;
|
|
} else {
|
|
/* printk(KERN_ERR "pas_ram test: %d finish, writing value:0x%02x, there are no error\n", count, write_value[i]); */
|
|
}
|
|
addr += WRITE_READ_MAX_LEN;
|
|
}
|
|
}
|
|
do_gettimeofday(&end);
|
|
time = 1000 * (end.tv_sec - start.tv_sec) + (end.tv_usec - start.tv_usec) / 1000;
|
|
printk(KERN_ERR "[PAS_RAM_TEST end] used time:%dms\n", time);
|
|
kfree(write_buf);
|
|
kfree(read_buf);
|
|
if (error)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
err:
|
|
printk(KERN_ERR "fail to read or write!\n");
|
|
kfree(write_buf);
|
|
kfree(read_buf);
|
|
return 0xff;
|
|
}
|
|
|
|
extern u8 dbg_pas_ram;
|
|
#endif
|
|
|
|
|
|
#if (DBG_AHB_RAM)
|
|
int ahb_ram_check(struct xradio_common *hw_priv)
|
|
{
|
|
int count = 0;
|
|
int time;
|
|
int i;
|
|
bool error = false;
|
|
unsigned char write_value[4] = {0x0, 0x55, 0xaa, 0xff};
|
|
struct timeval start;
|
|
struct timeval end;
|
|
unsigned int addr;
|
|
unsigned char *write_buf;
|
|
u32 val_read;
|
|
u32 val_expected;
|
|
|
|
write_buf = kmalloc(AHB_WRITE_MAX_LEN, GFP_KERNEL);
|
|
if (!write_buf)
|
|
return 0xff;
|
|
|
|
printk(KERN_ERR "[AHB_RAM_TEST begin] start address = 0x%08x, end address = 0x%08x\n", AHB_RAM_START_ADDR, AHB_RAM_END_ADDR);
|
|
do_gettimeofday(&start);
|
|
for (i = 0;i < 4;i++) {
|
|
/* ahb write*/
|
|
addr = AHB_RAM_START_ADDR;
|
|
memset(write_buf, write_value[i], AHB_WRITE_MAX_LEN);
|
|
while (addr + AHB_WRITE_MAX_LEN <= AHB_RAM_END_ADDR + 1) {
|
|
if (xradio_ahb_write(hw_priv, addr, write_buf, AHB_WRITE_MAX_LEN))
|
|
goto err;
|
|
addr += AHB_WRITE_MAX_LEN;
|
|
}
|
|
|
|
/* ahb read*/
|
|
addr = AHB_RAM_START_ADDR;
|
|
memset(&val_expected, write_value[i], AHB_READ_MAX_LEN);
|
|
while (addr + AHB_READ_MAX_LEN <= AHB_RAM_END_ADDR + 1) {
|
|
if (xradio_ahb_read_32(hw_priv, addr, &val_read))
|
|
goto err;
|
|
count++;
|
|
if (val_read != val_expected) {
|
|
printk(KERN_ERR "[ahb_ram err]:error address:0x%08x, write value:0x%08x, but read value:0x%08x\n",
|
|
addr, val_expected, val_read);
|
|
error = true;
|
|
} else {
|
|
/* printk(KERN_ERR "ahb_ram test: %d finish, writing value:0x%02x, there are no error\n", count, write_value[i]); */
|
|
}
|
|
addr += AHB_READ_MAX_LEN;
|
|
}
|
|
}
|
|
do_gettimeofday(&end);
|
|
time = 1000 * (end.tv_sec - start.tv_sec) + (end.tv_usec - start.tv_usec) / 1000;
|
|
printk(KERN_ERR "[AHB_RAM_TEST end] used time:%dms\n", time);
|
|
kfree(write_buf);
|
|
if (error)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
err:
|
|
printk(KERN_ERR "fail to read or write!\n");
|
|
kfree(write_buf);
|
|
return 0xff;
|
|
}
|
|
|
|
extern u8 dbg_ahb_ram;
|
|
#endif
|
|
|
|
int xradio_load_firmware(struct xradio_common *hw_priv)
|
|
{
|
|
int ret;
|
|
int i;
|
|
u32 val32;
|
|
u16 val16;
|
|
u32 dpll = 0;
|
|
int major_revision;
|
|
xradio_dbg(XRADIO_DBG_TRC, "%s\n", __func__);
|
|
|
|
SYS_BUG(!hw_priv);
|
|
|
|
/* Read CONFIG Register Value - We will read 32 bits */
|
|
ret = xradio_reg_read_32(hw_priv, HIF_CONFIG_REG_ID, &val32);
|
|
if (ret < 0) {
|
|
xradio_dbg(XRADIO_DBG_ERROR,
|
|
"%s: can't read config register, err=%d.\n",
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
/*check hardware type and revision.*/
|
|
hw_priv->hw_type = xradio_get_hw_type(val32, &major_revision);
|
|
switch (hw_priv->hw_type) {
|
|
case HIF_HW_TYPE_XRADIO:
|
|
xradio_dbg(XRADIO_DBG_NIY, "%s: HW_TYPE_XRADIO detected.\n",
|
|
__func__);
|
|
break;
|
|
default:
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: Unknown hardware: %d.\n",
|
|
__func__, hw_priv->hw_type);
|
|
return -ENOTSUPP;
|
|
}
|
|
if (major_revision == 4) {
|
|
hw_priv->hw_revision = XR829_HW_REV0;
|
|
xradio_dbg(XRADIO_DBG_ALWY, "XRADIO_HW_REV 1.0 detected.\n");
|
|
} else {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: Unsupported major revision %d.\n",
|
|
__func__, major_revision);
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
/*load sdd file, and get config from it.*/
|
|
ret = xradio_parse_sdd(hw_priv, &dpll);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/*set dpll initial value and check.*/
|
|
ret = xradio_reg_write_32(hw_priv, HIF_TSET_GEN_R_W_REG_ID, dpll);
|
|
if (ret < 0) {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: can't write DPLL register.\n",
|
|
__func__);
|
|
goto out;
|
|
}
|
|
msleep(5);
|
|
ret = xradio_reg_read_32(hw_priv, HIF_TSET_GEN_R_W_REG_ID, &val32);
|
|
if (ret < 0) {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: can't read DPLL register.\n",
|
|
__func__);
|
|
goto out;
|
|
}
|
|
if (val32 != dpll) {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: unable to initialise " \
|
|
"DPLL register. Wrote 0x%.8X, read 0x%.8X.\n",
|
|
__func__, dpll, val32);
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
/* Set wakeup bit in device */
|
|
ret = xradio_reg_bit_operate(hw_priv, HIF_CONTROL_REG_ID, HIF_CTRL_WUP_BIT, 0);
|
|
if (ret < 0) {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: device wake up failed.\n", __func__);
|
|
goto out;
|
|
}
|
|
|
|
/* Wait for wakeup */
|
|
for (i = 0 ; i < 300 ; i += 1 + i / 2) {
|
|
ret = xradio_reg_read_16(hw_priv, HIF_CONTROL_REG_ID, &val16);
|
|
if (ret < 0) {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: Wait_for_wakeup: "
|
|
"can't read control register.\n", __func__);
|
|
goto out;
|
|
}
|
|
if (val16 & HIF_CTRL_RDY_BIT) {
|
|
break;
|
|
}
|
|
msleep(i);
|
|
}
|
|
if ((val16 & HIF_CTRL_RDY_BIT) == 0) {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: Wait for wakeup:"
|
|
"device is not responding.\n", __func__);
|
|
#ifdef BOOT_NOT_READY_FIX
|
|
hw_priv->boot_not_ready_cnt++;
|
|
hw_priv->boot_not_ready = 1;
|
|
xradio_dbg(XRADIO_DBG_ERROR, "Device will restart at %d times!\n",
|
|
hw_priv->boot_not_ready_cnt);
|
|
#ifdef ERROR_HANG_DRIVER
|
|
if (hw_priv->boot_not_ready_cnt >= 6) {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "Boot not ready and device restart "
|
|
"more than 6, hang the driver.\n");
|
|
error_hang_driver = 1;
|
|
}
|
|
#endif
|
|
#endif
|
|
ret = -ETIMEDOUT;
|
|
goto out;
|
|
} else {
|
|
xradio_dbg(XRADIO_DBG_NIY, "WLAN device is ready.\n");
|
|
}
|
|
|
|
/* Checking for access mode and download firmware. */
|
|
ret = xradio_reg_bit_operate(hw_priv, HIF_CONFIG_REG_ID,
|
|
HIF_CONFIG_ACCESS_MODE_BIT, 0);
|
|
if (ret < 0) {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: check_access_mode: "
|
|
"can't read config register.\n", __func__);
|
|
goto out;
|
|
}
|
|
|
|
#if (DBG_XRADIO_HIF)
|
|
|
|
if (hif_test_rw) {
|
|
ret = HIF_R_W_TEST(hw_priv);
|
|
if (0 == ret) {
|
|
printk(KERN_ERR "HIF Test OK!\n");
|
|
} else if (1 == ret) {
|
|
printk(KERN_ERR "HIF Test faied!\n");
|
|
} else {
|
|
printk(KERN_ERR "Unkmow error!\n");
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#if (DBG_PAS_RAM)
|
|
/*test PAS_RAM*/
|
|
if (dbg_pas_ram == 1) {
|
|
int ret = pas_ram_check(hw_priv);
|
|
if (ret == 0) {
|
|
dbg_pas_ram = 2;
|
|
printk(KERN_ERR "PAS_RAM_Test: all are right\n");
|
|
} else if (ret == 1){
|
|
dbg_pas_ram = 3;
|
|
printk(KERN_ERR "PAS_RAM_Test: error write/read in some address\n");
|
|
} else {
|
|
dbg_pas_ram = 4;
|
|
printk(KERN_ERR "PAS_RAM_Test: unable to test the chip\n");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if (DBG_AHB_RAM)
|
|
/*test AHB_RAM*/
|
|
if (dbg_ahb_ram == 1) {
|
|
int ret = ahb_ram_check(hw_priv);
|
|
if (ret == 0) {
|
|
dbg_ahb_ram = 2;
|
|
printk(KERN_ERR "AHB_RAM_Test: all are right\n");
|
|
} else if (ret == 1){
|
|
dbg_ahb_ram = 3;
|
|
printk(KERN_ERR "AHB_RAM_Test: error write/read in some address\n");
|
|
} else {
|
|
dbg_ahb_ram = 4;
|
|
printk(KERN_ERR "AHB_RAM_Test: unable to test the chip\n");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef SUPPORT_DPLL_CHECK
|
|
/*Checking DPLL value and correct it if need.*/
|
|
xradio_update_dpllctrl(hw_priv, xradio_dllctrl_convert(dpll));
|
|
#else
|
|
xradio_dbg(XRADIO_DBG_ALWY, "%s: not need check dpll.\n", __func__);
|
|
#endif
|
|
|
|
/* Down bootloader. */
|
|
ret = xradio_bootloader(hw_priv);
|
|
if (ret < 0) {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: can't download bootloader.\n", __func__);
|
|
goto out;
|
|
}
|
|
/* Down firmware. */
|
|
ret = xradio_firmware(hw_priv);
|
|
if (ret < 0) {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: can't download firmware.\n", __func__);
|
|
goto out;
|
|
}
|
|
|
|
/* Register Interrupt Handler */
|
|
ret = hw_priv->sbus_ops->irq_subscribe(hw_priv->sbus_priv,
|
|
(sbus_irq_handler)xradio_irq_handler,
|
|
hw_priv);
|
|
if (ret < 0) {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: can't register IRQ handler.\n",
|
|
__func__);
|
|
goto out;
|
|
}
|
|
|
|
if (HIF_HW_TYPE_XRADIO == hw_priv->hw_type) {
|
|
/* If device is XRADIO the IRQ enable/disable bits
|
|
* are in CONFIG register */
|
|
ret = xradio_reg_bit_operate(hw_priv, HIF_CONFIG_REG_ID,
|
|
HIF_CONF_IRQ_RDY_ENABLE, 0);
|
|
if (ret < 0) {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: enable_irq: can't read " \
|
|
"config register.\n", __func__);
|
|
goto unsubscribe;
|
|
}
|
|
} else {
|
|
/* Enable device interrupts - Both DATA_RDY and WLAN_RDY */
|
|
ret = xradio_reg_bit_operate(hw_priv, HIF_CONTROL_REG_ID,
|
|
HIF_CTRL_IRQ_RDY_ENABLE, 0);
|
|
if (ret < 0) {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: enable_irq: can't read " \
|
|
"control register.\n", __func__);
|
|
goto unsubscribe;
|
|
}
|
|
}
|
|
|
|
/* Configure device for MESSSAGE MODE */
|
|
ret = xradio_reg_bit_operate(hw_priv, HIF_CONFIG_REG_ID,
|
|
0, HIF_CONFIG_ACCESS_MODE_BIT);
|
|
if (ret < 0) {
|
|
xradio_dbg(XRADIO_DBG_ERROR, "%s: set_mode: can't read config register.\n",
|
|
__func__);
|
|
goto unsubscribe;
|
|
}
|
|
|
|
/* Unless we read the CONFIG Register we are
|
|
* not able to get an interrupt */
|
|
mdelay(10);
|
|
xradio_reg_read_32(hw_priv, HIF_CONFIG_REG_ID, &val32);
|
|
return 0;
|
|
|
|
unsubscribe:
|
|
hw_priv->sbus_ops->irq_unsubscribe(hw_priv->sbus_priv);
|
|
out:
|
|
if (hw_priv->sdd) {
|
|
#ifdef USE_VFS_FIRMWARE
|
|
xr_fileclose(hw_priv->sdd);
|
|
#else
|
|
release_firmware(hw_priv->sdd);
|
|
#endif
|
|
hw_priv->sdd = NULL;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int xradio_dev_deinit(struct xradio_common *hw_priv)
|
|
{
|
|
hw_priv->sbus_ops->irq_unsubscribe(hw_priv->sbus_priv);
|
|
if (hw_priv->sdd) {
|
|
#ifdef USE_VFS_FIRMWARE
|
|
xr_fileclose(hw_priv->sdd);
|
|
#else
|
|
release_firmware(hw_priv->sdd);
|
|
#endif
|
|
hw_priv->sdd = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#undef APB_WRITE
|
|
#undef APB_READ
|
|
#undef REG_WRITE
|
|
#undef REG_READ
|