2 * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
9 * You should have received a copy of the GNU General Public License along
10 * with this program; if not, write to the Free Software Foundation, Inc.,
11 * 675 Mass Ave, Cambridge, MA 02139, USA.
15 #include <linux/kernel.h>
16 #include <linux/module.h>
17 #include <linux/init.h>
18 #include <linux/interrupt.h>
19 #include <linux/dma-mapping.h>
21 #include <sound/core.h>
22 #include <sound/pcm.h>
23 #include <sound/pcm_params.h>
24 #include <sound/soc.h>
26 #include <asm/mach-jz4740/dma.h>
27 #include "jz4740-pcm.h"
29 struct jz4740_runtime_data
{
30 unsigned int dma_period
;
35 struct jz4740_dma_chan
*dma
;
40 /* identify hardware playback capabilities */
41 static const struct snd_pcm_hardware jz4740_pcm_hardware
= {
42 .info
= SNDRV_PCM_INFO_MMAP
|
43 SNDRV_PCM_INFO_MMAP_VALID
|
44 SNDRV_PCM_INFO_INTERLEAVED
|
45 SNDRV_PCM_INFO_BLOCK_TRANSFER
,
46 .formats
= SNDRV_PCM_FMTBIT_S16_LE
|
48 .rates
= SNDRV_PCM_RATE_8000_48000
,
51 .period_bytes_min
= 32,
52 .period_bytes_max
= 2 * PAGE_SIZE
,
55 .buffer_bytes_max
= 128 * 2 * PAGE_SIZE
,
59 static void jz4740_pcm_start_transfer(struct jz4740_runtime_data
*prtd
, int stream
)
63 if (prtd
->dma_pos
+ prtd
->dma_period
> prtd
->dma_end
)
64 count
= prtd
->dma_end
- prtd
->dma_pos
;
66 count
= prtd
->dma_period
;
68 jz4740_dma_disable(prtd
->dma
);
70 if (stream
== SNDRV_PCM_STREAM_PLAYBACK
) {
71 jz4740_dma_set_src_addr(prtd
->dma
, prtd
->dma_pos
);
72 jz4740_dma_set_dst_addr(prtd
->dma
, prtd
->fifo_addr
);
74 jz4740_dma_set_src_addr(prtd
->dma
, prtd
->fifo_addr
);
75 jz4740_dma_set_dst_addr(prtd
->dma
, prtd
->dma_pos
);
78 jz4740_dma_set_transfer_count(prtd
->dma
, count
);
80 jz4740_dma_enable(prtd
->dma
);
82 prtd
->dma_pos
+= prtd
->dma_period
;
83 if (prtd
->dma_pos
>= prtd
->dma_end
)
84 prtd
->dma_pos
= prtd
->dma_start
;
87 static void jz4740_pcm_dma_transfer_done(struct jz4740_dma_chan
*dma
, int err
,
90 struct snd_pcm_substream
*substream
= dev_id
;
91 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
92 struct jz4740_runtime_data
*prtd
= runtime
->private_data
;
94 snd_pcm_period_elapsed(substream
);
96 jz4740_pcm_start_transfer(prtd
, substream
->stream
);
99 static int jz4740_pcm_hw_params(struct snd_pcm_substream
*substream
,
100 struct snd_pcm_hw_params
*params
)
102 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
103 struct jz4740_runtime_data
*prtd
= runtime
->private_data
;
104 struct snd_soc_pcm_runtime
*rtd
= substream
->private_data
;
105 struct jz4740_pcm_config
*config
;
107 config
= rtd
->dai
->cpu_dai
->dma_data
;
109 if (substream
->stream
== SNDRV_PCM_STREAM_PLAYBACK
)
110 prtd
->dma
= jz4740_dma_request(substream
, "PCM Playback");
112 prtd
->dma
= jz4740_dma_request(substream
, "PCM Capture");
118 jz4740_dma_configure(prtd
->dma
, config
->dma_config
);
119 prtd
->fifo_addr
= config
->fifo_addr
;
121 jz4740_dma_set_complete_cb(prtd
->dma
, jz4740_pcm_dma_transfer_done
);
123 snd_pcm_set_runtime_buffer(substream
, &substream
->dma_buffer
);
124 runtime
->dma_bytes
= params_buffer_bytes(params
);
126 prtd
->dma_period
= params_period_bytes(params
);
127 prtd
->dma_start
= runtime
->dma_addr
;
128 prtd
->dma_pos
= prtd
->dma_start
;
129 prtd
->dma_end
= prtd
->dma_start
+ runtime
->dma_bytes
;
134 static int jz4740_pcm_hw_free(struct snd_pcm_substream
*substream
)
136 struct jz4740_runtime_data
*prtd
= substream
->runtime
->private_data
;
138 snd_pcm_set_runtime_buffer(substream
, NULL
);
140 jz4740_dma_free(prtd
->dma
);
145 static int jz4740_pcm_prepare(struct snd_pcm_substream
*substream
)
147 struct jz4740_runtime_data
*prtd
= substream
->runtime
->private_data
;
153 prtd
->dma_pos
= prtd
->dma_start
;
158 static int jz4740_pcm_trigger(struct snd_pcm_substream
*substream
, int cmd
)
160 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
161 struct jz4740_runtime_data
*prtd
= runtime
->private_data
;
166 case SNDRV_PCM_TRIGGER_START
:
167 case SNDRV_PCM_TRIGGER_RESUME
:
168 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE
:
169 jz4740_pcm_start_transfer(prtd
, substream
->stream
);
171 case SNDRV_PCM_TRIGGER_STOP
:
172 case SNDRV_PCM_TRIGGER_SUSPEND
:
173 case SNDRV_PCM_TRIGGER_PAUSE_PUSH
:
174 jz4740_dma_disable(prtd
->dma
);
183 static snd_pcm_uframes_t
jz4740_pcm_pointer(struct snd_pcm_substream
*substream
)
185 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
186 struct jz4740_runtime_data
*prtd
= runtime
->private_data
;
187 unsigned long count
, pos
;
188 snd_pcm_uframes_t offset
;
189 struct jz4740_dma_chan
*dma
= prtd
->dma
;
191 count
= jz4740_dma_get_residue(dma
);
192 if (prtd
->dma_pos
== prtd
->dma_start
)
193 pos
= prtd
->dma_end
- prtd
->dma_start
- count
;
195 pos
= prtd
->dma_pos
- prtd
->dma_start
- count
;
197 offset
= bytes_to_frames(runtime
, pos
);
198 if (offset
>= runtime
->buffer_size
)
204 static int jz4740_pcm_open(struct snd_pcm_substream
*substream
)
206 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
207 struct jz4740_runtime_data
*prtd
;
209 snd_soc_set_runtime_hwparams(substream
, &jz4740_pcm_hardware
);
210 prtd
= kzalloc(sizeof(struct jz4740_runtime_data
), GFP_KERNEL
);
215 runtime
->private_data
= prtd
;
219 static int jz4740_pcm_close(struct snd_pcm_substream
*substream
)
221 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
222 struct jz4740_runtime_data
*prtd
= runtime
->private_data
;
229 static int jz4740_pcm_mmap(struct snd_pcm_substream
*substream
,
230 struct vm_area_struct
*vma
)
232 return remap_pfn_range(vma
, vma
->vm_start
,
233 substream
->dma_buffer
.addr
>> PAGE_SHIFT
,
234 vma
->vm_end
- vma
->vm_start
, vma
->vm_page_prot
);
237 static struct snd_pcm_ops jz4740_pcm_ops
= {
238 .open
= jz4740_pcm_open
,
239 .close
= jz4740_pcm_close
,
240 .ioctl
= snd_pcm_lib_ioctl
,
241 .hw_params
= jz4740_pcm_hw_params
,
242 .hw_free
= jz4740_pcm_hw_free
,
243 .prepare
= jz4740_pcm_prepare
,
244 .trigger
= jz4740_pcm_trigger
,
245 .pointer
= jz4740_pcm_pointer
,
246 .mmap
= jz4740_pcm_mmap
,
249 static int jz4740_pcm_preallocate_dma_buffer(struct snd_pcm
*pcm
, int stream
)
251 struct snd_pcm_substream
*substream
= pcm
->streams
[stream
].substream
;
252 struct snd_dma_buffer
*buf
= &substream
->dma_buffer
;
253 size_t size
= jz4740_pcm_hardware
.buffer_bytes_max
;
255 buf
->dev
.type
= SNDRV_DMA_TYPE_DEV
;
256 buf
->dev
.dev
= pcm
->card
->dev
;
257 buf
->private_data
= NULL
;
259 buf
->area
= dma_alloc_noncoherent(pcm
->card
->dev
, size
,
260 &buf
->addr
, GFP_KERNEL
);
269 static void jz4740_pcm_free(struct snd_pcm
*pcm
)
271 struct snd_pcm_substream
*substream
;
272 struct snd_dma_buffer
*buf
;
275 for (stream
= 0; stream
< 2; stream
++) {
276 substream
= pcm
->streams
[stream
].substream
;
280 buf
= &substream
->dma_buffer
;
284 dma_free_noncoherent(pcm
->card
->dev
, buf
->bytes
,
285 buf
->area
, buf
->addr
);
290 static u64 jz4740_pcm_dmamask
= DMA_BIT_MASK(32);
292 int jz4740_pcm_new(struct snd_card
*card
, struct snd_soc_dai
*dai
,
297 if (!card
->dev
->dma_mask
)
298 card
->dev
->dma_mask
= &jz4740_pcm_dmamask
;
300 if (!card
->dev
->coherent_dma_mask
)
301 card
->dev
->coherent_dma_mask
= DMA_BIT_MASK(32);
303 if (dai
->playback
.channels_min
) {
304 ret
= jz4740_pcm_preallocate_dma_buffer(pcm
,
305 SNDRV_PCM_STREAM_PLAYBACK
);
310 if (dai
->capture
.channels_min
) {
311 ret
= jz4740_pcm_preallocate_dma_buffer(pcm
,
312 SNDRV_PCM_STREAM_CAPTURE
);
321 struct snd_soc_platform jz4740_soc_platform
= {
322 .name
= "jz4740-pcm",
323 .pcm_ops
= &jz4740_pcm_ops
,
324 .pcm_new
= jz4740_pcm_new
,
325 .pcm_free
= jz4740_pcm_free
,
327 EXPORT_SYMBOL_GPL(jz4740_soc_platform
);
329 static int __init
jz4740_soc_platform_init(void)
331 return snd_soc_register_platform(&jz4740_soc_platform
);
333 module_init(jz4740_soc_platform_init
);
335 static void __exit
jz4740_soc_platform_exit(void)
337 snd_soc_unregister_platform(&jz4740_soc_platform
);
339 module_exit(jz4740_soc_platform_exit
);
341 MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
342 MODULE_DESCRIPTION("Ingenic SoC JZ4740 PCM driver");
343 MODULE_LICENSE("GPL");