2009-07-05 20:24:50 +04:00
/*
* soc - cache . c - - ASoC register cache helpers
*
* Copyright 2009 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 as published by the
* Free Software Foundation ; either version 2 of the License , or ( at your
* option ) any later version .
*/
2009-07-11 01:24:27 +04:00
# include <linux/i2c.h>
2009-07-11 02:28:16 +04:00
# include <linux/spi/spi.h>
2009-07-05 20:24:50 +04:00
# include <sound/soc.h>
2010-11-11 13:04:58 +03:00
# include <linux/bitmap.h>
2010-11-11 13:04:59 +03:00
# include <linux/rbtree.h>
2011-09-22 17:34:58 +04:00
# include <linux/export.h>
2009-07-05 20:24:50 +04:00
2011-01-21 18:29:02 +03:00
# include <trace/events/asoc.h>
2011-01-11 14:29:49 +03:00
static bool snd_soc_set_cache_val ( void * base , unsigned int idx ,
unsigned int val , unsigned int word_size )
{
switch ( word_size ) {
case 1 : {
u8 * cache = base ;
if ( cache [ idx ] = = val )
return true ;
cache [ idx ] = val ;
break ;
}
case 2 : {
u16 * cache = base ;
if ( cache [ idx ] = = val )
return true ;
cache [ idx ] = val ;
break ;
}
default :
BUG ( ) ;
}
return false ;
}
static unsigned int snd_soc_get_cache_val ( const void * base , unsigned int idx ,
unsigned int word_size )
{
2011-06-06 14:26:15 +04:00
if ( ! base )
return - 1 ;
2011-01-11 14:29:49 +03:00
switch ( word_size ) {
case 1 : {
const u8 * cache = base ;
return cache [ idx ] ;
}
case 2 : {
const u16 * cache = base ;
return cache [ idx ] ;
}
default :
BUG ( ) ;
}
/* unreachable */
return - 1 ;
}
2010-11-11 13:04:57 +03:00
static int snd_soc_flat_cache_sync ( struct snd_soc_codec * codec )
{
int i ;
2010-11-29 13:24:54 +03:00
int ret ;
2010-12-02 19:21:08 +03:00
const struct snd_soc_codec_driver * codec_drv ;
2010-11-11 13:04:57 +03:00
unsigned int val ;
codec_drv = codec - > driver ;
for ( i = 0 ; i < codec_drv - > reg_cache_size ; + + i ) {
2010-11-29 13:24:54 +03:00
ret = snd_soc_cache_read ( codec , i , & val ) ;
if ( ret )
return ret ;
2011-01-12 13:22:28 +03:00
if ( codec - > reg_def_copy )
if ( snd_soc_get_cache_val ( codec - > reg_def_copy ,
2011-01-11 14:29:49 +03:00
i , codec_drv - > reg_word_size ) = = val )
continue ;
2011-08-27 20:24:13 +04:00
WARN_ON ( ! snd_soc_codec_writable_register ( codec , i ) ) ;
2010-11-29 13:24:54 +03:00
ret = snd_soc_write ( codec , i , val ) ;
if ( ret )
return ret ;
2012-11-19 18:39:13 +04:00
dev_dbg ( codec - > dev , " ASoC: Synced register %#x, value = %#x \n " ,
2010-11-11 13:04:57 +03:00
i , val ) ;
}
return 0 ;
}
static int snd_soc_flat_cache_write ( struct snd_soc_codec * codec ,
unsigned int reg , unsigned int value )
{
2011-01-11 14:29:49 +03:00
snd_soc_set_cache_val ( codec - > reg_cache , reg , value ,
codec - > driver - > reg_word_size ) ;
2010-11-11 13:04:57 +03:00
return 0 ;
}
static int snd_soc_flat_cache_read ( struct snd_soc_codec * codec ,
unsigned int reg , unsigned int * value )
{
2011-01-11 14:29:49 +03:00
* value = snd_soc_get_cache_val ( codec - > reg_cache , reg ,
codec - > driver - > reg_word_size ) ;
2010-11-11 13:04:57 +03:00
return 0 ;
}
static int snd_soc_flat_cache_exit ( struct snd_soc_codec * codec )
{
if ( ! codec - > reg_cache )
return 0 ;
kfree ( codec - > reg_cache ) ;
codec - > reg_cache = NULL ;
return 0 ;
}
static int snd_soc_flat_cache_init ( struct snd_soc_codec * codec )
{
2011-01-12 13:22:28 +03:00
if ( codec - > reg_def_copy )
codec - > reg_cache = kmemdup ( codec - > reg_def_copy ,
2011-01-12 13:38:58 +03:00
codec - > reg_size , GFP_KERNEL ) ;
2010-11-11 13:04:57 +03:00
else
2011-01-12 13:38:58 +03:00
codec - > reg_cache = kzalloc ( codec - > reg_size , GFP_KERNEL ) ;
2010-11-11 13:04:57 +03:00
if ( ! codec - > reg_cache )
return - ENOMEM ;
return 0 ;
}
/* an array of all supported compression types */
static const struct snd_soc_cache_ops cache_types [ ] = {
2010-12-21 20:09:48 +03:00
/* Flat *must* be the first entry for fallback */
2010-11-11 13:04:57 +03:00
{
2010-11-29 13:54:28 +03:00
. id = SND_SOC_FLAT_COMPRESSION ,
2010-12-06 12:51:57 +03:00
. name = " flat " ,
2010-11-11 13:04:57 +03:00
. init = snd_soc_flat_cache_init ,
. exit = snd_soc_flat_cache_exit ,
. read = snd_soc_flat_cache_read ,
. write = snd_soc_flat_cache_write ,
. sync = snd_soc_flat_cache_sync
2010-11-11 13:04:58 +03:00
} ,
2010-11-11 13:04:57 +03:00
} ;
int snd_soc_cache_init ( struct snd_soc_codec * codec )
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( cache_types ) ; + + i )
2010-12-02 17:53:01 +03:00
if ( cache_types [ i ] . id = = codec - > compress_type )
2010-11-11 13:04:57 +03:00
break ;
2010-12-21 20:09:48 +03:00
/* Fall back to flat compression */
2010-11-11 13:04:57 +03:00
if ( i = = ARRAY_SIZE ( cache_types ) ) {
2012-11-19 18:39:13 +04:00
dev_warn ( codec - > dev , " ASoC: Could not match compress type: %d \n " ,
2010-12-21 20:09:48 +03:00
codec - > compress_type ) ;
i = 0 ;
2010-11-11 13:04:57 +03:00
}
mutex_init ( & codec - > cache_rw_mutex ) ;
codec - > cache_ops = & cache_types [ i ] ;
2010-12-06 12:51:57 +03:00
if ( codec - > cache_ops - > init ) {
if ( codec - > cache_ops - > name )
2012-11-19 18:39:13 +04:00
dev_dbg ( codec - > dev , " ASoC: Initializing %s cache for %s codec \n " ,
2010-12-06 12:51:57 +03:00
codec - > cache_ops - > name , codec - > name ) ;
2010-11-11 13:04:57 +03:00
return codec - > cache_ops - > init ( codec ) ;
2010-12-06 12:51:57 +03:00
}
2011-03-22 13:48:49 +03:00
return - ENOSYS ;
2010-11-11 13:04:57 +03:00
}
/*
* NOTE : keep in mind that this function might be called
* multiple times .
*/
int snd_soc_cache_exit ( struct snd_soc_codec * codec )
{
2010-12-06 12:51:57 +03:00
if ( codec - > cache_ops & & codec - > cache_ops - > exit ) {
if ( codec - > cache_ops - > name )
2012-11-19 18:39:13 +04:00
dev_dbg ( codec - > dev , " ASoC: Destroying %s cache for %s codec \n " ,
2010-12-06 12:51:57 +03:00
codec - > cache_ops - > name , codec - > name ) ;
2010-11-11 13:04:57 +03:00
return codec - > cache_ops - > exit ( codec ) ;
2010-12-06 12:51:57 +03:00
}
2011-03-22 13:48:49 +03:00
return - ENOSYS ;
2010-11-11 13:04:57 +03:00
}
/**
* snd_soc_cache_read : Fetch the value of a given register from the cache .
*
* @ codec : CODEC to configure .
* @ reg : The register index .
* @ value : The value to be returned .
*/
int snd_soc_cache_read ( struct snd_soc_codec * codec ,
unsigned int reg , unsigned int * value )
{
int ret ;
mutex_lock ( & codec - > cache_rw_mutex ) ;
if ( value & & codec - > cache_ops & & codec - > cache_ops - > read ) {
ret = codec - > cache_ops - > read ( codec , reg , value ) ;
mutex_unlock ( & codec - > cache_rw_mutex ) ;
return ret ;
}
mutex_unlock ( & codec - > cache_rw_mutex ) ;
2011-03-22 13:48:49 +03:00
return - ENOSYS ;
2010-11-11 13:04:57 +03:00
}
EXPORT_SYMBOL_GPL ( snd_soc_cache_read ) ;
/**
* snd_soc_cache_write : Set the value of a given register in the cache .
*
* @ codec : CODEC to configure .
* @ reg : The register index .
* @ value : The new register value .
*/
int snd_soc_cache_write ( struct snd_soc_codec * codec ,
unsigned int reg , unsigned int value )
{
int ret ;
mutex_lock ( & codec - > cache_rw_mutex ) ;
if ( codec - > cache_ops & & codec - > cache_ops - > write ) {
ret = codec - > cache_ops - > write ( codec , reg , value ) ;
mutex_unlock ( & codec - > cache_rw_mutex ) ;
return ret ;
}
mutex_unlock ( & codec - > cache_rw_mutex ) ;
2011-03-22 13:48:49 +03:00
return - ENOSYS ;
2010-11-11 13:04:57 +03:00
}
EXPORT_SYMBOL_GPL ( snd_soc_cache_write ) ;
/**
* snd_soc_cache_sync : Sync the register cache with the hardware .
*
* @ codec : CODEC 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 .
*/
int snd_soc_cache_sync ( struct snd_soc_codec * codec )
{
int ret ;
2011-01-21 18:29:02 +03:00
const char * name ;
2010-11-11 13:04:57 +03:00
if ( ! codec - > cache_sync ) {
return 0 ;
}
2011-02-07 22:01:41 +03:00
if ( ! codec - > cache_ops | | ! codec - > cache_ops - > sync )
2011-03-22 13:48:49 +03:00
return - ENOSYS ;
2011-02-07 22:01:41 +03:00
2011-01-21 18:29:02 +03:00
if ( codec - > cache_ops - > name )
name = codec - > cache_ops - > name ;
else
name = " unknown " ;
2011-02-07 22:01:41 +03:00
if ( codec - > cache_ops - > name )
2012-11-19 18:39:13 +04:00
dev_dbg ( codec - > dev , " ASoC: Syncing %s cache for %s codec \n " ,
2011-02-07 22:01:41 +03:00
codec - > cache_ops - > name , codec - > name ) ;
trace_snd_soc_cache_sync ( codec , name , " start " ) ;
ret = codec - > cache_ops - > sync ( codec ) ;
if ( ! ret )
codec - > cache_sync = 0 ;
trace_snd_soc_cache_sync ( codec , name , " end " ) ;
return ret ;
2010-11-11 13:04:57 +03:00
}
EXPORT_SYMBOL_GPL ( snd_soc_cache_sync ) ;
2011-01-13 15:20:36 +03:00
static int snd_soc_get_reg_access_index ( struct snd_soc_codec * codec ,
unsigned int reg )
{
const struct snd_soc_codec_driver * codec_drv ;
unsigned int min , max , index ;
codec_drv = codec - > driver ;
min = 0 ;
max = codec_drv - > reg_access_size - 1 ;
do {
index = ( min + max ) / 2 ;
if ( codec_drv - > reg_access_default [ index ] . reg = = reg )
return index ;
if ( codec_drv - > reg_access_default [ index ] . reg < reg )
min = index + 1 ;
else
max = index ;
} while ( min < = max ) ;
return - 1 ;
}
int snd_soc_default_volatile_register ( struct snd_soc_codec * codec ,
unsigned int reg )
{
int index ;
if ( reg > = codec - > driver - > reg_cache_size )
return 1 ;
index = snd_soc_get_reg_access_index ( codec , reg ) ;
if ( index < 0 )
return 0 ;
return codec - > driver - > reg_access_default [ index ] . vol ;
}
EXPORT_SYMBOL_GPL ( snd_soc_default_volatile_register ) ;
int snd_soc_default_readable_register ( struct snd_soc_codec * codec ,
unsigned int reg )
{
int index ;
if ( reg > = codec - > driver - > reg_cache_size )
return 1 ;
index = snd_soc_get_reg_access_index ( codec , reg ) ;
if ( index < 0 )
return 0 ;
return codec - > driver - > reg_access_default [ index ] . read ;
}
EXPORT_SYMBOL_GPL ( snd_soc_default_readable_register ) ;
2011-03-24 16:45:17 +03:00
int snd_soc_default_writable_register ( struct snd_soc_codec * codec ,
unsigned int reg )
{
int index ;
if ( reg > = codec - > driver - > reg_cache_size )
return 1 ;
index = snd_soc_get_reg_access_index ( codec , reg ) ;
if ( index < 0 )
return 0 ;
return codec - > driver - > reg_access_default [ index ] . write ;
}
EXPORT_SYMBOL_GPL ( snd_soc_default_writable_register ) ;