⑳tiny4412 Linux驱动开发之MMC子系统驱动程序

本次我们来说一下MMC子系统的控制器的开发部分,这部分也是和硬件平台相关的,在说这个之前,我们先来了解一下相关硬件的基础知识和概念.

MMC
MMC全称MultiMedia Card,由西门子公司和SanDisk公司1997年推出的多媒体记忆卡标准。MMC卡尺寸为32mm x24mm x 1.4mm,它将存贮单元和控制器一同做到了卡上,智能的控制器使得MMC保证兼容性和灵活性。MMC卡具有MMC和SPI两种工作模式,MMC模式是默认工作模式,具有MMC的全部特性。而SPI模式则是MMC协议的一个子集,主要用于低速系统。


SD
SD卡全称Secure DigitalMemory Card,由松下、东芝和SanDisk公司于1999年8月共同开发的新一代记忆卡标准,已完全兼容MMC标准。SD卡比MMC卡多了一个进行数据著作权保护的暗号认证功能,读写速度比MMC卡快4倍。SD卡尺寸为32mm x 24mm x2.1mm,长宽和MMC卡一样,只是比MMC卡厚了0.7mm,以容纳更大容量的存贮单元。SD卡与MMC卡保持向上兼容,也就是说,MMC卡可以被新的设有SD卡插槽的设备存取,但是SD卡却不可以被设有MMC插槽的设备存取。


SDIO
SDIO全称Secure DigitalInput and Output Card,SDIO是在SD标准上定义了一种外设接口,它使用SD的I/O接口来连接外围设备,并通过SD上的I/O数据接口与这些外围设备传输数据。现在已经有很多手持设备支持SDIO功能,而且许多SDIO外设也被开发出来,目前常见的SDIO外设有:WIFI Card、GPS Card、Bluetooth Card等等。


eMMC
eMMC全称Embedded MultiMediaCard,是MMC协会所制定的内嵌式存储器标准规格,主要应用于智能手机和移动嵌入式产品等。eMMC是一种嵌入式非易失性存储系统,由闪存和闪存控制器两部分组成,它的一个明显优势是在封装中集成了一个闪存控制器,它采用JEDEC标准BGA封装,并采用统一闪存接口管理闪存。eMMC结构由一个嵌入式存储解决方案组成,带有MMC接口、快闪存储设备及主控制器,所有这些由一个小型BGA封装。由于采用标准封装,eMMC也很容易升级,并不用改变硬件结构。eMMC的这种将Nand Flash芯片和控制芯片封装在一起的设计概念,就是为了简化产品内存储器的使用,客户只需要采购eMMC芯片放进产品中,不需要处理其它复杂的Nand Flash兼容性和管理问题,减少研发成本和研发周期。

本次是以SD卡拔插做为实验对象,所以,这里详细讲一下SDIO和SD卡的基础.SDIO是一种应用很广泛的接口,下面给出一张图清晰地表明了这一点:

SD卡物理结构

一张SD卡包括有存储单元、存储单元接口、电源检测、卡及接口控制器和接口驱动器5个部分,存储单元是存储数据部件,存储单元通过存储单元接口与卡控制单元进行数据传输:电源检测单元保证SD卡工作在合适的电压下,如出现掉电或上状态时,它会使控制单元和存储单元接口复位:卡及接口控制单元控制SD卡的运行状态,它包括有8个寄存器;接口驱动器控制SD卡引脚的输入输出。

SD卡总共有8个寄存器,用于设定或表示SD卡信息,参考下表,这些寄存器只能通过对应的命令访问,对SD卡进行控制操作并不是像操作控制器GPIO相关寄存器那样一次读写一个寄存器的,它是通过命令来控制,SDIO定义了64个命令,每个命令都有特殊意义,可以实现某一特定功能,SD卡接收到命令后,根据命令要求对SD卡内部寄存器进行修改,程序控制中只需要发送组合命令就可以实现SD卡的控制以及读写操作。

名称 bit宽度 描述
CID 128 卡识别号(Card identification number):用来识别的卡的个体号码(唯一的)
RCA 16 相对地址(Relative card address):卡的本地系统地址,初始化时,动态地由卡建议,主机核准。
DSR 16 驱动级寄存器(Driver Stage Register):配置卡的输出驱动
CSD 128 卡的特定数据(Card Specific Data):卡的操作条件信息
SCR 64 SD配置寄存器(SD Configuration Register):SD卡特殊特性信息
OCR 32 操作条件寄存器(Operation conditions register)
SSR 512 SD状态(SD Status):SD卡专有特征的信息
CSR 32 卡状态(Card Status):卡状态信息

SDIO总线

1).总线拓扑

SD卡一般都支持SDIO和SPI这两种接口,共3种模式,分别是4bit模式,1bit模式和SPI模式,SD卡总线拓扑参考下图。虽然可以共用总线,但不推荐多卡槽共用总线信号,要求一个单独SD总线应该连接一个单独的SD卡。

SD卡使用9-pin接口通信,其中3根电源线、1根时钟线、1根命令线和4根数据线,具体说明如下:

●CLK:时钟线,由SDIO主机产生;  
●CMD:命令控制线,SDIO主机通过该线发送命令控制SD卡,如果命令要求SD卡提供应答(响应),SD卡也是通过该线传输应答信息;
●D0-3:数据线,传输读写数据:SD卡可将D0拉低表示忙状态; 
●VDD、VSS1、VSS2:电源和地信号。

SDIO不管是从主机控制器向SD卡传输,还是SD卡向主机控制器传输都只以CLK时钟线的上升沿为有效。SD卡操作过程会使用两种不同频率的时钟同步数据,一个是识别卡阶段时钟频率FOD,最高为400kHz,另外一个是数据传输模式下时钟频率FPP,这个频率按MHz来计算,可以通过配置寄存器的方式修改.

2).SDIO传输模式

SPI mode, 1-bit mode, 4-bit mode.

Pin# SD 4-bit Mode SD 1-bit Mode SPI Mode
1 CD/DAT[3] Data Line 3 N/C Not Used CS Card Select
2 CMD Command Line CMD Command Line DI Data Input
3 VSS1 Ground VSS1 Ground VSS1 Ground
4 VDD Supply Voltage VDD Supply Voltage VDD Supply Voltage
5 CLK Clock CLK Clock SCLK Clock
6 VSS2 Ground VSS2 Ground VSS2 Ground
7 DAT[0] Data Line 0 DATA Data Line DO Data Output
8 DAT[1] Data Line 1/Interrupt IRQ Interrupt IRQ Interrupt
9 DAT[2] Data Line 2/Read Wait RW Read Wait NC Not Used

