找回密码
 注册
关于网站域名变更的通知
查看: 256|回复: 1
打印 上一主题 下一主题

基于S3C2440的嵌入式Linux驱动——MMC/SD子系统解读(一)

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2020-5-28 13:42 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x

6 f3 X8 v% r$ s! s在阅读本文之前,请先掌握以下基本知识,不然请略过本文。
! Y0 w, |# I5 U% l2 \6 T5 q3 Q4 D8 g3 z9 A. [& @# C- t1 Q7 ^
预备知识:# m/ Q, K9 g1 d  J  C& {

9 B6 l3 f# \# J) l熟读LDD3前十章节的内容。
6 |: P) @& q: W0 n1 W, U8 {, ]0 g! Y8 Y$ J$ a1 t+ {5 `5 R) m
熟悉内核驱动模型(sysfs)和platform总线。5 z. a7 c4 a9 |  O. f; j. X9 V

) F4 w( ~* `7 G- H$ ~6 f5 S简要了解过SD卡规范。
! x- I! g. E- R8 J* G& f
7 {4 b( q" ?" d7 Y0 P+ \' ?# }  D* ?! Z3 y
  K8 D/ L2 p# A( Z/ p
本文的内容基于如下硬件和软件平台:
7 I( i1 P! G3 l1 S, E+ F" M+ g7 q9 O8 k; }3 _; x
目标平台:TQ2440! q; K. ]/ B5 j/ c! h

$ K% e, t% e% n; O+ R" q6 G% qCPU:s3c2440
5 O, S4 i& V+ y2 K
) M9 J2 u' Y, n) u+ W" L! V3 B7 N内核版本:3.12.5+ F8 W: U/ ?, [4 K% N( j( y

5 @  @" s0 C7 w- q9 n0 U基于SD规范4.10,即《SD Specifications Part 1 Physical Layer Simplified Specification Version 4.10》。; a' e3 }) E! b

6 B0 p. ^8 p- Q7 w+ o一、MMC子系统构架5 V8 w1 B2 q! p
待写。。。
% W) `& p9 u1 \4 p4 D9 K" x: K- s. @& K  n. t
二、主要数据结构8 V5 @6 ^$ X: }7 q
待写。。。! k7 ~- q) u4 t1 }( L; f2 b

6 \! X: m0 }9 U/ n三、MMC子系统初始化
% `. |) D$ N8 }% W$ @# H首先看看子系统是如何初始化的,完成哪些工作。$ g) z" h+ O8 s, A- ~3 r8 {2 ~
# H' }: Z9 D/ N$ c5 J
代码位于linux/drivers/mmc/core/core.c。
9 Z$ f0 {8 S3 k5 P! l( Q% ?; o6 m/ D2 u5 [: q: I- e

& y6 l* g; ], dstatic int __init mmc_init(void), w# C$ c/ x5 z8 j3 G' ^( z' y
{
' o! _% O$ a# x7 f, u3 y    int ret;: q. P) C& n' k9 @, Z1 ~; v

$ N/ A5 \  G1 Z6 F) F. w    /* 创建一个工作队列*/
; g  L- C3 Z0 X# s9 a    workqueue = alloc_ordered_workqueue("kmmcd", 0);# o. x* |3 x3 a* M: I
    if (!workqueue)
5 L4 Z/ t$ S$ L9 x# |        return -ENOMEM;9 v, i' _& Z6 L6 ?9 T7 }2 w

8 r! W5 J( O# W: ~0 d    /* 注册mmc总线,总线提供probe方法
( L/ N/ y) \: _; C& K: }( ^2 l       并直接在内部调用驱动probe方法*/
9 H9 `& h8 J0 X- H6 a* h' v    ret = mmc_register_bus();
+ _8 A* O" n7 r( d" r    if (ret)6 Q) K1 J6 |* E6 b* x7 B
        goto destroy_workqueue;
% p) b) F; E" I    9 ], S+ Q* Z, u  D
    /* 注册名为mmc_host的类*/
: u* l6 V( z8 {2 h    ret = mmc_register_host_class();
0 [6 @1 }! d5 w    if (ret): s2 e3 b) E! ]) |. b
        goto unregister_bus;6 v, o/ t; O5 Z; t2 e) q$ h7 b

# k% M$ B* z: g1 {- ?# c# g. B    /* 注册sdio总线,总线提供probe方法! f7 u9 v  l/ ~% P- S
       并直接在内部调用驱动probe方法*/  z7 c) f5 T  [, c
    ret = sdio_register_bus();, P; c4 t! k) o" l# @! \
    if (ret)
' z" W1 u0 Q8 k: o$ @6 x  @& u        goto unregister_host_class;& n7 I/ ]: P5 I6 n# n6 W: t; E

# d% W  v% X/ z3 e    return 0;7 j7 K; w( D6 g5 o# E; i

% p4 g8 D) h: i: ?) A% Y9 [unregister_host_class:; X4 k' [. i( _$ Z) [3 w) v5 X
    mmc_unregister_host_class();
: V& }! T. E$ j# e4 @  Runregister_bus:  n: X/ g% ]8 H8 L9 _
    mmc_unregister_bus();
/ g# y$ z! F% }. C6 `$ P" Ndestroy_workqueue:
) P9 h2 ~: s3 ^% Y. ~    destroy_workqueue(workqueue);
! A, ?/ e3 g9 f  |
5 G0 r7 i1 f# C* @- \  I" X% Q8 G6 o    return ret;4 g: d; n7 n$ ]+ A
}; z) h# p, l3 ~5 d9 J7 ^. `- h

  H/ d  G: l1 f+ X4 W% @代码首先注册了一个工作队列,这个工作队列将用于扫描sd卡设备。我们会在后面进行说明。. T, C* R2 C( w5 ~  N7 c. r
2 V* g% L/ ?; B2 ~6 I
工作对类已内核线程的形式运行,可以用ps命令看到名为[kmmcd]的内核线程。: a" f- t; n0 U8 w. l
# N! l7 L. h5 }  g9 X3 l* u
接着注册了两条名为mmc和sdio的总线,以及一个名为mmc_host的类。具体代码如下:
. o% u; |. o# e8 j; I$ d" S7 d( x7 ^% S
. N" Y4 _+ R7 K9 m2 q  K
static struct bus_type mmc_bus_type = {
+ k; {4 d# p% ^' H        .name                = "mmc",* x5 d) u- T6 |5 n9 b  q/ L
        .dev_attrs        = mmc_dev_attrs,& _" ]0 p4 S. \9 k" c% F! ?6 L
        .match                = mmc_bus_match,3 W2 P& E. ~2 p2 Q5 @# ?6 b% e
        .uevent                = mmc_bus_uevent,
& s# {" B$ v/ [3 B. [* A5 Z        .probe                = mmc_bus_probe,
9 l, j- K' u* X7 `, \        .remove                = mmc_bus_remove,
1 i. v9 C3 E0 V" Z        .shutdown        = mmc_bus_shutdown,# f- ^+ b" L: d6 R; s$ h
        .pm                = &mmc_bus_pm_ops," j. d& _9 C# t# s! z( \
};1 M5 P) P: g0 T* N! _" ?! l! z

# a: H7 c( l! zint mmc_register_bus(void)
! H. r, B& O! ~{) }. L4 }6 P$ ^1 o9 v+ D
        return bus_register(&mmc_bus_type);
* i8 w4 s9 H& l% _0 ^- l}
4 l7 M- J; X$ u9 y7 V6 Y( U! a4 r$ ]- i$ [
static struct class mmc_host_class = {
0 N5 z8 G. A: _* y$ o; {    .name        = "mmc_host",
; A7 S* d5 u, B- P# @* _    .dev_release    = mmc_host_classdev_release,
4 ?- z: j" M; p/ [* Y# r};
. X1 J' X: P4 }& ^8 p : H! y; z6 V9 }
int mmc_register_host_class(void)/ ]) R9 Q" A& Y0 l0 E: g+ n. o" q
{
3 p8 x/ w" o# K0 Y4 X7 d& @( M. X    return class_register(&mmc_host_class);+ B5 @' p% r: u
}# y5 Z  a  Z+ E% T8 j6 h0 y
, V7 N4 E$ k- z$ j* \/ v6 U
static struct bus_type sdio_bus_type = {
- H2 A, O4 O/ q/ \5 s    .name        = "sdio",: v' ]2 Q& t6 v$ {! S# i9 @
    .dev_attrs    = sdio_dev_attrs,- i" S2 {% a, ]/ o( Y+ K
    .match        = sdio_bus_match,
9 ^' Z% R/ ?0 y* f' M) ]    .uevent        = sdio_bus_uevent,* {% T2 Q& X0 P' j9 w+ u7 R
    .probe        = sdio_bus_probe,
& L; R  N. q' r    .remove        = sdio_bus_remove,7 U9 t# Y9 m0 {/ s! J) e1 u8 |
    .pm        = SDIO_PM_OPS_PTR,
$ v: I% Q9 B) p$ x2 L};
5 ^2 X, P0 Y2 D2 k) ^6 {! L 9 I1 g1 h% b: r4 w+ Q
int sdio_register_bus(void)  I/ O8 J* V. g. g) X( V. A# r
{
' O* n4 h# `$ P5 K- x' D) a    return bus_register(&sdio_bus_type);: v' R# k0 m4 ^0 h' c
}
' b- |. K2 O5 k  J, u! m ( k9 u  f7 ~( _9 G) I  E$ m
static struct class mmc_host_class = {* c3 ~0 q7 z! n" W* x0 i
    .name        = "mmc_host",6 T: m1 e& V6 ^& Z2 d8 p
    .dev_release    = mmc_host_classdev_release,
9 I- w0 Z1 A4 M# p# x; o- A% y};: V. e3 a8 f6 P! }/ Y& B0 E* s
5 z, F. C: M  F' u4 }
int mmc_register_host_class(void), P: S" f1 R! @
{
  u0 ^8 Q2 e2 K7 v% W3 F$ n    return class_register(&mmc_host_class);
2 n, p# \; N' J% F& f2 C3 `}9 u6 L9 i# e, X; V
# t( a/ q, @  t2 [3 K* O) P
熟悉Linux的设备驱动模型的同学对这些肯定非常熟悉。总线和类的注册只是调用了相应的接口,这些就不再赘述了。+ ^  @9 \/ j: i" C
5 }4 q* ^7 y" N2 r" Q. B
其次,sdio总线不是我们关心的。我们只关心mmc总线。首先来看看mmc总线的match方法:
/ R/ [2 g; [" k' ^9 x
) O6 U8 ^+ N; l6 R8 t+ _代码位于linux/drivers/mmc/core/bus.c。
/ y3 ~3 c6 a+ |" D4 {1 z! P9 _! ]6 J. O

+ ^# m3 c, t1 v2 I8 ^( T5 I/*# `$ y) m3 z+ M+ ~* x4 x! ^
* This currently matches any MMC driver to any MMC card - drivers5 L5 |7 m4 K5 a/ Z8 X4 I2 C! m
* themselves make the decision whether to drive this card in their# [! y$ \! u/ V# l' K( J& y
* probe method." \1 p  @6 d8 p
*/  F7 M1 Q/ k! y# H: p9 U* m
static int mmc_bus_match(struct device *dev, struct device_driver *drv)
$ L% q- B; F  P+ U6 S9 g{
% C: I9 s# s% F        return 1;
2 I0 I& [% h/ v+ f1 [}4 g; _3 s% a8 [5 [1 b3 R
match返回居然直接返回了1。这表示任意的驱动都能和mmc卡设备成功匹配。
  |% @; N2 a* W8 J9 o
( B. E3 f  P/ @9 f1 x从注释中我们也能看出,驱动的probe方法将会决定驱动是否能真正的匹配这个mmc卡设备。
, ~# K, X4 x, M- m* R( z: ^3 q9 g' A! z2 c  f+ f
熟悉设备驱动模型的可能知道,随着match返回1表示匹配成功后,将会调用总线提供的probe方法。接着我们来看下mmc总线的probe方法。
$ ~1 O: I3 ^" Z9 f7 S  k4 `- V& Q$ p! L( T1 T+ r
代码位于linux/drivers/mmc/core/bus.c。
7 O' R& U! G; H# U" q5 G
( h3 u' `( X* ]8 n/ Z  r7 ~# D
& u' x# c4 U" v" {8 Zstatic int mmc_bus_probe(struct device *dev)6 n! D; W  Q9 @' H9 \8 W& H9 @
{
7 q+ s$ Y! X2 D) a        struct mmc_driver *drv = to_mmc_driver(dev->driver);
6 {/ K! Y. K6 D4 K        struct mmc_card *card = mmc_dev_to_card(dev);, k0 Z: ~" C/ c+ a

) ~/ \1 ~6 y" d* Z+ e& e- u        return drv->probe(card);4 \/ O. Z1 X4 S9 m/ Y
}
6 a/ \4 r* V" f6 G从这里我们可以看到在mmc的probe方法中直接调用了驱动probe方法,这也验证了刚才注释中所说的话。
; P# j# ?8 O  K5 C3 i& W. N3 ?7 x从上面分析可以看出,子系统初始化代码仅仅注册了两条总线和一个类,并建立了一个工作队列。
5 S% k2 ?* E' K( l. z) Y) f0 D% c' Y% `

; S, y4 o4 y8 [4 b, B0 p$ T2 ~( |0 c) S  ^
四、核心层与控制器层间的接口API* M) l9 E; ?3 ^! Y
MMC核心层要和SD卡设备进行通信,为了完成这一个工作需要将CMD或者ACMD命令通过MMC/SD控制器发送给SD卡。
/ E% t2 l3 K2 ^  D( c0 c" P6 ^7 h. {4 a- \8 G
那么MMC核心层如何将通信的数据包交给MMC/SD控制器,并让后者去发送呢?
) A5 B8 y7 C7 o0 ^' Z- w& b) `+ Z! f- _) H: ?5 c
MMC通过函数mmc_wait_for_req完成这个工作,我们来看下这个函数。2 Q. m7 w+ P2 @9 w2 J/ @
+ R6 K/ U& R* |6 M
4.1 mmc_wait_for_req 函数, g# J( U  H, a1 _+ S6 h* G
下列代码位于linux/drivers/mmc/core/core.c。
: {# Z8 R0 W* g
  e, i+ u3 n* ?' f' o/**5 |* F8 ?4 y% ?+ ~( x
*        mmc_wait_for_req - start a request and wait for completion
4 ?; i/ C6 }) d7 y# P *        @host: MMC host to start command- r8 s" J/ A) Y) }' `
*        @mrq: MMC request to start. K+ m5 C0 r0 d$ K! y: G5 l
*
: m# a( o' V3 x3 [: p *        Start a new MMC custom command request for a host, and wait1 w5 @) D5 s7 j
*        for the command to complete. Does not attempt to parse the4 S7 N7 Y$ K/ A' m$ j7 Z' T
*        response.6 [+ Y  j. n' _
*/
- }0 ?* Q9 [2 K) M9 bvoid mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq)8 ^9 s; ?3 Y* p. y: h$ e
{
) V! U0 Y/ L* F        __mmc_start_req(host, mrq);
2 [4 D$ m- X2 [# {& F        mmc_wait_for_req_done(host, mrq);
. b/ h/ r+ n/ a8 e}
( d: q8 w2 A4 z& R/ J# EEXPORT_SYMBOL(mmc_wait_for_req);& U" q% P5 H1 u1 O  U. O

7 s7 V: t( G2 o2 l& E    通过注释可以发现,该函数会阻塞并等待request的完成。
' d9 b. k% s4 }! L1 a& K! m   该函数分两步走,第一步调用__mmc_start_req发送命令,第二部调用 mmc_wait_for_req_done等待命令完成。! [3 H% u. Q* F* t  f; T
3 L; r1 a( n% N0 ]+ A! U
   分别来看下这两个函数 :
) c4 n. `) t5 F& o
2 q0 K& W6 q- I$ G* {4 D
; [! h: k3 Q$ \8 {
; `9 J. v- I* E% s9 |static int __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq)* n2 m8 \2 N& W! t
{% D& t# K5 d6 E* F% U2 F( I
        /* 初始化completion,并设置done方法*/
1 q5 r6 z' p7 ~% f, |1 o        init_completion(&mrq->completion);8 V& h5 x. p9 t1 _5 o  H
        mrq->done = mmc_wait_done;8 O. j) k- p5 G) E# b
        /* 如果mmc已经被拔出,设置错误并返回错误*/: ~% Z; ~0 s$ Z; O. k6 v! n7 V
        if (mmc_card_removed(host->card)) {
/ ^' b% k3 S1 x2 c9 ?6 s                mrq->cmd->error = -ENOMEDIUM;' v7 _/ f) q1 e* U  Q
                complete(&mrq->completion);' O: k. Q/ q; U+ B* \% e' n
                return -ENOMEDIUM;- o! u/ u, }1 k
        }
7 f& b& w: t, \8 s9 K- Y/ k        /* 发送命令 */4 C8 X' W! C8 `5 ]; V
        mmc_start_request(host, mrq);  B4 N6 t) k. [9 k: g* B# y
        return 0;
4 t- `3 ]' @7 p}
; n) a3 x3 r4 [该函数首先初始化了completion并设置了mrq->done方法为mmc_wait_done函数,该函数如下。. [; x& e& k2 [* L: u0 z
9 X" s: L5 v6 S
static void mmc_wait_done(struct mmc_request *mrq)* u6 r# y* }; T8 C
{
  N/ t5 a3 n. [  Y3 X4 \# x    complete(&mrq->completion);
# H. q$ ~' M- _1 b- F7 P}
& z6 g. |8 u/ t+ _. t$ }这边使用completion的目的是为了等待request发送的完成。  x" B, z, C) K5 v
在第二步mmc_wait_for_req_done中会使用wait_for_completion函数等待mmc控制器完成request,控制器驱动在完成request的发送后,会调用mrq->done方法来激活处于等待中的wait_for_completion函数。# E! p( [! ?1 ~8 ?( L  |

$ C" i# q, `- a. T9 D随后函数会首先检查sd卡是否已被拔出,如果卡都被拔出了则没有必要发送request,可以直接调用copletion函数告之相关的等待函数,并设置error值然后返回错误。
7 ~/ a; g, }$ b4 H6 l9 Q
4 F2 s9 Z5 f$ s( o7 V/ S5 ]4 D7 M#define mmc_card_removed(c)        ((c) && ((c)->state & MMC_CARD_REMOVED))0 B+ G! y9 o/ n7 ?! @9 p3 B2 t% \' N
如果sd卡存在,则调用mmc_start_request函数发送request,该函数如下:; j" r4 B) D# R, P; R1 _

" |/ ~) F) x' ]4 |/ x; O! t1 M; c1 W  y& y0 B1 N% x
static void, X  g/ Z* |& u- _! S7 b
mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)+ g3 G" U* J) G, M8 I5 D
{1 p/ Q! P. b: n# S
#ifdef CONFIG_MMC_DEBUG7 y! B5 z8 D+ R- }
    unsigned int i, sz;& B/ s7 I$ K' }
    struct scatterlist *sg;+ J0 ?: W, h) X% I7 k  `
#endif
, k/ t5 ]; N9 X0 @9 C: u 0 u6 ^# i1 a" g
    if (mrq->sbc) {% V3 V1 @, F  c  V2 M) l8 ~
        pr_debug("<%s: starting CMD%u arg %08x flags %08x>\n",+ [! L3 _9 l: K/ v, q
             mmc_hostname(host), mrq->sbc->opcode,
5 V! l+ a! q: F             mrq->sbc->arg, mrq->sbc->flags);
" [! R( e' f" X0 P# r+ S2 _! V    }% w5 @7 m; P7 M/ ~: \& c" u$ ~
9 N1 `- ^- p0 L2 n
    pr_debug("%s: starting CMD%u arg %08x flags %08x\n",
! y( Q5 D& e  Q         mmc_hostname(host), mrq->cmd->opcode,
7 t0 u  j0 H: P7 H9 O9 C         mrq->cmd->arg, mrq->cmd->flags);/ B4 w: Z) q% j+ k# E

6 o7 I% `2 v% \; W6 J    if (mrq->data) {  _( U8 \* z* _9 T
        pr_debug("%s:     blksz %d blocks %d flags %08x "
$ O, Y/ W! T8 J+ O  j            "tsac %d ms nsac %d\n",  K2 e- V- ]' B4 n: @
            mmc_hostname(host), mrq->data->blksz,
7 b. `  U+ C) ~8 U+ Z            mrq->data->blocks, mrq->data->flags,
% ?/ C: q; z6 s0 a' I+ c            mrq->data->timeout_ns / 1000000,, I$ s+ |/ C) E
            mrq->data->timeout_clks);, f0 M5 p6 k7 ?- y  T8 t- J2 b
    }/ C& {' j8 ^/ e5 _: [# G

6 T+ U8 s/ t* W& H( d" u) Q5 H    if (mrq->stop) {
$ a& U# ^3 H2 g/ E& c1 U        pr_debug("%s:     CMD%u arg %08x flags %08x\n",( S+ q9 F( s7 m
             mmc_hostname(host), mrq->stop->opcode,. G/ W0 n- S% A" O& `
             mrq->stop->arg, mrq->stop->flags);
6 d; }) J% _& f! b$ E# p) Y    }
' K' Y; G+ O" |
5 A$ A  {( @- p8 m    WARN_ON(!host->claimed);
8 |: X: A* N- F/ f4 ~, h / D+ X1 D# B" Z1 Q, X
    mrq->cmd->error = 0;8 ?* [$ J$ B1 ^* T$ h
    mrq->cmd->mrq = mrq;
4 f4 c  p0 r* \4 V    if (mrq->data) {. b4 O; \) Z/ J& }
        BUG_ON(mrq->data->blksz > host->max_blk_size);; v8 e' G6 r+ l+ d' K" v3 j; T' I
        BUG_ON(mrq->data->blocks > host->max_blk_count);4 k' B8 Z: R5 n
        BUG_ON(mrq->data->blocks * mrq->data->blksz >6 s% c: p9 n9 ?/ \1 l/ |
            host->max_req_size);" Y8 l; L. {& `: O: n

% `1 x$ [: h1 B: a#ifdef CONFIG_MMC_DEBUG
4 s2 `7 z) N, J: ?2 \; g5 ~        sz = 0;
9 l( L! I1 G; S( X5 t( w        for_each_sg(mrq->data->sg, sg, mrq->data->sg_len, i)7 u" D8 i+ F& @7 D9 ]9 d6 ?
            sz += sg->length;! y- |8 o$ ^2 |7 p' L& C- S9 `. P7 S# ~
        BUG_ON(sz != mrq->data->blocks * mrq->data->blksz);! ^& t; I1 H. T
#endif/ p; [; N, S% g! l/ \4 B2 H9 _/ V

  l8 L) D: w  c0 z: A        mrq->cmd->data = mrq->data;
* k: O4 H8 I4 I. N0 w        mrq->data->error = 0;% i- T. R! X# G& ?% |
        mrq->data->mrq = mrq;
" \) h9 V+ G. i        if (mrq->stop) {
) `5 }3 ?8 g5 w0 {% a( T            mrq->data->stop = mrq->stop;- H( I& S8 N! ^- w) N( ]  l
            mrq->stop->error = 0;: Q3 l+ z3 m, ~, l
            mrq->stop->mrq = mrq;
0 }1 X' X+ ?; M& t7 c" O        }* _, x8 }4 \& z2 l3 r" W
    }, u; E4 B3 R, P  [" i9 t' g
    mmc_host_clk_hold(host);
; D2 E7 i! Y1 @- {! z9 Q/ ?    led_trigger_event(host->led, LED_FULL);
: ]& Z4 h" j( _) J, J1 K% `    /* 发送request*/; I3 f  x$ I: T3 H& z& O/ {
    host->ops->request(host, mrq);
1 U) V7 _2 |( b0 [: P7 n}9 |5 D* {! F! V5 _% m
该函数会打印一堆信息,然后清除cmd->error,并绑定cmd和mrq,接着如果mrq是请求数据
  ?2 ]4 m3 A$ ummc_host_clk_hold函数是通过宏CONFIG_MMC_CLKGATE来进行使能的,这个宏默认是不打开的,具体就不分析了,简要说下这个宏的作用。
( J2 A4 `; F3 |. [! z" o5 y. @9 s+ K
这个宏的作用是使能时钟门控功能,这个功能在不需要MMC控制器工作的时候,停止MMC控制器,以节省功耗。
/ t: B5 E8 T; l% t8 Z$ w2 N, X2 V6 i
随后会调用led_trigger_event触发led事件,这个牵涉到Led子系统,就不进行说明了。
. ?1 A" C% R% r- B- T3 \3 M% I4 ]4 F+ X5 M7 j& A4 K  M
顺便提一句,s3c2440的mmc控制器驱动并没有使用这个led触发功能,也就是说host->led是为空的。8 b8 O+ _. ]8 h. Z* R7 q
; b, W3 t& X7 u
最后调用了mmc控制器驱动提供的request方法发送request。
  w+ j1 H+ M2 j# ?5 i5 D) b9 t, o
. t  _! ?9 T, B9 b- T6 r% ^这里需要注意下函数指针的形参:一个为host表示mmc控制器,一个为mrq表示request(请求)。
$ R; V4 l! g2 w0 C5 N5 N0 h
! Q. s. U! @1 J! l1 D很显然,要求host指向的mmc控制器发送mrq指向的请求,同时,也可以看出所有传递到mmc控制器驱动的请求都是使用struct mmc_request结构体进行封装的。$ Y3 Q) L! r; F! {) |) |( z# S! m

/ V: I5 C6 n# ~1 ]: q8 _至此,第一步完成,接着我们来看第二步:
/ T' ?9 @' W0 `. h5 N% n$ N/ w% M4 }6 ?' B( P+ p

' k& E" {/ W9 Z  ustatic void mmc_wait_for_req_done(struct mmc_host *host,# H; K0 A0 m. d: D& D; j2 P
                                  struct mmc_request *mrq)9 L% g, E5 }* h) _
{- L. O4 F) y* y8 O3 [
        struct mmc_command *cmd;
$ X+ j. q: R& Z% K- F: G) K  G/ y6 U 6 r8 o9 Q/ L& \6 @8 p) z) F
        while (1) {
, \6 H+ t" p4 s1 U* Z  q% s7 v                wait_for_completion(&mrq->completion);
- R6 }+ s3 f# T) ?, X) |/ y( S8 w % l" _" g. G0 W/ X7 _! b
                cmd = mrq->cmd;
, J4 N& |2 `( ?1 ~  t" b5 R, Q& U6 `
0 g! Y2 U9 }  R- U2 ^" ^: F% ?5 v                /*7 D2 f+ n( b! o7 C
                 * If host has timed out waiting for the sanitize
% r- ]. C+ O5 n1 P                 * to complete, card might be still in programming state( w1 @& i) K6 P, e. n4 {
                 * so let's try to bring the card out of programming2 l/ _! s: P; J; t2 W$ t+ \( R
                 * state." }' z0 s, I3 O$ h: G
                 */
3 c' K+ o7 l4 l# g4 t. C0 [) K* a, R                if (cmd->sanitize_busy && cmd->error == -ETIMEDOUT) {6 y) k; |5 O7 Y( {# x
                        if (!mmc_interrupt_hpi(host->card)) {
8 {1 @; Z- e' |( h+ j8 E4 F8 }1 V                                pr_warning("%s: %s: Interrupted sanitize\n",9 e- J" R& X- q* {
                                           mmc_hostname(host), __func__);
' W- z6 Y6 j5 T" d0 |! \* Y5 `                                cmd->error = 0;
/ C: ]2 Z3 A' _3 F                                break;& P$ y) L4 M4 O; i- M5 x9 F0 ~
                        } else {
. I3 ]7 F3 j, S3 z* @$ p% u                                pr_err("%s: %s: Failed to interrupt sanitize\n",
* o6 @4 s7 C: G; W3 |                                       mmc_hostname(host), __func__);
4 Q/ X8 Z0 v3 z$ X% `* [& ^                        }
: x: j5 @2 |! h                }
  R  A& F+ K) Z                if (!cmd->error || !cmd->retries ||/ K& O! C% D3 s6 q% X) M
                    mmc_card_removed(host->card))
, m4 w; v8 c( ]9 l                        break;
. G) Q2 ?. H/ ^7 l) y8 }
2 L8 d- z7 O: g" }. e                pr_debug("%s: req failed (CMD%u): %d, retrying...\n",* _* l8 x5 U* V6 A1 ~3 W( s
                         mmc_hostname(host), cmd->opcode, cmd->error);4 `! W, Y& \! L
                cmd->retries--;
