ARM 裸机之 NandFlash 和 iNand

本文介绍了 NandFlash 的基本原理和 iNand 的基础知识,分析了 iNand 读写的代码

本文介绍了 NandFlash 的基本原理和 iNand 的基础知识,分析了 iNand 读写的代码

ARM裸机 NandFlash 和 iNand

NandFlash的接口

Nand的型号与命名

  • Nand的型号命名都有含义,就拿K9F2G08来示例分析一下:
    • K9F表示是三星公司的NandFlash系列。
    • 2G表示Nand的大小是2Gbit(256MB)。
    • 08表示Nand是8位的(8位就是数据线有8根)
  • Nand命名中可以看出:厂家、系列型号、容量大小、数据位数。

Nand的数据位

  • Nand有8位数据位的,有16位数据位的。做电路时/写软件时应该根据自己实际采购的Nnad的位数来设计电路/写软件。
  • 说明Nand是并行接口的(8/16位)
  • Nand的数据线上传递的不一定全部是有效数据,也可能有命令、地址等。

Nand的结构

nand

  • Nand的结构可以看成是一个矩阵式存储器,其中被分成一个一个的小块,每一小块可以存储一个bit位,然后彼此以一定单位组合成整个Nand
  • Nand中可以被单次访问的最小单元(就是说对Nand进行一次读写至少要读写这么多,或者是这么多的整数倍)叫做Page(页),在K9F2G08芯片中,Page的大小是2KB+64B。也就是说我们要读写K9F2G08,每次至少要读写2KB或者n*2KB,即使我们只是想要其中的一个字节。这就是我们说的典型的块设备(现在有些块设备为了方便,提供了一种random read模式,可以只读取1个字节)。
  • 页往上还有个Block(块)的概念,1个等于若干个(譬如在K9F2G08中1个块等于64页)
  • 页往上就是整个Nand芯片了,叫做Device。一个Device是若干个Block,譬如K9F2F08一个Device有2048个block。所以整个Device大小为:2048×64×2K = 256MB

块设备分page、block有什么意义

  • 块设备不能完全按字节访问而必须块访问是物理上的限制,而不是人为设置的障碍。
  • 其次,Page和Block各有各的意义,譬如Nand中:
    • Page是读写Nand的最小单位
    • Block是擦除Nand的最小单位。(这些规则都是Nand的物理原理和限制要求的,不是谁想要这样的,所以对于我们做软件的来说,只能去想办法适应硬件,不是想着超越硬件)

NandFlash接口

NandFlash芯片内部有存储颗粒和内部管理接口电路,外部SoC根据NandFlash接口时序访问NandFlash芯片。S5PV210内部集成了NandFlash控制器。

nstruct

NandFlash引脚功能如下:

func

Nand芯片中主要包含2部分:

  • Nand存储颗粒+Nand接口电路。
  • 存储颗粒就是纯粹的Nand原理的存储单元,类似于仓库;
  • Nand接口电路是用来管理存储颗粒,并且给外界提供一个统一的Nand接口规格的访问接口的

Nand中有多个存储单元,每个单元都有自己的地址(地址是精确到字节的,可能是为了实现字节定位而预留的寻址方式)。所以Nand是地址编排精确到字节,但是实际读写却只能精确到页(所以Nand的很多操作都要求给的地址是页对齐的,譬如2K、4K、512K等这样的地址,不能给3000B这样的地址)。

cycle

Nand读写时地址传递是通过IO线发送的,因为地址有30位而IO只有8位,所以需要多个cycle才能发送完毕。一般的Nand都是4cycle或者5cycle发送地址(从这里把Nand分为了4cycle Nand和5cycle Nand)。

总结

  • Nand芯片内部有存储空间,并且有电路来管理这些存储空间,向外部提供统一的Nand接口的访问规则,
  • 然后外部的SoC可以使用Nand接口时序来读写这个Nand存储芯片。
  • Nand接口是一种公用接口,是一种标准,理论上来说外部SoC可以直接模拟Nand接口来读写Nand芯片,但是实际上因为nand接口对时序要求非常严格,而且时序很复杂,所以一般的SoC都是通过专用的硬件的Nand控制器(这些控制器一般是作为SoC的内部外设来存在的)来操控Nand芯片的。

