i.MX 6ULL 驱动开发 二十五:Regmap

一、Regmap概述

Regmap 机制主要目的是减少慢速 I/O 驱动上的重复逻辑提供一种通用接口来操作底层硬件上的寄存器。Regmap 除了能做到统一的 I/O 接口,还可以在驱动和硬件 IC 之间做一层缓存,从而能减少底层 I/O 的操作次数。

未使用 Regmap 机制的驱动框图如下
在这里插入图片描述
使用 Regmap 机制的驱动框图如下
在这里插入图片描述
说明:regmap 机制是对 SPI 子系统、I2C子系统等进行封装,为上层应用提供统一接口。

二、Regmap 驱动框架

在这里插入图片描述
regmap 分为 3

1、物理总线层:regmap 对不同的物理总线进行封装。

2、regmap 核心层:实现 regmap 机制。

3、regmap API 抽象层:向驱动提供 API 接口。

三、regmap 中重要对象

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

#ifdef CONFIG_DEBUG_FS
	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);
	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);

	bool defer_caching;

	u8 read_flag_mask;
	u8 write_flag_mask;

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

	/* 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 */
	u32 cache_only;
	/* if set, only the HW is modified not the cache */
	u32 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 */
	u32 cache_dirty;
	/* if set, the HW registers are known to match map->reg_defaults */
	bool no_sync_defaults;

	struct reg_default *patch;
	int patch_regs;

	/* if set, converts bulk rw to single rw */
	bool use_single_rw;
	/* if set, the device supports multi write mode */
	bool can_multi_write;

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

向驱动层抽象 regmap,为驱动层提供一个 regmap 对象。

2、设备的寄存器配置信息

/**
 * Configuration for the register map of a device.
 *
 * @name: Optional name of the regmap. Useful when a device has multiple
 *        register regions.
 *
 * @reg_bits: Number of bits in a register address, mandatory.
 * @reg_stride: The register address stride. Valid register addresses are a
 *              multiple of this value. If set to 0, a value of 1 will be
 *              used.
 * @pad_bits: Number of bits of padding between register and value.
 * @val_bits: Number of bits in a register value, mandatory.
 *
 * @writeable_reg: Optional callback returning true if the register
 *		   can be written to. If this field is NULL but wr_table
 *		   (see below) is not, the check is performed on such table
 *                 (a register is writeable if it belongs to one of the ranges
 *                  specified by wr_table).
 * @readable_reg: Optional callback returning true if the register
 *		  can be read from. If this field is NULL but rd_table
 *		   (see below) is not, the check is performed on such table
 *                 (a register is readable if it belongs to one of the ranges
 *                  specified by rd_table).
 * @volatile_reg: Optional callback returning true if the register
 *		  value can't be cached. If this field is NULL but
 *		  volatile_table (see below) is not, the check is performed on
 *                such table (a register is volatile if it belongs to one of
 *                the ranges specified by volatile_table).
 * @precious_reg: Optional callback returning true if the register
 *		  should not be read outside of a call from the driver
 *		  (e.g., a clear on read interrupt status register). If this
 *                field is NULL but precious_table (see below) is not, the
 *                check is performed on such table (a register is precious if
 *                it belongs to one of the ranges specified by precious_table).
 * @lock:	  Optional lock callback (overrides regmap's default lock
 *		  function, based on spinlock or mutex).
 * @unlock:	  As above for unlocking.
 * @lock_arg:	  this field is passed as the only argument of lock/unlock
 *		  functions (ignored in case regular lock/unlock functions
 *		  are not overridden).
 * @reg_read:	  Optional callback that if filled will be used to perform
 *           	  all the reads from the registers. Should only be provided for
 *		  devices whose read operation cannot be represented as a simple
 *		  read operation on a bus such as SPI, I2C, etc. Most of the
 *		  devices do not need this.
 * @reg_write:	  Same as above for writing.
 * @fast_io:	  Register IO is fast. Use a spinlock instead of a mutex
 *	     	  to perform locking. This field is ignored if custom lock/unlock
 *	     	  functions are used (see fields lock/unlock of struct regmap_config).
 *		  This field is a duplicate of a similar file in
 *		  'struct regmap_bus' and serves exact same purpose.
 *		   Use it only for "no-bus" cases.
 * @max_register: Optional, specifies the maximum valid register index.
 * @wr_table:     Optional, points to a struct regmap_access_table specifying
 *                valid ranges for write access.
 * @rd_table:     As above, for read access.
 * @volatile_table: As above, for volatile registers.
 * @precious_table: As above, for precious registers.
 * @reg_defaults: Power on reset values for registers (for use with
 *                register cache support).
 * @num_reg_defaults: Number of elements in reg_defaults.
 *
 * @read_flag_mask: Mask to be set in the top byte of the register when doing
 *                  a read.
 * @write_flag_mask: Mask to be set in the top byte of the register when doing
 *                   a write. If both read_flag_mask and write_flag_mask are
 *                   empty the regmap_bus default masks are used.
 * @use_single_rw: If set, converts the bulk read and write operations into
 *		    a series of single read and write operations. This is useful
 *		    for device that does not support bulk read and write.
 * @can_multi_write: If set, the device supports the multi write mode of bulk
 *                   write operations, if clear multi write requests will be
 *                   split into individual write operations
 *
 * @cache_type: The actual cache type.
 * @reg_defaults_raw: Power on reset values for registers (for use with
 *                    register cache support).
 * @num_reg_defaults_raw: Number of elements in reg_defaults_raw.
 * @reg_format_endian: Endianness for formatted register addresses. If this is
 *                     DEFAULT, the @reg_format_endian_default value from the
 *                     regmap bus is used.
 * @val_format_endian: Endianness for formatted register values. If this is
 *                     DEFAULT, the @reg_format_endian_default value from the
 *                     regmap bus is used.
 *
 * @ranges: Array of configuration entries for virtual address ranges.
 * @num_ranges: Number of range configuration entries.
 */
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;

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

