spkr.c revision 49982
11573Srgrimes/* 214272Spst * spkr.c -- device driver for console speaker 31573Srgrimes * 41573Srgrimes * v1.4 by Eric S. Raymond (esr@snark.thyrsus.com) Aug 1993 51573Srgrimes * modified for FreeBSD by Andrew A. Chernov <ache@astral.msk.su> 61573Srgrimes * 71573Srgrimes * $Id: spkr.c,v 1.37 1999/05/31 11:26:32 phk Exp $ 81573Srgrimes */ 91573Srgrimes 101573Srgrimes#include "speaker.h" 111573Srgrimes 121573Srgrimes#if NSPEAKER > 0 131573Srgrimes 141573Srgrimes#include "opt_devfs.h" 151573Srgrimes 161573Srgrimes#include <sys/param.h> 171573Srgrimes#include <sys/systm.h> 181573Srgrimes#include <sys/kernel.h> 191573Srgrimes#include <sys/buf.h> 201573Srgrimes#include <sys/uio.h> 211573Srgrimes#include <sys/conf.h> 221573Srgrimes#include <i386/isa/isa.h> 231573Srgrimes#include <i386/isa/timerreg.h> 241573Srgrimes#include <machine/clock.h> 251573Srgrimes#include <machine/speaker.h> 261573Srgrimes 271573Srgrimes#ifdef DEVFS 281573Srgrimes#include <sys/devfsext.h> 291573Srgrimesstatic void *devfs_token; 301573Srgrimes#endif 3114272Spst 321573Srgrimesstatic d_open_t spkropen; 3392986Sobrienstatic d_close_t spkrclose; 3492986Sobrienstatic d_write_t spkrwrite; 351573Srgrimesstatic d_ioctl_t spkrioctl; 3671579Sdeischen 371573Srgrimes#define CDEV_MAJOR 26 381573Srgrimesstatic struct cdevsw spkr_cdevsw = { 391573Srgrimes /* open */ spkropen, 401573Srgrimes /* close */ spkrclose, 411573Srgrimes /* read */ noread, 421573Srgrimes /* write */ spkrwrite, 431573Srgrimes /* ioctl */ spkrioctl, 441573Srgrimes /* stop */ nostop, 4571579Sdeischen /* reset */ noreset, 461573Srgrimes /* devtotty */ nodevtotty, 471573Srgrimes /* poll */ nopoll, 481573Srgrimes /* mmap */ nommap, 491573Srgrimes /* strategy */ nostrategy, 501573Srgrimes /* name */ "spkr", 511573Srgrimes /* parms */ noparms, 521573Srgrimes /* maj */ CDEV_MAJOR, 531573Srgrimes /* dump */ nodump, 541573Srgrimes /* psize */ nopsize, 551573Srgrimes /* flags */ 0, 561573Srgrimes /* maxio */ 0, 571573Srgrimes /* bmaj */ -1 581573Srgrimes}; 591573Srgrimes 60189291Sdelphij/**************** MACHINE DEPENDENT PART STARTS HERE ************************* 611573Srgrimes * 621573Srgrimes * This section defines a function tone() which causes a tone of given 631573Srgrimes * frequency and duration from the ISA console speaker. 641573Srgrimes * Another function endtone() is defined to force sound off, and there is 651573Srgrimes * also a rest() entry point to do pauses. 661573Srgrimes * 671573Srgrimes * Audible sound is generated using the Programmable Interval Timer (PIT) and 681573Srgrimes * Programmable Peripheral Interface (PPI) attached to the ISA speaker. The 691573Srgrimes * PPI controls whether sound is passed through at all; the PIT's channel 2 is 701573Srgrimes * used to generate clicks (a square wave) of whatever frequency is desired. 711573Srgrimes */ 721573Srgrimes 731573Srgrimes/* 741573Srgrimes * PPI control values. 751573Srgrimes * XXX should be in a header and used in clock.c. 761573Srgrimes */ 771573Srgrimes#define PPI_SPKR 0x03 /* turn these PPI bits on to pass sound */ 7814272Spst 791573Srgrimes#define SPKRPRI PSOCK 801573Srgrimesstatic char endtone, endrest; 81111010Snectar 8214272Spststatic void tone __P((unsigned int thz, unsigned int ticks)); 831573Srgrimesstatic void rest __P((int ticks)); 841573Srgrimesstatic void playinit __P((void)); 85189327Sdelphijstatic void playtone __P((int pitch, int value, int sustain)); 8656698Sjasonestatic int abs __P((int n)); 871573Srgrimesstatic void playstring __P((char *cp, size_t slen)); 88189327Sdelphij 89111010Snectar/* emit tone of frequency thz for given number of ticks */ 901573Srgrimesstatic void 911573Srgrimestone(thz, ticks) 921573Srgrimes unsigned int thz, ticks; 931573Srgrimes{ 941573Srgrimes unsigned int divisor; 951573Srgrimes int sps; 961573Srgrimes 971573Srgrimes if (thz <= 0) 981573Srgrimes return; 991573Srgrimes 1001573Srgrimes divisor = timer_freq / thz; 1011573Srgrimes 1021573Srgrimes#ifdef DEBUG 1031573Srgrimes (void) printf("tone: thz=%d ticks=%d\n", thz, ticks); 1041573Srgrimes#endif /* DEBUG */ 1051573Srgrimes 1061573Srgrimes /* set timer to generate clicks at given frequency in Hertz */ 107189291Sdelphij sps = splclock(); 1081573Srgrimes 1091573Srgrimes if (acquire_timer2(TIMER_SEL2 | TIMER_SQWAVE | TIMER_16BIT)) { 1101573Srgrimes /* enter list of waiting procs ??? */ 1111573Srgrimes splx(sps); 1121573Srgrimes return; 1131573Srgrimes } 1141573Srgrimes splx(sps); 1151573Srgrimes disable_intr(); 1161573Srgrimes outb(TIMER_CNTR2, (divisor & 0xff)); /* send lo byte */ 1171573Srgrimes outb(TIMER_CNTR2, (divisor >> 8)); /* send hi byte */ 1181573Srgrimes enable_intr(); 1191573Srgrimes 1201573Srgrimes /* turn the speaker on */ 1211573Srgrimes outb(IO_PPI, inb(IO_PPI) | PPI_SPKR); 1221573Srgrimes 1231573Srgrimes /* 1241573Srgrimes * Set timeout to endtone function, then give up the timeslice. 1251573Srgrimes * This is so other processes can execute while the tone is being 1261573Srgrimes * emitted. 12714272Spst */ 1281573Srgrimes if (ticks > 0) 1291573Srgrimes tsleep((caddr_t)&endtone, SPKRPRI | PCATCH, "spkrtn", ticks); 1301573Srgrimes outb(IO_PPI, inb(IO_PPI) & ~PPI_SPKR); 13114272Spst sps = splclock(); 1321573Srgrimes release_timer2(); 1331573Srgrimes splx(sps); 1341573Srgrimes} 1351573Srgrimes 1361573Srgrimes/* rest for given number of ticks */ 1371573Srgrimesstatic void 13814272Spstrest(ticks) 13914272Spst int ticks; 1401573Srgrimes{ 1411573Srgrimes /* 1421573Srgrimes * Set timeout to endrest function, then give up the timeslice. 1431573Srgrimes * This is so other processes can execute while the rest is being 14414272Spst * waited out. 14514272Spst */ 14614272Spst#ifdef DEBUG 14714272Spst (void) printf("rest: %d\n", ticks); 14814272Spst#endif /* DEBUG */ 14914272Spst if (ticks > 0) 15014272Spst tsleep((caddr_t)&endrest, SPKRPRI | PCATCH, "spkrrs", ticks); 15114272Spst} 15256698Sjasone 153190484Sdelphij/**************** PLAY STRING INTERPRETER BEGINS HERE ********************** 15414272Spst * 15514272Spst * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement; 15614272Spst * M[LNS] are missing; the ~ synonym and the _ slur mark and the octave- 15714272Spst * tracking facility are added. 158189327Sdelphij * Requires tone(), rest(), and endtone(). String play is not interruptible 15914272Spst * except possibly at physical block boundaries. 16014272Spst */ 16114272Spst 16214272Spsttypedef int bool; 16314272Spst#define TRUE 1 16414272Spst#define FALSE 0 165190484Sdelphij 16614272Spst#define toupper(c) ((c) - ' ' * (((c) >= 'a') && ((c) <= 'z'))) 16714272Spst#define isdigit(c) (((c) >= '0') && ((c) <= '9')) 16814272Spst#define dtoi(c) ((c) - '0') 16914272Spst 17014272Spststatic int octave; /* currently selected octave */ 17114272Spststatic int whole; /* whole-note time at current tempo, in ticks */ 17214272Spststatic int value; /* whole divisor for note time, quarter note = 1 */ 17314272Spststatic int fill; /* controls spacing of notes */ 1741573Srgrimesstatic bool octtrack; /* octave-tracking on? */ 1751573Srgrimesstatic bool octprefix; /* override current octave-tracking state? */ 1761573Srgrimes 1771573Srgrimes/* 1781573Srgrimes * Magic number avoidance... 1791573Srgrimes */ 18014272Spst#define SECS_PER_MIN 60 /* seconds per minute */ 1811573Srgrimes#define WHOLE_NOTE 4 /* quarter notes per whole note */ 1821573Srgrimes#define MIN_VALUE 64 /* the most we can divide a note by */ 183#define DFLT_VALUE 4 /* default value (quarter-note) */ 184#define FILLTIME 8 /* for articulation, break note in parts */ 185#define STACCATO 6 /* 6/8 = 3/4 of note is filled */ 186#define NORMAL 7 /* 7/8ths of note interval is filled */ 187#define LEGATO 8 /* all of note interval is filled */ 188#define DFLT_OCTAVE 4 /* default octave */ 189#define MIN_TEMPO 32 /* minimum tempo */ 190#define DFLT_TEMPO 120 /* default tempo */ 191#define MAX_TEMPO 255 /* max tempo */ 192#define NUM_MULT 3 /* numerator of dot multiplier */ 193#define DENOM_MULT 2 /* denominator of dot multiplier */ 194 195/* letter to half-tone: A B C D E F G */ 196static int notetab[8] = {9, 11, 0, 2, 4, 5, 7}; 197 198/* 199 * This is the American Standard A440 Equal-Tempered scale with frequencies 200 * rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook... 201 * our octave 0 is standard octave 2. 202 */ 203#define OCTAVE_NOTES 12 /* semitones per octave */ 204static int pitchtab[] = 205{ 206/* C C# D D# E F F# G G# A A# B*/ 207/* 0 */ 65, 69, 73, 78, 82, 87, 93, 98, 103, 110, 117, 123, 208/* 1 */ 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, 209/* 2 */ 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, 210/* 3 */ 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 211/* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975, 212/* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, 213/* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902, 214}; 215 216static void 217playinit() 218{ 219 octave = DFLT_OCTAVE; 220 whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO; 221 fill = NORMAL; 222 value = DFLT_VALUE; 223 octtrack = FALSE; 224 octprefix = TRUE; /* act as though there was an initial O(n) */ 225} 226 227/* play tone of proper duration for current rhythm signature */ 228static void 229playtone(pitch, value, sustain) 230 int pitch, value, sustain; 231{ 232 register int sound, silence, snum = 1, sdenom = 1; 233 234 /* this weirdness avoids floating-point arithmetic */ 235 for (; sustain; sustain--) 236 { 237 /* See the BUGS section in the man page for discussion */ 238 snum *= NUM_MULT; 239 sdenom *= DENOM_MULT; 240 } 241 242 if (value == 0 || sdenom == 0) 243 return; 244 245 if (pitch == -1) 246 rest(whole * snum / (value * sdenom)); 247 else 248 { 249 sound = (whole * snum) / (value * sdenom) 250 - (whole * (FILLTIME - fill)) / (value * FILLTIME); 251 silence = whole * (FILLTIME-fill) * snum / (FILLTIME * value * sdenom); 252 253#ifdef DEBUG 254 (void) printf("playtone: pitch %d for %d ticks, rest for %d ticks\n", 255 pitch, sound, silence); 256#endif /* DEBUG */ 257 258 tone(pitchtab[pitch], sound); 259 if (fill != LEGATO) 260 rest(silence); 261 } 262} 263 264static int 265abs(n) 266 int n; 267{ 268 if (n < 0) 269 return(-n); 270 else 271 return(n); 272} 273 274/* interpret and play an item from a notation string */ 275static void 276playstring(cp, slen) 277 char *cp; 278 size_t slen; 279{ 280 int pitch, oldfill, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE; 281 282#define GETNUM(cp, v) for(v=0; isdigit(cp[1]) && slen > 0; ) \ 283 {v = v * 10 + (*++cp - '0'); slen--;} 284 for (; slen--; cp++) 285 { 286 int sustain, timeval, tempo; 287 register char c = toupper(*cp); 288 289#ifdef DEBUG 290 (void) printf("playstring: %c (%x)\n", c, c); 291#endif /* DEBUG */ 292 293 switch (c) 294 { 295 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': 296 297 /* compute pitch */ 298 pitch = notetab[c - 'A'] + octave * OCTAVE_NOTES; 299 300 /* this may be followed by an accidental sign */ 301 if (cp[1] == '#' || cp[1] == '+') 302 { 303 ++pitch; 304 ++cp; 305 slen--; 306 } 307 else if (cp[1] == '-') 308 { 309 --pitch; 310 ++cp; 311 slen--; 312 } 313 314 /* 315 * If octave-tracking mode is on, and there has been no octave- 316 * setting prefix, find the version of the current letter note 317 * closest to the last regardless of octave. 318 */ 319 if (octtrack && !octprefix) 320 { 321 if (abs(pitch-lastpitch) > abs(pitch+OCTAVE_NOTES-lastpitch)) 322 { 323 ++octave; 324 pitch += OCTAVE_NOTES; 325 } 326 327 if (abs(pitch-lastpitch) > abs((pitch-OCTAVE_NOTES)-lastpitch)) 328 { 329 --octave; 330 pitch -= OCTAVE_NOTES; 331 } 332 } 333 octprefix = FALSE; 334 lastpitch = pitch; 335 336 /* ...which may in turn be followed by an override time value */ 337 GETNUM(cp, timeval); 338 if (timeval <= 0 || timeval > MIN_VALUE) 339 timeval = value; 340 341 /* ...and/or sustain dots */ 342 for (sustain = 0; cp[1] == '.'; cp++) 343 { 344 slen--; 345 sustain++; 346 } 347 348 /* ...and/or a slur mark */ 349 oldfill = fill; 350 if (cp[1] == '_') 351 { 352 fill = LEGATO; 353 ++cp; 354 slen--; 355 } 356 357 /* time to emit the actual tone */ 358 playtone(pitch, timeval, sustain); 359 360 fill = oldfill; 361 break; 362 363 case 'O': 364 if (cp[1] == 'N' || cp[1] == 'n') 365 { 366 octprefix = octtrack = FALSE; 367 ++cp; 368 slen--; 369 } 370 else if (cp[1] == 'L' || cp[1] == 'l') 371 { 372 octtrack = TRUE; 373 ++cp; 374 slen--; 375 } 376 else 377 { 378 GETNUM(cp, octave); 379 if (octave >= sizeof(pitchtab) / sizeof(pitchtab[0]) / OCTAVE_NOTES) 380 octave = DFLT_OCTAVE; 381 octprefix = TRUE; 382 } 383 break; 384 385 case '>': 386 if (octave < sizeof(pitchtab) / sizeof(pitchtab[0]) / OCTAVE_NOTES - 1) 387 octave++; 388 octprefix = TRUE; 389 break; 390 391 case '<': 392 if (octave > 0) 393 octave--; 394 octprefix = TRUE; 395 break; 396 397 case 'N': 398 GETNUM(cp, pitch); 399 for (sustain = 0; cp[1] == '.'; cp++) 400 { 401 slen--; 402 sustain++; 403 } 404 oldfill = fill; 405 if (cp[1] == '_') 406 { 407 fill = LEGATO; 408 ++cp; 409 slen--; 410 } 411 playtone(pitch - 1, value, sustain); 412 fill = oldfill; 413 break; 414 415 case 'L': 416 GETNUM(cp, value); 417 if (value <= 0 || value > MIN_VALUE) 418 value = DFLT_VALUE; 419 break; 420 421 case 'P': 422 case '~': 423 /* this may be followed by an override time value */ 424 GETNUM(cp, timeval); 425 if (timeval <= 0 || timeval > MIN_VALUE) 426 timeval = value; 427 for (sustain = 0; cp[1] == '.'; cp++) 428 { 429 slen--; 430 sustain++; 431 } 432 playtone(-1, timeval, sustain); 433 break; 434 435 case 'T': 436 GETNUM(cp, tempo); 437 if (tempo < MIN_TEMPO || tempo > MAX_TEMPO) 438 tempo = DFLT_TEMPO; 439 whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / tempo; 440 break; 441 442 case 'M': 443 if (cp[1] == 'N' || cp[1] == 'n') 444 { 445 fill = NORMAL; 446 ++cp; 447 slen--; 448 } 449 else if (cp[1] == 'L' || cp[1] == 'l') 450 { 451 fill = LEGATO; 452 ++cp; 453 slen--; 454 } 455 else if (cp[1] == 'S' || cp[1] == 's') 456 { 457 fill = STACCATO; 458 ++cp; 459 slen--; 460 } 461 break; 462 } 463 } 464} 465 466/******************* UNIX DRIVER HOOKS BEGIN HERE ************************** 467 * 468 * This section implements driver hooks to run playstring() and the tone(), 469 * endtone(), and rest() functions defined above. 470 */ 471 472static int spkr_active = FALSE; /* exclusion flag */ 473static struct buf *spkr_inbuf; /* incoming buf */ 474 475int 476spkropen(dev, flags, fmt, p) 477 dev_t dev; 478 int flags; 479 int fmt; 480 struct proc *p; 481{ 482#ifdef DEBUG 483 (void) printf("spkropen: entering with dev = %s\n", devtoname(dev)); 484#endif /* DEBUG */ 485 486 if (minor(dev) != 0) 487 return(ENXIO); 488 else if (spkr_active) 489 return(EBUSY); 490 else 491 { 492#ifdef DEBUG 493 (void) printf("spkropen: about to perform play initialization\n"); 494#endif /* DEBUG */ 495 playinit(); 496 spkr_inbuf = geteblk(DEV_BSIZE); 497 spkr_active = TRUE; 498 return(0); 499 } 500} 501 502int 503spkrwrite(dev, uio, ioflag) 504 dev_t dev; 505 struct uio *uio; 506 int ioflag; 507{ 508#ifdef DEBUG 509 printf("spkrwrite: entering with dev = %s, count = %d\n", 510 devtoname(dev), uio->uio_resid); 511#endif /* DEBUG */ 512 513 if (minor(dev) != 0) 514 return(ENXIO); 515 else if (uio->uio_resid > (DEV_BSIZE - 1)) /* prevent system crashes */ 516 return(E2BIG); 517 else 518 { 519 unsigned n; 520 char *cp; 521 int error; 522 523 n = uio->uio_resid; 524 cp = spkr_inbuf->b_data; 525 error = uiomove(cp, n, uio); 526 if (!error) { 527 cp[n] = '\0'; 528 playstring(cp, n); 529 } 530 return(error); 531 } 532} 533 534int 535spkrclose(dev, flags, fmt, p) 536 dev_t dev; 537 int flags; 538 int fmt; 539 struct proc *p; 540{ 541#ifdef DEBUG 542 (void) printf("spkrclose: entering with dev = %s\n", devtoname(dev)); 543#endif /* DEBUG */ 544 545 if (minor(dev) != 0) 546 return(ENXIO); 547 else 548 { 549 wakeup((caddr_t)&endtone); 550 wakeup((caddr_t)&endrest); 551 brelse(spkr_inbuf); 552 spkr_active = FALSE; 553 return(0); 554 } 555} 556 557int 558spkrioctl(dev, cmd, cmdarg, flags, p) 559 dev_t dev; 560 unsigned long cmd; 561 caddr_t cmdarg; 562 int flags; 563 struct proc *p; 564{ 565#ifdef DEBUG 566 (void) printf("spkrioctl: entering with dev = %lx, cmd = %lx\n", 567 (unsigned long)dev, cmd); 568#endif /* DEBUG */ 569 570 if (minor(dev) != 0) 571 return(ENXIO); 572 else if (cmd == SPKRTONE) 573 { 574 tone_t *tp = (tone_t *)cmdarg; 575 576 if (tp->frequency == 0) 577 rest(tp->duration); 578 else 579 tone(tp->frequency, tp->duration); 580 return 0; 581 } 582 else if (cmd == SPKRTUNE) 583 { 584 tone_t *tp = (tone_t *)(*(caddr_t *)cmdarg); 585 tone_t ttp; 586 int error; 587 588 for (; ; tp++) { 589 error = copyin(tp, &ttp, sizeof(tone_t)); 590 if (error) 591 return(error); 592 if (ttp.duration == 0) 593 break; 594 if (ttp.frequency == 0) 595 rest(ttp.duration); 596 else 597 tone(ttp.frequency, ttp.duration); 598 } 599 return(0); 600 } 601 return(EINVAL); 602} 603 604static void 605spkr_drvinit(void *unused) 606{ 607 cdevsw_add(&spkr_cdevsw); 608#ifdef DEVFS 609 devfs_token = devfs_add_devswf(&spkr_cdevsw, 0, DV_CHR, 610 UID_ROOT, GID_WHEEL, 0600, "speaker"); 611#endif 612} 613 614SYSINIT(spkrdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,spkr_drvinit,NULL) 615 616 617#endif /* NSPEAKER > 0 */ 618/* spkr.c ends here */ 619