2006-10-06 18:41:10 +02:00
/*
2006-11-24 15:49:39 +01:00
* eti_b1_wm8731 - - SoC audio for AT91RM9200 - based Endrelia ETI_B1 board .
2006-10-06 18:41:10 +02:00
*
* Author : Frank Mandarino < fmandarino @ endrelia . com >
* Endrelia Technologies Inc .
* Created : Mar 29 , 2006
*
* Based on corgi . c by :
*
* Copyright 2005 Wolfson Microelectronics PLC .
* Copyright 2005 Openedhand Ltd .
*
* Authors : Liam Girdwood < liam . girdwood @ wolfsonmicro . com >
* 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/moduleparam.h>
# include <linux/version.h>
# include <linux/kernel.h>
# include <linux/clk.h>
# include <linux/timer.h>
# include <linux/interrupt.h>
# include <linux/platform_device.h>
# include <sound/driver.h>
# include <sound/core.h>
# include <sound/pcm.h>
# include <sound/soc.h>
# include <sound/soc-dapm.h>
# include <asm/arch/hardware.h>
2006-11-24 15:49:39 +01:00
# include <asm/arch/at91_pio.h>
# include <asm/arch/gpio.h>
2006-10-06 18:41:10 +02:00
# include "../codecs/wm8731.h"
2006-11-24 15:49:39 +01:00
# include "at91-pcm.h"
2007-04-16 17:19:42 +02:00
# include "at91-ssc.h"
2006-10-06 18:41:10 +02:00
#if 0
2007-02-02 17:19:58 +01:00
# define DBG(x...) printk(KERN_INFO "eti_b1_wm8731: " x)
2006-10-06 18:41:10 +02:00
# else
# define DBG(x...)
# endif
2006-11-24 15:49:39 +01:00
# define AT91_PIO_TF1 (1 << (AT91_PIN_PB6 - PIN_BASE) % 32)
# define AT91_PIO_TK1 (1 << (AT91_PIN_PB7 - PIN_BASE) % 32)
# define AT91_PIO_TD1 (1 << (AT91_PIN_PB8 - PIN_BASE) % 32)
# define AT91_PIO_RD1 (1 << (AT91_PIN_PB9 - PIN_BASE) % 32)
# define AT91_PIO_RK1 (1 << (AT91_PIN_PB10 - PIN_BASE) % 32)
# define AT91_PIO_RF1 (1 << (AT91_PIN_PB11 - PIN_BASE) % 32)
2006-10-06 18:41:10 +02:00
static struct clk * pck1_clk ;
static struct clk * pllb_clk ;
2007-02-02 17:19:58 +01:00
2006-11-24 15:49:39 +01:00
static int eti_b1_startup ( struct snd_pcm_substream * substream )
2006-10-06 18:41:10 +02:00
{
2007-02-02 17:19:58 +01:00
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct snd_soc_codec_dai * codec_dai = rtd - > dai - > codec_dai ;
struct snd_soc_cpu_dai * cpu_dai = rtd - > dai - > cpu_dai ;
int ret ;
/* cpu clock is the AT91 master clock sent to the SSC */
ret = cpu_dai - > dai_ops . set_sysclk ( cpu_dai , AT91_SYSCLK_MCK ,
60000000 , SND_SOC_CLOCK_IN ) ;
if ( ret < 0 )
return ret ;
/* codec system clock is supplied by PCK1, set to 12MHz */
ret = codec_dai - > dai_ops . set_sysclk ( codec_dai , WM8731_SYSCLK ,
12000000 , SND_SOC_CLOCK_IN ) ;
if ( ret < 0 )
return ret ;
2006-10-06 18:41:10 +02:00
/* Start PCK1 clock. */
clk_enable ( pck1_clk ) ;
DBG ( " pck1 started \n " ) ;
return 0 ;
}
2006-11-24 15:49:39 +01:00
static void eti_b1_shutdown ( struct snd_pcm_substream * substream )
2006-10-06 18:41:10 +02:00
{
/* Stop PCK1 clock. */
clk_disable ( pck1_clk ) ;
DBG ( " pck1 stopped \n " ) ;
}
2007-02-02 17:19:58 +01:00
static int eti_b1_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct snd_soc_codec_dai * codec_dai = rtd - > dai - > codec_dai ;
struct snd_soc_cpu_dai * cpu_dai = rtd - > dai - > cpu_dai ;
int ret ;
# ifdef CONFIG_SND_AT91_SOC_ETI_SLAVE
unsigned int rate ;
int cmr_div , period ;
/* set codec DAI configuration */
ret = codec_dai - > dai_ops . set_fmt ( codec_dai , SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS ) ;
if ( ret < 0 )
return ret ;
/* set cpu DAI configuration */
ret = cpu_dai - > dai_ops . set_fmt ( cpu_dai , SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS ) ;
if ( ret < 0 )
return ret ;
/*
* The SSC clock dividers depend on the sample rate . The CMR . DIV
* field divides the system master clock MCK to drive the SSC TK
* signal which provides the codec BCLK . The TCMR . PERIOD and
* RCMR . PERIOD fields further divide the BCLK signal to drive
* the SSC TF and RF signals which provide the codec DACLRC and
* ADCLRC clocks .
*
* The dividers were determined through trial and error , where a
* CMR . DIV value is chosen such that the resulting BCLK value is
* divisible , or almost divisible , by ( 2 * sample rate ) , and then
* the TCMR . PERIOD or RCMR . PERIOD is BCLK / ( 2 * sample rate ) - 1.
*/
rate = params_rate ( params ) ;
switch ( rate ) {
case 8000 :
cmr_div = 25 ; /* BCLK = 60MHz/(2*25) = 1.2MHz */
period = 74 ; /* LRC = BCLK/(2*(74+1)) = 8000Hz */
break ;
case 32000 :
cmr_div = 7 ; /* BCLK = 60MHz/(2*7) ~= 4.28571428MHz */
period = 66 ; /* LRC = BCLK/(2*(66+1)) = 31982.942Hz */
break ;
case 48000 :
cmr_div = 13 ; /* BCLK = 60MHz/(2*13) ~= 2.3076923MHz */
period = 23 ; /* LRC = BCLK/(2*(23+1)) = 48076.923Hz */
break ;
default :
printk ( KERN_WARNING " unsupported rate %d on ETI-B1 board \n " , rate ) ;
return - EINVAL ;
}
/* set the MCK divider for BCLK */
ret = cpu_dai - > dai_ops . set_clkdiv ( cpu_dai , AT91SSC_CMR_DIV , cmr_div ) ;
if ( ret < 0 )
return ret ;
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
/* set the BCLK divider for DACLRC */
ret = cpu_dai - > dai_ops . set_clkdiv ( cpu_dai ,
AT91SSC_TCMR_PERIOD , period ) ;
} else {
/* set the BCLK divider for ADCLRC */
ret = cpu_dai - > dai_ops . set_clkdiv ( cpu_dai ,
AT91SSC_RCMR_PERIOD , period ) ;
}
if ( ret < 0 )
return ret ;
# else /* CONFIG_SND_AT91_SOC_ETI_SLAVE */
/*
* Codec in Master Mode .
*/
/* set codec DAI configuration */
ret = codec_dai - > dai_ops . set_fmt ( codec_dai , SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM ) ;
if ( ret < 0 )
return ret ;
/* set cpu DAI configuration */
ret = cpu_dai - > dai_ops . set_fmt ( cpu_dai , SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM ) ;
if ( ret < 0 )
return ret ;
# endif /* CONFIG_SND_AT91_SOC_ETI_SLAVE */
return 0 ;
}
2006-10-06 18:41:10 +02:00
static struct snd_soc_ops eti_b1_ops = {
. startup = eti_b1_startup ,
2007-02-02 17:19:58 +01:00
. hw_params = eti_b1_hw_params ,
2006-10-06 18:41:10 +02:00
. shutdown = eti_b1_shutdown ,
} ;
static const struct snd_soc_dapm_widget eti_b1_dapm_widgets [ ] = {
SND_SOC_DAPM_MIC ( " Int Mic " , NULL ) ,
SND_SOC_DAPM_SPK ( " Ext Spk " , NULL ) ,
} ;
static const char * intercon [ ] [ 3 ] = {
/* speaker connected to LHPOUT */
{ " Ext Spk " , NULL , " LHPOUT " } ,
/* mic is connected to Mic Jack, with WM8731 Mic Bias */
{ " MICIN " , NULL , " Mic Bias " } ,
{ " Mic Bias " , NULL , " Int Mic " } ,
/* terminator */
{ NULL , NULL , NULL } ,
} ;
/*
* Logic for a wm8731 as connected on a Endrelia ETI - B1 board .
*/
static int eti_b1_wm8731_init ( struct snd_soc_codec * codec )
{
int i ;
DBG ( " eti_b1_wm8731_init() called \n " ) ;
/* Add specific widgets */
for ( i = 0 ; i < ARRAY_SIZE ( eti_b1_dapm_widgets ) ; i + + ) {
snd_soc_dapm_new_control ( codec , & eti_b1_dapm_widgets [ i ] ) ;
}
/* Set up specific 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 ] ) ;
}
/* not connected */
snd_soc_dapm_set_endpoint ( codec , " RLINEIN " , 0 ) ;
snd_soc_dapm_set_endpoint ( codec , " LLINEIN " , 0 ) ;
/* always connected */
snd_soc_dapm_set_endpoint ( codec , " Int Mic " , 1 ) ;
snd_soc_dapm_set_endpoint ( codec , " Ext Spk " , 1 ) ;
snd_soc_dapm_sync_endpoints ( codec ) ;
return 0 ;
}
static struct snd_soc_dai_link eti_b1_dai = {
. name = " WM8731 " ,
2007-04-16 17:19:42 +02:00
. stream_name = " WM8731 PCM " ,
. cpu_dai = & at91_ssc_dai [ 1 ] ,
2006-10-06 18:41:10 +02:00
. codec_dai = & wm8731_dai ,
. init = eti_b1_wm8731_init ,
2007-02-02 17:19:58 +01:00
. ops = & eti_b1_ops ,
2006-10-06 18:41:10 +02:00
} ;
static struct snd_soc_machine snd_soc_machine_eti_b1 = {
2007-04-16 17:19:42 +02:00
. name = " ETI_B1_WM8731 " ,
2006-10-06 18:41:10 +02:00
. dai_link = & eti_b1_dai ,
. num_links = 1 ,
} ;
static struct wm8731_setup_data eti_b1_wm8731_setup = {
. i2c_address = 0x1a ,
} ;
static struct snd_soc_device eti_b1_snd_devdata = {
. machine = & snd_soc_machine_eti_b1 ,
2006-11-24 15:49:39 +01:00
. platform = & at91_soc_platform ,
2006-10-06 18:41:10 +02:00
. codec_dev = & soc_codec_dev_wm8731 ,
. codec_data = & eti_b1_wm8731_setup ,
} ;
static struct platform_device * eti_b1_snd_device ;
static int __init eti_b1_init ( void )
{
int ret ;
u32 ssc_pio_lines ;
2006-11-24 15:49:39 +01:00
struct at91_ssc_periph * ssc = eti_b1_dai . cpu_dai - > private_data ;
if ( ! request_mem_region ( AT91RM9200_BASE_SSC1 , SZ_16K , " soc-audio " ) ) {
DBG ( " SSC1 memory region is busy \n " ) ;
return - EBUSY ;
}
ssc - > base = ioremap ( AT91RM9200_BASE_SSC1 , SZ_16K ) ;
if ( ! ssc - > base ) {
DBG ( " SSC1 memory ioremap failed \n " ) ;
ret = - ENOMEM ;
goto fail_release_mem ;
}
ssc - > pid = AT91RM9200_ID_SSC1 ;
2006-10-06 18:41:10 +02:00
eti_b1_snd_device = platform_device_alloc ( " soc-audio " , - 1 ) ;
2006-11-24 15:49:39 +01:00
if ( ! eti_b1_snd_device ) {
DBG ( " platform device allocation failed \n " ) ;
ret = - ENOMEM ;
goto fail_io_unmap ;
}
2006-10-06 18:41:10 +02:00
platform_set_drvdata ( eti_b1_snd_device , & eti_b1_snd_devdata ) ;
eti_b1_snd_devdata . dev = & eti_b1_snd_device - > dev ;
ret = platform_device_add ( eti_b1_snd_device ) ;
if ( ret ) {
2006-11-24 15:49:39 +01:00
DBG ( " platform device add failed \n " ) ;
2006-10-06 18:41:10 +02:00
platform_device_put ( eti_b1_snd_device ) ;
2006-11-24 15:49:39 +01:00
goto fail_io_unmap ;
2006-10-06 18:41:10 +02:00
}
2006-11-24 15:49:39 +01:00
ssc_pio_lines = AT91_PIO_TF1 | AT91_PIO_TK1 | AT91_PIO_TD1
2007-02-02 17:19:58 +01:00
| AT91_PIO_RD1 /* | AT91_PIO_RK1 */ | AT91_PIO_RF1 ;
2006-10-06 18:41:10 +02:00
/* Reset all PIO registers and assign lines to peripheral A */
at91_sys_write ( AT91_PIOB + PIO_PDR , ssc_pio_lines ) ;
at91_sys_write ( AT91_PIOB + PIO_ODR , ssc_pio_lines ) ;
at91_sys_write ( AT91_PIOB + PIO_IFDR , ssc_pio_lines ) ;
at91_sys_write ( AT91_PIOB + PIO_CODR , ssc_pio_lines ) ;
at91_sys_write ( AT91_PIOB + PIO_IDR , ssc_pio_lines ) ;
at91_sys_write ( AT91_PIOB + PIO_MDDR , ssc_pio_lines ) ;
at91_sys_write ( AT91_PIOB + PIO_PUDR , ssc_pio_lines ) ;
at91_sys_write ( AT91_PIOB + PIO_ASR , ssc_pio_lines ) ;
at91_sys_write ( AT91_PIOB + PIO_OWDR , ssc_pio_lines ) ;
/*
* Set PCK1 parent to PLLB and its rate to 12 Mhz .
*/
pllb_clk = clk_get ( NULL , " pllb " ) ;
pck1_clk = clk_get ( NULL , " pck1 " ) ;
clk_set_parent ( pck1_clk , pllb_clk ) ;
clk_set_rate ( pck1_clk , 12000000 ) ;
DBG ( " MCLK rate %luHz \n " , clk_get_rate ( pck1_clk ) ) ;
/* assign the GPIO pin to PCK1 */
at91_set_B_periph ( AT91_PIN_PA24 , 0 ) ;
2007-02-02 17:19:58 +01:00
# ifdef CONFIG_SND_AT91_SOC_ETI_SLAVE
printk ( KERN_INFO " eti_b1_wm8731: Codec in Slave Mode \n " ) ;
# else
printk ( KERN_INFO " eti_b1_wm8731: Codec in Master Mode \n " ) ;
# endif
2006-10-06 18:41:10 +02:00
return ret ;
2006-11-24 15:49:39 +01:00
fail_io_unmap :
iounmap ( ssc - > base ) ;
fail_release_mem :
release_mem_region ( AT91RM9200_BASE_SSC1 , SZ_16K ) ;
return ret ;
2006-10-06 18:41:10 +02:00
}
static void __exit eti_b1_exit ( void )
{
2006-11-24 15:49:39 +01:00
struct at91_ssc_periph * ssc = eti_b1_dai . cpu_dai - > private_data ;
2006-10-06 18:41:10 +02:00
clk_put ( pck1_clk ) ;
clk_put ( pllb_clk ) ;
platform_device_unregister ( eti_b1_snd_device ) ;
2006-11-24 15:49:39 +01:00
iounmap ( ssc - > base ) ;
release_mem_region ( AT91RM9200_BASE_SSC1 , SZ_16K ) ;
2006-10-06 18:41:10 +02:00
}
module_init ( eti_b1_init ) ;
module_exit ( eti_b1_exit ) ;
/* Module information */
MODULE_AUTHOR ( " Frank Mandarino <fmandarino@endrelia.com> " ) ;
MODULE_DESCRIPTION ( " ALSA SoC ETI-B1-WM8731 " ) ;
MODULE_LICENSE ( " GPL " ) ;