SD设备包括SD存储设备和SDIO设备两种。
SD设备一般支持SD总线接口和SPI总线接口两种工作方式,两者的硬件接口不同,如图 1-1所示。
图 1-1 SD硬件接口
本文目前涵盖的内容如下:
SD存储设备。
SD设备的SD总线接口。
2.1 SD 协议栈功能
SD协议栈负责SD设备驱动管理,创建、删除设备节点,实现传输、控制等功能。
2.2 SD 协议栈框架 图 2-1 SD协议栈结构
SD协议栈的结构如图 2-1所示,分为三个层次,各个层次独立实现各自的功能,每个层次之间通过统一的接口进行调用。
这样做的优势:完全隔离Client层和Host层,使两者的开发相互独立。
Client层无需Host层的任何信息,仅仅处理具体应用协议。
Core层处理核心的设备管理和SD、SPI传输协议。
Host层仅仅处理硬件传输相关的工作。
3.1 Core核心层
Core核心层中有以下几个概念:
SD适配器 操作管理SD总线的抽象单元,对于SD总线的传输操作都通过SD适配器完成。
SDM 设备 SD 存储设备管理的抽象单元,负责控制器设备和Core设备的管理。
SD Core设备 SD设备的抽象,具体实现SD设备创建、销毁和传输等逻辑,传输时再具体调用SD适配器传输接口或SPI适配器传输接口。
3.1.1 SD适配器操作接口
SD总线是由SD适配器进行管理和操作的,SD总线适配器本身是一个链表的结构。
SD设备驱动首先应该创建SD总线适配器,SD设备都是挂在SD总线适配器链表上,通过适配器的接口来控制总线的,关系如图 3-1所示。
图 3-1适配器和设备的关系
1. 创建SD适配器
#include <SylixOS.h> LW_API INT API_SdAdapterCreate (CPCHAR pcName, PLW_SD_FUNCS psdfunc);
创建过程中创建SD总线控制块、SD总线锁,并将SD适配器加入到系统总线层链表。
2. 删除SD适配器
#include <SylixOS.h> LW_API INT API_SdAdapterDelete (CPCHAR pcName);
删除过程中将SD适配器从总线层链表中删除,并且删除SD总线锁。
3. 获取SD适配器
#include <SylixOS.h> LW_API INT API_SdAdapterDelete (CPCHAR pcName);
获取SD适配器的过程就是从总线层链表中拿到SD总线控制块。
3.1.2 SDM操作接口
1. 初始化SDM组建库
#include <SylixOS.h> LW_API INT API_SdmLibInit (VOID);
函数具体操作如下:
调用API_SdLibInit()初始化SD组建库。
初始化SDM自身的组建库。
2. 处理Host通知事件
#include <SylixOS.h> LW_API INT API_SdmEventNotify (POVID pvSdmHost, INT iEvtType);
主要的逻辑如代码清单 1所示。
代码清单 1 Host处理通知事件
switch(iEvtType){ case SDM_EVENT_BOOT_DEV_INSERT: __sdmDevCreate(psdmhost); break; case SDM_EVENT_DEV_INSERT: iError = hotplugEvent((VOIDFUNCPTR)__sdmDevCreate,(PVOID)psdmhost,0,0,0,0,0); break; case SDM_EVENT_DEV_REMOVE: iError = hotplugEvent((VOIDFUNCPTR)__sdmDevDelete,(PVOID)psdmhost,0,0,0,0,0); break; …… }
相应的,例如当SD设备作为BOOT设备插入时,驱动中应该有如代码清单 2的代码,这样系统就会在SD设备插入后调用__sdmDevCreate创建SDM设备。
代码清单 2 BOOT设备创建
if(bIsBootDev){ /* * BOOT设备特殊处理: * 在当前线程(而非热插拔线程)直接创建设备,提供启动速度 */ API_SdmEventNotify(pvSdmHost, SDM_EVENT_BOOT_DEV_INSERT); }
3. 创建SDM设备
#include <SylixOS.h> static VOID __sdmDevCreate (__SDM_HOST *psdmhost);
该函数调用了API_SdCoreDevCreate函数创建SDM设备。
SD存储设备插入后系统处理流程如图 3-2所示。
图 3-2 SD存储设备插入后系统处理流程
4. 删除SDM设备
#include <SylixOS.h> static VOID __sdmDevDelete (__SDM_HOST *psdmhost);
3.1.3 SD Core操作接口
SD Core设备是SD设备在Core层中的抽象实现,每一个SD设备都会对应一个SD Core设备。
1. 创建核心SD设备
#include <SylixOS.h> LW_API PLW_SDCORE_DEVICE API_SdCoreDevCreate (INT iAdpaterType, CPCHAR pcAdpaterName, CPCHAR pcDeviceName, PLW_SDCORE_CHAN psdcorechan);
函数实现的主要功能如下:
调用sdLib库中的API_SdDeviceCreate创建SD设备;
注册SD总线传输、控制、删除的函数。
2. 删除核心SD设备
#include <SylixOS.h> LW_API INT API_SdCoreDevDelete (PLW_SDCORE_DEVICE psdcoredevice);
3.1.4 SdLib具体实现
1. 初始化SD组建库
#include <SylixOS.h> LW_API INT API_SdLibInit (VOID);
2. 创建一个SD设备
#include <SylixOS.h> LW_API PLW_SD_DEVICE API_SdDeviceCreate (CPCHAR pcAdapterName, CPCHAR pcDeviceName);
其中核心的逻辑是将SD Memory设备链入适配器的设备链表。
3. 删除SD设备
#include <SylixOS.h> LW_API INT API_SdDeviceDelete (PLW_SD_DEVICE psddevice);
其中核心的逻辑是将SD Memory设备从适配器链表中删除。
3.1.5 SD总线传输
SD Xfer实现的是SD模式下的传输接口。
在系统中总线结构基本上都需要实现以下两个接口:
SDFUNC_pfuncMasterXfer——用于实现总线传输
SDFUNC_pfuncMasterCtl ——用于实现总线控制
在以上两个接口基础上,SD Xfer层实现了传输、控制、删除函数。
1. 对设备发送一次请求
#include <SylixOS.h> LW_API INT API_SdDeviceTransfer (PLW_SD_DEVICE psddevice, PLW_SD_MESSAGE psdmsg, INT iNum);
其中核心的逻辑是调用Host层中的SDHCI接口__sdhciTransfer。
2. 对设备发送一次控制请求
#include <SylixOS.h> LW_API INT API_SdDeviceCtl (PLW_SD_DEVICE psddevice, INT iCmd, LONG lArg);
3.2 Client设备层
Client层实现具体的SD设备应用协议。Client层的源码文件结构如图 3-3所示。
图 3-3 Client层源码文件
3.2.1 SD Memory存储设备接口
SD Memory处理和SD存储卡(包括SD卡、MMC卡、EMMC)相关的具体设备协议。
当SD存储卡作为启动设备或热插拔设备接入后,系统会创建SD Memory设备并进行初始化,最后调用文件系统接口完成文件系统挂载的工作。
1. sdmemoryDrv.c中主要的函数和功能
(1)驱动安装
代码清单 3驱动安装函数
LW_API INT API_SdMemDrvInstall (VOID) { _G_sddrvMem.SDDRV_cpcName ="/dev/sdmem"; _G_sddrvMem.SDDRV_pfuncDevCreate = __sdmemDevCreate; _G_sddrvMem.SDDRV_pfuncDevDelete = __sdmemDevDelete; API_SdmSdDrvRegister(&_G_sddrvMem); return(ERROR_NONE); }
当SD Memory设备接入时,系统会调用__sdmemDevCreate函数;当SD Memory设备移除时会调用__sdmemDevDelete函数。
(2)创建并初始化SD Memory设备,并挂载文件系统
(3)销毁SD Memory设备,并卸载文件系统
2.sdmemory.c中主要的函数和功能
sdmemory.c实现了SD Memory设备创建、删除、初始化、设备控制、文件系统读写接口等具体应用的逻辑。
(1)创建并初始化SD Memory设备
#include <SylixOS.h> LW_API PLW_BLK_DEV API_SdMemDevCreate (INT iAdapterType, CPCHAR pcAdapterName, CPCHAR pcDeviceName, LW_SDMEM_CHAN psdmemchan);
(2)删除SD Memory设备
#include <SylixOS.h> LW_API INT API_SdMemDevDelete (PLW_BLK_DEV pblkdevice);
(3) 初始化SD Memory设备
SD Memory设备初始化具体流程可以参考SD标准协议,详见4参考资料。
(4)SD Memory设备的块读取
代码清单4 SD Memory设备的块读取
static INT __sdMemBlkRd (__PSD_BLK_DEV psdblkdevice, VOID *pvRdBuffer, ULONG ulStartBlk, ULONG ulBlkCount) { …… if (ulBlkCount <=1){ iError = __sdMemRdSingleBlk(psdcoredevice,(UINT8 *)pvRdBuffer,(UINT32)ulStartBlk); }else{ iError = __sdMemRdMultiBlk(psdcoredevice,(UINT8 *)pvRdBuffer, (UINT32)ulStartBlk,(UINT32)ulBlkCount); } …… }
其中核心的实现如代码清单 4所示,根据读取块的数量选择调用单块读取命令或多块读取命令。
单块读取的流程如图 3-4所示。
图 3-4单块读取流程
多块读取的流程如图 3-5所示。
图 3-5多块读取流程
(5)SD Memory设备的块写入
代码清单 5 SD Memory设备的块写入
static INT __sdMemBlkWrt (__PSD_BLK_DEV psdblkdevice, VOID *pvWrtBuffer, ULONG ulStartBlk, ULONG ulBlkCount) { …… if (ulBlkCount <=1){ iError = __sdMemWrtSingleBlk(psdcoredevice,(UINT8 *)pvWrtBuffer,(UINT32)ulStartBlk); }else{ iError = __sdMemWrtMultiBlk(psdcoredevice,(UINT8 *)pvWrtBuffer, (UINT32)ulStartBlk,(UINT32)ulBlkCount); } …… }
其中核心的实现如代码清单 5所示,根据写入块的数量选择调用单块写入命令或多块写入命令。
单块写的流程如图 3-6所示。
图 3-6单块写流程
多块写的流程如图 3-7所示,在每块传输应答之后需要发送Stop标记。
图 3-7多块写流程
3.3 Host层
3.3.1 SD Host接口
目前驱动编写的思路是抽取出和SDHCI标准协议不兼容的部分在BSP包中实现。
3.3.2 SDHCI标准协议接口
1. 创建Host
#include <SylixOS.h> LW_API PVOID API_SdhciHostCreate (CPCHAR pcAdapterName, PLW_SDHCI_HOST_ATTR psdhcihostattr);
标准的SD协议要求驱动实现8bits/16bits/32bits读接口和8bits/16bits/32bits写接口,目前在sdhci.c中实现了标准的读写接口。
但是不同的芯片可能有自己不同的寄存器访问驱动,所以在标准协议接口中需要判断采用标准的读写接口还是驱动自己各异的驱动接口。
2. 总线传输
图 3-8传输流程
总线传输如图 3-8所示,分为准备、传输、等待、清理四个阶段。
3.3.3 BSP包中的驱动实现
1. 驱动初始化函数
代码清单 7驱动初始化
static INT __imx6SdhciIoDrvInit (PLW_SDHCI_HOST_ATTR psdhcihostattr) { static SDHCI_DRV_FUNCS sdhcidrvfunc; sdhcidrvfunc.sdhciReadB = __imx6SdhciReadB; sdhcidrvfunc.sdhciReadW = __imx6SdhciReadW; sdhcidrvfunc.sdhciReadL = __imx6SdhciReadL; sdhcidrvfunc.sdhciWriteB = __imx6SdhciWriteB; sdhcidrvfunc.sdhciWriteW = __imx6SdhciWriteW; sdhcidrvfunc.sdhciWriteL = __imx6SdhciWriteL; psdhcihostattr->SDHCIHOST_pdrvfuncs =&sdhcidrvfunc; return(ERROR_NONE); }
这个函数中注册了驱动中自己实现的读写接口,所以当系统调用到Host层最终的控制传输或数据传输时,会调用驱动自己实现的读写接口。
2. 注册SD设备驱动
代码清单 8注册SD设备驱动
INT imx6ulSdiDrvInstall (UINT uiChannel, UINT32 uiSdCdPin, UINT32 uiSdWpPin, BOOL bIsBootDev) { /* * 创建HOST */ pvSdhciHost = API_SdhciHostCreate(psdichannel->SDICH_cpcHostName,&psdichannel->SDICH_sdhciattr); /* * 获取创建的HOST */ pvSdmHost = API_SdhciSdmHostGet(pvSdhciHost); /* * 调用API_SdmEventNotify创建设备 * 或者在hotplug线程中创建设备 */ …… }
创建设备时,若是BOOT设备则在当前线程调用API_SdmEventNotify,若是热插拔线程,则在热插拔扫描函数中调用API_SdmEventNotify。
4. 参考资料《SD Specifications Part 1 Physical Layer Simplified Specification Version 5.00 August 10, 2016》
《SD Specifications Part A2 SD Host Controller Simplified Specification Version 3.00 February 25, 2011》