kirkwood-dma.c 9.8 KB
Newer Older
1 2 3 4
/*
 * kirkwood-dma.c
 *
 * (c) 2010 Arnaud Patard <apatard@mandriva.com>
5
 * (c) 2010 Arnaud Patard <arnaud.patard@rtp-net.org>
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 *
 *  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/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/mbus.h>
#include <sound/soc.h>
#include "kirkwood.h"

#define KIRKWOOD_RATES \
25 26 27 28
	(SNDRV_PCM_RATE_8000_192000 |		\
	 SNDRV_PCM_RATE_CONTINUOUS |		\
	 SNDRV_PCM_RATE_KNOT)

29 30 31
#define KIRKWOOD_FORMATS \
	(SNDRV_PCM_FMTBIT_S16_LE | \
	 SNDRV_PCM_FMTBIT_S24_LE | \
32 33 34
	 SNDRV_PCM_FMTBIT_S32_LE | \
	 SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE | \
	 SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE)
35

36 37 38 39 40
static struct kirkwood_dma_data *kirkwood_priv(struct snd_pcm_substream *subs)
{
	struct snd_soc_pcm_runtime *soc_runtime = subs->private_data;
	return snd_soc_dai_get_drvdata(soc_runtime->cpu_dai);
}
41 42 43 44 45 46 47 48 49

static struct snd_pcm_hardware kirkwood_dma_snd_hw = {
	.info = (SNDRV_PCM_INFO_INTERLEAVED |
		 SNDRV_PCM_INFO_MMAP |
		 SNDRV_PCM_INFO_MMAP_VALID |
		 SNDRV_PCM_INFO_BLOCK_TRANSFER |
		 SNDRV_PCM_INFO_PAUSE),
	.formats		= KIRKWOOD_FORMATS,
	.rates			= KIRKWOOD_RATES,
50 51
	.rate_min		= 8000,
	.rate_max		= 384000,
52
	.channels_min		= 1,
53
	.channels_max		= 8,
54 55 56 57 58 59 60 61
	.buffer_bytes_max	= KIRKWOOD_SND_MAX_PERIOD_BYTES * KIRKWOOD_SND_MAX_PERIODS,
	.period_bytes_min	= KIRKWOOD_SND_MIN_PERIOD_BYTES,
	.period_bytes_max	= KIRKWOOD_SND_MAX_PERIOD_BYTES,
	.periods_min		= KIRKWOOD_SND_MIN_PERIODS,
	.periods_max		= KIRKWOOD_SND_MAX_PERIODS,
	.fifo_size		= 0,
};

62
static u64 kirkwood_dma_dmamask = DMA_BIT_MASK(32);
63 64 65

static irqreturn_t kirkwood_dma_irq(int irq, void *dev_id)
{
66
	struct kirkwood_dma_data *priv = dev_id;
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
	unsigned long mask, status, cause;

	mask = readl(priv->io + KIRKWOOD_INT_MASK);
	status = readl(priv->io + KIRKWOOD_INT_CAUSE) & mask;

	cause = readl(priv->io + KIRKWOOD_ERR_CAUSE);
	if (unlikely(cause)) {
		printk(KERN_WARNING "%s: got err interrupt 0x%lx\n",
				__func__, cause);
		writel(cause, priv->io + KIRKWOOD_ERR_CAUSE);
	}

	/* we've enabled only bytes interrupts ... */
	if (status & ~(KIRKWOOD_INT_CAUSE_PLAY_BYTES | \
			KIRKWOOD_INT_CAUSE_REC_BYTES)) {
		printk(KERN_WARNING "%s: unexpected interrupt %lx\n",
			__func__, status);
		return IRQ_NONE;
	}

	/* ack int */
	writel(status, priv->io + KIRKWOOD_INT_CAUSE);

	if (status & KIRKWOOD_INT_CAUSE_PLAY_BYTES)
91
		snd_pcm_period_elapsed(priv->substream_play);
92 93

	if (status & KIRKWOOD_INT_CAUSE_REC_BYTES)
94
		snd_pcm_period_elapsed(priv->substream_rec);
95 96 97 98

	return IRQ_HANDLED;
}

99 100 101 102
static void
kirkwood_dma_conf_mbus_windows(void __iomem *base, int win,
			       unsigned long dma,
			       const struct mbus_dram_target_info *dram)
