首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

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

  • 24-03-03 17:01
  • 4279
  • 6134
blog.csdn.net

文章目录

  • 一、AP6181 Wi-Fi模块简介
    • 1.1 AP6181 硬件接口
    • 1.2 AP6181 驱动层级
  • 二 SDIO设备对象管理
    • 2.1 SDIO Bus Driver
      • 2.1.1 Host 数据结构描述
      • 2.1.2 rt_mmcsd_req 数据结构描述
      • 2.1.3 SDIO Bus 接口函数及初始化过程
    • 2.2 SDIO Card Device & Driver
      • 2.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 Layer
      • 2.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硬件接线原理图
正基公司的 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系统框图
从上面 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模块的启动时序图:
WLAN Boot-Up Sequence
从上图可以看出,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协议栈的层级关系图如下:
AP6181 驱动层级图
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.h
rt-thread-4.0.1\src\device.c

// 设备驱动框架层
rt-thread-4.0.1\components\drivers\include\drivers\sdio.h
rt-thread-4.0.1\components\drivers\sdio\sdio.c
rt-thread-4.0.1\components\drivers\include\drivers\mmcsd_card.h
rt-thread-4.0.1\components\drivers\include\drivers\mmcsd_core.h
rt-thread-4.0.1\components\drivers\include\drivers\mmcsd_cmd.h
rt-thread-4.0.1\components\drivers\sdio\mmcsd_core.c
rt-thread-4.0.1\components\drivers\include\drivers\mmcsd_host.h

// 设备驱动层
libraries\HAL_Drivers\drv_sdio.h
libraries\HAL_Drivers\drv_sdio.c
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

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 的设备驱动分离,符合编写代码的高内聚、低耦合原则。
Linux总线设备驱动模型
一个总线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.h

struct 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_t	max_seg_size;	/* maximum size of one dma segment */
	rt_uint32_t	max_dma_segs;	/* maximum number of dma segments in one request */
	rt_uint32_t	max_blk_size;   /* maximum block size */
	rt_uint32_t	max_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_t	clock;			/* clock rate */
	rt_uint16_t	vdd;			/* vdd stores the bit number of the selected voltage range from below. */

	rt_uint8_t	bus_mode;		/* command output mode */
#define MMCSD_BUSMODE_OPENDRAIN	1
#define MMCSD_BUSMODE_PUSHPULL	2

	rt_uint8_t	chip_select;		/* SPI chip select */
#define MMCSD_CS_IGNORE		0
#define MMCSD_CS_HIGH		1
#define MMCSD_CS_LOW		2

	rt_uint8_t	power_mode;		/* power supply mode */
#define MMCSD_POWER_OFF		0
#define MMCSD_POWER_UP		1
#define MMCSD_POWER_ON		2

	rt_uint8_t	bus_width;		/* data bus width */
#define MMCSD_BUS_WIDTH_1		0
#define MMCSD_BUS_WIDTH_4		2
#define MMCSD_BUS_WIDTH_8		3
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

结构体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.h

struct 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);
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

操作函数集合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.h

struct 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 int		bytes_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 */
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80

结构体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.h

int 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 API
int 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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

函数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.c

void 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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

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.h

struct rt_mmcsd_card {
	struct rt_mmcsd_host *host;
	rt_uint32_t	rca;		/* card addr */
	rt_uint32_t	resp_cid[4];	/* card CID register */
	rt_uint32_t	resp_csd[4];	/* card CSD register */
	rt_uint32_t	resp_scr[2];	/* card SCR register */

	rt_uint16_t	tacc_clks;	/* data access time by ns */
	rt_uint32_t	tacc_ns;	/* data access time by clk cycles */
	rt_uint32_t	max_data_rate;	/* max data transfer rate */
	rt_uint32_t	card_capacity;	/* card capacity, unit:KB */
	rt_uint32_t	card_blksize;	/* card block size */
	rt_uint32_t	erase_size;	/* erase size in sectors */
	rt_uint16_t	card_type;
#define CARD_TYPE_MMC                   0 /* MMC card */
#define CARD_TYPE_SD                    1 /* 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_scr	scr;
	struct rt_mmcsd_csd	csd;
	rt_uint32_t     hs_max_data_rate;  /* max data transfer rate in high speed mode */