Regmap 初始化时,驱动就需要把这个结构体传给 Regmap。这个结构体的定义在 include/linux/regmap.h,其中包含该设备的寄存器数量,寄存器位宽,缓存类型,读写属性等。

这一层是直接和驱动对接的。Regmap 根据传进来的 regmap_config 初始化对应的缓存和总线操作接口,驱动就可以正常调用 regmap_writeregmap_read 函数。

3、regmap 缓存类型

struct regcache_ops {
	const char *name;
	enum regcache_type type;
	int (*init)(struct regmap *map);
	int (*exit)(struct regmap *map);
#ifdef CONFIG_DEBUG_FS
	void (*debugfs_init)(struct regmap *map);
#endif
	int (*read)(struct regmap *map, unsigned int reg, unsigned int *value);
	int (*write)(struct regmap *map, unsigned int reg, unsigned int value);
	int (*sync)(struct regmap *map, unsigned int min, unsigned int max);
	int (*drop)(struct regmap *map, unsigned int min, unsigned int max);
};

在最新 Linux 4.0 版本中,已经有 3 种缓存类型,分别是数组(flat)、LZO 压缩和红黑树(rbtree)。

缓存的类型是在 Regmap 初始化时,由 .cache_type = REGCACHE_RBTREE 来指定的。对于 regmap_read 来说,会先判断当前缓存是否有值,然后再检查是否需要 bypass,若没有,则可以直接从缓存里面取值,调用 regcache_read 来获取值,若需要从硬件上读取,则调用具体协议的读写函数。

4、regmap 总线

