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

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

[复制链接]

该用户从未签到

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

EDA365欢迎您登录!

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

x
+ C2 L! q" s% \0 q% a4 s2 I/ @6 B
在阅读本文之前,请先掌握以下基本知识,不然请略过本文。
! K* t; h3 q1 ?& w5 _7 b
% h" e0 [$ c0 |* j5 O预备知识:! n7 h+ L/ B" E0 h& l. ]/ r3 m( `. F

1 J1 V, K) p7 J; J% J% M熟读LDD3前十章节的内容。8 e3 e3 n+ t8 Q) [8 Z* z" k$ A9 g/ r

  d( t) A2 @. f/ K. X. y熟悉内核驱动模型(sysfs)和platform总线。
8 w& l& x& M. ?) j' v9 Y' t& I- }  i# o
简要了解过SD卡规范。
9 p2 e2 w, U6 m3 N6 M+ c/ S2 Y; N5 d& z1 E
/ e  Z4 `! Y/ v& p/ f- [* i

6 z  K2 V: [" n, u" m+ @9 G( A本文的内容基于如下硬件和软件平台:
' `1 W1 }% e3 K8 c% f: B$ Y, V
9 l3 t# l1 G. K0 I8 l8 V% ^目标平台:TQ2440
& C* @" J4 l: N" T6 v( q  |5 c" }: L0 w* l1 q
CPU:s3c24400 C$ ~6 ~* j) w& i5 [
3 A. ]& g5 B0 T1 r+ a
内核版本:3.12.5
9 ?1 b$ K' O) K
6 y+ l! x5 j" c, ?5 _. a2 E$ F& y8 C6 J基于SD规范4.10,即《SD Specifications Part 1 Physical Layer Simplified Specification Version 4.10》。
4 t) A$ E: ]6 I& H0 e% x6 G2 ?5 Q+ q; D* D; t, u
一、MMC子系统构架7 Z# G8 A' ~1 q& `! n' b0 B6 S
待写。。。
6 V& B; I. |2 p7 k% V
9 V" `. A# v1 K/ c二、主要数据结构
$ H2 [% c; z7 g待写。。。
# _% a* D% ?6 Q* ]% ^% @& {) u, W( r5 Y8 Y+ u0 o6 k
三、MMC子系统初始化3 y6 f) B8 y' \5 T! z
首先看看子系统是如何初始化的,完成哪些工作。+ D. b% U6 ~% ~! v2 j. S

' U- ^# ?. P& Z, b1 y- [% W3 E代码位于linux/drivers/mmc/core/core.c。+ a$ y5 h1 B9 O6 b; C7 Y5 |
3 A" m3 ?: [/ [% @# q
) u5 L: b) j" N
static int __init mmc_init(void)
' F* C4 m7 A. W& y1 p6 D" W{4 [  m( x  r7 E5 X5 n; K
    int ret;
. k) p. k/ m& ]! @; @( J
, D9 N9 t; i) T& t+ Z7 f- u; T    /* 创建一个工作队列*/
7 ?! v. I' p7 T4 l; J/ v' r    workqueue = alloc_ordered_workqueue("kmmcd", 0);
; R6 `) ^+ I# k' @' o5 Z    if (!workqueue)
) Y. h! W1 p0 p/ G0 o0 K  Z        return -ENOMEM;" o- i! C; y  ?1 j8 P
9 s; x% X: p3 @3 p5 _# U, W
    /* 注册mmc总线,总线提供probe方法; C& @# S" N8 w1 N1 |& M
       并直接在内部调用驱动probe方法*/$ \( s( c2 v8 E: r
    ret = mmc_register_bus();2 g: R' f2 }( Z/ u9 q4 g
    if (ret)
5 E2 s0 U( t  R        goto destroy_workqueue;
: U5 Q2 O( g/ G& L    ; ]+ [& @9 F& r
    /* 注册名为mmc_host的类*/
. Z. [. g: J- s+ p& U    ret = mmc_register_host_class();
4 N: p/ `8 ?; k    if (ret)
; d9 X, y& l  Q0 b        goto unregister_bus;  Z2 _" e5 T+ K4 T( o

5 O3 U8 i& v2 l4 Z9 S( x1 B) [    /* 注册sdio总线,总线提供probe方法
! @  T0 t8 I- p0 P       并直接在内部调用驱动probe方法*/, V( h! f% M; c$ y" k: r, u$ Z0 a
    ret = sdio_register_bus();
( l1 }5 @; a" h: q    if (ret)( o/ `' A: x0 a7 W7 z1 T
        goto unregister_host_class;
' t# e; a! b+ ~0 R& A$ x
4 q' G) }$ z1 v7 e4 }    return 0;. _3 Y* }: `; P; D/ c# J

# R+ a) G; r6 o& ]* Y$ h1 V* hunregister_host_class:4 F9 W! n. S, \8 y
    mmc_unregister_host_class();  Z' v+ u3 M4 l2 z8 d4 b% Z
unregister_bus:* ~* U& V: U" x, c0 |
    mmc_unregister_bus();
9 k3 C6 b+ W# |destroy_workqueue:% i  W# \* n9 g
    destroy_workqueue(workqueue);
: u' n2 H4 ~- R* Z# _/ h/ g2 X   p$ k1 Q2 u, R8 b% ^! v
    return ret;# S5 W7 t# `9 I
}! _- r& z, d2 Y

/ }6 v# O" {( }1 u7 ~代码首先注册了一个工作队列,这个工作队列将用于扫描sd卡设备。我们会在后面进行说明。
1 \+ Y# m0 X4 x  P
; r& ^" \  X4 W( v% w工作对类已内核线程的形式运行,可以用ps命令看到名为[kmmcd]的内核线程。' c; i1 d0 A  H6 ]9 n
! n2 `  t: S$ W4 `$ D, a5 u4 F
接着注册了两条名为mmc和sdio的总线,以及一个名为mmc_host的类。具体代码如下:
5 [5 ?% H/ P. Q, ~& ?
* B) L1 O9 e' L- j# T0 v: E2 u: b. l/ D6 c- o5 H/ }' h+ [
static struct bus_type mmc_bus_type = {
4 M1 }- @% U. g" R        .name                = "mmc",
5 v7 X  Q& C$ n( v        .dev_attrs        = mmc_dev_attrs,
) x& c  w3 O: d% @' @        .match                = mmc_bus_match,, S5 f$ V: H) e* Z* j3 \
        .uevent                = mmc_bus_uevent,& e9 @; p9 _# J
        .probe                = mmc_bus_probe,  d- ~6 \  g: R0 T) C5 M7 N
        .remove                = mmc_bus_remove,3 X9 V! ?2 ~& H' V! u4 t  B2 O. ?  ]
        .shutdown        = mmc_bus_shutdown,0 l: G* r+ u+ I$ |
        .pm                = &mmc_bus_pm_ops,! y/ B  ~) l0 j
};! F: N7 m+ V# ]$ X2 X  d
4 a" ~/ J- r, j' x  _
int mmc_register_bus(void)
5 q5 l5 @, x/ P$ k7 o! s{
8 W1 x  B' C2 t! C- {, W        return bus_register(&mmc_bus_type);
8 H: B( N$ M! V( l& `}
  {; a) I; N& D4 W9 X# R! S0 v# n
static struct class mmc_host_class = {
+ x8 P1 z% B- {" {1 J3 D: T* a    .name        = "mmc_host",9 _5 _( w6 N: m3 |. [0 {/ I9 \
    .dev_release    = mmc_host_classdev_release,/ r0 V# r; \- k
};
6 Y( r, [7 f! w: `4 _ # W5 ^7 i: ^& ^, R* n5 w2 M
int mmc_register_host_class(void)$ P% p" \4 z+ f! v# Q  M
{
2 w8 [) c# d3 [3 f5 k    return class_register(&mmc_host_class);
6 m' A/ I7 |0 ~}
3 i2 ^( m# D1 J8 e7 e2 g: b* J8 f6 V3 x' p4 `
static struct bus_type sdio_bus_type = {
4 h8 |/ C" `6 b    .name        = "sdio",
9 V+ ^. f& X% @. {    .dev_attrs    = sdio_dev_attrs,+ _: J9 o, l) C1 ~9 V$ X7 x
    .match        = sdio_bus_match,4 N7 k/ @* Q- B; i) O
    .uevent        = sdio_bus_uevent,9 ]+ J) J7 U4 `0 |; B6 b$ ], d) Q
    .probe        = sdio_bus_probe,
( q$ Y9 r& i# S2 h1 T2 X1 s    .remove        = sdio_bus_remove,+ B4 g3 }* y* W7 j
    .pm        = SDIO_PM_OPS_PTR,2 ?* f# t: C" _7 s' y/ ?% H% Z
};: o  J3 }; ~! P

  P! h# q) M* a9 z8 H# R) Bint sdio_register_bus(void)0 [' @- O& P( r3 O$ _
{
" z/ L) g; N1 O    return bus_register(&sdio_bus_type);! p  {: H! g$ O7 c; c* b9 U1 O) G
}
, P  c3 N" C% c8 m/ \$ q' A * Y6 W: f- [- x& E
static struct class mmc_host_class = {/ z# A( ?* `6 ~0 Z2 ]% m  l( D9 z1 ?
    .name        = "mmc_host",
+ A0 |0 I% _) s; v! d    .dev_release    = mmc_host_classdev_release,7 _2 y( w. D2 c2 Z4 o$ F/ h- L$ m/ j
};
# v; X% G7 O1 T" A% l+ V  D " g# X$ b+ m% I+ C
int mmc_register_host_class(void)8 f+ e6 y: D/ x, `
{- H; ]& I( q* u
    return class_register(&mmc_host_class);
/ ]9 S* H+ X- b4 o3 X}. g: a9 W3 Q( i$ i" N! r3 w

, J* O! [# X7 o) O5 z% ~熟悉Linux的设备驱动模型的同学对这些肯定非常熟悉。总线和类的注册只是调用了相应的接口,这些就不再赘述了。
  u: K& O1 e& K4 J* Y+ [
% _* Q  A- {* S& N' U( Y% J# I其次,sdio总线不是我们关心的。我们只关心mmc总线。首先来看看mmc总线的match方法:
0 h* g: _% L. {* p9 g3 A
% h$ `2 V% i4 k; q! I代码位于linux/drivers/mmc/core/bus.c。
% v. [0 {' E" H2 P' x* B0 N0 f: L+ f: k

- Z) i* h4 c: M6 S; F/*2 c& _5 g6 Q, q. A( b# d6 {
* This currently matches any MMC driver to any MMC card - drivers
& @7 N% u; K# B6 I  p  E * themselves make the decision whether to drive this card in their$ e6 ~5 Z6 z7 |1 ]  w3 l! j
* probe method.
5 C7 m9 O1 E8 W */7 k" [1 R% l- o* W* X% x7 H
static int mmc_bus_match(struct device *dev, struct device_driver *drv)% B: Z+ b# ?! N" k. {' q+ v1 c
{
; q/ q. b9 S, o9 V' X7 f- s        return 1;( I0 w4 c4 n7 U5 B9 l" e$ l
}
4 B, v1 k  v4 Y' W/ cmatch返回居然直接返回了1。这表示任意的驱动都能和mmc卡设备成功匹配。. _7 @$ v' z1 O5 o6 }  w$ s
9 n1 }/ a6 q: s0 \
从注释中我们也能看出,驱动的probe方法将会决定驱动是否能真正的匹配这个mmc卡设备。0 z4 S# G, e9 C
* l9 _; _) M4 Y1 W7 F
熟悉设备驱动模型的可能知道,随着match返回1表示匹配成功后,将会调用总线提供的probe方法。接着我们来看下mmc总线的probe方法。
3 R, R6 j; }, a9 `/ x/ c: b
9 j' B$ g% S5 w- Z0 n4 U" L代码位于linux/drivers/mmc/core/bus.c。6 v6 M: v0 @7 {3 O5 U% J1 T2 B

% y/ |( F0 P/ K
' C( K7 H; P" _1 Qstatic int mmc_bus_probe(struct device *dev)) v2 G+ H- s2 Y5 B, G$ ]7 H
{# s! e# F* L! A# b/ C3 U6 h
        struct mmc_driver *drv = to_mmc_driver(dev->driver);
- [6 n( q9 I1 t0 k2 `( A3 U        struct mmc_card *card = mmc_dev_to_card(dev);6 I# s0 w, m! W0 `& `
1 B. E  x: w$ Q
        return drv->probe(card);
; [5 @* R  ^2 k# `2 p! ^}
' h' E" T! X% K& Y从这里我们可以看到在mmc的probe方法中直接调用了驱动probe方法,这也验证了刚才注释中所说的话。3 f1 a, Z$ c. b5 R3 U# ~. X
从上面分析可以看出,子系统初始化代码仅仅注册了两条总线和一个类,并建立了一个工作队列。, G$ u  X+ X! b; w& |

& T/ N0 Q/ m# e- O- |- j
6 o- b" ~4 P, I) ]- x  D% _" U: O# R" B: I7 J9 L5 {; K) x( b8 r5 ~
四、核心层与控制器层间的接口API
$ E3 @# i6 D4 r$ QMMC核心层要和SD卡设备进行通信,为了完成这一个工作需要将CMD或者ACMD命令通过MMC/SD控制器发送给SD卡。8 ?, G+ ^' `% B

% o$ T2 Q7 w  R* O- t6 x; Z" H0 Y那么MMC核心层如何将通信的数据包交给MMC/SD控制器,并让后者去发送呢?/ u5 T5 w7 t) \0 ]

7 n2 R5 ?: e4 `" |MMC通过函数mmc_wait_for_req完成这个工作,我们来看下这个函数。: x6 I; B% m4 {8 V5 @

7 O( F& u: D, c* P) Q+ w( R3 E4.1 mmc_wait_for_req 函数, a2 Z& V$ y2 \6 u: z8 V- f* W
下列代码位于linux/drivers/mmc/core/core.c。! O* F9 V/ J4 x) {4 d
$ x' \1 f7 w7 H& N# V! m; d
/**
% }4 L$ e1 x# `0 J *        mmc_wait_for_req - start a request and wait for completion
3 t$ d% G, q$ p/ ~4 }9 h* L( x$ o *        @host: MMC host to start command
8 g+ c0 `$ n2 L( h *        @mrq: MMC request to start
. z/ q6 Z( h1 Q5 n! C *" w' v) }8 [4 Q6 P+ @: N+ ]
*        Start a new MMC custom command request for a host, and wait0 ~7 L  O7 z* l
*        for the command to complete. Does not attempt to parse the
5 W: o/ c7 |! ?* ~5 ^8 w *        response.
6 E# F" k# A+ q7 S- C3 x# m' Y */
! p* M' ^% [  u4 P* o: r; [! W, evoid mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq)
& q% b3 ~6 r  Z; N! J9 ]/ b0 B{0 R% D& s% `; y  Y) Z. E
        __mmc_start_req(host, mrq);/ L1 V$ q) k2 d2 E9 Y
        mmc_wait_for_req_done(host, mrq);1 ^5 v4 p- R7 j
}& K$ n8 N4 e5 j5 l# c8 A* U  n  G
EXPORT_SYMBOL(mmc_wait_for_req);. @7 I2 J5 ]- Q+ |. S4 `
9 }/ ]2 A5 c+ R5 p, c; Z4 T: Q
    通过注释可以发现,该函数会阻塞并等待request的完成。
+ `" A7 T, b5 ?' Z   该函数分两步走,第一步调用__mmc_start_req发送命令,第二部调用 mmc_wait_for_req_done等待命令完成。: i, T7 N1 f7 N, Z- ^& ]- `- s
# s0 p( }6 H) z# D" f9 _' ?
   分别来看下这两个函数 :  f7 H! Y, l5 V; v
