600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > IOT-OS之RT-Thread(十五)--- SDIO设备对象管理 + AP6181(BCM43362) WiFi模块

IOT-OS之RT-Thread(十五)--- SDIO设备对象管理 + AP6181(BCM43362) WiFi模块

时间:2023-03-04 01:02:46

相关推荐

IOT-OS之RT-Thread(十五)--- SDIO设备对象管理 + AP6181(BCM43362) WiFi模块

文章目录

一、AP6181 Wi-Fi模块简介1.1 AP6181 硬件接口1.2 AP6181 驱动层级二 SDIO设备对象管理2.1 SDIO Bus Driver2.1.1 Host 数据结构描述2.1.2 rt_mmcsd_req 数据结构描述2.1.3 SDIO Bus 接口函数及初始化过程2.2 SDIO Card Device & Driver2.2.1 SDIO Card 数据结构描述2.2.2 SDIO Driver 数据结构描述2.2.3 SDIO Card接口函数及初始化过程2.2.4 SDIO Card与Driver的注册和匹配过程2.3 SDIO Host Driver Layer2.3.1 SDIO硬件设备数据结构描述2.3.2 SDIO硬件驱动初始化过程三、SDIO 驱动配置3.1 SDIO CubeMX配置3.2 SDIO时钟配置3.3 WL_REG_ON引脚配置3.4 配置SDIO编译选项更多文章:

本章介绍SDIO Wi-Fi模块的驱动实现过程,对SDIO不熟悉的可以先参阅博客:SD/MMC + SDIO,对RT-Thread驱动分层与主从分离思想不熟悉的,可以先参阅博客:驱动分层与主从分离思想。

一、AP6181 Wi-Fi模块简介

1.1 AP6181 硬件接口

Pandora开发板为我们提供了一个板载Wi-Fi 芯片 — AP6181,我们不需要再外接扩展模块(比如 ESP8266)即可实现连接 Wi-Fi 并访问外部网络的功能。我们先看下Pandora开发板原理图中 Wi-Fi 模块 AP6181 的硬件接线原理图:

正基公司的 AP6181 Wi-Fi 模组具有以下特点:

符合IEEE 802.11 b/g/n标准,可以实现单通道高达72.2Mbps 的传输速度(IEEE 802.11n 标准);支持标准接口SDIO v2.0(时钟频率高速模式可达50MHz,数据线位宽支持4位或1位模式);集成ARM Cortex-M3 (带有片上存储器)以运行 IEEE802.11 固件(用于Wi-Fi 数据帧的处理);

AP6181 Wi-Fi 模组内部实际封装的是Broadcom 43362 芯片,接下来看看 BCM43362 芯片内部都有哪些模块(图片取自BCM43362_datasheet):

从上面 BCM43362 系统框图可以看出,内部是集成了ARM处理器及RAM/ROM存储空间的,用于运行 Wi-Fi 固件,管理 Wi-Fi MAC/PHY/RADIO层的无线链路信道资源,完成 Wi-Fi 数据帧 与 以太网数据帧 之间的转换等功能。

AP6181 / BCM43362 模组外接引脚如下:

SDIO(Secure Digital Input Output):包括DATA[0:3]、CMD、CLK 共6个引脚,支持SDIO V2.0总线标准;WiFi INT / WL_HOST_WAKE:WLAN wake-up Host,当接收到数据帧后,产生中断信号,唤醒主机Host接收并处理该数据帧;WL_REG_ON:Internal regulators power enable/disable,我觉得跟BCM43362芯片引脚 WL_RST_N / POR(WLAN Reset / Power-On Reset,该引脚持续拉低则BCM 43362进入Power-down状态,给该引脚一个低电平脉冲则BCM 43362进入Reset 状态) 功能相似;Coexistence interface:相近频率的无线设备(比如蓝牙)通过该接口与BCM43362连接,可以共享BCM43362的无线介质(MAC/PHY/RADIO层及其后的ANT等资源),我们暂时不需要蓝牙功能,该引脚可忽略;WL_BT_ANT:向外与板载丝印天线相连,向内通过 T/R Switch 与WLAN Tx/Rx 相连,AP6181把BCM43362与T/R Switch封装到一起了;System Clock / XTAL:外接晶振XTAL_IN / XTAL_OUT,为AP6181或BCM43362提供系统时钟;Sleep Clock / LPO:睡眠时钟或者低功耗时钟输入,Pandora开发板并未使用该引脚;VBAT / VDDIO / LDO:Power supply,为AP6181或BCM43362提供电源支持。

从AP6181 模块的接线图可以看出,我们需要重点关注的是SDIO、WiFi INT、WL_REG_ON这三组共8个引脚,其余的引脚Pandora 开发板上已经帮我们接好了。SDIO引脚的定义在博客:SD/MMC + SDIO中已经有过介绍,WiFi INT引脚需要绑定自定义的中断处理函数,WL_REG_ON引脚是内部稳压电源的使能引脚,在WLAN模块正常工作时需要将其拉高,对该引脚的拉高时间有什么要求吗?我们看下AP6181 / BCM43362 WLAN模块的启动时序图:

从上图可以看出,WL_REG_ON引脚需要在 VBAT / VDDIO 上电2个睡眠时钟周期(32.768KHZ)后,1.5ms之内完成电平拉高,我们可以在 AP6181 驱动代码中设置WL_REG_ON引脚的拉高时机(比如0.1ms ~ 1ms)与动作。

1.2 AP6181 驱动层级

AP6181 模块不像 ESP8266 模块那样内部集成了 WLAN驱动与TCP/IP协议栈,甚至AP6181 为了节省成本,模块内可能就没有可供存放WLAN驱动代码的ROM区域。

AP6181 模块内部有ARM处理器和RAM 内存区域,在工作时也需要运行WLAN固件程序以处理WLAN数据帧,这就需要开发者在初始化该模块时,将主控端Flash中保存的WLAN固件代码传送到AP6181 模块内(RAM内存区域)。这样做虽然增加了点主控端的驱动代码和ROM占用空间,但也有三个明显的好处:

省去了大部分ROM空间,降低了模块的成本;不需要在模块出厂时单独为其烧录固件代码,减少了生产环节;固件代码便于维护和升级,只需要更新主控端Flash内的固件文件,模块初始化时自动会将新的固件代码传输到模块内。

由于Nand Flash成本比Nor Flash(可以在芯片内执行代码,而不需先拷贝到RAM内存中)更低,且在主控端Flash中更新固件代码更灵活方便,因此这种固件加载方式在设备驱动开发中很常见。

AP6181 模块是基于SDIO 总线协议进行通信的,因此模块与主控端最底层应该分别是SDIO Card controller与Host controller。SDIO Card内有一个CSA(Code Storage Area)可以用来存放WLAN固件代码(由AP6181 芯片供应商提供),SDIO Host controller上层则分别是SDIO Bus Driver和SDIO Card Driver。AP6181 模块提供的是Wi-Fi 网络访问服务,因此这里的SDIO Card Driver 也就是 WLAN Driver(由AP6181 芯片供应商提供公版驱动,开发者再根据需要调整或移植)。当 WLAN Driver 适配完成,接下来的Wi-Fi 管理与网络服务就可以交给操作系统了,Pandora 开发板上的 AP6181 模块WLAN驱动及TCP/IP协议栈的层级关系图如下:

RT-Thread为方便我们管理Wi-Fi 设备,提供了一个WLAN管理框架,相当于WLAN 设备无关层,可以向上提供统一的访问接口。当我们更换 Wi-Fi 模块时,只需要修改相应的适配代码,不需要修改上层的应用程序。

二 SDIO设备对象管理

前篇博客:SD/MMC + SDIO已经简单介绍过SDIO协议的三个部分:SDIO Host controller、SDIO Bus protocol、SDIO Card。上面已经简单介绍过本文需要用到的 SDIO Card — AP6181 模块,这里重点介绍主机端的SDIO Host controller 与 SDIO Bus protocol,包括SDIO Card Driver — WLAN Driver。

前篇博客:驱动分层与主从分离思想介绍了RT-Thread I/O 设备驱动管理的分层思想和一主多从工作模式的主从分离思想,也提到了Linux的总线设备驱动模型。SDIO驱动也可以看出明显的分层结构(只列出了SDIO相关的驱动文件,省去了MMC/SD相关的驱动文件):

// I/O 设备管理层rt-thread-4.0.1\include\rtdef.hrt-thread-4.0.1\src\device.c// 设备驱动框架层rt-thread-4.0.1\components\drivers\include\drivers\sdio.hrt-thread-4.0.1\components\drivers\sdio\sdio.crt-thread-4.0.1\components\drivers\include\drivers\mmcsd_card.hrt-thread-4.0.1\components\drivers\include\drivers\mmcsd_core.hrt-thread-4.0.1\components\drivers\include\drivers\mmcsd_cmd.hrt-thread-4.0.1\components\drivers\sdio\mmcsd_core.crt-thread-4.0.1\components\drivers\include\drivers\mmcsd_host.h// 设备驱动层libraries\HAL_Drivers\drv_sdio.hlibraries\HAL_Drivers\drv_sdio.c

SDIO的设备驱动框架层还可以细分为SDIO Card Layer、Core Layer、Host Layer,但这种分层没有清晰的展示出Host与Card的主从分离思想。上面并没有展示SDIO Card Driver — WLAN Driver 的相关文件,我也不知道应该将其放到设备驱动框架层还是设备驱动层。

如果按照主从分离思想看,SDIO协议可以分为主机Host 与卡设备Card,I/O Card可能支持的功能模块比较多(1个I/O Card最多可以包含7个功能模块),驱动种类与数量自然也比较多。如果我们把 I/O Card和 function device driver 看作一体,驱动代码的复用性就比较低。如果我们我们借鉴Linux的总线设备驱动模型(如下图所示),将 I/O Card和 function device driver 也分开,就可以根据需要灵活匹配 Driver 与 Function Device,不仅实现了Host — Card 的主从分离,也实现了 Function Device — Function Driver 的设备驱动分离,符合编写代码的高内聚、低耦合原则。

一个总线Bus 分别管理一个设备链表device_list 和一个驱动链表 driver_list,当新注册一个I/O Card/Device 或 Function device Driver 时,探测已有的 driver_list 或 device_list 并尝试与自己匹配(调用Bus提供的match接口函数)。当Driver 与 Device 完成匹配后,就可以调用Driver提供的probe接口函数完成Device设备的初始化,后面就可以通过Driver提供的API 来访问Device设备,实现相应功能的扩展服务。

2.1 SDIO Bus Driver

2.1.1 Host 数据结构描述

SDIO Bus要给出Command、Response、Data三部分分别是如何描述、配置和传输的,而这三部分的传输是受主机Host 控制的,在介绍Command/Response与Data的描述结构前,先介绍下主机Host 是如何描述的:

// rt-thread-4.0.1\components\drivers\include\drivers\mmcsd_host.hstruct rt_mmcsd_host {struct rt_mmcsd_card *card;const struct rt_mmcsd_host_ops *ops;rt_uint32_t freq_min;rt_uint32_t freq_max;struct rt_mmcsd_io_cfg io_cfg;rt_uint32_t valid_ocr;/* current valid OCR */#define VDD_165_195(1 << 7)/* VDD voltage 1.65 - 1.95 */#define VDD_20_21(1 << 8)/* VDD voltage 2.0 ~ 2.1 */#define VDD_21_22(1 << 9)/* VDD voltage 2.1 ~ 2.2 */#define VDD_22_23(1 << 10)/* VDD voltage 2.2 ~ 2.3 */#define VDD_23_24(1 << 11)/* VDD voltage 2.3 ~ 2.4 */#define VDD_24_25(1 << 12)/* VDD voltage 2.4 ~ 2.5 */#define VDD_25_26(1 << 13)/* VDD voltage 2.5 ~ 2.6 */#define VDD_26_27(1 << 14)/* VDD voltage 2.6 ~ 2.7 */#define VDD_27_28(1 << 15)/* VDD voltage 2.7 ~ 2.8 */#define VDD_28_29(1 << 16)/* VDD voltage 2.8 ~ 2.9 */#define VDD_29_30(1 << 17)/* VDD voltage 2.9 ~ 3.0 */#define VDD_30_31(1 << 18)/* VDD voltage 3.0 ~ 3.1 */#define VDD_31_32(1 << 19)/* VDD voltage 3.1 ~ 3.2 */#define VDD_32_33(1 << 20)/* VDD voltage 3.2 ~ 3.3 */#define VDD_33_34(1 << 21)/* VDD voltage 3.3 ~ 3.4 */#define VDD_34_35(1 << 22)/* VDD voltage 3.4 ~ 3.5 */#define VDD_35_36(1 << 23)/* VDD voltage 3.5 ~ 3.6 */rt_uint32_t flags; /* define device capabilities */#define MMCSD_BUSWIDTH_4(1 << 0)#define MMCSD_BUSWIDTH_8(1 << 1)#define MMCSD_MUTBLKWRITE(1 << 2)#define MMCSD_HOST_IS_SPI(1 << 3)#define controller_is_spi(host)(host->flags & MMCSD_HOST_IS_SPI)#define MMCSD_SUP_SDIO_IRQ(1 << 4)/* support signal pending SDIO IRQs */#define MMCSD_SUP_HIGHSPEED(1 << 5)/* support high speed */rt_uint32_tmax_seg_size;/* maximum size of one dma segment */rt_uint32_tmax_dma_segs;/* maximum number of dma segments in one request */rt_uint32_tmax_blk_size; /* maximum block size */rt_uint32_tmax_blk_count; /* maximum block count */rt_uint32_t spi_use_crc;struct rt_mutex bus_lock;struct rt_semaphore sem_ack;rt_uint32_t sdio_irq_num;struct rt_semaphore *sdio_irq_sem;struct rt_thread*sdio_irq_thread;void *private_data;};struct rt_mmcsd_io_cfg {rt_uint32_tclock;/* clock rate */rt_uint16_tvdd;/* vdd stores the bit number of the selected voltage range from below. */rt_uint8_tbus_mode;/* command output mode */#define MMCSD_BUSMODE_OPENDRAIN1#define MMCSD_BUSMODE_PUSHPULL2rt_uint8_tchip_select;/* SPI chip select */#define MMCSD_CS_IGNORE0#define MMCSD_CS_HIGH1#define MMCSD_CS_LOW2rt_uint8_tpower_mode;/* power supply mode */#define MMCSD_POWER_OFF0#define MMCSD_POWER_UP1#define MMCSD_POWER_ON2rt_uint8_tbus_width;/* data bus width */#define MMCSD_BUS_WIDTH_10#define MMCSD_BUS_WIDTH_42#define MMCSD_BUS_WIDTH_83};

结构体rt_mmcsd_host 描述了SDIO主机Host 应具有的属性和方法(SDIO这个名字出现较晚,很多地方仍用旧名MMC、SDMMC或MMCSD等),包括要访问的卡设备指针 *card、Host驱动层应实现的操作函数集合指针 *ops、Input/Output配置结构io_cfg(包括时钟频率、电源模式、总线模式及位宽等)、主机Host支持的有效工作电压valid_ocr、传输数据的DMA/Block配置、同步命令/响应的mutex/semaphore对象等。

最后几个成员变量是用于管理卡设备的中断处理过程的,sdio_irq_num为绑定到Host 的中断处理函数的数量,sdio_irq_sem 则用于通知线程 sdio_irq_thread 有中断被触发,sdio_irq_thread 是执行注册到Host 的中断处理函数的线程,当获得信号量sdio_irq_thread 后开始执行用户绑定的中断处理函数。

卡设备描述rt_mmcsd_card我们在下文介绍,这里先看主机Host 需要下驱动层实现并向其注册的操作函数有哪些:

// rt-thread-4.0.1\components\drivers\include\drivers\mmcsd_host.hstruct rt_mmcsd_host_ops {void (*request)(struct rt_mmcsd_host *host, struct rt_mmcsd_req *req);void (*set_iocfg)(struct rt_mmcsd_host *host, struct rt_mmcsd_io_cfg *io_cfg);rt_int32_t (*get_card_status)(struct rt_mmcsd_host *host);void (*enable_sdio_irq)(struct rt_mmcsd_host *host, rt_int32_t en);};

操作函数集合rt_mmcsd_host_ops中包含的接口函数功能介绍如下:

request 方法:用于Host 向 Card 发送Command / Data Request;set_iocfg 方法:用于配置Input/Output 结构io_cfg,包括时钟频率、电源模式、总线模式及位宽等;get_card_status 方法:获取卡设备的状态信息,尝试探测卡设备;enable_sdio_irq 方法:使能/禁用 SDIO 的中断请求功能。

2.1.2 rt_mmcsd_req 数据结构描述

接口函数request 用于Host 向 Card 发送Command / Data Request,参数rt_mmcsd_req 是如何描述的呢?