/**
 * Description of a hardware bus for the register map infrastructure.
 *
 * @fast_io: Register IO is fast. Use a spinlock instead of a mutex
 *	     to perform locking. This field is ignored if custom lock/unlock
 *	     functions are used (see fields lock/unlock of
 *	     struct regmap_config).
 * @write: Write operation.
 * @gather_write: Write operation with split register/value, return -ENOTSUPP
 *                if not implemented  on a given device.
 * @async_write: Write operation which completes asynchronously, optional and
 *               must serialise with respect to non-async I/O.
 * @read: Read operation.  Data is returned in the buffer used to transmit
 *         data.
 * @async_alloc: Allocate a regmap_async() structure.
 * @read_flag_mask: Mask to be set in the top byte of the register when doing
 *                  a read.
 * @reg_format_endian_default: Default endianness for formatted register
 *     addresses. Used when the regmap_config specifies DEFAULT. If this is
 *     DEFAULT, BIG is assumed.
 * @val_format_endian_default: Default endianness for formatted register
 *     values. Used when the regmap_config specifies DEFAULT. If this is
 *     DEFAULT, BIG is assumed.
 * @async_size: Size of struct used for async work.
 */
struct regmap_bus {
	bool fast_io;
	regmap_hw_write write;
	regmap_hw_gather_write gather_write;
	regmap_hw_async_write async_write;
	regmap_hw_reg_write reg_write;
	regmap_hw_read read;
	regmap_hw_reg_read reg_read;
	regmap_hw_free_context free_context;
	regmap_hw_async_alloc async_alloc;
	u8 read_flag_mask;
	enum regmap_endian reg_format_endian_default;
	enum regmap_endian val_format_endian_default;
};

Lernel 4.0 中,已经支持了 I2CSPIAC97MMIOSPMI 五种总线类型。

四、Regmap API

struct regmap *regmap_init(struct device *dev,
			   const struct regmap_bus *bus,
			   void *bus_context,
			   const struct regmap_config *config);
int regmap_attach_dev(struct device *dev, struct regmap *map,
				 const struct regmap_config *config);
struct regmap *regmap_init_i2c(struct i2c_client *i2c,
			       const struct regmap_config *config);
struct regmap *regmap_init_spi(struct spi_device *dev,
			       const struct regmap_config *config);
struct regmap *regmap_init_spmi_base(struct spmi_device *dev,
				     const struct regmap_config *config);
struct regmap *regmap_init_spmi_ext(struct spmi_device *dev,
				    const struct regmap_config *config);
struct regmap *regmap_init_mmio_clk(struct device *dev, const char *clk_id,
				    void __iomem *regs,
				    const struct regmap_config *config);
struct regmap *regmap_init_ac97(struct snd_ac97 *ac97,
				const struct regmap_config *config);

struct regmap *devm_regmap_init(struct device *dev,
				const struct regmap_bus *bus,
				void *bus_context,
				const struct regmap_config *config);
struct regmap *devm_regmap_init_i2c(struct i2c_client *i2c,
				    const struct regmap_config *config);
struct regmap *devm_regmap_init_spi(struct spi_device *dev,
				    const struct regmap_config *config);
struct regmap *devm_regmap_init_spmi_base(struct spmi_device *dev,
					  const struct regmap_config *config);
struct regmap *devm_regmap_init_spmi_ext(struct spmi_device *dev,
					 const struct regmap_config *config);
struct regmap *devm_regmap_init_mmio_clk(struct device *dev, const char *clk_id,
					 void __iomem *regs,
					 const struct regmap_config *config);
struct regmap *devm_regmap_init_ac97(struct snd_ac97 *ac97,
				     const struct regmap_config *config);

bool regmap_ac97_default_volatile(struct device *dev, unsigned int reg);

五、修改ap3216c驱动程序(I2C子系统)

#include "linux/init.h"
#include "linux/module.h"
#include "linux/printk.h"
#include "linux/i2c.h"
#include "linux/miscdevice.h"
#include "linux/fs.h"
#include "linux/delay.h"
#include "asm-generic/uaccess.h"
#include "linux/regmap.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数据高字节     */

#define NEWCHRDEV_MINOR 255         /* 次设备号(让MISC自动分配) */
#define NEWCHRDEV_NAME  "ap3216c"   /* 名子 */

