linux内核SPI总线驱动分析(一)(转)

下面有两个大的模块:

一个是SPI总线驱动的分析            (研究了具体实现的过程)

另一个是SPI总线驱动的编写(不用研究具体的实现过程)

SPI总线驱动分析

 

1 SPI概述
      SPI是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口,是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。SPI是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便。
      SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要4根线,事实上3根也可以。也是所有基于SPI的设备共有的,它们是SDI(数据输入),SDO(数据输出),SCLK(时钟),CS(片选)。
      MOSI(SDO):主器件数据输出,从器件数据输入。
      MISO(SDI):主器件数据输入,从器件数据输出。
      SCLK :时钟信号,由主器件产生。
      CS:从器件使能信号,由主器件控制。
      其中CS是控制芯片是否被选中的,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),对此芯片的操作才有效,这就允许在同一总线上连接多个SPI设备成为可能。需要注意的是,在具体的应用中,当一条SPI总线上连接有多个设备时,SPI本身的CS有可能被其他的GPIO脚代替,即每个设备的CS脚被连接到处理器端不同的GPIO,通过操作不同的GPIO口来控制具体的需要操作的SPI设备,减少各个SPI设备间的干扰。
      SPI是串行通讯协议,也就是说数据是一位一位从MSB或者LSB开始传输的,这就是SCK时钟线存在的原因,由SCK提供时钟脉冲,MISO、MOSI则基于此脉冲完成数据传输。 SPI支持4-32bits的串行数据传输,支持MSB和LSB,每次数据传输时当从设备的大小端发生变化时需要重新设置SPI Master的大小端。

2 Linux SPI驱动总体架构
      在2.6的linux内核中,SPI的驱动架构可以分为如下三个层次:SPI 核心层、SPI控制器驱动层和SPI设备驱动层。
      Linux 中SPI驱动代码位于drivers/spi目录。
2.1 SPI核心层
      SPI核心层是Linux的SPI核心部分,提供了核心数据结构的定义、SPI控制器驱动和设备驱动的注册、注销管理等API。其为硬件平台无关层,向下屏蔽了物理总线控制器的差异,定义了统一的访问策略和接口;其向上提供了统一的接口,以便SPI设备驱动通过总线控制器进行数据收发。
      Linux中,SPI核心层的代码位于driver/spi/ spi.c。由于该层是平台无关层,本文将不再叙述,有兴趣可以查阅相关资料。
2.2 SPI控制器驱动层
      SPI控制器驱动层,每种处理器平台都有自己的控制器驱动,属于平台移植相关层。它的职责是为系统中每条SPI总线实现相应的读写方法。在物理上,每个SPI控制器可以连接若干个SPI从设备。
      在系统开机时,SPI控制器驱动被首先装载。一个控制器驱动用于支持一条特定的SPI总线的读写。一个控制器驱动可以用数据结构struct spi_master来描述。

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


struct spi_master {
struct device dev;
s16 bus_num;
u16 num_chipselect;
int (*setup)(struct spi_device *spi);
int (*transfer)(struct spi_device *spi, struct spi_message *mesg);
void (*cleanup)(struct spi_device *spi);
};

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

 cleanup注销时候调用

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


struct spi_driver {
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
void (*shutdown)(struct spi_device *spi);
int (*suspend)(struct spi_device *spi, pm_message_t mesg);
int (*resume)(struct spi_device *spi);
struct device_driver driver;
};

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


struct spi_device {
struct device dev;
struct spi_master *master;
u32 max_speed_hz;
u8 chip_select;
u8 mode;
u8 bits_per_word;
int irq;
void *controller_state;
void *controller_data;
char modalias[];
};

.modalias   = "m25p10",

.mode   =SPI_MODE_0,   //CPOL=0, CPHA=0 此处选择具体数据传输模式

.max_speed_hz    = 10000000, //最大的spi时钟频率

/* Connected to SPI-0 as 1st Slave */

.bus_num    = 0,   //设备连接在spi控制器0上

.chip_select    = 0, //片选线号,在S5PC100的控制器驱动中没有使用它作为片选的依据,而是选择了下文controller_data里的方法。

