前言

  主要是想对Linux 下spi驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动、设备模型等也不进行详细说明原理。如果有任何错误地方,请指出,谢谢!

spi介绍

  SPI接口是Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(Master Slave)架构。支持多slave模式应用,一般仅支持单Master。时钟由Master控制,在时钟移位脉冲下,数据按位传输,高位在前,低位在后(MSB first)。SPI接口有2根单向数据线,为全双工通信,目前应用中的数据速率可达几Mbps的水平。

spi传输详细介绍

总线结构如下图所示:

SPI接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。

  1. MOSI:主器件数据输出,从器件数据输入
  2. MISO:主器件数据输入,从器件数据输出
  3. SCLK: 时钟信号,由主器件产生
  4. /SS: 从器件使能信号,由主器件控制

SPI接口在内部硬件实际上是两个简单的移位寄存器,传输的数据为8位,在主器件产生的从器件使能信号和移位脉冲下,按位传输,高位在前,低位在后。如下图所示,在SCLK的下降沿上数据改变,上升沿一位数据被存入移位寄存器。

在一个SPI时钟周期内,会完成如下操作:

  1. 主机通过MOSI线发送1位数据,从机通过该线读取这1位数据;
  2. 从机通过MISO线发送1位数据,主机通过该线读取这1位数据。这是通过移位寄存器来实现的。如下图所示,主机和从机各有一个移位寄存器,且二者连接成环。随着时钟脉冲,数据按照从高位到低位的方式依次移出主机寄存器和从机寄存器,并且依次移入从机寄存器和主机寄存器。当寄存器中的内容全部移出时,相当于完成了两个寄存器内容的交换。

SPI接口具有如下优点:

  1. 支持全双工操作;
  2. 操作简单;
  3. 数据传输速率较高(相对的)。

同时,它也具有如下缺点:

  1. 多个spi设备需要占用主机较多的管脚(每个从机都需要一根片选线);
  2. 只支持单个主机;
  3. 没有指定的流控制,没有应答机制确认是否接收到数据。

SPI编程主要搞清一个问题:模式

SPI有四种工作模式,具体由CPOL(Clock Polarity 时钟极性),CPHA(Clock Phase时钟相位)决定

当CPOL为0时,空闲的时候SCLK电平是低电平;

当CPOL为1时,空闲的时候SCLK电平是高电平;

当CPHA为0时,采集数据发生在时钟周期的前边缘(第一个边缘,可能是上升缘也可能是下降缘,由CPOL决定),这同时意味着输出数据发生在后边缘;

当CPHA为1时,采集数据发生在时钟周期的后边缘(第二个边缘,可能是上升缘也可能是下降缘,由CPOL决定),这同时意味着输出数据发生在前边缘;

摘抄一张网上的图:

我们编写spi接口的设备驱动程序的时候,最需要关心的就是spi控制器的部分和spi设备采用的是那种模式,确定模式后,我们得将spi控制器配置成一样的模式才能正常工作。

Linux下SPI驱动框架分析

Linux的spi接口驱动实现目录在linux-2.6.35\drivers\spi下。这个目录和一些层次比较明显的驱动目录布局不同,全放在这个文件夹下,因此还是只好通过看Kconfig 和 Makefile来找找思路

先看Makefile,里面关键几行:

obj-$(CONFIG_SPI_MASTER) += spi.o //这个是针对有spi控制器的soc选项,一般的soc都有spi控制器吧,呵呵

# SPI master controller drivers (bus) //下面的这些就是针对不同soc上的spi控制器的驱动了,我们可以通过make menuconfig的时候选上自己对应平台的

obj-$(CONFIG_SPI_ATMEL)                    += atmel_spi.o
obj-$(CONFIG_SPI_BFIN) += spi_bfin5xx.o
obj-$(CONFIG_SPI_BITBANG) += spi_bitbang.o
……
……
# SPI protocol drivers (device/link on bus) #这里在最后面分析
obj-$(CONFIG_SPI_SPIDEV) += spidev.o
obj-$(CONFIG_SPI_TLE62X0) += tle62x0.o

下面这些就是针对于主机作为spi从设备的时候用的,暂时貌似没支持,毕竟现实中几乎没有用过,而是作为master端出现

