关联内容:

【SPI】SPI学习之SPI硬件相关

【SPI】SPI学习之SPI驱动相关

【SPI】SPI学习之SPI调试相关


目录

spi驱动框架

SPI控制器

spi_master 结构体

spi主机设备类

spi_register_master函数

spi_unregister_master函数

SPI核心

spi_transfer 结构体

spi_message 结构体

spi_bitbang 结构体

spi总线结构体

spi_match_device函数

spi_message_init函数

spi_message_add_tail函数

spi_init函数

spi_sync函数

SPI设备驱动

spi_driver 结构体

struct spi_device 结构体

spi_board_info 结构体

spi_new_device函数

spi_alloc_device函数

spi_add_device函数

spi_setup函数

spi_register_board_info函数

spi_register_driver函数

spi_unregister_driver函数

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 queuestruct 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  0int (*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_writespidev_sync_read  ↓spi_transfer → spi_massage → spi_sync

【SPI】SPI学习之SPI驱动相关相关推荐

  1. 【SPI】SPI学习之SPI硬件相关

    关联内容: [SPI]SPI学习之SPI硬件相关 [SPI]SPI学习之SPI驱动相关 [SPI]SPI学习之SPI调试相关 目录 SPI硬件知识 SPI相关的缩写或说法 SPI外部信号描述 SPI硬 ...

  2. Linux SPI驱动学习——调用SPI读写函数

    Linux SPI驱动学习--调用SPI读写函数 博客说明 开发环境 1. 基于W25Q16型FLASH来看SPI的读写函数 附录 博客说明 撰写日期 2019.10.25 完稿日期 未完稿 最近维护 ...

  3. stm32 SD(SDSC)卡的学习与SPI 模式应用(卡槽介绍)

    这次的sd卡内容比较多,写的比较多,关于卡的命令和寄存器方面内容较多下面只是简要题一下,我们只需要指导如何使用即可,深入研究相对比较困难. 什么是SD 卡? SD 卡( Secure Digital ...

  4. Dubbo SPI机制学习总结(持续更新...)

    参考文章:Dubbo的SPI机制分析 首先来看看 Java SPI 的机制 Java SPI 起初是提供给厂商做插件开发用的,例如数据库驱动java.sql.Driver,市面上各种各样的数据库,不同 ...

  5. 【七夕特殊礼物】Dubbo学习之SPI实战与debug源码

    目录 绪论 环境搭建 dubbo-demo-interface dubbo-demo-xml dubbo-demo-xml-provider 源码跟踪 getExtension createExten ...

  6. 【单片机外设学习之SPI】

    stm32外设学习之spi 物理层 SPI 通讯使用3 条总线及片选线,3 条总线分别为SCK.MOSI.MISO,片选线为CS(或者NSS). CS: 片选信号线, 当有多个SPI 从设备与SPI ...

  7. SPI协议、MCP2515裸机驱动详解----主流SPI总线接口原理

    最近看到一个介绍SPI接口原理的帖子,看完觉得甚好.特来分析给大家一起学习. SPI概述 Serial Peripheral interface 通用串行外围设备接口 是Motorola首先在其MC6 ...

  8. STM32F103学习笔记——SPI读写Flash(二)

      此系列文章是小白学习STM32的一些学习笔记.小白第一次写笔记文章,有不足或是错误之处,请多体谅和交流! 目录 1.软件设计流程 2.SPI初始化 3.SPI发送接收一字节函数编写 4.FLASH ...

  9. 【STM32H7教程】第85章 STM32H7的SPI 总线应用之SPI Flash的STM32CubeProg下载算法制作

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第85章       STM32H7的SPI 总线应用之SPI ...

最新文章

  1. php pdo 时间,php – 使用PDO执行时间记录查询 – 自动完成功能无效
  2. Linux crontab 命令格式
  3. VScode+SSH Remote多级连跳配置
  4. Android深度探索读后感第二章
  5. python flask跨域_Flask配置Cors跨域的实现
  6. poj 3126 BFS
  7. Android下基于UVC的UsbCam的开发
  8. 用八类网线钳和剥线刀做网线水晶头
  9. flex-shrink如何如何分配容器收缩空间
  10. 关于 js 闭包的理解
  11. leetcode 滑动窗口
  12. linux入门指南(基础教程)
  13. 音频混音接口调节音量及设置静音
  14. Markdown语法学习|精简版
  15. Springboot项目修改文件传输(minio)限制大小
  16. python画k线图_Python使用matplotlib绘制k线图(去掉空白日期)
  17. if-elif-e方法lse的基本使用
  18. 7-45 航空公司VIP客户查询 (25 分)
  19. microsoft office visio 2007 安装过程中出错的解决(直接看后面)
  20. 神舟笔记本重装系统教程

热门文章

  1. pgsql 正则匹配中文字符
  2. 使用ISO文件搭建本地yum源
  3. ggplot2设置坐标轴范围_R语言画展ggplot2篇:如何改变坐标轴的范围?
  4. Python 字典的setdefault方法
  5. RH124 考试真题讲解(小部分、不全)
  6. vue移动端大转盘抽奖
  7. citra 图形设置_吊炸天的 Docker 图形化工具 —— Portainer
  8. Tensorflow object detection API应用大致流程
  9. 软件生存周期阶段及任务简述
  10. 【Halcon视觉】极坐标变换