atmel-pcm.c 14.2 KB
Newer Older
1 2
/*
 * atmel-pcm.c  --  ALSA PCM interface for the Atmel atmel SoC.
G
Geoffrey Wossum 已提交
3
 *
4 5 6 7 8 9 10 11 12 13 14 15 16 17
 *  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.
G
Geoffrey Wossum 已提交
18 19
 *
 * This program is free software; you can redistribute it and/or modify
20 21 22
 * 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.
G
Geoffrey Wossum 已提交
23
 *
24 25 26 27 28 29 30 31
 * 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
G
Geoffrey Wossum 已提交
32 33 34 35 36 37 38 39
 */

#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>
40
#include <linux/atmel-ssc.h>
G
Geoffrey Wossum 已提交
41 42 43 44 45 46

#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>

47
#include "atmel-pcm.h"
G
Geoffrey Wossum 已提交
48 49 50 51 52 53 54 55


/*--------------------------------------------------------------------------*\
 * Hardware definition
\*--------------------------------------------------------------------------*/
/* TODO: These values were taken from the AT91 platform driver, check
 *	 them against real values for AT32
 */
56 57 58 59 60 61 62 63 64 65 66
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,
G
Geoffrey Wossum 已提交
67 68 69 70 71 72
};


/*--------------------------------------------------------------------------*\
 * Data types
\*--------------------------------------------------------------------------*/
73 74 75 76
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 */
G
Geoffrey Wossum 已提交
77 78
	size_t period_size;

79
	dma_addr_t period_ptr;		/* physical address of next period */
G
Geoffrey Wossum 已提交
80

81
	/* PDC register save */
G
Geoffrey Wossum 已提交
82 83 84 85 86 87 88 89 90 91
	u32 pdc_xpr_save;
	u32 pdc_xcr_save;
	u32 pdc_xnpr_save;
	u32 pdc_xncr_save;
};


/*--------------------------------------------------------------------------*\
 * Helper functions
\*--------------------------------------------------------------------------*/
92 93
static int atmel_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
	int stream)
G
Geoffrey Wossum 已提交
94 95
{
	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
	struct snd_dma_buffer *buf = &substream->dma_buffer;
	size_t size = atmel_pcm_hardware.buffer_bytes_max;

	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);

	if (!buf->area)
G
Geoffrey Wossum 已提交
111 112
		return -ENOMEM;

113
	buf->bytes = size;
G
Geoffrey Wossum 已提交
114 115 116 117 118
	return 0;
}
/*--------------------------------------------------------------------------*\
 * ISR
\*--------------------------------------------------------------------------*/
119 120
static void atmel_pcm_dma_irq(u32 ssc_sr,
	struct snd_pcm_substream *substream)
G
Geoffrey Wossum 已提交
121
{
122 123
	struct atmel_runtime_data *prtd = substream->runtime->private_data;
	struct atmel_pcm_dma_params *params = prtd->params;
G
Geoffrey Wossum 已提交
124 125 126
	static int count;

	count++;
127

G
Geoffrey Wossum 已提交
128
	if (ssc_sr & params->mask->ssc_endbuf) {
129 130 131 132 133
		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);
G
Geoffrey Wossum 已提交
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154

		/* 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;
155

G
Geoffrey Wossum 已提交
156 157 158 159 160 161 162 163 164 165 166 167 168
		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
\*--------------------------------------------------------------------------*/
169 170
static int atmel_pcm_hw_params(struct snd_pcm_substream *substream,
	struct snd_pcm_hw_params *params)
G
Geoffrey Wossum 已提交
171 172
{
	struct snd_pcm_runtime *runtime = substream->runtime;
173
	struct atmel_runtime_data *prtd = runtime->private_data;
G
Geoffrey Wossum 已提交
174 175 176
	struct snd_soc_pcm_runtime *rtd = substream->private_data;

	/* this may get called several times by oss emulation
177 178
	 * with different params */

G
Geoffrey Wossum 已提交
179 180 181
	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
	runtime->dma_bytes = params_buffer_bytes(params);

182
	prtd->params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
183
	prtd->params->dma_intr_handler = atmel_pcm_dma_irq;
G
Geoffrey Wossum 已提交
184 185 186 187 188

	prtd->dma_buffer = runtime->dma_addr;
	prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes;
	prtd->period_size = params_period_bytes(params);

189 190 191 192 193 194
	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);
G
Geoffrey Wossum 已提交
195 196 197
	return 0;
}