# SPI slave controller drivers (upstream link)
# ... add above this line ...
# SPI slave drivers (protocol for that link)
# ... add above this line ...

再看Kconfig,第一个SPI选项我觉得有必要贴一下,首先只有选择它了才能进行后面的配置,其次它的help对spi的描述说的很清楚!

 menuconfig SPI
bool "SPI support"
depends on HAS_IOMEM
help
The "Serial Peripheral Interface" is a low level synchronous
protocol. Chips that support SPI can have data transfer rates
up to several tens of Mbit/sec. Chips are addressed with a
controller and a chipselect. Most SPI slaves don't support
dynamic device discovery; some are even write-only or read-only. SPI is widely used by microcontrollers to talk with sensors,
eeprom and flash memory, codecs and various other controller
chips, analog to digital (and d-to-a) converters, and more.
MMC and SD cards can be accessed using SPI protocol; and for
DataFlash cards used in MMC sockets, SPI must always be used. SPI is one of a family of similar protocols using a four wire
interface (select, clock, data in, data out) including Microwire
(half duplex), SSP, SSI, and PSP. This driver framework should
work with most such devices and controllers.

我们其次需要配上的选项就是SPI_MASTER(这里假设soc上是有spi控制器的,即使没有spi控制器,这个目录里也有实现通过gpio模式spi控制器的代码)和SPI_S3C24XX(这里假设是s3c平台,毕竟这个平台用于学习的最多吧)

config SPI_MASTER
# boolean "SPI Master Support"
boolean
default SPI
help
If your system has an master-capable SPI controller (which
provides the clock and chipselect), you can enable that
controller and the protocol drivers for the SPI slave chips
that are connected. config SPI_S3C24XX
tristate "Samsung S3C24XX series SPI"
depends on ARCH_S3C2410 && EXPERIMENTAL
select SPI_BITBANG
help
SPI driver for Samsung S3C24XX series ARM SoCs

于是从Makefile里得到如下语句和我们相关:

obj-$(CONFIG_SPI_S3C24XX)       += spi_s3c24xx_hw.o
spi_s3c24xx_hw-y := spi_s3c24xx.o
spi_s3c24xx_hw-$(CONFIG_SPI_S3C24XX_FIQ) += spi_s3c24xx_fiq.o#这里不考虑s3c 的fiq方式

于是我们要分析的代码主要有:spi.c spi_s3c24xx.c,瞬间没压力了,就两个文件,呵呵

下面主要从三个方面来分析spi框架

  1. spi控制器驱动的实现(毕竟spi控制器的驱动还是有可能要接触的)
  2. spi设备的驱动(我们更多的是编写设备的驱动,还是以eeprom为例吧,虽然我很想以spi接口的nor flash驱动为例,但是那又会牵涉出mtd子系统,这个留在mtd子系统分析吧)
  3. spi核心层的实现(上面1、2都是以各自的驱动实现为目标,并不深入到spi核心层,也就是至于spi核心层怎么为我们提供的服务不去关心,只需要按spi核心层使用它提供的服务就是了。所以现在统一分析spi核心层,看它是怎么提供的服务)

spi控制器驱动的实现

spi_s3c24xx.c为例,直接看s3c24xx_spi_init

 static struct platform_driver s3c24xx_spi_driver = {
.remove = __exit_p(s3c24xx_spi_remove),
.driver = {
.name = "s3c2410-spi",
.owner = THIS_MODULE,
.pm = S3C24XX_SPI_PMOPS,
},
}; static int __init s3c24xx_spi_init(void)
{
return platform_driver_probe(&s3c24xx_spi_driver, s3c24xx_spi_probe);
}

如果是我们自己实现,也会采用这种平台驱动的方式吧,平台驱动的内部流程就不分析了,直接看匹配成功后s3c24xx_spi_probe的调用,但这里还是插入平台spi控制器设备端相关的代码:

arch/arm/plat-s3c24xx/devs.c中:

 static struct resource s3c_spi0_resource[] = {
[0] = {
.start = S3C24XX_PA_SPI,
.end = S3C24XX_PA_SPI + 0x1f,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_SPI0,
.end = IRQ_SPI0,
.flags = IORESOURCE_IRQ,
}
}; static u64 s3c_device_spi0_dmamask = 0xffffffffUL;
struct platform_device s3c_device_spi0 = {
.name = "s3c2410-spi",
.id = 0,
.num_resources = ARRAY_SIZE(s3c_spi0_resource),
.resource = s3c_spi0_resource,
.dev = {
.dma_mask = &s3c_device_spi0_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
}; static struct resource s3c_spi1_resource[] = {
[0] = {
.start = S3C24XX_PA_SPI + S3C2410_SPI1,
.end = S3C24XX_PA_SPI + S3C2410_SPI1 + 0x1f,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_SPI1,
.end = IRQ_SPI1,
.flags = IORESOURCE_IRQ,
}
}; static u64 s3c_device_spi1_dmamask = 0xffffffffUL;
struct platform_device s3c_device_spi1 = {
.name = "s3c2410-spi",
.id = 1,
.num_resources = ARRAY_SIZE(s3c_spi1_resource),
.resource = s3c_spi1_resource,
.dev = {
.dma_mask = &s3c_device_spi1_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};

还没贴完,我一直在找platform_add_devices3c_spi1_resource或者s3c_device_spi1加入到平台总线的代码,但是没找到!汗!!! 突然想到网上的一句话:spi的驱动处于被忽视的。现在看来有道理,在s3c soc相关代码里只看到了gpio模拟的spi驱动,但这不影响我们分析spi控制器驱动,现在看s3c24xx_spi_probe

它主要的工作:

  1. 调用spi核心层的接口分配一个spi核心层能识别的spi控制器对象struct spi_master并额外分配出我们驱动需要的上下文数据空间sizeof(struct s3c24xx_spi),这个空间的地址可以通过核心层提供的接口提取出来:
master = spi_alloc_master(&pdev->dev, sizeof(struct s3c24xx_spi));
hw = spi_master_get_devdata(master);
  1. 提取spi控制器平台设备指定的平台数据
hw->pdata = pdata = pdev->dev.platform_data;

在上面贴出的平台设备端代码里面没有设置平台数据,它的类型为:

 struct s3c2410_spi_info {
int pin_cs; /* simple gpio cs */
unsigned int num_cs; /* total chipselects */
int bus_num; /* bus number to use. */
unsigned int use_fiq:1; /* use fiq */ void (*gpio_setup)(struct s3c2410_spi_info *spi, int enable);
void (*set_cs)(struct s3c2410_spi_info *spi, int cs, int pol); };

这个其实是必须指定的,且里面的以下成员是必须初始化的:

spi核心层会用到有:

num_cs表示该控制器支持的spi设备的数量

bus_num表示该控制器衍生出的总线号,如果小于0则spi核心层采用动态分配的

spi控制器驱动会用到的有:

gpio_setup 配置spi控制器要用的管脚

set_cs 片选回调接口,用来选中某个spi设备

然后根据平台设备指定的资源(寄存器地址空间、中断号)向操作系统请求资源空间并建立起映射为以后所用:

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
hw->ioarea = request_mem_region(res->start, resource_size(res),
pdev->name);
hw->regs = ioremap(res->start, resource_size(res));//映射以后要操作的寄存器地址区间
hw->irq = platform_get_irq(pdev, 0);
err = request_irq(hw->irq, s3c24xx_spi_irq, 0, pdev->name, hw); //注册中断
  1. spi硬件控制器初始化

    使能spi控制器部分的时钟
hw->clk = clk_get(&pdev->dev, "spi");
clk_enable(hw->clk);

设置spi控制器寄存器为默认状态:

writeb(0xff, hw->regs + S3C2410_SPPRE);
writeb(SPPIN_DEFAULT, hw->regs + S3C2410_SPPIN);
writeb(SPCON_DEFAULT, hw->regs + S3C2410_SPCON);

配置spi控制器相关的管脚

