Regmap子系统(Linux驱动开发篇)

1、使用的原因

  • Linux下大部分设备的驱动开发都是操作其内部寄存器,比如 I2C/SPI 设备的本质都是一样的,通过I2C/SPI接口读写芯片内部寄存器。
  • 在前面学习 I2C 和SPI 驱动的时候,针对 I2C 和 SPI 设备寄存器的操作都是通过相关的 API函数进行操作的。这样 Linux 内核中就会充斥着大量的重复、冗余代码,但是这些本质上都是对寄存器的操作,所以为了方便内核开发人员统一访问 I2C/SPI 设备的时候,Linux下使用i2c_transfer来读写I2C设备中的寄存器。SPI接口的话使用 spi_write/spi_read读写SPI设备中的寄存器。因为驱动属于内核的一部分,导致 Linux 内核里面就会充斥了大量的 i2c_transfer 这类的冗余代码,而且由于iic驱动的和spi各自写各自的驱动文件,导致代码的复用性也会降低。
  • 为此引入了 Regmap子系统

2、Regmap简介

  • 基于代码复用的原则,Linux内核引入了regmap 模型.

  • regmap将寄存器访问的共同逻辑抽象出来,驱动开发人员不需要再去纠结使用SPI或者 I2C接口 API函数,统一使用 regmap API函数,优点就是上面对应的缺点

  • regmap模型的重点在于:
    @通过 regmap 模型提供的统一接口函数来访问器件的寄存器,SOC 内部的寄存器也可以使用regmap接口函数来访问

    @regmap 是Linux 内核为了减少慢速 I/O 在驱动上的冗余开销,提供了一种通用的接口来操作硬件寄存器。

    @regmap在驱动和硬件之间添加了 cache,降低了低速 I/O 的操作次数,提高了访问效率,缺点是实时性会降低

  • 使用regmap的情况
    @)硬件寄存器操作,比如选用通过 I2C/SPI 接口来读写设备的内部寄存器,或者需要读写 SOC 内部的硬件寄存器

    @)提高代码复用性和驱动一致性,简化驱动开发过程。

    @)减少底层I/O 操作次数,提高访问效率

3、Regmap驱动框架
在这里插入图片描述

/*
@	Linux内核将regmap框架抽象为regmap结构体
@	定义在文件drivers/base/regmap/internal.h 中
@	regmap 结构体 
*/
  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 */
        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; 
... 
    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); 
    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; 
 
    int (*reg_read)(void *context, unsigned int reg,  unsigned int *val); 
     int (*reg_write)(void *context, unsigned int reg, 
                      unsigned int val); 
... 
     struct rb_root range_tree; 
     void *selector_work_buf;  /* Scratch buffer used for selector */
 };   
 
/***************************************************************************************
*上面介绍了结构体,我们要实例化一个结构体也就是实例化regmap框架,实例化就是一系列的初始化
*****************************************************************************************/
/*
@	regmap_config 结构体就是用来初始化 regmap结构体即时初始化regmap框架
@	定义在include/linux/regmap.h文件中
@	regmap_config 结构体
*/
 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); /*当寄存器值不能被读出来的时候此回调函数会被调用*/
     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; /*快速 I/O,使用spinlock替代mutex来提升锁性能*/
  
     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 reg_default *reg_defaults; /*寄存器模式值,为reg_default结构体类型*/
     unsigned int num_reg_defaults; /*默认寄存器表中的元素个数*/
     enum regcache_type cache_type; 
     const void *reg_defaults_raw; 
     unsigned int num_reg_defaults_raw; 
  
     u8 read_flag_mask; /*读标志掩码。*/
     u8 write_flag_mask; /*写标志掩码*/
  
     bool use_single_rw; 
     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; 
 };   


/*********************************************************************
*上面我们已经说了我们主要就是针对regmapAPI抽象层来下功夫==
*我们一般会在probe 函数中初始化regmap_config,然后申请并初始化 regmap
**********************************************************************/
/*
@	Linux内核提供了针对不同接口的regmap初始化函数,一下举例2个
@	SPI接口初始化函数为regmap_init_spi
@	I2C 接口的regmap初始化函数为regmap_init_i2c
@
@	spi:需要使用regmap的spi_device
@	i2c:需要使用regmap的i2c_client
@	config:regmap_config结构体,需要程序编写人员初始化一个regmap_config实例,然后将其地址赋值给此参数
@   返回值:申请到的并进过初始化的 regmap
*/
struct regmap * regmap_init_spi(struct spi_device  *spi,  const struct regmap_config  *config) 

