2006-10-06 18:40:25 +02:00
/*
2007-04-16 17:18:52 +02:00
* at91 - ssc . c - - ALSA SoC AT91 SSC Audio Layer Platform driver
2006-10-06 18:40:25 +02:00
*
* Author : Frank Mandarino < fmandarino @ endrelia . com >
* Endrelia Technologies Inc .
*
* Based on pxa2xx Platform drivers by
* Liam Girdwood < liam . girdwood @ 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/init.h>
# include <linux/module.h>
# include <linux/interrupt.h>
# include <linux/device.h>
# include <linux/delay.h>
# include <linux/clk.h>
2007-02-12 14:06:22 +01:00
# include <linux/atmel_pdc.h>
2006-10-06 18:40:25 +02:00
# include <sound/core.h>
# include <sound/pcm.h>
2007-04-16 17:18:52 +02:00
# include <sound/pcm_params.h>
2006-10-06 18:40:25 +02:00
# include <sound/initval.h>
# include <sound/soc.h>
2008-08-05 16:14:15 +01:00
# include <mach/hardware.h>
# include <mach/at91_pmc.h>
# include <mach/at91_ssc.h>
2006-10-06 18:40:25 +02:00
2006-11-24 15:49:39 +01:00
# include "at91-pcm.h"
2007-04-16 17:18:52 +02:00
# include "at91-ssc.h"
2006-10-06 18:40:25 +02:00
#if 0
2007-04-16 17:18:52 +02:00
# define DBG(x...) printk(KERN_DEBUG "at91-ssc:" x)
2006-10-06 18:40:25 +02:00
# else
# define DBG(x...)
# endif
2008-07-10 10:15:35 +01:00
# if defined(CONFIG_ARCH_AT91SAM9260) || defined(CONFIG_ARCH_AT91SAM9G20)
2006-11-24 15:49:39 +01:00
# define NUM_SSC_DEVICES 1
# else
# define NUM_SSC_DEVICES 3
# endif
2006-10-06 18:40:25 +02:00
/*
2006-11-24 15:49:39 +01:00
* SSC PDC registers required by the PCM DMA engine .
2006-10-06 18:40:25 +02:00
*/
2006-11-24 15:49:39 +01:00
static struct at91_pdc_regs pdc_tx_reg = {
2007-02-12 14:06:22 +01:00
. xpr = ATMEL_PDC_TPR ,
. xcr = ATMEL_PDC_TCR ,
. xnpr = ATMEL_PDC_TNPR ,
. xncr = ATMEL_PDC_TNCR ,
2006-10-06 18:40:25 +02:00
} ;
2006-11-24 15:49:39 +01:00
static struct at91_pdc_regs pdc_rx_reg = {
2007-02-12 14:06:22 +01:00
. xpr = ATMEL_PDC_RPR ,
. xcr = ATMEL_PDC_RCR ,
. xnpr = ATMEL_PDC_RNPR ,
. xncr = ATMEL_PDC_RNCR ,
2006-10-06 18:40:25 +02:00
} ;
/*
* SSC & PDC status bits for transmit and receive .
*/
2006-11-24 15:49:39 +01:00
static struct at91_ssc_mask ssc_tx_mask = {
2006-10-06 18:40:25 +02:00
. ssc_enable = AT91_SSC_TXEN ,
. ssc_disable = AT91_SSC_TXDIS ,
. ssc_endx = AT91_SSC_ENDTX ,
. ssc_endbuf = AT91_SSC_TXBUFE ,
2007-02-12 14:06:22 +01:00
. pdc_enable = ATMEL_PDC_TXTEN ,
. pdc_disable = ATMEL_PDC_TXTDIS ,
2006-10-06 18:40:25 +02:00
} ;
2006-11-24 15:49:39 +01:00
static struct at91_ssc_mask ssc_rx_mask = {
2006-10-06 18:40:25 +02:00
. ssc_enable = AT91_SSC_RXEN ,
. ssc_disable = AT91_SSC_RXDIS ,
. ssc_endx = AT91_SSC_ENDRX ,
. ssc_endbuf = AT91_SSC_RXBUFF ,
2007-02-12 14:06:22 +01:00
. pdc_enable = ATMEL_PDC_RXTEN ,
. pdc_disable = ATMEL_PDC_RXTDIS ,
2006-10-06 18:40:25 +02:00
} ;
/*
* DMA parameters .
*/
2006-11-24 15:49:39 +01:00
static struct at91_pcm_dma_params ssc_dma_params [ NUM_SSC_DEVICES ] [ 2 ] = {
2006-10-06 18:40:25 +02:00
{ {
2007-04-16 17:18:52 +02:00
. name = " SSC0 PCM out " ,
2006-11-24 15:49:39 +01:00
. pdc = & pdc_tx_reg ,
2006-10-06 18:40:25 +02:00
. mask = & ssc_tx_mask ,
} ,
{
2007-04-16 17:18:52 +02:00
. name = " SSC0 PCM in " ,
2006-11-24 15:49:39 +01:00
. pdc = & pdc_rx_reg ,
2006-10-06 18:40:25 +02:00
. mask = & ssc_rx_mask ,
} } ,
2006-11-24 15:49:39 +01:00
# if NUM_SSC_DEVICES == 3
2006-10-06 18:40:25 +02:00
{ {
2007-04-16 17:18:52 +02:00
. name = " SSC1 PCM out " ,
2006-11-24 15:49:39 +01:00
. pdc = & pdc_tx_reg ,
2006-10-06 18:40:25 +02:00
. mask = & ssc_tx_mask ,
} ,
{
2007-04-16 17:18:52 +02:00
. name = " SSC1 PCM in " ,
2006-11-24 15:49:39 +01:00
. pdc = & pdc_rx_reg ,
2006-10-06 18:40:25 +02:00
. mask = & ssc_rx_mask ,
} } ,
{ {
2007-04-16 17:18:52 +02:00
. name = " SSC2 PCM out " ,
2006-11-24 15:49:39 +01:00
. pdc = & pdc_tx_reg ,
2006-10-06 18:40:25 +02:00
. mask = & ssc_tx_mask ,
} ,
{
2007-04-16 17:18:52 +02:00
. name = " SSC2 PCM in " ,
2006-11-24 15:49:39 +01:00
. pdc = & pdc_rx_reg ,
2006-10-06 18:40:25 +02:00
. mask = & ssc_rx_mask ,
} } ,
2006-11-24 15:49:39 +01:00
# endif
2006-10-06 18:40:25 +02:00
} ;
2006-11-24 15:49:39 +01:00
struct at91_ssc_state {
2006-10-06 18:40:25 +02:00
u32 ssc_cmr ;
u32 ssc_rcmr ;
u32 ssc_rfmr ;
u32 ssc_tcmr ;
u32 ssc_tfmr ;
u32 ssc_sr ;
u32 ssc_imr ;
} ;
2006-11-24 15:49:39 +01:00
static struct at91_ssc_info {
2006-10-06 18:40:25 +02:00
char * name ;
2006-11-24 15:49:39 +01:00
struct at91_ssc_periph ssc ;
2006-10-06 18:40:25 +02:00
spinlock_t lock ; /* lock for dir_mask */
2007-02-02 17:18:38 +01:00
unsigned short dir_mask ; /* 0=unused, 1=playback, 2=capture */
unsigned short initialized ; /* 1=SSC has been initialized */
unsigned short daifmt ;
unsigned short cmr_div ;
unsigned short tcmr_period ;
unsigned short rcmr_period ;
2006-11-24 15:49:39 +01:00
struct at91_pcm_dma_params * dma_params [ 2 ] ;
struct at91_ssc_state ssc_state ;
2006-10-06 18:40:25 +02:00
2006-11-24 15:49:39 +01:00
} ssc_info [ NUM_SSC_DEVICES ] = {
2006-10-06 18:40:25 +02:00
{
. name = " ssc0 " ,
2007-04-19 15:22:56 +02:00
. lock = __SPIN_LOCK_UNLOCKED ( ssc_info [ 0 ] . lock ) ,
2006-10-06 18:40:25 +02:00
. dir_mask = 0 ,
. initialized = 0 ,
} ,
2006-11-24 15:49:39 +01:00
# if NUM_SSC_DEVICES == 3
2006-10-06 18:40:25 +02:00
{
. name = " ssc1 " ,
2007-04-19 15:22:56 +02:00
. lock = __SPIN_LOCK_UNLOCKED ( ssc_info [ 1 ] . lock ) ,
2006-10-06 18:40:25 +02:00
. dir_mask = 0 ,
. initialized = 0 ,
} ,
{
. name = " ssc2 " ,
2007-04-19 15:22:56 +02:00
. lock = __SPIN_LOCK_UNLOCKED ( ssc_info [ 2 ] . lock ) ,
2006-10-06 18:40:25 +02:00
. dir_mask = 0 ,
. initialized = 0 ,
} ,
2006-11-24 15:49:39 +01:00
# endif
2006-10-06 18:40:25 +02:00
} ;
2007-04-16 17:18:52 +02:00
static unsigned int at91_ssc_sysclk ;
2006-10-06 18:40:25 +02:00
2007-02-02 17:18:38 +01:00
/*
* SSC interrupt handler . Passes PDC interrupts to the DMA
* interrupt handler in the PCM driver .
*/
2007-04-16 17:18:52 +02:00
static irqreturn_t at91_ssc_interrupt ( int irq , void * dev_id )
2006-10-06 18:40:25 +02:00
{
2006-11-24 15:49:39 +01:00
struct at91_ssc_info * ssc_p = dev_id ;
struct at91_pcm_dma_params * dma_params ;
2006-10-06 18:40:25 +02:00
u32 ssc_sr ;
int i ;
2006-11-24 15:49:39 +01:00
ssc_sr = at91_ssc_read ( ssc_p - > ssc . base + AT91_SSC_SR )
& at91_ssc_read ( ssc_p - > ssc . base + AT91_SSC_IMR ) ;
2006-10-06 18:40:25 +02:00
/*
* Loop through the substreams attached to this SSC . If
* a DMA - related interrupt occurred on that substream , call
* the DMA interrupt handler function , if one has been
* registered in the dma_params structure by the PCM driver .
*/
for ( i = 0 ; i < ARRAY_SIZE ( ssc_p - > dma_params ) ; i + + ) {
dma_params = ssc_p - > dma_params [ i ] ;
if ( dma_params ! = NULL & & dma_params - > dma_intr_handler ! = NULL & &
( ssc_sr &
( dma_params - > mask - > ssc_endx | dma_params - > mask - > ssc_endbuf ) ) )
dma_params - > dma_intr_handler ( ssc_sr , dma_params - > substream ) ;
}
return IRQ_HANDLED ;
}
2007-02-02 17:18:38 +01:00
/*
* Startup . Only that one substream allowed in each direction .
*/
2007-04-16 17:18:52 +02:00
static int at91_ssc_startup ( struct snd_pcm_substream * substream )
2006-10-06 18:40:25 +02:00
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
2007-02-02 17:18:38 +01:00
struct at91_ssc_info * ssc_p = & ssc_info [ rtd - > dai - > cpu_dai - > id ] ;
2006-10-06 18:40:25 +02:00
int dir_mask ;
2007-04-16 17:18:52 +02:00
DBG ( " ssc_startup: SSC_SR=0x%08lx \n " ,
2006-11-24 15:49:39 +01:00
at91_ssc_read ( ssc_p - > ssc . base + AT91_SSC_SR ) ) ;
2006-10-06 18:40:25 +02:00
dir_mask = substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ? 0x1 : 0x2 ;
spin_lock_irq ( & ssc_p - > lock ) ;
if ( ssc_p - > dir_mask & dir_mask ) {
spin_unlock_irq ( & ssc_p - > lock ) ;
return - EBUSY ;
}
ssc_p - > dir_mask | = dir_mask ;
spin_unlock_irq ( & ssc_p - > lock ) ;
return 0 ;
}
2007-02-02 17:18:38 +01:00
/*
* Shutdown . Clear DMA parameters and shutdown the SSC if there
* are no other substreams open .
*/
2007-04-16 17:18:52 +02:00
static void at91_ssc_shutdown ( struct snd_pcm_substream * substream )
2006-10-06 18:40:25 +02:00
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
2007-02-02 17:18:38 +01:00
struct at91_ssc_info * ssc_p = & ssc_info [ rtd - > dai - > cpu_dai - > id ] ;
struct at91_pcm_dma_params * dma_params ;
2006-10-06 18:40:25 +02:00
int dir , dir_mask ;
dir = substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1 ;
2007-02-02 17:18:38 +01:00
dma_params = ssc_p - > dma_params [ dir ] ;
2006-10-06 18:40:25 +02:00
if ( dma_params ! = NULL ) {
2006-11-24 15:49:39 +01:00
at91_ssc_write ( dma_params - > ssc_base + AT91_SSC_CR ,
dma_params - > mask - > ssc_disable ) ;
2006-10-06 18:40:25 +02:00
DBG ( " %s disabled SSC_SR=0x%08lx \n " , ( dir ? " receive " : " transmit " ) ,
2006-11-24 15:49:39 +01:00
at91_ssc_read ( ssc_p - > ssc . base + AT91_SSC_SR ) ) ;
2006-10-06 18:40:25 +02:00
2006-11-24 15:49:39 +01:00
dma_params - > ssc_base = NULL ;
2006-10-06 18:40:25 +02:00
dma_params - > substream = NULL ;
ssc_p - > dma_params [ dir ] = NULL ;
}
dir_mask = 1 < < dir ;
spin_lock_irq ( & ssc_p - > lock ) ;
ssc_p - > dir_mask & = ~ dir_mask ;
if ( ! ssc_p - > dir_mask ) {
/* Shutdown the SSC clock. */
2006-11-24 15:49:39 +01:00
DBG ( " Stopping pid %d clock \n " , ssc_p - > ssc . pid ) ;
at91_sys_write ( AT91_PMC_PCDR , 1 < < ssc_p - > ssc . pid ) ;
2006-10-06 18:40:25 +02:00
2007-02-02 17:18:38 +01:00
if ( ssc_p - > initialized ) {
2006-11-24 15:49:39 +01:00
free_irq ( ssc_p - > ssc . pid , ssc_p ) ;
2007-02-02 17:18:38 +01:00
ssc_p - > initialized = 0 ;
}
2006-10-06 18:40:25 +02:00
/* Reset the SSC */
2006-11-24 15:49:39 +01:00
at91_ssc_write ( ssc_p - > ssc . base + AT91_SSC_CR , AT91_SSC_SWRST ) ;
2006-10-06 18:40:25 +02:00
2007-02-02 17:18:38 +01:00
/* Clear the SSC dividers */
ssc_p - > cmr_div = ssc_p - > tcmr_period = ssc_p - > rcmr_period = 0 ;
2006-10-06 18:40:25 +02:00
}
spin_unlock_irq ( & ssc_p - > lock ) ;
}
2007-02-02 17:18:38 +01:00
/*
* Record the SSC system clock rate .
*/
2008-07-07 16:07:37 +01:00
static int at91_ssc_set_dai_sysclk ( struct snd_soc_dai * cpu_dai ,
2007-02-02 17:18:38 +01:00
int clk_id , unsigned int freq , int dir )
2006-10-06 18:40:25 +02:00
{
2007-02-02 17:18:38 +01:00
/*
* The only clock supplied to the SSC is the AT91 master clock ,
* which is only used if the SSC is generating BCLK and / or
* LRC clocks .
*/
switch ( clk_id ) {
case AT91_SYSCLK_MCK :
2007-04-16 17:18:52 +02:00
at91_ssc_sysclk = freq ;
2007-02-02 17:18:38 +01:00
break ;
default :
return - EINVAL ;
}
2006-10-06 18:40:25 +02:00
return 0 ;
}
2007-02-02 17:18:38 +01:00
/*
* Record the DAI format for use in hw_params ( ) .
*/
2008-07-07 16:07:37 +01:00
static int at91_ssc_set_dai_fmt ( struct snd_soc_dai * cpu_dai ,
2007-02-02 17:18:38 +01:00
unsigned int fmt )
2006-10-06 18:40:25 +02:00
{
2007-02-02 17:18:38 +01:00
struct at91_ssc_info * ssc_p = & ssc_info [ cpu_dai - > id ] ;
2006-10-06 18:40:25 +02:00
2007-02-02 17:18:38 +01:00
ssc_p - > daifmt = fmt ;
2006-10-06 18:40:25 +02:00
return 0 ;
}
2007-02-02 17:18:38 +01:00
/*
* Record SSC clock dividers for use in hw_params ( ) .
*/
2008-07-07 16:07:37 +01:00
static int at91_ssc_set_dai_clkdiv ( struct snd_soc_dai * cpu_dai ,
2007-02-02 17:18:38 +01:00
int div_id , int div )
2006-10-06 18:40:25 +02:00
{
2007-02-02 17:18:38 +01:00
struct at91_ssc_info * ssc_p = & ssc_info [ cpu_dai - > id ] ;
switch ( div_id ) {
case AT91SSC_CMR_DIV :
/*
* The same master clock divider is used for both
* transmit and receive , so if a value has already
* been set , it must match this value .
*/
if ( ssc_p - > cmr_div = = 0 )
ssc_p - > cmr_div = div ;
else
if ( div ! = ssc_p - > cmr_div )
return - EBUSY ;
break ;
case AT91SSC_TCMR_PERIOD :
ssc_p - > tcmr_period = div ;
break ;
case AT91SSC_RCMR_PERIOD :
ssc_p - > rcmr_period = div ;
break ;
default :
return - EINVAL ;
}
return 0 ;
2006-10-06 18:40:25 +02:00
}
2007-02-02 17:18:38 +01:00
/*
* Configure the SSC .
*/
2007-04-16 17:18:52 +02:00
static int at91_ssc_hw_params ( struct snd_pcm_substream * substream ,
2006-10-06 18:40:25 +02:00
struct snd_pcm_hw_params * params )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
2007-02-02 17:18:38 +01:00
int id = rtd - > dai - > cpu_dai - > id ;
2006-11-24 15:49:39 +01:00
struct at91_ssc_info * ssc_p = & ssc_info [ id ] ;
struct at91_pcm_dma_params * dma_params ;
2006-10-06 18:40:25 +02:00
int dir , channels , bits ;
2007-02-02 17:18:38 +01:00
u32 tfmr , rfmr , tcmr , rcmr ;
int start_event ;
2006-10-06 18:40:25 +02:00
int ret ;
/*
* Currently , there is only one set of dma params for
* each direction . If more are added , this code will
* have to be changed to select the proper set .
*/
dir = substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1 ;
dma_params = & ssc_dma_params [ id ] [ dir ] ;
2006-11-24 15:49:39 +01:00
dma_params - > ssc_base = ssc_p - > ssc . base ;
2006-10-06 18:40:25 +02:00
dma_params - > substream = substream ;
ssc_p - > dma_params [ dir ] = dma_params ;
2007-02-02 17:18:38 +01:00
/*
* The cpu_dai - > dma_data field is only used to communicate the
* appropriate DMA parameters to the pcm driver hw_params ( )
* function . It should not be used for other purposes
* as it is common to all substreams .
*/
rtd - > dai - > cpu_dai - > dma_data = dma_params ;
2006-10-06 18:40:25 +02:00
2007-02-02 17:18:38 +01:00
channels = params_channels ( params ) ;
2006-10-06 18:40:25 +02:00
2007-04-16 17:18:52 +02:00
/*
* Determine sample size in bits and the PDC increment .
*/
switch ( params_format ( params ) ) {
case SNDRV_PCM_FORMAT_S8 :
bits = 8 ;
dma_params - > pdc_xfer_size = 1 ;
break ;
case SNDRV_PCM_FORMAT_S16_LE :
bits = 16 ;
dma_params - > pdc_xfer_size = 2 ;
break ;
case SNDRV_PCM_FORMAT_S24_LE :
bits = 24 ;
dma_params - > pdc_xfer_size = 4 ;
break ;
case SNDRV_PCM_FORMAT_S32_LE :
bits = 32 ;
dma_params - > pdc_xfer_size = 4 ;
break ;
default :
printk ( KERN_WARNING " at91-ssc: unsupported PCM format " ) ;
return - EINVAL ;
}
2007-02-02 17:18:38 +01:00
/*
* The SSC only supports up to 16 - bit samples in I2S format , due
2007-04-16 17:18:52 +02:00
* to the size of the Frame Mode Register FSLEN field .
2007-02-02 17:18:38 +01:00
*/
2007-04-16 17:18:52 +02:00
if ( ( ssc_p - > daifmt & SND_SOC_DAIFMT_FORMAT_MASK ) = = SND_SOC_DAIFMT_I2S
& & bits > 16 ) {
printk ( KERN_WARNING
" at91-ssc: sample size %d is too large for I2S \n " , bits ) ;
return - EINVAL ;
}
2006-10-06 18:40:25 +02:00
/*
2007-02-02 17:18:38 +01:00
* Compute SSC register settings .
2006-10-06 18:40:25 +02:00
*/
2007-04-16 17:18:52 +02:00
switch ( ssc_p - > daifmt
& ( SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_MASTER_MASK ) ) {
case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS :
2007-02-02 17:18:38 +01:00
/*
2007-04-16 17:18:52 +02:00
* I2S format , SSC provides BCLK and LRC clocks .
2007-02-02 17:18:38 +01:00
*
* The SSC transmit and receive clocks are generated from the
* MCK divider , and the BCLK signal is output on the SSC TK line .
*/
rcmr = ( ( ssc_p - > rcmr_period < < 24 ) & AT91_SSC_PERIOD )
| ( ( 1 < < 16 ) & AT91_SSC_STTDLY )
| ( ( AT91_SSC_START_FALLING_RF ) & AT91_SSC_START )
| ( ( AT91_SSC_CK_RISING ) & AT91_SSC_CKI )
| ( ( AT91_SSC_CKO_NONE ) & AT91_SSC_CKO )
| ( ( AT91_SSC_CKS_DIV ) & AT91_SSC_CKS ) ;
rfmr = ( ( AT91_SSC_FSEDGE_POSITIVE ) & AT91_SSC_FSEDGE )
| ( ( AT91_SSC_FSOS_NEGATIVE ) & AT91_SSC_FSOS )
| ( ( ( bits - 1 ) < < 16 ) & AT91_SSC_FSLEN )
| ( ( ( channels - 1 ) < < 8 ) & AT91_SSC_DATNB )
| ( ( 1 < < 7 ) & AT91_SSC_MSBF )
| ( ( 0 < < 5 ) & AT91_SSC_LOOP )
| ( ( ( bits - 1 ) < < 0 ) & AT91_SSC_DATALEN ) ;
tcmr = ( ( ssc_p - > tcmr_period < < 24 ) & AT91_SSC_PERIOD )
| ( ( 1 < < 16 ) & AT91_SSC_STTDLY )
| ( ( AT91_SSC_START_FALLING_RF ) & AT91_SSC_START )
| ( ( AT91_SSC_CKI_FALLING ) & AT91_SSC_CKI )
| ( ( AT91_SSC_CKO_CONTINUOUS ) & AT91_SSC_CKO )
| ( ( AT91_SSC_CKS_DIV ) & AT91_SSC_CKS ) ;
tfmr = ( ( AT91_SSC_FSEDGE_POSITIVE ) & AT91_SSC_FSEDGE )
| ( ( 0 < < 23 ) & AT91_SSC_FSDEN )
| ( ( AT91_SSC_FSOS_NEGATIVE ) & AT91_SSC_FSOS )
| ( ( ( bits - 1 ) < < 16 ) & AT91_SSC_FSLEN )
| ( ( ( channels - 1 ) < < 8 ) & AT91_SSC_DATNB )
| ( ( 1 < < 7 ) & AT91_SSC_MSBF )
| ( ( 0 < < 5 ) & AT91_SSC_DATDEF )
| ( ( ( bits - 1 ) < < 0 ) & AT91_SSC_DATALEN ) ;
break ;
2007-04-16 17:18:52 +02:00
case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM :
2007-02-02 17:18:38 +01:00
/*
2007-04-16 17:18:52 +02:00
* I2S format , CODEC supplies BCLK and LRC clocks .
2007-02-02 17:18:38 +01:00
*
* The SSC transmit clock is obtained from the BCLK signal on
* on the TK line , and the SSC receive clock is generated from the
* transmit clock .
*
* For single channel data , one sample is transferred on the falling
* edge of the LRC clock . For two channel data , one sample is
* transferred on both edges of the LRC clock .
*/
start_event = channels = = 1
? AT91_SSC_START_FALLING_RF
: AT91_SSC_START_EDGE_RF ;
rcmr = ( ( 0 < < 24 ) & AT91_SSC_PERIOD )
| ( ( 1 < < 16 ) & AT91_SSC_STTDLY )
| ( ( start_event ) & AT91_SSC_START )
| ( ( AT91_SSC_CK_RISING ) & AT91_SSC_CKI )
| ( ( AT91_SSC_CKO_NONE ) & AT91_SSC_CKO )
| ( ( AT91_SSC_CKS_CLOCK ) & AT91_SSC_CKS ) ;
rfmr = ( ( AT91_SSC_FSEDGE_POSITIVE ) & AT91_SSC_FSEDGE )
| ( ( AT91_SSC_FSOS_NONE ) & AT91_SSC_FSOS )
| ( ( 0 < < 16 ) & AT91_SSC_FSLEN )
| ( ( 0 < < 8 ) & AT91_SSC_DATNB )
| ( ( 1 < < 7 ) & AT91_SSC_MSBF )
| ( ( 0 < < 5 ) & AT91_SSC_LOOP )
| ( ( ( bits - 1 ) < < 0 ) & AT91_SSC_DATALEN ) ;
tcmr = ( ( 0 < < 24 ) & AT91_SSC_PERIOD )
| ( ( 1 < < 16 ) & AT91_SSC_STTDLY )
| ( ( start_event ) & AT91_SSC_START )
| ( ( AT91_SSC_CKI_FALLING ) & AT91_SSC_CKI )
| ( ( AT91_SSC_CKO_NONE ) & AT91_SSC_CKO )
| ( ( AT91_SSC_CKS_PIN ) & AT91_SSC_CKS ) ;
tfmr = ( ( AT91_SSC_FSEDGE_POSITIVE ) & AT91_SSC_FSEDGE )
| ( ( 0 < < 23 ) & AT91_SSC_FSDEN )
| ( ( AT91_SSC_FSOS_NONE ) & AT91_SSC_FSOS )
| ( ( 0 < < 16 ) & AT91_SSC_FSLEN )
| ( ( 0 < < 8 ) & AT91_SSC_DATNB )
| ( ( 1 < < 7 ) & AT91_SSC_MSBF )
| ( ( 0 < < 5 ) & AT91_SSC_DATDEF )
| ( ( ( bits - 1 ) < < 0 ) & AT91_SSC_DATALEN ) ;
break ;
2007-04-16 17:18:52 +02:00
case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS :
/*
* DSP / PCM Mode A format , SSC provides BCLK and LRC clocks .
*
* The SSC transmit and receive clocks are generated from the
* MCK divider , and the BCLK signal is output on the SSC TK line .
*/
rcmr = ( ( ssc_p - > rcmr_period < < 24 ) & AT91_SSC_PERIOD )
| ( ( 1 < < 16 ) & AT91_SSC_STTDLY )
| ( ( AT91_SSC_START_RISING_RF ) & AT91_SSC_START )
| ( ( AT91_SSC_CK_RISING ) & AT91_SSC_CKI )
| ( ( AT91_SSC_CKO_NONE ) & AT91_SSC_CKO )
| ( ( AT91_SSC_CKS_DIV ) & AT91_SSC_CKS ) ;
rfmr = ( ( AT91_SSC_FSEDGE_POSITIVE ) & AT91_SSC_FSEDGE )
| ( ( AT91_SSC_FSOS_POSITIVE ) & AT91_SSC_FSOS )
| ( ( 0 < < 16 ) & AT91_SSC_FSLEN )
| ( ( ( channels - 1 ) < < 8 ) & AT91_SSC_DATNB )
| ( ( 1 < < 7 ) & AT91_SSC_MSBF )
| ( ( 0 < < 5 ) & AT91_SSC_LOOP )
| ( ( ( bits - 1 ) < < 0 ) & AT91_SSC_DATALEN ) ;
tcmr = ( ( ssc_p - > tcmr_period < < 24 ) & AT91_SSC_PERIOD )
| ( ( 1 < < 16 ) & AT91_SSC_STTDLY )
| ( ( AT91_SSC_START_RISING_RF ) & AT91_SSC_START )
| ( ( AT91_SSC_CK_RISING ) & AT91_SSC_CKI )
| ( ( AT91_SSC_CKO_CONTINUOUS ) & AT91_SSC_CKO )
| ( ( AT91_SSC_CKS_DIV ) & AT91_SSC_CKS ) ;
tfmr = ( ( AT91_SSC_FSEDGE_POSITIVE ) & AT91_SSC_FSEDGE )
| ( ( 0 < < 23 ) & AT91_SSC_FSDEN )
| ( ( AT91_SSC_FSOS_POSITIVE ) & AT91_SSC_FSOS )
| ( ( 0 < < 16 ) & AT91_SSC_FSLEN )
| ( ( ( channels - 1 ) < < 8 ) & AT91_SSC_DATNB )
| ( ( 1 < < 7 ) & AT91_SSC_MSBF )
| ( ( 0 < < 5 ) & AT91_SSC_DATDEF )
| ( ( ( bits - 1 ) < < 0 ) & AT91_SSC_DATALEN ) ;
break ;
case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM :
2007-02-02 17:18:38 +01:00
default :
2007-04-16 17:18:52 +02:00
printk ( KERN_WARNING " at91-ssc: unsupported DAI format 0x%x. \n " ,
2007-02-02 17:18:38 +01:00
ssc_p - > daifmt ) ;
return - EINVAL ;
break ;
}
DBG ( " RCMR=%08x RFMR=%08x TCMR=%08x TFMR=%08x \n " , rcmr , rfmr , tcmr , tfmr ) ;
if ( ! ssc_p - > initialized ) {
2006-10-06 18:40:25 +02:00
/* Enable PMC peripheral clock for this SSC */
2006-11-24 15:49:39 +01:00
DBG ( " Starting pid %d clock \n " , ssc_p - > ssc . pid ) ;
at91_sys_write ( AT91_PMC_PCER , 1 < < ssc_p - > ssc . pid ) ;
2006-10-06 18:40:25 +02:00
2007-02-02 17:18:38 +01:00
/* Reset the SSC and its PDC registers */
2006-11-24 15:49:39 +01:00
at91_ssc_write ( ssc_p - > ssc . base + AT91_SSC_CR , AT91_SSC_SWRST ) ;
2006-10-06 18:40:25 +02:00
2007-02-12 14:06:22 +01:00
at91_ssc_write ( ssc_p - > ssc . base + ATMEL_PDC_RPR , 0 ) ;
at91_ssc_write ( ssc_p - > ssc . base + ATMEL_PDC_RCR , 0 ) ;
at91_ssc_write ( ssc_p - > ssc . base + ATMEL_PDC_RNPR , 0 ) ;
at91_ssc_write ( ssc_p - > ssc . base + ATMEL_PDC_RNCR , 0 ) ;
at91_ssc_write ( ssc_p - > ssc . base + ATMEL_PDC_TPR , 0 ) ;
at91_ssc_write ( ssc_p - > ssc . base + ATMEL_PDC_TCR , 0 ) ;
at91_ssc_write ( ssc_p - > ssc . base + ATMEL_PDC_TNPR , 0 ) ;
at91_ssc_write ( ssc_p - > ssc . base + ATMEL_PDC_TNCR , 0 ) ;
2006-10-06 18:40:25 +02:00
2007-04-16 17:18:52 +02:00
if ( ( ret = request_irq ( ssc_p - > ssc . pid , at91_ssc_interrupt ,
2006-10-06 18:40:25 +02:00
0 , ssc_p - > name , ssc_p ) ) < 0 ) {
2007-04-16 17:18:52 +02:00
printk ( KERN_WARNING " at91-ssc: request_irq failure \n " ) ;
2007-02-02 17:18:38 +01:00
DBG ( " Stopping pid %d clock \n " , ssc_p - > ssc . pid ) ;
2008-05-08 14:04:08 +02:00
at91_sys_write ( AT91_PMC_PCDR , 1 < < ssc_p - > ssc . pid ) ;
2006-10-06 18:40:25 +02:00
return ret ;
}
ssc_p - > initialized = 1 ;
}
2007-02-02 17:18:38 +01:00
/* set SSC clock mode register */
at91_ssc_write ( ssc_p - > ssc . base + AT91_SSC_CMR , ssc_p - > cmr_div ) ;
/* set receive clock mode and format */
at91_ssc_write ( ssc_p - > ssc . base + AT91_SSC_RCMR , rcmr ) ;
at91_ssc_write ( ssc_p - > ssc . base + AT91_SSC_RFMR , rfmr ) ;
/* set transmit clock mode and format */
at91_ssc_write ( ssc_p - > ssc . base + AT91_SSC_TCMR , tcmr ) ;
at91_ssc_write ( ssc_p - > ssc . base + AT91_SSC_TFMR , tfmr ) ;
2006-10-06 18:40:25 +02:00
2007-02-02 17:18:38 +01:00
DBG ( " hw_params: SSC initialized \n " ) ;
2006-10-06 18:40:25 +02:00
return 0 ;
}
2007-04-16 17:18:52 +02:00
static int at91_ssc_prepare ( struct snd_pcm_substream * substream )
2006-10-06 18:40:25 +02:00
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
2007-02-02 17:18:38 +01:00
struct at91_ssc_info * ssc_p = & ssc_info [ rtd - > dai - > cpu_dai - > id ] ;
struct at91_pcm_dma_params * dma_params ;
int dir ;
dir = substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1 ;
dma_params = ssc_p - > dma_params [ dir ] ;
2006-10-06 18:40:25 +02:00
2006-11-24 15:49:39 +01:00
at91_ssc_write ( dma_params - > ssc_base + AT91_SSC_CR ,
dma_params - > mask - > ssc_enable ) ;
2006-10-06 18:40:25 +02:00
2007-02-02 17:18:38 +01:00
DBG ( " %s enabled SSC_SR=0x%08lx \n " , dir ? " receive " : " transmit " ,
at91_ssc_read ( dma_params - > ssc_base + AT91_SSC_SR ) ) ;
2006-10-06 18:40:25 +02:00
return 0 ;
}
2007-02-02 17:18:38 +01:00
# ifdef CONFIG_PM
2007-04-16 17:18:52 +02:00
static int at91_ssc_suspend ( struct platform_device * pdev ,
2008-07-07 16:07:37 +01:00
struct snd_soc_dai * cpu_dai )
2007-02-02 17:18:38 +01:00
{
struct at91_ssc_info * ssc_p ;
if ( ! cpu_dai - > active )
return 0 ;
ssc_p = & ssc_info [ cpu_dai - > id ] ;
/* Save the status register before disabling transmit and receive. */
ssc_p - > ssc_state . ssc_sr = at91_ssc_read ( ssc_p - > ssc . base + AT91_SSC_SR ) ;
at91_ssc_write ( ssc_p - > ssc . base + AT91_SSC_CR ,
AT91_SSC_TXDIS | AT91_SSC_RXDIS ) ;
/* Save the current interrupt mask, then disable unmasked interrupts. */
ssc_p - > ssc_state . ssc_imr = at91_ssc_read ( ssc_p - > ssc . base + AT91_SSC_IMR ) ;
at91_ssc_write ( ssc_p - > ssc . base + AT91_SSC_IDR , ssc_p - > ssc_state . ssc_imr ) ;
ssc_p - > ssc_state . ssc_cmr = at91_ssc_read ( ssc_p - > ssc . base + AT91_SSC_CMR ) ;
ssc_p - > ssc_state . ssc_rcmr = at91_ssc_read ( ssc_p - > ssc . base + AT91_SSC_RCMR ) ;
ssc_p - > ssc_state . ssc_rfmr = at91_ssc_read ( ssc_p - > ssc . base + AT91_SSC_RFMR ) ;
ssc_p - > ssc_state . ssc_tcmr = at91_ssc_read ( ssc_p - > ssc . base + AT91_SSC_TCMR ) ;
ssc_p - > ssc_state . ssc_tfmr = at91_ssc_read ( ssc_p - > ssc . base + AT91_SSC_TFMR ) ;
return 0 ;
}
2007-04-16 17:18:52 +02:00
static int at91_ssc_resume ( struct platform_device * pdev ,
2008-07-07 16:07:37 +01:00
struct snd_soc_dai * cpu_dai )
2007-02-02 17:18:38 +01:00
{
struct at91_ssc_info * ssc_p ;
if ( ! cpu_dai - > active )
return 0 ;
ssc_p = & ssc_info [ cpu_dai - > id ] ;
at91_ssc_write ( ssc_p - > ssc . base + AT91_SSC_TFMR , ssc_p - > ssc_state . ssc_tfmr ) ;
at91_ssc_write ( ssc_p - > ssc . base + AT91_SSC_TCMR , ssc_p - > ssc_state . ssc_tcmr ) ;
at91_ssc_write ( ssc_p - > ssc . base + AT91_SSC_RFMR , ssc_p - > ssc_state . ssc_rfmr ) ;
at91_ssc_write ( ssc_p - > ssc . base + AT91_SSC_RCMR , ssc_p - > ssc_state . ssc_rcmr ) ;
at91_ssc_write ( ssc_p - > ssc . base + AT91_SSC_CMR , ssc_p - > ssc_state . ssc_cmr ) ;
at91_ssc_write ( ssc_p - > ssc . base + AT91_SSC_IER , ssc_p - > ssc_state . ssc_imr ) ;
at91_ssc_write ( ssc_p - > ssc . base + AT91_SSC_CR ,
( ( ssc_p - > ssc_state . ssc_sr & AT91_SSC_RXENA ) ? AT91_SSC_RXEN : 0 ) |
( ( ssc_p - > ssc_state . ssc_sr & AT91_SSC_TXENA ) ? AT91_SSC_TXEN : 0 ) ) ;
return 0 ;
}
# else
2007-04-16 17:18:52 +02:00
# define at91_ssc_suspend NULL
# define at91_ssc_resume NULL
2007-02-02 17:18:38 +01:00
# endif
2007-04-16 17:18:52 +02:00
# define AT91_SSC_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
2007-02-02 17:18:38 +01:00
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 )
2007-04-16 17:18:52 +02:00
# define AT91_SSC_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE )
2008-07-07 16:07:37 +01:00
struct snd_soc_dai at91_ssc_dai [ NUM_SSC_DEVICES ] = {
2007-04-16 17:18:52 +02:00
{ . name = " at91-ssc0 " ,
2006-10-06 18:40:25 +02:00
. id = 0 ,
2007-04-16 17:18:52 +02:00
. type = SND_SOC_DAI_PCM ,
. suspend = at91_ssc_suspend ,
. resume = at91_ssc_resume ,
2006-10-06 18:40:25 +02:00
. playback = {
. channels_min = 1 ,
2007-02-02 17:18:38 +01:00
. channels_max = 2 ,
2007-04-16 17:18:52 +02:00
. rates = AT91_SSC_RATES ,
. formats = AT91_SSC_FORMATS , } ,
2006-10-06 18:40:25 +02:00
. capture = {
. channels_min = 1 ,
2007-02-02 17:18:38 +01:00
. channels_max = 2 ,
2007-04-16 17:18:52 +02:00
. rates = AT91_SSC_RATES ,
. formats = AT91_SSC_FORMATS , } ,
2006-10-06 18:40:25 +02:00
. ops = {
2007-04-16 17:18:52 +02:00
. startup = at91_ssc_startup ,
. shutdown = at91_ssc_shutdown ,
. prepare = at91_ssc_prepare ,
. hw_params = at91_ssc_hw_params , } ,
2007-02-02 17:18:38 +01:00
. dai_ops = {
2007-04-16 17:18:52 +02:00
. set_sysclk = at91_ssc_set_dai_sysclk ,
. set_fmt = at91_ssc_set_dai_fmt ,
. set_clkdiv = at91_ssc_set_dai_clkdiv , } ,
2006-11-24 15:49:39 +01:00
. private_data = & ssc_info [ 0 ] . ssc ,
2006-10-06 18:40:25 +02:00
} ,
2006-11-24 15:49:39 +01:00
# if NUM_SSC_DEVICES == 3
2007-04-16 17:18:52 +02:00
{ . name = " at91-ssc1 " ,
2006-10-06 18:40:25 +02:00
. id = 1 ,
2007-04-16 17:18:52 +02:00
. type = SND_SOC_DAI_PCM ,
. suspend = at91_ssc_suspend ,
. resume = at91_ssc_resume ,
2006-10-06 18:40:25 +02:00
. playback = {
. channels_min = 1 ,
2007-02-02 17:18:38 +01:00
. channels_max = 2 ,
2007-04-16 17:18:52 +02:00
. rates = AT91_SSC_RATES ,
. formats = AT91_SSC_FORMATS , } ,
2006-10-06 18:40:25 +02:00
. capture = {
. channels_min = 1 ,
2007-02-02 17:18:38 +01:00
. channels_max = 2 ,
2007-04-16 17:18:52 +02:00
. rates = AT91_SSC_RATES ,
. formats = AT91_SSC_FORMATS , } ,
2006-10-06 18:40:25 +02:00
. ops = {
2007-04-16 17:18:52 +02:00
. startup = at91_ssc_startup ,
. shutdown = at91_ssc_shutdown ,
. prepare = at91_ssc_prepare ,
. hw_params = at91_ssc_hw_params , } ,
2007-02-02 17:18:38 +01:00
. dai_ops = {
2007-04-16 17:18:52 +02:00
. set_sysclk = at91_ssc_set_dai_sysclk ,
. set_fmt = at91_ssc_set_dai_fmt ,
. set_clkdiv = at91_ssc_set_dai_clkdiv , } ,
2006-11-24 15:49:39 +01:00
. private_data = & ssc_info [ 1 ] . ssc ,
2006-10-06 18:40:25 +02:00
} ,
2007-04-16 17:18:52 +02:00
{ . name = " at91-ssc2 " ,
2006-10-06 18:40:25 +02:00
. id = 2 ,
2007-04-16 17:18:52 +02:00
. type = SND_SOC_DAI_PCM ,
. suspend = at91_ssc_suspend ,
. resume = at91_ssc_resume ,
2006-10-06 18:40:25 +02:00
. playback = {
. channels_min = 1 ,
2007-02-02 17:18:38 +01:00
. channels_max = 2 ,
2007-04-16 17:18:52 +02:00
. rates = AT91_SSC_RATES ,
. formats = AT91_SSC_FORMATS , } ,
2006-10-06 18:40:25 +02:00
. capture = {
. channels_min = 1 ,
2007-02-02 17:18:38 +01:00
. channels_max = 2 ,
2007-04-16 17:18:52 +02:00
. rates = AT91_SSC_RATES ,
. formats = AT91_SSC_FORMATS , } ,
2006-10-06 18:40:25 +02:00
. ops = {
2007-04-16 17:18:52 +02:00
. startup = at91_ssc_startup ,
. shutdown = at91_ssc_shutdown ,
. prepare = at91_ssc_prepare ,
. hw_params = at91_ssc_hw_params , } ,
2007-02-02 17:18:38 +01:00
. dai_ops = {
2007-04-16 17:18:52 +02:00
. set_sysclk = at91_ssc_set_dai_sysclk ,
. set_fmt = at91_ssc_set_dai_fmt ,
. set_clkdiv = at91_ssc_set_dai_clkdiv , } ,
2006-11-24 15:49:39 +01:00
. private_data = & ssc_info [ 2 ] . ssc ,
2006-10-06 18:40:25 +02:00
} ,
2006-11-24 15:49:39 +01:00
# endif
2006-10-06 18:40:25 +02:00
} ;
2007-04-16 17:18:52 +02:00
EXPORT_SYMBOL_GPL ( at91_ssc_dai ) ;
2006-10-06 18:40:25 +02:00
/* Module information */
MODULE_AUTHOR ( " Frank Mandarino, fmandarino@endrelia.com, www.endrelia.com " ) ;
2007-04-16 17:18:52 +02:00
MODULE_DESCRIPTION ( " AT91 SSC ASoC Interface " ) ;
2006-10-06 18:40:25 +02:00
MODULE_LICENSE ( " GPL " ) ;