• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2010 Nuvoton technology corporation.
3  *
4  * Wan ZongShun <mcuos.com@gmail.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation;version 2 of the License.
9  *
10  */
11 
12 #include <linux/module.h>
13 #include <linux/init.h>
14 #include <linux/io.h>
15 #include <linux/platform_device.h>
16 #include <linux/slab.h>
17 #include <linux/dma-mapping.h>
18 
19 #include <sound/core.h>
20 #include <sound/pcm.h>
21 #include <sound/pcm_params.h>
22 #include <sound/soc.h>
23 
24 #include <mach/hardware.h>
25 
26 #include "nuc900-audio.h"
27 
28 static const struct snd_pcm_hardware nuc900_pcm_hardware = {
29 	.info			= SNDRV_PCM_INFO_INTERLEAVED |
30 					SNDRV_PCM_INFO_BLOCK_TRANSFER |
31 					SNDRV_PCM_INFO_MMAP |
32 					SNDRV_PCM_INFO_MMAP_VALID |
33 					SNDRV_PCM_INFO_PAUSE |
34 					SNDRV_PCM_INFO_RESUME,
35 	.buffer_bytes_max	= 4*1024,
36 	.period_bytes_min	= 1*1024,
37 	.period_bytes_max	= 4*1024,
38 	.periods_min		= 1,
39 	.periods_max		= 1024,
40 };
41 
nuc900_dma_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params)42 static int nuc900_dma_hw_params(struct snd_pcm_substream *substream,
43 	struct snd_pcm_hw_params *params)
44 {
45 	struct snd_pcm_runtime *runtime = substream->runtime;
46 	struct nuc900_audio *nuc900_audio = runtime->private_data;
47 	unsigned long flags;
48 	int ret = 0;
49 
50 	ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
51 	if (ret < 0)
52 		return ret;
53 
54 	spin_lock_irqsave(&nuc900_audio->lock, flags);
55 
56 	nuc900_audio->substream = substream;
57 	nuc900_audio->dma_addr[substream->stream] = runtime->dma_addr;
58 	nuc900_audio->buffersize[substream->stream] =
59 						params_buffer_bytes(params);
60 
61 	spin_unlock_irqrestore(&nuc900_audio->lock, flags);
62 
63 	return ret;
64 }
65 
nuc900_update_dma_register(struct snd_pcm_substream * substream,dma_addr_t dma_addr,size_t count)66 static void nuc900_update_dma_register(struct snd_pcm_substream *substream,
67 				dma_addr_t dma_addr, size_t count)
68 {
69 	struct snd_pcm_runtime *runtime = substream->runtime;
70 	struct nuc900_audio *nuc900_audio = runtime->private_data;
71 	void __iomem *mmio_addr, *mmio_len;
72 
73 	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
74 		mmio_addr = nuc900_audio->mmio + ACTL_PDSTB;
75 		mmio_len = nuc900_audio->mmio + ACTL_PDST_LENGTH;
76 	} else {
77 		mmio_addr = nuc900_audio->mmio + ACTL_RDSTB;
78 		mmio_len = nuc900_audio->mmio + ACTL_RDST_LENGTH;
79 	}
80 
81 	AUDIO_WRITE(mmio_addr, dma_addr);
82 	AUDIO_WRITE(mmio_len, count);
83 }
84 
nuc900_dma_start(struct snd_pcm_substream * substream)85 static void nuc900_dma_start(struct snd_pcm_substream *substream)
86 {
87 	struct snd_pcm_runtime *runtime = substream->runtime;
88 	struct nuc900_audio *nuc900_audio = runtime->private_data;
89 	unsigned long val;
90 
91 	val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
92 	val |= (T_DMA_IRQ | R_DMA_IRQ);
93 	AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val);
94 }
95 
nuc900_dma_stop(struct snd_pcm_substream * substream)96 static void nuc900_dma_stop(struct snd_pcm_substream *substream)
97 {
98 	struct snd_pcm_runtime *runtime = substream->runtime;
99 	struct nuc900_audio *nuc900_audio = runtime->private_data;
100 	unsigned long val;
101 
102 	val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
103 	val &= ~(T_DMA_IRQ | R_DMA_IRQ);
104 	AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val);
105 }
106 
nuc900_dma_interrupt(int irq,void * dev_id)107 static irqreturn_t nuc900_dma_interrupt(int irq, void *dev_id)
108 {
109 	struct snd_pcm_substream *substream = dev_id;
110 	struct nuc900_audio *nuc900_audio = substream->runtime->private_data;
111 	unsigned long val;
112 
113 	spin_lock(&nuc900_audio->lock);
114 
115 	val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
116 
117 	if (val & R_DMA_IRQ) {
118 		AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | R_DMA_IRQ);
119 
120 		val = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR);
121 
122 		if (val & R_DMA_MIDDLE_IRQ) {
123 			val |= R_DMA_MIDDLE_IRQ;
124 			AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val);
125 		}
126 
127 		if (val & R_DMA_END_IRQ) {
128 			val |= R_DMA_END_IRQ;
129 			AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val);
130 		}
131 	} else if (val & T_DMA_IRQ) {
132 		AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | T_DMA_IRQ);
133 
134 		val = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR);
135 
136 		if (val & P_DMA_MIDDLE_IRQ) {
137 			val |= P_DMA_MIDDLE_IRQ;
138 			AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val);
139 		}
140 
141 		if (val & P_DMA_END_IRQ) {
142 			val |= P_DMA_END_IRQ;
143 			AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val);
144 		}
145 	} else {
146 		dev_err(nuc900_audio->dev, "Wrong DMA interrupt status!\n");
147 		spin_unlock(&nuc900_audio->lock);
148 		return IRQ_HANDLED;
149 	}
150 
151 	spin_unlock(&nuc900_audio->lock);
152 
153 	snd_pcm_period_elapsed(substream);
154 
155 	return IRQ_HANDLED;
156 }
157 
nuc900_dma_hw_free(struct snd_pcm_substream * substream)158 static int nuc900_dma_hw_free(struct snd_pcm_substream *substream)
159 {
160 	snd_pcm_lib_free_pages(substream);
161 	return 0;
162 }
163 
nuc900_dma_prepare(struct snd_pcm_substream * substream)164 static int nuc900_dma_prepare(struct snd_pcm_substream *substream)
165 {
166 	struct snd_pcm_runtime *runtime = substream->runtime;
167 	struct nuc900_audio *nuc900_audio = runtime->private_data;
168 	unsigned long flags, val;
169 	int ret = 0;
170 
171 	spin_lock_irqsave(&nuc900_audio->lock, flags);
172 
173 	nuc900_update_dma_register(substream,
174 				nuc900_audio->dma_addr[substream->stream],
175 				nuc900_audio->buffersize[substream->stream]);
176 
177 	val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
178 
179 	switch (runtime->channels) {
180 	case 1:
181 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
182 			val &= ~(PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL);
183 			val |= PLAY_RIGHT_CHNNEL;
184 		} else {
185 			val &= ~(RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL);
186 			val |= RECORD_RIGHT_CHNNEL;
187 		}
188 		AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
189 		break;
190 	case 2:
191 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
192 			val |= (PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL);
193 		else
194 			val |= (RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL);
195 		AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
196 		break;
197 	default:
198 		ret = -EINVAL;
199 	}
200 	spin_unlock_irqrestore(&nuc900_audio->lock, flags);
201 	return ret;
202 }
203 
nuc900_dma_trigger(struct snd_pcm_substream * substream,int cmd)204 static int nuc900_dma_trigger(struct snd_pcm_substream *substream, int cmd)
205 {
206 	int ret = 0;
207 
208 	switch (cmd) {
209 	case SNDRV_PCM_TRIGGER_START:
210 	case SNDRV_PCM_TRIGGER_RESUME:
211 		nuc900_dma_start(substream);
212 		break;
213 
214 	case SNDRV_PCM_TRIGGER_STOP:
215 	case SNDRV_PCM_TRIGGER_SUSPEND:
216 		nuc900_dma_stop(substream);
217 		break;
218 
219 	default:
220 		ret = -EINVAL;
221 		break;
222 	}
223 
224 	return ret;
225 }
226 
nuc900_dma_getposition(struct snd_pcm_substream * substream,dma_addr_t * src,dma_addr_t * dst)227 static int nuc900_dma_getposition(struct snd_pcm_substream *substream,
228 					dma_addr_t *src, dma_addr_t *dst)
229 {
230 	struct snd_pcm_runtime *runtime = substream->runtime;
231 	struct nuc900_audio *nuc900_audio = runtime->private_data;
232 
233 	if (src != NULL)
234 		*src = AUDIO_READ(nuc900_audio->mmio + ACTL_PDSTC);
235 
236 	if (dst != NULL)
237 		*dst = AUDIO_READ(nuc900_audio->mmio + ACTL_RDSTC);
238 
239 	return 0;
240 }
241 
nuc900_dma_pointer(struct snd_pcm_substream * substream)242 static snd_pcm_uframes_t nuc900_dma_pointer(struct snd_pcm_substream *substream)
243 {
244 	struct snd_pcm_runtime *runtime = substream->runtime;
245 	dma_addr_t src, dst;
246 	unsigned long res;
247 
248 	nuc900_dma_getposition(substream, &src, &dst);
249 
250 	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
251 		res = dst - runtime->dma_addr;
252 	else
253 		res = src - runtime->dma_addr;
254 
255 	return bytes_to_frames(substream->runtime, res);
256 }
257 
nuc900_dma_open(struct snd_pcm_substream * substream)258 static int nuc900_dma_open(struct snd_pcm_substream *substream)
259 {
260 	struct snd_pcm_runtime *runtime = substream->runtime;
261 	struct nuc900_audio *nuc900_audio;
262 
263 	snd_soc_set_runtime_hwparams(substream, &nuc900_pcm_hardware);
264 
265 	nuc900_audio = nuc900_ac97_data;
266 
267 	if (request_irq(nuc900_audio->irq_num, nuc900_dma_interrupt,
268 			0, "nuc900-dma", substream))
269 		return -EBUSY;
270 
271 	runtime->private_data = nuc900_audio;
272 
273 	return 0;
274 }
275 
nuc900_dma_close(struct snd_pcm_substream * substream)276 static int nuc900_dma_close(struct snd_pcm_substream *substream)
277 {
278 	struct snd_pcm_runtime *runtime = substream->runtime;
279 	struct nuc900_audio *nuc900_audio = runtime->private_data;
280 
281 	free_irq(nuc900_audio->irq_num, substream);
282 
283 	return 0;
284 }
285 
nuc900_dma_mmap(struct snd_pcm_substream * substream,struct vm_area_struct * vma)286 static int nuc900_dma_mmap(struct snd_pcm_substream *substream,
287 	struct vm_area_struct *vma)
288 {
289 	struct snd_pcm_runtime *runtime = substream->runtime;
290 
291 	return dma_mmap_writecombine(substream->pcm->card->dev, vma,
292 					runtime->dma_area,
293 					runtime->dma_addr,
294 					runtime->dma_bytes);
295 }
296 
297 static struct snd_pcm_ops nuc900_dma_ops = {
298 	.open		= nuc900_dma_open,
299 	.close		= nuc900_dma_close,
300 	.ioctl		= snd_pcm_lib_ioctl,
301 	.hw_params	= nuc900_dma_hw_params,
302 	.hw_free	= nuc900_dma_hw_free,
303 	.prepare	= nuc900_dma_prepare,
304 	.trigger	= nuc900_dma_trigger,
305 	.pointer	= nuc900_dma_pointer,
306 	.mmap		= nuc900_dma_mmap,
307 };
308 
nuc900_dma_free_dma_buffers(struct snd_pcm * pcm)309 static void nuc900_dma_free_dma_buffers(struct snd_pcm *pcm)
310 {
311 	snd_pcm_lib_preallocate_free_for_all(pcm);
312 }
313 
nuc900_dma_new(struct snd_soc_pcm_runtime * rtd)314 static int nuc900_dma_new(struct snd_soc_pcm_runtime *rtd)
315 {
316 	struct snd_card *card = rtd->card->snd_card;
317 	struct snd_pcm *pcm = rtd->pcm;
318 	int ret;
319 
320 	ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
321 	if (ret)
322 		return ret;
323 
324 	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
325 		card->dev, 4 * 1024, (4 * 1024) - 1);
326 
327 	return 0;
328 }
329 
330 static struct snd_soc_platform_driver nuc900_soc_platform = {
331 	.ops		= &nuc900_dma_ops,
332 	.pcm_new	= nuc900_dma_new,
333 	.pcm_free	= nuc900_dma_free_dma_buffers,
334 };
335 
nuc900_soc_platform_probe(struct platform_device * pdev)336 static int nuc900_soc_platform_probe(struct platform_device *pdev)
337 {
338 	return snd_soc_register_platform(&pdev->dev, &nuc900_soc_platform);
339 }
340 
nuc900_soc_platform_remove(struct platform_device * pdev)341 static int nuc900_soc_platform_remove(struct platform_device *pdev)
342 {
343 	snd_soc_unregister_platform(&pdev->dev);
344 	return 0;
345 }
346 
347 static struct platform_driver nuc900_pcm_driver = {
348 	.driver = {
349 			.name = "nuc900-pcm-audio",
350 			.owner = THIS_MODULE,
351 	},
352 
353 	.probe = nuc900_soc_platform_probe,
354 	.remove = nuc900_soc_platform_remove,
355 };
356 
357 module_platform_driver(nuc900_pcm_driver);
358 
359 MODULE_AUTHOR("Wan ZongShun, <mcuos.com@gmail.com>");
360 MODULE_DESCRIPTION("nuc900 Audio DMA module");
361 MODULE_LICENSE("GPL");
362