struct regmap * regmap_init_i2c(struct i2c_client  *i2c,  const struct regmap_config    *config) 

/*
@	退出驱动的时候需要释放掉申请到的 regmap,不管是什么接口,全部使用 regmap_exit这个函数来释放regmap
@	map:需要释放的 regmap   
@	返回值:无
*/
void regmap_exit(struct regmap *map) 



/*******************************************************************
*对于寄存器的操作就两种:读和写。regmap提供了最核心的两个读写操作****
*********************************************************************/
/*
@	读寄存器操作
@	map:要操作的 regmap。
@	reg:要读的寄存器
@	val:读到的寄存器值
@	返回值:0,读取成功;其他值,读取失败
*/
int regmap_read(struct regmap    *map,   unsigned int    reg,  unsigned int      *val) 
/*
@	写寄存器操作
@	map:要操作的 regmap。
@	reg:要写的寄存器
@	val:要写的寄存器值
@	返回值:0,读取成功;其他值,写失败
*/
int regmap_write(struct regmap    *map,   unsigned int    reg,  unsigned int   *val) 
/*
@	读写的衍生API=用于读取多个寄存器的值
@	reg:要读取的第一个寄存器。
@	map:要操作的 regmap
@	val:读取到的数据缓冲区。 
@	val_count:要读取的寄存器数量。
@	返回值:0,写成功;其他值,读失败
*/
int regmap_bulk_read(struct regmap *map, unsigned int reg, void   *val, size_t     val_count) 
/*
@	读写的衍生API=用于写多个寄存器的值
@	reg:要写的第一个寄存器。
@	map:要操作的 regmap
@	val:要写的寄存器数据缓冲区。 
@	val_count:要写的寄存器数量。
@	返回值:0,写成功;其他值,写失败
*/
int regmap_bulk_write(struct regmap *map, unsigned int reg, void   *val, size_t     val_count) 

/*
@	读写的衍生API==修改寄存器指定的bit
@	map:要操作的 regmap
@	val:需要更新的位值。
@	返回值:0,写成功;其他值,写失败
*/
int regmap_update_bits (struct regmap    *map,   unsigned int   reg,  unsigned int    mask, unsigned int   val)

4、实战演练1
对象:
imx6ull开发板,传感器ICM20608,总线SPI

  • 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/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("ALIENTEK");
MODULE_INFO(intree, "Y");
  • 测试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>
/*
 * @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;
}

5、实战演练2(iic)

对象:
imx6ull开发板,ap3216c传感器、IIC总线

  • ap3216creg.h 设备信息
#ifndef AP3216C_H
#define AP3216C_H

#define AP3216C_ADDR    	0X1E	/* AP3216C器件地址  */

/* AP3316C寄存器 */
#define AP3216C_SYSTEMCONG	0x00	/* 配置寄存器       */
#define AP3216C_INTSTATUS	0X01	/* 中断状态寄存器   */
#define AP3216C_INTCLEAR	0X02	/* 中断清除寄存器   */
#define AP3216C_IRDATALOW	0x0A	/* IR数据低字节     */
#define AP3216C_IRDATAHIGH	0x0B	/* IR数据高字节     */
#define AP3216C_ALSDATALOW	0x0C	/* ALS数据低字节    */
#define AP3216C_ALSDATAHIGH	0X0D	/* ALS数据高字节    */
#define AP3216C_PSDATALOW	0X0E	/* PS数据低字节     */
#define AP3216C_PSDATAHIGH	0X0F	/* PS数据高字节     */

#endif


  • ap3216c.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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "ap3216creg.h"
#include <linux/regmap.h>

#define AP3216C_CNT	1
#define AP3216C_NAME	"ap3216c"

struct ap3216c_dev {
	struct i2c_client *client;	/* i2c 设备 */
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	struct device_node	*nd; /* 设备节点 */
	unsigned short ir, als, ps;		/* 三个光传感器数据 */
	struct regmap *regmap;				/* regmap */
	struct regmap_config regmap_config;	
};

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

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

