Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9aaca968 authored by Geoffrey Wossum's avatar Geoffrey Wossum Committed by Takashi Iwai
Browse files

[ALSA] Revised AT32 ASoC Patch



Attached is a revised version of my patch to add AT32 to ASoC.  This cleans
most of the style issues associated with the previous patch.  Also fixes an
issue with the playpaq_wm8510.c code depending on a non-released patch to th
AT32 portmux support.

Patch is against 2.6.24.3.atmel.3 kernel, the latest AVR32 kernel Atmel has
released, with the linux-2.6-asoc patches from when v2.6.24 was tagged also
applied.

[Fixed up minor checkpatch issues and updated for current kernels -- broonie]

Signed-off-by: default avatarGeoffrey Wossum <gwossum@acm.org>
Signed-off-by: default avatarMark Brown <broonie@opensource.wolfsonmicro.com>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent f10485e7
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ config SND_SOC_AC97_BUS
	bool

# All the supported Soc's
source "sound/soc/at32/Kconfig"
source "sound/soc/at91/Kconfig"
source "sound/soc/pxa/Kconfig"
source "sound/soc/s3c24xx/Kconfig"
+2 −1
Original line number Diff line number Diff line
snd-soc-core-objs := soc-core.o soc-dapm.o

obj-$(CONFIG_SND_SOC)	+= snd-soc-core.o
obj-$(CONFIG_SND_SOC)	+= codecs/ at91/ pxa/ s3c24xx/ sh/ fsl/ davinci/ omap/
obj-$(CONFIG_SND_SOC)	+= codecs/ at32/ at91/ pxa/ s3c24xx/ sh/ fsl/ davinci/
obj-$(CONFIG_SND_SOC)	+= omap/

sound/soc/at32/Kconfig

0 → 100644
+34 −0
Original line number Diff line number Diff line
config SND_AT32_SOC
        tristate "SoC Audio for the Atmel AT32 System-on-a-Chip"
        depends on AVR32 && SND_SOC
        help
          Say Y or M if you want to add support for codecs attached to 
          the AT32 SSC interface.  You will also need to
          to select the audio interfaces to support below.


config SND_AT32_SOC_SSC
        tristate



config SND_AT32_SOC_PLAYPAQ
        tristate "SoC Audio support for PlayPaq with WM8510"
        depends on SND_AT32_SOC && BOARD_PLAYPAQ
        select SND_AT32_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_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.
+11 −0
Original line number Diff line number Diff line
# AT32 Platform Support
snd-soc-at32-objs := at32-pcm.o
snd-soc-at32-ssc-objs := at32-ssc.o

obj-$(CONFIG_SND_AT32_SOC) += snd-soc-at32.o
obj-$(CONFIG_SND_AT32_SOC_SSC) += snd-soc-at32-ssc.o

# AT32 Machine Support
snd-soc-playpaq-objs := playpaq_wm8510.o

obj-$(CONFIG_SND_AT32_SOC_PLAYPAQ) += snd-soc-playpaq.o
+491 −0
Original line number Diff line number Diff line
/* sound/soc/at32/at32-pcm.c
 * ASoC PCM interface for Atmel AT32 SoC
 *
 * 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.
 *
 * Note that this is basically a port of the sound/soc/at91-pcm.c to
 * the AVR32 kernel.  Thanks to Frank Mandarino for that code.
 */

#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 <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>

#include "at32-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 at32_pcm_hardware = {
	.info = (SNDRV_PCM_INFO_MMAP |
		 SNDRV_PCM_INFO_MMAP_VALID |
		 SNDRV_PCM_INFO_INTERLEAVED |
		 SNDRV_PCM_INFO_BLOCK_TRANSFER |
		 SNDRV_PCM_INFO_PAUSE),

	.formats = SNDRV_PCM_FMTBIT_S16,
	.period_bytes_min = 32,
	.period_bytes_max = 8192,	/* 512 frames * 16 bytes / frame */
	.periods_min = 2,
	.periods_max = 1024,
	.buffer_bytes_max = 32 * 1024,
};



/*--------------------------------------------------------------------------*\
 * Data types
\*--------------------------------------------------------------------------*/
struct at32_runtime_data {
	struct at32_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 */
	int periods;		/* period index of period_ptr */

	/* Save PDC registers (for power management) */
	u32 pdc_xpr_save;
	u32 pdc_xcr_save;
	u32 pdc_xnpr_save;
	u32 pdc_xncr_save;
};



/*--------------------------------------------------------------------------*\
 * Helper functions
\*--------------------------------------------------------------------------*/
static int at32_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
{
	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
	struct snd_dma_buffer *dmabuf = &substream->dma_buffer;
	size_t size = at32_pcm_hardware.buffer_bytes_max;

	dmabuf->dev.type = SNDRV_DMA_TYPE_DEV;
	dmabuf->dev.dev = pcm->card->dev;
	dmabuf->private_data = NULL;
	dmabuf->area = dma_alloc_coherent(pcm->card->dev, size,
					  &dmabuf->addr, GFP_KERNEL);
	pr_debug("at32_pcm: preallocate_dma_buffer: "
		 "area=%p, addr=%p, size=%ld\n",
		 (void *)dmabuf->area, (void *)dmabuf->addr, size);

	if (!dmabuf->area)
		return -ENOMEM;

	dmabuf->bytes = size;
	return 0;
}



