2009-11-27 15:47:10 +03:00
/*
* raumfeld_audio . c - - SoC audio for Raumfeld audio devices
*
* Copyright ( c ) 2009 Daniel Mack < daniel @ caiaq . de >
*
* based on code from :
*
* Wolfson Microelectronics PLC .
* Openedhand Ltd .
* Liam Girdwood < lrg @ slimlogic . co . uk >
* Richard Purdie < richard @ openedhand . 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/i2c.h>
# include <linux/delay.h>
# include <linux/gpio.h>
# include <sound/pcm.h>
# include <sound/soc.h>
# include <asm/mach-types.h>
# include "pxa-ssp.h"
# define GPIO_SPDIF_RESET (38)
# define GPIO_MCLK_RESET (111)
# define GPIO_CODEC_RESET (120)
static struct i2c_client * max9486_client ;
static struct i2c_board_info max9486_hwmon_info = {
I2C_BOARD_INFO ( " max9485 " , 0x63 ) ,
} ;
# define MAX9485_MCLK_FREQ_112896 0x22
2010-01-15 19:36:49 +03:00
# define MAX9485_MCLK_FREQ_122880 0x23
# define MAX9485_MCLK_FREQ_225792 0x32
# define MAX9485_MCLK_FREQ_245760 0x33
2009-11-27 15:47:10 +03:00
static void set_max9485_clk ( char clk )
{
i2c_master_send ( max9486_client , & clk , 1 ) ;
}
static void raumfeld_enable_audio ( bool en )
{
if ( en ) {
gpio_set_value ( GPIO_MCLK_RESET , 1 ) ;
/* wait some time to let the clocks become stable */
msleep ( 100 ) ;
gpio_set_value ( GPIO_SPDIF_RESET , 1 ) ;
gpio_set_value ( GPIO_CODEC_RESET , 1 ) ;
} else {
gpio_set_value ( GPIO_MCLK_RESET , 0 ) ;
gpio_set_value ( GPIO_SPDIF_RESET , 0 ) ;
gpio_set_value ( GPIO_CODEC_RESET , 0 ) ;
}
}
/* CS4270 */
static int raumfeld_cs4270_startup ( struct snd_pcm_substream * substream )
{
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 ;
2009-11-27 15:47:10 +03:00
2010-01-15 19:36:49 +03:00
/* set freq to 0 to enable all possible codec sample rates */
return snd_soc_dai_set_sysclk ( codec_dai , 0 , 0 , 0 ) ;
}
2009-11-27 15:47:10 +03:00
2010-01-15 19:36:49 +03:00
static void raumfeld_cs4270_shutdown ( struct snd_pcm_substream * substream )
{
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 ;
2010-01-15 19:36:49 +03:00
/* set freq to 0 to enable all possible codec sample rates */
snd_soc_dai_set_sysclk ( codec_dai , 0 , 0 , 0 ) ;
2009-11-27 15:47:10 +03:00
}
static int raumfeld_cs4270_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 ;
2015-01-01 19:16:17 +03:00
unsigned int clk = 0 ;
2009-11-27 15:47:10 +03:00
int ret = 0 ;
switch ( params_rate ( params ) ) {
2010-01-15 19:36:49 +03:00
case 44100 :
set_max9485_clk ( MAX9485_MCLK_FREQ_112896 ) ;
clk = 11289600 ;
break ;
2009-11-27 15:47:10 +03:00
case 48000 :
set_max9485_clk ( MAX9485_MCLK_FREQ_122880 ) ;
clk = 12288000 ;
break ;
case 88200 :
2010-01-15 19:36:49 +03:00
set_max9485_clk ( MAX9485_MCLK_FREQ_225792 ) ;
clk = 22579200 ;
2009-11-27 15:47:10 +03:00
break ;
2010-01-15 19:36:49 +03:00
case 96000 :
set_max9485_clk ( MAX9485_MCLK_FREQ_245760 ) ;
clk = 24576000 ;
break ;
default :
return - EINVAL ;
2009-11-27 15:47:10 +03:00
}
ret = snd_soc_dai_set_sysclk ( codec_dai , 0 , clk , 0 ) ;
if ( ret < 0 )
return ret ;
/* setup the CPU DAI */
2009-11-30 16:06:37 +03:00
ret = snd_soc_dai_set_pll ( cpu_dai , 0 , 0 , 0 , clk ) ;
2009-11-27 15:47:10 +03:00
if ( ret < 0 )
return ret ;
ret = snd_soc_dai_set_clkdiv ( cpu_dai , PXA_SSP_DIV_SCR , 4 ) ;
if ( ret < 0 )
return ret ;
2010-01-15 19:36:49 +03:00
ret = snd_soc_dai_set_sysclk ( cpu_dai , PXA_SSP_CLK_EXT , clk , 1 ) ;
2009-11-27 15:47:10 +03:00
if ( ret < 0 )
return ret ;
return 0 ;
}
static struct snd_soc_ops raumfeld_cs4270_ops = {
. startup = raumfeld_cs4270_startup ,
2010-01-15 19:36:49 +03:00
. shutdown = raumfeld_cs4270_shutdown ,
2009-11-27 15:47:10 +03:00
. hw_params = raumfeld_cs4270_hw_params ,
} ;
2011-05-24 16:10:32 +04:00
static int raumfeld_analog_suspend ( struct snd_soc_card * card )
2009-11-27 15:47:10 +03:00
{
raumfeld_enable_audio ( false ) ;
return 0 ;
}
2011-05-24 16:10:32 +04:00
static int raumfeld_analog_resume ( struct snd_soc_card * card )
2009-11-27 15:47:10 +03:00
{
raumfeld_enable_audio ( true ) ;
return 0 ;
}
/* AK4104 */
static int raumfeld_ak4104_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 * cpu_dai = rtd - > cpu_dai ;
2015-01-01 19:16:17 +03:00
int ret = 0 , clk = 0 ;
2009-11-27 15:47:10 +03:00
switch ( params_rate ( params ) ) {
2010-01-15 19:36:49 +03:00
case 44100 :
set_max9485_clk ( MAX9485_MCLK_FREQ_112896 ) ;
clk = 11289600 ;
break ;
2009-11-27 15:47:10 +03:00
case 48000 :
set_max9485_clk ( MAX9485_MCLK_FREQ_122880 ) ;
clk = 12288000 ;
break ;
case 88200 :
2010-01-15 19:36:49 +03:00
set_max9485_clk ( MAX9485_MCLK_FREQ_225792 ) ;
clk = 22579200 ;
break ;
case 96000 :
set_max9485_clk ( MAX9485_MCLK_FREQ_245760 ) ;
clk = 24576000 ;
2009-11-27 15:47:10 +03:00
break ;
2010-01-15 19:36:49 +03:00
default :
return - EINVAL ;
2009-11-27 15:47:10 +03:00
}
/* setup the CPU DAI */
2009-11-30 16:06:37 +03:00
ret = snd_soc_dai_set_pll ( cpu_dai , 0 , 0 , 0 , clk ) ;
2009-11-27 15:47:10 +03:00
if ( ret < 0 )
return ret ;
ret = snd_soc_dai_set_clkdiv ( cpu_dai , PXA_SSP_DIV_SCR , 4 ) ;
if ( ret < 0 )
return ret ;
2010-01-15 19:36:49 +03:00
ret = snd_soc_dai_set_sysclk ( cpu_dai , PXA_SSP_CLK_EXT , clk , 1 ) ;
2009-11-27 15:47:10 +03:00
if ( ret < 0 )
return ret ;
return 0 ;
}
static struct snd_soc_ops raumfeld_ak4104_ops = {
. hw_params = raumfeld_ak4104_hw_params ,
} ;
2011-05-24 16:10:32 +04:00
# define DAI_LINK_CS4270 \
{ \
. name = " CS4270 " , \
. stream_name = " CS4270 " , \
. cpu_dai_name = " pxa-ssp-dai.0 " , \
. platform_name = " pxa-pcm-audio " , \
. codec_dai_name = " cs4270-hifi " , \
2012-02-24 18:09:38 +04:00
. codec_name = " cs4270.0-0048 " , \
2015-01-01 19:16:17 +03:00
. dai_fmt = SND_SOC_DAIFMT_I2S | \
SND_SOC_DAIFMT_NB_NF | \
SND_SOC_DAIFMT_CBS_CFS , \
2011-05-24 16:10:32 +04:00
. ops = & raumfeld_cs4270_ops , \
}
# define DAI_LINK_AK4104 \
{ \
. name = " ak4104 " , \
. stream_name = " Playback " , \
. cpu_dai_name = " pxa-ssp-dai.1 " , \
. codec_dai_name = " ak4104-hifi " , \
. platform_name = " pxa-pcm-audio " , \
2015-01-01 19:16:17 +03:00
. dai_fmt = SND_SOC_DAIFMT_I2S | \
SND_SOC_DAIFMT_NB_NF | \
SND_SOC_DAIFMT_CBS_CFS , \
2011-05-24 16:10:32 +04:00
. ops = & raumfeld_ak4104_ops , \
. codec_name = " spi0.0 " , \
}
static struct snd_soc_dai_link snd_soc_raumfeld_connector_dai [ ] =
2010-03-17 23:15:21 +03:00
{
2011-05-24 16:10:32 +04:00
DAI_LINK_CS4270 ,
DAI_LINK_AK4104 ,
} ;
static struct snd_soc_dai_link snd_soc_raumfeld_speaker_dai [ ] =
2010-03-17 23:15:21 +03:00
{
2011-05-24 16:10:32 +04:00
DAI_LINK_CS4270 ,
} ;
static struct snd_soc_card snd_soc_raumfeld_connector = {
. name = " Raumfeld Connector " ,
2011-12-22 05:44:43 +04:00
. owner = THIS_MODULE ,
2011-05-24 16:10:32 +04:00
. dai_link = snd_soc_raumfeld_connector_dai ,
. num_links = ARRAY_SIZE ( snd_soc_raumfeld_connector_dai ) ,
. suspend_post = raumfeld_analog_suspend ,
. resume_pre = raumfeld_analog_resume ,
} ;
static struct snd_soc_card snd_soc_raumfeld_speaker = {
. name = " Raumfeld Speaker " ,
2011-12-22 05:44:43 +04:00
. owner = THIS_MODULE ,
2011-05-24 16:10:32 +04:00
. dai_link = snd_soc_raumfeld_speaker_dai ,
. num_links = ARRAY_SIZE ( snd_soc_raumfeld_speaker_dai ) ,
. suspend_post = raumfeld_analog_suspend ,
. resume_pre = raumfeld_analog_resume ,
2009-11-27 15:47:10 +03:00
} ;
2010-03-17 23:15:21 +03:00
static struct platform_device * raumfeld_audio_device ;
2009-11-27 15:47:10 +03:00
static int __init raumfeld_audio_init ( void )
{
int ret ;
if ( ! machine_is_raumfeld_speaker ( ) & &
! machine_is_raumfeld_connector ( ) )
return 0 ;
max9486_client = i2c_new_device ( i2c_get_adapter ( 0 ) ,
& max9486_hwmon_info ) ;
if ( ! max9486_client )
return - ENOMEM ;
set_max9485_clk ( MAX9485_MCLK_FREQ_122880 ) ;
2011-05-24 16:10:32 +04:00
/* Register analog device */
2010-03-17 23:15:21 +03:00
raumfeld_audio_device = platform_device_alloc ( " soc-audio " , 0 ) ;
if ( ! raumfeld_audio_device )
2009-11-27 15:47:10 +03:00
return - ENOMEM ;
if ( machine_is_raumfeld_speaker ( ) )
2011-05-24 16:10:32 +04:00
platform_set_drvdata ( raumfeld_audio_device ,
& snd_soc_raumfeld_speaker ) ;
if ( machine_is_raumfeld_connector ( ) )
platform_set_drvdata ( raumfeld_audio_device ,
& snd_soc_raumfeld_connector ) ;
ret = platform_device_add ( raumfeld_audio_device ) ;
2011-09-02 17:47:41 +04:00
if ( ret < 0 ) {
platform_device_put ( raumfeld_audio_device ) ;
2009-11-27 15:47:10 +03:00
return ret ;
2011-09-02 17:47:41 +04:00
}
2009-11-27 15:47:10 +03:00
raumfeld_enable_audio ( true ) ;
2011-05-24 16:10:32 +04:00
return 0 ;
2009-11-27 15:47:10 +03:00
}
static void __exit raumfeld_audio_exit ( void )
{
raumfeld_enable_audio ( false ) ;
2010-03-17 23:15:21 +03:00
platform_device_unregister ( raumfeld_audio_device ) ;
2009-11-27 15:47:10 +03:00
i2c_unregister_device ( max9486_client ) ;
gpio_free ( GPIO_MCLK_RESET ) ;
gpio_free ( GPIO_CODEC_RESET ) ;
gpio_free ( GPIO_SPDIF_RESET ) ;
}
module_init ( raumfeld_audio_init ) ;
module_exit ( raumfeld_audio_exit ) ;
/* Module information */
MODULE_AUTHOR ( " Daniel Mack <daniel@caiaq.de> " ) ;
MODULE_DESCRIPTION ( " Raumfeld audio SoC " ) ;
MODULE_LICENSE ( " GPL " ) ;