你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32MP1嵌入式Linux驱动开发指南V1.7——Linux I2C驱动实验

[复制链接]
STMCU小助手 发布时间:2022-9-27 15:03
Linux I2C驱动实验" X. f2 S  C) b& ]  z" T
对于 I2C 我相信大家都很熟悉,基本上做过单片机开发的朋友都接触过,在电子产品硬件设计当中,I2C 是一种很常见的同步、串行、低速、近距离通信接口,用于连接各种 IC、传感器等器件,它们都会提供 I2C 接口与 SoC 主控相连,比如陀螺仪、加速度计、触摸屏等,其最大优势在于可以在总线上扩展多个外围设备的支持。4 L4 _& L: Z1 w( }7 M4 r4 V
Linux 内核开发者为了让驱动开发工程师在内核中方便的添加自己的 I2C 设备驱动程序,更容易的在 linux 下驱动自己的 I2C 接口硬件,进而引入了 I2C 总线框架。与 Linux 下的 platform 虚拟总线不同的是,I2C 是实际的物理总线,所以 I2C 总线框架也是Linux 下总线、设备、驱动模型的产物。# H4 [. X4 _- _
本章我们来学习一下如何在 Linux 下的 I2C 总线框架,以及如何使用 I2C 总线框架编写一个 I2C 接口的外设驱动程序;本章重点是学习 Linux 下的 I2C 总线框架。
1 i1 a" V5 ^* Y
- S6 }. w5 b& ]  ^8 Z40.1 I2C & AP3216C简介
) ^2 q/ j: n1 `) k( O" ~: [$ @40.1.1 I2C简介, P' M9 i# I3 Q
I2C是很常见的一种总线协议,I2C是NXP公司设计的,I2C使用两条线在主控制器和从机之间进行数据通信。一条是SCL(串行时钟线),另外一条是SDA(串行数据线),这两条数据线需要接上拉电阻,总线空闲的时候SCL和SDA处于高电平。I2C总线标准模式下速度可以达到100Kb/S,快速模式下可以达到400Kb/S。I2C总线工作是按照一定的协议来运行的,接下来就看一下I2C协议。
- T1 b7 s& I7 @9 s( i" A5 DI2C是支持多从机的,也就是一个I2C控制器下可以挂多个I2C从设备,这些不同的I2C从设备有不同的器件地址,这样I2C主控制器就可以通过I2C设备的器件地址访问指定的I2C设备了,一个I2C总线连接多个I2C设备如图40.1.1.1所示:  T& L6 _1 f) O1 A: P: @

3 s. ]8 I; F8 y% \ a8aa55bb42814fac97fce42e61d78380.png
' k! T1 e0 C$ k' H
" y* Q# d) a5 _# o8 S( M9 m图40.1.1.1 I2C多个设备连接结构图* T3 u0 a+ G: j6 x4 C8 o
图40.1.1.1中SDA和SCL这两根线必须要接一个上拉电阻,一般是4.7K。其余的I2C从器件都挂接到SDA和SCL这两根线上,这样就可以通过SDA和SCL这两根线来访问多个I2C设备。
. T8 [% J/ A* u  O! J接下来看一下I2C协议有关的术语:
1 Z9 o2 L( x3 B& c: q; ^7 Q/ c/ {& J, p1、起始位
% \( E4 f0 F* T. E顾名思义,也就是I2C通信起始标志,通过这个起始位就可以告诉I2C从机,“我”要开始进行I2C通信了。在SCL为高电平的时候,SDA出现下降沿就表示为起始位,如图40.1.1.2所示:
7 w4 z# f: o& y2 u8 t% f% ^$ D! m- V) |4 Q6 F
f659bb944ec94b82aedf31cd95360778.png
' w1 [- c9 I2 S- X5 Y
' O+ e* v( A9 O, t0 a1 y7 D图40.1.1.2 I2C通信起始位
8 ~; o+ L; T, X. v( v; _2、停止位
5 |' j( _7 Q/ X; V- x停止位就是停止I2C通信的标志位,和起始位的功能相反。在SCL位高电平的时候,SDA出现上升沿就表示为停止位,如图40.1.1.3所示:
, k+ ^# `  x/ E! F2 H2 v# Z$ X" e/ Q. x* _  I
781b27638e81445a8a184826f3a51eb5.png
9 X& V5 n  P0 T7 f# }0 G: B
, F& {4 o* Z% ~# z* |图40.1.1.3 I2C通信停止位' C2 r, l1 [1 J. x
3、数据传输0 [7 g+ T1 l, n7 A- E. k
I2C总线在数据传输的时候要保证在SCL高电平期间,SDA上的数据稳定,因此SDA上的数据变化只能在SCL低电平期间发生,如图40.1.1.4所示:
& C- I4 ~% W5 }1 H: f3 F& s. G; u: |
5218f029c370463681479be880ff9b24.png 1 V$ {3 n4 q  f6 E( u8 v- r  c' `

. |5 p& y% }/ b4 L; b3 I+ {图40.1.1.4 I2C数据传输) R# [+ ?9 W" P" |7 k7 k* Z
4、应答信号& G6 j  A" \1 M, F) y
当I2C主机发送完8位数据以后会将SDA设置为输入状态,等待I2C从机应答,也就是等待I2C从机告诉主机它接收到数据了。应答信号是由从机发出的,主机需要提供应答信号所需的时钟,主机发送完8位数据以后紧跟着的一个时钟信号就是给应答信号使用的。从机通过将SDA拉低来表示发出应答信号,表示通信成功,否则表示通信失败。7 w9 S  i& V7 S# F
5、I2C写时序" _% R  i( y& o: g
主机通过I2C总线与从机之间进行通信不外乎两个操作:写和读,I2C总线单字节写时序如图40.1.1.5所示:
2 D& F& G5 |5 V# t( X) t( p& }' a9 y5 @
1.png + v4 U( G* r9 o( ~2 ~; V
1 ?4 F9 k" }" F: W7 @
图40.1.1.5 I2C写时序: E, U8 i+ ?% M, a4 X. _4 O
图40.1.1.5就是I2C写时序,我们来看一下写时序的具体步骤:5 N1 g/ |9 G  f6 V
1)、开始信号。4 _  N- ?1 t$ `; w% _- {% h( N
2)、发送I2C设备地址,每个I2C器件都有一个设备地址,通过发送具体的设备地址来决定访问哪个I2C器件。这是一个8位的数据,其中高7位是设备地址,最后1位是读写位,为1的话表示这是一个读操作,为0的话表示这是一个写操作。& f% ?7 @" ?5 [1 ^7 n$ w: M! y
3)、 I2C器件地址后面跟着一个读写位,为0表示写操作,为1表示读操作。% a: S3 C1 e" E8 V% V6 z
4)、从机发送的ACK应答信号。7 j& [3 x8 D- _+ y! ~2 M, U
5)、重新发送开始信号。
* |+ P1 [+ v/ ~; A8 q6)、发送要写写入数据的寄存器地址。
! B4 w7 w- I3 Z/ Q  Z7)、从机发送的ACK应答信号。$ N( y' O8 |5 [2 q5 j8 w8 k# I& T3 S
8)、发送要写入寄存器的数据。5 j6 C1 C$ L. Z- M3 D, S
9)、从机发送的ACK应答信号。7 y& y7 E- ^7 W0 V  I
10)、停止信号。7 q+ I" `$ h! V6 h
6、I2C读时序
! ^  Q+ y+ \# |! x/ M; kI2C总线单字节读时序如图40.1.1.6所示:+ ~) i  \( c9 s8 i; O2 T; ]: K3 C' `
2 S/ Q* y  V* p( e2 W9 k* t
2.png
$ D/ U" p  X' Z8 u) m* O* k
2 y( l" I# V5 P- |  N图40.1.1.6 I2C单字节读时序7 |( Z1 E5 K# w7 Y$ @9 Y
I2C单字节读时序比写时序要复杂一点,读时序分为4大步,第一步是发送设备地址,第二步是发送要读取的寄存器地址,第三步重新发送设备地址,最后一步就是I2C从器件输出要读取的寄存器值,我们具体来看一下这步。
( l& y+ o' p: U0 ^, |9 R# l1)、主机发送起始信号。
9 d! d* u  ]4 [. c0 C4 S$ q2)、主机发送要读取的I2C从设备地址。6 ?  z0 w) n% a5 F2 U+ Z0 E
3)、读写控制位,因为是向I2C从设备发送数据,因此是写信号。& r! N( G3 Q; `  n3 N* o+ }3 y
4)、从机发送的ACK应答信号。
" F5 F9 g( S0 ^' a5)、重新发送START信号。
5 d: p( P' l4 e6)、主机发送要读取的寄存器地址。+ S, d; M" ~4 o9 j. M, C4 l
7)、从机发送的ACK应答信号。
, m5 D, w% t! b3 k8)、重新发送START信号。
. l8 |3 d, v! ?3 j9)、重新发送要读取的I2C从设备地址。( ~# N; i3 |0 {, f" V% Y
10)、读写控制位,这里是读信号,表示接下来是从I2C从设备里面读取数据。
: N# o. w3 [! v# \% p, p2 Z- ?' C11)、从机发送的ACK应答信号。
, E; I! q/ h* c12)、从I2C器件里面读取到的数据。
5 n1 Y$ M5 i4 o+ g! O6 W- q( E13)、主机发出NO ACK信号,表示读取完成,不需要从机再发送ACK信号了。9 }! _; M8 ^/ S! V/ B6 c
14)、主机发出STOP信号,停止I2C通信。
# x( P5 k( Q$ v- Q9 s# `7、I2C多字节读写时序
  r+ t! C$ [9 r' C7 \/ c有时候我们需要读写多个字节,多字节读写时序和单字节的基本一致,只是在读写数据的时候可以连续发送多个自己的数据,其他的控制时序都是和单字节一样的。
- t; `6 D9 U! a4 y* l1 Q" U4 H40.1.2 STM32MP1 I2C 简介( W. e" l: E& \
STM32MP157D 有 6 个 I2C 接口,其中 I2C4 和 I2C6 可以在 A7 安全模式或者 A7 非安全模式下使用,M4 无法使用,STM32MP157 的 I2C 部分特性如下:
3 Q; v* }  b! |. V$ j①、兼容I2C总线规范第03版。
) {0 Y5 c  R) [8 v9 K4 Q- U②、支持从模式和主模式,支持多主模式功能。& s& E1 C6 y" |, c* t! W/ s" Y
③、支持标准模式 (Sm)、快速模式 (Fm) 和超快速模式 (Fm+),其中,标准模式100kHz,快速模式400 kHz,超快速模式可以到1 MHz。
4 f# p7 Z( R; _④、7 位和10位寻址模式。3 V$ p' Z- q/ r$ e
⑤、多个7位从地址,所有7位地址应答模式。
0 ^- y& K5 z% U/ j9 Z& h1 |⑥、软件复位。
2 d6 q: P2 y; e8 J* \6 ?⑦、带DMA功能的1字节缓冲。
% o" z5 V! d& M( v⑧、广播呼叫。1 ]0 q4 N. G4 ^  I5 _+ n, c2 `
关于STM32M157 IIC更多详细的介绍,请参考《STM32MP157参考手册》相关章节。( k( f! ]8 }- D8 r- P
40.1.3 AP3216C简介
* D4 ^: ?; M" M& S- GSTM32MP1开发板上通过I2C5连接了一个三合一环境传感器:AP3216C,AP3216C是由敦南科技推出的一款传感器,其支持环境光强度(ALS)、接近距离(PS)和红外线强度(IR)这三个环境参数检测。该芯片可以通过IIC接口与主控制相连,并且支持中断,AP3216C的特点如下:
- F) T! W: Q$ q4 T- n2 L①、I2C接口,快速模式下波特率可以到400Kbit/S4 d# W% X! u. @0 F. y* D
②、多种工作模式选择:ALS、PS+IR、ALS+PS+IR、PD等等。' K3 o: _5 \- X9 J( W0 F
③、内建温度补偿电路。
6 y% S+ K$ |' D% U! D# O  ?( P④、宽工作温度范围(-30°C ~ +80°C)。
1 x/ G) ?1 {% N; j. U. u6 s⑤、超小封装,4.1mm x 2.4mm x 1.35mm; Y3 A9 `; |/ A( ?+ M7 D
⑥、环境光传感器具有16位分辨率。
; D* }- y, z( F⑦、接近传感器和红外传感器具有10位分辨率。" h  v$ J% j; z7 t! ?# t
AP3216C常被用于手机、平板、导航设备等,其内置的接近传感器可以用于检测是否有物体接近,比如手机上用来检测耳朵是否接触听筒,如果检测到的话就表示正在打电话,手机就会关闭手机屏幕以省电。也可以使用环境光传感器检测光照强度,可以实现自动背光亮度调节。: P. J* t/ d5 w# b
AP3216C结构如图40.1.3.1所示:- }  |, p9 {8 C# _; S2 e9 ^
2 R2 }) L- S& r# u9 a6 I2 v1 y
8ac2987a0e074e7aaebf24ae57cb0947.png
& j8 ?3 l8 ~9 a6 K2 R
- w, O* ?+ k/ W9 |8 c图40.1.3.1 AP3216C结构图( v1 x3 Q& j2 Z4 z) m. s+ I: D/ a
AP3216的设备地址为0X1E,同几乎所有的I2C从器件一样,AP3216C内部也有一些寄存器,通过这些寄存器我们可以配置AP3216C的工作模式,并且读取相应的数据。AP3216C我们用的寄存器如表40.1.3.1所示:+ y' ?% b# L7 h9 c0 ^, w

* n  N1 D" A5 O; n a7d476e2d9de476085fc1e6f5a9938e2.png
6 P* N2 g: s! V% G( N1 Z3 i+ z
$ o5 `) Z7 [1 O. p0 f: J表40.1.3.1 本章使用的AP3216C寄存器表
' y5 ]5 L$ }+ H- b4 j在表40.1.3.1中,0X00这个寄存器是模式控制寄存器,用来设置AP3216C的工作模式,一般开始先将其设置为0X04,也就是先软件复位一次AP3216C。接下来根据实际使用情况选择合适的工作模式,比如设置为0X03,也就是开启ALS+PS+IR。0X0A~0X0F这6个寄存器就是数据寄存器,保存着ALS、PS和IR这三个传感器获取到的数据值。如果同时打开ALS、PS和IR的读取间隔最少要112.5ms,因为AP3216C完成一次转换需要112.5ms。关于AP3216C的介绍就到这里,如果要想详细的研究此芯片的话,请大家自行查阅其数据手册。
% j0 O2 b/ Z1 ?! \2 ?40.2 Linux I2C总线框架简介

7 K2 h' r/ K4 N* z% s% G, E  s. w使用裸机的方式编写一个 I2C 器件的驱动程序,我们一般需要实现两部分:% Q, y2 Q/ n0 p9 ^7 K) J
①、I2C 主机驱动。
. M5 J4 A; W2 |  f2 i" L②、I2C 设备驱动。$ V2 ?6 F$ g1 V2 P: h, a6 e
I2C 主机驱动也就是 SoC 的 I2C 控制器对应的驱动程序,I2C 设备驱动其实就是挂在 I2C总线下的具体设备对应的驱动程序,例如 eeprom、触摸屏 IC、传感器 IC 等;对于主机驱动来说,一旦编写完成就不需要再做修改,其他的 I2C 设备直接调用主机驱动提供的 API 函数完成读写操作即可。这个正好符合 Linux 的驱动分离与分层的思想,因此 Linux 内核也将
! @/ ~# X" [" e0 r0 LI2C 驱动分为两部分。
" x) Y6 H' s7 x% `7 uLinux内核开发者为了让驱动开发工程师在内核中方便的添加自己的I2C设备驱动程序,方便大家更容易的在linux下驱动自己的I2C接口硬件,进而引入了I2C总线框架,我们一般也叫作I2C子系统,Linux下I2C子系统总体框架如下所示:0 M2 C; M9 m8 w9 |) o; P5 ^; V

, x1 v) Z2 w1 ]/ i" W2 ~ 4d30ac2efb4044fba8fbb909fb9f873b.png
) F4 w# q3 n9 t& J" ]2 X" X( q* t) D5 ^! Y' ?+ N" T# [
图40.2.3.1 I2C子系统框架图
3 ~( V6 q- O+ Y! t. y0 [从图40.2.3.1可以知道,I2C子系统分为三大组成部分:
* g  B& x8 _( g6 V7 R6 a1、I2C核心(I2C-core)1 u" [3 Z* g: N/ `" T4 B, S6 D8 L
I2C核心提供了I2C总线驱动(适配器)和设备驱动的注册、注销方法,I2C通信方法(algorithm)与具体硬件无关的代码,以及探测设备地址的上层代码等;4 P! Q3 `- f4 X7 W+ l5 y
2、I2C总线驱动(I2C adapter)7 E3 b6 M/ p2 a
I2C总线驱动是I2C适配器的软件实现,提供I2C适配器与从设备间完成数据通信的能力。I2C总线驱动由i2c_adapter和i2c_algorithm来描述。I2C适配器是SoC中内置i2c控制器的软件抽象,可以理解为他所代表的是一个I2C主机;
- h7 V3 }: g/ a" v5 n, w3、I2C设备驱动(I2C client driver)
8 L# U8 D4 i9 @& T7 w( ?! q$ M包括两部分:设备的注册和驱动的注册。
+ V" `5 `  n4 n/ w9 g6 L5 ]I2C子系统帮助内核统一管理I2C设备,让驱动开发工程师在内核中可以更加容易地添加自己的I2C设备驱动程序。( H! I8 t- J! u  P$ I
40.2.1 I2C 总线驱动
! K5 r/ k. L4 B* n. c1 K; n首先来看一下I2C总线,在讲platform的时候就说过,platform是虚拟出来的一条总线,目的是为了实现总线、设备、驱动框架。对于I2C而言,不需要虚拟出一条总线,直接使用I2C总线即可。I2C总线驱动重点是I2C适配器(也就是SoC的I2C接口控制器)驱动,这里要用到两个重要的数据结构:i2c_adapter和i2c_algorithm,I2C子系统将SoC的I2C适配器(控制器)抽象成一个i2c_adapter结构体,i2c_adapter结构体定义在include/linux/i2c.h文件中,结构体内容如下:
# M0 p7 N7 L$ ?) A% N5 J! N3 i, U# S2 ?5 K5 Q* Q  _6 l, n
  1. 示例代码40.2.1 i2c_adapter结构体
    * v# C$ I! ^4 w4 a8 ^# s
  2. 685 struct i2c_adapter {" q% l3 k8 H8 f1 P5 _4 U
  3. 686     struct module *owner;5 B, E1 x) Y; c$ l6 E
  4. 687     unsigned int class;      
      a0 c' A% w8 \  A, l  I
  5. 688     const struct i2c_algorithm *algo;
    . O2 q; J% r- w7 k3 X
  6. 689     void *algo_data;' R7 G5 l( ?2 R
  7. 690
    & [5 F1 e; _7 h$ u
  8. 691     /* data fields that are valid for all devices   */* s( Y6 h5 h2 c3 f
  9. 692     const struct i2c_lock_operations *lock_ops;* n, P+ z% J( t% j& z9 Q7 X
  10. 693     struct rt_mutex bus_lock;
    % k8 n  H' F* c4 |& Z6 \/ }
  11. 694     struct rt_mutex mux_lock;* e, e3 |& }* X1 m& n/ s+ H
  12. 695
    , w, ~. _7 ]+ T4 q1 `6 Q
  13. 696     int timeout;                            /* in jiffies */* O: X  ~: x' u7 B
  14. 697     int retries;0 {* e8 k! I' o
  15. 698     struct device dev;                      /* the adapter device */
    5 k- U7 b" ^& ~- ^
  16. 699     unsigned long locked_flags; /* owned by the I2C core */
    . _/ h- g# W6 j) l+ L: R
  17. 700 #define I2C_ALF_IS_SUSPENDED                0" p1 J! t- {- `3 Y- q9 O
  18. 701 #define I2C_ALF_SUSPEND_REPORTED            1' D( F) h5 h' W6 T
  19. 702/ V) n: x& ?: {2 e# Y
  20. 703     int nr;; a1 U' L3 ]& n$ E
  21. 704     char name[48];
    5 Q  J' u" k/ |. R: T" ^2 |
  22. 705     struct completion dev_released;' G3 f' J$ y# p( a  W* w" ]3 R+ q
  23. 706
    & ?# W# Z# U. G5 z  i
  24. 707     struct mutex userspace_clients_lock;
    8 n- L+ O- H5 _5 U( E6 n& i
  25. 708     struct list_head userspace_clients;% l# k' n( M" N, P( |! D
  26. 7094 Y1 [; a! z- f/ D1 W, J
  27. 710     struct i2c_bus_recovery_info *bus_recovery_info;0 o( u' t$ |) A! G# ^
  28. 711     const struct i2c_adapter_quirks *quirks;
    - D! B6 a- Z% h/ l
  29. 712
    % c1 q# B! R+ h8 j2 _2 n
  30. 713     struct irq_domain *host_notify_domain;
    # w$ I; Z8 O8 c1 }
  31. 714 };
复制代码
' V2 ?+ I- q0 ]8 H" N
第 688 行,i2c_algorithm 类型的指针变量 algo,对于一个 I2C 适配器,肯定要对外提供读写 API 函数,设备驱动程序可以使用这些 API 函数来完成读写操作。i2c_algorithm 就是 I2C 适配器与 IIC 设备进行通信的方法。
/ _3 G' h$ `$ r' a0 i5 Zi2c_algorithm 结构体定义在 include/linux/i2c.h 文件中,内容如下:' M! h) N$ p3 d& I2 l8 y9 R8 E
8 a4 s9 m) l- p8 w. Y5 K& L
  1. 示例代码40.2.1.2 i2c_algorithm结构体1 L7 K& s; M8 g6 |9 Z
  2. 526 struct i2c_algorithm {
    $ f! F8 K4 t" b
  3. 527     /*& B+ B3 d0 d1 t
  4. 528      * If an adapter algorithm can't do I2C-level access, set 5 [  y- ~2 q' e( u9 R! x
  5. 529      * master_xfer to NULL. If an adapter algorithm can do SMBus
    1 [9 j! Y2 P1 x
  6. 530      * access, set smbus_xfer. If set to NULL, the SMBus protocol is
    8 q/ a9 F  }* I  v
  7. 531      * simulated using common I2C messages.7 x/ X. D# L2 Y
  8. 532      *
    - t4 ^& F; X$ u8 E, d  [! T7 A
  9. 533      * master_xfer should return the number of messages successfully
    9 e$ t7 b4 Y( n9 B% ]1 b
  10. 534      * processed, or a negative value on error* Q) S2 c6 d  l0 X# Z1 B- B
  11. 535      */
    6 \% c, D5 K+ ~/ q
  12. 536     int (*master_xfer)(struct i2c_adapter *adap, 9 d" ^8 {1 l# m, V- l! c  S
  13. struct i2c_msg *msgs,( \- _4 X2 W$ s& r# C
  14. 537                int num);
    # Q# N$ Y. P0 x; g% f. e
  15. 538     int (*master_xfer_atomic)(struct i2c_adapter *adap,# H) p+ j5 c1 \+ ^( F
  16. 539                    struct i2c_msg *msgs, int num);
    * V1 _/ m5 H# E/ ]! ~
  17. 540     int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr,8 ^  m6 y9 j; {0 o# g; E) z% k1 h/ b
  18. 541               unsigned short flags, char read_write,) x+ t& ~6 p9 R3 `
  19. 542               u8 command, int size, union i2c_smbus_data *data);, d5 K( m: a  \5 F0 T
  20. 543     int (*smbus_xfer_atomic)(struct i2c_adapter *adap, u16 addr,0 X! {- V- r5 H, ~: X/ q5 [  e
  21. 544                  unsigned short flags, char read_write,
    : l/ z; U$ y3 \
  22. 545                  u8 command, int size, union i2c_smbus_data *data);
    6 e4 w/ [  H% a& F6 ~+ V
  23. 546- Z! w! {+ \' w  j# g/ P! p3 J$ O* A. p
  24. 547     /* To determine what the adapter supports */
    ) ~/ o5 x8 o2 i
  25. 548     u32 (*functionality)(struct i2c_adapter *adap);9 t4 G2 {/ V6 t1 k" s, A% W
  26. 549' x  ?, @' \& B! X6 @/ y. v$ e
  27. 550 #if IS_ENABLED(CONFIG_I2C_SLAVE)5 M  Z6 L2 E  d: T+ E* M
  28. 551     int (*reg_slave)(struct i2c_client *client);2 }8 d' K7 O0 ]) m
  29. 552     int (*unreg_slave)(struct i2c_client *client);' C% b/ e/ M7 w0 a
  30. 553 #endif
    " x" I$ J+ G0 D( U, z: }4 a! B
  31. 554 };
    ; Y8 ?$ T4 @' z& y, x
复制代码
% P2 H- F( E' _7 k
第536行,master_xfer就是I2C适配器的传输函数,可以通过此函数来完成与IIC设备之间的通信。9 E' u( x/ u' T7 C! f/ n! F! W0 F: Q
第540行,smbus_xfer就是SMBUS总线的传输函数。smbus协议是从I2C协议的基础上发展而来的,他们之间有很大的相似度,SMBus与I2C总线之间在时序特性上存在一些差别,应用于移动PC和桌面PC系统中的低速率通讯。- Y) G( a: q( p& [) d
综上所述,I2C总线驱动,或者说I2C适配器驱动的主要工作就是初始化i2c_adapter结构体变量,然后设置i2c_algorithm中的master_xfer函数。完成以后通过i2c_add_numbered_adapter或i2c_add_adapter这两个函数向I2C子系统注册设置好的i2c_adapter,这两个函数的原型如下:- b) l* p: g' }
int i2c_add_adapter(struct i2c_adapter *adapter)# }/ Q8 D9 e, m9 n% S4 B" ?+ j8 F1 T- i# P
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
# t4 t  P4 z; h* z* U" s$ y这两个函数的区别在于i2c_add_adapter会动态分配一个总线编号,而i2c_add_numbered_adapter函数则指定一个静态的总线编号。函数参数和返回值含义如下:
2 f* i7 C5 L0 w% padapter或adap:要添加到Linux内核中的i2c_adapter,也就是I2C适配器。+ ]; O3 U; |5 C9 T* y% G
返回值:0,成功;负值,失败。
& e3 d7 Z: O: c' b9 b8 O如果要删除I2C适配器的话使用i2c_del_adapter函数即可,函数原型如下:
- ]4 C# v4 `' yvoid i2c_del_adapter(struct i2c_adapter * adap)
5 b& Y$ I7 a: \0 ?函数参数和返回值含义如下:& g7 {5 L; {. t, m5 p2 V! G; F
adap:要删除的I2C适配器。
2 p8 r' h/ g0 O3 e) V返回值:无。
8 R) ~$ N( L! O% d, w关于I2C的总线(控制器或适配器)驱动就讲解到这里,一般SoC的I2C总线驱动都是由半导体厂商编写的,比如STM32MP1的I2C适配器驱动ST官方已经编写好了,这个不需要用户去编写。因此I2C总线驱动对我们这些SoC使用者来说是被屏蔽掉的,我们只要专注于I2C设备驱动即可,除非你是在半导体公司上班,工作内容就是写I2C适配器驱动。
* ?) D! S; ?. ]+ R/ `40.2.2 I2C总线设备
0 O* T- R! L& X8 ?I2C设备驱动重点关注两个数据结构:i2c_client和i2c_driver,根据总线、设备和驱动模型,I2C总线上一小节已经讲了。还剩下设备和驱动,i2c_client用于描述I2C总线下的设备,i2c_driver则用于描述I2C总线下的设备驱动,类似于platform总线下的platform_device和platform_driver。
1 ~8 C' b4 o9 n8 a: K' R1、i2c_client结构体6 P& J* z# r2 S1 J2 v+ O) ]# y- R
i2c_client结构体定义在include/linux/i2c.h文件中,内容如下:
6 P) h- Z; S# y" i8 [
! h7 G6 }; ~$ P5 ~( l
  1. 示例代码40.2.2.1 i2c_client结构体, o8 f" B3 q8 F; S, ]
  2. 313     struct i2c_client {
    1 R- \6 E. r1 j: X
  3. 314         unsigned short flags;                       /* div., see below              */# B) L& _+ h5 A" X7 o
  4. ......" @0 S7 q$ @$ r; j) t, V
  5. 328         struct i2c_adapter *adapter;        /* the adapter we sit on        */; m" y: Z  u7 G0 @% ~, s. V6 [
  6. 329         struct device dev;                              /* the device structure         */& R7 D9 E1 w6 h% w; n2 l
  7. 330         int init_irq;                  /* irq set at initialization    */0 `6 b4 K6 `3 w) g% B+ G2 K
  8. 331         int irq;                            /* irq issued by device                     */, M5 t) w: ^5 q- z: L; Z
  9. 332         struct list_head detected;- Q) [: `. o) o# P8 Y
  10. 333     #if IS_ENABLED(CONFIG_I2C_SLAVE)
    ; V1 u9 I6 X2 b- Z- H+ J6 _# E, I% k
  11. 334         i2c_slave_cb_t slave_cb;    /* callback for slave mode          */
    4 e. Q$ M+ b3 e( r
  12. 335     #endif
    6 o5 J" o" G" N) W0 i
  13. 336     };
复制代码

7 R6 K2 b' O% [+ x" u; N一个I2C设备对应一个i2c_client结构体变量,系统每检测到一个I2C从设备就会给这个设备分配一个i2c_client。, e- A4 Y, S+ j; E6 h
2、i2c_driver结构体
  F5 A5 s7 y% V4 w& |( W# ki2c_driver类似platform_driver,是我们编写I2C设备驱动重点要处理的内容,i2c_driver结构体定义在include/linux/i2c.h文件中,内容如下:
( P1 K, ~) [8 y$ Z; l
6 `5 t0 Z( Y1 H& d1 o( c! ~
  1. 示例代码40.2.2.2 i2c_driver结构体
    ' G$ ]( _( R( K+ f+ S5 I5 X
  2. 253     struct i2c_driver {
    % f/ v3 h5 W+ q* G& d  X
  3. 254         unsigned int class;
    " i* ^) g8 j2 q7 h% u
  4. 255 2 V9 i% o" S2 K( [6 W; ~- ]
  5. 256         /* Standard driver model interfaces */* j% U6 h: d0 i2 t* J* M
  6. 257         int (*probe)(struct i2c_client *client, 5 f. F" ^) S( L; O. Q
  7. const struct i2c_device_id *id);! i* c- j% L6 I' p) [- b
  8. 258         int (*remove)(struct i2c_client *client);
    4 L& @0 M1 ^) u8 r
  9. 259 3 e. J! ?: O8 \! T( Q* N7 s
  10. 260         /* New driver model interface to aid the seamless removal of
    # y* `7 z; e4 O. K
  11. 261          * the current probe()'s, more commonly unused than used
    ' o& [* _- D9 {
  12. 262          second parameter.*/
    & Q% x3 E" G6 J2 A) u
  13. 263         int (*probe_new)(struct i2c_client *client);
    5 I' B1 d* _+ z8 U
  14. 264 , \3 j0 Z4 ]5 A1 y( {* X* n
  15. 265       /* driver model interfaces that don't relate to enumeration  */( u  N5 N( E( }9 L0 b
  16. 266         void (*shutdown)(struct i2c_client *client);! m" g* F" d! U
  17. 267
    / a1 Z; B  @" x6 ^9 y$ }- |5 |8 q
  18. 268         /* Alert callback, for example for the SMBus alert protocol.9 Q4 b! T! h. K* m$ a$ {
  19. 269          * The format and meaning of the data value depends on the   k* _, |, R! _
  20. 270          * protocol. For the SMBus alert protocol, there is a single : G& \& [1 s9 }$ r* {
  21. 271          * bit of data passed as the alert response's low bit ("event " z2 s& _" p% U# h9 p
  22. 272          * flag"). For the SMBus Host Notify protocol, the data
    # z2 D3 j) Q& H$ U, _8 N) ~* e
  23. 273          * corresponds to the 16-bit payload data reported by the
    0 C- `6 p: ], M: r' T
  24. 274          slave device acting as master.*/
    ! h8 i; \5 {# k
  25. 275         void (*alert)(struct i2c_client *client,
    ) |2 u  ^2 j0 I" w
  26. enum i2c_alert_protocol protocol,
    7 H  C, d  T- I8 B3 F2 V
  27. 276                          unsigned int data);
    ; B  n3 P7 m9 t8 U. Z! v' f
  28. 277 , m3 c2 \$ t0 P  M
  29. 278         /* a ioctl like command that can be used to perform specific   Y" y$ ~( }/ x2 g4 N0 u
  30. 279          * functions with the device.
    ' K; r# E4 Q7 o( H8 y
  31. 280          */
    2 c# ^; i& j" S" T1 v' J
  32. 281         int (*command)(struct i2c_client *client, unsigned int cmd, ( G# O3 y3 C' p$ j; Q  t; O
  33. void *arg);
    " }! u. n8 N. j+ I
  34. 282
    , E  F0 h' k' J& e0 m! _
  35. 283         struct device_driver driver;
    8 @1 p- H# c2 B
  36. 284         const struct i2c_device_id *id_table;
    : R" m- r9 \! v/ v( z* \
  37. 285 + ~) l6 W% J' b/ Q# I, w
  38. 286         /* Device detection callback for automatic device creation */
    5 R7 o! o  e) }# G
  39. 287         int (*detect)(struct i2c_client *client,
    ' J0 f* j2 [9 t& K7 S  Y
  40. struct i2c_board_info *info);4 b% H" B" P1 e, B
  41. 288         const unsigned short *address_list;" I( L+ }; {& [( y
  42. 289         struct list_head clients;$ v# L% Q. {+ r/ a0 l, Z) A, ^
  43. 290
    5 W* X2 ~$ g( {" H
  44. 291         bool disable_i2c_core_irq_mapping;
    % c, [, ~+ `( |0 I
  45. 292     };
复制代码

* Y% a2 q6 u3 f& v0 b9 L第257行,当I2C设备和驱动匹配成功以后probe函数就会执行,和platform驱动一样。
( o% @/ A7 C0 @  F8 K第283行,device_driver驱动结构体,如果使用设备树的话,需要设置device_driver的of_match_table成员变量,也就是驱动的兼容(compatible)属性。$ k7 G3 t4 R. r; ?1 h" V
第284行,id_table是传统的、未使用设备树的设备匹配ID表。2 R- k2 z$ Q( }
对于我们I2C设备驱动编写人来说,重点工作就是构建i2c_driver,构建完成以后需要向I2C子系统注册这个i2c_driver。i2c_driver注册函数为int i2c_register_driver,此函数原型如下:
8 I. l$ p- Z3 b. X  i. Aint i2c_register_driver(struct module *owner,7 z- }8 p5 h" n# j+ h
struct i2c_driver *driver)
, e2 d! G6 y3 B4 w函数参数和返回值含义如下:
3 V3 z3 Z- y5 K$ ?+ Y* X7 U$ Y2 \owner:一般为THIS_MODULE。
/ V& X$ i$ G/ s3 p" rdriver:要注册的i2c_driver。6 _$ S4 k# m( Y$ h% O
返回值:0,成功;负值,失败。
' e0 x$ y, v6 B# k" V" t; |另外i2c_add_driver也常常用于注册i2c_driver,i2c_add_driver是一个宏,定义如下:
/ _8 p: F* E5 ^; z# \9 H8 i6 T; E: j2 `4 Z0 z- f  Y4 G
  1. 示例代码40.2.2.3 i2c_add_driver宏$ c# R0 t- `3 s- D) b' @4 T
  2. 844     #define i2c_add_driver(driver) \. P9 g. l' h' X# k/ \4 {6 z3 ]! a
  3. 845         i2c_register_driver(THIS_MODULE, driver)
复制代码

' r4 h" C* V$ E' {, {; Li2c_add_driver就是对i2c_register_driver做了一个简单的封装,只有一个参数,就是要注册的i2c_driver。
# Q( D# ~2 l) U  ]/ o) E$ L0 u注销I2C设备驱动的时候需要将前面注册的i2c_driver从I2C子系统中注销掉,需要用到i2c_del_driver函数,此函数原型如下:
9 f' Q7 j$ U1 L3 @void i2c_del_driver(struct i2c_driver *driver)
9 }. ^9 Y8 O# v! @; n函数参数和返回值含义如下:3 g4 F& n$ a* ^9 ?. m: N8 _
driver:要注销的i2c_driver。5 j# r/ d  _3 g) M% k2 v
返回值:无。
; C1 U& _6 O3 k" `2 V( Hi2c_driver的注册示例代码如下:
4 Q2 t) z6 d, _, F4 i4 }* R1 n7 o2 |# w5 e$ I$ U
  1. 示例代码40.2.2.4 i2c_driver注册流程
    9 z4 P, C( ?( l1 H3 j
  2. 1  /* i2c驱动的probe函数 */
    2 ^) L! B9 I* G1 U- P1 b
  3. 2  static int xxx_probe(struct i2c_client *client, * _7 `/ ]) {1 G' Y7 g5 t' @" v: u% r
  4. const struct i2c_device_id *id)- g0 c( W$ J7 [. @
  5. 3  {
    # D$ B$ J& t0 `7 H* y, s" U" y8 l3 b
  6. 4           /* 函数具体程序 */
    9 S  @  W) E  c% P/ a
  7. 5           return 0;# g3 y/ U: m3 O
  8. 6  }
    ( c$ a1 w& m- u4 a2 z( d
  9. 7  
    % E! b4 z; y2 P
  10. 8  /* i2c驱动的remove函数 */' ]4 |, u4 N2 k, N+ }
  11. 9  static int ap3216c_remove(struct i2c_client *client)& s2 @* P4 S1 J7 f6 O" X$ Y/ X0 }
  12. 10 {
    * N3 O% o. }0 E& J9 X) L
  13. 11          /* 函数具体程序 */
    ! S% P4 B& d! U$ \; G6 e5 j
  14. 12          return 0;' I$ S4 j' T* u+ }1 }2 X7 T/ g5 m# D
  15. 13 }
    / K" A9 A; y! n3 V! ]
  16. 14 8 ?( L6 ^1 Y0 Z* \. l
  17. 15 /* 传统匹配方式ID列表 */8 d: f! Y" U- H  g" J' [
  18. 16 static const struct i2c_device_id xxx_id[] = {
    1 W; c( _# T/ \4 H* `
  19. 17          {"xxx", 0},  
    3 a5 _: i% V6 [7 M
  20. 18          {}; {; L. ^' p" C/ c! m
  21. 19 };$ z( O) A3 x& y( v; t) V/ W
  22. 20
    & `  a' s6 _2 T
  23. 21 /* 设备树匹配列表 */8 O" j7 P' s3 Q( K: H& B6 r+ X& |' `
  24. 22 static const struct of_device_id xxx_of_match[] = {8 m( s2 P0 [( d+ }3 R! x3 n
  25. 23          { .compatible = "xxx" },
    : G; n! E- _5 {. ?- H! F, N5 V/ ?
  26. 24          { /* Sentinel */ }
    * D( n( ]! i$ ]. K: z4 M8 [2 R8 d5 Y
  27. 25 };3 l' Q7 c! @" _" e( T
  28. 26 5 ^. d% n: n. U0 O3 ?0 f
  29. 27 /* i2c驱动结构体 */   |; \: j& T& l2 A/ H7 i
  30. 28 static struct i2c_driver xxx_driver = {3 G' m2 Y6 B/ H! i  j# y! Q/ y$ N
  31. 29          .probe = xxx_probe,. S. ]6 T) P! q) |6 [" |2 J* b
  32. 30          .remove = xxx_remove,
    8 B5 H' D/ ~! X4 V/ M1 `
  33. 31          .driver = {" U6 d' X( j6 ^
  34. 32                  .owner = THIS_MODULE,6 i! N% m% W, M: T: w& v
  35. 33                  .name = "xxx",
    3 B4 O4 V) E6 H" n! y4 n$ N
  36. 34                  .of_match_table = xxx_of_match,
    4 E' J0 K+ p6 F- w! d1 t
  37. 35                 },
    , e$ J/ R2 ~- c6 k, W. k
  38. 36                  .id_table = xxx_id,
    6 B, S7 U+ F- a1 |
  39. 37                 };
    $ m" c7 w6 r- I8 [' z8 U
  40. 38         1 |; c" `( k- S8 U
  41. 39 /* 驱动入口函数 */
    # T- |+ V) b' |* C; X
  42. 40 static int __init xxx_init(void)
    . Z! W! ~# j- Z8 \1 i
  43. 41 {" `0 r& W) J2 X# P2 T
  44. 42          int ret = 0;6 t6 f  n& p- k* p1 s" Q# J( X% O' U% N
  45. 43 % T6 c. `) h% S! k
  46. 44          ret = i2c_add_driver(&xxx_driver);
    & Z: w$ G: \" u5 z) g0 J& l
  47. 45          return ret;
    : u' p) V. _+ [2 x9 z* l" B
  48. 46 }
    2 T! n, u9 m5 g7 L% _4 u
  49. 47 0 e" h9 ^+ K6 E) S6 D( O9 P" v
  50. 48 /* 驱动出口函数 */. W, Y: A& R$ w6 h6 j3 q
  51. 49 static void __exit xxx_exit(void)
    * v6 {3 L0 J+ F6 d( w. }. @- m
  52. 50 {% ]% n+ W  [! l' f" e$ @
  53. 51          i2c_del_driver(&xxx_driver);1 v: v9 G$ Z, V$ u! v6 }
  54. 52 }
      P- S: t: @/ J$ V$ c7 n
  55. 53 " D/ @- P' L- z) X: [5 E5 k# K7 \
  56. 54 module_init(xxx_init);
    + V& s# r" |, e8 z* ~) M* G
  57. 55 module_exit(xxx_exit);
    & l% s$ y3 e9 u* [
复制代码
* n- @3 l' x* {9 U
第16~19行,i2c_device_id,无设备树的时候匹配ID表。
, L- \( N  H; r4 t' ?* y1 f  f第22~25行,of_device_id,设备树所使用的匹配表。
# e* U2 p* ^* t' c第28~37行,i2c_driver,当I2C设备和I2C驱动匹配成功以后probe函数就会执行,这些和platform驱动一样,probe函数里面基本就是标准的字符设备驱动那一套了。3 j! E, ]4 J. b- E

" Q" u8 p. O' K40.2.3 I2C 设备和驱动匹配过程
- a3 M, G' k$ i" r/ r( f' tI2C设备和驱动的匹配过程是由I2C子系统核心层来完成的,drivers/i2c/i2c-core-base.c就是I2C的核心部分,I2C核心提供了一些与具体硬件无关的API函数,比如前面讲过的:
+ F# |6 U- d- ~7 |; F$ b1、i2c_adapter注册/注销函数
& I) A+ l6 W& ~$ V) l! hint i2c_add_adapter(struct i2c_adapter *adapter)
( q# D6 ~2 E6 c* |4 Eint i2c_add_numbered_adapter(struct i2c_adapter *adap)
! D: S' `: s  M! U7 zvoid i2c_del_adapter(struct i2c_adapter * adap)* `, a6 ^) E, ~% Y% Y
2、i2c_driver注册/注销函数9 t/ ~0 J; |# N
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)! C5 G/ P' |% A& l- G
int i2c_add_driver (struct i2c_driver *driver)
7 h( f& D! d; S8 M- w# c1 fvoid i2c_del_driver(struct i2c_driver *driver)
- a- b% a2 f9 l8 \1 J设备和驱动的匹配过程也是由核心层完成的,I2C总线的数据结构为i2c_bus_type,定义在drivers/i2c/i2c-core-base.c文件,i2c_bus_type内容如下:
9 u! }/ x! F: `
: `9 m6 |6 f3 o- r$ F+ J8 G
  1. 示例代码40.2.3.1 i2c_bus_type结构体
    2 Y  R$ R5 h& d. g+ N( e$ ^5 l
  2. 492 struct bus_type i2c_bus_type = {
    ( B' X& r$ D! V) k
  3. 493     .name             = "i2c",
      [3 O* k2 }% O
  4. 494     .match            = i2c_device_match,
    # V+ h1 S' i8 s9 ~; [, i, I$ C
  5. 495     .probe            = i2c_device_probe,0 F' \9 e2 P, k# d: k# ^
  6. 496     .remove           = i2c_device_remove,
    4 J. i' s: @% \' R# i2 m/ l2 \
  7. 497     .shutdown         = i2c_device_shutdown,
    * D, S4 f6 d' C9 l
  8. 498 };
复制代码

4 r0 h0 A. i$ `. s.match就是I2C总线的设备和驱动匹配函数,在这里就是i2c_device_match这个函数,此函数内容如下:
2 J4 w) h& M) X$ o5 ]. b* j; v7 v5 B' ^' X
  1. 示例代码40.2.3.2 i2c_device_match函数
    . j! m! ~/ y3 p, p
  2. 93  static int i2c_device_match(struct device *dev,
    2 Y3 \6 R% t" e* R
  3. struct device_driver *drv)
    2 S1 x7 q5 P. P/ C( Z7 x
  4. 94  {
    - B. W, L' C% _' K3 |
  5. 95      struct i2c_client   *client = i2c_verify_client(dev);
    1 I3 @( Y6 j& w9 C# F; X
  6. 96      struct i2c_driver   *driver;
    ' h* {' r6 c0 \& H  b
  7. 97 ) B. ]8 s5 m$ a, D2 A8 i, T
  8. 98
    - ]1 T" G0 {+ J* ^. `9 J! s
  9. 99      /* Attempt an OF style match */8 P; W! {* i2 o' S
  10. 100     if (i2c_of_match_device(drv->of_match_table, client))
    4 A2 n6 |: l8 q' K9 l, ]
  11. 101         return 1;: @& p: h6 V! t# x, w. B" {
  12. 102
    ( [; V$ N2 D- ?1 V1 c6 z, ^& B
  13. 103     /* Then ACPI style match */
    : H+ z1 k! ]8 ^5 e+ A) o- N; M
  14. 104     if (acpi_driver_match_device(dev, drv))& h; Z" H3 u+ M5 A5 h
  15. 105         return 1;
    0 X& K( C& w5 {! @9 Q- u. M0 s! Z
  16. 106
    + ~% Y& |0 C! j0 {2 O
  17. 107     driver = to_i2c_driver(drv);
    # Z) _  |! d" y5 ^2 q* n
  18. 108
    0 f$ C7 y, [: K9 d- i1 p' D
  19. 109     /* Finally an I2C match */
    , M1 z8 k2 w2 o( Y
  20. 110     if (i2c_match_id(driver->id_table, client)). b+ s. X- Q  z4 v7 _* R
  21. 111         return 1;
    2 E- S( C9 \5 h
  22. 112
    1 }4 t! D9 v9 e) d# n& Q
  23. 113     return 0;
    7 r( n6 b$ p1 p7 Z6 V' f) X
  24. 114 }
复制代码

" b; k8 a) s. I# }4 i第100行,i2c_of_match_device函数用于完成设备树中定义的设备与驱动匹配过程。比较I2C设备节点的compatible属性和of_device_id中的compatible属性是否相等,如果相当的话就表示I2C设备和驱动匹配。
3 A7 k! ?7 f0 f1 q第104行,acpi_driver_match_device函数用于ACPI形式的匹配。
, w) [( l# k. |  z! f1 M第110行,i2c_match_id函数用于传统的、无设备树的I2C设备和驱动匹配过程。比较I2C设备名字和i2c_device_id的name字段是否相等,相等的话就说明I2C设备和驱动匹配成功。5 Q; J8 }0 d& ^' J6 L
40.3 STM32MP1 I2C 适配器驱动分析, S# K& |8 [& M
上一小节我们讲解了Linux下的I2C子系统,重点分为I2C适配器驱动和I2C设备驱动,其中I2C适配器驱动就是SoC的I2C控制器驱动。I2C设备驱动是需要用户根据不同的I2C从设备去编写,而I2C适配器驱动一般都是SoC厂商去编写的,比如ST就已经提供了STM3MP21的I2C适配器驱动程序。在内核源码arch/arm/boot/dts/stm32mp151.dtsi设备树文件中找到STM32MP1的I2C控制器节点,节点内容如下所示:
+ W. j- F+ o/ B+ ]5 H9 ^6 w; O% M, m$ \0 w) L; e$ f8 g
  1. 示例代码40.3.1 I2C1控制器节点
    5 u9 o3 P2 E3 U
  2. 590     i2c1: i2c@40012000 {
    / r* g/ Q* }- ^' R# X( M% Y
  3. 591         compatible = "st,stm32mp15-i2c";9 ~' i& k, Z" f6 D4 b
  4. 592         reg = <0x40012000 0x400>;
    " O+ b% L) N3 ]5 t" A8 G% Y
  5. 593         interrupt-names = "event", "error";
    3 l, |2 G8 l1 A9 K% ~) c6 t
  6. 594         interrupts-extended = <&exti 21 IRQ_TYPE_LEVEL_HIGH>,; X8 F& g( C% n$ {
  7. 595                       <&intc GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
    ) ^9 h0 e- s5 S9 @1 X5 l, l' m" L
  8. 596         clocks = <&rcc I2C1_K>;/ r) q# e) b. q" W. \% b
  9. 597         resets = <&rcc I2C1_R>;: ]2 k5 a2 r9 {
  10. 598         #address-cells = <1>;+ z' t: l! U( V
  11. 599         #size-cells = <0>;
    # {0 n+ K& @6 Z
  12. 600         dmas = <&dmamux1 33 0x400 0x80000001>,
    & [1 c: e1 E% i
  13. 601                <&dmamux1 34 0x400 0x80000001>;; d1 T6 K; _, X! b' m
  14. 602         dma-names = "rx", "tx";3 h8 h) {% b+ X  |$ o  E
  15. 603         power-domains = <&pd_core>;" `. M& v+ @1 V1 g
  16. 604         st,syscfg-fmp = <&syscfg 0x4 0x1>;
    % y; f) M1 B2 V
  17. 605         wakeup-source;
    : [+ j5 T" K' ^8 @; e
  18. 606         status = "disabled";
    ' j* R" D. N+ B. J, s
  19. 607     };
复制代码
0 h$ n: @6 E5 f4 e: p
重点关注i2c1节点的compatible属性值,因为通过compatible属性值可以在Linux源码里面找到对应的驱动文件。这里i2c1节点的compatible属性值“st,stm32mp15-i2c”,在Linux源码中搜索这个字符串即可找到对应的驱动文件。STM32MP1的I2C适配器驱动驱动文件为drivers/i2c/busses/i2c-stm32f7.c,在此文件中有如下内容:
) K' l, K1 l$ o$ y- @
" O! Y( b- K: z/ g5 y) d3 q
  1. 示例代码40.3.2 i2c-stm32f7.c文件代码段
    1 f7 t6 l1 P) [: M5 x. ]% f
  2. 2520    static const struct of_device_id stm32f7_i2c_match[] = {
    8 A, H0 s  `) F4 b/ ~- \
  3. 2521     { .compatible = "st,stm32f7-i2c", .data = &stm32f7_setup},5 @6 D8 S' @4 |& [$ ^; @
  4. 2522     { .compatible = "st,stm32mp15-i2c", .data = &stm32mp15_setup},! u8 p3 w) `5 y" U
  5. 2523     {},
    $ b4 q$ e: v* Z& Y  i0 \9 @
  6. 2524    };
    2 ~0 E6 v' d! G( c, E  W
  7. 2525    MODULE_DEVICE_TABLE(of, stm32f7_i2c_match);- a: ^9 i, t! t  |5 `! K, U) O2 ~" O
  8. 2526
    # V, p0 w2 {* D2 B$ l& e4 i0 k
  9. 2527    static struct platform_driver stm32f7_i2c_driver = {# M5 M3 U: l# Y3 f' P6 t& h$ g% z
  10. 2528        .driver = {+ b# N; C' B0 d; Q
  11. 2529            .name = "stm32f7-i2c",2 T1 D7 s% U. s# Z
  12. 2530            .of_match_table = stm32f7_i2c_match,' @( x3 b5 J) u; a0 Y/ V
  13. 2531            .pm = &stm32f7_i2c_pm_ops,1 s0 I! q# W! v5 b
  14. 2532        },
    ' U) V; |* w4 l) `# y7 F  r: A
  15. 2533        .probe = stm32f7_i2c_probe,
    3 L$ E0 y: `1 r: Z7 {
  16. 2534        .remove = stm32f7_i2c_remove,7 }" ^$ m# T. E/ h; r
  17. 2535    };
    + Q+ v. G1 T8 G' j9 C
  18. 2536
    2 s* |( f4 F& I3 Y/ S
  19. 2537    module_platform_driver(stm32f7_i2c_driver);
复制代码
9 \2 O9 X( H+ G
从示例代码40.3.2可以看出,STM32MP1的I2C适配器驱动是个标准的platform驱动,由此可以看出,虽然I2C总线为别的设备提供了一种总线驱动框架,但是I2C适配器却是platform驱动。就像你的部门老大是你的领导,你是他的下属,但是放到整个公司,你的部门老大却也是老板的下属。6 s+ e9 Y9 q, e+ o
第2529行,“st,stm32mp15-i2c”属性值,设备树中 i2c1节点的compatible属性值就是与此匹配上的。因此i2c-stm32f7.c文件就是STM32MP1的I2C适配器驱动文件。
$ y0 S7 h) p8 M" _1 F第2533行,当设备和驱动匹配成功以后stm32f7_i2c_probe函数就会执行,stm32f7_i2c_probe函数就会完成I2C适配器初始化工作。9 M# K2 [  s2 A& W% X
stm32f7_i2c_probe函数内容如下所示(有省略):
5 r9 o. y0 Z$ m5 f' T! ^
; n' G7 H, X  r7 k0 `
  1. 示例代码40.3.3 stm32f7_i2c_probe函数代码段
    ; u* I) U0 m, j
  2. 2106    static int stm32f7_i2c_probe(struct platform_device *pdev)8 Q- N2 W0 o& P6 L* B4 e
  3. 2107    {+ H" `8 O. ]' I1 w$ s- C4 G8 a% _
  4. 2108        struct stm32f7_i2c_dev *i2c_dev;
    3 n' \5 d4 u1 }1 s) S4 @( P& v* m  S
  5. 2109        const struct stm32f7_i2c_setup *setup;
    . O" x" w+ l" X3 J
  6. 2110        struct resource *res;. S/ X/ n: m( {5 c$ V% G" K' K% O
  7. 2111        u32 rise_time, fall_time;! M( m" U+ M( v/ ^* D
  8. 2112        struct i2c_adapter *adap;
    2 |- D5 `/ U% N! Q
  9. 2113        struct reset_control *rst;
    : K2 g* p! n) X9 L
  10. 2114        dma_addr_t phy_addr;
    . a, Z: E: s7 h6 N
  11. 2115        int irq_error, ret;
      O) E' l* N! l) h( s# g
  12. 2116
      y; Q% v8 f/ |5 M/ {2 b% Y! h
  13. 2117        i2c_dev = devm_kzalloc(&pdev->dev, sizeof(*i2c_dev),   N) O0 E# R' a* U. _' W" B1 ]
  14. GFP_KERNEL);/ _: y; a2 h4 f& K
  15. 2118        if (!i2c_dev)6 C& d) o3 C7 _5 z  b! ?
  16. 2119            return -ENOMEM;
    ' u' @& D0 {1 H* S8 t
  17. 2120
    5 R" r( a9 E5 H" r4 W+ W
  18. 2121        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);- x3 J( A/ H6 b! f0 J
  19. 2122        i2c_dev->base = devm_ioremap_resource(&pdev->dev, res);
    , S" _' u! h* _6 R5 O
  20. 2123        if (IS_ERR(i2c_dev->base))
    ( E+ f( b3 z3 S8 A* U
  21. 2124            return PTR_ERR(i2c_dev->base);! J: z. u$ I# Y8 _1 m& M+ Q
  22. 2125        phy_addr = (dma_addr_t)res->start;& I( C! S; d. S( Y1 H# L& z
  23. 2126% M0 o- K& }( n+ @3 ]
  24. 2127        i2c_dev->irq_event = platform_get_irq(pdev, 0);
    ( x! {; N' y% \( V; u: y
  25. 2128        if (i2c_dev->irq_event <= 0) {  O5 L. B5 r4 t. q2 R, y
  26. 2129            if (i2c_dev->irq_event != -EPROBE_DEFER)
    7 g% }3 u! w2 |* {, ?, v
  27. 2130                dev_err(&pdev->dev, "Failed to get IRQ event: %d\n",
    ! T# K0 K. u8 @+ M) N- A
  28. 2131                    i2c_dev->irq_event);
    ; g/ W4 U& @) i5 N
  29. 2132            return i2c_dev->irq_event ? : -ENOENT;* B( H; p% U2 R3 T
  30. 2133        }
    ! h, r* r0 I% b" l# q# |# b: {2 G8 \
  31. 2134
    ) ?) R% v9 g- k8 v
  32. 2135        irq_error = platform_get_irq(pdev, 1);
    ' D  m  z) H3 A
  33. 2136        if (irq_error <= 0) {  I) _8 w3 w: i' k6 l& {
  34. 2137            if (irq_error != -EPROBE_DEFER)) Y* m  z( B8 y- C7 i( l3 s# J
  35. 2138                dev_err(&pdev->dev, "Failed to get IRQ error: %d\n"," C" o9 A6 H( j3 i* y+ C
  36. 2139                    irq_error);
    . _: D. j0 K9 ^, e) P. |( ~% K3 L
  37. 2140            return irq_error ? : -ENOENT;
    / H" {0 i( r. Z" u4 K0 S0 ~
  38. 2141        }0 \# L' S8 ]2 o2 z; p
  39. ......0 R  \* ^# x3 R6 I) j* n  A1 R
  40. 2159        ret = device_property_read_u32(&pdev->dev, "clock-frequency",
    + B$ d8 O% h+ Y2 Z# V; n7 ~: y
  41. 2160                           &i2c_dev->bus_rate);# |" I8 J. {! {
  42. 2161        if (ret)
    6 B4 ?& ]2 b/ w3 h. I1 c: m0 P! {
  43. 2162            i2c_dev->bus_rate = I2C_STD_RATE;* O: s. K7 i/ C6 E9 @- M; s
  44. 21633 g3 N$ i$ _9 h8 b0 B8 s: W7 Y
  45. 2164        if (i2c_dev->bus_rate > I2C_FASTPLUS_RATE) {4 s9 j( P' o% J( x& L) S
  46. 2165            dev_err(&pdev->dev, "Invalid bus speed (%i>%i)\n",
    2 y2 a* ?& |% m
  47. 2166                i2c_dev->bus_rate, I2C_FASTPLUS_RATE);# y/ _, Z9 m+ W" @) B
  48. 2167            return -EINVAL;0 f- J0 u) D) A: w1 L4 N$ u  a1 w
  49. 2168        }. }" e( }3 t! F  t8 {6 h
  50. ......2 Y* U! N' Q! y& o
  51. 2183
    + e3 z3 K3 s+ m/ \3 a1 {- {
  52. 2184        ret = devm_request_threaded_irq(&pdev->dev, ! H4 s( T+ W3 i% ^0 L8 _9 z) V
  53. i2c_dev->irq_event,* [$ Y4 c0 S5 J9 _/ p. }* g6 i
  54. 2185                        stm32f7_i2c_isr_event,7 B7 v- l1 e. r, y
  55. 2186                        stm32f7_i2c_isr_event_thread,8 ?2 o) A1 J+ _" F& v
  56. 2187                        IRQF_ONESHOT,) Y2 k/ t: P9 m" {, j6 C
  57. 2188                        pdev->name, i2c_dev);
    " k& ~( H% X% I- d
  58. 2189        if (ret) {
    " G' M5 h: E% U" P5 \& t
  59. 2190            dev_err(&pdev->dev, "Failed to request irq event %i\n",# ?- H2 K# w3 C6 m% B7 e
  60. 2191                i2c_dev->irq_event);
    8 T5 i: x3 Y9 ?5 V9 G
  61. 2192            goto clk_free;
    8 I$ ^" Q* Z; }# n
  62. 2193        }
    5 v7 U+ Z0 z+ H( @
  63. 2194
    0 X  \- B" m- {: }% B8 V' A
  64. 2195        ret = devm_request_irq(&pdev->dev, irq_error,
    - Z  C0 l2 ^  S
  65. stm32f7_i2c_isr_error, 0,! M# A) K8 c7 L
  66. 2196                       pdev->name, i2c_dev);
    ; T9 `1 E8 u- J: e4 X1 W
  67. 2197        if (ret) {
    ) ^4 `- b0 _: l- Y- N
  68. 2198            dev_err(&pdev->dev, "Failed to request irq error %i\n",* m4 R5 L! J5 n$ A7 Z7 v! x( N
  69. 2199                irq_error);
      L, V8 D0 F1 m- ^% x
  70. 2200            goto clk_free;* }  O1 _: ~% o- F
  71. 2201        }
    * ^- N7 p/ H7 c2 l2 U+ B
  72. , A' F! w) O; R! o$ |9 L
  73. 2226        if (i2c_dev->bus_rate > I2C_FAST_RATE) {+ J  z7 C  i5 b9 Q6 n8 f
  74. 2227            ret = stm32f7_i2c_setup_fm_plus_bits(pdev, i2c_dev);
    ) w* V8 b  O' d% l/ q$ \- \
  75. 2228            if (ret)
    " c) b# x8 ]  g* R
  76. 2229                goto clk_free;
      p  A3 p8 J& u1 o$ g
  77. 2230        }
    & z! X7 r$ t6 j7 N  S% j
  78. 22310 [) e8 _" l. B# Z  Q
  79. 2232        adap = &i2c_dev->adap;" [- [2 V9 q: z: M- Y3 x
  80. 2233        i2c_set_adapdata(adap, i2c_dev);
    " ?& r8 o0 E7 A
  81. 2234        snprintf(adap->name, sizeof(adap->name), "STM32F7 I2C(%pa)",2 V: r7 K" r4 ^; t, R2 I' ~
  82. 2235             &res->start);
    % r) {2 k! T$ D! I" I- Y
  83. 2236        adap->owner = THIS_MODULE;
    0 M- I0 i0 ], @+ m- C, P
  84. 2237        adap->timeout = 2 * HZ;/ P$ M  s9 e5 {, K/ `9 s
  85. 2238        adap->retries = 3;
    / _/ s! X5 z5 i, r
  86. 2239        adap->algo = &stm32f7_i2c_algo;- u! S  l3 O3 R
  87. 2240        adap->dev.parent = &pdev->dev;
      _$ P3 \8 r" _" b) K# c0 C" J
  88. 2241        adap->dev.of_node = pdev->dev.of_node;
    8 ?# h* V4 e- y6 s+ t7 j2 d
  89. 2242
    & O( r* P* K6 x  D
  90. 2243        init_completion(&i2c_dev->complete);( i) z% N" Y8 e9 s
  91. 2244) X+ d0 w' d% h3 [6 F8 G! s% }$ S0 o
  92. 2245        /* Init DMA config if supported */
    ' D; e1 E9 Q/ C( j: R1 W7 T% J
  93. 2246        i2c_dev->dma = stm32_i2c_dma_request(i2c_dev->dev, phy_addr,. k( n& L! o0 ?5 q3 w# o; j9 J
  94. 2247                             STM32F7_I2C_TXDR,8 ~9 m* Z- {9 r, j" k
  95. 2248                             STM32F7_I2C_RXDR);5 z# r: n  X* E4 n+ E% ~; A' t# `
  96. 2249        if (PTR_ERR(i2c_dev->dma) == -ENODEV)
    . L# {( D% y+ v$ g& K" E
  97. 2250            i2c_dev->dma = NULL;7 z5 I( Z6 k" W  D' Y
  98. 2251        else if (IS_ERR(i2c_dev->dma)) {
    1 X' G. U+ N0 b+ ?% S7 _
  99. 2252            ret = PTR_ERR(i2c_dev->dma);- T/ l- N& e8 e+ P! Y
  100. 2253            goto fmp_clear;" [0 W* K# }- U5 u% D8 s; X$ G* O
  101. 2254        }4 `4 `1 r3 I& Z8 K$ t0 \5 R2 \2 w
  102. ......, O4 m0 h( M4 b5 x  b, F
  103. 2276        stm32f7_i2c_hw_config(i2c_dev);+ V8 G+ J7 A9 W2 y, A+ c5 Q
  104. 2277
    , }- m8 i& l4 A5 Z9 F
  105. 2278        ret = i2c_add_adapter(adap);
    1 g. M$ s3 S; R, H
  106. 2279        if (ret)
    , b: X3 R& q$ z0 ~" t
  107. 2280            goto pm_disable;6 _) @) w; V) z; \' w
  108. ......
    $ |) M' S) M0 p1 @  p# b% }# y
  109. 2307        return 0;
    5 m8 ^+ J3 j1 U* {: k) R1 Y8 R/ O7 `
  110. ......
    . [" a. \  N. V/ a/ u4 a
  111. 2340    }
复制代码
; l0 m5 B( ]* I" c/ t' Y4 n# G
第2117行,ST使用stm32f7_i2c_dev结构体来表示STM32MP1系列SOC的I2C控制器,这里使用devm_kzalloc函数来申请内存。
, Z, U& D9 c: J" z; l4 J$ w; H! i第2121~2122行,调用platform_get_resource函数从设备树中获取I2C1控制器寄存器物理基地址,也就是0x40012000。获取到寄存器基地址以后使用devm_ioremap_resource函数对其进行内存映射,得到可以在Linux中使用的虚拟地址。# R; @1 e2 y) E( r0 M& B% \
第2127行和第2135行,调用platform_get_irq函数获取中断号。- e2 ^& a6 A8 l, D; T& D- r6 {
第2159~2160行,设置I2C频率默认为I2C_STD_RATE=100KHz,如果设备树节点设置了“clock-frequency”属性的话 I2C 频率就使用 clock-frequency 属性值。/ H) U' d0 e0 b
第2184~2196行,注册I2C控制器的两个中断。
4 E% ^0 K' \% f, ~- h第2232~2241行,stm32f7_i2c_dev结构体有个adap的成员变量,adap就是i2c_adapter,这里初始化i2c_adapter。第2239行设置i2c_adapter的algo成员变量为stm32f7_i2c_algo,也就是设置i2c_algorithm。
4 V8 c8 ]; {- F9 x% p( J+ N5 F第2246行,申请DMA,看来STM32MP1的I2C适配器驱动是可以采用DMA方式。
, {) t& s" x! j9 a第2276行,调用stm32f7_i2c_hw_config函数初始化I2C1控制器的相关硬件寄存器。
2 c( s7 n# j8 m! w; Y/ S/ h8 P第2278行,调用i2c_add_adapter函数向Linux内核注册i2c_adapter。
6 {& o7 T" `4 o/ }8 m( T+ B/ Z9 ]stm32f7_i2c_probe 函数主要的工作就是一下两点:
) A6 {! F6 k  T% s①、初始化 i2c_adapter,设置 i2c_algorithm 为 stm32f7_i2c_algo,最后向 Linux 内核注册i2c_adapter。
4 S) u4 S" S" T1 Y5 u1 x( v②、初始化 I2C1 控制器的相关寄存器。stm32f7_i2c_algo包含 I2C1 适配器与 I2C 设备的通信函数 master_xfer,stm32f7_i2c_algo 结构体定义如下:1 s7 x# c2 D: Q
0 O- p  p7 h1 t: x, ]1 o
  1. 示例代码40.3.4 stm32f7_i2c_algo结构体( u* d  e) R# h% o4 J  r, H3 {9 q8 ?
  2. 2098    static const struct i2c_algorithm stm32f7_i2c_algo = {9 c7 j0 r0 E0 Y' k( f0 I
  3. 2099        .master_xfer = stm32f7_i2c_xfer,
    ; i: ]; G7 d  g* l7 K
  4. 2100        .smbus_xfer = stm32f7_i2c_smbus_xfer,
    - k  n1 K& F# {+ Q& V0 R% A
  5. 2101        .functionality = stm32f7_i2c_func,9 a' l$ |) {% v4 Z9 J( {, g; C
  6. 2102        .reg_slave = stm32f7_i2c_reg_slave,  R* }; V0 Q; J: ^* `: i
  7. 2103        .unreg_slave = stm32f7_i2c_unreg_slave,: F9 J3 d3 t4 d
  8. 2104    };
复制代码

; ~! K4 x5 {( `+ ^我们先来看一下. functionality,functionality用于返回此I2C适配器支持什么样的通信协议,在这里functionality就是stm32f7_i2c_func函数,stm32f7_i2c_func函数内容如下:
0 c! N: o, ?# x3 y# k
4 s' k: ]8 N( s+ H
  1. 示例代码40.3.5stm32f7_i2c_func函数
    : `' ?% l8 F' T* _, `3 Q
  2. 2088    static u32 stm32f7_i2c_func(struct i2c_adapter *adap)
    4 }7 y7 M8 F" E
  3. 2089    {; l+ u$ e; T! x
  4. 2090        return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR | I2C_FUNC_SLAVE |
    ( f# r! t7 ?. }* D* F$ M+ }# b
  5. 2091            I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
    2 g! B9 _/ s5 G& B
  6. ......
    8 Y5 u+ J) {' T8 W. W/ g' Y1 U
  7. 2094            I2C_FUNC_SMBUS_PROC_CALL | I2C_FUNC_SMBUS_PEC |
    0 K  l/ A. I4 ]- u; w5 ?* S  C  z
  8. 2095            I2C_FUNC_SMBUS_I2C_BLOCK | I2C_FUNC_SMBUS_HOST_NOTIFY;
    1 X/ r% v% e$ c2 T8 y) {) E
  9. 2096    }
复制代码

4 s9 S2 i0 j4 }8 a8 m+ d重点来看一下stm32f7_i2c_xfer函数,因为最终就是通过此函数来完成与I2C设备通信的,此函数内容如下:9 h- C$ b( k# c) ]: u% P. [

0 M* a! t; G) [, u$ V) Q
  1. 示例代码40.3.6stm32f7_i2c_xfer函数
    7 |% O) \3 }6 ^2 [
  2. 1657    static int stm32f7_i2c_xfer(struct i2c_adapter *i2c_adap,
    # E" s# D1 Y( S
  3. 1658                    struct i2c_msg msgs[], int num)
    # f, m+ K! j1 {( \/ o( U: }
  4. 1659    {- _0 @; I" S4 I9 ?1 U3 l
  5. 1660        struct stm32f7_i2c_dev *i2c_dev = i2c_get_adapdata(i2c_adap);8 w$ ?, f' a" j" i; @8 N! G
  6. 1661        struct stm32f7_i2c_msg *f7_msg = &i2c_dev->f7_msg;
    8 ?& y) I0 `' {4 e* n/ r2 G) K# C: E
  7. 1662        struct stm32_i2c_dma *dma = i2c_dev->dma;  ?  u. }0 x( y7 Z
  8. 1663        unsigned long time_left;
    2 t& d; G; {( e5 E6 j! @" W" j5 c
  9. 1664        int ret;
    / K# U, w& X  o" y0 |! i+ l7 H+ }7 }
  10. 16651 Z% N0 [! f2 W% w/ R7 J' \) d
  11. 1666        i2c_dev->msg = msgs;
    / P2 A% z* W$ F3 Z. W
  12. 1667        i2c_dev->msg_num = num;  N* n" m, @+ n9 D" S
  13. 1668        i2c_dev->msg_id = 0;
    , H) H' Z- ?* D# Q
  14. 1669        f7_msg->smbus = false;. c: {. H( ?' M4 R1 H8 N! A
  15. 1670/ }% A) A) S: M  I8 k
  16. 1671        ret = pm_runtime_get_sync(i2c_dev->dev);3 V/ M- T! Z, K& A
  17. 1672        if (ret < 0)
    & w0 n* |* E9 n
  18. 1673            return ret;" X; S8 L& o6 S; l/ k& X
  19. 1674$ G7 Y; H8 H0 |
  20. 1675        ret = stm32f7_i2c_wait_free_bus(i2c_dev);
    : V) N3 w) N( |1 s% {" E0 J
  21. 1676        if (ret)$ d4 a. e5 N- x  r
  22. 1677            goto pm_free;3 t2 K7 M) q6 Q0 _& I& o
  23. 1678- i. r  d( e9 l/ Y
  24. 1679        stm32f7_i2c_xfer_msg(i2c_dev, msgs);0 V3 f' P5 P" v. b
  25. 16801 b$ R; m0 \* c6 B* w$ r+ n; o+ ]. Y) T% s
  26. 1681        time_left = wait_for_completion_timeout(&i2c_dev->complete,
    ' S1 i  y& ^5 p! Z0 ]/ D: ]
  27. 1682                            i2c_dev->adap.timeout);
    $ y9 o) T! p) Z% H; a
  28. 1683        ret = f7_msg->result;
    ! ~. b3 ^" g) u1 E
  29. 16843 r7 D# d; l4 q
  30. 1685        if (!time_left) {
    5 Y4 [5 g3 `- O
  31. 1686            dev_dbg(i2c_dev->dev, "Access to slave 0x%x timed out\n",( R, O, H7 T) _0 s' D
  32. 1687                i2c_dev->msg->addr);
    7 r$ O, u' Z2 ~
  33. 1688            if (i2c_dev->use_dma)% r. a0 A$ k/ P- b0 Y: n
  34. 1689                dmaengine_terminate_all(dma->chan_using);
    - e2 l1 v% h- [: j
  35. 1690            ret = -ETIMEDOUT;9 b" ^$ l" @: Y5 v) O8 @3 W& Y
  36. 1691        }3 ]5 ^9 }5 t6 X% [+ N. H. \; x. H
  37. 1692
    4 @( P  U5 {) L
  38. 1693    pm_free:, {" z+ r% Z8 l6 N% W
  39. 1694        pm_runtime_mark_last_busy(i2c_dev->dev);
    % z9 @; \0 t  D
  40. 1695        pm_runtime_put_autosuspend(i2c_dev->dev);: r: V  D3 X) R5 w8 p+ ^2 {1 Y
  41. 1696
    8 U1 b) o  h( ]  V' \
  42. 1697        return (ret < 0) ? ret : num;9 I# i, n# c' @+ y6 w' Z
  43. 1698    }
复制代码

! x8 I2 z2 s9 c* T2 m- `第1675行,调用stm32f7_i2c_wait_free_bus函数等待I2C总线空闲,也就是读取I2C控制的ISR寄存器的bit15(BUSY)位,此位用来标记I2C控制器是否忙。3 p7 w, y3 p+ o$ I5 }. e
第1679行,调用stm32f7_i2c_xfer_msg函数发送数据,此函数也是操作I2C控制器硬件寄存器的,具体内容这里就不分析了,大家感兴趣可以自己阅读代码。
7 x' w4 A  J8 d5 F; y. W+ `7 _  {) E: P3 r) k1 R' B
40.4 I2C设备驱动编写流程
! [' j% @- L* \/ O+ Y) @I2C适配器驱动SOC厂商已经替我们编写好了,我们需要做的就是编写具体的设备驱动,本小节我们就来学习一下I2C设备驱动的详细编写流程。3 K+ u( ?% A0 ^' _3 ^$ q( V! U
40.4.1 I2C设备信息描述# E1 k# z( g! P* u
1、未使用设备树的时候
2 W' a) |6 r% |' E, x  R8 B" m$ z" ~首先肯定要描述I2C设备节点信息,先来看一下没有使用设备树的时候是如何在BSP里面描述I2C设备信息的,在未使用设备树的时候需要在BSP里面使用i2c_board_info结构体来描述一个具体的I2C设备。i2c_board_info结构体如下:/ {7 b; T$ J1 K) n

- b# h# d" i0 o, y
  1. 示例代码40.4.1.1 i2c_board_info结构体  d& j1 C; T# ^4 p4 E" x+ @& n
  2. 406 struct i2c_board_info {# s; T3 `+ m- i' m* q* S7 F
  3. 407     char        type[I2C_NAME_SIZE];( r! n2 {9 s# J9 z. }. }' }6 i- X
  4. 408     unsigned short  flags;  S0 F9 b) B$ I1 b8 g2 ^
  5. 409     unsigned short  addr;& k% f! R: \  g7 g
  6. 410     const char  *dev_name;0 m8 S, J2 u* c0 ~2 M1 d0 Q& ~
  7. 411     void        *platform_data;" U7 w! g. ?! Q3 {' ?7 R; R$ T2 t
  8. 412     struct device_node *of_node;! F" Z: E# O" {! o1 P
  9. 413     struct fwnode_handle *fwnode;
    0 f0 N: x- O/ x, A0 n6 z
  10. 414     const struct property_entry *properties;% d, d7 K- y/ o+ z
  11. 415     const struct resource *resources;
    , J5 [& R3 k7 W% O$ X
  12. 416     unsigned int    num_resources;
    1 f0 F' d; O- d& d! S
  13. 417     int     irq;3 T1 w  v8 r# H
  14. 418 };
复制代码

) m9 w: C+ M" X" ]' C9 Mtype和addr这两个成员变量是必须要设置的,一个是I2C设备的名字,一个是I2C设备的器件地址。举个例子,打开arch/arm/mach-imx/mach-armadillo5x0.c文件,此文件中有关于s35390a这个I2C器件对应的设备描述信息:6 \5 O3 e1 N/ n0 s
: Q  O9 }3 ]2 A1 b+ l' s, q
  1. 示例代码40.4.1.2 s35390a的I2C设备信息( ^, l* M! k  o1 v
  2. 246 static struct i2c_board_info armadillo5x0_i2c_rtc = {2 P5 _! u% C6 U- T* e
  3. 247     I2C_BOARD_INFO("s35390a", 0x30),! [- ?) K) l  {/ f# Q
  4. 248 };
复制代码

" q- Z9 C. d4 w示例代码40.4.1.2中使用I2C_BOARD_INFO来完成armadillo5x0_i2c_rtc的初始化工作,I2C_BOARD_INFO是一个宏,定义如下:" g5 @+ G6 d6 V0 ]$ j. q/ [5 Y
  1. 示例代码40.4.1.3 I2C_BOARD_INFO宏
    ' H& Q' G2 s7 O! a7 b# I8 J7 a
  2. 433 #define I2C_BOARD_INFO(dev_type, dev_addr) \
    9 Y1 ]6 v! _) ]) F7 T) k$ x
  3. 434     .type = dev_type, .addr = (dev_addr)
复制代码
可以看出,I2C_BOARD_INFO宏其实就是设置i2c_board_info的type和addr这两个成员变量,因此示例代码40.4.1.2的主要工作就是设置I2C设备名字为s35390a,器件地址为0X30。# c1 q! X/ r4 O: u+ g
大家可以在Linux源码里面全局搜索i2c_board_info,会找到大量以i2c_board_info定义的I2C设备信息,这些就是未使用设备树的时候I2C设备的描述方式,当采用了设备树以后就不会再使用i2c_board_info来描述I2C设备了。; y5 v8 k2 f9 V' n
2、使用设备树的时候6 W& K1 g. B' T! W3 c& `' d
使用设备树的时候I2C设备信息通过创建相应的节点就行了,比如在我们的STM32MP1的开发板上有一个I2C器件AP3216C,这是三合一的环境传感器,并且该器件挂在STM32MP1 I2C5总线接口上,因此必须在i2c5节点下创建一个子节点来描述AP3216C设备,节点示例如下所示:+ f( y  f5 u8 {0 o- A6 ]! Z
9 e- K- W" Z4 n) {& }- q
  1. 示例代码40.4.1.4 i2c从设备节点示例
    & `- ^/ E2 ^( n+ j. x  D" G+ b
  2. 1   &i2c5 {) v2 ~/ q1 a+ y/ G" D
  3. 2       pinctrl-names = "default", "sleep";
    4 T) I+ @$ l+ ]
  4. 3       pinctrl-0 = <&i2c5_pins_a>;0 G0 S2 U5 E/ c; ]1 Y% k, Q' G% M
  5. 4       pinctrl-1 = <&i2c5_pins_sleep_a>;) d& t) M0 {' [- Q
  6. 5       status = "okay";
    . x" R0 I% ]) ~! l
  7. 6
    ( O7 x1 n3 e7 W$ \$ W& b6 O
  8. 7       ap3216c@1e {
    6 g/ d( ?( w! _* P
  9. 8           compatible = " alientek,ap3216c";1 x* k8 [( g6 z7 A: w. a
  10. 9           reg = <0x1e>;2 `7 G) c: Q1 C# u
  11. 10      };
    ; i: t' Q4 H1 Z. p5 S
  12. 11  };
复制代码

: t1 u. \" f. {$ Z第2~4行,设置了i2c5的pinmux的配置。
! i; h+ E5 c6 z8 m, q6 j第7~10行,向i2c5添加ap3216c子节点,第7行“ap3216c@1e”是子节点名字,“@”后面的“1e”就是ap3216c的I2C器件地址。第8行设置compatible属性值为“alientek,ap3216c”。第9行的reg属性也是设置ap3216c的器件地址的,因此值为0x1e。I2C设备节点的创建重点是compatible属性和reg属性的设置,一个用于匹配驱动,一个用于设置器件地址。' M# E' I7 c0 j9 I$ |- f$ J
40.4.2 I2C设备数据收发处理流程
: b  E/ [) L! G2 f在40.2.2小节已经说过了,I2C设备驱动首先要做的就是初始化i2c_driver并向Linux内核注册。当设备和驱动匹配以后i2c_driver里面的probe函数就会执行,probe函数里面所做的就是字符设备驱动那一套了。一般需要在probe函数里面初始化I2C设备,要初始化I2C设备就必须能够对I2C设备寄存器进行读写操作,这里就要用到i2c_transfer函数了。i2c_transfer函数最终会调用I2C适配器中i2c_algorithm里面的master_xfer函数,对于STM32MP1而言就是stm32f7_i2c_xfer这个函数。i2c_transfer函数原型如下:! H; z1 v" w0 y3 s8 r9 [5 l
int i2c_transfer(struct i2c_adapter *adap,, A9 [8 F# R# J2 [
struct i2c_msg *msgs,
3 G& r# K2 S+ C0 d, Pint num)- z2 s) J- w7 {
函数参数和返回值含义如下:
. d. U6 y5 O9 W5 C  j/ _( Aadap:所使用的I2C适配器,i2c_client会保存其对应的i2c_adapter。! S; P% }0 B7 m  h
msgs:I2C要发送的一个或多个消息。
, Q0 B) ]1 i: C! D$ c1 w4 lnum:消息数量,也就是msgs的数量。$ e3 B3 K2 s$ D
返回值:负值,失败,其他非负值,发送的msgs数量。
7 P  T8 @6 }# i: C! b) x. H我们重点来看一下msgs这个参数,这是一个i2c_msg类型的指针参数,I2C进行数据收发说白了就是消息的传递,Linux内核使用i2c_msg结构体来描述一个消息。i2c_msg结构体定义在include/uapi/linux/i2c.h文件中,结构体内容如下:/ w$ M$ Q7 }& A1 \, }6 O. ]4 u

