2011-09-19 17:34:03 +04:00
/*
* 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>
2012-01-22 20:23:42 +04:00
# include <linux/device.h>
2011-09-19 17:34:03 +04:00
# include <linux/lzo.h>
# include "internal.h"
2011-11-15 16:34:41 +04:00
static int regcache_lzo_exit ( struct regmap * map ) ;
2011-09-19 17:34:03 +04:00
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
2011-10-18 03:37:00 +04:00
static int regcache_lzo_block_count ( struct regmap * map )
2011-09-19 17:34:03 +04:00
{
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 )
{
2012-04-09 23:40:24 +04:00
return ( ( reg / map - > reg_stride ) * map - > cache_word_size ) /
2011-10-18 03:37:00 +04:00
DIV_ROUND_UP ( map - > cache_size_raw ,
regcache_lzo_block_count ( map ) ) ;
2011-09-19 17:34:03 +04:00
}
static inline int regcache_lzo_get_blkpos ( struct regmap * map ,
unsigned int reg )
{
2012-04-09 23:40:24 +04:00
return ( reg / map - > reg_stride ) %
( DIV_ROUND_UP ( map - > cache_size_raw ,
regcache_lzo_block_count ( map ) ) /
map - > cache_word_size ) ;
2011-09-19 17:34:03 +04:00
}
static inline int regcache_lzo_get_blksize ( struct regmap * map )
{
2011-10-18 03:37:00 +04:00
return DIV_ROUND_UP ( map - > cache_size_raw ,
regcache_lzo_block_count ( map ) ) ;
2011-09-19 17:34:03 +04:00
}
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 ;
2011-10-18 03:37:00 +04:00
blkcount = regcache_lzo_block_count ( map ) ;
2011-09-19 17:34:03 +04:00
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 :
2011-11-15 16:34:41 +04:00
regcache_lzo_exit ( map ) ;
2011-09-19 17:34:03 +04:00
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 ;
2011-10-18 03:37:00 +04:00
blkcount = regcache_lzo_block_count ( map ) ;
2011-09-19 17:34:03 +04:00
/*
* 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 */
2013-02-21 22:03:13 +04:00
* value = regcache_get_val ( map , lzo_block - > dst , blkpos ) ;
2011-09-19 17:34:03 +04:00
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 ;
2011-10-09 16:23:31 +04:00
return ret ;
2011-09-19 17:34:03 +04:00
}
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 */
2013-02-21 22:03:13 +04:00
if ( regcache_set_val ( map , lzo_block - > dst , blkpos , value ) ) {
2011-09-19 17:34:03 +04:00
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 */
2012-04-09 23:40:24 +04:00
set_bit ( reg / map - > reg_stride , lzo_block - > sync_bmp ) ;
2011-09-19 17:34:03 +04:00
kfree ( tmp_dst ) ;
kfree ( lzo_block - > src ) ;
return 0 ;
out :
lzo_block - > dst = tmp_dst ;
lzo_block - > dst_len = tmp_dst_len ;
return ret ;
}
2012-02-23 23:31:04 +04:00
static int regcache_lzo_sync ( struct regmap * map , unsigned int min ,
unsigned int max )
2011-09-19 17:34:03 +04:00
{
struct regcache_lzo_ctx * * lzo_blocks ;
unsigned int val ;
int i ;
int ret ;
lzo_blocks = map - > cache ;
2012-02-23 23:31:04 +04:00
i = min ;
2012-03-24 02:02:04 +04:00
for_each_set_bit_from ( i , lzo_blocks [ 0 ] - > sync_bmp ,
2012-02-23 23:31:04 +04:00
lzo_blocks [ 0 ] - > sync_bmp_nbits ) {
if ( i > max )
continue ;
2011-09-19 17:34:03 +04:00
ret = regcache_read ( map , i , & val ) ;
if ( ret )
return ret ;
2012-02-24 00:11:58 +04:00
/* Is this the hardware default? If so skip. */
ret = regcache_lookup_reg ( map , i ) ;
if ( ret > 0 & & val = = map - > reg_defaults [ ret ] . def )
continue ;
2011-09-19 17:34:03 +04:00
map - > cache_bypass = 1 ;
2011-09-29 17:36:25 +04:00
ret = _regmap_write ( map , i , val ) ;
2011-09-19 17:34:03 +04:00
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 = {
2011-11-02 19:00:03 +04:00
. type = REGCACHE_COMPRESSED ,
2011-09-19 17:34:03 +04:00
. name = " lzo " ,
. init = regcache_lzo_init ,
. exit = regcache_lzo_exit ,
. read = regcache_lzo_read ,
. write = regcache_lzo_write ,
. sync = regcache_lzo_sync
} ;