2009-05-23 14:18:41 +04:00
/*
* wm8974 . c - - WM8974 ALSA Soc Audio driver
*
2009-06-30 22:37:02 +04:00
* Copyright 2006 - 2009 Wolfson Microelectronics PLC .
2009-05-23 14:18:41 +04:00
*
2009-05-23 15:27:03 +04:00
* Author : Liam Girdwood < linux @ wolfsonmicro . com >
2009-05-23 14:18:41 +04:00
*
* 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/module.h>
# include <linux/moduleparam.h>
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/delay.h>
# include <linux/pm.h>
# include <linux/i2c.h>
# include <linux/platform_device.h>
# include <sound/core.h>
# include <sound/pcm.h>
# include <sound/pcm_params.h>
# include <sound/soc.h>
# include <sound/soc-dapm.h>
# include <sound/initval.h>
2009-06-30 22:30:33 +04:00
# include <sound/tlv.h>
2009-05-23 14:18:41 +04:00
# include "wm8974.h"
static const u16 wm8974_reg [ WM8974_CACHEREGNUM ] = {
2009-05-23 14:31:40 +04:00
0x0000 , 0x0000 , 0x0000 , 0x0000 ,
0x0050 , 0x0000 , 0x0140 , 0x0000 ,
0x0000 , 0x0000 , 0x0000 , 0x00ff ,
0x0000 , 0x0000 , 0x0100 , 0x00ff ,
0x0000 , 0x0000 , 0x012c , 0x002c ,
0x002c , 0x002c , 0x002c , 0x0000 ,
0x0032 , 0x0000 , 0x0000 , 0x0000 ,
0x0000 , 0x0000 , 0x0000 , 0x0000 ,
0x0038 , 0x000b , 0x0032 , 0x0000 ,
0x0008 , 0x000c , 0x0093 , 0x00e9 ,
0x0000 , 0x0000 , 0x0000 , 0x0000 ,
0x0003 , 0x0010 , 0x0000 , 0x0000 ,
0x0000 , 0x0002 , 0x0000 , 0x0000 ,
0x0000 , 0x0000 , 0x0039 , 0x0000 ,
0x0000 ,
2009-05-23 14:18:41 +04:00
} ;
2009-06-30 22:01:09 +04:00
# define WM8974_POWER1_BIASEN 0x08
2009-12-17 16:51:35 +03:00
# define WM8974_POWER1_BUFIOEN 0x04
2009-06-30 22:01:09 +04:00
2009-05-23 15:27:03 +04:00
struct wm8974_priv {
struct snd_soc_codec codec ;
u16 reg_cache [ WM8974_CACHEREGNUM ] ;
} ;
static struct snd_soc_codec * wm8974_codec ;
2009-08-15 15:15:10 +04:00
# define wm8974_reset(c) snd_soc_write(c, WM8974_RESET, 0)
2009-05-23 14:18:41 +04:00
static const char * wm8974_companding [ ] = { " Off " , " NC " , " u-law " , " A-law " } ;
static const char * wm8974_deemp [ ] = { " None " , " 32kHz " , " 44.1kHz " , " 48kHz " } ;
static const char * wm8974_eqmode [ ] = { " Capture " , " Playback " } ;
static const char * wm8974_bw [ ] = { " Narrow " , " Wide " } ;
static const char * wm8974_eq1 [ ] = { " 80Hz " , " 105Hz " , " 135Hz " , " 175Hz " } ;
static const char * wm8974_eq2 [ ] = { " 230Hz " , " 300Hz " , " 385Hz " , " 500Hz " } ;
static const char * wm8974_eq3 [ ] = { " 650Hz " , " 850Hz " , " 1.1kHz " , " 1.4kHz " } ;
static const char * wm8974_eq4 [ ] = { " 1.8kHz " , " 2.4kHz " , " 3.2kHz " , " 4.1kHz " } ;
static const char * wm8974_eq5 [ ] = { " 5.3kHz " , " 6.9kHz " , " 9kHz " , " 11.7kHz " } ;
static const char * wm8974_alc [ ] = { " ALC " , " Limiter " } ;
static const struct soc_enum wm8974_enum [ ] = {
SOC_ENUM_SINGLE ( WM8974_COMP , 1 , 4 , wm8974_companding ) , /* adc */
SOC_ENUM_SINGLE ( WM8974_COMP , 3 , 4 , wm8974_companding ) , /* dac */
SOC_ENUM_SINGLE ( WM8974_DAC , 4 , 4 , wm8974_deemp ) ,
SOC_ENUM_SINGLE ( WM8974_EQ1 , 8 , 2 , wm8974_eqmode ) ,
SOC_ENUM_SINGLE ( WM8974_EQ1 , 5 , 4 , wm8974_eq1 ) ,
SOC_ENUM_SINGLE ( WM8974_EQ2 , 8 , 2 , wm8974_bw ) ,
SOC_ENUM_SINGLE ( WM8974_EQ2 , 5 , 4 , wm8974_eq2 ) ,
SOC_ENUM_SINGLE ( WM8974_EQ3 , 8 , 2 , wm8974_bw ) ,
SOC_ENUM_SINGLE ( WM8974_EQ3 , 5 , 4 , wm8974_eq3 ) ,
SOC_ENUM_SINGLE ( WM8974_EQ4 , 8 , 2 , wm8974_bw ) ,
SOC_ENUM_SINGLE ( WM8974_EQ4 , 5 , 4 , wm8974_eq4 ) ,
SOC_ENUM_SINGLE ( WM8974_EQ5 , 8 , 2 , wm8974_bw ) ,
SOC_ENUM_SINGLE ( WM8974_EQ5 , 5 , 4 , wm8974_eq5 ) ,
SOC_ENUM_SINGLE ( WM8974_ALC3 , 8 , 2 , wm8974_alc ) ,
} ;
2009-07-01 00:10:34 +04:00
static const char * wm8974_auxmode_text [ ] = { " Buffer " , " Mixer " } ;
static const struct soc_enum wm8974_auxmode =
SOC_ENUM_SINGLE ( WM8974_INPUT , 3 , 2 , wm8974_auxmode_text ) ;
2009-06-30 22:30:33 +04:00
static const DECLARE_TLV_DB_SCALE ( digital_tlv , - 12750 , 50 , 1 ) ;
static const DECLARE_TLV_DB_SCALE ( eq_tlv , - 1200 , 100 , 0 ) ;
static const DECLARE_TLV_DB_SCALE ( inpga_tlv , - 1200 , 75 , 0 ) ;
static const DECLARE_TLV_DB_SCALE ( spk_tlv , - 5700 , 100 , 0 ) ;
2009-05-23 14:18:41 +04:00
static const struct snd_kcontrol_new wm8974_snd_controls [ ] = {
SOC_SINGLE ( " Digital Loopback Switch " , WM8974_COMP , 0 , 1 , 0 ) ,
SOC_ENUM ( " DAC Companding " , wm8974_enum [ 1 ] ) ,
SOC_ENUM ( " ADC Companding " , wm8974_enum [ 0 ] ) ,
SOC_ENUM ( " Playback De-emphasis " , wm8974_enum [ 2 ] ) ,
SOC_SINGLE ( " DAC Inversion Switch " , WM8974_DAC , 0 , 1 , 0 ) ,
2009-06-30 22:30:33 +04:00
SOC_SINGLE_TLV ( " PCM Volume " , WM8974_DACVOL , 0 , 255 , 0 , digital_tlv ) ,
2009-05-23 14:18:41 +04:00
SOC_SINGLE ( " High Pass Filter Switch " , WM8974_ADC , 8 , 1 , 0 ) ,
SOC_SINGLE ( " High Pass Cut Off " , WM8974_ADC , 4 , 7 , 0 ) ,
2009-07-21 13:15:06 +04:00
SOC_SINGLE ( " ADC Inversion Switch " , WM8974_ADC , 0 , 1 , 0 ) ,
2009-05-23 14:18:41 +04:00
2009-06-30 22:30:33 +04:00
SOC_SINGLE_TLV ( " Capture Volume " , WM8974_ADCVOL , 0 , 255 , 0 , digital_tlv ) ,
2009-05-23 14:18:41 +04:00
SOC_ENUM ( " Equaliser Function " , wm8974_enum [ 3 ] ) ,
SOC_ENUM ( " EQ1 Cut Off " , wm8974_enum [ 4 ] ) ,
2009-06-30 22:30:33 +04:00
SOC_SINGLE_TLV ( " EQ1 Volume " , WM8974_EQ1 , 0 , 24 , 1 , eq_tlv ) ,
2009-05-23 14:18:41 +04:00
SOC_ENUM ( " Equaliser EQ2 Bandwith " , wm8974_enum [ 5 ] ) ,
SOC_ENUM ( " EQ2 Cut Off " , wm8974_enum [ 6 ] ) ,
2009-06-30 22:30:33 +04:00
SOC_SINGLE_TLV ( " EQ2 Volume " , WM8974_EQ2 , 0 , 24 , 1 , eq_tlv ) ,
2009-05-23 14:18:41 +04:00
SOC_ENUM ( " Equaliser EQ3 Bandwith " , wm8974_enum [ 7 ] ) ,
SOC_ENUM ( " EQ3 Cut Off " , wm8974_enum [ 8 ] ) ,
2009-06-30 22:30:33 +04:00
SOC_SINGLE_TLV ( " EQ3 Volume " , WM8974_EQ3 , 0 , 24 , 1 , eq_tlv ) ,
2009-05-23 14:18:41 +04:00
SOC_ENUM ( " Equaliser EQ4 Bandwith " , wm8974_enum [ 9 ] ) ,
SOC_ENUM ( " EQ4 Cut Off " , wm8974_enum [ 10 ] ) ,
2009-06-30 22:30:33 +04:00
SOC_SINGLE_TLV ( " EQ4 Volume " , WM8974_EQ4 , 0 , 24 , 1 , eq_tlv ) ,
2009-05-23 14:18:41 +04:00
SOC_ENUM ( " Equaliser EQ5 Bandwith " , wm8974_enum [ 11 ] ) ,
SOC_ENUM ( " EQ5 Cut Off " , wm8974_enum [ 12 ] ) ,
2009-06-30 22:30:33 +04:00
SOC_SINGLE_TLV ( " EQ5 Volume " , WM8974_EQ5 , 0 , 24 , 1 , eq_tlv ) ,
2009-05-23 14:18:41 +04:00
SOC_SINGLE ( " DAC Playback Limiter Switch " , WM8974_DACLIM1 , 8 , 1 , 0 ) ,
SOC_SINGLE ( " DAC Playback Limiter Decay " , WM8974_DACLIM1 , 4 , 15 , 0 ) ,
SOC_SINGLE ( " DAC Playback Limiter Attack " , WM8974_DACLIM1 , 0 , 15 , 0 ) ,
SOC_SINGLE ( " DAC Playback Limiter Threshold " , WM8974_DACLIM2 , 4 , 7 , 0 ) ,
SOC_SINGLE ( " DAC Playback Limiter Boost " , WM8974_DACLIM2 , 0 , 15 , 0 ) ,
SOC_SINGLE ( " ALC Enable Switch " , WM8974_ALC1 , 8 , 1 , 0 ) ,
SOC_SINGLE ( " ALC Capture Max Gain " , WM8974_ALC1 , 3 , 7 , 0 ) ,
SOC_SINGLE ( " ALC Capture Min Gain " , WM8974_ALC1 , 0 , 7 , 0 ) ,
SOC_SINGLE ( " ALC Capture ZC Switch " , WM8974_ALC2 , 8 , 1 , 0 ) ,
SOC_SINGLE ( " ALC Capture Hold " , WM8974_ALC2 , 4 , 7 , 0 ) ,
SOC_SINGLE ( " ALC Capture Target " , WM8974_ALC2 , 0 , 15 , 0 ) ,
SOC_ENUM ( " ALC Capture Mode " , wm8974_enum [ 13 ] ) ,
SOC_SINGLE ( " ALC Capture Decay " , WM8974_ALC3 , 4 , 15 , 0 ) ,
SOC_SINGLE ( " ALC Capture Attack " , WM8974_ALC3 , 0 , 15 , 0 ) ,
SOC_SINGLE ( " ALC Capture Noise Gate Switch " , WM8974_NGATE , 3 , 1 , 0 ) ,
SOC_SINGLE ( " ALC Capture Noise Gate Threshold " , WM8974_NGATE , 0 , 7 , 0 ) ,
SOC_SINGLE ( " Capture PGA ZC Switch " , WM8974_INPPGA , 7 , 1 , 0 ) ,
2009-06-30 22:30:33 +04:00
SOC_SINGLE_TLV ( " Capture PGA Volume " , WM8974_INPPGA , 0 , 63 , 0 , inpga_tlv ) ,
2009-05-23 14:18:41 +04:00
SOC_SINGLE ( " Speaker Playback ZC Switch " , WM8974_SPKVOL , 7 , 1 , 0 ) ,
SOC_SINGLE ( " Speaker Playback Switch " , WM8974_SPKVOL , 6 , 1 , 1 ) ,
2009-07-01 00:10:34 +04:00
SOC_SINGLE_TLV ( " Speaker Playback Volume " , WM8974_SPKVOL , 0 , 63 , 0 , spk_tlv ) ,
SOC_ENUM ( " Aux Mode " , wm8974_auxmode ) ,
2009-05-23 14:18:41 +04:00
SOC_SINGLE ( " Capture Boost(+20dB) " , WM8974_ADCBOOST , 8 , 1 , 0 ) ,
2009-07-01 00:10:34 +04:00
SOC_SINGLE ( " Mono Playback Switch " , WM8974_MONOMIX , 6 , 1 , 1 ) ,
2010-01-29 17:31:06 +03:00
/* DAC / ADC oversampling */
SOC_SINGLE ( " DAC 128x Oversampling Switch " , WM8974_DAC , 8 , 1 , 0 ) ,
SOC_SINGLE ( " ADC 128x Oversampling Switch " , WM8974_ADC , 8 , 1 , 0 ) ,
2009-05-23 14:18:41 +04:00
} ;
/* Speaker Output Mixer */
static const struct snd_kcontrol_new wm8974_speaker_mixer_controls [ ] = {
SOC_DAPM_SINGLE ( " Line Bypass Switch " , WM8974_SPKMIX , 1 , 1 , 0 ) ,
SOC_DAPM_SINGLE ( " Aux Playback Switch " , WM8974_SPKMIX , 5 , 1 , 0 ) ,
SOC_DAPM_SINGLE ( " PCM Playback Switch " , WM8974_SPKMIX , 0 , 1 , 1 ) ,
} ;
/* Mono Output Mixer */
static const struct snd_kcontrol_new wm8974_mono_mixer_controls [ ] = {
SOC_DAPM_SINGLE ( " Line Bypass Switch " , WM8974_MONOMIX , 1 , 1 , 0 ) ,
SOC_DAPM_SINGLE ( " Aux Playback Switch " , WM8974_MONOMIX , 2 , 1 , 0 ) ,
2009-07-01 00:10:34 +04:00
SOC_DAPM_SINGLE ( " PCM Playback Switch " , WM8974_MONOMIX , 0 , 1 , 0 ) ,
} ;
/* Boost mixer */
static const struct snd_kcontrol_new wm8974_boost_mixer [ ] = {
SOC_DAPM_SINGLE ( " Aux Switch " , WM8974_INPPGA , 6 , 1 , 0 ) ,
} ;
/* Input PGA */
static const struct snd_kcontrol_new wm8974_inpga [ ] = {
SOC_DAPM_SINGLE ( " Aux Switch " , WM8974_INPUT , 2 , 1 , 0 ) ,
SOC_DAPM_SINGLE ( " MicN Switch " , WM8974_INPUT , 1 , 1 , 0 ) ,
SOC_DAPM_SINGLE ( " MicP Switch " , WM8974_INPUT , 0 , 1 , 0 ) ,
2009-05-23 14:18:41 +04:00
} ;
/* AUX Input boost vol */
static const struct snd_kcontrol_new wm8974_aux_boost_controls =
SOC_DAPM_SINGLE ( " Aux Volume " , WM8974_ADCBOOST , 0 , 7 , 0 ) ;
/* Mic Input boost vol */
static const struct snd_kcontrol_new wm8974_mic_boost_controls =
SOC_DAPM_SINGLE ( " Mic Volume " , WM8974_ADCBOOST , 4 , 7 , 0 ) ;
static const struct snd_soc_dapm_widget wm8974_dapm_widgets [ ] = {
SND_SOC_DAPM_MIXER ( " Speaker Mixer " , WM8974_POWER3 , 2 , 0 ,
& wm8974_speaker_mixer_controls [ 0 ] ,
ARRAY_SIZE ( wm8974_speaker_mixer_controls ) ) ,
SND_SOC_DAPM_MIXER ( " Mono Mixer " , WM8974_POWER3 , 3 , 0 ,
& wm8974_mono_mixer_controls [ 0 ] ,
ARRAY_SIZE ( wm8974_mono_mixer_controls ) ) ,
SND_SOC_DAPM_DAC ( " DAC " , " HiFi Playback " , WM8974_POWER3 , 0 , 0 ) ,
2009-07-01 00:10:34 +04:00
SND_SOC_DAPM_ADC ( " ADC " , " HiFi Capture " , WM8974_POWER2 , 0 , 0 ) ,
2009-05-23 14:18:41 +04:00
SND_SOC_DAPM_PGA ( " Aux Input " , WM8974_POWER1 , 6 , 0 , NULL , 0 ) ,
SND_SOC_DAPM_PGA ( " SpkN Out " , WM8974_POWER3 , 5 , 0 , NULL , 0 ) ,
SND_SOC_DAPM_PGA ( " SpkP Out " , WM8974_POWER3 , 6 , 0 , NULL , 0 ) ,
SND_SOC_DAPM_PGA ( " Mono Out " , WM8974_POWER3 , 7 , 0 , NULL , 0 ) ,
2009-07-01 00:10:34 +04:00
SND_SOC_DAPM_MIXER ( " Input PGA " , WM8974_POWER2 , 2 , 0 , wm8974_inpga ,
ARRAY_SIZE ( wm8974_inpga ) ) ,
SND_SOC_DAPM_MIXER ( " Boost Mixer " , WM8974_POWER2 , 4 , 0 ,
wm8974_boost_mixer , ARRAY_SIZE ( wm8974_boost_mixer ) ) ,
2009-05-23 14:18:41 +04:00
SND_SOC_DAPM_MICBIAS ( " Mic Bias " , WM8974_POWER1 , 4 , 0 ) ,
SND_SOC_DAPM_INPUT ( " MICN " ) ,
SND_SOC_DAPM_INPUT ( " MICP " ) ,
SND_SOC_DAPM_INPUT ( " AUX " ) ,
SND_SOC_DAPM_OUTPUT ( " MONOOUT " ) ,
SND_SOC_DAPM_OUTPUT ( " SPKOUTP " ) ,
SND_SOC_DAPM_OUTPUT ( " SPKOUTN " ) ,
} ;
static const struct snd_soc_dapm_route audio_map [ ] = {
/* Mono output mixer */
{ " Mono Mixer " , " PCM Playback Switch " , " DAC " } ,
{ " Mono Mixer " , " Aux Playback Switch " , " Aux Input " } ,
{ " Mono Mixer " , " Line Bypass Switch " , " Boost Mixer " } ,
/* Speaker output mixer */
{ " Speaker Mixer " , " PCM Playback Switch " , " DAC " } ,
{ " Speaker Mixer " , " Aux Playback Switch " , " Aux Input " } ,
{ " Speaker Mixer " , " Line Bypass Switch " , " Boost Mixer " } ,
/* Outputs */
{ " Mono Out " , NULL , " Mono Mixer " } ,
{ " MONOOUT " , NULL , " Mono Out " } ,
{ " SpkN Out " , NULL , " Speaker Mixer " } ,
{ " SpkP Out " , NULL , " Speaker Mixer " } ,
{ " SPKOUTN " , NULL , " SpkN Out " } ,
{ " SPKOUTP " , NULL , " SpkP Out " } ,
/* Boost Mixer */
2009-07-01 00:10:34 +04:00
{ " ADC " , NULL , " Boost Mixer " } ,
{ " Boost Mixer " , " Aux Switch " , " Aux Input " } ,
{ " Boost Mixer " , NULL , " Input PGA " } ,
{ " Boost Mixer " , NULL , " MICP " } ,
/* Input PGA */
{ " Input PGA " , " Aux Switch " , " Aux Input " } ,
{ " Input PGA " , " MicN Switch " , " MICN " } ,
{ " Input PGA " , " MicP Switch " , " MICP " } ,
2009-05-23 14:18:41 +04:00
/* Inputs */
2009-07-01 00:10:34 +04:00
{ " Aux Input " , NULL , " AUX " } ,
2009-05-23 14:18:41 +04:00
} ;
static int wm8974_add_widgets ( struct snd_soc_codec * codec )
{
snd_soc_dapm_new_controls ( codec , wm8974_dapm_widgets ,
ARRAY_SIZE ( wm8974_dapm_widgets ) ) ;
snd_soc_dapm_add_routes ( codec , audio_map , ARRAY_SIZE ( audio_map ) ) ;
return 0 ;
}
struct pll_ {
2009-09-30 17:31:38 +04:00
unsigned int pre_div : 1 ;
2009-05-23 14:18:41 +04:00
unsigned int n : 4 ;
unsigned int k ;
} ;
2009-06-30 22:02:32 +04:00
/* The size in bits of the pll divide multiplied by 10
* to allow rounding later */
# define FIXED_PLL_SIZE ((1 << 24) * 10)
2009-09-30 17:31:38 +04:00
static void pll_factors ( struct pll_ * pll_div ,
unsigned int target , unsigned int source )
2009-06-30 22:02:32 +04:00
{
unsigned long long Kpart ;
unsigned int K , Ndiv , Nmod ;
2009-09-30 17:31:38 +04:00
/* There is a fixed divide by 4 in the output path */
target * = 4 ;
2009-06-30 22:02:32 +04:00
Ndiv = target / source ;
if ( Ndiv < 6 ) {
2009-09-30 17:31:38 +04:00
source / = 2 ;
pll_div - > pre_div = 1 ;
2009-06-30 22:02:32 +04:00
Ndiv = target / source ;
} else
2009-09-30 17:31:38 +04:00
pll_div - > pre_div = 0 ;
2009-06-30 22:02:32 +04:00
if ( ( Ndiv < 6 ) | | ( Ndiv > 12 ) )
printk ( KERN_WARNING
2009-06-30 22:37:02 +04:00
" WM8974 N value %u outwith recommended range! \n " ,
2009-06-30 22:02:32 +04:00
Ndiv ) ;
2009-09-30 17:31:38 +04:00
pll_div - > n = Ndiv ;
2009-06-30 22:02:32 +04:00
Nmod = target % source ;
Kpart = FIXED_PLL_SIZE * ( long long ) Nmod ;
do_div ( Kpart , source ) ;
K = Kpart & 0xFFFFFFFF ;
/* Check if we need to round */
if ( ( K % 10 ) > = 5 )
K + = 5 ;
/* Move down to proper range now rounding is done */
K / = 10 ;
2009-09-30 17:31:38 +04:00
pll_div - > k = K ;
2009-06-30 22:02:32 +04:00
}
2009-05-23 14:18:41 +04:00
2009-09-05 21:52:16 +04:00
static int wm8974_set_dai_pll ( struct snd_soc_dai * codec_dai , int pll_id ,
int source , unsigned int freq_in , unsigned int freq_out )
2009-05-23 14:18:41 +04:00
{
struct snd_soc_codec * codec = codec_dai - > codec ;
2009-09-30 17:31:38 +04:00
struct pll_ pll_div ;
2009-05-23 14:18:41 +04:00
u16 reg ;
2009-05-23 14:31:40 +04:00
if ( freq_in = = 0 | | freq_out = = 0 ) {
2009-06-30 22:02:32 +04:00
/* Clock CODEC directly from MCLK */
2009-08-15 15:15:10 +04:00
reg = snd_soc_read ( codec , WM8974_CLOCK ) ;
snd_soc_write ( codec , WM8974_CLOCK , reg & 0x0ff ) ;
2009-06-30 22:02:32 +04:00
/* Turn off PLL */
2009-08-15 15:15:10 +04:00
reg = snd_soc_read ( codec , WM8974_POWER1 ) ;
snd_soc_write ( codec , WM8974_POWER1 , reg & 0x1df ) ;
2009-05-23 14:18:41 +04:00
return 0 ;
}
2009-09-30 17:31:38 +04:00
pll_factors ( & pll_div , freq_out , freq_in ) ;
2009-06-30 22:02:32 +04:00
2009-08-15 15:15:10 +04:00
snd_soc_write ( codec , WM8974_PLLN , ( pll_div . pre_div < < 4 ) | pll_div . n ) ;
snd_soc_write ( codec , WM8974_PLLK1 , pll_div . k > > 18 ) ;
snd_soc_write ( codec , WM8974_PLLK2 , ( pll_div . k > > 9 ) & 0x1ff ) ;
snd_soc_write ( codec , WM8974_PLLK3 , pll_div . k & 0x1ff ) ;
reg = snd_soc_read ( codec , WM8974_POWER1 ) ;
snd_soc_write ( codec , WM8974_POWER1 , reg | 0x020 ) ;
2009-05-23 14:31:40 +04:00
2009-06-30 22:02:32 +04:00
/* Run CODEC from PLL instead of MCLK */
2009-08-15 15:15:10 +04:00
reg = snd_soc_read ( codec , WM8974_CLOCK ) ;
snd_soc_write ( codec , WM8974_CLOCK , reg | 0x100 ) ;
2009-06-30 22:02:32 +04:00
return 0 ;
2009-05-23 14:18:41 +04:00
}
/*
* Configure WM8974 clock dividers .
*/
static int wm8974_set_dai_clkdiv ( struct snd_soc_dai * codec_dai ,
int div_id , int div )
{
struct snd_soc_codec * codec = codec_dai - > codec ;
u16 reg ;
switch ( div_id ) {
case WM8974_OPCLKDIV :
2009-08-15 15:15:10 +04:00
reg = snd_soc_read ( codec , WM8974_GPIO ) & 0x1cf ;
snd_soc_write ( codec , WM8974_GPIO , reg | div ) ;
2009-05-23 14:18:41 +04:00
break ;
case WM8974_MCLKDIV :
2009-08-15 15:15:10 +04:00
reg = snd_soc_read ( codec , WM8974_CLOCK ) & 0x11f ;
snd_soc_write ( codec , WM8974_CLOCK , reg | div ) ;
2009-05-23 14:18:41 +04:00
break ;
case WM8974_BCLKDIV :
2009-08-15 15:15:10 +04:00
reg = snd_soc_read ( codec , WM8974_CLOCK ) & 0x1e3 ;
snd_soc_write ( codec , WM8974_CLOCK , reg | div ) ;
2009-05-23 14:18:41 +04:00
break ;
default :
return - EINVAL ;
}
return 0 ;
}
static int wm8974_set_dai_fmt ( struct snd_soc_dai * codec_dai ,
unsigned int fmt )
{
struct snd_soc_codec * codec = codec_dai - > codec ;
u16 iface = 0 ;
2009-08-15 15:15:10 +04:00
u16 clk = snd_soc_read ( codec , WM8974_CLOCK ) & 0x1fe ;
2009-05-23 14:18:41 +04:00
/* set master/slave audio interface */
switch ( fmt & SND_SOC_DAIFMT_MASTER_MASK ) {
case SND_SOC_DAIFMT_CBM_CFM :
clk | = 0x0001 ;
break ;
case SND_SOC_DAIFMT_CBS_CFS :
break ;
default :
return - EINVAL ;
}
/* interface format */
switch ( fmt & SND_SOC_DAIFMT_FORMAT_MASK ) {
case SND_SOC_DAIFMT_I2S :
iface | = 0x0010 ;
break ;
case SND_SOC_DAIFMT_RIGHT_J :
break ;
case SND_SOC_DAIFMT_LEFT_J :
iface | = 0x0008 ;
break ;
case SND_SOC_DAIFMT_DSP_A :
iface | = 0x00018 ;
break ;
default :
return - EINVAL ;
}
/* clock inversion */
switch ( fmt & SND_SOC_DAIFMT_INV_MASK ) {
case SND_SOC_DAIFMT_NB_NF :
break ;
case SND_SOC_DAIFMT_IB_IF :
iface | = 0x0180 ;
break ;
case SND_SOC_DAIFMT_IB_NF :
iface | = 0x0100 ;
break ;
case SND_SOC_DAIFMT_NB_IF :
iface | = 0x0080 ;
break ;
default :
return - EINVAL ;
}
2009-08-15 15:15:10 +04:00
snd_soc_write ( codec , WM8974_IFACE , iface ) ;
snd_soc_write ( codec , WM8974_CLOCK , clk ) ;
2009-05-23 14:18:41 +04:00
return 0 ;
}
static int wm8974_pcm_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params ,
struct snd_soc_dai * dai )
{
struct snd_soc_codec * codec = dai - > codec ;
2009-08-15 15:15:10 +04:00
u16 iface = snd_soc_read ( codec , WM8974_IFACE ) & 0x19f ;
u16 adn = snd_soc_read ( codec , WM8974_ADD ) & 0x1f1 ;
2009-05-23 14:18:41 +04:00
/* bit size */
switch ( params_format ( params ) ) {
case SNDRV_PCM_FORMAT_S16_LE :
break ;
case SNDRV_PCM_FORMAT_S20_3LE :
iface | = 0x0020 ;
break ;
case SNDRV_PCM_FORMAT_S24_LE :
iface | = 0x0040 ;
break ;
case SNDRV_PCM_FORMAT_S32_LE :
iface | = 0x0060 ;
break ;
}
/* filter coefficient */
switch ( params_rate ( params ) ) {
2009-12-24 03:13:51 +03:00
case 8000 :
2009-05-23 14:18:41 +04:00
adn | = 0x5 < < 1 ;
break ;
2009-12-24 03:13:51 +03:00
case 11025 :
2009-05-23 14:18:41 +04:00
adn | = 0x4 < < 1 ;
break ;
2009-12-24 03:13:51 +03:00
case 16000 :
2009-05-23 14:18:41 +04:00
adn | = 0x3 < < 1 ;
break ;
2009-12-24 03:13:51 +03:00
case 22050 :
2009-05-23 14:18:41 +04:00
adn | = 0x2 < < 1 ;
break ;
2009-12-24 03:13:51 +03:00
case 32000 :
2009-05-23 14:18:41 +04:00
adn | = 0x1 < < 1 ;
break ;
2009-12-24 03:13:51 +03:00
case 44100 :
case 48000 :
2009-05-23 14:18:41 +04:00
break ;
}
2009-08-15 15:15:10 +04:00
snd_soc_write ( codec , WM8974_IFACE , iface ) ;
snd_soc_write ( codec , WM8974_ADD , adn ) ;
2009-05-23 14:18:41 +04:00
return 0 ;
}
static int wm8974_mute ( struct snd_soc_dai * dai , int mute )
{
struct snd_soc_codec * codec = dai - > codec ;
2009-08-15 15:15:10 +04:00
u16 mute_reg = snd_soc_read ( codec , WM8974_DAC ) & 0xffbf ;
2009-05-23 14:18:41 +04:00
2009-05-23 14:31:40 +04:00
if ( mute )
2009-08-15 15:15:10 +04:00
snd_soc_write ( codec , WM8974_DAC , mute_reg | 0x40 ) ;
2009-05-23 14:18:41 +04:00
else
2009-08-15 15:15:10 +04:00
snd_soc_write ( codec , WM8974_DAC , mute_reg ) ;
2009-05-23 14:18:41 +04:00
return 0 ;
}
/* liam need to make this lower power with dapm */
static int wm8974_set_bias_level ( struct snd_soc_codec * codec ,
enum snd_soc_bias_level level )
{
2009-08-15 15:15:10 +04:00
u16 power1 = snd_soc_read ( codec , WM8974_POWER1 ) & ~ 0x3 ;
2009-06-30 22:01:09 +04:00
2009-05-23 14:18:41 +04:00
switch ( level ) {
case SND_SOC_BIAS_ON :
case SND_SOC_BIAS_PREPARE :
2009-06-30 22:01:09 +04:00
power1 | = 0x1 ; /* VMID 50k */
2009-08-15 15:15:10 +04:00
snd_soc_write ( codec , WM8974_POWER1 , power1 ) ;
2009-05-23 14:18:41 +04:00
break ;
2009-06-30 22:01:09 +04:00
2009-05-23 14:18:41 +04:00
case SND_SOC_BIAS_STANDBY :
2009-06-30 22:01:09 +04:00
power1 | = WM8974_POWER1_BIASEN | WM8974_POWER1_BUFIOEN ;
if ( codec - > bias_level = = SND_SOC_BIAS_OFF ) {
/* Initial cap charge at VMID 5k */
2009-08-15 15:15:10 +04:00
snd_soc_write ( codec , WM8974_POWER1 , power1 | 0x3 ) ;
2009-06-30 22:01:09 +04:00
mdelay ( 100 ) ;
}
power1 | = 0x2 ; /* VMID 500k */
2009-08-15 15:15:10 +04:00
snd_soc_write ( codec , WM8974_POWER1 , power1 ) ;
2009-05-23 14:18:41 +04:00
break ;
2009-06-30 22:01:09 +04:00
2009-05-23 14:18:41 +04:00
case SND_SOC_BIAS_OFF :
2009-08-15 15:15:10 +04:00
snd_soc_write ( codec , WM8974_POWER1 , 0 ) ;
snd_soc_write ( codec , WM8974_POWER2 , 0 ) ;
snd_soc_write ( codec , WM8974_POWER3 , 0 ) ;
2009-05-23 14:18:41 +04:00
break ;
}
2009-06-30 22:01:09 +04:00
2009-05-23 14:18:41 +04:00
codec - > bias_level = level ;
return 0 ;
}
2009-05-23 14:31:40 +04:00
# define WM8974_RATES (SNDRV_PCM_RATE_8000_48000)
2009-05-23 14:18:41 +04:00
# define WM8974_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
SNDRV_PCM_FMTBIT_S24_LE )
static struct snd_soc_dai_ops wm8974_ops = {
. hw_params = wm8974_pcm_hw_params ,
. digital_mute = wm8974_mute ,
. set_fmt = wm8974_set_dai_fmt ,
. set_clkdiv = wm8974_set_dai_clkdiv ,
. set_pll = wm8974_set_dai_pll ,
} ;
struct snd_soc_dai wm8974_dai = {
. name = " WM8974 HiFi " ,
. playback = {
. stream_name = " Playback " ,
. channels_min = 1 ,
2009-06-30 22:01:52 +04:00
. channels_max = 2 , /* Only 1 channel of data */
2009-05-23 14:18:41 +04:00
. rates = WM8974_RATES ,
. formats = WM8974_FORMATS , } ,
. capture = {
. stream_name = " Capture " ,
. channels_min = 1 ,
2009-06-30 22:01:52 +04:00
. channels_max = 2 , /* Only 1 channel of data */
2009-05-23 14:18:41 +04:00
. rates = WM8974_RATES ,
. formats = WM8974_FORMATS , } ,
. ops = & wm8974_ops ,
2009-06-30 22:36:39 +04:00
. symmetric_rates = 1 ,
2009-05-23 14:18:41 +04:00
} ;
EXPORT_SYMBOL_GPL ( wm8974_dai ) ;
static int wm8974_suspend ( struct platform_device * pdev , pm_message_t state )
{
struct snd_soc_device * socdev = platform_get_drvdata ( pdev ) ;
struct snd_soc_codec * codec = socdev - > card - > codec ;
wm8974_set_bias_level ( codec , SND_SOC_BIAS_OFF ) ;
return 0 ;
}
static int wm8974_resume ( struct platform_device * pdev )
{
struct snd_soc_device * socdev = platform_get_drvdata ( pdev ) ;
struct snd_soc_codec * codec = socdev - > card - > codec ;
int i ;
u8 data [ 2 ] ;
u16 * cache = codec - > reg_cache ;
/* Sync reg_cache with the hardware */
for ( i = 0 ; i < ARRAY_SIZE ( wm8974_reg ) ; i + + ) {
data [ 0 ] = ( i < < 1 ) | ( ( cache [ i ] > > 8 ) & 0x0001 ) ;
data [ 1 ] = cache [ i ] & 0x00ff ;
codec - > hw_write ( codec - > control_data , data , 2 ) ;
}
wm8974_set_bias_level ( codec , SND_SOC_BIAS_STANDBY ) ;
wm8974_set_bias_level ( codec , codec - > suspend_bias_level ) ;
return 0 ;
}
2009-05-23 15:27:03 +04:00
static int wm8974_probe ( struct platform_device * pdev )
2009-05-23 14:18:41 +04:00
{
2009-05-23 15:27:03 +04:00
struct snd_soc_device * socdev = platform_get_drvdata ( pdev ) ;
struct snd_soc_codec * codec ;
2009-05-23 14:18:41 +04:00
int ret = 0 ;
2009-05-23 15:27:03 +04:00
if ( wm8974_codec = = NULL ) {
dev_err ( & pdev - > dev , " Codec device not registered \n " ) ;
return - ENODEV ;
}
2009-05-23 14:18:41 +04:00
2009-05-23 15:27:03 +04:00
socdev - > card - > codec = wm8974_codec ;
codec = wm8974_codec ;
2009-05-23 14:18:41 +04:00
/* register pcms */
ret = snd_soc_new_pcms ( socdev , SNDRV_DEFAULT_IDX1 , SNDRV_DEFAULT_STR1 ) ;
2009-05-23 14:31:40 +04:00
if ( ret < 0 ) {
2009-05-23 15:27:03 +04:00
dev_err ( codec - > dev , " failed to create pcms: %d \n " , ret ) ;
2009-05-23 14:18:41 +04:00
goto pcm_err ;
}
2009-05-23 15:27:03 +04:00
snd_soc_add_controls ( codec , wm8974_snd_controls ,
ARRAY_SIZE ( wm8974_snd_controls ) ) ;
2009-05-23 14:18:41 +04:00
wm8974_add_widgets ( codec ) ;
2009-05-23 15:27:03 +04:00
2009-05-23 14:18:41 +04:00
return ret ;
pcm_err :
return ret ;
}
2009-05-23 15:27:03 +04:00
/* power down chip */
static int wm8974_remove ( struct platform_device * pdev )
{
struct snd_soc_device * socdev = platform_get_drvdata ( pdev ) ;
2009-05-23 14:18:41 +04:00
2009-05-23 15:27:03 +04:00
snd_soc_free_pcms ( socdev ) ;
snd_soc_dapm_free ( socdev ) ;
2009-05-23 14:18:41 +04:00
2009-05-23 15:27:03 +04:00
return 0 ;
}
2009-05-23 14:18:41 +04:00
2009-05-23 15:27:03 +04:00
struct snd_soc_codec_device soc_codec_dev_wm8974 = {
. probe = wm8974_probe ,
. remove = wm8974_remove ,
. suspend = wm8974_suspend ,
. resume = wm8974_resume ,
} ;
EXPORT_SYMBOL_GPL ( soc_codec_dev_wm8974 ) ;
2009-05-23 14:18:41 +04:00
2009-05-23 15:27:03 +04:00
static __devinit int wm8974_register ( struct wm8974_priv * wm8974 )
2009-05-23 14:18:41 +04:00
{
int ret ;
2009-05-23 15:27:03 +04:00
struct snd_soc_codec * codec = & wm8974 - > codec ;
2009-05-23 14:18:41 +04:00
2009-05-23 15:27:03 +04:00
if ( wm8974_codec ) {
dev_err ( codec - > dev , " Another WM8974 is registered \n " ) ;
return - EINVAL ;
}
2009-05-23 14:18:41 +04:00
2009-05-23 15:27:03 +04:00
mutex_init ( & codec - > mutex ) ;
INIT_LIST_HEAD ( & codec - > dapm_widgets ) ;
INIT_LIST_HEAD ( & codec - > dapm_paths ) ;
2009-05-23 14:18:41 +04:00
2010-04-14 10:35:19 +04:00
snd_soc_codec_set_drvdata ( codec , wm8974 ) ;
2009-05-23 15:27:03 +04:00
codec - > name = " WM8974 " ;
codec - > owner = THIS_MODULE ;
codec - > bias_level = SND_SOC_BIAS_OFF ;
codec - > set_bias_level = wm8974_set_bias_level ;
codec - > dai = & wm8974_dai ;
codec - > num_dai = 1 ;
codec - > reg_cache_size = WM8974_CACHEREGNUM ;
codec - > reg_cache = & wm8974 - > reg_cache ;
2009-05-23 14:18:41 +04:00
2009-08-15 15:15:10 +04:00
ret = snd_soc_codec_set_cache_io ( codec , 7 , 9 , SND_SOC_I2C ) ;
if ( ret < 0 ) {
dev_err ( codec - > dev , " Failed to set cache I/O: %d \n " , ret ) ;
goto err ;
}
2009-05-23 15:27:03 +04:00
memcpy ( codec - > reg_cache , wm8974_reg , sizeof ( wm8974_reg ) ) ;
ret = wm8974_reset ( codec ) ;
2009-05-23 14:18:41 +04:00
if ( ret < 0 ) {
2009-05-23 15:27:03 +04:00
dev_err ( codec - > dev , " Failed to issue reset \n " ) ;
2009-08-15 15:15:10 +04:00
goto err ;
2009-05-23 14:18:41 +04:00
}
2009-05-23 15:27:03 +04:00
wm8974_dai . dev = codec - > dev ;
wm8974_set_bias_level ( codec , SND_SOC_BIAS_STANDBY ) ;
wm8974_codec = codec ;
ret = snd_soc_register_codec ( codec ) ;
if ( ret ! = 0 ) {
dev_err ( codec - > dev , " Failed to register codec: %d \n " , ret ) ;
2009-08-15 15:15:10 +04:00
goto err ;
2009-05-23 14:18:41 +04:00
}
2009-05-23 15:27:03 +04:00
ret = snd_soc_register_dai ( & wm8974_dai ) ;
if ( ret ! = 0 ) {
dev_err ( codec - > dev , " Failed to register DAI: %d \n " , ret ) ;
2009-08-15 15:15:10 +04:00
goto err_codec ;
2009-05-23 15:27:03 +04:00
}
2009-05-23 14:18:41 +04:00
return 0 ;
2009-08-15 15:15:10 +04:00
err_codec :
snd_soc_unregister_codec ( codec ) ;
err :
kfree ( wm8974 ) ;
return ret ;
2009-05-23 14:18:41 +04:00
}
2009-05-23 15:27:03 +04:00
static __devexit void wm8974_unregister ( struct wm8974_priv * wm8974 )
2009-05-23 14:18:41 +04:00
{
2009-05-23 15:27:03 +04:00
wm8974_set_bias_level ( & wm8974 - > codec , SND_SOC_BIAS_OFF ) ;
snd_soc_unregister_dai ( & wm8974_dai ) ;
snd_soc_unregister_codec ( & wm8974 - > codec ) ;
kfree ( wm8974 ) ;
wm8974_codec = NULL ;
2009-05-23 14:18:41 +04:00
}
2009-05-23 15:27:03 +04:00
static __devinit int wm8974_i2c_probe ( struct i2c_client * i2c ,
const struct i2c_device_id * id )
2009-05-23 14:18:41 +04:00
{
2009-05-23 15:27:03 +04:00
struct wm8974_priv * wm8974 ;
2009-05-23 14:18:41 +04:00
struct snd_soc_codec * codec ;
2009-05-23 15:27:03 +04:00
wm8974 = kzalloc ( sizeof ( struct wm8974_priv ) , GFP_KERNEL ) ;
if ( wm8974 = = NULL )
2009-05-23 14:18:41 +04:00
return - ENOMEM ;
2009-05-23 15:27:03 +04:00
codec = & wm8974 - > codec ;
codec - > hw_write = ( hw_write_t ) i2c_master_send ;
2009-05-23 14:18:41 +04:00
2009-05-23 15:27:03 +04:00
i2c_set_clientdata ( i2c , wm8974 ) ;
codec - > control_data = i2c ;
2009-05-23 14:18:41 +04:00
2009-05-23 15:27:03 +04:00
codec - > dev = & i2c - > dev ;
2009-05-23 14:18:41 +04:00
2009-05-23 15:27:03 +04:00
return wm8974_register ( wm8974 ) ;
}
2009-05-23 14:18:41 +04:00
2009-05-23 15:27:03 +04:00
static __devexit int wm8974_i2c_remove ( struct i2c_client * client )
{
struct wm8974_priv * wm8974 = i2c_get_clientdata ( client ) ;
wm8974_unregister ( wm8974 ) ;
2009-05-23 14:18:41 +04:00
return 0 ;
}
2009-05-23 15:27:03 +04:00
static const struct i2c_device_id wm8974_i2c_id [ ] = {
{ " wm8974 " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , wm8974_i2c_id ) ;
static struct i2c_driver wm8974_i2c_driver = {
. driver = {
2009-06-30 22:37:02 +04:00
. name = " WM8974 " ,
2009-05-23 15:27:03 +04:00
. owner = THIS_MODULE ,
} ,
. probe = wm8974_i2c_probe ,
. remove = __devexit_p ( wm8974_i2c_remove ) ,
. id_table = wm8974_i2c_id ,
2009-05-23 14:18:41 +04:00
} ;
static int __init wm8974_modinit ( void )
{
2009-05-23 15:27:03 +04:00
return i2c_add_driver ( & wm8974_i2c_driver ) ;
2009-05-23 14:18:41 +04:00
}
module_init ( wm8974_modinit ) ;
static void __exit wm8974_exit ( void )
{
2009-05-23 15:27:03 +04:00
i2c_del_driver ( & wm8974_i2c_driver ) ;
2009-05-23 14:18:41 +04:00
}
module_exit ( wm8974_exit ) ;
MODULE_DESCRIPTION ( " ASoC WM8974 driver " ) ;
MODULE_AUTHOR ( " Liam Girdwood " ) ;
MODULE_LICENSE ( " GPL " ) ;