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