一、概述
首先我们对SPI做个认识,SPI是串行外设接口(Serial Peripheral Interface)的缩写。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线。这4根线分别是它们是数据输入SDI(MISO)、数据输出SDO(MOSI)、时钟SCLK、片选CS。
SPI常用四种数据传输模式,主要是由输出串行同步时钟极性(CPOL)和相位(CPHA)进行配置。如果CPOL= 0,串行同步时钟的空闲状态为低电平;如果CPOL= 1,串行同步时钟的空闲状态为高电平。如果CPHA= 0,在串行同步时钟的前沿(上升或下降)数据被采样;如果CPHA = 1,在串行同步时钟的后沿(上升或下降)数据被采样。下面是针对4中模式的时序图:
注:我们需要设置的模式是根据spi芯片手册上支持的模式来设置的。
二、linux下SPI驱动开发
在带有linux系统的平台中和纯单片机中对于spi驱动的开发是有着本质的区别的:在linux系统中spi驱动是作为一个子系统模块的,更通俗的讲就是一套通信协议(spi驱动也叫协议驱动),也就是说无论你选择的平台是什么,只要是用的linux内核,那么都使用的是该协议,内核提供给用户的接口函数都是一致的,驱动的实现也是一致的,是用户不需要更改的,而用户需要关心的是: 一、你要打开的spi设备节点是哪个(spidevx.x),这个spi设备节点是在spi子系统中创建的。
二、你根据平台来选择配置多少个spi控制器以及添加多少个spi设备(这些信息都是在平台依赖的板级信息里面配置)。
每个平台中从上层spi控制器到下面的spi设备都是有一定的层次结构的,可以先通过下面的图概要的理解一下,后面还会从代码的来详细的分析一下其实现原理。
![](https://i-blog.csdnimg.cn/blog_migrate/b87242bbcb6c8a490a24030b29413406.jpeg)
我们以上面的这个图为思路
1、 Platform bus
Platform bus对应的结构是platform_bus_type,这个内核开始就定义好的。对于itop4412这个平台,platform bus的定义和注册可以参看arch/arm/driver/base/platform.c文件
点击(此处)折叠或打开
- struct bus_type platform_bus_type = {
- .name = "platform", //定义platform总线
- .dev_attrs= platform_dev_attrs,
- .match= platform_match,
- .uevent= platform_uevent,
- .pm= &platform_dev_pm_ops,
- };
-
- int __init platform_bus_init(void)
- {
- int error;
- early_platform_cleanup();
-
- error = device_register(&platform_bus);
- if (error)
- return error;
-
- error = bus_register(&platform_bus_type); //注册platform总线
- if (error)
- device_unregister(&platform_bus);
- return error;
- }
-
2、Platform_device
SPI控制器对应platform_device的定义方式,同样以itop4412中的SPI控制器为例,参看arch/arm/mach-exynos/dev-spi.c文件
点击(此处)折叠或打开
- struct platform_device exynos_device_spi0 = {
- .name = "s3c64xx-spi", //名称,要和Platform_driver匹配
- .id = 0, //第0个控制器,itop4412中有3个控制器
- .num_resources = ARRAY_SIZE(exynos_spi0_resource), //占用资源的种类
- .resource = exynos_spi0_resource, //指向资源结构数组的指针
- .dev = {
- .dma_mask = &spi_dmamask, //dma寻址范围
- .coherent_dma_mask = DMA_BIT_MASK(32), //可以通过关闭cache等措施保证一致性的dma寻址范围
- .platform_data = &exynos_spi0_pdata, //特殊的平台数据,参看后文
- },
- };
-
- static struct s3c64xx_spi_info exynos_spi0_pdata = {
- .cfg_gpio = s5pc1xx_spi_cfg_gpio, //用于控制器管脚的IO配置
- .fifo_lvl_mask = 0x7f,
- .rx_lvl_offset = 15,
- .high_speed = 1,
- .clk_from_cmu = true,
- .tx_st_done = 25,
}; -
- static int exynos_spi_cfg_gpio(struct platform_device *pdev){
- int gpio;
-
- switch (pdev->id) {
- case 0:
- s3c_gpio_cfgpin(EXYNOS4_GPB(0), S3C_GPIO_SFN(2));
- s3c_gpio_cfgpin(EXYNOS4_GPB(2), S3C_GPIO_SFN(2));
- s3c_gpio_cfgpin(EXYNOS4_GPB(3), S3C_GPIO_SFN(2));
- s3c_gpio_setpull(EXYNOS4_GPB(0), S3C_GPIO_PULL_UP);
- s3c_gpio_setpull(EXYNOS4_GPB(2), S3C_GPIO_PULL_UP);
- s3c_gpio_setpull(EXYNOS4_GPB(3), S3C_GPIO_PULL_UP);
-
- for (gpio = EXYNOS4_GPB(0); gpio < EXYNOS4_GPB(4); gpio++)
- s5p_gpio_set_drvstr(gpio, S5P_GPIO_DRVSTR_LV3);
- break;
- case 1:
- s3c_gpio_cfgpin(EXYNOS4_GPB(4), S3C_GPIO_SFN(2));
- s3c_gpio_cfgpin(EXYNOS4_GPB(6), S3C_GPIO_SFN(2));
- s3c_gpio_cfgpin(EXYNOS4_GPB(7), S3C_GPIO_SFN(2));
- s3c_gpio_setpull(EXYNOS4_GPB(4), S3C_GPIO_PULL_UP);
- s3c_gpio_setpull(EXYNOS4_GPB(6), S3C_GPIO_PULL_UP);
- s3c_gpio_setpull(EXYNOS4_GPB(7), S3C_GPIO_PULL_UP);
-
- for (gpio = EXYNOS4_GPB(4); gpio < EXYNOS4_GPB(8); gpio++)
- s5p_gpio_set_drvstr(gpio, S5P_GPIO_DRVSTR_LV3);
- break;
- case 2:
- s3c_gpio_cfgpin(EXYNOS4_GPC1(1), S3C_GPIO_SFN(5));
- s3c_gpio_cfgpin(EXYNOS4_GPC1(3), S3C_GPIO_SFN(5));
- s3c_gpio_cfgpin(EXYNOS4_GPC1(4), S3C_GPIO_SFN(5));
- s3c_gpio_setpull(EXYNOS4_GPC1(1), S3C_GPIO_PULL_UP);
- s3c_gpio_setpull(EXYNOS4_GPC1(3), S3C_GPIO_PULL_UP);
- s3c_gpio_setpull(EXYNOS4_GPC1(4), S3C_GPIO_PULL_UP);
-
- for (gpio = EXYNOS4_GPC1(1); gpio < EXYNOS4_GPC1(5); gpio++)
- s5p_gpio_set_drvstr(gpio, S5P_GPIO_DRVSTR_LV3);
- break;
- default:
- dev_err(&pdev->dev, "Invalid SPI Controller number!");
- return -EINVAL;
- }
3、Platform_driver
再看platform_driver,参看drivers/spi/spi_s3c64xx.c文件
点击(此处)折叠或打开
- static struct platform_driver s3c64xx_spi_driver = {
- .driver = {
- .name = "s3c64xx-spi", //名称,和platform_device对应
- .owner = THIS_MODULE,
- },
- .remove = s3c64xx_spi_remove,
- .suspend = s3c64xx_spi_suspend,
- .resume = s3c64xx_spi_resume,
- };
-
- platform_driver_probe(&s3c64xx_spi_driver, s3c64xx_spi_probe);//注册s3c64xx_spi_driver
和平台中注册的platform_device匹配后,调用s3c64xx_spi_probe。然后根据传入的platform_device参数,构建一个用于描述SPI控制器的结构体spi_master,并注册。spi_register_master(master)。后续注册的spi_device需要选定自己的spi_master,并利用spi_master提供的传输功能传输spi数据。
和I2C类似,SPI也有一个描述控制器的对象叫spi_master。其主要成员是主机控制器的序号(系统中可能存在多个SPI主机控制器)、片选数量、SPI模式和时钟设置用到的函数、数据传输用到的函数等。
点击(此处)折叠或打开
- struct spi_master {
- struct device dev;
- s16 bus_num; //表示是SPI主机控制器的编号。由平台代码决定
- u16 num_chipselect; //控制器支持的片选数量,即能支持多少个spi设备
- int (*setup)(struct spi_device *spi); //针对设备设置SPI的工作时钟及数据传输模式等。在spi_add_device函数中调用。
- int (*transfer)(struct spi_device *spi,struct spi_message *mesg); //实现数据的双向传输,可能会睡眠
- void (*cleanup)(struct spi_device *spi); //注销时调用
- };
4、Spi bus
Spi总线对应的总线类型为spi_bus_type,在内核的drivers/spi/spi.c中定义
点击(此处)折叠或打开
- 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,
- };
点击(此处)折叠或打开
- static int spi_match_device(struct device *dev, struct device_driver *drv)
- {
- const struct spi_device *spi = to_spi_device(dev);
- return strcmp(spi->modalias, drv->name) == 0;
- }
5、spi_device
下面该讲到spi_device的构建与注册了。spi_device对应的含义是挂接在spi总线上的一个设备,所以描述它的时候应该明确它自身的设备特性、传输要求、及挂接在哪个总线上。参看arch/arm/mach-exynos/mach-itop4412.c文件。
点击(此处)折叠或打开
- static struct spi_board_info spi2_board_info[] __initdata = {
- {
- .modalias = "spidev", //sp设备的名字,这个名字将会和spidev.c中的驱动名字匹配。
- .platform_data = NULL;
- .max_speed_hz = 10*1000*1000, //最大的spi时钟频率
- .bus_num = 2, //设备连接在spi控制器0上
- .chip_select = 0, //片选线号
- .mode = SPI_MODE_0, //CPOL=0, CPHA=0 此处选择具体数据传输模式
- .controller_data = &spi2_csi[0],
- },
- };
-
- static struct s3c64xx_spi_csinfo spi2_csi[] = {
- [0] = {
- .line = EXYNOS4_GPC1(2),
- .set_level = gpio_set_value,
- .fb_delay = 0x2,
- },
- };
-
- spi_register_board_info(spi2_board_info, ARRAY_SIZE(spi2_board_info));
- //注册spi_board_info。这个代码会把spi_board_info注册要链表board_list上。
事实上上文提到的spi_master的注册会在spi_register_board_info之后,spi_master注册的过程中会调用spi_match_master_to_boardinfo(有些平台是scan_boardinfo函数)来对比传入的总线号和板级配置的总线号是否匹配,然后创建并注册spi_device。
点击(此处)折叠或打开
- static void spi_match_master_to_boardinfo(struct spi_master *master,struct spi_board_info *bi)
- {
- struct spi_device *dev;
-
- if (master->bus_num != bi->bus_num) //spi控制器的总线号和板级配置的总线是否匹配
- return;
-
- dev = spi_new_device(master, bi); //创建spi新设备
- if (!dev)
- dev_err(master->dev.parent, "can't create new device for %s\n",bi->modalias);
- }
6、spi_driver
本文先以linux内核中的/driver/spi/spidev.c驱动为参考。
点击(此处)折叠或打开
- static struct spi_driver spidev_spi_driver = { //spi_driver的构建
- .driver = {
- .name = "spidev"
- .owner = THIS_MODULE,
- },
- .probe = spidev_probe,
- .remove = __devexit_p(spidev_remove),
- };
-
- spi_register_driver(&spidev_spi_driver);//spi driver的注册
-
- 在有匹配的spi device时,会调用m25p_probe
-
- static int __devinit spidev_probe(struct spi_device *spi)
- {
- ……
- }
根据传入的spi_device参数,可以找到对应的spi_master。接下来就可以利用spi子系统为我们完成数据交互了。