编写驱动程序步骤
- 实现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 spi_message, queue);
list_del_init(&mesg->queue);
//暂时解锁
spin_unlock_irqrestore(&master->queue_lock, flags);
//更新统计信息
statm = &master->statistics;
stats = &mesg->spi->statistics;
SPI_STATISTICS_INCREMENT_FIELD(statm, messages);
SPI_STATISTICS_INCREMENT_FIELD(stats, messages);
//便利mesg的transfer_list,依次传输spi_transfer
list_for_each_entry(xfer, &mesg->transfers, transfer_list)
{
//继续更新统计信息
spi_statistics_add_transfer_stats(statm, xfer, master);
spi_statistics_add_transfer_stats(stats, xfer, master);
//传输数据
if((xfer->tx_buf || xfer->rx_buf) && (xfer->len > 0))
{
if(xfer->rx_buf && xfer->tx_buf)
memcpy(xfer->rx_buf, xfer->tx_buf, xfer->len);
else if(xfer->rx_buf)
memset(xfer->rx_buf, 0xAA, xfer->len);
printk("transfer one\n");
}
else if((xfer->tx_buf || xfer->rx_buf) && (xfer->len <= 0))
printk("transfer invalid\n");
}
//设置状态为0,表示传输成功
mesg->status = 0;
//执行传输完成回调
if (mesg->complete)
mesg->complete(mesg->context);
//重新加锁
spin_lock_irqsave(&master->queue_lock, flags);
}
//完成后解锁
spin_unlock_irqrestore(&master->queue_lock, flags);
}
static int spi_virtual_setup(struct spi_device *spi_dev)
{
if(!gpio_is_valid(spi_dev->cs_gpio))
{
printk("%d is not a valid gpio\n", spi_dev->cs_gpio);
return -EINVAL;
}
//若采用GPIO编号模式还需要在驱动中将gpio设置为输出
return gpio_direction_output(spi_dev->cs_gpio, !(spi_dev->mode & SPI_CS_HIGH));
}
static int spi_virtual_transfer(struct spi_device *spi, struct spi_message *mesg)
{
//SPI框架在调用此函数时使用spi_master中的bus_lock_spinlock自旋锁进行了加锁,所以此函数不能休眠
unsigned long flags;
struct virtual_spi_master *virtual_master;
struct spi_master *master;
//获取spi_master
master = spi->master;
//从spi_master中获取virtual_spi_master
virtual_master = spi_master_get_devdata(master);
//获取自旋锁
spin_lock_irqsave(&master->queue_lock, flags);
//把mesg放入队列
mesg->actual_length = 0;
mesg->status = -EINPROGRESS;
list_add_tail(&mesg->queue, &master->queue);
//完成后解锁
spin_unlock_irqrestore(&master->queue_lock, flags);
//启动工作队列
schedule_work(&virtual_master->work_queue);
return 0;
}
static int spi_virtual_probe(struct platform_device *pdev)
{
int result;
int i, num_cs, cs_gpio;
struct spi_master *master;
struct virtual_spi_master *virtual_master;
printk("%s\r\n", __FUNCTION__);
//分配spi_master,其中私有数据大小为sizeof(struct virtual_spi_master),通过spi_master_get_devdata可以得到私有数据的地址
master = spi_alloc_master(&pdev->dev, sizeof(struct virtual_spi_master));
if(!master)
{
printk("alloc spi_master fail\n");
return -ENOMEM;
}
//获取spi_master私有数据的地址
virtual_master = spi_master_get_devdata(master);
//设置平台设备的驱动私有数据
pdev->dev.driver_data = (void*)virtual_master;
//初始化virtual_spi_master
virtual_master->spi_master = master;
INIT_WORK(&virtual_master->work_queue, spi_virtual_work);
//初始化spi_master
virtual_master->spi_master->use_gpio_descriptors = 0;
virtual_master->spi_master->setup = spi_virtual_setup;
virtual_master->spi_master->transfer = spi_virtual_transfer;
virtual_master->spi_master->dev.of_node = pdev->dev.of_node;
virtual_master->spi_master->bus_num = pdev->id;
virtual_master->spi_master->max_speed_hz = 1000000000;
virtual_master->spi_master->min_speed_hz = 1000;
virtual_master->spi_master->mode_bits = SPI_CPHA | SPI_CPOL | SPI_CS_HIGH | SPI_LSB_FIRST | SPI_3WIRE;
//片选引脚采用GPIO编号模式时,SPI驱动框架仅仅是从设备树中获取片选引脚编号并记录,未进行request引脚
num_cs = of_gpio_named_count(pdev->dev.of_node, "cs-gpios");
for (i = 0; i < num_cs; i++)
{
cs_gpio = of_get_named_gpio(pdev->dev.of_node, "cs-gpios", i);
if (cs_gpio == -EPROBE_DEFER)
{
/* 释放前面分配的 spi_master,它通过于 spi_master 绑定的 dev 来实现
* 其调用流程如下:
* spi_master_put
* spi_controller_put
* put_device
* kobject_put
* kref_put
* kobject_release
* kobject_cleanup
* t->release,这里应该是device_initialize为其注册的device_ktype中的device_release
* dev->class->dev_release,这里应该是分配spi_master时为dev.class绑定的spi_master_class中的spi_controller_release
*/
spi_master_put(virtual_master->spi_master);
return -EPROBE_DEFER;
}
if(gpio_is_valid(cs_gpio))
{
result = devm_gpio_request(&pdev->dev, cs_gpio, "virtual_spi_cs");
if(result < 0)
{
spi_master_put(virtual_master->spi_master);
printk("can't get CS gpio %i\n", cs_gpio);
return result;
}
}
}
//注册 spi_master
result = spi_register_master(virtual_master->spi_master);
if (result < 0)
{
printk("register spi_master fail\n");
spi_master_put(virtual_master->spi_master);
return result;
}
return 0;
}
static int spi_virtual_remove(struct platform_device *pdev)
{
struct virtual_spi_master *virtual_master;
printk("%s\r\n", __FUNCTION__);
//提取平台设备的驱动私有数据
virtual_master = (struct virtual_spi_master*)pdev->dev.driver_data;
//注销spi_master,在注销过程中会执行put_device操作,所以无需再次执行spi_master_put
spi_unregister_master(virtual_master->spi_master);
return 0;
}
static const struct of_device_id spi_virtual_of_match[] = {
{.compatible = "atk,virtual_spi_master"},
{ /* Sentinel */ }
};
static struct platform_driver spi_virtual_driver = {
.probe = spi_virtual_probe,
.remove = spi_virtual_remove,
.driver = {
.name = "virtual_spi",
.of_match_table = spi_virtual_of_match,
},
};
static int virtual_master_init(void)
{
printk("%s\r\n", __FUNCTION__);
return platform_driver_register(&spi_virtual_driver);
}
static void virtual_master_exit(void)
{
printk("%s\r\n", __FUNCTION__);
platform_driver_unregister(&spi_virtual_driver);
}
module_init(virtual_master_init);
module_exit(virtual_master_exit);
MODULE_DESCRIPTION("virtual SPI bus driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("csdn");
驱动测试程序编写
驱动测试程序基于spidev进行编写,它通过ioctl控制SPI总线进行数据收发,完整的代码如下所示:
/* 参考: tools\spi\spidev_fdx.c */
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
#include <errno.h>
int main(int argc, char **argv)
{
int fd;
int status;
struct spi_ioc_transfer xfer[1];
unsigned char tx_buf[1];
unsigned char rx_buf[1];
if(argc != 3)
{
printf("Usage: %s /dev/spidevB.D <val>\n", argv[0]);
return 0;
}
//打开spidev设备
fd = open(argv[1], O_RDWR);
if (fd < 0) {
printf("can not open %s\n", argv[1]);
return 1;
}
//通过ioctl控制SPI总线发送并接收一个字节的数据
tx_buf[0] = (unsigned char)strtoul(argv[2], NULL, 0);
rx_buf[0] = 0;
memset(xfer, 0, sizeof xfer);
xfer[0].tx_buf = (unsigned long)tx_buf;
xfer[0].rx_buf = (unsigned long)rx_buf;
xfer[0].len = 2;
status = ioctl(fd, SPI_IOC_MESSAGE(1), xfer);
if(status < 0)
{
printf("SPI_IOC_MESSAGE %d\n", errno);
return -1;
}
//打印接收到的数据
printf("Pre val = %d\n", rx_buf[0]);
return 0;
}
上机测试
- 修改设备树,增加虚拟SPI控制器的设备树节点,并在此节点中添加一个spidev的子节点,然后编译设备树,用新的设备树启动设备
- 从这里下载代码,使用make进行编译,然后使用make copy拷贝到目标板NFS跟文件系统的root目录中(执行make copy时需要确保Makefile中NFS根文件系统的路径正确)
- 在目标板中执行insmod spi_master.ko加载虚拟SPI控制器驱动(加载驱动时会提示”controller is unqueued, this is deprecated“,忽略这个提示即可,因为目前Linux更推荐使用队列模式的SPI控制器驱动)
- 执行命令./spi_test.out /dev/spidev1.0 12通过SPI总线发送1byte数据,同时将收到的数据打印出来(驱动中默认将发送的数据赋给接收)