/*--------------------------------------------------------------------------*\
 * ISR
\*--------------------------------------------------------------------------*/
static void at32_pcm_dma_irq(u32 ssc_sr, struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *rtd = substream->runtime;
	struct at32_runtime_data *prtd = rtd->private_data;
	struct at32_pcm_dma_params *params = prtd->params;
	static int count;

	count++;
	if (ssc_sr & params->mask->ssc_endbuf) {
		pr_warning("at32-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 at32_pcm_hw_params(struct snd_pcm_substream *substream,
			      struct snd_pcm_hw_params *params)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct at32_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 = rtd->dai->cpu_dai->dma_data;
	prtd->params->dma_intr_handler = at32_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("hw_params: DMA for %s initialized "
		 "(dma_bytes=%ld, period_size=%ld)\n",
		 prtd->params->name, runtime->dma_bytes, prtd->period_size);

	return 0;
}



static int at32_pcm_hw_free(struct snd_pcm_substream *substream)
{
	struct at32_runtime_data *prtd = substream->runtime->private_data;
	struct at32_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 at32_pcm_prepare(struct snd_pcm_substream *substream)
{
	struct at32_runtime_data *prtd = substream->runtime->private_data;
	struct at32_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 at32_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
	struct snd_pcm_runtime *rtd = substream->runtime;
	struct at32_runtime_data *prtd = rtd->private_data;
	struct at32_pcm_dma_params *params = prtd->params;
	int ret = 0;

	pr_debug("at32_pcm_trigger: buffer_size = %ld, "
		 "dma_area = %p, dma_bytes = %ld\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("trigger: period_ptr=%lx, xpr=%x, "
			 "xcr=%d, xnpr=%x, xncr=%d\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=%x, imr=%x\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 at32_pcm_pointer(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct at32_runtime_data *prtd = runtime->private_data;
	struct at32_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 at32_pcm_open(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct at32_runtime_data *prtd;
	int ret = 0;

	snd_soc_set_runtime_hwparams(substream, &at32_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(*prtd), GFP_KERNEL);
	if (prtd == NULL) {
		ret = -ENOMEM;
		goto out;
	}
	runtime->private_data = prtd;


out:
	return ret;
}



static int at32_pcm_close(struct snd_pcm_substream *substream)
{
	struct at32_runtime_data *prtd = substream->runtime->private_data;

	kfree(prtd);
	return 0;
}


static int at32_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);
}



static struct snd_pcm_ops at32_pcm_ops = {
	.open = at32_pcm_open,
	.close = at32_pcm_close,
	.ioctl = snd_pcm_lib_ioctl,
	.hw_params = at32_pcm_hw_params,
	.hw_free = at32_pcm_hw_free,
	.prepare = at32_pcm_prepare,
	.trigger = at32_pcm_trigger,
	.pointer = at32_pcm_pointer,
	.mmap = at32_pcm_mmap,
};



/*--------------------------------------------------------------------------*\
 * ASoC platform driver
\*--------------------------------------------------------------------------*/
static u64 at32_pcm_dmamask = 0xffffffff;

static int at32_pcm_new(struct snd_card *card,
			struct snd_soc_codec_dai *dai,
			struct snd_pcm *pcm)
{
	int ret = 0;

	if (!card->dev->dma_mask)
		card->dev->dma_mask = &at32_pcm_dmamask;
	if (!card->dev->coherent_dma_mask)
		card->dev->coherent_dma_mask = 0xffffffff;

	if (dai->playback.channels_min) {
		ret = at32_pcm_preallocate_dma_buffer(
			  pcm, SNDRV_PCM_STREAM_PLAYBACK);
		if (ret)
			goto out;
	}

	if (dai->capture.channels_min) {
		pr_debug("at32-pcm: Allocating PCM capture DMA buffer\n");
		ret = at32_pcm_preallocate_dma_buffer(
			  pcm, SNDRV_PCM_STREAM_CAPTURE);
		if (ret)
			goto out;
	}


out:
	return ret;
}



static void at32_pcm_free_dma_buffers(struct snd_pcm *pcm)
{
	struct snd_pcm_substream *substream;
	struct snd_dma_buffer *buf;
	int stream;

	for (stream = 0; stream < 2; stream++) {
		substream = pcm->streams[stream].substream;
		if (substream == NULL)
			continue;

		buf = &substream->dma_buffer;
		if (!buf->area)
			continue;
		dma_free_coherent(pcm->card->dev, buf->bytes,
				  buf->area, buf->addr);
		buf->area = NULL;
	}
}



#ifdef CONFIG_PM
static int at32_pcm_suspend(struct platform_device *pdev,
			    struct snd_soc_cpu_dai *dai)
{
	struct snd_pcm_runtime *runtime = dai->runtime;
	struct at32_runtime_data *prtd;
	struct at32_pcm_dma_params *params;

	if (runtime == NULL)
		return 0;
	prtd = runtime->private_data;
	params = prtd->params;

	/* Disable the PDC and save the PDC registers */
	ssc_writex(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 at32_pcm_resume(struct platform_device *pdev,
			   struct snd_soc_cpu_dai *dai)
{
	struct snd_pcm_runtime *runtime = dai->runtime;
	struct at32_runtime_data *prtd;
	struct at32_pcm_dma_params *params;

	if (runtime == NULL)
		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_writex(params->ssc->regs, PDC_PTCR, params->mask->pdc_enable);
	return 0;
}
#else /* CONFIG_PM */
#  define at32_pcm_suspend	NULL
#  define at32_pcm_resume	NULL
#endif /* CONFIG_PM */



struct snd_soc_platform at32_soc_platform = {
	.name = "at32-audio",
	.pcm_ops = &at32_pcm_ops,
	.pcm_new = at32_pcm_new,
	.pcm_free = at32_pcm_free_dma_buffers,
	.suspend = at32_pcm_suspend,
	.resume = at32_pcm_resume,
};
EXPORT_SYMBOL_GPL(at32_soc_platform);



MODULE_AUTHOR("Geoffrey Wossum <gwossum@acm.org>");
MODULE_DESCRIPTION("Atmel AT32 PCM module");
MODULE_LICENSE("GPL");
Loading