1. SD适配器简介
由于SylixOS的SD协议栈设计最开始是借鉴内核中的I2C和SPI框架的,具体的历史可以参见曾老师的博客http://blog.chinaunix.net/uid-30296321-id-5106707.html。所以SD同样使用适配器(Adapter)来表示具体的主控制器,在SylixOS中这是通过 LW_SD_ADAPTER 数据结构来表示的,如下所示:
typedef struct lw_sd_adapter { LW_BUS_ADAPTER SDADAPTER_busadapter; /* 总线节点 */ struct lw_sd_funcs *SDADAPTER_psdfunc; /* 总线适配器操作函数 */ LW_OBJECT_HANDLE SDADAPTER_hBusLock; /* 总线操作锁 */ INT SDADAPTER_iBusWidth; /* 总线位宽 */ #define SDBUS_WIDTH_1 0 #define SDBUS_WIDTH_4 2 #define SDBUS_WIDTH_8 4 #define SDBUS_WIDTH_4_DDR 8 #define SDBUS_WIDTH_8_DDR 16 LW_LIST_LINE_HEADER SDADAPTER_plineDevHeader; /* 设备链表 */ } LW_SD_ADAPTER, *PLW_SD_ADAPTER;
- SDADAPTER_psdfunc:适配器操作函数集,主要包括传输和控制两类接口,分别用于数据的传输和一些硬件上的控制设置,这个等会会详细讲解。
- SDADAPTER_iBusWidth:使用的SD总线线宽,对于eMMC类设备可能会支持8线以及DDR方式传输数据,在本次SD驱动中,我们只关心4线模式。
SD适配器中最重要的就是传输和控制,这在SylixOS下是通过 LW_SD_FUNCS 数据结构来表示的,其成员如下所示:
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:SD协议栈将需要发送的命令和数据封装成 LW_SD_MESSAGE 发送到底层驱动使用,这个数据结构我们在后续章节展开讲解。
- SDFUNC_pfuncMasterCtl:用于设置SD控制器,比如线宽、时钟频率等等。
2. 定义SD适配器操作函数集
我们定义一个全局的变量来表示操作函数集,如下所示:
static LW_SD_FUNCS sdfuncs = { .SDFUNC_pfuncMasterXfer = sdTransfer, .SDFUNC_pfuncMasterCtl = sdIoctl, };
定义变量的同时用 sdTransfer 和 sdIoctl 这两个函数来初始化这个变量,这两个函数目前我们先空实现,后面再一步步完善:
static int sdIoctl (PLW_SD_ADAPTER psdadapter, int cmd, long arg) { switch (cmd) { case SDBUS_CTRL_POWEROFF: // TODO:关闭电源 break; case SDBUS_CTRL_POWERUP: case SDBUS_CTRL_POWERON: // TODO:使能电源 break; case SDBUS_CTRL_SETBUSWIDTH: // TODO:设置线宽 break; case SDBUS_CTRL_SETCLK: // TODO:设置时钟频率 break; case SDBUS_CTRL_DELAYCLK: break; case SDBUS_CTRL_GETOCR: // TODO:获取支持的电压情况 *(UINT32 *)arg = SD_VDD_32_33 | SD_VDD_33_34; break; default: return -1; } return 0; } static int sdTransfer (PLW_SD_ADAPTER psdadapter, PLW_SD_DEVICE psddevice, PLW_SD_MESSAGE psdmsg, int num) { return 0; }
在sdIoctl中这里列出来了一些常用的cmd设置,但是在本次教程中并不是所有的cmd控制都需要实现,我们只需要实现如下三个:
- SDBUS_CTRL_SETBUSWIDTH:设置控制器线宽,arg的取值有 SDARG_SETBUSWIDTH_1、SDARG_SETBUSWIDTH_4、SDARG_SETBUSWIDTH_8等等。
- SDBUS_CTRL_SETCLK:设置时钟频率,arg的取值有400000、25000000、50000000等等。
- SDBUS_CTRL_GETOCR:获取控制器支持的工作电压,SD工作电压一般都在3.3v左右,所以这里我们固定返回 SD_VDD_32_33 | SD_VDD_33_34。
- 至于其他的cmd在本次教程中用不到,但是在实际的SD卡驱动中需要根据具体的硬件原理图来设置电源的使能和关闭等操作。
3. 创建SD适配器
初始化好操作函数集后,就可以通过 API_SdAdapterCreate 接口来创建SD适配器了,这个接口的函数原型如下:
LW_API INT API_SdAdapterCreate(CPCHAR pcName, PLW_SD_FUNCS psdfunc);
- pcName:SD总线名字,一般为”/bus/sd/xxx“,xxx根据控制器的序号来确定,本次我们使用的是控制器0,所以SD总线名字为”/bus/sd/0“。
- psdfunc:用于传入SD适配器操作函数集,这里我们设置为第2步中初始化的函数集。
根据上述信息,我们可以封装出sdDevCreate接口,如下所示:
int sdDevCreate (void) { // TODO:硬件初始化 // 创建SD适配器 API_SdAdapterCreate("/bus/sd/0", &sdfuncs); return 0; }
一些关于硬件的初始化,比如引脚复用、时钟树设置等等我们到后续在封装单独的接口,另外由于SD适配器是在SD协议栈内部使用的,一般不需要在驱动中显示的使用,所以不需要在驱动中再用额外的变量来保存创建出来的SD适配器,当然如果需要的话,内核也提供了 API_SdAdapterGet 接口来获取适配器:
LW_API PLW_SD_ADAPTER API_SdAdapterGet(CPCHAR pcName);
附源码:
/* * sd.c * * Created on: Apr 23, 2022 * Author: gewenbin */ #define __SYLIXOS_KERNEL #include <SylixOS.h> #include <linux/compat.h> static int sdIoctl (PLW_SD_ADAPTER psdadapter, int cmd, long arg) { switch (cmd) { case SDBUS_CTRL_POWEROFF: // TODO:关闭电源 break; case SDBUS_CTRL_POWERUP: case SDBUS_CTRL_POWERON: // TODO:使能电源 break; case SDBUS_CTRL_SETBUSWIDTH: // TODO:设置线宽 break; case SDBUS_CTRL_SETCLK: // TODO:设置时钟频率 break; case SDBUS_CTRL_DELAYCLK: break; case SDBUS_CTRL_GETOCR: // TODO:获取支持的电压情况 *(UINT32 *)arg = SD_VDD_32_33 | SD_VDD_33_34; break; default: return -1; } return 0; } static int sdTransfer (PLW_SD_ADAPTER psdadapter, PLW_SD_DEVICE psddevice, PLW_SD_MESSAGE psdmsg, int num) { return 0; } static LW_SD_FUNCS sdfuncs = { .SDFUNC_pfuncMasterXfer = sdTransfer, .SDFUNC_pfuncMasterCtl = sdIoctl, }; int sdDevCreate (void) { // TODO:硬件初始化 // 创建SD适配器 API_SdAdapterCreate("/bus/sd/0", &sdfuncs); return 0; }
评论