Linux子系统系列-SPI
在这个系列中,尽量catch point,将一些关键突破点列出,该系列来源于宋宝华老师的文章,fudan_adb大侠的一些文章和我的一些工作笔记。主要内容可能涵盖:SPI,PCI,USB,MM,PM,FS,Interrupt,可能几个难的子系统还不能尽快补上,像MM,PM,但会尽量将这个弄的简略而又不失丰富。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
像fudan_abc大侠所说的,当你理解了linux driver framework,一切隐藏在繁杂之后的简单就暴露出来了。
所以这个子系统系列的介绍会在linux驱动模型的基础上进行阐述,会偏重于framework的介绍,对于大牛可能会对这类文章不屑,但本系列仅当是一个知识备忘,当linux体系这张大网织的差不多了,会有一个全新的系列,来去繁就简,成之经典,毕竟,现阶段,对这些的感悟还不是太深,将原来的工作内容进行回忆,将现在工作碰到的问题补充,下一阶段会有更深的体会的。
由于这是这个系统的第一篇文章,可能零碎的东西介绍的会多些。
0,分层与分离
在面向对象的程序设计中,可以为某一类相似的事物定义一个基类,而具体的事物可以继承这个基类中的函数。Linux 内核中频繁使用到面向对象的设计思想。在设备驱动方面,往往为同类的设备设计了一个框架,而框架中的核心层则实现了该设备通用的一些功能。而且具体的设备不想使用核心层的函数,它可以重载之。这就是我们所说的在驱动设计中的分层思想。
此外,在驱动的设计中,我们还会使用分离的思想。如果一个设备的驱动和host的驱动休戚相关,那么,这就意味着这个普通的设备如果用在不同的host上,会采用n个版本的驱动。如果产品单一,也许感觉不到不使用分离思想来设计驱动的危害,但是我们想一下,这个世上被人们称道的多是什么?精品,艺术品!精品如何打造?注重细节,不只考虑单一需求!大家开发个东西不容易,怎么能随随便便就让它茫然众码矣呢,所以,何时何地,我们都要以打造精品的思想来要求自己,让自己的劳动力不浪费。
使用分离的思想来设计驱动的话,就够就是这样的:
外设驱动与主机控制器的驱动不相关,主机控制器的驱动不关心外设,而外设驱动也不关心主机,外设只是访问核心层的通用API进行数据传输,主机和外设之间可以进行任意的组合。相当于在控制器驱动和设备驱动之间增加一层核心层,对内对外都隐藏了对端的不确定性。仔细通读USB,SPI,PCI的代码就会发现这种思想的体现。
1,设备模型
在最新的设备驱动模型中,主要包含总线、设备和驱动三个实体,总线将设备和驱动绑定,在系统每注册一个设备的时候,会寻找与之匹配的驱动,反之,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。
所以,因此,由是之…(之所以写这么多,是因为自己在理解这个设备模型的时候,对照代码产生很多疑问,特别是在这儿。现在回过头来看,觉得很显而易见的啊,:)看来那个什么Q还是有些问题),根据这个模型的需求,一个现实的linux设备和驱动通常都需要挂接在一种总线上,否则谁来管他们的匹配啊,注册驱动和注册设备都是由不同的API来完成的。对于本身依附于PCI,USB,I2C,SPI等的设备而言,这自然不是问题,但是在嵌入式系统里面,Controller系统中集成的外设控制器,挂载在内存空间的外设确不依附于此类总线。那咋整呢?而且这些东西还不少,像SPI控制器,PCI控制器啊都是这些,这些很多都已经是主了,还让他们靠谁去?基于这一背景,Linux发明了一种虚拟的总线(就是我们所说的除了政治领袖以外的精神领袖),称为platform总线,相应的设备称为platform_device,而驱动称为platform_driver。
2,platform总线
这个框架中为platform总线定义了一个bus_type的实例platform_bus_type:
1 struct bus_type platform_bus_type = {
2 .name = "platform",
3 .dev_attrs = platform_dev_attrs,
4 .match = platform_match,
5 .uevent = platform_uevent,
6 .pm = PLATFORM_PM_OPS_PTR,
7 };
8 EXPORT_SYMBOL_GPL(platform_bus_type);
这里重点关注其match成员函数,正是此成员表明了platform_device和platform_driver之间如何匹配。
1 static int platform_match(struct device *dev, struct device_driver *drv)
2 {
3 struct platform_device *pdev;
4
5 pdev = container_of(dev, struct platform_device, dev);
6 return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
7 }
从代码中可以看出,匹配platform_device和platform_driver主要看两者的name字段是否相同。
对platform_device的定义通常在BSP包里面实现(即arch目录下的),在BSP文件中,将platform_device归纳为一个数组,最终通过platform_add_devices()函数统一注册。platform_add_devices()函数可以讲平台设备添加到系统中。
3,SPI
讲了这么多才说道SPI,看来老婆没有说错我啊,罗里啰嗦……因为该文档权当备忘,所以接下来还会罗嗦几句SPI的缘由
3.1 what is SPI
SPI(同步外设接口)是由摩托罗拉公司开发的全双工同步串行总线,其接口由MISO(串行数据输入),MOSI(串行数据输出),SCK(串行移位时钟),SS(从使能信号)四种信号构成(当然了,现在芯片技术日新月异,SPI模块的结构也在变化中,象OMAP系列中的SPI模块还支持5线的一种模式),SS决定了唯一的与主设备通信的从设备,主设备通过产生移位时钟来发起通讯。通讯时,数据由MOSI输出,MISO输入,数据在时钟的上升或下降沿由MOSI输出,在紧接着的下降或上升沿由MISO读入,这样经过8/16次时钟的改变,完成8/16位数据的传输。
SPI模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性(CPOL)和相位(CPHA)可以进行配置。如果 CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。
3.2 SPI Framework
#控制器设备和驱动
控制器设备在BSP的初始化中注册,驱动在drvier/spi中
#核心层驱动
核心层代码负责这个框架中通用的部分,满足分层的思想。主题承担的工作包括:注册spi总线,提供基本SPI总线操作API。
#SPI外设驱动
对于SPI的设备驱动,因为可爱的linux driver framework设计者的功劳,这里我们只需要用到spi.h中定义的方法就可以了,不用去修改spi控制器的代码。一般的,我们的设备驱动框架是使用spi_regiser_driver向系统进行注册,就可以让系统用你指定的与.name相匹配的硬件交互并执行你的读写请求,满足分离的思想。
spi.h中大部分函数中都会用到struct spi_device *spi这个指针,在probe函数中获得这个指针,保存好这个指针,就可以在驱动中的任何地方通过他去处理与spi设备相关的操作。
3.3 SPI控制器驱动
SPI控制器要挂载到platform总线上的,so,
需要在BSP文件中添加相应的资源代码:
通常,会在xxxx.c中添加:
static struct platform_device da850_spi_pdev1 = {
.name = "dm_spi",
.id = 1,
.resource = da850_spi_resources1,
.num_resources = ARRAY_SIZE(da850_spi_resources1),
.dev = {
.platform_data = &da850_spi_pdata1,
},
};
然后会在BSP的init过程中使用platform_device_register将它注册进系统
MACHINE_START(DAVINCI_DA850_EVM, "Gemstones Platform version 0.03")
.phys_io = IO_PHYS,
.io_pg_offst = (__IO_ADDRESS(IO_PHYS) >> 18) & 0xfffc,
.boot_params = (0xC0000100),
.map_io = da850_map_io,
.init_irq = da850_evm_irq_init,
.timer = &davinci_timer,
.init_machine = da850_evm_init,
MACHINE_END
-> da850_evm_init()->da850_init_spi1()->platform_device_register(&da850_spi_pdev1)
自此,将SPI控制器设备注册进了系统,name字段为dm_spi
我们还注意到,设备的资源信息还可以放在控制器的设备资源里面,利用platform提供的platform_data的支持,其中platform_data的形式是自定义的。设备可以通过以下方式拿到platform_data信息:
struct xxxx_plat_data *pdata = pdev->dev.platform_data;
其中,pdev为platform_device的指针,当然也可以通过别的方法,只要能获得device的指针。
到这里先小总结一下:
控制器驱动的流程主要就是:
第一步:注册platform_device;第二步:注册platform_driver,然后platform总线会让两者匹配在一起。
使用platform总线在驱动中大体有以下几个好处:
a,使得设备被挂接在一个总线上,使配套的sysfs节点、设备电源管理都成为可能。
b, 隔离了BSP和驱动。BSP中定义platform设备和设备使用的资源(可以使用platform_data的形式来包括platform设备的设备),设备的具体配置信息。而在驱动中,只需要通过通用API去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。
以上注册完设备,下面就要开始注册驱动。
注册platform driver,首先要有platform_driver数据结构
static struct platform_driver davinci_spi_driver = {
.driver = {
.name = "dm_spi",
.owner = THIS_MODULE,
},
.probe = davinci_spi_probe,
.remove = davinci_spi_remove,
.suspend = davinci_spi_suspend,
.suspend_late = davinci_spi_suspend_late,
.resume_early = davinci_spi_resume_early,
};
name字段要和device保持一致。
然后将probe,remove,suspend,suspend_late,resume_early函数注册,driver framwork会在合适的时候调用。
其中probe函数是比较重要的一个,该函数中将创建davinci_spi数据结构,这个数据结构在文档上描述为spi驱动的私有数据,该结构中包括spi_bitbang这个重要的数据结构,它的重要性在probe函数最后介绍。
在Linux中,每一个类型的驱动都会有一个相应的结构体来描述,这里的spi_master就是用来描述SPI主机控制器驱动的,其主要成员是bus_num,cs,spi模式和时钟设置用到的函数,数据传输用到的函数等。
分配、注册和注销SPI主机驱动结构体的API由SPI核心层提供:
struct spi_master * spi_alloc_master(struct device *host, unsigned size);
int spi_register_master(struct spi_master *master);
void spi_unregister_master(struct spi_master *master);
这里可能会有疑问,spi master controller 注册对应的驱动框架里的哪一层?按说,spi 主机控制器已经作为platform设备都注册过了。
这里就需要用驱动设计里面的分层思想来解释。使用到platform总线API注册到platform总线上的控制器设备和驱动,都是common的部分。specific的部分,会在platform driver注册后,在probe函数里面基于注册的common部分的资源信息来具体实现。称之为spi_master的注册部分。
一个master对应一个spi总线,或者说是一个spi接口,
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);
};
仔细浏览代码可以发现,在SPI向platform总线注册的时候,象platform_device中的device_data或driver_data都是空的,并没有将它赋值,这些都是在probe函数中完成的。
统观之,probe函数里面的数据结构的设计和代码都是按照驱动框架的要求来实现的,主要包括spi controller memory的映射,IRQ的注册,controller的reset和对register进行初始化赋值。最后,它将调用spi_bitbang_start()来创建一个work queue,由此SPI设备驱动可以向这个工作队列注册transfer方法。
在啰唆下去,估计光控制器这块的内容就要溢出了,该系列仅作为一个工作备忘和总结,一个框架性的介绍就够了,正如fudan_abc大侠所说的,源码,specific,datasheet是搞通linux的三件宝……
3.4 SPI设备驱动
SPI设备要挂载到SPI总线上,这个过程是由SPI core提供的一些API来完成的,spi_register_driver(),spi_register_device()。
module init函数中使用spi_register_driver注册外设驱动,并注册char设备,导出SPI操作API。
SPI外设驱动遍布内核的driver的各个子目录下,SPI是一种总线,spi_driver的作用是将spi外设挂接到该总线上,因此在spi_driver的probe()成员函数中,将注册SPI外设本身所属设备驱动的类型。和platform_driver对应着一个platform_device一样,spi_driver也对应着一个spi_device。platform_device需要在BSP文件(即arch)中添加板信息数据,而spi_device也需要,spi_device的板级信息用spi_board_info结构体来描述,该结构体记录SPI外设使用的主机控制器序号、片选序号、比特率、SPI传输模式等。在系统startup过程中,会通过前面提到的init_machine来对spi_register_board_info()进行调用,将spi的设备信息注册进系统。
这里大家可能会又有疑问,这里只是注册了board_info,那device在哪注册的?
内核中认为SPI是不可以热插拔的,SPI设备的注册不是在arch init的时候初始化的,我们可以从代码中发现在控制器驱动加载的时候,probe函数里面会使用spi_register_master来注册SPI Master,在该函数中调用scan_boardinfo->spi_new_device,在spi_new_device函数中,我们会发现spi_device数据结构,然后在代码中我们 会发现device_register将spi_device注册到系统(没有使用spi_register_device,其实殊途同归)。
3.5 SPI传输
在SPI外设驱动中,当透过SPI总线进行数据传输的时候,使用了一套与CPU无关的统一接口,这套接口最关键的一个数据结构就是spi_transfer,它用于描述SPI传输
struct spi_transfer {
const void *tx_buf;
void *rx_buf;
unsigned len;
dma_addr_t tx_dma;
dma_addr_t rx_dma;
unsigned cs_change:1;
u8 bits_per_word;
u16 delay_usecs;
u32 speed_hz;
struct list_head transfer_list;
};
而一次完整的SPI传输流程可能不只包含1次spi_transfer,它可能包含1个或多个spi_transfer,这些spi_transfer最终通过spi_message组织在一起,其定义如代码
struct spi_message {
struct list_head transfers;
struct spi_device *spi;
unsigned is_dma_mapped:1;
void (*complete)(void *context);
void *context;
unsigned actual_length;
int status;
struct list_head queue;
void *state;
};
通过spi_message_init()可以初始化spi_message,而将spi_transfer添加到spi_message队列的方法则是:
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);
发起一次spi_message的传输有同步和异步两种方式,使用同步API时,会阻塞等待这个消息被处理完。同步操作时使用的API是:
int spi_sync(struct spi_device *spi, struct spi_message *message);
使用异步API时,不会阻塞等待这个消息被处理完,但是可以在spi_message的complete字段挂接一个回调函数,当消息被处理完成后,该函数会被调用。异步操作时使用的API是:
int spi_async(struct spi_device *spi, struct spi_message *message);
下面代码是非常典型的初始化spi_transfer、spi_message并进行SPI数据传输的例子,同时它们也是SPI核心层的2个通用API,在SPI外设驱动中可以直接调用它们进行写和读操作。
static inline int
spi_write(struct spi_device *spi, const u8 *buf, size_t len)
{
struct spi_transfer t = {
.tx_buf = buf,
.len = len,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
return spi_sync(spi, &m);
}
static inline int
spi_read(struct spi_device *spi, u8 *buf, size_t len)
{
struct spi_transfer t = {
.rx_buf = buf,
.len = len,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
return spi_sync(spi, &m);
}
此外还有一个重要的结构体,新的驱动设计里被广泛使用
struct spi_bitbang {
struct workqueue_struct *workqueue;
struct work_struct work;
spinlock_t lock;
struct list_head queue;
u8 busy;
u8 use_dma;
u8 flags; /* extra spi->mode support */
struct spi_master *master;
int (*setup_transfer)(struct spi_device *spi,
struct spi_transfer *t);
void (*chipselect)(struct spi_device *spi, int is_on);
#define BITBANG_CS_ACTIVE 1 /* normally nCS, active low */
#define BITBANG_CS_INACTIVE 0
int (*txrx_bufs)(struct spi_device *spi, struct spi_transfer *t);
这是一个完成数据传输的重要结构体,数据传输是SPI接口的任务,结构体master代表了一个接口,当一个spi_message从上层函数传递下来时,master的成员函数bitbang->master->transfer将该数据传输任务添加到工作队列头。其中工作队列struct workqueue_struct *workqueue;的创建和 struct work_struct work的初始化都是在函数spi_bitbang_start()中进行的。
每次数据传输,都将要传输的数据分成多个数据段,这些数据段由数据管理结构体spi_transfer来管理。结构体spi_transfer又将挂在m->transfers。也就是说每次数据传输都将要传输的数据包装成一个结构体spi_message传输下来。
函数hw_txbyte()将要传输的数据段的第一个数据写入SPI数据寄存器XXXX_SPIDAT。即便是数据接收也得向数据寄存器写入数据才能触发一次数据的传输。只需将该数据段的第一个数据写入数据寄存器就可以触发数据传输结束中断,以后的数据就在中断处理函数中写入数据寄存器。
4,小结
分层,分离,注册平台设备,注册平台驱动,注册spi master设备,注册spi设备,注册spi驱动,注册char设备export API to userspace。通过总线驱动获得对应的设备资源,根据一些经典框架,完成各个子系统和设备的功能。