NandFlash的结构

Nand的单元组织:block与page(大页Nand与小页Nand)

  • Nand的和以前讲过的块设备(尤其是硬盘)的扇区是类似的。扇区最早在磁盘中是512字节,后来也有些高级硬盘扇区不是512字节而是1024字节/2048字节/4096字节等。Nand也是一样,不同的Nand的页的大小是不同的,也有512字节/1024字节/2048字节/4096字节等。每一个page的大小为512+16=528字节,称为small page。
  • 一个block等于多少page也是不定的,不同的Nand也不同。一个Nand芯片有多少block也是不定的,不同的Nand芯片也不同。
  • 总结:Nand的组织架构挺乱的,接口时序也不同,造成结构就是不同厂家的Nand芯片,或者是同一个厂家的不同系列型号存储容量的nand接口也不一样。
  • 所以nand有一个很大的问题就是一旦升级容量或者换芯片系列则硬件要重新做、软件要重新移植。

带内数据和带外数据(ECC与坏块标记)

Nand的每个页由2部分组成,这2部分各自都有一定的存储空间。譬如K9F2G08中为2K+64字节。其中的2K字节属于带内数据Data area(数据存储区域),是我们真正的存储空间,将来存储在Nand中的有效数据就是存在这2K范围内的(我们平时计算nand的容量时也是只考虑这2KB);64字节带外数据Spare area(备用区域)不能用来存储有效数据,是作为别的附加用途的(譬如用来存储ECC数据、用来存储坏块标志等····)

什么是ECC

error correction code,错误校验码。

因为nand存储本身出错(位反转)概率高(Nand较Nor最大的缺点就是稳定性),所以当我们将有效信息存储到Nand中时都会同时按照一定算法计算一个ECC信息(譬如CRC16等校验算法),将ECC信息同时存储到Nand这个页的带外数据区。然后等将来读取数据时,对数据用同样的算法再计算一次ECC,并且和从带外数据区读出的ECC进行校验。如果校验通过则证明Nand的有效数据可信,如果校验不通过则证明这个数据已经被损坏(只能丢弃或者尝试修复)。

坏块标志

Nand芯片用一段时间后,可能某些块会坏掉(这些块无法擦除了,或者无法读写了),nand的坏块非常类似于硬盘的坏道。坏块是不可避免的,而且随着Nand的使用坏块会越来越多。当坏块还不算太多时这个Nand都是可以用的,除非坏块太多了不划算使用了才会换新的。

所以我们为了管理Nand发明了一种坏块标志机制。Nand的每个页的64字节的带外数据中,我们(一般是文件系统)定义一个固定位置(譬如定位第24字节)来标记这个块是好的还是坏的。文件系统在发现这个块已经坏了没法用了时会将这个块标记为坏块,以后访问nand时直接跳过这个块即可。

Nand的地址时序

  • nand的地址有多位,分4/5周期通过IO引脚发送给Nand芯片来对Nand进行寻址。
  • 寻址的最小单位是字节,但是读写的最小单位是页。
  • nand的地址在写代码时要按照Nand要求的时序和顺序去依次写入。

Nand的命令码

cmd

  • 外部SoC要想通过Nand控制器来访问Nand(实质就是通过Nand接口),就必须按照Nand接口给nand发送命令、地址、数据等信息来读写Nand。
  • Nand芯片内部的管理电路本身可以接收外部发送的命令,然后根据这些命令来读写Nand内容与外部SoC交互。
  • 所以我们对nand进行的所有操作(擦除、读、写···)都要有命令、地址、数据的参与才能完成,而且必须按照Nand芯片规定的流程来做。

NandFlash的常见操作及流程分析