typedef struct{
    struct i2c_client *client;      /* i2C设备 */
    unsigned short ir, als, ps;     /* 传感器数据 */
    struct regmap *regmap;
    struct regmap_config regmap_config;
}newchrdev_t;

newchrdev_t newchrdev;

/*
 * @description	: 从ap3216c读取多个寄存器数据
 * @param - dev:  ap3216c设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return 		: 操作结果
 */
static int ap3216c_read_regs(newchrdev_t *dev, unsigned char reg, void *val, int len)
{
#if 0
	int ret;
	struct i2c_msg msg[2];
	struct i2c_client *client = dev->client;

	/* msg[0]为发送要读取的首地址 */
	msg[0].addr = client->addr;			/* ap3216c地址 */
	msg[0].flags = 0;					/* 标记为发送数据 */
	msg[0].buf = ®					/* 读取的首地址 */
	msg[0].len = 1;						/* reg长度*/

	/* msg[1]读取数据 */
	msg[1].addr = client->addr;			/* ap3216c地址 */
	msg[1].flags = I2C_M_RD;			/* 标记为读取数据*/
	msg[1].buf = val;					/* 读取数据缓冲区 */
	msg[1].len = len;					/* 要读取的数据长度*/

	ret = i2c_transfer(client->adapter, msg, 2);
	if(ret == 2) {
		ret = 0;
	} else {
		printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
		ret = -EREMOTEIO;
	}
	return ret;
#endif
    int ret = 0;
    ret = regmap_read(dev->regmap, reg, (unsigned int*)val);
    return ret;
}

/*
 * @description	: 向ap3216c多个寄存器写入数据
 * @param - dev:  ap3216c设备
 * @param - reg:  要写入的寄存器首地址
 * @param - val:  要写入的数据缓冲区
 * @param - len:  要写入的数据长度
 * @return 	  :   操作结果
 */
static int ap3216c_write_regs(newchrdev_t *dev, unsigned char reg, unsigned char *buf, unsigned char len)
{
#if 0
	unsigned char b[256];
	struct i2c_msg msg;
	struct i2c_client *client = dev->client;
	
	b[0] = reg;					/* 寄存器首地址 */
	memcpy(&b[1],buf,len);		/* 将要写入的数据拷贝到数组b里面 */
		
	msg.addr = client->addr;	/* ap3216c地址 */
	msg.flags = 0;				/* 标记为写数据 */

	msg.buf = b;				/* 要写入的数据缓冲区 */
	msg.len = len + 1;			/* 要写入的数据长度 */

	return i2c_transfer(client->adapter, &msg, 1);
#endif
    regmap_write(dev->regmap, reg, (unsigned int)*buf);
    return 0;
}

/*
 * @description	: 读取AP3216C的数据,读取原始数据,包括ALS,PS和IR, 注意!
 *				: 如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms
 * @param - ir	: ir数据
 * @param - ps 	: ps数据
 * @param - ps 	: als数据 
 * @return 		: 无。
 */