 if (hw->pdata) {
if (hw->set_cs == s3c24xx_spi_gpiocs)
gpio_direction_output(hw->pdata->pin_cs, 1); if (hw->pdata->gpio_setup)
hw->pdata->gpio_setup(hw->pdata, 1);
}
  1. master初始化
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; //指定该控制器支持的模式,spi接口的设备模式必须属于这个范畴才能被成功的加入
master->num_chipselect = hw->pdata->num_cs;//spi接口的设备号必须属于这个范围[0,num_cs]
master->bus_num = pdata->bus_num; //spi接口设备的总线号必须和这个匹配才会被添加到该控制器的管制下
hw->master->setup = s3c24xx_spi_setup;//每个spi设备添加的时候,都会以spi设备对象的指针为参数调用该接口,主要是让控制器有一个能对spi设备setup的过程 hw->master->cleanup = s3c24xx_spi_cleanup;//每个spi设备删除的时候,都会以spi设备对象的指针为参数调用该接口
  1. 注册到spi核心层
 status = spi_register_master(bitbang->master);

暂时不进入到spi核心层分析,这里我们只需要知道调用核心层的注册函数后,核心层会遍历所有注册到核心层的设备(实际最开始是加入到一个全局链表里,和I2C核心层的实现类似),然后尝试着添加每一个总线号为该控制器衍生出的总线号的设备到该spi控制器上,当然如果该spi控制器不支持某一个设备,那就取消添加这个设备,如果添加成功,那么该设备将会添加到spi总线上去,这条总线是spi核心层注册的,用来管理spi接口的设备和spi接口的驱动。

这里假设核心层在spi_register_master里找到了一个适合的设备(总线号匹配且cs_num有效),那么setup会被调用,也就是s3c24xx_spi_setup

这里多了一个数据类型:struct s3c24xx_spi_devstate,在spi控制器的probe里面s3c有定义控制器设备的数据类型,现在又定义了spi设备的数据类型,也就是说只要是想将spi设备加入到该spi控制器,那就必须提供s3c指定的设备相关的数据类型,下面看看该数据类型的定义

 /**
* s3c24xx_spi_devstate - per device data
* @hz: Last frequency calculated for @sppre field.
* @mode: Last mode setting for the @spcon field.
* @spcon: Value to write to the SPCON register.
* @sppre: Value to write to the SPPRE register.
*/ struct s3c24xx_spi_devstate {
unsigned int hz;
unsigned int mode;//这个比较重要,在前面spi硬件介绍已经说过,时钟极性、时钟相位
u8 spcon;
u8 sppre;
};

可以看出这些成功都是来指示spi控制器,在操作这个设备的时候应该怎么配置它的寄存器的。

s3c24xx_spi_setup里面可以看出,即使spi接口的设备没有提供该数据类型,该spi控制器的驱动也会使用一个默认的设置:

 if (!cs) {
cs = kzalloc(sizeof(struct s3c24xx_spi_devstate), GFP_KERNEL);
if (!cs) {
dev_err(&spi->dev, "no memory for controller state\n");
return -ENOMEM;
} cs->spcon = SPCON_DEFAULT;
cs->hz = -1;
spi->controller_state = cs;
}

然后会调用s3c24xx_spi_update_state,它其实就是将设备指定的模式、寄存器、hz等根据spi控制器的做个转换,为以后真正操作该设备的时候拿出来用。

spi设备的驱动

以eeprom为例,看文件at25.c:

 static struct spi_driver at25_driver = {
.driver = {
.name = "at25",
.owner = THIS_MODULE,
}, .probe = at25_probe,
.remove = __devexit_p(at25_remove),
}; static int __init at25_init(void)
{
return spi_register_driver(&at25_driver);
}

直接调用spi核心层提供的函数注册,也就是说它不需要关心是哪个控制器来实现最终的spi数据传输。从这里也可以看出核心层的作用,分隔了控制器和设备驱动的关联性,主要两边的驱动都容易实现。

前面将控制器驱动是以s3c为例的(我都有点后悔用s3c为例了,呵呵),而s3c上暂时没有使用到spi控制器,也没有这里at25要驱动的设备代码,所以我下面提取设备需要添加的代码的部分从其他平台提取,其实只要知道原理就行了,对吧!

以ti的一个soc dm365平台的arch/arm/mach-davinci/Board-dm355-evm.c为例:

