Linux 下的 SPI 驱动框架简介
SPI 驱动框架和 I2C 很类似,都分为主机控制器驱动和设备驱动,主机控制器也就是 SOC 的 SPI 控制器接口。
SPI 主机驱动也就是 SOC 的 SPI 控制器驱动,类似 I2C 驱动里面的适配器驱动。Linux 使用 spi_controller 表示 SPI 主机驱动,spi_controller 是个结构体,定义在 include/linux/spi/spi.h:
structspi_controller {structdevicedev;structlist_headlist;/* Other than negative (== assign one dynamically), bus_num is fully * board-specific. usually that simplifies to being SOC-specific. * example: one SOC has three SPI controllers, numbered 0..2, * and one board's schematics might show it using SPI-2. software * would normally use bus_num=2 for that controller. */ s16 bus_num;/* chipselects will be integral to many controllers; some others * might use board-specific GPIOs. */ u16 num_chipselect;/* Some SPI controllers pose alignment requirements on DMAable * buffers; let protocol drivers know about these requirements. */ u16 dma_alignment;/* spi_device.mode flags understood by this controller driver */ u32 mode_bits;/* spi_device.mode flags override flags for this controller */ u32 buswidth_override_bits;/* Bitmask of supported bits_per_word for transfers */ u32 bits_per_word_mask;······SPI 主机驱动的核心就是申请 spi_controller,然后初始化,最后向 Linux 内核注册。
1、spi_master 申请与释放 spi_alloc_master 函数用于申请 spi_master,函数原型如下:
staticinline struct spi_controller *spi_alloc_master(struct device *host, unsignedint size)函数参数和返回值含义如下 dev:设备,一般是 platform_device 中的 dev 成员变量。 size:私有数据大小,可以通过 spi_maser_get_devdata 函数获取到这些私有数据。 返回值:申请到的 spi_controller。
spi_master 的释放通过 spi_controller_put 来完成,当我们删除一个 SPI 主机驱动的时候就需要释放掉前面申请的 spi_master,其函数原型如下:
staticinlinevoidspi_controller_put(struct spi_controller *ctlr)其中,ctrl 就是要释放的 spi_master。
2、spi_master 的注册与注销 当 spi_master 初始化完成以后就要将其注册到 Linux 内核,spi_master 注册函数为 spi_register_controller,函数原型如下:
intspi_register_controller(struct spi_controller *ctlr);其中 ctlr 表示要注册的 spi_controller,返回值为 0 时成功,为负值时失败。 如果要注销的话可以使用 spi_unregister_controller 函数,此函数原型如下
voidspi_unregister_controller(struct spi_controller *ctlr);其中,ctlr 就是要注销的 spi_controller。
spi 设备驱动和 i2c 设备驱动也很类似,Linux 内核使用 spi_driver 来表示 spi 设备驱动,其原型如下:
structspi_driver {conststructspi_device_id *id_table;int (*probe)(struct spi_device *spi);void (*remove)(struct spi_device *spi);void (*shutdown)(struct spi_device *spi);structdevice_driverdriver;};可以看出,spi_driver 和 i2c_driver、platform_driver 基本一样,当 SPI 设备和驱动匹配成功以后 probe 函数就会执行。 同样的,spi_driver 初始化完成以后也需要向 Linux 内核注册,spi_driver 注册函数为 spi_register_driver,其原型如下:
#define spi_register_driver(driver) \ __spi_register_driver(THIS_MODULE, driver)driver 就表示要注册的 spi_driver,返回值为 0 表示注册成功,为负值表示失败。 注销 SPI 设备驱动以后也需要注销掉前面注册的 spi_driver,使用 spi_unregister_driver 函数完成 spi_driver 的注销,函数原型如下:
voidspi_unregister_driver(struct spi_driver *sdrv)其中,sdrv 就表示需要注销的 spi_driver。
1、IO 的 pinctrl 子节点的创建与修改 首先肯定是根据所使用的 IO 来创建和修改 pinctrl 子节点,一定要注意检查相应的 IO 有没有被其他设备所占用!!!
2、SPI 设备节点的创建与修改 采用设备树的情况下,SPI 设备信息描述就通过创建相应的设备子节点来完成,我们可以打开 imx6ull-14x14-evk.dtsi 文件,找到 spi 相关的配置:
spi-4 { compatible = "spi-gpio"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_spi4>; status = "okay"; gpio-sck = <&gpio5 11 0>; // 配置时钟线 gpio-mosi = <&gpio5 10 0>; // 配置数据线(主到从) cs-gpios = <&gpio5 7 GPIO_ACTIVE_LOW>; // 配置片选线,设置为低有效 num-chipselects = <1>; // 配置设备数量,表示只有 1 个设备 #address-cells = <1>; #size-cells = <0>; gpio_spi: gpio@0 { // 表示通道 0 compatible = "fairchild,74hc595"; // 设备类型 gpio-controller; #gpio-cells = <2>; reg = <0>; registers-number = <1>; spi-max-frequency = <100000>; // spi最大频率 enable-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>; // 使能控制引脚,低有效 };};SPI 设备驱动的核心时 spi_driver,这个我们之前学习过了。当我们向Linux内核注册成功以后就可以使用 SPI 核心层提供的 API 函数来对设备进行读写操作了。首先是 spi_transfer 结构体,此结构体用于描述 SPI 传输信息,其内容如下:
structspi_transfer {/* It's ok if tx_buf == rx_buf (right?) * for MicroWire, one buffer must be null * buffers must work with dma_*map_single() calls, unless * spi_message.is_dma_mapped reports a pre-existing mapping */constvoid *tx_buf;void *rx_buf;unsigned len;dma_addr_t tx_dma;dma_addr_t rx_dma;structsg_tabletx_sg;structsg_tablerx_sg;unsigned dummy_data:1;unsigned cs_off:1;unsigned cs_change:1;unsigned tx_nbits:3;unsigned rx_nbits:3;#define SPI_NBITS_SINGLE 0x01 /* 1bit transfer */#define SPI_NBITS_DUAL 0x02 /* 2bits transfer */#define SPI_NBITS_QUAD 0x04 /* 4bits transfer */ u8 bits_per_word;structspi_delaydelay;structspi_delaycs_change_delay;structspi_delayword_delay; u32 speed_hz; u32 effective_speed_hz;unsignedint ptp_sts_word_pre;unsignedint ptp_sts_word_post;structptp_system_timestamp *ptp_sts;bool timestamped;structlist_headtransfer_list;#define SPI_TRANS_FAIL_NO_START BIT(0) u16 error;};其中,tx_buf 保存着要发送的数据,rx_buf 保存接收到的数据。len 是要进行传输的数据长度,由于 SPI 是全双工通信,因此在一次通信中接收的字节数都是一样的。 spi_transfer 需要组织成 spi_message,spi_message 也是一个结构体,内容如下:
structspi_message {structlist_headtransfers;structspi_device *spi;unsigned is_dma_mapped:1;/* REVISIT: we might want a flag affecting the behavior of the * last transfer ... allowing things like "read 16 bit length L" * immediately followed by "read L bytes". Basically imposing * a specific message scheduling algorithm. * * Some controller drivers (message-at-a-time queue processing) * could provide that as their default scheduling algorithm. But * others (with multi-message pipelines) could need a flag to * tell them about such special cases. *//* Completion is reported through a callback */void (*complete)(void *context);void *context;unsigned frame_length;unsigned actual_length;int status;/* For optional use by whatever driver currently owns the * spi_message ... between calls to spi_async and then later * complete(), that's the spi_controller controller driver. */structlist_headqueue;void *state;/* List of spi_res reources when the spi message is processed */structlist_headresources;/* spi_prepare_message() was called for this message */bool prepared;};在使用 spi_message 之前需要对其进行初始化,spi_message 初始化函数为 spi_message_init,函数原型如下:
voidspi_message_init(struct spi_message *m)其中,m 就表示要初始化的 spi_message。 spi_message 准备好以后就可以进行数据传输了,数据传输分为同步传输和异步传输,同步传输会阻塞的等待 SPI 数据传输完成,同步传输函数为 spi_sync,函数原型如下:
voidspi_message_add_tail(struct spi_transfer *t, struct spi_message *m)其中,t 表示要添加到队列中的 spi_transfer,m 表示要加入的 spi_message。
spi_message 准备好以后就可以进行数据传输了,数据传输分为同步传输和异步传输,同步传输会阻塞的等待 SPI 数据传输完成,同步传输函数为 spi_sync,函数原型如下:
intspi_sync(struct spi_device *spi, struct spi_message *message)其中,spi 就是要进行数据传输的 spi_device,message 表示要传输的 spi_message。
异步传输不会阻塞的等待 SPI 数据传输完成,异步传输需要设置 spi_message 中的 complete 成员变量,其是一个回调函数,当 SPI 异步传输完成以后此函数就会被调用。SPI 异步传输函数为 spi_async,函数原型如下:
intspi_async(struct spi_device *spi, struct spi_message *message)其中,spi 就是要进行数据传输的 spi_device,message 就是要传输的 spi_message。
综上所述,SPI 数据传输步骤如下: ①、申请并初始化 spi_transfer,设置 spi_transfer 的 tx_buf 成员变量,然后设置 rx_buf 成员变量,最后设置 len 成员变量。 ②、使用 spi_message_init 函数初始化 spi_message。 ③、使用 spi_message_add_tail 函数将前面设置好的 spi_transfer 添加到 spi_message 队列中。 ④、使用 spi_sync 函数完成 SPI 数据同步传输。
我们使用 SPI 通信的 W25Q64 完成本次学习。查询原理图,得知可以将以下几个引脚复用为 SPI 功能。

图 1 spi原理图
打开 imx6ull-hcw-emmc.dtsi 文件,在 iomuxc 节点中添加一个新的子节点来描述 W25Q64 所使用的 SPI 引脚。内容如下:
pinctrl_ecspi3: w25q64{ fsl,pins = < MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0 MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1 MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1 MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1 >;};接下来,我们在设备树文件中添加以下代码
&ecspi3 { /delete-property/ dmas; /delete-property/ dma-names; fsl,spi-num-chipselects = <1>; cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_ecspi3>; status = "okay"; w25q64@0 { compatible = "hcw,w25q64"; reg = <0>; spi-max-frequency = <1000000>; };};检查引脚是否冲突,将冲突的设备设为 disabled,编译设备树,并使用新的 dtb 文件启动 Linux 内核。
新建文件,写入以下代码。 1、设备结构体
#define W25Q64_CNT 1#define W25Q64_NAME "w25q64"structw25q64_dev{dev_t devid;structcdevcdev;structclass *class;structdevice *device;structdevice_node *nd;int major;void *private_data;int cs_gpio;};staticstructw25q64_devw25q64dev;2、w25q64 spi 设备的注册与注销 对于 spi 设备驱动,首先就是要初始化并向系统注册 spi_driver,w25q64 的 spi_driver 初始化、注册、注销代码如下:
staticconststructof_device_idw25q64_of_match[] = { {.compatible = "hcw,w25q64"}, {}};staticstructspi_driverw25q64_drv = { .driver = { .name = "w25q64_drv", .owner = THIS_MODULE, .of_match_table = w25q64_of_match, }};staticint __init w25q64_init(void){return spi_register_driver(&w25q64_drv);}staticvoid __exit w25q64_exit(void){ spi_unregister_driver(&w25q64_drv);}module_init(w25q64_init);module_exit(w25q64_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("hcw");3、probe&remove 函数
staticintw25q64_probe(struct spi_device *dev){if(w25q64dev.major){ w25q64dev.devid = MKDEV(w25q64dev.major, 0); register_chrdev_region(w25q64dev.devid, W25Q64_CNT, W25Q64_NAME); }else{ alloc_chrdev_region(&w25q64dev.devid, 0, W25Q64_CNT, W25Q64_NAME); w25q64dev.major = MAJOR(w25q64dev.devid); } cdev_init(&w25q64dev.cdev, &w25q64_fops); cdev_add(&w25q64dev.cdev, w25q64dev.devid, W25Q64_CNT); w25q64dev.class = class_create(THIS_MODULE, W25Q64_NAME);if(IS_ERR(w25q64dev.class)) {return PTR_ERR(w25q64dev.class); } w25q64dev.device = device_create(w25q64dev.class, NULL, w25q64dev.devid, NULL, W25Q64_NAME);if(IS_ERR(w25q64dev.device)) {return PTR_ERR(w25q64dev.device); } dev->mode = SPI_MODE_0; spi_setup(dev); w25q64dev.private_data = dev;return0;}staticvoidw25q64_remove(struct spi_device *dev){ cdev_del(&w25q64dev.cdev); unregister_chrdev_region(w25q64dev.devid, W25Q64_CNT); device_destroy(w25q64dev.class, w25q64dev.devid); class_destroy(w25q64dev.class);}4、w25q64 读取 ID 函数 SPI 驱动最终是通过读写 w25q64 寄存器来实现的,因此需要编写对应的寄存器读写函数。
staticintw25q64_readID(struct w25q64_dev *dev, void *buf){int ret = -1;unsignedchar txdata[1];unsignedchar *rxdata;structspi_messagem;structspi_transfer *t;structspi_device *spi = (structspi_device *)dev->private_data; t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);if(!t) {return -ENOMEM; } rxdata = kzalloc(sizeof(char) * 4, GFP_KERNEL);if(!rxdata)goto out1; txdata[0] = 0x90; t->tx_buf = txdata; t->rx_buf = rxdata; t->len = 4; spi_message_init(&m); spi_message_add_tail(t, &m); ret = spi_sync(spi, &m);if(ret)goto out2;memcpy(buf, rxdata + 1, 3); // 从 rxdata 的首地址往后偏移 1 个字节开始拷贝,用来过滤掉第一个无效数据out2: kfree(rxdata);out1: kfree(t);return ret;}写好后,编译模块装载,运行测试程序,但是并没有如预想中的一样输出设备 ID。是怎么回事呢?我们查阅 STM32 平台下的标准代码可以得知,我们应该依次发送 90 00 00 00 ff ff。最终代码放在最后
uint16_tw25qxx_read_id(void){uint16_t id = 0;//片选有效 SPI_CS = 0;//发送0x90,读取厂商ID和设备ID spi_read_writeByte(0x90);//发送24位地址(3个字节) 前面两个字节可以任意,第三个字节必须是0x00 spi_read_writeByte(0x00); spi_read_writeByte(0x00); spi_read_writeByte(0x00);//一定是0x00//随便发2个字节的数据 id |= spi_read_writeByte(0xFF)<<8; //id:0xEF17 厂商ID:0xEF id |= spi_read_writeByte(0xFF); //设备ID:0x17//片选无效 SPI_CS = 1;return id;}
图 2 读到ID
/* * @Author: 胡城玮 * @FilePath: w25q64.c * @Date: 2026-03-22 * @Description: * @Version: 0.1 */#include<linux/types.h>#include<linux/kernel.h>#include<linux/delay.h>#include<linux/init.h>#include<linux/module.h>#include<linux/errno.h>#include<linux/gpio.h>#include<linux/cdev.h>#include<linux/device.h>#include<linux/of_gpio.h>#include<linux/semaphore.h>#include<linux/timer.h>#include<linux/i2c.h>#include<linux/spi/spi.h>#include<linux/of.h>#include<linux/of_address.h>#include<linux/of_gpio.h>#include<linux/platform_device.h>#include<asm/mach/map.h>#include<asm/uaccess.h>#include<asm/io.h>#define W25Q64_CNT 1#define W25Q64_NAME "w25q64"structw25q64_dev{dev_t devid;structcdevcdev;structclass *class;structdevice *device;structdevice_node *nd;int major;void *private_data;int cs_gpio;};staticstructw25q64_devw25q64dev;staticconststructof_device_idw25q64_of_match[] = { {.compatible = "hcw,w25q64"}, {}};staticconststructspi_device_idw25q64_id[] = { { "w25q64", 0 }, { }};MODULE_DEVICE_TABLE(of, w25q64_of_match);staticintw25q64_readID(struct w25q64_dev *dev, void *buf){int ret = -1;unsignedchar txdata[6] = {0x90, 0x00, 0x00, 0x00,0xff,0xff};unsignedchar *rxdata;structspi_messagem;structspi_transfer *t;structspi_device *spi = (structspi_device *)dev->private_data; t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);if(!t) {return -ENOMEM; } rxdata = kzalloc(sizeof(char) * 6, GFP_KERNEL);if(!rxdata)goto out1; t->tx_buf = txdata; t->rx_buf = rxdata; t->len = 6; spi_message_init(&m); spi_message_add_tail(t, &m); ret = spi_sync(spi, &m);if(ret)goto out2;memcpy(buf, rxdata + 4, 2); // 从 rxdata 的首地址往后偏移 4 个字节开始拷贝,用来过滤掉4个无效数据out2: kfree(rxdata);out1: kfree(t);return ret;}staticintw25q64_open(struct inode *nd, struct file *filp){ filp->private_data = &w25q64dev;return0;}staticssize_tw25q64_read(struct file *filp, char __user *buf, size_t cnt, loff_t *oft){int ret = 0;uint8_t data[3]; ret = w25q64_readID(&w25q64dev, data);if(ret)return ret; printk("read id = %02x %02x %02x\n", data[0], data[1], data[2]);int err = copy_to_user(buf, data, 3);if(err) {return -EFAULT; }return3;}staticintw25q64_release(struct inode *inode, struct file *filp){return0;}staticstructfile_operationsw25q64_fops = { .owner = THIS_MODULE, .read = w25q64_read, .open = w25q64_open, .release = w25q64_release};staticintw25q64_probe(struct spi_device *dev){ printk("matched!!!\r\n");if(w25q64dev.major){ w25q64dev.devid = MKDEV(w25q64dev.major, 0); register_chrdev_region(w25q64dev.devid, W25Q64_CNT, W25Q64_NAME); }else{ alloc_chrdev_region(&w25q64dev.devid, 0, W25Q64_CNT, W25Q64_NAME); w25q64dev.major = MAJOR(w25q64dev.devid); } cdev_init(&w25q64dev.cdev, &w25q64_fops); cdev_add(&w25q64dev.cdev, w25q64dev.devid, W25Q64_CNT); w25q64dev.class = class_create(THIS_MODULE, W25Q64_NAME);if(IS_ERR(w25q64dev.class)) {return PTR_ERR(w25q64dev.class); } w25q64dev.device = device_create(w25q64dev.class, NULL, w25q64dev.devid, NULL, W25Q64_NAME);if(IS_ERR(w25q64dev.device)) {return PTR_ERR(w25q64dev.device); } dev->mode = SPI_MODE_0; spi_setup(dev); w25q64dev.private_data = dev;return0;}staticvoidw25q64_remove(struct spi_device *dev){ cdev_del(&w25q64dev.cdev); unregister_chrdev_region(w25q64dev.devid, W25Q64_CNT); device_destroy(w25q64dev.class, w25q64dev.devid); class_destroy(w25q64dev.class);}staticstructspi_driverw25q64_drv = { .driver = { .name = "w25q64_drv", .owner = THIS_MODULE, .of_match_table = w25q64_of_match, }, .probe = w25q64_probe, .remove = w25q64_remove, .id_table = w25q64_id,};staticint __init w25q64_init(void){return spi_register_driver(&w25q64_drv);}staticvoid __exit w25q64_exit(void){ spi_unregister_driver(&w25q64_drv);}module_init(w25q64_init);module_exit(w25q64_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("hcw");