2006-10-06 20:36:07 +04:00
/*
* wm8731 . c - - WM8731 ALSA SoC Audio driver
*
* Copyright 2005 Openedhand Ltd .
*
* Author : Richard Purdie < richard @ openedhand . com >
*
* Based on wm8753 . c by Liam Girdwood
*
* 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/init.h>
# include <linux/delay.h>
# include <linux/pm.h>
# include <linux/i2c.h>
# include <linux/platform_device.h>
# include <sound/driver.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>
# include "wm8731.h"
# define AUDIO_NAME "wm8731"
# define WM8731_VERSION "0.12"
/*
* Debug
*/
# define WM8731_DEBUG 0
# ifdef WM8731_DEBUG
# define dbg(format, arg...) \
printk ( KERN_DEBUG AUDIO_NAME " : " format " \n " , # # arg )
# else
# define dbg(format, arg...) do {} while (0)
# endif
# define err(format, arg...) \
printk ( KERN_ERR AUDIO_NAME " : " format " \n " , # # arg )
# define info(format, arg...) \
printk ( KERN_INFO AUDIO_NAME " : " format " \n " , # # arg )
# define warn(format, arg...) \
printk ( KERN_WARNING AUDIO_NAME " : " format " \n " , # # arg )
struct snd_soc_codec_device soc_codec_dev_wm8731 ;
/*
* wm8731 register cache
* We can ' t read the WM8731 register space when we are
* using 2 wire for device control , so we cache them instead .
* There is no point in caching the reset register
*/
static const u16 wm8731_reg [ WM8731_CACHEREGNUM ] = {
0x0097 , 0x0097 , 0x0079 , 0x0079 ,
0x000a , 0x0008 , 0x009f , 0x000a ,
0x0000 , 0x0000
} ;
# define WM8731_DAIFMT \
( SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_RIGHT_J | \
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_NB_IF | SND_SOC_DAIFMT_IB_NF | \
SND_SOC_DAIFMT_IB_IF )
# define WM8731_DIR \
( SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE )
# define WM8731_RATES \
( SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \
SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 )
# define WM8731_HIFI_BITS \
( SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE )
static struct snd_soc_dai_mode wm8731_modes [ ] = {
/* codec frame and clock master modes */
/* 8k */
2006-10-13 14:33:56 +04:00
{
. fmt = WM8731_DAIFMT | SND_SOC_DAIFMT_CBM_CFM ,
2006-10-13 22:09:59 +04:00
. pcmfmt = WM8731_HIFI_BITS ,
. pcmrate = SNDRV_PCM_RATE_8000 ,
2006-10-13 14:33:56 +04:00
. pcmdir = WM8731_DIR ,
2006-10-19 22:35:56 +04:00
. flags = SND_SOC_DAI_BFS_RATE ,
2006-10-13 22:09:59 +04:00
. fs = 1536 ,
2006-10-19 22:35:56 +04:00
. bfs = 64 ,
2006-10-13 14:33:56 +04:00
} ,
{
. fmt = WM8731_DAIFMT | SND_SOC_DAIFMT_CBM_CFM ,
2006-10-13 22:09:59 +04:00
. pcmfmt = WM8731_HIFI_BITS ,
. pcmrate = SNDRV_PCM_RATE_8000 ,
2006-10-13 14:33:56 +04:00
. pcmdir = WM8731_DIR ,
2006-10-19 22:35:56 +04:00
. flags = SND_SOC_DAI_BFS_RATE ,
2006-10-13 22:09:59 +04:00
. fs = 2304 ,
2006-10-19 22:35:56 +04:00
. bfs = 64 ,
2006-10-13 14:33:56 +04:00
} ,
{
. fmt = WM8731_DAIFMT | SND_SOC_DAIFMT_CBM_CFM ,
2006-10-13 22:09:59 +04:00
. pcmfmt = WM8731_HIFI_BITS ,
. pcmrate = SNDRV_PCM_RATE_8000 ,
2006-10-13 14:33:56 +04:00
. pcmdir = WM8731_DIR ,
2006-10-19 22:35:56 +04:00
. flags = SND_SOC_DAI_BFS_RATE ,
2006-10-13 22:09:59 +04:00
. fs = 1408 ,
2006-10-19 22:35:56 +04:00
. bfs = 64 ,
2006-10-13 14:33:56 +04:00
} ,
{
. fmt = WM8731_DAIFMT | SND_SOC_DAIFMT_CBM_CFM ,
2006-10-13 22:09:59 +04:00
. pcmfmt = WM8731_HIFI_BITS ,
. pcmrate = SNDRV_PCM_RATE_8000 ,
2006-10-13 14:33:56 +04:00
. pcmdir = WM8731_DIR ,
2006-10-19 22:35:56 +04:00
. flags = SND_SOC_DAI_BFS_RATE ,
2006-10-13 22:09:59 +04:00
. fs = 2112 ,
2006-10-19 22:35:56 +04:00
. bfs = 64 ,
2006-10-13 14:33:56 +04:00
} ,
2006-10-06 20:36:07 +04:00
/* 32k */
2006-10-13 14:33:56 +04:00
{
. fmt = WM8731_DAIFMT | SND_SOC_DAIFMT_CBM_CFM ,
2006-10-13 22:09:59 +04:00
. pcmfmt = WM8731_HIFI_BITS ,
2006-10-13 14:33:56 +04:00
. pcmrate = SNDRV_PCM_RATE_32000 ,
. pcmdir = WM8731_DIR ,
2006-10-19 22:35:56 +04:00
. flags = SND_SOC_DAI_BFS_RATE ,
2006-10-13 22:09:59 +04:00
. fs = 384 ,
2006-10-19 22:35:56 +04:00
. bfs = 64 ,
2006-10-13 14:33:56 +04:00
} ,
{
. fmt = WM8731_DAIFMT | SND_SOC_DAIFMT_CBM_CFM ,
2006-10-13 22:09:59 +04:00
. pcmfmt = WM8731_HIFI_BITS ,
. pcmrate = SNDRV_PCM_RATE_32000 ,
2006-10-13 14:33:56 +04:00
. pcmdir = WM8731_DIR ,
2006-10-19 22:35:56 +04:00
. flags = SND_SOC_DAI_BFS_RATE ,
2006-10-13 22:09:59 +04:00
. fs = 576 ,
2006-10-19 22:35:56 +04:00
. bfs = 64 ,
2006-10-13 14:33:56 +04:00
} ,
2006-10-06 20:36:07 +04:00
/* 44.1k & 48k */
2006-10-13 14:33:56 +04:00
{
. fmt = WM8731_DAIFMT | SND_SOC_DAIFMT_CBM_CFM ,
2006-10-13 22:09:59 +04:00
. pcmfmt = WM8731_HIFI_BITS ,
2006-10-13 14:33:56 +04:00
. pcmrate = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 ,
. pcmdir = WM8731_DIR ,
2006-10-19 22:35:56 +04:00
. flags = SND_SOC_DAI_BFS_RATE ,
2006-10-13 22:09:59 +04:00
. fs = 256 ,
2006-10-19 22:35:56 +04:00
. bfs = 64 ,
2006-10-13 14:33:56 +04:00
} ,
{
. fmt = WM8731_DAIFMT | SND_SOC_DAIFMT_CBM_CFM ,
2006-10-13 22:09:59 +04:00
. pcmfmt = WM8731_HIFI_BITS ,
2006-10-13 14:33:56 +04:00
. pcmrate = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 ,
. pcmdir = WM8731_DIR ,
2006-10-19 22:35:56 +04:00
. flags = SND_SOC_DAI_BFS_RATE ,
2006-10-13 22:09:59 +04:00
. fs = 384 ,
2006-10-19 22:35:56 +04:00
. bfs = 64 ,
2006-10-13 14:33:56 +04:00
} ,
2006-10-06 20:36:07 +04:00
/* 88.2 & 96k */
2006-10-13 14:33:56 +04:00
{
. fmt = WM8731_DAIFMT | SND_SOC_DAIFMT_CBM_CFM ,
2006-10-13 22:09:59 +04:00
. pcmfmt = WM8731_HIFI_BITS ,
2006-10-13 14:33:56 +04:00
. pcmrate = SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 ,
. pcmdir = WM8731_DIR ,
2006-10-19 22:35:56 +04:00
. flags = SND_SOC_DAI_BFS_RATE ,
2006-10-13 22:09:59 +04:00
. fs = 128 ,
2006-10-19 22:35:56 +04:00
. bfs = 64 ,
2006-10-13 14:33:56 +04:00
} ,
{
. fmt = WM8731_DAIFMT | SND_SOC_DAIFMT_CBM_CFM ,
2006-10-13 22:09:59 +04:00
. pcmfmt = WM8731_HIFI_BITS ,
2006-10-13 14:33:56 +04:00
. pcmrate = SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 ,
. pcmdir = WM8731_DIR ,
2006-10-19 22:35:56 +04:00
. flags = SND_SOC_DAI_BFS_RATE ,
2006-10-13 22:09:59 +04:00
. fs = 192 ,
2006-10-19 22:35:56 +04:00
. bfs = 64 ,
2006-10-13 14:33:56 +04:00
} ,
2006-10-06 20:36:07 +04:00
/* USB codec frame and clock master modes */
/* 8k */
2006-10-13 14:33:56 +04:00
{
. fmt = WM8731_DAIFMT | SND_SOC_DAIFMT_CBM_CFM ,
2006-10-13 22:09:59 +04:00
. pcmfmt = WM8731_HIFI_BITS ,
. pcmrate = SNDRV_PCM_RATE_8000 ,
2006-10-13 14:33:56 +04:00
. pcmdir = WM8731_DIR ,
2006-10-13 22:09:59 +04:00
. flags = SND_SOC_DAI_BFS_DIV ,
. fs = 1500 ,
2006-10-13 14:33:56 +04:00
. bfs = SND_SOC_FSBD ( 1 ) ,
} ,
2006-10-06 20:36:07 +04:00
/* 44.1k */
2006-10-13 14:33:56 +04:00
{
. fmt = WM8731_DAIFMT | SND_SOC_DAIFMT_CBM_CFM ,
2006-10-13 22:09:59 +04:00
. pcmfmt = WM8731_HIFI_BITS ,
. pcmrate = SNDRV_PCM_RATE_44100 ,
2006-10-13 14:33:56 +04:00
. pcmdir = WM8731_DIR ,
. flags = SND_SOC_DAI_BFS_DIV ,
2006-10-13 22:09:59 +04:00
. fs = 272 ,
2006-10-13 14:33:56 +04:00
. bfs = SND_SOC_FSBD ( 1 ) ,
} ,
2006-10-06 20:36:07 +04:00
/* 48k */
2006-10-13 14:33:56 +04:00
{
. fmt = WM8731_DAIFMT | SND_SOC_DAIFMT_CBM_CFM ,
2006-10-13 22:09:59 +04:00
. pcmfmt = WM8731_HIFI_BITS ,
. pcmrate = SNDRV_PCM_RATE_48000 ,
2006-10-13 14:33:56 +04:00
. pcmdir = WM8731_DIR ,
2006-10-13 22:09:59 +04:00
. flags = SND_SOC_DAI_BFS_DIV ,
. fs = 250 ,
2006-10-13 14:33:56 +04:00
. bfs = SND_SOC_FSBD ( 1 ) ,
} ,
2006-10-06 20:36:07 +04:00
/* 88.2k */
2006-10-13 14:33:56 +04:00
{
2006-10-13 22:09:59 +04:00
. fmt = WM8731_DAIFMT | SND_SOC_DAIFMT_CBM_CFM ,
. pcmfmt = WM8731_HIFI_BITS ,
. pcmrate = SNDRV_PCM_RATE_88200 ,
2006-10-13 14:33:56 +04:00
. pcmdir = WM8731_DIR ,
. flags = SND_SOC_DAI_BFS_DIV ,
2006-10-13 22:09:59 +04:00
. fs = 136 ,
2006-10-13 14:33:56 +04:00
. bfs = SND_SOC_FSBD ( 1 ) ,
} ,
2006-10-06 20:36:07 +04:00
/* 96k */
2006-10-13 14:33:56 +04:00
{
. fmt = WM8731_DAIFMT | SND_SOC_DAIFMT_CBM_CFM ,
2006-10-13 22:09:59 +04:00
. pcmfmt = WM8731_HIFI_BITS ,
. pcmrate = SNDRV_PCM_RATE_96000 ,
2006-10-13 14:33:56 +04:00
. pcmdir = WM8731_DIR ,
2006-10-13 22:09:59 +04:00
. flags = SND_SOC_DAI_BFS_DIV ,
. fs = 125 ,
2006-10-13 14:33:56 +04:00
. bfs = SND_SOC_FSBD ( 1 ) ,
} ,
2006-10-06 20:36:07 +04:00
/* codec frame and clock slave modes */
2006-10-13 14:33:56 +04:00
{
. fmt = WM8731_DAIFMT | SND_SOC_DAIFMT_CBS_CFS ,
2006-10-13 22:09:59 +04:00
. pcmfmt = WM8731_HIFI_BITS ,
. pcmrate = WM8731_RATES ,
2006-10-13 14:33:56 +04:00
. pcmdir = WM8731_DIR ,
. flags = SND_SOC_DAI_BFS_DIV ,
2006-10-13 22:09:59 +04:00
. fs = SND_SOC_FS_ALL ,
2006-10-19 22:35:56 +04:00
. bfs = SND_SOC_FSB_ALL ,
2006-10-13 14:33:56 +04:00
} ,
2006-10-06 20:36:07 +04:00
} ;
/*
* read wm8731 register cache
*/
static inline unsigned int wm8731_read_reg_cache ( struct snd_soc_codec * codec ,
unsigned int reg )
{
u16 * cache = codec - > reg_cache ;
if ( reg = = WM8731_RESET )
return 0 ;
if ( reg > = WM8731_CACHEREGNUM )
return - 1 ;
return cache [ reg ] ;
}
/*
* write wm8731 register cache
*/
static inline void wm8731_write_reg_cache ( struct snd_soc_codec * codec ,
u16 reg , unsigned int value )
{
u16 * cache = codec - > reg_cache ;
if ( reg > = WM8731_CACHEREGNUM )
return ;
cache [ reg ] = value ;
}
/*
* write to the WM8731 register space
*/
static int wm8731_write ( struct snd_soc_codec * codec , unsigned int reg ,
unsigned int value )
{
u8 data [ 2 ] ;
/* data is
* D15 . . D9 WM8731 register offset
* D8 . . . D0 register data
*/
data [ 0 ] = ( reg < < 1 ) | ( ( value > > 8 ) & 0x0001 ) ;
data [ 1 ] = value & 0x00ff ;
wm8731_write_reg_cache ( codec , reg , value ) ;
if ( codec - > hw_write ( codec - > control_data , data , 2 ) = = 2 )
return 0 ;
else
return - EIO ;
}
# define wm8731_reset(c) wm8731_write(c, WM8731_RESET, 0)
static const char * wm8731_input_select [ ] = { " Line In " , " Mic " } ;
static const char * wm8731_deemph [ ] = { " None " , " 32Khz " , " 44.1Khz " , " 48Khz " } ;
static const struct soc_enum wm8731_enum [ ] = {
SOC_ENUM_SINGLE ( WM8731_APANA , 2 , 2 , wm8731_input_select ) ,
SOC_ENUM_SINGLE ( WM8731_APDIGI , 1 , 4 , wm8731_deemph ) ,
} ;
static const struct snd_kcontrol_new wm8731_snd_controls [ ] = {
2006-11-09 18:35:01 +03:00
SOC_DOUBLE_R ( " Master Playback Volume " , WM8731_LOUT1V , WM8731_ROUT1V ,
0 , 127 , 0 ) ,
SOC_DOUBLE_R ( " Master Playback ZC Switch " , WM8731_LOUT1V , WM8731_ROUT1V ,
7 , 1 , 0 ) ,
2006-10-06 20:36:07 +04:00
SOC_DOUBLE_R ( " Capture Volume " , WM8731_LINVOL , WM8731_RINVOL , 0 , 31 , 0 ) ,
SOC_DOUBLE_R ( " Line Capture Switch " , WM8731_LINVOL , WM8731_RINVOL , 7 , 1 , 1 ) ,
SOC_SINGLE ( " Mic Boost (+20dB) " , WM8731_APANA , 0 , 1 , 0 ) ,
SOC_SINGLE ( " Capture Mic Switch " , WM8731_APANA , 1 , 1 , 1 ) ,
SOC_SINGLE ( " Sidetone Playback Volume " , WM8731_APANA , 6 , 3 , 1 ) ,
SOC_SINGLE ( " ADC High Pass Filter Switch " , WM8731_APDIGI , 0 , 1 , 1 ) ,
SOC_SINGLE ( " Store DC Offset Switch " , WM8731_APDIGI , 4 , 1 , 0 ) ,
SOC_ENUM ( " Playback De-emphasis " , wm8731_enum [ 1 ] ) ,
} ;
/* add non dapm controls */
static int wm8731_add_controls ( struct snd_soc_codec * codec )
{
int err , i ;
for ( i = 0 ; i < ARRAY_SIZE ( wm8731_snd_controls ) ; i + + ) {
if ( ( err = snd_ctl_add ( codec - > card ,
snd_soc_cnew ( & wm8731_snd_controls [ i ] , codec , NULL ) ) ) < 0 )
return err ;
}
return 0 ;
}
/* Output Mixer */
static const struct snd_kcontrol_new wm8731_output_mixer_controls [ ] = {
SOC_DAPM_SINGLE ( " Line Bypass Switch " , WM8731_APANA , 3 , 1 , 0 ) ,
SOC_DAPM_SINGLE ( " Mic Sidetone Switch " , WM8731_APANA , 5 , 1 , 0 ) ,
SOC_DAPM_SINGLE ( " HiFi Playback Switch " , WM8731_APANA , 4 , 1 , 0 ) ,
} ;
/* Input mux */
static const struct snd_kcontrol_new wm8731_input_mux_controls =
SOC_DAPM_ENUM ( " Input Select " , wm8731_enum [ 0 ] ) ;
static const struct snd_soc_dapm_widget wm8731_dapm_widgets [ ] = {
SND_SOC_DAPM_MIXER ( " Output Mixer " , WM8731_PWR , 4 , 1 ,
& wm8731_output_mixer_controls [ 0 ] ,
ARRAY_SIZE ( wm8731_output_mixer_controls ) ) ,
SND_SOC_DAPM_DAC ( " DAC " , " HiFi Playback " , WM8731_PWR , 3 , 1 ) ,
SND_SOC_DAPM_OUTPUT ( " LOUT " ) ,
SND_SOC_DAPM_OUTPUT ( " LHPOUT " ) ,
SND_SOC_DAPM_OUTPUT ( " ROUT " ) ,
SND_SOC_DAPM_OUTPUT ( " RHPOUT " ) ,
SND_SOC_DAPM_ADC ( " ADC " , " HiFi Capture " , WM8731_PWR , 2 , 1 ) ,
SND_SOC_DAPM_MUX ( " Input Mux " , SND_SOC_NOPM , 0 , 0 , & wm8731_input_mux_controls ) ,
SND_SOC_DAPM_PGA ( " Line Input " , WM8731_PWR , 0 , 1 , NULL , 0 ) ,
SND_SOC_DAPM_MICBIAS ( " Mic Bias " , WM8731_PWR , 1 , 1 ) ,
SND_SOC_DAPM_INPUT ( " MICIN " ) ,
SND_SOC_DAPM_INPUT ( " RLINEIN " ) ,
SND_SOC_DAPM_INPUT ( " LLINEIN " ) ,
} ;
static const char * intercon [ ] [ 3 ] = {
/* output mixer */
{ " Output Mixer " , " Line Bypass Switch " , " Line Input " } ,
{ " Output Mixer " , " HiFi Playback Switch " , " DAC " } ,
{ " Output Mixer " , " Mic Sidetone Switch " , " Mic Bias " } ,
/* outputs */
{ " RHPOUT " , NULL , " Output Mixer " } ,
{ " ROUT " , NULL , " Output Mixer " } ,
{ " LHPOUT " , NULL , " Output Mixer " } ,
{ " LOUT " , NULL , " Output Mixer " } ,
/* input mux */
{ " Input Mux " , " Line In " , " Line Input " } ,
{ " Input Mux " , " Mic " , " Mic Bias " } ,
{ " ADC " , NULL , " Input Mux " } ,
/* inputs */
{ " Line Input " , NULL , " LLINEIN " } ,
{ " Line Input " , NULL , " RLINEIN " } ,
{ " Mic Bias " , NULL , " MICIN " } ,
/* terminator */
{ NULL , NULL , NULL } ,
} ;
static int wm8731_add_widgets ( struct snd_soc_codec * codec )
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( wm8731_dapm_widgets ) ; i + + ) {
snd_soc_dapm_new_control ( codec , & wm8731_dapm_widgets [ i ] ) ;
}
/* set up audio path interconnects */
for ( i = 0 ; intercon [ i ] [ 0 ] ! = NULL ; i + + ) {
snd_soc_dapm_connect_input ( codec , intercon [ i ] [ 0 ] ,
intercon [ i ] [ 1 ] , intercon [ i ] [ 2 ] ) ;
}
snd_soc_dapm_new_widgets ( codec ) ;
return 0 ;
}
struct _coeff_div {
u32 mclk ;
u32 rate ;
u16 fs ;
u8 sr : 4 ;
u8 bosr : 1 ;
u8 usb : 1 ;
} ;
/* codec mclk clock divider coefficients */
static const struct _coeff_div coeff_div [ ] = {
/* 48k */
{ 12288000 , 48000 , 256 , 0x0 , 0x0 , 0x0 } ,
{ 18432000 , 48000 , 384 , 0x0 , 0x1 , 0x0 } ,
{ 12000000 , 48000 , 250 , 0x0 , 0x0 , 0x1 } ,
/* 32k */
{ 12288000 , 32000 , 384 , 0x6 , 0x0 , 0x0 } ,
{ 18432000 , 32000 , 576 , 0x6 , 0x1 , 0x0 } ,
/* 8k */
{ 12288000 , 8000 , 1536 , 0x3 , 0x0 , 0x0 } ,
{ 18432000 , 8000 , 2304 , 0x3 , 0x1 , 0x0 } ,
{ 11289600 , 8000 , 1408 , 0xb , 0x0 , 0x0 } ,
{ 16934400 , 8000 , 2112 , 0xb , 0x1 , 0x0 } ,
{ 12000000 , 8000 , 1500 , 0x3 , 0x0 , 0x1 } ,
/* 96k */
{ 12288000 , 96000 , 128 , 0x7 , 0x0 , 0x0 } ,
{ 18432000 , 96000 , 192 , 0x7 , 0x1 , 0x0 } ,
{ 12000000 , 96000 , 125 , 0x7 , 0x0 , 0x1 } ,
/* 44.1k */
{ 11289600 , 44100 , 256 , 0x8 , 0x0 , 0x0 } ,
{ 16934400 , 44100 , 384 , 0x8 , 0x1 , 0x0 } ,
{ 12000000 , 44100 , 272 , 0x8 , 0x1 , 0x1 } ,
/* 88.2k */
{ 11289600 , 88200 , 128 , 0xf , 0x0 , 0x0 } ,
{ 16934400 , 88200 , 192 , 0xf , 0x1 , 0x0 } ,
{ 12000000 , 88200 , 136 , 0xf , 0x1 , 0x1 } ,
} ;
static inline int get_coeff ( int mclk , int rate )
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( coeff_div ) ; i + + ) {
if ( coeff_div [ i ] . rate = = rate & & coeff_div [ i ] . mclk = = mclk )
return i ;
}
return 0 ;
}
/* WM8731 supports numerous clocks per sample rate */
static unsigned int wm8731_config_sysclk ( struct snd_soc_codec_dai * dai ,
struct snd_soc_clock_info * info , unsigned int clk )
{
dai - > mclk = 0 ;
/* check that the calculated FS and rate actually match a clock from
* the machine driver */
if ( info - > fs * info - > rate = = clk )
dai - > mclk = clk ;
return dai - > mclk ;
}
static int wm8731_pcm_prepare ( struct snd_pcm_substream * substream )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct snd_soc_device * socdev = rtd - > socdev ;
struct snd_soc_codec * codec = socdev - > codec ;
u16 iface = 0 , srate ;
int i = get_coeff ( rtd - > codec_dai - > mclk ,
snd_soc_get_rate ( rtd - > codec_dai - > dai_runtime . pcmrate ) ) ;
/* set master/slave audio interface */
switch ( rtd - > codec_dai - > dai_runtime . fmt & SND_SOC_DAIFMT_CLOCK_MASK ) {
case SND_SOC_DAIFMT_CBM_CFM :
iface | = 0x0040 ;
break ;
case SND_SOC_DAIFMT_CBS_CFS :
break ;
}
srate = ( coeff_div [ i ] . sr < < 2 ) |
( coeff_div [ i ] . bosr < < 1 ) | coeff_div [ i ] . usb ;
wm8731_write ( codec , WM8731_SRATE , srate ) ;
/* interface format */
switch ( rtd - > codec_dai - > dai_runtime . fmt & SND_SOC_DAIFMT_FORMAT_MASK ) {
case SND_SOC_DAIFMT_I2S :
iface | = 0x0002 ;
break ;
case SND_SOC_DAIFMT_RIGHT_J :
break ;
case SND_SOC_DAIFMT_LEFT_J :
iface | = 0x0001 ;
break ;
case SND_SOC_DAIFMT_DSP_A :
iface | = 0x0003 ;
break ;
case SND_SOC_DAIFMT_DSP_B :
iface | = 0x0013 ;
break ;
}
/* bit size */
switch ( rtd - > codec_dai - > dai_runtime . pcmfmt ) {
case SNDRV_PCM_FMTBIT_S16_LE :
break ;
case SNDRV_PCM_FMTBIT_S20_3LE :
iface | = 0x0004 ;
break ;
case SNDRV_PCM_FMTBIT_S24_LE :
iface | = 0x0008 ;
break ;
case SNDRV_PCM_FMTBIT_S32_LE :
iface | = 0x000c ;
break ;
}
/* clock inversion */
switch ( rtd - > codec_dai - > dai_runtime . fmt & SND_SOC_DAIFMT_INV_MASK ) {
case SND_SOC_DAIFMT_NB_NF :
break ;
case SND_SOC_DAIFMT_IB_IF :
iface | = 0x0090 ;
break ;
case SND_SOC_DAIFMT_IB_NF :
iface | = 0x0080 ;
break ;
case SND_SOC_DAIFMT_NB_IF :
iface | = 0x0010 ;
break ;
}
/* set iface */
wm8731_write ( codec , WM8731_IFACE , iface ) ;
/* set active */
wm8731_write ( codec , WM8731_ACTIVE , 0x0001 ) ;
return 0 ;
}
static void wm8731_shutdown ( struct snd_pcm_substream * substream )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct snd_soc_device * socdev = rtd - > socdev ;
struct snd_soc_codec * codec = socdev - > codec ;
/* deactivate */
if ( ! codec - > active ) {
udelay ( 50 ) ;
wm8731_write ( codec , WM8731_ACTIVE , 0x0 ) ;
}
}
static int wm8731_mute ( struct snd_soc_codec * codec ,
struct snd_soc_codec_dai * dai , int mute )
{
u16 mute_reg = wm8731_read_reg_cache ( codec , WM8731_APDIGI ) & 0xfff7 ;
if ( mute )
wm8731_write ( codec , WM8731_APDIGI , mute_reg | 0x8 ) ;
else
wm8731_write ( codec , WM8731_APDIGI , mute_reg ) ;
return 0 ;
}
static int wm8731_dapm_event ( struct snd_soc_codec * codec , int event )
{
u16 reg = wm8731_read_reg_cache ( codec , WM8731_PWR ) & 0xff7f ;
switch ( event ) {
case SNDRV_CTL_POWER_D0 : /* full On */
/* vref/mid, osc on, dac unmute */
wm8731_write ( codec , WM8731_PWR , reg ) ;
break ;
case SNDRV_CTL_POWER_D1 : /* partial On */
case SNDRV_CTL_POWER_D2 : /* partial On */
break ;
case SNDRV_CTL_POWER_D3hot : /* Off, with power */
/* everything off except vref/vmid, */
wm8731_write ( codec , WM8731_PWR , reg | 0x0040 ) ;
break ;
case SNDRV_CTL_POWER_D3cold : /* Off, without power */
/* everything off, dac mute, inactive */
wm8731_write ( codec , WM8731_ACTIVE , 0x0 ) ;
wm8731_write ( codec , WM8731_PWR , 0xffff ) ;
break ;
}
codec - > dapm_state = event ;
return 0 ;
}
struct snd_soc_codec_dai wm8731_dai = {
. name = " WM8731 " ,
. playback = {
. stream_name = " Playback " ,
. channels_min = 1 ,
. channels_max = 2 ,
} ,
. capture = {
. stream_name = " Capture " ,
. channels_min = 1 ,
. channels_max = 2 ,
} ,
. config_sysclk = wm8731_config_sysclk ,
. digital_mute = wm8731_mute ,
. ops = {
. prepare = wm8731_pcm_prepare ,
. shutdown = wm8731_shutdown ,
} ,
. caps = {
. num_modes = ARRAY_SIZE ( wm8731_modes ) ,
. mode = wm8731_modes ,
} ,
} ;
EXPORT_SYMBOL_GPL ( wm8731_dai ) ;
static int wm8731_suspend ( struct platform_device * pdev , pm_message_t state )
{
struct snd_soc_device * socdev = platform_get_drvdata ( pdev ) ;
struct snd_soc_codec * codec = socdev - > codec ;
wm8731_write ( codec , WM8731_ACTIVE , 0x0 ) ;
wm8731_dapm_event ( codec , SNDRV_CTL_POWER_D3cold ) ;
return 0 ;
}
static int wm8731_resume ( struct platform_device * pdev )
{
struct snd_soc_device * socdev = platform_get_drvdata ( pdev ) ;
struct snd_soc_codec * codec = socdev - > codec ;
int i ;
u8 data [ 2 ] ;
u16 * cache = codec - > reg_cache ;
/* Sync reg_cache with the hardware */
for ( i = 0 ; i < ARRAY_SIZE ( wm8731_reg ) ; i + + ) {
data [ 0 ] = ( i < < 1 ) | ( ( cache [ i ] > > 8 ) & 0x0001 ) ;
data [ 1 ] = cache [ i ] & 0x00ff ;
codec - > hw_write ( codec - > control_data , data , 2 ) ;
}
wm8731_dapm_event ( codec , SNDRV_CTL_POWER_D3hot ) ;
wm8731_dapm_event ( codec , codec - > suspend_dapm_state ) ;
return 0 ;
}
/*
* initialise the WM8731 driver
* register the mixer and dsp interfaces with the kernel
*/
static int wm8731_init ( struct snd_soc_device * socdev )
{
struct snd_soc_codec * codec = socdev - > codec ;
int reg , ret = 0 ;
codec - > name = " WM8731 " ;
codec - > owner = THIS_MODULE ;
codec - > read = wm8731_read_reg_cache ;
codec - > write = wm8731_write ;
codec - > dapm_event = wm8731_dapm_event ;
codec - > dai = & wm8731_dai ;
codec - > num_dai = 1 ;
codec - > reg_cache_size = ARRAY_SIZE ( wm8731_reg ) ;
codec - > reg_cache =
kzalloc ( sizeof ( u16 ) * ARRAY_SIZE ( wm8731_reg ) , GFP_KERNEL ) ;
if ( codec - > reg_cache = = NULL )
return - ENOMEM ;
memcpy ( codec - > reg_cache ,
wm8731_reg , sizeof ( u16 ) * ARRAY_SIZE ( wm8731_reg ) ) ;
codec - > reg_cache_size = sizeof ( u16 ) * ARRAY_SIZE ( wm8731_reg ) ;
wm8731_reset ( codec ) ;
/* register pcms */
ret = snd_soc_new_pcms ( socdev , SNDRV_DEFAULT_IDX1 , SNDRV_DEFAULT_STR1 ) ;
if ( ret < 0 ) {
2007-01-31 12:02:23 +03:00
printk ( KERN_ERR " wm8731: failed to create pcms \n " ) ;
goto pcm_err ;
2006-10-06 20:36:07 +04:00
}
/* power on device */
wm8731_dapm_event ( codec , SNDRV_CTL_POWER_D3hot ) ;
/* set the update bits */
reg = wm8731_read_reg_cache ( codec , WM8731_LOUT1V ) ;
wm8731_write ( codec , WM8731_LOUT1V , reg | 0x0100 ) ;
reg = wm8731_read_reg_cache ( codec , WM8731_ROUT1V ) ;
wm8731_write ( codec , WM8731_ROUT1V , reg | 0x0100 ) ;
reg = wm8731_read_reg_cache ( codec , WM8731_LINVOL ) ;
wm8731_write ( codec , WM8731_LINVOL , reg | 0x0100 ) ;
reg = wm8731_read_reg_cache ( codec , WM8731_RINVOL ) ;
wm8731_write ( codec , WM8731_RINVOL , reg | 0x0100 ) ;
wm8731_add_controls ( codec ) ;
wm8731_add_widgets ( codec ) ;
ret = snd_soc_register_card ( socdev ) ;
if ( ret < 0 ) {
2007-01-31 12:02:23 +03:00
printk ( KERN_ERR " wm8731: failed to register card \n " ) ;
goto card_err ;
2006-10-06 20:36:07 +04:00
}
return ret ;
2007-01-31 12:02:23 +03:00
card_err :
snd_soc_free_pcms ( socdev ) ;
snd_soc_dapm_free ( socdev ) ;
pcm_err :
kfree ( codec - > reg_cache ) ;
return ret ;
2006-10-06 20:36:07 +04:00
}
static struct snd_soc_device * wm8731_socdev ;
# if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
/*
* WM8731 2 wire address is determined by GPIO5
* state during powerup .
* low = 0x1a
* high = 0x1b
*/
static unsigned short normal_i2c [ ] = { 0 , I2C_CLIENT_END } ;
/* Magic definition of all other variables and things */
I2C_CLIENT_INSMOD ;
static struct i2c_driver wm8731_i2c_driver ;
static struct i2c_client client_template ;
/* If the i2c layer weren't so broken, we could pass this kind of data
around */
static int wm8731_codec_probe ( struct i2c_adapter * adap , int addr , int kind )
{
struct snd_soc_device * socdev = wm8731_socdev ;
struct wm8731_setup_data * setup = socdev - > codec_data ;
struct snd_soc_codec * codec = socdev - > codec ;
struct i2c_client * i2c ;
int ret ;
if ( addr ! = setup - > i2c_address )
return - ENODEV ;
client_template . adapter = adap ;
client_template . addr = addr ;
i2c = kzalloc ( sizeof ( struct i2c_client ) , GFP_KERNEL ) ;
if ( i2c = = NULL ) {
kfree ( codec ) ;
return - ENOMEM ;
}
memcpy ( i2c , & client_template , sizeof ( struct i2c_client ) ) ;
i2c_set_clientdata ( i2c , codec ) ;
codec - > control_data = i2c ;
ret = i2c_attach_client ( i2c ) ;
if ( ret < 0 ) {
err ( " failed to attach codec at addr %x \n " , addr ) ;
goto err ;
}
ret = wm8731_init ( socdev ) ;
if ( ret < 0 ) {
err ( " failed to initialise WM8731 \n " ) ;
goto err ;
}
return ret ;
err :
kfree ( codec ) ;
kfree ( i2c ) ;
return ret ;
}
static int wm8731_i2c_detach ( struct i2c_client * client )
{
struct snd_soc_codec * codec = i2c_get_clientdata ( client ) ;
i2c_detach_client ( client ) ;
kfree ( codec - > reg_cache ) ;
kfree ( client ) ;
return 0 ;
}
static int wm8731_i2c_attach ( struct i2c_adapter * adap )
{
return i2c_probe ( adap , & addr_data , wm8731_codec_probe ) ;
}
/* corgi i2c codec control layer */
static struct i2c_driver wm8731_i2c_driver = {
. driver = {
. name = " WM8731 I2C Codec " ,
. owner = THIS_MODULE ,
} ,
. id = I2C_DRIVERID_WM8731 ,
. attach_adapter = wm8731_i2c_attach ,
. detach_client = wm8731_i2c_detach ,
. command = NULL ,
} ;
static struct i2c_client client_template = {
. name = " WM8731 " ,
. driver = & wm8731_i2c_driver ,
} ;
# endif
static int wm8731_probe ( struct platform_device * pdev )
{
struct snd_soc_device * socdev = platform_get_drvdata ( pdev ) ;
struct wm8731_setup_data * setup ;
struct snd_soc_codec * codec ;
int ret = 0 ;
info ( " WM8731 Audio Codec %s " , WM8731_VERSION ) ;
setup = socdev - > codec_data ;
codec = kzalloc ( sizeof ( struct snd_soc_codec ) , GFP_KERNEL ) ;
if ( codec = = NULL )
return - ENOMEM ;
socdev - > codec = codec ;
mutex_init ( & codec - > mutex ) ;
INIT_LIST_HEAD ( & codec - > dapm_widgets ) ;
INIT_LIST_HEAD ( & codec - > dapm_paths ) ;
wm8731_socdev = socdev ;
# if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
if ( setup - > i2c_address ) {
normal_i2c [ 0 ] = setup - > i2c_address ;
codec - > hw_write = ( hw_write_t ) i2c_master_send ;
ret = i2c_add_driver ( & wm8731_i2c_driver ) ;
if ( ret ! = 0 )
printk ( KERN_ERR " can't add i2c driver " ) ;
}
# else
/* Add other interfaces here */
# endif
return ret ;
}
/* power down chip */
static int wm8731_remove ( struct platform_device * pdev )
{
struct snd_soc_device * socdev = platform_get_drvdata ( pdev ) ;
struct snd_soc_codec * codec = socdev - > codec ;
if ( codec - > control_data )
wm8731_dapm_event ( codec , SNDRV_CTL_POWER_D3cold ) ;
snd_soc_free_pcms ( socdev ) ;
snd_soc_dapm_free ( socdev ) ;
# if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
i2c_del_driver ( & wm8731_i2c_driver ) ;
# endif
kfree ( codec ) ;
return 0 ;
}
struct snd_soc_codec_device soc_codec_dev_wm8731 = {
. probe = wm8731_probe ,
. remove = wm8731_remove ,
. suspend = wm8731_suspend ,
. resume = wm8731_resume ,
} ;
EXPORT_SYMBOL_GPL ( soc_codec_dev_wm8731 ) ;
MODULE_DESCRIPTION ( " ASoC WM8731 driver " ) ;
MODULE_AUTHOR ( " Richard Purdie " ) ;
MODULE_LICENSE ( " GPL " ) ;