在Linu 3.1开始,Linux引入了regmap来同意管理内核的I2C, SPI等总线,将I2C, SPI驱动做了一次重构,把I/O读写的重复逻辑在regmap中实现。

用一个I2C设备为例,在3.1之前的I2C设备驱动,需要各自调用i2c_transfer来实现读写,比如:

static int raydium_i2c_pda2_write(struct i2c_client *client,unsigned char addr, unsigned char *w_data, unsigned short length)
{int retval = -1;unsigned char retry;unsigned char buf[MAX_WRITE_PACKET_SIZE + 1];struct raydium_ts_data *data = i2c_get_clientdata(client);struct i2c_msg msg[] = {{.addr = RAYDIUM_I2C_NID,.flags = RAYDIUM_I2C_WRITE,.len = length + 1,.buf = buf,},};if (length > MAX_WRITE_PACKET_SIZE){return -EINVAL;}u8_i2c_mode = PDA2_MODE;buf[0] = addr;memcpy(&buf[1], w_data, length);for (retry = 0; retry < SYN_I2C_RETRY_TIMES; retry++){if (i2c_transfer(client->adapter, msg, 1) == 1){retval = length;break;}msleep(20);}if (retry == SYN_I2C_RETRY_TIMES){dev_err(&data->client->dev,"[touch]%s: I2C write over retry limit.addr[0x%x]\n",__func__, addr);retval = -EIO;}return retval;
}

需要自行构建i2c_msg,然后使用i2c_transfer传输数据。

但是使用regmap机制,就会变的更为简单。只需要如下几步:

// 1.构建regmap_config结构
static const struct regmap_config mlx90632_regmap = {.reg_bits = 16,.val_bits = 16,.volatile_table = &mlx90632_volatile_regs_tbl,.rd_table = &mlx90632_readable_regs_tbl,.wr_table = &mlx90632_writeable_regs_tbl,.use_single_rw = true,.reg_format_endian = REGMAP_ENDIAN_BIG,.val_format_endian = REGMAP_ENDIAN_BIG,.cache_type = REGCACHE_RBTREE,
};
// 2. 初始化regmap
regmap = regmap_init_i2c(client, &mlx90632_regmap);//3.读写操作
ret = regmap_read(mlx90632->regmap, MLX90632_EE_VERSION, &read);

我们分析三步操作:

1.regmap_config结构

在regmap_config,定义了寄存器的各种信息,比如寄存器地址长度,寄存器值的长度,读写寄存器的地址范围的信息,寄存器地址和值的大小端已经缓冲方式。

struct regmap_config {const char *name; // 可选,寄存器名字int reg_bits; // 寄存器地址位宽,必须填写int reg_stride; // 寄存器操作宽度,比如为1时,所有寄存器可操作,为2时,只有2^n可操作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;
};

2.regmap_init_i2c

regmap_init_i2c在/kernel/driver/base/rgmap/regmap-i2c.c中实现:

struct regmap *regmap_init_i2c(struct i2c_client *i2c,const struct regmap_config *config)
{const struct regmap_bus *bus = regmap_get_i2c_bus(i2c, config);if (IS_ERR(bus))return ERR_CAST(bus);return regmap_init(&i2c->dev, bus, &i2c->dev, config);
}

这里有两个新结构,一个regmap_bus和一个regmap。regmap_bus结构定位了I2C总线的读写函数。对于普通I2C设备,regmap_bus为:

static const struct regmap_bus *regmap_get_i2c_bus(struct i2c_client *i2c,const struct regmap_config *config)
{if (i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C))return &regmap_i2c;
}static struct regmap_bus regmap_i2c = {.write = regmap_i2c_write,.gather_write = regmap_i2c_gather_write,.read = regmap_i2c_read,.reg_format_endian_default = REGMAP_ENDIAN_BIG,.val_format_endian_default = REGMAP_ENDIAN_BIG,
};

regmap_bus结构定义了读写函数和默认的寄存器地址和寄存器值的大小端。我们看下这个read函数指针实现:

