SD 非标准控制器驱动
概述
SD 非标准接口驱动编写需要用户自行管理大部分数据。一般编写顺序是首先初始化总线适配器的私有数据,例如进行寄存器地址映射和中断绑定等,然后进行硬件寄存器的初始化,最后向内核注册准备好的数据。
SD/SPI 总线适配器创建
对应不同传输方式需要创建不同的总线适配器,下面分别介绍 SD 总线适配器的创建以及 SPI 总线适配器的创建。
SD 总线适配器的创建
SD 总线适配器驱动相关信息位于“libsylixos/SylixOS/system/device/sd”下,其适配器创建函数原型如下:
#include <SylixOS.h>
INT API_SdAdapterCreate(CPCHAR pcName, PLW_SD_FUNCS psdfunc);
函数 API_SdAdapterCreate 原型分析:
- 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR 。
- pcName 参数是 SD 适配器的名称,即 shell 命令 buss 显示的名称。
- psdfunc 参数是 SD 总线传输函数集指针。
注意 :
psdfunc 会在 SDM 中持续引用,因此该对象需要持续有效。
函数 API_SdAdapterCreate 使用结构体 PLW_SD_FUNCS 来向内核提供传输函数集合,其详细描述如下:
#include <SylixOS.h>
typedef struct lw_sd_funcs {
INT (*SDFUNC_pfuncMasterXfer)(PLW_SD_ADAPTER psdadapter,
struct lw_sd_device *psddevice,
PLW_SD_MESSAGE psdmsg,
INT iNum);
INT (*SDFUNC_pfuncMasterCtl)(PLW_SD_ADAPTER psdadapter,
INT iCmd,
LONG lArg);
} LW_SD_FUNCS, *PLW_SD_FUNCS;
- SDFUNC_pfuncMasterXfer:主控传输函数,上层会直接调用此函数实现消息发送。第一个参数 psdadapter 为 SD 总线适配器指针,第二个参数 psddevice 为 SD 总线上的设备结构体指针,第三个参数 psdmsg 为需要传输的消息结构体首地址指针,第四个参数 iNum 为需要传输的消息个数,以上四个参数即可告知主控器如何实现数据传输。
- SDFUNC_pfuncMasterCtl:主控IO控制函数,用来实现与硬件控制器相关的控制。第一个参数 psdadapter 为 SD 总线适配器指针,第二个参数 iCmd 为控制命令,第三个参数 lArg 与 iCmd 相关,需要 IO 控制函数支持的功能如下表所示。
在这里需要说明的是在IO控制函数支持的功能中参数 SDBUS_CTRL_POWERUP 和 SDBUS_CTRL_POWERON 可能会使人产生误解,实际上参数 SDBUS_CTRL_POWERUP 和参数 SDBUS_CTRL_POWERON 只是一种功能的两种不同名称而已,主要与个人习惯相关,并无其他特殊区分和设计,在适配器中两者的实现一致即可。
需要支持的功能 | 含义 |
---|---|
SDBUS_CTRL_POWEROFF | 关闭电源 |
SDBUS_CTRL_POWERUP | 上电 |
SDBUS_CTRL_POWERON | 打开电源,作用同上 |
SDBUS_CTRL_SETBUSWIDTH | 设置总线位宽 |
SDBUS_CTRL_SETCLK | 设置时钟频率 |
SDBUS_CTRL_STOPCLK | 停止时钟 |
SDBUS_CTRL_STARTCLK | 使能时钟 |
SDBUS_CTRL_DELAYCLK | 时钟延迟 |
SDBUS_CTRL_GETOCR | 获取适配器电压 |
注册到内核的传输函数集合中要用到多种结构体, PLW_SD_ADAPTER 总线适配器结构体指针主要包含当前总线适配器节点信息, PLW_SD_DEVICE 总线设备结构体指针主要包含当前 SD 设备的相关信息, PLW_SD_MESSAGE 消息请求结构体指针作用是指向需要发送的消息缓冲区,提供以上三种结构体后控制器即可知道如何进行发送,各种结构体的详细描述如下:
首先介绍 SD 总线适配器结构体,该结构体详细描述如下:
#include <SylixOS.h>
typedef struct lw_sd_adapter {
LW_BUS_ADAPTER SDADAPTER_busadapter; /* 总线节点 */
struct lw_sd_funcs *SDADAPTER_psdfunc; /* 总线适配器操作函数 */
LW_OBJECT_HANDLE SDADAPTER_hBusLock; /* 总线操作锁 */
INT SDADAPTER_iBusWidth; /* 总线位宽 */
LW_LIST_LINE_HEADER SDADAPTER_plineDevHeader; /* 设备链表 */
} LW_SD_ADAPTER,*PLW_SD_ADAPTER;
- SDADAPTER_busadapter:系统总线节点结构体。
- SDADAPTER_psdfunc:指向总线适配器的操作函数,即 API_SdAdapterCreate 函数注册到 SDM 层的操作函数集指针。
- SDADAPTER_hBusLock:SD 总线锁,不需要手动处理。
- SDADAPTER_iBusWidth:总线位宽,其取值见下表。
- SDADAPTER_plineDevHeader:指向此适配器下挂载的设备链表,不需要手动处理。
位宽选项值 | 含义 |
---|---|
SDBUS_WIDTH_1 | 1 位位宽 |
SDBUS_WIDTH_4 | 4 位位宽 |
SDBUS_WIDTH_8 | 8 位位宽 |
SDBUS_WIDTH_4_DDR | 4 位双通道 |
SDBUS_WIDTH_8_DDR | 8 位双通道 |
SD 总线设备结构体详细描述如下:
#include <SylixOS.h>
typedef struct lw_sd_device {
PLW_SD_ADAPTER SDDEV_psdAdapter; /* 从属的SD适配器 */
LW_LIST_LINE SDDEV_lineManage; /* 设备挂载链 */
atomic_t SDDEV_atomicUsageCnt; /* 设备使用计数 */
UINT8 SDDEV_ucType;
UINT32 SDDEV_uiRCA; /* 设备本地地址 */
UINT32 SDDEV_uiState; /* 设备状态位标 */
LW_SDDEV_CID SDDEV_cid;
LW_SDDEV_CSD SDDEV_csd;
LW_SDDEV_SCR SDDEV_scr;
CHAR SDDEV_pDevName[LW_CFG_OBJECT_NAME_SIZE];
PVOID SDDEV_pvUsr; /* 设备用户数据 */
} LW_SD_DEVICE, *PLW_SD_DEVICE;
- SDDEV_psdAdapter:指向该设备所从属的适配器的指针:。
- SDDEV_lineManage:该设备在总线适配器中的一个节点,一般不需要手动处理。
- SDDEV_atomicUsageCnt:用来记录设备打开次数。
- SDDEV_ucType:设备类型,其类型如下表中所示。
设备类型值 | 含义 |
---|---|
SDDEV_TYPE_MMC | MMC 卡 |
SDDEV_TYPE_SDSC | SDSC 标准的卡 |
SDDEV_TYPE_SDHC | SDHC 标准的 SD 卡 |
SDDEV_TYPE_SDXC | SDXC 标准的 SD 卡 |
SDDEV_TYPE_SDIO | SDIO 卡 |
SDDEV_TYPE_COMM | 暂不支持 |
- SDDEV_uiRCA:卡相对地址。
- SDDEV_uiState:目前设备状态,其状态取值如下表中所示。
- SDDEV_cid:SD 卡的 CID 寄存器值。
- SDDEV_csd:SD 卡的 CSD 寄存器值。
- SDDEV_scr:SD 卡的 SCR 寄存器值。
- SDDEV_pDevName:设备名称。
- SDDEV_pvUsr:用户自定义数据。
设备状态值 | 含义 |
---|---|
SD_STATE_EXIST | 该设备已经存在 |
SD_STATE_WRTP | 暂无使用 |
SD_STATE_BAD | 暂无使用 |
SD_STATE_READONLY | 暂无使用 |
将要发送的数据存在于 SD 消息结构体中,其详细描述如下:
#include <SylixOS.h>
typedef struct lw_sd_message {
LW_SD_COMMAND *SDMSG_psdcmdCmd; /* 发送命令 */
LW_SD_DATA *SDMSG_psddata; /* 数据传输属性 */
LW_SD_COMMAND *SDMSG_psdcmdStop; /* 停止命令 */
UINT8 *SDMSG_pucRdBuffer; /* 请求缓冲(读缓冲) */
UINT8 *SDMSG_pucWrtBuffer; /* 请求缓冲(写缓冲) */
} LW_SD_MESSAGE, *PLW_SD_MESSAGE;
- SDMSG_psdcmdCmd:需要发送的命令结构体。
- SDMSG_psddata:需要发送的数据属性。
- SDMSG_psdcmdStop:如果命令发送完成后需要发送停止命令,则填充此指针。
- SDMSG_pucRdBuffer:数据读取缓冲区。
- SDMSG_pucWrtBuffer:数据发送缓冲区。
LW_SD_MESSAGE 结构体中封装了很多协议相关的结构体,下面我们按顺序介绍。首先介绍下第一个结构体 LW_SD_COMMAND ,该结构体内部封装了 SD 命令相关的信息,其结构体详细描述如下:
#include <SylixOS.h>
typedef struct lw_sd_command {
UINT32 SDCMD_uiOpcode; /* 操作码(命令) */
UINT32 SDCMD_uiArg; /* 参数 */
UINT32 SDCMD_uiResp[4]; /* 应答(有效位最多128位) */
UINT32 SDCMD_uiFlag; /* 属性位标 (命令和应答属性) */
UINT32 SDCMD_uiRetry;
} LW_SD_COMMAND, *PLW_SD_COMMAND;
- SDCMD_uiOpcode:需要发送的 SD 命令。
- SDCMD_uiArg:此项有无数数据与 SDCMD_uiOpcode 相关。
- SDCMD_uiResp:应答数据,最多 128 位。
- SDCMD_uiFlag:SD 命令类型及应答类型,取值如下表所示。
- SDCMD_uiRetry:消息重试次数。
命令及应答类型取值 | 含义 |
---|---|
SD_RSP_PRESENT | 有应答 |
SD_RSP_136 | 136 位应答 |
SD_RSP_CRC | 应大中包含有效 CRC |
SD_RSP_BUSY | 命令引起的应答是忙信号 |
SD_RSP_OPCODE | 应答中包含了发送的命令 |
SD_CMD_AC | 带有指定地址的命令 |
SD_CMD_ADTC | 带有地址,并伴随数据传输命令 |
SD_CMD_BC | 广播命令,无应答 |
SD_CMD_BCR | 广播命令 有应答 |
SD_RSP_SPI_S1 | (SPI 模式)包含一个状态字节 |
SD_RSP_SPI_S2 | (SPI 模式)有第二个字节 |
SD_RSP_SPI_B4 | (SPI 模式)有四个字节 |
SD_RSP_SPI_BUSY | (SPI 模式)卡发送的忙信号 |
SD 消息结构体中的第二个结构体是 LW_SD_DATA ,该结构体主要封装了与数据相关的信息,其详细描述如下:
#include <SylixOS.h>
typedef struct lw_sd_data {
UINT32 SDDAT_uiBlkSize;
UINT32 SDDAT_uiBlkNum;
UINT32 SDDAT_uiFlags;
} LW_SD_DATA, *PLW_SD_DATA;
- SDDAT_uiBlkSize:块大小。
- SDDAT_uiBlkNum:块数量。
- SDDAT_uiFlags:数据传输标志,其取值如下表所示。
数据传输标志 | 含义 |
---|---|
SD_DAT_WRITE | 此次传输需要写数据 |
SD_DAT_READ | 此次传输需要读数据 |
SD_DAT_STREAM | 此次多字节传输按流方式传输 |
SPI 总线适配器的创建
SPI 总线适配器驱动相关信息位于“libsylixos/SylixOS/system/device/spi”文件夹下,在这里我们不做讨论,需要注意的是 SylixOS 内部适配器是根据名称来进行匹配的,因此名称需要唯一,具体实现详细见:SPI 总线。
向 SDM 注册 HOST 信息
在向 SDM 层注册信息时需要使用 API_SdmHostRegister 函数并填充 SD_HOST 结构体,此结构体主要用来将主控器的相关信息报告给 SDM 层并统一管理。
注意:
SD_HOST 会在 SDM 中持续引用,因此该对象需要持续有效。
首先来分析下函数 API_SdmHostRegister,该函数原型如下:
#include <SylixOS.h>
PVOID API_SdmHostRegister(SD_HOST *psdhost);
函数 API_SdmHostRegister 原型分析:
- 此函数成功返回 SDM 控制器指针,失败返回 LW_NULL ,注意需要在主控器私有数据结构体中保存返回的 SDM 控制器指针,SylixOS 的 SD 设备热插拔事件通知需要通过它来实现。
- p sdhost 参数是指向 SD 主控器信息结构体的指针。
下面我们来分析下一个最重要的结构体 SD_HOST ,其详细描述如下:
#include <SylixOS.h>
struct sd_host {
CPCHAR SDHOST_cpcName;
INT SDHOST_iType;
INT SDHOST_iCapbility; /* 主动支持的特性 */
VOID (*SDHOST_pfuncSpicsEn)(SD_HOST *psdhost);
VOID (*SDHOST_pfuncSpicsDis)(SD_HOST *psdhost);
INT (*SDHOST_pfuncCallbackInstall)
(
SD_HOST *psdhost,
INT iCallbackType, /* 安装的回调函数的类型 */
SD_CALLBACK callback, /* 回调函数指针 */
PVOID pvCallbackArg /* 回调函数的参数 */
);
INT (*SDHOST_pfuncCallbackUnInstall)
(
SD_HOST *psdhost,
INT iCallbackType /* 安装的回调函数的类型 */
);
VOID (*SDHOST_pfuncSdioIntEn)(SD_HOST *psdhost, BOOL bEnable);
BOOL (*SDHOST_pfuncIsCardWp) (SD_HOST *psdhost);
VOID (*SDHOST_pfuncDevAttach)(SD_HOST *psdhost, CPCHAR cpcDevName);
VOID (*SDHOST_pfuncDevDetach)(SD_HOST *psdhost);
};
- SDHOST_cpcName:SD 主控器名称,用来查找注册到内核中的 SD/SPI 总线适配器,需要注意的是必须与“SD/SPI总线适配器创建”章节中注册的名称一致,一个适配器有且只有一个名字,并且只能由一个 SDM 主控器管理,只能挂载一个设备。
- SDHOST_iType:用来标注正在注册的总线适配器是 SD 总线还是 SPI 总线,详细取值如下表所示。
SD 主控器类型 | 含义 |
---|---|
SDHOST_TYPE_SD | SD 总线适配器 |
SDHOST_TYPE_SPI | SPI 总线适配器 |
- SDHOST_iCapbility:正在注册的总线适配器主动支持的某些特性,详细取值如下表所示。
SD 主控器支持的特性 | 含义 |
---|---|
SDHOST_CAP_HIGHSPEED | 支持高速传输 |
SDHOST_CAP_DATA_4BIT | 支持 4 位数据传输 |
SDHOST_CAP_DATA_8BIT | 支持 8 位数据传输 |
SDHOST_CAP_DATA_4BIT_DDR | 支持 4 位 ddr 数据传输 |
SDHOST_CAP_DATA_8BIT_DDR | 支持 8 位 ddr 数据传输 |
SDHOST_CAP_MMC_FORCE_1BIT | MMC 卡强制使用 1 位总线 |
- SDHOST_pfuncSpicsEn:仅供 SPI 总线模式下进行片选操作。
- SDHOST_pfuncSpicsDis:仅供 SPI 总线模式下进行片选取消操作。
- SDHOST_pfuncCallbackInstall:安装回调函数,其中参数 iCallbackType 取值如下表所示,目前需要支持的并不多,后续随着 SD Stack 的发展可动态扩展。
- SDHOST_pfuncCallbackUnInstall:注销安装回调函数,其中参数 iCallbackType 取值同上。
- SDHOST_pfuncSdioIntEn:使能 SDIO 中断。
- SDHOST_pfuncIsCardWp:判断该主控器上对应的卡是否写保护。
- SDHOST_pfuncDevAttach:添加设备。
- SDHOST_pfuncDevDetach:删除设备。
结构体 SD_HOST 中必须提供的成员有 SDHOST_cpcName、SDHOST_iType、SDHOST_pfuncCallbackInstall 和 SDHOST_pfuncCallbackUnInstall,若主控器为 SPI 总线,成员 SDHOST_pfuncSpicsEn 和 SDHOST_pfuncSpicsDis 也必须提供。
安装的回调函数类型 | 含义 |
---|---|
SDHOST_CALLBACK_CHECK_DEV | 卡状态监测 |
热插拔检测
热插拔检测可以通过中断或使用系统提供的 hotplugPollAdd 函数自动轮询检测,这里不建议采用中断方式。下面不讨论两种方法优劣,仅提供关于如何通知 SDM 层设备插入/拔出的方法,热插拔中需要使用两个函数,一个是 API_SdmEventNotify 函数,另一个是 SD_CALLBACK 的回调函数。
分析 API_SdmEventNotify 函数,其原型如下:
#include <SylixOS.h>
INT API_SdmEventNotify(PVOID pvSdmHost, INT iEvtType);
API_SdmEventNotify 函数原型分析:
- 函数成功返回 ERROR_NONE ,失败返回 PX_ERROR 。
- pvSdmHost 参数是“向SDM注册HOST信息”章节中函数 API_SdmHostRegister 的返回值。
- iEvtType 参数用来通知内核发生的事件类型,其取值如下表 SDM 事件类型所示。
SDM 事件类型 | 含义 |
---|---|
SDM_EVENT_DEV_INSERT | SD 设备插入 |
SDM_EVENT_DEV_REMOVE | SD 设备移除 |
SDM_EVENT_SDIO_INTERRUPT | SDIO 设备发生中断 |
SDM_EVENT_BOOT_DEV_INSERT | SD BOOT 设备插入 |
在热插拔检测函数中仅需用到两个宏,在检测到设备插入时用 SDM_EVENT_DEV_INSERT 通知 SDM 层设备插入,在检测到设备移除时用 SDM_EVENT_DEV_REMOVE 通知 SDM 层设备移除。
SD BOOT 设备意思是此设备需要或可以尽快初始化,常见的情况是焊在板子上的 MMC 设备或 SD 存储卡设备需要做根文件系统等情况。使用 SDM_EVENT_BOOT_DEV_INSERT 这个参数即可通知 SDM 层采用 SD BOOT 设备插入方式立即创建设备,而不是将设备创建放入闲时工作队列,相比于上面的设备创建方法有迅速创建设备,提高启动速度的优点,故推荐此种设备使用这个方法。
此函数的另外一个作用是在发生 SDIO 中断时使用 SDM_EVENT_SDIO_INTERRUPT 参数,通知 SDM 发生了 SDIO 中断,我们在下一章详细介绍。
SD_CALLBACK 回调函数实际上在内核中的真正函数如下:
#include <SylixOS.h>
static INT __sdCoreSdCallbackCheckDev (PVOID pvDevHandle, INT iDevSta);
- 函数调用成功返回 ERROR_NONE ,失败返回 PX_ERROR 。
- pvDevHandle 参数是 SDM 层调用回调函数保存在主控器私有数据结构体中的参数。
- DevSta 参数是将要设置的设备状态,取值如下表所示。
设备状态 | 含义 |
---|---|
SD_DEVSTA_UNEXIST | 设备已经不存在,即将删除 |
SD_DEVSTA_EXIST | 设备存在 |
在检测到设备插入后会自动创建 SD 设备结构体,此时设备已经存在,其默认状态为 SD_DEVSTA_EXIST ;在设备移除后需要通知系统设备已经不存在,即将删除,需要手动设置设备状态为 SD_DEVSTA_UNEXIST 。
需要注意的是在主控器私有数据结构体中需要提供 SD_CALLBACK 回调函数指针类型和 PVOID 类型两个成员来保存 SDM 注册的回调函数及其参数。
SDIO 中断的处理
由于 SDIO 中断的处理比较复杂,所以 SylixOS 建议的做法是在中断处理函数中将 SDIO 中断处理函数加入到 SylixOS 中断延迟处理队列(优先级很高,不会发生很久不处理的情况),在 SDIO 中断处理函数中使用通知函数通知 SDM 层发生中断。使用到的函数与热插拔一样为 API_SdmEventNotify,但参数为 SDM_EVENT_SDIO_INTERRUPT 。
SD 存储卡做根文件系统
SD 存储卡做根文件系统的情况下需要迅速的创建设备,因此不能等待优先级较低的热插拔线程执行,SylixOS 建议的做法是在 SD 控制器初始化末尾直接通知 SDM 层创建设备。使用到的函数依然是 API_SdmEventNotify,参数为 SDM_EVENT_BOOT_DEV_INSERT 。
SD 非标准接口驱动完整示例
本节给出 SD 非标准接口驱动的实现。
所用宏定义及全局变量定义如下:
#define __SYLIXOS_KERNEL
#include <SylixOS.h>
#include <stdio.h>
#include <string.h>
#define __SDHOST_NAME "/bus/sd/0"
#define __SDHOST_CDNAME "sd_cd0"
#define __SDHOST_WPNAME "sd_wp0"
#define __SDHOST_MAXNUM 1
#define __SDHOST_VECTOR_CH0 21 /* 中断号 */
#define __SDHOST_PHYADDR_CH0 0x5A000000 /* 寄存器物理地址 */
#define __EMMC_RESERVE_SECTOR 0
#ifndef GPIO_NONE
#define GPIO_NONE LW_CFG_MAX_GPIOS
#endif
#define SD_ERR(fmt, arg...) printk("[SD]"fmt, ##arg);
static __SD_CHANNEL _G_sdChannel[__SDHOST_MAXNUM];
控制器内部结构定义如下:
typedef struct {
SD_HOST SDCH_sdhost;
CHAR SDCH_cName[10];
PLW_SD_ADAPTER SDCH_psdadapter;
LW_SD_FUNCS SDCH_sdFuncs;
UINT32 SDCH_sdOCR; /* 电压及设备容量支持 */
PLW_JOB_QUEUE SDCH_jqDefer; /* SDIO 中断在中断延迟队列处理 */
addr_t SDCH_phyAddr; /* 控制器基地址 */
addr_t SDCH_virAddr;
UINT32 SDCH_uVector; /* 中断号 */
UINT32 SDCH_uiSdiCd; /* SD 卡使用的 CD 管脚 */
UINT32 SDCH_uiSdiWp; /* SD 卡使用的 WP 管脚 */
SD_CALLBACK SDCH_callbackChkDev;
PVOID SDCH_pvCallBackArg;
PVOID SDCH_pvSdmHost;
INT SDCH_iCardSta; /* 当前SD卡状态 */
} __SD_CHANNEL, *__PSD_CHANNEL;
SD 系统库初始化函数如下:
VOID sdiLibInit (VOID)
{
/*
* SDM系统层初始化,包含SD存储卡库的初始化
*/
API_SdmLibInit();
/*
* SDIO库初始化
*/
API_SdmSdioLibInit();
API_SdMemDrvInstall(); /* 安装 SD 存储卡驱动 */
API_SdioBaseDrvInstall(); /* 安装 SDIO 基础驱动 */
}
初始化函数主要完成 SD 存储卡库和 SDIO 库的初始化,SD 存储卡驱动和 SDIO 基础驱动的安装。
下面函数实现 SD 总线的初始化。
INT sdDrvInstall (INT iChannel,
UINT32 uiSdCdPin,
UINT32 uiSdWpPin,
BOOL bIsBootDev)
{
__PSD_CHANNEL pChannel;
INT iRet;
/*
* 初始化控制器软件资源
*/
iRet = __sdDataInit(iChannel, uiSdCdPin, uiSdWpPin);
if (iRet != ERROR_NONE) {
return (PX_ERROR);
}
/*
* 初始化控制器硬件资源
*/
iRet = __sdHwInit(iChannel);
if (iRet != ERROR_NONE) {
goto __errb1;
}
/*
* 创建SD总线适配器
*/
pChannel = &_G_sdChannel[iChannel];
iRet = API_SdAdapterCreate(pChannel->SDCH_cName, &pChannel->SDCH_sdFuncs);
if (iRet != ERROR_NONE) {
SD_ERR("%s err:fail to create sd adapter!\r\n", __func__);
goto __errb1;
}
pChannel->SDCH_psdadapter = API_SdAdapterGet(pChannel->SDCH_cName);
/*
* 向 SDM 注册 HOST 信息
*/
pChannel->SDCH_pvSdmHost = API_SdmHostRegister(&pChannel->SDCH_sdhost);
if (!pChannel->SDCH_pvSdmHost) {
SD_ERR("%s err:fail to register SDM host!\r\n", __func__);
goto __errb2;
}
/*
* SDM层扩展参数设置,设置触发块大小,提高读写效率,默认64,此处仅作示例
*/
API_SdmHostExtOptSet(pChannel->SDCH_pvSdmHost,
SDHOST_EXTOPT_MAXBURST_SECTOR_SET,
128);
/*
* BOOT 设备特殊处理: 在当前线程(而非热插拔线程)直接创建设备, 提高启动速度
*/
if (bIsBootDev) {
iRet = API_SdmEventNotify(pChannel->SDCH_pvSdmHost, SDM_EVENT_BOOT_DEV_INSERT);
pChannel->SDCH_iCardSta = 1;
if (iRet) {
SD_ERR("%s err:fail to create boot device!\r\n", __func__);
pChannel->SDCH_iCardSta = 0;
}
/*
* CPU直接从SD卡启动情况可以使用此额外选项避开u-boot段等,其他情况建议使用分区
*/
API_SdmHostExtOptSet(pChannel->SDCH_pvSdmHost,
SDHOST_EXTOPT_RESERVE_SECTOR_SET,
__EMMC_RESERVE_SECTOR);
}
/*
* 加入到内核热插拔检测线程
*/
hotplugPollAdd((VOIDFUNCPTR)__sdCdHandle, (PVOID)pChannel);
return (ERROR_NONE);
__errb2:
API_SdAdapterDelete(pChannel->SDCH_cName);
__errb1:
__sdDataDeinit(iChannel);
return (PX_ERROR);
}
函数中调用的控制器软硬件资源初始化函数如下:
static INT __sdDataInit (INT iChannel, UINT32 uiSdCdPin, UINT32 uiSdWpPin)
{
__PSD_CHANNEL pChannel;
SD_HOST *pSdHost;
LW_SD_FUNCS *pSdFuncs;
INT iRet;
CHAR cSdCdName[10] = __SDHOST_CDNAME;
CHAR cSdWpName[10] = __SDHOST_WPNAME;
pChannel = &_G_sdChannel[iChannel];
pSdHost = &pChannel->SDCH_sdhost;
pSdFuncs = &pChannel->SDCH_sdFuncs;
pChannel->SDCH_uiSdiCd = uiSdCdPin; /* 保存两个管脚号 */
pChannel->SDCH_uiSdiWp = uiSdWpPin;
snprintf(pChannel->SDCH_cName, sizeof(__SDHOST_NAME) - 1, __SDHOST_NAME);
/* 名字非常重要,不能重复 */
switch (iChannel) {
case 0:
pChannel->SDCH_phyAddr = __SDHOST_PHYADDR_CH0; /* 初始化内存基地址 */
pChannel->SDCH_uVector = __SDHOST_VECTOR_CH0;
break;
case 1:
break;
}
iRet = API_InterVectorConnect(pChannel->SDCH_uVector, /* 绑定中断处理函数 */
__sdIrq,
pChannel,
"sd_isr");
if (iRet) {
SD_ERR("%s err:Vector setup fail!\r\n", __func__);
goto __erra1;
}
pChannel->SDCH_virAddr = (addr_t)API_VmmIoRemapNocache((PVOID)pChannel->SDCH_phyAddr,
LW_CFG_VMM_PAGE_SIZE);
/* 映射寄存器地址到虚拟内存 */
if (!pChannel->SDCH_virAddr) {
SD_ERR("%s err:No more space!\r\n", __func__);
goto __erra1;
}
cSdCdName[sizeof(__SDHOST_CDNAME) - 2] += iChannel;
cSdWpName[sizeof(__SDHOST_WPNAME) - 2] += iChannel;
if (uiSdCdPin != GPIO_NONE && uiSdCdPin != 0) { /* 申请热插拔检测管脚 */
iRet = API_GpioRequestOne(uiSdCdPin, LW_GPIOF_IN, cSdCdName);
if (iRet != ERROR_NONE) {
SD_ERR("%s err:failed to request gpio %d!\r\n", __func__, uiSdCdPin);
goto __erra2;
}
}
if (uiSdWpPin != GPIO_NONE && uiSdWpPin != 0) { /* 申请写保护检测管脚 */
iRet = API_GpioRequestOne(uiSdWpPin, LW_GPIOF_IN, cSdWpName);
if (iRet != ERROR_NONE) {
SD_ERR("%s err:failed to request gpio %d!\r\n", __func__, uiSdWpPin);
goto __erra3;
}
}
pChannel->SDCH_jqDefer = API_InterDeferGet(0); /* 获取 CPU0 中断延迟处理队列 */
pChannel->SDCH_cName[sizeof(__SDHOST_NAME) - 2] += iChannel;
/* 对名称敏感,不能同名 */
pChannel->SDCH_iCardSta = 0; /* 初始化状态为卡未插入 */
pChannel->SDCH_sdOCR = SD_VDD_32_33 | /* OCR中包含主控制器的电压支持 */
SD_VDD_33_34 | /* 情况,还有设备容量支持情况 */
SD_VDD_34_35 |
SD_VDD_35_36;
/*
* 注册SD总线需要使用的结构体
*/
pSdFuncs->SDFUNC_pfuncMasterCtl = __sdIoCtl;
pSdFuncs->SDFUNC_pfuncMasterXfer = __sdTransfer;
/*
* 注册到SDM层需要的结构体
*/
pSdHost->SDHOST_cpcName = pChannel->SDCH_cName;
pSdHost->SDHOST_iType = SDHOST_TYPE_SD;
pSdHost->SDHOST_iCapbility = SDHOST_CAP_DATA_4BIT;
pSdHost->SDHOST_pfuncCallbackInstall = __sdCallBackInstall;
pSdHost->SDHOST_pfuncCallbackUnInstall = __sdCallBackUnInstall;
pSdHost->SDHOST_pfuncSdioIntEn = __sdSdioIntEn;
return (ERROR_NONE);
__erra3:
API_GpioFree(uiSdCdPin);
__erra2:
API_VmmIoUnmap((PVOID)pChannel->SDCH_virAddr);
__erra1:
return (PX_ERROR);
}
static INT __sdHwInit (INT iChannel)
{
__PSD_CHANNEL pChannel;
addr_t atRegAddr;
pChannel = &_G_sdChannel[iChannel];
atRegAddr = pChannel->SDCH_virAddr;
/*
* 根据控制器不同实现不同
*/
return (ERROR_NONE);
}
回收 SD 控制器软件资源的实现如下:
static VOID __sdDataDeinit (INT iChannel)
{
__PSD_CHANNEL pChannel;
pChannel = &_G_sdChannel[iChannel];
API_GpioFree(pChannel->SDCH_uiSdiWp);
API_GpioFree(pChannel->SDCH_uiSdiCd);
API_VmmIoUnmap((PVOID)pChannel->SDCH_virAddr);
API_InterVectorDisconnect(pChannel->SDCH_uVector, __sdIrq, pChannel);
memset(pChannel, 0, sizeof(__SD_CHANNEL));
}
初始化 SD 控制器软件资源函数 __sdDataInit 中,注册的 __sdIoCtl 函数和 __sdTransfer 函数实现如下:
static INT __sdIoCtl (PLW_SD_ADAPTER psdadapter,
INT iCmd,
LONG lArg)
{
__SD_CHANNEL *pChannel;
INT iError = ERROR_NONE;
INT iNum;
iNum = psdadapter->SDADAPTER_busadapter.BUSADAPTER_cName
[sizeof(__SDHOST_NAME) - 2]- '0';
pChannel = &_G_sdChannel[iNum]; /* 用名称来判断是哪一个主控器 */
switch (iCmd) {
case SDBUS_CTRL_POWEROFF:
break;
case SDBUS_CTRL_POWERUP: /* 这两个功能相同 */
case SDBUS_CTRL_POWERON:
break;
case SDBUS_CTRL_SETBUSWIDTH:
break;
case SDBUS_CTRL_SETCLK:
break;
case SDBUS_CTRL_DELAYCLK:
break;
case SDBUS_CTRL_GETOCR:
*(UINT32 *)lArg = pChannel->SDCH_sdOCR;
iError = ERROR_NONE;
break;
default:
SD_ERR("%s error : can't support this cmd.\r\n", __func__);
iError = PX_ERROR;
break;
}
return (iError);
}
static INT __sdTransfer (PLW_SD_ADAPTER psdadapter,
PLW_SD_DEVICE psddevice,
PLW_SD_MESSAGE psdmsg,
INT iNum)
{
INT iCount = 0;
__SD_CHANNEL *pChannel;
INT iCh;
iCh = psdadapter->SDADAPTER_busadapter.BUSADAPTER_cName
[sizeof(__SDHOST_NAME) - 2] - '0';
pChannel = &_G_sdChannel[iCh];
while (iCount < iNum && psdmsg != NULL) {
/*
* 板级相关发送处理
*/
if (psdmsg->SDMSG_psdcmdStop) { /* 如果有停止命令则随后发送 */
}
iCount++;
psdmsg++;
}
return (ERROR_NONE);
}
__sdIoCtl 函数实现了 SD 主控 IO 控制,__sdTransfer 函数实现了 SD 主控传输。
初始化 SD 控制器软件资源时安装的回调函数如下:
static INT __sdCallBackInstall (SD_HOST *pHost,
INT iCallbackType,
SD_CALLBACK callback,
PVOID pvCallbackArg)
{
__SD_CHANNEL *pChannel = (__SD_CHANNEL *)pHost;
if (!pChannel) {
return (PX_ERROR);
}
if (iCallbackType == SDHOST_CALLBACK_CHECK_DEV) {
pChannel->SDCH_callbackChkDev = callback;
pChannel->SDCH_pvCallBackArg = pvCallbackArg;
}
return (ERROR_NONE);
}
下面函数用于卸载已安装的回调函数。
static INT __sdCallBackUnInstall (SD_HOST *pHost,
INT iCallbackType)
{
__SD_CHANNEL *pChannel = (__SD_CHANNEL *)pHost;
if (!pChannel) {
return (PX_ERROR);
}
if (iCallbackType == SDHOST_CALLBACK_CHECK_DEV) {
pChannel->SDCH_callbackChkDev = NULL;
pChannel->SDCH_pvCallBackArg = NULL;
}
return (ERROR_NONE);
}
使能 SDIO 中断的函数结构如下:
static VOID __sdSdioIntEn (SD_HOST *pHost, BOOL bEnable)
{
/*
* 自定义中断使能实现方法
*/
}
热插拔处理函数如下:
static VOID __sdCdHandle(PVOID pvArg)
{
INT iStaCurr;
__PSD_CHANNEL pChannel = (__PSD_CHANNEL)pvArg; /* 自行实现判断卡状态 */
iStaCurr = __sdStatGet(pChannel);
if (iStaCurr ^ pChannel->SDCH_iCardSta) { /* 状态变化 */
if (iStaCurr) { /* 插入状态 */
API_SdmEventNotify(pChannel->SDCH_pvSdmHost, SDM_EVENT_DEV_INSERT);
} else { /* 移除状态 */
if (pChannel->SDCH_callbackChkDev) {
pChannel->SDCH_callbackChkDev(pChannel->SDCH_pvCallBackArg, SDHOST_DEVSTA_UNEXIST);
}
API_SdmEventNotify(pChannel->SDCH_pvSdmHost, SDM_EVENT_DEV_REMOVE);
}
pChannel->SDCH_iCardSta = iStaCurr;
}
}
SDIO 中断处理函数如下:
static VOID __sdSdioIntHandle (PVOID pvArg)
{
__PSD_CHANNEL pChannel;
pChannel = (__PSD_CHANNEL)pvArg;
API_SdmEventNotify(pChannel->SDCH_pvSdmHost, SDM_EVENT_SDIO_INTERRUPT);
}
SD 控制器中断处理函数如下:
static irqreturn_t __sdIrq (PVOID pvArg, ULONG ulVector)
{
__PSD_CHANNEL pChannel;
pChannel = (__PSD_CHANNEL)pvArg;
/*
* 自行实现中断状态判断,如果是 SDIO 中断,则加入到中断延迟处理队列进行处理
*/
API_InterDeferJobAdd(pChannel->SDCH_jqDefer,
__sdSdioIntHandle,
(PVOID)pChannel);
return (LW_IRQ_HANDLED);
}
查看设备状态的函数实现如下,函数通过 SD 卡的检测管脚,检测 SD 卡是否存在。
static INT __sdStatGet (__PSD_CHANNEL pChannel)
{
UINT32 uiValue = 0;
if (pChannel->SDCH_uiSdiCd != GPIO_NONE && pChannel->SDCH_uiSdiCd != 0) {
uiValue = API_GpioGetValue(pChannel->SDCH_uiSdiCd);
} else {
uiValue = 0;
}
return (uiValue == 0);
}