198
static int atmel_pcm_hw_free(struct snd_pcm_substream *substream)
G
Geoffrey Wossum 已提交
199
{
200 201
	struct atmel_runtime_data *prtd = substream->runtime->private_data;
	struct atmel_pcm_dma_params *params = prtd->params;
G
Geoffrey Wossum 已提交
202 203 204 205 206 207 208 209 210 211

	if (params != NULL) {
		ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
			   params->mask->pdc_disable);
		prtd->params->dma_intr_handler = NULL;
	}

	return 0;
}

212
static int atmel_pcm_prepare(struct snd_pcm_substream *substream)
G
Geoffrey Wossum 已提交
213
{
214 215
	struct atmel_runtime_data *prtd = substream->runtime->private_data;
	struct atmel_pcm_dma_params *params = prtd->params;
G
Geoffrey Wossum 已提交
216 217 218 219 220 221 222 223

	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;
}

224 225
static int atmel_pcm_trigger(struct snd_pcm_substream *substream,
	int cmd)
G
Geoffrey Wossum 已提交
226 227
{
	struct snd_pcm_runtime *rtd = substream->runtime;
228 229
	struct atmel_runtime_data *prtd = rtd->private_data;
	struct atmel_pcm_dma_params *params = prtd->params;
G
Geoffrey Wossum 已提交
230 231
	int ret = 0;

232 233 234
	pr_debug("atmel-pcm:buffer_size = %ld,"
		"dma_area = %p, dma_bytes = %u\n",
		rtd->buffer_size, rtd->dma_area, rtd->dma_bytes);
G
Geoffrey Wossum 已提交
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250

	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);

251 252 253 254 255 256 257 258
		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));
G
Geoffrey Wossum 已提交
259 260 261 262 263 264

		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);

265 266 267
		pr_debug("sr=%u imr=%u\n",
			ssc_readx(params->ssc->regs, SSC_SR),
			ssc_readx(params->ssc->regs, SSC_IER));
G
Geoffrey Wossum 已提交
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
		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;
}

290 291
static snd_pcm_uframes_t atmel_pcm_pointer(
	struct snd_pcm_substream *substream)
G
Geoffrey Wossum 已提交
292 293
{
	struct snd_pcm_runtime *runtime = substream->runtime;
294 295
	struct atmel_runtime_data *prtd = runtime->private_data;
	struct atmel_pcm_dma_params *params = prtd->params;
G
Geoffrey Wossum 已提交
296 297 298 299 300 301 302 303 304 305 306 307
	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;
}

308
static int atmel_pcm_open(struct snd_pcm_substream *substream)
G
Geoffrey Wossum 已提交
309 310
{
	struct snd_pcm_runtime *runtime = substream->runtime;
311
	struct atmel_runtime_data *prtd;
G
Geoffrey Wossum 已提交
312 313
	int ret = 0;

314
	snd_soc_set_runtime_hwparams(substream, &atmel_pcm_hardware);
G
Geoffrey Wossum 已提交
315 316 317

	/* ensure that buffer size is a multiple of period size */
	ret = snd_pcm_hw_constraint_integer(runtime,
318
						SNDRV_PCM_HW_PARAM_PERIODS);
G
Geoffrey Wossum 已提交
319 320 321
	if (ret < 0)
		goto out;

322
	prtd = kzalloc(sizeof(struct atmel_runtime_data), GFP_KERNEL);
G
Geoffrey Wossum 已提交
323 324 325 326 327 328
	if (prtd == NULL) {
		ret = -ENOMEM;
		goto out;
	}
	runtime->private_data = prtd;

329
 out:
G
Geoffrey Wossum 已提交
330 331 332
	return ret;
}

333
static int atmel_pcm_close(struct snd_pcm_substream *substream)
G
Geoffrey Wossum 已提交
334
{
335
	struct atmel_runtime_data *prtd = substream->runtime->private_data;
G
Geoffrey Wossum 已提交
336 337 338 339 340

	kfree(prtd);
	return 0;
}

341 342
static int atmel_pcm_mmap(struct snd_pcm_substream *substream,
	struct vm_area_struct *vma)
G
Geoffrey Wossum 已提交
343 344
{
	return remap_pfn_range(vma, vma->vm_start,
345 346
		       substream->dma_buffer.addr >> PAGE_SHIFT,
		       vma->vm_end - vma->vm_start, vma->vm_page_prot);
G
Geoffrey Wossum 已提交
347 348
}

349
static struct snd_pcm_ops atmel_pcm_ops = {
350 351 352 353 354 355 356 357 358
	.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,
G
Geoffrey Wossum 已提交
359 360 361 362 363 364
};


/*--------------------------------------------------------------------------*\
 * ASoC platform driver
\*--------------------------------------------------------------------------*/
365
static u64 atmel_pcm_dmamask = 0xffffffff;
G
Geoffrey Wossum 已提交
366

367 368
static int atmel_pcm_new(struct snd_card *card,
	struct snd_soc_dai *dai, struct snd_pcm *pcm)
