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