分享

linux基础之spi驱动程序理解

 昵称28625772 2015-11-12

linux基础之spi驱动程序理解

分类: linux basics linux develop 2012-10-06 16:57 6243人阅读 评论(0) 收藏 举报

linuxiocstructdelay工作数据结构

============================================

作者:yuanlulu
http://blog.csdn.net/yuanlulu


版权没有,但是转载请保留此段声明
============================================


1.1    
重要的数据结构
1
 spi_device
虽然用户空间不需要直接用到spi_device结构体,但是这个结构体和用户空间的程序有密切的关系,理解它的成员有助于理解SPI设备节点的IOCTL命令,所以首先来介绍它。
在内核中,每个spi_device代表一个物理的SPI设备。它的成员如程序清单 1.1所示。
程序清单 1.1 spi_device

[cpp] view plaincopyprint?

1.  struct spi_device {  

2.           structdevice        dev;  

3.           structspi_master *master;  

4.           u32                      max_speed_hz;    /* 通信时钟最大频率 */  

5.           u8                        chip_select;    /* 片选号 */  

6.           u8                        mode;           /*SPI设备的模式,下面的宏是它各bit的含义  */  

7.  #define       SPI_CPHA         0x01                /* 采样的时钟相位                            */  

8.  #define       SPI_CPOL          0x02               /* 时钟信号起始相位:高或者是低电平*/  

9.  #define       SPI_MODE_0    (0|0)                      

10. #define       SPI_MODE_1    (0|SPI_CPHA)  

11. #define       SPI_MODE_2    (SPI_CPOL|0)  

12. #define       SPI_MODE_3    (SPI_CPOL|SPI_CPHA)  

13. #define       SPI_CS_HIGH   0x04                    /* 1时片选的有效信号是高电平*/  

14. #define       SPI_LSB_FIRST         0x08            /* 发送时低比特在前  */  

15. #define       SPI_3WIRE        0x10                 /* 输入输出信号使用同一根信号线 */  

16. #define       SPI_LOOP         0x20                 /* 回环模式 */  

17.          u8                        bits_per_word;    /* 每个通信字的字长(比特数) */  

18.          int                        irq;             /*使用到的中断 */  

19.          void                     *controller_state;  

20.          void                     *controller_data;  

21.          char                     modalias[32];      /* 设备驱动的名字*/  

22. };  

struct spi_device {

         structdevice        dev;

         structspi_master *master;

         u32                      max_speed_hz;    /* 通信时钟最大频率 */

         u8                        chip_select;    /* 片选号 */

         u8                        mode;           /*SPI设备的模式,下面的宏是它各bit的含义  */

#define       SPI_CPHA         0x01                /* 采样的时钟相位                            */

#define       SPI_CPOL          0x02               /* 时钟信号起始相位:高或者是低电平*/

#define       SPI_MODE_0    (0|0)                   

#define       SPI_MODE_1    (0|SPI_CPHA)

#define       SPI_MODE_2    (SPI_CPOL|0)

#define       SPI_MODE_3    (SPI_CPOL|SPI_CPHA)

#define       SPI_CS_HIGH   0x04                    /* 1时片选的有效信号是高电平*/

#define       SPI_LSB_FIRST         0x08            /* 发送时低比特在前  */

#define       SPI_3WIRE        0x10                 /* 输入输出信号使用同一根信号线 */

#define       SPI_LOOP         0x20                 /* 回环模式 */

         u8                        bits_per_word;    /* 每个通信字的字长(比特数) */

         int                        irq;             /*使用到的中断 */

         void                     *controller_state;

         void                     *controller_data;

         char                     modalias[32];      /* 设备驱动的名字*/

};





       
由于一个SPI总线上可以有多个SPI设备,因此需要片选号来区分它们,SPI控制器根据片选号来选择不同的片选线,从而实现每次只同一个设备通信。
       spi_device