// rt-thread-4.0.1\components\drivers\include\drivers\mmcsd_core.hstruct rt_mmcsd_req {struct rt_mmcsd_data *data;struct rt_mmcsd_cmd *cmd;struct rt_mmcsd_cmd *stop;};struct rt_mmcsd_data {rt_uint32_t blksize;rt_uint32_t blks;rt_uint32_t *buf;rt_int32_t err;rt_uint32_t flags;#define DATA_DIR_WRITE(1 << 0)#define DATA_DIR_READ(1 << 1)#define DATA_STREAM(1 << 2)unsigned intbytes_xfered;struct rt_mmcsd_cmd*stop;/* stop command */struct rt_mmcsd_req*mrq;/* associated request */rt_uint32_t timeout_ns;rt_uint32_t timeout_clks;};struct rt_mmcsd_cmd {rt_uint32_t cmd_code;rt_uint32_t arg;rt_uint32_t resp[4];rt_uint32_t flags;/*rsponse types *bits:0~3*/#define RESP_MASK(0xF)#define RESP_NONE(0)#define RESP_R1(1 << 0)#define RESP_R1B(2 << 0)#define RESP_R2(3 << 0)#define RESP_R3(4 << 0)#define RESP_R4(5 << 0)#define RESP_R6(6 << 0)#define RESP_R7(7 << 0)#define RESP_R5(8 << 0)/*SDIO command response type*//*command types *bits:4~5*/#define CMD_MASK(3 << 4)/* command type */#define CMD_AC(0 << 4)#define CMD_ADTC(1 << 4)#define CMD_BC(2 << 4)#define CMD_BCR(3 << 4)#define resp_type(cmd)((cmd)->flags & RESP_MASK)/*spi rsponse types *bits:6~8*/#define RESP_SPI_MASK(0x7 << 6)#define RESP_SPI_R1(1 << 6)#define RESP_SPI_R1B(2 << 6)#define RESP_SPI_R2(3 << 6)#define RESP_SPI_R3(4 << 6)#define RESP_SPI_R4(5 << 6)#define RESP_SPI_R5(6 << 6)#define RESP_SPI_R7(7 << 6)#define spi_resp_type(cmd)((cmd)->flags & RESP_SPI_MASK)/** These are the command types.*/#define cmd_type(cmd)((cmd)->flags & CMD_MASK)rt_int32_t retries;/* max number of retries */rt_int32_t err;struct rt_mmcsd_data *data;struct rt_mmcsd_req*mrq;/* associated request */};

结构体rt_mmcsd_req包含了数据指针 *data、命令指针 *cmd、停止命令指针 *stop三部分,SDIO总线上传输的主要就是Data、Command/Response两部分,附加一个停止命令方便判断数据流或多个数据块的传输结束。

结构体rt_mmcsd_data包括了数据块大小与数量、缓冲区首地址与字节数、读写标志(包括数据流还是数据块的标志)、停止传输命令、与该数据对象相关的请求、超时等信息。

结构体rt_mmcsd_cmd包括了命令编码(比如CMD5)、命令参数、响应数据、命令类型与响应类型标志(命令/响应在博客:SD/MMC + SDIO中有详细介绍)、允许该命令尝试发送的最大次数、与该命令对象相关的数据、与该命令对象相关的请求等信息。

2.1.3 SDIO Bus 接口函数及初始化过程

SDIO Bus有了上面主机Host 和请求 Request 的数据结构描述,再加上 Host 驱动层提供的方法集合 rt_mmcsd_host_ops (下文介绍Host 驱动层时,会介绍其如何实现并注册给 Host 的),就可以对外提供一些访问接口,方便开发者配置管理这些结构体,并通过 SDIO Bus 访问 Card device。SDIO Core Layer为我们提供的访问接口如下:

// rt-thread-4.0.1\components\drivers\include\drivers\mmcsd_core.hint mmcsd_wait_cd_changed(rt_int32_t timeout);void mmcsd_host_lock(struct rt_mmcsd_host *host);void mmcsd_host_unlock(struct rt_mmcsd_host *host);void mmcsd_req_complete(struct rt_mmcsd_host *host);void mmcsd_send_request(struct rt_mmcsd_host *host, struct rt_mmcsd_req *req);rt_int32_t mmcsd_send_cmd(struct rt_mmcsd_host *host, struct rt_mmcsd_cmd *cmd, int retries);rt_int32_t mmcsd_go_idle(struct rt_mmcsd_host *host);rt_int32_t mmcsd_spi_read_ocr(struct rt_mmcsd_host *host, rt_int32_t high_capacity, rt_uint32_t *ocr);rt_int32_t mmcsd_all_get_cid(struct rt_mmcsd_host *host, rt_uint32_t *cid);rt_int32_t mmcsd_get_cid(struct rt_mmcsd_host *host, rt_uint32_t *cid);rt_int32_t mmcsd_get_csd(struct rt_mmcsd_card *card, rt_uint32_t *csd);rt_int32_t mmcsd_select_card(struct rt_mmcsd_card *card);rt_int32_t mmcsd_deselect_cards(struct rt_mmcsd_card *host);rt_int32_t mmcsd_spi_use_crc(struct rt_mmcsd_host *host, rt_int32_t use_crc);void mmcsd_set_chip_select(struct rt_mmcsd_host *host, rt_int32_t mode);void mmcsd_set_clock(struct rt_mmcsd_host *host, rt_uint32_t clk);void mmcsd_set_bus_mode(struct rt_mmcsd_host *host, rt_uint32_t mode);void mmcsd_set_bus_width(struct rt_mmcsd_host *host, rt_uint32_t width);void mmcsd_set_data_timeout(struct rt_mmcsd_data *data, const struct rt_mmcsd_card *card);rt_uint32_t mmcsd_select_voltage(struct rt_mmcsd_host *host, rt_uint32_t ocr);void mmcsd_change(struct rt_mmcsd_host *host);void mmcsd_detect(void *param);struct rt_mmcsd_host *mmcsd_alloc_host(void);void mmcsd_free_host(struct rt_mmcsd_host *host);int rt_mmcsd_core_init(void);// SD memory card APIint rt_mmcsd_blk_init(void);rt_int32_t rt_mmcsd_blk_probe(struct rt_mmcsd_card *card);void rt_mmcsd_blk_remove(struct rt_mmcsd_card *card);

SDIO Core Layer提供的访问接口及涉及到的结构体描述就构成了SDIO Bus Driver 部分,这里我们重点关注SDIO Card的SD 4-bit通信模式,先忽略跟SPI 模式和SD memory Card相关的接口函数与涉及到的结构体变量。

先从SDIO Core Layer 的初始化过程开始看:

// rt-thread-4.0.1\components\drivers\sdio\mmcsd_core.c\int rt_mmcsd_core_init(void){rt_err_t ret;/* initialize detect SD cart thread *//* initialize mailbox and create detect SD card thread */ret = rt_mb_init(&mmcsd_detect_mb, "mmcsdmb",&mmcsd_detect_mb_pool[0], sizeof(mmcsd_detect_mb_pool) / sizeof(mmcsd_detect_mb_pool[0]),RT_IPC_FLAG_FIFO);RT_ASSERT(ret == RT_EOK);ret = rt_mb_init(&mmcsd_hotpluge_mb, "mmcsdhotplugmb",&mmcsd_hotpluge_mb_pool[0], sizeof(mmcsd_hotpluge_mb_pool) / sizeof(mmcsd_hotpluge_mb_pool[0]),RT_IPC_FLAG_FIFO);RT_ASSERT(ret == RT_EOK);ret = rt_thread_init(&mmcsd_detect_thread, "mmcsd_detect", mmcsd_detect, RT_NULL, &mmcsd_stack[0], RT_MMCSD_STACK_SIZE, RT_MMCSD_THREAD_PREORITY, 20);if (ret == RT_EOK) {rt_thread_startup(&mmcsd_detect_thread);}rt_sdio_init();return 0;}INIT_PREV_EXPORT(rt_mmcsd_core_init);

函数rt_mmcsd_core_init 会被自动初始化组件自动调用,既然不必自己调用,我们看看在初始化过程中都做了什么。首先初始化了一个SDIO Card探测邮箱mmcsd_detect_mb和一个探测线程mmcsd_detect_thread,当探测到SDIO Card新增时,会调用相应的初始化函数完成新增SDIO Card的初始化。

