SPI总线(二):驱动分析篇

本文转自https://blog.csdn.net/kai_zone/article/details/78041345


1 Linux SPI驱动总体架构
             linux内核中,SPI的驱动架构可以分为如下三个层次:SPI 核心层、SPI控制器驱动层和SPI设备驱动层。
      Linux 中SPI驱动代码位于drivers/spi目录。


1.1 SPI核心层
      SPI核心层是Linux的SPI核心部分,提供了核心数据结构的定义、SPI控制器驱动和设备驱动的注册、注销管理等API。其为硬件平台无关层,向下屏蔽了物理总线控制器的差异,定义了统一的访问策略和接口;其向上提供了统一的接口,以便SPI设备驱动通过总线控制器进行数据收发。
      Linux中,SPI核心层的代码位于driver/spi/ spi.c。由于该层是平台无关层,本文将不再叙述,有兴趣可以查阅相关资料。


1.2 SPI控制器驱动层
      SPI控制器驱动层,每种处理器平台都有自己的控制器驱动,属于平台移植相关层。它的职责是为系统中每条SPI总线实现相应的读写方法。在物理上,每个SPI控制器可以连接若干个SPI从设备。
      在系统开机时,SPI控制器驱动被首先装载。一个控制器驱动用于支持一条特定的SPI总线的读写。一个控制器驱动可以用数据结构struct spi_master来描述。

   在include/liunx/spi/spi.h文件中,在数据结构struct spi_master定义如下:


   
   
  1. struct spi_master {
  2. struct device dev;
  3. s16 bus_num;
  4. u16 num_chipselect;
  5. int (*setup)(struct spi_device *spi);
  6. int (*transfer)(struct spi_device *spi, struct spi_message *mesg);
  7. void (*cleanup)(struct spi_device *spi);
  8. };

      bus_num为该控制器对应的SPI总线号。
      num_chipselect 控制器支持的片选数量,即能支持多少个spi设备 
      setup函数是设置SPI总线的模式,时钟等的初始化函数, 针对设备设置SPI的工作时钟及数据传输模式等。在spi_add_device函数中调用。 
      transfer函数是实现SPI总线读写方法的函数。实现数据的双向传输,可能会睡眠

    cleanup注销时候调用


1.3 SPI设备驱动层
      SPI设备驱动层为用户接口层,其为用户提供了通过SPI总线访问具体设备的接口。
      SPI设备驱动层可以用两个模块来描述,struct spi_driver和struct spi_device。
      相关的数据结构如下:


   
   
  1. struct spi_driver {
  2. int (*probe)(struct spi_device *spi);
  3. int (*remove)(struct spi_device *spi);
  4. void (*shutdown)(struct spi_device *spi);
  5. int (*suspend)(struct spi_device *spi, pm_message_t mesg);
  6. int (*resume)(struct spi_device *spi);
  7. struct device_driver driver;
  8. };

  Driver是为device服务的,spi_driver注册时会扫描SPI bus上的设备,进行驱动和设备的绑定,probe函数用于驱动和设备匹配时被调用。从上面的结构体注释中我们可以知道,SPI的通信是通过消息队列机制,而不是像I2C那样通过与从设备进行对话的方式。


   
   
  1. struct spi_device {
  2. struct device dev;
  3. struct spi_master *master;
  4. u32 max_speed_hz;
  5. u8 chip_select;
  6. u8 mode;
  7. u8 bits_per_word;
  8. int irq;
  9. void *controller_state;
  10. void *controller_data;
  11. char modalias[ 32];
  12. };


   
   
  1. .modalias = "m25p10",
  2. .mode =SPI_MODE_0, //CPOL=0, CPHA=0 此处选择具体数据传输模式
  3. .max_speed_hz = 10000000, //最大的spi时钟频率
  4. /* Connected to SPI-0 as 1st Slave */
  5. .bus_num = 0, //设备连接在spi控制器0上
  6. .chip_select = 0, //片选线号,在S5PC100的控制器驱动中没有使用它作为片选的依据,而是选择了下文controller_data里的方法。
  7. .controller_data = &smdk_spi0_csi[ 0],

       通常来说spi_device对应着SPI总线上某个特定的slave。并且spi_device封装了一个spi_master结构体。spi_device结构体包含了私有的特定的slave设备特性,包括它最大的频率,片选那个,输入输出模式等等


2 OMAP3630 SPI控制器
      OMAP3630上SPI是一个主/从的同步串行总线,这边有4个独立的SPI模块(SPI1,SPI2,SPI3,SPI4),各个模块之间的区别在于SPI1支持多达4个SPI设备,SPI2和SPI3支持2个SPI设备,而SPI4只支持1个SPI设备。

SPI控制器具有以下特征:
     1.可编程的串行时钟,包括频率,相位,极性。
     2.支持4到32位数据传输
     3.支持4通道或者单通道的从模式
     4.支持主的多通道模式
         4.1全双工/半双工
         4.2只发送/只接收/收发都支持模式
         4.3灵活的I/O端口控制
         4.4每个通道都支持DMA读写
    5.支持多个中断源的中断时间
    6.支持wake-up的电源管理
    7.内置64字节的FIFO

 

3 spi_device以下一系列的操作是在platform板文件中完成!

spi_device的板信息用spi_board_info结构体来描述:

    
    
  1. struct spi_board_info {
  2. charmodalias[SPI_NAME_SIZE];
  3. const void*platform_data;
  4. void*controller_data;
  5. intirq;
  6. u32max_speed_hz;
  7. u16bus_num;
  8. u16chip_select;
  9. u8mode;
  10. };

