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
;
108 if (substream
->stream
== SNDRV_PCM_STREAM_PLAYBACK
) {
109 prtd
->dma
= jz4740_dma_request(substream
, "PCM Playback");
111 prtd
->dma
= jz4740_dma_request(substream
, "PCM Capture");
117 jz4740_dma_configure(prtd
->dma
, config
->dma_config
);
118 prtd
->fifo_addr
= config
->fifo_addr
;
120 jz4740_dma_set_complete_cb(prtd
->dma
, jz4740_pcm_dma_transfer_done
);
122 snd_pcm_set_runtime_buffer(substream
, &substream
->dma_buffer
);
123 runtime
->dma_bytes
= params_buffer_bytes(params
);
125 prtd
->dma_period
= params_period_bytes(params
);
126 prtd
->dma_start
= runtime
->dma_addr
;
127 prtd
->dma_pos
= prtd
->dma_start
;
128 prtd
->dma_end
= prtd
->dma_start
+ runtime
->dma_bytes
;
133 static int jz4740_pcm_hw_free(struct snd_pcm_substream
*substream
)
135 struct jz4740_runtime_data
*prtd
= substream
->runtime
->private_data
;
137 snd_pcm_set_runtime_buffer(substream
, NULL
);
139 jz4740_dma_free(prtd
->dma
);
144 static int jz4740_pcm_prepare(struct snd_pcm_substream
*substream
)
146 struct jz4740_runtime_data
*prtd
= substream
->runtime
->private_data
;
152 prtd
->dma_pos
= prtd
->dma_start
;
157 static int jz4740_pcm_trigger(struct snd_pcm_substream
*substream
, int cmd
)
159 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
160 struct jz4740_runtime_data
*prtd
= runtime
->private_data
;
165 case SNDRV_PCM_TRIGGER_START
:
166 case SNDRV_PCM_TRIGGER_RESUME
:
167 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE
:
168 jz4740_pcm_start_transfer(prtd
, substream
->stream
);
170 case SNDRV_PCM_TRIGGER_STOP
:
171 case SNDRV_PCM_TRIGGER_SUSPEND
:
172 case SNDRV_PCM_TRIGGER_PAUSE_PUSH
:
173 jz4740_dma_disable(prtd
->dma
);
182 static snd_pcm_uframes_t
jz4740_pcm_pointer(struct snd_pcm_substream
*substream
)
184 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
185 struct jz4740_runtime_data
*prtd
= runtime
->private_data
;
186 unsigned long count
, pos
;
187 snd_pcm_uframes_t offset
;
188 struct jz4740_dma_chan
*dma
= prtd
->dma
;
190 count
= jz4740_dma_get_residue(dma
);
191 if (prtd
->dma_pos
== prtd
->dma_start
)
192 pos
= prtd
->dma_end
- prtd
->dma_start
- count
;
194 pos
= prtd
->dma_pos
- prtd
->dma_start
- count
;
196 offset
= bytes_to_frames(runtime
, pos
);
197 if (offset
>= runtime
->buffer_size
)
203 static int jz4740_pcm_open(struct snd_pcm_substream
*substream
)
205 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
206 struct jz4740_runtime_data
*prtd
;
208 snd_soc_set_runtime_hwparams(substream
, &jz4740_pcm_hardware
);
209 prtd
= kzalloc(sizeof(struct jz4740_runtime_data
), GFP_KERNEL
);
214 runtime
->private_data
= prtd
;
218 static int jz4740_pcm_close(struct snd_pcm_substream
*substream
)
220 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
221 struct jz4740_runtime_data
*prtd
= runtime
->private_data
;
228 static int jz4740_pcm_mmap(struct snd_pcm_substream
*substream
,
229 struct vm_area_struct
*vma
)
231 return remap_pfn_range(vma
, vma
->vm_start
,
232 substream
->dma_buffer
.addr
>> PAGE_SHIFT
,
233 vma
->vm_end
- vma
->vm_start
, vma
->vm_page_prot
);
236 static struct snd_pcm_ops jz4740_pcm_ops
= {
237 .open
= jz4740_pcm_open
,
238 .close
= jz4740_pcm_close
,
239 .ioctl
= snd_pcm_lib_ioctl
,
240 .hw_params
= jz4740_pcm_hw_params
,
241 .hw_free
= jz4740_pcm_hw_free
,
242 .prepare
= jz4740_pcm_prepare
,
243 .trigger
= jz4740_pcm_trigger
,
244 .pointer
= jz4740_pcm_pointer
,
245 .mmap
= jz4740_pcm_mmap
,
248 static int jz4740_pcm_preallocate_dma_buffer(struct snd_pcm
*pcm
, int stream
)
250 struct snd_pcm_substream
*substream
= pcm
->streams
[stream
].substream
;
251 struct snd_dma_buffer
*buf
= &substream
->dma_buffer
;
252 size_t size
= jz4740_pcm_hardware
.buffer_bytes_max
;
254 buf
->dev
.type
= SNDRV_DMA_TYPE_DEV
;
255 buf
->dev
.dev
= pcm
->card
->dev
;
256 buf
->private_data
= NULL
;
258 buf
->area
= dma_alloc_noncoherent(pcm
->card
->dev
, size
,
259 &buf
->addr
, GFP_KERNEL
);
268 static void jz4740_pcm_free(struct snd_pcm
*pcm
)
270 struct snd_pcm_substream
*substream
;
271 struct snd_dma_buffer
*buf
;
274 for (stream
= 0; stream
< 2; stream
++) {
275 substream
= pcm
->streams
[stream
].substream
;
279 buf
= &substream
->dma_buffer
;
283 dma_free_noncoherent(pcm
->card
->dev
, buf
->bytes
,
284 buf
->area
, buf
->addr
);
289 static u64 jz4740_pcm_dmamask
= DMA_BIT_MASK(32);
291 int jz4740_pcm_new(struct snd_card
*card
, struct snd_soc_dai
*dai
,
296 if (!card
->dev
->dma_mask
)
297 card
->dev
->dma_mask
= &jz4740_pcm_dmamask
;
299 if (!card
->dev
->coherent_dma_mask
)
300 card
->dev
->coherent_dma_mask
= DMA_BIT_MASK(32);
302 if (dai
->playback
.channels_min
) {
303 ret
= jz4740_pcm_preallocate_dma_buffer(pcm
,
304 SNDRV_PCM_STREAM_PLAYBACK
);
309 if (dai
->capture
.channels_min
) {
310 ret
= jz4740_pcm_preallocate_dma_buffer(pcm
,
311 SNDRV_PCM_STREAM_CAPTURE
);
320 struct snd_soc_platform jz4740_soc_platform
= {
321 .name
= "jz4740-pcm",
322 .pcm_ops
= &jz4740_pcm_ops
,
323 .pcm_new
= jz4740_pcm_new
,
324 .pcm_free
= jz4740_pcm_free
,
326 EXPORT_SYMBOL_GPL(jz4740_soc_platform
);
328 static int __init
jz4740_soc_platform_init(void)
330 return snd_soc_register_platform(&jz4740_soc_platform
);
332 module_init(jz4740_soc_platform_init
);
334 static void __exit
jz4740_soc_platform_exit(void)
336 snd_soc_unregister_platform(&jz4740_soc_platform
);
338 module_exit(jz4740_soc_platform_exit
);
340 MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
341 MODULE_DESCRIPTION("Ingenic SoC JZ4740 PCM driver");
342 MODULE_LICENSE("GPL");