1508 lines
40 KiB
C
1508 lines
40 KiB
C
/**
|
|
******************************************************************************
|
|
*
|
|
* rwnx_cmds.c
|
|
*
|
|
* Handles queueing (push to IPC, ack/cfm from IPC) of commands issued to
|
|
* LMAC FW
|
|
*
|
|
* Copyright (C) RivieraWaves 2014-2019
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include <linux/list.h>
|
|
#include <linux/version.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/firmware.h>
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 12, 0)
|
|
#include <linux/hardirq.h>
|
|
#endif
|
|
#include <linux/fs.h>
|
|
#include "aicsdio_txrxif.h"
|
|
#include "aicsdio.h"
|
|
#include "aic_bsp_driver.h"
|
|
#include "md5.h"
|
|
#include "aic8800dc_compat.h"
|
|
#include "aic8800d80_compat.h"
|
|
#include "aicwf_firmware_array.h"
|
|
#define FW_PATH_MAX 200
|
|
|
|
extern int adap_test;
|
|
extern char aic_fw_path[FW_PATH_MAX];
|
|
extern struct aic_sdio_dev *aicbsp_sdiodev;
|
|
|
|
static void cmd_dump(const struct rwnx_cmd *cmd)
|
|
{
|
|
printk(KERN_CRIT "tkn[%d] flags:%04x result:%3d cmd:%4d - reqcfm(%4d)\n",
|
|
cmd->tkn, cmd->flags, cmd->result, cmd->id, cmd->reqid);
|
|
}
|
|
|
|
static void cmd_complete(struct rwnx_cmd_mgr *cmd_mgr, struct rwnx_cmd *cmd)
|
|
{
|
|
//printk("cmdcmp\n");
|
|
lockdep_assert_held(&cmd_mgr->lock);
|
|
|
|
list_del(&cmd->list);
|
|
cmd_mgr->queue_sz--;
|
|
|
|
cmd->flags |= RWNX_CMD_FLAG_DONE;
|
|
if (cmd->flags & RWNX_CMD_FLAG_NONBLOCK) {
|
|
kfree(cmd);
|
|
} else {
|
|
if (RWNX_CMD_WAIT_COMPLETE(cmd->flags)) {
|
|
cmd->result = 0;
|
|
complete(&cmd->complete);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int cmd_mgr_queue(struct rwnx_cmd_mgr *cmd_mgr, struct rwnx_cmd *cmd)
|
|
{
|
|
bool defer_push = false;
|
|
int err = 0;
|
|
|
|
spin_lock_bh(&cmd_mgr->lock);
|
|
|
|
if (cmd_mgr->state == RWNX_CMD_MGR_STATE_CRASHED) {
|
|
printk(KERN_CRIT"cmd queue crashed\n");
|
|
cmd->result = -EPIPE;
|
|
spin_unlock_bh(&cmd_mgr->lock);
|
|
return -EPIPE;
|
|
}
|
|
|
|
if (!list_empty(&cmd_mgr->cmds)) {
|
|
struct rwnx_cmd *last;
|
|
|
|
if (cmd_mgr->queue_sz == cmd_mgr->max_queue_sz) {
|
|
printk(KERN_CRIT"Too many cmds (%d) already queued\n",
|
|
cmd_mgr->max_queue_sz);
|
|
cmd->result = -ENOMEM;
|
|
spin_unlock_bh(&cmd_mgr->lock);
|
|
return -ENOMEM;
|
|
}
|
|
last = list_entry(cmd_mgr->cmds.prev, struct rwnx_cmd, list);
|
|
if (last->flags & (RWNX_CMD_FLAG_WAIT_ACK | RWNX_CMD_FLAG_WAIT_PUSH)) {
|
|
cmd->flags |= RWNX_CMD_FLAG_WAIT_PUSH;
|
|
defer_push = true;
|
|
}
|
|
}
|
|
|
|
if (cmd->flags & RWNX_CMD_FLAG_REQ_CFM)
|
|
cmd->flags |= RWNX_CMD_FLAG_WAIT_CFM;
|
|
|
|
cmd->tkn = cmd_mgr->next_tkn++;
|
|
cmd->result = -EINTR;
|
|
|
|
if (!(cmd->flags & RWNX_CMD_FLAG_NONBLOCK))
|
|
init_completion(&cmd->complete);
|
|
|
|
list_add_tail(&cmd->list, &cmd_mgr->cmds);
|
|
cmd_mgr->queue_sz++;
|
|
spin_unlock_bh(&cmd_mgr->lock);
|
|
|
|
if (!defer_push) {
|
|
//printk("queue:id=%x, param_len=%u\n", cmd->a2e_msg->id, cmd->a2e_msg->param_len);
|
|
rwnx_set_cmd_tx((void *)(cmd_mgr->sdiodev), cmd->a2e_msg, sizeof(struct lmac_msg) + cmd->a2e_msg->param_len);
|
|
//rwnx_ipc_msg_push(rwnx_hw, cmd, RWNX_CMD_A2EMSG_LEN(cmd->a2e_msg));
|
|
kfree(cmd->a2e_msg);
|
|
} else {
|
|
//WAKE_CMD_WORK(cmd_mgr);
|
|
printk("ERR: never defer push!!!!");
|
|
return 0;
|
|
}
|
|
|
|
if (!(cmd->flags & RWNX_CMD_FLAG_NONBLOCK)) {
|
|
unsigned long tout = msecs_to_jiffies(RWNX_80211_CMD_TIMEOUT_MS * cmd_mgr->queue_sz);
|
|
if (!wait_for_completion_killable_timeout(&cmd->complete, tout)) {
|
|
printk(KERN_CRIT"cmd timed-out\n");
|
|
cmd_dump(cmd);
|
|
spin_lock_bh(&cmd_mgr->lock);
|
|
cmd_mgr->state = RWNX_CMD_MGR_STATE_CRASHED;
|
|
if (!(cmd->flags & RWNX_CMD_FLAG_DONE)) {
|
|
cmd->result = -ETIMEDOUT;
|
|
cmd_complete(cmd_mgr, cmd);
|
|
}
|
|
spin_unlock_bh(&cmd_mgr->lock);
|
|
err = -ETIMEDOUT;
|
|
} else {
|
|
kfree(cmd);
|
|
}
|
|
} else {
|
|
cmd->result = 0;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int cmd_mgr_run_callback(struct rwnx_cmd_mgr *cmd_mgr, struct rwnx_cmd *cmd,
|
|
struct rwnx_cmd_e2amsg *msg, msg_cb_fct cb)
|
|
{
|
|
int res;
|
|
|
|
if (!cb) {
|
|
return 0;
|
|
}
|
|
spin_lock(&cmd_mgr->cb_lock);
|
|
res = cb(cmd, msg);
|
|
spin_unlock(&cmd_mgr->cb_lock);
|
|
|
|
return res;
|
|
}
|
|
|
|
static int cmd_mgr_msgind(struct rwnx_cmd_mgr *cmd_mgr, struct rwnx_cmd_e2amsg *msg,
|
|
msg_cb_fct cb)
|
|
{
|
|
struct rwnx_cmd *cmd;
|
|
bool found = false;
|
|
|
|
//printk("cmd->id=%x\n", msg->id);
|
|
spin_lock(&cmd_mgr->lock);
|
|
list_for_each_entry(cmd, &cmd_mgr->cmds, list) {
|
|
if (cmd->reqid == msg->id &&
|
|
(cmd->flags & RWNX_CMD_FLAG_WAIT_CFM)) {
|
|
|
|
if (!cmd_mgr_run_callback(cmd_mgr, cmd, msg, cb)) {
|
|
found = true;
|
|
cmd->flags &= ~RWNX_CMD_FLAG_WAIT_CFM;
|
|
|
|
if (WARN((msg->param_len > RWNX_CMD_E2AMSG_LEN_MAX),
|
|
"Unexpect E2A msg len %d > %d\n", msg->param_len,
|
|
RWNX_CMD_E2AMSG_LEN_MAX)) {
|
|
msg->param_len = RWNX_CMD_E2AMSG_LEN_MAX;
|
|
}
|
|
|
|
if (cmd->e2a_msg && msg->param_len)
|
|
memcpy(cmd->e2a_msg, &msg->param, msg->param_len);
|
|
|
|
if (RWNX_CMD_WAIT_COMPLETE(cmd->flags))
|
|
cmd_complete(cmd_mgr, cmd);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
spin_unlock(&cmd_mgr->lock);
|
|
|
|
if (!found)
|
|
cmd_mgr_run_callback(cmd_mgr, NULL, msg, cb);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void cmd_mgr_print(struct rwnx_cmd_mgr *cmd_mgr)
|
|
{
|
|
struct rwnx_cmd *cur;
|
|
|
|
spin_lock_bh(&cmd_mgr->lock);
|
|
list_for_each_entry(cur, &cmd_mgr->cmds, list) {
|
|
cmd_dump(cur);
|
|
}
|
|
spin_unlock_bh(&cmd_mgr->lock);
|
|
}
|
|
|
|
static void cmd_mgr_drain(struct rwnx_cmd_mgr *cmd_mgr)
|
|
{
|
|
struct rwnx_cmd *cur, *nxt;
|
|
|
|
spin_lock_bh(&cmd_mgr->lock);
|
|
list_for_each_entry_safe(cur, nxt, &cmd_mgr->cmds, list) {
|
|
list_del(&cur->list);
|
|
cmd_mgr->queue_sz--;
|
|
if (!(cur->flags & RWNX_CMD_FLAG_NONBLOCK))
|
|
complete(&cur->complete);
|
|
}
|
|
spin_unlock_bh(&cmd_mgr->lock);
|
|
}
|
|
|
|
void rwnx_cmd_mgr_init(struct rwnx_cmd_mgr *cmd_mgr)
|
|
{
|
|
cmd_mgr->max_queue_sz = RWNX_CMD_MAX_QUEUED;
|
|
INIT_LIST_HEAD(&cmd_mgr->cmds);
|
|
cmd_mgr->state = RWNX_CMD_MGR_STATE_INITED;
|
|
spin_lock_init(&cmd_mgr->lock);
|
|
spin_lock_init(&cmd_mgr->cb_lock);
|
|
cmd_mgr->queue = &cmd_mgr_queue;
|
|
cmd_mgr->print = &cmd_mgr_print;
|
|
cmd_mgr->drain = &cmd_mgr_drain;
|
|
cmd_mgr->llind = NULL;//&cmd_mgr_llind;
|
|
cmd_mgr->msgind = &cmd_mgr_msgind;
|
|
|
|
#if 0
|
|
INIT_WORK(&cmd_mgr->cmdWork, cmd_mgr_task_process);
|
|
cmd_mgr->cmd_wq = create_singlethread_workqueue("cmd_wq");
|
|
if (!cmd_mgr->cmd_wq) {
|
|
txrx_err("insufficient memory to create cmd workqueue.\n");
|
|
return;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void rwnx_cmd_mgr_deinit(struct rwnx_cmd_mgr *cmd_mgr)
|
|
{
|
|
cmd_mgr->print(cmd_mgr);
|
|
cmd_mgr->drain(cmd_mgr);
|
|
cmd_mgr->print(cmd_mgr);
|
|
memset(cmd_mgr, 0, sizeof(*cmd_mgr));
|
|
}
|
|
|
|
void rwnx_set_cmd_tx(void *dev, struct lmac_msg *msg, uint len)
|
|
{
|
|
struct aic_sdio_dev *sdiodev = (struct aic_sdio_dev *)dev;
|
|
struct aicwf_bus *bus = sdiodev->bus_if;
|
|
u8 *buffer = bus->cmd_buf;
|
|
u16 index = 0;
|
|
|
|
memset(buffer, 0, CMD_BUF_MAX);
|
|
buffer[0] = (len+4) & 0x00ff;
|
|
buffer[1] = ((len+4) >> 8) &0x0f;
|
|
buffer[2] = 0x11;
|
|
if (sdiodev->chipid == PRODUCT_ID_AIC8801 || sdiodev->chipid == PRODUCT_ID_AIC8800DC ||
|
|
sdiodev->chipid == PRODUCT_ID_AIC8800DW)
|
|
buffer[3] = 0x0;
|
|
else if (sdiodev->chipid == PRODUCT_ID_AIC8800D80)
|
|
buffer[3] = crc8_ponl_107(&buffer[0], 3); // crc8
|
|
index += 4;
|
|
//there is a dummy word
|
|
index += 4;
|
|
|
|
//make sure little endian
|
|
put_u16(&buffer[index], msg->id);
|
|
index += 2;
|
|
put_u16(&buffer[index], msg->dest_id);
|
|
index += 2;
|
|
put_u16(&buffer[index], msg->src_id);
|
|
index += 2;
|
|
put_u16(&buffer[index], msg->param_len);
|
|
index += 2;
|
|
memcpy(&buffer[index], (u8 *)msg->param, msg->param_len);
|
|
|
|
aicwf_bus_txmsg(bus, buffer, len + 8);
|
|
}
|
|
|
|
static inline void *rwnx_msg_zalloc(lmac_msg_id_t const id,
|
|
lmac_task_id_t const dest_id,
|
|
lmac_task_id_t const src_id,
|
|
uint16_t const param_len)
|
|
{
|
|
struct lmac_msg *msg;
|
|
gfp_t flags;
|
|
|
|
if (in_softirq())
|
|
flags = GFP_ATOMIC;
|
|
else
|
|
flags = GFP_KERNEL;
|
|
|
|
msg = (struct lmac_msg *)kzalloc(sizeof(struct lmac_msg) + param_len,
|
|
flags);
|
|
if (msg == NULL) {
|
|
printk(KERN_CRIT "%s: msg allocation failed\n", __func__);
|
|
return NULL;
|
|
}
|
|
msg->id = id;
|
|
msg->dest_id = dest_id;
|
|
msg->src_id = src_id;
|
|
msg->param_len = param_len;
|
|
|
|
return msg->param;
|
|
}
|
|
|
|
static void rwnx_msg_free(struct lmac_msg *msg, const void *msg_params)
|
|
{
|
|
kfree(msg);
|
|
}
|
|
|
|
|
|
static int rwnx_send_msg(struct aic_sdio_dev *sdiodev, const void *msg_params,
|
|
int reqcfm, lmac_msg_id_t reqid, void *cfm)
|
|
{
|
|
struct lmac_msg *msg;
|
|
struct rwnx_cmd *cmd;
|
|
bool nonblock;
|
|
int ret = 0;
|
|
|
|
msg = container_of((void *)msg_params, struct lmac_msg, param);
|
|
if (sdiodev->bus_if->state == BUS_DOWN_ST) {
|
|
rwnx_msg_free(msg, msg_params);
|
|
printk("bus is down\n");
|
|
return 0;
|
|
}
|
|
|
|
nonblock = 0;
|
|
cmd = kzalloc(sizeof(struct rwnx_cmd), nonblock ? GFP_ATOMIC : GFP_KERNEL);
|
|
cmd->result = -EINTR;
|
|
cmd->id = msg->id;
|
|
cmd->reqid = reqid;
|
|
cmd->a2e_msg = msg;
|
|
cmd->e2a_msg = cfm;
|
|
if (nonblock)
|
|
cmd->flags = RWNX_CMD_FLAG_NONBLOCK;
|
|
if (reqcfm)
|
|
cmd->flags |= RWNX_CMD_FLAG_REQ_CFM;
|
|
|
|
if (reqcfm) {
|
|
cmd->flags &= ~RWNX_CMD_FLAG_WAIT_ACK; // we don't need ack any more
|
|
ret = sdiodev->cmd_mgr.queue(&sdiodev->cmd_mgr, cmd);
|
|
} else {
|
|
rwnx_set_cmd_tx((void *)(sdiodev), cmd->a2e_msg, sizeof(struct lmac_msg) + cmd->a2e_msg->param_len);
|
|
}
|
|
|
|
if (!reqcfm)
|
|
kfree(cmd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int rwnx_send_dbg_mem_block_write_req(struct aic_sdio_dev *sdiodev, u32 mem_addr,
|
|
u32 mem_size, u32 *mem_data)
|
|
{
|
|
struct dbg_mem_block_write_req *mem_blk_write_req;
|
|
|
|
/* Build the DBG_MEM_BLOCK_WRITE_REQ message */
|
|
mem_blk_write_req = rwnx_msg_zalloc(DBG_MEM_BLOCK_WRITE_REQ, TASK_DBG, DRV_TASK_ID,
|
|
sizeof(struct dbg_mem_block_write_req));
|
|
if (!mem_blk_write_req)
|
|
return -ENOMEM;
|
|
|
|
/* Set parameters for the DBG_MEM_BLOCK_WRITE_REQ message */
|
|
mem_blk_write_req->memaddr = mem_addr;
|
|
mem_blk_write_req->memsize = mem_size;
|
|
memcpy(mem_blk_write_req->memdata, mem_data, mem_size);
|
|
|
|
/* Send the DBG_MEM_BLOCK_WRITE_REQ message to LMAC FW */
|
|
return rwnx_send_msg(sdiodev, mem_blk_write_req, 1, DBG_MEM_BLOCK_WRITE_CFM, NULL);
|
|
}
|
|
|
|
int rwnx_send_dbg_mem_read_req(struct aic_sdio_dev *sdiodev, u32 mem_addr,
|
|
struct dbg_mem_read_cfm *cfm)
|
|
{
|
|
struct dbg_mem_read_req *mem_read_req;
|
|
|
|
/* Build the DBG_MEM_READ_REQ message */
|
|
mem_read_req = rwnx_msg_zalloc(DBG_MEM_READ_REQ, TASK_DBG, DRV_TASK_ID,
|
|
sizeof(struct dbg_mem_read_req));
|
|
if (!mem_read_req)
|
|
return -ENOMEM;
|
|
|
|
/* Set parameters for the DBG_MEM_READ_REQ message */
|
|
mem_read_req->memaddr = mem_addr;
|
|
|
|
/* Send the DBG_MEM_READ_REQ message to LMAC FW */
|
|
return rwnx_send_msg(sdiodev, mem_read_req, 1, DBG_MEM_READ_CFM, cfm);
|
|
}
|
|
|
|
|
|
int rwnx_send_dbg_mem_write_req(struct aic_sdio_dev *sdiodev, u32 mem_addr, u32 mem_data)
|
|
{
|
|
struct dbg_mem_write_req *mem_write_req;
|
|
|
|
/* Build the DBG_MEM_WRITE_REQ message */
|
|
mem_write_req = rwnx_msg_zalloc(DBG_MEM_WRITE_REQ, TASK_DBG, DRV_TASK_ID,
|
|
sizeof(struct dbg_mem_write_req));
|
|
if (!mem_write_req)
|
|
return -ENOMEM;
|
|
|
|
/* Set parameters for the DBG_MEM_WRITE_REQ message */
|
|
mem_write_req->memaddr = mem_addr;
|
|
mem_write_req->memdata = mem_data;
|
|
|
|
/* Send the DBG_MEM_WRITE_REQ message to LMAC FW */
|
|
return rwnx_send_msg(sdiodev, mem_write_req, 1, DBG_MEM_WRITE_CFM, NULL);
|
|
}
|
|
|
|
int rwnx_send_dbg_mem_mask_write_req(struct aic_sdio_dev *sdiodev, u32 mem_addr,
|
|
u32 mem_mask, u32 mem_data)
|
|
{
|
|
struct dbg_mem_mask_write_req *mem_mask_write_req;
|
|
|
|
/* Build the DBG_MEM_MASK_WRITE_REQ message */
|
|
mem_mask_write_req = rwnx_msg_zalloc(DBG_MEM_MASK_WRITE_REQ, TASK_DBG, DRV_TASK_ID,
|
|
sizeof(struct dbg_mem_mask_write_req));
|
|
if (!mem_mask_write_req)
|
|
return -ENOMEM;
|
|
|
|
/* Set parameters for the DBG_MEM_MASK_WRITE_REQ message */
|
|
mem_mask_write_req->memaddr = mem_addr;
|
|
mem_mask_write_req->memmask = mem_mask;
|
|
mem_mask_write_req->memdata = mem_data;
|
|
|
|
/* Send the DBG_MEM_MASK_WRITE_REQ message to LMAC FW */
|
|
return rwnx_send_msg(sdiodev, mem_mask_write_req, 1, DBG_MEM_MASK_WRITE_CFM, NULL);
|
|
}
|
|
|
|
|
|
int rwnx_send_dbg_start_app_req(struct aic_sdio_dev *sdiodev, u32 boot_addr, u32 boot_type, struct dbg_start_app_cfm *start_app_cfm)
|
|
{
|
|
struct dbg_start_app_req *start_app_req;
|
|
|
|
/* Build the DBG_START_APP_REQ message */
|
|
start_app_req = rwnx_msg_zalloc(DBG_START_APP_REQ, TASK_DBG, DRV_TASK_ID,
|
|
sizeof(struct dbg_start_app_req));
|
|
if (!start_app_req) {
|
|
printk("start app nomen\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Set parameters for the DBG_START_APP_REQ message */
|
|
start_app_req->bootaddr = boot_addr;
|
|
start_app_req->boottype = boot_type;
|
|
|
|
/* Send the DBG_START_APP_REQ message to LMAC FW */
|
|
return rwnx_send_msg(sdiodev, start_app_req, 1, DBG_START_APP_CFM, start_app_cfm);
|
|
}
|
|
|
|
static msg_cb_fct dbg_hdlrs[MSG_I(DBG_MAX)] = {
|
|
};
|
|
|
|
static msg_cb_fct *msg_hdlrs[] = {
|
|
[TASK_DBG] = dbg_hdlrs,
|
|
};
|
|
|
|
void rwnx_rx_handle_msg(struct aic_sdio_dev *sdiodev, struct ipc_e2a_msg *msg)
|
|
{
|
|
sdiodev->cmd_mgr.msgind(&sdiodev->cmd_mgr, msg,
|
|
msg_hdlrs[MSG_T(msg->id)][MSG_I(msg->id)]);
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)
|
|
MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver);
|
|
#endif
|
|
|
|
#define MD5(x) x[0],x[1],x[2],x[3],x[4],x[5],x[6],x[7],x[8],x[9],x[10],x[11],x[12],x[13],x[14],x[15]
|
|
#define MD5PINRT "file md5:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\r\n"
|
|
|
|
int rwnx_load_firmware(u32 **fw_buf, const char *name, struct device *device)
|
|
{
|
|
|
|
#ifdef CONFIG_USE_FW_REQUEST
|
|
const struct firmware *fw = NULL;
|
|
u32 *dst = NULL;
|
|
void *buffer=NULL;
|
|
MD5_CTX md5;
|
|
unsigned char decrypt[16];
|
|
int size = 0;
|
|
int ret = 0;
|
|
|
|
printk("%s: request firmware = %s \n", __func__ ,name);
|
|
|
|
|
|
ret = request_firmware(&fw, name, NULL);
|
|
|
|
if (ret < 0) {
|
|
printk("Load %s fail\n", name);
|
|
release_firmware(fw);
|
|
return -1;
|
|
}
|
|
|
|
size = fw->size;
|
|
dst = (u32 *)fw->data;
|
|
|
|
if (size <= 0) {
|
|
printk("wrong size of firmware file\n");
|
|
release_firmware(fw);
|
|
return -1;
|
|
}
|
|
|
|
|
|
buffer = vmalloc(size);
|
|
memset(buffer, 0, size);
|
|
memcpy(buffer, dst, size);
|
|
|
|
*fw_buf = buffer;
|
|
|
|
MD5Init(&md5);
|
|
MD5Update(&md5, (unsigned char *)buffer, size);
|
|
MD5Final(&md5, decrypt);
|
|
printk(MD5PINRT, MD5(decrypt));
|
|
|
|
release_firmware(fw);
|
|
|
|
return size;
|
|
#else
|
|
void *buffer = NULL;
|
|
char *path = NULL;
|
|
struct file *fp = NULL;
|
|
int size = 0, len = 0, i = 0;
|
|
ssize_t rdlen = 0;
|
|
u32 *src = NULL, *dst = NULL;
|
|
MD5_CTX md5;
|
|
unsigned char decrypt[16];
|
|
|
|
#ifdef CONFIG_FIRMWARE_ARRAY
|
|
size = aicwf_get_firmware_array((char*)name, fw_buf);
|
|
printk("%s size:%d \r\n", __func__, size);
|
|
MD5Init(&md5);
|
|
MD5Update(&md5, (unsigned char *)*fw_buf, size);
|
|
MD5Final(&md5, decrypt);
|
|
printk(MD5PINRT, MD5(decrypt));
|
|
|
|
return size;
|
|
#endif
|
|
|
|
/* get the firmware path */
|
|
path = __getname();
|
|
if (!path) {
|
|
*fw_buf = NULL;
|
|
return -1;
|
|
}
|
|
|
|
if(strlen(aic_fw_path) > 0){
|
|
len = snprintf(path, AICBSP_FW_PATH_MAX, "%s/%s", aic_fw_path, name);
|
|
}else{
|
|
len = snprintf(path, AICBSP_FW_PATH_MAX, "%s/%s", AICBSP_FW_PATH, name);
|
|
}
|
|
if (len >= AICBSP_FW_PATH_MAX) {
|
|
printk("%s: %s file's path too long\n", __func__, name);
|
|
*fw_buf = NULL;
|
|
__putname(path);
|
|
return -1;
|
|
}
|
|
|
|
printk("%s :firmware path = %s \n", __func__, path);
|
|
|
|
/* open the firmware file */
|
|
fp = filp_open(path, O_RDONLY, 0);
|
|
if (IS_ERR_OR_NULL(fp)) {
|
|
printk("%s: %s file failed to open\n", __func__, name);
|
|
*fw_buf = NULL;
|
|
__putname(path);
|
|
fp = NULL;
|
|
return -1;
|
|
}
|
|
|
|
size = i_size_read(file_inode(fp));
|
|
if (size <= 0) {
|
|
printk("%s: %s file size invalid %d\n", __func__, name, size);
|
|
*fw_buf = NULL;
|
|
__putname(path);
|
|
filp_close(fp, NULL);
|
|
fp = NULL;
|
|
return -1;
|
|
}
|
|
|
|
/* start to read from firmware file */
|
|
buffer = vmalloc(size);
|
|
|
|
if (!buffer) {
|
|
*fw_buf = NULL;
|
|
__putname(path);
|
|
filp_close(fp, NULL);
|
|
fp = NULL;
|
|
return -1;
|
|
}else{
|
|
memset(buffer, 0, size);
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 13, 16)
|
|
rdlen = kernel_read(fp, buffer, size, &fp->f_pos);
|
|
#else
|
|
rdlen = kernel_read(fp, fp->f_pos, buffer, size);
|
|
#endif
|
|
|
|
if (size != rdlen) {
|
|
printk("%s: %s file rdlen invalid %ld\n", __func__, name, (long int)rdlen);
|
|
*fw_buf = NULL;
|
|
__putname(path);
|
|
filp_close(fp, NULL);
|
|
fp = NULL;
|
|
vfree(buffer);
|
|
buffer = NULL;
|
|
return -1;
|
|
}
|
|
if (rdlen > 0) {
|
|
fp->f_pos += rdlen;
|
|
}
|
|
|
|
/*start to transform the data format*/
|
|
src = (u32 *)buffer;
|
|
dst = (u32 *)vmalloc(size);
|
|
|
|
if (!dst) {
|
|
*fw_buf = NULL;
|
|
__putname(path);
|
|
filp_close(fp, NULL);
|
|
fp = NULL;
|
|
vfree(buffer);
|
|
buffer = NULL;
|
|
return -1;
|
|
}else{
|
|
memset(dst, 0, size);
|
|
}
|
|
|
|
for (i = 0; i < (size/4); i++) {
|
|
dst[i] = src[i];
|
|
}
|
|
|
|
__putname(path);
|
|
filp_close(fp, NULL);
|
|
fp = NULL;
|
|
vfree(buffer);
|
|
buffer = NULL;
|
|
*fw_buf = dst;
|
|
|
|
MD5Init(&md5);
|
|
MD5Update(&md5, (unsigned char *)dst, size);
|
|
MD5Final(&md5, decrypt);
|
|
|
|
printk(MD5PINRT, MD5(decrypt));
|
|
|
|
return size;
|
|
#endif
|
|
}
|
|
|
|
int aicwf_patch_table_load(struct aic_sdio_dev *rwnx_hw, char *filename)
|
|
{
|
|
struct device *dev = rwnx_hw->dev;
|
|
int err = 0;
|
|
unsigned int i = 0, size;
|
|
u32 *dst = NULL;
|
|
u8 *describle;
|
|
u32 fmacfw_patch_tbl_8800dc_u02_describe_size = 124;
|
|
u32 fmacfw_patch_tbl_8800dc_u02_describe_base;//read from patch_tbl
|
|
|
|
/* Copy the file on the Embedded side */
|
|
printk("### Upload %s \n", filename);
|
|
|
|
size = rwnx_load_firmware(&dst, filename,dev);
|
|
if (!dst) {
|
|
printk("No such file or directory\n");
|
|
return -1;
|
|
}
|
|
if (size <= 0) {
|
|
printk("wrong size of firmware file\n");
|
|
dst = NULL;
|
|
err = -1;
|
|
}
|
|
|
|
printk("tbl size = %d \n",size);
|
|
|
|
fmacfw_patch_tbl_8800dc_u02_describe_base = dst[0];
|
|
AICWFDBG(LOGINFO, "FMACFW_PATCH_TBL_8800DC_U02_DESCRIBE_BASE = %x \n",fmacfw_patch_tbl_8800dc_u02_describe_base);
|
|
|
|
if (!err && (i < size)) {
|
|
err=rwnx_send_dbg_mem_block_write_req(rwnx_hw, fmacfw_patch_tbl_8800dc_u02_describe_base, fmacfw_patch_tbl_8800dc_u02_describe_size + 4, dst);
|
|
if(err){
|
|
printk("write describe information fail \n");
|
|
}
|
|
|
|
describle=kzalloc(fmacfw_patch_tbl_8800dc_u02_describe_size,GFP_KERNEL);
|
|
memcpy(describle,&dst[1],fmacfw_patch_tbl_8800dc_u02_describe_size);
|
|
printk("%s",describle);
|
|
kfree(describle);
|
|
describle=NULL;
|
|
}
|
|
|
|
if (!err && (i < size)) {
|
|
for (i =(128/4); i < (size/4); i +=2) {
|
|
printk("patch_tbl: %x %x\n", dst[i], dst[i+1]);
|
|
err = rwnx_send_dbg_mem_write_req(rwnx_hw, dst[i], dst[i+1]);
|
|
}
|
|
if (err) {
|
|
printk("bin upload fail: %x, err:%d\r\n", dst[i], err);
|
|
}
|
|
}
|
|
|
|
if (dst) {
|
|
#ifndef CONFIG_FIRMWARE_ARRAY
|
|
vfree(dst);
|
|
#endif
|
|
dst = NULL;
|
|
}
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
extern char aic_fw_path[200];
|
|
extern int testmode;
|
|
int aicwf_plat_patch_load_8800dc(struct aic_sdio_dev *sdiodev)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (testmode == 0) {
|
|
#if !defined(CONFIG_FPGA_VERIFICATION)
|
|
if (chip_sub_id == 0) {
|
|
printk("u01 is loaing ###############\n");
|
|
ret = rwnx_plat_bin_fw_upload_android(sdiodev, ROM_FMAC_PATCH_ADDR, RWNX_MAC_PATCH_NAME2_8800DC);
|
|
} else if (chip_sub_id == 1) {
|
|
printk("u02 is loaing ###############\n");
|
|
ret = rwnx_plat_bin_fw_upload_android(sdiodev, ROM_FMAC_PATCH_ADDR, RWNX_MAC_PATCH_NAME2_8800DC_U02);
|
|
} else {
|
|
printk("unsupported id: %d\n", chip_sub_id);
|
|
}
|
|
#endif
|
|
} else {
|
|
if (chip_sub_id == 0) {
|
|
ret = rwnx_plat_bin_fw_upload_android(sdiodev, ROM_FMAC_PATCH_ADDR, RWNX_MAC_RF_PATCH_NAME_8800DC);
|
|
}
|
|
if (!ret) {
|
|
ret = rwnx_plat_bin_fw_upload_android(sdiodev, RAM_LMAC_FW_ADDR, RWNX_MAC_FW_RF_BASE_NAME_8800DC);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rwnx_plat_patch_load(struct aic_sdio_dev *sdiodev)
|
|
{
|
|
int ret = 0;
|
|
|
|
RWNX_DBG(RWNX_FN_ENTRY_STR);
|
|
|
|
if(sdiodev->chipid == PRODUCT_ID_AIC8800DC ||
|
|
sdiodev->chipid == PRODUCT_ID_AIC8800DW){
|
|
#if !defined(CONFIG_DPD)
|
|
aicwf_misc_ram_init_8800dc(sdiodev);
|
|
#endif
|
|
printk("rwnx_plat_patch_loading\n");
|
|
ret = aicwf_plat_patch_load_8800dc(sdiodev);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int rwnx_plat_bin_fw_upload_android(struct aic_sdio_dev *sdiodev, u32 fw_addr,
|
|
const char *filename)
|
|
{
|
|
struct device *dev = sdiodev->dev;
|
|
unsigned int i = 0;
|
|
int size;
|
|
u32 *dst = NULL;
|
|
int err = 0;
|
|
|
|
printk("%s\n",__func__);
|
|
|
|
/* load aic firmware */
|
|
size = rwnx_load_firmware(&dst, filename, dev);
|
|
if (size <= 0) {
|
|
printk("wrong size of firmware file\n");
|
|
#ifndef CONFIG_FIRMWARE_ARRAY
|
|
vfree(dst);
|
|
#endif
|
|
dst = NULL;
|
|
return -1;
|
|
}
|
|
|
|
/* Copy the file on the Embedded side */
|
|
if (size > 1024) {// > 1KB data
|
|
for (i = 0; i < (size - 1024); i += 1024) {//each time write 1KB
|
|
err = rwnx_send_dbg_mem_block_write_req(sdiodev, fw_addr + i, 1024, dst + i / 4);
|
|
if (err) {
|
|
printk("bin upload fail: %x, err:%d\r\n", fw_addr + i, err);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!err && (i < size)) {// <1KB data
|
|
err = rwnx_send_dbg_mem_block_write_req(sdiodev, fw_addr + i, size - i, dst + i / 4);
|
|
if (err) {
|
|
printk("bin upload fail: %x, err:%d\r\n", fw_addr + i, err);
|
|
}
|
|
}
|
|
|
|
if (dst) {
|
|
#ifndef CONFIG_FIRMWARE_ARRAY
|
|
vfree(dst);
|
|
#endif
|
|
dst = NULL;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int aicbt_patch_table_free(struct aicbt_patch_table **head)
|
|
{
|
|
struct aicbt_patch_table *p = *head, *n = NULL;
|
|
while (p) {
|
|
n = p->next;
|
|
vfree(p->name);
|
|
vfree(p->data);
|
|
vfree(p);
|
|
p = n;
|
|
}
|
|
*head = NULL;
|
|
return 0;
|
|
}
|
|
|
|
struct aicbt_patch_table *aicbt_patch_table_alloc(const char *filename)
|
|
{
|
|
uint8_t *rawdata = NULL, *p;
|
|
int size;
|
|
struct aicbt_patch_table *head = NULL, *new = NULL, *cur = NULL;
|
|
|
|
/* load aic firmware */
|
|
size = rwnx_load_firmware((u32 **)&rawdata, filename, NULL);
|
|
if (size <= 0) {
|
|
printk("wrong size of firmware file\n");
|
|
goto err;
|
|
}
|
|
|
|
p = rawdata;
|
|
if (memcmp(p, AICBT_PT_TAG, sizeof(AICBT_PT_TAG) < 16 ? sizeof(AICBT_PT_TAG) : 16)) {
|
|
printk("TAG err\n");
|
|
goto err;
|
|
}
|
|
p += 16;
|
|
|
|
while (p - rawdata < size) {
|
|
new = (struct aicbt_patch_table *)vmalloc(sizeof(struct aicbt_patch_table));
|
|
memset(new, 0, sizeof(struct aicbt_patch_table));
|
|
if (head == NULL) {
|
|
head = new;
|
|
cur = new;
|
|
} else {
|
|
cur->next = new;
|
|
cur = cur->next;
|
|
}
|
|
|
|
cur->name = (char *)vmalloc(sizeof(char) * 16);
|
|
memset(cur->name, 0, sizeof(char) * 16);
|
|
memcpy(cur->name, p, 16);
|
|
p += 16;
|
|
|
|
cur->type = *(uint32_t *)p;
|
|
p += 4;
|
|
|
|
cur->len = *(uint32_t *)p;
|
|
p += 4;
|
|
|
|
if((cur->type ) >= 1000 ) {//Temp Workaround
|
|
cur->len = 0;
|
|
}else{
|
|
if(cur->len > 0){
|
|
cur->data = (uint32_t *)vmalloc(sizeof(uint8_t) * cur->len * 8);
|
|
memset(cur->data, 0, sizeof(uint8_t) * cur->len * 8);
|
|
memcpy(cur->data, p, cur->len * 8);
|
|
p += cur->len * 8;
|
|
}
|
|
}
|
|
}
|
|
#ifndef CONFIG_FIRMWARE_ARRAY
|
|
vfree(rawdata);
|
|
#endif
|
|
return head;
|
|
|
|
err:
|
|
aicbt_patch_table_free(&head);
|
|
if (rawdata)
|
|
vfree(rawdata);
|
|
return NULL;
|
|
}
|
|
int aicbt_patch_info_unpack(struct aicbt_patch_info_t *patch_info, struct aicbt_patch_table *head_t)
|
|
{
|
|
if (AICBT_PT_INF == head_t->type) {
|
|
patch_info->info_len = head_t->len;
|
|
if(patch_info->info_len == 0)
|
|
return 0;
|
|
memcpy(&patch_info->adid_addrinf, head_t->data, patch_info->info_len * sizeof(uint32_t) * 2);
|
|
}
|
|
return 0;
|
|
}
|
|
int aicbt_patch_trap_data_load(struct aic_sdio_dev *sdiodev, struct aicbt_patch_table *head)
|
|
{
|
|
struct aicbt_patch_info_t patch_info = {
|
|
.info_len = 0,
|
|
.adid_addrinf = 0,
|
|
.addr_adid = 0,
|
|
.patch_addrinf = 0,
|
|
.addr_patch = 0,
|
|
.reset_addr = 0,
|
|
.reset_val = 0,
|
|
.adid_flag_addr = 0,
|
|
.adid_flag = 0,
|
|
};
|
|
if(head == NULL){
|
|
return -1;
|
|
}
|
|
|
|
if(sdiodev->chipid == PRODUCT_ID_AIC8801){
|
|
patch_info.addr_adid = FW_RAM_ADID_BASE_ADDR;
|
|
patch_info.addr_patch = FW_RAM_PATCH_BASE_ADDR;
|
|
}
|
|
else if(sdiodev->chipid == PRODUCT_ID_AIC8800DC){
|
|
if(aicbsp_info.chip_rev == CHIP_REV_U01){
|
|
patch_info.addr_adid = RAM_8800DC_U01_ADID_ADDR;
|
|
}else if(aicbsp_info.chip_rev == CHIP_REV_U02){
|
|
patch_info.addr_adid = RAM_8800DC_U02_ADID_ADDR;
|
|
}
|
|
patch_info.addr_patch = RAM_8800DC_FW_PATCH_ADDR;
|
|
aicbt_patch_info_unpack(&patch_info, head);
|
|
if(patch_info.reset_addr == 0) {
|
|
patch_info.reset_addr = FW_RESET_START_ADDR;
|
|
patch_info.reset_val = FW_RESET_START_VAL;
|
|
patch_info.adid_flag_addr = FW_ADID_FLAG_ADDR;
|
|
patch_info.adid_flag = FW_ADID_FLAG_VAL;
|
|
if (rwnx_send_dbg_mem_write_req(sdiodev, patch_info.reset_addr, patch_info.reset_val))
|
|
return -1;
|
|
if (rwnx_send_dbg_mem_write_req(sdiodev, patch_info.adid_flag_addr, patch_info.adid_flag))
|
|
return -1;
|
|
}
|
|
} else if(sdiodev->chipid == PRODUCT_ID_AIC8800D80){
|
|
if (aicbsp_info.chip_rev == CHIP_REV_U01) {
|
|
patch_info.addr_adid = FW_RAM_ADID_BASE_ADDR_8800D80;
|
|
patch_info.addr_patch = FW_RAM_PATCH_BASE_ADDR_8800D80;
|
|
} else if (aicbsp_info.chip_rev == CHIP_REV_U02 || aicbsp_info.chip_rev == CHIP_REV_U03) {
|
|
patch_info.addr_adid = FW_RAM_ADID_BASE_ADDR_8800D80_U02;
|
|
patch_info.addr_patch = FW_RAM_PATCH_BASE_ADDR_8800D80_U02;
|
|
}
|
|
aicbt_patch_info_unpack(&patch_info, head);
|
|
if(patch_info.info_len == 0) {
|
|
printk("%s, aicbt_patch_info_unpack fail\n", __func__);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (rwnx_plat_bin_fw_upload_android(sdiodev, patch_info.addr_adid, aicbsp_firmware_list[aicbsp_info.cpmode].bt_adid))
|
|
return -1;
|
|
if (rwnx_plat_bin_fw_upload_android(sdiodev, patch_info.addr_patch, aicbsp_firmware_list[aicbsp_info.cpmode].bt_patch))
|
|
return -1;
|
|
return 0;
|
|
|
|
}
|
|
|
|
static struct aicbt_info_t aicbt_info = {
|
|
.btmode = AICBT_BTMODE_DEFAULT,
|
|
.btport = AICBT_BTPORT_DEFAULT,
|
|
.uart_baud = AICBT_UART_BAUD_DEFAULT,
|
|
.uart_flowctrl = AICBT_UART_FC_DEFAULT,
|
|
.lpm_enable = AICBT_LPM_ENABLE_DEFAULT,
|
|
.txpwr_lvl = AICBT_TXPWR_LVL_DEFAULT,
|
|
};
|
|
|
|
struct aicbt_info_t aicbt_info_8800dc = {
|
|
.btmode = AICBT_BTMODE_BT_WIFI_COMBO,
|
|
.btport = AICBT_BTPORT_DEFAULT,
|
|
.uart_baud = AICBT_UART_BAUD_DEFAULT,
|
|
.uart_flowctrl = AICBT_UART_FC_DEFAULT,
|
|
.lpm_enable = AICBT_LPM_ENABLE_DEFAULT,
|
|
.txpwr_lvl = AICBT_TXPWR_LVL_8800dc,
|
|
};
|
|
|
|
int aicbt_patch_table_load(struct aic_sdio_dev *sdiodev, struct aicbt_patch_table *head)
|
|
{
|
|
struct aicbt_patch_table *p;
|
|
int ret = 0, i;
|
|
uint32_t *data = NULL;
|
|
if(head == NULL){
|
|
return -1;
|
|
}
|
|
if(sdiodev->chipid == PRODUCT_ID_AIC8801 || sdiodev->chipid == PRODUCT_ID_AIC8800D80){
|
|
if (sdiodev->chipid == PRODUCT_ID_AIC8800D80) {
|
|
//aicbt_info.btmode = AICBT_BTMODE_BT_ONLY_COANT;
|
|
aicbt_info.txpwr_lvl = AICBT_TXPWR_LVL_8800d80;
|
|
}
|
|
for (p = head; p != NULL; p = p->next) {
|
|
data = p->data;
|
|
if (AICBT_PT_BTMODE == p->type) {
|
|
*(data + 1) = aicbsp_info.hwinfo < 0;
|
|
*(data + 3) = aicbsp_info.hwinfo;
|
|
*(data + 5) = aicbsp_info.cpmode;
|
|
|
|
*(data + 7) = aicbt_info.btmode;
|
|
*(data + 9) = aicbt_info.btport;
|
|
*(data + 11) = aicbt_info.uart_baud;
|
|
*(data + 13) = aicbt_info.uart_flowctrl;
|
|
*(data + 15) = aicbt_info.lpm_enable;
|
|
*(data + 17) = aicbt_info.txpwr_lvl;
|
|
|
|
printk("%s bt btmode:%d \r\n", __func__, aicbt_info.btmode);
|
|
printk("%s bt uart_baud:%d \r\n", __func__, aicbt_info.uart_baud);
|
|
printk("%s bt uart_flowctrl:%d \r\n", __func__, aicbt_info.uart_flowctrl);
|
|
printk("%s bt lpm_enable:%d \r\n", __func__, aicbt_info.lpm_enable);
|
|
printk("%s bt tx_pwr:%d \r\n", __func__, aicbt_info.txpwr_lvl);
|
|
}
|
|
|
|
if (AICBT_PT_VER == p->type) {
|
|
printk("aicbsp: bt patch version: %s\n", (char *)p->data);
|
|
continue;
|
|
}
|
|
|
|
for (i = 0; i < p->len; i++) {
|
|
ret = rwnx_send_dbg_mem_write_req(sdiodev, *data, *(data + 1));
|
|
if (ret != 0)
|
|
return ret;
|
|
data += 2;
|
|
}
|
|
if (p->type == AICBT_PT_PWRON)
|
|
udelay(500);
|
|
}
|
|
}
|
|
else if(sdiodev->chipid == PRODUCT_ID_AIC8800DC){
|
|
for (p = head; p != NULL; p = p->next) {
|
|
data = p->data;
|
|
if (AICBT_PT_BTMODE == p->type) {
|
|
*(data + 1) = aicbsp_info.hwinfo < 0;
|
|
*(data + 3) = aicbsp_info.hwinfo;
|
|
*(data + 5) = aicbsp_info.cpmode;
|
|
|
|
*(data + 7) = aicbt_info_8800dc.btmode;
|
|
*(data + 9) = aicbt_info_8800dc.btport;
|
|
*(data + 11) = aicbt_info_8800dc.uart_baud;
|
|
*(data + 13) = aicbt_info_8800dc.uart_flowctrl;
|
|
*(data + 15) = aicbt_info_8800dc.lpm_enable;
|
|
*(data + 17) = aicbt_info_8800dc.txpwr_lvl;
|
|
|
|
printk("%s bt uart_baud:%d \r\n", __func__, aicbt_info_8800dc.uart_baud);
|
|
printk("%s bt uart_flowctrl:%d \r\n", __func__, aicbt_info_8800dc.uart_flowctrl);
|
|
printk("%s bt lpm_enable:%d \r\n", __func__, aicbt_info_8800dc.lpm_enable);
|
|
printk("%s bt tx_pwr:%d \r\n", __func__, aicbt_info_8800dc.txpwr_lvl);
|
|
}
|
|
if (AICBT_PT_INF == p->type) {
|
|
continue;
|
|
}
|
|
printk("########## p->type = %d \n",p->type);
|
|
printk("AICBT_PT_VER = %d \n",AICBT_PT_VER);
|
|
if (AICBT_PT_VER == p->type) {
|
|
printk("aicbsp: bt patch version: %s\n", (char *)p->data);
|
|
continue;
|
|
}
|
|
|
|
for (i = 0; i < p->len; i++) {
|
|
ret = rwnx_send_dbg_mem_write_req(sdiodev, *data, *(data + 1));
|
|
if (ret != 0)
|
|
return ret;
|
|
data += 2;
|
|
}
|
|
if (p->type == AICBT_PT_PWRON)
|
|
udelay(500);
|
|
}
|
|
}
|
|
///aicbt_patch_table_free(&head);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int aicbt_init(struct aic_sdio_dev *sdiodev)
|
|
{
|
|
int ret = 0;
|
|
struct aicbt_patch_table *head = aicbt_patch_table_alloc(aicbsp_firmware_list[aicbsp_info.cpmode].bt_table);
|
|
if (head == NULL){
|
|
printk("aicbt_patch_table_alloc fail\n");
|
|
return -1;
|
|
}
|
|
|
|
if (aicbt_patch_trap_data_load(sdiodev, head)) {
|
|
printk("aicbt_patch_trap_data_load fail\n");
|
|
ret = -1;
|
|
goto err;
|
|
}
|
|
|
|
if (aicbt_patch_table_load(sdiodev, head)) {
|
|
printk("aicbt_patch_table_load fail\n");
|
|
ret = -1;
|
|
goto err;
|
|
}
|
|
|
|
err:
|
|
aicbt_patch_table_free(&head);
|
|
return ret;
|
|
}
|
|
|
|
static int aicwifi_start_from_bootrom(struct aic_sdio_dev *sdiodev)
|
|
{
|
|
int ret = 0;
|
|
|
|
/* memory access */
|
|
const u32 fw_addr = RAM_FMAC_FW_ADDR;
|
|
struct dbg_start_app_cfm start_app_cfm;
|
|
|
|
/* fw start */
|
|
ret = rwnx_send_dbg_start_app_req(sdiodev, fw_addr, HOST_START_APP_AUTO, &start_app_cfm);
|
|
if (ret) {
|
|
return -1;
|
|
}
|
|
aicbsp_info.hwinfo_r = start_app_cfm.bootstatus & 0xFF;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int start_from_bootrom_8800DC(struct aic_sdio_dev *sdiodev)
|
|
{
|
|
int ret = 0;
|
|
u32 rd_addr;
|
|
u32 fw_addr;
|
|
u32 boot_type;
|
|
struct dbg_mem_read_cfm rd_cfm;
|
|
|
|
/* memory access */
|
|
if(testmode == 1){
|
|
rd_addr = RAM_LMAC_FW_ADDR;
|
|
fw_addr = RAM_LMAC_FW_ADDR;
|
|
}
|
|
else{
|
|
rd_addr = RAM_FMAC_FW_ADDR;
|
|
fw_addr = RAM_FMAC_FW_ADDR;
|
|
}
|
|
|
|
AICWFDBG(LOGINFO, "Read FW mem: %08x\n", rd_addr);
|
|
if ((ret = rwnx_send_dbg_mem_read_req(sdiodev, rd_addr, &rd_cfm))) {
|
|
return -1;
|
|
}
|
|
AICWFDBG(LOGINFO, "cfm: [%08x] = %08x\n", rd_cfm.memaddr, rd_cfm.memdata);
|
|
|
|
if (testmode == 0) {
|
|
boot_type = HOST_START_APP_DUMMY;
|
|
} else {
|
|
boot_type = HOST_START_APP_AUTO;
|
|
}
|
|
/* fw start */
|
|
AICWFDBG(LOGINFO, "Start app: %08x, %d\n", fw_addr, boot_type);
|
|
if ((ret = rwnx_send_dbg_start_app_req(sdiodev, fw_addr, boot_type ,NULL))) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
u32 adaptivity_patch_tbl[][2] = {
|
|
{0x0004, 0x0000320A}, //linkloss_thd
|
|
{0x0094, 0x00000000}, //ac_param_conf
|
|
{0x00F8, 0x00010138}, //tx_adaptivity_en
|
|
};
|
|
|
|
u32 patch_tbl[][2] = {
|
|
#if !defined(CONFIG_LINK_DET_5G)
|
|
{0x0104, 0x00000000}, //link_det_5g
|
|
#endif
|
|
#if defined(CONFIG_MCU_MESSAGE)
|
|
{0x004c, 0x0000004B}, //pkt_cnt_1724=0x4B
|
|
{0x0050, 0x0011FC00}, //ipc_base_addr
|
|
#endif
|
|
};
|
|
|
|
u32 syscfg_tbl_masked[][3] = {
|
|
{0x40506024, 0x000000FF, 0x000000DF}, // for clk gate lp_level
|
|
};
|
|
|
|
u32 rf_tbl_masked[][3] = {
|
|
{0x40344058, 0x00800000, 0x00000000},// pll trx
|
|
};
|
|
|
|
static int aicwifi_sys_config(struct aic_sdio_dev *sdiodev)
|
|
{
|
|
int ret, cnt;
|
|
int syscfg_num = sizeof(syscfg_tbl_masked) / sizeof(u32) / 3;
|
|
for (cnt = 0; cnt < syscfg_num; cnt++) {
|
|
ret = rwnx_send_dbg_mem_mask_write_req(sdiodev,
|
|
syscfg_tbl_masked[cnt][0], syscfg_tbl_masked[cnt][1], syscfg_tbl_masked[cnt][2]);
|
|
if (ret) {
|
|
printk("%x mask write fail: %d\n", syscfg_tbl_masked[cnt][0], ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = rwnx_send_dbg_mem_mask_write_req(sdiodev,
|
|
rf_tbl_masked[0][0], rf_tbl_masked[0][1], rf_tbl_masked[0][2]);
|
|
if (ret) {
|
|
printk("rf config %x write fail: %d\n", rf_tbl_masked[0][0], ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aicwifi_patch_config(struct aic_sdio_dev *sdiodev)
|
|
{
|
|
const u32 rd_patch_addr = RAM_FMAC_FW_ADDR + 0x0180;
|
|
u32 config_base;
|
|
uint32_t start_addr = 0x1e6000;
|
|
u32 patch_addr = start_addr;
|
|
u32 patch_num = sizeof(patch_tbl)/4;
|
|
struct dbg_mem_read_cfm rd_patch_addr_cfm;
|
|
u32 patch_addr_reg = 0x1e5318;
|
|
u32 patch_num_reg = 0x1e531c;
|
|
int ret = 0;
|
|
u16 cnt = 0;
|
|
int tmp_cnt = 0;
|
|
int adap_patch_num = 0;
|
|
|
|
if (aicbsp_info.cpmode == AICBSP_CPMODE_TEST) {
|
|
patch_addr_reg = 0x1e5304;
|
|
patch_num_reg = 0x1e5308;
|
|
}
|
|
|
|
ret = rwnx_send_dbg_mem_read_req(sdiodev, rd_patch_addr, &rd_patch_addr_cfm);
|
|
if (ret) {
|
|
printk("patch rd fail\n");
|
|
return ret;
|
|
}
|
|
|
|
config_base = rd_patch_addr_cfm.memdata;
|
|
|
|
ret = rwnx_send_dbg_mem_write_req(sdiodev, patch_addr_reg, patch_addr);
|
|
if (ret) {
|
|
printk("0x%x write fail\n", patch_addr_reg);
|
|
return ret;
|
|
}
|
|
|
|
if(adap_test){
|
|
printk("%s for adaptivity test \r\n", __func__);
|
|
adap_patch_num = sizeof(adaptivity_patch_tbl)/4;
|
|
ret = rwnx_send_dbg_mem_write_req(sdiodev, patch_num_reg, patch_num + adap_patch_num);
|
|
}else{
|
|
ret = rwnx_send_dbg_mem_write_req(sdiodev, patch_num_reg, patch_num);
|
|
}
|
|
if (ret) {
|
|
printk("0x%x write fail\n", patch_num_reg);
|
|
return ret;
|
|
}
|
|
|
|
for (cnt = 0; cnt < patch_num/2; cnt += 1) {
|
|
ret = rwnx_send_dbg_mem_write_req(sdiodev, start_addr+8*cnt, patch_tbl[cnt][0]+config_base);
|
|
if (ret) {
|
|
printk("%x write fail\n", start_addr+8*cnt);
|
|
return ret;
|
|
}
|
|
|
|
ret = rwnx_send_dbg_mem_write_req(sdiodev, start_addr+8*cnt+4, patch_tbl[cnt][1]);
|
|
if (ret) {
|
|
printk("%x write fail\n", start_addr+8*cnt+4);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
tmp_cnt = cnt;
|
|
|
|
if(adap_test){
|
|
for(cnt = 0; cnt < adap_patch_num/2; cnt+=1)
|
|
{
|
|
if((ret = rwnx_send_dbg_mem_write_req(sdiodev, start_addr+8*(cnt+tmp_cnt), adaptivity_patch_tbl[cnt][0]+config_base))) {
|
|
printk("%x write fail\n", start_addr+8*cnt);
|
|
}
|
|
|
|
if((ret = rwnx_send_dbg_mem_write_req(sdiodev, start_addr+8*(cnt+tmp_cnt)+4, adaptivity_patch_tbl[cnt][1]))) {
|
|
printk("%x write fail\n", start_addr+8*cnt+4);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int aicwifi_init(struct aic_sdio_dev *sdiodev)
|
|
{
|
|
if(sdiodev->chipid == PRODUCT_ID_AIC8801){
|
|
if (rwnx_plat_bin_fw_upload_android(sdiodev, RAM_FMAC_FW_ADDR, aicbsp_firmware_list[aicbsp_info.cpmode].wl_fw)) {
|
|
printk("download wifi fw fail\n");
|
|
return -1;
|
|
}
|
|
|
|
if (aicwifi_patch_config(sdiodev)) {
|
|
printk("aicwifi_patch_config fail\n");
|
|
return -1;
|
|
}
|
|
|
|
if (aicwifi_sys_config(sdiodev)) {
|
|
printk("aicwifi_sys_config fail\n");
|
|
return -1;
|
|
}
|
|
|
|
if (aicwifi_start_from_bootrom(sdiodev)) {
|
|
printk("wifi start fail\n");
|
|
return -1;
|
|
}
|
|
}else if (sdiodev->chipid == PRODUCT_ID_AIC8800DC || sdiodev->chipid == PRODUCT_ID_AIC8800DW){
|
|
printk("############ aicwifi_init begin \n");
|
|
|
|
system_config_8800dc(sdiodev);
|
|
printk("############ system_config_8800dc done\n");
|
|
|
|
rwnx_plat_patch_load(sdiodev);
|
|
printk("############ rwnx_plat_patch_load done\n");
|
|
|
|
//rwnx_plat_userconfig_load(sdiodev);
|
|
|
|
aicwf_patch_config_8800dc(sdiodev);
|
|
printk("############ aicwf_patch_config_8800dc done\n");
|
|
|
|
start_from_bootrom_8800DC(sdiodev);
|
|
}else if(sdiodev->chipid == PRODUCT_ID_AIC8800D80){
|
|
if (rwnx_plat_bin_fw_upload_android(sdiodev, RAM_FMAC_FW_ADDR, aicbsp_firmware_list[aicbsp_info.cpmode].wl_fw)) {
|
|
printk("8800d80 download wifi fw fail\n");
|
|
return -1;
|
|
}
|
|
|
|
if (aicwifi_patch_config_8800d80(sdiodev)) {
|
|
printk("aicwifi_patch_config_8800d80 fail\n");
|
|
return -1;
|
|
}
|
|
|
|
if (aicwifi_sys_config_8800d80(sdiodev)) {
|
|
printk("aicwifi_patch_config_8800d80 fail\n");
|
|
return -1;
|
|
}
|
|
|
|
if (aicwifi_start_from_bootrom(sdiodev)) {
|
|
printk("8800d80 wifi start fail\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_GPIO_WAKEUP
|
|
if (aicwf_sdio_writeb(sdiodev, sdiodev->sdio_reg.wakeup_reg, 4)) {
|
|
sdio_err("reg:%d write failed!\n", sdiodev->sdio_reg.wakeup_reg);
|
|
return -1;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
u32 aicbsp_syscfg_tbl[][2] = {
|
|
{0x40500014, 0x00000101}, // 1)
|
|
{0x40500018, 0x00000109}, // 2)
|
|
{0x40500004, 0x00000010}, // 3) the order should not be changed
|
|
|
|
// def CONFIG_PMIC_SETTING
|
|
// U02 bootrom only
|
|
{0x40040000, 0x00001AC8}, // 1) fix panic
|
|
{0x40040084, 0x00011580},
|
|
{0x40040080, 0x00000001},
|
|
{0x40100058, 0x00000000},
|
|
|
|
{0x50000000, 0x03220204}, // 2) pmic interface init
|
|
{0x50019150, 0x00000002}, // 3) for 26m xtal, set div1
|
|
{0x50017008, 0x00000000}, // 4) stop wdg
|
|
};
|
|
|
|
static int aicbsp_system_config(struct aic_sdio_dev *sdiodev)
|
|
{
|
|
int syscfg_num = sizeof(aicbsp_syscfg_tbl) / sizeof(u32) / 2;
|
|
int ret, cnt;
|
|
for (cnt = 0; cnt < syscfg_num; cnt++) {
|
|
ret = rwnx_send_dbg_mem_write_req(sdiodev, aicbsp_syscfg_tbl[cnt][0], aicbsp_syscfg_tbl[cnt][1]);
|
|
if (ret) {
|
|
sdio_err("%x write fail: %d\n", aicbsp_syscfg_tbl[cnt][0], ret);
|
|
return ret;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
int aicbsp_platform_init(struct aic_sdio_dev *sdiodev)
|
|
{
|
|
rwnx_cmd_mgr_init(&sdiodev->cmd_mgr);
|
|
sdiodev->cmd_mgr.sdiodev = (void *)sdiodev;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void aicbsp_platform_deinit(struct aic_sdio_dev *sdiodev)
|
|
{
|
|
(void)sdiodev;
|
|
}
|
|
|
|
int aicbsp_driver_fw_init(struct aic_sdio_dev *sdiodev)
|
|
{
|
|
u32 mem_addr;
|
|
struct dbg_mem_read_cfm rd_mem_addr_cfm;
|
|
u32 btenable = 0;
|
|
|
|
mem_addr = 0x40500000;
|
|
|
|
testmode = aicbsp_info.cpmode;
|
|
|
|
if(sdiodev->chipid == PRODUCT_ID_AIC8801){
|
|
|
|
if (rwnx_send_dbg_mem_read_req(sdiodev, mem_addr, &rd_mem_addr_cfm))
|
|
return -1;
|
|
|
|
aicbsp_info.chip_rev = (u8)(rd_mem_addr_cfm.memdata >> 16);
|
|
btenable = 1;
|
|
|
|
if (aicbsp_info.chip_rev != CHIP_REV_U02 &&
|
|
aicbsp_info.chip_rev != CHIP_REV_U03 &&
|
|
aicbsp_info.chip_rev != CHIP_REV_U04) {
|
|
pr_err("aicbsp: %s, unsupport chip rev: %d\n", __func__, aicbsp_info.chip_rev);
|
|
return -1;
|
|
}
|
|
|
|
if (aicbsp_info.chip_rev != CHIP_REV_U02)
|
|
aicbsp_firmware_list = fw_u03;
|
|
|
|
if (aicbsp_system_config(sdiodev))
|
|
return -1;
|
|
}
|
|
else if (sdiodev->chipid == PRODUCT_ID_AIC8800DC || sdiodev->chipid == PRODUCT_ID_AIC8800DW){
|
|
if (rwnx_send_dbg_mem_read_req(sdiodev, mem_addr, &rd_mem_addr_cfm))
|
|
return -1;
|
|
|
|
aicbsp_info.chip_rev = (u8)(rd_mem_addr_cfm.memdata >> 16);
|
|
|
|
btenable = ((rd_mem_addr_cfm.memdata >> 26) & 0x1);
|
|
AICWFDBG(LOGINFO, "btenable = %d \n",btenable);
|
|
|
|
if(btenable == 0){
|
|
sdiodev->chipid = PRODUCT_ID_AIC8800DW;
|
|
AICWFDBG(LOGINFO, "AIC8800DC change to AIC8800DW \n");
|
|
}
|
|
|
|
if (aicbsp_info.chip_rev != CHIP_REV_U01 &&
|
|
aicbsp_info.chip_rev != CHIP_REV_U02 &&
|
|
aicbsp_info.chip_rev != CHIP_REV_U03 &&
|
|
aicbsp_info.chip_rev != CHIP_REV_U04) {
|
|
pr_err("aicbsp: %s, unsupport chip rev: %d\n", __func__, aicbsp_info.chip_rev);
|
|
return -1;
|
|
}
|
|
if(aicbsp_info.chip_rev == CHIP_REV_U01){
|
|
aicbsp_firmware_list = fw_8800dc_u01;
|
|
}else{
|
|
aicbsp_firmware_list = fw_8800dc_u02;
|
|
}
|
|
}
|
|
else if(sdiodev->chipid == PRODUCT_ID_AIC8800D80){
|
|
|
|
if (rwnx_send_dbg_mem_read_req(sdiodev, mem_addr, &rd_mem_addr_cfm))
|
|
return -1;
|
|
|
|
aicbsp_info.chip_rev = (u8)(rd_mem_addr_cfm.memdata >> 16);
|
|
btenable = 1;
|
|
|
|
if (aicbsp_info.chip_rev == CHIP_REV_U01)
|
|
aicbsp_firmware_list = fw_8800d80_u01;
|
|
if (aicbsp_info.chip_rev == CHIP_REV_U02 || aicbsp_info.chip_rev == CHIP_REV_U03)
|
|
aicbsp_firmware_list = fw_8800d80_u02;
|
|
if (aicbsp_system_config_8800d80(sdiodev))
|
|
return -1;
|
|
}
|
|
|
|
AICWFDBG(LOGINFO, "aicbsp: %s, chip rev: %d\n", __func__, aicbsp_info.chip_rev);
|
|
|
|
#ifndef CONFIG_MCU_MESSAGE
|
|
if(btenable == 1){
|
|
if (aicbt_init(sdiodev))
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
if (aicwifi_init(sdiodev))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int aicbsp_get_feature(struct aicbsp_feature_t *feature, char *fw_path)
|
|
{
|
|
if (aicbsp_sdiodev->chipid == PRODUCT_ID_AIC8801 ||
|
|
aicbsp_sdiodev->chipid == PRODUCT_ID_AIC8800DC ||
|
|
aicbsp_sdiodev->chipid == PRODUCT_ID_AIC8800DW){
|
|
feature->sdio_clock = FEATURE_SDIO_CLOCK;
|
|
}else if (aicbsp_sdiodev->chipid == PRODUCT_ID_AIC8800D80){
|
|
feature->sdio_clock = FEATURE_SDIO_CLOCK_V3;
|
|
}
|
|
feature->sdio_phase = FEATURE_SDIO_PHASE;
|
|
feature->hwinfo = aicbsp_info.hwinfo;
|
|
feature->fwlog_en = aicbsp_info.fwlog_en;
|
|
if(fw_path != NULL){
|
|
sprintf(fw_path,"%s", AICBSP_FW_PATH);
|
|
}
|
|
sdio_dbg("%s, set FEATURE_SDIO_CLOCK %d MHz\n", __func__, feature->sdio_clock/1000000);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(aicbsp_get_feature);
|