Commit | Line | Data |
---|---|---|
15637ed4 | 1 | /* |
60cfd54d | 2 | * spkr.c -- device driver for console speaker |
15637ed4 | 3 | * |
60cfd54d AC |
4 | * v1.4 by Eric S. Raymond (esr@snark.thyrsus.com) Aug 1993 |
5 | * modified for FreeBSD by Andrew A. Chernov <ache@astral.msk.su> | |
d7136515 | 6 | * |
61864434 | 7 | * $Id: spkr.c,v 1.5 1993/11/15 01:33:11 ache Exp $ |
15637ed4 RG |
8 | */ |
9 | ||
10 | #include "speaker.h" | |
11 | ||
12 | #if NSPEAKER > 0 | |
13 | ||
14 | #include "param.h" | |
f9417549 | 15 | #include "systm.h" |
15637ed4 RG |
16 | #include "kernel.h" |
17 | #include "errno.h" | |
18 | #include "buf.h" | |
19 | #include "uio.h" | |
60cfd54d | 20 | |
f9417549 | 21 | #include "machine/speaker.h" |
15637ed4 RG |
22 | |
23 | /**************** MACHINE DEPENDENT PART STARTS HERE ************************* | |
24 | * | |
25 | * This section defines a function tone() which causes a tone of given | |
26 | * frequency and duration from the 80x86's console speaker. | |
27 | * Another function endtone() is defined to force sound off, and there is | |
28 | * also a rest() entry point to do pauses. | |
29 | * | |
30 | * Audible sound is generated using the Programmable Interval Timer (PIT) and | |
31 | * Programmable Peripheral Interface (PPI) attached to the 80x86's speaker. The | |
32 | * PPI controls whether sound is passed through at all; the PIT's channel 2 is | |
33 | * used to generate clicks (a square wave) of whatever frequency is desired. | |
34 | */ | |
35 | ||
36 | /* | |
37 | * PIT and PPI port addresses and control values | |
38 | * | |
39 | * Most of the magic is hidden in the TIMER_PREP value, which selects PIT | |
40 | * channel 2, frequency LSB first, square-wave mode and binary encoding. | |
41 | * The encoding is as follows: | |
42 | * | |
43 | * +----------+----------+---------------+-----+ | |
44 | * | 1 0 | 1 1 | 0 1 1 | 0 | | |
45 | * | SC1 SC0 | RW1 RW0 | M2 M1 M0 | BCD | | |
46 | * +----------+----------+---------------+-----+ | |
47 | * Counter Write Mode 3 Binary | |
48 | * Channel 2 LSB first, (Square Wave) Encoding | |
49 | * MSB second | |
50 | */ | |
51 | #define PPI 0x61 /* port of Programmable Peripheral Interface */ | |
52 | #define PPI_SPKR 0x03 /* turn these PPI bits on to pass sound */ | |
53 | #define PIT_CTRL 0x43 /* PIT control address */ | |
54 | #define PIT_COUNT 0x42 /* PIT count address */ | |
55 | #define PIT_MODE 0xB6 /* set timer mode for sound generation */ | |
56 | ||
57 | /* | |
58 | * Magic numbers for timer control. | |
59 | */ | |
60 | #define TIMER_CLK 1193180L /* corresponds to 18.2 MHz tick rate */ | |
61 | ||
f9417549 AC |
62 | #define SPKRPRI PSOCK |
63 | static char endtone, endrest; | |
15637ed4 | 64 | |
f9417549 AC |
65 | static int tone(thz, ticks) |
66 | /* emit tone of frequency thz for given number of ticks */ | |
67 | unsigned int thz, ticks; | |
15637ed4 | 68 | { |
f9417549 AC |
69 | unsigned int divisor = TIMER_CLK / thz; |
70 | int sps, error; | |
15637ed4 RG |
71 | |
72 | #ifdef DEBUG | |
f9417549 | 73 | (void) printf("tone: thz=%d ticks=%d\n", thz, ticks); |
15637ed4 RG |
74 | #endif /* DEBUG */ |
75 | ||
76 | /* set timer to generate clicks at given frequency in Hertz */ | |
77 | sps = spltty(); | |
78 | outb(PIT_CTRL, PIT_MODE); /* prepare timer */ | |
60cfd54d | 79 | outb(PIT_COUNT, (divisor & 0xff)); /* send lo byte */ |
15637ed4 RG |
80 | outb(PIT_COUNT, (divisor >> 8)); /* send hi byte */ |
81 | splx(sps); | |
82 | ||
83 | /* turn the speaker on */ | |
84 | outb(PPI, inb(PPI) | PPI_SPKR); | |
85 | ||
86 | /* | |
87 | * Set timeout to endtone function, then give up the timeslice. | |
88 | * This is so other processes can execute while the tone is being | |
89 | * emitted. | |
90 | */ | |
f9417549 | 91 | while ((error = tsleep((caddr_t)&endtone, |
61864434 | 92 | SPKRPRI | PCATCH, "spkrtn", ticks)) == ERESTART) |
f9417549 AC |
93 | ; |
94 | outb(PPI, inb(PPI) & ~PPI_SPKR); | |
15637ed4 | 95 | |
f9417549 AC |
96 | if (error == EWOULDBLOCK) |
97 | error = 0; | |
98 | return error; | |
15637ed4 RG |
99 | } |
100 | ||
f9417549 | 101 | static int rest(ticks) |
15637ed4 RG |
102 | /* rest for given number of ticks */ |
103 | int ticks; | |
104 | { | |
f9417549 | 105 | int error; |
15637ed4 RG |
106 | /* |
107 | * Set timeout to endrest function, then give up the timeslice. | |
108 | * This is so other processes can execute while the rest is being | |
109 | * waited out. | |
110 | */ | |
111 | #ifdef DEBUG | |
60cfd54d | 112 | (void) printf("rest: %d\n", ticks); |
15637ed4 | 113 | #endif /* DEBUG */ |
f9417549 | 114 | while ((error = tsleep((caddr_t)&endrest, |
61864434 | 115 | SPKRPRI | PCATCH, "spkrrs", ticks)) == ERESTART) |
f9417549 AC |
116 | ; |
117 | if (error == EWOULDBLOCK) | |
118 | error = 0; | |
119 | return error; | |
15637ed4 RG |
120 | } |
121 | ||
122 | /**************** PLAY STRING INTERPRETER BEGINS HERE ********************** | |
123 | * | |
124 | * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement; | |
60cfd54d AC |
125 | * M[LNS] are missing; the ~ synonym and the _ slur mark and the octave- |
126 | * tracking facility are added. | |
15637ed4 RG |
127 | * Requires tone(), rest(), and endtone(). String play is not interruptible |
128 | * except possibly at physical block boundaries. | |
129 | */ | |
130 | ||
131 | typedef int bool; | |
132 | #define TRUE 1 | |
133 | #define FALSE 0 | |
134 | ||
135 | #define toupper(c) ((c) - ' ' * (((c) >= 'a') && ((c) <= 'z'))) | |
136 | #define isdigit(c) (((c) >= '0') && ((c) <= '9')) | |
137 | #define dtoi(c) ((c) - '0') | |
138 | ||
139 | static int octave; /* currently selected octave */ | |
140 | static int whole; /* whole-note time at current tempo, in ticks */ | |
141 | static int value; /* whole divisor for note time, quarter note = 1 */ | |
142 | static int fill; /* controls spacing of notes */ | |
143 | static bool octtrack; /* octave-tracking on? */ | |
144 | static bool octprefix; /* override current octave-tracking state? */ | |
145 | ||
146 | /* | |
147 | * Magic number avoidance... | |
148 | */ | |
149 | #define SECS_PER_MIN 60 /* seconds per minute */ | |
150 | #define WHOLE_NOTE 4 /* quarter notes per whole note */ | |
151 | #define MIN_VALUE 64 /* the most we can divide a note by */ | |
152 | #define DFLT_VALUE 4 /* default value (quarter-note) */ | |
153 | #define FILLTIME 8 /* for articulation, break note in parts */ | |
154 | #define STACCATO 6 /* 6/8 = 3/4 of note is filled */ | |
155 | #define NORMAL 7 /* 7/8ths of note interval is filled */ | |
156 | #define LEGATO 8 /* all of note interval is filled */ | |
157 | #define DFLT_OCTAVE 4 /* default octave */ | |
158 | #define MIN_TEMPO 32 /* minimum tempo */ | |
159 | #define DFLT_TEMPO 120 /* default tempo */ | |
160 | #define MAX_TEMPO 255 /* max tempo */ | |
161 | #define NUM_MULT 3 /* numerator of dot multiplier */ | |
162 | #define DENOM_MULT 2 /* denominator of dot multiplier */ | |
163 | ||
164 | /* letter to half-tone: A B C D E F G */ | |
165 | static int notetab[8] = {9, 11, 0, 2, 4, 5, 7}; | |
166 | ||
167 | /* | |
168 | * This is the American Standard A440 Equal-Tempered scale with frequencies | |
169 | * rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook... | |
170 | * our octave 0 is standard octave 2. | |
171 | */ | |
172 | #define OCTAVE_NOTES 12 /* semitones per octave */ | |
173 | static int pitchtab[] = | |
174 | { | |
175 | /* C C# D D# E F F# G G# A A# B*/ | |
176 | /* 0 */ 65, 69, 73, 78, 82, 87, 93, 98, 103, 110, 117, 123, | |
177 | /* 1 */ 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, | |
178 | /* 2 */ 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, | |
179 | /* 3 */ 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, | |
180 | /* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975, | |
181 | /* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, | |
182 | /* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902, | |
183 | }; | |
184 | ||
185 | static void playinit() | |
186 | { | |
187 | octave = DFLT_OCTAVE; | |
f9417549 | 188 | whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO; |
15637ed4 RG |
189 | fill = NORMAL; |
190 | value = DFLT_VALUE; | |
191 | octtrack = FALSE; | |
192 | octprefix = TRUE; /* act as though there was an initial O(n) */ | |
193 | } | |
194 | ||
f9417549 | 195 | static int playtone(pitch, value, sustain) |
15637ed4 RG |
196 | /* play tone of proper duration for current rhythm signature */ |
197 | int pitch, value, sustain; | |
198 | { | |
199 | register int sound, silence, snum = 1, sdenom = 1; | |
f9417549 | 200 | int error; |
15637ed4 RG |
201 | |
202 | /* this weirdness avoids floating-point arithmetic */ | |
203 | for (; sustain; sustain--) | |
204 | { | |
60cfd54d | 205 | /* See the BUGS section in the man page for discussion */ |
15637ed4 RG |
206 | snum *= NUM_MULT; |
207 | sdenom *= DENOM_MULT; | |
208 | } | |
209 | ||
210 | if (pitch == -1) | |
f9417549 | 211 | error = rest(whole * snum / (value * sdenom)); |
15637ed4 RG |
212 | else |
213 | { | |
214 | sound = (whole * snum) / (value * sdenom) | |
215 | - (whole * (FILLTIME - fill)) / (value * FILLTIME); | |
216 | silence = whole * (FILLTIME-fill) * snum / (FILLTIME * value * sdenom); | |
217 | ||
218 | #ifdef DEBUG | |
60cfd54d | 219 | (void) printf("playtone: pitch %d for %d ticks, rest for %d ticks\n", |
15637ed4 RG |
220 | pitch, sound, silence); |
221 | #endif /* DEBUG */ | |
222 | ||
f9417549 AC |
223 | error = tone(pitchtab[pitch], sound); |
224 | if (error) return error; | |
15637ed4 | 225 | if (fill != LEGATO) |
f9417549 | 226 | error = rest(silence); |
15637ed4 | 227 | } |
f9417549 | 228 | return error; |
15637ed4 RG |
229 | } |
230 | ||
231 | static int abs(n) | |
232 | int n; | |
233 | { | |
234 | if (n < 0) | |
235 | return(-n); | |
236 | else | |
237 | return(n); | |
238 | } | |
239 | ||
f9417549 | 240 | static int playstring(cp, slen) |
15637ed4 RG |
241 | /* interpret and play an item from a notation string */ |
242 | char *cp; | |
243 | size_t slen; | |
244 | { | |
60cfd54d | 245 | int pitch, oldfill, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE; |
f9417549 | 246 | int error; |
15637ed4 RG |
247 | |
248 | #define GETNUM(cp, v) for(v=0; isdigit(cp[1]) && slen > 0; ) \ | |
249 | {v = v * 10 + (*++cp - '0'); slen--;} | |
250 | for (; slen--; cp++) | |
251 | { | |
252 | int sustain, timeval, tempo; | |
253 | register char c = toupper(*cp); | |
254 | ||
255 | #ifdef DEBUG | |
60cfd54d | 256 | (void) printf("playstring: %c (%x)\n", c, c); |
15637ed4 RG |
257 | #endif /* DEBUG */ |
258 | ||
259 | switch (c) | |
260 | { | |
261 | case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': | |
262 | ||
263 | /* compute pitch */ | |
264 | pitch = notetab[c - 'A'] + octave * OCTAVE_NOTES; | |
265 | ||
266 | /* this may be followed by an accidental sign */ | |
267 | if (cp[1] == '#' || cp[1] == '+') | |
268 | { | |
269 | ++pitch; | |
270 | ++cp; | |
271 | slen--; | |
272 | } | |
273 | else if (cp[1] == '-') | |
274 | { | |
275 | --pitch; | |
276 | ++cp; | |
277 | slen--; | |
278 | } | |
279 | ||
280 | /* | |
281 | * If octave-tracking mode is on, and there has been no octave- | |
282 | * setting prefix, find the version of the current letter note | |
283 | * closest to the last regardless of octave. | |
284 | */ | |
285 | if (octtrack && !octprefix) | |
286 | { | |
287 | if (abs(pitch-lastpitch) > abs(pitch+OCTAVE_NOTES-lastpitch)) | |
288 | { | |
289 | ++octave; | |
290 | pitch += OCTAVE_NOTES; | |
291 | } | |
292 | ||
293 | if (abs(pitch-lastpitch) > abs((pitch-OCTAVE_NOTES)-lastpitch)) | |
294 | { | |
295 | --octave; | |
296 | pitch -= OCTAVE_NOTES; | |
297 | } | |
298 | } | |
299 | octprefix = FALSE; | |
300 | lastpitch = pitch; | |
301 | ||
302 | /* ...which may in turn be followed by an override time value */ | |
303 | GETNUM(cp, timeval); | |
304 | if (timeval <= 0 || timeval > MIN_VALUE) | |
305 | timeval = value; | |
306 | ||
307 | /* ...and/or sustain dots */ | |
308 | for (sustain = 0; cp[1] == '.'; cp++) | |
309 | { | |
310 | slen--; | |
311 | sustain++; | |
312 | } | |
313 | ||
60cfd54d AC |
314 | /* ...and/or a slur mark */ |
315 | oldfill = fill; | |
316 | if (cp[1] == '_') | |
317 | { | |
318 | fill = LEGATO; | |
319 | ++cp; | |
320 | slen--; | |
321 | } | |
322 | ||
15637ed4 | 323 | /* time to emit the actual tone */ |
f9417549 | 324 | error = playtone(pitch, timeval, sustain); |
60cfd54d AC |
325 | |
326 | fill = oldfill; | |
f9417549 | 327 | if (error) return error; |
15637ed4 RG |
328 | break; |
329 | ||
330 | case 'O': | |
331 | if (cp[1] == 'N' || cp[1] == 'n') | |
332 | { | |
333 | octprefix = octtrack = FALSE; | |
334 | ++cp; | |
335 | slen--; | |
336 | } | |
337 | else if (cp[1] == 'L' || cp[1] == 'l') | |
338 | { | |
339 | octtrack = TRUE; | |
340 | ++cp; | |
341 | slen--; | |
342 | } | |
343 | else | |
344 | { | |
345 | GETNUM(cp, octave); | |
346 | if (octave >= sizeof(pitchtab) / OCTAVE_NOTES) | |
347 | octave = DFLT_OCTAVE; | |
348 | octprefix = TRUE; | |
349 | } | |
350 | break; | |
351 | ||
352 | case '>': | |
353 | if (octave < sizeof(pitchtab) / OCTAVE_NOTES - 1) | |
354 | octave++; | |
355 | octprefix = TRUE; | |
356 | break; | |
357 | ||
358 | case '<': | |
359 | if (octave > 0) | |
360 | octave--; | |
361 | octprefix = TRUE; | |
362 | break; | |
363 | ||
364 | case 'N': | |
365 | GETNUM(cp, pitch); | |
366 | for (sustain = 0; cp[1] == '.'; cp++) | |
367 | { | |
368 | slen--; | |
369 | sustain++; | |
370 | } | |
60cfd54d AC |
371 | oldfill = fill; |
372 | if (cp[1] == '_') | |
373 | { | |
374 | fill = LEGATO; | |
375 | ++cp; | |
376 | slen--; | |
377 | } | |
f9417549 | 378 | error = playtone(pitch - 1, value, sustain); |
60cfd54d | 379 | fill = oldfill; |
f9417549 | 380 | if (error) return error; |
15637ed4 RG |
381 | break; |
382 | ||
383 | case 'L': | |
384 | GETNUM(cp, value); | |
385 | if (value <= 0 || value > MIN_VALUE) | |
386 | value = DFLT_VALUE; | |
387 | break; | |
388 | ||
389 | case 'P': | |
390 | case '~': | |
391 | /* this may be followed by an override time value */ | |
392 | GETNUM(cp, timeval); | |
393 | if (timeval <= 0 || timeval > MIN_VALUE) | |
394 | timeval = value; | |
395 | for (sustain = 0; cp[1] == '.'; cp++) | |
396 | { | |
397 | slen--; | |
398 | sustain++; | |
399 | } | |
f9417549 AC |
400 | error = playtone(-1, timeval, sustain); |
401 | if (error) return error; | |
15637ed4 RG |
402 | break; |
403 | ||
404 | case 'T': | |
405 | GETNUM(cp, tempo); | |
406 | if (tempo < MIN_TEMPO || tempo > MAX_TEMPO) | |
407 | tempo = DFLT_TEMPO; | |
f9417549 | 408 | whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / tempo; |
15637ed4 RG |
409 | break; |
410 | ||
411 | case 'M': | |
412 | if (cp[1] == 'N' || cp[1] == 'n') | |
413 | { | |
414 | fill = NORMAL; | |
415 | ++cp; | |
416 | slen--; | |
417 | } | |
418 | else if (cp[1] == 'L' || cp[1] == 'l') | |
419 | { | |
420 | fill = LEGATO; | |
421 | ++cp; | |
422 | slen--; | |
423 | } | |
424 | else if (cp[1] == 'S' || cp[1] == 's') | |
425 | { | |
426 | fill = STACCATO; | |
427 | ++cp; | |
428 | slen--; | |
429 | } | |
430 | break; | |
431 | } | |
432 | } | |
f9417549 | 433 | return 0; |
15637ed4 RG |
434 | } |
435 | ||
436 | /******************* UNIX DRIVER HOOKS BEGIN HERE ************************** | |
437 | * | |
438 | * This section implements driver hooks to run playstring() and the tone(), | |
439 | * endtone(), and rest() functions defined above. | |
440 | */ | |
441 | ||
60cfd54d AC |
442 | static int spkr_active = FALSE; /* exclusion flag */ |
443 | static struct buf *spkr_inbuf; /* incoming buf */ | |
15637ed4 RG |
444 | |
445 | int spkropen(dev) | |
446 | dev_t dev; | |
447 | { | |
448 | #ifdef DEBUG | |
60cfd54d | 449 | (void) printf("spkropen: entering with dev = %x\n", dev); |
15637ed4 RG |
450 | #endif /* DEBUG */ |
451 | ||
452 | if (minor(dev) != 0) | |
453 | return(ENXIO); | |
454 | else if (spkr_active) | |
455 | return(EBUSY); | |
456 | else | |
457 | { | |
60cfd54d AC |
458 | #ifdef DEBUG |
459 | (void) printf("spkropen: about to perform play initialization\n"); | |
460 | #endif /* DEBUG */ | |
15637ed4 RG |
461 | playinit(); |
462 | spkr_inbuf = geteblk(DEV_BSIZE); | |
60cfd54d AC |
463 | spkr_active = TRUE; |
464 | return(0); | |
15637ed4 | 465 | } |
15637ed4 RG |
466 | } |
467 | ||
468 | int spkrwrite(dev, uio) | |
60cfd54d AC |
469 | dev_t dev; |
470 | struct uio *uio; | |
15637ed4 | 471 | { |
15637ed4 RG |
472 | #ifdef DEBUG |
473 | printf("spkrwrite: entering with dev = %x, count = %d\n", | |
474 | dev, uio->uio_resid); | |
475 | #endif /* DEBUG */ | |
476 | ||
477 | if (minor(dev) != 0) | |
478 | return(ENXIO); | |
60cfd54d AC |
479 | else if (uio->uio_resid > DEV_BSIZE) /* prevent system crashes */ |
480 | return(E2BIG); | |
15637ed4 RG |
481 | else |
482 | { | |
60cfd54d AC |
483 | unsigned n; |
484 | char *cp; | |
485 | int error; | |
486 | ||
487 | n = uio->uio_resid; | |
15637ed4 | 488 | cp = spkr_inbuf->b_un.b_addr; |
60cfd54d | 489 | if (!(error = uiomove(cp, n, uio))) |
f9417549 | 490 | error = playstring(cp, n); |
15637ed4 RG |
491 | return(error); |
492 | } | |
493 | } | |
494 | ||
495 | int spkrclose(dev) | |
496 | dev_t dev; | |
497 | { | |
498 | #ifdef DEBUG | |
60cfd54d | 499 | (void) printf("spkrclose: entering with dev = %x\n", dev); |
15637ed4 RG |
500 | #endif /* DEBUG */ |
501 | ||
502 | if (minor(dev) != 0) | |
503 | return(ENXIO); | |
504 | else | |
505 | { | |
f9417549 AC |
506 | wakeup((caddr_t)&endtone); |
507 | wakeup((caddr_t)&endrest); | |
15637ed4 | 508 | brelse(spkr_inbuf); |
60cfd54d AC |
509 | spkr_active = FALSE; |
510 | return(0); | |
15637ed4 | 511 | } |
15637ed4 RG |
512 | } |
513 | ||
514 | int spkrioctl(dev, cmd, cmdarg) | |
515 | dev_t dev; | |
516 | int cmd; | |
60cfd54d | 517 | caddr_t cmdarg; |
15637ed4 RG |
518 | { |
519 | #ifdef DEBUG | |
60cfd54d | 520 | (void) printf("spkrioctl: entering with dev = %x, cmd = %x\n"); |
15637ed4 RG |
521 | #endif /* DEBUG */ |
522 | ||
523 | if (minor(dev) != 0) | |
524 | return(ENXIO); | |
525 | else if (cmd == SPKRTONE) | |
526 | { | |
527 | tone_t *tp = (tone_t *)cmdarg; | |
528 | ||
529 | if (tp->frequency == 0) | |
f9417549 | 530 | return rest(tp->duration); |
15637ed4 | 531 | else |
f9417549 | 532 | return tone(tp->frequency, tp->duration); |
15637ed4 RG |
533 | } |
534 | else if (cmd == SPKRTUNE) | |
535 | { | |
536 | tone_t *tp = (tone_t *)(*(caddr_t *)cmdarg); | |
537 | tone_t ttp; | |
538 | int error; | |
539 | ||
540 | for (; ; tp++) { | |
541 | error = copyin(tp, &ttp, sizeof(tone_t)); | |
542 | if (error) | |
543 | return(error); | |
544 | if (ttp.duration == 0) | |
545 | break; | |
546 | if (ttp.frequency == 0) | |
f9417549 | 547 | error = rest(ttp.duration); |
15637ed4 | 548 | else |
f9417549 AC |
549 | error = tone(ttp.frequency, ttp.duration); |
550 | if (error) | |
551 | return(error); | |
15637ed4 | 552 | } |
60cfd54d | 553 | return(0); |
15637ed4 | 554 | } |
60cfd54d | 555 | return(EINVAL); |
15637ed4 RG |
556 | } |
557 | ||
558 | #endif /* NSPEAKER > 0 */ | |
559 | /* spkr.c ends here */ |