Annotation of src/sys/dev/sound/isa/i386/spkr/spkr.c, revision 1.1

1.1     ! dillon      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:  */
        !             9: 
        !            10: #include <sys/param.h>
        !            11: #include <sys/systm.h>
        !            12: #include <sys/kernel.h>
        !            13: #include <sys/buf.h>
        !            14: #include <sys/uio.h>
        !            15: #include <sys/conf.h>
        !            16: #include <sys/ctype.h>
        !            17: #include <i386/isa/isa.h>
        !            18: #include <i386/isa/timerreg.h>
        !            19: #include <machine/clock.h>
        !            20: #include <machine/speaker.h>
        !            21: 
        !            22: static d_open_t        spkropen;
        !            23: static d_close_t       spkrclose;
        !            24: static d_write_t       spkrwrite;
        !            25: static d_ioctl_t       spkrioctl;
        !            26: 
        !            27: #define CDEV_MAJOR 26
        !            28: static struct cdevsw spkr_cdevsw = {
        !            29:        /* open */      spkropen,
        !            30:        /* close */     spkrclose,
        !            31:        /* read */      noread,
        !            32:        /* write */     spkrwrite,
        !            33:        /* ioctl */     spkrioctl,
        !            34:        /* poll */      nopoll,
        !            35:        /* mmap */      nommap,
        !            36:        /* strategy */  nostrategy,
        !            37:        /* name */      "spkr",
        !            38:        /* maj */       CDEV_MAJOR,
        !            39:        /* dump */      nodump,
        !            40:        /* psize */     nopsize,
        !            41:        /* flags */     0,
        !            42:        /* bmaj */      -1
        !            43: };
        !            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 ISA 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 ISA 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:  * PPI control values.
        !            60:  * XXX should be in a header and used in clock.c.
        !            61:  */
        !            62: #define PPI_SPKR       0x03    /* turn these PPI bits on to pass sound */
        !            63: 
        !            64: #define SPKRPRI PSOCK
        !            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:     disable_intr();
        !           101:     outb(TIMER_CNTR2, (divisor & 0xff));       /* send lo byte */
        !           102:     outb(TIMER_CNTR2, (divisor >> 8)); /* send hi byte */
        !           103:     enable_intr();
        !           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, SPKRPRI | 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, SPKRPRI | 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, 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 = %s\n", devtoname(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: 
        !           485: int
        !           486: spkrwrite(dev, uio, ioflag)
        !           487:        dev_t           dev;
        !           488:        struct uio      *uio;
        !           489:        int             ioflag;
        !           490: {
        !           491: #ifdef DEBUG
        !           492:     printf("spkrwrite: entering with dev = %s, count = %d\n",
        !           493:                devtoname(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: 
        !           517: int
        !           518: spkrclose(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 = %s\n", devtoname(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: 
        !           540: int
        !           541: spkrioctl(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 = %s, cmd = %lx\n",
        !           550:        devtoname(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: static void
        !           588: spkr_drvinit(void *unused)
        !           589: {
        !           590:        make_dev(&spkr_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "speaker");
        !           591: }
        !           592: 
        !           593: SYSINIT(spkrdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,spkr_drvinit,NULL)
        !           594: 
        !           595: 
        !           596: /* spkr.c ends here */