700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > linux一个spi总线挂多个设备 Linux SPI总线设备驱动模型详解

linux一个spi总线挂多个设备 Linux SPI总线设备驱动模型详解

时间:2022-06-05 19:49:42

相关推荐

linux一个spi总线挂多个设备 Linux SPI总线设备驱动模型详解

随着技术不断进步,系统的拓扑结构越来越复杂,对热插拔、跨平台移植性的要求越来越高,早期的内核难以满足这些要求,从linux2.6内核开始,引入了总线设备驱动模型。其实在linux2.4总线的概念就已经提出来了,直到2.6版本的内核才运用。

Linux系统中有很多条总线,如I2C、USB、platform、PCI等。

以spi为例,假如有M种不同类型CPU,N中不同SPI外设,在写裸机驱动的时候,M种CPU驱动同一个外设需要M份代码,而N种外设使用同一个cpu又需要N份代码,所以需要M*N份代码,这是典型的高内聚低耦合架构。

这种网状的拓扑结构是不符合人的逻辑思维的,将M*N种耦合变成M+1+N中耦合,将大大减少linux移植工作。

在系统中抽象出一条SPI总线,然后总线中(总线注册的那个文件spi.c和spi.h,I2C总线注册是i2c-core.c和i2c-core.h)包含SPI控制器抽象结构体spi_master等,spi控制器和外设之间交互采用spi总线提供的标准api来进行,控制器设备和外设驱动填充相关结构体。

试想一下usb,当我们把鼠标或者键盘插入电脑时,是不是会有个驱动加载的过程?这就是在寻找总线上的驱动。总线有一种义务,就是感知设备在总线上的挂载和卸载,同时有义务去寻找与设备匹配的驱动。我们的spi也一样,当有外设挂载到spi总线上的时候,就会寻找总线上所有的驱动与之匹配,匹配成功,则由该驱动服务这个设备。反过来,总线有义务感知驱动在总线上的挂载和卸载,当驱动挂载到总线时,会寻找与之匹配的设备,该驱动就服务于匹配的设备。

总线在内核中的抽象

在linux内核中,总线由bus_type结构描述,定义在linux/device.h中。

[cpp]view plaincopy

structbus_type{

constchar*name;/*总线名称*/

int(*match)(structdevice*dev,struct

device_driver*drv);/*驱动与设备的匹配函数*/

………

}

主要关注match函数,当有一个设备挂载到一条总线上的时候,总线要把这个设备和挂载到这条总线上的驱动一一进行匹配,匹配的函数就是这个match指针。

总线的注册与注销

注册:bus_register(struct bus_type *bus)若成功,新的总线将被添加进系统,并可在/sys/bus下看到相应的目录。

注销:void bus_unregister(struct bus_type *bus)。

进入到板子的/sys/bus目录,ls一下,可以看到系统所有的总线。

随便进入一个目录,如SPI目录

Devices目录表示这条总线上所有挂载的设备。Drivers目录表示这条总线上所有的设备。

下面以一个示例来注册一条总线到系统中,一般情况下,是不需要另外添加总线到设备中的。添加的总线名字叫my_bus,加载驱动之后,会在/sys/bus目录下看到一个my_bus目录。

新建bus.c:

[cpp]view plaincopy

#include

#include

#include

#include

intmy_match(structdevice*dev,structdevice_driver*drv)

{

printk("my_matchwasrun\n");

return!strncmp(dev->kobj.name,drv->name,strlen(drv->name));

}

structbus_typemy_bus_type={

.name="my_bus",//总线名称

.match=my_match,//驱动与设备匹配函数

};

EXPORT_SYMBOL(my_bus_type);

staticintmy_bus_init()

{

intret;

ret=bus_register(&my_bus_type);

returnret;

}

staticvoidmy_bus_exit()

{

bus_unregister(&my_bus_type);

}

module_init(my_bus_init);

module_exit(my_bus_exit);

MODULE_LICENSE("GPL");

首先,总线也是内核的一个模块,我们把它编译成.ko的方式加载到内核,总线的名字是”my_bus”,总线的匹配函数是my_match,当总线上的驱动和设备都挂载上去时,会调用my_match函数进行配对,配对也很简单,就是对比驱动和设备名字是否相同。返回非0表示my_match匹配成功,返回0表示匹配失败。

EXPORT_SYMBOL(my_bus_type);将my_bus_type结构导出给外部文件用,因为设备和驱动都需要指明要挂载到哪条总线上。

编写Makefile,使之生成.ko模块,加载bus.ko,然后在/sys/bus目录下会生成my_bus目录

驱动描述结构

在Linux内核中,驱动由device_driver结构表示。

[cpp]view plaincopy

structdevice_driver{

{

constchar*name;/*驱动名称*/

structbus_type*bus;/*驱动程序所在的总线*/

int(*probe)(structdevice*dev);

………

}

Name表示驱动的名字;bus表示驱动要挂载到哪条总线上,待会儿将挂载到刚刚创建的my_bus总线上;probe表示驱动和设备匹配成功之后要运行的函数。

驱动的注册与注销:

驱动的注册使用:int driver_register(struct device_driver *drv)

驱动的注销使用:void driver_unregister(struct device_driver *drv)

接下来编写driver.c文件,编译成模块,将驱动加载到内核并挂载到my_bus总线上。

[cpp]view plaincopy

#include

#include

#include

#include

externstructbus_typemy_bus_type;

intmy_probe(structdevice*dev)

{

printk("driverfoundthedevicreitcanhandle\n");

return0;

}

structdevice_drivermy_driver=

{

.name="yty",//驱动名字

.bus=&my_bus_type,//属于哪条总线

.probe=my_probe,

};

staticintmy_driver_init()

{

returndriver_register(&my_driver);

}

staticintmy_driver_exit()

{

driver_unregister(&my_driver);

}

module_init(my_driver_init);

module_exit(my_driver_exit);

MODULE_LICENSE("GPL");

驱动的名字叫“yty”,属于bus.c中的my_bus_type这条总线,驱动和设备匹配成功之后,就会运行my_probe函数,也就是会打印出"driver found the devicre it can handle\n"信息。

编译成.ko文件,然后insmod,在/sys/bus/my_bus/drivers目录下就生成了yty目录。

设备描述结构

在Linux内核中,设备由struct device结构表示。

[cpp]view plaincopy

structdevice{

{

constchar*init_name;/*设备的名字*/

structbus_type*bus;/*设备所在的总线*/

………

}

设备的注册与注销

设备的注册使用int device_register(struct device *dev)

设备的注销使用:void device_unregister(struct device *dev)

编写device.c文件:

[cpp]view plaincopy

#include

#include

#include

#include

externstructbus_typemy_bus_type;

structdevicemy_dev=

{

.init_name="yty",

.bus=&my_bus_type

};

staticintmy_device_init()

{

intret;

ret=device_register(&my_dev);

returnret;

}

staticvoidmy_device_exit()

{

device_unregister(&my_dev);

}

module_init(my_device_init);

module_exit(my_device_exit);

MODULE_LICENSE("GPL");

.init_name要和驱动的.那么一样,要不然匹配不上,.bus仍然是要属于my_bus总线。

编译并加载.ko文件,然后会出现如下打印:

由图可知,当挂载设备到my_bus总线上时,先调用总线上的my_match函数,然后驱动来处理这个设备,驱动中的my_probe就运行了。

总线的感性认识就到此结束了。

SPI总线设备驱动分析

在sourceInsight中打开内核代码drivers/spi/spi.c文件,然后分析。

在spi_init函数中,调用了bus_register注册一条总线,总线的名字叫做spi,spi_bus_type结构就是我们需要关注的,顺便看看.match。

看内核代码挑重要的看,不要每一行都看,直接跳到strcmp函数去,可以知道总线上驱动和设备的配备是通过比较驱动和设备的名字。如果有多个相同的设备,那么就应该定义.id了,靠id来区别我这个驱动到底是服务哪个设备。

总线的注册就讲解完毕。在spi.c中,提供了注册设备和注册驱动的标准api、提供了spi收发函数、spi初始化函数等。可以理解为spi总线向我们提供了标准的API接口。

以系统提供的范例spidev.c为例:

我们知道,在注册一个spi驱动是调用系统给我们提供的函数-spi_register_driver,这个标准的api也是由spi.c提供给我们的。通过sourceInsight跳转到spi_register_driver函数,这个函数就在spi.c中。

由前面的范例代码知道,注册一个驱动使用driver_register。

sdrv->driver.bus = &spi_bus_type;表示这个驱动属于spi这条总线。另外spidev中的probe,remove都通过指针传到了spi_register_driver函数中。设备和驱动匹配成功,调用spi_drv_probe,它经过赋值之后,是指向spidev.c中的spidev_probe。在spi通用外设驱动spidev.c中,调用spi_async来实现发送和接收数据的,spi_async也是由spi.c提供的,即”总线提供标准API”。

Spi设备挂载分析:

添加外设之后,一般都是需要修改板级逻辑的,使用spi通用驱动也不例外。在borad-sam9x5ek.c中要添加。其它cpu类似。

在ek_board_init中调用了at91_add_device_spi函数,将设备注册到系统。

用sourceInsight继续追踪该函数。at91_add_device_spi调用spi_register_board_info调用spi_register_board_info。spi_register_board_info这个函数就是在spi.c中,也就是说,总线提供标准的API注册设备到总线上。这个API其实最终还是调用device_register将设备注册到总线上。

接下来看看spi_async是如何访问到spi相关寄存器的。追踪spi_async,spi_async调用__spi_async,然后调用return master->transfer(spi, message);也就是调用master的transfer指针函数,这个函数在哪里被赋值了呢?

找到atmel_spi.c文件。S3c6410板子是spi_s3c64xx.c。然后找到probe函数,atmel是atmel_spi_probe。就会看到如下代码:

spi_alloc_master也是spi总线提供的标准API,用于申请一个spi_master结构,然后对这个结构初始化,所以spi_async将调用atmel_spi_transfer,然后我们进一步追踪代码,atmel_spi_transfer调用atmel_spi_next_message调用atmel_spi_next_xfer调用atmel_spi_next_xfer_pio,atmel_spi_next_xfer_pio函数就是真正读写寄存器的操作了。访问寄存器不能直接写哦,需要iomap哦,而且要采用专门的读写函数,如readl、readb、writel、writeb、spi_wrtel等。

假如有多个控制器,那么外设怎么和某个控制器建立关系呢?这个任务是由板级逻辑来联系的。就以刚刚的spidev板级代码来说

max_speed_hz是说明我这个spidev外设,需要使控制器100万Hz的时钟频率,bus_num说明说明spidev外设需要使用spi0控制器。

总结:

SPI,I2C,USB等采用总线的方式,将主机驱动和外设驱动分离,这样就涉及到四个软件模块:

1.主机端的驱动。根据具体的cpu芯片手册操作IIC、SPI、USB等寄存器,产生各种波形。主机端驱动大部分由原厂实现好。

2.连接主机和外设的纽带。外设驱动不直接调用主机端的驱动来产生波形,而是调用一个标准的API,由这个标准的API把这个波形的传输请求间接转发给了具体的主机端驱动。

3.外设端驱动。外设挂载到IIC、SPI、USB等总线上,我们在probe()函数中去注册它的具体类型(I2C,SPI,USB等类型),当要去访问外设的时候,就调用标准的API。如SPI读写函数spi_async,I2C读写函数:i2c_smbus_read_bytei2c_smbus_write_byte等。

4.板级逻辑。板级逻辑用来描述主机和外设如何联系在一起的,假如cpu有多个SPI控制器,cpu又接有多个SPI外设,那究竟用哪个SPI控制器去控制外设?这个管理属于板级逻辑的责任。如board-sam9x5ek.c中:.bus_num= 0,表示用SPI0去控制spi通用外设驱动spidev。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。