自己对spi内核驱动的理解

本文详述了作者在学习SPI内核驱动时的心得,重点介绍了SPI设备驱动的三层结构、关键结构体和流程。通过分析配置、驱动过程和SPI控制器的操作,解释了如何在px30 kernel上编写SPI读写接口驱动。文章还涵盖了SPI设备驱动的注册、读写函数实现以及在字符设备上的应用。

自己对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。
ALT

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", &times);
			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", &times);
			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", &times);
			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驱动

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值