/* $OpenBSD: azalia_codec.c,v 1.189 2022/09/08 01:35:39 jsg Exp $ */ /* $NetBSD: azalia_codec.c,v 1.8 2006/05/10 11:17:27 kent Exp $ */ /*- * Copyright (c) 2005 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by TAMURA Kent * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #define XNAME(co) (((struct device *)co->az)->dv_xname) #define MIXER_DELTA(n) (AUDIO_MAX_GAIN / (n)) int azalia_add_convgroup(codec_t *, convgroupset_t *, struct io_pin *, int, nid_t *, int, uint32_t, uint32_t); int azalia_mixer_fix_indexes(codec_t *); int azalia_mixer_default(codec_t *); int azalia_mixer_ensure_capacity(codec_t *, size_t); u_char azalia_mixer_from_device_value(const codec_t *, nid_t, int, uint32_t ); uint32_t azalia_mixer_to_device_value(const codec_t *, nid_t, int, u_char); void azalia_devinfo_offon(mixer_devinfo_t *); void azalia_pin_config_ov(widget_t *, int, int); void azalia_ampcap_ov(widget_t *, int, int, int, int, int, int); int azalia_gpio_unmute(codec_t *, int); int azalia_codec_init_vtbl(codec_t *this) { /** * We can refer this->vid and this->subid. */ this->name = NULL; this->qrks = AZ_QRK_NONE; switch (this->vid) { case 0x10134206: this->name = "Cirrus Logic CS4206"; if (this->subid == 0xcb8910de || /* APPLE_MBA3_1 */ this->subid == 0x72708086 || /* APPLE_MBA4_1 */ this->subid == 0xcb7910de) { /* APPLE_MBP5_5 */ this->qrks |= AZ_QRK_GPIO_UNMUTE_1 | AZ_QRK_GPIO_UNMUTE_3; } break; case 0x10134208: this->name = "Cirrus Logic CS4208"; if (this->subid == 0x72708086) { /* APPLE_MBA6_1 */ this->qrks |= AZ_QRK_GPIO_UNMUTE_0 | AZ_QRK_GPIO_UNMUTE_1; } break; case 0x10ec0221: this->name = "Realtek ALC221"; this->qrks |= AZ_QRK_WID_CDIN_1C | AZ_QRK_WID_BEEP_1D; break; case 0x10ec0225: this->name = "Realtek ALC225"; break; case 0x10ec0233: case 0x10ec0235: this->name = "Realtek ALC233"; break; case 0x10ec0236: if (PCI_VENDOR(this->subid) == PCI_VENDOR_DELL) this->name = "Realtek ALC3204"; else this->name = "Realtek ALC236"; break; case 0x10ec0245: this->name = "Realtek ALC245"; break; case 0x10ec0255: this->name = "Realtek ALC255"; break; case 0x10ec0256: this->name = "Realtek ALC256"; break; case 0x10ec0257: this->name = "Realtek ALC257"; break; case 0x10ec0260: this->name = "Realtek ALC260"; if (this->subid == 0x008f1025) this->qrks |= AZ_QRK_GPIO_UNMUTE_0; break; case 0x10ec0262: this->name = "Realtek ALC262"; this->qrks |= AZ_QRK_WID_CDIN_1C | AZ_QRK_WID_BEEP_1D; break; case 0x10ec0268: this->name = "Realtek ALC268"; this->qrks |= AZ_QRK_WID_CDIN_1C | AZ_QRK_WID_BEEP_1D; break; case 0x10ec0269: this->name = "Realtek ALC269"; this->qrks |= AZ_QRK_WID_CDIN_1C | AZ_QRK_WID_BEEP_1D; /* * Enable dock audio on Thinkpad docks * 0x17aa : 0x21f3 = Thinkpad T430 * 0x17aa : 0x21f6 = Thinkpad T530 * 0x17aa : 0x21fa = Thinkpad X230 * 0x17aa : 0x21fb = Thinkpad T430s * 0x17aa : 0x2203 = Thinkpad X230t * 0x17aa : 0x2208 = Thinkpad T431s */ if (this->subid == 0x21f317aa || this->subid == 0x21f617aa || this->subid == 0x21fa17aa || this->subid == 0x21fb17aa || this->subid == 0x220317aa || this->subid == 0x220817aa) this->qrks |= AZ_QRK_WID_TPDOCK1; break; case 0x10ec0270: this->name = "Realtek ALC270"; break; case 0x10ec0272: this->name = "Realtek ALC272"; break; case 0x10ec0275: this->name = "Realtek ALC275"; break; case 0x10ec0280: this->name = "Realtek ALC280"; break; case 0x10ec0282: this->name = "Realtek ALC282"; this->qrks |= AZ_QRK_WID_CDIN_1C | AZ_QRK_WID_BEEP_1D; break; case 0x10ec0283: this->name = "Realtek ALC283"; break; case 0x10ec0285: this->name = "Realtek ALC285"; if (this->subid == 0x229217aa) { /* Thinkpad X1 Carbon 7 */ this->qrks |= AZ_QRK_ROUTE_SPKR2_DAC | AZ_QRK_WID_CLOSE_PCBEEP; } else if (this->subid == 0x22c017aa) { /* Thinkpad X1 Extreme 3 */ this->qrks |= AZ_QRK_DOLBY_ATMOS | AZ_QRK_ROUTE_SPKR2_DAC; } break; case 0x10ec0287: this->name = "Realtek ALC287"; break; case 0x10ec0292: this->name = "Realtek ALC292"; this->qrks |= AZ_QRK_WID_CDIN_1C | AZ_QRK_WID_BEEP_1D; /* * Enable dock audio on Thinkpad docks * 0x17aa : 0x220c = Thinkpad T440s * 0x17aa : 0x220e = Thinkpad T440p * 0x17aa : 0x2210 = Thinkpad T540p * 0x17aa : 0x2212 = Thinkpad T440 * 0x17aa : 0x2214 = Thinkpad X240 * 0x17aa : 0x2226 = Thinkpad X250 * 0x17aa : 0x501e = Thinkpad L440 * 0x17aa : 0x5034 = Thinkpad T450 * 0x17aa : 0x5036 = Thinkpad T450s * 0x17aa : 0x503c = Thinkpad L450 */ if (this->subid == 0x220c17aa || this->subid == 0x220e17aa || this->subid == 0x221017aa || this->subid == 0x221217aa || this->subid == 0x221417aa || this->subid == 0x222617aa || this->subid == 0x501e17aa || this->subid == 0x503417aa || this->subid == 0x503617aa || this->subid == 0x503c17aa) this->qrks |= AZ_QRK_WID_TPDOCK2; break; case 0x10ec0293: if (PCI_VENDOR(this->subid) == PCI_VENDOR_DELL) this->name = "Realtek ALC3235"; else this->name = "Realtek ALC293"; break; case 0x10ec0294: this->name = "Realtek ALC294"; break; case 0x10ec0295: if (PCI_VENDOR(this->subid) == PCI_VENDOR_DELL) this->name = "Realtek ALC3254"; else this->name = "Realtek ALC295"; break; case 0x10ec0298: this->name = "Realtek ALC298"; if (this->subid == 0x320019e5 || this->subid == 0x320119e5) /* Huawei Matebook X */ this->qrks |= AZ_QRK_DOLBY_ATMOS; break; case 0x10ec0299: this->name = "Realtek ALC299"; break; case 0x10ec0660: this->name = "Realtek ALC660"; if (this->subid == 0x13391043) { /* ASUS_G2K */ this->qrks |= AZ_QRK_GPIO_UNMUTE_0; } break; case 0x10ec0662: this->name = "Realtek ALC662"; this->qrks |= AZ_QRK_WID_CDIN_1C | AZ_QRK_WID_BEEP_1D; break; case 0x10ec0663: this->name = "Realtek ALC663"; break; case 0x10ec0668: if (PCI_VENDOR(this->subid) == PCI_VENDOR_DELL) this->name = "Realtek ALC3661"; else this->name = "Realtek ALC668"; break; case 0x10ec0671: this->name = "Realtek ALC671"; break; case 0x10ec0700: this->name = "Realtek ALC700"; break; case 0x10ec0861: this->name = "Realtek ALC861"; break; case 0x10ec0880: this->name = "Realtek ALC880"; this->qrks |= AZ_QRK_WID_CDIN_1C | AZ_QRK_WID_BEEP_1D; if (this->subid == 0x19931043 || /* ASUS_M5200 */ this->subid == 0x13231043) { /* ASUS_A7M */ this->qrks |= AZ_QRK_GPIO_UNMUTE_0; } if (this->subid == 0x203d161f) { /* MEDION_MD95257 */ this->qrks |= AZ_QRK_GPIO_UNMUTE_1; } break; case 0x10ec0882: this->name = "Realtek ALC882"; this->qrks |= AZ_QRK_WID_CDIN_1C | AZ_QRK_WID_BEEP_1D; if (this->subid == 0x13c21043 || /* ASUS_A7T */ this->subid == 0x19711043) { /* ASUS_W2J */ this->qrks |= AZ_QRK_GPIO_UNMUTE_0; } break; case 0x10ec0883: this->name = "Realtek ALC883"; this->qrks |= AZ_QRK_WID_CDIN_1C | AZ_QRK_WID_BEEP_1D; if (this->subid == 0x00981025) { /* ACER_ID */ this->qrks |= AZ_QRK_GPIO_UNMUTE_0 | AZ_QRK_GPIO_UNMUTE_1; } break; case 0x10ec0885: this->name = "Realtek ALC885"; this->qrks |= AZ_QRK_WID_CDIN_1C | AZ_QRK_WID_BEEP_1D; if (this->subid == 0x00a1106b || /* APPLE_MB3 */ this->subid == 0xcb7910de || /* APPLE_MACMINI3_1 (line-in + hp) */ this->subid == 0x00a0106b || /* APPLE_MB3_1 */ this->subid == 0x00a3106b) { /* APPLE_MB4 */ this->qrks |= AZ_QRK_GPIO_UNMUTE_0; } if (this->subid == 0x00a1106b || this->subid == 0xcb7910de || /* APPLE_MACMINI3_1 (internal spkr) */ this->subid == 0x00a0106b) this->qrks |= AZ_QRK_WID_OVREF50; break; case 0x10ec0887: this->name = "Realtek ALC887"; break; case 0x10ec0888: this->name = "Realtek ALC888"; this->qrks |= AZ_QRK_WID_CDIN_1C | AZ_QRK_WID_BEEP_1D; break; case 0x10ec0889: this->name = "Realtek ALC889"; break; case 0x10ec0892: this->name = "Realtek ALC892"; break; case 0x10ec0897: this->name = "Realtek ALC897"; break; case 0x10ec0900: this->name = "Realtek ALC1150"; break; case 0x10ec0b00: this->name = "Realtek ALC1200"; break; case 0x10ec1168: case 0x10ec1220: this->name = "Realtek ALC1220"; break; case 0x11060398: case 0x11061398: case 0x11062398: case 0x11063398: case 0x11064398: case 0x11065398: case 0x11066398: case 0x11067398: this->name = "VIA VT1702"; break; case 0x111d7603: this->name = "IDT 92HD75B3/4"; if (PCI_VENDOR(this->subid) == PCI_VENDOR_HP) this->qrks |= AZ_QRK_GPIO_UNMUTE_0; break; case 0x111d7604: this->name = "IDT 92HD83C1X"; break; case 0x111d7605: this->name = "IDT 92HD81B1X"; break; case 0x111d7608: this->name = "IDT 92HD75B1/2"; if (PCI_VENDOR(this->subid) == PCI_VENDOR_HP) this->qrks |= AZ_QRK_GPIO_UNMUTE_0; break; case 0x111d7674: this->name = "IDT 92HD73D1"; break; case 0x111d7675: this->name = "IDT 92HD73C1"; /* aka 92HDW74C1 */ if (PCI_VENDOR(this->subid) == PCI_VENDOR_DELL) this->qrks |= AZ_QRK_GPIO_UNMUTE_0; break; case 0x111d7676: this->name = "IDT 92HD73E1"; /* aka 92HDW74E1 */ break; case 0x111d7695: this->name = "IDT 92HD95"; /* aka IDT/TSI 92HD95B */ break; case 0x111d76b0: this->name = "IDT 92HD71B8"; break; case 0x111d76b2: this->name = "IDT 92HD71B7"; if (PCI_VENDOR(this->subid) == PCI_VENDOR_DELL || PCI_VENDOR(this->subid) == PCI_VENDOR_HP) this->qrks |= AZ_QRK_GPIO_UNMUTE_0; break; case 0x111d76b6: this->name = "IDT 92HD71B5"; break; case 0x111d76d4: this->name = "IDT 92HD83C1C"; break; case 0x111d76d5: this->name = "IDT 92HD81B1C"; break; case 0x11d4184a: this->name = "Analog Devices AD1884A"; break; case 0x11d41882: this->name = "Analog Devices AD1882"; break; case 0x11d41883: this->name = "Analog Devices AD1883"; break; case 0x11d41884: this->name = "Analog Devices AD1884"; break; case 0x11d4194a: this->name = "Analog Devices AD1984A"; break; case 0x11d41981: this->name = "Analog Devices AD1981HD"; this->qrks |= AZ_QRK_WID_AD1981_OAMP; break; case 0x11d41983: this->name = "Analog Devices AD1983"; break; case 0x11d41984: this->name = "Analog Devices AD1984"; break; case 0x11d41988: this->name = "Analog Devices AD1988A"; break; case 0x11d4198b: this->name = "Analog Devices AD1988B"; break; case 0x11d4882a: this->name = "Analog Devices AD1882A"; break; case 0x11d4989a: this->name = "Analog Devices AD1989A"; break; case 0x11d4989b: this->name = "Analog Devices AD1989B"; break; case 0x14f15045: this->name = "Conexant CX20549"; /* Venice */ break; case 0x14f15047: this->name = "Conexant CX20551"; /* Waikiki */ break; case 0x14f15051: this->name = "Conexant CX20561"; /* Hermosa */ break; case 0x14f1506e: this->name = "Conexant CX20590"; /* * Enable dock audio on Thinkpad docks * 0x17aa : 0x20f2 = Thinkpad T400 * 0x17aa : 0x215e = Thinkpad T410 * 0x17aa : 0x215f = Thinkpad T510 * 0x17aa : 0x21ce = Thinkpad T420 * 0x17aa : 0x21cf = Thinkpad T520 * 0x17aa : 0x21da = Thinkpad X220 * 0x17aa : 0x21db = Thinkpad X220t */ if (this->subid == 0x20f217aa || this->subid == 0x215e17aa || this->subid == 0x215f17aa || this->subid == 0x21ce17aa || this->subid == 0x21cf17aa || this->subid == 0x21da17aa || this->subid == 0x21db17aa) this->qrks |= AZ_QRK_WID_TPDOCK3; break; case 0x434d4980: this->name = "CMedia CMI9880"; break; case 0x83847612: this->name = "Sigmatel STAC9230X"; break; case 0x83847613: this->name = "Sigmatel STAC9230D"; break; case 0x83847614: this->name = "Sigmatel STAC9229X"; break; case 0x83847615: this->name = "Sigmatel STAC9229D"; break; case 0x83847616: this->name = "Sigmatel STAC9228X"; if (this->subid == 0x02271028 || /* DELL_V1400 */ this->subid == 0x01f31028) { /* DELL_I1400 */ this->qrks |= AZ_QRK_GPIO_UNMUTE_2; } break; case 0x83847617: this->name = "Sigmatel STAC9228D"; break; case 0x83847618: this->name = "Sigmatel STAC9227X"; break; case 0x83847619: this->name = "Sigmatel STAC9227D"; break; case 0x83847620: this->name = "Sigmatel STAC9274"; break; case 0x83847621: this->name = "Sigmatel STAC9274D"; break; case 0x83847626: this->name = "Sigmatel STAC9271X"; break; case 0x83847627: this->name = "Sigmatel STAC9271D"; break; case 0x83847632: this->name = "Sigmatel STAC9202"; break; case 0x83847634: this->name = "Sigmatel STAC9250"; break; case 0x83847636: this->name = "Sigmatel STAC9251"; break; case 0x83847638: this->name = "IDT 92HD700X"; break; case 0x83847639: this->name = "IDT 92HD700D"; break; case 0x83847645: this->name = "IDT 92HD206X"; break; case 0x83847646: this->name = "IDT 92HD206D"; break; case 0x83847661: /* FALLTHROUGH */ case 0x83847662: this->name = "Sigmatel STAC9225"; break; case 0x83847680: this->name = "Sigmatel STAC9220/1"; if (this->subid == 0x76808384) { /* APPLE_ID */ this->qrks |= AZ_QRK_GPIO_POL_0 | AZ_QRK_GPIO_UNMUTE_0 | AZ_QRK_GPIO_UNMUTE_1; } break; case 0x83847682: /* FALLTHROUGH */ case 0x83847683: this->name = "Sigmatel STAC9221D"; /* aka IDT 92HD202 */ break; case 0x83847690: this->name = "Sigmatel STAC9200"; /* aka IDT 92HD001 */ break; case 0x83847691: this->name = "Sigmatel STAC9200D"; break; case 0x83847698: this->name = "IDT 92HD005"; break; case 0x83847699: this->name = "IDT 92HD005D"; break; case 0x838476a0: this->name = "Sigmatel STAC9205X"; if (this->subid == 0x01f91028 || /* DELL_D630 */ this->subid == 0x02281028) { /* DELL_V1500 */ this->qrks |= AZ_QRK_GPIO_UNMUTE_0; } break; case 0x838476a1: this->name = "Sigmatel STAC9205D"; break; case 0x838476a2: this->name = "Sigmatel STAC9204X"; break; case 0x838476a3: this->name = "Sigmatel STAC9204D"; break; } return 0; } /* ---------------------------------------------------------------- * functions for generic codecs * ---------------------------------------------------------------- */ int azalia_widget_enabled(const codec_t *this, nid_t nid) { if (!VALID_WIDGET_NID(nid, this) || !this->w[nid].enable) return 0; return 1; } int azalia_init_dacgroup(codec_t *this) { this->dacs.ngroups = 0; if (this->na_dacs > 0) azalia_add_convgroup(this, &this->dacs, this->opins, this->nopins, this->a_dacs, this->na_dacs, COP_AWTYPE_AUDIO_OUTPUT, 0); if (this->na_dacs_d > 0) azalia_add_convgroup(this, &this->dacs, this->opins_d, this->nopins_d, this->a_dacs_d, this->na_dacs_d, COP_AWTYPE_AUDIO_OUTPUT, COP_AWCAP_DIGITAL); this->dacs.cur = 0; this->adcs.ngroups = 0; if (this->na_adcs > 0) azalia_add_convgroup(this, &this->adcs, this->ipins, this->nipins, this->a_adcs, this->na_adcs, COP_AWTYPE_AUDIO_INPUT, 0); if (this->na_adcs_d > 0) azalia_add_convgroup(this, &this->adcs, this->ipins_d, this->nipins_d, this->a_adcs_d, this->na_adcs_d, COP_AWTYPE_AUDIO_INPUT, COP_AWCAP_DIGITAL); this->adcs.cur = 0; return 0; } int azalia_add_convgroup(codec_t *this, convgroupset_t *group, struct io_pin *pins, int npins, nid_t *all_convs, int nall_convs, uint32_t type, uint32_t digital) { nid_t convs[HDA_MAX_CHANNELS]; int nconvs; nid_t conv; int i, j, k; nconvs = 0; /* default pin connections */ for (i = 0; i < npins; i++) { conv = pins[i].conv; if (conv < 0) continue; for (j = 0; j < nconvs; j++) { if (convs[j] == conv) break; } if (j < nconvs) continue; convs[nconvs++] = conv; if (nconvs >= nall_convs) { goto done; } } /* non-default connections */ for (i = 0; i < npins; i++) { for (j = 0; j < nall_convs; j++) { conv = all_convs[j]; for (k = 0; k < nconvs; k++) { if (convs[k] == conv) break; } if (k < nconvs) continue; if (type == COP_AWTYPE_AUDIO_OUTPUT) { k = azalia_codec_fnode(this, conv, pins[i].nid, 0); if (k < 0) continue; } else { if (!azalia_widget_enabled(this, conv)) continue; k = azalia_codec_fnode(this, pins[i].nid, conv, 0); if (k < 0) continue; } convs[nconvs++] = conv; if (nconvs >= nall_convs) { goto done; } } } /* Make sure the speaker dac is part of the analog output convgroup * or it won't get connected by azalia_codec_connect_stream(). */ if (type == COP_AWTYPE_AUDIO_OUTPUT && !digital && nconvs < nall_convs && this->spkr_dac != -1) { for (i = 0; i < nconvs; i++) if (convs[i] == this->spkr_dac) break; if (i == nconvs) convs[nconvs++] = this->spkr_dac; } done: for (i = 0; i < nconvs; i++) group->groups[group->ngroups].conv[i] = convs[i]; if (nconvs > 0) { group->groups[group->ngroups].nconv = i; group->ngroups++; } /* Disable converters that aren't in a convgroup. */ for (i = 0; i < nall_convs; i++) { conv = all_convs[i]; for (j = 0; j < nconvs; j++) if (convs[j] == conv) break; if (j == nconvs) this->w[conv].enable = 0; } return 0; } int azalia_codec_fnode(codec_t *this, nid_t node, int index, int depth) { const widget_t *w; int i, ret; w = &this->w[index]; if (w->nid == node) { return index; } /* back at the beginning or a bad end */ if (depth > 0 && (w->type == COP_AWTYPE_PIN_COMPLEX || w->type == COP_AWTYPE_BEEP_GENERATOR || w->type == COP_AWTYPE_AUDIO_OUTPUT || w->type == COP_AWTYPE_AUDIO_INPUT)) return -1; if (++depth >= 10) return -1; for (i = 0; i < w->nconnections; i++) { if (!azalia_widget_enabled(this, w->connections[i])) continue; ret = azalia_codec_fnode(this, node, w->connections[i], depth); if (ret >= 0) return ret; } return -1; } int azalia_unsol_event(codec_t *this, int tag) { mixer_ctrl_t mc; uint32_t result; int i, err, vol, vol2; err = 0; tag = CORB_UNSOL_TAG(tag); switch (tag) { case AZ_TAG_SPKR: mc.type = AUDIO_MIXER_ENUM; vol = 0; for (i = 0; !vol && !err && i < this->nsense_pins; i++) { if (!(this->spkr_muters & (1 << i))) continue; err = azalia_comresp(this, this->sense_pins[i], CORB_GET_PIN_WIDGET_CONTROL, 0, &result); if (err || !(result & CORB_PWC_OUTPUT)) continue; err = azalia_comresp(this, this->sense_pins[i], CORB_GET_PIN_SENSE, 0, &result); if (!err && (result & CORB_PS_PRESENCE)) vol = 1; } if (err) break; this->spkr_muted = vol; switch(this->spkr_mute_method) { case AZ_SPKR_MUTE_SPKR_MUTE: mc.un.ord = vol; err = azalia_mixer_set(this, this->speaker, MI_TARGET_OUTAMP, &mc); if (!err && this->speaker2 != -1 && (this->w[this->speaker2].widgetcap & COP_AWCAP_OUTAMP) && (this->w[this->speaker2].outamp_cap & COP_AMPCAP_MUTE)) err = azalia_mixer_set(this, this->speaker2, MI_TARGET_OUTAMP, &mc); break; case AZ_SPKR_MUTE_SPKR_DIR: mc.un.ord = vol ? 0 : 1; err = azalia_mixer_set(this, this->speaker, MI_TARGET_PINDIR, &mc); if (!err && this->speaker2 != -1 && (this->w[this->speaker2].d.pin.cap & COP_PINCAP_OUTPUT) && (this->w[this->speaker2].d.pin.cap & COP_PINCAP_INPUT)) err = azalia_mixer_set(this, this->speaker2, MI_TARGET_PINDIR, &mc); break; case AZ_SPKR_MUTE_DAC_MUTE: mc.un.ord = vol; err = azalia_mixer_set(this, this->spkr_dac, MI_TARGET_OUTAMP, &mc); break; } break; case AZ_TAG_PLAYVOL: if (this->playvols.master == this->audiofunc) return EINVAL; err = azalia_comresp(this, this->playvols.master, CORB_GET_VOLUME_KNOB, 0, &result); if (err) return err; vol = CORB_VKNOB_VOLUME(result) - this->playvols.hw_step; vol2 = vol * (AUDIO_MAX_GAIN / this->playvols.hw_nsteps); this->playvols.hw_step = CORB_VKNOB_VOLUME(result); vol = vol2 + this->playvols.vol_l; if (vol < 0) vol = 0; else if (vol > AUDIO_MAX_GAIN) vol = AUDIO_MAX_GAIN; this->playvols.vol_l = vol; vol = vol2 + this->playvols.vol_r; if (vol < 0) vol = 0; else if (vol > AUDIO_MAX_GAIN) vol = AUDIO_MAX_GAIN; this->playvols.vol_r = vol; mc.type = AUDIO_MIXER_VALUE; mc.un.value.num_channels = 2; mc.un.value.level[0] = this->playvols.vol_l; mc.un.value.level[1] = this->playvols.vol_r; err = azalia_mixer_set(this, this->playvols.master, MI_TARGET_PLAYVOL, &mc); break; default: DPRINTF(("%s: unknown tag %d\n", __func__, tag)); break; } return err; } /* ---------------------------------------------------------------- * Generic mixer functions * ---------------------------------------------------------------- */ 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" */ const widget_t *w, *ww; mixer_item_t *m; int err, i, j, k, bits; this->maxmixers = 10; this->nmixers = 0; this->mixers = mallocarray(this->maxmixers, sizeof(mixer_item_t), M_DEVBUF, M_NOWAIT | M_ZERO); if (this->mixers == NULL) { printf("%s: out of memory in %s\n", XNAME(this), __func__); return ENOMEM; } /* register classes */ 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; \ m->nid = i FOR_EACH_WIDGET(this, i) { w = &this->w[i]; if (!w->enable) continue; /* selector */ if (w->nconnections > 0 && w->type != COP_AWTYPE_AUDIO_MIXER && !(w->nconnections == 1 && azalia_widget_enabled(this, w->connections[0]) && strcmp(w->name, this->w[w->connections[0]].name) == 0) && w->nid != this->mic) { MIXER_REG_PROLOG; snprintf(d->label.name, sizeof(d->label.name), "%s_source", w->name); d->type = AUDIO_MIXER_ENUM; if (w->mixer_class >= 0) d->mixer_class = w->mixer_class; 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 (!azalia_widget_enabled(this, w->connections[j])) continue; 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 && w->nid != this->mic) { MIXER_REG_PROLOG; snprintf(d->label.name, sizeof(d->label.name), "%s_mute", w->name); if (w->mixer_class >= 0) d->mixer_class = w->mixer_class; else { if (w->type == COP_AWTYPE_AUDIO_MIXER || w->type == COP_AWTYPE_AUDIO_SELECTOR || w->type == COP_AWTYPE_PIN_COMPLEX) d->mixer_class = AZ_CLASS_OUTPUT; else d->mixer_class = AZ_CLASS_INPUT; } m->target = MI_TARGET_OUTAMP; azalia_devinfo_offon(d); this->nmixers++; } /* output gain */ if (w->widgetcap & COP_AWCAP_OUTAMP && COP_AMPCAP_NUMSTEPS(w->outamp_cap) && w->nid != this->mic) { MIXER_REG_PROLOG; snprintf(d->label.name, sizeof(d->label.name), "%s", w->name); d->type = AUDIO_MIXER_VALUE; if (w->mixer_class >= 0) d->mixer_class = w->mixer_class; else { if (w->type == COP_AWTYPE_AUDIO_MIXER || w->type == COP_AWTYPE_AUDIO_SELECTOR || 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); d->un.v.units.name[0] = 0; d->un.v.delta = MIXER_DELTA(COP_AMPCAP_NUMSTEPS(w->outamp_cap)); this->nmixers++; } /* input mute */ if (w->widgetcap & COP_AWCAP_INAMP && w->inamp_cap & COP_AMPCAP_MUTE && w->nid != this->speaker && w->nid != this->speaker2) { if (w->type != COP_AWTYPE_AUDIO_MIXER) { MIXER_REG_PROLOG; snprintf(d->label.name, sizeof(d->label.name), "%s_mute", w->name); if (w->mixer_class >= 0) d->mixer_class = w->mixer_class; else d->mixer_class = AZ_CLASS_INPUT; m->target = 0; azalia_devinfo_offon(d); this->nmixers++; } else { MIXER_REG_PROLOG; snprintf(d->label.name, sizeof(d->label.name), "%s_source", w->name); m->target = MI_TARGET_MUTESET; d->type = AUDIO_MIXER_SET; if (w->mixer_class >= 0) d->mixer_class = w->mixer_class; else d->mixer_class = AZ_CLASS_INPUT; for (j = 0, k = 0; j < w->nconnections && k < 32; j++) { if (!azalia_widget_enabled(this, w->connections[j])) continue; if (w->connections[j] == this->speaker || w->connections[j] == this->speaker2) continue; d->un.s.member[k].mask = 1 << j; strlcpy(d->un.s.member[k].label.name, this->w[w->connections[j]].name, MAX_AUDIO_DEV_LEN); k++; } d->un.s.num_mem = k; if (k != 0) this->nmixers++; } } /* input gain */ if (w->widgetcap & COP_AWCAP_INAMP && COP_AMPCAP_NUMSTEPS(w->inamp_cap) && w->nid != this->speaker && w->nid != this->speaker2) { 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->mixer_class >= 0) d->mixer_class = w->mixer_class; else d->mixer_class = AZ_CLASS_INPUT; m->target = 0; d->un.v.num_channels = WIDGET_CHANNELS(w); d->un.v.units.name[0] = 0; d->un.v.delta = MIXER_DELTA(COP_AMPCAP_NUMSTEPS(w->inamp_cap)); this->nmixers++; } else { for (j = 0; j < w->nconnections; j++) { if (!azalia_widget_enabled(this, w->connections[j])) continue; if (w->connections[j] == this->speaker || w->connections[j] == this->speaker2) continue; MIXER_REG_PROLOG; 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->mixer_class >= 0) d->mixer_class = w->mixer_class; else d->mixer_class = AZ_CLASS_INPUT; m->target = j; d->un.v.num_channels = WIDGET_CHANNELS(w); d->un.v.units.name[0] = 0; d->un.v.delta = MIXER_DELTA(COP_AMPCAP_NUMSTEPS(w->inamp_cap)); this->nmixers++; } } } /* hardcoded mixer inputs */ if (w->type == COP_AWTYPE_AUDIO_MIXER && !(w->widgetcap & COP_AWCAP_INAMP)) { MIXER_REG_PROLOG; snprintf(d->label.name, sizeof(d->label.name), "%s_source", w->name); m->target = MI_TARGET_MIXERSET; d->type = AUDIO_MIXER_SET; if (w->mixer_class >= 0) d->mixer_class = w->mixer_class; else d->mixer_class = AZ_CLASS_INPUT; for (j = 0, k = 0; j < w->nconnections && k < 32; j++) { if (!azalia_widget_enabled(this, w->connections[j])) continue; if (w->connections[j] == this->speaker || w->connections[j] == this->speaker2) continue; d->un.s.member[k].mask = 1 << j; strlcpy(d->un.s.member[k].label.name, this->w[w->connections[j]].name, MAX_AUDIO_DEV_LEN); k++; } d->un.s.num_mem = k; if (k != 0) 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) || COP_PINCAP_VREF(w->d.pin.cap) > 1)) { MIXER_REG_PROLOG; 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; k = 0; d->un.e.member[k].ord = 0; strlcpy(d->un.e.member[k].label.name, "none", MAX_AUDIO_DEV_LEN); k++; if (w->d.pin.cap & COP_PINCAP_OUTPUT) { d->un.e.member[k].ord = 1; strlcpy(d->un.e.member[k].label.name, AudioNoutput, MAX_AUDIO_DEV_LEN); k++; } if (w->d.pin.cap & COP_PINCAP_INPUT) { d->un.e.member[k].ord = 2; strlcpy(d->un.e.member[k].label.name, AudioNinput, MAX_AUDIO_DEV_LEN); k++; for (j = 0; j < 4; j++) { if (j == 0) { bits = (1 << CORB_PWC_VREF_GND); strlcpy(d->un.e.member[k].label.name, AudioNinput "-vr0", MAX_AUDIO_DEV_LEN); } else if (j == 1) { bits = (1 << CORB_PWC_VREF_50); strlcpy(d->un.e.member[k].label.name, AudioNinput "-vr50", MAX_AUDIO_DEV_LEN); } else if (j == 2) { bits = (1 << CORB_PWC_VREF_80); strlcpy(d->un.e.member[k].label.name, AudioNinput "-vr80", MAX_AUDIO_DEV_LEN); } else if (j == 3) { bits = (1 << CORB_PWC_VREF_100); strlcpy(d->un.e.member[k].label.name, AudioNinput "-vr100", MAX_AUDIO_DEV_LEN); } if ((COP_PINCAP_VREF(w->d.pin.cap) & bits) == bits) { d->un.e.member[k].ord = j + 3; k++; } } } d->un.e.num_mem = k; this->nmixers++; } /* pin headphone-boost */ if (w->type == COP_AWTYPE_PIN_COMPLEX && w->d.pin.cap & COP_PINCAP_HEADPHONE && w->nid != this->mic) { MIXER_REG_PROLOG; snprintf(d->label.name, sizeof(d->label.name), "%s_boost", w->name); d->mixer_class = AZ_CLASS_OUTPUT; m->target = MI_TARGET_PINBOOST; azalia_devinfo_offon(d); this->nmixers++; } if (w->type == COP_AWTYPE_PIN_COMPLEX && w->d.pin.cap & COP_PINCAP_EAPD) { MIXER_REG_PROLOG; snprintf(d->label.name, sizeof(d->label.name), "%s_eapd", w->name); d->mixer_class = AZ_CLASS_OUTPUT; m->target = MI_TARGET_EAPD; azalia_devinfo_offon(d); this->nmixers++; } } /* sense pins */ for (i = 0; i < this->nsense_pins; i++) { if (!azalia_widget_enabled(this, this->sense_pins[i])) { DPRINTF(("%s: sense pin %2.2x not found\n", __func__, this->sense_pins[i])); continue; } MIXER_REG_PROLOG; m->nid = this->w[this->sense_pins[i]].nid; snprintf(d->label.name, sizeof(d->label.name), "%s_sense", this->w[this->sense_pins[i]].name); d->type = AUDIO_MIXER_ENUM; d->mixer_class = AZ_CLASS_OUTPUT; m->target = MI_TARGET_PINSENSE; d->un.e.num_mem = 2; d->un.e.member[0].ord = 0; strlcpy(d->un.e.member[0].label.name, "unplugged", MAX_AUDIO_DEV_LEN); d->un.e.member[1].ord = 1; strlcpy(d->un.e.member[1].label.name, "plugged", MAX_AUDIO_DEV_LEN); this->nmixers++; } /* spkr mute by jack sense */ this->spkr_mute_method = AZ_SPKR_MUTE_NONE; if (this->speaker != -1 && this->spkr_dac != -1 && this->nsense_pins > 0) { w = &this->w[this->speaker]; if ((w->widgetcap & COP_AWCAP_OUTAMP) && (w->outamp_cap & COP_AMPCAP_MUTE)) this->spkr_mute_method = AZ_SPKR_MUTE_SPKR_MUTE; else if ((w->d.pin.cap & COP_PINCAP_OUTPUT) && (w->d.pin.cap & COP_PINCAP_INPUT)) this->spkr_mute_method = AZ_SPKR_MUTE_SPKR_DIR; else { w = &this->w[this->spkr_dac]; if (w->nid != this->dacs.groups[0].conv[0] && (w->widgetcap & COP_AWCAP_OUTAMP) && (w->outamp_cap & COP_AMPCAP_MUTE)) this->spkr_mute_method = AZ_SPKR_MUTE_DAC_MUTE; } } if (this->spkr_mute_method != AZ_SPKR_MUTE_NONE) { w = &this->w[this->speaker]; MIXER_REG_PROLOG; m->nid = w->nid; snprintf(d->label.name, sizeof(d->label.name), "%s_muters", w->name); m->target = MI_TARGET_SENSESET; d->type = AUDIO_MIXER_SET; d->mixer_class = AZ_CLASS_OUTPUT; this->spkr_muters = 0; for (i = 0, j = 0; i < this->nsense_pins; i++) { ww = &this->w[this->sense_pins[i]]; if (!(ww->d.pin.cap & COP_PINCAP_OUTPUT)) continue; if (!(ww->widgetcap & COP_AWCAP_UNSOL)) continue; d->un.s.member[j].mask = 1 << i; this->spkr_muters |= (1 << i); strlcpy(d->un.s.member[j++].label.name, ww->name, MAX_AUDIO_DEV_LEN); } d->un.s.num_mem = j; if (j != 0) this->nmixers++; } /* playback volume group */ if (this->playvols.nslaves > 0) { mixer_devinfo_t *d; err = azalia_mixer_ensure_capacity(this, this->nmixers + 3); /* volume */ m = &this->mixers[this->nmixers]; m->nid = this->playvols.master; m->target = MI_TARGET_PLAYVOL; d = &m->devinfo; d->mixer_class = AZ_CLASS_OUTPUT; snprintf(d->label.name, sizeof(d->label.name), "%s", AudioNmaster); d->type = AUDIO_MIXER_VALUE; d->un.v.num_channels = 2; d->un.v.delta = 8; this->nmixers++; d->next = this->nmixers; /* mute */ m = &this->mixers[this->nmixers]; m->nid = this->playvols.master; m->target = MI_TARGET_PLAYVOL; d = &m->devinfo; d->prev = this->nmixers - 1; d->mixer_class = AZ_CLASS_OUTPUT; snprintf(d->label.name, sizeof(d->label.name), "%s", AudioNmute); azalia_devinfo_offon(d); this->nmixers++; d->next = this->nmixers; /* slaves */ m = &this->mixers[this->nmixers]; m->nid = this->playvols.master; m->target = MI_TARGET_PLAYVOL; d = &m->devinfo; d->prev = this->nmixers - 1; d->mixer_class = AZ_CLASS_OUTPUT; snprintf(d->label.name, sizeof(d->label.name), "%s", "slaves"); d->type = AUDIO_MIXER_SET; for (i = 0, j = 0; i < this->playvols.nslaves; i++) { ww = &this->w[this->playvols.slaves[i]]; d->un.s.member[j].mask = (1 << i); strlcpy(d->un.s.member[j++].label.name, ww->name, MAX_AUDIO_DEV_LEN); } d->un.s.num_mem = j; this->nmixers++; } /* recording volume group */ if (this->recvols.nslaves > 0) { mixer_devinfo_t *d; err = azalia_mixer_ensure_capacity(this, this->nmixers + 3); /* volume */ m = &this->mixers[this->nmixers]; m->nid = this->recvols.master; m->target = MI_TARGET_RECVOL; d = &m->devinfo; d->mixer_class = AZ_CLASS_RECORD; snprintf(d->label.name, sizeof(d->label.name), "%s", AudioNvolume); d->type = AUDIO_MIXER_VALUE; d->un.v.num_channels = 2; d->un.v.delta = 8; this->nmixers++; d->next = this->nmixers; /* mute */ m = &this->mixers[this->nmixers]; m->nid = this->recvols.master; m->target = MI_TARGET_RECVOL; d = &m->devinfo; d->prev = this->nmixers - 1; d->mixer_class = AZ_CLASS_RECORD; snprintf(d->label.name, sizeof(d->label.name), "%s", AudioNmute); azalia_devinfo_offon(d); this->nmixers++; d->next = this->nmixers; /* slaves */ m = &this->mixers[this->nmixers]; m->nid = this->recvols.master; m->target = MI_TARGET_RECVOL; d = &m->devinfo; d->prev = this->nmixers - 1; d->mixer_class = AZ_CLASS_RECORD; snprintf(d->label.name, sizeof(d->label.name), "%s", "slaves"); d->type = AUDIO_MIXER_SET; for (i = 0, j = 0; i < this->recvols.nslaves; i++) { ww = &this->w[this->recvols.slaves[i]]; d->un.s.member[j].mask = (1 << i); strlcpy(d->un.s.member[j++].label.name, ww->name, MAX_AUDIO_DEV_LEN); } d->un.s.num_mem = j; this->nmixers++; } /* if the codec has more than one DAC group, the first is analog * and the second is digital. */ if (this->dacs.ngroups > 1) { MIXER_REG_PROLOG; strlcpy(d->label.name, AudioNmode, sizeof(d->label.name)); d->type = AUDIO_MIXER_ENUM; d->mixer_class = AZ_CLASS_OUTPUT; m->target = MI_TARGET_DAC; m->nid = this->audiofunc; d->un.e.member[0].ord = 0; strlcpy(d->un.e.member[0].label.name, "analog", MAX_AUDIO_DEV_LEN); d->un.e.member[1].ord = 1; strlcpy(d->un.e.member[1].label.name, "digital", MAX_AUDIO_DEV_LEN); d->un.e.num_mem = 2; this->nmixers++; } /* if the codec has more than one ADC group, the first is analog * and the second is digital. */ if (this->adcs.ngroups > 1) { MIXER_REG_PROLOG; strlcpy(d->label.name, AudioNmode, sizeof(d->label.name)); d->type = AUDIO_MIXER_ENUM; d->mixer_class = AZ_CLASS_RECORD; m->target = MI_TARGET_ADC; m->nid = this->audiofunc; d->un.e.member[0].ord = 0; strlcpy(d->un.e.member[0].label.name, "analog", MAX_AUDIO_DEV_LEN); d->un.e.member[1].ord = 1; strlcpy(d->un.e.member[1].label.name, "digital", MAX_AUDIO_DEV_LEN); d->un.e.num_mem = 2; this->nmixers++; } azalia_mixer_fix_indexes(this); azalia_mixer_default(this); return 0; } void azalia_devinfo_offon(mixer_devinfo_t *d) { d->type = AUDIO_MIXER_ENUM; 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); } 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 = mallocarray(newmax, sizeof(mixer_item_t), M_DEVBUF, M_NOWAIT | M_ZERO); if (newbuf == NULL) { printf("%s: out of memory in %s\n", XNAME(this), __func__); return ENOMEM; } bcopy(this->mixers, newbuf, this->maxmixers * sizeof(mixer_item_t)); free(this->mixers, M_DEVBUF, this->maxmixers * sizeof(mixer_item_t)); this->mixers = newbuf; this->maxmixers = newmax; return 0; } int azalia_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) printf("%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; } int azalia_mixer_default(codec_t *this) { widget_t *w; mixer_item_t *m; mixer_ctrl_t mc; int i, j, tgt, cap, err; /* unmute all */ for (i = 0; i < this->nmixers; i++) { 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; bzero(&mc, sizeof(mc)); mc.dev = i; mc.type = AUDIO_MIXER_ENUM; azalia_mixer_set(this, m->nid, m->target, &mc); } /* set unextreme volume */ for (i = 0; i < this->nmixers; i++) { m = &this->mixers[i]; if (!IS_MI_TARGET_INAMP(m->target) && m->target != MI_TARGET_OUTAMP) continue; if (m->devinfo.type != AUDIO_MIXER_VALUE) continue; bzero(&mc, sizeof(mc)); mc.dev = i; mc.type = AUDIO_MIXER_VALUE; mc.un.value.num_channels = 1; mc.un.value.level[0] = AUDIO_MAX_GAIN / 2; if (WIDGET_CHANNELS(&this->w[m->nid]) == 2) { mc.un.value.num_channels = 2; mc.un.value.level[1] = mc.un.value.level[0]; } azalia_mixer_set(this, m->nid, m->target, &mc); } /* unmute all */ for (i = 0; i < this->nmixers; i++) { m = &this->mixers[i]; if (m->target != MI_TARGET_MUTESET) continue; if (m->devinfo.type != AUDIO_MIXER_SET) continue; bzero(&mc, sizeof(mc)); mc.dev = i; mc.type = AUDIO_MIXER_SET; if (!azalia_widget_enabled(this, m->nid)) { DPRINTF(("%s: invalid set nid\n", __func__)); return EINVAL; } w = &this->w[m->nid]; for (j = 0; j < w->nconnections; j++) { if (!azalia_widget_enabled(this, w->connections[j])) continue; if (w->nid == this->input_mixer && w->connections[j] == this->mic) continue; mc.un.mask |= 1 << j; } azalia_mixer_set(this, m->nid, m->target, &mc); } /* make sure default connection is valid */ for (i = 0; i < this->nmixers; i++) { m = &this->mixers[i]; if (m->target != MI_TARGET_CONNLIST) continue; azalia_mixer_get(this, m->nid, m->target, &mc); for (j = 0; j < m->devinfo.un.e.num_mem; j++) { if (mc.un.ord == m->devinfo.un.e.member[j].ord) break; } if (j >= m->devinfo.un.e.num_mem) { bzero(&mc, sizeof(mc)); mc.dev = i; mc.type = AUDIO_MIXER_ENUM; mc.un.ord = m->devinfo.un.e.member[0].ord; } azalia_mixer_set(this, m->nid, m->target, &mc); } /* get default value for play group master */ for (i = 0; i < this->playvols.nslaves; i++) { if (!(this->playvols.cur & (1 << i))) continue; w = &this->w[this->playvols.slaves[i]]; if (!(COP_AMPCAP_NUMSTEPS(w->outamp_cap))) continue; mc.type = AUDIO_MIXER_VALUE; tgt = MI_TARGET_OUTAMP; azalia_mixer_get(this, w->nid, tgt, &mc); this->playvols.vol_l = mc.un.value.level[0]; this->playvols.vol_r = mc.un.value.level[0]; break; } this->playvols.mute = 0; /* get default value for record group master */ for (i = 0; i < this->recvols.nslaves; i++) { if (!(this->recvols.cur & (1 << i))) continue; w = &this->w[this->recvols.slaves[i]]; mc.type = AUDIO_MIXER_VALUE; tgt = MI_TARGET_OUTAMP; cap = w->outamp_cap; if (w->type == COP_AWTYPE_PIN_COMPLEX || w->type == COP_AWTYPE_AUDIO_INPUT) { tgt = 0; cap = w->inamp_cap; } if (!(COP_AMPCAP_NUMSTEPS(cap))) continue; azalia_mixer_get(this, w->nid, tgt, &mc); this->recvols.vol_l = mc.un.value.level[0]; this->recvols.vol_r = mc.un.value.level[0]; break; } this->recvols.mute = 0; err = azalia_codec_enable_unsol(this); if (err) return(err); return 0; } int azalia_codec_enable_unsol(codec_t *this) { widget_t *w; uint32_t result; int i, err; /* jack sense */ for (i = 0; i < this->nsense_pins; i++) { if (this->spkr_muters & (1 << i)) { azalia_comresp(this, this->sense_pins[i], CORB_SET_UNSOLICITED_RESPONSE, CORB_UNSOL_ENABLE | AZ_TAG_SPKR, NULL); } } if (this->spkr_muters != 0) azalia_unsol_event(this, AZ_TAG_SPKR); /* volume knob */ if (this->playvols.master != this->audiofunc) { w = &this->w[this->playvols.master]; err = azalia_comresp(this, w->nid, CORB_GET_VOLUME_KNOB, 0, &result); if (err) { DPRINTF(("%s: get volume knob error\n", __func__)); return err; } /* current level */ this->playvols.hw_step = CORB_VKNOB_VOLUME(result); this->playvols.hw_nsteps = COP_VKCAP_NUMSTEPS(w->d.volume.cap); /* indirect mode */ result &= ~(CORB_VKNOB_DIRECT); err = azalia_comresp(this, w->nid, CORB_SET_VOLUME_KNOB, result, NULL); if (err) { DPRINTF(("%s: set volume knob error\n", __func__)); /* XXX If there was an error setting indirect * mode, do not return an error. However, do not * enable unsolicited responses either. Most * likely the volume knob doesn't work right. * Perhaps it's simply not wired/enabled. */ return 0; } /* enable unsolicited responses */ result = CORB_UNSOL_ENABLE | AZ_TAG_PLAYVOL; err = azalia_comresp(this, w->nid, CORB_SET_UNSOLICITED_RESPONSE, result, NULL); if (err) { DPRINTF(("%s: set vknob unsol resp error\n", __func__)); return err; } } return 0; } int azalia_mixer_delete(codec_t *this) { if (this->mixers != NULL) { free(this->mixers, M_DEVBUF, 0); this->mixers = NULL; } return 0; } /** * @param mc mc->type must be set by the caller before the call */ int azalia_mixer_get(const codec_t *this, nid_t nid, int target, mixer_ctrl_t *mc) { uint32_t result, cap, value; nid_t n; int i, err; if (mc->type == AUDIO_MIXER_CLASS) { return(0); } /* inamp mute */ else if (IS_MI_TARGET_INAMP(target) && mc->type == AUDIO_MIXER_ENUM) { err = azalia_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 = azalia_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] = azalia_mixer_from_device_value(this, nid, target, CORB_GAGM_GAIN(result)); if (this->w[nid].type == COP_AWTYPE_AUDIO_SELECTOR || this->w[nid].type == COP_AWTYPE_AUDIO_MIXER) { n = this->w[nid].connections[MI_TARGET_INAMP(target)]; if (!azalia_widget_enabled(this, n)) { DPRINTF(("%s: nid %2.2x invalid index %d\n", __func__, nid, MI_TARGET_INAMP(target))); n = nid; } } else n = nid; mc->un.value.num_channels = WIDGET_CHANNELS(&this->w[n]); if (mc->un.value.num_channels == 2) { err = azalia_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] = azalia_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 = azalia_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 = azalia_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] = azalia_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 = azalia_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] = azalia_mixer_from_device_value (this, nid, target, CORB_GAGM_GAIN(result)); } } /* selection */ else if (target == MI_TARGET_CONNLIST) { err = azalia_comresp(this, nid, CORB_GET_CONNECTION_SELECT_CONTROL, 0, &result); if (err) return err; result = CORB_CSC_INDEX(result); if (!azalia_widget_enabled(this, this->w[nid].connections[result])) mc->un.ord = -1; else mc->un.ord = result; } /* pin I/O */ else if (target == MI_TARGET_PINDIR) { err = azalia_comresp(this, nid, CORB_GET_PIN_WIDGET_CONTROL, 0, &result); if (err) return err; value = result; if (!(result & (CORB_PWC_INPUT | CORB_PWC_OUTPUT))) mc->un.ord = 0; else if (result & CORB_PWC_OUTPUT) mc->un.ord = 1; else { cap = COP_PINCAP_VREF(this->w[nid].d.pin.cap); result &= CORB_PWC_VREF_MASK; if (result == CORB_PWC_VREF_GND) mc->un.ord = 3; else if (result == CORB_PWC_VREF_50) mc->un.ord = 4; else if (result == CORB_PWC_VREF_80) mc->un.ord = 5; else if (result == CORB_PWC_VREF_100) mc->un.ord = 6; else mc->un.ord = 2; } } /* pin headphone-boost */ else if (target == MI_TARGET_PINBOOST) { err = azalia_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->dacs.cur; } /* ADC selection */ else if (target == MI_TARGET_ADC) { mc->un.ord = this->adcs.cur; } /* S/PDIF */ else if (target == MI_TARGET_SPDIF) { err = azalia_comresp(this, nid, CORB_GET_DIGITAL_CONTROL, 0, &result); if (err) return err; mc->un.mask = result & 0xff & ~(CORB_DCC_DIGEN | CORB_DCC_NAUDIO); } else if (target == MI_TARGET_SPDIF_CC) { err = azalia_comresp(this, nid, CORB_GET_DIGITAL_CONTROL, 0, &result); if (err) return err; mc->un.value.num_channels = 1; mc->un.value.level[0] = CORB_DCC_CC(result); } /* EAPD */ else if (target == MI_TARGET_EAPD) { err = azalia_comresp(this, nid, CORB_GET_EAPD_BTL_ENABLE, 0, &result); if (err) return err; mc->un.ord = result & CORB_EAPD_EAPD ? 1 : 0; } /* sense pin */ else if (target == MI_TARGET_PINSENSE) { err = azalia_comresp(this, nid, CORB_GET_PIN_SENSE, 0, &result); if (err) return err; mc->un.ord = result & CORB_PS_PRESENCE ? 1 : 0; } /* mute set */ else if (target == MI_TARGET_MUTESET && mc->type == AUDIO_MIXER_SET) { const widget_t *w; if (!azalia_widget_enabled(this, nid)) { DPRINTF(("%s: invalid muteset nid\n", XNAME(this))); return EINVAL; } w = &this->w[nid]; mc->un.mask = 0; for (i = 0; i < w->nconnections; i++) { if (!azalia_widget_enabled(this, w->connections[i])) continue; err = azalia_comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_INPUT | CORB_GAGM_LEFT | MI_TARGET_INAMP(i), &result); if (err) return err; mc->un.mask |= (result & CORB_GAGM_MUTE) ? 0 : (1 << i); } } /* mixer set - show all connections */ else if (target == MI_TARGET_MIXERSET && mc->type == AUDIO_MIXER_SET) { const widget_t *w; if (!azalia_widget_enabled(this, nid)) { DPRINTF(("%s: invalid mixerset nid\n", XNAME(this))); return EINVAL; } w = &this->w[nid]; mc->un.mask = 0; for (i = 0; i < w->nconnections; i++) { if (!azalia_widget_enabled(this, w->connections[i])) continue; mc->un.mask |= (1 << i); } } else if (target == MI_TARGET_SENSESET && mc->type == AUDIO_MIXER_SET) { if (nid == this->speaker) { mc->un.mask = this->spkr_muters; } else { DPRINTF(("%s: invalid senseset nid\n", XNAME(this))); return EINVAL; } } else if (target == MI_TARGET_PLAYVOL) { if (mc->type == AUDIO_MIXER_VALUE) { mc->un.value.num_channels = 2; mc->un.value.level[0] = this->playvols.vol_l; mc->un.value.level[1] = this->playvols.vol_r; } else if (mc->type == AUDIO_MIXER_ENUM) { mc->un.ord = this->playvols.mute; } else if (mc->type == AUDIO_MIXER_SET) { mc->un.mask = this->playvols.cur; } else { DPRINTF(("%s: invalid outmaster mixer type\n", XNAME(this))); return EINVAL; } } else if (target == MI_TARGET_RECVOL) { if (mc->type == AUDIO_MIXER_VALUE) { mc->un.value.num_channels = 2; mc->un.value.level[0] = this->recvols.vol_l; mc->un.value.level[1] = this->recvols.vol_r; } else if (mc->type == AUDIO_MIXER_ENUM) { mc->un.ord = this->recvols.mute; } else if (mc->type == AUDIO_MIXER_SET) { mc->un.mask = this->recvols.cur; } else { DPRINTF(("%s: invalid inmaster mixer type\n", XNAME(this))); return EINVAL; } } else { DPRINTF(("%s: internal error in %s: target=%x\n", XNAME(this), __func__, target)); return -1; } return 0; } int azalia_mixer_set(codec_t *this, nid_t nid, int target, const mixer_ctrl_t *mc) { uint32_t result, value; int i, err; if (mc->type == AUDIO_MIXER_CLASS) { return(0); } /* inamp mute */ else if (IS_MI_TARGET_INAMP(target) && mc->type == AUDIO_MIXER_ENUM) { /* set stereo mute separately to keep each gain value */ err = azalia_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 = azalia_comresp(this, nid, CORB_SET_AMPLIFIER_GAIN_MUTE, value, &result); if (err) return err; if (WIDGET_CHANNELS(&this->w[nid]) == 2) { err = azalia_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 = azalia_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; err = azalia_comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_INPUT | CORB_GAGM_LEFT | MI_TARGET_INAMP(target), &result); if (err) return err; value = azalia_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 = azalia_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) { err = azalia_comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_INPUT | CORB_GAGM_RIGHT | MI_TARGET_INAMP(target), &result); if (err) return err; value = azalia_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 = azalia_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 = azalia_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 = azalia_comresp(this, nid, CORB_SET_AMPLIFIER_GAIN_MUTE, value, &result); if (err) return err; if (WIDGET_CHANNELS(&this->w[nid]) == 2) { err = azalia_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 = azalia_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; err = azalia_comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_OUTPUT | CORB_GAGM_LEFT, &result); if (err) return err; value = azalia_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 = azalia_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) { err = azalia_comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_OUTPUT | CORB_GAGM_RIGHT, &result); if (err) return err; value = azalia_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 = azalia_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 || !azalia_widget_enabled(this, this->w[nid].connections[mc->un.ord])) return EINVAL; err = azalia_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) { err = azalia_comresp(this, nid, CORB_GET_PIN_WIDGET_CONTROL, 0, &result); if (err) return err; value = result; value &= ~(CORB_PWC_VREF_MASK); if (mc->un.ord == 0) { value &= ~(CORB_PWC_OUTPUT | CORB_PWC_INPUT); } else if (mc->un.ord == 1) { value &= ~CORB_PWC_INPUT; value |= CORB_PWC_OUTPUT; if (this->qrks & AZ_QRK_WID_OVREF50) value |= CORB_PWC_VREF_50; } else { value &= ~CORB_PWC_OUTPUT; value |= CORB_PWC_INPUT; if (mc->un.ord == 3) value |= CORB_PWC_VREF_GND; if (mc->un.ord == 4) value |= CORB_PWC_VREF_50; if (mc->un.ord == 5) value |= CORB_PWC_VREF_80; if (mc->un.ord == 6) value |= CORB_PWC_VREF_100; } err = azalia_comresp(this, nid, CORB_SET_PIN_WIDGET_CONTROL, value, &result); if (err) return err; /* Run the unsolicited response handler for speaker mute * since it depends on pin direction. */ for (i = 0; i < this->nsense_pins; i++) { if (this->sense_pins[i] == nid) break; } if (i < this->nsense_pins) { azalia_unsol_event(this, AZ_TAG_SPKR); } } /* pin headphone-boost */ else if (target == MI_TARGET_PINBOOST) { if (mc->un.ord >= 2) return EINVAL; err = azalia_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 = azalia_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->dacs.ngroups) return EINVAL; if (mc->un.ord != this->dacs.cur) return azalia_codec_construct_format(this, mc->un.ord, this->adcs.cur); else return 0; } /* ADC selection */ else if (target == MI_TARGET_ADC) { if (this->running) return EBUSY; if (mc->un.ord >= this->adcs.ngroups) return EINVAL; if (mc->un.ord != this->adcs.cur) return azalia_codec_construct_format(this, this->dacs.cur, mc->un.ord); else return 0; } /* S/PDIF */ else if (target == MI_TARGET_SPDIF) { err = azalia_comresp(this, nid, CORB_GET_DIGITAL_CONTROL, 0, &result); result &= CORB_DCC_DIGEN | CORB_DCC_NAUDIO; result |= mc->un.mask & 0xff & ~CORB_DCC_DIGEN; err = azalia_comresp(this, nid, CORB_SET_DIGITAL_CONTROL_L, result, NULL); if (err) return err; } else if (target == MI_TARGET_SPDIF_CC) { if (mc->un.value.num_channels != 1) return EINVAL; if (mc->un.value.level[0] > 127) return EINVAL; err = azalia_comresp(this, nid, CORB_SET_DIGITAL_CONTROL_H, mc->un.value.level[0], NULL); if (err) return err; } /* EAPD */ else if (target == MI_TARGET_EAPD) { if (mc->un.ord >= 2) return EINVAL; err = azalia_comresp(this, nid, CORB_GET_EAPD_BTL_ENABLE, 0, &result); if (err) return err; result &= 0xff; if (mc->un.ord == 0) { result &= ~CORB_EAPD_EAPD; } else { result |= CORB_EAPD_EAPD; } err = azalia_comresp(this, nid, CORB_SET_EAPD_BTL_ENABLE, result, &result); if (err) return err; } else if (target == MI_TARGET_PINSENSE) { /* do nothing, control is read only */ } else if (target == MI_TARGET_MUTESET && mc->type == AUDIO_MIXER_SET) { const widget_t *w; if (!azalia_widget_enabled(this, nid)) { DPRINTF(("%s: invalid muteset nid\n", XNAME(this))); return EINVAL; } w = &this->w[nid]; for (i = 0; i < w->nconnections; i++) { if (!azalia_widget_enabled(this, w->connections[i])) continue; /* We have to set stereo mute separately * to keep each gain value. */ err = azalia_comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_INPUT | CORB_GAGM_LEFT | MI_TARGET_INAMP(i), &result); if (err) return err; value = CORB_AGM_INPUT | CORB_AGM_LEFT | (i << CORB_AGM_INDEX_SHIFT) | CORB_GAGM_GAIN(result); if ((mc->un.mask & (1 << i)) == 0) value |= CORB_AGM_MUTE; err = azalia_comresp(this, nid, CORB_SET_AMPLIFIER_GAIN_MUTE, value, &result); if (err) return err; if (WIDGET_CHANNELS(w) == 2) { err = azalia_comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_INPUT | CORB_GAGM_RIGHT | MI_TARGET_INAMP(i), &result); if (err) return err; value = CORB_AGM_INPUT | CORB_AGM_RIGHT | (i << CORB_AGM_INDEX_SHIFT) | CORB_GAGM_GAIN(result); if ((mc->un.mask & (1 << i)) == 0) value |= CORB_AGM_MUTE; err = azalia_comresp(this, nid, CORB_SET_AMPLIFIER_GAIN_MUTE, value, &result); if (err) return err; } } } else if (target == MI_TARGET_MIXERSET && mc->type == AUDIO_MIXER_SET) { /* do nothing, control is read only */ } else if (target == MI_TARGET_SENSESET && mc->type == AUDIO_MIXER_SET) { if (nid == this->speaker) { this->spkr_muters = mc->un.mask; azalia_unsol_event(this, AZ_TAG_SPKR); } else { DPRINTF(("%s: invalid senseset nid\n", XNAME(this))); return EINVAL; } } else if (target == MI_TARGET_PLAYVOL) { const widget_t *w; mixer_ctrl_t mc2; if (mc->type == AUDIO_MIXER_VALUE) { if (mc->un.value.num_channels != 2) return EINVAL; this->playvols.vol_l = mc->un.value.level[0]; this->playvols.vol_r = mc->un.value.level[1]; for (i = 0; i < this->playvols.nslaves; i++) { if (!(this->playvols.cur & (1 << i))) continue; w = &this->w[this->playvols.slaves[i]]; if (!(COP_AMPCAP_NUMSTEPS(w->outamp_cap))) continue; /* don't change volume if muted */ if (w->outamp_cap & COP_AMPCAP_MUTE) { mc2.type = AUDIO_MIXER_ENUM; azalia_mixer_get(this, w->nid, MI_TARGET_OUTAMP, &mc2); if (mc2.un.ord) continue; } mc2.type = AUDIO_MIXER_VALUE; mc2.un.value.num_channels = WIDGET_CHANNELS(w); mc2.un.value.level[0] = this->playvols.vol_l; mc2.un.value.level[1] = this->playvols.vol_r; err = azalia_mixer_set(this, w->nid, MI_TARGET_OUTAMP, &mc2); if (err) { DPRINTF(("%s: out slave %2.2x vol\n", __func__, w->nid)); return err; } } } else if (mc->type == AUDIO_MIXER_ENUM) { if (mc->un.ord != 0 && mc->un.ord != 1) return EINVAL; this->playvols.mute = mc->un.ord; for (i = 0; i < this->playvols.nslaves; i++) { if (!(this->playvols.cur & (1 << i))) continue; w = &this->w[this->playvols.slaves[i]]; if (!(w->outamp_cap & COP_AMPCAP_MUTE)) continue; if (this->spkr_muted == 1 && ((this->spkr_mute_method == AZ_SPKR_MUTE_SPKR_MUTE && (w->nid == this->speaker || w->nid == this->speaker2)) || (this->spkr_mute_method == AZ_SPKR_MUTE_DAC_MUTE && w->nid == this->spkr_dac))) { continue; } mc2.type = AUDIO_MIXER_ENUM; mc2.un.ord = this->playvols.mute; err = azalia_mixer_set(this, w->nid, MI_TARGET_OUTAMP, &mc2); if (err) { DPRINTF(("%s: out slave %2.2x mute\n", __func__, w->nid)); return err; } } } else if (mc->type == AUDIO_MIXER_SET) { this->playvols.cur = (mc->un.mask & this->playvols.mask); } else { DPRINTF(("%s: invalid output master mixer type\n", XNAME(this))); return EINVAL; } } else if (target == MI_TARGET_RECVOL) { const widget_t *w; mixer_ctrl_t mc2; uint32_t cap; int tgt; if (mc->type == AUDIO_MIXER_VALUE) { if (mc->un.value.num_channels != 2) return EINVAL; this->recvols.vol_l = mc->un.value.level[0]; this->recvols.vol_r = mc->un.value.level[1]; for (i = 0; i < this->recvols.nslaves; i++) { if (!(this->recvols.cur & (1 << i))) continue; w = &this->w[this->recvols.slaves[i]]; tgt = MI_TARGET_OUTAMP; cap = w->outamp_cap; if (w->type == COP_AWTYPE_AUDIO_INPUT || w->type == COP_AWTYPE_PIN_COMPLEX) { tgt = 0; cap = w->inamp_cap; } if (!(COP_AMPCAP_NUMSTEPS(cap))) continue; mc2.type = AUDIO_MIXER_VALUE; mc2.un.value.num_channels = WIDGET_CHANNELS(w); mc2.un.value.level[0] = this->recvols.vol_l; mc2.un.value.level[1] = this->recvols.vol_r; err = azalia_mixer_set(this, w->nid, tgt, &mc2); if (err) { DPRINTF(("%s: in slave %2.2x vol\n", __func__, w->nid)); return err; } } } else if (mc->type == AUDIO_MIXER_ENUM) { if (mc->un.ord != 0 && mc->un.ord != 1) return EINVAL; this->recvols.mute = mc->un.ord; for (i = 0; i < this->recvols.nslaves; i++) { if (!(this->recvols.cur & (1 << i))) continue; w = &this->w[this->recvols.slaves[i]]; tgt = MI_TARGET_OUTAMP; cap = w->outamp_cap; if (w->type == COP_AWTYPE_AUDIO_INPUT || w->type == COP_AWTYPE_PIN_COMPLEX) { tgt = 0; cap = w->inamp_cap; } if (!(cap & COP_AMPCAP_MUTE)) continue; mc2.type = AUDIO_MIXER_ENUM; mc2.un.ord = this->recvols.mute; err = azalia_mixer_set(this, w->nid, tgt, &mc2); if (err) { DPRINTF(("%s: out slave %2.2x mute\n", __func__, w->nid)); return err; } } } else if (mc->type == AUDIO_MIXER_SET) { this->recvols.cur = (mc->un.mask & this->recvols.mask); } else { DPRINTF(("%s: invalid input master mixer type\n", XNAME(this))); return EINVAL; } } else { DPRINTF(("%s: internal error in %s: target=%x\n", XNAME(this), __func__, target)); return -1; } return 0; } u_char azalia_mixer_from_device_value(const codec_t *this, nid_t nid, int target, uint32_t dv) { uint32_t steps; int max_gain, ctloff; if (IS_MI_TARGET_INAMP(target)) { steps = COP_AMPCAP_NUMSTEPS(this->w[nid].inamp_cap); ctloff = COP_AMPCAP_CTLOFF(this->w[nid].inamp_cap); } else if (target == MI_TARGET_OUTAMP) { steps = COP_AMPCAP_NUMSTEPS(this->w[nid].outamp_cap); ctloff = COP_AMPCAP_CTLOFF(this->w[nid].outamp_cap); } else { DPRINTF(("%s: unknown target: %d\n", __func__, target)); steps = 255; ctloff = 0; } dv -= ctloff; if (dv <= 0 || steps == 0) return(AUDIO_MIN_GAIN); max_gain = AUDIO_MAX_GAIN - AUDIO_MAX_GAIN % steps; if (dv >= steps) return(max_gain); return(dv * max_gain / steps); } uint32_t azalia_mixer_to_device_value(const codec_t *this, nid_t nid, int target, u_char uv) { uint32_t steps; int max_gain, ctloff; if (IS_MI_TARGET_INAMP(target)) { steps = COP_AMPCAP_NUMSTEPS(this->w[nid].inamp_cap); ctloff = COP_AMPCAP_CTLOFF(this->w[nid].inamp_cap); } else if (target == MI_TARGET_OUTAMP) { steps = COP_AMPCAP_NUMSTEPS(this->w[nid].outamp_cap); ctloff = COP_AMPCAP_CTLOFF(this->w[nid].outamp_cap); } else { DPRINTF(("%s: unknown target: %d\n", __func__, target)); steps = 255; ctloff = 0; } if (uv <= AUDIO_MIN_GAIN || steps == 0) return(ctloff); max_gain = AUDIO_MAX_GAIN - AUDIO_MAX_GAIN % steps; if (uv >= max_gain) return(steps + ctloff); return(uv * steps / max_gain + ctloff); } int azalia_gpio_unmute(codec_t *this, int pin) { uint32_t data, mask, dir; azalia_comresp(this, this->audiofunc, CORB_GET_GPIO_DATA, 0, &data); azalia_comresp(this, this->audiofunc, CORB_GET_GPIO_ENABLE_MASK, 0, &mask); azalia_comresp(this, this->audiofunc, CORB_GET_GPIO_DIRECTION, 0, &dir); data |= 1 << pin; mask |= 1 << pin; dir |= 1 << pin; azalia_comresp(this, this->audiofunc, CORB_SET_GPIO_ENABLE_MASK, mask, NULL); azalia_comresp(this, this->audiofunc, CORB_SET_GPIO_DIRECTION, dir, NULL); DELAY(1000); azalia_comresp(this, this->audiofunc, CORB_SET_GPIO_DATA, data, NULL); return 0; } void azalia_ampcap_ov(widget_t *w, int type, int offset, int steps, int size, int ctloff, int mute) { uint32_t cap; cap = (offset & 0x7f) | ((steps & 0x7f) << 8) | ((size & 0x7f) << 16) | ((ctloff & 0x7f) << 24) | (mute ? COP_AMPCAP_MUTE : 0); if (type == COP_OUTPUT_AMPCAP) { w->outamp_cap = cap; } else if (type == COP_INPUT_AMPCAP) { w->inamp_cap = cap; } } void azalia_pin_config_ov(widget_t *w, int mask, int val) { int bits, offset; switch (mask) { case CORB_CD_DEVICE_MASK: bits = CORB_CD_DEVICE_BITS; offset = CORB_CD_DEVICE_OFFSET; break; case CORB_CD_PORT_MASK: bits = CORB_CD_PORT_BITS; offset = CORB_CD_PORT_OFFSET; break; default: return; } val &= bits; w->d.pin.config &= ~(mask); w->d.pin.config |= val << offset; if (mask == CORB_CD_DEVICE_MASK) w->d.pin.device = val; } int azalia_codec_gpio_quirks(codec_t *this) { if (this->qrks & AZ_QRK_GPIO_POL_0) { azalia_comresp(this, this->audiofunc, CORB_SET_GPIO_POLARITY, 0, NULL); } if (this->qrks & AZ_QRK_GPIO_UNMUTE_0) { azalia_gpio_unmute(this, 0); } if (this->qrks & AZ_QRK_GPIO_UNMUTE_1) { azalia_gpio_unmute(this, 1); } if (this->qrks & AZ_QRK_GPIO_UNMUTE_2) { azalia_gpio_unmute(this, 2); } if (this->qrks & AZ_QRK_GPIO_UNMUTE_3) { azalia_gpio_unmute(this, 3); } return(0); } int azalia_codec_widget_quirks(codec_t *this, nid_t nid) { widget_t *w; w = &this->w[nid]; if (this->qrks & AZ_QRK_WID_BEEP_1D && nid == 0x1d && w->enable == 0) { azalia_pin_config_ov(w, CORB_CD_DEVICE_MASK, CORB_CD_BEEP); azalia_pin_config_ov(w, CORB_CD_PORT_MASK, CORB_CD_FIXED); w->widgetcap |= COP_AWCAP_STEREO; w->enable = 1; } if (this->qrks & AZ_QRK_WID_TPDOCK1 && nid == 0x19) { /* Thinkpad x230/t430 style dock microphone */ w->d.pin.config = 0x23a11040; w->enable = 1; } if (this->qrks & AZ_QRK_WID_TPDOCK1 && nid == 0x1b) { /* Thinkpad x230/t430 style dock headphone */ w->d.pin.config = 0x2121103f; w->enable = 1; } if (this->qrks & AZ_QRK_WID_TPDOCK2 && nid == 0x16) { /* Thinkpad x240/t440 style dock headphone */ w->d.pin.config = 0x21211010; w->enable = 1; } if (this->qrks & AZ_QRK_WID_TPDOCK2 && nid == 0x19) { /* Thinkpad x240/t440 style dock microphone */ w->d.pin.config = 0x21a11010; w->enable = 1; } if (this->qrks & AZ_QRK_WID_TPDOCK3 && nid == 0x1a) { /* Thinkpad x220/t420 style dock microphone */ w->d.pin.config = 0x21a190f0; w->enable = 1; } if (this->qrks & AZ_QRK_WID_TPDOCK3 && nid == 0x1c) { /* Thinkpad x220/t420 style dock headphone */ w->d.pin.config = 0x212140ff; w->enable = 1; } if (this->qrks & AZ_QRK_WID_CDIN_1C && nid == 0x1c && w->enable == 0 && w->d.pin.device == CORB_CD_CD) { azalia_pin_config_ov(w, CORB_CD_PORT_MASK, CORB_CD_FIXED); w->widgetcap |= COP_AWCAP_STEREO; w->enable = 1; } if ((this->qrks & AZ_QRK_WID_AD1981_OAMP) && ((nid == 0x05) || (nid == 0x06) || (nid == 0x07) || (nid == 0x09) || (nid == 0x18))) { azalia_ampcap_ov(w, COP_OUTPUT_AMPCAP, 31, 33, 6, 30, 1); } if ((this->qrks & AZ_QRK_WID_CLOSE_PCBEEP) && (nid == 0x20)) { /* Close PC beep passthrough to avoid headphone noise */ azalia_comresp(this, nid, CORB_SET_COEFFICIENT_INDEX, 0x36, NULL); azalia_comresp(this, nid, CORB_SET_PROCESSING_COEFFICIENT, 0x57d7, NULL); } return(0); } /* Magic init sequence to make the right speaker work (reverse-engineered) */ void azalia_codec_init_dolby_atmos(codec_t *this) { static uint16_t atmos_init[] = { 0x06, 0x73e, 0x00, 0x06, 0x73e, 0x80, 0x20, 0x500, 0x26, 0x20, 0x4f0, 0x00, 0x20, 0x500, 0x22, 0x20, 0x400, 0x31, 0x20, 0x500, 0x23, 0x20, 0x400, 0x0b, 0x20, 0x500, 0x25, 0x20, 0x400, 0x00, 0x20, 0x500, 0x26, 0x20, 0x4b0, 0x10, }; static struct { unsigned char v23; unsigned char v25; } atmos_v23_v25[] = { { 0x0c, 0x00 }, { 0x0d, 0x00 }, { 0x0e, 0x00 }, { 0x0f, 0x00 }, { 0x10, 0x00 }, { 0x1a, 0x40 }, { 0x1b, 0x82 }, { 0x1c, 0x00 }, { 0x1d, 0x00 }, { 0x1e, 0x00 }, { 0x1f, 0x00 }, { 0x20, 0xc2 }, { 0x21, 0xc8 }, { 0x22, 0x26 }, { 0x23, 0x24 }, { 0x27, 0xff }, { 0x28, 0xff }, { 0x29, 0xff }, { 0x2a, 0x8f }, { 0x2b, 0x02 }, { 0x2c, 0x48 }, { 0x2d, 0x34 }, { 0x2e, 0x00 }, { 0x2f, 0x00 }, { 0x30, 0x00 }, { 0x31, 0x00 }, { 0x32, 0x00 }, { 0x33, 0x00 }, { 0x34, 0x00 }, { 0x35, 0x01 }, { 0x36, 0x93 }, { 0x37, 0x0c }, { 0x38, 0x00 }, { 0x39, 0x00 }, { 0x3a, 0xf8 }, { 0x38, 0x80 }, }; int i; for (i = 0; i < nitems(atmos_init) / 3; i++) { if (azalia_comresp(this, atmos_init[i * 3], atmos_init[(i * 3) + 1], atmos_init[(i * 3) + 2], NULL)) return; } for (i = 0; i < nitems(atmos_v23_v25); i++) { if (azalia_comresp(this, 0x06, 0x73e, 0x00, NULL)) return; if (azalia_comresp(this, 0x20, 0x500, 0x26, NULL)) return; if (azalia_comresp(this, 0x20, 0x4b0, 0x00, NULL)) return; if (i == 0) { if (azalia_comresp(this, 0x21, 0xf09, 0x00, NULL)) return; } if (i != 20) { if (azalia_comresp(this, 0x06, 0x73e, 0x80, NULL)) return; } if (azalia_comresp(this, 0x20, 0x500, 0x26, NULL)) return; if (azalia_comresp(this, 0x20, 0x4f0, 0x00, NULL)) return; if (azalia_comresp(this, 0x20, 0x500, 0x23, NULL)) return; if (azalia_comresp(this, 0x20, 0x400, atmos_v23_v25[i].v23, NULL)) return; if (atmos_v23_v25[i].v23 != 0x1e) { if (azalia_comresp(this, 0x20, 0x500, 0x25, NULL)) return; if (azalia_comresp(this, 0x20, 0x400, atmos_v23_v25[i].v25, NULL)) return; } if (azalia_comresp(this, 0x20, 0x500, 0x26, NULL)) return; if (azalia_comresp(this, 0x20, 0x4b0, 0x10, NULL)) return; } }