SDIO Card是支持热插拔的,函数rt_mmcsd_core_init 也为热插拔功能初始化了一个邮箱mmcsd_hotpluge_mb ,通过定时轮询或中断触发方式检测到 SDIO Card 发生了热插拔事件,则会通过该邮箱通知主机Host 做出相应的处理。

接下来看SDIO Card探测线程都做了什么?

// rt-thread-4.0.1\components\drivers\sdio\mmcsd_core.cvoid mmcsd_detect(void *param){struct rt_mmcsd_host *host;rt_uint32_t ocr;rt_int32_t err;while (1) {if (rt_mb_recv(&mmcsd_detect_mb, (rt_ubase_t *)&host, RT_WAITING_FOREVER) == RT_EOK){if (host->card == RT_NULL){mmcsd_host_lock(host);mmcsd_power_up(host);mmcsd_go_idle(host);mmcsd_send_if_cond(host, host->valid_ocr);err = sdio_io_send_op_cond(host, 0, &ocr);if (!err){if (init_sdio(host, ocr))mmcsd_power_off(host);mmcsd_host_unlock(host);continue;}/* detect SD card */....../* detect mmc card */......mmcsd_host_unlock(host);}else{/* card removed */......rt_mb_send(&mmcsd_hotpluge_mb, (rt_uint32_t)host);}}}}void mmcsd_change(struct rt_mmcsd_host *host){rt_mb_send(&mmcsd_detect_mb, (rt_uint32_t)host);}

SDIO Card探测线程内部是一个死循环,等待接收来自探测邮箱mmcsd_detect_mb的消息,当该邮箱接收到消息后会调用SDIO Card的初始化函数,完成卡设备的初始化,卡设备的初始化流程已经在博客:SD/MMC + SDIO)中详细介绍过了。向探测邮箱mmcsd_detect_mb发送消息的函数是mmcsd_change,该函数应由开发者在新增或移除SDIO Card时调用,以便线程mmcsd_detect 能及时处理SDIO Card 的新增或移除。

如果读者熟悉其它的组件初始化函数,不难发现函数 rt_mmcsd_core_init 中并没有为主机Host 初始化一个对象,倒是提供了一个接口函数 mmcsd_alloc_host,从名字就能看出,该函数为我们创建并初始化一个 Host 对象,包括释放 Host 对象的接口函数 mmcsd_free_host,这两个函数需要我们在 Host 驱动层中主动调用。

从前面Linux总线设备驱动框架可以看出,SDIO Bus Driver 还应提供 match 方法,用来匹配 device 与 driver。RT-Thread将 match 方法放到了 SDIO Card device端,我们也稍后在下文介绍 match 方法。

2.2 SDIO Card Device & Driver

2.2.1 SDIO Card 数据结构描述

我们先从卡设备的数据结构描述开始入手:

// rt-thread-4.0.1\components\drivers\include\drivers\mmcsd_card.hstruct rt_mmcsd_card {struct rt_mmcsd_host *host;rt_uint32_trca;/* card addr */rt_uint32_tresp_cid[4];/* card CID register */rt_uint32_tresp_csd[4];/* card CSD register */rt_uint32_tresp_scr[2];/* card SCR register */rt_uint16_ttacc_clks;/* data access time by ns */rt_uint32_ttacc_ns;/* data access time by clk cycles */rt_uint32_tmax_data_rate;/* max data transfer rate */rt_uint32_tcard_capacity;/* card capacity, unit:KB */rt_uint32_tcard_blksize;/* card block size */rt_uint32_terase_size;/* erase size in sectors */rt_uint16_tcard_type;#define CARD_TYPE_MMC 0 /* MMC card */#define CARD_TYPE_SD1 /* SD card */#define CARD_TYPE_SDIO 2 /* SDIO card */#define CARD_TYPE_SDIO_COMBO 3 /* SD combo (IO+mem) card */rt_uint16_t flags;#define CARD_FLAG_HIGHSPEED (1 << 0) /* SDIO bus speed 50MHz */#define CARD_FLAG_SDHC (1 << 1) /* SDHC card */#define CARD_FLAG_SDXC (1 << 2) /* SDXC card */struct rt_sd_scrscr;struct rt_mmcsd_csdcsd;rt_uint32_ths_max_data_rate; /* max data transfer rate in high speed mode */rt_uint8_tsdio_function_num;/* totol number of SDIO functions */struct rt_sdio_cccr cccr; /* common card info */struct rt_sdio_ciscis; /* common tuple info */struct rt_sdio_function*sdio_function[SDIO_MAX_FUNCTIONS + 1]; /* SDIO functions (devices) */};#define SDIO_MAX_FUNCTIONS7

结构体rt_mmcsd_card 则包含了对应主机Host 对象的指针、卡设备的RCA/CID/CSD/SCR寄存器的值、数据访问时间和传输速率、卡容量/块大小/卡类型、卡速度与容量标志、高速模式下的最大数据传输速率、SDIO Card支持的功能设备数量及对象指针、CCCR(Card Common Control Registers) / CIS(Card Information Structure)等(卡设备的寄存器信息参考博客:SD/MMC + SDIO)。

一个 I/O Card 最多可以有 7 个 function device,比如我们常见的 Wi-Fi / BT二合一模块就是一个卡上有两个功能设备。对于 I/O Card 来说,我们通过 SDIO Bus 与卡设备通信,实际访问的是卡设备内包含的 Function device(SD memory card没有function device)。从具有多种 I/O 功能的SDIO卡内部CIA(Common I/O Area)映射图可以看出,CCCR卡通用控制寄存器相当于function device 0,所以sdio function devices数组成员数量为(SDIO_MAX_FUNCTIONS + 1)。

SD memory相关的比如CID/CSD/SCR寄存器不是本文关注点,我们重点关注跟 I/O Card相关的比如 rt_sdio_function 和 CCCR/CIS 等结构体的代码描述如下:

// rt-thread-4.0.1\components\drivers\include\drivers\mmcsd_card.h/* SDIO function devices */struct rt_sdio_function {struct rt_mmcsd_card*card;/* the card this device belongs to */rt_sdio_irq_handler_t*irq_handler;/* IRQ callback */rt_uint8_tnum;/* function number */rt_uint8_tfunc_code; /* Standard SDIO Function interface code */rt_uint16_tmanufacturer;/* manufacturer id */rt_uint16_tproduct;/* product id */rt_uint32_tmax_blk_size;/* maximum block size */rt_uint32_tcur_blk_size;/* current block size */rt_uint32_tenable_timeout_val; /* max enable timeout in msec */struct rt_sdio_function_tuple *tuples;void *priv;};typedef void (rt_sdio_irq_handler_t)(struct rt_sdio_function *);/* SDIO function CIS tuple (unknown to the core) */struct rt_sdio_function_tuple {struct rt_sdio_function_tuple *next;rt_uint8_t code;rt_uint8_t size;rt_uint8_t *data;};struct rt_sdio_cccr {rt_uint8_tsdio_version;rt_uint8_tsd_version;rt_uint8_tdirect_cmd:1,/* Card Supports Direct Commands during data transferonly SD mode, not used for SPI mode */multi_block:1, /* Card Supports Multi-Block */read_wait:1,/* Card Supports Read Waitonly SD mode, not used for SPI mode */suspend_resume:1, /* Card supports Suspend/Resumeonly SD mode, not used for SPI mode */s4mi:1, /* generate interrupts during a 4-bit multi-block data transfer */e4mi:1, /* Enable the multi-block IRQ during 4-bit transfer for the SDIO card */low_speed:1,/* Card is a Low-Speed card */low_speed_4:1; /* 4-bit support for Low-Speed cards */rt_uint8_tbus_width:1,/* Support SDIO bus width, 1:4bit, 0:1bit */cd_disable:1, /* Connect[0]/Disconnect[1] the 10K-90K ohm pull-up resistor on CD/DAT[3] (pin 1) of the card */power_ctrl:1, /* Support Master Power Control */high_speed:1; /* Support High-Speed */};struct rt_sdio_cis {rt_uint16_tmanufacturer;rt_uint16_tproduct;rt_uint16_tfunc0_blk_size;rt_uint32_tmax_tran_speed;};

SDIO卡的每个功能设备rt_sdio_function都包含所在卡设备的指针、中断回调函数指针(通过函数 sdio_attach_irq 绑定,在线程 host->sdio_irq_thread 中被调用)、功能设备数量、功能接口码、制造商ID与产品ID、最大块大小与当前块大小、功能元组等。结构体rt_sdio_function_tuple 被组织成一条单向链表,也即一个卡设备上的多个功能设备按链表形式组织管理。