这个结构体记录了SPI外设使用的主机控制器序号、片选信号、数据比特率、SPI传输方式等

构建的操作是以下的两个步骤:


   
   
  1. 1. static struct spi_board_info s3c_spi_devs[] __initdata = {
  2. {
  3. .modalias = "m25p10a",
  4. .mode = SPI_MODE_0,
  5. .max_speed_hz = 1000000,
  6. .bus_num = 0,
  7. .chip_select = 0,
  8. .controller_data = &smdk_spi0_csi[ SMDK_MMCSPI_CS],
  9. },
  10. };

2.而这个info在init函数调用的时候会初始化:

   spi_register_board_info(s3c_spi_devs,ARRAY_SIZE(s3c_spi_devs));
   
   

spi_register_board_info(s3c_spi_devs,ARRAY_SIZE(s3c_spi_devs));//注册spi_board_info。这个代码会把spi_board_info注册到链表board_list上。spi_device封装了一个spi_master结构体,事实上spi_master的注册会在spi_register_board_info之后,spi_master注册的过程中会调用scan_boardinfo扫描board_list,找到挂接在它上面的spi设备,然后创建并注册spi_device。

至此spi_device就构建并注册完成了!!!!!!!!!!!!!

 

4 spi_driver的构建与注册

 

driver有几个重要的结构体   :spi_driver、spi_transfer、spi_message

driver有几个重要的函数    :spi_message_init、spi_message_add_tail、spi_sync

 

   //spi_driver的构建


   
   
  1. static struct spi_driver m25p80_driver = {
  2. .driver = {
  3. .name = "m25p80",
  4. .bus =&spi_bus_type,
  5. .owner = THIS_MODULE,
  6. },
  7. .probe = m25p_probe,
  8. .remove =__devexit_p(m25p_remove),
  9. };

//spidriver的注册

spi_register_driver(&m25p80_driver);
   
   

在有匹配的spi_device时,会调用m25p_probe

probe里完成了spi_transfer、spi_message的构建;

spi_message_init、spi_message_add_tail、spi_sync、spi_write_then_read函数的调用

 

例如:


   
   
  1. static int m25p10a_read( struct m25p10a flash, loff_t from,
  2. size_t len, char *buf )
  3. {
  4. int r_count = 0, i;
  5. struct spi_transfer st[2];
  6. struct spi_message msg;
  7. spi_message_init( &msg );
  8. memset( st, 0, sizeof(st) );
  9. flash->cmd[ 0] = CMD_READ_BYTES;
  10. flash->cmd[ 1] = from >> 16;
  11. flash->cmd[ 2] = from >> 8;
  12. flash->cmd[ 3] = from;
  13. st[ 0 ].tx_buf = flash->cmd;
  14. st[ 0 ].len = CMD_SZ;
  15. spi_message_add_tail( &st[ 0], &msg );
  16. st[ 1 ].rx_buf = buf;
  17. st[ 1 ].len = len;
  18. spi_message_add_tail( &st[ 1], &msg );
  19. mutex_lock( &flash->lock );
  20. / Wait until finished previous write command. /
  21. if (wait_till_ready(flash)) {
  22. mutex_unlock( &flash->lock );
  23. return -1;
  24. }
  25. spi_sync( flash->spi, &msg );
  26. r_count = msg.actual_length - CMD_SZ;
  27. printk( "in (%s): read %d bytes\n", func, r_count );
  28. for( i = 0; i < r_count; i++ ) {
  29. printk( "0x%02x\n", buf[ i ] );
  30. }
  31. mutex_unlock( &flash->lock );
  32. return 0;
  33. }
  34. static int m25p10a_write( struct m25p10a *flash, loff_t to,
  35. size_t len, const char *buf )
  36. {
  37. int w_count = 0, i, page_offset;
  38. struct spi_transfer st[2];
  39. struct spi_message msg;
  40. write_enable( flash ); //写使能
  41. spi_message_init( &msg );
  42. memset( st, 0, sizeof(st) );
  43. flash->cmd[ 0] = CMD_PAGE_PROGRAM;
  44. flash->cmd[ 1] = to >> 16;
  45. flash->cmd[ 2] = to >> 8;
  46. flash->cmd[ 3] = to;
  47. st[ 0 ].tx_buf = flash->cmd;
  48. st[ 0 ].len = CMD_SZ;
  49. //填充spi_transfer,将transfer放在队列后面
  50. spi_message_add_tail( &st[ 0], &msg );
  51. st[ 1 ].tx_buf = buf;
  52. st[ 1 ].len = len;
  53. spi_message_add_tail( &st[ 1], &msg );
  54. spi_sync( flash->spi, &msg ); 调用spi_master发送spi_message
  55. return 0;
  56. }
  57. static int m25p10a_probe(struct spi_device *spi)
  58. {
  59. int ret = 0;
  60. struct m25p10a flash;
  61. char buf[ 256 ];
  62. flash = kzalloc( sizeof(struct m25p10a), GFP_KERNEL );
  63. flash->spi = spi;
  64. /* save flash as driver's private data */
  65. spi_set_drvdata( spi, flash );
  66. memset( buf, 0x7, 256 );
  67. m25p10a_write( flash, 0, 20, buf); //0地址写入20个7
  68. memset( buf, 0, 256 );
  69. m25p10a_read( flash, 0, 25, buf ); //0地址读出25个数
  70. return 0;
  71. }


到目前为止,完成了SPI的驱动和应用



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值