SPI基础

 

spi介绍

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

spi传输详细介绍

总线结构如下图所示:

看一下2440

 

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-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框架

  1. spi控制器驱动的实现(毕竟spi控制器的驱动还是有可能要接触的)
  2. spi设备的驱动(我们更多的是编写设备的驱动,还是以eeprom为例吧,虽然我很想以spi接口的nor flash驱动为例,但是那又会牵涉出mtd子系统,这个留在mtd子系统分析吧)
  3. 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:

它主要的工作:

  1. 调用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主要工作:

  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,
	.pm		= &spi_pm,
};
  1. 注册一个spi master(控制器)类
status = class_register(&spi_master_class);

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

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

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

  1. 如果总线号小于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中添加节点:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值