2011-03-07 10:04:55 +03:00
/*
* LM4857 AMP driver
*
* Copyright 2007 Wolfson Microelectronics PLC .
* Author : Graeme Gregory
2011-10-06 14:10:01 +04:00
* graeme . gregory @ wolfsonmicro . com
2011-03-07 10:04:55 +03:00
* Copyright 2011 Lars - Peter Clausen < lars @ metafoo . de >
*
* 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 .
*
*/
# include <linux/init.h>
# include <linux/module.h>
# include <linux/i2c.h>
# include <linux/slab.h>
# include <sound/core.h>
# include <sound/soc.h>
# include <sound/tlv.h>
struct lm4857 {
struct i2c_client * i2c ;
uint8_t mode ;
} ;
static const uint8_t lm4857_default_regs [ ] = {
0x00 , 0x00 , 0x00 , 0x00 ,
} ;
/* The register offsets in the cache array */
# define LM4857_MVOL 0
# define LM4857_LVOL 1
# define LM4857_RVOL 2
# define LM4857_CTRL 3
/* the shifts required to set these bits */
# define LM4857_3D 5
# define LM4857_WAKEUP 5
# define LM4857_EPGAIN 4
static int lm4857_write ( struct snd_soc_codec * codec , unsigned int reg ,
unsigned int value )
{
uint8_t data ;
int ret ;
ret = snd_soc_cache_write ( codec , reg , value ) ;
if ( ret < 0 )
return ret ;
data = ( reg < < 6 ) | value ;
ret = i2c_master_send ( codec - > control_data , & data , 1 ) ;
if ( ret ! = 1 ) {
dev_err ( codec - > dev , " Failed to write register: %d \n " , ret ) ;
return ret ;
}
return 0 ;
}
static unsigned int lm4857_read ( struct snd_soc_codec * codec ,
unsigned int reg )
{
unsigned int val ;
int ret ;
ret = snd_soc_cache_read ( codec , reg , & val ) ;
if ( ret )
return - 1 ;
return val ;
}
static int lm4857_get_mode ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
struct snd_soc_codec * codec = snd_kcontrol_chip ( kcontrol ) ;
struct lm4857 * lm4857 = snd_soc_codec_get_drvdata ( codec ) ;
ucontrol - > value . integer . value [ 0 ] = lm4857 - > mode ;
return 0 ;
}
static int lm4857_set_mode ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
struct snd_soc_codec * codec = snd_kcontrol_chip ( kcontrol ) ;
struct lm4857 * lm4857 = snd_soc_codec_get_drvdata ( codec ) ;
uint8_t value = ucontrol - > value . integer . value [ 0 ] ;
lm4857 - > mode = value ;
if ( codec - > dapm . bias_level = = SND_SOC_BIAS_ON )
snd_soc_update_bits ( codec , LM4857_CTRL , 0x0F , value + 6 ) ;
return 1 ;
}
static int lm4857_set_bias_level ( struct snd_soc_codec * codec ,
enum snd_soc_bias_level level )
{
struct lm4857 * lm4857 = snd_soc_codec_get_drvdata ( codec ) ;
switch ( level ) {
case SND_SOC_BIAS_ON :
snd_soc_update_bits ( codec , LM4857_CTRL , 0x0F , lm4857 - > mode + 6 ) ;
break ;
case SND_SOC_BIAS_STANDBY :
snd_soc_update_bits ( codec , LM4857_CTRL , 0x0F , 0 ) ;
break ;
default :
break ;
}
codec - > dapm . bias_level = level ;
return 0 ;
}
static const char * lm4857_mode [ ] = {
" Earpiece " ,
" Loudspeaker " ,
" Loudspeaker + Headphone " ,
" Headphone " ,
} ;
static const struct soc_enum lm4857_mode_enum =
SOC_ENUM_SINGLE_EXT ( ARRAY_SIZE ( lm4857_mode ) , lm4857_mode ) ;
static const struct snd_soc_dapm_widget lm4857_dapm_widgets [ ] = {
SND_SOC_DAPM_INPUT ( " IN " ) ,
SND_SOC_DAPM_OUTPUT ( " LS " ) ,
SND_SOC_DAPM_OUTPUT ( " HP " ) ,
SND_SOC_DAPM_OUTPUT ( " EP " ) ,
} ;
static const DECLARE_TLV_DB_SCALE ( stereo_tlv , - 4050 , 150 , 0 ) ;
static const DECLARE_TLV_DB_SCALE ( mono_tlv , - 3450 , 150 , 0 ) ;
static const struct snd_kcontrol_new lm4857_controls [ ] = {
SOC_SINGLE_TLV ( " Left Playback Volume " , LM4857_LVOL , 0 , 31 , 0 ,
stereo_tlv ) ,
SOC_SINGLE_TLV ( " Right Playback Volume " , LM4857_RVOL , 0 , 31 , 0 ,
stereo_tlv ) ,
SOC_SINGLE_TLV ( " Mono Playback Volume " , LM4857_MVOL , 0 , 31 , 0 ,
mono_tlv ) ,
SOC_SINGLE ( " Spk 3D Playback Switch " , LM4857_LVOL , LM4857_3D , 1 , 0 ) ,
SOC_SINGLE ( " HP 3D Playback Switch " , LM4857_RVOL , LM4857_3D , 1 , 0 ) ,
SOC_SINGLE ( " Fast Wakeup Playback Switch " , LM4857_CTRL ,
LM4857_WAKEUP , 1 , 0 ) ,
SOC_SINGLE ( " Earpiece 6dB Playback Switch " , LM4857_CTRL ,
LM4857_EPGAIN , 1 , 0 ) ,
SOC_ENUM_EXT ( " Mode " , lm4857_mode_enum ,
lm4857_get_mode , lm4857_set_mode ) ,
} ;
2011-03-31 05:57:33 +04:00
/* There is a demux between the input signal and the output signals.
2011-03-07 10:04:55 +03:00
* Currently there is no easy way to model it in ASoC and since it does not make
* much of a difference in practice simply connect the input direclty to the
* outputs . */
static const struct snd_soc_dapm_route lm4857_routes [ ] = {
{ " LS " , NULL , " IN " } ,
{ " HP " , NULL , " IN " } ,
{ " EP " , NULL , " IN " } ,
} ;
static int lm4857_probe ( struct snd_soc_codec * codec )
{
struct lm4857 * lm4857 = snd_soc_codec_get_drvdata ( codec ) ;
struct snd_soc_dapm_context * dapm = & codec - > dapm ;
int ret ;
codec - > control_data = lm4857 - > i2c ;
2012-02-03 21:43:09 +04:00
ret = snd_soc_add_codec_controls ( codec , lm4857_controls ,
2011-03-07 10:04:55 +03:00
ARRAY_SIZE ( lm4857_controls ) ) ;
if ( ret )
return ret ;
ret = snd_soc_dapm_new_controls ( dapm , lm4857_dapm_widgets ,
ARRAY_SIZE ( lm4857_dapm_widgets ) ) ;
if ( ret )
return ret ;
ret = snd_soc_dapm_add_routes ( dapm , lm4857_routes ,
ARRAY_SIZE ( lm4857_routes ) ) ;
if ( ret )
return ret ;
snd_soc_dapm_new_widgets ( dapm ) ;
return 0 ;
}
static struct snd_soc_codec_driver soc_codec_dev_lm4857 = {
. write = lm4857_write ,
. read = lm4857_read ,
. probe = lm4857_probe ,
. reg_cache_size = ARRAY_SIZE ( lm4857_default_regs ) ,
. reg_word_size = sizeof ( uint8_t ) ,
. reg_cache_default = lm4857_default_regs ,
. set_bias_level = lm4857_set_bias_level ,
} ;
static int __devinit lm4857_i2c_probe ( struct i2c_client * i2c ,
const struct i2c_device_id * id )
{
struct lm4857 * lm4857 ;
int ret ;
2011-12-26 16:56:25 +04:00
lm4857 = devm_kzalloc ( & i2c - > dev , sizeof ( * lm4857 ) , GFP_KERNEL ) ;
2011-03-07 10:04:55 +03:00
if ( ! lm4857 )
return - ENOMEM ;
i2c_set_clientdata ( i2c , lm4857 ) ;
lm4857 - > i2c = i2c ;
ret = snd_soc_register_codec ( & i2c - > dev , & soc_codec_dev_lm4857 , NULL , 0 ) ;
2011-12-26 16:56:25 +04:00
return ret ;
2011-03-07 10:04:55 +03:00
}
static int __devexit lm4857_i2c_remove ( struct i2c_client * i2c )
{
snd_soc_unregister_codec ( & i2c - > dev ) ;
return 0 ;
}
static const struct i2c_device_id lm4857_i2c_id [ ] = {
{ " lm4857 " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , lm4857_i2c_id ) ;
static struct i2c_driver lm4857_i2c_driver = {
. driver = {
. name = " lm4857 " ,
. owner = THIS_MODULE ,
} ,
. probe = lm4857_i2c_probe ,
. remove = __devexit_p ( lm4857_i2c_remove ) ,
. id_table = lm4857_i2c_id ,
} ;
2012-08-06 15:55:49 +04:00
module_i2c_driver ( lm4857_i2c_driver ) ;
2011-03-07 10:04:55 +03:00
MODULE_AUTHOR ( " Lars-Peter Clausen <lars@metafoo.de> " ) ;
MODULE_DESCRIPTION ( " LM4857 amplifier driver " ) ;
MODULE_LICENSE ( " GPL " ) ;