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