@ -48,6 +48,9 @@
# define WM8960_DISOP 0x40
# define WM8960_DRES_MASK 0x30
static bool is_pll_freq_available ( unsigned int source , unsigned int target ) ;
static int wm8960_set_pll ( struct snd_soc_codec * codec ,
unsigned int freq_in , unsigned int freq_out ) ;
/*
* wm8960 register cache
* We can ' t read the WM8960 register space when we are
@ -126,9 +129,12 @@ struct wm8960_priv {
struct snd_soc_dapm_widget * rout1 ;
struct snd_soc_dapm_widget * out3 ;
bool deemph ;
int playback_fs ;
int lrclk ;
int bclk ;
int sysclk ;
int clk_id ;
int freq_in ;
bool is_stream_in_use [ 2 ] ;
struct wm8960_data pdata ;
} ;
@ -164,8 +170,8 @@ static int wm8960_set_deemph(struct snd_soc_codec *codec)
if ( wm8960 - > deemph ) {
best = 1 ;
for ( i = 2 ; i < ARRAY_SIZE ( deemph_settings ) ; i + + ) {
if ( abs ( deemph_settings [ i ] - wm8960 - > playback_fs ) <
abs ( deemph_settings [ best ] - wm8960 - > playback_fs ) )
if ( abs ( deemph_settings [ i ] - wm8960 - > lrclk ) <
abs ( deemph_settings [ best ] - wm8960 - > lrclk ) )
best = i ;
}
@ -565,6 +571,9 @@ static struct {
{ 8000 , 5 } ,
} ;
/* -1 for reserved value */
static const int sysclk_divs [ ] = { 1 , - 1 , 2 , - 1 } ;
/* Multiply 256 for internal 256 div */
static const int dac_divs [ ] = { 256 , 384 , 512 , 768 , 1024 , 1408 , 1536 } ;
@ -574,61 +583,110 @@ static const int bclk_divs[] = {
120 , 160 , 220 , 240 , 320 , 320 , 320
} ;
static void wm8960_configure_clocking ( struct snd_soc_codec * codec ,
bool tx , int lrclk )
static int wm8960_configure_clocking ( struct snd_soc_codec * codec )
{
struct wm8960_priv * wm8960 = snd_soc_codec_get_drvdata ( codec ) ;
int sysclk , bclk , lrclk , freq_out , freq_in ;
u16 iface1 = snd_soc_read ( codec , WM8960_IFACE1 ) ;
u16 iface2 = snd_soc_read ( codec , WM8960_IFACE2 ) ;
u32 sysclk ;
int i , j ;
int i , j , k ;
if ( ! ( iface1 & ( 1 < < 6 ) ) ) {
dev_dbg ( codec - > dev ,
" Codec is slave mode, no need to configure clock \n " ) ;
return ;
return 0 ;
}
if ( wm8960 - > clk_id ! = WM8960_SYSCLK_MCLK & & ! wm8960 - > freq_in ) {
dev_err ( codec - > dev , " No MCLK configured \n " ) ;
return - EINVAL ;
}
if ( ! wm8960 - > sysclk ) {
dev_dbg ( codec - > dev , " No SYSCLK configured \n " ) ;
return ;
freq_in = wm8960 - > freq_in ;
bclk = wm8960 - > bclk ;
lrclk = wm8960 - > lrclk ;
/*
* If it ' s sysclk auto mode , check if the MCLK can provide sysclk or
* not . If MCLK can provide sysclk , using MCLK to provide sysclk
* directly . Otherwise , auto select a available pll out frequency
* and set PLL .
*/
if ( wm8960 - > clk_id = = WM8960_SYSCLK_AUTO ) {
/* disable the PLL and using MCLK to provide sysclk */
wm8960_set_pll ( codec , 0 , 0 ) ;
freq_out = freq_in ;
} else if ( wm8960 - > sysclk ) {
freq_out = wm8960 - > sysclk ;
} else {
dev_err ( codec - > dev , " No SYSCLK configured \n " ) ;
return - EINVAL ;
}
if ( ! wm8960 - > bclk | | ! lrclk ) {
dev_dbg ( codec - > dev , " No audio clocks configured \n " ) ;
return ;
/* check if the sysclk frequency is available. */
for ( i = 0 ; i < ARRAY_SIZE ( sysclk_divs ) ; + + i ) {
if ( sysclk_divs [ i ] = = - 1 )
continue ;
sysclk = freq_out / sysclk_divs [ i ] ;
for ( j = 0 ; j < ARRAY_SIZE ( dac_divs ) ; + + j ) {
if ( sysclk = = dac_divs [ j ] * lrclk ) {
for ( k = 0 ; k < ARRAY_SIZE ( bclk_divs ) ; + + k )
if ( sysclk = = bclk * bclk_divs [ k ] / 10 )
break ;
if ( k ! = ARRAY_SIZE ( bclk_divs ) )
break ;
}
}
if ( j ! = ARRAY_SIZE ( dac_divs ) )
break ;
}
for ( i = 0 ; i < ARRAY_SIZE ( dac_divs ) ; + + i ) {
if ( wm8960 - > sysclk = = lrclk * dac_divs [ i ] ) {
for ( j = 0 ; j < ARRAY_SIZE ( bclk_divs ) ; + + j ) {
sysclk = wm8960 - > bclk * bclk_divs [ j ] / 10 ;
if ( wm8960 - > sysclk = = sysclk )
if ( i ! = ARRAY_SIZE ( sysclk_divs ) ) {
goto configure_clock ;
} else if ( wm8960 - > clk_id ! = WM8960_SYSCLK_AUTO ) {
dev_err ( codec - > dev , " failed to configure clock \n " ) ;
return - EINVAL ;
}
/* get a available pll out frequency and set pll */
for ( i = 0 ; i < ARRAY_SIZE ( sysclk_divs ) ; + + i ) {
if ( sysclk_divs [ i ] = = - 1 )
continue ;
for ( j = 0 ; j < ARRAY_SIZE ( dac_divs ) ; + + j ) {
sysclk = lrclk * dac_divs [ j ] ;
freq_out = sysclk * sysclk_divs [ i ] ;
for ( k = 0 ; k < ARRAY_SIZE ( bclk_divs ) ; + + k ) {
if ( sysclk = = bclk * bclk_divs [ k ] / 10 & &
is_pll_freq_available ( freq_in , freq_out ) ) {
wm8960_set_pll ( codec ,
freq_in , freq_out ) ;
break ;
} else {
continue ;
}
}
if ( j ! = ARRAY_SIZE ( bclk_divs ) )
if ( k ! = ARRAY_SIZE ( bclk_divs ) )
break ;
}
if ( j ! = ARRAY_SIZE ( dac_divs ) )
break ;
}
if ( i = = ARRAY_SIZE ( dac_divs ) ) {
dev_err ( codec - > dev , " Unsupported sysclk %d \n " , wm8960 - > sysclk ) ;
return ;
if ( i = = ARRAY_SIZE ( sysclk _divs) ) {
dev_err ( codec - > dev , " failed to configure clock \n " ) ;
return - EINVAL ;
}
/*
* configure frame clock . If ADCLRC configure as GPIO pin , DACLRC
* pin is used as a frame clock for ADCs and DACs .
*/
if ( iface2 & ( 1 < < 6 ) )
snd_soc_update_bits ( codec , WM8960_CLOCK1 , 0x7 < < 3 , i < < 3 ) ;
else if ( tx )
snd_soc_update_bits ( codec , WM8960_CLOCK1 , 0x7 < < 3 , i < < 3 ) ;
else if ( ! tx )
snd_soc_update_bits ( codec , WM8960_CLOCK1 , 0x7 < < 6 , i < < 6 ) ;
configure_clock :
/* configure sysclk clock */
snd_soc_update_bits ( codec , WM8960_CLOCK1 , 3 < < 1 , i < < 1 ) ;
/* configure frame clock */
snd_soc_update_bits ( codec , WM8960_CLOCK1 , 0x7 < < 3 , j < < 3 ) ;
snd_soc_update_bits ( codec , WM8960_CLOCK1 , 0x7 < < 6 , j < < 6 ) ;
/* configure bit clock */
snd_soc_update_bits ( codec , WM8960_CLOCK2 , 0xf , j ) ;
snd_soc_update_bits ( codec , WM8960_CLOCK2 , 0xf , k ) ;
return 0 ;
}
static int wm8960_hw_params ( struct snd_pcm_substream * substream ,
@ -667,9 +725,9 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream,
return - EINVAL ;
}
wm8960 - > lrclk = params_rate ( params ) ;
/* Update filters for the new rate */
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
wm8960 - > playback_fs = params_rate ( params ) ;
if ( tx ) {
wm8960_set_deemph ( codec ) ;
} else {
for ( i = 0 ; i < ARRAY_SIZE ( alc_rates ) ; i + + )
@ -682,7 +740,23 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream,
/* set iface */
snd_soc_write ( codec , WM8960_IFACE1 , iface ) ;
wm8960_configure_clocking ( codec , tx , params_rate ( params ) ) ;
wm8960 - > is_stream_in_use [ tx ] = true ;
if ( snd_soc_codec_get_bias_level ( codec ) = = SND_SOC_BIAS_ON & &
! wm8960 - > is_stream_in_use [ ! tx ] )
return wm8960_configure_clocking ( codec ) ;
return 0 ;
}
static int wm8960_hw_free ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
{
struct snd_soc_codec * codec = dai - > codec ;
struct wm8960_priv * wm8960 = snd_soc_codec_get_drvdata ( codec ) ;
bool tx = substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ;
wm8960 - > is_stream_in_use [ tx ] = false ;
return 0 ;
}
@ -702,6 +776,7 @@ static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec,
enum snd_soc_bias_level level )
{
struct wm8960_priv * wm8960 = snd_soc_codec_get_drvdata ( codec ) ;
u16 pm2 = snd_soc_read ( codec , WM8960_POWER2 ) ;
int ret ;
switch ( level ) {
@ -721,11 +796,22 @@ static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec,
}
}
ret = wm8960_configure_clocking ( codec ) ;
if ( ret )
return ret ;
/* Set VMID to 2x50k */
snd_soc_update_bits ( codec , WM8960_POWER1 , 0x180 , 0x80 ) ;
break ;
case SND_SOC_BIAS_ON :
/*
* If it ' s sysclk auto mode , and the pll is enabled ,
* disable the pll
*/
if ( wm8960 - > clk_id = = WM8960_SYSCLK_AUTO & & ( pm2 & 0x1 ) )
wm8960_set_pll ( codec , 0 , 0 ) ;
if ( ! IS_ERR ( wm8960 - > mclk ) )
clk_disable_unprepare ( wm8960 - > mclk ) ;
break ;
@ -780,6 +866,7 @@ static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec,
enum snd_soc_bias_level level )
{
struct wm8960_priv * wm8960 = snd_soc_codec_get_drvdata ( codec ) ;
u16 pm2 = snd_soc_read ( codec , WM8960_POWER2 ) ;
int reg , ret ;
switch ( level ) {
@ -831,9 +918,21 @@ static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec,
return ret ;
}
}
ret = wm8960_configure_clocking ( codec ) ;
if ( ret )
return ret ;
break ;
case SND_SOC_BIAS_ON :
/*
* If it ' s sysclk auto mode , and the pll is enabled ,
* disable the pll
*/
if ( wm8960 - > clk_id = = WM8960_SYSCLK_AUTO & & ( pm2 & 0x1 ) )
wm8960_set_pll ( codec , 0 , 0 ) ;
if ( ! IS_ERR ( wm8960 - > mclk ) )
clk_disable_unprepare ( wm8960 - > mclk ) ;
@ -892,6 +991,28 @@ struct _pll_div {
u32 k : 24 ;
} ;
static bool is_pll_freq_available ( unsigned int source , unsigned int target )
{
unsigned int Ndiv ;
if ( source = = 0 | | target = = 0 )
return false ;
/* Scale up target to PLL operating frequency */
target * = 4 ;
Ndiv = target / source ;
if ( Ndiv < 6 ) {
source > > = 1 ;
Ndiv = target / source ;
}
if ( ( Ndiv < 6 ) | | ( Ndiv > 12 ) )
return false ;
return true ;
}
/* The size in bits of the pll divide multiplied by 10
* to allow rounding later */
# define FIXED_PLL_SIZE ((1 << 24) * 10)
@ -943,10 +1064,9 @@ static int pll_factors(unsigned int source, unsigned int target,
return 0 ;
}
static int wm8960_set_dai_ pll ( struct snd_soc_dai * codec_dai , int pll_id ,
int source , unsigned int freq_in , unsigned int freq_out )
static int wm8960_set_pll ( struct snd_soc_codec * codec ,
unsigned int freq_in , unsigned int freq_out )
{
struct snd_soc_codec * codec = codec_dai - > codec ;
u16 reg ;
static struct _pll_div pll_div ;
int ret ;
@ -986,6 +1106,20 @@ static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
return 0 ;
}
static int wm8960_set_dai_pll ( struct snd_soc_dai * codec_dai , int pll_id ,
int source , unsigned int freq_in , unsigned int freq_out )
{
struct snd_soc_codec * codec = codec_dai - > codec ;
struct wm8960_priv * wm8960 = snd_soc_codec_get_drvdata ( codec ) ;
wm8960 - > freq_in = freq_in ;
if ( pll_id = = WM8960_SYSCLK_AUTO )
return 0 ;
return wm8960_set_pll ( codec , freq_in , freq_out ) ;
}
static int wm8960_set_dai_clkdiv ( struct snd_soc_dai * codec_dai ,
int div_id , int div )
{
@ -1043,11 +1177,14 @@ static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
snd_soc_update_bits ( codec , WM8960_CLOCK1 ,
0x1 , WM8960_SYSCLK_PLL ) ;
break ;
case WM8960_SYSCLK_AUTO :
break ;
default :
return - EINVAL ;
}
wm8960 - > sysclk = freq ;
wm8960 - > clk_id = clk_id ;
return 0 ;
}
@ -1060,6 +1197,7 @@ static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
static const struct snd_soc_dai_ops wm8960_dai_ops = {
. hw_params = wm8960_hw_params ,
. hw_free = wm8960_hw_free ,
. digital_mute = wm8960_mute ,
. set_fmt = wm8960_set_dai_fmt ,
. set_clkdiv = wm8960_set_dai_clkdiv ,