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