diff --git a/sys/dev/pci/azalia.c b/sys/dev/pci/azalia.c index 971a3f957985..433d0a543eb6 100644 --- a/sys/dev/pci/azalia.c +++ b/sys/dev/pci/azalia.c @@ -1,4 +1,4 @@ -/* $NetBSD: azalia.c,v 1.20 2006/05/07 08:31:44 kent Exp $ */ +/* $NetBSD: azalia.c,v 1.21 2006/06/07 15:23:59 kent Exp $ */ /*- * Copyright (c) 2005 The NetBSD Foundation, Inc. @@ -49,7 +49,7 @@ */ #include -__KERNEL_RCSID(0, "$NetBSD: azalia.c,v 1.20 2006/05/07 08:31:44 kent Exp $"); +__KERNEL_RCSID(0, "$NetBSD: azalia.c,v 1.21 2006/06/07 15:23:59 kent Exp $"); #include #include @@ -120,6 +120,7 @@ typedef struct azalia_t { bus_space_handle_t ioh; bus_size_t map_size; bus_dma_tag_t dmat; + uint32_t subid; codec_t codecs[15]; int ncodecs; /* number of codecs */ @@ -135,8 +136,6 @@ typedef struct azalia_t { int nistreams, nostreams, nbstreams; stream_t pstream; stream_t rstream; - - int running; } azalia_t; #define XNAME(sc) ((sc)->dev.dv_xname) #define AZ_READ_1(z, r) bus_space_read_1((z)->iot, (z)->ioh, HDA_##r) @@ -167,7 +166,6 @@ static int azalia_free_dmamem(const azalia_t *, azalia_dma_t*); static int azalia_codec_init(codec_t *); static int azalia_codec_delete(codec_t *); -static int azalia_codec_construct_format(codec_t *); static void azalia_codec_add_bits(codec_t *, int, uint32_t, int); static void azalia_codec_add_format(codec_t *, int, int, int, uint32_t, int32_t); @@ -175,18 +173,6 @@ static int azalia_codec_comresp(const codec_t *, nid_t, uint32_t, uint32_t, uint32_t *); static int azalia_codec_connect_stream(codec_t *, int, uint16_t, int); -static int azalia_mixer_init(codec_t *); -static int azalia_mixer_delete(codec_t *); -static int azalia_mixer_get(const codec_t *, mixer_ctrl_t *); -static int azalia_mixer_set(codec_t *, const mixer_ctrl_t *); -static int azalia_mixer_ensure_capacity(codec_t *, size_t); -static u_char azalia_mixer_from_device_value(const codec_t *, - const mixer_item_t *, uint32_t ); -static uint32_t azalia_mixer_to_device_value(const codec_t *, - const mixer_item_t *, u_char); -static boolean_t azalia_mixer_validate_value(const codec_t *, - const mixer_item_t *, u_char); - static int azalia_widget_init(widget_t *, const codec_t *, int); static int azalia_widget_init_audio(widget_t *, const codec_t *); static int azalia_widget_print_audio(const widget_t *, const char *); @@ -349,6 +335,8 @@ azalia_pci_attach(struct device *parent, struct device *self, void *aux) azalia_pci_detach(self, 0); return; } + sc->subid = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_SUBSYS_ID_REG); + config_interrupts(self, azalia_attach_intr); } @@ -896,7 +884,7 @@ azalia_free_dmamem(const azalia_t *az, azalia_dma_t* d) static int azalia_codec_init(codec_t *this) { - uint32_t rev, result; + uint32_t rev, id, result; int err, addr, n, i; this->comresp = azalia_codec_comresp; @@ -909,14 +897,16 @@ azalia_codec_init(codec_t *this) if (err) return err; err = this->comresp(this, CORB_NID_ROOT, CORB_GET_PARAMETER, - COP_VENDOR_ID, &result); + COP_VENDOR_ID, &id); if (err) return err; - azalia_codec_init_vtbl(this, result); + this->vid = id; + this->subid = this->az->subid; + azalia_codec_init_vtbl(this); if (this->name == NULL) { aprint_normal("%s: codec: 0x%4.4x/0x%4.4x (rev. %u.%u)\n", - XNAME(this->az), result >> 16, result & 0xffff, + XNAME(this->az), id >> 16, id & 0xffff, COP_RID_REVISION(rev), COP_RID_STEPPING(rev)); } else { aprint_normal("%s: codec: %s (rev. %u.%u)\n", @@ -1036,13 +1026,13 @@ azalia_codec_init(codec_t *this) if (err) return err; - return azalia_mixer_init(this); + return this->mixer_init(this); } static int azalia_codec_delete(codec_t *this) { - azalia_mixer_delete(this); + this->mixer_delete(this); if (this->formats != NULL) { free(this->formats, M_DEVBUF); this->formats = NULL; @@ -1052,7 +1042,7 @@ azalia_codec_delete(codec_t *this) return 0; } -static int +int azalia_codec_construct_format(codec_t *this) { char flagbuf[FLAGBUFLEN]; @@ -1294,937 +1284,6 @@ exit: return err; } -/* ================================================================ - * HDA mixer functions - * ================================================================ */ - -static int -azalia_mixer_init(codec_t *this) -{ - /* - * pin "%2.2x" - * audio output "dac%2.2x" - * audio input "adc%2.2x" - * mixer "mixer%2.2x" - * selector "sel%2.2x" - */ - mixer_item_t *m; - int nadcs; - int err, i, j, k; - - nadcs = 0; - this->maxmixers = 10; - this->nmixers = 0; - this->mixers = malloc(sizeof(mixer_item_t) * this->maxmixers, - M_DEVBUF, M_ZERO | M_NOWAIT); - if (this->mixers == NULL) { - aprint_error("%s: out of memory in %s\n", XNAME(this->az), - __func__); - return ENOMEM; - } - - /* register classes */ - DPRINTF(("%s: register classes\n", __func__)); -#define AZ_CLASS_INPUT 0 -#define AZ_CLASS_OUTPUT 1 -#define AZ_CLASS_RECORD 2 - m = &this->mixers[AZ_CLASS_INPUT]; - m->devinfo.index = AZ_CLASS_INPUT; - strlcpy(m->devinfo.label.name, AudioCinputs, - sizeof(m->devinfo.label.name)); - m->devinfo.type = AUDIO_MIXER_CLASS; - m->devinfo.mixer_class = AZ_CLASS_INPUT; - m->devinfo.next = AUDIO_MIXER_LAST; - m->devinfo.prev = AUDIO_MIXER_LAST; - m->nid = 0; - - m = &this->mixers[AZ_CLASS_OUTPUT]; - m->devinfo.index = AZ_CLASS_OUTPUT; - strlcpy(m->devinfo.label.name, AudioCoutputs, - sizeof(m->devinfo.label.name)); - m->devinfo.type = AUDIO_MIXER_CLASS; - m->devinfo.mixer_class = AZ_CLASS_OUTPUT; - m->devinfo.next = AUDIO_MIXER_LAST; - m->devinfo.prev = AUDIO_MIXER_LAST; - m->nid = 0; - - m = &this->mixers[AZ_CLASS_RECORD]; - m->devinfo.index = AZ_CLASS_RECORD; - strlcpy(m->devinfo.label.name, AudioCrecord, - sizeof(m->devinfo.label.name)); - m->devinfo.type = AUDIO_MIXER_CLASS; - m->devinfo.mixer_class = AZ_CLASS_RECORD; - m->devinfo.next = AUDIO_MIXER_LAST; - m->devinfo.prev = AUDIO_MIXER_LAST; - m->nid = 0; - - this->nmixers = AZ_CLASS_RECORD + 1; - -#define MIXER_REG_PROLOG \ - mixer_devinfo_t *d; \ - err = azalia_mixer_ensure_capacity(this, this->nmixers + 1); \ - if (err) \ - return err; \ - m = &this->mixers[this->nmixers]; \ - d = &m->devinfo; \ - d->index = this->nmixers; \ - m->nid = i - - FOR_EACH_WIDGET(this, i) { - const widget_t *w; - - w = &this->w[i]; - - if (w->type == COP_AWTYPE_AUDIO_INPUT) - nadcs++; - - /* selector */ - if (w->type != COP_AWTYPE_AUDIO_MIXER && w->nconnections >= 2) { - MIXER_REG_PROLOG; - DPRINTF(("%s: selector %s\n", __func__, w->name)); - snprintf(d->label.name, sizeof(d->label.name), - "%s.source", w->name); - d->type = AUDIO_MIXER_ENUM; - if (w->type == COP_AWTYPE_AUDIO_MIXER) - d->mixer_class = AZ_CLASS_RECORD; - else if (w->type == COP_AWTYPE_AUDIO_SELECTOR) - d->mixer_class = AZ_CLASS_INPUT; - else - d->mixer_class = AZ_CLASS_OUTPUT; - d->next = AUDIO_MIXER_LAST; - d->prev = AUDIO_MIXER_LAST; - m->target = MI_TARGET_CONNLIST; - for (j = 0, k = 0; j < w->nconnections && k < 32; j++) { - if (!VALID_WIDGET_NID(w->connections[j], this)) - continue; - d->un.e.member[k].ord = k; - DPRINTF(("%s: selector %d=%s\n", __func__, j, - this->w[w->connections[j]].name)); - strlcpy(d->un.e.member[k].label.name, - this->w[w->connections[j]].name, - MAX_AUDIO_DEV_LEN); - k++; - } - d->un.e.num_mem = k; - this->nmixers++; - } - - /* output mute */ - if (w->widgetcap & COP_AWCAP_OUTAMP && - w->outamp_cap & COP_AMPCAP_MUTE) { - MIXER_REG_PROLOG; - DPRINTF(("%s: output mute %s\n", __func__, w->name)); - snprintf(d->label.name, sizeof(d->label.name), - "%s.mute", w->name); - d->type = AUDIO_MIXER_ENUM; - if (w->type == COP_AWTYPE_AUDIO_MIXER) - d->mixer_class = AZ_CLASS_OUTPUT; - else if (w->type == COP_AWTYPE_AUDIO_SELECTOR) - d->mixer_class = AZ_CLASS_OUTPUT; - else if (w->type == COP_AWTYPE_PIN_COMPLEX) - d->mixer_class = AZ_CLASS_OUTPUT; - else - d->mixer_class = AZ_CLASS_INPUT; - d->next = AUDIO_MIXER_LAST; - d->prev = AUDIO_MIXER_LAST; - m->target = MI_TARGET_OUTAMP; - d->un.e.num_mem = 2; - d->un.e.member[0].ord = 0; - strlcpy(d->un.e.member[0].label.name, AudioNoff, - MAX_AUDIO_DEV_LEN); - d->un.e.member[1].ord = 1; - strlcpy(d->un.e.member[1].label.name, AudioNon, - MAX_AUDIO_DEV_LEN); - this->nmixers++; - } - - /* output gain */ - if (w->widgetcap & COP_AWCAP_OUTAMP - && COP_AMPCAP_NUMSTEPS(w->outamp_cap)) { - MIXER_REG_PROLOG; - DPRINTF(("%s: output gain %s\n", __func__, w->name)); - snprintf(d->label.name, sizeof(d->label.name), - "%s", w->name); - d->type = AUDIO_MIXER_VALUE; - if (w->type == COP_AWTYPE_AUDIO_MIXER) - d->mixer_class = AZ_CLASS_OUTPUT; - else if (w->type == COP_AWTYPE_AUDIO_SELECTOR) - d->mixer_class = AZ_CLASS_OUTPUT; - else if (w->type == COP_AWTYPE_PIN_COMPLEX) - d->mixer_class = AZ_CLASS_OUTPUT; - else - d->mixer_class = AZ_CLASS_INPUT; - d->next = AUDIO_MIXER_LAST; - d->prev = AUDIO_MIXER_LAST; - m->target = MI_TARGET_OUTAMP; - d->un.v.num_channels = WIDGET_CHANNELS(w); -#ifdef MAX_VOLUME_255 - d->un.v.units.name[0] = 0; - d->un.v.delta = AUDIO_MAX_GAIN / - COP_AMPCAP_NUMSTEPS(w->outamp_cap); -#else - snprintf(d->un.v.units.name, sizeof(d->un.v.units.name), - "0.25x%ddB", COP_AMPCAP_STEPSIZE(w->outamp_cap)+1); - d->un.v.delta = 1; -#endif - this->nmixers++; - } - - /* input mute */ - if (w->widgetcap & COP_AWCAP_INAMP && - w->inamp_cap & COP_AMPCAP_MUTE) { - DPRINTF(("%s: input mute %s\n", __func__, w->name)); - for (j = 0; j < w->nconnections; j++) { - MIXER_REG_PROLOG; - if (!VALID_WIDGET_NID(w->connections[j], this)) - continue; - DPRINTF(("%s: input mute %s.%s\n", __func__, - w->name, this->w[w->connections[j]].name)); - snprintf(d->label.name, sizeof(d->label.name), - "%s.%s.mute", w->name, - this->w[w->connections[j]].name); - d->type = AUDIO_MIXER_ENUM; - if (w->type == COP_AWTYPE_PIN_COMPLEX) - d->mixer_class = AZ_CLASS_OUTPUT; - else if (w->type == COP_AWTYPE_AUDIO_INPUT) - d->mixer_class = AZ_CLASS_RECORD; - else - d->mixer_class = AZ_CLASS_INPUT; - d->next = AUDIO_MIXER_LAST; - d->prev = AUDIO_MIXER_LAST; - m->target = j; - d->un.e.num_mem = 2; - d->un.e.member[0].ord = 0; - strlcpy(d->un.e.member[0].label.name, - AudioNoff, MAX_AUDIO_DEV_LEN); - d->un.e.member[1].ord = 1; - strlcpy(d->un.e.member[1].label.name, - AudioNon, MAX_AUDIO_DEV_LEN); - this->nmixers++; - } - } - - /* input gain */ - if (w->widgetcap & COP_AWCAP_INAMP - && COP_AMPCAP_NUMSTEPS(w->inamp_cap)) { - DPRINTF(("%s: input gain %s\n", __func__, w->name)); - for (j = 0; j < w->nconnections; j++) { - MIXER_REG_PROLOG; - if (!VALID_WIDGET_NID(w->connections[j], this)) - continue; - DPRINTF(("%s: input gain %s.%s\n", __func__, - w->name, this->w[w->connections[j]].name)); - snprintf(d->label.name, sizeof(d->label.name), - "%s.%s", w->name, - this->w[w->connections[j]].name); - d->type = AUDIO_MIXER_VALUE; - if (w->type == COP_AWTYPE_PIN_COMPLEX) - d->mixer_class = AZ_CLASS_OUTPUT; - else if (w->type == COP_AWTYPE_AUDIO_INPUT) - d->mixer_class = AZ_CLASS_RECORD; - else - d->mixer_class = AZ_CLASS_INPUT; - d->next = AUDIO_MIXER_LAST; - d->prev = AUDIO_MIXER_LAST; - m->target = j; - d->un.v.num_channels = WIDGET_CHANNELS(w); -#ifdef MAX_VOLUME_255 - d->un.v.units.name[0] = 0; - d->un.v.delta = AUDIO_MAX_GAIN / - COP_AMPCAP_NUMSTEPS(w->inamp_cap); -#else - snprintf(d->un.v.units.name, - sizeof(d->un.v.units.name), "0.25x%ddB", - COP_AMPCAP_STEPSIZE(w->inamp_cap)+1); - d->un.v.delta = 1; -#endif - this->nmixers++; - } - } - - /* pin direction */ - if (w->type == COP_AWTYPE_PIN_COMPLEX && - w->d.pin.cap & COP_PINCAP_OUTPUT && - w->d.pin.cap & COP_PINCAP_INPUT) { - MIXER_REG_PROLOG; - DPRINTF(("%s: pin dir %s\n", __func__, w->name)); - snprintf(d->label.name, sizeof(d->label.name), - "%s.dir", w->name); - d->type = AUDIO_MIXER_ENUM; - d->mixer_class = AZ_CLASS_OUTPUT; - d->next = AUDIO_MIXER_LAST; - d->prev = AUDIO_MIXER_LAST; - m->target = MI_TARGET_PINDIR; - d->un.e.num_mem = 2; - d->un.e.member[0].ord = 0; - strlcpy(d->un.e.member[0].label.name, AudioNinput, - MAX_AUDIO_DEV_LEN); - d->un.e.member[1].ord = 1; - strlcpy(d->un.e.member[1].label.name, AudioNoutput, - MAX_AUDIO_DEV_LEN); - this->nmixers++; - } - - /* pin headphone-boost */ - if (w->type == COP_AWTYPE_PIN_COMPLEX && - w->d.pin.cap & COP_PINCAP_HEADPHONE) { - MIXER_REG_PROLOG; - DPRINTF(("%s: hpboost %s\n", __func__, w->name)); - snprintf(d->label.name, sizeof(d->label.name), - "%s.boost", w->name); - d->type = AUDIO_MIXER_ENUM; - d->mixer_class = AZ_CLASS_OUTPUT; - d->next = AUDIO_MIXER_LAST; - d->prev = AUDIO_MIXER_LAST; - m->target = MI_TARGET_PINBOOST; - d->un.e.num_mem = 2; - d->un.e.member[0].ord = 0; - strlcpy(d->un.e.member[0].label.name, AudioNoff, - MAX_AUDIO_DEV_LEN); - d->un.e.member[1].ord = 1; - strlcpy(d->un.e.member[1].label.name, AudioNon, - MAX_AUDIO_DEV_LEN); - this->nmixers++; - } - - /* volume knob */ - if (w->type == COP_AWTYPE_VOLUME_KNOB && - w->d.volume.cap & COP_VKCAP_DELTA) { - MIXER_REG_PROLOG; - DPRINTF(("%s: volume knob %s\n", __func__, w->name)); - strlcpy(d->label.name, w->name, sizeof(d->label.name)); - d->type = AUDIO_MIXER_VALUE; - d->mixer_class = AZ_CLASS_OUTPUT; - d->next = AUDIO_MIXER_LAST; - d->prev = AUDIO_MIXER_LAST; - m->target = MI_TARGET_VOLUME; - d->un.v.num_channels = 1; - d->un.v.units.name[0] = 0; -#ifdef MAX_VOLUME_255 - d->un.v.delta = AUDIO_MAX_GAIN / - COP_VKCAP_NUMSTEPS(w->d.volume.cap); -#else - d->un.v.delta = 1; -#endif - this->nmixers++; - } - } - - /* if the codec has multiple DAC groups, create "inputs.usingdac" */ - if (this->ndacgroups > 1) { - MIXER_REG_PROLOG; - DPRINTF(("%s: create inputs.usingdac\n", __func__)); - strlcpy(d->label.name, "usingdac", sizeof(d->label.name)); - d->type = AUDIO_MIXER_ENUM; - d->mixer_class = AZ_CLASS_INPUT; - d->next = AUDIO_MIXER_LAST; - d->prev = AUDIO_MIXER_LAST; - m->target = MI_TARGET_DAC; - for (i = 0; i < this->ndacgroups && i < 32; i++) { - d->un.e.member[i].ord = i; - for (j = 0; j < this->dacgroups[i].nconv; j++) { - if (j * 2 >= MAX_AUDIO_DEV_LEN) - break; - snprintf(d->un.e.member[i].label.name + j*2, - MAX_AUDIO_DEV_LEN - j*2, "%2.2x", - this->dacgroups[i].conv[j]); - } - } - d->un.e.num_mem = i; - this->nmixers++; - } - - /* if the codec has multiple ADCs, create "record.usingadc" */ - if (this->nadcs > 1) { - MIXER_REG_PROLOG; - DPRINTF(("%s: create inputs.usingadc\n", __func__)); - strlcpy(d->label.name, "usingadc", sizeof(d->label.name)); - d->type = AUDIO_MIXER_ENUM; - d->mixer_class = AZ_CLASS_RECORD; - d->next = AUDIO_MIXER_LAST; - d->prev = AUDIO_MIXER_LAST; - m->target = MI_TARGET_ADC; - for (i = 0; i < this->nadcs && i < 32; i++) { - d->un.e.member[i].ord = i; - strlcpy(d->un.e.member[i].label.name, - this->w[this->adcs[i]].name, MAX_AUDIO_DEV_LEN); - } - d->un.e.num_mem = i; - this->nmixers++; - } - - /* unmute all */ - DPRINTF(("%s: unmute\n", __func__)); - for (i = 0; i < this->nmixers; i++) { - mixer_ctrl_t mc; - - if (!IS_MI_TARGET_INAMP(this->mixers[i].target) && - this->mixers[i].target != MI_TARGET_OUTAMP) - continue; - if (this->mixers[i].devinfo.type != AUDIO_MIXER_ENUM) - continue; - mc.dev = i; - mc.type = AUDIO_MIXER_ENUM; - mc.un.ord = 0; - azalia_mixer_set(this, &mc); - } - - /* - * for bidirectional pins, - * green=front, orange=surround, gray=c/lfe, black=side --> output - * blue=line-in, pink=mic-in --> input - */ - DPRINTF(("%s: process bidirectional pins\n", __func__)); - for (i = 0; i < this->nmixers; i++) { - mixer_ctrl_t mc; - - if (this->mixers[i].target != MI_TARGET_PINDIR) - continue; - mc.dev = i; - mc.type = AUDIO_MIXER_ENUM; - switch (this->w[this->mixers[i].nid].d.pin.color) { - case CORB_CD_GREEN: - case CORB_CD_ORANGE: - case CORB_CD_GRAY: - case CORB_CD_BLACK: - mc.un.ord = 1; - break; - default: - mc.un.ord = 0; - } - azalia_mixer_set(this, &mc); - } - - /* set unextreme volume */ - DPRINTF(("%s: set volume\n", __func__)); - for (i = 0; i < this->nmixers; i++) { - mixer_ctrl_t mc; - - if (!IS_MI_TARGET_INAMP(this->mixers[i].target) && - this->mixers[i].target != MI_TARGET_OUTAMP && - this->mixers[i].target != MI_TARGET_VOLUME) - continue; - if (this->mixers[i].devinfo.type != AUDIO_MIXER_VALUE) - continue; - mc.dev = i; - mc.type = AUDIO_MIXER_VALUE; - mc.un.value.num_channels = 1; - mc.un.value.level[0] = AUDIO_MAX_GAIN / 2; - if (this->mixers[i].target != MI_TARGET_VOLUME && - WIDGET_CHANNELS(&this->w[this->mixers[i].nid]) == 2) { - mc.un.value.num_channels = 2; - mc.un.value.level[1] = AUDIO_MAX_GAIN / 2; - } - azalia_mixer_set(this, &mc); - } - - return 0; -} - -static int -azalia_mixer_delete(codec_t *this) -{ - if (this->mixers == NULL) - return 0; - free(this->mixers, M_DEVBUF); - this->mixers = NULL; - return 0; -} - -static int -azalia_mixer_get(const codec_t *this, mixer_ctrl_t *mc) -{ - const mixer_item_t *m; - uint32_t result; - int err; - - if (mc->dev >= this->nmixers) - return ENXIO; - m = &this->mixers[mc->dev]; - mc->type = m->devinfo.type; - if (mc->type == AUDIO_MIXER_CLASS) - return 0; /* nothing to do */ - - /* inamp mute */ - if (IS_MI_TARGET_INAMP(m->target) && m->devinfo.type == AUDIO_MIXER_ENUM) { - err = this->comresp(this, m->nid, CORB_GET_AMPLIFIER_GAIN_MUTE, - CORB_GAGM_INPUT | CORB_GAGM_LEFT | - MI_TARGET_INAMP(m->target), &result); - if (err) - return err; - mc->un.ord = result & CORB_GAGM_MUTE ? 1 : 0; - } - - /* inamp gain */ - else if (IS_MI_TARGET_INAMP(m->target) && m->devinfo.type == AUDIO_MIXER_VALUE) { - err = this->comresp(this, m->nid, CORB_GET_AMPLIFIER_GAIN_MUTE, - CORB_GAGM_INPUT | CORB_GAGM_LEFT | - MI_TARGET_INAMP(m->target), &result); - if (err) - return err; - mc->un.value.level[0] = azalia_mixer_from_device_value(this, m, - CORB_GAGM_GAIN(result)); - mc->un.value.num_channels = WIDGET_CHANNELS(&this->w[m->nid]); - if (mc->un.value.num_channels == 2) { - err = this->comresp(this, m->nid, - CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_INPUT | - CORB_GAGM_RIGHT | MI_TARGET_INAMP(m->target), - &result); - if (err) - return err; - mc->un.value.level[1] = azalia_mixer_from_device_value - (this, m, CORB_GAGM_GAIN(result)); - } - } - - /* outamp mute */ - else if (m->target == MI_TARGET_OUTAMP && m->devinfo.type == AUDIO_MIXER_ENUM) { - err = this->comresp(this, m->nid, CORB_GET_AMPLIFIER_GAIN_MUTE, - CORB_GAGM_OUTPUT | CORB_GAGM_LEFT | 0, &result); - if (err) - return err; - mc->un.ord = result & CORB_GAGM_MUTE ? 1 : 0; - } - - /* outamp gain */ - else if (m->target == MI_TARGET_OUTAMP && m->devinfo.type == AUDIO_MIXER_VALUE) { - err = this->comresp(this, m->nid, CORB_GET_AMPLIFIER_GAIN_MUTE, - CORB_GAGM_OUTPUT | CORB_GAGM_LEFT | 0, &result); - if (err) - return err; - mc->un.value.level[0] = azalia_mixer_from_device_value(this, m, - CORB_GAGM_GAIN(result)); - mc->un.value.num_channels = WIDGET_CHANNELS(&this->w[m->nid]); - if (mc->un.value.num_channels == 2) { - err = this->comresp(this, m->nid, - CORB_GET_AMPLIFIER_GAIN_MUTE, - CORB_GAGM_OUTPUT | CORB_GAGM_RIGHT | 0, &result); - if (err) - return err; - mc->un.value.level[1] = azalia_mixer_from_device_value - (this, m, CORB_GAGM_GAIN(result)); - } - } - - /* selection */ - else if (m->target == MI_TARGET_CONNLIST) { - int i; - err = this->comresp(this, m->nid, - CORB_GET_CONNECTION_SELECT_CONTROL, 0, &result); - if (err) - return err; - result = CORB_CSC_INDEX(result); - mc->un.ord = -1; - for (i = 0; i <= result; i++) { - if (!VALID_WIDGET_NID(this->w[m->nid].connections[i], this)) - continue; - mc->un.ord++; - } - } - - /* pin I/O */ - else if (m->target == MI_TARGET_PINDIR) { - err = this->comresp(this, m->nid, - CORB_GET_PIN_WIDGET_CONTROL, 0, &result); - if (err) - return err; - mc->un.ord = result & CORB_PWC_OUTPUT ? 1 : 0; - } - - /* pin headphone-boost */ - else if (m->target == MI_TARGET_PINBOOST) { - err = this->comresp(this, m->nid, - CORB_GET_PIN_WIDGET_CONTROL, 0, &result); - if (err) - return err; - mc->un.ord = result & CORB_PWC_HEADPHONE ? 1 : 0; - } - - /* DAC group selection */ - else if (m->target == MI_TARGET_DAC) { - mc->un.ord = this->cur_dac; - } - - /* ADC selection */ - else if (m->target == MI_TARGET_ADC) { - mc->un.ord = this->cur_adc; - } - - /* Volume knob */ - else if (m->target == MI_TARGET_VOLUME) { - err = this->comresp(this, m->nid, CORB_GET_VOLUME_KNOB, - 0, &result); - if (err) - return err; - mc->un.value.level[0] = azalia_mixer_from_device_value(this, m, - CORB_VKNOB_VOLUME(result)); - mc->un.value.num_channels = 1; - } - - else { - aprint_error("%s: internal error in %s: %x\n", XNAME(this->az), - __func__, m->target); - return -1; - } - return 0; -} - -static int -azalia_mixer_set(codec_t *this, const mixer_ctrl_t *mc) -{ - const mixer_item_t *m; - uint32_t result, value; - int err; - - if (mc->dev >= this->nmixers) - return ENXIO; - m = &this->mixers[mc->dev]; - if (mc->type != m->devinfo.type) - return EINVAL; - if (mc->type == AUDIO_MIXER_CLASS) - return 0; /* nothing to do */ - - /* inamp mute */ - if (IS_MI_TARGET_INAMP(m->target) && m->devinfo.type == AUDIO_MIXER_ENUM) { - /* We have to set stereo mute separately to keep each gain value. */ - err = this->comresp(this, m->nid, CORB_GET_AMPLIFIER_GAIN_MUTE, - CORB_GAGM_INPUT | CORB_GAGM_LEFT | - MI_TARGET_INAMP(m->target), &result); - if (err) - return err; - value = CORB_AGM_INPUT | CORB_AGM_LEFT | - (m->target << CORB_AGM_INDEX_SHIFT) | - CORB_GAGM_GAIN(result); - if (mc->un.ord) - value |= CORB_AGM_MUTE; - err = this->comresp(this, m->nid, CORB_SET_AMPLIFIER_GAIN_MUTE, - value, &result); - if (err) - return err; - if (WIDGET_CHANNELS(&this->w[m->nid]) == 2) { - err = this->comresp(this, m->nid, - CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_INPUT | - CORB_GAGM_RIGHT | MI_TARGET_INAMP(m->target), - &result); - if (err) - return err; - value = CORB_AGM_INPUT | CORB_AGM_RIGHT | - (m->target << CORB_AGM_INDEX_SHIFT) | - CORB_GAGM_GAIN(result); - if (mc->un.ord) - value |= CORB_AGM_MUTE; - err = this->comresp(this, m->nid, - CORB_SET_AMPLIFIER_GAIN_MUTE, value, &result); - if (err) - return err; - } - } - - /* inamp gain */ - else if (IS_MI_TARGET_INAMP(m->target) && m->devinfo.type == AUDIO_MIXER_VALUE) { - if (mc->un.value.num_channels < 1) - return EINVAL; - if (!azalia_mixer_validate_value(this, m, mc->un.value.level[0])) - return EINVAL; - err = this->comresp(this, m->nid, CORB_GET_AMPLIFIER_GAIN_MUTE, - CORB_GAGM_INPUT | CORB_GAGM_LEFT | - MI_TARGET_INAMP(m->target), &result); - if (err) - return err; - value = azalia_mixer_to_device_value(this, m, - mc->un.value.level[0]); - value = CORB_AGM_INPUT | CORB_AGM_LEFT | - (m->target << CORB_AGM_INDEX_SHIFT) | - (result & CORB_GAGM_MUTE ? CORB_AGM_MUTE : 0) | - (value & CORB_AGM_GAIN_MASK); - err = this->comresp(this, m->nid, CORB_SET_AMPLIFIER_GAIN_MUTE, - value, &result); - if (err) - return err; - if (mc->un.value.num_channels >= 2 && - WIDGET_CHANNELS(&this->w[m->nid]) == 2) { - if (!azalia_mixer_validate_value(this, m, - mc->un.value.level[1])) - return EINVAL; - err = this->comresp(this, m->nid, - CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_INPUT | - CORB_GAGM_RIGHT | MI_TARGET_INAMP(m->target), - &result); - if (err) - return err; - value = azalia_mixer_to_device_value(this, m, - mc->un.value.level[1]); - value = CORB_AGM_INPUT | CORB_AGM_RIGHT | - (m->target << CORB_AGM_INDEX_SHIFT) | - (result & CORB_GAGM_MUTE ? CORB_AGM_MUTE : 0) | - (value & CORB_AGM_GAIN_MASK); - err = this->comresp(this, m->nid, - CORB_SET_AMPLIFIER_GAIN_MUTE, value, &result); - if (err) - return err; - } - } - - /* outamp mute */ - else if (m->target == MI_TARGET_OUTAMP && m->devinfo.type == AUDIO_MIXER_ENUM) { - err = this->comresp(this, m->nid, CORB_GET_AMPLIFIER_GAIN_MUTE, - CORB_GAGM_OUTPUT | CORB_GAGM_LEFT, &result); - if (err) - return err; - value = CORB_AGM_OUTPUT | CORB_AGM_LEFT | CORB_GAGM_GAIN(result); - if (mc->un.ord) - value |= CORB_AGM_MUTE; - err = this->comresp(this, m->nid, CORB_SET_AMPLIFIER_GAIN_MUTE, - value, &result); - if (err) - return err; - if (WIDGET_CHANNELS(&this->w[m->nid]) == 2) { - err = this->comresp(this, m->nid, - CORB_GET_AMPLIFIER_GAIN_MUTE, - CORB_GAGM_OUTPUT | CORB_GAGM_RIGHT, &result); - if (err) - return err; - value = CORB_AGM_OUTPUT | CORB_AGM_RIGHT | - CORB_GAGM_GAIN(result); - if (mc->un.ord) - value |= CORB_AGM_MUTE; - err = this->comresp(this, m->nid, - CORB_SET_AMPLIFIER_GAIN_MUTE, value, &result); - if (err) - return err; - } - } - - /* outamp gain */ - else if (m->target == MI_TARGET_OUTAMP && m->devinfo.type == AUDIO_MIXER_VALUE) { - if (mc->un.value.num_channels < 1) - return EINVAL; - if (!azalia_mixer_validate_value(this, m, mc->un.value.level[0])) - return EINVAL; - err = this->comresp(this, m->nid, CORB_GET_AMPLIFIER_GAIN_MUTE, - CORB_GAGM_OUTPUT | CORB_GAGM_LEFT, &result); - if (err) - return err; - value = azalia_mixer_to_device_value(this, m, - mc->un.value.level[0]); - value = CORB_AGM_OUTPUT | CORB_AGM_LEFT | - (result & CORB_GAGM_MUTE ? CORB_AGM_MUTE : 0) | - (value & CORB_AGM_GAIN_MASK); - err = this->comresp(this, m->nid, CORB_SET_AMPLIFIER_GAIN_MUTE, - value, &result); - if (err) - return err; - if (mc->un.value.num_channels >= 2 && - WIDGET_CHANNELS(&this->w[m->nid]) == 2) { - if (!azalia_mixer_validate_value(this, m, - mc->un.value.level[1])) - return EINVAL; - err = this->comresp(this, m->nid, - CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_OUTPUT | - CORB_GAGM_RIGHT, &result); - if (err) - return err; - value = azalia_mixer_to_device_value(this, m, - mc->un.value.level[1]); - value = CORB_AGM_OUTPUT | CORB_AGM_RIGHT | - (result & CORB_GAGM_MUTE ? CORB_AGM_MUTE : 0) | - (value & CORB_AGM_GAIN_MASK); - err = this->comresp(this, m->nid, - CORB_SET_AMPLIFIER_GAIN_MUTE, value, &result); - if (err) - return err; - } - } - - /* selection */ - else if (m->target == MI_TARGET_CONNLIST) { - int i; - for (i = 0, value = 0; i < this->w[m->nid].nconnections; i++) { - if (!VALID_WIDGET_NID(this->w[m->nid].connections[i], this)) - continue; - if (value == mc->un.ord) - break; - value++; - } - if (i >= this->w[m->nid].nconnections) - return EINVAL; - err = this->comresp(this, m->nid, - CORB_SET_CONNECTION_SELECT_CONTROL, i, &result); - if (err) - return err; - } - - /* pin I/O */ - else if (m->target == MI_TARGET_PINDIR) { - if (mc->un.ord >= 2) - return EINVAL; - err = this->comresp(this, m->nid, - CORB_GET_PIN_WIDGET_CONTROL, 0, &result); - if (err) - return err; - if (mc->un.ord == 0) { - result &= ~CORB_PWC_OUTPUT; - result |= CORB_PWC_INPUT; - } else { - result &= ~CORB_PWC_INPUT; - result |= CORB_PWC_OUTPUT; - } - err = this->comresp(this, m->nid, - CORB_SET_PIN_WIDGET_CONTROL, result, &result); - if (err) - return err; - } - - /* pin headphone-boost */ - else if (m->target == MI_TARGET_PINBOOST) { - if (mc->un.ord >= 2) - return EINVAL; - err = this->comresp(this, m->nid, - CORB_GET_PIN_WIDGET_CONTROL, 0, &result); - if (err) - return err; - if (mc->un.ord == 0) { - result &= ~CORB_PWC_HEADPHONE; - } else { - result |= CORB_PWC_HEADPHONE; - } - err = this->comresp(this, m->nid, - CORB_SET_PIN_WIDGET_CONTROL, result, &result); - if (err) - return err; - } - - /* DAC group selection */ - else if (m->target == MI_TARGET_DAC) { - if (this->az->running) - return EBUSY; - if (mc->un.ord >= this->ndacgroups) - return EINVAL; - this->cur_dac = mc->un.ord; - return azalia_codec_construct_format(this); - } - - /* ADC selection */ - else if (m->target == MI_TARGET_ADC) { - if (this->az->running) - return EBUSY; - if (mc->un.ord >= this->nadcs) - return EINVAL; - this->cur_adc = mc->un.ord; - /* use this->adcs[this->cur_adc] */ - return azalia_codec_construct_format(this); - } - - /* Volume knob */ - else if (m->target == MI_TARGET_VOLUME) { - if (mc->un.value.num_channels != 1) - return EINVAL; - if (!azalia_mixer_validate_value(this, m, mc->un.value.level[0])) - return EINVAL; - value = azalia_mixer_to_device_value(this, m, - mc->un.value.level[0]) | CORB_VKNOB_DIRECT; - err = this->comresp(this, m->nid, CORB_SET_VOLUME_KNOB, - value, &result); - if (err) - return err; - } - - else { - aprint_error("%s: internal error in %s: %x\n", XNAME(this->az), - __func__, m->target); - return -1; - } - return 0; -} - -static int -azalia_mixer_ensure_capacity(codec_t *this, size_t newsize) -{ - size_t newmax; - void *newbuf; - - if (this->maxmixers >= newsize) - return 0; - newmax = this->maxmixers + 10; - if (newmax < newsize) - newmax = newsize; - newbuf = realloc(this->mixers, sizeof(mixer_item_t) * newmax, M_DEVBUF, - M_ZERO | M_NOWAIT); - if (newbuf == NULL) { - aprint_error("%s: out of memory in %s\n", XNAME(this->az), - __func__); - return ENOMEM; - } - this->mixers = newbuf; - this->maxmixers = newmax; - return 0; -} - -static u_char -azalia_mixer_from_device_value(const codec_t *this, const mixer_item_t *m, - uint32_t dv) -{ -#ifdef MAX_VOLUME_255 - uint32_t dmax; - - if (IS_MI_TARGET_INAMP(m->target)) - dmax = COP_AMPCAP_NUMSTEPS(this->w[m->nid].inamp_cap); - else if (m->target == MI_TARGET_OUTAMP) - dmax = COP_AMPCAP_NUMSTEPS(this->w[m->nid].outamp_cap); - else if (m->target == MI_TARGET_VOLUME) - dmax = COP_VKCAP_NUMSTEPS(this->w[m->nid].d.volume.cap); - else { - printf("unknown target: %d\n", m->target); - dmax = 255; - } - return dv * AUDIO_MAX_GAIN / dmax; -#else - return dv; -#endif -} - -static uint32_t -azalia_mixer_to_device_value(const codec_t *this, const mixer_item_t *m, - u_char uv) -{ -#ifdef MAX_VOLUME_255 - uint32_t dmax; - - if (IS_MI_TARGET_INAMP(m->target)) - dmax = COP_AMPCAP_NUMSTEPS(this->w[m->nid].inamp_cap); - else if (m->target == MI_TARGET_OUTAMP) - dmax = COP_AMPCAP_NUMSTEPS(this->w[m->nid].outamp_cap); - else if (m->target == MI_TARGET_VOLUME) - dmax = COP_VKCAP_NUMSTEPS(this->w[m->nid].d.volume.cap); - else { - printf("unknown target: %d\n", m->target); - dmax = 255; - } - return uv * dmax / AUDIO_MAX_GAIN; -#else - return uv; -#endif -} - -static boolean_t -azalia_mixer_validate_value(const codec_t *this, const mixer_item_t *m, - u_char uv) -{ -#ifdef MAX_VOLUME_255 - return TRUE; -#else - uint32_t dmax; - - if (IS_MI_TARGET_INAMP(m->target)) - dmax = COP_AMPCAP_NUMSTEPS(this->w[m->nid].inamp_cap); - else if (m->target == MI_TARGET_OUTAMP) - dmax = COP_AMPCAP_NUMSTEPS(this->w[m->nid].outamp_cap); - else if (m->target == MI_TARGET_VOLUME) - dmax = COP_VKCAP_NUMSTEPS(this->w[m->nid].d.volume.cap); - return uv <= dmax; -#endif -} - /* ================================================================ * HDA widget functions * ================================================================ */ @@ -2445,7 +1504,6 @@ azalia_widget_init_connection(widget_t *this, const codec_t *codec) length = COP_CLL_LENGTH(result); if (length == 0) return 0; - DPRINTF(("%s: CLE=0x%x\n", __func__, result)); this->nconnections = length; this->connections = malloc(sizeof(nid_t) * (length + 3), M_DEVBUF, M_NOWAIT); @@ -2459,7 +1517,6 @@ azalia_widget_init_connection(widget_t *this, const codec_t *codec) CORB_GET_CONNECTION_LIST_ENTRY, i, &result); if (err) return err; - DPRINTF(("%s: long[%d]=0x%x\n", __func__, i, result)); this->connections[i++] = CORB_CLE_LONG_0(result); this->connections[i++] = CORB_CLE_LONG_1(result); } @@ -2469,7 +1526,6 @@ azalia_widget_init_connection(widget_t *this, const codec_t *codec) CORB_GET_CONNECTION_LIST_ENTRY, i, &result); if (err) return err; - DPRINTF(("%s: short[%d]=0x%x\n", __func__, i, result)); this->connections[i++] = CORB_CLE_SHORT_0(result); this->connections[i++] = CORB_CLE_SHORT_1(result); this->connections[i++] = CORB_CLE_SHORT_2(result); @@ -2650,10 +1706,12 @@ static int azalia_open(void *v, int flags) { azalia_t *az; + codec_t *codec; DPRINTF(("%s: flags=0x%x\n", __func__, flags)); az = v; - az->running++; + codec = &az->codecs[az->codecno]; + codec->running++; return 0; } @@ -2661,10 +1719,12 @@ static void azalia_close(void *v) { azalia_t *az; + codec_t *codec; DPRINTF(("%s\n", __func__)); az = v; - az->running--; + codec = &az->codecs[az->codecno]; + codec->running--; } static int @@ -2771,7 +1831,7 @@ azalia_set_port(void *v, mixer_ctrl_t *mc) az = v; co = &az->codecs[az->codecno]; - return azalia_mixer_set(co, mc); + return co->set_port(co, mc); } static int @@ -2782,14 +1842,14 @@ azalia_get_port(void *v, mixer_ctrl_t *mc) az = v; co = &az->codecs[az->codecno]; - return azalia_mixer_get(co, mc); + return co->get_port(co, mc); } static int azalia_query_devinfo(void *v, mixer_devinfo_t *mdev) { azalia_t *az; - codec_t *co; + const codec_t *co; az = v; co = &az->codecs[az->codecno]; diff --git a/sys/dev/pci/azalia.h b/sys/dev/pci/azalia.h index a645572f1d0e..850a9f929bfe 100644 --- a/sys/dev/pci/azalia.h +++ b/sys/dev/pci/azalia.h @@ -1,4 +1,4 @@ -/* $NetBSD: azalia.h,v 1.6 2006/01/16 14:15:26 kent Exp $ */ +/* $NetBSD: azalia.h,v 1.7 2006/06/07 15:23:59 kent Exp $ */ /*- * Copyright (c) 2005 The NetBSD Foundation, Inc. @@ -520,8 +520,14 @@ typedef struct codec_t { int (*comresp)(const struct codec_t *, nid_t, uint32_t, uint32_t, uint32_t *); int (*init_dacgroup)(struct codec_t *); int (*init_widget)(const struct codec_t *, widget_t *, nid_t); + int (*mixer_init)(struct codec_t *); + int (*mixer_delete)(struct codec_t *); + int (*set_port)(struct codec_t *, mixer_ctrl_t *); + int (*get_port)(struct codec_t *, mixer_ctrl_t *); struct azalia_t *az; + uint32_t vid; /* codec vendor/device ID */ + uint32_t subid; /* PCI subvendor/device ID */ const char *name; int address; int nfunctions; @@ -538,6 +544,7 @@ typedef struct codec_t { int nadcs; nid_t adcs[32]; int cur_adc; /* currently selected ADC index */ + int running; int nmixers, maxmixers; mixer_item_t *mixers; @@ -548,4 +555,5 @@ typedef struct codec_t { } codec_t; -int azalia_codec_init_vtbl(codec_t *, uint32_t); +int azalia_codec_init_vtbl(codec_t *); +int azalia_codec_construct_format(codec_t *); diff --git a/sys/dev/pci/azalia_codec.c b/sys/dev/pci/azalia_codec.c index 72eee7a691e2..8cda01504ebd 100644 --- a/sys/dev/pci/azalia_codec.c +++ b/sys/dev/pci/azalia_codec.c @@ -1,4 +1,4 @@ -/* $NetBSD: azalia_codec.c,v 1.8 2006/05/10 11:17:27 kent Exp $ */ +/* $NetBSD: azalia_codec.c,v 1.9 2006/06/07 15:23:59 kent Exp $ */ /*- * Copyright (c) 2005 The NetBSD Foundation, Inc. @@ -37,19 +37,47 @@ */ #include -__KERNEL_RCSID(0, "$NetBSD: azalia_codec.c,v 1.8 2006/05/10 11:17:27 kent Exp $"); +__KERNEL_RCSID(0, "$NetBSD: azalia_codec.c,v 1.9 2006/06/07 15:23:59 kent Exp $"); +#include +#include +#include #include #include #include +#define XNAME(co) (((device_t)co->az)->dv_xname) +#define AZ_CLASS_INPUT 0 +#define AZ_CLASS_OUTPUT 1 +#define AZ_CLASS_RECORD 2 +#define ENUM_OFFON .un.e={2, {{{AudioNoff}, 0}, {{AudioNon}, 1}}} +#define ENUM_IO .un.e={2, {{{"input"}, 0}, {{"output"}, 1}}} -static int azalia_codec_init_dacgroup(codec_t *); -static int azalia_codec_add_dacgroup(codec_t *, int, uint32_t); -static int azalia_codec_find_pin(const codec_t *, int, int, uint32_t); -static int azalia_codec_find_dac(const codec_t *, int, int); + +static int generic_codec_init_dacgroup(codec_t *); +static int generic_codec_add_dacgroup(codec_t *, int, uint32_t); +static int generic_codec_find_pin(const codec_t *, int, int, uint32_t); +static int generic_codec_find_dac(const codec_t *, int, int); + +static int generic_mixer_init(codec_t *); +static int generic_mixer_fix_indexes(codec_t *); +static int generic_mixer_default(codec_t *); +static int generic_mixer_delete(codec_t *); +static int generic_mixer_ensure_capacity(codec_t *, size_t); +static int generic_mixer_get(const codec_t *, nid_t, int, mixer_ctrl_t *); +static int generic_mixer_set(codec_t *, nid_t, int, const mixer_ctrl_t *); +static u_char generic_mixer_from_device_value + (const codec_t *, nid_t, int, uint32_t ); +static uint32_t generic_mixer_to_device_value + (const codec_t *, nid_t, int, u_char); +static boolean_t generic_mixer_validate_value + (const codec_t *, nid_t, int, u_char); +static int generic_set_port(codec_t *, mixer_ctrl_t *); +static int generic_get_port(codec_t *, mixer_ctrl_t *); + +static int alc260_mixer_init(codec_t *); static int alc260_init_dacgroup(codec_t *); -static int alc260_init_widget(const codec_t *, widget_t *, nid_t); +static int alc260_set_port(codec_t *, mixer_ctrl_t *); static int alc880_init_dacgroup(codec_t *); static int alc882_init_dacgroup(codec_t *); static int alc882_init_widget(const codec_t *, widget_t *, nid_t); @@ -58,13 +86,24 @@ static int stac9221_init_dacgroup(codec_t *); int -azalia_codec_init_vtbl(codec_t *this, uint32_t vid) +azalia_codec_init_vtbl(codec_t *this) { - switch (vid) { + /** + * We can refer this->vid and this->subid. + */ + DPRINTF(("%s: vid=%08x subid=%08x\n", __func__, this->vid, this->subid)); + this->name = NULL; + this->init_dacgroup = generic_codec_init_dacgroup; + this->mixer_init = generic_mixer_init; + this->mixer_delete = generic_mixer_delete; + this->set_port = generic_set_port; + this->get_port = generic_get_port; + switch (this->vid) { case 0x10ec0260: this->name = "Realtek ALC260"; + this->mixer_init = alc260_mixer_init; this->init_dacgroup = alc260_init_dacgroup; - this->init_widget = alc260_init_widget; + this->set_port = alc260_set_port; break; case 0x10ec0880: this->name = "Realtek ALC880"; @@ -88,9 +127,6 @@ azalia_codec_init_vtbl(codec_t *this, uint32_t vid) this->name = "Sigmatel STAC9221D"; this->init_dacgroup = stac9221_init_dacgroup; break; - default: - this->name = NULL; - this->init_dacgroup = azalia_codec_init_dacgroup; } return 0; } @@ -100,7 +136,7 @@ azalia_codec_init_vtbl(codec_t *this, uint32_t vid) * ---------------------------------------------------------------- */ static int -azalia_codec_init_dacgroup(codec_t *this) +generic_codec_init_dacgroup(codec_t *this) { int i, j, assoc, group; @@ -113,8 +149,8 @@ azalia_codec_init_dacgroup(codec_t *this) */ this->ndacgroups = 0; for (assoc = 0; assoc < CORB_CD_ASSOCIATION_MAX; assoc++) { - azalia_codec_add_dacgroup(this, assoc, 0); - azalia_codec_add_dacgroup(this, assoc, COP_AWCAP_DIGITAL); + generic_codec_add_dacgroup(this, assoc, 0); + generic_codec_add_dacgroup(this, assoc, COP_AWCAP_DIGITAL); } /* find DACs which do not connect with any pins by default */ @@ -158,16 +194,16 @@ azalia_codec_init_dacgroup(codec_t *this) } static int -azalia_codec_add_dacgroup(codec_t *this, int assoc, uint32_t digital) +generic_codec_add_dacgroup(codec_t *this, int assoc, uint32_t digital) { int i, j, n, dac, seq; n = 0; for (seq = 0 ; seq < CORB_CD_SEQUENCE_MAX; seq++) { - i = azalia_codec_find_pin(this, assoc, seq, digital); + i = generic_codec_find_pin(this, assoc, seq, digital); if (i < 0) continue; - dac = azalia_codec_find_dac(this, i, 0); + dac = generic_codec_find_dac(this, i, 0); if (dac < 0) continue; /* duplication check */ @@ -203,7 +239,7 @@ azalia_codec_add_dacgroup(codec_t *this, int assoc, uint32_t digital) } static int -azalia_codec_find_pin(const codec_t *this, int assoc, int seq, uint32_t digital) +generic_codec_find_pin(const codec_t *this, int assoc, int seq, uint32_t digital) { int i; @@ -224,7 +260,7 @@ azalia_codec_find_pin(const codec_t *this, int assoc, int seq, uint32_t digital) } static int -azalia_codec_find_dac(const codec_t *this, int index, int depth) +generic_codec_find_dac(const codec_t *this, int index, int depth) { const widget_t *w; int i, j, ret; @@ -240,7 +276,7 @@ azalia_codec_find_dac(const codec_t *this, int index, int depth) } if (w->selected >= 0) { j = w->connections[w->selected]; - ret = azalia_codec_find_dac(this, j, depth); + ret = generic_codec_find_dac(this, j, depth); if (ret >= 0) { DPRINTF(("%s: DAC path: nid=0x%x index=%d\n", __func__, w->nid, index)); @@ -249,7 +285,7 @@ azalia_codec_find_dac(const codec_t *this, int index, int depth) } for (i = 0; i < w->nconnections; i++) { j = w->connections[i]; - ret = azalia_codec_find_dac(this, j, depth); + ret = generic_codec_find_dac(this, j, depth); if (ret >= 0) { DPRINTF(("%s: DAC path: nid=0x%x index=%d\n", __func__, w->nid, index)); @@ -260,9 +296,1202 @@ azalia_codec_find_dac(const codec_t *this, int index, int depth) } /* ---------------------------------------------------------------- - * Realtek ALC260 + * Generic mixer functions * ---------------------------------------------------------------- */ +static int +generic_mixer_init(codec_t *this) +{ + /* + * pin "%2.2x" + * audio output "dac%2.2x" + * audio input "adc%2.2x" + * mixer "mixer%2.2x" + * selector "sel%2.2x" + */ + mixer_item_t *m; + int nadcs; + int err, i, j, k; + + nadcs = 0; + this->maxmixers = 10; + this->nmixers = 0; + this->mixers = malloc(sizeof(mixer_item_t) * this->maxmixers, + M_DEVBUF, M_ZERO | M_NOWAIT); + if (this->mixers == NULL) { + aprint_error("%s: out of memory in %s\n", XNAME(this), __func__); + return ENOMEM; + } + + /* register classes */ + DPRINTF(("%s: register classes\n", __func__)); + m = &this->mixers[AZ_CLASS_INPUT]; + m->devinfo.index = AZ_CLASS_INPUT; + strlcpy(m->devinfo.label.name, AudioCinputs, + sizeof(m->devinfo.label.name)); + m->devinfo.type = AUDIO_MIXER_CLASS; + m->devinfo.mixer_class = AZ_CLASS_INPUT; + m->devinfo.next = AUDIO_MIXER_LAST; + m->devinfo.prev = AUDIO_MIXER_LAST; + m->nid = 0; + + m = &this->mixers[AZ_CLASS_OUTPUT]; + m->devinfo.index = AZ_CLASS_OUTPUT; + strlcpy(m->devinfo.label.name, AudioCoutputs, + sizeof(m->devinfo.label.name)); + m->devinfo.type = AUDIO_MIXER_CLASS; + m->devinfo.mixer_class = AZ_CLASS_OUTPUT; + m->devinfo.next = AUDIO_MIXER_LAST; + m->devinfo.prev = AUDIO_MIXER_LAST; + m->nid = 0; + + m = &this->mixers[AZ_CLASS_RECORD]; + m->devinfo.index = AZ_CLASS_RECORD; + strlcpy(m->devinfo.label.name, AudioCrecord, + sizeof(m->devinfo.label.name)); + m->devinfo.type = AUDIO_MIXER_CLASS; + m->devinfo.mixer_class = AZ_CLASS_RECORD; + m->devinfo.next = AUDIO_MIXER_LAST; + m->devinfo.prev = AUDIO_MIXER_LAST; + m->nid = 0; + + this->nmixers = AZ_CLASS_RECORD + 1; + +#define MIXER_REG_PROLOG \ + mixer_devinfo_t *d; \ + err = generic_mixer_ensure_capacity(this, this->nmixers + 1); \ + if (err) \ + return err; \ + m = &this->mixers[this->nmixers]; \ + d = &m->devinfo; \ + m->nid = i + + FOR_EACH_WIDGET(this, i) { + const widget_t *w; + + w = &this->w[i]; + + if (w->type == COP_AWTYPE_AUDIO_INPUT) + nadcs++; + + /* selector */ + if (w->type != COP_AWTYPE_AUDIO_MIXER && w->nconnections >= 2) { + MIXER_REG_PROLOG; + DPRINTF(("%s: selector %s\n", __func__, w->name)); + snprintf(d->label.name, sizeof(d->label.name), + "%s.source", w->name); + d->type = AUDIO_MIXER_ENUM; + if (w->type == COP_AWTYPE_AUDIO_MIXER) + d->mixer_class = AZ_CLASS_RECORD; + else if (w->type == COP_AWTYPE_AUDIO_SELECTOR) + d->mixer_class = AZ_CLASS_INPUT; + else + d->mixer_class = AZ_CLASS_OUTPUT; + m->target = MI_TARGET_CONNLIST; + for (j = 0, k = 0; j < w->nconnections && k < 32; j++) { + if (!VALID_WIDGET_NID(w->connections[j], this)) + continue; + DPRINTF(("%s: selector %d=%s\n", __func__, j, + this->w[w->connections[j]].name)); + d->un.e.member[k].ord = j; + strlcpy(d->un.e.member[k].label.name, + this->w[w->connections[j]].name, + MAX_AUDIO_DEV_LEN); + k++; + } + d->un.e.num_mem = k; + this->nmixers++; + } + + /* output mute */ + if (w->widgetcap & COP_AWCAP_OUTAMP && + w->outamp_cap & COP_AMPCAP_MUTE) { + MIXER_REG_PROLOG; + DPRINTF(("%s: output mute %s\n", __func__, w->name)); + snprintf(d->label.name, sizeof(d->label.name), + "%s.mute", w->name); + d->type = AUDIO_MIXER_ENUM; + if (w->type == COP_AWTYPE_AUDIO_MIXER) + d->mixer_class = AZ_CLASS_OUTPUT; + else if (w->type == COP_AWTYPE_AUDIO_SELECTOR) + d->mixer_class = AZ_CLASS_OUTPUT; + else if (w->type == COP_AWTYPE_PIN_COMPLEX) + d->mixer_class = AZ_CLASS_OUTPUT; + else + d->mixer_class = AZ_CLASS_INPUT; + m->target = MI_TARGET_OUTAMP; + d->un.e.num_mem = 2; + d->un.e.member[0].ord = 0; + strlcpy(d->un.e.member[0].label.name, AudioNoff, + MAX_AUDIO_DEV_LEN); + d->un.e.member[1].ord = 1; + strlcpy(d->un.e.member[1].label.name, AudioNon, + MAX_AUDIO_DEV_LEN); + this->nmixers++; + } + + /* output gain */ + if (w->widgetcap & COP_AWCAP_OUTAMP + && COP_AMPCAP_NUMSTEPS(w->outamp_cap)) { + MIXER_REG_PROLOG; + DPRINTF(("%s: output gain %s\n", __func__, w->name)); + snprintf(d->label.name, sizeof(d->label.name), + "%s", w->name); + d->type = AUDIO_MIXER_VALUE; + if (w->type == COP_AWTYPE_AUDIO_MIXER) + d->mixer_class = AZ_CLASS_OUTPUT; + else if (w->type == COP_AWTYPE_AUDIO_SELECTOR) + d->mixer_class = AZ_CLASS_OUTPUT; + else if (w->type == COP_AWTYPE_PIN_COMPLEX) + d->mixer_class = AZ_CLASS_OUTPUT; + else + d->mixer_class = AZ_CLASS_INPUT; + m->target = MI_TARGET_OUTAMP; + d->un.v.num_channels = WIDGET_CHANNELS(w); +#ifdef MAX_VOLUME_255 + d->un.v.units.name[0] = 0; + d->un.v.delta = AUDIO_MAX_GAIN / + COP_AMPCAP_NUMSTEPS(w->outamp_cap); +#else + snprintf(d->un.v.units.name, sizeof(d->un.v.units.name), + "0.25x%ddB", COP_AMPCAP_STEPSIZE(w->outamp_cap)+1); + d->un.v.delta = 1; +#endif + this->nmixers++; + } + + /* input mute */ + if (w->widgetcap & COP_AWCAP_INAMP && + w->inamp_cap & COP_AMPCAP_MUTE) { + DPRINTF(("%s: input mute %s\n", __func__, w->name)); + if (w->type != COP_AWTYPE_AUDIO_SELECTOR && + w->type != COP_AWTYPE_AUDIO_MIXER) { + MIXER_REG_PROLOG; + snprintf(d->label.name, sizeof(d->label.name), + "%s.mute", w->name); + d->type = AUDIO_MIXER_ENUM; + if (w->type == COP_AWTYPE_PIN_COMPLEX) + d->mixer_class = AZ_CLASS_OUTPUT; + else if (w->type == COP_AWTYPE_AUDIO_INPUT) + d->mixer_class = AZ_CLASS_RECORD; + else + d->mixer_class = AZ_CLASS_INPUT; + m->target = 0; + d->un.e.num_mem = 2; + d->un.e.member[0].ord = 0; + strlcpy(d->un.e.member[0].label.name, + AudioNoff, MAX_AUDIO_DEV_LEN); + d->un.e.member[1].ord = 1; + strlcpy(d->un.e.member[1].label.name, + AudioNon, MAX_AUDIO_DEV_LEN); + this->nmixers++; + } else { + for (j = 0; j < w->nconnections; j++) { + MIXER_REG_PROLOG; + if (!VALID_WIDGET_NID(w->connections[j], this)) + continue; + DPRINTF(("%s: input mute %s.%s\n", __func__, + w->name, this->w[w->connections[j]].name)); + snprintf(d->label.name, sizeof(d->label.name), + "%s.%s.mute", w->name, + this->w[w->connections[j]].name); + d->type = AUDIO_MIXER_ENUM; + if (w->type == COP_AWTYPE_PIN_COMPLEX) + d->mixer_class = AZ_CLASS_OUTPUT; + else if (w->type == COP_AWTYPE_AUDIO_INPUT) + d->mixer_class = AZ_CLASS_RECORD; + else + d->mixer_class = AZ_CLASS_INPUT; + m->target = j; + d->un.e.num_mem = 2; + d->un.e.member[0].ord = 0; + strlcpy(d->un.e.member[0].label.name, + AudioNoff, MAX_AUDIO_DEV_LEN); + d->un.e.member[1].ord = 1; + strlcpy(d->un.e.member[1].label.name, + AudioNon, MAX_AUDIO_DEV_LEN); + this->nmixers++; + } + } + } + + /* input gain */ + if (w->widgetcap & COP_AWCAP_INAMP + && COP_AMPCAP_NUMSTEPS(w->inamp_cap)) { + DPRINTF(("%s: input gain %s\n", __func__, w->name)); + if (w->type != COP_AWTYPE_AUDIO_SELECTOR && + w->type != COP_AWTYPE_AUDIO_MIXER) { + MIXER_REG_PROLOG; + snprintf(d->label.name, sizeof(d->label.name), + "%s", w->name); + d->type = AUDIO_MIXER_VALUE; + if (w->type == COP_AWTYPE_PIN_COMPLEX) + d->mixer_class = AZ_CLASS_OUTPUT; + else if (w->type == COP_AWTYPE_AUDIO_INPUT) + d->mixer_class = AZ_CLASS_RECORD; + else + d->mixer_class = AZ_CLASS_INPUT; + m->target = 0; + d->un.v.num_channels = WIDGET_CHANNELS(w); +#ifdef MAX_VOLUME_255 + d->un.v.units.name[0] = 0; + d->un.v.delta = AUDIO_MAX_GAIN / + COP_AMPCAP_NUMSTEPS(w->inamp_cap); +#else + snprintf(d->un.v.units.name, + sizeof(d->un.v.units.name), "0.25x%ddB", + COP_AMPCAP_STEPSIZE(w->inamp_cap)+1); + d->un.v.delta = 1; +#endif + this->nmixers++; + } else { + for (j = 0; j < w->nconnections; j++) { + MIXER_REG_PROLOG; + if (!VALID_WIDGET_NID(w->connections[j], this)) + continue; + DPRINTF(("%s: input gain %s.%s\n", __func__, + w->name, this->w[w->connections[j]].name)); + snprintf(d->label.name, sizeof(d->label.name), + "%s.%s", w->name, + this->w[w->connections[j]].name); + d->type = AUDIO_MIXER_VALUE; + if (w->type == COP_AWTYPE_PIN_COMPLEX) + d->mixer_class = AZ_CLASS_OUTPUT; + else if (w->type == COP_AWTYPE_AUDIO_INPUT) + d->mixer_class = AZ_CLASS_RECORD; + else + d->mixer_class = AZ_CLASS_INPUT; + m->target = j; + d->un.v.num_channels = WIDGET_CHANNELS(w); +#ifdef MAX_VOLUME_255 + d->un.v.units.name[0] = 0; + d->un.v.delta = AUDIO_MAX_GAIN / + COP_AMPCAP_NUMSTEPS(w->inamp_cap); +#else + snprintf(d->un.v.units.name, + sizeof(d->un.v.units.name), "0.25x%ddB", + COP_AMPCAP_STEPSIZE(w->inamp_cap)+1); + d->un.v.delta = 1; +#endif + this->nmixers++; + } + } + } + + /* pin direction */ + if (w->type == COP_AWTYPE_PIN_COMPLEX && + w->d.pin.cap & COP_PINCAP_OUTPUT && + w->d.pin.cap & COP_PINCAP_INPUT) { + MIXER_REG_PROLOG; + DPRINTF(("%s: pin dir %s\n", __func__, w->name)); + snprintf(d->label.name, sizeof(d->label.name), + "%s.dir", w->name); + d->type = AUDIO_MIXER_ENUM; + d->mixer_class = AZ_CLASS_OUTPUT; + m->target = MI_TARGET_PINDIR; + d->un.e.num_mem = 2; + d->un.e.member[0].ord = 0; + strlcpy(d->un.e.member[0].label.name, AudioNinput, + MAX_AUDIO_DEV_LEN); + d->un.e.member[1].ord = 1; + strlcpy(d->un.e.member[1].label.name, AudioNoutput, + MAX_AUDIO_DEV_LEN); + this->nmixers++; + } + + /* pin headphone-boost */ + if (w->type == COP_AWTYPE_PIN_COMPLEX && + w->d.pin.cap & COP_PINCAP_HEADPHONE) { + MIXER_REG_PROLOG; + DPRINTF(("%s: hpboost %s\n", __func__, w->name)); + snprintf(d->label.name, sizeof(d->label.name), + "%s.boost", w->name); + d->type = AUDIO_MIXER_ENUM; + d->mixer_class = AZ_CLASS_OUTPUT; + m->target = MI_TARGET_PINBOOST; + d->un.e.num_mem = 2; + d->un.e.member[0].ord = 0; + strlcpy(d->un.e.member[0].label.name, AudioNoff, + MAX_AUDIO_DEV_LEN); + d->un.e.member[1].ord = 1; + strlcpy(d->un.e.member[1].label.name, AudioNon, + MAX_AUDIO_DEV_LEN); + this->nmixers++; + } + + /* volume knob */ + if (w->type == COP_AWTYPE_VOLUME_KNOB && + w->d.volume.cap & COP_VKCAP_DELTA) { + MIXER_REG_PROLOG; + DPRINTF(("%s: volume knob %s\n", __func__, w->name)); + strlcpy(d->label.name, w->name, sizeof(d->label.name)); + d->type = AUDIO_MIXER_VALUE; + d->mixer_class = AZ_CLASS_OUTPUT; + m->target = MI_TARGET_VOLUME; + d->un.v.num_channels = 1; + d->un.v.units.name[0] = 0; +#ifdef MAX_VOLUME_255 + d->un.v.delta = AUDIO_MAX_GAIN / + COP_VKCAP_NUMSTEPS(w->d.volume.cap); +#else + d->un.v.delta = 1; +#endif + this->nmixers++; + } + } + + /* if the codec has multiple DAC groups, create "inputs.usingdac" */ + if (this->ndacgroups > 1) { + MIXER_REG_PROLOG; + DPRINTF(("%s: create inputs.usingdac\n", __func__)); + strlcpy(d->label.name, "usingdac", sizeof(d->label.name)); + d->type = AUDIO_MIXER_ENUM; + d->mixer_class = AZ_CLASS_INPUT; + m->target = MI_TARGET_DAC; + for (i = 0; i < this->ndacgroups && i < 32; i++) { + d->un.e.member[i].ord = i; + for (j = 0; j < this->dacgroups[i].nconv; j++) { + if (j * 2 >= MAX_AUDIO_DEV_LEN) + break; + snprintf(d->un.e.member[i].label.name + j*2, + MAX_AUDIO_DEV_LEN - j*2, "%2.2x", + this->dacgroups[i].conv[j]); + } + } + d->un.e.num_mem = i; + this->nmixers++; + } + + /* if the codec has multiple ADCs, create "record.usingadc" */ + if (this->nadcs > 1) { + MIXER_REG_PROLOG; + DPRINTF(("%s: create inputs.usingadc\n", __func__)); + strlcpy(d->label.name, "usingadc", sizeof(d->label.name)); + d->type = AUDIO_MIXER_ENUM; + d->mixer_class = AZ_CLASS_RECORD; + m->target = MI_TARGET_ADC; + for (i = 0; i < this->nadcs && i < 32; i++) { + d->un.e.member[i].ord = i; + strlcpy(d->un.e.member[i].label.name, + this->w[this->adcs[i]].name, MAX_AUDIO_DEV_LEN); + } + d->un.e.num_mem = i; + this->nmixers++; + } + + generic_mixer_fix_indexes(this); + generic_mixer_default(this); + return 0; +} + +static int +generic_mixer_ensure_capacity(codec_t *this, size_t newsize) +{ + size_t newmax; + void *newbuf; + + if (this->maxmixers >= newsize) + return 0; + newmax = this->maxmixers + 10; + if (newmax < newsize) + newmax = newsize; + newbuf = realloc(this->mixers, sizeof(mixer_item_t) * newmax, M_DEVBUF, + M_ZERO | M_NOWAIT); + if (newbuf == NULL) { + aprint_error("%s: out of memory in %s\n", XNAME(this), __func__); + return ENOMEM; + } + this->mixers = newbuf; + /* realloc(9) doesn't clear expanded area even if M_ZERO. */ + memset(&this->mixers[this->maxmixers], 0, + sizeof(mixer_item_t) * (newmax - this->maxmixers)); + this->maxmixers = newmax; + return 0; +} + +static int +generic_mixer_fix_indexes(codec_t *this) +{ + int i; + mixer_devinfo_t *d; + + for (i = 0; i < this->nmixers; i++) { + d = &this->mixers[i].devinfo; +#ifdef DIAGNOSTIC + if (d->index != 0 && d->index != i) + aprint_error("%s: index mismatch %d %d\n", __func__, + d->index, i); +#endif + d->index = i; + if (d->prev == 0) + d->prev = AUDIO_MIXER_LAST; + if (d->next == 0) + d->next = AUDIO_MIXER_LAST; + } + return 0; +} + +static int +generic_mixer_default(codec_t *this) +{ + int i; + mixer_item_t *m; + /* unmute all */ + DPRINTF(("%s: unmute\n", __func__)); + for (i = 0; i < this->nmixers; i++) { + mixer_ctrl_t mc; + + m = &this->mixers[i]; + if (!IS_MI_TARGET_INAMP(m->target) && + m->target != MI_TARGET_OUTAMP) + continue; + if (m->devinfo.type != AUDIO_MIXER_ENUM) + continue; + mc.dev = i; + mc.type = AUDIO_MIXER_ENUM; + mc.un.ord = 0; + generic_mixer_set(this, m->nid, m->target, &mc); + } + + /* + * for bidirectional pins, + * green=front, orange=surround, gray=c/lfe, black=side --> output + * blue=line-in, pink=mic-in --> input + */ + DPRINTF(("%s: process bidirectional pins\n", __func__)); + for (i = 0; i < this->nmixers; i++) { + mixer_ctrl_t mc; + + m = &this->mixers[i]; + if (m->target != MI_TARGET_PINDIR) + continue; + mc.dev = i; + mc.type = AUDIO_MIXER_ENUM; + switch (this->w[m->nid].d.pin.color) { + case CORB_CD_GREEN: + case CORB_CD_ORANGE: + case CORB_CD_GRAY: + case CORB_CD_BLACK: + mc.un.ord = 1; + break; + default: + mc.un.ord = 0; + } + generic_mixer_set(this, m->nid, m->target, &mc); + } + + /* set unextreme volume */ + DPRINTF(("%s: set volume\n", __func__)); + for (i = 0; i < this->nmixers; i++) { + mixer_ctrl_t mc; + + m = &this->mixers[i]; + if (!IS_MI_TARGET_INAMP(m->target) && + m->target != MI_TARGET_OUTAMP && + m->target != MI_TARGET_VOLUME) + continue; + if (m->devinfo.type != AUDIO_MIXER_VALUE) + continue; + mc.dev = i; + mc.type = AUDIO_MIXER_VALUE; + mc.un.value.num_channels = 1; + mc.un.value.level[0] = AUDIO_MAX_GAIN / 2; + if (m->target != MI_TARGET_VOLUME && + WIDGET_CHANNELS(&this->w[m->nid]) == 2) { + mc.un.value.num_channels = 2; + mc.un.value.level[1] = AUDIO_MAX_GAIN / 2; + } + generic_mixer_set(this, m->nid, m->target, &mc); + } + + return 0; +} + +static int +generic_mixer_delete(codec_t *this) +{ + if (this->mixers == NULL) + return 0; + free(this->mixers, M_DEVBUF); + this->mixers = NULL; + return 0; +} + +/** + * @param mc mc->type must be set by the caller before the call + */ +static int +generic_mixer_get(const codec_t *this, nid_t nid, int target, mixer_ctrl_t *mc) +{ + uint32_t result; + int err; + + /* inamp mute */ + if (IS_MI_TARGET_INAMP(target) && mc->type == AUDIO_MIXER_ENUM) { + err = this->comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE, + CORB_GAGM_INPUT | CORB_GAGM_LEFT | + MI_TARGET_INAMP(target), &result); + if (err) + return err; + mc->un.ord = result & CORB_GAGM_MUTE ? 1 : 0; + } + + /* inamp gain */ + else if (IS_MI_TARGET_INAMP(target) && mc->type == AUDIO_MIXER_VALUE) { + err = this->comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE, + CORB_GAGM_INPUT | CORB_GAGM_LEFT | + MI_TARGET_INAMP(target), &result); + if (err) + return err; + mc->un.value.level[0] = generic_mixer_from_device_value(this, + nid, target, CORB_GAGM_GAIN(result)); + mc->un.value.num_channels = WIDGET_CHANNELS(&this->w[nid]); + if (mc->un.value.num_channels == 2) { + err = this->comresp(this, nid, + CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_INPUT | + CORB_GAGM_RIGHT | MI_TARGET_INAMP(target), + &result); + if (err) + return err; + mc->un.value.level[1] = generic_mixer_from_device_value + (this, nid, target, CORB_GAGM_GAIN(result)); + } + } + + /* outamp mute */ + else if (target == MI_TARGET_OUTAMP && mc->type == AUDIO_MIXER_ENUM) { + err = this->comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE, + CORB_GAGM_OUTPUT | CORB_GAGM_LEFT | 0, &result); + if (err) + return err; + mc->un.ord = result & CORB_GAGM_MUTE ? 1 : 0; + } + + /* outamp gain */ + else if (target == MI_TARGET_OUTAMP && mc->type == AUDIO_MIXER_VALUE) { + err = this->comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE, + CORB_GAGM_OUTPUT | CORB_GAGM_LEFT | 0, &result); + if (err) + return err; + mc->un.value.level[0] = generic_mixer_from_device_value(this, + nid, target, CORB_GAGM_GAIN(result)); + mc->un.value.num_channels = WIDGET_CHANNELS(&this->w[nid]); + if (mc->un.value.num_channels == 2) { + err = this->comresp(this, nid, + CORB_GET_AMPLIFIER_GAIN_MUTE, + CORB_GAGM_OUTPUT | CORB_GAGM_RIGHT | 0, &result); + if (err) + return err; + mc->un.value.level[1] = generic_mixer_from_device_value + (this, nid, target, CORB_GAGM_GAIN(result)); + } + } + + /* selection */ + else if (target == MI_TARGET_CONNLIST) { + err = this->comresp(this, nid, + CORB_GET_CONNECTION_SELECT_CONTROL, 0, &result); + if (err) + return err; + result = CORB_CSC_INDEX(result); + if (!VALID_WIDGET_NID(this->w[nid].connections[result], this)) + mc->un.ord = -1; + else + mc->un.ord = result; + } + + /* pin I/O */ + else if (target == MI_TARGET_PINDIR) { + err = this->comresp(this, nid, + CORB_GET_PIN_WIDGET_CONTROL, 0, &result); + if (err) + return err; + mc->un.ord = result & CORB_PWC_OUTPUT ? 1 : 0; + } + + /* pin headphone-boost */ + else if (target == MI_TARGET_PINBOOST) { + err = this->comresp(this, nid, + CORB_GET_PIN_WIDGET_CONTROL, 0, &result); + if (err) + return err; + mc->un.ord = result & CORB_PWC_HEADPHONE ? 1 : 0; + } + + /* DAC group selection */ + else if (target == MI_TARGET_DAC) { + mc->un.ord = this->cur_dac; + } + + /* ADC selection */ + else if (target == MI_TARGET_ADC) { + mc->un.ord = this->cur_adc; + } + + /* Volume knob */ + else if (target == MI_TARGET_VOLUME) { + err = this->comresp(this, nid, CORB_GET_VOLUME_KNOB, + 0, &result); + if (err) + return err; + mc->un.value.level[0] = generic_mixer_from_device_value(this, + nid, target, CORB_VKNOB_VOLUME(result)); + mc->un.value.num_channels = 1; + } + + else { + aprint_error("%s: internal error in %s: target=%x\n", + XNAME(this), __func__, target); + return -1; + } + return 0; +} + +static int +generic_mixer_set(codec_t *this, nid_t nid, int target, const mixer_ctrl_t *mc) +{ + uint32_t result, value; + int err; + + /* inamp mute */ + if (IS_MI_TARGET_INAMP(target) && mc->type == AUDIO_MIXER_ENUM) { + /* We have to set stereo mute separately to keep each gain value. */ + err = this->comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE, + CORB_GAGM_INPUT | CORB_GAGM_LEFT | + MI_TARGET_INAMP(target), &result); + if (err) + return err; + value = CORB_AGM_INPUT | CORB_AGM_LEFT | + (target << CORB_AGM_INDEX_SHIFT) | + CORB_GAGM_GAIN(result); + if (mc->un.ord) + value |= CORB_AGM_MUTE; + err = this->comresp(this, nid, CORB_SET_AMPLIFIER_GAIN_MUTE, + value, &result); + if (err) + return err; + if (WIDGET_CHANNELS(&this->w[nid]) == 2) { + err = this->comresp(this, nid, + CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_INPUT | + CORB_GAGM_RIGHT | MI_TARGET_INAMP(target), + &result); + if (err) + return err; + value = CORB_AGM_INPUT | CORB_AGM_RIGHT | + (target << CORB_AGM_INDEX_SHIFT) | + CORB_GAGM_GAIN(result); + if (mc->un.ord) + value |= CORB_AGM_MUTE; + err = this->comresp(this, nid, + CORB_SET_AMPLIFIER_GAIN_MUTE, value, &result); + if (err) + return err; + } + } + + /* inamp gain */ + else if (IS_MI_TARGET_INAMP(target) && mc->type == AUDIO_MIXER_VALUE) { + if (mc->un.value.num_channels < 1) + return EINVAL; + if (!generic_mixer_validate_value(this, nid, target, + mc->un.value.level[0])) + return EINVAL; + err = this->comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE, + CORB_GAGM_INPUT | CORB_GAGM_LEFT | + MI_TARGET_INAMP(target), &result); + if (err) + return err; + value = generic_mixer_to_device_value(this, nid, target, + mc->un.value.level[0]); + value = CORB_AGM_INPUT | CORB_AGM_LEFT | + (target << CORB_AGM_INDEX_SHIFT) | + (result & CORB_GAGM_MUTE ? CORB_AGM_MUTE : 0) | + (value & CORB_AGM_GAIN_MASK); + err = this->comresp(this, nid, CORB_SET_AMPLIFIER_GAIN_MUTE, + value, &result); + if (err) + return err; + if (mc->un.value.num_channels >= 2 && + WIDGET_CHANNELS(&this->w[nid]) == 2) { + if (!generic_mixer_validate_value(this, nid, target, + mc->un.value.level[1])) + return EINVAL; + err = this->comresp(this, nid, + CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_INPUT | + CORB_GAGM_RIGHT | MI_TARGET_INAMP(target), + &result); + if (err) + return err; + value = generic_mixer_to_device_value(this, nid, target, + mc->un.value.level[1]); + value = CORB_AGM_INPUT | CORB_AGM_RIGHT | + (target << CORB_AGM_INDEX_SHIFT) | + (result & CORB_GAGM_MUTE ? CORB_AGM_MUTE : 0) | + (value & CORB_AGM_GAIN_MASK); + err = this->comresp(this, nid, + CORB_SET_AMPLIFIER_GAIN_MUTE, value, &result); + if (err) + return err; + } + } + + /* outamp mute */ + else if (target == MI_TARGET_OUTAMP && mc->type == AUDIO_MIXER_ENUM) { + err = this->comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE, + CORB_GAGM_OUTPUT | CORB_GAGM_LEFT, &result); + if (err) + return err; + value = CORB_AGM_OUTPUT | CORB_AGM_LEFT | CORB_GAGM_GAIN(result); + if (mc->un.ord) + value |= CORB_AGM_MUTE; + err = this->comresp(this, nid, CORB_SET_AMPLIFIER_GAIN_MUTE, + value, &result); + if (err) + return err; + if (WIDGET_CHANNELS(&this->w[nid]) == 2) { + err = this->comresp(this, nid, + CORB_GET_AMPLIFIER_GAIN_MUTE, + CORB_GAGM_OUTPUT | CORB_GAGM_RIGHT, &result); + if (err) + return err; + value = CORB_AGM_OUTPUT | CORB_AGM_RIGHT | + CORB_GAGM_GAIN(result); + if (mc->un.ord) + value |= CORB_AGM_MUTE; + err = this->comresp(this, nid, + CORB_SET_AMPLIFIER_GAIN_MUTE, value, &result); + if (err) + return err; + } + } + + /* outamp gain */ + else if (target == MI_TARGET_OUTAMP && mc->type == AUDIO_MIXER_VALUE) { + if (mc->un.value.num_channels < 1) + return EINVAL; + if (!generic_mixer_validate_value(this, nid, target, + mc->un.value.level[0])) + return EINVAL; + err = this->comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE, + CORB_GAGM_OUTPUT | CORB_GAGM_LEFT, &result); + if (err) + return err; + value = generic_mixer_to_device_value(this, nid, target, + mc->un.value.level[0]); + value = CORB_AGM_OUTPUT | CORB_AGM_LEFT | + (result & CORB_GAGM_MUTE ? CORB_AGM_MUTE : 0) | + (value & CORB_AGM_GAIN_MASK); + err = this->comresp(this, nid, CORB_SET_AMPLIFIER_GAIN_MUTE, + value, &result); + if (err) + return err; + if (mc->un.value.num_channels >= 2 && + WIDGET_CHANNELS(&this->w[nid]) == 2) { + if (!generic_mixer_validate_value(this, nid, target, + mc->un.value.level[1])) + return EINVAL; + err = this->comresp(this, nid, + CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_OUTPUT | + CORB_GAGM_RIGHT, &result); + if (err) + return err; + value = generic_mixer_to_device_value(this, nid, target, + mc->un.value.level[1]); + value = CORB_AGM_OUTPUT | CORB_AGM_RIGHT | + (result & CORB_GAGM_MUTE ? CORB_AGM_MUTE : 0) | + (value & CORB_AGM_GAIN_MASK); + err = this->comresp(this, nid, + CORB_SET_AMPLIFIER_GAIN_MUTE, value, &result); + if (err) + return err; + } + } + + /* selection */ + else if (target == MI_TARGET_CONNLIST) { + if (mc->un.ord < 0 || + mc->un.ord >= this->w[nid].nconnections || + !VALID_WIDGET_NID(this->w[nid].connections[mc->un.ord], this)) + return EINVAL; + err = this->comresp(this, nid, + CORB_SET_CONNECTION_SELECT_CONTROL, mc->un.ord, &result); + if (err) + return err; + } + + /* pin I/O */ + else if (target == MI_TARGET_PINDIR) { + if (mc->un.ord >= 2) + return EINVAL; + err = this->comresp(this, nid, + CORB_GET_PIN_WIDGET_CONTROL, 0, &result); + if (err) + return err; + if (mc->un.ord == 0) { + result &= ~CORB_PWC_OUTPUT; + result |= CORB_PWC_INPUT; + } else { + result &= ~CORB_PWC_INPUT; + result |= CORB_PWC_OUTPUT; + } + err = this->comresp(this, nid, + CORB_SET_PIN_WIDGET_CONTROL, result, &result); + if (err) + return err; + } + + /* pin headphone-boost */ + else if (target == MI_TARGET_PINBOOST) { + if (mc->un.ord >= 2) + return EINVAL; + err = this->comresp(this, nid, + CORB_GET_PIN_WIDGET_CONTROL, 0, &result); + if (err) + return err; + if (mc->un.ord == 0) { + result &= ~CORB_PWC_HEADPHONE; + } else { + result |= CORB_PWC_HEADPHONE; + } + err = this->comresp(this, nid, + CORB_SET_PIN_WIDGET_CONTROL, result, &result); + if (err) + return err; + } + + /* DAC group selection */ + else if (target == MI_TARGET_DAC) { + if (this->running) + return EBUSY; + if (mc->un.ord >= this->ndacgroups) + return EINVAL; + this->cur_dac = mc->un.ord; + return azalia_codec_construct_format(this); + } + + /* ADC selection */ + else if (target == MI_TARGET_ADC) { + if (this->running) + return EBUSY; + if (mc->un.ord >= this->nadcs) + return EINVAL; + this->cur_adc = mc->un.ord; + /* use this->adcs[this->cur_adc] */ + return azalia_codec_construct_format(this); + } + + /* Volume knob */ + else if (target == MI_TARGET_VOLUME) { + if (mc->un.value.num_channels != 1) + return EINVAL; + if (!generic_mixer_validate_value(this, nid, + target, mc->un.value.level[0])) + return EINVAL; + value = generic_mixer_to_device_value(this, nid, target, + mc->un.value.level[0]) | CORB_VKNOB_DIRECT; + err = this->comresp(this, nid, CORB_SET_VOLUME_KNOB, + value, &result); + if (err) + return err; + } + + else { + aprint_error("%s: internal error in %s: target=%x\n", + XNAME(this), __func__, target); + return -1; + } + return 0; +} + +static u_char +generic_mixer_from_device_value(const codec_t *this, nid_t nid, int target, + uint32_t dv) +{ +#ifdef MAX_VOLUME_255 + uint32_t dmax; + + if (IS_MI_TARGET_INAMP(target)) + dmax = COP_AMPCAP_NUMSTEPS(this->w[nid].inamp_cap); + else if (target == MI_TARGET_OUTAMP) + dmax = COP_AMPCAP_NUMSTEPS(this->w[nid].outamp_cap); + else if (target == MI_TARGET_VOLUME) + dmax = COP_VKCAP_NUMSTEPS(this->w[nid].d.volume.cap); + else { + printf("unknown target: %d\n", target); + dmax = 255; + } + return dv * AUDIO_MAX_GAIN / dmax; +#else + return dv; +#endif +} + +static uint32_t +generic_mixer_to_device_value(const codec_t *this, nid_t nid, int target, + u_char uv) +{ +#ifdef MAX_VOLUME_255 + uint32_t dmax; + + if (IS_MI_TARGET_INAMP(target)) + dmax = COP_AMPCAP_NUMSTEPS(this->w[nid].inamp_cap); + else if (target == MI_TARGET_OUTAMP) + dmax = COP_AMPCAP_NUMSTEPS(this->w[nid].outamp_cap); + else if (target == MI_TARGET_VOLUME) + dmax = COP_VKCAP_NUMSTEPS(this->w[nid].d.volume.cap); + else { + printf("unknown target: %d\n", target); + dmax = 255; + } + return uv * dmax / AUDIO_MAX_GAIN; +#else + return uv; +#endif +} + +static boolean_t +generic_mixer_validate_value(const codec_t *this, nid_t nid, int target, + u_char uv) +{ +#ifdef MAX_VOLUME_255 + return TRUE; +#else + uint32_t dmax; + + if (IS_MI_TARGET_INAMP(target)) + dmax = COP_AMPCAP_NUMSTEPS(this->w[nid].inamp_cap); + else if (target == MI_TARGET_OUTAMP) + dmax = COP_AMPCAP_NUMSTEPS(this->w[nid].outamp_cap); + else if (target == MI_TARGET_VOLUME) + dmax = COP_VKCAP_NUMSTEPS(this->w[nid].d.volume.cap); + return uv <= dmax; +#endif +} + +static int +generic_set_port(codec_t *this, mixer_ctrl_t *mc) +{ + const mixer_item_t *m; + + if (mc->dev >= this->nmixers) + return ENXIO; + m = &this->mixers[mc->dev]; + if (mc->type != m->devinfo.type) + return EINVAL; + if (mc->type == AUDIO_MIXER_CLASS) + return 0; /* nothing to do */ + return generic_mixer_set(this, m->nid, m->target, mc); +} + +static int +generic_get_port(codec_t *this, mixer_ctrl_t *mc) +{ + const mixer_item_t *m; + + if (mc->dev >= this->nmixers) + return ENXIO; + m = &this->mixers[mc->dev]; + mc->type = m->devinfo.type; + if (mc->type == AUDIO_MIXER_CLASS) + return 0; /* nothing to do */ + return generic_mixer_get(this, m->nid, m->target, mc); +} + + +/* ---------------------------------------------------------------- + * Realtek ALC260 + * + * Fujitsu LOOX T70M/T + * Internal Speaker: 0x10 + * Front Headphone: 0x14 + * Front mic: 0x12 + * ---------------------------------------------------------------- */ + +#define ALC260_FUJITSU_ID 0x132610cf +static const mixer_item_t alc260_mixer_items[] = { + {{AZ_CLASS_INPUT, {AudioCinputs}, AUDIO_MIXER_CLASS, AZ_CLASS_INPUT, 0, 0}, 0}, + {{AZ_CLASS_OUTPUT, {AudioCoutputs}, AUDIO_MIXER_CLASS, AZ_CLASS_OUTPUT, 0, 0}, 0}, + {{AZ_CLASS_RECORD, {AudioCrecord}, AUDIO_MIXER_CLASS, AZ_CLASS_RECORD, 0, 0}, 0}, + + {{0, {AudioNmaster}, AUDIO_MIXER_VALUE, AZ_CLASS_OUTPUT, + 0, 0, .un.v={{""}, 2, 3}}, 0x08, MI_TARGET_OUTAMP}, /* and 0x09, 0x0a(mono) */ + {{0, {AudioNmaster".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT, + 0, 0, ENUM_OFFON}, 0x0f, MI_TARGET_OUTAMP}, + {{0, {AudioNheadphone".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT, + 0, 0, ENUM_OFFON}, 0x10, MI_TARGET_OUTAMP}, + {{0, {AudioNheadphone".boost"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT, + 0, 0, ENUM_OFFON}, 0x10, MI_TARGET_PINBOOST}, + {{0, {AudioNmono".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT, + 0, 0, ENUM_OFFON}, 0x11, MI_TARGET_OUTAMP}, + {{0, {"mic1.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT, + 0, 0, ENUM_OFFON}, 0x12, MI_TARGET_OUTAMP}, + {{0, {"mic1"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT, + 0, 0, ENUM_IO}, 0x12, MI_TARGET_PINDIR}, + {{0, {"mic2.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT, + 0, 0, ENUM_OFFON}, 0x13, MI_TARGET_OUTAMP}, + {{0, {"mic2"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT, + 0, 0, ENUM_IO}, 0x13, MI_TARGET_PINDIR}, + {{0, {"line1.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT, + 0, 0, ENUM_OFFON}, 0x14, MI_TARGET_OUTAMP}, + {{0, {"line1"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT, + 0, 0, ENUM_IO}, 0x14, MI_TARGET_PINDIR}, + {{0, {"line2.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT, + 0, 0, ENUM_OFFON}, 0x15, MI_TARGET_OUTAMP}, + {{0, {"line2"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT, + 0, 0, ENUM_IO}, 0x15, MI_TARGET_PINDIR}, + + {{0, {AudioNdac".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT, + 0, 0, ENUM_OFFON}, 0x08, MI_TARGET_INAMP(0)}, /* and 0x09, 0x0a(mono) */ + {{0, {"mic1.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT, + 0, 0, ENUM_OFFON}, 0x07, MI_TARGET_INAMP(0)}, + {{0, {"mic1"}, AUDIO_MIXER_VALUE, AZ_CLASS_INPUT, + 0, 0, .un.v={{""}, 2, 3}}, 0x07, MI_TARGET_INAMP(0)}, + {{0, {"mic2.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT, + 0, 0, ENUM_OFFON}, 0x07, MI_TARGET_INAMP(1)}, + {{0, {"mic2"}, AUDIO_MIXER_VALUE, AZ_CLASS_INPUT, + 0, 0, .un.v={{""}, 2, 3}}, 0x07, MI_TARGET_INAMP(1)}, + {{0, {"line1.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT, + 0, 0, ENUM_OFFON}, 0x07, MI_TARGET_INAMP(2)}, + {{0, {"line1"}, AUDIO_MIXER_VALUE, AZ_CLASS_INPUT, + 0, 0, .un.v={{""}, 2, 3}}, 0x07, MI_TARGET_INAMP(2)}, + {{0, {"line2.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT, + 0, 0, ENUM_OFFON}, 0x07, MI_TARGET_INAMP(3)}, + {{0, {"line2"}, AUDIO_MIXER_VALUE, AZ_CLASS_INPUT, + 0, 0, .un.v={{""}, 2, 3}}, 0x07, MI_TARGET_INAMP(3)}, + {{0, {AudioNcd".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT, + 0, 0, ENUM_OFFON}, 0x07, MI_TARGET_INAMP(4)}, + {{0, {AudioNcd}, AUDIO_MIXER_VALUE, AZ_CLASS_INPUT, + 0, 0, .un.v={{""}, 2, 3}}, 0x07, MI_TARGET_INAMP(4)}, + {{0, {AudioNspeaker".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT, + 0, 0, ENUM_OFFON}, 0x07, MI_TARGET_INAMP(5)}, + {{0, {AudioNspeaker}, AUDIO_MIXER_VALUE, AZ_CLASS_INPUT, + 0, 0, .un.v={{""}, 2, 3}}, 0x07, MI_TARGET_INAMP(5)}, + + {{0, {"adc04.source"}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0, + .un.e={5, {{{"mic1"}, 0}, {{"mic2"}, 1}, {{"line1"}, 2}, + {{"line2"}, 3}, {{AudioNcd}, 4}}}}, + 0x04, MI_TARGET_CONNLIST}, + {{0, {"adc04.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0, + ENUM_OFFON}, 0x04, MI_TARGET_INAMP(0)}, + {{0, {"adc04"}, AUDIO_MIXER_VALUE, AZ_CLASS_RECORD, 0, 0, .un.v={{""}, 2, 7}}, + 0x04, MI_TARGET_INAMP(0)}, + {{0, {"adc05.source"}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0, + .un.e={6, {{{"mic1"}, 0}, {{"mic2"}, 1}, {{"line1"}, 2}, + {{"line2"}, 3}, {{AudioNcd}, 4}, {{AudioNmixerout}, 5}}}}, + 0x05, MI_TARGET_CONNLIST}, + {{0, {"adc05.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0, + ENUM_OFFON}, 0x05, MI_TARGET_INAMP(0)}, + {{0, {"adc05"}, AUDIO_MIXER_VALUE, AZ_CLASS_RECORD, 0, 0, + .un.v={{""}, 2, 7}}, 0x05, MI_TARGET_INAMP(0)}, + + {{0, {"usingdac"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT, 0, 0, + .un.e={2, {{{"analog"}, 0}, {{"digital"}, 1}}}}, 0, MI_TARGET_DAC}, + {{0, {"usingadc"}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0, + .un.e={3, {{{"adc04"}, 0}, {{"adc05"}, 1}, {{"digital"}, 2}}}}, 0, MI_TARGET_ADC}, +}; + +static const mixer_item_t alc260_loox_mixer_items[] = { + {{AZ_CLASS_INPUT, {AudioCinputs}, AUDIO_MIXER_CLASS, AZ_CLASS_INPUT, 0, 0}, 0}, + {{AZ_CLASS_OUTPUT, {AudioCoutputs}, AUDIO_MIXER_CLASS, AZ_CLASS_OUTPUT, 0, 0}, 0}, + {{AZ_CLASS_RECORD, {AudioCrecord}, AUDIO_MIXER_CLASS, AZ_CLASS_RECORD, 0, 0}, 0}, + + {{0, {AudioNmaster}, AUDIO_MIXER_VALUE, AZ_CLASS_OUTPUT, + 0, 0, .un.v={{""}, 2, 3}}, 0x08, MI_TARGET_OUTAMP}, /* and 0x09, 0x0a(mono) */ + {{0, {AudioNmaster".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT, + 0, 0, ENUM_OFFON}, 0x10, MI_TARGET_OUTAMP}, + {{0, {AudioNmaster".boost"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT, + 0, 0, ENUM_OFFON}, 0x10, MI_TARGET_PINBOOST}, + {{0, {AudioNheadphone".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT, + 0, 0, ENUM_OFFON}, 0x14, MI_TARGET_OUTAMP}, + {{0, {AudioNheadphone".boost"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT, + 0, 0, ENUM_OFFON}, 0x14, MI_TARGET_PINBOOST}, + + {{0, {AudioNdac".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT, + 0, 0, ENUM_OFFON}, 0x08, MI_TARGET_INAMP(0)}, /* and 0x09, 0x0a(mono) */ + {{0, {AudioNmicrophone".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT, + 0, 0, ENUM_OFFON}, 0x07, MI_TARGET_INAMP(0)}, + {{0, {AudioNmicrophone}, AUDIO_MIXER_VALUE, AZ_CLASS_INPUT, + 0, 0, .un.v={{""}, 2, 3}}, 0x07, MI_TARGET_INAMP(0)}, + {{0, {AudioNcd".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT, + 0, 0, ENUM_OFFON}, 0x07, MI_TARGET_INAMP(4)}, + {{0, {AudioNcd}, AUDIO_MIXER_VALUE, AZ_CLASS_INPUT, + 0, 0, .un.v={{""}, 2, 3}}, 0x07, MI_TARGET_INAMP(4)}, + {{0, {AudioNspeaker".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT, + 0, 0, ENUM_OFFON}, 0x07, MI_TARGET_INAMP(5)}, + {{0, {AudioNspeaker}, AUDIO_MIXER_VALUE, AZ_CLASS_INPUT, + 0, 0, .un.v={{""}, 2, 3}}, 0x07, MI_TARGET_INAMP(5)}, + + {{0, {"adc04.source"}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0, + .un.e={2, {{{AudioNmicrophone}, 0}, {{AudioNcd}, 4}}}}, 0x04, MI_TARGET_CONNLIST}, + {{0, {"adc04.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0, + ENUM_OFFON}, 0x04, MI_TARGET_INAMP(0)}, + {{0, {"adc04"}, AUDIO_MIXER_VALUE, AZ_CLASS_RECORD, 0, 0, + .un.v={{""}, 2, 7}}, 0x04, MI_TARGET_INAMP(0)}, + {{0, {"adc05.source"}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0, + .un.e={3, {{{AudioNmicrophone}, 0}, {{AudioNcd}, 4}, {{AudioNmixerout}, 5}}}}, + 0x05, MI_TARGET_CONNLIST}, + {{0, {"adc05.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0, + ENUM_OFFON}, 0x05, MI_TARGET_INAMP(0)}, + {{0, {"adc05"}, AUDIO_MIXER_VALUE, AZ_CLASS_RECORD, 0, 0, + .un.v={{""}, 2, 7}}, 0x05, MI_TARGET_INAMP(0)}, + + {{0, {"usingdac"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT, 0, 0, + .un.e={2, {{{"analog"}, 0}, {{"digital"}, 1}}}}, 0, MI_TARGET_DAC}, + {{0, {"usingadc"}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0, + .un.e={3, {{{"adc04"}, 0}, {{"adc05"}, 1}, {{"digital"}, 2}}}}, 0, MI_TARGET_ADC}, +}; + +static int +alc260_mixer_init(codec_t *this) +{ + const mixer_item_t *mi; + mixer_ctrl_t mc; + + switch (this->subid) { + case ALC260_FUJITSU_ID: + this->nmixers = sizeof(alc260_loox_mixer_items) / sizeof(mixer_item_t); + mi = alc260_loox_mixer_items; + break; + default: + this->nmixers = sizeof(alc260_mixer_items) / sizeof(mixer_item_t); + mi = alc260_mixer_items; + } + this->mixers = malloc(sizeof(mixer_item_t) * this->nmixers, + M_DEVBUF, M_ZERO | M_NOWAIT); + if (this->mixers == NULL) { + aprint_error("%s: out of memory in %s\n", XNAME(this), __func__); + return ENOMEM; + } + memcpy(this->mixers, mi, sizeof(mixer_item_t) * this->nmixers); + generic_mixer_fix_indexes(this); + generic_mixer_default(this); + + mc.dev = -1; /* no need for generic_mixer_set() */ + mc.type = AUDIO_MIXER_ENUM; + mc.un.ord = 1; /* pindir: output */ + generic_mixer_set(this, 0x0f, MI_TARGET_PINDIR, &mc); /* lineout */ + generic_mixer_set(this, 0x10, MI_TARGET_PINDIR, &mc); /* headphones */ + mc.un.ord = 0; /* pindir: input */ + generic_mixer_set(this, 0x12, MI_TARGET_PINDIR, &mc); /* mic1 */ + generic_mixer_set(this, 0x13, MI_TARGET_PINDIR, &mc); /* mic2 */ + generic_mixer_set(this, 0x14, MI_TARGET_PINDIR, &mc); /* line1 */ + generic_mixer_set(this, 0x15, MI_TARGET_PINDIR, &mc); /* line2 */ + mc.un.ord = 0; /* mute: off */ + generic_mixer_set(this, 0x08, MI_TARGET_INAMP(0), &mc); + generic_mixer_set(this, 0x08, MI_TARGET_INAMP(1), &mc); + generic_mixer_set(this, 0x09, MI_TARGET_INAMP(0), &mc); + generic_mixer_set(this, 0x09, MI_TARGET_INAMP(1), &mc); + generic_mixer_set(this, 0x0a, MI_TARGET_INAMP(0), &mc); + generic_mixer_set(this, 0x0a, MI_TARGET_INAMP(1), &mc); + if (this->subid == ALC260_FUJITSU_ID) { + mc.un.ord = 1; /* pindir: output */ + generic_mixer_set(this, 0x14, MI_TARGET_PINDIR, &mc); /* line1 */ + mc.un.ord = 4; /* connlist: cd */ + generic_mixer_set(this, 0x05, MI_TARGET_CONNLIST, &mc); + } + return 0; +} + static int alc260_init_dacgroup(codec_t *this) { @@ -282,51 +1511,51 @@ alc260_init_dacgroup(codec_t *this) } static int -alc260_init_widget(const codec_t *this, widget_t *w, nid_t nid) +alc260_set_port(codec_t *this, mixer_ctrl_t *mc) { - switch (nid) { - case 0x0b: /* selector for 0x12 */ - strlcpy(w->name, AudioNmicrophone "1", sizeof(w->name)); - break; - case 0x0c: /* selector for 0x13 */ - strlcpy(w->name, AudioNmicrophone "2", sizeof(w->name)); - break; - case 0x0d: /* selector for 0x14 */ - strlcpy(w->name, AudioNline "1", sizeof(w->name)); - break; - case 0x0e: /* selector for 0x15 */ - strlcpy(w->name, AudioNline "2", sizeof(w->name)); - break; - case 0x0f: - strlcpy(w->name, AudioNline, sizeof(w->name)); - break; - case 0x10: - /* AudioNheadphone is too long */ - strlcpy(w->name, "hp", sizeof(w->name)); - break; - case 0x11: - strlcpy(w->name, AudioNmono, sizeof(w->name)); - break; - case 0x12: - strlcpy(w->name, AudioNmicrophone "1", sizeof(w->name)); - break; - case 0x13: - strlcpy(w->name, AudioNmicrophone "2", sizeof(w->name)); - break; - case 0x14: - strlcpy(w->name, AudioNline "1", sizeof(w->name)); - break; - case 0x15: - strlcpy(w->name, AudioNline "2", sizeof(w->name)); - break; - case 0x16: - strlcpy(w->name, AudioNcd, sizeof(w->name)); - break; - case 0x17: - strlcpy(w->name, AudioNspeaker, sizeof(w->name)); - break; + const mixer_item_t *m; + mixer_ctrl_t mc2; + int err; + + if (mc->dev >= this->nmixers) + return ENXIO; + m = &this->mixers[mc->dev]; + if (mc->type != m->devinfo.type) + return EINVAL; + if (mc->type == AUDIO_MIXER_CLASS) + return 0; + if (m->nid == 0x08 && m->target == MI_TARGET_OUTAMP) { + DPRINTF(("%s: hook for outputs.master\n", __func__)); + err = generic_mixer_set(this, m->nid, m->target, mc); + if (!err) { + generic_mixer_set(this, 0x09, m->target, mc); + mc2 = *mc; + mc2.un.value.num_channels = 1; + mc2.un.value.level[0] = (mc2.un.value.level[0] + + mc2.un.value.level[1]) / 2; + generic_mixer_set(this, 0x0a, m->target, &mc2); + } + return err; + } else if (m->nid == 0x08 && m->target == MI_TARGET_INAMP(0)) { + DPRINTF(("%s: hook for inputs.dac.mute\n", __func__)); + err = generic_mixer_set(this, m->nid, m->target, mc); + if (!err) { + generic_mixer_set(this, 0x09, m->target, mc); + generic_mixer_set(this, 0x0a, m->target, mc); + } + return err; + } else if (m->nid == 0x04 && + m->target == MI_TARGET_CONNLIST && + m->devinfo.un.e.num_mem == 2) { + if (1 <= mc->un.ord && mc->un.ord <= 3) + return EINVAL; + } else if (m->nid == 0x05 && + m->target == MI_TARGET_CONNLIST && + m->devinfo.un.e.num_mem == 3) { + if (1 <= mc->un.ord && mc->un.ord <= 3) + return EINVAL; } - return 0; + return generic_mixer_set(this, m->nid, m->target, mc); } /* ----------------------------------------------------------------