mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-07 08:48:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
28
drivers/base/regmap/Kconfig
Normal file
28
drivers/base/regmap/Kconfig
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Generic register map support. There are no user servicable options here,
|
||||
# this is an API intended to be used by other kernel subsystems. These
|
||||
# subsystems should select the appropriate symbols.
|
||||
|
||||
config REGMAP
|
||||
default y if (REGMAP_I2C || REGMAP_SPI || REGMAP_SPMI || REGMAP_MMIO || REGMAP_IRQ)
|
||||
select LZO_COMPRESS
|
||||
select LZO_DECOMPRESS
|
||||
select IRQ_DOMAIN if REGMAP_IRQ
|
||||
bool
|
||||
|
||||
config REGMAP_I2C
|
||||
tristate
|
||||
depends on I2C
|
||||
|
||||
config REGMAP_SPI
|
||||
tristate
|
||||
depends on SPI
|
||||
|
||||
config REGMAP_SPMI
|
||||
tristate
|
||||
depends on SPMI
|
||||
|
||||
config REGMAP_MMIO
|
||||
tristate
|
||||
|
||||
config REGMAP_IRQ
|
||||
bool
|
8
drivers/base/regmap/Makefile
Normal file
8
drivers/base/regmap/Makefile
Normal file
|
@ -0,0 +1,8 @@
|
|||
obj-$(CONFIG_REGMAP) += regmap.o regcache.o
|
||||
obj-$(CONFIG_REGMAP) += regcache-rbtree.o regcache-lzo.o regcache-flat.o
|
||||
obj-$(CONFIG_DEBUG_FS) += regmap-debugfs.o
|
||||
obj-$(CONFIG_REGMAP_I2C) += regmap-i2c.o
|
||||
obj-$(CONFIG_REGMAP_SPI) += regmap-spi.o
|
||||
obj-$(CONFIG_REGMAP_SPMI) += regmap-spmi.o
|
||||
obj-$(CONFIG_REGMAP_MMIO) += regmap-mmio.o
|
||||
obj-$(CONFIG_REGMAP_IRQ) += regmap-irq.o
|
248
drivers/base/regmap/internal.h
Normal file
248
drivers/base/regmap/internal.h
Normal file
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
* 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>
|
||||
#include <linux/list.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
struct regmap;
|
||||
struct regcache_ops;
|
||||
|
||||
struct regmap_debugfs_off_cache {
|
||||
struct list_head list;
|
||||
off_t min;
|
||||
off_t max;
|
||||
unsigned int base_reg;
|
||||
unsigned int max_reg;
|
||||
};
|
||||
|
||||
struct regmap_format {
|
||||
size_t buf_size;
|
||||
size_t reg_bytes;
|
||||
size_t pad_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, unsigned int shift);
|
||||
void (*format_val)(void *buf, unsigned int val, unsigned int shift);
|
||||
unsigned int (*parse_val)(const void *buf);
|
||||
void (*parse_inplace)(void *buf);
|
||||
};
|
||||
|
||||
struct regmap_async {
|
||||
struct list_head list;
|
||||
struct regmap *map;
|
||||
void *work_buf;
|
||||
};
|
||||
|
||||
struct regmap {
|
||||
union {
|
||||
struct mutex mutex;
|
||||
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_FS
|
||||
struct 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;
|
||||
#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);
|
||||
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;
|
||||
u32 cache_dirty;
|
||||
|
||||
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 */
|
||||
};
|
||||
|
||||
struct regcache_ops {
|
||||
const char *name;
|
||||
enum regcache_type type;
|
||||
int (*init)(struct regmap *map);
|
||||
int (*exit)(struct regmap *map);
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
void (*debugfs_init)(struct regmap *map);
|
||||
#endif
|
||||
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, unsigned int min, unsigned int max);
|
||||
int (*drop)(struct regmap *map, unsigned int min, unsigned int max);
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
struct regmap_range_node {
|
||||
struct rb_node node;
|
||||
const char *name;
|
||||
struct regmap *map;
|
||||
|
||||
unsigned int range_min;
|
||||
unsigned int range_max;
|
||||
|
||||
unsigned int selector_reg;
|
||||
unsigned int selector_mask;
|
||||
int selector_shift;
|
||||
|
||||
unsigned int window_start;
|
||||
unsigned int window_len;
|
||||
};
|
||||
|
||||
struct regmap_field {
|
||||
struct regmap *regmap;
|
||||
unsigned int mask;
|
||||
/* lsb */
|
||||
unsigned int shift;
|
||||
unsigned int reg;
|
||||
|
||||
unsigned int id_size;
|
||||
unsigned int id_offset;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
extern void regmap_debugfs_initcall(void);
|
||||
extern void regmap_debugfs_init(struct regmap *map, const char *name);
|
||||
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, const char *name) { }
|
||||
static inline void regmap_debugfs_exit(struct regmap *map) { }
|
||||
#endif
|
||||
|
||||
/* regcache core declarations */
|
||||
int regcache_init(struct regmap *map, const struct regmap_config *config);
|
||||
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);
|
||||
int regcache_sync_block(struct regmap *map, void *block,
|
||||
unsigned long *cache_present,
|
||||
unsigned int block_base, unsigned int start,
|
||||
unsigned int end);
|
||||
|
||||
static inline const void *regcache_get_val_addr(struct regmap *map,
|
||||
const void *base,
|
||||
unsigned int idx)
|
||||
{
|
||||
return base + (map->cache_word_size * idx);
|
||||
}
|
||||
|
||||
unsigned int regcache_get_val(struct regmap *map, const void *base,
|
||||
unsigned int idx);
|
||||
bool regcache_set_val(struct regmap *map, void *base, unsigned int idx,
|
||||
unsigned int val);
|
||||
int regcache_lookup_reg(struct regmap *map, unsigned int reg);
|
||||
|
||||
int _regmap_raw_write(struct regmap *map, unsigned int reg,
|
||||
const void *val, size_t val_len);
|
||||
|
||||
void regmap_async_complete_cb(struct regmap_async *async, int ret);
|
||||
|
||||
extern struct regcache_ops regcache_rbtree_ops;
|
||||
extern struct regcache_ops regcache_lzo_ops;
|
||||
extern struct regcache_ops regcache_flat_ops;
|
||||
|
||||
static inline const char *regmap_name(const struct regmap *map)
|
||||
{
|
||||
if (map->dev)
|
||||
return dev_name(map->dev);
|
||||
|
||||
return map->name;
|
||||
}
|
||||
|
||||
#endif
|
72
drivers/base/regmap/regcache-flat.c
Normal file
72
drivers/base/regmap/regcache-flat.c
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Register cache access API - flat caching support
|
||||
*
|
||||
* Copyright 2012 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/device.h>
|
||||
#include <linux/seq_file.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
static int regcache_flat_init(struct regmap *map)
|
||||
{
|
||||
int i;
|
||||
unsigned int *cache;
|
||||
|
||||
map->cache = kzalloc(sizeof(unsigned int) * (map->max_register + 1),
|
||||
GFP_KERNEL);
|
||||
if (!map->cache)
|
||||
return -ENOMEM;
|
||||
|
||||
cache = map->cache;
|
||||
|
||||
for (i = 0; i < map->num_reg_defaults; i++)
|
||||
cache[map->reg_defaults[i].reg] = map->reg_defaults[i].def;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int regcache_flat_exit(struct regmap *map)
|
||||
{
|
||||
kfree(map->cache);
|
||||
map->cache = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int regcache_flat_read(struct regmap *map,
|
||||
unsigned int reg, unsigned int *value)
|
||||
{
|
||||
unsigned int *cache = map->cache;
|
||||
|
||||
*value = cache[reg];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int regcache_flat_write(struct regmap *map, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
unsigned int *cache = map->cache;
|
||||
|
||||
cache[reg] = value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct regcache_ops regcache_flat_ops = {
|
||||
.type = REGCACHE_FLAT,
|
||||
.name = "flat",
|
||||
.init = regcache_flat_init,
|
||||
.exit = regcache_flat_exit,
|
||||
.read = regcache_flat_read,
|
||||
.write = regcache_flat_write,
|
||||
};
|
378
drivers/base/regmap/regcache-lzo.c
Normal file
378
drivers/base/regmap/regcache-lzo.c
Normal file
|
@ -0,0 +1,378 @@
|
|||
/*
|
||||
* 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/device.h>
|
||||
#include <linux/lzo.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
static int regcache_lzo_exit(struct regmap *map);
|
||||
|
||||
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(struct regmap *map)
|
||||
{
|
||||
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->reg_stride) * map->cache_word_size) /
|
||||
DIV_ROUND_UP(map->cache_size_raw,
|
||||
regcache_lzo_block_count(map));
|
||||
}
|
||||
|
||||
static inline int regcache_lzo_get_blkpos(struct regmap *map,
|
||||
unsigned int reg)
|
||||
{
|
||||
return (reg / map->reg_stride) %
|
||||
(DIV_ROUND_UP(map->cache_size_raw,
|
||||
regcache_lzo_block_count(map)) /
|
||||
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(map));
|
||||
}
|
||||
|
||||
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);
|
||||
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_lzo_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(map);
|
||||
/*
|
||||
* 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(map, lzo_block->dst, blkpos);
|
||||
|
||||
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(map, lzo_block->dst, blkpos, value)) {
|
||||
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 / map->reg_stride, 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, unsigned int min,
|
||||
unsigned int max)
|
||||
{
|
||||
struct regcache_lzo_ctx **lzo_blocks;
|
||||
unsigned int val;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
lzo_blocks = map->cache;
|
||||
i = min;
|
||||
for_each_set_bit_from(i, lzo_blocks[0]->sync_bmp,
|
||||
lzo_blocks[0]->sync_bmp_nbits) {
|
||||
if (i > max)
|
||||
continue;
|
||||
|
||||
ret = regcache_read(map, i, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* 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, 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_COMPRESSED,
|
||||
.name = "lzo",
|
||||
.init = regcache_lzo_init,
|
||||
.exit = regcache_lzo_exit,
|
||||
.read = regcache_lzo_read,
|
||||
.write = regcache_lzo_write,
|
||||
.sync = regcache_lzo_sync
|
||||
};
|
536
drivers/base/regmap/regcache-rbtree.c
Normal file
536
drivers/base/regmap/regcache-rbtree.c
Normal file
|
@ -0,0 +1,536 @@
|
|||
/*
|
||||
* 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/device.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/rbtree.h>
|
||||
#include <linux/seq_file.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
static int regcache_rbtree_write(struct regmap *map, unsigned int reg,
|
||||
unsigned int value);
|
||||
static int regcache_rbtree_exit(struct regmap *map);
|
||||
|
||||
struct regcache_rbtree_node {
|
||||
/* block of adjacent registers */
|
||||
void *block;
|
||||
/* Which registers are present */
|
||||
long *cache_present;
|
||||
/* base register handled by this block */
|
||||
unsigned int base_reg;
|
||||
/* number of registers available in the block */
|
||||
unsigned int blklen;
|
||||
/* the actual rbtree node holding this block */
|
||||
struct rb_node node;
|
||||
} __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 regmap *map,
|
||||
struct regcache_rbtree_node *rbnode,
|
||||
unsigned int *base, unsigned int *top)
|
||||
{
|
||||
*base = rbnode->base_reg;
|
||||
*top = rbnode->base_reg + ((rbnode->blklen - 1) * map->reg_stride);
|
||||
}
|
||||
|
||||
static unsigned int regcache_rbtree_get_register(struct regmap *map,
|
||||
struct regcache_rbtree_node *rbnode, unsigned int idx)
|
||||
{
|
||||
return regcache_get_val(map, rbnode->block, idx);
|
||||
}
|
||||
|
||||
static void regcache_rbtree_set_register(struct regmap *map,
|
||||
struct regcache_rbtree_node *rbnode,
|
||||
unsigned int idx, unsigned int val)
|
||||
{
|
||||
set_bit(idx, rbnode->cache_present);
|
||||
regcache_set_val(map, rbnode->block, idx, val);
|
||||
}
|
||||
|
||||
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(map, 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(map, 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 regmap *map, 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(map, 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;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static int rbtree_show(struct seq_file *s, void *ignored)
|
||||
{
|
||||
struct regmap *map = s->private;
|
||||
struct regcache_rbtree_ctx *rbtree_ctx = map->cache;
|
||||
struct regcache_rbtree_node *n;
|
||||
struct rb_node *node;
|
||||
unsigned int base, top;
|
||||
size_t mem_size;
|
||||
int nodes = 0;
|
||||
int registers = 0;
|
||||
int this_registers, average;
|
||||
|
||||
map->lock(map->lock_arg);
|
||||
|
||||
mem_size = sizeof(*rbtree_ctx);
|
||||
|
||||
for (node = rb_first(&rbtree_ctx->root); node != NULL;
|
||||
node = rb_next(node)) {
|
||||
n = container_of(node, struct regcache_rbtree_node, node);
|
||||
mem_size += sizeof(*n);
|
||||
mem_size += (n->blklen * map->cache_word_size);
|
||||
mem_size += BITS_TO_LONGS(n->blklen) * sizeof(long);
|
||||
|
||||
regcache_rbtree_get_base_top_reg(map, n, &base, &top);
|
||||
this_registers = ((top - base) / map->reg_stride) + 1;
|
||||
seq_printf(s, "%x-%x (%d)\n", base, top, this_registers);
|
||||
|
||||
nodes++;
|
||||
registers += this_registers;
|
||||
}
|
||||
|
||||
if (nodes)
|
||||
average = registers / nodes;
|
||||
else
|
||||
average = 0;
|
||||
|
||||
seq_printf(s, "%d nodes, %d registers, average %d registers, used %zu bytes\n",
|
||||
nodes, registers, average, mem_size);
|
||||
|
||||
map->unlock(map->lock_arg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rbtree_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, rbtree_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations rbtree_fops = {
|
||||
.open = rbtree_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
static void rbtree_debugfs_init(struct regmap *map)
|
||||
{
|
||||
debugfs_create_file("rbtree", 0400, map->debugfs, map, &rbtree_fops);
|
||||
}
|
||||
#endif
|
||||
|
||||
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_rbtree_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->cache_present);
|
||||
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) / map->reg_stride;
|
||||
if (!test_bit(reg_tmp, rbnode->cache_present))
|
||||
return -ENOENT;
|
||||
*value = regcache_rbtree_get_register(map, rbnode, reg_tmp);
|
||||
} else {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int regcache_rbtree_insert_to_block(struct regmap *map,
|
||||
struct regcache_rbtree_node *rbnode,
|
||||
unsigned int base_reg,
|
||||
unsigned int top_reg,
|
||||
unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
unsigned int blklen;
|
||||
unsigned int pos, offset;
|
||||
unsigned long *present;
|
||||
u8 *blk;
|
||||
|
||||
blklen = (top_reg - base_reg) / map->reg_stride + 1;
|
||||
pos = (reg - base_reg) / map->reg_stride;
|
||||
offset = (rbnode->base_reg - base_reg) / map->reg_stride;
|
||||
|
||||
blk = krealloc(rbnode->block,
|
||||
blklen * map->cache_word_size,
|
||||
GFP_KERNEL);
|
||||
if (!blk)
|
||||
return -ENOMEM;
|
||||
|
||||
present = krealloc(rbnode->cache_present,
|
||||
BITS_TO_LONGS(blklen) * sizeof(*present), GFP_KERNEL);
|
||||
if (!present) {
|
||||
kfree(blk);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* insert the register value in the correct place in the rbnode block */
|
||||
if (pos == 0) {
|
||||
memmove(blk + offset * map->cache_word_size,
|
||||
blk, rbnode->blklen * map->cache_word_size);
|
||||
bitmap_shift_left(present, present, offset, blklen);
|
||||
}
|
||||
|
||||
/* update the rbnode block, its size and the base register */
|
||||
rbnode->block = blk;
|
||||
rbnode->blklen = blklen;
|
||||
rbnode->base_reg = base_reg;
|
||||
rbnode->cache_present = present;
|
||||
|
||||
regcache_rbtree_set_register(map, rbnode, pos, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct regcache_rbtree_node *
|
||||
regcache_rbtree_node_alloc(struct regmap *map, unsigned int reg)
|
||||
{
|
||||
struct regcache_rbtree_node *rbnode;
|
||||
const struct regmap_range *range;
|
||||
int i;
|
||||
|
||||
rbnode = kzalloc(sizeof(*rbnode), GFP_KERNEL);
|
||||
if (!rbnode)
|
||||
return NULL;
|
||||
|
||||
/* If there is a read table then use it to guess at an allocation */
|
||||
if (map->rd_table) {
|
||||
for (i = 0; i < map->rd_table->n_yes_ranges; i++) {
|
||||
if (regmap_reg_in_range(reg,
|
||||
&map->rd_table->yes_ranges[i]))
|
||||
break;
|
||||
}
|
||||
|
||||
if (i != map->rd_table->n_yes_ranges) {
|
||||
range = &map->rd_table->yes_ranges[i];
|
||||
rbnode->blklen = (range->range_max - range->range_min) /
|
||||
map->reg_stride + 1;
|
||||
rbnode->base_reg = range->range_min;
|
||||
}
|
||||
}
|
||||
|
||||
if (!rbnode->blklen) {
|
||||
rbnode->blklen = 1;
|
||||
rbnode->base_reg = reg;
|
||||
}
|
||||
|
||||
rbnode->block = kmalloc(rbnode->blklen * map->cache_word_size,
|
||||
GFP_KERNEL);
|
||||
if (!rbnode->block)
|
||||
goto err_free;
|
||||
|
||||
rbnode->cache_present = kzalloc(BITS_TO_LONGS(rbnode->blklen) *
|
||||
sizeof(*rbnode->cache_present), GFP_KERNEL);
|
||||
if (!rbnode->cache_present)
|
||||
goto err_free_block;
|
||||
|
||||
return rbnode;
|
||||
|
||||
err_free_block:
|
||||
kfree(rbnode->block);
|
||||
err_free:
|
||||
kfree(rbnode);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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 reg_tmp;
|
||||
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) / map->reg_stride;
|
||||
regcache_rbtree_set_register(map, rbnode, reg_tmp, value);
|
||||
} else {
|
||||
unsigned int base_reg, top_reg;
|
||||
unsigned int new_base_reg, new_top_reg;
|
||||
unsigned int min, max;
|
||||
unsigned int max_dist;
|
||||
|
||||
max_dist = map->reg_stride * sizeof(*rbnode_tmp) /
|
||||
map->cache_word_size;
|
||||
if (reg < max_dist)
|
||||
min = 0;
|
||||
else
|
||||
min = reg - max_dist;
|
||||
max = reg + max_dist;
|
||||
|
||||
/* 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);
|
||||
|
||||
regcache_rbtree_get_base_top_reg(map, rbnode_tmp,
|
||||
&base_reg, &top_reg);
|
||||
|
||||
if (base_reg <= max && top_reg >= min) {
|
||||
new_base_reg = min(reg, base_reg);
|
||||
new_top_reg = max(reg, top_reg);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = regcache_rbtree_insert_to_block(map, rbnode_tmp,
|
||||
new_base_reg,
|
||||
new_top_reg, reg,
|
||||
value);
|
||||
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.
|
||||
*/
|
||||
rbnode = regcache_rbtree_node_alloc(map, reg);
|
||||
if (!rbnode)
|
||||
return -ENOMEM;
|
||||
regcache_rbtree_set_register(map, rbnode,
|
||||
reg - rbnode->base_reg, value);
|
||||
regcache_rbtree_insert(map, &rbtree_ctx->root, rbnode);
|
||||
rbtree_ctx->cached_rbnode = rbnode;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int regcache_rbtree_sync(struct regmap *map, unsigned int min,
|
||||
unsigned int max)
|
||||
{
|
||||
struct regcache_rbtree_ctx *rbtree_ctx;
|
||||
struct rb_node *node;
|
||||
struct regcache_rbtree_node *rbnode;
|
||||
unsigned int base_reg, top_reg;
|
||||
unsigned int start, end;
|
||||
int ret;
|
||||
|
||||
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);
|
||||
|
||||
regcache_rbtree_get_base_top_reg(map, rbnode, &base_reg,
|
||||
&top_reg);
|
||||
if (base_reg > max)
|
||||
break;
|
||||
if (top_reg < min)
|
||||
continue;
|
||||
|
||||
if (min > base_reg)
|
||||
start = (min - base_reg) / map->reg_stride;
|
||||
else
|
||||
start = 0;
|
||||
|
||||
if (max < top_reg)
|
||||
end = (max - base_reg) / map->reg_stride + 1;
|
||||
else
|
||||
end = rbnode->blklen;
|
||||
|
||||
ret = regcache_sync_block(map, rbnode->block,
|
||||
rbnode->cache_present,
|
||||
rbnode->base_reg, start, end);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return regmap_async_complete(map);
|
||||
}
|
||||
|
||||
static int regcache_rbtree_drop(struct regmap *map, unsigned int min,
|
||||
unsigned int max)
|
||||
{
|
||||
struct regcache_rbtree_ctx *rbtree_ctx;
|
||||
struct regcache_rbtree_node *rbnode;
|
||||
struct rb_node *node;
|
||||
unsigned int base_reg, top_reg;
|
||||
unsigned int start, end;
|
||||
|
||||
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);
|
||||
|
||||
regcache_rbtree_get_base_top_reg(map, rbnode, &base_reg,
|
||||
&top_reg);
|
||||
if (base_reg > max)
|
||||
break;
|
||||
if (top_reg < min)
|
||||
continue;
|
||||
|
||||
if (min > base_reg)
|
||||
start = (min - base_reg) / map->reg_stride;
|
||||
else
|
||||
start = 0;
|
||||
|
||||
if (max < top_reg)
|
||||
end = (max - base_reg) / map->reg_stride + 1;
|
||||
else
|
||||
end = rbnode->blklen;
|
||||
|
||||
bitmap_clear(rbnode->cache_present, start, end - start);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct regcache_ops regcache_rbtree_ops = {
|
||||
.type = REGCACHE_RBTREE,
|
||||
.name = "rbtree",
|
||||
.init = regcache_rbtree_init,
|
||||
.exit = regcache_rbtree_exit,
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
.debugfs_init = rbtree_debugfs_init,
|
||||
#endif
|
||||
.read = regcache_rbtree_read,
|
||||
.write = regcache_rbtree_write,
|
||||
.sync = regcache_rbtree_sync,
|
||||
.drop = regcache_rbtree_drop,
|
||||
};
|
716
drivers/base/regmap/regcache.c
Normal file
716
drivers/base/regmap/regcache.c
Normal file
|
@ -0,0 +1,716 @@
|
|||
/*
|
||||
* 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 <linux/export.h>
|
||||
#include <linux/device.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_rbtree_ops,
|
||||
®cache_lzo_ops,
|
||||
®cache_flat_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) {
|
||||
u32 cache_bypass = map->cache_bypass;
|
||||
dev_warn(map->dev, "No cache defaults, reading back from HW\n");
|
||||
|
||||
/* Bypass the cache access till data read from HW*/
|
||||
map->cache_bypass = 1;
|
||||
tmp_buf = kmalloc(map->cache_size_raw, GFP_KERNEL);
|
||||
if (!tmp_buf)
|
||||
return -EINVAL;
|
||||
ret = regmap_raw_read(map, 0, tmp_buf,
|
||||
map->num_reg_defaults_raw);
|
||||
map->cache_bypass = cache_bypass;
|
||||
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, map->reg_defaults_raw, i);
|
||||
if (regmap_volatile(map, i * map->reg_stride))
|
||||
continue;
|
||||
count++;
|
||||
}
|
||||
|
||||
map->reg_defaults = kmalloc(count * sizeof(struct reg_default),
|
||||
GFP_KERNEL);
|
||||
if (!map->reg_defaults) {
|
||||
ret = -ENOMEM;
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
/* 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, map->reg_defaults_raw, i);
|
||||
if (regmap_volatile(map, i * map->reg_stride))
|
||||
continue;
|
||||
map->reg_defaults[j].reg = i * map->reg_stride;
|
||||
map->reg_defaults[j].def = val;
|
||||
j++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_free:
|
||||
if (map->cache_free)
|
||||
kfree(map->reg_defaults_raw);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int regcache_init(struct regmap *map, const struct regmap_config *config)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
void *tmp_buf;
|
||||
|
||||
for (i = 0; i < config->num_reg_defaults; i++)
|
||||
if (config->reg_defaults[i].reg % map->reg_stride)
|
||||
return -EINVAL;
|
||||
|
||||
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->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_word_size = DIV_ROUND_UP(config->val_bits, 8);
|
||||
map->cache_size_raw = map->cache_word_size * config->num_reg_defaults_raw;
|
||||
|
||||
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 (config->reg_defaults) {
|
||||
if (!map->num_reg_defaults)
|
||||
return -EINVAL;
|
||||
tmp_buf = kmemdup(config->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);
|
||||
ret = map->cache_ops->init(map);
|
||||
if (ret)
|
||||
goto err_free;
|
||||
}
|
||||
return 0;
|
||||
|
||||
err_free:
|
||||
kfree(map->reg_defaults);
|
||||
if (map->cache_free)
|
||||
kfree(map->reg_defaults_raw);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (map->cache_type == REGCACHE_NONE)
|
||||
return -ENOSYS;
|
||||
|
||||
BUG_ON(!map->cache_ops);
|
||||
|
||||
if (!regmap_volatile(map, reg)) {
|
||||
ret = map->cache_ops->read(map, reg, value);
|
||||
|
||||
if (ret == 0)
|
||||
trace_regmap_reg_read_cache(map->dev, reg, *value);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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_volatile(map, reg))
|
||||
return map->cache_ops->write(map, reg, value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int regcache_default_sync(struct regmap *map, unsigned int min,
|
||||
unsigned int max)
|
||||
{
|
||||
unsigned int reg;
|
||||
|
||||
for (reg = min; reg <= max; reg += map->reg_stride) {
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
if (regmap_volatile(map, reg) ||
|
||||
!regmap_writeable(map, reg))
|
||||
continue;
|
||||
|
||||
ret = regcache_read(map, reg, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Is this the hardware default? If so skip. */
|
||||
ret = regcache_lookup_reg(map, reg);
|
||||
if (ret >= 0 && val == map->reg_defaults[ret].def)
|
||||
continue;
|
||||
|
||||
map->cache_bypass = 1;
|
||||
ret = _regmap_write(map, reg, val);
|
||||
map->cache_bypass = 0;
|
||||
if (ret) {
|
||||
dev_err(map->dev, "Unable to sync register %#x. %d\n",
|
||||
reg, ret);
|
||||
return ret;
|
||||
}
|
||||
dev_dbg(map->dev, "Synced register %#x, value %#x\n", reg, val);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 i;
|
||||
const char *name;
|
||||
unsigned int bypass;
|
||||
|
||||
BUG_ON(!map->cache_ops);
|
||||
|
||||
map->lock(map->lock_arg);
|
||||
/* 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_dirty)
|
||||
goto out;
|
||||
|
||||
map->async = true;
|
||||
|
||||
/* Apply any patch first */
|
||||
map->cache_bypass = 1;
|
||||
for (i = 0; i < map->patch_regs; i++) {
|
||||
ret = _regmap_write(map, map->patch[i].reg, map->patch[i].def);
|
||||
if (ret != 0) {
|
||||
dev_err(map->dev, "Failed to write %x = %x: %d\n",
|
||||
map->patch[i].reg, map->patch[i].def, ret);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
map->cache_bypass = 0;
|
||||
|
||||
if (map->cache_ops->sync)
|
||||
ret = map->cache_ops->sync(map, 0, map->max_register);
|
||||
else
|
||||
ret = regcache_default_sync(map, 0, map->max_register);
|
||||
|
||||
if (ret == 0)
|
||||
map->cache_dirty = false;
|
||||
|
||||
out:
|
||||
/* Restore the bypass state */
|
||||
map->async = false;
|
||||
map->cache_bypass = bypass;
|
||||
map->unlock(map->lock_arg);
|
||||
|
||||
regmap_async_complete(map);
|
||||
|
||||
trace_regcache_sync(map->dev, name, "stop");
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regcache_sync);
|
||||
|
||||
/**
|
||||
* regcache_sync_region: Sync part of the register cache with the hardware.
|
||||
*
|
||||
* @map: map to sync.
|
||||
* @min: first register to sync
|
||||
* @max: last register to sync
|
||||
*
|
||||
* Write all non-default register values in the specified region to
|
||||
* the hardware.
|
||||
*
|
||||
* Return a negative value on failure, 0 on success.
|
||||
*/
|
||||
int regcache_sync_region(struct regmap *map, unsigned int min,
|
||||
unsigned int max)
|
||||
{
|
||||
int ret = 0;
|
||||
const char *name;
|
||||
unsigned int bypass;
|
||||
|
||||
BUG_ON(!map->cache_ops);
|
||||
|
||||
map->lock(map->lock_arg);
|
||||
|
||||
/* Remember the initial bypass state */
|
||||
bypass = map->cache_bypass;
|
||||
|
||||
name = map->cache_ops->name;
|
||||
dev_dbg(map->dev, "Syncing %s cache from %d-%d\n", name, min, max);
|
||||
|
||||
trace_regcache_sync(map->dev, name, "start region");
|
||||
|
||||
if (!map->cache_dirty)
|
||||
goto out;
|
||||
|
||||
map->async = true;
|
||||
|
||||
if (map->cache_ops->sync)
|
||||
ret = map->cache_ops->sync(map, min, max);
|
||||
else
|
||||
ret = regcache_default_sync(map, min, max);
|
||||
|
||||
out:
|
||||
/* Restore the bypass state */
|
||||
map->cache_bypass = bypass;
|
||||
map->async = false;
|
||||
map->unlock(map->lock_arg);
|
||||
|
||||
regmap_async_complete(map);
|
||||
|
||||
trace_regcache_sync(map->dev, name, "stop region");
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regcache_sync_region);
|
||||
|
||||
/**
|
||||
* regcache_drop_region: Discard part of the register cache
|
||||
*
|
||||
* @map: map to operate on
|
||||
* @min: first register to discard
|
||||
* @max: last register to discard
|
||||
*
|
||||
* Discard part of the register cache.
|
||||
*
|
||||
* Return a negative value on failure, 0 on success.
|
||||
*/
|
||||
int regcache_drop_region(struct regmap *map, unsigned int min,
|
||||
unsigned int max)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (!map->cache_ops || !map->cache_ops->drop)
|
||||
return -EINVAL;
|
||||
|
||||
map->lock(map->lock_arg);
|
||||
|
||||
trace_regcache_drop_region(map->dev, min, max);
|
||||
|
||||
ret = map->cache_ops->drop(map, min, max);
|
||||
|
||||
map->unlock(map->lock_arg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regcache_drop_region);
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
map->lock(map->lock_arg);
|
||||
WARN_ON(map->cache_bypass && enable);
|
||||
map->cache_only = enable;
|
||||
trace_regmap_cache_only(map->dev, enable);
|
||||
map->unlock(map->lock_arg);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regcache_cache_only);
|
||||
|
||||
/**
|
||||
* regcache_mark_dirty: Mark the register cache as dirty
|
||||
*
|
||||
* @map: map to mark
|
||||
*
|
||||
* Mark the register cache as dirty, for example due to the device
|
||||
* having been powered down for suspend. If the cache is not marked
|
||||
* as dirty then the cache sync will be suppressed.
|
||||
*/
|
||||
void regcache_mark_dirty(struct regmap *map)
|
||||
{
|
||||
map->lock(map->lock_arg);
|
||||
map->cache_dirty = true;
|
||||
map->unlock(map->lock_arg);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regcache_mark_dirty);
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
map->lock(map->lock_arg);
|
||||
WARN_ON(map->cache_only && enable);
|
||||
map->cache_bypass = enable;
|
||||
trace_regmap_cache_bypass(map->dev, enable);
|
||||
map->unlock(map->lock_arg);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regcache_cache_bypass);
|
||||
|
||||
bool regcache_set_val(struct regmap *map, void *base, unsigned int idx,
|
||||
unsigned int val)
|
||||
{
|
||||
if (regcache_get_val(map, base, idx) == val)
|
||||
return true;
|
||||
|
||||
/* Use device native format if possible */
|
||||
if (map->format.format_val) {
|
||||
map->format.format_val(base + (map->cache_word_size * idx),
|
||||
val, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (map->cache_word_size) {
|
||||
case 1: {
|
||||
u8 *cache = base;
|
||||
cache[idx] = val;
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
u16 *cache = base;
|
||||
cache[idx] = val;
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
u32 *cache = base;
|
||||
cache[idx] = val;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BUG();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int regcache_get_val(struct regmap *map, const void *base,
|
||||
unsigned int idx)
|
||||
{
|
||||
if (!base)
|
||||
return -EINVAL;
|
||||
|
||||
/* Use device native format if possible */
|
||||
if (map->format.parse_val)
|
||||
return map->format.parse_val(regcache_get_val_addr(map, base,
|
||||
idx));
|
||||
|
||||
switch (map->cache_word_size) {
|
||||
case 1: {
|
||||
const u8 *cache = base;
|
||||
return cache[idx];
|
||||
}
|
||||
case 2: {
|
||||
const u16 *cache = base;
|
||||
return cache[idx];
|
||||
}
|
||||
case 4: {
|
||||
const u32 *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;
|
||||
}
|
||||
|
||||
static bool regcache_reg_present(unsigned long *cache_present, unsigned int idx)
|
||||
{
|
||||
if (!cache_present)
|
||||
return true;
|
||||
|
||||
return test_bit(idx, cache_present);
|
||||
}
|
||||
|
||||
static int regcache_sync_block_single(struct regmap *map, void *block,
|
||||
unsigned long *cache_present,
|
||||
unsigned int block_base,
|
||||
unsigned int start, unsigned int end)
|
||||
{
|
||||
unsigned int i, regtmp, val;
|
||||
int ret;
|
||||
|
||||
for (i = start; i < end; i++) {
|
||||
regtmp = block_base + (i * map->reg_stride);
|
||||
|
||||
if (!regcache_reg_present(cache_present, i))
|
||||
continue;
|
||||
|
||||
val = regcache_get_val(map, block, i);
|
||||
|
||||
/* Is this the hardware default? If so skip. */
|
||||
ret = regcache_lookup_reg(map, regtmp);
|
||||
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 != 0) {
|
||||
dev_err(map->dev, "Unable to sync register %#x. %d\n",
|
||||
regtmp, ret);
|
||||
return ret;
|
||||
}
|
||||
dev_dbg(map->dev, "Synced register %#x, value %#x\n",
|
||||
regtmp, val);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int regcache_sync_block_raw_flush(struct regmap *map, const void **data,
|
||||
unsigned int base, unsigned int cur)
|
||||
{
|
||||
size_t val_bytes = map->format.val_bytes;
|
||||
int ret, count;
|
||||
|
||||
if (*data == NULL)
|
||||
return 0;
|
||||
|
||||
count = (cur - base) / map->reg_stride;
|
||||
|
||||
dev_dbg(map->dev, "Writing %zu bytes for %d registers from 0x%x-0x%x\n",
|
||||
count * val_bytes, count, base, cur - map->reg_stride);
|
||||
|
||||
map->cache_bypass = 1;
|
||||
|
||||
ret = _regmap_raw_write(map, base, *data, count * val_bytes);
|
||||
if (ret)
|
||||
dev_err(map->dev, "Unable to sync registers %#x-%#x. %d\n",
|
||||
base, cur - map->reg_stride, ret);
|
||||
|
||||
map->cache_bypass = 0;
|
||||
|
||||
*data = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int regcache_sync_block_raw(struct regmap *map, void *block,
|
||||
unsigned long *cache_present,
|
||||
unsigned int block_base, unsigned int start,
|
||||
unsigned int end)
|
||||
{
|
||||
unsigned int i, val;
|
||||
unsigned int regtmp = 0;
|
||||
unsigned int base = 0;
|
||||
const void *data = NULL;
|
||||
int ret;
|
||||
|
||||
for (i = start; i < end; i++) {
|
||||
regtmp = block_base + (i * map->reg_stride);
|
||||
|
||||
if (!regcache_reg_present(cache_present, i)) {
|
||||
ret = regcache_sync_block_raw_flush(map, &data,
|
||||
base, regtmp);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
continue;
|
||||
}
|
||||
|
||||
val = regcache_get_val(map, block, i);
|
||||
|
||||
/* Is this the hardware default? If so skip. */
|
||||
ret = regcache_lookup_reg(map, regtmp);
|
||||
if (ret >= 0 && val == map->reg_defaults[ret].def) {
|
||||
ret = regcache_sync_block_raw_flush(map, &data,
|
||||
base, regtmp);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
data = regcache_get_val_addr(map, block, i);
|
||||
base = regtmp;
|
||||
}
|
||||
}
|
||||
|
||||
return regcache_sync_block_raw_flush(map, &data, base, regtmp +
|
||||
map->reg_stride);
|
||||
}
|
||||
|
||||
int regcache_sync_block(struct regmap *map, void *block,
|
||||
unsigned long *cache_present,
|
||||
unsigned int block_base, unsigned int start,
|
||||
unsigned int end)
|
||||
{
|
||||
if (regmap_can_raw_write(map) && !map->use_single_rw)
|
||||
return regcache_sync_block_raw(map, block, cache_present,
|
||||
block_base, start, end);
|
||||
else
|
||||
return regcache_sync_block_single(map, block, cache_present,
|
||||
block_base, start, end);
|
||||
}
|
597
drivers/base/regmap/regmap-debugfs.c
Normal file
597
drivers/base/regmap/regmap-debugfs.c
Normal file
|
@ -0,0 +1,597 @@
|
|||
/*
|
||||
* 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/mutex.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/list.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
struct regmap_debugfs_node {
|
||||
struct regmap *map;
|
||||
const char *name;
|
||||
struct list_head link;
|
||||
};
|
||||
|
||||
static struct dentry *regmap_debugfs_root;
|
||||
static LIST_HEAD(regmap_debugfs_early_list);
|
||||
static DEFINE_MUTEX(regmap_debugfs_early_lock);
|
||||
|
||||
/* 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 ssize_t regmap_name_read_file(struct file *file,
|
||||
char __user *user_buf, size_t count,
|
||||
loff_t *ppos)
|
||||
{
|
||||
struct regmap *map = file->private_data;
|
||||
int ret;
|
||||
char *buf;
|
||||
|
||||
buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = snprintf(buf, PAGE_SIZE, "%s\n", map->dev->driver->name);
|
||||
if (ret < 0) {
|
||||
kfree(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
|
||||
kfree(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct file_operations regmap_name_fops = {
|
||||
.open = simple_open,
|
||||
.read = regmap_name_read_file,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
static void regmap_debugfs_free_dump_cache(struct regmap *map)
|
||||
{
|
||||
struct regmap_debugfs_off_cache *c;
|
||||
|
||||
while (!list_empty(&map->debugfs_off_cache)) {
|
||||
c = list_first_entry(&map->debugfs_off_cache,
|
||||
struct regmap_debugfs_off_cache,
|
||||
list);
|
||||
list_del(&c->list);
|
||||
kfree(c);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Work out where the start offset maps into register numbers, bearing
|
||||
* in mind that we suppress hidden registers.
|
||||
*/
|
||||
static unsigned int regmap_debugfs_get_dump_start(struct regmap *map,
|
||||
unsigned int base,
|
||||
loff_t from,
|
||||
loff_t *pos)
|
||||
{
|
||||
struct regmap_debugfs_off_cache *c = NULL;
|
||||
loff_t p = 0;
|
||||
unsigned int i, ret;
|
||||
unsigned int fpos_offset;
|
||||
unsigned int reg_offset;
|
||||
|
||||
/* Suppress the cache if we're using a subrange */
|
||||
if (base)
|
||||
return base;
|
||||
|
||||
/*
|
||||
* If we don't have a cache build one so we don't have to do a
|
||||
* linear scan each time.
|
||||
*/
|
||||
mutex_lock(&map->cache_lock);
|
||||
i = base;
|
||||
if (list_empty(&map->debugfs_off_cache)) {
|
||||
for (; i <= map->max_register; i += map->reg_stride) {
|
||||
/* Skip unprinted registers, closing off cache entry */
|
||||
if (!regmap_readable(map, i) ||
|
||||
regmap_precious(map, i)) {
|
||||
if (c) {
|
||||
c->max = p - 1;
|
||||
c->max_reg = i - map->reg_stride;
|
||||
list_add_tail(&c->list,
|
||||
&map->debugfs_off_cache);
|
||||
c = NULL;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* No cache entry? Start a new one */
|
||||
if (!c) {
|
||||
c = kzalloc(sizeof(*c), GFP_KERNEL);
|
||||
if (!c) {
|
||||
regmap_debugfs_free_dump_cache(map);
|
||||
mutex_unlock(&map->cache_lock);
|
||||
return base;
|
||||
}
|
||||
c->min = p;
|
||||
c->base_reg = i;
|
||||
}
|
||||
|
||||
p += map->debugfs_tot_len;
|
||||
}
|
||||
}
|
||||
|
||||
/* Close the last entry off if we didn't scan beyond it */
|
||||
if (c) {
|
||||
c->max = p - 1;
|
||||
c->max_reg = i - map->reg_stride;
|
||||
list_add_tail(&c->list,
|
||||
&map->debugfs_off_cache);
|
||||
}
|
||||
|
||||
/*
|
||||
* This should never happen; we return above if we fail to
|
||||
* allocate and we should never be in this code if there are
|
||||
* no registers at all.
|
||||
*/
|
||||
WARN_ON(list_empty(&map->debugfs_off_cache));
|
||||
ret = base;
|
||||
|
||||
/* Find the relevant block:offset */
|
||||
list_for_each_entry(c, &map->debugfs_off_cache, list) {
|
||||
if (from >= c->min && from <= c->max) {
|
||||
fpos_offset = from - c->min;
|
||||
reg_offset = fpos_offset / map->debugfs_tot_len;
|
||||
*pos = c->min + (reg_offset * map->debugfs_tot_len);
|
||||
mutex_unlock(&map->cache_lock);
|
||||
return c->base_reg + (reg_offset * map->reg_stride);
|
||||
}
|
||||
|
||||
*pos = c->max;
|
||||
ret = c->max_reg;
|
||||
}
|
||||
mutex_unlock(&map->cache_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline void regmap_calc_tot_len(struct regmap *map,
|
||||
void *buf, size_t count)
|
||||
{
|
||||
/* Calculate the length of a fixed format */
|
||||
if (!map->debugfs_tot_len) {
|
||||
map->debugfs_reg_len = regmap_calc_reg_len(map->max_register,
|
||||
buf, count);
|
||||
map->debugfs_val_len = 2 * map->format.val_bytes;
|
||||
map->debugfs_tot_len = map->debugfs_reg_len +
|
||||
map->debugfs_val_len + 3; /* : \n */
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t regmap_read_debugfs(struct regmap *map, unsigned int from,
|
||||
unsigned int to, char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
size_t buf_pos = 0;
|
||||
loff_t p = *ppos;
|
||||
ssize_t ret;
|
||||
int i;
|
||||
char *buf;
|
||||
unsigned int val, start_reg;
|
||||
|
||||
if (*ppos < 0 || !count)
|
||||
return -EINVAL;
|
||||
|
||||
buf = kmalloc(count, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
regmap_calc_tot_len(map, buf, count);
|
||||
|
||||
/* Work out which register we're starting at */
|
||||
start_reg = regmap_debugfs_get_dump_start(map, from, *ppos, &p);
|
||||
|
||||
for (i = start_reg; i <= to; i += map->reg_stride) {
|
||||
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 + map->debugfs_tot_len > count)
|
||||
break;
|
||||
|
||||
/* Format the register */
|
||||
snprintf(buf + buf_pos, count - buf_pos, "%.*x: ",
|
||||
map->debugfs_reg_len, i - from);
|
||||
buf_pos += map->debugfs_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", map->debugfs_val_len, val);
|
||||
else
|
||||
memset(buf + buf_pos, 'X',
|
||||
map->debugfs_val_len);
|
||||
buf_pos += 2 * map->format.val_bytes;
|
||||
|
||||
buf[buf_pos++] = '\n';
|
||||
}
|
||||
p += map->debugfs_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 ssize_t regmap_map_read_file(struct file *file, char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct regmap *map = file->private_data;
|
||||
|
||||
return regmap_read_debugfs(map, 0, map->max_register, user_buf,
|
||||
count, ppos);
|
||||
}
|
||||
|
||||
#undef REGMAP_ALLOW_WRITE_DEBUGFS
|
||||
#ifdef REGMAP_ALLOW_WRITE_DEBUGFS
|
||||
/*
|
||||
* This can be dangerous especially when we have clients such as
|
||||
* PMICs, therefore don't provide any real compile time configuration option
|
||||
* for this feature, people who want to use this will need to modify
|
||||
* the source code directly.
|
||||
*/
|
||||
static ssize_t regmap_map_write_file(struct file *file,
|
||||
const char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
char buf[32];
|
||||
size_t buf_size;
|
||||
char *start = buf;
|
||||
unsigned long reg, value;
|
||||
struct regmap *map = file->private_data;
|
||||
int ret;
|
||||
|
||||
buf_size = min(count, (sizeof(buf)-1));
|
||||
if (copy_from_user(buf, user_buf, buf_size))
|
||||
return -EFAULT;
|
||||
buf[buf_size] = 0;
|
||||
|
||||
while (*start == ' ')
|
||||
start++;
|
||||
reg = simple_strtoul(start, &start, 16);
|
||||
while (*start == ' ')
|
||||
start++;
|
||||
if (kstrtoul(start, 16, &value))
|
||||
return -EINVAL;
|
||||
|
||||
/* Userspace has been fiddling around behind the kernel's back */
|
||||
add_taint(TAINT_USER, LOCKDEP_STILL_OK);
|
||||
|
||||
ret = regmap_write(map, reg, value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return buf_size;
|
||||
}
|
||||
#else
|
||||
#define regmap_map_write_file NULL
|
||||
#endif
|
||||
|
||||
static const struct file_operations regmap_map_fops = {
|
||||
.open = simple_open,
|
||||
.read = regmap_map_read_file,
|
||||
.write = regmap_map_write_file,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
static ssize_t regmap_range_read_file(struct file *file, char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct regmap_range_node *range = file->private_data;
|
||||
struct regmap *map = range->map;
|
||||
|
||||
return regmap_read_debugfs(map, range->range_min, range->range_max,
|
||||
user_buf, count, ppos);
|
||||
}
|
||||
|
||||
static const struct file_operations regmap_range_fops = {
|
||||
.open = simple_open,
|
||||
.read = regmap_range_read_file,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
static ssize_t regmap_reg_ranges_read_file(struct file *file,
|
||||
char __user *user_buf, size_t count,
|
||||
loff_t *ppos)
|
||||
{
|
||||
struct regmap *map = file->private_data;
|
||||
struct regmap_debugfs_off_cache *c;
|
||||
loff_t p = 0;
|
||||
size_t buf_pos = 0;
|
||||
char *buf;
|
||||
char *entry;
|
||||
int ret;
|
||||
|
||||
if (*ppos < 0 || !count)
|
||||
return -EINVAL;
|
||||
|
||||
buf = kmalloc(count, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
entry = kmalloc(PAGE_SIZE, GFP_KERNEL);
|
||||
if (!entry) {
|
||||
kfree(buf);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* While we are at it, build the register dump cache
|
||||
* now so the read() operation on the `registers' file
|
||||
* can benefit from using the cache. We do not care
|
||||
* about the file position information that is contained
|
||||
* in the cache, just about the actual register blocks */
|
||||
regmap_calc_tot_len(map, buf, count);
|
||||
regmap_debugfs_get_dump_start(map, 0, *ppos, &p);
|
||||
|
||||
/* Reset file pointer as the fixed-format of the `registers'
|
||||
* file is not compatible with the `range' file */
|
||||
p = 0;
|
||||
mutex_lock(&map->cache_lock);
|
||||
list_for_each_entry(c, &map->debugfs_off_cache, list) {
|
||||
snprintf(entry, PAGE_SIZE, "%x-%x",
|
||||
c->base_reg, c->max_reg);
|
||||
if (p >= *ppos) {
|
||||
if (buf_pos + 1 + strlen(entry) > count)
|
||||
break;
|
||||
snprintf(buf + buf_pos, count - buf_pos,
|
||||
"%s", entry);
|
||||
buf_pos += strlen(entry);
|
||||
buf[buf_pos] = '\n';
|
||||
buf_pos++;
|
||||
}
|
||||
p += strlen(entry) + 1;
|
||||
}
|
||||
mutex_unlock(&map->cache_lock);
|
||||
|
||||
kfree(entry);
|
||||
ret = buf_pos;
|
||||
|
||||
if (copy_to_user(user_buf, buf, buf_pos)) {
|
||||
ret = -EFAULT;
|
||||
goto out_buf;
|
||||
}
|
||||
|
||||
*ppos += buf_pos;
|
||||
out_buf:
|
||||
kfree(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct file_operations regmap_reg_ranges_fops = {
|
||||
.open = simple_open,
|
||||
.read = regmap_reg_ranges_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; i += map->reg_stride) {
|
||||
/* 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 = simple_open,
|
||||
.read = regmap_access_read_file,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
void regmap_debugfs_init(struct regmap *map, const char *name)
|
||||
{
|
||||
struct rb_node *next;
|
||||
struct regmap_range_node *range_node;
|
||||
const char *devname = "dummy";
|
||||
|
||||
/* If we don't have the debugfs root yet, postpone init */
|
||||
if (!regmap_debugfs_root) {
|
||||
struct regmap_debugfs_node *node;
|
||||
node = kzalloc(sizeof(*node), GFP_KERNEL);
|
||||
if (!node)
|
||||
return;
|
||||
node->map = map;
|
||||
node->name = name;
|
||||
mutex_lock(®map_debugfs_early_lock);
|
||||
list_add(&node->link, ®map_debugfs_early_list);
|
||||
mutex_unlock(®map_debugfs_early_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&map->debugfs_off_cache);
|
||||
mutex_init(&map->cache_lock);
|
||||
|
||||
if (map->dev)
|
||||
devname = dev_name(map->dev);
|
||||
|
||||
if (name) {
|
||||
map->debugfs_name = kasprintf(GFP_KERNEL, "%s-%s",
|
||||
devname, name);
|
||||
name = map->debugfs_name;
|
||||
} else {
|
||||
name = devname;
|
||||
}
|
||||
|
||||
map->debugfs = debugfs_create_dir(name, regmap_debugfs_root);
|
||||
if (!map->debugfs) {
|
||||
dev_warn(map->dev, "Failed to create debugfs directory\n");
|
||||
return;
|
||||
}
|
||||
|
||||
debugfs_create_file("name", 0400, map->debugfs,
|
||||
map, ®map_name_fops);
|
||||
|
||||
debugfs_create_file("range", 0400, map->debugfs,
|
||||
map, ®map_reg_ranges_fops);
|
||||
|
||||
if (map->max_register || regmap_readable(map, 0)) {
|
||||
umode_t registers_mode;
|
||||
|
||||
if (IS_ENABLED(REGMAP_ALLOW_WRITE_DEBUGFS))
|
||||
registers_mode = 0600;
|
||||
else
|
||||
registers_mode = 0400;
|
||||
|
||||
debugfs_create_file("registers", registers_mode, map->debugfs,
|
||||
map, ®map_map_fops);
|
||||
debugfs_create_file("access", 0400, map->debugfs,
|
||||
map, ®map_access_fops);
|
||||
}
|
||||
|
||||
if (map->cache_type) {
|
||||
debugfs_create_bool("cache_only", 0400, map->debugfs,
|
||||
&map->cache_only);
|
||||
debugfs_create_bool("cache_dirty", 0400, map->debugfs,
|
||||
&map->cache_dirty);
|
||||
debugfs_create_bool("cache_bypass", 0400, map->debugfs,
|
||||
&map->cache_bypass);
|
||||
}
|
||||
|
||||
next = rb_first(&map->range_tree);
|
||||
while (next) {
|
||||
range_node = rb_entry(next, struct regmap_range_node, node);
|
||||
|
||||
if (range_node->name)
|
||||
debugfs_create_file(range_node->name, 0400,
|
||||
map->debugfs, range_node,
|
||||
®map_range_fops);
|
||||
|
||||
next = rb_next(&range_node->node);
|
||||
}
|
||||
|
||||
if (map->cache_ops && map->cache_ops->debugfs_init)
|
||||
map->cache_ops->debugfs_init(map);
|
||||
}
|
||||
|
||||
void regmap_debugfs_exit(struct regmap *map)
|
||||
{
|
||||
if (map->debugfs) {
|
||||
debugfs_remove_recursive(map->debugfs);
|
||||
mutex_lock(&map->cache_lock);
|
||||
regmap_debugfs_free_dump_cache(map);
|
||||
mutex_unlock(&map->cache_lock);
|
||||
kfree(map->debugfs_name);
|
||||
} else {
|
||||
struct regmap_debugfs_node *node, *tmp;
|
||||
|
||||
mutex_lock(®map_debugfs_early_lock);
|
||||
list_for_each_entry_safe(node, tmp, ®map_debugfs_early_list,
|
||||
link) {
|
||||
if (node->map == map) {
|
||||
list_del(&node->link);
|
||||
kfree(node);
|
||||
}
|
||||
}
|
||||
mutex_unlock(®map_debugfs_early_lock);
|
||||
}
|
||||
}
|
||||
|
||||
void regmap_debugfs_initcall(void)
|
||||
{
|
||||
struct regmap_debugfs_node *node, *tmp;
|
||||
|
||||
regmap_debugfs_root = debugfs_create_dir("regmap", NULL);
|
||||
if (!regmap_debugfs_root) {
|
||||
pr_warn("regmap: Failed to create debugfs root\n");
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock(®map_debugfs_early_lock);
|
||||
list_for_each_entry_safe(node, tmp, ®map_debugfs_early_list, link) {
|
||||
regmap_debugfs_init(node->map, node->name);
|
||||
list_del(&node->link);
|
||||
kfree(node);
|
||||
}
|
||||
mutex_unlock(®map_debugfs_early_lock);
|
||||
}
|
272
drivers/base/regmap/regmap-i2c.c
Normal file
272
drivers/base/regmap/regmap-i2c.c
Normal file
|
@ -0,0 +1,272 @@
|
|||
/*
|
||||
* Register map access API - I2C support
|
||||
*
|
||||
* 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/regmap.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
|
||||
static int regmap_smbus_byte_reg_read(void *context, unsigned int reg,
|
||||
unsigned int *val)
|
||||
{
|
||||
struct device *dev = context;
|
||||
struct i2c_client *i2c = to_i2c_client(dev);
|
||||
int ret;
|
||||
|
||||
if (reg > 0xff)
|
||||
return -EINVAL;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(i2c, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int regmap_smbus_byte_reg_write(void *context, unsigned int reg,
|
||||
unsigned int val)
|
||||
{
|
||||
struct device *dev = context;
|
||||
struct i2c_client *i2c = to_i2c_client(dev);
|
||||
|
||||
if (val > 0xff || reg > 0xff)
|
||||
return -EINVAL;
|
||||
|
||||
return i2c_smbus_write_byte_data(i2c, reg, val);
|
||||
}
|
||||
|
||||
static struct regmap_bus regmap_smbus_byte = {
|
||||
.reg_write = regmap_smbus_byte_reg_write,
|
||||
.reg_read = regmap_smbus_byte_reg_read,
|
||||
};
|
||||
|
||||
static int regmap_smbus_word_reg_read(void *context, unsigned int reg,
|
||||
unsigned int *val)
|
||||
{
|
||||
struct device *dev = context;
|
||||
struct i2c_client *i2c = to_i2c_client(dev);
|
||||
int ret;
|
||||
|
||||
if (reg > 0xff)
|
||||
return -EINVAL;
|
||||
|
||||
ret = i2c_smbus_read_word_data(i2c, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int regmap_smbus_word_reg_write(void *context, unsigned int reg,
|
||||
unsigned int val)
|
||||
{
|
||||
struct device *dev = context;
|
||||
struct i2c_client *i2c = to_i2c_client(dev);
|
||||
|
||||
if (val > 0xffff || reg > 0xff)
|
||||
return -EINVAL;
|
||||
|
||||
return i2c_smbus_write_word_data(i2c, reg, val);
|
||||
}
|
||||
|
||||
static struct regmap_bus regmap_smbus_word = {
|
||||
.reg_write = regmap_smbus_word_reg_write,
|
||||
.reg_read = regmap_smbus_word_reg_read,
|
||||
};
|
||||
|
||||
static int regmap_i2c_write(void *context, const void *data, size_t count)
|
||||
{
|
||||
struct device *dev = context;
|
||||
struct i2c_client *i2c = to_i2c_client(dev);
|
||||
int ret;
|
||||
|
||||
ret = i2c_master_send(i2c, data, count);
|
||||
if (ret == count)
|
||||
return 0;
|
||||
else if (ret < 0)
|
||||
return ret;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static int regmap_i2c_gather_write(void *context,
|
||||
const void *reg, size_t reg_size,
|
||||
const 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;
|
||||
|
||||
/* If the I2C controller can't do a gather tell the core, it
|
||||
* will substitute in a linear write for us.
|
||||
*/
|
||||
if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_NOSTART))
|
||||
return -ENOTSUPP;
|
||||
|
||||
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_NOSTART;
|
||||
xfer[1].len = val_size;
|
||||
xfer[1].buf = (void *)val;
|
||||
|
||||
ret = i2c_transfer(i2c->adapter, xfer, 2);
|
||||
if (ret == 2)
|
||||
return 0;
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (i2c->flags & I2C_CLIENT_TEN) {
|
||||
xfer[0].flags = I2C_M_RD|I2C_CLIENT_TEN;
|
||||
xfer[0].len = val_size;
|
||||
xfer[0].buf = (void *)reg;
|
||||
xfer[0].addr = xfer[0].buf[0] +
|
||||
(((i2c->addr & 0x7f) << 8) & 0xff00);
|
||||
xfer[0].buf = val;
|
||||
|
||||
ret = i2c_transfer(i2c->adapter, xfer, 1);
|
||||
|
||||
if (ret == 1)
|
||||
return 0;
|
||||
else if (ret < 0)
|
||||
return ret;
|
||||
else
|
||||
return -EIO;
|
||||
} else if (i2c->flags & I2C_CLIENT_SPEEDY) {
|
||||
xfer[0].flags = I2C_M_RD|I2C_CLIENT_SPEEDY;
|
||||
xfer[0].len = val_size;
|
||||
xfer[0].buf = (void *)reg;
|
||||
/*
|
||||
* The upper 4bit of 12bit speedy slave address is for device id.
|
||||
* 8bit is for device register offset.
|
||||
*/
|
||||
xfer[0].addr = xfer[0].buf[0] + ((i2c->addr & 0xf) << 8);
|
||||
xfer[0].buf = val;
|
||||
|
||||
ret = i2c_transfer(i2c->adapter, xfer, 1);
|
||||
|
||||
if (ret == 1)
|
||||
return 0;
|
||||
else if (ret < 0)
|
||||
return ret;
|
||||
else
|
||||
return -EIO;
|
||||
} else {
|
||||
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;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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 ®map_i2c;
|
||||
else if (config->val_bits == 16 && config->reg_bits == 8 &&
|
||||
i2c_check_functionality(i2c->adapter,
|
||||
I2C_FUNC_SMBUS_WORD_DATA))
|
||||
return ®map_smbus_word;
|
||||
else if (config->val_bits == 8 && config->reg_bits == 8 &&
|
||||
i2c_check_functionality(i2c->adapter,
|
||||
I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
return ®map_smbus_byte;
|
||||
|
||||
return ERR_PTR(-ENOTSUPP);
|
||||
}
|
||||
|
||||
/**
|
||||
* regmap_init_i2c(): Initialise register map
|
||||
*
|
||||
* @i2c: Device that will be interacted with
|
||||
* @config: Configuration for register map
|
||||
*
|
||||
* The return value will be an ERR_PTR() on error or a valid pointer to
|
||||
* a struct regmap.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regmap_init_i2c);
|
||||
|
||||
/**
|
||||
* devm_regmap_init_i2c(): Initialise managed register map
|
||||
*
|
||||
* @i2c: Device that will be interacted with
|
||||
* @config: Configuration for register map
|
||||
*
|
||||
* The return value will be an ERR_PTR() on error or a valid pointer
|
||||
* to a struct regmap. The regmap will be automatically freed by the
|
||||
* device management code.
|
||||
*/
|
||||
struct regmap *devm_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 devm_regmap_init(&i2c->dev, bus, &i2c->dev, config);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_regmap_init_i2c);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
598
drivers/base/regmap/regmap-irq.c
Normal file
598
drivers/base/regmap/regmap-irq.c
Normal file
|
@ -0,0 +1,598 @@
|
|||
/*
|
||||
* regmap based irq_chip
|
||||
*
|
||||
* 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/device.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
struct regmap_irq_chip_data {
|
||||
struct mutex lock;
|
||||
struct irq_chip irq_chip;
|
||||
|
||||
struct regmap *map;
|
||||
const struct regmap_irq_chip *chip;
|
||||
|
||||
int irq_base;
|
||||
struct irq_domain *domain;
|
||||
|
||||
int irq;
|
||||
int wake_count;
|
||||
|
||||
void *status_reg_buf;
|
||||
unsigned int *status_buf;
|
||||
unsigned int *mask_buf;
|
||||
unsigned int *mask_buf_def;
|
||||
unsigned int *wake_buf;
|
||||
|
||||
unsigned int irq_reg_stride;
|
||||
};
|
||||
|
||||
static inline const
|
||||
struct regmap_irq *irq_to_regmap_irq(struct regmap_irq_chip_data *data,
|
||||
int irq)
|
||||
{
|
||||
return &data->chip->irqs[irq];
|
||||
}
|
||||
|
||||
static void regmap_irq_lock(struct irq_data *data)
|
||||
{
|
||||
struct regmap_irq_chip_data *d = irq_data_get_irq_chip_data(data);
|
||||
|
||||
mutex_lock(&d->lock);
|
||||
}
|
||||
|
||||
static void regmap_irq_sync_unlock(struct irq_data *data)
|
||||
{
|
||||
struct regmap_irq_chip_data *d = irq_data_get_irq_chip_data(data);
|
||||
struct regmap *map = d->map;
|
||||
int i, ret;
|
||||
u32 reg;
|
||||
|
||||
if (d->chip->runtime_pm) {
|
||||
ret = pm_runtime_get_sync(map->dev);
|
||||
if (ret < 0)
|
||||
dev_err(map->dev, "IRQ sync failed to resume: %d\n",
|
||||
ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* If there's been a change in the mask write it back to the
|
||||
* hardware. We rely on the use of the regmap core cache to
|
||||
* suppress pointless writes.
|
||||
*/
|
||||
for (i = 0; i < d->chip->num_regs; i++) {
|
||||
reg = d->chip->mask_base +
|
||||
(i * map->reg_stride * d->irq_reg_stride);
|
||||
if (d->chip->mask_invert)
|
||||
ret = regmap_update_bits(d->map, reg,
|
||||
d->mask_buf_def[i], ~d->mask_buf[i]);
|
||||
else
|
||||
ret = regmap_update_bits(d->map, reg,
|
||||
d->mask_buf_def[i], d->mask_buf[i]);
|
||||
if (ret != 0)
|
||||
dev_err(d->map->dev, "Failed to sync masks in %x\n",
|
||||
reg);
|
||||
|
||||
reg = d->chip->wake_base +
|
||||
(i * map->reg_stride * d->irq_reg_stride);
|
||||
if (d->wake_buf) {
|
||||
if (d->chip->wake_invert)
|
||||
ret = regmap_update_bits(d->map, reg,
|
||||
d->mask_buf_def[i],
|
||||
~d->wake_buf[i]);
|
||||
else
|
||||
ret = regmap_update_bits(d->map, reg,
|
||||
d->mask_buf_def[i],
|
||||
d->wake_buf[i]);
|
||||
if (ret != 0)
|
||||
dev_err(d->map->dev,
|
||||
"Failed to sync wakes in %x: %d\n",
|
||||
reg, ret);
|
||||
}
|
||||
|
||||
if (!d->chip->init_ack_masked)
|
||||
continue;
|
||||
/*
|
||||
* Ack all the masked interrupts uncondictionly,
|
||||
* OR if there is masked interrupt which hasn't been Acked,
|
||||
* it'll be ignored in irq handler, then may introduce irq storm
|
||||
*/
|
||||
if (d->mask_buf[i] && (d->chip->ack_base || d->chip->use_ack)) {
|
||||
reg = d->chip->ack_base +
|
||||
(i * map->reg_stride * d->irq_reg_stride);
|
||||
ret = regmap_write(map, reg, d->mask_buf[i]);
|
||||
if (ret != 0)
|
||||
dev_err(d->map->dev, "Failed to ack 0x%x: %d\n",
|
||||
reg, ret);
|
||||
}
|
||||
}
|
||||
|
||||
if (d->chip->runtime_pm)
|
||||
pm_runtime_put(map->dev);
|
||||
|
||||
/* If we've changed our wakeup count propagate it to the parent */
|
||||
if (d->wake_count < 0)
|
||||
for (i = d->wake_count; i < 0; i++)
|
||||
irq_set_irq_wake(d->irq, 0);
|
||||
else if (d->wake_count > 0)
|
||||
for (i = 0; i < d->wake_count; i++)
|
||||
irq_set_irq_wake(d->irq, 1);
|
||||
|
||||
d->wake_count = 0;
|
||||
|
||||
mutex_unlock(&d->lock);
|
||||
}
|
||||
|
||||
static void regmap_irq_enable(struct irq_data *data)
|
||||
{
|
||||
struct regmap_irq_chip_data *d = irq_data_get_irq_chip_data(data);
|
||||
struct regmap *map = d->map;
|
||||
const struct regmap_irq *irq_data = irq_to_regmap_irq(d, data->hwirq);
|
||||
|
||||
d->mask_buf[irq_data->reg_offset / map->reg_stride] &= ~irq_data->mask;
|
||||
}
|
||||
|
||||
static void regmap_irq_disable(struct irq_data *data)
|
||||
{
|
||||
struct regmap_irq_chip_data *d = irq_data_get_irq_chip_data(data);
|
||||
struct regmap *map = d->map;
|
||||
const struct regmap_irq *irq_data = irq_to_regmap_irq(d, data->hwirq);
|
||||
|
||||
d->mask_buf[irq_data->reg_offset / map->reg_stride] |= irq_data->mask;
|
||||
}
|
||||
|
||||
static int regmap_irq_set_wake(struct irq_data *data, unsigned int on)
|
||||
{
|
||||
struct regmap_irq_chip_data *d = irq_data_get_irq_chip_data(data);
|
||||
struct regmap *map = d->map;
|
||||
const struct regmap_irq *irq_data = irq_to_regmap_irq(d, data->hwirq);
|
||||
|
||||
if (on) {
|
||||
if (d->wake_buf)
|
||||
d->wake_buf[irq_data->reg_offset / map->reg_stride]
|
||||
&= ~irq_data->mask;
|
||||
d->wake_count++;
|
||||
} else {
|
||||
if (d->wake_buf)
|
||||
d->wake_buf[irq_data->reg_offset / map->reg_stride]
|
||||
|= irq_data->mask;
|
||||
d->wake_count--;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct irq_chip regmap_irq_chip = {
|
||||
.irq_bus_lock = regmap_irq_lock,
|
||||
.irq_bus_sync_unlock = regmap_irq_sync_unlock,
|
||||
.irq_disable = regmap_irq_disable,
|
||||
.irq_enable = regmap_irq_enable,
|
||||
.irq_set_wake = regmap_irq_set_wake,
|
||||
};
|
||||
|
||||
static irqreturn_t regmap_irq_thread(int irq, void *d)
|
||||
{
|
||||
struct regmap_irq_chip_data *data = d;
|
||||
const struct regmap_irq_chip *chip = data->chip;
|
||||
struct regmap *map = data->map;
|
||||
int ret, i;
|
||||
bool handled = false;
|
||||
u32 reg;
|
||||
|
||||
if (chip->runtime_pm) {
|
||||
ret = pm_runtime_get_sync(map->dev);
|
||||
if (ret < 0) {
|
||||
dev_err(map->dev, "IRQ thread failed to resume: %d\n",
|
||||
ret);
|
||||
pm_runtime_put(map->dev);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Read in the statuses, using a single bulk read if possible
|
||||
* in order to reduce the I/O overheads.
|
||||
*/
|
||||
if (!map->use_single_rw && map->reg_stride == 1 &&
|
||||
data->irq_reg_stride == 1) {
|
||||
u8 *buf8 = data->status_reg_buf;
|
||||
u16 *buf16 = data->status_reg_buf;
|
||||
u32 *buf32 = data->status_reg_buf;
|
||||
|
||||
BUG_ON(!data->status_reg_buf);
|
||||
|
||||
ret = regmap_bulk_read(map, chip->status_base,
|
||||
data->status_reg_buf,
|
||||
chip->num_regs);
|
||||
if (ret != 0) {
|
||||
dev_err(map->dev, "Failed to read IRQ status: %d\n",
|
||||
ret);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
for (i = 0; i < data->chip->num_regs; i++) {
|
||||
switch (map->format.val_bytes) {
|
||||
case 1:
|
||||
data->status_buf[i] = buf8[i];
|
||||
break;
|
||||
case 2:
|
||||
data->status_buf[i] = buf16[i];
|
||||
break;
|
||||
case 4:
|
||||
data->status_buf[i] = buf32[i];
|
||||
break;
|
||||
default:
|
||||
BUG();
|
||||
return IRQ_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
for (i = 0; i < data->chip->num_regs; i++) {
|
||||
ret = regmap_read(map, chip->status_base +
|
||||
(i * map->reg_stride
|
||||
* data->irq_reg_stride),
|
||||
&data->status_buf[i]);
|
||||
|
||||
if (ret != 0) {
|
||||
dev_err(map->dev,
|
||||
"Failed to read IRQ status: %d\n",
|
||||
ret);
|
||||
if (chip->runtime_pm)
|
||||
pm_runtime_put(map->dev);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Ignore masked IRQs and ack if we need to; we ack early so
|
||||
* there is no race between handling and acknowleding the
|
||||
* interrupt. We assume that typically few of the interrupts
|
||||
* will fire simultaneously so don't worry about overhead from
|
||||
* doing a write per register.
|
||||
*/
|
||||
for (i = 0; i < data->chip->num_regs; i++) {
|
||||
data->status_buf[i] &= ~data->mask_buf[i];
|
||||
|
||||
if (data->status_buf[i] && (chip->ack_base || chip->use_ack)) {
|
||||
reg = chip->ack_base +
|
||||
(i * map->reg_stride * data->irq_reg_stride);
|
||||
ret = regmap_write(map, reg, data->status_buf[i]);
|
||||
if (ret != 0)
|
||||
dev_err(map->dev, "Failed to ack 0x%x: %d\n",
|
||||
reg, ret);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < chip->num_irqs; i++) {
|
||||
if (data->status_buf[chip->irqs[i].reg_offset /
|
||||
map->reg_stride] & chip->irqs[i].mask) {
|
||||
handle_nested_irq(irq_find_mapping(data->domain, i));
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (chip->runtime_pm)
|
||||
pm_runtime_put(map->dev);
|
||||
|
||||
if (handled)
|
||||
return IRQ_HANDLED;
|
||||
else
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
static int regmap_irq_map(struct irq_domain *h, unsigned int virq,
|
||||
irq_hw_number_t hw)
|
||||
{
|
||||
struct regmap_irq_chip_data *data = h->host_data;
|
||||
|
||||
irq_set_chip_data(virq, data);
|
||||
irq_set_chip(virq, &data->irq_chip);
|
||||
irq_set_nested_thread(virq, 1);
|
||||
|
||||
/* ARM needs us to explicitly flag the IRQ as valid
|
||||
* and will set them noprobe when we do so. */
|
||||
#ifdef CONFIG_ARM
|
||||
set_irq_flags(virq, IRQF_VALID);
|
||||
#else
|
||||
irq_set_noprobe(virq);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct irq_domain_ops regmap_domain_ops = {
|
||||
.map = regmap_irq_map,
|
||||
.xlate = irq_domain_xlate_twocell,
|
||||
};
|
||||
|
||||
/**
|
||||
* regmap_add_irq_chip(): Use standard regmap IRQ controller handling
|
||||
*
|
||||
* map: The regmap for the device.
|
||||
* irq: The IRQ the device uses to signal interrupts
|
||||
* irq_flags: The IRQF_ flags to use for the primary interrupt.
|
||||
* chip: Configuration for the interrupt controller.
|
||||
* data: Runtime data structure for the controller, allocated on success
|
||||
*
|
||||
* Returns 0 on success or an errno on failure.
|
||||
*
|
||||
* In order for this to be efficient the chip really should use a
|
||||
* register cache. The chip driver is responsible for restoring the
|
||||
* register values used by the IRQ controller over suspend and resume.
|
||||
*/
|
||||
int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags,
|
||||
int irq_base, const struct regmap_irq_chip *chip,
|
||||
struct regmap_irq_chip_data **data)
|
||||
{
|
||||
struct regmap_irq_chip_data *d;
|
||||
int i;
|
||||
int ret = -ENOMEM;
|
||||
u32 reg;
|
||||
|
||||
if (chip->num_regs <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < chip->num_irqs; i++) {
|
||||
if (chip->irqs[i].reg_offset % map->reg_stride)
|
||||
return -EINVAL;
|
||||
if (chip->irqs[i].reg_offset / map->reg_stride >=
|
||||
chip->num_regs)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (irq_base) {
|
||||
irq_base = irq_alloc_descs(irq_base, 0, chip->num_irqs, 0);
|
||||
if (irq_base < 0) {
|
||||
dev_warn(map->dev, "Failed to allocate IRQs: %d\n",
|
||||
irq_base);
|
||||
return irq_base;
|
||||
}
|
||||
}
|
||||
|
||||
d = kzalloc(sizeof(*d), GFP_KERNEL);
|
||||
if (!d)
|
||||
return -ENOMEM;
|
||||
|
||||
d->status_buf = kzalloc(sizeof(unsigned int) * chip->num_regs,
|
||||
GFP_KERNEL);
|
||||
if (!d->status_buf)
|
||||
goto err_alloc;
|
||||
|
||||
d->mask_buf = kzalloc(sizeof(unsigned int) * chip->num_regs,
|
||||
GFP_KERNEL);
|
||||
if (!d->mask_buf)
|
||||
goto err_alloc;
|
||||
|
||||
d->mask_buf_def = kzalloc(sizeof(unsigned int) * chip->num_regs,
|
||||
GFP_KERNEL);
|
||||
if (!d->mask_buf_def)
|
||||
goto err_alloc;
|
||||
|
||||
if (chip->wake_base) {
|
||||
d->wake_buf = kzalloc(sizeof(unsigned int) * chip->num_regs,
|
||||
GFP_KERNEL);
|
||||
if (!d->wake_buf)
|
||||
goto err_alloc;
|
||||
}
|
||||
|
||||
d->irq_chip = regmap_irq_chip;
|
||||
d->irq_chip.name = chip->name;
|
||||
d->irq = irq;
|
||||
d->map = map;
|
||||
d->chip = chip;
|
||||
d->irq_base = irq_base;
|
||||
|
||||
if (chip->irq_reg_stride)
|
||||
d->irq_reg_stride = chip->irq_reg_stride;
|
||||
else
|
||||
d->irq_reg_stride = 1;
|
||||
|
||||
if (!map->use_single_rw && map->reg_stride == 1 &&
|
||||
d->irq_reg_stride == 1) {
|
||||
d->status_reg_buf = kmalloc(map->format.val_bytes *
|
||||
chip->num_regs, GFP_KERNEL);
|
||||
if (!d->status_reg_buf)
|
||||
goto err_alloc;
|
||||
}
|
||||
|
||||
mutex_init(&d->lock);
|
||||
|
||||
for (i = 0; i < chip->num_irqs; i++)
|
||||
d->mask_buf_def[chip->irqs[i].reg_offset / map->reg_stride]
|
||||
|= chip->irqs[i].mask;
|
||||
|
||||
/* Mask all the interrupts by default */
|
||||
for (i = 0; i < chip->num_regs; i++) {
|
||||
d->mask_buf[i] = d->mask_buf_def[i];
|
||||
reg = chip->mask_base +
|
||||
(i * map->reg_stride * d->irq_reg_stride);
|
||||
if (chip->mask_invert)
|
||||
ret = regmap_update_bits(map, reg,
|
||||
d->mask_buf[i], ~d->mask_buf[i]);
|
||||
else
|
||||
ret = regmap_update_bits(map, reg,
|
||||
d->mask_buf[i], d->mask_buf[i]);
|
||||
if (ret != 0) {
|
||||
dev_err(map->dev, "Failed to set masks in 0x%x: %d\n",
|
||||
reg, ret);
|
||||
goto err_alloc;
|
||||
}
|
||||
|
||||
if (!chip->init_ack_masked)
|
||||
continue;
|
||||
|
||||
/* Ack masked but set interrupts */
|
||||
reg = chip->status_base +
|
||||
(i * map->reg_stride * d->irq_reg_stride);
|
||||
ret = regmap_read(map, reg, &d->status_buf[i]);
|
||||
if (ret != 0) {
|
||||
dev_err(map->dev, "Failed to read IRQ status: %d\n",
|
||||
ret);
|
||||
goto err_alloc;
|
||||
}
|
||||
|
||||
if (d->status_buf[i] && (chip->ack_base || chip->use_ack)) {
|
||||
reg = chip->ack_base +
|
||||
(i * map->reg_stride * d->irq_reg_stride);
|
||||
ret = regmap_write(map, reg,
|
||||
d->status_buf[i] & d->mask_buf[i]);
|
||||
if (ret != 0) {
|
||||
dev_err(map->dev, "Failed to ack 0x%x: %d\n",
|
||||
reg, ret);
|
||||
goto err_alloc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Wake is disabled by default */
|
||||
if (d->wake_buf) {
|
||||
for (i = 0; i < chip->num_regs; i++) {
|
||||
d->wake_buf[i] = d->mask_buf_def[i];
|
||||
reg = chip->wake_base +
|
||||
(i * map->reg_stride * d->irq_reg_stride);
|
||||
|
||||
if (chip->wake_invert)
|
||||
ret = regmap_update_bits(map, reg,
|
||||
d->mask_buf_def[i],
|
||||
0);
|
||||
else
|
||||
ret = regmap_update_bits(map, reg,
|
||||
d->mask_buf_def[i],
|
||||
d->wake_buf[i]);
|
||||
if (ret != 0) {
|
||||
dev_err(map->dev, "Failed to set masks in 0x%x: %d\n",
|
||||
reg, ret);
|
||||
goto err_alloc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (irq_base)
|
||||
d->domain = irq_domain_add_legacy(map->dev->of_node,
|
||||
chip->num_irqs, irq_base, 0,
|
||||
®map_domain_ops, d);
|
||||
else
|
||||
d->domain = irq_domain_add_linear(map->dev->of_node,
|
||||
chip->num_irqs,
|
||||
®map_domain_ops, d);
|
||||
if (!d->domain) {
|
||||
dev_err(map->dev, "Failed to create IRQ domain\n");
|
||||
ret = -ENOMEM;
|
||||
goto err_alloc;
|
||||
}
|
||||
|
||||
ret = request_threaded_irq(irq, NULL, regmap_irq_thread, irq_flags,
|
||||
chip->name, d);
|
||||
if (ret != 0) {
|
||||
dev_err(map->dev, "Failed to request IRQ %d for %s: %d\n",
|
||||
irq, chip->name, ret);
|
||||
goto err_domain;
|
||||
}
|
||||
|
||||
*data = d;
|
||||
|
||||
return 0;
|
||||
|
||||
err_domain:
|
||||
/* Should really dispose of the domain but... */
|
||||
err_alloc:
|
||||
kfree(d->wake_buf);
|
||||
kfree(d->mask_buf_def);
|
||||
kfree(d->mask_buf);
|
||||
kfree(d->status_buf);
|
||||
kfree(d->status_reg_buf);
|
||||
kfree(d);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regmap_add_irq_chip);
|
||||
|
||||
/**
|
||||
* regmap_del_irq_chip(): Stop interrupt handling for a regmap IRQ chip
|
||||
*
|
||||
* @irq: Primary IRQ for the device
|
||||
* @d: regmap_irq_chip_data allocated by regmap_add_irq_chip()
|
||||
*/
|
||||
void regmap_del_irq_chip(int irq, struct regmap_irq_chip_data *d)
|
||||
{
|
||||
if (!d)
|
||||
return;
|
||||
|
||||
free_irq(irq, d);
|
||||
irq_domain_remove(d->domain);
|
||||
kfree(d->wake_buf);
|
||||
kfree(d->mask_buf_def);
|
||||
kfree(d->mask_buf);
|
||||
kfree(d->status_reg_buf);
|
||||
kfree(d->status_buf);
|
||||
kfree(d);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regmap_del_irq_chip);
|
||||
|
||||
/**
|
||||
* regmap_irq_chip_get_base(): Retrieve interrupt base for a regmap IRQ chip
|
||||
*
|
||||
* Useful for drivers to request their own IRQs.
|
||||
*
|
||||
* @data: regmap_irq controller to operate on.
|
||||
*/
|
||||
int regmap_irq_chip_get_base(struct regmap_irq_chip_data *data)
|
||||
{
|
||||
WARN_ON(!data->irq_base);
|
||||
return data->irq_base;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regmap_irq_chip_get_base);
|
||||
|
||||
/**
|
||||
* regmap_irq_get_virq(): Map an interrupt on a chip to a virtual IRQ
|
||||
*
|
||||
* Useful for drivers to request their own IRQs.
|
||||
*
|
||||
* @data: regmap_irq controller to operate on.
|
||||
* @irq: index of the interrupt requested in the chip IRQs
|
||||
*/
|
||||
int regmap_irq_get_virq(struct regmap_irq_chip_data *data, int irq)
|
||||
{
|
||||
/* Handle holes in the IRQ list */
|
||||
if (!data->chip->irqs[irq].mask)
|
||||
return -EINVAL;
|
||||
|
||||
return irq_create_mapping(data->domain, irq);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regmap_irq_get_virq);
|
||||
|
||||
/**
|
||||
* regmap_irq_get_domain(): Retrieve the irq_domain for the chip
|
||||
*
|
||||
* Useful for drivers to request their own IRQs and for integration
|
||||
* with subsystems. For ease of integration NULL is accepted as a
|
||||
* domain, allowing devices to just call this even if no domain is
|
||||
* allocated.
|
||||
*
|
||||
* @data: regmap_irq controller to operate on.
|
||||
*/
|
||||
struct irq_domain *regmap_irq_get_domain(struct regmap_irq_chip_data *data)
|
||||
{
|
||||
if (data)
|
||||
return data->domain;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regmap_irq_get_domain);
|
350
drivers/base/regmap/regmap-mmio.c
Normal file
350
drivers/base/regmap/regmap-mmio.c
Normal file
|
@ -0,0 +1,350 @@
|
|||
/*
|
||||
* Register map access API - MMIO support
|
||||
*
|
||||
* Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
struct regmap_mmio_context {
|
||||
void __iomem *regs;
|
||||
unsigned reg_bytes;
|
||||
unsigned val_bytes;
|
||||
unsigned pad_bytes;
|
||||
struct clk *clk;
|
||||
};
|
||||
|
||||
static inline void regmap_mmio_regsize_check(size_t reg_size)
|
||||
{
|
||||
switch (reg_size) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 4:
|
||||
#ifdef CONFIG_64BIT
|
||||
case 8:
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
BUG();
|
||||
}
|
||||
}
|
||||
|
||||
static int regmap_mmio_regbits_check(size_t reg_bits)
|
||||
{
|
||||
switch (reg_bits) {
|
||||
case 8:
|
||||
case 16:
|
||||
case 32:
|
||||
#ifdef CONFIG_64BIT
|
||||
case 64:
|
||||
#endif
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void regmap_mmio_count_check(size_t count, u32 offset)
|
||||
{
|
||||
BUG_ON(count <= offset);
|
||||
}
|
||||
|
||||
static inline unsigned int
|
||||
regmap_mmio_get_offset(const void *reg, size_t reg_size)
|
||||
{
|
||||
switch (reg_size) {
|
||||
case 1:
|
||||
return *(u8 *)reg;
|
||||
case 2:
|
||||
return *(u16 *)reg;
|
||||
case 4:
|
||||
return *(u32 *)reg;
|
||||
#ifdef CONFIG_64BIT
|
||||
case 8:
|
||||
return *(u64 *)reg;
|
||||
#endif
|
||||
default:
|
||||
BUG();
|
||||
}
|
||||
}
|
||||
|
||||
static int regmap_mmio_gather_write(void *context,
|
||||
const void *reg, size_t reg_size,
|
||||
const void *val, size_t val_size)
|
||||
{
|
||||
struct regmap_mmio_context *ctx = context;
|
||||
unsigned int offset;
|
||||
int ret;
|
||||
|
||||
regmap_mmio_regsize_check(reg_size);
|
||||
|
||||
if (!IS_ERR(ctx->clk)) {
|
||||
ret = clk_enable(ctx->clk);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
offset = regmap_mmio_get_offset(reg, reg_size);
|
||||
|
||||
while (val_size) {
|
||||
switch (ctx->val_bytes) {
|
||||
case 1:
|
||||
writeb(*(u8 *)val, ctx->regs + offset);
|
||||
break;
|
||||
case 2:
|
||||
writew(*(u16 *)val, ctx->regs + offset);
|
||||
break;
|
||||
case 4:
|
||||
writel(*(u32 *)val, ctx->regs + offset);
|
||||
break;
|
||||
#ifdef CONFIG_64BIT
|
||||
case 8:
|
||||
writeq(*(u64 *)val, ctx->regs + offset);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
/* Should be caught by regmap_mmio_check_config */
|
||||
BUG();
|
||||
}
|
||||
val_size -= ctx->val_bytes;
|
||||
val += ctx->val_bytes;
|
||||
offset += ctx->val_bytes;
|
||||
}
|
||||
|
||||
if (!IS_ERR(ctx->clk))
|
||||
clk_disable(ctx->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int regmap_mmio_write(void *context, const void *data, size_t count)
|
||||
{
|
||||
struct regmap_mmio_context *ctx = context;
|
||||
unsigned int offset = ctx->reg_bytes + ctx->pad_bytes;
|
||||
|
||||
regmap_mmio_count_check(count, offset);
|
||||
|
||||
return regmap_mmio_gather_write(context, data, ctx->reg_bytes,
|
||||
data + offset, count - offset);
|
||||
}
|
||||
|
||||
static int regmap_mmio_read(void *context,
|
||||
const void *reg, size_t reg_size,
|
||||
void *val, size_t val_size)
|
||||
{
|
||||
struct regmap_mmio_context *ctx = context;
|
||||
unsigned int offset;
|
||||
int ret;
|
||||
|
||||
regmap_mmio_regsize_check(reg_size);
|
||||
|
||||
if (!IS_ERR(ctx->clk)) {
|
||||
ret = clk_enable(ctx->clk);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
offset = regmap_mmio_get_offset(reg, reg_size);
|
||||
|
||||
while (val_size) {
|
||||
switch (ctx->val_bytes) {
|
||||
case 1:
|
||||
*(u8 *)val = readb(ctx->regs + offset);
|
||||
break;
|
||||
case 2:
|
||||
*(u16 *)val = readw(ctx->regs + offset);
|
||||
break;
|
||||
case 4:
|
||||
*(u32 *)val = readl(ctx->regs + offset);
|
||||
break;
|
||||
#ifdef CONFIG_64BIT
|
||||
case 8:
|
||||
*(u64 *)val = readq(ctx->regs + offset);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
/* Should be caught by regmap_mmio_check_config */
|
||||
BUG();
|
||||
}
|
||||
val_size -= ctx->val_bytes;
|
||||
val += ctx->val_bytes;
|
||||
offset += ctx->val_bytes;
|
||||
}
|
||||
|
||||
if (!IS_ERR(ctx->clk))
|
||||
clk_disable(ctx->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void regmap_mmio_free_context(void *context)
|
||||
{
|
||||
struct regmap_mmio_context *ctx = context;
|
||||
|
||||
if (!IS_ERR(ctx->clk)) {
|
||||
clk_unprepare(ctx->clk);
|
||||
clk_put(ctx->clk);
|
||||
}
|
||||
kfree(context);
|
||||
}
|
||||
|
||||
static struct regmap_bus regmap_mmio = {
|
||||
.fast_io = true,
|
||||
.write = regmap_mmio_write,
|
||||
.gather_write = regmap_mmio_gather_write,
|
||||
.read = regmap_mmio_read,
|
||||
.free_context = regmap_mmio_free_context,
|
||||
.reg_format_endian_default = REGMAP_ENDIAN_NATIVE,
|
||||
.val_format_endian_default = REGMAP_ENDIAN_NATIVE,
|
||||
};
|
||||
|
||||
static struct regmap_mmio_context *regmap_mmio_gen_context(struct device *dev,
|
||||
const char *clk_id,
|
||||
void __iomem *regs,
|
||||
const struct regmap_config *config)
|
||||
{
|
||||
struct regmap_mmio_context *ctx;
|
||||
int min_stride;
|
||||
int ret;
|
||||
|
||||
ret = regmap_mmio_regbits_check(config->reg_bits);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
if (config->pad_bits)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
switch (config->val_bits) {
|
||||
case 8:
|
||||
/* The core treats 0 as 1 */
|
||||
min_stride = 0;
|
||||
break;
|
||||
case 16:
|
||||
min_stride = 2;
|
||||
break;
|
||||
case 32:
|
||||
min_stride = 4;
|
||||
break;
|
||||
#ifdef CONFIG_64BIT
|
||||
case 64:
|
||||
min_stride = 8;
|
||||
break;
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
if (config->reg_stride < min_stride)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
switch (config->reg_format_endian) {
|
||||
case REGMAP_ENDIAN_DEFAULT:
|
||||
case REGMAP_ENDIAN_NATIVE:
|
||||
break;
|
||||
default:
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
|
||||
if (!ctx)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
ctx->regs = regs;
|
||||
ctx->val_bytes = config->val_bits / 8;
|
||||
ctx->reg_bytes = config->reg_bits / 8;
|
||||
ctx->pad_bytes = config->pad_bits / 8;
|
||||
ctx->clk = ERR_PTR(-ENODEV);
|
||||
|
||||
if (clk_id == NULL)
|
||||
return ctx;
|
||||
|
||||
ctx->clk = clk_get(dev, clk_id);
|
||||
if (IS_ERR(ctx->clk)) {
|
||||
ret = PTR_ERR(ctx->clk);
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
ret = clk_prepare(ctx->clk);
|
||||
if (ret < 0) {
|
||||
clk_put(ctx->clk);
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
return ctx;
|
||||
|
||||
err_free:
|
||||
kfree(ctx);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* regmap_init_mmio_clk(): Initialise register map with register clock
|
||||
*
|
||||
* @dev: Device that will be interacted with
|
||||
* @clk_id: register clock consumer ID
|
||||
* @regs: Pointer to memory-mapped IO region
|
||||
* @config: Configuration for register map
|
||||
*
|
||||
* The return value will be an ERR_PTR() on error or a valid pointer to
|
||||
* a struct regmap.
|
||||
*/
|
||||
struct regmap *regmap_init_mmio_clk(struct device *dev, const char *clk_id,
|
||||
void __iomem *regs,
|
||||
const struct regmap_config *config)
|
||||
{
|
||||
struct regmap_mmio_context *ctx;
|
||||
|
||||
ctx = regmap_mmio_gen_context(dev, clk_id, regs, config);
|
||||
if (IS_ERR(ctx))
|
||||
return ERR_CAST(ctx);
|
||||
|
||||
return regmap_init(dev, ®map_mmio, ctx, config);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regmap_init_mmio_clk);
|
||||
|
||||
/**
|
||||
* devm_regmap_init_mmio_clk(): Initialise managed register map with clock
|
||||
*
|
||||
* @dev: Device that will be interacted with
|
||||
* @clk_id: register clock consumer ID
|
||||
* @regs: Pointer to memory-mapped IO region
|
||||
* @config: Configuration for register map
|
||||
*
|
||||
* The return value will be an ERR_PTR() on error or a valid pointer
|
||||
* to a struct regmap. The regmap will be automatically freed by the
|
||||
* device management code.
|
||||
*/
|
||||
struct regmap *devm_regmap_init_mmio_clk(struct device *dev, const char *clk_id,
|
||||
void __iomem *regs,
|
||||
const struct regmap_config *config)
|
||||
{
|
||||
struct regmap_mmio_context *ctx;
|
||||
|
||||
ctx = regmap_mmio_gen_context(dev, clk_id, regs, config);
|
||||
if (IS_ERR(ctx))
|
||||
return ERR_CAST(ctx);
|
||||
|
||||
return devm_regmap_init(dev, ®map_mmio, ctx, config);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_regmap_init_mmio_clk);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
149
drivers/base/regmap/regmap-spi.c
Normal file
149
drivers/base/regmap/regmap-spi.c
Normal file
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* Register map access API - SPI support
|
||||
*
|
||||
* 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/regmap.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
struct regmap_async_spi {
|
||||
struct regmap_async core;
|
||||
struct spi_message m;
|
||||
struct spi_transfer t[2];
|
||||
};
|
||||
|
||||
static void regmap_spi_complete(void *data)
|
||||
{
|
||||
struct regmap_async_spi *async = data;
|
||||
|
||||
regmap_async_complete_cb(&async->core, async->m.status);
|
||||
}
|
||||
|
||||
static int regmap_spi_write(void *context, const void *data, size_t count)
|
||||
{
|
||||
struct device *dev = context;
|
||||
struct spi_device *spi = to_spi_device(dev);
|
||||
|
||||
return spi_write(spi, data, count);
|
||||
}
|
||||
|
||||
static int regmap_spi_gather_write(void *context,
|
||||
const void *reg, size_t reg_len,
|
||||
const void *val, size_t val_len)
|
||||
{
|
||||
struct device *dev = context;
|
||||
struct spi_device *spi = to_spi_device(dev);
|
||||
struct spi_message m;
|
||||
struct spi_transfer t[2] = { { .tx_buf = reg, .len = reg_len, },
|
||||
{ .tx_buf = val, .len = val_len, }, };
|
||||
|
||||
spi_message_init(&m);
|
||||
spi_message_add_tail(&t[0], &m);
|
||||
spi_message_add_tail(&t[1], &m);
|
||||
|
||||
return spi_sync(spi, &m);
|
||||
}
|
||||
|
||||
static int regmap_spi_async_write(void *context,
|
||||
const void *reg, size_t reg_len,
|
||||
const void *val, size_t val_len,
|
||||
struct regmap_async *a)
|
||||
{
|
||||
struct regmap_async_spi *async = container_of(a,
|
||||
struct regmap_async_spi,
|
||||
core);
|
||||
struct device *dev = context;
|
||||
struct spi_device *spi = to_spi_device(dev);
|
||||
|
||||
async->t[0].tx_buf = reg;
|
||||
async->t[0].len = reg_len;
|
||||
async->t[1].tx_buf = val;
|
||||
async->t[1].len = val_len;
|
||||
|
||||
spi_message_init(&async->m);
|
||||
spi_message_add_tail(&async->t[0], &async->m);
|
||||
if (val)
|
||||
spi_message_add_tail(&async->t[1], &async->m);
|
||||
|
||||
async->m.complete = regmap_spi_complete;
|
||||
async->m.context = async;
|
||||
|
||||
return spi_async(spi, &async->m);
|
||||
}
|
||||
|
||||
static struct regmap_async *regmap_spi_async_alloc(void)
|
||||
{
|
||||
struct regmap_async_spi *async_spi;
|
||||
|
||||
async_spi = kzalloc(sizeof(*async_spi), GFP_KERNEL);
|
||||
if (!async_spi)
|
||||
return NULL;
|
||||
|
||||
return &async_spi->core;
|
||||
}
|
||||
|
||||
static int regmap_spi_read(void *context,
|
||||
const void *reg, size_t reg_size,
|
||||
void *val, size_t val_size)
|
||||
{
|
||||
struct device *dev = context;
|
||||
struct spi_device *spi = to_spi_device(dev);
|
||||
|
||||
return spi_write_then_read(spi, reg, reg_size, val, val_size);
|
||||
}
|
||||
|
||||
static struct regmap_bus regmap_spi = {
|
||||
.write = regmap_spi_write,
|
||||
.gather_write = regmap_spi_gather_write,
|
||||
.async_write = regmap_spi_async_write,
|
||||
.async_alloc = regmap_spi_async_alloc,
|
||||
.read = regmap_spi_read,
|
||||
.read_flag_mask = 0x80,
|
||||
.reg_format_endian_default = REGMAP_ENDIAN_BIG,
|
||||
.val_format_endian_default = REGMAP_ENDIAN_BIG,
|
||||
};
|
||||
|
||||
/**
|
||||
* regmap_init_spi(): Initialise register map
|
||||
*
|
||||
* @spi: Device that will be interacted with
|
||||
* @config: Configuration for register map
|
||||
*
|
||||
* The return value will be an ERR_PTR() on error or a valid pointer to
|
||||
* a struct regmap.
|
||||
*/
|
||||
struct regmap *regmap_init_spi(struct spi_device *spi,
|
||||
const struct regmap_config *config)
|
||||
{
|
||||
return regmap_init(&spi->dev, ®map_spi, &spi->dev, config);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regmap_init_spi);
|
||||
|
||||
/**
|
||||
* devm_regmap_init_spi(): Initialise register map
|
||||
*
|
||||
* @spi: Device that will be interacted with
|
||||
* @config: Configuration for register map
|
||||
*
|
||||
* The return value will be an ERR_PTR() on error or a valid pointer
|
||||
* to a struct regmap. The map will be automatically freed by the
|
||||
* device management code.
|
||||
*/
|
||||
struct regmap *devm_regmap_init_spi(struct spi_device *spi,
|
||||
const struct regmap_config *config)
|
||||
{
|
||||
return devm_regmap_init(&spi->dev, ®map_spi, &spi->dev, config);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_regmap_init_spi);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
256
drivers/base/regmap/regmap-spmi.c
Normal file
256
drivers/base/regmap/regmap-spmi.c
Normal file
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
* Register map access API - SPMI support
|
||||
*
|
||||
* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* Based on regmap-i2c.c:
|
||||
* 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 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/spmi.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
|
||||
static int regmap_spmi_base_read(void *context,
|
||||
const void *reg, size_t reg_size,
|
||||
void *val, size_t val_size)
|
||||
{
|
||||
u8 addr = *(u8 *)reg;
|
||||
int err = 0;
|
||||
|
||||
BUG_ON(reg_size != 1);
|
||||
|
||||
while (val_size-- && !err)
|
||||
err = spmi_register_read(context, addr++, val++);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int regmap_spmi_base_gather_write(void *context,
|
||||
const void *reg, size_t reg_size,
|
||||
const void *val, size_t val_size)
|
||||
{
|
||||
const u8 *data = val;
|
||||
u8 addr = *(u8 *)reg;
|
||||
int err = 0;
|
||||
|
||||
BUG_ON(reg_size != 1);
|
||||
|
||||
/*
|
||||
* SPMI defines a more bandwidth-efficient 'Register 0 Write' sequence,
|
||||
* use it when possible.
|
||||
*/
|
||||
if (addr == 0 && val_size) {
|
||||
err = spmi_register_zero_write(context, *data);
|
||||
if (err)
|
||||
goto err_out;
|
||||
|
||||
data++;
|
||||
addr++;
|
||||
val_size--;
|
||||
}
|
||||
|
||||
while (val_size) {
|
||||
err = spmi_register_write(context, addr, *data);
|
||||
if (err)
|
||||
goto err_out;
|
||||
|
||||
data++;
|
||||
addr++;
|
||||
val_size--;
|
||||
}
|
||||
|
||||
err_out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int regmap_spmi_base_write(void *context, const void *data,
|
||||
size_t count)
|
||||
{
|
||||
BUG_ON(count < 1);
|
||||
return regmap_spmi_base_gather_write(context, data, 1, data + 1,
|
||||
count - 1);
|
||||
}
|
||||
|
||||
static struct regmap_bus regmap_spmi_base = {
|
||||
.read = regmap_spmi_base_read,
|
||||
.write = regmap_spmi_base_write,
|
||||
.gather_write = regmap_spmi_base_gather_write,
|
||||
.reg_format_endian_default = REGMAP_ENDIAN_NATIVE,
|
||||
.val_format_endian_default = REGMAP_ENDIAN_NATIVE,
|
||||
};
|
||||
|
||||
/**
|
||||
* regmap_init_spmi_base(): Create regmap for the Base register space
|
||||
* @sdev: SPMI device that will be interacted with
|
||||
* @config: Configuration for register map
|
||||
*
|
||||
* The return value will be an ERR_PTR() on error or a valid pointer to
|
||||
* a struct regmap.
|
||||
*/
|
||||
struct regmap *regmap_init_spmi_base(struct spmi_device *sdev,
|
||||
const struct regmap_config *config)
|
||||
{
|
||||
return regmap_init(&sdev->dev, ®map_spmi_base, sdev, config);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regmap_init_spmi_base);
|
||||
|
||||
/**
|
||||
* devm_regmap_init_spmi_base(): Create managed regmap for Base register space
|
||||
* @sdev: SPMI device that will be interacted with
|
||||
* @config: Configuration for register map
|
||||
*
|
||||
* The return value will be an ERR_PTR() on error or a valid pointer
|
||||
* to a struct regmap. The regmap will be automatically freed by the
|
||||
* device management code.
|
||||
*/
|
||||
struct regmap *devm_regmap_init_spmi_base(struct spmi_device *sdev,
|
||||
const struct regmap_config *config)
|
||||
{
|
||||
return devm_regmap_init(&sdev->dev, ®map_spmi_base, sdev, config);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_regmap_init_spmi_base);
|
||||
|
||||
static int regmap_spmi_ext_read(void *context,
|
||||
const void *reg, size_t reg_size,
|
||||
void *val, size_t val_size)
|
||||
{
|
||||
int err = 0;
|
||||
size_t len;
|
||||
u16 addr;
|
||||
|
||||
BUG_ON(reg_size != 2);
|
||||
|
||||
addr = *(u16 *)reg;
|
||||
|
||||
/*
|
||||
* Split accesses into two to take advantage of the more
|
||||
* bandwidth-efficient 'Extended Register Read' command when possible
|
||||
*/
|
||||
while (addr <= 0xFF && val_size) {
|
||||
len = min_t(size_t, val_size, 16);
|
||||
|
||||
err = spmi_ext_register_read(context, addr, val, len);
|
||||
if (err)
|
||||
goto err_out;
|
||||
|
||||
addr += len;
|
||||
val += len;
|
||||
val_size -= len;
|
||||
}
|
||||
|
||||
while (val_size) {
|
||||
len = min_t(size_t, val_size, 8);
|
||||
|
||||
err = spmi_ext_register_readl(context, addr, val, val_size);
|
||||
if (err)
|
||||
goto err_out;
|
||||
|
||||
addr += len;
|
||||
val += len;
|
||||
val_size -= len;
|
||||
}
|
||||
|
||||
err_out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int regmap_spmi_ext_gather_write(void *context,
|
||||
const void *reg, size_t reg_size,
|
||||
const void *val, size_t val_size)
|
||||
{
|
||||
int err = 0;
|
||||
size_t len;
|
||||
u16 addr;
|
||||
|
||||
BUG_ON(reg_size != 2);
|
||||
|
||||
addr = *(u16 *)reg;
|
||||
|
||||
while (addr <= 0xFF && val_size) {
|
||||
len = min_t(size_t, val_size, 16);
|
||||
|
||||
err = spmi_ext_register_write(context, addr, val, len);
|
||||
if (err)
|
||||
goto err_out;
|
||||
|
||||
addr += len;
|
||||
val += len;
|
||||
val_size -= len;
|
||||
}
|
||||
|
||||
while (val_size) {
|
||||
len = min_t(size_t, val_size, 8);
|
||||
|
||||
err = spmi_ext_register_writel(context, addr, val, len);
|
||||
if (err)
|
||||
goto err_out;
|
||||
|
||||
addr += len;
|
||||
val += len;
|
||||
val_size -= len;
|
||||
}
|
||||
|
||||
err_out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int regmap_spmi_ext_write(void *context, const void *data,
|
||||
size_t count)
|
||||
{
|
||||
BUG_ON(count < 2);
|
||||
return regmap_spmi_ext_gather_write(context, data, 2, data + 2,
|
||||
count - 2);
|
||||
}
|
||||
|
||||
static struct regmap_bus regmap_spmi_ext = {
|
||||
.read = regmap_spmi_ext_read,
|
||||
.write = regmap_spmi_ext_write,
|
||||
.gather_write = regmap_spmi_ext_gather_write,
|
||||
.reg_format_endian_default = REGMAP_ENDIAN_NATIVE,
|
||||
.val_format_endian_default = REGMAP_ENDIAN_NATIVE,
|
||||
};
|
||||
|
||||
/**
|
||||
* regmap_init_spmi_ext(): Create regmap for Ext register space
|
||||
* @sdev: Device that will be interacted with
|
||||
* @config: Configuration for register map
|
||||
*
|
||||
* The return value will be an ERR_PTR() on error or a valid pointer to
|
||||
* a struct regmap.
|
||||
*/
|
||||
struct regmap *regmap_init_spmi_ext(struct spmi_device *sdev,
|
||||
const struct regmap_config *config)
|
||||
{
|
||||
return regmap_init(&sdev->dev, ®map_spmi_ext, sdev, config);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(regmap_init_spmi_ext);
|
||||
|
||||
/**
|
||||
* devm_regmap_init_spmi_ext(): Create managed regmap for Ext register space
|
||||
* @sdev: SPMI device that will be interacted with
|
||||
* @config: Configuration for register map
|
||||
*
|
||||
* The return value will be an ERR_PTR() on error or a valid pointer
|
||||
* to a struct regmap. The regmap will be automatically freed by the
|
||||
* device management code.
|
||||
*/
|
||||
struct regmap *devm_regmap_init_spmi_ext(struct spmi_device *sdev,
|
||||
const struct regmap_config *config)
|
||||
{
|
||||
return devm_regmap_init(&sdev->dev, ®map_spmi_ext, sdev, config);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_regmap_init_spmi_ext);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
2645
drivers/base/regmap/regmap.c
Normal file
2645
drivers/base/regmap/regmap.c
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue