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 * $FreeBSD$ 8 */ 9 10 #include <sys/param.h> 11 #include <sys/systm.h> 12 #include <sys/bus.h> 13 #include <sys/kernel.h> 14 #include <sys/module.h> 15 #include <sys/uio.h> 16 #include <sys/conf.h> 17 #include <sys/ctype.h> 18 #include <sys/malloc.h> 19 #include <isa/isavar.h> 20 #include <i386/isa/isa.h> 21 #include <i386/isa/timerreg.h> 22 #include <machine/clock.h> 23 #include <machine/speaker.h> 24 25 static d_open_t spkropen; 26 static d_close_t spkrclose; 27 static d_write_t spkrwrite; 28 static d_ioctl_t spkrioctl; 29 30 #define CDEV_MAJOR 26 31 static struct cdevsw spkr_cdevsw = { 32 /* open */ spkropen, 33 /* close */ spkrclose, 34 /* read */ noread, 35 /* write */ spkrwrite, 36 /* ioctl */ spkrioctl, 37 /* poll */ nopoll, 38 /* mmap */ nommap, 39 /* strategy */ nostrategy, 40 /* name */ "spkr", 41 /* maj */ CDEV_MAJOR, 42 /* dump */ nodump, 43 /* psize */ nopsize, 44 /* flags */ 0, 45 /* bmaj */ -1 46 }; 47 48 MALLOC_DEFINE(M_SPKR, "spkr", "Speaker buffer"); 49 50 /**************** MACHINE DEPENDENT PART STARTS HERE ************************* 51 * 52 * This section defines a function tone() which causes a tone of given 53 * frequency and duration from the ISA console speaker. 54 * Another function endtone() is defined to force sound off, and there is 55 * also a rest() entry point to do pauses. 56 * 57 * Audible sound is generated using the Programmable Interval Timer (PIT) and 58 * Programmable Peripheral Interface (PPI) attached to the ISA speaker. The 59 * PPI controls whether sound is passed through at all; the PIT's channel 2 is 60 * used to generate clicks (a square wave) of whatever frequency is desired. 61 */ 62 63 /* 64 * PPI control values. 65 * XXX should be in a header and used in clock.c. 66 */ 67 #define PPI_SPKR 0x03 /* turn these PPI bits on to pass sound */ 68 69 #define SPKRPRI PSOCK 70 static char endtone, endrest; 71 72 static void tone __P((unsigned int thz, unsigned int ticks)); 73 static void rest __P((int ticks)); 74 static void playinit __P((void)); 75 static void playtone __P((int pitch, int value, int sustain)); 76 static int abs __P((int n)); 77 static void playstring __P((char *cp, size_t slen)); 78 79 /* emit tone of frequency thz for given number of ticks */ 80 static void 81 tone(thz, ticks) 82 unsigned int thz, ticks; 83 { 84 unsigned int divisor; 85 int sps; 86 87 if (thz <= 0) 88 return; 89 90 divisor = timer_freq / thz; 91 92 #ifdef DEBUG 93 (void) printf("tone: thz=%d ticks=%d\n", thz, ticks); 94 #endif /* DEBUG */ 95 96 /* set timer to generate clicks at given frequency in Hertz */ 97 sps = splclock(); 98 99 if (acquire_timer2(TIMER_SEL2 | TIMER_SQWAVE | TIMER_16BIT)) { 100 /* enter list of waiting procs ??? */ 101 splx(sps); 102 return; 103 } 104 splx(sps); 105 disable_intr(); 106 outb(TIMER_CNTR2, (divisor & 0xff)); /* send lo byte */ 107 outb(TIMER_CNTR2, (divisor >> 8)); /* send hi byte */ 108 enable_intr(); 109 110 /* turn the speaker on */ 111 outb(IO_PPI, inb(IO_PPI) | PPI_SPKR); 112 113 /* 114 * Set timeout to endtone function, then give up the timeslice. 115 * This is so other processes can execute while the tone is being 116 * emitted. 117 */ 118 if (ticks > 0) 119 tsleep((caddr_t)&endtone, SPKRPRI | PCATCH, "spkrtn", ticks); 120 outb(IO_PPI, inb(IO_PPI) & ~PPI_SPKR); 121 sps = splclock(); 122 release_timer2(); 123 splx(sps); 124 } 125 126 /* rest for given number of ticks */ 127 static void 128 rest(ticks) 129 int ticks; 130 { 131 /* 132 * Set timeout to endrest function, then give up the timeslice. 133 * This is so other processes can execute while the rest is being 134 * waited out. 135 */ 136 #ifdef DEBUG 137 (void) printf("rest: %d\n", ticks); 138 #endif /* DEBUG */ 139 if (ticks > 0) 140 tsleep((caddr_t)&endrest, SPKRPRI | PCATCH, "spkrrs", ticks); 141 } 142 143 /**************** PLAY STRING INTERPRETER BEGINS HERE ********************** 144 * 145 * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement; 146 * M[LNS] are missing; the ~ synonym and the _ slur mark and the octave- 147 * tracking facility are added. 148 * Requires tone(), rest(), and endtone(). String play is not interruptible 149 * except possibly at physical block boundaries. 150 */ 151 152 typedef int bool; 153 #define TRUE 1 154 #define FALSE 0 155 156 #define dtoi(c) ((c) - '0') 157 158 static int octave; /* currently selected octave */ 159 static int whole; /* whole-note time at current tempo, in ticks */ 160 static int value; /* whole divisor for note time, quarter note = 1 */ 161 static int fill; /* controls spacing of notes */ 162 static bool octtrack; /* octave-tracking on? */ 163 static bool octprefix; /* override current octave-tracking state? */ 164 165 /* 166 * Magic number avoidance... 167 */ 168 #define SECS_PER_MIN 60 /* seconds per minute */ 169 #define WHOLE_NOTE 4 /* quarter notes per whole note */ 170 #define MIN_VALUE 64 /* the most we can divide a note by */ 171 #define DFLT_VALUE 4 /* default value (quarter-note) */ 172 #define FILLTIME 8 /* for articulation, break note in parts */ 173 #define STACCATO 6 /* 6/8 = 3/4 of note is filled */ 174 #define NORMAL 7 /* 7/8ths of note interval is filled */ 175 #define LEGATO 8 /* all of note interval is filled */ 176 #define DFLT_OCTAVE 4 /* default octave */ 177 #define MIN_TEMPO 32 /* minimum tempo */ 178 #define DFLT_TEMPO 120 /* default tempo */ 179 #define MAX_TEMPO 255 /* max tempo */ 180 #define NUM_MULT 3 /* numerator of dot multiplier */ 181 #define DENOM_MULT 2 /* denominator of dot multiplier */ 182 183 /* letter to half-tone: A B C D E F G */ 184 static int notetab[8] = {9, 11, 0, 2, 4, 5, 7}; 185 186 /* 187 * This is the American Standard A440 Equal-Tempered scale with frequencies 188 * rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook... 189 * our octave 0 is standard octave 2. 190 */ 191 #define OCTAVE_NOTES 12 /* semitones per octave */ 192 static int pitchtab[] = 193 { 194 /* C C# D D# E F F# G G# A A# B*/ 195 /* 0 */ 65, 69, 73, 78, 82, 87, 93, 98, 103, 110, 117, 123, 196 /* 1 */ 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, 197 /* 2 */ 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, 198 /* 3 */ 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 199 /* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975, 200 /* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, 201 /* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902, 202 }; 203 204 static void 205 playinit() 206 { 207 octave = DFLT_OCTAVE; 208 whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO; 209 fill = NORMAL; 210 value = DFLT_VALUE; 211 octtrack = FALSE; 212 octprefix = TRUE; /* act as though there was an initial O(n) */ 213 } 214 215 /* play tone of proper duration for current rhythm signature */ 216 static void 217 playtone(pitch, value, sustain) 218 int pitch, value, sustain; 219 { 220 register int sound, silence, snum = 1, sdenom = 1; 221 222 /* this weirdness avoids floating-point arithmetic */ 223 for (; sustain; sustain--) 224 { 225 /* See the BUGS section in the man page for discussion */ 226 snum *= NUM_MULT; 227 sdenom *= DENOM_MULT; 228 } 229 230 if (value == 0 || sdenom == 0) 231 return; 232 233 if (pitch == -1) 234 rest(whole * snum / (value * sdenom)); 235 else 236 { 237 sound = (whole * snum) / (value * sdenom) 238 - (whole * (FILLTIME - fill)) / (value * FILLTIME); 239 silence = whole * (FILLTIME-fill) * snum / (FILLTIME * value * sdenom); 240 241 #ifdef DEBUG 242 (void) printf("playtone: pitch %d for %d ticks, rest for %d ticks\n", 243 pitch, sound, silence); 244 #endif /* DEBUG */ 245 246 tone(pitchtab[pitch], sound); 247 if (fill != LEGATO) 248 rest(silence); 249 } 250 } 251 252 static int 253 abs(n) 254 int n; 255 { 256 if (n < 0) 257 return(-n); 258 else 259 return(n); 260 } 261 262 /* interpret and play an item from a notation string */ 263 static void 264 playstring(cp, slen) 265 char *cp; 266 size_t slen; 267 { 268 int pitch, oldfill, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE; 269 270 #define GETNUM(cp, v) for(v=0; isdigit(cp[1]) && slen > 0; ) \ 271 {v = v * 10 + (*++cp - '0'); slen--;} 272 for (; slen--; cp++) 273 { 274 int sustain, timeval, tempo; 275 register char c = toupper(*cp); 276 277 #ifdef DEBUG 278 (void) printf("playstring: %c (%x)\n", c, c); 279 #endif /* DEBUG */ 280 281 switch (c) 282 { 283 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': 284 285 /* compute pitch */ 286 pitch = notetab[c - 'A'] + octave * OCTAVE_NOTES; 287 288 /* this may be followed by an accidental sign */ 289 if (cp[1] == '#' || cp[1] == '+') 290 { 291 ++pitch; 292 ++cp; 293 slen--; 294 } 295 else if (cp[1] == '-') 296 { 297 --pitch; 298 ++cp; 299 slen--; 300 } 301 302 /* 303 * If octave-tracking mode is on, and there has been no octave- 304 * setting prefix, find the version of the current letter note 305 * closest to the last regardless of octave. 306 */ 307 if (octtrack && !octprefix) 308 { 309 if (abs(pitch-lastpitch) > abs(pitch+OCTAVE_NOTES-lastpitch)) 310 { 311 ++octave; 312 pitch += OCTAVE_NOTES; 313 } 314 315 if (abs(pitch-lastpitch) > abs((pitch-OCTAVE_NOTES)-lastpitch)) 316 { 317 --octave; 318 pitch -= OCTAVE_NOTES; 319 } 320 } 321 octprefix = FALSE; 322 lastpitch = pitch; 323 324 /* ...which may in turn be followed by an override time value */ 325 GETNUM(cp, timeval); 326 if (timeval <= 0 || timeval > MIN_VALUE) 327 timeval = value; 328 329 /* ...and/or sustain dots */ 330 for (sustain = 0; cp[1] == '.'; cp++) 331 { 332 slen--; 333 sustain++; 334 } 335 336 /* ...and/or a slur mark */ 337 oldfill = fill; 338 if (cp[1] == '_') 339 { 340 fill = LEGATO; 341 ++cp; 342 slen--; 343 } 344 345 /* time to emit the actual tone */ 346 playtone(pitch, timeval, sustain); 347 348 fill = oldfill; 349 break; 350 351 case 'O': 352 if (cp[1] == 'N' || cp[1] == 'n') 353 { 354 octprefix = octtrack = FALSE; 355 ++cp; 356 slen--; 357 } 358 else if (cp[1] == 'L' || cp[1] == 'l') 359 { 360 octtrack = TRUE; 361 ++cp; 362 slen--; 363 } 364 else 365 { 366 GETNUM(cp, octave); 367 if (octave >= sizeof(pitchtab) / sizeof(pitchtab[0]) / OCTAVE_NOTES) 368 octave = DFLT_OCTAVE; 369 octprefix = TRUE; 370 } 371 break; 372 373 case '>': 374 if (octave < sizeof(pitchtab) / sizeof(pitchtab[0]) / OCTAVE_NOTES - 1) 375 octave++; 376 octprefix = TRUE; 377 break; 378 379 case '<': 380 if (octave > 0) 381 octave--; 382 octprefix = TRUE; 383 break; 384 385 case 'N': 386 GETNUM(cp, pitch); 387 for (sustain = 0; cp[1] == '.'; cp++) 388 { 389 slen--; 390 sustain++; 391 } 392 oldfill = fill; 393 if (cp[1] == '_') 394 { 395 fill = LEGATO; 396 ++cp; 397 slen--; 398 } 399 playtone(pitch - 1, value, sustain); 400 fill = oldfill; 401 break; 402 403 case 'L': 404 GETNUM(cp, value); 405 if (value <= 0 || value > MIN_VALUE) 406 value = DFLT_VALUE; 407 break; 408 409 case 'P': 410 case '~': 411 /* this may be followed by an override time value */ 412 GETNUM(cp, timeval); 413 if (timeval <= 0 || timeval > MIN_VALUE) 414 timeval = value; 415 for (sustain = 0; cp[1] == '.'; cp++) 416 { 417 slen--; 418 sustain++; 419 } 420 playtone(-1, timeval, sustain); 421 break; 422 423 case 'T': 424 GETNUM(cp, tempo); 425 if (tempo < MIN_TEMPO || tempo > MAX_TEMPO) 426 tempo = DFLT_TEMPO; 427 whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / tempo; 428 break; 429 430 case 'M': 431 if (cp[1] == 'N' || cp[1] == 'n') 432 { 433 fill = NORMAL; 434 ++cp; 435 slen--; 436 } 437 else if (cp[1] == 'L' || cp[1] == 'l') 438 { 439 fill = LEGATO; 440 ++cp; 441 slen--; 442 } 443 else if (cp[1] == 'S' || cp[1] == 's') 444 { 445 fill = STACCATO; 446 ++cp; 447 slen--; 448 } 449 break; 450 } 451 } 452 } 453 454 /******************* UNIX DRIVER HOOKS BEGIN HERE ************************** 455 * 456 * This section implements driver hooks to run playstring() and the tone(), 457 * endtone(), and rest() functions defined above. 458 */ 459 460 static int spkr_active = FALSE; /* exclusion flag */ 461 static char *spkr_inbuf; /* incoming buf */ 462 463 int 464 spkropen(dev, flags, fmt, p) 465 dev_t dev; 466 int flags; 467 int fmt; 468 struct proc *p; 469 { 470 #ifdef DEBUG 471 (void) printf("spkropen: entering with dev = %s\n", devtoname(dev)); 472 #endif /* DEBUG */ 473 474 if (minor(dev) != 0) 475 return(ENXIO); 476 else if (spkr_active) 477 return(EBUSY); 478 else 479 { 480 #ifdef DEBUG 481 (void) printf("spkropen: about to perform play initialization\n"); 482 #endif /* DEBUG */ 483 playinit(); 484 spkr_inbuf = malloc(DEV_BSIZE, M_SPKR, M_WAITOK); 485 spkr_active = TRUE; 486 return(0); 487 } 488 } 489 490 int 491 spkrwrite(dev, uio, ioflag) 492 dev_t dev; 493 struct uio *uio; 494 int ioflag; 495 { 496 #ifdef DEBUG 497 printf("spkrwrite: entering with dev = %s, count = %d\n", 498 devtoname(dev), uio->uio_resid); 499 #endif /* DEBUG */ 500 501 if (minor(dev) != 0) 502 return(ENXIO); 503 else if (uio->uio_resid > (DEV_BSIZE - 1)) /* prevent system crashes */ 504 return(E2BIG); 505 else 506 { 507 unsigned n; 508 char *cp; 509 int error; 510 511 n = uio->uio_resid; 512 cp = spkr_inbuf; 513 error = uiomove(cp, n, uio); 514 if (!error) { 515 cp[n] = '\0'; 516 playstring(cp, n); 517 } 518 return(error); 519 } 520 } 521 522 int 523 spkrclose(dev, flags, fmt, p) 524 dev_t dev; 525 int flags; 526 int fmt; 527 struct proc *p; 528 { 529 #ifdef DEBUG 530 (void) printf("spkrclose: entering with dev = %s\n", devtoname(dev)); 531 #endif /* DEBUG */ 532 533 if (minor(dev) != 0) 534 return(ENXIO); 535 else 536 { 537 wakeup((caddr_t)&endtone); 538 wakeup((caddr_t)&endrest); 539 free(spkr_inbuf, M_SPKR); 540 spkr_active = FALSE; 541 return(0); 542 } 543 } 544 545 int 546 spkrioctl(dev, cmd, cmdarg, flags, p) 547 dev_t dev; 548 unsigned long cmd; 549 caddr_t cmdarg; 550 int flags; 551 struct proc *p; 552 { 553 #ifdef DEBUG 554 (void) printf("spkrioctl: entering with dev = %s, cmd = %lx\n", 555 devtoname(dev), cmd); 556 #endif /* DEBUG */ 557 558 if (minor(dev) != 0) 559 return(ENXIO); 560 else if (cmd == SPKRTONE) 561 { 562 tone_t *tp = (tone_t *)cmdarg; 563 564 if (tp->frequency == 0) 565 rest(tp->duration); 566 else 567 tone(tp->frequency, tp->duration); 568 return 0; 569 } 570 else if (cmd == SPKRTUNE) 571 { 572 tone_t *tp = (tone_t *)(*(caddr_t *)cmdarg); 573 tone_t ttp; 574 int error; 575 576 for (; ; tp++) { 577 error = copyin(tp, &ttp, sizeof(tone_t)); 578 if (error) 579 return(error); 580 if (ttp.duration == 0) 581 break; 582 if (ttp.frequency == 0) 583 rest(ttp.duration); 584 else 585 tone(ttp.frequency, ttp.duration); 586 } 587 return(0); 588 } 589 return(EINVAL); 590 } 591 592 static void 593 spkr_drvinit(void *unused) 594 { 595 make_dev(&spkr_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "speaker"); 596 } 597 598 SYSINIT(spkrdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,spkr_drvinit,NULL) 599 600 /* 601 * Install placeholder to claim the resources owned by the 602 * AT tone generator. 603 */ 604 static struct isa_pnp_id atspeaker_ids[] = { 605 { 0x0008d041 /* PNP0800 */, "AT speaker" }, 606 { 0 } 607 }; 608 609 static int 610 atspeaker_probe(device_t dev) 611 { 612 return(ISA_PNP_PROBE(device_get_parent(dev), dev, atspeaker_ids)); 613 } 614 615 static int 616 atspeaker_attach(device_t dev) 617 { 618 return(0); 619 } 620 621 static device_method_t atspeaker_methods[] = { 622 /* Device interface */ 623 DEVMETHOD(device_probe, atspeaker_probe), 624 DEVMETHOD(device_attach, atspeaker_attach), 625 DEVMETHOD(device_detach, bus_generic_detach), 626 DEVMETHOD(device_shutdown, bus_generic_shutdown), 627 DEVMETHOD(device_suspend, bus_generic_suspend), 628 DEVMETHOD(device_resume, bus_generic_resume), 629 { 0, 0 } 630 }; 631 632 static driver_t atspeaker_driver = { 633 "atspeaker", 634 atspeaker_methods, 635 1, /* no softc */ 636 }; 637 638 static devclass_t atspeaker_devclass; 639 640 DRIVER_MODULE(atspeaker, isa, atspeaker_driver, atspeaker_devclass, 0, 0); 641 642 /* spkr.c ends here */ 643