坏块检查

  • Flash使用之前要先统一擦除(擦除的单位是块)。Flash类设备擦除后里面全是1,所以擦干净之后读出来的值是0xff
  • 检查坏块的思路就是:先块擦除,然后将整块读出来,依次检测各自节是否为0xff,如果是则表明不是坏块,如果不是则表明是坏块。

页写(program)操作

  • 页烧录前需要擦除,如果页烧录之前没有擦除,烧录的数据将是错误的
  • 写操作(write)在flash的操作中就叫编程(program)
  • SoC写Flash时通过命令线、IO线依次发送写命令、写页地址、写数据等进入NandFlash
  • 写的过程:
    • SOC通过Nand控制器和Nand芯片完成顺序对接,
    • 然后按照时序要求将一页数据发给Nand芯片内部的接口电路。
    • 接口电路先接收收据到自己的缓冲区,
    • 然后再集中写入Nand芯片的存储区域中。
    • Nand接口电路将一页数据从缓冲区中写入Nand存储系统中需要一定的时间,这段时间Nand芯片不能再响应SOC发过来的其他命令,所以SoC要等待Nnad接口电路忙完。
      • 等待方法是SoC不断读取状态寄存器(这个状态寄存器有2种情况:一种是SoC的Nand控制器自带的,另一种是SoC通过发命令得到命令响应得到的)。通过检查这个状态寄存器的状态位就能知道Nand接口电路刚才写的那一页数据写完了没、写好了没。
      • 直到SoC收到正确的状态寄存器响应才能认为刚才要写的那一页数据已经ok。(如果SoC收到的状态一直不对,可以考虑重写或者认为这一页所在的块已经是坏块,或者整个Nand芯片已经挂掉了)。
    • 正常情况下就已经完了。但是因为Nand的读写有不靠谱情况,因此我们为了安全会去做ECC校验。ECC校验有硬件式校验和软件式校验2种。
      • 软件式校验可以采用的策略有很多,其中之一(Nand芯片手册上推荐的方式是):将刚才写入的1页数据读出来,和写入的内容进行逐一对比。如果读出的和写入的完全一样,说明刚才的写入过程正确完成了;如果读出来的和写入的不完全一样那就说明刚才的写入有问题。
      • 硬件式ECC:SoC的Nand控制器可以提供硬件式ECC(这个也是比较普遍的情况)。硬件式ECC就是在Nand的控制器中有个硬件模块专门做ECC操作。当我们操作Nand芯片时,只要按照SoC的要求按时打开ECC生成开关,则当我们写入Nand芯片时SoC的Nand控制器的ECC模块会自动生成ECC数据放在相应的寄存器中,然后我们只需要将这生成的ECC数据写入Nand芯片的带外数据区即可;在将来读取这块Nand芯片时,同样要打开硬件ECC开关,然后开始读,在读的过程当中硬件ECC会自动计算读进来的一页数据的ECC值并将之放到相应的寄存器中。然后我们再读取带外数据区中原来写入时存入的ECC值,和我们刚才读的时候得到的ECC值进行校验。校验通过则说明读写正确,校验不通过则说明不正确(放弃数据或者尝试修复)

write

擦除(erase)操作

擦除时必须给块对齐的地址。如果给了不对齐的地址,结果是不可知的(有些Nand芯片没关系,它内部会自动将其对齐,而有些Nand会返回地址错误)。

读写时给的地址也是一样,要求是页对齐地址。如果给了不对齐的,也是有可能对有可能错。

erase

擦除操作必须指定块对齐的地址。块擦除编程流程如下:

  • 获取擦除块的地址(块对齐)
  • 发出片选信号
  • 发送擦除命令,第一个周期发命令0x60,第二个周期发块地址,第三个周期发命令0xd0 。
  • 清除状态,等待状态。
  • 读取状态,擦除成功则取消片选,擦除失败取消片选。

页读(read)操作

