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

基于S3C2440的嵌入式Linux驱动——AT24C02(EEPROM I2C接口)驱动解读

[复制链接]
  • TA的每日心情

    2019-11-20 15:22
  • 签到天数: 2 天

    [LV.1]初来乍到

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

    EDA365欢迎您登录!

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

    x
    本文将介绍Linux中AT24C02驱动。AT24C02是一种EEPROM,使用I2C接口来访问。# _0 B8 H4 P0 J5 t- d* `; c, t

    5 Q3 M* p6 h3 ^  l0 b在开发板中,使用I2C控制器0和AT24C02连接,这里就不给出原理图了,如需要,可以搜索TQ2440开发板的原理图。
    9 q0 y3 j9 r+ s2 Y3 P; Y" F
    ( [, N/ N! o# y, w6 H目标平台:TQ2440 $ |6 p2 @$ W2 m( m

    " ?5 x, z6 r9 t- H9 o. I9 cCPU:s3c2440
    " N) a2 r4 [# x. Q0 U2 M
    , o7 I3 s+ X& ]" B% Y内核版本:2.6.32! G+ w. Q# @" W, t, [1 a  k* U" s
    ( g: ]0 @' i7 |, m" g# S; o5 B
    本文所有的代码均位于内核源码:linux/drivers/misc/eeprom/at24.c中。
    6 L4 M! g9 Q/ i$ \# V6 F9 N( Q: a
    : [) s% a0 |5 M# |+ B1. 模块注册和注销
    . w) u0 s/ V. @% n8 S- k2 ~static int __init at24_init(void)
    / n7 u* C$ ]! b{8 o6 X; D' _* v: ]( N* U  ]+ z
            /* 将io_limit向下圆整到最近的2的幂*/
    # p2 O1 C7 r/ n! p0 Z        io_limit = rounddown_pow_of_two(io_limit);
    , c+ C3 M4 k& w5 n8 j, s! ]        return i2c_add_driver(&at24_driver); /* i2c 驱动注册*/* y" \9 u" L' d3 ?- a
    }
    9 x7 l/ b3 e# r# n! b4 Q' kmodule_init(at24_init);
    . h' O' C  p& D% e0 q
    0 U8 g" M+ X9 Astatic void __exit at24_exit(void)3 k" ~# S- T# ~* i+ X5 c; U
    {% D+ Q+ l' b. r
            i2c_del_driver(&at24_driver);! A, W+ M0 h) U0 C" x2 l
    }2 r2 |& M1 ~8 U) e
    module_exit(at24_exit);7 O; S' a; |' k/ d( y, ?
    1 w5 d4 p, T/ i# C" v  ]3 e
    MODULE_DESCRIPTION("Driver for most I2C EEPROMs");$ ^4 R0 D7 `0 ~. a& \
    MODULE_AUTHOR("David Brownell and Wolfram Sang");7 E4 X2 }( f3 S4 O! x4 Y! j* O
    MODULE_LICENSE("GPL");
    $ ^# v  J# r, ]7 j& o0 o3 E0 L4 K) }# I3 p7 l1 d
    * G5 d* V) e6 d0 ?) @
    注册函数很简单。io_limit为写入时允许一次写入的最大字节,该参数为驱动模块参数,可由用户设置,默认值为128字节。
    1 R; m( J4 m7 C
    " z" b- D0 O1 d3 }  l$ Q首先对io_limit向下圆整到最近的2的幂,接着直接调用了i2c_add_driver来注册一个i2c驱动。0 x2 d" s2 m. K. K
    $ i5 e3 p6 e9 h+ l5 M' V
    注销函数更简单。注销之前注册的i2c驱动。
    ; l$ d! N/ r4 s8 ~* P% t
    / ~" n  N, J' a& l' s; Z% C
    4 k+ k0 c- Z- x; ~5 ?. G. g  J9 p( a9 ~  I; Z3 b
    2. 设备驱动绑定2 a# w* @; @) u8 P4 J5 ?. d& [# a( t
    熟悉I2C驱动架构的可能会知道I2C驱动的match函数,该函数将使用id表(struct i2c_device_id)和i2c设备(struct i2c_client)进行匹配,判断是否有name字段相同,如果相同则匹配完成,即可完成设备和驱动的绑定,接着便会调用驱动提供的probe方法。我们来看下驱动提供的id表。) E1 O& F/ P2 E# m$ w, N' z! G
    7 ^1 D0 J4 [0 G- u  [6 W- C

    / T, f' B: W) m2 @static struct i2c_driver at24_driver = {# n- C6 E$ o( s( s0 ?  V8 K
            .driver = {3 d0 }* e% v1 @6 X- e: r! c
                    .name = "at24",
    3 M% L/ y: a; |, P3 }                .owner = THIS_MODULE,6 M$ `5 J! d3 C9 J+ R) G- V2 q2 q0 u
            },( h- `9 O: Z* `1 }! Z0 y5 J
            .probe = at24_probe,
    2 g2 p1 \7 @6 h/ q  a3 M        .remove = __devexit_p(at24_remove),* G% U6 @9 b9 [! D- y7 f/ f
            .id_table = at24_ids,8 w, m5 _8 u# m, [8 L1 R2 l
    };
    9 G9 \+ t0 I4 o( y. L* x& p9 }* f1 S驱动提供的id为at24_ids,如下:. k( T+ ?# [; X1 n) n
    ' `  h( H5 `; e% D! ~/ H1 V
    ; @) s, f7 c. u7 p$ M# H" y
    static const struct i2c_device_id at24_ids[] = {; s" ~5 s' h% g9 R( h5 y% q
            /* needs 8 addresses as A0-A2 are ignored */
    1 A4 G$ ]. ?8 o. N        { "24c00", AT24_DEVICE_MAGIC(128 / 8, AT24_FLAG_TAKE8ADDR) },
    + O) Y+ T. v5 d* W  V        /* old variants can't be handled with this generic entry! */
    / j: |; L' W# F1 V: }& U- k0 o" d  q# u        { "24c01", AT24_DEVICE_MAGIC(1024 / 8, 0) },, U; b0 J7 o5 n% ]6 L4 S
            { "24c02", AT24_DEVICE_MAGIC(2048 / 8, 0) },( T4 T8 N4 L" S" F/ t
            /* spd is a 24c02 in memory DIMMs */4 v& ~0 g# J1 M
            { "spd", AT24_DEVICE_MAGIC(2048 / 8,
    ! k& c. v( U0 V" ~                AT24_FLAG_READONLY | AT24_FLAG_IRUGO) },) c1 z' o4 Z" `4 ]' i1 S. J
            { "24c04", AT24_DEVICE_MAGIC(4096 / 8, 0) },2 F# ^: y, m8 d. E/ N' P
            /* 24RF08 quirk is handled at i2c-core */
    4 s& F3 t% U% Y" f- o8 ^        { "24c08", AT24_DEVICE_MAGIC(8192 / 8, 0) },( \( T/ }4 \1 G9 f
            { "24c16", AT24_DEVICE_MAGIC(16384 / 8, 0) },
      k, A- W( C8 t, F1 G; K        { "24c32", AT24_DEVICE_MAGIC(32768 / 8, AT24_FLAG_ADDR16) },
    " \' n/ h6 H$ ?+ {        { "24c64", AT24_DEVICE_MAGIC(65536 / 8, AT24_FLAG_ADDR16) },% H- T# U' s5 J) o5 n* X
            { "24c128", AT24_DEVICE_MAGIC(131072 / 8, AT24_FLAG_ADDR16) },
    / m& e1 r* F3 _$ w$ j        { "24c256", AT24_DEVICE_MAGIC(262144 / 8, AT24_FLAG_ADDR16) },+ B5 r& S9 }+ c1 \
            { "24c512", AT24_DEVICE_MAGIC(524288 / 8, AT24_FLAG_ADDR16) },; c+ a& T# z" K4 P
            { "24c1024", AT24_DEVICE_MAGIC(1048576 / 8, AT24_FLAG_ADDR16) },
    9 b$ L0 G* Y; s5 c8 j/ M6 `        { "at24", 0 },
    ! T4 j# A5 {1 Q2 F$ ?6 J2 `        { /* END OF LIST */ }# m& l* Y0 i* s# S9 ^9 b
    };- z" P% m9 ~( M3 G3 g, T+ t5 k
    结构体成员的第一个参数即为name,表示设备的名字。第二个参数,在该驱动中,为一个幻术(magic),通过AT24_DEVICE_MAGIC宏计算。
    " t9 R# @, P/ U! R, D4 M4 Y/ Y( s$ p9 g/ _
    宏第一个参数为eeprom的大小,第二参数为一些标志位。我们看下这个宏:
    4 {3 w. V8 r3 V# I+ q5 b' ?) _& q7 w: [0 s8 w2 |

    ) {$ Q  [" M, a5 e" h/ ^, C#define AT24_SIZE_BYTELEN 5: V, }. l. K, a# z) C
    #define AT24_SIZE_FLAGS 8
    3 H% N! g5 N" s4 t
    6 p3 M1 s! }4 ?1 y9 j9 T9 a/* create non-zero magic value for given eeprom parameters */7 [; I+ u6 J. |# W, `# N8 y, ^
    #define AT24_DEVICE_MAGIC(_len, _flags)                 \) a& Z5 `4 a) w8 P3 r( }' h2 z
            ((1 << AT24_SIZE_FLAGS | (_flags))                 \
    & N* ^4 N/ |$ e5 `' x            << AT24_SIZE_BYTELEN | ilog2(_len))" m' [7 O3 H* x
    在这个表中,针对这里讲解的24c02,其大小为256字节,标志位为空。& n" k# T+ W0 T3 C/ b0 w
    ! c* J" P& @& ~4 B- y: I4 F! T

    . {; r, L4 R6 i0 D# v( ]& K, f( N, S: B$ N! P6 R8 }
    3.probe函数
    8 n5 g3 t2 R; j6 t$ [" Q    当i2c总线完成设备驱动绑定后,就会调用probe方法了。具体看下这个函数。
    - e+ G4 L! w, }& s% a9 S! i% U7 w
    ) T1 b3 o( g/ L; z+ w! k0 }
    static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id). Q$ Q* s4 Z( _
    {, H/ d, K! N# Z0 f5 B
            struct at24_platform_data chip;
    1 u" w) q, U. S/ @6 Q8 w  o% `        bool writable;6 ?# I% N6 s! R# V' m
            bool use_smbus = false;
    + q8 v5 Z8 o2 s5 u& ?  X        struct at24_data *at24;! G5 m. F9 i* P0 S3 }* ?& z
            int err;3 W8 j" E( y, D0 y
            unsigned i, num_addresses;+ }' G, H' S) l: j/ N
            kernel_ulong_t magic;' p2 W) U2 o7 J

    ) `# S: z) T; B9 d; v7 r5 B' x        /* 获取板级设备信息*/" i" ~& A& [9 j+ }
            if (client->dev.platform_data) {! c& n" c2 l: S) ?( M" C
                    chip = *(struct at24_platform_data *)client->dev.platform_data;
    , x2 O, E4 @' V0 b- D        } else {
      `2 d  J% X3 T0 A                /* 没有板级设备信息,也没有driver_data,直接出错*/- A7 |' c* {, B$ |. H( Z
                    if (!id->driver_data) {, r& V/ S, ~  V4 b: P
                            err = -ENODEV;
    ) d/ g$ [# V9 C& D, N' t& f                        goto err_out;! o* @% h0 |0 N) o  [) L' B; S2 T& E
                    }; c- z, l* f# O; x3 b3 B5 Z
                    magic = id->driver_data;3 n  b  k. b: G, M1 q
                    chip.byte_len = BIT(magic & AT24_BITMASK(AT24_SIZE_BYTELEN));* b1 B7 p/ |3 g( f
                    magic >>= AT24_SIZE_BYTELEN;4 c- z6 O3 M& D0 @6 C
                    chip.flags = magic & AT24_BITMASK(AT24_SIZE_FLAGS);' `/ b! }; G: s* X
                    /*# E  q& u* z: E: x/ G
                     * This is slow, but we can't know all eeproms, so we better6 L5 s8 W* o) E, _" g
                     * play safe. Specifying custom eeprom-types via platform_data  E3 s" r1 m5 T0 i2 o0 K
                     * is recommended anyhow.
    : j( }6 \0 V+ o% }9 b$ _/ n+ u+ V( @                 */
    . b! p7 V3 R2 J! w( K# h6 X; F                chip.page_size = 1;
    - }2 K/ ?- h5 L5 f, d$ N: K
    0 h5 O3 y3 N- m* _) D9 o/ {, |                chip.setup = NULL;
    ) h- A' U1 h; D5 K, I5 J& G                chip.context = NULL;
    : S% {! Y0 G4 E7 q, F) Z8 @) S2 c  O        }
    ! W9 i1 F; p5 w) l% U( y) E- T  i  g3 g2 l. e
            /* 检查参数,
    4 P" n0 a4 T- N5 a0 J            byte_len和page_size必须为2的幂,不是则打印警告*/
    + Q1 c- C& M" C# N        if (!is_power_of_2(chip.byte_len))9 A) h& E0 q* g/ J
                    dev_warn(&client->dev,& P3 k+ u1 |3 G8 \$ D
                            "byte_len looks suspicious (no power of 2)!\n");
    ' Q& |2 X% V* c1 q! F8 P        if (!is_power_of_2(chip.page_size))
    5 k7 x8 @$ I5 r" f4 j                dev_warn(&client->dev,5 t* _. h0 a7 @9 O# |% C$ h* M& [
                            "page_size looks suspicious (no power of 2)!\n");2 L: y5 ~- G1 b* K$ w
    0 ]- p# B% h) J5 b+ s0 o% \
            /* Use I2C operations unless we're stuck with SMBus extensions. */
    * M4 v8 d4 F3 U8 |        /* 检查是否支持I2C协议," E4 D/ ?# M" D1 o: [2 i' h
                如果不支持,则检查是否使用SMBUS协议*/
    9 q, U5 H+ i6 S& b0 X6 C        if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
    * D' [3 R7 C; V" [* z6 p3 _% G                /* 不支持I2C协议,但是使用16位地址,出错*/
    ) D- v- A5 L+ L  k4 u# `! C: ^                if (chip.flags & AT24_FLAG_ADDR16) {
    % _3 J2 W& v$ R1 L8 x6 N                        err = -EPFNOSUPPORT;. @2 V9 [  b+ i7 F3 ~% N0 h
                            goto err_out;0 j, Y. v  q3 J1 h  Y
                    }$ w0 U9 T: z: S/ G1 h
                    /*  不支持I2C协议,使用8位地址,
    ! v; s  ~. D6 v3 q' _5 G$ L3 U                   但是不支持I2C_FUNC_SMBUS_READ_I2C_BLOCK,出错*/' P/ K' W# W0 o& K$ o
                    if (!i2c_check_functionality(client->adapter,
      G" H' `. i$ ~* g, `) B+ _                                I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {# q& x2 H9 v2 n  {2 O) p
                            err = -EPFNOSUPPORT;
    & \! Y+ ~0 Y) _6 J. i                        goto err_out;
    6 _# y0 N  n9 V8 r3 M                }+ T/ W; k: W; w% \' Y8 W8 z  {
                    use_smbus = true; /*使用 SMBUS协议*/0 l7 v3 Y" ]& S7 |
            }8 @! `! H8 b9 c/ k) }0 B& l( G# l/ A

    ; K1 e; p# w" I) O' e        /*是否使用8个地址,根据id表,0 \6 K9 B  L+ O
                目前只有AT24C00使用8个地址,其他都为1个*/1 O8 G0 ^2 L! V- N. ]# w
            if (chip.flags & AT24_FLAG_TAKE8ADDR)" ~7 X; u0 r) L4 J- f9 B5 i0 \
                    num_addresses = 8;2 d, H1 r( S$ k: T4 F
            else
    " h! h( O: X# `. v1 c) p                /* 24C02需要1个地址,24C04为2个,以此类推*// @) }5 u4 E3 p, A2 D! W# z
                    num_addresses =        DIV_ROUND_UP(chip.byte_len,) X/ {4 s2 u, H# P; e: ~* h
                            (chip.flags & AT24_FLAG_ADDR16) ? 65536 : 256);- z$ m, x8 A+ u" ~9 V, [

    9 m$ Y; e$ {1 v* o! ^8 J+ ?        /* 分配struct at24_data,同时根据地址个数分配struct i2c_client*/- r, R! {7 i2 Y& i
            at24 = kzalloc(sizeof(struct at24_data) +0 j% X: d1 G' c5 r
                    num_addresses * sizeof(struct i2c_client *), GFP_KERNEL);+ c, d, `( p" c8 ?7 c& [1 H% r
            if (!at24) {
    0 d- f5 f( A! f+ h7 o) A. b. V                err = -ENOMEM;
    - ]0 x1 m" v! y) I. @                goto err_out;
    ) o5 h& t2 `6 k5 L: d        }0 M3 i+ T& x/ f5 Q- _1 y& m. F

    9 y2 G- }; ?3 I3 m# I: x        /* 初始化struct at24_data*// W& w6 t! l# \% r/ R
            mutex_init(&at24->lock);
    ' N5 u. `. G* M; B* Z8 G        at24->use_smbus = use_smbus;
    ; R+ h: d% Y0 A- p' e0 n* ~9 q        at24->chip = chip;7 [6 l* A: Y, z& d
            at24->num_addresses = num_addresses;/ f0 O: }- Z6 `# F3 F
    8 u; R! G2 r) \& L* S
            /*2 x$ m* J3 Z8 A, C' D4 m0 m- a
             * Export the EEPROM bytes through sysfs, since that's convenient.
    + B7 r; l$ d- n2 F, x6 \$ q7 E         * By default, only root should see the data (maybe passwords etc)
    * E' _; j; N+ l# g! g7 _2 n         */
    & p& k3 q8 Q" W* T' K/ R         /* 设置bin_attribute字段,二进制文件名为eeprom,- G; y3 c* x8 e; T
                 通过它即可读写设备 */% J6 V9 O% c# H, m& Y  C
            at24->bin.attr.name = "eeprom";
    ; u& ^; S& d' a' h1 D3 N" h        at24->bin.attr.mode = chip.flags & AT24_FLAG_IRUGO ? S_IRUGO : S_IRUSR;
    1 L- u1 B$ H  A  p- K        at24->bin.read = at24_bin_read;
    2 o; i& Q# K4 o. ^! M! R        at24->bin.size = chip.byte_len;
    1 H' s) w" D1 g. R" e0 L! Y- Y4 [
    2 U4 @7 r3 R2 h        at24->macc.read = at24_macc_read;  /***  先忽略***/% X; N" Y: F7 \4 N. m( C% A

    $ x9 t3 e7 I+ @        /* 判断设备是否可写*/
    - m! U! [! F) T4 H! o" e3 M. w        writable = !(chip.flags & AT24_FLAG_READONLY);- D1 \7 p9 [6 q# @8 O: Z4 i, V1 R
            if (writable) {$ n2 I4 e" X) e- D; T
                    if (!use_smbus || i2c_check_functionality(client->adapter,
    6 }7 a: c) a  Q  y' E! E& B% p) b                                I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) {* Y# x$ H% l) ?
    1 _" U; E; ?, D+ \- ]/ m. R
                            unsigned write_max = chip.page_size;4 c8 J8 B& W; y6 K) m

    ; }* U9 a- s, U                        at24->macc.write = at24_macc_write;  /***  先忽略***/
    , Z- }: K: q) ?' O
    3 a) j8 O: {$ u; a7 W, s! g                        at24->bin.write = at24_bin_write;    /* 写函数*/
    + h) p/ v) x7 R) d4 c& U" |5 Q; T                        at24->bin.attr.mode |= S_IWUSR;    /* 文件拥有者可写*/
    # o0 S- F: \$ \8 x! k% B, d3 g. g* g' y6 `" p3 m0 h) `
                            if (write_max > io_limit)  /* 一次最多写io_limit个字节*/
    % o, g! \7 r) r( c* Y; n                                write_max = io_limit;. x. P* m$ B6 H( Y; B- F5 j
                            /* 如果使用smbus,对write_max检查*/
    . ]' V- u2 S& G0 S+ l, `                        if (use_smbus && write_max > I2C_SMBUS_BLOCK_MAX)/ P, X) k/ [$ \
                                    write_max = I2C_SMBUS_BLOCK_MAX;* E; {4 k; U& |& P
                            at24->write_max = write_max;  , e% a! e1 A0 }& e2 r
    3 ~, f- e) }/ {# ]1 H* B  t
                            /* buffer (data + address at the beginning) */8 W9 |( ~% f) @; r5 A# n
                            /* 分配写缓冲区,多余两个字节用于保存寄存器地址*/
    0 |7 i) H0 ]/ V6 f* O6 ^6 T. z3 {3 Y                        at24->writebuf = kmalloc(write_max + 2, GFP_KERNEL);& H2 _/ G8 f3 l8 b. }% t
                            if (!at24->writebuf) {& K  ^- ?7 m8 M% }" k- ], j
                                    err = -ENOMEM;
    " W( h4 d" F" T5 Q8 i- E                                goto err_struct;( _* J/ h2 {4 j6 Y9 K$ s7 [8 E
                            }
    9 Y, o/ H" I% z- n% w  g                } else {; r/ n9 V4 D/ }# R/ F
                            dev_warn(&client->dev,
    - U# l8 o4 @  B, j4 p* C3 n                                "cannot write due to controller restrictions.");: K1 W2 e  `# R
                    }
    8 G; y% O% M2 e# ]* y% g        }
    7 }: Q& z5 }$ K' r0 x/ `7 N
    3 l6 r/ H9 g/ o7 p3 m        at24->client[0] = client;  /* 保存i2c设备client*/
    5 l9 K) |" I+ f4 L6 R# @" P% t  P$ L1 d4 a7 b# D) b( D4 j
            /* use dummy devices for multiple-address chips */
    " t  W+ U6 q4 x8 e        /* 为其余设备地址注册一个dummy设备*/6 v/ v6 c( L% f  y  f
            for (i = 1; i < num_addresses; i++) {! x6 x5 P6 x0 J" F
                    at24->client[i] = i2c_new_dummy(client->adapter,5 z% R0 B. x3 J) Q3 K7 }
                                            client->addr + i);     /* 设备地址每次加1 */
    ( Q  |$ o+ p7 x( o3 Y+ z  n9 b                if (!at24->client[i]) {* `. ?% u0 D. c+ R0 N
                            dev_err(&client->dev, "address 0x%02x unavailable\n",8 v9 x+ }, d. e* t) `
                                            client->addr + i);
    / T* V3 z  X" S) p- ]9 A9 p9 \                        err = -EADDRINUSE;# |2 V' X( F# t5 k+ f# c% ^
                            goto err_clients;& }5 f9 C5 e' p; x( \* p
                    }
    $ Q5 Q% u% H1 e5 c        }% E. N, s2 F* s) f7 `6 p$ k
    ; W7 w8 p! l+ v/ r* {
            /* 创建二进制属性*/5 n* I; z! Q/ q4 \% s
            err = sysfs_create_bin_file(&client->dev.kobj, &at24->bin);6 c' X& B( D2 n: o5 t
            if (err)" I# [( ?5 W5 E% Y
                    goto err_clients;
    : q4 A" `1 u/ z# B2 Q# C! w0 w$ R
    2 |2 G. P* {$ E6 S0 w% g/ A        i2c_set_clientdata(client, at24);  /* 保存驱动数据*/  X1 p/ A3 }* K  D- h9 u

    % g% Q* p# i$ t% x4 V        /* 打印设备信息*/% J" u6 @8 o0 F$ X
            dev_info(&client->dev, "%zu byte %s EEPROM %s\n",5 c" e( _* Z; ?, G4 H4 @2 ]
                    at24->bin.size, client->name,! l$ [" k% p( @% R% h. v( B
                    writable ? "(writable)" : "(read-only)");7 W3 U! A2 l" g) Y; G7 s! j
            dev_dbg(&client->dev,+ Q, f1 e# f4 W0 n
                    "page_size %d, num_addresses %d, write_max %d%s\n",
    ( j; `4 u) i! C2 n                chip.page_size, num_addresses,; f: G6 W7 h: z
                    at24->write_max,
    9 L7 L) x7 V& B+ A# Y1 c                use_smbus ? ", use_smbus" : "");0 j- V: U! T1 b: m. H
    6 U" ?$ Y$ ?. `
            /* export data to kernel code */
    . Q8 K9 D2 {; }# b- p        if (chip.setup)
    2 x: }! J' P) Q5 L" P/ _4 }* \+ {1 B                chip.setup(&at24->macc, chip.context);
    8 Z1 S! @! G. |, k
    1 k. T3 h# Y3 P. b& Q1 P, `: d        return 0;
    ' W6 y: J2 ^+ p- ^5 E, X+ A. ~. u! R* X
    7 Z/ M- g5 F+ u  z8 L6 rerr_clients:
    ! H3 w6 z, |- V1 ]. b9 @        for (i = 1; i < num_addresses; i++), m9 m* O" S! V& M/ l. f( ]; y
                    if (at24->client[i]); Q/ R; K+ ?0 K: K
                            i2c_unregister_device(at24->client[i]);
    ) k" b3 F7 X8 n$ X& |
    , j; h/ k% W( }* a        kfree(at24->writebuf);
    1 S5 R9 i! z8 l1 T" O  T& H: aerr_struct:3 x! x2 A1 y4 h# Q( z
            kfree(at24);
    / \8 _$ v* m8 V' l9 N- S4 Z}
    1 E+ [$ y5 v, Q- `7 `  H驱动首先获取板级设备信息(client->dev.platform_data),我们假设驱动移植时,添加了该板级设备信息。
    1 _6 ^0 S9 {2 f5 L& k1 U9 A/ l判断是使用I2C协议还是SMBus协议。在这里,I2C adpater使用I2C协议。( W. u) R7 y0 t) Q/ k: P4 x+ L: d

      h6 S5 M2 @5 m8 g+ `然后,判断设备需要多少个i2c设备地址。
    , n' J' D. B6 D' T. Z3 n/ \$ C9 p7 [, v: V# c
    这里补充下:根据at24c02的datasheet,设备地址的第1位到第3位,将根据不同的设备来进行设置。
    - I0 _+ C8 }0 Q; e. O0 W- y$ z( @2 R2 q, E5 M! D& ~, i
    例如,如果是at24c04,则设备地址的第1位将用来表示寄存器地址,因为内存大小为512字节,而寄存器地址只有8位(256字节),
    : u  C: {, o8 D0 [- Q
    : V9 l- `8 r6 ?. R/ f需要额外的一位用来表示512字节,因此使用了设备地址当中的一位来实现此目的。具体的请看datasheet。
    6 [* w% {$ E6 t4 @
    " G1 q2 k$ N+ U% h' Y% z9 Z) _/ u这里使用at24c02,num_addresses将为1。: C% [5 A/ g0 E4 K1 e
    # v" \6 m  V- L! Q# i5 T
    接着分配struct at24_data和struct i2c_client指针数组空间。3 d% j0 s/ x1 V0 t9 N* ~7 c
    ! @/ _/ S$ ~' b/ o/ X
    然后对struct at24_data进行了初始化工作。) l' H6 k$ Q4 D1 J7 E5 K8 a

    , C1 |6 p( G- g2 N4 o接着,对二进制属性进行了配置。名字为eeprom,同时配置了其读方法(at24_bin_read),如果设备可写,还将配置其写方法(at24_bin_write)。
    5 s# s/ u$ Q( c. m% p: P. D. {2 F' A
    接下来很重要的一步,如果设备使用多个地址,则需要为所有地址(除了第一个地址)分配一个dummy device,这样这些地址就不会被其他的I2C设备占用了。
    ) x/ o/ i7 N) m" J5 a$ F# u- D( m7 J7 S- _6 g
    最后,向sys文件系统注册了二进制属性文件,通过该二进制文件,用户即可访问该设备。
    + W- k3 p9 k4 n4 W
    ) q7 P# @( g4 g7 F0 T注意:驱动使用了struct memory_accessor的东东,对这个东东不是太了解,所以先忽略,这个东西不影响驱动整体的架构。/ g/ Z6 l2 M* `

    4 F: W# X, g8 l* |
    3 D: O# l( O; q! y
    3 t3 o5 V, y: m- j& [" @2 T4.设备访问方法
    # W/ I8 n/ R+ b   从第3结的分析可知,驱动并没有注册任何字符设备或者杂项设备,只是向sys文件系统注册了一个二进制属性文件。因此要访问设备,必须通过该文件的读写函数来。. T% D0 {8 \* l; z: t3 v

    ' ]1 F/ y' l# ~) u' _3 K5 r- c0 l读写函数在probe函数中指定为at24_bin_write和at24_bin_read,我们来分别看下。
    ; r' g: H0 M% u5 |
    & W( ~* Z; a0 b5 p  z4.1 写函数(at24_bin_write)  y! {& Y% L* l7 s+ c* @
    static ssize_t at24_bin_write(struct kobject *kobj, struct bin_attribute *attr,
    / i+ n1 }* ~3 U5 h! T                char *buf, loff_t off, size_t count)
    5 n+ k% Z8 f( }7 @{: ]. X. U) ^6 p& L: v( S# V5 T& z
            struct at24_data *at24;+ Y/ T. b- h! K. w5 V
    3 O2 o2 b. s' C3 S) U; C3 S7 z8 G
            /* 通过kobj获取device,再获取driver_data */
    4 s: K2 L3 p5 V3 w4 [        at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));/ L+ t+ k* w+ d9 ~5 |
            return at24_write(at24, buf, off, count);
    6 G, }) x9 h  D" ]}
    9 V' X/ q; a3 ~, V9 [" R- r该函数首先通过kobj获取了struct device的指针,再获取了at24。  q" \- O: X) x* H
    接着直接调用了at24_write。如下:
    * x. s. b$ _* P  Z" W: H( O' v% ^3 ~
    tatic ssize_t at24_write(struct at24_data *at24, const char *buf, loff_t off,* V( ?, N3 y7 [; A  [0 h
                              size_t count)% q; a. \2 M* G/ R" o; \  E$ \
    {) I( E$ V; L, g
            ssize_t retval = 0;0 E4 k0 q3 x& \" m

    / J0 |  i" T+ U        if (unlikely(!count))
    2 W2 O) J* a( }# y; f                return count;
    / J4 {& U9 E) \1 `( F. w* v
    # h$ F$ D  c; O$ t9 S- _! c        /*) a, p+ x- {- f7 i3 b1 f1 P$ G
             * Write data to chip, protecting against concurrent updates
      j: z! |, }+ v         * from this host, but not from other I2C masters.' J- V3 l/ m  ~7 J/ H/ ]* _6 t
             */8 |/ t0 Y7 ~, p+ ^8 @
              /* 访问设备前,加锁*/" K4 y0 I8 R4 O" k" L' h$ f/ N( K
            mutex_lock(&at24->lock);2 W* ]; r1 b( _! O
    5 C; ]! C8 j: d% s1 [- K1 T
            while (count) {
    ; {2 h) ?. V- {: C                ssize_t        status;+ @4 D, ^; I+ W+ e

    ( H* r4 @0 J/ b# {/ h                status = at24_eeprom_write(at24, buf, off, count);
    2 d/ w& _" p4 Z/ I7 r                if (status <= 0) {% P6 r6 n" y! ]8 a3 _. Q2 w
                            if (retval == 0)
    0 [+ y1 U0 C+ }! o, {                                retval = status;: d$ x) K+ j' G  d+ L6 [
                            break;
    ( l9 B; e9 J8 D* R& ]- ]/ @7 V                }; \+ E( ~4 P# q0 j9 e
                    buf += status;3 W2 N  I9 l7 R
                    off += status;$ U  q3 G4 L9 f% {0 X) _% t
                    count -= status;! l' j6 R. B8 H' U$ ^4 r2 T
                    retval += status;1 M$ R+ p$ x& m0 E7 D) C9 R
            }
    ( X( T' F9 Q+ k* \: H- k3 _% Q
    ' ]) S4 M& Q/ L2 [- a0 T) Q. }% P        mutex_unlock(&at24->lock);
    5 q( h! S2 {- |# T# |
    2 J& z* }# j7 T7 g8 b0 R- p; B        return retval;$ z3 b( J4 s7 ^$ N! s- {" `
    }
    " a! s3 u+ B* ?% ^! z: m4 O: j$ k% [7 Y5 P
    该函数不复杂。在访问设备前,首先加锁互斥体,以防止竞态。然后根据count来调用at24_eeprom_write函数将数据写入设备。
    + j4 s7 R. r5 o9 z% H3 M7 F" i写入成功后,更新偏移量等信息,如果还需要写入,则再次调用at24_eeprom_write函数。' ~& W+ t* m0 e( e4 i

    & `! v1 B" ]* n# i5 E+ ?( V% Y看下at24_eeprom_write函数:; J+ |5 a4 s- n7 U- V4 t
    + W: `0 R: `& Y
    /*3 b9 }8 S5 P- i. j
    * Note that if the hardware write-protect pin is pulled high, the whole
    , V# N$ X5 N. L, _# b- p * chip is normally write protected. But there are plenty of product4 d1 k1 I  e8 [, S9 b
    * variants here, including OTP fuses and partial chip protect.- f+ _2 O: ?% ]5 d3 D+ F
    ** w; |& `7 f8 s0 ^% \( ~" o' O
    * We only use page mode writes; the alternative is sloooow. This routine+ u# e4 C1 e2 a/ A5 J) K% J
    * writes at most one page.& P  a! V, R$ @& c$ H
    */3 a1 N' @  K4 E
    static ssize_t at24_eeprom_write(struct at24_data *at24, const char *buf,/ b1 n. _3 u" N% G" x; _2 K
                    unsigned offset, size_t count)' d8 y7 f5 a6 X8 M; }  _( [2 _
    {
    ' I) ^( c7 C% a+ t+ o; a        struct i2c_client *client;
    7 g2 {% a: Z  k  F  n9 d        struct i2c_msg msg;7 D/ m$ l( i8 K2 [$ F; ]
            ssize_t status;
    ; u- W" D( t3 y" s# [1 `4 X9 \3 [        unsigned long timeout, write_time;* d* ~) s$ k% c1 A
            unsigned next_page;; e/ {  N" H, M- k7 J* |: ?, {
    , S% Q! r9 B, [4 ?' M. ?2 b# i
            /* Get corresponding I2C address and adjust offset */: H6 ]2 |$ a5 ~, m! T6 [
            client = at24_translate_offset(at24, &offset);, P: h2 X0 A% g! b# O

    % M' B' X& k' ]; V: R        /* write_max is at most a page */9 w; f  g+ t* ?
            /* 检查写入的字节数*/
    , E/ E. `: v& s6 W) Q# V: I$ K        if (count > at24->write_max)
    % J" P' Y! `4 f& |+ h3 y. X                count = at24->write_max;
    ( F5 W1 F- E) S4 t+ U& I, P
    & j5 V% d; Z5 c5 o1 K) t' B* }3 U        /* Never roll over backwards, to the start of this page */4 C: Z9 t; D! @5 U! W0 c4 G6 S
            /* 写入不会超过下一页的边界*// j! y+ t9 \/ d
            next_page = roundup(offset + 1, at24->chip.page_size);
    3 x5 t. o: v$ j4 N2 S* C. h1 Z5 M3 l9 H        /* 根据页大小调整count*/) e( g! t+ V# u/ o+ b+ S" {1 B
            if (offset + count > next_page)5 @; O- W. }+ E9 S7 V
                    count = next_page - offset;
    / x$ Q3 d, }! c; ^. c7 }
    0 y: [6 t( D: k. O8 R" F0 K$ P+ D        /* If we'll use I2C calls for I/O, set up the message */
    6 h( R5 ~" C: ]0 m7 Y4 j! I2 {, k        /* 使用I2C协议,需要填充msg*/' ^+ Z0 Y1 K8 s3 P6 i. g
            if (!at24->use_smbus) {+ `3 A: @* W2 k/ R. M: ~
                    int i = 0;
    0 O* p& h4 y5 i0 q; B9 ~* H: g7 M9 B
    ) T( P0 C/ P" ]                msg.addr = client->addr;  /*设备地址*/( W7 s5 T" {8 [7 J
                    msg.flags = 0;# O9 F3 b- Z2 e4 e4 h' b& g  z

    4 a8 X; @' p$ |1 f6 F                /* msg.buf is u8 and casts will mask the values */
    6 z" H' f8 l2 j$ x4 B- D1 h/ m                /* 使用writebuf作为发送缓冲区 */
      X& g6 g; |6 Q                msg.buf = at24->writebuf;' p1 r+ A7 t% U! Z1 O
                    ) z! d1 `6 R$ U% ?5 o8 L( H8 M0 U$ m" g
                    /* 根据是8位还是16位地址,msg.buf的前一(两)个字节
    % X4 H' ~3 ?$ @/ V                     为设备内部的寄存器地址*/
    5 A2 e% Y% H5 r. z, g' V                if (at24->chip.flags & AT24_FLAG_ADDR16)
    $ H1 R9 d. f6 D+ m9 [6 @9 I0 X                        msg.buf[i++] = offset >> 8;  /* 16位地址,先写高位地址*/6 b( z9 y+ M$ q  `

    - x: [7 r9 r- p! W% D                msg.buf[i++] = offset;6 u& q% q0 L$ _" y8 B: L. B
                    /* 复制需要发送的数据 */
    5 x4 y0 z; J( F2 r# E' I                mEMCpy(&msg.buf[i], buf, count);
    6 \" F- J7 m; _' F9 \                msg.len = i + count;   /* 发送长度为数据长度加上地址长度*/' \% d9 y# ~; p: ]/ w. s4 {
            }
    + i6 K$ h% b* I$ q7 O# N- P+ m+ e
    2 w% Y% l1 Q4 m: i/ }, [        /*7 e8 \6 y% B/ U, o+ g4 S
             * Writes fail if the previous one didn't complete yet. We may( F2 p2 i8 }* V# D8 k% R1 F
             * loop a few times until this one succeeds, waiting at least
    0 n8 b& {1 v' K- ^( X         * long enough for one entire page write to work.+ o3 u: {' z) o8 \; n( ?/ k
             */' g5 a, h3 S: C" C# D
            timeout = jiffies + msecs_to_jiffies(write_timeout);! p% ^3 ]2 _) f% o  o5 l1 z
            do {' K7 z1 Z6 _# V# T) T" a# M
                    write_time = jiffies;+ ~6 w7 e  ^3 [1 e6 [4 z) b
                    if (at24->use_smbus) {' u0 h$ P0 P1 A0 A% `* Y7 g; h5 J
                            /* 使用SMBus协议发送*/& [+ J2 D( P0 W9 W! S
                            status = i2c_smbus_write_i2c_block_data(client,
    ' s2 S2 C5 N* j/ g& u3 a1 {                                        offset, count, buf);
    & A/ F9 ~. w  Y& b  _& G                        if (status == 0)4 S+ @6 R; x9 E! N' Z! E) ^/ F
                                    status = count;
    + `* m: v7 c7 u; V                } else {
    0 y( ~3 Y0 G9 [1 [1 l0 D                        /* 使用I2C协议发送*/
    * g- ~) X5 f; A- R                        status = i2c_transfer(client->adapter, &msg, 1);
    1 k$ i, M4 |6 l) \1 m- V                        if (status == 1)
    & K- Y! ]+ y! M2 ^' l) z                                status = count;7 [+ H" S- x' ?! O7 F  `
                    }
    0 o, n: |6 O% T8 U                dev_dbg(&client->dev, "write %zu@%d --> %zd (%ld)\n",1 C0 G+ z% ]' s) D: i* g0 g( P3 k
                                    count, offset, status, jiffies);
    4 w; i, w1 X# E7 r" v& l* D. M* [, j. m2 v7 u
                    if (status == count)' e) N8 m2 {" Q" q* i
                            return count;  /* 已全部写入,返回*/
    9 }* Z7 Q% ]- X
    , ?& N; }) |( R& M: i# W                /* REVISIT: at HZ=100, this is sloooow */
    ' n1 L& E  H7 p  Y                msleep(1);
    * E0 R( v5 i% ~; r6 t        } while (time_before(write_time, timeout));  /* 使用timeout */( n0 V1 v0 Q( w: @" x$ c& h3 |

    * R# k) \; l4 f. P$ r, j        return -ETIMEDOUT;  /* 超时,返回错误*/
    ) i$ E- j5 C, `- N8 ?" g}
    7 R2 h+ h9 z4 R* _3 N: U* n. N. Q* ~8 }' L& ~+ S* D
    该函数首先调用了at24_translate_offset函数,来获取地址对应的client:
    8 }; c0 D8 @! G/*
    " ]( ~! I$ i5 q* A1 N5 _7 s9 |4 k * This routine supports chips which consume multiple I2C addresses. It
    ( }  B5 j- `: }8 o7 O * computes the addressing information to be used for a given r/w request.
    / J/ [2 B; L4 i; ?# P * Assumes that sanity checks for offset happened at sysfs-layer.
    ! q5 X  W9 d) E, a; B */6 b7 q3 k& X" ~" {
    static struct i2c_client *at24_translate_offset(struct at24_data *at24,
    2 S! j) {* u$ X. W( H1 Z/ \/ O- ~                unsigned *offset)
    # T5 e# B, d3 o; V/ x2 N{4 ?& z! t8 q/ ]% l6 c! L) A
            unsigned i;2 a& Y+ ^9 A) k# m$ p% O
    0 h3 \; `. Q6 ?4 W' X
            /* 有多个I2C设备地址,根据offset获取该地址对应的client*/! ^9 u5 C+ }' c/ z" ^9 K9 @2 g
            if (at24->chip.flags & AT24_FLAG_ADDR16) {5 w) @/ I0 B: v" p! G
                    i = *offset >> 16;
    ; u* r% ~1 H8 L9 Q$ z) L                *offset &= 0xffff;
    " Y2 c9 O8 _8 ~* V3 L$ u        } else {
    , M( v! B% t* q1 f8 @5 R8 J! J! u                i = *offset >> 8;7 C: ~+ Y( U# z# o  l) \) F
                    *offset &= 0xff;
    2 B9 B2 F6 U! |$ E# b9 C        }, W: H4 }2 k( r* E: ~' K9 o
    . f7 u+ H9 P+ s! ]+ l. C/ e& w  L6 U
            return at24->client[i];1 w% i$ V' @% T0 e; _
    }. ~  a% ^, I1 a* O" F% ?

    ( d' k7 b, g, g然后,对写入的字节数(count)进行了调整。3 y. m) P# b' Z/ z# T7 v* l
    随后,如果使用I2C协议,则要组建msg用于发送。. V" T8 I' D- u, H9 v0 L

      [; V  w1 s7 X5 q6 Y最后,根据使用I2C还是SMBus协议,调用相应的发送函数来发送数据。% Z- b) m, z+ h. B
    3 Y) H8 L5 q$ h$ Y
    注意的是,这里使用了超时,超时时间write_timeout为驱动模块参数,可由用户设置,默认为25ms。如果发送超时了,while循环将终止。) \* N  Y# \5 O3 v0 P% Z) ~/ B

    ' k: d! D4 ~3 Y' ]( E2 Z( h至此,at24c02的写入过程就结束了。2 U. s$ k; O5 u$ U
    0 F' W' P0 r6 m8 @$ K1 C
    4.2 读函数(at24_bin_read)
    & L  ]( y+ ~1 W) l: \  写函数和读函数非常相似,只是在使用I2C协议时,组建的msg有所不同。同样读函数也使用了超时。3 T0 T: _9 I% w' u$ v

    ( n0 u0 }6 `+ z5 F2 P  因此,这里仅仅给出代码:
    0 b# b% ^3 ?. t( U& j9 x* Y! B/*
    ! c8 P" r" w) j * This routine supports chips which consume multiple I2C addresses. It, ?# x! r5 @- o' l" k9 w  I! P0 G
    * computes the addressing information to be used for a given r/w request.
    * L' [1 e* w' z. r * Assumes that sanity checks for offset happened at sysfs-layer., T7 B9 H% S8 k
    */
    2 |' R0 O+ u) z* w4 T! Nstatic struct i2c_client *at24_translate_offset(struct at24_data *at24,  l& ?0 k& u1 _1 h* W7 N/ a; C
                    unsigned *offset)% F/ |" E, g- @2 z- G) R) D
    {
    # m* _$ X2 D9 C+ \- O+ t$ C% A' U9 J6 d        unsigned i;2 x7 K- A9 Y1 y6 u: f
    " k4 u  C! N6 x" I, w& K9 W' E0 {
            /* 有多个I2C设备地址,根据offset获取该地址对应的client*/7 _5 o3 T- F. G& ^1 E8 h% I& p! Z
            if (at24->chip.flags & AT24_FLAG_ADDR16) {3 k& p6 v' ~3 k
                    i = *offset >> 16;& H4 p: V  v. c4 m6 l
                    *offset &= 0xffff;
    , ^8 b. ?# ?! e# Y3 z0 M4 Y        } else {. M" D. z7 L- O$ V4 c4 M. D, U  u2 j0 E
                    i = *offset >> 8;
    ' z# f! Q! H- X. v) c3 g  u0 ^, v                *offset &= 0xff;
    / G7 t! T- {: `+ K% D  k        }
    8 ]" Q  Z9 f) _8 w; o  x0 `! S7 a# b/ n; b* R
            return at24->client[i];* |" O2 U6 z2 \4 z; ?) h
    }5 K' C4 v7 p: B& a
    ) s) I7 x) D5 N! J7 A6 q
    static ssize_t at24_eeprom_read(struct at24_data *at24, char *buf,$ ^+ c+ a# D, `( y. Y$ g% d: |
                    unsigned offset, size_t count)
    . z/ p. b! l- A/ q+ D$ ]; v3 ]{
    6 c6 R, V$ |: ~/ i: o* h4 N        struct i2c_msg msg[2];
    - Q4 j  T. y. d        u8 msgbuf[2];* L* Y' J$ Z! _" l
            struct i2c_client *client;
    ! p! T9 d# m- T! x5 ?        unsigned long timeout, read_time;) [! T. v2 E' ?) g( c
            int status, i;9 K/ u, m/ o& R. D4 j# n3 R7 g

    % O+ z8 y! l0 w% T; f        memset(msg, 0, sizeof(msg));9 m1 @' W: o% V$ N. n: A

    7 f! }+ n& ]' }; d: v9 T5 j        /** {0 m5 U" U7 D% J4 m7 n- B& X
             * REVISIT some multi-address chips don't rollover page reads to
    ( S/ z; w2 l4 X         * the next slave address, so we may need to truncate the count.9 c4 _# e6 I: w8 |4 m
             * Those chips might need another quirk flag.  B. F, g. H% `6 h1 r4 R0 w; s' `, z
             *
    5 @1 P' z6 f+ V# d' T: e         * If the real hardware used four adjacent 24c02 chips and that( l3 B$ L0 F1 O5 w9 o9 w
             * were misconfigured as one 24c08, that would be a similar effect:0 F9 e; C0 L5 Q3 b4 B7 t% Z
             * one "eeprom" file not four, but larger reads would fail when1 v. `, _9 e* w# w  t3 @& _. f/ H5 W
             * they crossed certain pages.5 y  `, c5 }1 q: C5 {" [. ~$ v
             */' `; L0 k( {! k0 i$ ^3 A

    6 c  U9 |' R$ |0 {, u2 t0 X$ R        /*
    ( `% J  W+ n% ^4 c  G3 U         * Slave address and byte offset derive from the offset. Always. x- \5 m& Y) t7 U8 [
             * set the byte address; on a multi-master board, another master
    4 O0 @' j, n- D2 R0 A- t* n" f0 t         * may have changed the chip's "current" address pointer.; a- e6 t7 s3 t' l( M2 d
             */6 a. `; e: l, l. B
            client = at24_translate_offset(at24, &offset);
    - t( v: ^2 c- V: F& ^3 M/ a; C! P6 {8 n: w; ]. G
            if (count > io_limit)% i) Y6 k; ]* w$ T
                    count = io_limit;9 m# }, ?! S  `" k

    4 \; W( e+ H- r& {; O        if (at24->use_smbus) {
    " Y2 C7 C4 u- G* s2 A                /* Smaller eeproms can work given some SMBus extension calls */1 O0 A$ C: F7 P5 f, N* T+ `5 ?
                    if (count > I2C_SMBUS_BLOCK_MAX)
    % {2 R2 }# K2 h, r6 q8 b                        count = I2C_SMBUS_BLOCK_MAX;
    6 V& G+ ^% S- _! I' S        } else {
    $ Y3 k4 G2 F. b" c                /* 使用I2C协议,需要填充msg*/! g. U1 b# f5 w% e- A1 w7 S
                    /*
    5 l6 B% K/ L. n8 U. n1 Z6 n* g, v                 * When we have a better choice than SMBus calls, use a
    - [3 T' t7 S# }4 q( G; E& s5 l                 * combined I2C message. Write address; then read up to
    & G) A; h' m$ L8 e. u3 F1 Z                 * io_limit data bytes. Note that read page rollover helps us% w# X( i7 E# `" I. r* _  f5 S
                     * here (unlike writes). msgbuf is u8 and will cast to our
    ) j2 ~  i* {9 o2 l7 }                 * needs.
    ) u6 i" L% S8 E                 */* k0 f6 y7 z: _4 k2 D! ^
                    i = 0;6 M8 p7 ?7 Z, r$ B
                    if (at24->chip.flags & AT24_FLAG_ADDR16)
    1 j6 B4 ~9 }: |) {                        msgbuf[i++] = offset >> 8;' J' f1 e0 i  x$ ~( P2 e3 ^* i
                    msgbuf[i++] = offset;4 g% m0 d* M+ |7 c9 D
      ~% h) G4 i: k8 h
                    msg[0].addr = client->addr;
    - E# Q# }8 S- `7 H8 Z                msg[0].buf = msgbuf;
    ! e4 j7 V3 e4 E) R1 `5 ~/ P% U                msg[0].len = i;
      p1 W  M2 |6 k# R. q1 l" O, _# x, F& a% ]3 T, W+ V
                    msg[1].addr = client->addr;2 G& M. p  S0 s! _5 V2 J: R
                    msg[1].flags = I2C_M_RD;  /* 读模式*/8 G; m! x' r0 V1 p6 l
                    msg[1].buf = buf;* Y3 p5 M! z! ?( G: ^* [
                    msg[1].len = count;# R9 f% c/ ?% E  _" G
            }
    6 Z& l  O" n4 E. k( p- _0 Z( t- t' e. K$ g% K
            /*
    & h* P  N/ c7 V8 i# v         * Reads fail if the previous write didn't complete yet. We may; P& `9 C7 Y- \( b; p0 }) E, M) @
             * loop a few times until this one succeeds, waiting at least
    + Q! G# y" [! ~& C: E         * long enough for one entire page write to work.1 l6 Y5 T2 o0 p, W/ H0 u' v
             */& g' H; s- _) d& j# C
            timeout = jiffies + msecs_to_jiffies(write_timeout);
    & j' T+ G8 o4 u/ u" \" n        do {
    & A5 k: b, Y. S+ j/ n! c$ ]                read_time = jiffies;
    ( J0 C/ |3 k. P+ p                if (at24->use_smbus) {. \5 U8 A1 z1 ?+ m$ b
                            status = i2c_smbus_read_i2c_block_data(client, offset,; s3 s' z  M/ p, j
                                            count, buf);% s8 ^* y6 c6 Y
                    } else {9 c+ y1 B2 C; e( n" I% d7 v  p: w
                            status = i2c_transfer(client->adapter, msg, 2);
    4 N  L  i/ |& c* Y1 a                        if (status == 2)# H* R* S" @1 I' |
                                    status = count;7 L  d5 T, n. P
                    }
    : S$ F  _' v! E5 A& M                dev_dbg(&client->dev, "read %zu@%d --> %d (%ld)\n",
    0 Z' L( ~. w9 |2 M" W                                count, offset, status, jiffies);
    : ~) B% @% r! p" u2 C2 K' m5 C6 ^8 i( S2 b5 u7 Q7 t% d4 m! S' A
                    if (status == count)
    6 \" s' r7 f/ W! p                        return count;( W1 w8 `1 L: u  l
    8 R' W. o2 i% S3 Y1 ^5 v
                    /* REVISIT: at HZ=100, this is sloooow */: s) Z" y( t8 m' }  @
                    msleep(1);
    " B0 d* K& Y  L% i# R4 N& J: g        } while (time_before(read_time, timeout));  /* 使用timeout */5 O; d0 ^. E" E0 U& i

    9 o% R$ P0 w( j        return -ETIMEDOUT;% K* \3 P5 _4 U3 E* E  f3 l( }$ d
    }
    3 ~. q: M7 c# N3 m2 y3 k% D" P( q
    , x, o% A1 D1 a" M5 o/ g: astatic ssize_t at24_read(struct at24_data *at24,; M" {: h6 A+ |( _) J+ C
                    char *buf, loff_t off, size_t count)
    5 E5 O% P+ P4 l, M9 R& g{
    2 v% @. ?3 E( k# h' o9 m) r; E        ssize_t retval = 0;: T& N8 R& q: I6 u- q

    " H, Z9 o+ W( {; Y3 X1 \9 a  q        if (unlikely(!count))
    8 k0 v6 o5 V, t! w" \3 I7 _                return count;
    0 D  W0 P" g' O; p, z( ~! h8 c' Y% ?  t  j, j# x6 v' k
            /*
    6 T  c6 _, [( l- y6 A# Q         * Read data from chip, protecting against concurrent updates
    ' l+ |! o) @  d& \3 ^1 T. b! ~9 h         * from this host, but not from other I2C masters.
    ! K$ ]. Q0 }4 r         */
    / t4 j0 z- h0 Z/ Z% L  T         /* 访问设备前,加锁*/
    8 x& |" }: e8 F. M        mutex_lock(&at24->lock); 2 W! ~: |; B* k" W5 M  D) N, m6 H- Y

    & J; C; r  x; x. |        while (count) {; i; O8 ^0 N: ~! S
                    ssize_t        status;
    - `$ d0 k& o) z3 t9 I! B/ n5 A+ u
                    status = at24_eeprom_read(at24, buf, off, count);
    8 Y: E! c6 o3 T+ N+ F3 v$ u                if (status <= 0) {9 y9 L5 j( \3 u, n5 N: {) V2 z
                            if (retval == 0)
    0 E8 C" i( O3 ~                                retval = status;
    ; e) V* a# r. \5 E& Q" A2 Q8 W                        break;& h3 \  M! W' ^0 Z6 J! L: {. I6 r6 V
                    }# A4 G1 Y6 n- R( Q1 X* z& z) q
                    buf += status;
    ; B& z* m6 j$ {. F2 u' {; ^2 w                off += status;
    ' q( j: d! v* B5 T# c$ o4 C                count -= status;  y' q5 e, d9 z4 f/ M7 }
                    retval += status;
    0 Q& e' S5 V2 O3 J; M+ p2 M5 g/ h# B) h* w        }
    & H7 d" x6 o- L" b- g  a  d* q  m2 }
    % R! D. V) E( }4 L9 q% f( b        mutex_unlock(&at24->lock);: {* R0 W8 r5 c4 C3 Q# N4 w

      s& t5 x4 a) [9 G        return retval;5 B3 E% o( Q' u0 ^: ^
    }
    0 f* G( U$ @; v) e: r; j$ O0 z/ B8 S: c( f5 [) h
    static ssize_t at24_bin_read(struct kobject *kobj, struct bin_attribute *attr,
    1 z3 p0 M! t0 |; ?! t                char *buf, loff_t off, size_t count)
    * [+ e5 _% B5 F) W{
      E8 r2 m! a: `  b        struct at24_data *at24;
    * L) ^. r0 `' y; K2 h5 A* f1 X2 O$ `9 D7 a9 H3 R$ G2 E4 j
            /* 通过kobj获取device,再获取driver_data */5 P0 p. o% f$ h4 x- I2 D" F  h" e* [
            at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));2 J0 Z' t- U  v, E1 ~- K" \' j7 W
            return at24_read(at24, buf, off, count);* R9 L- U# P$ g; _7 m
    }% W3 c" k' X! s

    - R; ~) c3 X& f( q8 G5. 总结
    9 X1 V' t$ w4 v! S4 c    本文主要对at24c02的驱动架构进行了分析。该驱动基于i2c总线架构,提供了id表来帮助设备驱动的绑定,该驱动支持AT24CXX等多个系列,不仅仅是at24c02。, n0 p7 C& |- B! u5 z" X/ T

    " U: J# g  C8 a/ s* ?) w9 v6 k6 a其次,该驱动并没有注册任何字符设备或者杂项设备,而是通过sys文件系统的二进制属性文件来对设备进行访问。此外,驱动同时支持I2C协议和SMBus协议来访问设备。
    ( g. q7 n  l2 R- j' }% `& _6 D8 N5 c! T1 A7 K: q

    ' S* K; a0 o' y2 P1 F- G5 g7 u. R7 B: j

    & ~- d! g0 O1 I& ]6 W

    该用户从未签到

    2#
    发表于 2020-5-29 13:19 | 只看该作者
    基于S3C2440的嵌入式Linux驱动
    您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    关闭

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

    EDA365公众号

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

    GMT+8, 2025-7-1 00:42 , Processed in 0.125000 second(s), 23 queries , Gzip On.

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

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

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