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.12 1995/03/16 18:12:05 bde 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/proc.h> 20 #include <sys/uio.h> 21 #include <i386/isa/isa.h> 22 #include <i386/isa/timerreg.h> 23 #include <machine/clock.h> 24 #include <machine/speaker.h> 25 26 /**************** MACHINE DEPENDENT PART STARTS HERE ************************* 27 * 28 * This section defines a function tone() which causes a tone of given 29 * frequency and duration from the 80x86's console speaker. 30 * Another function endtone() is defined to force sound off, and there is 31 * also a rest() entry point to do pauses. 32 * 33 * Audible sound is generated using the Programmable Interval Timer (PIT) and 34 * Programmable Peripheral Interface (PPI) attached to the 80x86's speaker. The 35 * PPI controls whether sound is passed through at all; the PIT's channel 2 is 36 * used to generate clicks (a square wave) of whatever frequency is desired. 37 */ 38 39 /* 40 * PIT and PPI port addresses and control values 41 * 42 * Most of the magic is hidden in the TIMER_PREP value, which selects PIT 43 * channel 2, frequency LSB first, square-wave mode and binary encoding. 44 * The encoding is as follows: 45 * 46 * +----------+----------+---------------+-----+ 47 * | 1 0 | 1 1 | 0 1 1 | 0 | 48 * | SC1 SC0 | RW1 RW0 | M2 M1 M0 | BCD | 49 * +----------+----------+---------------+-----+ 50 * Counter Write Mode 3 Binary 51 * Channel 2 LSB first, (Square Wave) Encoding 52 * MSB second 53 */ 54 #define PPI_SPKR 0x03 /* turn these PPI bits on to pass sound */ 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 62 #define SPKRPRI PSOCK 63 static char endtone, endrest; 64 65 static void tone(thz, ticks) 66 /* emit tone of frequency thz for given number of ticks */ 67 unsigned int thz, ticks; 68 { 69 unsigned int divisor; 70 int sps; 71 72 if (thz <= 0) 73 return; 74 75 divisor = TIMER_CLK / thz; 76 77 #ifdef DEBUG 78 (void) printf("tone: thz=%d ticks=%d\n", thz, ticks); 79 #endif /* DEBUG */ 80 81 /* set timer to generate clicks at given frequency in Hertz */ 82 sps = spltty(); 83 84 if (acquire_timer2(PIT_MODE)) { 85 /* enter list of waiting procs ??? */ 86 return; 87 } 88 outb(TIMER_CNTR2, (divisor & 0xff)); /* send lo byte */ 89 outb(TIMER_CNTR2, (divisor >> 8)); /* send hi byte */ 90 splx(sps); 91 92 /* turn the speaker on */ 93 outb(IO_PPI, inb(IO_PPI) | PPI_SPKR); 94 95 /* 96 * Set timeout to endtone function, then give up the timeslice. 97 * This is so other processes can execute while the tone is being 98 * emitted. 99 */ 100 if (ticks > 0) 101 tsleep((caddr_t)&endtone, SPKRPRI | PCATCH, "spkrtn", ticks); 102 outb(IO_PPI, inb(IO_PPI) & ~PPI_SPKR); 103 release_timer2(); 104 } 105 106 static void rest(ticks) 107 /* rest for given number of ticks */ 108 int ticks; 109 { 110 /* 111 * Set timeout to endrest function, then give up the timeslice. 112 * This is so other processes can execute while the rest is being 113 * waited out. 114 */ 115 #ifdef DEBUG 116 (void) printf("rest: %d\n", ticks); 117 #endif /* DEBUG */ 118 if (ticks > 0) 119 tsleep((caddr_t)&endrest, SPKRPRI | PCATCH, "spkrrs", ticks); 120 } 121 122 /**************** PLAY STRING INTERPRETER BEGINS HERE ********************** 123 * 124 * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement; 125 * M[LNS] are missing; the ~ synonym and the _ slur mark and the octave- 126 * tracking facility are added. 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; 188 whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO; 189 fill = NORMAL; 190 value = DFLT_VALUE; 191 octtrack = FALSE; 192 octprefix = TRUE; /* act as though there was an initial O(n) */ 193 } 194 195 static void playtone(pitch, value, sustain) 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; 200 201 /* this weirdness avoids floating-point arithmetic */ 202 for (; sustain; sustain--) 203 { 204 /* See the BUGS section in the man page for discussion */ 205 snum *= NUM_MULT; 206 sdenom *= DENOM_MULT; 207 } 208 209 if (value == 0 || sdenom == 0) 210 return; 211 212 if (pitch == -1) 213 rest(whole * snum / (value * sdenom)); 214 else 215 { 216 sound = (whole * snum) / (value * sdenom) 217 - (whole * (FILLTIME - fill)) / (value * FILLTIME); 218 silence = whole * (FILLTIME-fill) * snum / (FILLTIME * value * sdenom); 219 220 #ifdef DEBUG 221 (void) printf("playtone: pitch %d for %d ticks, rest for %d ticks\n", 222 pitch, sound, silence); 223 #endif /* DEBUG */ 224 225 tone(pitchtab[pitch], sound); 226 if (fill != LEGATO) 227 rest(silence); 228 } 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 240 static void playstring(cp, slen) 241 /* interpret and play an item from a notation string */ 242 char *cp; 243 size_t slen; 244 { 245 int pitch, oldfill, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE; 246 247 #define GETNUM(cp, v) for(v=0; isdigit(cp[1]) && slen > 0; ) \ 248 {v = v * 10 + (*++cp - '0'); slen--;} 249 for (; slen--; cp++) 250 { 251 int sustain, timeval, tempo; 252 register char c = toupper(*cp); 253 254 #ifdef DEBUG 255 (void) printf("playstring: %c (%x)\n", c, c); 256 #endif /* DEBUG */ 257 258 switch (c) 259 { 260 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': 261 262 /* compute pitch */ 263 pitch = notetab[c - 'A'] + octave * OCTAVE_NOTES; 264 265 /* this may be followed by an accidental sign */ 266 if (cp[1] == '#' || cp[1] == '+') 267 { 268 ++pitch; 269 ++cp; 270 slen--; 271 } 272 else if (cp[1] == '-') 273 { 274 --pitch; 275 ++cp; 276 slen--; 277 } 278 279 /* 280 * If octave-tracking mode is on, and there has been no octave- 281 * setting prefix, find the version of the current letter note 282 * closest to the last regardless of octave. 283 */ 284 if (octtrack && !octprefix) 285 { 286 if (abs(pitch-lastpitch) > abs(pitch+OCTAVE_NOTES-lastpitch)) 287 { 288 ++octave; 289 pitch += OCTAVE_NOTES; 290 } 291 292 if (abs(pitch-lastpitch) > abs((pitch-OCTAVE_NOTES)-lastpitch)) 293 { 294 --octave; 295 pitch -= OCTAVE_NOTES; 296 } 297 } 298 octprefix = FALSE; 299 lastpitch = pitch; 300 301 /* ...which may in turn be followed by an override time value */ 302 GETNUM(cp, timeval); 303 if (timeval <= 0 || timeval > MIN_VALUE) 304 timeval = value; 305 306 /* ...and/or sustain dots */ 307 for (sustain = 0; cp[1] == '.'; cp++) 308 { 309 slen--; 310 sustain++; 311 } 312 313 /* ...and/or a slur mark */ 314 oldfill = fill; 315 if (cp[1] == '_') 316 { 317 fill = LEGATO; 318 ++cp; 319 slen--; 320 } 321 322 /* time to emit the actual tone */ 323 playtone(pitch, timeval, sustain); 324 325 fill = oldfill; 326 break; 327 328 case 'O': 329 if (cp[1] == 'N' || cp[1] == 'n') 330 { 331 octprefix = octtrack = FALSE; 332 ++cp; 333 slen--; 334 } 335 else if (cp[1] == 'L' || cp[1] == 'l') 336 { 337 octtrack = TRUE; 338 ++cp; 339 slen--; 340 } 341 else 342 { 343 GETNUM(cp, octave); 344 if (octave >= sizeof(pitchtab) / sizeof(pitchtab[0]) / OCTAVE_NOTES) 345 octave = DFLT_OCTAVE; 346 octprefix = TRUE; 347 } 348 break; 349 350 case '>': 351 if (octave < sizeof(pitchtab) / sizeof(pitchtab[0]) / OCTAVE_NOTES - 1) 352 octave++; 353 octprefix = TRUE; 354 break; 355 356 case '<': 357 if (octave > 0) 358 octave--; 359 octprefix = TRUE; 360 break; 361 362 case 'N': 363 GETNUM(cp, pitch); 364 for (sustain = 0; cp[1] == '.'; cp++) 365 { 366 slen--; 367 sustain++; 368 } 369 oldfill = fill; 370 if (cp[1] == '_') 371 { 372 fill = LEGATO; 373 ++cp; 374 slen--; 375 } 376 playtone(pitch - 1, value, sustain); 377 fill = oldfill; 378 break; 379 380 case 'L': 381 GETNUM(cp, value); 382 if (value <= 0 || value > MIN_VALUE) 383 value = DFLT_VALUE; 384 break; 385 386 case 'P': 387 case '~': 388 /* this may be followed by an override time value */ 389 GETNUM(cp, timeval); 390 if (timeval <= 0 || timeval > MIN_VALUE) 391 timeval = value; 392 for (sustain = 0; cp[1] == '.'; cp++) 393 { 394 slen--; 395 sustain++; 396 } 397 playtone(-1, timeval, sustain); 398 break; 399 400 case 'T': 401 GETNUM(cp, tempo); 402 if (tempo < MIN_TEMPO || tempo > MAX_TEMPO) 403 tempo = DFLT_TEMPO; 404 whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / tempo; 405 break; 406 407 case 'M': 408 if (cp[1] == 'N' || cp[1] == 'n') 409 { 410 fill = NORMAL; 411 ++cp; 412 slen--; 413 } 414 else if (cp[1] == 'L' || cp[1] == 'l') 415 { 416 fill = LEGATO; 417 ++cp; 418 slen--; 419 } 420 else if (cp[1] == 'S' || cp[1] == 's') 421 { 422 fill = STACCATO; 423 ++cp; 424 slen--; 425 } 426 break; 427 } 428 } 429 } 430 431 /******************* UNIX DRIVER HOOKS BEGIN HERE ************************** 432 * 433 * This section implements driver hooks to run playstring() and the tone(), 434 * endtone(), and rest() functions defined above. 435 */ 436 437 static int spkr_active = FALSE; /* exclusion flag */ 438 static struct buf *spkr_inbuf; /* incoming buf */ 439 440 int spkropen(dev) 441 dev_t dev; 442 { 443 #ifdef DEBUG 444 (void) printf("spkropen: entering with dev = %x\n", dev); 445 #endif /* DEBUG */ 446 447 if (minor(dev) != 0) 448 return(ENXIO); 449 else if (spkr_active) 450 return(EBUSY); 451 else 452 { 453 #ifdef DEBUG 454 (void) printf("spkropen: about to perform play initialization\n"); 455 #endif /* DEBUG */ 456 playinit(); 457 spkr_inbuf = geteblk(DEV_BSIZE); 458 spkr_active = TRUE; 459 return(0); 460 } 461 } 462 463 int spkrwrite(dev, uio) 464 dev_t dev; 465 struct uio *uio; 466 { 467 #ifdef DEBUG 468 printf("spkrwrite: entering with dev = %x, count = %d\n", 469 dev, uio->uio_resid); 470 #endif /* DEBUG */ 471 472 if (minor(dev) != 0) 473 return(ENXIO); 474 else if (uio->uio_resid > DEV_BSIZE) /* prevent system crashes */ 475 return(E2BIG); 476 else 477 { 478 unsigned n; 479 char *cp; 480 int error; 481 482 n = uio->uio_resid; 483 cp = spkr_inbuf->b_un.b_addr; 484 if (!(error = uiomove(cp, n, uio))) 485 playstring(cp, n); 486 return(error); 487 } 488 } 489 490 int spkrclose(dev) 491 dev_t dev; 492 { 493 #ifdef DEBUG 494 (void) printf("spkrclose: entering with dev = %x\n", dev); 495 #endif /* DEBUG */ 496 497 if (minor(dev) != 0) 498 return(ENXIO); 499 else 500 { 501 wakeup((caddr_t)&endtone); 502 wakeup((caddr_t)&endrest); 503 brelse(spkr_inbuf); 504 spkr_active = FALSE; 505 return(0); 506 } 507 } 508 509 int spkrioctl(dev, cmd, cmdarg) 510 dev_t dev; 511 int cmd; 512 caddr_t cmdarg; 513 { 514 #ifdef DEBUG 515 (void) printf("spkrioctl: entering with dev = %x, cmd = %x\n"); 516 #endif /* DEBUG */ 517 518 if (minor(dev) != 0) 519 return(ENXIO); 520 else if (cmd == SPKRTONE) 521 { 522 tone_t *tp = (tone_t *)cmdarg; 523 524 if (tp->frequency == 0) 525 rest(tp->duration); 526 else 527 tone(tp->frequency, tp->duration); 528 return 0; 529 } 530 else if (cmd == SPKRTUNE) 531 { 532 tone_t *tp = (tone_t *)(*(caddr_t *)cmdarg); 533 tone_t ttp; 534 int error; 535 536 for (; ; tp++) { 537 error = copyin(tp, &ttp, sizeof(tone_t)); 538 if (error) 539 return(error); 540 if (ttp.duration == 0) 541 break; 542 if (ttp.frequency == 0) 543 rest(ttp.duration); 544 else 545 tone(ttp.frequency, ttp.duration); 546 } 547 return(0); 548 } 549 return(EINVAL); 550 } 551 552 #endif /* NSPEAKER > 0 */ 553 /* spkr.c ends here */ 554