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

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

[复制链接]

该用户从未签到

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

EDA365欢迎您登录!

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

x

* U7 W6 [: Y- _2 O  F在阅读本文之前,请先掌握以下基本知识,不然请略过本文。$ k; @' b% v5 Z1 x; f

% R" M; B. f9 Z4 r$ m预备知识:5 ~3 U8 }9 l' c$ }6 _  _
6 m! a0 ]# c7 \6 G- v' E
熟读LDD3前十章节的内容。
2 s. c. G8 w5 P' W' n8 z3 t5 M( Z  I/ n& @; c
熟悉内核驱动模型(sysfs)和platform总线。
4 V& z0 q& c3 _% ~8 S
0 a! u" k- }4 A' B5 n0 C+ ]9 {# x简要了解过SD卡规范。4 T5 O; o9 [) F; |
: P6 H  p  v& b# W4 L8 A% C- L2 P' X
# w& p4 F- D: M  v9 a, K; H
! w; |" {5 C5 Y: I5 l' d
本文的内容基于如下硬件和软件平台:9 W, |& U7 g. d# C$ u
6 R. ~1 d4 @- k2 c  n( @% }
目标平台:TQ2440- C0 D- M1 D' w- B3 m: D

  S! f  W  h; T, n# ZCPU:s3c2440  z, w1 P( X+ @/ z7 `1 a2 A

; N+ F& u" D- t$ _( t内核版本:3.12.5
$ b  {- n- ~0 `/ \) |1 e+ q0 `5 Y0 f9 W" u% c7 A
基于SD规范4.10,即《SD Specifications Part 1 Physical Layer Simplified Specification Version 4.10》。
( b0 p7 S' D" k
- N; C0 {" F2 t' K0 @一、MMC子系统构架
3 J+ `( ^8 B+ E9 m) y3 ?待写。。。2 K: l# f# O' `: I

6 j; W$ _- N1 {4 e' \( i" H) P( Q二、主要数据结构
6 P* P9 X- x- C2 f. R待写。。。
3 ^: Y, u& f$ Z* Q5 t
& X$ @9 f) o6 A: _, D三、MMC子系统初始化( E; V4 {3 P& t; P
首先看看子系统是如何初始化的,完成哪些工作。8 R! |$ G2 G$ x6 H. x- I9 m$ i" A
. r8 `4 N. `$ N. a& A. z' f$ q( y
代码位于linux/drivers/mmc/core/core.c。
9 }/ l7 H. o9 N# T2 o7 S5 [
5 ~  |( i  p" [4 s/ A* V* r
8 S" z/ p' D0 Istatic int __init mmc_init(void)$ `" f: N4 k4 O
{
, E9 s9 S+ ?- T% z: P$ {    int ret;& T. }( s) k  [9 G
5 H5 F, s- \& g4 W7 R. R
    /* 创建一个工作队列*/. e0 C  v! f4 s! ^* X" E
    workqueue = alloc_ordered_workqueue("kmmcd", 0);; Z: b' o  F! Y0 I0 c/ C
    if (!workqueue)& c0 k6 C# T$ O+ c" _
        return -ENOMEM;' L1 C4 A3 N; l
: f7 B* ^# L. A
    /* 注册mmc总线,总线提供probe方法
  N+ L, i" X# `       并直接在内部调用驱动probe方法*/# |2 z9 w* {5 Y) r& }
    ret = mmc_register_bus();
$ m' w& T1 G; K, u9 g    if (ret)
3 r" h( \2 I& A; a9 N1 M        goto destroy_workqueue;
0 d. o+ p% r. z" B5 C, c. r  H; {   
# a$ f4 {6 X) [5 d0 o+ l* c( ^    /* 注册名为mmc_host的类*/% W) C8 L" y7 J; G9 }
    ret = mmc_register_host_class();0 l. I" `- Q" o7 C% |8 `1 v' ]0 t
    if (ret)
, E9 \8 d! Z7 s+ S        goto unregister_bus;% u- c3 l% k2 m  }

1 M  i3 x; e" X    /* 注册sdio总线,总线提供probe方法
8 a! H; f# B0 V0 P- K& x. g       并直接在内部调用驱动probe方法*/$ k$ s+ n( w: Z
    ret = sdio_register_bus();
0 s6 p: t+ v, X, \. J0 Y1 V    if (ret)7 {& ~% ^1 w, t9 t0 ?* B6 t/ Q
        goto unregister_host_class;
4 Q8 ~4 P" [- Z 1 o0 k9 q4 w# |& l  p
    return 0;
8 ^/ W7 b0 S: a$ ^6 L# J: i
, z/ k2 p; N6 g$ y) Gunregister_host_class:" G+ n' U( A& ^/ s0 o  m
    mmc_unregister_host_class();
- [! m/ d: |4 L5 H1 q$ |" k/ C7 A0 qunregister_bus:
3 N$ C) k, Y% K' j  y$ P    mmc_unregister_bus();
' M  D0 r, G9 h8 W2 N) _: Vdestroy_workqueue:
, p  a4 |4 }, k% N8 J    destroy_workqueue(workqueue);* R8 B" D" `. E4 K' z& i* e# X- S# W7 l

: T6 o- ~4 {% m% r    return ret;; E8 d: d: M; F2 \
}; }) t% p4 d- V8 s

4 R; y( W& H7 u8 o( [代码首先注册了一个工作队列,这个工作队列将用于扫描sd卡设备。我们会在后面进行说明。# P1 F& k7 O% {

8 n8 X  r* t3 {& q8 b, k4 b工作对类已内核线程的形式运行,可以用ps命令看到名为[kmmcd]的内核线程。# e1 b& i2 b( v  K+ h+ e
, @9 V& s- |+ P; T' M! g9 K; }( z
接着注册了两条名为mmc和sdio的总线,以及一个名为mmc_host的类。具体代码如下:# l; T" `5 z7 m7 _/ S0 e8 p
  |+ d! C  I( A% U& L; J
- B& s4 C4 c7 i2 {1 Z
static struct bus_type mmc_bus_type = {
  m/ f1 I% W% y0 H9 `! P        .name                = "mmc",/ @4 C. m$ M# v" r1 A
        .dev_attrs        = mmc_dev_attrs,
- J. \6 E+ r9 a! t3 o& X        .match                = mmc_bus_match,
1 I+ ^  F( Y4 {3 N9 b        .uevent                = mmc_bus_uevent,' Y% ~- [! S: _6 W; B
        .probe                = mmc_bus_probe,
$ R7 J% U  l! C8 F2 j1 Y" F        .remove                = mmc_bus_remove,, W/ z) I; M. M4 i6 K" X5 s
        .shutdown        = mmc_bus_shutdown,1 b  n0 m7 A0 j  f3 e5 J$ R" B8 |
        .pm                = &mmc_bus_pm_ops,
4 D* W- b9 u' H4 f" v  A" H};
( y7 g6 g# C5 v/ d
; o2 c, s( H' g* |int mmc_register_bus(void)
4 b$ L0 i) F6 l6 a! N{
/ b5 q* ~2 E! l2 C) L6 G  T        return bus_register(&mmc_bus_type);6 O! y1 @" N) a$ d6 I
}
. h7 H9 ~4 C! E+ ^2 z0 n: d7 o8 D& o# {1 g  X- B0 P
static struct class mmc_host_class = {5 |4 K7 }5 S9 s
    .name        = "mmc_host",/ f7 ]5 l1 Z& }* H. i- x
    .dev_release    = mmc_host_classdev_release,* ?; o+ i( D! J/ s' y
};4 m# l6 k1 u( F+ S
7 y# N9 g  e2 l8 c
int mmc_register_host_class(void)
5 Y6 W! J1 A# q{
, O, P) r5 m2 p- ^% x( z1 F    return class_register(&mmc_host_class);1 {: g9 O, u$ l( |1 |" ?
}; k* P, r1 n; T6 I; r( u

7 V1 Z9 g7 e( `6 e) Y8 Qstatic struct bus_type sdio_bus_type = {
3 W' O% ]+ f0 G& d2 [3 Y    .name        = "sdio",  o: n& c  O$ ?# P: y6 r. o% t5 b
    .dev_attrs    = sdio_dev_attrs,, y& [2 b" |" D/ }
    .match        = sdio_bus_match,$ x% `; e% }! o6 q& a4 j9 z
    .uevent        = sdio_bus_uevent,- I4 ]& w) T* w: y5 f1 ^
    .probe        = sdio_bus_probe,) d/ \( v* `9 c" g3 I- [) Z' R& P
    .remove        = sdio_bus_remove,6 `+ G3 O6 U: ~5 ]% G/ [5 w; ?% t
    .pm        = SDIO_PM_OPS_PTR,
) y% K! F6 G/ U  p0 ^};
+ k3 d- W5 C' V5 w' M/ L, s 6 A0 n- i8 n" M) W
int sdio_register_bus(void)
8 n8 D# B+ W  [' @& v{6 q# y; ]% y. o& J$ g0 ^4 L+ ]" t
    return bus_register(&sdio_bus_type);! y, \" w8 F/ _9 R1 {' I$ D
}
1 W% o, ]/ `0 Y; r" q
3 b$ L0 c4 P( X" l6 Xstatic struct class mmc_host_class = {+ c: q+ ?6 ^7 V9 R5 l, y! x
    .name        = "mmc_host",+ }1 D4 h# B) w0 c; D: l
    .dev_release    = mmc_host_classdev_release,/ i3 w1 S+ S3 y0 D0 i
};1 O; G1 D$ n* I. b& D+ H* x0 W
, w; j; a! {9 o
int mmc_register_host_class(void)) E; j' n3 `( `# P
{0 I: y3 }5 Q; |& \) r
    return class_register(&mmc_host_class);
' o: d1 p/ f1 x8 y6 A}+ e" B( q+ |/ L3 `$ f- H2 i
  `9 U0 J# E* }+ p. o/ K/ M3 _
熟悉Linux的设备驱动模型的同学对这些肯定非常熟悉。总线和类的注册只是调用了相应的接口,这些就不再赘述了。
+ u& t* p6 y/ W- T
: R$ B9 R% N0 y其次,sdio总线不是我们关心的。我们只关心mmc总线。首先来看看mmc总线的match方法:
+ E8 D! T0 j: p1 L, W8 X
5 D: r/ ]  N5 i2 T  d代码位于linux/drivers/mmc/core/bus.c。
1 H; I$ l; i+ r( X- |7 q! Z, F7 y' t- q+ U# q

- k: ?2 N3 w1 X( b* g+ p  H/*
- a1 }- b/ c: s5 e  e * This currently matches any MMC driver to any MMC card - drivers
' t0 t2 W, E' V, W5 b * themselves make the decision whether to drive this card in their4 C* g- j- p+ Z% e  [8 `+ y
* probe method.1 I( p4 h* K0 j0 h9 s- N$ T
*/
$ O; n. g) t8 s1 `- n* B/ g: v# k/ Gstatic int mmc_bus_match(struct device *dev, struct device_driver *drv)
( D4 g, W, ~% D  N* d& w5 w{  N5 S8 t8 M$ s( f) V, P4 |$ m# D
        return 1;
4 L( C6 [/ w* }4 R; T}2 J/ J" ]' n- k+ p6 b" e$ C
match返回居然直接返回了1。这表示任意的驱动都能和mmc卡设备成功匹配。
% l% t7 H. N/ [  A7 g8 Y2 E$ B
/ X& q' m7 f* p! W. x从注释中我们也能看出,驱动的probe方法将会决定驱动是否能真正的匹配这个mmc卡设备。. Z2 [% e: n7 i" A: t$ r; [
- u  V/ P# n" G, b# j
熟悉设备驱动模型的可能知道,随着match返回1表示匹配成功后,将会调用总线提供的probe方法。接着我们来看下mmc总线的probe方法。
  ?; i$ Z# K8 T4 V; H! i2 D8 \- a. e5 g0 e0 t3 N
代码位于linux/drivers/mmc/core/bus.c。
- e9 z: P- R: I5 T6 ?1 L% s) B8 l+ D7 G+ Y

; }5 z7 `" y8 Y# P0 }& Z. s( y1 b% Kstatic int mmc_bus_probe(struct device *dev)
! l' z0 N5 B, ~+ Z{
6 k; O8 z. ]3 \1 X2 X  \: f1 b        struct mmc_driver *drv = to_mmc_driver(dev->driver);7 U6 O( \) O! ^1 q( I$ S
        struct mmc_card *card = mmc_dev_to_card(dev);7 y) g2 w" O0 X/ e, N4 `1 J

- e7 s& f8 d/ E8 {& a" I        return drv->probe(card);7 c) \4 S6 _6 K; B: T. P- t
}' S! u2 D1 [: j. g
从这里我们可以看到在mmc的probe方法中直接调用了驱动probe方法,这也验证了刚才注释中所说的话。
% A( r$ p0 b6 z* d" m从上面分析可以看出,子系统初始化代码仅仅注册了两条总线和一个类,并建立了一个工作队列。( H! |! M1 O+ P9 c' N
* E' n4 ?7 K3 n7 q) y  R
* N7 f6 i. c$ t4 L

; O; V6 B0 b4 S4 M" A& Y四、核心层与控制器层间的接口API
2 G" h: Z2 |1 ?, q* AMMC核心层要和SD卡设备进行通信,为了完成这一个工作需要将CMD或者ACMD命令通过MMC/SD控制器发送给SD卡。* G  ]; J) q; O3 K. ]
& t4 y* e. D4 |  w5 [
那么MMC核心层如何将通信的数据包交给MMC/SD控制器,并让后者去发送呢?
: S5 C0 k( n1 h2 s
5 [6 n6 {9 V( d- mMMC通过函数mmc_wait_for_req完成这个工作,我们来看下这个函数。5 }7 t5 A8 |2 x

/ P- X1 d7 M2 m4.1 mmc_wait_for_req 函数
' U: r& z9 B. X( v下列代码位于linux/drivers/mmc/core/core.c。
* I' B; O2 z# l9 d0 X4 ?& l7 U! E
) C0 e& O6 w- V8 Q6 q/**& t7 @9 F7 p4 }. G: o
*        mmc_wait_for_req - start a request and wait for completion' G, T8 ]/ c5 H3 B3 _. u, |/ u
*        @host: MMC host to start command
: {  H. i5 \3 N& x *        @mrq: MMC request to start
# ^: N. c7 P$ M8 q6 T# Z3 M *
% `: b5 |6 |: B$ t0 l' `0 A$ i *        Start a new MMC custom command request for a host, and wait, |. {* y6 W8 L" U
*        for the command to complete. Does not attempt to parse the" {5 S8 n) ^- X
*        response.
% A6 w# `& K  f4 `+ X0 @* w/ W */. Q# @' \5 }9 ^, B* W
void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq)6 m; |6 O7 ?' \' T) m
{
+ _: r- P$ A( i( @- @: f4 @7 s        __mmc_start_req(host, mrq);* S& _6 @2 B  q* U& H" G; I
        mmc_wait_for_req_done(host, mrq);4 H$ M5 Q% K* `1 T  w; y- _/ P
}$ V7 r2 B$ `7 N, F0 V/ P/ m
EXPORT_SYMBOL(mmc_wait_for_req);
- v4 |" M5 P) |) H# l0 Y# w
& s  f: h( n* Z' ^0 _    通过注释可以发现,该函数会阻塞并等待request的完成。 : X2 f( @, p' u, t+ I1 _
   该函数分两步走,第一步调用__mmc_start_req发送命令,第二部调用 mmc_wait_for_req_done等待命令完成。, S9 ~7 e" \- ]0 M
! |7 k" z0 D6 x$ I6 J
   分别来看下这两个函数 :) p2 E& }4 v0 Z% u! m* e

# h  y% ~0 ~0 u- [. p  e/ D. B
/ c4 Y# F7 k+ s' ~9 T% j2 }" p0 t; Q- t/ l9 a# C
static int __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq)& f/ a. V3 z2 `5 w$ S7 y
{
* O* z3 x2 n0 K( Y& S        /* 初始化completion,并设置done方法*/( }; @3 I' Q0 V! }6 q2 M% A+ o
        init_completion(&mrq->completion);
' n3 j: _* ^/ N        mrq->done = mmc_wait_done;
9 c& y$ Z1 e. e" @8 t2 L% r3 F( E6 ~        /* 如果mmc已经被拔出,设置错误并返回错误*/' C& g; m8 o; m' K  i! ]6 ~$ T
        if (mmc_card_removed(host->card)) {
8 U9 T2 I- u8 q% h6 A) M: `                mrq->cmd->error = -ENOMEDIUM;
) K; k6 h, ]8 X2 y) H* ^                complete(&mrq->completion);
: h* g% |. i- y5 p, s% M* d+ q6 P                return -ENOMEDIUM;
" P; v1 j3 y. ?" K        }
' I' e! A% T1 p        /* 发送命令 */
: V! ?6 G" ~( Q2 L0 T        mmc_start_request(host, mrq);# q1 _$ h* |, v4 T. j( ~
        return 0;8 \. I; J4 d3 E
}
" V; y! [/ e9 o5 r该函数首先初始化了completion并设置了mrq->done方法为mmc_wait_done函数,该函数如下。
/ P/ r2 a' a2 ?+ N1 z( I/ w# N
& u( X3 ^! t0 T( ?. Cstatic void mmc_wait_done(struct mmc_request *mrq)3 j6 x3 k. j; H( e
{/ U' H. R8 G. o8 }3 x
    complete(&mrq->completion);
& ]4 _/ }+ h$ F( j}
0 p4 L2 R8 e, M4 @2 w/ ]+ T这边使用completion的目的是为了等待request发送的完成。6 |( B/ d1 ^+ @* l- B* ~
在第二步mmc_wait_for_req_done中会使用wait_for_completion函数等待mmc控制器完成request,控制器驱动在完成request的发送后,会调用mrq->done方法来激活处于等待中的wait_for_completion函数。
% X7 ^( j6 \' ^, {* d  b4 c
" G4 ^/ f. m( W& E: X随后函数会首先检查sd卡是否已被拔出,如果卡都被拔出了则没有必要发送request,可以直接调用copletion函数告之相关的等待函数,并设置error值然后返回错误。3 f4 ~- |5 l9 s& b0 w$ e% g& w

: I! w3 i- p4 a# U, x1 p2 K#define mmc_card_removed(c)        ((c) && ((c)->state & MMC_CARD_REMOVED))
1 t9 F0 B+ k' E5 ]- v+ J如果sd卡存在,则调用mmc_start_request函数发送request,该函数如下:1 g0 R! f9 P3 m7 z2 V( p

# N3 j3 t6 Y$ }, c/ x' ^9 h3 o7 @( B/ N8 L/ W" b& d, `7 B5 b
static void
& F7 c4 P8 T) u* l; wmmc_start_request(struct mmc_host *host, struct mmc_request *mrq)" [& d! Q" [0 B6 \& |
{3 {5 p3 i3 q. Z* r
#ifdef CONFIG_MMC_DEBUG! ^- a. F  w2 b4 f$ n2 V- g
    unsigned int i, sz;
5 E- F, |" ~2 c) K) t; U    struct scatterlist *sg;, L: M( W/ S4 i7 h9 v9 x( F" P( ~
#endif% Q9 B: H& a: m7 M' Y

5 D5 j" [$ ~* o1 [    if (mrq->sbc) {* l  d5 X! e8 Q6 g$ k# u1 [% f
        pr_debug("<%s: starting CMD%u arg %08x flags %08x>\n",' ]. A9 |2 q' s# j" z
             mmc_hostname(host), mrq->sbc->opcode,, z& W& x5 {# w6 _
             mrq->sbc->arg, mrq->sbc->flags);
+ p, e# K2 L( o* J) m/ W    }! g6 d& c+ ]  l# }9 }8 ]; s
% b8 @5 p8 O0 e7 K7 }9 O% Y
    pr_debug("%s: starting CMD%u arg %08x flags %08x\n",5 t6 W4 K7 O9 ], v
         mmc_hostname(host), mrq->cmd->opcode,
7 d: f/ N$ w; n# Y# `( T/ v. x7 i         mrq->cmd->arg, mrq->cmd->flags);: N' l6 E0 u2 o' D% L2 H3 {. u% {

8 i4 A( \2 w+ L5 T" e( P    if (mrq->data) {7 m, x: J0 D! ~; V, L, ]- _! N
        pr_debug("%s:     blksz %d blocks %d flags %08x "
7 G* e5 [3 `: n8 S% w- o            "tsac %d ms nsac %d\n",4 F% N- K' y+ p+ i7 u  F6 W1 m
            mmc_hostname(host), mrq->data->blksz,3 R7 ], v/ ~0 s0 s% z
            mrq->data->blocks, mrq->data->flags,
7 U) ]) O$ q; f9 m. N1 {1 }* Y            mrq->data->timeout_ns / 1000000,( H  ~5 k# n0 }# T/ S
            mrq->data->timeout_clks);
( e. v5 o( W: b/ b/ Y) M    }
+ d8 w( Z9 u" a. R# X* o
& L4 h( A( R9 U1 t    if (mrq->stop) {9 ^: K# f+ l1 b
        pr_debug("%s:     CMD%u arg %08x flags %08x\n",7 D6 l3 r* W7 `# z% s
             mmc_hostname(host), mrq->stop->opcode,- b  c5 D- X4 y4 S7 K6 P
             mrq->stop->arg, mrq->stop->flags);+ u% p) |- n# w. i
    }
2 v0 j$ z7 n$ b; ?$ A
$ r, g4 |; E. i4 s& P- o    WARN_ON(!host->claimed);) `: g5 {0 k8 P" J: @8 r. P

( L5 n0 }2 k, z4 n& r- e, `2 m    mrq->cmd->error = 0;
' V  w# ~) _' k% `4 F    mrq->cmd->mrq = mrq;
  G2 K1 J' r8 V4 ]) U    if (mrq->data) {9 z( U7 S* E7 M9 M. ^  ]9 h) e* Y
        BUG_ON(mrq->data->blksz > host->max_blk_size);$ F  |  s* z* ]2 S
        BUG_ON(mrq->data->blocks > host->max_blk_count);
; G8 d1 Z/ H' t# M+ j        BUG_ON(mrq->data->blocks * mrq->data->blksz >
7 l( \' d2 N, H% w5 A* c1 k; `            host->max_req_size);
% i' g* h/ ~; V % e- v9 }) _% f( d
#ifdef CONFIG_MMC_DEBUG
+ \2 a3 S7 W$ g7 g4 I9 G        sz = 0;  ]5 r$ p, R* l9 X# G* ~! ]# ?8 A
        for_each_sg(mrq->data->sg, sg, mrq->data->sg_len, i)
0 c& O1 i6 j* L: c+ d7 i8 x: X- L            sz += sg->length;. s" n& g; F5 M2 Y1 [2 p! g2 D9 g
        BUG_ON(sz != mrq->data->blocks * mrq->data->blksz);
6 g' }2 @% L4 p2 L#endif3 j2 z& X$ o$ `
" V3 P3 |1 u$ u6 h
        mrq->cmd->data = mrq->data;
8 u8 T" N; z! S& J$ R2 j4 K        mrq->data->error = 0;
, \/ ?- \' T8 k. Z9 I        mrq->data->mrq = mrq;
! f1 l4 @# l" I& N$ Z5 J        if (mrq->stop) {7 k4 K2 ?& ~( C
            mrq->data->stop = mrq->stop;
& x- S; E( s/ n) }2 p6 [/ G8 k/ H            mrq->stop->error = 0;3 q7 q2 }% x# j$ m
            mrq->stop->mrq = mrq;. M. E$ }/ `/ d  F& O& l
        }/ l, C& {+ |- L( o
    }* d+ c# u; L8 u# T3 C
    mmc_host_clk_hold(host);
- K+ E+ E; L  N  m4 N$ ?7 e    led_trigger_event(host->led, LED_FULL);
$ {& w2 l2 y) N    /* 发送request*/6 K, V: v1 }% C% v
    host->ops->request(host, mrq);" a- o, H8 M$ }" b6 i* w3 [3 P  [
}
& D3 I$ v4 s. F该函数会打印一堆信息,然后清除cmd->error,并绑定cmd和mrq,接着如果mrq是请求数据
, b5 F3 O9 V7 P2 E0 }1 b9 r8 @mmc_host_clk_hold函数是通过宏CONFIG_MMC_CLKGATE来进行使能的,这个宏默认是不打开的,具体就不分析了,简要说下这个宏的作用。# V- t) f+ }8 W3 F6 L1 I5 V3 u

$ M. ^! X7 _+ k这个宏的作用是使能时钟门控功能,这个功能在不需要MMC控制器工作的时候,停止MMC控制器,以节省功耗。
" \; T, D# u; h$ R" I/ a& H9 X4 F* T/ g% \9 S! h9 i
随后会调用led_trigger_event触发led事件,这个牵涉到Led子系统,就不进行说明了。
5 [0 w, H/ E' I) f% ?2 Q7 }: K3 @: J9 c; f
顺便提一句,s3c2440的mmc控制器驱动并没有使用这个led触发功能,也就是说host->led是为空的。1 N9 D# V4 J/ s2 U0 g
! \& h  f/ E. a6 J
最后调用了mmc控制器驱动提供的request方法发送request。
" R9 G3 w* t% Y! R
4 E+ E& S6 @* S( P这里需要注意下函数指针的形参:一个为host表示mmc控制器,一个为mrq表示request(请求)。9 Z  v1 a4 C4 s

* H( p. J& p( a# y6 |# \很显然,要求host指向的mmc控制器发送mrq指向的请求,同时,也可以看出所有传递到mmc控制器驱动的请求都是使用struct mmc_request结构体进行封装的。
3 P+ ^- E) K) I+ s( V! l4 j- K- W0 j
* U! X9 T1 {; k' A至此,第一步完成,接着我们来看第二步:
9 @% K% |9 @, t+ ~: |1 g4 S2 P
8 F4 w$ ^4 B9 b: i9 v$ N" J3 O
' b% Q  H( a  e5 E" [; ~) Ystatic void mmc_wait_for_req_done(struct mmc_host *host,( [7 d4 i3 X" D5 B5 y
                                  struct mmc_request *mrq)6 ?' u0 N+ P5 ]/ w8 E# I5 R
{
7 H/ P  w" |$ q" K+ p        struct mmc_command *cmd;
# S! |. f2 d2 s3 ]' N& |
9 X2 E) M2 \" R' T4 F3 ~+ d        while (1) {4 ?5 x) H3 p7 z; C- }
                wait_for_completion(&mrq->completion);: j) R) i" O% d/ w3 l& H$ d/ M+ g
' |! O3 `1 D8 ~, V2 u: M7 M
                cmd = mrq->cmd;" Z9 Q' Q$ N- \

% W! ]7 ?) [8 ^( T3 Q- R2 {+ c                /*  ~0 f8 L& E; t# G
                 * If host has timed out waiting for the sanitize
8 |6 T9 n5 F! R* p/ w                 * to complete, card might be still in programming state+ Z* E* n6 t: f+ d, A3 p
                 * so let's try to bring the card out of programming. D: W& H2 s! ?. ?
                 * state.
; G1 D" e# _) V# ^8 f- ~                 */. `% @6 B& t3 m4 d+ v
                if (cmd->sanitize_busy && cmd->error == -ETIMEDOUT) {
1 I3 M0 l& ?0 ]" O                        if (!mmc_interrupt_hpi(host->card)) {
. \! f! v7 g% r( e% G6 d0 A                                pr_warning("%s: %s: Interrupted sanitize\n",) f/ m- y% A3 n4 [' `' S
                                           mmc_hostname(host), __func__);  K" g; X& q+ M) |* s
                                cmd->error = 0;
" x: t9 e9 ?! D+ ]6 p1 N+ H, n; q. K0 `                                break;' `( E& V) y& m  M6 D
                        } else {
; u7 @6 T9 F# S& Y4 ]; I                                pr_err("%s: %s: Failed to interrupt sanitize\n",
5 n1 O5 m. `3 a) d0 c6 n7 ]                                       mmc_hostname(host), __func__);& T7 w. Y5 }  L3 s
                        }
4 d+ k& v) l) Y* K. O) ?  t) W                }" x& b/ ?4 m+ w
                if (!cmd->error || !cmd->retries ||; b& t$ _( }( F# k- Z6 Q
                    mmc_card_removed(host->card))
( A" V& y5 G+ }& ]  B* I                        break;
0 c1 Y7 g" r" h; K! [
, H" Q: c+ K! U% D- r- t, N                pr_debug("%s: req failed (CMD%u): %d, retrying...\n",, u7 `. m* T3 T) J; L! A9 V7 R
                         mmc_hostname(host), cmd->opcode, cmd->error);6 s. H+ \! H$ G% c( a8 m8 @
                cmd->retries--;1 K& P, Z% e3 a- w
                cmd->error = 0;
+ P+ i; J3 Y' x9 E7 m3 d4 z5 t) C                /* 没有成功,尝试再次发送request*/
+ {0 B4 W2 \' o; I' L" V                host->ops->request(host, mrq);
; d9 ?- s* V- u/ e- p        }2 B; F/ `, s% D* G* D; ^' G' m2 Y0 a( c
}
. e- P/ `0 a* x. T* {( E% m6 T这个函数首先调用了wait_for_completion来等待mmc控制器驱动调用mmc_wait_done来唤醒自己。
  H' k' |) J" z2 N  e被唤醒后会执行一系列检查,如果request成功发送,则会break,并直接返回。
$ w6 i' F  |8 a( K3 n# Z4 \. W2 r# d" `1 J# R9 a" R/ F. y0 U
如果没有发送成功,只要retries非0,则会尝试再次调用mmc控制器驱动的request方法再次发送。$ |7 ]" S# o! p, W" Y8 y

' z9 ~+ O. L7 z2 R4.2 CMD和ACMD发送函数
( O/ V. \8 ]: u7 r  y2 m* i通过4.1小结,我们知道MMC核心层如何将request交给MMC控制器驱动,并由后者发送该request给sd卡。/ f; s+ S) J5 a* F* _; r- r8 V
1 ~' h; m) X$ E6 }( A) ]9 d
通过SD卡规范,我们知道有两种形式的命令,一种为CMD,而另一种为ACMD。
5 P  j5 ]5 [* F8 [6 Z
( C+ X+ Y" d) K' p) sMMC子系统提供了两个函数来完成这两命令的发送,分别是mmc_wait_for_cmd和mmc_wait_for_app_cmd。
4 B" o) J- k2 a0 p
1 V+ m& P8 N) A, y先来看下CMD的发送函数:5 ^) w6 F, \4 m/ z
' C) l8 T, n& H3 `# ]$ S* ^6 u
下列代码位于linux/drivers/mmc/core/core.c。7 S0 X) X1 l0 _" M" M1 J- @9 s
+ ]: M- C, P* t" a% p
/**
1 Z: X6 r. Q- R *        mmc_wait_for_cmd - start a command and wait for completion: D% o( V! @$ z! M0 \/ r7 o+ ?
*        @host: MMC host to start command
& e( }( s$ }- V' p( k& L *        @cmd: MMC command to start) o" @9 v5 H! R, h1 z0 H7 O( j( E- h# C
*        @retries: maximum number of retries# P! U& [& B% U( Z
*
# R! |  E1 R+ u4 q; v% |0 N9 f  E *        Start a new MMC command for a host, and wait for the command
' J, h  L. `" j1 k5 a/ M7 p& w' Y *        to complete.  Return any error that occurred while the command
, M; e6 \# J" g9 C# u/ V) J+ [! ?$ \ *        was executing.  Do not attempt to parse the response.. P) w' {9 I# l3 H0 {, I; A
*/1 j5 A) z) E0 c% B' n8 }" l. s& `
int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries)' v9 T7 \9 B' t  `1 Q/ ]" s
{& n, a- x' _/ T# W  G+ Q8 J: G
        struct mmc_request mrq = {NULL};
3 b( e0 U: [- n$ B, i" C, O
& X# |! Z1 y: R- [        WARN_ON(!host->claimed);" u' D0 {, a% p9 ^& a# H. s1 w
; ]: L! E( X/ H
        /* 清空应答 *// O9 b8 p& v( N3 k. i* P
        memset(cmd->resp, 0, sizeof(cmd->resp));
& E: Q8 n+ U, U" f) l5 Z  `        cmd->retries = retries;( A# ~/ Q+ ]  D2 U9 }

& V! m) D9 M6 i        /* 保存命令*/+ G5 ^& h6 q/ X; ^1 J- B
        mrq.cmd = cmd;
+ e" j* r* P2 s6 l        cmd->data = NULL;
' w& A2 W3 H! q8 j) |- o: m + n5 B8 i: k# |/ v  r0 Q9 u7 n9 Q$ `
        /* 发送命令并等待response */8 Z/ c2 I! m( m
        mmc_wait_for_req(host, &mrq);
7 f3 l1 M1 S; t; O
4 Q) [8 ]' m% I5 o+ u        return cmd->error;
; F* k4 R- I( t* t3 C. W$ ]8 x}/ W& O* ^- _. d# T7 q, P+ b
有了4.1小结的分析,这个函数还是比较简单的。
3 a$ |& Q, ^( d: P! s( S/ l# z$ W- b0 V该函数首先清空命令的应答数据(resp),并保存命令(cmd)到mrq中,随后调用4.1小节中的mmc_wait_for_req函数发送CMD。
1 T" B+ t1 w0 Y8 E% _5 T* Q1 @2 _! h& C' [5 T
从这个函数的形参我们可以看出:所有需要发送的CMD都由mmc_command进行封装,在函数内部被mmc_request
1 X4 Y) w5 h$ `5 |1 B' O结构体进行再次封装,并将mmc_request交给MMC控制器驱动完成CMD的发送。( a8 P9 c) c; U

7 n* H1 E; {+ s  ^; b" w$ Y接着看下ACMD命令的发送函数mmc_wait_for_app_cmd:
% F  ?0 F' A% D1 X* S" H8 y- L4 W6 l" i$ k! `( y
下列代码位于Linux/drivers/mmc/core/sd_ops.h。
! ^7 Y& Z7 T! Y4 R; g) u( J# ~0 h/ w- x8 Z3 e
/**( `6 |* z$ ~. U+ d* G
*        mmc_wait_for_app_cmd - start an application command and wait for
  K+ q+ S9 e1 K7 f4 ?                                completion
) r' `& `" o9 V0 G' r* G9 M *        @host: MMC host to start command
" t/ \9 }9 O; Q3 ^ *        @card: Card to send MMC_APP_CMD to
7 G. R9 {% B& x7 U3 n *        @cmd: MMC command to start1 z2 f6 o' L9 |; k  b
*        @retries: maximum number of retries
+ d( f9 U1 N6 Z+ F+ s! W *
( ~! N  o1 q1 B  F4 F2 z8 }) t *        Sends a MMC_APP_CMD, checks the card response, sends the command) e8 ~. M7 ~" v0 t# v
*        in the parameter and waits for it to complete. Return any error
+ _. Q* }$ |# \7 p *        that occurred while the command was executing.  Do not attempt to3 F5 X, @- Q6 G
*        parse the response.
: E! T5 \+ {! H */
# j# |7 t/ j5 J: u' lint mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card,: K" N8 B( b& I0 E* W% P8 ]
        struct mmc_command *cmd, int retries)+ v6 A: S% B% a; P: L  e, r
{
5 j" f+ T2 c3 ^7 C        struct mmc_request mrq = {NULL};
% i3 v, g, E. y1 m* H
5 [" H. n) e  F: R$ i) a        int i, err;8 z; O5 @! U7 q; I. {5 m) K

# \- u3 n' Q: M4 h        BUG_ON(!cmd);! j" h9 t% |( @/ B
        BUG_ON(retries < 0);
  Y+ Q# I" e% Q! F+ ] 8 F0 _' [1 |0 u& z% K* C5 w
        err = -EIO;7 U) d, p: n' S) d! r: e
) W: O: `! v/ S% h2 l* t
        /*- Z6 ?' b! ^# |; M; r, f
         * We have to resend MMC_APP_CMD for each attempt so8 r5 e$ h! V$ R% }+ n6 ~& D( d: b
         * we cannot use the retries field in mmc_command.
: U' I0 z. \% q" Y8 m1 L- ^$ {         */3 @: r& E5 a4 Z  D4 ?7 r
        for (i = 0;i <= retries;i++) {- X; h) y; c+ H! m6 B* t
                /* 发送CMD55*/+ a8 |; B# s$ o8 f) |! B# I
                err = mmc_app_cmd(host, card);+ Y- l( A$ s% g/ [1 H& E
                if (err) {: X/ ^1 w3 {" t
                        /* no point in retrying; no APP commands allowed */9 m% [, `: b: W8 ]# ]
                        if (mmc_host_is_spi(host)) {
& q. v* ^0 @8 _# j                                if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)" K  a/ m3 d* l9 a! u2 t
                                        break;5 k  J3 W1 g8 K" a. z1 p
                        }
, |# v! N# N9 v$ ^, B" O& c8 s                        continue;
9 `8 k- g. X! v% ^6 l0 V                }' p' _' Q8 V* o  k! c
+ I$ g0 ~: @4 g3 e* P7 T/ P* x
                memset(&mrq, 0, sizeof(struct mmc_request));
1 h" s$ f5 O1 N$ K3 k. x( C
! o& F: |& l* i7 v! n/ G0 c4 L                memset(cmd->resp, 0, sizeof(cmd->resp));
* i$ [3 ^( j( K% U( B                cmd->retries = 0;4 o1 m% ]# k9 ?( g- e4 E

% k; {  N5 A& m. C; W% x! X/ s) d- d                mrq.cmd = cmd;8 T4 @' p+ L* s& e, B. P7 H/ Z
                cmd->data = NULL;1 O0 W  g- f: m$ T" _

( p" B- T2 e; e5 L                /* 发送ACMDx*/
: e% d- U+ h, g6 Q$ j                mmc_wait_for_req(host, &mrq);3 {! I& i% q! e8 R. x, A; `) C

: y0 s! \; N. P- v- }/ S! t                err = cmd->error;
$ r6 f/ m2 c4 V/ h1 ~! [. Q2 o                /* 发送成功,直接break并返回*/
- U" c1 P, \. M' I0 A) [6 e                if (!cmd->error): P! ?7 K( Y! _" ?. h. Y6 |# a
                        break;1 W, V2 y* p2 z! r) q0 n; `) x* B
; k& D7 o% K' [9 d2 j
                /* no point in retrying illegal APP commands */
& i2 v7 ^3 L$ Q& d4 w                if (mmc_host_is_spi(host)) {
$ a" K- t  d4 c1 ~; o" s) {) T5 J                        if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND): L$ e! O$ m2 @% ]
                                break;
& x& i4 X" ^2 o# u6 ]1 y                }
3 S: z' J3 c% ?# Z0 M3 ]        }
. O. w' K4 j8 M' L8 B  _ " ]- ~# L6 d* w3 f9 G. ?
        return err;( h; s7 W/ \) _
}
( l/ v  n& j' h: \! W ' m* o0 V, G% N$ r1 c
EXPORT_SYMBOL(mmc_wait_for_app_cmd);
9 q% v  J5 O) r% s) J该函数的形参cmd保存了代发送的ACMD命令。8 A. U  Y, I6 J, ]) ?; K% i
根据SD卡规范的要求:在发送ACMD命令只前,需要发送CMD55,以表示后面一个命令为AMD命令。. Z3 E8 f  ^% @6 u

1 u8 Q. A" A: v2 X/ G8 `9 T) D所以,该函数首先调用mmc_app_cmd函数来发送CMD55命令,我们来看下这个函数:! n5 z. W# T3 `9 p3 t
7 ]3 J4 N' V9 J. Q+ R7 ^! W* @
int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card)9 B& j* Y5 n  n* h( n, t
{
9 y! f# f8 ]. x4 S* |        int err;. J( ]! p9 U; ]  o% T% R
        struct mmc_command cmd = {0};
- V0 n! m+ H7 E ' y9 P! A1 T7 e9 n: t
        BUG_ON(!host);* F7 O1 t% z+ P0 `; X
        BUG_ON(card && (card->host != host));  d0 K, _5 t1 T5 ]9 W% I
) W, c; Q3 |* J+ y
        cmd.opcode = MMC_APP_CMD;        /* CMD55 */
' z7 S1 F( _, H. t. r  ?2 K1 ^ / E* j9 Y6 @) l& g" ?# Z
        if (card) {- D8 U" y' }/ ~: [9 R7 a
                cmd.arg = card->rca << 16;        /* 卡地址*/% B& f. P5 |8 ~6 [  e" F2 L1 B  C
                cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC;
  g. L& G( V( ]; E- d        } else {4 b9 T6 `8 k) T+ k2 |
                cmd.arg = 0;        /* 卡地址*/+ w4 w( b' a# d! W2 v; y
                cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_BCR;
+ [' J. W: _- j; V$ d        }2 u$ V! C, q5 a
6 A1 M1 W, t+ d( P2 b  ^
        /* 发送cmd并等待(阻塞方式)*/
; Y4 h8 R2 U/ {# A2 j% R        err = mmc_wait_for_cmd(host, &cmd, 0);$ T. x. v. `( [& J2 `
        if (err)
( h. P" h. x+ ]                return err;+ W, C9 m8 Q+ a" B
7 D# y2 k; i, f9 _4 f) F8 R
        /* Check that card supported application commands */: e3 m& N# k" e( }4 h5 s! d
        /* 检查card status第5位,判断SD卡是否支持ACMD*/; H7 V0 ^7 S! I* u: B7 x! w
        if (!mmc_host_is_spi(host) && !(cmd.resp[0] & R1_APP_CMD))
, i8 e4 }* o! @/ k                return -EOPNOTSUPP;7 B* ^# m# V  p8 C
( ~' j* ~0 c4 {+ D6 M
        return 0;
  O( q; K3 U2 f: g}
4 k4 X1 ?7 U1 B9 C5 g( Y% bEXPORT_SYMBOL_GPL(mmc_app_cmd);$ R* s1 Y/ v: ^  j
先来看下SD规范中关于CMD55的说明:
+ g$ w/ I3 H* B$ q
* o' k' y7 q7 ]. ?3 n( n9 z% c7 M, H
从上述命令说明中,我们可以看出:6 J7 h$ J0 x+ R: L, [% h

2 s7 h9 ]6 a. ?" C  F% d1 y7 K" G. ^8 m1)该命令为ac类型命令,也就是点对点命令,并且在DAT信号线上没有数据传输。8 f' R7 M; K" ^9 I
0 ~2 q4 ~: W  ~: {+ L- ?' {
2)其次,该命令的参数(31位至16位)为RCA,也就是卡的地址。
5 B9 N: a' e8 H2 p! l% f! l+ ~) k3)最后,命令的应答数据格式为R1。3 j) @) X" h- R6 h! E0 F# c

0 e3 ^: B1 K# u5 @/ ]回到函数中。
3 k; c2 c; y. B8 K/ H( p) d
1 Z% O( p5 w, X$ ~& Wcmd.arg为发送命令的参数,函数首先设置了命令的参数为sd卡地址(RCA),这符合上面的描述。& E; N+ u: r9 g, e1 l2 V

% V  W5 E  n$ r1 x; l8 Q随后调用了之前分析的mmc_wait_for_cmd函数发送CMD55命令。/ n9 u2 W, f3 P! k5 _$ h9 E. h
3 m. m3 C6 R' k6 o* y  C% ]$ @( j
上面提到CMD55命令的响应为R1,其格式如下:
% U! v/ A: H2 V; T+ `1 R/ b# _- \) I4 F6 z" b% ?- o
; E' k5 ?) i: |$ J9 x3 W5 q$ h

( A% R9 r: i, V# n/ O9 ?4 W! T% g9 N其中32bit的card status作为响应数据被保存在resp数组中。
+ u' L0 _7 ?$ l: z: T
9 o9 }) o6 n; G6 k2 [3 S; g1 M" Ncard status的具体位定义请查看SD规范的4.10.1小结。9 A: B7 n% H3 l; v* `* U
2 u# E! }- E, `8 s
最后检查CMD55的响应来判断SD卡是否支持ACMD命令。
# m; ]6 B7 i- E. V% j
# ^6 g& v7 W- v4 \/ Z' J; ICMD55发送成功后,返回到mmc_wait_for_app_cmd函数中。
' K1 B/ N8 j1 ]5 U* D' x8 j5 h( R# K* `
接着,cmd被保存到mrq.cmd 中,并调用mmc_wait_for_req中发送ACMD命令。
& a) K" Y" f1 f- ~3 C7 k
2 |- a! A% ]& ?, l' f! X
) c$ f: ^$ {" q9 k* d( z& w6 v) t五、小结- N6 r) w% Q/ L/ K$ c
    本问主要对MMC子系统架构进行了简单的介绍,并给出了一些关键数据结构。同时,对MMC子系统的初始化过程进行了简单分析,最后,重点介绍了CMD和ACMD命令的发送函数。
; \- q9 S) P! p* L5 l! R& F& h% J* |" ^3 h; R, D( M% @% o, ]4 N

, z7 u  A- b% I6 h9 f2 j
2 n- S( T! D0 `) k
: Y. t' V- I1 q8 ~  \2 k( Z+ p+ P7 B6 b6 o0 p
) N! g. C- R+ w. J+ }

; ~  a# e, d$ }  z

该用户从未签到

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

本版积分规则

关闭

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

EDA365公众号

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

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

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

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

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