0
点赞
收藏
分享

微信扫一扫

Linux驱动开发|SPI驱动

Java架构领域 2022-01-18 阅读 200

SPI驱动

SPI 是很常用的一个串行通信接口,用于连接各种外设、传感器等器件,在Linux裸机开发|SPI实验一文中已经对 SPI 接口做了详细的介绍。这里主要介绍 Linux 下的 SPI 驱动框架,并按照该框架去编写 SPI 设备驱动

一、SPI 驱动框架

SPI 驱动框架和 I2C 很类似,都分为主机控制器驱动和设备驱动,主机控制器也就是 SOC 的 SPI 控制器接口,和 I2C 适配器驱动一样, SPI 主机驱动一般都是由 SOC 厂商编写好的

1.1 SPI 主机驱动

SPI主机驱动就是 SOC的 SPI控制器驱动,内核使用 spi_master 表示 SPI主机驱动, spi_master是个结构体,定义在 include/linux/spi/spi.h文件中

struct spi_master { 
	struct device dev; 
	struct list_head list; 
	...... 
	s16 bus_num; 
	u16 num_chipselect; 
	u16 dma_alignment; 
	u16 mode_bits; 
	u32 bits_per_word_mask; 
	...... 
	u32 min_speed_hz; 
	u32 max_speed_hz; 
	u16 flags; 
	......
	spinlock_t bus_lock_spinlock; 
	struct mutex bus_lock_mutex; 
	bool bus_lock_flag; 
	...... 
	int (*setup)(struct spi_device *spi); 
	...... 
	/* 控制器数据传输函数 */
	int (*transfer)(struct spi_device *spi, struct spi_message *mesg); 
	...... 
	/* 用于SPI数据发送,发送的数据会打包成spi_message,以队列方式发送出去 */
	int (*transfer_one_message)(struct spi_master *master, struct spi_message *mesg); 
	...... 
};
  • spi_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释放 ***************************/
void spi_master_put(struct spi_master *master)
//master:要释放的 spi_master
  • spi_master的注册与注销: spi_master初始化完成后就需要将其注册到内核
/************************* spi_master注册 ***************************/
int spi_register_master(struct spi_master *master)
//master:要注册的 spi_master
//返回值:0,成功;负值,失败
/************************* spi_master注销 ***************************/
void spi_unregister_master(struct spi_master *master)
//master:要注销的 spi_master
1.2 SPI 设备驱动

Linux 内核使用 spi_driver 结构体来表示 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 注册函数
int spi_register_driver(struct spi_driver *sdrv)
//sdrv:要注册的 spi_driver
//返回值:0,注册成功;赋值,注册失败
  • 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.3 SPI 设备和驱动匹配过程

SPI 设备和驱动的匹配过程是由 SPI 总线来完成的,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);

	/* 用于完成设备树设备和驱动匹配 */
	if (of_driver_match_device(dev, drv))
		return 1;
	/* 用于ACPI形式的匹配 */
	if (acpi_driver_match_device(dev, drv))
		return 1;
	/* 用于传统无设备树设备和驱动匹配 */
	if (sdrv->id_table)
		return !!spi_match_id(sdrv->id_table, spi);
	/* 比较modalias成员变量和name成员变量是否相等 */
	return strcmp(spi->modalias, drv->name) == 0;
}
1.4 SPI 设备数据收发处理

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

struct spi_transfer {
	const void *tx_buf;	//保存着要发送的数据
	void *rx_buf;		//用于保存接收到的数据
	unsigned len;		//要进行传输的数据长度
	
	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;	

	struct list_head queue;
	void *state;
};
void spi_message_init(struct spi_message *m)
//m:要初始化的 spi_message
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
//t:要添加到队列中的 spi_transfer
//m:spi_transfer 要加入的 spi_message
/***同步传输会阻塞的等待 SPI 数据传输完成***/
int spi_sync(struct spi_device *spi, struct spi_message *message)
//spi:要进行数据传输的 spi_device
//message:要传输的 spi_message
/***异步传输不会阻塞等待,需设置spi_message中的
complete回调函数,当异步传输完成后此函数就会被调用***/
int spi_async(struct spi_device *spi, struct spi_message *message)
//spi:要进行数据传输的 spi_device
//message:要传输的 spi_message

综上所述, SPI 数据传输示例代码如下:

/********** SPI 多字节发送 **********/
static int spi_send(struct spi_device *spi, u8 *buf, int len){
	int ret;
	struct spi_message m;
	struct spi_transfer t = {	  //1. 定义一个spi_transfer结构体变量,并设置成员变量
		.tx_buf = buf,
		.len = len,
	};
	spi_message_init(&m); 		  //2. 初始化 spi_message
	spi_message_add_tail(t, &m);  //3. 将 spi_transfer 添加到 spi_message 队列
	ret = spi_sync(spi, &m); 	  //4. 同步传输
	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 = {	  //1. 定义一个spi_transfer结构体变量,并设置成员变量
		.rx_buf = buf,
		.len = len,
	};
	spi_message_init(&m); 		  //2. 初始化 spi_message
	spi_message_add_tail(t, &m);  //3. 将 spi_transfer 添加到 spi_message 队列
	ret = spi_sync(spi, &m);	  //4. 同步传输
	return ret;
}

二、SPI 驱动实验