4 X  S4 m% a* k1 s2 u6 Z
  1. 示例代码40.4.2.1 i2c_msg结构体1 F7 `: Y9 B0 T
  2. 69 struct i2c_msg {
    . M, ^  n& w$ `. A4 o
  3. 70          __u16 addr;                                     /* 从机地址                 */6 c& a, R5 l$ G: [
  4. 71          __u16 flags;                                    /* 标志                         */6 ]; T( ~! z8 Z4 M7 H: P5 J! Y
  5. 72                 #define I2C_M_TEN                        0x0010
    # S- k( E% Q, e. m
  6. 73                 #define I2C_M_RD                             0x0001
    ; S. p5 O/ s. V: m$ I6 \+ d; u, H
  7. 74                 #define I2C_M_STOP                       0x8000 + T6 c& R' g1 Y' h# ]2 }+ k  n
  8. 75                 #define I2C_M_NOSTART                0x4000 0 Z1 M! [* |: u% N
  9. 76                 #define I2C_M_REV_DIR_ADDR         0x2000  
    , f, h. Y$ C# K0 ~" F/ t. [. R
  10. 77                 #define I2C_M_IGNORE_NAK         0x1000  * u4 N$ a+ q1 [& |$ R
  11. 78                 #define I2C_M_NO_RD_ACK            0x0800
    # t4 L3 i& [. J  N3 I, X. B/ ?
  12. 79                 #define I2C_M_RECV_LEN             0x0400
    * [) E+ `0 u. |6 i4 S
  13. 80          __u16 len;                                      /* 消息(本msg)长度        */9 z3 }  v. ~1 s% s) {) ~' @: r' ]
  14. 81          __u8 *buf;                                      /* 消息数据                     */
    6 f8 m- J% |  ?: \5 e! }
  15. 82 };
复制代码
- A3 |6 c$ M; t9 f+ q* {+ Q
使用i2c_transfer函数发送数据之前要先构建好i2c_msg,使用i2c_transfer进行I2C数据收发的示例代码如下:3 L$ Q/ G9 t; K5 g, X' ]( j
* a0 e' O7 D9 m; j
示例代码40.4.2.2 I2C设备多寄存器数据读写6 y% U6 u( F) T; z- C
  1. 1  /* 设备结构体 */1 @0 d$ D) k/ X8 o5 i# }" v
  2. 2  struct xxx_dev {
    & o  ?/ ], s4 J( i
  3. 3           ......
    ; v4 E; q  e# m( f, X1 [* s
  4. 4           void *private_data; /* 私有数据,一般会设置为i2c_client */
    % f" k0 _; z3 _* I6 [/ l% u
  5. 5  };! t: d2 c" ^/ n6 R: Z0 W8 f
  6. 6  
    0 H6 b& w' W3 P" y) _
  7. 7  /*
    % F' H" J3 B, [# s2 ~
  8. 8   * @description        : 读取I2C设备多个寄存器数据. b' a( t  E" ]9 P, k4 \$ d
  9. 9   * @param – dev        : I2C设备- h: i# t* t: p9 v9 g# G
  10. 10  * @param – reg        : 要读取的寄存器首地址' m0 `$ ~# J# J/ r! p- z+ [$ {5 n
  11. 11  * @param – val        : 读取到的数据, R0 Z4 d8 \' Z- r
  12. 12  * @param – len        : 要读取的数据长度. R2 n) i( B1 J
  13. 13  * @return               : 操作结果
    * l2 y0 e' P2 p$ Z0 B0 t. V" {( z- Z7 O
  14. 14  */
    * u$ T9 F! Y; q" L9 l% |/ ]1 ]
  15. 15 static int xxx_read_regs(struct xxx_dev *dev, u8 reg, void *val, ( z  [. L- L7 P# h* f, }+ b4 g1 F
  16. int len)
    1 V. e8 j! y$ b/ h
  17. 16 {
    ' t0 E4 S( z4 Q4 q! I
  18. 17          int ret;/ `% X/ `- y; P' |* g$ U& J- f; Y
  19. 18          struct i2c_msg msg[2];4 Z, y1 F) Y' D2 m8 Y1 ]+ P6 s
  20. 19          struct i2c_client *client = (struct i2c_client *)' a4 ~& Z: A, J4 m" ~' f
  21. dev->private_data;& W* a2 L# ^$ S( c, B
  22. 20 9 z$ a, J! k+ X8 ?. D; _
  23. 21                  /* msg[0],第一条写消息,发送要读取的寄存器首地址 */* \+ P* u, ]) e9 i5 n& z1 j
  24. 22          msg[0].addr = client->addr;                 /* I2C器件地址                 */
    0 l; ~4 B/ e. P7 H
  25. 23          msg[0].flags = 0;                           /* 标记为发送数据         */5 C9 e' t7 }* Q
  26. 24          msg[0].buf = ®                          /* 读取的首地址                 */* z& p  x  I# P: ]3 ^( G
  27. 25          msg[0].len = 1;                             /* reg长度                        */
    3 v+ f! p/ x$ s
  28. 26
    # r; P& Z2 y: Q+ }% M# l: V
  29. 27          /* msg[1],第二条读消息,读取寄存器数据 */
    0 Z& n2 ~- \2 w7 |/ O
  30. 28          msg[1].addr = client->addr;                 /* I2C器件地址          */9 X* b+ H, N! l' f/ q$ t+ U; ?
  31. 29          msg[1].flags = I2C_M_RD;                    /* 标记为读取数据        */
    ) ^" A) v- D) J' c
  32. 30          msg[1].buf = val;                           /* 读取数据缓冲区         */
    % C) P2 A1 w8 e* L" q( l  n
  33. 31          msg[1].len = len;                           /* 要读取的数据长度        */
    ) s( l1 e) h' P# k; B- \
  34. 32 2 _2 z1 ?. u1 K. b+ A, P$ \4 b2 V$ G
  35. 33          ret = i2c_transfer(client->adapter, msg, 2);7 w0 U, k, `! ]# J- n
  36. 34          if(ret == 2) {2 o2 @( a% J0 l& i3 m
  37. 35              ret = 0;. I( p; Y% P" `! O- ~3 h
  38. 36          } else {
    ! O$ o& J1 t: Q, B) l" V: F2 D
  39. 37                      ret = -EREMOTEIO;& G$ X0 v: a7 \) _0 J! J
  40. 38          }- v3 D; e, ?7 |5 M8 E
  41. 39          return ret;
    + {, p' I4 a+ F# [
  42. 40 }
    . m, G% O/ H4 o6 z2 x: H- Z+ R
  43. 41
    6 h* l3 D4 ^* H( J  t( s! Z
  44. 42 /*) T! X$ j" o: @: E
  45. 43  * @description        : 向I2C设备多个寄存器写入数据% U/ [0 s( X* I7 n5 l& E! C/ |+ m
  46. 44  * @param – dev        : 要写入的设备结构体7 A7 A& `3 O2 Z4 a  n) x
  47. 45  * @param – reg        : 要写入的寄存器首地址
    ; L8 e' w% A( ?3 w. q( ?9 |# d* _
  48. 46  * @param – val        : 要写入的数据缓冲区
    , v# l! ?, g  j; x) }7 f0 P8 ^
  49. 47  * @param – len        : 要写入的数据长度, o# G' A( T/ @+ W
  50. 48  * @return                     : 操作结果
      f2 ~! H9 G* a$ J2 k; d+ D
  51. 49  */
    5 H3 H2 ^+ G, O: C" q# F
  52. 50 static s32 xxx_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf, " v# _# ]4 N- T6 ?: v# d
  53. u8 len); p; M) r. a. }- G  U7 X$ E
  54. 51 {% U0 {! a! u& _7 D# J
  55. 52          u8 b[256];
    % j7 T& |4 S& d( r+ a. d
  56. 53          struct i2c_msg msg;" N8 U1 U) T2 t
  57. 54          struct i2c_client *client = (struct i2c_client *)
    / b$ w- F& B' T' I1 e6 ?
  58. dev->private_data;
    . W9 m# u/ V* h+ t1 {! \& p
  59. 55  
    ( a1 C# L4 E: Y: z. ?9 y
  60. 56                  b[0] = reg;                         /* 寄存器首地址                                                 */8 ]6 F/ z! O5 g) Z0 p
  61. 57          memcpy(&b[1],buf,len);              /* 将要发送的数据拷贝到数组b里面        */9 L1 s& M  {2 W) _/ A& n
  62. 58      
    1 l- u- ^% i  z" }. f
  63. 59                  msg.addr = client->addr;            /* I2C器件地址                                                 */
    8 M2 m: J/ O, O  u- V+ T" [) d
  64. 60          msg.flags = 0;                      /* 标记为写数据                                                 */8 m+ ?) N' }8 ?5 ]
  65. 61 - \+ r2 ~  V. v. q" L4 U# f
  66. 62          msg.buf = b;                        /* 要发送的数据缓冲区                                 */7 x" d% q' p. I
  67. 63          msg.len = len + 1;                  /* 要发送的数据长度                                         */+ _& c5 P% r0 H8 M8 f& C
  68. 64
    . B' @, ]* H9 P" Q  I+ q4 ?- ~
  69. 65          return i2c_transfer(client->adapter, &msg, 1);" e4 S% S; \7 [! y. o/ G4 q: P  B
  70. 66 }
复制代码
; A# g# ]+ }7 X, i. l8 G" A. H
第2~5行,设备结构体,在设备结构体里面添加一个执行void的指针成员变量private_data,此成员变量用于保存设备的私有数据。在I2C设备驱动中我们一般将其指向I2C设备对应的i2c_client。9 G) X5 c$ I9 v2 s! E
第15~40行,xxx_read_regs函数用于读取I2C设备多个寄存器数据。第18行定义了一个i2c_msg数组,2个数组元素,因为I2C读取数据的时候要先发送要读取的寄存器地址,然后再读取数据,所以需要准备两个i2c_msg。一个用于发送寄存器地址,一个用于读取寄存器值。对于msg[0],将flags设置为0,表示写数据。msg[0]的addr是I2C设备的器件地址,msg[0]的buf成员变量就是要读取的寄存器地址。对于msg[1],将flags设置为I2C_M_RD,表示读取数据。msg[1]的buf成员变量用于保存读取到的数据,len成员变量就是要读取的数据长度。调用i2c_transfer函数完成I2C数据读操作。9 v$ q) Z' `* ?, w7 B
第50~66行,xxx_write_regs函数用于向I2C设备多个寄存器写数据,I2C写操作要比读操作简单一点,因此一个i2c_msg即可。数组b用于存放寄存器首地址和要发送的数据,第59行设置msg的addr为I2C器件地址。第60行设置msg的flags为0,也就是写数据。第62行设置要发送的数据,也就是数组b。第63行设置msg的len为len+1,因为要加上一个字节的寄存器地址。最后通过i2c_transfer函数完成向I2C设备的写操作。
7 H" a1 E9 r9 c( K3 `# _另外还有两个API函数分别用于I2C数据的收发操作,这两个函数最终都会调用i2c_transfer。首先来看一下I2C数据发送函数i2c_master_send,函数原型如下:
2 s8 _  {" t" A% d. h$ tint i2c_master_send(const struct i2c_client *client,4 I/ X. w, M5 M/ E1 k  {: e
const char *buf,' ]" F( K- }7 u5 z& b1 Q5 W- ?  c
int count)
! n) D2 C! d, Q$ Y5 [4 z: s" B' J函数参数和返回值含义如下:
3 n8 ?* `: z& m& F, _0 Rclient:I2C设备对应的i2c_client。6 P5 U9 Z  A  v
buf:要发送的数据。
$ T6 ?9 _$ ^: `9 u+ H  pcount:要发送的数据字节数,要小于64KB,以为i2c_msg的len成员变量是一个u16(无符号16位)类型的数据。, P" W5 `  H! g! r
返回值:负值,失败,其他非负值,发送的字节数。
4 w/ W  E  f, f" {I2C数据接收函数为i2c_master_recv,函数原型如下:0 d( \& h! f: n
int i2c_master_recv(const struct i2c_client *client,# S  P# P* {+ A9 v
char *buf,0 n; E1 h7 v' |9 [9 `, ~% Y% A
int count)
3 E! ]3 c* Z5 N函数参数和返回值含义如下:
2 N# G7 B$ N6 I1 Tclient:I2C设备对应的i2c_client。  M& k, I' _( V2 f5 i& [4 G
buf:要接收的数据。# x' B9 F# ]/ b) t$ ]
count:要接收的数据字节数,要小于64KB,以为i2c_msg的len成员变量是一个u16(无符号16位)类型的数据。
& Q" S7 a, h) s- O返回值:负值,失败,其他非负值,发送的字节数。
# P- |/ y9 }  r7 A5 V' ^关于Linux下I2C设备驱动的编写流程就讲解到这里,重点就是i2c_msg的构建和i2c_transfer函数的调用,接下来我们就编写AP3216C这个I2C设备的Linux驱动。, g' f( G2 B1 Y! S/ X/ Z" a
40.5 硬件原理图分析
- w% D; W. C$ m4 n) ~AP3612C原理图如图40.5.1所示:
3 j7 {8 k* z& m- I+ o- c
! y" P4 h3 H) A. T: H e2e8fcac177a40e6a16b00245700c744.png
  ~- {5 M# ?+ }/ o+ }2 v  q- n, J/ o5 Z2 {$ D7 F$ T
图40.5.1 AP3216C原理图. k+ `: ]6 b# `, g1 f
从图40.5.1可以看出AP3216C使用的是I2C5,其中I2C5_SCL使用的是PA11这个IO,I2C_SDA使用的是PA12这个IO。AP3216C还有个中断引脚,这里我们没有用到中断功能。
3 E8 G# P4 w$ z( \7 J( r* Q40.6 实验程序编写
9 ~/ L" y/ P. Z! K) k本实验对应的例程路径为:开发板光盘1、程序源码2、Linux驱动例程21_iic。
9 i7 Q" r; [, U" e$ }# ?1 v: ]8 w9 }40.6.1 修改设备树
8 m/ M5 v$ U" `9 z( x1、IO修改或添加1 x3 d: |% }" e2 J5 s
AP3216C用到了I2C5接口。因为I2C5所使用的IO分别为PA11和PA12,所以我们要根据数据手册设置I2C5的pinmux的配置。如果要用到AP3216C的中断功能的话还需要初始化AP_INT对应的PE4这个IO,本章实验我们不使用中断功能。因此只需要设置PA11和PA12这个两个IO复用为AF4功能,ST其实已经将这个两个IO设置好了,打开stm32mp15-pinctrl.dtsi,然后找到如下内容:$ i. F: ~9 n6 t. _- x$ N5 @% N

9 K  p5 n5 l6 z  E( u
  1. 示例代码40.6.1.1 I2C5的pinmux配置
    # z9 n$ W- |( \* N. X& t: q$ M
  2. 1   i2c5_pins_a: i2c5-0 {7 v4 K& w4 r3 U9 U* |- i  D
  3. 2       pins {
    8 D( l! Q; }/ B1 Y2 A
  4. 3           pinmux = <STM32_PINMUX('A', 11, AF4)>, /* I2C5_SCL         */. `; `: K( I" B6 a. ^5 _' `
  5. 4                <STM32_PINMUX('A', 12, AF4)>;                 /* I2C5_SDA         */
    ! U+ y" |4 T: G* e
  6. 5           bias-disable;
    9 x2 h+ J1 ?& w. d: J
  7. 6           drive-open-drain;
    $ o& }; e9 B* J/ y  w
  8. 7           slew-rate = <0>;# `  M1 ]; K0 Q
  9. 8       };9 z  N+ ?8 b0 P6 c2 V1 c- [6 ^
  10. 9   };
    " Y" A! _+ e' N, p, A. p. e, t( X1 N% ?
  11. 10
    : |. n' V, a+ @# r+ P: S: y  A
  12. 11  i2c5_pins_sleep_a: i2c5-1 {; W, |2 w* O( [6 c
  13. 12      pins {
    1 z  V3 c2 j, V; \% P. }; Q
  14. 13          pinmux = <STM32_PINMUX('A', 11, ANALOG)>, /* I2C5_SCL */1 _' s4 z! H* a& U7 H; N. A
  15. 14               <STM32_PINMUX('A', 12, ANALOG)>; /* I2C5_SDA */# f8 i* V0 n5 k0 c7 r
  16. 15
    % |# ^* ~6 a9 A0 o
  17. 16      };; o/ [3 U( q; g  J" o9 D
  18. 17  };
复制代码
, j4 r' C0 W3 [
示例代码40.6.1.1中,定义了I2C5接口的两个pinmux配置分别为:i2c5_pins_a和i2c5_pins_sleep_a。第一个默认的状态下使用,第二个是在sleep状态下使用。
. T# q7 R6 E- {$ D9 A. h% m; z* o: U  B9 r+ I% G$ W
2、在i2c5节点追加ap3216c子节点5 k* m6 Q2 t4 r: F
接着我们打开stm32mp157d-atk.dts文件,通过节点内容追加的方式,向i2c5节点中添加“ap3216c@1e”子节点,节点如下所示:
. a5 i9 x9 k4 b. ^4 d7 `$ A; s( G: b
2 E5 V2 l: T- w1 t, f, `
  1. 示例代码40.6.1.2 向i2c5追加ap3216c子节点
    ' t0 o0 W6 ]9 M, e3 e
  2. 1   &i2c5 {
    7 M& S- A6 @3 z0 ~
  3. 2       pinctrl-names = "default", "sleep";
    3 L7 \; c/ P& ]$ @) _
  4. 3       pinctrl-0 = <&i2c5_pins_a>;% ]* V7 z( i: h# O
  5. 4       pinctrl-1 = <&i2c5_pins_sleep_a>;9 @- U) p) h4 U8 w2 Z) c: U/ D6 Y
  6. 5       status = "okay";' O  a* J# M$ g. m% j; U
  7. 6
    0 F6 M: ~2 s# g) I  q  d
  8. 7       ap3216c@1e {4 i' s  r5 a. F$ X
  9. 8           compatible = "alientek,ap3216c";
    4 x1 _0 B& n% D  }  y6 M0 n) g& D
  10. 9           reg = <0x1e>;
    5 T# F$ L" d! w1 ~7 f2 N
  11. 10      };! Q" Y, N' b2 i* ~5 y
  12. 11  };
复制代码

1 ]; F! u* _% u& Y第2~4行,给I2C5节点设置了pinmux配置。
; L% A+ C9 J/ ~* L! W; L第7行,ap3216c子节点,@后面的“1e”是ap3216c的器件地址。) k% h2 }6 ~! T
第8行,设置compatible值为“alientek,ap3216c”。
, G1 R- p: G. M" {# k第9行,reg属性也是设置ap3216c器件地址的,因此reg设置为0x1e。. ^" C0 Y6 B0 k4 X5 K# p# K. b. ~
2 r/ k! g( Q* F& ^2 y
设备树修改完成以后使用“make dtbs”重新编译一下,然后使用新的设备树启动Linux内核。/sys/bus/i2c/devices目录下存放着所有I2C设备,如果设备树修改正确的话,会在/sys/bus/i2c/devices目录下看到一个名为“0-001e”的子目录,如图40.6.1.1所示:
/ Z' n) c+ W" l7 M# n
* H; l1 s, _- I* P: A! X1 Z( N 5fd20b99143f4ec891506ad00a129793.png : N+ M, ?/ y4 }

2 ^! s  k" \0 h3 u; @" I, a, `图40.6.1.1 当前系统I2C设备3 {! r( c; h- v- b. O
图40.6.1.1中的“0-001e”就是ap3216c的设备目录,“1e”就是ap3216c器件地址。进入0-001e目录,可以看到“name”文件,name文件保存着此设备名字,在这里就是“ap3216c”,如图40.6.1.2所示:% O; d8 A8 J, w. m
4 k' z8 T" ~8 y5 E* L
df2fed151ca54dd88674cf61a6e57db9.png 1 Y0 Y& S8 i: Z0 h4 X

0 H& t5 }6 w5 u5 s4 n1 N6 y8 ^. ?图40.6.1.2 ap3216c器件名字4 i7 l" p9 I/ b  y/ D
40.6.2 AP3216C驱动编写6 n$ r8 j, V7 \
新建名为“21_iic”的文件夹,然后在21_iic文件夹里面创建vscode工程,工作区命名为“iic”。工程创建好以后新建ap3216c.c和ap3216creg.h这两个文件,ap3216c.c为AP3216C的驱动代码,ap3216creg.h是AP3216C寄存器头文件。先在ap3216creg.h中定义好AP3216C的寄存器,输入如下内容,0 ], {+ Z% r4 }7 L+ e; B7 {

  J7 P! B0 p$ ~+ ^- `9 B
  1. 示例代码40.6.2.1 ap3216creg.h文件代码段6 o: h0 @1 {2 G$ e6 L8 \
  2. 1   #ifndef AP3216C_H
    . O- N$ [3 Q0 b+ H4 l, n* A1 H: `
  3. 2   #define AP3216C_H4 i8 j$ {2 B+ y! F: p: v
  4. 3   /***************************************************************% P1 R6 y2 w" w3 m5 V
  5. 4   Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
    / U3 L7 K4 ]1 s- h% _# Y  a& o
  6. 5   文件名    : ap3216creg.h8 r6 }* d& o/ D; ]6 T& r3 g# V
  7. 6   作者      : 正点原子Linux团队
    $ Q+ G5 _8 p0 ?/ t$ a$ L& x
  8. 7   版本      : V1.0
    # C0 M" Q' |! v% d8 i& H
  9. 8   描述      : AP3216C寄存器地址描述头文件2 ?$ f  n6 ]8 q/ d! {7 ^4 ^5 }
  10. 9   其他      : 无
    " e3 V0 c; r4 c& l$ c. G0 ~5 O
  11. 10  论坛      : <a href="http://www.openedv.com" target="_blank">www.openedv.com</a># i5 i; A! h) s4 ?
  12. 11  日志      : 初版V1.0 2021/03/19 正点原子Linux团队创建
    & h/ n' F2 A! o# A) b
  13. 12  ***************************************************************/  Q$ S) s  D8 {4 ^, A0 q
  14. 139 i* b7 ~# |8 c" y) C7 C
  15. 14  #define AP3216C_ADDR                0X1E            /* AP3216C器件地址          */4 L' i" |: N" |4 U( V3 i: S
  16. 15
    2 X9 y; N5 u+ h" }2 G7 |2 b
  17. 16  /* AP3316C寄存器 */+ S  \# S& L& a( ]; f
  18. 17  #define AP3216C_SYSTEMCONG         0x00           /* 配置寄存器                       */# {$ j) g, n! `9 S( J4 ^/ q$ }
  19. 18  #define AP3216C_INTSTATUS           0X01           /* 中断状态寄存器                   */+ Q8 _. N: r3 i2 Q2 v! I4 c1 q
  20. 19  #define AP3216C_INTCLEAR            0X02           /* 中断清除寄存器                   */' K) M) m" o3 f- O& V
  21. 20  #define AP3216C_IRDATALOW           0x0A           /* IR数据低字节             */4 t/ E: I' l; z# b9 j6 V5 B
  22. 21  #define AP3216C_IRDATAHIGH          0x0B           /* IR数据高字节             */* l: ?9 N6 L, A% I; g' j
  23. 22  #define AP3216C_ALSDATALOW          0x0C           /* ALS数据低字节            */
    ( `8 Q) ^( ?/ S& O+ ]& N. h$ l$ G
  24. 23  #define AP3216C_ALSDATAHIGH        0X0D          /* ALS数据高字节            */
    / l; C, G' R- q2 e
  25. 24  #define AP3216C_PSDATALOW           0X0E           /* PS数据低字节             */1 H/ g  j3 ^5 J# W8 o# i0 B
  26. 25  #define AP3216C_PSDATAHIGH          0X0F           /* PS数据高字节             */
    * I2 ]$ C# n" j; H( ~2 y
  27. 26# A1 D2 ^, n- n8 Z7 i# R
  28. 27  #endif
复制代码

( f( y. H. a* s. _ap3216creg.h没什么好讲的,就是一些寄存器宏定义。然后在ap3216c.c输入如下内容:! O1 W" K- s4 X, Q: @! |

% u5 {) s' J$ T5 ?! A: o
  1. 示例代码40.6.2.2 ap3216.c文件代码段( P5 r1 N; H+ B! Y* n
  2. 1  /***************************************************************; a( `/ A; d' G/ @6 k  l
  3. 2  Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.# C- }4 F1 {: H: K3 |
  4. 3  文件名          : ap3216c.c
    + ?. l: l& N$ v4 M8 \: ]
  5. 4  作者             : 正点原子Linux团队  m! P1 f0 |7 K. B  b8 |$ K
  6. 5  版本             : V1.0
    8 c1 _  j& W( @, v) k% l
  7. 6  描述            : AP3216C驱动程序
    ) L% ^+ M( ^* u% [6 t3 U6 S
  8. 7  其他            : 无
    / o( L* L8 c0 O0 [% I
  9. 8  论坛            : <a href="http://www.openedv.com" target="_blank">www.openedv.com</a>9 G" h' d  r& h8 w! M5 b( h
  10. 9  日志            : 初版V1.0 2021/03/19 正点原子Linux团队创建
    . C4 y/ K2 Q, Y' E
  11. 10 ***************************************************************/
    2 U! j6 w4 n6 {; K# `+ H; U
  12. 11  #include <linux/types.h>
    . V& H" x( U+ E! G+ S. g4 B( g
  13. 12  #include <linux/kernel.h># c" b/ ]  s- {
  14. 13  #include <linux/delay.h>1 q. i. a) Y  k2 ]" y: w
  15. 14  #include <linux/ide.h>
    # ^' Y4 t, o% k5 q: a7 E
  16. 15  #include <linux/init.h>
    " ^" L0 u0 {& v# }6 u$ s$ g2 @
  17. 16  #include <linux/module.h>
    . R7 Q& ]' M! C7 o0 @, k
  18. 17  #include <linux/errno.h>
    " ^; L9 m7 |. W9 x1 G/ s
  19. 18  #include <linux/gpio.h>9 Q  ?9 U* \; ~  d5 z& a8 }
  20. 19  #include <linux/cdev.h>
      }0 V6 I. Z( C- s0 C! v
  21. 20  #include <linux/device.h>- K/ n' F9 e* [: Q5 b5 w8 u2 t* x
  22. 21  #include <linux/of_gpio.h>+ P; d1 S) |( s! z- e
  23. 22  #include <linux/semaphore.h>7 A# T6 I4 i3 ?% r- Z7 t2 V
  24. 23  #include <linux/timer.h>& W) {4 b6 E5 O% _
  25. 24  #include <linux/i2c.h>  a1 x* L8 S" H" J' u0 q
  26. 25  #include <asm/mach/map.h>1 l) S2 t8 f# y- n6 O
  27. 26  #include <asm/uaccess.h>1 D3 u0 y, g: E* H+ @1 Q
  28. 27  #include <asm/io.h>
    / k* V; M9 x6 V
  29. 28  #include "ap3216creg.h"( S( U+ _/ Q5 H9 H' ~3 m8 p0 m/ v
  30. 29
    # ^& l0 L  n  l
  31. 30  #define AP3216C_CNT 1% ^* ?" P0 @5 g
  32. 31  #define AP3216C_NAME    "ap3216c", w3 D8 I" N1 w
  33. 32 8 ~2 m0 a/ L& O# w* y8 H
  34. 33  struct ap3216c_dev {* B1 F8 y! |7 c6 X7 L* P5 t
  35. 34      struct i2c_client *client;          /* i2c 设备                 */
    ; j$ D# u: C/ r8 }; X6 l# N
  36. 35      dev_t devid;                            /* 设备号                     */
    8 p4 {( ?9 Q0 G; J2 |
  37. 36      struct cdev cdev;                       /* cdev                     */
    ! g7 d. |3 f; {. _9 M) G
  38. 37      struct class *class;                    /* 类                              */
    5 ]5 h1 F2 P, ~) P
  39. 38      struct device *device;                  /* 设备                            */
    3 ]0 {  V$ r$ M" g
  40. 39      struct device_node  *nd;         /* 设备节点                         *// b: i4 q: c# N5 B7 E
  41. 40      unsigned short ir, als, ps;        /* 三个光传感器数据         */
      Q3 x' A% |% |$ R$ y% n9 j$ Z: i
  42. 41  };; X. Q8 o6 G4 Z% P: M% E& E) Y
  43. 42 4 S8 q( S' ~* z+ m6 j* h2 V# ?/ r" w# {! p
  44. 43  /*
    9 x, ]- y9 g+ k7 p
  45. 44   * @description        :        从ap3216c读取多个寄存器数据- u) L' Y% Y7 S; r* h3 j  F
  46. 45   * @param – dev        :         ap3216c设备
    4 z( T4 h. {& \- I8 i" i" X( X% i
  47. 46   * @param – reg        :          要读取的寄存器首地址& |( {2 T! A5 i$ F; T$ z
  48. 47   * @param – val        :          读取到的数据* J9 V: B3 g8 _( P! A
  49. 48   * @param – len        :          要读取的数据长度) Y: g& H( P  z% D
  50. 49   * @return              :         操作结果  D0 z, C/ o0 _9 L# t1 H
  51. 50   */
    & @6 C/ l7 e! ^* i; w1 A) u- j
  52. 51  static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, , S, {  h" Z( \$ w) b# ^; P( s/ S7 [7 d
  53. void *val, int len)+ ?3 j; h! u. c! E
  54. 52  {' Y# u" q! N# Z' D# r2 z( [
  55. 53      int ret;
    9 v9 P1 P. X9 Z3 b2 B
  56. 54      struct i2c_msg msg[2];
    ! u( S7 j* S' p0 K
  57. 55      struct i2c_client *client = (struct i2c_client *)dev->client;2 B! @: Q. R: L8 D8 r4 }
  58. 56 * A  V% l& {! h5 f
  59. 57      /* msg[0]为发送要读取的首地址 */4 J3 w2 `1 m' Z
  60. 58      msg[0].addr = client->addr;              /* ap3216c地址         */
    6 g, S9 V2 ?, k, q$ P
  61. 59      msg[0].flags = 0;                           /* 标记为发送数据         */
    2 Q( Q: Q1 N; O/ ^' g5 }
  62. 60      msg[0].buf = ®                                /* 读取的首地址                 */
    8 z, H7 [6 A$ {( x
  63. 61      msg[0].len = 1;                             /* reg长度                        */
    $ ]! }7 v0 o5 ]( X( [
  64. 62
    $ N' A6 Q$ _1 T7 ~6 V$ F
  65. 63      /* msg[1]读取数据 */) X) ^! G" l& u3 J0 g- [, _
  66. 64      msg[1].addr = client->addr;                 /* ap3216c地址         */9 m' e3 \  O3 m
  67. 65      msg[1].flags = I2C_M_RD;                    /* 标记为读取数据        */
    6 ^- a; a. ^# u7 ?% K
  68. 66      msg[1].buf = val;                           /* 读取数据缓冲区         */1 L) C# C) [/ b# `
  69. 67      msg[1].len = len;                           /* 要读取的数据长度        */4 U" s% ?; z3 {; r
  70. 68
    ! w( U; \8 c/ a3 P  ?, j2 u
  71. 69      ret = i2c_transfer(client->adapter, msg, 2);
    $ v! f0 O. @( |$ N5 a+ d
  72. 70      if(ret == 2) {% B' H  q* ~7 j4 [# S1 R/ n
  73. 71          ret = 0;
    4 C, R; u7 c' k& y; {- S
  74. 72      } else {! B6 R, p" B7 }) e5 x6 n. W% s
  75. 73          printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
    ; e/ b$ g. j2 S1 b* Z% r/ @( D
  76. 74          ret = -EREMOTEIO;( L$ K- W' _  g2 w6 B
  77. 75      }+ h* V) @: Y+ X6 R0 y0 T
  78. 76      return ret;3 s, |6 z: J7 `5 w  ^
  79. 77  }: b& M5 Q; D) Z! e
  80. 78 $ S1 |" V* j# H; X% a
  81. 79  /*
    9 z; y, a, S4 V, s
  82. 80   * @description:        向ap3216c多个寄存器写入数据
    & R4 `1 ~% w: k# ^. o( U0 p
  83. 81   * @param - dev:         ap3216c设备  ^- `* B  O6 A1 \# C# A, D
  84. 82   * @param - reg:         要写入的寄存器首地址. {/ \. d( ]0 ]4 ^4 g
  85. 83   * @param - val:         要写入的数据缓冲区$ Y8 q: o1 z9 G! f6 I5 N. M0 K
  86. 84   * @param - len:         要写入的数据长度: Q9 A6 j1 m6 @4 \$ m
  87. 85   * @return    :           操作结果# F$ a+ C, A+ I- F) J
  88. 86   */
    ' w0 a( t6 w' W# z9 Z0 S
  89. 87  static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg,
    * ^- \. B" W4 H/ i. c, d. q* _% A
  90. u8 *buf, u8 len)( T: S! D) w7 S3 B- ~3 h7 W; H' u
  91. 88  {
    3 W# @' c* Q$ u, @- S( P2 J
  92. 89      u8 b[256];' Z: S& C$ s1 L% L3 s& U* {
  93. 90      struct i2c_msg msg;
    / q7 ?! t, r  K1 x4 R6 l
  94. 91      struct i2c_client *client = (struct i2c_client *)dev->client;* ^- V9 o& t! M% l
  95. 92      
    & v" }8 m. T" e: o# @' `2 o9 W( N8 i
  96. 93      b[0] = reg;                         /* 寄存器首地址                                                 */
    % `$ L- k2 r! A# S
  97. 94      memcpy(&b[1],buf,len);              /* 将要写入的数据拷贝到数组b里面         */! q/ w! m' q$ Y% e' U
  98. 95          8 B' L9 y) _; ?9 a, z( e9 a
  99. 96      msg.addr = client->addr;            /* ap3216c地址                                         */
    " a" t1 g/ I! C  V/ s0 _" p
  100. 97      msg.flags = 0;                      /* 标记为写数据                                                 */' Y: ?4 j) X* ~( }
  101. 98 4 @# S8 F" ~6 ^) h' f2 n2 ?
  102. 99      msg.buf = b;                        /* 要写入的数据缓冲区                                 */
    % A% f, H$ U' R( }" f
  103. 100     msg.len = len + 1;                  /* 要写入的数据长度                                         */
    3 M. R" Y5 p# k* O# S
  104. 101
    , V; {) g" c4 Z0 z; Z/ @
  105. 102     return i2c_transfer(client->adapter, &msg, 1);
    1 u5 B* j# J3 b; B, i
  106. 103 }
    3 x% l  c0 y# @# c4 O
  107. 104
    , b/ ]* O  H6 G
  108. 105 /*
    ! S" h" |3 [  Y# ~4 v
  109. 106  * @description:        读取ap3216c指定寄存器值,读取一个寄存器, H0 k) N3 P2 f6 ]
  110. 107  * @param - dev:         ap3216c设备
    0 k+ g, q5 ^: ~$ `
  111. 108  * @param - reg:         要读取的寄存器
    ' }3 d3 ^+ _# M" T4 p# d. }
  112. 109  * @return    :           读取到的寄存器值
    # D# [2 V+ w. M" y7 b
  113. 110  */
    ' b5 p3 m. `" [: Z
  114. 111 static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev,
      B2 ~0 \2 g" g0 s- p1 \: X8 t
  115. u8 reg)
    ! l4 B' a  [9 g9 [, N5 n, s2 O3 O4 ~; ?
  116. 112 {* L. p+ P% W. p
  117. 113     u8 data = 0;% w6 E2 ]) e* U% U+ o: Y, N) F' t5 p
  118. 114* [! s9 {2 ^9 h2 Q' P
  119. 115     ap3216c_read_regs(dev, reg, &data, 1);
    + l8 b: v! S; y) c2 Z. q
  120. 116     return data;7 h, b, {3 q; X  m, Q9 [
  121. 117 }
    , y* B+ G5 [. U
  122. 1187 S( k7 i2 N5 i  t
  123. 119 /*0 U5 e9 Z3 E; W7 }8 ^
  124. 120  * @description:         向ap3216c指定寄存器写入指定的值,写一个寄存器
    " h* j: x' S. z) ^  R" Y- s
  125. 121  * @param - dev:         ap3216c设备- C: b- H+ b7 h+ c
  126. 122  * @param - reg:         要写的寄存器
    ( |9 `$ _+ f, J
  127. 123  * @param - data:        要写入的值9 n. C7 y4 r; R' D3 e
  128. 124  * @return   :            无
    ( x2 l5 Y" U; s* n, c; M
  129. 125  */
    + m  z% u' ^; I" T9 X, t% a- _7 [
  130. 126 static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, # Q( R0 R% v" W1 x! L
  131. u8 data)
      q, }% P+ [$ v
  132. 127 {
    + m6 @# |9 V7 X$ C# G
  133. 128     u8 buf = 0;
    2 ^7 k$ S, f  V0 ^
  134. 129     buf = data;
    ; |/ f/ C4 t2 J, ]
  135. 130     ap3216c_write_regs(dev, reg, &buf, 1);+ G7 W- R6 }+ i" r
  136. 131 }
    # Q) O: h$ L* i: u
  137. 132# [: L% |7 X: R  m
  138. 133 /*4 i/ D9 e7 k; m! H, y2 W! z+ O; c
  139. 134  * @description         : 读取AP3216C的数据,包括ALS,PS和IR, 注意!如果同时
    & I; A! E- A' s1 ~- z
  140. 135  *                         :打开ALS,IR+PS两次数据读取的时间间隔要大于112.5ms& }, i" ?) f- ]1 Q; V) x. }
  141. 136  * @param – ir        : ir数据
    6 H) N* V0 o/ m& A; Y% Z& J
  142. 137  * @param - ps           : ps数据
    ; y. w" }* y; C! B, Y
  143. 138  * @param - ps           : als数据
    7 r! y9 N! ^" u7 u* a) r. u- b3 x/ W) ]$ m
  144. 139  * @return               : 无。& c# w& {# o  i# T( P/ g, ^8 S
  145. 140  */
    ( A4 Q, o" ]/ r
  146. 141 void ap3216c_readdata(struct ap3216c_dev *dev)2 P/ b7 x' r( Z1 i0 |+ H5 p
  147. 142 {
    ) ~% k6 e1 |0 P1 |; A3 \" U# L3 b6 K" o
  148. 143     unsigned char i =0;
    5 `8 ?7 I2 D$ k
  149. 144     unsigned char buf[6];: R: p7 o7 }6 I1 A9 z
  150. 145     % l; J& p' m4 h2 f, C6 r$ f4 ?
  151. 146     /* 循环读取所有传感器数据 */
    / t+ e8 r% R' c) I
  152. 147     for(i = 0; i < 6; i++) {
    : E% p6 K9 B# B" L
  153. 148         buf<i> = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);  
    4 t6 r/ {( I" A( t* r6 i0 p
  154. 149     }
    7 |  O9 O# o. H7 f
  155. 1508 G. Q3 }1 T$ J! h, i
  156. 151     if(buf[0] & 0X80)           /* IR_OF位为1,则数据无效         */
    7 }; u9 U" q& z! E/ N8 g
  157. 152         dev->ir = 0;                    
    ( t3 Q' t' Y7 ^+ |
  158. 153     else                        /* 读取IR传感器的数据            *// i1 l8 m* e' K+ o* u' k, f6 L) r
  159. 154         dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);         
    , V4 S% A" d# J8 @  L% `8 X
  160. 155     6 ^! ]- c, F; _; z5 Y: Z& f
  161. 156     dev->als = ((unsigned short)buf[3] << 8) | buf[2];  
    3 d9 e7 {! j: F
  162. 157     
    - M( F0 Z; `* K. n, {) O
  163. 158     if(buf[4] & 0x40)           /* IR_OF位为1,则数据无效  */) {: G$ U* i* x8 @# e
  164. 159         dev->ps = 0;                                                        
    # V6 @( k5 O3 @% m6 f
  165. 160     else                        /* 读取PS传感器的数据            */8 }8 M+ M' z" ~4 y5 M3 Y& h/ F
  166. 161         dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
    6 r' B5 G' p4 ~' O( {* t* @2 b- L
  167. 162 }8 r+ ]. Q) y& @  H" O! G$ _) P
  168. 163. `( W; c9 [, `' M
  169. 164 /*6 X5 i3 O4 x: A0 P( U
  170. 165  * @description          : 打开设备3 j% v7 Q, q7 d3 T! C
  171. 166  * @param – inode        : 传递给驱动的inode
    4 [% R' ^. X2 g1 C5 k7 T1 Z7 P6 n
  172. 167  * @param - filp         : 设备文件,file结构体有个叫做private_data的成员变量
    # H5 Y% }( V& E
  173. 168  *                     一般在open的时候将private_data指向设备结构体。
    4 w+ V* N$ u; T; M
  174. 169  * @return               : 0 成功;其他 失败
    , ~, y- J+ U( s2 i; v4 W5 x+ A
  175. 170  */$ V% h- s1 z  e+ {
  176. 171 static int ap3216c_open(struct inode *inode, struct file *filp)
    2 y# S$ A5 _* u: k" ^$ q
  177. 172 {
    3 Z; O$ O9 H) `7 q  S5 C
  178. 173     /* 从file结构体获取cdev指针,再根据cdev获取ap3216c_dev首地址 */
    8 O4 \: B& p9 `1 H5 d/ [. f" L
  179. 174     struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;
    0 }" L) U$ U1 C; G
  180. 175     struct ap3216c_dev *ap3216cdev = container_of(cdev,
    7 n, Y) K/ @- I9 z- K# V' g
  181. struct ap3216c_dev, cdev);
    . G* M4 Z1 m& o. P4 H
  182. 176
    6 ]; S1 @3 ?7 D3 H3 P2 ?( n- Q; d6 I* q# D
  183. 177     /* 初始化AP3216C */# z$ `! }0 [# y* o3 X0 @  ?
  184. 178     ap3216c_write_reg(ap3216cdev, AP3216C_SYSTEMCONG, 0x04);        
    2 L( _; }+ q2 T8 q6 N
  185. 179     mdelay(50);                                                     
    ( u* @' I' o0 ^/ b
  186. 180     ap3216c_write_reg(ap3216cdev, AP3216C_SYSTEMCONG, 0X03);        3 t; k! F6 Q5 q% q
  187. 181     return 0;
    ) e" I9 t4 ]  S! {
  188. 182 }
    8 z& n9 x- l5 v( M" R* z4 _
  189. 183
    / ~# i! c- ]1 U( _7 }0 l0 G$ E
  190. 184 /*
    ' z( [7 C8 [& q# P& ]9 q' R8 [- [/ E
  191. 185  * @description         : 从设备读取数据 6 T# A% S8 |/ T7 Y6 m, l* D
  192. 186  * @param - filp         : 要打开的设备文件(文件描述符)
    6 V0 w7 c* j2 A  |3 `7 W7 S
  193. 187  * @param - buf         : 返回给用户空间的数据缓冲区
    ! d& D! U1 f! U" N0 ]
  194. 188  * @param - cnt          : 要读取的数据长度
    - N+ ?4 V% q7 e& |/ A; e6 W
  195. 189  * @param - offt         : 相对于文件首地址的偏移9 n: V, Q: T3 S, {8 [; A
  196. 190  * @return                : 读取的字节数,如果为负值,表示读取失败5 P! Q4 D8 }9 d) z& Q- K
  197. 191  */
    - B) r' [8 C: t2 E1 A& {
  198. 192 static ssize_t ap3216c_read(struct file *filp, char __user *buf, ! v3 K  Q: r8 ~1 A% O# l
  199. size_t cnt, loff_t *off)$ P/ a$ \( o) ?6 ?' ^- a6 S- K
  200. 193 {* U; _8 s% @' R& G; F: q1 z1 j
  201. 194     short data[3];8 v' H2 `# z. _
  202. 195     long err = 0;
    0 t" j1 w( x0 `" h
  203. 196
    ) }. B! K+ D" J
  204. 197     struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;* t, l% v! H* Z" A! `8 }" v' D) p
  205. 198     struct ap3216c_dev *dev = container_of(cdev, struct ap3216c_dev,
    ( W1 i5 A+ ~7 g' D, F
  206. cdev);
    & D2 m0 G) E0 `: S; e7 _7 l( E3 D4 ]
  207. 199     
    ; D4 e* P9 |+ b6 X
  208. 200     ap3216c_readdata(dev);
    # K5 w) g% o, T0 @. I
  209. 201" `9 G0 J/ R/ S( p: v  `
  210. 202     data[0] = dev->ir;
    " U0 [# W/ W2 q. t+ R/ c# @* j- a
  211. 203     data[1] = dev->als;3 {  @' G+ x" A! Y; O$ a  J
  212. 204     data[2] = dev->ps;
    " V5 P* c) H# e  ^1 f# _) I  j) ?
  213. 205     err = copy_to_user(buf, data, sizeof(data));
    4 j5 S" Q: X- y  ]. e2 c5 d
  214. 206     return 0;1 J- x) t3 _+ k7 p! O, Y
  215. 207 }$ t, x( v1 k: m
  216. 2080 X5 i4 d# }# b9 u4 E* S7 Z5 y2 a' ]
  217. 209 /*
    ( @! e+ \9 y# b' T# Z
  218. 210  * @description         : 关闭/释放设备4 \5 @$ Q3 ]& b8 p$ ^) v
  219. 211  * @param - filp         : 要关闭的设备文件(文件描述符)1 D, a0 k8 S* U4 `3 ~: X* c0 s2 w
  220. 212  * @return                : 0 成功;其他 失败
    # j5 ?  I, h7 g" d* I# K0 [' n
  221. 213  */
    0 u0 {0 n# r, h/ x2 O, i6 \
  222. 214 static int ap3216c_release(struct inode *inode, struct file *filp)
    : |/ n- q5 o& ]+ D  [
  223. 215 {
    # j) d( Z) ^0 M! b4 O" e
  224. 216     return 0;
    / B( M3 |2 o6 A' p0 c2 u
  225. 217 }
    ! J, R- m; ^8 l1 S2 @
  226. 218
    $ M4 C: Y$ B# x+ B' ^! Y
  227. 219 /* AP3216C操作函数 */
    6 G' X" _: c3 H( x, J4 R' o
  228. 220 static const struct file_operations ap3216c_ops = {2 A7 N- _3 F+ x( ?9 d; g
  229. 221     .owner = THIS_MODULE,1 t4 ^( N3 e1 _, g6 m4 p' E
  230. 222     .open = ap3216c_open,
    - k& D" ^9 f2 G1 U
  231. 223     .read = ap3216c_read,
    ! z! m5 h. h+ R' y  J  }5 C% v
  232. 224     .release = ap3216c_release,
    ( q' G, x. P9 Q4 G" R+ F. Q) A
  233. 225 };
    $ R; ]3 ]' x  F+ h6 Z0 y
  234. 226
    / I, `0 R. N2 c1 r  R' K
  235. 227  /*; J- }" l2 E0 Y4 W5 P% H
  236. 228   * @description         : i2c驱动的probe函数,当驱动与, ~1 z$ G8 T6 U6 p8 s
  237. 229   *                    设备匹配以后此函数就会执行
    0 h- K( `8 T7 j
  238. 230   * @param – client        : i2c设备
    7 ~5 o1 W& `4 U; }7 X6 k
  239. 231   * @param - id              : i2c设备ID
    1 H, a4 O0 S/ z  k$ W. V
  240. 232   * @return                  : 0,成功;其他负值,失败$ K( L$ o* o$ E- l3 g
  241. 233   */3 \4 [. Z9 t; B7 y/ d
  242. 234 static int ap3216c_probe(struct i2c_client *client, . I- b- l; A3 P! u: F7 e' H
  243. const struct i2c_device_id *id)3 C4 V  g8 Y5 a" p3 B8 [
  244. 235 {8 I/ V- j! v3 e& S) e) Q" ~
  245. 236     int ret;0 B" l! `$ E7 N& |: [1 @1 p
  246. 237     struct ap3216c_dev *ap3216cdev;
    0 M* p% M/ E2 w2 p( x4 ~
  247. 238     
    * U; C3 `) m& d7 U% A0 c
  248. 239       B9 ?1 `7 V7 h3 M8 w
  249. 240     ap3216cdev = devm_kzalloc(&client->dev, sizeof(*ap3216cdev), : o: \5 m5 h1 `4 q* q  J- e
  250. GFP_KERNEL);
    7 ]! U+ x( s. e4 W4 E. g9 O: e+ p; l
  251. 241     if(!ap3216cdev)% m/ U( S% B6 Z
  252. 242         return -ENOMEM;! d( g/ z% v' |& J" ~+ N/ R
  253. 243         
    . \% r1 [% r3 r7 i% X9 M; i
  254. 244     /* 注册字符设备驱动 */" q" i5 z0 }! U6 Y
  255. 245     /* 1、创建设备号 */3 q2 B6 y1 d' d4 ?
  256. 246     ret = alloc_chrdev_region(&ap3216cdev->devid, 0, AP3216C_CNT,
    # w! s3 l1 Z1 D' {+ V
  257. AP3216C_NAME);. g& Z/ _4 r5 l3 \' ?  @, E
  258. 247     if(ret < 0) {2 m' \1 M* o% t  e! j
  259. 248         pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n",
    ! O$ \. P, M( U6 Z# ~6 o8 w
  260. AP3216C_NAME, ret);
    . C8 m2 y/ ]  G
  261. 249         return -ENOMEM;2 e6 x4 \& Z' [% X6 N7 G
  262. 250     }
    0 {% d! P$ E* m1 g
  263. 251( i, h# e8 Y$ S% y, ]
  264. 252     /* 2、初始化cdev */1 I" N# t5 Y  I
  265. 253     ap3216cdev->cdev.owner = THIS_MODULE;5 m# e2 z, i, m; e* K  i) V
  266. 254     cdev_init(&ap3216cdev->cdev, &ap3216c_ops);+ b$ n* ?6 ]! M+ [+ J+ @
  267. 255     
    / Y6 F0 r: v: p! s( J: q
  268. 256     /* 3、添加一个cdev */- f, }8 j3 [' m
  269. 257     ret = cdev_add(&ap3216cdev->cdev, ap3216cdev->devid, 5 b' c/ u* Z) R* d) Y
  270. AP3216C_CNT);
    9 C' x* [1 b9 \$ y8 b) {! C0 W6 a
  271. 258     if(ret < 0) {
    + y: }+ K  T3 y) R$ w' {
  272. 259         goto del_unregister;
    , O# h, G" x) X& [  F3 o% Z$ }5 F
  273. 260     }( \3 G8 G  |: w+ l% j* `# V
  274. 261     
    - G' C- |! C9 E- x+ \. _  n
  275. 262     /* 4、创建类 */# M9 Q. {1 {  V
  276. 263     ap3216cdev->class = class_create(THIS_MODULE, AP3216C_NAME);
    ' T+ }) j- f. Q6 L  t, j& U+ e
  277. 264     if (IS_ERR(ap3216cdev->class)) {; N- e+ j9 @2 `4 _6 l+ v
  278. 265         goto del_cdev;
    5 z0 _" l. @" d5 o+ |; e0 h# S
  279. 266     }
      H" J# m- {* u; s* s
  280. 267% K2 w* s2 ~& i- p: x1 c- ^
  281. 268     /* 5、创建设备 */
    8 a6 e: }, s# u5 h
  282. 269     ap3216cdev->device = device_create(ap3216cdev->class, NULL,
    $ i9 k6 n+ n  M. M% W  b- c
  283. ap3216cdev->devid, NULL, AP3216C_NAME);
    , o! l1 E0 v0 B: F$ v, ?
  284. 270     if (IS_ERR(ap3216cdev->device)) {
    + r# N4 l- j6 Q9 k! w( S" ^7 s
  285. 271         goto destroy_class;% q8 A8 a, o' Z/ {
  286. 272     }/ N9 |3 Y" D# J5 g2 x5 E+ S4 j! i
  287. 273     ap3216cdev->client = client;
    9 q% Z. Z5 o& q* m
  288. 274     /* 保存ap3216cdev结构体 */
    % @+ ?% e- d1 X2 i: ?, w5 K- `
  289. 275     i2c_set_clientdata(client,ap3216cdev);
    & p, e% x5 I9 e& X# X2 o+ X
  290. 276% ^- B0 f( T7 Y4 Q5 z
  291. 277     return 0;
    3 x& f; ?' ^: [; j
  292. 278 destroy_class:6 C6 W6 K; \# E/ E- p* R# @" `& K
  293. 279     device_destroy(ap3216cdev->class, ap3216cdev->devid);
    4 o$ w8 I9 j; p& l$ _1 }2 H
  294. 280 del_cdev:3 N  w9 D) s7 M/ V2 A  w# h3 O( ~
  295. 281     cdev_del(&ap3216cdev->cdev);
    4 Z) v3 C. h% G/ ^
  296. 282 del_unregister:
    % c2 }+ V+ r5 M0 J) A, J- j& H
  297. 283     unregister_chrdev_region(ap3216cdev->devid, AP3216C_CNT);$ f4 ~+ ?, R2 V
  298. 284     return -EIO;
    1 j! R; a3 L) j$ D: m
  299. 285 }
    ! p+ Y' W6 O( m7 z: P! }
  300. 286- G6 G' T" X6 v* V# F3 |7 p9 R
  301. 287 /*9 g6 e. j' a0 r- u. k7 u' T, s+ P
  302. 288  * @description  : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行' @, y* a& P  Y
  303. 289  * @param - client          : i2c设备
    0 B% z( {' |: \# {" i# ~
  304. 290  * @return                  : 0,成功;其他负值,失败. s/ u: W# g: n9 y# M) o, Z6 q
  305. 291  */. o3 u$ u3 |& g) A# Z$ Z% h! n8 i
  306. 292 static int ap3216c_remove(struct i2c_client *client)
    5 l5 ^8 @$ g  Q4 i% p# C: M- {& ?
  307. 293 {
    5 a7 j! \2 G0 r) g5 E
  308. 294     struct ap3216c_dev *ap3216cdev = i2c_get_clientdata(client);
    9 I5 ]4 U) D& m1 p' K+ B
  309. 295     /* 注销字符设备驱动 */
    2 o' K* V1 a' N" }: m
  310. 296     /* 1、删除cdev */
    8 I! P9 S! v3 h! k0 Q
  311. 297     cdev_del(&ap3216cdev->cdev);& b/ [: w9 J* o% D6 P; J/ v( D
  312. 298     /* 2、注销设备号 */
    ' S6 X5 V1 Y$ L5 w2 K
  313. 299     unregister_chrdev_region(ap3216cdev->devid, AP3216C_CNT); 1 j: @& }6 ?; }9 l- Z
  314. 300     /* 3、注销设备 */
    ; v! x1 H0 m! Y, U" X2 C$ t+ }
  315. 301     device_destroy(ap3216cdev->class, ap3216cdev->devid);
    0 k, U1 C5 G9 t; s$ X' l
  316. 302     /* 4、注销类 */. l, d) H: C( ?$ t/ {" N1 \4 Q* P2 u
  317. 303     class_destroy(ap3216cdev->class);
    5 }. ~/ \; ?1 y% g) u" S
  318. 304     return 0;9 R5 }% |. j/ i# o
  319. 305 }
    " b0 I2 ]% k0 _$ K6 n
  320. 306
    8 ^% n7 v! A% g, C
  321. 307 /* 传统匹配方式ID列表 */( ^- R1 o5 s0 R/ X( Z
  322. 308 static const struct i2c_device_id ap3216c_id[] = {
    8 }4 K. W+ {9 |0 H( {/ r6 H
  323. 309     {"alientek,ap3216c", 0},  ; ?) O/ c' H( @
  324. 310     {}) R! d  b0 B2 u0 {8 L* e
  325. 311 };
    - y: L! T/ `* y0 e' k1 V
  326. 312  Q* D& G7 u. O5 \/ C) `
  327. 313 /* 设备树匹配列表 */- ?0 e: V0 L% F: p
  328. 314 static const struct of_device_id ap3216c_of_match[] = {
    8 X: x: X. ^) E8 D; s3 D) `
  329. 315     { .compatible = "alientek,ap3216c" },# m; `. ]4 J, r) c. f
  330. 316     { /* Sentinel */ }) ], i; ~& P7 D
  331. 317 };3 I0 M! S' Z- \& i
  332. 318
    3 E. m9 P  {" H  o! b' Y3 B
  333. 319 /* i2c驱动结构体 */   
    7 p2 C% u7 ^7 O7 O- L1 p3 P$ b
  334. 320 static struct i2c_driver ap3216c_driver = {8 r/ t0 a/ k  U# K* W' q
  335. 321     .probe = ap3216c_probe,$ o8 [! E- Y) O- `8 B8 p5 \5 X
  336. 322     .remove = ap3216c_remove,
    7 R# E3 ]" a3 D' ]. s. K7 B3 v
  337. 323     .driver = {
    - U4 a5 C0 |% E9 Q0 M' \8 }. u
  338. 324             .owner = THIS_MODULE,6 U0 n: [+ I2 }8 L
  339. 325             .name = "ap3216c"," S! y  U5 U2 n6 h' x' v/ b
  340. 326             .of_match_table = ap3216c_of_match,
    6 ^( T: M& F- j% m& U/ w
  341. 327            },
    : d0 y1 X/ r7 R6 ^: F& f
  342. 328     .id_table = ap3216c_id,. \0 _& G& }4 h- f" |7 G
  343. 329 };
    " F8 Y7 k* ^8 I  M
  344. 330              J1 O9 c' ]3 b0 W
  345. 331 /*
    0 `$ P! v! o) u3 |7 }: [) O
  346. 332  * @description         : 驱动入口函数
    ) v8 ^# {) z) _
  347. 333  * @param               : 无$ r6 s  Z" f. f: N6 u# d
  348. 334  * @return              : 无0 l+ p9 D1 g% |- `
  349. 335  */
    7 G, j2 F0 z( y& b3 n
  350. 336 static int __init ap3216c_init(void)' }7 x. y. ~! Z: H/ b0 N, o4 G$ }, f
  351. 337 {
    " E% E8 L- U/ a; z9 O, h
  352. 338     int ret = 0;+ v7 H8 J; E+ d  k
  353. 339
    % R( M1 x% a, Y! M  ^' }: E
  354. 340     ret = i2c_add_driver(&ap3216c_driver);/ L1 r, w6 U) K" Z
  355. 341     return ret;
    8 B" V8 R1 s6 w7 y  e1 y
  356. 342 }
    4 ?$ Y# l" i9 q% ~6 y* P+ B% _$ y2 |
  357. 343  |/ }: z) p2 R1 C, Q
  358. 344 /*. K. }; M) U* F" }
  359. 345  * @description         : 驱动出口函数
    3 t" v$ J7 Q) ]( t- S
  360. 346  * @param               : 无
    / {0 x9 f* X% Z4 H2 G$ {3 k9 Z
  361. 347  * @return              : 无* V% s4 ?3 A! N5 t: U9 Y
  362. 348  */- g2 f) ]( ?, Y" U6 M" q# y
  363. 349 static void __exit ap3216c_exit(void)
    8 u( ~  v: Q0 a* E
  364. 350 {1 @3 _0 ~( X2 \6 ]% c0 S8 {: \
  365. 351     i2c_del_driver(&ap3216c_driver);
    , P; }0 @8 ~: m. b8 R* o: u
  366. 352 }: W9 {% e& K$ f" e6 v  O
  367. 353' K; H7 I( w$ _7 O4 y
  368. 354 /* module_i2c_driver(ap3216c_driver) */
    & h: A4 |6 N0 Z0 V- U$ f7 }
  369. 355
    ; b! x4 W* Y8 h. q( Y4 M
  370. 356 module_init(ap3216c_init);
    6 @) H* `4 `% O; n0 v0 f
  371. 357 module_exit(ap3216c_exit);5 \) F! s6 ~2 {8 |& |& M
  372. 358 MODULE_LICENSE("GPL");- f( s$ R4 R5 ~& k4 ?' ~9 i& I& v
  373. 359 MODULE_AUTHOR("ALIENTEK");1 E' I- h0 a  |. ?
  374. 360 MODULE_INFO(intree, "Y");</i>
复制代码
# y: N/ Z' C$ A' J0 F) N
在示例代码40.6.2.2里,没有定义一个全局变量,那是因为linux内核不推荐使用全局变量,要使用内存的就用devm_kzalloc之类的函数去申请空间。
3 S: ^  s" U1 A. B+ R7 }& ^! w2 [# X第33~41行,自定义一个ap3216c_dev结构体。第34行的client成员变量用来存储从设备树提供的i2c_client结构体。第40行的 ir、als 和 ps 分别存储 AP3216C 的 IR、ALS 和 PS 数据。
1 I, @: V+ F9 k4 ]7 J' D) P/ p第 51~77 行,ap3216c_read_regs 函数实现多字节读取,但是 AP3216C 好像不支持连续多字节读取,此函数在测试其他 I2C 设备的时候可以实现多给字节连续读取,但是在 AP3216C 上不能连续读取多个字节,不过读取一个字节没有问题的。
% u7 T1 x) a7 a5 d( |- {" B& J第 87~103 行,ap3216c_write_regs 函数实现连续多字节写操作。( _/ d$ n9 A6 I" W" b% B/ r
第111~117行,ap3216c_read_reg函数用于读取AP3216C的指定寄存器数据,用于一个寄存器的数据读取。2 ^( R; `! }" ~# B
第126~131行,ap3216c_write_reg函数用于向AP3216C的指定寄存器写入数据,用于一个寄存器的数据写操作。& L# d/ a% e7 I
第141~162行,读取AP3216C的PS、ALS和IR等传感器原始数据值。, j6 I7 d7 X* Z
第171~225行,标准的字符设备驱动框架。ap3216c_dev结构体里有一个cdev的变量成员,第174行就是获取ap3216c_dev里的cdev这个变量的地址,在第175行使用container_of宏获取ap3216c_dev的首地址。
9 K" {7 I" G7 l  {; n& d; A  j第234~285行,ap3216c_probe函数,当I2C设备和驱动匹配成功以后此函数就会执行,和platform驱动框架一样。此函数前面都是标准的字符设备注册代码,第275行,调用i2c_set_clientdata函数将ap3216cdev变量的地址绑定到client,进行绑定之后,可以通过i2c_get_clientdata来获取ap3216cdev变量指针。, V: b! M# h/ l5 D
第292~305行,ap3216c_remove函数,当I2C驱动模块卸载时会执行此函数。第294行通过调用i2c_get_clientdata函数来得到ap3216cdev变量的地址,后面执行的一系列卸载、注销操作都是前面讲到过的标准字符设备。
% C% R( G4 Q4 H1 o+ s第308~311行,ap3216c_id匹配表,i2c_device_id类型。用于传统的设备和驱动匹配,也就是没有使用设备树的时候。4 H( v' v0 v+ q; S; y& h$ }+ a6 }
第314~317行,ap3216c_of_match匹配表,of_device_id类型,用于设备树设备和驱动匹配。这里只写了一个compatible属性,值为“alientek,ap3216c”。
# n% b0 j- Q( u8 S: O6 R第320~329行,ap3216c_driver结构体变量,i2c_driver类型。
; v- n+ z! ]' S第336~342行,驱动入口函数ap3216c_init,此函数通过调用i2c_add_driver来向Linux内核注册i2c_driver,也就是ap3216c_driver。" A; N: S) j" f
第349~352行,驱动出口函数ap3216c_exit,此函数通过调用i2c_del_driver来注销掉前面注册的ap3216c_driver。8 U' ~: y( D% D6 v
40.6.3 编写测试APP" u1 F2 b' q6 C2 S& F! Q
新建ap3216cApp.c文件,然后在里面输入如下所示内容:
$ U5 }8 G) w$ B2 ~
# G5 V4 [4 r& S4 S
  1. 示例代码40.6.3.1 测试APP / r! l. u. ^1 O$ F+ S7 q+ P
  2. 12  #include "stdio.h"
    - t- U: ^9 e; x$ m
  3. 13  #include "unistd.h"
    9 j3 I& v3 W. }4 o+ Q2 Q
  4. 14  #include "sys/types.h"
      \2 |9 U9 n0 k7 W
  5. 15  #include "sys/stat.h"
    - N, N5 n. Y/ ~$ s
  6. 16  #include "sys/ioctl.h"
    ! n0 f9 b0 L: K/ {+ O3 F2 o$ N
  7. 17  #include "fcntl.h"  l$ j% R9 |5 ]' Y3 r5 U
  8. 18  #include "stdlib.h"
    ' \- l& g% W6 _' O- q
  9. 19  #include "string.h"
    ( ~# @9 ]' }1 o- x+ T. I- K
  10. 20  #include <poll.h>) o8 I2 G  b# h+ [6 ]
  11. 21  #include <sys/select.h>
      o2 P2 ]- o. y
  12. 22  #include <sys/time.h>
    0 l7 ^' e; Y  R
  13. 23  #include <signal.h>
    9 e8 o) {6 [4 }# ?$ h6 ^
  14. 24  #include <fcntl.h>! R3 ~: n1 A3 e2 D/ n& `+ N7 G+ n4 X1 `5 G
  15. 25  /*  O+ _  C% e6 V6 k
  16. 26   * @description         : main主程序
    ) Y% X8 w/ y3 c. s
  17. 27   * @param - argc         : argv数组元素个数
    - ?/ [  j  f% m* ~1 J5 @
  18. 28   * @param - argv         : 具体参数
    8 A! h; v1 e; _4 F
  19. 29   * @return                : 0 成功;其他 失败
    6 q2 q8 {3 {- }  D7 J9 E
  20. 30   */
    5 X- e* _3 {9 l
  21. 31  int main(int argc, char *argv[])! W% Q/ U2 |4 F2 r, K
  22. 32  {
    " ^; G, h$ ^8 {% c
  23. 33      int fd;" U, A% V0 i& Z' Y
  24. 34      char *filename;+ W, ^% t- B- ]) a5 c6 ]
  25. 35      unsigned short databuf[3];
    ; P2 Z) n! T6 h' D2 y2 k
  26. 36      unsigned short ir, als, ps;
    , O- n$ b+ E) ]8 V
  27. 37      int ret = 0;. s5 ?+ Z" O  U- R
  28. 38! `: L1 X& p7 \) a; z/ Q9 V
  29. 39      if (argc != 2) {) {# ]% F# ?  X& P# c
  30. 40          printf("Error Usage!\r\n");1 W3 h) H" C. {6 b6 `$ B/ W$ ~, m
  31. 41          return -1;
    * p. u# J' [+ i% Z% u+ M+ Z
  32. 42      }
    , I3 c9 P( T  h* n, l
  33. 432 @! p; a7 _$ v7 n" \% X7 s
  34. 44      filename = argv[1];
    # _" v7 E, M# c" W# g  H; f
  35. 45      fd = open(filename, O_RDWR);  n. c) E, |) R
  36. 46      if(fd < 0) {) a2 i( }! z) y0 J( f% y
  37. 47          printf("can't open file %s\r\n", filename);
    ! ^1 }0 [2 ~) h) F# _* |7 V& n6 g
  38. 48          return -1;
    # p  b+ Q, F; r* R2 M
  39. 49      }
    % a% C5 e& O6 U- ^
  40. 50) G, K" B% B( B9 K) H; I& T7 Y
  41. 51      while (1) {. v* Y9 k9 a# m$ m: G
  42. 52          ret = read(fd, databuf, sizeof(databuf));
    2 {) c' v2 R3 e8 s: x& C$ j/ g
  43. 53          if(ret == 0) {                  /* 数据读取成功 */  }. P  v9 _, a" |4 Q3 P
  44. 54              ir =  databuf[0];           /* ir传感器数据 */' J# l7 o# G% j7 l7 ?1 e1 m
  45. 55              als = databuf[1];           /* als传感器数据 */  I! @6 V6 }9 P5 ~. n: ^/ K3 _
  46. 56              ps =  databuf[2];           /* ps传感器数据 */
    8 i' n# T& u' q
  47. 57              printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);+ q& F) |8 q( V/ M8 p
  48. 58          }1 Z; Q; B1 K) C9 w" x" t4 N! ]
  49. 59          usleep(200000);         /*100ms */, x' w' t* u8 a4 n8 a8 S
  50. 60      }" X: O+ j6 }! t2 a
  51. 61      close(fd);                          /* 关闭文件 */  6 I4 Z: R2 t, e3 X
  52. 62      return 0;
    + R6 y& o# h( l  A; I% B" C6 o' `
  53. 63  }
