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