spkr.c revision 8288
1139778Simp/* 212115Sdyson * spkr.c -- device driver for console speaker 312115Sdyson * 412115Sdyson * v1.4 by Eric S. Raymond (esr@snark.thyrsus.com) Aug 1993 512115Sdyson * modified for FreeBSD by Andrew A. Chernov <ache@astral.msk.su> 612115Sdyson * 7139778Simp * $Id: spkr.c,v 1.12 1995/03/16 18:12:05 bde Exp $ 812115Sdyson */ 912115Sdyson 1012115Sdyson#include "speaker.h" 1112115Sdyson 1212115Sdyson#if NSPEAKER > 0 1312115Sdyson 1412115Sdyson#include <sys/param.h> 1512115Sdyson#include <sys/systm.h> 1612115Sdyson#include <sys/kernel.h> 1712115Sdyson#include <sys/errno.h> 1812115Sdyson#include <sys/buf.h> 1912115Sdyson#include <sys/proc.h> 2012115Sdyson#include <sys/uio.h> 2112115Sdyson#include <i386/isa/isa.h> 2212115Sdyson#include <i386/isa/timerreg.h> 2312115Sdyson#include <machine/clock.h> 2412115Sdyson#include <machine/speaker.h> 2512115Sdyson 2612115Sdyson/**************** MACHINE DEPENDENT PART STARTS HERE ************************* 2712115Sdyson * 2812115Sdyson * This section defines a function tone() which causes a tone of given 2912115Sdyson * frequency and duration from the 80x86's console speaker. 3012115Sdyson * Another function endtone() is defined to force sound off, and there is 3112115Sdyson * also a rest() entry point to do pauses. 3212115Sdyson * 3312115Sdyson * Audible sound is generated using the Programmable Interval Timer (PIT) and 3412115Sdyson * Programmable Peripheral Interface (PPI) attached to the 80x86's speaker. The 3512115Sdyson * PPI controls whether sound is passed through at all; the PIT's channel 2 is 3660041Sphk * used to generate clicks (a square wave) of whatever frequency is desired. 3712115Sdyson */ 3812115Sdyson 3912115Sdyson/* 4012115Sdyson * PIT and PPI port addresses and control values 4160041Sphk * 4212115Sdyson * Most of the magic is hidden in the TIMER_PREP value, which selects PIT 4331561Sbde * channel 2, frequency LSB first, square-wave mode and binary encoding. 44221166Sjhb * The encoding is as follows: 4512115Sdyson * 4612115Sdyson * +----------+----------+---------------+-----+ 47251809Spfg * | 1 0 | 1 1 | 0 1 1 | 0 | 48202283Slulf * | SC1 SC0 | RW1 RW0 | M2 M1 M0 | BCD | 49202283Slulf * +----------+----------+---------------+-----+ 50251809Spfg * Counter Write Mode 3 Binary 51202283Slulf * Channel 2 LSB first, (Square Wave) Encoding 52202283Slulf * MSB second 53251809Spfg */ 5412115Sdyson#define PPI_SPKR 0x03 /* turn these PPI bits on to pass sound */ 55251612Spfg#define PIT_MODE 0xB6 /* set timer mode for sound generation */ 5612115Sdyson 5712115Sdyson/* 5812115Sdyson * Magic numbers for timer control. 5912115Sdyson */ 60252103Spfg#define TIMER_CLK 1193180L /* corresponds to 18.2 MHz tick rate */ 61246634Spfg 6212115Sdyson#define SPKRPRI PSOCK 63202283Slulfstatic char endtone, endrest; 64202283Slulf 6512115Sdysonstatic void tone(thz, ticks) 6612115Sdyson/* emit tone of frequency thz for given number of ticks */ 6712115Sdysonunsigned int thz, ticks; 68254283Spfg{ 69254283Spfg unsigned int divisor; 7012115Sdyson int sps; 71202283Slulf 7212115Sdyson if (thz <= 0) 73202283Slulf return; 7412115Sdyson 7512115Sdyson divisor = TIMER_CLK / thz; 76202283Slulf 7712115Sdyson#ifdef DEBUG 7812115Sdyson (void) printf("tone: thz=%d ticks=%d\n", thz, ticks); 7912115Sdyson#endif /* DEBUG */ 8012115Sdyson 8112115Sdyson /* set timer to generate clicks at given frequency in Hertz */ 8212115Sdyson sps = spltty(); 8312115Sdyson 8412115Sdyson if (acquire_timer2(PIT_MODE)) { 8512115Sdyson /* enter list of waiting procs ??? */ 8612115Sdyson return; 8712115Sdyson } 8812115Sdyson outb(TIMER_CNTR2, (divisor & 0xff)); /* send lo byte */ 8912115Sdyson outb(TIMER_CNTR2, (divisor >> 8)); /* send hi byte */ 9012115Sdyson splx(sps); 91202283Slulf 92202283Slulf /* turn the speaker on */ 9312115Sdyson outb(IO_PPI, inb(IO_PPI) | PPI_SPKR); 9412115Sdyson 95202283Slulf /* 96202283Slulf * Set timeout to endtone function, then give up the timeslice. 9712115Sdyson * This is so other processes can execute while the tone is being 9812115Sdyson * emitted. 9912115Sdyson */ 10012115Sdyson if (ticks > 0) 10175951Sbde tsleep((caddr_t)&endtone, SPKRPRI | PCATCH, "spkrtn", ticks); 10212115Sdyson outb(IO_PPI, inb(IO_PPI) & ~PPI_SPKR); 10312115Sdyson release_timer2(); 10412115Sdyson} 10512115Sdyson 10612115Sdysonstatic void rest(ticks) 10712115Sdyson/* rest for given number of ticks */ 10812115Sdysonint ticks; 10912115Sdyson{ 11012115Sdyson /* 11112115Sdyson * Set timeout to endrest function, then give up the timeslice. 112202283Slulf * This is so other processes can execute while the rest is being 11312115Sdyson * waited out. 11412115Sdyson */ 11512115Sdyson#ifdef DEBUG 11612115Sdyson (void) printf("rest: %d\n", ticks); 11775951Sbde#endif /* DEBUG */ 11812115Sdyson if (ticks > 0) 11912115Sdyson tsleep((caddr_t)&endrest, SPKRPRI | PCATCH, "spkrrs", ticks); 12012115Sdyson} 12112115Sdyson 12212115Sdyson/**************** PLAY STRING INTERPRETER BEGINS HERE ********************** 12324492Sbde * 12424492Sbde * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement; 12512115Sdyson * M[LNS] are missing; the ~ synonym and the _ slur mark and the octave- 12612115Sdyson * tracking facility are added. 12712115Sdyson * Requires tone(), rest(), and endtone(). String play is not interruptible 12812115Sdyson * except possibly at physical block boundaries. 12912115Sdyson */ 13012115Sdyson 131202283Slulftypedef int bool; 13212115Sdyson#define TRUE 1 13312115Sdyson#define FALSE 0 134202283Slulf 135202283Slulf#define toupper(c) ((c) - ' ' * (((c) >= 'a') && ((c) <= 'z'))) 136202283Slulf#define isdigit(c) (((c) >= '0') && ((c) <= '9')) 137202283Slulf#define dtoi(c) ((c) - '0') 13812115Sdyson 13912115Sdysonstatic int octave; /* currently selected octave */ 14012115Sdysonstatic int whole; /* whole-note time at current tempo, in ticks */ 141202283Slulfstatic int value; /* whole divisor for note time, quarter note = 1 */ 14212115Sdysonstatic int fill; /* controls spacing of notes */ 143221166Sjhbstatic bool octtrack; /* octave-tracking on? */ 14412115Sdysonstatic bool octprefix; /* override current octave-tracking state? */ 14512115Sdyson 146202283Slulf/* 14712115Sdyson * Magic number avoidance... 14812115Sdyson */ 14912115Sdyson#define SECS_PER_MIN 60 /* seconds per minute */ 15012115Sdyson#define WHOLE_NOTE 4 /* quarter notes per whole note */ 15112115Sdyson#define MIN_VALUE 64 /* the most we can divide a note by */ 15212115Sdyson#define DFLT_VALUE 4 /* default value (quarter-note) */ 15312115Sdyson#define FILLTIME 8 /* for articulation, break note in parts */ 15412115Sdyson#define STACCATO 6 /* 6/8 = 3/4 of note is filled */ 155202283Slulf#define NORMAL 7 /* 7/8ths of note interval is filled */ 156228583Spfg#define LEGATO 8 /* all of note interval is filled */ 157251658Spfg#define DFLT_OCTAVE 4 /* default octave */ 15812115Sdyson#define MIN_TEMPO 32 /* minimum tempo */ 15996749Siedowse#define DFLT_TEMPO 120 /* default tempo */ 16012115Sdyson#define MAX_TEMPO 255 /* max tempo */ 16112115Sdyson#define NUM_MULT 3 /* numerator of dot multiplier */ 16212115Sdyson#define DENOM_MULT 2 /* denominator of dot multiplier */ 16312115Sdyson 16412115Sdyson/* letter to half-tone: A B C D E F G */ 16512115Sdysonstatic int notetab[8] = {9, 11, 0, 2, 4, 5, 7}; 16612115Sdyson 167202283Slulf/* 16812115Sdyson * This is the American Standard A440 Equal-Tempered scale with frequencies 16912115Sdyson * rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook... 170251677Spfg * our octave 0 is standard octave 2. 171251677Spfg */ 17212115Sdyson#define OCTAVE_NOTES 12 /* semitones per octave */ 17312115Sdysonstatic int pitchtab[] = 174202283Slulf{ 17512115Sdyson/* C C# D D# E F F# G G# A A# B*/ 17612115Sdyson/* 0 */ 65, 69, 73, 78, 82, 87, 93, 98, 103, 110, 117, 123, 17712115Sdyson/* 1 */ 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, 17812115Sdyson/* 2 */ 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, 17912115Sdyson/* 3 */ 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 18012115Sdyson/* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975, 18143301Sdillon/* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, 182202283Slulf/* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902, 18312115Sdyson}; 18412115Sdyson 18512115Sdysonstatic void playinit() 18612115Sdyson{ 18712115Sdyson octave = DFLT_OCTAVE; 18812115Sdyson whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO; 18912115Sdyson fill = NORMAL; 19012115Sdyson value = DFLT_VALUE; 19112115Sdyson octtrack = FALSE; 19212115Sdyson octprefix = TRUE; /* act as though there was an initial O(n) */ 193202283Slulf} 19412115Sdyson 19512115Sdysonstatic void playtone(pitch, value, sustain) 19612115Sdyson/* play tone of proper duration for current rhythm signature */ 19712115Sdysonint pitch, value, sustain; 198254283Spfg{ 19912115Sdyson register int sound, silence, snum = 1, sdenom = 1; 20012115Sdyson 20112115Sdyson /* this weirdness avoids floating-point arithmetic */ 20212115Sdyson for (; sustain; sustain--) 20312115Sdyson { 204202283Slulf /* See the BUGS section in the man page for discussion */ 20512115Sdyson snum *= NUM_MULT; 20612115Sdyson sdenom *= DENOM_MULT; 207202283Slulf } 208202283Slulf 20912115Sdyson if (value == 0 || sdenom == 0) 21012115Sdyson return; 211202283Slulf 212202283Slulf if (pitch == -1) 21312115Sdyson rest(whole * snum / (value * sdenom)); 21412115Sdyson else 21512115Sdyson { 21612115Sdyson sound = (whole * snum) / (value * sdenom) 217202283Slulf - (whole * (FILLTIME - fill)) / (value * FILLTIME); 21812115Sdyson silence = whole * (FILLTIME-fill) * snum / (FILLTIME * value * sdenom); 21912115Sdyson 22012115Sdyson#ifdef DEBUG 22112115Sdyson (void) printf("playtone: pitch %d for %d ticks, rest for %d ticks\n", 22212115Sdyson pitch, sound, silence); 22312115Sdyson#endif /* DEBUG */ 22443301Sdillon 225202283Slulf tone(pitchtab[pitch], sound); 226202283Slulf if (fill != LEGATO) 22712115Sdyson rest(silence); 22812115Sdyson } 22912115Sdyson} 23012115Sdyson 23112115Sdysonstatic int abs(n) 23212115Sdysonint n; 23312115Sdyson{ 23412115Sdyson if (n < 0) 235221166Sjhb return(-n); 23612115Sdyson else 23712115Sdyson return(n); 238202283Slulf} 239202283Slulf 24012115Sdysonstatic void playstring(cp, slen) 24112115Sdyson/* interpret and play an item from a notation string */ 24212115Sdysonchar *cp; 24312115Sdysonsize_t slen; 24412115Sdyson{ 24512115Sdyson int pitch, oldfill, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE; 24612115Sdyson 247202283Slulf#define GETNUM(cp, v) for(v=0; isdigit(cp[1]) && slen > 0; ) \ 24812115Sdyson {v = v * 10 + (*++cp - '0'); slen--;} 24912115Sdyson for (; slen--; cp++) 25043301Sdillon { 251202283Slulf int sustain, timeval, tempo; 25212115Sdyson register char c = toupper(*cp); 25312115Sdyson 25412115Sdyson#ifdef DEBUG 25512115Sdyson (void) printf("playstring: %c (%x)\n", c, c); 256202283Slulf#endif /* DEBUG */ 25712115Sdyson 258221166Sjhb switch (c) 25912115Sdyson { 26012115Sdyson case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': 26112115Sdyson 26212115Sdyson /* compute pitch */ 26312115Sdyson pitch = notetab[c - 'A'] + octave * OCTAVE_NOTES; 26412115Sdyson 265221166Sjhb /* this may be followed by an accidental sign */ 26612115Sdyson if (cp[1] == '#' || cp[1] == '+') 26712115Sdyson { 268202283Slulf ++pitch; 269202283Slulf ++cp; 27012115Sdyson slen--; 27112115Sdyson } 27212115Sdyson else if (cp[1] == '-') 27312115Sdyson { 27412115Sdyson --pitch; 27512115Sdyson ++cp; 276221166Sjhb slen--; 277221166Sjhb } 278221166Sjhb 279221166Sjhb /* 280221166Sjhb * If octave-tracking mode is on, and there has been no octave- 281248282Skib * setting prefix, find the version of the current letter note 282221166Sjhb * closest to the last regardless of octave. 283221166Sjhb */ 284221166Sjhb if (octtrack && !octprefix) 28512115Sdyson { 28612115Sdyson if (abs(pitch-lastpitch) > abs(pitch+OCTAVE_NOTES-lastpitch)) 28712115Sdyson { 28812115Sdyson ++octave; 28912115Sdyson pitch += OCTAVE_NOTES; 290202283Slulf } 29112115Sdyson 29212115Sdyson if (abs(pitch-lastpitch) > abs((pitch-OCTAVE_NOTES)-lastpitch)) 29312115Sdyson { 29412115Sdyson --octave; 29512115Sdyson pitch -= OCTAVE_NOTES; 296202283Slulf } 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