spkr.c revision 8288
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 63static char endtone, endrest; 64 65static void tone(thz, ticks) 66/* emit tone of frequency thz for given number of ticks */ 67unsigned 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 106static void rest(ticks) 107/* rest for given number of ticks */ 108int 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 131typedef 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 139static int octave; /* currently selected octave */ 140static int whole; /* whole-note time at current tempo, in ticks */ 141static int value; /* whole divisor for note time, quarter note = 1 */ 142static int fill; /* controls spacing of notes */ 143static bool octtrack; /* octave-tracking on? */ 144static 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 */ 165static 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 */ 173static 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 185static 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 195static void playtone(pitch, value, sustain) 196/* play tone of proper duration for current rhythm signature */ 197int 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 231static int abs(n) 232int n; 233{ 234 if (n < 0) 235 return(-n); 236 else 237 return(n); 238} 239 240static void playstring(cp, slen) 241/* interpret and play an item from a notation string */ 242char *cp; 243size_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 437static int spkr_active = FALSE; /* exclusion flag */ 438static struct buf *spkr_inbuf; /* incoming buf */ 439 440int spkropen(dev) 441dev_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 463int spkrwrite(dev, uio) 464dev_t dev; 465struct 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 490int spkrclose(dev) 491dev_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 509int spkrioctl(dev, cmd, cmdarg) 510dev_t dev; 511int cmd; 512caddr_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