结构体rt_sdio_function 中的功能编号、制造商ID与产品ID三个成员信息实际上是 function device 与 function driver 之间相互匹配的依据,二者这三个成员信息一致的话,说明是Device与Driver是相互匹配的。

2.2.2 SDIO Driver 数据结构描述

前面的Linux总线设备驱动模型提到,SDIO Driver至少应提供一个 probe 接口,用于探测并初始化卡设备,我们看看RT-Thread 为SDIO 提供的驱动接口有哪些:

// rt-thread-4.0.1\components\drivers\include\drivers\sdio.hstruct rt_sdio_driver{char *name;rt_int32_t (*probe)(struct rt_mmcsd_card *card);rt_int32_t (*remove)(struct rt_mmcsd_card *card);struct rt_sdio_device_id *id;};struct rt_sdio_device_id{rt_uint8_t func_code;rt_uint16_t manufacturer;rt_uint16_t product;};

结构体rt_sdio_driver 包含的接口函数只有两个:probe 用于探测并初始化卡设备;remove 用于移除并释放卡设备资源,这两个接口函数需要function device驱动(本文中指的是AP6181 SDIO设备的WLAN驱动) 实现并注册。除了两个就接口函数指针,rt_sdio_driver 还包括驱动名和设备ID,驱动名便于标识驱动,设备ID 则是为了方便匹配要驱动的设备。

2.2.3 SDIO Card接口函数及初始化过程

SD I/O Card 重点是 Input/Output,前面有了Core Layer 提供的接口函数做基础,SDIO Card在这些接口函数基础上根据自己的规范再进行封装处理,为我们更方便的访问 I/O function device 提供的新的接口函数如下:

// rt-thread-4.0.1\components\drivers\include\drivers\sdio.hrt_int32_t sdio_io_send_op_cond(struct rt_mmcsd_host *host,rt_uint32_t ocr,rt_uint32_t*cmd5_resp);rt_int32_t sdio_io_rw_direct(struct rt_mmcsd_card *card,rt_int32_t rw,rt_uint32_t fn,rt_uint32_t reg_addr,rt_uint8_t *pdata,rt_uint8_t raw);rt_int32_t sdio_io_rw_extended(struct rt_mmcsd_card *card,rt_int32_t rw,rt_uint32_t fn,rt_uint32_t addr,rt_int32_t op_code,rt_uint8_t *buf,rt_uint32_t blocks,rt_uint32_t blksize);rt_int32_t sdio_io_rw_extended_block(struct rt_sdio_function *func,rt_int32_trw,rt_uint32_t addr,rt_int32_top_code,rt_uint8_t *buf,rt_uint32_t len);rt_uint8_t sdio_io_readb(struct rt_sdio_function *func, rt_uint32_t reg,rt_int32_t *err);rt_int32_t sdio_io_writeb(struct rt_sdio_function *func, rt_uint32_t reg,rt_uint8_tdata);rt_uint16_t sdio_io_readw(struct rt_sdio_function *func,rt_uint32_t addr,rt_int32_t *err);rt_int32_t sdio_io_writew(struct rt_sdio_function *func,rt_uint16_t data,rt_uint32_t addr);rt_uint32_t sdio_io_readl(struct rt_sdio_function *func,rt_uint32_t addr,rt_int32_t *err);rt_int32_t sdio_io_writel(struct rt_sdio_function *func,rt_uint32_t data,rt_uint32_t addr);rt_int32_t sdio_io_read_multi_fifo_b(struct rt_sdio_function *func, rt_uint32_t addr,rt_uint8_t *buf,rt_uint32_t len);rt_int32_t sdio_io_write_multi_fifo_b(struct rt_sdio_function *func, rt_uint32_t addr,rt_uint8_t *buf,rt_uint32_t len);rt_int32_t sdio_io_read_multi_incr_b(struct rt_sdio_function *func, rt_uint32_t addr,rt_uint8_t *buf,rt_uint32_t len);rt_int32_t sdio_io_write_multi_incr_b(struct rt_sdio_function *func, rt_uint32_t addr,rt_uint8_t *buf,rt_uint32_t len); rt_int32_t init_sdio(struct rt_mmcsd_host *host, rt_uint32_t ocr);rt_int32_t sdio_attach_irq(struct rt_sdio_function *func,rt_sdio_irq_handler_t *handler);rt_int32_t sdio_detach_irq(struct rt_sdio_function *func);void sdio_irq_wakeup(struct rt_mmcsd_host *host);rt_int32_t sdio_enable_func(struct rt_sdio_function *func);rt_int32_t sdio_disable_func(struct rt_sdio_function *func);void sdio_set_drvdata(struct rt_sdio_function *func, void *data);void* sdio_get_drvdata(struct rt_sdio_function *func);rt_int32_t sdio_set_block_size(struct rt_sdio_function *func,rt_uint32_t blksize);rt_int32_t sdio_register_driver(struct rt_sdio_driver *driver);rt_int32_t sdio_unregister_driver(struct rt_sdio_driver *driver);void rt_sdio_init(void);

前面大部分都是跟 I/O 相关的,我们依然从SDIO的初始化过程开始看:

// rt-thread-4.0.1\components\drivers\sdio\sdio.crt_int32_t init_sdio(struct rt_mmcsd_host *host, rt_uint32_t ocr){rt_int32_t err;rt_uint32_t current_ocr;......current_ocr = mmcsd_select_voltage(host, ocr);......err = sdio_init_card(host, current_ocr);if (err)goto remove_card;return 0;remove_card:rt_free(host->card);host->card = RT_NULL;err:LOG_E("init SDIO card failed");return err;}

SDIO设备的初始化过程在博客:SD/MMC + SDIO中以流程图的形式介绍过了,这里是代码实现过程。

SDIO初始化函数init_sdio 被前面介绍的SDIO 探测线程mmcsd_detect 调用,在函数 init_sdio 被调用之前,先执行了CMD0或CMD52 I/O Reset命令(函数mmcsd_go_idle)、CMD8命令(函数mmcsd_send_if_cond)和CMD5命令(函数sdio_io_send_op_cond),Host 与 Card 协商出有效的工作电压,在函数 init_sdio 中选择前面协商出的有效工作电压,开始执行 SDIO 卡设备初始化过程(函数 sdio_init_card)。

// rt-thread-4.0.1\components\drivers\sdio\sdio.cstatic rt_int32_t sdio_init_card(struct rt_mmcsd_host *host, rt_uint32_t ocr){rt_int32_t err = 0;rt_int32_t i, function_num;rt_uint32_t cmd5_resp;struct rt_mmcsd_card *card;err = sdio_io_send_op_cond(host, ocr, &cmd5_resp);......if (controller_is_spi(host)) ......function_num = (cmd5_resp & 0x70000000) >> 28;card = rt_malloc(sizeof(struct rt_mmcsd_card));......rt_memset(card, 0, sizeof(struct rt_mmcsd_card));card->card_type = CARD_TYPE_SDIO;card->sdio_function_num = function_num;card->host = host;host->card = card;card->sdio_function[0] = rt_malloc(sizeof(struct rt_sdio_function));if (!card->sdio_function[0]){LOG_E("malloc sdio_func0 failed");err = -RT_ENOMEM;goto err1;}rt_memset(card->sdio_function[0], 0, sizeof(struct rt_sdio_function));card->sdio_function[0]->card = card;card->sdio_function[0]->num = 0;if (!controller_is_spi(host)) {err = mmcsd_get_card_addr(host, &card->rca);if (err)goto err2;mmcsd_set_bus_mode(host, MMCSD_BUSMODE_PUSHPULL);}if (!controller_is_spi(host)) {err = mmcsd_select_card(card);if (err)goto err2;}err = sdio_read_cccr(card);if (err)goto err2;err = sdio_read_cis(card->sdio_function[0]);if (err)goto err2;err = sdio_set_highspeed(card);if (err)goto err2;if (card->flags & CARD_FLAG_HIGHSPEED) mmcsd_set_clock(host, 50000000); else mmcsd_set_clock(host, card->cis.max_tran_speed);err = sdio_set_bus_wide(card);if (err)goto err2;for (i = 1; i < function_num + 1; i++) {err = sdio_initialize_function(card, i);if (err)goto err3;}/* register sdio card */err = sdio_register_card(card);if (err)goto err3;return 0;err3:if (host->card){for (i = 1; i < host->card->sdio_function_num + 1; i++){if (host->card->sdio_function[i]){sdio_free_cis(host->card->sdio_function[i]);rt_free(host->card->sdio_function[i]);host->card->sdio_function[i] = RT_NULL;rt_free(host->card);host->card = RT_NULL;break;}}}err2:if (host->card && host->card->sdio_function[0]){sdio_free_cis(host->card->sdio_function[0]);rt_free(host->card->sdio_function[0]);host->card->sdio_function[0] = RT_NULL;}err1:if (host->card)rt_free(host->card);err:LOG_E("error %d while initialising SDIO card", err); return err;}