& T4 ^% k8 X% u

) l- l2 Y( |) Y' j- Y" q1 g. O' ], f( e( K' p. ~/ ~
static int __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq)
' b7 `/ w; X# f3 o0 M" [( |; t{+ T# E( F7 A) u% L6 b
        /* 初始化completion,并设置done方法*/
6 n/ ?$ v0 R( D$ t( W) F, Q+ s        init_completion(&mrq->completion);
' A9 V% C  y1 }        mrq->done = mmc_wait_done;( V; {8 ?6 J4 F8 @& k& L
        /* 如果mmc已经被拔出,设置错误并返回错误*/+ P+ T# E2 j$ U6 ^- D" k
        if (mmc_card_removed(host->card)) {
4 e; l6 g1 h$ F, S                mrq->cmd->error = -ENOMEDIUM;4 h: T6 y. w0 L- v
                complete(&mrq->completion);
! P5 G/ I! S9 A                return -ENOMEDIUM;
% C9 R' `! a5 ]" v+ m, ^        }9 B  O* }# z7 _9 f8 `9 z
        /* 发送命令 */
+ Y% p' l5 l/ }9 J- }        mmc_start_request(host, mrq);
6 q. B' b1 ?& i5 A8 ]* p        return 0;8 o* C' M6 d1 F; h- k: a9 Q4 s; g
}
9 A' N- L0 h( @5 x$ s  t4 k该函数首先初始化了completion并设置了mrq->done方法为mmc_wait_done函数,该函数如下。- q. Z, ?0 P2 f# r- v

+ ~- ]/ d: X8 z3 Mstatic void mmc_wait_done(struct mmc_request *mrq)
5 c! W! R  G+ s6 F+ d{
, y' K- R# [' C# R0 _# b0 `! @# G    complete(&mrq->completion);
8 @# c1 y1 d, q5 q}) V5 {! N6 u% M& `
这边使用completion的目的是为了等待request发送的完成。" Z) H2 |+ r  s" S8 ^3 F# R: p
在第二步mmc_wait_for_req_done中会使用wait_for_completion函数等待mmc控制器完成request,控制器驱动在完成request的发送后,会调用mrq->done方法来激活处于等待中的wait_for_completion函数。) Z  n; `4 _: ?! L1 `3 o& i
3 X- Z# @$ ^. i2 m# O* u4 J5 K
随后函数会首先检查sd卡是否已被拔出,如果卡都被拔出了则没有必要发送request,可以直接调用copletion函数告之相关的等待函数,并设置error值然后返回错误。$ K" M# |0 C* P

# s$ r* V7 F* q) ?2 r) O  Y6 B; i, h#define mmc_card_removed(c)        ((c) && ((c)->state & MMC_CARD_REMOVED)), r9 N! U- n) r) A/ R
如果sd卡存在,则调用mmc_start_request函数发送request,该函数如下:
. G6 q. O2 y; _: d/ M4 l- N$ d, u3 Y( E+ K. @
" b! M9 L) k- G/ V
static void, b/ m* S5 A* x0 _, ]5 @! ]9 {0 B
mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
% J; b, X+ K5 @( f{
7 k) c5 t+ b. v- Y0 U- Q4 a#ifdef CONFIG_MMC_DEBUG
, s) f5 I+ O5 E) B" ]    unsigned int i, sz;- Y' Q( N& H5 Q' ]) I- c3 G
    struct scatterlist *sg;