read

  • 发出片选信号
  • 发送页读取命令,第一个周期发命令0x00,第二个周期发送页地址,第三个周期发送命令0x30
  • 等待状态
  • 发送页读取第一个周期命令0x05
  • 写入页内偏移地址
  • 发送页读取第二个周期命令0xE0
  • 读取数据
  • 检查状态,如果读取成功,取消片选信号,读取失败取消片选信号。

S5PV210的NandFlash控制器

SoC的Nand控制器的作用

fc

NandFlash控制器的主要特性:

  • 支持512B,2KB,4KB,8KB页的NandFlash
  • 软件模式:能直接访问NandFlash芯片,支持读/烧录/擦除NandFlash芯片
  • 支持8bit的NandFlash芯片接口总线
  • 产生、检测、指示硬件ECC
  • 支持SLC/MLC类型NandFlash芯片
  • 支持1/4/8/12/16位的ECC
  • SFR接口:支持字节、半字、字访问数据和ECC数据寄存器,字访问其他寄存器。
  • SoC通过控制SFR(NandFlash控制器)

NandFlash 主要寄存器

  • NFCONF:NandFlash配置寄存器
  • NFCONT:NandFlash控制寄存器
  • NFCMMD:NandFlash命令寄存器
  • NFADDR:NandFlash地址寄存器
  • NFDATA:NandFlash数据寄存器
  • NFSBLK:烧录块起始地址
  • NFEBLK:烧录块结束地址
  • NFSTAT:状态寄存器

iNand介绍

iNand/eMMC/SDCard/MMCCard的关联

  • 最早出现的是MMC卡,卡片式结构,按照MMC协议设计。(相较于NandFlash芯片来说,MMC卡有2个优势:第一是卡片化,便于拆装;第二是统一了协议接口,兼容性好。)
  • 后来出现SD卡,兼容MMC协议。
    • SD卡较MMC有一些改进,譬如写保护、速率、容量等。
    • SD卡遵守SD协议,有多个版本。多个版本之间向前兼容。
  • iNand/eMMC在SD卡的基础上发展起来,较SD卡的区别就是将SD卡芯片化了(解决卡的接触不良问题,便于设备迷你化)。
  • iNand和eMMC的关联:eMMC是协议,iNand是Sandisk公司符合eMMC协议的一种芯片系列名称。

iNand/eMMC的结构框图及其与NandFlash的区别

  • iNand内部也是由存储系统和接口电路构成(和Nand结构特性类似,不同之处在于接口电路功能不同)。
  • iNand的接口电路挺复杂,功能很健全。譬如
    • 第一,提供eMMC接口协议,和SoC的eMMC接口控制器通信对接。
    • 第二,提供块的ECC校验相关的逻辑,也就是说iNand本身自己完成存储系统的ECC功能,SoC使用iNand时自己不用写代码来进行ECC相关操作,大大简化了SoC的编程难度。
      • NandFlash分2种:SLC和MLC,
        • SLC更稳定,但是容量小价格高;
        • MLC容易出错,但是容量大价格低
    • 第三,iNand芯片内部使用MLC Nand颗粒,所以性价比很高。
    • 第四,iNand接口电路还提供了cache机制,所以inand的操作速度很快。

iNand/eMMC的物理接口和SD卡物理接口的对比

(1)S5PV210芯片本身支持4通道的SD/MMC,在X210中实际是在SD/MMC0通道接了iNand芯片,而SD/MMC2接了SD卡(SD/MMC3也接了SD卡)。

从主板原理图可以找到 x210 的两个 SD 卡插槽对应的引脚:

sd2

sd3

(2)对比inand和SD卡接线,发现:这两个接线几乎是一样的,唯一的区别就是SD卡IO线有4根,而iNand的IO线有8根。

在核心板原理图上可以看到连接 inand (也就是 SD0 插槽的GPG0)、 SD2 卡(GPG1)和 SD3 卡(GPG2)的引脚

inand_sd23

(3)这个告诉我们,我们在实际操作iNand芯片时和操作SD卡时几乎是一样的(物理接线几乎一样,软件操作协议几乎一样)。

结论