3).总线协议

SD总线通信是基于命令和数据传输的。通讯由一个起始位(“0”),由一个停止位(“1”)终止。SD通信一般是主机发送一个命令(Command),从设备在接收到命令后作出响应(Response),如有需要会有数据(Data)传输参与.SD总线的基本交互是命令与响应交互.

SD数据是以块(Black)形式传输的,SDHC卡数据块长度一般为512字节,数据可以从主机到卡,也可以是从卡到主机。数据块需要CRC位来保证数据传输成功。CRC位由SD卡系统硬件生成。HOST控制器可以控制使用单线或4线传输,本开发板设计使用4线传输下图为主机向SD卡写入数据块操作示意:

SD数据传输支持单块和多块读写,它们分别对应不同的操作命令,多块写入还需要使用命令来停止整个写入操作。数据写入前需要检测SD卡忙状态,因为SD卡在接收到数据后编程到存储区过程需要一定操作时间。SD卡忙状态通过把D0线拉低表示。数据块读操作与之类似,只是无需忙状态检测.使用4数据线传输时,每次传输4bit数据,每根数据线都必须有起始位、终止位以及CRC位,CRC位每根数据线都要分别检查,并把检查结果汇总然后在数据传输完后通过D0线反馈给主机.SD卡数据包有两种格式,一种是常规数据(8bit宽),它先发低字节再发高字节,而每个字节则是先发高位再发低位,4线传输示意如下图.

4线同步发送,每根线发送一个字节的其中两个位,数据位在四线顺序排列发送,DAT3数据线发较高位,DAT0数据线发较低位。另外一种数据包发送格式是宽位数据包格式,对SD 卡而言宽位数据包发送方式是针对SD卡SSR(SD状态)寄存器内容发送的,SSR寄存器总共有512bit,在主机发出ACMD13命令后SD卡将SSR寄存器内容通过DAT线发送给主机。宽位数据包格式见下图:

4).命令

SD命令由主机发出,以广播命令和寻址命令为例,广播命令是针对与SD主机总线连接的所有从设备发送的,寻址命令是指定某个地址设备进行命令传输。

3.1 命令格式

SD命令格式固定为48bit,都是通过CMD线连续传输的(数据线不参与),见下图:

SD 命令的组成如下:
●起始位和终止位:命令的主体包含在起始位与终止位之间,它们都只包含一个数据位,起始位为0,终止位为1。
●传输标志:用于区分传输方向,该位为1时表示命令,方向为主机传输到SD卡,该位为0时表示响应,方向为SD卡传输到主机。
命令主体内容包括命令、地址信息/参数和CRC 校验三个部分。
●命令号:它固定占用6bit,所以总共有64个命令(代号CMD0~CMD63),每个命令都有特定的用途,部分命令不适用于SD卡操作,只是专门用于MMC卡或者SD I/O卡。
●地址/参数:每个命令有32bit地址信息/参数用于命令附加内容,例如,广播命令没有地址信息,这32bit用于指定参数,而寻址命令这32bit用于指定目标SD卡的地址。
●CRC7校验:长度为7bit的校验位用于验证命令传输内容正确性,如果发生外部干扰导致传输数据个别位状态改变将导致校准失败,也意味着命令传输失败,SD卡不执行命令。

3.2 命令类型

SD命令有4种类型:  
●无响应广播命令(bc),发送到所有卡,不返回任务响应;
●带响应广播命令(bcr),发送到所有卡,同时接收来自所有卡响应;
●寻址命令(ac),发送到选定卡,DAT线无数据传输;
●寻址数据传输命令(adtc),发送到选定卡,DAT线有数据传输。

另外,SD卡主机模块系统旨在为各种应用程序类型提供一个标准接口。在此环境中,需要有特定的客户/应用程序功能。为实现这些功能,在标准中定义了两种类型的通用命令:特定应用命令(ACMD)和常规命令(GEN_CMD)。要使用SD卡制造商特定的ACMD命令如ACMD6,需要在发送该命令之前无发送CMD55命令,告知SD卡接下来的命令为特定应用命令。CMD55命令只对紧接的第一个命令有效,SD卡如果检测到CMD55之后的第一条命令为ACMD则执行其特定应用功能,如果检测发现不是ACMD命令,则执行标准命令。

3.3 命令描述

SD卡系统的命令被分为多个类,每个类支持一种“卡的功能设置”。下表列举了SD卡部分命令信息,更多详细信息可以参考SD简易规格文件说明,表中填充位和保留位都必须被设置为0.虽然没有必须完全记住每个命令详细信息,但越熟悉命令对后面编程理解非常有帮助。

命令序号 类型 参数 响应 缩写 描述
        基本命令(Class 0)
CMD0 bc [31:0]填充位 - GO_IDLE_STATE 复位所有的卡到idle状态
CMD2 bcr [31:0]填充位 R2 ALL_SEND_CID 通知所有卡通过CMD线返回CID值
CMD3 bcr [31:0]填充位 R6 SEND_RELATIVE_ADDR 通知所有卡发布新RCA
CMD4 bc [31:16]DSR[15:0]填充位 - SET_DSR 编程所有卡的DSR
CMD7 ac [31:16]RCA[15:0]填充位 R1b SELECT/DESELECT_CARD 选择/取消选择RCA 地址卡
CMD8 bcr

[31:12]保留位

[11:8]VHS[7:0]检查模式

R7 SEND_IF_COND 发送SD卡接口条件,包含主机支持的电压信息,并询问卡是否支持
CMD9 ac [31:16]RCA[15:0]填充位 R2 SEND_CSD 选定卡通过CMD线发送CSD内容
CMD10 ac [31:16]RCA[15:0]填充位 R2 SEND_CID 选定卡通过CMD线发送CID内容
CMD12 ac [31:0]填充位 R1b STOP_TRANSMISSION 强制卡停止传输
CMD13 ac [31:16]RCA[15:0]填充位 R1 SEND_STATUS 选定卡通过CMD线发送它状态寄存器
CMD15 ac [31:16]RCA[15:0]填充位 - GO_INACTIVE_STATE 使选定卡进入“inactive”状态
        面向块的读操作(Class 2)