. z3 l) k2 Q: h# e% E2 ]* s                cmd->error = 0;
. Z; [8 h+ y6 w6 n& P% n) ]9 f7 Y                /* 没有成功,尝试再次发送request*/- P/ E0 D! I" a! E
                host->ops->request(host, mrq);5 q0 n0 \2 L& E4 e# t! Q
        }4 ]/ J) r  L( Q$ A7 g% o9 A5 a
}
- d+ J. g- R4 b' G: J这个函数首先调用了wait_for_completion来等待mmc控制器驱动调用mmc_wait_done来唤醒自己。
) a" c) f. O  V) B5 `# R被唤醒后会执行一系列检查,如果request成功发送,则会break,并直接返回。
1 d% z- d% L* a
8 w' x. p8 @5 @% ?8 Z如果没有发送成功,只要retries非0,则会尝试再次调用mmc控制器驱动的request方法再次发送。. m& ?; F+ \' D! m: \$ o/ Q2 C
( K7 @* J! s+ G, c1 }- b, |* A
4.2 CMD和ACMD发送函数9 a( O& k( x/ t1 s3 e
通过4.1小结,我们知道MMC核心层如何将request交给MMC控制器驱动,并由后者发送该request给sd卡。
6 n& U# z! a# E0 Z/ P% s6 S  P5 u) w: B" P9 J, g
通过SD卡规范,我们知道有两种形式的命令,一种为CMD,而另一种为ACMD。
3 n9 W5 r0 U1 g, C  b( X* z2 Y$ Y  w3 }1 Z1 |
MMC子系统提供了两个函数来完成这两命令的发送,分别是mmc_wait_for_cmd和mmc_wait_for_app_cmd。, U) ^1 ~9 _- \/ S; Y! W7 z
3 ^) J+ C2 N4 {
先来看下CMD的发送函数:
% s1 r/ x" I+ t* x  ~& T. [% }( O( R* @
下列代码位于linux/drivers/mmc/core/core.c。; X8 s; F, O% [" S3 y2 N0 j
" v9 ?* v1 m! u, \' u
/**% F  N" i; [- w3 {4 {( W
*        mmc_wait_for_cmd - start a command and wait for completion& d5 _# n( W# T2 W2 u/ ^
*        @host: MMC host to start command# I3 p+ B7 h. ~- f( W
*        @cmd: MMC command to start. Z! j* S) ]" d- `' _* I
*        @retries: maximum number of retries7 u( J3 y% X4 R% ^9 Q
*
$ o; R: }: n! V. _9 L( z *        Start a new MMC command for a host, and wait for the command; H/ {  l7 N  n- P: v- Z8 M
*        to complete.  Return any error that occurred while the command" i! E& J2 v2 N. ?- R" M
*        was executing.  Do not attempt to parse the response.: t' I! n5 t  e6 Y+ O
*/
8 e4 j8 u6 Q7 W: r- aint mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries)# F3 _+ A; b! ?: N0 i
{
0 z' o: V  ~/ O  J        struct mmc_request mrq = {NULL};
0 v0 |; g, @+ |) D' U, s 5 t' e4 q7 D1 M3 R9 Q( k
        WARN_ON(!host->claimed);1 T, p6 ~' @+ `7 h! J2 j
- A! m$ r4 ?- k2 ~) @
        /* 清空应答 */5 b& I( M8 s) _
        memset(cmd->resp, 0, sizeof(cmd->resp));- L0 Y$ f. C' F/ D8 P% I
        cmd->retries = retries;4 x  R; ]- A: \. v: k; N1 S; k% C9 }

