Linux驱动-Regmap子系统

文章目录

  • [1 Regmap简介](#1 Regmap简介)
    • [1.1 regmap优缺点](#1.1 regmap优缺点)
    • [1.2 regmap使用场景](#1.2 regmap使用场景)
  • [2 Regmap驱动框架](#2 Regmap驱动框架)
    • [2.1 regmap框架结构](#2.1 regmap框架结构)
    • [2.2 regmap结构体](#2.2 regmap结构体)
    • [2.3 regmap_config结构体](#2.3 regmap_config结构体)
  • [3 Regmap API函数](#3 Regmap API函数)
    • [3.1 regmap申请和初始化](#3.1 regmap申请和初始化)
    • [3.2 regmap设备访问API函数](#3.2 regmap设备访问API函数)
    • [3.3 regmap其他API函数](#3.3 regmap其他API函数)
  • [4 Regmap应用实例](#4 Regmap应用实例)
    • [4.1 修改设备树](#4.1 修改设备树)
    • [4.2 驱动程序](#4.2 驱动程序)
    • [4.3 用户侧验证程序](#4.3 用户侧验证程序)

1 Regmap简介

Linux下大部分设备的驱动开发都是操作其内部寄存器,比如I2C/SPI设备的本质都是一样的,通过I2C/SPI接口读写芯片内部寄存器。芯片内部寄存器也是同样的道理,比如STM32MP157的PWM、TIM等外设初始化,最终都是要落到寄存器的设置上。Linux下使用i2c_transfer来读写I2C设备中的寄存器,SPI接口的话使用spi_write/spi_read等。I2C/SPI芯片又非常的多,因此Linux内核里面就会充斥了大量的i2c_transfer这类的冗余代码,再者,代码的复用性也会降低。比如icm20608这个芯片既支持I2C接口,也支持SPI接口。假设在产品设计阶段一开始将icm20608设计为SPI接口,但是后面发现SPI接口不够用,或者SOC的引脚不够用,我们需要将icm20608改为I2C接口。这个时候icm20608的驱动就要大改,需要将SPI接口函数换为I2C的,工作量比较大。

基于代码复用的原则,Linux内核引入了regmap模型,regmap将寄存器访问的共同逻辑抽象出来,驱动开发人员不需要再去纠结使用SPI或者I2C接口API函数,统一使用regmap API函数。这样的好处就是统一使用regmap,降低了代码冗余,提高了驱动的可以移植性。regmap模型的重点在于通过regmap模型提供的统一接口函数来访问器件的寄存器,SOC内部的寄存器也可以使用regmap接口函数来访问。

1.1 regmap优缺点

regmap是Linux内核为了减少慢速I/O在驱动上的冗余开销,提供了一种通用的接口来操作硬件寄存器。另外,regmap在驱动和硬件之间添加了cache,降低了低速I/O的操作次数,提高了访问效率,缺点是实时性会降低。

1.2 regmap使用场景

regmap的使用场景:

  1. 硬件寄存器操作,比如选用通过I2C/SPI接口来读写设备的内部寄存器,或者需要读写SOC内部的硬件寄存器;
  2. 提高代码复用性和驱动一致性,简化驱动开发过程;
  3. 减少底层I/O操作次数,提高访问效率。

2 Regmap驱动框架

2.1 regmap框架结构

regmap驱动框架如下所示:

regmap框架分为三层:

  1. 底层物理总线:regmap就是对不同的物理总线进行封装,目前(5.4.31内核)regmap支持的物理总线有i2c、i3c、spi、mmio、sccb、sdw、slimbus、irq、spmi和w1;
  2. regmap核心层,用于实现regmap,不用关心具体实现;
  3. regmapAPI抽象层,regmap向驱动编写人员提供的API接口,驱动编写人员使用这些API接口来操作具体的芯片设备。

2.2 regmap结构体

Linux内核将regmap框架抽象为regmap结构体,这个结构体定义在文件drivers/base/regmap/internal.h中:

c 复制代码
struct regmap {
	union {
		struct mutex mutex;
		struct {
			spinlock_t spinlock;
			unsigned long spinlock_flags;
		};
	};
	regmap_lock lock;
	regmap_unlock unlock;
	void *lock_arg; /* This is passed to lock/unlock functions */
	gfp_t alloc_flags;

	struct device *dev; /* Device we do I/O on */
	void *work_buf;     /* Scratch buffer used to format I/O */
	struct regmap_format format;  /* Buffer format */
	const struct regmap_bus *bus;
	void *bus_context;
	const char *name;

	bool async;
	spinlock_t async_lock;
	wait_queue_head_t async_waitq;
	struct list_head async_list;
	struct list_head async_free;
	int async_ret;

#ifdef CONFIG_DEBUG_FS
	bool debugfs_disable;
	struct dentry *debugfs;
	const char *debugfs_name;

	unsigned int debugfs_reg_len;
	unsigned int debugfs_val_len;
	unsigned int debugfs_tot_len;

	struct list_head debugfs_off_cache;
	struct mutex cache_lock;
#endif

	unsigned int max_register;
	bool (*writeable_reg)(struct device *dev, unsigned int reg);
	bool (*readable_reg)(struct device *dev, unsigned int reg);
	bool (*volatile_reg)(struct device *dev, unsigned int reg);
	bool (*precious_reg)(struct device *dev, unsigned int reg);
	bool (*writeable_noinc_reg)(struct device *dev, unsigned int reg);
	bool (*readable_noinc_reg)(struct device *dev, unsigned int reg);
	const struct regmap_access_table *wr_table;
	const struct regmap_access_table *rd_table;
	const struct regmap_access_table *volatile_table;
	const struct regmap_access_table *precious_table;
	const struct regmap_access_table *wr_noinc_table;
	const struct regmap_access_table *rd_noinc_table;

	int (*reg_read)(void *context, unsigned int reg, unsigned int *val);
	int (*reg_write)(void *context, unsigned int reg, unsigned int val);
	int (*reg_update_bits)(void *context, unsigned int reg,
			       unsigned int mask, unsigned int val);

	bool defer_caching;

	unsigned long read_flag_mask;
	unsigned long write_flag_mask;

	/* number of bits to (left) shift the reg value when formatting*/
	int reg_shift;
	int reg_stride;
	int reg_stride_order;

	/* regcache specific members */
	const struct regcache_ops *cache_ops;
	enum regcache_type cache_type;

	/* number of bytes in reg_defaults_raw */
	unsigned int cache_size_raw;
	/* number of bytes per word in reg_defaults_raw */
	unsigned int cache_word_size;
	/* number of entries in reg_defaults */
	unsigned int num_reg_defaults;
	/* number of entries in reg_defaults_raw */
	unsigned int num_reg_defaults_raw;

	/* if set, only the cache is modified not the HW */
	bool cache_only;
	/* if set, only the HW is modified not the cache */
	bool cache_bypass;
	/* if set, remember to free reg_defaults_raw */
	bool cache_free;

	struct reg_default *reg_defaults;
	const void *reg_defaults_raw;
	void *cache;
	/* if set, the cache contains newer data than the HW */
	bool cache_dirty;
	/* if set, the HW registers are known to match map->reg_defaults */
	bool no_sync_defaults;

	struct reg_sequence *patch;
	int patch_regs;

	/* if set, converts bulk read to single read */
	bool use_single_read;
	/* if set, converts bulk write to single write */
	bool use_single_write;
	/* if set, the device supports multi write mode */
	bool can_multi_write;

	/* if set, raw reads/writes are limited to this size */
	size_t max_raw_read;
	size_t max_raw_write;

	struct rb_root range_tree;
	void *selector_work_buf;	/* Scratch buffer used for selector */

	struct hwspinlock *hwlock;
};

其中42行-53行有很多的函数以及table,这些需要驱动编写人员根据实际情况选择性的初始化,regmap的初始化通过结构体regmap_config来完成。

2.3 regmap_config结构体

结构体定义在drivers/base/regmap/internal.h中:结构体内容如下:

c 复制代码
struct regmap_config {
	const char *name;

	int reg_bits;
	int reg_stride;
	int pad_bits;
	int val_bits;

	bool (*writeable_reg)(struct device *dev, unsigned int reg);
	bool (*readable_reg)(struct device *dev, unsigned int reg);
	bool (*volatile_reg)(struct device *dev, unsigned int reg);
	bool (*precious_reg)(struct device *dev, unsigned int reg);
	bool (*writeable_noinc_reg)(struct device *dev, unsigned int reg);
	bool (*readable_noinc_reg)(struct device *dev, unsigned int reg);

	bool disable_locking;
	regmap_lock lock;
	regmap_unlock unlock;
	void *lock_arg;

	int (*reg_read)(void *context, unsigned int reg, unsigned int *val);
	int (*reg_write)(void *context, unsigned int reg, unsigned int val);

	bool fast_io;

	unsigned int max_register;
	const struct regmap_access_table *wr_table;
	const struct regmap_access_table *rd_table;
	const struct regmap_access_table *volatile_table;
	const struct regmap_access_table *precious_table;
	const struct regmap_access_table *wr_noinc_table;
	const struct regmap_access_table *rd_noinc_table;
	const struct reg_default *reg_defaults;
	unsigned int num_reg_defaults;
	enum regcache_type cache_type;
	const void *reg_defaults_raw;
	unsigned int num_reg_defaults_raw;

	unsigned long read_flag_mask;
	unsigned long write_flag_mask;
	bool zero_flag_mask;

	bool use_single_read;
	bool use_single_write;
	bool can_multi_write;

	enum regmap_endian reg_format_endian;
	enum regmap_endian val_format_endian;

	const struct regmap_range_cfg *ranges;
	unsigned int num_ranges;

	bool use_hwlock;
	unsigned int hwlock_id;
	unsigned int hwlock_mode;
};

regmap_config结构体分析:

  1. name:名字;
  2. reg_bits:寄存器地址位数,必填字段;
  3. reg_stride:寄存器地址步长;
  4. pad_bits:寄存器和值之间的填充位数;
  5. val_bits:寄存器值位数,必填字段;
  6. writeable_reg:可选的可写回调函数,寄存器可写的话此回调函数就会被调用并返回true;
  7. readable_reg:可选的可读回调函数,寄存器可读的话此回调函数就会被调用,并返回true;
  8. volatile_reg:可选的回调函数,当寄存器值不能缓存的时候此回调函数就会被调用,并返回true;
  9. precious_reg:当寄存器值不能被读出来的时候此回调函数会被调用,比如很多中断状态寄存器读清零,读这些寄存器就可以清除中断标志位,但是并没有读出这些寄存器内部的值;
  10. reg_read:可选的读操作回调函数,所有读寄存器的操作此回调函数就会执行;
  11. reg_write:可选的写操作回调函数,所有写寄存器的操作此回调函数就会执行;
  12. fast_io:快速I/O,使用spinlock替代mutex来提升锁性能;
  13. max_register:有效的最大寄存器地址,可选;
  14. wr_table:可写的地址范围,为regmap_access_table结构体类型;后面的rd_table、volatile_table、precious_table、wr_noinc_table和rd_noinc_table同理;
  15. reg_defaults:寄存器模式值,为reg_default结构体类型,此结构体有两个成员变量:reg和defreg是寄存器地址,def是默认值;
  16. num_reg_defaults:默认寄存器表中的元素个数;
  17. read_flag_mask:读标志掩码;
  18. write_flag_mask:写标志掩码。

3 Regmap API函数

3.1 regmap申请和初始化

regmap支持多种物理总线,比如I2C和SPI,需要根据所使用的接口来选择合适的regmap初始化函数。Linux内核提供了针对不同接口的regmap初始化函数,可自行查阅内核源码。在退出驱动的时候需要释放掉申请到的regmap,不管是什么接口,全部使用regmap_exit函数来释放regmap。一般在probe函数中初始化regmap_config,然后申请并初始化regmap。

c 复制代码
///
/// \brief regmap_init_spi  SPI接口初始化函数
/// \param spi              需要使用 regmap 的 spi_device
/// \param config           regmap_config 结构体,需要程序编写人员初始化一个regmap_config实例,然后将其地址赋值给此参数
/// \return                 申请到的并初始化完成 regmap
///
struct regmap * regmap_init_spi(struct spi_device *spi, const struct regmap_config *config);

///
/// \brief regmap_init_i2c  IIC接口初始化函数
/// \param i2c              需要使用 regmap 的 i2c_client
/// \param config           regmap_config 结构体,需要程序编写人员初始化一个regmap_config实例,然后将其地址赋值给此参数
/// \return                 申请到的并初始化完成 regmap
///
struct regmap * regmap_init_i2c(struct i2c_client *i2c, const struct regmap_config *config);

///
/// \brief regmap_exit  释放 regmap
/// \param map          需要释放的 regmap
///
void regmap_exit(struct regmap *map);

3.2 regmap设备访问API函数

不管是I2C还是SPI等接口,还是SOC内部的寄存器,对于寄存器的操作就两种:读和写。regmap提供了最核心的两个读写操作:regmap_read和regmap_write。这两个函数分别用来读/写寄存器。函数原型如下所示:

c 复制代码
///
/// \brief regmap_read  读取寄存器的值
/// \param map          要操作的regmap
/// \param reg          要读的寄存器
/// \param val          读到的寄存器值
/// \return             0,读取成功;其他值,读取失败
///
int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val);

///
/// \brief regmap_write 向寄存器中写入值
/// \param map          要操作的regmap
/// \param reg          要写的寄存器
/// \param val          要写的寄存器值
/// \return             0,写成功;其他值,写失败
///
int regmap_write(struct regmap *map, unsigned int reg, unsigned int val);

3.3 regmap其他API函数

在regmap_read和regmap_write的基础上还衍生出了其他一些regmap的API函数:

c 复制代码
///
/// \brief regmap_update_bits   修改寄存器指定的bit
/// \param map                  要操作的 regmap
/// \param reg                  要操作的寄存器
/// \param mask                 掩码,需要更新的位必须在掩码中设置为1
/// \param val                  需要更新的值
/// \return                     0,写成功;其他值,写失败
///
int regmap_update_bits (struct regmap *map, unsigned int reg, unsigned int mask, unsigned int val);

比如要将寄存器的bit1和bit2置1,那么mask应该设置为0X00000011,此时val的bit1和bit2应该设置为1,也就是0Xxxxxxx11。如果要清除寄存器的bit4和bit7,那么mask应该设置为0X10010000val的bit4和bit7设置为0,也就是0X0xx0xxxx。

c 复制代码
///
/// \brief regmap_bulk_read 读取多个寄存器的值
/// \param map              要操作的regmap
/// \param reg              要读取的第一个寄存器
/// \param val              读取到的数据缓冲区
/// \param val_count        要读取的寄存器数量
/// \return                 0,读成功;其他值,读失败
///
int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val, size_t val_count);

///
/// \brief regmap_bulk_write    往多个寄存器中写值
/// \param map                  要操作的regmap
/// \param reg                  要写的第一个寄存器
/// \param val                  要写的寄存器数据缓冲区
/// \param val_count            要写的寄存器数量
/// \return                     0,写成功;其他值,写失败
///
int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val, size_t val_count);

4 Regmap应用实例

4.1 修改设备树

基于STM32MP1的icm20608来验证regmap子系统,因此设备树不需要修改。

4.2 驱动程序

c 复制代码
#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
c 复制代码
#include <linux/spi/spi.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include "icm20608reg.h"
#include <linux/gpio.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/regmap.h>

#define ICM20608_CNT	1
#define ICM20608_NAME	"icm20608"

struct icm20608_dev {
	struct spi_device *spi;		/* spi设备 */
	dev_t devid;				/* 设备号 	 */
	struct cdev cdev;			/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;		/* 设备 	 */
	struct device_node	*nd; 	/* 设备节点 */
	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;		/* 温度原始值 			*/
	struct regmap *regmap;				/* regmap */
	struct regmap_config regmap_config;	
};

/*
 * @description	: 读取icm20608指定寄存器值,读取一个寄存器
 * @param - dev:  icm20608设备
 * @param - reg:  要读取的寄存器
 * @return 	  :   读取到的寄存器值
 */
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{
	u8 ret;
	unsigned int data;

	ret = regmap_read(dev->regmap, reg, &data);
	return (u8)data;
}

/*
 * @description	: 向icm20608指定寄存器写入指定的值,写一个寄存器
 * @param - dev:  icm20608设备
 * @param - reg:  要写的寄存器
 * @param - data: 要写入的值
 * @return   :    无
 */	

static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{
	regmap_write(dev->regmap,  reg, value);
}

/*
 * @description	: 读取ICM20608的数据,读取原始数据,包括三轴陀螺仪、
 * 				: 三轴加速度计和内部温度。
 * @param - dev	: ICM20608设备
 * @return 		: 无。
 */
void icm20608_readdata(struct icm20608_dev *dev)
{
	u8 ret;
	unsigned char data[14];

	ret = regmap_bulk_read(dev->regmap, 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)
{
	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 cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;
	struct icm20608_dev *dev = container_of(cdev, struct icm20608_dev, cdev);
            
	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 - spi : 要操作的设备
 * @return 	: 无
 */
void icm20608_reginit(struct icm20608_dev *dev)
{
	u8 value = 0;
	
	icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x80);
	mdelay(50);
	icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x01);
	mdelay(50);

	value = icm20608_read_onereg(dev, ICM20_WHO_AM_I);
	printk("ICM20608 ID = %#X\r\n", value);	

	icm20608_write_onereg(dev, ICM20_SMPLRT_DIV, 0x00); 	/* 输出速率是内部采样率		*/
	icm20608_write_onereg(dev, ICM20_GYRO_CONFIG, 0x18); 	/* 陀螺仪±2000dps量程 		*/
	icm20608_write_onereg(dev, ICM20_ACCEL_CONFIG, 0x18); 	/* 加速度计±16G量程 		*/
	icm20608_write_onereg(dev, ICM20_CONFIG, 0x04); 		/* 陀螺仪低通滤波BW=20Hz 	*/
	icm20608_write_onereg(dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW=21.2Hz 	*/
	icm20608_write_onereg(dev, ICM20_PWR_MGMT_2, 0x00); 	/* 打开加速度计和陀螺仪所有轴 	*/
	icm20608_write_onereg(dev, ICM20_LP_MODE_CFG, 0x00); 	/* 关闭低功耗 				*/
	icm20608_write_onereg(dev, ICM20_FIFO_EN, 0x00);		/* 关闭FIFO					*/
}

/*
  * @description     : spi驱动的probe函数,当驱动与
  *                    设备匹配以后此函数就会执行
  * @param - spi  	: spi设备
  * 
  */	
static int icm20608_probe(struct spi_device *spi)
{
	int ret;
	struct icm20608_dev *icm20608dev;
	
	/* 分配icm20608dev对象的空间 */
	icm20608dev = devm_kzalloc(&spi->dev, sizeof(*icm20608dev), GFP_KERNEL);
	if(!icm20608dev)
		return -ENOMEM;

	/* 初始化regmap_config设置 */
	icm20608dev->regmap_config.reg_bits = 8;			/* 寄存器长度8bit */
	icm20608dev->regmap_config.val_bits = 8;			/* 值长度8bit */
	icm20608dev->regmap_config.read_flag_mask = 0x80;  /* 读掩码设置为0X80,ICM20608使用SPI接口读的时候寄存器最高位应该为1 */

	/* 初始化IIC接口的regmap */
	icm20608dev->regmap = regmap_init_spi(spi, &icm20608dev->regmap_config);
	if (IS_ERR(icm20608dev->regmap)) {
		return  PTR_ERR(icm20608dev->regmap);
	}	
		
	/* 注册字符设备驱动 */
	/* 1、创建设备号 */
	ret = alloc_chrdev_region(&icm20608dev->devid, 0, ICM20608_CNT, ICM20608_NAME);
	if(ret < 0) {
		pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", ICM20608_NAME, ret);
        goto del_regmap;
	}

	/* 2、初始化cdev */
	icm20608dev->cdev.owner = THIS_MODULE;
	cdev_init(&icm20608dev->cdev, &icm20608_ops);
	
	/* 3、添加一个cdev */
	ret = cdev_add(&icm20608dev->cdev, icm20608dev->devid, ICM20608_CNT);
	if(ret < 0) {
		goto del_unregister;
	}
	
	/* 4、创建类 */
	icm20608dev->class = class_create(THIS_MODULE, ICM20608_NAME);
	if (IS_ERR(icm20608dev->class)) {
		goto del_cdev;
	}

	/* 5、创建设备 */
	icm20608dev->device = device_create(icm20608dev->class, NULL, icm20608dev->devid, NULL, ICM20608_NAME);
	if (IS_ERR(icm20608dev->device)) {
		goto destroy_class;
	}
	icm20608dev->spi = spi;
	
	/*初始化spi_device */
	spi->mode = SPI_MODE_0;	/*MODE0,CPOL=0,CPHA=0*/
	spi_setup(spi);
	
	/* 初始化ICM20608内部寄存器 */
	icm20608_reginit(icm20608dev);	
	/* 保存icm20608dev结构体 */
	spi_set_drvdata(spi, icm20608dev);

	return 0;
destroy_class:
	device_destroy(icm20608dev->class, icm20608dev->devid);
del_cdev:
	cdev_del(&icm20608dev->cdev);
del_unregister:
	unregister_chrdev_region(icm20608dev->devid, ICM20608_CNT);
del_regmap:
	regmap_exit(icm20608dev->regmap);
	return -EIO;
}

/*
 * @description     : spi驱动的remove函数,移除spi驱动的时候此函数会执行
 * @param - spi 	: spi设备
 * @return          : 0,成功;其他负值,失败
 */
static int icm20608_remove(struct spi_device *spi)
{
	struct icm20608_dev *icm20608dev = spi_get_drvdata(spi);

	/* 注销字符设备驱动 */
	/* 1、删除cdev */
	cdev_del(&icm20608dev->cdev);
	/* 2、注销设备号 */
	unregister_chrdev_region(icm20608dev->devid, ICM20608_CNT); 
	/* 3、注销设备 */
	device_destroy(icm20608dev->class, icm20608dev->devid);
	/* 4、注销类 */
	class_destroy(icm20608dev->class); 
	/* 5、删除regmap */
	regmap_exit(icm20608dev->regmap);

	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");
MODULE_AUTHOR("da");
MODULE_INFO(intree, "Y");

4.3 用户侧验证程序

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>
/*
 * @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;
}
相关推荐
DA022119 小时前
Linux驱动-I2C总线驱动
linux·c语言·linux驱动
DA02214 天前
Linux设备树,DTS、DTB浅析
linux·c语言·linux驱动
泽平59018 天前
Linux Pinctrl子系统
linux·运维·服务器·驱动开发·单片机·linux驱动
ttkwzyttk2 个月前
Linux内核中模块定义宏机制解析
linux驱动
BlueBirdssh2 个月前
时钟相位差
linux驱动
蓝天居士3 个月前
Linux设备驱动之gpio-keys(2)
linux驱动·gpio-keys
ton_tom4 个月前
设备驱动程序编程-Linux2.6.10-kdb安装
linux驱动
淮北也生橘125 个月前
Linux驱动知识点:容器嵌入机制(Container Embedding)
linux驱动·嵌入式linux
yan1236810 个月前
Linux 驱动之设备树
android·linux·驱动开发·linux驱动