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

2023-11-06 11:40

本文主要是介绍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_FSstruct 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;
#endifunsigned 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_FSvoid (*debugfs_init)(struct regmap *map);
#endifint (*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 0int 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;
#endifint 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 0unsigned 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);
#endifregmap_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 0icm20608_read_regs(dev, reg, &data, 1);
#endifregmap_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 0unsigned char buf = value;icm20608_write_regs(dev, reg, &buf, 1);
#endifregmap_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 0icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data, 14);
#endifregmap_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
#

这篇关于i.MX 6ULL 驱动开发 二十五:Regmap的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/356401

相关文章

Android开发中gradle下载缓慢的问题级解决方法

《Android开发中gradle下载缓慢的问题级解决方法》本文介绍了解决Android开发中Gradle下载缓慢问题的几种方法,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、网络环境优化二、Gradle版本与配置优化三、其他优化措施针对android开发中Gradle下载缓慢的问

使用Go语言开发一个命令行文件管理工具

《使用Go语言开发一个命令行文件管理工具》这篇文章主要为大家详细介绍了如何使用Go语言开发一款命令行文件管理工具,支持批量重命名,删除,创建,移动文件,需要的小伙伴可以了解下... 目录一、工具功能一览二、核心代码解析1. 主程序结构2. 批量重命名3. 批量删除4. 创建文件/目录5. 批量移动三、如何安

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

基于Python开发PPTX压缩工具

《基于Python开发PPTX压缩工具》在日常办公中,PPT文件往往因为图片过大而导致文件体积过大,不便于传输和存储,所以本文将使用Python开发一个PPTX压缩工具,需要的可以了解下... 目录引言全部代码环境准备代码结构代码实现运行结果引言在日常办公中,PPT文件往往因为图片过大而导致文件体积过大,

使用DeepSeek API 结合VSCode提升开发效率

《使用DeepSeekAPI结合VSCode提升开发效率》:本文主要介绍DeepSeekAPI与VisualStudioCode(VSCode)结合使用,以提升软件开发效率,具有一定的参考价值... 目录引言准备工作安装必要的 VSCode 扩展配置 DeepSeek API1. 创建 API 请求文件2.

基于Python开发电脑定时关机工具

《基于Python开发电脑定时关机工具》这篇文章主要为大家详细介绍了如何基于Python开发一个电脑定时关机工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 简介2. 运行效果3. 相关源码1. 简介这个程序就像一个“忠实的管家”,帮你按时关掉电脑,而且全程不需要你多做

Java中的Opencv简介与开发环境部署方法

《Java中的Opencv简介与开发环境部署方法》OpenCV是一个开源的计算机视觉和图像处理库,提供了丰富的图像处理算法和工具,它支持多种图像处理和计算机视觉算法,可以用于物体识别与跟踪、图像分割与... 目录1.Opencv简介Opencv的应用2.Java使用OpenCV进行图像操作opencv安装j

基于Qt开发一个简单的OFD阅读器

《基于Qt开发一个简单的OFD阅读器》这篇文章主要为大家详细介绍了如何使用Qt框架开发一个功能强大且性能优异的OFD阅读器,文中的示例代码讲解详细,有需要的小伙伴可以参考一下... 目录摘要引言一、OFD文件格式解析二、文档结构解析三、页面渲染四、用户交互五、性能优化六、示例代码七、未来发展方向八、结论摘要

在 VSCode 中配置 C++ 开发环境的详细教程

《在VSCode中配置C++开发环境的详细教程》本文详细介绍了如何在VisualStudioCode(VSCode)中配置C++开发环境,包括安装必要的工具、配置编译器、设置调试环境等步骤,通... 目录如何在 VSCode 中配置 C++ 开发环境:详细教程1. 什么是 VSCode?2. 安装 VSCo

C#图表开发之Chart详解

《C#图表开发之Chart详解》C#中的Chart控件用于开发图表功能,具有Series和ChartArea两个重要属性,Series属性是SeriesCollection类型,包含多个Series对... 目录OverviChina编程ewSeries类总结OverviewC#中,开发图表功能的控件是Char