CMD16 ac [31:0]块长度 R1 SET_BLOCK_LEN 对于标准SD卡,设置块命令的长度,对于SDHC卡块命令长度固定为512字节
CMD17 adtc [31:0]数据地址 R1 READ_SINGLE_BLOCK 对于标准卡,读取SEL_BLOCK_LEN长度字节的块;对于SDHC卡,读取512字节的块
CMD18 adtc [31:0]数据地址 R1 READ_MULTIPLE_BLOCK 连续从SD卡读取数据块,直到被CMD12中断。块长度同CMD17
        面向块的写操作(Class 4)
CMD24 adtc [31:0]数据地址 R1 WRITE_BLOCK 对于标准卡,写入SEL_BLOCK_LEN长度字节的块;对于SDHC卡,写入12字节的块
CMD25 adtc [31:0]数据地址 R1 WRITE_MILTIPLE_BLOCK 连续向SD卡写入数据块,直到被CMD12中断。每块长度同CMD17
CMD27 adtc [31:0]填充位 R1 PROGRAM_CSD 对CSD的可编程位进行编程
        擦除命令(Class 5)
CMD32 ac [31:0]数据地址 R1 ERASE_WR_BLK_START 设置擦除的起始块地址
CMD33 ac [31:0]数据地址 R1 ERASE_WR_BLK_END 设置擦除的结束块地址
CMD38 ac [31:0]填充位 R1b ERASE 擦除预先选定的块
        加锁命令(Class 7)
CMD42 adtc [31:0]保留 R1 LOCK_UNLOCK 加锁/解锁SD卡
        特定应用命令(Class 8)
CMD55 ac [31:16]RCA[15:0]填充位 R1 APP_CMD 指定下个命令为特定应用命令,不是标准命令
CMD56 adtc [31:1]填充位[0]读/写 R1 GEN_CMD 通用命令,或者特定应用命令中,用于传输一个数据块,最低位为1表示读数据,为0表示写数据
        SD卡特定应用命令
ACMD6 ac [31:2]填充位[1:0]总线宽度 R1 SET_BUS_WIDTH 定义数据总线宽度('00'=1bit,'10'=4bit)
ACMD13 adtc [31:0]填充位 R1 SD_STATUS 发送SD状态
ACMD41 Bcr

[32]保留位

[30]HCS(OCR[30])

[29:24]保留位

[23:0]VDD电压(OCR[23:0])

R3 SD_SEND_OP_COND 主机要求卡发送它的支持信息HCS)和OCR寄存器内容
ACMD51 adtc [31:0]填充位 R1 SEND_SCR 读取配置寄存器SCR

3.4 响应

响应由SD卡向主机发出,部分命令要求SD卡作出响应,这些响应多用于反馈SD卡的状态。SDIO总共有7个响应类型(代号:R1~R7),其中SD卡没有R4、R5类型响应。特定的命令对应有特定的响应类型,比如当主机发送CMD3命令时,可以得到响应R6.与命令一样,SD卡的响应也是通过 CMD 线连续传输的。根据响应内容大小可以分为短响应和长响应。短响应是48bit长度,只有R2类型是长响应,其长度为136bit。各个类型响应具体情况如下表。除了R3类型之外,其他响应都使用CRC7校验来校验,对于R2类型是使用CID和CSD寄存内部CRC7。

        R1(正常响应命令)
描述 起始位 传输位 命令号 卡状态 CRC7 终止位
bit 47 46 [45:40] [39:8] [7:1] 0
位宽 1 1 6 32 7 1
"0" "0" x x x "1"
备注 如果有传输到卡的数据,那么在数据线可能有busy信号
        R2(CID, CSD寄存器)
描述 起始位 传输位 保留 [127:1] 终止位
bit 135 134 [133:128] 127 0
位宽 1 1 6 x 1
"0" "0" "111111" CID或者CSD寄存器[127:1]为的值 "1"
备注 CID寄存器内容作为CMD2和CMD10响应,CSD寄存器内容作为CMD9响应.
        R3(OCR寄存器)
描述 起始位 传输位 保留 OCR寄存器 保留 终止位
bit 47 46 [45:40] [39:8] [7:1] 0
位宽 1 1 6 32 7 1
"0" "0" "111111" x "111111" "1"
备注 OCR寄存器的值作为ACMD41的响应
        R6(发布的RCA寄存器响应)
描述 起始位 传输位 CMD3 RCA寄存器 卡状态位 CRC7 终止位
bit 47 46 [45:40] [39:8] [7:1] 0
位宽 1 1 6 16 16 7 1
"0" "0" "000011" x x x "1"
备注 专用于命令CMD3的响应
        R7(发布的RCA寄存器响应)
描述 起始位 传输位 CMD8 保留 接收电压 检测模式 CRC7 终止位
bit 47 46 [45:40] [39:20] [19:16] [15:8] [7:1] 0
位宽 1 1 6 20 4 8 7 1
"0" "0" "001000" "00000h" x x x "1"
备注 专用于命令CMD8的响应,返回卡支持电压范围和检测模式

4). SD卡的操作模式及切换

4.1 SD卡的操作模式

SD卡系统(包括主机和SD卡)定义了两种操作模式:卡识别模式和数据传输模式。在系统复位后,主机处于卡识别模式,寻找总线上可用的SDIO设备;同时,SD卡也处于卡识别模式,直到被主机识别到,即当SD卡接收到SEND_RCA(CMD3)命令后,SD卡就会进入数据传输模式,而主机在总线上所有卡被识别后也进入数据传输模式。在每个操作模式下,SD卡都有几种状态,参考下表,通过命令控制实现卡状态的切换。

操作模式 SD卡状态
无效模式(Inactive) 无效状态(Inactive State)
卡识别模式(Card identification mode) 空闲状态(Idle State)
准备状态(Ready State)
识别状态(Identification State)
数据传输模式(Data transfer mode) 待机状态(Stand-by State)
传输状态(Transfer State)
发送数据状态(Sending-data State)
接收数据状态(Receive-data State)
编程状态(Programming State)
断开连接状态(Disconnect State)

4.2 卡识别模式