( w3 L7 t: S: J9 K3 }( o#endif
1 t& C# ^. S4 w& ?3 K. P; m 6 ?" j6 _% ^& h; v# f$ p  E3 X
    if (mrq->sbc) {* q! h, _$ }: h5 `9 t
        pr_debug("<%s: starting CMD%u arg %08x flags %08x>\n",
; a* C9 ^/ Y. g" Z             mmc_hostname(host), mrq->sbc->opcode,& [: Q, `( ^% b( q  B
             mrq->sbc->arg, mrq->sbc->flags);/ J, T/ j, R: I8 ]
    }2 a) |6 D. i) ?5 J- X
0 H- w, m& t* t; S
    pr_debug("%s: starting CMD%u arg %08x flags %08x\n",& j# @! `# C7 n
         mmc_hostname(host), mrq->cmd->opcode,
; m8 \: X9 {9 _# S/ W6 L         mrq->cmd->arg, mrq->cmd->flags);5 C" E" K4 _; C% y* m% x$ _

5 b- W3 b2 l3 y    if (mrq->data) {' b/ n7 v: [6 e- E$ f1 U/ e* l
        pr_debug("%s:     blksz %d blocks %d flags %08x "$ n3 n0 ]4 {9 d+ X, k: M$ X
            "tsac %d ms nsac %d\n",
2 t/ }9 h! w& N- e; u            mmc_hostname(host), mrq->data->blksz,6 n0 a# t, t) P! K' j6 _
            mrq->data->blocks, mrq->data->flags,
! |8 J1 r; i1 k) @4 X1 P            mrq->data->timeout_ns / 1000000,: S: D2 x6 a; J6 Q0 {
            mrq->data->timeout_clks);0 k, J  D5 u; T4 x
    }
; I1 p. h9 h9 L5 ^+ Y' l3 b . D: a1 h6 ?- o0 G3 S. b
    if (mrq->stop) {
' n5 A3 j) M: ~- |/ i        pr_debug("%s:     CMD%u arg %08x flags %08x\n",
- I/ h  g% U0 _) ^/ z7 ~) F' J             mmc_hostname(host), mrq->stop->opcode,. g) {$ A; a. c/ {$ J" B) {
             mrq->stop->arg, mrq->stop->flags);5 P& S6 Q0 K( n
    }4 X. {, i/ ?4 Y3 e& M

8 n) z& V$ |  B5 ?3 H  @6 L    WARN_ON(!host->claimed);: s, b- y. d! z1 p; s! F

" ^2 G3 I1 U% v# D    mrq->cmd->error = 0;6 c7 n. I: c( c5 B. `
    mrq->cmd->mrq = mrq;