@/kernel/driver/base/rgmap/regmap-i2c.c
static int regmap_i2c_read(void *context,const void *reg, size_t reg_size,void *val, size_t val_size)
{struct device *dev = context;struct i2c_client *i2c = to_i2c_client(dev);struct i2c_msg xfer[2];int ret;xfer[0].addr = i2c->addr;xfer[0].flags = 0;xfer[0].len = reg_size;xfer[0].buf = (void *)reg;xfer[1].addr = i2c->addr;xfer[1].flags = I2C_M_RD;xfer[1].len = val_size;xfer[1].buf = val;ret = i2c_transfer(i2c->adapter, xfer, 2);if (ret == 2)return 0;else if (ret < 0)return ret;elsereturn -EIO;
}

可以看到其实就是构造i2c_msg,选择操作寄存器地址然后读寄存器值。这正是regmap要做的事情,抽离出总线读写操作到regmap中,避免驱动去重复实现。

在看regmap_init函数:

struct regmap *regmap_init(struct device *dev,const struct regmap_bus *bus,void *bus_context,const struct regmap_config *config)
{struct regmap *map;int ret = -EINVAL;enum regmap_endian reg_endian, val_endian;map = kzalloc(sizeof(*map), GFP_KERNEL);// 将regmap_config定义的参数赋值到regmap中map->format.reg_bytes = DIV_ROUND_UP(config->reg_bits, 8);map->format.pad_bytes = config->pad_bits / 8;map->format.val_bytes = DIV_ROUND_UP(config->val_bits, 8);map->format.buf_size = DIV_ROUND_UP(config->reg_bits +config->val_bits + config->pad_bits, 8);map->reg_shift = config->pad_bits % 8;if (config->reg_stride)map->reg_stride = config->reg_stride;elsemap->reg_stride = 1;map->use_single_rw = config->use_single_rw;map->can_multi_write = config->can_multi_write;map->dev = dev;map->bus = bus;map->bus_context = bus_context;map->max_register = config->max_register;map->wr_table = config->wr_table;map->rd_table = config->rd_table;map->volatile_table = config->volatile_table;map->precious_table = config->precious_table;map->writeable_reg = config->writeable_reg;map->readable_reg = config->readable_reg;map->volatile_reg = config->volatile_reg;map->precious_reg = config->precious_reg;map->cache_type = config->cache_type;map->name = config->name;/* regmap中有reg_read操作方法,通过bus是否为空赋值,对于该驱动,此处只会设置map->reg_read为_regmap_bus_read */if (!bus) {map->reg_read  = config->reg_read;map->reg_write = config->reg_write;map->defer_caching = false;goto skip_format_initialization;} else if (!bus->read || !bus->write) {map->reg_read = _regmap_bus_reg_read;map->reg_write = _regmap_bus_reg_write;map->defer_caching = false;goto skip_format_initialization;} else {map->reg_read  = _regmap_bus_read;}// 设置地址和寄存器值的大小端reg_endian = regmap_get_reg_endian(bus, config);val_endian = regmap_get_val_endian(dev, bus, config);// 根据寄存器地址位宽和大小端解析寄存器地址switch (config->reg_bits + map->reg_shift) {case 32:switch (reg_endian) {case REGMAP_ENDIAN_BIG:map->format.format_reg = regmap_format_32_be;break;case REGMAP_ENDIAN_NATIVE:map->format.format_reg = regmap_format_32_native;break;default:goto err_map;}break;}// 根据寄存器值位宽和大小端解析寄存器值switch (config->val_bits) {case 16:switch (val_endian) {case REGMAP_ENDIAN_BIG:map->format.format_val = regmap_format_16_be;map->format.parse_val = regmap_parse_16_be;map->format.parse_inplace = regmap_parse_16_be_inplace;break;case REGMAP_ENDIAN_LITTLE:map->format.format_val = regmap_format_16_le;map->format.parse_val = regmap_parse_16_le;map->format.parse_inplace = regmap_parse_16_le_inplace;break;case REGMAP_ENDIAN_NATIVE:map->format.format_val = regmap_format_16_native;map->format.parse_val = regmap_parse_16_native;break;default:goto err_map;}break;}/* 对于val_bits = 16,reg_bits=16,regmap写函数选择_regmap_bus_raw_write */if (map->format.format_write) {map->defer_caching = false;map->reg_write = _regmap_bus_formatted_write;} else if (map->format.format_val) {map->defer_caching = true;map->reg_write = _regmap_bus_raw_write;}// 缓存初始化ret = regcache_init(map, config);
}