mode成员有两个比特位含义很重要。SPI_CPHA选择对数据线采样的时机,0选择每个时钟周期的第一个沿跳变时采样数据,1选择第二个时钟沿采样数据;SPI_CPOL选择每个时钟周期开始的极性,0表示时钟以低电平开始,1选择高电平开始。这两个比特有四种组合,对应SPI_MODE_0SPI_MODE_3
       
另一个比较重要的成员是bits_per_word。这个成员指定每次读写的字长,单位是比特。虽然大部分SPI接口的字长是8或者16,仍然会有一些特殊的例子。需要说明的是,如果这个成员为零的话,默认使用8作为字长。
       
最后一个成员并不是设备的名字,而是需要绑定的驱动的名字。


2
  spi_ioc_transfer
       
在用户使用设备节点的IOCTL命令传输数据的时候,需要用到 spi_ioc_transfer结构体,它的成员如程序清单 1.2所示。
程序清单 1.2  spi_ioc_transfer

[cpp] view plaincopyprint?

1.  struct spi_ioc_transfer {  

2.              __u64               tx_buf;                   /* 写数据缓冲  */  

3.              __u64               rx_buf;                   /* 读数据缓冲  */  

4.     

5.              __u32               len;                      /* 缓冲的长度 */  

6.              __u32               speed_hz;                 /* 通信的时钟频率 */  

7.     

8.              __u16               delay_usecs;    /* 两个spi_ioc_transfer之间的延时 */  

9.              __u8                 bits_per_word;           /* 字长(比特数)  */  

10.             __u8                 cs_change;               /* 是否改变片选 */  

11.             __u32               pad;                                

12. };  

struct spi_ioc_transfer {

            __u64               tx_buf;                   /* 写数据缓冲  */

            __u64               rx_buf;                   /* 读数据缓冲  */

 

            __u32               len;                      /* 缓冲的长度 */

            __u32               speed_hz;                 /* 通信的时钟频率 */

 

            __u16               delay_usecs;    /* 两个spi_ioc_transfer之间的延时 */

            __u8                 bits_per_word;           /* 字长(比特数)  */

            __u8                 cs_change;               /* 是否改变片选 */

            __u32               pad;                             

};





       
每个 spi_ioc_transfer都可以包含读和写的请求,其中读和写的长度必须相等。所以成员len不是tx_bufrx_buf缓冲的长度之和,而是它们各自的长度。SPI控制器驱动会先将tx_buf写到SPI总线上,然后再读取len长度的内容到rx_buf。如果只想进行一个方向的传输,把另一个方向的缓冲置为0就可以了。
speed_hz
bits_per_word这两个成员可以为每次通信配置不同的通信速率(必须小于spi_devicemax_speed_hz)和字长,如果它们为0的话就会使用spi_device中的配置。
delay_usecs
可以指定两个spi_ioc_transfer之间的延时,单位是微妙。一般不用定义。
cs_change
指定这个cs_change结束之后是否需要改变片选线。一般针对同一设备的连续的几个spi_ioc_transfer,只有最后一个需要将这个成员置位。这样省去了来回改变片选线的时间,有助于提高通信速率。


1.2    
获得同SPI设备通信的设备节点
为了在用户空间获得和SPI设备直接通信的设备节点,必须有两个条件要满足:首先要有SPI控制器驱动,其次是要在内核初始化的时候注册一个spi_board_info,它的modalias成员必须为“spidev”。有了这两个条件,就可以和SPI设备进行通信了。控制器的驱动一般由芯片厂家提供,开发者只需提供第二个条件。
spi_board_info
的定义如程序清单 1.3所示。
程序清单 1.3  struct spi_board_info

[cpp] view plaincopyprint?

1.  struct spi_board_info {  

2.           char            modalias[32];          /* 要绑定的驱动的名字 */  

3.           constvoid  *platform_data;                                          

4.           void            *controller_data;  

5.           int               irq;              

6.     

7.           u32             max_speed_hz;          /* 通信时钟最大速率 */  

8.     

9.           u16             bus_num;              /* 总线编号  */  

10.          u16             chip_select;           /* 片选号 */  

11.    

12.          u8               mode;                 /* spi_device中的mode成员类似   */  

13. };  

struct spi_board_info {

         char            modalias[32];          /* 要绑定的驱动的名字 */

         constvoid  *platform_data;                                       

         void            *controller_data;

         int               irq;           

 

         u32             max_speed_hz;          /* 通信时钟最大速率 */

 

         u16             bus_num;              /* 总线编号  */

         u16             chip_select;           /* 片选号 */

 

         u8               mode;                 /* spi_device中的mode成员类似   */

};



       
要了解这个结构体各个成员的意义请参考程序清单 1.1
       
定义并注册structspi_board_info的位置一般是内核的arch/xxx/mach-xxxx/board-xxxx.c,比如3250的内核,这个文件是arch/arm/mach-lpc32xx/board-smartarm3250.c。定义并注册struct spi_board_info的代码如程序清单 1.4所示。
程序清单 1.4  定义并注册spi_board_info

[cpp] view plaincopyprint?

1.  static int __init smartarm3250_spi_usp_register(void)  

2.  {  

3.           structspi_board_info info =  

4.           {  

5.                     .modalias= "spidev",  

6.                     .max_speed_hz= 5000000,  

7.                     .bus_num= 0,  

8.                     .chip_select= 0,  

9.           };  

10.    

11.          returnspi_register_board_info(&info, 1);  

12. }  

13. arch_initcall(smartarm3250_spi_usp_register);  

static int __init smartarm3250_spi_usp_register(void)

{

         structspi_board_info info =

         {

                   .modalias= "spidev",

                   .max_speed_hz= 5000000,

                   .bus_num= 0,

                   .chip_select= 0,

         };

 

         returnspi_register_board_info(&info, 1);

}

arch_initcall(smartarm3250_spi_usp_register);




       
由于3250内核代码在arch/arm/mach-lpc32xx/board-smartarm3250.c已经定义了一个smartarm3250_spi_eeprom_register函数,因此在增加程序清单 1.4代码前先将这个函数注释掉。
程序清单 1.4注册了一个挂在0SPI总线上的设备信息,它的片选号为0。增加完这段代码后将内核重新编译。在内核启动的时候,会为这个设备建立一个spi_device并和0SPI总线的驱动进行绑定。同时内核会为这个设备申请一个主设备号为153的的设备号,次设备号和注册的顺序有关,最多支持32个同类设备。
内核重新编译并重启之后,如果系统中运行了udev/dev下就会生成一个spidevX.D设备节点,其中X是总线编号,D是片选号。对于程序清单 1.4的代码应该自动生成的设备节点是spidev0.0
       
一般SPI控制器驱动由芯片厂商提供,开发者所要在内核做的工作就是添加类似程序清单 1.4的内容。这样内核空间的工作减少了,用户空间的工作量加大了,因为用户空间的开发者需要全面了解SPI设备的工作方式和接口协议。


1.3    
用户空间同设备节点的接口
       
对于/dev/spidevX.D设备节点,可以进行各种操作,这一小节介绍它支持的函数接口。
1
 open/close
       
打开和关闭设备节点没有特别之处,直接使用open/write就可以了。
2
 read/write
       
读写SPI设备可以直接使用read/write函数,但是每次读或者写的大小不能大于4096Byte
3
 IOCTL命令
       
用户空间对spidev设备节点使用IOCTL命令失败会返回-1
l        SPI_IOC_RD_MODE
读取SPI设备对应的spi_device.modemode的含义请参考程序清单 1.1。使用的方法如下:
                   ioctl(fd,SPI_IOC_RD_MODE, &mode);
             
其中第三个参数是一个uint8_t类型的变量。
l        SPI_IOC_WR_MODE
设置SPI设备对应的spi_device.mode。使用的方式如下:
                   ioctl(fd,SPI_IOC_WR_MODE, &mode);
l SPI_IOC_RD_LSB_FIRST
查看设备传输的时候是否先传输低比特位。如果是的话,返回1。使用的方式如下:
                   ioctl(fd,SPI_IOC_RD_LSB_FIRST, &lsb);
             