SDIO卡设备初始化函数 sdio_init_card 中,完成结构体 rt_mmcsd_card 成员变量的初始化配置,发送CMD3命令(函数mmcsd_get_card_addr)获得卡设备的RCA(Relative Card Address),发送CMD7命令(函数mmcsd_select_card)选中卡设备,读取SDIO Card的CCCR与CIS信息,配置时钟频率与总线位宽,开始执行 SDIO function device 初始化过程(函数sdio_initialize_function)。

// rt-thread-4.0.1\components\drivers\sdio\sdio.cstatic rt_int32_t sdio_initialize_function(struct rt_mmcsd_card *card,rt_uint32_t func_num){rt_int32_t ret;struct rt_sdio_function *func;RT_ASSERT(func_num <= SDIO_MAX_FUNCTIONS);func = rt_malloc(sizeof(struct rt_sdio_function));if (!func){LOG_E("malloc rt_sdio_function failed");ret = -RT_ENOMEM;goto err;}rt_memset(func, 0, sizeof(struct rt_sdio_function));func->card = card;func->num = func_num;ret = sdio_read_fbr(func);if (ret)goto err1;ret = sdio_read_cis(func);if (ret)goto err1;card->sdio_function[func_num] = func;return 0;err1:sdio_free_cis(func);rt_free(func);card->sdio_function[func_num] = RT_NULL;err:return ret;}

SDIO function device初始化过程主要是完成每个功能结构体 rt_sdio_function 成员变量的初始化配置,然后读取 SDIO function device的FBR(Function Basic Registers)和CIS信息。

完成了SDIO Card 与function device 的初始化过程,接下来看card device 与 driver 的注册和匹配过程。

2.2.4 SDIO Card与Driver的注册和匹配过程

SDIO Card 初始化函数 sdio_init_card 在完成 function device 的初始化后,开始执行卡设备的注册过程,该过程的实现代码如下:

// rt-thread-4.0.1\components\drivers\sdio\sdio.cstatic rt_int32_t sdio_register_card(struct rt_mmcsd_card *card){struct sdio_card *sc;struct sdio_driver *sd;rt_list_t *l;sc = rt_malloc(sizeof(struct sdio_card));......sc->card = card;rt_list_insert_after(&sdio_cards, &sc->list);if (rt_list_isempty(&sdio_drivers))goto out;for (l = (&sdio_drivers)->next; l != &sdio_drivers; l = l->next){sd = (struct sdio_driver *)rt_list_entry(l, struct sdio_driver, list);if (sdio_match_card(card, sd->drv->id))sd->drv->probe(card);}out:return 0;}static rt_list_t sdio_cards = RT_LIST_OBJECT_INIT(sdio_cards);static rt_list_t sdio_drivers = RT_LIST_OBJECT_INIT(sdio_drivers);struct sdio_card{struct rt_mmcsd_card *card;rt_list_t list;};struct sdio_driver{struct rt_sdio_driver *drv;rt_list_t list;};

SDIO Card 注册过程实际上是将新增的卡设备对象 sdio_card 插入到RT-Thread管理的一个链表中,并没有将其注册到 I/O 设备管理层,因此通过命令 list_device 是看不到这里注册的卡设备的。

前面Linux总线设备驱动框架中提到,当有Device或Driver新注册时,会调用Driver 提供的 match 方法,尝试完成 Device 与 Driver 的匹配,接下来看 Device 与 Driver 的匹配过程是如何实现的:

// rt-thread-4.0.1\components\drivers\sdio\sdio.crt_inline rt_int32_t sdio_match_card(struct rt_mmcsd_card *card,const struct rt_sdio_device_id *id){rt_uint8_t num = 1;if ((id->manufacturer != SDIO_ANY_MAN_ID) && (id->manufacturer != card->cis.manufacturer))return 0;while (num <= card->sdio_function_num){if ((id->product != SDIO_ANY_PROD_ID) && (id->product == card->sdio_function[num]->product))return 1;num++;}return 0;}static struct rt_mmcsd_card *sdio_match_driver(struct rt_sdio_device_id *id){rt_list_t *l;struct sdio_card *sc;struct rt_mmcsd_card *card;for (l = (&sdio_cards)->next; l != &sdio_cards; l = l->next){sc = (struct sdio_card *)rt_list_entry(l, struct sdio_card, list);card = sc->card;if (sdio_match_card(card, id))return card;}return RT_NULL;}

从上面的两个match函数代码可以看出,Device与Driver的匹配是通过 rt_sdio_device_id 比对实现的。当有新的卡设备注册时,会遍历驱动链表,看是否有能与新注册的卡设备相匹配的驱动。当有新驱动注册时,则会遍历卡设备链表,看是否有能与新注册的驱动相匹配的功能设备。待Device 与 Driver 完成匹配后,会调用Driver 提供的 probe 接口函数,完成Device 的探测和初始化。

知道了卡设备的注册实现过程,应该能想到驱动的注册实现过程:

// rt-thread-4.0.1\components\drivers\sdio\sdio.crt_int32_t sdio_register_driver(struct rt_sdio_driver *driver){struct sdio_driver *sd;struct rt_mmcsd_card *card;sd = rt_malloc(sizeof(struct sdio_driver));if (sd == RT_NULL){LOG_E("malloc sdio driver failed");return -RT_ENOMEM;}sd->drv = driver;rt_list_insert_after(&sdio_drivers, &sd->list);if (!rt_list_isempty(&sdio_cards)){card = sdio_match_driver(driver->id);if (card != RT_NULL)return driver->probe(card);}return -RT_EEMPTY;}

驱动 rt_sdio_driver 的注册,包括要求的两个接口函数 probe和remove 的实现,都需要功能设备的驱动代码完成了,本文中指的是AP6181 Wi-Fi 驱动代码。但Pandora开发板提供的源码中并没有AP6181 WLAN驱动的源码,而是以库文件的形式提供的,我们没法看到 AP6181 WLAN驱动对接口函数 probe和remove 的实现及注册过程了。

2.3 SDIO Host Driver Layer

SDIO Host controller 驱动层是在前面介绍的SDIO Core Layer的下层,该层负责实现SDIO Bus Driver 需要的操作函数集合 rt_mmcsd_host_ops,并将其注册到 SDIO Core Layer 用来支持Core Layer API 的实现。

2.3.1 SDIO硬件设备数据结构描述

在介绍该层实现并注册接口函数 rt_mmcsd_host_ops 之前,先看看SDIO Host controller 驱动层是如何描述SDIO 设备的:

// libraries\HAL_Drivers\drv_sdio.cstruct rthw_sdio{struct rt_mmcsd_host *host;struct stm32_sdio_des sdio_des;struct rt_event event;struct rt_mutex mutex;struct sdio_pkg *pkg;};struct sdio_pkg{struct rt_mmcsd_cmd *cmd;void *buff;rt_uint32_t flag;};// libraries\HAL_Drivers\drv_sdio.hstruct stm32_sdio_des{struct stm32_sdio *hw_sdio;dma_txconfig txconfig;dma_rxconfig rxconfig;sdio_clk_get clk_get;};typedef rt_err_t (*dma_txconfig)(rt_uint32_t *src, rt_uint32_t *dst, int size);typedef rt_err_t (*dma_rxconfig)(rt_uint32_t *src, rt_uint32_t *dst, int size);typedef rt_uint32_t (*sdio_clk_get)(struct stm32_sdio *hw_sdio);struct stm32_sdio{volatile rt_uint32_t power;volatile rt_uint32_t clkcr;volatile rt_uint32_t arg;volatile rt_uint32_t cmd;......volatile rt_uint32_t fifo;};

结构体rthw_sdio 包含了主机Host 对象的指针 *host、SDIO设备描述结构体 sdio_des(包括SDIO Host controller 寄存器、DMA Tx/Rx 配置函数指针、获取SDIO时钟频率函数指针等成员)、用于同步命令/响应的事件集与互斥量对象、SDIO数据包结构体指针 *pkg(包含SDIO 命令结构体指针 *cmd、传输数据缓冲区首地址 *buff 及其必要的标识位信息)等成员。结构体 stm32_sdio 成员既然要储存SDIO Host controller 寄存器的值,就需要使用 volatile 关键字来修饰,实际内容等同于 SDCARD_INSTANCE_TYPE。

