2009-11-25 18:41:04 +03:00
/*
* imx - ssi . c - - ALSA Soc Audio Layer
*
* Copyright 2009 Sascha Hauer < s . hauer @ pengutronix . de >
*
* This code is based on code copyrighted by Freescale ,
* Liam Girdwood , Javier Martin and probably others .
*
* 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 .
*
*
* The i . MX SSI core has some nasty limitations in AC97 mode . While most
* sane processor vendors have a FIFO per AC97 slot , the i . MX has only
* one FIFO which combines all valid receive slots . We cannot even select
* which slots we want to receive . The WM9712 with which this driver
* was developped with always sends GPIO status data in slot 12 which
* we receive in our ( PCM - ) data stream . The only chance we have is to
* manually skip this data in the FIQ handler . With sampling rates different
* from 48000 Hz not every frame has valid receive data , so the ratio
* between pcm data and GPIO status data changes . Our FIQ handler is not
* able to handle this , hence this driver only works with 48000 Hz sampling
* rate .
* Reading and writing AC97 registers is another challange . The core
* provides us status bits when the read register is updated with * another *
* value . When we read the same register two times ( and the register still
* contains the same value ) these status bits are not set . We work
* around this by not polling these bits but only wait a fixed delay .
*
*/
# include <linux/clk.h>
# include <linux/delay.h>
# include <linux/device.h>
# include <linux/dma-mapping.h>
# include <linux/init.h>
# include <linux/interrupt.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <sound/core.h>
# include <sound/initval.h>
# include <sound/pcm.h>
# include <sound/pcm_params.h>
# include <sound/soc.h>
# include <mach/ssi.h>
# include <mach/hardware.h>
# include "imx-ssi.h"
# define SSI_SACNT_DEFAULT (SSI_SACNT_AC97EN | SSI_SACNT_FV)
/*
* SSI Network Mode or TDM slots configuration .
* Should only be called when port is inactive ( i . e . SSIEN = 0 ) .
*/
static int imx_ssi_set_dai_tdm_slot ( struct snd_soc_dai * cpu_dai ,
unsigned int tx_mask , unsigned int rx_mask , int slots , int slot_width )
{
2010-01-06 20:56:52 +03:00
struct imx_ssi * ssi = cpu_dai - > private_data ;
2009-11-25 18:41:04 +03:00
u32 sccr ;
sccr = readl ( ssi - > base + SSI_STCCR ) ;
sccr & = ~ SSI_STCCR_DC_MASK ;
sccr | = SSI_STCCR_DC ( slots - 1 ) ;
writel ( sccr , ssi - > base + SSI_STCCR ) ;
sccr = readl ( ssi - > base + SSI_SRCCR ) ;
sccr & = ~ SSI_STCCR_DC_MASK ;
sccr | = SSI_STCCR_DC ( slots - 1 ) ;
writel ( sccr , ssi - > base + SSI_SRCCR ) ;
writel ( tx_mask , ssi - > base + SSI_STMSK ) ;
writel ( rx_mask , ssi - > base + SSI_SRMSK ) ;
return 0 ;
}
/*
* SSI DAI format configuration .
* Should only be called when port is inactive ( i . e . SSIEN = 0 ) .
* Note : We don ' t use the I2S modes but instead manually configure the
* SSI for I2S because the I2S mode is only a register preset .
*/
static int imx_ssi_set_dai_fmt ( struct snd_soc_dai * cpu_dai , unsigned int fmt )
{
2010-01-06 20:56:52 +03:00
struct imx_ssi * ssi = cpu_dai - > private_data ;
2009-11-25 18:41:04 +03:00
u32 strcr = 0 , scr ;
scr = readl ( ssi - > base + SSI_SCR ) & ~ ( SSI_SCR_SYN | SSI_SCR_NET ) ;
/* DAI mode */
switch ( fmt & SND_SOC_DAIFMT_FORMAT_MASK ) {
case SND_SOC_DAIFMT_I2S :
/* data on rising edge of bclk, frame low 1clk before data */
strcr | = SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0 ;
scr | = SSI_SCR_NET ;
break ;
case SND_SOC_DAIFMT_LEFT_J :
/* data on rising edge of bclk, frame high with data */
strcr | = SSI_STCR_TXBIT0 ;
break ;
case SND_SOC_DAIFMT_DSP_B :
/* data on rising edge of bclk, frame high with data */
strcr | = SSI_STCR_TFSL ;
break ;
case SND_SOC_DAIFMT_DSP_A :
/* data on rising edge of bclk, frame high 1clk before data */
strcr | = SSI_STCR_TFSL | SSI_STCR_TEFS ;
break ;
}
/* DAI clock inversion */
switch ( fmt & SND_SOC_DAIFMT_INV_MASK ) {
case SND_SOC_DAIFMT_IB_IF :
strcr | = SSI_STCR_TFSI ;
strcr & = ~ SSI_STCR_TSCKP ;
break ;
case SND_SOC_DAIFMT_IB_NF :
strcr & = ~ ( SSI_STCR_TSCKP | SSI_STCR_TFSI ) ;
break ;
case SND_SOC_DAIFMT_NB_IF :
strcr | = SSI_STCR_TFSI | SSI_STCR_TSCKP ;
break ;
case SND_SOC_DAIFMT_NB_NF :
strcr & = ~ SSI_STCR_TFSI ;
strcr | = SSI_STCR_TSCKP ;
break ;
}
/* DAI clock master masks */
switch ( fmt & SND_SOC_DAIFMT_MASTER_MASK ) {
2010-01-11 19:56:19 +03:00
case SND_SOC_DAIFMT_CBM_CFM :
2009-11-25 18:41:04 +03:00
break ;
2010-01-11 19:56:19 +03:00
default :
/* Master mode not implemented, needs handling of clocks. */
return - EINVAL ;
2009-11-25 18:41:04 +03:00
}
strcr | = SSI_STCR_TFEN0 ;
writel ( strcr , ssi - > base + SSI_STCR ) ;
writel ( strcr , ssi - > base + SSI_SRCR ) ;
writel ( scr , ssi - > base + SSI_SCR ) ;
return 0 ;
}
/*
* SSI system clock configuration .
* Should only be called when port is inactive ( i . e . SSIEN = 0 ) .
*/
static int imx_ssi_set_dai_sysclk ( struct snd_soc_dai * cpu_dai ,
int clk_id , unsigned int freq , int dir )
{
2010-01-06 20:56:52 +03:00
struct imx_ssi * ssi = cpu_dai - > private_data ;
2009-11-25 18:41:04 +03:00
u32 scr ;
scr = readl ( ssi - > base + SSI_SCR ) ;
switch ( clk_id ) {
case IMX_SSP_SYS_CLK :
if ( dir = = SND_SOC_CLOCK_OUT )
scr | = SSI_SCR_SYS_CLK_EN ;
else
scr & = ~ SSI_SCR_SYS_CLK_EN ;
break ;
default :
return - EINVAL ;
}
writel ( scr , ssi - > base + SSI_SCR ) ;
return 0 ;
}
/*
* SSI Clock dividers
* Should only be called when port is inactive ( i . e . SSIEN = 0 ) .
*/
static int imx_ssi_set_dai_clkdiv ( struct snd_soc_dai * cpu_dai ,
int div_id , int div )
{
2010-01-06 20:56:52 +03:00
struct imx_ssi * ssi = cpu_dai - > private_data ;
2009-11-25 18:41:04 +03:00
u32 stccr , srccr ;
stccr = readl ( ssi - > base + SSI_STCCR ) ;
srccr = readl ( ssi - > base + SSI_SRCCR ) ;
switch ( div_id ) {
case IMX_SSI_TX_DIV_2 :
stccr & = ~ SSI_STCCR_DIV2 ;
stccr | = div ;
break ;
case IMX_SSI_TX_DIV_PSR :
stccr & = ~ SSI_STCCR_PSR ;
stccr | = div ;
break ;
case IMX_SSI_TX_DIV_PM :
stccr & = ~ 0xff ;
stccr | = SSI_STCCR_PM ( div ) ;
break ;
case IMX_SSI_RX_DIV_2 :
stccr & = ~ SSI_STCCR_DIV2 ;
stccr | = div ;
break ;
case IMX_SSI_RX_DIV_PSR :
stccr & = ~ SSI_STCCR_PSR ;
stccr | = div ;
break ;
case IMX_SSI_RX_DIV_PM :
stccr & = ~ 0xff ;
stccr | = SSI_STCCR_PM ( div ) ;
break ;
default :
return - EINVAL ;
}
writel ( stccr , ssi - > base + SSI_STCCR ) ;
writel ( srccr , ssi - > base + SSI_SRCCR ) ;
return 0 ;
}
/*
* Should only be called when port is inactive ( i . e . SSIEN = 0 ) ,
* although can be called multiple times by upper layers .
*/
static int imx_ssi_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params ,
struct snd_soc_dai * cpu_dai )
{
2010-01-06 20:56:52 +03:00
struct imx_ssi * ssi = cpu_dai - > private_data ;
2010-03-22 12:11:15 +03:00
struct imx_pcm_dma_params * dma_data ;
2009-11-25 18:41:04 +03:00
u32 reg , sccr ;
/* Tx/Rx config */
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
reg = SSI_STCCR ;
2010-03-22 12:11:15 +03:00
dma_data = & ssi - > dma_params_tx ;
2009-11-25 18:41:04 +03:00
} else {
reg = SSI_SRCCR ;
2010-03-22 12:11:15 +03:00
dma_data = & ssi - > dma_params_rx ;
2009-11-25 18:41:04 +03:00
}
2010-03-22 12:11:15 +03:00
snd_soc_dai_set_dma_data ( cpu_dai , substream , dma_data ) ;
2009-11-25 18:41:04 +03:00
sccr = readl ( ssi - > base + reg ) & ~ SSI_STCCR_WL_MASK ;
/* DAI data (word) size */
switch ( params_format ( params ) ) {
case SNDRV_PCM_FORMAT_S16_LE :
sccr | = SSI_SRCCR_WL ( 16 ) ;
break ;
case SNDRV_PCM_FORMAT_S20_3LE :
sccr | = SSI_SRCCR_WL ( 20 ) ;
break ;
case SNDRV_PCM_FORMAT_S24_LE :
sccr | = SSI_SRCCR_WL ( 24 ) ;
break ;
}
writel ( sccr , ssi - > base + reg ) ;
return 0 ;
}
static int imx_ssi_trigger ( struct snd_pcm_substream * substream , int cmd ,
struct snd_soc_dai * dai )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct snd_soc_dai * cpu_dai = rtd - > dai - > cpu_dai ;
2010-01-06 20:56:52 +03:00
struct imx_ssi * ssi = cpu_dai - > private_data ;
2009-11-25 18:41:04 +03:00
unsigned int sier_bits , sier ;
unsigned int scr ;
scr = readl ( ssi - > base + SSI_SCR ) ;
sier = readl ( ssi - > base + SSI_SIER ) ;
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
if ( ssi - > flags & IMX_SSI_DMA )
sier_bits = SSI_SIER_TDMAE ;
else
sier_bits = SSI_SIER_TIE | SSI_SIER_TFE0_EN ;
} else {
if ( ssi - > flags & IMX_SSI_DMA )
sier_bits = SSI_SIER_RDMAE ;
else
sier_bits = SSI_SIER_RIE | SSI_SIER_RFF0_EN ;
}
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
case SNDRV_PCM_TRIGGER_RESUME :
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
scr | = SSI_SCR_TE ;
else
scr | = SSI_SCR_RE ;
sier | = sier_bits ;
if ( + + ssi - > enabled = = 1 )
scr | = SSI_SCR_SSIEN ;
break ;
case SNDRV_PCM_TRIGGER_STOP :
case SNDRV_PCM_TRIGGER_SUSPEND :
case SNDRV_PCM_TRIGGER_PAUSE_PUSH :
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
scr & = ~ SSI_SCR_TE ;
else
scr & = ~ SSI_SCR_RE ;
sier & = ~ sier_bits ;
if ( - - ssi - > enabled = = 0 )
scr & = ~ SSI_SCR_SSIEN ;
break ;
default :
return - EINVAL ;
}
if ( ! ( ssi - > flags & IMX_SSI_USE_AC97 ) )
/* rx/tx are always enabled to access ac97 registers */
writel ( scr , ssi - > base + SSI_SCR ) ;
writel ( sier , ssi - > base + SSI_SIER ) ;
return 0 ;
}
static struct snd_soc_dai_ops imx_ssi_pcm_dai_ops = {
. hw_params = imx_ssi_hw_params ,
. set_fmt = imx_ssi_set_dai_fmt ,
. set_clkdiv = imx_ssi_set_dai_clkdiv ,
. set_sysclk = imx_ssi_set_dai_sysclk ,
. set_tdm_slot = imx_ssi_set_dai_tdm_slot ,
. trigger = imx_ssi_trigger ,
} ;
static struct snd_soc_dai imx_ssi_dai = {
. playback = {
. channels_min = 2 ,
. channels_max = 2 ,
. rates = SNDRV_PCM_RATE_8000_96000 ,
. formats = SNDRV_PCM_FMTBIT_S16_LE ,
} ,
. capture = {
. channels_min = 2 ,
. channels_max = 2 ,
. rates = SNDRV_PCM_RATE_8000_96000 ,
. formats = SNDRV_PCM_FMTBIT_S16_LE ,
} ,
. ops = & imx_ssi_pcm_dai_ops ,
} ;
int snd_imx_pcm_mmap ( struct snd_pcm_substream * substream ,
struct vm_area_struct * vma )
{
struct snd_pcm_runtime * runtime = substream - > runtime ;
int ret ;
ret = dma_mmap_coherent ( NULL , vma , runtime - > dma_area ,
runtime - > dma_addr , runtime - > dma_bytes ) ;
pr_debug ( " %s: ret: %d %p 0x%08x 0x%08x \n " , __func__ , ret ,
runtime - > dma_area ,
runtime - > dma_addr ,
runtime - > dma_bytes ) ;
return ret ;
}
static int imx_pcm_preallocate_dma_buffer ( struct snd_pcm * pcm , int stream )
{
struct snd_pcm_substream * substream = pcm - > streams [ stream ] . substream ;
struct snd_dma_buffer * buf = & substream - > dma_buffer ;
size_t size = IMX_SSI_DMABUF_SIZE ;
buf - > dev . type = SNDRV_DMA_TYPE_DEV ;
buf - > dev . dev = pcm - > card - > dev ;
buf - > private_data = NULL ;
buf - > area = dma_alloc_writecombine ( pcm - > card - > dev , size ,
& buf - > addr , GFP_KERNEL ) ;
if ( ! buf - > area )
return - ENOMEM ;
buf - > bytes = size ;
return 0 ;
}
static u64 imx_pcm_dmamask = DMA_BIT_MASK ( 32 ) ;
int imx_pcm_new ( struct snd_card * card , struct snd_soc_dai * dai ,
struct snd_pcm * pcm )
{
int ret = 0 ;
if ( ! card - > dev - > dma_mask )
card - > dev - > dma_mask = & imx_pcm_dmamask ;
if ( ! card - > dev - > coherent_dma_mask )
card - > dev - > coherent_dma_mask = DMA_BIT_MASK ( 32 ) ;
if ( dai - > playback . channels_min ) {
ret = imx_pcm_preallocate_dma_buffer ( pcm ,
SNDRV_PCM_STREAM_PLAYBACK ) ;
if ( ret )
goto out ;
}
if ( dai - > capture . channels_min ) {
ret = imx_pcm_preallocate_dma_buffer ( pcm ,
SNDRV_PCM_STREAM_CAPTURE ) ;
if ( ret )
goto out ;
}
out :
return ret ;
}
void imx_pcm_free ( struct snd_pcm * pcm )
{
struct snd_pcm_substream * substream ;
struct snd_dma_buffer * buf ;
int stream ;
for ( stream = 0 ; stream < 2 ; stream + + ) {
substream = pcm - > streams [ stream ] . substream ;
if ( ! substream )
continue ;
buf = & substream - > dma_buffer ;
if ( ! buf - > area )
continue ;
dma_free_writecombine ( pcm - > card - > dev , buf - > bytes ,
buf - > area , buf - > addr ) ;
buf - > area = NULL ;
}
}
struct snd_soc_platform imx_soc_platform = {
. name = " imx-audio " ,
} ;
EXPORT_SYMBOL_GPL ( imx_soc_platform ) ;
static struct snd_soc_dai imx_ac97_dai = {
. name = " AC97 " ,
. ac97_control = 1 ,
. playback = {
. stream_name = " AC97 Playback " ,
. channels_min = 2 ,
. channels_max = 2 ,
. rates = SNDRV_PCM_RATE_48000 ,
. formats = SNDRV_PCM_FMTBIT_S16_LE ,
} ,
. capture = {
. stream_name = " AC97 Capture " ,
. channels_min = 2 ,
. channels_max = 2 ,
. rates = SNDRV_PCM_RATE_48000 ,
. formats = SNDRV_PCM_FMTBIT_S16_LE ,
} ,
. ops = & imx_ssi_pcm_dai_ops ,
} ;
static void setup_channel_to_ac97 ( struct imx_ssi * imx_ssi )
{
void __iomem * base = imx_ssi - > base ;
writel ( 0x0 , base + SSI_SCR ) ;
writel ( 0x0 , base + SSI_STCR ) ;
writel ( 0x0 , base + SSI_SRCR ) ;
writel ( SSI_SCR_SYN | SSI_SCR_NET , base + SSI_SCR ) ;
writel ( SSI_SFCSR_RFWM0 ( 8 ) |
SSI_SFCSR_TFWM0 ( 8 ) |
SSI_SFCSR_RFWM1 ( 8 ) |
SSI_SFCSR_TFWM1 ( 8 ) , base + SSI_SFCSR ) ;
writel ( SSI_STCCR_WL ( 16 ) | SSI_STCCR_DC ( 12 ) , base + SSI_STCCR ) ;
writel ( SSI_STCCR_WL ( 16 ) | SSI_STCCR_DC ( 12 ) , base + SSI_SRCCR ) ;
writel ( SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN , base + SSI_SCR ) ;
writel ( SSI_SOR_WAIT ( 3 ) , base + SSI_SOR ) ;
writel ( SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN |
SSI_SCR_TE | SSI_SCR_RE ,
base + SSI_SCR ) ;
writel ( SSI_SACNT_DEFAULT , base + SSI_SACNT ) ;
writel ( 0xff , base + SSI_SACCDIS ) ;
writel ( 0x300 , base + SSI_SACCEN ) ;
}
static struct imx_ssi * ac97_ssi ;
static void imx_ssi_ac97_write ( struct snd_ac97 * ac97 , unsigned short reg ,
unsigned short val )
{
struct imx_ssi * imx_ssi = ac97_ssi ;
void __iomem * base = imx_ssi - > base ;
unsigned int lreg ;
unsigned int lval ;
if ( reg > 0x7f )
return ;
pr_debug ( " %s: 0x%02x 0x%04x \n " , __func__ , reg , val ) ;
lreg = reg < < 12 ;
writel ( lreg , base + SSI_SACADD ) ;
lval = val < < 4 ;
writel ( lval , base + SSI_SACDAT ) ;
writel ( SSI_SACNT_DEFAULT | SSI_SACNT_WR , base + SSI_SACNT ) ;
udelay ( 100 ) ;
}
static unsigned short imx_ssi_ac97_read ( struct snd_ac97 * ac97 ,
unsigned short reg )
{
struct imx_ssi * imx_ssi = ac97_ssi ;
void __iomem * base = imx_ssi - > base ;
unsigned short val = - 1 ;
unsigned int lreg ;
lreg = ( reg & 0x7f ) < < 12 ;
writel ( lreg , base + SSI_SACADD ) ;
writel ( SSI_SACNT_DEFAULT | SSI_SACNT_RD , base + SSI_SACNT ) ;
udelay ( 100 ) ;
val = ( readl ( base + SSI_SACDAT ) > > 4 ) & 0xffff ;
pr_debug ( " %s: 0x%02x 0x%04x \n " , __func__ , reg , val ) ;
return val ;
}
static void imx_ssi_ac97_reset ( struct snd_ac97 * ac97 )
{
struct imx_ssi * imx_ssi = ac97_ssi ;
if ( imx_ssi - > ac97_reset )
imx_ssi - > ac97_reset ( ac97 ) ;
}
static void imx_ssi_ac97_warm_reset ( struct snd_ac97 * ac97 )
{
struct imx_ssi * imx_ssi = ac97_ssi ;
if ( imx_ssi - > ac97_warm_reset )
imx_ssi - > ac97_warm_reset ( ac97 ) ;
}
struct snd_ac97_bus_ops soc_ac97_ops = {
. read = imx_ssi_ac97_read ,
. write = imx_ssi_ac97_write ,
. reset = imx_ssi_ac97_reset ,
. warm_reset = imx_ssi_ac97_warm_reset
} ;
EXPORT_SYMBOL_GPL ( soc_ac97_ops ) ;
2010-01-06 20:56:52 +03:00
struct snd_soc_dai imx_ssi_pcm_dai [ 2 ] ;
2009-11-25 18:41:04 +03:00
EXPORT_SYMBOL_GPL ( imx_ssi_pcm_dai ) ;
static int imx_ssi_probe ( struct platform_device * pdev )
{
struct resource * res ;
struct imx_ssi * ssi ;
struct imx_ssi_platform_data * pdata = pdev - > dev . platform_data ;
struct snd_soc_platform * platform ;
int ret = 0 ;
unsigned int val ;
2010-01-06 20:56:52 +03:00
struct snd_soc_dai * dai = & imx_ssi_pcm_dai [ pdev - > id ] ;
if ( dai - > id > = ARRAY_SIZE ( imx_ssi_pcm_dai ) )
return - EINVAL ;
2009-11-25 18:41:04 +03:00
ssi = kzalloc ( sizeof ( * ssi ) , GFP_KERNEL ) ;
if ( ! ssi )
return - ENOMEM ;
if ( pdata ) {
ssi - > ac97_reset = pdata - > ac97_reset ;
ssi - > ac97_warm_reset = pdata - > ac97_warm_reset ;
ssi - > flags = pdata - > flags ;
}
ssi - > irq = platform_get_irq ( pdev , 0 ) ;
ssi - > clk = clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( ssi - > clk ) ) {
ret = PTR_ERR ( ssi - > clk ) ;
dev_err ( & pdev - > dev , " Cannot get the clock: %d \n " ,
ret ) ;
goto failed_clk ;
}
clk_enable ( ssi - > clk ) ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! res ) {
ret = - ENODEV ;
goto failed_get_resource ;
}
if ( ! request_mem_region ( res - > start , resource_size ( res ) , DRV_NAME ) ) {
dev_err ( & pdev - > dev , " request_mem_region failed \n " ) ;
ret = - EBUSY ;
goto failed_get_resource ;
}
ssi - > base = ioremap ( res - > start , resource_size ( res ) ) ;
if ( ! ssi - > base ) {
dev_err ( & pdev - > dev , " ioremap failed \n " ) ;
ret = - ENODEV ;
goto failed_ioremap ;
}
if ( ssi - > flags & IMX_SSI_USE_AC97 ) {
if ( ac97_ssi ) {
ret = - EBUSY ;
goto failed_ac97 ;
}
ac97_ssi = ssi ;
setup_channel_to_ac97 ( ssi ) ;
2010-01-06 20:56:52 +03:00
memcpy ( dai , & imx_ac97_dai , sizeof ( imx_ac97_dai ) ) ;
2009-11-25 18:41:04 +03:00
} else
2010-01-06 20:56:52 +03:00
memcpy ( dai , & imx_ssi_dai , sizeof ( imx_ssi_dai ) ) ;
2009-11-25 18:41:04 +03:00
writel ( 0x0 , ssi - > base + SSI_SIER ) ;
ssi - > dma_params_rx . dma_addr = res - > start + SSI_SRX0 ;
ssi - > dma_params_tx . dma_addr = res - > start + SSI_STX0 ;
res = platform_get_resource_byname ( pdev , IORESOURCE_DMA , " tx0 " ) ;
if ( res )
ssi - > dma_params_tx . dma = res - > start ;
res = platform_get_resource_byname ( pdev , IORESOURCE_DMA , " rx0 " ) ;
if ( res )
ssi - > dma_params_rx . dma = res - > start ;
2010-01-06 20:56:52 +03:00
dai - > id = pdev - > id ;
dai - > dev = & pdev - > dev ;
dai - > name = kasprintf ( GFP_KERNEL , " imx-ssi.%d " , pdev - > id ) ;
dai - > private_data = ssi ;
2009-11-25 18:41:04 +03:00
if ( ( cpu_is_mx27 ( ) | | cpu_is_mx21 ( ) ) & &
! ( ssi - > flags & IMX_SSI_USE_AC97 ) ) {
ssi - > flags | = IMX_SSI_DMA ;
platform = imx_ssi_dma_mx2_init ( pdev , ssi ) ;
} else
platform = imx_ssi_fiq_init ( pdev , ssi ) ;
imx_soc_platform . pcm_ops = platform - > pcm_ops ;
imx_soc_platform . pcm_new = platform - > pcm_new ;
imx_soc_platform . pcm_free = platform - > pcm_free ;
val = SSI_SFCSR_TFWM0 ( ssi - > dma_params_tx . burstsize ) |
SSI_SFCSR_RFWM0 ( ssi - > dma_params_rx . burstsize ) ;
writel ( val , ssi - > base + SSI_SFCSR ) ;
2010-01-06 20:56:52 +03:00
ret = snd_soc_register_dai ( dai ) ;
2009-11-25 18:41:04 +03:00
if ( ret ) {
dev_err ( & pdev - > dev , " register DAI failed \n " ) ;
goto failed_register ;
}
platform_set_drvdata ( pdev , ssi ) ;
return 0 ;
failed_register :
failed_ac97 :
iounmap ( ssi - > base ) ;
failed_ioremap :
release_mem_region ( res - > start , resource_size ( res ) ) ;
failed_get_resource :
clk_disable ( ssi - > clk ) ;
clk_put ( ssi - > clk ) ;
failed_clk :
kfree ( ssi ) ;
return ret ;
}
static int __devexit imx_ssi_remove ( struct platform_device * pdev )
{
struct resource * res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
struct imx_ssi * ssi = platform_get_drvdata ( pdev ) ;
2010-01-06 20:56:52 +03:00
struct snd_soc_dai * dai = & imx_ssi_pcm_dai [ pdev - > id ] ;
2009-11-25 18:41:04 +03:00
2010-01-06 20:56:52 +03:00
snd_soc_unregister_dai ( dai ) ;
2009-11-25 18:41:04 +03:00
if ( ssi - > flags & IMX_SSI_USE_AC97 )
ac97_ssi = NULL ;
if ( ! ( ssi - > flags & IMX_SSI_DMA ) )
imx_ssi_fiq_exit ( pdev , ssi ) ;
iounmap ( ssi - > base ) ;
release_mem_region ( res - > start , resource_size ( res ) ) ;
clk_disable ( ssi - > clk ) ;
clk_put ( ssi - > clk ) ;
kfree ( ssi ) ;
return 0 ;
}
static struct platform_driver imx_ssi_driver = {
. probe = imx_ssi_probe ,
. remove = __devexit_p ( imx_ssi_remove ) ,
. driver = {
. name = DRV_NAME ,
. owner = THIS_MODULE ,
} ,
} ;
static int __init imx_ssi_init ( void )
{
int ret ;
ret = snd_soc_register_platform ( & imx_soc_platform ) ;
if ( ret ) {
pr_err ( " failed to register soc platform: %d \n " , ret ) ;
return ret ;
}
ret = platform_driver_register ( & imx_ssi_driver ) ;
if ( ret ) {
snd_soc_unregister_platform ( & imx_soc_platform ) ;
return ret ;
}
return 0 ;
}
static void __exit imx_ssi_exit ( void )
{
platform_driver_unregister ( & imx_ssi_driver ) ;
snd_soc_unregister_platform ( & imx_soc_platform ) ;
}
module_init ( imx_ssi_init ) ;
module_exit ( imx_ssi_exit ) ;
/* Module information */
MODULE_AUTHOR ( " Sascha Hauer, <s.hauer@pengutronix.de> " ) ;
MODULE_DESCRIPTION ( " i.MX I2S/ac97 SoC Interface " ) ;
MODULE_LICENSE ( " GPL " ) ;