FreeBSD/Linux Kernel Cross Reference
sys/i386/isa/spkr.c
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 * $FreeBSD$
8 */
9
10 #include "speaker.h"
11
12 #if NSPEAKER > 0
13
14 #include "opt_devfs.h"
15
16 #include <sys/param.h>
17 #include <sys/systm.h>
18 #include <sys/kernel.h>
19 #include <sys/buf.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 #ifdef DEVFS
28 #include <sys/devfsext.h>
29 static void *devfs_token;
30 #endif
31
32 static d_open_t spkropen;
33 static d_close_t spkrclose;
34 static d_write_t spkrwrite;
35 static d_ioctl_t spkrioctl;
36
37 #define CDEV_MAJOR 26
38 static struct cdevsw spkr_cdevsw =
39 { spkropen, spkrclose, noread, spkrwrite, /*26*/
40 spkrioctl, nostop, nullreset, nodevtotty,/* spkr */
41 seltrue, nommap, NULL, "spkr", NULL, -1 };
42
43 /**************** MACHINE DEPENDENT PART STARTS HERE *************************
44 *
45 * This section defines a function tone() which causes a tone of given
46 * frequency and duration from the ISA console speaker.
47 * Another function endtone() is defined to force sound off, and there is
48 * also a rest() entry point to do pauses.
49 *
50 * Audible sound is generated using the Programmable Interval Timer (PIT) and
51 * Programmable Peripheral Interface (PPI) attached to the ISA speaker. The
52 * PPI controls whether sound is passed through at all; the PIT's channel 2 is
53 * used to generate clicks (a square wave) of whatever frequency is desired.
54 */
55
56 /*
57 * PPI control values.
58 * XXX should be in a header and used in clock.c.
59 */
60 #define PPI_SPKR 0x03 /* turn these PPI bits on to pass sound */
61
62 #define SPKRPRI PSOCK
63 static char endtone, endrest;
64
65 static void tone __P((unsigned int thz, unsigned int ticks));
66 static void rest __P((int ticks));
67 static void playinit __P((void));
68 static void playtone __P((int pitch, int value, int sustain));
69 static int abs __P((int n));
70 static void playstring __P((char *cp, size_t slen));
71
72 /* emit tone of frequency thz for given number of ticks */
73 static void
74 tone(thz, ticks)
75 unsigned int thz, ticks;
76 {
77 unsigned int divisor;
78 int sps;
79
80 if (thz <= 0)
81 return;
82
83 divisor = timer_freq / thz;
84
85 #ifdef DEBUG
86 (void) printf("tone: thz=%d ticks=%d\n", thz, ticks);
87 #endif /* DEBUG */
88
89 /* set timer to generate clicks at given frequency in Hertz */
90 sps = splclock();
91
92 if (acquire_timer2(TIMER_SEL2 | TIMER_SQWAVE | TIMER_16BIT)) {
93 /* enter list of waiting procs ??? */
94 splx(sps);
95 return;
96 }
97 splx(sps);
98 disable_intr();
99 outb(TIMER_CNTR2, (divisor & 0xff)); /* send lo byte */
100 outb(TIMER_CNTR2, (divisor >> 8)); /* send hi byte */
101 enable_intr();
102
103 /* turn the speaker on */
104 outb(IO_PPI, inb(IO_PPI) | PPI_SPKR);
105
106 /*
107 * Set timeout to endtone function, then give up the timeslice.
108 * This is so other processes can execute while the tone is being
109 * emitted.
110 */
111 if (ticks > 0)
112 tsleep((caddr_t)&endtone, SPKRPRI | PCATCH, "spkrtn", ticks);
113 outb(IO_PPI, inb(IO_PPI) & ~PPI_SPKR);
114 sps = splclock();
115 release_timer2();
116 splx(sps);
117 }
118
119 /* rest for given number of ticks */
120 static void
121 rest(ticks)
122 int 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
145 typedef 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) >= '') && ((c) <= '9'))
151 #define dtoi(c) ((c) - '')
152
153 static int octave; /* currently selected octave */
154 static int whole; /* whole-note time at current tempo, in ticks */
155 static int value; /* whole divisor for note time, quarter note = 1 */
156 static int fill; /* controls spacing of notes */
157 static bool octtrack; /* octave-tracking on? */
158 static 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 */
179 static 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 */
187 static 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
199 static void
200 playinit()
201 {
202 octave = DFLT_OCTAVE;
203 whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO;
204 fill = NORMAL;
205 value = DFLT_VALUE;
206 octtrack = FALSE;
207 octprefix = TRUE; /* act as though there was an initial O(n) */
208 }
209
210 /* play tone of proper duration for current rhythm signature */
211 static void
212 playtone(pitch, value, sustain)
213 int pitch, value, sustain;
214 {
215 register int sound, silence, snum = 1, sdenom = 1;
216
217 /* this weirdness avoids floating-point arithmetic */
218 for (; sustain; sustain--)
219 {
220 /* See the BUGS section in the man page for discussion */
221 snum *= NUM_MULT;
222 sdenom *= DENOM_MULT;
223 }
224
225 if (value == 0 || sdenom == 0)
226 return;
227
228 if (pitch == -1)
229 rest(whole * snum / (value * sdenom));
230 else
231 {
232 sound = (whole * snum) / (value * sdenom)
233 - (whole * (FILLTIME - fill)) / (value * FILLTIME);
234 silence = whole * (FILLTIME-fill) * snum / (FILLTIME * value * sdenom);
235
236 #ifdef DEBUG
237 (void) printf("playtone: pitch %d for %d ticks, rest for %d ticks\n",
238 pitch, sound, silence);
239 #endif /* DEBUG */
240
241 tone(pitchtab[pitch], sound);
242 if (fill != LEGATO)
243 rest(silence);
244 }
245 }
246
247 static int
248 abs(n)
249 int n;
250 {
251 if (n < 0)
252 return(-n);
253 else
254 return(n);
255 }
256
257 /* interpret and play an item from a notation string */
258 static void
259 playstring(cp, slen)
260 char *cp;
261 size_t slen;
262 {
263 int pitch, oldfill, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE;
264
265 #define GETNUM(cp, v) for(v=0; isdigit(cp[1]) && slen > 0; ) \
266 {v = v * 10 + (*++cp - ''); slen--;}
267 for (; slen--; cp++)
268 {
269 int sustain, timeval, tempo;
270 register char c = toupper(*cp);
271
272 #ifdef DEBUG
273 (void) printf("playstring: %c (%x)\n", c, c);
274 #endif /* DEBUG */
275
276 switch (c)
277 {
278 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
279
280 /* compute pitch */
281 pitch = notetab[c - 'A'] + octave * OCTAVE_NOTES;
282
283 /* this may be followed by an accidental sign */
284 if (cp[1] == '#' || cp[1] == '+')
285 {
286 ++pitch;
287 ++cp;
288 slen--;
289 }
290 else if (cp[1] == '-')
291 {
292 --pitch;
293 ++cp;
294 slen--;
295 }
296
297 /*
298 * If octave-tracking mode is on, and there has been no octave-
299 * setting prefix, find the version of the current letter note
300 * closest to the last regardless of octave.
301 */
302 if (octtrack && !octprefix)
303 {
304 if (abs(pitch-lastpitch) > abs(pitch+OCTAVE_NOTES-lastpitch))
305 {
306 ++octave;
307 pitch += OCTAVE_NOTES;
308 }
309
310 if (abs(pitch-lastpitch) > abs((pitch-OCTAVE_NOTES)-lastpitch))
311 {
312 --octave;
313 pitch -= OCTAVE_NOTES;
314 }
315 }
316 octprefix = FALSE;
317 lastpitch = pitch;
318
319 /* ...which may in turn be followed by an override time value */
320 GETNUM(cp, timeval);
321 if (timeval <= 0 || timeval > MIN_VALUE)
322 timeval = value;
323
324 /* ...and/or sustain dots */
325 for (sustain = 0; cp[1] == '.'; cp++)
326 {
327 slen--;
328 sustain++;
329 }
330
331 /* ...and/or a slur mark */
332 oldfill = fill;
333 if (cp[1] == '_')
334 {
335 fill = LEGATO;
336 ++cp;
337 slen--;
338 }
339
340 /* time to emit the actual tone */
341 playtone(pitch, timeval, sustain);
342
343 fill = oldfill;
344 break;
345
346 case 'O':
347 if (cp[1] == 'N' || cp[1] == 'n')
348 {
349 octprefix = octtrack = FALSE;
350 ++cp;
351 slen--;
352 }
353 else if (cp[1] == 'L' || cp[1] == 'l')
354 {
355 octtrack = TRUE;
356 ++cp;
357 slen--;
358 }
359 else
360 {
361 GETNUM(cp, octave);
362 if (octave >= sizeof(pitchtab) / sizeof(pitchtab[0]) / OCTAVE_NOTES)
363 octave = DFLT_OCTAVE;
364 octprefix = TRUE;
365 }
366 break;
367
368 case '>':
369 if (octave < sizeof(pitchtab) / sizeof(pitchtab[0]) / OCTAVE_NOTES - 1)
370 octave++;
371 octprefix = TRUE;
372 break;
373
374 case '<':
375 if (octave > 0)
376 octave--;
377 octprefix = TRUE;
378 break;
379
380 case 'N':
381 GETNUM(cp, pitch);
382 for (sustain = 0; cp[1] == '.'; cp++)
383 {
384 slen--;
385 sustain++;
386 }
387 oldfill = fill;
388 if (cp[1] == '_')
389 {
390 fill = LEGATO;
391 ++cp;
392 slen--;
393 }
394 playtone(pitch - 1, value, sustain);
395 fill = oldfill;
396 break;
397
398 case 'L':
399 GETNUM(cp, value);
400 if (value <= 0 || value > MIN_VALUE)
401 value = DFLT_VALUE;
402 break;
403
404 case 'P':
405 case '~':
406 /* this may be followed by an override time value */
407 GETNUM(cp, timeval);
408 if (timeval <= 0 || timeval > MIN_VALUE)
409 timeval = value;
410 for (sustain = 0; cp[1] == '.'; cp++)
411 {
412 slen--;
413 sustain++;
414 }
415 playtone(-1, timeval, sustain);
416 break;
417
418 case 'T':
419 GETNUM(cp, tempo);
420 if (tempo < MIN_TEMPO || tempo > MAX_TEMPO)
421 tempo = DFLT_TEMPO;
422 whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / tempo;
423 break;
424
425 case 'M':
426 if (cp[1] == 'N' || cp[1] == 'n')
427 {
428 fill = NORMAL;
429 ++cp;
430 slen--;
431 }
432 else if (cp[1] == 'L' || cp[1] == 'l')
433 {
434 fill = LEGATO;
435 ++cp;
436 slen--;
437 }
438 else if (cp[1] == 'S' || cp[1] == 's')
439 {
440 fill = STACCATO;
441 ++cp;
442 slen--;
443 }
444 break;
445 }
446 }
447 }
448
449 /******************* UNIX DRIVER HOOKS BEGIN HERE **************************
450 *
451 * This section implements driver hooks to run playstring() and the tone(),
452 * endtone(), and rest() functions defined above.
453 */
454
455 static int spkr_active = FALSE; /* exclusion flag */
456 static struct buf *spkr_inbuf; /* incoming buf */
457
458 int
459 spkropen(dev, flags, fmt, p)
460 dev_t dev;
461 int flags;
462 int fmt;
463 struct proc *p;
464 {
465 #ifdef DEBUG
466 (void) printf("spkropen: entering with dev = %x\n", dev);
467 #endif /* DEBUG */
468
469 if (minor(dev) != 0)
470 return(ENXIO);
471 else if (spkr_active)
472 return(EBUSY);
473 else
474 {
475 #ifdef DEBUG
476 (void) printf("spkropen: about to perform play initialization\n");
477 #endif /* DEBUG */
478 playinit();
479 spkr_inbuf = geteblk(DEV_BSIZE);
480 spkr_active = TRUE;
481 return(0);
482 }
483 }
484
485 int
486 spkrwrite(dev, uio, ioflag)
487 dev_t dev;
488 struct uio *uio;
489 int ioflag;
490 {
491 #ifdef DEBUG
492 printf("spkrwrite: entering with dev = %x, count = %d\n",
493 dev, uio->uio_resid);
494 #endif /* DEBUG */
495
496 if (minor(dev) != 0)
497 return(ENXIO);
498 else if (uio->uio_resid > (DEV_BSIZE - 1)) /* prevent system crashes */
499 return(E2BIG);
500 else
501 {
502 unsigned n;
503 char *cp;
504 int error;
505
506 n = uio->uio_resid;
507 cp = spkr_inbuf->b_data;
508 error = uiomove(cp, n, uio);
509 if (!error) {
510 cp[n] = '\0';
511 playstring(cp, n);
512 }
513 return(error);
514 }
515 }
516
517 int
518 spkrclose(dev, flags, fmt, p)
519 dev_t dev;
520 int flags;
521 int fmt;
522 struct proc *p;
523 {
524 #ifdef DEBUG
525 (void) printf("spkrclose: entering with dev = %x\n", dev);
526 #endif /* DEBUG */
527
528 if (minor(dev) != 0)
529 return(ENXIO);
530 else
531 {
532 wakeup((caddr_t)&endtone);
533 wakeup((caddr_t)&endrest);
534 brelse(spkr_inbuf);
535 spkr_active = FALSE;
536 return(0);
537 }
538 }
539
540 int
541 spkrioctl(dev, cmd, cmdarg, flags, p)
542 dev_t dev;
543 unsigned long cmd;
544 caddr_t cmdarg;
545 int flags;
546 struct proc *p;
547 {
548 #ifdef DEBUG
549 (void) printf("spkrioctl: entering with dev = %lx, cmd = %lx\n",
550 (unsigned long)dev, cmd);
551 #endif /* DEBUG */
552
553 if (minor(dev) != 0)
554 return(ENXIO);
555 else if (cmd == SPKRTONE)
556 {
557 tone_t *tp = (tone_t *)cmdarg;
558
559 if (tp->frequency == 0)
560 rest(tp->duration);
561 else
562 tone(tp->frequency, tp->duration);
563 return 0;
564 }
565 else if (cmd == SPKRTUNE)
566 {
567 tone_t *tp = (tone_t *)(*(caddr_t *)cmdarg);
568 tone_t ttp;
569 int error;
570
571 for (; ; tp++) {
572 error = copyin(tp, &ttp, sizeof(tone_t));
573 if (error)
574 return(error);
575 if (ttp.duration == 0)
576 break;
577 if (ttp.frequency == 0)
578 rest(ttp.duration);
579 else
580 tone(ttp.frequency, ttp.duration);
581 }
582 return(0);
583 }
584 return(EINVAL);
585 }
586
587
588 static int spkr_devsw_installed;
589
590 static void
591 spkr_drvinit(void *unused)
592 {
593 dev_t dev;
594
595 if( ! spkr_devsw_installed ) {
596 dev = makedev(CDEV_MAJOR, 0);
597 cdevsw_add(&dev,&spkr_cdevsw, NULL);
598 spkr_devsw_installed = 1;
599 #ifdef DEVFS
600 devfs_token = devfs_add_devswf(&spkr_cdevsw, 0, DV_CHR,
601 UID_ROOT, GID_WHEEL, 0600,
602 "speaker");
603 #endif
604 }
605 }
606
607 SYSINIT(spkrdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,spkr_drvinit,NULL)
608
609
610 #endif /* NSPEAKER > 0 */
611 /* spkr.c ends here */
Cache object: e140df7944760758a0dc02a98ac17019
|