diff options
Diffstat (limited to 'sound/soc/atmel')
| -rw-r--r-- | sound/soc/atmel/Kconfig | 40 | ||||
| -rw-r--r-- | sound/soc/atmel/Makefile | 8 | ||||
| -rw-r--r-- | sound/soc/atmel/atmel-pcm-dma.c | 144 | ||||
| -rw-r--r-- | sound/soc/atmel/atmel-pcm-pdc.c | 337 | ||||
| -rw-r--r-- | sound/soc/atmel/atmel-pcm.c | 412 | ||||
| -rw-r--r-- | sound/soc/atmel/atmel-pcm.h | 36 | ||||
| -rw-r--r-- | sound/soc/atmel/atmel_ssc_dai.c | 303 | ||||
| -rw-r--r-- | sound/soc/atmel/atmel_ssc_dai.h | 3 | ||||
| -rw-r--r-- | sound/soc/atmel/atmel_wm8904.c | 246 | ||||
| -rw-r--r-- | sound/soc/atmel/sam9g20_wm8731.c | 131 | ||||
| -rw-r--r-- | sound/soc/atmel/sam9x5_wm8731.c | 208 | ||||
| -rw-r--r-- | sound/soc/atmel/snd-soc-afeb9260.c | 49 |
12 files changed, 1282 insertions, 635 deletions
diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig index 72b09cfd3dc..27e3fc4a536 100644 --- a/sound/soc/atmel/Kconfig +++ b/sound/soc/atmel/Kconfig @@ -1,11 +1,20 @@ config SND_ATMEL_SOC tristate "SoC Audio for the Atmel System-on-Chip" - depends on ARCH_AT91 + 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 @@ -16,18 +25,39 @@ config SND_ATMEL_SOC_SSC config SND_AT91_SOC_SAM9G20_WM8731 tristate "SoC Audio support for WM8731-based At91sam9g20 evaluation board" - depends on ATMEL_SSC && ARCH_AT91SAM9G20 && SND_ATMEL_SOC && \ - AT91_PROGRAMMABLE_CLOCKS + 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 ATMEL_SSC && ARCH_AT91 && MACH_AFEB9260 && SND_ATMEL_SOC + 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 + select SND_SOC_TLV320AIC23_I2C help Say Y here to support sound on AFEB9260 board. diff --git a/sound/soc/atmel/Makefile b/sound/soc/atmel/Makefile index a5c0bf19da7..5baabc8bde3 100644 --- a/sound/soc/atmel/Makefile +++ b/sound/soc/atmel/Makefile @@ -1,12 +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 diff --git a/sound/soc/atmel/atmel-pcm-dma.c b/sound/soc/atmel/atmel-pcm-dma.c new file mode 100644 index 00000000000..b79a2a86451 --- /dev/null +++ b/sound/soc/atmel/atmel-pcm-dma.c @@ -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"); diff --git a/sound/soc/atmel/atmel-pcm-pdc.c b/sound/soc/atmel/atmel-pcm-pdc.c new file mode 100644 index 00000000000..a366b3503c2 --- /dev/null +++ b/sound/soc/atmel/atmel-pcm-pdc.c @@ -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"); diff --git a/sound/soc/atmel/atmel-pcm.c b/sound/soc/atmel/atmel-pcm.c index a21ff459e5d..8ae3fa5ac60 100644 --- a/sound/soc/atmel/atmel-pcm.c +++ b/sound/soc/atmel/atmel-pcm.c @@ -32,80 +32,25 @@ */ #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, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - .period_bytes_min = 32, - .period_bytes_max = 8192, - .periods_min = 2, - .periods_max = 1024, - .buffer_bytes_max = 32 * 1024, -}; - - -/*--------------------------------------------------------------------------*\ - * 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 */ - - /* PDC register save */ - u32 pdc_xpr_save; - u32 pdc_xcr_save; - u32 pdc_xnpr_save; - u32 pdc_xncr_save; -}; - - -/*--------------------------------------------------------------------------*\ - * Helper functions -\*--------------------------------------------------------------------------*/ 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_pcm_hardware.buffer_bytes_max; + 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:" - "preallocate_dma_buffer: area=%p, addr=%p, size=%d\n", - (void *) buf->area, - (void *) buf->addr, - 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; @@ -113,269 +58,28 @@ static int atmel_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, buf->bytes = size; return 0; } -/*--------------------------------------------------------------------------*\ - * 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_warning("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=%u, period_size=%u)\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 = %u\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 int atmel_pcm_mmap(struct snd_pcm_substream *substream, +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); -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, -}; - - -/*--------------------------------------------------------------------------*\ - * ASoC platform driver -\*--------------------------------------------------------------------------*/ -static u64 atmel_pcm_dmamask = 0xffffffff; - -static int atmel_pcm_new(struct snd_soc_pcm_runtime *rtd) +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 = 0; + int ret; - if (!card->dev->dma_mask) - card->dev->dma_mask = &atmel_pcm_dmamask; - if (!card->dev->coherent_dma_mask) - card->dev->coherent_dma_mask = 0xffffffff; + 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) @@ -383,8 +87,7 @@ static int atmel_pcm_new(struct snd_soc_pcm_runtime *rtd) } if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { - pr_debug("atmel-pcm:" - "Allocating PCM capture DMA buffer\n"); + pr_debug("atmel-pcm: allocating PCM capture DMA buffer\n"); ret = atmel_pcm_preallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE); if (ret) @@ -393,8 +96,9 @@ static int atmel_pcm_new(struct snd_soc_pcm_runtime *rtd) out: return ret; } +EXPORT_SYMBOL_GPL(atmel_pcm_new); -static void atmel_pcm_free_dma_buffers(struct snd_pcm *pcm) +void atmel_pcm_free(struct snd_pcm *pcm) { struct snd_pcm_substream *substream; struct snd_dma_buffer *buf; @@ -413,89 +117,5 @@ static void atmel_pcm_free_dma_buffers(struct snd_pcm *pcm) buf->area = NULL; } } +EXPORT_SYMBOL_GPL(atmel_pcm_free); -#ifdef CONFIG_PM -static int atmel_pcm_suspend(struct snd_soc_dai *dai) -{ - struct snd_pcm_runtime *runtime = dai->runtime; - struct atmel_runtime_data *prtd; - struct atmel_pcm_dma_params *params; - - if (!runtime) - return 0; - - prtd = runtime->private_data; - params = prtd->params; - - /* disable the PDC and save the PDC registers */ - - ssc_writel(params->ssc->regs, PDC_PTCR, params->mask->pdc_disable); - - prtd->pdc_xpr_save = ssc_readx(params->ssc->regs, params->pdc->xpr); - prtd->pdc_xcr_save = ssc_readx(params->ssc->regs, params->pdc->xcr); - prtd->pdc_xnpr_save = ssc_readx(params->ssc->regs, params->pdc->xnpr); - prtd->pdc_xncr_save = ssc_readx(params->ssc->regs, params->pdc->xncr); - - return 0; -} - -static int atmel_pcm_resume(struct snd_soc_dai *dai) -{ - struct snd_pcm_runtime *runtime = dai->runtime; - struct atmel_runtime_data *prtd; - struct atmel_pcm_dma_params *params; - - if (!runtime) - return 0; - - prtd = runtime->private_data; - params = prtd->params; - - /* restore the PDC registers and enable the PDC */ - ssc_writex(params->ssc->regs, params->pdc->xpr, prtd->pdc_xpr_save); - ssc_writex(params->ssc->regs, params->pdc->xcr, prtd->pdc_xcr_save); - ssc_writex(params->ssc->regs, params->pdc->xnpr, prtd->pdc_xnpr_save); - ssc_writex(params->ssc->regs, params->pdc->xncr, prtd->pdc_xncr_save); - - ssc_writel(params->ssc->regs, PDC_PTCR, params->mask->pdc_enable); - return 0; -} -#else -#define atmel_pcm_suspend NULL -#define atmel_pcm_resume NULL -#endif - -static struct snd_soc_platform_driver atmel_soc_platform = { - .ops = &atmel_pcm_ops, - .pcm_new = atmel_pcm_new, - .pcm_free = atmel_pcm_free_dma_buffers, - .suspend = atmel_pcm_suspend, - .resume = atmel_pcm_resume, -}; - -static int __devinit atmel_soc_platform_probe(struct platform_device *pdev) -{ - return snd_soc_register_platform(&pdev->dev, &atmel_soc_platform); -} - -static int __devexit atmel_soc_platform_remove(struct platform_device *pdev) -{ - snd_soc_unregister_platform(&pdev->dev); - return 0; -} - -static struct platform_driver atmel_pcm_driver = { - .driver = { - .name = "atmel-pcm-audio", - .owner = THIS_MODULE, - }, - - .probe = atmel_soc_platform_probe, - .remove = __devexit_p(atmel_soc_platform_remove), -}; - -module_platform_driver(atmel_pcm_driver); - -MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>"); -MODULE_DESCRIPTION("Atmel PCM module"); -MODULE_LICENSE("GPL"); diff --git a/sound/soc/atmel/atmel-pcm.h b/sound/soc/atmel/atmel-pcm.h index 5e0a95e6432..12ae814eff2 100644 --- a/sound/soc/atmel/atmel-pcm.h +++ b/sound/soc/atmel/atmel-pcm.h @@ -36,6 +36,8 @@ #include <linux/atmel-ssc.h> +#define ATMEL_SSC_DMABUF_SIZE (64 * 1024) + /* * Registers and status bits that are required by the PCM driver. */ @@ -50,6 +52,7 @@ struct atmel_pdc_regs { 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 */ @@ -80,4 +83,37 @@ struct atmel_pcm_dma_params { #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 */ diff --git a/sound/soc/atmel/atmel_ssc_dai.c b/sound/soc/atmel/atmel_ssc_dai.c index 354341ec0f4..de433cfd044 100644 --- a/sound/soc/atmel/atmel_ssc_dai.c +++ b/sound/soc/atmel/atmel_ssc_dai.c @@ -42,17 +42,11 @@ #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. @@ -79,6 +73,7 @@ static struct atmel_ssc_mask ssc_tx_mask = { .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, }; @@ -88,6 +83,7 @@ static struct atmel_ssc_mask ssc_rx_mask = { .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, }; @@ -107,7 +103,6 @@ static struct atmel_pcm_dma_params ssc_dma_params[NUM_SSC_DEVICES][2] = { .pdc = &pdc_rx_reg, .mask = &ssc_rx_mask, } }, -#if NUM_SSC_DEVICES == 3 {{ .name = "SSC1 PCM out", .pdc = &pdc_tx_reg, @@ -128,7 +123,6 @@ static struct atmel_pcm_dma_params ssc_dma_params[NUM_SSC_DEVICES][2] = { .pdc = &pdc_rx_reg, .mask = &ssc_rx_mask, } }, -#endif }; @@ -139,7 +133,6 @@ static struct atmel_ssc_info ssc_info[NUM_SSC_DEVICES] = { .dir_mask = SSC_DIR_MASK_UNUSED, .initialized = 0, }, -#if NUM_SSC_DEVICES == 3 { .name = "ssc1", .lock = __SPIN_LOCK_UNLOCKED(ssc_info[1].lock), @@ -152,7 +145,6 @@ static struct atmel_ssc_info ssc_info[NUM_SSC_DEVICES] = { .dir_mask = SSC_DIR_MASK_UNUSED, .initialized = 0, }, -#endif }; @@ -206,15 +198,27 @@ 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]; - int dir_mask; + 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) + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dir = 0; dir_mask = SSC_DIR_MASK_PLAYBACK; - else + } 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) { @@ -335,9 +339,9 @@ 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 = 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; @@ -354,19 +358,7 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream, 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 snd_soc_pcm_stream->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. - */ - snd_soc_dai_set_dma_data(rtd->cpu_dai, substream, dma_params); + dma_params = ssc_p->dma_params[dir]; channels = params_channels(params); @@ -475,7 +467,8 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream, | 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); + | 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) @@ -490,7 +483,8 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream, | 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); + | 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) @@ -543,6 +537,51 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream, 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); @@ -614,7 +653,8 @@ static int atmel_ssc_prepare(struct snd_pcm_substream *substream, dma_params = ssc_p->dma_params[dir]; - ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_enable); + 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", @@ -622,6 +662,33 @@ static int atmel_ssc_prepare(struct snd_pcm_substream *substream, 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) @@ -687,33 +754,6 @@ static int atmel_ssc_resume(struct snd_soc_dai *cpu_dai) # define atmel_ssc_resume NULL #endif /* CONFIG_PM */ -static int atmel_ssc_probe(struct snd_soc_dai *dai) -{ - struct atmel_ssc_info *ssc_p = &ssc_info[dai->id]; - int ret = 0; - - snd_soc_dai_set_drvdata(dai, ssc_p); - - /* - * Request SSC device - */ - ssc_p->ssc = ssc_request(dai->id); - if (IS_ERR(ssc_p->ssc)) { - printk(KERN_ERR "ASoC: Failed to request SSC %d\n", dai->id); - ret = PTR_ERR(ssc_p->ssc); - } - - return ret; -} - -static int atmel_ssc_remove(struct snd_soc_dai *dai) -{ - struct atmel_ssc_info *ssc_p = snd_soc_dai_get_drvdata(dai); - - ssc_free(ssc_p->ssc); - return 0; -} - #define ATMEL_SSC_RATES (SNDRV_PCM_RATE_8000_96000) #define ATMEL_SSC_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\ @@ -723,35 +763,13 @@ 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[NUM_SSC_DEVICES] = { - { - .name = "atmel-ssc-dai.0", - .probe = atmel_ssc_probe, - .remove = atmel_ssc_remove, - .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, - }, -#if NUM_SSC_DEVICES == 3 - { - .name = "atmel-ssc-dai.1", - .probe = atmel_ssc_probe, - .remove = atmel_ssc_remove, +static struct snd_soc_dai_driver atmel_ssc_dai = { .suspend = atmel_ssc_suspend, .resume = atmel_ssc_resume, .playback = { @@ -765,50 +783,55 @@ static struct snd_soc_dai_driver atmel_ssc_dai[NUM_SSC_DEVICES] = { .rates = ATMEL_SSC_RATES, .formats = ATMEL_SSC_FORMATS,}, .ops = &atmel_ssc_dai_ops, - }, - { - .name = "atmel-ssc-dai.2", - .probe = atmel_ssc_probe, - .remove = atmel_ssc_remove, - .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, - }, -#endif }; -static __devinit int asoc_ssc_probe(struct platform_device *pdev) -{ - BUG_ON(pdev->id < 0); - BUG_ON(pdev->id >= ARRAY_SIZE(atmel_ssc_dai)); - return snd_soc_register_dai(&pdev->dev, &atmel_ssc_dai[pdev->id]); -} +static const struct snd_soc_component_driver atmel_ssc_component = { + .name = "atmel-ssc", +}; -static int __devexit asoc_ssc_remove(struct platform_device *pdev) +static int asoc_ssc_init(struct device *dev) { - snd_soc_unregister_dai(&pdev->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 struct platform_driver asoc_ssc_driver = { - .driver = { - .name = "atmel-ssc-dai", - .owner = THIS_MODULE, - }, +static void asoc_ssc_exit(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ssc_device *ssc = platform_get_drvdata(pdev); - .probe = asoc_ssc_probe, - .remove = __devexit_p(asoc_ssc_remove), -}; + 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. @@ -816,50 +839,32 @@ static struct platform_driver asoc_ssc_driver = { int atmel_ssc_set_audio(int ssc_id) { struct ssc_device *ssc; - static struct platform_device *dma_pdev; - struct platform_device *ssc_pdev; int ret; - if (ssc_id < 0 || ssc_id >= ARRAY_SIZE(atmel_ssc_dai)) - return -EINVAL; - - /* Allocate a dummy device for DMA if we don't have one already */ - if (!dma_pdev) { - dma_pdev = platform_device_alloc("atmel-pcm-audio", -1); - if (!dma_pdev) - return -ENOMEM; - - ret = platform_device_add(dma_pdev); - if (ret < 0) { - platform_device_put(dma_pdev); - dma_pdev = NULL; - return ret; - } - } - - ssc_pdev = platform_device_alloc("atmel-ssc-dai", ssc_id); - if (!ssc_pdev) - return -ENOMEM; - /* If we can grab the SSC briefly to parent the DAI device off it */ ssc = ssc_request(ssc_id); - if (IS_ERR(ssc)) - pr_warn("Unable to parent ASoC SSC DAI on SSC: %ld\n", + if (IS_ERR(ssc)) { + pr_err("Unable to parent ASoC SSC DAI on SSC: %ld\n", PTR_ERR(ssc)); - else { - ssc_pdev->dev.parent = &(ssc->pdev->dev); - ssc_free(ssc); + return PTR_ERR(ssc); + } else { + ssc_info[ssc_id].ssc = ssc; } - ret = platform_device_add(ssc_pdev); - if (ret < 0) - platform_device_put(ssc_pdev); + ret = asoc_ssc_init(&ssc->pdev->dev); return ret; } EXPORT_SYMBOL_GPL(atmel_ssc_set_audio); -module_platform_driver(asoc_ssc_driver); +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"); diff --git a/sound/soc/atmel/atmel_ssc_dai.h b/sound/soc/atmel/atmel_ssc_dai.h index 5d4f0f9b4d9..b1f08d51149 100644 --- a/sound/soc/atmel/atmel_ssc_dai.h +++ b/sound/soc/atmel/atmel_ssc_dai.h @@ -117,6 +117,7 @@ struct atmel_ssc_info { struct atmel_ssc_state ssc_state; }; -int atmel_ssc_set_audio(int ssc); +int atmel_ssc_set_audio(int ssc_id); +void atmel_ssc_put_audio(int ssc_id); #endif /* _AT91_SSC_DAI_H */ diff --git a/sound/soc/atmel/atmel_wm8904.c b/sound/soc/atmel/atmel_wm8904.c new file mode 100644 index 00000000000..b4e36901a40 --- /dev/null +++ b/sound/soc/atmel/atmel_wm8904.c @@ -0,0 +1,246 @@ +/* + * 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" + +#define MCLK_RATE 32768 + +static struct clk *mclk; + +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 int atmel_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + if (dapm->bias_level == SND_SOC_BIAS_STANDBY) { + switch (level) { + case SND_SOC_BIAS_PREPARE: + clk_prepare_enable(mclk); + break; + case SND_SOC_BIAS_OFF: + clk_disable_unprepare(mclk); + break; + default: + break; + } + } + + return 0; +}; + +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, + .set_bias_level = atmel_set_bias_level, + .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; + struct clk *clk_src; + 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; + } + + mclk = clk_get(NULL, "pck0"); + if (IS_ERR(mclk)) { + dev_err(&pdev->dev, "failed to get pck0\n"); + ret = PTR_ERR(mclk); + goto err_set_audio; + } + + clk_src = clk_get(NULL, "clk32k"); + if (IS_ERR(clk_src)) { + dev_err(&pdev->dev, "failed to get clk32k\n"); + ret = PTR_ERR(clk_src); + goto err_set_audio; + } + + ret = clk_set_parent(mclk, clk_src); + clk_put(clk_src); + if (ret != 0) { + dev_err(&pdev->dev, "failed to set MCLK parent\n"); + goto err_set_audio; + } + + dev_info(&pdev->dev, "setting pck0 to %dHz\n", MCLK_RATE); + clk_set_rate(mclk, MCLK_RATE); + + 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"); diff --git a/sound/soc/atmel/sam9g20_wm8731.c b/sound/soc/atmel/sam9g20_wm8731.c index c88351488f4..bb1149126c5 100644 --- a/sound/soc/atmel/sam9g20_wm8731.c +++ b/sound/soc/atmel/sam9g20_wm8731.c @@ -37,6 +37,7 @@ #include <linux/interrupt.h> #include <linux/platform_device.h> #include <linux/i2c.h> +#include <linux/of.h> #include <linux/atmel-ssc.h> @@ -47,7 +48,6 @@ #include <asm/mach-types.h> #include <mach/hardware.h> -#include <mach/gpio.h> #include "../codecs/wm8731.h" #include "atmel-pcm.h" @@ -154,35 +154,24 @@ static int at91sam9g20ek_wm8731_init(struct snd_soc_pcm_runtime *rtd) return ret; } - /* Add specific widgets */ - snd_soc_dapm_new_controls(dapm, at91sam9g20ek_dapm_widgets, - ARRAY_SIZE(at91sam9g20ek_dapm_widgets)); - /* Set up specific audio path interconnects */ - snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon)); - /* not connected */ snd_soc_dapm_nc_pin(dapm, "RLINEIN"); snd_soc_dapm_nc_pin(dapm, "LLINEIN"); -#ifdef ENABLE_MIC_INPUT - snd_soc_dapm_enable_pin(dapm, "Int Mic"); -#else - snd_soc_dapm_nc_pin(dapm, "Int Mic"); +#ifndef ENABLE_MIC_INPUT + snd_soc_dapm_nc_pin(&rtd->card->dapm, "Int Mic"); #endif - /* always connected */ - snd_soc_dapm_enable_pin(dapm, "Ext Spk"); - return 0; } static struct snd_soc_dai_link at91sam9g20ek_dai = { .name = "WM8731", .stream_name = "WM8731 PCM", - .cpu_dai_name = "atmel-ssc-dai.0", + .cpu_dai_name = "at91rm9200_ssc.0", .codec_dai_name = "wm8731-hifi", .init = at91sam9g20ek_wm8731_init, - .platform_name = "atmel-pcm-audio", + .platform_name = "at91rm9200_ssc.0", .codec_name = "wm8731.0-001b", .ops = &at91sam9g20ek_ops, }; @@ -193,22 +182,31 @@ static struct snd_soc_card snd_soc_at91sam9g20ek = { .dai_link = &at91sam9g20ek_dai, .num_links = 1, .set_bias_level = at91sam9g20ek_set_bias_level, -}; -static struct platform_device *at91sam9g20ek_snd_device; + .dapm_widgets = at91sam9g20ek_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(at91sam9g20ek_dapm_widgets), + .dapm_routes = intercon, + .num_dapm_routes = ARRAY_SIZE(intercon), +}; -static int __init at91sam9g20ek_init(void) +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 (!(machine_is_at91sam9g20ek() || machine_is_at91sam9g20ek_2mmc())) - return -ENODEV; + if (!np) { + if (!(machine_is_at91sam9g20ek() || + machine_is_at91sam9g20ek_2mmc())) + return -ENODEV; + } ret = atmel_ssc_set_audio(0); - if (ret != 0) { - pr_err("Failed to set SSC 0 for audio: %d\n", ret); - return ret; + if (ret) { + dev_err(&pdev->dev, "ssc channel is not valid\n"); + return -EINVAL; } /* @@ -236,45 +234,92 @@ static int __init at91sam9g20ek_init(void) clk_set_rate(mclk, MCLK_RATE); - at91sam9g20ek_snd_device = platform_device_alloc("soc-audio", -1); - if (!at91sam9g20ek_snd_device) { - printk(KERN_ERR "ASoC: Platform device allocation failed\n"); - ret = -ENOMEM; - goto err_mclk; + 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); } - platform_set_drvdata(at91sam9g20ek_snd_device, - &snd_soc_at91sam9g20ek); - - ret = platform_device_add(at91sam9g20ek_snd_device); + ret = snd_soc_register_card(card); if (ret) { - printk(KERN_ERR "ASoC: Platform device allocation failed\n"); - goto err_device_add; + printk(KERN_ERR "ASoC: snd_soc_register_card() failed\n"); } return ret; -err_device_add: - platform_device_put(at91sam9g20ek_snd_device); err_mclk: clk_put(mclk); mclk = NULL; err: + atmel_ssc_put_audio(0); return ret; } -static void __exit at91sam9g20ek_exit(void) +static int at91sam9g20ek_audio_remove(struct platform_device *pdev) { - platform_device_unregister(at91sam9g20ek_snd_device); - at91sam9g20ek_snd_device = NULL; - clk_put(mclk); + 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; } -module_init(at91sam9g20ek_init); -module_exit(at91sam9g20ek_exit); +#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"); diff --git a/sound/soc/atmel/sam9x5_wm8731.c b/sound/soc/atmel/sam9x5_wm8731.c new file mode 100644 index 00000000000..3188036a18f --- /dev/null +++ b/sound/soc/atmel/sam9x5_wm8731.c @@ -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); diff --git a/sound/soc/atmel/snd-soc-afeb9260.c b/sound/soc/atmel/snd-soc-afeb9260.c index 4ca667d477f..9579799ace5 100644 --- a/sound/soc/atmel/snd-soc-afeb9260.c +++ b/sound/soc/atmel/snd-soc-afeb9260.c @@ -46,29 +46,8 @@ static int afeb9260_hw_params(struct snd_pcm_substream *substream, { 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 err; - /* Set codec DAI configuration */ - err = snd_soc_dai_set_fmt(codec_dai, - SND_SOC_DAIFMT_I2S| - SND_SOC_DAIFMT_NB_IF | - SND_SOC_DAIFMT_CBM_CFM); - if (err < 0) { - printk(KERN_ERR "can't set codec DAI configuration\n"); - return err; - } - - /* Set cpu DAI configuration */ - err = snd_soc_dai_set_fmt(cpu_dai, - SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_NB_IF | - SND_SOC_DAIFMT_CBM_CFM); - if (err < 0) { - printk(KERN_ERR "can't set cpu DAI configuration\n"); - return 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); @@ -91,7 +70,7 @@ static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = { SND_SOC_DAPM_MIC("Mic Jack", NULL), }; -static const struct snd_soc_dapm_route audio_map[] = { +static const struct snd_soc_dapm_route afeb9260_audio_map[] = { {"Headphone Jack", NULL, "LHPOUT"}, {"Headphone Jack", NULL, "RHPOUT"}, @@ -101,24 +80,6 @@ static const struct snd_soc_dapm_route audio_map[] = { {"MICIN", NULL, "Mic Jack"}, }; -static int afeb9260_tlv320aic23_init(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_codec *codec = rtd->codec; - struct snd_soc_dapm_context *dapm = &codec->dapm; - - /* Add afeb9260 specific widgets */ - snd_soc_dapm_new_controls(dapm, tlv320aic23_dapm_widgets, - ARRAY_SIZE(tlv320aic23_dapm_widgets)); - - /* Set up afeb9260 specific audio path audio_map */ - snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map)); - - snd_soc_dapm_enable_pin(dapm, "Headphone Jack"); - snd_soc_dapm_enable_pin(dapm, "Line In"); - snd_soc_dapm_enable_pin(dapm, "Mic Jack"); - - return 0; -} /* Digital audio interface glue - connects codec <--> CPU */ static struct snd_soc_dai_link afeb9260_dai = { @@ -128,7 +89,8 @@ static struct snd_soc_dai_link afeb9260_dai = { .codec_dai_name = "tlv320aic23-hifi", .platform_name = "atmel_pcm-audio", .codec_name = "tlv320aic23-codec.0-001a", - .init = afeb9260_tlv320aic23_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_CBM_CFM, .ops = &afeb9260_ops, }; @@ -138,6 +100,11 @@ static struct snd_soc_card snd_soc_machine_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; |
