cortex-a8裸机系列:第十六章 iNand与SD
0赞一、 iNand介绍
1. iNand/eMMC/SDCard/MMCCard的关联
最早出现的是MMC卡,卡片式结构,按照MMC协议设计。相比于Nand来说,MMC有两个优势:第一卡片化,便于拆装;第二是统一了协议接口,兼容性好。
后来出现的SD卡,兼容MMC协议。SD卡较MMC卡有一些改进,如写保护,速率,容量等。SD卡遵循SD协议,有多个版本。多个版本之间是向前兼容。
iNand/eMMC在SD卡的基础上发展起来,较SD卡的区别就是将SD卡芯片化了(解决卡的接触不良问题,便于设备迷你化)。
iNand和eMMC的关联:MMC是协议,eMMC具备MMC协议的芯片。iNand是Sandisk公司符合MMC协议的一种芯片系列名称。
2. iNand/eMMC的结构框图及其与Nand的区别
iNand内部也是由存储系统和接口电路构成(那Nand结构特性类似,不同之处在于接口电路功能不同)
iNand的接口电路负责,功能较多。如:
第一:提供eMMC接口协议,和soc的的eMMC接口控制器通信对接
第二:提供块的ECC校验相关的逻辑,也就是iNand本身自己完成存储系统的ECC功能,soc使用iNand时自己不用写代码进行ECC相关操作,大大简化了soc的编程难度。
第三:iNand芯片内部使用MLC Nand颗粒,所以性价比很高。
第四:iNand接口电路提供了cache机制,所以iNand的操作速度很快。
3. iNand/eMMC的物理接口与SD卡物理接口区别
iNand/eMMC接口,8根数据线,一根时钟线,一根命令线。
SD卡,4根数据线,1根时钟线,一根命令线,一根使能线。
S5pv210本身支持4通道的SD/MMC,在九鼎开发板上,通道0接了iNand芯片,在通道2接了SD卡,通道3也接了SD卡。
对比两个的物理接口接线,结论:两个接口几乎一样,唯一的区别是SD卡IO线有4根,iNand的IO线有8根。
因此,操作iNand芯片时和操作SD卡时几乎是一样的(物理接线几乎一样,软件操作协议几乎一样)。
结论:iNand/eMMC其实就是芯片化的SD/MMC卡,软件操作和SD卡相同。
分析iNand芯片的操作代码时,其实就是以前的SD卡的操作代码。一些细节的区别就是为了区分各种不同版本的SD卡、iNand的细节差异。
二、 SD卡/iNand操作
1. 硬件接口:DATA、CLK、CMD
iNand的DATA有8根,支持1、4、8并行传输模式;SD的DATA有4根,支持1、4并行数据传输。
CMD用来传输命令,CLK用来传输时钟。
接口有CLK线,工作时主机soc通过CLK线传输时钟信号给SD卡/iNand芯片。说明:SD/iNand是工作在同步模式下,SD/iNand的工作速率是由主机给他的CLK频率决定的。
2. 命令响应的操作模式
命令从主机发到卡,响应从卡到主机。
SD协议事先定义了很多标准命令(CMD0,CMD1,。。。。),每个命令都有它的作用和使用条件和对应的响应。SD卡工作的时候就是一个一个的命令周期组合起来的,在一个命令周期中,主机先发送CMD给SD卡,然后SD卡解析这个命令并且执行这个命令,然后SD卡根据结果回发给主机soc一个响应。(有些命令是不需要响应的,这时候SD卡不会给主机回发响应,主机也不用等待响应)。标准的命令+响应的周期中,主机发完一个命令后,应该等待SD卡的响应而不是接着发下一条命令。
三、 SD/iNand的体系结构图
SD卡内部有一个接口控制器,这个控制器类似于一个单片机(据说是51单片机),这个单片机的程序功能就是通过CMD线接收外部主机soc发给SD卡的命令码,然后执行这个命令并且回发响应给数据soc。这个单片机处理命令及回发响应遵循的就是SD协议。这个单片机同时可以控制SD卡内部的存储单元,可以读写存储单元。
四、 SD/iNand的寄存器(重点是RCA寄存器)
这里的寄存器是指SD卡内部的寄存器,而不是主机soc的SD控制器的寄存器。(很多外置芯片内部都是有寄存器的,这些寄存器可以按照一定的规则访问,访问这些寄存器可以得知芯片的一些信息)
寄存器如下:
RCA(relative address,相对地址寄存器)。访问SD卡时,实际上SD内部的存储单元的地址没有绝对数字,都是使用相对地址。相对地址由SD卡自己决定的,存放在RCA寄存器中。
五、 Soc的SD/MMC/iNand控制器简介
不同的soc可能在SD/MMC/iNand等支持方面有差异,但是如果支持都是通过内部提供SD控制器来支持的。
S5pv210的SD卡控制器在1031页。特性如下:
六、 SD/iNand程序分析
1. SD卡工作在命令码+响应的模式下
使用宏定义进行命令的定义。
SD协议的命令分两种:CMDx和ACMDx。CMD是单命令,就是单独发一个CMD可表示一个意思。ACMD是一种扩展,就是发两个CMD加起来表示一个意思。可以认为ACMDx = CMDy (y一般是55)+ CMDz。
2. 卡类型识别SD or MMC
MMC协议、SD协议、eMMC协议本身是一脉相承的,所以造成了一定的兼容性,所以当soc控制器工作时连接到soc的可能是一个MMC卡、也可能是SD卡、也可能是iNand芯片。主机的soc需要取识别这个卡到底是什么版本的卡。
使用宏定义定义了一些类型。
因为不同版本的卡内部协议是不同的,所以对卡识别命令的响应也是不同的,soc通过发送一些命令,听取响应就可以根据不同的响应判断卡的版本
3. 卡状态
SD卡内部的接口控制器类似于一个单片机,这个单片机的工作其实是一个状态机。所以SD卡任何时候都有一种状态(空闲状态、准备好状态、读写状态、出错状态。。。都是实现定义好的),在这种状态下,能够接收的命令是一定的,接收到命令后执行一定的操作然后根据操作结果会跳转为其他状态。如果主机发送的命令和当前状态不符合,那状态机就不响应。如果收到的命令和当前状态相符,就会执行响应操作,执行完毕后,根据结果跳转为另外的状态。
如上图,有若干个状态,在每个状态可以执行特定的操作。
使用宏定义卡的状态
4. 卡回复类型
一般来说,SD卡的命令都属于:命令+响应的模式。也是有极少数的SD卡命令是不需要回复的。
卡回复有多种类型,每种卡回复类型都有自己的解析规则。然后卡在特定状态下响应特定命令时有可能回复的响应都是SD协议事先规定好的。
如下,使用宏定义定义响应。
5. Linux内核风格的寄存器定义
使用基地址加偏移来进行寄存器的定义。
使用以下四种方式来定义寄存器。
其中vu_xxx,是通过typedef定义的别名。
6. SD控制器初始化 int Hsmmc_Init(void)
1) GPIO初始化
使用宏编译,选择编译哪一通道的SD/MMC的代码。
以通道0为例,查看原理图,得知接到通道0的iNand的MMC接口和GPG0,GPG1复用,所以首先要设置这些GPIO为SD/MMC功能。
查看210数据手册,GPG0,和SD有关。
而GPG1,可以当做SD的DATA[7:4]:
设置管脚为SD功能,开启上拉,设置驱动强度为最强
2) SD控制器时钟设置
两个寄存器比较重要,SRC寄存器(控制源时钟选择),DIV(控制对源时钟的分频)。
CLK_SRC4的最低16位控制SD/MMC的4个通道的时钟源选择。
CLK_DIV4低16位控制SD/MMC的4个通道的对时钟源分频。
选择时钟源为SLCKEPLL,分频值为2,所以SCLK_MMCC0-3的时钟是48M。
3) 对SD控制器进行全复位
这个复位不是复位SD卡,复位SD卡需要发送命令,而是复位SD控制器,并判断复位是否结束。这一步操作和soc的SD控制器有关,有的不需要复位。
SWRSTx寄存器有4个,每个对应各自的通道。
有3种复位,一种是复位数据,一种是复位命令,一种是都复位。这里是进行都复位,也就是将寄存器的最低位置1。在复位期间这一位一直为1,当复位结束后,这一位会为0,所以判断这一位为0,说明复位结束。
4) 设置SD卡的时钟
SD卡内部没有时钟,需要外部提供时钟,因为需要设置外部提供给SD卡的时钟,也就是MMC_CLK。
此时刚开始和SD卡通信,soc不清楚SD卡属于哪个版本(高版本和低版本的读写速率不同,高版本的可以工作在低版本的速率下,而低版本的SD卡不能工作在高版本的速率下),所以先设置外部SD卡的时钟为低速率40K时钟,以便和SD卡通信,后面根据读取SD的类型,在设置时钟。
对于Hsmmc_SetClock函数,这个是设置MMC_CLK的时钟。
操作为以下几步:
第一步:关闭时钟
第二步:设置时钟
第三步:开启时钟
对于Hsmmc_ClockOn函数,有两个参数,1表示开启时钟,0表示关闭时钟。
主要是和CLKCON寄存器打交道。该寄存器也有4个,分别对于不同通道的SD的CLK设置。
这里用到的是第2位和第3位。第2位控制外部SD卡的时钟是否使能。使能之后,时钟不是马上就稳定的,需要检测第3位,为1说明时钟稳定,为0说明时钟不稳定,需要等待。
设置外部SD时钟。主要和CONTROL2,CONTROL3寄存器有关系。根据要设置的时钟值,设置CONTROL3。这里主要是设置反馈时钟模式。当外部SD卡时钟较高时,对于时钟要进行反馈,为了时钟的准确,会对时钟进行校准。对于频率比较低的时钟,不需要使用反馈时钟。
设置反馈模式后,需要开始反馈时钟,就在CONTROL2寄存器中设置。也就是14位和15位要置1,使能反馈时钟。
时钟源选择SCLK_MMC,也就是48M时钟。
将最高两位置1。
设置外部SD时钟的分频值,以设置外部的时钟。
这里采用一个C语言技巧,来得到分频值。先得到48MH/2^n 最接近设置时钟的n值。然后再将该值处理写入到CLKCON寄存器的[15:8]位,以设置分频值。
并开启内部时钟。
时钟设置完毕后,检测内部时钟是否稳定,最后使能外部SD时钟。
5) 设置超时时间和速度模式
寄存器设置超时时间,用于数据线超时检测。超时后,会在中断状态寄存器标明。使用的时钟是TMCLK。
6) 设置控制器寄存器
设置物理特性。如数据模式,命令数据什么边沿输出,DMA使用以及额外的数据位宽扩展等。
程序中将第2位清零,表明主机的命令和数据输出都是在时钟的下降沿变化,因为对于外部SD/MMC卡,命令和数据是上升沿有效,对于soc的SD/MMC控制器来说,命令和数据就需要在时钟下降沿变化,以满足数据时序的建立/保持时间。
7) 清除中断标志位
两个寄存器。
NORINTSTS 普通中断状态寄存器,当对应中断开启后,会将对应状态位会置1,写入1,清除中断标志位。初始化的时候,需要将之都清除。
ERRINTSTS :错误中断状态寄存器,发生错误,如果错误使能,会将对应位置1。初始化需要置1,清除错误中断状态位。
8) 中断、错误使能
将所有中断、错误都使能。
7. 发送命令函数
对于发送命令Hsmmc_IssueCommand函数。
需要检测是否可以发命令,这个查看寄存器PRNSTS,最低两位,一个判断数据线,一个判断命令线。发送命令前,需要检查是否能够发命令,也就是看这个寄存器的第0位。
如果命令还需要数据线参与,那么还需要检查数据线是否可以发送数据。
写入参数,每个命令都是带有参数的。
写命令:
该寄存器CMDREG命令寄存器,往这个寄存器写值,SD控制器会把命令和参数给发送给SD卡。
[13:8]位是命令。
后面的位是说明命令的。
Command type是命令的类型。不同的命令,这个地方要写入不同的值。
DATAPRNT: 数据选择,有的命令需要数据线参与,需要数据线参与的,这一位要置1。
ENCMDIDC: 使能响应的索引检查。响应中有一个索引和目录的索引是一样的,可以通过检查该索引,判断是否有错误。
ENCMDCRC: 检查响应的CRC。
RSPTYP: 响应数据的长度。
将数据写入到命令寄存器后,SD控制器会将该命令发送出去,并自动读取响应到寄存器CMPRSP中。CMPRSP寄存器有4个。
寄存器的内容:
对于不同的响应,值存的地方不一样。
命令发送完毕后,需要等待命令结束。
对于Hsmmc_WaitForCommandDone函数。
其实就是读取NORINTSTS寄存器的15位,当传输出现错误后,该位会置1。出现错误后,读取错误中断状态寄存器,得到错误号返回。
如果命令传输成功后,NORINTSTS寄存器的第0位会置1,表示命令完成。
经过以上的操作,命令发送就结束了。返回0,说明命令发送成功。
8. SD卡初始化序列
初始化序列根据下图来进行。
发送CMD0,复位SD卡。
发送CMD8,判断是否有响应。
无响应。会出现超时,于是ERRINTSTS寄存器的最低位会为1,说明没有响应。而发送命令函数,如果没有响应,会将这个ERRINTSTS寄存器的值给返回回来,为1,说明未响应,可能是SD1.0版本,或者是没有卡。
然后发送ACMD41命令,而发ACMD41命令,其实是两个命令的组合,一个CMD55,一个CMD41,如果有响应,说明是SD1.0卡,没有响应说明识别卡错误。
对于有响应,发送命令函数会返回0,执行else的程序。
读取响应,判断卡支持的电压,如果电压不对,返回错误。然后再发送ACMD14命令,读取响应,判断卡的类型。
当判断卡的类型后,需要发送CMD2和CMD3。让卡就绪。
发送CMD3,读取RCA的值,SD卡进入stand-by状态。
发送CMD7。SD卡进入transfer状态。
最后获取SCR。
然后根据SCR的类型,设置外部SD卡的时钟。
设置数据位宽为4。
发送CMD16,设置块长度为512字节。读取SD卡的CSD寄存器,得到卡的一些信息。返回0表示初始化成功。
初始化完后,SD卡进入transfer状态了。
初始化不成功的话,返回-1。
9. SD卡操作
SD卡的操作就根据以下的图来进行操作了。
发送CMD3命令后,SD卡进入stand-by状态了。要对SD卡进行读写,要进入transfer状态,发送CMD7命令。所以初始化中,发送完CMD3命令后,再发送CMD7命令,SD卡进入transfer状态,就可以进行读写了。
1) SD的block读
对于Hsmmc_ReadBlock函数
先将中断和错误都清掉。
函数能实现读多块的功能,所以外面有一个while循环,判断多块读是否完成。SD卡一次最多能够读65536块(由SD卡控制器决定),当需要读的块数大于65536后,就要分多次读。
BLKSIZE寄存器,设置块大小,以及DMA数据长度。将块大小设置为512字节。DMA没有使用的话,设置为最大长度。
CLKCNT寄存器,设置传输的块数。
在多块数据传输时,这个寄存器每传输完一块,就会减一,减到0,就停止数据传输。
要设置这个值,必须是要没有数据传输的时候设置。在数据传输过程中,读这个寄存器返回非法的数,写这个寄存器无效。这里能传输的最大块数为65535,所以之前的程序需要对传输的块数进行处理。
设置读的地址。对于SDHC卡,地址是块地址。对于V1和V2版本,是字节地址,所以需要块 * 512,得到字节地址。
得到下一次读块的地址。
写入命令参数,对于读SD,参数是读取数据的地址。
这里,读分成两种,一种是读一块,一种是读多块。
读一块使用的命令是CMD17,第多块使用的命令是CMD18。
TRNMOD寄存器,设置传输模式。
第五位:MUL1SIN0 ,多块操作使能。对于读一块,设置为0。对于读多块,要设置为1。每读取一块数据,BLKCNT寄存器会减一,当减到0的时候,读取结束。
第四位: RD1WT0 , 数据传输模式, 1读,0是写。 对于读操作,设置为1.
第二位: ENACMD12, 数据传输接收后,是否自动发送ACMD12命令。对于多块操作,需要ACMD12命令来停止传输。多块传输中,最后一个块传输后,如果开启这一位,会自动发送ACMD12命令。
第一位:ENBLKCNT, 使能多块计数。使能后,传输一块后,CLKCNT会减一。
第0位:ENDMA, 使能DMA。
设置好传输模式后,发送对应的命令。
命令发送完毕后,需要等待命令操作完成。
读取多块数据。
判断读是否准备好。
判断NORINTSTS寄存器的第5位。为1,表示读缓冲可以使用。
因为参数提供的内存地址是char *型,所以先判断地址是否4字节对齐。然后分成不对齐和对齐处理。
对于BDATA寄存器,这是一个soc内部的寄存器,当读该寄存器时,SD控制器会自动从SD卡读取4个字节数据放入到这个寄存器中。所以读这个寄存器得到32位数据。
如果内存地址对齐,需要将内存地址强制转化为uint32_t类型,写入一次,地址加4。如果内存地址不对齐,就只能一个字节一个字节的操作。
块数据读取结束后,需要判断传输是否结束。
判断NORINTSTS寄存器的第1位。为1,表示传输完成。