在卡识别模式下,主机会复位所有处于“卡识别模式”的SD卡,确认其工作电压范围,识别SD卡类型,并且获取SD卡的相对地址(卡相对地址较短,便于寻址)。在卡识别过程中,要求SD卡工作在识别时钟频率FOD的状态下。卡识别模式下SD卡状态转换如下图。

主机上电后,所有卡处于空闲状态,包括当前处于无效状态的卡。主机也可以发送GO_IDLE_STATE(CMD0)让所有卡软复位从而进入空闲状态,但当前处于无效状态的卡并不会复位。

主机在开始与卡通信前,需要先确定双方在互相支持的电压范围内。SD卡有一个电压支持范围,主机当前电压必须在该范围可能才能与卡正常通信。SEND_IF_COND(CMD8)命令就是用于验证卡接口操作条件的(主要是电压支持)。卡会根据命令的参数来检测操作条件匹配性,如果卡支持主机电压就产生响应,否则不响应。而主机则根据响应内容确定卡的电压匹配性。CMD8是SD卡标准V2.0版本才有的新命令,所以如果主机有接收到响应,可以判断卡为V2.0或更高版本SD卡。

SD_SEND_OP_COND(ACMD41)命令可以识别或拒绝不匹配它的电压范围的卡。ACMD41命令的VDD电压参数用于设置主机支持电压范围,卡响应会返回卡支持的电压范围。对于对CMD8有响应的卡,把ACMD41命令的HCS位设置为1,可以测试卡的容量类型,如果卡响应的CCS位为1说明为高容量SD卡,否则为标准卡。卡在响应ACMD41之后进入准备状态,不响应ACMD41的卡为不可用卡,进入无效状态。ACMD41是应用特定命令,发送该命令之前必须先发CMD55。

ALL_SEND_CID(CMD2)用来控制所有卡返回它们的卡识别号(CID),处于准备状态的卡在发送CID之后就进入识别状态。之后主机就发送SEND_RELATIVE_ADDR(CMD3)命令,让卡自己推荐一个相对地址(RCA)并响应命令。这个RCA是16bit地址,而CID是128bit地址,使用RCA简化通信。卡在接收到CMD3并发出响应后就进入数据传输模式,并处于待机状态,主机在获取所有卡RCA之后也进入数据传输模式。

4.3 数据传输模式

只有SD卡系统处于数据传输模式下才可以进行数据读写操作。数据传输模式下可以将主机SD时钟频率设置为FPP,默认最高为25MHz,频率切换可以通过CMD4命令来实现。数据传输模式下,SD卡状态转换过程见下图。

CMD7用来选定和取消指定的卡,卡在待机状态下还不能进行数据通信,因为总线上可能有多个卡都是出于待机状态,必须选择一个RCA地址目标卡使其进入传输状态才可以进行数据通信。同时通过CMD7命令也可以让已经被选择的目标卡返回到待机状态。

数据传输模式下的数据通信都是主机和目标卡之间通过寻址命令点对点进行的。卡处于传输状态下可以使用CMD那张表中面向块的读写以及擦除命令对卡进行数据读写、擦除。CMD12可以中断正在进行的数据通信,让卡返回到传输状态。CMD0和CMD15会中止任何数据编程操作,返回卡识别模式,这可能导致卡数据被损坏。

下面我们看一下Exynos4的资源情况:

1).电路图如下:

2).核心板:

3).SoC feature:

4).SDMMC Clock Domain

MMC子系统

系统框架:

Linux MMC子系统主要分成三个部分:

●MMC核心层:完成不同协议和规范的实现,为host层和设备驱动层提供接口函数。MMC核心层由三个部分组成:MMC,SD和SDIO,分别为三类设备驱动提供接口函数;
●Host驱动层:针对不同主机端的SDHC、MMC控制器的驱动;
●Client驱动层:针对不同客户端的设备驱动程序。如SD卡、T-flash卡、SDIO接口的GPS和wi-fi等设备驱动。

代码结构:

MMC子系统代码主要在drivers/mmc目录下,共有三个目录:
Card:与块设备调用相关驱动,如MMC/SD卡设备驱动,SDIOUART;
Core:整个MMC的核心层,这部分完成不同协议和规范的实现,为host层和设备驱动层提供接口函数;
Host:针对不同主机端的SDHC、MMC控制器的驱动,这部分需要由驱动工程师来完成;

注册流程:

在linux系统中,系统启动时将加载相关配置的驱动模块,而各模块的加载将通过各自之间相应的结构关系进行先后顺序进行设备注册,下面是mmc子系统的注册流程:
core —> host —> card

core层的注册主要创建两条虚拟总线mmc_bus和sdio_bus,为host层
host注册主要为相关控制器的初始化及配置参数
card层主要用于与block设备进行绑定,为数据读写准备

MMC控制器驱动的软件框架

MMC子系统读写流程

读流程

数据读命令:CMD17和CMD18(single block和multiple block)

写流程

数据读命令:CMD24和CMD25(single block和multiple block)

下面是我实现的代码,主要是根据原厂的本地化了一下,区别不大:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>

#include <linux/mmc/host.h>

#include <plat/sdhci.h>
#include <plat/regs-sdhci.h>

#include "sdhci.h"


#define MAX_BUS_CLK         (4)

/**
 * struct sdhci_exynos4 - EXYNOS4 SDHCI instance
 * @host: The SDHCI host created
 * @pdev: The platform device we where created from.
 * @ioarea: The resource created when we claimed the IO area.
 * @pdata: The platform data for this controller.
 * @cur_clk: The index of the current bus clock.
 * @clk_io: The clock for the internal bus interface.
 * @clk_bus: The clocks that are available for the SD/MMC bus clock.
 */
struct sdhci_exynos4 {
    struct sdhci_host       *host;
    struct platform_device  *pdev;
    struct resource         *ioarea;
    struct s3c_sdhci_platdata *pdata;
    unsigned int            cur_clk;
    int                     ext_cd_irq;
    int                     ext_cd_gpio;
    struct clk              *clk_io;
    struct clk              *clk_bus[MAX_BUS_CLK];
};

struct sdhci_exynos4_drv_data {
    unsigned int sdhci_quirks;
};


static inline struct sdhci_exynos4 *
to_exynos4(struct sdhci_host *host)
{
    return sdhci_priv(host);
}