.controller_data = &smdk_spi0_csi[0],

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

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

spi_device的板信息用spi_board_info结构体来描述:
struct spi_board_info {
charmodalias[SPI_NAME_SIZE];
const void*platform_data;
void*controller_data;
intirq;
u32max_speed_hz;
u16bus_num;
u16chip_select;
u8mode;
};

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

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

1.

static struct spi_board_info s3c_spi_devs[] __initdata = {

{

.modalias = "m25p10a",

.mode = SPI_MODE_0,

.max_speed_hz = ,

.bus_num = ,

.chip_select = ,

.controller_data = &smdk_spi0_csi[SMDK_MMCSPI_CS],

},

};

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就构建并注册完成了!!!!!!!!!!!!!

 

5 spi_driver的构建与注册

 

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

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

//spi_driver的构建

static struct spi_driver   m25p80_driver = { 

.driver = {

        .name   ="m25p80",

        .bus    =&spi_bus_type,

        .owner  = THIS_MODULE,

    },

    .probe  = m25p_probe,

    .remove =__devexit_p(m25p_remove),

};

//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函数的调用

 

例如:


 */
static int m25p10a_read( struct m25p10a *flash, loff_t from,
size_t len, char *buf )
{
int r_count = , i;
struct spi_transfer st[];
struct spi_message msg; spi_message_init( &msg );
memset( st, , sizeof(st) ); flash->cmd[] = CMD_READ_BYTES;
flash->cmd[] = from >> ;
flash->cmd[] = from >> ;
flash->cmd[] = from; st[ ].tx_buf = flash->cmd;
st[ ].len = CMD_SZ;
spi_message_add_tail( &st[], &msg ); st[ ].rx_buf = buf;
st[ ].len = len;
spi_message_add_tail( &st[], &msg ); mutex_lock( &flash->lock ); /* Wait until finished previous write command. */
if (wait_till_ready(flash)) {
mutex_unlock( &flash->lock );
return -;
} spi_sync( flash->spi, &msg );
r_count = msg.actual_length - CMD_SZ;
printk( "in (%s): read %d bytes\n", __func__, r_count );
for( i = ; i < r_count; i++ ) {
printk( "0x%02x\n", buf[ i ] );
} mutex_unlock( &flash->lock );
return ;
}
static int m25p10a_write( struct m25p10a *flash, loff_t to,
size_t len, const char *buf )
{
int w_count = , i, page_offset; struct spi_transfer st[]; struct spi_message msg;
write_enable( flash ); //写使能 spi_message_init( &msg ); memset( st, , sizeof(st) ); flash->cmd[] = CMD_PAGE_PROGRAM;
flash->cmd[] = to >> ;
flash->cmd[] = to >> ;
flash->cmd[] = to; st[ ].tx_buf = flash->cmd;
st[ ].len = CMD_SZ;
//填充spi_transfer,将transfer放在队列后面
spi_message_add_tail( &st[], &msg ); st[ ].tx_buf = buf;
st[ ].len = len;
spi_message_add_tail( &st[], &msg ); spi_sync( flash->spi, &msg ); 调用spi_master发送spi_message return ;
}

static int m25p10a_probe(struct spi_device *spi)
{
int ret = ;
struct m25p10a *flash;
char buf[ ];
flash = kzalloc( sizeof(struct m25p10a), GFP_KERNEL );
flash->spi = spi;
/* save flash as driver's private data */
spi_set_drvdata( spi, flash );
memset( buf, 0x7, );
m25p10a_write( flash, , , buf); //0地址写入20个7
memset( buf, , );
m25p10a_read( flash, , , buf ); //0地址读出25个数 return ;
}

 

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