iNand/eMMC其实就是芯片化的SD/MMC卡,软件操作和SD卡相同。分析iNand芯片的操作代码时,其实就是以前的SD卡的操作代码。一些细节的区别就是为了区分各种不同版本的SD卡、iNand的细节差异。

SD卡/iNand操作

硬件接口:DATA、CLK、CMD

  • iNand的IO线有8根,支持1、4、8线并行传输模式;SD卡IO线有4根,支持1、4线并行传输模式。

    SD 存储卡系统的总线拓扑结构

    sd_bus

    • CLK:主机向卡发送的时钟信号;
    • CMD: 双向的命令/响应信号;
    • DAT0~DAT3: 4 个双向的数据信号;
    • VDD、VSS1、VSS2: 电源和地信号。
  • CMD线用来传输命令、CLK线用来传输时钟信号。

  • 接口有CLK线,工作时主机SoC通过CLK线传输时钟信号给SD卡/iNand芯片,说明:SD/iNand是同步的,SD/iNand的工作速率是由主机给它的CLK频率决定的。

命令响应的操作模式

response

SD协议事先定义了很多标准命令(CMD0、CMD1·····),每个命令都有它的作用和使用条件和对应的响应。SD卡工作的时候就是一个一个的命令周期组合起来的,在一个命令周期中,主机先发送CMD给SD卡,然后SD卡解析这个命令并且执行这个命令,然后SD卡根据结果回发给主机SoC一个响应。(有些命令是不需要响应的,这时SD卡不会给主机回发响应,主机也不用等待响应)。

标准的命令+响应的周期中,主机发完一个命令后应该等待SD卡的响应而不是接着发下一条命令。

read

write

SD/iNand的体系结构图

sd

SD卡内部有一个接口控制器,这个控制器类似于一个单片机,这个单片机的程序功能就是通过CMD线接收外部主机SoC发给SD卡的命令码,然后执行这个命令并且回发响应给主机SoC。这个单片机处理命令及回发响应遵循的就是SD协议。这个单片机同时可以控制SD卡内部的存储单元,可以读写存储单元

sdpan

SD/iNand的寄存器(重点是RCA寄存器)

  • 这里说的是SD卡内部的寄存器,而不是主机SoC的SD控制器的寄存器。(很多外置芯片内部都是有寄存器的,这些寄存器可以按照一定的规则访问,访问这些寄存器可以得知芯片的一些信息)。
  • RCA(relative address,相对地址寄存器)。我们在访问SD卡时,实际上SD卡内部每个存储单元的地址没有绝对数字,都是使用相对地址。相对地址由SD卡自己决定的,存放在RCA寄存器中。

SoC的SD/MMC/iNand控制器简介

  • 不同的SoC可能在SD/MMC/iNand等支持方面有差异,但是如果支持都是通过内部提供SD控制器来支持的。
  • S5PV210的SD卡控制器在Section8.7部分

SD/iNand 相关实战知识点

命令码CMD和ACMD

  • SD卡工作在命令+响应的模式下。
  • SD协议的命令分2种:CMDx和ACMDx。CMD是单命令命令,就是单独发一个CMD即可表示一个意思。ACMD是一种扩展,累加命令(Accumulate cmd),就是发2个CMD加起来表示一个意思。可以认为ACMDx = CMDy+CMDz(y一般是55)

卡类型识别SD or MMC?

  • MMC协议、SD协议、eMMC协议本身是一脉相承的,所以造成了一定的兼容性,所以当我们SoC控制器工作时连接到SoC上的可能是一个MMC卡、也可能是SD卡、也可能是iNand芯片。主机SoC需要去识别这个卡到底是什么版本的卡

  • SoC如何区分卡种类?因为不同版本的卡内部协议不同的,所以对卡识别命令的响应也是不同的。SoC通过发送一些命令、听取响应就可以根据不同的响应判定卡的版本。

    card_init