	rt_uint8_t      sdio_function_num;	/* totol number of SDIO functions */
	struct rt_sdio_cccr    cccr;  /* common card info */
	struct rt_sdio_cis     cis;  /* common tuple info */
	struct rt_sdio_function	*sdio_function[SDIO_MAX_FUNCTIONS + 1]; /* SDIO functions (devices) */
};

#define SDIO_MAX_FUNCTIONS		7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

结构体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_t		num;		/* function number */

	rt_uint8_t		func_code;   /*  Standard SDIO Function interface code  */
	rt_uint16_t		manufacturer;		/* manufacturer id */
	rt_uint16_t		product;		/* product id */

	rt_uint32_t		max_blk_size;	/* maximum block size */
	rt_uint32_t		cur_blk_size;	/* current block size */

	rt_uint32_t		enable_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_t		sdio_version;
	rt_uint8_t		sd_version;
	rt_uint8_t		direct_cmd:1,     /*  Card Supports Direct Commands during data transfer
	                                               only SD mode, not used for SPI mode */
				multi_block:1,    /*  Card Supports Multi-Block */
				read_wait:1,      /*  Card Supports Read Wait
				                       only SD mode, not used for SPI mode */
				suspend_resume:1, /*  Card supports Suspend/Resume
				                       only 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_t		bus_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_t		manufacturer;
	rt_uint16_t		product;
	rt_uint16_t		func0_blk_size;
	rt_uint32_t		max_tran_speed;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64

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.h

struct 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;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

结构体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.h

rt_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_t               rw,
                              rt_uint32_t              addr,
                              rt_int32_t               op_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_t               data);
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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

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

// rt-thread-4.0.1\components\drivers\sdio\sdio.c

rt_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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

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.c

static 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114

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.c

static 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

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.c

static 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;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

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.c

rt_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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

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

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

// rt-thread-4.0.1\components\drivers\sdio\sdio.c

rt_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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

驱动 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.c

struct 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.h

struct 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;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

结构体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.c

int 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

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_BIT
    host->flags = MMCSD_BUSWIDTH_4 | MMCSD_MUTBLKWRITE | MMCSD_SUP_SDIO_IRQ;
#else
    host->flags = MMCSD_MUTBLKWRITE | MMCSD_SUP_SDIO_IRQ;
#endif
    host->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,
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

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

配置完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 */
  ......
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

前面介绍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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

从注释代码可知,函数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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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.c

int rt_hw_sdio_init(void)
{
#ifdef BSP_USING_WIFI

    #include 
    #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);

#endif

    struct stm32_sdio_des sdio_des;
    ......
}
INIT_DEVICE_EXPORT(rt_hw_sdio_init);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

从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\Kconfig

menu "Hardware Drivers Config"
......
menu "On-chip Peripheral Drivers"
    ......
    config BSP_USING_SDIO
            bool "Enable SDIO"
            select RT_USING_SDIO
            default n

endmenu
......
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

保存配置,在env环境执行menuconfig命令,启用刚配置的 Enable SDIO 编译选项,配置图示如下:
启用SDIO编译选项
保存配置后,RT-Thread SDIO驱动就可以正常使用了,AP6181 Wi-Fi 模组的SDIO 驱动部分也配置完成了。下一篇博客:WLAN管理框架 + AP6181(BCM43362) WiFi模块 将介绍 AP6181 WLAN驱动移植及WLAN框架部分,包括上层的LwIP协议栈移植。

本示例工程源码下载地址:https://github.com/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(十二)— 驱动分层与主从分离思想》
注:本文转载自blog.csdn.net的流云IoT的文章"https://blog.csdn.net/m0_37621078/article/details/105097567"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top