FreeBSD/Linux Kernel Cross Reference
sys/pc98/pc98/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: releng/5.0/sys/pc98/pc98/spkr.c 106234 2002-10-31 05:12:22Z nyan $
8 */
9
10 /*
11 * modified for PC98 by Kakefuda
12 */
13
14 #include <sys/param.h>
15 #include <sys/systm.h>
16 #include <sys/bus.h>
17 #include <sys/kernel.h>
18 #include <sys/module.h>
19 #include <sys/bio.h>
20 #include <sys/uio.h>
21 #include <sys/conf.h>
22 #include <sys/ctype.h>
23 #include <sys/malloc.h>
24 #include <isa/isavar.h>
25 #ifdef PC98
26 #include <pc98/pc98/pc98.h>
27 #else
28 #include <i386/isa/isa.h>
29 #endif
30 #include <i386/isa/timerreg.h>
31 #include <machine/clock.h>
32 #include <machine/speaker.h>
33
34 static d_open_t spkropen;
35 static d_close_t spkrclose;
36 static d_write_t spkrwrite;
37 static d_ioctl_t spkrioctl;
38
39 #define CDEV_MAJOR 26
40 static struct cdevsw spkr_cdevsw = {
41 /* open */ spkropen,
42 /* close */ spkrclose,
43 /* read */ noread,
44 /* write */ spkrwrite,
45 /* ioctl */ spkrioctl,
46 /* poll */ nopoll,
47 /* mmap */ nommap,
48 /* strategy */ nostrategy,
49 /* name */ "spkr",
50 /* maj */ CDEV_MAJOR,
51 /* dump */ nodump,
52 /* psize */ nopsize,
53 /* flags */ 0,
54 };
55
56 static MALLOC_DEFINE(M_SPKR, "spkr", "Speaker buffer");
57
58 /**************** MACHINE DEPENDENT PART STARTS HERE *************************
59 *
60 * This section defines a function tone() which causes a tone of given
61 * frequency and duration from the ISA console speaker.
62 * Another function endtone() is defined to force sound off, and there is
63 * also a rest() entry point to do pauses.
64 *
65 * Audible sound is generated using the Programmable Interval Timer (PIT) and
66 * Programmable Peripheral Interface (PPI) attached to the ISA speaker. The
67 * PPI controls whether sound is passed through at all; the PIT's channel 2 is
68 * used to generate clicks (a square wave) of whatever frequency is desired.
69 */
70
71 /*
72 * PPI control values.
73 * XXX should be in a header and used in clock.c.
74 */
75 #ifdef PC98
76 #define PPI_SPKR 0x08 /* turn these PPI bits on to pass sound */
77 #define PIT_COUNT 0x3fdb /* PIT count address */
78 #else
79 #define PPI_SPKR 0x03 /* turn these PPI bits on to pass sound */
80 #endif
81
82 #define SPKRPRI PSOCK
83 static char endtone, endrest;
84
85 static void tone(unsigned int thz, unsigned int ticks);
86 static void rest(int ticks);
87 static void playinit(void);
88 static void playtone(int pitch, int value, int sustain);
89 static int abs(int n);
90 static void playstring(char *cp, size_t slen);
91
92 /* emit tone of frequency thz for given number of ticks */
93 static void
94 tone(thz, ticks)
95 unsigned int thz, ticks;
96 {
97 unsigned int divisor;
98 int sps;
99
100 if (thz <= 0)
101 return;
102
103 divisor = timer_freq / thz;
104
105 #ifdef DEBUG
106 (void) printf("tone: thz=%d ticks=%d\n", thz, ticks);
107 #endif /* DEBUG */
108
109 /* set timer to generate clicks at given frequency in Hertz */
110 sps = splclock();
111
112 #ifdef PC98
113 if (acquire_timer1(TIMER_SEL1 | TIMER_SQWAVE | TIMER_16BIT)) {
114 #else
115 if (acquire_timer2(TIMER_SEL2 | TIMER_SQWAVE | TIMER_16BIT)) {
116 #endif
117 /* enter list of waiting procs ??? */
118 splx(sps);
119 return;
120 }
121 splx(sps);
122 disable_intr();
123 #ifdef PC98
124 outb(PIT_COUNT, (divisor & 0xff)); /* send lo byte */
125 outb(PIT_COUNT, (divisor >> 8)); /* send hi byte */
126 #else
127 outb(TIMER_CNTR2, (divisor & 0xff)); /* send lo byte */
128 outb(TIMER_CNTR2, (divisor >> 8)); /* send hi byte */
129 #endif
130 enable_intr();
131
132 /* turn the speaker on */
133 #ifdef PC98
134 outb(IO_PPI, inb(IO_PPI) & ~PPI_SPKR);
135 #else
136 outb(IO_PPI, inb(IO_PPI) | PPI_SPKR);
137 #endif
138
139 /*
140 * Set timeout to endtone function, then give up the timeslice.
141 * This is so other processes can execute while the tone is being
142 * emitted.
143 */
144 if (ticks > 0)
145 tsleep((caddr_t)&endtone, SPKRPRI | PCATCH, "spkrtn", ticks);
146 #ifdef PC98
147 outb(IO_PPI, inb(IO_PPI) | PPI_SPKR);
148 sps = splclock();
149 release_timer1();
150 splx(sps);
151 #else
152 outb(IO_PPI, inb(IO_PPI) & ~PPI_SPKR);
153 sps = splclock();
154 release_timer2();
155 splx(sps);
156 #endif
157 }
158
159 /* rest for given number of ticks */
160 static void
161 rest(ticks)
162 int ticks;
163 {
164 /*
165 * Set timeout to endrest function, then give up the timeslice.
166 * This is so other processes can execute while the rest is being
167 * waited out.
168 */
169 #ifdef DEBUG
170 (void) printf("rest: %d\n", ticks);
171 #endif /* DEBUG */
172 if (ticks > 0)
173 tsleep((caddr_t)&endrest, SPKRPRI | PCATCH, "spkrrs", ticks);
174 }
175
176 /**************** PLAY STRING INTERPRETER BEGINS HERE **********************
177 *
178 * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement;
179 * M[LNS] are missing; the ~ synonym and the _ slur mark and the octave-
180 * tracking facility are added.
181 * Requires tone(), rest(), and endtone(). String play is not interruptible
182 * except possibly at physical block boundaries.
183 */
184
185 typedef int bool;
186 #define TRUE 1
187 #define FALSE 0
188
189 #define dtoi(c) ((c) - '')
190
191 static int octave; /* currently selected octave */
192 static int whole; /* whole-note time at current tempo, in ticks */
193 static int value; /* whole divisor for note time, quarter note = 1 */
194 static int fill; /* controls spacing of notes */
195 static bool octtrack; /* octave-tracking on? */
196 static bool octprefix; /* override current octave-tracking state? */
197
198 /*
199 * Magic number avoidance...
200 */
201 #define SECS_PER_MIN 60 /* seconds per minute */
202 #define WHOLE_NOTE 4 /* quarter notes per whole note */
203 #define MIN_VALUE 64 /* the most we can divide a note by */
204 #define DFLT_VALUE 4 /* default value (quarter-note) */
205 #define FILLTIME 8 /* for articulation, break note in parts */
206 #define STACCATO 6 /* 6/8 = 3/4 of note is filled */
207 #define NORMAL 7 /* 7/8ths of note interval is filled */
208 #define LEGATO 8 /* all of note interval is filled */
209 #define DFLT_OCTAVE 4 /* default octave */
210 #define MIN_TEMPO 32 /* minimum tempo */
211 #define DFLT_TEMPO 120 /* default tempo */
212 #define MAX_TEMPO 255 /* max tempo */
213 #define NUM_MULT 3 /* numerator of dot multiplier */
214 #define DENOM_MULT 2 /* denominator of dot multiplier */
215
216 /* letter to half-tone: A B C D E F G */
217 static int notetab[8] = {9, 11, 0, 2, 4, 5, 7};
218
219 /*
220 * This is the American Standard A440 Equal-Tempered scale with frequencies
221 * rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook...
222 * our octave 0 is standard octave 2.
223 */
224 #define OCTAVE_NOTES 12 /* semitones per octave */
225 static int pitchtab[] =
226 {
227 /* C C# D D# E F F# G G# A A# B*/
228 /* 0 */ 65, 69, 73, 78, 82, 87, 93, 98, 103, 110, 117, 123,
229 /* 1 */ 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247,
230 /* 2 */ 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494,
231 /* 3 */ 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988,
232 /* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975,
233 /* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951,
234 /* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902,
235 };
236
237 static void
238 playinit()
239 {
240 octave = DFLT_OCTAVE;
241 whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO;
242 fill = NORMAL;
243 value = DFLT_VALUE;
244 octtrack = FALSE;
245 octprefix = TRUE; /* act as though there was an initial O(n) */
246 }
247
248 /* play tone of proper duration for current rhythm signature */
249 static void
250 playtone(pitch, value, sustain)
251 int pitch, value, sustain;
252 {
253 register int sound, silence, snum = 1, sdenom = 1;
254
255 /* this weirdness avoids floating-point arithmetic */
256 for (; sustain; sustain--)
257 {
258 /* See the BUGS section in the man page for discussion */
259 snum *= NUM_MULT;
260 sdenom *= DENOM_MULT;
261 }
262
263 if (value == 0 || sdenom == 0)
264 return;
265
266 if (pitch == -1)
267 rest(whole * snum / (value * sdenom));
268 else
269 {
270 sound = (whole * snum) / (value * sdenom)
271 - (whole * (FILLTIME - fill)) / (value * FILLTIME);
272 silence = whole * (FILLTIME-fill) * snum / (FILLTIME * value * sdenom);
273
274 #ifdef DEBUG
275 (void) printf("playtone: pitch %d for %d ticks, rest for %d ticks\n",
276 pitch, sound, silence);
277 #endif /* DEBUG */
278
279 tone(pitchtab[pitch], sound);
280 if (fill != LEGATO)
281 rest(silence);
282 }
283 }
284
285 static int
286 abs(n)
287 int n;
288 {
289 if (n < 0)
290 return(-n);
291 else
292 return(n);
293 }
294
295 /* interpret and play an item from a notation string */
296 static void
297 playstring(cp, slen)
298 char *cp;
299 size_t slen;
300 {
301 int pitch, oldfill, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE;
302
303 #define GETNUM(cp, v) for(v=0; isdigit(cp[1]) && slen > 0; ) \
304 {v = v * 10 + (*++cp - ''); slen--;}
305 for (; slen--; cp++)
306 {
307 int sustain, timeval, tempo;
308 register char c = toupper(*cp);
309
310 #ifdef DEBUG
311 (void) printf("playstring: %c (%x)\n", c, c);
312 #endif /* DEBUG */
313
314 switch (c)
315 {
316 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
317
318 /* compute pitch */
319 pitch = notetab[c - 'A'] + octave * OCTAVE_NOTES;
320
321 /* this may be followed by an accidental sign */
322 if (cp[1] == '#' || cp[1] == '+')
323 {
324 ++pitch;
325 ++cp;
326 slen--;
327 }
328 else if (cp[1] == '-')
329 {
330 --pitch;
331 ++cp;
332 slen--;
333 }
334
335 /*
336 * If octave-tracking mode is on, and there has been no octave-
337 * setting prefix, find the version of the current letter note
338 * closest to the last regardless of octave.
339 */
340 if (octtrack && !octprefix)
341 {
342 if (abs(pitch-lastpitch) > abs(pitch+OCTAVE_NOTES-lastpitch))
343 {
344 ++octave;
345 pitch += OCTAVE_NOTES;
346 }
347
348 if (abs(pitch-lastpitch) > abs((pitch-OCTAVE_NOTES)-lastpitch))
349 {
350 --octave;
351 pitch -= OCTAVE_NOTES;
352 }
353 }
354 octprefix = FALSE;
355 lastpitch = pitch;
356
357 /* ...which may in turn be followed by an override time value */
358 GETNUM(cp, timeval);
359 if (timeval <= 0 || timeval > MIN_VALUE)
360 timeval = value;
361
362 /* ...and/or sustain dots */
363 for (sustain = 0; cp[1] == '.'; cp++)
364 {
365 slen--;
366 sustain++;
367 }
368
369 /* ...and/or a slur mark */
370 oldfill = fill;
371 if (cp[1] == '_')
372 {
373 fill = LEGATO;
374 ++cp;
375 slen--;
376 }
377
378 /* time to emit the actual tone */
379 playtone(pitch, timeval, sustain);
380
381 fill = oldfill;
382 break;
383
384 case 'O':
385 if (cp[1] == 'N' || cp[1] == 'n')
386 {
387 octprefix = octtrack = FALSE;
388 ++cp;
389 slen--;
390 }
391 else if (cp[1] == 'L' || cp[1] == 'l')
392 {
393 octtrack = TRUE;
394 ++cp;
395 slen--;
396 }
397 else
398 {
399 GETNUM(cp, octave);
400 if (octave >= sizeof(pitchtab) / sizeof(pitchtab[0]) / OCTAVE_NOTES)
401 octave = DFLT_OCTAVE;
402 octprefix = TRUE;
403 }
404 break;
405
406 case '>':
407 if (octave < sizeof(pitchtab) / sizeof(pitchtab[0]) / OCTAVE_NOTES - 1)
408 octave++;
409 octprefix = TRUE;
410 break;
411
412 case '<':
413 if (octave > 0)
414 octave--;
415 octprefix = TRUE;
416 break;
417
418 case 'N':
419 GETNUM(cp, pitch);
420 for (sustain = 0; cp[1] == '.'; cp++)
421 {
422 slen--;
423 sustain++;
424 }
425 oldfill = fill;
426 if (cp[1] == '_')
427 {
428 fill = LEGATO;
429 ++cp;
430 slen--;
431 }
432 playtone(pitch - 1, value, sustain);
433 fill = oldfill;
434 break;
435
436 case 'L':
437 GETNUM(cp, value);
438 if (value <= 0 || value > MIN_VALUE)
439 value = DFLT_VALUE;
440 break;
441
442 case 'P':
443 case '~':
444 /* this may be followed by an override time value */
445 GETNUM(cp, timeval);
446 if (timeval <= 0 || timeval > MIN_VALUE)
447 timeval = value;
448 for (sustain = 0; cp[1] == '.'; cp++)
449 {
450 slen--;
451 sustain++;
452 }
453 playtone(-1, timeval, sustain);
454 break;
455
456 case 'T':
457 GETNUM(cp, tempo);
458 if (tempo < MIN_TEMPO || tempo > MAX_TEMPO)
459 tempo = DFLT_TEMPO;
460 whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / tempo;
461 break;
462
463 case 'M':
464 if (cp[1] == 'N' || cp[1] == 'n')
465 {
466 fill = NORMAL;
467 ++cp;
468 slen--;
469 }
470 else if (cp[1] == 'L' || cp[1] == 'l')
471 {
472 fill = LEGATO;
473 ++cp;
474 slen--;
475 }
476 else if (cp[1] == 'S' || cp[1] == 's')
477 {
478 fill = STACCATO;
479 ++cp;
480 slen--;
481 }
482 break;
483 }
484 }
485 }
486
487 /******************* UNIX DRIVER HOOKS BEGIN HERE **************************
488 *
489 * This section implements driver hooks to run playstring() and the tone(),
490 * endtone(), and rest() functions defined above.
491 */
492
493 static int spkr_active = FALSE; /* exclusion flag */
494 static char *spkr_inbuf; /* incoming buf */
495
496 static int
497 spkropen(dev, flags, fmt, td)
498 dev_t dev;
499 int flags;
500 int fmt;
501 struct thread *td;
502 {
503 #ifdef DEBUG
504 (void) printf("spkropen: entering with dev = %s\n", devtoname(dev));
505 #endif /* DEBUG */
506
507 if (minor(dev) != 0)
508 return(ENXIO);
509 else if (spkr_active)
510 return(EBUSY);
511 else
512 {
513 #ifdef DEBUG
514 (void) printf("spkropen: about to perform play initialization\n");
515 #endif /* DEBUG */
516 playinit();
517 spkr_inbuf = malloc(DEV_BSIZE, M_SPKR, M_WAITOK);
518 spkr_active = TRUE;
519 return(0);
520 }
521 }
522
523 static int
524 spkrwrite(dev, uio, ioflag)
525 dev_t dev;
526 struct uio *uio;
527 int ioflag;
528 {
529 #ifdef DEBUG
530 printf("spkrwrite: entering with dev = %s, count = %d\n",
531 devtoname(dev), uio->uio_resid);
532 #endif /* DEBUG */
533
534 if (minor(dev) != 0)
535 return(ENXIO);
536 else if (uio->uio_resid > (DEV_BSIZE - 1)) /* prevent system crashes */
537 return(E2BIG);
538 else
539 {
540 unsigned n;
541 char *cp;
542 int error;
543
544 n = uio->uio_resid;
545 cp = spkr_inbuf;
546 error = uiomove(cp, n, uio);
547 if (!error) {
548 cp[n] = '\0';
549 playstring(cp, n);
550 }
551 return(error);
552 }
553 }
554
555 static int
556 spkrclose(dev, flags, fmt, td)
557 dev_t dev;
558 int flags;
559 int fmt;
560 struct thread *td;
561 {
562 #ifdef DEBUG
563 (void) printf("spkrclose: entering with dev = %s\n", devtoname(dev));
564 #endif /* DEBUG */
565
566 if (minor(dev) != 0)
567 return(ENXIO);
568 else
569 {
570 wakeup((caddr_t)&endtone);
571 wakeup((caddr_t)&endrest);
572 free(spkr_inbuf, M_SPKR);
573 spkr_active = FALSE;
574 return(0);
575 }
576 }
577
578 static int
579 spkrioctl(dev, cmd, cmdarg, flags, td)
580 dev_t dev;
581 unsigned long cmd;
582 caddr_t cmdarg;
583 int flags;
584 struct thread *td;
585 {
586 #ifdef DEBUG
587 (void) printf("spkrioctl: entering with dev = %s, cmd = %lx\n",
588 devtoname(dev), cmd);
589 #endif /* DEBUG */
590
591 if (minor(dev) != 0)
592 return(ENXIO);
593 else if (cmd == SPKRTONE)
594 {
595 tone_t *tp = (tone_t *)cmdarg;
596
597 if (tp->frequency == 0)
598 rest(tp->duration);
599 else
600 tone(tp->frequency, tp->duration);
601 return 0;
602 }
603 else if (cmd == SPKRTUNE)
604 {
605 tone_t *tp = (tone_t *)(*(caddr_t *)cmdarg);
606 tone_t ttp;
607 int error;
608
609 for (; ; tp++) {
610 error = copyin(tp, &ttp, sizeof(tone_t));
611 if (error)
612 return(error);
613 if (ttp.duration == 0)
614 break;
615 if (ttp.frequency == 0)
616 rest(ttp.duration);
617 else
618 tone(ttp.frequency, ttp.duration);
619 }
620 return(0);
621 }
622 return(EINVAL);
623 }
624
625 /*
626 * Install placeholder to claim the resources owned by the
627 * AT tone generator.
628 */
629 static struct isa_pnp_id pcspeaker_ids[] = {
630 { 0 }
631 };
632
633 static dev_t pcspeaker_dev;
634
635 static int
636 pcspeaker_probe(device_t dev)
637 {
638 int error;
639
640 error = ISA_PNP_PROBE(device_get_parent(dev), dev, pcspeaker_ids);
641
642 /* PnP match */
643 if (error == 0)
644 return (0);
645
646 /* No match */
647 if (error == ENXIO)
648 return (ENXIO);
649
650 /* Not configured by hints. */
651 if (strncmp(device_get_name(dev), "pcspeaker", 9))
652 return (ENXIO);
653
654 device_set_desc(dev, "PC98 speaker");
655
656 return (0);
657 }
658
659 static int
660 pcspeaker_attach(device_t dev)
661 {
662
663 if (pcspeaker_dev) {
664 device_printf(dev, "Already attached!\n");
665 return (ENXIO);
666 }
667
668 pcspeaker_dev = make_dev(&spkr_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600,
669 "speaker");
670 return (0);
671 }
672
673 static int
674 pcspeaker_detach(device_t dev)
675 {
676 destroy_dev(pcspeaker_dev);
677 return (0);
678 }
679
680 static device_method_t pcspeaker_methods[] = {
681 /* Device interface */
682 DEVMETHOD(device_probe, pcspeaker_probe),
683 DEVMETHOD(device_attach, pcspeaker_attach),
684 DEVMETHOD(device_detach, pcspeaker_detach),
685 DEVMETHOD(device_shutdown, bus_generic_shutdown),
686 DEVMETHOD(device_suspend, bus_generic_suspend),
687 DEVMETHOD(device_resume, bus_generic_resume),
688 { 0, 0 }
689 };
690
691 static driver_t pcspeaker_driver = {
692 "pcspeaker",
693 pcspeaker_methods,
694 1, /* no softc */
695 };
696
697 static devclass_t pcspeaker_devclass;
698
699 DRIVER_MODULE(pcspeaker, isa, pcspeaker_driver, pcspeaker_devclass, 0, 0);
700
701 /* spkr.c ends here */
Cache object: e6ad1e17ee5063a93ceac4fb9c6b5dfe
|