卡状态

  • SD卡内部的接口控制器类似于一个单片机,这个单片机其实是一个状态机。所以SD卡任何时候都属于某一种状态(空闲状态、准备好状态、读写状态、出错状态····都是事先定义好的),在这种状态下能够接受的命令是一定的,接受到命令之后执行一定的操作然后根据操作结果会跳转为其他状态。如果主机发过来的命令和当前状态不符状态机就不响应,如果收到命令和当前状态相符就会执行相应操作,执行完之后根据结果跳转为其他状态。

    card_stat

卡回复类型

  • 一般来说,SD卡的命令都属于:命令+响应的模式。也有极少数的SD卡命令是不需要回复的。
  • 卡回复有R1、R7、R1B等8种类型,每种卡回复类型都有自己的解析规则。然后卡在特定状态下响应特定命令时有可能回复哪种响应都是SD协议事先规定好的,详细细节要查阅协议文档。

linux内核风格的寄存器定义

  • 定义一个基地址,然后定义要访问的寄存器和基地址之间的偏移量,在最终访问寄存器地址时,地址就等于基地址+偏移量

SD/iNand相关的GPIO初始化

  • GPG0相关的GPIO初始化,参考LED部分的设置技术
  • 时钟设置参考裸机第六部分时钟那一章,设置时使用到了位操作技巧,参考C高级第二部分
  • 要求能够在两三分钟之内完全看懂这些代码涉及到的知识,要能够在数据手册、原理图中找到相对应的点,要能够瞬间明白代码中涉及到的C语言语法技巧,这样才叫融会贯通,才能够从代码中学到东西。

SD/iNand的时钟设置

  • SD卡本身工作需要时钟,但是自己又没有时钟发生单元,依靠主机SoC的控制器通过SD接口中的CLK线传一个时钟过来给SD卡内部使用。所以主机SD卡控制器先初始化好自己的时钟,然后将自己的时钟传给SD卡。
  • 因为此时刚开始和SD卡通信,主机不清楚SD卡属于哪个版本(高版本和低版本的SD卡的读写速率不同,高版本的可以工作在低版本的速率下,低版本的SD卡不能工作在高版本速率下),所以先给SD卡发400KHz的低速率时钟,SD卡拿到这个时钟后就能工作了。然后在后面和SD卡进行进一步通信时去识别SD卡的版本号,识别后再根据SD卡的版本进一步给它更合适的时钟。

SD/iNand 代码分析

GPIO 的设置

我们要通过 GPIO 控制外部 SD2 插槽的 SD 卡。从主板原理图和核心板原理图可以找到控制 SD2 的 GPIO 管脚,从下图可以看到 SD2 卡接的是 MMC2 系列管脚

sd2

从核心板原理图可以看到 MMC2 接到的是 S5PV210 的 GPG2 管脚,那么就要把 GPG2 管脚先初始化成和 SD 相关的功能

inand0_sd23.jpg

从《S5PV210数据手册》的P162 可以看到,GPG2CON 寄存器的说明。由此可以看到应该把 GPG2 配置成 0x2222222

Port Group GPG2 Control Register (GPG2CON, R/W, Address = 0xE020_01E0)

GPG2CON Bit 说明 初始值
GPG2CON[6] [27:24] 0010 = SD_2_DATA[3] 0000
GPG2CON[5] [23:20] 0010 = SD_2_DATA[2] 0000
GPG2CON[4] [19:16] 0010 = SD_2_DATA[1] 0000
GPG2CON[3] [15:12] 0010 = SD_2_DATA[0] 0000
GPG2CON[2] [11:8] 0010 = SD_2_CDn 0000
GPG2CON[1] [7:4] 0010 = SD_2_CMD 0000
GPG2CON[0] [3:0] 0000 = Input
0001 = Output
0010 = SD_2_CLK
0011 ~ 1110 = Reserved
1111 = GPG2_INT[0]
0000
GPG2PUD Bit 说明 初始值
GPG2PUD[n] [2n+1:2n]
n=0~6
00 = Pull-up/ down disabled
01 = Pull-down enabled
10 = Pull-up enabled
11 = Reserved
0x1555
GPG2DRV Bit 说明 初始值
GPG2DRV[n] [2n+1:2n] n=0~6 00 = 1x
10 = 2x
01 = 3x
11 = 4x
0x0000