其中lsb是一个uint8_t类型的变量。返回的结果存在lsb中。
l SPI_IOC_WR_LSB_FIRST
设置设备传输的时候是否先传输低比特位。当传入非零的时候,低比特在前,当传入0的时候高比特在前(默认)。使用的方式如下:
                   ioctl(fd,SPI_IOC_WR_LSB_FIRST, &lsb);
l SPI_IOC_RD_BITS_PER_WORD
读取SPI设备的字长。使用的方式如下:
                   ioctl(fd,SPI_IOC_RD_BITS_PER_WORD, &bits);
             
其中bits是一个uibt8_t类型的变量。返回的结果保存在bits中。
l SPI_IOC_WR_BITS_PER_WORD
设置SPI通信的字长。使用的方式如下:
                   ioctl(fd,SPI_IOC_WR_BITS_PER_WORD, &bits);
l SPI_IOC_RD_MAX_SPEED_HZ
读取SPI设备的通信的最大时钟频率。使用的方式如下:
                   ioctl(fd,SPI_IOC_RD_MAX_SPEED_HZ, &speed);
             
其中speed是一个uint32_t类型的变量。返回的结果保存在speed中。
l SPI_IOC_WR_MAX_SPEED_HZ
设置SPI设备的通信的最大时钟频率。使用的方式如下:
                   ioctl(fd,SPI_IOC_WR_MAX_SPEED_HZ, &speed);
l SPI_IOC_MESSAGE(N)
一次进行双向/多次读写操作。使用的方式如下:
                   structspi_ioc_transfer  xfer[2];
                   ......
                   status= ioctl(fd, SPI_IOC_MESSAGE(2), xfer);
其中N是本次通信中xfer的数组长度。spi_ioc_transfer的信息请参考程序清单 1.2


/************************************************************************************/
如果想要在用户空间编写spi驱动,这就要在内核的arch/.../mach-*/board-*.c 中声明一个spi_board_info,
它的名字一定要是“spidev”,比如:

[cpp] view plaincopyprint?

1.  struct spi_board_info info =  

2.  {  

3.   .modalias = "spidev",  

4.   .max_speed_hz = 5000000,  

5.   .bus_num = 0,  

6.   .chip_select = 0,  

7.  };  

 struct spi_board_info info =

 {

  .modalias = "spidev",

  .max_speed_hz = 5000000,

  .bus_num = 0,

  .chip_select = 0,

 };



 return spi_register_board_info(&info, 1);
这样只要控制器驱动加载了,spidev模块就会和这个设备绑定,并为设备申请一个设备号,主设备号为153,次设备号和设备加载的次序有关。
目前spidev支持最多32个设备。设备的名字是spidevX.D,其中X是总线编号,D是设备的片选号。如果正确安装并配置了udev/dev目录下便会生成spidevX.D
设备节点。直接对这些设备节点操作就行了。
 
 
spidev
的设备节点的接口包括open/close/read/write/ioctl
~~~~~~~~~~~~~~~~~~~~~~~~~
其中open/close没有什么特别之处。
read/write
的话有大小的限制,读写的大小默认不能超过4096字节。这个大小是一个模块加载参数,可以修改。
允许多个用户同时打开设备节点,spidev使用mutext进行互斥,多个用户同时读写时只有一个活动的用户,其他用户睡眠。
 
 
spidev
ioctl命令。
~~~~~~~~
SPI_IOC_RD_MODE
:读取spi_devicemode
SPI_IOC_RD_LSB_FIRST
:如果是SPI_LSB_FIRST的方式则返回1
SPI_IOC_RD_BITS_PER_WORD
:读取spi_devicebits_per_word.
SPI_IOC_RD_MAX_SPEED_HZ:
读取spi_devicemax_speed_hz.
SPI_IOC_WR_MODE:
设置spi_devicemode,并调用spi_setup立即使设置生效。
SPI_IOC_WR_LSB_FIRST
:设置spi使用SPI_LSB_FIRST的传输模式。立即生效。
SPI_IOC_WR_BITS_PER_WORD
:读取字长。
SPI_IOC_WR_MAX_SPEED_HZ
:设置时钟速率。
无论读取,用户传输的第三个参数都被当作缓冲地址指针。读取时存放结果,写入时存放要写的内容。


