Index: head/sys/dev/sound/pcm/ac97.c =================================================================== --- head/sys/dev/sound/pcm/ac97.c (revision 150824) +++ head/sys/dev/sound/pcm/ac97.c (revision 150825) @@ -1,916 +1,957 @@ /*- * Copyright (c) 1999 Cameron Grant * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include "mixer_if.h" SND_DECLARE_FILE("$FreeBSD$"); MALLOC_DEFINE(M_AC97, "ac97", "ac97 codec"); struct ac97mixtable_entry { int reg:8; /* register index */ /* reg < 0 if inverted polarity */ unsigned bits:4; /* width of control field */ unsigned ofs:4; /* offset (only if stereo=0) */ unsigned stereo:1; /* set for stereo controls */ unsigned mute:1; /* bit15 is MUTE */ unsigned recidx:4; /* index in rec mux */ unsigned mask:1; /* use only masked bits */ unsigned enable:1; /* entry is enabled */ }; #define AC97_NAMELEN 16 struct ac97_info { kobj_t methods; device_t dev; void *devinfo; u_int32_t id; unsigned count, caps, se, extcaps, extid, extstat, noext:1; u_int32_t flags; struct ac97mixtable_entry mix[32]; char name[AC97_NAMELEN]; struct mtx *lock; }; struct ac97_vendorid { u_int32_t id; const char *name; }; struct ac97_codecid { u_int32_t id; u_int8_t stepmask; u_int8_t noext:1; char *name; ac97_patch patch; }; static const struct ac97mixtable_entry ac97mixtable_default[32] = { /* [offset] reg bits of st mu re mk en */ [SOUND_MIXER_VOLUME] = { AC97_MIX_MASTER, 5, 0, 1, 1, 6, 0, 1 }, [SOUND_MIXER_OGAIN] = { AC97_MIX_AUXOUT, 5, 0, 1, 1, 0, 0, 0 }, [SOUND_MIXER_PHONEOUT] = { AC97_MIX_MONO, 5, 0, 0, 1, 7, 0, 0 }, [SOUND_MIXER_BASS] = { AC97_MIX_TONE, 4, 8, 0, 0, 0, 1, 0 }, [SOUND_MIXER_TREBLE] = { AC97_MIX_TONE, 4, 0, 0, 0, 0, 1, 0 }, [SOUND_MIXER_PCM] = { AC97_MIX_PCM, 5, 0, 1, 1, 0, 0, 1 }, [SOUND_MIXER_SPEAKER] = { AC97_MIX_BEEP, 4, 1, 0, 1, 0, 0, 0 }, [SOUND_MIXER_LINE] = { AC97_MIX_LINE, 5, 0, 1, 1, 5, 0, 1 }, [SOUND_MIXER_PHONEIN] = { AC97_MIX_PHONE, 5, 0, 0, 1, 8, 0, 0 }, [SOUND_MIXER_MIC] = { AC97_MIX_MIC, 5, 0, 0, 1, 1, 1, 1 }, #if 0 /* use igain for the mic 20dB boost */ [SOUND_MIXER_IGAIN] = { -AC97_MIX_MIC, 1, 6, 0, 0, 0, 1, 1 }, #endif [SOUND_MIXER_CD] = { AC97_MIX_CD, 5, 0, 1, 1, 2, 0, 1 }, [SOUND_MIXER_LINE1] = { AC97_MIX_AUX, 5, 0, 1, 1, 4, 0, 0 }, [SOUND_MIXER_VIDEO] = { AC97_MIX_VIDEO, 5, 0, 1, 1, 3, 0, 0 }, [SOUND_MIXER_RECLEV] = { -AC97_MIX_RGAIN, 4, 0, 1, 1, 0, 0, 1 } }; static const struct ac97_vendorid ac97vendorid[] = { { 0x41445300, "Analog Devices" }, { 0x414b4d00, "Asahi Kasei" }, { 0x414c4300, "Realtek" }, { 0x414c4700, "Avance Logic" }, { 0x43525900, "Cirrus Logic" }, { 0x434d4900, "C-Media Electronics" }, { 0x43585400, "Conexant" }, { 0x44543000, "Diamond Technology" }, { 0x454d4300, "eMicro" }, { 0x45838300, "ESS Technology" }, { 0x48525300, "Intersil" }, { 0x49434500, "ICEnsemble" }, { 0x49544500, "ITE, Inc." }, { 0x4e534300, "National Semiconductor" }, { 0x50534300, "Philips Semiconductor" }, { 0x83847600, "SigmaTel" }, { 0x53494c00, "Silicon Laboratories" }, { 0x54524100, "TriTech" }, { 0x54584e00, "Texas Instruments" }, { 0x56494100, "VIA Technologies" }, { 0x57454300, "Winbond" }, { 0x574d4c00, "Wolfson" }, { 0x594d4800, "Yamaha" }, /* * XXX This is a fluke, really! The real vendor * should be SigmaTel, not this! This should be * removed someday! */ { 0x01408300, "Creative" }, { 0x00000000, NULL } }; static struct ac97_codecid ac97codecid[] = { { 0x41445303, 0x00, 0, "AD1819", 0 }, { 0x41445340, 0x00, 0, "AD1881", 0 }, { 0x41445348, 0x00, 0, "AD1881A", 0 }, { 0x41445360, 0x00, 0, "AD1885", 0 }, { 0x41445361, 0x00, 0, "AD1886", ad1886_patch }, { 0x41445362, 0x00, 0, "AD1887", 0 }, { 0x41445363, 0x00, 0, "AD1886A", 0 }, { 0x41445368, 0x00, 0, "AD1888", ad198x_patch }, { 0x41445370, 0x00, 0, "AD1980", ad198x_patch }, { 0x41445372, 0x00, 0, "AD1981A", 0 }, { 0x41445374, 0x00, 0, "AD1981B", 0 }, { 0x41445375, 0x00, 0, "AD1985", ad198x_patch }, { 0x414b4d00, 0x00, 1, "AK4540", 0 }, { 0x414b4d01, 0x00, 1, "AK4542", 0 }, { 0x414b4d02, 0x00, 1, "AK4543", 0 }, { 0x414b4d06, 0x00, 0, "AK4544A", 0 }, { 0x454b4d07, 0x00, 0, "AK4545", 0 }, { 0x414c4320, 0x0f, 0, "ALC100", 0 }, { 0x414c4730, 0x0f, 0, "ALC101", 0 }, { 0x414c4710, 0x0f, 0, "ALC200", 0 }, { 0x414c4740, 0x0f, 0, "ALC202", 0 }, { 0x414c4720, 0x0f, 0, "ALC650", 0 }, { 0x414c4760, 0x0f, 0, "ALC655", 0 }, { 0x414c4780, 0x0f, 0, "ALC658", 0 }, { 0x414c4790, 0x0f, 0, "ALC850", 0 }, { 0x43525900, 0x07, 0, "CS4297", 0 }, { 0x43525910, 0x07, 0, "CS4297A", 0 }, { 0x43525920, 0x07, 0, "CS4294/98", 0 }, { 0x4352592d, 0x07, 0, "CS4294", 0 }, { 0x43525930, 0x07, 0, "CS4299", 0 }, { 0x43525940, 0x07, 0, "CS4201", 0 }, { 0x43525958, 0x07, 0, "CS4205", 0 }, { 0x43525960, 0x07, 0, "CS4291A", 0 }, - { 0x434d4961, 0x00, 0, "CMI9739", 0 }, + { 0x434d4961, 0x00, 0, "CMI9739", cmi9739_patch }, { 0x434d4941, 0x00, 0, "CMI9738", 0 }, + { 0x434d4978, 0x00, 0, "CMI9761", 0 }, + { 0x434d4982, 0x00, 0, "CMI9761", 0 }, + { 0x434d4983, 0x00, 0, "CMI9761", 0 }, { 0x43585421, 0x00, 0, "HSD11246", 0 }, { 0x43585428, 0x07, 0, "CX20468", 0 }, { 0x44543000, 0x00, 0, "DT0398", 0 }, { 0x454d4323, 0x00, 0, "EM28023", 0 }, { 0x454d4328, 0x00, 0, "EM28028", 0 }, { 0x45838308, 0x00, 0, "ES1988", 0 }, /* Formerly ES1921(?) */ { 0x48525300, 0x00, 0, "HMP9701", 0 }, { 0x49434501, 0x00, 0, "ICE1230", 0 }, { 0x49434511, 0x00, 0, "ICE1232", 0 }, { 0x49434514, 0x00, 0, "ICE1232A", 0 }, { 0x49434551, 0x03, 0, "VT1616", 0 }, /* Via badged ICE */ { 0x49544520, 0x00, 0, "ITE2226E", 0 }, { 0x49544560, 0x07, 0, "ITE2646E", 0 }, /* XXX: patch needed */ { 0x4e534340, 0x00, 0, "LM4540", 0 }, /* Spec blank on revid */ { 0x4e534343, 0x00, 0, "LM4543", 0 }, /* Ditto */ { 0x4e534346, 0x00, 0, "LM4546A", 0 }, { 0x4e534348, 0x00, 0, "LM4548A", 0 }, { 0x4e534331, 0x00, 0, "LM4549", 0 }, { 0x4e534349, 0x00, 0, "LM4549A", 0 }, { 0x4e534350, 0x00, 0, "LM4550", 0 }, { 0x50534301, 0x00, 0, "UCB1510", 0 }, { 0x50534304, 0x00, 0, "UCB1400", 0 }, { 0x83847600, 0x00, 0, "STAC9700/83/84", 0 }, { 0x83847604, 0x00, 0, "STAC9701/03/04/05", 0 }, { 0x83847605, 0x00, 0, "STAC9704", 0 }, { 0x83847608, 0x00, 0, "STAC9708/11", 0 }, { 0x83847609, 0x00, 0, "STAC9721/23", 0 }, { 0x83847644, 0x00, 0, "STAC9744/45", 0 }, { 0x83847650, 0x00, 0, "STAC9750/51", 0 }, { 0x83847652, 0x00, 0, "STAC9752/53", 0 }, { 0x83847656, 0x00, 0, "STAC9756/57", 0 }, { 0x83847658, 0x00, 0, "STAC9758/59", 0 }, { 0x83847660, 0x00, 0, "STAC9760/61", 0 }, /* Extrapolated */ { 0x83847662, 0x00, 0, "STAC9762/63", 0 }, /* Extrapolated */ + { 0x83847666, 0x00, 0, "STAC9766/67", 0 }, { 0x53494c22, 0x00, 0, "Si3036", 0 }, { 0x53494c23, 0x00, 0, "Si3038", 0 }, { 0x54524103, 0x00, 0, "TR28023", 0 }, /* Extrapolated */ { 0x54524106, 0x00, 0, "TR28026", 0 }, { 0x54524108, 0x00, 0, "TR28028", 0 }, { 0x54524123, 0x00, 0, "TR28602", 0 }, { 0x54524e03, 0x07, 0, "TLV320AIC27", 0 }, { 0x54584e20, 0x00, 0, "TLC320AD90", 0 }, { 0x56494161, 0x00, 0, "VIA1612A", 0 }, { 0x574d4c00, 0x00, 0, "WM9701A", 0 }, { 0x574d4c03, 0x00, 0, "WM9703/4/7/8", 0 }, { 0x574d4c04, 0x00, 0, "WM9704Q", 0 }, { 0x574d4c05, 0x00, 0, "WM9705/10", 0 }, { 0x574d4d09, 0x00, 0, "WM9709", 0 }, { 0x574d4c12, 0x00, 0, "WM9711/12", 0 }, /* XXX: patch needed */ { 0x57454301, 0x00, 0, "W83971D", 0 }, { 0x594d4800, 0x00, 0, "YMF743", 0 }, { 0x594d4802, 0x00, 0, "YMF752", 0 }, { 0x594d4803, 0x00, 0, "YMF753", 0 }, /* * XXX This is a fluke, really! The real codec * should be STAC9704, not this! This should be * removed someday! */ { 0x01408384, 0x00, 0, "EV1938", 0 }, { 0, 0, 0, NULL, 0 } }; static char *ac97enhancement[] = { "no 3D Stereo Enhancement", "Analog Devices Phat Stereo", "Creative Stereo Enhancement", "National Semi 3D Stereo Enhancement", "Yamaha Ymersion", "BBE 3D Stereo Enhancement", "Crystal Semi 3D Stereo Enhancement", "Qsound QXpander", "Spatializer 3D Stereo Enhancement", "SRS 3D Stereo Enhancement", "Platform Tech 3D Stereo Enhancement", "AKM 3D Audio", "Aureal Stereo Enhancement", "Aztech 3D Enhancement", "Binaura 3D Audio Enhancement", "ESS Technology Stereo Enhancement", "Harman International VMAx", "Nvidea 3D Stereo Enhancement", "Philips Incredible Sound", "Texas Instruments 3D Stereo Enhancement", "VLSI Technology 3D Stereo Enhancement", "TriTech 3D Stereo Enhancement", "Realtek 3D Stereo Enhancement", "Samsung 3D Stereo Enhancement", "Wolfson Microelectronics 3D Enhancement", "Delta Integration 3D Enhancement", "SigmaTel 3D Enhancement", "Reserved 27", "Rockwell 3D Stereo Enhancement", "Reserved 29", "Reserved 30", "Reserved 31" }; static char *ac97feature[] = { "mic channel", "reserved", "tone", "simulated stereo", "headphone", "bass boost", "18 bit DAC", "20 bit DAC", "18 bit ADC", "20 bit ADC" }; static char *ac97extfeature[] = { "variable rate PCM", "double rate PCM", "reserved 1", "variable rate mic", "reserved 2", "reserved 3", "center DAC", "surround DAC", "LFE DAC", "AMAP", "reserved 4", "reserved 5", "reserved 6", "reserved 7", }; u_int16_t ac97_rdcd(struct ac97_info *codec, int reg) { if (codec->flags & AC97_F_RDCD_BUG) { u_int16_t i[2], j = 100; i[0] = AC97_READ(codec->methods, codec->devinfo, reg); i[1] = AC97_READ(codec->methods, codec->devinfo, reg); while (i[0] != i[1] && j) i[j-- & 1] = AC97_READ(codec->methods, codec->devinfo, reg); #if 0 if (j < 100) { device_printf(codec->dev, "%s(): Inconsistent register value at" " 0x%08x (retry: %d)\n", __func__, reg, 100 - j); } #endif return i[!(j & 1)]; } return AC97_READ(codec->methods, codec->devinfo, reg); } void ac97_wrcd(struct ac97_info *codec, int reg, u_int16_t val) { AC97_WRITE(codec->methods, codec->devinfo, reg, val); } static void ac97_reset(struct ac97_info *codec) { u_int32_t i, ps; ac97_wrcd(codec, AC97_REG_RESET, 0); for (i = 0; i < 500; i++) { ps = ac97_rdcd(codec, AC97_REG_POWER) & AC97_POWER_STATUS; if (ps == AC97_POWER_STATUS) return; DELAY(1000); } device_printf(codec->dev, "AC97 reset timed out.\n"); } int ac97_setrate(struct ac97_info *codec, int which, int rate) { u_int16_t v; switch(which) { case AC97_REGEXT_FDACRATE: case AC97_REGEXT_SDACRATE: case AC97_REGEXT_LDACRATE: case AC97_REGEXT_LADCRATE: case AC97_REGEXT_MADCRATE: break; default: return -1; } snd_mtxlock(codec->lock); if (rate != 0) { v = rate; if (codec->extstat & AC97_EXTCAP_DRA) v >>= 1; ac97_wrcd(codec, which, v); } v = ac97_rdcd(codec, which); if (codec->extstat & AC97_EXTCAP_DRA) v <<= 1; snd_mtxunlock(codec->lock); return v; } int ac97_setextmode(struct ac97_info *codec, u_int16_t mode) { mode &= AC97_EXTCAPS; if ((mode & ~codec->extcaps) != 0) { device_printf(codec->dev, "ac97 invalid mode set 0x%04x\n", mode); return -1; } snd_mtxlock(codec->lock); ac97_wrcd(codec, AC97_REGEXT_STAT, mode); codec->extstat = ac97_rdcd(codec, AC97_REGEXT_STAT) & AC97_EXTCAPS; snd_mtxunlock(codec->lock); return (mode == codec->extstat)? 0 : -1; } u_int16_t ac97_getextmode(struct ac97_info *codec) { return codec->extstat; } u_int16_t ac97_getextcaps(struct ac97_info *codec) { return codec->extcaps; } u_int16_t ac97_getcaps(struct ac97_info *codec) { return codec->caps; } static int ac97_setrecsrc(struct ac97_info *codec, int channel) { struct ac97mixtable_entry *e = &codec->mix[channel]; if (e->recidx > 0) { int val = e->recidx - 1; val |= val << 8; snd_mtxlock(codec->lock); ac97_wrcd(codec, AC97_REG_RECSEL, val); snd_mtxunlock(codec->lock); return 0; } else return -1; } static int ac97_setmixer(struct ac97_info *codec, unsigned channel, unsigned left, unsigned right) { struct ac97mixtable_entry *e = &codec->mix[channel]; if (e->reg && e->enable && e->bits) { int mask, max, val, reg; reg = (e->reg >= 0) ? e->reg : -e->reg; /* AC97 register */ max = (1 << e->bits) - 1; /* actual range */ mask = (max << 8) | max; /* bits of interest */ if (!e->stereo) right = left; /* * Invert the range if the polarity requires so, * then scale to 0..max-1 to compute the value to * write into the codec, and scale back to 0..100 * for the return value. */ if (e->reg > 0) { left = 100 - left; right = 100 - right; } left = (left * max) / 100; right = (right * max) / 100; val = (left << 8) | right; left = (left * 100) / max; right = (right * 100) / max; if (e->reg > 0) { left = 100 - left; right = 100 - right; } /* * For mono controls, trim val and mask, also taking * care of e->ofs (offset of control field). */ if (e->ofs) { val &= max; val <<= e->ofs; mask = (max << e->ofs); } /* * If we have a mute bit, add it to the mask and * update val and set mute if both channels require a * zero volume. */ if (e->mute == 1) { mask |= AC97_MUTE; if (left == 0 && right == 0) val = AC97_MUTE; } /* * If the mask bit is set, do not alter the other bits. */ snd_mtxlock(codec->lock); if (e->mask) { int cur = ac97_rdcd(codec, reg); val |= cur & ~(mask); } ac97_wrcd(codec, reg, val); snd_mtxunlock(codec->lock); return left | (right << 8); } else { #if 0 printf("ac97_setmixer: reg=%d, bits=%d, enable=%d\n", e->reg, e->bits, e->enable); #endif return -1; } } static void ac97_fix_auxout(struct ac97_info *codec) { int keep_ogain; /* * By default, The ac97 aux_out register (0x04) corresponds to OSS's * OGAIN setting. * * We first check whether aux_out is a valid register. If not * we may not want to keep ogain. */ keep_ogain = ac97_rdcd(codec, AC97_MIX_AUXOUT) & 0x8000; /* * Determine what AUX_OUT really means, it can be: * * 1. Headphone out. * 2. 4-Channel Out * 3. True line level out (effectively master volume). * * See Sections 5.2.1 and 5.27 for AUX_OUT Options in AC97r2.{2,3}. */ if (codec->extcaps & AC97_EXTCAP_SDAC && ac97_rdcd(codec, AC97_MIXEXT_SURROUND) == 0x8080) { codec->mix[SOUND_MIXER_OGAIN].reg = AC97_MIXEXT_SURROUND; keep_ogain = 1; } if (keep_ogain == 0) { bzero(&codec->mix[SOUND_MIXER_OGAIN], sizeof(codec->mix[SOUND_MIXER_OGAIN])); } } static void ac97_fix_tone(struct ac97_info *codec) { /* Hide treble and bass if they don't exist */ if ((codec->caps & AC97_CAP_TONE) == 0) { bzero(&codec->mix[SOUND_MIXER_BASS], sizeof(codec->mix[SOUND_MIXER_BASS])); bzero(&codec->mix[SOUND_MIXER_TREBLE], sizeof(codec->mix[SOUND_MIXER_TREBLE])); } } +static void +ac97_fix_volume(struct ac97_info *codec) +{ + struct snddev_info *d = device_get_softc(codec->dev); + +#if 0 + /* XXX For the sake of debugging purposes */ + ac97_wrcd(codec, AC97_MIX_PCM, 0); + bzero(&codec->mix[SOUND_MIXER_PCM], + sizeof(codec->mix[SOUND_MIXER_PCM])); + codec->flags |= AC97_F_SOFTVOL; + if (d) + d->flags |= SD_F_SOFTVOL; + return; +#endif + switch (codec->id) { + case 0x434d4941: /* CMI9738 */ + case 0x434d4961: /* CMI9739 */ + case 0x434d4978: /* CMI9761 */ + case 0x434d4982: /* CMI9761 */ + case 0x434d4983: /* CMI9761 */ + ac97_wrcd(codec, AC97_MIX_PCM, 0); + break; + default: + return; + break; + } + bzero(&codec->mix[SOUND_MIXER_PCM], + sizeof(codec->mix[SOUND_MIXER_PCM])); + codec->flags |= AC97_F_SOFTVOL; + if (d) + d->flags |= SD_F_SOFTVOL; +} + static const char* ac97_hw_desc(u_int32_t id, const char* vname, const char* cname, char* buf) { if (cname == NULL) { sprintf(buf, "Unknown AC97 Codec (id = 0x%08x)", id); return buf; } if (vname == NULL) vname = "Unknown"; if (bootverbose) { sprintf(buf, "%s %s AC97 Codec (id = 0x%08x)", vname, cname, id); } else { sprintf(buf, "%s %s AC97 Codec", vname, cname); } return buf; } static unsigned ac97_initmixer(struct ac97_info *codec) { ac97_patch codec_patch; const char *cname, *vname; char desc[80]; u_int8_t model, step; unsigned i, j, k, bit, old; u_int32_t id; int reg; snd_mtxlock(codec->lock); codec->count = AC97_INIT(codec->methods, codec->devinfo); if (codec->count == 0) { device_printf(codec->dev, "ac97 codec init failed\n"); snd_mtxunlock(codec->lock); return ENODEV; } ac97_wrcd(codec, AC97_REG_POWER, (codec->flags & AC97_F_EAPD_INV)? 0x8000 : 0x0000); ac97_reset(codec); ac97_wrcd(codec, AC97_REG_POWER, (codec->flags & AC97_F_EAPD_INV)? 0x8000 : 0x0000); i = ac97_rdcd(codec, AC97_REG_RESET); j = ac97_rdcd(codec, AC97_REG_RESET); /* * Let see if this codec can return consistent value. * If not, turn on aggressive read workaround * (STAC9704 comes in mind). */ if (i != j) { codec->flags |= AC97_F_RDCD_BUG; i = ac97_rdcd(codec, AC97_REG_RESET); } codec->caps = i & 0x03ff; codec->se = (i & 0x7c00) >> 10; id = (ac97_rdcd(codec, AC97_REG_ID1) << 16) | ac97_rdcd(codec, AC97_REG_ID2); if (id == 0 || id == 0xffffffff) { device_printf(codec->dev, "ac97 codec invalid or not present (id == %x)\n", id); snd_mtxunlock(codec->lock); return ENODEV; } codec->id = id; codec->noext = 0; codec_patch = NULL; cname = NULL; model = step = 0; for (i = 0; ac97codecid[i].id; i++) { u_int32_t modelmask = 0xffffffff ^ ac97codecid[i].stepmask; if ((ac97codecid[i].id & modelmask) == (id & modelmask)) { codec->noext = ac97codecid[i].noext; codec_patch = ac97codecid[i].patch; cname = ac97codecid[i].name; model = (id & modelmask) & 0xff; step = (id & ~modelmask) & 0xff; break; } } vname = NULL; for (i = 0; ac97vendorid[i].id; i++) { if (ac97vendorid[i].id == (id & 0xffffff00)) { vname = ac97vendorid[i].name; break; } } codec->extcaps = 0; codec->extid = 0; codec->extstat = 0; if (!codec->noext) { i = ac97_rdcd(codec, AC97_REGEXT_ID); if (i != 0xffff) { codec->extcaps = i & 0x3fff; codec->extid = (i & 0xc000) >> 14; codec->extstat = ac97_rdcd(codec, AC97_REGEXT_STAT) & AC97_EXTCAPS; } } for (i = 0; i < 32; i++) { codec->mix[i] = ac97mixtable_default[i]; } ac97_fix_auxout(codec); ac97_fix_tone(codec); + ac97_fix_volume(codec); if (codec_patch) codec_patch(codec); for (i = 0; i < 32; i++) { k = codec->noext? codec->mix[i].enable : 1; reg = codec->mix[i].reg; if (reg < 0) reg = -reg; if (k && reg) { j = old = ac97_rdcd(codec, reg); /* * Test for mute bit (except for AC97_MIX_TONE, * where we simply assume it as available). */ if (codec->mix[i].mute) { ac97_wrcd(codec, reg, j | 0x8000); j = ac97_rdcd(codec, reg); } else j |= 0x8000; if ((j & 0x8000)) { /* * Test whether the control width should be * 4, 5 or 6 bit. For 5bit register, we should * test it whether it's really 5 or 6bit. Leave * 4bit register alone, because sometimes an * attempt to write past 4th bit may cause * incorrect result especially for AC97_MIX_BEEP * (ac97 2.3). */ bit = codec->mix[i].bits; if (bit == 5) bit++; j = ((1 << bit) - 1) << codec->mix[i].ofs; ac97_wrcd(codec, reg, j | (codec->mix[i].mute ? 0x8000 : 0)); k = ac97_rdcd(codec, reg) & j; k >>= codec->mix[i].ofs; if (reg == AC97_MIX_TONE && ((k & 0x0001) == 0x0000)) k >>= 1; for (j = 0; k >> j; j++) ; if (j != 0) { #if 0 device_printf(codec->dev, "%2d: [ac97_rdcd() = %d] [Testbit = %d] %d -> %d\n", i, k, bit, codec->mix[i].bits, j); #endif codec->mix[i].enable = 1; codec->mix[i].bits = j; } else codec->mix[i].enable = 0; } else codec->mix[i].enable = 0; ac97_wrcd(codec, reg, old); } #if 0 printf("mixch %d, en=%d, b=%d\n", i, codec->mix[i].enable, codec->mix[i].bits); #endif } device_printf(codec->dev, "<%s>\n", ac97_hw_desc(codec->id, vname, cname, desc)); if (bootverbose) { if (codec->flags & AC97_F_RDCD_BUG) device_printf(codec->dev, "Buggy AC97 Codec: aggressive ac97_rdcd() workaround enabled\n"); + if (codec->flags & AC97_F_SOFTVOL) + device_printf(codec->dev, "Soft PCM volume\n"); device_printf(codec->dev, "Codec features "); for (i = j = 0; i < 10; i++) if (codec->caps & (1 << i)) printf("%s%s", j++? ", " : "", ac97feature[i]); printf("%s%d bit master volume", j++? ", " : "", codec->mix[SOUND_MIXER_VOLUME].bits); printf("%s%s\n", j? ", " : "", ac97enhancement[codec->se]); if (codec->extcaps != 0 || codec->extid) { device_printf(codec->dev, "%s codec", codec->extid? "Secondary" : "Primary"); if (codec->extcaps) printf(" extended features "); for (i = j = 0; i < 14; i++) if (codec->extcaps & (1 << i)) printf("%s%s", j++? ", " : "", ac97extfeature[i]); printf("\n"); } } i = 0; while ((ac97_rdcd(codec, AC97_REG_POWER) & 2) == 0) { if (++i == 100) { device_printf(codec->dev, "ac97 codec reports dac not ready\n"); break; } DELAY(1000); } if (bootverbose) device_printf(codec->dev, "ac97 codec dac ready count: %d\n", i); snd_mtxunlock(codec->lock); return 0; } static unsigned ac97_reinitmixer(struct ac97_info *codec) { snd_mtxlock(codec->lock); codec->count = AC97_INIT(codec->methods, codec->devinfo); if (codec->count == 0) { device_printf(codec->dev, "ac97 codec init failed\n"); snd_mtxunlock(codec->lock); return ENODEV; } ac97_wrcd(codec, AC97_REG_POWER, (codec->flags & AC97_F_EAPD_INV)? 0x8000 : 0x0000); ac97_reset(codec); ac97_wrcd(codec, AC97_REG_POWER, (codec->flags & AC97_F_EAPD_INV)? 0x8000 : 0x0000); if (!codec->noext) { ac97_wrcd(codec, AC97_REGEXT_STAT, codec->extstat); if ((ac97_rdcd(codec, AC97_REGEXT_STAT) & AC97_EXTCAPS) != codec->extstat) device_printf(codec->dev, "ac97 codec failed to reset extended mode (%x, got %x)\n", codec->extstat, ac97_rdcd(codec, AC97_REGEXT_STAT) & AC97_EXTCAPS); } if ((ac97_rdcd(codec, AC97_REG_POWER) & 2) == 0) device_printf(codec->dev, "ac97 codec reports dac not ready\n"); snd_mtxunlock(codec->lock); return 0; } struct ac97_info * ac97_create(device_t dev, void *devinfo, kobj_class_t cls) { struct ac97_info *codec; codec = (struct ac97_info *)malloc(sizeof *codec, M_AC97, M_NOWAIT); if (codec == NULL) return NULL; snprintf(codec->name, AC97_NAMELEN, "%s:ac97", device_get_nameunit(dev)); codec->lock = snd_mtxcreate(codec->name, "ac97 codec"); codec->methods = kobj_create(cls, M_AC97, M_WAITOK); if (codec->methods == NULL) { snd_mtxlock(codec->lock); snd_mtxfree(codec->lock); free(codec, M_AC97); return NULL; } codec->dev = dev; codec->devinfo = devinfo; codec->flags = 0; return codec; } void ac97_destroy(struct ac97_info *codec) { snd_mtxlock(codec->lock); if (codec->methods != NULL) kobj_delete(codec->methods, M_AC97); snd_mtxfree(codec->lock); free(codec, M_AC97); } void ac97_setflags(struct ac97_info *codec, u_int32_t val) { codec->flags = val; } u_int32_t ac97_getflags(struct ac97_info *codec) { return codec->flags; } /* -------------------------------------------------------------------- */ static int ac97mix_init(struct snd_mixer *m) { struct ac97_info *codec = mix_getdevinfo(m); u_int32_t i, mask; if (codec == NULL) return -1; if (ac97_initmixer(codec)) return -1; mask = 0; for (i = 0; i < 32; i++) mask |= codec->mix[i].enable? 1 << i : 0; mix_setdevs(m, mask); mask = 0; for (i = 0; i < 32; i++) mask |= codec->mix[i].recidx? 1 << i : 0; mix_setrecdevs(m, mask); return 0; } static int ac97mix_uninit(struct snd_mixer *m) { struct ac97_info *codec = mix_getdevinfo(m); if (codec == NULL) return -1; /* if (ac97_uninitmixer(codec)) return -1; */ ac97_destroy(codec); return 0; } static int ac97mix_reinit(struct snd_mixer *m) { struct ac97_info *codec = mix_getdevinfo(m); if (codec == NULL) return -1; return ac97_reinitmixer(codec); } static int ac97mix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct ac97_info *codec = mix_getdevinfo(m); if (codec == NULL) return -1; return ac97_setmixer(codec, dev, left, right); } static int ac97mix_setrecsrc(struct snd_mixer *m, u_int32_t src) { int i; struct ac97_info *codec = mix_getdevinfo(m); if (codec == NULL) return -1; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) if ((src & (1 << i)) != 0) break; return (ac97_setrecsrc(codec, i) == 0)? 1 << i : -1; } static kobj_method_t ac97mixer_methods[] = { KOBJMETHOD(mixer_init, ac97mix_init), KOBJMETHOD(mixer_uninit, ac97mix_uninit), KOBJMETHOD(mixer_reinit, ac97mix_reinit), KOBJMETHOD(mixer_set, ac97mix_set), KOBJMETHOD(mixer_setrecsrc, ac97mix_setrecsrc), { 0, 0 } }; MIXER_DECLARE(ac97mixer); /* -------------------------------------------------------------------- */ kobj_class_t ac97_getmixerclass(void) { return &ac97mixer_class; } Index: head/sys/dev/sound/pcm/ac97.h =================================================================== --- head/sys/dev/sound/pcm/ac97.h (revision 150824) +++ head/sys/dev/sound/pcm/ac97.h (revision 150825) @@ -1,107 +1,108 @@ /*- * Copyright (c) 1999 Cameron Grant * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #define AC97_MUTE 0x8080 #define AC97_REG_RESET 0x00 #define AC97_CAP_MICCHANNEL (1 << 0) #define AC97_CAP_TONE (1 << 2) #define AC97_CAP_SIMSTEREO (1 << 3) #define AC97_CAP_HEADPHONE (1 << 4) #define AC97_CAP_LOUDNESS (1 << 5) #define AC97_CAP_DAC_18 (1 << 6) #define AC97_CAP_DAC_20 (1 << 7) #define AC97_CAP_ADC_18 (1 << 8) #define AC97_CAP_ADC_20 (1 << 9) #define AC97_MIX_MASTER 0x02 #define AC97_MIX_AUXOUT 0x04 #define AC97_MIX_MONO 0x06 #define AC97_MIX_TONE 0x08 #define AC97_MIX_BEEP 0x0a #define AC97_MIX_PHONE 0x0c #define AC97_MIX_MIC 0x0e #define AC97_MIX_LINE 0x10 #define AC97_MIX_CD 0x12 #define AC97_MIX_VIDEO 0x14 #define AC97_MIX_AUX 0x16 #define AC97_MIX_PCM 0x18 #define AC97_REG_RECSEL 0x1a #define AC97_MIX_RGAIN 0x1c #define AC97_MIX_MGAIN 0x1e #define AC97_REG_GEN 0x20 #define AC97_REG_3D 0x22 #define AC97_REG_POWER 0x26 #define AC97_POWER_ADC (1 << 0) #define AC97_POWER_DAC (1 << 1) #define AC97_POWER_ANL (1 << 2) #define AC97_POWER_REF (1 << 3) #define AC97_POWER_STATUS (AC97_POWER_ADC | AC97_POWER_DAC | \ AC97_POWER_REF | AC97_POWER_ANL ) #define AC97_REGEXT_ID 0x28 #define AC97_EXTCAP_VRA (1 << 0) #define AC97_EXTCAP_DRA (1 << 1) #define AC97_EXTCAP_VRM (1 << 3) #define AC97_EXTCAPS (AC97_EXTCAP_VRA | AC97_EXTCAP_DRA | AC97_EXTCAP_VRM) #define AC97_EXTCAP_SDAC (1 << 7) #define AC97_REGEXT_STAT 0x2a #define AC97_REGEXT_FDACRATE 0x2c #define AC97_REGEXT_SDACRATE 0x2e #define AC97_REGEXT_LDACRATE 0x30 #define AC97_REGEXT_LADCRATE 0x32 #define AC97_REGEXT_MADCRATE 0x34 #define AC97_MIXEXT_CLFE 0x36 #define AC97_MIXEXT_SURROUND 0x38 #define AC97_REG_ID1 0x7c #define AC97_REG_ID2 0x7e #define AC97_F_EAPD_INV 0x00000001 #define AC97_F_RDCD_BUG 0x00000002 +#define AC97_F_SOFTVOL 0x00000004 #define AC97_DECLARE(name) static DEFINE_CLASS(name, name ## _methods, sizeof(struct kobj)) #define AC97_CREATE(dev, devinfo, cls) ac97_create(dev, devinfo, &cls ## _class) struct ac97_info; #include "ac97_if.h" extern kobj_class_t ac97_getmixerclass(void); struct ac97_info *ac97_create(device_t dev, void *devinfo, kobj_class_t cls); void ac97_destroy(struct ac97_info *codec); void ac97_setflags(struct ac97_info *codec, u_int32_t val); u_int32_t ac97_getflags(struct ac97_info *codec); int ac97_setrate(struct ac97_info *codec, int which, int rate); int ac97_setextmode(struct ac97_info *codec, u_int16_t mode); u_int16_t ac97_getextmode(struct ac97_info *codec); u_int16_t ac97_getextcaps(struct ac97_info *codec); u_int16_t ac97_getcaps(struct ac97_info *codec); u_int16_t ac97_rdcd(struct ac97_info *codec, int reg); void ac97_wrcd(struct ac97_info *codec, int reg, u_int16_t val); Index: head/sys/dev/sound/pcm/channel.c =================================================================== --- head/sys/dev/sound/pcm/channel.c (revision 150824) +++ head/sys/dev/sound/pcm/channel.c (revision 150825) @@ -1,1462 +1,1493 @@ /*- * Copyright (c) 1999 Cameron Grant * Portions Copyright by Luigi Rizzo - 1997-99 * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "opt_isa.h" #include #include "feeder_if.h" SND_DECLARE_FILE("$FreeBSD$"); #define MIN_CHUNK_SIZE 256 /* for uiomove etc. */ #define DMA_ALIGN_THRESHOLD 4 #define DMA_ALIGN_MASK (~(DMA_ALIGN_THRESHOLD - 1)) #define CANCHANGE(c) (!(c->flags & CHN_F_TRIGGERED)) /* #define DEB(x) x */ static int chn_targetirqrate = 32; TUNABLE_INT("hw.snd.targetirqrate", &chn_targetirqrate); static int sysctl_hw_snd_targetirqrate(SYSCTL_HANDLER_ARGS) { int err, val; val = chn_targetirqrate; err = sysctl_handle_int(oidp, &val, sizeof(val), req); if (val < 16 || val > 512) err = EINVAL; else chn_targetirqrate = val; return err; } SYSCTL_PROC(_hw_snd, OID_AUTO, targetirqrate, CTLTYPE_INT | CTLFLAG_RW, 0, sizeof(int), sysctl_hw_snd_targetirqrate, "I", ""); static int report_soft_formats = 1; SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_formats, CTLFLAG_RW, &report_soft_formats, 1, "report software-emulated formats"); static int chn_buildfeeder(struct pcm_channel *c); static void chn_lockinit(struct pcm_channel *c, int dir) { switch(dir) { case PCMDIR_PLAY: c->lock = snd_mtxcreate(c->name, "pcm play channel"); break; case PCMDIR_REC: c->lock = snd_mtxcreate(c->name, "pcm record channel"); break; case PCMDIR_VIRTUAL: c->lock = snd_mtxcreate(c->name, "pcm virtual play channel"); break; case 0: c->lock = snd_mtxcreate(c->name, "pcm fake channel"); break; } } static void chn_lockdestroy(struct pcm_channel *c) { snd_mtxfree(c->lock); } static int chn_polltrigger(struct pcm_channel *c) { struct snd_dbuf *bs = c->bufsoft; unsigned amt, lim; CHN_LOCKASSERT(c); if (c->flags & CHN_F_MAPPED) { if (sndbuf_getprevblocks(bs) == 0) return 1; else return (sndbuf_getblocks(bs) > sndbuf_getprevblocks(bs))? 1 : 0; } else { amt = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(bs) : sndbuf_getready(bs); #if 0 lim = (c->flags & CHN_F_HAS_SIZE)? sndbuf_getblksz(bs) : 1; #endif lim = 1; return (amt >= lim)? 1 : 0; } return 0; } static int chn_pollreset(struct pcm_channel *c) { struct snd_dbuf *bs = c->bufsoft; CHN_LOCKASSERT(c); sndbuf_updateprevtotal(bs); return 1; } static void chn_wakeup(struct pcm_channel *c) { struct snd_dbuf *bs = c->bufsoft; struct pcmchan_children *pce; CHN_LOCKASSERT(c); if (SLIST_EMPTY(&c->children)) { if (SEL_WAITING(sndbuf_getsel(bs)) && chn_polltrigger(c)) selwakeuppri(sndbuf_getsel(bs), PRIBIO); } else { SLIST_FOREACH(pce, &c->children, link) { CHN_LOCK(pce->channel); chn_wakeup(pce->channel); CHN_UNLOCK(pce->channel); } } wakeup(bs); } static int chn_sleep(struct pcm_channel *c, char *str, int timeout) { struct snd_dbuf *bs = c->bufsoft; int ret; CHN_LOCKASSERT(c); #ifdef USING_MUTEX ret = msleep(bs, c->lock, PRIBIO | PCATCH, str, timeout); #else ret = tsleep(bs, PRIBIO | PCATCH, str, timeout); #endif return ret; } /* * chn_dmaupdate() tracks the status of a dma transfer, - * updating pointers. It must be called at spltty(). + * updating pointers. */ static unsigned int chn_dmaupdate(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; unsigned int delta, old, hwptr, amt; KASSERT(sndbuf_getsize(b) > 0, ("bufsize == 0")); CHN_LOCKASSERT(c); old = sndbuf_gethwptr(b); hwptr = chn_getptr(c); delta = (sndbuf_getsize(b) + hwptr - old) % sndbuf_getsize(b); sndbuf_sethwptr(b, hwptr); DEB( if (delta >= ((sndbuf_getsize(b) * 15) / 16)) { if (!(c->flags & (CHN_F_CLOSING | CHN_F_ABORTING))) device_printf(c->dev, "hwptr went backwards %d -> %d\n", old, hwptr); } ); if (c->direction == PCMDIR_PLAY) { amt = MIN(delta, sndbuf_getready(b)); if (amt > 0) sndbuf_dispose(b, NULL, amt); } else { amt = MIN(delta, sndbuf_getfree(b)); if (amt > 0) sndbuf_acquire(b, NULL, amt); } return delta; } void chn_wrupdate(struct pcm_channel *c) { int ret; CHN_LOCKASSERT(c); KASSERT(c->direction == PCMDIR_PLAY, ("chn_wrupdate on bad channel")); if ((c->flags & (CHN_F_MAPPED | CHN_F_VIRTUAL)) || !(c->flags & CHN_F_TRIGGERED)) return; chn_dmaupdate(c); ret = chn_wrfeed(c); /* tell the driver we've updated the primary buffer */ chn_trigger(c, PCMTRIG_EMLDMAWR); DEB(if (ret) printf("chn_wrupdate: chn_wrfeed returned %d\n", ret);) } int chn_wrfeed(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; unsigned int ret, amt; CHN_LOCKASSERT(c); #if 0 DEB( if (c->flags & CHN_F_CLOSING) { sndbuf_dump(b, "b", 0x02); sndbuf_dump(bs, "bs", 0x02); }) #endif if (c->flags & CHN_F_MAPPED) sndbuf_acquire(bs, NULL, sndbuf_getfree(bs)); amt = sndbuf_getfree(b); KASSERT(amt <= sndbuf_getsize(bs), ("%s(%s): amt %d > source size %d, flags 0x%x", __func__, c->name, amt, sndbuf_getsize(bs), c->flags)); if (SLIST_EMPTY(&c->children)) { /* * Hardware channel */ if (sndbuf_getready(bs) < amt) c->xruns++; ret = (amt > 0) ? sndbuf_feed(bs, b, c, c->feeder, amt) : ENOSPC; } else { /* * vchan */ if (amt > 0) { ret = sndbuf_feed(bs, b, c, c->feeder, amt); /* * Possible vchan xruns. There should be no empty space * left in buffer. */ if (sndbuf_getfree(b) > 0) c->xruns++; } else ret = ENOSPC; } if (ret == 0 && sndbuf_getfree(b) < amt) chn_wakeup(c); return ret; } static void chn_wrintr(struct pcm_channel *c) { int ret; CHN_LOCKASSERT(c); /* update pointers in primary buffer */ chn_dmaupdate(c); /* ...and feed from secondary to primary */ ret = chn_wrfeed(c); /* tell the driver we've updated the primary buffer */ chn_trigger(c, PCMTRIG_EMLDMAWR); DEB(if (ret) printf("chn_wrintr: chn_wrfeed returned %d\n", ret);) } /* * user write routine - uiomove data into secondary buffer, trigger if necessary * if blocking, sleep, rinse and repeat. * * called externally, so must handle locking */ int chn_write(struct pcm_channel *c, struct uio *buf) { int ret, timeout, newsize, count, sz; struct snd_dbuf *bs = c->bufsoft; void *off; int t, x,togo,p; CHN_LOCKASSERT(c); /* * XXX Certain applications attempt to write larger size * of pcm data than c->blocksize2nd without blocking, * resulting partial write. Expand the block size so that * the write operation avoids blocking. */ if ((c->flags & CHN_F_NBIO) && buf->uio_resid > sndbuf_getblksz(bs)) { DEB(device_printf(c->dev, "broken app, nbio and tried to write %d bytes with fragsz %d\n", buf->uio_resid, sndbuf_getblksz(bs))); newsize = 16; while (newsize < min(buf->uio_resid, CHN_2NDBUFMAXSIZE / 2)) newsize <<= 1; chn_setblocksize(c, sndbuf_getblkcnt(bs), newsize); DEB(device_printf(c->dev, "frags reset to %d x %d\n", sndbuf_getblkcnt(bs), sndbuf_getblksz(bs))); } ret = 0; count = hz; while (!ret && (buf->uio_resid > 0) && (count > 0)) { sz = sndbuf_getfree(bs); if (sz == 0) { if (c->flags & CHN_F_NBIO) ret = EWOULDBLOCK; else { timeout = (hz * sndbuf_getblksz(bs)) / (sndbuf_getspd(bs) * sndbuf_getbps(bs)); if (timeout < 1) timeout = 1; timeout = 1; ret = chn_sleep(c, "pcmwr", timeout); if (ret == EWOULDBLOCK) { count -= timeout; ret = 0; } else if (ret == 0) count = hz; } } else { sz = MIN(sz, buf->uio_resid); KASSERT(sz > 0, ("confusion in chn_write")); /* printf("sz: %d\n", sz); */ /* * The following assumes that the free space in * the buffer can never be less around the * unlock-uiomove-lock sequence. */ togo = sz; while (ret == 0 && togo> 0) { p = sndbuf_getfreeptr(bs); t = MIN(togo, sndbuf_getsize(bs) - p); off = sndbuf_getbufofs(bs, p); CHN_UNLOCK(c); ret = uiomove(off, t, buf); CHN_LOCK(c); togo -= t; x = sndbuf_acquire(bs, NULL, t); } ret = 0; if (ret == 0 && !(c->flags & CHN_F_TRIGGERED)) chn_start(c, 0); } } /* printf("ret: %d left: %d\n", ret, buf->uio_resid); */ if (count <= 0) { c->flags |= CHN_F_DEAD; printf("%s: play interrupt timeout, channel dead\n", c->name); } return ret; } static int chn_rddump(struct pcm_channel *c, unsigned int cnt) { struct snd_dbuf *b = c->bufhard; CHN_LOCKASSERT(c); #if 0 static uint32_t kk = 0; printf("%u: dumping %d bytes\n", ++kk, cnt); #endif c->xruns++; sndbuf_setxrun(b, sndbuf_getxrun(b) + cnt); return sndbuf_dispose(b, NULL, cnt); } /* * Feed new data from the read buffer. Can be called in the bottom half. - * Hence must be called at spltty. */ int chn_rdfeed(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; unsigned int ret, amt; CHN_LOCKASSERT(c); DEB( if (c->flags & CHN_F_CLOSING) { sndbuf_dump(b, "b", 0x02); sndbuf_dump(bs, "bs", 0x02); }) #if 0 amt = sndbuf_getready(b); if (sndbuf_getfree(bs) < amt) { c->xruns++; amt = sndbuf_getfree(bs); } #endif amt = sndbuf_getfree(bs); if (amt < sndbuf_getready(b)) c->xruns++; ret = (amt > 0)? sndbuf_feed(b, bs, c, c->feeder, amt) : 0; amt = sndbuf_getready(b); if (amt > 0) chn_rddump(c, amt); chn_wakeup(c); return ret; } void chn_rdupdate(struct pcm_channel *c) { int ret; CHN_LOCKASSERT(c); KASSERT(c->direction == PCMDIR_REC, ("chn_rdupdate on bad channel")); if ((c->flags & CHN_F_MAPPED) || !(c->flags & CHN_F_TRIGGERED)) return; chn_trigger(c, PCMTRIG_EMLDMARD); chn_dmaupdate(c); ret = chn_rdfeed(c); if (ret) printf("chn_rdfeed: %d\n", ret); } /* read interrupt routine. Must be called with interrupts blocked. */ static void chn_rdintr(struct pcm_channel *c) { int ret; CHN_LOCKASSERT(c); /* tell the driver to update the primary buffer if non-dma */ chn_trigger(c, PCMTRIG_EMLDMARD); /* update pointers in primary buffer */ chn_dmaupdate(c); /* ...and feed from primary to secondary */ ret = chn_rdfeed(c); } /* * user read routine - trigger if necessary, uiomove data from secondary buffer * if blocking, sleep, rinse and repeat. * * called externally, so must handle locking */ int chn_read(struct pcm_channel *c, struct uio *buf) { int ret, timeout, sz, count; struct snd_dbuf *bs = c->bufsoft; void *off; int t, x,togo,p; CHN_LOCKASSERT(c); if (!(c->flags & CHN_F_TRIGGERED)) chn_start(c, 0); ret = 0; count = hz; while (!ret && (buf->uio_resid > 0) && (count > 0)) { sz = MIN(buf->uio_resid, sndbuf_getready(bs)); if (sz > 0) { /* * The following assumes that the free space in * the buffer can never be less around the * unlock-uiomove-lock sequence. */ togo = sz; while (ret == 0 && togo> 0) { p = sndbuf_getreadyptr(bs); t = MIN(togo, sndbuf_getsize(bs) - p); off = sndbuf_getbufofs(bs, p); CHN_UNLOCK(c); ret = uiomove(off, t, buf); CHN_LOCK(c); togo -= t; x = sndbuf_dispose(bs, NULL, t); } ret = 0; } else { if (c->flags & CHN_F_NBIO) { ret = EWOULDBLOCK; } else { timeout = (hz * sndbuf_getblksz(bs)) / (sndbuf_getspd(bs) * sndbuf_getbps(bs)); if (timeout < 1) timeout = 1; ret = chn_sleep(c, "pcmrd", timeout); if (ret == EWOULDBLOCK) { count -= timeout; ret = 0; } else { count = hz; } } } } if (count <= 0) { c->flags |= CHN_F_DEAD; printf("%s: record interrupt timeout, channel dead\n", c->name); } return ret; } void chn_intr(struct pcm_channel *c) { CHN_LOCK(c); c->interrupts++; if (c->direction == PCMDIR_PLAY) chn_wrintr(c); else chn_rdintr(c); CHN_UNLOCK(c); } u_int32_t chn_start(struct pcm_channel *c, int force) { u_int32_t i, j; struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; CHN_LOCKASSERT(c); /* if we're running, or if we're prevented from triggering, bail */ if ((c->flags & CHN_F_TRIGGERED) || ((c->flags & CHN_F_NOTRIGGER) && !force)) return EINVAL; i = (c->direction == PCMDIR_PLAY)? sndbuf_getready(bs) : sndbuf_getfree(bs); j = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(b) : sndbuf_getready(b); if (force || (i >= j)) { c->flags |= CHN_F_TRIGGERED; /* * if we're starting because a vchan started, don't feed any data * or it becomes impossible to start vchans synchronised with the * first one. the hardbuf should be empty so we top it up with * silence to give it something to chew. the real data will be * fed at the first irq. */ if (c->direction == PCMDIR_PLAY) { /* * Reduce pops during playback startup. */ sndbuf_fillsilence(b); if (SLIST_EMPTY(&c->children)) chn_wrfeed(c); } sndbuf_setrun(b, 1); c->xruns = 0; chn_trigger(c, PCMTRIG_START); return 0; } return 0; } void chn_resetbuf(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; c->blocks = 0; sndbuf_reset(b); sndbuf_reset(bs); } /* * chn_sync waits until the space in the given channel goes above * a threshold. The threshold is checked against fl or rl respectively. * Assume that the condition can become true, do not check here... */ int chn_sync(struct pcm_channel *c, int threshold) { u_long rdy; int ret; struct snd_dbuf *bs = c->bufsoft; CHN_LOCKASSERT(c); /* if we haven't yet started and nothing is buffered, else start*/ if (!(c->flags & CHN_F_TRIGGERED)) { if (sndbuf_getready(bs) > 0) { ret = chn_start(c, 1); if (ret) return ret; } else { return 0; } } for (;;) { rdy = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(bs) : sndbuf_getready(bs); if (rdy <= threshold) { ret = chn_sleep(c, "pcmsyn", 1); if (ret == ERESTART || ret == EINTR) { DEB(printf("chn_sync: tsleep returns %d\n", ret)); return -1; } } else break; } return 0; } /* called externally, handle locking */ int chn_poll(struct pcm_channel *c, int ev, struct thread *td) { struct snd_dbuf *bs = c->bufsoft; int ret; CHN_LOCKASSERT(c); if (!(c->flags & CHN_F_MAPPED) && !(c->flags & CHN_F_TRIGGERED)) chn_start(c, 1); ret = 0; if (chn_polltrigger(c) && chn_pollreset(c)) ret = ev; else selrecord(td, sndbuf_getsel(bs)); return ret; } /* * chn_abort terminates a running dma transfer. it may sleep up to 200ms. * it returns the number of bytes that have not been transferred. * * called from: dsp_close, dsp_ioctl, with channel locked */ int chn_abort(struct pcm_channel *c) { int missing = 0; struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; CHN_LOCKASSERT(c); if (!(c->flags & CHN_F_TRIGGERED)) return 0; c->flags |= CHN_F_ABORTING; c->flags &= ~CHN_F_TRIGGERED; /* kill the channel */ chn_trigger(c, PCMTRIG_ABORT); sndbuf_setrun(b, 0); if (!(c->flags & CHN_F_VIRTUAL)) chn_dmaupdate(c); missing = sndbuf_getready(bs) + sndbuf_getready(b); c->flags &= ~CHN_F_ABORTING; return missing; } /* * this routine tries to flush the dma transfer. It is called * on a close of a playback channel. * first, if there is data in the buffer, but the dma has not yet * begun, we need to start it. * next, we wait for the play buffer to drain * finally, we stop the dma. * * called from: dsp_close, not valid for record channels. */ int chn_flush(struct pcm_channel *c) { int ret, count, resid, resid_p; struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; CHN_LOCKASSERT(c); KASSERT(c->direction == PCMDIR_PLAY, ("chn_flush on bad channel")); DEB(printf("chn_flush: c->flags 0x%08x\n", c->flags)); /* if we haven't yet started and nothing is buffered, else start*/ if (!(c->flags & CHN_F_TRIGGERED)) { if (sndbuf_getready(bs) > 0) { ret = chn_start(c, 1); if (ret) return ret; } else { return 0; } } c->flags |= CHN_F_CLOSING; resid = sndbuf_getready(bs) + sndbuf_getready(b); resid_p = resid; count = 10; ret = 0; while ((count > 0) && (resid > sndbuf_getsize(b)) && (ret == 0)) { /* still pending output data. */ ret = chn_sleep(c, "pcmflu", hz / 10); if (ret == EWOULDBLOCK) ret = 0; if (ret == 0) { resid = sndbuf_getready(bs) + sndbuf_getready(b); if (resid == resid_p) count--; if (resid > resid_p) DEB(printf("chn_flush: buffer length increasind %d -> %d\n", resid_p, resid)); resid_p = resid; } } if (count == 0) DEB(printf("chn_flush: timeout, hw %d, sw %d\n", sndbuf_getready(b), sndbuf_getready(bs))); c->flags &= ~CHN_F_TRIGGERED; /* kill the channel */ chn_trigger(c, PCMTRIG_ABORT); sndbuf_setrun(b, 0); c->flags &= ~CHN_F_CLOSING; return 0; } int fmtvalid(u_int32_t fmt, u_int32_t *fmtlist) { int i; for (i = 0; fmtlist[i]; i++) if (fmt == fmtlist[i]) return 1; return 0; } int chn_reset(struct pcm_channel *c, u_int32_t fmt) { int hwspd, r; CHN_LOCKASSERT(c); c->flags &= CHN_F_RESET; c->interrupts = 0; c->xruns = 0; r = CHANNEL_RESET(c->methods, c->devinfo); if (fmt != 0) { #if 0 hwspd = DSP_DEFAULT_SPEED; /* only do this on a record channel until feederbuilder works */ if (c->direction == PCMDIR_REC) RANGE(hwspd, chn_getcaps(c)->minspeed, chn_getcaps(c)->maxspeed); c->speed = hwspd; #endif hwspd = chn_getcaps(c)->minspeed; c->speed = hwspd; if (r == 0) r = chn_setformat(c, fmt); if (r == 0) r = chn_setspeed(c, hwspd); +#if 0 if (r == 0) r = chn_setvolume(c, 100, 100); +#endif } if (r == 0) r = chn_setblocksize(c, 0, 0); if (r == 0) { chn_resetbuf(c); r = CHANNEL_RESETDONE(c->methods, c->devinfo); } return r; } int chn_init(struct pcm_channel *c, void *devinfo, int dir, int direction) { struct feeder_class *fc; struct snd_dbuf *b, *bs; int ret; chn_lockinit(c, dir); b = NULL; bs = NULL; c->devinfo = NULL; c->feeder = NULL; ret = ENOMEM; b = sndbuf_create(c->dev, c->name, "primary", c); if (b == NULL) goto out; bs = sndbuf_create(c->dev, c->name, "secondary", c); if (bs == NULL) goto out; CHN_LOCK(c); ret = EINVAL; fc = feeder_getclass(NULL); if (fc == NULL) goto out; if (chn_addfeeder(c, fc, NULL)) goto out; /* * XXX - sndbuf_setup() & sndbuf_resize() expect to be called * with the channel unlocked because they are also called * from driver methods that don't know about locking */ CHN_UNLOCK(c); sndbuf_setup(bs, NULL, 0); CHN_LOCK(c); c->bufhard = b; c->bufsoft = bs; c->flags = 0; c->feederflags = 0; ret = ENODEV; CHN_UNLOCK(c); /* XXX - Unlock for CHANNEL_INIT() malloc() call */ c->devinfo = CHANNEL_INIT(c->methods, devinfo, b, c, direction); CHN_LOCK(c); if (c->devinfo == NULL) goto out; ret = ENOMEM; if ((sndbuf_getsize(b) == 0) && ((c->flags & CHN_F_VIRTUAL) == 0)) goto out; ret = chn_setdir(c, direction); if (ret) goto out; ret = sndbuf_setfmt(b, AFMT_U8); if (ret) goto out; ret = sndbuf_setfmt(bs, AFMT_U8); if (ret) goto out; out: CHN_UNLOCK(c); if (ret) { if (c->devinfo) { if (CHANNEL_FREE(c->methods, c->devinfo)) sndbuf_free(b); } if (bs) sndbuf_destroy(bs); if (b) sndbuf_destroy(b); c->flags |= CHN_F_DEAD; chn_lockdestroy(c); return ret; } return 0; } int chn_kill(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; if (c->flags & CHN_F_TRIGGERED) chn_trigger(c, PCMTRIG_ABORT); while (chn_removefeeder(c) == 0); if (CHANNEL_FREE(c->methods, c->devinfo)) sndbuf_free(b); c->flags |= CHN_F_DEAD; sndbuf_destroy(bs); sndbuf_destroy(b); chn_lockdestroy(c); return 0; } int chn_setdir(struct pcm_channel *c, int dir) { #ifdef DEV_ISA struct snd_dbuf *b = c->bufhard; #endif int r; CHN_LOCKASSERT(c); c->direction = dir; r = CHANNEL_SETDIR(c->methods, c->devinfo, c->direction); #ifdef DEV_ISA if (!r && SND_DMA(b)) sndbuf_dmasetdir(b, c->direction); #endif return r; } int chn_setvolume(struct pcm_channel *c, int left, int right) { CHN_LOCKASSERT(c); /* should add a feeder for volume changing if channel returns -1 */ - c->volume = (left << 8) | right; + if (left > 100) + left = 100; + if (left < 0) + left = 0; + if (right > 100) + right = 100; + if (right < 0) + right = 0; + c->volume = left | (right << 8); return 0; } static int chn_tryspeed(struct pcm_channel *c, int speed) { struct pcm_feeder *f; struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; struct snd_dbuf *x; int r, delta; CHN_LOCKASSERT(c); DEB(printf("setspeed, channel %s\n", c->name)); DEB(printf("want speed %d, ", speed)); if (speed <= 0) return EINVAL; if (CANCHANGE(c)) { r = 0; c->speed = speed; sndbuf_setspd(bs, speed); RANGE(speed, chn_getcaps(c)->minspeed, chn_getcaps(c)->maxspeed); DEB(printf("try speed %d, ", speed)); sndbuf_setspd(b, CHANNEL_SETSPEED(c->methods, c->devinfo, speed)); DEB(printf("got speed %d\n", sndbuf_getspd(b))); delta = sndbuf_getspd(b) - sndbuf_getspd(bs); if (delta < 0) delta = -delta; c->feederflags &= ~(1 << FEEDER_RATE); /* * Used to be 500. It was too big! */ if (delta > 25) c->feederflags |= 1 << FEEDER_RATE; else sndbuf_setspd(bs, sndbuf_getspd(b)); r = chn_buildfeeder(c); DEB(printf("r = %d\n", r)); if (r) goto out; r = chn_setblocksize(c, 0, 0); if (r) goto out; if (!(c->feederflags & (1 << FEEDER_RATE))) goto out; r = EINVAL; f = chn_findfeeder(c, FEEDER_RATE); DEB(printf("feedrate = %p\n", f)); if (f == NULL) goto out; x = (c->direction == PCMDIR_REC)? b : bs; r = FEEDER_SET(f, FEEDRATE_SRC, sndbuf_getspd(x)); DEB(printf("feeder_set(FEEDRATE_SRC, %d) = %d\n", sndbuf_getspd(x), r)); if (r) goto out; x = (c->direction == PCMDIR_REC)? bs : b; r = FEEDER_SET(f, FEEDRATE_DST, sndbuf_getspd(x)); DEB(printf("feeder_set(FEEDRATE_DST, %d) = %d\n", sndbuf_getspd(x), r)); out: if (!r) r = CHANNEL_SETFORMAT(c->methods, c->devinfo, sndbuf_getfmt(b)); if (!r) sndbuf_setfmt(bs, c->format); DEB(printf("setspeed done, r = %d\n", r)); return r; } else return EINVAL; } int chn_setspeed(struct pcm_channel *c, int speed) { int r, oldspeed = c->speed; r = chn_tryspeed(c, speed); if (r) { DEB(printf("Failed to set speed %d falling back to %d\n", speed, oldspeed)); r = chn_tryspeed(c, oldspeed); } return r; } static int chn_tryformat(struct pcm_channel *c, u_int32_t fmt) { struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; int r; CHN_LOCKASSERT(c); if (CANCHANGE(c)) { DEB(printf("want format %d\n", fmt)); c->format = fmt; r = chn_buildfeeder(c); if (r == 0) { sndbuf_setfmt(bs, c->format); chn_resetbuf(c); r = CHANNEL_SETFORMAT(c->methods, c->devinfo, sndbuf_getfmt(b)); if (r == 0) r = chn_tryspeed(c, c->speed); } return r; } else return EINVAL; } int chn_setformat(struct pcm_channel *c, u_int32_t fmt) { u_int32_t oldfmt = c->format; int r; r = chn_tryformat(c, fmt); if (r) { DEB(printf("Format change %d failed, reverting to %d\n", fmt, oldfmt)); chn_tryformat(c, oldfmt); } return r; } int chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz) { struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; int irqhz, tmp, ret, maxsize, reqblksz, tmpblksz; CHN_LOCKASSERT(c); if (!CANCHANGE(c) || (c->flags & CHN_F_MAPPED)) { KASSERT(sndbuf_getsize(bs) == 0 || sndbuf_getsize(bs) >= sndbuf_getsize(b), ("%s(%s): bufsoft size %d < bufhard size %d", __func__, c->name, sndbuf_getsize(bs), sndbuf_getsize(b))); return EINVAL; } c->flags |= CHN_F_SETBLOCKSIZE; ret = 0; DEB(printf("%s(%d, %d)\n", __func__, blkcnt, blksz)); if (blksz == 0 || blksz == -1) { if (blksz == -1) c->flags &= ~CHN_F_HAS_SIZE; if (!(c->flags & CHN_F_HAS_SIZE)) { blksz = (sndbuf_getbps(bs) * sndbuf_getspd(bs)) / chn_targetirqrate; tmp = 32; while (tmp <= blksz) tmp <<= 1; tmp >>= 1; blksz = tmp; blkcnt = CHN_2NDBUFMAXSIZE / blksz; RANGE(blksz, 16, CHN_2NDBUFMAXSIZE / 2); RANGE(blkcnt, 2, CHN_2NDBUFMAXSIZE / blksz); DEB(printf("%s: defaulting to (%d, %d)\n", __func__, blkcnt, blksz)); } else { blkcnt = sndbuf_getblkcnt(bs); blksz = sndbuf_getblksz(bs); DEB(printf("%s: updating (%d, %d)\n", __func__, blkcnt, blksz)); } } else { ret = EINVAL; if ((blksz < 16) || (blkcnt < 2) || (blkcnt * blksz > CHN_2NDBUFMAXSIZE)) goto out; ret = 0; c->flags |= CHN_F_HAS_SIZE; } reqblksz = blksz; if (reqblksz < sndbuf_getbps(bs)) reqblksz = sndbuf_getbps(bs); if (reqblksz % sndbuf_getbps(bs)) reqblksz -= reqblksz % sndbuf_getbps(bs); /* adjust for different hw format/speed */ irqhz = (sndbuf_getbps(bs) * sndbuf_getspd(bs)) / blksz; DEB(printf("%s: soft bps %d, spd %d, irqhz == %d\n", __func__, sndbuf_getbps(bs), sndbuf_getspd(bs), irqhz)); RANGE(irqhz, 16, 512); tmpblksz = (sndbuf_getbps(b) * sndbuf_getspd(b)) / irqhz; /* round down to 2^x */ blksz = 32; while (blksz <= tmpblksz) blksz <<= 1; blksz >>= 1; /* round down to fit hw buffer size */ if (sndbuf_getmaxsize(b) > 0) RANGE(blksz, 16, sndbuf_getmaxsize(b) / 2); else /* virtual channels don't appear to allocate bufhard */ RANGE(blksz, 16, CHN_2NDBUFMAXSIZE / 2); DEB(printf("%s: hard blksz requested %d (maxsize %d), ", __func__, blksz, sndbuf_getmaxsize(b))); /* Increase the size of bufsoft if before increasing bufhard. */ maxsize = sndbuf_getsize(b); if (sndbuf_getsize(bs) > maxsize) maxsize = sndbuf_getsize(bs); if (reqblksz * blkcnt > maxsize) maxsize = reqblksz * blkcnt; if (sndbuf_getsize(bs) != maxsize || sndbuf_getblksz(bs) != reqblksz) { ret = sndbuf_remalloc(bs, maxsize/reqblksz, reqblksz); if (ret) goto out1; } CHN_UNLOCK(c); sndbuf_setblksz(b, CHANNEL_SETBLOCKSIZE(c->methods, c->devinfo, blksz)); CHN_LOCK(c); /* Decrease the size of bufsoft after decreasing bufhard. */ maxsize = sndbuf_getsize(b); if (reqblksz * blkcnt > maxsize) maxsize = reqblksz * blkcnt; if (maxsize > sndbuf_getsize(bs)) printf("Danger! %s bufsoft size increasing from %d to %d after CHANNEL_SETBLOCKSIZE()\n", c->name, sndbuf_getsize(bs), maxsize); if (sndbuf_getsize(bs) != maxsize || sndbuf_getblksz(bs) != reqblksz) { ret = sndbuf_remalloc(bs, maxsize/reqblksz, reqblksz); if (ret) goto out1; } irqhz = (sndbuf_getbps(b) * sndbuf_getspd(b)) / sndbuf_getblksz(b); DEB(printf("got %d, irqhz == %d\n", sndbuf_getblksz(b), irqhz)); chn_resetbuf(c); out1: KASSERT(sndbuf_getsize(bs) == 0 || sndbuf_getsize(bs) >= sndbuf_getsize(b), ("%s(%s): bufsoft size %d < bufhard size %d, reqblksz=%d blksz=%d maxsize=%d blkcnt=%d", __func__, c->name, sndbuf_getsize(bs), sndbuf_getsize(b), reqblksz, blksz, maxsize, blkcnt)); out: c->flags &= ~CHN_F_SETBLOCKSIZE; #if 0 if (1) { static uint32_t kk = 0; printf("%u: b %d/%d/%d : (%d)%d/0x%0x | bs %d/%d/%d : (%d)%d/0x%0x\n", ++kk, sndbuf_getsize(b), sndbuf_getblksz(b), sndbuf_getblkcnt(b), sndbuf_getbps(b), sndbuf_getspd(b), sndbuf_getfmt(b), sndbuf_getsize(bs), sndbuf_getblksz(bs), sndbuf_getblkcnt(bs), sndbuf_getbps(bs), sndbuf_getspd(bs), sndbuf_getfmt(bs)); if (sndbuf_getsize(b) % sndbuf_getbps(b) || sndbuf_getblksz(b) % sndbuf_getbps(b) || sndbuf_getsize(bs) % sndbuf_getbps(bs) || sndbuf_getblksz(b) % sndbuf_getbps(b)) { printf("%u: bps/blksz alignment screwed!\n", kk); } } #endif return ret; } int chn_trigger(struct pcm_channel *c, int go) { #ifdef DEV_ISA struct snd_dbuf *b = c->bufhard; #endif int ret; CHN_LOCKASSERT(c); #ifdef DEV_ISA if (SND_DMA(b) && (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD)) sndbuf_dmabounce(b); #endif ret = CHANNEL_TRIGGER(c->methods, c->devinfo, go); return ret; } int chn_getptr(struct pcm_channel *c) { int hwptr; int a = (1 << c->align) - 1; CHN_LOCKASSERT(c); hwptr = (c->flags & CHN_F_TRIGGERED)? CHANNEL_GETPTR(c->methods, c->devinfo) : 0; /* don't allow unaligned values in the hwa ptr */ #if 1 hwptr &= ~a ; /* Apply channel align mask */ #endif hwptr &= DMA_ALIGN_MASK; /* Apply DMA align mask */ return hwptr; } struct pcmchan_caps * chn_getcaps(struct pcm_channel *c) { CHN_LOCKASSERT(c); return CHANNEL_GETCAPS(c->methods, c->devinfo); } u_int32_t chn_getformats(struct pcm_channel *c) { u_int32_t *fmtlist, fmts; int i; fmtlist = chn_getcaps(c)->fmtlist; fmts = 0; for (i = 0; fmtlist[i]; i++) fmts |= fmtlist[i]; /* report software-supported formats */ if (report_soft_formats) fmts |= AFMT_MU_LAW|AFMT_A_LAW|AFMT_U32_LE|AFMT_U32_BE| AFMT_S32_LE|AFMT_S32_BE|AFMT_U24_LE|AFMT_U24_BE| AFMT_S24_LE|AFMT_S24_BE|AFMT_U16_LE|AFMT_U16_BE| AFMT_S16_LE|AFMT_S16_BE|AFMT_U8|AFMT_S8; return fmts; } static int chn_buildfeeder(struct pcm_channel *c) { struct feeder_class *fc; struct pcm_feederdesc desc; u_int32_t tmp[2], type, flags, hwfmt, *fmtlist; int err; CHN_LOCKASSERT(c); while (chn_removefeeder(c) == 0); KASSERT((c->feeder == NULL), ("feeder chain not empty")); c->align = sndbuf_getalign(c->bufsoft); if (SLIST_EMPTY(&c->children)) { fc = feeder_getclass(NULL); KASSERT(fc != NULL, ("can't find root feeder")); err = chn_addfeeder(c, fc, NULL); if (err) { DEB(printf("can't add root feeder, err %d\n", err)); return err; } c->feeder->desc->out = c->format; } else { if (c->flags & CHN_F_HAS_VCHAN) { desc.type = FEEDER_MIXER; desc.in = 0; } else { DEB(printf("can't decide which feeder type to use!\n")); return EOPNOTSUPP; } desc.out = c->format; desc.flags = 0; fc = feeder_getclass(&desc); if (fc == NULL) { DEB(printf("can't find vchan feeder\n")); return EOPNOTSUPP; } err = chn_addfeeder(c, fc, &desc); if (err) { DEB(printf("can't add vchan feeder, err %d\n", err)); return err; } } + c->feederflags &= ~(1 << FEEDER_VOLUME); + if (c->direction == PCMDIR_PLAY && + !(c->flags & CHN_F_VIRTUAL) && + c->parentsnddev && (c->parentsnddev->flags & SD_F_SOFTVOL) && + c->parentsnddev->mixer_dev) + c->feederflags |= 1 << FEEDER_VOLUME; flags = c->feederflags; fmtlist = chn_getcaps(c)->fmtlist; DEB(printf("feederflags %x\n", flags)); for (type = FEEDER_RATE; type <= FEEDER_LAST; type++) { if (flags & (1 << type)) { desc.type = type; desc.in = 0; desc.out = 0; desc.flags = 0; DEB(printf("find feeder type %d, ", type)); fc = feeder_getclass(&desc); DEB(printf("got %p\n", fc)); if (fc == NULL) { DEB(printf("can't find required feeder type %d\n", type)); return EOPNOTSUPP; } if ((type == FEEDER_RATE && !fmtvalid(fc->desc->in, fmtlist)) || c->feeder->desc->out != fc->desc->in) { DEB(printf("build fmtchain from 0x%x to 0x%x: ", c->feeder->desc->out, fc->desc->in)); tmp[0] = fc->desc->in; tmp[1] = 0; if (chn_fmtchain(c, tmp) == 0) { DEB(printf("failed\n")); return ENODEV; } DEB(printf("ok\n")); } err = chn_addfeeder(c, fc, fc->desc); if (err) { DEB(printf("can't add feeder %p, output 0x%x, err %d\n", fc, fc->desc->out, err)); return err; } DEB(printf("added feeder %p, output 0x%x\n", fc, c->feeder->desc->out)); } } if (fmtvalid(c->feeder->desc->out, fmtlist) && !(c->direction == PCMDIR_REC && c->format != c->feeder->desc->out)) hwfmt = c->feeder->desc->out; else { if (c->direction == PCMDIR_REC) { tmp[0] = c->format; tmp[1] = 0; hwfmt = chn_fmtchain(c, tmp); } else hwfmt = chn_fmtchain(c, fmtlist); } if (hwfmt == 0 || !fmtvalid(hwfmt, fmtlist)) { DEB(printf("Invalid hardware format: 0x%x\n", hwfmt)); return ENODEV; } sndbuf_setfmt(c->bufhard, hwfmt); + + if ((flags & (1 << FEEDER_VOLUME))) { + int vol = 100 | (100 << 8); + + CHN_UNLOCK(c); + /* + * XXX This is ugly! The way mixer subs being so secretive + * about its own internals force us to use this silly + * monkey trick. + */ + if (mixer_ioctl(c->parentsnddev->mixer_dev, + MIXER_READ(SOUND_MIXER_PCM), (caddr_t)&vol, -1, NULL) != 0) + device_printf(c->dev, "Soft Volume: Failed to read default value\n"); + CHN_LOCK(c); + chn_setvolume(c, vol & 0x7f, (vol >> 8) & 0x7f); + } return 0; } int chn_notify(struct pcm_channel *c, u_int32_t flags) { struct pcmchan_children *pce; struct pcm_channel *child; int run; CHN_LOCK(c); if (SLIST_EMPTY(&c->children)) { CHN_UNLOCK(c); return ENODEV; } run = (c->flags & CHN_F_TRIGGERED)? 1 : 0; /* * if the hwchan is running, we can't change its rate, format or * blocksize */ if (run) flags &= CHN_N_VOLUME | CHN_N_TRIGGER; if (flags & CHN_N_RATE) { /* * we could do something here, like scan children and decide on * the most appropriate rate to mix at, but we don't for now */ } if (flags & CHN_N_FORMAT) { /* * we could do something here, like scan children and decide on * the most appropriate mixer feeder to use, but we don't for now */ } if (flags & CHN_N_VOLUME) { /* * we could do something here but we don't for now */ } if (flags & CHN_N_BLOCKSIZE) { int blksz; /* * scan the children, find the lowest blocksize and use that * for the hard blocksize */ blksz = sndbuf_getmaxsize(c->bufhard) / 2; SLIST_FOREACH(pce, &c->children, link) { child = pce->channel; CHN_LOCK(child); if (sndbuf_getblksz(child->bufhard) < blksz) blksz = sndbuf_getblksz(child->bufhard); CHN_UNLOCK(child); } chn_setblocksize(c, 2, blksz); } if (flags & CHN_N_TRIGGER) { int nrun; /* * scan the children, and figure out if any are running * if so, we need to be running, otherwise we need to be stopped * if we aren't in our target sstate, move to it */ nrun = 0; SLIST_FOREACH(pce, &c->children, link) { child = pce->channel; CHN_LOCK(child); if (child->flags & CHN_F_TRIGGERED) nrun = 1; CHN_UNLOCK(child); } if (nrun && !run) chn_start(c, 1); if (!nrun && run) chn_abort(c); } CHN_UNLOCK(c); return 0; } void chn_lock(struct pcm_channel *c) { CHN_LOCK(c); } void chn_unlock(struct pcm_channel *c) { CHN_UNLOCK(c); } Index: head/sys/dev/sound/pcm/mixer.c =================================================================== --- head/sys/dev/sound/pcm/mixer.c (revision 150824) +++ head/sys/dev/sound/pcm/mixer.c (revision 150825) @@ -1,522 +1,542 @@ /*- * Copyright (c) 1999 Cameron Grant * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include "mixer_if.h" SND_DECLARE_FILE("$FreeBSD$"); MALLOC_DEFINE(M_MIXER, "mixer", "mixer"); #define MIXER_NAMELEN 16 struct snd_mixer { KOBJ_FIELDS; const char *type; void *devinfo; int busy; int hwvol_muted; int hwvol_mixer; int hwvol_step; + device_t dev; u_int32_t hwvol_mute_level; u_int32_t devs; u_int32_t recdevs; u_int32_t recsrc; u_int16_t level[32]; char name[MIXER_NAMELEN]; struct mtx *lock; }; static u_int16_t snd_mixerdefaults[SOUND_MIXER_NRDEVICES] = { [SOUND_MIXER_VOLUME] = 75, [SOUND_MIXER_BASS] = 50, [SOUND_MIXER_TREBLE] = 50, [SOUND_MIXER_SYNTH] = 75, [SOUND_MIXER_PCM] = 75, [SOUND_MIXER_SPEAKER] = 75, [SOUND_MIXER_LINE] = 75, [SOUND_MIXER_MIC] = 0, [SOUND_MIXER_CD] = 75, [SOUND_MIXER_LINE1] = 75, [SOUND_MIXER_VIDEO] = 75, [SOUND_MIXER_RECLEV] = 0, [SOUND_MIXER_OGAIN] = 50, [SOUND_MIXER_MONITOR] = 75, }; static char* snd_mixernames[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES; static d_open_t mixer_open; static d_close_t mixer_close; static struct cdevsw mixer_cdevsw = { .d_version = D_VERSION, .d_flags = D_TRACKCLOSE | D_NEEDGIANT, .d_open = mixer_open, .d_close = mixer_close, .d_ioctl = mixer_ioctl, .d_name = "mixer", }; #ifdef USING_DEVFS static eventhandler_tag mixer_ehtag; #endif static struct cdev * mixer_get_devt(device_t dev) { struct snddev_info *snddev; snddev = device_get_softc(dev); return snddev->mixer_dev; } #ifdef SND_DYNSYSCTL static int mixer_lookup(char *devname) { int i; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) if (strncmp(devname, snd_mixernames[i], strlen(snd_mixernames[i])) == 0) return i; return -1; } #endif static int mixer_set(struct snd_mixer *mixer, unsigned dev, unsigned lev) { + struct snddev_info *d; unsigned l, r; int v; if ((dev >= SOUND_MIXER_NRDEVICES) || (0 == (mixer->devs & (1 << dev)))) return -1; l = min((lev & 0x00ff), 100); r = min(((lev & 0xff00) >> 8), 100); - v = MIXER_SET(mixer, dev, l, r); - if (v < 0) - return -1; + d = device_get_softc(mixer->dev); + if (dev == SOUND_MIXER_PCM && d && + (d->flags & SD_F_SOFTVOL)) { + struct snddev_channel *sce; + struct pcm_channel *ch; +#ifdef USING_MUTEX + int locked = (mixer->lock && mtx_owned((struct mtx *)(mixer->lock))) ? 1 : 0; + if (locked) + snd_mtxunlock(mixer->lock); +#endif + SLIST_FOREACH(sce, &d->channels, link) { + ch = sce->channel; + CHN_LOCK(ch); + if (ch->direction == PCMDIR_PLAY && + (ch->feederflags & (1 << FEEDER_VOLUME))) + chn_setvolume(ch, l, r); + CHN_UNLOCK(ch); + } +#ifdef USING_MUTEX + if (locked) + snd_mtxlock(mixer->lock); +#endif + } else { + v = MIXER_SET(mixer, dev, l, r); + if (v < 0) + return -1; + } + mixer->level[dev] = l | (r << 8); return 0; } static int mixer_get(struct snd_mixer *mixer, int dev) { if ((dev < SOUND_MIXER_NRDEVICES) && (mixer->devs & (1 << dev))) return mixer->level[dev]; else return -1; } static int mixer_setrecsrc(struct snd_mixer *mixer, u_int32_t src) { src &= mixer->recdevs; if (src == 0) src = SOUND_MASK_MIC; mixer->recsrc = MIXER_SETRECSRC(mixer, src); return 0; } static int mixer_getrecsrc(struct snd_mixer *mixer) { return mixer->recsrc; } void mix_setdevs(struct snd_mixer *m, u_int32_t v) { + struct snddev_info *d = device_get_softc(m->dev); + if (d && (d->flags & SD_F_SOFTVOL)) + v |= SOUND_MASK_PCM; m->devs = v; } void mix_setrecdevs(struct snd_mixer *m, u_int32_t v) { m->recdevs = v; } u_int32_t mix_getdevs(struct snd_mixer *m) { return m->devs; } u_int32_t mix_getrecdevs(struct snd_mixer *m) { return m->recdevs; } void * mix_getdevinfo(struct snd_mixer *m) { return m->devinfo; } int mixer_init(device_t dev, kobj_class_t cls, void *devinfo) { struct snddev_info *snddev; struct snd_mixer *m; u_int16_t v; struct cdev *pdev; int i, unit, val; m = (struct snd_mixer *)kobj_create(cls, M_MIXER, M_WAITOK | M_ZERO); snprintf(m->name, MIXER_NAMELEN, "%s:mixer", device_get_nameunit(dev)); m->lock = snd_mtxcreate(m->name, "pcm mixer"); m->type = cls->name; m->devinfo = devinfo; m->busy = 0; + m->dev = dev; if (MIXER_INIT(m)) goto bad; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { v = snd_mixerdefaults[i]; if (resource_int_value(device_get_name(dev), device_get_unit(dev), snd_mixernames[i], &val) == 0) { if (val >= 0 && val <= 100) { v = (u_int16_t) val; } } mixer_set(m, i, v | (v << 8)); } mixer_setrecsrc(m, SOUND_MASK_MIC); unit = device_get_unit(dev); pdev = make_dev(&mixer_cdevsw, PCMMKMINOR(unit, SND_DEV_CTL, 0), UID_ROOT, GID_WHEEL, 0666, "mixer%d", unit); pdev->si_drv1 = m; snddev = device_get_softc(dev); snddev->mixer_dev = pdev; return 0; bad: snd_mtxlock(m->lock); snd_mtxfree(m->lock); kobj_delete((kobj_t)m, M_MIXER); return -1; } int mixer_uninit(device_t dev) { int i; struct snd_mixer *m; struct cdev *pdev; pdev = mixer_get_devt(dev); m = pdev->si_drv1; snd_mtxlock(m->lock); if (m->busy) { snd_mtxunlock(m->lock); return EBUSY; } pdev->si_drv1 = NULL; destroy_dev(pdev); for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) mixer_set(m, i, 0); mixer_setrecsrc(m, SOUND_MASK_MIC); MIXER_UNINIT(m); snd_mtxfree(m->lock); kobj_delete((kobj_t)m, M_MIXER); return 0; } int mixer_reinit(device_t dev) { struct snd_mixer *m; struct cdev *pdev; int i; pdev = mixer_get_devt(dev); m = pdev->si_drv1; snd_mtxlock(m->lock); i = MIXER_REINIT(m); if (i) { snd_mtxunlock(m->lock); return i; } for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) mixer_set(m, i, m->level[i]); mixer_setrecsrc(m, m->recsrc); snd_mtxunlock(m->lock); return 0; } #ifdef SND_DYNSYSCTL static int sysctl_hw_snd_hwvol_mixer(SYSCTL_HANDLER_ARGS) { char devname[32]; int error, dev; struct snd_mixer *m; m = oidp->oid_arg1; snd_mtxlock(m->lock); strncpy(devname, snd_mixernames[m->hwvol_mixer], sizeof(devname)); snd_mtxunlock(m->lock); error = sysctl_handle_string(oidp, &devname[0], sizeof(devname), req); snd_mtxlock(m->lock); if (error == 0 && req->newptr != NULL) { dev = mixer_lookup(devname); if (dev == -1) { snd_mtxunlock(m->lock); return EINVAL; } else if (dev != m->hwvol_mixer) { m->hwvol_mixer = dev; m->hwvol_muted = 0; } } snd_mtxunlock(m->lock); return error; } #endif int mixer_hwvol_init(device_t dev) { struct snd_mixer *m; struct cdev *pdev; pdev = mixer_get_devt(dev); m = pdev->si_drv1; m->hwvol_mixer = SOUND_MIXER_VOLUME; m->hwvol_step = 5; #ifdef SND_DYNSYSCTL SYSCTL_ADD_INT(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), OID_AUTO, "hwvol_step", CTLFLAG_RW, &m->hwvol_step, 0, ""); SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), OID_AUTO, "hwvol_mixer", CTLTYPE_STRING | CTLFLAG_RW, m, 0, sysctl_hw_snd_hwvol_mixer, "A", ""); #endif return 0; } void mixer_hwvol_mute(device_t dev) { struct snd_mixer *m; struct cdev *pdev; pdev = mixer_get_devt(dev); m = pdev->si_drv1; snd_mtxlock(m->lock); if (m->hwvol_muted) { m->hwvol_muted = 0; mixer_set(m, m->hwvol_mixer, m->hwvol_mute_level); } else { m->hwvol_muted++; m->hwvol_mute_level = mixer_get(m, m->hwvol_mixer); mixer_set(m, m->hwvol_mixer, 0); } snd_mtxunlock(m->lock); } void mixer_hwvol_step(device_t dev, int left_step, int right_step) { struct snd_mixer *m; int level, left, right; struct cdev *pdev; pdev = mixer_get_devt(dev); m = pdev->si_drv1; snd_mtxlock(m->lock); if (m->hwvol_muted) { m->hwvol_muted = 0; level = m->hwvol_mute_level; } else level = mixer_get(m, m->hwvol_mixer); if (level != -1) { left = level & 0xff; right = level >> 8; left += left_step * m->hwvol_step; if (left < 0) left = 0; right += right_step * m->hwvol_step; if (right < 0) right = 0; mixer_set(m, m->hwvol_mixer, left | right << 8); } snd_mtxunlock(m->lock); } /* ----------------------------------------------------------------------- */ static int mixer_open(struct cdev *i_dev, int flags, int mode, struct thread *td) { struct snd_mixer *m; - intrmask_t s; m = i_dev->si_drv1; - s = spltty(); snd_mtxlock(m->lock); m->busy++; snd_mtxunlock(m->lock); - splx(s); return 0; } static int mixer_close(struct cdev *i_dev, int flags, int mode, struct thread *td) { struct snd_mixer *m; - intrmask_t s; m = i_dev->si_drv1; - s = spltty(); snd_mtxlock(m->lock); if (!m->busy) { snd_mtxunlock(m->lock); - splx(s); return EBADF; } m->busy--; snd_mtxunlock(m->lock); - splx(s); return 0; } int mixer_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) { struct snd_mixer *m; - intrmask_t s; int ret, *arg_i = (int *)arg; int v = -1, j = cmd & 0xff; m = i_dev->si_drv1; - if (!m->busy) + if (mode != -1 && !m->busy) return EBADF; - s = spltty(); snd_mtxlock(m->lock); if ((cmd & MIXER_WRITE(0)) == MIXER_WRITE(0)) { if (j == SOUND_MIXER_RECSRC) ret = mixer_setrecsrc(m, *arg_i); else ret = mixer_set(m, j, *arg_i); snd_mtxunlock(m->lock); - splx(s); return (ret == 0)? 0 : ENXIO; } if ((cmd & MIXER_READ(0)) == MIXER_READ(0)) { switch (j) { case SOUND_MIXER_DEVMASK: case SOUND_MIXER_CAPS: case SOUND_MIXER_STEREODEVS: v = mix_getdevs(m); break; case SOUND_MIXER_RECMASK: v = mix_getrecdevs(m); break; case SOUND_MIXER_RECSRC: v = mixer_getrecsrc(m); break; default: v = mixer_get(m, j); } *arg_i = v; snd_mtxunlock(m->lock); return (v != -1)? 0 : ENXIO; } snd_mtxunlock(m->lock); - splx(s); return ENXIO; } #ifdef USING_DEVFS static void mixer_clone(void *arg, struct ucred *cred, char *name, int namelen, struct cdev **dev) { struct snddev_info *sd; if (*dev != NULL) return; if (strcmp(name, "mixer") == 0) { sd = devclass_get_softc(pcm_devclass, snd_unit); if (sd != NULL) { *dev = sd->mixer_dev; dev_ref(*dev); } } } static void mixer_sysinit(void *p) { mixer_ehtag = EVENTHANDLER_REGISTER(dev_clone, mixer_clone, 0, 1000); } static void mixer_sysuninit(void *p) { if (mixer_ehtag != NULL) EVENTHANDLER_DEREGISTER(dev_clone, mixer_ehtag); } SYSINIT(mixer_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, mixer_sysinit, NULL); SYSUNINIT(mixer_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, mixer_sysuninit, NULL); #endif