/**
 * get_curclk - convert ctrl2 register to clock source number
 * @ctrl2: Control2 register value.
 */
static u32
get_curclk(u32 ctrl2)
{
    ctrl2 &= S3C_SDHCI_CTRL2_SELBASECLK_MASK;
    ctrl2 >>= S3C_SDHCI_CTRL2_SELBASECLK_SHIFT;

    return ctrl2;
}

static void
sdhci_exynos4_check_sclk(struct sdhci_host *host)
{
    struct sdhci_exynos4 *ourhost = to_exynos4(host);
    u32 tmp = readl(host->ioaddr + S3C_SDHCI_CONTROL2);

    if(get_curclk(tmp) != ourhost->cur_clk) {
        printk("%s restored ctrl2 clock setting\n", __func__);

        tmp &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK;
        tmp |= ourhost->cur_clk << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT;
        writel(tmp, host->ioaddr + S3C_SDHCI_CONTROL2);
    }
}

static unsigned int
sdhci_exynos4_consider_clock(struct sdhci_exynos4 *ourhost,
                             unsigned int src,
                             unsigned int wanted)
{
    unsigned long rate;
    struct clk *clksrc = ourhost->clk_bus[src];
    int div;

    if(!clksrc)
        return UINT_MAX;

    /**
     * if controller uses a non-standard clock division, find the best clock
     * speed possible with selected clock source and skip the division.
    */
    if(ourhost->host->quirks & SDHCI_QUIRK_NONSTANDARD_CLOCK) {
        rate = clk_round_rate(clksrc, wanted);
        return wanted - rate;
    }

    rate = clk_get_rate(clksrc);

    for(div = 1; div < 256; div *= 2) {
        if((rate / div) <= wanted)
            break;
    }

    printk("%s clk %d: rate %ld, want %d, got %ld\n", __func__, src, rate, wanted, rate / div);

    return (wanted - (rate / div));
}

/**
 * sdhci_exynos4_get_max_clk - callback to get maximum clock frequency.
 * @host: The SDHCI host instance.
 *
 * Callback to return the maximum clock rate acheivable by the controller.
*/
static unsigned int
sdhci_exynos4_get_max_clk(struct sdhci_host *host)
{
    struct sdhci_exynos4 *ourhost = to_exynos4(host);
    struct clk *busclk;
    unsigned int rate, max;
    int clk;

    sdhci_exynos4_check_sclk(host);

    for(max = 0, clk = 0; clk < MAX_BUS_CLK; clk++) {
        busclk = ourhost->clk_bus[clk];
        if(!busclk)
            continue;

        rate = clk_get_rate(busclk);
        if(rate > max)
            max = rate;
    }

    return max;
}

/**
 * sdhci_exynos4_set_clock - callback on clock change
 * @host: The SDHCI host being changed
 * @clock: The clock rate being requested.
 *
 * When the card's clock is going to be changed, look at the new frequency
 * and find the best clock source to go with it.
*/
static void
sdhci_exynos4_set_clock(struct sdhci_host *host, unsigned int clock)
{
    struct sdhci_exynos4 *ourhost = to_exynos4(host);
    unsigned int best = UINT_MAX;
    unsigned int delta;
    int best_src = 0;
    int src;
    u32 ctrl;

    // don't bother if the clock is going off
    if(0 == clock)
        return;

    for(src = 0; src < MAX_BUS_CLK; src++) {
        delta = sdhci_exynos4_consider_clock(ourhost, src, clock);
        if(delta < best) {
            best = delta;
            best_src = src;
        }
    }

    printk("%s selected source %d, clock %d, delta %d\n", __func__, best_src, clock, best);

    // select the new clock source
    if(ourhost->cur_clk != best_src) {
        struct clk *clk = ourhost->clk_bus[best_src];

        // turn clock off to card before changing clock source
        writew(0, host->ioaddr + SDHCI_CLOCK_CONTROL);

        ourhost->cur_clk = best_src;
        host->max_clk = clk_get_rate(clk);

        ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2);
        ctrl &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK;
        ctrl |= best_src << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT;
        writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL2);
    }

    // reprogram default hardware configuration
    writel(S3C64XX_SDHCI_CONTROL4_DRIVE_9mA, host->ioaddr + S3C64XX_SDHCI_CONTROL4);

    ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2);
    ctrl |= (S3C64XX_SDHCI_CTRL2_ENSTAASYNCCLR |
             S3C64XX_SDHCI_CTRL2_ENCMDCNFMSK |
             S3C_SDHCI_CTRL2_ENFBCLKRX |
             S3C_SDHCI_CTRL2_DFCNT_NONE |
             S3C_SDHCI_CTRL2_ENCLKOUTHOLD);

    writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL2);

    // reconfigure the controller for new clock rate
    ctrl = (S3C_SDHCI_CTRL3_FCSEL1 | S3C_SDHCI_CTRL3_FCSEL0);
    if(clock < 25 * 1000000)
        ctrl |= (S3C_SDHCI_CTRL3_FCSEL3 | S3C_SDHCI_CTRL3_FCSEL2);

    writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL3);
}

/**
 * sdhci_exynos4_get_min_clock - callback to get minimal supported clock value
 * @host: The SDHCI host being queried
 *
 * To init mmc host properly a minimal clock value is needed. For high system
 * bus clock's values the standard formula gives values out of allowed range.
 * The clock still can be set to lower values, if clock source other then
 * system bus is selected.
*/
static unsigned int
sdhci_exynos4_get_min_clock(struct sdhci_host *host)
{
    struct sdhci_exynos4 *ourhost = to_exynos4(host);
    unsigned int delta, min = UINT_MAX;
    int src;

    for(src = 0; src < MAX_BUS_CLK; src++) {
        delta = sdhci_exynos4_consider_clock(ourhost, src, 0);
        if(UINT_MAX == delta)
            continue;

        // delta is a negative value in this case
        if(-delta < min)
            min = -delta;
    }

    return min;
}

/**
 * sdhci_exynos4_platform_8bit_width - support 8bit buswidth
 * @host: The SDHCI host being queried
 * @width: MMC_BUS_WIDTH_ macro for the bus width being requested
 *
 * We have 8-bit width support but is not a v3 controller.
 * So we add platform_8bit_width() and support 8bit width.
 */