本章实验介绍了如何驱动 I.MX6U-ALPHA开发板上的 ICM-20608这个 SPI接口的六轴传感器,并在应用程序中读取 ICM-20608的原始传感器数据。ICM-20608六轴传感器的介绍请参考Linux裸机开发|SPI实验一文

2.1 修改设备树
  • 修改或添加pinctrl节点:在 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是片选信号,因为要自己控制片选信号,所以将其复用为普通的GPIO
  • 添加子节点:在 ecspi3节点下追加 icm20608子节点
&ecspi3 { 
	fsl,spi-num-chipselects = <1>; 	//设置当前片选数量为 1
	/* 若使用“cs-gpios”属性的话 SPI 主机驱动就会控制片选引脚 */
	cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>; 	/* cant't use cs-gpios! */ 
	pinctrl-names = "default"; 
	pinctrl-0 = <&pinctrl_ecspi3>; 
	status = "okay"; 
 
	spidev: icm20608@0 { 
		compatible = "alientek,icm20608"; 
		spi-max-frequency = <8000000>; 	//设置SPI最大时钟频率为 8MHz
		reg = <0>; 
	}; 
};
  • 检查PIN是否冲突:检查pinctrl中设置以及设备节点中指定的引脚有没有被别的外设使用

保存修改后,在kernel主目录下使用“make dtbs”命令编译设备树,使用新的设备树文件启动Llinux系统

2.2 驱动程序编写
  • 新建 icm20608reg.h 寄存器头文件
#ifndef ICM20608_H
#define ICM20608_H

#define ICM20608G_ID			0XAF	/* ID值 */
#define ICM20608D_ID			0XAE	/* ID值 */
/* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */
#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 驱动文件
#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;
/* 从icm20608读取多个寄存器数据 */
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;
}
/* 向icm20608多个寄存器写入数据 */
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;
}
/* 读取icm20608指定寄存器值,读取一个寄存器 */
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg){
	u8 data = 0;
	icm20608_read_regs(dev, reg, &data, 1);
	return data;
}
/* 向icm20608指定寄存器写入指定的值 */	
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value){
	u8 buf = value;
	icm20608_write_regs(dev, reg, &buf, 1);
}
/* 读取ICM20608的数据,读取原始数据 */
void icm20608_readdata(struct icm20608_dev *dev){
	unsigned char data[14];
	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]);
}
/* 打开设备 */
static int icm20608_open(struct inode *inode, struct file *filp){
	filp->private_data = &icm20608dev; /* 设置私有数据 */
	return 0;
}
/* 从设备读取数据 */
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;
}
/* 关闭/释放设备 */
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内部寄存器初始化函数 */
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						*/
}
/* spi驱动的probe函数,当驱动与设备匹配以后此函数就会执行 */	
static int icm20608_probe(struct spi_device *spi){
	int ret = 0;

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

	/* 获取设备树中cs片选信号 */
	icm20608dev.nd = of_find_node_by_path("/soc/aips-bus@02000000/spba-bus@02000000/ecspi@02010000");
	if(icm20608dev.nd == NULL) {
		printk("ecspi3 node not find!\r\n");
		return -EINVAL;
	} 

	/* 2、 获取设备树中的gpio属性,得到BEEP所使用的BEEP编号 */
	icm20608dev.cs_gpio = of_get_named_gpio(icm20608dev.nd, "cs-gpio", 0);
	if(icm20608dev.cs_gpio < 0) {
		printk("can't get cs-gpio");
		return -EINVAL;
	}

	/* 3、设置GPIO1_IO20为输出,并且输出高电平 */
	ret = gpio_direction_output(icm20608dev.cs_gpio, 1);
	if(ret < 0) {
		printk("can't set gpio!\r\n");
	}

	/*初始化spi_device */
	spi->mode = SPI_MODE_0;	/*MODE0,CPOL=0,CPHA=0*/
	spi_setup(spi);
	icm20608dev.private_data = spi; /* 设置私有数据 */

	/* 初始化ICM20608内部寄存器 */
	icm20608_reginit();		
	return 0;
}
/* spi驱动的remove函数,移除spi驱动的时候此函数会执行 */
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,
};		   
/* 驱动入口函数 */
static int __init icm20608_init(void){
	return spi_register_driver(&icm20608_driver);
}
/* 驱动出口函数 */
static void __exit icm20608_exit(void){
	spi_unregister_driver(&icm20608_driver);
}

module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
2.3 测试程序编写

新建icm20608App.c测试文件

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;
}
2.4 运行测试
  • 修改Makefile编译目标变量
obj-m := icm20608.o
  • 使用“make -j32”编译出驱动模块文件
make -j32
  • 使用“arm-linux-gnueabihf-gcc -march-armv7-a -mfpu-neon -mfloat=hard”命令编译测试APP
arm-linux-gnueabihf-gcc -march=armv7-a -mfpu=neon -mfloat=hard icm20608App.c -o  icm20608App
  • 将驱动文件和APP可执行文件拷贝至“rootfs/lib/modules/4.1.15”中

  • 使用“modprobe”命令加载驱动,加载成功后总线就会进行匹配

depmod  #第一次加载驱动时,需使用“depmod”命令
modprobe icm20608.ko
  • 加载成功后使用以下命令来测试,APP 不断的从 icm20608 中读取数据,并输出到终端上
./icm20608App  /dev/icm20608

在这里插入图片描述

举报

相关推荐

0 条评论