SD卡(Secure Digital Memory Card)具有体积小、容量大、数据传输快、可插拔、安全性好等优点,被广泛应用于便携式设备上。例如作为数码相机的存储卡,作为手机、平板多媒体扩展卡用的TF卡(micro sd)。笔者此处就S5PV210的 sd卡驱动实现作一个简单的介绍。

1. sd卡概述

sd卡技术是在MMC卡的基础上发展起来的,其尺寸与MMC卡一样,只是比MMC卡厚了0.7mm,因此sd设备可以识别并存取MMC卡。sd卡接口除了保留MMC卡的7针外,还在两边加了2针,作为数据线,目的是通过把传输方式由串行变成并行,以提高传输速率。此时的规范为sd1.0版本,最高容量只能到4GB。为了跟进产品的更新换代,sd联合协会在06年发布了容量更大、存储更快的下一代sd卡规范sd2.0。该规范重新定义了sd卡的速度等级,分为三档:Class 2、4、6,分别对应写入速度2MB/s、4MB/s、6MB/s。根据卡容量又分为标准卡(小于2GB)和高容量卡(2GB~32GB),目前市面上应用的sd卡绝大部分都是sd2.0版本的卡。为了让储存卡更加迷你,通过sd卡规范标准,又衍生了MiniSD卡和Micro SD卡,这些卡均比标准sd卡尺寸小,通过sd转接卡可以当作一般的sd卡使用。尤其是Micro SD卡,可以算是最小的存储卡了,超小的体积可以极大的节省消费电子产品内部设计的空间,基本目前的android手机均是选用Micro SD卡作为多媒体扩展储存卡。随着科技的进步,sd2.0规范sd卡也渐渐无法满足应用的需求,在10年sd联合协会又发布了新的sd3.0规范,该规范定义了sdxc和uhs,并增加了Class10,容量范围为32GB~2TB。在sdxc卡仍需进一步坐等其价格下降的情况下,sd4.0规范已经开始在紧张的制订中,这已超出本文的讨论范围内了。

2. sd卡驱动编写

sd卡共支持三种传输模式:spi模式、1位sd模式、4位sd模式。所有的sd卡都必须支持较老的spi/mmc模式,这个模式支持慢速的四线spi接口,使很多微控制器都可以通过spi或模拟spi接口来读写sd卡。由于S5PV210具有sd卡主机控制器,并且支持sd2.0标准,此处只分析4位sd模式、sd2.0及sd1.0版本的sd卡驱动实现,sd2.0以上版本sd卡、MMC卡、spi方式读写sd卡在本文不适用。

sd2.0标准定义了物理层相关规范以及主机控制器规范,sd卡驱动的编写必须参考这两个规范,主要是对S5PV210中的sd主机控制器的编程,这部分对所有具有sd主机控制器的微处理器均是适用的,只是寄存器名称的差异。此处只根据sd2.0规范讲解几个重要的过程或概念,这些过程具体的实现请参考sd驱动模块中相应的函数实现。

2.1. sd卡初始化及识别过程

sd卡上电后,将进入idle状态,此时的sd卡为1位sd模式。通过拉低CS线将可使sd进入spi模式(不再讨论范围内),在sd模式下卡的初始化及识别过程见图2.1.1,其步骤如下:

1) 发送CMD0软件复位所有的卡到idle状态。
2) 发送CMD8来检查卡是否支持主机电压(2.7v~3.3v),这个命令在sd2.0以上才被定义,若没有收到回复信号,则可能为sd1.0或MMC卡,若接收到卡回复信号,说明为sd2.0版本卡,跳转到步骤5
3) CMD8没有收到回复信号,可进一步发送ACMD41(CMD55+CMD41),参数HCS位为0(非高容量卡),如果没有回复信号,说明是MMC卡或其它不能识别的卡,可进一步发送CMD1确定是否MMC卡(此处不再分析)
4) ACMD41能收到回复,并且从回复中确定sd卡己准备好,即可确定这是sd1.x版本的卡,若回复中表明sd卡未准备好,则需重复发送ACMD41等待卡准备好,可通过超时(卡一直busy)判断卡不支持主机电压,此时表明卡不可用。判断出sd1.x的卡后,跳转到步骤9
5) CMD8有回复说明为sd2.0以上的卡,从回复中确定卡是否能在该电压下工作,不能则认为卡不可用。
6) 回复中确定卡能在2.7v~3.3v电压工作后,进一步发送ACMD41(CMD55+CMD41),参数HCS位为1表明主机支持高容量的卡
7) 检查ACMD41卡回复中忙标志,若卡处于忙状态,则重复发送ACDM41,直到卡准备好,可通过超时(卡一直忙状态)可认为该卡不可用。
8) ACMD41回复准备好后,再检查回复中的CCS位,该位为1说明是sd2.0高容量sdhc卡,若为0,则说明为sd2.0标准容量卡。
9) 在识别出sd1.x、sd2.0标准卡或sd2.0高容量卡后,此时卡进入ready态。进一步通过CMD2请求卡发送其CID(Card Identification),此时卡进入Identification态。
10) 卡在Identification态后,发送CMD3请求卡发布一个16位新的相对地址(RCA),以后主机与卡之间的点对点通信均会以这个RCA地址来进行,此时卡进入Stand-by态。
11) 至此,卡的初始化及识别过程结束,此时卡进入数据传输模式(data transfer mode)

05_sd卡驱动实现 - 图1

图2.1.1. sd卡初始化及识别流程

2.2. 数据传输模式

sd卡是一个非常典型的状态机,每个状态只会响应该个状态下的特定命令,不要尝试在某个状态下发送这个状态不支持的命令,sd卡不会对该命令进行响应,命令只会超时。应该通过特定的触发条件转变状态或等待状态迁移完成后,再发送对应状态的命令。如图2.2.1,要想写一个块的数据到sd卡,在stand-by态的情况下,必须通过CMD7选择卡,让卡进入transfer态,然后再发送CMD24单块写命令,再发送一块的数据,此时卡进入Programming态,这时如果又紧接发送CMD24进行单块写将不会成功,必须等待sd卡编程完,从Programming态返回到transfer态才能再次接收下一个块写命令。同样,在transfer态想通过CMD9来获得Card-Specific Data(CSD),必须通过CMD7取消选择卡,此时卡进入stand-by态后,即可通过CMD9来获得卡信息。

05_sd卡驱动实现 - 图2

图2.2.1. sd卡数据传输模式

2.3. 主机控制器对卡的初始化

sd卡主机控制器都可以根据sd2.0规范给出的卡初始化及识别流程进行卡的初始化,主机控制器寄存器的设置主要有以下几点,具体的实现可参考Hsmmc_Init()这个初始化函数。

