forked from Minki/linux
6335d05548
Considering the fact that most cpu_dai or codec_dai are using a same 'snd_soc_dai_ops' for several similar interfaces, 'ops' would be better made a pointer instead, to make sharing easier and code a bit cleaner. The patch below is rather preliminary since the asoc tree is being actively developed, and this touches almost every piece of code, (and possibly many others in development need to be changed as well). Building of all codecs are OK, yet to every SoC, I didn't test that. Signed-off-by: Eric Miao <eric.miao@marvell.com> Acked-by: Timur Tabi <timur@freescale.com> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
782 lines
20 KiB
C
782 lines
20 KiB
C
/*
|
|
* atmel_ssc_dai.c -- ALSA SoC ATMEL SSC Audio Layer Platform driver
|
|
*
|
|
* Copyright (C) 2005 SAN People
|
|
* Copyright (C) 2008 Atmel
|
|
*
|
|
* Author: Sedji Gaouaou <sedji.gaouaou@atmel.com>
|
|
* ATMEL CORP.
|
|
*
|
|
* Based on at91-ssc.c by
|
|
* Frank Mandarino <fmandarino@endrelia.com>
|
|
* Based on pxa2xx Platform drivers by
|
|
* Liam Girdwood <lrg@slimlogic.co.uk>
|
|
*
|
|
* 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.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/device.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/atmel_pdc.h>
|
|
|
|
#include <linux/atmel-ssc.h>
|
|
#include <sound/core.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/initval.h>
|
|
#include <sound/soc.h>
|
|
|
|
#include <mach/hardware.h>
|
|
|
|
#include "atmel-pcm.h"
|
|
#include "atmel_ssc_dai.h"
|
|
|
|
|
|
#if defined(CONFIG_ARCH_AT91SAM9260) || defined(CONFIG_ARCH_AT91SAM9G20)
|
|
#define NUM_SSC_DEVICES 1
|
|
#else
|
|
#define NUM_SSC_DEVICES 3
|
|
#endif
|
|
|
|
/*
|
|
* SSC PDC registers required by the PCM DMA engine.
|
|
*/
|
|
static struct atmel_pdc_regs pdc_tx_reg = {
|
|
.xpr = ATMEL_PDC_TPR,
|
|
.xcr = ATMEL_PDC_TCR,
|
|
.xnpr = ATMEL_PDC_TNPR,
|
|
.xncr = ATMEL_PDC_TNCR,
|
|
};
|
|
|
|
static struct atmel_pdc_regs pdc_rx_reg = {
|
|
.xpr = ATMEL_PDC_RPR,
|
|
.xcr = ATMEL_PDC_RCR,
|
|
.xnpr = ATMEL_PDC_RNPR,
|
|
.xncr = ATMEL_PDC_RNCR,
|
|
};
|
|
|
|
/*
|
|
* SSC & PDC status bits for transmit and receive.
|
|
*/
|
|
static struct atmel_ssc_mask ssc_tx_mask = {
|
|
.ssc_enable = SSC_BIT(CR_TXEN),
|
|
.ssc_disable = SSC_BIT(CR_TXDIS),
|
|
.ssc_endx = SSC_BIT(SR_ENDTX),
|
|
.ssc_endbuf = SSC_BIT(SR_TXBUFE),
|
|
.pdc_enable = ATMEL_PDC_TXTEN,
|
|
.pdc_disable = ATMEL_PDC_TXTDIS,
|
|
};
|
|
|
|
static struct atmel_ssc_mask ssc_rx_mask = {
|
|
.ssc_enable = SSC_BIT(CR_RXEN),
|
|
.ssc_disable = SSC_BIT(CR_RXDIS),
|
|
.ssc_endx = SSC_BIT(SR_ENDRX),
|
|
.ssc_endbuf = SSC_BIT(SR_RXBUFF),
|
|
.pdc_enable = ATMEL_PDC_RXTEN,
|
|
.pdc_disable = ATMEL_PDC_RXTDIS,
|
|
};
|
|
|
|
|
|
/*
|
|
* DMA parameters.
|
|
*/
|
|
static struct atmel_pcm_dma_params ssc_dma_params[NUM_SSC_DEVICES][2] = {
|
|
{{
|
|
.name = "SSC0 PCM out",
|
|
.pdc = &pdc_tx_reg,
|
|
.mask = &ssc_tx_mask,
|
|
},
|
|
{
|
|
.name = "SSC0 PCM in",
|
|
.pdc = &pdc_rx_reg,
|
|
.mask = &ssc_rx_mask,
|
|
} },
|
|
#if NUM_SSC_DEVICES == 3
|
|
{{
|
|
.name = "SSC1 PCM out",
|
|
.pdc = &pdc_tx_reg,
|
|
.mask = &ssc_tx_mask,
|
|
},
|
|
{
|
|
.name = "SSC1 PCM in",
|
|
.pdc = &pdc_rx_reg,
|
|
.mask = &ssc_rx_mask,
|
|
} },
|
|
{{
|
|
.name = "SSC2 PCM out",
|
|
.pdc = &pdc_tx_reg,
|
|
.mask = &ssc_tx_mask,
|
|
},
|
|
{
|
|
.name = "SSC2 PCM in",
|
|
.pdc = &pdc_rx_reg,
|
|
.mask = &ssc_rx_mask,
|
|
} },
|
|
#endif
|
|
};
|
|
|
|
|
|
static struct atmel_ssc_info ssc_info[NUM_SSC_DEVICES] = {
|
|
{
|
|
.name = "ssc0",
|
|
.lock = __SPIN_LOCK_UNLOCKED(ssc_info[0].lock),
|
|
.dir_mask = SSC_DIR_MASK_UNUSED,
|
|
.initialized = 0,
|
|
},
|
|
#if NUM_SSC_DEVICES == 3
|
|
{
|
|
.name = "ssc1",
|
|
.lock = __SPIN_LOCK_UNLOCKED(ssc_info[1].lock),
|
|
.dir_mask = SSC_DIR_MASK_UNUSED,
|
|
.initialized = 0,
|
|
},
|
|
{
|
|
.name = "ssc2",
|
|
.lock = __SPIN_LOCK_UNLOCKED(ssc_info[2].lock),
|
|
.dir_mask = SSC_DIR_MASK_UNUSED,
|
|
.initialized = 0,
|
|
},
|
|
#endif
|
|
};
|
|
|
|
|
|
/*
|
|
* SSC interrupt handler. Passes PDC interrupts to the DMA
|
|
* interrupt handler in the PCM driver.
|
|
*/
|
|
static irqreturn_t atmel_ssc_interrupt(int irq, void *dev_id)
|
|
{
|
|
struct atmel_ssc_info *ssc_p = dev_id;
|
|
struct atmel_pcm_dma_params *dma_params;
|
|
u32 ssc_sr;
|
|
u32 ssc_substream_mask;
|
|
int i;
|
|
|
|
ssc_sr = (unsigned long)ssc_readl(ssc_p->ssc->regs, SR)
|
|
& (unsigned long)ssc_readl(ssc_p->ssc->regs, IMR);
|
|
|
|
/*
|
|
* 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_substream_mask = (dma_params->mask->ssc_endx |
|
|
dma_params->mask->ssc_endbuf);
|
|
if (ssc_sr & ssc_substream_mask) {
|
|
dma_params->dma_intr_handler(ssc_sr,
|
|
dma_params->
|
|
substream);
|
|
}
|
|
}
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------------------------------*\
|
|
* DAI functions
|
|
\*-------------------------------------------------------------------------*/
|
|
/*
|
|
* Startup. Only that one substream allowed in each direction.
|
|
*/
|
|
static int atmel_ssc_startup(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream);
|
|
struct atmel_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id];
|
|
int dir_mask;
|
|
|
|
pr_debug("atmel_ssc_startup: SSC_SR=0x%u\n",
|
|
ssc_readl(ssc_p->ssc->regs, SR));
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
dir_mask = SSC_DIR_MASK_PLAYBACK;
|
|
else
|
|
dir_mask = SSC_DIR_MASK_CAPTURE;
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Shutdown. Clear DMA parameters and shutdown the SSC if there
|
|
* are no other substreams open.
|
|
*/
|
|
static void atmel_ssc_shutdown(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream);
|
|
struct atmel_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id];
|
|
struct atmel_pcm_dma_params *dma_params;
|
|
int dir, dir_mask;
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
dir = 0;
|
|
else
|
|
dir = 1;
|
|
|
|
dma_params = ssc_p->dma_params[dir];
|
|
|
|
if (dma_params != NULL) {
|
|
ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_disable);
|
|
pr_debug("atmel_ssc_shutdown: %s disabled SSC_SR=0x%08x\n",
|
|
(dir ? "receive" : "transmit"),
|
|
ssc_readl(ssc_p->ssc->regs, SR));
|
|
|
|
dma_params->ssc = NULL;
|
|
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) {
|
|
if (ssc_p->initialized) {
|
|
/* Shutdown the SSC clock. */
|
|
pr_debug("atmel_ssc_dau: Stopping clock\n");
|
|
clk_disable(ssc_p->ssc->clk);
|
|
|
|
free_irq(ssc_p->ssc->irq, ssc_p);
|
|
ssc_p->initialized = 0;
|
|
}
|
|
|
|
/* Reset the SSC */
|
|
ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_SWRST));
|
|
/* Clear the SSC dividers */
|
|
ssc_p->cmr_div = ssc_p->tcmr_period = ssc_p->rcmr_period = 0;
|
|
}
|
|
spin_unlock_irq(&ssc_p->lock);
|
|
}
|
|
|
|
|
|
/*
|
|
* Record the DAI format for use in hw_params().
|
|
*/
|
|
static int atmel_ssc_set_dai_fmt(struct snd_soc_dai *cpu_dai,
|
|
unsigned int fmt)
|
|
{
|
|
struct atmel_ssc_info *ssc_p = &ssc_info[cpu_dai->id];
|
|
|
|
ssc_p->daifmt = fmt;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Record SSC clock dividers for use in hw_params().
|
|
*/
|
|
static int atmel_ssc_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
|
|
int div_id, int div)
|
|
{
|
|
struct atmel_ssc_info *ssc_p = &ssc_info[cpu_dai->id];
|
|
|
|
switch (div_id) {
|
|
case ATMEL_SSC_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 ATMEL_SSC_TCMR_PERIOD:
|
|
ssc_p->tcmr_period = div;
|
|
break;
|
|
|
|
case ATMEL_SSC_RCMR_PERIOD:
|
|
ssc_p->rcmr_period = div;
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Configure the SSC.
|
|
*/
|
|
static int atmel_ssc_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream);
|
|
int id = rtd->dai->cpu_dai->id;
|
|
struct atmel_ssc_info *ssc_p = &ssc_info[id];
|
|
struct atmel_pcm_dma_params *dma_params;
|
|
int dir, channels, bits;
|
|
u32 tfmr, rfmr, tcmr, rcmr;
|
|
int start_event;
|
|
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.
|
|
*/
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
dir = 0;
|
|
else
|
|
dir = 1;
|
|
|
|
dma_params = &ssc_dma_params[id][dir];
|
|
dma_params->ssc = ssc_p->ssc;
|
|
dma_params->substream = substream;
|
|
|
|
ssc_p->dma_params[dir] = dma_params;
|
|
|
|
/*
|
|
* 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;
|
|
|
|
channels = params_channels(params);
|
|
|
|
/*
|
|
* 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 "atmel_ssc_dai: unsupported PCM format");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* The SSC only supports up to 16-bit samples in I2S format, due
|
|
* to the size of the Frame Mode Register FSLEN field.
|
|
*/
|
|
if ((ssc_p->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S
|
|
&& bits > 16) {
|
|
printk(KERN_WARNING
|
|
"atmel_ssc_dai: sample size %d"
|
|
"is too large for I2S\n", bits);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Compute SSC register settings.
|
|
*/
|
|
switch (ssc_p->daifmt
|
|
& (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_MASTER_MASK)) {
|
|
|
|
case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS:
|
|
/*
|
|
* I2S 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_BF(RCMR_PERIOD, ssc_p->rcmr_period)
|
|
| SSC_BF(RCMR_STTDLY, START_DELAY)
|
|
| SSC_BF(RCMR_START, SSC_START_FALLING_RF)
|
|
| SSC_BF(RCMR_CKI, SSC_CKI_RISING)
|
|
| SSC_BF(RCMR_CKO, SSC_CKO_NONE)
|
|
| SSC_BF(RCMR_CKS, SSC_CKS_DIV);
|
|
|
|
rfmr = SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
|
|
| SSC_BF(RFMR_FSOS, SSC_FSOS_NEGATIVE)
|
|
| SSC_BF(RFMR_FSLEN, (bits - 1))
|
|
| SSC_BF(RFMR_DATNB, (channels - 1))
|
|
| SSC_BIT(RFMR_MSBF)
|
|
| SSC_BF(RFMR_LOOP, 0)
|
|
| SSC_BF(RFMR_DATLEN, (bits - 1));
|
|
|
|
tcmr = SSC_BF(TCMR_PERIOD, ssc_p->tcmr_period)
|
|
| SSC_BF(TCMR_STTDLY, START_DELAY)
|
|
| SSC_BF(TCMR_START, SSC_START_FALLING_RF)
|
|
| SSC_BF(TCMR_CKI, SSC_CKI_FALLING)
|
|
| SSC_BF(TCMR_CKO, SSC_CKO_CONTINUOUS)
|
|
| SSC_BF(TCMR_CKS, SSC_CKS_DIV);
|
|
|
|
tfmr = SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
|
|
| SSC_BF(TFMR_FSDEN, 0)
|
|
| SSC_BF(TFMR_FSOS, SSC_FSOS_NEGATIVE)
|
|
| SSC_BF(TFMR_FSLEN, (bits - 1))
|
|
| SSC_BF(TFMR_DATNB, (channels - 1))
|
|
| SSC_BIT(TFMR_MSBF)
|
|
| SSC_BF(TFMR_DATDEF, 0)
|
|
| SSC_BF(TFMR_DATLEN, (bits - 1));
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM:
|
|
/*
|
|
* I2S format, CODEC supplies BCLK and LRC clocks.
|
|
*
|
|
* 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)
|
|
? SSC_START_FALLING_RF
|
|
: SSC_START_EDGE_RF);
|
|
|
|
rcmr = SSC_BF(RCMR_PERIOD, 0)
|
|
| SSC_BF(RCMR_STTDLY, START_DELAY)
|
|
| SSC_BF(RCMR_START, start_event)
|
|
| SSC_BF(RCMR_CKI, SSC_CKI_RISING)
|
|
| SSC_BF(RCMR_CKO, SSC_CKO_NONE)
|
|
| SSC_BF(RCMR_CKS, SSC_CKS_CLOCK);
|
|
|
|
rfmr = SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
|
|
| SSC_BF(RFMR_FSOS, SSC_FSOS_NONE)
|
|
| SSC_BF(RFMR_FSLEN, 0)
|
|
| SSC_BF(RFMR_DATNB, 0)
|
|
| SSC_BIT(RFMR_MSBF)
|
|
| SSC_BF(RFMR_LOOP, 0)
|
|
| SSC_BF(RFMR_DATLEN, (bits - 1));
|
|
|
|
tcmr = SSC_BF(TCMR_PERIOD, 0)
|
|
| SSC_BF(TCMR_STTDLY, START_DELAY)
|
|
| SSC_BF(TCMR_START, start_event)
|
|
| SSC_BF(TCMR_CKI, SSC_CKI_FALLING)
|
|
| SSC_BF(TCMR_CKO, SSC_CKO_NONE)
|
|
| SSC_BF(TCMR_CKS, SSC_CKS_PIN);
|
|
|
|
tfmr = SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
|
|
| SSC_BF(TFMR_FSDEN, 0)
|
|
| SSC_BF(TFMR_FSOS, SSC_FSOS_NONE)
|
|
| SSC_BF(TFMR_FSLEN, 0)
|
|
| SSC_BF(TFMR_DATNB, 0)
|
|
| SSC_BIT(TFMR_MSBF)
|
|
| SSC_BF(TFMR_DATDEF, 0)
|
|
| SSC_BF(TFMR_DATLEN, (bits - 1));
|
|
break;
|
|
|
|
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_BF(RCMR_PERIOD, ssc_p->rcmr_period)
|
|
| SSC_BF(RCMR_STTDLY, 1)
|
|
| SSC_BF(RCMR_START, SSC_START_RISING_RF)
|
|
| SSC_BF(RCMR_CKI, SSC_CKI_RISING)
|
|
| SSC_BF(RCMR_CKO, SSC_CKO_NONE)
|
|
| SSC_BF(RCMR_CKS, SSC_CKS_DIV);
|
|
|
|
rfmr = SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
|
|
| SSC_BF(RFMR_FSOS, SSC_FSOS_POSITIVE)
|
|
| SSC_BF(RFMR_FSLEN, 0)
|
|
| SSC_BF(RFMR_DATNB, (channels - 1))
|
|
| SSC_BIT(RFMR_MSBF)
|
|
| SSC_BF(RFMR_LOOP, 0)
|
|
| SSC_BF(RFMR_DATLEN, (bits - 1));
|
|
|
|
tcmr = SSC_BF(TCMR_PERIOD, ssc_p->tcmr_period)
|
|
| SSC_BF(TCMR_STTDLY, 1)
|
|
| SSC_BF(TCMR_START, SSC_START_RISING_RF)
|
|
| SSC_BF(TCMR_CKI, SSC_CKI_RISING)
|
|
| SSC_BF(TCMR_CKO, SSC_CKO_CONTINUOUS)
|
|
| SSC_BF(TCMR_CKS, SSC_CKS_DIV);
|
|
|
|
tfmr = SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
|
|
| SSC_BF(TFMR_FSDEN, 0)
|
|
| SSC_BF(TFMR_FSOS, SSC_FSOS_POSITIVE)
|
|
| SSC_BF(TFMR_FSLEN, 0)
|
|
| SSC_BF(TFMR_DATNB, (channels - 1))
|
|
| SSC_BIT(TFMR_MSBF)
|
|
| SSC_BF(TFMR_DATDEF, 0)
|
|
| SSC_BF(TFMR_DATLEN, (bits - 1));
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM:
|
|
default:
|
|
printk(KERN_WARNING "atmel_ssc_dai: unsupported DAI format 0x%x\n",
|
|
ssc_p->daifmt);
|
|
return -EINVAL;
|
|
break;
|
|
}
|
|
pr_debug("atmel_ssc_hw_params: "
|
|
"RCMR=%08x RFMR=%08x TCMR=%08x TFMR=%08x\n",
|
|
rcmr, rfmr, tcmr, tfmr);
|
|
|
|
if (!ssc_p->initialized) {
|
|
|
|
/* Enable PMC peripheral clock for this SSC */
|
|
pr_debug("atmel_ssc_dai: Starting clock\n");
|
|
clk_enable(ssc_p->ssc->clk);
|
|
|
|
/* Reset the SSC and its PDC registers */
|
|
ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_SWRST));
|
|
|
|
ssc_writel(ssc_p->ssc->regs, PDC_RPR, 0);
|
|
ssc_writel(ssc_p->ssc->regs, PDC_RCR, 0);
|
|
ssc_writel(ssc_p->ssc->regs, PDC_RNPR, 0);
|
|
ssc_writel(ssc_p->ssc->regs, PDC_RNCR, 0);
|
|
|
|
ssc_writel(ssc_p->ssc->regs, PDC_TPR, 0);
|
|
ssc_writel(ssc_p->ssc->regs, PDC_TCR, 0);
|
|
ssc_writel(ssc_p->ssc->regs, PDC_TNPR, 0);
|
|
ssc_writel(ssc_p->ssc->regs, PDC_TNCR, 0);
|
|
|
|
ret = request_irq(ssc_p->ssc->irq, atmel_ssc_interrupt, 0,
|
|
ssc_p->name, ssc_p);
|
|
if (ret < 0) {
|
|
printk(KERN_WARNING
|
|
"atmel_ssc_dai: request_irq failure\n");
|
|
pr_debug("Atmel_ssc_dai: Stoping clock\n");
|
|
clk_disable(ssc_p->ssc->clk);
|
|
return ret;
|
|
}
|
|
|
|
ssc_p->initialized = 1;
|
|
}
|
|
|
|
/* set SSC clock mode register */
|
|
ssc_writel(ssc_p->ssc->regs, CMR, ssc_p->cmr_div);
|
|
|
|
/* set receive clock mode and format */
|
|
ssc_writel(ssc_p->ssc->regs, RCMR, rcmr);
|
|
ssc_writel(ssc_p->ssc->regs, RFMR, rfmr);
|
|
|
|
/* set transmit clock mode and format */
|
|
ssc_writel(ssc_p->ssc->regs, TCMR, tcmr);
|
|
ssc_writel(ssc_p->ssc->regs, TFMR, tfmr);
|
|
|
|
pr_debug("atmel_ssc_dai,hw_params: SSC initialized\n");
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int atmel_ssc_prepare(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream);
|
|
struct atmel_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id];
|
|
struct atmel_pcm_dma_params *dma_params;
|
|
int dir;
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
dir = 0;
|
|
else
|
|
dir = 1;
|
|
|
|
dma_params = ssc_p->dma_params[dir];
|
|
|
|
ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_enable);
|
|
|
|
pr_debug("%s enabled SSC_SR=0x%08x\n",
|
|
dir ? "receive" : "transmit",
|
|
ssc_readl(ssc_p->ssc->regs, SR));
|
|
return 0;
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_PM
|
|
static int atmel_ssc_suspend(struct snd_soc_dai *cpu_dai)
|
|
{
|
|
struct atmel_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 = ssc_readl(ssc_p->ssc->regs, SR);
|
|
ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_TXDIS) | SSC_BIT(CR_RXDIS));
|
|
|
|
/* Save the current interrupt mask, then disable unmasked interrupts */
|
|
ssc_p->ssc_state.ssc_imr = ssc_readl(ssc_p->ssc->regs, IMR);
|
|
ssc_writel(ssc_p->ssc->regs, IDR, ssc_p->ssc_state.ssc_imr);
|
|
|
|
ssc_p->ssc_state.ssc_cmr = ssc_readl(ssc_p->ssc->regs, CMR);
|
|
ssc_p->ssc_state.ssc_rcmr = ssc_readl(ssc_p->ssc->regs, RCMR);
|
|
ssc_p->ssc_state.ssc_rfmr = ssc_readl(ssc_p->ssc->regs, RFMR);
|
|
ssc_p->ssc_state.ssc_tcmr = ssc_readl(ssc_p->ssc->regs, TCMR);
|
|
ssc_p->ssc_state.ssc_tfmr = ssc_readl(ssc_p->ssc->regs, TFMR);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
static int atmel_ssc_resume(struct snd_soc_dai *cpu_dai)
|
|
{
|
|
struct atmel_ssc_info *ssc_p;
|
|
u32 cr;
|
|
|
|
if (!cpu_dai->active)
|
|
return 0;
|
|
|
|
ssc_p = &ssc_info[cpu_dai->id];
|
|
|
|
/* restore SSC register settings */
|
|
ssc_writel(ssc_p->ssc->regs, TFMR, ssc_p->ssc_state.ssc_tfmr);
|
|
ssc_writel(ssc_p->ssc->regs, TCMR, ssc_p->ssc_state.ssc_tcmr);
|
|
ssc_writel(ssc_p->ssc->regs, RFMR, ssc_p->ssc_state.ssc_rfmr);
|
|
ssc_writel(ssc_p->ssc->regs, RCMR, ssc_p->ssc_state.ssc_rcmr);
|
|
ssc_writel(ssc_p->ssc->regs, CMR, ssc_p->ssc_state.ssc_cmr);
|
|
|
|
/* re-enable interrupts */
|
|
ssc_writel(ssc_p->ssc->regs, IER, ssc_p->ssc_state.ssc_imr);
|
|
|
|
/* Re-enable recieve and transmit as appropriate */
|
|
cr = 0;
|
|
cr |=
|
|
(ssc_p->ssc_state.ssc_sr & SSC_BIT(SR_RXEN)) ? SSC_BIT(CR_RXEN) : 0;
|
|
cr |=
|
|
(ssc_p->ssc_state.ssc_sr & SSC_BIT(SR_TXEN)) ? SSC_BIT(CR_TXEN) : 0;
|
|
ssc_writel(ssc_p->ssc->regs, CR, cr);
|
|
|
|
return 0;
|
|
}
|
|
#else /* CONFIG_PM */
|
|
# define atmel_ssc_suspend NULL
|
|
# define atmel_ssc_resume NULL
|
|
#endif /* CONFIG_PM */
|
|
|
|
|
|
#define ATMEL_SSC_RATES (SNDRV_PCM_RATE_8000_96000)
|
|
|
|
#define ATMEL_SSC_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\
|
|
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
|
|
|
|
static struct snd_soc_dai_ops atmel_ssc_dai_ops = {
|
|
.startup = atmel_ssc_startup,
|
|
.shutdown = atmel_ssc_shutdown,
|
|
.prepare = atmel_ssc_prepare,
|
|
.hw_params = atmel_ssc_hw_params,
|
|
.set_fmt = atmel_ssc_set_dai_fmt,
|
|
.set_clkdiv = atmel_ssc_set_dai_clkdiv,
|
|
};
|
|
|
|
struct snd_soc_dai atmel_ssc_dai[NUM_SSC_DEVICES] = {
|
|
{ .name = "atmel-ssc0",
|
|
.id = 0,
|
|
.suspend = atmel_ssc_suspend,
|
|
.resume = atmel_ssc_resume,
|
|
.playback = {
|
|
.channels_min = 1,
|
|
.channels_max = 2,
|
|
.rates = ATMEL_SSC_RATES,
|
|
.formats = ATMEL_SSC_FORMATS,},
|
|
.capture = {
|
|
.channels_min = 1,
|
|
.channels_max = 2,
|
|
.rates = ATMEL_SSC_RATES,
|
|
.formats = ATMEL_SSC_FORMATS,},
|
|
.ops = &atmel_ssc_dai_ops,
|
|
.private_data = &ssc_info[0],
|
|
},
|
|
#if NUM_SSC_DEVICES == 3
|
|
{ .name = "atmel-ssc1",
|
|
.id = 1,
|
|
.suspend = atmel_ssc_suspend,
|
|
.resume = atmel_ssc_resume,
|
|
.playback = {
|
|
.channels_min = 1,
|
|
.channels_max = 2,
|
|
.rates = ATMEL_SSC_RATES,
|
|
.formats = ATMEL_SSC_FORMATS,},
|
|
.capture = {
|
|
.channels_min = 1,
|
|
.channels_max = 2,
|
|
.rates = ATMEL_SSC_RATES,
|
|
.formats = ATMEL_SSC_FORMATS,},
|
|
.ops = &atmel_ssc_dai_ops,
|
|
.private_data = &ssc_info[1],
|
|
},
|
|
{ .name = "atmel-ssc2",
|
|
.id = 2,
|
|
.suspend = atmel_ssc_suspend,
|
|
.resume = atmel_ssc_resume,
|
|
.playback = {
|
|
.channels_min = 1,
|
|
.channels_max = 2,
|
|
.rates = ATMEL_SSC_RATES,
|
|
.formats = ATMEL_SSC_FORMATS,},
|
|
.capture = {
|
|
.channels_min = 1,
|
|
.channels_max = 2,
|
|
.rates = ATMEL_SSC_RATES,
|
|
.formats = ATMEL_SSC_FORMATS,},
|
|
.ops = &atmel_ssc_dai_ops,
|
|
.private_data = &ssc_info[2],
|
|
},
|
|
#endif
|
|
};
|
|
EXPORT_SYMBOL_GPL(atmel_ssc_dai);
|
|
|
|
static int __init atmel_ssc_modinit(void)
|
|
{
|
|
return snd_soc_register_dais(atmel_ssc_dai, ARRAY_SIZE(atmel_ssc_dai));
|
|
}
|
|
module_init(atmel_ssc_modinit);
|
|
|
|
static void __exit atmel_ssc_modexit(void)
|
|
{
|
|
snd_soc_unregister_dais(atmel_ssc_dai, ARRAY_SIZE(atmel_ssc_dai));
|
|
}
|
|
module_exit(atmel_ssc_modexit);
|
|
|
|
/* Module information */
|
|
MODULE_AUTHOR("Sedji Gaouaou, sedji.gaouaou@atmel.com, www.atmel.com");
|
|
MODULE_DESCRIPTION("ATMEL SSC ASoC Interface");
|
|
MODULE_LICENSE("GPL");
|