 static struct spi_eeprom at25640a = {
.byte_len = SZ_64K / 8,
.name = "at25640a",
.page_size = 32,
.flags = EE_ADDR2,
}; static struct spi_board_info dm355_evm_spi_info[] __initconst = {
{
.modalias = "at25",
.platform_data = &at25640a,
.max_speed_hz = 10 * 1000 * 1000, /* at 3v3 */
.bus_num = 0,
.chip_select = 0,
.mode = SPI_MODE_0,
},
}; dm355_init_spi0(BIT(0), dm355_evm_spi_info,
ARRAY_SIZE(dm355_evm_spi_info)); void __init dm355_init_spi0(unsigned chipselect_mask,
struct spi_board_info *info, unsigned len)
{
/* for now, assume we need MISO */
davinci_cfg_reg(DM355_SPI0_SDI); /* not all slaves will be wired up */
if (chipselect_mask & BIT(0))
davinci_cfg_reg(DM355_SPI0_SDENA0); if (chipselect_mask & BIT(1))
davinci_cfg_reg(DM355_SPI0_SDENA1); spi_register_board_info(info, len);//这也是核心层提供的函数,用来将spi设备的数据添加的一个全局的链表,在spi控制器驱动加载的时候会扫描该链表 platform_device_register(&dm355_spi0_device);
}

注意:因为不是s3c的平台,前面将s3c平台的时候s3c有定义它要求的spi设备的数据类型struct s3c24xx_spi_devstate,而我们这是另外一个平台的spi设备,它用的是spi下eeprom通用的数据类型struct spi_eeprom。(我还是后悔没在分析s3c spi控制器的时候就用dm365 soc作为目标,毕竟我以前也搞过dm385/8这类soc)

现在我们回到at25_init

这里假设spi控制器已经添加,且由于spi控制器的添加,spi设备也被同时添加到spi总线,所以这里直接看at25_probe:

主要工作:

  1. 提取设备的数据 这个就是刚才贴出来的那段代码设置的。
const struct spi_eeprom *chip;
chip = spi->dev.platform_data;

也就是

static struct spi_eeprom at25640a = {
.byte_len = SZ_64K / 8,
.name = "at25640a",
.page_size = 32,
.flags = EE_ADDR2,
};
  1. 分配并初始化一个对象用来描述一个这样的eeprom设备,在at25中它定义了类型为struct at25_data来描述它,也可以说它是驱动的上下文数据啦
  2. 创建应用层用来操作的文件,这个和I2C中eeprom的驱动实现类似,这里就不再重复原理了
sysfs_bin_attr_init(&at25->bin);
at25->bin.attr.name = "eeprom";
at25->bin.attr.mode = S_IRUSR;
at25->bin.read = at25_bin_read;
at25->bin.size = at25->chip.byte_len;
if (!(chip->flags & EE_READONLY)) {
at25->bin.write = at25_bin_write;
at25->bin.attr.mode |= S_IWUSR;
at25->mem.write = at25_mem_write;
}

貌似这样就完成了,驱动实现at25_bin_readat25_bin_write来处理应用层的读写。

下面以at25_bin_read为例分析下数据的通讯:

Spi核心定义了struct spi_transferstruct spi_message来辅佐一次数据的传送,那么我们在at25_bin_read里面要做的肯定就是将buf及offset、count转换成transfer和message了,方法如下:

         spi_message_init(&m);//初始化message,它代表一次传输
memset(t, 0, sizeof t);//初始化transfer,它代表一次传输中的一个事务,一次传输可以有多个事务 t[0].tx_buf = command; // command包含了读命令及读的位置偏移
t[0].len = at25->addrlen + 1;//buf的长度,也就是command的长度
spi_message_add_tail(&t[0], &m);//将该消息加入到这次传输中 t[1].rx_buf = buf;//要发送的数据
t[1].len = count;// 要发送的数据长度
spi_message_add_tail(&t[1], &m); //将该消息加入到这次传输中

最后调用status = spi_sync(at25->spi, &m);将消息送到核心层(其实就是由核心层调用控制器送出数据)。

这样驱动就完成了,很简单吧!主要是因为核心层帮我们做了很多事情,下面开始分析核心层的实现

spi核心层的实现

主要看spi.c文件:

static int __init spi_init(void);
postcore_initcall(spi_init);

从这里可以知道spi_init的调用(也就是spi核心层的初始化)是在驱动加载前的。

spi_init主要工作:

