spi介绍
SPI接口是Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(Master Slave)架构。支持多slave模式应用,一般仅支持单Master。时钟由Master控制,在时钟移位脉冲下,数据按位传输,高位在前,低位在后(MSB first)。SPI接口有2根单向数据线,为全双工通信,目前应用中的数据速率可达几Mbps的水平。
spi传输详细介绍
总线结构如下图所示:
看一下2440
SPI接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。
- MOSI:主器件数据输出,从器件数据输入
- MISO:主器件数据输入,从器件数据输出
- SCLK: 时钟信号,由主器件产生
- /SS: 从器件使能信号,由主器件控制
SPI接口在内部硬件实际上是两个简单的移位寄存器,传输的数据为8位,在主器件产生的从器件使能信号和移位脉冲下,按位传输,高位在前,低位在后。如下图所示,在SCLK的下降沿上数据改变,上升沿一位数据被存入移位寄存器。
在一个SPI时钟周期内,会完成如下操作:
- 主机通过MOSI线发送1位数据,从机通过该线读取这1位数据;
- 从机通过MISO线发送1位数据,主机通过该线读取这1位数据。这是通过移位寄存器来实现的。如下图所示,主机和从机各有一个移位寄存器,且二者连接成环。随着时钟脉冲,数据按照从高位到低位的方式依次移出主机寄存器和从机寄存器,并且依次移入从机寄存器和主机寄存器。当寄存器中的内容全部移出时,相当于完成了两个寄存器内容的交换。
SPI接口具有如下优点:
- 支持全双工操作;
- 操作简单;
- 数据传输速率较高(相对的)。
同时,它也具有如下缺点:
- 多个spi设备需要占用主机较多的管脚(每个从机都需要一根片选线);
- 只支持单个主机;
- 没有指定的流控制,没有应答机制确认是否接收到数据。
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-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_ROCKHIP
(这里假设是s3c平台,毕竟这个平台用于学习的最多吧)
3288
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_ROCKCHIP_CORE
tristate "ROCKCHIP SPI controller core support"
help
general driver for SPI controller core from ROCKCHIP
config SPI_ROCKCHIP
tristate "ROCKCHIP SPI interface driver"
depends on SPI_ROCKCHIP_CORE
config SPI_ROCKCHIP_TEST
bool "ROCKCHIP spi test code"
depends on SPI_ROCKCHIP
于是从Makefile里得到如下语句和我们相关:
obj-$(CONFIG_SPI_ROCKCHIP_CORE) += spi-rockchip-core.o
obj-$(CONFIG_SPI_ROCKCHIP) += spi-rockchip.o
obj-$(CONFIG_SPI_ROCKCHIP_TEST) += spi-rockchip-test.o
于是我们要分析的代码主要有:spi.c spi-rockchip-test.c spi-rockchip-core.c spi-rockchip.c,瞬间没压力了,就这几个文件
下面主要从三个方面来分析spi框架
- spi控制器驱动的实现(毕竟spi控制器的驱动还是有可能要接触的)
- spi设备的驱动(我们更多的是编写设备的驱动,还是以eeprom为例吧,虽然我很想以spi接口的nor flash驱动为例,但是那又会牵涉出mtd子系统,这个留在mtd子系统分析吧)
- spi核心层的实现(上面1、2都是以各自的驱动实现为目标,并不深入到spi核心层,也就是至于spi核心层怎么为我们提供的服务不去关心,只需要按spi核心层使用它提供的服务就是了。所以现在统一分析spi核心层,看它是怎么提供的服务)
spi控制器驱动的实现
以spi-rockchip.c
为例,直接看rockchip_spi_init
:
#ifdef CONFIG_OF
static const struct of_device_id rockchip_spi_dt_match[] = {
{ .compatible = "rockchip,rockchip-spi",
},
{ },
};
MODULE_DEVICE_TABLE(of, rockchip_spi_dt_match);
#endif /* CONFIG_OF */
static struct platform_driver rockchip_spi_driver = {
.driver = {
.name = "rockchip-spi",
.owner = THIS_MODULE,
.pm = &rockchip_spi_pm,
.of_match_table = of_match_ptr(rockchip_spi_dt_match),
},
.remove = rockchip_spi_remove,
};
static int __init rockchip_spi_init(void)
{
return platform_driver_probe(&rockchip_spi_driver, rockchip_spi_probe);
}
如果是我们自己实现,也会采用这种平台驱动的方式吧,平台驱动的内部流程就不分析了,直接看匹配成功后rockchip_spi_probe
的调用,但这里还是插入平台spi控制器设备端相关的代码:
新的内核使用了设备树,在设备树加载完成后,会把设备树节点自动转换成platform_device格式,名字放到of_node地方来进行匹配,下面来在设备树中进行验证
kernel/arch/arm/boot/dts/rk3288.dtsi
spi2: spi@ff130000 {
compatible = "rockchip,rockchip-spi";
reg = <0xff130000 0x1000>;
interrupts = <GIC_SPI 46 IRQ_TYPE_LEVEL_HIGH>;
#address-cells = <1>;
#size-cells = <0>;
pinctrl-names = "default";
pinctrl-0 = <&spi2_txd &spi2_rxd &spi2_clk &spi2_cs0 &spi2_cs1>;
rockchip,spi-src-clk = <2>;
num-cs = <2>;
clocks = <&clk_spi2>, <&clk_gates6 6>;
clock-names = "spi","pclk_spi2";
dmas = <&pdma1 15>, <&pdma1 16>;
#dma-cells = <2>;
dma-names = "tx", "rx";
status = "disabled";
};
由于设备树中包含了compatible信息和platform_driver中和以匹配上,所以调用了rockchip_spi_probe
rockchip_spi_probe:
它主要的工作:
- 调用spi核心层的接口分配一个spi核心层能识别的spi控制器对象
struct spi_master
并额外分配出我们驱动需要的上下文数据空间sizeof(struct s3c24xx_spi)
,这个空间的地址可以通过核心层提供的接口提取出来:
spi设备的驱动
spi核心层的实现
主要看spi.c文件:
static int __init spi_init(void);
postcore_initcall(spi_init);
从这里可以知道spi_init
的调用(也就是spi核心层的初始化)是在驱动加载前的。
spi_init
主要工作:
- 分配一个buf,这个在后面的处理中会用到它
buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
- 注册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,
.pm = &spi_pm,
};
- 注册一个spi master(控制器)类
status = class_register(&spi_master_class);
所有注册到核心层的spi控制器都属于这个class。
现在我们分析下在spi控制器驱动和spi接口的设备驱动中有调用过的spi核心层的接口
控制器有调用spi_register_master
,下面来分析它:
- 如果总线号小于0,则动态分配一个,这个前面有提过:
设备树
有三个节点用于对该soc的spi控制器的描述:
kernel/arch/arm/boot/dts/rk3288.dtsi
spi0: spi@ff110000 {
compatible = "rockchip,rockchip-spi";
reg = <0xff110000 0x1000>;
interrupts = <GIC_SPI 44 IRQ_TYPE_LEVEL_HIGH>;
#address-cells = <1>;
#size-cells = <0>;
pinctrl-names = "default";
pinctrl-0 = <&spi0_txd &spi0_rxd &spi0_clk &spi0_cs0 &spi0_cs1>;
rockchip,spi-src-clk = <0>;
num-cs = <2>;
clocks =<&clk_spi0>, <&clk_gates6 4>;
clock-names = "spi","pclk_spi0";
dmas = <&pdma1 11>, <&pdma1 12>;
#dma-cells = <2>;
dma-names = "tx", "rx";
status = "disabled";
};
spi1: spi@ff120000 {
compatible = "rockchip,rockchip-spi";
reg = <0xff120000 0x1000>;
interrupts = <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH>;
#address-cells = <1>;
#size-cells = <0>;
pinctrl-names = "default";
pinctrl-0 = <&spi1_txd &spi1_rxd &spi1_clk &spi1_cs0>;
rockchip,spi-src-clk = <1>;
num-cs = <1>;
clocks = <&clk_spi1>, <&clk_gates6 5>;
clock-names = "spi","pclk_spi1";
dmas = <&pdma1 13>, <&pdma1 14>;
#dma-cells = <2>;
dma-names = "tx", "rx";
status = "disabled";
};
spi2: spi@ff130000 {
compatible = "rockchip,rockchip-spi";
reg = <0xff130000 0x1000>;
interrupts = <GIC_SPI 46 IRQ_TYPE_LEVEL_HIGH>;
#address-cells = <1>;
#size-cells = <0>;
pinctrl-names = "default";
pinctrl-0 = <&spi2_txd &spi2_rxd &spi2_clk &spi2_cs0 &spi2_cs1>;
rockchip,spi-src-clk = <2>;
num-cs = <2>;
clocks = <&clk_spi2>, <&clk_gates6 6>;
clock-names = "spi","pclk_spi2";
dmas = <&pdma1 15>, <&pdma1 16>;
#dma-cells = <2>;
dma-names = "tx", "rx";
status = "disabled";
};
当我们3288中用到spi2控制器时,我们只需要在kernel/arch/arm/boot/dts/rp-rk3288.dts中添加节点: