自己对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
  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值