/*
|
* Copyright (C) 2017 Spreadtrum Communications Inc.
|
* This software is licensed under the terms of the GNU General Public
|
* License version 2, as published by the Free Software Foundation, and
|
* may be copied, distributed, and modified under those terms.
|
*
|
* This program is distributed in the hope that it will be useful,
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* GNU General Public License for more details.
|
*/
|
#include <wcn_bus.h>
|
|
#include "wcn_integrate.h"
|
#include "wcn_sipc.h"
|
#include "wcn_txrx.h"
|
|
#define SIPC_WCN_DST 3
|
|
#define SIPC_TYPE_SBUF 0
|
#define SIPC_TYPE_SBLOCK 1
|
|
#define SIPC_CHN_ATCMD 4
|
#define SIPC_CHN_LOOPCHECK 11
|
#define SIPC_CHN_ASSERT 12
|
#define SIPC_CHN_LOG 5
|
#define SIPC_CHN_BT 4
|
#define SIPC_CHN_FM 4
|
#define SIPC_CHN_WIFI_CMD 7
|
#define SIPC_CHN_WIFI_DATA0 8
|
#define SIPC_CHN_WIFI_DATA1 9
|
|
#define INIT_SIPC_CHN_SBUF(idx, channel, bid,\
|
blen, bnum, tbsize, rbsize)\
|
{.index = idx, .chntype = SIPC_TYPE_SBUF,\
|
.chn = channel, .dst = SIPC_WCN_DST, .sbuf.bufid = bid,\
|
.sbuf.len = blen, .sbuf.bufnum = bnum,\
|
.sbuf.txbufsize = tbsize, .sbuf.rxbufsize = rbsize}
|
|
#define INIT_SIPC_CHN_SBLOCK(idx, channel,\
|
tbnum, tbsize, rbnum, rbsize)\
|
{.index = idx, .chntype = SIPC_TYPE_SBLOCK,\
|
.chn = channel, .dst = SIPC_WCN_DST,\
|
.sblk.txblocknum = tbnum, .sblk.txblocksize = tbsize,\
|
.sblk.rxblocknum = rbnum, .sblk.rxblocksize = rbsize}
|
|
static struct wcn_sipc_info_t g_sipc_info = {0};
|
|
/* sipc channel info */
|
static struct sipc_chn_info g_sipc_chn[SIPC_CHN_NUM] = {
|
INIT_SIPC_CHN_SBUF(SIPC_ATCMD_TX, SIPC_CHN_ATCMD,
|
5, 128, 0, 0x2400, 0x2400),
|
INIT_SIPC_CHN_SBUF(SIPC_ATCMD_RX, SIPC_CHN_ATCMD,
|
5, 128, 0, 0x2400, 0x2400),
|
INIT_SIPC_CHN_SBUF(SIPC_LOOPCHECK_RX, SIPC_CHN_LOOPCHECK,
|
0, 128, 1, 0x400, 0x400),
|
INIT_SIPC_CHN_SBUF(SIPC_ASSERT_RX, SIPC_CHN_ASSERT,
|
0, 1024, 1, 0x400, 0x400),
|
INIT_SIPC_CHN_SBUF(SIPC_LOG_RX, SIPC_CHN_LOG,
|
0, 8*1024, 1, 0x8000, 0x30000),
|
INIT_SIPC_CHN_SBUF(SIPC_BT_TX, SIPC_CHN_BT,
|
11, 4096, 0, 0x2400, 0x2400),
|
INIT_SIPC_CHN_SBUF(SIPC_BT_RX, SIPC_CHN_BT,
|
10, 4096, 0, 0x2400, 0x2400),
|
INIT_SIPC_CHN_SBUF(SIPC_FM_TX, SIPC_CHN_FM,
|
14, 128, 0, 0x2400, 0x2400),
|
INIT_SIPC_CHN_SBUF(SIPC_FM_RX, SIPC_CHN_FM,
|
13, 128, 0, 0x2400, 0x2400),
|
INIT_SIPC_CHN_SBLOCK(SIPC_WIFI_CMD_TX, SIPC_CHN_WIFI_CMD,
|
4, 2048, 16, 2048),
|
INIT_SIPC_CHN_SBLOCK(SIPC_WIFI_CMD_RX, SIPC_CHN_WIFI_CMD,
|
4, 2048, 16, 2048),
|
INIT_SIPC_CHN_SBLOCK(SIPC_WIFI_DATA0_TX, SIPC_CHN_WIFI_DATA0,
|
64, 1664, 256, 1664),
|
INIT_SIPC_CHN_SBLOCK(SIPC_WIFI_DATA0_RX, SIPC_CHN_WIFI_DATA0,
|
64, 1664, 256, 1664),
|
INIT_SIPC_CHN_SBLOCK(SIPC_WIFI_DATA1_TX, SIPC_CHN_WIFI_DATA1,
|
64, 1664, 0, 0),
|
INIT_SIPC_CHN_SBLOCK(SIPC_WIFI_DATA1_RX, SIPC_CHN_WIFI_DATA1,
|
64, 1664, 0, 0),
|
};
|
|
#define SIPC_INVALID_CHN(index) ((index >= SIPC_CHN_NUM) ? 1 : 0)
|
#define SIPC_TYPE(index) (g_sipc_chn[index].chntype)
|
#define SIPC_CHN(index) (&g_sipc_chn[index])
|
|
static inline char *sipc_chn_tostr(int chn, int bufid)
|
{
|
switch (chn) {
|
case SIPC_CHN_ATCMD:
|
if (bufid == 5)
|
return "ATCMD";
|
else if (bufid == 10 || bufid == 11)
|
return "BT";
|
else if (bufid == 13 || bufid == 14)
|
return "FM";
|
case SIPC_CHN_LOG:
|
return "LOG";
|
case SIPC_CHN_LOOPCHECK:
|
return "LOOPCHECK";
|
case SIPC_CHN_ASSERT:
|
return "ASSERT";
|
case SIPC_CHN_WIFI_CMD:
|
return "WIFICMD";
|
case SIPC_CHN_WIFI_DATA0:
|
return "WIFIDATA0";
|
case SIPC_CHN_WIFI_DATA1:
|
return "WIFIDATA1";
|
default:
|
return "Unknown Channel";
|
}
|
}
|
|
static inline int wcn_sipc_buf_list_alloc(int chn,
|
struct mbuf_t **head, struct mbuf_t **tail, int *num)
|
{
|
return buf_list_alloc(chn, head, tail, num);
|
}
|
|
static inline int wcn_sipc_buf_list_free(int chn,
|
struct mbuf_t *head, struct mbuf_t *tail, int num)
|
{
|
return buf_list_free(chn, head, tail, num);
|
}
|
|
static int wcn_sipc_recv(struct sipc_chn_info *sipc_chn, void *buf, int len)
|
{
|
struct mbuf_t *head, *tail;
|
struct mchn_ops_t *wcn_sipc_ops = NULL;
|
|
wcn_sipc_ops = chn_ops(sipc_chn->index);
|
if (unlikely(!wcn_sipc_ops))
|
return -E_NULLPOINT;
|
|
head = kzalloc(sizeof(struct mbuf_t), GFP_KERNEL);
|
if (unlikely(!head))
|
return -E_NOMEM;
|
|
head->buf = buf;
|
head->len = len;
|
head->next = NULL;
|
tail = head;
|
wcn_sipc_ops->pop_link(sipc_chn->index, head, tail, 1);
|
|
return 0;
|
}
|
|
static int wcn_sipc_sbuf_write(u8 index, void *buf, int len)
|
{
|
int cnt = -1;
|
struct sipc_chn_info *sipc_chn;
|
|
if (SIPC_INVALID_CHN(index))
|
return -E_INVALIDPARA;
|
sipc_chn = SIPC_CHN(index);
|
cnt = sbuf_write(sipc_chn->dst, sipc_chn->chn,
|
sipc_chn->sbuf.bufid, buf + PUB_HEAD_RSV, len, -1);
|
WCN_INFO("sbuf chn[%s] write cnt=%d\n",
|
sipc_chn_tostr(sipc_chn->chn, sipc_chn->sbuf.bufid), cnt);
|
|
return cnt;
|
}
|
|
static void wcn_sipc_sbuf_notifer(int event, void *data)
|
{
|
int cnt = -1;
|
int ret = -1;
|
u8 *buf;
|
struct bus_puh_t *puh = NULL;
|
struct sipc_chn_info *sipc_chn = (struct sipc_chn_info *)data;
|
|
if (unlikely(!sipc_chn))
|
return;
|
|
switch (event) {
|
case SBUF_NOTIFY_WRITE:
|
break;
|
case SBUF_NOTIFY_READ:
|
buf = kzalloc(sipc_chn->sbuf.len + PUB_HEAD_RSV, GFP_KERNEL);
|
if (unlikely(!buf)) {
|
WCN_ERR("[%s]:mem alloc fail!\n", __func__);
|
return;
|
}
|
cnt = sbuf_read(sipc_chn->dst,
|
sipc_chn->chn,
|
sipc_chn->sbuf.bufid,
|
(void *)(buf + PUB_HEAD_RSV),
|
sipc_chn->sbuf.len, 0);
|
puh = (struct bus_puh_t *)buf;
|
puh->len = cnt;
|
WCN_DEBUG("sbuf chn[%s] read cnt=%d\n",
|
sipc_chn_tostr(sipc_chn->chn, 0), cnt);
|
if (cnt < 0) {
|
WCN_ERR("sbuf read cnt[%d] invalid\n", cnt);
|
kfree(buf);
|
return;
|
}
|
ret = wcn_sipc_recv(sipc_chn, buf, cnt);
|
if (ret < 0) {
|
WCN_ERR("sbuf recv fail[%d]\n", ret);
|
kfree(buf);
|
return;
|
}
|
break;
|
default:
|
WCN_ERR("sbuf read event[%d] invalid\n", event);
|
}
|
}
|
|
static int wcn_sipc_sblk_write(u8 index, void *buf, int len)
|
{
|
int ret = -1;
|
u8 *addr = NULL;
|
struct sblock blk;
|
struct sipc_chn_info *sipc_chn;
|
|
if (SIPC_INVALID_CHN(index))
|
return -E_INVALIDPARA;
|
sipc_chn = SIPC_CHN(index);
|
/* Get a free swcnblk. */
|
ret = sblock_get(sipc_chn->dst, sipc_chn->chn, &blk, 0);
|
if (ret) {
|
WCN_ERR("[%s]:Failed to get free swcnblk(%d)!\n",
|
sipc_chn_tostr(sipc_chn->chn, 0), ret);
|
return -ENOMEM;
|
}
|
if (blk.length < len) {
|
WCN_ERR("[%s]:The size of swcnblk is so tiny!\n",
|
sipc_chn_tostr(sipc_chn->chn, 0));
|
sblock_put(sipc_chn->dst, sipc_chn->chn, &blk);
|
return E_INVALIDPARA;
|
}
|
addr = (u8 *)blk.addr + SIPC_SBLOCK_HEAD_RESERV;
|
blk.length = len + SIPC_SBLOCK_HEAD_RESERV;
|
memcpy(((u8 *)addr), buf, len);
|
ret = sblock_send(sipc_chn->dst, sipc_chn->chn, &blk);
|
if (ret) {
|
WCN_ERR("[%s]:err:%d\n",
|
sipc_chn_tostr(sipc_chn->chn, 0), ret);
|
sblock_put(sipc_chn->dst, sipc_chn->chn, &blk);
|
}
|
|
return ret;
|
}
|
|
static void wcn_sipc_sblk_recv(struct sipc_chn_info *sipc_chn)
|
{
|
u32 length = 0;
|
int ret = -1;
|
struct sblock blk;
|
|
WCN_DEBUG("[%s]:recv sblock msg",
|
sipc_chn_tostr(sipc_chn->chn, 0));
|
|
while (!sblock_receive(sipc_chn->dst, sipc_chn->chn, &blk, 0)) {
|
length = blk.length - SIPC_SBLOCK_HEAD_RESERV;
|
WCN_DEBUG("sblk length %d", length);
|
wcn_sipc_recv(sipc_chn,
|
(u8 *)blk.addr + SIPC_SBLOCK_HEAD_RESERV, length);
|
ret = sblock_release(sipc_chn->dst, sipc_chn->chn, &blk);
|
if (ret)
|
WCN_ERR("release swcnblk[%d] err:%d\n",
|
sipc_chn->chn, ret);
|
}
|
}
|
|
static void wcn_sipc_sblk_notifer(int event, void *data)
|
{
|
struct sipc_chn_info *sipc_chn = (struct sipc_chn_info *)data;
|
|
if (unlikely(!sipc_chn))
|
return;
|
switch (event) {
|
case SBLOCK_NOTIFY_RECV:
|
wcn_sipc_sblk_recv(sipc_chn);
|
break;
|
/* SBLOCK_NOTIFY_GET cmd not need process it */
|
case SBLOCK_NOTIFY_GET:
|
break;
|
default:
|
WCN_ERR("Invalid event swcnblk notify:%d\n", event);
|
break;
|
}
|
}
|
|
struct wcn_sipc_data_ops sipc_data_ops[] = {
|
{
|
.sipc_write = wcn_sipc_sbuf_write,
|
.sipc_notifer = wcn_sipc_sbuf_notifer,
|
},
|
{
|
.sipc_write = wcn_sipc_sblk_write,
|
.sipc_notifer = wcn_sipc_sblk_notifer,
|
},
|
};
|
|
static int wcn_sipc_buf_push(int index, struct mbuf_t *head,
|
struct mbuf_t *tail, int num)
|
{
|
struct mchn_ops_t *wcn_sipc_ops = NULL;
|
|
wcn_sipc_ops = chn_ops(index);
|
if (unlikely(!wcn_sipc_ops))
|
return -E_NULLPOINT;
|
|
if (wcn_sipc_ops->inout == WCNBUS_TX) {
|
sipc_data_ops[SIPC_TYPE(index)].sipc_write(
|
index, (void *)(head->buf), head->len);
|
wcn_sipc_ops->pop_link(index, head, tail, num);
|
} else if (wcn_sipc_ops->inout == WCNBUS_RX) {
|
/* free buf mem */
|
if (SIPC_TYPE(index) == SIPC_TYPE_SBUF)
|
kfree(head->buf);
|
/* free buf head */
|
kfree(head);
|
} else
|
return -E_INVALIDPARA;
|
|
return 0;
|
}
|
|
static inline unsigned int wcn_sipc_get_status(void)
|
{
|
return g_sipc_info.sipc_chn_status;
|
}
|
|
static inline void wcn_sipc_set_status(unsigned int flag)
|
{
|
mutex_lock(&g_sipc_info.status_lock);
|
g_sipc_info.sipc_chn_status = flag;
|
mutex_unlock(&g_sipc_info.status_lock);
|
}
|
|
static unsigned long long wcn_sipc_get_rxcnt(void)
|
{
|
return wcn_get_cp2_comm_rx_count();
|
}
|
|
static int wcn_sipc_chn_init(struct mchn_ops_t *ops)
|
{
|
int ret = -1;
|
u8 chntype = 0;
|
struct sipc_chn_info *sipc_chn;
|
|
if (SIPC_INVALID_CHN(ops->channel))
|
return -E_INVALIDPARA;
|
sipc_chn = SIPC_CHN(ops->channel);
|
WCN_INFO("[%s]:index[%d] chn[%d]\n",
|
__func__,
|
ops->channel,
|
sipc_chn->chn);
|
|
chntype = sipc_chn->chntype;
|
if (chntype == SIPC_TYPE_SBUF) {
|
/* sbuf */
|
WCN_DEBUG("bufid[%d] len[%d] bufnum[%d]\n",
|
sipc_chn->sbuf.bufid,
|
sipc_chn->sbuf.len,
|
sipc_chn->sbuf.bufnum);
|
if (sipc_chn->sbuf.bufnum) {
|
ret = sbuf_create(sipc_chn->dst, sipc_chn->chn,
|
sipc_chn->sbuf.bufnum,
|
sipc_chn->sbuf.txbufsize,
|
sipc_chn->sbuf.rxbufsize);
|
if (ret < 0) {
|
WCN_ERR("sbuf chn[%d] create fail!\n",
|
ops->channel);
|
return ret;
|
}
|
}
|
if (ops->inout == WCNBUS_RX) {
|
ret = sbuf_register_notifier(
|
sipc_chn->dst,
|
sipc_chn->chn,
|
sipc_chn->sbuf.bufid,
|
sipc_data_ops[chntype].sipc_notifer,
|
sipc_chn);
|
if (ret < 0) {
|
WCN_ERR("sbuf chn[%d] registerfail!\n",
|
ops->channel);
|
return ret;
|
}
|
}
|
WCN_INFO("sbuf chn[%d] create success!\n", ops->channel);
|
} else if (chntype == SIPC_TYPE_SBLOCK) {
|
WCN_DEBUG("tbnum[%d] tbsz[%d] rbnum[%d] rbsz[%d]\n",
|
sipc_chn->sblk.txblocknum,
|
sipc_chn->sblk.txblocksize,
|
sipc_chn->sblk.rxblocknum,
|
sipc_chn->sblk.rxblocksize);
|
/* sblock */
|
if (ops->inout == WCNBUS_TX) {
|
ret = sblock_create(
|
sipc_chn->dst,
|
sipc_chn->chn,
|
sipc_chn->sblk.txblocknum,
|
sipc_chn->sblk.txblocksize,
|
sipc_chn->sblk.rxblocknum,
|
sipc_chn->sblk.rxblocksize);
|
if (ret < 0) {
|
WCN_ERR("sblock chn[%d] create fail!\n",
|
ops->channel);
|
return ret;
|
}
|
}
|
if (ops->inout == WCNBUS_RX) {
|
ret = sblock_register_notifier(
|
sipc_chn->dst,
|
sipc_chn->chn,
|
sipc_data_ops[chntype].sipc_notifer,
|
sipc_chn);
|
if (ret < 0) {
|
WCN_ERR("sblock chn[%d] register fail!\n",
|
ops->channel);
|
sblock_destroy(sipc_chn->dst, sipc_chn->chn);
|
return ret;
|
}
|
}
|
WCN_INFO("sblock chn[%d] create success!\n", ops->channel);
|
} else {
|
WCN_ERR("invalid sipc type!");
|
return -E_INVALIDPARA;
|
}
|
|
bus_chn_init(ops, HW_TYPE_SIPC);
|
|
return ret;
|
}
|
|
static int wcn_sipc_chn_deinit(struct mchn_ops_t *ops)
|
{
|
struct sipc_chn_info *sipc_chn;
|
|
bus_chn_deinit(ops);
|
|
if (SIPC_INVALID_CHN(ops->channel))
|
return -E_INVALIDPARA;
|
sipc_chn = SIPC_CHN(ops->channel);
|
/* sbuf */
|
if (sipc_chn->chntype == SIPC_TYPE_SBUF) {
|
if (sipc_chn->sbuf.bufnum)
|
sbuf_destroy(sipc_chn->dst, sipc_chn->chn);
|
} else if (sipc_chn->chntype == SIPC_TYPE_SBLOCK) {
|
if (ops->inout == WCNBUS_TX)
|
sblock_destroy(sipc_chn->dst, sipc_chn->chn);
|
}
|
WCN_INFO("sipc chn[%d] deinit success!\n", ops->channel);
|
|
return 0;
|
}
|
|
static void wcn_sipc_module_init(void)
|
{
|
mutex_init(&g_sipc_info.status_lock);
|
WCN_INFO("sipc module init success\n");
|
}
|
|
static void wcn_sipc_module_deinit(void)
|
{
|
mutex_destroy(&g_sipc_info.status_lock);
|
WCN_INFO("sipc module deinit success\n");
|
}
|
|
static struct sprdwcn_bus_ops sipc_bus_ops = {
|
.chn_init = wcn_sipc_chn_init,
|
.chn_deinit = wcn_sipc_chn_deinit,
|
.list_alloc = wcn_sipc_buf_list_alloc,
|
.list_free = wcn_sipc_buf_list_free,
|
.push_list = wcn_sipc_buf_push,
|
.get_carddump_status = wcn_sipc_get_status,
|
.set_carddump_status = wcn_sipc_set_status,
|
.get_rx_total_cnt = wcn_sipc_get_rxcnt,
|
|
};
|
|
void module_bus_init(void)
|
{
|
wcn_sipc_module_init();
|
module_ops_register(&sipc_bus_ops);
|
WCN_INFO("sipc bus init success\n");
|
}
|
EXPORT_SYMBOL(module_bus_init);
|
|
void module_bus_deinit(void)
|
{
|
module_ops_unregister();
|
wcn_sipc_module_deinit();
|
WCN_INFO("sipc bus deinit success\n");
|
}
|
EXPORT_SYMBOL(module_bus_deinit);
|