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