void ap3216c_read_data(newchrdev_t *dev)
{
    unsigned char i =0;
    unsigned char buf[6];

    /* 循环读取所有传感器数据 */
    for(i = 0; i < 6; i++)	
    {
        ap3216c_read_regs(dev, AP3216C_IRDATALOW + i, buf+i, 1);
    }

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

static int lcs_ap3216c_init(newchrdev_t *dev)
{
    unsigned char buf = 0;
    /* 1、复位ap3216c */
    buf = 0x04;
    ap3216c_write_regs(dev, AP3216C_SYSTEMCONG, &buf, 1);
    /* 2、至少延时10ms */
    mdelay(50);
    /* 3、开启ALS、PS+IR */
    buf = 0x03;
    ap3216c_write_regs(dev, AP3216C_SYSTEMCONG, &buf, 1);

    return 0;
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int ap3216c_open(struct inode *inode, struct file *filp)
{
    /* 1、设置私有数据 */
    filp->private_data = &newchrdev;
    /* 2、初始化ap3216c */
    lcs_ap3216c_init(&newchrdev);
    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 *offt)
{
    short data[3];
    long err = 0;

    newchrdev_t *dev = (newchrdev_t*)filp->private_data;

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

static const struct file_operations ap3216c_ops = {
    .owner   = THIS_MODULE,
    .open = ap3216c_open,
    .read = ap3216c_read,
    .release = ap3216c_release,
};

/* MISC设备结构体 */
static struct miscdevice ap3216c_miscdev = {
	.minor = NEWCHRDEV_MINOR,
	.name = NEWCHRDEV_NAME,
	.fops = &ap3216c_ops,
};

 /*
  * @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 = 0;
    /* 1、注册 MISC 子系统 */
    ret = misc_register(&ap3216c_miscdev);
    if(ret < 0){
        printk("ap3216c misc device register failed!\r\n");
        ret = -EFAULT;
    }
    /* 2、提取 i2c 设备 */
    newchrdev.client = client;
    /* 3、初始化 regmap */
    /* 3.1、初始化regmap_config设置 */
    newchrdev.regmap_config.reg_bits = 8;     /* 寄存器长度8bit */
    newchrdev.regmap_config.val_bits = 8;     /* 值长度8bit */
    /* 3.2、初始化IIC接口的regmap */
    newchrdev.regmap = regmap_init_i2c(client, &newchrdev.regmap_config);
    if (IS_ERR(newchrdev.regmap)) {
        ret = -EFAULT;
        goto ap3216c_probe_fail;
    }

    return 0;
ap3216c_probe_fail:
    misc_deregister(&ap3216c_miscdev);
    return ret;
}

/*
 * @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
 * @param - client 	: i2c设备
 * @return          : 0,成功;其他负值,失败
 */
static int ap3216c_remove(struct i2c_client *client)
{
    int ret = 0;
    /* 释放regmap */
	regmap_exit(newchrdev.regmap);
    /* MISC 驱动框架卸载 */
    misc_deregister(&ap3216c_miscdev);
    return ret;
}

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

/* 设备树匹配列表 */
static const struct of_device_id ap3216c_of_match[] = {
	{ .compatible = "lsc,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");

测试:

# ls
ap3216c_app        ap3216c_regmap.ko
#
# ls -l /dev/ap3216c
ls: /dev/ap3216c: No such file or directory
#
# insmod ap3216c_regmap.ko
#
# ls -l /dev/ap3216c
crw-rw----    1 root     root       10,  55 Jan  1 00:33 /dev/ap3216c
#
# rmmod ap3216c_regmap.ko
#
# ls -l /dev/ap3216c
ls: /dev/ap3216c: No such file or directory
#
# insmod ap3216c_regmap.ko
#
# ./ap3216c_app /dev/ap3216c
ir = 0, als = 0, ps = 0
ir = 4, als = 194, ps = 909
ir = 0, als = 182, ps = 805
ir = 12, als = 192, ps = 835
ir = 0, als = 7, ps = 1023
ir = 11, als = 0, ps = 1023
ir = 2, als = 0, ps = 1023
ir = 0, als = 205, ps = 834
ir = 13, als = 192, ps = 838
ir = 9, als = 193, ps = 852
ir = 12, als = 0, ps = 1023
ir = 0, als = 0, ps = 1023
ir = 0, als = 5, ps = 1023
ir = 0, als = 1, ps = 1023
ir = 3, als = 189, ps = 844
ir = 0, als = 190, ps = 808
^C
#
# rmmod ap3216c_regmap.ko

六、修改icm20608驱动程序(SPI子系统)

#include "linux/init.h"
#include "linux/module.h"
#include "linux/spi/spi.h"
#include "linux/miscdevice.h"
#include "linux/fs.h"
#include "linux/printk.h"
#include "linux/delay.h"
#include "asm-generic/uaccess.h"
#include "linux/regmap.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

#define NEWCHRDEV_MINOR 255         /* 次设备号(让MISC自动分配) */
#define NEWCHRDEV_NAME  "icm20608"   /* 名子 */

typedef struct{
    void *private_data;			/* 私有数据(保存SPI设备指针)*/
    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;
	struct regmap_config regmap_config;
}newchrdev_t;
newchrdev_t newchrdev;

/*
 * @description	: 从icm20608读取多个寄存器数据
 * @param - dev:  icm20608设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return 		: 操作结果
 * 说明:icm20608_read_regs函数内部操作SPI子系统已经封装,封装好的函数为spi_read
 */
static int icm20608_read_regs(newchrdev_t *dev, unsigned char reg, void *buf, int len)
{
	int ret = -1;
	unsigned char txdata[1];
	unsigned char * rxdata;
	struct spi_message m;
	struct spi_transfer *t;
	struct spi_device *spi = (struct spi_device *)dev->private_data;
    
	t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);	/* 申请内存 */
	if(!t) {
		return -ENOMEM;
	}

	rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL);	/* 申请内存 */
	if(!rxdata) {
		goto out1;
	}

	/* 一共发送len+1个字节的数据,第一个字节为
	寄存器首地址,一共要读取len个字节长度的数据,*/
	txdata[0] = reg | 0x80;		/* 写数据的时候首寄存器地址bit8要置1 */			
	t->tx_buf = txdata;			/* 要发送的数据 */
    t->rx_buf = rxdata;			/* 要读取的数据 */
	t->len = len+1;				/* t->len=发送的长度+读取的长度 */
	spi_message_init(&m);		/* 初始化spi_message */
	spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
	ret = spi_sync(spi, &m);	/* 同步发送 */
	if(ret) {
		goto out2;
	}
	
    memcpy(buf , rxdata+1, len);  /* 只需要读取的数据 */

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

/*
 * @description	: 向icm20608多个寄存器写入数据
 * @param - dev:  icm20608设备
 * @param - reg:  要写入的寄存器首地址
 * @param - val:  要写入的数据缓冲区
 * @param - len:  要写入的数据长度
 * @return 	  :   操作结果
 * 说明:icm20608_write_regs函数内部操作SPI子系统已经封装,封装好的函数为spi_write
 */
static signed int icm20608_write_regs(newchrdev_t *dev, unsigned char reg, unsigned char *buf, unsigned char len)
{
	int ret = -1;
	unsigned char *txdata;
	struct spi_message m;
	struct spi_transfer *t;
	struct spi_device *spi = (struct spi_device *)dev->private_data;
	
	t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);	/* 申请内存 */
	if(!t) {
		return -ENOMEM;
	}
	
	txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);
	if(!txdata) {
		goto out1;
	}
	
	/* 一共发送len+1个字节的数据,第一个字节为
	寄存器首地址,len为要写入的寄存器的集合,*/
	*txdata = reg & ~0x80;	/* 写数据的时候首寄存器地址bit8要清零 */
    memcpy(txdata+1, buf, len);	/* 把len个寄存器拷贝到txdata里,等待发送 */
	t->tx_buf = txdata;			/* 要发送的数据 */
	t->len = len+1;				/* t->len=发送的长度+读取的长度 */
	spi_message_init(&m);		/* 初始化spi_message */
	spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
	ret = spi_sync(spi, &m);	/* 同步发送 */
    if(ret) {
        goto out2;
    }
	
out2:
	kfree(txdata);				/* 释放内存 */
out1:
	kfree(t);					/* 释放内存 */
	return ret;
}

