mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-09 01:28:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
63
sound/soc/atmel/Kconfig
Normal file
63
sound/soc/atmel/Kconfig
Normal file
|
@ -0,0 +1,63 @@
|
|||
config SND_ATMEL_SOC
|
||||
tristate "SoC Audio for the Atmel System-on-Chip"
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
Say Y or M if you want to add support for codecs attached to
|
||||
the ATMEL SSC interface. You will also need
|
||||
to select the audio interfaces to support below.
|
||||
|
||||
config SND_ATMEL_SOC_PDC
|
||||
tristate
|
||||
depends on SND_ATMEL_SOC
|
||||
|
||||
config SND_ATMEL_SOC_DMA
|
||||
tristate
|
||||
depends on SND_ATMEL_SOC
|
||||
select SND_SOC_GENERIC_DMAENGINE_PCM
|
||||
|
||||
config SND_ATMEL_SOC_SSC
|
||||
tristate
|
||||
depends on SND_ATMEL_SOC
|
||||
help
|
||||
Say Y or M if you want to add support for codecs the
|
||||
ATMEL SSC interface. You will also needs to select the individual
|
||||
machine drivers to support below.
|
||||
|
||||
config SND_AT91_SOC_SAM9G20_WM8731
|
||||
tristate "SoC Audio support for WM8731-based At91sam9g20 evaluation board"
|
||||
depends on ARCH_AT91 && ATMEL_SSC && SND_ATMEL_SOC
|
||||
select SND_ATMEL_SOC_PDC
|
||||
select SND_ATMEL_SOC_SSC
|
||||
select SND_SOC_WM8731
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on WM8731-based
|
||||
AT91sam9g20 evaluation board.
|
||||
|
||||
config SND_ATMEL_SOC_WM8904
|
||||
tristate "Atmel ASoC driver for boards using WM8904 codec"
|
||||
depends on ARCH_AT91 && ATMEL_SSC && SND_ATMEL_SOC && I2C
|
||||
select SND_ATMEL_SOC_SSC
|
||||
select SND_ATMEL_SOC_DMA
|
||||
select SND_SOC_WM8904
|
||||
help
|
||||
Say Y if you want to add support for Atmel ASoC driver for boards using
|
||||
WM8904 codec.
|
||||
|
||||
config SND_AT91_SOC_SAM9X5_WM8731
|
||||
tristate "SoC Audio support for WM8731-based at91sam9x5 board"
|
||||
depends on ATMEL_SSC && SND_ATMEL_SOC && SOC_AT91SAM9X5
|
||||
select SND_ATMEL_SOC_SSC
|
||||
select SND_ATMEL_SOC_DMA
|
||||
select SND_SOC_WM8731
|
||||
help
|
||||
Say Y if you want to add support for audio SoC on an
|
||||
at91sam9x5 based board that is using WM8731 codec.
|
||||
|
||||
config SND_AT91_SOC_AFEB9260
|
||||
tristate "SoC Audio support for AFEB9260 board"
|
||||
depends on ARCH_AT91 && ATMEL_SSC && ARCH_AT91 && MACH_AFEB9260 && SND_ATMEL_SOC
|
||||
select SND_ATMEL_SOC_PDC
|
||||
select SND_ATMEL_SOC_SSC
|
||||
select SND_SOC_TLV320AIC23_I2C
|
||||
help
|
||||
Say Y here to support sound on AFEB9260 board.
|
20
sound/soc/atmel/Makefile
Normal file
20
sound/soc/atmel/Makefile
Normal file
|
@ -0,0 +1,20 @@
|
|||
# AT91 Platform Support
|
||||
snd-soc-atmel-pcm-objs := atmel-pcm.o
|
||||
snd-soc-atmel-pcm-pdc-objs := atmel-pcm-pdc.o
|
||||
snd-soc-atmel-pcm-dma-objs := atmel-pcm-dma.o
|
||||
snd-soc-atmel_ssc_dai-objs := atmel_ssc_dai.o
|
||||
|
||||
obj-$(CONFIG_SND_ATMEL_SOC) += snd-soc-atmel-pcm.o
|
||||
obj-$(CONFIG_SND_ATMEL_SOC_PDC) += snd-soc-atmel-pcm-pdc.o
|
||||
obj-$(CONFIG_SND_ATMEL_SOC_DMA) += snd-soc-atmel-pcm-dma.o
|
||||
obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel_ssc_dai.o
|
||||
|
||||
# AT91 Machine Support
|
||||
snd-soc-sam9g20-wm8731-objs := sam9g20_wm8731.o
|
||||
snd-atmel-soc-wm8904-objs := atmel_wm8904.o
|
||||
snd-soc-sam9x5-wm8731-objs := sam9x5_wm8731.o
|
||||
|
||||
obj-$(CONFIG_SND_AT91_SOC_SAM9G20_WM8731) += snd-soc-sam9g20-wm8731.o
|
||||
obj-$(CONFIG_SND_ATMEL_SOC_WM8904) += snd-atmel-soc-wm8904.o
|
||||
obj-$(CONFIG_SND_AT91_SOC_SAM9X5_WM8731) += snd-soc-sam9x5-wm8731.o
|
||||
obj-$(CONFIG_SND_AT91_SOC_AFEB9260) += snd-soc-afeb9260.o
|
144
sound/soc/atmel/atmel-pcm-dma.c
Normal file
144
sound/soc/atmel/atmel-pcm-dma.c
Normal file
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* atmel-pcm-dma.c -- ALSA PCM DMA support for the Atmel SoC.
|
||||
*
|
||||
* Copyright (C) 2012 Atmel
|
||||
*
|
||||
* Author: Bo Shen <voice.shen@atmel.com>
|
||||
*
|
||||
* Based on atmel-pcm by:
|
||||
* Sedji Gaouaou <sedji.gaouaou@atmel.com>
|
||||
* Copyright 2008 Atmel
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/atmel-ssc.h>
|
||||
#include <linux/platform_data/dma-atmel.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
|
||||
#include "atmel-pcm.h"
|
||||
|
||||
/*--------------------------------------------------------------------------*\
|
||||
* Hardware definition
|
||||
\*--------------------------------------------------------------------------*/
|
||||
static const struct snd_pcm_hardware atmel_pcm_dma_hardware = {
|
||||
.info = SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_RESUME |
|
||||
SNDRV_PCM_INFO_PAUSE,
|
||||
.period_bytes_min = 256, /* lighting DMA overhead */
|
||||
.period_bytes_max = 2 * 0xffff, /* if 2 bytes format */
|
||||
.periods_min = 8,
|
||||
.periods_max = 1024, /* no limit */
|
||||
.buffer_bytes_max = ATMEL_SSC_DMABUF_SIZE,
|
||||
};
|
||||
|
||||
/**
|
||||
* atmel_pcm_dma_irq: SSC interrupt handler for DMAENGINE enabled SSC
|
||||
*
|
||||
* We use DMAENGINE to send/receive data to/from SSC so this ISR is only to
|
||||
* check if any overrun occured.
|
||||
*/
|
||||
static void atmel_pcm_dma_irq(u32 ssc_sr,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct atmel_pcm_dma_params *prtd;
|
||||
|
||||
prtd = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
|
||||
|
||||
if (ssc_sr & prtd->mask->ssc_error) {
|
||||
if (snd_pcm_running(substream))
|
||||
pr_warn("atmel-pcm: buffer %s on %s (SSC_SR=%#x)\n",
|
||||
substream->stream == SNDRV_PCM_STREAM_PLAYBACK
|
||||
? "underrun" : "overrun", prtd->name,
|
||||
ssc_sr);
|
||||
|
||||
/* stop RX and capture: will be enabled again at restart */
|
||||
ssc_writex(prtd->ssc->regs, SSC_CR, prtd->mask->ssc_disable);
|
||||
snd_pcm_stream_lock(substream);
|
||||
snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
|
||||
snd_pcm_stream_unlock(substream);
|
||||
|
||||
/* now drain RHR and read status to remove xrun condition */
|
||||
ssc_readx(prtd->ssc->regs, SSC_RHR);
|
||||
ssc_readx(prtd->ssc->regs, SSC_SR);
|
||||
}
|
||||
}
|
||||
|
||||
static int atmel_pcm_configure_dma(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params, struct dma_slave_config *slave_config)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct atmel_pcm_dma_params *prtd;
|
||||
struct ssc_device *ssc;
|
||||
int ret;
|
||||
|
||||
prtd = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
|
||||
ssc = prtd->ssc;
|
||||
|
||||
ret = snd_hwparams_to_dma_slave_config(substream, params, slave_config);
|
||||
if (ret) {
|
||||
pr_err("atmel-pcm: hwparams to dma slave configure failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
slave_config->dst_addr = ssc->phybase + SSC_THR;
|
||||
slave_config->dst_maxburst = 1;
|
||||
} else {
|
||||
slave_config->src_addr = ssc->phybase + SSC_RHR;
|
||||
slave_config->src_maxburst = 1;
|
||||
}
|
||||
|
||||
prtd->dma_intr_handler = atmel_pcm_dma_irq;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct snd_dmaengine_pcm_config atmel_dmaengine_pcm_config = {
|
||||
.prepare_slave_config = atmel_pcm_configure_dma,
|
||||
.pcm_hardware = &atmel_pcm_dma_hardware,
|
||||
.prealloc_buffer_size = ATMEL_SSC_DMABUF_SIZE,
|
||||
};
|
||||
|
||||
int atmel_pcm_dma_platform_register(struct device *dev)
|
||||
{
|
||||
return snd_dmaengine_pcm_register(dev, &atmel_dmaengine_pcm_config,
|
||||
SND_DMAENGINE_PCM_FLAG_NO_RESIDUE);
|
||||
}
|
||||
EXPORT_SYMBOL(atmel_pcm_dma_platform_register);
|
||||
|
||||
void atmel_pcm_dma_platform_unregister(struct device *dev)
|
||||
{
|
||||
snd_dmaengine_pcm_unregister(dev);
|
||||
}
|
||||
EXPORT_SYMBOL(atmel_pcm_dma_platform_unregister);
|
||||
|
||||
MODULE_AUTHOR("Bo Shen <voice.shen@atmel.com>");
|
||||
MODULE_DESCRIPTION("Atmel DMA based PCM module");
|
||||
MODULE_LICENSE("GPL");
|
337
sound/soc/atmel/atmel-pcm-pdc.c
Normal file
337
sound/soc/atmel/atmel-pcm-pdc.c
Normal file
|
@ -0,0 +1,337 @@
|
|||
/*
|
||||
* atmel-pcm.c -- ALSA PCM interface for the Atmel atmel SoC.
|
||||
*
|
||||
* Copyright (C) 2005 SAN People
|
||||
* Copyright (C) 2008 Atmel
|
||||
*
|
||||
* Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
|
||||
*
|
||||
* Based on at91-pcm. by:
|
||||
* Frank Mandarino <fmandarino@endrelia.com>
|
||||
* Copyright 2006 Endrelia Technologies Inc.
|
||||
*
|
||||
* Based on pxa2xx-pcm.c by:
|
||||
*
|
||||
* Author: Nicolas Pitre
|
||||
* Created: Nov 30, 2004
|
||||
* Copyright: (C) 2004 MontaVista Software, Inc.
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/dma-mapping.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/soc.h>
|
||||
|
||||
#include "atmel-pcm.h"
|
||||
|
||||
|
||||
/*--------------------------------------------------------------------------*\
|
||||
* Hardware definition
|
||||
\*--------------------------------------------------------------------------*/
|
||||
/* TODO: These values were taken from the AT91 platform driver, check
|
||||
* them against real values for AT32
|
||||
*/
|
||||
static const struct snd_pcm_hardware atmel_pcm_hardware = {
|
||||
.info = SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_PAUSE,
|
||||
.period_bytes_min = 32,
|
||||
.period_bytes_max = 8192,
|
||||
.periods_min = 2,
|
||||
.periods_max = 1024,
|
||||
.buffer_bytes_max = ATMEL_SSC_DMABUF_SIZE,
|
||||
};
|
||||
|
||||
|
||||
/*--------------------------------------------------------------------------*\
|
||||
* Data types
|
||||
\*--------------------------------------------------------------------------*/
|
||||
struct atmel_runtime_data {
|
||||
struct atmel_pcm_dma_params *params;
|
||||
dma_addr_t dma_buffer; /* physical address of dma buffer */
|
||||
dma_addr_t dma_buffer_end; /* first address beyond DMA buffer */
|
||||
size_t period_size;
|
||||
|
||||
dma_addr_t period_ptr; /* physical address of next period */
|
||||
};
|
||||
|
||||
/*--------------------------------------------------------------------------*\
|
||||
* ISR
|
||||
\*--------------------------------------------------------------------------*/
|
||||
static void atmel_pcm_dma_irq(u32 ssc_sr,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct atmel_runtime_data *prtd = substream->runtime->private_data;
|
||||
struct atmel_pcm_dma_params *params = prtd->params;
|
||||
static int count;
|
||||
|
||||
count++;
|
||||
|
||||
if (ssc_sr & params->mask->ssc_endbuf) {
|
||||
pr_warn("atmel-pcm: buffer %s on %s (SSC_SR=%#x, count=%d)\n",
|
||||
substream->stream == SNDRV_PCM_STREAM_PLAYBACK
|
||||
? "underrun" : "overrun",
|
||||
params->name, ssc_sr, count);
|
||||
|
||||
/* re-start the PDC */
|
||||
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
|
||||
params->mask->pdc_disable);
|
||||
prtd->period_ptr += prtd->period_size;
|
||||
if (prtd->period_ptr >= prtd->dma_buffer_end)
|
||||
prtd->period_ptr = prtd->dma_buffer;
|
||||
|
||||
ssc_writex(params->ssc->regs, params->pdc->xpr,
|
||||
prtd->period_ptr);
|
||||
ssc_writex(params->ssc->regs, params->pdc->xcr,
|
||||
prtd->period_size / params->pdc_xfer_size);
|
||||
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
|
||||
params->mask->pdc_enable);
|
||||
}
|
||||
|
||||
if (ssc_sr & params->mask->ssc_endx) {
|
||||
/* Load the PDC next pointer and counter registers */
|
||||
prtd->period_ptr += prtd->period_size;
|
||||
if (prtd->period_ptr >= prtd->dma_buffer_end)
|
||||
prtd->period_ptr = prtd->dma_buffer;
|
||||
|
||||
ssc_writex(params->ssc->regs, params->pdc->xnpr,
|
||||
prtd->period_ptr);
|
||||
ssc_writex(params->ssc->regs, params->pdc->xncr,
|
||||
prtd->period_size / params->pdc_xfer_size);
|
||||
}
|
||||
|
||||
snd_pcm_period_elapsed(substream);
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------------------------------------------------------*\
|
||||
* PCM operations
|
||||
\*--------------------------------------------------------------------------*/
|
||||
static int atmel_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct atmel_runtime_data *prtd = runtime->private_data;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
|
||||
/* this may get called several times by oss emulation
|
||||
* with different params */
|
||||
|
||||
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
||||
runtime->dma_bytes = params_buffer_bytes(params);
|
||||
|
||||
prtd->params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
|
||||
prtd->params->dma_intr_handler = atmel_pcm_dma_irq;
|
||||
|
||||
prtd->dma_buffer = runtime->dma_addr;
|
||||
prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes;
|
||||
prtd->period_size = params_period_bytes(params);
|
||||
|
||||
pr_debug("atmel-pcm: "
|
||||
"hw_params: DMA for %s initialized "
|
||||
"(dma_bytes=%zu, period_size=%zu)\n",
|
||||
prtd->params->name,
|
||||
runtime->dma_bytes,
|
||||
prtd->period_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct atmel_runtime_data *prtd = substream->runtime->private_data;
|
||||
struct atmel_pcm_dma_params *params = prtd->params;
|
||||
|
||||
if (params != NULL) {
|
||||
ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
|
||||
params->mask->pdc_disable);
|
||||
prtd->params->dma_intr_handler = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_pcm_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct atmel_runtime_data *prtd = substream->runtime->private_data;
|
||||
struct atmel_pcm_dma_params *params = prtd->params;
|
||||
|
||||
ssc_writex(params->ssc->regs, SSC_IDR,
|
||||
params->mask->ssc_endx | params->mask->ssc_endbuf);
|
||||
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
|
||||
params->mask->pdc_disable);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_pcm_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd)
|
||||
{
|
||||
struct snd_pcm_runtime *rtd = substream->runtime;
|
||||
struct atmel_runtime_data *prtd = rtd->private_data;
|
||||
struct atmel_pcm_dma_params *params = prtd->params;
|
||||
int ret = 0;
|
||||
|
||||
pr_debug("atmel-pcm:buffer_size = %ld,"
|
||||
"dma_area = %p, dma_bytes = %zu\n",
|
||||
rtd->buffer_size, rtd->dma_area, rtd->dma_bytes);
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
prtd->period_ptr = prtd->dma_buffer;
|
||||
|
||||
ssc_writex(params->ssc->regs, params->pdc->xpr,
|
||||
prtd->period_ptr);
|
||||
ssc_writex(params->ssc->regs, params->pdc->xcr,
|
||||
prtd->period_size / params->pdc_xfer_size);
|
||||
|
||||
prtd->period_ptr += prtd->period_size;
|
||||
ssc_writex(params->ssc->regs, params->pdc->xnpr,
|
||||
prtd->period_ptr);
|
||||
ssc_writex(params->ssc->regs, params->pdc->xncr,
|
||||
prtd->period_size / params->pdc_xfer_size);
|
||||
|
||||
pr_debug("atmel-pcm: trigger: "
|
||||
"period_ptr=%lx, xpr=%u, "
|
||||
"xcr=%u, xnpr=%u, xncr=%u\n",
|
||||
(unsigned long)prtd->period_ptr,
|
||||
ssc_readx(params->ssc->regs, params->pdc->xpr),
|
||||
ssc_readx(params->ssc->regs, params->pdc->xcr),
|
||||
ssc_readx(params->ssc->regs, params->pdc->xnpr),
|
||||
ssc_readx(params->ssc->regs, params->pdc->xncr));
|
||||
|
||||
ssc_writex(params->ssc->regs, SSC_IER,
|
||||
params->mask->ssc_endx | params->mask->ssc_endbuf);
|
||||
ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
|
||||
params->mask->pdc_enable);
|
||||
|
||||
pr_debug("sr=%u imr=%u\n",
|
||||
ssc_readx(params->ssc->regs, SSC_SR),
|
||||
ssc_readx(params->ssc->regs, SSC_IER));
|
||||
break; /* SNDRV_PCM_TRIGGER_START */
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
|
||||
params->mask->pdc_disable);
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
|
||||
params->mask->pdc_enable);
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t atmel_pcm_pointer(
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct atmel_runtime_data *prtd = runtime->private_data;
|
||||
struct atmel_pcm_dma_params *params = prtd->params;
|
||||
dma_addr_t ptr;
|
||||
snd_pcm_uframes_t x;
|
||||
|
||||
ptr = (dma_addr_t) ssc_readx(params->ssc->regs, params->pdc->xpr);
|
||||
x = bytes_to_frames(runtime, ptr - prtd->dma_buffer);
|
||||
|
||||
if (x == runtime->buffer_size)
|
||||
x = 0;
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
static int atmel_pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct atmel_runtime_data *prtd;
|
||||
int ret = 0;
|
||||
|
||||
snd_soc_set_runtime_hwparams(substream, &atmel_pcm_hardware);
|
||||
|
||||
/* ensure that buffer size is a multiple of period size */
|
||||
ret = snd_pcm_hw_constraint_integer(runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
prtd = kzalloc(sizeof(struct atmel_runtime_data), GFP_KERNEL);
|
||||
if (prtd == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
runtime->private_data = prtd;
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int atmel_pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct atmel_runtime_data *prtd = substream->runtime->private_data;
|
||||
|
||||
kfree(prtd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops atmel_pcm_ops = {
|
||||
.open = atmel_pcm_open,
|
||||
.close = atmel_pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = atmel_pcm_hw_params,
|
||||
.hw_free = atmel_pcm_hw_free,
|
||||
.prepare = atmel_pcm_prepare,
|
||||
.trigger = atmel_pcm_trigger,
|
||||
.pointer = atmel_pcm_pointer,
|
||||
.mmap = atmel_pcm_mmap,
|
||||
};
|
||||
|
||||
static struct snd_soc_platform_driver atmel_soc_platform = {
|
||||
.ops = &atmel_pcm_ops,
|
||||
.pcm_new = atmel_pcm_new,
|
||||
.pcm_free = atmel_pcm_free,
|
||||
};
|
||||
|
||||
int atmel_pcm_pdc_platform_register(struct device *dev)
|
||||
{
|
||||
return snd_soc_register_platform(dev, &atmel_soc_platform);
|
||||
}
|
||||
EXPORT_SYMBOL(atmel_pcm_pdc_platform_register);
|
||||
|
||||
void atmel_pcm_pdc_platform_unregister(struct device *dev)
|
||||
{
|
||||
snd_soc_unregister_platform(dev);
|
||||
}
|
||||
EXPORT_SYMBOL(atmel_pcm_pdc_platform_unregister);
|
||||
|
||||
MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>");
|
||||
MODULE_DESCRIPTION("Atmel PCM module");
|
||||
MODULE_LICENSE("GPL");
|
121
sound/soc/atmel/atmel-pcm.c
Normal file
121
sound/soc/atmel/atmel-pcm.c
Normal file
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* atmel-pcm.c -- ALSA PCM interface for the Atmel atmel SoC.
|
||||
*
|
||||
* Copyright (C) 2005 SAN People
|
||||
* Copyright (C) 2008 Atmel
|
||||
*
|
||||
* Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
|
||||
*
|
||||
* Based on at91-pcm. by:
|
||||
* Frank Mandarino <fmandarino@endrelia.com>
|
||||
* Copyright 2006 Endrelia Technologies Inc.
|
||||
*
|
||||
* Based on pxa2xx-pcm.c by:
|
||||
*
|
||||
* Author: Nicolas Pitre
|
||||
* Created: Nov 30, 2004
|
||||
* Copyright: (C) 2004 MontaVista Software, Inc.
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include "atmel-pcm.h"
|
||||
|
||||
static int atmel_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 = ATMEL_SSC_DMABUF_SIZE;
|
||||
|
||||
buf->dev.type = SNDRV_DMA_TYPE_DEV;
|
||||
buf->dev.dev = pcm->card->dev;
|
||||
buf->private_data = NULL;
|
||||
buf->area = dma_alloc_coherent(pcm->card->dev, size,
|
||||
&buf->addr, GFP_KERNEL);
|
||||
pr_debug("atmel-pcm: alloc dma buffer: area=%p, addr=%p, size=%zu\n",
|
||||
(void *)buf->area, (void *)(long)buf->addr, size);
|
||||
|
||||
if (!buf->area)
|
||||
return -ENOMEM;
|
||||
|
||||
buf->bytes = size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int atmel_pcm_mmap(struct snd_pcm_substream *substream,
|
||||
struct vm_area_struct *vma)
|
||||
{
|
||||
return remap_pfn_range(vma, vma->vm_start,
|
||||
substream->dma_buffer.addr >> PAGE_SHIFT,
|
||||
vma->vm_end - vma->vm_start, vma->vm_page_prot);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(atmel_pcm_mmap);
|
||||
|
||||
int atmel_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int ret;
|
||||
|
||||
ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
|
||||
pr_debug("atmel-pcm: allocating PCM playback DMA buffer\n");
|
||||
ret = atmel_pcm_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_PLAYBACK);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
|
||||
pr_debug("atmel-pcm: allocating PCM capture DMA buffer\n");
|
||||
ret = atmel_pcm_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_CAPTURE);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(atmel_pcm_new);
|
||||
|
||||
void atmel_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_coherent(pcm->card->dev, buf->bytes,
|
||||
buf->area, buf->addr);
|
||||
buf->area = NULL;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(atmel_pcm_free);
|
||||
|
119
sound/soc/atmel/atmel-pcm.h
Normal file
119
sound/soc/atmel/atmel-pcm.h
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* at91-pcm.h - ALSA PCM interface for the Atmel AT91 SoC.
|
||||
*
|
||||
* Copyright (C) 2005 SAN People
|
||||
* Copyright (C) 2008 Atmel
|
||||
*
|
||||
* Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
|
||||
*
|
||||
* Based on at91-pcm. by:
|
||||
* Frank Mandarino <fmandarino@endrelia.com>
|
||||
* Copyright 2006 Endrelia Technologies Inc.
|
||||
*
|
||||
* Based on pxa2xx-pcm.c by:
|
||||
*
|
||||
* Author: Nicolas Pitre
|
||||
* Created: Nov 30, 2004
|
||||
* Copyright: (C) 2004 MontaVista Software, Inc.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef _ATMEL_PCM_H
|
||||
#define _ATMEL_PCM_H
|
||||
|
||||
#include <linux/atmel-ssc.h>
|
||||
|
||||
#define ATMEL_SSC_DMABUF_SIZE (64 * 1024)
|
||||
|
||||
/*
|
||||
* Registers and status bits that are required by the PCM driver.
|
||||
*/
|
||||
struct atmel_pdc_regs {
|
||||
unsigned int xpr; /* PDC recv/trans pointer */
|
||||
unsigned int xcr; /* PDC recv/trans counter */
|
||||
unsigned int xnpr; /* PDC next recv/trans pointer */
|
||||
unsigned int xncr; /* PDC next recv/trans counter */
|
||||
unsigned int ptcr; /* PDC transfer control */
|
||||
};
|
||||
|
||||
struct atmel_ssc_mask {
|
||||
u32 ssc_enable; /* SSC recv/trans enable */
|
||||
u32 ssc_disable; /* SSC recv/trans disable */
|
||||
u32 ssc_error; /* SSC error conditions */
|
||||
u32 ssc_endx; /* SSC ENDTX or ENDRX */
|
||||
u32 ssc_endbuf; /* SSC TXBUFE or RXBUFF */
|
||||
u32 pdc_enable; /* PDC recv/trans enable */
|
||||
u32 pdc_disable; /* PDC recv/trans disable */
|
||||
};
|
||||
|
||||
/*
|
||||
* This structure, shared between the PCM driver and the interface,
|
||||
* contains all information required by the PCM driver to perform the
|
||||
* PDC DMA operation. All fields except dma_intr_handler() are initialized
|
||||
* by the interface. The dma_intr_handler() pointer is set by the PCM
|
||||
* driver and called by the interface SSC interrupt handler if it is
|
||||
* non-NULL.
|
||||
*/
|
||||
struct atmel_pcm_dma_params {
|
||||
char *name; /* stream identifier */
|
||||
int pdc_xfer_size; /* PDC counter increment in bytes */
|
||||
struct ssc_device *ssc; /* SSC device for stream */
|
||||
struct atmel_pdc_regs *pdc; /* PDC receive or transmit registers */
|
||||
struct atmel_ssc_mask *mask; /* SSC & PDC status bits */
|
||||
struct snd_pcm_substream *substream;
|
||||
void (*dma_intr_handler)(u32, struct snd_pcm_substream *);
|
||||
};
|
||||
|
||||
/*
|
||||
* SSC register access (since ssc_writel() / ssc_readl() require literal name)
|
||||
*/
|
||||
#define ssc_readx(base, reg) (__raw_readl((base) + (reg)))
|
||||
#define ssc_writex(base, reg, value) __raw_writel((value), (base) + (reg))
|
||||
|
||||
int atmel_pcm_new(struct snd_soc_pcm_runtime *rtd);
|
||||
void atmel_pcm_free(struct snd_pcm *pcm);
|
||||
int atmel_pcm_mmap(struct snd_pcm_substream *substream,
|
||||
struct vm_area_struct *vma);
|
||||
|
||||
#if defined(CONFIG_SND_ATMEL_SOC_PDC) || \
|
||||
defined(CONFIG_SND_ATMEL_SOC_PDC_MODULE)
|
||||
int atmel_pcm_pdc_platform_register(struct device *dev);
|
||||
void atmel_pcm_pdc_platform_unregister(struct device *dev);
|
||||
#else
|
||||
static inline int atmel_pcm_pdc_platform_register(struct device *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline void atmel_pcm_pdc_platform_unregister(struct device *dev)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SND_ATMEL_SOC_DMA) || \
|
||||
defined(CONFIG_SND_ATMEL_SOC_DMA_MODULE)
|
||||
int atmel_pcm_dma_platform_register(struct device *dev);
|
||||
void atmel_pcm_dma_platform_unregister(struct device *dev);
|
||||
#else
|
||||
static inline int atmel_pcm_dma_platform_register(struct device *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline void atmel_pcm_dma_platform_unregister(struct device *dev)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _ATMEL_PCM_H */
|
864
sound/soc/atmel/atmel_ssc_dai.c
Normal file
864
sound/soc/atmel/atmel_ssc_dai.c
Normal file
|
@ -0,0 +1,864 @@
|
|||
/*
|
||||
* 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 "atmel-pcm.h"
|
||||
#include "atmel_ssc_dai.h"
|
||||
|
||||
|
||||
#define NUM_SSC_DEVICES 3
|
||||
|
||||
/*
|
||||
* 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),
|
||||
.ssc_error = SSC_BIT(SR_OVRUN),
|
||||
.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),
|
||||
.ssc_error = SSC_BIT(SR_OVRUN),
|
||||
.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,
|
||||
} },
|
||||
{{
|
||||
.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,
|
||||
} },
|
||||
};
|
||||
|
||||
|
||||
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,
|
||||
},
|
||||
{
|
||||
.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,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* 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 atmel_ssc_info *ssc_p = &ssc_info[dai->id];
|
||||
struct atmel_pcm_dma_params *dma_params;
|
||||
int dir, 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 = 0;
|
||||
dir_mask = SSC_DIR_MASK_PLAYBACK;
|
||||
} else {
|
||||
dir = 1;
|
||||
dir_mask = SSC_DIR_MASK_CAPTURE;
|
||||
}
|
||||
|
||||
dma_params = &ssc_dma_params[dai->id][dir];
|
||||
dma_params->ssc = ssc_p->ssc;
|
||||
dma_params->substream = substream;
|
||||
|
||||
ssc_p->dma_params[dir] = dma_params;
|
||||
|
||||
snd_soc_dai_set_dma_data(dai, substream, dma_params);
|
||||
|
||||
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 atmel_ssc_info *ssc_p = &ssc_info[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)
|
||||
{
|
||||
int id = dai->id;
|
||||
struct atmel_ssc_info *ssc_p = &ssc_info[id];
|
||||
struct ssc_device *ssc = ssc_p->ssc;
|
||||
struct atmel_pcm_dma_params *dma_params;
|
||||
int dir, channels, bits;
|
||||
u32 tfmr, rfmr, tcmr, rcmr;
|
||||
int ret;
|
||||
int fslen, fslen_ext;
|
||||
|
||||
/*
|
||||
* 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_p->dma_params[dir];
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
if (bits > 16 && !ssc->pdata->has_fslen_ext) {
|
||||
dev_err(dai->dev,
|
||||
"sample size %d is too large for SSC device\n",
|
||||
bits);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
fslen_ext = (bits - 1) / 16;
|
||||
fslen = (bits - 1) % 16;
|
||||
|
||||
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_FSLEN_EXT, fslen_ext)
|
||||
| SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
|
||||
| SSC_BF(RFMR_FSOS, SSC_FSOS_NEGATIVE)
|
||||
| SSC_BF(RFMR_FSLEN, fslen)
|
||||
| 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_FSLEN_EXT, fslen_ext)
|
||||
| SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
|
||||
| SSC_BF(TFMR_FSDEN, 0)
|
||||
| SSC_BF(TFMR_FSOS, SSC_FSOS_NEGATIVE)
|
||||
| SSC_BF(TFMR_FSLEN, fslen)
|
||||
| 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.
|
||||
*/
|
||||
rcmr = SSC_BF(RCMR_PERIOD, 0)
|
||||
| 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->clk_from_rk_pin ?
|
||||
SSC_CKS_PIN : 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, (channels - 1))
|
||||
| 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, SSC_START_FALLING_RF)
|
||||
| SSC_BF(TCMR_CKI, SSC_CKI_FALLING)
|
||||
| SSC_BF(TCMR_CKO, SSC_CKO_NONE)
|
||||
| SSC_BF(TCMR_CKS, ssc->clk_from_rk_pin ?
|
||||
SSC_CKS_CLOCK : 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, (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_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:
|
||||
/*
|
||||
* DSP/PCM Mode A 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.
|
||||
*
|
||||
* Data is transferred on first BCLK after LRC pulse rising
|
||||
* edge.If stereo, the right channel data is contiguous with
|
||||
* the left channel data.
|
||||
*/
|
||||
rcmr = SSC_BF(RCMR_PERIOD, 0)
|
||||
| SSC_BF(RCMR_STTDLY, START_DELAY)
|
||||
| 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->clk_from_rk_pin ?
|
||||
SSC_CKS_PIN : 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, (channels - 1))
|
||||
| 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, SSC_START_RISING_RF)
|
||||
| SSC_BF(TCMR_CKI, SSC_CKI_FALLING)
|
||||
| SSC_BF(TCMR_CKO, SSC_CKO_NONE)
|
||||
| SSC_BF(RCMR_CKS, ssc->clk_from_rk_pin ?
|
||||
SSC_CKS_CLOCK : 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, (channels - 1))
|
||||
| SSC_BIT(TFMR_MSBF)
|
||||
| SSC_BF(TFMR_DATDEF, 0)
|
||||
| SSC_BF(TFMR_DATLEN, (bits - 1));
|
||||
break;
|
||||
|
||||
default:
|
||||
printk(KERN_WARNING "atmel_ssc_dai: unsupported DAI format 0x%x\n",
|
||||
ssc_p->daifmt);
|
||||
return -EINVAL;
|
||||
}
|
||||
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 atmel_ssc_info *ssc_p = &ssc_info[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_disable);
|
||||
ssc_writel(ssc_p->ssc->regs, IDR, dma_params->mask->ssc_error);
|
||||
|
||||
pr_debug("%s enabled SSC_SR=0x%08x\n",
|
||||
dir ? "receive" : "transmit",
|
||||
ssc_readl(ssc_p->ssc->regs, SR));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_ssc_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd, struct snd_soc_dai *dai)
|
||||
{
|
||||
struct atmel_ssc_info *ssc_p = &ssc_info[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];
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_enable);
|
||||
break;
|
||||
default:
|
||||
ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_disable);
|
||||
break;
|
||||
}
|
||||
|
||||
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 receive 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 const struct snd_soc_dai_ops atmel_ssc_dai_ops = {
|
||||
.startup = atmel_ssc_startup,
|
||||
.shutdown = atmel_ssc_shutdown,
|
||||
.prepare = atmel_ssc_prepare,
|
||||
.trigger = atmel_ssc_trigger,
|
||||
.hw_params = atmel_ssc_hw_params,
|
||||
.set_fmt = atmel_ssc_set_dai_fmt,
|
||||
.set_clkdiv = atmel_ssc_set_dai_clkdiv,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_driver atmel_ssc_dai = {
|
||||
.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,
|
||||
};
|
||||
|
||||
static const struct snd_soc_component_driver atmel_ssc_component = {
|
||||
.name = "atmel-ssc",
|
||||
};
|
||||
|
||||
static int asoc_ssc_init(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct ssc_device *ssc = platform_get_drvdata(pdev);
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_register_component(dev, &atmel_ssc_component,
|
||||
&atmel_ssc_dai, 1);
|
||||
if (ret) {
|
||||
dev_err(dev, "Could not register DAI: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (ssc->pdata->use_dma)
|
||||
ret = atmel_pcm_dma_platform_register(dev);
|
||||
else
|
||||
ret = atmel_pcm_pdc_platform_register(dev);
|
||||
|
||||
if (ret) {
|
||||
dev_err(dev, "Could not register PCM: %d\n", ret);
|
||||
goto err_unregister_dai;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister_dai:
|
||||
snd_soc_unregister_component(dev);
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void asoc_ssc_exit(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct ssc_device *ssc = platform_get_drvdata(pdev);
|
||||
|
||||
if (ssc->pdata->use_dma)
|
||||
atmel_pcm_dma_platform_unregister(dev);
|
||||
else
|
||||
atmel_pcm_pdc_platform_unregister(dev);
|
||||
|
||||
snd_soc_unregister_component(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* atmel_ssc_set_audio - Allocate the specified SSC for audio use.
|
||||
*/
|
||||
int atmel_ssc_set_audio(int ssc_id)
|
||||
{
|
||||
struct ssc_device *ssc;
|
||||
int ret;
|
||||
|
||||
/* If we can grab the SSC briefly to parent the DAI device off it */
|
||||
ssc = ssc_request(ssc_id);
|
||||
if (IS_ERR(ssc)) {
|
||||
pr_err("Unable to parent ASoC SSC DAI on SSC: %ld\n",
|
||||
PTR_ERR(ssc));
|
||||
return PTR_ERR(ssc);
|
||||
} else {
|
||||
ssc_info[ssc_id].ssc = ssc;
|
||||
}
|
||||
|
||||
ret = asoc_ssc_init(&ssc->pdev->dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(atmel_ssc_set_audio);
|
||||
|
||||
void atmel_ssc_put_audio(int ssc_id)
|
||||
{
|
||||
struct ssc_device *ssc = ssc_info[ssc_id].ssc;
|
||||
|
||||
asoc_ssc_exit(&ssc->pdev->dev);
|
||||
ssc_free(ssc);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(atmel_ssc_put_audio);
|
||||
|
||||
/* Module information */
|
||||
MODULE_AUTHOR("Sedji Gaouaou, sedji.gaouaou@atmel.com, www.atmel.com");
|
||||
MODULE_DESCRIPTION("ATMEL SSC ASoC Interface");
|
||||
MODULE_LICENSE("GPL");
|
123
sound/soc/atmel/atmel_ssc_dai.h
Normal file
123
sound/soc/atmel/atmel_ssc_dai.h
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* atmel_ssc_dai.h - ALSA SSC interface for the Atmel SoC
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef _ATMEL_SSC_DAI_H
|
||||
#define _ATMEL_SSC_DAI_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/atmel-ssc.h>
|
||||
|
||||
#include "atmel-pcm.h"
|
||||
|
||||
/* SSC system clock ids */
|
||||
#define ATMEL_SYSCLK_MCK 0 /* SSC uses AT91 MCK as system clock */
|
||||
|
||||
/* SSC divider ids */
|
||||
#define ATMEL_SSC_CMR_DIV 0 /* MCK divider for BCLK */
|
||||
#define ATMEL_SSC_TCMR_PERIOD 1 /* BCLK divider for transmit FS */
|
||||
#define ATMEL_SSC_RCMR_PERIOD 2 /* BCLK divider for receive FS */
|
||||
/*
|
||||
* SSC direction masks
|
||||
*/
|
||||
#define SSC_DIR_MASK_UNUSED 0
|
||||
#define SSC_DIR_MASK_PLAYBACK 1
|
||||
#define SSC_DIR_MASK_CAPTURE 2
|
||||
|
||||
/*
|
||||
* SSC register values that Atmel left out of <linux/atmel-ssc.h>. These
|
||||
* are expected to be used with SSC_BF
|
||||
*/
|
||||
/* START bit field values */
|
||||
#define SSC_START_CONTINUOUS 0
|
||||
#define SSC_START_TX_RX 1
|
||||
#define SSC_START_LOW_RF 2
|
||||
#define SSC_START_HIGH_RF 3
|
||||
#define SSC_START_FALLING_RF 4
|
||||
#define SSC_START_RISING_RF 5
|
||||
#define SSC_START_LEVEL_RF 6
|
||||
#define SSC_START_EDGE_RF 7
|
||||
#define SSS_START_COMPARE_0 8
|
||||
|
||||
/* CKI bit field values */
|
||||
#define SSC_CKI_FALLING 0
|
||||
#define SSC_CKI_RISING 1
|
||||
|
||||
/* CKO bit field values */
|
||||
#define SSC_CKO_NONE 0
|
||||
#define SSC_CKO_CONTINUOUS 1
|
||||
#define SSC_CKO_TRANSFER 2
|
||||
|
||||
/* CKS bit field values */
|
||||
#define SSC_CKS_DIV 0
|
||||
#define SSC_CKS_CLOCK 1
|
||||
#define SSC_CKS_PIN 2
|
||||
|
||||
/* FSEDGE bit field values */
|
||||
#define SSC_FSEDGE_POSITIVE 0
|
||||
#define SSC_FSEDGE_NEGATIVE 1
|
||||
|
||||
/* FSOS bit field values */
|
||||
#define SSC_FSOS_NONE 0
|
||||
#define SSC_FSOS_NEGATIVE 1
|
||||
#define SSC_FSOS_POSITIVE 2
|
||||
#define SSC_FSOS_LOW 3
|
||||
#define SSC_FSOS_HIGH 4
|
||||
#define SSC_FSOS_TOGGLE 5
|
||||
|
||||
#define START_DELAY 1
|
||||
|
||||
struct atmel_ssc_state {
|
||||
u32 ssc_cmr;
|
||||
u32 ssc_rcmr;
|
||||
u32 ssc_rfmr;
|
||||
u32 ssc_tcmr;
|
||||
u32 ssc_tfmr;
|
||||
u32 ssc_sr;
|
||||
u32 ssc_imr;
|
||||
};
|
||||
|
||||
|
||||
struct atmel_ssc_info {
|
||||
char *name;
|
||||
struct ssc_device *ssc;
|
||||
spinlock_t lock; /* lock for dir_mask */
|
||||
unsigned short dir_mask; /* 0=unused, 1=playback, 2=capture */
|
||||
unsigned short initialized; /* true if SSC has been initialized */
|
||||
unsigned short daifmt;
|
||||
unsigned short cmr_div;
|
||||
unsigned short tcmr_period;
|
||||
unsigned short rcmr_period;
|
||||
struct atmel_pcm_dma_params *dma_params[2];
|
||||
struct atmel_ssc_state ssc_state;
|
||||
};
|
||||
|
||||
int atmel_ssc_set_audio(int ssc_id);
|
||||
void atmel_ssc_put_audio(int ssc_id);
|
||||
|
||||
#endif /* _AT91_SSC_DAI_H */
|
196
sound/soc/atmel/atmel_wm8904.c
Normal file
196
sound/soc/atmel/atmel_wm8904.c
Normal file
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* atmel_wm8904 - Atmel ASoC driver for boards with WM8904 codec.
|
||||
*
|
||||
* Copyright (C) 2012 Atmel
|
||||
*
|
||||
* Author: Bo Shen <voice.shen@atmel.com>
|
||||
*
|
||||
* GPLv2 or later
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include "../codecs/wm8904.h"
|
||||
#include "atmel_ssc_dai.h"
|
||||
|
||||
static const struct snd_soc_dapm_widget atmel_asoc_wm8904_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_HP("Headphone Jack", NULL),
|
||||
SND_SOC_DAPM_MIC("Mic", NULL),
|
||||
SND_SOC_DAPM_LINE("Line In Jack", NULL),
|
||||
};
|
||||
|
||||
static int atmel_asoc_wm8904_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_dai *codec_dai = rtd->codec_dai;
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_dai_set_pll(codec_dai, WM8904_FLL_MCLK, WM8904_FLL_MCLK,
|
||||
32768, params_rate(params) * 256);
|
||||
if (ret < 0) {
|
||||
pr_err("%s - failed to set wm8904 codec PLL.", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* As here wm8904 use FLL output as its system clock
|
||||
* so calling set_sysclk won't care freq parameter
|
||||
* then we pass 0
|
||||
*/
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8904_CLK_FLL,
|
||||
0, SND_SOC_CLOCK_IN);
|
||||
if (ret < 0) {
|
||||
pr_err("%s -failed to set wm8904 SYSCLK\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops atmel_asoc_wm8904_ops = {
|
||||
.hw_params = atmel_asoc_wm8904_hw_params,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_link atmel_asoc_wm8904_dailink = {
|
||||
.name = "WM8904",
|
||||
.stream_name = "WM8904 PCM",
|
||||
.codec_dai_name = "wm8904-hifi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S
|
||||
| SND_SOC_DAIFMT_NB_NF
|
||||
| SND_SOC_DAIFMT_CBM_CFM,
|
||||
.ops = &atmel_asoc_wm8904_ops,
|
||||
};
|
||||
|
||||
static struct snd_soc_card atmel_asoc_wm8904_card = {
|
||||
.name = "atmel_asoc_wm8904",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = &atmel_asoc_wm8904_dailink,
|
||||
.num_links = 1,
|
||||
.dapm_widgets = atmel_asoc_wm8904_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(atmel_asoc_wm8904_dapm_widgets),
|
||||
.fully_routed = true,
|
||||
};
|
||||
|
||||
static int atmel_asoc_wm8904_dt_init(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct device_node *codec_np, *cpu_np;
|
||||
struct snd_soc_card *card = &atmel_asoc_wm8904_card;
|
||||
struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink;
|
||||
int ret;
|
||||
|
||||
if (!np) {
|
||||
dev_err(&pdev->dev, "only device tree supported\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = snd_soc_of_parse_card_name(card, "atmel,model");
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to parse card name\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_of_parse_audio_routing(card, "atmel,audio-routing");
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to parse audio routing\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0);
|
||||
if (!cpu_np) {
|
||||
dev_err(&pdev->dev, "failed to get dai and pcm info\n");
|
||||
ret = -EINVAL;
|
||||
return ret;
|
||||
}
|
||||
dailink->cpu_of_node = cpu_np;
|
||||
dailink->platform_of_node = cpu_np;
|
||||
of_node_put(cpu_np);
|
||||
|
||||
codec_np = of_parse_phandle(np, "atmel,audio-codec", 0);
|
||||
if (!codec_np) {
|
||||
dev_err(&pdev->dev, "failed to get codec info\n");
|
||||
ret = -EINVAL;
|
||||
return ret;
|
||||
}
|
||||
dailink->codec_of_node = codec_np;
|
||||
of_node_put(codec_np);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_asoc_wm8904_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = &atmel_asoc_wm8904_card;
|
||||
struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink;
|
||||
int id, ret;
|
||||
|
||||
card->dev = &pdev->dev;
|
||||
ret = atmel_asoc_wm8904_dt_init(pdev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to init dt info\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
id = of_alias_get_id((struct device_node *)dailink->cpu_of_node, "ssc");
|
||||
ret = atmel_ssc_set_audio(id);
|
||||
if (ret != 0) {
|
||||
dev_err(&pdev->dev, "failed to set SSC %d for audio\n", id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_card(card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed\n");
|
||||
goto err_set_audio;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_set_audio:
|
||||
atmel_ssc_put_audio(id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int atmel_asoc_wm8904_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
||||
struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink;
|
||||
int id;
|
||||
|
||||
id = of_alias_get_id((struct device_node *)dailink->cpu_of_node, "ssc");
|
||||
|
||||
snd_soc_unregister_card(card);
|
||||
atmel_ssc_put_audio(id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id atmel_asoc_wm8904_dt_ids[] = {
|
||||
{ .compatible = "atmel,asoc-wm8904", },
|
||||
{ }
|
||||
};
|
||||
#endif
|
||||
|
||||
static struct platform_driver atmel_asoc_wm8904_driver = {
|
||||
.driver = {
|
||||
.name = "atmel-wm8904-audio",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(atmel_asoc_wm8904_dt_ids),
|
||||
},
|
||||
.probe = atmel_asoc_wm8904_probe,
|
||||
.remove = atmel_asoc_wm8904_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(atmel_asoc_wm8904_driver);
|
||||
|
||||
/* Module information */
|
||||
MODULE_AUTHOR("Bo Shen <voice.shen@atmel.com>");
|
||||
MODULE_DESCRIPTION("ALSA SoC machine driver for Atmel EK with WM8904 codec");
|
||||
MODULE_LICENSE("GPL");
|
325
sound/soc/atmel/sam9g20_wm8731.c
Normal file
325
sound/soc/atmel/sam9g20_wm8731.c
Normal file
|
@ -0,0 +1,325 @@
|
|||
/*
|
||||
* sam9g20_wm8731 -- SoC audio for AT91SAM9G20-based
|
||||
* ATMEL AT91SAM9G20ek board.
|
||||
*
|
||||
* Copyright (C) 2005 SAN People
|
||||
* Copyright (C) 2008 Atmel
|
||||
*
|
||||
* Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
|
||||
*
|
||||
* Based on ati_b1_wm8731.c by:
|
||||
* Frank Mandarino <fmandarino@endrelia.com>
|
||||
* Copyright 2006 Endrelia Technologies Inc.
|
||||
* Based on corgi.c by:
|
||||
* Copyright 2005 Wolfson Microelectronics PLC.
|
||||
* Copyright 2005 Openedhand Ltd.
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#include <linux/atmel-ssc.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <asm/mach-types.h>
|
||||
#include <mach/hardware.h>
|
||||
|
||||
#include "../codecs/wm8731.h"
|
||||
#include "atmel-pcm.h"
|
||||
#include "atmel_ssc_dai.h"
|
||||
|
||||
#define MCLK_RATE 12000000
|
||||
|
||||
/*
|
||||
* As shipped the board does not have inputs. However, it is relatively
|
||||
* straightforward to modify the board to hook them up so support is left
|
||||
* in the driver.
|
||||
*/
|
||||
#undef ENABLE_MIC_INPUT
|
||||
|
||||
static struct clk *mclk;
|
||||
|
||||
static int at91sam9g20ek_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_dai *codec_dai = rtd->codec_dai;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
int ret;
|
||||
|
||||
/* set codec DAI configuration */
|
||||
ret = snd_soc_dai_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 = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops at91sam9g20ek_ops = {
|
||||
.hw_params = at91sam9g20ek_hw_params,
|
||||
};
|
||||
|
||||
static int at91sam9g20ek_set_bias_level(struct snd_soc_card *card,
|
||||
struct snd_soc_dapm_context *dapm,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
static int mclk_on;
|
||||
int ret = 0;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_ON:
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
if (!mclk_on)
|
||||
ret = clk_enable(mclk);
|
||||
if (ret == 0)
|
||||
mclk_on = 1;
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_OFF:
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
if (mclk_on)
|
||||
clk_disable(mclk);
|
||||
mclk_on = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct snd_soc_dapm_widget at91sam9g20ek_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_MIC("Int Mic", NULL),
|
||||
SND_SOC_DAPM_SPK("Ext Spk", NULL),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route intercon[] = {
|
||||
|
||||
/* 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"},
|
||||
};
|
||||
|
||||
/*
|
||||
* Logic for a wm8731 as connected on a at91sam9g20ek board.
|
||||
*/
|
||||
static int at91sam9g20ek_wm8731_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_codec *codec = rtd->codec;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct snd_soc_dapm_context *dapm = &codec->dapm;
|
||||
int ret;
|
||||
|
||||
printk(KERN_DEBUG
|
||||
"at91sam9g20ek_wm8731 "
|
||||
": at91sam9g20ek_wm8731_init() called\n");
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_MCLK,
|
||||
MCLK_RATE, SND_SOC_CLOCK_IN);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Failed to set WM8731 SYSCLK: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* not connected */
|
||||
snd_soc_dapm_nc_pin(dapm, "RLINEIN");
|
||||
snd_soc_dapm_nc_pin(dapm, "LLINEIN");
|
||||
|
||||
#ifndef ENABLE_MIC_INPUT
|
||||
snd_soc_dapm_nc_pin(&rtd->card->dapm, "Int Mic");
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_link at91sam9g20ek_dai = {
|
||||
.name = "WM8731",
|
||||
.stream_name = "WM8731 PCM",
|
||||
.cpu_dai_name = "at91rm9200_ssc.0",
|
||||
.codec_dai_name = "wm8731-hifi",
|
||||
.init = at91sam9g20ek_wm8731_init,
|
||||
.platform_name = "at91rm9200_ssc.0",
|
||||
.codec_name = "wm8731.0-001b",
|
||||
.ops = &at91sam9g20ek_ops,
|
||||
};
|
||||
|
||||
static struct snd_soc_card snd_soc_at91sam9g20ek = {
|
||||
.name = "AT91SAMG20-EK",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = &at91sam9g20ek_dai,
|
||||
.num_links = 1,
|
||||
.set_bias_level = at91sam9g20ek_set_bias_level,
|
||||
|
||||
.dapm_widgets = at91sam9g20ek_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(at91sam9g20ek_dapm_widgets),
|
||||
.dapm_routes = intercon,
|
||||
.num_dapm_routes = ARRAY_SIZE(intercon),
|
||||
};
|
||||
|
||||
static int at91sam9g20ek_audio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct device_node *codec_np, *cpu_np;
|
||||
struct clk *pllb;
|
||||
struct snd_soc_card *card = &snd_soc_at91sam9g20ek;
|
||||
int ret;
|
||||
|
||||
if (!np) {
|
||||
if (!(machine_is_at91sam9g20ek() ||
|
||||
machine_is_at91sam9g20ek_2mmc()))
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = atmel_ssc_set_audio(0);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "ssc channel is not valid\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Codec MCLK is supplied by PCK0 - set it up.
|
||||
*/
|
||||
mclk = clk_get(NULL, "pck0");
|
||||
if (IS_ERR(mclk)) {
|
||||
printk(KERN_ERR "ASoC: Failed to get MCLK\n");
|
||||
ret = PTR_ERR(mclk);
|
||||
goto err;
|
||||
}
|
||||
|
||||
pllb = clk_get(NULL, "pllb");
|
||||
if (IS_ERR(pllb)) {
|
||||
printk(KERN_ERR "ASoC: Failed to get PLLB\n");
|
||||
ret = PTR_ERR(pllb);
|
||||
goto err_mclk;
|
||||
}
|
||||
ret = clk_set_parent(mclk, pllb);
|
||||
clk_put(pllb);
|
||||
if (ret != 0) {
|
||||
printk(KERN_ERR "ASoC: Failed to set MCLK parent\n");
|
||||
goto err_mclk;
|
||||
}
|
||||
|
||||
clk_set_rate(mclk, MCLK_RATE);
|
||||
|
||||
card->dev = &pdev->dev;
|
||||
|
||||
/* Parse device node info */
|
||||
if (np) {
|
||||
ret = snd_soc_of_parse_card_name(card, "atmel,model");
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
ret = snd_soc_of_parse_audio_routing(card,
|
||||
"atmel,audio-routing");
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
/* Parse codec info */
|
||||
at91sam9g20ek_dai.codec_name = NULL;
|
||||
codec_np = of_parse_phandle(np, "atmel,audio-codec", 0);
|
||||
if (!codec_np) {
|
||||
dev_err(&pdev->dev, "codec info missing\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
at91sam9g20ek_dai.codec_of_node = codec_np;
|
||||
|
||||
/* Parse dai and platform info */
|
||||
at91sam9g20ek_dai.cpu_dai_name = NULL;
|
||||
at91sam9g20ek_dai.platform_name = NULL;
|
||||
cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0);
|
||||
if (!cpu_np) {
|
||||
dev_err(&pdev->dev, "dai and pcm info missing\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
at91sam9g20ek_dai.cpu_of_node = cpu_np;
|
||||
at91sam9g20ek_dai.platform_of_node = cpu_np;
|
||||
|
||||
of_node_put(codec_np);
|
||||
of_node_put(cpu_np);
|
||||
}
|
||||
|
||||
ret = snd_soc_register_card(card);
|
||||
if (ret) {
|
||||
printk(KERN_ERR "ASoC: snd_soc_register_card() failed\n");
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
err_mclk:
|
||||
clk_put(mclk);
|
||||
mclk = NULL;
|
||||
err:
|
||||
atmel_ssc_put_audio(0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int at91sam9g20ek_audio_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
||||
|
||||
clk_disable(mclk);
|
||||
mclk = NULL;
|
||||
snd_soc_unregister_card(card);
|
||||
atmel_ssc_put_audio(0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id at91sam9g20ek_wm8731_dt_ids[] = {
|
||||
{ .compatible = "atmel,at91sam9g20ek-wm8731-audio", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, at91sam9g20ek_wm8731_dt_ids);
|
||||
#endif
|
||||
|
||||
static struct platform_driver at91sam9g20ek_audio_driver = {
|
||||
.driver = {
|
||||
.name = "at91sam9g20ek-audio",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(at91sam9g20ek_wm8731_dt_ids),
|
||||
},
|
||||
.probe = at91sam9g20ek_audio_probe,
|
||||
.remove = at91sam9g20ek_audio_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(at91sam9g20ek_audio_driver);
|
||||
|
||||
/* Module information */
|
||||
MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>");
|
||||
MODULE_DESCRIPTION("ALSA SoC AT91SAM9G20EK_WM8731");
|
||||
MODULE_ALIAS("platform:at91sam9g20ek-audio");
|
||||
MODULE_LICENSE("GPL");
|
208
sound/soc/atmel/sam9x5_wm8731.c
Normal file
208
sound/soc/atmel/sam9x5_wm8731.c
Normal file
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* sam9x5_wm8731 -- SoC audio for AT91SAM9X5-based boards
|
||||
* that are using WM8731 as codec.
|
||||
*
|
||||
* Copyright (C) 2011 Atmel,
|
||||
* Nicolas Ferre <nicolas.ferre@atmel.com>
|
||||
*
|
||||
* Copyright (C) 2013 Paratronic,
|
||||
* Richard Genoud <richard.genoud@gmail.com>
|
||||
*
|
||||
* Based on sam9g20_wm8731.c by:
|
||||
* Sedji Gaouaou <sedji.gaouaou@atmel.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/of.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/device.h>
|
||||
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dai.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
|
||||
#include "../codecs/wm8731.h"
|
||||
#include "atmel_ssc_dai.h"
|
||||
|
||||
|
||||
#define MCLK_RATE 12288000
|
||||
|
||||
#define DRV_NAME "sam9x5-snd-wm8731"
|
||||
|
||||
struct sam9x5_drvdata {
|
||||
int ssc_id;
|
||||
};
|
||||
|
||||
/*
|
||||
* Logic for a wm8731 as connected on a at91sam9x5ek based board.
|
||||
*/
|
||||
static int sam9x5_wm8731_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct device *dev = rtd->dev;
|
||||
int ret;
|
||||
|
||||
dev_dbg(dev, "ASoC: %s called\n", __func__);
|
||||
|
||||
/* set the codec system clock for DAC and ADC */
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL,
|
||||
MCLK_RATE, SND_SOC_CLOCK_IN);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "ASoC: Failed to set WM8731 SYSCLK: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Audio paths on at91sam9x5ek board:
|
||||
*
|
||||
* |A| ------------> | | ---R----> Headphone Jack
|
||||
* |T| <----\ | WM | ---L--/
|
||||
* |9| ---> CLK <--> | 8731 | <--R----- Line In Jack
|
||||
* |1| <------------ | | <--L--/
|
||||
*/
|
||||
static const struct snd_soc_dapm_widget sam9x5_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_HP("Headphone Jack", NULL),
|
||||
SND_SOC_DAPM_LINE("Line In Jack", NULL),
|
||||
};
|
||||
|
||||
static int sam9x5_wm8731_driver_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct device_node *codec_np, *cpu_np;
|
||||
struct snd_soc_card *card;
|
||||
struct snd_soc_dai_link *dai;
|
||||
struct sam9x5_drvdata *priv;
|
||||
int ret;
|
||||
|
||||
if (!np) {
|
||||
dev_err(&pdev->dev, "No device node supplied\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
card = devm_kzalloc(&pdev->dev, sizeof(*card), GFP_KERNEL);
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
dai = devm_kzalloc(&pdev->dev, sizeof(*dai), GFP_KERNEL);
|
||||
if (!dai || !card || !priv) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
snd_soc_card_set_drvdata(card, priv);
|
||||
|
||||
card->dev = &pdev->dev;
|
||||
card->owner = THIS_MODULE;
|
||||
card->dai_link = dai;
|
||||
card->num_links = 1;
|
||||
card->dapm_widgets = sam9x5_dapm_widgets;
|
||||
card->num_dapm_widgets = ARRAY_SIZE(sam9x5_dapm_widgets);
|
||||
dai->name = "WM8731";
|
||||
dai->stream_name = "WM8731 PCM";
|
||||
dai->codec_dai_name = "wm8731-hifi";
|
||||
dai->init = sam9x5_wm8731_init;
|
||||
dai->dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF
|
||||
| SND_SOC_DAIFMT_CBM_CFM;
|
||||
|
||||
ret = snd_soc_of_parse_card_name(card, "atmel,model");
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "atmel,model node missing\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = snd_soc_of_parse_audio_routing(card, "atmel,audio-routing");
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "atmel,audio-routing node missing\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
codec_np = of_parse_phandle(np, "atmel,audio-codec", 0);
|
||||
if (!codec_np) {
|
||||
dev_err(&pdev->dev, "atmel,audio-codec node missing\n");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
dai->codec_of_node = codec_np;
|
||||
|
||||
cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0);
|
||||
if (!cpu_np) {
|
||||
dev_err(&pdev->dev, "atmel,ssc-controller node missing\n");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
dai->cpu_of_node = cpu_np;
|
||||
dai->platform_of_node = cpu_np;
|
||||
|
||||
priv->ssc_id = of_alias_get_id(cpu_np, "ssc");
|
||||
|
||||
ret = atmel_ssc_set_audio(priv->ssc_id);
|
||||
if (ret != 0) {
|
||||
dev_err(&pdev->dev,
|
||||
"ASoC: Failed to set SSC %d for audio: %d\n",
|
||||
ret, priv->ssc_id);
|
||||
goto out;
|
||||
}
|
||||
|
||||
of_node_put(codec_np);
|
||||
of_node_put(cpu_np);
|
||||
|
||||
ret = snd_soc_register_card(card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"ASoC: Platform device allocation failed\n");
|
||||
goto out_put_audio;
|
||||
}
|
||||
|
||||
dev_dbg(&pdev->dev, "ASoC: %s ok\n", __func__);
|
||||
|
||||
return ret;
|
||||
|
||||
out_put_audio:
|
||||
atmel_ssc_put_audio(priv->ssc_id);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sam9x5_wm8731_driver_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
||||
struct sam9x5_drvdata *priv = card->drvdata;
|
||||
|
||||
snd_soc_unregister_card(card);
|
||||
atmel_ssc_put_audio(priv->ssc_id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id sam9x5_wm8731_of_match[] = {
|
||||
{ .compatible = "atmel,sam9x5-wm8731-audio", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sam9x5_wm8731_of_match);
|
||||
|
||||
static struct platform_driver sam9x5_wm8731_driver = {
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(sam9x5_wm8731_of_match),
|
||||
},
|
||||
.probe = sam9x5_wm8731_driver_probe,
|
||||
.remove = sam9x5_wm8731_driver_remove,
|
||||
};
|
||||
module_platform_driver(sam9x5_wm8731_driver);
|
||||
|
||||
/* Module information */
|
||||
MODULE_AUTHOR("Nicolas Ferre <nicolas.ferre@atmel.com>");
|
||||
MODULE_AUTHOR("Richard Genoud <richard.genoud@gmail.com>");
|
||||
MODULE_DESCRIPTION("ALSA SoC machine driver for AT91SAM9x5 - WM8731");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:" DRV_NAME);
|
151
sound/soc/atmel/snd-soc-afeb9260.c
Normal file
151
sound/soc/atmel/snd-soc-afeb9260.c
Normal file
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* afeb9260.c -- SoC audio for AFEB9260
|
||||
*
|
||||
* Copyright (C) 2009 Sergey Lapin <slapin@ossfans.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* 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., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <linux/atmel-ssc.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <asm/mach-types.h>
|
||||
#include <mach/hardware.h>
|
||||
#include <linux/gpio.h>
|
||||
|
||||
#include "../codecs/tlv320aic23.h"
|
||||
#include "atmel-pcm.h"
|
||||
#include "atmel_ssc_dai.h"
|
||||
|
||||
#define CODEC_CLOCK 12000000
|
||||
|
||||
static int afeb9260_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_dai *codec_dai = rtd->codec_dai;
|
||||
int err;
|
||||
|
||||
/* Set the codec system clock for DAC and ADC */
|
||||
err =
|
||||
snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, SND_SOC_CLOCK_IN);
|
||||
|
||||
if (err < 0) {
|
||||
printk(KERN_ERR "can't set codec system clock\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops afeb9260_ops = {
|
||||
.hw_params = afeb9260_hw_params,
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_HP("Headphone Jack", NULL),
|
||||
SND_SOC_DAPM_LINE("Line In", NULL),
|
||||
SND_SOC_DAPM_MIC("Mic Jack", NULL),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route afeb9260_audio_map[] = {
|
||||
{"Headphone Jack", NULL, "LHPOUT"},
|
||||
{"Headphone Jack", NULL, "RHPOUT"},
|
||||
|
||||
{"LLINEIN", NULL, "Line In"},
|
||||
{"RLINEIN", NULL, "Line In"},
|
||||
|
||||
{"MICIN", NULL, "Mic Jack"},
|
||||
};
|
||||
|
||||
|
||||
/* Digital audio interface glue - connects codec <--> CPU */
|
||||
static struct snd_soc_dai_link afeb9260_dai = {
|
||||
.name = "TLV320AIC23",
|
||||
.stream_name = "AIC23",
|
||||
.cpu_dai_name = "atmel-ssc-dai.0",
|
||||
.codec_dai_name = "tlv320aic23-hifi",
|
||||
.platform_name = "atmel_pcm-audio",
|
||||
.codec_name = "tlv320aic23-codec.0-001a",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_IF |
|
||||
SND_SOC_DAIFMT_CBM_CFM,
|
||||
.ops = &afeb9260_ops,
|
||||
};
|
||||
|
||||
/* Audio machine driver */
|
||||
static struct snd_soc_card snd_soc_machine_afeb9260 = {
|
||||
.name = "AFEB9260",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = &afeb9260_dai,
|
||||
.num_links = 1,
|
||||
|
||||
.dapm_widgets = tlv320aic23_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(tlv320aic23_dapm_widgets),
|
||||
.dapm_routes = afeb9260_audio_map,
|
||||
.num_dapm_routes = ARRAY_SIZE(afeb9260_audio_map),
|
||||
};
|
||||
|
||||
static struct platform_device *afeb9260_snd_device;
|
||||
|
||||
static int __init afeb9260_soc_init(void)
|
||||
{
|
||||
int err;
|
||||
struct device *dev;
|
||||
|
||||
if (!(machine_is_afeb9260()))
|
||||
return -ENODEV;
|
||||
|
||||
|
||||
afeb9260_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!afeb9260_snd_device) {
|
||||
printk(KERN_ERR "ASoC: Platform device allocation failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
platform_set_drvdata(afeb9260_snd_device, &snd_soc_machine_afeb9260);
|
||||
err = platform_device_add(afeb9260_snd_device);
|
||||
if (err)
|
||||
goto err1;
|
||||
|
||||
dev = &afeb9260_snd_device->dev;
|
||||
|
||||
return 0;
|
||||
err1:
|
||||
platform_device_put(afeb9260_snd_device);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit afeb9260_soc_exit(void)
|
||||
{
|
||||
platform_device_unregister(afeb9260_snd_device);
|
||||
}
|
||||
|
||||
module_init(afeb9260_soc_init);
|
||||
module_exit(afeb9260_soc_exit);
|
||||
|
||||
MODULE_AUTHOR("Sergey Lapin <slapin@ossfans.org>");
|
||||
MODULE_DESCRIPTION("ALSA SoC for AFEB9260");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue