SD 非标准控制器驱动

更新时间:
2024-03-14
下载文档

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 为控制命令,第三个参数 lArgiCmd 相关,需要 IO 控制函数支持的功能如下表所示。

在这里需要说明的是在IO控制函数支持的功能中参数 SDBUS_CTRL_POWERUPSDBUS_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_11 位位宽
SDBUS_WIDTH_44 位位宽
SDBUS_WIDTH_88 位位宽
SDBUS_WIDTH_4_DDR4 位双通道
SDBUS_WIDTH_8_DDR8 位双通道

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_MMCMMC 卡
SDDEV_TYPE_SDSCSDSC 标准的卡
SDDEV_TYPE_SDHCSDHC 标准的 SD 卡
SDDEV_TYPE_SDXCSDXC 标准的 SD 卡
SDDEV_TYPE_SDIOSDIO 卡
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_136136 位应答
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_SDSD 总线适配器
SDHOST_TYPE_SPISPI 总线适配器
  • 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_1BITMMC 卡强制使用 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_INSERTSD 设备插入
SDM_EVENT_DEV_REMOVESD 设备移除
SDM_EVENT_SDIO_INTERRUPTSDIO 设备发生中断
SDM_EVENT_BOOT_DEV_INSERTSD 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);
}
文档内容是否对您有所帮助?
有帮助
没帮助