2.3.2 SDIO硬件驱动初始化过程

接下来看SDIO Host controller 驱动层是如何实现并注册函数集 rt_mmcsd_host_ops 的,先从该层的初始化过程看起:

// libraries\HAL_Drivers\drv_sdio.cint rt_hw_sdio_init(void){struct stm32_sdio_des sdio_des;SD_HandleTypeDef hsd;hsd.Instance = SDCARD_INSTANCE;{rt_uint32_t tmpreg = 0x00U;....../* enable DMA clock && Delay after an RCC peripheral clock enabling*/SET_BIT(RCC->AHB1ENR, sdio_config.dma_rx.dma_rcc);tmpreg = READ_BIT(RCC->AHB1ENR, sdio_config.dma_rx.dma_rcc);UNUSED(tmpreg); /* To avoid compiler warnings */}HAL_NVIC_SetPriority(SDIO_IRQn, 2, 0);HAL_NVIC_EnableIRQ(SDIO_IRQn);HAL_SD_MspInit(&hsd);sdio_des.clk_get = stm32_sdio_clock_get;sdio_des.hw_sdio = (struct stm32_sdio *)SDCARD_INSTANCE;sdio_des.rxconfig = DMA_RxConfig;sdio_des.txconfig = DMA_TxConfig;host = sdio_host_create(&sdio_des);if (host == RT_NULL){LOG_E("host create fail");return -1;}return 0;}INIT_DEVICE_EXPORT(rt_hw_sdio_init);/*** @brief This function get stm32 sdio clock.* @param hw_sdio: stm32_sdio* @retval PCLK2Freq*/static rt_uint32_t stm32_sdio_clock_get(struct stm32_sdio *hw_sdio){return HAL_RCC_GetPCLK2Freq();}static rt_err_t DMA_TxConfig(rt_uint32_t *src, rt_uint32_t *dst, int Size){SD_LowLevel_DMA_TxConfig((uint32_t *)src, (uint32_t *)dst, Size / 4);return RT_EOK;}static rt_err_t DMA_RxConfig(rt_uint32_t *src, rt_uint32_t *dst, int Size){SD_LowLevel_DMA_RxConfig((uint32_t *)src, (uint32_t *)dst, Size / 4);return RT_EOK;}

SDIO硬件驱动层设备的初始化可以分为三部分:一是完成SDIO 接口引脚的初始化(通过CubeMX 生成的函数HAL_SD_MspInit 实现)和SDIO 外设中断使能(通过函数HAL_NVIC_EnableIRQ 实现);二是完成结构体 stm32_sdio_des 的成员初始化配置(三个函数指针和一个HAL标准库提供的SDIO 寄存器结构体);三是创建 Host 对象(通过函数 sdio_host_create 实现)。函数 rt_hw_sdio_init 也被自动初始化组件调用,不需要开发者再主动调用了。

SDIO硬件接口引脚的初始化在下文介绍CubeMX配置时可以看到,结构体 stm32_sdio_des 中DMA Tx/Rx 配置函数主要完成SDIO DMA Tx/Rx 通道的参数配置,这里就不展开介绍了。获取时钟频率的函数 stm32_sdio_clock_get 值得我们注意,不同芯片使用的SDIO时钟源不同,该函数的实现也有差异,比如我们使用的STM32L475芯片中SDIO的时钟源就不一定是PCLK2,具体使用哪个时钟源在下文介绍 CubeMX 时钟树时可以看到。

下面继续看Host 对象的创建过程:

// libraries\HAL_Drivers\drv_sdio.c/*** @brief This function create mmcsd host.* @param sdio_des stm32_sdio_des* @retval rt_mmcsd_host*/struct rt_mmcsd_host *sdio_host_create(struct stm32_sdio_des *sdio_des){struct rt_mmcsd_host *host;struct rthw_sdio *sdio = RT_NULL;if ((sdio_des == RT_NULL) || (sdio_des->txconfig == RT_NULL) || (sdio_des->rxconfig == RT_NULL))return RT_NULL;sdio = rt_malloc(sizeof(struct rthw_sdio));if (sdio == RT_NULL)return RT_NULL;rt_memset(sdio, 0, sizeof(struct rthw_sdio));host = mmcsd_alloc_host();if (host == RT_NULL){LOG_E("L:%d F:%s mmcsd alloc host fail");rt_free(sdio);return RT_NULL;}rt_memcpy(&sdio->sdio_des, sdio_des, sizeof(struct stm32_sdio_des));sdio->sdio_des.hw_sdio = (sdio_des->hw_sdio == RT_NULL ? (struct stm32_sdio *)SDIO_BASE_ADDRESS : sdio_des->hw_sdio);sdio->sdio_des.clk_get = (sdio_des->clk_get == RT_NULL ? stm32_sdio_clk_get : sdio_des->clk_get);rt_event_init(&sdio->event, "sdio", RT_IPC_FLAG_FIFO);rt_mutex_init(&sdio->mutex, "sdio", RT_IPC_FLAG_FIFO);/* set host defautl attributes */host->ops = &ops;host->freq_min = 400 * 1000;host->freq_max = SDIO_MAX_FREQ;host->valid_ocr = 0X00FFFF80;/* The voltage range supported is 1.65v-3.6v */#ifndef SDIO_USING_1_BIThost->flags = MMCSD_BUSWIDTH_4 | MMCSD_MUTBLKWRITE | MMCSD_SUP_SDIO_IRQ;#elsehost->flags = MMCSD_MUTBLKWRITE | MMCSD_SUP_SDIO_IRQ;#endifhost->max_seg_size = SDIO_BUFF_SIZE;host->max_dma_segs = 1;host->max_blk_size = 512;host->max_blk_count = 512;/* link up host and sdio */sdio->host = host;host->private_data = sdio;rthw_sdio_irq_update(host, 1);/* ready to change */mmcsd_change(host);return host;}static const struct rt_mmcsd_host_ops ops ={rthw_sdio_request,rthw_sdio_iocfg,rthw_sd_delect,rthw_sdio_irq_update,};

在函数 sdio_host_create 中完成了 rt_mmcsd_host 成员的初始化配置,包括SDIO硬件设备驱动层实现的函数集合 rt_mmcsd_host_ops 的注册。有了该层向Host 注册的函数集合 rt_mmcsd_host_ops,SDIO Core Layer 提供的接口函数才能真是使用。

完成rt_mmcsd_host 成员的初始化配置后,通过函数 rthw_sdio_irq_update 使能SDIO 中断,并通知Core Layer 有新增卡设备。函数mmcsd_change 会向探测邮箱 mmcsd_detect_mb 发送刚完成配置的 Host 对象指针,探测线程 mmcsd_detect 则会接收到来自探测邮箱的 Host 对象指针,并开始SDIO卡设备的探测和初始化过程(前面已经介绍过了)。

三、SDIO 驱动配置

前面已经介绍了RT-Thread的SDIO设备对象是如何管理的,下面看看Pandora 开发板上的 AP6181 SDIO模块驱动如何配置。

3.1 SDIO CubeMX配置

AP6181 Wi-Fi 模块与Pandora开发板直接的硬件接线原理图在前面已经介绍过了,我们需要重点关注的分别是SDIO接口的6个引脚、WiFi INT引脚和WL_REG_ON引脚,后两个引脚可以通过PIN设备对象提供的接口函数来配置,我们只需要在CubeMX内配置SDIO接口的6个引脚即可,配置图示如下:

需要注意的是,SDIO的CMD引脚和D0 ~ D3 引脚需要配置为上拉模式,因为传输的命令/响应或者数据的开始位都是低电平信号,若按默认的悬空配置,可能会产生干扰信号,让SDIO设备不能稳定工作。

配置完SDIO总线引脚,接下来配置SDIO的时钟源和时钟频率,配置图示如下:

从上图可以看出,SDMMC使用的并不是PCLK2时钟,而是PLLSAI1Q时钟。SDMMC的高速模式最高支持50MHZ,Pandora开发板提供的外部高速晶振频率为8MHZ,无法通过倍频分频得到50MHZ,最接近的可以达到48MHZ,因此这里将PLLSAI1Q的时钟频率配置到48MHZ(通过配置倍频系数N和分频系数R的值实现)。当然也可以选择PLLQ时钟,为了达到STM32L475支持的最高频率80MHZ,PLLCLK时钟的倍频与分频系数已确定,PLLQ与PLLCLK共用倍频系数N,因此我们只能将PLLQ时钟配置到40MHZ,时钟频率不如PLLSAI1Q,因此我们选择SDMMC的时钟源为PLLSAI1Q。配置完SDMMC的时钟后,就可以通过GENERATE CODE生成代码了。

