编写驱动程序步骤
- 实现SPI总线设置的函数setup,用于设置SPI总线,若片选采用GPIO编号模式还需要在这里将GPIO设置为输出
- 实现SPI总线数据传输的函数transfer,用于传输SPI的数据包,它通常将spi_message放入到控制器的链表中,然后触发工作队列,去执行真正的发送任务。
- 通过spi_alloc_master分配一个struct spi_master,分配struct spi_master时还可以额外分配一段存储私有数据的空间(通过函数spi_master_get_devdata可以得到这段私有数据空间的地址)
- 初始化struct spi_master,主要包含设备树节点、支持的模式、支持的最大频率和最小频率、片选引脚是GPIO编号模式还是描述符模式、setup函数(用于设置SPI总线)、transfer函数(用于传输spi_message,若提供了transfer函数则是阻塞模式的SPI控制器驱动)
- 若片选采用GPIO编号模式还需要对片选引脚进行request操作,若片选采用GPIO描述符模式则无该步骤
- 通过spi_register_master注册SPI控制器驱动
- 设备或驱动卸载时spi_unregister_master注销SPI控制器
编写驱动程
这里编写一个虚拟的SPI控制器驱动,通过printk来输出SPI控制器的工作状态。
设备树编写
在顶层设备树根节点中加入如下节点:
virtual_spi_master {
compatible = "atk,virtual_spi_master";
status = "okay";
//片选列表,一个spi_master至少有一个片选
cs-gpios = <&gpioh 6 GPIO_ACTIVE_LOW>;
//片选数量
num-chipselects = <1>;
//reg中地址字段的字数,必须为1
#address-cells = <1>;
//reg中地址空间大小的字数,必须为0
#size-cells = <0>;
//一个spidev的设备节点,以便在应用层通过spidev来测试SPI控制器驱动
virtual_spi_dev: virtual_spi_dev@0 {
compatible = "rohm,dh2228fv";
reg = <0>;
spi-max-frequency = <100000>;
};
};
用make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs -j8编译设备树,用新的.dtb文件启动系统
驱动代码编写
完整的驱动代码如下所示:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/list.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/spi/spi.h>
#include <linux/of_gpio.h>
struct virtual_spi_master{
struct spi_master *spi_master;
struct work_struct work_queue;
};
//工作队列函数,用于模拟SPI控制器的硬件中断
static void spi_virtual_work(struct work_struct *work)
{
unsigned long flags;
struct spi_message *mesg;
struct spi_transfer *xfer;
struct spi_statistics *statm;
struct spi_statistics *stats;
struct virtual_spi_master *virtual_master = container_of(work, struct virtual_spi_master, work_queue);
struct spi_master *master = virtual_master->spi_master;
//获取自旋锁
spin_lock_irqsave(&master->queue_lock, flags);
//便利存储mesg的队列
while(!list_empty(&master->queue))
{
//从队列中取出一个mesg,并将其从队列中删除
mesg = list_entry(master->queue.next, struct