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