103 104 105 106 107 108 109 110 111
{
	int i;

	/* First disable and clear windows */
	writel(0, base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win));
	writel(0, base + KIRKWOOD_AUDIO_WIN_BASE_REG(win));

	/* try to find matching cs for current dma address */
	for (i = 0; i < dram->num_cs; i++) {
112
		const struct mbus_dram_window *cs = dram->cs + i;
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
		if ((cs->base & 0xffff0000) < (dma & 0xffff0000)) {
			writel(cs->base & 0xffff0000,
				base + KIRKWOOD_AUDIO_WIN_BASE_REG(win));
			writel(((cs->size - 1) & 0xffff0000) |
				(cs->mbus_attr << 8) |
				(dram->mbus_dram_target_id << 4) | 1,
				base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win));
		}
	}
}

static int kirkwood_dma_open(struct snd_pcm_substream *substream)
{
	int err;
	struct snd_pcm_runtime *runtime = substream->runtime;
128
	struct kirkwood_dma_data *priv = kirkwood_priv(substream);
129
	const struct mbus_dram_target_info *dram;
130 131 132 133
	unsigned long addr;

	snd_soc_set_runtime_hwparams(substream, &kirkwood_dma_snd_hw);

L
Lucas De Marchi 已提交
134
	/* Ensure that all constraints linked to dma burst are fulfilled */
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
	err = snd_pcm_hw_constraint_minmax(runtime,
			SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
			priv->burst * 2,
			KIRKWOOD_AUDIO_BUF_MAX-1);
	if (err < 0)
		return err;

	err = snd_pcm_hw_constraint_step(runtime, 0,
			SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
			priv->burst);
	if (err < 0)
		return err;

	err = snd_pcm_hw_constraint_step(substream->runtime, 0,
			 SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
			 priv->burst);
	if (err < 0)
		return err;

154
	if (!priv->substream_play && !priv->substream_rec) {
155
		err = request_irq(priv->irq, kirkwood_dma_irq, IRQF_SHARED,
156 157
				  "kirkwood-i2s", priv);
		if (err)
158 159 160 161
			return -EBUSY;

		/*
		 * Enable Error interrupts. We're only ack'ing them but
L
Lucas De Marchi 已提交
162
		 * it's useful for diagnostics
163 164 165 166
		 */
		writel((unsigned long)-1, priv->io + KIRKWOOD_ERR_MASK);
	}

167
	dram = mv_mbus_dram_info();
168
	addr = substream->dma_buffer.addr;
169
	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
170
		priv->substream_play = substream;
171
		kirkwood_dma_conf_mbus_windows(priv->io,
172
			KIRKWOOD_PLAYBACK_WIN, addr, dram);
173
	} else {
174
		priv->substream_rec = substream;
175
		kirkwood_dma_conf_mbus_windows(priv->io,
176
			KIRKWOOD_RECORD_WIN, addr, dram);
177 178 179 180 181 182 183
	}

	return 0;
}

static int kirkwood_dma_close(struct snd_pcm_substream *substream)
{
184
	struct kirkwood_dma_data *priv = kirkwood_priv(substream);
185

186
	if (!priv)
187 188 189
		return 0;

	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
190
		priv->substream_play = NULL;
191
	else
192
		priv->substream_rec = NULL;
193

194
	if (!priv->substream_play && !priv->substream_rec) {
195
		writel(0, priv->io + KIRKWOOD_ERR_MASK);
196
		free_irq(priv->irq, priv);
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
	}

	return 0;
}

static int kirkwood_dma_hw_params(struct snd_pcm_substream *substream,
				struct snd_pcm_hw_params *params)
{
	struct snd_pcm_runtime *runtime = substream->runtime;

	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
	runtime->dma_bytes = params_buffer_bytes(params);

	return 0;
}

static int kirkwood_dma_hw_free(struct snd_pcm_substream *substream)
{
	snd_pcm_set_runtime_buffer(substream, NULL);
	return 0;
}

static int kirkwood_dma_prepare(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
222
	struct kirkwood_dma_data *priv = kirkwood_priv(substream);
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
	unsigned long size, count;

	/* compute buffer size in term of "words" as requested in specs */
	size = frames_to_bytes(runtime, runtime->buffer_size);
	size = (size>>2)-1;
	count = snd_pcm_lib_period_bytes(substream);

	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
		writel(count, priv->io + KIRKWOOD_PLAY_BYTE_INT_COUNT);
		writel(runtime->dma_addr, priv->io + KIRKWOOD_PLAY_BUF_ADDR);
		writel(size, priv->io + KIRKWOOD_PLAY_BUF_SIZE);
	} else {
		writel(count, priv->io + KIRKWOOD_REC_BYTE_INT_COUNT);
		writel(runtime->dma_addr, priv->io + KIRKWOOD_REC_BUF_ADDR);
		writel(size, priv->io + KIRKWOOD_REC_BUF_SIZE);
	}


	return 0;
}

