2008-11-21 17:01:41 +03:00
/*
* zylonite . c - - SoC audio for Zylonite
*
* Copyright 2008 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 .
*
*/
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/device.h>
2009-01-16 19:35:52 +03:00
# include <linux/clk.h>
2008-11-21 17:01:41 +03:00
# include <linux/i2c.h>
# include <sound/core.h>
# include <sound/pcm.h>
# include <sound/pcm_params.h>
# include <sound/soc.h>
# include "../codecs/wm9713.h"
# include "pxa2xx-ac97.h"
# include "pxa-ssp.h"
2009-01-16 19:35:52 +03:00
/*
* There is a physical switch SW15 on the board which changes the MCLK
* for the WM9713 between the standard AC97 master clock and the
* output of the CLK_POUT signal from the PXA .
*/
static int clk_pout ;
module_param ( clk_pout , int , 0 ) ;
MODULE_PARM_DESC ( clk_pout , " Use CLK_POUT as WM9713 MCLK (SW15 on board). " ) ;
static struct clk * pout ;
2008-11-21 17:01:41 +03:00
static struct snd_soc_card zylonite ;
static const struct snd_soc_dapm_widget zylonite_dapm_widgets [ ] = {
SND_SOC_DAPM_HP ( " Headphone " , NULL ) ,
SND_SOC_DAPM_MIC ( " Headset Microphone " , NULL ) ,
SND_SOC_DAPM_MIC ( " Handset Microphone " , NULL ) ,
SND_SOC_DAPM_SPK ( " Multiactor " , NULL ) ,
SND_SOC_DAPM_SPK ( " Headset Earpiece " , NULL ) ,
} ;
/* Currently supported audio map */
static const struct snd_soc_dapm_route audio_map [ ] = {
/* Headphone output connected to HPL/HPR */
{ " Headphone " , NULL , " HPL " } ,
{ " Headphone " , NULL , " HPR " } ,
/* On-board earpiece */
{ " Headset Earpiece " , NULL , " OUT3 " } ,
/* Headphone mic */
{ " MIC2A " , NULL , " Mic Bias " } ,
{ " Mic Bias " , NULL , " Headset Microphone " } ,
/* On-board mic */
{ " MIC1 " , NULL , " Mic Bias " } ,
{ " Mic Bias " , NULL , " Handset Microphone " } ,
/* Multiactor differentially connected over SPKL/SPKR */
{ " Multiactor " , NULL , " SPKL " } ,
{ " Multiactor " , NULL , " SPKR " } ,
} ;
2010-03-17 23:15:21 +03:00
static int zylonite_wm9713_init ( struct snd_soc_pcm_runtime * rtd )
2008-11-21 17:01:41 +03:00
{
2010-03-17 23:15:21 +03:00
struct snd_soc_codec * codec = rtd - > codec ;
2010-11-05 16:53:46 +03:00
struct snd_soc_dapm_context * dapm = & codec - > dapm ;
2010-03-17 23:15:21 +03:00
2009-01-16 19:35:52 +03:00
if ( clk_pout )
2010-03-17 23:15:21 +03:00
snd_soc_dai_set_pll ( rtd - > codec_dai , 0 , 0 ,
2009-09-05 21:52:16 +04:00
clk_get_rate ( pout ) , 0 ) ;
2008-11-21 17:01:41 +03:00
2010-11-05 16:53:46 +03:00
snd_soc_dapm_new_controls ( dapm , zylonite_dapm_widgets ,
2008-11-21 17:01:41 +03:00
ARRAY_SIZE ( zylonite_dapm_widgets ) ) ;
2010-11-05 16:53:46 +03:00
snd_soc_dapm_add_routes ( dapm , audio_map , ARRAY_SIZE ( audio_map ) ) ;
2008-11-21 17:01:41 +03:00
/* Static setup for now */
2010-11-05 16:53:46 +03:00
snd_soc_dapm_enable_pin ( dapm , " Headphone " ) ;
snd_soc_dapm_enable_pin ( dapm , " Headset Earpiece " ) ;
2008-11-21 17:01:41 +03:00
return 0 ;
}
static int zylonite_voice_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
2010-03-17 23:15:21 +03:00
struct snd_soc_dai * codec_dai = rtd - > codec_dai ;
struct snd_soc_dai * cpu_dai = rtd - > cpu_dai ;
2009-01-23 14:49:45 +03:00
unsigned int pll_out = 0 ;
2008-11-21 17:01:41 +03:00
unsigned int wm9713_div = 0 ;
int ret = 0 ;
2009-03-13 17:27:08 +03:00
int rate = params_rate ( params ) ;
int width = snd_pcm_format_physical_width ( params_format ( params ) ) ;
/* Only support ratios that we can generate neatly from the AC97
* based master clock - in particular , this excludes 44.1 kHz .
* In most applications the voice DAC will be used for telephony
* data so multiples of 8 kHz will be the common case .
*/
switch ( rate ) {
2008-11-21 17:01:41 +03:00
case 8000 :
wm9713_div = 12 ;
break ;
case 16000 :
wm9713_div = 6 ;
break ;
case 48000 :
wm9713_div = 2 ;
break ;
2009-03-13 17:27:08 +03:00
default :
/* Don't support OSS emulation */
return - EINVAL ;
2008-11-21 17:01:41 +03:00
}
2009-03-13 17:27:08 +03:00
/* Add 1 to the width for the leading clock cycle */
pll_out = rate * ( width + 1 ) * 8 ;
2008-11-21 17:01:41 +03:00
2009-03-13 17:27:08 +03:00
ret = snd_soc_dai_set_sysclk ( cpu_dai , PXA_SSP_CLK_AUDIO , 0 , 1 ) ;
2008-11-21 17:01:41 +03:00
if ( ret < 0 )
return ret ;
2009-09-05 21:52:16 +04:00
ret = snd_soc_dai_set_pll ( cpu_dai , 0 , 0 , 0 , pll_out ) ;
2009-01-23 14:49:45 +03:00
if ( ret < 0 )
return ret ;
2009-01-16 19:35:52 +03:00
if ( clk_pout )
ret = snd_soc_dai_set_clkdiv ( codec_dai , WM9713_PCMCLK_PLL_DIV ,
WM9713_PCMDIV ( wm9713_div ) ) ;
else
ret = snd_soc_dai_set_clkdiv ( codec_dai , WM9713_PCMCLK_DIV ,
WM9713_PCMDIV ( wm9713_div ) ) ;
2008-11-21 17:01:41 +03:00
if ( ret < 0 )
return ret ;
2009-03-13 17:27:08 +03:00
ret = snd_soc_dai_set_fmt ( codec_dai , SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS ) ;
if ( ret < 0 )
return ret ;
ret = snd_soc_dai_set_fmt ( cpu_dai , SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS ) ;
if ( ret < 0 )
return ret ;
2008-11-21 17:01:41 +03:00
return 0 ;
}
static struct snd_soc_ops zylonite_voice_ops = {
. hw_params = zylonite_voice_hw_params ,
} ;
static struct snd_soc_dai_link zylonite_dai [ ] = {
{
. name = " AC97 " ,
. stream_name = " AC97 HiFi " ,
2010-03-17 23:15:21 +03:00
. codec_name = " wm9713-codec " ,
. platform_name = " pxa-pcm-audio " ,
2011-02-23 02:29:11 +03:00
. cpu_dai_name = " pxa2xx-ac97 " ,
2011-03-18 14:47:33 +03:00
. codec_dai_name = " wm9713-hifi " ,
2008-11-21 17:01:41 +03:00
. init = zylonite_wm9713_init ,
} ,
{
. name = " AC97 Aux " ,
. stream_name = " AC97 Aux " ,
2010-03-17 23:15:21 +03:00
. codec_name = " wm9713-codec " ,
. platform_name = " pxa-pcm-audio " ,
2011-02-23 02:29:11 +03:00
. cpu_dai_name = " pxa2xx-ac97-aux " ,
2011-03-18 14:47:33 +03:00
. codec_dai_name = " wm9713-aux " ,
2008-11-21 17:01:41 +03:00
} ,
{
. name = " WM9713 Voice " ,
. stream_name = " WM9713 Voice " ,
2010-03-17 23:15:21 +03:00
. codec_name = " wm9713-codec " ,
. platform_name = " pxa-pcm-audio " ,
. cpu_dai_name = " pxa-ssp-dai.2 " ,
2011-03-18 14:47:33 +03:00
. codec_dai_name = " wm9713-voice " ,
2008-11-21 17:01:41 +03:00
. ops = & zylonite_voice_ops ,
} ,
} ;
2011-01-26 17:17:20 +03:00
static int zylonite_probe ( struct snd_soc_card * card )
2009-01-16 19:35:52 +03:00
{
int ret ;
if ( clk_pout ) {
pout = clk_get ( NULL , " CLK_POUT " ) ;
if ( IS_ERR ( pout ) ) {
2011-10-02 00:03:34 +04:00
dev_err ( card - > dev , " Unable to obtain CLK_POUT: %ld \n " ,
2009-01-16 19:35:52 +03:00
PTR_ERR ( pout ) ) ;
return PTR_ERR ( pout ) ;
}
ret = clk_enable ( pout ) ;
if ( ret ! = 0 ) {
2011-10-02 00:03:34 +04:00
dev_err ( card - > dev , " Unable to enable CLK_POUT: %d \n " ,
2009-01-16 19:35:52 +03:00
ret ) ;
clk_put ( pout ) ;
return ret ;
}
2011-10-02 00:03:34 +04:00
dev_dbg ( card - > dev , " MCLK enabled at %luHz \n " ,
2009-01-16 19:35:52 +03:00
clk_get_rate ( pout ) ) ;
}
return 0 ;
}
2011-01-26 17:17:20 +03:00
static int zylonite_remove ( struct snd_soc_card * card )
2009-01-16 19:35:52 +03:00
{
if ( clk_pout ) {
clk_disable ( pout ) ;
clk_put ( pout ) ;
}
return 0 ;
}
2011-01-26 17:05:25 +03:00
static int zylonite_suspend_post ( struct snd_soc_card * card )
2009-01-16 19:35:52 +03:00
{
if ( clk_pout )
clk_disable ( pout ) ;
return 0 ;
}
2011-01-26 17:05:25 +03:00
static int zylonite_resume_pre ( struct snd_soc_card * card )
2009-01-16 19:35:52 +03:00
{
int ret = 0 ;
if ( clk_pout ) {
ret = clk_enable ( pout ) ;
if ( ret ! = 0 )
2011-10-02 00:03:34 +04:00
dev_err ( card - > dev , " Unable to enable CLK_POUT: %d \n " ,
2009-01-16 19:35:52 +03:00
ret ) ;
}
return ret ;
}
2008-11-21 17:01:41 +03:00
static struct snd_soc_card zylonite = {
. name = " Zylonite " ,
2011-12-22 05:44:43 +04:00
. owner = THIS_MODULE ,
2009-01-16 19:35:52 +03:00
. probe = & zylonite_probe ,
. remove = & zylonite_remove ,
. suspend_post = & zylonite_suspend_post ,
. resume_pre = & zylonite_resume_pre ,
2008-11-21 17:01:41 +03:00
. dai_link = zylonite_dai ,
. num_links = ARRAY_SIZE ( zylonite_dai ) ,
} ;
static struct platform_device * zylonite_snd_ac97_device ;
static int __init zylonite_init ( void )
{
int ret ;
zylonite_snd_ac97_device = platform_device_alloc ( " soc-audio " , - 1 ) ;
if ( ! zylonite_snd_ac97_device )
return - ENOMEM ;
2010-03-17 23:15:21 +03:00
platform_set_drvdata ( zylonite_snd_ac97_device , & zylonite ) ;
2008-11-21 17:01:41 +03:00
ret = platform_device_add ( zylonite_snd_ac97_device ) ;
if ( ret ! = 0 )
platform_device_put ( zylonite_snd_ac97_device ) ;
return ret ;
}
static void __exit zylonite_exit ( void )
{
platform_device_unregister ( zylonite_snd_ac97_device ) ;
}
module_init ( zylonite_init ) ;
module_exit ( zylonite_exit ) ;
MODULE_AUTHOR ( " Mark Brown <broonie@opensource.wolfsonmicro.com> " ) ;
MODULE_DESCRIPTION ( " ALSA SoC WM9713 Zylonite " ) ;
MODULE_LICENSE ( " GPL " ) ;