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