复制代码

% }/ \  {0 W2 k8 V  N3 ]ap3216cApp.c文件内容很简单,就是在while循环中不断的读取AP3216C的设备文件,从而得到ir、als和ps这三个数据值,然后将其输出到终端上。
# b/ z( H5 O# N! W/ m! \1 Q7 \# o& v" n+ B$ u4 z
40.7 运行测试* s8 Y" b( n( k# v
40.7.1 编译驱动程序和测试APP( i4 R6 x$ f4 `! v- c6 i
1、编译驱动程序+ o, A0 P- ~9 Z2 {1 k. U  U4 }: {5 I; \
编写Makefile文件,本章实验的Makefile文件和第四十章实验基本一样,只是将obj-m变量的值改为“ap3216c.o”,Makefile内容如下所示:6 Y8 B9 o8 N/ F0 |

! W8 r9 t* F0 a% H2 G$ b
  1. 示例代码40.7.1.1 Makefile文件
    & {# {4 L0 a. j( G
  2. 1  KERNELDIR := /home/zuozhongkai/linux/my_linux/linux-5.4.31
    0 m" K8 ?* C* a" |
  3. ...... % w2 }1 D* v1 m" ^7 i) S4 G; b
  4. 4  obj-m := ap3216c.o0 N3 Z2 }+ g2 A0 J3 g
  5. ......
    % g2 g& w: \) z0 R4 U& {
  6. 11 clean:
    7 K* r/ W" w( ]9 f" J( h- T
  7. 12  $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
复制代码
1 X( e* O9 `9 C2 d& ^
第4行,设置obj-m变量的值为“ap3216c.o”。
  c! e4 z! U& U3 o. Y$ l' R输入如下命令编译出驱动模块文件:& P- n: Q8 E3 {% u
make -j32" @+ c- n( P3 c* G( |. ^
编译成功以后就会生成一个名为“ap3216c.ko”的驱动模块文件。- K. S8 L5 P  q
2、编译测试APP9 T6 w- L. B0 p3 t6 K
输入如下命令编译ap3216cApp.c这个测试程序:
# C' _3 \  w+ c, P5 Sarm-none-linux-gnueabihf-gcc ap3216cApp.c -o ap3216cApp
2 ]1 I! f4 Q- F编译成功以后就会生成ap3216cApp这个应用程序。9 h8 i' x+ P4 h
40.7.2 运行测试1 `1 H  o- P! }6 O8 J. D7 e
将上一小节编译出来ap3216c.ko和ap3216cApp这两个文件拷贝到rootfs/lib/modules/5.4.31目录中,重启开发板,进入到目录lib/modules/5.4.31中。输入如下命令加载ap3216c.ko这个驱动模块。7 M$ M3 G( U0 w: c$ a
depmod //第一次加载驱动的时候需要运行此命令
9 I+ B. _: b/ Q0 x  cmodprobe ap3216c //加载驱动模块
8 c/ c2 X8 _6 e  M8 Q( c7 P当驱动模块加载成功以后使用ap3216cApp来测试,输入如下命令:
9 x6 t8 M6 d0 c5 ?% M5 {./ap3216cApp /dev/ap3216c! W; V2 E1 V4 ^) F) f' a
测试APP会不断的从AP3216C中读取数据,然后输出到终端上,如图40.7.2.1所示:
, y: Q2 @- [- c7 C
9 N5 x0 _6 [. H( z  k  p) m abfe797123a54c5bad13596f12b073e5.png + n" B4 ?' v( p4 V  ]9 ]/ [4 M
4 I+ F5 X& H% @4 W" \2 z
图40.7.2.1 获取到的AP3216C数据
, e/ \. N: \" _7 B8 A大家可以用手电筒照一下AP3216C,或者手指靠近AP3216C来观察传感器数据有没有变化。  j$ X( P+ V# L# {
————————————————
# W9 g! L. Z1 n4 d+ g版权声明:正点原子
: I) N/ k+ Z) n9 [- A4 G# D6 ?; w
# f  t, L* u  _
收藏 评论0 发布时间:2022-9-27 15:03

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版