在介绍SDIO wifi Marvell8801/Marvell88w8801之前先附上模块链接:点击购买Marvell8801开发板
代码工程的GITHUB连接:点进进入GITHUB仓库
https://github.com/sj15712795029/stm32f1_marvell88w8801_marvell8801_wifi
Marvell自己实现驱动系列文章分为几篇介绍:
SDIO wifi Marvell8801/Marvell88w8801 介绍(一) ---- 芯片介绍
SDIO wifi Marvell8801/Marvell88w8801 介绍(二) ---- SDIO协议介绍
SDIO wifi Marvell8801/Marvell88w8801 介绍(三) ---- 寄存器介绍
SDIO wifi Marvell8801/Marvell88w8801 介绍(四) ---- 命令/事件/数据格式
SDIO wifi Marvell8801/Marvell88w8801 介绍(五) ---- TLV
SDIO wifi Marvell8801/Marvell88w8801 介绍(六) ---- 实现初始化功能
SDIO wifi Marvell8801/Marvell88w8801 介绍(七) ---- 实现搜索功能
SDIO wifi Marvell8801/Marvell88w8801 介绍(八) ---- 实现STA功能
SDIO wifi Marvell8801/Marvell88w8801 介绍(九) ---- 实现AP功能
SDIO wifi Marvell8801/Marvell88w8801 介绍(十) ---- 移植TCP/IP协议栈LWIP
SDIO wifi Marvell8801/Marvell88w8801 介绍(十一) ---- 自己编写LWIP没有的DHCP server
SDIO wifi Marvell8801/Marvell88w8801 介绍(十二) ---- MQTT介绍
SDIO wifi Marvell8801/Marvell88w8801 介绍(十三) ---- 百度云操作说明
SDIO wifi Marvell8801/Marvell88w8801 介绍(十四) ---- 上位机STA操作/代码
SDIO wifi Marvell8801/Marvell88w8801 介绍(十五) ---- 上位机AP操作/代码
SDIO wifi Marvell8801/Marvell88w8801 介绍(十六) ---- 上位机UDP操作/代码
SDIO wifi Marvell8801/Marvell88w8801 介绍(十七) ---- 上位机TCP操作/代码
SDIO wifi Marvell8801/Marvell88w8801 介绍(十八) ---- 上位机PING操作/代码
SDIO wifi Marvell8801/Marvell88w8801 介绍(十九) ---- 上位机云服务器(百度云)操作/代码
每篇更新打开专栏可以看到:打开Marvell8801/Marvell8801 专栏
1. Marvell88w8801 IOPORT
Marvell的IO PORT分为两种,第一种是control io port,主要用于传输cmd/cmd response/event,另外fw也是通过这个io port写下去的,这个是固定的,另外一种是data port,用于传输上行和下行数据,read是1~16,write是根据get hw spec的命令响应得到的,关于data port在在后面lwip移植的时候再做说明,此部分只是说control io port.
Control io port你可以简单的理解为wifi的寄存器地址,cmd/fw data都是往这个地址去写,cmd response是通过这个地址读回来,要想得到这个control io port需要通过读几个寄存器得到这个,也就是前面文章介绍寄存器的时候说的:
整个程序处理如下:
/******************************************************************************
* 函数名: mrvl88w8801_get_control_io_port
* 参数: NULL
* 返回值: 返回执行结果
* 描述: 通过IO_PORT_0_REG IO_PORT_1_REG IO_PORT_2_REG得到
control io port(也就是cmd/cmd resp/fw data的读写寄存器地址)
******************************************************************************/
static uint8_t mrvl88w8801_get_control_io_port()
{
uint32_t control_io_port = 0;
uint8_t temp_op_port = 0;;
hw_sdio_cmd52(SDIO_EXCU_READ,SDIO_FUNC_1,IO_PORT_0_REG,0,&temp_op_port);
control_io_port |= temp_op_port;
hw_sdio_cmd52(SDIO_EXCU_READ,SDIO_FUNC_1,IO_PORT_1_REG,0,&temp_op_port);
control_io_port |= (temp_op_port<<8);
hw_sdio_cmd52(SDIO_EXCU_READ,SDIO_FUNC_1,IO_PORT_2_REG,0,&temp_op_port);
control_io_port |= (temp_op_port<<16);
COMP_DEBUG("WIFI:io_port 0x%x\n",control_io_port);
pmrvl88w8801_core->control_io_port = control_io_port;
return COMP_ERR_OK;
}
此程序比较简单,就是通过SDIO cmd52把IO_PORT_0_REG IO_PORT_1_REG IO_PORT_2_REG的寄存器地址值读出来,组合起来就得到control io port,并且存储到pmarvell88w8801_core的全局变量成员control_io_port中,后续 download fw/cmd/cmd response都会用到这个
2. Marvell88w8801 download fw
Download fw的过程在说明Linux driver的时候整个过程说过,我这部分在哪个基础做了一个简单的,主要实现在mrvl88w8801_download_fw这个函数中
一共有以下几个step:
Step 1: 首先把fw的二进制文件转换为数组,转换成的样子如下,截图是部分fw,完整的请参照代码
Step 2:把数组长度和数组赋值给一个变量
Step 3: polling card status
/******************************************************************************
* 函数名: mrvl88w8801_download_fw_wait
* 参数: NULL
* 返回值: 返回执行结果
* 描述: polling card status,读取寄存器CARD_TO_HOST_EVENT_REG 0x30的状态
判断bit0,bit3是否都被置位
******************************************************************************/
static uint8_t mrvl88w8801_download_fw_wait()
{
uint8_t status = 0;;
while(1)
{
hw_sdio_cmd52(SDIO_EXCU_READ,SDIO_FUNC_1,CARD_TO_HOST_EVENT_REG,0,&status);
if((status & CARD_IO_READY) == CARD_IO_READY && (status & DN_LD_CARD_RDY) == DN_LD_CARD_RDY)
break;
}
return COMP_ERR_OK;
}
Step 4: Get next block len
/* Get next block length */
hw_sdio_cmd52(SDIO_EXCU_READ,SDIO_FUNC_1,READ_BASE_0_REG,0,&fw_req_temp_size);
fw_req_size |= fw_req_temp_size;
hw_sdio_cmd52(SDIO_EXCU_READ,SDIO_FUNC_1,READ_BASE_1_REG,0,&fw_req_temp_size);
fw_req_size |= (fw_req_temp_size<<8);
if(fw_req_size == 0)
continue;
//COMP_DEBUG("WIFI:Required: %d bytes, Remaining: %d bytes\r\n", fw_req_size, fw_len);
if (fw_req_size & 1)
{
/* 若size为奇数(如17)则说明接收方出现了CRC校验错误 */
COMP_DEBUG("WIFI:Error: an odd size is invalid!\n");
return WIFI_ERR_REQ_INVALID_FW_SIZE;
}
Step 5: Write Next Block
hw_sdio_cmd53(SDIO_EXCU_WRITE,SDIO_FUNC_1,control_io_port,0,(uint8_t *)fw_data,fw_req_size);
Step 6:firmware active check
/* Check firmware active */
for(index = 0; index < WIFI_CONF_MAX_POLL_TRIES; index++)
{
mrvl88w8801_get_fw_status(&fw_status);
if (fw_status == FIRMWARE_READY)
{
COMP_DEBUG("WIFI:fw is active index %d\n",index);
return COMP_ERR_OK;
}
}
整个流程看mrvl88w8801_download_fw这个函数,流程很容易看懂,完整的api代码如下:
/******************************************************************************
* 函数名: mrvl88w8801_download_fw
* 参数: NULL
* 返回值: 返回执行结果
* 描述: 下载firmware到芯片
******************************************************************************/
static uint8_t mrvl88w8801_download_fw()
{
uint16_t index;
uint16_t fw_status;
const uint8_t *fw_data = mrvl88w8801_fw;
uint32_t fw_len = sizeof(mrvl88w8801_fw);
uint16_t fw_req_size = 0;
uint8_t fw_req_temp_size = 0;
uint32_t control_io_port = pmrvl88w8801_core->control_io_port;
#if 0
/* Check firmware active */
for(index = 0; index < WIFI_CONF_MIN_POLL_TRIES; index++)
{
mrvl88w8801_get_fw_status(&fw_status);
if (fw_status == FIRMWARE_READY)
{
COMP_DEBUG("WIFI:fw is active index %d\n",index);
return COMP_ERR_OK;
}
}
#endif
while (fw_len)
{
fw_req_size = 0;
fw_req_temp_size = 0;
if(fw_len != sizeof(mrvl88w8801_fw))
{
/* polling card status */
mrvl88w8801_download_fw_wait();
}
/* Get next block length */
hw_sdio_cmd52(SDIO_EXCU_READ,SDIO_FUNC_1,READ_BASE_0_REG,0,&fw_req_temp_size);
fw_req_size |= fw_req_temp_size;
hw_sdio_cmd52(SDIO_EXCU_READ,SDIO_FUNC_1,READ_BASE_1_REG,0,&fw_req_temp_size);
fw_req_size |= (fw_req_temp_size<<8);
if(fw_req_size == 0)
continue;
//COMP_DEBUG("WIFI:Required: %d bytes, Remaining: %d bytes\r\n", fw_req_size, fw_len);
if (fw_req_size & 1)
{
/* 若size为奇数(如17)则说明接收方出现了CRC校验错误 */
COMP_DEBUG("WIFI:Error: an odd size is invalid!\n");
return WIFI_ERR_REQ_INVALID_FW_SIZE;
}
if (fw_req_size > fw_len)
fw_req_size = fw_len;
/* Write block */
hw_sdio_cmd53(SDIO_EXCU_WRITE,SDIO_FUNC_1,control_io_port,0,(uint8_t *)fw_data,fw_req_size);
fw_len -= fw_req_size;
fw_data += fw_req_size;
}
/* Check firmware active */
for(index = 0; index < WIFI_CONF_MAX_POLL_TRIES; index++)
{
mrvl88w8801_get_fw_status(&fw_status);
if (fw_status == FIRMWARE_READY)
{
COMP_DEBUG("WIFI:fw is active index %d\n",index);
return COMP_ERR_OK;
}
}
return WIFI_ERR_INVALID_FW_STATUS;
}
3. Marvell88w8801 init cmd
此部分和Linux还有差异,主要是精简版的,比Linux少了很多命令
下面直接来看下所有的CMD
Step 1:发送HostCmd_CMD_FUNC_INIT 0x00a9命令和处理cmd response
首先来看下HostCmd_CMD_FUNC_INIT 0x00a9的命令
1)发送HostCmd_CMD_FUNC_INIT 0x00a9
/******************************************************************************
* 函数名: mrvl88w8801_func_init
* 参数: tx(IN) -->tx buffer
* 返回值: 返回执行结果
* 描述: 组HostCmd_CMD_FUNC_INIT command的封包
******************************************************************************/
static uint8_t mrvl88w8801_func_init(uint8_t* tx)
{
uint16_t tx_packet_len = CMD_HDR_SIZE;
HostCmd_DS_COMMAND *cmd = (HostCmd_DS_COMMAND *)tx;
cmd->pack_len = tx_packet_len;
cmd->pack_type = TYPE_CMD_CMDRSP;
cmd->command = HostCmd_CMD_FUNC_INIT;
cmd->size = tx_packet_len - CMD_SDIO_HDR_SIZE;
cmd->seq_num = 0;
cmd->bss = 0;
cmd->result = 0;
return COMP_ERR_OK;
}
然后通过CMD53发送出去
hw_sdio_cmd53(SDIO_EXCU_WRITE,SDIO_FUNC_1,pmrvl88w8801_core->control_io_port +CTRL_PORT,0,tx,tx_len);
2)处理cmd response
mrvl88w8801_process_cmdrsp中case HostCmd_CMD_FUNC_INIT然后发送下一条cmd
HostCmd_CMD_MAC_CONTROL
Step 2: 发送HostCmd_CMD_MAC_CONTROL 0x0028命令和处理cmd response
首先来看下HostCmd_CMD_MAC_CONTROL 0x0028的命令
另外,补充一点,此文档和Linux代码有出入,文档bit 0~bit2 是reserved,但是驱动中是这样的,也就是说bit0是RX on,bit 1是TX on,此部分要注意
1)发送HostCmd_CMD_MAC_CONTROL 0x0028命令
/******************************************************************************
* 函数名: mrvl88w8801_mac_control
* 参数: tx(IN) -->tx buffer
data_buff(IN) -->action行为指针
* 返回值: 返回执行结果
* 描述: 组HostCmd_CMD_MAC_CONTROL command的封包
******************************************************************************/
static uint8_t mrvl88w8801_mac_control(uint8_t* tx,void *data_buff)
{
uint16_t action = *((uint16_t *) data_buff);
HostCmd_DS_COMMAND *cmd = (HostCmd_DS_COMMAND *)tx;
HostCmd_DS_MAC_CONTROL *pmac = &cmd->params.mac_ctrl;
uint16_t tx_packet_len = CMD_HDR_SIZE + sizeof(HostCmd_DS_MAC_CONTROL);
cmd->pack_len = tx_packet_len;
cmd->pack_type = TYPE_CMD_CMDRSP;
cmd->command = HostCmd_CMD_MAC_CONTROL;
cmd->size = tx_packet_len - CMD_SDIO_HDR_SIZE;
cmd->seq_num = 0;
cmd->bss = 0;
cmd->result = 0;
pmac->action = comp_cpu_to_le16(action);
return COMP_ERR_OK;
}
然后通过CMD53发送出去
2)处理HostCmd_CMD_MAC_CONTROL 0x0028 cmd response
mrvl88w8801_process_cmdrsp中case HostCmd_CMD_MAC_CONTROL然后发送下一条cmd
HostCmd_CMD_GET_HW_SPEC
Step 3: 发送HostCmd_CMD_GET_HW_SPECL 0x0003命令和处理cmd response
首先来看下HostCmd_CMD_GET_HW_SPECL 0x0003的命令
1)发送HostCmd_CMD_GET_HW_SPECL 0x0003命令
/******************************************************************************
* 函数名: mrvl88w8801_get_hw_spec
* 参数: tx(IN) -->tx buffer
* 返回值: 返回执行结果
* 描述: 组HostCmd_CMD_GET_HW_SPEC command的封包
******************************************************************************/
static uint8_t mrvl88w8801_get_hw_spec(uint8_t* tx)
{
HostCmd_DS_COMMAND *cmd = (HostCmd_DS_COMMAND *)tx;
uint16_t tx_packet_len = CMD_HDR_SIZE + sizeof(HostCmd_DS_GET_HW_SPEC);
cmd->pack_len = tx_packet_len;
cmd->pack_type = TYPE_CMD_CMDRSP;
cmd->command = HostCmd_CMD_GET_HW_SPEC;
cmd->size = tx_packet_len - CMD_SDIO_HDR_SIZE;
cmd->seq_num = 0;
cmd->bss = 0;
cmd->result = 0;
return COMP_ERR_OK;
}
然后通过CMD53发送出去
2)处理HostCmd_CMD_GET_HW_SPECL 0x0003 cmd response
此部分的cmd response的处理方式和上面两个cmd还有差异,上面两个cmd是收到response就继续下发下一条cmd,但是这个需要解析get hw spec的命令,主要是用于拿到2.1小节我们说的write data port的最大值
另外,我们在发送HostCmd_CMD_802_11_MAC_ADDRESS 0x004d的cmd
Step 4: 发送HostCmd_CMD_802_11_MAC_ADDRESS 0x004d命令和处理cmd response
首先来看下HostCmd_CMD_802_11_MAC_ADDRESS 0x004d的命令
1)发送HostCmd_CMD_802_11_MAC_ADDRESS 0x004d命令
/******************************************************************************
* 函数名: mrvl88w8801_mac_addr_prepare
* 参数: tx(IN) -->tx buffer
cmd_action(IN) -->set/get
data_buff(IN) -->mac address,此部分主要用于set
data_len(IN) -->mac的长度
* 返回值: 返回执行结果
* 描述: 组HostCmd_CMD_802_11_MAC_ADDRESS command的封包
******************************************************************************/
static uint8_t mrvl88w8801_mac_addr_prepare(uint8_t* tx,uint16_t cmd_action,void *data_buff,uint16_t data_len)
{
HostCmd_DS_COMMAND *cmd = (HostCmd_DS_COMMAND *)tx;
HostCmd_DS_802_11_MAC_ADDRESS *pmac_addr = &cmd->params.mac_addr;
uint16_t tx_packet_len = CMD_HDR_SIZE + sizeof(HostCmd_DS_802_11_MAC_ADDRESS)+data_len;
cmd->pack_len = tx_packet_len;
cmd->pack_type = TYPE_CMD_CMDRSP;
cmd->command = HostCmd_CMD_802_11_MAC_ADDRESS;
cmd->size = tx_packet_len - CMD_SDIO_HDR_SIZE;
cmd->seq_num = 0;
cmd->bss = 0;
cmd->result = 0;
if(cmd_action == HostCmd_ACT_GEN_GET)
{
pmac_addr->action = HostCmd_ACT_GEN_GET;
comp_memset(pmac_addr->mac_addr,0,MAC_ADDR_LENGTH);
}
else /* set mac address */
{
pmac_addr->action = HostCmd_ACT_GEN_SET;
comp_memcpy(pmac_addr->mac_addr,data_buff,MAC_ADDR_LENGTH);
}
return COMP_ERR_OK;
}
2)处理HostCmd_CMD_802_11_MAC_ADDRESS 0x004d cmd response
此部分只有一个cmd response的解析
/******************************************************************************
* 函数名: mrvl88w8801_ret_mac_address
* 参数: rx_buffer(IN) -->rx buffer
len(IN) -->rx buffer len
* 返回值: 返回执行结果
* 描述: 此部分主要是处理mac cmd reponse,如果拿到mac,那么会做两个事情:
1.更新pmrvl88w8801_core结构体中断额mac_address
2.初始化lwip的mac
******************************************************************************/
static uint8_t mrvl88w8801_ret_mac_address(uint8_t *rx_buffer,int len)
{
HostCmd_DS_802_11_MAC_ADDRESS *pconnect_rsp = (HostCmd_DS_802_11_MAC_ADDRESS *)rx_buffer;
comp_memcpy(pmrvl88w8801_core->mac_address,pconnect_rsp->mac_addr,MAC_ADDR_LENGTH);
COMP_DEBUG("mrvl88w8801_ret_mac_address mac dump\n");
ethernet_sta_driver_init(pmrvl88w8801_core->mac_address);
if(mrvl_wifi_cb && mrvl_wifi_cb->wifi_init_result)
mrvl_wifi_cb->wifi_init_result(COMP_ERR_OK);
return COMP_ERR_OK;
}
注释说的很清楚了,至于初始化lwip的mac部分我们后续再做说明,到此,我们init的过程已经完结,相比于Linux,我们做的精简,而且易懂,你再配合这code看,会有种大彻大悟的感觉