static int
sdhci_exynos4_platform_8bit_width(struct sdhci_host *host, int width)
{
    u8 ctrl;

    ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);

    switch(width)
    {
        case MMC_BUS_WIDTH_8:
            ctrl |= SDHCI_CTRL_8BITBUS;
            ctrl &= ~SDHCI_CTRL_4BITBUS;
            break;
        
        case MMC_BUS_WIDTH_4:
            ctrl |= SDHCI_CTRL_4BITBUS;
            ctrl &= ~SDHCI_CTRL_8BITBUS;
            break;

        default:
            ctrl &= ~SDHCI_CTRL_4BITBUS;
            ctrl &= ~SDHCI_CTRL_8BITBUS;
            break;
    }

    sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);

    return 0;
}

static struct sdhci_ops sdhci_exynos4_ops = {
    .get_max_clock      = sdhci_exynos4_get_max_clk,
    .set_clock          = sdhci_exynos4_set_clock,
    .get_min_clock      = sdhci_exynos4_get_min_clock,
    .platform_8bit_width= sdhci_exynos4_platform_8bit_width,
};

// sdhci_cmu_set_clock - callback on clock change
static void
sdhci_cmu_set_clock(struct sdhci_host *host, unsigned int clock)
{
    struct sdhci_exynos4 *ourhost = to_exynos4(host);
    unsigned long timeout;
    u16 clk = 0;

    // don't bother if the clock is going off
    if(0 == clock) {
        clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
        clk &= ~SDHCI_CLOCK_CARD_EN;
        sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
        return;    
    }

    sdhci_exynos4_set_clock(host, clock);

    clk_set_rate(ourhost->clk_bus[ourhost->cur_clk], clock);

    host->clock = clock;

    clk = SDHCI_CLOCK_INT_EN;
    sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);

    // wait max 20 ms
    timeout = 20;
    while(!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL)) & SDHCI_CLOCK_INT_STABLE)) {
        if(0 == timeout) {
            printk("%s %s: Internal clock never stabilised.\n", __func__, mmc_hostname(host->mmc));
            return;
        }

        timeout--;
        mdelay(1);
    }

    clk |= SDHCI_CLOCK_CARD_EN;
    sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
}

// sdhci_cmu_get_min_clock - callback to get minimal supported clock value
static unsigned int
sdhci_cmu_get_min_clock(struct sdhci_host *host)
{
    struct sdhci_exynos4 *ourhost = to_exynos4(host);

    /**
     * initial clock can be in the frequency range of
     * 100KHz-400KHz, so we set it as max value.
    */
    return clk_round_rate(ourhost->clk_bus[ourhost->cur_clk], 400000);
}

// sdhci_cmu_get_max_clock - callback to get maximum clock frequency
static unsigned int
sdhci_cmu_get_max_clock(struct sdhci_host *host)
{
    struct sdhci_exynos4 *ourhost = to_exynos4(host);

    return clk_round_rate(ourhost->clk_bus[ourhost->cur_clk], UINT_MAX);
}

static inline struct sdhci_exynos4_drv_data *
sdhci_exynos4_get_driver_data(struct platform_device *pdev)
{
    return (struct sdhci_exynos4_drv_data *)
            platform_get_device_id(pdev)->driver_data;
}

static void 
sdhci_exynos4_notify_change(struct platform_device *dev, int state)
{
    struct sdhci_host *host = platform_get_drvdata(dev);
    unsigned long flags;

    if(host) {
        spin_lock_irqsave(&host->lock, flags);
        if(state) {
            dev_dbg(&dev->dev, "card inserted.\n");
            host->flags     &= ~SDHCI_DEVICE_DEAD;
            host->quirks    |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
        } else {
            dev_dbg(&dev->dev, "card remove.\n");
            host->flags     |= SDHCI_DEVICE_DEAD;
            host->quirks    &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION;
        }

        tasklet_schedule(&host->card_tasklet);
        spin_unlock_irqrestore(&host->lock, flags);
    }
}

static irqreturn_t
sdhci_exynos4_gpio_card_detect_thread(int irq, void *dev_id)
{
    struct sdhci_exynos4 *sc = dev_id;
    int status = gpio_get_value(sc->ext_cd_gpio);
    if(sc->pdata->ext_cd_gpio_invert)
        status = !status;
    sdhci_exynos4_notify_change(sc->pdev, status);

    return IRQ_HANDLED;
}

static void
sdhci_exynos4_setup_card_detect_gpio(struct sdhci_exynos4 *sc)
{
    struct s3c_sdhci_platdata *pdata = sc->pdata;
    struct device *dev = &sc->pdev->dev;

    if(0 == gpio_request(pdata->ext_cd_gpio, "SDHCI EXT CD")) {
        sc->ext_cd_gpio = pdata->ext_cd_gpio;
        sc->ext_cd_irq  = gpio_to_irq(pdata->ext_cd_gpio);

        if(sc->ext_cd_irq &&
            request_threaded_irq(sc->ext_cd_irq, NULL,
                                sdhci_exynos4_gpio_card_detect_thread,
                                IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
                                dev_name(dev), sc) == 0) {
            int status = gpio_get_value(sc->ext_cd_gpio);
            
            if(pdata->ext_cd_gpio_invert)
                status = !status;
            
            sdhci_exynos4_notify_change(sc->pdev, status);
        } else {
            printk("%s cannot request irq for card detect\n", __func__);
            sc->ext_cd_irq = 0;
        }
    } else {
        printk("%s cannot request gpio for card detect\n", __func__);
    }
}

