1. SPI总线
1.1. SPI总线概述
SPI,是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议。
SPI总线的构成及信号类型如图1-1所示:
- MOSI – 主设备数据输出,从设备数据输入 对应MOSI master output slave input
- MISO – 主设备数据输入,从设备数据输出 对应MISO master input slave output
- CLK – 时钟信号,由主设备产生
- nCS – 从设备使能信号,由主设备控制
1.2. SPI总线时序
SPI接口在Master控制下产生的从设备使能信号和时钟信号,两个双向移位寄存器按位传输进行数据交换,传输数据高位在前(MSB first),低位在后。如下图所示,在CLK的下降沿上数据改变,上升沿一位数据被存入移位寄存器。
在一个SPI时钟周期内,会完成如下操作:
(1)Master通过MOSI线发送1位数据,同时Slave通过MOSI线读取这1位数据;
(2)Slave通过MISO线发送1位数据,同时Master通过MISO线读取这1位数据。
Master和Slave各有一个移位寄存器,如图1-3所示,而且这两个移位寄存器连接成环状。依照CLK的变化,数据以MSB first的方式依次移出Master寄存器和Slave寄存器,并且依次移入Slave寄存器和Master寄存器。当寄存器中的内容全部移出时,相当于完成了两个寄存器内容的交换。
1.3. SPI总线传输模式
SPI总线传输一共有4中模式,这4种模式分别由时钟极性(CPOL,Clock Polarity)和时钟相位(CPHA,Clock Phase)来定义,其中CPOL参数规定了SCK时钟信号空闲状态的电平,CPHA规定了数据是在SCK时钟的上升沿被采样还是下降沿被采样。这四种模式的时序图如下图1-4所示:
- 模式0:CPOL= 0,CPHA=0。CLK串行时钟线空闲是为低电平,数据在SCK时钟的上升沿被采样,数据在CLK时钟的下降沿切换
- 模式1:CPOL= 0,CPHA=1。CLK串行时钟线空闲是为低电平,数据在SCK时钟的下降沿被采样,数据在CLK时钟的上升沿切换
- 模式2:CPOL= 1,CPHA=0。CLK串行时钟线空闲是为高电平,数据在SCK时钟的下降沿被采样,数据在CLK时钟的上升沿切换
- 模式3:CPOL= 1,CPHA=1。CLK串行时钟线空闲是为高电平,数据在SCK时钟的上升沿被采样,数据在CLK时钟的下降沿切换
其中比较常用的模式是模式0和模式3。为了更清晰的描述SPI总线的时序,下面展现了模式0下的SPI时序图1-5:
1.4. SPI总线的优缺点
(1) 在点对点的通信中,SPI接口不需要进行寻址操作,且为全双工通信,显得简单高效。
(2) SPI接口没有指定的流控制,没有应答机制确认是否接收到数据。
2. Linux SPI 框架
2.1. 软件架构
Linux系统对spi设备具有很好的支持,linux系统下的spi驱动程序从逻辑上可以分为3个部分:
- spi核心(SPI Core):SPI Core是Linux内核用来维护和管理spi的核心部分,SPI Core提供操作接口函数,允许一个spi master,spi driver和spi device初始化时在SPI Core中进行注册,以及推出时进行注销。
- spi控制器驱动(SPI Master Driver):SPI Master针对不同类型的spi控制器硬件,实现spi总线的硬件访问操作。SPI Master通过接口函数向SPI Core注册一个控制器。
- spi设备驱动(SPI Device Driver):SPI Driver是对应于spi设备端的驱动程序,通过接口函数向SPI Core进行注册,SPI Driver的作用是将spi设备挂接到spi总线上;
Linux的软件架构图如图2-1所示:
2.2. 初始化及退出流程
2.2.1. 注册spi控制器
注册spi控制器到内核分为两个阶段:
第一个阶段,使用spi_alloc_master,分配一个spi_master的空间,具体流程如图2-2所示:
第二阶段,使用spi_register_master将第一阶段分配的spi_master注册到内核中,具体流程如2-3所示:
2.2.2. 注销spi控制器
spi控制器注销的流程如图2-4所示:
2.3. 关键数据结构
2.3.1. spi_device
struct spi_device {
struct device dev; /*spi控制器对应的device结构
struct spi_master *master; /*设备使用的master结构,挂在哪个主控制器下*/
u32 max_speed_hz; /*通讯时钟最大频率*/
u8 chip_select; /*片选号,每个master支持多个spi_device */
u8 mode;
#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; /*每个字长的比特数,默认是8*/
int irq;
void *controller_state; /*控制器状态*/
void *controller_data; /*控制器数据*/
char modalias[SPI_NAME_SIZE]; /* 设备驱动的名字 */
int cs_gpio; /* chip select gpio */
/*
* likely need more hooks for more protocol options affecting how
* the controller talks to each chip, like:
* - memory packing (12 bit samples into low bits, others zeroed)
* - priority
* - drop chipselect after each word
* - chipselect delays
* - ...
*/
};
spi_device代表一个外围spi设备,由master controller driver注册完成后扫描BSP中注册设备产生的设备链表并向spi_bus注册产生。在内核中,每个spi_device代表一个物理的spi设备。
2.3.2. spi_driver
struct spi_driver {
const struct spi_device_id *id_table; /*支持的spi_device设备表*/
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
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;
};
spi_driver代表一个SPI protocol drivers,即外设驱动
2.3.3. struct spi_master
struct spi_master {
struct device dev; /*spi控制器对应的device结构*/
struct list_head list; /*链表
/* other than negative (== assign one dynamically), bus_num is fully
* board-specific. usually that simplifies to being SOC-specific.
* example: one SOC has three SPI controllers, numbered 0..2,
* and one board's schematics might show it using SPI-2. software
* would normally use bus_num=2 for that controller.
*/
s16 bus_num; /*总线(或控制器编号)*/
/* chipselects will be integral to many controllers; some others
* might use board-specific GPIOs.
*/
u16 num_chipselect; /*片选数量*/
/* some SPI controllers pose alignment requirements on DMAable
* buffers; let protocol drivers know about these requirements.
*/
u16 dma_alignment;
/* spi_device.mode flags understood by this controller driver */
u16 mode_bits; /* master支持的设备模式 */
/* bitmask of supported bits_per_word for transfers */
u32 bits_per_word_mask;
/* other constraints relevant to this driver */
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 */
/* lock and mutex for SPI bus locking */
spinlock_t bus_lock_spinlock;
struct mutex bus_lock_mutex;
/* flag indicating that the SPI bus is locked for exclusive use */
bool bus_lock_flag;
/* Setup mode and clock, etc (spi driver may call many times).
*
* IMPORTANT: this may be called when transfers to another
* device are active. DO NOT UPDATE SHARED REGISTERS in ways
* which could break those transfers.
*/
int (*setup)(struct spi_device *spi); /*根据spi设备更新硬件配置。设置spi工作模式、时钟等*/
/* bidirectional bulk transfers
*
* + The transfer() method may not sleep; its main role is
* just to add the message to the queue.
* + For now there's no remove-from-queue operation, or
* any other request management
* + To a given spi_device, message queueing is pure fifo
*
* + The master's main job is to process its message queue,
* selecting a chip then transferring data
* + If there are multiple spi_device children, the i/o queue
* arbitration algorithm is unspecified (round robin, fifo,
* priority, reservations, preemption, etc)
*
* + Chipselect stays active during the entire message
* (unless modified by spi_transfer.cs_change != 0).
* + The message transfers use clock and SPI mode parameters
* previously established by setup() for this device
*/
int (*transfer)(struct spi_device *spi,
struct spi_message *mesg); /*添加消息到队列的方法,此函数不可睡眠。它的职责是安排发生的传送并且调用注册的回调函数complete()*/
/* called on release() to free memory provided by spi_master */
void (*cleanup)(struct spi_device *spi);/*cleanup函数会在spidev_release函数中被调用,spidev_release被登记为spi dev的release函数。*/
/*
* These hooks are for drivers that want to use the generic
* master transfer queueing mechanism. If these are used, the
* transfer() function above must NOT be specified by the driver.
* Over time we expect SPI drivers to be phased over to this API.
*/
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); /*清理回调函数*/
/* gpio chip select */
int *cs_gpios;
};
spi_master代表一个spi控制器。
2.3.4. struct spi_message 和spi_transfer
要完成和SPI设备的数据传输工作,我们还需要另外两个数据结构:spi_message和spi_transfer。
spi_message包含了一个的spi_transfer结构序列,一旦控制器接收了一个spi_message,其中的spi_transfer应该按顺序被发送,并且不能被其它spi_message打断,所以我们认为spi_message就是一次SPI数据交换的原子操作。下面我们看看这两个数据结构的定义:
struct spi_message :
struct spi_message {
struct list_head transfers; /*spi_transfer链表队列,此次消息的传输段队列,一个消息可以包含多个传输段。*/
struct spi_device *spi; /*传输的目的设备*/
unsigned is_dma_mapped:1; /*如果为真,此次调用提供dma和cpu虚拟地址。*/
/* REVISIT: we might want a flag affecting the behavior of the
* last transfer ... allowing things like "read 16 bit length L"
* immediately followed by "read L bytes". Basically imposing
* a specific message scheduling algorithm.
*
* Some controller drivers (message-at-a-time queue processing)
* could provide that as their default scheduling algorithm. But
* others (with multi-message pipelines) could need a flag to
* tell them about such special cases.
*/
/* completion is reported through a callback */
void (*complete)(void *context);/*异步调用完成后的回调函数*/
void *context; /*回调函数的参数*/
unsigned actual_length; /*实际传输的长度*/
int status; /*该消息的发送结果,成功被置0,否则是一个负的错误码。*/
/* for optional use by whatever driver currently owns the
* spi_message ... between calls to spi_async and then later
* complete(), that's the spi_master controller driver.
*/
struct list_head queue;
void *state;
};
链表字段queue用于把该结构挂在代表控制器的spi_master结构的queue字段上,控制器上可以同时被加入多个spi_message进行排队。另一个链表字段transfers则用于链接挂在本message下的spi_tranfer结构。complete回调函数则会在该message下的所有spi_transfer都被传输完成时被调用,以便通知协议驱动处理接收到的数据以及准备下一批需要发送的数据。我们再来看看spi_transfer结构:
spi_transfer
struct spi_transfer {
/* it's ok if tx_buf == rx_buf (right?)
* for MicroWire, one buffer must be null
* buffers must work with dma_*map_single() calls, unless
* spi_message.is_dma_mapped reports a pre-existing mapping
*/
const void *tx_buf; /*发送缓冲区*/
void *rx_buf; /*接收缓冲区*/
unsigned len; /*缓冲区长度,tx和rx的大小(字节数)。指它们各自的大小*/
dma_addr_t tx_dma; /*tx的dma地址*/
dma_addr_t rx_dma; /*rx的dma地址*/
unsigned cs_change:1; /*当前spi_transfer发送完成之后重新片选*/
u8 bits_per_word; /*每个字长的比特数,0代表使用spi_device中的默认值8*/
u16 delay_usecs; /*发送完成一个spi_transfer后的延时时间,此次传输结束和片选改变之间的延时,之后就会启动另一个传输或者结束整个消息*/
u32 speed_hz; /*通信时钟。如果是0,使用默认值*/
#ifdef CONFIG_SPI_LOMBO
struct lombo_spi_operate_para *esop;
#endif
struct list_head transfer_list; /*用于链接到spi_message,用来连接的双向链接节点*/
};
首先,transfer_list链表字段用于把该transfer挂在一个spi_message结构中,tx_buf和rx_buf提供了非dma模式下的数据缓冲区地址,len则是需要传输数据的长度,tx_dma和rx_dma则给出了dma模式下的缓冲区地址。原则来讲,spi_transfer才是传输的最小单位,之所以又引进了spi_message进行打包,我觉得原因是:有时候希望往spi设备的多个不连续的地址(或寄存器)一次性写入,如果没有spi_message进行把这样的多个spi_transfer打包,因为通常真正的数据传送工作是在另一个内核线程(工作队列)中完成的,不打包的后果就是会造成更多的进程切换,效率降低,延迟增加,尤其对于多个不连续地址的小规模数据传送而言就更为明显。
2.3.5. spi_board_info
struct spi_board_info {
/* the device name and module name are coupled, like platform_bus;
* "modalias" is normally the driver name.
*
* platform_data goes to spi_device.dev.platform_data,
* controller_data goes to spi_device.controller_data,
* irq is copied too
*/
char modalias[SPI_NAME_SIZE]; /*名字*/
const void *platform_data; /*平台数据*/
void *controller_data; /*控制器数据*/
int irq;
/* slower signaling on noisy or low voltage boards */
u32 max_speed_hz; /*最大速率*/
/* bus_num is board specific and matches the bus_num of some
* spi_master that will probably be registered later.
*
* chip_select reflects how this chip is wired to that master;
* it's less than num_chipselect.
*/
u16 bus_num; /*spi总线编号*/
u16 chip_select; /*片选*/
/* mode becomes spi_device.mode, and is essential for chips
* where the default of SPI_CS_HIGH = 0 is wrong.
*/
u8 mode; /*模式 */
/* ... may need additional spi_device chip config data here.
* avoid stuff protocol drivers can set; but include stuff
* needed to behave without being bound to a driver:
* - quirks like clock rate mattering when not selected
*/
};
2.4. 数据传输流程
整体的数据传输流程大致上是这样的:
- 定义一个spi_message结构;
- 用spi_message_init函数初始化spi_message;
- 定义一个或数个spi_transfer结构,初始化并为数据准备缓冲区并赋值给spi_transfer相应的字段(tx_buf,rx_buf等);
- 通过spi_message_init函数把这些spi_transfer挂在spi_message结构下;
- 如果使用同步方式,调用spi_sync(),如果使用异步方式,调用spi_async();(我调试外设时,只使用过spi_sync
传输示意图如图2-5所示:
2.4.1. 数据准备
2.4.1.1. spi_message_init
static inline void spi_message_init(struct spi_message *m)
{
memset(m, 0, sizeof *m);
INIT_LIST_HEAD(&m->transfers);
}
初始化spi_message:清空message,初始化transfers链表头。
2.4.1.2. spi_message_add_tail
static inline void
spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
{
list_add_tail(&t->transfer_list, &m->transfers);
}
将spi_transfer加入到spi_message的链表尾部。
2.4.2. 数据传输
SPI数据传输可以有两种方式:同步方式和异步方式。所谓同步方式是指数据传输的发起者必须等待本次传输的结束,期间不能做其它事情,用代码来解释就是,调用传输的函数后,直到数据传输完成,函数才会返回。而异步方式则正好相反,数据传输的发起者无需等待传输的结束,数据传输期间还可以做其它事情,用代码来解释就是,调用传输的函数后,函数会立刻返回而不用等待数据传输完成,我们只需设置一个回调函数,传输完成后,该回调函数会被调用以通知发起者数据传送已经完成。同步方式简单易用,很适合处理那些少量数据的单次传输。但是对于数据量大、次数多的传输来说,异步方式就显得更加合适。
对于SPI控制器来说,要支持异步方式必须要考虑以下两种状况:
- 对于同一个数据传输的发起者,既然异步方式无需等待数据传输完成即可返回,返回后,该发起者可以立刻又发起一个message,而这时上一个message还没有处理完。
- 对于另外一个不同的发起者来说,也有可能同时发起一次message传输请求
首先分析spi_sync()接口的实现流程,如图2-6:
其次分析spi_async_locked接口的实现流程,如图2-7所示:
spi_queued_transfer接口的实现流程如图3-8所示:
spi_pump_messages函数的处理流程如图3-9所示:
图中transfer_one_message是spi控制器驱动要实现的,主要功能是处理spi_message中的每个spi_transfer。
2.5. 关键函数解析
2.5.1. spi_alloc_master
原型:
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
功能:
分配一个spi_master结构体指针。
参数:
dev:spi控制器device指针
size :分配的driver-private data大小
返回值 :
成功,返回spi_master指针;否则返回NULL
2.5.2. spi_register_master
原型:
int spi_register_master(struct spi_master *master)
功能
注册spi控制器驱动到内核。
参数
master:spi_master指针
返回值
成功,返回0;否则返回错误码
2.5.3. spi_unregister_master
原型:
void spi_unregister_master(struct spi_master *master)
功能
注销spi控制器驱动。
参数
master:spi_master指针
返回值 无
3. Demo
(参考自正点原子)
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "icm20608reg.h"
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : icm20608.c
作者 : 左工
版本 : V1.0
描述 : ICM20608 SPI驱动程序
其他 : 无
论坛 :
日志 : 初版V1.0 2019/9/2 左工创建
***************************************************************/
#define ICM20608_CNT 1
#define ICM20608_NAME "icm20608"
struct icm20608_dev {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
int major; /* 主设备号 */
void *private_data; /* 私有数据 */
int cs_gpio; /* 片选所使用的GPIO编号 */
signed int gyro_x_adc; /* 陀螺仪X轴原始值 */
signed int gyro_y_adc; /* 陀螺仪Y轴原始值 */
signed int gyro_z_adc; /* 陀螺仪Z轴原始值 */
signed int accel_x_adc; /* 加速度计X轴原始值 */
signed int accel_y_adc; /* 加速度计Y轴原始值 */
signed int accel_z_adc; /* 加速度计Z轴原始值 */
signed int temp_adc; /* 温度原始值 */
};
static struct icm20608_dev icm20608dev;
/*
* @description : 从icm20608读取多个寄存器数据
* @param - dev: icm20608设备
* @param - reg: 要读取的寄存器首地址
* @param - val: 读取到的数据
* @param - len: 要读取的数据长度
* @return : 操作结果
*/
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{
int ret;
unsigned char txdata[len];
struct spi_message m;
struct spi_transfer *t;
struct spi_device *spi = (struct spi_device *)dev->private_data;
gpio_set_value(dev->cs_gpio, 0); /* 片选拉低,选中ICM20608 */
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* 申请内存 */
/* 第1次,发送要读取的寄存地址 */
txdata[0] = reg | 0x80; /* 写数据的时候寄存器地址bit8要置1 */
t->tx_buf = txdata; /* 要发送的数据 */
t->len = 1; /* 1个字节 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
/* 第2次,读取数据 */
txdata[0] = 0xff; /* 随便一个值,此处无意义 */
t->rx_buf = buf; /* 读取到的数据 */
t->len = len; /* 要读取的数据长度 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
kfree(t); /* 释放内存 */
gpio_set_value(dev->cs_gpio, 1); /* 片选拉高,释放ICM20608 */
return ret;
}
/*
* @description : 向icm20608多个寄存器写入数据
* @param - dev: icm20608设备
* @param - reg: 要写入的寄存器首地址
* @param - val: 要写入的数据缓冲区
* @param - len: 要写入的数据长度
* @return : 操作结果
*/
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)
{
int ret;
unsigned char txdata[len];
struct spi_message m;
struct spi_transfer *t;
struct spi_device *spi = (struct spi_device *)dev->private_data;
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* 申请内存 */
gpio_set_value(dev->cs_gpio, 0); /* 片选拉低 */
/* 第1次,发送要读取的寄存地址 */
txdata[0] = reg & ~0x80; /* 写数据的时候寄存器地址bit8要清零 */
t->tx_buf = txdata; /* 要发送的数据 */
t->len = 1; /* 1个字节 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
/* 第2次,发送要写入的数据 */
t->tx_buf = buf; /* 要写入的数据 */
t->len = len; /* 写入的字节数 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
kfree(t); /* 释放内存 */
gpio_set_value(dev->cs_gpio, 1);/* 片选拉高,释放ICM20608 */
return ret;
}
/*
* @description : 读取icm20608指定寄存器值,读取一个寄存器
* @param - dev: icm20608设备
* @param - reg: 要读取的寄存器
* @return : 读取到的寄存器值
*/
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{
u8 data = 0;
icm20608_read_regs(dev, reg, &data, 1);
return data;
}
/*
* @description : 向icm20608指定寄存器写入指定的值,写一个寄存器
* @param - dev: icm20608设备
* @param - reg: 要写的寄存器
* @param - data: 要写入的值
* @return : 无
*/
static void icm20608_write_onereg