相关的代码如下:

	// 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);	

SoC 和SD相关的控制器时钟的设置

由 S5PV210 的时钟框图 P362 可知,SCLK_MMC 的时钟源可以通过 MUX_MMC 开关来选择,我们这里选择的是 SCLK_EPLL,在通过分频器分频得到最后的时钟。相关寄存器如下:

Clock Source Control Registers (CLK_SRC4, R/W, Address = 0xE010_0210)

CLK_SRC0 Bit 说明 初始值
MMC2_SEL [11:8] Control MUXMMC2, which is the source clock of MMC2
(0000: XXTI, 0001: XUSBXTI, 0010: SCLK_HDMI27M, 0011:
SCLK_USBPHY0, 0100: SCLK_USBPHY1, 0101:
SCLK_HDMIPHY, 0110: SCLKMPLL, 0111: SCLKEPLL, 1000:
SCLKVPLL, OTHERS: reserved)
0x0

Clock Divider Control Register (CLK_DIV4, R/W, Address = 0xE010_0310)

CLK_DIV4 Bit 说明 初始值
MMC2_RATIO [11:8] DIVMMC2 clock divider ratio,
SCLK_MMC2 = MOUTMMC2 / (MMC2_RATIO + 1)
0x0

复位主机控制器

复位主机SoC控制器,而不是复位SD卡。

	__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);
	}	

在 7.9.1 SD/MMC 控制器章节(P1061) 可以看到软复位的寄存器

Register Address R/W 说明 初始值
SWRST2 0xEB20_002F R/W Software Reset Register (Channel 2) 0x0

向该寄存器的每个位写入1时,会产生复位脉冲。完成复位后,主机控制器清除每个位。由于完成软件复位需要时间,SD主机驱动程序确认这些位是0。

SWRST Bit 说明 初始值
保留 [7:3] 保留 0x0
RSTDAT [2] 仅复位数据电路 1 - 复位;0 - 复位完成 0x0
RSTCMD [1] 仅复位命令电路 1 - 复位;0 - 复位完成 0x0
RSTALL [0] 复位所有电路 1 - 复位;0 - 复位完成 0x0

设置 SD 卡的时钟

上面设置的是SoC的SD控制器的时钟,现在设置的是SD卡的时钟

Hsmmc_SetClock(400000); //400k

通过 SoC 的 SD/MMC 控制器开关 SD 卡的时钟

CLKCON Bit 说明 初始值
SELFREQ [15:8] SDCLK 频率选择  
STBLEXTCLK [3] 外部时钟稳定
如果将该寄存器中的 SD Clock Enable 写到 1 后,SD 时钟输出稳定,则该位被设置为 1。 SD 主机驱动器应该等待该位被设置为 1 再发出命令启动
0x0
ENSDCLK [2] SD 时钟使能
如果该位写为0,则主机控制器停止SDCLK。
如果该位为0,则 SDCLK 频率选择会发生变化。然后,主机控制器保持相同的时钟频率,直到SDCLK停止(停止在SDCLK=0时)。如果在当前状态寄存器中插入的卡被拔除,则会清除该位(RW)。
0x0
ENINTCLK [0] 启用内部时钟 0x0
static void Hsmmc_ClockOn(uint8_t On)
{
	uint32_t Timeout;
	if (On) {
		__REGw(HSMMC_BASE+CLKCON_OFFSET) |= (1<<2); // sd时钟使能
		Timeout = 1000; // Wait max 10 ms
		while (!(__REGw(HSMMC_BASE+CLKCON_OFFSET) & (1<<3))) {
			// 等待SD输出时钟稳定
			if (Timeout == 0) {
				return;
			}
			Timeout--;
			Delay_us(10);
		}
	} else {
		__REGw(HSMMC_BASE+CLKCON_OFFSET) &= ~(1<<2); // sd时钟禁止
	}
}