/ M9 h4 [' W4 n0 o7 H0 e    if (mrq->data) {% G$ s0 E: F/ r  @7 e% I6 r
        BUG_ON(mrq->data->blksz > host->max_blk_size);; i: O6 O' j5 V* g
        BUG_ON(mrq->data->blocks > host->max_blk_count);* |/ \8 |( L+ t3 R( r, C7 ~$ h
        BUG_ON(mrq->data->blocks * mrq->data->blksz >
  U& `/ F6 c, _7 j3 ]7 C" H* t$ u            host->max_req_size);$ w9 F" Z& ^$ p+ ~7 j8 N( b+ T

; @% W" U: ?: f. {( ~; Y' w+ T#ifdef CONFIG_MMC_DEBUG# b! o$ A9 l' r& B
        sz = 0;
9 ]; I0 y# C% x: K9 Z2 Q        for_each_sg(mrq->data->sg, sg, mrq->data->sg_len, i)
+ g! h- z: o. {! c            sz += sg->length;4 B: I; D$ c! B" b
        BUG_ON(sz != mrq->data->blocks * mrq->data->blksz);6 b' M% o: e; Z/ G. s
#endif/ u( j. C$ Z: {1 ?9 N! t7 o  a

& `# N. R+ Z5 Q, t6 c7 u        mrq->cmd->data = mrq->data;/ K/ w7 b' X' h: N1 v
        mrq->data->error = 0;4 H' f" x# l7 Y7 h# Z5 T2 q" h
        mrq->data->mrq = mrq;
6 N) {0 _/ A# }* P1 G$ W        if (mrq->stop) {
$ T* Y& L, J& d( X7 ^, p% u            mrq->data->stop = mrq->stop;
5 K( f- p% L1 `4 I9 Z+ W* q            mrq->stop->error = 0;% }8 L* W7 ?6 O: f2 @6 k
            mrq->stop->mrq = mrq;
" \+ t/ i6 N6 q# Q  W        }, h& S1 I; S  ^! K4 x" X* d
    }
, ?8 c8 d1 k/ }    mmc_host_clk_hold(host);
; W+ {! C$ i. r# _8 f    led_trigger_event(host->led, LED_FULL);$ x. C" u* F( _$ a
    /* 发送request*/; o# j$ V8 Z0 r! H
    host->ops->request(host, mrq);
6 j1 [# G# q4 Z}$ J  k4 ]8 [1 _  ]
该函数会打印一堆信息,然后清除cmd->error,并绑定cmd和mrq,接着如果mrq是请求数据
* u- r- O. {, K+ O1 k' u" ommc_host_clk_hold函数是通过宏CONFIG_MMC_CLKGATE来进行使能的,这个宏默认是不打开的,具体就不分析了,简要说下这个宏的作用。
" ]% V5 F% \0 m- n: E" R2 o. N7 v, ?( }0 s* {
这个宏的作用是使能时钟门控功能,这个功能在不需要MMC控制器工作的时候,停止MMC控制器,以节省功耗。
4 w* R5 ^% h( ?, {% M2 m3 |: i, h7 M3 O1 z6 n# v$ I% K
随后会调用led_trigger_event触发led事件,这个牵涉到Led子系统,就不进行说明了。
* r/ H# C6 m: O
, ?/ n, q; Z) `& A! n9 v( h& G顺便提一句,s3c2440的mmc控制器驱动并没有使用这个led触发功能,也就是说host->led是为空的。
: e" @' a2 f2 r8 G# [1 I, g( b" V' I6 O" e7 ~
最后调用了mmc控制器驱动提供的request方法发送request。
6 ?% F" D3 \& e; ?  e8 m% I! P6 J8 U% X5 h1 q
这里需要注意下函数指针的形参:一个为host表示mmc控制器,一个为mrq表示request(请求)。3 @, x  P- }/ j1 A

! g1 z, B2 D$ \) o. _很显然,要求host指向的mmc控制器发送mrq指向的请求,同时,也可以看出所有传递到mmc控制器驱动的请求都是使用struct mmc_request结构体进行封装的。) h9 ~4 t  T  ?8 a7 k
& {) w4 d: S% @+ o- O! W
至此,第一步完成,接着我们来看第二步:
7 a; |0 L/ Q$ X" o7 z3 ^1 ?) y7 ^0 k
# D. N" P1 W( f9 p# g, P) F
static void mmc_wait_for_req_done(struct mmc_host *host,
! K7 d" |/ p3 M  R) a                                  struct mmc_request *mrq)
. q8 ~: B, {9 [1 j# T+ S8 b{
0 _' F9 o8 A$ r( B# D: x. t        struct mmc_command *cmd;- n/ w& ^& z( u

. _9 `. d& k1 _! O- S& t7 Z" b        while (1) {
& {+ X$ ^6 z5 \* E8 ~% Y% E' I: B                wait_for_completion(&mrq->completion);( z: u* W# ?0 \2 P7 a% s

3 `  f2 {) }5 E' O0 q5 w2 }' E9 l                cmd = mrq->cmd;
* d1 v) e. \8 S. I& J; Y ; G3 L: J! M  e3 U5 P1 m
                /*
, H# Q% X8 z$ ^  K$ u2 B0 f7 B                 * If host has timed out waiting for the sanitize- q' c- }& p" O& b
                 * to complete, card might be still in programming state9 x3 i( Q8 n( S* W, ]8 R
                 * so let's try to bring the card out of programming# u8 \1 y! f, ^8 ^
                 * state.) E- o9 b, u& O# p
                 */  I1 u4 o% u+ K8 h+ b- S
                if (cmd->sanitize_busy && cmd->error == -ETIMEDOUT) {/ H5 f6 P! s2 ]6 ~
                        if (!mmc_interrupt_hpi(host->card)) {9 s& [; g# K8 g5 f- p2 t  _! n/ `
                                pr_warning("%s: %s: Interrupted sanitize\n",6 O3 g: F. ?3 h6 m0 q. Y) }
                                           mmc_hostname(host), __func__);4 d7 \8 e$ |, G" T( W' W
                                cmd->error = 0;: r( s4 h: D  E9 }. l0 N" s, N5 C
                                break;
7 q! }" B* G- z: |" w8 y+ o' D                        } else {* R0 O) S: d0 i* {4 ?/ Y+ n
                                pr_err("%s: %s: Failed to interrupt sanitize\n",
1 M2 s; K% k% Z5 B1 D4 Q- L                                       mmc_hostname(host), __func__);+ u* t  T9 t* Q% s
                        }
6 ?" f) M  S( h/ ?' e                }7 n0 v, H; L2 _, Z
                if (!cmd->error || !cmd->retries ||
- M' e2 d+ l) g; ]7 K' D% z* V; Y3 n6 Q                    mmc_card_removed(host->card))
/ M; B9 z  b# k; i                        break;8 S3 @4 Q! a+ }$ I; `  a3 t7 D  u8 g8 W
# Z9 I! `' P/ F. o: d7 e
                pr_debug("%s: req failed (CMD%u): %d, retrying...\n",) u8 @1 v2 k  j4 z1 e' I" f
                         mmc_hostname(host), cmd->opcode, cmd->error);
; g/ b* s6 q! L( Q+ {                cmd->retries--;/ T. l0 `7 ^$ |: T1 p- h
                cmd->error = 0;+ ^- n# o- j% v; t
                /* 没有成功,尝试再次发送request*/
4 W! A% x) l+ D5 [8 h9 M                host->ops->request(host, mrq);
6 W8 q0 }. G7 H/ @: F8 H        }; c, j  `2 ~# X7 ?( B0 ^
}
2 G0 B3 Q, i+ v. q0 v2 I2 s这个函数首先调用了wait_for_completion来等待mmc控制器驱动调用mmc_wait_done来唤醒自己。
& T0 t4 y4 \0 c" [# A* w被唤醒后会执行一系列检查,如果request成功发送,则会break,并直接返回。
' j  y/ t! ]- K. v! I* T& |; }/ o: c2 Z" r
如果没有发送成功,只要retries非0,则会尝试再次调用mmc控制器驱动的request方法再次发送。
! ]$ k+ R7 g# d/ a/ N5 t
* p' k/ M2 L9 d4.2 CMD和ACMD发送函数
/ |& U5 i5 G  c9 T' m7 ?通过4.1小结,我们知道MMC核心层如何将request交给MMC控制器驱动,并由后者发送该request给sd卡。
( p& F4 }9 [# |: p$ [) E7 D
4 T# t& a& x2 U1 V0 {$ D, T通过SD卡规范,我们知道有两种形式的命令,一种为CMD,而另一种为ACMD。
" t2 w9 T, \3 P$ c% `- o) f# ^+ m7 C' d+ w
MMC子系统提供了两个函数来完成这两命令的发送,分别是mmc_wait_for_cmd和mmc_wait_for_app_cmd。
" U/ p$ h, n) x, [. V/ o+ S; l: c4 P, r9 O
先来看下CMD的发送函数:
& y$ _. Q2 L) x5 N
8 O# d" h% y2 A( _! e' _6 M下列代码位于linux/drivers/mmc/core/core.c。" o) ]. v: |  `5 p9 t) k
3 v8 h; S/ I& P2 u# y
/**
) D( K$ @/ C$ M- Z" Z+ t *        mmc_wait_for_cmd - start a command and wait for completion
. G; j- i7 u& m0 h' A: L *        @host: MMC host to start command* m+ e& _- O' P* {" f  k: @: a& B
*        @cmd: MMC command to start3 O$ T6 W) N4 K. H% o
*        @retries: maximum number of retries
9 q+ c5 s# {: l *
( V: o8 b  M* m9 O0 Z2 ^( ]2 x2 j *        Start a new MMC command for a host, and wait for the command
& p2 g7 R/ X3 O1 }- @1 w6 K *        to complete.  Return any error that occurred while the command
+ Q1 U+ b- ~) r, l+ b *        was executing.  Do not attempt to parse the response.) H1 C) O: R0 V1 [5 y7 |1 C
*/* z& `% b3 g- t! \. k
int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries)
& R1 `6 D9 z: |9 w' g( X{* h$ e2 L+ U2 Q: Q, t% a* z) x! _
        struct mmc_request mrq = {NULL};7 s$ C3 L/ i+ R' ~% N1 y" H

: Q8 U) O9 w' M5 {' t! Y- M  f, h* h        WARN_ON(!host->claimed);
6 O' C6 F' l" P* u
( `3 U4 ]+ b' f7 W; O4 U" O        /* 清空应答 */4 K# }* _$ c/ [6 a' d- h
        memset(cmd->resp, 0, sizeof(cmd->resp));0 L2 l4 Z! n% d; h
        cmd->retries = retries;- t. }; _& U' R( }3 S! N: {

% S: E; C1 R: ^' n8 v$ f1 W, i        /* 保存命令*/
* I( c# a* m! j; E9 a1 d0 j" `        mrq.cmd = cmd;
! {1 E7 r: B" {% q' S& ~7 j        cmd->data = NULL;. e5 Q. V+ L8 n9 {& w  m

6 e8 u1 \$ }/ M: W- m  Y" s& F        /* 发送命令并等待response */
0 A  ?: O. |  l% E) X0 H- n        mmc_wait_for_req(host, &mrq);
: s; ~" Z3 s# k2 s$ |' t- c# [4 j$ m ) {# R+ I  M$ m& r* e4 N
        return cmd->error;
& v: P2 F+ {. P! {}
, L1 `% }, R3 H' Z1 R3 p- _- h有了4.1小结的分析,这个函数还是比较简单的。  x6 Q5 m6 a) G" w8 [
该函数首先清空命令的应答数据(resp),并保存命令(cmd)到mrq中,随后调用4.1小节中的mmc_wait_for_req函数发送CMD。
: t. a  C3 B) _, s% l# [$ k5 f4 c
- b' C, U; R$ q1 B8 e从这个函数的形参我们可以看出:所有需要发送的CMD都由mmc_command进行封装,在函数内部被mmc_request
/ s" {  O7 R( h& `; y% D9 v结构体进行再次封装,并将mmc_request交给MMC控制器驱动完成CMD的发送。
) [/ ~  d% F" u$ u, @/ o% _) _3 e1 i( ^- P8 x0 `. k0 E' M
接着看下ACMD命令的发送函数mmc_wait_for_app_cmd:
5 g0 E4 I2 L7 ~5 y3 k0 w. [) r. [- f9 D9 x
下列代码位于Linux/drivers/mmc/core/sd_ops.h。; Y1 f3 `# Z5 ~2 {9 E3 k' I

- b# t5 w# d! W2 b8 _5 i8 c/**
2 L) V! V# B1 a* } *        mmc_wait_for_app_cmd - start an application command and wait for; K+ r( s+ v  l: e  x4 H5 p! l' f
                                completion
& a# n0 u7 p! Y: C4 o3 y; q  |+ k *        @host: MMC host to start command
+ t9 V* q' n( F* I: ^2 I *        @card: Card to send MMC_APP_CMD to
2 a; |- V& R4 J6 f3 y *        @cmd: MMC command to start, g- D4 j0 W/ |0 @' L
*        @retries: maximum number of retries. w2 h, U  c" @. j6 F
*
( N! p9 E$ c% G+ D. I *        Sends a MMC_APP_CMD, checks the card response, sends the command1 l* B9 b8 G5 }% _4 c" H
*        in the parameter and waits for it to complete. Return any error
  s* ~+ I* H; I6 A2 l3 ~ *        that occurred while the command was executing.  Do not attempt to
$ M4 a4 w5 H* A9 k6 `, P+ ?5 s *        parse the response.  k' [2 m# N' z: J
*/3 P8 O& A3 Y0 r6 C& l! u5 \1 K
int mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card,
9 I/ f3 s, `; u' u7 ?, I        struct mmc_command *cmd, int retries)& [# ]% n% b. [  i3 A  c3 P9 {
{3 F; h) C2 K8 L+ k1 Y3 Q
        struct mmc_request mrq = {NULL};5 B* C7 @0 I, V; R* G* G
1 q7 i8 z& B- K
        int i, err;% F. L* w! d3 a( G9 g1 r/ o

) u+ D1 R' n) x" C* }        BUG_ON(!cmd);0 M% n9 F) s+ B8 d% E
        BUG_ON(retries < 0);1 r5 j: k: y  o8 U

# X  M4 B" i3 n3 \* f+ C) v        err = -EIO;
" U  z+ i* l( o, o8 N( Q$ R
/ C' {# n5 h$ f8 K/ P( R        /*8 B, w; G# n$ x; P3 J( _  z5 P  j# `
         * We have to resend MMC_APP_CMD for each attempt so
" P% U- p/ ?7 f$ s         * we cannot use the retries field in mmc_command.$ j3 [  m! e/ R  U5 n; P
         */
8 h- {; T( k# p/ l4 B. z8 A7 a        for (i = 0;i <= retries;i++) {- T6 @' G: G0 U( |0 w; ^
                /* 发送CMD55*/
+ ~# v9 X: q" w& H  S! W1 g                err = mmc_app_cmd(host, card);
/ L0 B9 R: I9 ?; J% [& q$ H: T                if (err) {
* b& J! n' k( g/ ^                        /* no point in retrying; no APP commands allowed */4 b: G* }3 X  ]1 x$ A4 I9 x
                        if (mmc_host_is_spi(host)) {. ?' o6 D1 `; |' v9 g) x5 C- F4 m
                                if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)* o6 f: |, I" W) m/ I
                                        break;
( z7 q% V! P( x" P5 a                        }
. c% `+ \2 f( p6 e$ u6 f                        continue;
# ?% \7 N2 P/ o( [! J3 g! @! I                }
7 H7 \/ K' S# Y& }- H$ }' ^
, _* L8 B* n% f- w/ }6 x                memset(&mrq, 0, sizeof(struct mmc_request));
' ?( |+ m6 o- m& R5 ` * k$ q& ^0 \  E$ G
                memset(cmd->resp, 0, sizeof(cmd->resp));1 @% s- E3 m6 {, E/ s# F3 J: [1 V
                cmd->retries = 0;& \8 {& z% S  m. e3 u7 Y# N
4 w6 R1 o2 `( T: w2 D7 ]
                mrq.cmd = cmd;
5 M9 r9 K( N6 J# B3 j' h                cmd->data = NULL;
* [- F4 S* K1 ]0 e3 A- E
7 `) l" Q' T' S9 v                /* 发送ACMDx*/5 u3 A$ Q% R; i* V: S$ P% F6 k
                mmc_wait_for_req(host, &mrq);
* d$ Q" x1 S- t
/ \5 K3 R8 G! b! L; _6 y                err = cmd->error;
; k. v6 d. q% N: x& @  {                /* 发送成功,直接break并返回*/
: J: A, n  D, d: {# ^                if (!cmd->error)' D4 ]# i6 _* P4 w, d) H
                        break;
: ]/ X& [$ N! K1 T
3 \- z& V! F/ E6 Z+ |. |                /* no point in retrying illegal APP commands *// v$ }; I1 x% L
                if (mmc_host_is_spi(host)) {+ v7 _4 d# z  y1 l0 o
                        if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)* ?/ O9 K0 Y1 z3 N4 P
                                break;& [9 T/ M0 D% ]# ^3 u: e* F/ @
                }
' n8 y) ]% Q8 {& u& b1 P        }
" X! s7 Z0 W% V3 g $ N- q& D0 K7 j  ?
        return err;% n; V8 ?, \, _, j" U, M
}
$ j9 X9 a1 @$ E0 d/ x: b$ _ ( [3 b4 k- R/ i" G: S. k( \9 t2 z
EXPORT_SYMBOL(mmc_wait_for_app_cmd);5 n6 Q5 t5 d8 e' w5 t9 \
该函数的形参cmd保存了代发送的ACMD命令。' ?* T# M9 Y) K2 W' x% {3 z
根据SD卡规范的要求:在发送ACMD命令只前,需要发送CMD55,以表示后面一个命令为AMD命令。% r. H" T4 y1 D
; Q/ r# b6 q  }6 ^( I( g  }7 L& c
所以,该函数首先调用mmc_app_cmd函数来发送CMD55命令,我们来看下这个函数:
3 @' N& W. J  ~8 h6 w
  `* [" j- s5 u# F* L( W6 X; b; iint mmc_app_cmd(struct mmc_host *host, struct mmc_card *card)% d; g2 p6 i: F; A6 S# ^' z8 k5 U
{' w5 T) q8 ^+ F1 W0 h  T
        int err;1 T3 D$ W' Z; m% }$ B4 E0 `+ l
        struct mmc_command cmd = {0};
) e- F3 C- i+ K* r: i4 g7 z 9 q, U$ }$ l9 m7 m
        BUG_ON(!host);
1 Q( J9 g' i, R5 y9 W; [- |4 r8 s" d' _        BUG_ON(card && (card->host != host));) y/ K8 `5 G* ~5 ~, z8 ?5 I! f- G
+ [* O& `7 l% U- e6 m; T6 s
        cmd.opcode = MMC_APP_CMD;        /* CMD55 *// `" p* \5 r9 N: y- ?

4 K6 l2 G  |! a- h8 ^6 X        if (card) {! Z6 B5 }8 p) }) V+ I
                cmd.arg = card->rca << 16;        /* 卡地址*/
+ ~7 S6 Q! s; r                cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC;8 P) L6 K( @8 S* l
        } else {! P8 v( h" D5 I  X: \
                cmd.arg = 0;        /* 卡地址*/
7 C% a8 Q% x6 e6 t                cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_BCR;
3 q5 Q# e# e. Y7 B, ?/ `5 F        }  _0 k5 F$ C( ~8 a) f4 X/ \
( N8 W7 F# Q" m! w& A
        /* 发送cmd并等待(阻塞方式)*/
0 X& J" a0 Y* v$ j+ m        err = mmc_wait_for_cmd(host, &cmd, 0);
7 V/ ]' P) q  o5 ?8 V% v7 a8 _, E        if (err)
; E# q, y3 B9 x                return err;7 x4 N# H; L2 Y' l1 u% G3 u

& e/ n3 i6 E6 `. S" @9 ?% O        /* Check that card supported application commands */
, m: P! B! [+ z/ S$ U" p" T( E        /* 检查card status第5位,判断SD卡是否支持ACMD*/
' O3 d6 B; j+ @4 |! x* o        if (!mmc_host_is_spi(host) && !(cmd.resp[0] & R1_APP_CMD))
+ E; d2 q7 D0 E0 `* B, X                return -EOPNOTSUPP;$ d5 L9 w( v5 F
/ H+ A# E$ l3 a. k
        return 0;' ~( z6 h  h/ K/ N# L2 T* I4 l5 d
}6 l' M- x) k3 a- e3 f$ C: R
EXPORT_SYMBOL_GPL(mmc_app_cmd);8 A- q& X3 V: e* c
先来看下SD规范中关于CMD55的说明:
2 `8 F4 k! s8 A6 b' z! p& d+ I
6 G* B' y, Q  X( C, s2 p5 e
6 E5 q& I8 {3 z/ v+ ^从上述命令说明中,我们可以看出:
* J$ K2 e, n4 t8 C! |% p% [& \1 ?+ ^- e
1)该命令为ac类型命令,也就是点对点命令,并且在DAT信号线上没有数据传输。/ z7 K9 o9 |  V; Y

4 A% W: b3 }& i$ ~. ?3 ~; ~& O2)其次,该命令的参数(31位至16位)为RCA,也就是卡的地址。
( a& j$ ?/ {& A2 D3)最后,命令的应答数据格式为R1。
. o* B, q4 y8 n8 Z6 D, u9 s$ d' h, N5 {* V( K
回到函数中。8 l! K  r. k/ }" j& N. y, d3 X4 b

1 K1 L' S2 r6 \2 v- L/ v4 h& Zcmd.arg为发送命令的参数,函数首先设置了命令的参数为sd卡地址(RCA),这符合上面的描述。
) R" l' \8 ^  U
9 A) E0 D. @" a) x随后调用了之前分析的mmc_wait_for_cmd函数发送CMD55命令。
( Y/ I7 c" a/ @; ]
8 i, z# b$ J1 n; U5 ]+ v1 H上面提到CMD55命令的响应为R1,其格式如下:1 O1 m) v4 m5 e$ B' n

! }" p2 Z5 a: S1 W3 F
4 @+ n5 X8 a% r, p7 i$ O- c3 ]0 Q8 C4 s% e" P5 c  z6 P* c
其中32bit的card status作为响应数据被保存在resp数组中。; x+ l$ X/ R. G( G# F' y( ]4 z2 W
1 t; k) @8 S: B) y/ d3 E
card status的具体位定义请查看SD规范的4.10.1小结。
1 d% P# M# j6 u' O/ O! U' p. w
4 e5 _9 v3 w7 G! S6 p$ Q最后检查CMD55的响应来判断SD卡是否支持ACMD命令。) ^7 I3 E9 e$ L1 V, p4 K, l) G
5 Y& s- i4 F8 {$ W
CMD55发送成功后,返回到mmc_wait_for_app_cmd函数中。$ j3 D) k/ M9 s9 ~" D" b9 A1 {
0 v3 i4 C$ s. q! s) ^$ l
接着,cmd被保存到mrq.cmd 中,并调用mmc_wait_for_req中发送ACMD命令。2 e+ P6 J* _3 q

! D7 ?* }$ ^6 C8 q/ N5 n& G% D4 Y+ z: c& Q& q
五、小结. a& _, ]  o) E: P
    本问主要对MMC子系统架构进行了简单的介绍,并给出了一些关键数据结构。同时,对MMC子系统的初始化过程进行了简单分析,最后,重点介绍了CMD和ACMD命令的发送函数。6 b! W+ c+ Y2 s+ P* A
7 n! E2 M( h) `* ^6 }7 M

+ {: i1 S' n* k$ g3 e5 o5 n
9 q" G2 {) J8 s  ]6 R( P+ q; E3 R! [9 a: ]

( e; M4 y0 B5 Z7 U1 n
8 ?. D& L, S' F9 A& _7 x: G7 u
+ g2 {; q4 y0 U6 F  L

该用户从未签到

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

本版积分规则

关闭

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

EDA365公众号

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

GMT+8, 2025-11-26 00:28 , Processed in 0.187500 second(s), 26 queries , Gzip On.

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

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

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