/*- * Copyright 2003,2004 Yuriy Tsibizov * 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 * in this position and unchanged. * 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 ``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 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. * * $Id: emu10kx-dev.c,v 1.62 2005/10/15 09:05:26 chibis Exp $ * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #if __FreeBSD_version >= 500000 #include #else #define mtx_init(LOCK, DEVDESC, LOCKDESC, MTX_TYPE) #define mtx_lock(LOCK) #define mtx_unlock(LOCK) #define mtx_destroy(LOCK) #endif #include #include #include #if __FreeBSD_version >= 500000 #include #endif #include #include #include #include #include #include #include #include "emu10kx.h" #include "emu10kx-dev.h" #include "emu10kx-mixer.h" #include "emu10kx-rm.h" #include "emu10kx-ir.h" #include "alsa/8010.h" static d_open_t emu10kx_open; static d_close_t emu10kx_close; static d_read_t emu10kx_read; static d_ioctl_t emu10kx_ioctl; static int emu10kx_prepare(struct emu_sc_info *sc, struct sbuf *s); static struct cdevsw emu10kx_cdevsw = { .d_open = emu10kx_open, .d_close = emu10kx_close, .d_read = emu10kx_read, .d_ioctl = emu10kx_ioctl, .d_name = "emu10kx", #if __FreeBSD_version < 500000 .d_maj = EMU10KX_MAJOR, #endif #if __FreeBSD_version > 502103 .d_version = D_VERSION, #endif }; #define RANGE(var, low, high) (var) = \ (((var)<(low))? (low) : ((var)>(high))? (high) : (var)) /* ARGSUSED */ #if __FreeBSD_version < 500000 static int emu10kx_open(dev_t i_dev, int flags __unused, int mode __unused, struct proc *td __unused) #else static int emu10kx_open(struct cdev *i_dev, int flags __unused, int mode __unused, struct thread *td __unused) #endif { int error; struct emu_sc_info *sc; sc = i_dev->si_drv1; mtx_lock(&sc->emu10kx_lock); if (sc->emu10kx_isopen) { mtx_unlock(&sc->emu10kx_lock); return EBUSY; } sc->emu10kx_isopen = 1; mtx_unlock(&sc->emu10kx_lock); if (sbuf_new(&sc->emu10kx_sbuf, NULL, 4096, 0) == NULL) { error = ENXIO; goto out; } sc->emu10kx_bufptr = 0; error = (emu10kx_prepare(sc, &sc->emu10kx_sbuf) > 0) ? 0 : ENOMEM; out: if (error) { mtx_lock(&sc->emu10kx_lock); sc->emu10kx_isopen = 0; mtx_unlock(&sc->emu10kx_lock); } return (error); } #if __FreeBSD_version < 500000 static int emu10kx_close(dev_t i_dev, int flags __unused, int mode __unused, struct proc *td __unused) #else static int emu10kx_close(struct cdev *i_dev, int flags __unused, int mode __unused, struct thread *td __unused) #endif { struct emu_sc_info *sc; sc = i_dev->si_drv1; mtx_lock(&sc->emu10kx_lock); if (!(sc->emu10kx_isopen)) { mtx_unlock(&sc->emu10kx_lock); return EBADF; } sbuf_delete(&sc->emu10kx_sbuf); sc->emu10kx_isopen = 0; mtx_unlock(&sc->emu10kx_lock); return 0; } #if __FreeBSD_version < 500000 static int emu10kx_read(dev_t i_dev, struct uio *buf, int flag __unused) #else static int emu10kx_read(struct cdev *i_dev, struct uio *buf, int flag __unused) #endif { int l, err; struct emu_sc_info *sc; sc = i_dev->si_drv1; mtx_lock(&sc->emu10kx_lock); if (!(sc->emu10kx_isopen)) { mtx_unlock(&sc->emu10kx_lock); return EBADF; } mtx_unlock(&sc->emu10kx_lock); l = min(buf->uio_resid, sbuf_len(&sc->emu10kx_sbuf) - sc->emu10kx_bufptr); err = (l > 0) ? uiomove(sbuf_data(&sc->emu10kx_sbuf) + sc->emu10kx_bufptr, l, buf) : 0; sc->emu10kx_bufptr += l; return err; } static int emu10kx_prepare(struct emu_sc_info *sc, struct sbuf *s) { sbuf_printf(s, "FreeBSD EMU10Kx Audio Driver\n"); sbuf_printf(s, "\nHardware resource usage:\n"); sbuf_printf(s, "DSP General Purpose Registers: %d used, %d total\n", sc->rm->num_used, sc->rm->num_gprs); sbuf_printf(s, "DSP Instruction Registers: %d used, %d total\n", sc->routing_code_end, sc->code_size); sbuf_printf(s, "Card supports"); if (sc->has_ac97) { sbuf_printf(s, " AC97 codec"); } else { sbuf_printf(s, " NO AC97 codec"); }; if (sc->has_51) sbuf_printf(s, " and 5.1 output"); if (sc->has_71) sbuf_printf(s, " and 7.1 output"); if (sc->is_emu10k1) sbuf_printf(s, ", SBLive! DSP code"); if (sc->is_emu10k2) sbuf_printf(s, ", Audigy DSP code"); if (sc->is_ca0151) sbuf_printf(s, " Audigy DSP code with Audigy2 hacks"); if (sc->is_ca0108) sbuf_printf(s, " Audigy DSP code with Audigy2Value hacks"); sbuf_printf(s, "\n"); if (sc->broken_digital) sbuf_printf(s, "Digital mode unsupported\n"); sbuf_printf(s, "\nInstalled devices:\n"); if (sc->mixer) { sbuf_printf(s, "EMU10Kx Mixer Interface on %s\n", device_get_nameunit(sc->dev)); sbuf_printf(s, "\t FX BUSes Inputs Outputs \n"); /* 1234567890123456789012345678901234567890 */ sbuf_printf(s, "\tPlayback %-9d %-9d %-9d\n", sc->mixer->pmixercount[MIXER_FX], sc->mixer->pmixercount[MIXER_INPUT], sc->mixer->pmixercount[MIXER_OUTPUT]); sbuf_printf(s, "\tRecording %-9d %-9d %-9d\n", sc->mixer->rmixercount[MIXER_FX], sc->mixer->rmixercount[MIXER_INPUT], sc->mixer->rmixercount[MIXER_OUTPUT]); } if (sc->pcm) { sbuf_printf(s, "EMU10Kx PCM Interface on %s\n", device_get_nameunit(sc->pcm)); sbuf_printf(s, "\t%s mode, S/PDIF output is %s\n", sc->mixer->is_digital ? "Digital" : "Analog", sc->mixer->spdif_mode ? "AC3 encoded" : "PCM stereo"); } if (sc->midi[0]) { sbuf_printf(s, "EMU10Kx MIDI Interface\n"); sbuf_printf(s, "\tOn-card connector on %s\n", device_get_nameunit(sc->midi[0])); } if (sc->midi[1]) { sbuf_printf(s, "\tOn-Drive connector on %s\n", device_get_nameunit(sc->midi[1])); } if (sc->midi[0]) { sbuf_printf(s, "\tIR reciever MIDI events %s\n", sc->enable_ir ? "enabled" : "disabled"); } sbuf_finish(s); return sbuf_len(s); } static int emu10kx_invalid(struct emu_sc_info *sc, int flags) { if ((sc->enable_debug == 0) && (flags & DEBUG_MIXER)) return (1); if (sc->enable_debug) return (0); if ((!(sc->has_51)) && (flags & NEED_51)) return (1); if ((!(sc->has_71)) && (flags & NEED_71)) return (1); if ((!(sc->has_ac97)) && (flags & NEED_AC97)) return (1); return (0); } /* ARGSUSED */ #if __FreeBSD_version < 500000 static int emu10kx_ioctl(dev_t i_dev, u_long cmd, caddr_t addr, int flags __unused, struct proc *td __unused) #else static int emu10kx_ioctl(struct cdev *i_dev, u_long cmd, caddr_t addr, int flags __unused, struct thread *td __unused) #endif { struct emu_sc_info *sc; struct emu10kx_mixer_page *page; struct emu10kx_mixer_ctl *ctl; struct emu10kx_params *params; struct emu10kx_peek *peek; int *ampvalue; int *cardmode; int *ir; int *dbg; int code_offset; int gpr_offset; int voice; int offset; int value; int ret; int i; sc = i_dev->si_drv1; ret = 0; switch (cmd) { case E10GETPAGECOUNT: page = (struct emu10kx_mixer_page *)addr; page->page = 5; break; case E10GETPAGEINFO: page = (struct emu10kx_mixer_page *)addr; if ((page->page < 0) || (page->page > 4)) return EINVAL; switch (page->page) { case MIXER_FX: strncpy(page->desc, "FX (PCM) buses", 64); page->playback_controls = sc->mixer->pmixercount[MIXER_FX]; page->recording_controls = sc->mixer->rmixercount[MIXER_FX]; break; case MIXER_INPUT: strncpy(page->desc, "DSP Inputs", 64); page->playback_controls = sc->mixer->pmixercount[MIXER_INPUT]; page->recording_controls = sc->mixer->rmixercount[MIXER_INPUT]; break; case MIXER_OUTPUT: strncpy(page->desc, "DSP Outputs", 64); page->playback_controls = sc->mixer->pmixercount[MIXER_OUTPUT]; page->recording_controls = sc->mixer->rmixercount[MIXER_OUTPUT]; break; case MIXER_AC97: strncpy(page->desc, "AC97 Codec", 64); page->playback_controls = sc->mixer->pmixercount[MIXER_AC97]; page->recording_controls = sc->mixer->rmixercount[MIXER_AC97]; break; case MIXER_MAIN: strncpy(page->desc, "Main Mix", 64); page->playback_controls = 1; page->recording_controls = 1; break; default: return EINVAL; }; break; case E10GETMIXER: ctl = (struct emu10kx_mixer_ctl *)addr; if ((ctl->page < 0) || (ctl->page > 4)) return EINVAL; if ((ctl->connector < 0)) return EINVAL; switch (ctl->page) { case MIXER_FX: if ((unsigned)ctl->connector > sc->mixer->pmixercount[MIXER_FX]) return EINVAL; if (emu10kx_invalid(sc, sc->mixer->fxbuses[ctl->connector].flags)) return EINVAL; strncpy(ctl->desc, sc->mixer->fxbuses[ctl->connector].desc, 64); for (i = 0; i < 4; i++) ctl->volume[i] = sc->mixer->fxbuses[ctl->connector].volume[i]; break; case MIXER_INPUT: if ((unsigned)ctl->connector > sc->mixer->pmixercount[MIXER_INPUT]) return EINVAL; if (emu10kx_invalid(sc, sc->mixer->inputs[ctl->connector].flags)) return EINVAL; strncpy(ctl->desc, sc->mixer->inputs[ctl->connector].desc, 64); for (i = 0; i < 4; i++) ctl->volume[i] = sc->mixer->inputs[ctl->connector].volume[i]; break; case MIXER_OUTPUT: if ((unsigned)ctl->connector > sc->mixer->pmixercount[MIXER_OUTPUT]) return EINVAL; if (emu10kx_invalid(sc, sc->mixer->outputs[ctl->connector].flags)) return EINVAL; strncpy(ctl->desc, sc->mixer->outputs[ctl->connector].desc, 64); for (i = 0; i < 4; i++) ctl->volume[i] = sc->mixer->outputs[ctl->connector].volume[i]; break; case MIXER_AC97: return EINVAL; break; case MIXER_MAIN: if ((unsigned)ctl->connector > 1) return EINVAL; strncpy(ctl->desc, "Main Mix", 64); for (i = 0; i < 4; i++) ctl->volume[i] = sc->mixer->mix.volume[i]; break; default: return EINVAL; }; break; case E10SETMIXER: ctl = (struct emu10kx_mixer_ctl *)addr; if ((ctl->page < 0) || (ctl->page > 4)) return EINVAL; if ((ctl->connector < 0)) return EINVAL; switch (ctl->page) { case MIXER_FX: if ((unsigned)ctl->connector > sc->mixer->pmixercount[MIXER_FX]) return EINVAL; if (emu10kx_invalid(sc, sc->mixer->fxbuses[ctl->connector].flags)) return EINVAL; for (i = 0; i < 4; i++) { if (sc->mixer->fxbuses[ctl->connector].volume[i] < 0) ctl->volume[i] = sc->mixer->fxbuses[ctl->connector].volume[i]; if (ctl->volume[i] < 0) ctl->volume[i] = sc->mixer->fxbuses[ctl->connector].volume[i]; if (ctl->volume[i] > 100) ctl->volume[i] = 100; sc->mixer->fxbuses[ctl->connector].volume[i] = ctl->volume[i]; if ((sc->mixer->fxbuses[ctl->connector].volume[i] >= 0) && (sc->mixer->fxbuses[ctl->connector].gpr[i] >= 0)) emumix_set_fxvol(sc, sc->mixer->fxbuses[ctl->connector].gpr[i], sc->mixer->fxbuses[ctl->connector].volume[i]); } break; case MIXER_INPUT: if ((unsigned)ctl->connector > sc->mixer->pmixercount[MIXER_INPUT]) return EINVAL; if (emu10kx_invalid(sc, sc->mixer->inputs[ctl->connector].flags)) return EINVAL; for (i = 0; i < 4; i++) { if (sc->mixer->inputs[ctl->connector].volume[i] < 0) ctl->volume[i] = sc->mixer->inputs[ctl->connector].volume[i]; if (ctl->volume[i] < 0) ctl->volume[i] = sc->mixer->inputs[ctl->connector].volume[i]; if (ctl->volume[i] > 100) ctl->volume[i] = 100; sc->mixer->inputs[ctl->connector].volume[i] = ctl->volume[i]; if ((sc->mixer->inputs[ctl->connector].volume[i] >= 0) && (sc->mixer->inputs[ctl->connector].gpr[i] >= 0)) emumix_set_fxvol(sc, sc->mixer->inputs[ctl->connector].gpr[i], sc->mixer->inputs[ctl->connector].volume[i]); } break; case MIXER_OUTPUT: if ((unsigned)ctl->connector > sc->mixer->pmixercount[MIXER_OUTPUT]) return EINVAL; if (emu10kx_invalid(sc, sc->mixer->outputs[ctl->connector].flags)) return EINVAL; for (i = 0; i < 2; i++) { if (sc->mixer->outputs[ctl->connector].volume[i] < 0) ctl->volume[i] = sc->mixer->outputs[ctl->connector].volume[i]; if (ctl->volume[i] < 0) ctl->volume[i] = sc->mixer->outputs[ctl->connector].volume[i]; if (ctl->volume[i] > 100) ctl->volume[i] = 100; sc->mixer->outputs[ctl->connector].volume[i] = ctl->volume[i]; if ((sc->mixer->outputs[ctl->connector].volume[i] >= 0) && (sc->mixer->outputs[ctl->connector].gpr[i] >= 0)) emumix_set_fxvol(sc, sc->mixer->outputs[ctl->connector].gpr[i], sc->mixer->outputs[ctl->connector].volume[i]); } break; case MIXER_AC97: return EINVAL; break; case MIXER_MAIN: if ((unsigned)ctl->connector > 1) return EINVAL; for (i = 0; i < 4; i++) { if (sc->mixer->mix.volume[i] < 0) ctl->volume[i] = sc->mixer->mix.volume[i]; if (ctl->volume[i] < 0) ctl->volume[i] = sc->mixer->mix.volume[i]; if (ctl->volume[i] > 100) ctl->volume[i] = 100; sc->mixer->mix.volume[i] = ctl->volume[i]; if ((sc->mixer->mix.volume[i] >= 0) && (sc->mixer->mix.gpr[i] >= 0)) emumix_set_fxvol(sc, sc->mixer->mix.gpr[i], sc->mixer->mix.volume[i]); } break; default: return EINVAL; }; break; case E10MIXERAMPLIFY: ampvalue = (int *)addr; if (*ampvalue == 0) { *ampvalue = sc->mixer->amp.volume[MIXER_P_VOLUME]; return 0; }; RANGE(*ampvalue, 1, 16); /* not a bug: volume is a value 1..16 used to amplify signal */ sc->mixer->amp.volume[MIXER_P_VOLUME] = *ampvalue; emumix_set_gpr(sc, sc->mixer->amp.gpr[GPR_P_VOLUME], sc->mixer->amp.volume[MIXER_P_VOLUME]); break; case E10GETMODE: cardmode = (int *)addr; *cardmode = sc->mixer->is_digital ? 1 : 0; break; case E10SETMODE: cardmode = (int *)addr; emumix_set_mode(sc, *cardmode); break; case E10GETSPDIFMODE: cardmode = (int *)addr; *cardmode = sc->mixer->spdif_mode ? 1 : 0; break; case E10SETSPDIFMODE: cardmode = (int *)addr; emumix_set_spdif_mode(sc, *cardmode); *cardmode = sc->mixer->spdif_mode ? 1 : 0; break; case E10ENABLEIR: ir = (int *)addr; if (*ir == 1) emu_enable_ir(sc); *ir = sc->enable_ir; break; case E10MIXERRESET: emumix_reset(sc); emumix_set_mode(sc, sc->mixer->is_digital); break; case E10DEBUG: dbg = (int *)addr; if (*dbg == 1) sc->enable_debug = 1; if (*dbg == 0) sc->enable_debug = 0; *dbg = sc->enable_debug; break; case E10PARAMS: params = (struct emu10kx_params *)addr; params->mchannel_fx = sc->mchannel_fx; params->dsp_zero = sc->dsp_zero; params->code_base = sc->code_base; params->code_size = sc->code_size; params->gpr_base = sc->gpr_base; params->num_gprs = sc->num_gprs; params->input_base = sc->input_base; params->output_base = sc->output_base; params->opcode_shift = sc->opcode_shift; params->high_operand_shift = sc->high_operand_shift; params->is_emu10k1 = sc->is_emu10k1; params->is_emu10k2 = sc->is_emu10k2; params->is_ca0151 = sc->is_ca0151; params->is_ca0108 = sc->is_ca0108; params->num_g = NUM_G; params->iocfg = emu_rd(sc, HCFG, 4); params->a_iocfg = emu_rd(sc, A_IOCFG, 2); break; case E10PEEKDSP: peek = (struct emu10kx_peek *)addr; code_offset = peek->addr; if (code_offset < 0) code_offset = 0; peek->code[0] = 0; peek->code[1] = 0; if ((unsigned)code_offset < sc->code_size) { peek->code[0] = emu_rdptr(sc, 0, sc->code_base + code_offset * 2); peek->code[1] = emu_rdptr(sc, 0, sc->code_base + code_offset * 2 + 1); }; break; case E10PEEKGPR: peek = (struct emu10kx_peek *)addr; gpr_offset = peek->addr; if (gpr_offset < 0) gpr_offset = 0; if ((unsigned)gpr_offset < sc->num_gprs) { peek->code[0] = emu_rdptr(sc, 0, GPR(gpr_offset)); peek->code[1] = 0; }; break; case E10PEEKVOICE: peek = (struct emu10kx_peek *)addr; offset = peek->addr; voice = peek->voice; if ((voice < 0) || (voice > NUM_G)) break; if (offset < 0) break; if (offset <= 0x7f) { peek->code[0] = emu_rdptr(sc, (unsigned)voice, (unsigned)offset); peek->code[1] = 0; }; break; case E10PEEKP16V: peek = (struct emu10kx_peek *)addr; offset = peek->addr; voice = peek->voice; if ((voice < 0) || (voice > 16)) /* XXX maximum p16v voice? */ break; if (offset < 0) break; if (offset <= 0x7f) { peek->code[0] = emu_rd_p16vptr(sc, (unsigned)voice, (unsigned)offset); peek->code[1] = 0; }; break; case E10POKEP16V: peek = (struct emu10kx_peek *)addr; offset = peek->addr; voice = peek->voice; value = peek->code[0]; if (sc->enable_debug == 0) break; if ((voice < 0) || (voice > 16)) /* XXX maximum p16v voice? */ break; if (offset < 0) break; if (offset <= 0x7f) { emu_wr_p16vptr(sc, (unsigned)voice, (unsigned)offset, value); peek->code[0] = 0; peek->code[1] = 0; }; break; default: return ENOTTY; } return ret; } #if __FreeBSD_version < 500000 static int unit2minor(int unit) { KASSERT(unit <= 0xffffff, ("Invalid unit (%d) in unit2minor", unit)); return ((unit & 0xff) | ((unit << 8) & ~0xffff)); } #endif /* INIT & UNINIT */ int emu10kx_dev_init(struct emu_sc_info *sc) { int unit; mtx_init(&sc->emu10kx_lock, "kxdevlock", NULL, 0); unit = device_get_unit(sc->dev); sc->cdev = make_dev(&emu10kx_cdevsw, unit2minor(unit), UID_ROOT, GID_WHEEL, 0640, "emu10kx%d", unit); #if __FreeBSD_version < 500000 device_printf(sc->dev, "major %d minor %d\n", EMU10KX_MAJOR, unit2minor(unit)); if (sc->cdev != 0) { #else if (sc->cdev != NULL) { #endif sc->cdev->si_drv1 = sc; return 0; } return ENXIO; } int emu10kx_dev_uninit(struct emu_sc_info *sc) { intrmask_t s; s = spltty(); mtx_lock(&sc->emu10kx_lock); if (sc->emu10kx_isopen) { mtx_unlock(&sc->emu10kx_lock); splx(s); return EBUSY; } if (sc->cdev) destroy_dev(sc->cdev); sc->cdev = 0; splx(s); mtx_destroy(&sc->emu10kx_lock); return 0; }