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

STM32MP1嵌入式Linux驱动开发指南V1.7——SPI总线框架

[复制链接]
STMCU小助手 发布时间:2022-9-27 11:50
Linux SPI总线框架) [* U" g# B: B  W: R% U
到目前为止,我们已经给大家介绍了Linux下的platform总线框架、I2C总线框架,本章将向大家介绍Linux下的SPI总线框架。与I2C总线一样,SPI是物理总线,也是一种很常用的串行通信协议;本章我们就来学习如何在Linux下编写SPI总线接口的设备驱动。本章实验的最终目的就是驱动STM32MP1开发板上的ICM-20608这个SPI接口的六轴传感器,可以在应用程序中读取ICM-20608的原始传感器数据。
4 Y; g. c2 O- c9 U$ ~/ T. i
, r4 T7 r8 m; [. o  S6 b44.1 SPI & ICM-20608简介* c2 q# t' V/ t. E
44.1.1 SPI简介' [6 H7 v: `7 Y" f; T! C
上一章我们讲解了I2C,I2C是串行通信的一种,只需要两根线就可以完成主机和从机之间的通信,但是I2C的速度最高只能到400KHz,如果对于访问速度要求比价高的话I2C就不适合了。本章我们就来学习一下另外一个和I2C一样广泛使用的串行通信:SPI,SPI全称是Serial Perripheral Interface,也就是串行外围设备接口。SPI是Motorola公司推出的一种同步串行接口技术,是一种高速、全双工的同步通信总线,SPI时钟频率相比I2C要高很多,最高可以工作在上百MHz。SPI以主从方式工作,通常是有一个主设备和一个或多个从设备,一般SPI需要4根线,但是也可以使用三根线(单向传输),本章我们讲解标准的4线SPI,这四根线如下:
4 L# w: `; O* b" E- _& G9 `, v7 K8 H①、CS/SS,Slave Select/Chip Select,这个是片选信号线,用于选择需要进行通信的从设备。I2C主机是通过发送从机设备地址来选择需要进行通信的从机设备的,SPI主机不需要发送从机设备,直接将相应的从机设备片选信号拉低即可。
6 @; t) z4 s7 h2 n- Q/ s②、SCK,Serial Clock,串行时钟,和I2C的SCL一样,为SPI通信提供时钟。5 T( T/ H$ x; }: w/ N
③、MOSI/SDO,Master Out Slave In/Serial Data Output,简称主出从入信号线,这根数据线只能用于主机向从机发送数据,也就是主机输出,从机输入。
1 C+ ~. M, }- T% ^  N④、MISO/SDI,Master In Slave Out/Serial Data Input,简称主入从出信号线,这根数据线只能用户从机向主机发送数据,也就是主机输入,从机输出。
# W6 y% o8 e/ I! |& Z6 N% e0 \SPI通信都是由主机发起的,主机需要提供通信的时钟信号。主机通过SPI线连接多个从设备的结构如图44.1.1.1所示:
; ~8 ?5 T3 P3 s5 k( A1 J6 N: W& s8 r" ^* d7 Z
d049c296a5624ed2a16908b02c0bc5ea.png ; e# e/ W' L- q

) c5 F+ Y5 g$ h5 k图44.1.1.1 SPI设备连接图5 V) W5 m+ h7 [( S, T  \5 Y
SPI有四种工作模式,通过串行时钟极性(CPOL)和相位(CPHA)的搭配来得到四种工作模式:
( E$ m: E* R0 b' w9 ^①、CPOL=0,串行时钟空闲状态为低电平。
" ?- l% C4 Q1 t' c% |②、CPOL=1,串行时钟空闲状态为高电平,此时可以通过配置时钟相位(CPHA)来选择具体的传输协议。; p! B* d  w5 X) Z" z% h+ F1 n$ e( [0 Z
③、CPHA=0,串行时钟的第一个跳变沿(上升沿或下降沿)采集数据。
7 x8 ]! K% q1 l5 g$ y5 y: y+ C+ c) b( `④、CPHA=1,串行时钟的第二个跳变沿(上升沿或下降沿)采集数据。
9 |1 b, ^  W1 c; h9 X这四种工作模式如图44.1.1.2所示:
; i9 x  _( q* K; q4 m; y) L; a' s: I8 m2 b! Y  d& L
e9f2ff0be74741d59f270654bb9c3566.png
7 p) i; Q' ]: m2 E  ?# M
4 T6 H/ X; B5 K; U& B, M  H) K图44.1.1.2 SPI四种工作模式
; ^$ u6 p6 `- W- b$ _6 I1 w- [$ Y和I2C一样,SPI也是有时序图的,以CPOL=0,CPHA=0这个工作模式为例,SPI进行全双工通信的时序如图44.1.1.3所示:7 {) ^, ~) n4 U. V# U

& `/ m2 F5 e$ Z& u0 u  n dd6c4ab491654e4784396009a3488766.png
, C0 {6 [* ^/ E  U! ?" F  r! ?/ W/ }# N' E# J
图44.1.1.3 SPI时序图8 G# \' z+ |- v) U& a
从图44.1.1.3可以看出,SPI的时序图很简单,不像I2C那样还要分为读时序和写时序,因为SPI是全双工的,所以读写时序可以一起完成。图44.1.1.3中,CS片选信号先拉低,选中要通信的从设备,然后通过MOSI和MISO这两根数据线进行收发数据,MOSI数据线发出了0XD2这个数据给从设备,同时从设备也通过MISO线给主设备返回了0X66这个数据。这个就是SPI时序图。1 L" F0 N" S- d  _) z
关于SPI就讲解到这里,接下来我们看一下STM32MP1自带的SPI外设。2 S7 W# f% s" k+ V% V
44.1.2 STM32MP1 SPI简介* M( k* M9 k; I1 @. c
STM32MP1自带的SPI全称为:Serial peripheral interface。SPI特性如下:2 k+ @: p0 M! X! P" Z
①、全双工同步串口接口。
; ?6 n* G% f9 @②、半双工模式。" M* \2 R0 `5 k0 W
③、可配置的主/从模式。) G2 I, R8 p# F5 o, s. @
④、支持I2S协议。0 X7 Q. Y" }$ U7 x6 p
⑤、在达到 FIFO 阈值、超时、操作完成以及发生访问错误时产生中断。3 J; Q% f# Q9 ~, ]" Q" X
⑥、允许16位,24位或者32位数据长度。, W; Z4 Y$ z% S+ `. m: J7 M
⑦、支持软件片选和硬件片选。
2 C% q$ ?/ `( ~6 TSTM32MP1的SPI可以工作在主模或从模式,本章我们使用主模式,此芯片有6个SPI,其中SPI1~3是支持I2S协议。在主模式下,可以选择硬件片选和软件片选,如果使用了硬件片选,那么每一个SPI只支持一个外设,软件片选就可以支持无数个外设,本章实验我们不使用硬件片选信号,因为硬件片选信号只能使用指定的片选IO,软件片选的话可以使用任意的IO。
. P( X& w) j4 U8 W
( K* @, I% _: a  H- ]* ?9 [' k44.1.3 ICM-20608简介$ T+ K; w$ X: {, a4 a5 X3 O
ICM-20608是InvenSense出品的一款6轴MEMS传感器,包括3轴加速度和3轴陀螺仪。ICM-20608尺寸非常小,只有3x3x0.75mm,采用16P的LGA封装。ICM-20608内部有一个512字节的FIFO。陀螺仪的量程范围可以编程设置,可选择±250,±500,±1000和±2000°/s,加速度的量程范围也可以编程设置,可选择±2g,±4g,±4g,±8g和±16g。陀螺仪和加速度计都是16位的ADC,并且支持I2C和SPI两种协议,使用I2C接口的话通信速度最高可以达到400KHz,使用SPI接口的话通信速度最高可达到8MHz。开发板上的ICM-20608通过SPI接口和STM32MP157连接在一起。ICM-20608特性如下:
' ~) q4 c. @. T" H: C8 |①、陀螺仪支持X,Y和Z三轴输出,内部集成16位ADC,测量范围可设置:±250,±500,±1000和±2000°/s。
4 c+ L8 n8 O+ Y②、加速度计支持X,Y和Z轴输出,内部集成16位ADC,测量范围可设置:±2g,±4g,±4g,±8g和±16g。3 b! ~% ]8 V' S, e. V  s
③、用户可编程中断。
7 q; _7 B+ m! M( c. o. C④、内部包含512字节的FIFO。: G6 M) U: Q# a: i: k* g
⑤、内部包含一个数字温度传感器。
' a, ?9 b2 j0 G2 T; A⑥、耐10000g的冲击。- }2 ?1 U+ L+ j; }4 ]0 m4 R: K* @
⑦、支持快速I2C,速度可达400KHz。
, v- ?* p& w$ p2 X; j8 \9 T⑧、支持SPI,速度可达8MHz。  w3 t% }( ~, w- f( z
ICM-20608的3轴方向如图44.1.3.1所示:
7 s) L( d& V( h8 E- S& T, B3 H5 c$ n" P( W
3f5159cedb7a42a298be70998eea5a0b.png
2 Z& v- H; g8 F/ U. ^4 u$ B& ^4 G  ^9 s* ]8 g* C
图44.1.3.1 ICM-20608检测轴方向和极性
  p" J' H) {" c$ |ICM-20608的结构框图如图44.1.3.2所示:
' i5 t+ S) Y* i( g& N
- |) `/ W* {6 a. b2 p" k 92b66799792e4b55b987b576bebb41c5.png
5 f# l) c* K3 q
4 q7 ~+ ?) D' ?8 T& c* x1 v- a  e图44.1.3.2 ICM-20608框图
5 K9 U9 G' J% c- ~0 u7 M如果使用IIC接口的话,ICM-20608的AD0引脚决定I2C设备从地址的最后一位,如果AD0为0的话ICM-20608从设备地址是0X68,如果AD0为1的话ICM-20608从设备地址为0X69。本章我们使用SPI接口,跟上一章使用AP3216C一样,ICM-20608也是通过读写寄存器来配置和读取传感器数据,使用SPI接口读写寄存器需要16个时钟或者更多(如果读写操作包括多个字节的话),第一个字节包含要读写的寄存器地址,寄存器地址最高位是读写标志位,如果是读的话寄存器地址最高位要为1,如果是写的话寄存器地址最高位要为0,剩下的7位才是实际的寄存器地址,寄存器地址后面跟着的就是读写的数据。表44.1.3.1列出了本章实验用到的一些寄存器和位,关于ICM-20608的详细寄存器和位的介绍请参考ICM-20608的寄存器手册:) R. n( N8 ~/ u0 [2 v5 [8 `9 X0 a; m

, X" V) z# N# D9 M' x) m4 D. c 74d1356a07884fa5b5e220844c4b66d2.png
/ `7 H  J% Y+ D& K+ ^
/ }6 `, y& @7 v8 sICM-20608的介绍就到这里,关于ICM-20608的详细介绍请参考ICM-20608的数据手册和寄存器手册。
+ p4 D9 {3 w1 M2 k44.2 Linux下SPI驱动框架
3 x! ~/ k" N9 o3 R9 `  sSPI总线框架和I2C总线框架很类似,都采用了主机控制器驱动和设备驱动分离的思想;主机控制器也就是SoC的SPI控制器,例如STM32MP1的SPI控制器;而设备驱动对应的则是挂在SPI总线下的从机设备驱动程序。主机控制器针对具体的SOC平台,例如STM32MP1,对于同一个SOC平台来说,SPI控制器驱动程序是不用动的,不管外接的是什么SPI从机设备,对应的控制器驱动程序都一样,所以我们的重点就落在了种类繁多的SPI从机设备驱动开发了。SPI控制器驱动程序一般是不需要驱动开发工程师自己编写,SOC厂商会提供相应的主机驱动程序。
, R0 N& Z0 q) x! n1 c6 p! V在Linux内核当中,与I2C总线框架一样,SPI总线框架(也可以叫做SPI子系统)也可以分为三个部分:
* b) a& j+ W8 j1 dSPI核心层:SPI核心层是Linux的SPI子系统的核心代码部分,提供了核心数据结构的定义、SPI控制器驱动和设备驱动的注册、注销、管理等API。其为硬件平台无关层,向下屏蔽了物理总线控制器的差异,定义了统一的访问策略和接口;其向上提供了统一的接口,以便SPI设备驱动通过总线控制器进行数据收发。在Linux系统中,SPI核心层的代码位于drivers/spi/spi.c。
, N$ e/ ~0 ?* F+ h* i% wSPI控制器驱动层:每种处理器平台都有自己的SPI控制器驱动程序,它的职责是为系统中的SPI总线实现相应的读写方法。例如STM32MP1就有六个SPI,那么就有六个SPI控制器,每个控制器都有一条特定的SPI总线的读写。SPI子系统使用struct spi_master数据结构体来描述SPI控制器。在内核源码drivers/spi目录下有很多以spi-xxxx.c命名的源文件,如图44.2.1:: t/ [: @! N' S; ^  P

8 }' ^. y" b! }! k2 I" x 2a53ac1fd8734ac0b2d6840affcba34a.png
; ^5 E) S* Q! I; Z( ?  Q: n. G- O* [0 S8 E( A3 `8 r
图44.2.1 SPI控制器驱动源码
/ d# W  `8 R) j# e; R这些文件就是具体平台对应的SPI控制器驱动程序,使用SPI核心层提供的接口向SPI子系统注册SPI控制器。+ n$ {4 N# z; F$ \: ^4 _6 o* S+ x- T
SPI设备驱动层:SPI从设备对应的驱动程序,比如一些SPI接口的芯片器件对应的驱动程序。接下来我们详细的聊聊SPI子系统。6 u$ F2 h  Q) I- J$ K6 {2 T# V0 A1 T
44.2.1 SPI主机驱动3 A) {% ^8 F7 a& {# u5 A9 {
SPI主机驱动就是SoC的SPI控制器驱动,类似I2C总线的适配器驱动。SPI子系统使用spi_master结构体来描述SPI控制器,其实spi_master是一个宏,这个宏定义在include/linux/spi/spi.h文件中,如下所示:) j5 Q- `  A- h  m0 B" |
#define spi_master spi_controller
  l, X- i# O/ @9 t: y. \, B所以由此可以知道,spi_master就是spi_controller结构体,该结构体定义在include/linux/spi/spi.h文件中,如下所示:
& c/ W! B& x! S/ w$ N" P5 y
1 V; @1 X+ d8 b$ }- B' t3 ^6 ~' Z
  1. 示例代码44.2.1 spi_controller结构体# u# e# M* N0 i: d
  2. 424 struct spi_controller {
    , a) w, v, N7 @/ O- ?
  3. 425     struct device   dev;                    /* device 对象 */
    & O5 D" f$ L& m6 j6 z
  4. 426
    # r' t7 i! Z. `1 q
  5. 427     struct list_head list;- \- x$ n/ g, ^  t+ X) `
  6. ......0 ]- H+ B8 G. J# `, g' ]4 N
  7. 435     s16         bus_num;                    /* SPI总线编号 */
    3 F8 d- T9 D# Y* y
  8. ......
    9 C4 U. L8 }% W- {5 T6 k) w
  9. 440     u16         num_chipselect;          /* 片选                 */* N  a- u7 n7 [7 X3 @. o
  10. 441% L) d7 m* ]& B
  11. 442     /* some SPI controllers pose alignment requirements on DMAable
    . k, ]& G, ?# G
  12. 443      * buffers; let protocol drivers know about these requirements.8 g5 l; t$ C7 X) \) c0 R4 |; L
  13. 444      */
    6 g2 P/ n5 O9 N# X- M  R
  14. 445     u16         dma_alignment;# n& E" d3 j6 N3 ^! O
  15. 446
    1 @& m+ T! T: ~5 t0 E
  16. 447     /* spi_device.mode flags understood by this controller driver */
    * E. F7 [7 {; h
  17. 448     u32         mode_bits;              /* 模式位 */
    8 V6 X; ^5 r( K! r6 ?
  18. ......
    " J: K3 I  P  c% \. j3 F/ ?
  19. 455     /* limits on transfer speed */3 `- }/ E# P4 F9 \4 T. c  \
  20. 456     u32         min_speed_hz;         /* SPI控制器支持的最小传输速率 */
    / `2 L2 L  S: S& k; a
  21. 457     u32         max_speed_hz;         /* SPI控制器支持的最大传输速率 */
    % l0 ]/ n: x" [7 l- n
  22. 458
    / ~. ^; h7 s' J1 I: q8 ?" ^7 z  |- q
  23. 459     /* other constraints relevant to this driver */
    2 t6 i7 b6 `. j3 i" F- ?: J- L
  24. 460     u16         flags;                         /* 传输类型标志 */
    ( x' u7 v$ G0 ~, M
  25. ......; o3 C0 i- v6 T% u
  26. 468
    & ]) ?3 f/ }& m1 F! h0 @
  27. 469     /* flag indicating this is an SPI slave controller */
    7 s; N+ W% @* \3 H1 W/ b
  28. 470     bool            slave;                  /* 标志该控制器是否为SPI从设备存在 */
    % c6 H$ t2 F1 b0 }
  29. 471
    6 @( V3 t4 b+ S+ ?! ~/ P" _
  30. 472     /*
    ' d) `. F5 B  K! Z
  31. 473      * on some hardware transfer / message size may be constrained: H2 ~. j8 E7 N8 k( d: e
  32. 474      * the limit may depend on device transfer settings2 ?" X9 \" p1 R4 D5 y9 d
  33. 475      */
    : i$ v2 ~- {5 L" v9 q4 P# g8 s
  34. 476     size_t (*max_transfer_size)(struct spi_device *spi);
    - D; J. y; F$ B/ X+ P$ y
  35. 477     size_t (*max_message_size)(struct spi_device *spi);. Y* V  ]! y% F7 P
  36. 478
    - G. j5 B0 J/ H( n2 ~, u
  37. 479     /* I/O mutex */
    8 {$ {+ E; ?3 Q& @
  38. 480     struct mutex        io_mutex;/ L+ _9 ?; ]7 H0 {' B
  39. 481$ K: u7 }9 g; ^9 F
  40. 482     /* lock and mutex for SPI bus locking */4 K% \$ ^- t& n( N, Q
  41. 483     spinlock_t      bus_lock_spinlock;
    % r' o/ x4 F' |% H9 k
  42. 484     struct mutex        bus_lock_mutex;
    ) q% A* I. N, ]( c
  43. 485
    ( u0 H+ Y8 \* E3 w7 P- k0 x0 L3 S
  44. 486    /* flag indicating that the SPI bus is locked for exclusive use */0 Y$ x$ i+ w4 N2 X: R) o
  45. 487     bool            bus_lock_flag;- K  |3 x! t; b* W0 S4 h* O4 ^
  46. ......
    . q3 H! n, t) L# Y
  47. 495     int         (*setup)(struct spi_device *spi);
    8 e  F& q! D( L( X" r0 x# L8 U- ?
  48. ....... V+ A' V6 Y7 j4 \4 L; q4 \, D4 F
  49. 527     int         (*transfer)(struct spi_device *spi,
    % Y' I1 h8 G! }' S2 ]
  50. 528                         struct spi_message *mesg);: c6 \2 _/ X' F. C0 [
  51. ......( U8 V8 M6 l/ f
  52. 567     int (*prepare_transfer_hardware)(struct spi_controller *ctlr);8 _0 [' R) j5 G; r
  53. 568     int (*transfer_one_message)(struct spi_controller *ctlr,9 g  n4 S! c+ u: V: W" }/ M
  54. 569                     struct spi_message *mesg);
    / s2 I. n: R& f" q- A5 a
  55. ......) v# q2 J( D; {/ z% l1 n
  56. 607 };
复制代码

+ W4 n& e" u/ [# y第495行,SPI控制器的setup函数,类似于初始化函数。) W" v9 }6 B( P9 O% Q0 o! F* x" b
第527行,SPI控制器的transfer函数,和i2c_algorithm中的master_xfer函数一样,控制器数据传输函数。
, Y% G/ {5 v4 I: t3 ^5 J第568行,transfer_one_message 函数,也用于 SPI 数据发送,用于发送一个 spi_message,
0 {, p+ t6 z- P* T7 N
  d1 A' j5 M, t3 b; bSPI 的数据会打包成 spi_message,然后以队列方式发送出去。7 i- F  f& k* z/ r7 s" ]) ?
SPI 主机端最终会通过 transfer 函数与 SPI 设备进行通信,因此对于 SPI 主机控制器的驱动编写者而言 transfer 函数是需要实现的,因为不同的 SOC 其 SPI 控制器不同,寄存器都不一样。和 I2C 适配器驱动一样,SPI 主机驱动一般都是 SOC 厂商去编写的,所以我们作为 SOC 的使用者,这一部分的驱动就不用操心了,除非你是在 SOC 原厂工作,内容就是写 SPI 主机驱动。
' f. {: o* |, {& ~SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册spi_master。
2 y3 \) ^, b/ X1、spi_master申请与释放+ p& V  q$ r7 K3 y& @" O8 D
spi_alloc_master函数用于申请spi_master,函数原型如下:. _& F4 v! Q* K5 G
struct spi_controller *spi_alloc_master(struct device *host,  h* ~- e- F5 }; P: ^) U& s
unsigned int size)  o& S# X  h+ E. S9 J
函数参数和返回值含义如下:
8 ^$ q- K- F3 R! Q: O3 fhost:设备,一般是platform_device中的dev成员变量。  p& z# J' j) A7 _2 _+ b
size:私有数据大小,可以通过spi_master_get_devdata函数获取到这些私有数据。
0 {$ Z9 T$ x" Y  D0 k* w7 y返回值:申请到的spi_controller,也就是spi_master。
! F6 f/ W% V% z5 \" G  Ospi_master的释放通过spi_master_put函数来完成,当我们删除一个SPI主机驱动的时候就需要释放掉前面申请的spi_master,spi_master_put本质上是个宏:2 K6 ?* G- g0 M! }; ?9 z% `; N
#define spi_master_put(_ctlr) spi_controller_put(_ctlr)
$ r" x& k4 q2 w$ g8 T1 Sspi_master_put函数最终通过调用spi_controller_put函数来完成spi_master释放,原型如下:( x1 p' M3 x% X3 j( [  t6 w+ K
void spi_master_put(struct spi_controller *ctlr)# U, W% F+ }* Z" `" b9 F
函数参数和返回值含义如下:9 T* o- h9 h  x5 h( r( B
ctlr:要释放的spi_master。% x$ e2 ^# k7 v: `8 W3 E1 c2 k
返回值:无。+ k6 j1 o, u5 K
2、spi_master的注册与注销
$ s; F, T; v3 U) F; U当spi_master初始化完成以后就需要将其注册到Linux内核,spi_master注册函数为spi_register_master,函数原型如下:2 C% _# ~5 Z# b! f5 P0 S
int spi_register_master(struct spi_controller *ctlr)9 p+ q) G4 w: f& _+ D; Q! b! _
函数参数和返回值含义如下:
% B* j7 L8 U2 d1 ^7 r0 tctlr:要注册的spi_master。# R* n% H% e2 d6 a
返回值:0,成功;负值,失败。$ U; W' r4 t( x( c2 V7 U* t
如果要注销spi_master的话可以使用spi_unregister_master函数,此函数原型为:
8 t! Y9 S6 }5 v* lvoid spi_unregister_master(struct spi_controller *ctlr)
+ Y3 p# y! C- N+ j4 {1 U函数参数和返回值含义如下:0 }- F& R' T8 [' _" v5 B
ctlr:要注销的spi_master。2 `, k' c! I1 d  y9 I' K
返回值:无。
0 t( \/ M2 ~" O: \! b( K% U44.2.2 SPI设备驱动
& b  A, P1 ~7 V( }# N* s  dspi设备驱动和i2c设备驱动也很类似,Linux内核使用spi_driver结构体来表示spi设备驱动,我们在编写SPI设备驱动的时候需要实现spi_driver。spi_driver结构体定义在include/linux/spi/spi.h文件中,结构体内容如下:
- S  A" T, v+ C0 O9 Z2 E7 |; N: L
" h2 {( o' Q3 n
  1. 示例代码44.2.2.1 spi_driver结构体
    * f. K5 d6 D+ M6 p
  2. 259 struct spi_driver {2 C2 z( _1 G5 v& R
  3. 260     const struct spi_device_id *id_table;
    % D" i8 P* [9 s5 D4 T4 B
  4. 261     int         (*probe)(struct spi_device *spi);. o+ c4 z3 i: S, G/ `- s% F
  5. 262     int         (*remove)(struct spi_device *spi);; P: O; y3 j- L
  6. 263     void              (*shutdown)(struct spi_device *spi);6 _0 ?% I/ E& m# _) M
  7. 264     struct device_driver    driver;
    4 u7 g6 z! v( M5 j
  8. 265 };
复制代码
! Y$ F4 y7 |$ Z. C
可以看出,spi_driver和i2c_driver、platform_driver基本一样,当SPI设备和驱动匹配成功以后probe函数就会执行。+ s8 Z# W3 s$ X3 ~% V7 D' S" P# P6 n  m
同样的,spi_driver初始化完成以后需要向Linux内核注册,spi_driver注册函数为spi_register_driver,函数原型如下:0 e+ V& t# s: L( k' q$ C3 x7 T5 I! b
int spi_register_driver(struct spi_driver *sdrv): Z+ V6 _9 [3 _6 q$ p  `8 x
函数参数和返回值含义如下:
) X7 Z2 V5 v8 g' A  Psdrv:要注册的spi_driver。4 ]7 E: v7 W$ [- l/ g8 u* ^6 R9 e, p; X
返回值:0,注册成功;赋值,注册失败。( X6 |- I0 b& h% q& J
注销SPI设备驱动以后也需要注销掉前面注册的spi_driver,使用spi_unregister_driver函数完成spi_driver的注销,函数原型如下:1 z: o0 r" O& U) h
void spi_unregister_driver(struct spi_driver *sdrv), ~1 g4 P0 ^: g$ h6 q
函数参数和返回值含义如下:8 s3 c5 F  P/ |) J
sdrv:要注销的spi_driver。) ?5 W4 o( \  e
返回值:无。
! e; K7 u3 h. w! m1 Uspi_driver注册示例程序如下:
  `% n1 D& Y$ Y2 i# X1 i& x7 a7 Z/ N, A- B9 R$ N- J) W8 @0 {0 F$ p
  1. 示例代码44.2.2.2 spi_driver注册示例程序1 Z+ \1 s- P* E& h) w
  2. 1  /* probe函数 */; h) w' J2 h! u2 e
  3. 2  static int xxx_probe(struct spi_device *spi)
    ' v" N: I+ w* n" B# ]& z( f& i: r
  4. 3  {
    ' w7 K& U5 h. U, i5 V( f9 m
  5. 4           /* 具体函数内容 */
    4 g+ n( U0 y& }& {
  6. 5           return 0;: Q2 S4 L. u# \& x# m* g
  7. 6  }4 u4 Q* b* @3 f
  8. 7  ) ^% [( q' p+ {( L" V1 M+ L
  9. 8  /* remove函数 */
    6 G" H" s  k7 |3 z, t, e/ r
  10. 9  static int xxx_remove(struct spi_device *spi), H* W! g8 w0 G$ l1 W
  11. 10 {
    ; R! ]- v+ Z% f9 k" A0 H
  12. 11          /* 具体函数内容 */( _* r: h# L- {
  13. 12                  return 0;( M9 i" |/ ~7 C2 q
  14. 13 }
    : S# k6 \7 y8 J# Y1 j5 X
  15. 14 /* 传统匹配方式ID列表 */+ S3 A/ C0 }/ y
  16. 15 static const struct spi_device_id xxx_id[] = {
    1 I9 i2 a! l, }  ]5 O9 A
  17. 16          {"xxx", 0},  5 h5 S  L& ^+ y& I- f
  18. 17          {}
    ( X, W6 \, U" ~# ^) t
  19. 18 };# O4 q! G( J* T! e0 ~
  20. 19 & h# {  q( W- G* U
  21. 20 /* 设备树匹配列表 */
    1 L0 }, _5 e* Z5 m- N$ s. v
  22. 21 static const struct of_device_id xxx_of_match[] = {  }7 I8 T9 d; z8 a
  23. 22          { .compatible = "xxx" },4 J2 `$ p9 _; W6 m: r2 E/ Z
  24. 23          { /* Sentinel */ }2 K: ]: g3 w& P% d* h
  25. 24 };
    8 b/ Y' l; p/ n: Y
  26. 25 / u: Y& u& w. h2 R) j
  27. 26 /* SPI驱动结构体 */
    . O: v* I, T* y
  28. 27 static struct spi_driver xxx_driver = {) [: w3 @' @3 d  V6 S; R
  29. 28          .probe = xxx_probe,# C5 t' g+ J# {$ x
  30. 29          .remove = xxx_remove,5 W) Q0 q9 Q# O" v
  31. 30          .driver = {. `# z1 T. Y5 u; `
  32. 31                  .owner = THIS_MODULE,
    ; d, K( M( U0 U" j0 j
  33. 32                  .name = "xxx",
    + G/ G  `6 G) l3 O$ o) y6 X
  34. 33                  .of_match_table = xxx_of_match,
    5 c, H: L& @/ H0 o3 l' O* ]0 m* L
  35. 34                 },! B# b7 k0 L9 L2 }- x
  36. 35          .id_table = xxx_id,. T, y0 u7 I+ u/ W! e, o- X/ S
  37. 36 };
    1 w( ^. `/ e7 L5 Q7 P9 |, r0 w' [
  38. 37         
    ) I# ~: Q8 V: u# m0 Z' t
  39. 38 /* 驱动入口函数 */: ]- G; n' l9 ^1 Q$ D+ H5 U
  40. 39 static int __init xxx_init(void)# H5 T6 T* s  N3 u
  41. 40 {; ~" p% V1 k2 n' `% R( X# @8 F
  42. 41          return spi_register_driver(&xxx_driver);
    . M# M6 b8 f- R7 D9 I
  43. 42 }' X/ |! }& D1 D$ g6 D
  44. 43
    * b# t* G- s! A: X% D
  45. 44 /* 驱动出口函数 */
    * |5 A; I& Z9 P# Y2 c" [( T, \
  46. 45 static void __exit xxx_exit(void)$ v- }5 V1 P9 @& a3 c6 l
  47. 46 {
    . G9 q. l) P5 a. v
  48. 47          spi_unregister_driver(&xxx_driver);
    3 P9 L; p/ I5 F% y
  49. 48 }* [6 e" D5 p* A; l, o& q
  50. 49
    & N+ d, M3 V' x8 J3 y5 z3 k$ ~
  51. 50 module_init(xxx_init);
    8 N8 X- n! i9 s" D$ f& F* ?. x
  52. 51 module_exit(xxx_exit);
复制代码
9 h6 o& ^, j( V+ |; O6 Z) E4 @  z
第1~36行,spi_driver结构体,需要SPI设备驱动人员编写,包括匹配表、probe函数等。和i2c_driver、platform_driver一样,就不详细讲解了。
* A) k% Z: Q  t  q第39~42行,在驱动入口函数中调用spi_register_driver来注册spi_driver。" m) k9 F  L; I6 @. |. H8 P
第45~48行,在驱动出口函数中调用spi_unregister_driver来注销spi_driver。# C8 p: A' d* C3 R; Q1 k! V

/ V. t, F4 C  e0 F& j9 f44.2.3 SPI设备和驱动匹配过程
9 N$ j6 k6 a% o/ qSPI设备和驱动的匹配过程是由SPI总线来完成的,这点和platform、I2C等驱动一样,SPI总线为spi_bus_type,定义在drivers/spi/spi.c文件中,内容如下:
. F, n/ I8 X' H$ c& D1 M* l- X0 X, x4 Y0 W) z8 N( O
  1. 示例代码44.2.3.1 spi_bus_type结构体
    9 P7 `' [2 I+ `4 V3 u, t! f
  2. 377 struct bus_type spi_bus_type = {
    2 J/ n6 ^, n2 ]% H
  3. 378     .name               = "spi",; ^# {( Z7 a! X; a9 m! `# _4 ^; f
  4. 379     .dev_groups         = spi_dev_groups," M4 d- q1 w# A2 |
  5. 380     .match              = spi_match_device,
    , N) Z+ Z8 I% \8 a+ P3 `
  6. 381     .uevent             = spi_uevent,
    # b( B( D0 I" Y6 J1 v; J
  7. 382 };
复制代码

' G- y9 h- e* B, g& {  S1 O8 _9 a可以看出,SPI设备和驱动的匹配函数为spi_match_device,函数内容如下:/ q0 x4 x( ^/ b/ [' `7 D* G0 L

$ W5 x* [' R8 N/ f2 T5 Q, z
  1. 示例代码44.2.3.2 spi_match_device函数
    . c2 w3 B" G- |8 T) L
  2. 342 static int spi_match_device(struct device *dev, 8 r. g9 @0 y# r. P, ?
  3. struct device_driver *drv)# ]: v6 `* c  h3 @8 a
  4. 343 {: w9 z9 j( i( n! p/ L4 M; C* i
  5. 344     const struct spi_device *spi = to_spi_device(dev);
    $ u8 S5 V/ d* x
  6. 345     const struct spi_driver *sdrv = to_spi_driver(drv);
    : ^& g: N$ y, w+ ]1 y% ~3 I
  7. 3467 L* d1 G& ?  E, u! M0 r; y0 T: `
  8. 347     /* Check override first, and if set, only use the named driver */0 s) ~( x8 p" B" t& F0 o% b5 U
  9. 348     if (spi->driver_override); x$ h6 v$ e, R9 N
  10. 349         return strcmp(spi->driver_override, drv->name) == 0;
    7 m2 c* M  E' Q3 f0 W4 f  Q5 f
  11. 350# S' h$ f: @- u2 u
  12. 351     /* Attempt an OF style match */
    6 S4 |* Q6 @# m0 s
  13. 352     if (of_driver_match_device(dev, drv))
    / b5 J+ ]3 o8 w' A1 p) u4 k2 [9 C
  14. 353         return 1;
    / v1 J9 k' b1 J! |0 N' H7 m
  15. 354& W& m% f# S% I0 J5 j( t' \2 @
  16. 355     /* Then try ACPI */. I; h1 o: v/ Y
  17. 356     if (acpi_driver_match_device(dev, drv))3 ^7 a' L1 v; @8 T
  18. 357         return 1;% I4 D, E  C4 L: ?& U! X" [
  19. 358
    8 Z. x" E/ M3 e. Y3 n# k1 z  u/ P
  20. 359     if (sdrv->id_table)
    . _  b! R! D+ m: u# }* ]2 X
  21. 360         return !!spi_match_id(sdrv->id_table, spi);' W' A3 |, `1 O/ z
  22. 3619 L+ R) |. k9 L! @, G2 s  p( _. m3 W% Q: u
  23. 362     return strcmp(spi->modalias, drv->name) == 0;" M; w) N; }( \. [  w1 ?) F1 \' {5 }  d9 C
  24. 363 }
复制代码
2 Y+ T' c+ R( X' V
spi_match_device函数和i2c_match_device函数的对于设备和驱动的匹配过程基本一样。$ n& O: o2 _) \! l! z: K, ~3 |
第352行,of_driver_match_device函数用于完成设备树设备和驱动匹配。比较SPI设备节点的compatible属性和of_device_id中的compatible属性是否相等,如果相当的话就表示SPI设备和驱动匹配。/ T" c2 v0 P! J/ y+ b: K' F$ o
第356行,acpi_driver_match_device函数用于ACPI形式的匹配。' Y; y( d" x; O& t% W
第360行,spi_match_id函数用于传统的、无设备树的SPI设备和驱动匹配过程。比较SPI设备名字和spi_device_id的name字段是否相等,相等的话就说明SPI设备和驱动匹配。# t2 g: o% y' ~# w0 Z7 Q! L: v5 f
第362行,比较spi_device中modalias成员变量和device_driver中的name成员变量是否相等。
/ G/ x6 R8 ~. R9 Y* D" T44.3 STM32MP1 SPI主机驱动分析
, c: ]6 t" C/ r6 x和I2C的适配器驱动一样,SPI主机驱动一般都由SOC厂商编写好了,打开stm32mp151.dtsi文件,找到如下所示内容:
- R3 Z" n1 R  C4 K' f4 z: o, X! a' t- Y. |6 {
  1. 示例代码44.3.1 stm32mp151.dtsi文件中的spi1节点内容
    5 `8 d* H: f9 z- E7 X
  2. 1   spi1: spi@44004000 {  b( Q" }+ ?* U
  3. 2       #address-cells = <1>;
    ; [; I& Y8 D9 F
  4. 3       #size-cells = <0>;
    ) X6 _  f$ ?$ E0 D; ]
  5. 4       compatible = "st,stm32h7-spi";
    9 C' A1 `# D7 ^6 n6 j6 p
  6. 5       reg = <0x44004000 0x400>;: e/ b* h9 s' b% J3 V
  7. 6       interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>;
    3 E+ j* F+ d9 P6 C9 \+ i6 K- ~
  8. 7       clocks = <&rcc SPI1_K>;& G6 y6 X' {- L- Q8 ^
  9. 8       resets = <&rcc SPI1_R>;
    9 \2 H* s' b5 ^7 R; [, L( O1 t
  10. 9       dmas = <&dmamux1 37 0x400 0x01>,: M( t9 i) G' w
  11. 10            <&dmamux1 38 0x400 0x01>;
    1 V* |- F$ n, a7 L5 p0 S
  12. 11      dma-names = "rx", "tx";
    ) M6 d: ^% ^) y
  13. 12      power-domains = <&pd_core>;
    . m8 U3 A0 R5 r9 s5 i. `4 {
  14. 13      status = "disabled";
    , ^8 a& S  \) H, o% ~$ x0 D3 W& F
  15. 14  };
复制代码

" C6 u) ^" F; B重点来看一下第4行的compatible属性值,compatible属性为“st,stm32h7-spi”,在Linux内核源码中搜素这两个属性值即可找到STM32MP1对应的SPI主机驱动。STM32MP1的SPI主机驱动文件为drivers/spi/spi-stm32.c,在此文件中找到如下内容:
: h/ Y* B$ C  `5 |# O8 x' @' C5 `- B1 R: Q2 f( x- z
  1. 示例代码44.3.2 stm32_spi_driver结构体8 Q# F2 U* m+ R) X# T! [
  2. 1859    static const struct of_device_id stm32_spi_of_match[] = {
    . ?$ E; e: h; h# e$ K5 [6 O+ w% Z
  3. 1860                 {         .compatible = "st,stm32h7-spi",
    % `1 b. Z1 T2 k7 V8 W  M- r
  4.                                 .data = (void *)&stm32h7_spi_cfg },
    - C+ a4 ]2 B9 n. P# z! l
  5. 1861                {        .compatible = "st,stm32f4-spi", " Y. S( \' J9 Z6 y( e6 K0 s7 t
  6.                                 .data = (void *)&stm32f4_spi_cfg },1 T$ }1 D9 l! r8 i9 I. n' @: a! F- L
  7. 1862        {},5 t. z# O' D3 l# Y6 e
  8. 1863    };3 Q, k0 g4 `( E" I$ t
  9. ......8 e* I. S9 {1 A/ M( }! ]$ ^8 a
  10. 2154    static struct platform_driver stm32_spi_driver = {  o1 _0 c  l5 j+ q( B) B/ X7 o
  11. 2155        .probe = stm32_spi_probe,; {& m* u: v; I  {
  12. 2156        .remove = stm32_spi_remove,
    + w4 i1 K, n0 A2 }# `: t
  13. 2157        .driver = {' n. f2 `) Q0 H4 B; @" d  Z+ G3 z
  14. 2158            .name = DRIVER_NAME,
    - ~3 S" K7 r7 O( ^( p
  15. 2159            .pm = &stm32_spi_pm_ops,
    + v) e: |7 `+ G
  16. 2160            .of_match_table = stm32_spi_of_match,
    $ C$ G- p+ B' \4 D5 l6 f6 h0 D
  17. 2161        },
    ) n( M1 ?# |. O1 G
  18. 2162    };2 m' w7 _, V* A4 M1 V
  19. 2163
    - W( y3 L; d% C$ e7 W/ I1 ~
  20. 2164    module_platform_driver(stm32_spi_driver);
复制代码
  W- @1 \% a& r; q1 ~
第1860行,“st,stm32h7-spi”匹配项,因此可知STM32MP1主机驱动就是spi-stm32.c这个文件。- i3 y8 i2 k& E! o
第2154~2164行,从这里可以知道,该主机驱动程序是基于platform总线框架编写,platform_driver结构体变量为stm32_spi_driver,当platform总线下设备和设备驱动匹配成功之后就会执行stm32_spi_probe函数,同样当驱动模块卸载的时候就会执行stm32_spi_remove函数。: n4 L4 U, H9 }' ?4 k
接下来我们重点来看下stm32_spi_probe函数做了些什么,函数如下所示:
. Q" ~$ f) z' ^2 j/ W+ T3 f
' t+ x3 J7 s% q, P# z% y
  1. 示例代码44.3.3 stm32_spi_probe函数
    4 ~" @" ~) R# b6 N
  2. 1866    static int stm32_spi_probe(struct platform_device *pdev)
    2 U  L9 o( {5 E
  3. 1867    {  k. }( M' L$ R! I
  4. 1868        struct spi_master *master;0 w$ @' Y* u6 v% `( W$ I
  5. 1869        struct stm32_spi *spi;6 q) s5 y; c1 J- M8 I
  6. 1870        struct resource *res;3 _7 Q9 j7 r. t8 j* R/ x2 z9 R$ A8 F
  7. 1871        struct reset_control *rst;0 W  L: ^/ \' M% k1 T( H
  8. 1872        int i, ret, num_cs, cs_gpio;
    $ j& @6 }" I4 g7 c. ~
  9. 1873, b( Y. |& d, L. D; y
  10. 1874        master = spi_alloc_master(&pdev->dev,
    % T' q0 D% S7 s) F. g! H. D
  11. sizeof(struct stm32_spi));
    / |+ k) O' H0 h+ s( O+ ?, u
  12. 1875        if (!master) {
    0 w* I" ~/ c1 I/ {3 s2 B
  13. 1876            dev_err(&pdev->dev, "spi master allocation failed\n");
    ; E9 g7 q( |; `
  14. 1877            return -ENOMEM;
    ; E. B9 b. V( `" d/ w
  15. 1878        }1 ]( B! Q& i- u/ w
  16. .....+ {" `! @- M0 N
  17. 1892        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);- \  l6 b( S2 |! I" ]8 |
  18. 1893        spi->base = devm_ioremap_resource(&pdev->dev, res);
    ! c; C+ K3 T3 m
  19. 1894        if (IS_ERR(spi->base)) {
    ) V) }- ^5 z, L% w+ |; m
  20. 1895            ret = PTR_ERR(spi->base);
    & m8 \, R% L7 u  C
  21. 1896            goto err_master_put;: K+ q0 Q; [7 u& X2 W; ?4 q/ T3 l
  22. 1897        }1 ~% |$ L" U# m: i( r0 {* V! u
  23. 1898
    * p# A2 B  f" S/ U0 c
  24. 1899        spi->phys_addr = (dma_addr_t)res->start;1 V& @. B9 H* }6 l
  25. 19007 L* S3 K3 L; \+ ]/ n
  26. 1901        spi->irq = platform_get_irq(pdev, 0);
    2 Y, u, j. G' b- k" D
  27. 1902        if (spi->irq <= 0) {
    , S. k9 c6 G# ~  r6 x1 b" ~
  28. 1903            ret = spi->irq;2 _0 y! j! I) g" V5 I- {; O: Y
  29. 1904            if (ret != -EPROBE_DEFER)
    : @5 r3 y! |8 c! d8 Q
  30. 1905                dev_err(&pdev->dev, "failed to get irq: %d\n", ret);  r, k! k+ R, N; Z& N
  31. 1906            goto err_master_put;
    - |' o1 K3 i. u
  32. 1907        }
    2 Y1 ]$ M, _: k: d) r
  33. 1908        ret = devm_request_threaded_irq(&pdev->dev, spi->irq,
    % b+ Z$ q2 S5 v
  34. 1909                        spi->cfg->irq_handler_event,
    " Y/ i* p# c" t, G$ ]; e+ x
  35. 1910                        spi->cfg->irq_handler_thread,0 I% t5 t3 V9 A: v9 N9 m& `
  36. 1911                        IRQF_ONESHOT, pdev->name, master);" c+ ~7 q# V& B3 ^. v3 P7 H$ p
  37. ......
    % I- P7 g6 n2 z" f- Z
  38. 1963        master->dev.of_node = pdev->dev.of_node;0 L/ i: F" d0 c8 p' ^% C- _
  39. 1964        master->auto_runtime_pm = true;
    # m  f# y1 T/ i  q# B% p
  40. 1965        master->bus_num = pdev->id;
    8 Q3 z6 h. I1 N+ }8 j6 t, d" s6 E
  41. 1966        master->mode_bits = SPI_CPHA | SPI_CPOL | SPI_CS_HIGH |
    6 R* u% \4 I" L3 }8 C
  42. 1967                                                    SPI_LSB_FIRST | SPI_3WIRE;/ N3 y2 k6 ^" u: ?4 m
  43. 1968        master->bits_per_word_mask = spi->cfg->get_bpw_mask(spi);
    4 ?0 |: N8 j- \) L: y
  44. 1969                master->max_speed_hz =         spi->clk_rate /
    . G( p; k, F3 `
  45.                                        spi->cfg->baud_rate_div_min;
    4 S+ x0 v1 I7 v+ f! h
  46. 1970                master->min_speed_hz = spi->clk_rate /
    # Q" d! G! P4 Q4 M# Q9 Q) B: b
  47.                                                                   spi->cfg->baud_rate_div_max;
    . U; D7 R! O( \, j# x- \
  48. 1971        master->setup = stm32_spi_setup;
    ' G6 z. q' K: A# N$ L) M
  49. 1972        master->prepare_message = stm32_spi_prepare_msg;
    - `9 w  d1 p/ R# y+ {
  50. 1973        master->transfer_one = stm32_spi_transfer_one;1 S+ O, D7 [5 I& M. d9 ?
  51. 1974        master->unprepare_message = stm32_spi_unprepare_msg;4 k) b; X; D  g- e) C% M4 d  s; Z
  52. ......
    4 l( k+ c. W9 v7 j$ B7 L5 r
  53. 2026        ret = spi_register_master(master);
    ( R* ?6 K; q3 k8 I9 q' g7 N+ L+ n
  54. ......
    / E, Q, r; Y) b4 G4 r& p

  55. 6 X1 N5 B; g2 N3 v9 R, ^
  56. 2050    }
复制代码

# L' G* L6 q& L7 y' k, @第1874行,通过调用spi_alloc_master函数为master指针申请内存,也就是实例化master。3 v, b' p) a8 M2 `
第1901~1911行,获取中断号和注册中断函数。, B" b/ K# o5 N6 P
第1963~1974行,对master变量进行初始化和赋值,从结果可以看到,master 结构体中并没有设置 transfer 和 transfer_one_message 这两个用于 SPI 数据传输的函数,而是使用了 transfer_one 作为 SPI 数据传输的函数,对应的函数为stm32_spi_transfer_one,也就是该函数是STM32MP1 SPI数据传输的函数。3 A$ X) _4 ]0 c  T
第2026行,调用spi_register_master函数向SPI子系统注册一个master。
& t' [# X/ t4 `' g/ \; n; a这里简单第看看stm32_spi_transfer_one函数的内容,如下所示(有省略):
. x6 }& l, T& ^9 [# f7 O  I. e% v( ~+ `' Y1 t3 S( W
  1. 示例代码44.3.4 stm32_spi_transferZ_one函数' H# U' l) X" w5 k
  2. 1683    static int stm32_spi_transfer_one(struct spi_master *master,. h( k2 @5 m$ g* D$ r4 O) |  f6 E5 ?
  3. 1684                      struct spi_device *spi_dev,
    + l% ?% F* E* [4 u. m
  4. 1685                      struct spi_transfer *transfer)2 r+ X  _! @% `9 \7 ^; m
  5. 1686    {9 {2 G+ s6 d' B$ M
  6. 1687        struct stm32_spi *spi = spi_master_get_devdata(master);
    ; m: X2 P+ S4 V8 H7 ~* O% |$ j
  7. 1688        u32 xfer_time, midi_delay_ns;* Y$ D; a$ f4 z  I* x* e
  8. 1689        unsigned long timeout;
      k! J0 s) j: ~& L
  9. 1690        int ret;2 `" U- K( F- ]
  10. 1691
    - m6 E8 Q- K, q
  11. 1692        spi->tx_buf = transfer->tx_buf;# M6 y3 ^; Z% T$ D. z1 k. k
  12. 1693        spi->rx_buf = transfer->rx_buf;. m# B& |. {$ \. ^3 j
  13. 1694        spi->tx_len = spi->tx_buf ? transfer->len : 0;
    - [9 I, Z1 ^+ f' Z' Q
  14. 1695        spi->rx_len = spi->rx_buf ? transfer->len : 0;
    6 ^7 b* Y0 X" o- {
  15. 1696! Q' g: C: `' s0 M5 j  b
  16. 1697        spi->cur_usedma = (master->can_dma &&
    8 C! Y4 C6 {# q
  17. 1698                   master->can_dma(master, spi_dev, transfer));! ]( K. o( J* P& @; y4 L; w
  18. 1699
    . {9 B# S. f- N- k' U" ~
  19. 1700        ret = stm32_spi_transfer_one_setup(spi, spi_dev, transfer);% Y3 w- N; K$ t; o8 U
  20. 1701        if (ret) {: G! ]  _5 p8 ?8 U& Y8 b! S
  21. 1702            dev_err(spi->dev, "SPI transfer setup failed\n");
    6 s( W4 o. f+ V1 w0 ^) c3 r
  22. 1703            return ret;4 e) K3 Z7 `8 O8 }; h- o* l* W( T: k
  23. 1704        }
    9 l0 K: F1 m# M3 L7 a- k1 B
  24. 1705
    & J8 r% B' Y" i/ U- G' f& Y
  25. 1706        reinit_completion(&spi->xfer_completion);
    5 G! O5 x5 B, w  {) [9 s
  26. 1707        spi->xfer_status = 0;
    ' x5 U' {; `9 t( G, U: D
  27. 1708
    - B, d$ m5 S5 E( Y
  28. 1709        if (spi->cur_usedma)' ]: M: }7 i. J+ |" W0 b- X
  29. 1710            ret = stm32_spi_transfer_one_dma(spi, transfer);
    6 A+ c/ X; G4 z8 j3 i
  30. 1711        else' j+ ~0 E% A9 {
  31. 1712            ret = spi->cfg->transfer_one_irq(spi);
    ) U1 p0 v% a( t1 G7 l
  32. 1713
    ' N( U9 `; @4 L6 n1 {/ q0 H: d! ^
  33. 1714        if (ret)0 w% w2 C8 ^& e
  34. 1715            return ret;9 _/ O: `* p. S( k
  35. ......2 Y( c7 R' Q; e. m* i: h% `1 V+ F) m
  36. 1739    }
复制代码

2 a; E2 l0 |$ G' U第1709~1715行,如果启动了DMA那么就使用stm32_spi_transfer_one_dma来进行传输数据,没有用DMA的话就调用transfer_one_irq函数。这两个函数也就是控制寄存器来进行收发数据。
; h4 X1 d6 S) k- f1 R! C7 i关于STM32MP1 SPI主机程序的分析就到这里了。
3 j- L' b  ~- B( A1 N, k3 g) o5 J' x. Q! {7 @6 }- g
44.4 SPI设备驱动编写流程
, x( F( @/ H* X" t44.4.1 SPI设备信息描述* [: S! f  K0 u8 C( j
1、IO的pinctrl子节点创建与修改9 X$ W) u' a9 s
首先肯定是根据所使用的IO来创建或者修改pinctrl子节点,这个没有什么好说的。唯独要注意的是检查相应的IO有没有被其它的设备所使用,如果有多个pinctrl配置相同的IO是没有关系的,只要保证没有被设备调用就行。1 @8 m" d5 k/ C3 G  a
2、SPI设备节点的创建与修改
) u4 L5 o4 j/ M采用设备树方式的情况下,SPI从机设备信息描述就通过创建相应的设备子节点来完成,我们可以打开stm32mp157d-atk.dts这个设备树文件,然后在里边创建一个SPI从机设备节点,描述该设备的相关信息,我们后面再创建。
3 R! v9 l/ w9 u. x( h4 o( ~0 l44.4.2 SPI从机设备数据收发处理流程- P6 }7 I) u0 G. ~2 ~- s, W  K  S& H
SPI设备驱动的核心是spi_driver,这个我们已经在44.2.2小节讲过了。当我们向Linux内核注册成功spi_driver以后就可以使用SPI核心层提供的API函数来对设备进行读写操作了。首先是spi_transfer结构体,此结构体用于描述SPI传输信息,结构体内容如下:
  M" c2 m  d; o; Q$ F# c% L/ Z! a" k. G
  1. 示例代码44.4.2.1 spi_transfer结构体
    7 u" N3 ]6 V. J' V3 a7 J9 V
  2. 811 struct spi_transfer {
    0 H7 C% D+ A' O
  3. ......
    , ?+ U( v# T: }' k9 W
  4. 817     const void  *tx_buf;' ]! y! {8 [) P1 K9 l* F& [& Q& `" }
  5. 818     void        *rx_buf;
    ; [7 e! X0 q& q
  6. 819     unsigned    len;8 k8 d" E- a4 F4 g, T$ n
  7. 820
    : j7 S8 K; M# x8 X* p7 b
  8. 821     dma_addr_t  tx_dma;
    3 a, x" t' G, l  C
  9. 822     dma_addr_t  rx_dma;6 t: L& g( w% O( h- J
  10. 823     struct sg_table tx_sg;: D; D, z! X, G  F9 k
  11. 824     struct sg_table rx_sg;. {: I7 k; U% |( Y0 D
  12. 8257 K9 l' n: w8 P% z! q2 b
  13. 826     unsigned    cs_change:1;
    # Y) `' r& U) X
  14. 827     unsigned    tx_nbits:3;& x6 M. J$ H3 w6 U3 W0 B
  15. 828     unsigned    rx_nbits:3;
    : X) e, Y2 ?" ~6 E& D$ p
  16. 829 #define SPI_NBITS_SINGLE    0x01 /* 1bit transfer */
    1 H+ J" c# o4 v9 _& Q; [; f  k
  17. 830 #define SPI_NBITS_DUAL      0x02 /* 2bits transfer */+ q  J6 `7 H  \6 z6 e' ], ^
  18. 831 #define SPI_NBITS_QUAD      0x04 /* 4bits transfer */
    : _1 z" h! l1 Y* o
  19. 832     u8      bits_per_word;
    / K) O& R' p( ^2 R6 H* _% i
  20. 833     u8      word_delay_usecs;
    9 L% R2 z$ J3 D6 `6 c+ C9 F1 S1 x
  21. 834     u16     delay_usecs;
    ! w3 e& q0 h- n5 B# n3 h9 w$ B
  22. 835     u16     cs_change_delay;
    9 t" E) a! u* Q# P. R# j
  23. 836     u8      cs_change_delay_unit;+ b7 ^+ a3 v; g3 o
  24. 837 #define SPI_DELAY_UNIT_USECS    01 ^) N8 S! ~/ z5 t" c9 S1 U
  25. 838 #define SPI_DELAY_UNIT_NSECS    1, c" \! K( @/ F% f7 ]& }! r. x
  26. 839 #define SPI_DELAY_UNIT_SCK  2
    - y" N7 L' e- }
  27. 840     u32     speed_hz;
    ' l7 H7 \" J8 f
  28. 841     u16     word_delay;
    9 x: O6 B9 x$ X# C* y
  29. 842" D* w0 r% T7 y1 C: G( i8 r% V7 a
  30. 843     u32     effective_speed_hz;0 z( b; D2 N1 l
  31. 844! Q7 O' G, `  Y$ J! i- x1 _
  32. 845     struct list_head transfer_list;) p( h0 J3 U7 X
  33. 846 };
    7 Z5 v0 a3 {1 B9 N3 x: h. S. x: y
复制代码
; J7 C0 U) \' }. e6 K
第817行,tx_buf保存着要发送的数据。
, |7 I2 [. \. E1 O4 C" c. f* L, [第818行,rx_buf用于保存接收到的数据。
- L$ p2 D: [: k0 }. H+ @; @第819行,len是要进行传输的数据长度,SPI是全双工通信,因此在一次通信中发送和接收的字节数都是一样的,所以spi_transfer中也就没有发送长度和接收长度之分。
9 s5 v) i( C, X4 ^" y% lspi_transfer需要组织成spi_message,spi_message也是一个结构体,内容如下:
4 x% a* h3 J) v4 r2 o. K$ s
3 Q* U# Q7 }1 z4 K" o" `
  1. 示例代码44.4.2.2 spi_message结构体; J+ {5 ?; n. N" a9 O, r
  2. 878 struct spi_message {% m5 @0 h7 J1 h* p0 S5 n
  3. 879     struct list_head    transfers;6 Q3 K- w7 i+ I8 {
  4. 880
    1 Y; R+ [- y2 [4 L! F( A
  5. 881     struct spi_device   *spi;
    1 C6 ^% D" P4 P4 f0 a( _
  6. 882$ R* j/ K& G% C" Z% c3 [+ _6 W3 w
  7. 883     unsigned        is_dma_mapped:1;2 S  n! L: P2 n: F9 Z" Y
  8. ......; Q- a3 m; s$ }
  9. 897     void            (*complete)(void *context);4 ^( _7 S# f, N( _* d3 D
  10. 898     void            *context;
    6 w, O: R  f4 c6 z
  11. 899     unsigned        frame_length;- ?) i6 _# R% H/ J1 g7 [7 @
  12. 900     unsigned        actual_length;9 g( f; Q# O3 m" G' K
  13. 901     int         status;0 V- R2 r. Y  E, t* O
  14. ......1 k* }' K' ~6 ~% I& U" t9 n
  15. 907     struct list_head    queue;
    * _1 g0 j2 x4 Z" n6 D
  16. 908     void            *state;
    : S# f/ B+ C7 d$ l  z  O1 T9 n& _- x: b
  17. 909& g- u( E. b9 V& X+ G# n/ E
  18. 910     /* list of spi_res reources when the spi message is processed */; o' B5 Q" x) @% W" h( }
  19. 911     struct list_head        resources;; z& B8 K- X/ Y) Q: X2 y
  20. 912 };
    " v, `& ~9 Z0 i" v2 X8 S  I
复制代码

- V% B) e; ^2 o# s. m& E: V6 o在使用spi_message之前需要对其进行初始化,spi_message初始化函数为spi_message_init,函数原型如下:( r6 G' r/ c4 Z
void spi_message_init(struct spi_message *m)
2 w& T" g, e5 P& u! x5 q# V函数参数和返回值含义如下:
- z, W; l# G) `" |# jm:要初始化的spi_message。! |5 a2 d; L) n8 n4 s) H
返回值:无。
: R% ]( e, w. k& S! v4 gspi_message初始化完成以后需要将spi_transfer添加到spi_message队列中,这里我们要用到spi_message_add_tail函数,此函数原型如下:$ Q- A& h" N; g" \
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)8 J7 s. |$ b3 {. c+ a5 a
函数参数和返回值含义如下:) c$ ?. q) F) d/ r3 ]3 \
t:要添加到队列中的spi_transfer。
6 y( {1 D; q5 i1 [, Y1 om:spi_transfer要加入的spi_message。
  ~6 i+ T, ?+ N" k4 h/ R# t- L8 o返回值:无。1 s! D6 i1 \$ e6 T! o- v
spi_message准备好以后就可以进行数据传输了,数据传输分为同步传输和异步传输,同步传输会阻塞的等待SPI数据传输完成,同步传输函数为spi_sync,函数原型如下:
% }; P2 w# i" ]1 V) X- `5 Wint spi_sync(struct spi_device *spi, struct spi_message *message)5 Q. A5 [8 F* Z! I" a4 D
函数参数和返回值含义如下:
! {* v: k0 E% Hspi:要进行数据传输的spi_device。7 l: W+ q% d! G# Z
message:要传输的spi_message。
; v0 l0 d! \1 b4 i( w9 s返回值:无。1 l3 ~; R) y. g* k% @
异步传输不会阻塞的等到SPI数据传输完成,异步传输需要设置spi_message中的complete成员变量,complete是一个回调函数,当SPI异步传输完成以后此函数就会被调用。SPI异步传输函数为spi_async,函数原型如下:' d4 M; t1 K/ K% ~) l; \% t
int spi_async(struct spi_device *spi, struct spi_message *message)
9 M9 s" a, k  E函数参数和返回值含义如下:8 k  n$ \3 f+ l) m+ O' e0 f
spi:要进行数据传输的spi_device。
) m  A  K! W3 J/ A  k6 xmessage:要传输的spi_message。/ [) x  o% V# l
返回值:无。
+ g& S) m9 S7 h8 {$ b" y在本章实验中,我们采用同步传输方式来完成SPI数据的传输工作,也就是spi_sync函数。4 b& [  Z6 Q. X" j" {) P1 j/ ?! {
综上所述,SPI数据传输步骤如下:4 Q& X/ n( _1 i
①、申请并初始化spi_transfer,设置spi_transfer的tx_buf成员变量,tx_buf为要发送的数据。然后设置rx_buf成员变量,rx_buf保存着接收到的数据。最后设置len成员变量,也就是要进行数据通信的长度。, O+ \% d* n2 o1 w
②、使用spi_message_init函数初始化spi_message。
# j2 ?& b9 l( V$ \( C3 J4 @③、使用spi_message_add_tail函数将前面设置好的spi_transfer添加到spi_message队列中。
+ X. {! t* ^1 X: n④、使用spi_sync函数完成SPI数据同步传输。
6 X  j. [7 }7 N  y1 O& z通过SPI进行n个字节的数据发送和接收的示例代码如下所示:
, P1 w7 z' }5 B+ R4 Q  U' J/ a& P$ v
  1. 示例代码44.4.2.3 SPI数据读写操作
    7 y" Z$ X) T- W1 A% i* w3 f) f
  2. /* SPI多字节发送 */# Y* {' W; \! {7 ?4 R# W+ S
  3. static int spi_send(struct spi_device *spi, u8 *buf, int len)3 p; @# [  G! s! g- V& T% r8 ]
  4. {
    , L2 j. Z3 W8 ^2 e
  5.     int ret;
    9 g7 C  B* S& z; j% M6 n  M
  6.     struct spi_message m;
    & q$ l# `6 W2 z3 e3 Q
  7. 4 }) n1 v+ n/ J
  8.     struct spi_transfer t = {6 ]( N- M* }8 x/ ^/ \
  9.         .tx_buf = buf,
    - V$ w2 T- O" O7 ?/ C& L6 @
  10.         .len = len,; t7 p: V7 L5 H  X  l0 D
  11.     };
    6 a! c4 ]: @% S/ l& O  c

  12. 0 r: F$ E+ j$ b
  13.     spi_message_init(&m);               /* 初始化spi_message */" M! h9 X6 c0 t0 o- Q. h# E! R, f
  14.     spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
    % Y7 [$ S. c; N4 n+ F
  15.     ret = spi_sync(spi, &m);            /* 同步传输 */; b; V5 f: C% K
  16.     return ret;8 ]/ A# t  U2 ~, O- }* x2 |
  17. }1 @! H) k  ~9 t6 d% `- R" x

  18. - {$ l- P4 ?' P& T6 @7 I
  19. /* SPI多字节接收 */
    5 b8 x$ t, n6 c- G# Q5 S
  20. static int spi_receive(struct spi_device *spi, u8 *buf, int len)5 r0 C2 [2 m, ]0 D8 x0 P: }
  21. {7 G8 w5 c" D5 P) Z+ }
  22.     int ret;5 e, H% V- y8 W2 U$ W4 \5 ]
  23.     struct spi_message m;
    : H& V9 |6 }9 K

  24. , @- A) ?4 d' X9 Z
  25.     struct spi_transfer t = {! |# `  o# B: I% z
  26.         .rx_buf = buf,
      I6 d$ J8 L/ Q% }6 t2 P
  27.         .len = len,! r" i$ h1 B# M, d  q
  28.     };
    ! {* [% f. e" J8 _8 j- g8 ~

  29. 0 ~! i! ?' E8 _! a# l& f
  30.     spi_message_init(&m);               /* 初始化spi_message */
    " E" ~) Q& }1 t
  31.     spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */; B. f, V4 e) h# ?% C0 v8 s$ l' [
  32.     ret = spi_sync(spi, &m);            /* 同步传输 */
    6 x/ P0 R3 W$ T7 a
  33.     return ret;
    ; F2 x' k! B0 P, Z2 G% u
  34. };
复制代码
% x6 `% V& p5 I& _; d6 c1 J; L
44.5 硬件原理图分析; m& f8 h5 [/ e5 @$ }: c
STM32MP1开发板上ICM20608原理如图44.5.1所示:+ W0 M6 |% d8 o* T/ j; P7 S

1 @4 ?# A0 A0 }6 g* z 6e2f1696fc474107a5fdc85bd0d750bc.png ; r4 O- Q1 G7 c7 g: b

# ]) n+ c* s  b6 h7 w* s4 c, a2 B图44.5.1 ICM-20608 原理图
; f. X9 P/ n& d9 P( m5 w图44.5.1这是ICM-20608的硬件原理图。正点原子STM32MP1开发板的PZ0~3分别连接到ICM-20608的SCK、SDA、AD0和CS。其中6D_INT为ICM20608的中断引脚,连接到PA14引脚上,在本章实验上没有用到ICM20608的中断引脚。
4 C3 A( U5 ~1 T
* O! u7 O, x- }$ y7 ^2 ^44.6 实验程序编写' r# L7 k5 S4 K$ \7 {
本实验对应的例程路径为:开发板光盘1、程序源码2、Linux驱动例程22_spi。
! B8 @: i6 ~" V0 V0 |. p( @4 w44.6.1 修改设备树/ W5 b+ P8 |) D) ~. v, A( s: o& s
1、添加或者查找ICM20608所使用的IO的pinmux配置
; M/ v) X  _  i' D5 F6 T首先在stm32mp15-pinctrl.dtsi文件中添加IO配置信息,ICM20608连接到了STM32MP157的SPI1接口,因此先在stm32mp15-pinctrl.dtsi里面搜索一下,看看有没有SPI1接口引脚配置(在本教程中,默认是有的)。如果没有的话就自行添加,有的话就检查一下SPI1接口的引脚配置是否和自己所使用的硬件一致,不一致的话就要修改。修改后的引脚信息如下所示:% B: a* h; j; [3 I8 t* P- U& e

9 V* S  c- Y! g- F8 _: x4 ?
  1. 示例代码44.6.1.1 spi1的pinmux配置7 q3 U( U- ^8 T1 \3 c' ^
  2. 1  spi1_pins_a: spi1-0 {1 \' V4 S7 M' \8 r' `! j
  3. 2   pins1 {  |* {! h. L  @# l5 A) O8 o6 g
  4. 3       pinmux =         <STM32_PINMUX('Z', 0, AF5)>,         /* SPI1_SCK         */, N  Q! |2 u, u# v
  5. 4                             <STM32_PINMUX('Z', 2, AF5)>;        /* SPI1_MOSI         */
    ; |' J# T, k% E* A
  6. 5       bias-disable;' E% D4 _' M5 Y' G2 a- c* }
  7. 6       drive-push-pull;* @. S9 P4 T; f! x% G1 u1 a$ Y
  8. 7       slew-rate = <1>;
    4 a& |- U5 @5 ^4 l' y) m
  9. 8   };
    . K/ @3 e2 u( [7 t# d
  10. 9  
    % [3 x9 n" Z8 F5 p# @
  11. 10  pins2 {
    % q! u# S, C4 C. D% R; ?
  12. 11      pinmux = <STM32_PINMUX('Z', 1, AF5)>;                 /* SPI1_MISO         */
    7 Y4 D! Q9 _- s- \" K+ ]! E; e
  13. 12      bias-disable;
    * }& W  }8 y, X5 B( p9 Z
  14. 13  };
    3 y) f5 R0 R0 P  m& R: e
  15. 14
      b3 v9 E( }! V' I& O
  16. 15  pins3 {
    9 y; r0 O4 d5 o  u! ^5 \2 b# @
  17. 16      pinmux = <STM32_PINMUX('Z', 3, GPIO)>;          /* SPI1_NSS         */
    ; y% d& x5 K2 ]$ f3 x
  18. 17          drive-push-pull;
    + r* x& n! J% l; @, I! N# y
  19. 18          bias-pull-up;
      l# S2 _0 o. h& [" V/ l
  20. 19      output-high;
    0 L! c2 ?" z: U- N* l0 _& D
  21. 20          slew-rate = <0>;7 ~/ c( m# U- Z
  22. 21  };1 k* k% l; l5 L
  23. 22 };
    ; x8 R8 w1 k5 H  h5 M# D
  24. 23
    - y8 Q4 d$ E5 V2 t  G  I
  25. 24 spi1_sleep_pins_a: spi1-sleep-0 {  _6 q4 K2 [5 U+ Z/ g0 E
  26. 25  pins {
    " e2 i; I6 L% X1 D4 V  o
  27. 26      pinmux = <STM32_PINMUX('Z', 0, ANALOG)>,         /* SPI1_SCK         */
    & V/ V$ k, T9 I7 ^* _
  28. 27           <STM32_PINMUX('Z', 1, ANALOG)>,                 /* SPI1_MISO         */, ]0 l; {& @% @2 J+ {
  29. 28           <STM32_PINMUX('Z', 2, ANALOG)>,                 /* SPI1_MOSI         *// O. R) b4 u5 N1 _
  30. 29           <STM32_PINMUX('Z', 3, ANALOG)>;                 /* SPI1_NSS         */! q* m% S7 s4 I" c2 E
  31. 30  };
    0 I5 ^& e  U6 o/ |: e) y6 J0 ~
  32. 31 };
复制代码
- C: Z: E6 ?& z$ B% j1 P' l: b/ y' Y
示例代码44.6.3.1里15~21行是设置ICM20608的片选信号,直接复用为GPIO,也就是使用软件片选。
/ _# A$ }. Z0 |5 T- o
8 ^3 M) ?* i$ [/ B8 h3 N2、在SPI1节点下添加pinmux并追加icm20608子节点
3 ]: G, h$ f2 j! l3 g$ U, b在stm32mp157d-atk.dts文件,追加SPI1节点,追加如下所示内容:
- ?$ b5 {; _. ]* l9 t' _4 I
; ~3 A1 g- F) N- E+ e
  1. 示例代码44.6.1.2追加内容的spi1节点
    2 E0 u, Z& {/ @0 }9 q+ v
  2. 1   &spi1 {# G4 ^) N! K* O: [  O4 T4 O
  3. 2       pinctrl-names = "default", "sleep";
    ) ]' x, {' w  e2 `' _2 ]  q
  4. 3       pinctrl-0 = <&spi1_pins_a>;) n# A9 k! M" o4 m6 u
  5. 4       pinctrl-1 = <&spi1_sleep_pins_a>;
    0 ]* ?% v( j, U4 N2 r
  6. 5       cs-gpios = <&gpioz 3 GPIO_ACTIVE_LOW>;3 L  k* L+ t" ]# `4 u, w
  7. 6       status = "okay";; R" K" |2 t( Y  q
  8. 7 + i8 T/ @; D- I4 e. ^
  9. 8       spidev: icm20608@0 {0 j% l* l  t* e8 O$ U) g
  10. 9           compatible = "alientek,icm20608";% I5 }1 |; X4 ]& A' {  B
  11. 10          reg = <0>; /* CS #0 */
    - b* M; b. H( A) c8 _
  12. 11          spi-max-frequency = <80000000>;
    6 j6 S1 A6 l0 t3 O8 R
  13. 12      };
    . X+ Y6 \' d  O0 j9 v
  14. 13  };
    4 ~& U( G! M% B+ [$ v# A) u" h% B

  15. ! C1 c4 D0 [7 k; w" I& H
复制代码
. \: p5 T  n0 L; s
第2~4行,设置IO要使用的pinmux配置。
3 N0 R( i" J3 J/ n% `  D- F第5行,“cs-gpios”属性是用来设置SPI的片选引脚。SPI主机驱动就会根据此属性去控制设备的片选引脚,本实验我们使用PZ3作为片选引脚。关于cs-gpios属性的详细描述可以参考绑定文档:Documentation/devicetree/bindings/spi/spi-controller.yaml。如果一个SPI接口下连接了多个SPI芯片,那么cs-gpios属性里面就要添加所有SPI芯片的片选信号,比如:
/ d3 E/ X* L$ d3 l5 u3 \; h; Lcs-gpios = <&gpio1 0 0>, <&gpio1 1 0>, <&gpio1 2 0>, <&gpio1 3 0>;
$ l+ q3 L8 Y3 @, T& h6 i1 O上述描述说明此时SPI节点下有4个SPI芯片,第一个SPI芯片的片选引脚为gpio1_0,一以此类推。5 j  c; m. a. B" X- A( Y
第8~12行,icm20608设备子节点,从第5行的cs-gpios节点可以看出,此时SPI接口下只有一个ICM20608,而且ICM20608的片选索引为0,因此@后面为0。注意,@后面的数字就是对应SPI芯片片选信号在cs-gpios中的索引值。第9行设置节点属性兼容值为“alientek,icm20608”,第10行reg属性表示icm20608所使用的片选,和第8行@后面的数字含义相同,这里也设置为0,也就是cs-gpios属性中的第一个片选信号。第11行设置SPI最大时钟频率为8MHz,这是ICM20608的SPI接口所能支持的最大的时钟频率。
9 T( a* g+ C0 h
1 A5 ?* ~$ r- G8 S6 p  m44.6.2 编写ICM20608驱动3 e. b5 O. }$ u
新建名为“22_spi”的文件夹,然后在22_spi文件夹里面创建vscode工程,工作区命名为“spi”。工程创建好以后新建icm20608.c和icm20608reg.h这两个文件,icm20608.c为ICM20608的驱动代码,icm20608reg.h是ICM20608寄存器头文件。先在icm20608reg.h中定义好ICM20608的寄存器,输入如下内容(有省略,完整的内容请参考例程):
& n1 U1 M9 m" t7 F, ^* r% ]2 D/ V, p: I7 a" D5 v
  1. 示例代码44.6.2.1 icm20608reg.h文件内容
    1 C% l# |9 j8 p; h& I7 V
  2. 1  #ifndef ICM20608_H
    + o4 E( N6 x4 P8 ]* J* {
  3. 2  #define ICM20608_H
    % t$ k$ c# `( Z# }
  4. 3  /***************************************************************
    ! w; P  b7 e/ k4 K# N
  5. 4  Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
    ; x& d4 w% m! m9 D, |1 `2 z1 Y
  6. 5  文件名         : icm20608reg.h1 Y- j1 E) m5 y. K
  7. 6  作者            : 左忠凯
    : s1 ~4 `1 V2 p9 J$ ?
  8. 7  版本            : V1.0
    / b3 ~7 R) Q* A$ E+ \
  9. 8  描述           : ICM20608寄存器地址描述头文件3 V# n5 g3 g6 e/ F; {: W" ?
  10. 9  其他             : 无
      _1 F; K1 |: T3 ]( w
  11. 10 论坛            : <a href="http://www.openedv.com" target="_blank">www.openedv.com</a>
    7 s& v/ C0 }! n$ d
  12. 11 日志            : 初版V1.0 2019/9/2 左忠凯创建: @- N9 y2 a2 `
  13. 12 ***************************************************************/; Y, u/ ^9 n9 c4 U
  14. 13 #define ICM20608G_ID         0XAF    /* ID值 */
    1 r, `" f" t  W
  15. 14 #define ICM20608D_ID         0XAE    /* ID值 */
    $ M9 L& u0 t& J0 A7 C8 F3 a/ V
  16. 15
    % G, q# f. j* t! a
  17. 16 /* ICM20608寄存器
    8 ^. \# {) ^4 i! c
  18. 17  *复位后所有寄存器地址都为0,除了
    2 m* x( w# b$ n0 N9 A2 ^5 b
  19. 18  *Register 107(0X6B) Power Management 1          = 0x40
    ! R! O+ R8 I! S) \% H' h  J  ]  v
  20. 19  *Register 117(0X75) WHO_AM_I                     = 0xAF或0xAE' B2 Y: B$ |: r4 b4 C2 Y, p* ~
  21. 20  */- ^1 A, N8 W& x1 I2 F
  22. 21 /* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */
    ( R# R- @2 ~6 t; p; P- p& W
  23. 22 #define  ICM20_SELF_TEST_X_GYRO            0x00. Z9 v0 z  ^! h% G1 ~4 j
  24. 23 #define  ICM20_SELF_TEST_Y_GYRO             0x01
    ; E' C3 b7 I% J2 Z
  25. 24 #define  ICM20_SELF_TEST_Z_GYRO            0x02, ^- ?' i, @, k4 {3 v
  26. 25 #define  ICM20_SELF_TEST_X_ACCEL           0x0D
    0 U8 I$ p3 j2 z5 j
  27. 26 #define  ICM20_SELF_TEST_Y_ACCEL            0x0E7 w# }7 F% r) s
  28. 27 #define  ICM20_SELF_TEST_Z_ACCEL            0x0F+ n6 m# j% a' K
  29. ......
    + _0 o1 G3 x, [: g* j: ^$ z
  30. 88 #endif
复制代码
) u; Z# T) Y3 R7 z$ V# ~- n
接下来继续编写icm20608.c文件,因为icm20608.c文件内容比较长,因此这里就将其分开来讲解。
+ u. `1 o$ E( S. g1 K  u' |6 b: h
1、icm20608设备结构体创建8 B5 d" O3 N7 r0 p* E1 ^
首先创建一个icm20608设备结构体,如下所示:* t  I0 B5 _% p0 g" b

( j# m/ J( E+ i; l9 Q
  1. 示例代码44.6.2.2 icm20608设备结构体创建
    ; E: h# J9 x! Q1 x8 @9 _( M) P
  2. 1   #include <linux/spi/spi.h>/ |( i, l2 p$ _# }; x
  3. 2   #include <linux/kernel.h>9 a2 o$ p+ b* O' s8 {
  4. 3   #include <linux/module.h>( @% y# Y: W/ A& P# t7 C1 D+ {
  5. ......2 T8 [* w, K- [: E9 U, E9 c8 j
  6. 14
    - q5 f% l* B: \% A$ u
  7. 15  #define ICM20608_CNT    1, c, i- n( D9 ]( i' C: m) D
  8. 16  #define ICM20608_NAME   "icm20608"
    - \) t0 D6 y+ y* }
  9. 17
    - ^' c% ~% f: r! X: _* i" m
  10. 18  struct icm20608_dev {
    ( h: x$ t: q1 t) p; b. b- E
  11. 19      struct spi_device *spi;           /* spi设备                         */1 k; a2 u3 C# T! u3 R& f3 p
  12. 20      dev_t devid;                        /* 设备号                             */" f4 N  l; @4 q9 g& T
  13. 21      struct cdev cdev;                   /* cdev                             */0 O6 E% t) O2 l" Z  H
  14. 22      struct class *class;               /* 类                                      */
    : S- M8 a2 y. d% b. ?
  15. 23      struct device *device;              /* 设备                                    */# f) D# b# T" {/ A
  16. 24      struct device_node  *nd;            /* 设备节点                                 */
    6 _5 m+ x! S5 B& h, e3 n
  17. 25      signed int gyro_x_adc;              /* 陀螺仪X轴原始值           */
    ! E3 z, U! D: j9 c2 k
  18. 26      signed int gyro_y_adc;              /* 陀螺仪Y轴原始值            */+ D# W6 ]9 u: Z7 D5 s! V7 w5 i
  19. 27      signed int gyro_z_adc;              /* 陀螺仪Z轴原始值             */( m8 q" C, g* U- Y' w$ ~8 E
  20. 28      signed int accel_x_adc;             /* 加速度计X轴原始值          */
    9 G- f+ q: v# f& R# l' L% A
  21. 29      signed int accel_y_adc;             /* 加速度计Y轴原始值          */
    - S+ Q8 {' x, c6 j, w% B- V9 x
  22. 30      signed int accel_z_adc;             /* 加速度计Z轴原始值          */: o; A6 O  Q0 E: I0 b# q
  23. 31      signed int temp_adc;                /* 温度原始值                  */& M% F5 b9 A" Y+ e
  24. 32  };
    & a% \* k* [* v7 Q! Y0 _
复制代码
4 N4 a3 [& n: t2 m
icm20608的设备结构体icm20608_dev没什么好讲的,重点看一下第19行的spi,对于SPI设备驱动来讲最核心的就是spi_device。probe函数会向驱动提供当前SPI设备对应的spi_device,因此把这个结构体赋值到spi成员里,我们可以在字符串设备里操作spi成员就可以操作对应的SPI设备。
. q' D0 e! ?! I- x2、icm20608的spi_driver注册与注销
# w) S% ~0 ^) O1 b" ?6 _8 b9 J8 h对于SPI设备驱动,首先就是要初始化并向系统注册spi_driver,icm20608的spi_driver初始化、注册与注销代码如下:
- v( f6 {. A) T) T/ V3 r% K0 U' H. ^' w! k4 _9 Z5 P) w) a  K
  1. 示例代码44.6.2.3 icm20608的spi_driver初始化、注册与注销
    " n& a! M6 t1 M! Z5 L
  2. 1   /* 传统匹配方式ID列表 */
    + e& X& H! @) d
  3. 2   static const struct spi_device_id icm20608_id[] = {1 j) ?1 \& \: f" y; `
  4. 3       {"alientek,icm20608", 0},
    6 a7 C7 Z! ]- C  D! l& _1 Z
  5. 4       {}6 F  |5 o3 |8 m! t% g' k8 v
  6. 5   };) e* {8 s6 {4 N" w: m1 O
  7. 6
    7 e* w9 a0 e5 m% Y2 h
  8. 7   /* 设备树匹配列表 */2 l! I, g/ V% k
  9. 8   static const struct of_device_id icm20608_of_match[] = {
    1 p+ R( k$ @! C; s+ S6 u1 ]$ h9 K
  10. 9       { .compatible = "alientek,icm20608" },8 a7 B" C0 l+ I: c# d0 e+ }
  11. 10      { /* Sentinel */ }
    - ]3 Y: X- B& [7 D, K- I
  12. 11  };* C/ E! w" J* A% K5 G
  13. 12
    & n2 D: Z& s8 ^0 G$ O" u4 ]7 Q
  14. 13  /* SPI驱动结构体 */0 d0 _3 L/ R8 Z" W) z9 e6 n5 v
  15. 14  static struct spi_driver icm20608_driver = {* F: r  \9 x2 U) j& R5 G
  16. 15      .probe = icm20608_probe,
    % I( P! Z+ X% ?3 O& K6 U
  17. 16      .remove = icm20608_remove,
    5 \5 d. f& L+ e: {- k
  18. 17      .driver = {: S2 G7 D  q: `% s; w/ U# G. R2 `
  19. 18              .owner = THIS_MODULE,1 E$ j& c( v" V8 ~& m* R# K
  20. 19              .name = "icm20608",
    3 j. x4 r/ y; p" [0 R
  21. 20              .of_match_table = icm20608_of_match,3 k4 u5 n# \9 j6 D7 P4 L  I: S
  22. 21             },
    4 d& \$ |$ K  A$ b1 p1 }6 y
  23. 22      .id_table = icm20608_id,
    + X+ ?9 N  A0 B5 x2 m4 s' o7 T
  24. 23  };
    ; f3 N5 A, W9 K2 _0 m
  25. 24
    4 Q& q, v  _) G# K/ E
  26. 25  /*" e* t% J: G1 \7 {0 \' X2 r
  27. 26   * @description         : 驱动入口函数
    % P! S& g7 Y, `$ Y. j
  28. 27   * @param               : 无
    # A, n) }# C! o% a
  29. 28   * @return              : 无
    9 S: u% T! u! d5 z5 ]+ W
  30. 29   */
    4 A+ f1 C3 O9 I% S3 R. h3 W
  31. 30  static int __init icm20608_init(void), P1 y3 J& K8 }) a. n/ r
  32. 31  {
    . h7 Q6 v; N: f) L7 I6 ~" y
  33. 32      return spi_register_driver(&icm20608_driver);7 G* Y, h2 r, t6 I
  34. 33  }
    # k4 M9 L1 U' e
  35. 346 t4 o1 h9 G: Q+ D3 ^
  36. 35  /*
    2 {6 c. G3 r$ a8 W7 h
  37. 36   * @description         : 驱动出口函数' F5 M* Q/ q" K' t
  38. 37   * @param               : 无' s9 i0 k' a# i4 a3 w
  39. 38   * @return              : 无
    $ W4 I: Z. z  Q0 N
  40. 39   */
    9 @9 a8 x/ r3 u9 n
  41. 40  static void __exit icm20608_exit(void)7 y8 W+ \" V( ^0 k
  42. 41  {
    * D9 v8 S; Z+ X! c6 g. [* R6 ^9 j# `
  43. 42      spi_unregister_driver(&icm20608_driver);2 t, `0 e& p- B" _7 v' @
  44. 43  }3 j: ~7 d5 Q& m
  45. 44
    , K9 s4 n2 Z# ]
  46. 45  module_init(icm20608_init);
    0 h. |. B+ K" X) t" u8 {
  47. 46  module_exit(icm20608_exit);
    & \1 p1 B8 k, p7 _8 C" i8 {
  48. 47  MODULE_LICENSE("GPL");! p9 K3 ?' o* N' |, |  f4 g
  49. 48  MODULE_AUTHOR("ALIENTEK");! k+ T! v/ a0 a+ P/ l
  50. 49  MODULE_INFO(intree, "Y");
    & B7 G7 K3 m% y! F# Q
复制代码

9 Q& a7 G$ {$ o9 w5 F9 P0 o第2~5行,传统的设备和驱动匹配表。) P2 Y7 Z6 _" g2 M
第8~11行,设备树的设备与驱动匹配表,这里只有一个匹配项:“alientek,icm20608”。
* N% R9 [$ @2 ]3 B$ n( T% K第14~23行,icm20608的spi_driver结构体变量,当icm20608设备和此驱动匹配成功以后第15行的icm20608_probe函数就会执行。同样的,当注销此驱动的时候icm20608_remove函数会执行。
# R! q) E7 n- t, @- i- ~第30~33行,icm20608_init函数为icm20608的驱动入口函数,在此函数中使用spi_register_driver向Linux系统注册上面定义的icm20608_driver。
# x8 N/ L! a8 w4 M第40~43行,icm20608_exit函数为icm20608的驱动出口函数,在此函数中使用spi_unregister_driver注销掉前面注册的icm20608_driver。
! i0 U; A( H# G/ B2 r+ p+ ~
  ?+ E+ @# e3 `9 G+ h8 [2 R3、probe&remove函数1 `' |. c9 l! ^+ o4 A
icm20608_driver中的probe和remove函数内容如下所示:
3 h9 e  P; m  A1 E2 m& l
* w2 X. M. q1 J- k7 s  c, C* z
  1. 示例代码44.6.3.4 probe和remove函数
    " F4 Y, u8 x" u/ H3 _4 I# S! }/ P' J
  2. 1  /*$ |8 p/ f. r1 O
  3. 2    * @description         : spi驱动的probe函数,当驱动与设备匹配以后此函数# r1 T# Z: X; S/ [  f3 }! _
  4. 3    *                    就会执行
    # s( T( B( b$ j$ e, k
  5. 4    * @param - spi          : spi设备2 |# D5 d  w2 G$ ?5 K
  6. 5    */
    + B6 @) D- Z+ H+ Y: H
  7. 6  static int icm20608_probe(struct spi_device *spi)
    5 b. l% G# }4 t% d# P
  8. 7  {
    ( V7 z, I$ ^3 d' r& G) L% O+ a5 _
  9. 8           int ret;1 ^+ d  ?) |2 M5 G
  10. 9           struct icm20608_dev *icm20608dev;- r, i: |; f( p' w, @1 F' u
  11. 10  ; t8 o% D6 l" h1 Y( H
  12. 11          /* 分配icm20608dev对象的空间 */" l# |% {7 R7 D7 Y8 {0 C
  13. 12          icm20608dev = devm_kzalloc(&spi->dev, sizeof(*icm20608dev), 0 S3 n6 G- E' N! P
  14.                                                                   GFP_KERNEL);3 B# {% X: [4 K
  15. 13          if(!icm20608dev)
    " L, n# o" d  w4 E! d; U
  16. 14              return -ENOMEM;
    9 [- _% \( S. u; t: E0 c
  17. 15      3 c; X) U9 o( ]$ n
  18. 16          /* 注册字符设备驱动 */
    5 v& G) c' ?( a1 D. V* @
  19. 17          /* 1、创建设备号 */; H6 W0 R7 k3 R# h) |3 B: X
  20. 18                  ret = alloc_chrdev_region(&icm20608dev->devid, 0, ICM20608_CNT,
    6 i3 K  A, p; E
  21.                                                                                  ICM20608_NAME);. K: L  n! L. c3 A, E: C
  22. 19          if(ret < 0) {* `# q% O* A1 H* [0 L
  23. 20              pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n",
    $ Q! [6 ^; U& N4 H. h0 J7 }
  24.                                 ICM20608_NAME, ret);1 g1 S- Y& z  [  k0 j1 b+ c
  25. 21                 return 0;
    - [8 P4 H/ _/ J+ N
  26. 22          }% n2 ~  g) }" f" P$ N3 P$ h
  27. 23
      K. G, v+ U3 N  `
  28. 24          /* 2、初始化cdev */
    ) w& O2 |' ?: O) g3 D2 m6 ~
  29. 25          icm20608dev->cdev.owner = THIS_MODULE;0 X% E: J' w0 B7 L+ |: [8 m
  30. 26          cdev_init(&icm20608dev->cdev, &icm20608_ops);8 b0 O1 e: c. U' }: ]
  31. 27  ) o, o0 ^: s2 r
  32. 28          /* 3、添加一个cdev */3 p; v6 Q* B: X0 R' e$ j
  33. 29          ret = cdev_add(&icm20608dev->cdev, icm20608dev->devid, + G3 t/ ^$ J9 i, F
  34.                                                  ICM20608_CNT);
    1 Q- L& H7 `' F
  35. 30          if(ret < 0) {
    7 Y$ g$ M  Y0 k4 S4 q
  36. 31              goto del_unregister;
    5 ~6 k0 ?! l; u) k2 o: d
  37. 32          }
    . E' C1 d2 a6 c0 u" W) y0 b- M7 y6 s
  38. 33  6 B  O, `+ w* ?. D
  39. 34          /* 4、创建类 */
    % ^+ q5 e8 D6 F/ e
  40. 35          icm20608dev->class = class_create(THIS_MODULE, ICM20608_NAME);
    + Q* m, J1 Z4 H6 \1 Q2 {
  41. 36          if (IS_ERR(icm20608dev->class)) {0 j2 U& \. f! k( Z
  42. 37              goto del_cdev;
    7 p/ j3 e: S; Z: Z" G
  43. 38          }4 ?8 x3 h9 X; O+ q6 o% Y
  44. 39 . P+ {8 g  C4 l1 h& T- A; l
  45. 40          /* 5、创建设备 */
    % [  {4 u4 }* e+ s
  46. 41          icm20608dev->device = device_create(icm20608dev->class, NULL,
    ' V" }# G2 e: M7 i' h+ e
  47.                                                         icm20608dev->devid, NULL, ICM20608_NAME);
    7 v3 N4 w+ Y& {
  48. 42          if (IS_ERR(icm20608dev->device)) {$ t  }# k8 }* N4 ]
  49. 43              goto destroy_class;6 w# d& ]) o& m1 s  b
  50. 44          }
    & v# d  M& ~! \2 H; T/ T5 [
  51. 45          icm20608dev->spi = spi;
    : V: x3 n) L7 `3 C) G
  52. 46  5 W  W" z' t  B1 w
  53. 47          /*初始化spi_device */
    0 A2 k$ p+ e$ l( w& |1 _9 `# e
  54. 48          spi->mode = SPI_MODE_0; /*MODE0,CPOL=0,CPHA=0*/% U' X. ?. h1 e4 q! Q
  55. 49          spi_setup(spi);
    - ]) r2 `) E8 H( u8 o  m
  56. 50  
    8 y  P: U7 A: U! ^% H% x
  57. 51          /* 初始化ICM20608内部寄存器 */
      e' R) Z! R0 E0 A  l+ Z% B
  58. 52          icm20608_reginit(icm20608dev);  
    5 A& m' k7 b9 @4 o1 a$ G9 O0 U% k- ?
  59. 53          /* 保存icm20608dev结构体 */; `* G7 t& l( G" J
  60. 54          spi_set_drvdata(spi, icm20608dev);
    $ R2 W; x6 k2 T9 E) Z2 R* |" v! ]
  61. 55
    7 s- i& z1 S/ w" h/ N6 X+ m
  62. 56          return 0;
    ( K6 S7 q/ p" z- X: E& q5 x0 V9 A
  63. 57         destroy_class:
    , k9 F+ y$ M# T* `$ W
  64. 58          device_destroy(icm20608dev->class, icm20608dev->devid);: V7 k- y* @' `! f$ H4 c) c
  65. 59         del_cdev:
      `" _, S: D( Q) R  N( J  e4 s
  66. 60          cdev_del(&icm20608dev->cdev);+ ~1 }5 v6 m- K2 K7 l8 W
  67. 61         del_unregister:
    5 E$ [! H; }* ?6 S* k
  68. 62          unregister_chrdev_region(icm20608dev->devid, ICM20608_CNT);
    ' k4 M0 C5 k7 c* K! A& G* c
  69. 63          return -EIO;! z2 H& K, k2 c: [
  70. 64 }
    # ]1 @: O! x" S9 Z
  71. 65   n% c! h% M  C
  72. 66 /*1 ^9 ^/ h2 {$ E) L3 {& J" d, L0 S& V
  73. 67  * @description        : spi驱动的remove函数,移除spi驱动的时候此函数会执行
    ! h* J) b# d1 v7 {' a
  74. 68  * @param - spi         : spi设备
    5 U; y/ S, o2 A4 v" `4 U
  75. 69  * @return                 : 0,成功;其他负值,失败- ^3 X2 Q% J0 j" v2 i, `
  76. 70  */* {; ^4 W# f, @0 t
  77. 71 static int icm20608_remove(struct spi_device *spi)2 [$ L* }  z. r  v" ?& I* F  g
  78. 72 {
    2 h8 I% R# {5 j; N
  79. 73          struct icm20608_dev *icm20608dev = spi_get_drvdata(spi);
    ! b8 h9 X! z, A' ]7 q1 T: k
  80. 74          /* 注销字符设备驱动 */
    / f7 _$ m0 E4 ?7 L
  81. 75          /* 1、删除cdev */% |6 q- u5 s* E1 Z$ r# k9 F1 ^) o
  82. 76          cdev_del(&icm20608dev->cdev);
      g* B+ H3 {$ `+ L) m
  83. 77          /* 2、注销设备号 */1 V8 k! S+ o2 I( M( |
  84. 78          unregister_chrdev_region(icm20608dev->devid, ICM20608_CNT);
    8 z/ V8 r3 [. S' y
  85. 79          /* 3、注销设备 */; F; I) R  @3 Y0 |( R4 t
  86. 80          device_destroy(icm20608dev->class, icm20608dev->devid);
    % U# ], F: q. M' E
  87. 81          /* 4、注销类 */7 t; ?# h4 c5 r( q9 @; Y
  88. 82          class_destroy(icm20608dev->class); 3 X, }: I9 m( G* _9 a# d% H2 L
  89. 83          return 0;3 f& \* Z( R' b  M
  90. 84 }
复制代码
9 A+ A5 k# m4 X/ \
第6~64行,probe函数,当设备与驱动匹配成功以后此函数就会执行。第12行就是给自定义的结构体申请空间。第25~44行都是标准的注册字符设备驱动。注意我们这边驱动没有去读取“cs-gpios”属性,那是因为SPI的核心会自动控制的,我们不用关心它。8 E" ~2 G) k" |5 ~; |* K
第45行,将probe函数的spi_device参数赋值给我们自定义的spi成员变量,也就是保存spi_device结构体对象。
+ u9 G( D! b, Y第48行,设置SPI为模式0,也就是CPOL=0,CPHA=0。4 C. ^" L% e7 R. I- g
第49行,设置好spi_device以后需要使用spi_setup配置一下。4 |/ k. Z+ G7 Y) y0 A
第52行,调用icm20608_reginit函数初始化ICM20608,主要是初始化ICM20608指定寄存器。
% l: T1 l3 r& ]& T+ L! y: g  b第54行,icm20608dev变量是在probe函数里申请的内存空间,在remove函数需要释放掉。这里使用spi_set_drvdata函数将icm20608dev地址保存起来,后面再remove函数里面可以通过spi_get_drvdata函数获取到icm20608dev地址。
: D: l  K% R, g7 d8 V& |第71~84行,icm20608_remove函数,注销驱动的时候此函数就会执行。我们可以使用spi_get_drvdata函数来获取icm20608dev的地址。5 ^7 |6 H  i9 q9 n

. I+ W7 B! N& J* p6 w; m, Z9 D: o8 @4、icm20608寄存器读写与初始化) n  q5 Y; X6 x
SPI驱动最终是通过读写icm20608的寄存器来实现的,因此需要编写相应的寄存器读写函数,并且使用这些读写函数来完成对icm20608的初始化。icm20608的寄存器读写以及初始化代码如下:( k" x5 e( A$ N

9 @8 O* u4 N7 V
  1. 示例代码44.6.2.5 icm20608寄存器读写以及初始化3 ~: J4 z! M0 L9 a2 ?6 ^
  2. 1   /*6 e) S7 n8 O1 D# {* T( H* x" Q
  3. 2    * @description         : 从icm20608读取多个寄存器数据
    $ e1 D- R+ {$ i' E. `
  4. 3    * @param – dev        : icm20608设备
    2 h8 z3 N% O+ F3 K' |5 O$ n* k
  5. 4    * @param – reg        : 要读取的寄存器首地址6 q* ~# X7 `! j5 K' B  r
  6. 5    * @param – val        : 读取到的数据$ ~- t( m& B0 A% h9 n0 l
  7. 6    * @param – len        : 要读取的数据长度
    ; W9 t8 h( y# _* m
  8. 7    * @return              : 操作结果
    $ V% S# s: O. Q
  9. 8    */' d: _4 A% X9 i0 u2 z! G9 I
  10. 9   static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, % b' W6 W, S$ P% m& ]$ O
  11. void *buf, int len)
    , S2 o7 F/ a( e- l5 z
  12. 10  {+ g4 _4 `0 W+ @: t' U. }9 d
  13. 11
    ( q: a- U% h$ {! ~" o
  14. 12      int ret = -1;& p) A4 {3 }: B% o: v
  15. 13      unsigned char txdata[1];
    % m7 f4 r' o$ d) `- V. n
  16. 14      unsigned char * rxdata;
    ! r" I1 q; [& ~
  17. 15      struct spi_message m;
    1 P0 t$ b, f# f. L, g7 g
  18. 16      struct spi_transfer *t;7 o  Y" x3 {: u: S& Q
  19. 17      struct spi_device *spi = (struct spi_device *)dev->spi;
    8 p7 x0 [5 a9 F) y
  20. 18      0 t& @3 o% x3 H# J4 z3 y. I+ e" l
  21. 19      t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);/* 申请内存*/
    6 l+ P7 \5 Z9 [0 _0 _2 Q% \* ]
  22. 20      if(!t) {# P( v) s) a4 J; P4 X. x
  23. 21          return -ENOMEM;! h! q7 Z1 i% P+ X7 q2 g/ ^
  24. 22      }( J) M  z& u  n$ O- y3 ^7 S2 v
  25. 23      rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL);   /* 申请内存 */
    , z5 c+ T% V5 P8 e* R1 O- }$ `
  26. 24      if(!rxdata) {
    5 T) D6 e% z% y( T9 d1 ^
  27. 25          goto out1;) n& v8 U$ |) F& d
  28. 26      }
    $ h% J" O- l8 e* T  D' W
  29. 27      /* 一共发送len+1个字节的数据,第一个字节为: X) R) r  C& d: D% i! n- `' Z
  30. 28      寄存器首地址,一共要读取len个字节长度的数据,*/
    # `) [- R( U- y; Q
  31. 29      txdata[0] = reg | 0x80;         /* 写数据的时候首寄存器地址bit8要置1         */           
    , Q$ |6 q" C  d% r
  32. 30      t->tx_buf = txdata;              /* 要发送的数据                                                         */& O5 J6 d- T- j* G- y" i/ o
  33. 31      t->rx_buf = rxdata;             /* 要读取的数据                                                         */
    ' S  b% ~! ^8 R2 B" u! C& A
  34. 32      t->len = len+1;                  /* t->len=发送的长度+读取的长度                 */
    # d, _/ u) ]6 @" q; L9 h7 ?
  35. 33      spi_message_init(&m);           /* 初始化spi_message                                         */6 u, T+ Q) j8 [$ L
  36. 34      spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message*/6 V" ^; j& n  H
  37. 35      ret = spi_sync(spi, &m);        /* 同步发送                                                                 */7 u1 \  S' p, H# z* b! z
  38. 36      if(ret) {) R0 O: J" g$ n# m# E6 y. ]
  39. 37          goto out2;# T3 {' u: W) W4 X. d" G
  40. 38      }
    ! q3 B) L3 y- h+ V
  41. 39      
    6 q( E: ^- c/ N% W( k/ f+ Y, s
  42. 40      memcpy(buf , rxdata+1, len);          /* 只需要读取的数据         */
    + P8 J8 b; W% y1 I) G' L; p
  43. 41
      @& W) Q0 ~" h
  44. 42  out2:; A  d4 X% a5 V; c
  45. 43      kfree(rxdata);                          /* 释放内存                         */8 q8 Z/ ^  [% S( s( A
  46. 44  out1:   
    5 h; w- Y- k: o8 i: ^" Z: r3 q
  47. 45      kfree(t);                               /* 释放内存                         */
      P& m( x' Y8 Q% h& M, V' q4 E
  48. 46      " v  w8 p6 s- H0 H- Y6 T; V# _
  49. 47      return ret;7 `5 y$ j7 z% a! ?8 @
  50. 48  }
    / c7 A9 s4 y8 {  N
  51. 49 ; n8 ]6 P" J0 u
  52. 50  /*% `% u6 A  C: n5 {* N
  53. 51   * @description : 向icm20608多个寄存器写入数据" A! D+ B# @; t0 Q& j# M  M
  54. 52   * @param - dev:  icm20608设备
    & E4 D  V. f2 v8 g! G
  55. 53   * @param - reg:  要写入的寄存器首地址; j3 j, A# z: z4 t1 u6 T
  56. 54   * @param - val:  要写入的数据缓冲区
    ! I8 W; j3 l) E  p
  57. 55   * @param - len:  要写入的数据长度" G4 k9 z) r/ G1 C
  58. 56   * @return    :   操作结果
    / K1 O; V+ H* {! \7 b; y
  59. 57   */
    9 O! o3 a* W! A0 B
  60. 58  static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg,
    8 k' T) h7 V/ ?; d/ @0 M% u' }# ~
  61.                                                                                 u8 *buf, u8 len)4 k) v/ R; D6 {7 O
  62. 59  {. R1 o( D  z% v! f" z0 j  A: Z* `& f
  63. 60      int ret = -1;( }: T6 T: g# G. d: V' g! [
  64. 61      unsigned char *txdata;
    2 v9 W4 V, B+ I. K6 @. \4 Y
  65. 62      struct spi_message m;1 W1 q- u* s( p2 }
  66. 63      struct spi_transfer *t;
    1 V3 r/ e; L( C: b, u. {% e8 B3 ^
  67. 64      struct spi_device *spi = (struct spi_device *)dev->spi;
    2 \6 V& b1 k$ H/ {$ E4 @
  68. 65      
    + s& }3 J8 s: Z% N- P# R1 @4 ^
  69. 66      t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);/* 申请内存*/
    ' P) x# ^) s) S' Y  R
  70. 67      if(!t) {, c6 a, D$ A* q# T/ {# U8 W& w
  71. 68          return -ENOMEM;
    6 \  g8 I8 [: @9 u+ A
  72. 69      }
    / W. ]: J& H( {) i: S1 B* k" I, g
  73. 70      9 F: i* v8 u0 j4 d" k7 \" ]. E
  74. 71      txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);4 N6 m$ c& ]- W- c6 U; g
  75. 72      if(!txdata) {: q) W+ C7 x& `2 s
  76. 73          goto out1;7 j7 G- ^& G" k3 p1 Z0 f) m( P
  77. 74      }
    % d* x4 E6 r4 `& A
  78. 75      / _9 R5 U% O# Y( s4 P% V. I5 C6 X; g
  79. 76      /* 一共发送len+1个字节的数据,第一个字节为
    : @7 l6 [, B* o; j5 ]
  80. 77      寄存器首地址,len为要写入的寄存器的集合,*/5 \+ o: Z2 E3 }) w$ e
  81. 78      *txdata = reg & ~0x80;                  /* 写数据的时候首寄存器地址bit8要清零 */0 D0 V6 ~; o- r3 v% u
  82. 79      memcpy(txdata+1, buf, len); /* 把len个数据拷贝到txdata里        */$ [% [3 U# L# O1 R- m6 I
  83. 80      t->tx_buf = txdata;                 /* 要发送的数据                                         */
    3 F* C  a% W$ y2 ?6 ^2 r0 R1 j
  84. 81      t->len = len+1;                     /* t->len=发送的长度+读取的长度        */
    3 `- H" n: |! m3 d, {6 `2 b! P: s
  85. 82      spi_message_init(&m);               /* 初始化spi_message                         */  ?+ R' ~0 Y" m( k
  86. 83      spi_message_add_tail(t, &m);/*添加到spi_message队列                 */9 ^3 E% k3 [* I4 X
  87. 84      ret = spi_sync(spi, &m);            /* 同步发送                                                 */9 R+ J/ ~5 v& K4 X# Y8 |
  88. 85      if(ret) {4 }. N  r) ?$ C+ p" F/ |* ~
  89. 86          goto out2;
    / }) \, L2 ^; W8 i- g0 t  Z
  90. 87      }
    4 v$ U- [% A/ d3 A" x7 }
  91. 88      
    ; l$ c) A: Y6 u0 G  H8 p$ b& E3 @6 Q
  92. 89  out2:- N' c+ e# A6 V) z
  93. 90      kfree(txdata);                      /* 释放内存 */2 J8 S( @  f* ~8 ]  _% S4 v
  94. 91  out1:
    % |3 Y* ^. U" S% O/ U5 s
  95. 92      kfree(t);                           /* 释放内存 */# I4 j! i! S8 X
  96. 93      return ret;* P2 Y& z# _0 i8 ^$ _* C4 d
  97. 94
    * r0 b8 H9 ]' A' m* V4 Y5 u* M
  98. 95  }
    2 @; I9 w6 `- Z8 F/ Z6 M
  99. 96
    + E* x/ [. ~6 m* Y0 Q6 r5 g
  100. 97  /*
      ?4 T8 f; g& m' |
  101. 98   * @description         : 读取icm20608指定寄存器值,读取一个寄存器
    * ^) r, j% c7 }
  102. 99   * @param – dev        : icm20608设备! l; l% s- i- N: S* z+ C4 c
  103. 100  * @param – reg        : 要读取的寄存器. j+ w- B2 `, P& O/ E# S# Z
  104. 101  * @return                    : 读取到的寄存器值
    / z3 ?2 ^9 V# Q3 V& Z  i7 q
  105. 102  */: C: m+ h' i6 }! K
  106. 103 static unsigned char icm20608_read_onereg(struct icm20608_dev *dev,
      p! S& L  f" t: x
  107. u8 reg)$ y3 z2 i0 c- W- C) B9 o
  108. 104 {* C2 X0 i. R. \  ]8 @$ ^9 Z# F
  109. 105     u8 data = 0;2 D2 F* T3 f- S5 `
  110. 106     icm20608_read_regs(dev, reg, &data, 1);
    ' _& i; t: i  t7 h* N" G9 J
  111. 107     return data;
    % |$ n" @+ |  F: y; ]
  112. 108 }
    ' g! X' \7 E: W
  113. 109, }* s" V: }; A% |$ V
  114. 110 /*- z0 X9 E* e( t7 t4 ~8 a
  115. 111  * @description         : 向icm20608指定寄存器写入指定的值,写一个寄存器
    8 y' j, b3 E# I# V8 h* Z
  116. 112  * @param – dev        : icm20608设备
    " x$ `; O/ W& x6 p
  117. 113  * @param – reg        : 要写的寄存器
    ( s+ s, w6 x! Q: ]
  118. 114  * @param – data        : 要写入的值5 E4 `. L. |* C0 F( t8 o; j
  119. 115  * @return                   : 无% q) D' F2 X4 I, |( W( e( r3 V' ?
  120. 116  */
    4 W$ P3 ~6 V6 Y* y
  121. 117( M1 D# f- A5 \# P# R* \$ ?" P
  122. 118 static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg,
    $ ~, U& J2 X4 W; L6 |
  123. u8 value)
    9 e$ I, Y: ~) n; P9 _' C
  124. 119 {
    ( V& w7 G+ x  G0 P
  125. 120     u8 buf = value;( r4 G2 |3 Y2 A2 I
  126. 121     icm20608_write_regs(dev, reg, &buf, 1);: i; _- V0 o5 n4 p/ R
  127. 122 }
    7 I8 t5 B$ u! U) k- Z
  128. 123
    2 I/ L1 T3 H% j/ u( |& c
  129. 124 /*
    % T/ W; q* g) [7 @! ~
  130. 125  * @description         : 读取ICM20608的数据,读取原始数据,包括三轴陀螺仪、
    % m# i% k4 v) f# K
  131. 126  *                              : 三轴加速度计和内部温度。( a! I1 t7 N0 Z6 z& h, F/ m# Z6 `
  132. 127  * @param - dev         : ICM20608设备
    . r/ U( _( x9 G# F7 n: ?) s
  133. 128  * @return              : 无。
    9 Y7 m1 y" a; F
  134. 129  */+ E9 r+ a% |- I# G  _( ?
  135. 130 void icm20608_readdata(struct icm20608_dev *dev)
    . b! G# E) o. e3 @7 M
  136. 131 {7 z4 F( f0 R5 j, B+ Y, ]: E. d
  137. 132     unsigned char data[14];3 q0 T1 n1 C9 F! N& M2 ~. E
  138. 133     icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data, 14);
    ; _: Z  H/ o* j# C7 S
  139. 134( U# A5 E7 c9 U4 S: v/ q* }+ o
  140. 135     dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]); 8 {$ g6 o! ]3 m: k$ a9 S; k
  141. 136     dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]);
    9 h! e7 s$ r2 b1 }( j1 N6 e
  142. 137     dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]); : q2 Y, E7 |, X0 V, p
  143. 138     dev->temp_adc    = (signed short)((data[6] << 8) | data[7]); 6 g  ~" N/ z2 z! V0 U( _
  144. 139     dev->gyro_x_adc  = (signed short)((data[8] << 8) | data[9]);
    ) m$ r- h! @2 i6 @3 F& d6 e8 ^
  145. 140     dev->gyro_y_adc  = (signed short)((data[10] << 8) | data[11]);9 C. V) ]$ K: x: D
  146. 141     dev->gyro_z_adc  = (signed short)((data[12] << 8) | data[13]);
      t. h3 F$ ~$ ~5 G: c; |/ s( P
  147. 142 }9 U+ P  Z) h' e- \, i3 K/ K  ]- M
  148. 143
    - j4 d# a0 d! S) H* Y6 y0 P
  149. 144 /*
    7 s! h: u+ h& R2 N& w% n
  150. 145  * ICM20608内部寄存器初始化函数 3 S4 p1 d( g7 L5 D
  151. 146  * @param - spi         : 要操作的设备4 k3 r: R5 L* L; j+ F# ~
  152. 147  * @return                  : 无0 l- M# w$ x. n5 A* {* c
  153. 148  */) ~# y$ N6 W! e/ y9 K. G
  154. 149 void icm20608_reginit(struct icm20608_dev *dev)
    ' ]9 f( p/ P8 K! O& p9 b
  155. 150 {
    ; F- w! P5 d' A* P
  156. 151     u8 value = 0;( l6 V) ]3 |, [* j
  157. 152     
    / F% f7 S' {+ J* {7 r. X
  158. 153     icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x80);
    ( O9 U1 b9 B, z8 X
  159. 154     mdelay(50);
    / J/ X7 s- t6 M# v2 l8 L! W* \
  160. 155     icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x01);+ Y0 k+ V6 m9 Z7 C) R
  161. 156     mdelay(50);5 y3 t" V" \/ n' _2 p/ t4 t7 b
  162. 157
    ! s; i# I: R& I& V$ `7 n) }
  163. 158     value = icm20608_read_onereg(dev, ICM20_WHO_AM_I);
    ) a( O7 u6 }, p3 T
  164. 159     printk("ICM20608 ID = %#X\r\n", value); ) @" [. K$ _3 D5 k+ C
  165. 160
    0 y' i9 B6 V  `; o6 h2 j) I+ N
  166. 161     icm20608_write_onereg(dev, ICM20_SMPLRT_DIV, 0x00);
    * x. @8 E; R# n% A# ]1 F
  167. 162     icm20608_write_onereg(dev, ICM20_GYRO_CONFIG, 0x18); 6 {. F1 N) f( e; @" b7 \9 @8 z
  168. 163     icm20608_write_onereg(dev, ICM20_ACCEL_CONFIG, 0x18);
    6 z" w0 Z9 y! B2 {
  169. 164     icm20608_write_onereg(dev, ICM20_CONFIG, 0x04);
    0 r) L4 T( k5 f( h& |& N
  170. 165     icm20608_write_onereg(dev, ICM20_ACCEL_CONFIG2, 0x04);
    ' J! Y  }2 U5 N) l2 v% k' p2 n
  171. 166     icm20608_write_onereg(dev, ICM20_PWR_MGMT_2, 0x00);
    $ H7 g# \6 [8 g8 n
  172. 167     icm20608_write_onereg(dev, ICM20_LP_MODE_CFG, 0x00);+ c; ]0 C* }* o, D
  173. 168     icm20608_write_onereg(dev, ICM20_FIFO_EN, 0x00);
    2 d4 N2 B  c1 R
  174. 169 }
