diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig index 0f6c7fb..590d72c 100644 --- a/drivers/base/regmap/Kconfig +++ b/drivers/base/regmap/Kconfig @@ -16,3 +16,6 @@ config REGMAP_SPI config REGMAP_IRQ bool + +config REGMAP_PAGING + bool diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index fcafc5b..b7931e1 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -31,6 +31,18 @@ struct regmap_format { unsigned int (*parse_val)(void *buf); }; +#ifdef CONFIG_REGMAP_PAGING +struct regmap_paging { + unsigned int page_mask; + unsigned int reg_mask; + unsigned int reg_bits; + bool (*pageable_reg)(struct device *dev, unsigned int reg); + unsigned int sel_reg; + int sel_shift; + unsigned int sel_mask; +}; +#endif + struct regmap { struct mutex lock; @@ -79,6 +91,10 @@ struct regmap { struct reg_default *patch; int patch_regs; + +#ifdef CONFIG_REGMAP_PAGING + struct regmap_paging page; +#endif }; struct regcache_ops { diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 7a3f535..45794cc 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -80,6 +80,24 @@ static bool regmap_volatile_range(struct regmap *map, unsigned int reg, return true; } +#ifdef CONFIG_REGMAP_PAGING +static bool regmap_pageable_range(struct regmap *map, unsigned int reg, + unsigned int num) +{ + if (map->page.pageable_reg) { + unsigned int i; + + for (i = 0; i < num; i++) + if (map->page.pageable_reg(map->dev, reg)) + return true; + + return false; + } + + return true; +} +#endif + static void regmap_format_2_6_write(struct regmap *map, unsigned int reg, unsigned int val) { @@ -199,6 +217,17 @@ struct regmap *regmap_init(struct device *dev, map->volatile_reg = config->volatile_reg; map->precious_reg = config->precious_reg; map->cache_type = config->cache_type; +#ifdef CONFIG_REGMAP_PAGING + map->page.page_mask = ((1 << config->page_bits) - 1) << config->reg_bits; + map->page.reg_mask = (1 << config->reg_bits) - 1; + map->page.reg_bits = config->reg_bits; + map->page.pageable_reg = config->pageable_reg; + map->page.sel_reg = config->page_sel_reg; + map->page.sel_shift = config->page_sel_shift; + map->page.sel_mask = ((1 << config->page_bits) - 1) << + config->page_sel_shift; +#endif + if (config->read_flag_mask || config->write_flag_mask) { map->read_flag_mask = config->read_flag_mask; @@ -396,41 +425,66 @@ void regmap_exit(struct regmap *map) } EXPORT_SYMBOL_GPL(regmap_exit); -static int _regmap_raw_write(struct regmap *map, unsigned int reg, - const void *val, size_t val_len) -{ - u8 *u8 = map->work_buf; - void *buf; - int ret = -ENOTSUPP; - size_t len; - int i; +static int _regmap_update_bits(struct regmap *map, unsigned int reg, + unsigned int mask, unsigned int val, + bool *change); - /* Check for unwritable registers before we start */ - if (map->writeable_reg) - for (i = 0; i < val_len / map->format.val_bytes; i++) - if (!map->writeable_reg(map->dev, reg + i)) - return -EINVAL; +#ifdef CONFIG_REGMAP_PAGING +static int _regmap_paged_access(struct regmap *map, + int (*regmap_bus_access)(struct regmap *map, unsigned int reg, + void *val, unsigned int val_len), + unsigned int reg, void *val, unsigned int val_len) +{ + unsigned int val_num = val_len / map->format.val_bytes; + u8 *_val = val; + unsigned int _reg = reg & map->page.reg_mask; + unsigned int _page = reg >> map->page.reg_bits; + unsigned int _num; + size_t _len; + bool change; + int ret; - if (!map->cache_bypass && map->format.parse_val) { - unsigned int ival; - int val_bytes = map->format.val_bytes; - for (i = 0; i < val_len / val_bytes; i++) { - memcpy(map->work_buf, val + (i * val_bytes), val_bytes); - ival = map->format.parse_val(map->work_buf); - ret = regcache_write(map, reg + i, ival); - if (ret) { - dev_err(map->dev, - "Error in caching of register: %u ret: %d\n", - reg + i, ret); + /* Split write into separate blocks, one for each page */ + while (val_num) { + if ((_reg + val_num - 1) >> map->page.reg_bits != 0) + _num = (map->page.reg_mask - _reg + 1); + else + _num = val_num; + + if (regmap_pageable_range(map, _reg, _num)) { + /* Update page selector, try to use cache. + Page selector is in nonpageable register, so it is + safe to reenter update function here. */ + ret = _regmap_update_bits(map, + map->page.sel_reg, + map->page.sel_mask, + _page << map->page.sel_shift, + &change); + if (ret < 0) return ret; - } - } - if (map->cache_only) { - map->cache_dirty = true; - return 0; } + + _len = _num * map->format.val_bytes; + ret = regmap_bus_access(map, _reg, _val, _len); + if (ret < 0) + return ret; + + val_num -= _num; + _val += _len; + _reg = 0; + _page++; } + return 0; +} +#endif /* CONFIG_REGMAP_PAGING */ + +static int _regmap_bus_write(struct regmap *map, unsigned int reg, + void *val, size_t val_len) +{ + u8 *u8 = map->work_buf; + int ret = -ENOTSUPP; + map->format.format_reg(map->work_buf, reg); u8[0] |= map->write_flag_mask; @@ -456,6 +510,9 @@ static int _regmap_raw_write(struct regmap *map, unsigned int reg, /* If that didn't work fall back on linearising by hand. */ if (ret == -ENOTSUPP) { + void *buf; + size_t len; + len = map->format.reg_bytes + map->format.pad_bytes + val_len; buf = kzalloc(len, GFP_KERNEL); if (!buf) @@ -475,6 +532,52 @@ static int _regmap_raw_write(struct regmap *map, unsigned int reg, return ret; } +static int _regmap_raw_write(struct regmap *map, unsigned int reg, + const void *val, size_t val_len) +{ + void *_val = (void *)val; + int i; + int ret; + + /* Check for unwritable registers before we start */ + if (map->writeable_reg) + for (i = 0; i < val_len / map->format.val_bytes; i++) + if (!map->writeable_reg(map->dev, reg + i)) + return -EINVAL; + + if (!map->cache_bypass && map->format.parse_val) { + unsigned int ival; + int val_bytes = map->format.val_bytes; + for (i = 0; i < val_len / map->format.val_bytes; i++) { + memcpy(map->work_buf, val + (i * val_bytes), val_bytes); + ival = map->format.parse_val(map->work_buf); + ret = regcache_write(map, reg + i, ival); + if (ret) { + dev_err(map->dev, + "Error in caching of register: %u ret: %d\n", + reg + i, ret); + return ret; + } + } + if (map->cache_only) { + map->cache_dirty = true; + return 0; + } + } + +#ifdef CONFIG_REGMAP_PAGING + /* Maintain paging, if enabled and register is page dependent */ + if (map->page.page_mask && + !(map->page.sel_reg == reg && + val_len == map->format.val_bytes)) { + return _regmap_paged_access(map, &_regmap_bus_write, + reg, _val, val_len); + } +#endif /* CONFIG_REGMAP_PAGING */ + + return _regmap_bus_write(map, reg, _val, val_len); +} + int _regmap_write(struct regmap *map, unsigned int reg, unsigned int val) { @@ -494,6 +597,28 @@ int _regmap_write(struct regmap *map, unsigned int reg, trace_regmap_reg_write(map->dev, reg, val); if (map->format.format_write) { +#ifdef CONFIG_REGMAP_PAGING + /* Maintain paging, if enabled and register is page dependent */ + if (map->page.page_mask && + map->page.sel_reg != reg && + (!map->page.pageable_reg || + map->page.pageable_reg(map->dev, reg))) { + unsigned int page = reg >> map->page.reg_bits; + bool change; + + /* Update page selector, when needed. + Page selector is in nonpageable register, so it is + safe to reenter for its update in here. */ + ret = _regmap_update_bits(map, + map->page.sel_reg, + map->page.sel_mask, + page << map->page.sel_shift, + &change); + if (ret < 0) + return ret; + } +#endif + map->format.format_write(map, reg, val); trace_regmap_hw_write_start(map->dev, reg, 1); @@ -620,7 +745,7 @@ out: } EXPORT_SYMBOL_GPL(regmap_bulk_write); -static int _regmap_raw_read(struct regmap *map, unsigned int reg, void *val, +static int _regmap_bus_read(struct regmap *map, unsigned int reg, void *val, unsigned int val_len) { u8 *u8 = map->work_buf; @@ -649,6 +774,21 @@ static int _regmap_raw_read(struct regmap *map, unsigned int reg, void *val, return ret; } +static int _regmap_raw_read(struct regmap *map, unsigned int reg, void *val, + unsigned int val_len) +{ +#ifdef CONFIG_REGMAP_PAGING + /* Maintain paging, if enabled and register is page dependent */ + if (map->page.page_mask && + !(map->page.sel_reg == reg && + val_len == map->format.val_bytes)) + return _regmap_paged_access(map, &_regmap_bus_read, + reg, val, val_len); +#endif /* CONFIG_REGMAP_PAGING */ + + return _regmap_bus_read(map, reg, val, val_len); +} + static int _regmap_read(struct regmap *map, unsigned int reg, unsigned int *val) { @@ -792,11 +932,9 @@ static int _regmap_update_bits(struct regmap *map, unsigned int reg, int ret; unsigned int tmp, orig; - mutex_lock(&map->lock); - ret = _regmap_read(map, reg, &orig); if (ret != 0) - goto out; + return ret; tmp = orig & ~mask; tmp |= val & mask; @@ -808,9 +946,6 @@ static int _regmap_update_bits(struct regmap *map, unsigned int reg, *change = false; } -out: - mutex_unlock(&map->lock); - return ret; } @@ -828,7 +963,12 @@ int regmap_update_bits(struct regmap *map, unsigned int reg, unsigned int mask, unsigned int val) { bool change; - return _regmap_update_bits(map, reg, mask, val, &change); + int ret; + + mutex_lock(&map->lock); + ret = _regmap_update_bits(map, reg, mask, val, &change); + mutex_unlock(&map->lock); + return ret; } EXPORT_SYMBOL_GPL(regmap_update_bits); @@ -848,7 +988,12 @@ int regmap_update_bits_check(struct regmap *map, unsigned int reg, unsigned int mask, unsigned int val, bool *change) { - return _regmap_update_bits(map, reg, mask, val, change); + int ret; + + mutex_lock(&map->lock); + ret = _regmap_update_bits(map, reg, mask, val, change); + mutex_unlock(&map->lock); + return ret; } EXPORT_SYMBOL_GPL(regmap_update_bits_check); diff --git a/include/linux/regmap.h b/include/linux/regmap.h index a90abb6..e4f9db0 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -50,6 +50,14 @@ struct reg_default { * @pad_bits: Number of bits of padding between register and value. * @val_bits: Number of bits in a register value, mandatory. * + * @page_bits: Number of address top-most bits designated to be page number. + * These bits will be written to page selector, for page switch. + * @page_sel_reg: Register to update selected page in (available on any page). + * @page_sel_shift: Bit shift for page selector inside the register. + * @pageable_reg: Optional callback returning true, if a register needs + * page switching (ie. it is page dependent). Register + * in page_sel_reg is always considered as page independent. + * * @writeable_reg: Optional callback returning true if the register * can be written to. * @readable_reg: Optional callback returning true if the register @@ -81,6 +89,13 @@ struct regmap_config { int pad_bits; int val_bits; +#ifdef CONFIG_REGMAP_PAGING + int page_bits; + unsigned int page_sel_reg; + int page_sel_shift; + bool (*pageable_reg)(struct device *dev, unsigned int reg); +#endif + 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);