  1. 分配一个buf,这个在后面的处理中会用到它
buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
  1. 注册spi总线,这个总线前面有说过也用到过
status = bus_register(&spi_bus_type);
struct bus_type spi_bus_type = {
.name = "spi",
.dev_attrs = spi_dev_attrs,
.match = spi_match_device,
.uevent = spi_uevent,
.suspend = spi_suspend,
.resume = spi_resume,
};
  1. 注册一个spi master(控制器)类
status = class_register(&spi_master_class);

所有注册到核心层的spi控制器都属于这个class。

现在我们分析下在spi控制器驱动和spi接口的设备驱动中有调用过的spi核心层的接口

控制器有调用spi_register_master,下面来分析它:

  1. 如果总线号小于0,则动态分配一个,这个前面有提过:
 if (master->bus_num < 0) {
/* FIXME switch to an IDR based scheme, something like
* I2C now uses, so we can't run out of "dynamic" IDs
*/
master->bus_num = atomic_dec_return(&dyn_bus_id);
dynamic = 1;
}
  1. 将该spi控制器(控制器也是一个设备)添加到设备模型中
dev_set_name(&master->dev, "spi%u", master->bus_num);

status = device_add(&master->dev);
  1. 最关键的部分,也是前面有提到的扫描设备并进行设备的注册 scan_boardinfo(master);

它里面的实现:

list_for_each_entry(bi, &board_list, list) {//扫描board_list链表,这个链表里面的设备成员的添加在spi设备驱动那有贴出即spi_register_board_info。
struct spi_board_info *chip = bi->board_info;
unsigned n; for (n = bi->n_board_info; n > 0; n--, chip++) {
if (chip->bus_num != master->bus_num)//检查总线号
continue;
/* NOTE: this relies on spi_new_device to
* issue diagnostics when given bogus inputs
*/ (void) spi_new_device(master, chip);//最终的添加设备实现
}
} spi_new_device:
分配并初始化一个spi_device,它的初始化是根据设备注册时的信息来初始化的:
proxy = spi_alloc_device(master);
proxy->chip_select = chip->chip_select;
proxy->max_speed_hz = chip->max_speed_hz;
proxy->mode = chip->mode;
proxy->irq = chip->irq;
strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias));
proxy->dev.platform_data = (void *) chip->platform_data;
proxy->controller_data = chip->controller_data;
proxy->controller_state = NULL;

最终添加的spi总线:

              status = spi_add_device(proxy);

需要注意的是它里面除了调用device_add外,还调用了status = spi_setup(spi);这里面实现控制器注册的接口的回调。

好了,spi_register_master分析完了,它主要的工作就是将自己添加的设备模型的同时,添加所有符合当前控制器号的所有spi设备到spi总线

下面分析下spi_register_driver,它就相对来说容易很多了:

int spi_register_driver(struct spi_driver *sdrv)
{
sdrv->driver.bus = &spi_bus_type; if (sdrv->probe)
sdrv->driver.probe = spi_drv_probe;
if (sdrv->remove)
sdrv->driver.remove = spi_drv_remove;
if (sdrv->shutdown)
sdrv->driver.shutdown = spi_drv_shutdown;
return driver_register(&sdrv->driver);
}

它主要是指定了spi总线,并将驱动注册到设备模型中

下面分析下spi_sync,这个函数是我们前面在分析应用层at25_bin_read读的过程中遇到的一个函数:

int spi_sync(struct spi_device *spi, struct spi_message *message)
{
DECLARE_COMPLETION_ONSTACK(done);//在栈上定义一个完成量用来同步
int status; message->complete = spi_complete;//回调函数
message->context = &done;
status = spi_async(spi, message);//真正发起操作的函数,异步的 if (status == 0) {
wait_for_completion(&done);//阻塞等待完成,当spi_complete被调用时才会继续往下走,spi_complete的调用是在本次操作完成后以回调的形式message->complete调用的
status = message->status;
}
message->context = NULL;
return status;
}

下面看spi_async