regmap_init()函数初始化了regmap,regmap中函数bus指针和config寄存器参数,已经足够用来操作I2C的寄存器了。reg_read回调函数是我们在读数据要使用的方法,我们需要看其实现:

map->reg_read  = _regmap_bus_read;static int _regmap_bus_read(void *context, unsigned int reg,unsigned int *val)
{int ret;struct regmap *map = context;if (!map->format.parse_val)return -EINVAL;// 读出寄存器数据ret = _regmap_raw_read(map, reg, map->work_buf, map->format.val_bytes);if (ret == 0) // 根据寄存器位值宽和大小端得到寄存器值*val = map->format.parse_val(map->work_buf);return ret;
}static int _regmap_raw_read(struct regmap *map, unsigned int reg, void *val,unsigned int val_len)
{int ret;// 使用bus定义的read函数指针读数据ret = map->bus->read(map->bus_context, map->work_buf,map->format.reg_bytes + map->format.pad_bytes,val, val_len);return ret;
}               

3.regmap_read()

regmap_read函数用来读取寄存器值,实现如下:

int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val)
{int ret;// 通过寄存器操作宽度判断寄存器操作是否合法if (reg % map->reg_stride)return -EINVAL;map->lock(map->lock_arg);ret = _regmap_read(map, reg, val);map->unlock(map->lock_arg);return ret;
}static int _regmap_read(struct regmap *map, unsigned int reg,unsigned int *val)
{// 1.直接从缓存取值if (!map->cache_bypass) {ret = regcache_read(map, reg, val);if (ret == 0)return 0;}// 2.最终调用bus定义的read读寄存器数据ret = map->reg_read(context, reg, val);// 3.将寄存器结果写入缓存if (!map->cache_bypass)regcache_write(map, reg, *val);return ret;
}

regmap_read函数最后调用bus的read函数,同理,regmap_write函数最终也会调用bus的write函数。

关于缓冲,需要解释的是,在regmap中加入了一层缓存,减少IO操作次数,提供硬件操作效率。

在Linux 4.0 版本中,已经有 3 种缓存类型,分别是数组(flat)、LZO 压缩和红黑树(rbtree)。数组好理解,是最简单的缓存类型,当设备寄存器很少时,可以用这种类型来缓存寄存器值。LZO(Lempel–Ziv–Oberhumer) 是 Linux 中经常用到的一种压缩算法,Linux 编译后就会用这个算法来压缩。这个算法有 3 个特性:压缩快,解压不需要额外内存,压缩比可以自动调节。在这里,你可以理解为一个数组缓存,套了一层压缩,来节约内存。当设备寄存器数量中等时,可以考虑这种缓存类型。而最后一类红黑树,它的特性就是索引快,所以当设备寄存器数量比较大,或者对寄存器操作延时要求低时,就可以用这种缓存类型。

到此,在驱动程序中regmap的基本使用方法和调用简析就结束了。