设置 SD 卡的时钟

CONTROL2_2, R/W, Address = 0xEB20_0080

CONTROL2 Bit 说明 初始值
ENSTAASYNCCLR [31] 启用异步模式清除写入状态
该位使正常和错误中断状态位的Async-clear启用。在初始化程序命令操作过程中,该位应被启用。
0 = Disable
1 = Enable
0x0
ENCMDCNFMSK [30] 启用命令冲突掩码 0x0
ENCLKOUTHOLD [8] SDCLK Hold Enable
SDCLK的进入和退出保持状态由主机控制器完成
改为应该为1
0x0
SELBASE CLK [5:4] 基础时钟源选择
00 or 01 = HCLK
10 = SCLK_MMC0~3 (from SYSCON),
11 = Reserved
00

CONTROL3_2, R/W, Address = 0xEB20_0084

CONTROL3 Bit 说明 初始值
FCSEL3 [31] Feedback Clock Select [3]  
FCSEL2 [23] Feedback Clock Select [2]  
FCSEL1 [15] Feedback Clock Select [1]。SD/MMC 控制器获取 SD 卡反馈回来的时钟,以确定对方时钟是否与设定的一致。当时钟比率较大时,用来提高时钟精度 0x0
FIA1 [14:8] FIFO 中断地址寄存器
FIFO(512Byte缓冲存储器,字地址单元)
初始值(0x3F)在256字节(64字)位置产生
0x3F
CLKCON Bit 说明 初始值
SELFREQ [15:8] SDCLK 频率选择  
STBLEXTCLK [3] 外部时钟稳定 如果将该寄存器中的 SD Clock Enable 写到 1 后,SD 时钟输出稳定,则该位被设置为 1。 SD 主机驱动器应该等待该位被设置为 1 再发出命令启动 0x0
ENSDCLK [2] SD 时钟使能 如果该位写为0,则主机控制器停止SDCLK。 如果该位为0,则 SDCLK 频率选择会发生变化。然后,主机控制器保持相同的时钟频率,直到SDCLK停止(停止在SDCLK=0时)。如果在当前状态寄存器中插入的卡被拔除,则会清除该位(RW)。 0x0
ENINTCLK [0] 启用内部时钟  
static void Hsmmc_SetClock(uint32_t Clock)
{
	uint32_t Temp;
	uint32_t Timeout;
	uint32_t i;
	Hsmmc_ClockOn(0); // 关闭时钟	
	Temp = __REG(HSMMC_BASE+CONTROL2_OFFSET);
	// Set SCLK_MMC(48M) from SYSCON as a clock source	
	Temp = (Temp & (~(3<<4))) | (2<<4);
	Temp |= (1u<<31) | (1u<<30) | (1<<8);
	if (Clock <= 500000) {
		Temp &= ~((1<<14) | (1<<15));
		__REG(HSMMC_BASE+CONTROL3_OFFSET) = 0;
	} else {
		Temp |= ((1<<14) | (1<<15));
		__REG(HSMMC_BASE+CONTROL3_OFFSET) = (1u<<31) | (1<<23);
	}
	__REG(HSMMC_BASE+CONTROL2_OFFSET) = Temp;
	
	for (i=0; i<=8; i++) {
		if (Clock >= (48000000/(1<<i))) {
			break;
		}
	}
	Temp = ((1<<i) / 2) << 8; // clock div
	Temp |= (1<<0); // Internal Clock Enable
	__REGw(HSMMC_BASE+CLKCON_OFFSET) = Temp;
	Timeout = 1000; // Wait max 10 ms
	while (!(__REGw(HSMMC_BASE+CLKCON_OFFSET) & (1<<1))) {
		// 等待内部时钟振荡稳定
		if (Timeout == 0) {
			return;
		}
		Timeout--;
		Delay_us(10);
	}
	
	Hsmmc_ClockOn(1); // 使能时钟
}

后面的代码分析与上面一致就不过多赘述