spkr.c revision 38505
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.34 1998/06/07 17:11:00 dfr Exp $
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>
29static void	*devfs_token;
30#endif
31
32static	d_open_t	spkropen;
33static	d_close_t	spkrclose;
34static	d_write_t	spkrwrite;
35static	d_ioctl_t	spkrioctl;
36
37#define CDEV_MAJOR 26
38static 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
63static char endtone, endrest;
64
65static void tone __P((unsigned int thz, unsigned int ticks));
66static void rest __P((int ticks));
67static void playinit __P((void));
68static void playtone __P((int pitch, int value, int sustain));
69static int abs __P((int n));
70static void playstring __P((char *cp, size_t slen));
71
72/* emit tone of frequency thz for given number of ticks */
73static void
74tone(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 */
120static void
121rest(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
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
200playinit()
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 */
211static void
212playtone(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
247static int
248abs(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 */
258static void
259playstring(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
455static int spkr_active = FALSE; /* exclusion flag */
456static struct buf *spkr_inbuf;  /* incoming buf */
457
458int
459spkropen(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
485int
486spkrwrite(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
517int
518spkrclose(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
540int
541spkrioctl(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
588static int spkr_devsw_installed;
589
590static void
591spkr_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
607SYSINIT(spkrdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,spkr_drvinit,NULL)
608
609
610#endif  /* NSPEAKER > 0 */
611/* spkr.c ends here */
612