复制代码
# P9 d3 E6 R6 v4 J& q8 l+ U
第9~48行,icm20608_read_regs函数,从icm20608中读取连续多个寄存器数据;注意:在本实验中,SPI为全双工通讯没有所谓的发送和接收长度之分。要读取或者发送N个字节就要封装N+1个字节,第1个字节是告诉设备我们要进行读还是写,后面的N个字节才是我们要读或者发送的数据。
* h$ j+ g, Z" R1 i第58~95行,icm20608_write_rege函数,向icm20608连续写入多个寄存器数据。此函数和icm20608_read_regs函数区别不大。
' i% M! M2 `* M第103~108行,icm20608_read_onereg函数,读取icm20608指定一个寄存器数据。1 y" N' r5 O& H; r: Y7 B. z3 \4 c
第118~122行,icm20608_write_onereg函数,向icm20608指定一个寄存器写入数据。
6 @) B( z% A+ f' R; \& U第130~142行,icm20608_readdata函数,读取icm20608六轴传感器和温度传感器原始数据值,应用程序读取icm20608的时候这些传感器原始数据就会上报给应用程序。
# ^) N6 d" W1 _% I7 {: o; g第149~169行,icm20608_reginit函数,初始化icm20608。, y6 O5 Y6 O$ T& A, |
# ~: f4 M. @, _' A) M
5、字符设备驱动框架
# _: K- `$ m2 R3 Gicm20608的字符设备驱动框架如下:& n; P6 ^. r  m! w6 v3 ]
8 }. V6 h4 U4 {( Y9 N
  1. 示例代码44.6.2.6 icm20608字符设备驱动
    & a6 c$ a+ w$ Q9 m1 @, m! S
  2. 1   /*/ k0 k) _$ |; {4 }5 \; U
  3. 2    * @description           : 打开设备
    / @% ~; U3 m( n! w/ y1 e
  4. 3    * @param – inode        : 传递给驱动的inode
    4 t1 f. @* s: ]& V7 R
  5. 4    * @param - filp         : 要打开的设备文件(文件描述符)
    $ s: Q: h/ s* J3 d9 y
  6. 5    * @return                : 0 成功;其他 失败; |  Q+ n( E/ p9 r% q/ ^
  7. 6    */
    * I4 c9 ]. L) I
  8. 7   static int icm20608_open(struct inode *inode, struct file *filp)) L9 S0 i2 Q5 H% m, k( a
  9. 8   {
    " I) H3 z8 ]9 P- d2 ]
  10. 9       return 0;
    0 Z2 w  Z) ^0 i* S% ^
  11. 10  }
    7 M7 X  ^( j: o, R8 c! O/ [
  12. 11
    5 ^- h* F+ ~! X5 K5 L' K* z5 S% \
  13. 12  /*
    " H/ T1 I" M' V2 g3 B
  14. 13   * @description         : 从设备读取数据 7 j0 M: y+ |* O
  15. 14   * @param – filp        : 要打开的设备文件(文件描述符)
    " o. P# B2 S2 ?- f
  16. 15   * @param - buf          : 返回给用户空间的数据缓冲区! o+ m( N4 ]( F4 I2 W+ ^1 j" w
  17. 16   * @param - cnt         : 要读取的数据长度- E+ C% ^) H' A5 t1 |
  18. 17   * @param – offt        : 相对于文件首地址的偏移9 f7 ~; l- q( N" u; C9 A. d& N! T
  19. 18   * @return                : 读取的字节数,如果为负值,表示读取失败
    1 `- q1 g! `+ E4 g4 d1 Y8 E
  20. 19   */
    $ z/ ^/ [: ?/ S3 W" x  g, P
  21. 20  static ssize_t icm20608_read(struct file *filp, char __user *buf,
    3 C8 Y! m0 G. c& V; q" r  X, w0 I
  22. size_t cnt, loff_t *off). j5 n# t5 y2 w' M; l" G) U2 c
  23. 21  {
    1 Q' m% Z' E+ t; w
  24. 22      signed int data[7];; b( h  G' d% `5 O
  25. 23      long err = 0;
    8 q+ u4 i( X6 S2 W5 W7 e3 r
  26. 24      struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;2 n4 s: i0 z4 U2 o$ |
  27. 25      struct icm20608_dev *dev = container_of(cdev, struct icm20608_dev,
    5 H! g! K$ Z. F* S. ?
  28.                                      cdev);
    ' {7 t  c" M5 H/ ?
  29. 26              - \% f4 G& y' k" w3 P5 S, y- T
  30. 27      icm20608_readdata(dev);, W# k2 V; V+ M1 Y7 G
  31. 28      data[0] = dev->gyro_x_adc;
    9 s# o! N+ F: Y8 \4 H) l- F: n
  32. 29      data[1] = dev->gyro_y_adc;
    : {2 E  [% W2 ?5 Q: {2 j: e
  33. 30      data[2] = dev->gyro_z_adc;
    6 }5 z; }, s: L2 Q
  34. 31      data[3] = dev->accel_x_adc;
    * N, l0 c) b# i
  35. 32      data[4] = dev->accel_y_adc;2 L6 C1 F% C: u/ j# }/ C7 E
  36. 33      data[5] = dev->accel_z_adc;" `: a! f* i1 X  ~1 O
  37. 34      data[6] = dev->temp_adc;# ^& ^. q. \2 A4 B
  38. 35      err = copy_to_user(buf, data, sizeof(data));
    . i! b3 i" b3 L+ P9 R5 `7 M: k( _
  39. 36      return 0;
    # J9 _! I4 i6 H0 d. ^8 m7 O
  40. 37  }" Q$ w! c5 v0 z. C5 Y2 ?0 B( P% B+ d
  41. 38' O8 e6 d" Y/ X( u8 Y8 }
  42. 39  /*+ S$ |% t" q$ Q: c( t8 c; Z
  43. 40   * @description          : 关闭/释放设备
    3 Q+ v; X  N6 f6 b' f
  44. 41   * @param – filp        : 要关闭的设备文件(文件描述符)
    $ p" D; a' j, [* D* C5 p
  45. 42   * @return                : 0 成功;其他 失败6 |  a( M! y  Z% l
  46. 43   */5 ^* A8 k: Z* J1 F9 n2 |
  47. 44  static int icm20608_release(struct inode *inode, struct file *filp)& q& N; N6 l& r- T* G# f
  48. 45  {
    7 W! G% F6 z, N
  49. 46      return 0;( N" a( f4 J$ I. X) @% u$ E, X
  50. 47  }
    / l1 }, }. J# W; w
  51. 48! u3 ]* K9 _9 K( U' V
  52. 49  /* icm20608操作函数 */9 K8 |5 f4 t) X' b2 u* ]
  53. 50  static const struct file_operations icm20608_ops = {
    1 p* ^* Q9 g/ y3 P/ t- z  G7 K
  54. 51      .owner = THIS_MODULE,; [$ f! Z9 i& q! [7 B
  55. 52      .open = icm20608_open,
    - q5 y- Y& ~8 r6 o- g( ?
  56. 53      .read = icm20608_read,2 G. Z1 y. o8 U; o3 \! Y6 k
  57. 54      .release = icm20608_release,
    2 [8 Y6 @$ m* |( W6 C
  58. 55  };
复制代码
) q7 e4 X4 B) r% z9 r. P5 ]! P5 Q$ L
字符设备驱动框架没什么好说的,重点是第20~37行的icm20608_read函数,当应用程序调用read函数读取icm20608设备文件的时候此函数就会执行。其中的24和25行里,通过probe注册的cdev变量,获取到我们自定义的icm20608_dev结构体的首地址。此函数调用上面编写好的icm20608_readdata函数读取icm20608的原始数据并将其上报给应用程序。大家注意,在内核中尽量不要使用浮点运算,所以不要在驱动将icm20608的原始值转换为对应的实际值,因为会涉及到浮点计算。
9 d9 ^( `4 x0 ?1 D; t6 M. ~这个驱动例程还不是很完美,在icm20608_read没有加锁。如果多个程序去读取这个驱动的时候就会出现读取数据出错,有能力的可以把这一点补充完整。
- K; T3 \1 X7 {3 k0 B9 K
4 X1 y8 R# k5 r( M44.6.3 编写测试APP
! P; e: O, k& c# k1 e4 g" K6 Q新建icm20608App.c文件,然后在里面输入如下所示内容:
2 O" J- o$ R& Q
) C: A" E9 W$ l4 T9 j
  1. 示例代码44.6.3.1 icm20608App.c文件代码
    # j1 H- _+ @5 c8 }+ v9 `8 ^; h3 ?9 s
  2. 12  #include "stdio.h"
    & j: m1 k. S5 b7 r' Y, Z
  3. 13  #include "unistd.h"% ]! O" K9 p- M% F) i4 u! e
  4. 14  #include "sys/types.h"
    2 K1 x7 R4 W' m2 D- P  l* D, a7 r
  5. 15  #include "sys/stat.h"( e9 z! H" |: s) r/ \# S$ `
  6. 16  #include "sys/ioctl.h"+ K3 ?" o6 E& v# [0 q
  7. 17  #include "fcntl.h"
    + L9 {! O  W- z  T/ `( y3 F
  8. 18  #include "stdlib.h"
    % u6 y; A1 ?9 V; b  D+ w, O: I/ ?3 Q9 m
  9. 19  #include "string.h"0 L- A" F( Y( J& o2 p
  10. 20  #include <poll.h>
    0 Z+ ^, C- ?8 ~9 e+ K
  11. 21  #include <sys/select.h>
    - w# W& Y3 T- d, K4 ]: A% H
  12. 22  #include <sys/time.h>  A6 M$ \' H6 l4 `7 e
  13. 23  #include <signal.h>
    6 F* E9 r! `/ d7 L6 T. J8 {$ W& W8 O
  14. 24  #include <fcntl.h>
    0 P) x+ K6 |3 c
  15. 25  /*1 e$ W) V2 `- Q* i( C) S- P1 Q
  16. 26   * @description          : main主程序5 j1 P, [9 i  u: u' F
  17. 27   * @param – argc        : argv数组元素个数
    6 |5 F3 h6 k$ W7 a7 i6 @  v
  18. 28   * @param - argv         : 具体参数8 N4 ?  n, |) U0 ^" G
  19. 29   * @return               : 0 成功;其他 失败' o3 p' }, e+ C) g
  20. 30   */
    : C  E0 a7 c' S4 n. P
  21. 31  int main(int argc, char *argv[])
      y- o5 v; E; C% T
  22. 32  {
    # }- A2 q  _3 J3 J7 i; ?0 o! o
  23. 33      int fd;, x  w; U$ `8 o
  24. 34      char *filename;
    ! G- J- Q  M% X( Y9 q3 [
  25. 35      signed int databuf[7];7 \$ ~. |0 h# q1 |5 S
  26. 36      unsigned char data[14];1 Q# Z! A1 P  n1 ~  W
  27. 37      signed int gyro_x_adc, gyro_y_adc, gyro_z_adc;8 k5 a0 q% A9 E/ ]$ k
  28. 38      signed int accel_x_adc, accel_y_adc, accel_z_adc;
    : v7 Y; b. o; g; \2 M# c2 d
  29. 39      signed int temp_adc;3 P* O( t+ m4 Y: s6 Z
  30. 40
    ! T' `8 ?1 T# `: H7 R, f3 k
  31. 41      float gyro_x_act, gyro_y_act, gyro_z_act;
      v  H& u8 ~8 @% ]& D4 N; t
  32. 42      float accel_x_act, accel_y_act, accel_z_act;
    ! z4 Z- S1 ~5 P& s- c
  33. 43      float temp_act;
    , r; @$ S; X% \: j% `7 E
  34. 44
    ' A( K" Y* C$ X. L& ]
  35. 45      int ret = 0;$ \4 I' G) ?; e
  36. 46
    $ v/ h+ e7 n7 L/ U  Q
  37. 47      if (argc != 2) {, l9 q. t2 F, ?! s. R
  38. 48          printf("Error Usage!\r\n");
    " A2 w- ?2 L6 h. i/ [
  39. 49          return -1;
    8 i1 @3 F1 @; w5 L& J$ w- `0 W
  40. 50      }
    % a; K( `8 Q' I  x2 p0 k
  41. 516 U2 V$ L9 R8 \3 u% F; n: A
  42. 52      filename = argv[1];
    % u! c, e. }. E' O4 ?) J
  43. 53      fd = open(filename, O_RDWR);
    * ~1 x0 ~3 Z; y4 i3 S
  44. 54      if(fd < 0) {2 m& }' q- @7 w7 x+ G
  45. 55          printf("can't open file %s\r\n", filename);
    5 b8 v1 R8 j8 z3 R3 g9 f
  46. 56          return -1;* O) G0 I( c# [9 F: V. y
  47. 57      }/ ?# @& H4 v5 N0 d# E6 I; G
  48. 58& H5 Q: x* u1 d4 c4 E5 E
  49. 59      while (1) {% z0 p+ ~9 R, E
  50. 60          ret = read(fd, databuf, sizeof(databuf));* b0 {# T9 ]4 A$ l
  51. 61          if(ret == 0) {          /* 数据读取成功 */. n4 U9 T( N: m, P
  52. 62              gyro_x_adc = databuf[0];
    9 [% P; A1 R& Z' |" f$ D
  53. 63              gyro_y_adc = databuf[1];  m# E% L8 t) r. I
  54. 64              gyro_z_adc = databuf[2];
    7 i2 d$ {) M* m8 T6 z& q
  55. 65              accel_x_adc = databuf[3];+ b6 T. ]3 P+ d
  56. 66              accel_y_adc = databuf[4];
    5 }0 w% K" A3 L2 C
  57. 67              accel_z_adc = databuf[5];# D5 V- I4 I7 \
  58. 68              temp_adc = databuf[6];
    4 `" q1 r# X# `& e5 G1 v
  59. 69
    % c9 B: b  Y% v' Z6 T3 Q: c
  60. 70              /* 计算实际值 */5 Y: u$ I& s0 {" p
  61. 71              gyro_x_act = (float)(gyro_x_adc)  / 16.4;
    4 v, u: f2 O* I: V/ c7 c
  62. 72              gyro_y_act = (float)(gyro_y_adc)  / 16.4;
    ' F! P/ i( q$ l" l% z# e( K  V
  63. 73              gyro_z_act = (float)(gyro_z_adc)  / 16.4;
    ! B0 n# a5 S5 a! l
  64. 74              accel_x_act = (float)(accel_x_adc) / 2048;! r0 G+ X0 ~5 o( f
  65. 75              accel_y_act = (float)(accel_y_adc) / 2048;
      q& i' ]) E' W9 j  M3 N
  66. 76              accel_z_act = (float)(accel_z_adc) / 2048;
    $ B  B1 I6 d( a5 z1 p2 t0 a0 G. M
  67. 77              temp_act = ((float)(temp_adc) - 25 ) / 326.8 + 25;
    & {0 ~  [$ I6 A, [: _  |, [" N
  68. 78
    ' e& g4 A( s+ n* f+ X4 ?0 f
  69. 79" ~8 l( H2 }$ f: C  N
  70. 80                      printf("\r\n原始值:\r\n");* _9 d* P! R+ t% z
  71. 81                         printf("gx = %d, gy = %d, gz = %d\r\n", gyro_x_adc,                                         gyro_y_adc, gyro_z_adc);
    3 Z- r$ z/ l: V
  72. 82                       printf("ax = %d, ay = %d, az = %d\r\n", accel_x_adc, 7 H6 `5 b5 Y6 P6 c/ h9 D
  73.                                 accel_y_adc, accel_z_adc);! V' \  X# ]0 A+ ?: s: |# I2 Z6 ^
  74. 83                      printf("temp = %d\r\n", temp_adc);3 _- D" N" [* t6 W7 |) G3 v8 |
  75. 84                      printf("实际值:");9 |( Z: [; ^! O* s' C: _( Q
  76. 85                     printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz
    4 C* h, E0 {7 y. o% G  n
  77.                  = %.2f°/S\r\n", gyro_x_act, gyro_y_act, gyro_z_act);- K, p5 J% O6 _7 Q! C6 P; c) h
  78. 86                            printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n",: F; v. E; V$ W- u! ^7 K; M0 e+ `
  79.                          accel_x_act, accel_y_act, accel_z_act);8 Y4 c  _: h/ S. |) B; N6 s
  80. 87                      printf("act temp = %.2f°C\r\n", temp_act);& m6 u9 n5 M0 F9 p4 o, _
  81. 88          }
    ' r+ S6 U6 }8 w( A2 ]7 `
  82. 89          usleep(100000);         /*100ms         *// U; O7 M0 S. M  d; q+ i. v* `! x
  83. 90      }. s) c+ t. s7 w6 H2 x6 ]' F; e
  84. 91      close(fd);                          /* 关闭文件         */  * q  n. Y0 Y  ?
  85. 92      return 0;  B$ h, L( g" ]0 [, H. Q; o
  86. 93  }- A1 x( A5 V3 T3 m& [
复制代码
9 s; L" m/ M* \( p- R
第60~91行,在while循环中每隔100ms从icm20608中读取一次数据,读取到icm20608原始数据以后将其转换为实际值,比如陀螺仪就是角速度、加速度计就是g值。注意,我们在icm20608驱动中将陀螺仪和加速度计的测量范围全部设置到了最大,分别为±2000和±16g。因此,在计算实际值的时候陀螺仪使用16.4,加速度计使用2048。最终将传感器原始数据和得到的实际值显示在终端上。0 |" Q6 r1 h# r* }3 r1 Q4 i# c

, c7 r" \" _/ k. f2 C7 D* u44.7 运行测试, i; x! ~! F7 T0 N7 i9 O. f
44.7.1 编译驱动程序和测试APP& [* T1 l; W! X, _/ ?
1、内核使能SPI控制器" }2 G4 @* B  N
ST官方系统把SPI控制器的驱动编译成模块,我们需要把SPI控制器驱动编译进内核,这样就可以在启动linux内核的时候自动加载SPI控制器驱动,无需我们手动加载,方便我们使用。打开Linux内核图形化配置界面,按下路径找到对应的配置项:
* _- v) x" I8 x  `Device Drivers
& h1 m" ~3 [! a& U* r0 H3 {6 Q$ TSPI support (SPI [=y])
% x: m- _# G' K. h% r<*> STMicroelectronics STM32 SPI controller //编译进内核# E! j+ d% Q; P3 x% \/ P: x
如图44.7.1.1所示:
, n' h. i$ s6 S  a+ s0 A3 y% S& B" }0 I& B; |
a764f7e1a9194889bace7c78b5330538.png
) n5 A% F5 m2 x8 q5 N$ i4 F! c: y( [5 i, X( e) O5 G" n( }
图44.7.1.1 SPI控制器配置项" V8 D4 X5 [$ }' c6 C" U2 H4 u! T
图44.7.1.1本来是选择为“M”,我们要改为“*”,也就是编译进内核。接着我们重新编译设备树和内核,运行以下命令进行编译:" ?5 z* z" |+ g9 [2 W
make dtbs uImage LOADADDR=0XC2000040 -j323 i2 W! R3 s  B3 B' h" h
使用新编译好的stm32mp157d-atk.dtb和uImage镜像启动系统,如果SPI控制器驱动工作正常就会有图44.7.1.2所示提示信息:
0 d! p( |( W: l! ~
/ l% Z- ?2 e5 e& R/ @4 M 2dfa0c2bff5a47bb998161d37ddef90a.png / C  i7 q9 u# c9 x- c& q* `7 ^" A
5 ~5 b+ @; N9 i/ x/ U: T3 I
图44.7.1.2 SPI控制器初始化
; e4 S) g- l. e1 ?1 O% t$ {如果没有输出图44.7.1.2中“spi_stm32 44004000.spi: driver initialized”这句话,那就要检查一下设备树和内配配置是否有问题,通过查看/sys/bus/spi/devices/下有没有spi相关的设备,就能够知道设备树配置是否正确,比如本例程如图44.7.1.3所示:
) }2 K; F8 C" Q" q  k& I+ P, m: ]* g
4fb4fe72991e47188e57cd149f575932.png 1 {0 G  [7 I2 v! ?! F) l+ x

7 U5 }/ X! ]$ t- S+ v+ u图44.7.1.3 SPI设备
5 [+ D. ]1 p# S6 I; Y2、编译驱动程序0 `. m9 A! I- N
编写Makefile文件,本章实验的Makefile文件和第四十章实验基本一样,只是将obj-m变量的值改为“icm20608.o”,Makefile内容如下所示:6 _6 u7 n0 J8 C. }; _

9 v& N/ T9 V. E$ y. x, W; l示例代码44.7.1.1 Makefile文件
& ?, e6 }9 _+ T0 S
  1. 1  KERNELDIR := /home/zuozhongkai/linux/my_linux/linux-5.4.31" ?1 r2 j3 U! Q, A2 t3 C* V8 [
  2. ......
    : g# l# h6 @2 \6 u1 g. T
  3. 4  obj-m := icm20608.o3 v/ i0 n! A/ R& {1 D8 r& r
  4. ......
    / [* V+ F! E( n9 [, S# V
  5. 11 clean:9 ?7 k1 h4 `7 u# g3 W
  6. 12  $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
复制代码
6 f, c" ~8 j" D* C$ ^/ h
第4行,设置obj-m变量的值为“icm20608.o”。
8 I; c, r: Z$ n输入如下命令编译出驱动模块文件:
. n/ y) q1 |- Z2 Y- s, ?5 a
- \* N: p4 e3 T8 q: d$ emake -j16
2 H- A2 z( S; C, l编译成功以后就会生成一个名为“icm20608.ko”的驱动模块文件。3 }8 Z+ V" }9 g5 i
3、编译测试APP( ~2 a! N" @5 x
在icm20608App.c这个测试APP中我们用到了浮点计算,而STM32MP1是支持硬件浮点的,因此我们在编译icm20608App.c的时候就可以使能硬件浮点,这样可以加速浮点计算。使能硬件浮点很简单,在编译的时候加入如下参数即可:
! n8 p5 A7 f" z5 E& M1 Q+ C-march-armv7-a -mfpu-neon -mfloat=hard1 |7 D" W5 F/ t/ Y* G
输入如下命令使能硬件浮点编译icm20608App.c这个测试程序:
# ^0 F; u$ q9 [8 V9 ~" u: r3 Xarm-none-linux-gnueabihf-gcc -march=armv7-a -mfpu=neon -mfloat-abi=hard icm20608App.c -o icm20608App; b" Q, S8 e, P% x. E* o8 V
编译成功以后就会生成icm20608App这个应用程序,那么究竟有没有使用硬件浮点呢?使用arm-linux-gnueabihf-readelf查看一下编译出来的icm20608App就知道了,输入如下命令:
( v1 M) R  \# x* g7 Garm-none-linux-gnueabihf-readelf -A icm20608App
' `% q" P! B+ Z; z7 r; ?结果如图44.7.1.4所示:
$ z; s6 q+ o+ \/ {2 n5 M/ c0 Z
2 S' v  C; U* f) C/ M# c' o deef27167f0d44619d4bf6edb0cd95c9.png . r; W+ F+ R5 C. z( o  @
% Q. v: s: b- ]+ V& h" R
图44.7.1.4 icm20608App文件信息7 H$ v# [' k; u$ M
从图44.7.1.4可以看出FPU架构为VFPv3,SIMD使用了NEON,说明icm20608App这个应用程序使用了硬件浮点。
/ N5 w) F/ s2 O/ g' w3 H$ n/ i, O# ^' h7 m3 T% r, a0 L
44.7.2 运行测试
" A) H; i+ g0 A, w, c将上一小节编译出来icm20608.ko和icm20608App这两个文件拷贝到rootfs/lib/modules/5.3.41目录中,重启开发板,进入到目录lib/modules/5.3.41中。输入如下命令加载icm20608.ko这个驱动模块。3 _# n4 H9 w$ Q& S. R
depmod //第一次加载驱动的时候需要运行此命令1 \0 G# {/ c& l: q3 W- a
modprobe icm20608.ko //加载驱动模块
& B: O- m3 _9 d( S, x1 [' d当驱动模块加载成功以后使用icm20608App来测试,输入如下命令:
2 M; A5 A2 [/ M./icm20608App /dev/icm20608
  D+ z9 d( |( K7 l/ F测试APP会不断的从ICM20608中读取数据,然后输出到终端上,如图44.7.2.1所示:5 a0 ?0 H6 Y  V! j1 S% H

% w. p* J) m8 I# }5 ^ 39bc64300e6e48fd89710e823984ce45.png 3 o+ @6 Z" j& h, o
# F/ M  D2 S" E! P, f- A
图44.7.2.1获取到的ICM20608数据
: Y$ D  s8 R$ q/ V可以看出,开发板静止状态下,Z轴方向的加速度为0.97g,这个就是重力加速度。对于陀螺仪来讲,静止状态下三轴的角速度应该在0°/S左右。ICM20608内温度传感器采集到的温度在39.51度,大家可以晃动一下开发板,这个时候陀螺仪和加速度计的值就会有变化。/ ^: d- B; h& G6 P
————————————————! u2 U" ~. ]8 j2 ]9 p  C' X
版权声明:正点原子9 m: _. c* J2 t6 m

; f7 W! Q0 i: r) z1 t, t3 g
' m7 a) F1 ?  z# e$ a
收藏 评论0 发布时间:2022-9-27 11:50

举报

0个回答

所属标签

相似分享

官网相关资源

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