FreeBSD/Linux Kernel Cross Reference
sys/i386/isa/pcaudio.c
1 /*-
2 * Copyright (c) 1994 Søren Schmidt
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer
10 * in this position and unchanged.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote products
15 * derived from this software withough specific prior written permission
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 *
28 * $FreeBSD: src/sys/i386/isa/pcaudio.c,v 1.32.2.1 1999/09/05 08:13:16 peter Exp $
29 */
30
31 #include "pca.h"
32 #if NPCA > 0
33
34 #include <sys/param.h>
35 #include <sys/systm.h>
36 #include <sys/conf.h>
37 #include <sys/uio.h>
38 #include <sys/fcntl.h>
39 #include <sys/proc.h>
40 #include <sys/kernel.h>
41
42 #include <machine/clock.h>
43 #include <machine/pcaudioio.h>
44
45 #include <i386/isa/isa.h>
46 #include <i386/isa/isa_device.h>
47 #include <i386/isa/timerreg.h>
48
49 #define DSP_ULAW_NOT_WANTED
50 #include <i386/isa/sound/ulaw.h>
51
52 #ifdef DEVFS
53 #include <sys/devfsext.h>
54 #endif /* DEVFS */
55
56 #define BUF_SIZE 8192
57 #define SAMPLE_RATE 8000
58 #define INTERRUPT_RATE 16000
59
60 static struct pca_status {
61 char open; /* device open */
62 char queries; /* did others try opening */
63 unsigned char *buf[2]; /* double buffering */
64 unsigned char *buffer; /* current buffer ptr */
65 unsigned in_use[2]; /* buffers fill */
66 unsigned index; /* index in current buffer */
67 unsigned counter; /* sample counter */
68 unsigned scale; /* sample counter scale */
69 unsigned sample_rate; /* sample rate */
70 unsigned processed; /* samples processed */
71 unsigned volume; /* volume for pc-speaker */
72 char encoding; /* Ulaw, Alaw or linear */
73 char current; /* current buffer */
74 unsigned char oldval; /* old timer port value */
75 char timer_on; /* is playback running */
76 struct selinfo wsel; /* select status */
77 } pca_status;
78
79 static char buffer1[BUF_SIZE];
80 static char buffer2[BUF_SIZE];
81 static char volume_table[256];
82
83 #ifdef DEVFS
84 static void *pca_devfs_token;
85 static void *pcac_devfs_token;
86 #endif
87
88 static int pca_sleep = 0;
89 static int pca_initialized = 0;
90
91 static void pcaintr(struct clockframe *frame);
92 static int pcaprobe(struct isa_device *dvp);
93 static int pcaattach(struct isa_device *dvp);
94
95 struct isa_driver pcadriver = {
96 pcaprobe, pcaattach, "pca",
97 };
98
99 static d_open_t pcaopen;
100 static d_close_t pcaclose;
101 static d_write_t pcawrite;
102 static d_ioctl_t pcaioctl;
103 static d_select_t pcaselect;
104
105 #define CDEV_MAJOR 24
106 static struct cdevsw pca_cdevsw =
107 { pcaopen, pcaclose, noread, pcawrite, /*24*/
108 pcaioctl, nostop, nullreset, nodevtotty,/* pcaudio */
109 pcaselect, nommap, NULL, "pca", NULL, -1 };
110
111 static void pca_continue __P((void));
112 static void pca_init __P((void));
113 static void pca_pause __P((void));
114
115 static inline void
116 conv(const void *table, void *buff, unsigned long n)
117 {
118 __asm__("1:\tmovb (%2), %3\n"
119 "\txlatb\n"
120 "\tmovb %3, (%2)\n"
121 "\tinc %2\n"
122 "\tdec %1\n"
123 "\tjnz 1b\n"
124 :
125 :"b" ((long)table), "c" (n), "D" ((long)buff), "a" ((char)n)
126 :"bx","cx","di","ax");
127 }
128
129
130 static void
131 pca_volume(int volume)
132 {
133 int i, j;
134
135 for (i=0; i<256; i++) {
136 j = ((i-128)*volume)/100;
137 if (j<-128)
138 j = -128;
139 if (j>127)
140 j = 127;
141 volume_table[i] = (((255-(j + 128))/4)+1);
142 }
143 }
144
145
146 static void
147 pca_init(void)
148 {
149 pca_status.open = 0;
150 pca_status.queries = 0;
151 pca_status.timer_on = 0;
152 pca_status.buf[0] = (unsigned char *)&buffer1[0];
153 pca_status.buf[1] = (unsigned char *)&buffer2[0];
154 pca_status.buffer = pca_status.buf[0];
155 pca_status.in_use[0] = pca_status.in_use[1] = 0;
156 pca_status.current = 0;
157 pca_status.sample_rate = SAMPLE_RATE;
158 pca_status.scale = (pca_status.sample_rate << 8) / INTERRUPT_RATE;
159 pca_status.encoding = AUDIO_ENCODING_ULAW;
160 pca_status.volume = 100;
161
162 pca_volume(pca_status.volume);
163 }
164
165
166 static int
167 pca_start(void)
168 {
169 int x = splhigh();
170 int rv = 0;
171
172 /* use the first buffer */
173 pca_status.current = 0;
174 pca_status.index = 0;
175 pca_status.counter = 0;
176 pca_status.buffer = pca_status.buf[pca_status.current];
177 pca_status.oldval = inb(IO_PPI) | 0x03;
178 /* acquire the timers */
179 if (acquire_timer2(TIMER_LSB|TIMER_ONESHOT))
180 rv = -1;
181 else if (acquire_timer0(INTERRUPT_RATE, pcaintr)) {
182 release_timer2();
183 rv = -1;
184 } else
185 pca_status.timer_on = 1;
186
187 splx(x);
188 return rv;
189 }
190
191
192 static void
193 pca_stop(void)
194 {
195 int x = splhigh();
196
197 /* release the timers */
198 release_timer0();
199 release_timer2();
200 /* reset the buffer */
201 pca_status.in_use[0] = pca_status.in_use[1] = 0;
202 pca_status.index = 0;
203 pca_status.counter = 0;
204 pca_status.current = 0;
205 pca_status.buffer = pca_status.buf[pca_status.current];
206 pca_status.timer_on = 0;
207 splx(x);
208 }
209
210
211 static void
212 pca_pause(void)
213 {
214 int x = splhigh();
215
216 release_timer0();
217 release_timer2();
218 pca_status.timer_on = 0;
219 splx(x);
220 }
221
222
223 static void
224 pca_continue(void)
225 {
226 int x = splhigh();
227
228 pca_status.oldval = inb(IO_PPI) | 0x03;
229 acquire_timer2(TIMER_LSB|TIMER_ONESHOT);
230 acquire_timer0(INTERRUPT_RATE, pcaintr);
231 pca_status.timer_on = 1;
232 splx(x);
233 }
234
235
236 static int
237 pca_wait(void)
238 {
239 int error, x;
240
241 if (!pca_status.timer_on)
242 return 0;
243
244 while (pca_status.in_use[0] || pca_status.in_use[1]) {
245 x = spltty();
246 pca_sleep = 1;
247 error = tsleep(&pca_sleep, PZERO|PCATCH, "pca_drain", 0);
248 pca_sleep = 0;
249 splx(x);
250 if (error != 0 && error != ERESTART) {
251 pca_stop();
252 return error;
253 }
254 }
255 return 0;
256 }
257
258
259 static int
260 pcaprobe(struct isa_device *dvp)
261 {
262 return(-1);
263 }
264
265
266 static int
267 pcaattach(struct isa_device *dvp)
268 {
269 printf("pca%d: PC speaker audio driver\n", dvp->id_unit);
270 pca_init();
271 #ifdef DEVFS
272 pca_devfs_token =
273 devfs_add_devswf(&pca_cdevsw, 0, DV_CHR, 0, 0, 0600, "pcaudio");
274 pcac_devfs_token =
275 devfs_add_devswf(&pca_cdevsw, 128, DV_CHR, 0, 0, 0600,
276 "pcaudioctl");
277 #endif /*DEVFS*/
278
279 return 1;
280 }
281
282
283 static int
284 pcaopen(dev_t dev, int flags, int fmt, struct proc *p)
285 {
286 /* audioctl device can always be opened */
287 if (minor(dev) == 128)
288 return 0;
289 if (minor(dev) > 0)
290 return ENXIO;
291
292 if (!pca_initialized) {
293 pca_init();
294 pca_initialized = 1;
295 }
296
297 /* audio device can only be open by one process */
298 if (pca_status.open) {
299 pca_status.queries = 1;
300 return EBUSY;
301 }
302 pca_status.buffer = pca_status.buf[0];
303 pca_status.in_use[0] = pca_status.in_use[1] = 0;
304 pca_status.timer_on = 0;
305 pca_status.open = 1;
306 pca_status.processed = 0;
307 return 0;
308 }
309
310
311 static int
312 pcaclose(dev_t dev, int flags, int fmt, struct proc *p)
313 {
314 /* audioctl device can always be closed */
315 if (minor(dev) == 128)
316 return 0;
317 if (minor(dev) > 0)
318 return ENXIO;
319 /* audio device close drains all output and restores timers */
320 pca_wait();
321 pca_stop();
322 pca_status.open = 0;
323 return 0;
324 }
325
326
327 static int
328 pcawrite(dev_t dev, struct uio *uio, int flag)
329 {
330 int count, error, which, x;
331
332 /* only audio device can be written */
333 if (minor(dev) > 0)
334 return ENXIO;
335
336 while ((count = min(BUF_SIZE, uio->uio_resid)) > 0) {
337 if (pca_status.in_use[0] && pca_status.in_use[1]) {
338 x = spltty();
339 pca_sleep = 1;
340 error = tsleep(&pca_sleep, PZERO|PCATCH, "pca_wait", 0);
341 pca_sleep = 0;
342 splx(x);
343 if (error != 0 && error != ERESTART) {
344 pca_stop();
345 return error;
346 }
347 }
348 which = pca_status.in_use[0] ? 1 : 0;
349 if (count && !pca_status.in_use[which]) {
350 uiomove(pca_status.buf[which], count, uio);
351 pca_status.processed += count;
352 switch (pca_status.encoding) {
353 case AUDIO_ENCODING_ULAW:
354 conv(ulaw_dsp, pca_status.buf[which], count);
355 break;
356
357 case AUDIO_ENCODING_ALAW:
358 break;
359
360 case AUDIO_ENCODING_RAW:
361 break;
362 }
363 pca_status.in_use[which] = count;
364 if (!pca_status.timer_on)
365 if (pca_start())
366 return EBUSY;
367 }
368 }
369 return 0;
370 }
371
372
373 static int
374 pcaioctl(dev_t dev, int cmd, caddr_t data, int flag, struct proc *p)
375 {
376 audio_info_t *auptr;
377
378 switch(cmd) {
379
380 case AUDIO_GETINFO:
381 auptr = (audio_info_t *)data;
382 auptr->play.sample_rate = pca_status.sample_rate;
383 auptr->play.channels = 1;
384 auptr->play.precision = 8;
385 auptr->play.encoding = pca_status.encoding;
386
387 auptr->play.gain = pca_status.volume;
388 auptr->play.port = 0;
389
390 auptr->play.samples = pca_status.processed;
391 auptr->play.eof = 0;
392 auptr->play.pause = !pca_status.timer_on;
393 auptr->play.error = 0;
394 auptr->play.waiting = pca_status.queries;
395
396 auptr->play.open = pca_status.open;
397 auptr->play.active = pca_status.timer_on;
398 return 0;
399
400 case AUDIO_SETINFO:
401 auptr = (audio_info_t *)data;
402 if (auptr->play.sample_rate != (unsigned int)~0) {
403 pca_status.sample_rate = auptr->play.sample_rate;
404 pca_status.scale =
405 (pca_status.sample_rate << 8) / INTERRUPT_RATE;
406 }
407 if (auptr->play.encoding != (unsigned int)~0) {
408 pca_status.encoding = auptr->play.encoding;
409 }
410 if (auptr->play.gain != (unsigned int)~0) {
411 pca_status.volume = auptr->play.gain;
412 pca_volume(pca_status.volume);
413 }
414 if (auptr->play.pause != (unsigned char)~0) {
415 if (auptr->play.pause)
416 pca_pause();
417 else
418 pca_continue();
419 }
420
421 return 0;
422
423 case AUDIO_DRAIN:
424 case AUDIO_COMPAT_DRAIN:
425 return pca_wait();
426
427 case AUDIO_FLUSH:
428 case AUDIO_COMPAT_FLUSH:
429 pca_stop();
430 return 0;
431
432 }
433 return ENXIO;
434 }
435
436
437 static void
438 pcaintr(struct clockframe *frame)
439 {
440 if (pca_status.index < pca_status.in_use[pca_status.current]) {
441 disable_intr();
442 __asm__("outb %0,$0x61\n"
443 "andb $0xFE,%0\n"
444 "outb %0,$0x61"
445 : : "a" ((char)pca_status.oldval) );
446 __asm__("xlatb\n"
447 "outb %0,$0x42"
448 : : "a" ((char)pca_status.buffer[pca_status.index]),
449 "b" ((long)volume_table) );
450 enable_intr();
451 pca_status.counter += pca_status.scale;
452 pca_status.index = (pca_status.counter >> 8);
453 }
454 if (pca_status.index >= pca_status.in_use[pca_status.current]) {
455 pca_status.index = pca_status.counter = 0;
456 pca_status.in_use[pca_status.current] = 0;
457 pca_status.current ^= 1;
458 pca_status.buffer = pca_status.buf[pca_status.current];
459 if (pca_sleep)
460 wakeup(&pca_sleep);
461 if (pca_status.wsel.si_pid) {
462 selwakeup((struct selinfo *)&pca_status.wsel.si_pid);
463 pca_status.wsel.si_pid = 0;
464 pca_status.wsel.si_flags = 0;
465 }
466 }
467 }
468
469
470 static int
471 pcaselect(dev_t dev, int rw, struct proc *p)
472 {
473 int s = spltty();
474 struct proc *p1;
475
476 switch (rw) {
477
478 case FWRITE:
479 if (!pca_status.in_use[0] || !pca_status.in_use[1]) {
480 splx(s);
481 return(1);
482 }
483 if (pca_status.wsel.si_pid && (p1=pfind(pca_status.wsel.si_pid))
484 && p1->p_wchan == (caddr_t)&selwait)
485 pca_status.wsel.si_flags = SI_COLL;
486 else
487 pca_status.wsel.si_pid = p->p_pid;
488 splx(s);
489 return 0;
490 default:
491 splx(s);
492 return(0);
493 }
494 }
495
496 static pca_devsw_installed = 0;
497
498 static void pca_drvinit(void *unused)
499 {
500 dev_t dev;
501
502 if( ! pca_devsw_installed ) {
503 dev = makedev(CDEV_MAJOR, 0);
504 cdevsw_add(&dev,&pca_cdevsw, NULL);
505 pca_devsw_installed = 1;
506 }
507 }
508
509 SYSINIT(pcadev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,pca_drvinit,NULL)
510
511
512 #endif
Cache object: 13e101233ebcd0b42232e1e3e527736a
|