Linux I2C设备regmap机制简析相关推荐

  1. linux下i2c设备驱动程序,Linux I2C 设备驱动

    I2C 设备驱动要使用 i2c_driver 和 i2c_client 数据结构并填充其中的成员函数.i2c_client 一般被包含在设备的私有信息结构体yyy_data 中,而 i2c_drive ...

  2. 手把手教你写Linux I2C设备驱动

    手把手教你写Linux I2C设备驱动 标签:Linux 设备 驱动 详解 i2c 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http:/ ...

  3. linux探测i2c设备连接状态,手把手教你写Linux I2C设备驱动

    Linux I2C驱动是嵌入式Linux驱动开发人员经常需要编写的一种驱动,因为凡是系统中使用到的I2C设备,几乎都需要编写相应的I2C驱动去配置和控制它,例如 RTC实时时钟芯片.音视频采集芯片.音 ...

  4. 手把手教你写Linux I2C设备驱动 tvp5158

    Linux I2C驱动是嵌入式Linux驱动开发人员经常需要编写的一种驱动,因为凡是系统中使用到的I2C设备,几乎都需要编写相应的I2C驱动去配置和控制它,例如 RTC实时时钟芯片.音视频采集芯片.音 ...

  5. linux i2c detect函数,手把手教你写Linux I2C设备驱动

    Linux I2C驱动是嵌入式Linux驱动开发人员经常需要编写的一种驱动,因为凡是系统中使用到的I2C设备,几乎都需要编写相应的I2C驱动去配置和控制它,例如 RTC实时时钟芯片.音视频采集芯片.音 ...

  6. Linux添加一个i2c设备,手把手教你写Linux I2C设备驱动

    Linux I2C驱动是嵌入式Linux驱动开发人员经常需要编写的一种驱动,因为凡是系统中使用到的I2C设备,几乎都需要编写相应的I2C驱动去配置和控制它,例如 RTC实时时钟芯片.音视频采集芯片.音 ...

  7. linux I2C设备驱动

    linux内核的总线-设备-驱动模型,当总线上的设备与总线上的驱动匹配时,就会调用驱动的probe函数,完成一系列的操作 I2C也是内核的一种总线 一.I2C设备的4种构建方法 1.静态注册设备 (1 ...

  8. linux 脚本给设备节点权限,[Linux] I2C设备读写及文件节点创建

    Linux Kernel Version:3.0.35 Platform:Freescale DSA2L 通过I2C读取VGA屏的EDID信息(主要是分辨率),解析后喂给CH7036芯片(LVDS转V ...

  9. linux i2c 设备节点读写

    最近需要操作24C02,封装了一下函数方便以后操作. 参考链接: https://my.oschina.net/handawei/blog/68526 http://blog.csdn.net/one ...

最新文章

  1. invalid floating point operation什么意思_数据可视化有意思的小例子:Taylor Swift 歌词数据分析和可视化...
  2. 使用ABP打造SAAS系统(2)——前端框架选择
  3. tensorflow出现报错AttributeError: module ‘tensorflow.python.keras.backend‘ has no attribute ‘get_graph‘
  4. java 正则判断二进制_regex – 正则表达式,用于定义一些二进制序列
  5. 苹果无人车四个最新专利:手势控制变道、车辆导流、路况感知及车辆控制
  6. python 中的 [-1::1] 啥意思
  7. 基于MATLAB的路径规划算法
  8. 麦克风声源定位原理_麦克风阵列原理及应用
  9. Python初级入门精讲-王大鹏-专题视频课程
  10. MongoDB 认证、添加用户、用户权限控制
  11. cas112-27-6|三乙二醇/二缩三乙二醇/三甘醇|三乙二醇 透明液体
  12. 未来WiFi技术新方向:传输、覆盖、能耗
  13. 180522 安卓-DDCTF2018(RSA)
  14. 数学建模:线性规划及 Python 求解
  15. FlexRay学习笔记_2
  16. 苹果邮箱怎么登录qq邮箱_gmail邮箱登录官网方法
  17. 爱克发胶片_GE/AGFA爱克发工业胶片系统
  18. CAD转换器,CAD图纸转换JPG图片
  19. 如何修改jar包,并且能够运行,实操有效!
  20. Fiddler + 海马模拟器转包教程

热门文章

  1. 基于proteus的51单片机仿真实例二、关于proteus
  2. 《安富莱嵌入式周报》第268期:2022.05.30--2022.06.05
  3. latex 精准调整控制表格每一行之间的行距
  4. Android Wear与Apple Watch交互设计对比
  5. 转载于掘金的vue3学习笔记
  6. esp分区中的EFI启动项文件有什么用
  7. 【原创】PE检测工具
  8. 《护理教育学》名词解释、简答题、问答题汇总
  9. 学游戏原画都需要掌握哪些软件
  10. Swift - 设置UILabel、UITextView的文字行间距