static int __devinit
sdhci_exynos4_probe(struct platform_device *pdev)
{
    struct s3c_sdhci_platdata *pdata;
    struct sdhci_exynos4_drv_data *drv_data;
    struct device *dev = &pdev->dev;
    struct sdhci_host *host;
    struct sdhci_exynos4 *sc;
    struct resource *res;
    int ret, irq, ptr, clks;

    if(!pdev->dev.platform_data) {
        printk("%s no device data specified\n", __func__);
        return -ENOENT;
    }

    irq = platform_get_irq(pdev, 0);
    if(irq < 0) {
        printk("%s no irq specified\n", __func__);
        return irq;
    }

    host = sdhci_alloc_host(dev, sizeof(struct sdhci_exynos4));
    if(IS_ERR(host)) {
        printk("%s sdhci_alloc_host failed !\n", __func__);
        return PTR_ERR(host);
    }

    pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
    if(!pdata) {
        ret = -ENOMEM;
        goto err_io_clk;
    }
    memcpy(pdata, pdev->dev.platform_data, sizeof(*pdata));

    drv_data = sdhci_exynos4_get_driver_data(pdev);
    sc = sdhci_priv(host);

    sc->host    = host;
    sc->pdev    = pdev;
    sc->pdata   = pdata;
    sc->ext_cd_gpio = pdata->ext_cd_gpio;

    platform_set_drvdata(pdev, host);

    sc->clk_io = clk_get(dev, "hsmmc");
    if(IS_ERR(sc->clk_io)) {
        printk("%s get clock failed !\n", __func__);
        ret = PTR_ERR(sc->clk_io);
        goto err_io_clk;
    }

    // enable the local io clock and keep it running for the moment
    clk_enable(sc->clk_io);

    for(clks = 0, ptr = 0; ptr < MAX_BUS_CLK; ptr++) {
        struct clk *clk;
        char name[14] = {0};

        snprintf(name, 14, "mmc_busclk.%d", ptr);
        clk = clk_get(dev, name);
        if(IS_ERR(clk))
            continue;
        
        clks++;
        sc->clk_bus[ptr] = clk;

        // save current clock index to know which clock bus
        // is used later in overriding functions.
        sc->cur_clk = ptr;

        clk_enable(clk);

        printk("%s clock source %d: %s (%ld Hz)\n", __func__, ptr, name, clk_get_rate(clk));
    }

    if(0 == clks) {
        printk("%s find any bus clocks failed !\n", __func__);
        ret = -ENOENT;
        goto err_no_busclks;
    }

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    host->ioaddr = devm_request_and_ioremap(&pdev->dev, res);
    if(!host->ioaddr) {
        printk("%s map register failed !\n", __func__);
        ret = -ENXIO;
        goto err_req_regs;
    }

    // Ensure we have minimal gpio selected CMD/CLK/Detect
    if(pdata->cfg_gpio)
        pdata->cfg_gpio(pdev, pdata->max_width);

    host->hw_name   = "samsung-hsmmc";
    host->ops       = &sdhci_exynos4_ops;
    host->quirks    = 0;
    host->irq       = irq;

    // setup quirks for the controller
    host->quirks |= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC;
    host->quirks |= SDHCI_QUIRK_NO_HISPD_BIT;
    if(drv_data)
        host->quirks |= drv_data->sdhci_quirks;

#ifndef CONFIG_MMC_SDHCI_S3C_DMA
    // we currently see overruns on errors, so disable the SDMA support as well
    host->quirks |= SDHCI_QUIRK_BROKEN_DMA;
#endif

    /**
     * IT seems we do not get an DATA transfer complete on non-busy
     * transfers, not sure if this is a problem with this specific
     * SDHCI block, or a missing configuration that needs to be set.
    */
    host->quirks |= SDHCI_QUIRK_NO_BUSY_IRQ;

    // this host supports the Auto CMD12
    host->quirks |= SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC;

    if(S3C_SDHCI_CD_NONE == pdata->cd_type
       || S3C_SDHCI_CD_PERMANENT == pdata->cd_type)
        host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;

    if(S3C_SDHCI_CD_GPIO == pdata->cd_type)
        host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;

    if(S3C_SDHCI_CD_PERMANENT == pdata->cd_type)
        host->mmc->caps = MMC_CAP_NONREMOVABLE;

    switch(pdata->max_width)
    {
        case 8:
            host->mmc->caps |= MMC_CAP_8_BIT_DATA;
        case 4:
            host->mmc->caps |= MMC_CAP_4_BIT_DATA;
            break;
    }

    if(pdata->pm_caps)
        host->mmc->pm_caps |= pdata->pm_caps;

    host->quirks |= (SDHCI_QUIRK_32BIT_DMA_ADDR | SDHCI_QUIRK_32BIT_DMA_SIZE);

    // HSMMC on Samsung SoCs uses SDCLK as timeout clock
    host->quirks |= SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK;

    // if controller does not have internal clock divider,
    // we can use overriding functions instead of default.
    if(host->quirks & SDHCI_QUIRK_NONSTANDARD_CLOCK) {
        sdhci_exynos4_ops.set_clock     = sdhci_cmu_set_clock;
        sdhci_exynos4_ops.get_min_clock = sdhci_cmu_get_min_clock;
        sdhci_exynos4_ops.get_max_clock = sdhci_cmu_get_max_clock;
    }

    // it supports additional host capabilities if needed
    if(pdata->host_caps)
        host->mmc->caps |= pdata->host_caps;

    if(pdata->host_caps2)
        host->mmc->caps2 |= pdata->host_caps2;

    pm_runtime_enable(&pdev->dev);
    pm_runtime_set_autosuspend_delay(&pdev->dev, 50);
    pm_runtime_use_autosuspend(&pdev->dev);
    pm_suspend_ignore_children(&pdev->dev, 1);

    ret = sdhci_add_host(host);
    if(ret) {
        printk("%s sdhci_add_host failed !\n", __func__);
        pm_runtime_forbid(&pdev->dev);
        pm_runtime_get_noresume(&pdev->dev);
        goto err_req_regs;
    }

    /**
     * the following two methods of card detection might call
     * sdhci_exynos4_notify_change() immediately, so they can be
     * called only aftr sdhci_add_host(). setup errors are ignored.
    */
    if(S3C_SDHCI_CD_EXTERNAL == pdata->cd_type && pdata->ext_cd_init)
        pdata->ext_cd_init(&sdhci_exynos4_notify_change);

    if(S3C_SDHCI_CD_GPIO == pdata->cd_type
       && gpio_is_valid(pdata->ext_cd_gpio))
        sdhci_exynos4_setup_card_detect_gpio(sc);

    return 0;

err_req_regs:
    for(ptr = 0; ptr < MAX_BUS_CLK; ptr++) {
        if(sc->clk_bus[ptr]) {
            clk_disable(sc->clk_bus[ptr]);
            clk_put(sc->clk_bus[ptr]);
        }
    }

err_no_busclks:
    clk_disable(sc->clk_io);
    clk_put(sc->clk_io);

err_io_clk:
    sdhci_free_host(host);

    return ret;
}

