总线驱动--SPI驱动
总线驱动–SPI驱动
文章目录
- 总线驱动--SPI驱动
- SPI主机驱动
- 1、spi_master 申请与释放
- 2、spi_master 的注册与注销
- SPI设备驱动
- SPI 设备和驱动匹配过程
- I.MX6U SPI 主机驱动分析
- SPI 设备驱动编写流程
- SPI 设备信息描述
- 1、 IO 的 pinctrl 子节点创建与修改
- 2、 SPI 设备节点的创建与修改
- SPI 设备数据收发处理流程
- ICM20608实例
- 修改设备树
- 1、添加 ICM20608 所使用的 IO
- 2、在 ecspi3 节点追加 icm20608 子节点
- 编写 ICM20608 驱动
- icm20608reg.h
- icm20608.c
- icm20608App.c
- 编译测试 APP
SPI主机驱动
SPI 主机驱动就是 SOC 的 SPI 控制器驱动,类似 I2C 驱动里面的适配器驱动。 Linux 内核使用 spi_master 表示 SPI 主机驱动, spi_master 是个结构体,定义在 include/linux/spi/spi.h 文件中,内容如下:
struct spi_master {struct device dev; // 设备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; // DMA对齐要求/* spi_device.mode flags understood by this controller driver */u16 mode_bits; // SPI模式/* bitmask of supported bits_per_word for transfers */u32 bits_per_word_mask; // 支持的字长掩码
#define SPI_BPW_MASK(bits) BIT((bits) - 1) // 位掩码
#define SPI_BIT_MASK(bits) (((bits) == 32) ? ~0U : (BIT(bits) - 1)) // 位掩码
#define SPI_BPW_RANGE_MASK(min, max) (SPI_BIT_MASK(max) - SPI_BIT_MASK(min - 1)) // 位掩码/* limits on transfer speed */ // 传输速度限制u32 min_speed_hz; // 最小传输速度u32 max_speed_hz; // 最大传输速度/* 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 */
#define SPI_MASTER_MUST_RX BIT(3) /* requires rx */
#define SPI_MASTER_MUST_TX BIT(4) /* requires tx */ /* 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);/* 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);/* called on release() to free memory provided by spi_master */void (*cleanup)(struct spi_device *spi);/** Used to enable core support for DMA handling, if can_dma()* exists and returns true then the transfer will be mapped* prior to transfer_one() being called. The driver should* not modify or store xfer and dma_tx and dma_rx must be set* while the device is prepared.*/bool (*can_dma)(struct spi_master *master,struct spi_device *spi,struct spi_transfer *xfer);/** 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 idling; // 是否空闲bool busy; // 是否忙bool running; // 是否运行bool rt; // 是否实时bool auto_runtime_pm; // 是否自动运行时电源管理bool cur_msg_prepared; // 当前消息是否准备好bool cur_msg_mapped; // 当前消息是否映射struct completion xfer_completion; // 完成size_t max_dma_len; // 最大DMA长度/* 准备传输硬件 */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 (*prepare_message)(struct spi_master *master,struct spi_message *message);/* 取消准备消息 */int (*unprepare_message)(struct spi_master *master,struct spi_message *message);/** 这些钩子是为使用核心提供的transfer_one_message()的通用实现的驱动程序而设计的。*//* 设置芯片选择 */void (*set_cs)(struct spi_device *spi, bool enable);/* 传输单个spi_transfer */int (*transfer_one)(struct spi_master *master, struct spi_device *spi,struct spi_transfer *transfer);/* 处理错误 */void (*handle_err)(struct spi_master *master,struct spi_message *message);/* gpio chip select */int *cs_gpios; // GPIO片选/* DMA channels for use with core dmaengine helpers */struct dma_chan *dma_tx; // DMA发送通道struct dma_chan *dma_rx; // DMA接收通道/* dummy data for full duplex devices */void *dummy_rx; // 全双工设备的虚拟接收缓冲区void *dummy_tx; // 全双工设备的虚拟发送缓冲区
};
- transfer 函数,和 i2c_algorithm 中的 master_xfer 函数一样,控制器数据传输函数。
- transfer_one_message 函数,也用于 SPI 数据发送,用于发送一个 spi_message,SPI 的数据会打包成 spi_message,然后以队列方式发送出去。
也就是 SPI 主机端最终会通过 transfer 函数与 SPI 设备进行通信,因此对于 SPI 主机控制器的驱动编写者而言 transfer 函数是需要实现的,因为不同的 SOC 其 SPI 控制器不同,寄存器都不一样。和 I2C 适配器驱动一样, SPI 主机驱动一般都是 SOC 厂商去编写的,所以我们作为 SOC 的使用者,这一部分的驱动就不用操心了,除非你是在 SOC 原厂工作,内容就是写 SPI 主机驱动。
SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册spi_master。
1、spi_master 申请与释放
spi_alloc_master 函数用于申请 spi_master,函数原型如下:
struct spi_master *spi_alloc_master(struct device *dev,
unsigned size)
函数参数和返回值含义如下:
dev:设备,一般是 platform_device 中的 dev 成员变量。
size:私有数据大小,可以通过 spi_master_get_devdata 函数获取到这些私有数据。
返回值:申请到的 spi_master。
spi_master 的释放通过 spi_master_put 函数来完成,当我们删除一个 SPI 主机驱动的时候就
需要释放掉前面申请的 spi_master,spi_master_put 函数原型如下:
void spi_master_put(struct spi_master *master)
函数参数和返回值含义如下:
master:要释放的 spi_master。
返回值:无。
2、spi_master 的注册与注销
当 spi_master 初始化完成以后就需要将其注册到 Linux 内核,spi_master 注册函数为spi_register_master,函数原型如下:
int spi_register_master(struct spi_master *master)
函数参数和返回值含义如下:
master:要注册的 spi_master。
返回值:0,成功;负值,失败。
I.MX6U 的 SPI 主机驱动会采用 spi_bitbang_start 这个 API 函数来完成 spi_master 的注册,spi_bitbang_start 函数内部其实也是通过调用 spi_register_master 函数来完成 spi_master 的注册。
如果要注销 spi_master 的话可以使用 spi_unregister_master 函数,此函数原型为:
void spi_unregister_master(struct spi_master *master)
函数参数和返回值含义如下:
master:要注销的 spi_master。
返回值:无。
如果使用 spi_bitbang_start 注册 spi_master 的话就要使用 spi_bitbang_stop 来注销掉spi_master。
SPI设备驱动
spi 设备驱动也和 i2c 设备驱动也很类似, Linux 内核使用 spi_driver 结构体来表示 spi 设备驱动,我们在编写 SPI 设备驱动的时候需要实现 spi_driver。 spi_driver 结构体定义在include/linux/spi/spi.h 文件中,结构体内容如下:
struct spi_driver {// SPI设备ID表const struct spi_device_id *id_table;// 探测SPI设备int (*probe)(struct spi_device *spi);// 移除SPI设备int (*remove)(struct spi_device *spi);// 关闭SPI设备void (*shutdown)(struct spi_device *spi);// 设备驱动struct device_driver driver;
};
可以看出,spi_driver 和 i2c_driver、platform_driver 基本一样,当 SPI 设备和驱动匹配成功
以后 probe 函数就会执行。
同样的, spi_driver 初始化完成以后需要向 Linux 内核注册, spi_driver 注册函数为spi_register_driver,函数原型如下:
int spi_register_driver(struct spi_driver *sdrv)
函数参数和返回值含义如下:
sdrv:要注册的 spi_driver。
返回值:0,注册成功;赋值,注册失败。
注销 SPI 设备驱动以后也需要注销掉前面注册的 spi_driver,使用 spi_unregister_driver 函数完成 spi_driver 的注销,函数原型如下:
void spi_unregister_driver(struct spi_driver *sdrv)
函数参数和返回值含义如下:
sdrv:要注销的 spi_driver
返回值: 无。
spi_driver 注册示例程序如下:
/* probe 函数 */static int xxx_probe(struct spi_device *spi){/* 具体函数内容 */return 0;}/* remove 函数 */static int xxx_remove(struct spi_device *spi){/* 具体函数内容 */return 0;}/* 传统匹配方式 ID 列表 */static const struct spi_device_id xxx_id[] = {{"xxx", 0},{}};/* 设备树匹配列表 */static const struct of_device_id xxx_of_match[] = {{ .compatible = "xxx" },{ /* Sentinel */ }};/* SPI 驱动结构体 */static struct spi_driver xxx_driver = {.probe = xxx_probe,.remove = xxx_remove,.driver = {.owner = THIS_MODULE,.name = "xxx",.of_match_table = xxx_of_match,},.id_table = xxx_id,};/* 驱动入口函数 */static int init xxx_init(void)
{return spi_register_driver(&xxx_driver);}/* 驱动出口函数 */static void exit xxx_exit(void){spi_unregister_driver(&xxx_driver);}module_init(xxx_init);module_exit(xxx_exit);
第 1~36 行,spi_driver 结构体,需要 SPI 设备驱动人员编写,包括匹配表、probe 函数等。
和 i2c_driver、platform_driver 一样,就不详细讲解了。
第 39~42 行,在驱动入口函数中调用 spi_register_driver 来注册 spi_driver。
第 45~48 行,在驱动出口函数中调用 spi_unregister_driver 来注销 spi_driver。
SPI 设备和驱动匹配过程
SPI 设备和驱动的匹配过程是由 SPI 总线来完成的,这点和 platform、 I2C 等驱动一样, SPI总线为 spi_bus_type,定义在 drivers/spi/spi.c 文件中,内容如下:
struct bus_type spi_bus_type = {.name = "spi",.dev_groups = spi_dev_groups,.match = spi_match_device,.uevent = spi_uevent,
};
可以看出, SPI 设备和驱动的匹配函数为 spi_match_device,函数内容如下:
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);/* Attempt an OF style match */if (of_driver_match_device(dev, drv))return 1;/* Then try ACPI */if (acpi_driver_match_device(dev, drv))return 1;if (sdrv->id_table)return !!spi_match_id(sdrv->id_table, spi);return strcmp(spi->modalias, drv->name) == 0;
}
- spi_match_device 函数和 i2c_match_device 函数的对于设备和驱动的匹配过程基本一样。
- of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 SPI 设备节点的 compatible 属性和 of_device_id 中的compatible 属性是否相等,如果相当的话就表示 SPI 设备和驱动匹配。
- acpi_driver_match_device 函数用于 ACPI 形式的匹配。
- spi_match_id 函数用于传统的、无设备树的 SPI 设备和驱动匹配过程。比较SPI设备名字和
- spi_device_id 的 name 字段是否相等,相等的话就说明 SPI 设备和驱动匹配。
比较 spi_device 中 modalias 成员变量和 device_driver 中的 name 成员变量是否相等。
I.MX6U SPI 主机驱动分析
和 I2C 的适配器驱动一样, SPI 主机驱动一般都由 SOC 厂商编写好了,打开 imx6ull.dtsi文件,找到如下所示内容:
ecspi1: ecspi@02008000 {compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";reg = <0x02008000 0x4000>;interrupts = <GIC_SPI 31 IRQ_TYPE_LEVEL_HIGH>;dmas = <&sdma 3 7 1>, <&sdma 4 7 2>;dma-names = "rx", "tx";clocks = <&clks IMX6SLL_CLK_ECSPI1>,<&clks IMX6SLL_CLK_ECSPI1>;clock-names = "ipg", "per";status = "disabled";};ecspi2: ecspi@0200c000 {compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";reg = <0x0200c000 0x4000>;interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;dmas = <&sdma 5 7 1>, <&sdma 6 7 2>;dma-names = "rx", "tx";clocks = <&clks IMX6SLL_CLK_ECSPI2>,<&clks IMX6SLL_CLK_ECSPI2>;clock-names = "ipg", "per";status = "disabled";};ecspi3: ecspi@02010000 {compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";reg = <0x02010000 0x4000>;interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;dmas = <&sdma 7 7 1>, <&sdma 8 7 2>;dma-names = "rx", "tx";clocks = <&clks IMX6SLL_CLK_ECSPI3>,<&clks IMX6SLL_CLK_ECSPI3>;clock-names = "ipg", "per";status = "disabled";};ecspi4: ecspi@02014000 {compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";reg = <0x02014000 0x4000>;interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>;dmas = <&sdma 9 7 1>, <&sdma 10 7 2>;dma-names = "rx", "tx";clocks = <&clks IMX6SLL_CLK_ECSPI4>,<&clks IMX6SLL_CLK_ECSPI4>;clock-names = "ipg", "per";status = "disabled";};
重点来看一下第 4 行的 compatible 属性值, compatible 属性有两个值“fsl,imx6ul-ecspi”和“fsl,imx51-ecspi”,在 Linux 内核源码中搜素这两个属性值即可找到 I.MX6U 对应的 ECSPI(SPI)主机驱动。 I.MX6U 的 ECSPI 主机驱动文件为 drivers/spi/spi-imx.c,在此文件中找到如下内容:
static struct platform_device_id spi_imx_devtype[] = {{.name = "imx1-cspi",.driver_data = (kernel_ulong_t) &imx1_cspi_devtype_data,}, {.name = "imx21-cspi",.driver_data = (kernel_ulong_t) &imx21_cspi_devtype_data,}, {.name = "imx27-cspi",.driver_data = (kernel_ulong_t) &imx27_cspi_devtype_data,}, {.name = "imx31-cspi",.driver_data = (kernel_ulong_t) &imx31_cspi_devtype_data,}, {.name = "imx35-cspi",.driver_data = (kernel_ulong_t) &imx35_cspi_devtype_data,}, {.name = "imx51-ecspi",.driver_data = (kernel_ulong_t) &imx51_ecspi_devtype_data,}, {.name = "imx6ul-ecspi",.driver_data = (kernel_ulong_t) &imx6ul_ecspi_devtype_data,}, {/* sentinel */}};
static const struct of_device_id spi_imx_dt_ids[] = {{ .compatible = "fsl,imx1-cspi", .data = &imx1_cspi_devtype_data, },{ .compatible = "fsl,imx21-cspi", .data = &imx21_cspi_devtype_data, },{ .compatible = "fsl,imx27-cspi", .data = &imx27_cspi_devtype_data, },{ .compatible = "fsl,imx31-cspi", .data = &imx31_cspi_devtype_data, },{ .compatible = "fsl,imx35-cspi", .data = &imx35_cspi_devtype_data, },{ .compatible = "fsl,imx51-ecspi", .data = &imx51_ecspi_devtype_data, },{ .compatible = "fsl,imx6ul-ecspi", .data = &imx6ul_ecspi_devtype_data, },{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, spi_imx_dt_ids);
static struct platform_driver spi_imx_driver = {.driver = {.name = DRIVER_NAME,.of_match_table = spi_imx_dt_ids,.pm = IMX_SPI_PM,},.id_table = spi_imx_devtype,.probe = spi_imx_probe,.remove = spi_imx_remove,
};
module_platform_driver(spi_imx_driver);
- spi_imx_devtype 为 SPI 无设备树匹配表。
- spi_imx_dt_ids 为 SPI 设备树匹配表。
- “fsl,imx6ul-ecspi”匹配项,因此可知 I.MX6U 的 ECSPI 驱动就是 spi-imx.c 这个文件。
- platform_driver 驱动框架,和 I2C 的适配器驱动一样,SPI 主机驱动器采用了 platfom 驱动框架。当设备和驱动匹配成功以后 spi_imx_probe 函数就会执行。
spi_imx_probe 函数会从设备树中读取相应的节点属性值,申请并初始化 spi_master,最后调用
spi_bitbang_start 函数(spi_bitbang_start 会调用 spi_register_master 函数)向 Linux 内核注册spi_master。
对于 I.MX6U 来讲,SPI 主机的最终数据收发函数为spi_imx_transfer,此函数通过如下层层调用最终实现SPI 数据发送:
spi_imx_transfer-> spi_imx_pio_transfer-> spi_imx_push-> spi_imx->tx
spi_imx 是个 spi_imx_data 类型的机构指针变量,其中 tx 和 rx 这两个成员变量分别为 SPI数据发送和接收函数。I.MX6U SPI 主机驱动会维护一个 spi_imx_data 类型的变量 spi_imx,并且使用 spi_imx_setupxfer 函数来设置 spi_imx 的 tx 和 rx 函数。根据要发送的数据数据位宽的不同,分别有 8 位、16 位和 32 位的发送函数,如下所示:
spi_imx_buf_tx_u8
spi_imx_buf_tx_u16
spi_imx_buf_tx_u32
同理,也有 8 位、16 位和 32 位的数据接收函数,如下所示:
spi_imx_buf_rx_u8
spi_imx_buf_rx_u16
spi_imx_buf_rx_u32
我们就以 spi_imx_buf_tx_u8 这个函数为例,看看,一个自己的数据发送是怎么完成的,在spi-imx.c 文件中找到如下所示内容:
#define MXC_SPI_BUF_TX(type) \
static void spi_imx_buf_tx_##type(struct spi_imx_data *spi_imx) \
{ \type val = 0; \\if (spi_imx->tx_buf) { \val = *(type *)spi_imx->tx_buf; \spi_imx->tx_buf += sizeof(type); \} \\spi_imx->count -= sizeof(type); \\writel(val, spi_imx->base + MXC_CSPITXDATA); \
}MXC_SPI_BUF_RX(u8)
MXC_SPI_BUF_TX(u8)
MXC_SPI_BUF_RX(u16)
MXC_SPI_BUF_TX(u16)
MXC_SPI_BUF_RX(u32)
MXC_SPI_BUF_TX(u32)
spi_imx_buf_tx_u8 函数是通过 MXC_SPI_BUF_TX 宏来实现的。第 164 行就是将要发送的数据值写入到 ECSPI 的 TXDATA 寄存器里面去,这和我们 SPI 裸机实验的方法一样。将第 168 行的 MXC_SPI_BUF_TX(u8)展开就是 spi_imx_buf_tx_u8 函数。其他的 tx 和 rx 函数都是这样实现的,这里就不做介绍了。基本套路和 I2C 的适配器驱动程序类似。
SPI 设备驱动编写流程
SPI 设备信息描述
1、 IO 的 pinctrl 子节点创建与修改
首先肯定是根据所使用的 IO 来创建或修改 pinctrl 子节点,这个没什么好说的,唯独要注意的就是检查相应的 IO 有没有被其他的设备所使用,如果有的话需要将其删除掉!
2、 SPI 设备节点的创建与修改
采用设备树的情况下, SPI 设备信息描述就通过创建相应的设备子节点来完成,我们可以打开 imx6qdl-sabresd.dtsi 这个设备树头文件,在此文件里面找到如下所示内容:
&ecspi1 {fsl,spi-num-chipselects = <1>;cs-gpios = <&gpio4 9 0>;pinctrl-names = "default";pinctrl-0 = <&pinctrl_ecspi1>;status = "okay";flash: m25p80@0 {#address-cells = <1>;#size-cells = <1>;compatible = "st,m25p32";spi-max-frequency = <20000000>;reg = <0>;};
};
I.MX6Q 的一款板子上的一个SPI 设备节点,在这个板子的 ECSPI 接
口上接了一个m25p80,这是一个 SPI 接口的设备。
- 设置“fsl,spi-num-chipselects”属性为 1,表示只有一个设备。
- 设置“cs-gpios”属性,也就是片选信号为 GPIO4_IO09。
- 设置“pinctrl-names”属性,也就是 SPI 设备所使用的 IO 名字。
- 设置“pinctrl-0”属性,也就是所使用的 IO 对应的 pinctrl 节点。
- 将 ecspi1 节点的“status”属性改为“okay”。
- ecspi1 下的 m25p80 设备信息,每一个 SPI 设备都采用一个子节点来描述其设备信息。第 315 行的“m25p80@0”后面的“0”表示 m25p80 的接到了ECSPI 的通道 0上。这个要根据自己的具体硬件来设置。
- SPI 设备的 compatible 属性值,用于匹配设备驱动。
- “spi-max-frequency”属性设置 SPI 控制器的最高频率,这个要根据所使用的SPI 设备来设置,比如在这里将 SPI 控制器最高频率设置为 20MHz。
reg 属性设置 m25p80 这个设备所使用的ECSPI 通道,和“m25p80@0”后面的“0”一样。
我们一会在编写 ICM20608 的设备树节点信息的时候就参考示例代码 中的内容即可。
SPI 设备数据收发处理流程
SPI 设备驱动的核心是 spi_driver,这个我们已经在 62.1.2 小节讲过了。当我们向 Linux 内核注册成功 spi_driver 以后就可以使用 SPI 核心层提供的 API 函数来对设备进行读写操作了。首先是 spi_transfer 结构体,此结构体用于描述 SPI 传输信息,结构体内容如下:
/*** struct spi_transfer - 一个读写缓冲区对* @tx_buf: 待写入的数据(dma-safe memory),或者为NULL* @rx_buf: 待读取的数据(dma-safe memory),或者为NULL* @tx_dma: tx_buf的DMA地址,如果@spi_message.is_dma_mapped* @rx_dma: rx_buf的DMA地址,如果@spi_message.is_dma_mapped* @tx_nbits: 写入使用的位数。如果为0,则使用默认值(SPI_NBITS_SINGLE)。* @rx_nbits: 读取使用的位数。如果为0,则使用默认值(SPI_NBITS_SINGLE)。* @len: rx和tx缓冲区的大小(以字节为单位)* @speed_hz: 选择与设备默认速度不同的速度进行此传输。如果为0,则使用默认值(来自@spi_device)。* @bits_per_word: 选择与设备默认的bits_per_word不同的bits_per_word进行此传输。如果为0,则使用默认值(来自@spi_device)。* @cs_change: 在此传输完成后影响chipselect* @delay_usecs: 在此传输之后延迟的微秒数,然后(可选)更改chipselect状态,然后开始下一个传输或完成此@spi_message。* @transfer_list: 通过@spi_message.transfers对传输进行排序* @tx_sg: 用于传输的Scatterlist,目前不适用于客户端使用* @rx_sg: 用于接收的Scatterlist,目前不适用于客户端使用** SPI传输始终写入与读取相同数量的字节。* 协议驱动程序应始终提供@rx_buf和/或@tx_buf。* 在某些情况下,它们可能还希望为正在传输的数据提供DMA地址;当底层驱动程序使用dma时,这可能会减少开销。** 如果传输缓冲区为null,则填充的数据将被忽略。如果tx_buf和rx_buf相同,则可以使用单个缓冲区进行全双工传输。
在这种情况下,传输长度必须是偶数,并且将交替写入和读取数据。
SPI传输的字节顺序为big-endian,除了SPI_LSB_FIRST。因此,例如,当bits_per_word为16时,缓冲区的长度为2N字节(@len = 2N),并以CPU字节顺序保存N个16位字。
当SPI传输的字大小不是8位的2的幂时,这些内存字包括额外的位。内存中的字始终由协议驱动程序视为右对齐,因此未定义的(rx)或未使用的(tx)位始终是最高有效位。
所有SPI传输都从相关的chipselect处开始。通常,它保持选定状态,直到消息中的最后一个传输之后。驱动程序可以使用cs_change来影响chipselect信号。
(i)如果传输不是消息中的最后一个,则使用此标志使chipselect在消息中间短暂地变为非活动状态。以这种方式切换chipselect可能需要终止芯片命令,从而使单个spi_message执行所有芯片事务。
(ii)当传输是消息中的最后一个时,芯片可能保持选定状态,直到下一个传输。在没有阻止消息发送到其他设备的情况下,这只是性能提示;启动发送到另一个设备的消息会取消选择此设备。但在其他情况下,这可以用于确保正确性。某些设备需要从一系列spi_message提交构建协议事务,在这些事务中,一个消息的内容由先前消息的结果确定,并且整个事务在chipselect变为非活动状态时结束。
当SPI可以以1x、2x或4x传输时,它可以通过@tx_nbits和@rx_nbits从设备获取此传输信息。在双向传输中,这两个应该都设置。用户可以使用SPI_NBITS_SINGLE(1x)SPI_NBITS_DUAL(2x)和SPI_NBITS_QUAD(4x)设置传输模式以支持这三种传输。
提交spi_message(及其spi_transfers)到较低层的代码负责管理其内存。对您没有明确设置的每个字段进行零初始化,以隔离未来的API更新。在提交消息及其传输后,请忽略它们,直到完成回调。
*/
struct spi_transfer {const void *tx_buf; /* 待写入的数据(dma-safe memory),或者为NULL */void *rx_buf; /* 待读取的数据(dma-safe memory),或者为NULL */unsigned len; /* rx和tx缓冲区的大小(以字节为单位) */dma_addr_t tx_dma; /* tx_buf的DMA地址,如果@spi_message.is_dma_mapped */dma_addr_t rx_dma; /* rx_buf的DMA地址,如果@spi_message.is_dma_mapped */struct sg_table tx_sg; /* 用于传输的Scatterlist,目前不适用于客户端使用 */struct sg_table rx_sg; /* 用于接收的Scatterlist,目前不适用于客户端使用 */unsigned cs_change:1; /* 在此传输完成后影响chipselect */unsigned tx_nbits:3; /* 写入使用的位数。如果为0,则使用默认值(SPI_NBITS_SINGLE)。 */unsigned rx_nbits:3; /* 读取使用的位数。如果为0,则使用默认值(SPI_NBITS_SINGLE)。 */
#define SPI_NBITS_SINGLE 0x01 /* 1bit transfer */
#define SPI_NBITS_DUAL 0x02 /* 2bits transfer */
#define SPI_NBITS_QUAD 0x04 /* 4bits transfer */u8 bits_per_word; /* 选择与设备默认的bits_per_word不同的bits_per_word进行此传输。如果为0,则使用默认值(来自@spi_device)。 */u16 delay_usecs; /* 在此传输之后延迟的微秒数,然后(可选)更改chipselect状态,然后开始下一个传输或完成此@spi_message。 */u32 speed_hz; /* 选择与设备默认速度不同的速度进行此传输。如果为0,则使用默认值(来自@spi_device)。 */struct list_head transfer_list; /* 通过@spi_message.transfers对传输进行排序 */
};
- tx_buf 保存着要发送的数据。
- rx_buf 用于保存接收到的数据。
- len 是要进行传输的数据长度, SPI 是全双工通信,因此在一次通信中发送和接收的字节数都是一样的,所以 spi_transfer 中也就没有发送长度和接收长度之分。
spi_transfer 需要组织成 spi_message, spi_message 也是一个结构体,内容如下:
struct spi_message {struct list_head transfers;struct spi_device *spi;unsigned is_dma_mapped:1;/* 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 frame_length;unsigned actual_length;int status;/* 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;
};
在使用spi_message之前需要对其进行初始化, spi_message初始化函数为spi_message_init,函数原型如下:
void spi_message_init(struct spi_message *m)
函数参数和返回值含义如下:
m:要初始化的 spi_message。
返回值:无。
spi_message 初始化完成以后需要将 spi_transfer 添加到 spi_message 队列中,这里我们要用到 spi_message_add_tail 函数,此函数原型如下:
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
函数参数和返回值含义如下:
t:要添加到队列中的 spi_transfer。
m:spi_transfer 要加入的 spi_message。
返回值:无。
spi_message 准备好以后既可以进行数据传输了,数据传输分为同步传输和异步传输,同步传输会阻塞的等待SPI 数据传输完成,同步传输函数为 spi_sync,函数原型如下:
int spi_sync(struct spi_device *spi, struct spi_message *message)
函数参数和返回值含义如下:
spi:要进行数据传输的 spi_device。
message:要传输的 spi_message。
返回值:无。
异步传输不会阻塞的等到SPI 数据传输完成,异步传输需要设置 spi_message 中的 complete成员变量,complete 是一个回调函数,当 SPI 异步传输完成以后此函数就会被调用。SPI 异步传输函数为 spi_async,函数原型如下:
int spi_async(struct spi_device *spi, struct spi_message *message)
函数参数和返回值含义如下:
spi:要进行数据传输的 spi_device。
message:要传输的 spi_message。
返回值:无。
我们采用同步传输方式来完成 SPI 数据的传输工作,也就是 spi_sync 函数。综上所述,SPI 数据传输步骤如下:
①、申请并初始化 spi_transfer,设置 spi_transfer 的 tx_buf 成员变量, tx_buf 为要发送的数据。然后设置 rx_buf 成员变量, rx_buf 保存着接收到的数据。最后设置 len 成员变量,也就是要进行数据通信的长度。
②、使用 spi_message_init 函数初始化 spi_message。
③、使用spi_message_add_tail函数将前面设置好的spi_transfer添加到spi_message队列中。
④、使用 spi_sync 函数完成 SPI 数据同步传输。
通过 SPI 进行 n 个字节的数据发送和接收的示例代码如下所示:
spi_message_init(&m); /* 初始化 spi_message */
spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */ ret = spi_sync(spi, &m); /*
同步传输 */
return ret;
spi_message_init(&m); /* 初始化 spi_message */
spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */ ret = spi_sync(spi, &m); /*
同步传输 */
return ret;
/* SPI 多字节发送 */
static int spi_send(struct spi_device *spi, u8 *buf, int len)
{int ret;
struct spi_message m;
struct spi_transfer t = {.tx_buf = buf,
.len = len,
};
}
/* SPI 多字节接收 */
static int spi_receive(struct spi_device *spi, u8 *buf, int len)
{int ret;
struct spi_message m;
struct spi_transfer t = {.rx_buf = buf,
.len = len,
};
}
ICM20608实例
修改设备树
1、添加 ICM20608 所使用的 IO
首先在 imx6ull-alientek-emmc.dts 文件中添加 ICM20608 所使用的 IO 信息,在 iomuxc 节点中添加一个新的子节点来描述 ICM20608 所使用的 SPI 引脚,子节点名字为 pinctrl_ecspi3,节点内容如下所示:
pinctrl_ecspi3: icm20608 {fsl,pins = <MX6UL_PAD_UART2_TX_DATA GPIO1_IO20 0x10b0 /* CS */MX6UL_PAD_UART2_RX_DATA ECSPI3_SCLK 0x10b1 /* SCLK */MX6UL_PAD_UART2_RTS_B ECSPI3_MISO 0x10b1 /* MISO */ MX6UL_PAD_UART2_CTS_B ECSPI3_MOSI 0x10b1 /、* MOSI */>;
};
UART2_TX_DATA 这个 IO 是 ICM20608 的片选信号,这里我们并没有将其复用为 ECSPI3的 SS0 信号,而是将其复用为了普通的 GPIO。因为我们需要自己控制片选信号,所以将其复用为普通的 GPIO。
2、在 ecspi3 节点追加 icm20608 子节点
在 imx6ull-alientek-emmc.dts 文件中并没有任何向 ecspi3 节点追加内容的代码,这是因为NXP 官方的 6ULL EVK 开发板上没有连接 SPI 设备。在 imx6ull-alientek-emmc.dts 文件最后面加入如下所示内容:
&ecspi3 {fsl,spi-num-chipselects = <1>;cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;pinctrl-names = "default";pinctrl-0 = <&pinctrl_ecspi3>;status = "okay";spidev: icm20608@0 {compatible = "alientek,icm20608";spi-max-frequency = <8000000>;reg = <0>;};
};
第 2 行,设置当前片选数量为 1,因为就只接了一个 ICM20608。
第 3 行,一定要使用 “cs-gpios”属性来描述片选引脚,SPI 主机驱动就会控制片选引脚。
第 5 行,设置 IO 要使用的 pinctrl 子节点,也就是我们在示例代码 中新建的pinctrl_ecspi3。
第 6 行,imx6ull.dtsi 文件中默认将 ecspi3 节点状态(status)设置为“disable”,这里我们要将其改为“okay”。
第 8~12 行,icm20608 设备子节点,因为icm20608 连接在ECSPI3 的第 0 个通道上,因此@后面为 0。第 9 行设置节点属性兼容值为“alientek,icm20608”,第 10 行设置 SPI 最大时钟频率为 8MHz,这是 ICM20608 的SPI 接口所能支持的最大的时钟频率。第 11 行,icm20608 连接
在通道 0 上,因此 reg 为 0。
imx6ull-alientek-emmc.dts 文件修改完成以后重新编译一下,得到新的 dtb 文件,并使用新的 dtb 启动 Linux 系统
编写 ICM20608 驱动
icm20608reg.h
#ifndef ICM20608_H
#define ICM20608_H#define ICM20608G_ID 0XAF /* ID值 */
#define ICM20608D_ID 0XAE /* ID值 *//* ICM20608寄存器 *复位后所有寄存器地址都为0,除了*Register 107(0X6B) Power Management 1 = 0x40*Register 117(0X75) WHO_AM_I = 0xAF或0xAE*/
/* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */
#define ICM20_SELF_TEST_X_GYRO 0x00
#define ICM20_SELF_TEST_Y_GYRO 0x01
#define ICM20_SELF_TEST_Z_GYRO 0x02
#define ICM20_SELF_TEST_X_ACCEL 0x0D
#define ICM20_SELF_TEST_Y_ACCEL 0x0E
#define ICM20_SELF_TEST_Z_ACCEL 0x0F/* 陀螺仪静态偏移 */
#define ICM20_XG_OFFS_USRH 0x13
#define ICM20_XG_OFFS_USRL 0x14
#define ICM20_YG_OFFS_USRH 0x15
#define ICM20_YG_OFFS_USRL 0x16
#define ICM20_ZG_OFFS_USRH 0x17
#define ICM20_ZG_OFFS_USRL 0x18#define ICM20_SMPLRT_DIV 0x19
#define ICM20_CONFIG 0x1A
#define ICM20_GYRO_CONFIG 0x1B
#define ICM20_ACCEL_CONFIG 0x1C
#define ICM20_ACCEL_CONFIG2 0x1D
#define ICM20_LP_MODE_CFG 0x1E
#define ICM20_ACCEL_WOM_THR 0x1F
#define ICM20_FIFO_EN 0x23
#define ICM20_FSYNC_INT 0x36
#define ICM20_INT_PIN_CFG 0x37
#define ICM20_INT_ENABLE 0x38
#define ICM20_INT_STATUS 0x3A/* 加速度输出 */
#define ICM20_ACCEL_XOUT_H 0x3B
#define ICM20_ACCEL_XOUT_L 0x3C
#define ICM20_ACCEL_YOUT_H 0x3D
#define ICM20_ACCEL_YOUT_L 0x3E
#define ICM20_ACCEL_ZOUT_H 0x3F
#define ICM20_ACCEL_ZOUT_L 0x40/* 温度输出 */
#define ICM20_TEMP_OUT_H 0x41
#define ICM20_TEMP_OUT_L 0x42/* 陀螺仪输出 */
#define ICM20_GYRO_XOUT_H 0x43
#define ICM20_GYRO_XOUT_L 0x44
#define ICM20_GYRO_YOUT_H 0x45
#define ICM20_GYRO_YOUT_L 0x46
#define ICM20_GYRO_ZOUT_H 0x47
#define ICM20_GYRO_ZOUT_L 0x48#define ICM20_SIGNAL_PATH_RESET 0x68
#define ICM20_ACCEL_INTEL_CTRL 0x69
#define ICM20_USER_CTRL 0x6A
#define ICM20_PWR_MGMT_1 0x6B
#define ICM20_PWR_MGMT_2 0x6C
#define ICM20_FIFO_COUNTH 0x72
#define ICM20_FIFO_COUNTL 0x73
#define ICM20_FIFO_R_W 0x74
#define ICM20_WHO_AM_I 0x75/* 加速度静态偏移 */
#define ICM20_XA_OFFSET_H 0x77
#define ICM20_XA_OFFSET_L 0x78
#define ICM20_YA_OFFSET_H 0x7A
#define ICM20_YA_OFFSET_L 0x7B
#define ICM20_ZA_OFFSET_H 0x7D
#define ICM20_ZA_OFFSET_L 0x7E#endif
icm20608.c
#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"#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; /* 私有数据 */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 = -1;unsigned char txdata[1];unsigned char * rxdata;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); /* 申请内存 */if(!t) {return -ENOMEM;}rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL); /* 申请内存 */if(!rxdata) {goto out1;}/* 一共发送len+1个字节的数据,第一个字节为寄存器首地址,一共要读取len个字节长度的数据,*/txdata[0] = reg | 0x80; /* 写数据的时候首寄存器地址bit8要置1 */ t->tx_buf = txdata; /* 要发送的数据 */t->rx_buf = rxdata; /* 要读取的数据 */t->len = len+1; /* t->len=发送的长度+读取的长度 */spi_message_init(&m); /* 初始化spi_message */spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */ret = spi_sync(spi, &m); /* 同步发送 */if(ret) {goto out2;}memcpy(buf , rxdata+1, len); /* 只需要读取的数据 */out2:kfree(rxdata); /* 释放内存 */
out1: kfree(t); /* 释放内存 */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 = -1;unsigned char *txdata;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); /* 申请内存 */if(!t) {return -ENOMEM;}txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);if(!txdata) {goto out1;}/* 一共发送len+1个字节的数据,第一个字节为寄存器首地址,len为要写入的寄存器的集合,*/*txdata = reg & ~0x80; /* 写数据的时候首寄存器地址bit8要清零 */memcpy(txdata+1, buf, len); /* 把len个寄存器拷贝到txdata里,等待发送 */t->tx_buf = txdata; /* 要发送的数据 */t->len = len+1; /* t->len=发送的长度+读取的长度 */spi_message_init(&m); /* 初始化spi_message */spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */ret = spi_sync(spi, &m); /* 同步发送 */if(ret) {goto out2;}out2:kfree(txdata); /* 释放内存 */
out1:kfree(t); /* 释放内存 */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(struct icm20608_dev *dev, u8 reg, u8 value)
{u8 buf = value;icm20608_write_regs(dev, reg, &buf, 1);
}/** @description : 读取ICM20608的数据,读取原始数据,包括三轴陀螺仪、* : 三轴加速度计和内部温度。* @param - dev : ICM20608设备* @return : 无。*/
void icm20608_readdata(struct icm20608_dev *dev)
{unsigned char data[14] = { 0 };icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data, 14);dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]); dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]); dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]); dev->temp_adc = (signed short)((data[6] << 8) | data[7]); dev->gyro_x_adc = (signed short)((data[8] << 8) | data[9]); dev->gyro_y_adc = (signed short)((data[10] << 8) | data[11]);dev->gyro_z_adc = (signed short)((data[12] << 8) | data[13]);
}/** @description : 打开设备* @param - inode : 传递给驱动的inode* @param - filp : 设备文件,file结构体有个叫做pr似有ate_data的成员变量* 一般在open的时候将private_data似有向设备结构体。* @return : 0 成功;其他 失败*/
static int icm20608_open(struct inode *inode, struct file *filp)
{filp->private_data = &icm20608dev; /* 设置私有数据 */return 0;
}/** @description : 从设备读取数据 * @param - filp : 要打开的设备文件(文件描述符)* @param - buf : 返回给用户空间的数据缓冲区* @param - cnt : 要读取的数据长度* @param - offt : 相对于文件首地址的偏移* @return : 读取的字节数,如果为负值,表示读取失败*/
static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{signed int data[7];long err = 0;struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;icm20608_readdata(dev);data[0] = dev->gyro_x_adc;data[1] = dev->gyro_y_adc;data[2] = dev->gyro_z_adc;data[3] = dev->accel_x_adc;data[4] = dev->accel_y_adc;data[5] = dev->accel_z_adc;data[6] = dev->temp_adc;err = copy_to_user(buf, data, sizeof(data));return 0;
}/** @description : 关闭/释放设备* @param - filp : 要关闭的设备文件(文件描述符)* @return : 0 成功;其他 失败*/
static int icm20608_release(struct inode *inode, struct file *filp)
{return 0;
}/* icm20608操作函数 */
static const struct file_operations icm20608_ops = {.owner = THIS_MODULE,.open = icm20608_open,.read = icm20608_read,.release = icm20608_release,
};/** ICM20608内部寄存器初始化函数 * @param : 无* @return : 无*/
void icm20608_reginit(void)
{u8 value = 0;icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80);mdelay(50);icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01);mdelay(50);value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I);printk("ICM20608 ID = %#X\r\n", value); icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00); /* 输出速率是内部采样率 */icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18); /* 陀螺仪±2000dps量程 */icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18); /* 加速度计±16G量程 */icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04); /* 陀螺仪低通滤波BW=20Hz */icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW=21.2Hz */icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2, 0x00); /* 打开加速度计和陀螺仪所有轴 */icm20608_write_onereg(&icm20608dev, ICM20_LP_MODE_CFG, 0x00); /* 关闭低功耗 */icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN, 0x00); /* 关闭FIFO */
}/** @description : spi驱动的probe函数,当驱动与* 设备匹配以后此函数就会执行* @param - client : i2c设备* @param - id : i2c设备ID* */
static int icm20608_probe(struct spi_device *spi)
{/* 1、构建设备号 */if (icm20608dev.major) {icm20608dev.devid = MKDEV(icm20608dev.major, 0);register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);} else {alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME);icm20608dev.major = MAJOR(icm20608dev.devid);}/* 2、注册设备 */cdev_init(&icm20608dev.cdev, &icm20608_ops);cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);/* 3、创建类 */icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);if (IS_ERR(icm20608dev.class)) {return PTR_ERR(icm20608dev.class);}/* 4、创建设备 */icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME);if (IS_ERR(icm20608dev.device)) {return PTR_ERR(icm20608dev.device);}/*初始化spi_device */spi->mode = SPI_MODE_0; /*MODE0,CPOL=0,CPHA=0*/spi_setup(spi);icm20608dev.private_data = spi; /* 设置私有数据 *//* 初始化ICM20608内部寄存器 */icm20608_reginit(); return 0;
}/** @description : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行* @param - client : i2c设备* @return : 0,成功;其他负值,失败*/
static int icm20608_remove(struct spi_device *spi)
{/* 删除设备 */cdev_del(&icm20608dev.cdev);unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);/* 注销掉类和设备 */device_destroy(icm20608dev.class, icm20608dev.devid);class_destroy(icm20608dev.class);return 0;
}/* 传统匹配方式ID列表 */
static const struct spi_device_id icm20608_id[] = {{"alientek,icm20608", 0}, {}
};/* 设备树匹配列表 */
static const struct of_device_id icm20608_of_match[] = {{ .compatible = "alientek,icm20608" },{ /* Sentinel */ }
};/* SPI驱动结构体 */
static struct spi_driver icm20608_driver = {.probe = icm20608_probe,.remove = icm20608_remove,.driver = {.owner = THIS_MODULE,.name = "icm20608",.of_match_table = icm20608_of_match, },.id_table = icm20608_id,
};/** @description : 驱动入口函数* @param : 无* @return : 无*/
static int __init icm20608_init(void)
{return spi_register_driver(&icm20608_driver);
}/** @description : 驱动出口函数* @param : 无* @return : 无*/
static void __exit icm20608_exit(void)
{spi_unregister_driver(&icm20608_driver);
}module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
icm20608App.c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
/***************************************************************使用方法 :./icm20608App /dev/icm20608***************************************************************//** @description : main主程序* @param - argc : argv数组元素个数* @param - argv : 具体参数* @return : 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd;char *filename;signed int databuf[7];unsigned char data[14];signed int gyro_x_adc, gyro_y_adc, gyro_z_adc;signed int accel_x_adc, accel_y_adc, accel_z_adc;signed int temp_adc;float gyro_x_act, gyro_y_act, gyro_z_act;float accel_x_act, accel_y_act, accel_z_act;float temp_act;int ret = 0;if (argc != 2) {printf("Error Usage!\r\n");return -1;}filename = argv[1];fd = open(filename, O_RDWR);if(fd < 0) {printf("can't open file %s\r\n", filename);return -1;}while (1) {ret = read(fd, databuf, sizeof(databuf));if(ret == 0) { /* 数据读取成功 */gyro_x_adc = databuf[0];gyro_y_adc = databuf[1];gyro_z_adc = databuf[2];accel_x_adc = databuf[3];accel_y_adc = databuf[4];accel_z_adc = databuf[5];temp_adc = databuf[6];/* 计算实际值 */gyro_x_act = (float)(gyro_x_adc) / 16.4;gyro_y_act = (float)(gyro_y_adc) / 16.4;gyro_z_act = (float)(gyro_z_adc) / 16.4;accel_x_act = (float)(accel_x_adc) / 2048;accel_y_act = (float)(accel_y_adc) / 2048;accel_z_act = (float)(accel_z_adc) / 2048;temp_act = ((float)(temp_adc) - 25 ) / 326.8 + 25;printf("\r\n原始值:\r\n");printf("gx = %d, gy = %d, gz = %d\r\n", gyro_x_adc, gyro_y_adc, gyro_z_adc);printf("ax = %d, ay = %d, az = %d\r\n", accel_x_adc, accel_y_adc, accel_z_adc);printf("temp = %d\r\n", temp_adc);printf("实际值:");printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n", gyro_x_act, gyro_y_act, gyro_z_act);printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n", accel_x_act, accel_y_act, accel_z_act);printf("act temp = %.2f°C\r\n", temp_act);}usleep(100000); /*100ms */}close(fd); /* 关闭文件 */ return 0;
}
编译测试 APP
在 icm20608App.c 这个测试 APP 中我们用到了浮点计算,而 I.MX6U 是支持硬件浮点的,因此我们在编译 icm20608App.c 的时候就可以使能硬件浮点,这样可以加速浮点计算。使能硬件浮点很简单,在编译的时候加入如下参数即可:
-march-armv7-a -mfpu-neon -mfloat=hard
输入如下命令使能硬件浮点编译 icm20608App.c 这个测试程序:
arm-linux-gnueabihf-gcc -march=armv7-a -mfpu=neon -mfloat-abi=hard icm20608App.c -o
icm20608App
编译成功以后就会生成 icm20608App 这个应用程序,那么究竟有没有使用硬件浮点呢?使
用 arm-linux-gnueabihf-readelf 查看一下编译出来的 icm20608App 就知道了,输入如下命令:
arm-linux-gnueabihf-readelf -A icm20608App
可以看出 FPU 架构为 VFPv3, SIMD 使用了 NEON,并且使用了 SP 和 DP,说明 icm20608App 这个应用程序使用了硬件浮点。
struct spi_device {/* 设备模型表示 */struct device dev;/* SPI控制器 */struct spi_master *master;/* 最大时钟频率 */u32 max_speed_hz;/* 芯片选择 */u8 chip_select;/* 每个字的位数 */u8 bits_per_word;/* SPI模式 */u16 mode;
#define SPI_CPHA 0x01 /* 时钟相位 */
#define SPI_CPOL 0x02 /* 时钟极性 */
#define SPI_MODE_0 (0|0) /* (原始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 /* 片选高电平有效 */
#define SPI_LSB_FIRST 0x08 /* 每个字的位在传输线上的顺序 */
#define SPI_3WIRE 0x10 /* SI/SO信号共享 */
#define SPI_LOOP 0x20 /* 回环模式 */
#define SPI_NO_CS 0x40 /* 1个设备/总线,没有片选 */
#define SPI_READY 0x80 /* 从机拉低以暂停 */
#define SPI_TX_DUAL 0x100 /* 用2根线传输 */
#define SPI_TX_QUAD 0x200 /* 用4根线传输 */
#define SPI_RX_DUAL 0x400 /* 用2根线接收 */
#define SPI_RX_QUAD 0x800 /* 用4根线接收 *//* 中断号 */int irq;/* 控制器的运行时状态 */void *controller_state;/* 控制器的板级定义,例如FIFO初始化参数;来自board_info.controller_data */void *controller_data;/* 驱动程序名称或别名 */char modalias[SPI_NAME_SIZE];/* 片选GPIO号 */int cs_gpio; /** 可能需要更多的钩子来处理更多的协议选项,影响控制器与每个芯片的通信方式,例如:* - 内存打包(12位样本打包到低位,其他位置为零)* - 优先级* - 每个字后放弃片选* - 片选延迟* - ...*/
};
总线驱动--SPI驱动相关推荐
- [转载]Linux驱动-SPI驱动 之二:SPI通用接口层
通过上一篇文章的介绍,我们知道,SPI通用接口层用于把具体SPI设备的协议驱动和SPI控制器驱动联接在一起,通用接口层除了为协议驱动和控制器驱动提供一系列的标准接口API,同时还为这些接口API定义了 ...
- 2:STM32CubeMX配置STM32F103C8T6驱动-SPI驱动
STM32CubeMX下载地址: 下载界面 1:时钟 CS 2:模块 3:代码配置 u8 SPI_WriteByte(u8 Byte) {u8 rxdata = 1;HAL_GPIO_WritePin ...
- 4412 SPI驱动
1.Linux主机驱动和外设驱动分离思想(I2C驱动里有) SPI驱动总线架构:SPI核心层(x),SPI控制器驱动层(x),SPI设备驱动层(√).前面两个设备驱动搞明白了可以去看 2.教程中介绍: ...
- platform总线(Linux驱动开发篇)
简单介绍platform驱动中的led驱动,input设备驱动,i2c驱动,spi驱动 1.Platform led驱动 最简单的了解platform平台的例子,可以理解为3部分,由驱动层,系统核心层 ...
- Linux SPI总线和设备驱动架构之四:SPI数据传输的队列化
我们知道,SPI数据传输可以有两种方式:同步方式和异步方式.所谓同步方式是指数据传输的发起者必须等待本次传输的结束,期间不能做其它事情,用代码来解释就是,调用传输的函数后,直到数据传输完成,函数才会返 ...
- Linux SPI总线和设备驱动架构之三:SPI控制器驱动
通过第一篇文章,我们已经知道,整个SPI驱动架构可以分为协议驱动.通用接口层和控制器驱动三大部分.其中,控制器驱动负责最底层的数据收发工作,为了完成数据的收发工作,控制器驱动需要完成以下这些功能: 1 ...
- Linux SPI总线和设备驱动架构之二:SPI通用接口层
通过上一篇文章的介绍,我们知道,SPI通用接口层用于把具体SPI设备的协议驱动和SPI控制器驱动联接在一起,通用接口层除了为协议驱动和控制器驱动提供一系列的标准接口API,同时还为这些接口API定义了 ...
- Linux SPI总线和设备驱动架构之一:系统概述
SPI是"Serial Peripheral Interface" 的缩写,是一种四线制的同步串行通信接口,用来连接微控制器.传感器.存储设备,SPI设备分为主设备和从设备两种,用 ...
- stm32h7高速通信_【STM32H7教程】第75章 STM32H7的SPI总线应用之驱动DAC8501(双路输出,16bit分辨率,0-5V)...
第75章 STM32H7的SPI总线应用之驱动DAC8501(双路输出,16bit分辨率,0-5V) 本章节为大家讲解标准SPI接线方式驱动模数转换器DAC8501,制作了中断和DMA两种 ...
最新文章
- 4G EPS 中的小区选择
- SHA256CryptoServiceProvider vs SHA256Managed
- osgi实战学习之路:6. Service-1
- c++ 多key_详解Zabbix自定义Key监控Rabbitmq(监控特定队列)
- Ping Function
- 第一次接广告的心得,关于广告我怎么看
- Graph QL和SAP Graph的区别
- 鹤峰:美丽的茶乡—— 山水篇
- 微软私有云分享(R2)22 计算机配置文件与基础设置
- linux下mtr命令,如何使用Linux mtr命令
- mysql查询20-30的数据_20. mysql查询表的数据大小
- Magento教程 8:如何新增首页选单?
- IPMI从驱动到应用(下篇 )
- Tomcat(介绍,JDK安装,Tomcat安装,配置Tomcat监听80端口)
- python基于情感词典的情感分析
- java代码 软件_适合新手的java代码编写软件有哪些?
- java 图片合并成pdf_Java多张图片合成PDF
- 万能五笔输入法弹窗_万能五笔输入法广告怎么去掉
- Magick.NET Crack,支持多种文件格式的综合库
- 小程序云开发 免费版 云函数数量超过限制 怎么办