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% \
' 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
' 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
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: |
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 @
+ 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
$ 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
& 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
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 ~
) 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
- 示例代码40.2.1 i2c_adapter结构体
* v# C$ I! ^4 w4 a8 ^# s - 685 struct i2c_adapter {" q% l3 k8 H8 f1 P5 _4 U
- 686 struct module *owner;5 B, E1 x) Y; c$ l6 E
- 687 unsigned int class;
a0 c' A% w8 \ A, l I - 688 const struct i2c_algorithm *algo;
. O2 q; J% r- w7 k3 X - 689 void *algo_data;' R7 G5 l( ?2 R
- 690
& [5 F1 e; _7 h$ u - 691 /* data fields that are valid for all devices */* s( Y6 h5 h2 c3 f
- 692 const struct i2c_lock_operations *lock_ops;* n, P+ z% J( t% j& z9 Q7 X
- 693 struct rt_mutex bus_lock;
% k8 n H' F* c4 |& Z6 \/ } - 694 struct rt_mutex mux_lock;* e, e3 |& }* X1 m& n/ s+ H
- 695
, w, ~. _7 ]+ T4 q1 `6 Q - 696 int timeout; /* in jiffies */* O: X ~: x' u7 B
- 697 int retries;0 {* e8 k! I' o
- 698 struct device dev; /* the adapter device */
5 k- U7 b" ^& ~- ^ - 699 unsigned long locked_flags; /* owned by the I2C core */
. _/ h- g# W6 j) l+ L: R - 700 #define I2C_ALF_IS_SUSPENDED 0" p1 J! t- {- `3 Y- q9 O
- 701 #define I2C_ALF_SUSPEND_REPORTED 1' D( F) h5 h' W6 T
- 702/ V) n: x& ?: {2 e# Y
- 703 int nr;; a1 U' L3 ]& n$ E
- 704 char name[48];
5 Q J' u" k/ |. R: T" ^2 | - 705 struct completion dev_released;' G3 f' J$ y# p( a W* w" ]3 R+ q
- 706
& ?# W# Z# U. G5 z i - 707 struct mutex userspace_clients_lock;
8 n- L+ O- H5 _5 U( E6 n& i - 708 struct list_head userspace_clients;% l# k' n( M" N, P( |! D
- 7094 Y1 [; a! z- f/ D1 W, J
- 710 struct i2c_bus_recovery_info *bus_recovery_info;0 o( u' t$ |) A! G# ^
- 711 const struct i2c_adapter_quirks *quirks;
- D! B6 a- Z% h/ l - 712
% c1 q# B! R+ h8 j2 _2 n - 713 struct irq_domain *host_notify_domain;
# w$ I; Z8 O8 c1 } - 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
- 示例代码40.2.1.2 i2c_algorithm结构体1 L7 K& s; M8 g6 |9 Z
- 526 struct i2c_algorithm {
$ f! F8 K4 t" b - 527 /*& B+ B3 d0 d1 t
- 528 * If an adapter algorithm can't do I2C-level access, set 5 [ y- ~2 q' e( u9 R! x
- 529 * master_xfer to NULL. If an adapter algorithm can do SMBus
1 [9 j! Y2 P1 x - 530 * access, set smbus_xfer. If set to NULL, the SMBus protocol is
8 q/ a9 F }* I v - 531 * simulated using common I2C messages.7 x/ X. D# L2 Y
- 532 *
- t4 ^& F; X$ u8 E, d [! T7 A - 533 * master_xfer should return the number of messages successfully
9 e$ t7 b4 Y( n9 B% ]1 b - 534 * processed, or a negative value on error* Q) S2 c6 d l0 X# Z1 B- B
- 535 */
6 \% c, D5 K+ ~/ q - 536 int (*master_xfer)(struct i2c_adapter *adap, 9 d" ^8 {1 l# m, V- l! c S
- struct i2c_msg *msgs,( \- _4 X2 W$ s& r# C
- 537 int num);
# Q# N$ Y. P0 x; g% f. e - 538 int (*master_xfer_atomic)(struct i2c_adapter *adap,# H) p+ j5 c1 \+ ^( F
- 539 struct i2c_msg *msgs, int num);
* V1 _/ m5 H# E/ ]! ~ - 540 int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr,8 ^ m6 y9 j; {0 o# g; E) z% k1 h/ b
- 541 unsigned short flags, char read_write,) x+ t& ~6 p9 R3 `
- 542 u8 command, int size, union i2c_smbus_data *data);, d5 K( m: a \5 F0 T
- 543 int (*smbus_xfer_atomic)(struct i2c_adapter *adap, u16 addr,0 X! {- V- r5 H, ~: X/ q5 [ e
- 544 unsigned short flags, char read_write,
: l/ z; U$ y3 \ - 545 u8 command, int size, union i2c_smbus_data *data);
6 e4 w/ [ H% a& F6 ~+ V - 546- Z! w! {+ \' w j# g/ P! p3 J$ O* A. p
- 547 /* To determine what the adapter supports */
) ~/ o5 x8 o2 i - 548 u32 (*functionality)(struct i2c_adapter *adap);9 t4 G2 {/ V6 t1 k" s, A% W
- 549' x ?, @' \& B! X6 @/ y. v$ e
- 550 #if IS_ENABLED(CONFIG_I2C_SLAVE)5 M Z6 L2 E d: T+ E* M
- 551 int (*reg_slave)(struct i2c_client *client);2 }8 d' K7 O0 ]) m
- 552 int (*unreg_slave)(struct i2c_client *client);' C% b/ e/ M7 w0 a
- 553 #endif
" x" I$ J+ G0 D( U, z: }4 a! B - 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- 示例代码40.2.2.1 i2c_client结构体, o8 f" B3 q8 F; S, ]
- 313 struct i2c_client {
1 R- \6 E. r1 j: X - 314 unsigned short flags; /* div., see below */# B) L& _+ h5 A" X7 o
- ......" @0 S7 q$ @$ r; j) t, V
- 328 struct i2c_adapter *adapter; /* the adapter we sit on */; m" y: Z u7 G0 @% ~, s. V6 [
- 329 struct device dev; /* the device structure */& R7 D9 E1 w6 h% w; n2 l
- 330 int init_irq; /* irq set at initialization */0 `6 b4 K6 `3 w) g% B+ G2 K
- 331 int irq; /* irq issued by device */, M5 t) w: ^5 q- z: L; Z
- 332 struct list_head detected;- Q) [: `. o) o# P8 Y
- 333 #if IS_ENABLED(CONFIG_I2C_SLAVE)
; V1 u9 I6 X2 b- Z- H+ J6 _# E, I% k - 334 i2c_slave_cb_t slave_cb; /* callback for slave mode */
4 e. Q$ M+ b3 e( r - 335 #endif
6 o5 J" o" G" N) W0 i - 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! ~- 示例代码40.2.2.2 i2c_driver结构体
' G$ ]( _( R( K+ f+ S5 I5 X - 253 struct i2c_driver {
% f/ v3 h5 W+ q* G& d X - 254 unsigned int class;
" i* ^) g8 j2 q7 h% u - 255 2 V9 i% o" S2 K( [6 W; ~- ]
- 256 /* Standard driver model interfaces */* j% U6 h: d0 i2 t* J* M
- 257 int (*probe)(struct i2c_client *client, 5 f. F" ^) S( L; O. Q
- const struct i2c_device_id *id);! i* c- j% L6 I' p) [- b
- 258 int (*remove)(struct i2c_client *client);
4 L& @0 M1 ^) u8 r - 259 3 e. J! ?: O8 \! T( Q* N7 s
- 260 /* New driver model interface to aid the seamless removal of
# y* `7 z; e4 O. K - 261 * the current probe()'s, more commonly unused than used
' o& [* _- D9 { - 262 second parameter.*/
& Q% x3 E" G6 J2 A) u - 263 int (*probe_new)(struct i2c_client *client);
5 I' B1 d* _+ z8 U - 264 , \3 j0 Z4 ]5 A1 y( {* X* n
- 265 /* driver model interfaces that don't relate to enumeration */( u N5 N( E( }9 L0 b
- 266 void (*shutdown)(struct i2c_client *client);! m" g* F" d! U
- 267
/ a1 Z; B @" x6 ^9 y$ }- |5 |8 q - 268 /* Alert callback, for example for the SMBus alert protocol.9 Q4 b! T! h. K* m$ a$ {
- 269 * The format and meaning of the data value depends on the k* _, |, R! _
- 270 * protocol. For the SMBus alert protocol, there is a single : G& \& [1 s9 }$ r* {
- 271 * bit of data passed as the alert response's low bit ("event " z2 s& _" p% U# h9 p
- 272 * flag"). For the SMBus Host Notify protocol, the data
# z2 D3 j) Q& H$ U, _8 N) ~* e - 273 * corresponds to the 16-bit payload data reported by the
0 C- `6 p: ], M: r' T - 274 slave device acting as master.*/
! h8 i; \5 {# k - 275 void (*alert)(struct i2c_client *client,
) |2 u ^2 j0 I" w - enum i2c_alert_protocol protocol,
7 H C, d T- I8 B3 F2 V - 276 unsigned int data);
; B n3 P7 m9 t8 U. Z! v' f - 277 , m3 c2 \$ t0 P M
- 278 /* a ioctl like command that can be used to perform specific Y" y$ ~( }/ x2 g4 N0 u
- 279 * functions with the device.
' K; r# E4 Q7 o( H8 y - 280 */
2 c# ^; i& j" S" T1 v' J - 281 int (*command)(struct i2c_client *client, unsigned int cmd, ( G# O3 y3 C' p$ j; Q t; O
- void *arg);
" }! u. n8 N. j+ I - 282
, E F0 h' k' J& e0 m! _ - 283 struct device_driver driver;
8 @1 p- H# c2 B - 284 const struct i2c_device_id *id_table;
: R" m- r9 \! v/ v( z* \ - 285 + ~) l6 W% J' b/ Q# I, w
- 286 /* Device detection callback for automatic device creation */
5 R7 o! o e) }# G - 287 int (*detect)(struct i2c_client *client,
' J0 f* j2 [9 t& K7 S Y - struct i2c_board_info *info);4 b% H" B" P1 e, B
- 288 const unsigned short *address_list;" I( L+ }; {& [( y
- 289 struct list_head clients;$ v# L% Q. {+ r/ a0 l, Z) A, ^
- 290
5 W* X2 ~$ g( {" H - 291 bool disable_i2c_core_irq_mapping;
% c, [, ~+ `( |0 I - 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
- 示例代码40.2.2.3 i2c_add_driver宏$ c# R0 t- `3 s- D) b' @4 T
- 844 #define i2c_add_driver(driver) \. P9 g. l' h' X# k/ \4 {6 z3 ]! a
- 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
- 示例代码40.2.2.4 i2c_driver注册流程
9 z4 P, C( ?( l1 H3 j - 1 /* i2c驱动的probe函数 */
2 ^) L! B9 I* G1 U- P1 b - 2 static int xxx_probe(struct i2c_client *client, * _7 `/ ]) {1 G' Y7 g5 t' @" v: u% r
- const struct i2c_device_id *id)- g0 c( W$ J7 [. @
- 3 {
# D$ B$ J& t0 `7 H* y, s" U" y8 l3 b - 4 /* 函数具体程序 */
9 S @ W) E c% P/ a - 5 return 0;# g3 y/ U: m3 O
- 6 }
( c$ a1 w& m- u4 a2 z( d - 7
% E! b4 z; y2 P - 8 /* i2c驱动的remove函数 */' ]4 |, u4 N2 k, N+ }
- 9 static int ap3216c_remove(struct i2c_client *client)& s2 @* P4 S1 J7 f6 O" X$ Y/ X0 }
- 10 {
* N3 O% o. }0 E& J9 X) L - 11 /* 函数具体程序 */
! S% P4 B& d! U$ \; G6 e5 j - 12 return 0;' I$ S4 j' T* u+ }1 }2 X7 T/ g5 m# D
- 13 }
/ K" A9 A; y! n3 V! ] - 14 8 ?( L6 ^1 Y0 Z* \. l
- 15 /* 传统匹配方式ID列表 */8 d: f! Y" U- H g" J' [
- 16 static const struct i2c_device_id xxx_id[] = {
1 W; c( _# T/ \4 H* ` - 17 {"xxx", 0},
3 a5 _: i% V6 [7 M - 18 {}; {; L. ^' p" C/ c! m
- 19 };$ z( O) A3 x& y( v; t) V/ W
- 20
& ` a' s6 _2 T - 21 /* 设备树匹配列表 */8 O" j7 P' s3 Q( K: H& B6 r+ X& |' `
- 22 static const struct of_device_id xxx_of_match[] = {8 m( s2 P0 [( d+ }3 R! x3 n
- 23 { .compatible = "xxx" },
: G; n! E- _5 {. ?- H! F, N5 V/ ? - 24 { /* Sentinel */ }
* D( n( ]! i$ ]. K: z4 M8 [2 R8 d5 Y - 25 };3 l' Q7 c! @" _" e( T
- 26 5 ^. d% n: n. U0 O3 ?0 f
- 27 /* i2c驱动结构体 */ |; \: j& T& l2 A/ H7 i
- 28 static struct i2c_driver xxx_driver = {3 G' m2 Y6 B/ H! i j# y! Q/ y$ N
- 29 .probe = xxx_probe,. S. ]6 T) P! q) |6 [" |2 J* b
- 30 .remove = xxx_remove,
8 B5 H' D/ ~! X4 V/ M1 ` - 31 .driver = {" U6 d' X( j6 ^
- 32 .owner = THIS_MODULE,6 i! N% m% W, M: T: w& v
- 33 .name = "xxx",
3 B4 O4 V) E6 H" n! y4 n$ N - 34 .of_match_table = xxx_of_match,
4 E' J0 K+ p6 F- w! d1 t - 35 },
, e$ J/ R2 ~- c6 k, W. k - 36 .id_table = xxx_id,
6 B, S7 U+ F- a1 | - 37 };
$ m" c7 w6 r- I8 [' z8 U - 38 1 |; c" `( k- S8 U
- 39 /* 驱动入口函数 */
# T- |+ V) b' |* C; X - 40 static int __init xxx_init(void)
. Z! W! ~# j- Z8 \1 i - 41 {" `0 r& W) J2 X# P2 T
- 42 int ret = 0;6 t6 f n& p- k* p1 s" Q# J( X% O' U% N
- 43 % T6 c. `) h% S! k
- 44 ret = i2c_add_driver(&xxx_driver);
& Z: w$ G: \" u5 z) g0 J& l - 45 return ret;
: u' p) V. _+ [2 x9 z* l" B - 46 }
2 T! n, u9 m5 g7 L% _4 u - 47 0 e" h9 ^+ K6 E) S6 D( O9 P" v
- 48 /* 驱动出口函数 */. W, Y: A& R$ w6 h6 j3 q
- 49 static void __exit xxx_exit(void)
* v6 {3 L0 J+ F6 d( w. }. @- m - 50 {% ]% n+ W [! l' f" e$ @
- 51 i2c_del_driver(&xxx_driver);1 v: v9 G$ Z, V$ u! v6 }
- 52 }
P- S: t: @/ J$ V$ c7 n - 53 " D/ @- P' L- z) X: [5 E5 k# K7 \
- 54 module_init(xxx_init);
+ V& s# r" |, e8 z* ~) M* G - 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- 示例代码40.2.3.1 i2c_bus_type结构体
2 Y R$ R5 h& d. g+ N( e$ ^5 l - 492 struct bus_type i2c_bus_type = {
( B' X& r$ D! V) k - 493 .name = "i2c",
[3 O* k2 }% O - 494 .match = i2c_device_match,
# V+ h1 S' i8 s9 ~; [, i, I$ C - 495 .probe = i2c_device_probe,0 F' \9 e2 P, k# d: k# ^
- 496 .remove = i2c_device_remove,
4 J. i' s: @% \' R# i2 m/ l2 \ - 497 .shutdown = i2c_device_shutdown,
* D, S4 f6 d' C9 l - 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
- 示例代码40.2.3.2 i2c_device_match函数
. j! m! ~/ y3 p, p - 93 static int i2c_device_match(struct device *dev,
2 Y3 \6 R% t" e* R - struct device_driver *drv)
2 S1 x7 q5 P. P/ C( Z7 x - 94 {
- B. W, L' C% _' K3 | - 95 struct i2c_client *client = i2c_verify_client(dev);
1 I3 @( Y6 j& w9 C# F; X - 96 struct i2c_driver *driver;
' h* {' r6 c0 \& H b - 97 ) B. ]8 s5 m$ a, D2 A8 i, T
- 98
- ]1 T" G0 {+ J* ^. `9 J! s - 99 /* Attempt an OF style match */8 P; W! {* i2 o' S
- 100 if (i2c_of_match_device(drv->of_match_table, client))
4 A2 n6 |: l8 q' K9 l, ] - 101 return 1;: @& p: h6 V! t# x, w. B" {
- 102
( [; V$ N2 D- ?1 V1 c6 z, ^& B - 103 /* Then ACPI style match */
: H+ z1 k! ]8 ^5 e+ A) o- N; M - 104 if (acpi_driver_match_device(dev, drv))& h; Z" H3 u+ M5 A5 h
- 105 return 1;
0 X& K( C& w5 {! @9 Q- u. M0 s! Z - 106
+ ~% Y& |0 C! j0 {2 O - 107 driver = to_i2c_driver(drv);
# Z) _ |! d" y5 ^2 q* n - 108
0 f$ C7 y, [: K9 d- i1 p' D - 109 /* Finally an I2C match */
, M1 z8 k2 w2 o( Y - 110 if (i2c_match_id(driver->id_table, client)). b+ s. X- Q z4 v7 _* R
- 111 return 1;
2 E- S( C9 \5 h - 112
1 }4 t! D9 v9 e) d# n& Q - 113 return 0;
7 r( n6 b$ p1 p7 Z6 V' f) X - 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
- 示例代码40.3.1 I2C1控制器节点
5 u9 o3 P2 E3 U - 590 i2c1: i2c@40012000 {
/ r* g/ Q* }- ^' R# X( M% Y - 591 compatible = "st,stm32mp15-i2c";9 ~' i& k, Z" f6 D4 b
- 592 reg = <0x40012000 0x400>;
" O+ b% L) N3 ]5 t" A8 G% Y - 593 interrupt-names = "event", "error";
3 l, |2 G8 l1 A9 K% ~) c6 t - 594 interrupts-extended = <&exti 21 IRQ_TYPE_LEVEL_HIGH>,; X8 F& g( C% n$ {
- 595 <&intc GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
) ^9 h0 e- s5 S9 @1 X5 l, l' m" L - 596 clocks = <&rcc I2C1_K>;/ r) q# e) b. q" W. \% b
- 597 resets = <&rcc I2C1_R>;: ]2 k5 a2 r9 {
- 598 #address-cells = <1>;+ z' t: l! U( V
- 599 #size-cells = <0>;
# {0 n+ K& @6 Z - 600 dmas = <&dmamux1 33 0x400 0x80000001>,
& [1 c: e1 E% i - 601 <&dmamux1 34 0x400 0x80000001>;; d1 T6 K; _, X! b' m
- 602 dma-names = "rx", "tx";3 h8 h) {% b+ X |$ o E
- 603 power-domains = <&pd_core>;" `. M& v+ @1 V1 g
- 604 st,syscfg-fmp = <&syscfg 0x4 0x1>;
% y; f) M1 B2 V - 605 wakeup-source;
: [+ j5 T" K' ^8 @; e - 606 status = "disabled";
' j* R" D. N+ B. J, s - 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- 示例代码40.3.2 i2c-stm32f7.c文件代码段
1 f7 t6 l1 P) [: M5 x. ]% f - 2520 static const struct of_device_id stm32f7_i2c_match[] = {
8 A, H0 s `) F4 b/ ~- \ - 2521 { .compatible = "st,stm32f7-i2c", .data = &stm32f7_setup},5 @6 D8 S' @4 |& [$ ^; @
- 2522 { .compatible = "st,stm32mp15-i2c", .data = &stm32mp15_setup},! u8 p3 w) `5 y" U
- 2523 {},
$ b4 q$ e: v* Z& Y i0 \9 @ - 2524 };
2 ~0 E6 v' d! G( c, E W - 2525 MODULE_DEVICE_TABLE(of, stm32f7_i2c_match);- a: ^9 i, t! t |5 `! K, U) O2 ~" O
- 2526
# V, p0 w2 {* D2 B$ l& e4 i0 k - 2527 static struct platform_driver stm32f7_i2c_driver = {# M5 M3 U: l# Y3 f' P6 t& h$ g% z
- 2528 .driver = {+ b# N; C' B0 d; Q
- 2529 .name = "stm32f7-i2c",2 T1 D7 s% U. s# Z
- 2530 .of_match_table = stm32f7_i2c_match,' @( x3 b5 J) u; a0 Y/ V
- 2531 .pm = &stm32f7_i2c_pm_ops,1 s0 I! q# W! v5 b
- 2532 },
' U) V; |* w4 l) `# y7 F r: A - 2533 .probe = stm32f7_i2c_probe,
3 L$ E0 y: `1 r: Z7 { - 2534 .remove = stm32f7_i2c_remove,7 }" ^$ m# T. E/ h; r
- 2535 };
+ Q+ v. G1 T8 G' j9 C - 2536
2 s* |( f4 F& I3 Y/ S - 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 `- 示例代码40.3.3 stm32f7_i2c_probe函数代码段
; u* I) U0 m, j - 2106 static int stm32f7_i2c_probe(struct platform_device *pdev)8 Q- N2 W0 o& P6 L* B4 e
- 2107 {+ H" `8 O. ]' I1 w$ s- C4 G8 a% _
- 2108 struct stm32f7_i2c_dev *i2c_dev;
3 n' \5 d4 u1 }1 s) S4 @( P& v* m S - 2109 const struct stm32f7_i2c_setup *setup;
. O" x" w+ l" X3 J - 2110 struct resource *res;. S/ X/ n: m( {5 c$ V% G" K' K% O
- 2111 u32 rise_time, fall_time;! M( m" U+ M( v/ ^* D
- 2112 struct i2c_adapter *adap;
2 |- D5 `/ U% N! Q - 2113 struct reset_control *rst;
: K2 g* p! n) X9 L - 2114 dma_addr_t phy_addr;
. a, Z: E: s7 h6 N - 2115 int irq_error, ret;
O) E' l* N! l) h( s# g - 2116
y; Q% v8 f/ |5 M/ {2 b% Y! h - 2117 i2c_dev = devm_kzalloc(&pdev->dev, sizeof(*i2c_dev), N) O0 E# R' a* U. _' W" B1 ]
- GFP_KERNEL);/ _: y; a2 h4 f& K
- 2118 if (!i2c_dev)6 C& d) o3 C7 _5 z b! ?
- 2119 return -ENOMEM;
' u' @& D0 {1 H* S8 t - 2120
5 R" r( a9 E5 H" r4 W+ W - 2121 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);- x3 J( A/ H6 b! f0 J
- 2122 i2c_dev->base = devm_ioremap_resource(&pdev->dev, res);
, S" _' u! h* _6 R5 O - 2123 if (IS_ERR(i2c_dev->base))
( E+ f( b3 z3 S8 A* U - 2124 return PTR_ERR(i2c_dev->base);! J: z. u$ I# Y8 _1 m& M+ Q
- 2125 phy_addr = (dma_addr_t)res->start;& I( C! S; d. S( Y1 H# L& z
- 2126% M0 o- K& }( n+ @3 ]
- 2127 i2c_dev->irq_event = platform_get_irq(pdev, 0);
( x! {; N' y% \( V; u: y - 2128 if (i2c_dev->irq_event <= 0) { O5 L. B5 r4 t. q2 R, y
- 2129 if (i2c_dev->irq_event != -EPROBE_DEFER)
7 g% }3 u! w2 |* {, ?, v - 2130 dev_err(&pdev->dev, "Failed to get IRQ event: %d\n",
! T# K0 K. u8 @+ M) N- A - 2131 i2c_dev->irq_event);
; g/ W4 U& @) i5 N - 2132 return i2c_dev->irq_event ? : -ENOENT;* B( H; p% U2 R3 T
- 2133 }
! h, r* r0 I% b" l# q# |# b: {2 G8 \ - 2134
) ?) R% v9 g- k8 v - 2135 irq_error = platform_get_irq(pdev, 1);
' D m z) H3 A - 2136 if (irq_error <= 0) { I) _8 w3 w: i' k6 l& {
- 2137 if (irq_error != -EPROBE_DEFER)) Y* m z( B8 y- C7 i( l3 s# J
- 2138 dev_err(&pdev->dev, "Failed to get IRQ error: %d\n"," C" o9 A6 H( j3 i* y+ C
- 2139 irq_error);
. _: D. j0 K9 ^, e) P. |( ~% K3 L - 2140 return irq_error ? : -ENOENT;
/ H" {0 i( r. Z" u4 K0 S0 ~ - 2141 }0 \# L' S8 ]2 o2 z; p
- ......0 R \* ^# x3 R6 I) j* n A1 R
- 2159 ret = device_property_read_u32(&pdev->dev, "clock-frequency",
+ B$ d8 O% h+ Y2 Z# V; n7 ~: y - 2160 &i2c_dev->bus_rate);# |" I8 J. {! {
- 2161 if (ret)
6 B4 ?& ]2 b/ w3 h. I1 c: m0 P! { - 2162 i2c_dev->bus_rate = I2C_STD_RATE;* O: s. K7 i/ C6 E9 @- M; s
- 21633 g3 N$ i$ _9 h8 b0 B8 s: W7 Y
- 2164 if (i2c_dev->bus_rate > I2C_FASTPLUS_RATE) {4 s9 j( P' o% J( x& L) S
- 2165 dev_err(&pdev->dev, "Invalid bus speed (%i>%i)\n",
2 y2 a* ?& |% m - 2166 i2c_dev->bus_rate, I2C_FASTPLUS_RATE);# y/ _, Z9 m+ W" @) B
- 2167 return -EINVAL;0 f- J0 u) D) A: w1 L4 N$ u a1 w
- 2168 }. }" e( }3 t! F t8 {6 h
- ......2 Y* U! N' Q! y& o
- 2183
+ e3 z3 K3 s+ m/ \3 a1 {- { - 2184 ret = devm_request_threaded_irq(&pdev->dev, ! H4 s( T+ W3 i% ^0 L8 _9 z) V
- i2c_dev->irq_event,* [$ Y4 c0 S5 J9 _/ p. }* g6 i
- 2185 stm32f7_i2c_isr_event,7 B7 v- l1 e. r, y
- 2186 stm32f7_i2c_isr_event_thread,8 ?2 o) A1 J+ _" F& v
- 2187 IRQF_ONESHOT,) Y2 k/ t: P9 m" {, j6 C
- 2188 pdev->name, i2c_dev);
" k& ~( H% X% I- d - 2189 if (ret) {
" G' M5 h: E% U" P5 \& t - 2190 dev_err(&pdev->dev, "Failed to request irq event %i\n",# ?- H2 K# w3 C6 m% B7 e
- 2191 i2c_dev->irq_event);
8 T5 i: x3 Y9 ?5 V9 G - 2192 goto clk_free;
8 I$ ^" Q* Z; }# n - 2193 }
5 v7 U+ Z0 z+ H( @ - 2194
0 X \- B" m- {: }% B8 V' A - 2195 ret = devm_request_irq(&pdev->dev, irq_error,
- Z C0 l2 ^ S - stm32f7_i2c_isr_error, 0,! M# A) K8 c7 L
- 2196 pdev->name, i2c_dev);
; T9 `1 E8 u- J: e4 X1 W - 2197 if (ret) {
) ^4 `- b0 _: l- Y- N - 2198 dev_err(&pdev->dev, "Failed to request irq error %i\n",* m4 R5 L! J5 n$ A7 Z7 v! x( N
- 2199 irq_error);
L, V8 D0 F1 m- ^% x - 2200 goto clk_free;* } O1 _: ~% o- F
- 2201 }
* ^- N7 p/ H7 c2 l2 U+ B - , A' F! w) O; R! o$ |9 L
- 2226 if (i2c_dev->bus_rate > I2C_FAST_RATE) {+ J z7 C i5 b9 Q6 n8 f
- 2227 ret = stm32f7_i2c_setup_fm_plus_bits(pdev, i2c_dev);
) w* V8 b O' d% l/ q$ \- \ - 2228 if (ret)
" c) b# x8 ] g* R - 2229 goto clk_free;
p A3 p8 J& u1 o$ g - 2230 }
& z! X7 r$ t6 j7 N S% j - 22310 [) e8 _" l. B# Z Q
- 2232 adap = &i2c_dev->adap;" [- [2 V9 q: z: M- Y3 x
- 2233 i2c_set_adapdata(adap, i2c_dev);
" ?& r8 o0 E7 A - 2234 snprintf(adap->name, sizeof(adap->name), "STM32F7 I2C(%pa)",2 V: r7 K" r4 ^; t, R2 I' ~
- 2235 &res->start);
% r) {2 k! T$ D! I" I- Y - 2236 adap->owner = THIS_MODULE;
0 M- I0 i0 ], @+ m- C, P - 2237 adap->timeout = 2 * HZ;/ P$ M s9 e5 {, K/ `9 s
- 2238 adap->retries = 3;
/ _/ s! X5 z5 i, r - 2239 adap->algo = &stm32f7_i2c_algo;- u! S l3 O3 R
- 2240 adap->dev.parent = &pdev->dev;
_$ P3 \8 r" _" b) K# c0 C" J - 2241 adap->dev.of_node = pdev->dev.of_node;
8 ?# h* V4 e- y6 s+ t7 j2 d - 2242
& O( r* P* K6 x D - 2243 init_completion(&i2c_dev->complete);( i) z% N" Y8 e9 s
- 2244) X+ d0 w' d% h3 [6 F8 G! s% }$ S0 o
- 2245 /* Init DMA config if supported */
' D; e1 E9 Q/ C( j: R1 W7 T% J - 2246 i2c_dev->dma = stm32_i2c_dma_request(i2c_dev->dev, phy_addr,. k( n& L! o0 ?5 q3 w# o; j9 J
- 2247 STM32F7_I2C_TXDR,8 ~9 m* Z- {9 r, j" k
- 2248 STM32F7_I2C_RXDR);5 z# r: n X* E4 n+ E% ~; A' t# `
- 2249 if (PTR_ERR(i2c_dev->dma) == -ENODEV)
. L# {( D% y+ v$ g& K" E - 2250 i2c_dev->dma = NULL;7 z5 I( Z6 k" W D' Y
- 2251 else if (IS_ERR(i2c_dev->dma)) {
1 X' G. U+ N0 b+ ?% S7 _ - 2252 ret = PTR_ERR(i2c_dev->dma);- T/ l- N& e8 e+ P! Y
- 2253 goto fmp_clear;" [0 W* K# }- U5 u% D8 s; X$ G* O
- 2254 }4 `4 `1 r3 I& Z8 K$ t0 \5 R2 \2 w
- ......, O4 m0 h( M4 b5 x b, F
- 2276 stm32f7_i2c_hw_config(i2c_dev);+ V8 G+ J7 A9 W2 y, A+ c5 Q
- 2277
, }- m8 i& l4 A5 Z9 F - 2278 ret = i2c_add_adapter(adap);
1 g. M$ s3 S; R, H - 2279 if (ret)
, b: X3 R& q$ z0 ~" t - 2280 goto pm_disable;6 _) @) w; V) z; \' w
- ......
$ |) M' S) M0 p1 @ p# b% }# y - 2307 return 0;
5 m8 ^+ J3 j1 U* {: k) R1 Y8 R/ O7 ` - ......
. [" a. \ N. V/ a/ u4 a - 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
- 示例代码40.3.4 stm32f7_i2c_algo结构体( u* d e) R# h% o4 J r, H3 {9 q8 ?
- 2098 static const struct i2c_algorithm stm32f7_i2c_algo = {9 c7 j0 r0 E0 Y' k( f0 I
- 2099 .master_xfer = stm32f7_i2c_xfer,
; i: ]; G7 d g* l7 K - 2100 .smbus_xfer = stm32f7_i2c_smbus_xfer,
- k n1 K& F# {+ Q& V0 R% A - 2101 .functionality = stm32f7_i2c_func,9 a' l$ |) {% v4 Z9 J( {, g; C
- 2102 .reg_slave = stm32f7_i2c_reg_slave, R* }; V0 Q; J: ^* `: i
- 2103 .unreg_slave = stm32f7_i2c_unreg_slave,: F9 J3 d3 t4 d
- 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- 示例代码40.3.5stm32f7_i2c_func函数
: `' ?% l8 F' T* _, `3 Q - 2088 static u32 stm32f7_i2c_func(struct i2c_adapter *adap)
4 }7 y7 M8 F" E - 2089 {; l+ u$ e; T! x
- 2090 return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR | I2C_FUNC_SLAVE |
( f# r! t7 ?. }* D* F$ M+ }# b - 2091 I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
2 g! B9 _/ s5 G& B - ......
8 Y5 u+ J) {' T8 W. W/ g' Y1 U - 2094 I2C_FUNC_SMBUS_PROC_CALL | I2C_FUNC_SMBUS_PEC |
0 K l/ A. I4 ]- u; w5 ?* S C z - 2095 I2C_FUNC_SMBUS_I2C_BLOCK | I2C_FUNC_SMBUS_HOST_NOTIFY;
1 X/ r% v% e$ c2 T8 y) {) E - 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- 示例代码40.3.6stm32f7_i2c_xfer函数
7 |% O) \3 }6 ^2 [ - 1657 static int stm32f7_i2c_xfer(struct i2c_adapter *i2c_adap,
# E" s# D1 Y( S - 1658 struct i2c_msg msgs[], int num)
# f, m+ K! j1 {( \/ o( U: } - 1659 {- _0 @; I" S4 I9 ?1 U3 l
- 1660 struct stm32f7_i2c_dev *i2c_dev = i2c_get_adapdata(i2c_adap);8 w$ ?, f' a" j" i; @8 N! G
- 1661 struct stm32f7_i2c_msg *f7_msg = &i2c_dev->f7_msg;
8 ?& y) I0 `' {4 e* n/ r2 G) K# C: E - 1662 struct stm32_i2c_dma *dma = i2c_dev->dma; ? u. }0 x( y7 Z
- 1663 unsigned long time_left;
2 t& d; G; {( e5 E6 j! @" W" j5 c - 1664 int ret;
/ K# U, w& X o" y0 |! i+ l7 H+ }7 } - 16651 Z% N0 [! f2 W% w/ R7 J' \) d
- 1666 i2c_dev->msg = msgs;
/ P2 A% z* W$ F3 Z. W - 1667 i2c_dev->msg_num = num; N* n" m, @+ n9 D" S
- 1668 i2c_dev->msg_id = 0;
, H) H' Z- ?* D# Q - 1669 f7_msg->smbus = false;. c: {. H( ?' M4 R1 H8 N! A
- 1670/ }% A) A) S: M I8 k
- 1671 ret = pm_runtime_get_sync(i2c_dev->dev);3 V/ M- T! Z, K& A
- 1672 if (ret < 0)
& w0 n* |* E9 n - 1673 return ret;" X; S8 L& o6 S; l/ k& X
- 1674$ G7 Y; H8 H0 |
- 1675 ret = stm32f7_i2c_wait_free_bus(i2c_dev);
: V) N3 w) N( |1 s% {" E0 J - 1676 if (ret)$ d4 a. e5 N- x r
- 1677 goto pm_free;3 t2 K7 M) q6 Q0 _& I& o
- 1678- i. r d( e9 l/ Y
- 1679 stm32f7_i2c_xfer_msg(i2c_dev, msgs);0 V3 f' P5 P" v. b
- 16801 b$ R; m0 \* c6 B* w$ r+ n; o+ ]. Y) T% s
- 1681 time_left = wait_for_completion_timeout(&i2c_dev->complete,
' S1 i y& ^5 p! Z0 ]/ D: ] - 1682 i2c_dev->adap.timeout);
$ y9 o) T! p) Z% H; a - 1683 ret = f7_msg->result;
! ~. b3 ^" g) u1 E - 16843 r7 D# d; l4 q
- 1685 if (!time_left) {
5 Y4 [5 g3 `- O - 1686 dev_dbg(i2c_dev->dev, "Access to slave 0x%x timed out\n",( R, O, H7 T) _0 s' D
- 1687 i2c_dev->msg->addr);
7 r$ O, u' Z2 ~ - 1688 if (i2c_dev->use_dma)% r. a0 A$ k/ P- b0 Y: n
- 1689 dmaengine_terminate_all(dma->chan_using);
- e2 l1 v% h- [: j - 1690 ret = -ETIMEDOUT;9 b" ^$ l" @: Y5 v) O8 @3 W& Y
- 1691 }3 ]5 ^9 }5 t6 X% [+ N. H. \; x. H
- 1692
4 @( P U5 {) L - 1693 pm_free:, {" z+ r% Z8 l6 N% W
- 1694 pm_runtime_mark_last_busy(i2c_dev->dev);
% z9 @; \0 t D - 1695 pm_runtime_put_autosuspend(i2c_dev->dev);: r: V D3 X) R5 w8 p+ ^2 {1 Y
- 1696
8 U1 b) o h( ] V' \ - 1697 return (ret < 0) ? ret : num;9 I# i, n# c' @+ y6 w' Z
- 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- 示例代码40.4.1.1 i2c_board_info结构体 d& j1 C; T# ^4 p4 E" x+ @& n
- 406 struct i2c_board_info {# s; T3 `+ m- i' m* q* S7 F
- 407 char type[I2C_NAME_SIZE];( r! n2 {9 s# J9 z. }. }' }6 i- X
- 408 unsigned short flags; S0 F9 b) B$ I1 b8 g2 ^
- 409 unsigned short addr;& k% f! R: \ g7 g
- 410 const char *dev_name;0 m8 S, J2 u* c0 ~2 M1 d0 Q& ~
- 411 void *platform_data;" U7 w! g. ?! Q3 {' ?7 R; R$ T2 t
- 412 struct device_node *of_node;! F" Z: E# O" {! o1 P
- 413 struct fwnode_handle *fwnode;
0 f0 N: x- O/ x, A0 n6 z - 414 const struct property_entry *properties;% d, d7 K- y/ o+ z
- 415 const struct resource *resources;
, J5 [& R3 k7 W% O$ X - 416 unsigned int num_resources;
1 f0 F' d; O- d& d! S - 417 int irq;3 T1 w v8 r# H
- 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
- 示例代码40.4.1.2 s35390a的I2C设备信息( ^, l* M! k o1 v
- 246 static struct i2c_board_info armadillo5x0_i2c_rtc = {2 P5 _! u% C6 U- T* e
- 247 I2C_BOARD_INFO("s35390a", 0x30),! [- ?) K) l {/ f# Q
- 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
- 示例代码40.4.1.3 I2C_BOARD_INFO宏
' H& Q' G2 s7 O! a7 b# I8 J7 a - 433 #define I2C_BOARD_INFO(dev_type, dev_addr) \
9 Y1 ]6 v! _) ]) F7 T) k$ x - 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
- 示例代码40.4.1.4 i2c从设备节点示例
& `- ^/ E2 ^( n+ j. x D" G+ b - 1 &i2c5 {) v2 ~/ q1 a+ y/ G" D
- 2 pinctrl-names = "default", "sleep";
4 T) I+ @$ l+ ] - 3 pinctrl-0 = <&i2c5_pins_a>;0 G0 S2 U5 E/ c; ]1 Y% k, Q' G% M
- 4 pinctrl-1 = <&i2c5_pins_sleep_a>;) d& t) M0 {' [- Q
- 5 status = "okay";
. x" R0 I% ]) ~! l - 6
( O7 x1 n3 e7 W$ \$ W& b6 O - 7 ap3216c@1e {
6 g/ d( ?( w! _* P - 8 compatible = " alientek,ap3216c";1 x* k8 [( g6 z7 A: w. a
- 9 reg = <0x1e>;2 `7 G) c: Q1 C# u
- 10 };
; i: t' Q4 H1 Z. p5 S - 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- 示例代码40.4.2.1 i2c_msg结构体1 F7 `: Y9 B0 T
- 69 struct i2c_msg {
. M, ^ n& w$ `. A4 o - 70 __u16 addr; /* 从机地址 */6 c& a, R5 l$ G: [
- 71 __u16 flags; /* 标志 */6 ]; T( ~! z8 Z4 M7 H: P5 J! Y
- 72 #define I2C_M_TEN 0x0010
# S- k( E% Q, e. m - 73 #define I2C_M_RD 0x0001
; S. p5 O/ s. V: m$ I6 \+ d; u, H - 74 #define I2C_M_STOP 0x8000 + T6 c& R' g1 Y' h# ]2 }+ k n
- 75 #define I2C_M_NOSTART 0x4000 0 Z1 M! [* |: u% N
- 76 #define I2C_M_REV_DIR_ADDR 0x2000
, f, h. Y$ C# K0 ~" F/ t. [. R - 77 #define I2C_M_IGNORE_NAK 0x1000 * u4 N$ a+ q1 [& |$ R
- 78 #define I2C_M_NO_RD_ACK 0x0800
# t4 L3 i& [. J N3 I, X. B/ ? - 79 #define I2C_M_RECV_LEN 0x0400
* [) E+ `0 u. |6 i4 S - 80 __u16 len; /* 消息(本msg)长度 */9 z3 } v. ~1 s% s) {) ~' @: r' ]
- 81 __u8 *buf; /* 消息数据 */
6 f8 m- J% | ?: \5 e! } - 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 @0 d$ D) k/ X8 o5 i# }" v
- 2 struct xxx_dev {
& o ?/ ], s4 J( i - 3 ......
; v4 E; q e# m( f, X1 [* s - 4 void *private_data; /* 私有数据,一般会设置为i2c_client */
% f" k0 _; z3 _* I6 [/ l% u - 5 };! t: d2 c" ^/ n6 R: Z0 W8 f
- 6
0 H6 b& w' W3 P" y) _ - 7 /*
% F' H" J3 B, [# s2 ~ - 8 * @description : 读取I2C设备多个寄存器数据. b' a( t E" ]9 P, k4 \$ d
- 9 * @param – dev : I2C设备- h: i# t* t: p9 v9 g# G
- 10 * @param – reg : 要读取的寄存器首地址' m0 `$ ~# J# J/ r! p- z+ [$ {5 n
- 11 * @param – val : 读取到的数据, R0 Z4 d8 \' Z- r
- 12 * @param – len : 要读取的数据长度. R2 n) i( B1 J
- 13 * @return : 操作结果
* l2 y0 e' P2 p$ Z0 B0 t. V" {( z- Z7 O - 14 */
* u$ T9 F! Y; q" L9 l% |/ ]1 ] - 15 static int xxx_read_regs(struct xxx_dev *dev, u8 reg, void *val, ( z [. L- L7 P# h* f, }+ b4 g1 F
- int len)
1 V. e8 j! y$ b/ h - 16 {
' t0 E4 S( z4 Q4 q! I - 17 int ret;/ `% X/ `- y; P' |* g$ U& J- f; Y
- 18 struct i2c_msg msg[2];4 Z, y1 F) Y' D2 m8 Y1 ]+ P6 s
- 19 struct i2c_client *client = (struct i2c_client *)' a4 ~& Z: A, J4 m" ~' f
- dev->private_data;& W* a2 L# ^$ S( c, B
- 20 9 z$ a, J! k+ X8 ?. D; _
- 21 /* msg[0],第一条写消息,发送要读取的寄存器首地址 */* \+ P* u, ]) e9 i5 n& z1 j
- 22 msg[0].addr = client->addr; /* I2C器件地址 */
0 l; ~4 B/ e. P7 H - 23 msg[0].flags = 0; /* 标记为发送数据 */5 C9 e' t7 }* Q
- 24 msg[0].buf = ® /* 读取的首地址 */* z& p x I# P: ]3 ^( G
- 25 msg[0].len = 1; /* reg长度 */
3 v+ f! p/ x$ s - 26
# r; P& Z2 y: Q+ }% M# l: V - 27 /* msg[1],第二条读消息,读取寄存器数据 */
0 Z& n2 ~- \2 w7 |/ O - 28 msg[1].addr = client->addr; /* I2C器件地址 */9 X* b+ H, N! l' f/ q$ t+ U; ?
- 29 msg[1].flags = I2C_M_RD; /* 标记为读取数据 */
) ^" A) v- D) J' c - 30 msg[1].buf = val; /* 读取数据缓冲区 */
% C) P2 A1 w8 e* L" q( l n - 31 msg[1].len = len; /* 要读取的数据长度 */
) s( l1 e) h' P# k; B- \ - 32 2 _2 z1 ?. u1 K. b+ A, P$ \4 b2 V$ G
- 33 ret = i2c_transfer(client->adapter, msg, 2);7 w0 U, k, `! ]# J- n
- 34 if(ret == 2) {2 o2 @( a% J0 l& i3 m
- 35 ret = 0;. I( p; Y% P" `! O- ~3 h
- 36 } else {
! O$ o& J1 t: Q, B) l" V: F2 D - 37 ret = -EREMOTEIO;& G$ X0 v: a7 \) _0 J! J
- 38 }- v3 D; e, ?7 |5 M8 E
- 39 return ret;
+ {, p' I4 a+ F# [ - 40 }
. m, G% O/ H4 o6 z2 x: H- Z+ R - 41
6 h* l3 D4 ^* H( J t( s! Z - 42 /*) T! X$ j" o: @: E
- 43 * @description : 向I2C设备多个寄存器写入数据% U/ [0 s( X* I7 n5 l& E! C/ |+ m
- 44 * @param – dev : 要写入的设备结构体7 A7 A& `3 O2 Z4 a n) x
- 45 * @param – reg : 要写入的寄存器首地址
; L8 e' w% A( ?3 w. q( ?9 |# d* _ - 46 * @param – val : 要写入的数据缓冲区
, v# l! ?, g j; x) }7 f0 P8 ^ - 47 * @param – len : 要写入的数据长度, o# G' A( T/ @+ W
- 48 * @return : 操作结果
f2 ~! H9 G* a$ J2 k; d+ D - 49 */
5 H3 H2 ^+ G, O: C" q# F - 50 static s32 xxx_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf, " v# _# ]4 N- T6 ?: v# d
- u8 len); p; M) r. a. }- G U7 X$ E
- 51 {% U0 {! a! u& _7 D# J
- 52 u8 b[256];
% j7 T& |4 S& d( r+ a. d - 53 struct i2c_msg msg;" N8 U1 U) T2 t
- 54 struct i2c_client *client = (struct i2c_client *)
/ b$ w- F& B' T' I1 e6 ? - dev->private_data;
. W9 m# u/ V* h+ t1 {! \& p - 55
( a1 C# L4 E: Y: z. ?9 y - 56 b[0] = reg; /* 寄存器首地址 */8 ]6 F/ z! O5 g) Z0 p
- 57 memcpy(&b[1],buf,len); /* 将要发送的数据拷贝到数组b里面 */9 L1 s& M {2 W) _/ A& n
- 58
1 l- u- ^% i z" }. f - 59 msg.addr = client->addr; /* I2C器件地址 */
8 M2 m: J/ O, O u- V+ T" [) d - 60 msg.flags = 0; /* 标记为写数据 */8 m+ ?) N' }8 ?5 ]
- 61 - \+ r2 ~ V. v. q" L4 U# f
- 62 msg.buf = b; /* 要发送的数据缓冲区 */7 x" d% q' p. I
- 63 msg.len = len + 1; /* 要发送的数据长度 */+ _& c5 P% r0 H8 M8 f& C
- 64
. B' @, ]* H9 P" Q I+ q4 ?- ~ - 65 return i2c_transfer(client->adapter, &msg, 1);" e4 S% S; \7 [! y. o/ G4 q: P B
- 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
~- {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- 示例代码40.6.1.1 I2C5的pinmux配置
# z9 n$ W- |( \* N. X& t: q$ M - 1 i2c5_pins_a: i2c5-0 {7 v4 K& w4 r3 U9 U* |- i D
- 2 pins {
8 D( l! Q; }/ B1 Y2 A - 3 pinmux = <STM32_PINMUX('A', 11, AF4)>, /* I2C5_SCL */. `; `: K( I" B6 a. ^5 _' `
- 4 <STM32_PINMUX('A', 12, AF4)>; /* I2C5_SDA */
! U+ y" |4 T: G* e - 5 bias-disable;
9 x2 h+ J1 ?& w. d: J - 6 drive-open-drain;
$ o& }; e9 B* J/ y w - 7 slew-rate = <0>;# ` M1 ]; K0 Q
- 8 };9 z N+ ?8 b0 P6 c2 V1 c- [6 ^
- 9 };
" Y" A! _+ e' N, p, A. p. e, t( X1 N% ? - 10
: |. n' V, a+ @# r+ P: S: y A - 11 i2c5_pins_sleep_a: i2c5-1 {; W, |2 w* O( [6 c
- 12 pins {
1 z V3 c2 j, V; \% P. }; Q - 13 pinmux = <STM32_PINMUX('A', 11, ANALOG)>, /* I2C5_SCL */1 _' s4 z! H* a& U7 H; N. A
- 14 <STM32_PINMUX('A', 12, ANALOG)>; /* I2C5_SDA */# f8 i* V0 n5 k0 c7 r
- 15
% |# ^* ~6 a9 A0 o - 16 };; o/ [3 U( q; g J" o9 D
- 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, `- 示例代码40.6.1.2 向i2c5追加ap3216c子节点
' t0 o0 W6 ]9 M, e3 e - 1 &i2c5 {
7 M& S- A6 @3 z0 ~ - 2 pinctrl-names = "default", "sleep";
3 L7 \; c/ P& ]$ @) _ - 3 pinctrl-0 = <&i2c5_pins_a>;% ]* V7 z( i: h# O
- 4 pinctrl-1 = <&i2c5_pins_sleep_a>;9 @- U) p) h4 U8 w2 Z) c: U/ D6 Y
- 5 status = "okay";' O a* J# M$ g. m% j; U
- 6
0 F6 M: ~2 s# g) I q d - 7 ap3216c@1e {4 i' s r5 a. F$ X
- 8 compatible = "alientek,ap3216c";
4 x1 _0 B& n% D } y6 M0 n) g& D - 9 reg = <0x1e>;
5 T# F$ L" d! w1 ~7 f2 N - 10 };! Q" Y, N' b2 i* ~5 y
- 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
: 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
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- 示例代码40.6.2.1 ap3216creg.h文件代码段6 o: h0 @1 {2 G$ e6 L8 \
- 1 #ifndef AP3216C_H
. O- N$ [3 Q0 b+ H4 l, n* A1 H: ` - 2 #define AP3216C_H4 i8 j$ {2 B+ y! F: p: v
- 3 /***************************************************************% P1 R6 y2 w" w3 m5 V
- 4 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
/ U3 L7 K4 ]1 s- h% _# Y a& o - 5 文件名 : ap3216creg.h8 r6 }* d& o/ D; ]6 T& r3 g# V
- 6 作者 : 正点原子Linux团队
$ Q+ G5 _8 p0 ?/ t$ a$ L& x - 7 版本 : V1.0
# C0 M" Q' |! v% d8 i& H - 8 描述 : AP3216C寄存器地址描述头文件2 ?$ f n6 ]8 q/ d! {7 ^4 ^5 }
- 9 其他 : 无
" e3 V0 c; r4 c& l$ c. G0 ~5 O - 10 论坛 : <a href="http://www.openedv.com" target="_blank">www.openedv.com</a># i5 i; A! h) s4 ?
- 11 日志 : 初版V1.0 2021/03/19 正点原子Linux团队创建
& h/ n' F2 A! o# A) b - 12 ***************************************************************/ Q$ S) s D8 {4 ^, A0 q
- 139 i* b7 ~# |8 c" y) C7 C
- 14 #define AP3216C_ADDR 0X1E /* AP3216C器件地址 */4 L' i" |: N" |4 U( V3 i: S
- 15
2 X9 y; N5 u+ h" }2 G7 |2 b - 16 /* AP3316C寄存器 */+ S \# S& L& a( ]; f
- 17 #define AP3216C_SYSTEMCONG 0x00 /* 配置寄存器 */# {$ j) g, n! `9 S( J4 ^/ q$ }
- 18 #define AP3216C_INTSTATUS 0X01 /* 中断状态寄存器 */+ Q8 _. N: r3 i2 Q2 v! I4 c1 q
- 19 #define AP3216C_INTCLEAR 0X02 /* 中断清除寄存器 */' K) M) m" o3 f- O& V
- 20 #define AP3216C_IRDATALOW 0x0A /* IR数据低字节 */4 t/ E: I' l; z# b9 j6 V5 B
- 21 #define AP3216C_IRDATAHIGH 0x0B /* IR数据高字节 */* l: ?9 N6 L, A% I; g' j
- 22 #define AP3216C_ALSDATALOW 0x0C /* ALS数据低字节 */
( `8 Q) ^( ?/ S& O+ ]& N. h$ l$ G - 23 #define AP3216C_ALSDATAHIGH 0X0D /* ALS数据高字节 */
/ l; C, G' R- q2 e - 24 #define AP3216C_PSDATALOW 0X0E /* PS数据低字节 */1 H/ g j3 ^5 J# W8 o# i0 B
- 25 #define AP3216C_PSDATAHIGH 0X0F /* PS数据高字节 */
* I2 ]$ C# n" j; H( ~2 y - 26# A1 D2 ^, n- n8 Z7 i# R
- 27 #endif
复制代码
( f( y. H. a* s. _ap3216creg.h没什么好讲的,就是一些寄存器宏定义。然后在ap3216c.c输入如下内容:! O1 W" K- s4 X, Q: @! |
% u5 {) s' J$ T5 ?! A: o- 示例代码40.6.2.2 ap3216.c文件代码段( P5 r1 N; H+ B! Y* n
- 1 /***************************************************************; a( `/ A; d' G/ @6 k l
- 2 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.# C- }4 F1 {: H: K3 |
- 3 文件名 : ap3216c.c
+ ?. l: l& N$ v4 M8 \: ] - 4 作者 : 正点原子Linux团队 m! P1 f0 |7 K. B b8 |$ K
- 5 版本 : V1.0
8 c1 _ j& W( @, v) k% l - 6 描述 : AP3216C驱动程序
) L% ^+ M( ^* u% [6 t3 U6 S - 7 其他 : 无
/ o( L* L8 c0 O0 [% I - 8 论坛 : <a href="http://www.openedv.com" target="_blank">www.openedv.com</a>9 G" h' d r& h8 w! M5 b( h
- 9 日志 : 初版V1.0 2021/03/19 正点原子Linux团队创建
. C4 y/ K2 Q, Y' E - 10 ***************************************************************/
2 U! j6 w4 n6 {; K# `+ H; U - 11 #include <linux/types.h>
. V& H" x( U+ E! G+ S. g4 B( g - 12 #include <linux/kernel.h># c" b/ ] s- {
- 13 #include <linux/delay.h>1 q. i. a) Y k2 ]" y: w
- 14 #include <linux/ide.h>
# ^' Y4 t, o% k5 q: a7 E - 15 #include <linux/init.h>
" ^" L0 u0 {& v# }6 u$ s$ g2 @ - 16 #include <linux/module.h>
. R7 Q& ]' M! C7 o0 @, k - 17 #include <linux/errno.h>
" ^; L9 m7 |. W9 x1 G/ s - 18 #include <linux/gpio.h>9 Q ?9 U* \; ~ d5 z& a8 }
- 19 #include <linux/cdev.h>
}0 V6 I. Z( C- s0 C! v - 20 #include <linux/device.h>- K/ n' F9 e* [: Q5 b5 w8 u2 t* x
- 21 #include <linux/of_gpio.h>+ P; d1 S) |( s! z- e
- 22 #include <linux/semaphore.h>7 A# T6 I4 i3 ?% r- Z7 t2 V
- 23 #include <linux/timer.h>& W) {4 b6 E5 O% _
- 24 #include <linux/i2c.h> a1 x* L8 S" H" J' u0 q
- 25 #include <asm/mach/map.h>1 l) S2 t8 f# y- n6 O
- 26 #include <asm/uaccess.h>1 D3 u0 y, g: E* H+ @1 Q
- 27 #include <asm/io.h>
/ k* V; M9 x6 V - 28 #include "ap3216creg.h"( S( U+ _/ Q5 H9 H' ~3 m8 p0 m/ v
- 29
# ^& l0 L n l - 30 #define AP3216C_CNT 1% ^* ?" P0 @5 g
- 31 #define AP3216C_NAME "ap3216c", w3 D8 I" N1 w
- 32 8 ~2 m0 a/ L& O# w* y8 H
- 33 struct ap3216c_dev {* B1 F8 y! |7 c6 X7 L* P5 t
- 34 struct i2c_client *client; /* i2c 设备 */
; j$ D# u: C/ r8 }; X6 l# N - 35 dev_t devid; /* 设备号 */
8 p4 {( ?9 Q0 G; J2 | - 36 struct cdev cdev; /* cdev */
! g7 d. |3 f; {. _9 M) G - 37 struct class *class; /* 类 */
5 ]5 h1 F2 P, ~) P - 38 struct device *device; /* 设备 */
3 ]0 { V$ r$ M" g - 39 struct device_node *nd; /* 设备节点 *// b: i4 q: c# N5 B7 E
- 40 unsigned short ir, als, ps; /* 三个光传感器数据 */
Q3 x' A% |% |$ R$ y% n9 j$ Z: i - 41 };; X. Q8 o6 G4 Z% P: M% E& E) Y
- 42 4 S8 q( S' ~* z+ m6 j* h2 V# ?/ r" w# {! p
- 43 /*
9 x, ]- y9 g+ k7 p - 44 * @description : 从ap3216c读取多个寄存器数据- u) L' Y% Y7 S; r* h3 j F
- 45 * @param – dev : ap3216c设备
4 z( T4 h. {& \- I8 i" i" X( X% i - 46 * @param – reg : 要读取的寄存器首地址& |( {2 T! A5 i$ F; T$ z
- 47 * @param – val : 读取到的数据* J9 V: B3 g8 _( P! A
- 48 * @param – len : 要读取的数据长度) Y: g& H( P z% D
- 49 * @return : 操作结果 D0 z, C/ o0 _9 L# t1 H
- 50 */
& @6 C/ l7 e! ^* i; w1 A) u- j - 51 static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, , S, { h" Z( \$ w) b# ^; P( s/ S7 [7 d
- void *val, int len)+ ?3 j; h! u. c! E
- 52 {' Y# u" q! N# Z' D# r2 z( [
- 53 int ret;
9 v9 P1 P. X9 Z3 b2 B - 54 struct i2c_msg msg[2];
! u( S7 j* S' p0 K - 55 struct i2c_client *client = (struct i2c_client *)dev->client;2 B! @: Q. R: L8 D8 r4 }
- 56 * A V% l& {! h5 f
- 57 /* msg[0]为发送要读取的首地址 */4 J3 w2 `1 m' Z
- 58 msg[0].addr = client->addr; /* ap3216c地址 */
6 g, S9 V2 ?, k, q$ P - 59 msg[0].flags = 0; /* 标记为发送数据 */
2 Q( Q: Q1 N; O/ ^' g5 } - 60 msg[0].buf = ® /* 读取的首地址 */
8 z, H7 [6 A$ {( x - 61 msg[0].len = 1; /* reg长度 */
$ ]! }7 v0 o5 ]( X( [ - 62
$ N' A6 Q$ _1 T7 ~6 V$ F - 63 /* msg[1]读取数据 */) X) ^! G" l& u3 J0 g- [, _
- 64 msg[1].addr = client->addr; /* ap3216c地址 */9 m' e3 \ O3 m
- 65 msg[1].flags = I2C_M_RD; /* 标记为读取数据 */
6 ^- a; a. ^# u7 ?% K - 66 msg[1].buf = val; /* 读取数据缓冲区 */1 L) C# C) [/ b# `
- 67 msg[1].len = len; /* 要读取的数据长度 */4 U" s% ?; z3 {; r
- 68
! w( U; \8 c/ a3 P ?, j2 u - 69 ret = i2c_transfer(client->adapter, msg, 2);
$ v! f0 O. @( |$ N5 a+ d - 70 if(ret == 2) {% B' H q* ~7 j4 [# S1 R/ n
- 71 ret = 0;
4 C, R; u7 c' k& y; {- S - 72 } else {! B6 R, p" B7 }) e5 x6 n. W% s
- 73 printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
; e/ b$ g. j2 S1 b* Z% r/ @( D - 74 ret = -EREMOTEIO;( L$ K- W' _ g2 w6 B
- 75 }+ h* V) @: Y+ X6 R0 y0 T
- 76 return ret;3 s, |6 z: J7 `5 w ^
- 77 }: b& M5 Q; D) Z! e
- 78 $ S1 |" V* j# H; X% a
- 79 /*
9 z; y, a, S4 V, s - 80 * @description: 向ap3216c多个寄存器写入数据
& R4 `1 ~% w: k# ^. o( U0 p - 81 * @param - dev: ap3216c设备 ^- `* B O6 A1 \# C# A, D
- 82 * @param - reg: 要写入的寄存器首地址. {/ \. d( ]0 ]4 ^4 g
- 83 * @param - val: 要写入的数据缓冲区$ Y8 q: o1 z9 G! f6 I5 N. M0 K
- 84 * @param - len: 要写入的数据长度: Q9 A6 j1 m6 @4 \$ m
- 85 * @return : 操作结果# F$ a+ C, A+ I- F) J
- 86 */
' w0 a( t6 w' W# z9 Z0 S - 87 static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg,
* ^- \. B" W4 H/ i. c, d. q* _% A - u8 *buf, u8 len)( T: S! D) w7 S3 B- ~3 h7 W; H' u
- 88 {
3 W# @' c* Q$ u, @- S( P2 J - 89 u8 b[256];' Z: S& C$ s1 L% L3 s& U* {
- 90 struct i2c_msg msg;
/ q7 ?! t, r K1 x4 R6 l - 91 struct i2c_client *client = (struct i2c_client *)dev->client;* ^- V9 o& t! M% l
- 92
& v" }8 m. T" e: o# @' `2 o9 W( N8 i - 93 b[0] = reg; /* 寄存器首地址 */
% `$ L- k2 r! A# S - 94 memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组b里面 */! q/ w! m' q$ Y% e' U
- 95 8 B' L9 y) _; ?9 a, z( e9 a
- 96 msg.addr = client->addr; /* ap3216c地址 */
" a" t1 g/ I! C V/ s0 _" p - 97 msg.flags = 0; /* 标记为写数据 */' Y: ?4 j) X* ~( }
- 98 4 @# S8 F" ~6 ^) h' f2 n2 ?
- 99 msg.buf = b; /* 要写入的数据缓冲区 */
% A% f, H$ U' R( }" f - 100 msg.len = len + 1; /* 要写入的数据长度 */
3 M. R" Y5 p# k* O# S - 101
, V; {) g" c4 Z0 z; Z/ @ - 102 return i2c_transfer(client->adapter, &msg, 1);
1 u5 B* j# J3 b; B, i - 103 }
3 x% l c0 y# @# c4 O - 104
, b/ ]* O H6 G - 105 /*
! S" h" |3 [ Y# ~4 v - 106 * @description: 读取ap3216c指定寄存器值,读取一个寄存器, H0 k) N3 P2 f6 ]
- 107 * @param - dev: ap3216c设备
0 k+ g, q5 ^: ~$ ` - 108 * @param - reg: 要读取的寄存器
' }3 d3 ^+ _# M" T4 p# d. } - 109 * @return : 读取到的寄存器值
# D# [2 V+ w. M" y7 b - 110 */
' b5 p3 m. `" [: Z - 111 static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev,
B2 ~0 \2 g" g0 s- p1 \: X8 t - u8 reg)
! l4 B' a [9 g9 [, N5 n, s2 O3 O4 ~; ? - 112 {* L. p+ P% W. p
- 113 u8 data = 0;% w6 E2 ]) e* U% U+ o: Y, N) F' t5 p
- 114* [! s9 {2 ^9 h2 Q' P
- 115 ap3216c_read_regs(dev, reg, &data, 1);
+ l8 b: v! S; y) c2 Z. q - 116 return data;7 h, b, {3 q; X m, Q9 [
- 117 }
, y* B+ G5 [. U - 1187 S( k7 i2 N5 i t
- 119 /*0 U5 e9 Z3 E; W7 }8 ^
- 120 * @description: 向ap3216c指定寄存器写入指定的值,写一个寄存器
" h* j: x' S. z) ^ R" Y- s - 121 * @param - dev: ap3216c设备- C: b- H+ b7 h+ c
- 122 * @param - reg: 要写的寄存器
( |9 `$ _+ f, J - 123 * @param - data: 要写入的值9 n. C7 y4 r; R' D3 e
- 124 * @return : 无
( x2 l5 Y" U; s* n, c; M - 125 */
+ m z% u' ^; I" T9 X, t% a- _7 [ - 126 static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, # Q( R0 R% v" W1 x! L
- u8 data)
q, }% P+ [$ v - 127 {
+ m6 @# |9 V7 X$ C# G - 128 u8 buf = 0;
2 ^7 k$ S, f V0 ^ - 129 buf = data;
; |/ f/ C4 t2 J, ] - 130 ap3216c_write_regs(dev, reg, &buf, 1);+ G7 W- R6 }+ i" r
- 131 }
# Q) O: h$ L* i: u - 132# [: L% |7 X: R m
- 133 /*4 i/ D9 e7 k; m! H, y2 W! z+ O; c
- 134 * @description : 读取AP3216C的数据,包括ALS,PS和IR, 注意!如果同时
& I; A! E- A' s1 ~- z - 135 * :打开ALS,IR+PS两次数据读取的时间间隔要大于112.5ms& }, i" ?) f- ]1 Q; V) x. }
- 136 * @param – ir : ir数据
6 H) N* V0 o/ m& A; Y% Z& J - 137 * @param - ps : ps数据
; y. w" }* y; C! B, Y - 138 * @param - ps : als数据
7 r! y9 N! ^" u7 u* a) r. u- b3 x/ W) ]$ m - 139 * @return : 无。& c# w& {# o i# T( P/ g, ^8 S
- 140 */
( A4 Q, o" ]/ r - 141 void ap3216c_readdata(struct ap3216c_dev *dev)2 P/ b7 x' r( Z1 i0 |+ H5 p
- 142 {
) ~% k6 e1 |0 P1 |; A3 \" U# L3 b6 K" o - 143 unsigned char i =0;
5 `8 ?7 I2 D$ k - 144 unsigned char buf[6];: R: p7 o7 }6 I1 A9 z
- 145 % l; J& p' m4 h2 f, C6 r$ f4 ?
- 146 /* 循环读取所有传感器数据 */
/ t+ e8 r% R' c) I - 147 for(i = 0; i < 6; i++) {
: E% p6 K9 B# B" L - 148 buf<i> = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);
4 t6 r/ {( I" A( t* r6 i0 p - 149 }
7 | O9 O# o. H7 f - 1508 G. Q3 }1 T$ J! h, i
- 151 if(buf[0] & 0X80) /* IR_OF位为1,则数据无效 */
7 }; u9 U" q& z! E/ N8 g - 152 dev->ir = 0;
( t3 Q' t' Y7 ^+ | - 153 else /* 读取IR传感器的数据 *// i1 l8 m* e' K+ o* u' k, f6 L) r
- 154 dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);
, V4 S% A" d# J8 @ L% `8 X - 155 6 ^! ]- c, F; _; z5 Y: Z& f
- 156 dev->als = ((unsigned short)buf[3] << 8) | buf[2];
3 d9 e7 {! j: F - 157
- M( F0 Z; `* K. n, {) O - 158 if(buf[4] & 0x40) /* IR_OF位为1,则数据无效 */) {: G$ U* i* x8 @# e
- 159 dev->ps = 0;
# V6 @( k5 O3 @% m6 f - 160 else /* 读取PS传感器的数据 */8 }8 M+ M' z" ~4 y5 M3 Y& h/ F
- 161 dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
6 r' B5 G' p4 ~' O( {* t* @2 b- L - 162 }8 r+ ]. Q) y& @ H" O! G$ _) P
- 163. `( W; c9 [, `' M
- 164 /*6 X5 i3 O4 x: A0 P( U
- 165 * @description : 打开设备3 j% v7 Q, q7 d3 T! C
- 166 * @param – inode : 传递给驱动的inode
4 [% R' ^. X2 g1 C5 k7 T1 Z7 P6 n - 167 * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
# H5 Y% }( V& E - 168 * 一般在open的时候将private_data指向设备结构体。
4 w+ V* N$ u; T; M - 169 * @return : 0 成功;其他 失败
, ~, y- J+ U( s2 i; v4 W5 x+ A - 170 */$ V% h- s1 z e+ {
- 171 static int ap3216c_open(struct inode *inode, struct file *filp)
2 y# S$ A5 _* u: k" ^$ q - 172 {
3 Z; O$ O9 H) `7 q S5 C - 173 /* 从file结构体获取cdev指针,再根据cdev获取ap3216c_dev首地址 */
8 O4 \: B& p9 `1 H5 d/ [. f" L - 174 struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;
0 }" L) U$ U1 C; G - 175 struct ap3216c_dev *ap3216cdev = container_of(cdev,
7 n, Y) K/ @- I9 z- K# V' g - struct ap3216c_dev, cdev);
. G* M4 Z1 m& o. P4 H - 176
6 ]; S1 @3 ?7 D3 H3 P2 ?( n- Q; d6 I* q# D - 177 /* 初始化AP3216C */# z$ `! }0 [# y* o3 X0 @ ?
- 178 ap3216c_write_reg(ap3216cdev, AP3216C_SYSTEMCONG, 0x04);
2 L( _; }+ q2 T8 q6 N - 179 mdelay(50);
( u* @' I' o0 ^/ b - 180 ap3216c_write_reg(ap3216cdev, AP3216C_SYSTEMCONG, 0X03); 3 t; k! F6 Q5 q% q
- 181 return 0;
) e" I9 t4 ] S! { - 182 }
8 z& n9 x- l5 v( M" R* z4 _ - 183
/ ~# i! c- ]1 U( _7 }0 l0 G$ E - 184 /*
' z( [7 C8 [& q# P& ]9 q' R8 [- [/ E - 185 * @description : 从设备读取数据 6 T# A% S8 |/ T7 Y6 m, l* D
- 186 * @param - filp : 要打开的设备文件(文件描述符)
6 V0 w7 c* j2 A |3 `7 W7 S - 187 * @param - buf : 返回给用户空间的数据缓冲区
! d& D! U1 f! U" N0 ] - 188 * @param - cnt : 要读取的数据长度
- N+ ?4 V% q7 e& |/ A; e6 W - 189 * @param - offt : 相对于文件首地址的偏移9 n: V, Q: T3 S, {8 [; A
- 190 * @return : 读取的字节数,如果为负值,表示读取失败5 P! Q4 D8 }9 d) z& Q- K
- 191 */
- B) r' [8 C: t2 E1 A& { - 192 static ssize_t ap3216c_read(struct file *filp, char __user *buf, ! v3 K Q: r8 ~1 A% O# l
- size_t cnt, loff_t *off)$ P/ a$ \( o) ?6 ?' ^- a6 S- K
- 193 {* U; _8 s% @' R& G; F: q1 z1 j
- 194 short data[3];8 v' H2 `# z. _
- 195 long err = 0;
0 t" j1 w( x0 `" h - 196
) }. B! K+ D" J - 197 struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;* t, l% v! H* Z" A! `8 }" v' D) p
- 198 struct ap3216c_dev *dev = container_of(cdev, struct ap3216c_dev,
( W1 i5 A+ ~7 g' D, F - cdev);
& D2 m0 G) E0 `: S; e7 _7 l( E3 D4 ] - 199
; D4 e* P9 |+ b6 X - 200 ap3216c_readdata(dev);
# K5 w) g% o, T0 @. I - 201" `9 G0 J/ R/ S( p: v `
- 202 data[0] = dev->ir;
" U0 [# W/ W2 q. t+ R/ c# @* j- a - 203 data[1] = dev->als;3 { @' G+ x" A! Y; O$ a J
- 204 data[2] = dev->ps;
" V5 P* c) H# e ^1 f# _) I j) ? - 205 err = copy_to_user(buf, data, sizeof(data));
4 j5 S" Q: X- y ]. e2 c5 d - 206 return 0;1 J- x) t3 _+ k7 p! O, Y
- 207 }$ t, x( v1 k: m
- 2080 X5 i4 d# }# b9 u4 E* S7 Z5 y2 a' ]
- 209 /*
( @! e+ \9 y# b' T# Z - 210 * @description : 关闭/释放设备4 \5 @$ Q3 ]& b8 p$ ^) v
- 211 * @param - filp : 要关闭的设备文件(文件描述符)1 D, a0 k8 S* U4 `3 ~: X* c0 s2 w
- 212 * @return : 0 成功;其他 失败
# j5 ? I, h7 g" d* I# K0 [' n - 213 */
0 u0 {0 n# r, h/ x2 O, i6 \ - 214 static int ap3216c_release(struct inode *inode, struct file *filp)
: |/ n- q5 o& ]+ D [ - 215 {
# j) d( Z) ^0 M! b4 O" e - 216 return 0;
/ B( M3 |2 o6 A' p0 c2 u - 217 }
! J, R- m; ^8 l1 S2 @ - 218
$ M4 C: Y$ B# x+ B' ^! Y - 219 /* AP3216C操作函数 */
6 G' X" _: c3 H( x, J4 R' o - 220 static const struct file_operations ap3216c_ops = {2 A7 N- _3 F+ x( ?9 d; g
- 221 .owner = THIS_MODULE,1 t4 ^( N3 e1 _, g6 m4 p' E
- 222 .open = ap3216c_open,
- k& D" ^9 f2 G1 U - 223 .read = ap3216c_read,
! z! m5 h. h+ R' y J }5 C% v - 224 .release = ap3216c_release,
( q' G, x. P9 Q4 G" R+ F. Q) A - 225 };
$ R; ]3 ]' x F+ h6 Z0 y - 226
/ I, `0 R. N2 c1 r R' K - 227 /*; J- }" l2 E0 Y4 W5 P% H
- 228 * @description : i2c驱动的probe函数,当驱动与, ~1 z$ G8 T6 U6 p8 s
- 229 * 设备匹配以后此函数就会执行
0 h- K( `8 T7 j - 230 * @param – client : i2c设备
7 ~5 o1 W& `4 U; }7 X6 k - 231 * @param - id : i2c设备ID
1 H, a4 O0 S/ z k$ W. V - 232 * @return : 0,成功;其他负值,失败$ K( L$ o* o$ E- l3 g
- 233 */3 \4 [. Z9 t; B7 y/ d
- 234 static int ap3216c_probe(struct i2c_client *client, . I- b- l; A3 P! u: F7 e' H
- const struct i2c_device_id *id)3 C4 V g8 Y5 a" p3 B8 [
- 235 {8 I/ V- j! v3 e& S) e) Q" ~
- 236 int ret;0 B" l! `$ E7 N& |: [1 @1 p
- 237 struct ap3216c_dev *ap3216cdev;
0 M* p% M/ E2 w2 p( x4 ~ - 238
* U; C3 `) m& d7 U% A0 c - 239 B9 ?1 `7 V7 h3 M8 w
- 240 ap3216cdev = devm_kzalloc(&client->dev, sizeof(*ap3216cdev), : o: \5 m5 h1 `4 q* q J- e
- GFP_KERNEL);
7 ]! U+ x( s. e4 W4 E. g9 O: e+ p; l - 241 if(!ap3216cdev)% m/ U( S% B6 Z
- 242 return -ENOMEM;! d( g/ z% v' |& J" ~+ N/ R
- 243
. \% r1 [% r3 r7 i% X9 M; i - 244 /* 注册字符设备驱动 */" q" i5 z0 }! U6 Y
- 245 /* 1、创建设备号 */3 q2 B6 y1 d' d4 ?
- 246 ret = alloc_chrdev_region(&ap3216cdev->devid, 0, AP3216C_CNT,
# w! s3 l1 Z1 D' {+ V - AP3216C_NAME);. g& Z/ _4 r5 l3 \' ? @, E
- 247 if(ret < 0) {2 m' \1 M* o% t e! j
- 248 pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n",
! O$ \. P, M( U6 Z# ~6 o8 w - AP3216C_NAME, ret);
. C8 m2 y/ ] G - 249 return -ENOMEM;2 e6 x4 \& Z' [% X6 N7 G
- 250 }
0 {% d! P$ E* m1 g - 251( i, h# e8 Y$ S% y, ]
- 252 /* 2、初始化cdev */1 I" N# t5 Y I
- 253 ap3216cdev->cdev.owner = THIS_MODULE;5 m# e2 z, i, m; e* K i) V
- 254 cdev_init(&ap3216cdev->cdev, &ap3216c_ops);+ b$ n* ?6 ]! M+ [+ J+ @
- 255
/ Y6 F0 r: v: p! s( J: q - 256 /* 3、添加一个cdev */- f, }8 j3 [' m
- 257 ret = cdev_add(&ap3216cdev->cdev, ap3216cdev->devid, 5 b' c/ u* Z) R* d) Y
- AP3216C_CNT);
9 C' x* [1 b9 \$ y8 b) {! C0 W6 a - 258 if(ret < 0) {
+ y: }+ K T3 y) R$ w' { - 259 goto del_unregister;
, O# h, G" x) X& [ F3 o% Z$ }5 F - 260 }( \3 G8 G |: w+ l% j* `# V
- 261
- G' C- |! C9 E- x+ \. _ n - 262 /* 4、创建类 */# M9 Q. {1 { V
- 263 ap3216cdev->class = class_create(THIS_MODULE, AP3216C_NAME);
' T+ }) j- f. Q6 L t, j& U+ e - 264 if (IS_ERR(ap3216cdev->class)) {; N- e+ j9 @2 `4 _6 l+ v
- 265 goto del_cdev;
5 z0 _" l. @" d5 o+ |; e0 h# S - 266 }
H" J# m- {* u; s* s - 267% K2 w* s2 ~& i- p: x1 c- ^
- 268 /* 5、创建设备 */
8 a6 e: }, s# u5 h - 269 ap3216cdev->device = device_create(ap3216cdev->class, NULL,
$ i9 k6 n+ n M. M% W b- c - ap3216cdev->devid, NULL, AP3216C_NAME);
, o! l1 E0 v0 B: F$ v, ? - 270 if (IS_ERR(ap3216cdev->device)) {
+ r# N4 l- j6 Q9 k! w( S" ^7 s - 271 goto destroy_class;% q8 A8 a, o' Z/ {
- 272 }/ N9 |3 Y" D# J5 g2 x5 E+ S4 j! i
- 273 ap3216cdev->client = client;
9 q% Z. Z5 o& q* m - 274 /* 保存ap3216cdev结构体 */
% @+ ?% e- d1 X2 i: ?, w5 K- ` - 275 i2c_set_clientdata(client,ap3216cdev);
& p, e% x5 I9 e& X# X2 o+ X - 276% ^- B0 f( T7 Y4 Q5 z
- 277 return 0;
3 x& f; ?' ^: [; j - 278 destroy_class:6 C6 W6 K; \# E/ E- p* R# @" `& K
- 279 device_destroy(ap3216cdev->class, ap3216cdev->devid);
4 o$ w8 I9 j; p& l$ _1 }2 H - 280 del_cdev:3 N w9 D) s7 M/ V2 A w# h3 O( ~
- 281 cdev_del(&ap3216cdev->cdev);
4 Z) v3 C. h% G/ ^ - 282 del_unregister:
% c2 }+ V+ r5 M0 J) A, J- j& H - 283 unregister_chrdev_region(ap3216cdev->devid, AP3216C_CNT);$ f4 ~+ ?, R2 V
- 284 return -EIO;
1 j! R; a3 L) j$ D: m - 285 }
! p+ Y' W6 O( m7 z: P! } - 286- G6 G' T" X6 v* V# F3 |7 p9 R
- 287 /*9 g6 e. j' a0 r- u. k7 u' T, s+ P
- 288 * @description : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行' @, y* a& P Y
- 289 * @param - client : i2c设备
0 B% z( {' |: \# {" i# ~ - 290 * @return : 0,成功;其他负值,失败. s/ u: W# g: n9 y# M) o, Z6 q
- 291 */. o3 u$ u3 |& g) A# Z$ Z% h! n8 i
- 292 static int ap3216c_remove(struct i2c_client *client)
5 l5 ^8 @$ g Q4 i% p# C: M- {& ? - 293 {
5 a7 j! \2 G0 r) g5 E - 294 struct ap3216c_dev *ap3216cdev = i2c_get_clientdata(client);
9 I5 ]4 U) D& m1 p' K+ B - 295 /* 注销字符设备驱动 */
2 o' K* V1 a' N" }: m - 296 /* 1、删除cdev */
8 I! P9 S! v3 h! k0 Q - 297 cdev_del(&ap3216cdev->cdev);& b/ [: w9 J* o% D6 P; J/ v( D
- 298 /* 2、注销设备号 */
' S6 X5 V1 Y$ L5 w2 K - 299 unregister_chrdev_region(ap3216cdev->devid, AP3216C_CNT); 1 j: @& }6 ?; }9 l- Z
- 300 /* 3、注销设备 */
; v! x1 H0 m! Y, U" X2 C$ t+ } - 301 device_destroy(ap3216cdev->class, ap3216cdev->devid);
0 k, U1 C5 G9 t; s$ X' l - 302 /* 4、注销类 */. l, d) H: C( ?$ t/ {" N1 \4 Q* P2 u
- 303 class_destroy(ap3216cdev->class);
5 }. ~/ \; ?1 y% g) u" S - 304 return 0;9 R5 }% |. j/ i# o
- 305 }
" b0 I2 ]% k0 _$ K6 n - 306
8 ^% n7 v! A% g, C - 307 /* 传统匹配方式ID列表 */( ^- R1 o5 s0 R/ X( Z
- 308 static const struct i2c_device_id ap3216c_id[] = {
8 }4 K. W+ {9 |0 H( {/ r6 H - 309 {"alientek,ap3216c", 0}, ; ?) O/ c' H( @
- 310 {}) R! d b0 B2 u0 {8 L* e
- 311 };
- y: L! T/ `* y0 e' k1 V - 312 Q* D& G7 u. O5 \/ C) `
- 313 /* 设备树匹配列表 */- ?0 e: V0 L% F: p
- 314 static const struct of_device_id ap3216c_of_match[] = {
8 X: x: X. ^) E8 D; s3 D) ` - 315 { .compatible = "alientek,ap3216c" },# m; `. ]4 J, r) c. f
- 316 { /* Sentinel */ }) ], i; ~& P7 D
- 317 };3 I0 M! S' Z- \& i
- 318
3 E. m9 P {" H o! b' Y3 B - 319 /* i2c驱动结构体 */
7 p2 C% u7 ^7 O7 O- L1 p3 P$ b - 320 static struct i2c_driver ap3216c_driver = {8 r/ t0 a/ k U# K* W' q
- 321 .probe = ap3216c_probe,$ o8 [! E- Y) O- `8 B8 p5 \5 X
- 322 .remove = ap3216c_remove,
7 R# E3 ]" a3 D' ]. s. K7 B3 v - 323 .driver = {
- U4 a5 C0 |% E9 Q0 M' \8 }. u - 324 .owner = THIS_MODULE,6 U0 n: [+ I2 }8 L
- 325 .name = "ap3216c"," S! y U5 U2 n6 h' x' v/ b
- 326 .of_match_table = ap3216c_of_match,
6 ^( T: M& F- j% m& U/ w - 327 },
: d0 y1 X/ r7 R6 ^: F& f - 328 .id_table = ap3216c_id,. \0 _& G& }4 h- f" |7 G
- 329 };
" F8 Y7 k* ^8 I M - 330 J1 O9 c' ]3 b0 W
- 331 /*
0 `$ P! v! o) u3 |7 }: [) O - 332 * @description : 驱动入口函数
) v8 ^# {) z) _ - 333 * @param : 无$ r6 s Z" f. f: N6 u# d
- 334 * @return : 无0 l+ p9 D1 g% |- `
- 335 */
7 G, j2 F0 z( y& b3 n - 336 static int __init ap3216c_init(void)' }7 x. y. ~! Z: H/ b0 N, o4 G$ }, f
- 337 {
" E% E8 L- U/ a; z9 O, h - 338 int ret = 0;+ v7 H8 J; E+ d k
- 339
% R( M1 x% a, Y! M ^' }: E - 340 ret = i2c_add_driver(&ap3216c_driver);/ L1 r, w6 U) K" Z
- 341 return ret;
8 B" V8 R1 s6 w7 y e1 y - 342 }
4 ?$ Y# l" i9 q% ~6 y* P+ B% _$ y2 | - 343 |/ }: z) p2 R1 C, Q
- 344 /*. K. }; M) U* F" }
- 345 * @description : 驱动出口函数
3 t" v$ J7 Q) ]( t- S - 346 * @param : 无
/ {0 x9 f* X% Z4 H2 G$ {3 k9 Z - 347 * @return : 无* V% s4 ?3 A! N5 t: U9 Y
- 348 */- g2 f) ]( ?, Y" U6 M" q# y
- 349 static void __exit ap3216c_exit(void)
8 u( ~ v: Q0 a* E - 350 {1 @3 _0 ~( X2 \6 ]% c0 S8 {: \
- 351 i2c_del_driver(&ap3216c_driver);
, P; }0 @8 ~: m. b8 R* o: u - 352 }: W9 {% e& K$ f" e6 v O
- 353' K; H7 I( w$ _7 O4 y
- 354 /* module_i2c_driver(ap3216c_driver) */
& h: A4 |6 N0 Z0 V- U$ f7 } - 355
; b! x4 W* Y8 h. q( Y4 M - 356 module_init(ap3216c_init);
6 @) H* `4 `% O; n0 v0 f - 357 module_exit(ap3216c_exit);5 \) F! s6 ~2 {8 |& |& M
- 358 MODULE_LICENSE("GPL");- f( s$ R4 R5 ~& k4 ?' ~9 i& I& v
- 359 MODULE_AUTHOR("ALIENTEK");1 E' I- h0 a |. ?
- 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- 示例代码40.6.3.1 测试APP / r! l. u. ^1 O$ F+ S7 q+ P
- 12 #include "stdio.h"
- t- U: ^9 e; x$ m - 13 #include "unistd.h"
9 j3 I& v3 W. }4 o+ Q2 Q - 14 #include "sys/types.h"
\2 |9 U9 n0 k7 W - 15 #include "sys/stat.h"
- N, N5 n. Y/ ~$ s - 16 #include "sys/ioctl.h"
! n0 f9 b0 L: K/ {+ O3 F2 o$ N - 17 #include "fcntl.h" l$ j% R9 |5 ]' Y3 r5 U
- 18 #include "stdlib.h"
' \- l& g% W6 _' O- q - 19 #include "string.h"
( ~# @9 ]' }1 o- x+ T. I- K - 20 #include <poll.h>) o8 I2 G b# h+ [6 ]
- 21 #include <sys/select.h>
o2 P2 ]- o. y - 22 #include <sys/time.h>
0 l7 ^' e; Y R - 23 #include <signal.h>
9 e8 o) {6 [4 }# ?$ h6 ^ - 24 #include <fcntl.h>! R3 ~: n1 A3 e2 D/ n& `+ N7 G+ n4 X1 `5 G
- 25 /* O+ _ C% e6 V6 k
- 26 * @description : main主程序
) Y% X8 w/ y3 c. s - 27 * @param - argc : argv数组元素个数
- ?/ [ j f% m* ~1 J5 @ - 28 * @param - argv : 具体参数
8 A! h; v1 e; _4 F - 29 * @return : 0 成功;其他 失败
6 q2 q8 {3 {- } D7 J9 E - 30 */
5 X- e* _3 {9 l - 31 int main(int argc, char *argv[])! W% Q/ U2 |4 F2 r, K
- 32 {
" ^; G, h$ ^8 {% c - 33 int fd;" U, A% V0 i& Z' Y
- 34 char *filename;+ W, ^% t- B- ]) a5 c6 ]
- 35 unsigned short databuf[3];
; P2 Z) n! T6 h' D2 y2 k - 36 unsigned short ir, als, ps;
, O- n$ b+ E) ]8 V - 37 int ret = 0;. s5 ?+ Z" O U- R
- 38! `: L1 X& p7 \) a; z/ Q9 V
- 39 if (argc != 2) {) {# ]% F# ? X& P# c
- 40 printf("Error Usage!\r\n");1 W3 h) H" C. {6 b6 `$ B/ W$ ~, m
- 41 return -1;
* p. u# J' [+ i% Z% u+ M+ Z - 42 }
, I3 c9 P( T h* n, l - 432 @! p; a7 _$ v7 n" \% X7 s
- 44 filename = argv[1];
# _" v7 E, M# c" W# g H; f - 45 fd = open(filename, O_RDWR); n. c) E, |) R
- 46 if(fd < 0) {) a2 i( }! z) y0 J( f% y
- 47 printf("can't open file %s\r\n", filename);
! ^1 }0 [2 ~) h) F# _* |7 V& n6 g - 48 return -1;
# p b+ Q, F; r* R2 M - 49 }
% a% C5 e& O6 U- ^ - 50) G, K" B% B( B9 K) H; I& T7 Y
- 51 while (1) {. v* Y9 k9 a# m$ m: G
- 52 ret = read(fd, databuf, sizeof(databuf));
2 {) c' v2 R3 e8 s: x& C$ j/ g - 53 if(ret == 0) { /* 数据读取成功 */ }. P v9 _, a" |4 Q3 P
- 54 ir = databuf[0]; /* ir传感器数据 */' J# l7 o# G% j7 l7 ?1 e1 m
- 55 als = databuf[1]; /* als传感器数据 */ I! @6 V6 }9 P5 ~. n: ^/ K3 _
- 56 ps = databuf[2]; /* ps传感器数据 */
8 i' n# T& u' q - 57 printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);+ q& F) |8 q( V/ M8 p
- 58 }1 Z; Q; B1 K) C9 w" x" t4 N! ]
- 59 usleep(200000); /*100ms */, x' w' t* u8 a4 n8 a8 S
- 60 }" X: O+ j6 }! t2 a
- 61 close(fd); /* 关闭文件 */ 6 I4 Z: R2 t, e3 X
- 62 return 0;
+ R6 y& o# h( l A; I% B" C6 o' ` - 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- 示例代码40.7.1.1 Makefile文件
& {# {4 L0 a. j( G - 1 KERNELDIR := /home/zuozhongkai/linux/my_linux/linux-5.4.31
0 m" K8 ?* C* a" | - ...... % w2 }1 D* v1 m" ^7 i) S4 G; b
- 4 obj-m := ap3216c.o0 N3 Z2 }+ g2 A0 J3 g
- ......
% g2 g& w: \) z0 R4 U& { - 11 clean:
7 K* r/ W" w( ]9 f" J( h- T - 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
+ 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 _
|