Linux SPI 驱动实验

一、SPI介绍

SPI 驱动框架和 I2C 很类似,都分为主机控制器驱动和设备驱动,主机控制器也就是 SOC的 SPI 控制器接口。

1、SPI 主机驱动(一般半导体厂商已写好)

SPI 主机驱动就是 SOC 的 SPI 控制器驱动,类似 I2C 驱动里面的适配器驱动。Linux 内核使用 spi_master 表示 SPI 主机驱动,spi_master 是个结构体,定义在 include/linux/spi/spi.h 文件中,内容如下(有缩减):

struct spi_master {
	......
	int (*transfer)(struct spi_device *spi,struct spi_message *mesg);
	......
	int (*transfer_one_message)(struct spi_master *master,struct spi_message *mesg);
	.....
};

transfer 函数,和 i2c_algorithm 中的 master_xfer 函数一样,控制器数据传输函数。
transfer_one_message 函数,也用于 SPI 数据发送,用于发送一个 spi_message,SPI 的数据会打包成 spi_message,然后以队列方式发送出去。

SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册spi_master。

1.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。
返回值:无。

1.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。

2、SPI 设备驱动(需要自己写)

我们在编写 SPI 设备驱动的时候需要实现 spi_driver。spi_driver 结构体定义在include/linux/spi/spi.h 文件中,结构体内容如下:

struct spi_driver {
	const struct spi_device_id *id_table;
	int (*probe)(struct spi_device *spi);
	int (*remove)(struct spi_device *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);

3、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;
1}

4、 I.MX6U SPI 主机驱动分析

ecspi3: ecspi@02010000 {
	#address-cells = <1>;
	#size-cells = <0>;
	compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
	reg = <0x02010000 0x4000>;
	interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
	clocks = <&clks IMX6UL_CLK_ECSPI3>,
	<&clks IMX6UL_CLK_ECSPI3>;
	clock-names = "ipg", "per";
	dmas = <&sdma 7 7 1>, <&sdma 8 7 2>;
	dma-names = "rx", "tx";
	status = "disabled";
};

重点来看一下第 4 行的 compatible 属性值,compatible 属性有两个值“fsl,imx6ul-ecspi”和“fsl,imx51-ecspi”,在 Linux 内核源码中搜素这两个属性值即可找到 I.MX6U 对应的 ECSPI(SPI)主机驱动。

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 设备驱动编写流程

1、SPI 设备信息描述

1.1、IO 的 pinctrl 子节点创建与修改
首先肯定是根据所使用的 IO 来创建或修改 pinctrl 子节点,要注意的就是检查相应的 IO 有没有被其他的设备所使用,如果有的话需要将其删除掉!

2.2、SPI 设备节点的创建与修改
采用设备树的情况下,SPI 设备信息描述就通过创建相应的设备子节点来完成,我们可以打开 imx6qdl-sabresd.dtsi 这个设备树头文件,在此文件里面找到如下所示内容:

&ecspi1 {
	fsl,spi-num-chipselects = <1>;		//属性为 1,表示只有一个设备
	cs-gpios = <&gpio4 9 0>;			//也就是片选信号为 GPIO4_IO09
	pinctrl-names = "default";			//也就是 SPI 设备所使用的 IO 名字
	pinctrl-0 = <&pinctrl_ecspi1>;		//所使用的 IO 对应的 pinctrl 节点
	status = "okay";					//将 ecspi1 节点的“status”属性改为“okay”

	flash: m25p80@0 {			//“m25p80@0”后面的“0”表示 m25p80 的接到了 ECSPI 的通道 0上。这个要根据自己的具体硬件来设置。	
		#address-cells = <1>;	
		#size-cells = <1>;
		compatible = "st,m25p32";		//SPI 设备的 compatible 属性值,用于匹配设备驱动
		spi-max-frequency = <20000000>;	//里将 SPI 控制器最高频率设置为 20MHz
		reg = <0>;						//和“m25p80@0”后面的“0”一样
	};
};

2、SPI 设备数据收发处理流程

SPI 设备驱动的核心是 spi_driver,当我们向 Linux 内核注册成功 spi_driver 以后就可以使用 SPI 核心层提供的 API 函数来对设备进行读写操作了。首先是 spi_transfer 结构体,此结构体用于描述 SPI 传输信息,结构体内容如下:

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;		//tx_buf 保存着要发送的数据
	void *rx_buf;			//rx_buf 用于保存接收到的数据
	unsigned len;			//len 是要进行传输的数据长度,SPI 是全双工通信,因此在一次通信中发送和接收的字节数都是一样的,所以 spi_transfer 中也就没有发送长度和接收长度之分。
	
	dma_addr_t tx_dma;
	dma_addr_t rx_dma;
	struct sg_table tx_sg;
	struct sg_table rx_sg;
	
	unsigned cs_change:1;
	unsigned tx_nbits:3;
	unsigned rx_nbits:3;
	#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;
	u16 delay_usecs;
	u32 speed_hz;
	
	struct list_head transfer_list;
};

spi_transfer 需要组织成 spi_message,spi_message 也是一个结构体,内容如下:

struct spi_message {
	struct list_head transfers;
	
	struct spi_device *spi;
	
	unsigned is_dma_mapped:1;
	......
	/* 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 多字节发送 */
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_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_receive(struct spi_device *spi, u8 *buf, int len)
{
	 int ret;
	 struct spi_message m;
	 
	 struct spi_transfer t = {
		 .rx_buf = buf,
		 .len = len,
	 };
	 spi_message_init(&m); /* 初始化 spi_message */
	 spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */
	 ret = spi_sync(spi, &m); /* 同步传输 */
	 return ret;
}

三、试验程序编写

1、修改设备树

首先在 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 */
	>;
};

2、在 ecspi3 节点追加 icm20608 子节点

在 imx6ull-alientek-emmc.dts 文件最后面
加入如下所示内容:

&ecspi3 {
	fsl,spi-num-chipselects = <1>;				//设置当前片选数量为 1,因为就只接了一个 ICM20608
	cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;		//SPI 主机驱动就会控制片选引脚
	pinctrl-names = "default";					//
	pinctrl-0 = <&pinctrl_ecspi3>;
	status = "okay";

	spidev: icm20608@0 {
		compatible = "alientek,icm20608";
		spi-max-frequency = <8000000>;
		reg = <0>;
	};
};

3、编写 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_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

3.1、icm20608 设备结构体创建

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.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; /* 私有数据 */
	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;

3.2、icm20608 的 spi_driver 注册与注销

/* 传统匹配方式 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");
MODULE_AUTHOR("hsd");

3.3、probe&remove 函数

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);			//设置好 spi_device 以后需要使用 spi_setup 配置一下
	icm20608dev.private_data = spi; /* 设置私有数据 */

	/* 初始化 ICM20608 内部寄存器 */
	icm20608_reginit(); 
	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;
}

3.4、icm20608 寄存器读写与初始化

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);
	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);
	ret = spi_sync(spi, &m); /* 同步发送 */
	if(ret) {
		goto out2;
	}

out2:
	kfree(txdata); /* 释放内存 */
out1:
	kfree(t); /* 释放内存 */
	return ret;
}

/*
* @description : 读取 icm20608 指定寄存器值,读取一个寄存器
* @param – dev : icm20608 设备
I.MX6U 嵌入式 Linux 驱动开发指南
* @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]);
}

/*
* 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); 
	icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18); 
	icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04); 
	icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04);
	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); 
}

3.5、字符设备驱动框架

/*
* @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,
};

APP

#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>

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;
}



  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入式学习者。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值