1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /********************************************************************* |
3 | * |
4 | * 2002/06/30 Karsten Wiese: |
5 | * removed kernel-version dependencies. |
6 | * ripped from linux kernel 2.4.18 (OSS Implementation) by me. |
7 | * In the OSS Version, this file is compiled to a separate MODULE, |
8 | * that is used by the pinnacle and the classic driver. |
9 | * since there is no classic driver for alsa yet (i dont have a classic |
10 | * & writing one blindfold is difficult) this file's object is statically |
11 | * linked into the pinnacle-driver-module for now. look for the string |
12 | * "uncomment this to make this a module again" |
13 | * to do guess what. |
14 | * |
15 | * the following is a copy of the 2.4.18 OSS FREE file-heading comment: |
16 | * |
17 | * msnd.c - Driver Base |
18 | * |
19 | * Turtle Beach MultiSound Sound Card Driver for Linux |
20 | * |
21 | * Copyright (C) 1998 Andrew Veliath |
22 | * |
23 | ********************************************************************/ |
24 | |
25 | #include <linux/kernel.h> |
26 | #include <linux/sched/signal.h> |
27 | #include <linux/types.h> |
28 | #include <linux/interrupt.h> |
29 | #include <linux/io.h> |
30 | #include <linux/fs.h> |
31 | #include <linux/delay.h> |
32 | #include <linux/module.h> |
33 | |
34 | #include <sound/core.h> |
35 | #include <sound/initval.h> |
36 | #include <sound/pcm.h> |
37 | #include <sound/pcm_params.h> |
38 | |
39 | #include "msnd.h" |
40 | |
41 | #define LOGNAME "msnd" |
42 | |
43 | |
44 | void snd_msnd_init_queue(void __iomem *base, int start, int size) |
45 | { |
46 | writew(PCTODSP_BASED(start), addr: base + JQS_wStart); |
47 | writew(PCTODSP_OFFSET(size) - 1, addr: base + JQS_wSize); |
48 | writew(val: 0, addr: base + JQS_wHead); |
49 | writew(val: 0, addr: base + JQS_wTail); |
50 | } |
51 | EXPORT_SYMBOL(snd_msnd_init_queue); |
52 | |
53 | static int snd_msnd_wait_TXDE(struct snd_msnd *dev) |
54 | { |
55 | unsigned int io = dev->io; |
56 | int timeout = 1000; |
57 | |
58 | while (timeout-- > 0) |
59 | if (inb(port: io + HP_ISR) & HPISR_TXDE) |
60 | return 0; |
61 | |
62 | return -EIO; |
63 | } |
64 | |
65 | static int snd_msnd_wait_HC0(struct snd_msnd *dev) |
66 | { |
67 | unsigned int io = dev->io; |
68 | int timeout = 1000; |
69 | |
70 | while (timeout-- > 0) |
71 | if (!(inb(port: io + HP_CVR) & HPCVR_HC)) |
72 | return 0; |
73 | |
74 | return -EIO; |
75 | } |
76 | |
77 | int snd_msnd_send_dsp_cmd(struct snd_msnd *dev, u8 cmd) |
78 | { |
79 | unsigned long flags; |
80 | |
81 | spin_lock_irqsave(&dev->lock, flags); |
82 | if (snd_msnd_wait_HC0(dev) == 0) { |
83 | outb(value: cmd, port: dev->io + HP_CVR); |
84 | spin_unlock_irqrestore(lock: &dev->lock, flags); |
85 | return 0; |
86 | } |
87 | spin_unlock_irqrestore(lock: &dev->lock, flags); |
88 | |
89 | snd_printd(KERN_ERR LOGNAME ": Send DSP command timeout\n" ); |
90 | |
91 | return -EIO; |
92 | } |
93 | EXPORT_SYMBOL(snd_msnd_send_dsp_cmd); |
94 | |
95 | int snd_msnd_send_word(struct snd_msnd *dev, unsigned char high, |
96 | unsigned char mid, unsigned char low) |
97 | { |
98 | unsigned int io = dev->io; |
99 | |
100 | if (snd_msnd_wait_TXDE(dev) == 0) { |
101 | outb(value: high, port: io + HP_TXH); |
102 | outb(value: mid, port: io + HP_TXM); |
103 | outb(value: low, port: io + HP_TXL); |
104 | return 0; |
105 | } |
106 | |
107 | snd_printd(KERN_ERR LOGNAME ": Send host word timeout\n" ); |
108 | |
109 | return -EIO; |
110 | } |
111 | EXPORT_SYMBOL(snd_msnd_send_word); |
112 | |
113 | int snd_msnd_upload_host(struct snd_msnd *dev, const u8 *bin, int len) |
114 | { |
115 | int i; |
116 | |
117 | if (len % 3 != 0) { |
118 | snd_printk(KERN_ERR LOGNAME |
119 | ": Upload host data not multiple of 3!\n" ); |
120 | return -EINVAL; |
121 | } |
122 | |
123 | for (i = 0; i < len; i += 3) |
124 | if (snd_msnd_send_word(dev, bin[i], bin[i + 1], bin[i + 2])) |
125 | return -EIO; |
126 | |
127 | inb(port: dev->io + HP_RXL); |
128 | inb(port: dev->io + HP_CVR); |
129 | |
130 | return 0; |
131 | } |
132 | EXPORT_SYMBOL(snd_msnd_upload_host); |
133 | |
134 | int snd_msnd_enable_irq(struct snd_msnd *dev) |
135 | { |
136 | unsigned long flags; |
137 | |
138 | if (dev->irq_ref++) |
139 | return 0; |
140 | |
141 | snd_printdd(LOGNAME ": Enabling IRQ\n" ); |
142 | |
143 | spin_lock_irqsave(&dev->lock, flags); |
144 | if (snd_msnd_wait_TXDE(dev) == 0) { |
145 | outb(inb(port: dev->io + HP_ICR) | HPICR_TREQ, port: dev->io + HP_ICR); |
146 | if (dev->type == msndClassic) |
147 | outb(value: dev->irqid, port: dev->io + HP_IRQM); |
148 | |
149 | outb(inb(port: dev->io + HP_ICR) & ~HPICR_TREQ, port: dev->io + HP_ICR); |
150 | outb(inb(port: dev->io + HP_ICR) | HPICR_RREQ, port: dev->io + HP_ICR); |
151 | enable_irq(irq: dev->irq); |
152 | snd_msnd_init_queue(dev->DSPQ, dev->dspq_data_buff, |
153 | dev->dspq_buff_size); |
154 | spin_unlock_irqrestore(lock: &dev->lock, flags); |
155 | return 0; |
156 | } |
157 | spin_unlock_irqrestore(lock: &dev->lock, flags); |
158 | |
159 | snd_printd(KERN_ERR LOGNAME ": Enable IRQ failed\n" ); |
160 | |
161 | return -EIO; |
162 | } |
163 | EXPORT_SYMBOL(snd_msnd_enable_irq); |
164 | |
165 | int snd_msnd_disable_irq(struct snd_msnd *dev) |
166 | { |
167 | unsigned long flags; |
168 | |
169 | if (--dev->irq_ref > 0) |
170 | return 0; |
171 | |
172 | if (dev->irq_ref < 0) |
173 | snd_printd(KERN_WARNING LOGNAME ": IRQ ref count is %d\n" , |
174 | dev->irq_ref); |
175 | |
176 | snd_printdd(LOGNAME ": Disabling IRQ\n" ); |
177 | |
178 | spin_lock_irqsave(&dev->lock, flags); |
179 | if (snd_msnd_wait_TXDE(dev) == 0) { |
180 | outb(inb(port: dev->io + HP_ICR) & ~HPICR_RREQ, port: dev->io + HP_ICR); |
181 | if (dev->type == msndClassic) |
182 | outb(HPIRQ_NONE, port: dev->io + HP_IRQM); |
183 | disable_irq(irq: dev->irq); |
184 | spin_unlock_irqrestore(lock: &dev->lock, flags); |
185 | return 0; |
186 | } |
187 | spin_unlock_irqrestore(lock: &dev->lock, flags); |
188 | |
189 | snd_printd(KERN_ERR LOGNAME ": Disable IRQ failed\n" ); |
190 | |
191 | return -EIO; |
192 | } |
193 | EXPORT_SYMBOL(snd_msnd_disable_irq); |
194 | |
195 | static inline long get_play_delay_jiffies(struct snd_msnd *chip, long size) |
196 | { |
197 | long tmp = (size * HZ * chip->play_sample_size) / 8; |
198 | return tmp / (chip->play_sample_rate * chip->play_channels); |
199 | } |
200 | |
201 | static void snd_msnd_dsp_write_flush(struct snd_msnd *chip) |
202 | { |
203 | if (!(chip->mode & FMODE_WRITE) || !test_bit(F_WRITING, &chip->flags)) |
204 | return; |
205 | set_bit(F_WRITEFLUSH, addr: &chip->flags); |
206 | /* interruptible_sleep_on_timeout( |
207 | &chip->writeflush, |
208 | get_play_delay_jiffies(&chip, chip->DAPF.len));*/ |
209 | clear_bit(F_WRITEFLUSH, addr: &chip->flags); |
210 | if (!signal_pending(current)) |
211 | schedule_timeout_interruptible( |
212 | timeout: get_play_delay_jiffies(chip, size: chip->play_period_bytes)); |
213 | clear_bit(F_WRITING, addr: &chip->flags); |
214 | } |
215 | |
216 | void snd_msnd_dsp_halt(struct snd_msnd *chip, struct file *file) |
217 | { |
218 | if ((file ? file->f_mode : chip->mode) & FMODE_READ) { |
219 | clear_bit(F_READING, addr: &chip->flags); |
220 | snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_STOP); |
221 | snd_msnd_disable_irq(chip); |
222 | if (file) { |
223 | snd_printd(KERN_INFO LOGNAME |
224 | ": Stopping read for %p\n" , file); |
225 | chip->mode &= ~FMODE_READ; |
226 | } |
227 | clear_bit(F_AUDIO_READ_INUSE, addr: &chip->flags); |
228 | } |
229 | if ((file ? file->f_mode : chip->mode) & FMODE_WRITE) { |
230 | if (test_bit(F_WRITING, &chip->flags)) { |
231 | snd_msnd_dsp_write_flush(chip); |
232 | snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_STOP); |
233 | } |
234 | snd_msnd_disable_irq(chip); |
235 | if (file) { |
236 | snd_printd(KERN_INFO |
237 | LOGNAME ": Stopping write for %p\n" , file); |
238 | chip->mode &= ~FMODE_WRITE; |
239 | } |
240 | clear_bit(F_AUDIO_WRITE_INUSE, addr: &chip->flags); |
241 | } |
242 | } |
243 | EXPORT_SYMBOL(snd_msnd_dsp_halt); |
244 | |
245 | |
246 | int snd_msnd_DARQ(struct snd_msnd *chip, int bank) |
247 | { |
248 | int /*size, n,*/ timeout = 3; |
249 | u16 wTmp; |
250 | /* void *DAQD; */ |
251 | |
252 | /* Increment the tail and check for queue wrap */ |
253 | wTmp = readw(addr: chip->DARQ + JQS_wTail) + PCTODSP_OFFSET(DAQDS__size); |
254 | if (wTmp > readw(addr: chip->DARQ + JQS_wSize)) |
255 | wTmp = 0; |
256 | while (wTmp == readw(addr: chip->DARQ + JQS_wHead) && timeout--) |
257 | udelay(1); |
258 | |
259 | if (chip->capturePeriods == 2) { |
260 | void __iomem *pDAQ = chip->mappedbase + DARQ_DATA_BUFF + |
261 | bank * DAQDS__size + DAQDS_wStart; |
262 | unsigned short offset = 0x3000 + chip->capturePeriodBytes; |
263 | |
264 | if (readw(addr: pDAQ) != PCTODSP_BASED(0x3000)) |
265 | offset = 0x3000; |
266 | writew(PCTODSP_BASED(offset), addr: pDAQ); |
267 | } |
268 | |
269 | writew(val: wTmp, addr: chip->DARQ + JQS_wTail); |
270 | |
271 | #if 0 |
272 | /* Get our digital audio queue struct */ |
273 | DAQD = bank * DAQDS__size + chip->mappedbase + DARQ_DATA_BUFF; |
274 | |
275 | /* Get length of data */ |
276 | size = readw(DAQD + DAQDS_wSize); |
277 | |
278 | /* Read data from the head (unprotected bank 1 access okay |
279 | since this is only called inside an interrupt) */ |
280 | outb(HPBLKSEL_1, chip->io + HP_BLKS); |
281 | n = msnd_fifo_write(&chip->DARF, |
282 | (char *)(chip->base + bank * DAR_BUFF_SIZE), |
283 | size, 0); |
284 | if (n <= 0) { |
285 | outb(HPBLKSEL_0, chip->io + HP_BLKS); |
286 | return n; |
287 | } |
288 | outb(HPBLKSEL_0, chip->io + HP_BLKS); |
289 | #endif |
290 | |
291 | return 1; |
292 | } |
293 | EXPORT_SYMBOL(snd_msnd_DARQ); |
294 | |
295 | int snd_msnd_DAPQ(struct snd_msnd *chip, int start) |
296 | { |
297 | u16 DAPQ_tail; |
298 | int protect = start, nbanks = 0; |
299 | void __iomem *DAQD; |
300 | static int play_banks_submitted; |
301 | /* unsigned long flags; |
302 | spin_lock_irqsave(&chip->lock, flags); not necessary */ |
303 | |
304 | DAPQ_tail = readw(addr: chip->DAPQ + JQS_wTail); |
305 | while (DAPQ_tail != readw(addr: chip->DAPQ + JQS_wHead) || start) { |
306 | int bank_num = DAPQ_tail / PCTODSP_OFFSET(DAQDS__size); |
307 | |
308 | if (start) { |
309 | start = 0; |
310 | play_banks_submitted = 0; |
311 | } |
312 | |
313 | /* Get our digital audio queue struct */ |
314 | DAQD = bank_num * DAQDS__size + chip->mappedbase + |
315 | DAPQ_DATA_BUFF; |
316 | |
317 | /* Write size of this bank */ |
318 | writew(val: chip->play_period_bytes, addr: DAQD + DAQDS_wSize); |
319 | if (play_banks_submitted < 3) |
320 | ++play_banks_submitted; |
321 | else if (chip->playPeriods == 2) { |
322 | unsigned short offset = chip->play_period_bytes; |
323 | |
324 | if (readw(addr: DAQD + DAQDS_wStart) != PCTODSP_BASED(0x0)) |
325 | offset = 0; |
326 | |
327 | writew(PCTODSP_BASED(offset), addr: DAQD + DAQDS_wStart); |
328 | } |
329 | ++nbanks; |
330 | |
331 | /* Then advance the tail */ |
332 | /* |
333 | if (protect) |
334 | snd_printd(KERN_INFO "B %X %lX\n", |
335 | bank_num, xtime.tv_usec); |
336 | */ |
337 | |
338 | DAPQ_tail = (++bank_num % 3) * PCTODSP_OFFSET(DAQDS__size); |
339 | writew(val: DAPQ_tail, addr: chip->DAPQ + JQS_wTail); |
340 | /* Tell the DSP to play the bank */ |
341 | snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_START); |
342 | if (protect) |
343 | if (2 == bank_num) |
344 | break; |
345 | } |
346 | /* |
347 | if (protect) |
348 | snd_printd(KERN_INFO "%lX\n", xtime.tv_usec); |
349 | */ |
350 | /* spin_unlock_irqrestore(&chip->lock, flags); not necessary */ |
351 | return nbanks; |
352 | } |
353 | EXPORT_SYMBOL(snd_msnd_DAPQ); |
354 | |
355 | static void snd_msnd_play_reset_queue(struct snd_msnd *chip, |
356 | unsigned int pcm_periods, |
357 | unsigned int pcm_count) |
358 | { |
359 | int n; |
360 | void __iomem *pDAQ = chip->mappedbase + DAPQ_DATA_BUFF; |
361 | |
362 | chip->last_playbank = -1; |
363 | chip->playLimit = pcm_count * (pcm_periods - 1); |
364 | chip->playPeriods = pcm_periods; |
365 | writew(PCTODSP_OFFSET(0 * DAQDS__size), addr: chip->DAPQ + JQS_wHead); |
366 | writew(PCTODSP_OFFSET(0 * DAQDS__size), addr: chip->DAPQ + JQS_wTail); |
367 | |
368 | chip->play_period_bytes = pcm_count; |
369 | |
370 | for (n = 0; n < pcm_periods; ++n, pDAQ += DAQDS__size) { |
371 | writew(PCTODSP_BASED((u32)(pcm_count * n)), |
372 | addr: pDAQ + DAQDS_wStart); |
373 | writew(val: 0, addr: pDAQ + DAQDS_wSize); |
374 | writew(val: 1, addr: pDAQ + DAQDS_wFormat); |
375 | writew(val: chip->play_sample_size, addr: pDAQ + DAQDS_wSampleSize); |
376 | writew(val: chip->play_channels, addr: pDAQ + DAQDS_wChannels); |
377 | writew(val: chip->play_sample_rate, addr: pDAQ + DAQDS_wSampleRate); |
378 | writew(HIMT_PLAY_DONE * 0x100 + n, addr: pDAQ + DAQDS_wIntMsg); |
379 | writew(val: n, addr: pDAQ + DAQDS_wFlags); |
380 | } |
381 | } |
382 | |
383 | static void snd_msnd_capture_reset_queue(struct snd_msnd *chip, |
384 | unsigned int pcm_periods, |
385 | unsigned int pcm_count) |
386 | { |
387 | int n; |
388 | void __iomem *pDAQ; |
389 | /* unsigned long flags; */ |
390 | |
391 | /* snd_msnd_init_queue(chip->DARQ, DARQ_DATA_BUFF, DARQ_BUFF_SIZE); */ |
392 | |
393 | chip->last_recbank = 2; |
394 | chip->captureLimit = pcm_count * (pcm_periods - 1); |
395 | chip->capturePeriods = pcm_periods; |
396 | writew(PCTODSP_OFFSET(0 * DAQDS__size), addr: chip->DARQ + JQS_wHead); |
397 | writew(PCTODSP_OFFSET(chip->last_recbank * DAQDS__size), |
398 | addr: chip->DARQ + JQS_wTail); |
399 | |
400 | #if 0 /* Critical section: bank 1 access. this is how the OSS driver does it:*/ |
401 | spin_lock_irqsave(&chip->lock, flags); |
402 | outb(HPBLKSEL_1, chip->io + HP_BLKS); |
403 | memset_io(chip->mappedbase, 0, DAR_BUFF_SIZE * 3); |
404 | outb(HPBLKSEL_0, chip->io + HP_BLKS); |
405 | spin_unlock_irqrestore(&chip->lock, flags); |
406 | #endif |
407 | |
408 | chip->capturePeriodBytes = pcm_count; |
409 | snd_printdd("snd_msnd_capture_reset_queue() %i\n" , pcm_count); |
410 | |
411 | pDAQ = chip->mappedbase + DARQ_DATA_BUFF; |
412 | |
413 | for (n = 0; n < pcm_periods; ++n, pDAQ += DAQDS__size) { |
414 | u32 tmp = pcm_count * n; |
415 | |
416 | writew(PCTODSP_BASED(tmp + 0x3000), addr: pDAQ + DAQDS_wStart); |
417 | writew(val: pcm_count, addr: pDAQ + DAQDS_wSize); |
418 | writew(val: 1, addr: pDAQ + DAQDS_wFormat); |
419 | writew(val: chip->capture_sample_size, addr: pDAQ + DAQDS_wSampleSize); |
420 | writew(val: chip->capture_channels, addr: pDAQ + DAQDS_wChannels); |
421 | writew(val: chip->capture_sample_rate, addr: pDAQ + DAQDS_wSampleRate); |
422 | writew(HIMT_RECORD_DONE * 0x100 + n, addr: pDAQ + DAQDS_wIntMsg); |
423 | writew(val: n, addr: pDAQ + DAQDS_wFlags); |
424 | } |
425 | } |
426 | |
427 | static const struct snd_pcm_hardware snd_msnd_playback = { |
428 | .info = SNDRV_PCM_INFO_MMAP_IOMEM | |
429 | SNDRV_PCM_INFO_INTERLEAVED | |
430 | SNDRV_PCM_INFO_MMAP_VALID | |
431 | SNDRV_PCM_INFO_BATCH, |
432 | .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, |
433 | .rates = SNDRV_PCM_RATE_8000_48000, |
434 | .rate_min = 8000, |
435 | .rate_max = 48000, |
436 | .channels_min = 1, |
437 | .channels_max = 2, |
438 | .buffer_bytes_max = 0x3000, |
439 | .period_bytes_min = 0x40, |
440 | .period_bytes_max = 0x1800, |
441 | .periods_min = 2, |
442 | .periods_max = 3, |
443 | .fifo_size = 0, |
444 | }; |
445 | |
446 | static const struct snd_pcm_hardware snd_msnd_capture = { |
447 | .info = SNDRV_PCM_INFO_MMAP_IOMEM | |
448 | SNDRV_PCM_INFO_INTERLEAVED | |
449 | SNDRV_PCM_INFO_MMAP_VALID | |
450 | SNDRV_PCM_INFO_BATCH, |
451 | .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, |
452 | .rates = SNDRV_PCM_RATE_8000_48000, |
453 | .rate_min = 8000, |
454 | .rate_max = 48000, |
455 | .channels_min = 1, |
456 | .channels_max = 2, |
457 | .buffer_bytes_max = 0x3000, |
458 | .period_bytes_min = 0x40, |
459 | .period_bytes_max = 0x1800, |
460 | .periods_min = 2, |
461 | .periods_max = 3, |
462 | .fifo_size = 0, |
463 | }; |
464 | |
465 | |
466 | static int snd_msnd_playback_open(struct snd_pcm_substream *substream) |
467 | { |
468 | struct snd_pcm_runtime *runtime = substream->runtime; |
469 | struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
470 | |
471 | set_bit(F_AUDIO_WRITE_INUSE, addr: &chip->flags); |
472 | clear_bit(F_WRITING, addr: &chip->flags); |
473 | snd_msnd_enable_irq(chip); |
474 | |
475 | runtime->dma_area = (__force void *)chip->mappedbase; |
476 | runtime->dma_addr = chip->base; |
477 | runtime->dma_bytes = 0x3000; |
478 | |
479 | chip->playback_substream = substream; |
480 | runtime->hw = snd_msnd_playback; |
481 | return 0; |
482 | } |
483 | |
484 | static int snd_msnd_playback_close(struct snd_pcm_substream *substream) |
485 | { |
486 | struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
487 | |
488 | snd_msnd_disable_irq(chip); |
489 | clear_bit(F_AUDIO_WRITE_INUSE, addr: &chip->flags); |
490 | return 0; |
491 | } |
492 | |
493 | |
494 | static int snd_msnd_playback_hw_params(struct snd_pcm_substream *substream, |
495 | struct snd_pcm_hw_params *params) |
496 | { |
497 | int i; |
498 | struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
499 | void __iomem *pDAQ = chip->mappedbase + DAPQ_DATA_BUFF; |
500 | |
501 | chip->play_sample_size = snd_pcm_format_width(format: params_format(p: params)); |
502 | chip->play_channels = params_channels(p: params); |
503 | chip->play_sample_rate = params_rate(p: params); |
504 | |
505 | for (i = 0; i < 3; ++i, pDAQ += DAQDS__size) { |
506 | writew(val: chip->play_sample_size, addr: pDAQ + DAQDS_wSampleSize); |
507 | writew(val: chip->play_channels, addr: pDAQ + DAQDS_wChannels); |
508 | writew(val: chip->play_sample_rate, addr: pDAQ + DAQDS_wSampleRate); |
509 | } |
510 | /* dont do this here: |
511 | * snd_msnd_calibrate_adc(chip->play_sample_rate); |
512 | */ |
513 | |
514 | return 0; |
515 | } |
516 | |
517 | static int snd_msnd_playback_prepare(struct snd_pcm_substream *substream) |
518 | { |
519 | struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
520 | unsigned int pcm_size = snd_pcm_lib_buffer_bytes(substream); |
521 | unsigned int pcm_count = snd_pcm_lib_period_bytes(substream); |
522 | unsigned int pcm_periods = pcm_size / pcm_count; |
523 | |
524 | snd_msnd_play_reset_queue(chip, pcm_periods, pcm_count); |
525 | chip->playDMAPos = 0; |
526 | return 0; |
527 | } |
528 | |
529 | static int snd_msnd_playback_trigger(struct snd_pcm_substream *substream, |
530 | int cmd) |
531 | { |
532 | struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
533 | int result = 0; |
534 | |
535 | if (cmd == SNDRV_PCM_TRIGGER_START) { |
536 | snd_printdd("snd_msnd_playback_trigger(START)\n" ); |
537 | chip->banksPlayed = 0; |
538 | set_bit(F_WRITING, addr: &chip->flags); |
539 | snd_msnd_DAPQ(chip, 1); |
540 | } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { |
541 | snd_printdd("snd_msnd_playback_trigger(STop)\n" ); |
542 | /* interrupt diagnostic, comment this out later */ |
543 | clear_bit(F_WRITING, addr: &chip->flags); |
544 | snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_STOP); |
545 | } else { |
546 | snd_printd(KERN_ERR "snd_msnd_playback_trigger(?????)\n" ); |
547 | result = -EINVAL; |
548 | } |
549 | |
550 | snd_printdd("snd_msnd_playback_trigger() ENDE\n" ); |
551 | return result; |
552 | } |
553 | |
554 | static snd_pcm_uframes_t |
555 | snd_msnd_playback_pointer(struct snd_pcm_substream *substream) |
556 | { |
557 | struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
558 | |
559 | return bytes_to_frames(runtime: substream->runtime, size: chip->playDMAPos); |
560 | } |
561 | |
562 | |
563 | static const struct snd_pcm_ops snd_msnd_playback_ops = { |
564 | .open = snd_msnd_playback_open, |
565 | .close = snd_msnd_playback_close, |
566 | .hw_params = snd_msnd_playback_hw_params, |
567 | .prepare = snd_msnd_playback_prepare, |
568 | .trigger = snd_msnd_playback_trigger, |
569 | .pointer = snd_msnd_playback_pointer, |
570 | .mmap = snd_pcm_lib_mmap_iomem, |
571 | }; |
572 | |
573 | static int snd_msnd_capture_open(struct snd_pcm_substream *substream) |
574 | { |
575 | struct snd_pcm_runtime *runtime = substream->runtime; |
576 | struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
577 | |
578 | set_bit(F_AUDIO_READ_INUSE, addr: &chip->flags); |
579 | snd_msnd_enable_irq(chip); |
580 | runtime->dma_area = (__force void *)chip->mappedbase + 0x3000; |
581 | runtime->dma_addr = chip->base + 0x3000; |
582 | runtime->dma_bytes = 0x3000; |
583 | memset(runtime->dma_area, 0, runtime->dma_bytes); |
584 | chip->capture_substream = substream; |
585 | runtime->hw = snd_msnd_capture; |
586 | return 0; |
587 | } |
588 | |
589 | static int snd_msnd_capture_close(struct snd_pcm_substream *substream) |
590 | { |
591 | struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
592 | |
593 | snd_msnd_disable_irq(chip); |
594 | clear_bit(F_AUDIO_READ_INUSE, addr: &chip->flags); |
595 | return 0; |
596 | } |
597 | |
598 | static int snd_msnd_capture_prepare(struct snd_pcm_substream *substream) |
599 | { |
600 | struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
601 | unsigned int pcm_size = snd_pcm_lib_buffer_bytes(substream); |
602 | unsigned int pcm_count = snd_pcm_lib_period_bytes(substream); |
603 | unsigned int pcm_periods = pcm_size / pcm_count; |
604 | |
605 | snd_msnd_capture_reset_queue(chip, pcm_periods, pcm_count); |
606 | chip->captureDMAPos = 0; |
607 | return 0; |
608 | } |
609 | |
610 | static int snd_msnd_capture_trigger(struct snd_pcm_substream *substream, |
611 | int cmd) |
612 | { |
613 | struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
614 | |
615 | if (cmd == SNDRV_PCM_TRIGGER_START) { |
616 | chip->last_recbank = -1; |
617 | set_bit(F_READING, addr: &chip->flags); |
618 | if (snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_START) == 0) |
619 | return 0; |
620 | |
621 | clear_bit(F_READING, addr: &chip->flags); |
622 | } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { |
623 | clear_bit(F_READING, addr: &chip->flags); |
624 | snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_STOP); |
625 | return 0; |
626 | } |
627 | return -EINVAL; |
628 | } |
629 | |
630 | |
631 | static snd_pcm_uframes_t |
632 | snd_msnd_capture_pointer(struct snd_pcm_substream *substream) |
633 | { |
634 | struct snd_pcm_runtime *runtime = substream->runtime; |
635 | struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
636 | |
637 | return bytes_to_frames(runtime, size: chip->captureDMAPos); |
638 | } |
639 | |
640 | |
641 | static int snd_msnd_capture_hw_params(struct snd_pcm_substream *substream, |
642 | struct snd_pcm_hw_params *params) |
643 | { |
644 | int i; |
645 | struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
646 | void __iomem *pDAQ = chip->mappedbase + DARQ_DATA_BUFF; |
647 | |
648 | chip->capture_sample_size = snd_pcm_format_width(format: params_format(p: params)); |
649 | chip->capture_channels = params_channels(p: params); |
650 | chip->capture_sample_rate = params_rate(p: params); |
651 | |
652 | for (i = 0; i < 3; ++i, pDAQ += DAQDS__size) { |
653 | writew(val: chip->capture_sample_size, addr: pDAQ + DAQDS_wSampleSize); |
654 | writew(val: chip->capture_channels, addr: pDAQ + DAQDS_wChannels); |
655 | writew(val: chip->capture_sample_rate, addr: pDAQ + DAQDS_wSampleRate); |
656 | } |
657 | return 0; |
658 | } |
659 | |
660 | |
661 | static const struct snd_pcm_ops snd_msnd_capture_ops = { |
662 | .open = snd_msnd_capture_open, |
663 | .close = snd_msnd_capture_close, |
664 | .hw_params = snd_msnd_capture_hw_params, |
665 | .prepare = snd_msnd_capture_prepare, |
666 | .trigger = snd_msnd_capture_trigger, |
667 | .pointer = snd_msnd_capture_pointer, |
668 | .mmap = snd_pcm_lib_mmap_iomem, |
669 | }; |
670 | |
671 | |
672 | int snd_msnd_pcm(struct snd_card *card, int device) |
673 | { |
674 | struct snd_msnd *chip = card->private_data; |
675 | struct snd_pcm *pcm; |
676 | int err; |
677 | |
678 | err = snd_pcm_new(card, id: "MSNDPINNACLE" , device, playback_count: 1, capture_count: 1, rpcm: &pcm); |
679 | if (err < 0) |
680 | return err; |
681 | |
682 | snd_pcm_set_ops(pcm, direction: SNDRV_PCM_STREAM_PLAYBACK, ops: &snd_msnd_playback_ops); |
683 | snd_pcm_set_ops(pcm, direction: SNDRV_PCM_STREAM_CAPTURE, ops: &snd_msnd_capture_ops); |
684 | |
685 | pcm->private_data = chip; |
686 | strcpy(p: pcm->name, q: "Hurricane" ); |
687 | |
688 | return 0; |
689 | } |
690 | EXPORT_SYMBOL(snd_msnd_pcm); |
691 | |
692 | MODULE_DESCRIPTION("Common routines for Turtle Beach Multisound drivers" ); |
693 | MODULE_LICENSE("GPL" ); |
694 | |
695 | |