3.2 SDIO时钟配置

还记得前面介绍CPU架构与BSP移植过程的博客中提到的,需要将CubeMX生成的SystemClock_Config函数代码复制到 board.c 中。这里新增了SDIO时钟配置,所以需要更新board.c 中函数SystemClock_Config 的代码,也即直接将CubeMX生成的SystemClock_Config函数代码(在源文件.\board\CubeMX_Config\Src\main.c中)复制到board.c 中即可。函数SystemClock_Config中新增的关于SDMMC时钟的代码如下:

// projects\stm32l475_wifi_sample\board\board.c/*** @brief System Clock Configuration* @retval None*/void SystemClock_Config(void){....../* Initializes the CPU, AHB and APB busses clocks */....../* Initializes the CPU, AHB and APB busses clocks */......PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1|RCC_PERIPHCLK_SDMMC1;PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2;PeriphClkInit.Sdmmc1ClockSelection = RCC_SDMMC1CLKSOURCE_PLLSAI1;PeriphClkInit.PLLSAI1.PLLSAI1Source = RCC_PLLSOURCE_HSE;PeriphClkInit.PLLSAI1.PLLSAI1M = 1;PeriphClkInit.PLLSAI1.PLLSAI1N = 12;PeriphClkInit.PLLSAI1.PLLSAI1P = RCC_PLLP_DIV7;PeriphClkInit.PLLSAI1.PLLSAI1Q = RCC_PLLQ_DIV2;PeriphClkInit.PLLSAI1.PLLSAI1R = RCC_PLLR_DIV2;PeriphClkInit.PLLSAI1.PLLSAI1ClockOut = RCC_PLLSAI1_48M2CLK;if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK){Error_Handler();}/* Configure the main internal regulator output voltage */......}

前面介绍SDIO硬件设备驱动层在初始化结构体 stm32_sdio_des 时,需要提供一个获取SDIO时钟频率的函数指针,RT-Thread提供的默认函数是获取PCL2的时钟频率,但这里我们使用的是PLLSAI1Q时钟,我们应该如何获取PLLSAI1Q时钟频率呢?我们查看HAL标准库中与RCC相关的文件,从中找到跟获取RCC频率相关的函数声明如下:

// libraries\STM32L4xx_HAL\STM32L4xx_HAL_Driver\Src\stm32l4xx_hal_rcc.c/*** @brief Return the PCLK2 frequency.* @note Each time PCLK2 changes, this function must be called to update the* right PCLK2 value. Otherwise, any configuration based on this function will be incorrect.* @retval PCLK2 frequency in Hz*/uint32_t HAL_RCC_GetPCLK2Freq(void);// libraries\STM32L4xx_HAL\STM32L4xx_HAL_Driver\Src\stm32l4xx_hal_rcc_ex.c/*** @brief Return the peripheral clock frequency for peripherals with clock source from PLLSAIs* @note Return 0 if peripheral clock identifier not managed by this API* @param PeriphClk Peripheral clock identifier* This parameter can be one of the following values:* @arg @ref RCC_PERIPHCLK_RTC RTC peripheral clock* @arg @ref RCC_PERIPHCLK_ADC ADC peripheral clock* @arg @ref RCC_PERIPHCLK_I2C1 I2C1 peripheral clock* @arg @ref RCC_PERIPHCLK_I2C2 I2C2 peripheral clock* @arg @ref RCC_PERIPHCLK_I2C3 I2C3 peripheral clock* @arg @ref RCC_PERIPHCLK_LPTIM1 LPTIM1 peripheral clock* @arg @ref RCC_PERIPHCLK_LPTIM2 LPTIM2 peripheral clock* @arg @ref RCC_PERIPHCLK_LPUART1 LPUART1 peripheral clock* @arg @ref RCC_PERIPHCLK_RNG RNG peripheral clock* @arg @ref RCC_PERIPHCLK_SAI1 SAI1 peripheral clock (only for devices with SAI1)* @arg @ref RCC_PERIPHCLK_SDMMC1 SDMMC1 peripheral clock* @arg @ref RCC_PERIPHCLK_USART1 USART1 peripheral clock* @arg @ref RCC_PERIPHCLK_USART2 USART1 peripheral clock* @arg @ref RCC_PERIPHCLK_USART3 USART1 peripheral clock* @retval Frequency in Hz*/uint32_t HAL_RCCEx_GetPeriphCLKFreq(uint32_t PeriphClk);

从注释代码可知,函数HAL_RCCEx_GetPeriphCLKFreq 可以获得PLLSAIs 时钟的频率,我们使用的外设是SDMMC1,所以传入该函数的参数为 RCC_PERIPHCLK_SDMMC1,修改获取SDMMC时钟频率的函数代码如下:

// libraries\HAL_Drivers\drv_sdio.c/*** @brief This function get stm32 sdio clock.* @param hw_sdio: stm32_sdio* @retval PLLSAI1QFreq*/static rt_uint32_t stm32_sdio_clock_get(struct stm32_sdio *hw_sdio){return HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SDMMC1);}

3.3 WL_REG_ON引脚配置

在本文开头已经介绍过WL_REG_ON 引脚的作用,从WLAN Boot-Up时序图可以看出,WL_REG_ON 引脚应该SDIO 初始化前被拉高,我们将该引脚拉高的操作放到函数rt_hw_sdio_init 的最前面。由于WL_REG_ON 引脚并非所有SDIO 设备共有的,可能只是部分设备(比如AP6181)特有的,因此我们使用一个条件宏将WL_REG_ON 引脚拉高的操作包含在内。函数rt_hw_sdio_init 中新增代码如下:

// libraries\HAL_Drivers\drv_sdio.cint rt_hw_sdio_init(void){#ifdef BSP_USING_WIFI#include <drivers/pin.h>#define WIFI_REG_ON_PIN GET_PIN(D, 1)rt_pin_mode(WIFI_REG_ON_PIN, PIN_MODE_OUTPUT);rt_pin_write(WIFI_REG_ON_PIN, PIN_LOW);rt_thread_mdelay(1);rt_pin_write(WIFI_REG_ON_PIN, PIN_HIGH);#endifstruct stm32_sdio_des sdio_des;......}INIT_DEVICE_EXPORT(rt_hw_sdio_init);

从WLAN Boot-Up时序图得知,WL_REG_ON 引脚的拉高时间为 2个睡眠时钟周期(32.768KHZ)后,1.5ms之内,这里设置 1ms后拉高。可能读者发现不延迟等待直接拉高也可以,系统从上电启动开始,运行到此处应该已经够2个睡眠时钟周期了,所以也是可以正常运行的。

3.4 配置SDIO编译选项

配置好SDIO总线引脚及时钟后,要想使用RT-Thread提供的SDIO驱动代码,还需要定义宏BSP_USING_SDIO与RT_USING_SDIO,我们依然在Kconfig中配置SDIO外设编译选项,新增SDIO编译选项配置代码如下:

// projects\stm32l475_wifi_sample\board\Kconfigmenu "Hardware Drivers Config"......menu "On-chip Peripheral Drivers"......config BSP_USING_SDIObool "Enable SDIO"select RT_USING_SDIOdefault nendmenu......

保存配置,在env环境执行menuconfig命令,启用刚配置的 Enable SDIO 编译选项,配置图示如下:

保存配置后,RT-Thread SDIO驱动就可以正常使用了,AP6181 Wi-Fi 模组的SDIO 驱动部分也配置完成了。下一篇博客:WLAN管理框架 + AP6181(BCM43362) WiFi模块 将介绍 AP6181 WLAN驱动移植及WLAN框架部分,包括上层的LwIP协议栈移植。

本示例工程源码下载地址:/StreamAI/RT-Thread_Projects/tree/master/projects/stm32l475_wifi_sample

更多文章:

《RT-Thread Sample Project Source Code Based on STM32L475》《IOT-OS之RT-Thread(十六)— WLAN管理框架 + AP6181(BCM43362) WiFi模块》《STM32之CubeL4(四)— SD/MMC + SDIO + HAL》《IOT-OS之RT-Thread(十四)— AT命令集 + ESP8266 WiFi模块》《IOT-OS之RT-Thread(十二)— 驱动分层与主从分离思想》

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。