/*
 * @description	: 读取icm20608指定寄存器值,读取一个寄存器
 * @param - dev:  icm20608设备
 * @param - reg:  要读取的寄存器
 * @return 	  :   读取到的寄存器值
 */
static unsigned char icm20608_read_onereg(newchrdev_t *dev, unsigned char reg)
{
	unsigned char data = 0;
#if 0
	icm20608_read_regs(dev, reg, &data, 1);
#endif
	regmap_read(dev->regmap, reg, (unsigned int*)&data);
	return data;
}

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

static void icm20608_write_onereg(newchrdev_t *dev, unsigned char reg, unsigned char value)
{
#if 0
	unsigned char buf = value;
	icm20608_write_regs(dev, reg, &buf, 1);
#endif
	regmap_write(dev->regmap,  reg, (unsigned int)value);
}

/*
 * ICM20608内部寄存器初始化函数 
 * @param  	: 无
 * @return 	: 无
 */
void icm20608_reginit(void)
{
	unsigned char value = 0;
	
    icm20608_write_onereg(&newchrdev, ICM20_PWR_MGMT_1, 0x80);
	mdelay(50);
	icm20608_write_onereg(&newchrdev, ICM20_PWR_MGMT_1, 0x01);
	mdelay(50);

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

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

/*
 * @description	: 读取ICM20608的数据,读取原始数据,包括三轴陀螺仪、
 * 				: 三轴加速度计和内部温度。
 * @param - dev	: ICM20608设备
 * @return 		: 无。
 */
void icm20608_readdata(newchrdev_t *dev)
{
	unsigned char data[14] = "";
#if 0
	icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data, 14);
#endif
	regmap_bulk_read(dev->regmap, ICM20_ACCEL_XOUT_H, (void*)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结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int icm20608_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &newchrdev; /* 设置私有数据 */
    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 *offt)
{
    newchrdev_t *dev = (newchrdev_t *)filp->private_data;
    signed int data[7];
	long err = 0;
    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;
    printk("data[0] = %d data[1] = %d data[2] = %d data[3] = %d data[4] = %d data[5] = %d\r\n",data[0],data[1],data[2],data[3],data[4],data[5]);
    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;
}

static const struct file_operations icm20608_ops = {
    .owner   = THIS_MODULE,
    .open = icm20608_open,
	.read = icm20608_read,
	.release = icm20608_release,
};

/* MISC设备结构体 */
static struct miscdevice icm20608_miscdev = {
	.minor = NEWCHRDEV_MINOR,
	.name = NEWCHRDEV_NAME,
	.fops = &icm20608_ops,
};

/*
 * @description     : spi驱动的probe函数,当驱动与
 *                    设备匹配以后此函数就会执行
 * @param - client  : spi设备
 * @param - id      : spi设备ID
 * 
 */	
static int icm20608_probe(struct spi_device *spi)
{
    int ret = 0;
    /* 使用 MISC 注册字符设备驱动 */
    ret = misc_register(&icm20608_miscdev);
    if(ret < 0){
        printk("icm20608 misc device register failed!\r\n");
        ret = -EFAULT;
    }
	/* 初始化regmap_config设置 */
	newchrdev.regmap_config.reg_bits = 8;			/* 寄存器长度8bit */
	newchrdev.regmap_config.val_bits = 8;			/* 值长度8bit */
	newchrdev.regmap_config.read_flag_mask = 0x80;  /* 读掩码设置为0X80,ICM20608使用SPI接口读的时候寄存器最高位应该为1 */

	/* 初始化IIC接口的regmap */
	newchrdev.regmap = regmap_init_spi(spi, &newchrdev.regmap_config);
	if (IS_ERR(newchrdev.regmap)) {
		 ret = -EFAULT;
		 goto icm20608_probe_fail;
	}	
    newchrdev.private_data = spi; /* 设置私有数据 */
	/* 初始化icm20608 */
    icm20608_reginit();

	return 0;
icm20608_probe_fail:
	misc_deregister(&icm20608_miscdev);
    return ret;
}

/*
 * @description     : spi驱动的remove函数,移除spi驱动的时候此函数会执行
 * @param - client 	: spi设备
 * @return          : 0,成功;其他负值,失败
 */
static int icm20608_remove(struct spi_device *spi)
{
    int ret = 0;
	/* 删除regmap */
	regmap_exit(newchrdev.regmap);
    /* MISC 驱动框架卸载 */
    misc_deregister(&icm20608_miscdev);
    return ret;
}

/* 传统匹配方式ID列表 */
static const struct spi_device_id icm20608_id[] = {
    {"invensense,icm20608", 0},  
    {}
};

/* 设备树匹配列表 */
static const struct of_device_id icm20608_of_match[] = {
    { .compatible = "invensense,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)
{
    int ret = 0;
    ret = spi_register_driver(&icm20608_driver);
    return ret;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit icm20608_exit(void)
{
    spi_unregister_driver(&icm20608_driver);
}

/* module_i2c_driver(ap3216c_driver) */

module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");

测试:

# ls
ap3216c_app         icm20608_app
ap3216c_regmap.ko   icm20608_regmap.ko
#
# insmod icm20608_regmap.ko
ICM20608 ID = 0XAE
#
# ls -l /dev/icm20608
crw-rw----    1 root     root       10,  56 Jan  1 04:39 /dev/icm20608
#
# rmmod icm20608_regmap.ko
#
# ls -l /dev/icm20608
ls: /dev/icm20608: No such file or directory
#
# insmod icm20608_regmap.ko
ICM20608 ID = 0XAE
#
# ./icm20608_app /dev/icm20608
data[0] = 14 data[1] = 8 data[2] = -2 data[3] = 45 data[4] = 7 data[5] = 2073

原始值:
gx = 14, gy = 8, gz = -2
ax = 45, ay = 7, az = 2073
temp = 759
实际值:act gx = 0.85°/S, act gy = 0.49°/S, act gz = -0.12°/S
act ax = 0.02g, act ay = 0.00g, act az = 1.01g
act temp = 27.25°C
data[0] = 11 data[1] = 10 data[2] = -1 data[3] = 44 data[4] = 11 data[5] = 2073

原始值:
gx = 11, gy = 10, gz = -1
ax = 44, ay = 11, az = 2073
temp = 765
实际值:act gx = 0.67°/S, act gy = 0.61°/S, act gz = -0.06°/S
act ax = 0.02g, act ay = 0.01g, act az = 1.01g
act temp = 27.26°C
data[0] = 596 data[1] = 95 data[2] = -193 data[3] = 53 data[4] = 1623 data[5] = 1281

原始值:
gx = 596, gy = 95, gz = -193
ax = 53, ay = 1623, az = 1281
temp = 756
实际值:act gx = 36.34°/S, act gy = 5.79°/S, act gz = -11.77°/S
act ax = 0.03g, act ay = 0.79g, act az = 0.63g
act temp = 27.24°C
data[0] = 549 data[1] = -117 data[2] = 2 data[3] = 208 data[4] = 2005 data[5] = 486

原始值:
gx = 549, gy = -117, gz = 2
ax = 208, ay = 2005, az = 486
temp = 761
实际值:act gx = 33.48°/S, act gy = -7.13°/S, act gz = 0.12°/S
act ax = 0.10g, act ay = 0.98g, act az = 0.24g
act temp = 27.25°C
data[0] = -2424 data[1] = 137 data[2] = 171 data[3] = 15 data[4] = 1678 data[5] = 1001

原始值:
gx = -2424, gy = 137, gz = 171
ax = 15, ay = 1678, az = 1001
temp = 766
实际值:act gx = -147.80°/S, act gy = 8.35°/S, act gz = 10.43°/S
act ax = 0.01g, act ay = 0.82g, act az = 0.49g
act temp = 27.27°C
data[0] = 15 data[1] = 6 data[2] = -2 data[3] = 49 data[4] = 5 data[5] = 2071

原始值:
gx = 15, gy = 6, gz = -2
ax = 49, ay = 5, az = 2071
temp = 766
实际值:act gx = 0.91°/S, act gy = 0.37°/S, act gz = -0.12°/S
act ax = 0.02g, act ay = 0.00g, act az = 1.01g
act temp = 27.27°C
^C
# random: nonblocking pool is initialized

# rmmod icm20608_regmap.ko
#
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值