blob: 1855b14d90c3922989abfd43a0e53b57423174f6 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
2 * linux/sound/oss/dmasound/dmasound_q40.c
3 *
4 * Q40 DMA Sound Driver
5 *
6 * See linux/sound/oss/dmasound/dmasound_core.c for copyright and credits
7 * prior to 28/01/2001
8 *
9 * 28/01/2001 [0.1] Iain Sandoe
10 * - added versioning
11 * - put in and populated the hardware_afmts field.
12 * [0.2] - put in SNDCTL_DSP_GETCAPS value.
13 * [0.3] - put in default hard/soft settings.
14 */
15
16
17#include <linux/module.h>
18#include <linux/init.h>
19#include <linux/slab.h>
20#include <linux/soundcard.h>
21#include <linux/interrupt.h>
22
23#include <asm/uaccess.h>
24#include <asm/q40ints.h>
25#include <asm/q40_master.h>
26
27#include "dmasound.h"
28
29#define DMASOUND_Q40_REVISION 0
30#define DMASOUND_Q40_EDITION 3
31
32static int expand_bal; /* Balance factor for expanding (not volume!) */
33static int expand_data; /* Data for expanding */
34
35
36/*** Low level stuff *********************************************************/
37
38
Al Viro1ef64e62005-10-21 03:22:18 -040039static void *Q40Alloc(unsigned int size, gfp_t flags);
Linus Torvalds1da177e2005-04-16 15:20:36 -070040static void Q40Free(void *, unsigned int);
41static int Q40IrqInit(void);
42#ifdef MODULE
43static void Q40IrqCleanUp(void);
44#endif
45static void Q40Silence(void);
46static void Q40Init(void);
47static int Q40SetFormat(int format);
48static int Q40SetVolume(int volume);
49static void Q40PlayNextFrame(int index);
50static void Q40Play(void);
David Howells7d12e782006-10-05 14:55:46 +010051static irqreturn_t Q40StereoInterrupt(int irq, void *dummy);
52static irqreturn_t Q40MonoInterrupt(int irq, void *dummy);
Linus Torvalds1da177e2005-04-16 15:20:36 -070053static void Q40Interrupt(void);
54
55
56/*** Mid level stuff *********************************************************/
57
58
59
60/* userCount, frameUsed, frameLeft == byte counts */
Al Viro031eb4c2006-01-12 01:06:33 -080061static ssize_t q40_ct_law(const u_char __user *userPtr, size_t userCount,
Linus Torvalds1da177e2005-04-16 15:20:36 -070062 u_char frame[], ssize_t *frameUsed,
63 ssize_t frameLeft)
64{
65 char *table = dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8: dmasound_alaw2dma8;
66 ssize_t count, used;
67 u_char *p = (u_char *) &frame[*frameUsed];
68
69 used = count = min_t(size_t, userCount, frameLeft);
70 if (copy_from_user(p,userPtr,count))
71 return -EFAULT;
72 while (count > 0) {
73 *p = table[*p]+128;
74 p++;
75 count--;
76 }
77 *frameUsed += used ;
78 return used;
79}
80
81
Al Viro031eb4c2006-01-12 01:06:33 -080082static ssize_t q40_ct_s8(const u_char __user *userPtr, size_t userCount,
Linus Torvalds1da177e2005-04-16 15:20:36 -070083 u_char frame[], ssize_t *frameUsed,
84 ssize_t frameLeft)
85{
86 ssize_t count, used;
87 u_char *p = (u_char *) &frame[*frameUsed];
88
89 used = count = min_t(size_t, userCount, frameLeft);
90 if (copy_from_user(p,userPtr,count))
91 return -EFAULT;
92 while (count > 0) {
93 *p = *p + 128;
94 p++;
95 count--;
96 }
97 *frameUsed += used;
98 return used;
99}
100
Al Viro031eb4c2006-01-12 01:06:33 -0800101static ssize_t q40_ct_u8(const u_char __user *userPtr, size_t userCount,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700102 u_char frame[], ssize_t *frameUsed,
103 ssize_t frameLeft)
104{
105 ssize_t count, used;
106 u_char *p = (u_char *) &frame[*frameUsed];
107
108 used = count = min_t(size_t, userCount, frameLeft);
109 if (copy_from_user(p,userPtr,count))
110 return -EFAULT;
111 *frameUsed += used;
112 return used;
113}
114
115
116/* a bit too complicated to optimise right now ..*/
Al Viro031eb4c2006-01-12 01:06:33 -0800117static ssize_t q40_ctx_law(const u_char __user *userPtr, size_t userCount,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700118 u_char frame[], ssize_t *frameUsed,
119 ssize_t frameLeft)
120{
121 unsigned char *table = (unsigned char *)
122 (dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8: dmasound_alaw2dma8);
123 unsigned int data = expand_data;
124 u_char *p = (u_char *) &frame[*frameUsed];
125 int bal = expand_bal;
126 int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
127 int utotal, ftotal;
128
129 ftotal = frameLeft;
130 utotal = userCount;
131 while (frameLeft) {
132 u_char c;
133 if (bal < 0) {
134 if (userCount == 0)
135 break;
136 if (get_user(c, userPtr++))
137 return -EFAULT;
138 data = table[c];
139 data += 0x80;
140 userCount--;
141 bal += hSpeed;
142 }
143 *p++ = data;
144 frameLeft--;
145 bal -= sSpeed;
146 }
147 expand_bal = bal;
148 expand_data = data;
149 *frameUsed += (ftotal - frameLeft);
150 utotal -= userCount;
151 return utotal;
152}
153
154
Al Viro031eb4c2006-01-12 01:06:33 -0800155static ssize_t q40_ctx_s8(const u_char __user *userPtr, size_t userCount,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700156 u_char frame[], ssize_t *frameUsed,
157 ssize_t frameLeft)
158{
159 u_char *p = (u_char *) &frame[*frameUsed];
160 unsigned int data = expand_data;
161 int bal = expand_bal;
162 int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
163 int utotal, ftotal;
164
165
166 ftotal = frameLeft;
167 utotal = userCount;
168 while (frameLeft) {
169 u_char c;
170 if (bal < 0) {
171 if (userCount == 0)
172 break;
173 if (get_user(c, userPtr++))
174 return -EFAULT;
175 data = c ;
176 data += 0x80;
177 userCount--;
178 bal += hSpeed;
179 }
180 *p++ = data;
181 frameLeft--;
182 bal -= sSpeed;
183 }
184 expand_bal = bal;
185 expand_data = data;
186 *frameUsed += (ftotal - frameLeft);
187 utotal -= userCount;
188 return utotal;
189}
190
191
Al Viro031eb4c2006-01-12 01:06:33 -0800192static ssize_t q40_ctx_u8(const u_char __user *userPtr, size_t userCount,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700193 u_char frame[], ssize_t *frameUsed,
194 ssize_t frameLeft)
195{
196 u_char *p = (u_char *) &frame[*frameUsed];
197 unsigned int data = expand_data;
198 int bal = expand_bal;
199 int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
200 int utotal, ftotal;
201
202 ftotal = frameLeft;
203 utotal = userCount;
204 while (frameLeft) {
205 u_char c;
206 if (bal < 0) {
207 if (userCount == 0)
208 break;
209 if (get_user(c, userPtr++))
210 return -EFAULT;
211 data = c ;
212 userCount--;
213 bal += hSpeed;
214 }
215 *p++ = data;
216 frameLeft--;
217 bal -= sSpeed;
218 }
219 expand_bal = bal;
220 expand_data = data;
221 *frameUsed += (ftotal - frameLeft) ;
222 utotal -= userCount;
223 return utotal;
224}
225
226/* compressing versions */
Al Viro031eb4c2006-01-12 01:06:33 -0800227static ssize_t q40_ctc_law(const u_char __user *userPtr, size_t userCount,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700228 u_char frame[], ssize_t *frameUsed,
229 ssize_t frameLeft)
230{
231 unsigned char *table = (unsigned char *)
232 (dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8: dmasound_alaw2dma8);
233 unsigned int data = expand_data;
234 u_char *p = (u_char *) &frame[*frameUsed];
235 int bal = expand_bal;
236 int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
237 int utotal, ftotal;
238
239 ftotal = frameLeft;
240 utotal = userCount;
241 while (frameLeft) {
242 u_char c;
243 while(bal<0) {
244 if (userCount == 0)
245 goto lout;
246 if (!(bal<(-hSpeed))) {
247 if (get_user(c, userPtr))
248 return -EFAULT;
249 data = 0x80 + table[c];
250 }
251 userPtr++;
252 userCount--;
253 bal += hSpeed;
254 }
255 *p++ = data;
256 frameLeft--;
257 bal -= sSpeed;
258 }
259 lout:
260 expand_bal = bal;
261 expand_data = data;
262 *frameUsed += (ftotal - frameLeft);
263 utotal -= userCount;
264 return utotal;
265}
266
267
Al Viro031eb4c2006-01-12 01:06:33 -0800268static ssize_t q40_ctc_s8(const u_char __user *userPtr, size_t userCount,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700269 u_char frame[], ssize_t *frameUsed,
270 ssize_t frameLeft)
271{
272 u_char *p = (u_char *) &frame[*frameUsed];
273 unsigned int data = expand_data;
274 int bal = expand_bal;
275 int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
276 int utotal, ftotal;
277
278 ftotal = frameLeft;
279 utotal = userCount;
280 while (frameLeft) {
281 u_char c;
282 while (bal < 0) {
283 if (userCount == 0)
284 goto lout;
285 if (!(bal<(-hSpeed))) {
286 if (get_user(c, userPtr))
287 return -EFAULT;
288 data = c + 0x80;
289 }
290 userPtr++;
291 userCount--;
292 bal += hSpeed;
293 }
294 *p++ = data;
295 frameLeft--;
296 bal -= sSpeed;
297 }
298 lout:
299 expand_bal = bal;
300 expand_data = data;
301 *frameUsed += (ftotal - frameLeft);
302 utotal -= userCount;
303 return utotal;
304}
305
306
Al Viro031eb4c2006-01-12 01:06:33 -0800307static ssize_t q40_ctc_u8(const u_char __user *userPtr, size_t userCount,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700308 u_char frame[], ssize_t *frameUsed,
309 ssize_t frameLeft)
310{
311 u_char *p = (u_char *) &frame[*frameUsed];
312 unsigned int data = expand_data;
313 int bal = expand_bal;
314 int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
315 int utotal, ftotal;
316
317 ftotal = frameLeft;
318 utotal = userCount;
319 while (frameLeft) {
320 u_char c;
321 while (bal < 0) {
322 if (userCount == 0)
323 goto lout;
324 if (!(bal<(-hSpeed))) {
325 if (get_user(c, userPtr))
326 return -EFAULT;
327 data = c ;
328 }
329 userPtr++;
330 userCount--;
331 bal += hSpeed;
332 }
333 *p++ = data;
334 frameLeft--;
335 bal -= sSpeed;
336 }
337 lout:
338 expand_bal = bal;
339 expand_data = data;
340 *frameUsed += (ftotal - frameLeft) ;
341 utotal -= userCount;
342 return utotal;
343}
344
345
346static TRANS transQ40Normal = {
347 q40_ct_law, q40_ct_law, q40_ct_s8, q40_ct_u8, NULL, NULL, NULL, NULL
348};
349
350static TRANS transQ40Expanding = {
351 q40_ctx_law, q40_ctx_law, q40_ctx_s8, q40_ctx_u8, NULL, NULL, NULL, NULL
352};
353
354static TRANS transQ40Compressing = {
355 q40_ctc_law, q40_ctc_law, q40_ctc_s8, q40_ctc_u8, NULL, NULL, NULL, NULL
356};
357
358
359/*** Low level stuff *********************************************************/
360
Al Viro1ef64e62005-10-21 03:22:18 -0400361static void *Q40Alloc(unsigned int size, gfp_t flags)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700362{
363 return kmalloc(size, flags); /* change to vmalloc */
364}
365
366static void Q40Free(void *ptr, unsigned int size)
367{
368 kfree(ptr);
369}
370
371static int __init Q40IrqInit(void)
372{
373 /* Register interrupt handler. */
374 request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0,
375 "DMA sound", Q40Interrupt);
376
377 return(1);
378}
379
380
381#ifdef MODULE
382static void Q40IrqCleanUp(void)
383{
384 master_outb(0,SAMPLE_ENABLE_REG);
385 free_irq(Q40_IRQ_SAMPLE, Q40Interrupt);
386}
387#endif /* MODULE */
388
389
390static void Q40Silence(void)
391{
392 master_outb(0,SAMPLE_ENABLE_REG);
393 *DAC_LEFT=*DAC_RIGHT=127;
394}
395
396static char *q40_pp;
397static unsigned int q40_sc;
398
399static void Q40PlayNextFrame(int index)
400{
401 u_char *start;
402 u_long size;
403 u_char speed;
404
405 /* used by Q40Play() if all doubts whether there really is something
406 * to be played are already wiped out.
407 */
408 start = write_sq.buffers[write_sq.front];
409 size = (write_sq.count == index ? write_sq.rear_size : write_sq.block_size);
410
411 q40_pp=start;
412 q40_sc=size;
413
414 write_sq.front = (write_sq.front+1) % write_sq.max_count;
415 write_sq.active++;
416
417 speed=(dmasound.hard.speed==10000 ? 0 : 1);
418
419 master_outb( 0,SAMPLE_ENABLE_REG);
420 free_irq(Q40_IRQ_SAMPLE, Q40Interrupt);
421 if (dmasound.soft.stereo)
422 request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0,
423 "Q40 sound", Q40Interrupt);
424 else
425 request_irq(Q40_IRQ_SAMPLE, Q40MonoInterrupt, 0,
426 "Q40 sound", Q40Interrupt);
427
428 master_outb( speed, SAMPLE_RATE_REG);
429 master_outb( 1,SAMPLE_CLEAR_REG);
430 master_outb( 1,SAMPLE_ENABLE_REG);
431}
432
433static void Q40Play(void)
434{
435 unsigned long flags;
436
437 if (write_sq.active || write_sq.count<=0 ) {
438 /* There's already a frame loaded */
439 return;
440 }
441
442 /* nothing in the queue */
443 if (write_sq.count <= 1 && write_sq.rear_size < write_sq.block_size && !write_sq.syncing) {
444 /* hmmm, the only existing frame is not
445 * yet filled and we're not syncing?
446 */
447 return;
448 }
449 spin_lock_irqsave(&dmasound.lock, flags);
450 Q40PlayNextFrame(1);
451 spin_unlock_irqrestore(&dmasound.lock, flags);
452}
453
David Howells7d12e782006-10-05 14:55:46 +0100454static irqreturn_t Q40StereoInterrupt(int irq, void *dummy)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700455{
456 spin_lock(&dmasound.lock);
457 if (q40_sc>1){
458 *DAC_LEFT=*q40_pp++;
459 *DAC_RIGHT=*q40_pp++;
460 q40_sc -=2;
461 master_outb(1,SAMPLE_CLEAR_REG);
462 }else Q40Interrupt();
463 spin_unlock(&dmasound.lock);
464 return IRQ_HANDLED;
465}
David Howells7d12e782006-10-05 14:55:46 +0100466static irqreturn_t Q40MonoInterrupt(int irq, void *dummy)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700467{
468 spin_lock(&dmasound.lock);
469 if (q40_sc>0){
470 *DAC_LEFT=*q40_pp;
471 *DAC_RIGHT=*q40_pp++;
472 q40_sc --;
473 master_outb(1,SAMPLE_CLEAR_REG);
474 }else Q40Interrupt();
475 spin_unlock(&dmasound.lock);
476 return IRQ_HANDLED;
477}
478static void Q40Interrupt(void)
479{
480 if (!write_sq.active) {
481 /* playing was interrupted and sq_reset() has already cleared
482 * the sq variables, so better don't do anything here.
483 */
484 WAKE_UP(write_sq.sync_queue);
485 master_outb(0,SAMPLE_ENABLE_REG); /* better safe */
486 goto exit;
487 } else write_sq.active=0;
488 write_sq.count--;
489 Q40Play();
490
491 if (q40_sc<2)
492 { /* there was nothing to play, disable irq */
493 master_outb(0,SAMPLE_ENABLE_REG);
494 *DAC_LEFT=*DAC_RIGHT=127;
495 }
496 WAKE_UP(write_sq.action_queue);
497
498 exit:
499 master_outb(1,SAMPLE_CLEAR_REG);
500}
501
502
503static void Q40Init(void)
504{
505 int i, idx;
506 const int freq[] = {10000, 20000};
507
508 /* search a frequency that fits into the allowed error range */
509
510 idx = -1;
511 for (i = 0; i < 2; i++)
512 if ((100 * abs(dmasound.soft.speed - freq[i]) / freq[i]) <= catchRadius)
513 idx = i;
514
515 dmasound.hard = dmasound.soft;
516 /*sound.hard.stereo=1;*/ /* no longer true */
517 dmasound.hard.size=8;
518
519 if (idx > -1) {
520 dmasound.soft.speed = freq[idx];
521 dmasound.trans_write = &transQ40Normal;
522 } else
523 dmasound.trans_write = &transQ40Expanding;
524
525 Q40Silence();
526
527 if (dmasound.hard.speed > 20200) {
528 /* squeeze the sound, we do that */
529 dmasound.hard.speed = 20000;
530 dmasound.trans_write = &transQ40Compressing;
531 } else if (dmasound.hard.speed > 10000) {
532 dmasound.hard.speed = 20000;
533 } else {
534 dmasound.hard.speed = 10000;
535 }
536 expand_bal = -dmasound.soft.speed;
537}
538
539
540static int Q40SetFormat(int format)
541{
542 /* Q40 sound supports only 8bit modes */
543
544 switch (format) {
545 case AFMT_QUERY:
546 return(dmasound.soft.format);
547 case AFMT_MU_LAW:
548 case AFMT_A_LAW:
549 case AFMT_S8:
550 case AFMT_U8:
551 break;
552 default:
553 format = AFMT_S8;
554 }
555
556 dmasound.soft.format = format;
557 dmasound.soft.size = 8;
558 if (dmasound.minDev == SND_DEV_DSP) {
559 dmasound.dsp.format = format;
560 dmasound.dsp.size = 8;
561 }
562 Q40Init();
563
564 return(format);
565}
566
567static int Q40SetVolume(int volume)
568{
569 return 0;
570}
571
572
573/*** Machine definitions *****************************************************/
574
575static SETTINGS def_hard = {
576 .format = AFMT_U8,
577 .stereo = 0,
578 .size = 8,
579 .speed = 10000
580} ;
581
582static SETTINGS def_soft = {
583 .format = AFMT_U8,
584 .stereo = 0,
585 .size = 8,
586 .speed = 8000
587} ;
588
589static MACHINE machQ40 = {
590 .name = "Q40",
591 .name2 = "Q40",
592 .owner = THIS_MODULE,
593 .dma_alloc = Q40Alloc,
594 .dma_free = Q40Free,
595 .irqinit = Q40IrqInit,
596#ifdef MODULE
597 .irqcleanup = Q40IrqCleanUp,
598#endif /* MODULE */
599 .init = Q40Init,
600 .silence = Q40Silence,
601 .setFormat = Q40SetFormat,
602 .setVolume = Q40SetVolume,
603 .play = Q40Play,
604 .min_dsp_speed = 10000,
605 .version = ((DMASOUND_Q40_REVISION<<8) | DMASOUND_Q40_EDITION),
606 .hardware_afmts = AFMT_U8, /* h'ware-supported formats *only* here */
607 .capabilities = DSP_CAP_BATCH /* As per SNDCTL_DSP_GETCAPS */
608};
609
610
611/*** Config & Setup **********************************************************/
612
613
Adrian Bunk0a1b42d2008-06-10 01:26:28 +0300614static int __init dmasound_q40_init(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700615{
616 if (MACH_IS_Q40) {
617 dmasound.mach = machQ40;
618 dmasound.mach.default_hard = def_hard ;
619 dmasound.mach.default_soft = def_soft ;
620 return dmasound_init();
621 } else
622 return -ENODEV;
623}
624
625static void __exit dmasound_q40_cleanup(void)
626{
627 dmasound_deinit();
628}
629
630module_init(dmasound_q40_init);
631module_exit(dmasound_q40_cleanup);
632
633MODULE_DESCRIPTION("Q40/Q60 sound driver");
634MODULE_LICENSE("GPL");