1) 设置功能引脚,把相应引脚配置成sd接口用引脚
2) 设置sd卡时钟在100k~400k,sd卡在识别阶段必须用慢速时钟进行访问
3) 按照规范给出的卡初始化流程对卡进行发送相应的命令并处理回复,成功后卡进入stand-by态
4) 通过发送CMD7选择卡,使卡进入transfer态,因为卡的大部分操作如读、写、擦除等均是在这个状态下来进行的,此时卡已完全准备好接收读写命令了。
5) 设置sd卡的时钟到一个较高值,sd卡默认支持最高25M时钟,对于sd1.1以上标准,可以检查卡是否支持高速模式,若支持则切换到高速模式,最高支持50M,频率越高,数据传输速率越快
6) 通过ACMD6(CMD55+CMD6)来设置sd模式的位宽为4,sd卡初始化后默认是1线宽,更多的数据线将有更大的带宽,数据传输速率最高12.5MB/s(25M、4线)或25MB/s(50M、4线)。
7) 发送CMD16设置块长度,对于标准卡,可通过CMD16来设置块命令(如块读、块写)所操作块的长度(以字节数计),可实现字节的读写,但对于高容量卡这个命令将被忽略,高容量卡一个块的长度均是固定512字节的。通常通过CMD16设置块长度为512字节。至此卡初始化完成。

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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
int Hsmmc_Init(void)
{
int32_t Timeout;

uint32_t Capacity;

uint32_t i;

uint32_t OCR;

uint32_t Temp;

uint8_t SwitchStatus[64];

SD_SCR SCR;

uint8_t CSD[16];

uint32_t c_size, c_size_multi, read_bl_len;



// 设置HSMMC的接口引脚配置

#if (HSMMC_NUM == 0)

// channel 0,GPG0[0:6] = CLK, CMD, CDn, DAT[0:3]

GPG0CON_REG = 0x2222222;

// pull up enable

GPG0PUD_REG = 0x2aaa;

GPG0DRV_REG = 0x3fff;

// channel 0 clock src = SCLKEPLL = 96M

CLK_SRC4_REG = (CLK_SRC4_REG & (~(0xf<<0)))| (0x7<<0);

// channel 0 clock = SCLKEPLL/2 = 48M

CLK_DIV4_REG = (CLK_DIV4_REG & (~(0xf<<0)))| (0x1<<0);



#elif (HSMMC_NUM == 1)

// channel 1,GPG1[0:6] = CLK, CMD, CDn, DAT[0:3]

GPG1CON_REG = 0x2222222;

// pull up enable

GPG1PUD_REG = 0x2aaa;

GPG1DRV_REG = 0x3fff;

// channel 1 clock src = SCLKEPLL = 96M

CLK_SRC4_REG = (CLK_SRC4_REG & (~(0xf<<4)))| (0x7<<4);

// channel 1 clock = SCLKEPLL/2 = 48M

CLK_DIV4_REG = (CLK_DIV4_REG & (~(0xf<<4)))| (0x1<<4);



#elif (HSMMC_NUM == 2)

// channel 2,GPG2[0:6] = CLK, CMD, CDn, DAT[0:3]

GPG2CON_REG = 0x2222222;

// pull up enable

GPG2PUD_REG = 0x2aaa;

GPG2DRV_REG = 0x3fff;

// channel 2 clock src = SCLKEPLL = 96M

CLK_SRC4_REG = (CLK_SRC4_REG & (~(0xf<<8)))| (0x7<<8);

// channel 2 clock = SCLKEPLL/2 = 48M

CLK_DIV4_REG = (CLK_DIV4_REG & (~(0xf<<8)))| (0x1<<8);



#elif (HSMMC_NUM == 3)

// channel 3,GPG3[0:6] = CLK, CMD, CDn, DAT[0:3]

GPG3CON_REG = 0x2222222;

// pull up enable

GPG3PUD_REG = 0x2aaa;

GPG3DRV_REG = 0x3fff;

// channel 3 clock src = SCLKEPLL = 96M

CLK_SRC4_REG = (CLK_SRC4_REG & (~(0xf<<12)))| (0x7<<12);

// channel 3 clock = SCLKEPLL/2 = 48M

CLK_DIV4_REG = (CLK_DIV4_REG & (~(0xf<<12)))| (0x1<<12);  



#endif

// software reset for all

__REGb(HSMMC_BASE+SWRST_OFFSET) = 0x1;

Timeout = 1000; // Wait max 10 ms

while (__REGb(HSMMC_BASE+SWRST_OFFSET) &(1<<0)) {
    if (Timeout== 0) {
        return-1; // reset timeout

    }

    Timeout--;

    Delay_us(10);

}  



Hsmmc_SetClock(400000); // 400k

__REGb(HSMMC_BASE+TIMEOUTCON_OFFSET) = 0xe; // 最大超时时间

__REGb(HSMMC_BASE+HOSTCTL_OFFSET) &= ~(1<<2);// 正常速度模式

// 清除正常中断状态标志

__REGw(HSMMC_BASE+NORINTSTS_OFFSET) =__REGw(HSMMC_BASE+NORINTSTS_OFFSET);

// 清除错误中断状态标志

__REGw(HSMMC_BASE+ERRINTSTS_OFFSET) =__REGw(HSMMC_BASE+ERRINTSTS_OFFSET);

__REGw(HSMMC_BASE+NORINTSTSEN_OFFSET) = 0x7fff; //[14:0]中断状态使能

__REGw(HSMMC_BASE+ERRINTSTSEN_OFFSET) = 0x3ff; //[9:0]错误中断状态使能

__REGw(HSMMC_BASE+NORINTSIGEN_OFFSET) = 0x7fff; //[14:0]中断信号使能  

__REGw(HSMMC_BASE+ERRINTSIGEN_OFFSET) = 0x3ff; //[9:0]错误中断信号使能



Hsmmc_IssueCommand(CMD0, 0, 0, CMD_RESP_NONE); // 复位所有卡到空闲状态 



CardType = UNUSABLE; // 卡类型初始化不可用

// 没有回复,MMC/sd v1.x/notcard

if (Hsmmc_IssueCommand(CMD8, 0x1aa, 0, CMD_RESP_R7)) {
    for (i=0;i<100; i++) {
    Hsmmc_IssueCommand(CMD55,0, 0, CMD_RESP_R1);

// CMD41有回复说明为sd卡

    if(!Hsmmc_IssueCommand(CMD41, 0, 0, CMD_RESP_R3)) {

// 获得回复的OCR(操作条件寄存器)值

        OCR = __REG(HSMMC_BASE+RSPREG0_OFFSET);

// 卡上电是否完成上电流程,是否busy

        if (OCR& 0x80000000) {

            CardType= SD_V1; // 正确识别出sd v1.x卡

            Debug("SDcard version 1.x is detected\r\n");

            break;

        }

    } else {
        // MMC卡识别

        Debug("MMCcard is not supported\r\n");

        return-1;

    }

    Delay_us(1000);            

    }

} else { // sd v2.0

    Temp =__REG(HSMMC_BASE+RSPREG0_OFFSET);

// 判断卡是否支持2.7~3.3v电压

    if(((Temp&0xff) == 0xaa) && (((Temp>>8)&0xf) == 0x1)) {
    OCR = 0;

    for (i=0;i<100; i++) {
        OCR |=(1<<30);

        Hsmmc_IssueCommand(CMD55,0, 0, CMD_RESP_R1);

        Hsmmc_IssueCommand(CMD41,OCR, 0, CMD_RESP_R3); // reday态

        OCR =__REG(HSMMC_BASE+RSPREG0_OFFSET);

// 卡上电是否完成上电流程,是否busy

        if (OCR& 0x80000000) {

// 判断卡为标准卡还是高容量卡

        if (OCR& (1<<30)) {

            CardType= SD_HC; // 高容量卡

            Debug("SDHCcard is detected\r\n");

        } else {
            CardType= SD_V2; // 标准卡

            Debug("SDversion 2.0 standard card is detected\r\n");

        }

        break;

        }

        Delay_us(1000);

    }

}

}

if (CardType == SD_HC || CardType == SD_V1 || CardType== SD_V2) {
// 请求卡发送CID(卡ID寄存器)号,进入ident

    Hsmmc_IssueCommand(CMD2,0, 0, CMD_RESP_R2);

// 请求卡发布新的RCA(卡相对地址),Stand-by状态

    Hsmmc_IssueCommand(CMD3,0, 0, CMD_RESP_R6);

// 从卡回复中得到卡相对地址

    RCA =(__REG(HSMMC_BASE+RSPREG0_OFFSET) >> 16) & 0xffff;

// 选择已标记的卡,transfer状态

    Hsmmc_IssueCommand(CMD7,RCA<<16, 0, CMD_RESP_R1);

    Hsmmc_Get_SCR(&SCR);

    if(SCR.SD_SPEC == 0) { // sd 1.0 - sd 1.01

// Version 1.0 doesn't support switching

        Hsmmc_SetClock(24000000);// 设置SDCLK = 48M/2 = 24M           

    } else { //sd 1.10 / sd 2.0

    Temp = 0;

    for (i=0;i<4; i++) {
// switch check

    if(Hsmmc_Switch(0, 0, 1, SwitchStatus) == 0) {
// Group 1, function 1 high-speed bit 273

        if(!(SwitchStatus[34] & (1<<1))) {
        // Thehigh-speed function is ready

// Group, function 1 high-speed support bit 401

        if(SwitchStatus[50] & (1<<1)) {

        //high-speed is supported

        if(Hsmmc_Switch(1, 0, 1, SwitchStatus) == 0) { // switch

// function switch in group 1 is ok?

            if ((SwitchStatus[47]& 0xf) == 1) {
// result of the switch high-speed in function group 1

            Debug("Switchto high speed mode: CLK @ 50M\r\n");

            Hsmmc_SetClock(48000000);// 设置SDCLK = 48M   

            Temp= 1;

            }

        }

        }

        break;

        }

    }

    }

    if (Temp ==0) {
        Hsmmc_SetClock(24000000);// 设置SDCLK = 48M/2 = 24M

    }

    }



    if (!Hsmmc_SetBusWidth(4)){
        Debug("Setbus width error\r\n");

        return-1; // 位宽设置出错

    }

// 此时卡应在transfer态

    if(Hsmmc_GetCardState() == CARD_TRAN) {

// 设置块长度为512字节

    if(!Hsmmc_IssueCommand(CMD16, 512, 0, CMD_RESP_R1)) {

        __REGw(HSMMC_BASE+NORINTSTS_OFFSET)= 0xffff; // 清除中断标志

        Hsmmc_Get_CSD(CSD);

// CSD v1.0->sd V1.x, sd v2.00 standard

        if((CSD[15]>>6) == 0) {

        read_bl_len= CSD[10] & 0xf; // [83:80]

        c_size_multi= ((CSD[6] & 0x3) << 1) + ((CSD[5] & 0x80) >> 7);

        c_size =((int32_t)(CSD[9]&0x3) << 10) + ((int32_t)CSD[8]<<2)

                + (CSD[7]>>6); // [73:62]

        Capacity= (c_size + 1) << ((c_size_multi + 2) +

(read_bl_len-9)); // block(512 byte)

        } else {
        c_size =((CSD[8]&0x3f) << 16) + (CSD[7]<<8) + CSD[6];

// 卡容量为字节(c_size+1)*512Kbyte,以1扇区512 byte字,卡的扇区数为

        Capacity= (c_size+1) << 10;// block (512 byte)

        }

        Debug("CardInitialization succeed\r\n");  

        Debug("Capacity:%ldMB\r\n", Capacity / (1024*1024 / 512));

        return 0;// 初始化成功                        

    }

    }

}

Debug("Card Initialization failed\r\n");

return -1; // 卡工作异常

}

