自己对spi内核驱动的理解
这篇文章主要是我学习韦东山老师的的spi驱动,基于px30 kernel上编写一个spi 读写接口驱动代码的一个记录,总结了spi的大致流程,大家看的时候最好结合内核spi代码一起看,讲的可能不够详细,不够清楚,如果有错误请谅解,欢迎指正~~
首先我们来认识一下有关spi驱动有关的几个结构体:
结构体位置: include/linux/spi.spi.h
spi_device为一个spi的物理设备,包含着一些board信息,模式,频率等在dts中定义。

spi_master为spi控制器,bus_num由dts中的id来决定,其驱动代码一般都由厂家提供,不需要自己手动去编写。

spi_transfer主要用于数据的发送,将要发送和接收的数据地址,长度传入一般即可,bits_per_word默认为8字节传输。

spi_message包含多个spi_stransfer。

spi框架分为三层:spi设备驱动层,spi核心层,spi控制器驱动层。
spi控制器驱动层:实现spi0,spi1等控制器的注册(spi-具体芯片.c)
spi核心层:驱动的整个框架(spi.c)
spi设备驱动层:实现自己的驱动

配置make menuconfig
选择自己对应板子的选项
Device Drivers ---->
[*]SPI support ---->
<*>Rockchip SPI controller driver
驱动过程的分析
总体大致流程:
在dts文件中打开对应的spi控制器状态为okay,则会根据compatible来匹配对应驱动文件,则module_platform_driver会创建该spi的平台设备,pdev->dev(即spi_device,包含的内容见上述结构体)会成功被申请,然后会将pdev->dev地址赋值给spi_device dev,则得到spi _device;然后执行驱动文件中的probe函数,填充spi master结构体,注册spi_master控制器;接下来实现自己的spi 设备驱动,可以注册成为字符设备,也可以注册成为misc设备,即为spi driver。在spi.c中为一些接口函数,spi_read,spi_write等接口函数在spi.h的头文件中,其中用到了spi transfer和spi message,spi message中有一个或者多个的spi transfer,也可以在自己的spi设备驱动中实现。这样,四个结构体都已经用上。
spi控制器流程
在创建master结构体的时候,需要将spi控制器的物理地址转成虚拟地址保存下来,所以我们要新建一个自己的结构体来保存该虚拟地址,方便设置spi的寄存器,需要其他自己的变量也可以在此结构体中声明。
一、从probe函数开始分析
1)spi_alloc_master出一个master结构体,第二个参数为自己定义的结构体,该函数会将自定义的结构体添加到master结构体申请的空间之后,即为master[1];
2)通过spi_master_get_devdata,将master[1]获取出来,通过映射函数将物理地址转化为虚拟地址,并保存在自定义结构体的变量中;
3)填充master的一些参数和一些接口函数,主要bus_num,num_chipselect, mode_bits,setup, transfer等。参数具体作用见结构体图。
4)设置完参数后,开始注册spi驱动,通过spi_register_master注册,内核驱动中可能还会在spi_register_master上在封装一层;在该函数中,
spi_register_master
|___ 1.判断num_chipselect是否等于0;
|___ 2.判断bus_num,dev.of_node;dev.of_node会关联到设备树节点中。
|___ 3.通过调用spi_match_master_to_boardinfo()函数在board_list中通过bus_num匹配对应的设备。
|___如果master->bus_num 与board中的bus_num相同,则创建一个新的spi_master
|___spi_new_device()
|___spi_add_device()
|___device_add()
二、实现steup函数,在setup函数中主要实现设置传输模式和传输频率的的设置, spi_mode由CPOL和CPHA来设定,在px30的kernel中,有spi_setup函数已经实现,若要自己重新写个驱动则需实现;
三、实现transfer函数,在px30的kernel中已经弃用,不用自己定义的transfer,通过使用spi_master_initialize_queue中来指定transfer函数,若要自己重新写个驱动则需实现;若自己实现:
|___发送第一个spi_trabsfer之前先setup
|___从spi_message中逐个取出spi_trabsfer,执行它
|___唤醒等待线程
在内核代码中,发送数据有两种方式,1. 中断传输(适合小于32字节的数据发送);2 dma方式(小于32字节的数据不适合使用),在px30内核代码中,通过控制use_dma来控制使用哪种方式;
spi_read() spi_write(), spi_write_then_read(),为内核实现的读写数据的接口函数,我觉得还可以加一个spi_write_and_read(),分析px30内核中的spi_read为例:
spi_read(struct spi_device *spi, void *buf, size_t len)
|___定义一个struct spi_transfer t与struct spi_message m,将参数buf,len,填入t中;
|___spi_message_init(&m) 初始化spi message m,将m结构体清0,初始化链表头;
|___spi_message_add_tail(&t, &m) 将t添加到m->transfer中
|___ spi_sync(spi, &m)
|___ __spi_sync()
|___ __spi_pump_message()
|___master->transfer_one_message()即spi_transfer_one_message
|___master -> transfer_on即调用probe中的transfer_one
这样一次读操作的过程就玩成了。
SPI设备驱动:实现一个简单的读写驱动的接口
1.注册spi_register_driver(), 我将其注册为一个字符设备,采用cdev方法注册;
2.实现probe函数
|___动态分配一个主设备号
|___注册一个file_operation
|___初始化spi_my_data_t一个自己的结构体,包含spi_device *spi等,这个结构体成员根据自己需要来添加,可以设定自己想要的值,spi_my_data_t -> spi = spi,
与之前dts注册出来的spi_devcie关联起来;
|___初始化mode, max_speed_HZ, bits_per_word;
3.接着自己实现了一些spi_my_read, spi_my_write, spi_my_write_then_read, spi_my_write_then_read等接口函数,其他和接口函数根据自己驱动需要来实现 如实现spi flash,spi OLED等操作函数都在这实现,我这里只是做了一个简单的读写驱动的接口而已, 若要将接口函数提供给上层,可以用ioctl等函数来实现;
自己的spi设备驱动代码
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/fs.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/spi/spi.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/miscdevice.h>
#include <linux/hrtimer.h>
#include <linux/platform_data/spi-rockchip.h>
#include <asm/uaccess.h>
#include <linux/syscalls.h>
#include <linux/cdev.h>
int spi_major;
struct cdev spi_cdev;
struct class *spi_class;
#define MAX_MY_SPI_NUM 3
struct spi_my_data {
int id;
struct spi_device *spi;
struct device *dev;
};
/*保存每个挂在在spi bus上的设备的数据*/
struct spi_my_data *spi_my_data_group[MAX_MY_SPI_NUM];
int spi_my_read(int id, void *rx_buf, size_t len)
{
struct spi_device *spi = NULL;
if ( NULL == rx_buf)
{
printk("Error: rx_buff is NULL!\n");
return -1;
}
if (id >= MAX_MY_SPI_NUM)
{
printk("Error: NOt have is id!\n");
return -1;
}
if (NULL == spi_my_data_group[id])
{
printk("Error: spi_my_data_group[%d] is NULL!\n", id);
return -1;
}
spi = spi_my_data_group[id] -> spi;
spi_read(spi, rx_buf, len);
return 0;
}
//EXPORT_SYMBOL(spi_my_read);
int spi_my_write(int id, void *tx_buf, size_t len)
{
struct spi_device *spi = NULL;
if ( NULL == tx_buf)
{
printk("Error: tx_buff is NULL!\n");
return -1;
}
if (id >= MAX_MY_SPI_NUM)
{
printk("Error: NOt have is id!\n");
return -1;
}
if (NULL == spi_my_data_group[id])
{
printk("Error: spi_my_data_group[%d] is NULL!\n", id);
return -1;
}
spi = spi_my_data_group[id] -> spi;
spi_write(spi, tx_buf, len);
return 0;
}
//EXPORT_SYMBOL(spi_my_write);
int spi_my_write_then_read(int id, void *tx_buf, size_t len1, void *rx_buf, size_t len2)
{
struct spi_device *spi = NULL;
struct spi_message m;
struct spi_transfer t[] = {
{
.tx_buf = tx_buf,
.len = len1,
},
{
.rx_buf = rx_buf,
.len = len2,
},
};
if ( NULL == tx_buf || NULL == rx_buf)
{
printk("Error: tx_buff is NULL!\n");
return -1;
}
if (id >= MAX_MY_SPI_NUM)
{
printk("Error: NOt have is id!\n");
return -1;
}
if (NULL == spi_my_data_group[id])
{
printk("Error: spi_my_data_group[%d] is NULL!\n", id);
return -1;
}
spi = spi_my_data_group[id] -> spi;
spi_message_init(&m);
spi_message_add_tail(&t[0], &m);
spi_message_add_tail(&t[1], &m);
return spi_sync(spi, &m);
}
//EXPORT_SYMBOL(spi_my_write_then_read);
int spi_my_write_and_read(int id, void *tx_buf, void *rx_buf, size_t len)
{
struct spi_device *spi = NULL;
struct spi_transfer t = {
.tx_buf = tx_buf,
.rx_buf = rx_buf,
.len = len,
};
struct spi_message m;
if ( NULL == tx_buf || NULL == rx_buf)
{
printk("Error: tx_buff is NULL!\n");
return -1;
}
if (id >= MAX_MY_SPI_NUM)
{
printk("Error: NOt have is id!\n");
return -1;
}
if (NULL == spi_my_data_group[id])
{
printk("Error: spi_my_data_group[%d] is NULL!\n", id);
return -1;
}
spi = spi_my_data_group[id] -> spi;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
return spi_sync(spi, &m);
}
//EXPORT_SYMBOL(spi_my_write_and_read);
static ssize_t spi_rockchip_my_read(struct file *filp, char __user *buf, size_t count, loff_t *f_ops)
{
return 0;
}
static ssize_t spi_rockchip_my_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_ops)
{
int argc = 0, i;
char tmp[64];
char *argv[16];
char *cmd, *data;
unsigned int id = 0, times = 0, size = 0;
unsigned long us = 0, bytes = 0;
char *txbuf = NULL, *rxbuf = NULL;
ktime_t start_time;
ktime_t end_time;
ktime_t cost_time;
memset(tmp, 0, sizeof(tmp));
if (copy_from_user(tmp, buf, count))
return -EFAULT;
cmd = tmp;
data = tmp;
while (data < (tmp + count)) {
data = strstr(data, " ");
if (!data)
break;
*data = 0;
argv[argc] = ++data;
argc++;
if (argc >= 16)
break;
}
tmp[count - 1] = 0;
if (!strcmp(cmd, "setspeed")) {
int id = 0, val;
struct spi_device *spi = NULL;
sscanf(argv[0], "%d", &id);
sscanf(argv[1], "%d", &val);
if (id >= MAX_MY_SPI_NUM)
return count;
if (!spi_my_data_group[id]) {
pr_err("g_spi.%d is NULL\n", id);
return count ;
} else {
spi = spi_my_data_group[id]->spi;
}
spi->max_speed_hz = val;
} else if (!strcmp(cmd, "write")) {
char name[64];
int fd;
mm_segment_t old_fs = get_fs();
sscanf(argv[0], "%d", &id);
sscanf(argv[1], "%d", ×);
sscanf(argv[2], "%d", &size);
if (argc > 3) {
sscanf(argv[3], "%s", name);
set_fs(KERNEL_DS);
}
txbuf = kzalloc(size, GFP_KERNEL);
if (!txbuf) {
printk("spi write alloc buf size %d fail\n", size);
return count;
}
if (argc > 3) {
fd = sys_open(name, O_RDONLY, 0);
if (fd < 0) {
printk("open %s fail\n", name);
} else {
sys_read(fd, (char __user *)txbuf, size);
sys_close(fd);
}
set_fs(old_fs);
} else {
for (i = 0; i < size; i++)
txbuf[i] = i % 256;
}
start_time = ktime_get();
for (i = 0; i < times; i++)
spi_my_write(id, txbuf, size);
end_time = ktime_get();
cost_time = ktime_sub(end_time, start_time);
us = ktime_to_us(cost_time);
bytes = size * times * 1;
bytes = bytes * 1000 / us;
printk("spi write %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes);
kfree(txbuf);
} else if (!strcmp(cmd, "read")) {
sscanf(argv[0], "%d", &id);
sscanf(argv[1], "%d", ×);
sscanf(argv[2], "%d", &size);
rxbuf = kzalloc(size, GFP_KERNEL);
if (!rxbuf) {
printk("spi read alloc buf size %d fail\n", size);
return count;
}
start_time = ktime_get();
for (i = 0; i < times; i++)
spi_my_read(id, rxbuf, size);
end_time = ktime_get();
cost_time = ktime_sub(end_time, start_time);
us = ktime_to_us(cost_time);
bytes = size * times * 1;
bytes = bytes * 1000 / us;
printk("spi read %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes);
kfree(rxbuf);
} else if (!strcmp(cmd, "loop")) {
sscanf(argv[0], "%d", &id);
sscanf(argv[1], "%d", ×);
sscanf(argv[2], "%d", &size);
txbuf = kzalloc(size, GFP_KERNEL);
if (!txbuf) {
printk("spi tx alloc buf size %d fail\n", size);
return count;
}
rxbuf = kzalloc(size, GFP_KERNEL);
if (!rxbuf) {
kfree(txbuf);
printk("spi rx alloc buf size %d fail\n", size);
return count;
}
for (i = 0; i < size; i++)
txbuf[i] = i % 256;
start_time = ktime_get();
for (i = 0; i < times; i++)
spi_my_write_and_read(id, txbuf, rxbuf, size);
// spi_my_write_then_read(id, txbuf, size, rxbuf, size);
end_time = ktime_get();
cost_time = ktime_sub(end_time, start_time);
us = ktime_to_us(cost_time);
if (memcmp(txbuf, rxbuf, size))
printk("spi loop test fail\n");
bytes = size * times;
bytes = bytes * 1000 / us;
printk("spi loop %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes);
kfree(txbuf);
kfree(rxbuf);
} else {
printk("echo id number size > /dev/spi_misc_test\n");
printk("echo write 0 10 255 > /dev/spi_misc_test\n");
printk("echo write 0 10 255 init.rc > /dev/spi_misc_test\n");
printk("echo read 0 10 255 > /dev/spi_misc_test\n");
printk("echo loop 0 10 255 > /dev/spi_misc_test\n");
printk("echo setspeed 0 1000000 > /dev/spi_misc_test\n");
}
return 0;
}
static long spi_rockchip_my_ioctl (struct file *file, unsigned int cmd, unsigned long arg)
{
return 0;
}
struct file_operations spi_rockchip_my_ops = {
.owner = THIS_MODULE,
//.read = spi_rockchip_my_read,
//.write = spi_rockchip_my_write,
//.unlocked_ioctl = spi_rockchip_my_ioctl,
};
static void set_spi_cdev(void)
{
int err, devno;
devno = MKDEV(spi_major, 0);
cdev_init(&spi_cdev, &spi_rockchip_my_ops);
spi_cdev.owner = THIS_MODULE;
spi_cdev.ops = &spi_rockchip_my_ops;
err = cdev_add(&spi_cdev, devno, 1);
if (err)
printk("add %d spi device error\n", devno);
}
static int rockchip_spi_my_probe(struct spi_device *spi)
{
int t_buf[4] = {0};
int r_buf[4] = {0};
dev_t dev;
int ret;
struct spi_my_data *spi_my_data_t = NULL;
int id;
if (!spi)
return -ENOMEM;
if (!spi->dev.of_node)
return -ENOMEM;
/*动态分配一个主设备号*/
ret = alloc_chrdev_region(&dev, 0, 1, "spi_my_drv");
if (ret < 0)
{
printk("spi rocjchip alloc_chrdev_region fail!\n");
return -1;
}
spi_major = MAJOR(dev);
/*注册一个file_operation*/
set_spi_cdev();
spi_class = class_create(THIS_MODULE, "spi_my_drv");
device_create(spi_class, NULL, MKDEV(spi_major, 0), "SPI_MY_DRV", "spi_my_drv");
/*初始化spi_device,可以设定自己想要的值*/
spi_my_data_t = (struct spi_my_data *)kzalloc(sizeof(struct spi_my_data), GFP_KERNEL);
if (!spi_my_data_t) {
dev_err(&spi->dev, "ERR: no memory for spi_test_data\n");
return -ENOMEM;
}
spi->bits_per_word = 8;
spi_my_data_t -> spi = spi;
spi_my_data_t -> dev = &spi -> dev;
if (of_property_read_u32(spi->dev.of_node, "id", &id)) {
dev_warn(&spi->dev, "fail to get id, default set 0\n");
id = 0;
}
/*初始化mode, max_speed_HZ, bits_per_word*/
ret = spi_setup(spi);
if (ret < 0) {
dev_err(spi_my_data_t->dev, "ERR: fail to setup spi\n");
return -1;
}
spi_my_data_group[id] = spi_my_data_t;
printk("%s:name=%s,bus_num=%d,cs=%d,mode=%d,speed=%d\n", __func__, spi->modalias, spi->master->bus_num, spi->chip_select, spi->mode, spi->max_speed_hz);
return 0;
}
static int rockchip_spi_my_remove(struct spi_device *spi)
{
return 0;
}
static const struct of_device_id rockchip_spi_my_match[] = {
{ .compatible = "rockship, spi_my_drv_cs0", },
{ },
};
MODULE_DEVICE_TABLE(of, rockchip_spi_my_match);
static struct spi_driver spi_rockchip_my_driver = {
.driver = {
.name = "spi_my_drv",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(rockchip_spi_my_match),
},
.probe = rockchip_spi_my_probe,
.remove = rockchip_spi_my_remove,
};
static int __init spi_rockchip_my_init(void)
{
int ret;
ret = spi_register_driver(&spi_rockchip_my_driver);
return ret;
}
static void __exit spi_rockchip_my_exit(void)
{
device_destroy(spi_class, MKDEV(spi_major, 0));
class_destroy(spi_class);
cdev_del(&spi_cdev);
unregister_chrdev_region(MKDEV(spi_major, 0), 1);
spi_unregister_driver(&spi_rockchip_my_driver);
}
module_init(spi_rockchip_my_init);
module_exit(spi_rockchip_my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("San Huid ,Inc");
参考px30内核和韦东山老师的的spi驱动
本文详述了作者在学习SPI内核驱动时的心得,重点介绍了SPI设备驱动的三层结构、关键结构体和流程。通过分析配置、驱动过程和SPI控制器的操作,解释了如何在px30 kernel上编写SPI读写接口驱动。文章还涵盖了SPI设备驱动的注册、读写函数实现以及在字符设备上的应用。

1849

被折叠的 条评论
为什么被折叠?



