Merge branch 'for-linus' of git://opensource.wolfsonmicro.com/regmap
* 'for-linus' of git://opensource.wolfsonmicro.com/regmap: (62 commits) mfd: Enable rbtree cache for wm831x devices regmap: Support some block operations on cached devices regmap: Allow caches for devices with no defaults regmap: Ensure rbtree syncs registers set to zero properly regmap: Allow rbtree to cache zero default values regmap: Warn on raw I/O as well as bulk reads that bypass cache regmap: Return a sensible error code if we fail to read the cache regmap: Use bsearch() to search the register defaults regmap: Fix doc comment regmap: Optimize the lookup path to use binary search regmap: Ensure we scream if we enable cache bypass/only at the same time regmap: Implement regcache_cache_bypass helper function regmap: Save/restore the bypass state upon syncing regmap: Lock the sync path, ensure we use the lockless _regmap_write() regmap: Fix apostrophe usage regmap: Make _regmap_write() global regmap: Fix lock used for regcache_cache_only() regmap: Grab the lock in regcache_cache_only() regmap: Modify map->cache_bypass directly regmap: Fix regcache_sync generic implementation ...
This commit is contained in:
@@ -4,6 +4,8 @@
|
||||
|
||||
config REGMAP
|
||||
default y if (REGMAP_I2C || REGMAP_SPI)
|
||||
select LZO_COMPRESS
|
||||
select LZO_DECOMPRESS
|
||||
bool
|
||||
|
||||
config REGMAP_I2C
|
||||
|
@@ -1,3 +1,4 @@
|
||||
obj-$(CONFIG_REGMAP) += regmap.o
|
||||
obj-$(CONFIG_REGMAP) += regmap.o regcache.o regcache-indexed.o regcache-rbtree.o regcache-lzo.o
|
||||
obj-$(CONFIG_DEBUG_FS) += regmap-debugfs.o
|
||||
obj-$(CONFIG_REGMAP_I2C) += regmap-i2c.o
|
||||
obj-$(CONFIG_REGMAP_SPI) += regmap-spi.o
|
||||
|
128
drivers/base/regmap/internal.h
Normal file
128
drivers/base/regmap/internal.h
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Register map access API internal header
|
||||
*
|
||||
* Copyright 2011 Wolfson Microelectronics plc
|
||||
*
|
||||
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef _REGMAP_INTERNAL_H
|
||||
#define _REGMAP_INTERNAL_H
|
||||
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/fs.h>
|
||||
|
||||
struct regmap;
|
||||
struct regcache_ops;
|
||||
|
||||
struct regmap_format {
|
||||
size_t buf_size;
|
||||
size_t reg_bytes;
|
||||
size_t val_bytes;
|
||||
void (*format_write)(struct regmap *map,
|
||||
unsigned int reg, unsigned int val);
|
||||
void (*format_reg)(void *buf, unsigned int reg);
|
||||
void (*format_val)(void *buf, unsigned int val);
|
||||
unsigned int (*parse_val)(void *buf);
|
||||
};
|
||||
|
||||
struct regmap {
|
||||
struct mutex lock;
|
||||
|
||||
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;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
struct dentry *debugfs;
|
||||
#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);
|
||||
|
||||
u8 read_flag_mask;
|
||||
u8 write_flag_mask;
|
||||
|
||||
/* 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 */
|
||||
unsigned int cache_only:1;
|
||||
/* if set, only the HW is modified not the cache */
|
||||
unsigned int cache_bypass:1;
|
||||
/* if set, remember to free reg_defaults_raw */
|
||||
unsigned int cache_free:1;
|
||||
|
||||
struct reg_default *reg_defaults;
|
||||
const void *reg_defaults_raw;
|
||||
void *cache;
|
||||
};
|
||||
|
||||
struct regcache_ops {
|
||||
const char *name;
|
||||
enum regcache_type type;
|
||||
int (*init)(struct regmap *map);
|
||||
int (*exit)(struct regmap *map);
|
||||
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);
|
||||
};
|
||||
|
||||
bool regmap_writeable(struct regmap *map, unsigned int reg);
|
||||
bool regmap_readable(struct regmap *map, unsigned int reg);
|
||||
bool regmap_volatile(struct regmap *map, unsigned int reg);
|
||||
bool regmap_precious(struct regmap *map, unsigned int reg);
|
||||
|
||||
int _regmap_write(struct regmap *map, unsigned int reg,
|
||||
unsigned int val);
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
extern void regmap_debugfs_initcall(void);
|
||||
extern void regmap_debugfs_init(struct regmap *map);
|
||||
extern void regmap_debugfs_exit(struct regmap *map);
|
||||
#else
|
||||
static inline void regmap_debugfs_initcall(void) { }
|
||||
static inline void regmap_debugfs_init(struct regmap *map) { }
|
||||
static inline void regmap_debugfs_exit(struct regmap *map) { }
|
||||
#endif
|
||||
|
||||
/* regcache core declarations */
|
||||
int regcache_init(struct regmap *map);
|
||||
void regcache_exit(struct regmap *map);
|
||||
int regcache_read(struct regmap *map,
|
||||
unsigned int reg, unsigned int *value);
|
||||
int regcache_write(struct regmap *map,
|
||||
unsigned int reg, unsigned int value);
|
||||
int regcache_sync(struct regmap *map);
|
||||
|
||||
unsigned int regcache_get_val(const void *base, unsigned int idx,
|
||||
unsigned int word_size);
|
||||
bool regcache_set_val(void *base, unsigned int idx,
|
||||
unsigned int val, unsigned int word_size);
|
||||
int regcache_lookup_reg(struct regmap *map, unsigned int reg);
|
||||
int regcache_insert_reg(struct regmap *map, unsigned int reg,
|
||||
unsigned int val);
|
||||
|
||||
extern struct regcache_ops regcache_indexed_ops;
|
||||
extern struct regcache_ops regcache_rbtree_ops;
|
||||
extern struct regcache_ops regcache_lzo_ops;
|
||||
|
||||
#endif
|
64
drivers/base/regmap/regcache-indexed.c
Normal file
64
drivers/base/regmap/regcache-indexed.c
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Register cache access API - indexed caching support
|
||||
*
|
||||
* Copyright 2011 Wolfson Microelectronics plc
|
||||
*
|
||||
* Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
static int regcache_indexed_read(struct regmap *map, unsigned int reg,
|
||||
unsigned int *value)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = regcache_lookup_reg(map, reg);
|
||||
if (ret >= 0)
|
||||
*value = map->reg_defaults[ret].def;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int regcache_indexed_write(struct regmap *map, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = regcache_lookup_reg(map, reg);
|
||||
if (ret < 0)
|
||||
return regcache_insert_reg(map, reg, value);
|
||||
map->reg_defaults[ret].def = value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int regcache_indexed_sync(struct regmap *map)
|
||||
{
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
for (i = 0; i < map->num_reg_defaults; i++) {
|
||||
ret = _regmap_write(map, map->reg_defaults[i].reg,
|
||||
map->reg_defaults[i].def);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
dev_dbg(map->dev, "Synced register %#x, value %#x\n",
|
||||
map->reg_defaults[i].reg,
|
||||
map->reg_defaults[i].def);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct regcache_ops regcache_indexed_ops = {
|
||||
.type = REGCACHE_INDEXED,
|
||||
.name = "indexed",
|
||||
.read = regcache_indexed_read,
|
||||
.write = regcache_indexed_write,
|
||||
.sync = regcache_indexed_sync
|
||||
};
|
361
drivers/base/regmap/regcache-lzo.c
Normal file
361
drivers/base/regmap/regcache-lzo.c
Normal file
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
* Register cache access API - LZO caching support
|
||||
*
|
||||
* Copyright 2011 Wolfson Microelectronics plc
|
||||
*
|
||||
* Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/lzo.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
struct regcache_lzo_ctx {
|
||||
void *wmem;
|
||||
void *dst;
|
||||
const void *src;
|
||||
size_t src_len;
|
||||
size_t dst_len;
|
||||
size_t decompressed_size;
|
||||
unsigned long *sync_bmp;
|
||||
int sync_bmp_nbits;
|
||||
};
|
||||
|
||||
#define LZO_BLOCK_NUM 8
|
||||
static int regcache_lzo_block_count(void)
|
||||
{
|
||||
return LZO_BLOCK_NUM;
|
||||
}
|
||||
|
||||
static int regcache_lzo_prepare(struct regcache_lzo_ctx *lzo_ctx)
|
||||
{
|
||||
lzo_ctx->wmem = kmalloc(LZO1X_MEM_COMPRESS, GFP_KERNEL);
|
||||
if (!lzo_ctx->wmem)
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int regcache_lzo_compress(struct regcache_lzo_ctx *lzo_ctx)
|
||||
{
|
||||
size_t compress_size;
|
||||
int ret;
|
||||
|
||||
ret = lzo1x_1_compress(lzo_ctx->src, lzo_ctx->src_len,
|
||||
lzo_ctx->dst, &compress_size, lzo_ctx->wmem);
|
||||
if (ret != LZO_E_OK || compress_size > lzo_ctx->dst_len)
|
||||
return -EINVAL;
|
||||
lzo_ctx->dst_len = compress_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int regcache_lzo_decompress(struct regcache_lzo_ctx *lzo_ctx)
|
||||
{
|
||||
size_t dst_len;
|
||||
int ret;
|
||||
|
||||
dst_len = lzo_ctx->dst_len;
|
||||
ret = lzo1x_decompress_safe(lzo_ctx->src, lzo_ctx->src_len,
|
||||
lzo_ctx->dst, &dst_len);
|
||||
if (ret != LZO_E_OK || dst_len != lzo_ctx->dst_len)
|
||||
return -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int regcache_lzo_compress_cache_block(struct regmap *map,
|
||||
struct regcache_lzo_ctx *lzo_ctx)
|
||||
{
|
||||
int ret;
|
||||
|
||||
lzo_ctx->dst_len = lzo1x_worst_compress(PAGE_SIZE);
|
||||
lzo_ctx->dst = kmalloc(lzo_ctx->dst_len, GFP_KERNEL);
|
||||
if (!lzo_ctx->dst) {
|
||||
lzo_ctx->dst_len = 0;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = regcache_lzo_compress(lzo_ctx);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int regcache_lzo_decompress_cache_block(struct regmap *map,
|
||||
struct regcache_lzo_ctx *lzo_ctx)
|
||||
{
|
||||
int ret;
|
||||
|
||||
lzo_ctx->dst_len = lzo_ctx->decompressed_size;
|
||||
lzo_ctx->dst = kmalloc(lzo_ctx->dst_len, GFP_KERNEL);
|
||||
if (!lzo_ctx->dst) {
|
||||
lzo_ctx->dst_len = 0;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = regcache_lzo_decompress(lzo_ctx);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int regcache_lzo_get_blkindex(struct regmap *map,
|
||||
unsigned int reg)
|
||||
{
|
||||
return (reg * map->cache_word_size) /
|
||||
DIV_ROUND_UP(map->cache_size_raw, regcache_lzo_block_count());
|
||||
}
|
||||
|
||||
static inline int regcache_lzo_get_blkpos(struct regmap *map,
|
||||
unsigned int reg)
|
||||
{
|
||||
return reg % (DIV_ROUND_UP(map->cache_size_raw, regcache_lzo_block_count()) /
|
||||
map->cache_word_size);
|
||||
}
|
||||
|
||||
static inline int regcache_lzo_get_blksize(struct regmap *map)
|
||||
{
|
||||
return DIV_ROUND_UP(map->cache_size_raw, regcache_lzo_block_count());
|
||||
}
|
||||
|
||||
static int regcache_lzo_init(struct regmap *map)
|
||||
{
|
||||
struct regcache_lzo_ctx **lzo_blocks;
|
||||
size_t bmp_size;
|
||||
int ret, i, blksize, blkcount;
|
||||
const char *p, *end;
|
||||
unsigned long *sync_bmp;
|
||||
|
||||
ret = 0;
|
||||
|
||||
blkcount = regcache_lzo_block_count();
|
||||
map->cache = kzalloc(blkcount * sizeof *lzo_blocks,
|
||||
GFP_KERNEL);
|
||||
if (!map->cache)
|
||||
return -ENOMEM;
|
||||
lzo_blocks = map->cache;
|
||||
|
||||
/*
|
||||
* allocate a bitmap to be used when syncing the cache with
|
||||
* the hardware. Each time a register is modified, the corresponding
|
||||
* bit is set in the bitmap, so we know that we have to sync
|
||||
* that register.
|
||||
*/
|
||||
bmp_size = map->num_reg_defaults_raw;
|
||||
sync_bmp = kmalloc(BITS_TO_LONGS(bmp_size) * sizeof(long),
|
||||
GFP_KERNEL);
|
||||
if (!sync_bmp) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
bitmap_zero(sync_bmp, bmp_size);
|
||||
|
||||
/* allocate the lzo blocks and initialize them */
|
||||
for (i = 0; i < blkcount; i++) {
|
||||
lzo_blocks[i] = kzalloc(sizeof **lzo_blocks,
|
||||
GFP_KERNEL);
|
||||
if (!lzo_blocks[i]) {
|
||||
kfree(sync_bmp);
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
lzo_blocks[i]->sync_bmp = sync_bmp;
|
||||
lzo_blocks[i]->sync_bmp_nbits = bmp_size;
|
||||
/* alloc the working space for the compressed block */
|
||||
ret = regcache_lzo_prepare(lzo_blocks[i]);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
}
|
||||
|
||||
blksize = regcache_lzo_get_blksize(map);
|
||||
p = map->reg_defaults_raw;
|
||||
end = map->reg_defaults_raw + map->cache_size_raw;
|
||||
/* compress the register map and fill the lzo blocks */
|
||||
for (i = 0; i < blkcount; i++, p += blksize) {
|
||||
lzo_blocks[i]->src = p;
|
||||
if (p + blksize > end)
|
||||
lzo_blocks[i]->src_len = end - p;
|
||||
else
|
||||
lzo_blocks[i]->src_len = blksize;
|
||||
ret = regcache_lzo_compress_cache_block(map,
|
||||
lzo_blocks[i]);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
lzo_blocks[i]->decompressed_size =
|
||||
lzo_blocks[i]->src_len;
|
||||
}
|
||||
|
||||
return 0;
|
||||
err:
|
||||
regcache_exit(map);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int regcache_lzo_exit(struct regmap *map)
|
||||
{
|
||||
struct regcache_lzo_ctx **lzo_blocks;
|
||||
int i, blkcount;
|
||||
|
||||
lzo_blocks = map->cache;
|
||||
if (!lzo_blocks)
|
||||
return 0;
|
||||
|
||||
blkcount = regcache_lzo_block_count();
|
||||
/*
|
||||
* the pointer to the bitmap used for syncing the cache
|
||||
* is shared amongst all lzo_blocks. Ensure it is freed
|
||||
* only once.
|
||||
*/
|
||||
if (lzo_blocks[0])
|
||||
kfree(lzo_blocks[0]->sync_bmp);
|
||||
for (i = 0; i < blkcount; i++) {
|
||||
if (lzo_blocks[i]) {
|
||||
kfree(lzo_blocks[i]->wmem);
|
||||
kfree(lzo_blocks[i]->dst);
|
||||
}
|
||||
/* each lzo_block is a pointer returned by kmalloc or NULL */
|
||||
kfree(lzo_blocks[i]);
|
||||
}
|
||||
kfree(lzo_blocks);
|
||||
map->cache = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int regcache_lzo_read(struct regmap *map,
|
||||
unsigned int reg, unsigned int *value)
|
||||
{
|
||||
struct regcache_lzo_ctx *lzo_block, **lzo_blocks;
|
||||
int ret, blkindex, blkpos;
|
||||
size_t blksize, tmp_dst_len;
|
||||
void *tmp_dst;
|
||||
|
||||
/* index of the compressed lzo block */
|
||||
blkindex = regcache_lzo_get_blkindex(map, reg);
|
||||
/* register index within the decompressed block */
|
||||
blkpos = regcache_lzo_get_blkpos(map, reg);
|
||||
/* size of the compressed block */
|
||||
blksize = regcache_lzo_get_blksize(map);
|
||||
lzo_blocks = map->cache;
|
||||
lzo_block = lzo_blocks[blkindex];
|
||||
|
||||
/* save the pointer and length of the compressed block */
|
||||
tmp_dst = lzo_block->dst;
|
||||
tmp_dst_len = lzo_block->dst_len;
|
||||
|
||||
/* prepare the source to be the compressed block */
|
||||
lzo_block->src = lzo_block->dst;
|
||||
lzo_block->src_len = lzo_block->dst_len;
|
||||
|
||||
/* decompress the block */
|
||||
ret = regcache_lzo_decompress_cache_block(map, lzo_block);
|
||||
if (ret >= 0)
|
||||
/* fetch the value from the cache */
|
||||
*value = regcache_get_val(lzo_block->dst, blkpos,
|
||||
map->cache_word_size);
|
||||
|
||||
kfree(lzo_block->dst);
|
||||
/* restore the pointer and length of the compressed block */
|
||||
lzo_block->dst = tmp_dst;
|
||||
lzo_block->dst_len = tmp_dst_len;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int regcache_lzo_write(struct regmap *map,
|
||||
unsigned int reg, unsigned int value)
|
||||
{
|
||||
struct regcache_lzo_ctx *lzo_block, **lzo_blocks;
|
||||
int ret, blkindex, blkpos;
|
||||
size_t blksize, tmp_dst_len;
|
||||
void *tmp_dst;
|
||||
|
||||
/* index of the compressed lzo block */
|
||||
blkindex = regcache_lzo_get_blkindex(map, reg);
|
||||
/* register index within the decompressed block */
|
||||
blkpos = regcache_lzo_get_blkpos(map, reg);
|
||||
/* size of the compressed block */
|
||||
blksize = regcache_lzo_get_blksize(map);
|
||||
lzo_blocks = map->cache;
|
||||
lzo_block = lzo_blocks[blkindex];
|
||||
|
||||
/* save the pointer and length of the compressed block */
|
||||
tmp_dst = lzo_block->dst;
|
||||
tmp_dst_len = lzo_block->dst_len;
|
||||
|
||||
/* prepare the source to be the compressed block */
|
||||
lzo_block->src = lzo_block->dst;
|
||||
lzo_block->src_len = lzo_block->dst_len;
|
||||
|
||||
/* decompress the block */
|
||||
ret = regcache_lzo_decompress_cache_block(map, lzo_block);
|
||||
if (ret < 0) {
|
||||
kfree(lzo_block->dst);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* write the new value to the cache */
|
||||
if (regcache_set_val(lzo_block->dst, blkpos, value,
|
||||
map->cache_word_size)) {
|
||||
kfree(lzo_block->dst);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* prepare the source to be the decompressed block */
|
||||
lzo_block->src = lzo_block->dst;
|
||||
lzo_block->src_len = lzo_block->dst_len;
|
||||
|
||||
/* compress the block */
|
||||
ret = regcache_lzo_compress_cache_block(map, lzo_block);
|
||||
if (ret < 0) {
|
||||
kfree(lzo_block->dst);
|
||||
kfree(lzo_block->src);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* set the bit so we know we have to sync this register */
|
||||
set_bit(reg, lzo_block->sync_bmp);
|
||||
kfree(tmp_dst);
|
||||
kfree(lzo_block->src);
|
||||
return 0;
|
||||
out:
|
||||
lzo_block->dst = tmp_dst;
|
||||
lzo_block->dst_len = tmp_dst_len;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int regcache_lzo_sync(struct regmap *map)
|
||||
{
|
||||
struct regcache_lzo_ctx **lzo_blocks;
|
||||
unsigned int val;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
lzo_blocks = map->cache;
|
||||
for_each_set_bit(i, lzo_blocks[0]->sync_bmp, lzo_blocks[0]->sync_bmp_nbits) {
|
||||
ret = regcache_read(map, i, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
map->cache_bypass = 1;
|
||||
ret = _regmap_write(map, i, val);
|
||||
map->cache_bypass = 0;
|
||||
if (ret)
|
||||
return ret;
|
||||
dev_dbg(map->dev, "Synced register %#x, value %#x\n",
|
||||
i, val);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct regcache_ops regcache_lzo_ops = {
|
||||
.type = REGCACHE_LZO,
|
||||
.name = "lzo",
|
||||
.init = regcache_lzo_init,
|
||||
.exit = regcache_lzo_exit,
|
||||
.read = regcache_lzo_read,
|
||||
.write = regcache_lzo_write,
|
||||
.sync = regcache_lzo_sync
|
||||
};
|
345
drivers/base/regmap/regcache-rbtree.c
Normal file
345
drivers/base/regmap/regcache-rbtree.c
Normal file
@@ -0,0 +1,345 @@
|
||||
/*
|
||||
* Register cache access API - rbtree caching support
|
||||
*
|
||||
* Copyright 2011 Wolfson Microelectronics plc
|
||||
*
|
||||
* Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/rbtree.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
static int regcache_rbtree_write(struct regmap *map, unsigned int reg,
|
||||
unsigned int value);
|
||||
|
||||
struct regcache_rbtree_node {
|
||||
/* the actual rbtree node holding this block */
|
||||
struct rb_node node;
|
||||
/* base register handled by this block */
|
||||
unsigned int base_reg;
|
||||
/* block of adjacent registers */
|
||||
void *block;
|
||||
/* number of registers available in the block */
|
||||
unsigned int blklen;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct regcache_rbtree_ctx {
|
||||
struct rb_root root;
|
||||
struct regcache_rbtree_node *cached_rbnode;
|
||||
};
|
||||
|
||||
static inline void regcache_rbtree_get_base_top_reg(
|
||||
struct regcache_rbtree_node *rbnode,
|
||||
unsigned int *base, unsigned int *top)
|
||||
{
|
||||
*base = rbnode->base_reg;
|
||||
*top = rbnode->base_reg + rbnode->blklen - 1;
|
||||
}
|
||||
|
||||
static unsigned int regcache_rbtree_get_register(
|
||||
struct regcache_rbtree_node *rbnode, unsigned int idx,
|
||||
unsigned int word_size)
|
||||
{
|
||||
return regcache_get_val(rbnode->block, idx, word_size);
|
||||
}
|
||||
|
||||
static void regcache_rbtree_set_register(struct regcache_rbtree_node *rbnode,
|
||||
unsigned int idx, unsigned int val,
|
||||
unsigned int word_size)
|
||||
{
|
||||
regcache_set_val(rbnode->block, idx, val, word_size);
|
||||
}
|
||||
|
||||
static struct regcache_rbtree_node *regcache_rbtree_lookup(struct regmap *map,
|
||||
unsigned int reg)
|
||||
{
|
||||
struct regcache_rbtree_ctx *rbtree_ctx = map->cache;
|
||||
struct rb_node *node;
|
||||
struct regcache_rbtree_node *rbnode;
|
||||
unsigned int base_reg, top_reg;
|
||||
|
||||
rbnode = rbtree_ctx->cached_rbnode;
|
||||
if (rbnode) {
|
||||
regcache_rbtree_get_base_top_reg(rbnode, &base_reg, &top_reg);
|
||||
if (reg >= base_reg && reg <= top_reg)
|
||||
return rbnode;
|
||||
}
|
||||
|
||||
node = rbtree_ctx->root.rb_node;
|
||||
while (node) {
|
||||
rbnode = container_of(node, struct regcache_rbtree_node, node);
|
||||
regcache_rbtree_get_base_top_reg(rbnode, &base_reg, &top_reg);
|
||||
if (reg >= base_reg && reg <= top_reg) {
|
||||
rbtree_ctx->cached_rbnode = rbnode;
|
||||
return rbnode;
|
||||
} else if (reg > top_reg) {
|
||||
node = node->rb_right;
|
||||
} else if (reg < base_reg) {
|
||||
node = node->rb_left;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int regcache_rbtree_insert(struct rb_root *root,
|
||||
struct regcache_rbtree_node *rbnode)
|
||||
{
|
||||
struct rb_node **new, *parent;
|
||||
struct regcache_rbtree_node *rbnode_tmp;
|
||||
unsigned int base_reg_tmp, top_reg_tmp;
|
||||
unsigned int base_reg;
|
||||
|
||||
parent = NULL;
|
||||
new = &root->rb_node;
|
||||
while (*new) {
|
||||
rbnode_tmp = container_of(*new, struct regcache_rbtree_node,
|
||||
node);
|
||||
/* base and top registers of the current rbnode */
|
||||
regcache_rbtree_get_base_top_reg(rbnode_tmp, &base_reg_tmp,
|
||||
&top_reg_tmp);
|
||||
/* base register of the rbnode to be added */
|
||||
base_reg = rbnode->base_reg;
|
||||
parent = *new;
|
||||
/* if this register has already been inserted, just return */
|
||||
if (base_reg >= base_reg_tmp &&
|
||||
base_reg <= top_reg_tmp)
|
||||
return 0;
|
||||
else if (base_reg > top_reg_tmp)
|
||||
new = &((*new)->rb_right);
|
||||
else if (base_reg < base_reg_tmp)
|
||||
new = &((*new)->rb_left);
|
||||
}
|
||||
|
||||
/* insert the node into the rbtree */
|
||||
rb_link_node(&rbnode->node, parent, new);
|
||||
rb_insert_color(&rbnode->node, root);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int regcache_rbtree_init(struct regmap *map)
|
||||
{
|
||||
struct regcache_rbtree_ctx *rbtree_ctx;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
map->cache = kmalloc(sizeof *rbtree_ctx, GFP_KERNEL);
|
||||
if (!map->cache)
|
||||
return -ENOMEM;
|
||||
|
||||
rbtree_ctx = map->cache;
|
||||
rbtree_ctx->root = RB_ROOT;
|
||||
rbtree_ctx->cached_rbnode = NULL;
|
||||
|
||||
for (i = 0; i < map->num_reg_defaults; i++) {
|
||||
ret = regcache_rbtree_write(map,
|
||||
map->reg_defaults[i].reg,
|
||||
map->reg_defaults[i].def);
|
||||
if (ret)
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
regcache_exit(map);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int regcache_rbtree_exit(struct regmap *map)
|
||||
{
|
||||
struct rb_node *next;
|
||||
struct regcache_rbtree_ctx *rbtree_ctx;
|
||||
struct regcache_rbtree_node *rbtree_node;
|
||||
|
||||
/* if we've already been called then just return */
|
||||
rbtree_ctx = map->cache;
|
||||
if (!rbtree_ctx)
|
||||
return 0;
|
||||
|
||||
/* free up the rbtree */
|
||||
next = rb_first(&rbtree_ctx->root);
|
||||
while (next) {
|
||||
rbtree_node = rb_entry(next, struct regcache_rbtree_node, node);
|
||||
next = rb_next(&rbtree_node->node);
|
||||
rb_erase(&rbtree_node->node, &rbtree_ctx->root);
|
||||
kfree(rbtree_node->block);
|
||||
kfree(rbtree_node);
|
||||
}
|
||||
|
||||
/* release the resources */
|
||||
kfree(map->cache);
|
||||
map->cache = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int regcache_rbtree_read(struct regmap *map,
|
||||
unsigned int reg, unsigned int *value)
|
||||
{
|
||||
struct regcache_rbtree_node *rbnode;
|
||||
unsigned int reg_tmp;
|
||||
|
||||
rbnode = regcache_rbtree_lookup(map, reg);
|
||||
if (rbnode) {
|
||||
reg_tmp = reg - rbnode->base_reg;
|
||||
*value = regcache_rbtree_get_register(rbnode, reg_tmp,
|
||||
map->cache_word_size);
|
||||
} else {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int regcache_rbtree_insert_to_block(struct regcache_rbtree_node *rbnode,
|
||||
unsigned int pos, unsigned int reg,
|
||||
unsigned int value, unsigned int word_size)
|
||||
{
|
||||
u8 *blk;
|
||||
|
||||
blk = krealloc(rbnode->block,
|
||||
(rbnode->blklen + 1) * word_size, GFP_KERNEL);
|
||||
if (!blk)
|
||||
return -ENOMEM;
|
||||
|
||||
/* insert the register value in the correct place in the rbnode block */
|
||||
memmove(blk + (pos + 1) * word_size,
|
||||
blk + pos * word_size,
|
||||
(rbnode->blklen - pos) * word_size);
|
||||
|
||||
/* update the rbnode block, its size and the base register */
|
||||
rbnode->block = blk;
|
||||
rbnode->blklen++;
|
||||
if (!pos)
|
||||
rbnode->base_reg = reg;
|
||||
|
||||
regcache_rbtree_set_register(rbnode, pos, value, word_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int regcache_rbtree_write(struct regmap *map, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
struct regcache_rbtree_ctx *rbtree_ctx;
|
||||
struct regcache_rbtree_node *rbnode, *rbnode_tmp;
|
||||
struct rb_node *node;
|
||||
unsigned int val;
|
||||
unsigned int reg_tmp;
|
||||
unsigned int pos;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
rbtree_ctx = map->cache;
|
||||
/* if we can't locate it in the cached rbnode we'll have
|
||||
* to traverse the rbtree looking for it.
|
||||
*/
|
||||
rbnode = regcache_rbtree_lookup(map, reg);
|
||||
if (rbnode) {
|
||||
reg_tmp = reg - rbnode->base_reg;
|
||||
val = regcache_rbtree_get_register(rbnode, reg_tmp,
|
||||
map->cache_word_size);
|
||||
if (val == value)
|
||||
return 0;
|
||||
regcache_rbtree_set_register(rbnode, reg_tmp, value,
|
||||
map->cache_word_size);
|
||||
} else {
|
||||
/* look for an adjacent register to the one we are about to add */
|
||||
for (node = rb_first(&rbtree_ctx->root); node;
|
||||
node = rb_next(node)) {
|
||||
rbnode_tmp = rb_entry(node, struct regcache_rbtree_node, node);
|
||||
for (i = 0; i < rbnode_tmp->blklen; i++) {
|
||||
reg_tmp = rbnode_tmp->base_reg + i;
|
||||
if (abs(reg_tmp - reg) != 1)
|
||||
continue;
|
||||
/* decide where in the block to place our register */
|
||||
if (reg_tmp + 1 == reg)
|
||||
pos = i + 1;
|
||||
else
|
||||
pos = i;
|
||||
ret = regcache_rbtree_insert_to_block(rbnode_tmp, pos,
|
||||
reg, value,
|
||||
map->cache_word_size);
|
||||
if (ret)
|
||||
return ret;
|
||||
rbtree_ctx->cached_rbnode = rbnode_tmp;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/* we did not manage to find a place to insert it in an existing
|
||||
* block so create a new rbnode with a single register in its block.
|
||||
* This block will get populated further if any other adjacent
|
||||
* registers get modified in the future.
|
||||
*/
|
||||
rbnode = kzalloc(sizeof *rbnode, GFP_KERNEL);
|
||||
if (!rbnode)
|
||||
return -ENOMEM;
|
||||
rbnode->blklen = 1;
|
||||
rbnode->base_reg = reg;
|
||||
rbnode->block = kmalloc(rbnode->blklen * map->cache_word_size,
|
||||
GFP_KERNEL);
|
||||
if (!rbnode->block) {
|
||||
kfree(rbnode);
|
||||
return -ENOMEM;
|
||||
}
|
||||
regcache_rbtree_set_register(rbnode, 0, value, map->cache_word_size);
|
||||
regcache_rbtree_insert(&rbtree_ctx->root, rbnode);
|
||||
rbtree_ctx->cached_rbnode = rbnode;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int regcache_rbtree_sync(struct regmap *map)
|
||||
{
|
||||
struct regcache_rbtree_ctx *rbtree_ctx;
|
||||
struct rb_node *node;
|
||||
struct regcache_rbtree_node *rbnode;
|
||||
unsigned int regtmp;
|
||||
unsigned int val;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
rbtree_ctx = map->cache;
|
||||
for (node = rb_first(&rbtree_ctx->root); node; node = rb_next(node)) {
|
||||
rbnode = rb_entry(node, struct regcache_rbtree_node, node);
|
||||
for (i = 0; i < rbnode->blklen; i++) {
|
||||
regtmp = rbnode->base_reg + i;
|
||||
val = regcache_rbtree_get_register(rbnode, i,
|
||||
map->cache_word_size);
|
||||
|
||||
/* Is this the hardware default? If so skip. */
|
||||
ret = regcache_lookup_reg(map, i);
|
||||
if (ret > 0 && val == map->reg_defaults[ret].def)
|
||||
continue;
|
||||
|
||||
map->cache_bypass = 1;
|
||||
ret = _regmap_write(map, regtmp, val);
|
||||
map->cache_bypass = 0;
|
||||
if (ret)
|
||||
return ret;
|
||||
dev_dbg(map->dev, "Synced register %#x, value %#x\n",
|
||||
regtmp, val);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct regcache_ops regcache_rbtree_ops = {
|
||||
.type = REGCACHE_RBTREE,
|
||||
.name = "rbtree",
|
||||
.init = regcache_rbtree_init,
|
||||
.exit = regcache_rbtree_exit,
|
||||
.read = regcache_rbtree_read,
|
||||
.write = regcache_rbtree_write,
|
||||
.sync = regcache_rbtree_sync
|
||||
};
|
401
drivers/base/regmap/regcache.c
Normal file
401
drivers/base/regmap/regcache.c
Normal file
@@ -0,0 +1,401 @@
|
||||
/*
|
||||
* Register cache access API
|
||||
*
|
||||
* Copyright 2011 Wolfson Microelectronics plc
|
||||
*
|
||||
* Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <trace/events/regmap.h>
|
||||
#include <linux/bsearch.h>
|
||||
#include <linux/sort.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
static const struct regcache_ops *cache_types[] = {
|
||||
®cache_indexed_ops,
|
||||
®cache_rbtree_ops,
|
||||
®cache_lzo_ops,
|
||||
};
|
||||
|
||||
static int regcache_hw_init(struct regmap *map)
|
||||
{
|
||||
int i, j;
|
||||
int ret;
|
||||
int count;
|
||||
unsigned int val;
|
||||
void *tmp_buf;
|
||||
|
||||
if (!map->num_reg_defaults_raw)
|
||||
return -EINVAL;
|
||||
|
||||
if (!map->reg_defaults_raw) {
|
||||
dev_warn(map->dev, "No cache defaults, reading back from HW\n");
|
||||
tmp_buf = kmalloc(map->cache_size_raw, GFP_KERNEL);
|
||||
if (!tmp_buf)
|
||||
return -EINVAL;
|
||||
ret = regmap_bulk_read(map, 0, tmp_buf,
|
||||
map->num_reg_defaults_raw);
|
||||
if (ret < 0) {
|
||||
kfree(tmp_buf);
|
||||
return ret;
|
||||
}
|
||||
map->reg_defaults_raw = tmp_buf;
|
||||
map->cache_free = 1;
|
||||
}
|
||||
|
||||
/* calculate the size of reg_defaults */
|
||||
for (count = 0, i = 0; i < map->num_reg_defaults_raw; i++) {
|
||||
val = regcache_get_val(map->reg_defaults_raw,
|
||||
i, map->cache_word_size);
|
||||
if (!val)
|
||||
continue;
|
||||
count++;
|
||||
}
|
||||
|
||||
map->reg_defaults = kmalloc(count * sizeof(struct reg_default),
|
||||
GFP_KERNEL);
|
||||
if (!map->reg_defaults)
|
||||
return -ENOMEM;
|
||||
|
||||
/* fill the reg_defaults */
|
||||
map->num_reg_defaults = count;
|
||||
for (i = 0, j = 0; i < map->num_reg_defaults_raw; i++) {
|
||||
val = regcache_get_val(map->reg_defaults_raw,
|
||||
i, map->cache_word_size);
|
||||
if (!val)
|
||||
continue;
|
||||
map->reg_defaults[j].reg = i;
|
||||
map->reg_defaults[j].def = val;
|
||||
j++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int regcache_init(struct regmap *map)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
void *tmp_buf;
|
||||
|
||||
if (map->cache_type == REGCACHE_NONE) {
|
||||
map->cache_bypass = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(cache_types); i++)
|
||||
if (cache_types[i]->type == map->cache_type)
|
||||
break;
|
||||
|
||||
if (i == ARRAY_SIZE(cache_types)) {
|
||||
dev_err(map->dev, "Could not match compress type: %d\n",
|
||||
map->cache_type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
map->cache = NULL;
|
||||
map->cache_ops = cache_types[i];
|
||||
|
||||
if (!map->cache_ops->read ||
|
||||
!map->cache_ops->write ||
|
||||
!map->cache_ops->name)
|
||||
return -EINVAL;
|
||||
|
||||
/* We still need to ensure that the reg_defaults
|
||||
* won't vanish from under us. We'll need to make
|
||||
* a copy of it.
|
||||
*/
|
||||
if (map->reg_defaults) {
|
||||
if (!map->num_reg_defaults)
|
||||
return -EINVAL;
|
||||
tmp_buf = kmemdup(map->reg_defaults, map->num_reg_defaults *
|
||||
sizeof(struct reg_default), GFP_KERNEL);
|
||||
if (!tmp_buf)
|
||||
return -ENOMEM;
|
||||
map->reg_defaults = tmp_buf;
|
||||
} else if (map->num_reg_defaults_raw) {
|
||||
/* Some devices such as PMICs don't have cache defaults,
|
||||
* we cope with this by reading back the HW registers and
|
||||
* crafting the cache defaults by hand.
|
||||
*/
|
||||
ret = regcache_hw_init(map);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!map->max_register)
|
||||
map->max_register = map->num_reg_defaults_raw;
|
||||
|
||||
if (map->cache_ops->init) {
|
||||
dev_dbg(map->dev, "Initializing %s cache\n",
|
||||
map->cache_ops->name);
|
||||
return map->cache_ops->init(map);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void regcache_exit(struct regmap *map)
|
||||
{
|
||||
if (map->cache_type == REGCACHE_NONE)
|
||||
return;
|
||||
|
||||
BUG_ON(!map->cache_ops);
|
||||
|
||||
kfree(map->reg_defaults);
|
||||
if (map->cache_free)
|
||||
kfree(map->reg_defaults_raw);
|
||||
|
||||
if (map->cache_ops->exit) {
|
||||
dev_dbg(map->dev, "Destroying %s cache\n",
|
||||
map->cache_ops->name);
|
||||
map->cache_ops->exit(map);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* regcache_read: Fetch the value of a given register from the cache.
|
||||
*
|
||||
* @map: map to configure.
|
||||
* @reg: The register index.
|
||||
* @value: The value to be returned.
|
||||
*
|
||||
* Return a negative value on failure, 0 on success.
|
||||
*/
|
||||
int regcache_read(struct regmap *map,
|
||||
unsigned int reg, unsigned int *value)
|
||||
{
|
||||
if (map->cache_type == REGCACHE_NONE)
|
||||
return -ENOSYS;
|
||||
|
||||
BUG_ON(!map->cache_ops);
|
||||
|
||||
if (!regmap_readable(map, reg))
|
||||
return -EIO;
|
||||
|
||||
if (!regmap_volatile(map, reg))
|
||||
return map->cache_ops->read(map, reg, value);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regcache_read);
|
||||
|
||||
/**
|
||||
* regcache_write: Set the value of a given register in the cache.
|
||||
*
|
||||
* @map: map to configure.
|
||||
* @reg: The register index.
|
||||
* @value: The new register value.
|
||||
*
|
||||
* Return a negative value on failure, 0 on success.
|
||||
*/
|
||||
int regcache_write(struct regmap *map,
|
||||
unsigned int reg, unsigned int value)
|
||||
{
|
||||
if (map->cache_type == REGCACHE_NONE)
|
||||
return 0;
|
||||
|
||||
BUG_ON(!map->cache_ops);
|
||||
|
||||
if (!regmap_writeable(map, reg))
|
||||
return -EIO;
|
||||
|
||||
if (!regmap_volatile(map, reg))
|
||||
return map->cache_ops->write(map, reg, value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regcache_write);
|
||||
|
||||
/**
|
||||
* regcache_sync: Sync the register cache with the hardware.
|
||||
*
|
||||
* @map: map to configure.
|
||||
*
|
||||
* Any registers that should not be synced should be marked as
|
||||
* volatile. In general drivers can choose not to use the provided
|
||||
* syncing functionality if they so require.
|
||||
*
|
||||
* Return a negative value on failure, 0 on success.
|
||||
*/
|
||||
int regcache_sync(struct regmap *map)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned int val;
|
||||
unsigned int i;
|
||||
const char *name;
|
||||
unsigned int bypass;
|
||||
|
||||
BUG_ON(!map->cache_ops);
|
||||
|
||||
mutex_lock(&map->lock);
|
||||
/* Remember the initial bypass state */
|
||||
bypass = map->cache_bypass;
|
||||
dev_dbg(map->dev, "Syncing %s cache\n",
|
||||
map->cache_ops->name);
|
||||
name = map->cache_ops->name;
|
||||
trace_regcache_sync(map->dev, name, "start");
|
||||
if (map->cache_ops->sync) {
|
||||
ret = map->cache_ops->sync(map);
|
||||
} else {
|
||||
for (i = 0; i < map->num_reg_defaults; i++) {
|
||||
ret = regcache_read(map, i, &val);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
map->cache_bypass = 1;
|
||||
ret = _regmap_write(map, i, val);
|
||||
map->cache_bypass = 0;
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
dev_dbg(map->dev, "Synced register %#x, value %#x\n",
|
||||
map->reg_defaults[i].reg,
|
||||
map->reg_defaults[i].def);
|
||||
}
|
||||
|
||||
}
|
||||
out:
|
||||
trace_regcache_sync(map->dev, name, "stop");
|
||||
/* Restore the bypass state */
|
||||
map->cache_bypass = bypass;
|
||||
mutex_unlock(&map->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regcache_sync);
|
||||
|
||||
/**
|
||||
* regcache_cache_only: Put a register map into cache only mode
|
||||
*
|
||||
* @map: map to configure
|
||||
* @cache_only: flag if changes should be written to the hardware
|
||||
*
|
||||
* When a register map is marked as cache only writes to the register
|
||||
* map API will only update the register cache, they will not cause
|
||||
* any hardware changes. This is useful for allowing portions of
|
||||
* drivers to act as though the device were functioning as normal when
|
||||
* it is disabled for power saving reasons.
|
||||
*/
|
||||
void regcache_cache_only(struct regmap *map, bool enable)
|
||||
{
|
||||
mutex_lock(&map->lock);
|
||||
WARN_ON(map->cache_bypass && enable);
|
||||
map->cache_only = enable;
|
||||
mutex_unlock(&map->lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regcache_cache_only);
|
||||
|
||||
/**
|
||||
* regcache_cache_bypass: Put a register map into cache bypass mode
|
||||
*
|
||||
* @map: map to configure
|
||||
* @cache_bypass: flag if changes should not be written to the hardware
|
||||
*
|
||||
* When a register map is marked with the cache bypass option, writes
|
||||
* to the register map API will only update the hardware and not the
|
||||
* the cache directly. This is useful when syncing the cache back to
|
||||
* the hardware.
|
||||
*/
|
||||
void regcache_cache_bypass(struct regmap *map, bool enable)
|
||||
{
|
||||
mutex_lock(&map->lock);
|
||||
WARN_ON(map->cache_only && enable);
|
||||
map->cache_bypass = enable;
|
||||
mutex_unlock(&map->lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regcache_cache_bypass);
|
||||
|
||||
bool regcache_set_val(void *base, unsigned int idx,
|
||||
unsigned int val, unsigned int word_size)
|
||||
{
|
||||
switch (word_size) {
|
||||
case 1: {
|
||||
u8 *cache = base;
|
||||
if (cache[idx] == val)
|
||||
return true;
|
||||
cache[idx] = val;
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
u16 *cache = base;
|
||||
if (cache[idx] == val)
|
||||
return true;
|
||||
cache[idx] = val;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BUG();
|
||||
}
|
||||
/* unreachable */
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int regcache_get_val(const void *base, unsigned int idx,
|
||||
unsigned int word_size)
|
||||
{
|
||||
if (!base)
|
||||
return -EINVAL;
|
||||
|
||||
switch (word_size) {
|
||||
case 1: {
|
||||
const u8 *cache = base;
|
||||
return cache[idx];
|
||||
}
|
||||
case 2: {
|
||||
const u16 *cache = base;
|
||||
return cache[idx];
|
||||
}
|
||||
default:
|
||||
BUG();
|
||||
}
|
||||
/* unreachable */
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int regcache_default_cmp(const void *a, const void *b)
|
||||
{
|
||||
const struct reg_default *_a = a;
|
||||
const struct reg_default *_b = b;
|
||||
|
||||
return _a->reg - _b->reg;
|
||||
}
|
||||
|
||||
int regcache_lookup_reg(struct regmap *map, unsigned int reg)
|
||||
{
|
||||
struct reg_default key;
|
||||
struct reg_default *r;
|
||||
|
||||
key.reg = reg;
|
||||
key.def = 0;
|
||||
|
||||
r = bsearch(&key, map->reg_defaults, map->num_reg_defaults,
|
||||
sizeof(struct reg_default), regcache_default_cmp);
|
||||
|
||||
if (r)
|
||||
return r - map->reg_defaults;
|
||||
else
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
int regcache_insert_reg(struct regmap *map, unsigned int reg,
|
||||
unsigned int val)
|
||||
{
|
||||
void *tmp;
|
||||
|
||||
tmp = krealloc(map->reg_defaults,
|
||||
(map->num_reg_defaults + 1) * sizeof(struct reg_default),
|
||||
GFP_KERNEL);
|
||||
if (!tmp)
|
||||
return -ENOMEM;
|
||||
map->reg_defaults = tmp;
|
||||
map->num_reg_defaults++;
|
||||
map->reg_defaults[map->num_reg_defaults - 1].reg = reg;
|
||||
map->reg_defaults[map->num_reg_defaults - 1].def = val;
|
||||
sort(map->reg_defaults, map->num_reg_defaults,
|
||||
sizeof(struct reg_default), regcache_default_cmp, NULL);
|
||||
return 0;
|
||||
}
|
209
drivers/base/regmap/regmap-debugfs.c
Normal file
209
drivers/base/regmap/regmap-debugfs.c
Normal file
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Register map access API - debugfs
|
||||
*
|
||||
* Copyright 2011 Wolfson Microelectronics plc
|
||||
*
|
||||
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
static struct dentry *regmap_debugfs_root;
|
||||
|
||||
/* Calculate the length of a fixed format */
|
||||
static size_t regmap_calc_reg_len(int max_val, char *buf, size_t buf_size)
|
||||
{
|
||||
snprintf(buf, buf_size, "%x", max_val);
|
||||
return strlen(buf);
|
||||
}
|
||||
|
||||
static int regmap_open_file(struct inode *inode, struct file *file)
|
||||
{
|
||||
file->private_data = inode->i_private;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t regmap_map_read_file(struct file *file, char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
int reg_len, val_len, tot_len;
|
||||
size_t buf_pos = 0;
|
||||
loff_t p = 0;
|
||||
ssize_t ret;
|
||||
int i;
|
||||
struct regmap *map = file->private_data;
|
||||
char *buf;
|
||||
unsigned int val;
|
||||
|
||||
if (*ppos < 0 || !count)
|
||||
return -EINVAL;
|
||||
|
||||
buf = kmalloc(count, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Calculate the length of a fixed format */
|
||||
reg_len = regmap_calc_reg_len(map->max_register, buf, count);
|
||||
val_len = 2 * map->format.val_bytes;
|
||||
tot_len = reg_len + val_len + 3; /* : \n */
|
||||
|
||||
for (i = 0; i < map->max_register + 1; i++) {
|
||||
if (!regmap_readable(map, i))
|
||||
continue;
|
||||
|
||||
if (regmap_precious(map, i))
|
||||
continue;
|
||||
|
||||
/* If we're in the region the user is trying to read */
|
||||
if (p >= *ppos) {
|
||||
/* ...but not beyond it */
|
||||
if (buf_pos >= count - 1 - tot_len)
|
||||
break;
|
||||
|
||||
/* Format the register */
|
||||
snprintf(buf + buf_pos, count - buf_pos, "%.*x: ",
|
||||
reg_len, i);
|
||||
buf_pos += reg_len + 2;
|
||||
|
||||
/* Format the value, write all X if we can't read */
|
||||
ret = regmap_read(map, i, &val);
|
||||
if (ret == 0)
|
||||
snprintf(buf + buf_pos, count - buf_pos,
|
||||
"%.*x", val_len, val);
|
||||
else
|
||||
memset(buf + buf_pos, 'X', val_len);
|
||||
buf_pos += 2 * map->format.val_bytes;
|
||||
|
||||
buf[buf_pos++] = '\n';
|
||||
}
|
||||
p += tot_len;
|
||||
}
|
||||
|
||||
ret = buf_pos;
|
||||
|
||||
if (copy_to_user(user_buf, buf, buf_pos)) {
|
||||
ret = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
*ppos += buf_pos;
|
||||
|
||||
out:
|
||||
kfree(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct file_operations regmap_map_fops = {
|
||||
.open = regmap_open_file,
|
||||
.read = regmap_map_read_file,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
static ssize_t regmap_access_read_file(struct file *file,
|
||||
char __user *user_buf, size_t count,
|
||||
loff_t *ppos)
|
||||
{
|
||||
int reg_len, tot_len;
|
||||
size_t buf_pos = 0;
|
||||
loff_t p = 0;
|
||||
ssize_t ret;
|
||||
int i;
|
||||
struct regmap *map = file->private_data;
|
||||
char *buf;
|
||||
|
||||
if (*ppos < 0 || !count)
|
||||
return -EINVAL;
|
||||
|
||||
buf = kmalloc(count, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Calculate the length of a fixed format */
|
||||
reg_len = regmap_calc_reg_len(map->max_register, buf, count);
|
||||
tot_len = reg_len + 10; /* ': R W V P\n' */
|
||||
|
||||
for (i = 0; i < map->max_register + 1; i++) {
|
||||
/* Ignore registers which are neither readable nor writable */
|
||||
if (!regmap_readable(map, i) && !regmap_writeable(map, i))
|
||||
continue;
|
||||
|
||||
/* If we're in the region the user is trying to read */
|
||||
if (p >= *ppos) {
|
||||
/* ...but not beyond it */
|
||||
if (buf_pos >= count - 1 - tot_len)
|
||||
break;
|
||||
|
||||
/* Format the register */
|
||||
snprintf(buf + buf_pos, count - buf_pos,
|
||||
"%.*x: %c %c %c %c\n",
|
||||
reg_len, i,
|
||||
regmap_readable(map, i) ? 'y' : 'n',
|
||||
regmap_writeable(map, i) ? 'y' : 'n',
|
||||
regmap_volatile(map, i) ? 'y' : 'n',
|
||||
regmap_precious(map, i) ? 'y' : 'n');
|
||||
|
||||
buf_pos += tot_len;
|
||||
}
|
||||
p += tot_len;
|
||||
}
|
||||
|
||||
ret = buf_pos;
|
||||
|
||||
if (copy_to_user(user_buf, buf, buf_pos)) {
|
||||
ret = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
*ppos += buf_pos;
|
||||
|
||||
out:
|
||||
kfree(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct file_operations regmap_access_fops = {
|
||||
.open = regmap_open_file,
|
||||
.read = regmap_access_read_file,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
void regmap_debugfs_init(struct regmap *map)
|
||||
{
|
||||
map->debugfs = debugfs_create_dir(dev_name(map->dev),
|
||||
regmap_debugfs_root);
|
||||
if (!map->debugfs) {
|
||||
dev_warn(map->dev, "Failed to create debugfs directory\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (map->max_register) {
|
||||
debugfs_create_file("registers", 0400, map->debugfs,
|
||||
map, ®map_map_fops);
|
||||
debugfs_create_file("access", 0400, map->debugfs,
|
||||
map, ®map_access_fops);
|
||||
}
|
||||
}
|
||||
|
||||
void regmap_debugfs_exit(struct regmap *map)
|
||||
{
|
||||
debugfs_remove_recursive(map->debugfs);
|
||||
}
|
||||
|
||||
void regmap_debugfs_initcall(void)
|
||||
{
|
||||
regmap_debugfs_root = debugfs_create_dir("regmap", NULL);
|
||||
if (!regmap_debugfs_root) {
|
||||
pr_warn("regmap: Failed to create debugfs root\n");
|
||||
return;
|
||||
}
|
||||
}
|
@@ -90,11 +90,9 @@ static int regmap_i2c_read(struct device *dev,
|
||||
}
|
||||
|
||||
static struct regmap_bus regmap_i2c = {
|
||||
.type = &i2c_bus_type,
|
||||
.write = regmap_i2c_write,
|
||||
.gather_write = regmap_i2c_gather_write,
|
||||
.read = regmap_i2c_read,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -48,11 +48,9 @@ static int regmap_spi_read(struct device *dev,
|
||||
}
|
||||
|
||||
static struct regmap_bus regmap_spi = {
|
||||
.type = &spi_bus_type,
|
||||
.write = regmap_spi_write,
|
||||
.gather_write = regmap_spi_gather_write,
|
||||
.read = regmap_spi_read,
|
||||
.owner = THIS_MODULE,
|
||||
.read_flag_mask = 0x80,
|
||||
};
|
||||
|
||||
|
@@ -15,29 +15,54 @@
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
#include <linux/regmap.h>
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include <trace/events/regmap.h>
|
||||
|
||||
struct regmap;
|
||||
#include "internal.h"
|
||||
|
||||
struct regmap_format {
|
||||
size_t buf_size;
|
||||
size_t reg_bytes;
|
||||
size_t val_bytes;
|
||||
void (*format_write)(struct regmap *map,
|
||||
unsigned int reg, unsigned int val);
|
||||
void (*format_reg)(void *buf, unsigned int reg);
|
||||
void (*format_val)(void *buf, unsigned int val);
|
||||
unsigned int (*parse_val)(void *buf);
|
||||
};
|
||||
bool regmap_writeable(struct regmap *map, unsigned int reg)
|
||||
{
|
||||
if (map->max_register && reg > map->max_register)
|
||||
return false;
|
||||
|
||||
struct regmap {
|
||||
struct mutex lock;
|
||||
if (map->writeable_reg)
|
||||
return map->writeable_reg(map->dev, reg);
|
||||
|
||||
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;
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool regmap_readable(struct regmap *map, unsigned int reg)
|
||||
{
|
||||
if (map->max_register && reg > map->max_register)
|
||||
return false;
|
||||
|
||||
if (map->readable_reg)
|
||||
return map->readable_reg(map->dev, reg);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool regmap_volatile(struct regmap *map, unsigned int reg)
|
||||
{
|
||||
if (map->max_register && reg > map->max_register)
|
||||
return false;
|
||||
|
||||
if (map->volatile_reg)
|
||||
return map->volatile_reg(map->dev, reg);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool regmap_precious(struct regmap *map, unsigned int reg)
|
||||
{
|
||||
if (map->max_register && reg > map->max_register)
|
||||
return false;
|
||||
|
||||
if (map->precious_reg)
|
||||
return map->precious_reg(map->dev, reg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void regmap_format_4_12_write(struct regmap *map,
|
||||
unsigned int reg, unsigned int val)
|
||||
@@ -116,6 +141,25 @@ struct regmap *regmap_init(struct device *dev,
|
||||
map->format.val_bytes = config->val_bits / 8;
|
||||
map->dev = dev;
|
||||
map->bus = bus;
|
||||
map->max_register = config->max_register;
|
||||
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->reg_defaults = config->reg_defaults;
|
||||
map->num_reg_defaults = config->num_reg_defaults;
|
||||
map->num_reg_defaults_raw = config->num_reg_defaults_raw;
|
||||
map->reg_defaults_raw = config->reg_defaults_raw;
|
||||
map->cache_size_raw = (config->val_bits / 8) * config->num_reg_defaults_raw;
|
||||
map->cache_word_size = config->val_bits / 8;
|
||||
|
||||
if (config->read_flag_mask || config->write_flag_mask) {
|
||||
map->read_flag_mask = config->read_flag_mask;
|
||||
map->write_flag_mask = config->write_flag_mask;
|
||||
} else {
|
||||
map->read_flag_mask = bus->read_flag_mask;
|
||||
}
|
||||
|
||||
switch (config->reg_bits) {
|
||||
case 4:
|
||||
@@ -171,6 +215,12 @@ struct regmap *regmap_init(struct device *dev,
|
||||
goto err_map;
|
||||
}
|
||||
|
||||
ret = regcache_init(map);
|
||||
if (ret < 0)
|
||||
goto err_map;
|
||||
|
||||
regmap_debugfs_init(map);
|
||||
|
||||
return map;
|
||||
|
||||
err_map:
|
||||
@@ -185,6 +235,8 @@ EXPORT_SYMBOL_GPL(regmap_init);
|
||||
*/
|
||||
void regmap_exit(struct regmap *map)
|
||||
{
|
||||
regcache_exit(map);
|
||||
regmap_debugfs_exit(map);
|
||||
kfree(map->work_buf);
|
||||
kfree(map);
|
||||
}
|
||||
@@ -193,19 +245,38 @@ 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;
|
||||
|
||||
/* 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;
|
||||
|
||||
map->format.format_reg(map->work_buf, reg);
|
||||
|
||||
/* Try to do a gather write if we can */
|
||||
if (map->bus->gather_write)
|
||||
u8[0] |= map->write_flag_mask;
|
||||
|
||||
trace_regmap_hw_write_start(map->dev, reg,
|
||||
val_len / map->format.val_bytes);
|
||||
|
||||
/* If we're doing a single register write we can probably just
|
||||
* send the work_buf directly, otherwise try to do a gather
|
||||
* write.
|
||||
*/
|
||||
if (val == map->work_buf + map->format.reg_bytes)
|
||||
ret = map->bus->write(map->dev, map->work_buf,
|
||||
map->format.reg_bytes + val_len);
|
||||
else if (map->bus->gather_write)
|
||||
ret = map->bus->gather_write(map->dev, map->work_buf,
|
||||
map->format.reg_bytes,
|
||||
val, val_len);
|
||||
|
||||
/* Otherwise fall back on linearising by hand. */
|
||||
/* If that didn't work fall back on linearising by hand. */
|
||||
if (ret == -ENOTSUPP) {
|
||||
len = map->format.reg_bytes + val_len;
|
||||
buf = kmalloc(len, GFP_KERNEL);
|
||||
@@ -219,19 +290,39 @@ static int _regmap_raw_write(struct regmap *map, unsigned int reg,
|
||||
kfree(buf);
|
||||
}
|
||||
|
||||
trace_regmap_hw_write_done(map->dev, reg,
|
||||
val_len / map->format.val_bytes);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _regmap_write(struct regmap *map, unsigned int reg,
|
||||
unsigned int val)
|
||||
int _regmap_write(struct regmap *map, unsigned int reg,
|
||||
unsigned int val)
|
||||
{
|
||||
int ret;
|
||||
BUG_ON(!map->format.format_write && !map->format.format_val);
|
||||
|
||||
if (!map->cache_bypass) {
|
||||
ret = regcache_write(map, reg, val);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
if (map->cache_only)
|
||||
return 0;
|
||||
}
|
||||
|
||||
trace_regmap_reg_write(map->dev, reg, val);
|
||||
|
||||
if (map->format.format_write) {
|
||||
map->format.format_write(map, reg, val);
|
||||
|
||||
return map->bus->write(map->dev, map->work_buf,
|
||||
map->format.buf_size);
|
||||
trace_regmap_hw_write_start(map->dev, reg, 1);
|
||||
|
||||
ret = map->bus->write(map->dev, map->work_buf,
|
||||
map->format.buf_size);
|
||||
|
||||
trace_regmap_hw_write_done(map->dev, reg, 1);
|
||||
|
||||
return ret;
|
||||
} else {
|
||||
map->format.format_val(map->work_buf + map->format.reg_bytes,
|
||||
val);
|
||||
@@ -286,6 +377,8 @@ int regmap_raw_write(struct regmap *map, unsigned int reg,
|
||||
{
|
||||
int ret;
|
||||
|
||||
WARN_ON(map->cache_type != REGCACHE_NONE);
|
||||
|
||||
mutex_lock(&map->lock);
|
||||
|
||||
ret = _regmap_raw_write(map, reg, val, val_len);
|
||||
@@ -305,20 +398,23 @@ static int _regmap_raw_read(struct regmap *map, unsigned int reg, void *val,
|
||||
map->format.format_reg(map->work_buf, reg);
|
||||
|
||||
/*
|
||||
* Some buses flag reads by setting the high bits in the
|
||||
* Some buses or devices flag reads by setting the high bits in the
|
||||
* register addresss; since it's always the high bits for all
|
||||
* current formats we can do this here rather than in
|
||||
* formatting. This may break if we get interesting formats.
|
||||
*/
|
||||
if (map->bus->read_flag_mask)
|
||||
u8[0] |= map->bus->read_flag_mask;
|
||||
u8[0] |= map->read_flag_mask;
|
||||
|
||||
trace_regmap_hw_read_start(map->dev, reg,
|
||||
val_len / map->format.val_bytes);
|
||||
|
||||
ret = map->bus->read(map->dev, map->work_buf, map->format.reg_bytes,
|
||||
val, val_len);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
trace_regmap_hw_read_done(map->dev, reg,
|
||||
val_len / map->format.val_bytes);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _regmap_read(struct regmap *map, unsigned int reg,
|
||||
@@ -329,9 +425,20 @@ static int _regmap_read(struct regmap *map, unsigned int reg,
|
||||
if (!map->format.parse_val)
|
||||
return -EINVAL;
|
||||
|
||||
if (!map->cache_bypass) {
|
||||
ret = regcache_read(map, reg, val);
|
||||
if (ret == 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (map->cache_only)
|
||||
return -EBUSY;
|
||||
|
||||
ret = _regmap_raw_read(map, reg, map->work_buf, map->format.val_bytes);
|
||||
if (ret == 0)
|
||||
if (ret == 0) {
|
||||
*val = map->format.parse_val(map->work_buf);
|
||||
trace_regmap_reg_read(map->dev, reg, *val);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -375,6 +482,14 @@ int regmap_raw_read(struct regmap *map, unsigned int reg, void *val,
|
||||
size_t val_len)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
bool vol = true;
|
||||
|
||||
for (i = 0; i < val_len / map->format.val_bytes; i++)
|
||||
if (!regmap_volatile(map, reg + i))
|
||||
vol = false;
|
||||
|
||||
WARN_ON(!vol && map->cache_type != REGCACHE_NONE);
|
||||
|
||||
mutex_lock(&map->lock);
|
||||
|
||||
@@ -402,16 +517,30 @@ int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val,
|
||||
{
|
||||
int ret, i;
|
||||
size_t val_bytes = map->format.val_bytes;
|
||||
bool vol = true;
|
||||
|
||||
if (!map->format.parse_val)
|
||||
return -EINVAL;
|
||||
|
||||
ret = regmap_raw_read(map, reg, val, val_bytes * val_count);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
/* Is this a block of volatile registers? */
|
||||
for (i = 0; i < val_count; i++)
|
||||
if (!regmap_volatile(map, reg + i))
|
||||
vol = false;
|
||||
|
||||
for (i = 0; i < val_count * val_bytes; i += val_bytes)
|
||||
map->format.parse_val(val + i);
|
||||
if (vol || map->cache_type == REGCACHE_NONE) {
|
||||
ret = regmap_raw_read(map, reg, val, val_bytes * val_count);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < val_count * val_bytes; i += val_bytes)
|
||||
map->format.parse_val(val + i);
|
||||
} else {
|
||||
for (i = 0; i < val_count; i++) {
|
||||
ret = regmap_read(map, reg + i, val + (i * val_bytes));
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -450,3 +579,11 @@ out:
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regmap_update_bits);
|
||||
|
||||
static int __init regmap_initcall(void)
|
||||
{
|
||||
regmap_debugfs_initcall();
|
||||
|
||||
return 0;
|
||||
}
|
||||
postcore_initcall(regmap_initcall);
|
||||
|
Reference in New Issue
Block a user