Linux下的SPI设备驱动详解
- 格式:ppt
- 大小:2.27 MB
- 文档页数:23
Linux内核中SPI总线驱动分析本文主要有两个大的模块:一个是SPI总线驱动的分析(研究了具体实现的过程);另一个是SPI总线驱动的编写(不用研究具体的实现过程)。
1 SPI概述SPI是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口,是Motorola首先在其MC68HCXX系列处理器上定义的。
SPI接口主要应用在EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。
SPI是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便。
SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要4根线,事实上3根也可以。
也是所有基于SPI的设备共有的,它们是SDI(数据输入),SDO(数据输出),SCLK(时钟),CS(片选)。
MOSI(SDO):主器件数据输出,从器件数据输入。
MISO(SDI):主器件数据输入,从器件数据输出。
SCLK :时钟信号,由主器件产生。
CS:从器件使能信号,由主器件控制。
其中CS是控制芯片是否被选中的,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),对此芯片的操作才有效,这就允许在同一总线上连接多个SPI设备成为可能。
需要注意的是,在具体的应用中,当一条SPI总线上连接有多个设备时,SPI本身的CS 有可能被其他的GPIO脚代替,即每个设备的CS脚被连接到处理器端不同的GPIO,通过操作不同的GPIO口来控制具体的需要操作的SPI设备,减少各个SPI设备间的干扰。
SPI是串行通讯协议,也就是说数据是一位一位从MSB或者LSB开始传输的,这就是SCK 时钟线存在的原因,由SCK提供时钟脉冲,MISO、MOSI则基于此脉冲完成数据传输。
SPI 支持4-32bits的串行数据传输,支持MSB和LSB,每次数据传输时当从设备的大小端发生变化时需要重新设置SPI Master的大小端。
如何在Zynq 7000平台上使用Linux spidev.c驱动
在上一篇博客中,介绍了如何配置Vivado下的硬件工程、例化SPI硬件接口和如何使用petalinux加载Xilinx提供的SPI总线驱动,如果要通过SPI 控制外部器件,还需要添加SPI的设备驱动以实现SPI的对外控制逻辑。
在Linux内核的driver/spi目录下有许多外设的设备驱动可以参考,这篇博客主要介绍如何使用其中的spidev,c这个设备驱动来实现对外设的控制。
spidev是一个通用的SPI外设驱动,它提供了spi字符驱动的注册,并向上层应用程序提供了I/O控制接口,当我们仅需要利用SPI接口向外设发送和接收简单的控制序列时,可直接使用该驱动,下面介绍具体的使用方法。
一、在前一篇博客中,我们采用xilinx针对Zynq 7000处理器提供的spi-cadence.c驱动实现了芯片上SPI总线驱动的注册,接下来需要修改设备树文件以时我们的外设挂接在SPI总线下。
在petalinux工程的../subsystems/linux/configs/device-tree目录下找到zynq 相关的设备树文件,目录所包含的文件如下图所示。
Linux下的SPI总线驱动(一)2013-04-12 15:08:46分类:LINUX版权所有,转载请说明转自一.SPI理论介绍SPI总线全名,串行外围设备接口,是一种串行的主从接口,集成于很多微控制器内部。
和I2C使用2根线相比,SPI总线使用4根线:MOSI (SPI 总线主机输出/ 从机输入)、MISO (SPI总线主机输入/从机输出)、SCLK(时钟信号,由主设备产生)、CS(从设备使能信号,由主设备控制)。
由于SPI总线有专用的数据线用于数据的发送和接收,因此可以工作于全双工,当前市面上可以找到的SPI外围设备包括RF芯片、智能卡接口、E2PROM、RTC、触摸屏传感器、ADC。
SCLK信号线只由主设备控制,从设备不能控制信号线。
同样,在一个基于SPI的设备中,至少有一个主控设备。
这样传输的特点:这样的传输方式有一个优点,与普通的串行通讯不同,普通的串行通讯一次连续传送至少8位数据,而SPI允许数据一位一位的传送,甚至允许暂停,因为SCLK 时钟线由主控设备控制,当没有时钟跳变时,从设备不采集或传送数据。
也就是说,主设备通过对SCLK时钟线的控制可以完成对通讯的控制。
SPI还是一个数据交换协议:因为SPI的数据输入和输出线独立,所以允许同时完成数据的输入和输出。
不同的SPI 设备的实现方式不尽相同,主要是数据改变和采集的时间不同,在时钟信号上沿或下沿采集有不同定义,具体请参考相关器件的文档。
在点对点的通信中,SPI接口不需要进行寻址操作,且为全双工通信,显得简单高效。
在多个从设备的系统中,每个从设备需要独立的使能信号,硬件上比I2C 系统要稍微复杂一些。
二.SPI驱动移植我们下面将的驱动的移植是针对Mini2440的SPI驱动的移植Step1:在Linux Source Code中修改arch/arm/mach-s3c2440/文件,加入头文件:#include <linux/spi/>#include <../mach-s3c2410/include/mach/>然后加入如下代码:static struct spi_board_info s3c2410_spi0_board[] ={[0] = {.modalias = "spidev", us_num = 0, hip_select = 0, rq = IRQ_EINT9, ax_speed_hz = 500 * 1000,in_cs = S3C2410_GPG(2),.num_cs = 1, us_num = 0, pio_setup = s3c24xx_spi_gpiocfg_bus0_gpe11_12_13, odalias = "spidev",.bus_num = 1,.chip_select = 0,.irq = IRQ_EINT2,.max_speed_hz = 500 * 1000,}};static struct s3c2410_spi_info s3c2410_spi1_platdata = {.pin_cs = S3C2410_GPG(3),.num_cs = 1,.bus_num = 1,.gpio_setup = s3c24xx_spi_gpiocfg_bus1_gpg5_6_7,};Step2:在mini2440_devices[]平台数组中添加如下代码:&s3c_device_spi0,&s3c_device_spi1,Step3:最后在mini2440_machine_init函数中加入如下代码:&s3c2410_spi0_platdata;spi_register_board_info(s3c2410_spi0_board, ARRAY_SIZE(s3c2410_spi0_board)); &s3c2410_spi1_platdata;spi_register_board_info(s3c2410_spi1_board, ARRAY_SIZE(s3c2410_spi1_board)); Step4:最后需要修改arch/arm/plat-s3c24xx/KConfig文件找到config S3C24XX_SPI_BUS0_GPE11_GPE12_GPE13boolhelpSPI GPIO configuration code for BUS0 when connected toGPE11, GPE12 and GPE13.config S3C24XX_SPI_BUS1_GPG5_GPG6_GPG7boolhelpSPI GPIO configuration code for BUS 1 when connected toGPG5, GPG6 and GPG7.修改为config S3C24XX_SPI_BUS0_GPE11_GPE12_GPE13bool "S3C24XX_SPI_BUS0_GPE11_GPE12_GPE13"helpSPI GPIO configuration code for BUS0 when connected toGPE11, GPE12 and GPE13.config S3C24XX_SPI_BUS1_GPG5_GPG6_GPG7bool "S3C24XX_SPI_BUS1_GPG5_GPG6_GPG7"helpSPI GPIO configuration code for BUS 1 when connected toGPG5, GPG6 and GPG7.Step5:最后make menuconfig配置,选中System Type和SPI support相应文件Step6:执行make生成zInage,将编译好的内核导入开发板,并且编译测试程序运行即可。
L i n u x下S P I驱动测试程序-CAL-FENGHAI-(2020YEAR-YICAI)_JINGBIANLinux下的SPI总线驱动(一)2013-04-12 15:08:46分类:LINUX版权所有,转载请说明转自一.SPI理论介绍SPI总线全名,串行外围设备接口,是一种串行的主从接口,集成于很多微控制器内部。
和I2C使用2根线相比,SPI总线使用4根线:MOSI (SPI 总线主机输出/ 从机输入)、 MISO (SPI总线主机输入/从机输出)、SCLK(时钟信号,由主设备产生)、CS(从设备使能信号,由主设备控制)。
由于SPI总线有专用的数据线用于数据的发送和接收,因此可以工作于全双工,当前市面上可以找到的SPI外围设备包括RF芯片、智能卡接口、E2PROM、RTC、触摸屏传感器、ADC。
SCLK信号线只由主设备控制,从设备不能控制信号线。
同样,在一个基于SPI的设备中,至少有一个主控设备。
这样传输的特点:这样的传输方式有一个优点,与普通的串行通讯不同,普通的串行通讯一次连续传送至少8位数据,而SPI允许数据一位一位的传送,甚至允许暂停,因为SCLK 时钟线由主控设备控制,当没有时钟跳变时,从设备不采集或传送数据。
也就是说,主设备通过对SCLK时钟线的控制可以完成对通讯的控制。
SPI还是一个数据交换协议:因为SPI的数据输入和输出线独立,所以允许同时完成数据的输入和输出。
不同的SPI设备的实现方式不尽相同,主要是数据改变和采集的时间不同,在时钟信号上沿或下沿采集有不同定义,具体请参考相关器件的文档。
在点对点的通信中,SPI接口不需要进行寻址操作,且为全双工通信,显得简单高效。
在多个从设备的系统中,每个从设备需要独立的使能信号,硬件上比I2C 系统要稍微复杂一些。
二.SPI驱动移植我们下面将的驱动的移植是针对Mini2440的SPI驱动的移植Step1 :在Linux Source Code中修改arch/arm/mach-s3c2440/文件,加入头文件:#include <linux/spi/>#include <../mach-s3c2410/include/mach/>然后加入如下代码:static struct spi_board_info s3c2410_spi0_board[] ={[0] = {.modalias = "spidev", us_num = 0, hip_select = 0, rq = IRQ_EINT9, ax_speed_hz = 500 * 1000, in_cs = S3C2410_GPG(2),.num_cs = 1, us_num = 0, pio_setup = s3c24xx_spi_gpiocfg_bus0_gpe11_12_13, odalias = "spidev",.bus_num = 1,.chip_select = 0,.irq = IRQ_EINT2,.max_speed_hz = 500 * 1000,}};static struct s3c2410_spi_info s3c2410_spi1_platdata = {.pin_cs = S3C2410_GPG(3),.num_cs = 1,.bus_num = 1,.gpio_setup = s3c24xx_spi_gpiocfg_bus1_gpg5_6_7,};Step2:在mini2440_devices[]平台数组中添加如下代码:&s3c_device_spi0,&s3c_device_spi1,Step3:最后在mini2440_machine_init函数中加入如下代码:&s3c2410_spi0_platdata;spi_register_board_info(s3c2410_spi0_board, ARRAY_SIZE(s3c2410_spi0_board));&s3c2410_spi1_platdata;spi_register_board_info(s3c2410_spi1_board, ARRAY_SIZE(s3c2410_spi1_board));Step4:最后需要修改arch/arm/plat-s3c24xx/KConfig文件找到config S3C24XX_SPI_BUS0_GPE11_GPE12_GPE13boolhelpSPI GPIO configuration code for BUS0 when connected toGPE11, GPE12 and GPE13.config S3C24XX_SPI_BUS1_GPG5_GPG6_GPG7boolhelpSPI GPIO configuration code for BUS 1 when connected toGPG5, GPG6 and GPG7.修改为config S3C24XX_SPI_BUS0_GPE11_GPE12_GPE13bool "S3C24XX_SPI_BUS0_GPE11_GPE12_GPE13"helpSPI GPIO configuration code for BUS0 when connected toGPE11, GPE12 and GPE13.config S3C24XX_SPI_BUS1_GPG5_GPG6_GPG7bool "S3C24XX_SPI_BUS1_GPG5_GPG6_GPG7"helpSPI GPIO configuration code for BUS 1 when connected toGPG5, GPG6 and GPG7.Step5:最后make menuconfig配置,选中System Type和SPI support相应文件Step6:执行make生成zInage,将编译好的内核导入开发板,并且编译测试程序运行即可。
Linuxkernel有关spi设备树参数解析⼀、最近做了⼀个 spi 设备驱动从板级设备驱动升级到设备树设备驱动,这其中要了解 spi 设备树代码的解析。
⼆、设备树配置如下:503 &spi0 {504 status = "okay";505 pinctrl-name = "default";506 pinctrl-0 = <&spi0_pins>;507 ti,pindir-d0-out-d1-in;508509 wk2124A {510 compatible = "wk2124A"; // 匹配字符串511 reg = <0>; // cs512 # spi-cpha = <1>; // 配置 spi 的模式513 # spi-tx-bus-width = <1>; // 这是是 spi-tx 的总线宽度514 # spi-rx-bus-width = <1>;515 spi-max-frequency = <10000000>; // spi 最⼤速率配置516 };517 };三、代码跟踪// drivers/spi/spi.c2772 postcore_initcall(spi_init); // spi_init2733 static int __init spi_init(void)2734 {2735 int status;27362737 buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);2738 if (!buf) {2739 status = -ENOMEM;2740 goto err0;2741 }27422743 status = bus_register(&spi_bus_type);2744 if (status < 0)2745 goto err1;27462747 status = class_register(&spi_master_class);2748 if (status < 0)2749 goto err2;27502751 if (IS_ENABLED(CONFIG_OF_DYNAMIC))2752 WARN_ON(of_reconfig_notifier_register(&spi_of_notifier)); // 这⾥要注册主机和从机27532754 return 0;27552756 err2:2757 bus_unregister(&spi_bus_type);2758 err1:2759 kfree(buf);2760 buf = NULL;2761 err0:2762 return status;2763 }2726 static struct notifier_block spi_of_notifier = {2727 .notifier_call = of_spi_notify,2728 };2686 static int of_spi_notify(struct notifier_block *nb, unsigned long action,2687 void *arg)2688 {2689 struct of_reconfig_data *rd = arg;2690 struct spi_master *master;2691 struct spi_device *spi;26922693 switch (of_reconfig_get_state_change(action, arg)) {2694 case OF_RECONFIG_CHANGE_ADD:2695 master = of_find_spi_master_by_node(rd->dn->parent); // 找到主机节点2696 if (master == NULL)2697 return NOTIFY_OK; /* not for us */26982699 spi = of_register_spi_device(master, rd->dn); // ---> 注册设备2700 put_device(&master->dev);27222723 return NOTIFY_OK;2724 }1428 #if defined(CONFIG_OF)1429 static struct spi_device *1430 of_register_spi_device(struct spi_master *master, struct device_node *nc)1431 {1432 struct spi_device *spi;1433 int rc;1434 u32 value;14351436 /* Alloc an spi_device */1437 spi = spi_alloc_device(master);1438 if (!spi) {1439 dev_err(&master->dev, "spi_device alloc error for %s\n",1440 nc->full_name);1441 rc = -ENOMEM;1442 goto err_out;1443 }14441445 /* Select device driver */1446 rc = of_modalias_node(nc, spi->modalias, // 匹配到从机1447 sizeof(spi->modalias));1448 if (rc < 0) {1449 dev_err(&master->dev, "cannot find modalias for %s\n",1450 nc->full_name);1451 goto err_out;1452 }14531454 /* Device address */1455 rc = of_property_read_u32(nc, "reg", &value); // 设备节点 reg 表⽰ cs1456 if (rc) {1457 dev_err(&master->dev, "%s has no valid 'reg' property (%d)\n",1458 nc->full_name, rc);1459 goto err_out;1460 }1461 spi->chip_select = value;14621463 /* Mode (clock phase/polarity/etc.) */ // 选择 spi 的模式1464 if (of_find_property(nc, "spi-cpha", NULL))1465 spi->mode |= SPI_CPHA;1466 if (of_find_property(nc, "spi-cpol", NULL))1467 spi->mode |= SPI_CPOL;1468 if (of_find_property(nc, "spi-cs-high", NULL)) // 选择 spi cs 是⾼有效还是低有效 1469 spi->mode |= SPI_CS_HIGH;1470 if (of_find_property(nc, "spi-3wire", NULL))1471 spi->mode |= SPI_3WIRE;1472 if (of_find_property(nc, "spi-lsb-first", NULL))1473 spi->mode |= SPI_LSB_FIRST;14741475 /* Device DUAL/QUAD mode */ // 选择单线还是双线通道1476 if (!of_property_read_u32(nc, "spi-tx-bus-width", &value)) {1477 switch (value) {1478 case 1:1479 break;1480 case 2:1481 spi->mode |= SPI_TX_DUAL;1482 break;1483 case 4:1484 spi->mode |= SPI_TX_QUAD;1485 break;1486 default:1487 dev_warn(&master->dev,1488 "spi-tx-bus-width %d not supported\n",1489 value);1490 break;1491 }1492 }14931494 if (!of_property_read_u32(nc, "spi-rx-bus-width", &value)) {1495 switch (value) {1496 case 1:1497 break;1498 case 2:1499 spi->mode |= SPI_RX_DUAL;1500 break;1501 case 4:1502 spi->mode |= SPI_RX_QUAD;1503 break;1504 default:1505 dev_warn(&master->dev,1506 "spi-rx-bus-width %d not supported\n",1508 break;1509 }1510 }15111512 /* Device speed */ // 设备速度配置1513 rc = of_property_read_u32(nc, "spi-max-frequency", &value);1514 if (rc) {1515 dev_err(&master->dev, "%s has no valid 'spi-max-frequency' property (%d)\n", 1516 nc->full_name, rc);1517 goto err_out;1518 }1519 spi->max_speed_hz = value;15201521 /* Store a pointer to the node in the device structure */1522 of_node_get(nc);1523 spi->dev.of_node = nc; // 保存设备结构体15241525 /* Register the new device */1526 rc = spi_add_device(spi);1527 if (rc) {1528 dev_err(&master->dev, "spi_device register error %s\n",1529 nc->full_name);1530 goto err_out;1531 }15321533 return spi;15341535 err_out:1536 spi_dev_put(spi);1537 return ERR_PTR(rc);1538 }。
Linux下spi驱动开发(1)华清远见刘洪涛一、概述基于子系统去开发驱动程序已经是linux内核中普遍的做法了。
前面写过基于I2C子系统的驱动开发。
本文介绍另外一种常用总线SPI的开发方法。
SPI子系统的开发和I2C有很多的相似性,大家可以对比学习。
本主题分为两个部分叙述,第一部分介绍基于SPI子系统开发的理论框架;第二部分以华清远见教学平台FS_S5PC100上的M25P10芯片为例(内核版本2.6.29),编写一个SPI驱动程序实例。
二、SPI总线协议简介介绍驱动开发前,需要先熟悉下SPI通讯协议中的几个关键的地方,后面在编写驱动时,需要考虑相关因素。
SPI总线由MISO(串行数据输入)、MOSI(串行数据输出)、SCK(串行移位时钟)、CS(使能信号)4个信号线组成。
如FS_S5PC100上的M25P10芯片接线为:上图中M25P10的D脚为它的数据输入脚,Q为数据输出脚,C为时钟脚。
SPI常用四种数据传输模式,主要差别在于:输出串行同步时钟极性(CPOL)和相位(CPHA)可以进行配置。
如果CPOL= 0,串行同步时钟的空闲状态为低电平;如果CPOL= 1,串行同步时钟的空闲状态为高电平。
如果CPHA= 0,在串行同步时钟的前沿(上升或下降)数据被采样;如果CPHA = 1,在串行同步时钟的后沿(上升或下降)数据被采样。
这四种模式中究竟选择哪种模式取决于设备。
如M25P10的手册中明确它可以支持的两种模式为:CPOL=0 CPHA=0 和CPOL=1 CPHA=1三、linux下SPI驱动开发首先明确SPI驱动层次,如下图:SPI总线可理解为SPI控制器引出的总线1、Platform busP latform bus对应的结构是platform_bus_type,这个内核开始就定义好的。
我们不需要定义。
2、Platform_deviceSPI控制器对应platform_device的定义方式,同样以S5PC100中的SPI控制器为例,参看arch/arm/plat-s5pc1xx/dev-spi.c文件struct platform_device s3c_device_spi0 = {.name = "s3c64xx-spi", //名称,要和Platform_driver匹配.id = 0, //第0个控制器,S5PC100中有3个控制器.num_resources = ARRAY_SIZE(s5pc1xx_spi0_resource),//占用资源的种类.resource = s5pc1xx_spi0_resource,//指向资源结构数组的指针.dev = {.dma_mask = &spi_dmamask, //dma寻址范围.coherent_dma_mask = DMA_BIT_MASK(32), //可以通过关闭cache等措施保证一致性的dma寻址范围.platform_data = &s5pc1xx_spi0_pdata,//特殊的平台数据,参看后文},};static struct s3c64xx_spi_cntrlr_info s5pc1xx_spi0_pdata = {.cfg_gpio = s5pc1xx_spi_cfg_gpio, //用于控制器管脚的IO配置.fifo_lvl_mask = 0x7f,.rx_lvl_offset = 13,};static int s5pc1xx_spi_cfg_gpio(struct platform_device *pdev){s witch (pdev->id) {c ase 0:s3c_gpio_cfgpin(S5PC1XX_GPB(0), S5PC1XX_GPB0_SPI_MISO0);s3c_gpio_cfgpin(S5PC1XX_GPB(1), S5PC1XX_GPB1_SPI_CLK0);s3c_gpio_cfgpin(S5PC1XX_GPB(2), S5PC1XX_GPB2_SPI_MOSI0);s3c_gpio_setpull(S5PC1XX_GPB(0), S3C_GPIO_PULL_UP);s3c_gpio_setpull(S5PC1XX_GPB(1), S3C_GPIO_PULL_UP);s3c_gpio_setpull(S5PC1XX_GPB(2), S3C_GPIO_PULL_UP);break;c ase 1:s3c_gpio_cfgpin(S5PC1XX_GPB(4), S5PC1XX_GPB4_SPI_MISO1);s3c_gpio_cfgpin(S5PC1XX_GPB(5), S5PC1XX_GPB5_SPI_CLK1);s3c_gpio_cfgpin(S5PC1XX_GPB(6), S5PC1XX_GPB6_SPI_MOSI1);s3c_gpio_setpull(S5PC1XX_GPB(4), S3C_GPIO_PULL_UP);s3c_gpio_setpull(S5PC1XX_GPB(5), S3C_GPIO_PULL_UP);s3c_gpio_setpull(S5PC1XX_GPB(6), S3C_GPIO_PULL_UP);break;c ase 2:s3c_gpio_cfgpin(S5PC1XX_GPG3(0), S5PC1XX_GPG3_0_SPI_CLK2);s3c_gpio_cfgpin(S5PC1XX_GPG3(2), S5PC1XX_GPG3_2_SPI_MISO2);s3c_gpio_cfgpin(S5PC1XX_GPG3(3), S5PC1XX_GPG3_3_SPI_MOSI2);s3c_gpio_setpull(S5PC1XX_GPG3(0), S3C_GPIO_PULL_UP);s3c_gpio_setpull(S5PC1XX_GPG3(2), S3C_GPIO_PULL_UP);s3c_gpio_setpull(S5PC1XX_GPG3(3), S3C_GPIO_PULL_UP);break;d efault: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。
linuxSPI驱动——gpio模拟spi驱动(三)⼀:⾸先在我的平台注册platform_device,保证能让spi-gpio.c能执⾏到probe函数。
1: struct spi_gpio_platform_data {2: unsigned sck;3: unsigned mosi;4: unsigned miso;5:6: u16 num_chipselect;7: };1: //#define NCS GPIO_PB(2) //定义SS所对应的GPIO接⼝编号2: //#define SCLK GPIO_PB(0) //定义SCLK所对应的GPIO接⼝编号3: //#define MOSI GPIO_PB(4) //定义SCLK所对应的GPIO接⼝编号4: //#define MISO GPIO_PB(1)5: static struct spi_gpio_platform_data jz_spi_gpio_data = {6: .sck = GPIO_PB(0), //GPIO_SPI_SCK,7: .mosi = GPIO_PB(4), //GPIO_SPI_MOSI,8: .miso = GPIO_PB(1), //GPIO_SPI_MISO,9: .num_chipselect = 1,10: };11:12: struct platform_device jz_spi_gpio_device = {13: .name = "spi_gpio",14: .id = 0,15: .dev = {16: .platform_data = &jz_spi_gpio_data,17: },18: };注册platform device1: platform_device_register(&jz_spi_gpio_device);⼆:注册platform_driver在spi_gpio.c⾥⾯注册platform driver1: MODULE_ALIAS("platform:" DRIVER_NAME);2:3: static struct platform_driver spi_gpio_driver = {4: = DRIVER_NAME,5: .driver.owner = THIS_MODULE,6: .remove = __exit_p(spi_gpio_remove),7: };8:9: static int __init spi_gpio_init(void)10: {11: return platform_driver_probe(&spi_gpio_driver, spi_gpio_probe);12: }13: module_init(spi_gpio_init);14:15: static void __exit spi_gpio_exit(void)16: {17: platform_driver_unregister(&spi_gpio_driver);18: }19: module_exit(spi_gpio_exit);20:21:22: MODULE_DESCRIPTION("SPI master driver using generic bitbanged GPIO ");23: MODULE_AUTHOR("David Brownell");24: MODULE_LICENSE("GPL");三:具体算法分析1: struct spi_gpio {2: struct spi_bitbang bitbang; /* gpio 模拟spi算法相关的结构 */3: struct spi_gpio_platform_data pdata; /* spi platform data 对应模拟spi的四个gpio编号 */4: struct platform_device *pdev; /* 对应注册的 platform device */5: };1:2: static int __init spi_gpio_probe(struct platform_device *pdev)3: {4: int status;5: struct spi_master *master;6: struct spi_gpio *spi_gpio;7: struct spi_gpio_platform_data *pdata;8: u16 master_flags = 0;9:10: pdata = pdev->dev.platform_data; /* 存放spi的四根gpio */11: #ifdef GENERIC_BITBANG12: if (!pdata || !pdata->num_chipselect)13: return -ENODEV;14: #endif15:16: /* 申请注册四个gpio */17: status = spi_gpio_request(pdata, dev_name(&pdev->dev), &master_flags);18: if (status < 0) {19: return status;20: }21:22: /* alloc a spi master ,master->dev->p->driver_data = &master[1]*/23: master = spi_alloc_master(&pdev->dev, sizeof *spi_gpio);24: if (!master) {25: status = -ENOMEM;26: goto gpio_free;27: }28: /* spi_gpio指向⼀块空间, 即指向mstaer[1]29: pdev->dev->p->driver_data = spi_gpio;30: 初始化spi_gpio31: */32: spi_gpio = spi_master_get_devdata(master);33: platform_set_drvdata(pdev, spi_gpio);34:35: spi_gpio->pdev = pdev;36: if (pdata)37: spi_gpio->pdata = *pdata;38:39: master->flags = master_flags;40: master->bus_num = pdev->id;41: master->num_chipselect = SPI_N_CHIPSEL;42: master->setup = spi_gpio_setup; /* setup ⽐如cs引脚申请 */43: master->cleanup = spi_gpio_cleanup;44: /* spi_gpio->bitbang.master = master */45: spi_gpio->bitbang.master = spi_master_get(master);46: spi_gpio->bitbang.chipselect = spi_gpio_chipselect;47: /* spi_gpio->bitbang.txrx_word 数组函数四个元素指针,分别指向spi四种mode算法函数 */ 48: if ((master_flags & (SPI_MASTER_NO_TX | SPI_MASTER_NO_RX)) == 0) {49: spi_gpio->bitbang.txrx_word[SPI_MODE_0] = spi_gpio_txrx_word_mode0;50: spi_gpio->bitbang.txrx_word[SPI_MODE_1] = spi_gpio_txrx_word_mode1;51: spi_gpio->bitbang.txrx_word[SPI_MODE_2] = spi_gpio_txrx_word_mode2;52: spi_gpio->bitbang.txrx_word[SPI_MODE_3] = spi_gpio_txrx_word_mode3;53: } else {54: spi_gpio->bitbang.txrx_word[SPI_MODE_0] = spi_gpio_spec_txrx_word_mode0;55: spi_gpio->bitbang.txrx_word[SPI_MODE_1] = spi_gpio_spec_txrx_word_mode1;56: spi_gpio->bitbang.txrx_word[SPI_MODE_2] = spi_gpio_spec_txrx_word_mode2;57: spi_gpio->bitbang.txrx_word[SPI_MODE_3] = spi_gpio_spec_txrx_word_mode3;58: }59: /* spi_gpio->bitbang.setup_transfer初始化传输的bits_per_word和speed */60: spi_gpio->bitbang.setup_transfer = spi_bitbang_setup_transfer;61: spi_gpio->bitbang.flags = SPI_CS_HIGH;62: /* spi_gpio->bitbang相关算法接⼝初始化 */63: status = spi_bitbang_start(&spi_gpio->bitbang);64: if (status < 0) {65: spi_master_put(spi_gpio->bitbang.master);66: gpio_free:67: if (SPI_MISO_GPIO != SPI_GPIO_NO_MISO)68: gpio_free(SPI_MISO_GPIO);69: if (SPI_MOSI_GPIO != SPI_GPIO_NO_MOSI)70: gpio_free(SPI_MOSI_GPIO);71: gpio_free(SPI_SCK_GPIO);72: spi_master_put(master);73: }74:75: return status;76: }四:总之最终让spi_gpi0整个对象存放了整个gpio模拟spi的算法结构;⽽pdev->dev->p->driver_data = spi_gpio; platform device和 platform driver两者match结果是:root@CarRadio:/# ls /sys/bus/platform/devices/spi_gpio.0/driver modalias power spi0.0 spi_master subsystem ueventroot@CarRadio:/# ls /sys/bus/platform/devices/spi_gpio.0/driver/spi_gpio.0 uevent。
Linux驱动:SPI驱动编写要点题外话:⾯对成功和失败,⼀个⼈有没有“冠军之⼼”,直接影响他的表现。
⼏周前剖析了Linux SPI 驱动框架,算是明⽩个所以然,对于这么⼀个庞⼤的框架,并不是每⼀⾏代码都要⾃⼰去敲,因为前⼈已经把这个框架搭建好了,作为驱动开发者的我们只需要搞清楚哪⼀部分是需要⾃⼰修改或重新编写就OK了。
结合Linux内核⾯向对象的设计思想,SPI总的设计思路⼤概是这样的:第①处:内核中抽象了SPI控制器,让spi_master成为他的象征,他的实例化对象就是与硬⽣⽣的SPI控制器对应的,在Linux内核中习惯将集成到SOC上的控制器⽤假想的platform总线来进⾏管理,于是乎spi_master的实例化就得依靠platform_device和platform_driver来联⼿完成了。
细嚼慢咽:这⾥的platform_device就相当于是spi_master的静态描述:包括⼏号控制器、寄存器地址、引脚配置等等,把这些信息以“资源”的形式挂在platform_device上,等platform_driver找到命中注定的那个他之后就可以获得这个(静态描述)资源,probe中⽤这些资源就⽣出了spi_master实例对象。
这⼀系列流程前辈们都已经做好了,我们要做的就是将这静态描述platform_device修改成和⾃⼰SOC中的spi 控制器⼀致的特性即可。
即:适当修改arch/arm/mach-s5pv210/dev-spi.c中platform_device涉及的成员。
1// SPI0的寄存器地址2static struct resource s5pv210_spi0_resource[] = {3 [0] = {4 .start = S5PV210_PA_SPI0,5 .end = S5PV210_PA_SPI0 + 0x100 - 1,6 .flags = IORESOURCE_MEM,7 },8 [1] = {9 .start = DMACH_SPI0_TX,10 .end = DMACH_SPI0_TX,11 .flags = IORESOURCE_DMA,12 },13 [2] = {14 .start = DMACH_SPI0_RX,15 .end = DMACH_SPI0_RX,16 .flags = IORESOURCE_DMA,17 },18 [3] = {19 .start = IRQ_SPI0,20 .end = IRQ_SPI0,21 .flags = IORESOURCE_IRQ,22 },23 };2425/**26 * struct s3c64xx_spi_info - SPI Controller defining structure27 * @src_clk_nr: Clock source index for the CLK_CFG[SPI_CLKSEL] field.28 * @src_clk_name: Platform name of the corresponding clock.29 * @clk_from_cmu: If the SPI clock/prescalar control block is present30 * by the platform's clock-management-unit and not in SPI controller.31 * @num_cs: Number of CS this controller emulates.32 * @cfg_gpio: Configure pins for this SPI controller.33 * @fifo_lvl_mask: All tx fifo_lvl fields start at offset-634 * @rx_lvl_offset: Depends on tx fifo_lvl field and bus number35 * @high_speed: If the controller supports HIGH_SPEED_EN bit36 * @tx_st_done: Depends on tx fifo_lvl field37*/38static struct s3c64xx_spi_info s5pv210_spi0_pdata = {39 .cfg_gpio = s5pv210_spi_cfg_gpio, //将GPIO配置成SPI0引脚的函数40 .fifo_lvl_mask = 0x1ff,41 .rx_lvl_offset = 15,42 .high_speed = 1, //看s5pv210的使⽤⼿册P901可知:这是⽤来配置CH_CFG寄存器的,主要是210⽤于从设备时......43 .tx_st_done = 25,44 };4546static u64 spi_dmamask = DMA_BIT_MASK(32);4748struct platform_device s5pv210_device_spi0 = {49 .name = "s3c64xx-spi",50 .id = 0,51 .num_resources = ARRAY_SIZE(s5pv210_spi0_resource),52 .resource = s5pv210_spi0_resource,53 .dev = {54 .dma_mask = &spi_dmamask,55 .coherent_dma_mask = DMA_BIT_MASK(32),56 .platform_data = &s5pv210_spi0_pdata,//特殊的spi_master数据57 },58 };platform_device第②处:添加/修改SPI外设“静态描述”的结构。