01c22067b23e1e065a2753cf8a4ed4ea88dcb93b
[dragonfly.git] / sys / dev / sound / isa / i386 / spkr / 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: src/sys/i386/isa/spkr.c,v 1.45 2000/01/29 16:00:32 peter Exp $
8  * $DragonFly: src/sys/dev/sound/isa/i386/spkr/Attic/spkr.c,v 1.5 2003/07/19 21:14:34 dillon Exp $
9  */
10
11 #include <sys/param.h>
12 #include <sys/systm.h>
13 #include <sys/kernel.h>
14 #include <sys/buf.h>
15 #include <sys/uio.h>
16 #include <sys/conf.h>
17 #include <sys/ctype.h>
18 #include <i386/isa/isa.h>
19 #include <i386/isa/timerreg.h>
20 #include <machine/clock.h>
21 #include <machine/speaker.h>
22
23 static  d_open_t        spkropen;
24 static  d_close_t       spkrclose;
25 static  d_write_t       spkrwrite;
26 static  d_ioctl_t       spkrioctl;
27
28 #define CDEV_MAJOR 26
29 static struct cdevsw spkr_cdevsw = {
30         /* open */      spkropen,
31         /* close */     spkrclose,
32         /* read */      noread,
33         /* write */     spkrwrite,
34         /* ioctl */     spkrioctl,
35         /* poll */      nopoll,
36         /* mmap */      nommap,
37         /* strategy */  nostrategy,
38         /* name */      "spkr",
39         /* maj */       CDEV_MAJOR,
40         /* dump */      nodump,
41         /* psize */     nopsize,
42         /* flags */     0,
43         /* bmaj */      -1
44 };
45
46 /**************** MACHINE DEPENDENT PART STARTS HERE *************************
47  *
48  * This section defines a function tone() which causes a tone of given
49  * frequency and duration from the ISA console speaker.
50  * Another function endtone() is defined to force sound off, and there is
51  * also a rest() entry point to do pauses.
52  *
53  * Audible sound is generated using the Programmable Interval Timer (PIT) and
54  * Programmable Peripheral Interface (PPI) attached to the ISA speaker. The
55  * PPI controls whether sound is passed through at all; the PIT's channel 2 is
56  * used to generate clicks (a square wave) of whatever frequency is desired.
57  */
58
59 /*
60  * PPI control values.
61  * XXX should be in a header and used in clock.c.
62  */
63 #define PPI_SPKR        0x03    /* turn these PPI bits on to pass sound */
64
65 static char endtone, endrest;
66
67 static void tone __P((unsigned int thz, unsigned int ticks));
68 static void rest __P((int ticks));
69 static void playinit __P((void));
70 static void playtone __P((int pitch, int value, int sustain));
71 static int abs __P((int n));
72 static void playstring __P((char *cp, size_t slen));
73
74 /* emit tone of frequency thz for given number of ticks */
75 static void
76 tone(thz, ticks)
77         unsigned int thz, ticks;
78 {
79     unsigned int divisor;
80     int sps;
81
82     if (thz <= 0)
83         return;
84
85     divisor = timer_freq / thz;
86
87 #ifdef DEBUG
88     (void) printf("tone: thz=%d ticks=%d\n", thz, ticks);
89 #endif /* DEBUG */
90
91     /* set timer to generate clicks at given frequency in Hertz */
92     sps = splclock();
93
94     if (acquire_timer2(TIMER_SEL2 | TIMER_SQWAVE | TIMER_16BIT)) {
95         /* enter list of waiting procs ??? */
96         splx(sps);
97         return;
98     }
99     splx(sps);
100     clock_lock();
101     outb(TIMER_CNTR2, (divisor & 0xff));        /* send lo byte */
102     outb(TIMER_CNTR2, (divisor >> 8));  /* send hi byte */
103     clock_unlock();
104
105     /* turn the speaker on */
106     outb(IO_PPI, inb(IO_PPI) | PPI_SPKR);
107
108     /*
109      * Set timeout to endtone function, then give up the timeslice.
110      * This is so other processes can execute while the tone is being
111      * emitted.
112      */
113     if (ticks > 0)
114         tsleep((caddr_t)&endtone, PCATCH, "spkrtn", ticks);
115     outb(IO_PPI, inb(IO_PPI) & ~PPI_SPKR);
116     sps = splclock();
117     release_timer2();
118     splx(sps);
119 }
120
121 /* rest for given number of ticks */
122 static void
123 rest(ticks)
124         int     ticks;
125 {
126     /*
127      * Set timeout to endrest function, then give up the timeslice.
128      * This is so other processes can execute while the rest is being
129      * waited out.
130      */
131 #ifdef DEBUG
132     (void) printf("rest: %d\n", ticks);
133 #endif /* DEBUG */
134     if (ticks > 0)
135         tsleep((caddr_t)&endrest, PCATCH, "spkrrs", ticks);
136 }
137
138 /**************** PLAY STRING INTERPRETER BEGINS HERE **********************
139  *
140  * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement;
141  * M[LNS] are missing; the ~ synonym and the _ slur mark and the octave-
142  * tracking facility are added.
143  * Requires tone(), rest(), and endtone(). String play is not interruptible
144  * except possibly at physical block boundaries.
145  */
146
147 typedef int     bool;
148 #define TRUE    1
149 #define FALSE   0
150
151 #define dtoi(c)         ((c) - '0')
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 - '0'); 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_t dev, int flags, int fmt, struct thread *td)
460 {
461 #ifdef DEBUG
462     (void) printf("spkropen: entering with dev = %s\n", devtoname(dev));
463 #endif /* DEBUG */
464
465     if (minor(dev) != 0)
466         return(ENXIO);
467     else if (spkr_active)
468         return(EBUSY);
469     else
470     {
471 #ifdef DEBUG
472         (void) printf("spkropen: about to perform play initialization\n");
473 #endif /* DEBUG */
474         playinit();
475         spkr_inbuf = geteblk(DEV_BSIZE);
476         spkr_active = TRUE;
477         return(0);
478     }
479 }
480
481 int
482 spkrwrite(dev, uio, ioflag)
483         dev_t           dev;
484         struct uio      *uio;
485         int             ioflag;
486 {
487 #ifdef DEBUG
488     printf("spkrwrite: entering with dev = %s, count = %d\n",
489                 devtoname(dev), uio->uio_resid);
490 #endif /* DEBUG */
491
492     if (minor(dev) != 0)
493         return(ENXIO);
494     else if (uio->uio_resid > (DEV_BSIZE - 1))     /* prevent system crashes */
495         return(E2BIG);
496     else
497     {
498         unsigned n;
499         char *cp;
500         int error;
501
502         n = uio->uio_resid;
503         cp = spkr_inbuf->b_data;
504         error = uiomove(cp, n, uio);
505         if (!error) {
506                 cp[n] = '\0';
507                 playstring(cp, n);
508         }
509         return(error);
510     }
511 }
512
513 int
514 spkrclose(dev_t dev, int flags, int fmt, struct thread *td)
515 {
516 #ifdef DEBUG
517     (void) printf("spkrclose: entering with dev = %s\n", devtoname(dev));
518 #endif /* DEBUG */
519
520     if (minor(dev) != 0)
521         return(ENXIO);
522     else
523     {
524         wakeup((caddr_t)&endtone);
525         wakeup((caddr_t)&endrest);
526         brelse(spkr_inbuf);
527         spkr_active = FALSE;
528         return(0);
529     }
530 }
531
532 int
533 spkrioctl(dev_t dev, unsigned long cmd, caddr_t cmdarg, int flags, struct thread*td)
534 {
535 #ifdef DEBUG
536     (void) printf("spkrioctl: entering with dev = %s, cmd = %lx\n",
537         devtoname(dev), cmd);
538 #endif /* DEBUG */
539
540     if (minor(dev) != 0)
541         return(ENXIO);
542     else if (cmd == SPKRTONE)
543     {
544         tone_t  *tp = (tone_t *)cmdarg;
545
546         if (tp->frequency == 0)
547             rest(tp->duration);
548         else
549             tone(tp->frequency, tp->duration);
550         return 0;
551     }
552     else if (cmd == SPKRTUNE)
553     {
554         tone_t  *tp = (tone_t *)(*(caddr_t *)cmdarg);
555         tone_t ttp;
556         int error;
557
558         for (; ; tp++) {
559             error = copyin(tp, &ttp, sizeof(tone_t));
560             if (error)
561                     return(error);
562             if (ttp.duration == 0)
563                     break;
564             if (ttp.frequency == 0)
565                  rest(ttp.duration);
566             else
567                  tone(ttp.frequency, ttp.duration);
568         }
569         return(0);
570     }
571     return(EINVAL);
572 }
573
574 static void
575 spkr_drvinit(void *unused)
576 {
577         make_dev(&spkr_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "speaker");
578 }
579
580 SYSINIT(spkrdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,spkr_drvinit,NULL)
581
582
583 /* spkr.c ends here */