linux内核SPI总线驱动分析(一)(转)的更多相关文章

  1. linux内核SPI总线驱动分析(二)(转)

    简而言之,SPI驱动的编写分为: 1.spi_device就构建并注册  在板文件中添加spi_board_info,并在板文件的init函数中调用spi_register_board_info(s3 ...

  2. Linux内核中SPI总线驱动分析

    本文主要有两个大的模块:一个是SPI总线驱动的分析 (研究了具体实现的过程): 另一个是SPI总线驱动的编写(不用研究具体的实现过程). 1 SPI概述 SPI是英语Serial Peripheral ...

  3. Linux SD/MMC/SDIO驱动分析_转

    转自:Linux SD/MMC/SDIO驱动分析    https://www.cnblogs.com/cslunatic/p/3678045.html#3053341 一.SD/MMC/SDIO概念 ...

  4. Linux内核--网络栈实现分析(十一)--驱动程序层(下)

    本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7555870 更多请查看专栏,地 ...

  5. Linux内核--网络栈实现分析(七)--数据包的传递过程(下)

    本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7545855 更多请查看专栏,地 ...

  6. Linux内核--网络栈实现分析(二)--数据包的传递过程--转

    转载地址http://blog.csdn.net/yming0221/article/details/7492423 作者:闫明 本文分析基于Linux Kernel 1.2.13 注:标题中的”(上 ...

  7. Linux内核调用I2C驱动_驱动嵌套驱动方法

    禁止转载!!!! Linux内核调用I2C驱动_以MPU6050为例 0. 导语 最近一段时间都在恶补数据结构和C++,加上导师的事情比较多,Linux内核驱动的学习进程总是被阻碍.不过,十一假期终于 ...

  8. Linux 内核 EISA 总线

    扩展 ISA (EISA) 总线是一个对 ISA 的 32-位 扩展, 带有一个兼容的接口连接器; ISA 设备板可被插入一个 EISA 连接器. 增加的线在 ISA 接触之下被连接. 如同 PCI ...

  9. 【转】 linux内核移植和驱动添加(三)

    原文网址:http://blog.chinaunix.net/uid-29589379-id-4708909.html 原文地址:linux内核移植和驱动添加(三) 作者:genehang 四,LED ...

随机推荐

  1. python 字典实现类似c的switch case

    #python 字典实现类似c的switch def print_hi(): print('hi') def print_hello(): print('hello') def print_goodb ...

  2. Android——inflate 将一个xml中定义的布局找出来

    通俗的说,inflate就相当于将一个xml中定义的布局找出来. 因为在一个Activity里如果直接用findViewById()的话,对应的是setConentView()的那个layout里的组 ...

  3. cmd中目录的变更

    1.在同一个盘符下的目录的切换使用cd加空格加子目录进行切换 2.在不同的盘符下进行切换直接使用在当前目录后面加上其他盘符的名称即可

  4. Spark核心概念

    1.Application     基于spark的用户程序,包含了一个Driver Program以及集群上中多个executor:     spark中只要有一个sparkcontext就是一个a ...

  5. UDKtoUE4Tool-UDKUE3资源移植UE4工具

    UDKtoUE4Tool UDKtoUE4Tool 是一个把UE3/UDK资源包(T3D格式)转换成UE4(T3D格式)的工具.作者Matt3D使用C#实现,未来考虑发布到Unreal Marketp ...

  6. Unity AssetBundles and Resources指引 (一)

    本文内容主要翻译自下面这篇文章 https://unity3d.com/cn/learn/tutorials/topics/best-practices/guide-assetbundles-and- ...

  7. java.lang.NoSuchMethodException

    这个异常遇到过若干次,提示信息也比较清楚的指示出它的特点,当无法找到某一特定方法时,就会抛出该异常! 我所遇到的抛出此异常的情景主要有以下两种: 1:对应的JAVA类中没有对应的属性,也就是说在页面的 ...

  8. Tomcat安装后启动一闪而过

    出现这种问题一般是环境变量没配置好.除了JDK环境变量还有Tomcat环境变量:CATALINA_HOME 和CATALINA_BASE 虽然JDK里面会含有JRE,但是最好是在环境变量里面也配置一个 ...

  9. [Nginx 1] Nginx简介

    导读:现在项目中用到这个Nginx了,本来是想着把代码调通了就得了.想想还是花点时间总结总结,就利用门卫思维吧.今天主要是一个整体的介绍,然后在学习的过程中,接着总结Nginx的其他使用事宜. 一.什 ...

  10. java根据sessionid获取session

    import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpSession; /** * * Class ...