/*
 * @description	: 向ap3216c指定寄存器写入指定的值,写一个寄存器
 * @param - dev:  ap3216c设备
 * @param - reg:  要写的寄存器
 * @param - data: 要写入的值
 * @return   :    无
 */
static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data)
{
	regmap_write(dev->regmap, reg, data);
}

/*
 * @description	: 读取AP3216C的数据,读取原始数据,包括ALS,PS和IR, 注意!
 *				: 如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms
 * @param - ir	: ir数据
 * @param - ps 	: ps数据
 * @param - ps 	: als数据 
 * @return 		: 无。
 */
void ap3216c_readdata(struct ap3216c_dev *dev)
{
	unsigned char i =0;
    unsigned char buf[6];
	
	/* 循环读取所有传感器数据 */
    for(i = 0; i < 6; i++) {
        buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);	
    }

    if(buf[0] & 0X80) 	/* IR_OF位为1,则数据无效 */
		dev->ir = 0;					
	else 				/* 读取IR传感器的数据   		*/
		dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03); 			
	
	dev->als = ((unsigned short)buf[3] << 8) | buf[2];	/* 读取ALS传感器的数据 			 */  
	
    if(buf[4] & 0x40)	/* IR_OF位为1,则数据无效 			*/
		dev->ps = 0;    													
	else 				/* 读取PS传感器的数据    */
		dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F); 
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int ap3216c_open(struct inode *inode, struct file *filp)
{
	/* 从file结构体获取cdev的指针,在根据cdev获取ap3216c_dev结构体的首地址 */
	struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;
	struct ap3216c_dev *ap3216cdev = container_of(cdev, struct ap3216c_dev, cdev);



	/* 初始化AP3216C */
	ap3216c_write_reg(ap3216cdev, AP3216C_SYSTEMCONG, 0x04);		/* 复位AP3216C 			*/
	mdelay(50);														/* AP3216C复位最少10ms 	*/
	ap3216c_write_reg(ap3216cdev, AP3216C_SYSTEMCONG, 0X03);		/* 开启ALS、PS+IR 		*/
	return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
	short data[3];
	long err = 0;

	struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;
	struct ap3216c_dev *dev = container_of(cdev, struct ap3216c_dev, cdev);
	
	ap3216c_readdata(dev);

	data[0] = dev->ir;
	data[1] = dev->als;
	data[2] = dev->ps;
	err = copy_to_user(buf, data, sizeof(data));
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int ap3216c_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/* AP3216C操作函数 */
static const struct file_operations ap3216c_ops = {
	.owner = THIS_MODULE,
	.open = ap3216c_open,
	.read = ap3216c_read,
	.release = ap3216c_release,
};

 /*
  * @description     : i2c驱动的probe函数,当驱动与
  *                    设备匹配以后此函数就会执行
  * @param - client  : i2c设备
  * @param - id      : i2c设备ID
  * @return          : 0,成功;其他负值,失败
  */
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	int ret;
	struct ap3216c_dev *ap3216cdev;
	ap3216cdev = devm_kzalloc(&client->dev, sizeof(*ap3216cdev), GFP_KERNEL);
	if(!ap3216cdev)
		return -ENOMEM;

	/* 初始化regmap_config设置 */
	ap3216cdev->regmap_config.reg_bits = 8;		/* 寄存器长度8bit */
	ap3216cdev->regmap_config.val_bits = 8;		/* 值长度8bit */

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

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

	/* 5、创建设备 */
	ap3216cdev->device = device_create(ap3216cdev->class, NULL, ap3216cdev->devid, NULL, AP3216C_NAME);
	if (IS_ERR(ap3216cdev->device)) {
		goto destroy_class;
	}
	ap3216cdev->client = client;
	/* 保存ap3216cdev结构体 */
	i2c_set_clientdata(client,ap3216cdev);

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

/*
 * @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
 * @param - client 	: i2c设备
 * @return          : 0,成功;其他负值,失败
 */
static int ap3216c_remove(struct i2c_client *client)
{
	struct ap3216c_dev *ap3216cdev = i2c_get_clientdata(client);
	/* 注销字符设备驱动 */
	/* 1、删除cdev */
	cdev_del(&ap3216cdev->cdev);
	/* 2、注销设备号 */
	unregister_chrdev_region(ap3216cdev->devid, AP3216C_CNT); 
	/* 3、注销设备 */
	device_destroy(ap3216cdev->class, ap3216cdev->devid);
	/* 4、注销类 */
	class_destroy(ap3216cdev->class); 
	/* 5、释放regmap */
	regmap_exit(ap3216cdev->regmap);
	return 0;
}

/* 传统匹配方式ID列表 */
static const struct i2c_device_id ap3216c_id[] = {
	{"alientek,ap3216c", 0},  
	{}
};

/* 设备树匹配列表 */
static const struct of_device_id ap3216c_of_match[] = {
	{ .compatible = "alientek,ap3216c" },
	{ /* Sentinel */ }
};

/* i2c驱动结构体 */	
static struct i2c_driver ap3216c_driver = {
	.probe = ap3216c_probe,
	.remove = ap3216c_remove,
	.driver = {
			.owner = THIS_MODULE,
		   	.name = "ap3216c",
		   	.of_match_table = ap3216c_of_match, 
		   },
	.id_table = ap3216c_id,
};
		   
/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init ap3216c_init(void)
{
	int ret = 0;

	ret = i2c_add_driver(&ap3216c_driver);
	return ret;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit ap3216c_exit(void)
{
	i2c_del_driver(&ap3216c_driver);
}

/* module_i2c_driver(ap3216c_driver) */

module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");

  • 测试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>
/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd;
	char *filename;
	unsigned short databuf[3];
	unsigned short ir, als, ps;
	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) { 			/* 数据读取成功 */
			ir =  databuf[0]; 	/* ir传感器数据 */
			als = databuf[1]; 	/* als传感器数据 */
			ps =  databuf[2]; 	/* ps传感器数据 */
			printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);
		}
		usleep(200000); /*100ms */
	}
	close(fd);	/* 关闭文件 */	
	return 0;
}


  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,这是一个关于Linux驱动的问题,接下来我会尽力回答。 首先,我们需要明确一下几个概念: 1. SMBus:系统管理总线,是一种基于I2C总线的协议,用于管理和监控系统中的硬件设备。 2. Block Write/Read:SMBus协议中的一种传输方式,即在一次传输中可以传输多个字节。 3. regmapLinux内核提供的一种寄存器映射机制,可以将设备寄存器映射到内存中,方便驱动程序对设备寄存器进行访问。 接下来,让我们来看一下如何通过regmap()读取某设备的温度。 1. 首先,需要在驱动程序中初始化一个regmap结构体,用于映射设备寄存器到内存中。具体代码如下: ``` static int driver_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct regmap *regmap; int ret; /* 初始化regmap结构体 */ regmap = devm_regmap_init_i2c(client, &driver_regmap_config); if (IS_ERR(regmap)) { dev_err(&client->dev, "regmap init failed\n"); return PTR_ERR(regmap); } /* 其他初始化操作 */ return 0; } ``` 2. 然后,在驱动程序中编写读取温度的函数,并使用regmap_read()函数进行读取。具体代码如下: ``` static int driver_read_temp(struct device *dev) { struct regmap *regmap = dev_get_drvdata(dev); u8 temp_buf[2]; int ret; int temp; /* 使用Block Write方式写入寄存器地址 */ ret = regmap_block_write(regmap, TEMP_REG_ADDR, &temp_buf, 1); if (ret < 0) { dev_err(dev, "write temp reg failed\n"); return ret; } /* 使用Block Read方式读取温度值 */ ret = regmap_block_read(regmap, TEMP_REG_ADDR, &temp_buf, 2); if (ret < 0) { dev_err(dev, "read temp reg failed\n"); return ret; } /* 将读取到的温度值转换为实际温度 */ temp = (temp_buf[0] << 8) | temp_buf[1]; temp = (temp * TEMP_RESOLUTION) / 1000; return temp; } ``` 在上面的代码中,TEMP_REG_ADDR指的是温度寄存器的地址,temp_buf用于存储读取到的温度值,TEMP_RESOLUTION是一个常量,用于将读取到的温度值转换为实际温度。 以上就是基于SMBus Block Write/Read方式通过regmap()读取某设备温度的一个例子。希望能对你有所帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

栋哥爱做饭

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

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

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

打赏作者

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

抵扣说明:

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

余额充值