static int __devexit
sdhci_exynos4_remove(struct platform_device *pdev)
{
    struct s3c_sdhci_platdata *pdata = pdev->dev.platform_data;
    struct sdhci_host *host = platform_get_drvdata(pdev);
    struct sdhci_exynos4 *sc = sdhci_priv(host);
    int ptr = 0;

    if(S3C_SDHCI_CD_EXTERNAL == pdata->cd_type && pdata->ext_cd_cleanup)
        pdata->ext_cd_cleanup(&sdhci_exynos4_notify_change);

    if(sc->ext_cd_irq)
        free_irq(sc->ext_cd_irq, sc);

    if(gpio_is_valid(sc->ext_cd_gpio))
        gpio_free(sc->ext_cd_gpio);

    sdhci_remove_host(host, 1);

    pm_runtime_disable(&pdev->dev);

    for(ptr = 0; ptr < 3; ptr++) {
        if(sc->clk_bus[ptr]) {
            clk_disable(sc->clk_bus[ptr]);
            clk_put(sc->clk_bus[ptr]);
        }
    }

    clk_disable(sc->clk_io);
    clk_put(sc->clk_io);

    sdhci_free_host(host);
    platform_set_drvdata(pdev, NULL);

    return 0;
}

#ifdef CONFIG_PM_SLEEP
static int
sdhci_exynos4_suspend(struct device *dev)
{
    struct sdhci_host *host = dev_get_drvdata(dev);
    struct sdhci_exynos4 *sc = sdhci_priv(host);
    struct s3c_sdhci_platdata *pdata = sc->pdata;
    int ret = -1;

    ret = sdhci_suspend_host(host);
    if(ret < 0)
        return ret;

    if(pdata && S3C_SDHCI_CD_EXTERNAL == pdata->cd_type && pdata->ext_cd_cleanup)
        pdata->ext_cd_cleanup(&sdhci_exynos4_notify_change);

    if(sc->ext_cd_irq)
        free_irq(sc->ext_cd_irq, sc);

    return 0;
}

static int
sdhci_exynos4_resume(struct device *dev)
{
    struct sdhci_host *host = dev_get_drvdata(dev);
    struct sdhci_exynos4 *sc = sdhci_priv(host);
    struct s3c_sdhci_platdata *pdata = sc->pdata;
    int ret = -1;

    ret = sdhci_resume_host(host);
    if(ret < 0)
        return ret;

    if(sc->ext_cd_irq && pdata && S3C_SDHCI_CD_GPIO == pdata->cd_type) {
        if(request_threaded_irq(sc->ext_cd_irq, NULL,
                                sdhci_exynos4_gpio_card_detect_thread,
                                IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
                                dev_name(dev), sc) == 0) {
            int status = gpio_get_value(sc->ext_cd_gpio);
            if(pdata->ext_cd_gpio_invert)
                status = !status;
            sdhci_exynos4_notify_change(sc->pdev, status);
        } else {
            dev_warn(dev, "cannot request irq for card detect\n");
            sc->ext_cd_irq = 0;
        }
    }

    return 0;
}
#endif

#ifdef CONFIG_PM_RUNTIME
static int
sdhci_exynos4_runtime_suspend(struct device *dev)
{
    struct sdhci_host *host = dev_get_drvdata(dev);

    return sdhci_runtime_suspend_host(host);
}

static int
sdhci_exynos4_runtime_resume(struct device *dev)
{
    struct sdhci_host *host = dev_get_drvdata(dev);

    return sdhci_runtime_resume_host(host);
}
#endif

#ifdef CONFIG_PM
static const struct dev_pm_ops sdhci_exynos4_pmops = {
    SET_SYSTEM_SLEEP_PM_OPS(sdhci_exynos4_suspend, sdhci_exynos4_resume)
    SET_RUNTIME_PM_OPS(sdhci_exynos4_runtime_suspend, sdhci_exynos4_runtime_resume, NULL)
};

#define SDHCI_EXYNOS4_PMOPS (&sdhci_exynos4_pmops)
#else
#define SDHCI_EXYNOS4_PMOPS NULL
#endif


#if defined (CONFIG_CPU_EXYNOS4210) || defined (CONFIG_SOC_EXYNOS4212)
static struct sdhci_exynos4_drv_data exynos4_sdhci_drv_data = {
    .sdhci_quirks = SDHCI_QUIRK_NONSTANDARD_CLOCK,
};

#define EXYNOS4_SDHCI_DRV_DATA ((kernel_ulong_t)&exynos4_sdhci_drv_data)
#else
#define EXYNOS4_SDHCI_DRV_DATA ((kernel_ulong_t)NULL)
#endif

static struct platform_device_id sdhci_exynos4_driver_ids[] = {
    {
        .name       = "s3c-sdhci",
        .driver_data= (kernel_ulong_t)NULL,
    }, {
        .name       = "exynos4-sdhci",
        .driver_data= EXYNOS4_SDHCI_DRV_DATA,
    },
    {}
};
MODULE_DEVICE_TABLE(paltform, sdhci_exynos4_driver_ids);

static struct platform_driver sdhci_exynos4_driver = {
    .probe      = sdhci_exynos4_probe,
    .remove     = __devexit_p(sdhci_exynos4_remove),
    .id_table   = sdhci_exynos4_driver_ids,
    .driver     = {
        .owner  = THIS_MODULE,
        .name   = "s3c_sdhci",
        .pm     = SDHCI_EXYNOS4_PMOPS,
    },
};

module_platform_driver(sdhci_exynos4_driver);

MODULE_LICENSE("GPL");

有了驱动程序,我们验证一下效果(基于官方文件的效果,我本地化的代码和这个效果类似):

首先在已经有驱动程序的前提下,当插入SD卡时会出现如下现象:

当拔下SD卡时,会出现如下现象:

上面的验证是有相关驱动的,如果没有相关驱动,那么就不会出现上面的现象,但是这种情况下仍然是可以正常启动和操作的,原因是因为uboot里有初始化这些器件,然后,在系统启动后,会把kernel全部加在到RAM中去,因为是Nandflash或者功能类似的SD卡,这些不能按位操作的,只能加载到能按位操作的RAM中去,而RAM驱动,是拥有的,所以,我们还是可以操作的.

猜你喜欢

转载自blog.csdn.net/qq_23922117/article/details/83345752