SPI_IOC_MESSAGE
:这个命令用来进行复杂的通信。参数涉及到一个结构体。各个成员的意义与spi_transfer一致。

[cpp] view plaincopyprint?

1.  struct spi_ioc_transfer {  

2.   __u64  tx_buf;  

3.   __u64  rx_buf;  

4.   __u32  len;  

5.   __u32  speed_hz;  

6.   __u16  delay_usecs;  

7.   __u8  bits_per_word;  

8.   __u8  cs_change;  

9.   __u32  pad;  

10.  /* If the contents of 'struct spi_ioc_transfer' ever change 

11.   * incompatibly, then the ioctl number (currently 0) must change; 

12.   * ioctls with constant size fields get a bit more in the way of 

13.   * error checking than ones (like this) where that field varies. 

14.   * 

15.   * NOTE: struct layout is the same in 64bit and 32bit userspace. 

16.   */  

17. };  

struct spi_ioc_transfer {

 __u64  tx_buf;

 __u64  rx_buf;

 __u32  len;

 __u32  speed_hz;

 __u16  delay_usecs;

 __u8  bits_per_word;

 __u8  cs_change;

 __u32  pad;

 /* If the contents of 'struct spi_ioc_transfer' ever change

  * incompatibly, then the ioctl number (currently 0) must change;

  * ioctls with constant size fields get a bit more in the way of

  * error checking than ones (like this) where that field varies.

  *

  * NOTE: struct layout is the same in 64bit and 32bit userspace.

  */

};



内核文档中一个例子:

[cpp] view plaincopyprint?

1.  static void do_msg(int fd, int len)    

2.  {    

3.   struct spi_ioc_transfer xfer[2];    

4.   unsigned char  buf[32], *bp;    

5.   int   status;    

6.   memset(xfer, 0, sizeof xfer);    

7.   memset(buf, 0, sizeof buf);    

8.   if (len > sizeof buf)    

9.    len = sizeof buf;    

10.  buf[0] = 0xaa;    

11.  xfer[0].tx_buf = (__u64) buf;    

12.  xfer[0].len = 1;    

13.  xfer[1].rx_buf = (__u64) buf;    

14.  xfer[1].len = len;    

15.  status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer);    

16.  if (status < 0) {    

17.   perror("SPI_IOC_MESSAGE");    

18.   return;    

19.  }    

20.  printf("response(%2d, %2d): ", len, status);    

21.  for (bp = buf; len; len--)    

22.   printf(" %02x", *bp++);    

23.  printf("/n");    

24. }    

static void do_msg(int fd, int len) 

{ 

 struct spi_ioc_transfer xfer[2]; 

 unsigned char  buf[32], *bp; 

 int   status; 

 memset(xfer, 0, sizeof xfer); 

 memset(buf, 0, sizeof buf); 

 if (len > sizeof buf) 

  len = sizeof buf; 

 buf[0] = 0xaa; 

 xfer[0].tx_buf = (__u64) buf; 

 xfer[0].len = 1; 

 xfer[1].rx_buf = (__u64) buf; 

 xfer[1].len = len; 

 status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer); 

 if (status < 0) { 

  perror("SPI_IOC_MESSAGE"); 

  return; 

 } 

 printf("response(%2d, %2d): ", len, status); 

 for (bp = buf; len; len--) 

  printf(" %02x", *bp++); 

 printf("/n"); 

} 




内核在documentation/spi目录下有spidev的例子。
 
 
 
注意
~~~~
虽然多个用户不能同一时刻对spi进行设置或读写,但是同一用户却无法组织其他用户修改同一设备的设置。
举例来说,usr1打开设备节点,然后使用ioctl设置了时钟速率,此时usr1线程被调度出去,然后usr2操作同一个设备,将它的时钟设为另一个值。
此时usr1重新调度去使用read函数,则达不到预期的效果。
建议不要有两个程序操作spidevX.D设备节点。

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多