+ P: {/ I) y) ^7 ?" A  x$ O/ V6 O2 |. e        /* 保存命令*/
) d3 P+ j7 A: e, L; F        mrq.cmd = cmd;4 o  r- k% h6 S& H$ N
        cmd->data = NULL;+ l$ l( {; m3 a( q- _2 L
: F" N$ ]3 I# T! \1 H
        /* 发送命令并等待response */
+ R% P0 _' Y# O/ I( d0 t6 I( ]0 G        mmc_wait_for_req(host, &mrq);9 C0 Y4 A+ W& G& H
% i& X$ M, @/ D1 r4 |( o
        return cmd->error;
! a6 @* f; P6 S4 X3 ~" t1 I}9 e0 }& C1 h' M# D
有了4.1小结的分析,这个函数还是比较简单的。
5 k6 Q# W5 w1 A( k$ m) c  o& F该函数首先清空命令的应答数据(resp),并保存命令(cmd)到mrq中,随后调用4.1小节中的mmc_wait_for_req函数发送CMD。
+ O: C1 F# e* i9 O
- h5 ]0 L4 Y; m. t% n* F* j从这个函数的形参我们可以看出:所有需要发送的CMD都由mmc_command进行封装,在函数内部被mmc_request ' C  q% T3 J* O' u; M
结构体进行再次封装,并将mmc_request交给MMC控制器驱动完成CMD的发送。8 S3 s9 C/ h2 P  B& h5 |. v

  F. C- t, Y; Y  o  O* r. Q: Y, B3 i接着看下ACMD命令的发送函数mmc_wait_for_app_cmd:
) V# c- `' G! _6 {/ x/ @' x; o
下列代码位于Linux/drivers/mmc/core/sd_ops.h。
8 b" a+ @" _- n: n' p# Y
; b; i5 a1 c4 M$ G& o" Y9 u/**
+ R! I( e5 |( j1 ] *        mmc_wait_for_app_cmd - start an application command and wait for
: q9 |1 _8 K$ e% Y                                completion
7 f) t! o4 c, z2 B5 Q) l1 f  O2 }8 O *        @host: MMC host to start command
* a, Y' u, M1 V' Z* H *        @card: Card to send MMC_APP_CMD to
5 q( Z7 A2 c1 N2 H *        @cmd: MMC command to start
5 d# @8 r9 _# @, q *        @retries: maximum number of retries
5 f* @9 X# w5 m$ H; P% ?" z5 d *. |, K; m; V1 U! d/ N
*        Sends a MMC_APP_CMD, checks the card response, sends the command% ~# \! L7 L5 x5 P+ |6 Y
*        in the parameter and waits for it to complete. Return any error0 a4 \6 K1 _7 h& _! J
*        that occurred while the command was executing.  Do not attempt to. ?/ F' K4 y% C5 j0 n' _
*        parse the response.1 x; c% o' R) h6 r
*/
. e1 h9 P/ `% A$ Y1 o# rint mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card,5 N7 `* y" L2 N, h, ]
        struct mmc_command *cmd, int retries)6 Q6 J4 ~$ R1 N* t+ u2 d
{0 [/ a# z4 W" p- A5 b$ \" Y
        struct mmc_request mrq = {NULL};! Q1 C2 }! X* i& v' p

% m1 o. o2 m; K- y/ @        int i, err;
+ |0 V! E0 \0 q5 f+ C- H( C& m
8 L. ~3 o/ d! Z3 Q        BUG_ON(!cmd);6 h. Y4 `1 j4 {
        BUG_ON(retries < 0);
/ s: M& T3 @( ^; F
6 |. e) N6 a7 w: `        err = -EIO;
' G1 k$ a' p* ?+ Z* B
6 G6 e1 o6 h: F: I1 d9 ^. y        /*) T" `6 F: e: r( K- g9 X) k
         * We have to resend MMC_APP_CMD for each attempt so7 X% N' _  k* C9 D& e
         * we cannot use the retries field in mmc_command.
$ z- h: V& W  P" X9 X         */
  ^. @& Q; c  j        for (i = 0;i <= retries;i++) {
1 }* _$ Z0 X3 o* |' w& q% g4 a                /* 发送CMD55*/
& I9 ?* I. U2 A# u6 a5 h0 Y6 o: g/ {                err = mmc_app_cmd(host, card);
+ M0 t8 _) [$ T1 [- q( m                if (err) {
. z) D4 [9 E8 y, i' I, ^                        /* no point in retrying; no APP commands allowed */  p0 D0 a# r4 T$ r' f9 m
                        if (mmc_host_is_spi(host)) {8 o. F" S; H/ p* J
                                if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)
3 u' `3 o8 U( ]; o/ N3 [/ n                                        break;5 ?; |  z& o& j, L/ w
                        }
( r9 q: W0 }2 v3 B) R( W$ N* N                        continue;
0 d6 I9 q% }6 g, ]                }
' \7 i! _2 X# @: i
0 z# Q) d0 W9 r' R$ O7 o8 I* o                memset(&mrq, 0, sizeof(struct mmc_request));: K* \1 }$ F; L3 E, X+ w

; f* o; j2 L+ W# v6 C) k8 a2 S: f                memset(cmd->resp, 0, sizeof(cmd->resp));
) w% F( h  W% |2 ~2 L- ?" A                cmd->retries = 0;
, u2 d, N/ U  l+ ^" M6 o1 a 8 v* ?) g' a: Z7 K6 q! L& _
                mrq.cmd = cmd;; y# N* t, u: `
                cmd->data = NULL;. X, ?9 p4 ~; f3 _' E
) h* a$ V! s  T) h
                /* 发送ACMDx*/2 |; }( B% x, F, ]! b
                mmc_wait_for_req(host, &mrq);2 H: B4 }$ S4 R! b1 w8 m( B

4 F6 x: o  y+ r                err = cmd->error;
. R& n. n& T  l                /* 发送成功,直接break并返回*/
1 W: o0 E+ _2 @3 q2 V2 E4 d                if (!cmd->error)7 q) q/ ^" M7 Y+ B# I4 c, y+ I& H0 ]
                        break;! r( B0 o! T9 u, i
- A0 K' K! Y& h' ~5 X5 }  L
                /* no point in retrying illegal APP commands */
