本文主要是介绍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_write
和 regmap_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
中,已经支持了 I2C
、SPI
、AC97
、MMIO
和 SPMI
五种总线类型。
四、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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!