int spi_async(struct spi_device *spi, struct spi_message *message)
{
struct spi_master *master = spi->master;//提取当前设备对应的spi控制器,我们最终的操作肯定是由控制器来完成的 /* Half-duplex links include original MicroWire, and ones with
* only one data pin like SPI_3WIRE (switches direction) or where
* either MOSI or MISO is missing. They can also be caused by
* software limitations.
*/ if ((master->flags & SPI_MASTER_HALF_DUPLEX)
|| (spi->mode & SPI_3WIRE)) {
struct spi_transfer *xfer;
unsigned flags = master->flags; list_for_each_entry(xfer, &message->transfers, transfer_list) {
if (xfer->rx_buf && xfer->tx_buf)
return -EINVAL; if ((flags & SPI_MASTER_NO_TX) && xfer->tx_buf)
return -EINVAL;
if ((flags & SPI_MASTER_NO_RX) && xfer->rx_buf)
return -EINVAL;
}
} message->spi = spi;
message->status = -EINPROGRESS;
return master->transfer(spi, message);

调用控制器的transfer将消息发送出去,这个master->transfer是在控制器的s3c24xx_spi_probe—> spi_bitbang_start里面指定:

if (!bitbang->master->transfer)
bitbang->master->transfer = spi_bitbang_transfer;
}

于是最终会调用spi_bitbang_transfer来完成数据的发送,下面看spi_bitbang_transfer的实现:

int spi_bitbang_transfer(struct spi_device *spi, struct spi_message *m)
{
struct spi_bitbang *bitbang;
unsigned long flags;
int status = 0; m->actual_length = 0;
m->status = -EINPROGRESS; bitbang = spi_master_get_devdata(spi->master); spin_lock_irqsave(&bitbang->lock, flags); if (!spi->max_speed_hz)
status = -ENETDOWN;
else {
list_add_tail(&m->queue, &bitbang->queue);
queue_work(bitbang->workqueue, &bitbang->work);//将当前消息加入到一个链表,采用工作队列的方式来异步处理消息的发送
} spin_unlock_irqrestore(&bitbang->lock, flags); return status;
}

这里的工作队列bitbang->workqueue和工作也是在控制器的s3c24xx_spi_probe—> spi_bitbang_start里面初始化的:

INIT_WORK(&bitbang->work, bitbang_work);
bitbang->workqueue = create_singlethread_workqueue(
dev_name(bitbang->master->dev.parent));

于是bitbang_work最终会被调用,下面看bitbang_work,它是最终控制器处理发送的函数:

主要的工作是:

从链表里面取出一个消息spi_message,然后将消息里面的spi_transfer一个个取出来发送出去,这里面的实现就和具体的spi硬件控制器相关了,这里不再分析了。

最后再说一下前面分析Makefile的时候有两句:

# SPI protocol drivers (device/link on bus)
obj-$(CONFIG_SPI_SPIDEV) += spidev.o
obj-$(CONFIG_SPI_TLE62X0) += tle62x0.o

这里的spidev在Kconfig描述如下:

config SPI_SPIDEV
tristate "User mode SPI device driver support"
depends on EXPERIMENTAL
help
This supports user mode SPI protocol drivers.
Note that this application programming interface is EXPERIMENTAL
and hence SUBJECT TO CHANGE WITHOUT NOTICE while it stabilizes.

可以看出,它是为了支持在应用层实现驱动的驱动,这就类似于I2C里面的i2c-dev.c的实现吧!spidev.c是在核心层基础之上将SPI controller模拟成一个字符型的驱动,向文件系统提供标准的文件系统接口,用来操作对应的SPI controller。

比较简单,所以也不再分析了。

关于设备树里spi相关的补充请参考linux驱动基础系列--linux spi驱动框架分析(续)

------------------------------完毕!

2014年5月