static snd_pcm_uframes_t kirkwood_dma_pointer(struct snd_pcm_substream
						*substream)
{
247
	struct kirkwood_dma_data *priv = kirkwood_priv(substream);
248 249 250 251 252 253 254 255 256 257 258 259
	snd_pcm_uframes_t count;

	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
		count = bytes_to_frames(substream->runtime,
			readl(priv->io + KIRKWOOD_PLAY_BYTE_COUNT));
	else
		count = bytes_to_frames(substream->runtime,
			readl(priv->io + KIRKWOOD_REC_BYTE_COUNT));

	return count;
}

260
static struct snd_pcm_ops kirkwood_dma_ops = {
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
	.open =		kirkwood_dma_open,
	.close =        kirkwood_dma_close,
	.ioctl =	snd_pcm_lib_ioctl,
	.hw_params =	kirkwood_dma_hw_params,
	.hw_free =      kirkwood_dma_hw_free,
	.prepare =      kirkwood_dma_prepare,
	.pointer =	kirkwood_dma_pointer,
};

static int kirkwood_dma_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 = kirkwood_dma_snd_hw.buffer_bytes_max;

	buf->dev.type = SNDRV_DMA_TYPE_DEV;
	buf->dev.dev = pcm->card->dev;
	buf->area = dma_alloc_coherent(pcm->card->dev, size,
			&buf->addr, GFP_KERNEL);
	if (!buf->area)
		return -ENOMEM;
	buf->bytes = size;
	buf->private_data = NULL;

	return 0;
}

289
static int kirkwood_dma_new(struct snd_soc_pcm_runtime *rtd)
290
{
291 292
	struct snd_card *card = rtd->card->snd_card;
	struct snd_pcm *pcm = rtd->pcm;
293 294 295 296 297
	int ret;

	if (!card->dev->dma_mask)
		card->dev->dma_mask = &kirkwood_dma_dmamask;
	if (!card->dev->coherent_dma_mask)
298
		card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
299

300
	if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
301 302 303 304 305 306
		ret = kirkwood_dma_preallocate_dma_buffer(pcm,
				SNDRV_PCM_STREAM_PLAYBACK);
		if (ret)
			return ret;
	}

307
	if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
		ret = kirkwood_dma_preallocate_dma_buffer(pcm,
				SNDRV_PCM_STREAM_CAPTURE);
		if (ret)
			return ret;
	}

	return 0;
}

static void kirkwood_dma_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)
			continue;
		buf = &substream->dma_buffer;
		if (!buf->area)
			continue;

		dma_free_coherent(pcm->card->dev, buf->bytes,
				buf->area, buf->addr);
		buf->area = NULL;
	}
}

337 338
static struct snd_soc_platform_driver kirkwood_soc_platform = {
	.ops		= &kirkwood_dma_ops,
339 340 341 342
	.pcm_new	= kirkwood_dma_new,
	.pcm_free	= kirkwood_dma_free_dma_buffers,
};

343
static int kirkwood_soc_platform_probe(struct platform_device *pdev)
344
{
345
	return snd_soc_register_platform(&pdev->dev, &kirkwood_soc_platform);
346 347
}

348
static int kirkwood_soc_platform_remove(struct platform_device *pdev)
349
{
350 351 352 353 354 355 356 357 358 359 360
	snd_soc_unregister_platform(&pdev->dev);
	return 0;
}

static struct platform_driver kirkwood_pcm_driver = {
	.driver = {
			.name = "kirkwood-pcm-audio",
			.owner = THIS_MODULE,
	},

	.probe = kirkwood_soc_platform_probe,
361
	.remove = kirkwood_soc_platform_remove,
362 363
};

364
module_platform_driver(kirkwood_pcm_driver);
365

366
MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>");
367 368
MODULE_DESCRIPTION("Marvell Kirkwood Audio DMA module");
MODULE_LICENSE("GPL");
369
MODULE_ALIAS("platform:kirkwood-pcm-audio");