diff options
Diffstat (limited to 'sound/soc/atmel')
| -rw-r--r-- | sound/soc/atmel/Kconfig | 54 | ||||
| -rw-r--r-- | sound/soc/atmel/Makefile | 12 | ||||
| -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 | 429 | ||||
| -rw-r--r-- | sound/soc/atmel/atmel-pcm.h | 38 | ||||
| -rw-r--r-- | sound/soc/atmel/atmel_ssc_dai.c | 316 | ||||
| -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/playpaq_wm8510.c | 471 | ||||
| -rw-r--r-- | sound/soc/atmel/sam9g20_wm8731.c | 146 | ||||
| -rw-r--r-- | sound/soc/atmel/sam9x5_wm8731.c | 208 | ||||
| -rw-r--r-- | sound/soc/atmel/snd-soc-afeb9260.c | 55 | 
13 files changed, 1297 insertions, 1162 deletions
diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig index e720d5e6f04..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 || AVR32 +	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,36 +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 +	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_AT32_SOC_PLAYPAQ -        tristate "SoC Audio support for PlayPaq with WM8510" -        depends on SND_ATMEL_SOC && BOARD_PLAYPAQ -        select SND_ATMEL_SOC_SSC -        select SND_SOC_WM8510 -        help -          Say Y or M here if you want to add support for SoC audio -          on the LRS PlayPaq. +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_AT32_SOC_PLAYPAQ_SLAVE -        bool "Run CODEC on PlayPaq in slave mode" -        depends on SND_AT32_SOC_PLAYPAQ -        default n -        help -          Say Y if you want to run with the AT32 SSC generating the BCLK -          and FRAME signals on the PlayPaq.  Unless you want to play -          with the AT32 as the SSC master, you probably want to say N here, -          as this will give you better sound quality. +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 && 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 e7ea56bd5f8..5baabc8bde3 100644 --- a/sound/soc/atmel/Makefile +++ b/sound/soc/atmel/Makefile @@ -1,16 +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 - -# AT32 Machine Support -snd-soc-playpaq-objs := playpaq_wm8510.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_AT32_SOC_PLAYPAQ) += snd-soc-playpaq.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 d0e75323ec1..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,277 +58,36 @@ 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_card *card, -	struct snd_soc_dai *dai, struct snd_pcm *pcm) +int atmel_pcm_new(struct snd_soc_pcm_runtime *rtd)  { -	int ret = 0; +	struct snd_card *card = rtd->card->snd_card; +	struct snd_pcm *pcm = rtd->pcm; +	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 (dai->driver->playback.channels_min) { +	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 (dai->driver->capture.channels_min) { -		pr_debug("at32-pcm:" -				"Allocating PCM capture DMA buffer\n"); +	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) @@ -392,8 +96,9 @@ static int atmel_pcm_new(struct snd_card *card,   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; @@ -412,99 +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), -}; - -static int __init snd_atmel_pcm_init(void) -{ -	return platform_driver_register(&atmel_pcm_driver); -} -module_init(snd_atmel_pcm_init); - -static void __exit snd_atmel_pcm_exit(void) -{ -	platform_driver_unregister(&atmel_pcm_driver); -} -module_exit(snd_atmel_pcm_exit); - -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 2597329302e..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 */ @@ -60,7 +63,7 @@ struct atmel_ssc_mask {   * 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 dms_intr_handler() pointer is set by the PCM + * 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.   */ @@ -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 5d230cee3fa..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); @@ -402,7 +394,7 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream,  	if ((ssc_p->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S  		&& bits > 16) {  		printk(KERN_WARNING -				"atmel_ssc_dai: sample size %d" +				"atmel_ssc_dai: sample size %d "  				"is too large for I2S\n", bits);  		return -EINVAL;  	} @@ -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) @@ -672,7 +739,7 @@ static int atmel_ssc_resume(struct snd_soc_dai *cpu_dai)  	/* re-enable interrupts */  	ssc_writel(ssc_p->ssc->regs, IER, ssc_p->ssc_state.ssc_imr); -	/* Re-enable recieve and transmit as appropriate */ +	/* 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; @@ -687,52 +754,22 @@ 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 |\  			  SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) -static struct snd_soc_dai_ops atmel_ssc_dai_ops = { +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, +static struct snd_soc_dai_driver atmel_ssc_dai = {  		.suspend = atmel_ssc_suspend,  		.resume = atmel_ssc_resume,  		.playback = { @@ -746,69 +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, -	}, -#if NUM_SSC_DEVICES == 3 -	{ -		.name = "atmel-ssc-dai.1", -		.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, -	}, -	{ -		.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,61 +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) { -		ssc_free(ssc); -		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); -static int __init snd_atmel_ssc_init(void) +void atmel_ssc_put_audio(int ssc_id)  { -	return platform_driver_register(&asoc_ssc_driver); -} -module_init(snd_atmel_ssc_init); +	struct ssc_device *ssc = ssc_info[ssc_id].ssc; -static void __exit snd_atmel_ssc_exit(void) -{ -	platform_driver_unregister(&asoc_ssc_driver); +	asoc_ssc_exit(&ssc->pdev->dev); +	ssc_free(ssc);  } -module_exit(snd_atmel_ssc_exit); +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/playpaq_wm8510.c b/sound/soc/atmel/playpaq_wm8510.c deleted file mode 100644 index 5f4e59f4461..00000000000 --- a/sound/soc/atmel/playpaq_wm8510.c +++ /dev/null @@ -1,471 +0,0 @@ -/* sound/soc/at32/playpaq_wm8510.c - * ASoC machine driver for PlayPaq using WM8510 codec - * - * Copyright (C) 2008 Long Range Systems - *    Geoffrey Wossum <gwossum@acm.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 code is largely inspired by sound/soc/at91/eti_b1_wm8731.c - * - * NOTE: If you don't have the AT32 enhanced portmux configured (which - * isn't currently in the mainline or Atmel patched kernel), you will - * need to set the MCLK pin (PA30) to peripheral A in your board initialization - * code.  Something like: - *	at32_select_periph(GPIO_PIN_PA(30), GPIO_PERIPH_A, 0); - * - */ - -/* #define DEBUG */ - -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/kernel.h> -#include <linux/errno.h> -#include <linux/clk.h> -#include <linux/timer.h> -#include <linux/interrupt.h> -#include <linux/platform_device.h> - -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/soc.h> -#include <sound/soc-dapm.h> - -#include <mach/at32ap700x.h> -#include <mach/portmux.h> - -#include "../codecs/wm8510.h" -#include "atmel-pcm.h" -#include "atmel_ssc_dai.h" - - -/*-------------------------------------------------------------------------*\ - * constants -\*-------------------------------------------------------------------------*/ -#define MCLK_PIN		GPIO_PIN_PA(30) -#define MCLK_PERIPH		GPIO_PERIPH_A - - -/*-------------------------------------------------------------------------*\ - * data types -\*-------------------------------------------------------------------------*/ -/* SSC clocking data */ -struct ssc_clock_data { -	/* CMR div */ -	unsigned int cmr_div; - -	/* Frame period (as needed by xCMR.PERIOD) */ -	unsigned int period; - -	/* The SSC clock rate these settings where calculated for */ -	unsigned long ssc_rate; -}; - - -/*-------------------------------------------------------------------------*\ - * module data -\*-------------------------------------------------------------------------*/ -static struct clk *_gclk0; -static struct clk *_pll0; - -#define CODEC_CLK (_gclk0) - - -/*-------------------------------------------------------------------------*\ - * Sound SOC operations -\*-------------------------------------------------------------------------*/ -#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE -static struct ssc_clock_data playpaq_wm8510_calc_ssc_clock( -	struct snd_pcm_hw_params *params, -	struct snd_soc_dai *cpu_dai) -{ -	struct at32_ssc_info *ssc_p = snd_soc_dai_get_drvdata(cpu_dai); -	struct ssc_device *ssc = ssc_p->ssc; -	struct ssc_clock_data cd; -	unsigned int rate, width_bits, channels; -	unsigned int bitrate, ssc_div; -	unsigned actual_rate; - - -	/* -	 * Figure out required bitrate -	 */ -	rate = params_rate(params); -	channels = params_channels(params); -	width_bits = snd_pcm_format_physical_width(params_format(params)); -	bitrate = rate * width_bits * channels; - - -	/* -	 * Figure out required SSC divider and period for required bitrate -	 */ -	cd.ssc_rate = clk_get_rate(ssc->clk); -	ssc_div = cd.ssc_rate / bitrate; -	cd.cmr_div = ssc_div / 2; -	if (ssc_div & 1) { -		/* round cmr_div up */ -		cd.cmr_div++; -	} -	cd.period = width_bits - 1; - - -	/* -	 * Find actual rate, compare to requested rate -	 */ -	actual_rate = (cd.ssc_rate / (cd.cmr_div * 2)) / (2 * (cd.period + 1)); -	pr_debug("playpaq_wm8510: Request rate = %u, actual rate = %u\n", -		 rate, actual_rate); - - -	return cd; -} -#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */ - - - -static int playpaq_wm8510_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; -	struct at32_ssc_info *ssc_p = snd_soc_dai_get_drvdata(cpu_dai); -	struct ssc_device *ssc = ssc_p->ssc; -	unsigned int pll_out = 0, bclk = 0, mclk_div = 0; -	int ret; - - -	/* Due to difficulties with getting the correct clocks from the AT32's -	 * PLL0, we're going to let the CODEC be in charge of all the clocks -	 */ -#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE -	const unsigned int fmt = (SND_SOC_DAIFMT_I2S | -				  SND_SOC_DAIFMT_NB_NF | -				  SND_SOC_DAIFMT_CBM_CFM); -#else -	struct ssc_clock_data cd; -	const unsigned int fmt = (SND_SOC_DAIFMT_I2S | -				  SND_SOC_DAIFMT_NB_NF | -				  SND_SOC_DAIFMT_CBS_CFS); -#endif - -	if (ssc == NULL) { -		pr_warning("playpaq_wm8510_hw_params: ssc is NULL!\n"); -		return -EINVAL; -	} - - -	/* -	 * Figure out PLL and BCLK dividers for WM8510 -	 */ -	switch (params_rate(params)) { -	case 48000: -		pll_out = 24576000; -		mclk_div = WM8510_MCLKDIV_2; -		bclk = WM8510_BCLKDIV_8; -		break; - -	case 44100: -		pll_out = 22579200; -		mclk_div = WM8510_MCLKDIV_2; -		bclk = WM8510_BCLKDIV_8; -		break; - -	case 22050: -		pll_out = 22579200; -		mclk_div = WM8510_MCLKDIV_4; -		bclk = WM8510_BCLKDIV_8; -		break; - -	case 16000: -		pll_out = 24576000; -		mclk_div = WM8510_MCLKDIV_6; -		bclk = WM8510_BCLKDIV_8; -		break; - -	case 11025: -		pll_out = 22579200; -		mclk_div = WM8510_MCLKDIV_8; -		bclk = WM8510_BCLKDIV_8; -		break; - -	case 8000: -		pll_out = 24576000; -		mclk_div = WM8510_MCLKDIV_12; -		bclk = WM8510_BCLKDIV_8; -		break; - -	default: -		pr_warning("playpaq_wm8510: Unsupported sample rate %d\n", -			   params_rate(params)); -		return -EINVAL; -	} - - -	/* -	 * set CPU and CODEC DAI configuration -	 */ -	ret = snd_soc_dai_set_fmt(codec_dai, fmt); -	if (ret < 0) { -		pr_warning("playpaq_wm8510: " -			   "Failed to set CODEC DAI format (%d)\n", -			   ret); -		return ret; -	} -	ret = snd_soc_dai_set_fmt(cpu_dai, fmt); -	if (ret < 0) { -		pr_warning("playpaq_wm8510: " -			   "Failed to set CPU DAI format (%d)\n", -			   ret); -		return ret; -	} - - -	/* -	 * Set CPU clock configuration -	 */ -#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE -	cd = playpaq_wm8510_calc_ssc_clock(params, cpu_dai); -	pr_debug("playpaq_wm8510: cmr_div = %d, period = %d\n", -		 cd.cmr_div, cd.period); -	ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_CMR_DIV, cd.cmr_div); -	if (ret < 0) { -		pr_warning("playpaq_wm8510: Failed to set CPU CMR_DIV (%d)\n", -			   ret); -		return ret; -	} -	ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_TCMR_PERIOD, -					  cd.period); -	if (ret < 0) { -		pr_warning("playpaq_wm8510: " -			   "Failed to set CPU transmit period (%d)\n", -			   ret); -		return ret; -	} -#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */ - - -	/* -	 * Set CODEC clock configuration -	 */ -	pr_debug("playpaq_wm8510: " -		 "pll_in = %ld, pll_out = %u, bclk = %x, mclk = %x\n", -		 clk_get_rate(CODEC_CLK), pll_out, bclk, mclk_div); - - -#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE -	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_BCLKDIV, bclk); -	if (ret < 0) { -		pr_warning -		    ("playpaq_wm8510: Failed to set CODEC DAI BCLKDIV (%d)\n", -		     ret); -		return ret; -	} -#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */ - - -	ret = snd_soc_dai_set_pll(codec_dai, 0, 0, -					 clk_get_rate(CODEC_CLK), pll_out); -	if (ret < 0) { -		pr_warning("playpaq_wm8510: Failed to set CODEC DAI PLL (%d)\n", -			   ret); -		return ret; -	} - - -	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_MCLKDIV, mclk_div); -	if (ret < 0) { -		pr_warning("playpaq_wm8510: Failed to set CODEC MCLKDIV (%d)\n", -			   ret); -		return ret; -	} - - -	return 0; -} - - - -static struct snd_soc_ops playpaq_wm8510_ops = { -	.hw_params = playpaq_wm8510_hw_params, -}; - - - -static const struct snd_soc_dapm_widget playpaq_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 SPKOUT */ -	{"Ext Spk", NULL, "SPKOUTP"}, -	{"Ext Spk", NULL, "SPKOUTN"}, - -	{"Mic Bias", NULL, "Int Mic"}, -	{"MICN", NULL, "Mic Bias"}, -	{"MICP", NULL, "Mic Bias"}, -}; - - - -static int playpaq_wm8510_init(struct snd_soc_pcm_runtime *rtd) -{ -	struct snd_soc_codec *codec = rtd->codec; -	int i; - -	/* -	 * Add DAPM widgets -	 */ -	for (i = 0; i < ARRAY_SIZE(playpaq_dapm_widgets); i++) -		snd_soc_dapm_new_control(codec, &playpaq_dapm_widgets[i]); - - - -	/* -	 * Setup audio path interconnects -	 */ -	snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); - - - -	/* always connected pins */ -	snd_soc_dapm_enable_pin(codec, "Int Mic"); -	snd_soc_dapm_enable_pin(codec, "Ext Spk"); -	snd_soc_dapm_sync(codec); - - - -	/* Make CSB show PLL rate */ -	snd_soc_dai_set_clkdiv(rtd->codec_dai, WM8510_OPCLKDIV, -				       WM8510_OPCLKDIV_1 | 4); - -	return 0; -} - - - -static struct snd_soc_dai_link playpaq_wm8510_dai = { -	.name = "WM8510", -	.stream_name = "WM8510 PCM", -	.cpu_dai_name= "atmel-ssc-dai.0", -	.platform_name = "atmel-pcm-audio", -	.codec_name = "wm8510-codec.0-0x1a", -	.codec_dai_name = "wm8510-hifi", -	.init = playpaq_wm8510_init, -	.ops = &playpaq_wm8510_ops, -}; - - - -static struct snd_soc_card snd_soc_playpaq = { -	.name = "LRS_PlayPaq_WM8510", -	.dai_link = &playpaq_wm8510_dai, -	.num_links = 1, -}; - -static struct platform_device *playpaq_snd_device; - - -static int __init playpaq_asoc_init(void) -{ -	int ret = 0; - -	/* -	 * Configure MCLK for WM8510 -	 */ -	_gclk0 = clk_get(NULL, "gclk0"); -	if (IS_ERR(_gclk0)) { -		_gclk0 = NULL; -		goto err_gclk0; -	} -	_pll0 = clk_get(NULL, "pll0"); -	if (IS_ERR(_pll0)) { -		_pll0 = NULL; -		goto err_pll0; -	} -	if (clk_set_parent(_gclk0, _pll0)) { -		pr_warning("snd-soc-playpaq: " -			   "Failed to set PLL0 as parent for DAC clock\n"); -		goto err_set_clk; -	} -	clk_set_rate(CODEC_CLK, 12000000); -	clk_enable(CODEC_CLK); - -#if defined CONFIG_AT32_ENHANCED_PORTMUX -	at32_select_periph(MCLK_PIN, MCLK_PERIPH, 0); -#endif - - -	/* -	 * Create and register platform device -	 */ -	playpaq_snd_device = platform_device_alloc("soc-audio", 0); -	if (playpaq_snd_device == NULL) { -		ret = -ENOMEM; -		goto err_device_alloc; -	} - -	platform_set_drvdata(playpaq_snd_device, &snd_soc_playpaq); - -	ret = platform_device_add(playpaq_snd_device); -	if (ret) { -		pr_warning("playpaq_wm8510: platform_device_add failed (%d)\n", -			   ret); -		goto err_device_add; -	} - -	return 0; - - -err_device_add: -	if (playpaq_snd_device != NULL) { -		platform_device_put(playpaq_snd_device); -		playpaq_snd_device = NULL; -	} -err_device_alloc: -err_set_clk: -	if (_pll0 != NULL) { -		clk_put(_pll0); -		_pll0 = NULL; -	} -err_pll0: -	if (_gclk0 != NULL) { -		clk_put(_gclk0); -		_gclk0 = NULL; -	} -	return ret; -} - - -static void __exit playpaq_asoc_exit(void) -{ -	if (_gclk0 != NULL) { -		clk_put(_gclk0); -		_gclk0 = NULL; -	} -	if (_pll0 != NULL) { -		clk_put(_pll0); -		_pll0 = NULL; -	} - -#if defined CONFIG_AT32_ENHANCED_PORTMUX -	at32_free_pin(MCLK_PIN); -#endif - -	platform_device_unregister(playpaq_snd_device); -	playpaq_snd_device = NULL; -} - -module_init(playpaq_asoc_init); -module_exit(playpaq_asoc_exit); - -MODULE_AUTHOR("Geoffrey Wossum <gwossum@acm.org>"); -MODULE_DESCRIPTION("ASoC machine driver for LRS PlayPaq"); -MODULE_LICENSE("GPL"); diff --git a/sound/soc/atmel/sam9g20_wm8731.c b/sound/soc/atmel/sam9g20_wm8731.c index 293569dfd0e..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> @@ -44,11 +45,9 @@  #include <sound/pcm.h>  #include <sound/pcm_params.h>  #include <sound/soc.h> -#include <sound/soc-dapm.h>  #include <asm/mach-types.h>  #include <mach/hardware.h> -#include <mach/gpio.h>  #include "../codecs/wm8731.h"  #include "atmel-pcm.h" @@ -93,6 +92,7 @@ static struct snd_soc_ops at91sam9g20ek_ops = {  };  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; @@ -140,75 +140,73 @@ 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_XTAL, +	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;  	} -	/* Add specific widgets */ -	snd_soc_dapm_new_controls(codec, at91sam9g20ek_dapm_widgets, -				  ARRAY_SIZE(at91sam9g20ek_dapm_widgets)); -	/* Set up specific audio path interconnects */ -	snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); -  	/* not connected */ -	snd_soc_dapm_nc_pin(codec, "RLINEIN"); -	snd_soc_dapm_nc_pin(codec, "LLINEIN"); +	snd_soc_dapm_nc_pin(dapm, "RLINEIN"); +	snd_soc_dapm_nc_pin(dapm, "LLINEIN"); -#ifdef ENABLE_MIC_INPUT -	snd_soc_dapm_enable_pin(codec, "Int Mic"); -#else -	snd_soc_dapm_nc_pin(codec, "Int Mic"); +#ifndef ENABLE_MIC_INPUT +	snd_soc_dapm_nc_pin(&rtd->card->dapm, "Int Mic");  #endif -	/* always connected */ -	snd_soc_dapm_enable_pin(codec, "Ext Spk"); - -	snd_soc_dapm_sync(codec); -  	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", -	.codec_name = "wm8731-codec.0-001b", +	.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, -}; -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;  	}  	/* @@ -222,9 +220,9 @@ static int __init at91sam9g20ek_init(void)  	}  	pllb = clk_get(NULL, "pllb"); -	if (IS_ERR(mclk)) { +	if (IS_ERR(pllb)) {  		printk(KERN_ERR "ASoC: Failed to get PLLB\n"); -		ret = PTR_ERR(mclk); +		ret = PTR_ERR(pllb);  		goto err_mclk;  	}  	ret = clk_set_parent(mclk, pllb); @@ -236,19 +234,46 @@ 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; +	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"); -		platform_device_put(at91sam9g20ek_snd_device); +		printk(KERN_ERR "ASoC: snd_soc_register_card() failed\n");  	}  	return ret; @@ -257,21 +282,44 @@ 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 e3d283561c1..9579799ace5 100644 --- a/sound/soc/atmel/snd-soc-afeb9260.c +++ b/sound/soc/atmel/snd-soc-afeb9260.c @@ -30,7 +30,6 @@  #include <sound/pcm.h>  #include <sound/pcm_params.h>  #include <sound/soc.h> -#include <sound/soc-dapm.h>  #include <asm/mach-types.h>  #include <mach/hardware.h> @@ -47,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); @@ -92,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"}, @@ -102,25 +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; - -	/* Add afeb9260 specific widgets */ -	snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets, -				  ARRAY_SIZE(tlv320aic23_dapm_widgets)); - -	/* Set up afeb9260 specific audio path audio_map */ -	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); - -	snd_soc_dapm_enable_pin(codec, "Headphone Jack"); -	snd_soc_dapm_enable_pin(codec, "Line In"); -	snd_soc_dapm_enable_pin(codec, "Mic Jack"); - -	snd_soc_dapm_sync(codec); - -	return 0; -}  /* Digital audio interface glue - connects codec <--> CPU */  static struct snd_soc_dai_link afeb9260_dai = { @@ -129,16 +88,23 @@ static struct snd_soc_dai_link afeb9260_dai = {  	.cpu_dai_name = "atmel-ssc-dai.0",  	.codec_dai_name = "tlv320aic23-hifi",  	.platform_name = "atmel_pcm-audio", -	.codec_name = "tlv320aic23-codec.0-0x1a", -	.init = afeb9260_tlv320aic23_init, +	.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; @@ -167,7 +133,6 @@ static int __init afeb9260_soc_init(void)  	return 0;  err1: -	platform_device_del(afeb9260_snd_device);  	platform_device_put(afeb9260_snd_device);  	return err;  }  | 