linux驱动基础系列--linux spi驱动框架分析的更多相关文章

  1. linux驱动基础系列--linux spi驱动框架分析(续)

    前言 这篇文章是对linux驱动基础系列--linux spi驱动框架分析的补充,主要是添加了最新的linux内核里设备树相关内容. spi设备树相关信息 如之前的文章里所述,控制器的device和s ...

  2. linux驱动基础系列--Linux mmc sd sdio驱动分析

    前言 主要是想对Linux mmc子系统(包含mmc sd sdio)驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.块设备驱动.设备模型等也不进行详细说明原 ...

  3. linux驱动基础系列--Linux I2c驱动分析

    前言 主要是想对Linux I2c驱动框架有一个整体的把控,因此会忽略协议上的某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型.sysfs等也不进行详细说明原理,涉及到i2c协议部分也只 ...

  4. linux驱动基础系列--Linux下Spi接口Wifi驱动分析

    前言 本文纯粹的纸上谈兵,我并未在实际开发过程中遇到需要编写或调试这类驱动的时候,本文仅仅是根据源码分析后的记录!基于内核版本:2.6.35.6 .主要是想对spi接口的wifi驱动框架有一个整体的把 ...

  5. linux驱动基础系列--Linux 串口、usb转串口驱动分析

    前言 主要是想对Linux 串口.usb转串口驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如字符设备驱动.平台驱动等也不进行详细说明原理.如果有任何错误地方,请指出, ...

  6. linux驱动基础系列--linux rtc子系统

    前言 linux驱动子系统太多了,连时钟也搞了个子系统,这导致一般的时钟芯片的驱动也会涉及到至少2个子系统,一个是时钟芯片接口子系统(比如I2c接口的时钟芯片),一个是内核给所有时钟芯片提供的rtc子 ...

  7. Linux驱动修炼之道-SPI驱动框架源码分析(上)【转】

    转自:http://blog.csdn.net/lanmanck/article/details/6895318 SPI驱动架构,以前用过,不过没这个详细,跟各位一起分享: 来自:http://blo ...

  8. Linux基础系列—Linux体系结构和Linux内核结构

    /** ****************************************************************************** * @author    暴走的小 ...

  9. Linux入门基础(一):Linux基本操作

    命令行BASH基本操作 Shell 用户不能直接操作内核,所以用户操作通过shell传递给内核 shell分为两种 : GUI 图形界面 (linux一般是GNOME) CLI 命令行界面 (linu ...

随机推荐

  1. CSP201403-1:相反数

    引言:CSP(http://www.cspro.org/lead/application/ccf/login.jsp)是由中国计算机学会(CCF)发起的“计算机职业资格认证”考试,针对计算机软件开发. ...

  2. Leetcode 54. Spiral Matrix & 59. Spiral Matrix II

    54. Spiral Matrix [Medium] Description Given a matrix of m x n elements (m rows, n columns), return ...

  3. 时间动态协同过滤(TimeSVD++)

    原作者 原论文地址 http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.379.1951&rep=rep1&type=pd ...

  4. Hyperledger04

    代码 'use strict'; var Fabric_Client = require('fabric-client'); var path = require('path'); var util ...

  5. [leetcode-646-Maximum Length of Pair Chain]

    You are given n pairs of numbers. In every pair, the first number is always smaller than the second ...

  6. Android—实现科大讯飞语音合成

    背景(可以不看) 实验室项目开发的APP需要有语音提示功能,之前的做法是人工录音,剪辑片段,调用Android的多媒体,播放,呵呵呵,,,这是21世纪!这样肯定显得有点low啊,且不说档次,应用场景也 ...

  7. Delphi 检查文件是否存在

    Delphi下检查文件是否存在,我们可以使用FileExists函数 其原形如下: Function FileExists(const FileName: string): Boolean; 示例: ...

  8. NIO Q&A(持续补充。。。。)

    Q:NIO是非阻塞的.但调用的selector.select()方法会阻塞.这和NIO非阻塞岂不是矛盾了? A:非阻塞指的是 IO 事件本身不阻塞,但是获取 IO 事件的 select 方法是需要阻塞 ...

  9. Mac Java配置JAVA——HOME

    命令行中输入:  export JAVA_HOME=$(/usr/libexec/java_home) 

  10. BZOJ4602: [Sdoi2016]齿轮 DFS 逆元

    这道题就是一个DFS,有一篇奶牛题几乎一样.但是这道题卡精度. 这道题网上的另一篇题解是有问题的.取对数这种方法可以被轻松卡.比如1e18 与 (1e9-1)*(1e9+1)取对数根本无法保证不被卡精 ...