SPISPI学习之SPI驱动相关
Posted Evan_ZGYF丶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SPISPI学习之SPI驱动相关相关的知识,希望对你有一定的参考价值。
关联内容:
目录
spi驱动框架
目录:linux/driver/spi/
<从设备驱动层> spidev.c 自己编写
<核心层> spi.c 内核提供
<控制器层> spi-xxx.c(瑞芯微:spi-rockchip.c 全志:spi-sunxi.c) 原厂提供
SPI控制器
spi_master 结构体
spi主机侧
struct spi_master用来描述一个SPI主控制器,我们一般不需要自己编写spi控制器驱动.
结构体master代表一个SPI接口,或者叫一个SPI主机控制器,一个接口对应一条SPI总线,master->bus_num则记录了这个总线号
struct spi_master
struct device dev;
struct list_head list;
s16 bus_num;
/*总线编号,从零开始.系统会用这个值去和系统中board_list链表中加入的每一个boardinfo结构
(每个boardinfo结构都是一个spi_board_info的集合,每一个spi_board_info都是对应一个SPI(从)设备的描述)中的每一个
spi_board_info中的bus_num进行匹配,如果匹配上就说明这个spi_board_info描述的SPI(从)设备是链接在此总线上 的,因此就会调用spi_new_device去创建一个spi_device*/
u16 num_chipselect;
//支持的片选的数量.从设备的片选号不能大于这个数.该值当然不能为0,否则会注册失败
u16 dma_alignment;
u16 mode_bits;
u16 flags;
#define SPI_MASTER_HALF_DUPLEX BIT(0) /* can't do full duplex */
#define SPI_MASTER_NO_RX BIT(1) /* can't do buffer read */
#define SPI_MASTER_NO_TX BIT(2) /* can't do buffer write */
spinlock_t bus_lock_spinlock;
struct mutex bus_lock_mutex;
bool bus_lock_flag;
int (*setup)(struct spi_device *spi);
//根据spi设备更新硬件配置
int (*transfer)(struct spi_device *spi,struct spi_message *mesg);
/*添加消息到队列的方法.此函数不可睡眠,其作用只是安排需要的传送,并且在适当的时候(传\\
送完成或者失败)调用spi_message中的complete方法,来将结果报告给用户*/
void (*cleanup)(struct spi_device *spi);
/*cleanup函数会在spidev_release函数中被调用,spidev_release被登记为spi dev的release
函数*/
bool queued;
struct kthread_worker kworker;
struct task_struct *kworker_task;
struct kthread_work pump_messages;
spinlock_t queue_lock;
struct list_head queue
struct spi_message *cur_msg;
bool busy;
bool running;
bool rt;
int (*prepare_transfer_hardware)(struct spi_master *master);
int (*transfer_one_message)(struct spi_master *master,
struct spi_message *mesg);
int (*unprepare_transfer_hardware)(struct spi_master *master);
int *cs_gpios;
;
spi主机设备类
static struct class spi_master_class =
.name = "spi_master",
.owner = THIS_MODULE,
.dev_release = spi_master_release,
;
spi_register_master函数
注册spi主机
在注册板级设备或主机设备的时候都会添加
spi板级设备添加进board_list链表,spi主机设备添加进spi_master_list链表
不管是先注册spi板级设备还是先注册spi主机设备
都会调用list_for_each_entry遍历对应的要匹配的设备的链表,查找是否有匹配的例子
若找到都会调用spi_match_master_to_boardinfo函数添加spi设备
int spi_register_master(struct spi_master *master)
static atomic_t dyn_bus_id = ATOMIC_INIT((1<<15) - 1);
struct device *dev = master->dev.parent; //获得spi主机设备的父设备
struct boardinfo *bi;
int status = -ENODEV;
int dynamic = 0;
if (!dev)
return -ENODEV;
if (master->num_chipselect == 0) //判断片选个数
return -EINVAL;
if (master->bus_num < 0) //验证spi总线编号
master->bus_num = atomic_dec_return(&dyn_bus_id);
dynamic = 1;
spin_lock_init(&master->bus_lock_spinlock);
mutex_init(&master->bus_lock_mutex);
master->bus_lock_flag = 0;
dev_set_name(&master->dev, "spi%u", master->bus_num); //设置spi主机设备名
status = device_add(&master->dev); //添加spi主机设备
if (status < 0)
goto done;
dev_dbg(dev, "registered master %s%s\\n", dev_name(&master->dev),dynamic ? " (dynamic)" : "");
mutex_lock(&board_lock);
list_add_tail(&master->list, &spi_master_list); //spi主机list链表添加进全局spi_master_list链表
list_for_each_entry(bi, &board_list, list) //遍历全局board_list查找bi结构体
spi_match_master_to_boardinfo(master, &bi->board_info); //找到匹配的板级spi设备
mutex_unlock(&board_lock);
status = 0;
of_register_spi_devices(master);
done:
return status;
EXPORT_SYMBOL_GPL(spi_register_master);
spi_unregister_master函数
注销spi主机
void spi_unregister_master(struct spi_master *master)
int dummy;
mutex_lock(&board_lock);
list_del(&master->list); //删除链表
mutex_unlock(&board_lock);
dummy = device_for_each_child(&master->dev, NULL, __unregister); //调用__unregister函数注销子设备
device_unregister(&master->dev); //注销设备
EXPORT_SYMBOL_GPL(spi_unregister_master);
SPI核心
spi传输模式
通过SPI控制寄存器1的2个位,软件可以选择4种时钟相位与极性组合的一种。
- CPOL:时钟极性选择,为0时SPI总线空闲为低电平,为1时SPI总线空闲为高电平
- CPOL时钟极性位指定时钟的高电平或低电平有效,不会影响传输格式。
- CPHA:时钟相位选择,为0时在SCK第一个跳变沿采样,为1时在SCK第二个跳变沿采样
- CPHA时钟相位控制位选择两种传输格式的一种。
工作模式 \\ 相位 | CPOL | CPHA |
mode 0 | 0 | 0 |
mode 1 | 0 | 1 |
mode 2 | 1 | 0 |
mode 3 | 1 | 1 |
- 工作方式1:
当CPHA=0、CPOL=0时SPI总线工作在方式1。MISO引脚上的数据在第一个SPSCK沿跳变之前已经上线了,而为了保证正确传输,MOSI引脚的MSB位必须与SPSCK的第一个边沿同步,在SPI传输过程中,首先将数据上线,然后在同步时钟信号的上升沿时,SPI的接收方捕捉位信号,在时钟信号的一个周期结束时(下降沿),下一位数据信号上线,再重复上述过程,直到一个字节的8位信号传输结束。
- 工作方式2:
当CPHA=0、CPOL=1时SPI总线工作在方式2。与前者唯一不同之处只是在同步时钟信号的下降沿时捕捉位信号,上升沿时下一位数据上线。
- 工作方式3:
当CPHA=1、CPOL=0时SPI总线工作在方式3。MISO引脚和MOSI引脚上的数据的MSB位必须与SPSCK的第一个边沿同步,在 SPI传输过程中,在同步时钟信号周期开始时(上升沿)数据上线,然后在同步时钟信号的下降沿时,SPI的接收方捕捉位信号,在时钟信号的一个周期结束时(上升沿),下一位数据信号上线,再重复上述过程,直到一个字节的8位信号传输结束。
- 工作方式4:
当CPHA=1、CPOL=1时SPI总线工作在方式4。与前者唯一不同之处只是在同步时钟信号的上升沿时捕捉位信号,下降沿时下一位数据上线。
#define SPI_CPHA 0x01 //时钟相位
#define SPI_CPOL 0x02 //时钟相位
#define SPI_MODE_0 (0|0) //模式0
#define SPI_MODE_1 (0|SPI_CPHA) //模式1
#define SPI_MODE_2 (SPI_CPOL|0) //模式2
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA) //模式3
#define SPI_CS_HIGH 0x04 //片选高电平
#define SPI_LSB_FIRST 0x08 //LSB
#define SPI_3WIRE 0x10 //3线模式 SI和SO同一根线
#define SPI_LOOP 0x20 //回送模式
#define SPI_NO_CS 0x40 //单个设备占用一根SPI总线,所以没片选
#define SPI_READY 0x80 //从机拉低电平停止数据传输
spi_transfer 结构体
struct spi_transfer是对一次完整的数据传输的描述.每个spi_transfer总是读取和写入同样长度的比特数,但是可以很容易的使用空指针舍弃读或写.为spi_transfer和spi_message分配的内存应该在消息处理期间保证是完整的.
struct spi_transfer
const void *tx_buf;
/*发送缓冲区地址,这里存放要写入设备的数据(必须是dma_safe),或者为NULL*/
void *rx_buf;
/*接收缓冲区地址,从设备中读取的数据(必须是dma_safe)就放在这里,或者为NULL*/
unsigned len;
/*传输数据的长度.记录了tx和rx的大小(字节数),这里不是指它的和,而是各自的长度,他们总是相等的*/
dma_addr_t tx_dma;
/*如果spi_message.is_dma_mapped是真,这个是tx的dma地址*/
dma_addr_t rx_dma;
/*如果spi_message.is_dma_mapped是真,这个是rx的dma地址*/
unsigned cs_change:1;
/*影响此次传输之后的片选.指示本次transfer结束之后是否要重新片选并调用setup改变设置.*/
/*若为1则表示当该transfer传输完后,改变片选信号.这个标志可以减少系统开销*/
u8 bits_per_word;
/*每个字长的比特数.如果是0,使用默认值*/
u16 delay_usecs;
/*此次传输结束和片选改变之间的延时,之后就会启动另一个传输或者结束整个消息*/
u32 speed_hz;
/*通信时钟.如果是0,使用默认值*/
struct list_head transfer_list;
/*用来连接的双向链表节点,用于将该transfer链入message*/
;
spi_message 结构体
struct spi_message:就是对多个spi_transfer的封装.spi_message用来原子的执行spi_transfer表示的一串数组传输请求.这个传输队列是原子的,这意味着在这个消息完成之前不会有其它消息占用总线.消息的执行总是按照FIFO的顺序.向底层提交spi_message的代码要负责管理它的内存空间.未显示初始化的内存需要使用0来初始化.为spi_transfer和spi_message分配的内存应该在消息处理期间保证是完整的。
struct spi_message
struct list_head transfers;
/*此次消息的传输段(spi_transfer)队列,一个消息可以包含多个传输段*/
struct spi_device *spi;
/*
* 传输的目的设备,无论如何这里都是spi从设备,
* 至于数据流向(是从主机到从设备还是从从设备到主机)这是由write/read
* 每个传输段(spi_transfer)内部的tx_buf或者是rx_buf决定的
*/
unsigned is_dma_mapped:1;
/*
* 如果为真,此次调用提供dma和cpu虚拟地址.spi主机提供了dma缓存池.
* 如果此消息确定要使用dma(那当然更好了).则从那个缓存池中申请高速缓存.
* 替代传输段(spi_transfer)中的tx_buf/rx_buf
*/
void (*complete)(void*context);
/*用于异步传输完成时调用的回调函数*/
void *context;
/*回调函数的参数*/
unsigned actual_length;
/*
* 此次传输的实际长度,这个长度包括了此消息spi_message中所有传输段
* spi_transfer传输的长度之和(不管每个传输段spi_transfer到底是输入还是输出,
* 因为本来具体的传输就是针对每一个传输段spi_transfer来进行的)
*/
int status;
/*执行的结果.成功被置0,否则是一个负的错误码*/
struct list_head queue;
/*用于将该message链入bitbang等待队列*/
void *state;
;
spi_bitbang 结构体
struct spi_bitbang结构用于控制实际的数据传输.
struct spi_bitbang
struct workqueue_struct *workqueue; /*工作队列*/
struct work_struct work;
spinlock_t lock;
struct list_head queue;
u8 busy;
u8 use_dma;
u8 flags; /* extra spi->mode support */
struct spi_master *master; /*bitbang所属的master*/
int (*setup_transfer)(struct spi_device *spi,struct spi_transfer *t);
/*用于设置设备传输时的时钟,字长等*/
void (*chipselect)(struct spi_device *spi, int is_on);
#define BITBANG_CS_ACTIVE 1 /* normally nCS, active low */
#define BITBANG_CS_INACTIVE 0
int (*txrx_bufs)(struct spi_device *spi, struct spi_transfer *t);
/* txrx_word[SPI_MODE_*]() just looks like a shift register */
u32 (*txrx_word[4])(struct spi_device *spi,unsigned nsecs,u32 word, u8 bits);
;
spi总线结构体
struct bus_type spi_bus_type =
.name = "spi",
.dev_attrs = spi_dev_attrs,
.match = spi_match_device, //匹配方法
.uevent = spi_uevent,
.suspend = spi_suspend,
.resume = spi_resume,
;
EXPORT_SYMBOL_GPL(spi_bus_type);
spi_match_device函数
前面的匹配方法是spi板级设备与spi主机设备的匹配方法,匹配的结果是添加新spi设备spi_new_device
这里的匹配是spi设备和spi驱动的匹配,匹配的结果是会调用spi驱动的设备驱动文件probe方法,既spi_drv_probe
static int spi_match_device(struct device *dev, struct device_driver *drv)
const struct spi_device *spi = to_spi_device(dev);
const struct spi_driver *sdrv = to_spi_driver(drv);
if (of_driver_match_device(dev, drv)) //设备文件驱动表的匹配
return 1;
if (sdrv->id_table) //spi设备驱动存在支持id表
return !!spi_match_id(sdrv->id_table, spi); //spi设备驱动表的匹配
return strcmp(spi->modalias, drv->name) == 0; //比较spi设备的名字和spi设备驱动的名字
spi_message_init函数
初始化spi消息
static inline void spi_message_init(struct spi_message *m)
memset(m, 0, sizeof *m);
INIT_LIST_HEAD(&m->transfers); //初始化spi消息的事务链表头
spi_message_add_tail函数
添加传输事务到spi传输链表
static inline void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
list_add_tail(&t->transfer_list, &m->transfers);
spi_init函数
spi子系统的初始化spi_init
static int __init spi_init(void)
int status;
buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL); //分配数据收发缓冲区
if (!buf)
status = -ENOMEM;
goto err0;
status = bus_register(&spi_bus_type); //注册spi总线
if (status < 0)
goto err1;
status = class_register(&spi_master_class); //注册spi主机类 "/sys/class/spi_master"
if (status < 0)
goto err2;
return 0;
err2:
bus_unregister(&spi_bus_type);
err1:
kfree(buf);
buf = NULL;
err0:
return status;
postcore_initcall(spi_init);
//入口声明 #define postcore_initcall(fn) __define_initcall("2",fn,2)
spi_sync函数
int spi_sync(struct spi_device *spi, struct spi_message *message)
return __spi_sync(spi, message, 0); //调用__spi_sync函数
EXPORT_SYMBOL_GPL(spi_sync);
SPI设备驱动
spi_driver 结构体
spi设备侧
struct spi_driver:用于描述SPI(从)设备驱动.驱动核心将根据driver.name和spi_board_info的modalias进行匹配,如过modalia和name相等,则绑定驱动程序和arch/.../mach-xxx/board-xxx.c中调用spi_register_board_info注册的信息对应的spi_device设备.它的形式和struct platform_driver是一致的.
struct spi_driver
const struct spi_device_id *id_table;
int (*probe)(struct spi_device *spi);
/*和spi_device匹配成功之后会调用这个方法.因此这个方法需要对设备和私有数据进行初始化*/
int (*remove)(struct spi_device *spi);
/*解除spi_device和spi_driver的绑定,释放probe申请的资源*/
void (*shutdown)(struct spi_device *spi);
/*一般牵扯到电源管理会用到,关闭*/
int (*suspend)(struct spi_device *spi, pm_message_t mesg);
/*一般牵扯到电源管理会用到,挂起*/
int (*resume)(struct spi_device *spi);
/*一般牵扯到电源管理会用到,恢复*/
struct device_driver driver;
;
struct spi_device 结构体
spi设备侧
spi_device 用来描述一个SPI从设备信息
SPI子系统只支持主模式,也就是说SOC上的SPI只能工作在master模式,外围设备只能为slave模式
struct spi_device
struct device dev;
struct spi_master *master; //对应的控制器指针
u32 max_speed_hz; //spi传输时钟
u8 chip_select; //片选号,用来区分同一主控制器上的设备
u8 mode; //各bit的定义如下,主要是传输模式/片选极性
#define SPI_CPHA 0x01 /* clock phase */
#define SPI_CPOL 0x02 /* clock polarity */
#define SPI_MODE_0 (0|0) /* (original MicroWire) */
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH 0x04 /* chipselect active high? *//*片选电位为高*/
#define SPI_LSB_FIRST 0x08 /* per-word bits-on-wire *//*先输出低比特*/
#define SPI_3WIRE 0x10 /* SI/SO signals shared *//*输入输出共享接口,此时只能做半双工*/
#define SPI_LOOP 0x20 /* loopback mode *//*回写/回显模式*/
#define SPI_NO_CS 0x40 /* 1 dev/bus, no chipselect */
#define SPI_READY 0x80 /* slave pulls low to pause */
u8 bits_per_word; /*每个字长的比特数*/
int irq; /*使用到的中断号*/
void *controller_state;
void *controller_data;
char modalias[SPI_NAME_SIZE]; /*spi设备的名字*/
int cs_gpio; /* chip select gpio */
;
spi_board_info 结构体
spi 设备侧
struct spi_board_info是板级信息,是在移植时就写好的,并且要将其注册.
该结构也是对SPI(从)设备(spi_device)的描述,只不过它是板级信息,最终该结构的所有字段都将用于初始化SPI设备结构体spi_device
struct spi_board_info
char modalias[SPI_NAME_SIZE];
/*
* spi设备名,会拷贝到spi_device的相应字段中.
* 这是设备spi_device在SPI总线spi_bus_type上匹配驱动的唯一标识
*/
const void *platform_data; /*平台数据*/
void *controller_data;
int irq; /*中断号*/
u32 max_speed_hz;/*SPI设备工作时的波特率*/
u16 bus_num;
/*
* 该SPI(从)设备所在总线的总线号,就记录了所属的spi_master之中的bus_num编号.
* 一个spi_master就对应一条总线
*/
u16 chip_select;
/*片选号.该SPI(从)设备在该条SPI总线上的设备号的唯一标识*/
u8 mode;
/*参考spi_device中的成员*/
;
spi_new_device函数
spi设备的添加
struct spi_device *spi_new_device(struct spi_master *master,struct spi_board_info *chip)
struct spi_device *proxy;
int status;
proxy = spi_alloc_device(master); //3.1 spi设备初始化
if (!proxy)
return NULL;
WARN_ON(strlen(chip->modalias) >= sizeof(proxy->modalias));
proxy->chip_select = chip->chip_select; //片选
proxy->max_speed_hz = chip->max_speed_hz; //最大速率
proxy->mode = chip->mode; //模式
proxy->irq = chip->irq; //中断号
strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias));
proxy->dev.platform_data = (void *) chip->platform_data;
proxy->controller_data = chip->controller_data;
proxy->controller_state = NULL;
status = spi_add_device(proxy); //3.2 添加spi设备
if (status < 0)
spi_dev_put(proxy); //增加spi设备引用计数
return NULL;
return proxy;
EXPORT_SYMBOL_GPL(spi_new_device);
spi_alloc_device函数
分配spi设备
struct spi_device *spi_alloc_device(struct spi_master *master)
struct spi_device *spi;
struct device *dev = master->dev.parent;
if (!spi_master_get(master)) //判断spi主机是否存在
return NULL;
spi = kzalloc(sizeof *spi, GFP_KERNEL); //分配内存
if (!spi)
dev_err(dev, "cannot alloc spi_device\\n");
spi_master_put(master); //增加主机引用计数
return NULL;
spi->master = master; //设置spi主机
spi->dev.parent = dev; //spi设备文件的父设备为spi主机设备文件的父设备
spi->dev.bus = &spi_bus_type; //总线类型
spi->dev.release = spidev_release; //释放方法
device_initialize(&spi->dev); //设备初始化
return spi;
EXPORT_SYMBOL_GPL(spi_alloc_device);
spi_add_device函数
添加spi设备
int spi_add_device(struct spi_device *spi)
static DEFINE_MUTEX(spi_add_lock);
struct device *dev = spi->master->dev.parent;
struct device *d;
int status;
if (spi->chip_select >= spi->master->num_chipselect)
dev_err(dev, "cs%d >= max %d\\n",spi->chip_select,spi->master->num_chipselect);
return -EINVAL;
dev_set_name(&spi->dev, "%s.%u", dev_name(&spi->master->dev),spi->chip_select);
mutex_lock(&spi_add_lock);
d = bus_find_device_by_name(&spi_bus_type, NULL, dev_name(&spi->dev)); //查找总线上的spi设备
if (d != NULL) //判断是否已经在使用了
dev_err(dev, "chipselect %d already in use\\n",spi->chip_select);
put_device(d);
status = -EBUSY;
goto done;
status = spi_setup(spi); //调用spi主机 setup方法
if (status < 0)
dev_err(dev, "can't setup %s, status %d\\n",dev_name(&spi->dev), status);
goto done;
status = device_add(&spi->dev); //添加设备
if (status < 0)
dev_err(dev, "can't add %s, status %d\\n",dev_name(&spi->dev), status);
else
dev_dbg(dev, "registered child %s\\n", dev_name(&spi->dev));
done:
mutex_unlock(&spi_add_lock);
return status;
EXPORT_SYMBOL_GPL(spi_add_device);
spi_setup函数
spi setup方法
int spi_setup(struct spi_device *spi)
unsigned bad_bits;
int status;
bad_bits = spi->mode & ~spi->master->mode_bits; //比较spi设备的模式和spi主机支持的模式
if (bad_bits) //存在不支持的模式
dev_err(&spi->dev, "setup: unsupported mode bits %x\\n",bad_bits);
return -EINVAL;
if (!spi->bits_per_word) //若没设置设备的每个字含多少位
spi->bits_per_word = 8; //则默认设置为8
status = spi->master->setup(spi); //调用spi主机的setup方法
dev_dbg(&spi->dev, "setup mode %d, %s%s%s%s""%u bits/w, %u Hz max --> %d\\n",
(int) (spi->mode & (SPI_CPOL | SPI_CPHA)),(spi->mode & SPI_CS_HIGH) ? "cs_high, " : "",
(spi->mode & SPI_LSB_FIRST) ? "lsb, " : "",(spi->mode & SPI_3WIRE) ? "3wire, " : "",
(spi->mode & SPI_LOOP) ? "loopback, " : "",spi->bits_per_word, spi->max_speed_hz,status);
return status;
EXPORT_SYMBOL_GPL(spi_setup);
spi_register_board_info函数
板级设备注册(静态注册,一般在板级初始化函数中调用)
int __init spi_register_board_info(struct spi_board_info const *info, unsigned n)
struct boardinfo *bi;
int i;
bi = kzalloc(n * sizeof(*bi), GFP_KERNEL); //分配内存
if (!bi)
return -ENOMEM;
for (i = 0; i < n; i++, bi++, info++)
struct spi_master *master;
memcpy(&bi->board_info, info, sizeof(*info)); //设置bi的板级信息
mutex_lock(&board_lock);
list_add_tail(&bi->list, &board_list); //添加bi->list到全局board_list链表
list_for_each_entry(master, &spi_master_list, list) //遍历spi主机链表
spi_match_master_to_boardinfo(master, &bi->board_info); //spi板级设备与spi主机匹配
mutex_unlock(&board_lock);
return 0;
spi_register_driver函数
spi设备驱动注册
这里的probe方法会在设备与驱动匹配的时候给调用
参看really_probe函数的部分代码
int spi_register_driver(struct spi_driver *sdrv)
sdrv->driver.bus = &spi_bus_type; //总线类型
if (sdrv->probe) //若存在probe方法
sdrv->driver.probe = spi_drv_probe; //设置其设备驱动文件的probe方法为spi_drv_probe
if (sdrv->remove) //若存在remove方法
sdrv->driver.remove = spi_drv_remove; //设置其设备驱动文件的remove方法为spi_drv_remove
if (sdrv->shutdown) 若存在shutdown方法
sdrv->driver.shutdown = spi_drv_shutdown; //设置其设备驱动文件的shutdown方法为spi_drv_shutdown
return driver_register(&sdrv->driver); //注册设备驱动
EXPORT_SYMBOL_GPL(spi_register_driver);
spi_unregister_driver函数
spi设备驱动注销
static inline void spi_unregister_driver(struct spi_driver *sdrv)
if (sdrv)
driver_unregister(&sdrv->driver); //注销设备驱动
spi的传输流程
spidev_sync_write
spidev_sync_read
↓
spi_transfer → spi_massage → spi_sync
以上是关于SPISPI学习之SPI驱动相关的主要内容,如果未能解决你的问题,请参考以下文章