3 `) m0 g5 C0 \% i, w5 b% g                if (mmc_host_is_spi(host)) {* A' c+ A" j9 C2 V, Q
                        if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)
- y! W! U( |2 l+ b" J4 Q# i                                break;+ W  I' q# R) f; ?2 Y% X, }
                }- ~( h2 i' F2 E, E3 Y1 s0 N
        }( ^% e' D5 m* v: ?

! `: O% e4 a# K4 h8 R3 {, o% j        return err;" J1 i8 H; ?6 X9 ~( q
}
  [$ |, K3 v7 ?2 m5 I7 \+ o
8 _6 V' z* v1 G5 E6 E0 _$ uEXPORT_SYMBOL(mmc_wait_for_app_cmd);1 n6 U' p6 T+ }' t
该函数的形参cmd保存了代发送的ACMD命令。1 e& g, {* h3 f* T( G
根据SD卡规范的要求:在发送ACMD命令只前,需要发送CMD55,以表示后面一个命令为AMD命令。1 s: h1 I3 \5 C, }* e

; b  X5 j& o' f  v* H) \所以,该函数首先调用mmc_app_cmd函数来发送CMD55命令,我们来看下这个函数:0 r3 T9 E6 [% M2 E& b( v3 Y
' ]9 \, l/ C: Q2 ~- K# @
int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card)
. B& p+ r8 \5 k. c7 W- k6 k5 a  N{& s5 y6 J8 S; D0 A# g4 u
        int err;
7 m9 l* ?( n; f; V5 Q  D5 b  w        struct mmc_command cmd = {0};
& J3 \7 g, [! g" ?0 Z+ k) U- Y ' |& r1 w; s% p" n5 }
        BUG_ON(!host);
7 M" s1 U" A( `        BUG_ON(card && (card->host != host));  z; Y9 u" P8 m: Z- |4 J; f) i
  F  Q; j& r7 B  a! ?
        cmd.opcode = MMC_APP_CMD;        /* CMD55 */
. \0 `4 N8 t. [" S ! r5 {- G% F3 c- _9 N1 ^
        if (card) {
% f/ U* ?1 j+ e6 }/ g                cmd.arg = card->rca << 16;        /* 卡地址*/
" |6 Y! i5 \; N: R4 ~                cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC;. e  p  n% Z+ T, H2 y- m- L
        } else {( R  h) S) M& [% o
                cmd.arg = 0;        /* 卡地址*/
4 M7 s( x. I2 J& [, W. J' Z                cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_BCR;
9 y, Y% t* g3 ~* x- @4 Q# c. W        }& r0 K% I8 J0 O. {" D3 }+ L
* g9 m  f9 `5 ^( b/ a6 L# u
        /* 发送cmd并等待(阻塞方式)*/
2 B  E5 u9 f: |1 A: j7 |* S        err = mmc_wait_for_cmd(host, &cmd, 0);
, t6 `3 f( |/ E% v4 G( k' E$ z0 a        if (err)
3 @3 Z7 \3 S) p3 k5 R& ?                return err;' W& h# p; F+ o8 e/ [% E
; C8 ^& }% n- M  ]/ l* W- |& x
        /* Check that card supported application commands */
; E8 i% |8 \, v9 c* B        /* 检查card status第5位,判断SD卡是否支持ACMD*/2 _; W$ N- }$ Q0 J; E) a
        if (!mmc_host_is_spi(host) && !(cmd.resp[0] & R1_APP_CMD))
! G2 a( h; h) O) e5 l                return -EOPNOTSUPP;6 e6 r& {, k) b" h2 K3 q! n1 f" l

( a2 G4 ^8 c5 a; Y' Y6 C" R        return 0;
0 u( U9 p! u! H}* K, Q. b0 V6 H5 q
EXPORT_SYMBOL_GPL(mmc_app_cmd);8 B/ ]% O" b& I6 a" C4 y3 X
先来看下SD规范中关于CMD55的说明:) t0 J, B2 r: o& [( _  I' ?
7 j+ n' Z+ p- c& R
5 m, l- m- \/ ]1 ^9 {9 Q
从上述命令说明中,我们可以看出:1 Y( c4 Q( C0 d" n  \- S' c

, d/ {2 ]: \  c/ j6 l* o6 m1)该命令为ac类型命令,也就是点对点命令,并且在DAT信号线上没有数据传输。& _# y* r' u" r# F1 e

/ o+ F2 b0 @6 Y. Z2)其次,该命令的参数(31位至16位)为RCA,也就是卡的地址。# @; l* {& a. G3 l* [" J9 {% A# c
3)最后,命令的应答数据格式为R1。
: |' f# E  A# Y2 U3 a$ q2 y
  m4 |4 J) l% Y. T! M回到函数中。! n3 D; L6 n7 R
( d/ F. w) z. I
cmd.arg为发送命令的参数,函数首先设置了命令的参数为sd卡地址(RCA),这符合上面的描述。; y/ Z( t- W* h$ X4 Y" c

" S' Y) ]+ p$ Z, H6 D! G) @- B随后调用了之前分析的mmc_wait_for_cmd函数发送CMD55命令。
2 H: \: [1 ?% P8 B6 p! n9 K
- }3 ]+ y( D% f6 g6 h3 x上面提到CMD55命令的响应为R1,其格式如下:
+ l& d# {% J0 V7 C' b8 w$ G1 P4 X& N) n) ]# a# g

- M) ?4 k) S% `' i* [
5 F1 H% t4 m* _/ @4 T其中32bit的card status作为响应数据被保存在resp数组中。
" s" i- R9 D) i6 A4 a# r1 C* A8 E/ W3 A0 W+ k
card status的具体位定义请查看SD规范的4.10.1小结。
; O  W- v. @" k: d! x  T  K# s! M" H! z' A2 v7 W
最后检查CMD55的响应来判断SD卡是否支持ACMD命令。# T* ?# g8 A& a- }4 A' i: Q
2 }2 ]8 b" ~& u: k
CMD55发送成功后,返回到mmc_wait_for_app_cmd函数中。
" i! g( R" Y5 J4 _9 }0 ?6 f4 z1 |3 L+ s- F
接着,cmd被保存到mrq.cmd 中,并调用mmc_wait_for_req中发送ACMD命令。
" u1 c7 P8 X: O3 Z/ J7 E$ e6 t0 J  L

, z) V! K( R1 L1 M$ C3 y五、小结+ x4 z$ h) q# v  M
    本问主要对MMC子系统架构进行了简单的介绍,并给出了一些关键数据结构。同时,对MMC子系统的初始化过程进行了简单分析,最后,重点介绍了CMD和ACMD命令的发送函数。7 @. f9 A! n9 _; ?

) U4 t# M- R. h* t  `& `1 W2 M7 D5 N$ d, l
& N" C0 ]  `' E! R) z$ t' w7 \: O
9 C+ X2 ]4 y& f+ D8 c6 v3 u

% T( B' t" V9 |# K; a+ P/ L- K  p
4 G# P& L0 K$ C' a( d% b% T

该用户从未签到

2#
发表于 2020-5-28 14:26 | 只看该作者
基于S3C2440的嵌入式Linux驱动——MMC/SD子系统解读(一)
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

推荐内容上一条 /1 下一条

EDA365公众号

关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

GMT+8, 2025-11-25 23:07 , Processed in 0.203125 second(s), 26 queries , Gzip On.

深圳市墨知创新科技有限公司

地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

快速回复 返回顶部 返回列表