diff options
Diffstat (limited to 'sound/pci/cs5535audio/cs5535audio_pcm.c')
-rw-r--r-- | sound/pci/cs5535audio/cs5535audio_pcm.c | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/sound/pci/cs5535audio/cs5535audio_pcm.c b/sound/pci/cs5535audio/cs5535audio_pcm.c new file mode 100644 index 000000000000..5802ed9d57be --- /dev/null +++ b/sound/pci/cs5535audio/cs5535audio_pcm.c | |||
@@ -0,0 +1,430 @@ | |||
1 | /* | ||
2 | * Driver for audio on multifunction CS5535 companion device | ||
3 | * Copyright (C) Jaya Kumar | ||
4 | * | ||
5 | * Based on Jaroslav Kysela and Takashi Iwai's examples. | ||
6 | * This work was sponsored by CIS(M) Sdn Bhd. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU General Public License for more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License | ||
19 | * along with this program; if not, write to the Free Software | ||
20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
21 | * | ||
22 | * todo: add be fmt support, spdif, pm | ||
23 | */ | ||
24 | |||
25 | #include <linux/init.h> | ||
26 | #include <linux/slab.h> | ||
27 | #include <linux/pci.h> | ||
28 | #include <sound/driver.h> | ||
29 | #include <sound/core.h> | ||
30 | #include <sound/control.h> | ||
31 | #include <sound/initval.h> | ||
32 | #include <sound/asoundef.h> | ||
33 | #include <sound/pcm.h> | ||
34 | #include <sound/pcm_params.h> | ||
35 | #include <sound/ac97_codec.h> | ||
36 | #include "cs5535audio.h" | ||
37 | |||
38 | static snd_pcm_hardware_t snd_cs5535audio_playback = | ||
39 | { | ||
40 | .info = ( | ||
41 | SNDRV_PCM_INFO_MMAP | | ||
42 | SNDRV_PCM_INFO_INTERLEAVED | | ||
43 | SNDRV_PCM_INFO_BLOCK_TRANSFER | | ||
44 | SNDRV_PCM_INFO_MMAP_VALID | | ||
45 | SNDRV_PCM_INFO_PAUSE | | ||
46 | SNDRV_PCM_INFO_SYNC_START | ||
47 | ), | ||
48 | .formats = ( | ||
49 | SNDRV_PCM_FMTBIT_S16_LE | ||
50 | ), | ||
51 | .rates = ( | ||
52 | SNDRV_PCM_RATE_CONTINUOUS | | ||
53 | SNDRV_PCM_RATE_8000_48000 | ||
54 | ), | ||
55 | .rate_min = 4000, | ||
56 | .rate_max = 48000, | ||
57 | .channels_min = 2, | ||
58 | .channels_max = 2, | ||
59 | .buffer_bytes_max = (128*1024), | ||
60 | .period_bytes_min = 64, | ||
61 | .period_bytes_max = (64*1024 - 16), | ||
62 | .periods_min = 1, | ||
63 | .periods_max = CS5535AUDIO_MAX_DESCRIPTORS, | ||
64 | .fifo_size = 0, | ||
65 | }; | ||
66 | |||
67 | static snd_pcm_hardware_t snd_cs5535audio_capture = | ||
68 | { | ||
69 | .info = ( | ||
70 | SNDRV_PCM_INFO_MMAP | | ||
71 | SNDRV_PCM_INFO_INTERLEAVED | | ||
72 | SNDRV_PCM_INFO_BLOCK_TRANSFER | | ||
73 | SNDRV_PCM_INFO_MMAP_VALID | | ||
74 | SNDRV_PCM_INFO_SYNC_START | ||
75 | ), | ||
76 | .formats = ( | ||
77 | SNDRV_PCM_FMTBIT_S16_LE | ||
78 | ), | ||
79 | .rates = ( | ||
80 | SNDRV_PCM_RATE_CONTINUOUS | | ||
81 | SNDRV_PCM_RATE_8000_48000 | ||
82 | ), | ||
83 | .rate_min = 4000, | ||
84 | .rate_max = 48000, | ||
85 | .channels_min = 2, | ||
86 | .channels_max = 2, | ||
87 | .buffer_bytes_max = (128*1024), | ||
88 | .period_bytes_min = 64, | ||
89 | .period_bytes_max = (64*1024 - 16), | ||
90 | .periods_min = 1, | ||
91 | .periods_max = CS5535AUDIO_MAX_DESCRIPTORS, | ||
92 | .fifo_size = 0, | ||
93 | }; | ||
94 | |||
95 | static int snd_cs5535audio_playback_open(snd_pcm_substream_t *substream) | ||
96 | { | ||
97 | int err; | ||
98 | cs5535audio_t *cs5535au = snd_pcm_substream_chip(substream); | ||
99 | snd_pcm_runtime_t *runtime = substream->runtime; | ||
100 | |||
101 | runtime->hw = snd_cs5535audio_playback; | ||
102 | cs5535au->playback_substream = substream; | ||
103 | runtime->private_data = &(cs5535au->dmas[CS5535AUDIO_DMA_PLAYBACK]); | ||
104 | snd_pcm_set_sync(substream); | ||
105 | if ((err = snd_pcm_hw_constraint_integer(runtime, | ||
106 | SNDRV_PCM_HW_PARAM_PERIODS)) < 0) | ||
107 | return err; | ||
108 | |||
109 | return 0; | ||
110 | } | ||
111 | |||
112 | static int snd_cs5535audio_playback_close(snd_pcm_substream_t *substream) | ||
113 | { | ||
114 | return 0; | ||
115 | } | ||
116 | |||
117 | #define CS5535AUDIO_DESC_LIST_SIZE \ | ||
118 | PAGE_ALIGN(CS5535AUDIO_MAX_DESCRIPTORS * sizeof(cs5535audio_dma_desc_t)) | ||
119 | |||
120 | static int cs5535audio_build_dma_packets(cs5535audio_t *cs5535au, | ||
121 | cs5535audio_dma_t *dma, | ||
122 | snd_pcm_substream_t *substream, | ||
123 | unsigned int periods, | ||
124 | unsigned int period_bytes) | ||
125 | { | ||
126 | unsigned int i; | ||
127 | u32 addr, desc_addr, jmpprd_addr; | ||
128 | cs5535audio_dma_desc_t *lastdesc; | ||
129 | |||
130 | if (periods > CS5535AUDIO_MAX_DESCRIPTORS) | ||
131 | return -ENOMEM; | ||
132 | |||
133 | if (dma->desc_buf.area == NULL) { | ||
134 | if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, | ||
135 | snd_dma_pci_data(cs5535au->pci), | ||
136 | CS5535AUDIO_DESC_LIST_SIZE+1, | ||
137 | &dma->desc_buf) < 0) | ||
138 | return -ENOMEM; | ||
139 | dma->period_bytes = dma->periods = 0; | ||
140 | } | ||
141 | |||
142 | if (dma->periods == periods && dma->period_bytes == period_bytes) | ||
143 | return 0; | ||
144 | |||
145 | /* the u32 cast is okay because in snd*create we succesfully told | ||
146 | pci alloc that we're only 32 bit capable so the uppper will be 0 */ | ||
147 | addr = (u32) substream->runtime->dma_addr; | ||
148 | desc_addr = (u32) dma->desc_buf.addr; | ||
149 | for (i = 0; i < periods; i++) { | ||
150 | cs5535audio_dma_desc_t *desc = | ||
151 | &((cs5535audio_dma_desc_t *) dma->desc_buf.area)[i]; | ||
152 | desc->addr = cpu_to_le32(addr); | ||
153 | desc->size = period_bytes; | ||
154 | desc->ctlreserved = PRD_EOP; | ||
155 | desc_addr += sizeof(cs5535audio_dma_desc_t); | ||
156 | addr += period_bytes; | ||
157 | } | ||
158 | /* we reserved one dummy descriptor at the end to do the PRD jump */ | ||
159 | lastdesc = &((cs5535audio_dma_desc_t *) dma->desc_buf.area)[periods]; | ||
160 | lastdesc->addr = cpu_to_le32((u32) dma->desc_buf.addr); | ||
161 | lastdesc->size = 0; | ||
162 | lastdesc->ctlreserved = PRD_JMP; | ||
163 | jmpprd_addr = cpu_to_le32(lastdesc->addr + | ||
164 | (sizeof(cs5535audio_dma_desc_t)*periods)); | ||
165 | |||
166 | dma->period_bytes = period_bytes; | ||
167 | dma->periods = periods; | ||
168 | spin_lock_irq(&cs5535au->reg_lock); | ||
169 | dma->ops->disable_dma(cs5535au); | ||
170 | dma->ops->setup_prd(cs5535au, jmpprd_addr); | ||
171 | spin_unlock_irq(&cs5535au->reg_lock); | ||
172 | return 0; | ||
173 | } | ||
174 | |||
175 | static void cs5535audio_playback_enable_dma(cs5535audio_t *cs5535au) | ||
176 | { | ||
177 | cs_writeb(cs5535au, ACC_BM0_CMD, BM_CTL_EN); | ||
178 | } | ||
179 | |||
180 | static void cs5535audio_playback_disable_dma(cs5535audio_t *cs5535au) | ||
181 | { | ||
182 | cs_writeb(cs5535au, ACC_BM0_CMD, 0); | ||
183 | } | ||
184 | |||
185 | static void cs5535audio_playback_pause_dma(cs5535audio_t *cs5535au) | ||
186 | { | ||
187 | cs_writeb(cs5535au, ACC_BM0_CMD, BM_CTL_PAUSE); | ||
188 | } | ||
189 | |||
190 | static void cs5535audio_playback_setup_prd(cs5535audio_t *cs5535au, | ||
191 | u32 prd_addr) | ||
192 | { | ||
193 | cs_writel(cs5535au, ACC_BM0_PRD, prd_addr); | ||
194 | } | ||
195 | |||
196 | static u32 cs5535audio_playback_read_dma_pntr(cs5535audio_t *cs5535au) | ||
197 | { | ||
198 | return cs_readl(cs5535au, ACC_BM0_PNTR); | ||
199 | } | ||
200 | |||
201 | static void cs5535audio_capture_enable_dma(cs5535audio_t *cs5535au) | ||
202 | { | ||
203 | cs_writeb(cs5535au, ACC_BM1_CMD, BM_CTL_EN); | ||
204 | } | ||
205 | |||
206 | static void cs5535audio_capture_disable_dma(cs5535audio_t *cs5535au) | ||
207 | { | ||
208 | cs_writeb(cs5535au, ACC_BM1_CMD, 0); | ||
209 | } | ||
210 | |||
211 | static void cs5535audio_capture_pause_dma(cs5535audio_t *cs5535au) | ||
212 | { | ||
213 | cs_writeb(cs5535au, ACC_BM1_CMD, BM_CTL_PAUSE); | ||
214 | } | ||
215 | |||
216 | static void cs5535audio_capture_setup_prd(cs5535audio_t *cs5535au, | ||
217 | u32 prd_addr) | ||
218 | { | ||
219 | cs_writel(cs5535au, ACC_BM1_PRD, prd_addr); | ||
220 | } | ||
221 | |||
222 | static u32 cs5535audio_capture_read_dma_pntr(cs5535audio_t *cs5535au) | ||
223 | { | ||
224 | return cs_readl(cs5535au, ACC_BM1_PNTR); | ||
225 | } | ||
226 | |||
227 | static void cs5535audio_clear_dma_packets(cs5535audio_t *cs5535au, | ||
228 | cs5535audio_dma_t *dma, | ||
229 | snd_pcm_substream_t *substream) | ||
230 | { | ||
231 | snd_dma_free_pages(&dma->desc_buf); | ||
232 | dma->desc_buf.area = NULL; | ||
233 | } | ||
234 | |||
235 | static int snd_cs5535audio_hw_params(snd_pcm_substream_t *substream, | ||
236 | snd_pcm_hw_params_t *hw_params) | ||
237 | { | ||
238 | cs5535audio_t *cs5535au = snd_pcm_substream_chip(substream); | ||
239 | cs5535audio_dma_t *dma = substream->runtime->private_data; | ||
240 | int err; | ||
241 | |||
242 | err = snd_pcm_lib_malloc_pages(substream, | ||
243 | params_buffer_bytes(hw_params)); | ||
244 | if (err < 0) | ||
245 | return err; | ||
246 | dma->buf_addr = substream->runtime->dma_addr; | ||
247 | dma->buf_bytes = params_buffer_bytes(hw_params); | ||
248 | |||
249 | err = cs5535audio_build_dma_packets(cs5535au, dma, substream, | ||
250 | params_periods(hw_params), | ||
251 | params_period_bytes(hw_params)); | ||
252 | return err; | ||
253 | } | ||
254 | |||
255 | static int snd_cs5535audio_hw_free(snd_pcm_substream_t *substream) | ||
256 | { | ||
257 | cs5535audio_t *cs5535au = snd_pcm_substream_chip(substream); | ||
258 | cs5535audio_dma_t *dma = substream->runtime->private_data; | ||
259 | |||
260 | cs5535audio_clear_dma_packets(cs5535au, dma, substream); | ||
261 | return snd_pcm_lib_free_pages(substream); | ||
262 | } | ||
263 | |||
264 | static int snd_cs5535audio_playback_prepare(snd_pcm_substream_t *substream) | ||
265 | { | ||
266 | cs5535audio_t *cs5535au = snd_pcm_substream_chip(substream); | ||
267 | return snd_ac97_set_rate(cs5535au->ac97, AC97_PCM_FRONT_DAC_RATE, | ||
268 | substream->runtime->rate); | ||
269 | } | ||
270 | |||
271 | static int snd_cs5535audio_trigger(snd_pcm_substream_t *substream, int cmd) | ||
272 | { | ||
273 | cs5535audio_t *cs5535au = snd_pcm_substream_chip(substream); | ||
274 | cs5535audio_dma_t *dma = substream->runtime->private_data; | ||
275 | |||
276 | switch (cmd) { | ||
277 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | ||
278 | spin_lock_irq(&cs5535au->reg_lock); | ||
279 | dma->ops->pause_dma(cs5535au); | ||
280 | spin_unlock_irq(&cs5535au->reg_lock); | ||
281 | break; | ||
282 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | ||
283 | spin_lock_irq(&cs5535au->reg_lock); | ||
284 | dma->ops->enable_dma(cs5535au); | ||
285 | spin_unlock_irq(&cs5535au->reg_lock); | ||
286 | break; | ||
287 | case SNDRV_PCM_TRIGGER_START: | ||
288 | spin_lock_irq(&cs5535au->reg_lock); | ||
289 | dma->ops->enable_dma(cs5535au); | ||
290 | spin_unlock_irq(&cs5535au->reg_lock); | ||
291 | break; | ||
292 | case SNDRV_PCM_TRIGGER_STOP: | ||
293 | spin_lock_irq(&cs5535au->reg_lock); | ||
294 | dma->ops->disable_dma(cs5535au); | ||
295 | spin_unlock_irq(&cs5535au->reg_lock); | ||
296 | break; | ||
297 | default: | ||
298 | snd_printk(KERN_ERR "unhandled trigger\n"); | ||
299 | return -EINVAL; | ||
300 | break; | ||
301 | } | ||
302 | return 0; | ||
303 | } | ||
304 | |||
305 | static snd_pcm_uframes_t snd_cs5535audio_pcm_pointer(snd_pcm_substream_t | ||
306 | *substream) | ||
307 | { | ||
308 | cs5535audio_t *cs5535au = snd_pcm_substream_chip(substream); | ||
309 | u32 curdma; | ||
310 | cs5535audio_dma_t *dma; | ||
311 | |||
312 | dma = substream->runtime->private_data; | ||
313 | curdma = dma->ops->read_dma_pntr(cs5535au); | ||
314 | if (curdma < dma->buf_addr) { | ||
315 | snd_printk(KERN_ERR "curdma=%x < %x bufaddr.\n", | ||
316 | curdma, dma->buf_addr); | ||
317 | return 0; | ||
318 | } | ||
319 | curdma -= dma->buf_addr; | ||
320 | if (curdma >= dma->buf_bytes) { | ||
321 | snd_printk(KERN_ERR "diff=%x >= %x buf_bytes.\n", | ||
322 | curdma, dma->buf_bytes); | ||
323 | return 0; | ||
324 | } | ||
325 | return bytes_to_frames(substream->runtime, curdma); | ||
326 | } | ||
327 | |||
328 | static int snd_cs5535audio_capture_open(snd_pcm_substream_t *substream) | ||
329 | { | ||
330 | int err; | ||
331 | cs5535audio_t *cs5535au = snd_pcm_substream_chip(substream); | ||
332 | snd_pcm_runtime_t *runtime = substream->runtime; | ||
333 | |||
334 | runtime->hw = snd_cs5535audio_capture; | ||
335 | cs5535au->capture_substream = substream; | ||
336 | runtime->private_data = &(cs5535au->dmas[CS5535AUDIO_DMA_CAPTURE]); | ||
337 | snd_pcm_set_sync(substream); | ||
338 | if ((err = snd_pcm_hw_constraint_integer(runtime, | ||
339 | SNDRV_PCM_HW_PARAM_PERIODS)) < 0) | ||
340 | return err; | ||
341 | return 0; | ||
342 | } | ||
343 | |||
344 | static int snd_cs5535audio_capture_close(snd_pcm_substream_t *substream) | ||
345 | { | ||
346 | return 0; | ||
347 | } | ||
348 | |||
349 | static int snd_cs5535audio_capture_prepare(snd_pcm_substream_t *substream) | ||
350 | { | ||
351 | cs5535audio_t *cs5535au = snd_pcm_substream_chip(substream); | ||
352 | return snd_ac97_set_rate(cs5535au->ac97, AC97_PCM_LR_ADC_RATE, | ||
353 | substream->runtime->rate); | ||
354 | } | ||
355 | |||
356 | static snd_pcm_ops_t snd_cs5535audio_playback_ops = { | ||
357 | .open = snd_cs5535audio_playback_open, | ||
358 | .close = snd_cs5535audio_playback_close, | ||
359 | .ioctl = snd_pcm_lib_ioctl, | ||
360 | .hw_params = snd_cs5535audio_hw_params, | ||
361 | .hw_free = snd_cs5535audio_hw_free, | ||
362 | .prepare = snd_cs5535audio_playback_prepare, | ||
363 | .trigger = snd_cs5535audio_trigger, | ||
364 | .pointer = snd_cs5535audio_pcm_pointer, | ||
365 | }; | ||
366 | |||
367 | static snd_pcm_ops_t snd_cs5535audio_capture_ops = { | ||
368 | .open = snd_cs5535audio_capture_open, | ||
369 | .close = snd_cs5535audio_capture_close, | ||
370 | .ioctl = snd_pcm_lib_ioctl, | ||
371 | .hw_params = snd_cs5535audio_hw_params, | ||
372 | .hw_free = snd_cs5535audio_hw_free, | ||
373 | .prepare = snd_cs5535audio_capture_prepare, | ||
374 | .trigger = snd_cs5535audio_trigger, | ||
375 | .pointer = snd_cs5535audio_pcm_pointer, | ||
376 | }; | ||
377 | |||
378 | static void snd_cs5535audio_pcm_free(snd_pcm_t *pcm) | ||
379 | { | ||
380 | snd_pcm_lib_preallocate_free_for_all(pcm); | ||
381 | } | ||
382 | |||
383 | static cs5535audio_dma_ops_t snd_cs5535audio_playback_dma_ops = { | ||
384 | .type = CS5535AUDIO_DMA_PLAYBACK, | ||
385 | .enable_dma = cs5535audio_playback_enable_dma, | ||
386 | .disable_dma = cs5535audio_playback_disable_dma, | ||
387 | .setup_prd = cs5535audio_playback_setup_prd, | ||
388 | .pause_dma = cs5535audio_playback_pause_dma, | ||
389 | .read_dma_pntr = cs5535audio_playback_read_dma_pntr, | ||
390 | }; | ||
391 | |||
392 | static cs5535audio_dma_ops_t snd_cs5535audio_capture_dma_ops = { | ||
393 | .type = CS5535AUDIO_DMA_CAPTURE, | ||
394 | .enable_dma = cs5535audio_capture_enable_dma, | ||
395 | .disable_dma = cs5535audio_capture_disable_dma, | ||
396 | .setup_prd = cs5535audio_capture_setup_prd, | ||
397 | .pause_dma = cs5535audio_capture_pause_dma, | ||
398 | .read_dma_pntr = cs5535audio_capture_read_dma_pntr, | ||
399 | }; | ||
400 | |||
401 | int __devinit snd_cs5535audio_pcm(cs5535audio_t *cs5535au) | ||
402 | { | ||
403 | snd_pcm_t *pcm; | ||
404 | int err; | ||
405 | |||
406 | err = snd_pcm_new(cs5535au->card, "CS5535 Audio", 0, 1, 1, &pcm); | ||
407 | if (err < 0) | ||
408 | return err; | ||
409 | |||
410 | cs5535au->dmas[CS5535AUDIO_DMA_PLAYBACK].ops = | ||
411 | &snd_cs5535audio_playback_dma_ops; | ||
412 | cs5535au->dmas[CS5535AUDIO_DMA_CAPTURE].ops = | ||
413 | &snd_cs5535audio_capture_dma_ops; | ||
414 | snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, | ||
415 | &snd_cs5535audio_playback_ops); | ||
416 | snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, | ||
417 | &snd_cs5535audio_capture_ops); | ||
418 | |||
419 | pcm->private_data = cs5535au; | ||
420 | pcm->private_free = snd_cs5535audio_pcm_free; | ||
421 | pcm->info_flags = 0; | ||
422 | strcpy(pcm->name, "CS5535 Audio"); | ||
423 | |||
424 | snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, | ||
425 | snd_dma_pci_data(cs5535au->pci), | ||
426 | 64*1024, 128*1024); | ||
427 | |||
428 | return 0; | ||
429 | } | ||
430 | |||