G
Geoffrey Wossum 已提交
369 370 371 372
{
	int ret = 0;

	if (!card->dev->dma_mask)
373
		card->dev->dma_mask = &atmel_pcm_dmamask;
G
Geoffrey Wossum 已提交
374 375 376
	if (!card->dev->coherent_dma_mask)
		card->dev->coherent_dma_mask = 0xffffffff;

377
	if (dai->driver->playback.channels_min) {
378 379
		ret = atmel_pcm_preallocate_dma_buffer(pcm,
			SNDRV_PCM_STREAM_PLAYBACK);
G
Geoffrey Wossum 已提交
380 381 382 383
		if (ret)
			goto out;
	}

384
	if (dai->driver->capture.channels_min) {
385
		pr_debug("atmel-pcm:"
386 387 388
				"Allocating PCM capture DMA buffer\n");
		ret = atmel_pcm_preallocate_dma_buffer(pcm,
			SNDRV_PCM_STREAM_CAPTURE);
G
Geoffrey Wossum 已提交
389 390 391
		if (ret)
			goto out;
	}
392
 out:
G
Geoffrey Wossum 已提交
393 394 395
	return ret;
}

396
static void atmel_pcm_free_dma_buffers(struct snd_pcm *pcm)
G
Geoffrey Wossum 已提交
397 398 399 400 401 402 403
{
	struct snd_pcm_substream *substream;
	struct snd_dma_buffer *buf;
	int stream;

	for (stream = 0; stream < 2; stream++) {
		substream = pcm->streams[stream].substream;
404
		if (!substream)
G
Geoffrey Wossum 已提交
405 406 407 408 409 410 411 412 413 414 415 416
			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
417
static int atmel_pcm_suspend(struct snd_soc_dai *dai)
G
Geoffrey Wossum 已提交
418
{
419
	struct snd_pcm_runtime *runtime = dai->runtime;
420 421
	struct atmel_runtime_data *prtd;
	struct atmel_pcm_dma_params *params;
G
Geoffrey Wossum 已提交
422

423
	if (!runtime)
G
Geoffrey Wossum 已提交
424
		return 0;
425

G
Geoffrey Wossum 已提交
426 427 428
	prtd = runtime->private_data;
	params = prtd->params;

429 430 431
	/* disable the PDC and save the PDC registers */

	ssc_writel(params->ssc->regs, PDC_PTCR, params->mask->pdc_disable);
G
Geoffrey Wossum 已提交
432 433 434 435 436 437 438 439 440

	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;
}

441
static int atmel_pcm_resume(struct snd_soc_dai *dai)
G
Geoffrey Wossum 已提交
442
{
443
	struct snd_pcm_runtime *runtime = dai->runtime;
444 445
	struct atmel_runtime_data *prtd;
	struct atmel_pcm_dma_params *params;
G
Geoffrey Wossum 已提交
446

447
	if (!runtime)
G
Geoffrey Wossum 已提交
448
		return 0;
449

G
Geoffrey Wossum 已提交
450 451 452
	prtd = runtime->private_data;
	params = prtd->params;

453
	/* restore the PDC registers and enable the PDC */
G
Geoffrey Wossum 已提交
454 455 456 457 458
	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);

459
	ssc_writel(params->ssc->regs, PDC_PTCR, params->mask->pdc_enable);
G
Geoffrey Wossum 已提交
460 461
	return 0;
}
462 463 464 465 466
#else
#define atmel_pcm_suspend	NULL
#define atmel_pcm_resume	NULL
#endif

467 468
static struct snd_soc_platform_driver atmel_soc_platform = {
	.ops		= &atmel_pcm_ops,
469 470 471 472
	.pcm_new	= atmel_pcm_new,
	.pcm_free	= atmel_pcm_free_dma_buffers,
	.suspend	= atmel_pcm_suspend,
	.resume		= atmel_pcm_resume,
G
Geoffrey Wossum 已提交
473 474
};

475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496
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)
M
Mark Brown 已提交
497
{
498
	return platform_driver_register(&atmel_pcm_driver);
M
Mark Brown 已提交
499
}
500
module_init(snd_atmel_pcm_init);
M
Mark Brown 已提交
501

502
static void __exit snd_atmel_pcm_exit(void)
M
Mark Brown 已提交
503
{
504
	platform_driver_unregister(&atmel_pcm_driver);
M
Mark Brown 已提交
505
}
506
module_exit(snd_atmel_pcm_exit);
M
Mark Brown 已提交
507

508 509
MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>");
MODULE_DESCRIPTION("Atmel PCM module");
G
Geoffrey Wossum 已提交
510
MODULE_LICENSE("GPL");