2.4. 主机命令的发送

sd规范对命令包格式、回复包、数据的传输方式等均作了详细的要求。虽然sd卡主机控制器可以帮我们对命令进行打包,对回复进行解包,产生CRC,并在sd总线上输出相应的时序。我们仍需要告诉sd卡主机控制器需发送的命令、这个命令的参数、这个命令发送后是否需要使用data线, sd卡的回复类型。这些设置通过主机控制器CMDREG寄存器来实现。主要有以下几点,具体的实现可参考Hsmmc_IssueCommand()这个命令发送函数。

1) 命令发送时,需检查命令线是否已被使用,若是,则等待正在发送的命令发送完才能发送这个命令
2) 如果命令回复会带忙信号(如R1b回复),则需检查数据线是否已被使用,若是,则等待数据线空闲,带忙回复命令发送后,sd卡会拉低DAT[0]线表明sd卡正忙,数据线不可用。
3) 把命令参数写入ARGUMENT这个寄存器中
4) 在CMDREG中设置命令值[13:8]
5) 设置是否需使用data线,如块读、块写等命令发送后,会紧接着在data线上传输数据,其它不需传输数据的命令不要设置使用data线CMDREG[5]
6) 设置sd卡的回复类型,绝大部分命令在sd卡正确响应后,都会对主机进行回复(R1-R7,R1b),每个命令对应的回复类型请参考sd卡规范。回复类型长度可能为136或48,回复中是否包含CRC或命令值的反馈,如果包含,则告诉主控制器检查回复中相应的CRC或命令值反馈是否正确,以确定传输正确。CMDREG设置好后,主控制器就会发送命令并接收设定长度的回复并根据设定检查CRC、命令值反馈是否正确(若回复中包含CRC或命令值反馈的话)
7) 等待命令完成,检查中断状态位NORINTSTS[15]以确定命令是否有错误,若没有错误并且检测到NORINTSTS[0]命令完成位为1,则说明命令发送成功。其它情况说明命令未能成功发送。

2.5. 驱动模块接口

通常对于一个sd卡驱动模块,至少实现卡初始化、块读、块写这三个接口函数。这通常是一个文件系统最基本的底层磁盘接口实现。具体可参考Hsmmc_Init()、Hsmmc_ReadBlock()和Hsmmc_WriteBlock()这三个函数的实现。

3. 附录

Hsmmc.rar,包含sd卡驱动模块实现Hsmmc.c/Hsmmc.h。
源码: http://pan.baidu.com/s/1nsJx0