diff options
Diffstat (limited to 'sound/pci/hda')
49 files changed, 65613 insertions, 0 deletions
diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig new file mode 100644 index 000000000..d29d8372a --- /dev/null +++ b/sound/pci/hda/Kconfig @@ -0,0 +1,321 @@ +# SPDX-License-Identifier: GPL-2.0-only +menu "HD-Audio" + +config SND_HDA + tristate + select SND_PCM + select SND_VMASTER + select SND_JACK + select SND_HDA_CORE + +config SND_HDA_GENERIC_LEDS + bool + +config SND_HDA_INTEL + tristate "HD Audio PCI" + depends on SND_PCI + select SND_HDA + select SND_INTEL_DSP_CONFIG + help + Say Y here to include support for Intel "High Definition + Audio" (Azalia) and its compatible devices. + + This option enables the HD-audio controller. Don't forget + to choose the appropriate codec options below. + + To compile this driver as a module, choose M here: the module + will be called snd-hda-intel. + +config SND_HDA_TEGRA + tristate "NVIDIA Tegra HD Audio" + depends on ARCH_TEGRA + select SND_HDA + select SND_HDA_ALIGNED_MMIO + help + Say Y here to support the HDA controller present in NVIDIA + Tegra SoCs + + This options enables support for the HD Audio controller + present in some NVIDIA Tegra SoCs, used to communicate audio + to the HDMI output. + + To compile this driver as a module, choose M here: the module + will be called snd-hda-tegra. + +if SND_HDA + +config SND_HDA_HWDEP + bool "Build hwdep interface for HD-audio driver" + select SND_HWDEP + help + Say Y here to build a hwdep interface for HD-audio driver. + This interface can be used for out-of-band communication + with codecs for debugging purposes. + +config SND_HDA_RECONFIG + bool "Allow dynamic codec reconfiguration" + help + Say Y here to enable the HD-audio codec re-configuration feature. + It allows user to clear the whole codec configuration, change the + codec setup, add extra verbs, and re-configure the codec dynamically. + + Note that this item alone doesn't provide the sysfs interface, but + enables the feature just for the patch loader below. + If you need the traditional sysfs entries for the manual interaction, + turn on CONFIG_SND_HDA_HWDEP as well. + +config SND_HDA_INPUT_BEEP + bool "Support digital beep via input layer" + depends on INPUT=y || INPUT=SND_HDA + help + Say Y here to build a digital beep interface for HD-audio + driver. This interface is used to generate digital beeps. + +config SND_HDA_INPUT_BEEP_MODE + int "Digital beep registration mode (0=off, 1=on)" + depends on SND_HDA_INPUT_BEEP=y + default "1" + range 0 1 + help + Set 0 to disable the digital beep interface for HD-audio by default. + Set 1 to always enable the digital beep interface for HD-audio by + default. + +config SND_HDA_PATCH_LOADER + bool "Support initialization patch loading for HD-audio" + select FW_LOADER + select SND_HDA_RECONFIG + help + Say Y here to allow the HD-audio driver to load a pseudo + firmware file ("patch") for overriding the BIOS setup at + start up. The "patch" file can be specified via patch module + option, such as patch=hda-init. + +config SND_HDA_SCODEC_CS35L41 + tristate + select SND_HDA_GENERIC + select REGMAP_IRQ + +config SND_HDA_CS_DSP_CONTROLS + tristate + select CS_DSP + +config SND_HDA_SCODEC_CS35L41_I2C + tristate "Build CS35L41 HD-audio side codec support for I2C Bus" + depends on I2C + depends on ACPI + depends on SND_SOC + select SND_SOC_CS35L41_LIB + select SND_HDA_SCODEC_CS35L41 + select SND_HDA_CS_DSP_CONTROLS + help + Say Y or M here to include CS35L41 I2C HD-audio side codec support + in snd-hda-intel driver, such as ALC287. + +comment "Set to Y if you want auto-loading the side codec driver" + depends on SND_HDA=y && SND_HDA_SCODEC_CS35L41_I2C=m + +config SND_HDA_SCODEC_CS35L41_SPI + tristate "Build CS35L41 HD-audio codec support for SPI Bus" + depends on SPI_MASTER + depends on ACPI + depends on SND_SOC + select SND_SOC_CS35L41_LIB + select SND_HDA_SCODEC_CS35L41 + select SND_HDA_CS_DSP_CONTROLS + help + Say Y or M here to include CS35L41 SPI HD-audio side codec support + in snd-hda-intel driver, such as ALC287. + +comment "Set to Y if you want auto-loading the side codec driver" + depends on SND_HDA=y && SND_HDA_SCODEC_CS35L41_SPI=m + +config SND_HDA_CODEC_REALTEK + tristate "Build Realtek HD-audio codec support" + select SND_HDA_GENERIC + select SND_HDA_GENERIC_LEDS + help + Say Y or M here to include Realtek HD-audio codec support in + snd-hda-intel driver, such as ALC880. + +comment "Set to Y if you want auto-loading the codec driver" + depends on SND_HDA=y && SND_HDA_CODEC_REALTEK=m + +config SND_HDA_CODEC_ANALOG + tristate "Build Analog Devices HD-audio codec support" + select SND_HDA_GENERIC + help + Say Y or M here to include Analog Devices HD-audio codec support in + snd-hda-intel driver, such as AD1986A. + +comment "Set to Y if you want auto-loading the codec driver" + depends on SND_HDA=y && SND_HDA_CODEC_ANALOG=m + +config SND_HDA_CODEC_SIGMATEL + tristate "Build IDT/Sigmatel HD-audio codec support" + select SND_HDA_GENERIC + select SND_HDA_GENERIC_LEDS + help + Say Y or M here to include IDT (Sigmatel) HD-audio codec support in + snd-hda-intel driver, such as STAC9200. + +comment "Set to Y if you want auto-loading the codec driver" + depends on SND_HDA=y && SND_HDA_CODEC_SIGMATEL=m + +config SND_HDA_CODEC_VIA + tristate "Build VIA HD-audio codec support" + select SND_HDA_GENERIC + help + Say Y or M here to include VIA HD-audio codec support in + snd-hda-intel driver, such as VT1708. + +comment "Set to Y if you want auto-loading the codec driver" + depends on SND_HDA=y && SND_HDA_CODEC_VIA=m + +config SND_HDA_CODEC_HDMI + tristate "Build HDMI/DisplayPort HD-audio codec support" + select SND_DYNAMIC_MINORS + help + Say Y or M here to include HDMI and DisplayPort HD-audio codec + support in snd-hda-intel driver. This includes all AMD/ATI, + Intel and Nvidia HDMI/DisplayPort codecs. + + Note that this option mandatorily enables CONFIG_SND_DYNAMIC_MINORS + to assure the multiple streams for DP-MST support. + +comment "Set to Y if you want auto-loading the codec driver" + depends on SND_HDA=y && SND_HDA_CODEC_HDMI=m + +config SND_HDA_CODEC_CIRRUS + tristate "Build Cirrus Logic codec support" + select SND_HDA_GENERIC + help + Say Y or M here to include Cirrus Logic codec support in + snd-hda-intel driver, such as CS4206. + +comment "Set to Y if you want auto-loading the codec driver" + depends on SND_HDA=y && SND_HDA_CODEC_CIRRUS=m + +config SND_HDA_CODEC_CS8409 + tristate "Build Cirrus Logic HDA bridge support" + select SND_HDA_GENERIC + help + Say Y or M here to include Cirrus Logic HDA bridge support in + snd-hda-intel driver, such as CS8409. + +comment "Set to Y if you want auto-loading the codec driver" + depends on SND_HDA=y && SND_HDA_CODEC_CS8409=m + +config SND_HDA_CODEC_CONEXANT + tristate "Build Conexant HD-audio codec support" + select SND_HDA_GENERIC + select SND_HDA_GENERIC_LEDS + help + Say Y or M here to include Conexant HD-audio codec support in + snd-hda-intel driver, such as CX20549. + +comment "Set to Y if you want auto-loading the codec driver" + depends on SND_HDA=y && SND_HDA_CODEC_CONEXANT=m + +config SND_HDA_CODEC_CA0110 + tristate "Build Creative CA0110-IBG codec support" + select SND_HDA_GENERIC + help + Say Y or M here to include Creative CA0110-IBG codec support in + snd-hda-intel driver, found on some Creative X-Fi cards. + +comment "Set to Y if you want auto-loading the codec driver" + depends on SND_HDA=y && SND_HDA_CODEC_CA0110=m + +config SND_HDA_CODEC_CA0132 + tristate "Build Creative CA0132 codec support" + help + Say Y or M here to include Creative CA0132 codec support in + snd-hda-intel driver. + +comment "Set to Y if you want auto-loading the codec driver" + depends on SND_HDA=y && SND_HDA_CODEC_CA0132=m + +config SND_HDA_CODEC_CA0132_DSP + bool "Support new DSP code for CA0132 codec" + depends on SND_HDA_CODEC_CA0132 + default y + select SND_HDA_DSP_LOADER + select FW_LOADER + help + Say Y here to enable the DSP for Creative CA0132 for extended + features like equalizer or echo cancellation. + + Note that this option requires the external firmware file + (ctefx.bin). + +config SND_HDA_CODEC_CMEDIA + tristate "Build C-Media HD-audio codec support" + select SND_HDA_GENERIC + help + Say Y or M here to include C-Media HD-audio codec support in + snd-hda-intel driver, such as CMI9880. + +comment "Set to Y if you want auto-loading the codec driver" + depends on SND_HDA=y && SND_HDA_CODEC_CMEDIA=m + +config SND_HDA_CODEC_SI3054 + tristate "Build Silicon Labs 3054 HD-modem codec support" + help + Say Y or M here to include Silicon Labs 3054 HD-modem codec + (and compatibles) support in snd-hda-intel driver. + +comment "Set to Y if you want auto-loading the codec driver" + depends on SND_HDA=y && SND_HDA_CODEC_SI3054=m + +config SND_HDA_GENERIC + tristate "Enable generic HD-audio codec parser" + select SND_CTL_LED if SND_HDA_GENERIC_LEDS + select LEDS_CLASS if SND_HDA_GENERIC_LEDS + help + Say Y or M here to enable the generic HD-audio codec parser + in snd-hda-intel driver. + +comment "Set to Y if you want auto-loading the codec driver" + depends on SND_HDA=y && SND_HDA_GENERIC=m + +config SND_HDA_POWER_SAVE_DEFAULT + int "Default time-out for HD-audio power-save mode" + depends on PM + default 0 + help + The default time-out value in seconds for HD-audio automatic + power-save mode. 0 means to disable the power-save mode. + +config SND_HDA_INTEL_HDMI_SILENT_STREAM + bool "Enable Silent Stream always for HDMI" + depends on SND_HDA_INTEL + help + Say Y to enable HD-Audio Keep Alive (KAE) aka Silent Stream + for HDMI on hardware that supports the feature. + + When enabled, the HDMI/DisplayPort codec will continue to provide + a continuous clock and a valid but silent data stream to + any connected external receiver. This allows to avoid gaps + at start of playback. Many receivers require multiple seconds + to start playing audio after the clock has been stopped. + This feature can impact power consumption as resources + are kept reserved both at transmitter and receiver. + +config SND_HDA_CTL_DEV_ID + bool "Use the device identifier field for controls" + depends on SND_HDA_INTEL + help + Say Y to use the device identifier field for (mixer) + controls (old behaviour until this option is available). + + When enabled, the multiple HDA codecs may set the device + field in control (mixer) element identifiers. The use + of this field is not recommended and defined for mixer controls. + + The old behaviour (Y) is obsolete and will be removed. Consider + to not enable this option. + +endif + +endmenu diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile new file mode 100644 index 000000000..00d306104 --- /dev/null +++ b/sound/pci/hda/Makefile @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-hda-intel-objs := hda_intel.o +snd-hda-tegra-objs := hda_tegra.o + +snd-hda-codec-y := hda_bind.o hda_codec.o hda_jack.o hda_auto_parser.o hda_sysfs.o +snd-hda-codec-y += hda_controller.o +snd-hda-codec-$(CONFIG_SND_PROC_FS) += hda_proc.o + +snd-hda-codec-$(CONFIG_SND_HDA_HWDEP) += hda_hwdep.o +snd-hda-codec-$(CONFIG_SND_HDA_INPUT_BEEP) += hda_beep.o + +# for trace-points +CFLAGS_hda_controller.o := -I$(src) +CFLAGS_hda_intel.o := -I$(src) + +snd-hda-codec-generic-objs := hda_generic.o +snd-hda-codec-realtek-objs := patch_realtek.o +snd-hda-codec-cmedia-objs := patch_cmedia.o +snd-hda-codec-analog-objs := patch_analog.o +snd-hda-codec-idt-objs := patch_sigmatel.o +snd-hda-codec-si3054-objs := patch_si3054.o +snd-hda-codec-cirrus-objs := patch_cirrus.o +snd-hda-codec-cs8409-objs := patch_cs8409.o patch_cs8409-tables.o +snd-hda-codec-ca0110-objs := patch_ca0110.o +snd-hda-codec-ca0132-objs := patch_ca0132.o +snd-hda-codec-conexant-objs := patch_conexant.o +snd-hda-codec-via-objs := patch_via.o +snd-hda-codec-hdmi-objs := patch_hdmi.o hda_eld.o + +# side codecs +snd-hda-scodec-cs35l41-objs := cs35l41_hda.o +snd-hda-scodec-cs35l41-i2c-objs := cs35l41_hda_i2c.o +snd-hda-scodec-cs35l41-spi-objs := cs35l41_hda_spi.o +snd-hda-cs-dsp-ctls-objs := hda_cs_dsp_ctl.o + +# common driver +obj-$(CONFIG_SND_HDA) := snd-hda-codec.o + +# codec drivers +obj-$(CONFIG_SND_HDA_GENERIC) += snd-hda-codec-generic.o +obj-$(CONFIG_SND_HDA_CODEC_REALTEK) += snd-hda-codec-realtek.o +obj-$(CONFIG_SND_HDA_CODEC_CMEDIA) += snd-hda-codec-cmedia.o +obj-$(CONFIG_SND_HDA_CODEC_ANALOG) += snd-hda-codec-analog.o +obj-$(CONFIG_SND_HDA_CODEC_SIGMATEL) += snd-hda-codec-idt.o +obj-$(CONFIG_SND_HDA_CODEC_SI3054) += snd-hda-codec-si3054.o +obj-$(CONFIG_SND_HDA_CODEC_CIRRUS) += snd-hda-codec-cirrus.o +obj-$(CONFIG_SND_HDA_CODEC_CS8409) += snd-hda-codec-cs8409.o +obj-$(CONFIG_SND_HDA_CODEC_CA0110) += snd-hda-codec-ca0110.o +obj-$(CONFIG_SND_HDA_CODEC_CA0132) += snd-hda-codec-ca0132.o +obj-$(CONFIG_SND_HDA_CODEC_CONEXANT) += snd-hda-codec-conexant.o +obj-$(CONFIG_SND_HDA_CODEC_VIA) += snd-hda-codec-via.o +obj-$(CONFIG_SND_HDA_CODEC_HDMI) += snd-hda-codec-hdmi.o + +# side codecs +obj-$(CONFIG_SND_HDA_SCODEC_CS35L41) += snd-hda-scodec-cs35l41.o +obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_I2C) += snd-hda-scodec-cs35l41-i2c.o +obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_SPI) += snd-hda-scodec-cs35l41-spi.o +obj-$(CONFIG_SND_HDA_CS_DSP_CONTROLS) += snd-hda-cs-dsp-ctls.o + +# this must be the last entry after codec drivers; +# otherwise the codec patches won't be hooked before the PCI probe +# when built in kernel +obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-intel.o +obj-$(CONFIG_SND_HDA_TEGRA) += snd-hda-tegra.o diff --git a/sound/pci/hda/ca0132_regs.h b/sound/pci/hda/ca0132_regs.h new file mode 100644 index 000000000..0ead571fb --- /dev/null +++ b/sound/pci/hda/ca0132_regs.h @@ -0,0 +1,396 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * HD audio interface patch for Creative CA0132 chip. + * CA0132 registers defines. + * + * Copyright (c) 2011, Creative Technology Ltd. + */ + +#ifndef __CA0132_REGS_H +#define __CA0132_REGS_H + +#define DSP_CHIP_OFFSET 0x100000 +#define DSP_DBGCNTL_MODULE_OFFSET 0xE30 +#define DSP_DBGCNTL_INST_OFFSET \ + (DSP_CHIP_OFFSET + DSP_DBGCNTL_MODULE_OFFSET) + +#define DSP_DBGCNTL_EXEC_LOBIT 0x0 +#define DSP_DBGCNTL_EXEC_HIBIT 0x3 +#define DSP_DBGCNTL_EXEC_MASK 0xF + +#define DSP_DBGCNTL_SS_LOBIT 0x4 +#define DSP_DBGCNTL_SS_HIBIT 0x7 +#define DSP_DBGCNTL_SS_MASK 0xF0 + +#define DSP_DBGCNTL_STATE_LOBIT 0xA +#define DSP_DBGCNTL_STATE_HIBIT 0xD +#define DSP_DBGCNTL_STATE_MASK 0x3C00 + +#define XRAM_CHIP_OFFSET 0x0 +#define XRAM_XRAM_CHANNEL_COUNT 0xE000 +#define XRAM_XRAM_MODULE_OFFSET 0x0 +#define XRAM_XRAM_CHAN_INCR 4 +#define XRAM_XRAM_INST_OFFSET(_chan) \ + (XRAM_CHIP_OFFSET + XRAM_XRAM_MODULE_OFFSET + \ + (_chan * XRAM_XRAM_CHAN_INCR)) + +#define YRAM_CHIP_OFFSET 0x40000 +#define YRAM_YRAM_CHANNEL_COUNT 0x8000 +#define YRAM_YRAM_MODULE_OFFSET 0x0 +#define YRAM_YRAM_CHAN_INCR 4 +#define YRAM_YRAM_INST_OFFSET(_chan) \ + (YRAM_CHIP_OFFSET + YRAM_YRAM_MODULE_OFFSET + \ + (_chan * YRAM_YRAM_CHAN_INCR)) + +#define UC_CHIP_OFFSET 0x80000 +#define UC_UC_CHANNEL_COUNT 0x10000 +#define UC_UC_MODULE_OFFSET 0x0 +#define UC_UC_CHAN_INCR 4 +#define UC_UC_INST_OFFSET(_chan) \ + (UC_CHIP_OFFSET + UC_UC_MODULE_OFFSET + \ + (_chan * UC_UC_CHAN_INCR)) + +#define AXRAM_CHIP_OFFSET 0x3C000 +#define AXRAM_AXRAM_CHANNEL_COUNT 0x1000 +#define AXRAM_AXRAM_MODULE_OFFSET 0x0 +#define AXRAM_AXRAM_CHAN_INCR 4 +#define AXRAM_AXRAM_INST_OFFSET(_chan) \ + (AXRAM_CHIP_OFFSET + AXRAM_AXRAM_MODULE_OFFSET + \ + (_chan * AXRAM_AXRAM_CHAN_INCR)) + +#define AYRAM_CHIP_OFFSET 0x78000 +#define AYRAM_AYRAM_CHANNEL_COUNT 0x1000 +#define AYRAM_AYRAM_MODULE_OFFSET 0x0 +#define AYRAM_AYRAM_CHAN_INCR 4 +#define AYRAM_AYRAM_INST_OFFSET(_chan) \ + (AYRAM_CHIP_OFFSET + AYRAM_AYRAM_MODULE_OFFSET + \ + (_chan * AYRAM_AYRAM_CHAN_INCR)) + +#define DSPDMAC_CHIP_OFFSET 0x110000 +#define DSPDMAC_DMA_CFG_CHANNEL_COUNT 12 +#define DSPDMAC_DMACFG_MODULE_OFFSET 0xF00 +#define DSPDMAC_DMACFG_CHAN_INCR 0x10 +#define DSPDMAC_DMACFG_INST_OFFSET(_chan) \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_DMACFG_MODULE_OFFSET + \ + (_chan * DSPDMAC_DMACFG_CHAN_INCR)) + +#define DSPDMAC_DMACFG_DBADR_LOBIT 0x0 +#define DSPDMAC_DMACFG_DBADR_HIBIT 0x10 +#define DSPDMAC_DMACFG_DBADR_MASK 0x1FFFF +#define DSPDMAC_DMACFG_LP_LOBIT 0x11 +#define DSPDMAC_DMACFG_LP_HIBIT 0x11 +#define DSPDMAC_DMACFG_LP_MASK 0x20000 + +#define DSPDMAC_DMACFG_AINCR_LOBIT 0x12 +#define DSPDMAC_DMACFG_AINCR_HIBIT 0x12 +#define DSPDMAC_DMACFG_AINCR_MASK 0x40000 + +#define DSPDMAC_DMACFG_DWR_LOBIT 0x13 +#define DSPDMAC_DMACFG_DWR_HIBIT 0x13 +#define DSPDMAC_DMACFG_DWR_MASK 0x80000 + +#define DSPDMAC_DMACFG_AJUMP_LOBIT 0x14 +#define DSPDMAC_DMACFG_AJUMP_HIBIT 0x17 +#define DSPDMAC_DMACFG_AJUMP_MASK 0xF00000 + +#define DSPDMAC_DMACFG_AMODE_LOBIT 0x18 +#define DSPDMAC_DMACFG_AMODE_HIBIT 0x19 +#define DSPDMAC_DMACFG_AMODE_MASK 0x3000000 + +#define DSPDMAC_DMACFG_LK_LOBIT 0x1A +#define DSPDMAC_DMACFG_LK_HIBIT 0x1A +#define DSPDMAC_DMACFG_LK_MASK 0x4000000 + +#define DSPDMAC_DMACFG_AICS_LOBIT 0x1B +#define DSPDMAC_DMACFG_AICS_HIBIT 0x1F +#define DSPDMAC_DMACFG_AICS_MASK 0xF8000000 + +#define DSPDMAC_DMACFG_LP_SINGLE 0 +#define DSPDMAC_DMACFG_LP_LOOPING 1 + +#define DSPDMAC_DMACFG_AINCR_XANDY 0 +#define DSPDMAC_DMACFG_AINCR_XORY 1 + +#define DSPDMAC_DMACFG_DWR_DMA_RD 0 +#define DSPDMAC_DMACFG_DWR_DMA_WR 1 + +#define DSPDMAC_DMACFG_AMODE_LINEAR 0 +#define DSPDMAC_DMACFG_AMODE_RSV1 1 +#define DSPDMAC_DMACFG_AMODE_WINTLV 2 +#define DSPDMAC_DMACFG_AMODE_GINTLV 3 + +#define DSPDMAC_DSP_ADR_OFS_CHANNEL_COUNT 12 +#define DSPDMAC_DSPADROFS_MODULE_OFFSET 0xF04 +#define DSPDMAC_DSPADROFS_CHAN_INCR 0x10 +#define DSPDMAC_DSPADROFS_INST_OFFSET(_chan) \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_DSPADROFS_MODULE_OFFSET + \ + (_chan * DSPDMAC_DSPADROFS_CHAN_INCR)) + +#define DSPDMAC_DSPADROFS_COFS_LOBIT 0x0 +#define DSPDMAC_DSPADROFS_COFS_HIBIT 0xF +#define DSPDMAC_DSPADROFS_COFS_MASK 0xFFFF + +#define DSPDMAC_DSPADROFS_BOFS_LOBIT 0x10 +#define DSPDMAC_DSPADROFS_BOFS_HIBIT 0x1F +#define DSPDMAC_DSPADROFS_BOFS_MASK 0xFFFF0000 + +#define DSPDMAC_DSP_ADR_WOFS_CHANNEL_COUNT 12 +#define DSPDMAC_DSPADRWOFS_MODULE_OFFSET 0xF04 +#define DSPDMAC_DSPADRWOFS_CHAN_INCR 0x10 + +#define DSPDMAC_DSPADRWOFS_INST_OFFSET(_chan) \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_DSPADRWOFS_MODULE_OFFSET + \ + (_chan * DSPDMAC_DSPADRWOFS_CHAN_INCR)) + +#define DSPDMAC_DSPADRWOFS_WCOFS_LOBIT 0x0 +#define DSPDMAC_DSPADRWOFS_WCOFS_HIBIT 0xA +#define DSPDMAC_DSPADRWOFS_WCOFS_MASK 0x7FF + +#define DSPDMAC_DSPADRWOFS_WCBFR_LOBIT 0xB +#define DSPDMAC_DSPADRWOFS_WCBFR_HIBIT 0xF +#define DSPDMAC_DSPADRWOFS_WCBFR_MASK 0xF800 + +#define DSPDMAC_DSPADRWOFS_WBOFS_LOBIT 0x10 +#define DSPDMAC_DSPADRWOFS_WBOFS_HIBIT 0x1A +#define DSPDMAC_DSPADRWOFS_WBOFS_MASK 0x7FF0000 + +#define DSPDMAC_DSPADRWOFS_WBBFR_LOBIT 0x1B +#define DSPDMAC_DSPADRWOFS_WBBFR_HIBIT 0x1F +#define DSPDMAC_DSPADRWOFS_WBBFR_MASK 0xF8000000 + +#define DSPDMAC_DSP_ADR_GOFS_CHANNEL_COUNT 12 +#define DSPDMAC_DSPADRGOFS_MODULE_OFFSET 0xF04 +#define DSPDMAC_DSPADRGOFS_CHAN_INCR 0x10 +#define DSPDMAC_DSPADRGOFS_INST_OFFSET(_chan) \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_DSPADRGOFS_MODULE_OFFSET + \ + (_chan * DSPDMAC_DSPADRGOFS_CHAN_INCR)) + +#define DSPDMAC_DSPADRGOFS_GCOFS_LOBIT 0x0 +#define DSPDMAC_DSPADRGOFS_GCOFS_HIBIT 0x9 +#define DSPDMAC_DSPADRGOFS_GCOFS_MASK 0x3FF + +#define DSPDMAC_DSPADRGOFS_GCS_LOBIT 0xA +#define DSPDMAC_DSPADRGOFS_GCS_HIBIT 0xC +#define DSPDMAC_DSPADRGOFS_GCS_MASK 0x1C00 + +#define DSPDMAC_DSPADRGOFS_GCBFR_LOBIT 0xD +#define DSPDMAC_DSPADRGOFS_GCBFR_HIBIT 0xF +#define DSPDMAC_DSPADRGOFS_GCBFR_MASK 0xE000 + +#define DSPDMAC_DSPADRGOFS_GBOFS_LOBIT 0x10 +#define DSPDMAC_DSPADRGOFS_GBOFS_HIBIT 0x19 +#define DSPDMAC_DSPADRGOFS_GBOFS_MASK 0x3FF0000 + +#define DSPDMAC_DSPADRGOFS_GBS_LOBIT 0x1A +#define DSPDMAC_DSPADRGOFS_GBS_HIBIT 0x1C +#define DSPDMAC_DSPADRGOFS_GBS_MASK 0x1C000000 + +#define DSPDMAC_DSPADRGOFS_GBBFR_LOBIT 0x1D +#define DSPDMAC_DSPADRGOFS_GBBFR_HIBIT 0x1F +#define DSPDMAC_DSPADRGOFS_GBBFR_MASK 0xE0000000 + +#define DSPDMAC_XFR_CNT_CHANNEL_COUNT 12 +#define DSPDMAC_XFRCNT_MODULE_OFFSET 0xF08 +#define DSPDMAC_XFRCNT_CHAN_INCR 0x10 + +#define DSPDMAC_XFRCNT_INST_OFFSET(_chan) \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_XFRCNT_MODULE_OFFSET + \ + (_chan * DSPDMAC_XFRCNT_CHAN_INCR)) + +#define DSPDMAC_XFRCNT_CCNT_LOBIT 0x0 +#define DSPDMAC_XFRCNT_CCNT_HIBIT 0xF +#define DSPDMAC_XFRCNT_CCNT_MASK 0xFFFF + +#define DSPDMAC_XFRCNT_BCNT_LOBIT 0x10 +#define DSPDMAC_XFRCNT_BCNT_HIBIT 0x1F +#define DSPDMAC_XFRCNT_BCNT_MASK 0xFFFF0000 + +#define DSPDMAC_IRQ_CNT_CHANNEL_COUNT 12 +#define DSPDMAC_IRQCNT_MODULE_OFFSET 0xF0C +#define DSPDMAC_IRQCNT_CHAN_INCR 0x10 +#define DSPDMAC_IRQCNT_INST_OFFSET(_chan) \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_IRQCNT_MODULE_OFFSET + \ + (_chan * DSPDMAC_IRQCNT_CHAN_INCR)) + +#define DSPDMAC_IRQCNT_CICNT_LOBIT 0x0 +#define DSPDMAC_IRQCNT_CICNT_HIBIT 0xF +#define DSPDMAC_IRQCNT_CICNT_MASK 0xFFFF + +#define DSPDMAC_IRQCNT_BICNT_LOBIT 0x10 +#define DSPDMAC_IRQCNT_BICNT_HIBIT 0x1F +#define DSPDMAC_IRQCNT_BICNT_MASK 0xFFFF0000 + +#define DSPDMAC_AUD_CHSEL_CHANNEL_COUNT 12 +#define DSPDMAC_AUDCHSEL_MODULE_OFFSET 0xFC0 +#define DSPDMAC_AUDCHSEL_CHAN_INCR 0x4 +#define DSPDMAC_AUDCHSEL_INST_OFFSET(_chan) \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_AUDCHSEL_MODULE_OFFSET + \ + (_chan * DSPDMAC_AUDCHSEL_CHAN_INCR)) + +#define DSPDMAC_AUDCHSEL_ACS_LOBIT 0x0 +#define DSPDMAC_AUDCHSEL_ACS_HIBIT 0x1F +#define DSPDMAC_AUDCHSEL_ACS_MASK 0xFFFFFFFF + +#define DSPDMAC_CHNLSTART_MODULE_OFFSET 0xFF0 +#define DSPDMAC_CHNLSTART_INST_OFFSET \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_CHNLSTART_MODULE_OFFSET) + +#define DSPDMAC_CHNLSTART_EN_LOBIT 0x0 +#define DSPDMAC_CHNLSTART_EN_HIBIT 0xB +#define DSPDMAC_CHNLSTART_EN_MASK 0xFFF + +#define DSPDMAC_CHNLSTART_VAI1_LOBIT 0xC +#define DSPDMAC_CHNLSTART_VAI1_HIBIT 0xF +#define DSPDMAC_CHNLSTART_VAI1_MASK 0xF000 + +#define DSPDMAC_CHNLSTART_DIS_LOBIT 0x10 +#define DSPDMAC_CHNLSTART_DIS_HIBIT 0x1B +#define DSPDMAC_CHNLSTART_DIS_MASK 0xFFF0000 + +#define DSPDMAC_CHNLSTART_VAI2_LOBIT 0x1C +#define DSPDMAC_CHNLSTART_VAI2_HIBIT 0x1F +#define DSPDMAC_CHNLSTART_VAI2_MASK 0xF0000000 + +#define DSPDMAC_CHNLSTATUS_MODULE_OFFSET 0xFF4 +#define DSPDMAC_CHNLSTATUS_INST_OFFSET \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_CHNLSTATUS_MODULE_OFFSET) + +#define DSPDMAC_CHNLSTATUS_ISC_LOBIT 0x0 +#define DSPDMAC_CHNLSTATUS_ISC_HIBIT 0xB +#define DSPDMAC_CHNLSTATUS_ISC_MASK 0xFFF + +#define DSPDMAC_CHNLSTATUS_AOO_LOBIT 0xC +#define DSPDMAC_CHNLSTATUS_AOO_HIBIT 0xC +#define DSPDMAC_CHNLSTATUS_AOO_MASK 0x1000 + +#define DSPDMAC_CHNLSTATUS_AOU_LOBIT 0xD +#define DSPDMAC_CHNLSTATUS_AOU_HIBIT 0xD +#define DSPDMAC_CHNLSTATUS_AOU_MASK 0x2000 + +#define DSPDMAC_CHNLSTATUS_AIO_LOBIT 0xE +#define DSPDMAC_CHNLSTATUS_AIO_HIBIT 0xE +#define DSPDMAC_CHNLSTATUS_AIO_MASK 0x4000 + +#define DSPDMAC_CHNLSTATUS_AIU_LOBIT 0xF +#define DSPDMAC_CHNLSTATUS_AIU_HIBIT 0xF +#define DSPDMAC_CHNLSTATUS_AIU_MASK 0x8000 + +#define DSPDMAC_CHNLSTATUS_IEN_LOBIT 0x10 +#define DSPDMAC_CHNLSTATUS_IEN_HIBIT 0x1B +#define DSPDMAC_CHNLSTATUS_IEN_MASK 0xFFF0000 + +#define DSPDMAC_CHNLSTATUS_VAI0_LOBIT 0x1C +#define DSPDMAC_CHNLSTATUS_VAI0_HIBIT 0x1F +#define DSPDMAC_CHNLSTATUS_VAI0_MASK 0xF0000000 + +#define DSPDMAC_CHNLPROP_MODULE_OFFSET 0xFF8 +#define DSPDMAC_CHNLPROP_INST_OFFSET \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_CHNLPROP_MODULE_OFFSET) + +#define DSPDMAC_CHNLPROP_DCON_LOBIT 0x0 +#define DSPDMAC_CHNLPROP_DCON_HIBIT 0xB +#define DSPDMAC_CHNLPROP_DCON_MASK 0xFFF + +#define DSPDMAC_CHNLPROP_FFS_LOBIT 0xC +#define DSPDMAC_CHNLPROP_FFS_HIBIT 0xC +#define DSPDMAC_CHNLPROP_FFS_MASK 0x1000 + +#define DSPDMAC_CHNLPROP_NAJ_LOBIT 0xD +#define DSPDMAC_CHNLPROP_NAJ_HIBIT 0xD +#define DSPDMAC_CHNLPROP_NAJ_MASK 0x2000 + +#define DSPDMAC_CHNLPROP_ENH_LOBIT 0xE +#define DSPDMAC_CHNLPROP_ENH_HIBIT 0xE +#define DSPDMAC_CHNLPROP_ENH_MASK 0x4000 + +#define DSPDMAC_CHNLPROP_MSPCE_LOBIT 0x10 +#define DSPDMAC_CHNLPROP_MSPCE_HIBIT 0x1B +#define DSPDMAC_CHNLPROP_MSPCE_MASK 0xFFF0000 + +#define DSPDMAC_CHNLPROP_AC_LOBIT 0x1C +#define DSPDMAC_CHNLPROP_AC_HIBIT 0x1F +#define DSPDMAC_CHNLPROP_AC_MASK 0xF0000000 + +#define DSPDMAC_ACTIVE_MODULE_OFFSET 0xFFC +#define DSPDMAC_ACTIVE_INST_OFFSET \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_ACTIVE_MODULE_OFFSET) + +#define DSPDMAC_ACTIVE_AAR_LOBIT 0x0 +#define DSPDMAC_ACTIVE_AAR_HIBIT 0xB +#define DSPDMAC_ACTIVE_AAR_MASK 0xFFF + +#define DSPDMAC_ACTIVE_WFR_LOBIT 0xC +#define DSPDMAC_ACTIVE_WFR_HIBIT 0x17 +#define DSPDMAC_ACTIVE_WFR_MASK 0xFFF000 + +#define DSP_AUX_MEM_BASE 0xE000 +#define INVALID_CHIP_ADDRESS (~0U) + +#define X_SIZE (XRAM_XRAM_CHANNEL_COUNT * XRAM_XRAM_CHAN_INCR) +#define Y_SIZE (YRAM_YRAM_CHANNEL_COUNT * YRAM_YRAM_CHAN_INCR) +#define AX_SIZE (AXRAM_AXRAM_CHANNEL_COUNT * AXRAM_AXRAM_CHAN_INCR) +#define AY_SIZE (AYRAM_AYRAM_CHANNEL_COUNT * AYRAM_AYRAM_CHAN_INCR) +#define UC_SIZE (UC_UC_CHANNEL_COUNT * UC_UC_CHAN_INCR) + +#define XEXT_SIZE (X_SIZE + AX_SIZE) +#define YEXT_SIZE (Y_SIZE + AY_SIZE) + +#define U64K 0x10000UL + +#define X_END (XRAM_CHIP_OFFSET + X_SIZE) +#define X_EXT (XRAM_CHIP_OFFSET + XEXT_SIZE) +#define AX_END (XRAM_CHIP_OFFSET + U64K*4) + +#define Y_END (YRAM_CHIP_OFFSET + Y_SIZE) +#define Y_EXT (YRAM_CHIP_OFFSET + YEXT_SIZE) +#define AY_END (YRAM_CHIP_OFFSET + U64K*4) + +#define UC_END (UC_CHIP_OFFSET + UC_SIZE) + +#define X_RANGE_MAIN(a, s) \ + (((a)+((s)-1)*XRAM_XRAM_CHAN_INCR < X_END)) +#define X_RANGE_AUX(a, s) \ + (((a) >= X_END) && ((a)+((s)-1)*XRAM_XRAM_CHAN_INCR < AX_END)) +#define X_RANGE_EXT(a, s) \ + (((a)+((s)-1)*XRAM_XRAM_CHAN_INCR < X_EXT)) +#define X_RANGE_ALL(a, s) \ + (((a)+((s)-1)*XRAM_XRAM_CHAN_INCR < AX_END)) + +#define Y_RANGE_MAIN(a, s) \ + (((a) >= YRAM_CHIP_OFFSET) && \ + ((a)+((s)-1)*YRAM_YRAM_CHAN_INCR < Y_END)) +#define Y_RANGE_AUX(a, s) \ + (((a) >= Y_END) && \ + ((a)+((s)-1)*YRAM_YRAM_CHAN_INCR < AY_END)) +#define Y_RANGE_EXT(a, s) \ + (((a) >= YRAM_CHIP_OFFSET) && \ + ((a)+((s)-1)*YRAM_YRAM_CHAN_INCR < Y_EXT)) +#define Y_RANGE_ALL(a, s) \ + (((a) >= YRAM_CHIP_OFFSET) && \ + ((a)+((s)-1)*YRAM_YRAM_CHAN_INCR < AY_END)) + +#define UC_RANGE(a, s) \ + (((a) >= UC_CHIP_OFFSET) && \ + ((a)+((s)-1)*UC_UC_CHAN_INCR < UC_END)) + +#define X_OFF(a) \ + (((a) - XRAM_CHIP_OFFSET) / XRAM_XRAM_CHAN_INCR) +#define AX_OFF(a) \ + (((a) % (AXRAM_AXRAM_CHANNEL_COUNT * \ + AXRAM_AXRAM_CHAN_INCR)) / AXRAM_AXRAM_CHAN_INCR) + +#define Y_OFF(a) \ + (((a) - YRAM_CHIP_OFFSET) / YRAM_YRAM_CHAN_INCR) +#define AY_OFF(a) \ + (((a) % (AYRAM_AYRAM_CHANNEL_COUNT * \ + AYRAM_AYRAM_CHAN_INCR)) / AYRAM_AYRAM_CHAN_INCR) + +#define UC_OFF(a) (((a) - UC_CHIP_OFFSET) / UC_UC_CHAN_INCR) + +#define X_EXT_MAIN_SIZE(a) (XRAM_XRAM_CHANNEL_COUNT - X_OFF(a)) +#define X_EXT_AUX_SIZE(a, s) ((s) - X_EXT_MAIN_SIZE(a)) + +#define Y_EXT_MAIN_SIZE(a) (YRAM_YRAM_CHANNEL_COUNT - Y_OFF(a)) +#define Y_EXT_AUX_SIZE(a, s) ((s) - Y_EXT_MAIN_SIZE(a)) + +#endif diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c new file mode 100644 index 000000000..c79a12e5c --- /dev/null +++ b/sound/pci/hda/cs35l41_hda.c @@ -0,0 +1,1558 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// CS35l41 ALSA HDA audio driver +// +// Copyright 2021 Cirrus Logic, Inc. +// +// Author: Lucas Tanure <tanureal@opensource.cirrus.com> + +#include <linux/acpi.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <sound/hda_codec.h> +#include <sound/soc.h> +#include <linux/pm_runtime.h> +#include "hda_local.h" +#include "hda_auto_parser.h" +#include "hda_jack.h" +#include "hda_generic.h" +#include "hda_component.h" +#include "cs35l41_hda.h" +#include "hda_cs_dsp_ctl.h" + +#define CS35L41_FIRMWARE_ROOT "cirrus/" +#define CS35L41_PART "cs35l41" + +#define HALO_STATE_DSP_CTL_NAME "HALO_STATE" +#define HALO_STATE_DSP_CTL_TYPE 5 +#define HALO_STATE_DSP_CTL_ALG 262308 +#define CAL_R_DSP_CTL_NAME "CAL_R" +#define CAL_STATUS_DSP_CTL_NAME "CAL_STATUS" +#define CAL_CHECKSUM_DSP_CTL_NAME "CAL_CHECKSUM" +#define CAL_AMBIENT_DSP_CTL_NAME "CAL_AMBIENT" +#define CAL_DSP_CTL_TYPE 5 +#define CAL_DSP_CTL_ALG 205 + +static bool firmware_autostart = 1; +module_param(firmware_autostart, bool, 0444); +MODULE_PARM_DESC(firmware_autostart, "Allow automatic firmware download on boot" + "(0=Disable, 1=Enable) (default=1); "); + +static const struct reg_sequence cs35l41_hda_config[] = { + { CS35L41_PLL_CLK_CTRL, 0x00000430 }, // 3072000Hz, BCLK Input, PLL_REFCLK_EN = 1 + { CS35L41_DSP_CLK_CTRL, 0x00000003 }, // DSP CLK EN + { CS35L41_GLOBAL_CLK_CTRL, 0x00000003 }, // GLOBAL_FS = 48 kHz + { CS35L41_SP_ENABLES, 0x00010000 }, // ASP_RX1_EN = 1 + { CS35L41_SP_RATE_CTRL, 0x00000021 }, // ASP_BCLK_FREQ = 3.072 MHz + { CS35L41_SP_FORMAT, 0x20200200 }, // 32 bits RX/TX slots, I2S, clk consumer + { CS35L41_SP_HIZ_CTRL, 0x00000002 }, // Hi-Z unused + { CS35L41_SP_TX_WL, 0x00000018 }, // 24 cycles/slot + { CS35L41_SP_RX_WL, 0x00000018 }, // 24 cycles/slot + { CS35L41_DAC_PCM1_SRC, 0x00000008 }, // DACPCM1_SRC = ASPRX1 + { CS35L41_ASP_TX1_SRC, 0x00000018 }, // ASPTX1 SRC = VMON + { CS35L41_ASP_TX2_SRC, 0x00000019 }, // ASPTX2 SRC = IMON + { CS35L41_ASP_TX3_SRC, 0x00000032 }, // ASPTX3 SRC = ERRVOL + { CS35L41_ASP_TX4_SRC, 0x00000033 }, // ASPTX4 SRC = CLASSH_TGT + { CS35L41_DSP1_RX1_SRC, 0x00000008 }, // DSP1RX1 SRC = ASPRX1 + { CS35L41_DSP1_RX2_SRC, 0x00000009 }, // DSP1RX2 SRC = ASPRX2 + { CS35L41_DSP1_RX3_SRC, 0x00000018 }, // DSP1RX3 SRC = VMON + { CS35L41_DSP1_RX4_SRC, 0x00000019 }, // DSP1RX4 SRC = IMON + { CS35L41_DSP1_RX5_SRC, 0x00000020 }, // DSP1RX5 SRC = ERRVOL + { CS35L41_AMP_DIG_VOL_CTRL, 0x00000000 }, // AMP_VOL_PCM 0.0 dB + { CS35L41_AMP_GAIN_CTRL, 0x00000084 }, // AMP_GAIN_PCM 4.5 dB +}; + +static const struct reg_sequence cs35l41_hda_config_dsp[] = { + { CS35L41_PLL_CLK_CTRL, 0x00000430 }, // 3072000Hz, BCLK Input, PLL_REFCLK_EN = 1 + { CS35L41_DSP_CLK_CTRL, 0x00000003 }, // DSP CLK EN + { CS35L41_GLOBAL_CLK_CTRL, 0x00000003 }, // GLOBAL_FS = 48 kHz + { CS35L41_SP_ENABLES, 0x00010001 }, // ASP_RX1_EN = 1, ASP_TX1_EN = 1 + { CS35L41_SP_RATE_CTRL, 0x00000021 }, // ASP_BCLK_FREQ = 3.072 MHz + { CS35L41_SP_FORMAT, 0x20200200 }, // 32 bits RX/TX slots, I2S, clk consumer + { CS35L41_SP_HIZ_CTRL, 0x00000003 }, // Hi-Z unused/disabled + { CS35L41_SP_TX_WL, 0x00000018 }, // 24 cycles/slot + { CS35L41_SP_RX_WL, 0x00000018 }, // 24 cycles/slot + { CS35L41_DAC_PCM1_SRC, 0x00000032 }, // DACPCM1_SRC = ERR_VOL + { CS35L41_ASP_TX1_SRC, 0x00000018 }, // ASPTX1 SRC = VMON + { CS35L41_ASP_TX2_SRC, 0x00000019 }, // ASPTX2 SRC = IMON + { CS35L41_ASP_TX3_SRC, 0x00000028 }, // ASPTX3 SRC = VPMON + { CS35L41_ASP_TX4_SRC, 0x00000029 }, // ASPTX4 SRC = VBSTMON + { CS35L41_DSP1_RX1_SRC, 0x00000008 }, // DSP1RX1 SRC = ASPRX1 + { CS35L41_DSP1_RX2_SRC, 0x00000008 }, // DSP1RX2 SRC = ASPRX1 + { CS35L41_DSP1_RX3_SRC, 0x00000018 }, // DSP1RX3 SRC = VMON + { CS35L41_DSP1_RX4_SRC, 0x00000019 }, // DSP1RX4 SRC = IMON + { CS35L41_DSP1_RX5_SRC, 0x00000029 }, // DSP1RX5 SRC = VBSTMON + { CS35L41_AMP_DIG_VOL_CTRL, 0x00000000 }, // AMP_VOL_PCM 0.0 dB + { CS35L41_AMP_GAIN_CTRL, 0x00000233 }, // AMP_GAIN_PCM = 17.5dB AMP_GAIN_PDM = 19.5dB +}; + +static const struct reg_sequence cs35l41_hda_mute[] = { + { CS35L41_AMP_GAIN_CTRL, 0x00000000 }, // AMP_GAIN_PCM 0.5 dB + { CS35L41_AMP_DIG_VOL_CTRL, 0x0000A678 }, // AMP_VOL_PCM Mute +}; + +static void cs35l41_add_controls(struct cs35l41_hda *cs35l41) +{ + struct hda_cs_dsp_ctl_info info; + + info.device_name = cs35l41->amp_name; + info.fw_type = cs35l41->firmware_type; + info.card = cs35l41->codec->card; + + hda_cs_dsp_add_controls(&cs35l41->cs_dsp, &info); +} + +static const struct cs_dsp_client_ops client_ops = { + .control_remove = hda_cs_dsp_control_remove, +}; + +static int cs35l41_request_firmware_file(struct cs35l41_hda *cs35l41, + const struct firmware **firmware, char **filename, + const char *dir, const char *ssid, const char *amp_name, + int spkid, const char *filetype) +{ + const char * const dsp_name = cs35l41->cs_dsp.name; + char *s, c; + int ret = 0; + + if (spkid > -1 && ssid && amp_name) + *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s-spkid%d-%s.%s", dir, CS35L41_PART, + dsp_name, hda_cs_dsp_fw_ids[cs35l41->firmware_type], + ssid, spkid, amp_name, filetype); + else if (spkid > -1 && ssid) + *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s-spkid%d.%s", dir, CS35L41_PART, + dsp_name, hda_cs_dsp_fw_ids[cs35l41->firmware_type], + ssid, spkid, filetype); + else if (ssid && amp_name) + *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s-%s.%s", dir, CS35L41_PART, + dsp_name, hda_cs_dsp_fw_ids[cs35l41->firmware_type], + ssid, amp_name, filetype); + else if (ssid) + *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s.%s", dir, CS35L41_PART, + dsp_name, hda_cs_dsp_fw_ids[cs35l41->firmware_type], + ssid, filetype); + else + *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s.%s", dir, CS35L41_PART, + dsp_name, hda_cs_dsp_fw_ids[cs35l41->firmware_type], + filetype); + + if (*filename == NULL) + return -ENOMEM; + + /* + * Make sure that filename is lower-case and any non alpha-numeric + * characters except full stop and '/' are replaced with hyphens. + */ + s = *filename; + while (*s) { + c = *s; + if (isalnum(c)) + *s = tolower(c); + else if (c != '.' && c != '/') + *s = '-'; + s++; + } + + ret = firmware_request_nowarn(firmware, *filename, cs35l41->dev); + if (ret != 0) { + dev_dbg(cs35l41->dev, "Failed to request '%s'\n", *filename); + kfree(*filename); + *filename = NULL; + } + + return ret; +} + +static int cs35l41_request_firmware_files_spkid(struct cs35l41_hda *cs35l41, + const struct firmware **wmfw_firmware, + char **wmfw_filename, + const struct firmware **coeff_firmware, + char **coeff_filename) +{ + int ret; + + /* try cirrus/part-dspN-fwtype-sub<-spkidN><-ampname>.wmfw */ + ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, cs35l41->amp_name, + cs35l41->speaker_id, "wmfw"); + if (!ret) { + /* try cirrus/part-dspN-fwtype-sub<-spkidN><-ampname>.bin */ + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, cs35l41->amp_name, + cs35l41->speaker_id, "bin"); + return 0; + } + + /* try cirrus/part-dspN-fwtype-sub<-ampname>.wmfw */ + ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, + CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id, + cs35l41->amp_name, -1, "wmfw"); + if (!ret) { + /* try cirrus/part-dspN-fwtype-sub<-spkidN><-ampname>.bin */ + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id, + cs35l41->amp_name, cs35l41->speaker_id, "bin"); + return 0; + } + + /* try cirrus/part-dspN-fwtype-sub<-spkidN>.wmfw */ + ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, + CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id, + NULL, cs35l41->speaker_id, "wmfw"); + if (!ret) { + /* try cirrus/part-dspN-fwtype-sub<-spkidN><-ampname>.bin */ + ret = cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, + cs35l41->amp_name, cs35l41->speaker_id, "bin"); + if (ret) + /* try cirrus/part-dspN-fwtype-sub<-spkidN>.bin */ + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, + NULL, cs35l41->speaker_id, "bin"); + return 0; + } + + /* try cirrus/part-dspN-fwtype-sub.wmfw */ + ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, + CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id, + NULL, -1, "wmfw"); + if (!ret) { + /* try cirrus/part-dspN-fwtype-sub<-spkidN><-ampname>.bin */ + ret = cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, + cs35l41->amp_name, cs35l41->speaker_id, "bin"); + if (ret) + /* try cirrus/part-dspN-fwtype-sub<-spkidN>.bin */ + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, + NULL, cs35l41->speaker_id, "bin"); + return 0; + } + + /* fallback try cirrus/part-dspN-fwtype.wmfw */ + ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, + CS35L41_FIRMWARE_ROOT, NULL, NULL, -1, "wmfw"); + if (!ret) { + /* fallback try cirrus/part-dspN-fwtype.bin */ + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, NULL, NULL, -1, "bin"); + return 0; + } + + dev_warn(cs35l41->dev, "Failed to request firmware\n"); + + return ret; +} + +static int cs35l41_request_firmware_files(struct cs35l41_hda *cs35l41, + const struct firmware **wmfw_firmware, + char **wmfw_filename, + const struct firmware **coeff_firmware, + char **coeff_filename) +{ + int ret; + + if (cs35l41->speaker_id > -1) + return cs35l41_request_firmware_files_spkid(cs35l41, wmfw_firmware, wmfw_filename, + coeff_firmware, coeff_filename); + + /* try cirrus/part-dspN-fwtype-sub<-ampname>.wmfw */ + ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, + CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id, + cs35l41->amp_name, -1, "wmfw"); + if (!ret) { + /* try cirrus/part-dspN-fwtype-sub<-ampname>.bin */ + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id, + cs35l41->amp_name, -1, "bin"); + return 0; + } + + /* try cirrus/part-dspN-fwtype-sub.wmfw */ + ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, + CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id, + NULL, -1, "wmfw"); + if (!ret) { + /* try cirrus/part-dspN-fwtype-sub<-ampname>.bin */ + ret = cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, + cs35l41->amp_name, -1, "bin"); + if (ret) + /* try cirrus/part-dspN-fwtype-sub.bin */ + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, + NULL, -1, "bin"); + return 0; + } + + /* fallback try cirrus/part-dspN-fwtype.wmfw */ + ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, + CS35L41_FIRMWARE_ROOT, NULL, NULL, -1, "wmfw"); + if (!ret) { + /* fallback try cirrus/part-dspN-fwtype.bin */ + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, NULL, NULL, -1, "bin"); + return 0; + } + + dev_warn(cs35l41->dev, "Failed to request firmware\n"); + + return ret; +} + +#if IS_ENABLED(CONFIG_EFI) +static int cs35l41_apply_calibration(struct cs35l41_hda *cs35l41, unsigned int ambient, + unsigned int r0, unsigned int status, unsigned int checksum) +{ + int ret; + + ret = hda_cs_dsp_write_ctl(&cs35l41->cs_dsp, CAL_AMBIENT_DSP_CTL_NAME, CAL_DSP_CTL_TYPE, + CAL_DSP_CTL_ALG, &ambient, 4); + if (ret) { + dev_err(cs35l41->dev, "Cannot Write Control: %s - %d\n", CAL_AMBIENT_DSP_CTL_NAME, + ret); + return ret; + } + ret = hda_cs_dsp_write_ctl(&cs35l41->cs_dsp, CAL_R_DSP_CTL_NAME, CAL_DSP_CTL_TYPE, + CAL_DSP_CTL_ALG, &r0, 4); + if (ret) { + dev_err(cs35l41->dev, "Cannot Write Control: %s - %d\n", CAL_R_DSP_CTL_NAME, ret); + return ret; + } + ret = hda_cs_dsp_write_ctl(&cs35l41->cs_dsp, CAL_STATUS_DSP_CTL_NAME, CAL_DSP_CTL_TYPE, + CAL_DSP_CTL_ALG, &status, 4); + if (ret) { + dev_err(cs35l41->dev, "Cannot Write Control: %s - %d\n", CAL_STATUS_DSP_CTL_NAME, + ret); + return ret; + } + ret = hda_cs_dsp_write_ctl(&cs35l41->cs_dsp, CAL_CHECKSUM_DSP_CTL_NAME, CAL_DSP_CTL_TYPE, + CAL_DSP_CTL_ALG, &checksum, 4); + if (ret) { + dev_err(cs35l41->dev, "Cannot Write Control: %s - %d\n", CAL_CHECKSUM_DSP_CTL_NAME, + ret); + return ret; + } + + return 0; +} + +static int cs35l41_save_calibration(struct cs35l41_hda *cs35l41) +{ + static efi_guid_t efi_guid = EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d, 0x93, 0xfe, + 0x5a, 0xa3, 0x5d, 0xb3); + static efi_char16_t efi_name[] = L"CirrusSmartAmpCalibrationData"; + const struct cs35l41_amp_efi_data *efi_data; + const struct cs35l41_amp_cal_data *cl; + unsigned long data_size = 0; + efi_status_t status; + int ret = 0; + u8 *data = NULL; + u32 attr; + + /* Get real size of UEFI variable */ + status = efi.get_variable(efi_name, &efi_guid, &attr, &data_size, data); + if (status == EFI_BUFFER_TOO_SMALL) { + ret = -ENODEV; + /* Allocate data buffer of data_size bytes */ + data = vmalloc(data_size); + if (!data) + return -ENOMEM; + /* Get variable contents into buffer */ + status = efi.get_variable(efi_name, &efi_guid, &attr, &data_size, data); + if (status == EFI_SUCCESS) { + efi_data = (struct cs35l41_amp_efi_data *)data; + dev_dbg(cs35l41->dev, "Calibration: Size=%d, Amp Count=%d\n", + efi_data->size, efi_data->count); + if (efi_data->count > cs35l41->index) { + cl = &efi_data->data[cs35l41->index]; + dev_dbg(cs35l41->dev, + "Calibration: Ambient=%02x, Status=%02x, R0=%d\n", + cl->calAmbient, cl->calStatus, cl->calR); + + /* Calibration can only be applied whilst the DSP is not running */ + ret = cs35l41_apply_calibration(cs35l41, + cpu_to_be32(cl->calAmbient), + cpu_to_be32(cl->calR), + cpu_to_be32(cl->calStatus), + cpu_to_be32(cl->calR + 1)); + } + } + vfree(data); + } + return ret; +} +#else +static int cs35l41_save_calibration(struct cs35l41_hda *cs35l41) +{ + dev_warn(cs35l41->dev, "Calibration not supported without EFI support.\n"); + return 0; +} +#endif + +static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41) +{ + const struct firmware *coeff_firmware = NULL; + const struct firmware *wmfw_firmware = NULL; + struct cs_dsp *dsp = &cs35l41->cs_dsp; + char *coeff_filename = NULL; + char *wmfw_filename = NULL; + int ret; + + if (!cs35l41->halo_initialized) { + cs35l41_configure_cs_dsp(cs35l41->dev, cs35l41->regmap, dsp); + dsp->client_ops = &client_ops; + + ret = cs_dsp_halo_init(&cs35l41->cs_dsp); + if (ret) + return ret; + cs35l41->halo_initialized = true; + } + + ret = cs35l41_request_firmware_files(cs35l41, &wmfw_firmware, &wmfw_filename, + &coeff_firmware, &coeff_filename); + if (ret < 0) + return ret; + + dev_dbg(cs35l41->dev, "Loading WMFW Firmware: %s\n", wmfw_filename); + if (coeff_filename) + dev_dbg(cs35l41->dev, "Loading Coefficient File: %s\n", coeff_filename); + else + dev_warn(cs35l41->dev, "No Coefficient File available.\n"); + + ret = cs_dsp_power_up(dsp, wmfw_firmware, wmfw_filename, coeff_firmware, coeff_filename, + hda_cs_dsp_fw_ids[cs35l41->firmware_type]); + if (ret) + goto err_release; + + cs35l41_add_controls(cs35l41); + + ret = cs35l41_save_calibration(cs35l41); + +err_release: + release_firmware(wmfw_firmware); + release_firmware(coeff_firmware); + kfree(wmfw_filename); + kfree(coeff_filename); + + return ret; +} + +static void cs35l41_shutdown_dsp(struct cs35l41_hda *cs35l41) +{ + struct cs_dsp *dsp = &cs35l41->cs_dsp; + + cs_dsp_stop(dsp); + cs_dsp_power_down(dsp); + cs35l41->firmware_running = false; + dev_dbg(cs35l41->dev, "Unloaded Firmware\n"); +} + +static void cs35l41_remove_dsp(struct cs35l41_hda *cs35l41) +{ + struct cs_dsp *dsp = &cs35l41->cs_dsp; + + cancel_work_sync(&cs35l41->fw_load_work); + + mutex_lock(&cs35l41->fw_mutex); + cs35l41_shutdown_dsp(cs35l41); + cs_dsp_remove(dsp); + cs35l41->halo_initialized = false; + mutex_unlock(&cs35l41->fw_mutex); +} + +/* Protection release cycle to get the speaker out of Safe-Mode */ +static void cs35l41_error_release(struct device *dev, struct regmap *regmap, unsigned int mask) +{ + regmap_write(regmap, CS35L41_PROTECT_REL_ERR_IGN, 0); + regmap_set_bits(regmap, CS35L41_PROTECT_REL_ERR_IGN, mask); + regmap_clear_bits(regmap, CS35L41_PROTECT_REL_ERR_IGN, mask); +} + +/* Clear all errors to release safe mode. Global Enable must be cleared first. */ +static void cs35l41_irq_release(struct cs35l41_hda *cs35l41) +{ + cs35l41_error_release(cs35l41->dev, cs35l41->regmap, cs35l41->irq_errors); + cs35l41->irq_errors = 0; +} + +static void cs35l41_hda_playback_hook(struct device *dev, int action) +{ + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + struct regmap *reg = cs35l41->regmap; + int ret = 0; + + switch (action) { + case HDA_GEN_PCM_ACT_OPEN: + pm_runtime_get_sync(dev); + mutex_lock(&cs35l41->fw_mutex); + cs35l41->playback_started = true; + if (cs35l41->firmware_running) { + regmap_multi_reg_write(reg, cs35l41_hda_config_dsp, + ARRAY_SIZE(cs35l41_hda_config_dsp)); + regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, + CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK, + 1 << CS35L41_VMON_EN_SHIFT | 1 << CS35L41_IMON_EN_SHIFT); + cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, + CSPL_MBOX_CMD_RESUME); + } else { + regmap_multi_reg_write(reg, cs35l41_hda_config, + ARRAY_SIZE(cs35l41_hda_config)); + } + ret = regmap_update_bits(reg, CS35L41_PWR_CTRL2, + CS35L41_AMP_EN_MASK, 1 << CS35L41_AMP_EN_SHIFT); + if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) + regmap_write(reg, CS35L41_GPIO1_CTRL1, 0x00008001); + mutex_unlock(&cs35l41->fw_mutex); + break; + case HDA_GEN_PCM_ACT_PREPARE: + mutex_lock(&cs35l41->fw_mutex); + ret = cs35l41_global_enable(reg, cs35l41->hw_cfg.bst_type, 1); + mutex_unlock(&cs35l41->fw_mutex); + break; + case HDA_GEN_PCM_ACT_CLEANUP: + mutex_lock(&cs35l41->fw_mutex); + regmap_multi_reg_write(reg, cs35l41_hda_mute, ARRAY_SIZE(cs35l41_hda_mute)); + ret = cs35l41_global_enable(reg, cs35l41->hw_cfg.bst_type, 0); + mutex_unlock(&cs35l41->fw_mutex); + break; + case HDA_GEN_PCM_ACT_CLOSE: + mutex_lock(&cs35l41->fw_mutex); + ret = regmap_update_bits(reg, CS35L41_PWR_CTRL2, + CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT); + if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) + regmap_write(reg, CS35L41_GPIO1_CTRL1, 0x00000001); + if (cs35l41->firmware_running) { + cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, + CSPL_MBOX_CMD_PAUSE); + regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, + CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK, + 0 << CS35L41_VMON_EN_SHIFT | 0 << CS35L41_IMON_EN_SHIFT); + } + cs35l41_irq_release(cs35l41); + cs35l41->playback_started = false; + mutex_unlock(&cs35l41->fw_mutex); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + break; + default: + dev_warn(cs35l41->dev, "Playback action not supported: %d\n", action); + break; + } + + if (ret) + dev_err(cs35l41->dev, "Regmap access fail: %d\n", ret); +} + +static int cs35l41_hda_channel_map(struct device *dev, unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) +{ + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + static const char * const channel_name[] = { "L", "R" }; + + if (!cs35l41->amp_name) { + if (*rx_slot >= ARRAY_SIZE(channel_name)) + return -EINVAL; + + cs35l41->amp_name = devm_kasprintf(cs35l41->dev, GFP_KERNEL, "%s%d", + channel_name[*rx_slot], cs35l41->channel_index); + if (!cs35l41->amp_name) + return -ENOMEM; + } + + return cs35l41_set_channels(cs35l41->dev, cs35l41->regmap, tx_num, tx_slot, rx_num, + rx_slot); +} + +static void cs35l41_ready_for_reset(struct cs35l41_hda *cs35l41) +{ + mutex_lock(&cs35l41->fw_mutex); + if (cs35l41->firmware_running) { + + regcache_cache_only(cs35l41->regmap, false); + + cs35l41_exit_hibernate(cs35l41->dev, cs35l41->regmap); + cs35l41_shutdown_dsp(cs35l41); + cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type); + + regcache_cache_only(cs35l41->regmap, true); + regcache_mark_dirty(cs35l41->regmap); + } + mutex_unlock(&cs35l41->fw_mutex); +} + +static int cs35l41_system_suspend(struct device *dev) +{ + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + int ret; + + dev_dbg(cs35l41->dev, "System Suspend\n"); + + if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST_NO_VSPK_SWITCH) { + dev_err_once(cs35l41->dev, "System Suspend not supported\n"); + return 0; /* don't block the whole system suspend */ + } + + ret = pm_runtime_force_suspend(dev); + if (ret) + return ret; + + /* Shutdown DSP before system suspend */ + cs35l41_ready_for_reset(cs35l41); + + /* + * Reset GPIO may be shared, so cannot reset here. + * However beyond this point, amps may be powered down. + */ + return 0; +} + +static int cs35l41_system_resume(struct device *dev) +{ + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + int ret; + + dev_dbg(cs35l41->dev, "System Resume\n"); + + if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST_NO_VSPK_SWITCH) { + dev_err_once(cs35l41->dev, "System Resume not supported\n"); + return 0; /* don't block the whole system resume */ + } + + if (cs35l41->reset_gpio) { + usleep_range(2000, 2100); + gpiod_set_value_cansleep(cs35l41->reset_gpio, 1); + } + + usleep_range(2000, 2100); + + ret = pm_runtime_force_resume(dev); + + mutex_lock(&cs35l41->fw_mutex); + if (!ret && cs35l41->request_fw_load && !cs35l41->fw_request_ongoing) { + cs35l41->fw_request_ongoing = true; + schedule_work(&cs35l41->fw_load_work); + } + mutex_unlock(&cs35l41->fw_mutex); + + return ret; +} + +static int cs35l41_runtime_idle(struct device *dev) +{ + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + + if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST_NO_VSPK_SWITCH) + return -EBUSY; /* suspend not supported yet on this model */ + return 0; +} + +static int cs35l41_runtime_suspend(struct device *dev) +{ + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + int ret = 0; + + dev_dbg(cs35l41->dev, "Runtime Suspend\n"); + + if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST_NO_VSPK_SWITCH) { + dev_dbg(cs35l41->dev, "Runtime Suspend not supported\n"); + return 0; + } + + mutex_lock(&cs35l41->fw_mutex); + + if (cs35l41->playback_started) { + regmap_multi_reg_write(cs35l41->regmap, cs35l41_hda_mute, + ARRAY_SIZE(cs35l41_hda_mute)); + cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 0); + regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, + CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT); + if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) + regmap_write(cs35l41->regmap, CS35L41_GPIO1_CTRL1, 0x00000001); + regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, + CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK, + 0 << CS35L41_VMON_EN_SHIFT | 0 << CS35L41_IMON_EN_SHIFT); + cs35l41->playback_started = false; + } + + if (cs35l41->firmware_running) { + ret = cs35l41_enter_hibernate(cs35l41->dev, cs35l41->regmap, + cs35l41->hw_cfg.bst_type); + if (ret) + goto err; + } else { + cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type); + } + + regcache_cache_only(cs35l41->regmap, true); + regcache_mark_dirty(cs35l41->regmap); + +err: + mutex_unlock(&cs35l41->fw_mutex); + + return ret; +} + +static int cs35l41_runtime_resume(struct device *dev) +{ + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + int ret = 0; + + dev_dbg(cs35l41->dev, "Runtime Resume\n"); + + if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST_NO_VSPK_SWITCH) { + dev_dbg(cs35l41->dev, "Runtime Resume not supported\n"); + return 0; + } + + mutex_lock(&cs35l41->fw_mutex); + + regcache_cache_only(cs35l41->regmap, false); + + if (cs35l41->firmware_running) { + ret = cs35l41_exit_hibernate(cs35l41->dev, cs35l41->regmap); + if (ret) { + dev_warn(cs35l41->dev, "Unable to exit Hibernate."); + goto err; + } + } + + /* Test key needs to be unlocked to allow the OTP settings to re-apply */ + cs35l41_test_key_unlock(cs35l41->dev, cs35l41->regmap); + ret = regcache_sync(cs35l41->regmap); + cs35l41_test_key_lock(cs35l41->dev, cs35l41->regmap); + if (ret) { + dev_err(cs35l41->dev, "Failed to restore register cache: %d\n", ret); + goto err; + } + + if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) + cs35l41_init_boost(cs35l41->dev, cs35l41->regmap, &cs35l41->hw_cfg); + +err: + mutex_unlock(&cs35l41->fw_mutex); + + return ret; +} + +static int cs35l41_smart_amp(struct cs35l41_hda *cs35l41) +{ + int halo_sts; + int ret; + + ret = cs35l41_init_dsp(cs35l41); + if (ret) { + dev_warn(cs35l41->dev, "Cannot Initialize Firmware. Error: %d\n", ret); + goto clean_dsp; + } + + ret = cs35l41_write_fs_errata(cs35l41->dev, cs35l41->regmap); + if (ret) { + dev_err(cs35l41->dev, "Cannot Write FS Errata: %d\n", ret); + goto clean_dsp; + } + + ret = cs_dsp_run(&cs35l41->cs_dsp); + if (ret) { + dev_err(cs35l41->dev, "Fail to start dsp: %d\n", ret); + goto clean_dsp; + } + + ret = read_poll_timeout(hda_cs_dsp_read_ctl, ret, + be32_to_cpu(halo_sts) == HALO_STATE_CODE_RUN, + 1000, 15000, false, &cs35l41->cs_dsp, HALO_STATE_DSP_CTL_NAME, + HALO_STATE_DSP_CTL_TYPE, HALO_STATE_DSP_CTL_ALG, + &halo_sts, sizeof(halo_sts)); + + if (ret) { + dev_err(cs35l41->dev, "Timeout waiting for HALO Core to start. State: %d\n", + halo_sts); + goto clean_dsp; + } + + cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, CSPL_MBOX_CMD_PAUSE); + cs35l41->firmware_running = true; + + return 0; + +clean_dsp: + cs35l41_shutdown_dsp(cs35l41); + return ret; +} + +static void cs35l41_load_firmware(struct cs35l41_hda *cs35l41, bool load) +{ + if (cs35l41->firmware_running && !load) { + dev_dbg(cs35l41->dev, "Unloading Firmware\n"); + cs35l41_shutdown_dsp(cs35l41); + } else if (!cs35l41->firmware_running && load) { + dev_dbg(cs35l41->dev, "Loading Firmware\n"); + cs35l41_smart_amp(cs35l41); + } else { + dev_dbg(cs35l41->dev, "Unable to Load firmware.\n"); + } +} + +static int cs35l41_fw_load_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs35l41_hda *cs35l41 = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = cs35l41->request_fw_load; + return 0; +} + +static void cs35l41_fw_load_work(struct work_struct *work) +{ + struct cs35l41_hda *cs35l41 = container_of(work, struct cs35l41_hda, fw_load_work); + + pm_runtime_get_sync(cs35l41->dev); + + mutex_lock(&cs35l41->fw_mutex); + + /* Recheck if playback is ongoing, mutex will block playback during firmware loading */ + if (cs35l41->playback_started) + dev_err(cs35l41->dev, "Cannot Load/Unload firmware during Playback. Retrying...\n"); + else + cs35l41_load_firmware(cs35l41, cs35l41->request_fw_load); + + cs35l41->fw_request_ongoing = false; + mutex_unlock(&cs35l41->fw_mutex); + + pm_runtime_mark_last_busy(cs35l41->dev); + pm_runtime_put_autosuspend(cs35l41->dev); +} + +static int cs35l41_fw_load_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs35l41_hda *cs35l41 = snd_kcontrol_chip(kcontrol); + unsigned int ret = 0; + + mutex_lock(&cs35l41->fw_mutex); + + if (cs35l41->request_fw_load == ucontrol->value.integer.value[0]) + goto err; + + if (cs35l41->fw_request_ongoing) { + dev_dbg(cs35l41->dev, "Existing request not complete\n"); + ret = -EBUSY; + goto err; + } + + /* Check if playback is ongoing when initial request is made */ + if (cs35l41->playback_started) { + dev_err(cs35l41->dev, "Cannot Load/Unload firmware during Playback\n"); + ret = -EBUSY; + goto err; + } + + cs35l41->fw_request_ongoing = true; + cs35l41->request_fw_load = ucontrol->value.integer.value[0]; + schedule_work(&cs35l41->fw_load_work); + +err: + mutex_unlock(&cs35l41->fw_mutex); + + return ret; +} + +static int cs35l41_fw_type_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs35l41_hda *cs35l41 = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = cs35l41->firmware_type; + + return 0; +} + +static int cs35l41_fw_type_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs35l41_hda *cs35l41 = snd_kcontrol_chip(kcontrol); + + if (ucontrol->value.enumerated.item[0] < HDA_CS_DSP_NUM_FW) { + cs35l41->firmware_type = ucontrol->value.enumerated.item[0]; + return 0; + } + + return -EINVAL; +} + +static int cs35l41_fw_type_ctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(hda_cs_dsp_fw_ids), hda_cs_dsp_fw_ids); +} + +static int cs35l41_create_controls(struct cs35l41_hda *cs35l41) +{ + char fw_type_ctl_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + char fw_load_ctl_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct snd_kcontrol_new fw_type_ctl = { + .name = fw_type_ctl_name, + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .info = cs35l41_fw_type_ctl_info, + .get = cs35l41_fw_type_ctl_get, + .put = cs35l41_fw_type_ctl_put, + }; + struct snd_kcontrol_new fw_load_ctl = { + .name = fw_load_ctl_name, + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .info = snd_ctl_boolean_mono_info, + .get = cs35l41_fw_load_ctl_get, + .put = cs35l41_fw_load_ctl_put, + }; + int ret; + + scnprintf(fw_type_ctl_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s DSP1 Firmware Type", + cs35l41->amp_name); + scnprintf(fw_load_ctl_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s DSP1 Firmware Load", + cs35l41->amp_name); + + ret = snd_ctl_add(cs35l41->codec->card, snd_ctl_new1(&fw_type_ctl, cs35l41)); + if (ret) { + dev_err(cs35l41->dev, "Failed to add KControl %s = %d\n", fw_type_ctl.name, ret); + return ret; + } + + dev_dbg(cs35l41->dev, "Added Control %s\n", fw_type_ctl.name); + + ret = snd_ctl_add(cs35l41->codec->card, snd_ctl_new1(&fw_load_ctl, cs35l41)); + if (ret) { + dev_err(cs35l41->dev, "Failed to add KControl %s = %d\n", fw_load_ctl.name, ret); + return ret; + } + + dev_dbg(cs35l41->dev, "Added Control %s\n", fw_load_ctl.name); + + return 0; +} + +static int cs35l41_hda_bind(struct device *dev, struct device *master, void *master_data) +{ + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + struct hda_component *comps = master_data; + int ret = 0; + + if (!comps || cs35l41->index < 0 || cs35l41->index >= HDA_MAX_COMPONENTS) + return -EINVAL; + + comps = &comps[cs35l41->index]; + if (comps->dev) + return -EBUSY; + + pm_runtime_get_sync(dev); + + mutex_lock(&cs35l41->fw_mutex); + + comps->dev = dev; + if (!cs35l41->acpi_subsystem_id) + cs35l41->acpi_subsystem_id = kasprintf(GFP_KERNEL, "%.8x", + comps->codec->core.subsystem_id); + cs35l41->codec = comps->codec; + strscpy(comps->name, dev_name(dev), sizeof(comps->name)); + + cs35l41->firmware_type = HDA_CS_DSP_FW_SPK_PROT; + + if (firmware_autostart) { + dev_dbg(cs35l41->dev, "Firmware Autostart.\n"); + cs35l41->request_fw_load = true; + if (cs35l41_smart_amp(cs35l41) < 0) + dev_warn(cs35l41->dev, "Cannot Run Firmware, reverting to dsp bypass...\n"); + } else { + dev_dbg(cs35l41->dev, "Firmware Autostart is disabled.\n"); + } + + ret = cs35l41_create_controls(cs35l41); + + comps->playback_hook = cs35l41_hda_playback_hook; + + mutex_unlock(&cs35l41->fw_mutex); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static void cs35l41_hda_unbind(struct device *dev, struct device *master, void *master_data) +{ + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + struct hda_component *comps = master_data; + + if (comps[cs35l41->index].dev == dev) + memset(&comps[cs35l41->index], 0, sizeof(*comps)); +} + +static const struct component_ops cs35l41_hda_comp_ops = { + .bind = cs35l41_hda_bind, + .unbind = cs35l41_hda_unbind, +}; + +static irqreturn_t cs35l41_bst_short_err(int irq, void *data) +{ + struct cs35l41_hda *cs35l41 = data; + + dev_crit_ratelimited(cs35l41->dev, "LBST Error\n"); + set_bit(CS35L41_BST_SHORT_ERR_RLS_SHIFT, &cs35l41->irq_errors); + + return IRQ_HANDLED; +} + +static irqreturn_t cs35l41_bst_dcm_uvp_err(int irq, void *data) +{ + struct cs35l41_hda *cs35l41 = data; + + dev_crit_ratelimited(cs35l41->dev, "DCM VBST Under Voltage Error\n"); + set_bit(CS35L41_BST_UVP_ERR_RLS_SHIFT, &cs35l41->irq_errors); + + return IRQ_HANDLED; +} + +static irqreturn_t cs35l41_bst_ovp_err(int irq, void *data) +{ + struct cs35l41_hda *cs35l41 = data; + + dev_crit_ratelimited(cs35l41->dev, "VBST Over Voltage error\n"); + set_bit(CS35L41_BST_OVP_ERR_RLS_SHIFT, &cs35l41->irq_errors); + + return IRQ_HANDLED; +} + +static irqreturn_t cs35l41_temp_err(int irq, void *data) +{ + struct cs35l41_hda *cs35l41 = data; + + dev_crit_ratelimited(cs35l41->dev, "Over temperature error\n"); + set_bit(CS35L41_TEMP_ERR_RLS_SHIFT, &cs35l41->irq_errors); + + return IRQ_HANDLED; +} + +static irqreturn_t cs35l41_temp_warn(int irq, void *data) +{ + struct cs35l41_hda *cs35l41 = data; + + dev_crit_ratelimited(cs35l41->dev, "Over temperature warning\n"); + set_bit(CS35L41_TEMP_WARN_ERR_RLS_SHIFT, &cs35l41->irq_errors); + + return IRQ_HANDLED; +} + +static irqreturn_t cs35l41_amp_short(int irq, void *data) +{ + struct cs35l41_hda *cs35l41 = data; + + dev_crit_ratelimited(cs35l41->dev, "Amp short error\n"); + set_bit(CS35L41_AMP_SHORT_ERR_RLS_SHIFT, &cs35l41->irq_errors); + + return IRQ_HANDLED; +} + +static const struct cs35l41_irq cs35l41_irqs[] = { + CS35L41_IRQ(BST_OVP_ERR, "Boost Overvoltage Error", cs35l41_bst_ovp_err), + CS35L41_IRQ(BST_DCM_UVP_ERR, "Boost Undervoltage Error", cs35l41_bst_dcm_uvp_err), + CS35L41_IRQ(BST_SHORT_ERR, "Boost Inductor Short Error", cs35l41_bst_short_err), + CS35L41_IRQ(TEMP_WARN, "Temperature Warning", cs35l41_temp_warn), + CS35L41_IRQ(TEMP_ERR, "Temperature Error", cs35l41_temp_err), + CS35L41_IRQ(AMP_SHORT_ERR, "Amp Short", cs35l41_amp_short), +}; + +static const struct regmap_irq cs35l41_reg_irqs[] = { + CS35L41_REG_IRQ(IRQ1_STATUS1, BST_OVP_ERR), + CS35L41_REG_IRQ(IRQ1_STATUS1, BST_DCM_UVP_ERR), + CS35L41_REG_IRQ(IRQ1_STATUS1, BST_SHORT_ERR), + CS35L41_REG_IRQ(IRQ1_STATUS1, TEMP_WARN), + CS35L41_REG_IRQ(IRQ1_STATUS1, TEMP_ERR), + CS35L41_REG_IRQ(IRQ1_STATUS1, AMP_SHORT_ERR), +}; + +static struct regmap_irq_chip cs35l41_regmap_irq_chip = { + .name = "cs35l41 IRQ1 Controller", + .status_base = CS35L41_IRQ1_STATUS1, + .mask_base = CS35L41_IRQ1_MASK1, + .ack_base = CS35L41_IRQ1_STATUS1, + .num_regs = 4, + .irqs = cs35l41_reg_irqs, + .num_irqs = ARRAY_SIZE(cs35l41_reg_irqs), + .runtime_pm = true, +}; + +static int cs35l41_hda_apply_properties(struct cs35l41_hda *cs35l41) +{ + struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg; + bool using_irq = false; + int irq, irq_pol; + int ret; + int i; + + if (!cs35l41->hw_cfg.valid) + return -EINVAL; + + ret = cs35l41_init_boost(cs35l41->dev, cs35l41->regmap, hw_cfg); + if (ret) + return ret; + + if (hw_cfg->gpio1.valid) { + switch (hw_cfg->gpio1.func) { + case CS35L41_NOT_USED: + break; + case CS35l41_VSPK_SWITCH: + hw_cfg->gpio1.func = CS35L41_GPIO1_GPIO; + hw_cfg->gpio1.out_en = true; + break; + case CS35l41_SYNC: + hw_cfg->gpio1.func = CS35L41_GPIO1_MDSYNC; + break; + default: + dev_err(cs35l41->dev, "Invalid function %d for GPIO1\n", + hw_cfg->gpio1.func); + return -EINVAL; + } + } + + if (hw_cfg->gpio2.valid) { + switch (hw_cfg->gpio2.func) { + case CS35L41_NOT_USED: + break; + case CS35L41_INTERRUPT: + using_irq = true; + hw_cfg->gpio2.func = CS35L41_GPIO2_INT_OPEN_DRAIN; + break; + default: + dev_err(cs35l41->dev, "Invalid GPIO2 function %d\n", hw_cfg->gpio2.func); + return -EINVAL; + } + } + + irq_pol = cs35l41_gpio_config(cs35l41->regmap, hw_cfg); + + if (cs35l41->irq && using_irq) { + ret = devm_regmap_add_irq_chip(cs35l41->dev, cs35l41->regmap, cs35l41->irq, + IRQF_ONESHOT | IRQF_SHARED | irq_pol, + 0, &cs35l41_regmap_irq_chip, &cs35l41->irq_data); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(cs35l41_irqs); i++) { + irq = regmap_irq_get_virq(cs35l41->irq_data, cs35l41_irqs[i].irq); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(cs35l41->dev, irq, NULL, + cs35l41_irqs[i].handler, + IRQF_ONESHOT | IRQF_SHARED | irq_pol, + cs35l41_irqs[i].name, cs35l41); + if (ret) + return ret; + } + } + + return cs35l41_hda_channel_map(cs35l41->dev, 0, NULL, 1, &hw_cfg->spk_pos); +} + +static int cs35l41_get_speaker_id(struct device *dev, int amp_index, + int num_amps, int fixed_gpio_id) +{ + struct gpio_desc *speaker_id_desc; + int speaker_id = -ENODEV; + + if (fixed_gpio_id >= 0) { + dev_dbg(dev, "Found Fixed Speaker ID GPIO (index = %d)\n", fixed_gpio_id); + speaker_id_desc = gpiod_get_index(dev, NULL, fixed_gpio_id, GPIOD_IN); + if (IS_ERR(speaker_id_desc)) { + speaker_id = PTR_ERR(speaker_id_desc); + return speaker_id; + } + speaker_id = gpiod_get_value_cansleep(speaker_id_desc); + gpiod_put(speaker_id_desc); + dev_dbg(dev, "Speaker ID = %d\n", speaker_id); + } else { + int base_index; + int gpios_per_amp; + int count; + int tmp; + int i; + + count = gpiod_count(dev, "spk-id"); + if (count > 0) { + speaker_id = 0; + gpios_per_amp = count / num_amps; + base_index = gpios_per_amp * amp_index; + + if (count % num_amps) + return -EINVAL; + + dev_dbg(dev, "Found %d Speaker ID GPIOs per Amp\n", gpios_per_amp); + + for (i = 0; i < gpios_per_amp; i++) { + speaker_id_desc = gpiod_get_index(dev, "spk-id", i + base_index, + GPIOD_IN); + if (IS_ERR(speaker_id_desc)) { + speaker_id = PTR_ERR(speaker_id_desc); + break; + } + tmp = gpiod_get_value_cansleep(speaker_id_desc); + gpiod_put(speaker_id_desc); + if (tmp < 0) { + speaker_id = tmp; + break; + } + speaker_id |= tmp << i; + } + dev_dbg(dev, "Speaker ID = %d\n", speaker_id); + } + } + return speaker_id; +} + +/* + * Device CLSA010(0/1) doesn't have _DSD so a gpiod_get by the label reset won't work. + * And devices created by serial-multi-instantiate don't have their device struct + * pointing to the correct fwnode, so acpi_dev must be used here. + * And devm functions expect that the device requesting the resource has the correct + * fwnode. + */ +static int cs35l41_no_acpi_dsd(struct cs35l41_hda *cs35l41, struct device *physdev, int id, + const char *hid) +{ + struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg; + + /* check I2C address to assign the index */ + cs35l41->index = id == 0x40 ? 0 : 1; + cs35l41->channel_index = 0; + cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, 0, GPIOD_OUT_HIGH); + cs35l41->speaker_id = cs35l41_get_speaker_id(physdev, 0, 0, 2); + hw_cfg->spk_pos = cs35l41->index; + hw_cfg->gpio2.func = CS35L41_INTERRUPT; + hw_cfg->gpio2.valid = true; + hw_cfg->valid = true; + + if (strncmp(hid, "CLSA0100", 8) == 0) { + hw_cfg->bst_type = CS35L41_EXT_BOOST_NO_VSPK_SWITCH; + } else if (strncmp(hid, "CLSA0101", 8) == 0) { + hw_cfg->bst_type = CS35L41_EXT_BOOST; + hw_cfg->gpio1.func = CS35l41_VSPK_SWITCH; + hw_cfg->gpio1.valid = true; + } else { + /* + * Note: CLSA010(0/1) are special cases which use a slightly different design. + * All other HIDs e.g. CSC3551 require valid ACPI _DSD properties to be supported. + */ + dev_err(cs35l41->dev, "Error: ACPI _DSD Properties are missing for HID %s.\n", hid); + hw_cfg->valid = false; + hw_cfg->gpio1.valid = false; + hw_cfg->gpio2.valid = false; + return -EINVAL; + } + + return 0; +} + +static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, int id) +{ + struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg; + u32 values[HDA_MAX_COMPONENTS]; + struct acpi_device *adev; + struct device *physdev; + const char *sub; + char *property; + size_t nval; + int i, ret; + + adev = acpi_dev_get_first_match_dev(hid, NULL, -1); + if (!adev) { + dev_err(cs35l41->dev, "Failed to find an ACPI device for %s\n", hid); + return -ENODEV; + } + + physdev = get_device(acpi_get_first_physical_node(adev)); + acpi_dev_put(adev); + + sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); + if (IS_ERR(sub)) + sub = NULL; + cs35l41->acpi_subsystem_id = sub; + + property = "cirrus,dev-index"; + ret = device_property_count_u32(physdev, property); + if (ret <= 0) { + ret = cs35l41_no_acpi_dsd(cs35l41, physdev, id, hid); + goto err_put_physdev; + } + if (ret > ARRAY_SIZE(values)) { + ret = -EINVAL; + goto err; + } + nval = ret; + + ret = device_property_read_u32_array(physdev, property, values, nval); + if (ret) + goto err; + + cs35l41->index = -1; + for (i = 0; i < nval; i++) { + if (values[i] == id) { + cs35l41->index = i; + break; + } + } + if (cs35l41->index == -1) { + dev_err(cs35l41->dev, "No index found in %s\n", property); + ret = -ENODEV; + goto err; + } + + /* To use the same release code for all laptop variants we can't use devm_ version of + * gpiod_get here, as CLSA010* don't have a fully functional bios with an _DSD node + */ + cs35l41->reset_gpio = fwnode_gpiod_get_index(acpi_fwnode_handle(adev), "reset", cs35l41->index, + GPIOD_OUT_LOW, "cs35l41-reset"); + + property = "cirrus,speaker-position"; + ret = device_property_read_u32_array(physdev, property, values, nval); + if (ret) + goto err; + hw_cfg->spk_pos = values[cs35l41->index]; + + cs35l41->channel_index = 0; + for (i = 0; i < cs35l41->index; i++) + if (values[i] == hw_cfg->spk_pos) + cs35l41->channel_index++; + + property = "cirrus,gpio1-func"; + ret = device_property_read_u32_array(physdev, property, values, nval); + if (ret) + goto err; + hw_cfg->gpio1.func = values[cs35l41->index]; + hw_cfg->gpio1.valid = true; + + property = "cirrus,gpio2-func"; + ret = device_property_read_u32_array(physdev, property, values, nval); + if (ret) + goto err; + hw_cfg->gpio2.func = values[cs35l41->index]; + hw_cfg->gpio2.valid = true; + + property = "cirrus,boost-peak-milliamp"; + ret = device_property_read_u32_array(physdev, property, values, nval); + if (ret == 0) + hw_cfg->bst_ipk = values[cs35l41->index]; + else + hw_cfg->bst_ipk = -1; + + property = "cirrus,boost-ind-nanohenry"; + ret = device_property_read_u32_array(physdev, property, values, nval); + if (ret == 0) + hw_cfg->bst_ind = values[cs35l41->index]; + else + hw_cfg->bst_ind = -1; + + property = "cirrus,boost-cap-microfarad"; + ret = device_property_read_u32_array(physdev, property, values, nval); + if (ret == 0) + hw_cfg->bst_cap = values[cs35l41->index]; + else + hw_cfg->bst_cap = -1; + + cs35l41->speaker_id = cs35l41_get_speaker_id(physdev, cs35l41->index, nval, -1); + + if (hw_cfg->bst_ind > 0 || hw_cfg->bst_cap > 0 || hw_cfg->bst_ipk > 0) + hw_cfg->bst_type = CS35L41_INT_BOOST; + else + hw_cfg->bst_type = CS35L41_EXT_BOOST; + + hw_cfg->valid = true; + put_device(physdev); + + return 0; + +err: + dev_err(cs35l41->dev, "Failed property %s: %d\n", property, ret); +err_put_physdev: + put_device(physdev); + + return ret; +} + +int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int irq, + struct regmap *regmap) +{ + unsigned int int_sts, regid, reg_revid, mtl_revid, chipid, int_status; + struct cs35l41_hda *cs35l41; + int ret; + + BUILD_BUG_ON(ARRAY_SIZE(cs35l41_irqs) != ARRAY_SIZE(cs35l41_reg_irqs)); + BUILD_BUG_ON(ARRAY_SIZE(cs35l41_irqs) != CS35L41_NUM_IRQ); + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + cs35l41 = devm_kzalloc(dev, sizeof(*cs35l41), GFP_KERNEL); + if (!cs35l41) + return -ENOMEM; + + cs35l41->dev = dev; + cs35l41->irq = irq; + cs35l41->regmap = regmap; + dev_set_drvdata(dev, cs35l41); + + ret = cs35l41_hda_read_acpi(cs35l41, device_name, id); + if (ret) + return dev_err_probe(cs35l41->dev, ret, "Platform not supported\n"); + + if (IS_ERR(cs35l41->reset_gpio)) { + ret = PTR_ERR(cs35l41->reset_gpio); + cs35l41->reset_gpio = NULL; + if (ret == -EBUSY) { + dev_info(cs35l41->dev, "Reset line busy, assuming shared reset\n"); + } else { + dev_err_probe(cs35l41->dev, ret, "Failed to get reset GPIO\n"); + goto err; + } + } + if (cs35l41->reset_gpio) { + usleep_range(2000, 2100); + gpiod_set_value_cansleep(cs35l41->reset_gpio, 1); + } + + usleep_range(2000, 2100); + + ret = regmap_read_poll_timeout(cs35l41->regmap, CS35L41_IRQ1_STATUS4, int_status, + int_status & CS35L41_OTP_BOOT_DONE, 1000, 100000); + if (ret) { + dev_err(cs35l41->dev, "Failed waiting for OTP_BOOT_DONE: %d\n", ret); + goto err; + } + + ret = regmap_read(cs35l41->regmap, CS35L41_IRQ1_STATUS3, &int_sts); + if (ret || (int_sts & CS35L41_OTP_BOOT_ERR)) { + dev_err(cs35l41->dev, "OTP Boot status %x error: %d\n", + int_sts & CS35L41_OTP_BOOT_ERR, ret); + ret = -EIO; + goto err; + } + + ret = regmap_read(cs35l41->regmap, CS35L41_DEVID, ®id); + if (ret) { + dev_err(cs35l41->dev, "Get Device ID failed: %d\n", ret); + goto err; + } + + ret = regmap_read(cs35l41->regmap, CS35L41_REVID, ®_revid); + if (ret) { + dev_err(cs35l41->dev, "Get Revision ID failed: %d\n", ret); + goto err; + } + + mtl_revid = reg_revid & CS35L41_MTLREVID_MASK; + + chipid = (mtl_revid % 2) ? CS35L41R_CHIP_ID : CS35L41_CHIP_ID; + if (regid != chipid) { + dev_err(cs35l41->dev, "CS35L41 Device ID (%X). Expected ID %X\n", regid, chipid); + ret = -ENODEV; + goto err; + } + + ret = cs35l41_test_key_unlock(cs35l41->dev, cs35l41->regmap); + if (ret) + goto err; + + ret = cs35l41_register_errata_patch(cs35l41->dev, cs35l41->regmap, reg_revid); + if (ret) + goto err; + + ret = cs35l41_otp_unpack(cs35l41->dev, cs35l41->regmap); + if (ret) { + dev_err(cs35l41->dev, "OTP Unpack failed: %d\n", ret); + goto err; + } + + ret = cs35l41_test_key_lock(cs35l41->dev, cs35l41->regmap); + if (ret) + goto err; + + INIT_WORK(&cs35l41->fw_load_work, cs35l41_fw_load_work); + mutex_init(&cs35l41->fw_mutex); + + pm_runtime_set_autosuspend_delay(cs35l41->dev, 3000); + pm_runtime_use_autosuspend(cs35l41->dev); + pm_runtime_mark_last_busy(cs35l41->dev); + pm_runtime_set_active(cs35l41->dev); + pm_runtime_get_noresume(cs35l41->dev); + pm_runtime_enable(cs35l41->dev); + + ret = cs35l41_hda_apply_properties(cs35l41); + if (ret) + goto err_pm; + + pm_runtime_put_autosuspend(cs35l41->dev); + + ret = component_add(cs35l41->dev, &cs35l41_hda_comp_ops); + if (ret) { + dev_err(cs35l41->dev, "Register component failed: %d\n", ret); + goto err_pm; + } + + dev_info(cs35l41->dev, "Cirrus Logic CS35L41 (%x), Revision: %02X\n", regid, reg_revid); + + return 0; + +err_pm: + pm_runtime_dont_use_autosuspend(cs35l41->dev); + pm_runtime_disable(cs35l41->dev); + pm_runtime_put_noidle(cs35l41->dev); + +err: + if (cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type)) + gpiod_set_value_cansleep(cs35l41->reset_gpio, 0); + gpiod_put(cs35l41->reset_gpio); + kfree(cs35l41->acpi_subsystem_id); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(cs35l41_hda_probe, SND_HDA_SCODEC_CS35L41); + +void cs35l41_hda_remove(struct device *dev) +{ + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + + pm_runtime_get_sync(cs35l41->dev); + pm_runtime_dont_use_autosuspend(cs35l41->dev); + pm_runtime_disable(cs35l41->dev); + + if (cs35l41->halo_initialized) + cs35l41_remove_dsp(cs35l41); + + component_del(cs35l41->dev, &cs35l41_hda_comp_ops); + + pm_runtime_put_noidle(cs35l41->dev); + + if (cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type)) + gpiod_set_value_cansleep(cs35l41->reset_gpio, 0); + gpiod_put(cs35l41->reset_gpio); + kfree(cs35l41->acpi_subsystem_id); +} +EXPORT_SYMBOL_NS_GPL(cs35l41_hda_remove, SND_HDA_SCODEC_CS35L41); + +const struct dev_pm_ops cs35l41_hda_pm_ops = { + RUNTIME_PM_OPS(cs35l41_runtime_suspend, cs35l41_runtime_resume, + cs35l41_runtime_idle) + SYSTEM_SLEEP_PM_OPS(cs35l41_system_suspend, cs35l41_system_resume) +}; +EXPORT_SYMBOL_NS_GPL(cs35l41_hda_pm_ops, SND_HDA_SCODEC_CS35L41); + +MODULE_DESCRIPTION("CS35L41 HDA Driver"); +MODULE_IMPORT_NS(SND_HDA_CS_DSP_CONTROLS); +MODULE_AUTHOR("Lucas Tanure, Cirrus Logic Inc, <tanureal@opensource.cirrus.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/pci/hda/cs35l41_hda.h b/sound/pci/hda/cs35l41_hda.h new file mode 100644 index 000000000..bdb35f3be --- /dev/null +++ b/sound/pci/hda/cs35l41_hda.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * CS35L41 ALSA HDA audio driver + * + * Copyright 2021 Cirrus Logic, Inc. + * + * Author: Lucas Tanure <tanureal@opensource.cirrus.com> + */ + +#ifndef __CS35L41_HDA_H__ +#define __CS35L41_HDA_H__ + +#include <linux/efi.h> +#include <linux/regulator/consumer.h> +#include <linux/gpio/consumer.h> +#include <linux/device.h> +#include <sound/cs35l41.h> + +#include <linux/firmware/cirrus/cs_dsp.h> +#include <linux/firmware/cirrus/wmfw.h> + +struct cs35l41_amp_cal_data { + u32 calTarget[2]; + u32 calTime[2]; + s8 calAmbient; + u8 calStatus; + u16 calR; +} __packed; + +struct cs35l41_amp_efi_data { + u32 size; + u32 count; + struct cs35l41_amp_cal_data data[]; +} __packed; + +enum cs35l41_hda_spk_pos { + CS35l41_LEFT, + CS35l41_RIGHT, +}; + +enum cs35l41_hda_gpio_function { + CS35L41_NOT_USED, + CS35l41_VSPK_SWITCH, + CS35L41_INTERRUPT, + CS35l41_SYNC, +}; + +struct cs35l41_hda { + struct device *dev; + struct regmap *regmap; + struct gpio_desc *reset_gpio; + struct cs35l41_hw_cfg hw_cfg; + struct hda_codec *codec; + + int irq; + int index; + int channel_index; + unsigned volatile long irq_errors; + const char *amp_name; + const char *acpi_subsystem_id; + int firmware_type; + int speaker_id; + struct mutex fw_mutex; + struct work_struct fw_load_work; + + struct regmap_irq_chip_data *irq_data; + bool firmware_running; + bool request_fw_load; + bool fw_request_ongoing; + bool halo_initialized; + bool playback_started; + struct cs_dsp cs_dsp; +}; + +enum halo_state { + HALO_STATE_CODE_INIT_DOWNLOAD = 0, + HALO_STATE_CODE_START, + HALO_STATE_CODE_RUN +}; + +extern const struct dev_pm_ops cs35l41_hda_pm_ops; + +int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int irq, + struct regmap *regmap); +void cs35l41_hda_remove(struct device *dev); + +#endif /*__CS35L41_HDA_H__*/ diff --git a/sound/pci/hda/cs35l41_hda_i2c.c b/sound/pci/hda/cs35l41_hda_i2c.c new file mode 100644 index 000000000..5a6252d9b --- /dev/null +++ b/sound/pci/hda/cs35l41_hda_i2c.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// CS35l41 HDA I2C driver +// +// Copyright 2021 Cirrus Logic, Inc. +// +// Author: Lucas Tanure <tanureal@opensource.cirrus.com> + +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/i2c.h> + +#include "cs35l41_hda.h" + +static int cs35l41_hda_i2c_probe(struct i2c_client *clt, const struct i2c_device_id *id) +{ + const char *device_name; + + /* + * Compare against the device name so it works for SPI, normal ACPI + * and for ACPI by serial-multi-instantiate matching cases. + */ + if (strstr(dev_name(&clt->dev), "CLSA0100")) + device_name = "CLSA0100"; + else if (strstr(dev_name(&clt->dev), "CLSA0101")) + device_name = "CLSA0101"; + else if (strstr(dev_name(&clt->dev), "CSC3551")) + device_name = "CSC3551"; + else + return -ENODEV; + + return cs35l41_hda_probe(&clt->dev, device_name, clt->addr, clt->irq, + devm_regmap_init_i2c(clt, &cs35l41_regmap_i2c)); +} + +static void cs35l41_hda_i2c_remove(struct i2c_client *clt) +{ + cs35l41_hda_remove(&clt->dev); +} + +static const struct i2c_device_id cs35l41_hda_i2c_id[] = { + { "cs35l41-hda", 0 }, + {} +}; + +static const struct acpi_device_id cs35l41_acpi_hda_match[] = { + {"CLSA0100", 0 }, + {"CLSA0101", 0 }, + {"CSC3551", 0 }, + {} +}; +MODULE_DEVICE_TABLE(acpi, cs35l41_acpi_hda_match); + +static struct i2c_driver cs35l41_i2c_driver = { + .driver = { + .name = "cs35l41-hda", + .acpi_match_table = cs35l41_acpi_hda_match, + .pm = &cs35l41_hda_pm_ops, + }, + .id_table = cs35l41_hda_i2c_id, + .probe = cs35l41_hda_i2c_probe, + .remove = cs35l41_hda_i2c_remove, +}; +module_i2c_driver(cs35l41_i2c_driver); + +MODULE_DESCRIPTION("HDA CS35L41 driver"); +MODULE_IMPORT_NS(SND_HDA_SCODEC_CS35L41); +MODULE_AUTHOR("Lucas Tanure <tanureal@opensource.cirrus.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/pci/hda/cs35l41_hda_spi.c b/sound/pci/hda/cs35l41_hda_spi.c new file mode 100644 index 000000000..71979cfb4 --- /dev/null +++ b/sound/pci/hda/cs35l41_hda_spi.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// CS35l41 HDA SPI driver +// +// Copyright 2021 Cirrus Logic, Inc. +// +// Author: Lucas Tanure <tanureal@opensource.cirrus.com> + +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/spi/spi.h> + +#include "cs35l41_hda.h" + +static int cs35l41_hda_spi_probe(struct spi_device *spi) +{ + const char *device_name; + + /* + * Compare against the device name so it works for SPI, normal ACPI + * and for ACPI by serial-multi-instantiate matching cases. + */ + if (strstr(dev_name(&spi->dev), "CSC3551")) + device_name = "CSC3551"; + else + return -ENODEV; + + return cs35l41_hda_probe(&spi->dev, device_name, spi->chip_select, spi->irq, + devm_regmap_init_spi(spi, &cs35l41_regmap_spi)); +} + +static void cs35l41_hda_spi_remove(struct spi_device *spi) +{ + cs35l41_hda_remove(&spi->dev); +} + +static const struct spi_device_id cs35l41_hda_spi_id[] = { + { "cs35l41-hda", 0 }, + {} +}; + +static const struct acpi_device_id cs35l41_acpi_hda_match[] = { + { "CSC3551", 0 }, + {} +}; +MODULE_DEVICE_TABLE(acpi, cs35l41_acpi_hda_match); + +static struct spi_driver cs35l41_spi_driver = { + .driver = { + .name = "cs35l41-hda", + .acpi_match_table = cs35l41_acpi_hda_match, + .pm = &cs35l41_hda_pm_ops, + }, + .id_table = cs35l41_hda_spi_id, + .probe = cs35l41_hda_spi_probe, + .remove = cs35l41_hda_spi_remove, +}; +module_spi_driver(cs35l41_spi_driver); + +MODULE_DESCRIPTION("HDA CS35L41 driver"); +MODULE_IMPORT_NS(SND_HDA_SCODEC_CS35L41); +MODULE_AUTHOR("Lucas Tanure <tanureal@opensource.cirrus.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/pci/hda/hda_auto_parser.c b/sound/pci/hda/hda_auto_parser.c new file mode 100644 index 000000000..7c6b1fe8d --- /dev/null +++ b/sound/pci/hda/hda_auto_parser.c @@ -0,0 +1,1057 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BIOS auto-parser helper functions for HD-audio + * + * Copyright (c) 2012 Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/slab.h> +#include <linux/export.h> +#include <linux/sort.h> +#include <sound/core.h> +#include <sound/hda_codec.h> +#include "hda_local.h" +#include "hda_auto_parser.h" + +/* + * Helper for automatic pin configuration + */ + +static int is_in_nid_list(hda_nid_t nid, const hda_nid_t *list) +{ + for (; *list; list++) + if (*list == nid) + return 1; + return 0; +} + +/* a pair of input pin and its sequence */ +struct auto_out_pin { + hda_nid_t pin; + short seq; +}; + +static int compare_seq(const void *ap, const void *bp) +{ + const struct auto_out_pin *a = ap; + const struct auto_out_pin *b = bp; + return (int)(a->seq - b->seq); +} + +/* + * Sort an associated group of pins according to their sequence numbers. + * then store it to a pin array. + */ +static void sort_pins_by_sequence(hda_nid_t *pins, struct auto_out_pin *list, + int num_pins) +{ + int i; + sort(list, num_pins, sizeof(list[0]), compare_seq, NULL); + for (i = 0; i < num_pins; i++) + pins[i] = list[i].pin; +} + + +/* add the found input-pin to the cfg->inputs[] table */ +static void add_auto_cfg_input_pin(struct hda_codec *codec, struct auto_pin_cfg *cfg, + hda_nid_t nid, int type) +{ + if (cfg->num_inputs < AUTO_CFG_MAX_INS) { + cfg->inputs[cfg->num_inputs].pin = nid; + cfg->inputs[cfg->num_inputs].type = type; + cfg->inputs[cfg->num_inputs].has_boost_on_pin = + nid_has_volume(codec, nid, HDA_INPUT); + cfg->num_inputs++; + } +} + +static int compare_input_type(const void *ap, const void *bp) +{ + const struct auto_pin_cfg_item *a = ap; + const struct auto_pin_cfg_item *b = bp; + if (a->type != b->type) + return (int)(a->type - b->type); + + /* If has both hs_mic and hp_mic, pick the hs_mic ahead of hp_mic. */ + if (a->is_headset_mic && b->is_headphone_mic) + return -1; /* don't swap */ + else if (a->is_headphone_mic && b->is_headset_mic) + return 1; /* swap */ + + /* In case one has boost and the other one has not, + pick the one with boost first. */ + return (int)(b->has_boost_on_pin - a->has_boost_on_pin); +} + +/* Reorder the surround channels + * ALSA sequence is front/surr/clfe/side + * HDA sequence is: + * 4-ch: front/surr => OK as it is + * 6-ch: front/clfe/surr + * 8-ch: front/clfe/rear/side|fc + */ +static void reorder_outputs(unsigned int nums, hda_nid_t *pins) +{ + switch (nums) { + case 3: + case 4: + swap(pins[1], pins[2]); + break; + } +} + +/* check whether the given pin has a proper pin I/O capability bit */ +static bool check_pincap_validity(struct hda_codec *codec, hda_nid_t pin, + unsigned int dev) +{ + unsigned int pincap = snd_hda_query_pin_caps(codec, pin); + + /* some old hardware don't return the proper pincaps */ + if (!pincap) + return true; + + switch (dev) { + case AC_JACK_LINE_OUT: + case AC_JACK_SPEAKER: + case AC_JACK_HP_OUT: + case AC_JACK_SPDIF_OUT: + case AC_JACK_DIG_OTHER_OUT: + return !!(pincap & AC_PINCAP_OUT); + default: + return !!(pincap & AC_PINCAP_IN); + } +} + +static bool can_be_headset_mic(struct hda_codec *codec, + struct auto_pin_cfg_item *item, + int seq_number) +{ + int attr; + unsigned int def_conf; + if (item->type != AUTO_PIN_MIC) + return false; + + if (item->is_headset_mic || item->is_headphone_mic) + return false; /* Already assigned */ + + def_conf = snd_hda_codec_get_pincfg(codec, item->pin); + attr = snd_hda_get_input_pin_attr(def_conf); + if (attr <= INPUT_PIN_ATTR_DOCK) + return false; + + if (seq_number >= 0) { + int seq = get_defcfg_sequence(def_conf); + if (seq != seq_number) + return false; + } + + return true; +} + +/* + * Parse all pin widgets and store the useful pin nids to cfg + * + * The number of line-outs or any primary output is stored in line_outs, + * and the corresponding output pins are assigned to line_out_pins[], + * in the order of front, rear, CLFE, side, ... + * + * If more extra outputs (speaker and headphone) are found, the pins are + * assisnged to hp_pins[] and speaker_pins[], respectively. If no line-out jack + * is detected, one of speaker of HP pins is assigned as the primary + * output, i.e. to line_out_pins[0]. So, line_outs is always positive + * if any analog output exists. + * + * The analog input pins are assigned to inputs array. + * The digital input/output pins are assigned to dig_in_pin and dig_out_pin, + * respectively. + */ +int snd_hda_parse_pin_defcfg(struct hda_codec *codec, + struct auto_pin_cfg *cfg, + const hda_nid_t *ignore_nids, + unsigned int cond_flags) +{ + hda_nid_t nid; + short seq, assoc_line_out; + struct auto_out_pin line_out[ARRAY_SIZE(cfg->line_out_pins)]; + struct auto_out_pin speaker_out[ARRAY_SIZE(cfg->speaker_pins)]; + struct auto_out_pin hp_out[ARRAY_SIZE(cfg->hp_pins)]; + int i; + + if (!snd_hda_get_int_hint(codec, "parser_flags", &i)) + cond_flags = i; + + memset(cfg, 0, sizeof(*cfg)); + + memset(line_out, 0, sizeof(line_out)); + memset(speaker_out, 0, sizeof(speaker_out)); + memset(hp_out, 0, sizeof(hp_out)); + assoc_line_out = 0; + + for_each_hda_codec_node(nid, codec) { + unsigned int wid_caps = get_wcaps(codec, nid); + unsigned int wid_type = get_wcaps_type(wid_caps); + unsigned int def_conf; + short assoc, loc, conn, dev; + + /* read all default configuration for pin complex */ + if (wid_type != AC_WID_PIN) + continue; + /* ignore the given nids (e.g. pc-beep returns error) */ + if (ignore_nids && is_in_nid_list(nid, ignore_nids)) + continue; + + def_conf = snd_hda_codec_get_pincfg(codec, nid); + conn = get_defcfg_connect(def_conf); + if (conn == AC_JACK_PORT_NONE) + continue; + loc = get_defcfg_location(def_conf); + dev = get_defcfg_device(def_conf); + + /* workaround for buggy BIOS setups */ + if (dev == AC_JACK_LINE_OUT) { + if (conn == AC_JACK_PORT_FIXED || + conn == AC_JACK_PORT_BOTH) + dev = AC_JACK_SPEAKER; + } + + if (!check_pincap_validity(codec, nid, dev)) + continue; + + switch (dev) { + case AC_JACK_LINE_OUT: + seq = get_defcfg_sequence(def_conf); + assoc = get_defcfg_association(def_conf); + + if (!(wid_caps & AC_WCAP_STEREO)) + if (!cfg->mono_out_pin) + cfg->mono_out_pin = nid; + if (!assoc) + continue; + if (!assoc_line_out) + assoc_line_out = assoc; + else if (assoc_line_out != assoc) { + codec_info(codec, + "ignore pin 0x%x with mismatching assoc# 0x%x vs 0x%x\n", + nid, assoc, assoc_line_out); + continue; + } + if (cfg->line_outs >= ARRAY_SIZE(cfg->line_out_pins)) { + codec_info(codec, + "ignore pin 0x%x, too many assigned pins\n", + nid); + continue; + } + line_out[cfg->line_outs].pin = nid; + line_out[cfg->line_outs].seq = seq; + cfg->line_outs++; + break; + case AC_JACK_SPEAKER: + seq = get_defcfg_sequence(def_conf); + assoc = get_defcfg_association(def_conf); + if (cfg->speaker_outs >= ARRAY_SIZE(cfg->speaker_pins)) { + codec_info(codec, + "ignore pin 0x%x, too many assigned pins\n", + nid); + continue; + } + speaker_out[cfg->speaker_outs].pin = nid; + speaker_out[cfg->speaker_outs].seq = (assoc << 4) | seq; + cfg->speaker_outs++; + break; + case AC_JACK_HP_OUT: + seq = get_defcfg_sequence(def_conf); + assoc = get_defcfg_association(def_conf); + if (cfg->hp_outs >= ARRAY_SIZE(cfg->hp_pins)) { + codec_info(codec, + "ignore pin 0x%x, too many assigned pins\n", + nid); + continue; + } + hp_out[cfg->hp_outs].pin = nid; + hp_out[cfg->hp_outs].seq = (assoc << 4) | seq; + cfg->hp_outs++; + break; + case AC_JACK_MIC_IN: + add_auto_cfg_input_pin(codec, cfg, nid, AUTO_PIN_MIC); + break; + case AC_JACK_LINE_IN: + add_auto_cfg_input_pin(codec, cfg, nid, AUTO_PIN_LINE_IN); + break; + case AC_JACK_CD: + add_auto_cfg_input_pin(codec, cfg, nid, AUTO_PIN_CD); + break; + case AC_JACK_AUX: + add_auto_cfg_input_pin(codec, cfg, nid, AUTO_PIN_AUX); + break; + case AC_JACK_SPDIF_OUT: + case AC_JACK_DIG_OTHER_OUT: + if (cfg->dig_outs >= ARRAY_SIZE(cfg->dig_out_pins)) { + codec_info(codec, + "ignore pin 0x%x, too many assigned pins\n", + nid); + continue; + } + cfg->dig_out_pins[cfg->dig_outs] = nid; + cfg->dig_out_type[cfg->dig_outs] = + (loc == AC_JACK_LOC_HDMI) ? + HDA_PCM_TYPE_HDMI : HDA_PCM_TYPE_SPDIF; + cfg->dig_outs++; + break; + case AC_JACK_SPDIF_IN: + case AC_JACK_DIG_OTHER_IN: + cfg->dig_in_pin = nid; + if (loc == AC_JACK_LOC_HDMI) + cfg->dig_in_type = HDA_PCM_TYPE_HDMI; + else + cfg->dig_in_type = HDA_PCM_TYPE_SPDIF; + break; + } + } + + /* Find a pin that could be a headset or headphone mic */ + if (cond_flags & HDA_PINCFG_HEADSET_MIC || cond_flags & HDA_PINCFG_HEADPHONE_MIC) { + bool hsmic = !!(cond_flags & HDA_PINCFG_HEADSET_MIC); + bool hpmic = !!(cond_flags & HDA_PINCFG_HEADPHONE_MIC); + for (i = 0; (hsmic || hpmic) && (i < cfg->num_inputs); i++) + if (hsmic && can_be_headset_mic(codec, &cfg->inputs[i], 0xc)) { + cfg->inputs[i].is_headset_mic = 1; + hsmic = false; + } else if (hpmic && can_be_headset_mic(codec, &cfg->inputs[i], 0xd)) { + cfg->inputs[i].is_headphone_mic = 1; + hpmic = false; + } + + /* If we didn't find our sequence number mark, fall back to any sequence number */ + for (i = 0; (hsmic || hpmic) && (i < cfg->num_inputs); i++) { + if (!can_be_headset_mic(codec, &cfg->inputs[i], -1)) + continue; + if (hsmic) { + cfg->inputs[i].is_headset_mic = 1; + hsmic = false; + } else if (hpmic) { + cfg->inputs[i].is_headphone_mic = 1; + hpmic = false; + } + } + + if (hsmic) + codec_dbg(codec, "Told to look for a headset mic, but didn't find any.\n"); + if (hpmic) + codec_dbg(codec, "Told to look for a headphone mic, but didn't find any.\n"); + } + + /* FIX-UP: + * If no line-out is defined but multiple HPs are found, + * some of them might be the real line-outs. + */ + if (!cfg->line_outs && cfg->hp_outs > 1 && + !(cond_flags & HDA_PINCFG_NO_HP_FIXUP)) { + i = 0; + while (i < cfg->hp_outs) { + /* The real HPs should have the sequence 0x0f */ + if ((hp_out[i].seq & 0x0f) == 0x0f) { + i++; + continue; + } + /* Move it to the line-out table */ + line_out[cfg->line_outs++] = hp_out[i]; + cfg->hp_outs--; + memmove(hp_out + i, hp_out + i + 1, + sizeof(hp_out[0]) * (cfg->hp_outs - i)); + } + memset(hp_out + cfg->hp_outs, 0, + sizeof(hp_out[0]) * (AUTO_CFG_MAX_OUTS - cfg->hp_outs)); + if (!cfg->hp_outs) + cfg->line_out_type = AUTO_PIN_HP_OUT; + + } + + /* sort by sequence */ + sort_pins_by_sequence(cfg->line_out_pins, line_out, cfg->line_outs); + sort_pins_by_sequence(cfg->speaker_pins, speaker_out, + cfg->speaker_outs); + sort_pins_by_sequence(cfg->hp_pins, hp_out, cfg->hp_outs); + + /* + * FIX-UP: if no line-outs are detected, try to use speaker or HP pin + * as a primary output + */ + if (!cfg->line_outs && + !(cond_flags & HDA_PINCFG_NO_LO_FIXUP)) { + if (cfg->speaker_outs) { + cfg->line_outs = cfg->speaker_outs; + memcpy(cfg->line_out_pins, cfg->speaker_pins, + sizeof(cfg->speaker_pins)); + cfg->speaker_outs = 0; + memset(cfg->speaker_pins, 0, sizeof(cfg->speaker_pins)); + cfg->line_out_type = AUTO_PIN_SPEAKER_OUT; + } else if (cfg->hp_outs) { + cfg->line_outs = cfg->hp_outs; + memcpy(cfg->line_out_pins, cfg->hp_pins, + sizeof(cfg->hp_pins)); + cfg->hp_outs = 0; + memset(cfg->hp_pins, 0, sizeof(cfg->hp_pins)); + cfg->line_out_type = AUTO_PIN_HP_OUT; + } + } + + reorder_outputs(cfg->line_outs, cfg->line_out_pins); + reorder_outputs(cfg->hp_outs, cfg->hp_pins); + reorder_outputs(cfg->speaker_outs, cfg->speaker_pins); + + /* sort inputs in the order of AUTO_PIN_* type */ + sort(cfg->inputs, cfg->num_inputs, sizeof(cfg->inputs[0]), + compare_input_type, NULL); + + /* + * debug prints of the parsed results + */ + codec_info(codec, "autoconfig for %s: line_outs=%d (0x%x/0x%x/0x%x/0x%x/0x%x) type:%s\n", + codec->core.chip_name, cfg->line_outs, cfg->line_out_pins[0], + cfg->line_out_pins[1], cfg->line_out_pins[2], + cfg->line_out_pins[3], cfg->line_out_pins[4], + cfg->line_out_type == AUTO_PIN_HP_OUT ? "hp" : + (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT ? + "speaker" : "line")); + codec_info(codec, " speaker_outs=%d (0x%x/0x%x/0x%x/0x%x/0x%x)\n", + cfg->speaker_outs, cfg->speaker_pins[0], + cfg->speaker_pins[1], cfg->speaker_pins[2], + cfg->speaker_pins[3], cfg->speaker_pins[4]); + codec_info(codec, " hp_outs=%d (0x%x/0x%x/0x%x/0x%x/0x%x)\n", + cfg->hp_outs, cfg->hp_pins[0], + cfg->hp_pins[1], cfg->hp_pins[2], + cfg->hp_pins[3], cfg->hp_pins[4]); + codec_info(codec, " mono: mono_out=0x%x\n", cfg->mono_out_pin); + if (cfg->dig_outs) + codec_info(codec, " dig-out=0x%x/0x%x\n", + cfg->dig_out_pins[0], cfg->dig_out_pins[1]); + codec_info(codec, " inputs:\n"); + for (i = 0; i < cfg->num_inputs; i++) { + codec_info(codec, " %s=0x%x\n", + hda_get_autocfg_input_label(codec, cfg, i), + cfg->inputs[i].pin); + } + if (cfg->dig_in_pin) + codec_info(codec, " dig-in=0x%x\n", cfg->dig_in_pin); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_parse_pin_defcfg); + +/** + * snd_hda_get_input_pin_attr - Get the input pin attribute from pin config + * @def_conf: pin configuration value + * + * Guess the input pin attribute (INPUT_PIN_ATTR_XXX) from the given + * default pin configuration value. + */ +int snd_hda_get_input_pin_attr(unsigned int def_conf) +{ + unsigned int loc = get_defcfg_location(def_conf); + unsigned int conn = get_defcfg_connect(def_conf); + if (conn == AC_JACK_PORT_NONE) + return INPUT_PIN_ATTR_UNUSED; + /* Windows may claim the internal mic to be BOTH, too */ + if (conn == AC_JACK_PORT_FIXED || conn == AC_JACK_PORT_BOTH) + return INPUT_PIN_ATTR_INT; + if ((loc & 0x30) == AC_JACK_LOC_INTERNAL) + return INPUT_PIN_ATTR_INT; + if ((loc & 0x30) == AC_JACK_LOC_SEPARATE) + return INPUT_PIN_ATTR_DOCK; + if (loc == AC_JACK_LOC_REAR) + return INPUT_PIN_ATTR_REAR; + if (loc == AC_JACK_LOC_FRONT) + return INPUT_PIN_ATTR_FRONT; + return INPUT_PIN_ATTR_NORMAL; +} +EXPORT_SYMBOL_GPL(snd_hda_get_input_pin_attr); + +/** + * hda_get_input_pin_label - Give a label for the given input pin + * @codec: the HDA codec + * @item: ping config item to refer + * @pin: the pin NID + * @check_location: flag to add the jack location prefix + * + * When @check_location is true, the function checks the pin location + * for mic and line-in pins, and set an appropriate prefix like "Front", + * "Rear", "Internal". + */ +static const char *hda_get_input_pin_label(struct hda_codec *codec, + const struct auto_pin_cfg_item *item, + hda_nid_t pin, bool check_location) +{ + unsigned int def_conf; + static const char * const mic_names[] = { + "Internal Mic", "Dock Mic", "Mic", "Rear Mic", "Front Mic" + }; + int attr; + + def_conf = snd_hda_codec_get_pincfg(codec, pin); + + switch (get_defcfg_device(def_conf)) { + case AC_JACK_MIC_IN: + if (item && item->is_headset_mic) + return "Headset Mic"; + if (item && item->is_headphone_mic) + return "Headphone Mic"; + if (!check_location) + return "Mic"; + attr = snd_hda_get_input_pin_attr(def_conf); + if (!attr) + return "None"; + return mic_names[attr - 1]; + case AC_JACK_LINE_IN: + if (!check_location) + return "Line"; + attr = snd_hda_get_input_pin_attr(def_conf); + if (!attr) + return "None"; + if (attr == INPUT_PIN_ATTR_DOCK) + return "Dock Line"; + return "Line"; + case AC_JACK_AUX: + return "Aux"; + case AC_JACK_CD: + return "CD"; + case AC_JACK_SPDIF_IN: + return "SPDIF In"; + case AC_JACK_DIG_OTHER_IN: + return "Digital In"; + case AC_JACK_HP_OUT: + return "Headphone Mic"; + default: + return "Misc"; + } +} + +/* Check whether the location prefix needs to be added to the label. + * If all mic-jacks are in the same location (e.g. rear panel), we don't + * have to put "Front" prefix to each label. In such a case, returns false. + */ +static int check_mic_location_need(struct hda_codec *codec, + const struct auto_pin_cfg *cfg, + int input) +{ + unsigned int defc; + int i, attr, attr2; + + defc = snd_hda_codec_get_pincfg(codec, cfg->inputs[input].pin); + attr = snd_hda_get_input_pin_attr(defc); + /* for internal or docking mics, we need locations */ + if (attr <= INPUT_PIN_ATTR_NORMAL) + return 1; + + attr = 0; + for (i = 0; i < cfg->num_inputs; i++) { + defc = snd_hda_codec_get_pincfg(codec, cfg->inputs[i].pin); + attr2 = snd_hda_get_input_pin_attr(defc); + if (attr2 >= INPUT_PIN_ATTR_NORMAL) { + if (attr && attr != attr2) + return 1; /* different locations found */ + attr = attr2; + } + } + return 0; +} + +/** + * hda_get_autocfg_input_label - Get a label for the given input + * @codec: the HDA codec + * @cfg: the parsed pin configuration + * @input: the input index number + * + * Get a label for the given input pin defined by the autocfg item. + * Unlike hda_get_input_pin_label(), this function checks all inputs + * defined in autocfg and avoids the redundant mic/line prefix as much as + * possible. + */ +const char *hda_get_autocfg_input_label(struct hda_codec *codec, + const struct auto_pin_cfg *cfg, + int input) +{ + int type = cfg->inputs[input].type; + int has_multiple_pins = 0; + + if ((input > 0 && cfg->inputs[input - 1].type == type) || + (input < cfg->num_inputs - 1 && cfg->inputs[input + 1].type == type)) + has_multiple_pins = 1; + if (has_multiple_pins && type == AUTO_PIN_MIC) + has_multiple_pins &= check_mic_location_need(codec, cfg, input); + has_multiple_pins |= codec->force_pin_prefix; + return hda_get_input_pin_label(codec, &cfg->inputs[input], + cfg->inputs[input].pin, + has_multiple_pins); +} +EXPORT_SYMBOL_GPL(hda_get_autocfg_input_label); + +/* return the position of NID in the list, or -1 if not found */ +static int find_idx_in_nid_list(hda_nid_t nid, const hda_nid_t *list, int nums) +{ + int i; + for (i = 0; i < nums; i++) + if (list[i] == nid) + return i; + return -1; +} + +/* get a unique suffix or an index number */ +static const char *check_output_sfx(hda_nid_t nid, const hda_nid_t *pins, + int num_pins, int *indexp) +{ + static const char * const channel_sfx[] = { + " Front", " Surround", " CLFE", " Side" + }; + int i; + + i = find_idx_in_nid_list(nid, pins, num_pins); + if (i < 0) + return NULL; + if (num_pins == 1) + return ""; + if (num_pins > ARRAY_SIZE(channel_sfx)) { + if (indexp) + *indexp = i; + return ""; + } + return channel_sfx[i]; +} + +static const char *check_output_pfx(struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid); + int attr = snd_hda_get_input_pin_attr(def_conf); + + /* check the location */ + switch (attr) { + case INPUT_PIN_ATTR_DOCK: + return "Dock "; + case INPUT_PIN_ATTR_FRONT: + return "Front "; + } + return ""; +} + +static int get_hp_label_index(struct hda_codec *codec, hda_nid_t nid, + const hda_nid_t *pins, int num_pins) +{ + int i, j, idx = 0; + + const char *pfx = check_output_pfx(codec, nid); + + i = find_idx_in_nid_list(nid, pins, num_pins); + if (i < 0) + return -1; + for (j = 0; j < i; j++) + if (pfx == check_output_pfx(codec, pins[j])) + idx++; + + return idx; +} + +static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid, + const struct auto_pin_cfg *cfg, + const char *name, char *label, int maxlen, + int *indexp) +{ + unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid); + int attr = snd_hda_get_input_pin_attr(def_conf); + const char *pfx, *sfx = ""; + + /* handle as a speaker if it's a fixed line-out */ + if (!strcmp(name, "Line Out") && attr == INPUT_PIN_ATTR_INT) + name = "Speaker"; + pfx = check_output_pfx(codec, nid); + + if (cfg) { + /* try to give a unique suffix if needed */ + sfx = check_output_sfx(nid, cfg->line_out_pins, cfg->line_outs, + indexp); + if (!sfx) + sfx = check_output_sfx(nid, cfg->speaker_pins, cfg->speaker_outs, + indexp); + if (!sfx) { + /* don't add channel suffix for Headphone controls */ + int idx = get_hp_label_index(codec, nid, cfg->hp_pins, + cfg->hp_outs); + if (idx >= 0 && indexp) + *indexp = idx; + sfx = ""; + } + } + snprintf(label, maxlen, "%s%s%s", pfx, name, sfx); + return 1; +} + +#define is_hdmi_cfg(conf) \ + (get_defcfg_location(conf) == AC_JACK_LOC_HDMI) + +/** + * snd_hda_get_pin_label - Get a label for the given I/O pin + * @codec: the HDA codec + * @nid: pin NID + * @cfg: the parsed pin configuration + * @label: the string buffer to store + * @maxlen: the max length of string buffer (including termination) + * @indexp: the pointer to return the index number (for multiple ctls) + * + * Get a label for the given pin. This function works for both input and + * output pins. When @cfg is given as non-NULL, the function tries to get + * an optimized label using hda_get_autocfg_input_label(). + * + * This function tries to give a unique label string for the pin as much as + * possible. For example, when the multiple line-outs are present, it adds + * the channel suffix like "Front", "Surround", etc (only when @cfg is given). + * If no unique name with a suffix is available and @indexp is non-NULL, the + * index number is stored in the pointer. + */ +int snd_hda_get_pin_label(struct hda_codec *codec, hda_nid_t nid, + const struct auto_pin_cfg *cfg, + char *label, int maxlen, int *indexp) +{ + unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid); + const char *name = NULL; + int i; + bool hdmi; + + if (indexp) + *indexp = 0; + if (get_defcfg_connect(def_conf) == AC_JACK_PORT_NONE) + return 0; + + switch (get_defcfg_device(def_conf)) { + case AC_JACK_LINE_OUT: + return fill_audio_out_name(codec, nid, cfg, "Line Out", + label, maxlen, indexp); + case AC_JACK_SPEAKER: + return fill_audio_out_name(codec, nid, cfg, "Speaker", + label, maxlen, indexp); + case AC_JACK_HP_OUT: + return fill_audio_out_name(codec, nid, cfg, "Headphone", + label, maxlen, indexp); + case AC_JACK_SPDIF_OUT: + case AC_JACK_DIG_OTHER_OUT: + hdmi = is_hdmi_cfg(def_conf); + name = hdmi ? "HDMI" : "SPDIF"; + if (cfg && indexp) + for (i = 0; i < cfg->dig_outs; i++) { + hda_nid_t pin = cfg->dig_out_pins[i]; + unsigned int c; + if (pin == nid) + break; + c = snd_hda_codec_get_pincfg(codec, pin); + if (hdmi == is_hdmi_cfg(c)) + (*indexp)++; + } + break; + default: + if (cfg) { + for (i = 0; i < cfg->num_inputs; i++) { + if (cfg->inputs[i].pin != nid) + continue; + name = hda_get_autocfg_input_label(codec, cfg, i); + if (name) + break; + } + } + if (!name) + name = hda_get_input_pin_label(codec, NULL, nid, true); + break; + } + if (!name) + return 0; + strscpy(label, name, maxlen); + return 1; +} +EXPORT_SYMBOL_GPL(snd_hda_get_pin_label); + +/** + * snd_hda_add_verbs - Add verbs to the init list + * @codec: the HDA codec + * @list: zero-terminated verb list to add + * + * Append the given verb list to the execution list. The verbs will be + * performed at init and resume time via snd_hda_apply_verbs(). + */ +int snd_hda_add_verbs(struct hda_codec *codec, + const struct hda_verb *list) +{ + const struct hda_verb **v; + v = snd_array_new(&codec->verbs); + if (!v) + return -ENOMEM; + *v = list; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_add_verbs); + +/** + * snd_hda_apply_verbs - Execute the init verb lists + * @codec: the HDA codec + */ +void snd_hda_apply_verbs(struct hda_codec *codec) +{ + const struct hda_verb **v; + int i; + + snd_array_for_each(&codec->verbs, i, v) + snd_hda_sequence_write(codec, *v); +} +EXPORT_SYMBOL_GPL(snd_hda_apply_verbs); + +/** + * snd_hda_apply_pincfgs - Set each pin config in the given list + * @codec: the HDA codec + * @cfg: NULL-terminated pin config table + */ +void snd_hda_apply_pincfgs(struct hda_codec *codec, + const struct hda_pintbl *cfg) +{ + for (; cfg->nid; cfg++) + snd_hda_codec_set_pincfg(codec, cfg->nid, cfg->val); +} +EXPORT_SYMBOL_GPL(snd_hda_apply_pincfgs); + +static void set_pin_targets(struct hda_codec *codec, + const struct hda_pintbl *cfg) +{ + for (; cfg->nid; cfg++) + snd_hda_set_pin_ctl_cache(codec, cfg->nid, cfg->val); +} + +void __snd_hda_apply_fixup(struct hda_codec *codec, int id, int action, int depth) +{ + const char *modelname = codec->fixup_name; + + while (id >= 0) { + const struct hda_fixup *fix = codec->fixup_list + id; + + if (++depth > 10) + break; + if (fix->chained_before) + __snd_hda_apply_fixup(codec, fix->chain_id, action, depth + 1); + + switch (fix->type) { + case HDA_FIXUP_PINS: + if (action != HDA_FIXUP_ACT_PRE_PROBE || !fix->v.pins) + break; + codec_dbg(codec, "%s: Apply pincfg for %s\n", + codec->core.chip_name, modelname); + snd_hda_apply_pincfgs(codec, fix->v.pins); + break; + case HDA_FIXUP_VERBS: + if (action != HDA_FIXUP_ACT_PROBE || !fix->v.verbs) + break; + codec_dbg(codec, "%s: Apply fix-verbs for %s\n", + codec->core.chip_name, modelname); + snd_hda_add_verbs(codec, fix->v.verbs); + break; + case HDA_FIXUP_FUNC: + if (!fix->v.func) + break; + codec_dbg(codec, "%s: Apply fix-func for %s\n", + codec->core.chip_name, modelname); + fix->v.func(codec, fix, action); + break; + case HDA_FIXUP_PINCTLS: + if (action != HDA_FIXUP_ACT_PROBE || !fix->v.pins) + break; + codec_dbg(codec, "%s: Apply pinctl for %s\n", + codec->core.chip_name, modelname); + set_pin_targets(codec, fix->v.pins); + break; + default: + codec_err(codec, "%s: Invalid fixup type %d\n", + codec->core.chip_name, fix->type); + break; + } + if (!fix->chained || fix->chained_before) + break; + id = fix->chain_id; + } +} +EXPORT_SYMBOL_GPL(__snd_hda_apply_fixup); + +/** + * snd_hda_apply_fixup - Apply the fixup chain with the given action + * @codec: the HDA codec + * @action: fixup action (HDA_FIXUP_ACT_XXX) + */ +void snd_hda_apply_fixup(struct hda_codec *codec, int action) +{ + if (codec->fixup_list) + __snd_hda_apply_fixup(codec, codec->fixup_id, action, 0); +} +EXPORT_SYMBOL_GPL(snd_hda_apply_fixup); + +#define IGNORE_SEQ_ASSOC (~(AC_DEFCFG_SEQUENCE | AC_DEFCFG_DEF_ASSOC)) + +static bool pin_config_match(struct hda_codec *codec, + const struct hda_pintbl *pins, + bool match_all_pins) +{ + const struct hda_pincfg *pin; + int i; + + snd_array_for_each(&codec->init_pins, i, pin) { + hda_nid_t nid = pin->nid; + u32 cfg = pin->cfg; + const struct hda_pintbl *t_pins; + int found; + + t_pins = pins; + found = 0; + for (; t_pins->nid; t_pins++) { + if (t_pins->nid == nid) { + found = 1; + if ((t_pins->val & IGNORE_SEQ_ASSOC) == (cfg & IGNORE_SEQ_ASSOC)) + break; + else if ((cfg & 0xf0000000) == 0x40000000 && (t_pins->val & 0xf0000000) == 0x40000000) + break; + else + return false; + } + } + if (match_all_pins && + !found && (cfg & 0xf0000000) != 0x40000000) + return false; + } + + return true; +} + +/** + * snd_hda_pick_pin_fixup - Pick up a fixup matching with the pin quirk list + * @codec: the HDA codec + * @pin_quirk: zero-terminated pin quirk list + * @fixlist: the fixup list + * @match_all_pins: all valid pins must match with the table entries + */ +void snd_hda_pick_pin_fixup(struct hda_codec *codec, + const struct snd_hda_pin_quirk *pin_quirk, + const struct hda_fixup *fixlist, + bool match_all_pins) +{ + const struct snd_hda_pin_quirk *pq; + + if (codec->fixup_id != HDA_FIXUP_ID_NOT_SET) + return; + + for (pq = pin_quirk; pq->subvendor; pq++) { + if ((codec->core.subsystem_id & 0xffff0000) != (pq->subvendor << 16)) + continue; + if (codec->core.vendor_id != pq->codec) + continue; + if (pin_config_match(codec, pq->pins, match_all_pins)) { + codec->fixup_id = pq->value; +#ifdef CONFIG_SND_DEBUG_VERBOSE + codec->fixup_name = pq->name; + codec_dbg(codec, "%s: picked fixup %s (pin match)\n", + codec->core.chip_name, codec->fixup_name); +#endif + codec->fixup_list = fixlist; + return; + } + } +} +EXPORT_SYMBOL_GPL(snd_hda_pick_pin_fixup); + +/** + * snd_hda_pick_fixup - Pick up a fixup matching with PCI/codec SSID or model string + * @codec: the HDA codec + * @models: NULL-terminated model string list + * @quirk: zero-terminated PCI/codec SSID quirk list + * @fixlist: the fixup list + * + * Pick up a fixup entry matching with the given model string or SSID. + * If a fixup was already set beforehand, the function doesn't do anything. + * When a special model string "nofixup" is given, also no fixup is applied. + * + * The function tries to find the matching model name at first, if given. + * If the model string contains the SSID alias, try to look up with the given + * alias ID. + * If nothing matched, try to look up the PCI SSID. + * If still nothing matched, try to look up the codec SSID. + */ +void snd_hda_pick_fixup(struct hda_codec *codec, + const struct hda_model_fixup *models, + const struct snd_pci_quirk *quirk, + const struct hda_fixup *fixlist) +{ + const struct snd_pci_quirk *q; + int id = HDA_FIXUP_ID_NOT_SET; + const char *name = NULL; + const char *type = NULL; + unsigned int vendor, device; + + if (codec->fixup_id != HDA_FIXUP_ID_NOT_SET) + return; + + /* when model=nofixup is given, don't pick up any fixups */ + if (codec->modelname && !strcmp(codec->modelname, "nofixup")) { + id = HDA_FIXUP_ID_NO_FIXUP; + fixlist = NULL; + codec_dbg(codec, "%s: picked no fixup (nofixup specified)\n", + codec->core.chip_name); + goto found; + } + + /* match with the model name string */ + if (codec->modelname && models) { + while (models->name) { + if (!strcmp(codec->modelname, models->name)) { + id = models->id; + name = models->name; + codec_dbg(codec, "%s: picked fixup %s (model specified)\n", + codec->core.chip_name, codec->fixup_name); + goto found; + } + models++; + } + } + + if (!quirk) + return; + + /* match with the SSID alias given by the model string "XXXX:YYYY" */ + if (codec->modelname && + sscanf(codec->modelname, "%04x:%04x", &vendor, &device) == 2) { + q = snd_pci_quirk_lookup_id(vendor, device, quirk); + if (q) { + type = "alias SSID"; + goto found_device; + } + } + + /* match with the PCI SSID */ + q = snd_pci_quirk_lookup(codec->bus->pci, quirk); + if (q) { + type = "PCI SSID"; + goto found_device; + } + + /* match with the codec SSID */ + q = snd_pci_quirk_lookup_id(codec->core.subsystem_id >> 16, + codec->core.subsystem_id & 0xffff, + quirk); + if (q) { + type = "codec SSID"; + goto found_device; + } + + return; /* no matching */ + + found_device: + id = q->value; +#ifdef CONFIG_SND_DEBUG_VERBOSE + name = q->name; +#endif + codec_dbg(codec, "%s: picked fixup %s for %s %04x:%04x\n", + codec->core.chip_name, name ? name : "", + type, q->subvendor, q->subdevice); + found: + codec->fixup_id = id; + codec->fixup_list = fixlist; + codec->fixup_name = name; +} +EXPORT_SYMBOL_GPL(snd_hda_pick_fixup); diff --git a/sound/pci/hda/hda_auto_parser.h b/sound/pci/hda/hda_auto_parser.h new file mode 100644 index 000000000..df63d66af --- /dev/null +++ b/sound/pci/hda/hda_auto_parser.h @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * BIOS auto-parser helper functions for HD-audio + * + * Copyright (c) 2012 Takashi Iwai <tiwai@suse.de> + */ + +#ifndef __SOUND_HDA_AUTO_PARSER_H +#define __SOUND_HDA_AUTO_PARSER_H + +/* + * Helper for automatic pin configuration + */ + +enum { + AUTO_PIN_MIC, + AUTO_PIN_LINE_IN, + AUTO_PIN_CD, + AUTO_PIN_AUX, + AUTO_PIN_LAST +}; + +enum { + AUTO_PIN_LINE_OUT, + AUTO_PIN_SPEAKER_OUT, + AUTO_PIN_HP_OUT +}; + +#define AUTO_CFG_MAX_OUTS HDA_MAX_OUTS +#define AUTO_CFG_MAX_INS 18 + +struct auto_pin_cfg_item { + hda_nid_t pin; + int type; + unsigned int is_headset_mic:1; + unsigned int is_headphone_mic:1; /* Mic-only in headphone jack */ + unsigned int has_boost_on_pin:1; +}; + +struct auto_pin_cfg; +const char *hda_get_autocfg_input_label(struct hda_codec *codec, + const struct auto_pin_cfg *cfg, + int input); +int snd_hda_get_pin_label(struct hda_codec *codec, hda_nid_t nid, + const struct auto_pin_cfg *cfg, + char *label, int maxlen, int *indexp); + +enum { + INPUT_PIN_ATTR_UNUSED, /* pin not connected */ + INPUT_PIN_ATTR_INT, /* internal mic/line-in */ + INPUT_PIN_ATTR_DOCK, /* docking mic/line-in */ + INPUT_PIN_ATTR_NORMAL, /* mic/line-in jack */ + INPUT_PIN_ATTR_REAR, /* mic/line-in jack in rear */ + INPUT_PIN_ATTR_FRONT, /* mic/line-in jack in front */ + INPUT_PIN_ATTR_LAST = INPUT_PIN_ATTR_FRONT, +}; + +int snd_hda_get_input_pin_attr(unsigned int def_conf); + +struct auto_pin_cfg { + int line_outs; + /* sorted in the order of Front/Surr/CLFE/Side */ + hda_nid_t line_out_pins[AUTO_CFG_MAX_OUTS]; + int speaker_outs; + hda_nid_t speaker_pins[AUTO_CFG_MAX_OUTS]; + int hp_outs; + int line_out_type; /* AUTO_PIN_XXX_OUT */ + hda_nid_t hp_pins[AUTO_CFG_MAX_OUTS]; + int num_inputs; + struct auto_pin_cfg_item inputs[AUTO_CFG_MAX_INS]; + int dig_outs; + hda_nid_t dig_out_pins[2]; + hda_nid_t dig_in_pin; + hda_nid_t mono_out_pin; + int dig_out_type[2]; /* HDA_PCM_TYPE_XXX */ + int dig_in_type; /* HDA_PCM_TYPE_XXX */ +}; + +/* bit-flags for snd_hda_parse_pin_def_config() behavior */ +#define HDA_PINCFG_NO_HP_FIXUP (1 << 0) /* no HP-split */ +#define HDA_PINCFG_NO_LO_FIXUP (1 << 1) /* don't take other outs as LO */ +#define HDA_PINCFG_HEADSET_MIC (1 << 2) /* Try to find headset mic; mark seq number as 0xc to trigger */ +#define HDA_PINCFG_HEADPHONE_MIC (1 << 3) /* Try to find headphone mic; mark seq number as 0xd to trigger */ + +int snd_hda_parse_pin_defcfg(struct hda_codec *codec, + struct auto_pin_cfg *cfg, + const hda_nid_t *ignore_nids, + unsigned int cond_flags); + +/* older function */ +#define snd_hda_parse_pin_def_config(codec, cfg, ignore) \ + snd_hda_parse_pin_defcfg(codec, cfg, ignore, 0) + +static inline int auto_cfg_hp_outs(const struct auto_pin_cfg *cfg) +{ + return (cfg->line_out_type == AUTO_PIN_HP_OUT) ? + cfg->line_outs : cfg->hp_outs; +} +static inline const hda_nid_t *auto_cfg_hp_pins(const struct auto_pin_cfg *cfg) +{ + return (cfg->line_out_type == AUTO_PIN_HP_OUT) ? + cfg->line_out_pins : cfg->hp_pins; +} +static inline int auto_cfg_speaker_outs(const struct auto_pin_cfg *cfg) +{ + return (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) ? + cfg->line_outs : cfg->speaker_outs; +} +static inline const hda_nid_t *auto_cfg_speaker_pins(const struct auto_pin_cfg *cfg) +{ + return (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) ? + cfg->line_out_pins : cfg->speaker_pins; +} + +#endif /* __SOUND_HDA_AUTO_PARSER_H */ diff --git a/sound/pci/hda/hda_beep.c b/sound/pci/hda/hda_beep.c new file mode 100644 index 000000000..e63621bcb --- /dev/null +++ b/sound/pci/hda/hda_beep.c @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Digital Beep Input Interface for HD-audio codec + * + * Author: Matt Ranostay <matt.ranostay@konsulko.com> + * Copyright (c) 2008 Embedded Alley Solutions Inc + */ + +#include <linux/input.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/export.h> +#include <sound/core.h> +#include "hda_beep.h" +#include "hda_local.h" + +enum { + DIGBEEP_HZ_STEP = 46875, /* 46.875 Hz */ + DIGBEEP_HZ_MIN = 93750, /* 93.750 Hz */ + DIGBEEP_HZ_MAX = 12000000, /* 12 KHz */ +}; + +/* generate or stop tone */ +static void generate_tone(struct hda_beep *beep, int tone) +{ + struct hda_codec *codec = beep->codec; + + if (tone && !beep->playing) { + snd_hda_power_up(codec); + if (beep->power_hook) + beep->power_hook(beep, true); + beep->playing = 1; + } + snd_hda_codec_write(codec, beep->nid, 0, + AC_VERB_SET_BEEP_CONTROL, tone); + if (!tone && beep->playing) { + beep->playing = 0; + if (beep->power_hook) + beep->power_hook(beep, false); + snd_hda_power_down(codec); + } +} + +static void snd_hda_generate_beep(struct work_struct *work) +{ + struct hda_beep *beep = + container_of(work, struct hda_beep, beep_work); + + if (beep->enabled) + generate_tone(beep, beep->tone); +} + +/* (non-standard) Linear beep tone calculation for IDT/STAC codecs + * + * The tone frequency of beep generator on IDT/STAC codecs is + * defined from the 8bit tone parameter, in Hz, + * freq = 48000 * (257 - tone) / 1024 + * that is from 12kHz to 93.75Hz in steps of 46.875 Hz + */ +static int beep_linear_tone(struct hda_beep *beep, int hz) +{ + if (hz <= 0) + return 0; + hz *= 1000; /* fixed point */ + hz = hz - DIGBEEP_HZ_MIN + + DIGBEEP_HZ_STEP / 2; /* round to nearest step */ + if (hz < 0) + hz = 0; /* turn off PC beep*/ + else if (hz >= (DIGBEEP_HZ_MAX - DIGBEEP_HZ_MIN)) + hz = 1; /* max frequency */ + else { + hz /= DIGBEEP_HZ_STEP; + hz = 255 - hz; + } + return hz; +} + +/* HD-audio standard beep tone parameter calculation + * + * The tone frequency in Hz is calculated as + * freq = 48000 / (tone * 4) + * from 47Hz to 12kHz + */ +static int beep_standard_tone(struct hda_beep *beep, int hz) +{ + if (hz <= 0) + return 0; /* disabled */ + hz = 12000 / hz; + if (hz > 0xff) + return 0xff; + if (hz <= 0) + return 1; + return hz; +} + +static int snd_hda_beep_event(struct input_dev *dev, unsigned int type, + unsigned int code, int hz) +{ + struct hda_beep *beep = input_get_drvdata(dev); + + switch (code) { + case SND_BELL: + if (hz) + hz = 1000; + fallthrough; + case SND_TONE: + if (beep->linear_tone) + beep->tone = beep_linear_tone(beep, hz); + else + beep->tone = beep_standard_tone(beep, hz); + break; + default: + return -1; + } + + /* schedule beep event */ + schedule_work(&beep->beep_work); + return 0; +} + +static void turn_on_beep(struct hda_beep *beep) +{ + if (beep->keep_power_at_enable) + snd_hda_power_up_pm(beep->codec); +} + +static void turn_off_beep(struct hda_beep *beep) +{ + cancel_work_sync(&beep->beep_work); + if (beep->playing) { + /* turn off beep */ + generate_tone(beep, 0); + } + if (beep->keep_power_at_enable) + snd_hda_power_down_pm(beep->codec); +} + +/** + * snd_hda_enable_beep_device - Turn on/off beep sound + * @codec: the HDA codec + * @enable: flag to turn on/off + */ +int snd_hda_enable_beep_device(struct hda_codec *codec, int enable) +{ + struct hda_beep *beep = codec->beep; + if (!beep) + return 0; + enable = !!enable; + if (beep->enabled != enable) { + beep->enabled = enable; + if (enable) + turn_on_beep(beep); + else + turn_off_beep(beep); + return 1; + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_enable_beep_device); + +static int beep_dev_register(struct snd_device *device) +{ + struct hda_beep *beep = device->device_data; + int err; + + err = input_register_device(beep->dev); + if (!err) + beep->registered = true; + return err; +} + +static int beep_dev_disconnect(struct snd_device *device) +{ + struct hda_beep *beep = device->device_data; + + if (beep->registered) + input_unregister_device(beep->dev); + else + input_free_device(beep->dev); + if (beep->enabled) + turn_off_beep(beep); + return 0; +} + +static int beep_dev_free(struct snd_device *device) +{ + struct hda_beep *beep = device->device_data; + + beep->codec->beep = NULL; + kfree(beep); + return 0; +} + +/** + * snd_hda_attach_beep_device - Attach a beep input device + * @codec: the HDA codec + * @nid: beep NID + * + * Attach a beep object to the given widget. If beep hint is turned off + * explicitly or beep_mode of the codec is turned off, this doesn't nothing. + * + * Currently, only one beep device is allowed to each codec. + */ +int snd_hda_attach_beep_device(struct hda_codec *codec, int nid) +{ + static const struct snd_device_ops ops = { + .dev_register = beep_dev_register, + .dev_disconnect = beep_dev_disconnect, + .dev_free = beep_dev_free, + }; + struct input_dev *input_dev; + struct hda_beep *beep; + int err; + + if (!snd_hda_get_bool_hint(codec, "beep")) + return 0; /* disabled explicitly by hints */ + if (codec->beep_mode == HDA_BEEP_MODE_OFF) + return 0; /* disabled by module option */ + + beep = kzalloc(sizeof(*beep), GFP_KERNEL); + if (beep == NULL) + return -ENOMEM; + snprintf(beep->phys, sizeof(beep->phys), + "card%d/codec#%d/beep0", codec->card->number, codec->addr); + /* enable linear scale */ + snd_hda_codec_write_cache(codec, nid, 0, + AC_VERB_SET_DIGI_CONVERT_2, 0x01); + + beep->nid = nid; + beep->codec = codec; + codec->beep = beep; + + INIT_WORK(&beep->beep_work, &snd_hda_generate_beep); + mutex_init(&beep->mutex); + + input_dev = input_allocate_device(); + if (!input_dev) { + err = -ENOMEM; + goto err_free; + } + + /* setup digital beep device */ + input_dev->name = "HDA Digital PCBeep"; + input_dev->phys = beep->phys; + input_dev->id.bustype = BUS_PCI; + input_dev->dev.parent = &codec->card->card_dev; + + input_dev->id.vendor = codec->core.vendor_id >> 16; + input_dev->id.product = codec->core.vendor_id & 0xffff; + input_dev->id.version = 0x01; + + input_dev->evbit[0] = BIT_MASK(EV_SND); + input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE); + input_dev->event = snd_hda_beep_event; + input_set_drvdata(input_dev, beep); + + beep->dev = input_dev; + + err = snd_device_new(codec->card, SNDRV_DEV_JACK, beep, &ops); + if (err < 0) + goto err_input; + + return 0; + + err_input: + input_free_device(beep->dev); + err_free: + kfree(beep); + codec->beep = NULL; + return err; +} +EXPORT_SYMBOL_GPL(snd_hda_attach_beep_device); + +/** + * snd_hda_detach_beep_device - Detach the beep device + * @codec: the HDA codec + */ +void snd_hda_detach_beep_device(struct hda_codec *codec) +{ + if (!codec->bus->shutdown && codec->beep) + snd_device_free(codec->card, codec->beep); +} +EXPORT_SYMBOL_GPL(snd_hda_detach_beep_device); + +static bool ctl_has_mute(struct snd_kcontrol *kcontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + return query_amp_caps(codec, get_amp_nid(kcontrol), + get_amp_direction(kcontrol)) & AC_AMPCAP_MUTE; +} + +/* get/put callbacks for beep mute mixer switches */ + +/** + * snd_hda_mixer_amp_switch_get_beep - Get callback for beep controls + * @kcontrol: ctl element + * @ucontrol: pointer to get/store the data + */ +int snd_hda_mixer_amp_switch_get_beep(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_beep *beep = codec->beep; + int chs = get_amp_channels(kcontrol); + + if (beep && (!beep->enabled || !ctl_has_mute(kcontrol))) { + if (chs & 1) + ucontrol->value.integer.value[0] = beep->enabled; + if (chs & 2) + ucontrol->value.integer.value[1] = beep->enabled; + return 0; + } + return snd_hda_mixer_amp_switch_get(kcontrol, ucontrol); +} +EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_switch_get_beep); + +/** + * snd_hda_mixer_amp_switch_put_beep - Put callback for beep controls + * @kcontrol: ctl element + * @ucontrol: pointer to get/store the data + */ +int snd_hda_mixer_amp_switch_put_beep(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_beep *beep = codec->beep; + if (beep) { + u8 chs = get_amp_channels(kcontrol); + int enable = 0; + long *valp = ucontrol->value.integer.value; + if (chs & 1) { + enable |= *valp; + valp++; + } + if (chs & 2) + enable |= *valp; + snd_hda_enable_beep_device(codec, enable); + } + if (!ctl_has_mute(kcontrol)) + return 0; + return snd_hda_mixer_amp_switch_put(kcontrol, ucontrol); +} +EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_switch_put_beep); diff --git a/sound/pci/hda/hda_beep.h b/sound/pci/hda/hda_beep.h new file mode 100644 index 000000000..db76e3ddb --- /dev/null +++ b/sound/pci/hda/hda_beep.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Digital Beep Input Interface for HD-audio codec + * + * Author: Matt Ranostay <matt.ranostay@konsulko.com> + * Copyright (c) 2008 Embedded Alley Solutions Inc + */ + +#ifndef __SOUND_HDA_BEEP_H +#define __SOUND_HDA_BEEP_H + +#include <sound/hda_codec.h> + +#define HDA_BEEP_MODE_OFF 0 +#define HDA_BEEP_MODE_ON 1 + +/* beep information */ +struct hda_beep { + struct input_dev *dev; + struct hda_codec *codec; + char phys[32]; + int tone; + hda_nid_t nid; + unsigned int registered:1; + unsigned int enabled:1; + unsigned int linear_tone:1; /* linear tone for IDT/STAC codec */ + unsigned int playing:1; + unsigned int keep_power_at_enable:1; /* set by driver */ + struct work_struct beep_work; /* scheduled task for beep event */ + struct mutex mutex; + void (*power_hook)(struct hda_beep *beep, bool on); +}; + +#ifdef CONFIG_SND_HDA_INPUT_BEEP +int snd_hda_enable_beep_device(struct hda_codec *codec, int enable); +int snd_hda_attach_beep_device(struct hda_codec *codec, int nid); +void snd_hda_detach_beep_device(struct hda_codec *codec); +#else +static inline int snd_hda_attach_beep_device(struct hda_codec *codec, int nid) +{ + return 0; +} +static inline void snd_hda_detach_beep_device(struct hda_codec *codec) +{ +} +#endif +#endif diff --git a/sound/pci/hda/hda_bind.c b/sound/pci/hda/hda_bind.c new file mode 100644 index 000000000..890c2f7c3 --- /dev/null +++ b/sound/pci/hda/hda_bind.c @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HD-audio codec driver binding + * Copyright (c) Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/export.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <sound/core.h> +#include <sound/hda_codec.h> +#include "hda_local.h" +#include "hda_jack.h" + +/* + * find a matching codec id + */ +static int hda_codec_match(struct hdac_device *dev, struct hdac_driver *drv) +{ + struct hda_codec *codec = container_of(dev, struct hda_codec, core); + struct hda_codec_driver *driver = + container_of(drv, struct hda_codec_driver, core); + const struct hda_device_id *list; + /* check probe_id instead of vendor_id if set */ + u32 id = codec->probe_id ? codec->probe_id : codec->core.vendor_id; + u32 rev_id = codec->core.revision_id; + + for (list = driver->id; list->vendor_id; list++) { + if (list->vendor_id == id && + (!list->rev_id || list->rev_id == rev_id)) { + codec->preset = list; + return 1; + } + } + return 0; +} + +/* process an unsolicited event */ +static void hda_codec_unsol_event(struct hdac_device *dev, unsigned int ev) +{ + struct hda_codec *codec = container_of(dev, struct hda_codec, core); + + /* ignore unsol events during shutdown */ + if (codec->bus->shutdown) + return; + + /* ignore unsol events during system suspend/resume */ + if (codec->core.dev.power.power_state.event != PM_EVENT_ON) + return; + + if (codec->patch_ops.unsol_event) + codec->patch_ops.unsol_event(codec, ev); +} + +/** + * snd_hda_codec_set_name - set the codec name + * @codec: the HDA codec + * @name: name string to set + */ +int snd_hda_codec_set_name(struct hda_codec *codec, const char *name) +{ + int err; + + if (!name) + return 0; + err = snd_hdac_device_set_chip_name(&codec->core, name); + if (err < 0) + return err; + + /* update the mixer name */ + if (!*codec->card->mixername || + codec->bus->mixer_assigned >= codec->core.addr) { + snprintf(codec->card->mixername, + sizeof(codec->card->mixername), "%s %s", + codec->core.vendor_name, codec->core.chip_name); + codec->bus->mixer_assigned = codec->core.addr; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_codec_set_name); + +static int hda_codec_driver_probe(struct device *dev) +{ + struct hda_codec *codec = dev_to_hda_codec(dev); + struct module *owner = dev->driver->owner; + hda_codec_patch_t patch; + int err; + + if (codec->bus->core.ext_ops) { + if (WARN_ON(!codec->bus->core.ext_ops->hdev_attach)) + return -EINVAL; + return codec->bus->core.ext_ops->hdev_attach(&codec->core); + } + + if (WARN_ON(!codec->preset)) + return -EINVAL; + + err = snd_hda_codec_set_name(codec, codec->preset->name); + if (err < 0) + goto error; + err = snd_hdac_regmap_init(&codec->core); + if (err < 0) + goto error; + + if (!try_module_get(owner)) { + err = -EINVAL; + goto error; + } + + patch = (hda_codec_patch_t)codec->preset->driver_data; + if (patch) { + err = patch(codec); + if (err < 0) + goto error_module_put; + } + + err = snd_hda_codec_build_pcms(codec); + if (err < 0) + goto error_module; + err = snd_hda_codec_build_controls(codec); + if (err < 0) + goto error_module; + /* only register after the bus probe finished; otherwise it's racy */ + if (!codec->bus->bus_probing && codec->card->registered) { + err = snd_card_register(codec->card); + if (err < 0) + goto error_module; + snd_hda_codec_register(codec); + } + + codec->core.lazy_cache = true; + return 0; + + error_module: + if (codec->patch_ops.free) + codec->patch_ops.free(codec); + error_module_put: + module_put(owner); + + error: + snd_hda_codec_cleanup_for_unbind(codec); + codec->preset = NULL; + return err; +} + +static int hda_codec_driver_remove(struct device *dev) +{ + struct hda_codec *codec = dev_to_hda_codec(dev); + + if (codec->bus->core.ext_ops) { + if (WARN_ON(!codec->bus->core.ext_ops->hdev_detach)) + return -EINVAL; + return codec->bus->core.ext_ops->hdev_detach(&codec->core); + } + + snd_hda_codec_disconnect_pcms(codec); + snd_hda_jack_tbl_disconnect(codec); + if (!refcount_dec_and_test(&codec->pcm_ref)) + wait_event(codec->remove_sleep, !refcount_read(&codec->pcm_ref)); + snd_power_sync_ref(codec->bus->card); + + if (codec->patch_ops.free) + codec->patch_ops.free(codec); + snd_hda_codec_cleanup_for_unbind(codec); + codec->preset = NULL; + module_put(dev->driver->owner); + return 0; +} + +static void hda_codec_driver_shutdown(struct device *dev) +{ + snd_hda_codec_shutdown(dev_to_hda_codec(dev)); +} + +int __hda_codec_driver_register(struct hda_codec_driver *drv, const char *name, + struct module *owner) +{ + drv->core.driver.name = name; + drv->core.driver.owner = owner; + drv->core.driver.bus = &snd_hda_bus_type; + drv->core.driver.probe = hda_codec_driver_probe; + drv->core.driver.remove = hda_codec_driver_remove; + drv->core.driver.shutdown = hda_codec_driver_shutdown; + drv->core.driver.pm = &hda_codec_driver_pm; + drv->core.type = HDA_DEV_LEGACY; + drv->core.match = hda_codec_match; + drv->core.unsol_event = hda_codec_unsol_event; + return driver_register(&drv->core.driver); +} +EXPORT_SYMBOL_GPL(__hda_codec_driver_register); + +void hda_codec_driver_unregister(struct hda_codec_driver *drv) +{ + driver_unregister(&drv->core.driver); +} +EXPORT_SYMBOL_GPL(hda_codec_driver_unregister); + +static inline bool codec_probed(struct hda_codec *codec) +{ + return device_attach(hda_codec_dev(codec)) > 0 && codec->preset; +} + +/* try to auto-load codec module */ +static void request_codec_module(struct hda_codec *codec) +{ +#ifdef MODULE + char modalias[32]; + const char *mod = NULL; + + switch (codec->probe_id) { + case HDA_CODEC_ID_GENERIC_HDMI: +#if IS_MODULE(CONFIG_SND_HDA_CODEC_HDMI) + mod = "snd-hda-codec-hdmi"; +#endif + break; + case HDA_CODEC_ID_GENERIC: +#if IS_MODULE(CONFIG_SND_HDA_GENERIC) + mod = "snd-hda-codec-generic"; +#endif + break; + default: + snd_hdac_codec_modalias(&codec->core, modalias, sizeof(modalias)); + mod = modalias; + break; + } + + if (mod) + request_module(mod); +#endif /* MODULE */ +} + +/* try to auto-load and bind the codec module */ +static void codec_bind_module(struct hda_codec *codec) +{ +#ifdef MODULE + request_codec_module(codec); + if (codec_probed(codec)) + return; +#endif +} + +#if IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI) +/* if all audio out widgets are digital, let's assume the codec as a HDMI/DP */ +static bool is_likely_hdmi_codec(struct hda_codec *codec) +{ + hda_nid_t nid; + + /* + * For ASoC users, if snd_hda_hdmi_codec module is denylisted and any + * event causes i915 enumeration to fail, ->wcaps remains uninitialized. + */ + if (!codec->wcaps) + return true; + + for_each_hda_codec_node(nid, codec) { + unsigned int wcaps = get_wcaps(codec, nid); + switch (get_wcaps_type(wcaps)) { + case AC_WID_AUD_IN: + return false; /* HDMI parser supports only HDMI out */ + case AC_WID_AUD_OUT: + if (!(wcaps & AC_WCAP_DIGITAL)) + return false; + break; + } + } + return true; +} +#else +/* no HDMI codec parser support */ +#define is_likely_hdmi_codec(codec) false +#endif /* CONFIG_SND_HDA_CODEC_HDMI */ + +static int codec_bind_generic(struct hda_codec *codec) +{ + if (codec->probe_id) + return -ENODEV; + + if (is_likely_hdmi_codec(codec)) { + codec->probe_id = HDA_CODEC_ID_GENERIC_HDMI; + request_codec_module(codec); + if (codec_probed(codec)) + return 0; + } + + codec->probe_id = HDA_CODEC_ID_GENERIC; + request_codec_module(codec); + if (codec_probed(codec)) + return 0; + return -ENODEV; +} + +#if IS_ENABLED(CONFIG_SND_HDA_GENERIC) +#define is_generic_config(codec) \ + (codec->modelname && !strcmp(codec->modelname, "generic")) +#else +#define is_generic_config(codec) 0 +#endif + +/** + * snd_hda_codec_configure - (Re-)configure the HD-audio codec + * @codec: the HDA codec + * + * Start parsing of the given codec tree and (re-)initialize the whole + * patch instance. + * + * Returns 0 if successful or a negative error code. + */ +int snd_hda_codec_configure(struct hda_codec *codec) +{ + int err; + + if (codec->configured) + return 0; + + if (is_generic_config(codec)) + codec->probe_id = HDA_CODEC_ID_GENERIC; + else + codec->probe_id = 0; + + if (!device_is_registered(&codec->core.dev)) { + err = snd_hdac_device_register(&codec->core); + if (err < 0) + return err; + } + + if (!codec->preset) + codec_bind_module(codec); + if (!codec->preset) { + err = codec_bind_generic(codec); + if (err < 0) { + codec_dbg(codec, "Unable to bind the codec\n"); + return err; + } + } + + codec->configured = 1; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_codec_configure); diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c new file mode 100644 index 000000000..bd19f92ae --- /dev/null +++ b/sound/pci/hda/hda_codec.c @@ -0,0 +1,4094 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Universal Interface for Intel High Definition Audio Codec + * + * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <sound/core.h> +#include <sound/hda_codec.h> +#include <sound/asoundef.h> +#include <sound/tlv.h> +#include <sound/initval.h> +#include <sound/jack.h> +#include "hda_local.h" +#include "hda_beep.h" +#include "hda_jack.h" +#include <sound/hda_hwdep.h> +#include <sound/hda_component.h> + +#define codec_in_pm(codec) snd_hdac_is_in_pm(&codec->core) +#define hda_codec_is_power_on(codec) snd_hdac_is_power_on(&codec->core) +#define codec_has_epss(codec) \ + ((codec)->core.power_caps & AC_PWRST_EPSS) +#define codec_has_clkstop(codec) \ + ((codec)->core.power_caps & AC_PWRST_CLKSTOP) + +/* + * Send and receive a verb - passed to exec_verb override for hdac_device + */ +static int codec_exec_verb(struct hdac_device *dev, unsigned int cmd, + unsigned int flags, unsigned int *res) +{ + struct hda_codec *codec = container_of(dev, struct hda_codec, core); + struct hda_bus *bus = codec->bus; + int err; + + if (cmd == ~0) + return -1; + + again: + snd_hda_power_up_pm(codec); + mutex_lock(&bus->core.cmd_mutex); + if (flags & HDA_RW_NO_RESPONSE_FALLBACK) + bus->no_response_fallback = 1; + err = snd_hdac_bus_exec_verb_unlocked(&bus->core, codec->core.addr, + cmd, res); + bus->no_response_fallback = 0; + mutex_unlock(&bus->core.cmd_mutex); + snd_hda_power_down_pm(codec); + if (!codec_in_pm(codec) && res && err == -EAGAIN) { + if (bus->response_reset) { + codec_dbg(codec, + "resetting BUS due to fatal communication error\n"); + snd_hda_bus_reset(bus); + } + goto again; + } + /* clear reset-flag when the communication gets recovered */ + if (!err || codec_in_pm(codec)) + bus->response_reset = 0; + return err; +} + +/** + * snd_hda_sequence_write - sequence writes + * @codec: the HDA codec + * @seq: VERB array to send + * + * Send the commands sequentially from the given array. + * The array must be terminated with NID=0. + */ +void snd_hda_sequence_write(struct hda_codec *codec, const struct hda_verb *seq) +{ + for (; seq->nid; seq++) + snd_hda_codec_write(codec, seq->nid, 0, seq->verb, seq->param); +} +EXPORT_SYMBOL_GPL(snd_hda_sequence_write); + +/* connection list element */ +struct hda_conn_list { + struct list_head list; + int len; + hda_nid_t nid; + hda_nid_t conns[]; +}; + +/* look up the cached results */ +static struct hda_conn_list * +lookup_conn_list(struct hda_codec *codec, hda_nid_t nid) +{ + struct hda_conn_list *p; + list_for_each_entry(p, &codec->conn_list, list) { + if (p->nid == nid) + return p; + } + return NULL; +} + +static int add_conn_list(struct hda_codec *codec, hda_nid_t nid, int len, + const hda_nid_t *list) +{ + struct hda_conn_list *p; + + p = kmalloc(struct_size(p, conns, len), GFP_KERNEL); + if (!p) + return -ENOMEM; + p->len = len; + p->nid = nid; + memcpy(p->conns, list, len * sizeof(hda_nid_t)); + list_add(&p->list, &codec->conn_list); + return 0; +} + +static void remove_conn_list(struct hda_codec *codec) +{ + while (!list_empty(&codec->conn_list)) { + struct hda_conn_list *p; + p = list_first_entry(&codec->conn_list, typeof(*p), list); + list_del(&p->list); + kfree(p); + } +} + +/* read the connection and add to the cache */ +static int read_and_add_raw_conns(struct hda_codec *codec, hda_nid_t nid) +{ + hda_nid_t list[32]; + hda_nid_t *result = list; + int len; + + len = snd_hda_get_raw_connections(codec, nid, list, ARRAY_SIZE(list)); + if (len == -ENOSPC) { + len = snd_hda_get_num_raw_conns(codec, nid); + result = kmalloc_array(len, sizeof(hda_nid_t), GFP_KERNEL); + if (!result) + return -ENOMEM; + len = snd_hda_get_raw_connections(codec, nid, result, len); + } + if (len >= 0) + len = snd_hda_override_conn_list(codec, nid, len, result); + if (result != list) + kfree(result); + return len; +} + +/** + * snd_hda_get_conn_list - get connection list + * @codec: the HDA codec + * @nid: NID to parse + * @listp: the pointer to store NID list + * + * Parses the connection list of the given widget and stores the pointer + * to the list of NIDs. + * + * Returns the number of connections, or a negative error code. + * + * Note that the returned pointer isn't protected against the list + * modification. If snd_hda_override_conn_list() might be called + * concurrently, protect with a mutex appropriately. + */ +int snd_hda_get_conn_list(struct hda_codec *codec, hda_nid_t nid, + const hda_nid_t **listp) +{ + bool added = false; + + for (;;) { + int err; + const struct hda_conn_list *p; + + /* if the connection-list is already cached, read it */ + p = lookup_conn_list(codec, nid); + if (p) { + if (listp) + *listp = p->conns; + return p->len; + } + if (snd_BUG_ON(added)) + return -EINVAL; + + err = read_and_add_raw_conns(codec, nid); + if (err < 0) + return err; + added = true; + } +} +EXPORT_SYMBOL_GPL(snd_hda_get_conn_list); + +/** + * snd_hda_get_connections - copy connection list + * @codec: the HDA codec + * @nid: NID to parse + * @conn_list: connection list array; when NULL, checks only the size + * @max_conns: max. number of connections to store + * + * Parses the connection list of the given widget and stores the list + * of NIDs. + * + * Returns the number of connections, or a negative error code. + */ +int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid, + hda_nid_t *conn_list, int max_conns) +{ + const hda_nid_t *list; + int len = snd_hda_get_conn_list(codec, nid, &list); + + if (len > 0 && conn_list) { + if (len > max_conns) { + codec_err(codec, "Too many connections %d for NID 0x%x\n", + len, nid); + return -EINVAL; + } + memcpy(conn_list, list, len * sizeof(hda_nid_t)); + } + + return len; +} +EXPORT_SYMBOL_GPL(snd_hda_get_connections); + +/** + * snd_hda_override_conn_list - add/modify the connection-list to cache + * @codec: the HDA codec + * @nid: NID to parse + * @len: number of connection list entries + * @list: the list of connection entries + * + * Add or modify the given connection-list to the cache. If the corresponding + * cache already exists, invalidate it and append a new one. + * + * Returns zero or a negative error code. + */ +int snd_hda_override_conn_list(struct hda_codec *codec, hda_nid_t nid, int len, + const hda_nid_t *list) +{ + struct hda_conn_list *p; + + p = lookup_conn_list(codec, nid); + if (p) { + list_del(&p->list); + kfree(p); + } + + return add_conn_list(codec, nid, len, list); +} +EXPORT_SYMBOL_GPL(snd_hda_override_conn_list); + +/** + * snd_hda_get_conn_index - get the connection index of the given NID + * @codec: the HDA codec + * @mux: NID containing the list + * @nid: NID to select + * @recursive: 1 when searching NID recursively, otherwise 0 + * + * Parses the connection list of the widget @mux and checks whether the + * widget @nid is present. If it is, return the connection index. + * Otherwise it returns -1. + */ +int snd_hda_get_conn_index(struct hda_codec *codec, hda_nid_t mux, + hda_nid_t nid, int recursive) +{ + const hda_nid_t *conn; + int i, nums; + + nums = snd_hda_get_conn_list(codec, mux, &conn); + for (i = 0; i < nums; i++) + if (conn[i] == nid) + return i; + if (!recursive) + return -1; + if (recursive > 10) { + codec_dbg(codec, "too deep connection for 0x%x\n", nid); + return -1; + } + recursive++; + for (i = 0; i < nums; i++) { + unsigned int type = get_wcaps_type(get_wcaps(codec, conn[i])); + if (type == AC_WID_PIN || type == AC_WID_AUD_OUT) + continue; + if (snd_hda_get_conn_index(codec, conn[i], nid, recursive) >= 0) + return i; + } + return -1; +} +EXPORT_SYMBOL_GPL(snd_hda_get_conn_index); + +/** + * snd_hda_get_num_devices - get DEVLIST_LEN parameter of the given widget + * @codec: the HDA codec + * @nid: NID of the pin to parse + * + * Get the device entry number on the given widget. This is a feature of + * DP MST audio. Each pin can have several device entries in it. + */ +unsigned int snd_hda_get_num_devices(struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int wcaps = get_wcaps(codec, nid); + unsigned int parm; + + if (!codec->dp_mst || !(wcaps & AC_WCAP_DIGITAL) || + get_wcaps_type(wcaps) != AC_WID_PIN) + return 0; + + parm = snd_hdac_read_parm_uncached(&codec->core, nid, AC_PAR_DEVLIST_LEN); + if (parm == -1) + parm = 0; + return parm & AC_DEV_LIST_LEN_MASK; +} +EXPORT_SYMBOL_GPL(snd_hda_get_num_devices); + +/** + * snd_hda_get_devices - copy device list without cache + * @codec: the HDA codec + * @nid: NID of the pin to parse + * @dev_list: device list array + * @max_devices: max. number of devices to store + * + * Copy the device list. This info is dynamic and so not cached. + * Currently called only from hda_proc.c, so not exported. + */ +int snd_hda_get_devices(struct hda_codec *codec, hda_nid_t nid, + u8 *dev_list, int max_devices) +{ + unsigned int parm; + int i, dev_len, devices; + + parm = snd_hda_get_num_devices(codec, nid); + if (!parm) /* not multi-stream capable */ + return 0; + + dev_len = parm + 1; + dev_len = dev_len < max_devices ? dev_len : max_devices; + + devices = 0; + while (devices < dev_len) { + if (snd_hdac_read(&codec->core, nid, + AC_VERB_GET_DEVICE_LIST, devices, &parm)) + break; /* error */ + + for (i = 0; i < 8; i++) { + dev_list[devices] = (u8)parm; + parm >>= 4; + devices++; + if (devices >= dev_len) + break; + } + } + return devices; +} + +/** + * snd_hda_get_dev_select - get device entry select on the pin + * @codec: the HDA codec + * @nid: NID of the pin to get device entry select + * + * Get the devcie entry select on the pin. Return the device entry + * id selected on the pin. Return 0 means the first device entry + * is selected or MST is not supported. + */ +int snd_hda_get_dev_select(struct hda_codec *codec, hda_nid_t nid) +{ + /* not support dp_mst will always return 0, using first dev_entry */ + if (!codec->dp_mst) + return 0; + + return snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_DEVICE_SEL, 0); +} +EXPORT_SYMBOL_GPL(snd_hda_get_dev_select); + +/** + * snd_hda_set_dev_select - set device entry select on the pin + * @codec: the HDA codec + * @nid: NID of the pin to set device entry select + * @dev_id: device entry id to be set + * + * Set the device entry select on the pin nid. + */ +int snd_hda_set_dev_select(struct hda_codec *codec, hda_nid_t nid, int dev_id) +{ + int ret, num_devices; + + /* not support dp_mst will always return 0, using first dev_entry */ + if (!codec->dp_mst) + return 0; + + /* AC_PAR_DEVLIST_LEN is 0 based. */ + num_devices = snd_hda_get_num_devices(codec, nid) + 1; + /* If Device List Length is 0 (num_device = 1), + * the pin is not multi stream capable. + * Do nothing in this case. + */ + if (num_devices == 1) + return 0; + + /* Behavior of setting index being equal to or greater than + * Device List Length is not predictable + */ + if (num_devices <= dev_id) + return -EINVAL; + + ret = snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_DEVICE_SEL, dev_id); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_hda_set_dev_select); + +/* + * read widget caps for each widget and store in cache + */ +static int read_widget_caps(struct hda_codec *codec, hda_nid_t fg_node) +{ + int i; + hda_nid_t nid; + + codec->wcaps = kmalloc_array(codec->core.num_nodes, 4, GFP_KERNEL); + if (!codec->wcaps) + return -ENOMEM; + nid = codec->core.start_nid; + for (i = 0; i < codec->core.num_nodes; i++, nid++) + codec->wcaps[i] = snd_hdac_read_parm_uncached(&codec->core, + nid, AC_PAR_AUDIO_WIDGET_CAP); + return 0; +} + +/* read all pin default configurations and save codec->init_pins */ +static int read_pin_defaults(struct hda_codec *codec) +{ + hda_nid_t nid; + + for_each_hda_codec_node(nid, codec) { + struct hda_pincfg *pin; + unsigned int wcaps = get_wcaps(codec, nid); + unsigned int wid_type = get_wcaps_type(wcaps); + if (wid_type != AC_WID_PIN) + continue; + pin = snd_array_new(&codec->init_pins); + if (!pin) + return -ENOMEM; + pin->nid = nid; + pin->cfg = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_CONFIG_DEFAULT, 0); + /* + * all device entries are the same widget control so far + * fixme: if any codec is different, need fix here + */ + pin->ctrl = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, + 0); + } + return 0; +} + +/* look up the given pin config list and return the item matching with NID */ +static struct hda_pincfg *look_up_pincfg(struct hda_codec *codec, + struct snd_array *array, + hda_nid_t nid) +{ + struct hda_pincfg *pin; + int i; + + snd_array_for_each(array, i, pin) { + if (pin->nid == nid) + return pin; + } + return NULL; +} + +/* set the current pin config value for the given NID. + * the value is cached, and read via snd_hda_codec_get_pincfg() + */ +int snd_hda_add_pincfg(struct hda_codec *codec, struct snd_array *list, + hda_nid_t nid, unsigned int cfg) +{ + struct hda_pincfg *pin; + + /* the check below may be invalid when pins are added by a fixup + * dynamically (e.g. via snd_hda_codec_update_widgets()), so disabled + * for now + */ + /* + if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN) + return -EINVAL; + */ + + pin = look_up_pincfg(codec, list, nid); + if (!pin) { + pin = snd_array_new(list); + if (!pin) + return -ENOMEM; + pin->nid = nid; + } + pin->cfg = cfg; + return 0; +} + +/** + * snd_hda_codec_set_pincfg - Override a pin default configuration + * @codec: the HDA codec + * @nid: NID to set the pin config + * @cfg: the pin default config value + * + * Override a pin default configuration value in the cache. + * This value can be read by snd_hda_codec_get_pincfg() in a higher + * priority than the real hardware value. + */ +int snd_hda_codec_set_pincfg(struct hda_codec *codec, + hda_nid_t nid, unsigned int cfg) +{ + return snd_hda_add_pincfg(codec, &codec->driver_pins, nid, cfg); +} +EXPORT_SYMBOL_GPL(snd_hda_codec_set_pincfg); + +/** + * snd_hda_codec_get_pincfg - Obtain a pin-default configuration + * @codec: the HDA codec + * @nid: NID to get the pin config + * + * Get the current pin config value of the given pin NID. + * If the pincfg value is cached or overridden via sysfs or driver, + * returns the cached value. + */ +unsigned int snd_hda_codec_get_pincfg(struct hda_codec *codec, hda_nid_t nid) +{ + struct hda_pincfg *pin; + +#ifdef CONFIG_SND_HDA_RECONFIG + { + unsigned int cfg = 0; + mutex_lock(&codec->user_mutex); + pin = look_up_pincfg(codec, &codec->user_pins, nid); + if (pin) + cfg = pin->cfg; + mutex_unlock(&codec->user_mutex); + if (cfg) + return cfg; + } +#endif + pin = look_up_pincfg(codec, &codec->driver_pins, nid); + if (pin) + return pin->cfg; + pin = look_up_pincfg(codec, &codec->init_pins, nid); + if (pin) + return pin->cfg; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_codec_get_pincfg); + +/** + * snd_hda_codec_set_pin_target - remember the current pinctl target value + * @codec: the HDA codec + * @nid: pin NID + * @val: assigned pinctl value + * + * This function stores the given value to a pinctl target value in the + * pincfg table. This isn't always as same as the actually written value + * but can be referred at any time via snd_hda_codec_get_pin_target(). + */ +int snd_hda_codec_set_pin_target(struct hda_codec *codec, hda_nid_t nid, + unsigned int val) +{ + struct hda_pincfg *pin; + + pin = look_up_pincfg(codec, &codec->init_pins, nid); + if (!pin) + return -EINVAL; + pin->target = val; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_codec_set_pin_target); + +/** + * snd_hda_codec_get_pin_target - return the current pinctl target value + * @codec: the HDA codec + * @nid: pin NID + */ +int snd_hda_codec_get_pin_target(struct hda_codec *codec, hda_nid_t nid) +{ + struct hda_pincfg *pin; + + pin = look_up_pincfg(codec, &codec->init_pins, nid); + if (!pin) + return 0; + return pin->target; +} +EXPORT_SYMBOL_GPL(snd_hda_codec_get_pin_target); + +/** + * snd_hda_shutup_pins - Shut up all pins + * @codec: the HDA codec + * + * Clear all pin controls to shup up before suspend for avoiding click noise. + * The controls aren't cached so that they can be resumed properly. + */ +void snd_hda_shutup_pins(struct hda_codec *codec) +{ + const struct hda_pincfg *pin; + int i; + + /* don't shut up pins when unloading the driver; otherwise it breaks + * the default pin setup at the next load of the driver + */ + if (codec->bus->shutdown) + return; + snd_array_for_each(&codec->init_pins, i, pin) { + /* use read here for syncing after issuing each verb */ + snd_hda_codec_read(codec, pin->nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0); + } + codec->pins_shutup = 1; +} +EXPORT_SYMBOL_GPL(snd_hda_shutup_pins); + +#ifdef CONFIG_PM +/* Restore the pin controls cleared previously via snd_hda_shutup_pins() */ +static void restore_shutup_pins(struct hda_codec *codec) +{ + const struct hda_pincfg *pin; + int i; + + if (!codec->pins_shutup) + return; + if (codec->bus->shutdown) + return; + snd_array_for_each(&codec->init_pins, i, pin) { + snd_hda_codec_write(codec, pin->nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + pin->ctrl); + } + codec->pins_shutup = 0; +} +#endif + +static void hda_jackpoll_work(struct work_struct *work) +{ + struct hda_codec *codec = + container_of(work, struct hda_codec, jackpoll_work.work); + + /* for non-polling trigger: we need nothing if already powered on */ + if (!codec->jackpoll_interval && snd_hdac_is_power_on(&codec->core)) + return; + + /* the power-up/down sequence triggers the runtime resume */ + snd_hda_power_up_pm(codec); + /* update jacks manually if polling is required, too */ + if (codec->jackpoll_interval) { + snd_hda_jack_set_dirty_all(codec); + snd_hda_jack_poll_all(codec); + } + snd_hda_power_down_pm(codec); + + if (!codec->jackpoll_interval) + return; + + schedule_delayed_work(&codec->jackpoll_work, + codec->jackpoll_interval); +} + +/* release all pincfg lists */ +static void free_init_pincfgs(struct hda_codec *codec) +{ + snd_array_free(&codec->driver_pins); +#ifdef CONFIG_SND_HDA_RECONFIG + snd_array_free(&codec->user_pins); +#endif + snd_array_free(&codec->init_pins); +} + +/* + * audio-converter setup caches + */ +struct hda_cvt_setup { + hda_nid_t nid; + u8 stream_tag; + u8 channel_id; + u16 format_id; + unsigned char active; /* cvt is currently used */ + unsigned char dirty; /* setups should be cleared */ +}; + +/* get or create a cache entry for the given audio converter NID */ +static struct hda_cvt_setup * +get_hda_cvt_setup(struct hda_codec *codec, hda_nid_t nid) +{ + struct hda_cvt_setup *p; + int i; + + snd_array_for_each(&codec->cvt_setups, i, p) { + if (p->nid == nid) + return p; + } + p = snd_array_new(&codec->cvt_setups); + if (p) + p->nid = nid; + return p; +} + +/* + * PCM device + */ +void snd_hda_codec_pcm_put(struct hda_pcm *pcm) +{ + if (refcount_dec_and_test(&pcm->codec->pcm_ref)) + wake_up(&pcm->codec->remove_sleep); +} +EXPORT_SYMBOL_GPL(snd_hda_codec_pcm_put); + +struct hda_pcm *snd_hda_codec_pcm_new(struct hda_codec *codec, + const char *fmt, ...) +{ + struct hda_pcm *pcm; + va_list args; + + pcm = kzalloc(sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return NULL; + + pcm->codec = codec; + va_start(args, fmt); + pcm->name = kvasprintf(GFP_KERNEL, fmt, args); + va_end(args); + if (!pcm->name) { + kfree(pcm); + return NULL; + } + + list_add_tail(&pcm->list, &codec->pcm_list_head); + refcount_inc(&codec->pcm_ref); + return pcm; +} +EXPORT_SYMBOL_GPL(snd_hda_codec_pcm_new); + +/* + * codec destructor + */ +void snd_hda_codec_disconnect_pcms(struct hda_codec *codec) +{ + struct hda_pcm *pcm; + + list_for_each_entry(pcm, &codec->pcm_list_head, list) { + if (pcm->disconnected) + continue; + if (pcm->pcm) + snd_device_disconnect(codec->card, pcm->pcm); + snd_hda_codec_pcm_put(pcm); + pcm->disconnected = 1; + } +} + +static void codec_release_pcms(struct hda_codec *codec) +{ + struct hda_pcm *pcm, *n; + + list_for_each_entry_safe(pcm, n, &codec->pcm_list_head, list) { + list_del(&pcm->list); + if (pcm->pcm) + snd_device_free(pcm->codec->card, pcm->pcm); + clear_bit(pcm->device, pcm->codec->bus->pcm_dev_bits); + kfree(pcm->name); + kfree(pcm); + } +} + +/** + * snd_hda_codec_cleanup_for_unbind - Prepare codec for removal + * @codec: codec device to cleanup + */ +void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec) +{ + if (codec->core.registered) { + /* pm_runtime_put() is called in snd_hdac_device_exit() */ + pm_runtime_get_noresume(hda_codec_dev(codec)); + pm_runtime_disable(hda_codec_dev(codec)); + codec->core.registered = 0; + } + + snd_hda_codec_disconnect_pcms(codec); + cancel_delayed_work_sync(&codec->jackpoll_work); + if (!codec->in_freeing) + snd_hda_ctls_clear(codec); + codec_release_pcms(codec); + snd_hda_detach_beep_device(codec); + memset(&codec->patch_ops, 0, sizeof(codec->patch_ops)); + snd_hda_jack_tbl_clear(codec); + codec->proc_widget_hook = NULL; + codec->spec = NULL; + + /* free only driver_pins so that init_pins + user_pins are restored */ + snd_array_free(&codec->driver_pins); + snd_array_free(&codec->cvt_setups); + snd_array_free(&codec->spdif_out); + snd_array_free(&codec->verbs); + codec->follower_dig_outs = NULL; + codec->spdif_status_reset = 0; + snd_array_free(&codec->mixers); + snd_array_free(&codec->nids); + remove_conn_list(codec); + snd_hdac_regmap_exit(&codec->core); + codec->configured = 0; + refcount_set(&codec->pcm_ref, 1); /* reset refcount */ +} +EXPORT_SYMBOL_GPL(snd_hda_codec_cleanup_for_unbind); + +static unsigned int hda_set_power_state(struct hda_codec *codec, + unsigned int power_state); + +/* enable/disable display power per codec */ +void snd_hda_codec_display_power(struct hda_codec *codec, bool enable) +{ + if (codec->display_power_control) + snd_hdac_display_power(&codec->bus->core, codec->addr, enable); +} + +/** + * snd_hda_codec_register - Finalize codec initialization + * @codec: codec device to register + * + * Also called from hda_bind.c + */ +void snd_hda_codec_register(struct hda_codec *codec) +{ + if (codec->core.registered) + return; + if (device_is_registered(hda_codec_dev(codec))) { + snd_hda_codec_display_power(codec, true); + pm_runtime_enable(hda_codec_dev(codec)); + /* it was powered up in snd_hda_codec_new(), now all done */ + snd_hda_power_down(codec); + codec->core.registered = 1; + } +} +EXPORT_SYMBOL_GPL(snd_hda_codec_register); + +static int snd_hda_codec_dev_register(struct snd_device *device) +{ + snd_hda_codec_register(device->device_data); + return 0; +} + +/** + * snd_hda_codec_unregister - Unregister specified codec device + * @codec: codec device to unregister + */ +void snd_hda_codec_unregister(struct hda_codec *codec) +{ + codec->in_freeing = 1; + /* + * snd_hda_codec_device_new() is used by legacy HDA and ASoC driver. + * We can't unregister ASoC device since it will be unregistered in + * snd_hdac_ext_bus_device_remove(). + */ + if (codec->core.type == HDA_DEV_LEGACY) + snd_hdac_device_unregister(&codec->core); + snd_hda_codec_display_power(codec, false); + + /* + * In the case of ASoC HD-audio bus, the device refcount is released in + * snd_hdac_ext_bus_device_remove() explicitly. + */ + if (codec->core.type == HDA_DEV_LEGACY) + put_device(hda_codec_dev(codec)); +} +EXPORT_SYMBOL_GPL(snd_hda_codec_unregister); + +static int snd_hda_codec_dev_free(struct snd_device *device) +{ + snd_hda_codec_unregister(device->device_data); + return 0; +} + +static void snd_hda_codec_dev_release(struct device *dev) +{ + struct hda_codec *codec = dev_to_hda_codec(dev); + + free_init_pincfgs(codec); + snd_hdac_device_exit(&codec->core); + snd_hda_sysfs_clear(codec); + kfree(codec->modelname); + kfree(codec->wcaps); + kfree(codec); +} + +#define DEV_NAME_LEN 31 + +/** + * snd_hda_codec_device_init - allocate HDA codec device + * @bus: codec's parent bus + * @codec_addr: the codec address on the parent bus + * @fmt: format string for the device's name + * + * Returns newly allocated codec device or ERR_PTR() on failure. + */ +struct hda_codec * +snd_hda_codec_device_init(struct hda_bus *bus, unsigned int codec_addr, + const char *fmt, ...) +{ + va_list vargs; + char name[DEV_NAME_LEN]; + struct hda_codec *codec; + int err; + + if (snd_BUG_ON(!bus)) + return ERR_PTR(-EINVAL); + if (snd_BUG_ON(codec_addr > HDA_MAX_CODEC_ADDRESS)) + return ERR_PTR(-EINVAL); + + codec = kzalloc(sizeof(*codec), GFP_KERNEL); + if (!codec) + return ERR_PTR(-ENOMEM); + + va_start(vargs, fmt); + vsprintf(name, fmt, vargs); + va_end(vargs); + + err = snd_hdac_device_init(&codec->core, &bus->core, name, codec_addr); + if (err < 0) { + kfree(codec); + return ERR_PTR(err); + } + + codec->bus = bus; + codec->depop_delay = -1; + codec->fixup_id = HDA_FIXUP_ID_NOT_SET; + codec->core.dev.release = snd_hda_codec_dev_release; + codec->core.type = HDA_DEV_LEGACY; + + mutex_init(&codec->spdif_mutex); + mutex_init(&codec->control_mutex); + snd_array_init(&codec->mixers, sizeof(struct hda_nid_item), 32); + snd_array_init(&codec->nids, sizeof(struct hda_nid_item), 32); + snd_array_init(&codec->init_pins, sizeof(struct hda_pincfg), 16); + snd_array_init(&codec->driver_pins, sizeof(struct hda_pincfg), 16); + snd_array_init(&codec->cvt_setups, sizeof(struct hda_cvt_setup), 8); + snd_array_init(&codec->spdif_out, sizeof(struct hda_spdif_out), 16); + snd_array_init(&codec->jacktbl, sizeof(struct hda_jack_tbl), 16); + snd_array_init(&codec->verbs, sizeof(struct hda_verb *), 8); + INIT_LIST_HEAD(&codec->conn_list); + INIT_LIST_HEAD(&codec->pcm_list_head); + INIT_DELAYED_WORK(&codec->jackpoll_work, hda_jackpoll_work); + refcount_set(&codec->pcm_ref, 1); + init_waitqueue_head(&codec->remove_sleep); + + return codec; +} +EXPORT_SYMBOL_GPL(snd_hda_codec_device_init); + +/** + * snd_hda_codec_new - create a HDA codec + * @bus: the bus to assign + * @card: card for this codec + * @codec_addr: the codec address + * @codecp: the pointer to store the generated codec + * + * Returns 0 if successful, or a negative error code. + */ +int snd_hda_codec_new(struct hda_bus *bus, struct snd_card *card, + unsigned int codec_addr, struct hda_codec **codecp) +{ + struct hda_codec *codec; + int ret; + + codec = snd_hda_codec_device_init(bus, codec_addr, "hdaudioC%dD%d", + card->number, codec_addr); + if (IS_ERR(codec)) + return PTR_ERR(codec); + *codecp = codec; + + ret = snd_hda_codec_device_new(bus, card, codec_addr, *codecp, true); + if (ret) + put_device(hda_codec_dev(*codecp)); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_hda_codec_new); + +int snd_hda_codec_device_new(struct hda_bus *bus, struct snd_card *card, + unsigned int codec_addr, struct hda_codec *codec, + bool snddev_managed) +{ + char component[31]; + hda_nid_t fg; + int err; + static const struct snd_device_ops dev_ops = { + .dev_register = snd_hda_codec_dev_register, + .dev_free = snd_hda_codec_dev_free, + }; + + dev_dbg(card->dev, "%s: entry\n", __func__); + + if (snd_BUG_ON(!bus)) + return -EINVAL; + if (snd_BUG_ON(codec_addr > HDA_MAX_CODEC_ADDRESS)) + return -EINVAL; + + codec->core.exec_verb = codec_exec_verb; + codec->card = card; + codec->addr = codec_addr; + +#ifdef CONFIG_PM + codec->power_jiffies = jiffies; +#endif + + snd_hda_sysfs_init(codec); + + if (codec->bus->modelname) { + codec->modelname = kstrdup(codec->bus->modelname, GFP_KERNEL); + if (!codec->modelname) + return -ENOMEM; + } + + fg = codec->core.afg ? codec->core.afg : codec->core.mfg; + err = read_widget_caps(codec, fg); + if (err < 0) + return err; + err = read_pin_defaults(codec); + if (err < 0) + return err; + + /* power-up all before initialization */ + hda_set_power_state(codec, AC_PWRST_D0); + codec->core.dev.power.power_state = PMSG_ON; + + snd_hda_codec_proc_new(codec); + + snd_hda_create_hwdep(codec); + + sprintf(component, "HDA:%08x,%08x,%08x", codec->core.vendor_id, + codec->core.subsystem_id, codec->core.revision_id); + snd_component_add(card, component); + + if (snddev_managed) { + /* ASoC features component management instead */ + err = snd_device_new(card, SNDRV_DEV_CODEC, codec, &dev_ops); + if (err < 0) + return err; + } + +#ifdef CONFIG_PM + /* PM runtime needs to be enabled later after binding codec */ + if (codec->core.dev.power.runtime_auto) + pm_runtime_forbid(&codec->core.dev); + else + /* Keep the usage_count consistent across subsequent probing */ + pm_runtime_get_noresume(&codec->core.dev); +#endif + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_codec_device_new); + +/** + * snd_hda_codec_update_widgets - Refresh widget caps and pin defaults + * @codec: the HDA codec + * + * Forcibly refresh the all widget caps and the init pin configurations of + * the given codec. + */ +int snd_hda_codec_update_widgets(struct hda_codec *codec) +{ + hda_nid_t fg; + int err; + + err = snd_hdac_refresh_widgets(&codec->core); + if (err < 0) + return err; + + /* Assume the function group node does not change, + * only the widget nodes may change. + */ + kfree(codec->wcaps); + fg = codec->core.afg ? codec->core.afg : codec->core.mfg; + err = read_widget_caps(codec, fg); + if (err < 0) + return err; + + snd_array_free(&codec->init_pins); + err = read_pin_defaults(codec); + + return err; +} +EXPORT_SYMBOL_GPL(snd_hda_codec_update_widgets); + +/* update the stream-id if changed */ +static void update_pcm_stream_id(struct hda_codec *codec, + struct hda_cvt_setup *p, hda_nid_t nid, + u32 stream_tag, int channel_id) +{ + unsigned int oldval, newval; + + if (p->stream_tag != stream_tag || p->channel_id != channel_id) { + oldval = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONV, 0); + newval = (stream_tag << 4) | channel_id; + if (oldval != newval) + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_CHANNEL_STREAMID, + newval); + p->stream_tag = stream_tag; + p->channel_id = channel_id; + } +} + +/* update the format-id if changed */ +static void update_pcm_format(struct hda_codec *codec, struct hda_cvt_setup *p, + hda_nid_t nid, int format) +{ + unsigned int oldval; + + if (p->format_id != format) { + oldval = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_STREAM_FORMAT, 0); + if (oldval != format) { + msleep(1); + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_STREAM_FORMAT, + format); + } + p->format_id = format; + } +} + +/** + * snd_hda_codec_setup_stream - set up the codec for streaming + * @codec: the CODEC to set up + * @nid: the NID to set up + * @stream_tag: stream tag to pass, it's between 0x1 and 0xf. + * @channel_id: channel id to pass, zero based. + * @format: stream format. + */ +void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid, + u32 stream_tag, + int channel_id, int format) +{ + struct hda_codec *c; + struct hda_cvt_setup *p; + int type; + int i; + + if (!nid) + return; + + codec_dbg(codec, + "hda_codec_setup_stream: NID=0x%x, stream=0x%x, channel=%d, format=0x%x\n", + nid, stream_tag, channel_id, format); + p = get_hda_cvt_setup(codec, nid); + if (!p) + return; + + if (codec->patch_ops.stream_pm) + codec->patch_ops.stream_pm(codec, nid, true); + if (codec->pcm_format_first) + update_pcm_format(codec, p, nid, format); + update_pcm_stream_id(codec, p, nid, stream_tag, channel_id); + if (!codec->pcm_format_first) + update_pcm_format(codec, p, nid, format); + + p->active = 1; + p->dirty = 0; + + /* make other inactive cvts with the same stream-tag dirty */ + type = get_wcaps_type(get_wcaps(codec, nid)); + list_for_each_codec(c, codec->bus) { + snd_array_for_each(&c->cvt_setups, i, p) { + if (!p->active && p->stream_tag == stream_tag && + get_wcaps_type(get_wcaps(c, p->nid)) == type) + p->dirty = 1; + } + } +} +EXPORT_SYMBOL_GPL(snd_hda_codec_setup_stream); + +static void really_cleanup_stream(struct hda_codec *codec, + struct hda_cvt_setup *q); + +/** + * __snd_hda_codec_cleanup_stream - clean up the codec for closing + * @codec: the CODEC to clean up + * @nid: the NID to clean up + * @do_now: really clean up the stream instead of clearing the active flag + */ +void __snd_hda_codec_cleanup_stream(struct hda_codec *codec, hda_nid_t nid, + int do_now) +{ + struct hda_cvt_setup *p; + + if (!nid) + return; + + if (codec->no_sticky_stream) + do_now = 1; + + codec_dbg(codec, "hda_codec_cleanup_stream: NID=0x%x\n", nid); + p = get_hda_cvt_setup(codec, nid); + if (p) { + /* here we just clear the active flag when do_now isn't set; + * actual clean-ups will be done later in + * purify_inactive_streams() called from snd_hda_codec_prpapre() + */ + if (do_now) + really_cleanup_stream(codec, p); + else + p->active = 0; + } +} +EXPORT_SYMBOL_GPL(__snd_hda_codec_cleanup_stream); + +static void really_cleanup_stream(struct hda_codec *codec, + struct hda_cvt_setup *q) +{ + hda_nid_t nid = q->nid; + if (q->stream_tag || q->channel_id) + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CHANNEL_STREAMID, 0); + if (q->format_id) + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_STREAM_FORMAT, 0 +); + memset(q, 0, sizeof(*q)); + q->nid = nid; + if (codec->patch_ops.stream_pm) + codec->patch_ops.stream_pm(codec, nid, false); +} + +/* clean up the all conflicting obsolete streams */ +static void purify_inactive_streams(struct hda_codec *codec) +{ + struct hda_codec *c; + struct hda_cvt_setup *p; + int i; + + list_for_each_codec(c, codec->bus) { + snd_array_for_each(&c->cvt_setups, i, p) { + if (p->dirty) + really_cleanup_stream(c, p); + } + } +} + +#ifdef CONFIG_PM +/* clean up all streams; called from suspend */ +static void hda_cleanup_all_streams(struct hda_codec *codec) +{ + struct hda_cvt_setup *p; + int i; + + snd_array_for_each(&codec->cvt_setups, i, p) { + if (p->stream_tag) + really_cleanup_stream(codec, p); + } +} +#endif + +/* + * amp access functions + */ + +/** + * query_amp_caps - query AMP capabilities + * @codec: the HD-auio codec + * @nid: the NID to query + * @direction: either #HDA_INPUT or #HDA_OUTPUT + * + * Query AMP capabilities for the given widget and direction. + * Returns the obtained capability bits. + * + * When cap bits have been already read, this doesn't read again but + * returns the cached value. + */ +u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction) +{ + if (!(get_wcaps(codec, nid) & AC_WCAP_AMP_OVRD)) + nid = codec->core.afg; + return snd_hda_param_read(codec, nid, + direction == HDA_OUTPUT ? + AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP); +} +EXPORT_SYMBOL_GPL(query_amp_caps); + +/** + * snd_hda_check_amp_caps - query AMP capabilities + * @codec: the HD-audio codec + * @nid: the NID to query + * @dir: either #HDA_INPUT or #HDA_OUTPUT + * @bits: bit mask to check the result + * + * Check whether the widget has the given amp capability for the direction. + */ +bool snd_hda_check_amp_caps(struct hda_codec *codec, hda_nid_t nid, + int dir, unsigned int bits) +{ + if (!nid) + return false; + if (get_wcaps(codec, nid) & (1 << (dir + 1))) + if (query_amp_caps(codec, nid, dir) & bits) + return true; + return false; +} +EXPORT_SYMBOL_GPL(snd_hda_check_amp_caps); + +/** + * snd_hda_override_amp_caps - Override the AMP capabilities + * @codec: the CODEC to clean up + * @nid: the NID to clean up + * @dir: either #HDA_INPUT or #HDA_OUTPUT + * @caps: the capability bits to set + * + * Override the cached AMP caps bits value by the given one. + * This function is useful if the driver needs to adjust the AMP ranges, + * e.g. limit to 0dB, etc. + * + * Returns zero if successful or a negative error code. + */ +int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir, + unsigned int caps) +{ + unsigned int parm; + + snd_hda_override_wcaps(codec, nid, + get_wcaps(codec, nid) | AC_WCAP_AMP_OVRD); + parm = dir == HDA_OUTPUT ? AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP; + return snd_hdac_override_parm(&codec->core, nid, parm, caps); +} +EXPORT_SYMBOL_GPL(snd_hda_override_amp_caps); + +static unsigned int encode_amp(struct hda_codec *codec, hda_nid_t nid, + int ch, int dir, int idx) +{ + unsigned int cmd = snd_hdac_regmap_encode_amp(nid, ch, dir, idx); + + /* enable fake mute if no h/w mute but min=mute */ + if ((query_amp_caps(codec, nid, dir) & + (AC_AMPCAP_MUTE | AC_AMPCAP_MIN_MUTE)) == AC_AMPCAP_MIN_MUTE) + cmd |= AC_AMP_FAKE_MUTE; + return cmd; +} + +/** + * snd_hda_codec_amp_update - update the AMP mono value + * @codec: HD-audio codec + * @nid: NID to read the AMP value + * @ch: channel to update (0 or 1) + * @dir: #HDA_INPUT or #HDA_OUTPUT + * @idx: the index value (only for input direction) + * @mask: bit mask to set + * @val: the bits value to set + * + * Update the AMP values for the given channel, direction and index. + */ +int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid, + int ch, int dir, int idx, int mask, int val) +{ + unsigned int cmd = encode_amp(codec, nid, ch, dir, idx); + + return snd_hdac_regmap_update_raw(&codec->core, cmd, mask, val); +} +EXPORT_SYMBOL_GPL(snd_hda_codec_amp_update); + +/** + * snd_hda_codec_amp_stereo - update the AMP stereo values + * @codec: HD-audio codec + * @nid: NID to read the AMP value + * @direction: #HDA_INPUT or #HDA_OUTPUT + * @idx: the index value (only for input direction) + * @mask: bit mask to set + * @val: the bits value to set + * + * Update the AMP values like snd_hda_codec_amp_update(), but for a + * stereo widget with the same mask and value. + */ +int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid, + int direction, int idx, int mask, int val) +{ + int ch, ret = 0; + + if (snd_BUG_ON(mask & ~0xff)) + mask &= 0xff; + for (ch = 0; ch < 2; ch++) + ret |= snd_hda_codec_amp_update(codec, nid, ch, direction, + idx, mask, val); + return ret; +} +EXPORT_SYMBOL_GPL(snd_hda_codec_amp_stereo); + +/** + * snd_hda_codec_amp_init - initialize the AMP value + * @codec: the HDA codec + * @nid: NID to read the AMP value + * @ch: channel (left=0 or right=1) + * @dir: #HDA_INPUT or #HDA_OUTPUT + * @idx: the index value (only for input direction) + * @mask: bit mask to set + * @val: the bits value to set + * + * Works like snd_hda_codec_amp_update() but it writes the value only at + * the first access. If the amp was already initialized / updated beforehand, + * this does nothing. + */ +int snd_hda_codec_amp_init(struct hda_codec *codec, hda_nid_t nid, int ch, + int dir, int idx, int mask, int val) +{ + unsigned int cmd = encode_amp(codec, nid, ch, dir, idx); + + if (!codec->core.regmap) + return -EINVAL; + return snd_hdac_regmap_update_raw_once(&codec->core, cmd, mask, val); +} +EXPORT_SYMBOL_GPL(snd_hda_codec_amp_init); + +/** + * snd_hda_codec_amp_init_stereo - initialize the stereo AMP value + * @codec: the HDA codec + * @nid: NID to read the AMP value + * @dir: #HDA_INPUT or #HDA_OUTPUT + * @idx: the index value (only for input direction) + * @mask: bit mask to set + * @val: the bits value to set + * + * Call snd_hda_codec_amp_init() for both stereo channels. + */ +int snd_hda_codec_amp_init_stereo(struct hda_codec *codec, hda_nid_t nid, + int dir, int idx, int mask, int val) +{ + int ch, ret = 0; + + if (snd_BUG_ON(mask & ~0xff)) + mask &= 0xff; + for (ch = 0; ch < 2; ch++) + ret |= snd_hda_codec_amp_init(codec, nid, ch, dir, + idx, mask, val); + return ret; +} +EXPORT_SYMBOL_GPL(snd_hda_codec_amp_init_stereo); + +static u32 get_amp_max_value(struct hda_codec *codec, hda_nid_t nid, int dir, + unsigned int ofs) +{ + u32 caps = query_amp_caps(codec, nid, dir); + /* get num steps */ + caps = (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT; + if (ofs < caps) + caps -= ofs; + return caps; +} + +/** + * snd_hda_mixer_amp_volume_info - Info callback for a standard AMP mixer + * @kcontrol: referred ctl element + * @uinfo: pointer to get/store the data + * + * The control element is supposed to have the private_value field + * set up via HDA_COMPOSE_AMP_VAL*() or related macros. + */ +int snd_hda_mixer_amp_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + u16 nid = get_amp_nid(kcontrol); + u8 chs = get_amp_channels(kcontrol); + int dir = get_amp_direction(kcontrol); + unsigned int ofs = get_amp_offset(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = chs == 3 ? 2 : 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = get_amp_max_value(codec, nid, dir, ofs); + if (!uinfo->value.integer.max) { + codec_warn(codec, + "num_steps = 0 for NID=0x%x (ctl = %s)\n", + nid, kcontrol->id.name); + return -EINVAL; + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_volume_info); + + +static inline unsigned int +read_amp_value(struct hda_codec *codec, hda_nid_t nid, + int ch, int dir, int idx, unsigned int ofs) +{ + unsigned int val; + val = snd_hda_codec_amp_read(codec, nid, ch, dir, idx); + val &= HDA_AMP_VOLMASK; + if (val >= ofs) + val -= ofs; + else + val = 0; + return val; +} + +static inline int +update_amp_value(struct hda_codec *codec, hda_nid_t nid, + int ch, int dir, int idx, unsigned int ofs, + unsigned int val) +{ + unsigned int maxval; + + if (val > 0) + val += ofs; + /* ofs = 0: raw max value */ + maxval = get_amp_max_value(codec, nid, dir, 0); + if (val > maxval) + val = maxval; + return snd_hda_codec_amp_update(codec, nid, ch, dir, idx, + HDA_AMP_VOLMASK, val); +} + +/** + * snd_hda_mixer_amp_volume_get - Get callback for a standard AMP mixer volume + * @kcontrol: ctl element + * @ucontrol: pointer to get/store the data + * + * The control element is supposed to have the private_value field + * set up via HDA_COMPOSE_AMP_VAL*() or related macros. + */ +int snd_hda_mixer_amp_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = get_amp_nid(kcontrol); + int chs = get_amp_channels(kcontrol); + int dir = get_amp_direction(kcontrol); + int idx = get_amp_index(kcontrol); + unsigned int ofs = get_amp_offset(kcontrol); + long *valp = ucontrol->value.integer.value; + + if (chs & 1) + *valp++ = read_amp_value(codec, nid, 0, dir, idx, ofs); + if (chs & 2) + *valp = read_amp_value(codec, nid, 1, dir, idx, ofs); + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_volume_get); + +/** + * snd_hda_mixer_amp_volume_put - Put callback for a standard AMP mixer volume + * @kcontrol: ctl element + * @ucontrol: pointer to get/store the data + * + * The control element is supposed to have the private_value field + * set up via HDA_COMPOSE_AMP_VAL*() or related macros. + */ +int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = get_amp_nid(kcontrol); + int chs = get_amp_channels(kcontrol); + int dir = get_amp_direction(kcontrol); + int idx = get_amp_index(kcontrol); + unsigned int ofs = get_amp_offset(kcontrol); + long *valp = ucontrol->value.integer.value; + int change = 0; + + if (chs & 1) { + change = update_amp_value(codec, nid, 0, dir, idx, ofs, *valp); + valp++; + } + if (chs & 2) + change |= update_amp_value(codec, nid, 1, dir, idx, ofs, *valp); + return change; +} +EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_volume_put); + +/* inquiry the amp caps and convert to TLV */ +static void get_ctl_amp_tlv(struct snd_kcontrol *kcontrol, unsigned int *tlv) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = get_amp_nid(kcontrol); + int dir = get_amp_direction(kcontrol); + unsigned int ofs = get_amp_offset(kcontrol); + bool min_mute = get_amp_min_mute(kcontrol); + u32 caps, val1, val2; + + caps = query_amp_caps(codec, nid, dir); + val2 = (caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT; + val2 = (val2 + 1) * 25; + val1 = -((caps & AC_AMPCAP_OFFSET) >> AC_AMPCAP_OFFSET_SHIFT); + val1 += ofs; + val1 = ((int)val1) * ((int)val2); + if (min_mute || (caps & AC_AMPCAP_MIN_MUTE)) + val2 |= TLV_DB_SCALE_MUTE; + tlv[SNDRV_CTL_TLVO_TYPE] = SNDRV_CTL_TLVT_DB_SCALE; + tlv[SNDRV_CTL_TLVO_LEN] = 2 * sizeof(unsigned int); + tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN] = val1; + tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] = val2; +} + +/** + * snd_hda_mixer_amp_tlv - TLV callback for a standard AMP mixer volume + * @kcontrol: ctl element + * @op_flag: operation flag + * @size: byte size of input TLV + * @_tlv: TLV data + * + * The control element is supposed to have the private_value field + * set up via HDA_COMPOSE_AMP_VAL*() or related macros. + */ +int snd_hda_mixer_amp_tlv(struct snd_kcontrol *kcontrol, int op_flag, + unsigned int size, unsigned int __user *_tlv) +{ + unsigned int tlv[4]; + + if (size < 4 * sizeof(unsigned int)) + return -ENOMEM; + get_ctl_amp_tlv(kcontrol, tlv); + if (copy_to_user(_tlv, tlv, sizeof(tlv))) + return -EFAULT; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_tlv); + +/** + * snd_hda_set_vmaster_tlv - Set TLV for a virtual master control + * @codec: HD-audio codec + * @nid: NID of a reference widget + * @dir: #HDA_INPUT or #HDA_OUTPUT + * @tlv: TLV data to be stored, at least 4 elements + * + * Set (static) TLV data for a virtual master volume using the AMP caps + * obtained from the reference NID. + * The volume range is recalculated as if the max volume is 0dB. + */ +void snd_hda_set_vmaster_tlv(struct hda_codec *codec, hda_nid_t nid, int dir, + unsigned int *tlv) +{ + u32 caps; + int nums, step; + + caps = query_amp_caps(codec, nid, dir); + nums = (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT; + step = (caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT; + step = (step + 1) * 25; + tlv[SNDRV_CTL_TLVO_TYPE] = SNDRV_CTL_TLVT_DB_SCALE; + tlv[SNDRV_CTL_TLVO_LEN] = 2 * sizeof(unsigned int); + tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN] = -nums * step; + tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] = step; +} +EXPORT_SYMBOL_GPL(snd_hda_set_vmaster_tlv); + +/* find a mixer control element with the given name */ +static struct snd_kcontrol * +find_mixer_ctl(struct hda_codec *codec, const char *name, int dev, int idx) +{ + struct snd_ctl_elem_id id; + memset(&id, 0, sizeof(id)); + id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + id.device = dev; + id.index = idx; + if (snd_BUG_ON(strlen(name) >= sizeof(id.name))) + return NULL; + strcpy(id.name, name); + return snd_ctl_find_id(codec->card, &id); +} + +/** + * snd_hda_find_mixer_ctl - Find a mixer control element with the given name + * @codec: HD-audio codec + * @name: ctl id name string + * + * Get the control element with the given id string and IFACE_MIXER. + */ +struct snd_kcontrol *snd_hda_find_mixer_ctl(struct hda_codec *codec, + const char *name) +{ + return find_mixer_ctl(codec, name, 0, 0); +} +EXPORT_SYMBOL_GPL(snd_hda_find_mixer_ctl); + +static int find_empty_mixer_ctl_idx(struct hda_codec *codec, const char *name, + int start_idx) +{ + int i, idx; + /* 16 ctlrs should be large enough */ + for (i = 0, idx = start_idx; i < 16; i++, idx++) { + if (!find_mixer_ctl(codec, name, 0, idx)) + return idx; + } + return -EBUSY; +} + +/** + * snd_hda_ctl_add - Add a control element and assign to the codec + * @codec: HD-audio codec + * @nid: corresponding NID (optional) + * @kctl: the control element to assign + * + * Add the given control element to an array inside the codec instance. + * All control elements belonging to a codec are supposed to be added + * by this function so that a proper clean-up works at the free or + * reconfiguration time. + * + * If non-zero @nid is passed, the NID is assigned to the control element. + * The assignment is shown in the codec proc file. + * + * snd_hda_ctl_add() checks the control subdev id field whether + * #HDA_SUBDEV_NID_FLAG bit is set. If set (and @nid is zero), the lower + * bits value is taken as the NID to assign. The #HDA_NID_ITEM_AMP bit + * specifies if kctl->private_value is a HDA amplifier value. + */ +int snd_hda_ctl_add(struct hda_codec *codec, hda_nid_t nid, + struct snd_kcontrol *kctl) +{ + int err; + unsigned short flags = 0; + struct hda_nid_item *item; + + if (kctl->id.subdevice & HDA_SUBDEV_AMP_FLAG) { + flags |= HDA_NID_ITEM_AMP; + if (nid == 0) + nid = get_amp_nid_(kctl->private_value); + } + if ((kctl->id.subdevice & HDA_SUBDEV_NID_FLAG) != 0 && nid == 0) + nid = kctl->id.subdevice & 0xffff; + if (kctl->id.subdevice & (HDA_SUBDEV_NID_FLAG|HDA_SUBDEV_AMP_FLAG)) + kctl->id.subdevice = 0; + err = snd_ctl_add(codec->card, kctl); + if (err < 0) + return err; + item = snd_array_new(&codec->mixers); + if (!item) + return -ENOMEM; + item->kctl = kctl; + item->nid = nid; + item->flags = flags; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_ctl_add); + +/** + * snd_hda_add_nid - Assign a NID to a control element + * @codec: HD-audio codec + * @nid: corresponding NID (optional) + * @kctl: the control element to assign + * @index: index to kctl + * + * Add the given control element to an array inside the codec instance. + * This function is used when #snd_hda_ctl_add cannot be used for 1:1 + * NID:KCTL mapping - for example "Capture Source" selector. + */ +int snd_hda_add_nid(struct hda_codec *codec, struct snd_kcontrol *kctl, + unsigned int index, hda_nid_t nid) +{ + struct hda_nid_item *item; + + if (nid > 0) { + item = snd_array_new(&codec->nids); + if (!item) + return -ENOMEM; + item->kctl = kctl; + item->index = index; + item->nid = nid; + return 0; + } + codec_err(codec, "no NID for mapping control %s:%d:%d\n", + kctl->id.name, kctl->id.index, index); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_hda_add_nid); + +/** + * snd_hda_ctls_clear - Clear all controls assigned to the given codec + * @codec: HD-audio codec + */ +void snd_hda_ctls_clear(struct hda_codec *codec) +{ + int i; + struct hda_nid_item *items = codec->mixers.list; + + down_write(&codec->card->controls_rwsem); + for (i = 0; i < codec->mixers.used; i++) + snd_ctl_remove(codec->card, items[i].kctl); + up_write(&codec->card->controls_rwsem); + snd_array_free(&codec->mixers); + snd_array_free(&codec->nids); +} + +/** + * snd_hda_lock_devices - pseudo device locking + * @bus: the BUS + * + * toggle card->shutdown to allow/disallow the device access (as a hack) + */ +int snd_hda_lock_devices(struct hda_bus *bus) +{ + struct snd_card *card = bus->card; + struct hda_codec *codec; + + spin_lock(&card->files_lock); + if (card->shutdown) + goto err_unlock; + card->shutdown = 1; + if (!list_empty(&card->ctl_files)) + goto err_clear; + + list_for_each_codec(codec, bus) { + struct hda_pcm *cpcm; + list_for_each_entry(cpcm, &codec->pcm_list_head, list) { + if (!cpcm->pcm) + continue; + if (cpcm->pcm->streams[0].substream_opened || + cpcm->pcm->streams[1].substream_opened) + goto err_clear; + } + } + spin_unlock(&card->files_lock); + return 0; + + err_clear: + card->shutdown = 0; + err_unlock: + spin_unlock(&card->files_lock); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_hda_lock_devices); + +/** + * snd_hda_unlock_devices - pseudo device unlocking + * @bus: the BUS + */ +void snd_hda_unlock_devices(struct hda_bus *bus) +{ + struct snd_card *card = bus->card; + + spin_lock(&card->files_lock); + card->shutdown = 0; + spin_unlock(&card->files_lock); +} +EXPORT_SYMBOL_GPL(snd_hda_unlock_devices); + +/** + * snd_hda_codec_reset - Clear all objects assigned to the codec + * @codec: HD-audio codec + * + * This frees the all PCM and control elements assigned to the codec, and + * clears the caches and restores the pin default configurations. + * + * When a device is being used, it returns -EBSY. If successfully freed, + * returns zero. + */ +int snd_hda_codec_reset(struct hda_codec *codec) +{ + struct hda_bus *bus = codec->bus; + + if (snd_hda_lock_devices(bus) < 0) + return -EBUSY; + + /* OK, let it free */ + device_release_driver(hda_codec_dev(codec)); + + /* allow device access again */ + snd_hda_unlock_devices(bus); + return 0; +} + +typedef int (*map_follower_func_t)(struct hda_codec *, void *, struct snd_kcontrol *); + +/* apply the function to all matching follower ctls in the mixer list */ +static int map_followers(struct hda_codec *codec, const char * const *followers, + const char *suffix, map_follower_func_t func, void *data) +{ + struct hda_nid_item *items; + const char * const *s; + int i, err; + + items = codec->mixers.list; + for (i = 0; i < codec->mixers.used; i++) { + struct snd_kcontrol *sctl = items[i].kctl; + if (!sctl || sctl->id.iface != SNDRV_CTL_ELEM_IFACE_MIXER) + continue; + for (s = followers; *s; s++) { + char tmpname[sizeof(sctl->id.name)]; + const char *name = *s; + if (suffix) { + snprintf(tmpname, sizeof(tmpname), "%s %s", + name, suffix); + name = tmpname; + } + if (!strcmp(sctl->id.name, name)) { + err = func(codec, data, sctl); + if (err) + return err; + break; + } + } + } + return 0; +} + +static int check_follower_present(struct hda_codec *codec, + void *data, struct snd_kcontrol *sctl) +{ + return 1; +} + +/* call kctl->put with the given value(s) */ +static int put_kctl_with_value(struct snd_kcontrol *kctl, int val) +{ + struct snd_ctl_elem_value *ucontrol; + ucontrol = kzalloc(sizeof(*ucontrol), GFP_KERNEL); + if (!ucontrol) + return -ENOMEM; + ucontrol->value.integer.value[0] = val; + ucontrol->value.integer.value[1] = val; + kctl->put(kctl, ucontrol); + kfree(ucontrol); + return 0; +} + +struct follower_init_arg { + struct hda_codec *codec; + int step; +}; + +/* initialize the follower volume with 0dB via snd_ctl_apply_vmaster_followers() */ +static int init_follower_0dB(struct snd_kcontrol *follower, + struct snd_kcontrol *kctl, + void *_arg) +{ + struct follower_init_arg *arg = _arg; + int _tlv[4]; + const int *tlv = NULL; + int step; + int val; + + if (kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) { + if (kctl->tlv.c != snd_hda_mixer_amp_tlv) { + codec_err(arg->codec, + "Unexpected TLV callback for follower %s:%d\n", + kctl->id.name, kctl->id.index); + return 0; /* ignore */ + } + get_ctl_amp_tlv(kctl, _tlv); + tlv = _tlv; + } else if (kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_READ) + tlv = kctl->tlv.p; + + if (!tlv || tlv[SNDRV_CTL_TLVO_TYPE] != SNDRV_CTL_TLVT_DB_SCALE) + return 0; + + step = tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP]; + step &= ~TLV_DB_SCALE_MUTE; + if (!step) + return 0; + if (arg->step && arg->step != step) { + codec_err(arg->codec, + "Mismatching dB step for vmaster follower (%d!=%d)\n", + arg->step, step); + return 0; + } + + arg->step = step; + val = -tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN] / step; + if (val > 0) { + put_kctl_with_value(follower, val); + return val; + } + + return 0; +} + +/* unmute the follower via snd_ctl_apply_vmaster_followers() */ +static int init_follower_unmute(struct snd_kcontrol *follower, + struct snd_kcontrol *kctl, + void *_arg) +{ + return put_kctl_with_value(follower, 1); +} + +static int add_follower(struct hda_codec *codec, + void *data, struct snd_kcontrol *follower) +{ + return snd_ctl_add_follower(data, follower); +} + +/** + * __snd_hda_add_vmaster - create a virtual master control and add followers + * @codec: HD-audio codec + * @name: vmaster control name + * @tlv: TLV data (optional) + * @followers: follower control names (optional) + * @suffix: suffix string to each follower name (optional) + * @init_follower_vol: initialize followers to unmute/0dB + * @access: kcontrol access rights + * @ctl_ret: store the vmaster kcontrol in return + * + * Create a virtual master control with the given name. The TLV data + * must be either NULL or a valid data. + * + * @followers is a NULL-terminated array of strings, each of which is a + * follower control name. All controls with these names are assigned to + * the new virtual master control. + * + * This function returns zero if successful or a negative error code. + */ +int __snd_hda_add_vmaster(struct hda_codec *codec, char *name, + unsigned int *tlv, const char * const *followers, + const char *suffix, bool init_follower_vol, + unsigned int access, struct snd_kcontrol **ctl_ret) +{ + struct snd_kcontrol *kctl; + int err; + + if (ctl_ret) + *ctl_ret = NULL; + + err = map_followers(codec, followers, suffix, check_follower_present, NULL); + if (err != 1) { + codec_dbg(codec, "No follower found for %s\n", name); + return 0; + } + kctl = snd_ctl_make_virtual_master(name, tlv); + if (!kctl) + return -ENOMEM; + kctl->vd[0].access |= access; + err = snd_hda_ctl_add(codec, 0, kctl); + if (err < 0) + return err; + + err = map_followers(codec, followers, suffix, add_follower, kctl); + if (err < 0) + return err; + + /* init with master mute & zero volume */ + put_kctl_with_value(kctl, 0); + if (init_follower_vol) { + struct follower_init_arg arg = { + .codec = codec, + .step = 0, + }; + snd_ctl_apply_vmaster_followers(kctl, + tlv ? init_follower_0dB : init_follower_unmute, + &arg); + } + + if (ctl_ret) + *ctl_ret = kctl; + return 0; +} +EXPORT_SYMBOL_GPL(__snd_hda_add_vmaster); + +/* meta hook to call each driver's vmaster hook */ +static void vmaster_hook(void *private_data, int enabled) +{ + struct hda_vmaster_mute_hook *hook = private_data; + + hook->hook(hook->codec, enabled); +} + +/** + * snd_hda_add_vmaster_hook - Add a vmaster hw specific hook + * @codec: the HDA codec + * @hook: the vmaster hook object + * + * Add a hw specific hook (like EAPD) with the given vmaster switch kctl. + */ +int snd_hda_add_vmaster_hook(struct hda_codec *codec, + struct hda_vmaster_mute_hook *hook) +{ + if (!hook->hook || !hook->sw_kctl) + return 0; + hook->codec = codec; + snd_ctl_add_vmaster_hook(hook->sw_kctl, vmaster_hook, hook); + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_add_vmaster_hook); + +/** + * snd_hda_sync_vmaster_hook - Sync vmaster hook + * @hook: the vmaster hook + * + * Call the hook with the current value for synchronization. + * Should be called in init callback. + */ +void snd_hda_sync_vmaster_hook(struct hda_vmaster_mute_hook *hook) +{ + if (!hook->hook || !hook->codec) + return; + /* don't call vmaster hook in the destructor since it might have + * been already destroyed + */ + if (hook->codec->bus->shutdown) + return; + snd_ctl_sync_vmaster_hook(hook->sw_kctl); +} +EXPORT_SYMBOL_GPL(snd_hda_sync_vmaster_hook); + + +/** + * snd_hda_mixer_amp_switch_info - Info callback for a standard AMP mixer switch + * @kcontrol: referred ctl element + * @uinfo: pointer to get/store the data + * + * The control element is supposed to have the private_value field + * set up via HDA_COMPOSE_AMP_VAL*() or related macros. + */ +int snd_hda_mixer_amp_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int chs = get_amp_channels(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = chs == 3 ? 2 : 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_switch_info); + +/** + * snd_hda_mixer_amp_switch_get - Get callback for a standard AMP mixer switch + * @kcontrol: ctl element + * @ucontrol: pointer to get/store the data + * + * The control element is supposed to have the private_value field + * set up via HDA_COMPOSE_AMP_VAL*() or related macros. + */ +int snd_hda_mixer_amp_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = get_amp_nid(kcontrol); + int chs = get_amp_channels(kcontrol); + int dir = get_amp_direction(kcontrol); + int idx = get_amp_index(kcontrol); + long *valp = ucontrol->value.integer.value; + + if (chs & 1) + *valp++ = (snd_hda_codec_amp_read(codec, nid, 0, dir, idx) & + HDA_AMP_MUTE) ? 0 : 1; + if (chs & 2) + *valp = (snd_hda_codec_amp_read(codec, nid, 1, dir, idx) & + HDA_AMP_MUTE) ? 0 : 1; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_switch_get); + +/** + * snd_hda_mixer_amp_switch_put - Put callback for a standard AMP mixer switch + * @kcontrol: ctl element + * @ucontrol: pointer to get/store the data + * + * The control element is supposed to have the private_value field + * set up via HDA_COMPOSE_AMP_VAL*() or related macros. + */ +int snd_hda_mixer_amp_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = get_amp_nid(kcontrol); + int chs = get_amp_channels(kcontrol); + int dir = get_amp_direction(kcontrol); + int idx = get_amp_index(kcontrol); + long *valp = ucontrol->value.integer.value; + int change = 0; + + if (chs & 1) { + change = snd_hda_codec_amp_update(codec, nid, 0, dir, idx, + HDA_AMP_MUTE, + *valp ? 0 : HDA_AMP_MUTE); + valp++; + } + if (chs & 2) + change |= snd_hda_codec_amp_update(codec, nid, 1, dir, idx, + HDA_AMP_MUTE, + *valp ? 0 : HDA_AMP_MUTE); + hda_call_check_power_status(codec, nid); + return change; +} +EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_switch_put); + +/* + * SPDIF out controls + */ + +static int snd_hda_spdif_mask_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_hda_spdif_cmask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = IEC958_AES0_PROFESSIONAL | + IEC958_AES0_NONAUDIO | + IEC958_AES0_CON_EMPHASIS_5015 | + IEC958_AES0_CON_NOT_COPYRIGHT; + ucontrol->value.iec958.status[1] = IEC958_AES1_CON_CATEGORY | + IEC958_AES1_CON_ORIGINAL; + return 0; +} + +static int snd_hda_spdif_pmask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = IEC958_AES0_PROFESSIONAL | + IEC958_AES0_NONAUDIO | + IEC958_AES0_PRO_EMPHASIS_5015; + return 0; +} + +static int snd_hda_spdif_default_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + int idx = kcontrol->private_value; + struct hda_spdif_out *spdif; + + if (WARN_ON(codec->spdif_out.used <= idx)) + return -EINVAL; + mutex_lock(&codec->spdif_mutex); + spdif = snd_array_elem(&codec->spdif_out, idx); + ucontrol->value.iec958.status[0] = spdif->status & 0xff; + ucontrol->value.iec958.status[1] = (spdif->status >> 8) & 0xff; + ucontrol->value.iec958.status[2] = (spdif->status >> 16) & 0xff; + ucontrol->value.iec958.status[3] = (spdif->status >> 24) & 0xff; + mutex_unlock(&codec->spdif_mutex); + + return 0; +} + +/* convert from SPDIF status bits to HDA SPDIF bits + * bit 0 (DigEn) is always set zero (to be filled later) + */ +static unsigned short convert_from_spdif_status(unsigned int sbits) +{ + unsigned short val = 0; + + if (sbits & IEC958_AES0_PROFESSIONAL) + val |= AC_DIG1_PROFESSIONAL; + if (sbits & IEC958_AES0_NONAUDIO) + val |= AC_DIG1_NONAUDIO; + if (sbits & IEC958_AES0_PROFESSIONAL) { + if ((sbits & IEC958_AES0_PRO_EMPHASIS) == + IEC958_AES0_PRO_EMPHASIS_5015) + val |= AC_DIG1_EMPHASIS; + } else { + if ((sbits & IEC958_AES0_CON_EMPHASIS) == + IEC958_AES0_CON_EMPHASIS_5015) + val |= AC_DIG1_EMPHASIS; + if (!(sbits & IEC958_AES0_CON_NOT_COPYRIGHT)) + val |= AC_DIG1_COPYRIGHT; + if (sbits & (IEC958_AES1_CON_ORIGINAL << 8)) + val |= AC_DIG1_LEVEL; + val |= sbits & (IEC958_AES1_CON_CATEGORY << 8); + } + return val; +} + +/* convert to SPDIF status bits from HDA SPDIF bits + */ +static unsigned int convert_to_spdif_status(unsigned short val) +{ + unsigned int sbits = 0; + + if (val & AC_DIG1_NONAUDIO) + sbits |= IEC958_AES0_NONAUDIO; + if (val & AC_DIG1_PROFESSIONAL) + sbits |= IEC958_AES0_PROFESSIONAL; + if (sbits & IEC958_AES0_PROFESSIONAL) { + if (val & AC_DIG1_EMPHASIS) + sbits |= IEC958_AES0_PRO_EMPHASIS_5015; + } else { + if (val & AC_DIG1_EMPHASIS) + sbits |= IEC958_AES0_CON_EMPHASIS_5015; + if (!(val & AC_DIG1_COPYRIGHT)) + sbits |= IEC958_AES0_CON_NOT_COPYRIGHT; + if (val & AC_DIG1_LEVEL) + sbits |= (IEC958_AES1_CON_ORIGINAL << 8); + sbits |= val & (0x7f << 8); + } + return sbits; +} + +/* set digital convert verbs both for the given NID and its followers */ +static void set_dig_out(struct hda_codec *codec, hda_nid_t nid, + int mask, int val) +{ + const hda_nid_t *d; + + snd_hdac_regmap_update(&codec->core, nid, AC_VERB_SET_DIGI_CONVERT_1, + mask, val); + d = codec->follower_dig_outs; + if (!d) + return; + for (; *d; d++) + snd_hdac_regmap_update(&codec->core, *d, + AC_VERB_SET_DIGI_CONVERT_1, mask, val); +} + +static inline void set_dig_out_convert(struct hda_codec *codec, hda_nid_t nid, + int dig1, int dig2) +{ + unsigned int mask = 0; + unsigned int val = 0; + + if (dig1 != -1) { + mask |= 0xff; + val = dig1; + } + if (dig2 != -1) { + mask |= 0xff00; + val |= dig2 << 8; + } + set_dig_out(codec, nid, mask, val); +} + +static int snd_hda_spdif_default_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + int idx = kcontrol->private_value; + struct hda_spdif_out *spdif; + hda_nid_t nid; + unsigned short val; + int change; + + if (WARN_ON(codec->spdif_out.used <= idx)) + return -EINVAL; + mutex_lock(&codec->spdif_mutex); + spdif = snd_array_elem(&codec->spdif_out, idx); + nid = spdif->nid; + spdif->status = ucontrol->value.iec958.status[0] | + ((unsigned int)ucontrol->value.iec958.status[1] << 8) | + ((unsigned int)ucontrol->value.iec958.status[2] << 16) | + ((unsigned int)ucontrol->value.iec958.status[3] << 24); + val = convert_from_spdif_status(spdif->status); + val |= spdif->ctls & 1; + change = spdif->ctls != val; + spdif->ctls = val; + if (change && nid != (u16)-1) + set_dig_out_convert(codec, nid, val & 0xff, (val >> 8) & 0xff); + mutex_unlock(&codec->spdif_mutex); + return change; +} + +#define snd_hda_spdif_out_switch_info snd_ctl_boolean_mono_info + +static int snd_hda_spdif_out_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + int idx = kcontrol->private_value; + struct hda_spdif_out *spdif; + + if (WARN_ON(codec->spdif_out.used <= idx)) + return -EINVAL; + mutex_lock(&codec->spdif_mutex); + spdif = snd_array_elem(&codec->spdif_out, idx); + ucontrol->value.integer.value[0] = spdif->ctls & AC_DIG1_ENABLE; + mutex_unlock(&codec->spdif_mutex); + return 0; +} + +static inline void set_spdif_ctls(struct hda_codec *codec, hda_nid_t nid, + int dig1, int dig2) +{ + set_dig_out_convert(codec, nid, dig1, dig2); + /* unmute amp switch (if any) */ + if ((get_wcaps(codec, nid) & AC_WCAP_OUT_AMP) && + (dig1 & AC_DIG1_ENABLE)) + snd_hda_codec_amp_stereo(codec, nid, HDA_OUTPUT, 0, + HDA_AMP_MUTE, 0); +} + +static int snd_hda_spdif_out_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + int idx = kcontrol->private_value; + struct hda_spdif_out *spdif; + hda_nid_t nid; + unsigned short val; + int change; + + if (WARN_ON(codec->spdif_out.used <= idx)) + return -EINVAL; + mutex_lock(&codec->spdif_mutex); + spdif = snd_array_elem(&codec->spdif_out, idx); + nid = spdif->nid; + val = spdif->ctls & ~AC_DIG1_ENABLE; + if (ucontrol->value.integer.value[0]) + val |= AC_DIG1_ENABLE; + change = spdif->ctls != val; + spdif->ctls = val; + if (change && nid != (u16)-1) + set_spdif_ctls(codec, nid, val & 0xff, -1); + mutex_unlock(&codec->spdif_mutex); + return change; +} + +static const struct snd_kcontrol_new dig_mixes[] = { + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK), + .info = snd_hda_spdif_mask_info, + .get = snd_hda_spdif_cmask_get, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PRO_MASK), + .info = snd_hda_spdif_mask_info, + .get = snd_hda_spdif_pmask_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = snd_hda_spdif_mask_info, + .get = snd_hda_spdif_default_get, + .put = snd_hda_spdif_default_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH), + .info = snd_hda_spdif_out_switch_info, + .get = snd_hda_spdif_out_switch_get, + .put = snd_hda_spdif_out_switch_put, + }, + { } /* end */ +}; + +/** + * snd_hda_create_dig_out_ctls - create Output SPDIF-related controls + * @codec: the HDA codec + * @associated_nid: NID that new ctls associated with + * @cvt_nid: converter NID + * @type: HDA_PCM_TYPE_* + * Creates controls related with the digital output. + * Called from each patch supporting the digital out. + * + * Returns 0 if successful, or a negative error code. + */ +int snd_hda_create_dig_out_ctls(struct hda_codec *codec, + hda_nid_t associated_nid, + hda_nid_t cvt_nid, + int type) +{ + int err; + struct snd_kcontrol *kctl; + const struct snd_kcontrol_new *dig_mix; + int idx = 0; + int val = 0; + const int spdif_index = 16; + struct hda_spdif_out *spdif; + struct hda_bus *bus = codec->bus; + + if (bus->primary_dig_out_type == HDA_PCM_TYPE_HDMI && + type == HDA_PCM_TYPE_SPDIF) { + idx = spdif_index; + } else if (bus->primary_dig_out_type == HDA_PCM_TYPE_SPDIF && + type == HDA_PCM_TYPE_HDMI) { + /* suppose a single SPDIF device */ + for (dig_mix = dig_mixes; dig_mix->name; dig_mix++) { + struct snd_ctl_elem_id id; + + kctl = find_mixer_ctl(codec, dig_mix->name, 0, 0); + if (!kctl) + break; + id = kctl->id; + id.index = spdif_index; + snd_ctl_rename_id(codec->card, &kctl->id, &id); + } + bus->primary_dig_out_type = HDA_PCM_TYPE_HDMI; + } + if (!bus->primary_dig_out_type) + bus->primary_dig_out_type = type; + + idx = find_empty_mixer_ctl_idx(codec, "IEC958 Playback Switch", idx); + if (idx < 0) { + codec_err(codec, "too many IEC958 outputs\n"); + return -EBUSY; + } + spdif = snd_array_new(&codec->spdif_out); + if (!spdif) + return -ENOMEM; + for (dig_mix = dig_mixes; dig_mix->name; dig_mix++) { + kctl = snd_ctl_new1(dig_mix, codec); + if (!kctl) + return -ENOMEM; + kctl->id.index = idx; + kctl->private_value = codec->spdif_out.used - 1; + err = snd_hda_ctl_add(codec, associated_nid, kctl); + if (err < 0) + return err; + } + spdif->nid = cvt_nid; + snd_hdac_regmap_read(&codec->core, cvt_nid, + AC_VERB_GET_DIGI_CONVERT_1, &val); + spdif->ctls = val; + spdif->status = convert_to_spdif_status(spdif->ctls); + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_create_dig_out_ctls); + +/** + * snd_hda_spdif_out_of_nid - get the hda_spdif_out entry from the given NID + * @codec: the HDA codec + * @nid: widget NID + * + * call within spdif_mutex lock + */ +struct hda_spdif_out *snd_hda_spdif_out_of_nid(struct hda_codec *codec, + hda_nid_t nid) +{ + struct hda_spdif_out *spdif; + int i; + + snd_array_for_each(&codec->spdif_out, i, spdif) { + if (spdif->nid == nid) + return spdif; + } + return NULL; +} +EXPORT_SYMBOL_GPL(snd_hda_spdif_out_of_nid); + +/** + * snd_hda_spdif_ctls_unassign - Unassign the given SPDIF ctl + * @codec: the HDA codec + * @idx: the SPDIF ctl index + * + * Unassign the widget from the given SPDIF control. + */ +void snd_hda_spdif_ctls_unassign(struct hda_codec *codec, int idx) +{ + struct hda_spdif_out *spdif; + + if (WARN_ON(codec->spdif_out.used <= idx)) + return; + mutex_lock(&codec->spdif_mutex); + spdif = snd_array_elem(&codec->spdif_out, idx); + spdif->nid = (u16)-1; + mutex_unlock(&codec->spdif_mutex); +} +EXPORT_SYMBOL_GPL(snd_hda_spdif_ctls_unassign); + +/** + * snd_hda_spdif_ctls_assign - Assign the SPDIF controls to the given NID + * @codec: the HDA codec + * @idx: the SPDIF ctl idx + * @nid: widget NID + * + * Assign the widget to the SPDIF control with the given index. + */ +void snd_hda_spdif_ctls_assign(struct hda_codec *codec, int idx, hda_nid_t nid) +{ + struct hda_spdif_out *spdif; + unsigned short val; + + if (WARN_ON(codec->spdif_out.used <= idx)) + return; + mutex_lock(&codec->spdif_mutex); + spdif = snd_array_elem(&codec->spdif_out, idx); + if (spdif->nid != nid) { + spdif->nid = nid; + val = spdif->ctls; + set_spdif_ctls(codec, nid, val & 0xff, (val >> 8) & 0xff); + } + mutex_unlock(&codec->spdif_mutex); +} +EXPORT_SYMBOL_GPL(snd_hda_spdif_ctls_assign); + +/* + * SPDIF sharing with analog output + */ +static int spdif_share_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_multi_out *mout = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = mout->share_spdif; + return 0; +} + +static int spdif_share_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_multi_out *mout = snd_kcontrol_chip(kcontrol); + mout->share_spdif = !!ucontrol->value.integer.value[0]; + return 0; +} + +static const struct snd_kcontrol_new spdif_share_sw = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "IEC958 Default PCM Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = spdif_share_sw_get, + .put = spdif_share_sw_put, +}; + +/** + * snd_hda_create_spdif_share_sw - create Default PCM switch + * @codec: the HDA codec + * @mout: multi-out instance + */ +int snd_hda_create_spdif_share_sw(struct hda_codec *codec, + struct hda_multi_out *mout) +{ + struct snd_kcontrol *kctl; + + if (!mout->dig_out_nid) + return 0; + + kctl = snd_ctl_new1(&spdif_share_sw, mout); + if (!kctl) + return -ENOMEM; + /* ATTENTION: here mout is passed as private_data, instead of codec */ + return snd_hda_ctl_add(codec, mout->dig_out_nid, kctl); +} +EXPORT_SYMBOL_GPL(snd_hda_create_spdif_share_sw); + +/* + * SPDIF input + */ + +#define snd_hda_spdif_in_switch_info snd_hda_spdif_out_switch_info + +static int snd_hda_spdif_in_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = codec->spdif_in_enable; + return 0; +} + +static int snd_hda_spdif_in_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = kcontrol->private_value; + unsigned int val = !!ucontrol->value.integer.value[0]; + int change; + + mutex_lock(&codec->spdif_mutex); + change = codec->spdif_in_enable != val; + if (change) { + codec->spdif_in_enable = val; + snd_hdac_regmap_write(&codec->core, nid, + AC_VERB_SET_DIGI_CONVERT_1, val); + } + mutex_unlock(&codec->spdif_mutex); + return change; +} + +static int snd_hda_spdif_in_status_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = kcontrol->private_value; + unsigned int val; + unsigned int sbits; + + snd_hdac_regmap_read(&codec->core, nid, + AC_VERB_GET_DIGI_CONVERT_1, &val); + sbits = convert_to_spdif_status(val); + ucontrol->value.iec958.status[0] = sbits; + ucontrol->value.iec958.status[1] = sbits >> 8; + ucontrol->value.iec958.status[2] = sbits >> 16; + ucontrol->value.iec958.status[3] = sbits >> 24; + return 0; +} + +static const struct snd_kcontrol_new dig_in_ctls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, SWITCH), + .info = snd_hda_spdif_in_switch_info, + .get = snd_hda_spdif_in_switch_get, + .put = snd_hda_spdif_in_switch_put, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT), + .info = snd_hda_spdif_mask_info, + .get = snd_hda_spdif_in_status_get, + }, + { } /* end */ +}; + +/** + * snd_hda_create_spdif_in_ctls - create Input SPDIF-related controls + * @codec: the HDA codec + * @nid: audio in widget NID + * + * Creates controls related with the SPDIF input. + * Called from each patch supporting the SPDIF in. + * + * Returns 0 if successful, or a negative error code. + */ +int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid) +{ + int err; + struct snd_kcontrol *kctl; + const struct snd_kcontrol_new *dig_mix; + int idx; + + idx = find_empty_mixer_ctl_idx(codec, "IEC958 Capture Switch", 0); + if (idx < 0) { + codec_err(codec, "too many IEC958 inputs\n"); + return -EBUSY; + } + for (dig_mix = dig_in_ctls; dig_mix->name; dig_mix++) { + kctl = snd_ctl_new1(dig_mix, codec); + if (!kctl) + return -ENOMEM; + kctl->private_value = nid; + err = snd_hda_ctl_add(codec, nid, kctl); + if (err < 0) + return err; + } + codec->spdif_in_enable = + snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_DIGI_CONVERT_1, 0) & + AC_DIG1_ENABLE; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_create_spdif_in_ctls); + +/** + * snd_hda_codec_set_power_to_all - Set the power state to all widgets + * @codec: the HDA codec + * @fg: function group (not used now) + * @power_state: the power state to set (AC_PWRST_*) + * + * Set the given power state to all widgets that have the power control. + * If the codec has power_filter set, it evaluates the power state and + * filter out if it's unchanged as D3. + */ +void snd_hda_codec_set_power_to_all(struct hda_codec *codec, hda_nid_t fg, + unsigned int power_state) +{ + hda_nid_t nid; + + for_each_hda_codec_node(nid, codec) { + unsigned int wcaps = get_wcaps(codec, nid); + unsigned int state = power_state; + if (!(wcaps & AC_WCAP_POWER)) + continue; + if (codec->power_filter) { + state = codec->power_filter(codec, nid, power_state); + if (state != power_state && power_state == AC_PWRST_D3) + continue; + } + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_POWER_STATE, + state); + } +} +EXPORT_SYMBOL_GPL(snd_hda_codec_set_power_to_all); + +/** + * snd_hda_codec_eapd_power_filter - A power filter callback for EAPD + * @codec: the HDA codec + * @nid: widget NID + * @power_state: power state to evalue + * + * Don't power down the widget if it controls eapd and EAPD_BTLENABLE is set. + * This can be used a codec power_filter callback. + */ +unsigned int snd_hda_codec_eapd_power_filter(struct hda_codec *codec, + hda_nid_t nid, + unsigned int power_state) +{ + if (nid == codec->core.afg || nid == codec->core.mfg) + return power_state; + if (power_state == AC_PWRST_D3 && + get_wcaps_type(get_wcaps(codec, nid)) == AC_WID_PIN && + (snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_EAPD)) { + int eapd = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_EAPD_BTLENABLE, 0); + if (eapd & 0x02) + return AC_PWRST_D0; + } + return power_state; +} +EXPORT_SYMBOL_GPL(snd_hda_codec_eapd_power_filter); + +/* + * set power state of the codec, and return the power state + */ +static unsigned int hda_set_power_state(struct hda_codec *codec, + unsigned int power_state) +{ + hda_nid_t fg = codec->core.afg ? codec->core.afg : codec->core.mfg; + int count; + unsigned int state; + int flags = 0; + + /* this delay seems necessary to avoid click noise at power-down */ + if (power_state == AC_PWRST_D3) { + if (codec->depop_delay < 0) + msleep(codec_has_epss(codec) ? 10 : 100); + else if (codec->depop_delay > 0) + msleep(codec->depop_delay); + flags = HDA_RW_NO_RESPONSE_FALLBACK; + } + + /* repeat power states setting at most 10 times*/ + for (count = 0; count < 10; count++) { + if (codec->patch_ops.set_power_state) + codec->patch_ops.set_power_state(codec, fg, + power_state); + else { + state = power_state; + if (codec->power_filter) + state = codec->power_filter(codec, fg, state); + if (state == power_state || power_state != AC_PWRST_D3) + snd_hda_codec_read(codec, fg, flags, + AC_VERB_SET_POWER_STATE, + state); + snd_hda_codec_set_power_to_all(codec, fg, power_state); + } + state = snd_hda_sync_power_state(codec, fg, power_state); + if (!(state & AC_PWRST_ERROR)) + break; + } + + return state; +} + +/* sync power states of all widgets; + * this is called at the end of codec parsing + */ +static void sync_power_up_states(struct hda_codec *codec) +{ + hda_nid_t nid; + + /* don't care if no filter is used */ + if (!codec->power_filter) + return; + + for_each_hda_codec_node(nid, codec) { + unsigned int wcaps = get_wcaps(codec, nid); + unsigned int target; + if (!(wcaps & AC_WCAP_POWER)) + continue; + target = codec->power_filter(codec, nid, AC_PWRST_D0); + if (target == AC_PWRST_D0) + continue; + if (!snd_hda_check_power_state(codec, nid, target)) + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_POWER_STATE, target); + } +} + +#ifdef CONFIG_SND_HDA_RECONFIG +/* execute additional init verbs */ +static void hda_exec_init_verbs(struct hda_codec *codec) +{ + if (codec->init_verbs.list) + snd_hda_sequence_write(codec, codec->init_verbs.list); +} +#else +static inline void hda_exec_init_verbs(struct hda_codec *codec) {} +#endif + +#ifdef CONFIG_PM +/* update the power on/off account with the current jiffies */ +static void update_power_acct(struct hda_codec *codec, bool on) +{ + unsigned long delta = jiffies - codec->power_jiffies; + + if (on) + codec->power_on_acct += delta; + else + codec->power_off_acct += delta; + codec->power_jiffies += delta; +} + +void snd_hda_update_power_acct(struct hda_codec *codec) +{ + update_power_acct(codec, hda_codec_is_power_on(codec)); +} + +/* + * call suspend and power-down; used both from PM and power-save + * this function returns the power state in the end + */ +static unsigned int hda_call_codec_suspend(struct hda_codec *codec) +{ + unsigned int state; + + snd_hdac_enter_pm(&codec->core); + if (codec->patch_ops.suspend) + codec->patch_ops.suspend(codec); + if (!codec->no_stream_clean_at_suspend) + hda_cleanup_all_streams(codec); + state = hda_set_power_state(codec, AC_PWRST_D3); + update_power_acct(codec, true); + snd_hdac_leave_pm(&codec->core); + return state; +} + +/* + * kick up codec; used both from PM and power-save + */ +static void hda_call_codec_resume(struct hda_codec *codec) +{ + snd_hdac_enter_pm(&codec->core); + if (codec->core.regmap) + regcache_mark_dirty(codec->core.regmap); + + codec->power_jiffies = jiffies; + + hda_set_power_state(codec, AC_PWRST_D0); + restore_shutup_pins(codec); + hda_exec_init_verbs(codec); + snd_hda_jack_set_dirty_all(codec); + if (codec->patch_ops.resume) + codec->patch_ops.resume(codec); + else { + if (codec->patch_ops.init) + codec->patch_ops.init(codec); + snd_hda_regmap_sync(codec); + } + + if (codec->jackpoll_interval) + hda_jackpoll_work(&codec->jackpoll_work.work); + else + snd_hda_jack_report_sync(codec); + codec->core.dev.power.power_state = PMSG_ON; + snd_hdac_leave_pm(&codec->core); +} + +static int hda_codec_runtime_suspend(struct device *dev) +{ + struct hda_codec *codec = dev_to_hda_codec(dev); + unsigned int state; + + /* Nothing to do if card registration fails and the component driver never probes */ + if (!codec->card) + return 0; + + cancel_delayed_work_sync(&codec->jackpoll_work); + + state = hda_call_codec_suspend(codec); + if (codec->link_down_at_suspend || + (codec_has_clkstop(codec) && codec_has_epss(codec) && + (state & AC_PWRST_CLK_STOP_OK))) + snd_hdac_codec_link_down(&codec->core); + snd_hda_codec_display_power(codec, false); + + if (codec->bus->jackpoll_in_suspend && + (dev->power.power_state.event != PM_EVENT_SUSPEND)) + schedule_delayed_work(&codec->jackpoll_work, + codec->jackpoll_interval); + return 0; +} + +static int hda_codec_runtime_resume(struct device *dev) +{ + struct hda_codec *codec = dev_to_hda_codec(dev); + + /* Nothing to do if card registration fails and the component driver never probes */ + if (!codec->card) + return 0; + + snd_hda_codec_display_power(codec, true); + snd_hdac_codec_link_up(&codec->core); + hda_call_codec_resume(codec); + pm_runtime_mark_last_busy(dev); + return 0; +} + +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PM_SLEEP +static int hda_codec_pm_prepare(struct device *dev) +{ + struct hda_codec *codec = dev_to_hda_codec(dev); + + cancel_delayed_work_sync(&codec->jackpoll_work); + dev->power.power_state = PMSG_SUSPEND; + return pm_runtime_suspended(dev); +} + +static void hda_codec_pm_complete(struct device *dev) +{ + struct hda_codec *codec = dev_to_hda_codec(dev); + + /* If no other pm-functions are called between prepare() and complete() */ + if (dev->power.power_state.event == PM_EVENT_SUSPEND) + dev->power.power_state = PMSG_RESUME; + + if (pm_runtime_suspended(dev) && (codec->jackpoll_interval || + hda_codec_need_resume(codec) || codec->forced_resume)) + pm_request_resume(dev); +} + +static int hda_codec_pm_suspend(struct device *dev) +{ + dev->power.power_state = PMSG_SUSPEND; + return pm_runtime_force_suspend(dev); +} + +static int hda_codec_pm_resume(struct device *dev) +{ + dev->power.power_state = PMSG_RESUME; + return pm_runtime_force_resume(dev); +} + +static int hda_codec_pm_freeze(struct device *dev) +{ + struct hda_codec *codec = dev_to_hda_codec(dev); + + cancel_delayed_work_sync(&codec->jackpoll_work); + dev->power.power_state = PMSG_FREEZE; + return pm_runtime_force_suspend(dev); +} + +static int hda_codec_pm_thaw(struct device *dev) +{ + dev->power.power_state = PMSG_THAW; + return pm_runtime_force_resume(dev); +} + +static int hda_codec_pm_restore(struct device *dev) +{ + dev->power.power_state = PMSG_RESTORE; + return pm_runtime_force_resume(dev); +} +#endif /* CONFIG_PM_SLEEP */ + +/* referred in hda_bind.c */ +const struct dev_pm_ops hda_codec_driver_pm = { +#ifdef CONFIG_PM_SLEEP + .prepare = hda_codec_pm_prepare, + .complete = hda_codec_pm_complete, + .suspend = hda_codec_pm_suspend, + .resume = hda_codec_pm_resume, + .freeze = hda_codec_pm_freeze, + .thaw = hda_codec_pm_thaw, + .poweroff = hda_codec_pm_suspend, + .restore = hda_codec_pm_restore, +#endif /* CONFIG_PM_SLEEP */ + SET_RUNTIME_PM_OPS(hda_codec_runtime_suspend, hda_codec_runtime_resume, + NULL) +}; + +/* suspend the codec at shutdown; called from driver's shutdown callback */ +void snd_hda_codec_shutdown(struct hda_codec *codec) +{ + struct hda_pcm *cpcm; + + /* Skip the shutdown if codec is not registered */ + if (!codec->core.registered) + return; + + cancel_delayed_work_sync(&codec->jackpoll_work); + list_for_each_entry(cpcm, &codec->pcm_list_head, list) + snd_pcm_suspend_all(cpcm->pcm); + + pm_runtime_force_suspend(hda_codec_dev(codec)); + pm_runtime_disable(hda_codec_dev(codec)); +} + +/* + * add standard channel maps if not specified + */ +static int add_std_chmaps(struct hda_codec *codec) +{ + struct hda_pcm *pcm; + int str, err; + + list_for_each_entry(pcm, &codec->pcm_list_head, list) { + for (str = 0; str < 2; str++) { + struct hda_pcm_stream *hinfo = &pcm->stream[str]; + struct snd_pcm_chmap *chmap; + const struct snd_pcm_chmap_elem *elem; + + if (!pcm->pcm || pcm->own_chmap || !hinfo->substreams) + continue; + elem = hinfo->chmap ? hinfo->chmap : snd_pcm_std_chmaps; + err = snd_pcm_add_chmap_ctls(pcm->pcm, str, elem, + hinfo->channels_max, + 0, &chmap); + if (err < 0) + return err; + chmap->channel_mask = SND_PCM_CHMAP_MASK_2468; + } + } + return 0; +} + +/* default channel maps for 2.1 speakers; + * since HD-audio supports only stereo, odd number channels are omitted + */ +const struct snd_pcm_chmap_elem snd_pcm_2_1_chmaps[] = { + { .channels = 2, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } }, + { .channels = 4, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_LFE, SNDRV_CHMAP_LFE } }, + { } +}; +EXPORT_SYMBOL_GPL(snd_pcm_2_1_chmaps); + +int snd_hda_codec_build_controls(struct hda_codec *codec) +{ + int err = 0; + hda_exec_init_verbs(codec); + /* continue to initialize... */ + if (codec->patch_ops.init) + err = codec->patch_ops.init(codec); + if (!err && codec->patch_ops.build_controls) + err = codec->patch_ops.build_controls(codec); + if (err < 0) + return err; + + /* we create chmaps here instead of build_pcms */ + err = add_std_chmaps(codec); + if (err < 0) + return err; + + if (codec->jackpoll_interval) + hda_jackpoll_work(&codec->jackpoll_work.work); + else + snd_hda_jack_report_sync(codec); /* call at the last init point */ + sync_power_up_states(codec); + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_codec_build_controls); + +/* + * PCM stuff + */ +static int hda_pcm_default_open_close(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + return 0; +} + +static int hda_pcm_default_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + snd_hda_codec_setup_stream(codec, hinfo->nid, stream_tag, 0, format); + return 0; +} + +static int hda_pcm_default_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + snd_hda_codec_cleanup_stream(codec, hinfo->nid); + return 0; +} + +static int set_pcm_default_values(struct hda_codec *codec, + struct hda_pcm_stream *info) +{ + int err; + + /* query support PCM information from the given NID */ + if (info->nid && (!info->rates || !info->formats)) { + err = snd_hda_query_supported_pcm(codec, info->nid, + info->rates ? NULL : &info->rates, + info->formats ? NULL : &info->formats, + info->maxbps ? NULL : &info->maxbps); + if (err < 0) + return err; + } + if (info->ops.open == NULL) + info->ops.open = hda_pcm_default_open_close; + if (info->ops.close == NULL) + info->ops.close = hda_pcm_default_open_close; + if (info->ops.prepare == NULL) { + if (snd_BUG_ON(!info->nid)) + return -EINVAL; + info->ops.prepare = hda_pcm_default_prepare; + } + if (info->ops.cleanup == NULL) { + if (snd_BUG_ON(!info->nid)) + return -EINVAL; + info->ops.cleanup = hda_pcm_default_cleanup; + } + return 0; +} + +/* + * codec prepare/cleanup entries + */ +/** + * snd_hda_codec_prepare - Prepare a stream + * @codec: the HDA codec + * @hinfo: PCM information + * @stream: stream tag to assign + * @format: format id to assign + * @substream: PCM substream to assign + * + * Calls the prepare callback set by the codec with the given arguments. + * Clean up the inactive streams when successful. + */ +int snd_hda_codec_prepare(struct hda_codec *codec, + struct hda_pcm_stream *hinfo, + unsigned int stream, + unsigned int format, + struct snd_pcm_substream *substream) +{ + int ret; + mutex_lock(&codec->bus->prepare_mutex); + if (hinfo->ops.prepare) + ret = hinfo->ops.prepare(hinfo, codec, stream, format, + substream); + else + ret = -ENODEV; + if (ret >= 0) + purify_inactive_streams(codec); + mutex_unlock(&codec->bus->prepare_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(snd_hda_codec_prepare); + +/** + * snd_hda_codec_cleanup - Clean up stream resources + * @codec: the HDA codec + * @hinfo: PCM information + * @substream: PCM substream + * + * Calls the cleanup callback set by the codec with the given arguments. + */ +void snd_hda_codec_cleanup(struct hda_codec *codec, + struct hda_pcm_stream *hinfo, + struct snd_pcm_substream *substream) +{ + mutex_lock(&codec->bus->prepare_mutex); + if (hinfo->ops.cleanup) + hinfo->ops.cleanup(hinfo, codec, substream); + mutex_unlock(&codec->bus->prepare_mutex); +} +EXPORT_SYMBOL_GPL(snd_hda_codec_cleanup); + +/* global */ +const char *snd_hda_pcm_type_name[HDA_PCM_NTYPES] = { + "Audio", "SPDIF", "HDMI", "Modem" +}; + +/* + * get the empty PCM device number to assign + */ +static int get_empty_pcm_device(struct hda_bus *bus, unsigned int type) +{ + /* audio device indices; not linear to keep compatibility */ + /* assigned to static slots up to dev#10; if more needed, assign + * the later slot dynamically (when CONFIG_SND_DYNAMIC_MINORS=y) + */ + static const int audio_idx[HDA_PCM_NTYPES][5] = { + [HDA_PCM_TYPE_AUDIO] = { 0, 2, 4, 5, -1 }, + [HDA_PCM_TYPE_SPDIF] = { 1, -1 }, + [HDA_PCM_TYPE_HDMI] = { 3, 7, 8, 9, -1 }, + [HDA_PCM_TYPE_MODEM] = { 6, -1 }, + }; + int i; + + if (type >= HDA_PCM_NTYPES) { + dev_err(bus->card->dev, "Invalid PCM type %d\n", type); + return -EINVAL; + } + + for (i = 0; audio_idx[type][i] >= 0; i++) { +#ifndef CONFIG_SND_DYNAMIC_MINORS + if (audio_idx[type][i] >= 8) + break; +#endif + if (!test_and_set_bit(audio_idx[type][i], bus->pcm_dev_bits)) + return audio_idx[type][i]; + } + +#ifdef CONFIG_SND_DYNAMIC_MINORS + /* non-fixed slots starting from 10 */ + for (i = 10; i < 32; i++) { + if (!test_and_set_bit(i, bus->pcm_dev_bits)) + return i; + } +#endif + + dev_warn(bus->card->dev, "Too many %s devices\n", + snd_hda_pcm_type_name[type]); +#ifndef CONFIG_SND_DYNAMIC_MINORS + dev_warn(bus->card->dev, + "Consider building the kernel with CONFIG_SND_DYNAMIC_MINORS=y\n"); +#endif + return -EAGAIN; +} + +/* call build_pcms ops of the given codec and set up the default parameters */ +int snd_hda_codec_parse_pcms(struct hda_codec *codec) +{ + struct hda_pcm *cpcm; + int err; + + if (!list_empty(&codec->pcm_list_head)) + return 0; /* already parsed */ + + if (!codec->patch_ops.build_pcms) + return 0; + + err = codec->patch_ops.build_pcms(codec); + if (err < 0) { + codec_err(codec, "cannot build PCMs for #%d (error %d)\n", + codec->core.addr, err); + return err; + } + + list_for_each_entry(cpcm, &codec->pcm_list_head, list) { + int stream; + + for (stream = 0; stream < 2; stream++) { + struct hda_pcm_stream *info = &cpcm->stream[stream]; + + if (!info->substreams) + continue; + err = set_pcm_default_values(codec, info); + if (err < 0) { + codec_warn(codec, + "fail to setup default for PCM %s\n", + cpcm->name); + return err; + } + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_codec_parse_pcms); + +/* assign all PCMs of the given codec */ +int snd_hda_codec_build_pcms(struct hda_codec *codec) +{ + struct hda_bus *bus = codec->bus; + struct hda_pcm *cpcm; + int dev, err; + + err = snd_hda_codec_parse_pcms(codec); + if (err < 0) + return err; + + /* attach a new PCM streams */ + list_for_each_entry(cpcm, &codec->pcm_list_head, list) { + if (cpcm->pcm) + continue; /* already attached */ + if (!cpcm->stream[0].substreams && !cpcm->stream[1].substreams) + continue; /* no substreams assigned */ + + dev = get_empty_pcm_device(bus, cpcm->pcm_type); + if (dev < 0) { + cpcm->device = SNDRV_PCM_INVALID_DEVICE; + continue; /* no fatal error */ + } + cpcm->device = dev; + err = snd_hda_attach_pcm_stream(bus, codec, cpcm); + if (err < 0) { + codec_err(codec, + "cannot attach PCM stream %d for codec #%d\n", + dev, codec->core.addr); + continue; /* no fatal error */ + } + } + + return 0; +} + +/** + * snd_hda_add_new_ctls - create controls from the array + * @codec: the HDA codec + * @knew: the array of struct snd_kcontrol_new + * + * This helper function creates and add new controls in the given array. + * The array must be terminated with an empty entry as terminator. + * + * Returns 0 if successful, or a negative error code. + */ +int snd_hda_add_new_ctls(struct hda_codec *codec, + const struct snd_kcontrol_new *knew) +{ + int err; + + for (; knew->name; knew++) { + struct snd_kcontrol *kctl; + int addr = 0, idx = 0; + if (knew->iface == (__force snd_ctl_elem_iface_t)-1) + continue; /* skip this codec private value */ + for (;;) { + kctl = snd_ctl_new1(knew, codec); + if (!kctl) + return -ENOMEM; + /* Do not use the id.device field for MIXER elements. + * This field is for real device numbers (like PCM) but codecs + * are hidden components from the user space view (unrelated + * to the mixer element identification). + */ + if (addr > 0 && codec->ctl_dev_id) + kctl->id.device = addr; + if (idx > 0) + kctl->id.index = idx; + err = snd_hda_ctl_add(codec, 0, kctl); + if (!err) + break; + /* try first with another device index corresponding to + * the codec addr; if it still fails (or it's the + * primary codec), then try another control index + */ + if (!addr && codec->core.addr) { + addr = codec->core.addr; + if (!codec->ctl_dev_id) + idx += 10 * addr; + } else if (!idx && !knew->index) { + idx = find_empty_mixer_ctl_idx(codec, + knew->name, 0); + if (idx <= 0) + return err; + } else + return err; + } + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_add_new_ctls); + +#ifdef CONFIG_PM +/** + * snd_hda_codec_set_power_save - Configure codec's runtime PM + * @codec: codec device to configure + * @delay: autosuspend delay + */ +void snd_hda_codec_set_power_save(struct hda_codec *codec, int delay) +{ + struct device *dev = hda_codec_dev(codec); + + if (delay == 0 && codec->auto_runtime_pm) + delay = 3000; + + if (delay > 0) { + pm_runtime_set_autosuspend_delay(dev, delay); + pm_runtime_use_autosuspend(dev); + pm_runtime_allow(dev); + if (!pm_runtime_suspended(dev)) + pm_runtime_mark_last_busy(dev); + } else { + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_forbid(dev); + } +} +EXPORT_SYMBOL_GPL(snd_hda_codec_set_power_save); + +/** + * snd_hda_set_power_save - reprogram autosuspend for the given delay + * @bus: HD-audio bus + * @delay: autosuspend delay in msec, 0 = off + * + * Synchronize the runtime PM autosuspend state from the power_save option. + */ +void snd_hda_set_power_save(struct hda_bus *bus, int delay) +{ + struct hda_codec *c; + + list_for_each_codec(c, bus) + snd_hda_codec_set_power_save(c, delay); +} +EXPORT_SYMBOL_GPL(snd_hda_set_power_save); + +/** + * snd_hda_check_amp_list_power - Check the amp list and update the power + * @codec: HD-audio codec + * @check: the object containing an AMP list and the status + * @nid: NID to check / update + * + * Check whether the given NID is in the amp list. If it's in the list, + * check the current AMP status, and update the power-status according + * to the mute status. + * + * This function is supposed to be set or called from the check_power_status + * patch ops. + */ +int snd_hda_check_amp_list_power(struct hda_codec *codec, + struct hda_loopback_check *check, + hda_nid_t nid) +{ + const struct hda_amp_list *p; + int ch, v; + + if (!check->amplist) + return 0; + for (p = check->amplist; p->nid; p++) { + if (p->nid == nid) + break; + } + if (!p->nid) + return 0; /* nothing changed */ + + for (p = check->amplist; p->nid; p++) { + for (ch = 0; ch < 2; ch++) { + v = snd_hda_codec_amp_read(codec, p->nid, ch, p->dir, + p->idx); + if (!(v & HDA_AMP_MUTE) && v > 0) { + if (!check->power_on) { + check->power_on = 1; + snd_hda_power_up_pm(codec); + } + return 1; + } + } + } + if (check->power_on) { + check->power_on = 0; + snd_hda_power_down_pm(codec); + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_check_amp_list_power); +#endif + +/* + * input MUX helper + */ + +/** + * snd_hda_input_mux_info - Info callback helper for the input-mux enum + * @imux: imux helper object + * @uinfo: pointer to get/store the data + */ +int snd_hda_input_mux_info(const struct hda_input_mux *imux, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int index; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = imux->num_items; + if (!imux->num_items) + return 0; + index = uinfo->value.enumerated.item; + if (index >= imux->num_items) + index = imux->num_items - 1; + strcpy(uinfo->value.enumerated.name, imux->items[index].label); + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_input_mux_info); + +/** + * snd_hda_input_mux_put - Put callback helper for the input-mux enum + * @codec: the HDA codec + * @imux: imux helper object + * @ucontrol: pointer to get/store the data + * @nid: input mux NID + * @cur_val: pointer to get/store the current imux value + */ +int snd_hda_input_mux_put(struct hda_codec *codec, + const struct hda_input_mux *imux, + struct snd_ctl_elem_value *ucontrol, + hda_nid_t nid, + unsigned int *cur_val) +{ + unsigned int idx; + + if (!imux->num_items) + return 0; + idx = ucontrol->value.enumerated.item[0]; + if (idx >= imux->num_items) + idx = imux->num_items - 1; + if (*cur_val == idx) + return 0; + snd_hda_codec_write_cache(codec, nid, 0, AC_VERB_SET_CONNECT_SEL, + imux->items[idx].index); + *cur_val = idx; + return 1; +} +EXPORT_SYMBOL_GPL(snd_hda_input_mux_put); + + +/** + * snd_hda_enum_helper_info - Helper for simple enum ctls + * @kcontrol: ctl element + * @uinfo: pointer to get/store the data + * @num_items: number of enum items + * @texts: enum item string array + * + * process kcontrol info callback of a simple string enum array + * when @num_items is 0 or @texts is NULL, assume a boolean enum array + */ +int snd_hda_enum_helper_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo, + int num_items, const char * const *texts) +{ + static const char * const texts_default[] = { + "Disabled", "Enabled" + }; + + if (!texts || !num_items) { + num_items = 2; + texts = texts_default; + } + + return snd_ctl_enum_info(uinfo, 1, num_items, texts); +} +EXPORT_SYMBOL_GPL(snd_hda_enum_helper_info); + +/* + * Multi-channel / digital-out PCM helper functions + */ + +/* setup SPDIF output stream */ +static void setup_dig_out_stream(struct hda_codec *codec, hda_nid_t nid, + unsigned int stream_tag, unsigned int format) +{ + struct hda_spdif_out *spdif; + unsigned int curr_fmt; + bool reset; + + spdif = snd_hda_spdif_out_of_nid(codec, nid); + /* Add sanity check to pass klockwork check. + * This should never happen. + */ + if (WARN_ON(spdif == NULL)) + return; + + curr_fmt = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_STREAM_FORMAT, 0); + reset = codec->spdif_status_reset && + (spdif->ctls & AC_DIG1_ENABLE) && + curr_fmt != format; + + /* turn off SPDIF if needed; otherwise the IEC958 bits won't be + updated */ + if (reset) + set_dig_out_convert(codec, nid, + spdif->ctls & ~AC_DIG1_ENABLE & 0xff, + -1); + snd_hda_codec_setup_stream(codec, nid, stream_tag, 0, format); + if (codec->follower_dig_outs) { + const hda_nid_t *d; + for (d = codec->follower_dig_outs; *d; d++) + snd_hda_codec_setup_stream(codec, *d, stream_tag, 0, + format); + } + /* turn on again (if needed) */ + if (reset) + set_dig_out_convert(codec, nid, + spdif->ctls & 0xff, -1); +} + +static void cleanup_dig_out_stream(struct hda_codec *codec, hda_nid_t nid) +{ + snd_hda_codec_cleanup_stream(codec, nid); + if (codec->follower_dig_outs) { + const hda_nid_t *d; + for (d = codec->follower_dig_outs; *d; d++) + snd_hda_codec_cleanup_stream(codec, *d); + } +} + +/** + * snd_hda_multi_out_dig_open - open the digital out in the exclusive mode + * @codec: the HDA codec + * @mout: hda_multi_out object + */ +int snd_hda_multi_out_dig_open(struct hda_codec *codec, + struct hda_multi_out *mout) +{ + mutex_lock(&codec->spdif_mutex); + if (mout->dig_out_used == HDA_DIG_ANALOG_DUP) + /* already opened as analog dup; reset it once */ + cleanup_dig_out_stream(codec, mout->dig_out_nid); + mout->dig_out_used = HDA_DIG_EXCLUSIVE; + mutex_unlock(&codec->spdif_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_multi_out_dig_open); + +/** + * snd_hda_multi_out_dig_prepare - prepare the digital out stream + * @codec: the HDA codec + * @mout: hda_multi_out object + * @stream_tag: stream tag to assign + * @format: format id to assign + * @substream: PCM substream to assign + */ +int snd_hda_multi_out_dig_prepare(struct hda_codec *codec, + struct hda_multi_out *mout, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + mutex_lock(&codec->spdif_mutex); + setup_dig_out_stream(codec, mout->dig_out_nid, stream_tag, format); + mutex_unlock(&codec->spdif_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_multi_out_dig_prepare); + +/** + * snd_hda_multi_out_dig_cleanup - clean-up the digital out stream + * @codec: the HDA codec + * @mout: hda_multi_out object + */ +int snd_hda_multi_out_dig_cleanup(struct hda_codec *codec, + struct hda_multi_out *mout) +{ + mutex_lock(&codec->spdif_mutex); + cleanup_dig_out_stream(codec, mout->dig_out_nid); + mutex_unlock(&codec->spdif_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_multi_out_dig_cleanup); + +/** + * snd_hda_multi_out_dig_close - release the digital out stream + * @codec: the HDA codec + * @mout: hda_multi_out object + */ +int snd_hda_multi_out_dig_close(struct hda_codec *codec, + struct hda_multi_out *mout) +{ + mutex_lock(&codec->spdif_mutex); + mout->dig_out_used = 0; + mutex_unlock(&codec->spdif_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_multi_out_dig_close); + +/** + * snd_hda_multi_out_analog_open - open analog outputs + * @codec: the HDA codec + * @mout: hda_multi_out object + * @substream: PCM substream to assign + * @hinfo: PCM information to assign + * + * Open analog outputs and set up the hw-constraints. + * If the digital outputs can be opened as follower, open the digital + * outputs, too. + */ +int snd_hda_multi_out_analog_open(struct hda_codec *codec, + struct hda_multi_out *mout, + struct snd_pcm_substream *substream, + struct hda_pcm_stream *hinfo) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + runtime->hw.channels_max = mout->max_channels; + if (mout->dig_out_nid) { + if (!mout->analog_rates) { + mout->analog_rates = hinfo->rates; + mout->analog_formats = hinfo->formats; + mout->analog_maxbps = hinfo->maxbps; + } else { + runtime->hw.rates = mout->analog_rates; + runtime->hw.formats = mout->analog_formats; + hinfo->maxbps = mout->analog_maxbps; + } + if (!mout->spdif_rates) { + snd_hda_query_supported_pcm(codec, mout->dig_out_nid, + &mout->spdif_rates, + &mout->spdif_formats, + &mout->spdif_maxbps); + } + mutex_lock(&codec->spdif_mutex); + if (mout->share_spdif) { + if ((runtime->hw.rates & mout->spdif_rates) && + (runtime->hw.formats & mout->spdif_formats)) { + runtime->hw.rates &= mout->spdif_rates; + runtime->hw.formats &= mout->spdif_formats; + if (mout->spdif_maxbps < hinfo->maxbps) + hinfo->maxbps = mout->spdif_maxbps; + } else { + mout->share_spdif = 0; + /* FIXME: need notify? */ + } + } + mutex_unlock(&codec->spdif_mutex); + } + return snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, 2); +} +EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_open); + +/** + * snd_hda_multi_out_analog_prepare - Preapre the analog outputs. + * @codec: the HDA codec + * @mout: hda_multi_out object + * @stream_tag: stream tag to assign + * @format: format id to assign + * @substream: PCM substream to assign + * + * Set up the i/o for analog out. + * When the digital out is available, copy the front out to digital out, too. + */ +int snd_hda_multi_out_analog_prepare(struct hda_codec *codec, + struct hda_multi_out *mout, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + const hda_nid_t *nids = mout->dac_nids; + int chs = substream->runtime->channels; + struct hda_spdif_out *spdif; + int i; + + mutex_lock(&codec->spdif_mutex); + spdif = snd_hda_spdif_out_of_nid(codec, mout->dig_out_nid); + if (mout->dig_out_nid && mout->share_spdif && + mout->dig_out_used != HDA_DIG_EXCLUSIVE) { + if (chs == 2 && spdif != NULL && + snd_hda_is_supported_format(codec, mout->dig_out_nid, + format) && + !(spdif->status & IEC958_AES0_NONAUDIO)) { + mout->dig_out_used = HDA_DIG_ANALOG_DUP; + setup_dig_out_stream(codec, mout->dig_out_nid, + stream_tag, format); + } else { + mout->dig_out_used = 0; + cleanup_dig_out_stream(codec, mout->dig_out_nid); + } + } + mutex_unlock(&codec->spdif_mutex); + + /* front */ + snd_hda_codec_setup_stream(codec, nids[HDA_FRONT], stream_tag, + 0, format); + if (!mout->no_share_stream && + mout->hp_nid && mout->hp_nid != nids[HDA_FRONT]) + /* headphone out will just decode front left/right (stereo) */ + snd_hda_codec_setup_stream(codec, mout->hp_nid, stream_tag, + 0, format); + /* extra outputs copied from front */ + for (i = 0; i < ARRAY_SIZE(mout->hp_out_nid); i++) + if (!mout->no_share_stream && mout->hp_out_nid[i]) + snd_hda_codec_setup_stream(codec, + mout->hp_out_nid[i], + stream_tag, 0, format); + + /* surrounds */ + for (i = 1; i < mout->num_dacs; i++) { + if (chs >= (i + 1) * 2) /* independent out */ + snd_hda_codec_setup_stream(codec, nids[i], stream_tag, + i * 2, format); + else if (!mout->no_share_stream) /* copy front */ + snd_hda_codec_setup_stream(codec, nids[i], stream_tag, + 0, format); + } + + /* extra surrounds */ + for (i = 0; i < ARRAY_SIZE(mout->extra_out_nid); i++) { + int ch = 0; + if (!mout->extra_out_nid[i]) + break; + if (chs >= (i + 1) * 2) + ch = i * 2; + else if (!mout->no_share_stream) + break; + snd_hda_codec_setup_stream(codec, mout->extra_out_nid[i], + stream_tag, ch, format); + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_prepare); + +/** + * snd_hda_multi_out_analog_cleanup - clean up the setting for analog out + * @codec: the HDA codec + * @mout: hda_multi_out object + */ +int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec, + struct hda_multi_out *mout) +{ + const hda_nid_t *nids = mout->dac_nids; + int i; + + for (i = 0; i < mout->num_dacs; i++) + snd_hda_codec_cleanup_stream(codec, nids[i]); + if (mout->hp_nid) + snd_hda_codec_cleanup_stream(codec, mout->hp_nid); + for (i = 0; i < ARRAY_SIZE(mout->hp_out_nid); i++) + if (mout->hp_out_nid[i]) + snd_hda_codec_cleanup_stream(codec, + mout->hp_out_nid[i]); + for (i = 0; i < ARRAY_SIZE(mout->extra_out_nid); i++) + if (mout->extra_out_nid[i]) + snd_hda_codec_cleanup_stream(codec, + mout->extra_out_nid[i]); + mutex_lock(&codec->spdif_mutex); + if (mout->dig_out_nid && mout->dig_out_used == HDA_DIG_ANALOG_DUP) { + cleanup_dig_out_stream(codec, mout->dig_out_nid); + mout->dig_out_used = 0; + } + mutex_unlock(&codec->spdif_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_cleanup); + +/** + * snd_hda_get_default_vref - Get the default (mic) VREF pin bits + * @codec: the HDA codec + * @pin: referred pin NID + * + * Guess the suitable VREF pin bits to be set as the pin-control value. + * Note: the function doesn't set the AC_PINCTL_IN_EN bit. + */ +unsigned int snd_hda_get_default_vref(struct hda_codec *codec, hda_nid_t pin) +{ + unsigned int pincap; + unsigned int oldval; + oldval = snd_hda_codec_read(codec, pin, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + pincap = snd_hda_query_pin_caps(codec, pin); + pincap = (pincap & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT; + /* Exception: if the default pin setup is vref50, we give it priority */ + if ((pincap & AC_PINCAP_VREF_80) && oldval != PIN_VREF50) + return AC_PINCTL_VREF_80; + else if (pincap & AC_PINCAP_VREF_50) + return AC_PINCTL_VREF_50; + else if (pincap & AC_PINCAP_VREF_100) + return AC_PINCTL_VREF_100; + else if (pincap & AC_PINCAP_VREF_GRD) + return AC_PINCTL_VREF_GRD; + return AC_PINCTL_VREF_HIZ; +} +EXPORT_SYMBOL_GPL(snd_hda_get_default_vref); + +/** + * snd_hda_correct_pin_ctl - correct the pin ctl value for matching with the pin cap + * @codec: the HDA codec + * @pin: referred pin NID + * @val: pin ctl value to audit + */ +unsigned int snd_hda_correct_pin_ctl(struct hda_codec *codec, + hda_nid_t pin, unsigned int val) +{ + static const unsigned int cap_lists[][2] = { + { AC_PINCTL_VREF_100, AC_PINCAP_VREF_100 }, + { AC_PINCTL_VREF_80, AC_PINCAP_VREF_80 }, + { AC_PINCTL_VREF_50, AC_PINCAP_VREF_50 }, + { AC_PINCTL_VREF_GRD, AC_PINCAP_VREF_GRD }, + }; + unsigned int cap; + + if (!val) + return 0; + cap = snd_hda_query_pin_caps(codec, pin); + if (!cap) + return val; /* don't know what to do... */ + + if (val & AC_PINCTL_OUT_EN) { + if (!(cap & AC_PINCAP_OUT)) + val &= ~(AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN); + else if ((val & AC_PINCTL_HP_EN) && !(cap & AC_PINCAP_HP_DRV)) + val &= ~AC_PINCTL_HP_EN; + } + + if (val & AC_PINCTL_IN_EN) { + if (!(cap & AC_PINCAP_IN)) + val &= ~(AC_PINCTL_IN_EN | AC_PINCTL_VREFEN); + else { + unsigned int vcap, vref; + int i; + vcap = (cap & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT; + vref = val & AC_PINCTL_VREFEN; + for (i = 0; i < ARRAY_SIZE(cap_lists); i++) { + if (vref == cap_lists[i][0] && + !(vcap & cap_lists[i][1])) { + if (i == ARRAY_SIZE(cap_lists) - 1) + vref = AC_PINCTL_VREF_HIZ; + else + vref = cap_lists[i + 1][0]; + } + } + val &= ~AC_PINCTL_VREFEN; + val |= vref; + } + } + + return val; +} +EXPORT_SYMBOL_GPL(snd_hda_correct_pin_ctl); + +/** + * _snd_hda_set_pin_ctl - Helper to set pin ctl value + * @codec: the HDA codec + * @pin: referred pin NID + * @val: pin control value to set + * @cached: access over codec pinctl cache or direct write + * + * This function is a helper to set a pin ctl value more safely. + * It corrects the pin ctl value via snd_hda_correct_pin_ctl(), stores the + * value in pin target array via snd_hda_codec_set_pin_target(), then + * actually writes the value via either snd_hda_codec_write_cache() or + * snd_hda_codec_write() depending on @cached flag. + */ +int _snd_hda_set_pin_ctl(struct hda_codec *codec, hda_nid_t pin, + unsigned int val, bool cached) +{ + val = snd_hda_correct_pin_ctl(codec, pin, val); + snd_hda_codec_set_pin_target(codec, pin, val); + if (cached) + return snd_hda_codec_write_cache(codec, pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, val); + else + return snd_hda_codec_write(codec, pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, val); +} +EXPORT_SYMBOL_GPL(_snd_hda_set_pin_ctl); + +/** + * snd_hda_add_imux_item - Add an item to input_mux + * @codec: the HDA codec + * @imux: imux helper object + * @label: the name of imux item to assign + * @index: index number of imux item to assign + * @type_idx: pointer to store the resultant label index + * + * When the same label is used already in the existing items, the number + * suffix is appended to the label. This label index number is stored + * to type_idx when non-NULL pointer is given. + */ +int snd_hda_add_imux_item(struct hda_codec *codec, + struct hda_input_mux *imux, const char *label, + int index, int *type_idx) +{ + int i, label_idx = 0; + if (imux->num_items >= HDA_MAX_NUM_INPUTS) { + codec_err(codec, "hda_codec: Too many imux items!\n"); + return -EINVAL; + } + for (i = 0; i < imux->num_items; i++) { + if (!strncmp(label, imux->items[i].label, strlen(label))) + label_idx++; + } + if (type_idx) + *type_idx = label_idx; + if (label_idx > 0) + snprintf(imux->items[imux->num_items].label, + sizeof(imux->items[imux->num_items].label), + "%s %d", label, label_idx); + else + strscpy(imux->items[imux->num_items].label, label, + sizeof(imux->items[imux->num_items].label)); + imux->items[imux->num_items].index = index; + imux->num_items++; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_add_imux_item); + +/** + * snd_hda_bus_reset_codecs - Reset the bus + * @bus: HD-audio bus + */ +void snd_hda_bus_reset_codecs(struct hda_bus *bus) +{ + struct hda_codec *codec; + + list_for_each_codec(codec, bus) { + /* FIXME: maybe a better way needed for forced reset */ + if (current_work() != &codec->jackpoll_work.work) + cancel_delayed_work_sync(&codec->jackpoll_work); +#ifdef CONFIG_PM + if (hda_codec_is_power_on(codec)) { + hda_call_codec_suspend(codec); + hda_call_codec_resume(codec); + } +#endif + } +} + +/** + * snd_print_pcm_bits - Print the supported PCM fmt bits to the string buffer + * @pcm: PCM caps bits + * @buf: the string buffer to write + * @buflen: the max buffer length + * + * used by hda_proc.c and hda_eld.c + */ +void snd_print_pcm_bits(int pcm, char *buf, int buflen) +{ + static const unsigned int bits[] = { 8, 16, 20, 24, 32 }; + int i, j; + + for (i = 0, j = 0; i < ARRAY_SIZE(bits); i++) + if (pcm & (AC_SUPPCM_BITS_8 << i)) + j += scnprintf(buf + j, buflen - j, " %d", bits[i]); + + buf[j] = '\0'; /* necessary when j == 0 */ +} +EXPORT_SYMBOL_GPL(snd_print_pcm_bits); + +MODULE_DESCRIPTION("HDA codec core"); +MODULE_LICENSE("GPL"); diff --git a/sound/pci/hda/hda_component.h b/sound/pci/hda/hda_component.h new file mode 100644 index 000000000..534e845b9 --- /dev/null +++ b/sound/pci/hda/hda_component.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * HD audio Component Binding Interface + * + * Copyright (C) 2021 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#include <linux/component.h> + +#define HDA_MAX_COMPONENTS 4 +#define HDA_MAX_NAME_SIZE 50 + +struct hda_component { + struct device *dev; + char name[HDA_MAX_NAME_SIZE]; + struct hda_codec *codec; + void (*playback_hook)(struct device *dev, int action); +}; diff --git a/sound/pci/hda/hda_controller.c b/sound/pci/hda/hda_controller.c new file mode 100644 index 000000000..083df287c --- /dev/null +++ b/sound/pci/hda/hda_controller.c @@ -0,0 +1,1326 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Implementation of primary alsa driver code base for Intel HD Audio. + * + * Copyright(c) 2004 Intel Corporation. All rights reserved. + * + * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de> + * PeiSen Hou <pshou@realtek.com.tw> + */ + +#include <linux/clocksource.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> + +#ifdef CONFIG_X86 +/* for art-tsc conversion */ +#include <asm/tsc.h> +#endif + +#include <sound/core.h> +#include <sound/initval.h> +#include "hda_controller.h" +#include "hda_local.h" + +#define CREATE_TRACE_POINTS +#include "hda_controller_trace.h" + +/* DSP lock helpers */ +#define dsp_lock(dev) snd_hdac_dsp_lock(azx_stream(dev)) +#define dsp_unlock(dev) snd_hdac_dsp_unlock(azx_stream(dev)) +#define dsp_is_locked(dev) snd_hdac_stream_is_locked(azx_stream(dev)) + +/* assign a stream for the PCM */ +static inline struct azx_dev * +azx_assign_device(struct azx *chip, struct snd_pcm_substream *substream) +{ + struct hdac_stream *s; + + s = snd_hdac_stream_assign(azx_bus(chip), substream); + if (!s) + return NULL; + return stream_to_azx_dev(s); +} + +/* release the assigned stream */ +static inline void azx_release_device(struct azx_dev *azx_dev) +{ + snd_hdac_stream_release(azx_stream(azx_dev)); +} + +static inline struct hda_pcm_stream * +to_hda_pcm_stream(struct snd_pcm_substream *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + return &apcm->info->stream[substream->stream]; +} + +static u64 azx_adjust_codec_delay(struct snd_pcm_substream *substream, + u64 nsec) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream); + u64 codec_frames, codec_nsecs; + + if (!hinfo->ops.get_delay) + return nsec; + + codec_frames = hinfo->ops.get_delay(hinfo, apcm->codec, substream); + codec_nsecs = div_u64(codec_frames * 1000000000LL, + substream->runtime->rate); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return nsec + codec_nsecs; + + return (nsec > codec_nsecs) ? nsec - codec_nsecs : 0; +} + +/* + * PCM ops + */ + +static int azx_pcm_close(struct snd_pcm_substream *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream); + struct azx *chip = apcm->chip; + struct azx_dev *azx_dev = get_azx_dev(substream); + + trace_azx_pcm_close(chip, azx_dev); + mutex_lock(&chip->open_mutex); + azx_release_device(azx_dev); + if (hinfo->ops.close) + hinfo->ops.close(hinfo, apcm->codec, substream); + snd_hda_power_down(apcm->codec); + mutex_unlock(&chip->open_mutex); + snd_hda_codec_pcm_put(apcm->info); + return 0; +} + +static int azx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct azx *chip = apcm->chip; + struct azx_dev *azx_dev = get_azx_dev(substream); + int ret = 0; + + trace_azx_pcm_hw_params(chip, azx_dev); + dsp_lock(azx_dev); + if (dsp_is_locked(azx_dev)) { + ret = -EBUSY; + goto unlock; + } + + azx_dev->core.bufsize = 0; + azx_dev->core.period_bytes = 0; + azx_dev->core.format_val = 0; + +unlock: + dsp_unlock(azx_dev); + return ret; +} + +static int azx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct azx_dev *azx_dev = get_azx_dev(substream); + struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream); + + /* reset BDL address */ + dsp_lock(azx_dev); + if (!dsp_is_locked(azx_dev)) + snd_hdac_stream_cleanup(azx_stream(azx_dev)); + + snd_hda_codec_cleanup(apcm->codec, hinfo, substream); + + azx_stream(azx_dev)->prepared = 0; + dsp_unlock(azx_dev); + return 0; +} + +static int azx_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct azx *chip = apcm->chip; + struct azx_dev *azx_dev = get_azx_dev(substream); + struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int format_val, stream_tag; + int err; + struct hda_spdif_out *spdif = + snd_hda_spdif_out_of_nid(apcm->codec, hinfo->nid); + unsigned short ctls = spdif ? spdif->ctls : 0; + + trace_azx_pcm_prepare(chip, azx_dev); + dsp_lock(azx_dev); + if (dsp_is_locked(azx_dev)) { + err = -EBUSY; + goto unlock; + } + + snd_hdac_stream_reset(azx_stream(azx_dev)); + format_val = snd_hdac_calc_stream_format(runtime->rate, + runtime->channels, + runtime->format, + hinfo->maxbps, + ctls); + if (!format_val) { + dev_err(chip->card->dev, + "invalid format_val, rate=%d, ch=%d, format=%d\n", + runtime->rate, runtime->channels, runtime->format); + err = -EINVAL; + goto unlock; + } + + err = snd_hdac_stream_set_params(azx_stream(azx_dev), format_val); + if (err < 0) + goto unlock; + + snd_hdac_stream_setup(azx_stream(azx_dev)); + + stream_tag = azx_dev->core.stream_tag; + /* CA-IBG chips need the playback stream starting from 1 */ + if ((chip->driver_caps & AZX_DCAPS_CTX_WORKAROUND) && + stream_tag > chip->capture_streams) + stream_tag -= chip->capture_streams; + err = snd_hda_codec_prepare(apcm->codec, hinfo, stream_tag, + azx_dev->core.format_val, substream); + + unlock: + if (!err) + azx_stream(azx_dev)->prepared = 1; + dsp_unlock(azx_dev); + return err; +} + +static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct azx *chip = apcm->chip; + struct hdac_bus *bus = azx_bus(chip); + struct azx_dev *azx_dev; + struct snd_pcm_substream *s; + struct hdac_stream *hstr; + bool start; + int sbits = 0; + int sync_reg; + + azx_dev = get_azx_dev(substream); + trace_azx_pcm_trigger(chip, azx_dev, cmd); + + hstr = azx_stream(azx_dev); + if (chip->driver_caps & AZX_DCAPS_OLD_SSYNC) + sync_reg = AZX_REG_OLD_SSYNC; + else + sync_reg = AZX_REG_SSYNC; + + if (dsp_is_locked(azx_dev) || !hstr->prepared) + return -EPIPE; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + start = true; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + start = false; + break; + default: + return -EINVAL; + } + + snd_pcm_group_for_each_entry(s, substream) { + if (s->pcm->card != substream->pcm->card) + continue; + azx_dev = get_azx_dev(s); + sbits |= 1 << azx_dev->core.index; + snd_pcm_trigger_done(s, substream); + } + + spin_lock(&bus->reg_lock); + + /* first, set SYNC bits of corresponding streams */ + snd_hdac_stream_sync_trigger(hstr, true, sbits, sync_reg); + + snd_pcm_group_for_each_entry(s, substream) { + if (s->pcm->card != substream->pcm->card) + continue; + azx_dev = get_azx_dev(s); + if (start) { + azx_dev->insufficient = 1; + snd_hdac_stream_start(azx_stream(azx_dev), true); + } else { + snd_hdac_stream_stop(azx_stream(azx_dev)); + } + } + spin_unlock(&bus->reg_lock); + + snd_hdac_stream_sync(hstr, start, sbits); + + spin_lock(&bus->reg_lock); + /* reset SYNC bits */ + snd_hdac_stream_sync_trigger(hstr, false, sbits, sync_reg); + if (start) + snd_hdac_stream_timecounter_init(hstr, sbits); + spin_unlock(&bus->reg_lock); + return 0; +} + +unsigned int azx_get_pos_lpib(struct azx *chip, struct azx_dev *azx_dev) +{ + return snd_hdac_stream_get_pos_lpib(azx_stream(azx_dev)); +} +EXPORT_SYMBOL_GPL(azx_get_pos_lpib); + +unsigned int azx_get_pos_posbuf(struct azx *chip, struct azx_dev *azx_dev) +{ + return snd_hdac_stream_get_pos_posbuf(azx_stream(azx_dev)); +} +EXPORT_SYMBOL_GPL(azx_get_pos_posbuf); + +unsigned int azx_get_position(struct azx *chip, + struct azx_dev *azx_dev) +{ + struct snd_pcm_substream *substream = azx_dev->core.substream; + unsigned int pos; + int stream = substream->stream; + int delay = 0; + + if (chip->get_position[stream]) + pos = chip->get_position[stream](chip, azx_dev); + else /* use the position buffer as default */ + pos = azx_get_pos_posbuf(chip, azx_dev); + + if (pos >= azx_dev->core.bufsize) + pos = 0; + + if (substream->runtime) { + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream); + + if (chip->get_delay[stream]) + delay += chip->get_delay[stream](chip, azx_dev, pos); + if (hinfo->ops.get_delay) + delay += hinfo->ops.get_delay(hinfo, apcm->codec, + substream); + substream->runtime->delay = delay; + } + + trace_azx_get_position(chip, azx_dev, pos, delay); + return pos; +} +EXPORT_SYMBOL_GPL(azx_get_position); + +static snd_pcm_uframes_t azx_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct azx *chip = apcm->chip; + struct azx_dev *azx_dev = get_azx_dev(substream); + return bytes_to_frames(substream->runtime, + azx_get_position(chip, azx_dev)); +} + +/* + * azx_scale64: Scale base by mult/div while not overflowing sanely + * + * Derived from scale64_check_overflow in kernel/time/timekeeping.c + * + * The tmestamps for a 48Khz stream can overflow after (2^64/10^9)/48K which + * is about 384307 ie ~4.5 days. + * + * This scales the calculation so that overflow will happen but after 2^64 / + * 48000 secs, which is pretty large! + * + * In caln below: + * base may overflow, but since there isn’t any additional division + * performed on base it’s OK + * rem can’t overflow because both are 32-bit values + */ + +#ifdef CONFIG_X86 +static u64 azx_scale64(u64 base, u32 num, u32 den) +{ + u64 rem; + + rem = do_div(base, den); + + base *= num; + rem *= num; + + do_div(rem, den); + + return base + rem; +} + +static int azx_get_sync_time(ktime_t *device, + struct system_counterval_t *system, void *ctx) +{ + struct snd_pcm_substream *substream = ctx; + struct azx_dev *azx_dev = get_azx_dev(substream); + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct azx *chip = apcm->chip; + struct snd_pcm_runtime *runtime; + u64 ll_counter, ll_counter_l, ll_counter_h; + u64 tsc_counter, tsc_counter_l, tsc_counter_h; + u32 wallclk_ctr, wallclk_cycles; + bool direction; + u32 dma_select; + u32 timeout; + u32 retry_count = 0; + + runtime = substream->runtime; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + direction = 1; + else + direction = 0; + + /* 0th stream tag is not used, so DMA ch 0 is for 1st stream tag */ + do { + timeout = 100; + dma_select = (direction << GTSCC_CDMAS_DMA_DIR_SHIFT) | + (azx_dev->core.stream_tag - 1); + snd_hdac_chip_writel(azx_bus(chip), GTSCC, dma_select); + + /* Enable the capture */ + snd_hdac_chip_updatel(azx_bus(chip), GTSCC, 0, GTSCC_TSCCI_MASK); + + while (timeout) { + if (snd_hdac_chip_readl(azx_bus(chip), GTSCC) & + GTSCC_TSCCD_MASK) + break; + + timeout--; + } + + if (!timeout) { + dev_err(chip->card->dev, "GTSCC capture Timedout!\n"); + return -EIO; + } + + /* Read wall clock counter */ + wallclk_ctr = snd_hdac_chip_readl(azx_bus(chip), WALFCC); + + /* Read TSC counter */ + tsc_counter_l = snd_hdac_chip_readl(azx_bus(chip), TSCCL); + tsc_counter_h = snd_hdac_chip_readl(azx_bus(chip), TSCCU); + + /* Read Link counter */ + ll_counter_l = snd_hdac_chip_readl(azx_bus(chip), LLPCL); + ll_counter_h = snd_hdac_chip_readl(azx_bus(chip), LLPCU); + + /* Ack: registers read done */ + snd_hdac_chip_writel(azx_bus(chip), GTSCC, GTSCC_TSCCD_SHIFT); + + tsc_counter = (tsc_counter_h << TSCCU_CCU_SHIFT) | + tsc_counter_l; + + ll_counter = (ll_counter_h << LLPC_CCU_SHIFT) | ll_counter_l; + wallclk_cycles = wallclk_ctr & WALFCC_CIF_MASK; + + /* + * An error occurs near frame "rollover". The clocks in + * frame value indicates whether this error may have + * occurred. Here we use the value of 10 i.e., + * HDA_MAX_CYCLE_OFFSET + */ + if (wallclk_cycles < HDA_MAX_CYCLE_VALUE - HDA_MAX_CYCLE_OFFSET + && wallclk_cycles > HDA_MAX_CYCLE_OFFSET) + break; + + /* + * Sleep before we read again, else we may again get + * value near to MAX_CYCLE. Try to sleep for different + * amount of time so we dont hit the same number again + */ + udelay(retry_count++); + + } while (retry_count != HDA_MAX_CYCLE_READ_RETRY); + + if (retry_count == HDA_MAX_CYCLE_READ_RETRY) { + dev_err_ratelimited(chip->card->dev, + "Error in WALFCC cycle count\n"); + return -EIO; + } + + *device = ns_to_ktime(azx_scale64(ll_counter, + NSEC_PER_SEC, runtime->rate)); + *device = ktime_add_ns(*device, (wallclk_cycles * NSEC_PER_SEC) / + ((HDA_MAX_CYCLE_VALUE + 1) * runtime->rate)); + + *system = convert_art_to_tsc(tsc_counter); + + return 0; +} + +#else +static int azx_get_sync_time(ktime_t *device, + struct system_counterval_t *system, void *ctx) +{ + return -ENXIO; +} +#endif + +static int azx_get_crosststamp(struct snd_pcm_substream *substream, + struct system_device_crosststamp *xtstamp) +{ + return get_device_system_crosststamp(azx_get_sync_time, + substream, NULL, xtstamp); +} + +static inline bool is_link_time_supported(struct snd_pcm_runtime *runtime, + struct snd_pcm_audio_tstamp_config *ts) +{ + if (runtime->hw.info & SNDRV_PCM_INFO_HAS_LINK_SYNCHRONIZED_ATIME) + if (ts->type_requested == SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK_SYNCHRONIZED) + return true; + + return false; +} + +static int azx_get_time_info(struct snd_pcm_substream *substream, + struct timespec64 *system_ts, struct timespec64 *audio_ts, + struct snd_pcm_audio_tstamp_config *audio_tstamp_config, + struct snd_pcm_audio_tstamp_report *audio_tstamp_report) +{ + struct azx_dev *azx_dev = get_azx_dev(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct system_device_crosststamp xtstamp; + int ret; + u64 nsec; + + if ((substream->runtime->hw.info & SNDRV_PCM_INFO_HAS_LINK_ATIME) && + (audio_tstamp_config->type_requested == SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK)) { + + snd_pcm_gettime(substream->runtime, system_ts); + + nsec = timecounter_read(&azx_dev->core.tc); + if (audio_tstamp_config->report_delay) + nsec = azx_adjust_codec_delay(substream, nsec); + + *audio_ts = ns_to_timespec64(nsec); + + audio_tstamp_report->actual_type = SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK; + audio_tstamp_report->accuracy_report = 1; /* rest of structure is valid */ + audio_tstamp_report->accuracy = 42; /* 24 MHz WallClock == 42ns resolution */ + + } else if (is_link_time_supported(runtime, audio_tstamp_config)) { + + ret = azx_get_crosststamp(substream, &xtstamp); + if (ret) + return ret; + + switch (runtime->tstamp_type) { + case SNDRV_PCM_TSTAMP_TYPE_MONOTONIC: + return -EINVAL; + + case SNDRV_PCM_TSTAMP_TYPE_MONOTONIC_RAW: + *system_ts = ktime_to_timespec64(xtstamp.sys_monoraw); + break; + + default: + *system_ts = ktime_to_timespec64(xtstamp.sys_realtime); + break; + + } + + *audio_ts = ktime_to_timespec64(xtstamp.device); + + audio_tstamp_report->actual_type = + SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK_SYNCHRONIZED; + audio_tstamp_report->accuracy_report = 1; + /* 24 MHz WallClock == 42ns resolution */ + audio_tstamp_report->accuracy = 42; + + } else { + audio_tstamp_report->actual_type = SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT; + } + + return 0; +} + +static const struct snd_pcm_hardware azx_pcm_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + /* No full-resume yet implemented */ + /* SNDRV_PCM_INFO_RESUME |*/ + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_HAS_WALL_CLOCK | /* legacy */ + SNDRV_PCM_INFO_HAS_LINK_ATIME | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = AZX_MAX_BUF_SIZE, + .period_bytes_min = 128, + .period_bytes_max = AZX_MAX_BUF_SIZE / 2, + .periods_min = 2, + .periods_max = AZX_MAX_FRAG, + .fifo_size = 0, +}; + +static int azx_pcm_open(struct snd_pcm_substream *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream); + struct azx *chip = apcm->chip; + struct azx_dev *azx_dev; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + int buff_step; + + snd_hda_codec_pcm_get(apcm->info); + mutex_lock(&chip->open_mutex); + azx_dev = azx_assign_device(chip, substream); + trace_azx_pcm_open(chip, azx_dev); + if (azx_dev == NULL) { + err = -EBUSY; + goto unlock; + } + runtime->private_data = azx_dev; + + runtime->hw = azx_pcm_hw; + if (chip->gts_present) + runtime->hw.info |= SNDRV_PCM_INFO_HAS_LINK_SYNCHRONIZED_ATIME; + runtime->hw.channels_min = hinfo->channels_min; + runtime->hw.channels_max = hinfo->channels_max; + runtime->hw.formats = hinfo->formats; + runtime->hw.rates = hinfo->rates; + snd_pcm_limit_hw_rates(runtime); + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + + /* avoid wrap-around with wall-clock */ + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_TIME, + 20, + 178000000); + + if (chip->align_buffer_size) + /* constrain buffer sizes to be multiple of 128 + bytes. This is more efficient in terms of memory + access but isn't required by the HDA spec and + prevents users from specifying exact period/buffer + sizes. For example for 44.1kHz, a period size set + to 20ms will be rounded to 19.59ms. */ + buff_step = 128; + else + /* Don't enforce steps on buffer sizes, still need to + be multiple of 4 bytes (HDA spec). Tested on Intel + HDA controllers, may not work on all devices where + option needs to be disabled */ + buff_step = 4; + + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + buff_step); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + buff_step); + snd_hda_power_up(apcm->codec); + if (hinfo->ops.open) + err = hinfo->ops.open(hinfo, apcm->codec, substream); + else + err = -ENODEV; + if (err < 0) { + azx_release_device(azx_dev); + goto powerdown; + } + snd_pcm_limit_hw_rates(runtime); + /* sanity check */ + if (snd_BUG_ON(!runtime->hw.channels_min) || + snd_BUG_ON(!runtime->hw.channels_max) || + snd_BUG_ON(!runtime->hw.formats) || + snd_BUG_ON(!runtime->hw.rates)) { + azx_release_device(azx_dev); + if (hinfo->ops.close) + hinfo->ops.close(hinfo, apcm->codec, substream); + err = -EINVAL; + goto powerdown; + } + + /* disable LINK_ATIME timestamps for capture streams + until we figure out how to handle digital inputs */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.info &= ~SNDRV_PCM_INFO_HAS_WALL_CLOCK; /* legacy */ + runtime->hw.info &= ~SNDRV_PCM_INFO_HAS_LINK_ATIME; + } + + snd_pcm_set_sync(substream); + mutex_unlock(&chip->open_mutex); + return 0; + + powerdown: + snd_hda_power_down(apcm->codec); + unlock: + mutex_unlock(&chip->open_mutex); + snd_hda_codec_pcm_put(apcm->info); + return err; +} + +static const struct snd_pcm_ops azx_pcm_ops = { + .open = azx_pcm_open, + .close = azx_pcm_close, + .hw_params = azx_pcm_hw_params, + .hw_free = azx_pcm_hw_free, + .prepare = azx_pcm_prepare, + .trigger = azx_pcm_trigger, + .pointer = azx_pcm_pointer, + .get_time_info = azx_get_time_info, +}; + +static void azx_pcm_free(struct snd_pcm *pcm) +{ + struct azx_pcm *apcm = pcm->private_data; + if (apcm) { + list_del(&apcm->list); + apcm->info->pcm = NULL; + kfree(apcm); + } +} + +#define MAX_PREALLOC_SIZE (32 * 1024 * 1024) + +int snd_hda_attach_pcm_stream(struct hda_bus *_bus, struct hda_codec *codec, + struct hda_pcm *cpcm) +{ + struct hdac_bus *bus = &_bus->core; + struct azx *chip = bus_to_azx(bus); + struct snd_pcm *pcm; + struct azx_pcm *apcm; + int pcm_dev = cpcm->device; + unsigned int size; + int s, err; + int type = SNDRV_DMA_TYPE_DEV_SG; + + list_for_each_entry(apcm, &chip->pcm_list, list) { + if (apcm->pcm->device == pcm_dev) { + dev_err(chip->card->dev, "PCM %d already exists\n", + pcm_dev); + return -EBUSY; + } + } + err = snd_pcm_new(chip->card, cpcm->name, pcm_dev, + cpcm->stream[SNDRV_PCM_STREAM_PLAYBACK].substreams, + cpcm->stream[SNDRV_PCM_STREAM_CAPTURE].substreams, + &pcm); + if (err < 0) + return err; + strscpy(pcm->name, cpcm->name, sizeof(pcm->name)); + apcm = kzalloc(sizeof(*apcm), GFP_KERNEL); + if (apcm == NULL) { + snd_device_free(chip->card, pcm); + return -ENOMEM; + } + apcm->chip = chip; + apcm->pcm = pcm; + apcm->codec = codec; + apcm->info = cpcm; + pcm->private_data = apcm; + pcm->private_free = azx_pcm_free; + if (cpcm->pcm_type == HDA_PCM_TYPE_MODEM) + pcm->dev_class = SNDRV_PCM_CLASS_MODEM; + list_add_tail(&apcm->list, &chip->pcm_list); + cpcm->pcm = pcm; + for (s = 0; s < 2; s++) { + if (cpcm->stream[s].substreams) + snd_pcm_set_ops(pcm, s, &azx_pcm_ops); + } + /* buffer pre-allocation */ + size = CONFIG_SND_HDA_PREALLOC_SIZE * 1024; + if (size > MAX_PREALLOC_SIZE) + size = MAX_PREALLOC_SIZE; + if (chip->uc_buffer) + type = SNDRV_DMA_TYPE_DEV_WC_SG; + snd_pcm_set_managed_buffer_all(pcm, type, chip->card->dev, + size, MAX_PREALLOC_SIZE); + return 0; +} + +static unsigned int azx_command_addr(u32 cmd) +{ + unsigned int addr = cmd >> 28; + + if (addr >= AZX_MAX_CODECS) { + snd_BUG(); + addr = 0; + } + + return addr; +} + +/* receive a response */ +static int azx_rirb_get_response(struct hdac_bus *bus, unsigned int addr, + unsigned int *res) +{ + struct azx *chip = bus_to_azx(bus); + struct hda_bus *hbus = &chip->bus; + int err; + + again: + err = snd_hdac_bus_get_response(bus, addr, res); + if (!err) + return 0; + + if (hbus->no_response_fallback) + return -EIO; + + if (!bus->polling_mode) { + dev_warn(chip->card->dev, + "azx_get_response timeout, switching to polling mode: last cmd=0x%08x\n", + bus->last_cmd[addr]); + bus->polling_mode = 1; + goto again; + } + + if (chip->msi) { + dev_warn(chip->card->dev, + "No response from codec, disabling MSI: last cmd=0x%08x\n", + bus->last_cmd[addr]); + if (chip->ops->disable_msi_reset_irq && + chip->ops->disable_msi_reset_irq(chip) < 0) + return -EIO; + goto again; + } + + if (chip->probing) { + /* If this critical timeout happens during the codec probing + * phase, this is likely an access to a non-existing codec + * slot. Better to return an error and reset the system. + */ + return -EIO; + } + + /* no fallback mechanism? */ + if (!chip->fallback_to_single_cmd) + return -EIO; + + /* a fatal communication error; need either to reset or to fallback + * to the single_cmd mode + */ + if (hbus->allow_bus_reset && !hbus->response_reset && !hbus->in_reset) { + hbus->response_reset = 1; + dev_err(chip->card->dev, + "No response from codec, resetting bus: last cmd=0x%08x\n", + bus->last_cmd[addr]); + return -EAGAIN; /* give a chance to retry */ + } + + dev_err(chip->card->dev, + "azx_get_response timeout, switching to single_cmd mode: last cmd=0x%08x\n", + bus->last_cmd[addr]); + chip->single_cmd = 1; + hbus->response_reset = 0; + snd_hdac_bus_stop_cmd_io(bus); + return -EIO; +} + +/* + * Use the single immediate command instead of CORB/RIRB for simplicity + * + * Note: according to Intel, this is not preferred use. The command was + * intended for the BIOS only, and may get confused with unsolicited + * responses. So, we shouldn't use it for normal operation from the + * driver. + * I left the codes, however, for debugging/testing purposes. + */ + +/* receive a response */ +static int azx_single_wait_for_response(struct azx *chip, unsigned int addr) +{ + int timeout = 50; + + while (timeout--) { + /* check IRV busy bit */ + if (azx_readw(chip, IRS) & AZX_IRS_VALID) { + /* reuse rirb.res as the response return value */ + azx_bus(chip)->rirb.res[addr] = azx_readl(chip, IR); + return 0; + } + udelay(1); + } + if (printk_ratelimit()) + dev_dbg(chip->card->dev, "get_response timeout: IRS=0x%x\n", + azx_readw(chip, IRS)); + azx_bus(chip)->rirb.res[addr] = -1; + return -EIO; +} + +/* send a command */ +static int azx_single_send_cmd(struct hdac_bus *bus, u32 val) +{ + struct azx *chip = bus_to_azx(bus); + unsigned int addr = azx_command_addr(val); + int timeout = 50; + + bus->last_cmd[azx_command_addr(val)] = val; + while (timeout--) { + /* check ICB busy bit */ + if (!((azx_readw(chip, IRS) & AZX_IRS_BUSY))) { + /* Clear IRV valid bit */ + azx_writew(chip, IRS, azx_readw(chip, IRS) | + AZX_IRS_VALID); + azx_writel(chip, IC, val); + azx_writew(chip, IRS, azx_readw(chip, IRS) | + AZX_IRS_BUSY); + return azx_single_wait_for_response(chip, addr); + } + udelay(1); + } + if (printk_ratelimit()) + dev_dbg(chip->card->dev, + "send_cmd timeout: IRS=0x%x, val=0x%x\n", + azx_readw(chip, IRS), val); + return -EIO; +} + +/* receive a response */ +static int azx_single_get_response(struct hdac_bus *bus, unsigned int addr, + unsigned int *res) +{ + if (res) + *res = bus->rirb.res[addr]; + return 0; +} + +/* + * The below are the main callbacks from hda_codec. + * + * They are just the skeleton to call sub-callbacks according to the + * current setting of chip->single_cmd. + */ + +/* send a command */ +static int azx_send_cmd(struct hdac_bus *bus, unsigned int val) +{ + struct azx *chip = bus_to_azx(bus); + + if (chip->disabled) + return 0; + if (chip->single_cmd) + return azx_single_send_cmd(bus, val); + else + return snd_hdac_bus_send_cmd(bus, val); +} + +/* get a response */ +static int azx_get_response(struct hdac_bus *bus, unsigned int addr, + unsigned int *res) +{ + struct azx *chip = bus_to_azx(bus); + + if (chip->disabled) + return 0; + if (chip->single_cmd) + return azx_single_get_response(bus, addr, res); + else + return azx_rirb_get_response(bus, addr, res); +} + +static const struct hdac_bus_ops bus_core_ops = { + .command = azx_send_cmd, + .get_response = azx_get_response, +}; + +#ifdef CONFIG_SND_HDA_DSP_LOADER +/* + * DSP loading code (e.g. for CA0132) + */ + +/* use the first stream for loading DSP */ +static struct azx_dev * +azx_get_dsp_loader_dev(struct azx *chip) +{ + struct hdac_bus *bus = azx_bus(chip); + struct hdac_stream *s; + + list_for_each_entry(s, &bus->stream_list, list) + if (s->index == chip->playback_index_offset) + return stream_to_azx_dev(s); + + return NULL; +} + +int snd_hda_codec_load_dsp_prepare(struct hda_codec *codec, unsigned int format, + unsigned int byte_size, + struct snd_dma_buffer *bufp) +{ + struct hdac_bus *bus = &codec->bus->core; + struct azx *chip = bus_to_azx(bus); + struct azx_dev *azx_dev; + struct hdac_stream *hstr; + bool saved = false; + int err; + + azx_dev = azx_get_dsp_loader_dev(chip); + hstr = azx_stream(azx_dev); + spin_lock_irq(&bus->reg_lock); + if (hstr->opened) { + chip->saved_azx_dev = *azx_dev; + saved = true; + } + spin_unlock_irq(&bus->reg_lock); + + err = snd_hdac_dsp_prepare(hstr, format, byte_size, bufp); + if (err < 0) { + spin_lock_irq(&bus->reg_lock); + if (saved) + *azx_dev = chip->saved_azx_dev; + spin_unlock_irq(&bus->reg_lock); + return err; + } + + hstr->prepared = 0; + return err; +} +EXPORT_SYMBOL_GPL(snd_hda_codec_load_dsp_prepare); + +void snd_hda_codec_load_dsp_trigger(struct hda_codec *codec, bool start) +{ + struct hdac_bus *bus = &codec->bus->core; + struct azx *chip = bus_to_azx(bus); + struct azx_dev *azx_dev = azx_get_dsp_loader_dev(chip); + + snd_hdac_dsp_trigger(azx_stream(azx_dev), start); +} +EXPORT_SYMBOL_GPL(snd_hda_codec_load_dsp_trigger); + +void snd_hda_codec_load_dsp_cleanup(struct hda_codec *codec, + struct snd_dma_buffer *dmab) +{ + struct hdac_bus *bus = &codec->bus->core; + struct azx *chip = bus_to_azx(bus); + struct azx_dev *azx_dev = azx_get_dsp_loader_dev(chip); + struct hdac_stream *hstr = azx_stream(azx_dev); + + if (!dmab->area || !hstr->locked) + return; + + snd_hdac_dsp_cleanup(hstr, dmab); + spin_lock_irq(&bus->reg_lock); + if (hstr->opened) + *azx_dev = chip->saved_azx_dev; + hstr->locked = false; + spin_unlock_irq(&bus->reg_lock); +} +EXPORT_SYMBOL_GPL(snd_hda_codec_load_dsp_cleanup); +#endif /* CONFIG_SND_HDA_DSP_LOADER */ + +/* + * reset and start the controller registers + */ +void azx_init_chip(struct azx *chip, bool full_reset) +{ + if (snd_hdac_bus_init_chip(azx_bus(chip), full_reset)) { + /* correct RINTCNT for CXT */ + if (chip->driver_caps & AZX_DCAPS_CTX_WORKAROUND) + azx_writew(chip, RINTCNT, 0xc0); + } +} +EXPORT_SYMBOL_GPL(azx_init_chip); + +void azx_stop_all_streams(struct azx *chip) +{ + struct hdac_bus *bus = azx_bus(chip); + + snd_hdac_stop_streams(bus); +} +EXPORT_SYMBOL_GPL(azx_stop_all_streams); + +void azx_stop_chip(struct azx *chip) +{ + snd_hdac_bus_stop_chip(azx_bus(chip)); +} +EXPORT_SYMBOL_GPL(azx_stop_chip); + +/* + * interrupt handler + */ +static void stream_update(struct hdac_bus *bus, struct hdac_stream *s) +{ + struct azx *chip = bus_to_azx(bus); + struct azx_dev *azx_dev = stream_to_azx_dev(s); + + /* check whether this IRQ is really acceptable */ + if (!chip->ops->position_check || + chip->ops->position_check(chip, azx_dev)) { + spin_unlock(&bus->reg_lock); + snd_pcm_period_elapsed(azx_stream(azx_dev)->substream); + spin_lock(&bus->reg_lock); + } +} + +irqreturn_t azx_interrupt(int irq, void *dev_id) +{ + struct azx *chip = dev_id; + struct hdac_bus *bus = azx_bus(chip); + u32 status; + bool active, handled = false; + int repeat = 0; /* count for avoiding endless loop */ + +#ifdef CONFIG_PM + if (azx_has_pm_runtime(chip)) + if (!pm_runtime_active(chip->card->dev)) + return IRQ_NONE; +#endif + + spin_lock(&bus->reg_lock); + + if (chip->disabled) + goto unlock; + + do { + status = azx_readl(chip, INTSTS); + if (status == 0 || status == 0xffffffff) + break; + + handled = true; + active = false; + if (snd_hdac_bus_handle_stream_irq(bus, status, stream_update)) + active = true; + + status = azx_readb(chip, RIRBSTS); + if (status & RIRB_INT_MASK) { + /* + * Clearing the interrupt status here ensures that no + * interrupt gets masked after the RIRB wp is read in + * snd_hdac_bus_update_rirb. This avoids a possible + * race condition where codec response in RIRB may + * remain unserviced by IRQ, eventually falling back + * to polling mode in azx_rirb_get_response. + */ + azx_writeb(chip, RIRBSTS, RIRB_INT_MASK); + active = true; + if (status & RIRB_INT_RESPONSE) { + if (chip->driver_caps & AZX_DCAPS_CTX_WORKAROUND) + udelay(80); + snd_hdac_bus_update_rirb(bus); + } + } + } while (active && ++repeat < 10); + + unlock: + spin_unlock(&bus->reg_lock); + + return IRQ_RETVAL(handled); +} +EXPORT_SYMBOL_GPL(azx_interrupt); + +/* + * Codec initerface + */ + +/* + * Probe the given codec address + */ +static int probe_codec(struct azx *chip, int addr) +{ + unsigned int cmd = (addr << 28) | (AC_NODE_ROOT << 20) | + (AC_VERB_PARAMETERS << 8) | AC_PAR_VENDOR_ID; + struct hdac_bus *bus = azx_bus(chip); + int err; + unsigned int res = -1; + + mutex_lock(&bus->cmd_mutex); + chip->probing = 1; + azx_send_cmd(bus, cmd); + err = azx_get_response(bus, addr, &res); + chip->probing = 0; + mutex_unlock(&bus->cmd_mutex); + if (err < 0 || res == -1) + return -EIO; + dev_dbg(chip->card->dev, "codec #%d probed OK\n", addr); + return 0; +} + +void snd_hda_bus_reset(struct hda_bus *bus) +{ + struct azx *chip = bus_to_azx(&bus->core); + + bus->in_reset = 1; + azx_stop_chip(chip); + azx_init_chip(chip, true); + if (bus->core.chip_init) + snd_hda_bus_reset_codecs(bus); + bus->in_reset = 0; +} + +/* HD-audio bus initialization */ +int azx_bus_init(struct azx *chip, const char *model) +{ + struct hda_bus *bus = &chip->bus; + int err; + + err = snd_hdac_bus_init(&bus->core, chip->card->dev, &bus_core_ops); + if (err < 0) + return err; + + bus->card = chip->card; + mutex_init(&bus->prepare_mutex); + bus->pci = chip->pci; + bus->modelname = model; + bus->mixer_assigned = -1; + bus->core.snoop = azx_snoop(chip); + if (chip->get_position[0] != azx_get_pos_lpib || + chip->get_position[1] != azx_get_pos_lpib) + bus->core.use_posbuf = true; + bus->core.bdl_pos_adj = chip->bdl_pos_adj; + if (chip->driver_caps & AZX_DCAPS_CORBRP_SELF_CLEAR) + bus->core.corbrp_self_clear = true; + + if (chip->driver_caps & AZX_DCAPS_4K_BDLE_BOUNDARY) + bus->core.align_bdle_4k = true; + + /* enable sync_write flag for stable communication as default */ + bus->core.sync_write = 1; + + return 0; +} +EXPORT_SYMBOL_GPL(azx_bus_init); + +/* Probe codecs */ +int azx_probe_codecs(struct azx *chip, unsigned int max_slots) +{ + struct hdac_bus *bus = azx_bus(chip); + int c, codecs, err; + + codecs = 0; + if (!max_slots) + max_slots = AZX_DEFAULT_CODECS; + + /* First try to probe all given codec slots */ + for (c = 0; c < max_slots; c++) { + if ((bus->codec_mask & (1 << c)) & chip->codec_probe_mask) { + if (probe_codec(chip, c) < 0) { + /* Some BIOSen give you wrong codec addresses + * that don't exist + */ + dev_warn(chip->card->dev, + "Codec #%d probe error; disabling it...\n", c); + bus->codec_mask &= ~(1 << c); + /* More badly, accessing to a non-existing + * codec often screws up the controller chip, + * and disturbs the further communications. + * Thus if an error occurs during probing, + * better to reset the controller chip to + * get back to the sanity state. + */ + azx_stop_chip(chip); + azx_init_chip(chip, true); + } + } + } + + /* Then create codec instances */ + for (c = 0; c < max_slots; c++) { + if ((bus->codec_mask & (1 << c)) & chip->codec_probe_mask) { + struct hda_codec *codec; + err = snd_hda_codec_new(&chip->bus, chip->card, c, &codec); + if (err < 0) + continue; + codec->jackpoll_interval = chip->jackpoll_interval; + codec->beep_mode = chip->beep_mode; + codec->ctl_dev_id = chip->ctl_dev_id; + codecs++; + } + } + if (!codecs) { + dev_err(chip->card->dev, "no codecs initialized\n"); + return -ENXIO; + } + return 0; +} +EXPORT_SYMBOL_GPL(azx_probe_codecs); + +/* configure each codec instance */ +int azx_codec_configure(struct azx *chip) +{ + struct hda_codec *codec, *next; + int success = 0; + + list_for_each_codec(codec, &chip->bus) { + if (!snd_hda_codec_configure(codec)) + success++; + } + + if (success) { + /* unregister failed codecs if any codec has been probed */ + list_for_each_codec_safe(codec, next, &chip->bus) { + if (!codec->configured) { + codec_err(codec, "Unable to configure, disabling\n"); + snd_hdac_device_unregister(&codec->core); + } + } + } + + return success ? 0 : -ENODEV; +} +EXPORT_SYMBOL_GPL(azx_codec_configure); + +static int stream_direction(struct azx *chip, unsigned char index) +{ + if (index >= chip->capture_index_offset && + index < chip->capture_index_offset + chip->capture_streams) + return SNDRV_PCM_STREAM_CAPTURE; + return SNDRV_PCM_STREAM_PLAYBACK; +} + +/* initialize SD streams */ +int azx_init_streams(struct azx *chip) +{ + int i; + int stream_tags[2] = { 0, 0 }; + + /* initialize each stream (aka device) + * assign the starting bdl address to each stream (device) + * and initialize + */ + for (i = 0; i < chip->num_streams; i++) { + struct azx_dev *azx_dev = kzalloc(sizeof(*azx_dev), GFP_KERNEL); + int dir, tag; + + if (!azx_dev) + return -ENOMEM; + + dir = stream_direction(chip, i); + /* stream tag must be unique throughout + * the stream direction group, + * valid values 1...15 + * use separate stream tag if the flag + * AZX_DCAPS_SEPARATE_STREAM_TAG is used + */ + if (chip->driver_caps & AZX_DCAPS_SEPARATE_STREAM_TAG) + tag = ++stream_tags[dir]; + else + tag = i + 1; + snd_hdac_stream_init(azx_bus(chip), azx_stream(azx_dev), + i, dir, tag); + } + + return 0; +} +EXPORT_SYMBOL_GPL(azx_init_streams); + +void azx_free_streams(struct azx *chip) +{ + struct hdac_bus *bus = azx_bus(chip); + struct hdac_stream *s; + + while (!list_empty(&bus->stream_list)) { + s = list_first_entry(&bus->stream_list, struct hdac_stream, list); + list_del(&s->list); + kfree(stream_to_azx_dev(s)); + } +} +EXPORT_SYMBOL_GPL(azx_free_streams); diff --git a/sound/pci/hda/hda_controller.h b/sound/pci/hda/hda_controller.h new file mode 100644 index 000000000..8556031bc --- /dev/null +++ b/sound/pci/hda/hda_controller.h @@ -0,0 +1,214 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Common functionality for the alsa driver code base for HD Audio. + */ + +#ifndef __SOUND_HDA_CONTROLLER_H +#define __SOUND_HDA_CONTROLLER_H + +#include <linux/timecounter.h> +#include <linux/interrupt.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/hda_codec.h> +#include <sound/hda_register.h> + +#define AZX_MAX_CODECS HDA_MAX_CODECS +#define AZX_DEFAULT_CODECS 4 + +/* driver quirks (capabilities) */ +/* bits 0-7 are used for indicating driver type */ +#define AZX_DCAPS_NO_TCSEL (1 << 8) /* No Intel TCSEL bit */ +#define AZX_DCAPS_NO_MSI (1 << 9) /* No MSI support */ +#define AZX_DCAPS_SNOOP_MASK (3 << 10) /* snoop type mask */ +#define AZX_DCAPS_SNOOP_OFF (1 << 12) /* snoop default off */ +#ifdef CONFIG_SND_HDA_I915 +#define AZX_DCAPS_I915_COMPONENT (1 << 13) /* bind with i915 gfx */ +#else +#define AZX_DCAPS_I915_COMPONENT 0 /* NOP */ +#endif +/* 14 unused */ +#define AZX_DCAPS_CTX_WORKAROUND (1 << 15) /* X-Fi workaround */ +#define AZX_DCAPS_POSFIX_LPIB (1 << 16) /* Use LPIB as default */ +#define AZX_DCAPS_AMD_WORKAROUND (1 << 17) /* AMD-specific workaround */ +#define AZX_DCAPS_NO_64BIT (1 << 18) /* No 64bit address */ +/* 19 unused */ +#define AZX_DCAPS_OLD_SSYNC (1 << 20) /* Old SSYNC reg for ICH */ +#define AZX_DCAPS_NO_ALIGN_BUFSIZE (1 << 21) /* no buffer size alignment */ +/* 22 unused */ +#define AZX_DCAPS_4K_BDLE_BOUNDARY (1 << 23) /* BDLE in 4k boundary */ +/* 24 unused */ +#define AZX_DCAPS_COUNT_LPIB_DELAY (1 << 25) /* Take LPIB as delay */ +#define AZX_DCAPS_PM_RUNTIME (1 << 26) /* runtime PM support */ +#define AZX_DCAPS_RETRY_PROBE (1 << 27) /* retry probe if no codec is configured */ +#define AZX_DCAPS_CORBRP_SELF_CLEAR (1 << 28) /* CORBRP clears itself after reset */ +#define AZX_DCAPS_NO_MSI64 (1 << 29) /* Stick to 32-bit MSIs */ +#define AZX_DCAPS_SEPARATE_STREAM_TAG (1 << 30) /* capture and playback use separate stream tag */ + +enum { + AZX_SNOOP_TYPE_NONE, + AZX_SNOOP_TYPE_SCH, + AZX_SNOOP_TYPE_ATI, + AZX_SNOOP_TYPE_NVIDIA, +}; + +struct azx_dev { + struct hdac_stream core; + + unsigned int irq_pending:1; + /* + * For VIA: + * A flag to ensure DMA position is 0 + * when link position is not greater than FIFO size + */ + unsigned int insufficient:1; +}; + +#define azx_stream(dev) (&(dev)->core) +#define stream_to_azx_dev(s) container_of(s, struct azx_dev, core) + +struct azx; + +/* Functions to read/write to hda registers. */ +struct hda_controller_ops { + /* Disable msi if supported, PCI only */ + int (*disable_msi_reset_irq)(struct azx *); + /* Check if current position is acceptable */ + int (*position_check)(struct azx *chip, struct azx_dev *azx_dev); + /* enable/disable the link power */ + int (*link_power)(struct azx *chip, bool enable); +}; + +struct azx_pcm { + struct azx *chip; + struct snd_pcm *pcm; + struct hda_codec *codec; + struct hda_pcm *info; + struct list_head list; +}; + +typedef unsigned int (*azx_get_pos_callback_t)(struct azx *, struct azx_dev *); +typedef int (*azx_get_delay_callback_t)(struct azx *, struct azx_dev *, unsigned int pos); + +struct azx { + struct hda_bus bus; + + struct snd_card *card; + struct pci_dev *pci; + int dev_index; + + /* chip type specific */ + int driver_type; + unsigned int driver_caps; + int playback_streams; + int playback_index_offset; + int capture_streams; + int capture_index_offset; + int num_streams; + int jackpoll_interval; /* jack poll interval in jiffies */ + + /* Register interaction. */ + const struct hda_controller_ops *ops; + + /* position adjustment callbacks */ + azx_get_pos_callback_t get_position[2]; + azx_get_delay_callback_t get_delay[2]; + + /* locks */ + struct mutex open_mutex; /* Prevents concurrent open/close operations */ + + /* PCM */ + struct list_head pcm_list; /* azx_pcm list */ + + /* HD codec */ + int codec_probe_mask; /* copied from probe_mask option */ + unsigned int beep_mode; + bool ctl_dev_id; + +#ifdef CONFIG_SND_HDA_PATCH_LOADER + const struct firmware *fw; +#endif + + /* flags */ + int bdl_pos_adj; + unsigned int running:1; + unsigned int fallback_to_single_cmd:1; + unsigned int single_cmd:1; + unsigned int msi:1; + unsigned int probing:1; /* codec probing phase */ + unsigned int snoop:1; + unsigned int uc_buffer:1; /* non-cached pages for stream buffers */ + unsigned int align_buffer_size:1; + unsigned int disabled:1; /* disabled by vga_switcheroo */ + unsigned int pm_prepared:1; + + /* GTS present */ + unsigned int gts_present:1; + +#ifdef CONFIG_SND_HDA_DSP_LOADER + struct azx_dev saved_azx_dev; +#endif +}; + +#define azx_bus(chip) (&(chip)->bus.core) +#define bus_to_azx(_bus) container_of(_bus, struct azx, bus.core) + +static inline bool azx_snoop(struct azx *chip) +{ + return !IS_ENABLED(CONFIG_X86) || chip->snoop; +} + +/* + * macros for easy use + */ + +#define azx_writel(chip, reg, value) \ + snd_hdac_chip_writel(azx_bus(chip), reg, value) +#define azx_readl(chip, reg) \ + snd_hdac_chip_readl(azx_bus(chip), reg) +#define azx_writew(chip, reg, value) \ + snd_hdac_chip_writew(azx_bus(chip), reg, value) +#define azx_readw(chip, reg) \ + snd_hdac_chip_readw(azx_bus(chip), reg) +#define azx_writeb(chip, reg, value) \ + snd_hdac_chip_writeb(azx_bus(chip), reg, value) +#define azx_readb(chip, reg) \ + snd_hdac_chip_readb(azx_bus(chip), reg) + +#define azx_has_pm_runtime(chip) \ + ((chip)->driver_caps & AZX_DCAPS_PM_RUNTIME) + +/* PCM setup */ +static inline struct azx_dev *get_azx_dev(struct snd_pcm_substream *substream) +{ + return substream->runtime->private_data; +} +unsigned int azx_get_position(struct azx *chip, struct azx_dev *azx_dev); +unsigned int azx_get_pos_lpib(struct azx *chip, struct azx_dev *azx_dev); +unsigned int azx_get_pos_posbuf(struct azx *chip, struct azx_dev *azx_dev); + +/* Stream control. */ +void azx_stop_all_streams(struct azx *chip); + +/* Allocation functions. */ +#define azx_alloc_stream_pages(chip) \ + snd_hdac_bus_alloc_stream_pages(azx_bus(chip)) +#define azx_free_stream_pages(chip) \ + snd_hdac_bus_free_stream_pages(azx_bus(chip)) + +/* Low level azx interface */ +void azx_init_chip(struct azx *chip, bool full_reset); +void azx_stop_chip(struct azx *chip); +#define azx_enter_link_reset(chip) \ + snd_hdac_bus_enter_link_reset(azx_bus(chip)) +irqreturn_t azx_interrupt(int irq, void *dev_id); + +/* Codec interface */ +int azx_bus_init(struct azx *chip, const char *model); +int azx_probe_codecs(struct azx *chip, unsigned int max_slots); +int azx_codec_configure(struct azx *chip); +int azx_init_streams(struct azx *chip); +void azx_free_streams(struct azx *chip); + +#endif /* __SOUND_HDA_CONTROLLER_H */ diff --git a/sound/pci/hda/hda_controller_trace.h b/sound/pci/hda/hda_controller_trace.h new file mode 100644 index 000000000..bf48304e2 --- /dev/null +++ b/sound/pci/hda/hda_controller_trace.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM hda_controller +#define TRACE_INCLUDE_FILE hda_controller_trace + +#if !defined(_TRACE_HDA_CONTROLLER_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_HDA_CONTROLLER_H + +#include <linux/tracepoint.h> + +struct azx; +struct azx_dev; + +TRACE_EVENT(azx_pcm_trigger, + + TP_PROTO(struct azx *chip, struct azx_dev *dev, int cmd), + + TP_ARGS(chip, dev, cmd), + + TP_STRUCT__entry( + __field( int, card ) + __field( int, idx ) + __field( int, cmd ) + ), + + TP_fast_assign( + __entry->card = (chip)->card->number; + __entry->idx = (dev)->core.index; + __entry->cmd = cmd; + ), + + TP_printk("[%d:%d] cmd=%d", __entry->card, __entry->idx, __entry->cmd) +); + +TRACE_EVENT(azx_get_position, + + TP_PROTO(struct azx *chip, struct azx_dev *dev, unsigned int pos, unsigned int delay), + + TP_ARGS(chip, dev, pos, delay), + + TP_STRUCT__entry( + __field( int, card ) + __field( int, idx ) + __field( unsigned int, pos ) + __field( unsigned int, delay ) + ), + + TP_fast_assign( + __entry->card = (chip)->card->number; + __entry->idx = (dev)->core.index; + __entry->pos = pos; + __entry->delay = delay; + ), + + TP_printk("[%d:%d] pos=%u, delay=%u", __entry->card, __entry->idx, __entry->pos, __entry->delay) +); + +DECLARE_EVENT_CLASS(azx_pcm, + TP_PROTO(struct azx *chip, struct azx_dev *azx_dev), + + TP_ARGS(chip, azx_dev), + + TP_STRUCT__entry( + __field( unsigned char, stream_tag ) + ), + + TP_fast_assign( + __entry->stream_tag = (azx_dev)->core.stream_tag; + ), + + TP_printk("stream_tag: %d", __entry->stream_tag) +); + +DEFINE_EVENT(azx_pcm, azx_pcm_open, + TP_PROTO(struct azx *chip, struct azx_dev *azx_dev), + TP_ARGS(chip, azx_dev) +); + +DEFINE_EVENT(azx_pcm, azx_pcm_close, + TP_PROTO(struct azx *chip, struct azx_dev *azx_dev), + TP_ARGS(chip, azx_dev) +); + +DEFINE_EVENT(azx_pcm, azx_pcm_hw_params, + TP_PROTO(struct azx *chip, struct azx_dev *azx_dev), + TP_ARGS(chip, azx_dev) +); + +DEFINE_EVENT(azx_pcm, azx_pcm_prepare, + TP_PROTO(struct azx *chip, struct azx_dev *azx_dev), + TP_ARGS(chip, azx_dev) +); + +#endif /* _TRACE_HDA_CONTROLLER_H */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#include <trace/define_trace.h> diff --git a/sound/pci/hda/hda_cs_dsp_ctl.c b/sound/pci/hda/hda_cs_dsp_ctl.c new file mode 100644 index 000000000..1622a22f9 --- /dev/null +++ b/sound/pci/hda/hda_cs_dsp_ctl.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// HDA DSP ALSA Control Driver +// +// Copyright 2022 Cirrus Logic, Inc. +// +// Author: Stefan Binding <sbinding@opensource.cirrus.com> + +#include <linux/module.h> +#include <sound/soc.h> +#include <linux/firmware/cirrus/cs_dsp.h> +#include <linux/firmware/cirrus/wmfw.h> +#include "hda_cs_dsp_ctl.h" + +#define ADSP_MAX_STD_CTRL_SIZE 512 + +struct hda_cs_dsp_coeff_ctl { + struct cs_dsp_coeff_ctl *cs_ctl; + struct snd_card *card; + struct snd_kcontrol *kctl; +}; + +static const char * const hda_cs_dsp_fw_text[HDA_CS_DSP_NUM_FW] = { + [HDA_CS_DSP_FW_SPK_PROT] = "Prot", + [HDA_CS_DSP_FW_SPK_CALI] = "Cali", + [HDA_CS_DSP_FW_SPK_DIAG] = "Diag", + [HDA_CS_DSP_FW_MISC] = "Misc", +}; + +const char * const hda_cs_dsp_fw_ids[HDA_CS_DSP_NUM_FW] = { + [HDA_CS_DSP_FW_SPK_PROT] = "spk-prot", + [HDA_CS_DSP_FW_SPK_CALI] = "spk-cali", + [HDA_CS_DSP_FW_SPK_DIAG] = "spk-diag", + [HDA_CS_DSP_FW_MISC] = "misc", +}; +EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_fw_ids, SND_HDA_CS_DSP_CONTROLS); + +static int hda_cs_dsp_coeff_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) +{ + struct hda_cs_dsp_coeff_ctl *ctl = (struct hda_cs_dsp_coeff_ctl *)snd_kcontrol_chip(kctl); + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = cs_ctl->len; + + return 0; +} + +static int hda_cs_dsp_coeff_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct hda_cs_dsp_coeff_ctl *ctl = (struct hda_cs_dsp_coeff_ctl *)snd_kcontrol_chip(kctl); + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; + char *p = ucontrol->value.bytes.data; + int ret = 0; + + mutex_lock(&cs_ctl->dsp->pwr_lock); + ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, p, cs_ctl->len); + mutex_unlock(&cs_ctl->dsp->pwr_lock); + + return ret; +} + +static int hda_cs_dsp_coeff_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct hda_cs_dsp_coeff_ctl *ctl = (struct hda_cs_dsp_coeff_ctl *)snd_kcontrol_chip(kctl); + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; + char *p = ucontrol->value.bytes.data; + int ret; + + mutex_lock(&cs_ctl->dsp->pwr_lock); + ret = cs_dsp_coeff_read_ctrl(cs_ctl, 0, p, cs_ctl->len); + mutex_unlock(&cs_ctl->dsp->pwr_lock); + + return ret; +} + +static unsigned int wmfw_convert_flags(unsigned int in) +{ + unsigned int out, rd, wr, vol; + + rd = SNDRV_CTL_ELEM_ACCESS_READ; + wr = SNDRV_CTL_ELEM_ACCESS_WRITE; + vol = SNDRV_CTL_ELEM_ACCESS_VOLATILE; + + out = 0; + + if (in) { + out |= rd; + if (in & WMFW_CTL_FLAG_WRITEABLE) + out |= wr; + if (in & WMFW_CTL_FLAG_VOLATILE) + out |= vol; + } else { + out |= rd | wr | vol; + } + + return out; +} + +static void hda_cs_dsp_add_kcontrol(struct hda_cs_dsp_coeff_ctl *ctl, const char *name) +{ + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; + struct snd_kcontrol_new kcontrol = {0}; + struct snd_kcontrol *kctl; + int ret = 0; + + if (cs_ctl->len > ADSP_MAX_STD_CTRL_SIZE) { + dev_err(cs_ctl->dsp->dev, "KControl %s: length %zu exceeds maximum %d\n", name, + cs_ctl->len, ADSP_MAX_STD_CTRL_SIZE); + return; + } + + kcontrol.name = name; + kcontrol.info = hda_cs_dsp_coeff_info; + kcontrol.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kcontrol.access = wmfw_convert_flags(cs_ctl->flags); + kcontrol.get = hda_cs_dsp_coeff_get; + kcontrol.put = hda_cs_dsp_coeff_put; + + /* Save ctl inside private_data, ctl is owned by cs_dsp, + * and will be freed when cs_dsp removes the control */ + kctl = snd_ctl_new1(&kcontrol, (void *)ctl); + if (!kctl) + return; + + ret = snd_ctl_add(ctl->card, kctl); + if (ret) { + dev_err(cs_ctl->dsp->dev, "Failed to add KControl %s = %d\n", kcontrol.name, ret); + return; + } + + dev_dbg(cs_ctl->dsp->dev, "Added KControl: %s\n", kcontrol.name); + ctl->kctl = kctl; +} + +static void hda_cs_dsp_control_add(struct cs_dsp_coeff_ctl *cs_ctl, + const struct hda_cs_dsp_ctl_info *info) +{ + struct cs_dsp *cs_dsp = cs_ctl->dsp; + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct hda_cs_dsp_coeff_ctl *ctl; + const char *region_name; + int ret; + + region_name = cs_dsp_mem_region_name(cs_ctl->alg_region.type); + if (!region_name) { + dev_warn(cs_dsp->dev, "Unknown region type: %d\n", cs_ctl->alg_region.type); + return; + } + + ret = scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s %s %.12s %x", info->device_name, + cs_dsp->name, hda_cs_dsp_fw_text[info->fw_type], cs_ctl->alg_region.alg); + + if (cs_ctl->subname) { + int avail = SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret - 2; + int skip = 0; + + /* Truncate the subname from the start if it is too long */ + if (cs_ctl->subname_len > avail) + skip = cs_ctl->subname_len - avail; + + snprintf(name + ret, SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret, + " %.*s", cs_ctl->subname_len - skip, cs_ctl->subname + skip); + } + + ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); + if (!ctl) + return; + + ctl->cs_ctl = cs_ctl; + ctl->card = info->card; + cs_ctl->priv = ctl; + + hda_cs_dsp_add_kcontrol(ctl, name); +} + +void hda_cs_dsp_add_controls(struct cs_dsp *dsp, const struct hda_cs_dsp_ctl_info *info) +{ + struct cs_dsp_coeff_ctl *cs_ctl; + + /* + * pwr_lock would cause mutex inversion with ALSA control lock compared + * to the get/put functions. + * It is safe to walk the list without holding a mutex because entries + * are persistent and only cs_dsp_power_up() or cs_dsp_remove() can + * change the list. + */ + lockdep_assert_not_held(&dsp->pwr_lock); + + list_for_each_entry(cs_ctl, &dsp->ctl_list, list) { + if (cs_ctl->flags & WMFW_CTL_FLAG_SYS) + continue; + + if (cs_ctl->priv) + continue; + + hda_cs_dsp_control_add(cs_ctl, info); + } +} +EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_add_controls, SND_HDA_CS_DSP_CONTROLS); + +void hda_cs_dsp_control_remove(struct cs_dsp_coeff_ctl *cs_ctl) +{ + struct hda_cs_dsp_coeff_ctl *ctl = cs_ctl->priv; + + kfree(ctl); +} +EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_control_remove, SND_HDA_CS_DSP_CONTROLS); + +int hda_cs_dsp_write_ctl(struct cs_dsp *dsp, const char *name, int type, + unsigned int alg, const void *buf, size_t len) +{ + struct cs_dsp_coeff_ctl *cs_ctl; + struct hda_cs_dsp_coeff_ctl *ctl; + int ret; + + mutex_lock(&dsp->pwr_lock); + cs_ctl = cs_dsp_get_ctl(dsp, name, type, alg); + ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, buf, len); + mutex_unlock(&dsp->pwr_lock); + if (ret) + return ret; + + if (cs_ctl->flags & WMFW_CTL_FLAG_SYS) + return 0; + + ctl = cs_ctl->priv; + + snd_ctl_notify(ctl->card, SNDRV_CTL_EVENT_MASK_VALUE, &ctl->kctl->id); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_write_ctl, SND_HDA_CS_DSP_CONTROLS); + +int hda_cs_dsp_read_ctl(struct cs_dsp *dsp, const char *name, int type, + unsigned int alg, void *buf, size_t len) +{ + int ret; + + mutex_lock(&dsp->pwr_lock); + ret = cs_dsp_coeff_read_ctrl(cs_dsp_get_ctl(dsp, name, type, alg), 0, buf, len); + mutex_unlock(&dsp->pwr_lock); + + return ret; + +} +EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_read_ctl, SND_HDA_CS_DSP_CONTROLS); + +MODULE_DESCRIPTION("CS_DSP ALSA Control HDA Library"); +MODULE_AUTHOR("Stefan Binding, <sbinding@opensource.cirrus.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/pci/hda/hda_cs_dsp_ctl.h b/sound/pci/hda/hda_cs_dsp_ctl.h new file mode 100644 index 000000000..2cf93359c --- /dev/null +++ b/sound/pci/hda/hda_cs_dsp_ctl.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * HDA DSP ALSA Control Driver + * + * Copyright 2022 Cirrus Logic, Inc. + * + * Author: Stefan Binding <sbinding@opensource.cirrus.com> + */ + +#ifndef __HDA_CS_DSP_CTL_H__ +#define __HDA_CS_DSP_CTL_H__ + +#include <sound/soc.h> +#include <linux/firmware/cirrus/cs_dsp.h> + +enum hda_cs_dsp_fw_id { + HDA_CS_DSP_FW_SPK_PROT, + HDA_CS_DSP_FW_SPK_CALI, + HDA_CS_DSP_FW_SPK_DIAG, + HDA_CS_DSP_FW_MISC, + HDA_CS_DSP_NUM_FW +}; + +struct hda_cs_dsp_ctl_info { + struct snd_card *card; + enum hda_cs_dsp_fw_id fw_type; + const char *device_name; +}; + +extern const char * const hda_cs_dsp_fw_ids[HDA_CS_DSP_NUM_FW]; + +void hda_cs_dsp_add_controls(struct cs_dsp *dsp, const struct hda_cs_dsp_ctl_info *info); +void hda_cs_dsp_control_remove(struct cs_dsp_coeff_ctl *cs_ctl); +int hda_cs_dsp_write_ctl(struct cs_dsp *dsp, const char *name, int type, + unsigned int alg, const void *buf, size_t len); +int hda_cs_dsp_read_ctl(struct cs_dsp *dsp, const char *name, int type, + unsigned int alg, void *buf, size_t len); + +#endif /*__HDA_CS_DSP_CTL_H__*/ diff --git a/sound/pci/hda/hda_eld.c b/sound/pci/hda/hda_eld.c new file mode 100644 index 000000000..1d108ed5c --- /dev/null +++ b/sound/pci/hda/hda_eld.c @@ -0,0 +1,771 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Generic routines and proc interface for ELD(EDID Like Data) information + * + * Copyright(c) 2008 Intel Corporation. + * Copyright (c) 2013 Anssi Hannula <anssi.hannula@iki.fi> + * + * Authors: + * Wu Fengguang <wfg@linux.intel.com> + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <asm/unaligned.h> +#include <sound/hda_chmap.h> +#include <sound/hda_codec.h> +#include "hda_local.h" + +enum eld_versions { + ELD_VER_CEA_861D = 2, + ELD_VER_PARTIAL = 31, +}; + +enum cea_edid_versions { + CEA_EDID_VER_NONE = 0, + CEA_EDID_VER_CEA861 = 1, + CEA_EDID_VER_CEA861A = 2, + CEA_EDID_VER_CEA861BCD = 3, + CEA_EDID_VER_RESERVED = 4, +}; + +static const char * const eld_connection_type_names[4] = { + "HDMI", + "DisplayPort", + "2-reserved", + "3-reserved" +}; + +enum cea_audio_coding_types { + AUDIO_CODING_TYPE_REF_STREAM_HEADER = 0, + AUDIO_CODING_TYPE_LPCM = 1, + AUDIO_CODING_TYPE_AC3 = 2, + AUDIO_CODING_TYPE_MPEG1 = 3, + AUDIO_CODING_TYPE_MP3 = 4, + AUDIO_CODING_TYPE_MPEG2 = 5, + AUDIO_CODING_TYPE_AACLC = 6, + AUDIO_CODING_TYPE_DTS = 7, + AUDIO_CODING_TYPE_ATRAC = 8, + AUDIO_CODING_TYPE_SACD = 9, + AUDIO_CODING_TYPE_EAC3 = 10, + AUDIO_CODING_TYPE_DTS_HD = 11, + AUDIO_CODING_TYPE_MLP = 12, + AUDIO_CODING_TYPE_DST = 13, + AUDIO_CODING_TYPE_WMAPRO = 14, + AUDIO_CODING_TYPE_REF_CXT = 15, + /* also include valid xtypes below */ + AUDIO_CODING_TYPE_HE_AAC = 15, + AUDIO_CODING_TYPE_HE_AAC2 = 16, + AUDIO_CODING_TYPE_MPEG_SURROUND = 17, +}; + +enum cea_audio_coding_xtypes { + AUDIO_CODING_XTYPE_HE_REF_CT = 0, + AUDIO_CODING_XTYPE_HE_AAC = 1, + AUDIO_CODING_XTYPE_HE_AAC2 = 2, + AUDIO_CODING_XTYPE_MPEG_SURROUND = 3, + AUDIO_CODING_XTYPE_FIRST_RESERVED = 4, +}; + +static const char * const cea_audio_coding_type_names[] = { + /* 0 */ "undefined", + /* 1 */ "LPCM", + /* 2 */ "AC-3", + /* 3 */ "MPEG1", + /* 4 */ "MP3", + /* 5 */ "MPEG2", + /* 6 */ "AAC-LC", + /* 7 */ "DTS", + /* 8 */ "ATRAC", + /* 9 */ "DSD (One Bit Audio)", + /* 10 */ "E-AC-3/DD+ (Dolby Digital Plus)", + /* 11 */ "DTS-HD", + /* 12 */ "MLP (Dolby TrueHD)", + /* 13 */ "DST", + /* 14 */ "WMAPro", + /* 15 */ "HE-AAC", + /* 16 */ "HE-AACv2", + /* 17 */ "MPEG Surround", +}; + +/* + * The following two lists are shared between + * - HDMI audio InfoFrame (source to sink) + * - CEA E-EDID Extension (sink to source) + */ + +/* + * SS1:SS0 index => sample size + */ +static const int cea_sample_sizes[4] = { + 0, /* 0: Refer to Stream Header */ + AC_SUPPCM_BITS_16, /* 1: 16 bits */ + AC_SUPPCM_BITS_20, /* 2: 20 bits */ + AC_SUPPCM_BITS_24, /* 3: 24 bits */ +}; + +/* + * SF2:SF1:SF0 index => sampling frequency + */ +static const int cea_sampling_frequencies[8] = { + 0, /* 0: Refer to Stream Header */ + SNDRV_PCM_RATE_32000, /* 1: 32000Hz */ + SNDRV_PCM_RATE_44100, /* 2: 44100Hz */ + SNDRV_PCM_RATE_48000, /* 3: 48000Hz */ + SNDRV_PCM_RATE_88200, /* 4: 88200Hz */ + SNDRV_PCM_RATE_96000, /* 5: 96000Hz */ + SNDRV_PCM_RATE_176400, /* 6: 176400Hz */ + SNDRV_PCM_RATE_192000, /* 7: 192000Hz */ +}; + +static unsigned int hdmi_get_eld_data(struct hda_codec *codec, hda_nid_t nid, + int byte_index) +{ + unsigned int val; + + val = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_HDMI_ELDD, byte_index); +#ifdef BE_PARANOID + codec_info(codec, "HDMI: ELD data byte %d: 0x%x\n", byte_index, val); +#endif + return val; +} + +#define GRAB_BITS(buf, byte, lowbit, bits) \ +({ \ + BUILD_BUG_ON(lowbit > 7); \ + BUILD_BUG_ON(bits > 8); \ + BUILD_BUG_ON(bits <= 0); \ + \ + (buf[byte] >> (lowbit)) & ((1 << (bits)) - 1); \ +}) + +static void hdmi_update_short_audio_desc(struct hda_codec *codec, + struct cea_sad *a, + const unsigned char *buf) +{ + int i; + int val; + + val = GRAB_BITS(buf, 1, 0, 7); + a->rates = 0; + for (i = 0; i < 7; i++) + if (val & (1 << i)) + a->rates |= cea_sampling_frequencies[i + 1]; + + a->channels = GRAB_BITS(buf, 0, 0, 3); + a->channels++; + + a->sample_bits = 0; + a->max_bitrate = 0; + + a->format = GRAB_BITS(buf, 0, 3, 4); + switch (a->format) { + case AUDIO_CODING_TYPE_REF_STREAM_HEADER: + codec_info(codec, "HDMI: audio coding type 0 not expected\n"); + break; + + case AUDIO_CODING_TYPE_LPCM: + val = GRAB_BITS(buf, 2, 0, 3); + for (i = 0; i < 3; i++) + if (val & (1 << i)) + a->sample_bits |= cea_sample_sizes[i + 1]; + break; + + case AUDIO_CODING_TYPE_AC3: + case AUDIO_CODING_TYPE_MPEG1: + case AUDIO_CODING_TYPE_MP3: + case AUDIO_CODING_TYPE_MPEG2: + case AUDIO_CODING_TYPE_AACLC: + case AUDIO_CODING_TYPE_DTS: + case AUDIO_CODING_TYPE_ATRAC: + a->max_bitrate = GRAB_BITS(buf, 2, 0, 8); + a->max_bitrate *= 8000; + break; + + case AUDIO_CODING_TYPE_SACD: + break; + + case AUDIO_CODING_TYPE_EAC3: + break; + + case AUDIO_CODING_TYPE_DTS_HD: + break; + + case AUDIO_CODING_TYPE_MLP: + break; + + case AUDIO_CODING_TYPE_DST: + break; + + case AUDIO_CODING_TYPE_WMAPRO: + a->profile = GRAB_BITS(buf, 2, 0, 3); + break; + + case AUDIO_CODING_TYPE_REF_CXT: + a->format = GRAB_BITS(buf, 2, 3, 5); + if (a->format == AUDIO_CODING_XTYPE_HE_REF_CT || + a->format >= AUDIO_CODING_XTYPE_FIRST_RESERVED) { + codec_info(codec, + "HDMI: audio coding xtype %d not expected\n", + a->format); + a->format = 0; + } else + a->format += AUDIO_CODING_TYPE_HE_AAC - + AUDIO_CODING_XTYPE_HE_AAC; + break; + } +} + +/* + * Be careful, ELD buf could be totally rubbish! + */ +int snd_hdmi_parse_eld(struct hda_codec *codec, struct parsed_hdmi_eld *e, + const unsigned char *buf, int size) +{ + int mnl; + int i; + + memset(e, 0, sizeof(*e)); + e->eld_ver = GRAB_BITS(buf, 0, 3, 5); + if (e->eld_ver != ELD_VER_CEA_861D && + e->eld_ver != ELD_VER_PARTIAL) { + codec_info(codec, "HDMI: Unknown ELD version %d\n", e->eld_ver); + goto out_fail; + } + + e->baseline_len = GRAB_BITS(buf, 2, 0, 8); + mnl = GRAB_BITS(buf, 4, 0, 5); + e->cea_edid_ver = GRAB_BITS(buf, 4, 5, 3); + + e->support_hdcp = GRAB_BITS(buf, 5, 0, 1); + e->support_ai = GRAB_BITS(buf, 5, 1, 1); + e->conn_type = GRAB_BITS(buf, 5, 2, 2); + e->sad_count = GRAB_BITS(buf, 5, 4, 4); + + e->aud_synch_delay = GRAB_BITS(buf, 6, 0, 8) * 2; + e->spk_alloc = GRAB_BITS(buf, 7, 0, 7); + + e->port_id = get_unaligned_le64(buf + 8); + + /* not specified, but the spec's tendency is little endian */ + e->manufacture_id = get_unaligned_le16(buf + 16); + e->product_id = get_unaligned_le16(buf + 18); + + if (mnl > ELD_MAX_MNL) { + codec_info(codec, "HDMI: MNL is reserved value %d\n", mnl); + goto out_fail; + } else if (ELD_FIXED_BYTES + mnl > size) { + codec_info(codec, "HDMI: out of range MNL %d\n", mnl); + goto out_fail; + } else + strscpy(e->monitor_name, buf + ELD_FIXED_BYTES, mnl + 1); + + for (i = 0; i < e->sad_count; i++) { + if (ELD_FIXED_BYTES + mnl + 3 * (i + 1) > size) { + codec_info(codec, "HDMI: out of range SAD %d\n", i); + goto out_fail; + } + hdmi_update_short_audio_desc(codec, e->sad + i, + buf + ELD_FIXED_BYTES + mnl + 3 * i); + } + + /* + * HDMI sink's ELD info cannot always be retrieved for now, e.g. + * in console or for audio devices. Assume the highest speakers + * configuration, to _not_ prohibit multi-channel audio playback. + */ + if (!e->spk_alloc) + e->spk_alloc = 0xffff; + + return 0; + +out_fail: + return -EINVAL; +} + +int snd_hdmi_get_eld_size(struct hda_codec *codec, hda_nid_t nid) +{ + return snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_HDMI_DIP_SIZE, + AC_DIPSIZE_ELD_BUF); +} + +int snd_hdmi_get_eld(struct hda_codec *codec, hda_nid_t nid, + unsigned char *buf, int *eld_size) +{ + int i; + int ret = 0; + int size; + + /* + * ELD size is initialized to zero in caller function. If no errors and + * ELD is valid, actual eld_size is assigned. + */ + + size = snd_hdmi_get_eld_size(codec, nid); + if (size == 0) { + /* wfg: workaround for ASUS P5E-VM HDMI board */ + codec_info(codec, "HDMI: ELD buf size is 0, force 128\n"); + size = 128; + } + if (size < ELD_FIXED_BYTES || size > ELD_MAX_SIZE) { + codec_info(codec, "HDMI: invalid ELD buf size %d\n", size); + return -ERANGE; + } + + /* set ELD buffer */ + for (i = 0; i < size; i++) { + unsigned int val = hdmi_get_eld_data(codec, nid, i); + /* + * Graphics driver might be writing to ELD buffer right now. + * Just abort. The caller will repoll after a while. + */ + if (!(val & AC_ELDD_ELD_VALID)) { + codec_info(codec, "HDMI: invalid ELD data byte %d\n", i); + ret = -EINVAL; + goto error; + } + val &= AC_ELDD_ELD_DATA; + /* + * The first byte cannot be zero. This can happen on some DVI + * connections. Some Intel chips may also need some 250ms delay + * to return non-zero ELD data, even when the graphics driver + * correctly writes ELD content before setting ELD_valid bit. + */ + if (!val && !i) { + codec_dbg(codec, "HDMI: 0 ELD data\n"); + ret = -EINVAL; + goto error; + } + buf[i] = val; + } + + *eld_size = size; +error: + return ret; +} + +/* + * SNDRV_PCM_RATE_* and AC_PAR_PCM values don't match, print correct rates with + * hdmi-specific routine. + */ +static void hdmi_print_pcm_rates(int pcm, char *buf, int buflen) +{ + static const unsigned int alsa_rates[] = { + 5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, + 88200, 96000, 176400, 192000, 384000 + }; + int i, j; + + for (i = 0, j = 0; i < ARRAY_SIZE(alsa_rates); i++) + if (pcm & (1 << i)) + j += scnprintf(buf + j, buflen - j, " %d", + alsa_rates[i]); + + buf[j] = '\0'; /* necessary when j == 0 */ +} + +#define SND_PRINT_RATES_ADVISED_BUFSIZE 80 + +static void hdmi_show_short_audio_desc(struct hda_codec *codec, + struct cea_sad *a) +{ + char buf[SND_PRINT_RATES_ADVISED_BUFSIZE]; + char buf2[8 + SND_PRINT_BITS_ADVISED_BUFSIZE] = ", bits ="; + + if (!a->format) + return; + + hdmi_print_pcm_rates(a->rates, buf, sizeof(buf)); + + if (a->format == AUDIO_CODING_TYPE_LPCM) + snd_print_pcm_bits(a->sample_bits, buf2 + 8, sizeof(buf2) - 8); + else if (a->max_bitrate) + snprintf(buf2, sizeof(buf2), + ", max bitrate = %d", a->max_bitrate); + else + buf2[0] = '\0'; + + codec_dbg(codec, + "HDMI: supports coding type %s: channels = %d, rates =%s%s\n", + cea_audio_coding_type_names[a->format], + a->channels, buf, buf2); +} + +void snd_hdmi_show_eld(struct hda_codec *codec, struct parsed_hdmi_eld *e) +{ + int i; + + codec_dbg(codec, "HDMI: detected monitor %s at connection type %s\n", + e->monitor_name, + eld_connection_type_names[e->conn_type]); + + if (e->spk_alloc) { + char buf[SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE]; + snd_hdac_print_channel_allocation(e->spk_alloc, buf, sizeof(buf)); + codec_dbg(codec, "HDMI: available speakers:%s\n", buf); + } + + for (i = 0; i < e->sad_count; i++) + hdmi_show_short_audio_desc(codec, e->sad + i); +} + +#ifdef CONFIG_SND_PROC_FS + +static void hdmi_print_sad_info(int i, struct cea_sad *a, + struct snd_info_buffer *buffer) +{ + char buf[SND_PRINT_RATES_ADVISED_BUFSIZE]; + + snd_iprintf(buffer, "sad%d_coding_type\t[0x%x] %s\n", + i, a->format, cea_audio_coding_type_names[a->format]); + snd_iprintf(buffer, "sad%d_channels\t\t%d\n", i, a->channels); + + hdmi_print_pcm_rates(a->rates, buf, sizeof(buf)); + snd_iprintf(buffer, "sad%d_rates\t\t[0x%x]%s\n", i, a->rates, buf); + + if (a->format == AUDIO_CODING_TYPE_LPCM) { + snd_print_pcm_bits(a->sample_bits, buf, sizeof(buf)); + snd_iprintf(buffer, "sad%d_bits\t\t[0x%x]%s\n", + i, a->sample_bits, buf); + } + + if (a->max_bitrate) + snd_iprintf(buffer, "sad%d_max_bitrate\t%d\n", + i, a->max_bitrate); + + if (a->profile) + snd_iprintf(buffer, "sad%d_profile\t\t%d\n", i, a->profile); +} + +void snd_hdmi_print_eld_info(struct hdmi_eld *eld, + struct snd_info_buffer *buffer, + hda_nid_t pin_nid, int dev_id, hda_nid_t cvt_nid) +{ + struct parsed_hdmi_eld *e = &eld->info; + char buf[SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE]; + int i; + static const char * const eld_version_names[32] = { + "reserved", + "reserved", + "CEA-861D or below", + [3 ... 30] = "reserved", + [31] = "partial" + }; + static const char * const cea_edid_version_names[8] = { + "no CEA EDID Timing Extension block present", + "CEA-861", + "CEA-861-A", + "CEA-861-B, C or D", + [4 ... 7] = "reserved" + }; + + snd_iprintf(buffer, "monitor_present\t\t%d\n", eld->monitor_present); + snd_iprintf(buffer, "eld_valid\t\t%d\n", eld->eld_valid); + snd_iprintf(buffer, "codec_pin_nid\t\t0x%x\n", pin_nid); + snd_iprintf(buffer, "codec_dev_id\t\t0x%x\n", dev_id); + snd_iprintf(buffer, "codec_cvt_nid\t\t0x%x\n", cvt_nid); + if (!eld->eld_valid) + return; + snd_iprintf(buffer, "monitor_name\t\t%s\n", e->monitor_name); + snd_iprintf(buffer, "connection_type\t\t%s\n", + eld_connection_type_names[e->conn_type]); + snd_iprintf(buffer, "eld_version\t\t[0x%x] %s\n", e->eld_ver, + eld_version_names[e->eld_ver]); + snd_iprintf(buffer, "edid_version\t\t[0x%x] %s\n", e->cea_edid_ver, + cea_edid_version_names[e->cea_edid_ver]); + snd_iprintf(buffer, "manufacture_id\t\t0x%x\n", e->manufacture_id); + snd_iprintf(buffer, "product_id\t\t0x%x\n", e->product_id); + snd_iprintf(buffer, "port_id\t\t\t0x%llx\n", (long long)e->port_id); + snd_iprintf(buffer, "support_hdcp\t\t%d\n", e->support_hdcp); + snd_iprintf(buffer, "support_ai\t\t%d\n", e->support_ai); + snd_iprintf(buffer, "audio_sync_delay\t%d\n", e->aud_synch_delay); + + snd_hdac_print_channel_allocation(e->spk_alloc, buf, sizeof(buf)); + snd_iprintf(buffer, "speakers\t\t[0x%x]%s\n", e->spk_alloc, buf); + + snd_iprintf(buffer, "sad_count\t\t%d\n", e->sad_count); + + for (i = 0; i < e->sad_count; i++) + hdmi_print_sad_info(i, e->sad + i, buffer); +} + +void snd_hdmi_write_eld_info(struct hdmi_eld *eld, + struct snd_info_buffer *buffer) +{ + struct parsed_hdmi_eld *e = &eld->info; + char line[64]; + char name[64]; + char *sname; + long long val; + unsigned int n; + + while (!snd_info_get_line(buffer, line, sizeof(line))) { + if (sscanf(line, "%s %llx", name, &val) != 2) + continue; + /* + * We don't allow modification to these fields: + * monitor_name manufacture_id product_id + * eld_version edid_version + */ + if (!strcmp(name, "monitor_present")) + eld->monitor_present = val; + else if (!strcmp(name, "eld_valid")) + eld->eld_valid = val; + else if (!strcmp(name, "connection_type")) + e->conn_type = val; + else if (!strcmp(name, "port_id")) + e->port_id = val; + else if (!strcmp(name, "support_hdcp")) + e->support_hdcp = val; + else if (!strcmp(name, "support_ai")) + e->support_ai = val; + else if (!strcmp(name, "audio_sync_delay")) + e->aud_synch_delay = val; + else if (!strcmp(name, "speakers")) + e->spk_alloc = val; + else if (!strcmp(name, "sad_count")) + e->sad_count = val; + else if (!strncmp(name, "sad", 3)) { + sname = name + 4; + n = name[3] - '0'; + if (name[4] >= '0' && name[4] <= '9') { + sname++; + n = 10 * n + name[4] - '0'; + } + if (n >= ELD_MAX_SAD) + continue; + if (!strcmp(sname, "_coding_type")) + e->sad[n].format = val; + else if (!strcmp(sname, "_channels")) + e->sad[n].channels = val; + else if (!strcmp(sname, "_rates")) + e->sad[n].rates = val; + else if (!strcmp(sname, "_bits")) + e->sad[n].sample_bits = val; + else if (!strcmp(sname, "_max_bitrate")) + e->sad[n].max_bitrate = val; + else if (!strcmp(sname, "_profile")) + e->sad[n].profile = val; + if (n >= e->sad_count) + e->sad_count = n + 1; + } + } +} +#endif /* CONFIG_SND_PROC_FS */ + +/* update PCM info based on ELD */ +void snd_hdmi_eld_update_pcm_info(struct parsed_hdmi_eld *e, + struct hda_pcm_stream *hinfo) +{ + u32 rates; + u64 formats; + unsigned int maxbps; + unsigned int channels_max; + int i; + + /* assume basic audio support (the basic audio flag is not in ELD; + * however, all audio capable sinks are required to support basic + * audio) */ + rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000; + formats = SNDRV_PCM_FMTBIT_S16_LE; + maxbps = 16; + channels_max = 2; + for (i = 0; i < e->sad_count; i++) { + struct cea_sad *a = &e->sad[i]; + rates |= a->rates; + if (a->channels > channels_max) + channels_max = a->channels; + if (a->format == AUDIO_CODING_TYPE_LPCM) { + if (a->sample_bits & AC_SUPPCM_BITS_20) { + formats |= SNDRV_PCM_FMTBIT_S32_LE; + if (maxbps < 20) + maxbps = 20; + } + if (a->sample_bits & AC_SUPPCM_BITS_24) { + formats |= SNDRV_PCM_FMTBIT_S32_LE; + if (maxbps < 24) + maxbps = 24; + } + } + } + + /* restrict the parameters by the values the codec provides */ + hinfo->rates &= rates; + hinfo->formats &= formats; + hinfo->maxbps = min(hinfo->maxbps, maxbps); + hinfo->channels_max = min(hinfo->channels_max, channels_max); +} + + +/* ATI/AMD specific stuff (ELD emulation) */ + +#define ATI_VERB_SET_AUDIO_DESCRIPTOR 0x776 +#define ATI_VERB_SET_SINK_INFO_INDEX 0x780 +#define ATI_VERB_GET_SPEAKER_ALLOCATION 0xf70 +#define ATI_VERB_GET_AUDIO_DESCRIPTOR 0xf76 +#define ATI_VERB_GET_AUDIO_VIDEO_DELAY 0xf7b +#define ATI_VERB_GET_SINK_INFO_INDEX 0xf80 +#define ATI_VERB_GET_SINK_INFO_DATA 0xf81 + +#define ATI_SPKALLOC_SPKALLOC 0x007f +#define ATI_SPKALLOC_TYPE_HDMI 0x0100 +#define ATI_SPKALLOC_TYPE_DISPLAYPORT 0x0200 + +/* first three bytes are just standard SAD */ +#define ATI_AUDIODESC_CHANNELS 0x00000007 +#define ATI_AUDIODESC_RATES 0x0000ff00 +#define ATI_AUDIODESC_LPCM_STEREO_RATES 0xff000000 + +/* in standard HDMI VSDB format */ +#define ATI_DELAY_VIDEO_LATENCY 0x000000ff +#define ATI_DELAY_AUDIO_LATENCY 0x0000ff00 + +enum ati_sink_info_idx { + ATI_INFO_IDX_MANUFACTURER_ID = 0, + ATI_INFO_IDX_PRODUCT_ID = 1, + ATI_INFO_IDX_SINK_DESC_LEN = 2, + ATI_INFO_IDX_PORT_ID_LOW = 3, + ATI_INFO_IDX_PORT_ID_HIGH = 4, + ATI_INFO_IDX_SINK_DESC_FIRST = 5, + ATI_INFO_IDX_SINK_DESC_LAST = 22, /* max len 18 bytes */ +}; + +int snd_hdmi_get_eld_ati(struct hda_codec *codec, hda_nid_t nid, + unsigned char *buf, int *eld_size, bool rev3_or_later) +{ + int spkalloc, ati_sad, aud_synch; + int sink_desc_len = 0; + int pos, i; + + /* ATI/AMD does not have ELD, emulate it */ + + spkalloc = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SPEAKER_ALLOCATION, 0); + + if (spkalloc <= 0) { + codec_info(codec, "HDMI ATI/AMD: no speaker allocation for ELD\n"); + return -EINVAL; + } + + memset(buf, 0, ELD_FIXED_BYTES + ELD_MAX_MNL + ELD_MAX_SAD * 3); + + /* version */ + buf[0] = ELD_VER_CEA_861D << 3; + + /* speaker allocation from EDID */ + buf[7] = spkalloc & ATI_SPKALLOC_SPKALLOC; + + /* is DisplayPort? */ + if (spkalloc & ATI_SPKALLOC_TYPE_DISPLAYPORT) + buf[5] |= 0x04; + + pos = ELD_FIXED_BYTES; + + if (rev3_or_later) { + int sink_info; + + snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_PORT_ID_LOW); + sink_info = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0); + put_unaligned_le32(sink_info, buf + 8); + + snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_PORT_ID_HIGH); + sink_info = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0); + put_unaligned_le32(sink_info, buf + 12); + + snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_MANUFACTURER_ID); + sink_info = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0); + put_unaligned_le16(sink_info, buf + 16); + + snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_PRODUCT_ID); + sink_info = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0); + put_unaligned_le16(sink_info, buf + 18); + + snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_SINK_DESC_LEN); + sink_desc_len = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0); + + if (sink_desc_len > ELD_MAX_MNL) { + codec_info(codec, "HDMI ATI/AMD: Truncating HDMI sink description with length %d\n", + sink_desc_len); + sink_desc_len = ELD_MAX_MNL; + } + + buf[4] |= sink_desc_len; + + for (i = 0; i < sink_desc_len; i++) { + snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_SINK_DESC_FIRST + i); + buf[pos++] = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0); + } + } + + for (i = AUDIO_CODING_TYPE_LPCM; i <= AUDIO_CODING_TYPE_WMAPRO; i++) { + if (i == AUDIO_CODING_TYPE_SACD || i == AUDIO_CODING_TYPE_DST) + continue; /* not handled by ATI/AMD */ + + snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_AUDIO_DESCRIPTOR, i << 3); + ati_sad = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_AUDIO_DESCRIPTOR, 0); + + if (ati_sad <= 0) + continue; + + if (ati_sad & ATI_AUDIODESC_RATES) { + /* format is supported, copy SAD as-is */ + buf[pos++] = (ati_sad & 0x0000ff) >> 0; + buf[pos++] = (ati_sad & 0x00ff00) >> 8; + buf[pos++] = (ati_sad & 0xff0000) >> 16; + } + + if (i == AUDIO_CODING_TYPE_LPCM + && (ati_sad & ATI_AUDIODESC_LPCM_STEREO_RATES) + && (ati_sad & ATI_AUDIODESC_LPCM_STEREO_RATES) >> 16 != (ati_sad & ATI_AUDIODESC_RATES)) { + /* for PCM there is a separate stereo rate mask */ + buf[pos++] = ((ati_sad & 0x000000ff) & ~ATI_AUDIODESC_CHANNELS) | 0x1; + /* rates from the extra byte */ + buf[pos++] = (ati_sad & 0xff000000) >> 24; + buf[pos++] = (ati_sad & 0x00ff0000) >> 16; + } + } + + if (pos == ELD_FIXED_BYTES + sink_desc_len) { + codec_info(codec, "HDMI ATI/AMD: no audio descriptors for ELD\n"); + return -EINVAL; + } + + /* + * HDMI VSDB latency format: + * separately for both audio and video: + * 0 field not valid or unknown latency + * [1..251] msecs = (x-1)*2 (max 500ms with x = 251 = 0xfb) + * 255 audio/video not supported + * + * HDA latency format: + * single value indicating video latency relative to audio: + * 0 unknown or 0ms + * [1..250] msecs = x*2 (max 500ms with x = 250 = 0xfa) + * [251..255] reserved + */ + aud_synch = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_AUDIO_VIDEO_DELAY, 0); + if ((aud_synch & ATI_DELAY_VIDEO_LATENCY) && (aud_synch & ATI_DELAY_AUDIO_LATENCY)) { + int video_latency_hdmi = (aud_synch & ATI_DELAY_VIDEO_LATENCY); + int audio_latency_hdmi = (aud_synch & ATI_DELAY_AUDIO_LATENCY) >> 8; + + if (video_latency_hdmi <= 0xfb && audio_latency_hdmi <= 0xfb && + video_latency_hdmi > audio_latency_hdmi) + buf[6] = video_latency_hdmi - audio_latency_hdmi; + /* else unknown/invalid or 0ms or video ahead of audio, so use zero */ + } + + /* SAD count */ + buf[5] |= ((pos - ELD_FIXED_BYTES - sink_desc_len) / 3) << 4; + + /* Baseline ELD block length is 4-byte aligned */ + pos = round_up(pos, 4); + + /* Baseline ELD length (4-byte header is not counted in) */ + buf[2] = (pos - 4) / 4; + + *eld_size = pos; + + return 0; +} diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c new file mode 100644 index 000000000..dbf7aa88e --- /dev/null +++ b/sound/pci/hda/hda_generic.c @@ -0,0 +1,6097 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Universal Interface for Intel High Definition Audio Codec + * + * Generic widget tree parser + * + * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/export.h> +#include <linux/sort.h> +#include <linux/delay.h> +#include <linux/ctype.h> +#include <linux/string.h> +#include <linux/bitops.h> +#include <linux/module.h> +#include <linux/leds.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/tlv.h> +#include <sound/hda_codec.h> +#include "hda_local.h" +#include "hda_auto_parser.h" +#include "hda_jack.h" +#include "hda_beep.h" +#include "hda_generic.h" + + +/** + * snd_hda_gen_spec_init - initialize hda_gen_spec struct + * @spec: hda_gen_spec object to initialize + * + * Initialize the given hda_gen_spec object. + */ +int snd_hda_gen_spec_init(struct hda_gen_spec *spec) +{ + snd_array_init(&spec->kctls, sizeof(struct snd_kcontrol_new), 32); + snd_array_init(&spec->paths, sizeof(struct nid_path), 8); + snd_array_init(&spec->loopback_list, sizeof(struct hda_amp_list), 8); + mutex_init(&spec->pcm_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_gen_spec_init); + +/** + * snd_hda_gen_add_kctl - Add a new kctl_new struct from the template + * @spec: hda_gen_spec object + * @name: name string to override the template, NULL if unchanged + * @temp: template for the new kctl + * + * Add a new kctl (actually snd_kcontrol_new to be instantiated later) + * element based on the given snd_kcontrol_new template @temp and the + * name string @name to the list in @spec. + * Returns the newly created object or NULL as error. + */ +struct snd_kcontrol_new * +snd_hda_gen_add_kctl(struct hda_gen_spec *spec, const char *name, + const struct snd_kcontrol_new *temp) +{ + struct snd_kcontrol_new *knew = snd_array_new(&spec->kctls); + if (!knew) + return NULL; + *knew = *temp; + if (name) + knew->name = kstrdup(name, GFP_KERNEL); + else if (knew->name) + knew->name = kstrdup(knew->name, GFP_KERNEL); + if (!knew->name) + return NULL; + return knew; +} +EXPORT_SYMBOL_GPL(snd_hda_gen_add_kctl); + +static void free_kctls(struct hda_gen_spec *spec) +{ + if (spec->kctls.list) { + struct snd_kcontrol_new *kctl = spec->kctls.list; + int i; + for (i = 0; i < spec->kctls.used; i++) + kfree(kctl[i].name); + } + snd_array_free(&spec->kctls); +} + +static void snd_hda_gen_spec_free(struct hda_gen_spec *spec) +{ + if (!spec) + return; + free_kctls(spec); + snd_array_free(&spec->paths); + snd_array_free(&spec->loopback_list); +#ifdef CONFIG_SND_HDA_GENERIC_LEDS + if (spec->led_cdevs[LED_AUDIO_MUTE]) + led_classdev_unregister(spec->led_cdevs[LED_AUDIO_MUTE]); + if (spec->led_cdevs[LED_AUDIO_MICMUTE]) + led_classdev_unregister(spec->led_cdevs[LED_AUDIO_MICMUTE]); +#endif +} + +/* + * store user hints + */ +static void parse_user_hints(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + int val; + + val = snd_hda_get_bool_hint(codec, "jack_detect"); + if (val >= 0) + codec->no_jack_detect = !val; + val = snd_hda_get_bool_hint(codec, "inv_jack_detect"); + if (val >= 0) + codec->inv_jack_detect = !!val; + val = snd_hda_get_bool_hint(codec, "trigger_sense"); + if (val >= 0) + codec->no_trigger_sense = !val; + val = snd_hda_get_bool_hint(codec, "inv_eapd"); + if (val >= 0) + codec->inv_eapd = !!val; + val = snd_hda_get_bool_hint(codec, "pcm_format_first"); + if (val >= 0) + codec->pcm_format_first = !!val; + val = snd_hda_get_bool_hint(codec, "sticky_stream"); + if (val >= 0) + codec->no_sticky_stream = !val; + val = snd_hda_get_bool_hint(codec, "spdif_status_reset"); + if (val >= 0) + codec->spdif_status_reset = !!val; + val = snd_hda_get_bool_hint(codec, "pin_amp_workaround"); + if (val >= 0) + codec->pin_amp_workaround = !!val; + val = snd_hda_get_bool_hint(codec, "single_adc_amp"); + if (val >= 0) + codec->single_adc_amp = !!val; + val = snd_hda_get_bool_hint(codec, "power_save_node"); + if (val >= 0) + codec->power_save_node = !!val; + + val = snd_hda_get_bool_hint(codec, "auto_mute"); + if (val >= 0) + spec->suppress_auto_mute = !val; + val = snd_hda_get_bool_hint(codec, "auto_mic"); + if (val >= 0) + spec->suppress_auto_mic = !val; + val = snd_hda_get_bool_hint(codec, "line_in_auto_switch"); + if (val >= 0) + spec->line_in_auto_switch = !!val; + val = snd_hda_get_bool_hint(codec, "auto_mute_via_amp"); + if (val >= 0) + spec->auto_mute_via_amp = !!val; + val = snd_hda_get_bool_hint(codec, "need_dac_fix"); + if (val >= 0) + spec->need_dac_fix = !!val; + val = snd_hda_get_bool_hint(codec, "primary_hp"); + if (val >= 0) + spec->no_primary_hp = !val; + val = snd_hda_get_bool_hint(codec, "multi_io"); + if (val >= 0) + spec->no_multi_io = !val; + val = snd_hda_get_bool_hint(codec, "multi_cap_vol"); + if (val >= 0) + spec->multi_cap_vol = !!val; + val = snd_hda_get_bool_hint(codec, "inv_dmic_split"); + if (val >= 0) + spec->inv_dmic_split = !!val; + val = snd_hda_get_bool_hint(codec, "indep_hp"); + if (val >= 0) + spec->indep_hp = !!val; + val = snd_hda_get_bool_hint(codec, "add_stereo_mix_input"); + if (val >= 0) + spec->add_stereo_mix_input = !!val; + /* the following two are just for compatibility */ + val = snd_hda_get_bool_hint(codec, "add_out_jack_modes"); + if (val >= 0) + spec->add_jack_modes = !!val; + val = snd_hda_get_bool_hint(codec, "add_in_jack_modes"); + if (val >= 0) + spec->add_jack_modes = !!val; + val = snd_hda_get_bool_hint(codec, "add_jack_modes"); + if (val >= 0) + spec->add_jack_modes = !!val; + val = snd_hda_get_bool_hint(codec, "power_down_unused"); + if (val >= 0) + spec->power_down_unused = !!val; + val = snd_hda_get_bool_hint(codec, "add_hp_mic"); + if (val >= 0) + spec->hp_mic = !!val; + val = snd_hda_get_bool_hint(codec, "hp_mic_detect"); + if (val >= 0) + spec->suppress_hp_mic_detect = !val; + val = snd_hda_get_bool_hint(codec, "vmaster"); + if (val >= 0) + spec->suppress_vmaster = !val; + + if (!snd_hda_get_int_hint(codec, "mixer_nid", &val)) + spec->mixer_nid = val; +} + +/* + * pin control value accesses + */ + +#define update_pin_ctl(codec, pin, val) \ + snd_hda_codec_write_cache(codec, pin, 0, \ + AC_VERB_SET_PIN_WIDGET_CONTROL, val) + +/* restore the pinctl based on the cached value */ +static inline void restore_pin_ctl(struct hda_codec *codec, hda_nid_t pin) +{ + update_pin_ctl(codec, pin, snd_hda_codec_get_pin_target(codec, pin)); +} + +/* set the pinctl target value and write it if requested */ +static void set_pin_target(struct hda_codec *codec, hda_nid_t pin, + unsigned int val, bool do_write) +{ + if (!pin) + return; + val = snd_hda_correct_pin_ctl(codec, pin, val); + snd_hda_codec_set_pin_target(codec, pin, val); + if (do_write) + update_pin_ctl(codec, pin, val); +} + +/* set pinctl target values for all given pins */ +static void set_pin_targets(struct hda_codec *codec, int num_pins, + hda_nid_t *pins, unsigned int val) +{ + int i; + for (i = 0; i < num_pins; i++) + set_pin_target(codec, pins[i], val, false); +} + +/* + * parsing paths + */ + +/* return the position of NID in the list, or -1 if not found */ +static int find_idx_in_nid_list(hda_nid_t nid, const hda_nid_t *list, int nums) +{ + int i; + for (i = 0; i < nums; i++) + if (list[i] == nid) + return i; + return -1; +} + +/* return true if the given NID is contained in the path */ +static bool is_nid_contained(struct nid_path *path, hda_nid_t nid) +{ + return find_idx_in_nid_list(nid, path->path, path->depth) >= 0; +} + +static struct nid_path *get_nid_path(struct hda_codec *codec, + hda_nid_t from_nid, hda_nid_t to_nid, + int anchor_nid) +{ + struct hda_gen_spec *spec = codec->spec; + struct nid_path *path; + int i; + + snd_array_for_each(&spec->paths, i, path) { + if (path->depth <= 0) + continue; + if ((!from_nid || path->path[0] == from_nid) && + (!to_nid || path->path[path->depth - 1] == to_nid)) { + if (!anchor_nid || + (anchor_nid > 0 && is_nid_contained(path, anchor_nid)) || + (anchor_nid < 0 && !is_nid_contained(path, anchor_nid))) + return path; + } + } + return NULL; +} + +/** + * snd_hda_get_path_idx - get the index number corresponding to the path + * instance + * @codec: the HDA codec + * @path: nid_path object + * + * The returned index starts from 1, i.e. the actual array index with offset 1, + * and zero is handled as an invalid path + */ +int snd_hda_get_path_idx(struct hda_codec *codec, struct nid_path *path) +{ + struct hda_gen_spec *spec = codec->spec; + struct nid_path *array = spec->paths.list; + ssize_t idx; + + if (!spec->paths.used) + return 0; + idx = path - array; + if (idx < 0 || idx >= spec->paths.used) + return 0; + return idx + 1; +} +EXPORT_SYMBOL_GPL(snd_hda_get_path_idx); + +/** + * snd_hda_get_path_from_idx - get the path instance corresponding to the + * given index number + * @codec: the HDA codec + * @idx: the path index + */ +struct nid_path *snd_hda_get_path_from_idx(struct hda_codec *codec, int idx) +{ + struct hda_gen_spec *spec = codec->spec; + + if (idx <= 0 || idx > spec->paths.used) + return NULL; + return snd_array_elem(&spec->paths, idx - 1); +} +EXPORT_SYMBOL_GPL(snd_hda_get_path_from_idx); + +/* check whether the given DAC is already found in any existing paths */ +static bool is_dac_already_used(struct hda_codec *codec, hda_nid_t nid) +{ + struct hda_gen_spec *spec = codec->spec; + const struct nid_path *path; + int i; + + snd_array_for_each(&spec->paths, i, path) { + if (path->path[0] == nid) + return true; + } + return false; +} + +/* check whether the given two widgets can be connected */ +static bool is_reachable_path(struct hda_codec *codec, + hda_nid_t from_nid, hda_nid_t to_nid) +{ + if (!from_nid || !to_nid) + return false; + return snd_hda_get_conn_index(codec, to_nid, from_nid, true) >= 0; +} + +/* nid, dir and idx */ +#define AMP_VAL_COMPARE_MASK (0xffff | (1U << 18) | (0x0f << 19)) + +/* check whether the given ctl is already assigned in any path elements */ +static bool is_ctl_used(struct hda_codec *codec, unsigned int val, int type) +{ + struct hda_gen_spec *spec = codec->spec; + const struct nid_path *path; + int i; + + val &= AMP_VAL_COMPARE_MASK; + snd_array_for_each(&spec->paths, i, path) { + if ((path->ctls[type] & AMP_VAL_COMPARE_MASK) == val) + return true; + } + return false; +} + +/* check whether a control with the given (nid, dir, idx) was assigned */ +static bool is_ctl_associated(struct hda_codec *codec, hda_nid_t nid, + int dir, int idx, int type) +{ + unsigned int val = HDA_COMPOSE_AMP_VAL(nid, 3, idx, dir); + return is_ctl_used(codec, val, type); +} + +static void print_nid_path(struct hda_codec *codec, + const char *pfx, struct nid_path *path) +{ + char buf[40]; + char *pos = buf; + int i; + + *pos = 0; + for (i = 0; i < path->depth; i++) + pos += scnprintf(pos, sizeof(buf) - (pos - buf), "%s%02x", + pos != buf ? ":" : "", + path->path[i]); + + codec_dbg(codec, "%s path: depth=%d '%s'\n", pfx, path->depth, buf); +} + +/* called recursively */ +static bool __parse_nid_path(struct hda_codec *codec, + hda_nid_t from_nid, hda_nid_t to_nid, + int anchor_nid, struct nid_path *path, + int depth) +{ + const hda_nid_t *conn; + int i, nums; + + if (to_nid == anchor_nid) + anchor_nid = 0; /* anchor passed */ + else if (to_nid == (hda_nid_t)(-anchor_nid)) + return false; /* hit the exclusive nid */ + + nums = snd_hda_get_conn_list(codec, to_nid, &conn); + for (i = 0; i < nums; i++) { + if (conn[i] != from_nid) { + /* special case: when from_nid is 0, + * try to find an empty DAC + */ + if (from_nid || + get_wcaps_type(get_wcaps(codec, conn[i])) != AC_WID_AUD_OUT || + is_dac_already_used(codec, conn[i])) + continue; + } + /* anchor is not requested or already passed? */ + if (anchor_nid <= 0) + goto found; + } + if (depth >= MAX_NID_PATH_DEPTH) + return false; + for (i = 0; i < nums; i++) { + unsigned int type; + type = get_wcaps_type(get_wcaps(codec, conn[i])); + if (type == AC_WID_AUD_OUT || type == AC_WID_AUD_IN || + type == AC_WID_PIN) + continue; + if (__parse_nid_path(codec, from_nid, conn[i], + anchor_nid, path, depth + 1)) + goto found; + } + return false; + + found: + path->path[path->depth] = conn[i]; + path->idx[path->depth + 1] = i; + if (nums > 1 && get_wcaps_type(get_wcaps(codec, to_nid)) != AC_WID_AUD_MIX) + path->multi[path->depth + 1] = 1; + path->depth++; + return true; +} + +/* + * snd_hda_parse_nid_path - parse the widget path from the given nid to + * the target nid + * @codec: the HDA codec + * @from_nid: the NID where the path start from + * @to_nid: the NID where the path ends at + * @anchor_nid: the anchor indication + * @path: the path object to store the result + * + * Returns true if a matching path is found. + * + * The parsing behavior depends on parameters: + * when @from_nid is 0, try to find an empty DAC; + * when @anchor_nid is set to a positive value, only paths through the widget + * with the given value are evaluated. + * when @anchor_nid is set to a negative value, paths through the widget + * with the negative of given value are excluded, only other paths are chosen. + * when @anchor_nid is zero, no special handling about path selection. + */ +static bool snd_hda_parse_nid_path(struct hda_codec *codec, hda_nid_t from_nid, + hda_nid_t to_nid, int anchor_nid, + struct nid_path *path) +{ + if (__parse_nid_path(codec, from_nid, to_nid, anchor_nid, path, 1)) { + path->path[path->depth] = to_nid; + path->depth++; + return true; + } + return false; +} + +/** + * snd_hda_add_new_path - parse the path between the given NIDs and + * add to the path list + * @codec: the HDA codec + * @from_nid: the NID where the path start from + * @to_nid: the NID where the path ends at + * @anchor_nid: the anchor indication, see snd_hda_parse_nid_path() + * + * If no valid path is found, returns NULL. + */ +struct nid_path * +snd_hda_add_new_path(struct hda_codec *codec, hda_nid_t from_nid, + hda_nid_t to_nid, int anchor_nid) +{ + struct hda_gen_spec *spec = codec->spec; + struct nid_path *path; + + if (from_nid && to_nid && !is_reachable_path(codec, from_nid, to_nid)) + return NULL; + + /* check whether the path has been already added */ + path = get_nid_path(codec, from_nid, to_nid, anchor_nid); + if (path) + return path; + + path = snd_array_new(&spec->paths); + if (!path) + return NULL; + memset(path, 0, sizeof(*path)); + if (snd_hda_parse_nid_path(codec, from_nid, to_nid, anchor_nid, path)) + return path; + /* push back */ + spec->paths.used--; + return NULL; +} +EXPORT_SYMBOL_GPL(snd_hda_add_new_path); + +/* clear the given path as invalid so that it won't be picked up later */ +static void invalidate_nid_path(struct hda_codec *codec, int idx) +{ + struct nid_path *path = snd_hda_get_path_from_idx(codec, idx); + if (!path) + return; + memset(path, 0, sizeof(*path)); +} + +/* return a DAC if paired to the given pin by codec driver */ +static hda_nid_t get_preferred_dac(struct hda_codec *codec, hda_nid_t pin) +{ + struct hda_gen_spec *spec = codec->spec; + const hda_nid_t *list = spec->preferred_dacs; + + if (!list) + return 0; + for (; *list; list += 2) + if (*list == pin) + return list[1]; + return 0; +} + +/* look for an empty DAC slot */ +static hda_nid_t look_for_dac(struct hda_codec *codec, hda_nid_t pin, + bool is_digital) +{ + struct hda_gen_spec *spec = codec->spec; + bool cap_digital; + int i; + + for (i = 0; i < spec->num_all_dacs; i++) { + hda_nid_t nid = spec->all_dacs[i]; + if (!nid || is_dac_already_used(codec, nid)) + continue; + cap_digital = !!(get_wcaps(codec, nid) & AC_WCAP_DIGITAL); + if (is_digital != cap_digital) + continue; + if (is_reachable_path(codec, nid, pin)) + return nid; + } + return 0; +} + +/* replace the channels in the composed amp value with the given number */ +static unsigned int amp_val_replace_channels(unsigned int val, unsigned int chs) +{ + val &= ~(0x3U << 16); + val |= chs << 16; + return val; +} + +static bool same_amp_caps(struct hda_codec *codec, hda_nid_t nid1, + hda_nid_t nid2, int dir) +{ + if (!(get_wcaps(codec, nid1) & (1 << (dir + 1)))) + return !(get_wcaps(codec, nid2) & (1 << (dir + 1))); + return (query_amp_caps(codec, nid1, dir) == + query_amp_caps(codec, nid2, dir)); +} + +/* look for a widget suitable for assigning a mute switch in the path */ +static hda_nid_t look_for_out_mute_nid(struct hda_codec *codec, + struct nid_path *path) +{ + int i; + + for (i = path->depth - 1; i >= 0; i--) { + if (nid_has_mute(codec, path->path[i], HDA_OUTPUT)) + return path->path[i]; + if (i != path->depth - 1 && i != 0 && + nid_has_mute(codec, path->path[i], HDA_INPUT)) + return path->path[i]; + } + return 0; +} + +/* look for a widget suitable for assigning a volume ctl in the path */ +static hda_nid_t look_for_out_vol_nid(struct hda_codec *codec, + struct nid_path *path) +{ + struct hda_gen_spec *spec = codec->spec; + int i; + + for (i = path->depth - 1; i >= 0; i--) { + hda_nid_t nid = path->path[i]; + if ((spec->out_vol_mask >> nid) & 1) + continue; + if (nid_has_volume(codec, nid, HDA_OUTPUT)) + return nid; + } + return 0; +} + +/* + * path activation / deactivation + */ + +/* can have the amp-in capability? */ +static bool has_amp_in(struct hda_codec *codec, struct nid_path *path, int idx) +{ + hda_nid_t nid = path->path[idx]; + unsigned int caps = get_wcaps(codec, nid); + unsigned int type = get_wcaps_type(caps); + + if (!(caps & AC_WCAP_IN_AMP)) + return false; + if (type == AC_WID_PIN && idx > 0) /* only for input pins */ + return false; + return true; +} + +/* can have the amp-out capability? */ +static bool has_amp_out(struct hda_codec *codec, struct nid_path *path, int idx) +{ + hda_nid_t nid = path->path[idx]; + unsigned int caps = get_wcaps(codec, nid); + unsigned int type = get_wcaps_type(caps); + + if (!(caps & AC_WCAP_OUT_AMP)) + return false; + if (type == AC_WID_PIN && !idx) /* only for output pins */ + return false; + return true; +} + +/* check whether the given (nid,dir,idx) is active */ +static bool is_active_nid(struct hda_codec *codec, hda_nid_t nid, + unsigned int dir, unsigned int idx) +{ + struct hda_gen_spec *spec = codec->spec; + int type = get_wcaps_type(get_wcaps(codec, nid)); + const struct nid_path *path; + int i, n; + + if (nid == codec->core.afg) + return true; + + snd_array_for_each(&spec->paths, n, path) { + if (!path->active) + continue; + if (codec->power_save_node) { + if (!path->stream_enabled) + continue; + /* ignore unplugged paths except for DAC/ADC */ + if (!(path->pin_enabled || path->pin_fixed) && + type != AC_WID_AUD_OUT && type != AC_WID_AUD_IN) + continue; + } + for (i = 0; i < path->depth; i++) { + if (path->path[i] == nid) { + if (dir == HDA_OUTPUT || idx == -1 || + path->idx[i] == idx) + return true; + break; + } + } + } + return false; +} + +/* check whether the NID is referred by any active paths */ +#define is_active_nid_for_any(codec, nid) \ + is_active_nid(codec, nid, HDA_OUTPUT, -1) + +/* get the default amp value for the target state */ +static int get_amp_val_to_activate(struct hda_codec *codec, hda_nid_t nid, + int dir, unsigned int caps, bool enable) +{ + unsigned int val = 0; + + if (caps & AC_AMPCAP_NUM_STEPS) { + /* set to 0dB */ + if (enable) + val = (caps & AC_AMPCAP_OFFSET) >> AC_AMPCAP_OFFSET_SHIFT; + } + if (caps & (AC_AMPCAP_MUTE | AC_AMPCAP_MIN_MUTE)) { + if (!enable) + val |= HDA_AMP_MUTE; + } + return val; +} + +/* is this a stereo widget or a stereo-to-mono mix? */ +static bool is_stereo_amps(struct hda_codec *codec, hda_nid_t nid, int dir) +{ + unsigned int wcaps = get_wcaps(codec, nid); + hda_nid_t conn; + + if (wcaps & AC_WCAP_STEREO) + return true; + if (dir != HDA_INPUT || get_wcaps_type(wcaps) != AC_WID_AUD_MIX) + return false; + if (snd_hda_get_num_conns(codec, nid) != 1) + return false; + if (snd_hda_get_connections(codec, nid, &conn, 1) < 0) + return false; + return !!(get_wcaps(codec, conn) & AC_WCAP_STEREO); +} + +/* initialize the amp value (only at the first time) */ +static void init_amp(struct hda_codec *codec, hda_nid_t nid, int dir, int idx) +{ + unsigned int caps = query_amp_caps(codec, nid, dir); + int val = get_amp_val_to_activate(codec, nid, dir, caps, false); + + if (is_stereo_amps(codec, nid, dir)) + snd_hda_codec_amp_init_stereo(codec, nid, dir, idx, 0xff, val); + else + snd_hda_codec_amp_init(codec, nid, 0, dir, idx, 0xff, val); +} + +/* update the amp, doing in stereo or mono depending on NID */ +static int update_amp(struct hda_codec *codec, hda_nid_t nid, int dir, int idx, + unsigned int mask, unsigned int val) +{ + if (is_stereo_amps(codec, nid, dir)) + return snd_hda_codec_amp_stereo(codec, nid, dir, idx, + mask, val); + else + return snd_hda_codec_amp_update(codec, nid, 0, dir, idx, + mask, val); +} + +/* calculate amp value mask we can modify; + * if the given amp is controlled by mixers, don't touch it + */ +static unsigned int get_amp_mask_to_modify(struct hda_codec *codec, + hda_nid_t nid, int dir, int idx, + unsigned int caps) +{ + unsigned int mask = 0xff; + + if (caps & (AC_AMPCAP_MUTE | AC_AMPCAP_MIN_MUTE)) { + if (is_ctl_associated(codec, nid, dir, idx, NID_PATH_MUTE_CTL)) + mask &= ~0x80; + } + if (caps & AC_AMPCAP_NUM_STEPS) { + if (is_ctl_associated(codec, nid, dir, idx, NID_PATH_VOL_CTL) || + is_ctl_associated(codec, nid, dir, idx, NID_PATH_BOOST_CTL)) + mask &= ~0x7f; + } + return mask; +} + +static void activate_amp(struct hda_codec *codec, hda_nid_t nid, int dir, + int idx, int idx_to_check, bool enable) +{ + unsigned int caps; + unsigned int mask, val; + + caps = query_amp_caps(codec, nid, dir); + val = get_amp_val_to_activate(codec, nid, dir, caps, enable); + mask = get_amp_mask_to_modify(codec, nid, dir, idx_to_check, caps); + if (!mask) + return; + + val &= mask; + update_amp(codec, nid, dir, idx, mask, val); +} + +static void check_and_activate_amp(struct hda_codec *codec, hda_nid_t nid, + int dir, int idx, int idx_to_check, + bool enable) +{ + /* check whether the given amp is still used by others */ + if (!enable && is_active_nid(codec, nid, dir, idx_to_check)) + return; + activate_amp(codec, nid, dir, idx, idx_to_check, enable); +} + +static void activate_amp_out(struct hda_codec *codec, struct nid_path *path, + int i, bool enable) +{ + hda_nid_t nid = path->path[i]; + init_amp(codec, nid, HDA_OUTPUT, 0); + check_and_activate_amp(codec, nid, HDA_OUTPUT, 0, 0, enable); +} + +static void activate_amp_in(struct hda_codec *codec, struct nid_path *path, + int i, bool enable, bool add_aamix) +{ + struct hda_gen_spec *spec = codec->spec; + const hda_nid_t *conn; + int n, nums, idx; + int type; + hda_nid_t nid = path->path[i]; + + nums = snd_hda_get_conn_list(codec, nid, &conn); + if (nums < 0) + return; + type = get_wcaps_type(get_wcaps(codec, nid)); + if (type == AC_WID_PIN || + (type == AC_WID_AUD_IN && codec->single_adc_amp)) { + nums = 1; + idx = 0; + } else + idx = path->idx[i]; + + for (n = 0; n < nums; n++) + init_amp(codec, nid, HDA_INPUT, n); + + /* here is a little bit tricky in comparison with activate_amp_out(); + * when aa-mixer is available, we need to enable the path as well + */ + for (n = 0; n < nums; n++) { + if (n != idx) { + if (conn[n] != spec->mixer_merge_nid) + continue; + /* when aamix is disabled, force to off */ + if (!add_aamix) { + activate_amp(codec, nid, HDA_INPUT, n, n, false); + continue; + } + } + check_and_activate_amp(codec, nid, HDA_INPUT, n, idx, enable); + } +} + +/* sync power of each widget in the given path */ +static hda_nid_t path_power_update(struct hda_codec *codec, + struct nid_path *path, + bool allow_powerdown) +{ + hda_nid_t nid, changed = 0; + int i, state, power; + + for (i = 0; i < path->depth; i++) { + nid = path->path[i]; + if (!(get_wcaps(codec, nid) & AC_WCAP_POWER)) + continue; + if (nid == codec->core.afg) + continue; + if (!allow_powerdown || is_active_nid_for_any(codec, nid)) + state = AC_PWRST_D0; + else + state = AC_PWRST_D3; + power = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_POWER_STATE, 0); + if (power != (state | (state << 4))) { + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_POWER_STATE, state); + changed = nid; + /* all known codecs seem to be capable to handl + * widgets state even in D3, so far. + * if any new codecs need to restore the widget + * states after D0 transition, call the function + * below. + */ +#if 0 /* disabled */ + if (state == AC_PWRST_D0) + snd_hdac_regmap_sync_node(&codec->core, nid); +#endif + } + } + return changed; +} + +/* do sync with the last power state change */ +static void sync_power_state_change(struct hda_codec *codec, hda_nid_t nid) +{ + if (nid) { + msleep(10); + snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_POWER_STATE, 0); + } +} + +/** + * snd_hda_activate_path - activate or deactivate the given path + * @codec: the HDA codec + * @path: the path to activate/deactivate + * @enable: flag to activate or not + * @add_aamix: enable the input from aamix NID + * + * If @add_aamix is set, enable the input from aa-mix NID as well (if any). + */ +void snd_hda_activate_path(struct hda_codec *codec, struct nid_path *path, + bool enable, bool add_aamix) +{ + struct hda_gen_spec *spec = codec->spec; + int i; + + path->active = enable; + + /* make sure the widget is powered up */ + if (enable && (spec->power_down_unused || codec->power_save_node)) + path_power_update(codec, path, codec->power_save_node); + + for (i = path->depth - 1; i >= 0; i--) { + hda_nid_t nid = path->path[i]; + + if (enable && path->multi[i]) + snd_hda_codec_write_cache(codec, nid, 0, + AC_VERB_SET_CONNECT_SEL, + path->idx[i]); + if (has_amp_in(codec, path, i)) + activate_amp_in(codec, path, i, enable, add_aamix); + if (has_amp_out(codec, path, i)) + activate_amp_out(codec, path, i, enable); + } +} +EXPORT_SYMBOL_GPL(snd_hda_activate_path); + +/* if the given path is inactive, put widgets into D3 (only if suitable) */ +static void path_power_down_sync(struct hda_codec *codec, struct nid_path *path) +{ + struct hda_gen_spec *spec = codec->spec; + + if (!(spec->power_down_unused || codec->power_save_node) || path->active) + return; + sync_power_state_change(codec, path_power_update(codec, path, true)); +} + +/* turn on/off EAPD on the given pin */ +static void set_pin_eapd(struct hda_codec *codec, hda_nid_t pin, bool enable) +{ + struct hda_gen_spec *spec = codec->spec; + if (spec->own_eapd_ctl || + !(snd_hda_query_pin_caps(codec, pin) & AC_PINCAP_EAPD)) + return; + if (spec->keep_eapd_on && !enable) + return; + if (codec->inv_eapd) + enable = !enable; + snd_hda_codec_write_cache(codec, pin, 0, + AC_VERB_SET_EAPD_BTLENABLE, + enable ? 0x02 : 0x00); +} + +/* re-initialize the path specified by the given path index */ +static void resume_path_from_idx(struct hda_codec *codec, int path_idx) +{ + struct nid_path *path = snd_hda_get_path_from_idx(codec, path_idx); + if (path) + snd_hda_activate_path(codec, path, path->active, false); +} + + +/* + * Helper functions for creating mixer ctl elements + */ + +static int hda_gen_mixer_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +static int hda_gen_bind_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +static int hda_gen_bind_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +enum { + HDA_CTL_WIDGET_VOL, + HDA_CTL_WIDGET_MUTE, + HDA_CTL_BIND_MUTE, +}; +static const struct snd_kcontrol_new control_templates[] = { + HDA_CODEC_VOLUME(NULL, 0, 0, 0), + /* only the put callback is replaced for handling the special mute */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .subdevice = HDA_SUBDEV_AMP_FLAG, + .info = snd_hda_mixer_amp_switch_info, + .get = snd_hda_mixer_amp_switch_get, + .put = hda_gen_mixer_mute_put, /* replaced */ + .private_value = HDA_COMPOSE_AMP_VAL(0, 3, 0, 0), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_hda_mixer_amp_switch_info, + .get = hda_gen_bind_mute_get, + .put = hda_gen_bind_mute_put, /* replaced */ + .private_value = HDA_COMPOSE_AMP_VAL(0, 3, 0, 0), + }, +}; + +/* add dynamic controls from template */ +static struct snd_kcontrol_new * +add_control(struct hda_gen_spec *spec, int type, const char *name, + int cidx, unsigned long val) +{ + struct snd_kcontrol_new *knew; + + knew = snd_hda_gen_add_kctl(spec, name, &control_templates[type]); + if (!knew) + return NULL; + knew->index = cidx; + if (get_amp_nid_(val)) + knew->subdevice = HDA_SUBDEV_AMP_FLAG; + if (knew->access == 0) + knew->access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + knew->private_value = val; + return knew; +} + +static int add_control_with_pfx(struct hda_gen_spec *spec, int type, + const char *pfx, const char *dir, + const char *sfx, int cidx, unsigned long val) +{ + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + snprintf(name, sizeof(name), "%s %s %s", pfx, dir, sfx); + if (!add_control(spec, type, name, cidx, val)) + return -ENOMEM; + return 0; +} + +#define add_pb_vol_ctrl(spec, type, pfx, val) \ + add_control_with_pfx(spec, type, pfx, "Playback", "Volume", 0, val) +#define add_pb_sw_ctrl(spec, type, pfx, val) \ + add_control_with_pfx(spec, type, pfx, "Playback", "Switch", 0, val) +#define __add_pb_vol_ctrl(spec, type, pfx, cidx, val) \ + add_control_with_pfx(spec, type, pfx, "Playback", "Volume", cidx, val) +#define __add_pb_sw_ctrl(spec, type, pfx, cidx, val) \ + add_control_with_pfx(spec, type, pfx, "Playback", "Switch", cidx, val) + +static int add_vol_ctl(struct hda_codec *codec, const char *pfx, int cidx, + unsigned int chs, struct nid_path *path) +{ + unsigned int val; + if (!path) + return 0; + val = path->ctls[NID_PATH_VOL_CTL]; + if (!val) + return 0; + val = amp_val_replace_channels(val, chs); + return __add_pb_vol_ctrl(codec->spec, HDA_CTL_WIDGET_VOL, pfx, cidx, val); +} + +/* return the channel bits suitable for the given path->ctls[] */ +static int get_default_ch_nums(struct hda_codec *codec, struct nid_path *path, + int type) +{ + int chs = 1; /* mono (left only) */ + if (path) { + hda_nid_t nid = get_amp_nid_(path->ctls[type]); + if (nid && (get_wcaps(codec, nid) & AC_WCAP_STEREO)) + chs = 3; /* stereo */ + } + return chs; +} + +static int add_stereo_vol(struct hda_codec *codec, const char *pfx, int cidx, + struct nid_path *path) +{ + int chs = get_default_ch_nums(codec, path, NID_PATH_VOL_CTL); + return add_vol_ctl(codec, pfx, cidx, chs, path); +} + +/* create a mute-switch for the given mixer widget; + * if it has multiple sources (e.g. DAC and loopback), create a bind-mute + */ +static int add_sw_ctl(struct hda_codec *codec, const char *pfx, int cidx, + unsigned int chs, struct nid_path *path) +{ + unsigned int val; + int type = HDA_CTL_WIDGET_MUTE; + + if (!path) + return 0; + val = path->ctls[NID_PATH_MUTE_CTL]; + if (!val) + return 0; + val = amp_val_replace_channels(val, chs); + if (get_amp_direction_(val) == HDA_INPUT) { + hda_nid_t nid = get_amp_nid_(val); + int nums = snd_hda_get_num_conns(codec, nid); + if (nums > 1) { + type = HDA_CTL_BIND_MUTE; + val |= nums << 19; + } + } + return __add_pb_sw_ctrl(codec->spec, type, pfx, cidx, val); +} + +static int add_stereo_sw(struct hda_codec *codec, const char *pfx, + int cidx, struct nid_path *path) +{ + int chs = get_default_ch_nums(codec, path, NID_PATH_MUTE_CTL); + return add_sw_ctl(codec, pfx, cidx, chs, path); +} + +/* playback mute control with the software mute bit check */ +static void sync_auto_mute_bits(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_gen_spec *spec = codec->spec; + + if (spec->auto_mute_via_amp) { + hda_nid_t nid = get_amp_nid(kcontrol); + bool enabled = !((spec->mute_bits >> nid) & 1); + ucontrol->value.integer.value[0] &= enabled; + ucontrol->value.integer.value[1] &= enabled; + } +} + +static int hda_gen_mixer_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + sync_auto_mute_bits(kcontrol, ucontrol); + return snd_hda_mixer_amp_switch_put(kcontrol, ucontrol); +} + +/* + * Bound mute controls + */ +#define AMP_VAL_IDX_SHIFT 19 +#define AMP_VAL_IDX_MASK (0x0f<<19) + +static int hda_gen_bind_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned long pval; + int err; + + mutex_lock(&codec->control_mutex); + pval = kcontrol->private_value; + kcontrol->private_value = pval & ~AMP_VAL_IDX_MASK; /* index 0 */ + err = snd_hda_mixer_amp_switch_get(kcontrol, ucontrol); + kcontrol->private_value = pval; + mutex_unlock(&codec->control_mutex); + return err; +} + +static int hda_gen_bind_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned long pval; + int i, indices, err = 0, change = 0; + + sync_auto_mute_bits(kcontrol, ucontrol); + + mutex_lock(&codec->control_mutex); + pval = kcontrol->private_value; + indices = (pval & AMP_VAL_IDX_MASK) >> AMP_VAL_IDX_SHIFT; + for (i = 0; i < indices; i++) { + kcontrol->private_value = (pval & ~AMP_VAL_IDX_MASK) | + (i << AMP_VAL_IDX_SHIFT); + err = snd_hda_mixer_amp_switch_put(kcontrol, ucontrol); + if (err < 0) + break; + change |= err; + } + kcontrol->private_value = pval; + mutex_unlock(&codec->control_mutex); + return err < 0 ? err : change; +} + +/* any ctl assigned to the path with the given index? */ +static bool path_has_mixer(struct hda_codec *codec, int path_idx, int ctl_type) +{ + struct nid_path *path = snd_hda_get_path_from_idx(codec, path_idx); + return path && path->ctls[ctl_type]; +} + +static const char * const channel_name[] = { + "Front", "Surround", "CLFE", "Side", "Back", +}; + +/* give some appropriate ctl name prefix for the given line out channel */ +static const char *get_line_out_pfx(struct hda_codec *codec, int ch, + int *index, int ctl_type) +{ + struct hda_gen_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + + *index = 0; + if (cfg->line_outs == 1 && !spec->multi_ios && + !codec->force_pin_prefix && + !cfg->hp_outs && !cfg->speaker_outs) + return spec->vmaster_mute.hook ? "PCM" : "Master"; + + /* if there is really a single DAC used in the whole output paths, + * use it master (or "PCM" if a vmaster hook is present) + */ + if (spec->multiout.num_dacs == 1 && !spec->mixer_nid && + !codec->force_pin_prefix && + !spec->multiout.hp_out_nid[0] && !spec->multiout.extra_out_nid[0]) + return spec->vmaster_mute.hook ? "PCM" : "Master"; + + /* multi-io channels */ + if (ch >= cfg->line_outs) + goto fixed_name; + + switch (cfg->line_out_type) { + case AUTO_PIN_SPEAKER_OUT: + /* if the primary channel vol/mute is shared with HP volume, + * don't name it as Speaker + */ + if (!ch && cfg->hp_outs && + !path_has_mixer(codec, spec->hp_paths[0], ctl_type)) + break; + if (cfg->line_outs == 1) + return "Speaker"; + if (cfg->line_outs == 2) + return ch ? "Bass Speaker" : "Speaker"; + break; + case AUTO_PIN_HP_OUT: + /* if the primary channel vol/mute is shared with spk volume, + * don't name it as Headphone + */ + if (!ch && cfg->speaker_outs && + !path_has_mixer(codec, spec->speaker_paths[0], ctl_type)) + break; + /* for multi-io case, only the primary out */ + if (ch && spec->multi_ios) + break; + *index = ch; + return "Headphone"; + case AUTO_PIN_LINE_OUT: + /* This deals with the case where one HP or one Speaker or + * one HP + one Speaker need to share the DAC with LO + */ + if (!ch) { + bool hp_lo_shared = false, spk_lo_shared = false; + + if (cfg->speaker_outs) + spk_lo_shared = !path_has_mixer(codec, + spec->speaker_paths[0], ctl_type); + if (cfg->hp_outs) + hp_lo_shared = !path_has_mixer(codec, spec->hp_paths[0], ctl_type); + if (hp_lo_shared && spk_lo_shared) + return spec->vmaster_mute.hook ? "PCM" : "Master"; + if (hp_lo_shared) + return "Headphone+LO"; + if (spk_lo_shared) + return "Speaker+LO"; + } + } + + /* for a single channel output, we don't have to name the channel */ + if (cfg->line_outs == 1 && !spec->multi_ios) + return "Line Out"; + + fixed_name: + if (ch >= ARRAY_SIZE(channel_name)) { + snd_BUG(); + return "PCM"; + } + + return channel_name[ch]; +} + +/* + * Parse output paths + */ + +/* badness definition */ +enum { + /* No primary DAC is found for the main output */ + BAD_NO_PRIMARY_DAC = 0x10000, + /* No DAC is found for the extra output */ + BAD_NO_DAC = 0x4000, + /* No possible multi-ios */ + BAD_MULTI_IO = 0x120, + /* No individual DAC for extra output */ + BAD_NO_EXTRA_DAC = 0x102, + /* No individual DAC for extra surrounds */ + BAD_NO_EXTRA_SURR_DAC = 0x101, + /* Primary DAC shared with main surrounds */ + BAD_SHARED_SURROUND = 0x100, + /* No independent HP possible */ + BAD_NO_INDEP_HP = 0x10, + /* Primary DAC shared with main CLFE */ + BAD_SHARED_CLFE = 0x10, + /* Primary DAC shared with extra surrounds */ + BAD_SHARED_EXTRA_SURROUND = 0x10, + /* Volume widget is shared */ + BAD_SHARED_VOL = 0x10, +}; + +/* look for widgets in the given path which are appropriate for + * volume and mute controls, and assign the values to ctls[]. + * + * When no appropriate widget is found in the path, the badness value + * is incremented depending on the situation. The function returns the + * total badness for both volume and mute controls. + */ +static int assign_out_path_ctls(struct hda_codec *codec, struct nid_path *path) +{ + struct hda_gen_spec *spec = codec->spec; + hda_nid_t nid; + unsigned int val; + int badness = 0; + + if (!path) + return BAD_SHARED_VOL * 2; + + if (path->ctls[NID_PATH_VOL_CTL] || + path->ctls[NID_PATH_MUTE_CTL]) + return 0; /* already evaluated */ + + nid = look_for_out_vol_nid(codec, path); + if (nid) { + val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT); + if (spec->dac_min_mute) + val |= HDA_AMP_VAL_MIN_MUTE; + if (is_ctl_used(codec, val, NID_PATH_VOL_CTL)) + badness += BAD_SHARED_VOL; + else + path->ctls[NID_PATH_VOL_CTL] = val; + } else + badness += BAD_SHARED_VOL; + nid = look_for_out_mute_nid(codec, path); + if (nid) { + unsigned int wid_type = get_wcaps_type(get_wcaps(codec, nid)); + if (wid_type == AC_WID_PIN || wid_type == AC_WID_AUD_OUT || + nid_has_mute(codec, nid, HDA_OUTPUT)) + val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT); + else + val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_INPUT); + if (is_ctl_used(codec, val, NID_PATH_MUTE_CTL)) + badness += BAD_SHARED_VOL; + else + path->ctls[NID_PATH_MUTE_CTL] = val; + } else + badness += BAD_SHARED_VOL; + return badness; +} + +const struct badness_table hda_main_out_badness = { + .no_primary_dac = BAD_NO_PRIMARY_DAC, + .no_dac = BAD_NO_DAC, + .shared_primary = BAD_NO_PRIMARY_DAC, + .shared_surr = BAD_SHARED_SURROUND, + .shared_clfe = BAD_SHARED_CLFE, + .shared_surr_main = BAD_SHARED_SURROUND, +}; +EXPORT_SYMBOL_GPL(hda_main_out_badness); + +const struct badness_table hda_extra_out_badness = { + .no_primary_dac = BAD_NO_DAC, + .no_dac = BAD_NO_DAC, + .shared_primary = BAD_NO_EXTRA_DAC, + .shared_surr = BAD_SHARED_EXTRA_SURROUND, + .shared_clfe = BAD_SHARED_EXTRA_SURROUND, + .shared_surr_main = BAD_NO_EXTRA_SURR_DAC, +}; +EXPORT_SYMBOL_GPL(hda_extra_out_badness); + +/* get the DAC of the primary output corresponding to the given array index */ +static hda_nid_t get_primary_out(struct hda_codec *codec, int idx) +{ + struct hda_gen_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + + if (cfg->line_outs > idx) + return spec->private_dac_nids[idx]; + idx -= cfg->line_outs; + if (spec->multi_ios > idx) + return spec->multi_io[idx].dac; + return 0; +} + +/* return the DAC if it's reachable, otherwise zero */ +static inline hda_nid_t try_dac(struct hda_codec *codec, + hda_nid_t dac, hda_nid_t pin) +{ + return is_reachable_path(codec, dac, pin) ? dac : 0; +} + +/* try to assign DACs to pins and return the resultant badness */ +static int try_assign_dacs(struct hda_codec *codec, int num_outs, + const hda_nid_t *pins, hda_nid_t *dacs, + int *path_idx, + const struct badness_table *bad) +{ + struct hda_gen_spec *spec = codec->spec; + int i, j; + int badness = 0; + hda_nid_t dac; + + if (!num_outs) + return 0; + + for (i = 0; i < num_outs; i++) { + struct nid_path *path; + hda_nid_t pin = pins[i]; + + if (!spec->obey_preferred_dacs) { + path = snd_hda_get_path_from_idx(codec, path_idx[i]); + if (path) { + badness += assign_out_path_ctls(codec, path); + continue; + } + } + + dacs[i] = get_preferred_dac(codec, pin); + if (dacs[i]) { + if (is_dac_already_used(codec, dacs[i])) + badness += bad->shared_primary; + } else if (spec->obey_preferred_dacs) { + badness += BAD_NO_PRIMARY_DAC; + } + + if (!dacs[i]) + dacs[i] = look_for_dac(codec, pin, false); + if (!dacs[i] && !i) { + /* try to steal the DAC of surrounds for the front */ + for (j = 1; j < num_outs; j++) { + if (is_reachable_path(codec, dacs[j], pin)) { + dacs[0] = dacs[j]; + dacs[j] = 0; + invalidate_nid_path(codec, path_idx[j]); + path_idx[j] = 0; + break; + } + } + } + dac = dacs[i]; + if (!dac) { + if (num_outs > 2) + dac = try_dac(codec, get_primary_out(codec, i), pin); + if (!dac) + dac = try_dac(codec, dacs[0], pin); + if (!dac) + dac = try_dac(codec, get_primary_out(codec, i), pin); + if (dac) { + if (!i) + badness += bad->shared_primary; + else if (i == 1) + badness += bad->shared_surr; + else + badness += bad->shared_clfe; + } else if (is_reachable_path(codec, spec->private_dac_nids[0], pin)) { + dac = spec->private_dac_nids[0]; + badness += bad->shared_surr_main; + } else if (!i) + badness += bad->no_primary_dac; + else + badness += bad->no_dac; + } + if (!dac) + continue; + path = snd_hda_add_new_path(codec, dac, pin, -spec->mixer_nid); + if (!path && !i && spec->mixer_nid) { + /* try with aamix */ + path = snd_hda_add_new_path(codec, dac, pin, 0); + } + if (!path) { + dacs[i] = 0; + badness += bad->no_dac; + } else { + /* print_nid_path(codec, "output", path); */ + path->active = true; + path_idx[i] = snd_hda_get_path_idx(codec, path); + badness += assign_out_path_ctls(codec, path); + } + } + + return badness; +} + +/* return NID if the given pin has only a single connection to a certain DAC */ +static hda_nid_t get_dac_if_single(struct hda_codec *codec, hda_nid_t pin) +{ + struct hda_gen_spec *spec = codec->spec; + int i; + hda_nid_t nid_found = 0; + + for (i = 0; i < spec->num_all_dacs; i++) { + hda_nid_t nid = spec->all_dacs[i]; + if (!nid || is_dac_already_used(codec, nid)) + continue; + if (is_reachable_path(codec, nid, pin)) { + if (nid_found) + return 0; + nid_found = nid; + } + } + return nid_found; +} + +/* check whether the given pin can be a multi-io pin */ +static bool can_be_multiio_pin(struct hda_codec *codec, + unsigned int location, hda_nid_t nid) +{ + unsigned int defcfg, caps; + + defcfg = snd_hda_codec_get_pincfg(codec, nid); + if (get_defcfg_connect(defcfg) != AC_JACK_PORT_COMPLEX) + return false; + if (location && get_defcfg_location(defcfg) != location) + return false; + caps = snd_hda_query_pin_caps(codec, nid); + if (!(caps & AC_PINCAP_OUT)) + return false; + return true; +} + +/* count the number of input pins that are capable to be multi-io */ +static int count_multiio_pins(struct hda_codec *codec, hda_nid_t reference_pin) +{ + struct hda_gen_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + unsigned int defcfg = snd_hda_codec_get_pincfg(codec, reference_pin); + unsigned int location = get_defcfg_location(defcfg); + int type, i; + int num_pins = 0; + + for (type = AUTO_PIN_LINE_IN; type >= AUTO_PIN_MIC; type--) { + for (i = 0; i < cfg->num_inputs; i++) { + if (cfg->inputs[i].type != type) + continue; + if (can_be_multiio_pin(codec, location, + cfg->inputs[i].pin)) + num_pins++; + } + } + return num_pins; +} + +/* + * multi-io helper + * + * When hardwired is set, try to fill ony hardwired pins, and returns + * zero if any pins are filled, non-zero if nothing found. + * When hardwired is off, try to fill possible input pins, and returns + * the badness value. + */ +static int fill_multi_ios(struct hda_codec *codec, + hda_nid_t reference_pin, + bool hardwired) +{ + struct hda_gen_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + int type, i, j, num_pins, old_pins; + unsigned int defcfg = snd_hda_codec_get_pincfg(codec, reference_pin); + unsigned int location = get_defcfg_location(defcfg); + int badness = 0; + struct nid_path *path; + + old_pins = spec->multi_ios; + if (old_pins >= 2) + goto end_fill; + + num_pins = count_multiio_pins(codec, reference_pin); + if (num_pins < 2) + goto end_fill; + + for (type = AUTO_PIN_LINE_IN; type >= AUTO_PIN_MIC; type--) { + for (i = 0; i < cfg->num_inputs; i++) { + hda_nid_t nid = cfg->inputs[i].pin; + hda_nid_t dac = 0; + + if (cfg->inputs[i].type != type) + continue; + if (!can_be_multiio_pin(codec, location, nid)) + continue; + for (j = 0; j < spec->multi_ios; j++) { + if (nid == spec->multi_io[j].pin) + break; + } + if (j < spec->multi_ios) + continue; + + if (hardwired) + dac = get_dac_if_single(codec, nid); + else if (!dac) + dac = look_for_dac(codec, nid, false); + if (!dac) { + badness++; + continue; + } + path = snd_hda_add_new_path(codec, dac, nid, + -spec->mixer_nid); + if (!path) { + badness++; + continue; + } + /* print_nid_path(codec, "multiio", path); */ + spec->multi_io[spec->multi_ios].pin = nid; + spec->multi_io[spec->multi_ios].dac = dac; + spec->out_paths[cfg->line_outs + spec->multi_ios] = + snd_hda_get_path_idx(codec, path); + spec->multi_ios++; + if (spec->multi_ios >= 2) + break; + } + } + end_fill: + if (badness) + badness = BAD_MULTI_IO; + if (old_pins == spec->multi_ios) { + if (hardwired) + return 1; /* nothing found */ + else + return badness; /* no badness if nothing found */ + } + if (!hardwired && spec->multi_ios < 2) { + /* cancel newly assigned paths */ + spec->paths.used -= spec->multi_ios - old_pins; + spec->multi_ios = old_pins; + return badness; + } + + /* assign volume and mute controls */ + for (i = old_pins; i < spec->multi_ios; i++) { + path = snd_hda_get_path_from_idx(codec, spec->out_paths[cfg->line_outs + i]); + badness += assign_out_path_ctls(codec, path); + } + + return badness; +} + +/* map DACs for all pins in the list if they are single connections */ +static bool map_singles(struct hda_codec *codec, int outs, + const hda_nid_t *pins, hda_nid_t *dacs, int *path_idx) +{ + struct hda_gen_spec *spec = codec->spec; + int i; + bool found = false; + for (i = 0; i < outs; i++) { + struct nid_path *path; + hda_nid_t dac; + if (dacs[i]) + continue; + dac = get_dac_if_single(codec, pins[i]); + if (!dac) + continue; + path = snd_hda_add_new_path(codec, dac, pins[i], + -spec->mixer_nid); + if (!path && !i && spec->mixer_nid) + path = snd_hda_add_new_path(codec, dac, pins[i], 0); + if (path) { + dacs[i] = dac; + found = true; + /* print_nid_path(codec, "output", path); */ + path->active = true; + path_idx[i] = snd_hda_get_path_idx(codec, path); + } + } + return found; +} + +static inline bool has_aamix_out_paths(struct hda_gen_spec *spec) +{ + return spec->aamix_out_paths[0] || spec->aamix_out_paths[1] || + spec->aamix_out_paths[2]; +} + +/* create a new path including aamix if available, and return its index */ +static int check_aamix_out_path(struct hda_codec *codec, int path_idx) +{ + struct hda_gen_spec *spec = codec->spec; + struct nid_path *path; + hda_nid_t path_dac, dac, pin; + + path = snd_hda_get_path_from_idx(codec, path_idx); + if (!path || !path->depth || + is_nid_contained(path, spec->mixer_nid)) + return 0; + path_dac = path->path[0]; + dac = spec->private_dac_nids[0]; + pin = path->path[path->depth - 1]; + path = snd_hda_add_new_path(codec, dac, pin, spec->mixer_nid); + if (!path) { + if (dac != path_dac) + dac = path_dac; + else if (spec->multiout.hp_out_nid[0]) + dac = spec->multiout.hp_out_nid[0]; + else if (spec->multiout.extra_out_nid[0]) + dac = spec->multiout.extra_out_nid[0]; + else + dac = 0; + if (dac) + path = snd_hda_add_new_path(codec, dac, pin, + spec->mixer_nid); + } + if (!path) + return 0; + /* print_nid_path(codec, "output-aamix", path); */ + path->active = false; /* unused as default */ + path->pin_fixed = true; /* static route */ + return snd_hda_get_path_idx(codec, path); +} + +/* check whether the independent HP is available with the current config */ +static bool indep_hp_possible(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + struct nid_path *path; + int i, idx; + + if (cfg->line_out_type == AUTO_PIN_HP_OUT) + idx = spec->out_paths[0]; + else + idx = spec->hp_paths[0]; + path = snd_hda_get_path_from_idx(codec, idx); + if (!path) + return false; + + /* assume no path conflicts unless aamix is involved */ + if (!spec->mixer_nid || !is_nid_contained(path, spec->mixer_nid)) + return true; + + /* check whether output paths contain aamix */ + for (i = 0; i < cfg->line_outs; i++) { + if (spec->out_paths[i] == idx) + break; + path = snd_hda_get_path_from_idx(codec, spec->out_paths[i]); + if (path && is_nid_contained(path, spec->mixer_nid)) + return false; + } + for (i = 0; i < cfg->speaker_outs; i++) { + path = snd_hda_get_path_from_idx(codec, spec->speaker_paths[i]); + if (path && is_nid_contained(path, spec->mixer_nid)) + return false; + } + + return true; +} + +/* fill the empty entries in the dac array for speaker/hp with the + * shared dac pointed by the paths + */ +static void refill_shared_dacs(struct hda_codec *codec, int num_outs, + hda_nid_t *dacs, int *path_idx) +{ + struct nid_path *path; + int i; + + for (i = 0; i < num_outs; i++) { + if (dacs[i]) + continue; + path = snd_hda_get_path_from_idx(codec, path_idx[i]); + if (!path) + continue; + dacs[i] = path->path[0]; + } +} + +/* fill in the dac_nids table from the parsed pin configuration */ +static int fill_and_eval_dacs(struct hda_codec *codec, + bool fill_hardwired, + bool fill_mio_first) +{ + struct hda_gen_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + int i, err, badness; + + /* set num_dacs once to full for look_for_dac() */ + spec->multiout.num_dacs = cfg->line_outs; + spec->multiout.dac_nids = spec->private_dac_nids; + memset(spec->private_dac_nids, 0, sizeof(spec->private_dac_nids)); + memset(spec->multiout.hp_out_nid, 0, sizeof(spec->multiout.hp_out_nid)); + memset(spec->multiout.extra_out_nid, 0, sizeof(spec->multiout.extra_out_nid)); + spec->multi_ios = 0; + snd_array_free(&spec->paths); + + /* clear path indices */ + memset(spec->out_paths, 0, sizeof(spec->out_paths)); + memset(spec->hp_paths, 0, sizeof(spec->hp_paths)); + memset(spec->speaker_paths, 0, sizeof(spec->speaker_paths)); + memset(spec->aamix_out_paths, 0, sizeof(spec->aamix_out_paths)); + memset(spec->digout_paths, 0, sizeof(spec->digout_paths)); + memset(spec->input_paths, 0, sizeof(spec->input_paths)); + memset(spec->loopback_paths, 0, sizeof(spec->loopback_paths)); + memset(&spec->digin_path, 0, sizeof(spec->digin_path)); + + badness = 0; + + /* fill hard-wired DACs first */ + if (fill_hardwired) { + bool mapped; + do { + mapped = map_singles(codec, cfg->line_outs, + cfg->line_out_pins, + spec->private_dac_nids, + spec->out_paths); + mapped |= map_singles(codec, cfg->hp_outs, + cfg->hp_pins, + spec->multiout.hp_out_nid, + spec->hp_paths); + mapped |= map_singles(codec, cfg->speaker_outs, + cfg->speaker_pins, + spec->multiout.extra_out_nid, + spec->speaker_paths); + if (!spec->no_multi_io && + fill_mio_first && cfg->line_outs == 1 && + cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) { + err = fill_multi_ios(codec, cfg->line_out_pins[0], true); + if (!err) + mapped = true; + } + } while (mapped); + } + + badness += try_assign_dacs(codec, cfg->line_outs, cfg->line_out_pins, + spec->private_dac_nids, spec->out_paths, + spec->main_out_badness); + + if (!spec->no_multi_io && fill_mio_first && + cfg->line_outs == 1 && cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) { + /* try to fill multi-io first */ + err = fill_multi_ios(codec, cfg->line_out_pins[0], false); + if (err < 0) + return err; + /* we don't count badness at this stage yet */ + } + + if (cfg->line_out_type != AUTO_PIN_HP_OUT) { + err = try_assign_dacs(codec, cfg->hp_outs, cfg->hp_pins, + spec->multiout.hp_out_nid, + spec->hp_paths, + spec->extra_out_badness); + if (err < 0) + return err; + badness += err; + } + if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) { + err = try_assign_dacs(codec, cfg->speaker_outs, + cfg->speaker_pins, + spec->multiout.extra_out_nid, + spec->speaker_paths, + spec->extra_out_badness); + if (err < 0) + return err; + badness += err; + } + if (!spec->no_multi_io && + cfg->line_outs == 1 && cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) { + err = fill_multi_ios(codec, cfg->line_out_pins[0], false); + if (err < 0) + return err; + badness += err; + } + + if (spec->mixer_nid) { + spec->aamix_out_paths[0] = + check_aamix_out_path(codec, spec->out_paths[0]); + if (cfg->line_out_type != AUTO_PIN_HP_OUT) + spec->aamix_out_paths[1] = + check_aamix_out_path(codec, spec->hp_paths[0]); + if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) + spec->aamix_out_paths[2] = + check_aamix_out_path(codec, spec->speaker_paths[0]); + } + + if (!spec->no_multi_io && + cfg->hp_outs && cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) + if (count_multiio_pins(codec, cfg->hp_pins[0]) >= 2) + spec->multi_ios = 1; /* give badness */ + + /* re-count num_dacs and squash invalid entries */ + spec->multiout.num_dacs = 0; + for (i = 0; i < cfg->line_outs; i++) { + if (spec->private_dac_nids[i]) + spec->multiout.num_dacs++; + else { + memmove(spec->private_dac_nids + i, + spec->private_dac_nids + i + 1, + sizeof(hda_nid_t) * (cfg->line_outs - i - 1)); + spec->private_dac_nids[cfg->line_outs - 1] = 0; + } + } + + spec->ext_channel_count = spec->min_channel_count = + spec->multiout.num_dacs * 2; + + if (spec->multi_ios == 2) { + for (i = 0; i < 2; i++) + spec->private_dac_nids[spec->multiout.num_dacs++] = + spec->multi_io[i].dac; + } else if (spec->multi_ios) { + spec->multi_ios = 0; + badness += BAD_MULTI_IO; + } + + if (spec->indep_hp && !indep_hp_possible(codec)) + badness += BAD_NO_INDEP_HP; + + /* re-fill the shared DAC for speaker / headphone */ + if (cfg->line_out_type != AUTO_PIN_HP_OUT) + refill_shared_dacs(codec, cfg->hp_outs, + spec->multiout.hp_out_nid, + spec->hp_paths); + if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) + refill_shared_dacs(codec, cfg->speaker_outs, + spec->multiout.extra_out_nid, + spec->speaker_paths); + + return badness; +} + +#define DEBUG_BADNESS + +#ifdef DEBUG_BADNESS +#define debug_badness(fmt, ...) \ + codec_dbg(codec, fmt, ##__VA_ARGS__) +#else +#define debug_badness(fmt, ...) \ + do { if (0) codec_dbg(codec, fmt, ##__VA_ARGS__); } while (0) +#endif + +#ifdef DEBUG_BADNESS +static inline void print_nid_path_idx(struct hda_codec *codec, + const char *pfx, int idx) +{ + struct nid_path *path; + + path = snd_hda_get_path_from_idx(codec, idx); + if (path) + print_nid_path(codec, pfx, path); +} + +static void debug_show_configs(struct hda_codec *codec, + struct auto_pin_cfg *cfg) +{ + struct hda_gen_spec *spec = codec->spec; + static const char * const lo_type[3] = { "LO", "SP", "HP" }; + int i; + + debug_badness("multi_outs = %x/%x/%x/%x : %x/%x/%x/%x (type %s)\n", + cfg->line_out_pins[0], cfg->line_out_pins[1], + cfg->line_out_pins[2], cfg->line_out_pins[3], + spec->multiout.dac_nids[0], + spec->multiout.dac_nids[1], + spec->multiout.dac_nids[2], + spec->multiout.dac_nids[3], + lo_type[cfg->line_out_type]); + for (i = 0; i < cfg->line_outs; i++) + print_nid_path_idx(codec, " out", spec->out_paths[i]); + if (spec->multi_ios > 0) + debug_badness("multi_ios(%d) = %x/%x : %x/%x\n", + spec->multi_ios, + spec->multi_io[0].pin, spec->multi_io[1].pin, + spec->multi_io[0].dac, spec->multi_io[1].dac); + for (i = 0; i < spec->multi_ios; i++) + print_nid_path_idx(codec, " mio", + spec->out_paths[cfg->line_outs + i]); + if (cfg->hp_outs) + debug_badness("hp_outs = %x/%x/%x/%x : %x/%x/%x/%x\n", + cfg->hp_pins[0], cfg->hp_pins[1], + cfg->hp_pins[2], cfg->hp_pins[3], + spec->multiout.hp_out_nid[0], + spec->multiout.hp_out_nid[1], + spec->multiout.hp_out_nid[2], + spec->multiout.hp_out_nid[3]); + for (i = 0; i < cfg->hp_outs; i++) + print_nid_path_idx(codec, " hp ", spec->hp_paths[i]); + if (cfg->speaker_outs) + debug_badness("spk_outs = %x/%x/%x/%x : %x/%x/%x/%x\n", + cfg->speaker_pins[0], cfg->speaker_pins[1], + cfg->speaker_pins[2], cfg->speaker_pins[3], + spec->multiout.extra_out_nid[0], + spec->multiout.extra_out_nid[1], + spec->multiout.extra_out_nid[2], + spec->multiout.extra_out_nid[3]); + for (i = 0; i < cfg->speaker_outs; i++) + print_nid_path_idx(codec, " spk", spec->speaker_paths[i]); + for (i = 0; i < 3; i++) + print_nid_path_idx(codec, " mix", spec->aamix_out_paths[i]); +} +#else +#define debug_show_configs(codec, cfg) /* NOP */ +#endif + +/* find all available DACs of the codec */ +static void fill_all_dac_nids(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + hda_nid_t nid; + + spec->num_all_dacs = 0; + memset(spec->all_dacs, 0, sizeof(spec->all_dacs)); + for_each_hda_codec_node(nid, codec) { + if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_AUD_OUT) + continue; + if (spec->num_all_dacs >= ARRAY_SIZE(spec->all_dacs)) { + codec_err(codec, "Too many DACs!\n"); + break; + } + spec->all_dacs[spec->num_all_dacs++] = nid; + } +} + +static int parse_output_paths(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + struct auto_pin_cfg *best_cfg; + unsigned int val; + int best_badness = INT_MAX; + int badness; + bool fill_hardwired = true, fill_mio_first = true; + bool best_wired = true, best_mio = true; + bool hp_spk_swapped = false; + + best_cfg = kmalloc(sizeof(*best_cfg), GFP_KERNEL); + if (!best_cfg) + return -ENOMEM; + *best_cfg = *cfg; + + for (;;) { + badness = fill_and_eval_dacs(codec, fill_hardwired, + fill_mio_first); + if (badness < 0) { + kfree(best_cfg); + return badness; + } + debug_badness("==> lo_type=%d, wired=%d, mio=%d, badness=0x%x\n", + cfg->line_out_type, fill_hardwired, fill_mio_first, + badness); + debug_show_configs(codec, cfg); + if (badness < best_badness) { + best_badness = badness; + *best_cfg = *cfg; + best_wired = fill_hardwired; + best_mio = fill_mio_first; + } + if (!badness) + break; + fill_mio_first = !fill_mio_first; + if (!fill_mio_first) + continue; + fill_hardwired = !fill_hardwired; + if (!fill_hardwired) + continue; + if (hp_spk_swapped) + break; + hp_spk_swapped = true; + if (cfg->speaker_outs > 0 && + cfg->line_out_type == AUTO_PIN_HP_OUT) { + cfg->hp_outs = cfg->line_outs; + memcpy(cfg->hp_pins, cfg->line_out_pins, + sizeof(cfg->hp_pins)); + cfg->line_outs = cfg->speaker_outs; + memcpy(cfg->line_out_pins, cfg->speaker_pins, + sizeof(cfg->speaker_pins)); + cfg->speaker_outs = 0; + memset(cfg->speaker_pins, 0, sizeof(cfg->speaker_pins)); + cfg->line_out_type = AUTO_PIN_SPEAKER_OUT; + fill_hardwired = true; + continue; + } + if (cfg->hp_outs > 0 && + cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) { + cfg->speaker_outs = cfg->line_outs; + memcpy(cfg->speaker_pins, cfg->line_out_pins, + sizeof(cfg->speaker_pins)); + cfg->line_outs = cfg->hp_outs; + memcpy(cfg->line_out_pins, cfg->hp_pins, + sizeof(cfg->hp_pins)); + cfg->hp_outs = 0; + memset(cfg->hp_pins, 0, sizeof(cfg->hp_pins)); + cfg->line_out_type = AUTO_PIN_HP_OUT; + fill_hardwired = true; + continue; + } + break; + } + + if (badness) { + debug_badness("==> restoring best_cfg\n"); + *cfg = *best_cfg; + fill_and_eval_dacs(codec, best_wired, best_mio); + } + debug_badness("==> Best config: lo_type=%d, wired=%d, mio=%d\n", + cfg->line_out_type, best_wired, best_mio); + debug_show_configs(codec, cfg); + + if (cfg->line_out_pins[0]) { + struct nid_path *path; + path = snd_hda_get_path_from_idx(codec, spec->out_paths[0]); + if (path) + spec->vmaster_nid = look_for_out_vol_nid(codec, path); + if (spec->vmaster_nid) { + snd_hda_set_vmaster_tlv(codec, spec->vmaster_nid, + HDA_OUTPUT, spec->vmaster_tlv); + if (spec->dac_min_mute) + spec->vmaster_tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] |= TLV_DB_SCALE_MUTE; + } + } + + /* set initial pinctl targets */ + if (spec->prefer_hp_amp || cfg->line_out_type == AUTO_PIN_HP_OUT) + val = PIN_HP; + else + val = PIN_OUT; + set_pin_targets(codec, cfg->line_outs, cfg->line_out_pins, val); + if (cfg->line_out_type != AUTO_PIN_HP_OUT) + set_pin_targets(codec, cfg->hp_outs, cfg->hp_pins, PIN_HP); + if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) { + val = spec->prefer_hp_amp ? PIN_HP : PIN_OUT; + set_pin_targets(codec, cfg->speaker_outs, + cfg->speaker_pins, val); + } + + /* clear indep_hp flag if not available */ + if (spec->indep_hp && !indep_hp_possible(codec)) + spec->indep_hp = 0; + + kfree(best_cfg); + return 0; +} + +/* add playback controls from the parsed DAC table */ +static int create_multi_out_ctls(struct hda_codec *codec, + const struct auto_pin_cfg *cfg) +{ + struct hda_gen_spec *spec = codec->spec; + int i, err, noutputs; + + noutputs = cfg->line_outs; + if (spec->multi_ios > 0 && cfg->line_outs < 3) + noutputs += spec->multi_ios; + + for (i = 0; i < noutputs; i++) { + const char *name; + int index; + struct nid_path *path; + + path = snd_hda_get_path_from_idx(codec, spec->out_paths[i]); + if (!path) + continue; + + name = get_line_out_pfx(codec, i, &index, NID_PATH_VOL_CTL); + if (!name || !strcmp(name, "CLFE")) { + /* Center/LFE */ + err = add_vol_ctl(codec, "Center", 0, 1, path); + if (err < 0) + return err; + err = add_vol_ctl(codec, "LFE", 0, 2, path); + if (err < 0) + return err; + } else { + err = add_stereo_vol(codec, name, index, path); + if (err < 0) + return err; + } + + name = get_line_out_pfx(codec, i, &index, NID_PATH_MUTE_CTL); + if (!name || !strcmp(name, "CLFE")) { + err = add_sw_ctl(codec, "Center", 0, 1, path); + if (err < 0) + return err; + err = add_sw_ctl(codec, "LFE", 0, 2, path); + if (err < 0) + return err; + } else { + err = add_stereo_sw(codec, name, index, path); + if (err < 0) + return err; + } + } + return 0; +} + +static int create_extra_out(struct hda_codec *codec, int path_idx, + const char *pfx, int cidx) +{ + struct nid_path *path; + int err; + + path = snd_hda_get_path_from_idx(codec, path_idx); + if (!path) + return 0; + err = add_stereo_vol(codec, pfx, cidx, path); + if (err < 0) + return err; + err = add_stereo_sw(codec, pfx, cidx, path); + if (err < 0) + return err; + return 0; +} + +/* add playback controls for speaker and HP outputs */ +static int create_extra_outs(struct hda_codec *codec, int num_pins, + const int *paths, const char *pfx) +{ + int i; + + for (i = 0; i < num_pins; i++) { + const char *name; + char tmp[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + int err, idx = 0; + + if (num_pins == 2 && i == 1 && !strcmp(pfx, "Speaker")) + name = "Bass Speaker"; + else if (num_pins >= 3) { + snprintf(tmp, sizeof(tmp), "%s %s", + pfx, channel_name[i]); + name = tmp; + } else { + name = pfx; + idx = i; + } + err = create_extra_out(codec, paths[i], name, idx); + if (err < 0) + return err; + } + return 0; +} + +static int create_hp_out_ctls(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + return create_extra_outs(codec, spec->autocfg.hp_outs, + spec->hp_paths, + "Headphone"); +} + +static int create_speaker_out_ctls(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + return create_extra_outs(codec, spec->autocfg.speaker_outs, + spec->speaker_paths, + "Speaker"); +} + +/* + * independent HP controls + */ + +static void call_hp_automute(struct hda_codec *codec, + struct hda_jack_callback *jack); +static int indep_hp_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_hda_enum_bool_helper_info(kcontrol, uinfo); +} + +static int indep_hp_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_gen_spec *spec = codec->spec; + ucontrol->value.enumerated.item[0] = spec->indep_hp_enabled; + return 0; +} + +static void update_aamix_paths(struct hda_codec *codec, bool do_mix, + int nomix_path_idx, int mix_path_idx, + int out_type); + +static int indep_hp_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_gen_spec *spec = codec->spec; + unsigned int select = ucontrol->value.enumerated.item[0]; + int ret = 0; + + mutex_lock(&spec->pcm_mutex); + if (spec->active_streams) { + ret = -EBUSY; + goto unlock; + } + + if (spec->indep_hp_enabled != select) { + hda_nid_t *dacp; + if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT) + dacp = &spec->private_dac_nids[0]; + else + dacp = &spec->multiout.hp_out_nid[0]; + + /* update HP aamix paths in case it conflicts with indep HP */ + if (spec->have_aamix_ctl) { + if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT) + update_aamix_paths(codec, spec->aamix_mode, + spec->out_paths[0], + spec->aamix_out_paths[0], + spec->autocfg.line_out_type); + else + update_aamix_paths(codec, spec->aamix_mode, + spec->hp_paths[0], + spec->aamix_out_paths[1], + AUTO_PIN_HP_OUT); + } + + spec->indep_hp_enabled = select; + if (spec->indep_hp_enabled) + *dacp = 0; + else + *dacp = spec->alt_dac_nid; + + call_hp_automute(codec, NULL); + ret = 1; + } + unlock: + mutex_unlock(&spec->pcm_mutex); + return ret; +} + +static const struct snd_kcontrol_new indep_hp_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Independent HP", + .info = indep_hp_info, + .get = indep_hp_get, + .put = indep_hp_put, +}; + + +static int create_indep_hp_ctls(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + hda_nid_t dac; + + if (!spec->indep_hp) + return 0; + if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT) + dac = spec->multiout.dac_nids[0]; + else + dac = spec->multiout.hp_out_nid[0]; + if (!dac) { + spec->indep_hp = 0; + return 0; + } + + spec->indep_hp_enabled = false; + spec->alt_dac_nid = dac; + if (!snd_hda_gen_add_kctl(spec, NULL, &indep_hp_ctl)) + return -ENOMEM; + return 0; +} + +/* + * channel mode enum control + */ + +static int ch_mode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_gen_spec *spec = codec->spec; + int chs; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = spec->multi_ios + 1; + if (uinfo->value.enumerated.item > spec->multi_ios) + uinfo->value.enumerated.item = spec->multi_ios; + chs = uinfo->value.enumerated.item * 2 + spec->min_channel_count; + sprintf(uinfo->value.enumerated.name, "%dch", chs); + return 0; +} + +static int ch_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_gen_spec *spec = codec->spec; + ucontrol->value.enumerated.item[0] = + (spec->ext_channel_count - spec->min_channel_count) / 2; + return 0; +} + +static inline struct nid_path * +get_multiio_path(struct hda_codec *codec, int idx) +{ + struct hda_gen_spec *spec = codec->spec; + return snd_hda_get_path_from_idx(codec, + spec->out_paths[spec->autocfg.line_outs + idx]); +} + +static void update_automute_all(struct hda_codec *codec); + +/* Default value to be passed as aamix argument for snd_hda_activate_path(); + * used for output paths + */ +static bool aamix_default(struct hda_gen_spec *spec) +{ + return !spec->have_aamix_ctl || spec->aamix_mode; +} + +static int set_multi_io(struct hda_codec *codec, int idx, bool output) +{ + struct hda_gen_spec *spec = codec->spec; + hda_nid_t nid = spec->multi_io[idx].pin; + struct nid_path *path; + + path = get_multiio_path(codec, idx); + if (!path) + return -EINVAL; + + if (path->active == output) + return 0; + + if (output) { + set_pin_target(codec, nid, PIN_OUT, true); + snd_hda_activate_path(codec, path, true, aamix_default(spec)); + set_pin_eapd(codec, nid, true); + } else { + set_pin_eapd(codec, nid, false); + snd_hda_activate_path(codec, path, false, aamix_default(spec)); + set_pin_target(codec, nid, spec->multi_io[idx].ctl_in, true); + path_power_down_sync(codec, path); + } + + /* update jack retasking in case it modifies any of them */ + update_automute_all(codec); + + return 0; +} + +static int ch_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_gen_spec *spec = codec->spec; + int i, ch; + + ch = ucontrol->value.enumerated.item[0]; + if (ch < 0 || ch > spec->multi_ios) + return -EINVAL; + if (ch == (spec->ext_channel_count - spec->min_channel_count) / 2) + return 0; + spec->ext_channel_count = ch * 2 + spec->min_channel_count; + for (i = 0; i < spec->multi_ios; i++) + set_multi_io(codec, i, i < ch); + spec->multiout.max_channels = max(spec->ext_channel_count, + spec->const_channel_count); + if (spec->need_dac_fix) + spec->multiout.num_dacs = spec->multiout.max_channels / 2; + return 1; +} + +static const struct snd_kcontrol_new channel_mode_enum = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Channel Mode", + .info = ch_mode_info, + .get = ch_mode_get, + .put = ch_mode_put, +}; + +static int create_multi_channel_mode(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + + if (spec->multi_ios > 0) { + if (!snd_hda_gen_add_kctl(spec, NULL, &channel_mode_enum)) + return -ENOMEM; + } + return 0; +} + +/* + * aamix loopback enable/disable switch + */ + +#define loopback_mixing_info indep_hp_info + +static int loopback_mixing_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_gen_spec *spec = codec->spec; + ucontrol->value.enumerated.item[0] = spec->aamix_mode; + return 0; +} + +static void update_aamix_paths(struct hda_codec *codec, bool do_mix, + int nomix_path_idx, int mix_path_idx, + int out_type) +{ + struct hda_gen_spec *spec = codec->spec; + struct nid_path *nomix_path, *mix_path; + + nomix_path = snd_hda_get_path_from_idx(codec, nomix_path_idx); + mix_path = snd_hda_get_path_from_idx(codec, mix_path_idx); + if (!nomix_path || !mix_path) + return; + + /* if HP aamix path is driven from a different DAC and the + * independent HP mode is ON, can't turn on aamix path + */ + if (out_type == AUTO_PIN_HP_OUT && spec->indep_hp_enabled && + mix_path->path[0] != spec->alt_dac_nid) + do_mix = false; + + if (do_mix) { + snd_hda_activate_path(codec, nomix_path, false, true); + snd_hda_activate_path(codec, mix_path, true, true); + path_power_down_sync(codec, nomix_path); + } else { + snd_hda_activate_path(codec, mix_path, false, false); + snd_hda_activate_path(codec, nomix_path, true, false); + path_power_down_sync(codec, mix_path); + } +} + +/* re-initialize the output paths; only called from loopback_mixing_put() */ +static void update_output_paths(struct hda_codec *codec, int num_outs, + const int *paths) +{ + struct hda_gen_spec *spec = codec->spec; + struct nid_path *path; + int i; + + for (i = 0; i < num_outs; i++) { + path = snd_hda_get_path_from_idx(codec, paths[i]); + if (path) + snd_hda_activate_path(codec, path, path->active, + spec->aamix_mode); + } +} + +static int loopback_mixing_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_gen_spec *spec = codec->spec; + const struct auto_pin_cfg *cfg = &spec->autocfg; + unsigned int val = ucontrol->value.enumerated.item[0]; + + if (val == spec->aamix_mode) + return 0; + spec->aamix_mode = val; + if (has_aamix_out_paths(spec)) { + update_aamix_paths(codec, val, spec->out_paths[0], + spec->aamix_out_paths[0], + cfg->line_out_type); + update_aamix_paths(codec, val, spec->hp_paths[0], + spec->aamix_out_paths[1], + AUTO_PIN_HP_OUT); + update_aamix_paths(codec, val, spec->speaker_paths[0], + spec->aamix_out_paths[2], + AUTO_PIN_SPEAKER_OUT); + } else { + update_output_paths(codec, cfg->line_outs, spec->out_paths); + if (cfg->line_out_type != AUTO_PIN_HP_OUT) + update_output_paths(codec, cfg->hp_outs, spec->hp_paths); + if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) + update_output_paths(codec, cfg->speaker_outs, + spec->speaker_paths); + } + return 1; +} + +static const struct snd_kcontrol_new loopback_mixing_enum = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Loopback Mixing", + .info = loopback_mixing_info, + .get = loopback_mixing_get, + .put = loopback_mixing_put, +}; + +static int create_loopback_mixing_ctl(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + + if (!spec->mixer_nid) + return 0; + if (!snd_hda_gen_add_kctl(spec, NULL, &loopback_mixing_enum)) + return -ENOMEM; + spec->have_aamix_ctl = 1; + return 0; +} + +/* + * shared headphone/mic handling + */ + +static void call_update_outputs(struct hda_codec *codec); + +/* for shared I/O, change the pin-control accordingly */ +static void update_hp_mic(struct hda_codec *codec, int adc_mux, bool force) +{ + struct hda_gen_spec *spec = codec->spec; + bool as_mic; + unsigned int val; + hda_nid_t pin; + + pin = spec->hp_mic_pin; + as_mic = spec->cur_mux[adc_mux] == spec->hp_mic_mux_idx; + + if (!force) { + val = snd_hda_codec_get_pin_target(codec, pin); + if (as_mic) { + if (val & PIN_IN) + return; + } else { + if (val & PIN_OUT) + return; + } + } + + val = snd_hda_get_default_vref(codec, pin); + /* if the HP pin doesn't support VREF and the codec driver gives an + * alternative pin, set up the VREF on that pin instead + */ + if (val == AC_PINCTL_VREF_HIZ && spec->shared_mic_vref_pin) { + const hda_nid_t vref_pin = spec->shared_mic_vref_pin; + unsigned int vref_val = snd_hda_get_default_vref(codec, vref_pin); + if (vref_val != AC_PINCTL_VREF_HIZ) + snd_hda_set_pin_ctl_cache(codec, vref_pin, + PIN_IN | (as_mic ? vref_val : 0)); + } + + if (!spec->hp_mic_jack_modes) { + if (as_mic) + val |= PIN_IN; + else + val = PIN_HP; + set_pin_target(codec, pin, val, true); + call_hp_automute(codec, NULL); + } +} + +/* create a shared input with the headphone out */ +static int create_hp_mic(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + unsigned int defcfg; + hda_nid_t nid; + + if (!spec->hp_mic) { + if (spec->suppress_hp_mic_detect) + return 0; + /* automatic detection: only if no input or a single internal + * input pin is found, try to detect the shared hp/mic + */ + if (cfg->num_inputs > 1) + return 0; + else if (cfg->num_inputs == 1) { + defcfg = snd_hda_codec_get_pincfg(codec, cfg->inputs[0].pin); + if (snd_hda_get_input_pin_attr(defcfg) != INPUT_PIN_ATTR_INT) + return 0; + } + } + + spec->hp_mic = 0; /* clear once */ + if (cfg->num_inputs >= AUTO_CFG_MAX_INS) + return 0; + + nid = 0; + if (cfg->line_out_type == AUTO_PIN_HP_OUT && cfg->line_outs > 0) + nid = cfg->line_out_pins[0]; + else if (cfg->hp_outs > 0) + nid = cfg->hp_pins[0]; + if (!nid) + return 0; + + if (!(snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_IN)) + return 0; /* no input */ + + cfg->inputs[cfg->num_inputs].pin = nid; + cfg->inputs[cfg->num_inputs].type = AUTO_PIN_MIC; + cfg->inputs[cfg->num_inputs].is_headphone_mic = 1; + cfg->num_inputs++; + spec->hp_mic = 1; + spec->hp_mic_pin = nid; + /* we can't handle auto-mic together with HP-mic */ + spec->suppress_auto_mic = 1; + codec_dbg(codec, "Enable shared I/O jack on NID 0x%x\n", nid); + return 0; +} + +/* + * output jack mode + */ + +static int create_hp_mic_jack_mode(struct hda_codec *codec, hda_nid_t pin); + +static const char * const out_jack_texts[] = { + "Line Out", "Headphone Out", +}; + +static int out_jack_mode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_hda_enum_helper_info(kcontrol, uinfo, 2, out_jack_texts); +} + +static int out_jack_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = kcontrol->private_value; + if (snd_hda_codec_get_pin_target(codec, nid) == PIN_HP) + ucontrol->value.enumerated.item[0] = 1; + else + ucontrol->value.enumerated.item[0] = 0; + return 0; +} + +static int out_jack_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = kcontrol->private_value; + unsigned int val; + + val = ucontrol->value.enumerated.item[0] ? PIN_HP : PIN_OUT; + if (snd_hda_codec_get_pin_target(codec, nid) == val) + return 0; + snd_hda_set_pin_ctl_cache(codec, nid, val); + return 1; +} + +static const struct snd_kcontrol_new out_jack_mode_enum = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = out_jack_mode_info, + .get = out_jack_mode_get, + .put = out_jack_mode_put, +}; + +static bool find_kctl_name(struct hda_codec *codec, const char *name, int idx) +{ + struct hda_gen_spec *spec = codec->spec; + const struct snd_kcontrol_new *kctl; + int i; + + snd_array_for_each(&spec->kctls, i, kctl) { + if (!strcmp(kctl->name, name) && kctl->index == idx) + return true; + } + return false; +} + +static void get_jack_mode_name(struct hda_codec *codec, hda_nid_t pin, + char *name, size_t name_len) +{ + struct hda_gen_spec *spec = codec->spec; + int idx = 0; + + snd_hda_get_pin_label(codec, pin, &spec->autocfg, name, name_len, &idx); + strlcat(name, " Jack Mode", name_len); + + for (; find_kctl_name(codec, name, idx); idx++) + ; +} + +static int get_out_jack_num_items(struct hda_codec *codec, hda_nid_t pin) +{ + struct hda_gen_spec *spec = codec->spec; + if (spec->add_jack_modes) { + unsigned int pincap = snd_hda_query_pin_caps(codec, pin); + if ((pincap & AC_PINCAP_OUT) && (pincap & AC_PINCAP_HP_DRV)) + return 2; + } + return 1; +} + +static int create_out_jack_modes(struct hda_codec *codec, int num_pins, + hda_nid_t *pins) +{ + struct hda_gen_spec *spec = codec->spec; + int i; + + for (i = 0; i < num_pins; i++) { + hda_nid_t pin = pins[i]; + if (pin == spec->hp_mic_pin) + continue; + if (get_out_jack_num_items(codec, pin) > 1) { + struct snd_kcontrol_new *knew; + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + get_jack_mode_name(codec, pin, name, sizeof(name)); + knew = snd_hda_gen_add_kctl(spec, name, + &out_jack_mode_enum); + if (!knew) + return -ENOMEM; + knew->private_value = pin; + } + } + + return 0; +} + +/* + * input jack mode + */ + +/* from AC_PINCTL_VREF_HIZ to AC_PINCTL_VREF_100 */ +#define NUM_VREFS 6 + +static const char * const vref_texts[NUM_VREFS] = { + "Line In", "Mic 50pc Bias", "Mic 0V Bias", + "", "Mic 80pc Bias", "Mic 100pc Bias" +}; + +static unsigned int get_vref_caps(struct hda_codec *codec, hda_nid_t pin) +{ + unsigned int pincap; + + pincap = snd_hda_query_pin_caps(codec, pin); + pincap = (pincap & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT; + /* filter out unusual vrefs */ + pincap &= ~(AC_PINCAP_VREF_GRD | AC_PINCAP_VREF_100); + return pincap; +} + +/* convert from the enum item index to the vref ctl index (0=HIZ, 1=50%...) */ +static int get_vref_idx(unsigned int vref_caps, unsigned int item_idx) +{ + unsigned int i, n = 0; + + for (i = 0; i < NUM_VREFS; i++) { + if (vref_caps & (1 << i)) { + if (n == item_idx) + return i; + n++; + } + } + return 0; +} + +/* convert back from the vref ctl index to the enum item index */ +static int cvt_from_vref_idx(unsigned int vref_caps, unsigned int idx) +{ + unsigned int i, n = 0; + + for (i = 0; i < NUM_VREFS; i++) { + if (i == idx) + return n; + if (vref_caps & (1 << i)) + n++; + } + return 0; +} + +static int in_jack_mode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = kcontrol->private_value; + unsigned int vref_caps = get_vref_caps(codec, nid); + + snd_hda_enum_helper_info(kcontrol, uinfo, hweight32(vref_caps), + vref_texts); + /* set the right text */ + strcpy(uinfo->value.enumerated.name, + vref_texts[get_vref_idx(vref_caps, uinfo->value.enumerated.item)]); + return 0; +} + +static int in_jack_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = kcontrol->private_value; + unsigned int vref_caps = get_vref_caps(codec, nid); + unsigned int idx; + + idx = snd_hda_codec_get_pin_target(codec, nid) & AC_PINCTL_VREFEN; + ucontrol->value.enumerated.item[0] = cvt_from_vref_idx(vref_caps, idx); + return 0; +} + +static int in_jack_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = kcontrol->private_value; + unsigned int vref_caps = get_vref_caps(codec, nid); + unsigned int val, idx; + + val = snd_hda_codec_get_pin_target(codec, nid); + idx = cvt_from_vref_idx(vref_caps, val & AC_PINCTL_VREFEN); + if (idx == ucontrol->value.enumerated.item[0]) + return 0; + + val &= ~AC_PINCTL_VREFEN; + val |= get_vref_idx(vref_caps, ucontrol->value.enumerated.item[0]); + snd_hda_set_pin_ctl_cache(codec, nid, val); + return 1; +} + +static const struct snd_kcontrol_new in_jack_mode_enum = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = in_jack_mode_info, + .get = in_jack_mode_get, + .put = in_jack_mode_put, +}; + +static int get_in_jack_num_items(struct hda_codec *codec, hda_nid_t pin) +{ + struct hda_gen_spec *spec = codec->spec; + int nitems = 0; + if (spec->add_jack_modes) + nitems = hweight32(get_vref_caps(codec, pin)); + return nitems ? nitems : 1; +} + +static int create_in_jack_mode(struct hda_codec *codec, hda_nid_t pin) +{ + struct hda_gen_spec *spec = codec->spec; + struct snd_kcontrol_new *knew; + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + unsigned int defcfg; + + if (pin == spec->hp_mic_pin) + return 0; /* already done in create_out_jack_mode() */ + + /* no jack mode for fixed pins */ + defcfg = snd_hda_codec_get_pincfg(codec, pin); + if (snd_hda_get_input_pin_attr(defcfg) == INPUT_PIN_ATTR_INT) + return 0; + + /* no multiple vref caps? */ + if (get_in_jack_num_items(codec, pin) <= 1) + return 0; + + get_jack_mode_name(codec, pin, name, sizeof(name)); + knew = snd_hda_gen_add_kctl(spec, name, &in_jack_mode_enum); + if (!knew) + return -ENOMEM; + knew->private_value = pin; + return 0; +} + +/* + * HP/mic shared jack mode + */ +static int hp_mic_jack_mode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = kcontrol->private_value; + int out_jacks = get_out_jack_num_items(codec, nid); + int in_jacks = get_in_jack_num_items(codec, nid); + const char *text = NULL; + int idx; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = out_jacks + in_jacks; + if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items) + uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1; + idx = uinfo->value.enumerated.item; + if (idx < out_jacks) { + if (out_jacks > 1) + text = out_jack_texts[idx]; + else + text = "Headphone Out"; + } else { + idx -= out_jacks; + if (in_jacks > 1) { + unsigned int vref_caps = get_vref_caps(codec, nid); + text = vref_texts[get_vref_idx(vref_caps, idx)]; + } else + text = "Mic In"; + } + + strcpy(uinfo->value.enumerated.name, text); + return 0; +} + +static int get_cur_hp_mic_jack_mode(struct hda_codec *codec, hda_nid_t nid) +{ + int out_jacks = get_out_jack_num_items(codec, nid); + int in_jacks = get_in_jack_num_items(codec, nid); + unsigned int val = snd_hda_codec_get_pin_target(codec, nid); + int idx = 0; + + if (val & PIN_OUT) { + if (out_jacks > 1 && val == PIN_HP) + idx = 1; + } else if (val & PIN_IN) { + idx = out_jacks; + if (in_jacks > 1) { + unsigned int vref_caps = get_vref_caps(codec, nid); + val &= AC_PINCTL_VREFEN; + idx += cvt_from_vref_idx(vref_caps, val); + } + } + return idx; +} + +static int hp_mic_jack_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = kcontrol->private_value; + ucontrol->value.enumerated.item[0] = + get_cur_hp_mic_jack_mode(codec, nid); + return 0; +} + +static int hp_mic_jack_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = kcontrol->private_value; + int out_jacks = get_out_jack_num_items(codec, nid); + int in_jacks = get_in_jack_num_items(codec, nid); + unsigned int val, oldval, idx; + + oldval = get_cur_hp_mic_jack_mode(codec, nid); + idx = ucontrol->value.enumerated.item[0]; + if (oldval == idx) + return 0; + + if (idx < out_jacks) { + if (out_jacks > 1) + val = idx ? PIN_HP : PIN_OUT; + else + val = PIN_HP; + } else { + idx -= out_jacks; + if (in_jacks > 1) { + unsigned int vref_caps = get_vref_caps(codec, nid); + val = snd_hda_codec_get_pin_target(codec, nid); + val &= ~(AC_PINCTL_VREFEN | PIN_HP); + val |= get_vref_idx(vref_caps, idx) | PIN_IN; + } else + val = snd_hda_get_default_vref(codec, nid) | PIN_IN; + } + snd_hda_set_pin_ctl_cache(codec, nid, val); + call_hp_automute(codec, NULL); + + return 1; +} + +static const struct snd_kcontrol_new hp_mic_jack_mode_enum = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = hp_mic_jack_mode_info, + .get = hp_mic_jack_mode_get, + .put = hp_mic_jack_mode_put, +}; + +static int create_hp_mic_jack_mode(struct hda_codec *codec, hda_nid_t pin) +{ + struct hda_gen_spec *spec = codec->spec; + struct snd_kcontrol_new *knew; + + knew = snd_hda_gen_add_kctl(spec, "Headphone Mic Jack Mode", + &hp_mic_jack_mode_enum); + if (!knew) + return -ENOMEM; + knew->private_value = pin; + spec->hp_mic_jack_modes = 1; + return 0; +} + +/* + * Parse input paths + */ + +/* add the powersave loopback-list entry */ +static int add_loopback_list(struct hda_gen_spec *spec, hda_nid_t mix, int idx) +{ + struct hda_amp_list *list; + + list = snd_array_new(&spec->loopback_list); + if (!list) + return -ENOMEM; + list->nid = mix; + list->dir = HDA_INPUT; + list->idx = idx; + spec->loopback.amplist = spec->loopback_list.list; + return 0; +} + +/* return true if either a volume or a mute amp is found for the given + * aamix path; the amp has to be either in the mixer node or its direct leaf + */ +static bool look_for_mix_leaf_ctls(struct hda_codec *codec, hda_nid_t mix_nid, + hda_nid_t pin, unsigned int *mix_val, + unsigned int *mute_val) +{ + int idx, num_conns; + const hda_nid_t *list; + hda_nid_t nid; + + idx = snd_hda_get_conn_index(codec, mix_nid, pin, true); + if (idx < 0) + return false; + + *mix_val = *mute_val = 0; + if (nid_has_volume(codec, mix_nid, HDA_INPUT)) + *mix_val = HDA_COMPOSE_AMP_VAL(mix_nid, 3, idx, HDA_INPUT); + if (nid_has_mute(codec, mix_nid, HDA_INPUT)) + *mute_val = HDA_COMPOSE_AMP_VAL(mix_nid, 3, idx, HDA_INPUT); + if (*mix_val && *mute_val) + return true; + + /* check leaf node */ + num_conns = snd_hda_get_conn_list(codec, mix_nid, &list); + if (num_conns < idx) + return false; + nid = list[idx]; + if (!*mix_val && nid_has_volume(codec, nid, HDA_OUTPUT) && + !is_ctl_associated(codec, nid, HDA_OUTPUT, 0, NID_PATH_VOL_CTL)) + *mix_val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT); + if (!*mute_val && nid_has_mute(codec, nid, HDA_OUTPUT) && + !is_ctl_associated(codec, nid, HDA_OUTPUT, 0, NID_PATH_MUTE_CTL)) + *mute_val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT); + + return *mix_val || *mute_val; +} + +/* create input playback/capture controls for the given pin */ +static int new_analog_input(struct hda_codec *codec, int input_idx, + hda_nid_t pin, const char *ctlname, int ctlidx, + hda_nid_t mix_nid) +{ + struct hda_gen_spec *spec = codec->spec; + struct nid_path *path; + unsigned int mix_val, mute_val; + int err, idx; + + if (!look_for_mix_leaf_ctls(codec, mix_nid, pin, &mix_val, &mute_val)) + return 0; + + path = snd_hda_add_new_path(codec, pin, mix_nid, 0); + if (!path) + return -EINVAL; + print_nid_path(codec, "loopback", path); + spec->loopback_paths[input_idx] = snd_hda_get_path_idx(codec, path); + + idx = path->idx[path->depth - 1]; + if (mix_val) { + err = __add_pb_vol_ctrl(spec, HDA_CTL_WIDGET_VOL, ctlname, ctlidx, mix_val); + if (err < 0) + return err; + path->ctls[NID_PATH_VOL_CTL] = mix_val; + } + + if (mute_val) { + err = __add_pb_sw_ctrl(spec, HDA_CTL_WIDGET_MUTE, ctlname, ctlidx, mute_val); + if (err < 0) + return err; + path->ctls[NID_PATH_MUTE_CTL] = mute_val; + } + + path->active = true; + path->stream_enabled = true; /* no DAC/ADC involved */ + err = add_loopback_list(spec, mix_nid, idx); + if (err < 0) + return err; + + if (spec->mixer_nid != spec->mixer_merge_nid && + !spec->loopback_merge_path) { + path = snd_hda_add_new_path(codec, spec->mixer_nid, + spec->mixer_merge_nid, 0); + if (path) { + print_nid_path(codec, "loopback-merge", path); + path->active = true; + path->pin_fixed = true; /* static route */ + path->stream_enabled = true; /* no DAC/ADC involved */ + spec->loopback_merge_path = + snd_hda_get_path_idx(codec, path); + } + } + + return 0; +} + +static int is_input_pin(struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int pincap = snd_hda_query_pin_caps(codec, nid); + return (pincap & AC_PINCAP_IN) != 0; +} + +/* Parse the codec tree and retrieve ADCs */ +static int fill_adc_nids(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + hda_nid_t nid; + hda_nid_t *adc_nids = spec->adc_nids; + int max_nums = ARRAY_SIZE(spec->adc_nids); + int nums = 0; + + for_each_hda_codec_node(nid, codec) { + unsigned int caps = get_wcaps(codec, nid); + int type = get_wcaps_type(caps); + + if (type != AC_WID_AUD_IN || (caps & AC_WCAP_DIGITAL)) + continue; + adc_nids[nums] = nid; + if (++nums >= max_nums) + break; + } + spec->num_adc_nids = nums; + + /* copy the detected ADCs to all_adcs[] */ + spec->num_all_adcs = nums; + memcpy(spec->all_adcs, spec->adc_nids, nums * sizeof(hda_nid_t)); + + return nums; +} + +/* filter out invalid adc_nids that don't give all active input pins; + * if needed, check whether dynamic ADC-switching is available + */ +static int check_dyn_adc_switch(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + struct hda_input_mux *imux = &spec->input_mux; + unsigned int ok_bits; + int i, n, nums; + + nums = 0; + ok_bits = 0; + for (n = 0; n < spec->num_adc_nids; n++) { + for (i = 0; i < imux->num_items; i++) { + if (!spec->input_paths[i][n]) + break; + } + if (i >= imux->num_items) { + ok_bits |= (1 << n); + nums++; + } + } + + if (!ok_bits) { + /* check whether ADC-switch is possible */ + for (i = 0; i < imux->num_items; i++) { + for (n = 0; n < spec->num_adc_nids; n++) { + if (spec->input_paths[i][n]) { + spec->dyn_adc_idx[i] = n; + break; + } + } + } + + codec_dbg(codec, "enabling ADC switching\n"); + spec->dyn_adc_switch = 1; + } else if (nums != spec->num_adc_nids) { + /* shrink the invalid adcs and input paths */ + nums = 0; + for (n = 0; n < spec->num_adc_nids; n++) { + if (!(ok_bits & (1 << n))) + continue; + if (n != nums) { + spec->adc_nids[nums] = spec->adc_nids[n]; + for (i = 0; i < imux->num_items; i++) { + invalidate_nid_path(codec, + spec->input_paths[i][nums]); + spec->input_paths[i][nums] = + spec->input_paths[i][n]; + spec->input_paths[i][n] = 0; + } + } + nums++; + } + spec->num_adc_nids = nums; + } + + if (imux->num_items == 1 || + (imux->num_items == 2 && spec->hp_mic)) { + codec_dbg(codec, "reducing to a single ADC\n"); + spec->num_adc_nids = 1; /* reduce to a single ADC */ + } + + /* single index for individual volumes ctls */ + if (!spec->dyn_adc_switch && spec->multi_cap_vol) + spec->num_adc_nids = 1; + + return 0; +} + +/* parse capture source paths from the given pin and create imux items */ +static int parse_capture_source(struct hda_codec *codec, hda_nid_t pin, + int cfg_idx, int num_adcs, + const char *label, int anchor) +{ + struct hda_gen_spec *spec = codec->spec; + struct hda_input_mux *imux = &spec->input_mux; + int imux_idx = imux->num_items; + bool imux_added = false; + int c; + + for (c = 0; c < num_adcs; c++) { + struct nid_path *path; + hda_nid_t adc = spec->adc_nids[c]; + + if (!is_reachable_path(codec, pin, adc)) + continue; + path = snd_hda_add_new_path(codec, pin, adc, anchor); + if (!path) + continue; + print_nid_path(codec, "input", path); + spec->input_paths[imux_idx][c] = + snd_hda_get_path_idx(codec, path); + + if (!imux_added) { + if (spec->hp_mic_pin == pin) + spec->hp_mic_mux_idx = imux->num_items; + spec->imux_pins[imux->num_items] = pin; + snd_hda_add_imux_item(codec, imux, label, cfg_idx, NULL); + imux_added = true; + if (spec->dyn_adc_switch) + spec->dyn_adc_idx[imux_idx] = c; + } + } + + return 0; +} + +/* + * create playback/capture controls for input pins + */ + +/* fill the label for each input at first */ +static int fill_input_pin_labels(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + const struct auto_pin_cfg *cfg = &spec->autocfg; + int i; + + for (i = 0; i < cfg->num_inputs; i++) { + hda_nid_t pin = cfg->inputs[i].pin; + const char *label; + int j, idx; + + if (!is_input_pin(codec, pin)) + continue; + + label = hda_get_autocfg_input_label(codec, cfg, i); + idx = 0; + for (j = i - 1; j >= 0; j--) { + if (spec->input_labels[j] && + !strcmp(spec->input_labels[j], label)) { + idx = spec->input_label_idxs[j] + 1; + break; + } + } + + spec->input_labels[i] = label; + spec->input_label_idxs[i] = idx; + } + + return 0; +} + +#define CFG_IDX_MIX 99 /* a dummy cfg->input idx for stereo mix */ + +static int create_input_ctls(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + const struct auto_pin_cfg *cfg = &spec->autocfg; + hda_nid_t mixer = spec->mixer_nid; + int num_adcs; + int i, err; + unsigned int val; + + num_adcs = fill_adc_nids(codec); + if (num_adcs < 0) + return 0; + + err = fill_input_pin_labels(codec); + if (err < 0) + return err; + + for (i = 0; i < cfg->num_inputs; i++) { + hda_nid_t pin; + + pin = cfg->inputs[i].pin; + if (!is_input_pin(codec, pin)) + continue; + + val = PIN_IN; + if (cfg->inputs[i].type == AUTO_PIN_MIC) + val |= snd_hda_get_default_vref(codec, pin); + if (pin != spec->hp_mic_pin && + !snd_hda_codec_get_pin_target(codec, pin)) + set_pin_target(codec, pin, val, false); + + if (mixer) { + if (is_reachable_path(codec, pin, mixer)) { + err = new_analog_input(codec, i, pin, + spec->input_labels[i], + spec->input_label_idxs[i], + mixer); + if (err < 0) + return err; + } + } + + err = parse_capture_source(codec, pin, i, num_adcs, + spec->input_labels[i], -mixer); + if (err < 0) + return err; + + if (spec->add_jack_modes) { + err = create_in_jack_mode(codec, pin); + if (err < 0) + return err; + } + } + + /* add stereo mix when explicitly enabled via hint */ + if (mixer && spec->add_stereo_mix_input == HDA_HINT_STEREO_MIX_ENABLE) { + err = parse_capture_source(codec, mixer, CFG_IDX_MIX, num_adcs, + "Stereo Mix", 0); + if (err < 0) + return err; + else + spec->suppress_auto_mic = 1; + } + + return 0; +} + + +/* + * input source mux + */ + +/* get the input path specified by the given adc and imux indices */ +static struct nid_path *get_input_path(struct hda_codec *codec, int adc_idx, int imux_idx) +{ + struct hda_gen_spec *spec = codec->spec; + if (imux_idx < 0 || imux_idx >= HDA_MAX_NUM_INPUTS) { + snd_BUG(); + return NULL; + } + if (spec->dyn_adc_switch) + adc_idx = spec->dyn_adc_idx[imux_idx]; + if (adc_idx < 0 || adc_idx >= AUTO_CFG_MAX_INS) { + snd_BUG(); + return NULL; + } + return snd_hda_get_path_from_idx(codec, spec->input_paths[imux_idx][adc_idx]); +} + +static int mux_select(struct hda_codec *codec, unsigned int adc_idx, + unsigned int idx); + +static int mux_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_gen_spec *spec = codec->spec; + return snd_hda_input_mux_info(&spec->input_mux, uinfo); +} + +static int mux_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_gen_spec *spec = codec->spec; + /* the ctls are created at once with multiple counts */ + unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + + ucontrol->value.enumerated.item[0] = spec->cur_mux[adc_idx]; + return 0; +} + +static int mux_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + return mux_select(codec, adc_idx, + ucontrol->value.enumerated.item[0]); +} + +static const struct snd_kcontrol_new cap_src_temp = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Source", + .info = mux_enum_info, + .get = mux_enum_get, + .put = mux_enum_put, +}; + +/* + * capture volume and capture switch ctls + */ + +typedef int (*put_call_t)(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +/* call the given amp update function for all amps in the imux list at once */ +static int cap_put_caller(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, + put_call_t func, int type) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_gen_spec *spec = codec->spec; + const struct hda_input_mux *imux; + struct nid_path *path; + int i, adc_idx, ret, err = 0; + + imux = &spec->input_mux; + adc_idx = kcontrol->id.index; + mutex_lock(&codec->control_mutex); + for (i = 0; i < imux->num_items; i++) { + path = get_input_path(codec, adc_idx, i); + if (!path || !path->ctls[type]) + continue; + kcontrol->private_value = path->ctls[type]; + ret = func(kcontrol, ucontrol); + if (ret < 0) { + err = ret; + break; + } + if (ret > 0) + err = 1; + } + mutex_unlock(&codec->control_mutex); + if (err >= 0 && spec->cap_sync_hook) + spec->cap_sync_hook(codec, kcontrol, ucontrol); + return err; +} + +/* capture volume ctl callbacks */ +#define cap_vol_info snd_hda_mixer_amp_volume_info +#define cap_vol_get snd_hda_mixer_amp_volume_get +#define cap_vol_tlv snd_hda_mixer_amp_tlv + +static int cap_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return cap_put_caller(kcontrol, ucontrol, + snd_hda_mixer_amp_volume_put, + NID_PATH_VOL_CTL); +} + +static const struct snd_kcontrol_new cap_vol_temp = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Volume", + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK), + .info = cap_vol_info, + .get = cap_vol_get, + .put = cap_vol_put, + .tlv = { .c = cap_vol_tlv }, +}; + +/* capture switch ctl callbacks */ +#define cap_sw_info snd_ctl_boolean_stereo_info +#define cap_sw_get snd_hda_mixer_amp_switch_get + +static int cap_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return cap_put_caller(kcontrol, ucontrol, + snd_hda_mixer_amp_switch_put, + NID_PATH_MUTE_CTL); +} + +static const struct snd_kcontrol_new cap_sw_temp = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Switch", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = cap_sw_info, + .get = cap_sw_get, + .put = cap_sw_put, +}; + +static int parse_capvol_in_path(struct hda_codec *codec, struct nid_path *path) +{ + hda_nid_t nid; + int i, depth; + + path->ctls[NID_PATH_VOL_CTL] = path->ctls[NID_PATH_MUTE_CTL] = 0; + for (depth = 0; depth < 3; depth++) { + if (depth >= path->depth) + return -EINVAL; + i = path->depth - depth - 1; + nid = path->path[i]; + if (!path->ctls[NID_PATH_VOL_CTL]) { + if (nid_has_volume(codec, nid, HDA_OUTPUT)) + path->ctls[NID_PATH_VOL_CTL] = + HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT); + else if (nid_has_volume(codec, nid, HDA_INPUT)) { + int idx = path->idx[i]; + if (!depth && codec->single_adc_amp) + idx = 0; + path->ctls[NID_PATH_VOL_CTL] = + HDA_COMPOSE_AMP_VAL(nid, 3, idx, HDA_INPUT); + } + } + if (!path->ctls[NID_PATH_MUTE_CTL]) { + if (nid_has_mute(codec, nid, HDA_OUTPUT)) + path->ctls[NID_PATH_MUTE_CTL] = + HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT); + else if (nid_has_mute(codec, nid, HDA_INPUT)) { + int idx = path->idx[i]; + if (!depth && codec->single_adc_amp) + idx = 0; + path->ctls[NID_PATH_MUTE_CTL] = + HDA_COMPOSE_AMP_VAL(nid, 3, idx, HDA_INPUT); + } + } + } + return 0; +} + +static bool is_inv_dmic_pin(struct hda_codec *codec, hda_nid_t nid) +{ + struct hda_gen_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + unsigned int val; + int i; + + if (!spec->inv_dmic_split) + return false; + for (i = 0; i < cfg->num_inputs; i++) { + if (cfg->inputs[i].pin != nid) + continue; + if (cfg->inputs[i].type != AUTO_PIN_MIC) + return false; + val = snd_hda_codec_get_pincfg(codec, nid); + return snd_hda_get_input_pin_attr(val) == INPUT_PIN_ATTR_INT; + } + return false; +} + +/* capture switch put callback for a single control with hook call */ +static int cap_single_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_gen_spec *spec = codec->spec; + int ret; + + ret = snd_hda_mixer_amp_switch_put(kcontrol, ucontrol); + if (ret < 0) + return ret; + + if (spec->cap_sync_hook) + spec->cap_sync_hook(codec, kcontrol, ucontrol); + + return ret; +} + +static int add_single_cap_ctl(struct hda_codec *codec, const char *label, + int idx, bool is_switch, unsigned int ctl, + bool inv_dmic) +{ + struct hda_gen_spec *spec = codec->spec; + char tmpname[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + int type = is_switch ? HDA_CTL_WIDGET_MUTE : HDA_CTL_WIDGET_VOL; + const char *sfx = is_switch ? "Switch" : "Volume"; + unsigned int chs = inv_dmic ? 1 : 3; + struct snd_kcontrol_new *knew; + + if (!ctl) + return 0; + + if (label) + snprintf(tmpname, sizeof(tmpname), + "%s Capture %s", label, sfx); + else + snprintf(tmpname, sizeof(tmpname), + "Capture %s", sfx); + knew = add_control(spec, type, tmpname, idx, + amp_val_replace_channels(ctl, chs)); + if (!knew) + return -ENOMEM; + if (is_switch) { + knew->put = cap_single_sw_put; + if (spec->mic_mute_led) + knew->access |= SNDRV_CTL_ELEM_ACCESS_MIC_LED; + } + if (!inv_dmic) + return 0; + + /* Make independent right kcontrol */ + if (label) + snprintf(tmpname, sizeof(tmpname), + "Inverted %s Capture %s", label, sfx); + else + snprintf(tmpname, sizeof(tmpname), + "Inverted Capture %s", sfx); + knew = add_control(spec, type, tmpname, idx, + amp_val_replace_channels(ctl, 2)); + if (!knew) + return -ENOMEM; + if (is_switch) { + knew->put = cap_single_sw_put; + if (spec->mic_mute_led) + knew->access |= SNDRV_CTL_ELEM_ACCESS_MIC_LED; + } + return 0; +} + +/* create single (and simple) capture volume and switch controls */ +static int create_single_cap_vol_ctl(struct hda_codec *codec, int idx, + unsigned int vol_ctl, unsigned int sw_ctl, + bool inv_dmic) +{ + int err; + err = add_single_cap_ctl(codec, NULL, idx, false, vol_ctl, inv_dmic); + if (err < 0) + return err; + err = add_single_cap_ctl(codec, NULL, idx, true, sw_ctl, inv_dmic); + if (err < 0) + return err; + return 0; +} + +/* create bound capture volume and switch controls */ +static int create_bind_cap_vol_ctl(struct hda_codec *codec, int idx, + unsigned int vol_ctl, unsigned int sw_ctl) +{ + struct hda_gen_spec *spec = codec->spec; + struct snd_kcontrol_new *knew; + + if (vol_ctl) { + knew = snd_hda_gen_add_kctl(spec, NULL, &cap_vol_temp); + if (!knew) + return -ENOMEM; + knew->index = idx; + knew->private_value = vol_ctl; + knew->subdevice = HDA_SUBDEV_AMP_FLAG; + } + if (sw_ctl) { + knew = snd_hda_gen_add_kctl(spec, NULL, &cap_sw_temp); + if (!knew) + return -ENOMEM; + knew->index = idx; + knew->private_value = sw_ctl; + knew->subdevice = HDA_SUBDEV_AMP_FLAG; + if (spec->mic_mute_led) + knew->access |= SNDRV_CTL_ELEM_ACCESS_MIC_LED; + } + return 0; +} + +/* return the vol ctl when used first in the imux list */ +static unsigned int get_first_cap_ctl(struct hda_codec *codec, int idx, int type) +{ + struct nid_path *path; + unsigned int ctl; + int i; + + path = get_input_path(codec, 0, idx); + if (!path) + return 0; + ctl = path->ctls[type]; + if (!ctl) + return 0; + for (i = 0; i < idx - 1; i++) { + path = get_input_path(codec, 0, i); + if (path && path->ctls[type] == ctl) + return 0; + } + return ctl; +} + +/* create individual capture volume and switch controls per input */ +static int create_multi_cap_vol_ctl(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + struct hda_input_mux *imux = &spec->input_mux; + int i, err, type; + + for (i = 0; i < imux->num_items; i++) { + bool inv_dmic; + int idx; + + idx = imux->items[i].index; + if (idx >= spec->autocfg.num_inputs) + continue; + inv_dmic = is_inv_dmic_pin(codec, spec->imux_pins[i]); + + for (type = 0; type < 2; type++) { + err = add_single_cap_ctl(codec, + spec->input_labels[idx], + spec->input_label_idxs[idx], + type, + get_first_cap_ctl(codec, i, type), + inv_dmic); + if (err < 0) + return err; + } + } + return 0; +} + +static int create_capture_mixers(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + struct hda_input_mux *imux = &spec->input_mux; + int i, n, nums, err; + + if (spec->dyn_adc_switch) + nums = 1; + else + nums = spec->num_adc_nids; + + if (!spec->auto_mic && imux->num_items > 1) { + struct snd_kcontrol_new *knew; + const char *name; + name = nums > 1 ? "Input Source" : "Capture Source"; + knew = snd_hda_gen_add_kctl(spec, name, &cap_src_temp); + if (!knew) + return -ENOMEM; + knew->count = nums; + } + + for (n = 0; n < nums; n++) { + bool multi = false; + bool multi_cap_vol = spec->multi_cap_vol; + bool inv_dmic = false; + int vol, sw; + + vol = sw = 0; + for (i = 0; i < imux->num_items; i++) { + struct nid_path *path; + path = get_input_path(codec, n, i); + if (!path) + continue; + parse_capvol_in_path(codec, path); + if (!vol) + vol = path->ctls[NID_PATH_VOL_CTL]; + else if (vol != path->ctls[NID_PATH_VOL_CTL]) { + multi = true; + if (!same_amp_caps(codec, vol, + path->ctls[NID_PATH_VOL_CTL], HDA_INPUT)) + multi_cap_vol = true; + } + if (!sw) + sw = path->ctls[NID_PATH_MUTE_CTL]; + else if (sw != path->ctls[NID_PATH_MUTE_CTL]) { + multi = true; + if (!same_amp_caps(codec, sw, + path->ctls[NID_PATH_MUTE_CTL], HDA_INPUT)) + multi_cap_vol = true; + } + if (is_inv_dmic_pin(codec, spec->imux_pins[i])) + inv_dmic = true; + } + + if (!multi) + err = create_single_cap_vol_ctl(codec, n, vol, sw, + inv_dmic); + else if (!multi_cap_vol && !inv_dmic) + err = create_bind_cap_vol_ctl(codec, n, vol, sw); + else + err = create_multi_cap_vol_ctl(codec); + if (err < 0) + return err; + } + + return 0; +} + +/* + * add mic boosts if needed + */ + +/* check whether the given amp is feasible as a boost volume */ +static bool check_boost_vol(struct hda_codec *codec, hda_nid_t nid, + int dir, int idx) +{ + unsigned int step; + + if (!nid_has_volume(codec, nid, dir) || + is_ctl_associated(codec, nid, dir, idx, NID_PATH_VOL_CTL) || + is_ctl_associated(codec, nid, dir, idx, NID_PATH_BOOST_CTL)) + return false; + + step = (query_amp_caps(codec, nid, dir) & AC_AMPCAP_STEP_SIZE) + >> AC_AMPCAP_STEP_SIZE_SHIFT; + if (step < 0x20) + return false; + return true; +} + +/* look for a boost amp in a widget close to the pin */ +static unsigned int look_for_boost_amp(struct hda_codec *codec, + struct nid_path *path) +{ + unsigned int val = 0; + hda_nid_t nid; + int depth; + + for (depth = 0; depth < 3; depth++) { + if (depth >= path->depth - 1) + break; + nid = path->path[depth]; + if (depth && check_boost_vol(codec, nid, HDA_OUTPUT, 0)) { + val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT); + break; + } else if (check_boost_vol(codec, nid, HDA_INPUT, + path->idx[depth])) { + val = HDA_COMPOSE_AMP_VAL(nid, 3, path->idx[depth], + HDA_INPUT); + break; + } + } + + return val; +} + +static int parse_mic_boost(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + struct hda_input_mux *imux = &spec->input_mux; + int i; + + if (!spec->num_adc_nids) + return 0; + + for (i = 0; i < imux->num_items; i++) { + struct nid_path *path; + unsigned int val; + int idx; + char boost_label[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + + idx = imux->items[i].index; + if (idx >= imux->num_items) + continue; + + /* check only line-in and mic pins */ + if (cfg->inputs[idx].type > AUTO_PIN_LINE_IN) + continue; + + path = get_input_path(codec, 0, i); + if (!path) + continue; + + val = look_for_boost_amp(codec, path); + if (!val) + continue; + + /* create a boost control */ + snprintf(boost_label, sizeof(boost_label), + "%s Boost Volume", spec->input_labels[idx]); + if (!add_control(spec, HDA_CTL_WIDGET_VOL, boost_label, + spec->input_label_idxs[idx], val)) + return -ENOMEM; + + path->ctls[NID_PATH_BOOST_CTL] = val; + } + return 0; +} + +#ifdef CONFIG_SND_HDA_GENERIC_LEDS +/* + * vmaster mute LED hook helpers + */ + +static int create_mute_led_cdev(struct hda_codec *codec, + int (*callback)(struct led_classdev *, + enum led_brightness), + bool micmute) +{ + struct hda_gen_spec *spec = codec->spec; + struct led_classdev *cdev; + int idx = micmute ? LED_AUDIO_MICMUTE : LED_AUDIO_MUTE; + int err; + + cdev = devm_kzalloc(&codec->core.dev, sizeof(*cdev), GFP_KERNEL); + if (!cdev) + return -ENOMEM; + + cdev->name = micmute ? "hda::micmute" : "hda::mute"; + cdev->max_brightness = 1; + cdev->default_trigger = micmute ? "audio-micmute" : "audio-mute"; + cdev->brightness_set_blocking = callback; + cdev->brightness = ledtrig_audio_get(idx); + cdev->flags = LED_CORE_SUSPENDRESUME; + + err = led_classdev_register(&codec->core.dev, cdev); + if (err < 0) + return err; + spec->led_cdevs[idx] = cdev; + return 0; +} + +/** + * snd_hda_gen_add_mute_led_cdev - Create a LED classdev and enable as vmaster mute LED + * @codec: the HDA codec + * @callback: the callback for LED classdev brightness_set_blocking + */ +int snd_hda_gen_add_mute_led_cdev(struct hda_codec *codec, + int (*callback)(struct led_classdev *, + enum led_brightness)) +{ + struct hda_gen_spec *spec = codec->spec; + int err; + + if (callback) { + err = create_mute_led_cdev(codec, callback, false); + if (err) { + codec_warn(codec, "failed to create a mute LED cdev\n"); + return err; + } + } + + if (spec->vmaster_mute.hook) + codec_err(codec, "vmaster hook already present before cdev!\n"); + + spec->vmaster_mute_led = 1; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_gen_add_mute_led_cdev); + +/** + * snd_hda_gen_add_micmute_led_cdev - Create a LED classdev and enable as mic-mute LED + * @codec: the HDA codec + * @callback: the callback for LED classdev brightness_set_blocking + * + * Called from the codec drivers for offering the mic mute LED controls. + * This creates a LED classdev and sets up the cap_sync_hook that is called at + * each time when the capture mixer switch changes. + * + * When NULL is passed to @callback, no classdev is created but only the + * LED-trigger is set up. + * + * Returns 0 or a negative error. + */ +int snd_hda_gen_add_micmute_led_cdev(struct hda_codec *codec, + int (*callback)(struct led_classdev *, + enum led_brightness)) +{ + struct hda_gen_spec *spec = codec->spec; + int err; + + if (callback) { + err = create_mute_led_cdev(codec, callback, true); + if (err) { + codec_warn(codec, "failed to create a mic-mute LED cdev\n"); + return err; + } + } + + spec->mic_mute_led = 1; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_gen_add_micmute_led_cdev); +#endif /* CONFIG_SND_HDA_GENERIC_LEDS */ + +/* + * parse digital I/Os and set up NIDs in BIOS auto-parse mode + */ +static void parse_digital(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + struct nid_path *path; + int i, nums; + hda_nid_t dig_nid, pin; + + /* support multiple SPDIFs; the secondary is set up as a follower */ + nums = 0; + for (i = 0; i < spec->autocfg.dig_outs; i++) { + pin = spec->autocfg.dig_out_pins[i]; + dig_nid = look_for_dac(codec, pin, true); + if (!dig_nid) + continue; + path = snd_hda_add_new_path(codec, dig_nid, pin, 0); + if (!path) + continue; + print_nid_path(codec, "digout", path); + path->active = true; + path->pin_fixed = true; /* no jack detection */ + spec->digout_paths[i] = snd_hda_get_path_idx(codec, path); + set_pin_target(codec, pin, PIN_OUT, false); + if (!nums) { + spec->multiout.dig_out_nid = dig_nid; + spec->dig_out_type = spec->autocfg.dig_out_type[0]; + } else { + spec->multiout.follower_dig_outs = spec->follower_dig_outs; + if (nums >= ARRAY_SIZE(spec->follower_dig_outs) - 1) + break; + spec->follower_dig_outs[nums - 1] = dig_nid; + } + nums++; + } + + if (spec->autocfg.dig_in_pin) { + pin = spec->autocfg.dig_in_pin; + for_each_hda_codec_node(dig_nid, codec) { + unsigned int wcaps = get_wcaps(codec, dig_nid); + if (get_wcaps_type(wcaps) != AC_WID_AUD_IN) + continue; + if (!(wcaps & AC_WCAP_DIGITAL)) + continue; + path = snd_hda_add_new_path(codec, pin, dig_nid, 0); + if (path) { + print_nid_path(codec, "digin", path); + path->active = true; + path->pin_fixed = true; /* no jack */ + spec->dig_in_nid = dig_nid; + spec->digin_path = snd_hda_get_path_idx(codec, path); + set_pin_target(codec, pin, PIN_IN, false); + break; + } + } + } +} + + +/* + * input MUX handling + */ + +static bool dyn_adc_pcm_resetup(struct hda_codec *codec, int cur); + +/* select the given imux item; either unmute exclusively or select the route */ +static int mux_select(struct hda_codec *codec, unsigned int adc_idx, + unsigned int idx) +{ + struct hda_gen_spec *spec = codec->spec; + const struct hda_input_mux *imux; + struct nid_path *old_path, *path; + + imux = &spec->input_mux; + if (!imux->num_items) + return 0; + + if (idx >= imux->num_items) + idx = imux->num_items - 1; + if (spec->cur_mux[adc_idx] == idx) + return 0; + + old_path = get_input_path(codec, adc_idx, spec->cur_mux[adc_idx]); + if (!old_path) + return 0; + if (old_path->active) + snd_hda_activate_path(codec, old_path, false, false); + + spec->cur_mux[adc_idx] = idx; + + if (spec->hp_mic) + update_hp_mic(codec, adc_idx, false); + + if (spec->dyn_adc_switch) + dyn_adc_pcm_resetup(codec, idx); + + path = get_input_path(codec, adc_idx, idx); + if (!path) + return 0; + if (path->active) + return 0; + snd_hda_activate_path(codec, path, true, false); + if (spec->cap_sync_hook) + spec->cap_sync_hook(codec, NULL, NULL); + path_power_down_sync(codec, old_path); + return 1; +} + +/* power up/down widgets in the all paths that match with the given NID + * as terminals (either start- or endpoint) + * + * returns the last changed NID, or zero if unchanged. + */ +static hda_nid_t set_path_power(struct hda_codec *codec, hda_nid_t nid, + int pin_state, int stream_state) +{ + struct hda_gen_spec *spec = codec->spec; + hda_nid_t last, changed = 0; + struct nid_path *path; + int n; + + snd_array_for_each(&spec->paths, n, path) { + if (!path->depth) + continue; + if (path->path[0] == nid || + path->path[path->depth - 1] == nid) { + bool pin_old = path->pin_enabled; + bool stream_old = path->stream_enabled; + + if (pin_state >= 0) + path->pin_enabled = pin_state; + if (stream_state >= 0) + path->stream_enabled = stream_state; + if ((!path->pin_fixed && path->pin_enabled != pin_old) + || path->stream_enabled != stream_old) { + last = path_power_update(codec, path, true); + if (last) + changed = last; + } + } + } + return changed; +} + +/* check the jack status for power control */ +static bool detect_pin_state(struct hda_codec *codec, hda_nid_t pin) +{ + if (!is_jack_detectable(codec, pin)) + return true; + return snd_hda_jack_detect_state(codec, pin) != HDA_JACK_NOT_PRESENT; +} + +/* power up/down the paths of the given pin according to the jack state; + * power = 0/1 : only power up/down if it matches with the jack state, + * < 0 : force power up/down to follow the jack sate + * + * returns the last changed NID, or zero if unchanged. + */ +static hda_nid_t set_pin_power_jack(struct hda_codec *codec, hda_nid_t pin, + int power) +{ + bool on; + + if (!codec->power_save_node) + return 0; + + on = detect_pin_state(codec, pin); + + if (power >= 0 && on != power) + return 0; + return set_path_power(codec, pin, on, -1); +} + +static void pin_power_callback(struct hda_codec *codec, + struct hda_jack_callback *jack, + bool on) +{ + if (jack && jack->nid) + sync_power_state_change(codec, + set_pin_power_jack(codec, jack->nid, on)); +} + +/* callback only doing power up -- called at first */ +static void pin_power_up_callback(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + pin_power_callback(codec, jack, true); +} + +/* callback only doing power down -- called at last */ +static void pin_power_down_callback(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + pin_power_callback(codec, jack, false); +} + +/* set up the power up/down callbacks */ +static void add_pin_power_ctls(struct hda_codec *codec, int num_pins, + const hda_nid_t *pins, bool on) +{ + int i; + hda_jack_callback_fn cb = + on ? pin_power_up_callback : pin_power_down_callback; + + for (i = 0; i < num_pins && pins[i]; i++) { + if (is_jack_detectable(codec, pins[i])) + snd_hda_jack_detect_enable_callback(codec, pins[i], cb); + else + set_path_power(codec, pins[i], true, -1); + } +} + +/* enabled power callback to each available I/O pin with jack detections; + * the digital I/O pins are excluded because of the unreliable detectsion + */ +static void add_all_pin_power_ctls(struct hda_codec *codec, bool on) +{ + struct hda_gen_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + int i; + + if (!codec->power_save_node) + return; + add_pin_power_ctls(codec, cfg->line_outs, cfg->line_out_pins, on); + if (cfg->line_out_type != AUTO_PIN_HP_OUT) + add_pin_power_ctls(codec, cfg->hp_outs, cfg->hp_pins, on); + if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) + add_pin_power_ctls(codec, cfg->speaker_outs, cfg->speaker_pins, on); + for (i = 0; i < cfg->num_inputs; i++) + add_pin_power_ctls(codec, 1, &cfg->inputs[i].pin, on); +} + +/* sync path power up/down with the jack states of given pins */ +static void sync_pin_power_ctls(struct hda_codec *codec, int num_pins, + const hda_nid_t *pins) +{ + int i; + + for (i = 0; i < num_pins && pins[i]; i++) + if (is_jack_detectable(codec, pins[i])) + set_pin_power_jack(codec, pins[i], -1); +} + +/* sync path power up/down with pins; called at init and resume */ +static void sync_all_pin_power_ctls(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + int i; + + if (!codec->power_save_node) + return; + sync_pin_power_ctls(codec, cfg->line_outs, cfg->line_out_pins); + if (cfg->line_out_type != AUTO_PIN_HP_OUT) + sync_pin_power_ctls(codec, cfg->hp_outs, cfg->hp_pins); + if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) + sync_pin_power_ctls(codec, cfg->speaker_outs, cfg->speaker_pins); + for (i = 0; i < cfg->num_inputs; i++) + sync_pin_power_ctls(codec, 1, &cfg->inputs[i].pin); +} + +/* add fake paths if not present yet */ +static int add_fake_paths(struct hda_codec *codec, hda_nid_t nid, + int num_pins, const hda_nid_t *pins) +{ + struct hda_gen_spec *spec = codec->spec; + struct nid_path *path; + int i; + + for (i = 0; i < num_pins; i++) { + if (!pins[i]) + break; + if (get_nid_path(codec, nid, pins[i], 0)) + continue; + path = snd_array_new(&spec->paths); + if (!path) + return -ENOMEM; + memset(path, 0, sizeof(*path)); + path->depth = 2; + path->path[0] = nid; + path->path[1] = pins[i]; + path->active = true; + } + return 0; +} + +/* create fake paths to all outputs from beep */ +static int add_fake_beep_paths(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + hda_nid_t nid = spec->beep_nid; + int err; + + if (!codec->power_save_node || !nid) + return 0; + err = add_fake_paths(codec, nid, cfg->line_outs, cfg->line_out_pins); + if (err < 0) + return err; + if (cfg->line_out_type != AUTO_PIN_HP_OUT) { + err = add_fake_paths(codec, nid, cfg->hp_outs, cfg->hp_pins); + if (err < 0) + return err; + } + if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) { + err = add_fake_paths(codec, nid, cfg->speaker_outs, + cfg->speaker_pins); + if (err < 0) + return err; + } + return 0; +} + +/* power up/down beep widget and its output paths */ +static void beep_power_hook(struct hda_beep *beep, bool on) +{ + set_path_power(beep->codec, beep->nid, -1, on); +} + +/** + * snd_hda_gen_fix_pin_power - Fix the power of the given pin widget to D0 + * @codec: the HDA codec + * @pin: NID of pin to fix + */ +int snd_hda_gen_fix_pin_power(struct hda_codec *codec, hda_nid_t pin) +{ + struct hda_gen_spec *spec = codec->spec; + struct nid_path *path; + + path = snd_array_new(&spec->paths); + if (!path) + return -ENOMEM; + memset(path, 0, sizeof(*path)); + path->depth = 1; + path->path[0] = pin; + path->active = true; + path->pin_fixed = true; + path->stream_enabled = true; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_gen_fix_pin_power); + +/* + * Jack detections for HP auto-mute and mic-switch + */ + +/* check each pin in the given array; returns true if any of them is plugged */ +static bool detect_jacks(struct hda_codec *codec, int num_pins, const hda_nid_t *pins) +{ + int i; + bool present = false; + + for (i = 0; i < num_pins; i++) { + hda_nid_t nid = pins[i]; + if (!nid) + break; + /* don't detect pins retasked as inputs */ + if (snd_hda_codec_get_pin_target(codec, nid) & AC_PINCTL_IN_EN) + continue; + if (snd_hda_jack_detect_state(codec, nid) == HDA_JACK_PRESENT) + present = true; + } + return present; +} + +/* standard HP/line-out auto-mute helper */ +static void do_automute(struct hda_codec *codec, int num_pins, const hda_nid_t *pins, + int *paths, bool mute) +{ + struct hda_gen_spec *spec = codec->spec; + int i; + + for (i = 0; i < num_pins; i++) { + hda_nid_t nid = pins[i]; + unsigned int val, oldval; + if (!nid) + break; + + oldval = snd_hda_codec_get_pin_target(codec, nid); + if (oldval & PIN_IN) + continue; /* no mute for inputs */ + + if (spec->auto_mute_via_amp) { + struct nid_path *path; + hda_nid_t mute_nid; + + path = snd_hda_get_path_from_idx(codec, paths[i]); + if (!path) + continue; + mute_nid = get_amp_nid_(path->ctls[NID_PATH_MUTE_CTL]); + if (!mute_nid) + continue; + if (mute) + spec->mute_bits |= (1ULL << mute_nid); + else + spec->mute_bits &= ~(1ULL << mute_nid); + continue; + } else { + /* don't reset VREF value in case it's controlling + * the amp (see alc861_fixup_asus_amp_vref_0f()) + */ + if (spec->keep_vref_in_automute) + val = oldval & ~PIN_HP; + else + val = 0; + if (!mute) + val |= oldval; + /* here we call update_pin_ctl() so that the pinctl is + * changed without changing the pinctl target value; + * the original target value will be still referred at + * the init / resume again + */ + update_pin_ctl(codec, nid, val); + } + + set_pin_eapd(codec, nid, !mute); + if (codec->power_save_node) { + bool on = !mute; + if (on) + on = detect_pin_state(codec, nid); + set_path_power(codec, nid, on, -1); + } + } +} + +/** + * snd_hda_gen_update_outputs - Toggle outputs muting + * @codec: the HDA codec + * + * Update the mute status of all outputs based on the current jack states. + */ +void snd_hda_gen_update_outputs(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + int *paths; + int on; + + /* Control HP pins/amps depending on master_mute state; + * in general, HP pins/amps control should be enabled in all cases, + * but currently set only for master_mute, just to be safe + */ + if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT) + paths = spec->out_paths; + else + paths = spec->hp_paths; + do_automute(codec, ARRAY_SIZE(spec->autocfg.hp_pins), + spec->autocfg.hp_pins, paths, spec->master_mute); + + if (!spec->automute_speaker) + on = 0; + else + on = spec->hp_jack_present | spec->line_jack_present; + on |= spec->master_mute; + spec->speaker_muted = on; + if (spec->autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT) + paths = spec->out_paths; + else + paths = spec->speaker_paths; + do_automute(codec, ARRAY_SIZE(spec->autocfg.speaker_pins), + spec->autocfg.speaker_pins, paths, on); + + /* toggle line-out mutes if needed, too */ + /* if LO is a copy of either HP or Speaker, don't need to handle it */ + if (spec->autocfg.line_out_pins[0] == spec->autocfg.hp_pins[0] || + spec->autocfg.line_out_pins[0] == spec->autocfg.speaker_pins[0]) + return; + if (!spec->automute_lo) + on = 0; + else + on = spec->hp_jack_present; + on |= spec->master_mute; + spec->line_out_muted = on; + paths = spec->out_paths; + do_automute(codec, ARRAY_SIZE(spec->autocfg.line_out_pins), + spec->autocfg.line_out_pins, paths, on); +} +EXPORT_SYMBOL_GPL(snd_hda_gen_update_outputs); + +static void call_update_outputs(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + if (spec->automute_hook) + spec->automute_hook(codec); + else + snd_hda_gen_update_outputs(codec); + + /* sync the whole vmaster followers to reflect the new auto-mute status */ + if (spec->auto_mute_via_amp && !codec->bus->shutdown) + snd_ctl_sync_vmaster(spec->vmaster_mute.sw_kctl, false); +} + +/** + * snd_hda_gen_hp_automute - standard HP-automute helper + * @codec: the HDA codec + * @jack: jack object, NULL for the whole + */ +void snd_hda_gen_hp_automute(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + struct hda_gen_spec *spec = codec->spec; + hda_nid_t *pins = spec->autocfg.hp_pins; + int num_pins = ARRAY_SIZE(spec->autocfg.hp_pins); + + /* No detection for the first HP jack during indep-HP mode */ + if (spec->indep_hp_enabled) { + pins++; + num_pins--; + } + + spec->hp_jack_present = detect_jacks(codec, num_pins, pins); + if (!spec->detect_hp || (!spec->automute_speaker && !spec->automute_lo)) + return; + call_update_outputs(codec); +} +EXPORT_SYMBOL_GPL(snd_hda_gen_hp_automute); + +/** + * snd_hda_gen_line_automute - standard line-out-automute helper + * @codec: the HDA codec + * @jack: jack object, NULL for the whole + */ +void snd_hda_gen_line_automute(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + struct hda_gen_spec *spec = codec->spec; + + if (spec->autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT) + return; + /* check LO jack only when it's different from HP */ + if (spec->autocfg.line_out_pins[0] == spec->autocfg.hp_pins[0]) + return; + + spec->line_jack_present = + detect_jacks(codec, ARRAY_SIZE(spec->autocfg.line_out_pins), + spec->autocfg.line_out_pins); + if (!spec->automute_speaker || !spec->detect_lo) + return; + call_update_outputs(codec); +} +EXPORT_SYMBOL_GPL(snd_hda_gen_line_automute); + +/** + * snd_hda_gen_mic_autoswitch - standard mic auto-switch helper + * @codec: the HDA codec + * @jack: jack object, NULL for the whole + */ +void snd_hda_gen_mic_autoswitch(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + struct hda_gen_spec *spec = codec->spec; + int i; + + if (!spec->auto_mic) + return; + + for (i = spec->am_num_entries - 1; i > 0; i--) { + hda_nid_t pin = spec->am_entry[i].pin; + /* don't detect pins retasked as outputs */ + if (snd_hda_codec_get_pin_target(codec, pin) & AC_PINCTL_OUT_EN) + continue; + if (snd_hda_jack_detect_state(codec, pin) == HDA_JACK_PRESENT) { + mux_select(codec, 0, spec->am_entry[i].idx); + return; + } + } + mux_select(codec, 0, spec->am_entry[0].idx); +} +EXPORT_SYMBOL_GPL(snd_hda_gen_mic_autoswitch); + +/* call appropriate hooks */ +static void call_hp_automute(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + struct hda_gen_spec *spec = codec->spec; + if (spec->hp_automute_hook) + spec->hp_automute_hook(codec, jack); + else + snd_hda_gen_hp_automute(codec, jack); +} + +static void call_line_automute(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + struct hda_gen_spec *spec = codec->spec; + if (spec->line_automute_hook) + spec->line_automute_hook(codec, jack); + else + snd_hda_gen_line_automute(codec, jack); +} + +static void call_mic_autoswitch(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + struct hda_gen_spec *spec = codec->spec; + if (spec->mic_autoswitch_hook) + spec->mic_autoswitch_hook(codec, jack); + else + snd_hda_gen_mic_autoswitch(codec, jack); +} + +/* update jack retasking */ +static void update_automute_all(struct hda_codec *codec) +{ + call_hp_automute(codec, NULL); + call_line_automute(codec, NULL); + call_mic_autoswitch(codec, NULL); +} + +/* + * Auto-Mute mode mixer enum support + */ +static int automute_mode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_gen_spec *spec = codec->spec; + static const char * const texts3[] = { + "Disabled", "Speaker Only", "Line Out+Speaker" + }; + + if (spec->automute_speaker_possible && spec->automute_lo_possible) + return snd_hda_enum_helper_info(kcontrol, uinfo, 3, texts3); + return snd_hda_enum_bool_helper_info(kcontrol, uinfo); +} + +static int automute_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_gen_spec *spec = codec->spec; + unsigned int val = 0; + if (spec->automute_speaker) + val++; + if (spec->automute_lo) + val++; + + ucontrol->value.enumerated.item[0] = val; + return 0; +} + +static int automute_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_gen_spec *spec = codec->spec; + + switch (ucontrol->value.enumerated.item[0]) { + case 0: + if (!spec->automute_speaker && !spec->automute_lo) + return 0; + spec->automute_speaker = 0; + spec->automute_lo = 0; + break; + case 1: + if (spec->automute_speaker_possible) { + if (!spec->automute_lo && spec->automute_speaker) + return 0; + spec->automute_speaker = 1; + spec->automute_lo = 0; + } else if (spec->automute_lo_possible) { + if (spec->automute_lo) + return 0; + spec->automute_lo = 1; + } else + return -EINVAL; + break; + case 2: + if (!spec->automute_lo_possible || !spec->automute_speaker_possible) + return -EINVAL; + if (spec->automute_speaker && spec->automute_lo) + return 0; + spec->automute_speaker = 1; + spec->automute_lo = 1; + break; + default: + return -EINVAL; + } + call_update_outputs(codec); + return 1; +} + +static const struct snd_kcontrol_new automute_mode_enum = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Auto-Mute Mode", + .info = automute_mode_info, + .get = automute_mode_get, + .put = automute_mode_put, +}; + +static int add_automute_mode_enum(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + + if (!snd_hda_gen_add_kctl(spec, NULL, &automute_mode_enum)) + return -ENOMEM; + return 0; +} + +/* + * Check the availability of HP/line-out auto-mute; + * Set up appropriately if really supported + */ +static int check_auto_mute_availability(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + int present = 0; + int i, err; + + if (spec->suppress_auto_mute) + return 0; + + if (cfg->hp_pins[0]) + present++; + if (cfg->line_out_pins[0]) + present++; + if (cfg->speaker_pins[0]) + present++; + if (present < 2) /* need two different output types */ + return 0; + + if (!cfg->speaker_pins[0] && + cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) { + memcpy(cfg->speaker_pins, cfg->line_out_pins, + sizeof(cfg->speaker_pins)); + cfg->speaker_outs = cfg->line_outs; + } + + if (!cfg->hp_pins[0] && + cfg->line_out_type == AUTO_PIN_HP_OUT) { + memcpy(cfg->hp_pins, cfg->line_out_pins, + sizeof(cfg->hp_pins)); + cfg->hp_outs = cfg->line_outs; + } + + for (i = 0; i < cfg->hp_outs; i++) { + hda_nid_t nid = cfg->hp_pins[i]; + if (!is_jack_detectable(codec, nid)) + continue; + codec_dbg(codec, "Enable HP auto-muting on NID 0x%x\n", nid); + snd_hda_jack_detect_enable_callback(codec, nid, + call_hp_automute); + spec->detect_hp = 1; + } + + if (cfg->line_out_type == AUTO_PIN_LINE_OUT && cfg->line_outs) { + if (cfg->speaker_outs) + for (i = 0; i < cfg->line_outs; i++) { + hda_nid_t nid = cfg->line_out_pins[i]; + if (!is_jack_detectable(codec, nid)) + continue; + codec_dbg(codec, "Enable Line-Out auto-muting on NID 0x%x\n", nid); + snd_hda_jack_detect_enable_callback(codec, nid, + call_line_automute); + spec->detect_lo = 1; + } + spec->automute_lo_possible = spec->detect_hp; + } + + spec->automute_speaker_possible = cfg->speaker_outs && + (spec->detect_hp || spec->detect_lo); + + spec->automute_lo = spec->automute_lo_possible; + spec->automute_speaker = spec->automute_speaker_possible; + + if (spec->automute_speaker_possible || spec->automute_lo_possible) { + /* create a control for automute mode */ + err = add_automute_mode_enum(codec); + if (err < 0) + return err; + } + return 0; +} + +/* check whether all auto-mic pins are valid; setup indices if OK */ +static bool auto_mic_check_imux(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + const struct hda_input_mux *imux; + int i; + + imux = &spec->input_mux; + for (i = 0; i < spec->am_num_entries; i++) { + spec->am_entry[i].idx = + find_idx_in_nid_list(spec->am_entry[i].pin, + spec->imux_pins, imux->num_items); + if (spec->am_entry[i].idx < 0) + return false; /* no corresponding imux */ + } + + /* we don't need the jack detection for the first pin */ + for (i = 1; i < spec->am_num_entries; i++) + snd_hda_jack_detect_enable_callback(codec, + spec->am_entry[i].pin, + call_mic_autoswitch); + return true; +} + +static int compare_attr(const void *ap, const void *bp) +{ + const struct automic_entry *a = ap; + const struct automic_entry *b = bp; + return (int)(a->attr - b->attr); +} + +/* + * Check the availability of auto-mic switch; + * Set up if really supported + */ +static int check_auto_mic_availability(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + unsigned int types; + int i, num_pins; + + if (spec->suppress_auto_mic) + return 0; + + types = 0; + num_pins = 0; + for (i = 0; i < cfg->num_inputs; i++) { + hda_nid_t nid = cfg->inputs[i].pin; + unsigned int attr; + attr = snd_hda_codec_get_pincfg(codec, nid); + attr = snd_hda_get_input_pin_attr(attr); + if (types & (1 << attr)) + return 0; /* already occupied */ + switch (attr) { + case INPUT_PIN_ATTR_INT: + if (cfg->inputs[i].type != AUTO_PIN_MIC) + return 0; /* invalid type */ + break; + case INPUT_PIN_ATTR_UNUSED: + return 0; /* invalid entry */ + default: + if (cfg->inputs[i].type > AUTO_PIN_LINE_IN) + return 0; /* invalid type */ + if (!spec->line_in_auto_switch && + cfg->inputs[i].type != AUTO_PIN_MIC) + return 0; /* only mic is allowed */ + if (!is_jack_detectable(codec, nid)) + return 0; /* no unsol support */ + break; + } + if (num_pins >= MAX_AUTO_MIC_PINS) + return 0; + types |= (1 << attr); + spec->am_entry[num_pins].pin = nid; + spec->am_entry[num_pins].attr = attr; + num_pins++; + } + + if (num_pins < 2) + return 0; + + spec->am_num_entries = num_pins; + /* sort the am_entry in the order of attr so that the pin with a + * higher attr will be selected when the jack is plugged. + */ + sort(spec->am_entry, num_pins, sizeof(spec->am_entry[0]), + compare_attr, NULL); + + if (!auto_mic_check_imux(codec)) + return 0; + + spec->auto_mic = 1; + spec->num_adc_nids = 1; + spec->cur_mux[0] = spec->am_entry[0].idx; + codec_dbg(codec, "Enable auto-mic switch on NID 0x%x/0x%x/0x%x\n", + spec->am_entry[0].pin, + spec->am_entry[1].pin, + spec->am_entry[2].pin); + + return 0; +} + +/** + * snd_hda_gen_path_power_filter - power_filter hook to make inactive widgets + * into power down + * @codec: the HDA codec + * @nid: NID to evalute + * @power_state: target power state + */ +unsigned int snd_hda_gen_path_power_filter(struct hda_codec *codec, + hda_nid_t nid, + unsigned int power_state) +{ + struct hda_gen_spec *spec = codec->spec; + + if (!spec->power_down_unused && !codec->power_save_node) + return power_state; + if (power_state != AC_PWRST_D0 || nid == codec->core.afg) + return power_state; + if (get_wcaps_type(get_wcaps(codec, nid)) >= AC_WID_POWER) + return power_state; + if (is_active_nid_for_any(codec, nid)) + return power_state; + return AC_PWRST_D3; +} +EXPORT_SYMBOL_GPL(snd_hda_gen_path_power_filter); + +/* mute all aamix inputs initially; parse up to the first leaves */ +static void mute_all_mixer_nid(struct hda_codec *codec, hda_nid_t mix) +{ + int i, nums; + const hda_nid_t *conn; + bool has_amp; + + nums = snd_hda_get_conn_list(codec, mix, &conn); + has_amp = nid_has_mute(codec, mix, HDA_INPUT); + for (i = 0; i < nums; i++) { + if (has_amp) + update_amp(codec, mix, HDA_INPUT, i, + 0xff, HDA_AMP_MUTE); + else if (nid_has_volume(codec, conn[i], HDA_OUTPUT)) + update_amp(codec, conn[i], HDA_OUTPUT, 0, + 0xff, HDA_AMP_MUTE); + } +} + +/** + * snd_hda_gen_stream_pm - Stream power management callback + * @codec: the HDA codec + * @nid: audio widget + * @on: power on/off flag + * + * Set this in patch_ops.stream_pm. Only valid with power_save_node flag. + */ +void snd_hda_gen_stream_pm(struct hda_codec *codec, hda_nid_t nid, bool on) +{ + if (codec->power_save_node) + set_path_power(codec, nid, -1, on); +} +EXPORT_SYMBOL_GPL(snd_hda_gen_stream_pm); + +/** + * snd_hda_gen_parse_auto_config - Parse the given BIOS configuration and + * set up the hda_gen_spec + * @codec: the HDA codec + * @cfg: Parsed pin configuration + * + * return 1 if successful, 0 if the proper config is not found, + * or a negative error code + */ +int snd_hda_gen_parse_auto_config(struct hda_codec *codec, + struct auto_pin_cfg *cfg) +{ + struct hda_gen_spec *spec = codec->spec; + int err; + + parse_user_hints(codec); + + if (spec->vmaster_mute_led || spec->mic_mute_led) + snd_ctl_led_request(); + + if (spec->mixer_nid && !spec->mixer_merge_nid) + spec->mixer_merge_nid = spec->mixer_nid; + + if (cfg != &spec->autocfg) { + spec->autocfg = *cfg; + cfg = &spec->autocfg; + } + + if (!spec->main_out_badness) + spec->main_out_badness = &hda_main_out_badness; + if (!spec->extra_out_badness) + spec->extra_out_badness = &hda_extra_out_badness; + + fill_all_dac_nids(codec); + + if (!cfg->line_outs) { + if (cfg->dig_outs || cfg->dig_in_pin) { + spec->multiout.max_channels = 2; + spec->no_analog = 1; + goto dig_only; + } + if (!cfg->num_inputs && !cfg->dig_in_pin) + return 0; /* can't find valid BIOS pin config */ + } + + if (!spec->no_primary_hp && + cfg->line_out_type == AUTO_PIN_SPEAKER_OUT && + cfg->line_outs <= cfg->hp_outs) { + /* use HP as primary out */ + cfg->speaker_outs = cfg->line_outs; + memcpy(cfg->speaker_pins, cfg->line_out_pins, + sizeof(cfg->speaker_pins)); + cfg->line_outs = cfg->hp_outs; + memcpy(cfg->line_out_pins, cfg->hp_pins, sizeof(cfg->hp_pins)); + cfg->hp_outs = 0; + memset(cfg->hp_pins, 0, sizeof(cfg->hp_pins)); + cfg->line_out_type = AUTO_PIN_HP_OUT; + } + + err = parse_output_paths(codec); + if (err < 0) + return err; + err = create_multi_channel_mode(codec); + if (err < 0) + return err; + err = create_multi_out_ctls(codec, cfg); + if (err < 0) + return err; + err = create_hp_out_ctls(codec); + if (err < 0) + return err; + err = create_speaker_out_ctls(codec); + if (err < 0) + return err; + err = create_indep_hp_ctls(codec); + if (err < 0) + return err; + err = create_loopback_mixing_ctl(codec); + if (err < 0) + return err; + err = create_hp_mic(codec); + if (err < 0) + return err; + err = create_input_ctls(codec); + if (err < 0) + return err; + + /* add power-down pin callbacks at first */ + add_all_pin_power_ctls(codec, false); + + spec->const_channel_count = spec->ext_channel_count; + /* check the multiple speaker and headphone pins */ + if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) + spec->const_channel_count = max(spec->const_channel_count, + cfg->speaker_outs * 2); + if (cfg->line_out_type != AUTO_PIN_HP_OUT) + spec->const_channel_count = max(spec->const_channel_count, + cfg->hp_outs * 2); + spec->multiout.max_channels = max(spec->ext_channel_count, + spec->const_channel_count); + + err = check_auto_mute_availability(codec); + if (err < 0) + return err; + + err = check_dyn_adc_switch(codec); + if (err < 0) + return err; + + err = check_auto_mic_availability(codec); + if (err < 0) + return err; + + /* add stereo mix if available and not enabled yet */ + if (!spec->auto_mic && spec->mixer_nid && + spec->add_stereo_mix_input == HDA_HINT_STEREO_MIX_AUTO && + spec->input_mux.num_items > 1) { + err = parse_capture_source(codec, spec->mixer_nid, + CFG_IDX_MIX, spec->num_all_adcs, + "Stereo Mix", 0); + if (err < 0) + return err; + } + + + err = create_capture_mixers(codec); + if (err < 0) + return err; + + err = parse_mic_boost(codec); + if (err < 0) + return err; + + /* create "Headphone Mic Jack Mode" if no input selection is + * available (or user specifies add_jack_modes hint) + */ + if (spec->hp_mic_pin && + (spec->auto_mic || spec->input_mux.num_items == 1 || + spec->add_jack_modes)) { + err = create_hp_mic_jack_mode(codec, spec->hp_mic_pin); + if (err < 0) + return err; + } + + if (spec->add_jack_modes) { + if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) { + err = create_out_jack_modes(codec, cfg->line_outs, + cfg->line_out_pins); + if (err < 0) + return err; + } + if (cfg->line_out_type != AUTO_PIN_HP_OUT) { + err = create_out_jack_modes(codec, cfg->hp_outs, + cfg->hp_pins); + if (err < 0) + return err; + } + } + + /* add power-up pin callbacks at last */ + add_all_pin_power_ctls(codec, true); + + /* mute all aamix input initially */ + if (spec->mixer_nid) + mute_all_mixer_nid(codec, spec->mixer_nid); + + dig_only: + parse_digital(codec); + + if (spec->power_down_unused || codec->power_save_node) { + if (!codec->power_filter) + codec->power_filter = snd_hda_gen_path_power_filter; + if (!codec->patch_ops.stream_pm) + codec->patch_ops.stream_pm = snd_hda_gen_stream_pm; + } + + if (!spec->no_analog && spec->beep_nid) { + err = snd_hda_attach_beep_device(codec, spec->beep_nid); + if (err < 0) + return err; + if (codec->beep && codec->power_save_node) { + err = add_fake_beep_paths(codec); + if (err < 0) + return err; + codec->beep->power_hook = beep_power_hook; + } + } + + return 1; +} +EXPORT_SYMBOL_GPL(snd_hda_gen_parse_auto_config); + + +/* + * Build control elements + */ + +/* follower controls for virtual master */ +static const char * const follower_pfxs[] = { + "Front", "Surround", "Center", "LFE", "Side", + "Headphone", "Speaker", "Mono", "Line Out", + "CLFE", "Bass Speaker", "PCM", + "Speaker Front", "Speaker Surround", "Speaker CLFE", "Speaker Side", + "Headphone Front", "Headphone Surround", "Headphone CLFE", + "Headphone Side", "Headphone+LO", "Speaker+LO", + NULL, +}; + +/** + * snd_hda_gen_build_controls - Build controls from the parsed results + * @codec: the HDA codec + * + * Pass this to build_controls patch_ops. + */ +int snd_hda_gen_build_controls(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + int err; + + if (spec->kctls.used) { + err = snd_hda_add_new_ctls(codec, spec->kctls.list); + if (err < 0) + return err; + } + + if (spec->multiout.dig_out_nid) { + err = snd_hda_create_dig_out_ctls(codec, + spec->multiout.dig_out_nid, + spec->multiout.dig_out_nid, + spec->pcm_rec[1]->pcm_type); + if (err < 0) + return err; + if (!spec->no_analog) { + err = snd_hda_create_spdif_share_sw(codec, + &spec->multiout); + if (err < 0) + return err; + spec->multiout.share_spdif = 1; + } + } + if (spec->dig_in_nid) { + err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid); + if (err < 0) + return err; + } + + /* if we have no master control, let's create it */ + if (!spec->no_analog && !spec->suppress_vmaster && + !snd_hda_find_mixer_ctl(codec, "Master Playback Volume")) { + err = snd_hda_add_vmaster(codec, "Master Playback Volume", + spec->vmaster_tlv, follower_pfxs, + "Playback Volume", 0); + if (err < 0) + return err; + } + if (!spec->no_analog && !spec->suppress_vmaster && + !snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) { + err = __snd_hda_add_vmaster(codec, "Master Playback Switch", + NULL, follower_pfxs, + "Playback Switch", true, + spec->vmaster_mute_led ? + SNDRV_CTL_ELEM_ACCESS_SPK_LED : 0, + &spec->vmaster_mute.sw_kctl); + if (err < 0) + return err; + if (spec->vmaster_mute.hook) { + snd_hda_add_vmaster_hook(codec, &spec->vmaster_mute); + snd_hda_sync_vmaster_hook(&spec->vmaster_mute); + } + } + + free_kctls(spec); /* no longer needed */ + + err = snd_hda_jack_add_kctls(codec, &spec->autocfg); + if (err < 0) + return err; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_gen_build_controls); + + +/* + * PCM definitions + */ + +static void call_pcm_playback_hook(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream, + int action) +{ + struct hda_gen_spec *spec = codec->spec; + if (spec->pcm_playback_hook) + spec->pcm_playback_hook(hinfo, codec, substream, action); +} + +static void call_pcm_capture_hook(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream, + int action) +{ + struct hda_gen_spec *spec = codec->spec; + if (spec->pcm_capture_hook) + spec->pcm_capture_hook(hinfo, codec, substream, action); +} + +/* + * Analog playback callbacks + */ +static int playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hda_gen_spec *spec = codec->spec; + int err; + + mutex_lock(&spec->pcm_mutex); + err = snd_hda_multi_out_analog_open(codec, + &spec->multiout, substream, + hinfo); + if (!err) { + spec->active_streams |= 1 << STREAM_MULTI_OUT; + call_pcm_playback_hook(hinfo, codec, substream, + HDA_GEN_PCM_ACT_OPEN); + } + mutex_unlock(&spec->pcm_mutex); + return err; +} + +static int playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct hda_gen_spec *spec = codec->spec; + int err; + + err = snd_hda_multi_out_analog_prepare(codec, &spec->multiout, + stream_tag, format, substream); + if (!err) + call_pcm_playback_hook(hinfo, codec, substream, + HDA_GEN_PCM_ACT_PREPARE); + return err; +} + +static int playback_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hda_gen_spec *spec = codec->spec; + int err; + + err = snd_hda_multi_out_analog_cleanup(codec, &spec->multiout); + if (!err) + call_pcm_playback_hook(hinfo, codec, substream, + HDA_GEN_PCM_ACT_CLEANUP); + return err; +} + +static int playback_pcm_close(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hda_gen_spec *spec = codec->spec; + mutex_lock(&spec->pcm_mutex); + spec->active_streams &= ~(1 << STREAM_MULTI_OUT); + call_pcm_playback_hook(hinfo, codec, substream, + HDA_GEN_PCM_ACT_CLOSE); + mutex_unlock(&spec->pcm_mutex); + return 0; +} + +static int capture_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + call_pcm_capture_hook(hinfo, codec, substream, HDA_GEN_PCM_ACT_OPEN); + return 0; +} + +static int capture_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + snd_hda_codec_setup_stream(codec, hinfo->nid, stream_tag, 0, format); + call_pcm_capture_hook(hinfo, codec, substream, + HDA_GEN_PCM_ACT_PREPARE); + return 0; +} + +static int capture_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + snd_hda_codec_cleanup_stream(codec, hinfo->nid); + call_pcm_capture_hook(hinfo, codec, substream, + HDA_GEN_PCM_ACT_CLEANUP); + return 0; +} + +static int capture_pcm_close(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + call_pcm_capture_hook(hinfo, codec, substream, HDA_GEN_PCM_ACT_CLOSE); + return 0; +} + +static int alt_playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hda_gen_spec *spec = codec->spec; + int err = 0; + + mutex_lock(&spec->pcm_mutex); + if (spec->indep_hp && !spec->indep_hp_enabled) + err = -EBUSY; + else + spec->active_streams |= 1 << STREAM_INDEP_HP; + call_pcm_playback_hook(hinfo, codec, substream, + HDA_GEN_PCM_ACT_OPEN); + mutex_unlock(&spec->pcm_mutex); + return err; +} + +static int alt_playback_pcm_close(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hda_gen_spec *spec = codec->spec; + mutex_lock(&spec->pcm_mutex); + spec->active_streams &= ~(1 << STREAM_INDEP_HP); + call_pcm_playback_hook(hinfo, codec, substream, + HDA_GEN_PCM_ACT_CLOSE); + mutex_unlock(&spec->pcm_mutex); + return 0; +} + +static int alt_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + snd_hda_codec_setup_stream(codec, hinfo->nid, stream_tag, 0, format); + call_pcm_playback_hook(hinfo, codec, substream, + HDA_GEN_PCM_ACT_PREPARE); + return 0; +} + +static int alt_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + snd_hda_codec_cleanup_stream(codec, hinfo->nid); + call_pcm_playback_hook(hinfo, codec, substream, + HDA_GEN_PCM_ACT_CLEANUP); + return 0; +} + +/* + * Digital out + */ +static int dig_playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hda_gen_spec *spec = codec->spec; + return snd_hda_multi_out_dig_open(codec, &spec->multiout); +} + +static int dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct hda_gen_spec *spec = codec->spec; + return snd_hda_multi_out_dig_prepare(codec, &spec->multiout, + stream_tag, format, substream); +} + +static int dig_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hda_gen_spec *spec = codec->spec; + return snd_hda_multi_out_dig_cleanup(codec, &spec->multiout); +} + +static int dig_playback_pcm_close(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hda_gen_spec *spec = codec->spec; + return snd_hda_multi_out_dig_close(codec, &spec->multiout); +} + +/* + * Analog capture + */ +#define alt_capture_pcm_open capture_pcm_open +#define alt_capture_pcm_close capture_pcm_close + +static int alt_capture_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct hda_gen_spec *spec = codec->spec; + + snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number + 1], + stream_tag, 0, format); + call_pcm_capture_hook(hinfo, codec, substream, + HDA_GEN_PCM_ACT_PREPARE); + return 0; +} + +static int alt_capture_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hda_gen_spec *spec = codec->spec; + + snd_hda_codec_cleanup_stream(codec, + spec->adc_nids[substream->number + 1]); + call_pcm_capture_hook(hinfo, codec, substream, + HDA_GEN_PCM_ACT_CLEANUP); + return 0; +} + +/* + */ +static const struct hda_pcm_stream pcm_analog_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 8, + /* NID is set in build_pcms */ + .ops = { + .open = playback_pcm_open, + .close = playback_pcm_close, + .prepare = playback_pcm_prepare, + .cleanup = playback_pcm_cleanup + }, +}; + +static const struct hda_pcm_stream pcm_analog_capture = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + /* NID is set in build_pcms */ + .ops = { + .open = capture_pcm_open, + .close = capture_pcm_close, + .prepare = capture_pcm_prepare, + .cleanup = capture_pcm_cleanup + }, +}; + +static const struct hda_pcm_stream pcm_analog_alt_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + /* NID is set in build_pcms */ + .ops = { + .open = alt_playback_pcm_open, + .close = alt_playback_pcm_close, + .prepare = alt_playback_pcm_prepare, + .cleanup = alt_playback_pcm_cleanup + }, +}; + +static const struct hda_pcm_stream pcm_analog_alt_capture = { + .substreams = 2, /* can be overridden */ + .channels_min = 2, + .channels_max = 2, + /* NID is set in build_pcms */ + .ops = { + .open = alt_capture_pcm_open, + .close = alt_capture_pcm_close, + .prepare = alt_capture_pcm_prepare, + .cleanup = alt_capture_pcm_cleanup + }, +}; + +static const struct hda_pcm_stream pcm_digital_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + /* NID is set in build_pcms */ + .ops = { + .open = dig_playback_pcm_open, + .close = dig_playback_pcm_close, + .prepare = dig_playback_pcm_prepare, + .cleanup = dig_playback_pcm_cleanup + }, +}; + +static const struct hda_pcm_stream pcm_digital_capture = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + /* NID is set in build_pcms */ +}; + +/* Used by build_pcms to flag that a PCM has no playback stream */ +static const struct hda_pcm_stream pcm_null_stream = { + .substreams = 0, + .channels_min = 0, + .channels_max = 0, +}; + +/* + * dynamic changing ADC PCM streams + */ +static bool dyn_adc_pcm_resetup(struct hda_codec *codec, int cur) +{ + struct hda_gen_spec *spec = codec->spec; + hda_nid_t new_adc = spec->adc_nids[spec->dyn_adc_idx[cur]]; + + if (spec->cur_adc && spec->cur_adc != new_adc) { + /* stream is running, let's swap the current ADC */ + __snd_hda_codec_cleanup_stream(codec, spec->cur_adc, 1); + spec->cur_adc = new_adc; + snd_hda_codec_setup_stream(codec, new_adc, + spec->cur_adc_stream_tag, 0, + spec->cur_adc_format); + return true; + } + return false; +} + +/* analog capture with dynamic dual-adc changes */ +static int dyn_adc_capture_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct hda_gen_spec *spec = codec->spec; + spec->cur_adc = spec->adc_nids[spec->dyn_adc_idx[spec->cur_mux[0]]]; + spec->cur_adc_stream_tag = stream_tag; + spec->cur_adc_format = format; + snd_hda_codec_setup_stream(codec, spec->cur_adc, stream_tag, 0, format); + call_pcm_capture_hook(hinfo, codec, substream, HDA_GEN_PCM_ACT_PREPARE); + return 0; +} + +static int dyn_adc_capture_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hda_gen_spec *spec = codec->spec; + snd_hda_codec_cleanup_stream(codec, spec->cur_adc); + spec->cur_adc = 0; + call_pcm_capture_hook(hinfo, codec, substream, HDA_GEN_PCM_ACT_CLEANUP); + return 0; +} + +static const struct hda_pcm_stream dyn_adc_pcm_analog_capture = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + .nid = 0, /* fill later */ + .ops = { + .prepare = dyn_adc_capture_pcm_prepare, + .cleanup = dyn_adc_capture_pcm_cleanup + }, +}; + +static void fill_pcm_stream_name(char *str, size_t len, const char *sfx, + const char *chip_name) +{ + char *p; + + if (*str) + return; + strscpy(str, chip_name, len); + + /* drop non-alnum chars after a space */ + for (p = strchr(str, ' '); p; p = strchr(p + 1, ' ')) { + if (!isalnum(p[1])) { + *p = 0; + break; + } + } + strlcat(str, sfx, len); +} + +/* copy PCM stream info from @default_str, and override non-NULL entries + * from @spec_str and @nid + */ +static void setup_pcm_stream(struct hda_pcm_stream *str, + const struct hda_pcm_stream *default_str, + const struct hda_pcm_stream *spec_str, + hda_nid_t nid) +{ + *str = *default_str; + if (nid) + str->nid = nid; + if (spec_str) { + if (spec_str->substreams) + str->substreams = spec_str->substreams; + if (spec_str->channels_min) + str->channels_min = spec_str->channels_min; + if (spec_str->channels_max) + str->channels_max = spec_str->channels_max; + if (spec_str->rates) + str->rates = spec_str->rates; + if (spec_str->formats) + str->formats = spec_str->formats; + if (spec_str->maxbps) + str->maxbps = spec_str->maxbps; + } +} + +/** + * snd_hda_gen_build_pcms - build PCM streams based on the parsed results + * @codec: the HDA codec + * + * Pass this to build_pcms patch_ops. + */ +int snd_hda_gen_build_pcms(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + struct hda_pcm *info; + bool have_multi_adcs; + + if (spec->no_analog) + goto skip_analog; + + fill_pcm_stream_name(spec->stream_name_analog, + sizeof(spec->stream_name_analog), + " Analog", codec->core.chip_name); + info = snd_hda_codec_pcm_new(codec, "%s", spec->stream_name_analog); + if (!info) + return -ENOMEM; + spec->pcm_rec[0] = info; + + if (spec->multiout.num_dacs > 0) { + setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK], + &pcm_analog_playback, + spec->stream_analog_playback, + spec->multiout.dac_nids[0]); + info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = + spec->multiout.max_channels; + if (spec->autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT && + spec->autocfg.line_outs == 2) + info->stream[SNDRV_PCM_STREAM_PLAYBACK].chmap = + snd_pcm_2_1_chmaps; + } + if (spec->num_adc_nids) { + setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_CAPTURE], + (spec->dyn_adc_switch ? + &dyn_adc_pcm_analog_capture : &pcm_analog_capture), + spec->stream_analog_capture, + spec->adc_nids[0]); + } + + skip_analog: + /* SPDIF for stream index #1 */ + if (spec->multiout.dig_out_nid || spec->dig_in_nid) { + fill_pcm_stream_name(spec->stream_name_digital, + sizeof(spec->stream_name_digital), + " Digital", codec->core.chip_name); + info = snd_hda_codec_pcm_new(codec, "%s", + spec->stream_name_digital); + if (!info) + return -ENOMEM; + codec->follower_dig_outs = spec->multiout.follower_dig_outs; + spec->pcm_rec[1] = info; + if (spec->dig_out_type) + info->pcm_type = spec->dig_out_type; + else + info->pcm_type = HDA_PCM_TYPE_SPDIF; + if (spec->multiout.dig_out_nid) + setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK], + &pcm_digital_playback, + spec->stream_digital_playback, + spec->multiout.dig_out_nid); + if (spec->dig_in_nid) + setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_CAPTURE], + &pcm_digital_capture, + spec->stream_digital_capture, + spec->dig_in_nid); + } + + if (spec->no_analog) + return 0; + + /* If the use of more than one ADC is requested for the current + * model, configure a second analog capture-only PCM. + */ + have_multi_adcs = (spec->num_adc_nids > 1) && + !spec->dyn_adc_switch && !spec->auto_mic; + /* Additional Analaog capture for index #2 */ + if (spec->alt_dac_nid || have_multi_adcs) { + fill_pcm_stream_name(spec->stream_name_alt_analog, + sizeof(spec->stream_name_alt_analog), + " Alt Analog", codec->core.chip_name); + info = snd_hda_codec_pcm_new(codec, "%s", + spec->stream_name_alt_analog); + if (!info) + return -ENOMEM; + spec->pcm_rec[2] = info; + if (spec->alt_dac_nid) + setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK], + &pcm_analog_alt_playback, + spec->stream_analog_alt_playback, + spec->alt_dac_nid); + else + setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK], + &pcm_null_stream, NULL, 0); + if (have_multi_adcs) { + setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_CAPTURE], + &pcm_analog_alt_capture, + spec->stream_analog_alt_capture, + spec->adc_nids[1]); + info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = + spec->num_adc_nids - 1; + } else { + setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_CAPTURE], + &pcm_null_stream, NULL, 0); + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_gen_build_pcms); + + +/* + * Standard auto-parser initializations + */ + +/* configure the given path as a proper output */ +static void set_output_and_unmute(struct hda_codec *codec, int path_idx) +{ + struct nid_path *path; + hda_nid_t pin; + + path = snd_hda_get_path_from_idx(codec, path_idx); + if (!path || !path->depth) + return; + pin = path->path[path->depth - 1]; + restore_pin_ctl(codec, pin); + snd_hda_activate_path(codec, path, path->active, + aamix_default(codec->spec)); + set_pin_eapd(codec, pin, path->active); +} + +/* initialize primary output paths */ +static void init_multi_out(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + int i; + + for (i = 0; i < spec->autocfg.line_outs; i++) + set_output_and_unmute(codec, spec->out_paths[i]); +} + + +static void __init_extra_out(struct hda_codec *codec, int num_outs, int *paths) +{ + int i; + + for (i = 0; i < num_outs; i++) + set_output_and_unmute(codec, paths[i]); +} + +/* initialize hp and speaker paths */ +static void init_extra_out(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + + if (spec->autocfg.line_out_type != AUTO_PIN_HP_OUT) + __init_extra_out(codec, spec->autocfg.hp_outs, spec->hp_paths); + if (spec->autocfg.line_out_type != AUTO_PIN_SPEAKER_OUT) + __init_extra_out(codec, spec->autocfg.speaker_outs, + spec->speaker_paths); +} + +/* initialize multi-io paths */ +static void init_multi_io(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + int i; + + for (i = 0; i < spec->multi_ios; i++) { + hda_nid_t pin = spec->multi_io[i].pin; + struct nid_path *path; + path = get_multiio_path(codec, i); + if (!path) + continue; + if (!spec->multi_io[i].ctl_in) + spec->multi_io[i].ctl_in = + snd_hda_codec_get_pin_target(codec, pin); + snd_hda_activate_path(codec, path, path->active, + aamix_default(spec)); + } +} + +static void init_aamix_paths(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + + if (!spec->have_aamix_ctl) + return; + if (!has_aamix_out_paths(spec)) + return; + update_aamix_paths(codec, spec->aamix_mode, spec->out_paths[0], + spec->aamix_out_paths[0], + spec->autocfg.line_out_type); + update_aamix_paths(codec, spec->aamix_mode, spec->hp_paths[0], + spec->aamix_out_paths[1], + AUTO_PIN_HP_OUT); + update_aamix_paths(codec, spec->aamix_mode, spec->speaker_paths[0], + spec->aamix_out_paths[2], + AUTO_PIN_SPEAKER_OUT); +} + +/* set up input pins and loopback paths */ +static void init_analog_input(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + int i; + + for (i = 0; i < cfg->num_inputs; i++) { + hda_nid_t nid = cfg->inputs[i].pin; + if (is_input_pin(codec, nid)) + restore_pin_ctl(codec, nid); + + /* init loopback inputs */ + if (spec->mixer_nid) { + resume_path_from_idx(codec, spec->loopback_paths[i]); + resume_path_from_idx(codec, spec->loopback_merge_path); + } + } +} + +/* initialize ADC paths */ +static void init_input_src(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + struct hda_input_mux *imux = &spec->input_mux; + struct nid_path *path; + int i, c, nums; + + if (spec->dyn_adc_switch) + nums = 1; + else + nums = spec->num_adc_nids; + + for (c = 0; c < nums; c++) { + for (i = 0; i < imux->num_items; i++) { + path = get_input_path(codec, c, i); + if (path) { + bool active = path->active; + if (i == spec->cur_mux[c]) + active = true; + snd_hda_activate_path(codec, path, active, false); + } + } + if (spec->hp_mic) + update_hp_mic(codec, c, true); + } + + if (spec->cap_sync_hook) + spec->cap_sync_hook(codec, NULL, NULL); +} + +/* set right pin controls for digital I/O */ +static void init_digital(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + int i; + hda_nid_t pin; + + for (i = 0; i < spec->autocfg.dig_outs; i++) + set_output_and_unmute(codec, spec->digout_paths[i]); + pin = spec->autocfg.dig_in_pin; + if (pin) { + restore_pin_ctl(codec, pin); + resume_path_from_idx(codec, spec->digin_path); + } +} + +/* clear unsol-event tags on unused pins; Conexant codecs seem to leave + * invalid unsol tags by some reason + */ +static void clear_unsol_on_unused_pins(struct hda_codec *codec) +{ + const struct hda_pincfg *pin; + int i; + + snd_array_for_each(&codec->init_pins, i, pin) { + hda_nid_t nid = pin->nid; + if (is_jack_detectable(codec, nid) && + !snd_hda_jack_tbl_get(codec, nid)) + snd_hda_codec_write_cache(codec, nid, 0, + AC_VERB_SET_UNSOLICITED_ENABLE, 0); + } +} + +/** + * snd_hda_gen_init - initialize the generic spec + * @codec: the HDA codec + * + * This can be put as patch_ops init function. + */ +int snd_hda_gen_init(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + + if (spec->init_hook) + spec->init_hook(codec); + + if (!spec->skip_verbs) + snd_hda_apply_verbs(codec); + + init_multi_out(codec); + init_extra_out(codec); + init_multi_io(codec); + init_aamix_paths(codec); + init_analog_input(codec); + init_input_src(codec); + init_digital(codec); + + clear_unsol_on_unused_pins(codec); + + sync_all_pin_power_ctls(codec); + + /* call init functions of standard auto-mute helpers */ + update_automute_all(codec); + + snd_hda_regmap_sync(codec); + + if (spec->vmaster_mute.sw_kctl && spec->vmaster_mute.hook) + snd_hda_sync_vmaster_hook(&spec->vmaster_mute); + + hda_call_check_power_status(codec, 0x01); + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_gen_init); + +/** + * snd_hda_gen_free - free the generic spec + * @codec: the HDA codec + * + * This can be put as patch_ops free function. + */ +void snd_hda_gen_free(struct hda_codec *codec) +{ + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_FREE); + snd_hda_gen_spec_free(codec->spec); + kfree(codec->spec); + codec->spec = NULL; +} +EXPORT_SYMBOL_GPL(snd_hda_gen_free); + +#ifdef CONFIG_PM +/** + * snd_hda_gen_check_power_status - check the loopback power save state + * @codec: the HDA codec + * @nid: NID to inspect + * + * This can be put as patch_ops check_power_status function. + */ +int snd_hda_gen_check_power_status(struct hda_codec *codec, hda_nid_t nid) +{ + struct hda_gen_spec *spec = codec->spec; + return snd_hda_check_amp_list_power(codec, &spec->loopback, nid); +} +EXPORT_SYMBOL_GPL(snd_hda_gen_check_power_status); +#endif + + +/* + * the generic codec support + */ + +static const struct hda_codec_ops generic_patch_ops = { + .build_controls = snd_hda_gen_build_controls, + .build_pcms = snd_hda_gen_build_pcms, + .init = snd_hda_gen_init, + .free = snd_hda_gen_free, + .unsol_event = snd_hda_jack_unsol_event, +#ifdef CONFIG_PM + .check_power_status = snd_hda_gen_check_power_status, +#endif +}; + +/* + * snd_hda_parse_generic_codec - Generic codec parser + * @codec: the HDA codec + */ +static int snd_hda_parse_generic_codec(struct hda_codec *codec) +{ + struct hda_gen_spec *spec; + int err; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return -ENOMEM; + snd_hda_gen_spec_init(spec); + codec->spec = spec; + + err = snd_hda_parse_pin_defcfg(codec, &spec->autocfg, NULL, 0); + if (err < 0) + goto error; + + err = snd_hda_gen_parse_auto_config(codec, &spec->autocfg); + if (err < 0) + goto error; + + codec->patch_ops = generic_patch_ops; + return 0; + +error: + snd_hda_gen_free(codec); + return err; +} + +static const struct hda_device_id snd_hda_id_generic[] = { + HDA_CODEC_ENTRY(HDA_CODEC_ID_GENERIC, "Generic", snd_hda_parse_generic_codec), + {} /* terminator */ +}; +MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_generic); + +static struct hda_codec_driver generic_driver = { + .id = snd_hda_id_generic, +}; + +module_hda_codec_driver(generic_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Generic HD-audio codec parser"); diff --git a/sound/pci/hda/hda_generic.h b/sound/pci/hda/hda_generic.h new file mode 100644 index 000000000..34eba40cc --- /dev/null +++ b/sound/pci/hda/hda_generic.h @@ -0,0 +1,356 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Generic BIOS auto-parser helper functions for HD-audio + * + * Copyright (c) 2012 Takashi Iwai <tiwai@suse.de> + */ + +#ifndef __SOUND_HDA_GENERIC_H +#define __SOUND_HDA_GENERIC_H + +#include <linux/leds.h> + +/* table entry for multi-io paths */ +struct hda_multi_io { + hda_nid_t pin; /* multi-io widget pin NID */ + hda_nid_t dac; /* DAC to be connected */ + unsigned int ctl_in; /* cached input-pin control value */ +}; + +/* Widget connection path + * + * For output, stored in the order of DAC -> ... -> pin, + * for input, pin -> ... -> ADC. + * + * idx[i] contains the source index number to select on of the widget path[i]; + * e.g. idx[1] is the index of the DAC (path[0]) selected by path[1] widget + * multi[] indicates whether it's a selector widget with multi-connectors + * (i.e. the connection selection is mandatory) + * vol_ctl and mute_ctl contains the NIDs for the assigned mixers + */ + +#define MAX_NID_PATH_DEPTH 10 + +enum { + NID_PATH_VOL_CTL, + NID_PATH_MUTE_CTL, + NID_PATH_BOOST_CTL, + NID_PATH_NUM_CTLS +}; + +struct nid_path { + int depth; + hda_nid_t path[MAX_NID_PATH_DEPTH]; + unsigned char idx[MAX_NID_PATH_DEPTH]; + unsigned char multi[MAX_NID_PATH_DEPTH]; + unsigned int ctls[NID_PATH_NUM_CTLS]; /* NID_PATH_XXX_CTL */ + bool active:1; /* activated by driver */ + bool pin_enabled:1; /* pins are enabled */ + bool pin_fixed:1; /* path with fixed pin */ + bool stream_enabled:1; /* stream is active */ +}; + +/* mic/line-in auto switching entry */ + +#define MAX_AUTO_MIC_PINS 3 + +struct automic_entry { + hda_nid_t pin; /* pin */ + int idx; /* imux index, -1 = invalid */ + unsigned int attr; /* pin attribute (INPUT_PIN_ATTR_*) */ +}; + +/* active stream id */ +enum { STREAM_MULTI_OUT, STREAM_INDEP_HP }; + +/* PCM hook action */ +enum { + HDA_GEN_PCM_ACT_OPEN, + HDA_GEN_PCM_ACT_PREPARE, + HDA_GEN_PCM_ACT_CLEANUP, + HDA_GEN_PCM_ACT_CLOSE, +}; + +/* DAC assignment badness table */ +struct badness_table { + int no_primary_dac; /* no primary DAC */ + int no_dac; /* no secondary DACs */ + int shared_primary; /* primary DAC is shared with main output */ + int shared_surr; /* secondary DAC shared with main or primary */ + int shared_clfe; /* third DAC shared with main or primary */ + int shared_surr_main; /* secondary DAC sahred with main/DAC0 */ +}; + +extern const struct badness_table hda_main_out_badness; +extern const struct badness_table hda_extra_out_badness; + +struct hda_gen_spec { + char stream_name_analog[32]; /* analog PCM stream */ + const struct hda_pcm_stream *stream_analog_playback; + const struct hda_pcm_stream *stream_analog_capture; + + char stream_name_alt_analog[32]; /* alternative analog PCM stream */ + const struct hda_pcm_stream *stream_analog_alt_playback; + const struct hda_pcm_stream *stream_analog_alt_capture; + + char stream_name_digital[32]; /* digital PCM stream */ + const struct hda_pcm_stream *stream_digital_playback; + const struct hda_pcm_stream *stream_digital_capture; + + /* PCM */ + unsigned int active_streams; + struct mutex pcm_mutex; + + /* playback */ + struct hda_multi_out multiout; /* playback set-up + * max_channels, dacs must be set + * dig_out_nid and hp_nid are optional + */ + hda_nid_t alt_dac_nid; + hda_nid_t follower_dig_outs[3]; /* optional - for auto-parsing */ + int dig_out_type; + + /* capture */ + unsigned int num_adc_nids; + hda_nid_t adc_nids[AUTO_CFG_MAX_INS]; + hda_nid_t dig_in_nid; /* digital-in NID; optional */ + hda_nid_t mixer_nid; /* analog-mixer NID */ + hda_nid_t mixer_merge_nid; /* aamix merge-point NID (optional) */ + const char *input_labels[HDA_MAX_NUM_INPUTS]; + int input_label_idxs[HDA_MAX_NUM_INPUTS]; + + /* capture setup for dynamic dual-adc switch */ + hda_nid_t cur_adc; + unsigned int cur_adc_stream_tag; + unsigned int cur_adc_format; + + /* capture source */ + struct hda_input_mux input_mux; + unsigned int cur_mux[3]; + + /* channel model */ + /* min_channel_count contains the minimum channel count for primary + * outputs. When multi_ios is set, the channels can be configured + * between min_channel_count and (min_channel_count + multi_ios * 2). + * + * ext_channel_count contains the current channel count of the primary + * out. This varies in the range above. + * + * Meanwhile, const_channel_count is the channel count for all outputs + * including headphone and speakers. It's a constant value, and the + * PCM is set up as max(ext_channel_count, const_channel_count). + */ + int min_channel_count; /* min. channel count for primary out */ + int ext_channel_count; /* current channel count for primary */ + int const_channel_count; /* channel count for all */ + + /* PCM information */ + struct hda_pcm *pcm_rec[3]; /* used in build_pcms() */ + + /* dynamic controls, init_verbs and input_mux */ + struct auto_pin_cfg autocfg; + struct snd_array kctls; + hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS]; + hda_nid_t imux_pins[HDA_MAX_NUM_INPUTS]; + unsigned int dyn_adc_idx[HDA_MAX_NUM_INPUTS]; + /* shared hp/mic */ + hda_nid_t shared_mic_vref_pin; + hda_nid_t hp_mic_pin; + int hp_mic_mux_idx; + + /* DAC/ADC lists */ + int num_all_dacs; + hda_nid_t all_dacs[16]; + int num_all_adcs; + hda_nid_t all_adcs[AUTO_CFG_MAX_INS]; + + /* path list */ + struct snd_array paths; + + /* path indices */ + int out_paths[AUTO_CFG_MAX_OUTS]; + int hp_paths[AUTO_CFG_MAX_OUTS]; + int speaker_paths[AUTO_CFG_MAX_OUTS]; + int aamix_out_paths[3]; + int digout_paths[AUTO_CFG_MAX_OUTS]; + int input_paths[HDA_MAX_NUM_INPUTS][AUTO_CFG_MAX_INS]; + int loopback_paths[HDA_MAX_NUM_INPUTS]; + int loopback_merge_path; + int digin_path; + + /* auto-mic stuff */ + int am_num_entries; + struct automic_entry am_entry[MAX_AUTO_MIC_PINS]; + + /* for pin sensing */ + /* current status; set in hda_generic.c */ + unsigned int hp_jack_present:1; + unsigned int line_jack_present:1; + unsigned int speaker_muted:1; /* current status of speaker mute */ + unsigned int line_out_muted:1; /* current status of LO mute */ + + /* internal states of automute / autoswitch behavior */ + unsigned int auto_mic:1; + unsigned int automute_speaker:1; /* automute speaker outputs */ + unsigned int automute_lo:1; /* automute LO outputs */ + + /* capabilities detected by parser */ + unsigned int detect_hp:1; /* Headphone detection enabled */ + unsigned int detect_lo:1; /* Line-out detection enabled */ + unsigned int automute_speaker_possible:1; /* there are speakers and either LO or HP */ + unsigned int automute_lo_possible:1; /* there are line outs and HP */ + + /* additional parameters set by codec drivers */ + unsigned int master_mute:1; /* master mute over all */ + unsigned int keep_vref_in_automute:1; /* Don't clear VREF in automute */ + unsigned int line_in_auto_switch:1; /* allow line-in auto switch */ + unsigned int auto_mute_via_amp:1; /* auto-mute via amp instead of pinctl */ + + /* parser behavior flags; set before snd_hda_gen_parse_auto_config() */ + unsigned int suppress_auto_mute:1; /* suppress input jack auto mute */ + unsigned int suppress_auto_mic:1; /* suppress input jack auto switch */ + + /* other parse behavior flags */ + unsigned int need_dac_fix:1; /* need to limit DACs for multi channels */ + unsigned int hp_mic:1; /* Allow HP as a mic-in */ + unsigned int suppress_hp_mic_detect:1; /* Don't detect HP/mic */ + unsigned int no_primary_hp:1; /* Don't prefer HP pins to speaker pins */ + unsigned int no_multi_io:1; /* Don't try multi I/O config */ + unsigned int multi_cap_vol:1; /* allow multiple capture xxx volumes */ + unsigned int inv_dmic_split:1; /* inverted dmic w/a for conexant */ + unsigned int own_eapd_ctl:1; /* set EAPD by own function */ + unsigned int keep_eapd_on:1; /* don't turn off EAPD automatically */ + unsigned int vmaster_mute_led:1; /* add SPK-LED flag to vmaster mute switch */ + unsigned int mic_mute_led:1; /* add MIC-LED flag to capture mute switch */ + unsigned int indep_hp:1; /* independent HP supported */ + unsigned int prefer_hp_amp:1; /* enable HP amp for speaker if any */ + unsigned int add_stereo_mix_input:2; /* add aamix as a capture src */ + unsigned int add_jack_modes:1; /* add i/o jack mode enum ctls */ + unsigned int power_down_unused:1; /* power down unused widgets */ + unsigned int dac_min_mute:1; /* minimal = mute for DACs */ + unsigned int suppress_vmaster:1; /* don't create vmaster kctls */ + unsigned int obey_preferred_dacs:1; /* obey preferred_dacs assignment */ + + /* other internal flags */ + unsigned int no_analog:1; /* digital I/O only */ + unsigned int dyn_adc_switch:1; /* switch ADCs (for ALC275) */ + unsigned int indep_hp_enabled:1; /* independent HP enabled */ + unsigned int have_aamix_ctl:1; + unsigned int hp_mic_jack_modes:1; + unsigned int skip_verbs:1; /* don't apply verbs at snd_hda_gen_init() */ + + /* additional mute flags (only effective with auto_mute_via_amp=1) */ + u64 mute_bits; + + /* bitmask for skipping volume controls */ + u64 out_vol_mask; + + /* badness tables for output path evaluations */ + const struct badness_table *main_out_badness; + const struct badness_table *extra_out_badness; + + /* preferred pin/DAC pairs; an array of paired NIDs */ + const hda_nid_t *preferred_dacs; + + /* loopback mixing mode */ + bool aamix_mode; + + /* digital beep */ + hda_nid_t beep_nid; + + /* for virtual master */ + hda_nid_t vmaster_nid; + unsigned int vmaster_tlv[4]; + struct hda_vmaster_mute_hook vmaster_mute; + + struct hda_loopback_check loopback; + struct snd_array loopback_list; + + /* multi-io */ + int multi_ios; + struct hda_multi_io multi_io[4]; + + /* hooks */ + void (*init_hook)(struct hda_codec *codec); + void (*automute_hook)(struct hda_codec *codec); + void (*cap_sync_hook)(struct hda_codec *codec, + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + + /* PCM hooks */ + void (*pcm_playback_hook)(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream, + int action); + void (*pcm_capture_hook)(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream, + int action); + + /* automute / autoswitch hooks */ + void (*hp_automute_hook)(struct hda_codec *codec, + struct hda_jack_callback *cb); + void (*line_automute_hook)(struct hda_codec *codec, + struct hda_jack_callback *cb); + void (*mic_autoswitch_hook)(struct hda_codec *codec, + struct hda_jack_callback *cb); + + /* leds */ + struct led_classdev *led_cdevs[NUM_AUDIO_LEDS]; +}; + +/* values for add_stereo_mix_input flag */ +enum { + HDA_HINT_STEREO_MIX_DISABLE, /* No stereo mix input */ + HDA_HINT_STEREO_MIX_ENABLE, /* Add stereo mix input */ + HDA_HINT_STEREO_MIX_AUTO, /* Add only if auto-mic is disabled */ +}; + +int snd_hda_gen_spec_init(struct hda_gen_spec *spec); + +int snd_hda_gen_init(struct hda_codec *codec); +void snd_hda_gen_free(struct hda_codec *codec); + +int snd_hda_get_path_idx(struct hda_codec *codec, struct nid_path *path); +struct nid_path *snd_hda_get_path_from_idx(struct hda_codec *codec, int idx); +struct nid_path * +snd_hda_add_new_path(struct hda_codec *codec, hda_nid_t from_nid, + hda_nid_t to_nid, int anchor_nid); +void snd_hda_activate_path(struct hda_codec *codec, struct nid_path *path, + bool enable, bool add_aamix); + +struct snd_kcontrol_new * +snd_hda_gen_add_kctl(struct hda_gen_spec *spec, const char *name, + const struct snd_kcontrol_new *temp); + +int snd_hda_gen_parse_auto_config(struct hda_codec *codec, + struct auto_pin_cfg *cfg); +int snd_hda_gen_build_controls(struct hda_codec *codec); +int snd_hda_gen_build_pcms(struct hda_codec *codec); + +/* standard jack event callbacks */ +void snd_hda_gen_hp_automute(struct hda_codec *codec, + struct hda_jack_callback *jack); +void snd_hda_gen_line_automute(struct hda_codec *codec, + struct hda_jack_callback *jack); +void snd_hda_gen_mic_autoswitch(struct hda_codec *codec, + struct hda_jack_callback *jack); +void snd_hda_gen_update_outputs(struct hda_codec *codec); + +#ifdef CONFIG_PM +int snd_hda_gen_check_power_status(struct hda_codec *codec, hda_nid_t nid); +#endif +unsigned int snd_hda_gen_path_power_filter(struct hda_codec *codec, + hda_nid_t nid, + unsigned int power_state); +void snd_hda_gen_stream_pm(struct hda_codec *codec, hda_nid_t nid, bool on); +int snd_hda_gen_fix_pin_power(struct hda_codec *codec, hda_nid_t pin); + +int snd_hda_gen_add_mute_led_cdev(struct hda_codec *codec, + int (*callback)(struct led_classdev *, + enum led_brightness)); +int snd_hda_gen_add_micmute_led_cdev(struct hda_codec *codec, + int (*callback)(struct led_classdev *, + enum led_brightness)); + +#endif /* __SOUND_HDA_GENERIC_H */ diff --git a/sound/pci/hda/hda_hwdep.c b/sound/pci/hda/hda_hwdep.c new file mode 100644 index 000000000..125e97fe0 --- /dev/null +++ b/sound/pci/hda/hda_hwdep.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HWDEP Interface for HD-audio codec + * + * Copyright (c) 2007 Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/compat.h> +#include <linux/nospec.h> +#include <sound/core.h> +#include <sound/hda_codec.h> +#include "hda_local.h" +#include <sound/hda_hwdep.h> +#include <sound/minors.h> + +/* + * write/read an out-of-bound verb + */ +static int verb_write_ioctl(struct hda_codec *codec, + struct hda_verb_ioctl __user *arg) +{ + u32 verb, res; + + if (get_user(verb, &arg->verb)) + return -EFAULT; + res = snd_hda_codec_read(codec, verb >> 24, 0, + (verb >> 8) & 0xffff, verb & 0xff); + if (put_user(res, &arg->res)) + return -EFAULT; + return 0; +} + +static int get_wcap_ioctl(struct hda_codec *codec, + struct hda_verb_ioctl __user *arg) +{ + u32 verb, res; + + if (get_user(verb, &arg->verb)) + return -EFAULT; + /* open-code get_wcaps(verb>>24) with nospec */ + verb >>= 24; + if (verb < codec->core.start_nid || + verb >= codec->core.start_nid + codec->core.num_nodes) { + res = 0; + } else { + verb -= codec->core.start_nid; + verb = array_index_nospec(verb, codec->core.num_nodes); + res = codec->wcaps[verb]; + } + if (put_user(res, &arg->res)) + return -EFAULT; + return 0; +} + + +/* + */ +static int hda_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct hda_codec *codec = hw->private_data; + void __user *argp = (void __user *)arg; + + switch (cmd) { + case HDA_IOCTL_PVERSION: + return put_user(HDA_HWDEP_VERSION, (int __user *)argp); + case HDA_IOCTL_VERB_WRITE: + return verb_write_ioctl(codec, argp); + case HDA_IOCTL_GET_WCAP: + return get_wcap_ioctl(codec, argp); + } + return -ENOIOCTLCMD; +} + +#ifdef CONFIG_COMPAT +static int hda_hwdep_ioctl_compat(struct snd_hwdep *hw, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hda_hwdep_ioctl(hw, file, cmd, (unsigned long)compat_ptr(arg)); +} +#endif + +static int hda_hwdep_open(struct snd_hwdep *hw, struct file *file) +{ +#ifndef CONFIG_SND_DEBUG_VERBOSE + if (!capable(CAP_SYS_RAWIO)) + return -EACCES; +#endif + return 0; +} + +int snd_hda_create_hwdep(struct hda_codec *codec) +{ + char hwname[16]; + struct snd_hwdep *hwdep; + int err; + + sprintf(hwname, "HDA Codec %d", codec->addr); + err = snd_hwdep_new(codec->card, hwname, codec->addr, &hwdep); + if (err < 0) + return err; + codec->hwdep = hwdep; + sprintf(hwdep->name, "HDA Codec %d", codec->addr); + hwdep->iface = SNDRV_HWDEP_IFACE_HDA; + hwdep->private_data = codec; + hwdep->exclusive = 1; + + hwdep->ops.open = hda_hwdep_open; + hwdep->ops.ioctl = hda_hwdep_ioctl; +#ifdef CONFIG_COMPAT + hwdep->ops.ioctl_compat = hda_hwdep_ioctl_compat; +#endif + + /* for sysfs */ + hwdep->dev.groups = snd_hda_dev_attr_groups; + dev_set_drvdata(&hwdep->dev, codec); + + return 0; +} diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c new file mode 100644 index 000000000..5aaf3dcec --- /dev/null +++ b/sound/pci/hda/hda_intel.c @@ -0,0 +1,2831 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * hda_intel.c - Implementation of primary alsa driver code base + * for Intel HD Audio. + * + * Copyright(c) 2004 Intel Corporation. All rights reserved. + * + * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de> + * PeiSen Hou <pshou@realtek.com.tw> + * + * CONTACTS: + * + * Matt Jared matt.jared@intel.com + * Andy Kopp andy.kopp@intel.com + * Dan Kogan dan.d.kogan@intel.com + * + * CHANGES: + * + * 2004.12.01 Major rewrite by tiwai, merged the work of pshou + */ + +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/mutex.h> +#include <linux/io.h> +#include <linux/pm_runtime.h> +#include <linux/clocksource.h> +#include <linux/time.h> +#include <linux/completion.h> +#include <linux/acpi.h> +#include <linux/pgtable.h> + +#ifdef CONFIG_X86 +/* for snoop control */ +#include <asm/set_memory.h> +#include <asm/cpufeature.h> +#endif +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/hdaudio.h> +#include <sound/hda_i915.h> +#include <sound/intel-dsp-config.h> +#include <linux/vgaarb.h> +#include <linux/vga_switcheroo.h> +#include <linux/apple-gmux.h> +#include <linux/firmware.h> +#include <sound/hda_codec.h> +#include "hda_controller.h" +#include "hda_intel.h" + +#define CREATE_TRACE_POINTS +#include "hda_intel_trace.h" + +/* position fix mode */ +enum { + POS_FIX_AUTO, + POS_FIX_LPIB, + POS_FIX_POSBUF, + POS_FIX_VIACOMBO, + POS_FIX_COMBO, + POS_FIX_SKL, + POS_FIX_FIFO, +}; + +/* Defines for ATI HD Audio support in SB450 south bridge */ +#define ATI_SB450_HDAUDIO_MISC_CNTR2_ADDR 0x42 +#define ATI_SB450_HDAUDIO_ENABLE_SNOOP 0x02 + +/* Defines for Nvidia HDA support */ +#define NVIDIA_HDA_TRANSREG_ADDR 0x4e +#define NVIDIA_HDA_ENABLE_COHBITS 0x0f +#define NVIDIA_HDA_ISTRM_COH 0x4d +#define NVIDIA_HDA_OSTRM_COH 0x4c +#define NVIDIA_HDA_ENABLE_COHBIT 0x01 + +/* Defines for Intel SCH HDA snoop control */ +#define INTEL_HDA_CGCTL 0x48 +#define INTEL_HDA_CGCTL_MISCBDCGE (0x1 << 6) +#define INTEL_SCH_HDA_DEVC 0x78 +#define INTEL_SCH_HDA_DEVC_NOSNOOP (0x1<<11) + +/* max number of SDs */ +/* ICH, ATI and VIA have 4 playback and 4 capture */ +#define ICH6_NUM_CAPTURE 4 +#define ICH6_NUM_PLAYBACK 4 + +/* ULI has 6 playback and 5 capture */ +#define ULI_NUM_CAPTURE 5 +#define ULI_NUM_PLAYBACK 6 + +/* ATI HDMI may have up to 8 playbacks and 0 capture */ +#define ATIHDMI_NUM_CAPTURE 0 +#define ATIHDMI_NUM_PLAYBACK 8 + + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +static char *model[SNDRV_CARDS]; +static int position_fix[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = -1}; +static int bdl_pos_adj[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = -1}; +static int probe_mask[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = -1}; +static int probe_only[SNDRV_CARDS]; +static int jackpoll_ms[SNDRV_CARDS]; +static int single_cmd = -1; +static int enable_msi = -1; +#ifdef CONFIG_SND_HDA_PATCH_LOADER +static char *patch[SNDRV_CARDS]; +#endif +#ifdef CONFIG_SND_HDA_INPUT_BEEP +static bool beep_mode[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = + CONFIG_SND_HDA_INPUT_BEEP_MODE}; +#endif +static bool dmic_detect = 1; +static bool ctl_dev_id = IS_ENABLED(CONFIG_SND_HDA_CTL_DEV_ID) ? 1 : 0; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Intel HD audio interface."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Intel HD audio interface."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Intel HD audio interface."); +module_param_array(model, charp, NULL, 0444); +MODULE_PARM_DESC(model, "Use the given board model."); +module_param_array(position_fix, int, NULL, 0444); +MODULE_PARM_DESC(position_fix, "DMA pointer read method." + "(-1 = system default, 0 = auto, 1 = LPIB, 2 = POSBUF, 3 = VIACOMBO, 4 = COMBO, 5 = SKL+, 6 = FIFO)."); +module_param_array(bdl_pos_adj, int, NULL, 0644); +MODULE_PARM_DESC(bdl_pos_adj, "BDL position adjustment offset."); +module_param_array(probe_mask, int, NULL, 0444); +MODULE_PARM_DESC(probe_mask, "Bitmask to probe codecs (default = -1)."); +module_param_array(probe_only, int, NULL, 0444); +MODULE_PARM_DESC(probe_only, "Only probing and no codec initialization."); +module_param_array(jackpoll_ms, int, NULL, 0444); +MODULE_PARM_DESC(jackpoll_ms, "Ms between polling for jack events (default = 0, using unsol events only)"); +module_param(single_cmd, bint, 0444); +MODULE_PARM_DESC(single_cmd, "Use single command to communicate with codecs " + "(for debugging only)."); +module_param(enable_msi, bint, 0444); +MODULE_PARM_DESC(enable_msi, "Enable Message Signaled Interrupt (MSI)"); +#ifdef CONFIG_SND_HDA_PATCH_LOADER +module_param_array(patch, charp, NULL, 0444); +MODULE_PARM_DESC(patch, "Patch file for Intel HD audio interface."); +#endif +#ifdef CONFIG_SND_HDA_INPUT_BEEP +module_param_array(beep_mode, bool, NULL, 0444); +MODULE_PARM_DESC(beep_mode, "Select HDA Beep registration mode " + "(0=off, 1=on) (default=1)."); +#endif +module_param(dmic_detect, bool, 0444); +MODULE_PARM_DESC(dmic_detect, "Allow DSP driver selection (bypass this driver) " + "(0=off, 1=on) (default=1); " + "deprecated, use snd-intel-dspcfg.dsp_driver option instead"); +module_param(ctl_dev_id, bool, 0444); +MODULE_PARM_DESC(ctl_dev_id, "Use control device identifier (based on codec address)."); + +#ifdef CONFIG_PM +static int param_set_xint(const char *val, const struct kernel_param *kp); +static const struct kernel_param_ops param_ops_xint = { + .set = param_set_xint, + .get = param_get_int, +}; +#define param_check_xint param_check_int + +static int power_save = CONFIG_SND_HDA_POWER_SAVE_DEFAULT; +module_param(power_save, xint, 0644); +MODULE_PARM_DESC(power_save, "Automatic power-saving timeout " + "(in second, 0 = disable)."); + +static bool pm_blacklist = true; +module_param(pm_blacklist, bool, 0644); +MODULE_PARM_DESC(pm_blacklist, "Enable power-management denylist"); + +/* reset the HD-audio controller in power save mode. + * this may give more power-saving, but will take longer time to + * wake up. + */ +static bool power_save_controller = 1; +module_param(power_save_controller, bool, 0644); +MODULE_PARM_DESC(power_save_controller, "Reset controller in power save mode."); +#else +#define power_save 0 +#endif /* CONFIG_PM */ + +static int align_buffer_size = -1; +module_param(align_buffer_size, bint, 0644); +MODULE_PARM_DESC(align_buffer_size, + "Force buffer and period sizes to be multiple of 128 bytes."); + +#ifdef CONFIG_X86 +static int hda_snoop = -1; +module_param_named(snoop, hda_snoop, bint, 0444); +MODULE_PARM_DESC(snoop, "Enable/disable snooping"); +#else +#define hda_snoop true +#endif + + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Intel HDA driver"); + +#if defined(CONFIG_PM) && defined(CONFIG_VGA_SWITCHEROO) +#if IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI) +#define SUPPORT_VGA_SWITCHEROO +#endif +#endif + + +/* + */ + +/* driver types */ +enum { + AZX_DRIVER_ICH, + AZX_DRIVER_PCH, + AZX_DRIVER_SCH, + AZX_DRIVER_SKL, + AZX_DRIVER_HDMI, + AZX_DRIVER_ATI, + AZX_DRIVER_ATIHDMI, + AZX_DRIVER_ATIHDMI_NS, + AZX_DRIVER_GFHDMI, + AZX_DRIVER_VIA, + AZX_DRIVER_SIS, + AZX_DRIVER_ULI, + AZX_DRIVER_NVIDIA, + AZX_DRIVER_TERA, + AZX_DRIVER_CTX, + AZX_DRIVER_CTHDA, + AZX_DRIVER_CMEDIA, + AZX_DRIVER_ZHAOXIN, + AZX_DRIVER_GENERIC, + AZX_NUM_DRIVERS, /* keep this as last entry */ +}; + +#define azx_get_snoop_type(chip) \ + (((chip)->driver_caps & AZX_DCAPS_SNOOP_MASK) >> 10) +#define AZX_DCAPS_SNOOP_TYPE(type) ((AZX_SNOOP_TYPE_ ## type) << 10) + +/* quirks for old Intel chipsets */ +#define AZX_DCAPS_INTEL_ICH \ + (AZX_DCAPS_OLD_SSYNC | AZX_DCAPS_NO_ALIGN_BUFSIZE) + +/* quirks for Intel PCH */ +#define AZX_DCAPS_INTEL_PCH_BASE \ + (AZX_DCAPS_NO_ALIGN_BUFSIZE | AZX_DCAPS_COUNT_LPIB_DELAY |\ + AZX_DCAPS_SNOOP_TYPE(SCH)) + +/* PCH up to IVB; no runtime PM; bind with i915 gfx */ +#define AZX_DCAPS_INTEL_PCH_NOPM \ + (AZX_DCAPS_INTEL_PCH_BASE | AZX_DCAPS_I915_COMPONENT) + +/* PCH for HSW/BDW; with runtime PM */ +/* no i915 binding for this as HSW/BDW has another controller for HDMI */ +#define AZX_DCAPS_INTEL_PCH \ + (AZX_DCAPS_INTEL_PCH_BASE | AZX_DCAPS_PM_RUNTIME) + +/* HSW HDMI */ +#define AZX_DCAPS_INTEL_HASWELL \ + (/*AZX_DCAPS_ALIGN_BUFSIZE |*/ AZX_DCAPS_COUNT_LPIB_DELAY |\ + AZX_DCAPS_PM_RUNTIME | AZX_DCAPS_I915_COMPONENT |\ + AZX_DCAPS_SNOOP_TYPE(SCH)) + +/* Broadwell HDMI can't use position buffer reliably, force to use LPIB */ +#define AZX_DCAPS_INTEL_BROADWELL \ + (/*AZX_DCAPS_ALIGN_BUFSIZE |*/ AZX_DCAPS_POSFIX_LPIB |\ + AZX_DCAPS_PM_RUNTIME | AZX_DCAPS_I915_COMPONENT |\ + AZX_DCAPS_SNOOP_TYPE(SCH)) + +#define AZX_DCAPS_INTEL_BAYTRAIL \ + (AZX_DCAPS_INTEL_PCH_BASE | AZX_DCAPS_I915_COMPONENT) + +#define AZX_DCAPS_INTEL_BRASWELL \ + (AZX_DCAPS_INTEL_PCH_BASE | AZX_DCAPS_PM_RUNTIME |\ + AZX_DCAPS_I915_COMPONENT) + +#define AZX_DCAPS_INTEL_SKYLAKE \ + (AZX_DCAPS_INTEL_PCH_BASE | AZX_DCAPS_PM_RUNTIME |\ + AZX_DCAPS_SEPARATE_STREAM_TAG | AZX_DCAPS_I915_COMPONENT) + +#define AZX_DCAPS_INTEL_BROXTON AZX_DCAPS_INTEL_SKYLAKE + +/* quirks for ATI SB / AMD Hudson */ +#define AZX_DCAPS_PRESET_ATI_SB \ + (AZX_DCAPS_NO_TCSEL | AZX_DCAPS_POSFIX_LPIB |\ + AZX_DCAPS_SNOOP_TYPE(ATI)) + +/* quirks for ATI/AMD HDMI */ +#define AZX_DCAPS_PRESET_ATI_HDMI \ + (AZX_DCAPS_NO_TCSEL | AZX_DCAPS_POSFIX_LPIB|\ + AZX_DCAPS_NO_MSI64) + +/* quirks for ATI HDMI with snoop off */ +#define AZX_DCAPS_PRESET_ATI_HDMI_NS \ + (AZX_DCAPS_PRESET_ATI_HDMI | AZX_DCAPS_SNOOP_OFF) + +/* quirks for AMD SB */ +#define AZX_DCAPS_PRESET_AMD_SB \ + (AZX_DCAPS_NO_TCSEL | AZX_DCAPS_AMD_WORKAROUND |\ + AZX_DCAPS_SNOOP_TYPE(ATI) | AZX_DCAPS_PM_RUNTIME |\ + AZX_DCAPS_RETRY_PROBE) + +/* quirks for Nvidia */ +#define AZX_DCAPS_PRESET_NVIDIA \ + (AZX_DCAPS_NO_MSI | AZX_DCAPS_CORBRP_SELF_CLEAR |\ + AZX_DCAPS_SNOOP_TYPE(NVIDIA)) + +#define AZX_DCAPS_PRESET_CTHDA \ + (AZX_DCAPS_NO_MSI | AZX_DCAPS_POSFIX_LPIB |\ + AZX_DCAPS_NO_64BIT |\ + AZX_DCAPS_4K_BDLE_BOUNDARY | AZX_DCAPS_SNOOP_OFF) + +/* + * vga_switcheroo support + */ +#ifdef SUPPORT_VGA_SWITCHEROO +#define use_vga_switcheroo(chip) ((chip)->use_vga_switcheroo) +#define needs_eld_notify_link(chip) ((chip)->bus.keep_power) +#else +#define use_vga_switcheroo(chip) 0 +#define needs_eld_notify_link(chip) false +#endif + +#define CONTROLLER_IN_GPU(pci) (((pci)->vendor == 0x8086) && \ + (((pci)->device == 0x0a0c) || \ + ((pci)->device == 0x0c0c) || \ + ((pci)->device == 0x0d0c) || \ + ((pci)->device == 0x160c) || \ + ((pci)->device == 0x490d) || \ + ((pci)->device == 0x4f90) || \ + ((pci)->device == 0x4f91) || \ + ((pci)->device == 0x4f92))) + +#define IS_BXT(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x5a98) + +static const char * const driver_short_names[] = { + [AZX_DRIVER_ICH] = "HDA Intel", + [AZX_DRIVER_PCH] = "HDA Intel PCH", + [AZX_DRIVER_SCH] = "HDA Intel MID", + [AZX_DRIVER_SKL] = "HDA Intel PCH", /* kept old name for compatibility */ + [AZX_DRIVER_HDMI] = "HDA Intel HDMI", + [AZX_DRIVER_ATI] = "HDA ATI SB", + [AZX_DRIVER_ATIHDMI] = "HDA ATI HDMI", + [AZX_DRIVER_ATIHDMI_NS] = "HDA ATI HDMI", + [AZX_DRIVER_GFHDMI] = "HDA GF HDMI", + [AZX_DRIVER_VIA] = "HDA VIA VT82xx", + [AZX_DRIVER_SIS] = "HDA SIS966", + [AZX_DRIVER_ULI] = "HDA ULI M5461", + [AZX_DRIVER_NVIDIA] = "HDA NVidia", + [AZX_DRIVER_TERA] = "HDA Teradici", + [AZX_DRIVER_CTX] = "HDA Creative", + [AZX_DRIVER_CTHDA] = "HDA Creative", + [AZX_DRIVER_CMEDIA] = "HDA C-Media", + [AZX_DRIVER_ZHAOXIN] = "HDA Zhaoxin", + [AZX_DRIVER_GENERIC] = "HD-Audio Generic", +}; + +static int azx_acquire_irq(struct azx *chip, int do_disconnect); +static void set_default_power_save(struct azx *chip); + +/* + * initialize the PCI registers + */ +/* update bits in a PCI register byte */ +static void update_pci_byte(struct pci_dev *pci, unsigned int reg, + unsigned char mask, unsigned char val) +{ + unsigned char data; + + pci_read_config_byte(pci, reg, &data); + data &= ~mask; + data |= (val & mask); + pci_write_config_byte(pci, reg, data); +} + +static void azx_init_pci(struct azx *chip) +{ + int snoop_type = azx_get_snoop_type(chip); + + /* Clear bits 0-2 of PCI register TCSEL (at offset 0x44) + * TCSEL == Traffic Class Select Register, which sets PCI express QOS + * Ensuring these bits are 0 clears playback static on some HD Audio + * codecs. + * The PCI register TCSEL is defined in the Intel manuals. + */ + if (!(chip->driver_caps & AZX_DCAPS_NO_TCSEL)) { + dev_dbg(chip->card->dev, "Clearing TCSEL\n"); + update_pci_byte(chip->pci, AZX_PCIREG_TCSEL, 0x07, 0); + } + + /* For ATI SB450/600/700/800/900 and AMD Hudson azalia HD audio, + * we need to enable snoop. + */ + if (snoop_type == AZX_SNOOP_TYPE_ATI) { + dev_dbg(chip->card->dev, "Setting ATI snoop: %d\n", + azx_snoop(chip)); + update_pci_byte(chip->pci, + ATI_SB450_HDAUDIO_MISC_CNTR2_ADDR, 0x07, + azx_snoop(chip) ? ATI_SB450_HDAUDIO_ENABLE_SNOOP : 0); + } + + /* For NVIDIA HDA, enable snoop */ + if (snoop_type == AZX_SNOOP_TYPE_NVIDIA) { + dev_dbg(chip->card->dev, "Setting Nvidia snoop: %d\n", + azx_snoop(chip)); + update_pci_byte(chip->pci, + NVIDIA_HDA_TRANSREG_ADDR, + 0x0f, NVIDIA_HDA_ENABLE_COHBITS); + update_pci_byte(chip->pci, + NVIDIA_HDA_ISTRM_COH, + 0x01, NVIDIA_HDA_ENABLE_COHBIT); + update_pci_byte(chip->pci, + NVIDIA_HDA_OSTRM_COH, + 0x01, NVIDIA_HDA_ENABLE_COHBIT); + } + + /* Enable SCH/PCH snoop if needed */ + if (snoop_type == AZX_SNOOP_TYPE_SCH) { + unsigned short snoop; + pci_read_config_word(chip->pci, INTEL_SCH_HDA_DEVC, &snoop); + if ((!azx_snoop(chip) && !(snoop & INTEL_SCH_HDA_DEVC_NOSNOOP)) || + (azx_snoop(chip) && (snoop & INTEL_SCH_HDA_DEVC_NOSNOOP))) { + snoop &= ~INTEL_SCH_HDA_DEVC_NOSNOOP; + if (!azx_snoop(chip)) + snoop |= INTEL_SCH_HDA_DEVC_NOSNOOP; + pci_write_config_word(chip->pci, INTEL_SCH_HDA_DEVC, snoop); + pci_read_config_word(chip->pci, + INTEL_SCH_HDA_DEVC, &snoop); + } + dev_dbg(chip->card->dev, "SCH snoop: %s\n", + (snoop & INTEL_SCH_HDA_DEVC_NOSNOOP) ? + "Disabled" : "Enabled"); + } +} + +/* + * In BXT-P A0, HD-Audio DMA requests is later than expected, + * and makes an audio stream sensitive to system latencies when + * 24/32 bits are playing. + * Adjusting threshold of DMA fifo to force the DMA request + * sooner to improve latency tolerance at the expense of power. + */ +static void bxt_reduce_dma_latency(struct azx *chip) +{ + u32 val; + + val = azx_readl(chip, VS_EM4L); + val &= (0x3 << 20); + azx_writel(chip, VS_EM4L, val); +} + +/* + * ML_LCAP bits: + * bit 0: 6 MHz Supported + * bit 1: 12 MHz Supported + * bit 2: 24 MHz Supported + * bit 3: 48 MHz Supported + * bit 4: 96 MHz Supported + * bit 5: 192 MHz Supported + */ +static int intel_get_lctl_scf(struct azx *chip) +{ + struct hdac_bus *bus = azx_bus(chip); + static const int preferred_bits[] = { 2, 3, 1, 4, 5 }; + u32 val, t; + int i; + + val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCAP); + + for (i = 0; i < ARRAY_SIZE(preferred_bits); i++) { + t = preferred_bits[i]; + if (val & (1 << t)) + return t; + } + + dev_warn(chip->card->dev, "set audio clock frequency to 6MHz"); + return 0; +} + +static int intel_ml_lctl_set_power(struct azx *chip, int state) +{ + struct hdac_bus *bus = azx_bus(chip); + u32 val; + int timeout; + + /* + * Changes to LCTL.SCF are only needed for the first multi-link dealing + * with external codecs + */ + val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL); + val &= ~AZX_ML_LCTL_SPA; + val |= state << AZX_ML_LCTL_SPA_SHIFT; + writel(val, bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL); + /* wait for CPA */ + timeout = 50; + while (timeout) { + if (((readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL)) & + AZX_ML_LCTL_CPA) == (state << AZX_ML_LCTL_CPA_SHIFT)) + return 0; + timeout--; + udelay(10); + } + + return -1; +} + +static void intel_init_lctl(struct azx *chip) +{ + struct hdac_bus *bus = azx_bus(chip); + u32 val; + int ret; + + /* 0. check lctl register value is correct or not */ + val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL); + /* only perform additional configurations if the SCF is initially based on 6MHz */ + if ((val & AZX_ML_LCTL_SCF) != 0) + return; + + /* + * Before operating on SPA, CPA must match SPA. + * Any deviation may result in undefined behavior. + */ + if (((val & AZX_ML_LCTL_SPA) >> AZX_ML_LCTL_SPA_SHIFT) != + ((val & AZX_ML_LCTL_CPA) >> AZX_ML_LCTL_CPA_SHIFT)) + return; + + /* 1. turn link down: set SPA to 0 and wait CPA to 0 */ + ret = intel_ml_lctl_set_power(chip, 0); + udelay(100); + if (ret) + goto set_spa; + + /* 2. update SCF to select an audio clock different from 6MHz */ + val &= ~AZX_ML_LCTL_SCF; + val |= intel_get_lctl_scf(chip); + writel(val, bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL); + +set_spa: + /* 4. turn link up: set SPA to 1 and wait CPA to 1 */ + intel_ml_lctl_set_power(chip, 1); + udelay(100); +} + +static void hda_intel_init_chip(struct azx *chip, bool full_reset) +{ + struct hdac_bus *bus = azx_bus(chip); + struct pci_dev *pci = chip->pci; + u32 val; + + snd_hdac_set_codec_wakeup(bus, true); + if (chip->driver_type == AZX_DRIVER_SKL) { + pci_read_config_dword(pci, INTEL_HDA_CGCTL, &val); + val = val & ~INTEL_HDA_CGCTL_MISCBDCGE; + pci_write_config_dword(pci, INTEL_HDA_CGCTL, val); + } + azx_init_chip(chip, full_reset); + if (chip->driver_type == AZX_DRIVER_SKL) { + pci_read_config_dword(pci, INTEL_HDA_CGCTL, &val); + val = val | INTEL_HDA_CGCTL_MISCBDCGE; + pci_write_config_dword(pci, INTEL_HDA_CGCTL, val); + } + + snd_hdac_set_codec_wakeup(bus, false); + + /* reduce dma latency to avoid noise */ + if (IS_BXT(pci)) + bxt_reduce_dma_latency(chip); + + if (bus->mlcap != NULL) + intel_init_lctl(chip); +} + +/* calculate runtime delay from LPIB */ +static int azx_get_delay_from_lpib(struct azx *chip, struct azx_dev *azx_dev, + unsigned int pos) +{ + struct snd_pcm_substream *substream = azx_dev->core.substream; + int stream = substream->stream; + unsigned int lpib_pos = azx_get_pos_lpib(chip, azx_dev); + int delay; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + delay = pos - lpib_pos; + else + delay = lpib_pos - pos; + if (delay < 0) { + if (delay >= azx_dev->core.delay_negative_threshold) + delay = 0; + else + delay += azx_dev->core.bufsize; + } + + if (delay >= azx_dev->core.period_bytes) { + dev_info(chip->card->dev, + "Unstable LPIB (%d >= %d); disabling LPIB delay counting\n", + delay, azx_dev->core.period_bytes); + delay = 0; + chip->driver_caps &= ~AZX_DCAPS_COUNT_LPIB_DELAY; + chip->get_delay[stream] = NULL; + } + + return bytes_to_frames(substream->runtime, delay); +} + +static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev); + +/* called from IRQ */ +static int azx_position_check(struct azx *chip, struct azx_dev *azx_dev) +{ + struct hda_intel *hda = container_of(chip, struct hda_intel, chip); + int ok; + + ok = azx_position_ok(chip, azx_dev); + if (ok == 1) { + azx_dev->irq_pending = 0; + return ok; + } else if (ok == 0) { + /* bogus IRQ, process it later */ + azx_dev->irq_pending = 1; + schedule_work(&hda->irq_pending_work); + } + return 0; +} + +#define display_power(chip, enable) \ + snd_hdac_display_power(azx_bus(chip), HDA_CODEC_IDX_CONTROLLER, enable) + +/* + * Check whether the current DMA position is acceptable for updating + * periods. Returns non-zero if it's OK. + * + * Many HD-audio controllers appear pretty inaccurate about + * the update-IRQ timing. The IRQ is issued before actually the + * data is processed. So, we need to process it afterwords in a + * workqueue. + * + * Returns 1 if OK to proceed, 0 for delay handling, -1 for skipping update + */ +static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev) +{ + struct snd_pcm_substream *substream = azx_dev->core.substream; + struct snd_pcm_runtime *runtime = substream->runtime; + int stream = substream->stream; + u32 wallclk; + unsigned int pos; + snd_pcm_uframes_t hwptr, target; + + wallclk = azx_readl(chip, WALLCLK) - azx_dev->core.start_wallclk; + if (wallclk < (azx_dev->core.period_wallclk * 2) / 3) + return -1; /* bogus (too early) interrupt */ + + if (chip->get_position[stream]) + pos = chip->get_position[stream](chip, azx_dev); + else { /* use the position buffer as default */ + pos = azx_get_pos_posbuf(chip, azx_dev); + if (!pos || pos == (u32)-1) { + dev_info(chip->card->dev, + "Invalid position buffer, using LPIB read method instead.\n"); + chip->get_position[stream] = azx_get_pos_lpib; + if (chip->get_position[0] == azx_get_pos_lpib && + chip->get_position[1] == azx_get_pos_lpib) + azx_bus(chip)->use_posbuf = false; + pos = azx_get_pos_lpib(chip, azx_dev); + chip->get_delay[stream] = NULL; + } else { + chip->get_position[stream] = azx_get_pos_posbuf; + if (chip->driver_caps & AZX_DCAPS_COUNT_LPIB_DELAY) + chip->get_delay[stream] = azx_get_delay_from_lpib; + } + } + + if (pos >= azx_dev->core.bufsize) + pos = 0; + + if (WARN_ONCE(!azx_dev->core.period_bytes, + "hda-intel: zero azx_dev->period_bytes")) + return -1; /* this shouldn't happen! */ + if (wallclk < (azx_dev->core.period_wallclk * 5) / 4 && + pos % azx_dev->core.period_bytes > azx_dev->core.period_bytes / 2) + /* NG - it's below the first next period boundary */ + return chip->bdl_pos_adj ? 0 : -1; + azx_dev->core.start_wallclk += wallclk; + + if (azx_dev->core.no_period_wakeup) + return 1; /* OK, no need to check period boundary */ + + if (runtime->hw_ptr_base != runtime->hw_ptr_interrupt) + return 1; /* OK, already in hwptr updating process */ + + /* check whether the period gets really elapsed */ + pos = bytes_to_frames(runtime, pos); + hwptr = runtime->hw_ptr_base + pos; + if (hwptr < runtime->status->hw_ptr) + hwptr += runtime->buffer_size; + target = runtime->hw_ptr_interrupt + runtime->period_size; + if (hwptr < target) { + /* too early wakeup, process it later */ + return chip->bdl_pos_adj ? 0 : -1; + } + + return 1; /* OK, it's fine */ +} + +/* + * The work for pending PCM period updates. + */ +static void azx_irq_pending_work(struct work_struct *work) +{ + struct hda_intel *hda = container_of(work, struct hda_intel, irq_pending_work); + struct azx *chip = &hda->chip; + struct hdac_bus *bus = azx_bus(chip); + struct hdac_stream *s; + int pending, ok; + + if (!hda->irq_pending_warned) { + dev_info(chip->card->dev, + "IRQ timing workaround is activated for card #%d. Suggest a bigger bdl_pos_adj.\n", + chip->card->number); + hda->irq_pending_warned = 1; + } + + for (;;) { + pending = 0; + spin_lock_irq(&bus->reg_lock); + list_for_each_entry(s, &bus->stream_list, list) { + struct azx_dev *azx_dev = stream_to_azx_dev(s); + if (!azx_dev->irq_pending || + !s->substream || + !s->running) + continue; + ok = azx_position_ok(chip, azx_dev); + if (ok > 0) { + azx_dev->irq_pending = 0; + spin_unlock(&bus->reg_lock); + snd_pcm_period_elapsed(s->substream); + spin_lock(&bus->reg_lock); + } else if (ok < 0) { + pending = 0; /* too early */ + } else + pending++; + } + spin_unlock_irq(&bus->reg_lock); + if (!pending) + return; + msleep(1); + } +} + +/* clear irq_pending flags and assure no on-going workq */ +static void azx_clear_irq_pending(struct azx *chip) +{ + struct hdac_bus *bus = azx_bus(chip); + struct hdac_stream *s; + + spin_lock_irq(&bus->reg_lock); + list_for_each_entry(s, &bus->stream_list, list) { + struct azx_dev *azx_dev = stream_to_azx_dev(s); + azx_dev->irq_pending = 0; + } + spin_unlock_irq(&bus->reg_lock); +} + +static int azx_acquire_irq(struct azx *chip, int do_disconnect) +{ + struct hdac_bus *bus = azx_bus(chip); + + if (request_irq(chip->pci->irq, azx_interrupt, + chip->msi ? 0 : IRQF_SHARED, + chip->card->irq_descr, chip)) { + dev_err(chip->card->dev, + "unable to grab IRQ %d, disabling device\n", + chip->pci->irq); + if (do_disconnect) + snd_card_disconnect(chip->card); + return -1; + } + bus->irq = chip->pci->irq; + chip->card->sync_irq = bus->irq; + pci_intx(chip->pci, !chip->msi); + return 0; +} + +/* get the current DMA position with correction on VIA chips */ +static unsigned int azx_via_get_position(struct azx *chip, + struct azx_dev *azx_dev) +{ + unsigned int link_pos, mini_pos, bound_pos; + unsigned int mod_link_pos, mod_dma_pos, mod_mini_pos; + unsigned int fifo_size; + + link_pos = snd_hdac_stream_get_pos_lpib(azx_stream(azx_dev)); + if (azx_dev->core.substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* Playback, no problem using link position */ + return link_pos; + } + + /* Capture */ + /* For new chipset, + * use mod to get the DMA position just like old chipset + */ + mod_dma_pos = le32_to_cpu(*azx_dev->core.posbuf); + mod_dma_pos %= azx_dev->core.period_bytes; + + fifo_size = azx_stream(azx_dev)->fifo_size - 1; + + if (azx_dev->insufficient) { + /* Link position never gather than FIFO size */ + if (link_pos <= fifo_size) + return 0; + + azx_dev->insufficient = 0; + } + + if (link_pos <= fifo_size) + mini_pos = azx_dev->core.bufsize + link_pos - fifo_size; + else + mini_pos = link_pos - fifo_size; + + /* Find nearest previous boudary */ + mod_mini_pos = mini_pos % azx_dev->core.period_bytes; + mod_link_pos = link_pos % azx_dev->core.period_bytes; + if (mod_link_pos >= fifo_size) + bound_pos = link_pos - mod_link_pos; + else if (mod_dma_pos >= mod_mini_pos) + bound_pos = mini_pos - mod_mini_pos; + else { + bound_pos = mini_pos - mod_mini_pos + azx_dev->core.period_bytes; + if (bound_pos >= azx_dev->core.bufsize) + bound_pos = 0; + } + + /* Calculate real DMA position we want */ + return bound_pos + mod_dma_pos; +} + +#define AMD_FIFO_SIZE 32 + +/* get the current DMA position with FIFO size correction */ +static unsigned int azx_get_pos_fifo(struct azx *chip, struct azx_dev *azx_dev) +{ + struct snd_pcm_substream *substream = azx_dev->core.substream; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int pos, delay; + + pos = snd_hdac_stream_get_pos_lpib(azx_stream(azx_dev)); + if (!runtime) + return pos; + + runtime->delay = AMD_FIFO_SIZE; + delay = frames_to_bytes(runtime, AMD_FIFO_SIZE); + if (azx_dev->insufficient) { + if (pos < delay) { + delay = pos; + runtime->delay = bytes_to_frames(runtime, pos); + } else { + azx_dev->insufficient = 0; + } + } + + /* correct the DMA position for capture stream */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (pos < delay) + pos += azx_dev->core.bufsize; + pos -= delay; + } + + return pos; +} + +static int azx_get_delay_from_fifo(struct azx *chip, struct azx_dev *azx_dev, + unsigned int pos) +{ + struct snd_pcm_substream *substream = azx_dev->core.substream; + + /* just read back the calculated value in the above */ + return substream->runtime->delay; +} + +static void __azx_shutdown_chip(struct azx *chip, bool skip_link_reset) +{ + azx_stop_chip(chip); + if (!skip_link_reset) + azx_enter_link_reset(chip); + azx_clear_irq_pending(chip); + display_power(chip, false); +} + +#ifdef CONFIG_PM +static DEFINE_MUTEX(card_list_lock); +static LIST_HEAD(card_list); + +static void azx_shutdown_chip(struct azx *chip) +{ + __azx_shutdown_chip(chip, false); +} + +static void azx_add_card_list(struct azx *chip) +{ + struct hda_intel *hda = container_of(chip, struct hda_intel, chip); + mutex_lock(&card_list_lock); + list_add(&hda->list, &card_list); + mutex_unlock(&card_list_lock); +} + +static void azx_del_card_list(struct azx *chip) +{ + struct hda_intel *hda = container_of(chip, struct hda_intel, chip); + mutex_lock(&card_list_lock); + list_del_init(&hda->list); + mutex_unlock(&card_list_lock); +} + +/* trigger power-save check at writing parameter */ +static int param_set_xint(const char *val, const struct kernel_param *kp) +{ + struct hda_intel *hda; + struct azx *chip; + int prev = power_save; + int ret = param_set_int(val, kp); + + if (ret || prev == power_save) + return ret; + + mutex_lock(&card_list_lock); + list_for_each_entry(hda, &card_list, list) { + chip = &hda->chip; + if (!hda->probe_continued || chip->disabled) + continue; + snd_hda_set_power_save(&chip->bus, power_save * 1000); + } + mutex_unlock(&card_list_lock); + return 0; +} + +/* + * power management + */ +static bool azx_is_pm_ready(struct snd_card *card) +{ + struct azx *chip; + struct hda_intel *hda; + + if (!card) + return false; + chip = card->private_data; + hda = container_of(chip, struct hda_intel, chip); + if (chip->disabled || hda->init_failed || !chip->running) + return false; + return true; +} + +static void __azx_runtime_resume(struct azx *chip) +{ + struct hda_intel *hda = container_of(chip, struct hda_intel, chip); + struct hdac_bus *bus = azx_bus(chip); + struct hda_codec *codec; + int status; + + display_power(chip, true); + if (hda->need_i915_power) + snd_hdac_i915_set_bclk(bus); + + /* Read STATESTS before controller reset */ + status = azx_readw(chip, STATESTS); + + azx_init_pci(chip); + hda_intel_init_chip(chip, true); + + /* Avoid codec resume if runtime resume is for system suspend */ + if (!chip->pm_prepared) { + list_for_each_codec(codec, &chip->bus) { + if (codec->relaxed_resume) + continue; + + if (codec->forced_resume || (status & (1 << codec->addr))) + pm_request_resume(hda_codec_dev(codec)); + } + } + + /* power down again for link-controlled chips */ + if (!hda->need_i915_power) + display_power(chip, false); +} + +#ifdef CONFIG_PM_SLEEP +static int azx_prepare(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + + if (!azx_is_pm_ready(card)) + return 0; + + chip = card->private_data; + chip->pm_prepared = 1; + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + + flush_work(&azx_bus(chip)->unsol_work); + + /* HDA controller always requires different WAKEEN for runtime suspend + * and system suspend, so don't use direct-complete here. + */ + return 0; +} + +static void azx_complete(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + + if (!azx_is_pm_ready(card)) + return; + + chip = card->private_data; + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + chip->pm_prepared = 0; +} + +static int azx_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + struct hdac_bus *bus; + + if (!azx_is_pm_ready(card)) + return 0; + + chip = card->private_data; + bus = azx_bus(chip); + azx_shutdown_chip(chip); + if (bus->irq >= 0) { + free_irq(bus->irq, chip); + bus->irq = -1; + chip->card->sync_irq = -1; + } + + if (chip->msi) + pci_disable_msi(chip->pci); + + trace_azx_suspend(chip); + return 0; +} + +static int azx_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + + if (!azx_is_pm_ready(card)) + return 0; + + chip = card->private_data; + if (chip->msi) + if (pci_enable_msi(chip->pci) < 0) + chip->msi = 0; + if (azx_acquire_irq(chip, 1) < 0) + return -EIO; + + __azx_runtime_resume(chip); + + trace_azx_resume(chip); + return 0; +} + +/* put codec down to D3 at hibernation for Intel SKL+; + * otherwise BIOS may still access the codec and screw up the driver + */ +static int azx_freeze_noirq(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip = card->private_data; + struct pci_dev *pci = to_pci_dev(dev); + + if (!azx_is_pm_ready(card)) + return 0; + if (chip->driver_type == AZX_DRIVER_SKL) + pci_set_power_state(pci, PCI_D3hot); + + return 0; +} + +static int azx_thaw_noirq(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip = card->private_data; + struct pci_dev *pci = to_pci_dev(dev); + + if (!azx_is_pm_ready(card)) + return 0; + if (chip->driver_type == AZX_DRIVER_SKL) + pci_set_power_state(pci, PCI_D0); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static int azx_runtime_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + + if (!azx_is_pm_ready(card)) + return 0; + chip = card->private_data; + + /* enable controller wake up event */ + azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) | STATESTS_INT_MASK); + + azx_shutdown_chip(chip); + trace_azx_runtime_suspend(chip); + return 0; +} + +static int azx_runtime_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + + if (!azx_is_pm_ready(card)) + return 0; + chip = card->private_data; + __azx_runtime_resume(chip); + + /* disable controller Wake Up event*/ + azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) & ~STATESTS_INT_MASK); + + trace_azx_runtime_resume(chip); + return 0; +} + +static int azx_runtime_idle(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + struct hda_intel *hda; + + if (!card) + return 0; + + chip = card->private_data; + hda = container_of(chip, struct hda_intel, chip); + if (chip->disabled || hda->init_failed) + return 0; + + if (!power_save_controller || !azx_has_pm_runtime(chip) || + azx_bus(chip)->codec_powered || !chip->running) + return -EBUSY; + + /* ELD notification gets broken when HD-audio bus is off */ + if (needs_eld_notify_link(chip)) + return -EBUSY; + + return 0; +} + +static const struct dev_pm_ops azx_pm = { + SET_SYSTEM_SLEEP_PM_OPS(azx_suspend, azx_resume) +#ifdef CONFIG_PM_SLEEP + .prepare = azx_prepare, + .complete = azx_complete, + .freeze_noirq = azx_freeze_noirq, + .thaw_noirq = azx_thaw_noirq, +#endif + SET_RUNTIME_PM_OPS(azx_runtime_suspend, azx_runtime_resume, azx_runtime_idle) +}; + +#define AZX_PM_OPS &azx_pm +#else +#define azx_add_card_list(chip) /* NOP */ +#define azx_del_card_list(chip) /* NOP */ +#define AZX_PM_OPS NULL +#endif /* CONFIG_PM */ + + +static int azx_probe_continue(struct azx *chip); + +#ifdef SUPPORT_VGA_SWITCHEROO +static struct pci_dev *get_bound_vga(struct pci_dev *pci); + +static void azx_vs_set_state(struct pci_dev *pci, + enum vga_switcheroo_state state) +{ + struct snd_card *card = pci_get_drvdata(pci); + struct azx *chip = card->private_data; + struct hda_intel *hda = container_of(chip, struct hda_intel, chip); + struct hda_codec *codec; + bool disabled; + + wait_for_completion(&hda->probe_wait); + if (hda->init_failed) + return; + + disabled = (state == VGA_SWITCHEROO_OFF); + if (chip->disabled == disabled) + return; + + if (!hda->probe_continued) { + chip->disabled = disabled; + if (!disabled) { + dev_info(chip->card->dev, + "Start delayed initialization\n"); + if (azx_probe_continue(chip) < 0) + dev_err(chip->card->dev, "initialization error\n"); + } + } else { + dev_info(chip->card->dev, "%s via vga_switcheroo\n", + disabled ? "Disabling" : "Enabling"); + if (disabled) { + list_for_each_codec(codec, &chip->bus) { + pm_runtime_suspend(hda_codec_dev(codec)); + pm_runtime_disable(hda_codec_dev(codec)); + } + pm_runtime_suspend(card->dev); + pm_runtime_disable(card->dev); + /* when we get suspended by vga_switcheroo we end up in D3cold, + * however we have no ACPI handle, so pci/acpi can't put us there, + * put ourselves there */ + pci->current_state = PCI_D3cold; + chip->disabled = true; + if (snd_hda_lock_devices(&chip->bus)) + dev_warn(chip->card->dev, + "Cannot lock devices!\n"); + } else { + snd_hda_unlock_devices(&chip->bus); + chip->disabled = false; + pm_runtime_enable(card->dev); + list_for_each_codec(codec, &chip->bus) { + pm_runtime_enable(hda_codec_dev(codec)); + pm_runtime_resume(hda_codec_dev(codec)); + } + } + } +} + +static bool azx_vs_can_switch(struct pci_dev *pci) +{ + struct snd_card *card = pci_get_drvdata(pci); + struct azx *chip = card->private_data; + struct hda_intel *hda = container_of(chip, struct hda_intel, chip); + + wait_for_completion(&hda->probe_wait); + if (hda->init_failed) + return false; + if (chip->disabled || !hda->probe_continued) + return true; + if (snd_hda_lock_devices(&chip->bus)) + return false; + snd_hda_unlock_devices(&chip->bus); + return true; +} + +/* + * The discrete GPU cannot power down unless the HDA controller runtime + * suspends, so activate runtime PM on codecs even if power_save == 0. + */ +static void setup_vga_switcheroo_runtime_pm(struct azx *chip) +{ + struct hda_intel *hda = container_of(chip, struct hda_intel, chip); + struct hda_codec *codec; + + if (hda->use_vga_switcheroo && !needs_eld_notify_link(chip)) { + list_for_each_codec(codec, &chip->bus) + codec->auto_runtime_pm = 1; + /* reset the power save setup */ + if (chip->running) + set_default_power_save(chip); + } +} + +static void azx_vs_gpu_bound(struct pci_dev *pci, + enum vga_switcheroo_client_id client_id) +{ + struct snd_card *card = pci_get_drvdata(pci); + struct azx *chip = card->private_data; + + if (client_id == VGA_SWITCHEROO_DIS) + chip->bus.keep_power = 0; + setup_vga_switcheroo_runtime_pm(chip); +} + +static void init_vga_switcheroo(struct azx *chip) +{ + struct hda_intel *hda = container_of(chip, struct hda_intel, chip); + struct pci_dev *p = get_bound_vga(chip->pci); + struct pci_dev *parent; + if (p) { + dev_info(chip->card->dev, + "Handle vga_switcheroo audio client\n"); + hda->use_vga_switcheroo = 1; + + /* cleared in either gpu_bound op or codec probe, or when its + * upstream port has _PR3 (i.e. dGPU). + */ + parent = pci_upstream_bridge(p); + chip->bus.keep_power = parent ? !pci_pr3_present(parent) : 1; + chip->driver_caps |= AZX_DCAPS_PM_RUNTIME; + pci_dev_put(p); + } +} + +static const struct vga_switcheroo_client_ops azx_vs_ops = { + .set_gpu_state = azx_vs_set_state, + .can_switch = azx_vs_can_switch, + .gpu_bound = azx_vs_gpu_bound, +}; + +static int register_vga_switcheroo(struct azx *chip) +{ + struct hda_intel *hda = container_of(chip, struct hda_intel, chip); + struct pci_dev *p; + int err; + + if (!hda->use_vga_switcheroo) + return 0; + + p = get_bound_vga(chip->pci); + err = vga_switcheroo_register_audio_client(chip->pci, &azx_vs_ops, p); + pci_dev_put(p); + + if (err < 0) + return err; + hda->vga_switcheroo_registered = 1; + + return 0; +} +#else +#define init_vga_switcheroo(chip) /* NOP */ +#define register_vga_switcheroo(chip) 0 +#define check_hdmi_disabled(pci) false +#define setup_vga_switcheroo_runtime_pm(chip) /* NOP */ +#endif /* SUPPORT_VGA_SWITCHER */ + +/* + * destructor + */ +static void azx_free(struct azx *chip) +{ + struct pci_dev *pci = chip->pci; + struct hda_intel *hda = container_of(chip, struct hda_intel, chip); + struct hdac_bus *bus = azx_bus(chip); + + if (hda->freed) + return; + + if (azx_has_pm_runtime(chip) && chip->running) { + pm_runtime_get_noresume(&pci->dev); + pm_runtime_forbid(&pci->dev); + pm_runtime_dont_use_autosuspend(&pci->dev); + } + + chip->running = 0; + + azx_del_card_list(chip); + + hda->init_failed = 1; /* to be sure */ + complete_all(&hda->probe_wait); + + if (use_vga_switcheroo(hda)) { + if (chip->disabled && hda->probe_continued) + snd_hda_unlock_devices(&chip->bus); + if (hda->vga_switcheroo_registered) + vga_switcheroo_unregister_client(chip->pci); + } + + if (bus->chip_init) { + azx_clear_irq_pending(chip); + azx_stop_all_streams(chip); + azx_stop_chip(chip); + } + + if (bus->irq >= 0) + free_irq(bus->irq, (void*)chip); + + azx_free_stream_pages(chip); + azx_free_streams(chip); + snd_hdac_bus_exit(bus); + +#ifdef CONFIG_SND_HDA_PATCH_LOADER + release_firmware(chip->fw); +#endif + display_power(chip, false); + + if (chip->driver_caps & AZX_DCAPS_I915_COMPONENT) + snd_hdac_i915_exit(bus); + + hda->freed = 1; +} + +static int azx_dev_disconnect(struct snd_device *device) +{ + struct azx *chip = device->device_data; + struct hdac_bus *bus = azx_bus(chip); + + chip->bus.shutdown = 1; + cancel_work_sync(&bus->unsol_work); + + return 0; +} + +static int azx_dev_free(struct snd_device *device) +{ + azx_free(device->device_data); + return 0; +} + +#ifdef SUPPORT_VGA_SWITCHEROO +#ifdef CONFIG_ACPI +/* ATPX is in the integrated GPU's namespace */ +static bool atpx_present(void) +{ + struct pci_dev *pdev = NULL; + acpi_handle dhandle, atpx_handle; + acpi_status status; + + while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev)) != NULL) { + dhandle = ACPI_HANDLE(&pdev->dev); + if (dhandle) { + status = acpi_get_handle(dhandle, "ATPX", &atpx_handle); + if (ACPI_SUCCESS(status)) { + pci_dev_put(pdev); + return true; + } + } + } + while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_OTHER << 8, pdev)) != NULL) { + dhandle = ACPI_HANDLE(&pdev->dev); + if (dhandle) { + status = acpi_get_handle(dhandle, "ATPX", &atpx_handle); + if (ACPI_SUCCESS(status)) { + pci_dev_put(pdev); + return true; + } + } + } + return false; +} +#else +static bool atpx_present(void) +{ + return false; +} +#endif + +/* + * Check of disabled HDMI controller by vga_switcheroo + */ +static struct pci_dev *get_bound_vga(struct pci_dev *pci) +{ + struct pci_dev *p; + + /* check only discrete GPU */ + switch (pci->vendor) { + case PCI_VENDOR_ID_ATI: + case PCI_VENDOR_ID_AMD: + if (pci->devfn == 1) { + p = pci_get_domain_bus_and_slot(pci_domain_nr(pci->bus), + pci->bus->number, 0); + if (p) { + /* ATPX is in the integrated GPU's ACPI namespace + * rather than the dGPU's namespace. However, + * the dGPU is the one who is involved in + * vgaswitcheroo. + */ + if (((p->class >> 16) == PCI_BASE_CLASS_DISPLAY) && + (atpx_present() || apple_gmux_detect(NULL, NULL))) + return p; + pci_dev_put(p); + } + } + break; + case PCI_VENDOR_ID_NVIDIA: + if (pci->devfn == 1) { + p = pci_get_domain_bus_and_slot(pci_domain_nr(pci->bus), + pci->bus->number, 0); + if (p) { + if ((p->class >> 16) == PCI_BASE_CLASS_DISPLAY) + return p; + pci_dev_put(p); + } + } + break; + } + return NULL; +} + +static bool check_hdmi_disabled(struct pci_dev *pci) +{ + bool vga_inactive = false; + struct pci_dev *p = get_bound_vga(pci); + + if (p) { + if (vga_switcheroo_get_client_state(p) == VGA_SWITCHEROO_OFF) + vga_inactive = true; + pci_dev_put(p); + } + return vga_inactive; +} +#endif /* SUPPORT_VGA_SWITCHEROO */ + +/* + * allow/deny-listing for position_fix + */ +static const struct snd_pci_quirk position_fix_list[] = { + SND_PCI_QUIRK(0x1028, 0x01cc, "Dell D820", POS_FIX_LPIB), + SND_PCI_QUIRK(0x1028, 0x01de, "Dell Precision 390", POS_FIX_LPIB), + SND_PCI_QUIRK(0x103c, 0x306d, "HP dv3", POS_FIX_LPIB), + SND_PCI_QUIRK(0x1043, 0x813d, "ASUS P5AD2", POS_FIX_LPIB), + SND_PCI_QUIRK(0x1043, 0x81b3, "ASUS", POS_FIX_LPIB), + SND_PCI_QUIRK(0x1043, 0x81e7, "ASUS M2V", POS_FIX_LPIB), + SND_PCI_QUIRK(0x104d, 0x9069, "Sony VPCS11V9E", POS_FIX_LPIB), + SND_PCI_QUIRK(0x10de, 0xcb89, "Macbook Pro 7,1", POS_FIX_LPIB), + SND_PCI_QUIRK(0x1297, 0x3166, "Shuttle", POS_FIX_LPIB), + SND_PCI_QUIRK(0x1458, 0xa022, "ga-ma770-ud3", POS_FIX_LPIB), + SND_PCI_QUIRK(0x1462, 0x1002, "MSI Wind U115", POS_FIX_LPIB), + SND_PCI_QUIRK(0x1565, 0x8218, "Biostar Microtech", POS_FIX_LPIB), + SND_PCI_QUIRK(0x1849, 0x0888, "775Dual-VSTA", POS_FIX_LPIB), + SND_PCI_QUIRK(0x8086, 0x2503, "DG965OT AAD63733-203", POS_FIX_LPIB), + {} +}; + +static int check_position_fix(struct azx *chip, int fix) +{ + const struct snd_pci_quirk *q; + + switch (fix) { + case POS_FIX_AUTO: + case POS_FIX_LPIB: + case POS_FIX_POSBUF: + case POS_FIX_VIACOMBO: + case POS_FIX_COMBO: + case POS_FIX_SKL: + case POS_FIX_FIFO: + return fix; + } + + q = snd_pci_quirk_lookup(chip->pci, position_fix_list); + if (q) { + dev_info(chip->card->dev, + "position_fix set to %d for device %04x:%04x\n", + q->value, q->subvendor, q->subdevice); + return q->value; + } + + /* Check VIA/ATI HD Audio Controller exist */ + if (chip->driver_type == AZX_DRIVER_VIA) { + dev_dbg(chip->card->dev, "Using VIACOMBO position fix\n"); + return POS_FIX_VIACOMBO; + } + if (chip->driver_caps & AZX_DCAPS_AMD_WORKAROUND) { + dev_dbg(chip->card->dev, "Using FIFO position fix\n"); + return POS_FIX_FIFO; + } + if (chip->driver_caps & AZX_DCAPS_POSFIX_LPIB) { + dev_dbg(chip->card->dev, "Using LPIB position fix\n"); + return POS_FIX_LPIB; + } + if (chip->driver_type == AZX_DRIVER_SKL) { + dev_dbg(chip->card->dev, "Using SKL position fix\n"); + return POS_FIX_SKL; + } + return POS_FIX_AUTO; +} + +static void assign_position_fix(struct azx *chip, int fix) +{ + static const azx_get_pos_callback_t callbacks[] = { + [POS_FIX_AUTO] = NULL, + [POS_FIX_LPIB] = azx_get_pos_lpib, + [POS_FIX_POSBUF] = azx_get_pos_posbuf, + [POS_FIX_VIACOMBO] = azx_via_get_position, + [POS_FIX_COMBO] = azx_get_pos_lpib, + [POS_FIX_SKL] = azx_get_pos_posbuf, + [POS_FIX_FIFO] = azx_get_pos_fifo, + }; + + chip->get_position[0] = chip->get_position[1] = callbacks[fix]; + + /* combo mode uses LPIB only for playback */ + if (fix == POS_FIX_COMBO) + chip->get_position[1] = NULL; + + if ((fix == POS_FIX_POSBUF || fix == POS_FIX_SKL) && + (chip->driver_caps & AZX_DCAPS_COUNT_LPIB_DELAY)) { + chip->get_delay[0] = chip->get_delay[1] = + azx_get_delay_from_lpib; + } + + if (fix == POS_FIX_FIFO) + chip->get_delay[0] = chip->get_delay[1] = + azx_get_delay_from_fifo; +} + +/* + * deny-lists for probe_mask + */ +static const struct snd_pci_quirk probe_mask_list[] = { + /* Thinkpad often breaks the controller communication when accessing + * to the non-working (or non-existing) modem codec slot. + */ + SND_PCI_QUIRK(0x1014, 0x05b7, "Thinkpad Z60", 0x01), + SND_PCI_QUIRK(0x17aa, 0x2010, "Thinkpad X/T/R60", 0x01), + SND_PCI_QUIRK(0x17aa, 0x20ac, "Thinkpad X/T/R61", 0x01), + /* broken BIOS */ + SND_PCI_QUIRK(0x1028, 0x20ac, "Dell Studio Desktop", 0x01), + /* including bogus ALC268 in slot#2 that conflicts with ALC888 */ + SND_PCI_QUIRK(0x17c0, 0x4085, "Medion MD96630", 0x01), + /* forced codec slots */ + SND_PCI_QUIRK(0x1043, 0x1262, "ASUS W5Fm", 0x103), + SND_PCI_QUIRK(0x1046, 0x1262, "ASUS W5F", 0x103), + SND_PCI_QUIRK(0x1558, 0x0351, "Schenker Dock 15", 0x105), + /* WinFast VP200 H (Teradici) user reported broken communication */ + SND_PCI_QUIRK(0x3a21, 0x040d, "WinFast VP200 H", 0x101), + {} +}; + +#define AZX_FORCE_CODEC_MASK 0x100 + +static void check_probe_mask(struct azx *chip, int dev) +{ + const struct snd_pci_quirk *q; + + chip->codec_probe_mask = probe_mask[dev]; + if (chip->codec_probe_mask == -1) { + q = snd_pci_quirk_lookup(chip->pci, probe_mask_list); + if (q) { + dev_info(chip->card->dev, + "probe_mask set to 0x%x for device %04x:%04x\n", + q->value, q->subvendor, q->subdevice); + chip->codec_probe_mask = q->value; + } + } + + /* check forced option */ + if (chip->codec_probe_mask != -1 && + (chip->codec_probe_mask & AZX_FORCE_CODEC_MASK)) { + azx_bus(chip)->codec_mask = chip->codec_probe_mask & 0xff; + dev_info(chip->card->dev, "codec_mask forced to 0x%x\n", + (int)azx_bus(chip)->codec_mask); + } +} + +/* + * allow/deny-list for enable_msi + */ +static const struct snd_pci_quirk msi_deny_list[] = { + SND_PCI_QUIRK(0x103c, 0x2191, "HP", 0), /* AMD Hudson */ + SND_PCI_QUIRK(0x103c, 0x2192, "HP", 0), /* AMD Hudson */ + SND_PCI_QUIRK(0x103c, 0x21f7, "HP", 0), /* AMD Hudson */ + SND_PCI_QUIRK(0x103c, 0x21fa, "HP", 0), /* AMD Hudson */ + SND_PCI_QUIRK(0x1043, 0x81f2, "ASUS", 0), /* Athlon64 X2 + nvidia */ + SND_PCI_QUIRK(0x1043, 0x81f6, "ASUS", 0), /* nvidia */ + SND_PCI_QUIRK(0x1043, 0x822d, "ASUS", 0), /* Athlon64 X2 + nvidia MCP55 */ + SND_PCI_QUIRK(0x1179, 0xfb44, "Toshiba Satellite C870", 0), /* AMD Hudson */ + SND_PCI_QUIRK(0x1849, 0x0888, "ASRock", 0), /* Athlon64 X2 + nvidia */ + SND_PCI_QUIRK(0xa0a0, 0x0575, "Aopen MZ915-M", 0), /* ICH6 */ + {} +}; + +static void check_msi(struct azx *chip) +{ + const struct snd_pci_quirk *q; + + if (enable_msi >= 0) { + chip->msi = !!enable_msi; + return; + } + chip->msi = 1; /* enable MSI as default */ + q = snd_pci_quirk_lookup(chip->pci, msi_deny_list); + if (q) { + dev_info(chip->card->dev, + "msi for device %04x:%04x set to %d\n", + q->subvendor, q->subdevice, q->value); + chip->msi = q->value; + return; + } + + /* NVidia chipsets seem to cause troubles with MSI */ + if (chip->driver_caps & AZX_DCAPS_NO_MSI) { + dev_info(chip->card->dev, "Disabling MSI\n"); + chip->msi = 0; + } +} + +/* check the snoop mode availability */ +static void azx_check_snoop_available(struct azx *chip) +{ + int snoop = hda_snoop; + + if (snoop >= 0) { + dev_info(chip->card->dev, "Force to %s mode by module option\n", + snoop ? "snoop" : "non-snoop"); + chip->snoop = snoop; + chip->uc_buffer = !snoop; + return; + } + + snoop = true; + if (azx_get_snoop_type(chip) == AZX_SNOOP_TYPE_NONE && + chip->driver_type == AZX_DRIVER_VIA) { + /* force to non-snoop mode for a new VIA controller + * when BIOS is set + */ + u8 val; + pci_read_config_byte(chip->pci, 0x42, &val); + if (!(val & 0x80) && (chip->pci->revision == 0x30 || + chip->pci->revision == 0x20)) + snoop = false; + } + + if (chip->driver_caps & AZX_DCAPS_SNOOP_OFF) + snoop = false; + + chip->snoop = snoop; + if (!snoop) { + dev_info(chip->card->dev, "Force to non-snoop mode\n"); + /* C-Media requires non-cached pages only for CORB/RIRB */ + if (chip->driver_type != AZX_DRIVER_CMEDIA) + chip->uc_buffer = true; + } +} + +static void azx_probe_work(struct work_struct *work) +{ + struct hda_intel *hda = container_of(work, struct hda_intel, probe_work.work); + azx_probe_continue(&hda->chip); +} + +static int default_bdl_pos_adj(struct azx *chip) +{ + /* some exceptions: Atoms seem problematic with value 1 */ + if (chip->pci->vendor == PCI_VENDOR_ID_INTEL) { + switch (chip->pci->device) { + case 0x0f04: /* Baytrail */ + case 0x2284: /* Braswell */ + return 32; + } + } + + switch (chip->driver_type) { + /* + * increase the bdl size for Glenfly Gpus for hardware + * limitation on hdac interrupt interval + */ + case AZX_DRIVER_GFHDMI: + return 128; + case AZX_DRIVER_ICH: + case AZX_DRIVER_PCH: + return 1; + default: + return 32; + } +} + +/* + * constructor + */ +static const struct hda_controller_ops pci_hda_ops; + +static int azx_create(struct snd_card *card, struct pci_dev *pci, + int dev, unsigned int driver_caps, + struct azx **rchip) +{ + static const struct snd_device_ops ops = { + .dev_disconnect = azx_dev_disconnect, + .dev_free = azx_dev_free, + }; + struct hda_intel *hda; + struct azx *chip; + int err; + + *rchip = NULL; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + hda = devm_kzalloc(&pci->dev, sizeof(*hda), GFP_KERNEL); + if (!hda) + return -ENOMEM; + + chip = &hda->chip; + mutex_init(&chip->open_mutex); + chip->card = card; + chip->pci = pci; + chip->ops = &pci_hda_ops; + chip->driver_caps = driver_caps; + chip->driver_type = driver_caps & 0xff; + check_msi(chip); + chip->dev_index = dev; + if (jackpoll_ms[dev] >= 50 && jackpoll_ms[dev] <= 60000) + chip->jackpoll_interval = msecs_to_jiffies(jackpoll_ms[dev]); + INIT_LIST_HEAD(&chip->pcm_list); + INIT_WORK(&hda->irq_pending_work, azx_irq_pending_work); + INIT_LIST_HEAD(&hda->list); + init_vga_switcheroo(chip); + init_completion(&hda->probe_wait); + + assign_position_fix(chip, check_position_fix(chip, position_fix[dev])); + + if (single_cmd < 0) /* allow fallback to single_cmd at errors */ + chip->fallback_to_single_cmd = 1; + else /* explicitly set to single_cmd or not */ + chip->single_cmd = single_cmd; + + azx_check_snoop_available(chip); + + if (bdl_pos_adj[dev] < 0) + chip->bdl_pos_adj = default_bdl_pos_adj(chip); + else + chip->bdl_pos_adj = bdl_pos_adj[dev]; + + err = azx_bus_init(chip, model[dev]); + if (err < 0) + return err; + + /* use the non-cached pages in non-snoop mode */ + if (!azx_snoop(chip)) + azx_bus(chip)->dma_type = SNDRV_DMA_TYPE_DEV_WC_SG; + + if (chip->driver_type == AZX_DRIVER_NVIDIA) { + dev_dbg(chip->card->dev, "Enable delay in RIRB handling\n"); + chip->bus.core.needs_damn_long_delay = 1; + } + + check_probe_mask(chip, dev); + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); + if (err < 0) { + dev_err(card->dev, "Error creating device [card]!\n"); + azx_free(chip); + return err; + } + + /* continue probing in work context as may trigger request module */ + INIT_DELAYED_WORK(&hda->probe_work, azx_probe_work); + + *rchip = chip; + + return 0; +} + +static int azx_first_init(struct azx *chip) +{ + int dev = chip->dev_index; + struct pci_dev *pci = chip->pci; + struct snd_card *card = chip->card; + struct hdac_bus *bus = azx_bus(chip); + int err; + unsigned short gcap; + unsigned int dma_bits = 64; + +#if BITS_PER_LONG != 64 + /* Fix up base address on ULI M5461 */ + if (chip->driver_type == AZX_DRIVER_ULI) { + u16 tmp3; + pci_read_config_word(pci, 0x40, &tmp3); + pci_write_config_word(pci, 0x40, tmp3 | 0x10); + pci_write_config_dword(pci, PCI_BASE_ADDRESS_1, 0); + } +#endif + /* + * Fix response write request not synced to memory when handle + * hdac interrupt on Glenfly Gpus + */ + if (chip->driver_type == AZX_DRIVER_GFHDMI) + bus->polling_mode = 1; + + err = pcim_iomap_regions(pci, 1 << 0, "ICH HD audio"); + if (err < 0) + return err; + + bus->addr = pci_resource_start(pci, 0); + bus->remap_addr = pcim_iomap_table(pci)[0]; + + if (chip->driver_type == AZX_DRIVER_SKL) + snd_hdac_bus_parse_capabilities(bus); + + /* + * Some Intel CPUs has always running timer (ART) feature and + * controller may have Global time sync reporting capability, so + * check both of these before declaring synchronized time reporting + * capability SNDRV_PCM_INFO_HAS_LINK_SYNCHRONIZED_ATIME + */ + chip->gts_present = false; + +#ifdef CONFIG_X86 + if (bus->ppcap && boot_cpu_has(X86_FEATURE_ART)) + chip->gts_present = true; +#endif + + if (chip->msi) { + if (chip->driver_caps & AZX_DCAPS_NO_MSI64) { + dev_dbg(card->dev, "Disabling 64bit MSI\n"); + pci->no_64bit_msi = true; + } + if (pci_enable_msi(pci) < 0) + chip->msi = 0; + } + + pci_set_master(pci); + + gcap = azx_readw(chip, GCAP); + dev_dbg(card->dev, "chipset global capabilities = 0x%x\n", gcap); + + /* AMD devices support 40 or 48bit DMA, take the safe one */ + if (chip->pci->vendor == PCI_VENDOR_ID_AMD) + dma_bits = 40; + + /* disable SB600 64bit support for safety */ + if (chip->pci->vendor == PCI_VENDOR_ID_ATI) { + struct pci_dev *p_smbus; + dma_bits = 40; + p_smbus = pci_get_device(PCI_VENDOR_ID_ATI, + PCI_DEVICE_ID_ATI_SBX00_SMBUS, + NULL); + if (p_smbus) { + if (p_smbus->revision < 0x30) + gcap &= ~AZX_GCAP_64OK; + pci_dev_put(p_smbus); + } + } + + /* NVidia hardware normally only supports up to 40 bits of DMA */ + if (chip->pci->vendor == PCI_VENDOR_ID_NVIDIA) + dma_bits = 40; + + /* disable 64bit DMA address on some devices */ + if (chip->driver_caps & AZX_DCAPS_NO_64BIT) { + dev_dbg(card->dev, "Disabling 64bit DMA\n"); + gcap &= ~AZX_GCAP_64OK; + } + + /* disable buffer size rounding to 128-byte multiples if supported */ + if (align_buffer_size >= 0) + chip->align_buffer_size = !!align_buffer_size; + else { + if (chip->driver_caps & AZX_DCAPS_NO_ALIGN_BUFSIZE) + chip->align_buffer_size = 0; + else + chip->align_buffer_size = 1; + } + + /* allow 64bit DMA address if supported by H/W */ + if (!(gcap & AZX_GCAP_64OK)) + dma_bits = 32; + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(dma_bits))) + dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(32)); + dma_set_max_seg_size(&pci->dev, UINT_MAX); + + /* read number of streams from GCAP register instead of using + * hardcoded value + */ + chip->capture_streams = (gcap >> 8) & 0x0f; + chip->playback_streams = (gcap >> 12) & 0x0f; + if (!chip->playback_streams && !chip->capture_streams) { + /* gcap didn't give any info, switching to old method */ + + switch (chip->driver_type) { + case AZX_DRIVER_ULI: + chip->playback_streams = ULI_NUM_PLAYBACK; + chip->capture_streams = ULI_NUM_CAPTURE; + break; + case AZX_DRIVER_ATIHDMI: + case AZX_DRIVER_ATIHDMI_NS: + chip->playback_streams = ATIHDMI_NUM_PLAYBACK; + chip->capture_streams = ATIHDMI_NUM_CAPTURE; + break; + case AZX_DRIVER_GFHDMI: + case AZX_DRIVER_GENERIC: + default: + chip->playback_streams = ICH6_NUM_PLAYBACK; + chip->capture_streams = ICH6_NUM_CAPTURE; + break; + } + } + chip->capture_index_offset = 0; + chip->playback_index_offset = chip->capture_streams; + chip->num_streams = chip->playback_streams + chip->capture_streams; + + /* sanity check for the SDxCTL.STRM field overflow */ + if (chip->num_streams > 15 && + (chip->driver_caps & AZX_DCAPS_SEPARATE_STREAM_TAG) == 0) { + dev_warn(chip->card->dev, "number of I/O streams is %d, " + "forcing separate stream tags", chip->num_streams); + chip->driver_caps |= AZX_DCAPS_SEPARATE_STREAM_TAG; + } + + /* initialize streams */ + err = azx_init_streams(chip); + if (err < 0) + return err; + + err = azx_alloc_stream_pages(chip); + if (err < 0) + return err; + + /* initialize chip */ + azx_init_pci(chip); + + snd_hdac_i915_set_bclk(bus); + + hda_intel_init_chip(chip, (probe_only[dev] & 2) == 0); + + /* codec detection */ + if (!azx_bus(chip)->codec_mask) { + dev_err(card->dev, "no codecs found!\n"); + /* keep running the rest for the runtime PM */ + } + + if (azx_acquire_irq(chip, 0) < 0) + return -EBUSY; + + strcpy(card->driver, "HDA-Intel"); + strscpy(card->shortname, driver_short_names[chip->driver_type], + sizeof(card->shortname)); + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx irq %i", + card->shortname, bus->addr, bus->irq); + + return 0; +} + +#ifdef CONFIG_SND_HDA_PATCH_LOADER +/* callback from request_firmware_nowait() */ +static void azx_firmware_cb(const struct firmware *fw, void *context) +{ + struct snd_card *card = context; + struct azx *chip = card->private_data; + + if (fw) + chip->fw = fw; + else + dev_err(card->dev, "Cannot load firmware, continue without patching\n"); + if (!chip->disabled) { + /* continue probing */ + azx_probe_continue(chip); + } +} +#endif + +static int disable_msi_reset_irq(struct azx *chip) +{ + struct hdac_bus *bus = azx_bus(chip); + int err; + + free_irq(bus->irq, chip); + bus->irq = -1; + chip->card->sync_irq = -1; + pci_disable_msi(chip->pci); + chip->msi = 0; + err = azx_acquire_irq(chip, 1); + if (err < 0) + return err; + + return 0; +} + +/* Denylist for skipping the whole probe: + * some HD-audio PCI entries are exposed without any codecs, and such devices + * should be ignored from the beginning. + */ +static const struct pci_device_id driver_denylist[] = { + { PCI_DEVICE_SUB(0x1022, 0x1487, 0x1043, 0x874f) }, /* ASUS ROG Zenith II / Strix */ + { PCI_DEVICE_SUB(0x1022, 0x1487, 0x1462, 0xcb59) }, /* MSI TRX40 Creator */ + { PCI_DEVICE_SUB(0x1022, 0x1487, 0x1462, 0xcb60) }, /* MSI TRX40 */ + {} +}; + +static const struct hda_controller_ops pci_hda_ops = { + .disable_msi_reset_irq = disable_msi_reset_irq, + .position_check = azx_position_check, +}; + +static DECLARE_BITMAP(probed_devs, SNDRV_CARDS); + +static int azx_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + struct snd_card *card; + struct hda_intel *hda; + struct azx *chip; + bool schedule_probe; + int dev; + int err; + + if (pci_match_id(driver_denylist, pci)) { + dev_info(&pci->dev, "Skipping the device on the denylist\n"); + return -ENODEV; + } + + dev = find_first_zero_bit(probed_devs, SNDRV_CARDS); + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + set_bit(dev, probed_devs); + return -ENOENT; + } + + /* + * stop probe if another Intel's DSP driver should be activated + */ + if (dmic_detect) { + err = snd_intel_dsp_driver_probe(pci); + if (err != SND_INTEL_DSP_DRIVER_ANY && err != SND_INTEL_DSP_DRIVER_LEGACY) { + dev_dbg(&pci->dev, "HDAudio driver not selected, aborting probe\n"); + return -ENODEV; + } + } else { + dev_warn(&pci->dev, "dmic_detect option is deprecated, pass snd-intel-dspcfg.dsp_driver=1 option instead\n"); + } + + err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); + if (err < 0) { + dev_err(&pci->dev, "Error creating card!\n"); + return err; + } + + err = azx_create(card, pci, dev, pci_id->driver_data, &chip); + if (err < 0) + goto out_free; + card->private_data = chip; + hda = container_of(chip, struct hda_intel, chip); + + pci_set_drvdata(pci, card); + + err = register_vga_switcheroo(chip); + if (err < 0) { + dev_err(card->dev, "Error registering vga_switcheroo client\n"); + goto out_free; + } + + if (check_hdmi_disabled(pci)) { + dev_info(card->dev, "VGA controller is disabled\n"); + dev_info(card->dev, "Delaying initialization\n"); + chip->disabled = true; + } + + schedule_probe = !chip->disabled; + +#ifdef CONFIG_SND_HDA_PATCH_LOADER + if (patch[dev] && *patch[dev]) { + dev_info(card->dev, "Applying patch firmware '%s'\n", + patch[dev]); + err = request_firmware_nowait(THIS_MODULE, true, patch[dev], + &pci->dev, GFP_KERNEL, card, + azx_firmware_cb); + if (err < 0) + goto out_free; + schedule_probe = false; /* continued in azx_firmware_cb() */ + } +#endif /* CONFIG_SND_HDA_PATCH_LOADER */ + +#ifndef CONFIG_SND_HDA_I915 + if (CONTROLLER_IN_GPU(pci)) + dev_err(card->dev, "Haswell/Broadwell HDMI/DP must build in CONFIG_SND_HDA_I915\n"); +#endif + + if (schedule_probe) + schedule_delayed_work(&hda->probe_work, 0); + + set_bit(dev, probed_devs); + if (chip->disabled) + complete_all(&hda->probe_wait); + return 0; + +out_free: + snd_card_free(card); + return err; +} + +#ifdef CONFIG_PM +/* On some boards setting power_save to a non 0 value leads to clicking / + * popping sounds when ever we enter/leave powersaving mode. Ideally we would + * figure out how to avoid these sounds, but that is not always feasible. + * So we keep a list of devices where we disable powersaving as its known + * to causes problems on these devices. + */ +static const struct snd_pci_quirk power_save_denylist[] = { + /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */ + SND_PCI_QUIRK(0x1849, 0xc892, "Asrock B85M-ITX", 0), + /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */ + SND_PCI_QUIRK(0x1849, 0x0397, "Asrock N68C-S UCC", 0), + /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */ + SND_PCI_QUIRK(0x1849, 0x7662, "Asrock H81M-HDS", 0), + /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */ + SND_PCI_QUIRK(0x1043, 0x8733, "Asus Prime X370-Pro", 0), + /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */ + SND_PCI_QUIRK(0x1028, 0x0497, "Dell Precision T3600", 0), + /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */ + /* Note the P55A-UD3 and Z87-D3HP share the subsys id for the HDA dev */ + SND_PCI_QUIRK(0x1458, 0xa002, "Gigabyte P55A-UD3 / Z87-D3HP", 0), + /* https://bugzilla.redhat.com/show_bug.cgi?id=1525104 */ + SND_PCI_QUIRK(0x8086, 0x2040, "Intel DZ77BH-55K", 0), + /* https://bugzilla.kernel.org/show_bug.cgi?id=199607 */ + SND_PCI_QUIRK(0x8086, 0x2057, "Intel NUC5i7RYB", 0), + /* https://bugs.launchpad.net/bugs/1821663 */ + SND_PCI_QUIRK(0x8086, 0x2064, "Intel SDP 8086:2064", 0), + /* https://bugzilla.redhat.com/show_bug.cgi?id=1520902 */ + SND_PCI_QUIRK(0x8086, 0x2068, "Intel NUC7i3BNB", 0), + /* https://bugzilla.kernel.org/show_bug.cgi?id=198611 */ + SND_PCI_QUIRK(0x17aa, 0x2227, "Lenovo X1 Carbon 3rd Gen", 0), + SND_PCI_QUIRK(0x17aa, 0x316e, "Lenovo ThinkCentre M70q", 0), + /* https://bugzilla.redhat.com/show_bug.cgi?id=1689623 */ + SND_PCI_QUIRK(0x17aa, 0x367b, "Lenovo IdeaCentre B550", 0), + /* https://bugzilla.redhat.com/show_bug.cgi?id=1572975 */ + SND_PCI_QUIRK(0x17aa, 0x36a7, "Lenovo C50 All in one", 0), + /* https://bugs.launchpad.net/bugs/1821663 */ + SND_PCI_QUIRK(0x1631, 0xe017, "Packard Bell NEC IMEDIA 5204", 0), + /* KONTRON SinglePC may cause a stall at runtime resume */ + SND_PCI_QUIRK(0x1734, 0x1232, "KONTRON SinglePC", 0), + {} +}; +#endif /* CONFIG_PM */ + +static void set_default_power_save(struct azx *chip) +{ + int val = power_save; + +#ifdef CONFIG_PM + if (pm_blacklist) { + const struct snd_pci_quirk *q; + + q = snd_pci_quirk_lookup(chip->pci, power_save_denylist); + if (q && val) { + dev_info(chip->card->dev, "device %04x:%04x is on the power_save denylist, forcing power_save to 0\n", + q->subvendor, q->subdevice); + val = 0; + } + } +#endif /* CONFIG_PM */ + snd_hda_set_power_save(&chip->bus, val * 1000); +} + +/* number of codec slots for each chipset: 0 = default slots (i.e. 4) */ +static const unsigned int azx_max_codecs[AZX_NUM_DRIVERS] = { + [AZX_DRIVER_NVIDIA] = 8, + [AZX_DRIVER_TERA] = 1, +}; + +static int azx_probe_continue(struct azx *chip) +{ + struct hda_intel *hda = container_of(chip, struct hda_intel, chip); + struct hdac_bus *bus = azx_bus(chip); + struct pci_dev *pci = chip->pci; + int dev = chip->dev_index; + int err; + + if (chip->disabled || hda->init_failed) + return -EIO; + if (hda->probe_retry) + goto probe_retry; + + to_hda_bus(bus)->bus_probing = 1; + hda->probe_continued = 1; + + /* bind with i915 if needed */ + if (chip->driver_caps & AZX_DCAPS_I915_COMPONENT) { + err = snd_hdac_i915_init(bus); + if (err < 0) { + /* if the controller is bound only with HDMI/DP + * (for HSW and BDW), we need to abort the probe; + * for other chips, still continue probing as other + * codecs can be on the same link. + */ + if (CONTROLLER_IN_GPU(pci)) { + dev_err(chip->card->dev, + "HSW/BDW HD-audio HDMI/DP requires binding with gfx driver\n"); + goto out_free; + } else { + /* don't bother any longer */ + chip->driver_caps &= ~AZX_DCAPS_I915_COMPONENT; + } + } + + /* HSW/BDW controllers need this power */ + if (CONTROLLER_IN_GPU(pci)) + hda->need_i915_power = true; + } + + /* Request display power well for the HDA controller or codec. For + * Haswell/Broadwell, both the display HDA controller and codec need + * this power. For other platforms, like Baytrail/Braswell, only the + * display codec needs the power and it can be released after probe. + */ + display_power(chip, true); + + err = azx_first_init(chip); + if (err < 0) + goto out_free; + +#ifdef CONFIG_SND_HDA_INPUT_BEEP + chip->beep_mode = beep_mode[dev]; +#endif + + chip->ctl_dev_id = ctl_dev_id; + + /* create codec instances */ + if (bus->codec_mask) { + err = azx_probe_codecs(chip, azx_max_codecs[chip->driver_type]); + if (err < 0) + goto out_free; + } + +#ifdef CONFIG_SND_HDA_PATCH_LOADER + if (chip->fw) { + err = snd_hda_load_patch(&chip->bus, chip->fw->size, + chip->fw->data); + if (err < 0) + goto out_free; +#ifndef CONFIG_PM + release_firmware(chip->fw); /* no longer needed */ + chip->fw = NULL; +#endif + } +#endif + + probe_retry: + if (bus->codec_mask && !(probe_only[dev] & 1)) { + err = azx_codec_configure(chip); + if (err) { + if ((chip->driver_caps & AZX_DCAPS_RETRY_PROBE) && + ++hda->probe_retry < 60) { + schedule_delayed_work(&hda->probe_work, + msecs_to_jiffies(1000)); + return 0; /* keep things up */ + } + dev_err(chip->card->dev, "Cannot probe codecs, giving up\n"); + goto out_free; + } + } + + err = snd_card_register(chip->card); + if (err < 0) + goto out_free; + + setup_vga_switcheroo_runtime_pm(chip); + + chip->running = 1; + azx_add_card_list(chip); + + set_default_power_save(chip); + + if (azx_has_pm_runtime(chip)) { + pm_runtime_use_autosuspend(&pci->dev); + pm_runtime_allow(&pci->dev); + pm_runtime_put_autosuspend(&pci->dev); + } + +out_free: + if (err < 0) { + pci_set_drvdata(pci, NULL); + snd_card_free(chip->card); + return err; + } + + if (!hda->need_i915_power) + display_power(chip, false); + complete_all(&hda->probe_wait); + to_hda_bus(bus)->bus_probing = 0; + hda->probe_retry = 0; + return 0; +} + +static void azx_remove(struct pci_dev *pci) +{ + struct snd_card *card = pci_get_drvdata(pci); + struct azx *chip; + struct hda_intel *hda; + + if (card) { + /* cancel the pending probing work */ + chip = card->private_data; + hda = container_of(chip, struct hda_intel, chip); + /* FIXME: below is an ugly workaround. + * Both device_release_driver() and driver_probe_device() + * take *both* the device's and its parent's lock before + * calling the remove() and probe() callbacks. The codec + * probe takes the locks of both the codec itself and its + * parent, i.e. the PCI controller dev. Meanwhile, when + * the PCI controller is unbound, it takes its lock, too + * ==> ouch, a deadlock! + * As a workaround, we unlock temporarily here the controller + * device during cancel_work_sync() call. + */ + device_unlock(&pci->dev); + cancel_delayed_work_sync(&hda->probe_work); + device_lock(&pci->dev); + + clear_bit(chip->dev_index, probed_devs); + pci_set_drvdata(pci, NULL); + snd_card_free(card); + } +} + +static void azx_shutdown(struct pci_dev *pci) +{ + struct snd_card *card = pci_get_drvdata(pci); + struct azx *chip; + + if (!card) + return; + chip = card->private_data; + if (chip && chip->running) + __azx_shutdown_chip(chip, true); +} + +/* PCI IDs */ +static const struct pci_device_id azx_ids[] = { + /* CPT */ + { PCI_DEVICE(0x8086, 0x1c20), + .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH_NOPM }, + /* PBG */ + { PCI_DEVICE(0x8086, 0x1d20), + .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH_NOPM }, + /* Panther Point */ + { PCI_DEVICE(0x8086, 0x1e20), + .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH_NOPM }, + /* Lynx Point */ + { PCI_DEVICE(0x8086, 0x8c20), + .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH }, + /* 9 Series */ + { PCI_DEVICE(0x8086, 0x8ca0), + .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH }, + /* Wellsburg */ + { PCI_DEVICE(0x8086, 0x8d20), + .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH }, + { PCI_DEVICE(0x8086, 0x8d21), + .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH }, + /* Lewisburg */ + { PCI_DEVICE(0x8086, 0xa1f0), + .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_SKYLAKE }, + { PCI_DEVICE(0x8086, 0xa270), + .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_SKYLAKE }, + /* Lynx Point-LP */ + { PCI_DEVICE(0x8086, 0x9c20), + .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH }, + /* Lynx Point-LP */ + { PCI_DEVICE(0x8086, 0x9c21), + .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH }, + /* Wildcat Point-LP */ + { PCI_DEVICE(0x8086, 0x9ca0), + .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH }, + /* Sunrise Point */ + { PCI_DEVICE(0x8086, 0xa170), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE }, + /* Sunrise Point-LP */ + { PCI_DEVICE(0x8086, 0x9d70), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE }, + /* Kabylake */ + { PCI_DEVICE(0x8086, 0xa171), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE }, + /* Kabylake-LP */ + { PCI_DEVICE(0x8086, 0x9d71), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE }, + /* Kabylake-H */ + { PCI_DEVICE(0x8086, 0xa2f0), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE }, + /* Coffelake */ + { PCI_DEVICE(0x8086, 0xa348), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* Cannonlake */ + { PCI_DEVICE(0x8086, 0x9dc8), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* CometLake-LP */ + { PCI_DEVICE(0x8086, 0x02C8), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* CometLake-H */ + { PCI_DEVICE(0x8086, 0x06C8), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + { PCI_DEVICE(0x8086, 0xf1c8), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* CometLake-S */ + { PCI_DEVICE(0x8086, 0xa3f0), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* CometLake-R */ + { PCI_DEVICE(0x8086, 0xf0c8), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* Icelake */ + { PCI_DEVICE(0x8086, 0x34c8), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* Icelake-H */ + { PCI_DEVICE(0x8086, 0x3dc8), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* Jasperlake */ + { PCI_DEVICE(0x8086, 0x38c8), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + { PCI_DEVICE(0x8086, 0x4dc8), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* Tigerlake */ + { PCI_DEVICE(0x8086, 0xa0c8), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* Tigerlake-H */ + { PCI_DEVICE(0x8086, 0x43c8), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* DG1 */ + { PCI_DEVICE(0x8086, 0x490d), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* DG2 */ + { PCI_DEVICE(0x8086, 0x4f90), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + { PCI_DEVICE(0x8086, 0x4f91), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + { PCI_DEVICE(0x8086, 0x4f92), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* Alderlake-S */ + { PCI_DEVICE(0x8086, 0x7ad0), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* Alderlake-P */ + { PCI_DEVICE(0x8086, 0x51c8), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + { PCI_DEVICE(0x8086, 0x51c9), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + { PCI_DEVICE(0x8086, 0x51cd), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* Alderlake-M */ + { PCI_DEVICE(0x8086, 0x51cc), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* Alderlake-N */ + { PCI_DEVICE(0x8086, 0x54c8), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* Elkhart Lake */ + { PCI_DEVICE(0x8086, 0x4b55), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + { PCI_DEVICE(0x8086, 0x4b58), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* Raptor Lake */ + { PCI_DEVICE(0x8086, 0x7a50), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + { PCI_DEVICE(0x8086, 0x51ca), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + { PCI_DEVICE(0x8086, 0x51cb), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + { PCI_DEVICE(0x8086, 0x51ce), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + { PCI_DEVICE(0x8086, 0x51cf), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* Meteorlake-P */ + { PCI_DEVICE(0x8086, 0x7e28), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* Lunarlake-P */ + { PCI_DEVICE(0x8086, 0xa828), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* Broxton-P(Apollolake) */ + { PCI_DEVICE(0x8086, 0x5a98), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_BROXTON }, + /* Broxton-T */ + { PCI_DEVICE(0x8086, 0x1a98), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_BROXTON }, + /* Gemini-Lake */ + { PCI_DEVICE(0x8086, 0x3198), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_BROXTON }, + /* Haswell */ + { PCI_DEVICE(0x8086, 0x0a0c), + .driver_data = AZX_DRIVER_HDMI | AZX_DCAPS_INTEL_HASWELL }, + { PCI_DEVICE(0x8086, 0x0c0c), + .driver_data = AZX_DRIVER_HDMI | AZX_DCAPS_INTEL_HASWELL }, + { PCI_DEVICE(0x8086, 0x0d0c), + .driver_data = AZX_DRIVER_HDMI | AZX_DCAPS_INTEL_HASWELL }, + /* Broadwell */ + { PCI_DEVICE(0x8086, 0x160c), + .driver_data = AZX_DRIVER_HDMI | AZX_DCAPS_INTEL_BROADWELL }, + /* 5 Series/3400 */ + { PCI_DEVICE(0x8086, 0x3b56), + .driver_data = AZX_DRIVER_SCH | AZX_DCAPS_INTEL_PCH_NOPM }, + { PCI_DEVICE(0x8086, 0x3b57), + .driver_data = AZX_DRIVER_SCH | AZX_DCAPS_INTEL_PCH_NOPM }, + /* Poulsbo */ + { PCI_DEVICE(0x8086, 0x811b), + .driver_data = AZX_DRIVER_SCH | AZX_DCAPS_INTEL_PCH_BASE | + AZX_DCAPS_POSFIX_LPIB }, + /* Oaktrail */ + { PCI_DEVICE(0x8086, 0x080a), + .driver_data = AZX_DRIVER_SCH | AZX_DCAPS_INTEL_PCH_BASE }, + /* BayTrail */ + { PCI_DEVICE(0x8086, 0x0f04), + .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_BAYTRAIL }, + /* Braswell */ + { PCI_DEVICE(0x8086, 0x2284), + .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_BRASWELL }, + /* ICH6 */ + { PCI_DEVICE(0x8086, 0x2668), + .driver_data = AZX_DRIVER_ICH | AZX_DCAPS_INTEL_ICH }, + /* ICH7 */ + { PCI_DEVICE(0x8086, 0x27d8), + .driver_data = AZX_DRIVER_ICH | AZX_DCAPS_INTEL_ICH }, + /* ESB2 */ + { PCI_DEVICE(0x8086, 0x269a), + .driver_data = AZX_DRIVER_ICH | AZX_DCAPS_INTEL_ICH }, + /* ICH8 */ + { PCI_DEVICE(0x8086, 0x284b), + .driver_data = AZX_DRIVER_ICH | AZX_DCAPS_INTEL_ICH }, + /* ICH9 */ + { PCI_DEVICE(0x8086, 0x293e), + .driver_data = AZX_DRIVER_ICH | AZX_DCAPS_INTEL_ICH }, + /* ICH9 */ + { PCI_DEVICE(0x8086, 0x293f), + .driver_data = AZX_DRIVER_ICH | AZX_DCAPS_INTEL_ICH }, + /* ICH10 */ + { PCI_DEVICE(0x8086, 0x3a3e), + .driver_data = AZX_DRIVER_ICH | AZX_DCAPS_INTEL_ICH }, + /* ICH10 */ + { PCI_DEVICE(0x8086, 0x3a6e), + .driver_data = AZX_DRIVER_ICH | AZX_DCAPS_INTEL_ICH }, + /* Generic Intel */ + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_ANY_ID), + .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8, + .class_mask = 0xffffff, + .driver_data = AZX_DRIVER_ICH | AZX_DCAPS_NO_ALIGN_BUFSIZE }, + /* ATI SB 450/600/700/800/900 */ + { PCI_DEVICE(0x1002, 0x437b), + .driver_data = AZX_DRIVER_ATI | AZX_DCAPS_PRESET_ATI_SB }, + { PCI_DEVICE(0x1002, 0x4383), + .driver_data = AZX_DRIVER_ATI | AZX_DCAPS_PRESET_ATI_SB }, + /* AMD Hudson */ + { PCI_DEVICE(0x1022, 0x780d), + .driver_data = AZX_DRIVER_GENERIC | AZX_DCAPS_PRESET_ATI_SB }, + /* AMD, X370 & co */ + { PCI_DEVICE(0x1022, 0x1457), + .driver_data = AZX_DRIVER_GENERIC | AZX_DCAPS_PRESET_AMD_SB }, + /* AMD, X570 & co */ + { PCI_DEVICE(0x1022, 0x1487), + .driver_data = AZX_DRIVER_GENERIC | AZX_DCAPS_PRESET_AMD_SB }, + /* AMD Stoney */ + { PCI_DEVICE(0x1022, 0x157a), + .driver_data = AZX_DRIVER_GENERIC | AZX_DCAPS_PRESET_ATI_SB | + AZX_DCAPS_PM_RUNTIME }, + /* AMD Raven */ + { PCI_DEVICE(0x1022, 0x15e3), + .driver_data = AZX_DRIVER_GENERIC | AZX_DCAPS_PRESET_AMD_SB }, + /* ATI HDMI */ + { PCI_DEVICE(0x1002, 0x0002), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS | + AZX_DCAPS_PM_RUNTIME }, + { PCI_DEVICE(0x1002, 0x1308), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS }, + { PCI_DEVICE(0x1002, 0x157a), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS }, + { PCI_DEVICE(0x1002, 0x15b3), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS }, + { PCI_DEVICE(0x1002, 0x793b), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0x7919), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0x960f), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0x970f), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0x9840), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS }, + { PCI_DEVICE(0x1002, 0xaa00), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0xaa08), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0xaa10), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0xaa18), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0xaa20), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0xaa28), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0xaa30), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0xaa38), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0xaa40), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0xaa48), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0xaa50), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0xaa58), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0xaa60), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0xaa68), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0xaa80), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0xaa88), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0xaa90), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0xaa98), + .driver_data = AZX_DRIVER_ATIHDMI | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(0x1002, 0x9902), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS }, + { PCI_DEVICE(0x1002, 0xaaa0), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS }, + { PCI_DEVICE(0x1002, 0xaaa8), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS }, + { PCI_DEVICE(0x1002, 0xaab0), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS }, + { PCI_DEVICE(0x1002, 0xaac0), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS | + AZX_DCAPS_PM_RUNTIME }, + { PCI_DEVICE(0x1002, 0xaac8), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS | + AZX_DCAPS_PM_RUNTIME }, + { PCI_DEVICE(0x1002, 0xaad8), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS | + AZX_DCAPS_PM_RUNTIME }, + { PCI_DEVICE(0x1002, 0xaae0), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS | + AZX_DCAPS_PM_RUNTIME }, + { PCI_DEVICE(0x1002, 0xaae8), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS | + AZX_DCAPS_PM_RUNTIME }, + { PCI_DEVICE(0x1002, 0xaaf0), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS | + AZX_DCAPS_PM_RUNTIME }, + { PCI_DEVICE(0x1002, 0xaaf8), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS | + AZX_DCAPS_PM_RUNTIME }, + { PCI_DEVICE(0x1002, 0xab00), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS | + AZX_DCAPS_PM_RUNTIME }, + { PCI_DEVICE(0x1002, 0xab08), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS | + AZX_DCAPS_PM_RUNTIME }, + { PCI_DEVICE(0x1002, 0xab10), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS | + AZX_DCAPS_PM_RUNTIME }, + { PCI_DEVICE(0x1002, 0xab18), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS | + AZX_DCAPS_PM_RUNTIME }, + { PCI_DEVICE(0x1002, 0xab20), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS | + AZX_DCAPS_PM_RUNTIME }, + { PCI_DEVICE(0x1002, 0xab28), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS | + AZX_DCAPS_PM_RUNTIME }, + { PCI_DEVICE(0x1002, 0xab30), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS | + AZX_DCAPS_PM_RUNTIME }, + { PCI_DEVICE(0x1002, 0xab38), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS | + AZX_DCAPS_PM_RUNTIME }, + /* GLENFLY */ + { PCI_DEVICE(0x6766, PCI_ANY_ID), + .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8, + .class_mask = 0xffffff, + .driver_data = AZX_DRIVER_GFHDMI | AZX_DCAPS_POSFIX_LPIB | + AZX_DCAPS_NO_MSI | AZX_DCAPS_NO_64BIT }, + /* VIA VT8251/VT8237A */ + { PCI_DEVICE(0x1106, 0x3288), .driver_data = AZX_DRIVER_VIA }, + /* VIA GFX VT7122/VX900 */ + { PCI_DEVICE(0x1106, 0x9170), .driver_data = AZX_DRIVER_GENERIC }, + /* VIA GFX VT6122/VX11 */ + { PCI_DEVICE(0x1106, 0x9140), .driver_data = AZX_DRIVER_GENERIC }, + /* SIS966 */ + { PCI_DEVICE(0x1039, 0x7502), .driver_data = AZX_DRIVER_SIS }, + /* ULI M5461 */ + { PCI_DEVICE(0x10b9, 0x5461), .driver_data = AZX_DRIVER_ULI }, + /* NVIDIA MCP */ + { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_ANY_ID), + .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8, + .class_mask = 0xffffff, + .driver_data = AZX_DRIVER_NVIDIA | AZX_DCAPS_PRESET_NVIDIA }, + /* Teradici */ + { PCI_DEVICE(0x6549, 0x1200), + .driver_data = AZX_DRIVER_TERA | AZX_DCAPS_NO_64BIT }, + { PCI_DEVICE(0x6549, 0x2200), + .driver_data = AZX_DRIVER_TERA | AZX_DCAPS_NO_64BIT }, + /* Creative X-Fi (CA0110-IBG) */ + /* CTHDA chips */ + { PCI_DEVICE(0x1102, 0x0010), + .driver_data = AZX_DRIVER_CTHDA | AZX_DCAPS_PRESET_CTHDA }, + { PCI_DEVICE(0x1102, 0x0012), + .driver_data = AZX_DRIVER_CTHDA | AZX_DCAPS_PRESET_CTHDA }, +#if !IS_ENABLED(CONFIG_SND_CTXFI) + /* the following entry conflicts with snd-ctxfi driver, + * as ctxfi driver mutates from HD-audio to native mode with + * a special command sequence. + */ + { PCI_DEVICE(PCI_VENDOR_ID_CREATIVE, PCI_ANY_ID), + .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8, + .class_mask = 0xffffff, + .driver_data = AZX_DRIVER_CTX | AZX_DCAPS_CTX_WORKAROUND | + AZX_DCAPS_NO_64BIT | AZX_DCAPS_POSFIX_LPIB }, +#else + /* this entry seems still valid -- i.e. without emu20kx chip */ + { PCI_DEVICE(0x1102, 0x0009), + .driver_data = AZX_DRIVER_CTX | AZX_DCAPS_CTX_WORKAROUND | + AZX_DCAPS_NO_64BIT | AZX_DCAPS_POSFIX_LPIB }, +#endif + /* CM8888 */ + { PCI_DEVICE(0x13f6, 0x5011), + .driver_data = AZX_DRIVER_CMEDIA | + AZX_DCAPS_NO_MSI | AZX_DCAPS_POSFIX_LPIB | AZX_DCAPS_SNOOP_OFF }, + /* Vortex86MX */ + { PCI_DEVICE(0x17f3, 0x3010), .driver_data = AZX_DRIVER_GENERIC }, + /* VMware HDAudio */ + { PCI_DEVICE(0x15ad, 0x1977), .driver_data = AZX_DRIVER_GENERIC }, + /* AMD/ATI Generic, PCI class code and Vendor ID for HD Audio */ + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_ANY_ID), + .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8, + .class_mask = 0xffffff, + .driver_data = AZX_DRIVER_GENERIC | AZX_DCAPS_PRESET_ATI_HDMI }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_ANY_ID), + .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8, + .class_mask = 0xffffff, + .driver_data = AZX_DRIVER_GENERIC | AZX_DCAPS_PRESET_ATI_HDMI }, + /* Zhaoxin */ + { PCI_DEVICE(0x1d17, 0x3288), .driver_data = AZX_DRIVER_ZHAOXIN }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, azx_ids); + +/* pci_driver definition */ +static struct pci_driver azx_driver = { + .name = KBUILD_MODNAME, + .id_table = azx_ids, + .probe = azx_probe, + .remove = azx_remove, + .shutdown = azx_shutdown, + .driver = { + .pm = AZX_PM_OPS, + }, +}; + +module_pci_driver(azx_driver); diff --git a/sound/pci/hda/hda_intel.h b/sound/pci/hda/hda_intel.h new file mode 100644 index 000000000..0f39418f9 --- /dev/null +++ b/sound/pci/hda/hda_intel.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + */ +#ifndef __SOUND_HDA_INTEL_H +#define __SOUND_HDA_INTEL_H + +#include "hda_controller.h" + +struct hda_intel { + struct azx chip; + + /* for pending irqs */ + struct work_struct irq_pending_work; + + /* sync probing */ + struct completion probe_wait; + struct delayed_work probe_work; + + /* card list (for power_save trigger) */ + struct list_head list; + + /* extra flags */ + unsigned int irq_pending_warned:1; + unsigned int probe_continued:1; + + /* vga_switcheroo setup */ + unsigned int use_vga_switcheroo:1; + unsigned int vga_switcheroo_registered:1; + unsigned int init_failed:1; /* delayed init failed */ + unsigned int freed:1; /* resources already released */ + + bool need_i915_power:1; /* the hda controller needs i915 power */ + + int probe_retry; /* being probe-retry */ +}; + +#endif diff --git a/sound/pci/hda/hda_intel_trace.h b/sound/pci/hda/hda_intel_trace.h new file mode 100644 index 000000000..73a7adfa1 --- /dev/null +++ b/sound/pci/hda/hda_intel_trace.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM hda_intel +#define TRACE_INCLUDE_FILE hda_intel_trace + +#if !defined(_TRACE_HDA_INTEL_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_HDA_INTEL_H + +#include <linux/tracepoint.h> + +DECLARE_EVENT_CLASS(hda_pm, + TP_PROTO(struct azx *chip), + + TP_ARGS(chip), + + TP_STRUCT__entry( + __field(int, dev_index) + ), + + TP_fast_assign( + __entry->dev_index = (chip)->dev_index; + ), + + TP_printk("card index: %d", __entry->dev_index) +); + +DEFINE_EVENT(hda_pm, azx_suspend, + TP_PROTO(struct azx *chip), + TP_ARGS(chip) +); + +DEFINE_EVENT(hda_pm, azx_resume, + TP_PROTO(struct azx *chip), + TP_ARGS(chip) +); + +#ifdef CONFIG_PM +DEFINE_EVENT(hda_pm, azx_runtime_suspend, + TP_PROTO(struct azx *chip), + TP_ARGS(chip) +); + +DEFINE_EVENT(hda_pm, azx_runtime_resume, + TP_PROTO(struct azx *chip), + TP_ARGS(chip) +); +#endif + +#endif /* _TRACE_HDA_INTEL_H */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#include <trace/define_trace.h> diff --git a/sound/pci/hda/hda_jack.c b/sound/pci/hda/hda_jack.c new file mode 100644 index 000000000..7d7786df6 --- /dev/null +++ b/sound/pci/hda/hda_jack.c @@ -0,0 +1,770 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Jack-detection handling for HD-audio + * + * Copyright (c) 2011 Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/export.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/jack.h> +#include <sound/hda_codec.h> +#include "hda_local.h" +#include "hda_auto_parser.h" +#include "hda_jack.h" + +/** + * is_jack_detectable - Check whether the given pin is jack-detectable + * @codec: the HDA codec + * @nid: pin NID + * + * Check whether the given pin is capable to report the jack detection. + * The jack detection might not work by various reasons, e.g. the jack + * detection is prohibited in the codec level, the pin config has + * AC_DEFCFG_MISC_NO_PRESENCE bit, no unsol support, etc. + */ +bool is_jack_detectable(struct hda_codec *codec, hda_nid_t nid) +{ + if (codec->no_jack_detect) + return false; + if (!(snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_PRES_DETECT)) + return false; + if (get_defcfg_misc(snd_hda_codec_get_pincfg(codec, nid)) & + AC_DEFCFG_MISC_NO_PRESENCE) + return false; + if (!(get_wcaps(codec, nid) & AC_WCAP_UNSOL_CAP) && + !codec->jackpoll_interval) + return false; + return true; +} +EXPORT_SYMBOL_GPL(is_jack_detectable); + +/* execute pin sense measurement */ +static u32 read_pin_sense(struct hda_codec *codec, hda_nid_t nid, int dev_id) +{ + u32 pincap; + u32 val; + + if (!codec->no_trigger_sense) { + pincap = snd_hda_query_pin_caps(codec, nid); + if (pincap & AC_PINCAP_TRIG_REQ) /* need trigger? */ + snd_hda_codec_read(codec, nid, 0, + AC_VERB_SET_PIN_SENSE, 0); + } + val = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_PIN_SENSE, dev_id); + if (codec->inv_jack_detect) + val ^= AC_PINSENSE_PRESENCE; + return val; +} + +/** + * snd_hda_jack_tbl_get_mst - query the jack-table entry for the given NID + * @codec: the HDA codec + * @nid: pin NID to refer to + * @dev_id: pin device entry id + */ +struct hda_jack_tbl * +snd_hda_jack_tbl_get_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id) +{ + struct hda_jack_tbl *jack = codec->jacktbl.list; + int i; + + if (!nid || !jack) + return NULL; + for (i = 0; i < codec->jacktbl.used; i++, jack++) + if (jack->nid == nid && jack->dev_id == dev_id) + return jack; + return NULL; +} +EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get_mst); + +/** + * snd_hda_jack_tbl_get_from_tag - query the jack-table entry for the given tag + * @codec: the HDA codec + * @tag: tag value to refer to + * @dev_id: pin device entry id + */ +struct hda_jack_tbl * +snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec, + unsigned char tag, int dev_id) +{ + struct hda_jack_tbl *jack = codec->jacktbl.list; + int i; + + if (!tag || !jack) + return NULL; + for (i = 0; i < codec->jacktbl.used; i++, jack++) + if (jack->tag == tag && jack->dev_id == dev_id) + return jack; + return NULL; +} +EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get_from_tag); + +static struct hda_jack_tbl * +any_jack_tbl_get_from_nid(struct hda_codec *codec, hda_nid_t nid) +{ + struct hda_jack_tbl *jack = codec->jacktbl.list; + int i; + + if (!nid || !jack) + return NULL; + for (i = 0; i < codec->jacktbl.used; i++, jack++) + if (jack->nid == nid) + return jack; + return NULL; +} + +/** + * snd_hda_jack_tbl_new - create a jack-table entry for the given NID + * @codec: the HDA codec + * @nid: pin NID to assign + * @dev_id: pin device entry id + */ +static struct hda_jack_tbl * +snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid, int dev_id) +{ + struct hda_jack_tbl *jack = + snd_hda_jack_tbl_get_mst(codec, nid, dev_id); + struct hda_jack_tbl *existing_nid_jack = + any_jack_tbl_get_from_nid(codec, nid); + + WARN_ON(dev_id != 0 && !codec->dp_mst); + + if (jack) + return jack; + jack = snd_array_new(&codec->jacktbl); + if (!jack) + return NULL; + jack->nid = nid; + jack->dev_id = dev_id; + jack->jack_dirty = 1; + if (existing_nid_jack) { + jack->tag = existing_nid_jack->tag; + + /* + * Copy jack_detect from existing_nid_jack to avoid + * snd_hda_jack_detect_enable_callback_mst() making multiple + * SET_UNSOLICITED_ENABLE calls on the same pin. + */ + jack->jack_detect = existing_nid_jack->jack_detect; + } else { + jack->tag = codec->jacktbl.used; + } + + return jack; +} + +void snd_hda_jack_tbl_disconnect(struct hda_codec *codec) +{ + struct hda_jack_tbl *jack = codec->jacktbl.list; + int i; + + for (i = 0; i < codec->jacktbl.used; i++, jack++) { + if (!codec->bus->shutdown && jack->jack) + snd_device_disconnect(codec->card, jack->jack); + } +} + +void snd_hda_jack_tbl_clear(struct hda_codec *codec) +{ + struct hda_jack_tbl *jack = codec->jacktbl.list; + int i; + + for (i = 0; i < codec->jacktbl.used; i++, jack++) { + struct hda_jack_callback *cb, *next; + + /* free jack instances manually when clearing/reconfiguring */ + if (!codec->bus->shutdown && jack->jack) + snd_device_free(codec->card, jack->jack); + + for (cb = jack->callback; cb; cb = next) { + next = cb->next; + kfree(cb); + } + } + snd_array_free(&codec->jacktbl); +} + +#define get_jack_plug_state(sense) !!(sense & AC_PINSENSE_PRESENCE) + +/* update the cached value and notification flag if needed */ +static void jack_detect_update(struct hda_codec *codec, + struct hda_jack_tbl *jack) +{ + if (!jack->jack_dirty) + return; + + if (jack->phantom_jack) + jack->pin_sense = AC_PINSENSE_PRESENCE; + else + jack->pin_sense = read_pin_sense(codec, jack->nid, + jack->dev_id); + + /* A gating jack indicates the jack is invalid if gating is unplugged */ + if (jack->gating_jack && + !snd_hda_jack_detect_mst(codec, jack->gating_jack, jack->dev_id)) + jack->pin_sense &= ~AC_PINSENSE_PRESENCE; + + jack->jack_dirty = 0; + + /* If a jack is gated by this one update it. */ + if (jack->gated_jack) { + struct hda_jack_tbl *gated = + snd_hda_jack_tbl_get_mst(codec, jack->gated_jack, + jack->dev_id); + if (gated) { + gated->jack_dirty = 1; + jack_detect_update(codec, gated); + } + } +} + +/** + * snd_hda_jack_set_dirty_all - Mark all the cached as dirty + * @codec: the HDA codec + * + * This function sets the dirty flag to all entries of jack table. + * It's called from the resume path in hda_codec.c. + */ +void snd_hda_jack_set_dirty_all(struct hda_codec *codec) +{ + struct hda_jack_tbl *jack = codec->jacktbl.list; + int i; + + for (i = 0; i < codec->jacktbl.used; i++, jack++) + if (jack->nid) + jack->jack_dirty = 1; +} +EXPORT_SYMBOL_GPL(snd_hda_jack_set_dirty_all); + +/** + * snd_hda_jack_pin_sense - execute pin sense measurement + * @codec: the CODEC to sense + * @nid: the pin NID to sense + * @dev_id: pin device entry id + * + * Execute necessary pin sense measurement and return its Presence Detect, + * Impedance, ELD Valid etc. status bits. + */ +u32 snd_hda_jack_pin_sense(struct hda_codec *codec, hda_nid_t nid, int dev_id) +{ + struct hda_jack_tbl *jack = + snd_hda_jack_tbl_get_mst(codec, nid, dev_id); + if (jack) { + jack_detect_update(codec, jack); + return jack->pin_sense; + } + return read_pin_sense(codec, nid, dev_id); +} +EXPORT_SYMBOL_GPL(snd_hda_jack_pin_sense); + +/** + * snd_hda_jack_detect_state_mst - query pin Presence Detect status + * @codec: the CODEC to sense + * @nid: the pin NID to sense + * @dev_id: pin device entry id + * + * Query and return the pin's Presence Detect status, as either + * HDA_JACK_NOT_PRESENT, HDA_JACK_PRESENT or HDA_JACK_PHANTOM. + */ +int snd_hda_jack_detect_state_mst(struct hda_codec *codec, + hda_nid_t nid, int dev_id) +{ + struct hda_jack_tbl *jack = + snd_hda_jack_tbl_get_mst(codec, nid, dev_id); + if (jack && jack->phantom_jack) + return HDA_JACK_PHANTOM; + else if (snd_hda_jack_pin_sense(codec, nid, dev_id) & + AC_PINSENSE_PRESENCE) + return HDA_JACK_PRESENT; + else + return HDA_JACK_NOT_PRESENT; +} +EXPORT_SYMBOL_GPL(snd_hda_jack_detect_state_mst); + +static struct hda_jack_callback * +find_callback_from_list(struct hda_jack_tbl *jack, + hda_jack_callback_fn func) +{ + struct hda_jack_callback *cb; + + if (!func) + return NULL; + + for (cb = jack->callback; cb; cb = cb->next) { + if (cb->func == func) + return cb; + } + + return NULL; +} + +/** + * snd_hda_jack_detect_enable_callback_mst - enable the jack-detection + * @codec: the HDA codec + * @nid: pin NID to enable + * @func: callback function to register + * @dev_id: pin device entry id + * + * In the case of error, the return value will be a pointer embedded with + * errno. Check and handle the return value appropriately with standard + * macros such as @IS_ERR() and @PTR_ERR(). + */ +struct hda_jack_callback * +snd_hda_jack_detect_enable_callback_mst(struct hda_codec *codec, hda_nid_t nid, + int dev_id, hda_jack_callback_fn func) +{ + struct hda_jack_tbl *jack; + struct hda_jack_callback *callback = NULL; + int err; + + jack = snd_hda_jack_tbl_new(codec, nid, dev_id); + if (!jack) + return ERR_PTR(-ENOMEM); + + callback = find_callback_from_list(jack, func); + + if (func && !callback) { + callback = kzalloc(sizeof(*callback), GFP_KERNEL); + if (!callback) + return ERR_PTR(-ENOMEM); + callback->func = func; + callback->nid = jack->nid; + callback->dev_id = jack->dev_id; + callback->next = jack->callback; + jack->callback = callback; + } + + if (jack->jack_detect) + return callback; /* already registered */ + jack->jack_detect = 1; + if (codec->jackpoll_interval > 0) + return callback; /* No unsol if we're polling instead */ + err = snd_hda_codec_write_cache(codec, nid, 0, + AC_VERB_SET_UNSOLICITED_ENABLE, + AC_USRSP_EN | jack->tag); + if (err < 0) + return ERR_PTR(err); + return callback; +} +EXPORT_SYMBOL_GPL(snd_hda_jack_detect_enable_callback_mst); + +/** + * snd_hda_jack_detect_enable - Enable the jack detection on the given pin + * @codec: the HDA codec + * @nid: pin NID to enable jack detection + * @dev_id: pin device entry id + * + * Enable the jack detection with the default callback. Returns zero if + * successful or a negative error code. + */ +int snd_hda_jack_detect_enable(struct hda_codec *codec, hda_nid_t nid, + int dev_id) +{ + return PTR_ERR_OR_ZERO(snd_hda_jack_detect_enable_callback_mst(codec, + nid, + dev_id, + NULL)); +} +EXPORT_SYMBOL_GPL(snd_hda_jack_detect_enable); + +/** + * snd_hda_jack_set_gating_jack - Set gating jack. + * @codec: the HDA codec + * @gated_nid: gated pin NID + * @gating_nid: gating pin NID + * + * Indicates the gated jack is only valid when the gating jack is plugged. + */ +int snd_hda_jack_set_gating_jack(struct hda_codec *codec, hda_nid_t gated_nid, + hda_nid_t gating_nid) +{ + struct hda_jack_tbl *gated = snd_hda_jack_tbl_new(codec, gated_nid, 0); + struct hda_jack_tbl *gating = + snd_hda_jack_tbl_new(codec, gating_nid, 0); + + WARN_ON(codec->dp_mst); + + if (!gated || !gating) + return -EINVAL; + + gated->gating_jack = gating_nid; + gating->gated_jack = gated_nid; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_jack_set_gating_jack); + +/** + * snd_hda_jack_bind_keymap - bind keys generated from one NID to another jack. + * @codec: the HDA codec + * @key_nid: key event is generated by this pin NID + * @keymap: map of key type and key code + * @jack_nid: key reports to the jack of this pin NID + * + * This function is used in the case of key is generated from one NID while is + * reported to the jack of another NID. + */ +int snd_hda_jack_bind_keymap(struct hda_codec *codec, hda_nid_t key_nid, + const struct hda_jack_keymap *keymap, + hda_nid_t jack_nid) +{ + const struct hda_jack_keymap *map; + struct hda_jack_tbl *key_gen = snd_hda_jack_tbl_get(codec, key_nid); + struct hda_jack_tbl *report_to = snd_hda_jack_tbl_get(codec, jack_nid); + + WARN_ON(codec->dp_mst); + + if (!key_gen || !report_to || !report_to->jack) + return -EINVAL; + + key_gen->key_report_jack = jack_nid; + + if (keymap) + for (map = keymap; map->type; map++) + snd_jack_set_key(report_to->jack, map->type, map->key); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_jack_bind_keymap); + +/** + * snd_hda_jack_set_button_state - report button event to the hda_jack_tbl button_state. + * @codec: the HDA codec + * @jack_nid: the button event reports to the jack_tbl of this NID + * @button_state: the button event captured by codec + * + * Codec driver calls this function to report the button event. + */ +void snd_hda_jack_set_button_state(struct hda_codec *codec, hda_nid_t jack_nid, + int button_state) +{ + struct hda_jack_tbl *jack = snd_hda_jack_tbl_get(codec, jack_nid); + + if (!jack) + return; + + if (jack->key_report_jack) { + struct hda_jack_tbl *report_to = + snd_hda_jack_tbl_get(codec, jack->key_report_jack); + + if (report_to) { + report_to->button_state = button_state; + return; + } + } + + jack->button_state = button_state; +} +EXPORT_SYMBOL_GPL(snd_hda_jack_set_button_state); + +/** + * snd_hda_jack_report_sync - sync the states of all jacks and report if changed + * @codec: the HDA codec + */ +void snd_hda_jack_report_sync(struct hda_codec *codec) +{ + struct hda_jack_tbl *jack; + int i, state; + + /* update all jacks at first */ + jack = codec->jacktbl.list; + for (i = 0; i < codec->jacktbl.used; i++, jack++) + if (jack->nid) + jack_detect_update(codec, jack); + + /* report the updated jacks; it's done after updating all jacks + * to make sure that all gating jacks properly have been set + */ + jack = codec->jacktbl.list; + for (i = 0; i < codec->jacktbl.used; i++, jack++) + if (jack->nid) { + if (!jack->jack || jack->block_report) + continue; + state = jack->button_state; + if (get_jack_plug_state(jack->pin_sense)) + state |= jack->type; + snd_jack_report(jack->jack, state); + if (jack->button_state) { + snd_jack_report(jack->jack, + state & ~jack->button_state); + jack->button_state = 0; /* button released */ + } + } +} +EXPORT_SYMBOL_GPL(snd_hda_jack_report_sync); + +/* guess the jack type from the pin-config */ +static int get_input_jack_type(struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid); + switch (get_defcfg_device(def_conf)) { + case AC_JACK_LINE_OUT: + case AC_JACK_SPEAKER: + return SND_JACK_LINEOUT; + case AC_JACK_HP_OUT: + return SND_JACK_HEADPHONE; + case AC_JACK_SPDIF_OUT: + case AC_JACK_DIG_OTHER_OUT: + return SND_JACK_AVOUT; + case AC_JACK_MIC_IN: + return SND_JACK_MICROPHONE; + default: + return SND_JACK_LINEIN; + } +} + +static void hda_free_jack_priv(struct snd_jack *jack) +{ + struct hda_jack_tbl *jacks = jack->private_data; + jacks->nid = 0; + jacks->jack = NULL; +} + +/** + * snd_hda_jack_add_kctl_mst - Add a kctl for the given pin + * @codec: the HDA codec + * @nid: pin NID to assign + * @dev_id : pin device entry id + * @name: string name for the jack + * @phantom_jack: flag to deal as a phantom jack + * @type: jack type bits to be reported, 0 for guessing from pincfg + * @keymap: optional jack / key mapping + * + * This assigns a jack-detection kctl to the given pin. The kcontrol + * will have the given name and index. + */ +int snd_hda_jack_add_kctl_mst(struct hda_codec *codec, hda_nid_t nid, + int dev_id, const char *name, bool phantom_jack, + int type, const struct hda_jack_keymap *keymap) +{ + struct hda_jack_tbl *jack; + const struct hda_jack_keymap *map; + int err, state, buttons; + + jack = snd_hda_jack_tbl_new(codec, nid, dev_id); + if (!jack) + return 0; + if (jack->jack) + return 0; /* already created */ + + if (!type) + type = get_input_jack_type(codec, nid); + + buttons = 0; + if (keymap) { + for (map = keymap; map->type; map++) + buttons |= map->type; + } + + err = snd_jack_new(codec->card, name, type | buttons, + &jack->jack, true, phantom_jack); + if (err < 0) + return err; + + jack->phantom_jack = !!phantom_jack; + jack->type = type; + jack->button_state = 0; + jack->jack->private_data = jack; + jack->jack->private_free = hda_free_jack_priv; + if (keymap) { + for (map = keymap; map->type; map++) + snd_jack_set_key(jack->jack, map->type, map->key); + } + + state = snd_hda_jack_detect_mst(codec, nid, dev_id); + snd_jack_report(jack->jack, state ? jack->type : 0); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_jack_add_kctl_mst); + +static int add_jack_kctl(struct hda_codec *codec, hda_nid_t nid, + const struct auto_pin_cfg *cfg, + const char *base_name) +{ + unsigned int def_conf, conn; + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + int err; + bool phantom_jack; + + WARN_ON(codec->dp_mst); + + if (!nid) + return 0; + def_conf = snd_hda_codec_get_pincfg(codec, nid); + conn = get_defcfg_connect(def_conf); + if (conn == AC_JACK_PORT_NONE) + return 0; + phantom_jack = (conn != AC_JACK_PORT_COMPLEX) || + !is_jack_detectable(codec, nid); + + if (base_name) + strscpy(name, base_name, sizeof(name)); + else + snd_hda_get_pin_label(codec, nid, cfg, name, sizeof(name), NULL); + if (phantom_jack) + /* Example final name: "Internal Mic Phantom Jack" */ + strncat(name, " Phantom", sizeof(name) - strlen(name) - 1); + err = snd_hda_jack_add_kctl(codec, nid, name, phantom_jack, 0, NULL); + if (err < 0) + return err; + + if (!phantom_jack) + return snd_hda_jack_detect_enable(codec, nid, 0); + return 0; +} + +/** + * snd_hda_jack_add_kctls - Add kctls for all pins included in the given pincfg + * @codec: the HDA codec + * @cfg: pin config table to parse + */ +int snd_hda_jack_add_kctls(struct hda_codec *codec, + const struct auto_pin_cfg *cfg) +{ + const hda_nid_t *p; + int i, err; + + for (i = 0; i < cfg->num_inputs; i++) { + /* If we have headphone mics; make sure they get the right name + before grabbed by output pins */ + if (cfg->inputs[i].is_headphone_mic) { + if (auto_cfg_hp_outs(cfg) == 1) + err = add_jack_kctl(codec, auto_cfg_hp_pins(cfg)[0], + cfg, "Headphone Mic"); + else + err = add_jack_kctl(codec, cfg->inputs[i].pin, + cfg, "Headphone Mic"); + } else + err = add_jack_kctl(codec, cfg->inputs[i].pin, cfg, + NULL); + if (err < 0) + return err; + } + + for (i = 0, p = cfg->line_out_pins; i < cfg->line_outs; i++, p++) { + err = add_jack_kctl(codec, *p, cfg, NULL); + if (err < 0) + return err; + } + for (i = 0, p = cfg->hp_pins; i < cfg->hp_outs; i++, p++) { + if (*p == *cfg->line_out_pins) /* might be duplicated */ + break; + err = add_jack_kctl(codec, *p, cfg, NULL); + if (err < 0) + return err; + } + for (i = 0, p = cfg->speaker_pins; i < cfg->speaker_outs; i++, p++) { + if (*p == *cfg->line_out_pins) /* might be duplicated */ + break; + err = add_jack_kctl(codec, *p, cfg, NULL); + if (err < 0) + return err; + } + for (i = 0, p = cfg->dig_out_pins; i < cfg->dig_outs; i++, p++) { + err = add_jack_kctl(codec, *p, cfg, NULL); + if (err < 0) + return err; + } + err = add_jack_kctl(codec, cfg->dig_in_pin, cfg, NULL); + if (err < 0) + return err; + err = add_jack_kctl(codec, cfg->mono_out_pin, cfg, NULL); + if (err < 0) + return err; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_jack_add_kctls); + +static void call_jack_callback(struct hda_codec *codec, unsigned int res, + struct hda_jack_tbl *jack) +{ + struct hda_jack_callback *cb; + + for (cb = jack->callback; cb; cb = cb->next) { + cb->jack = jack; + cb->unsol_res = res; + cb->func(codec, cb); + } + if (jack->gated_jack) { + struct hda_jack_tbl *gated = + snd_hda_jack_tbl_get_mst(codec, jack->gated_jack, + jack->dev_id); + if (gated) { + for (cb = gated->callback; cb; cb = cb->next) { + cb->jack = gated; + cb->unsol_res = res; + cb->func(codec, cb); + } + } + } +} + +/** + * snd_hda_jack_unsol_event - Handle an unsolicited event + * @codec: the HDA codec + * @res: the unsolicited event data + */ +void snd_hda_jack_unsol_event(struct hda_codec *codec, unsigned int res) +{ + struct hda_jack_tbl *event; + int tag = (res & AC_UNSOL_RES_TAG) >> AC_UNSOL_RES_TAG_SHIFT; + + if (codec->dp_mst) { + int dev_entry = + (res & AC_UNSOL_RES_DE) >> AC_UNSOL_RES_DE_SHIFT; + + event = snd_hda_jack_tbl_get_from_tag(codec, tag, dev_entry); + } else { + event = snd_hda_jack_tbl_get_from_tag(codec, tag, 0); + } + if (!event) + return; + + if (event->key_report_jack) { + struct hda_jack_tbl *report_to = + snd_hda_jack_tbl_get_mst(codec, event->key_report_jack, + event->dev_id); + if (report_to) + report_to->jack_dirty = 1; + } else + event->jack_dirty = 1; + + call_jack_callback(codec, res, event); + snd_hda_jack_report_sync(codec); +} +EXPORT_SYMBOL_GPL(snd_hda_jack_unsol_event); + +/** + * snd_hda_jack_poll_all - Poll all jacks + * @codec: the HDA codec + * + * Poll all detectable jacks with dirty flag, update the status, call + * callbacks and call snd_hda_jack_report_sync() if any changes are found. + */ +void snd_hda_jack_poll_all(struct hda_codec *codec) +{ + struct hda_jack_tbl *jack = codec->jacktbl.list; + int i, changes = 0; + + for (i = 0; i < codec->jacktbl.used; i++, jack++) { + unsigned int old_sense; + if (!jack->nid || !jack->jack_dirty || jack->phantom_jack) + continue; + old_sense = get_jack_plug_state(jack->pin_sense); + jack_detect_update(codec, jack); + if (old_sense == get_jack_plug_state(jack->pin_sense)) + continue; + changes = 1; + call_jack_callback(codec, 0, jack); + } + if (changes) + snd_hda_jack_report_sync(codec); +} +EXPORT_SYMBOL_GPL(snd_hda_jack_poll_all); + diff --git a/sound/pci/hda/hda_jack.h b/sound/pci/hda/hda_jack.h new file mode 100644 index 000000000..ff7d289c0 --- /dev/null +++ b/sound/pci/hda/hda_jack.h @@ -0,0 +1,195 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Jack-detection handling for HD-audio + * + * Copyright (c) 2011 Takashi Iwai <tiwai@suse.de> + */ + +#ifndef __SOUND_HDA_JACK_H +#define __SOUND_HDA_JACK_H + +#include <linux/err.h> +#include <sound/jack.h> + +struct auto_pin_cfg; +struct hda_jack_tbl; +struct hda_jack_callback; + +typedef void (*hda_jack_callback_fn) (struct hda_codec *, struct hda_jack_callback *); + +struct hda_jack_callback { + hda_nid_t nid; + int dev_id; + hda_jack_callback_fn func; + unsigned int private_data; /* arbitrary data */ + unsigned int unsol_res; /* unsolicited event bits */ + struct hda_jack_tbl *jack; /* associated jack entry */ + struct hda_jack_callback *next; +}; + +struct hda_jack_tbl { + hda_nid_t nid; + int dev_id; + unsigned char tag; /* unsol event tag */ + struct hda_jack_callback *callback; + /* jack-detection stuff */ + unsigned int pin_sense; /* cached pin-sense value */ + unsigned int jack_detect:1; /* capable of jack-detection? */ + unsigned int jack_dirty:1; /* needs to update? */ + unsigned int phantom_jack:1; /* a fixed, always present port? */ + unsigned int block_report:1; /* in a transitional state - do not report to userspace */ + hda_nid_t gating_jack; /* valid when gating jack plugged */ + hda_nid_t gated_jack; /* gated is dependent on this jack */ + hda_nid_t key_report_jack; /* key reports to this jack */ + int type; + int button_state; + struct snd_jack *jack; +}; + +struct hda_jack_keymap { + enum snd_jack_types type; + int key; +}; + +struct hda_jack_tbl * +snd_hda_jack_tbl_get_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id); + +/** + * snd_hda_jack_tbl_get - query the jack-table entry for the given NID + * @codec: the HDA codec + * @nid: pin NID to refer to + */ +static inline struct hda_jack_tbl * +snd_hda_jack_tbl_get(struct hda_codec *codec, hda_nid_t nid) +{ + return snd_hda_jack_tbl_get_mst(codec, nid, 0); +} + +struct hda_jack_tbl * +snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec, + unsigned char tag, int dev_id); + +void snd_hda_jack_tbl_disconnect(struct hda_codec *codec); +void snd_hda_jack_tbl_clear(struct hda_codec *codec); + +void snd_hda_jack_set_dirty_all(struct hda_codec *codec); + +int snd_hda_jack_detect_enable(struct hda_codec *codec, hda_nid_t nid, + int dev_id); + +struct hda_jack_callback * +snd_hda_jack_detect_enable_callback_mst(struct hda_codec *codec, hda_nid_t nid, + int dev_id, hda_jack_callback_fn func); + +/** + * snd_hda_jack_detect_enable - enable the jack-detection + * @codec: the HDA codec + * @nid: pin NID to enable + * @func: callback function to register + * + * In the case of error, the return value will be a pointer embedded with + * errno. Check and handle the return value appropriately with standard + * macros such as @IS_ERR() and @PTR_ERR(). + */ +static inline struct hda_jack_callback * +snd_hda_jack_detect_enable_callback(struct hda_codec *codec, hda_nid_t nid, + hda_jack_callback_fn cb) +{ + return snd_hda_jack_detect_enable_callback_mst(codec, nid, 0, cb); +} + +int snd_hda_jack_set_gating_jack(struct hda_codec *codec, hda_nid_t gated_nid, + hda_nid_t gating_nid); + +int snd_hda_jack_bind_keymap(struct hda_codec *codec, hda_nid_t key_nid, + const struct hda_jack_keymap *keymap, + hda_nid_t jack_nid); + +void snd_hda_jack_set_button_state(struct hda_codec *codec, hda_nid_t jack_nid, + int button_state); + +u32 snd_hda_jack_pin_sense(struct hda_codec *codec, hda_nid_t nid, int dev_id); + +/* the jack state returned from snd_hda_jack_detect_state() */ +enum { + HDA_JACK_NOT_PRESENT, HDA_JACK_PRESENT, HDA_JACK_PHANTOM, +}; + +int snd_hda_jack_detect_state_mst(struct hda_codec *codec, hda_nid_t nid, + int dev_id); + +/** + * snd_hda_jack_detect_state - query pin Presence Detect status + * @codec: the CODEC to sense + * @nid: the pin NID to sense + * + * Query and return the pin's Presence Detect status, as either + * HDA_JACK_NOT_PRESENT, HDA_JACK_PRESENT or HDA_JACK_PHANTOM. + */ +static inline int +snd_hda_jack_detect_state(struct hda_codec *codec, hda_nid_t nid) +{ + return snd_hda_jack_detect_state_mst(codec, nid, 0); +} + +/** + * snd_hda_jack_detect_mst - Detect the jack + * @codec: the HDA codec + * @nid: pin NID to check jack detection + * @dev_id: pin device entry id + */ +static inline bool +snd_hda_jack_detect_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id) +{ + return snd_hda_jack_detect_state_mst(codec, nid, dev_id) != + HDA_JACK_NOT_PRESENT; +} + +/** + * snd_hda_jack_detect - Detect the jack + * @codec: the HDA codec + * @nid: pin NID to check jack detection + */ +static inline bool +snd_hda_jack_detect(struct hda_codec *codec, hda_nid_t nid) +{ + return snd_hda_jack_detect_mst(codec, nid, 0); +} + +bool is_jack_detectable(struct hda_codec *codec, hda_nid_t nid); + +int snd_hda_jack_add_kctl_mst(struct hda_codec *codec, hda_nid_t nid, + int dev_id, const char *name, bool phantom_jack, + int type, const struct hda_jack_keymap *keymap); + +/** + * snd_hda_jack_add_kctl - Add a kctl for the given pin + * @codec: the HDA codec + * @nid: pin NID to assign + * @name: string name for the jack + * @phantom_jack: flag to deal as a phantom jack + * @type: jack type bits to be reported, 0 for guessing from pincfg + * @keymap: optional jack / key mapping + * + * This assigns a jack-detection kctl to the given pin. The kcontrol + * will have the given name and index. + */ +static inline int +snd_hda_jack_add_kctl(struct hda_codec *codec, hda_nid_t nid, + const char *name, bool phantom_jack, + int type, const struct hda_jack_keymap *keymap) +{ + return snd_hda_jack_add_kctl_mst(codec, nid, 0, + name, phantom_jack, type, keymap); +} + +int snd_hda_jack_add_kctls(struct hda_codec *codec, + const struct auto_pin_cfg *cfg); + +void snd_hda_jack_report_sync(struct hda_codec *codec); + +void snd_hda_jack_unsol_event(struct hda_codec *codec, unsigned int res); + +void snd_hda_jack_poll_all(struct hda_codec *codec); + +#endif /* __SOUND_HDA_JACK_H */ diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h new file mode 100644 index 000000000..53a5a62b7 --- /dev/null +++ b/sound/pci/hda/hda_local.h @@ -0,0 +1,737 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Universal Interface for Intel High Definition Audio Codec + * + * Local helper functions + * + * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de> + */ + +#ifndef __SOUND_HDA_LOCAL_H +#define __SOUND_HDA_LOCAL_H + +/* We abuse kcontrol_new.subdev field to pass the NID corresponding to + * the given new control. If id.subdev has a bit flag HDA_SUBDEV_NID_FLAG, + * snd_hda_ctl_add() takes the lower-bit subdev value as a valid NID. + * + * Note that the subdevice field is cleared again before the real registration + * in snd_hda_ctl_add(), so that this value won't appear in the outside. + */ +#define HDA_SUBDEV_NID_FLAG (1U << 31) +#define HDA_SUBDEV_AMP_FLAG (1U << 30) + +/* + * for mixer controls + */ +#define HDA_COMPOSE_AMP_VAL_OFS(nid,chs,idx,dir,ofs) \ + ((nid) | ((chs)<<16) | ((dir)<<18) | ((idx)<<19) | ((ofs)<<23)) +#define HDA_AMP_VAL_MIN_MUTE (1<<29) +#define HDA_COMPOSE_AMP_VAL(nid,chs,idx,dir) \ + HDA_COMPOSE_AMP_VAL_OFS(nid, chs, idx, dir, 0) +/* mono volume with index (index=0,1,...) (channel=1,2) */ +#define HDA_CODEC_VOLUME_MONO_IDX(xname, xcidx, nid, channel, xindex, dir, flags) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \ + .subdevice = HDA_SUBDEV_AMP_FLAG, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, \ + .info = snd_hda_mixer_amp_volume_info, \ + .get = snd_hda_mixer_amp_volume_get, \ + .put = snd_hda_mixer_amp_volume_put, \ + .tlv = { .c = snd_hda_mixer_amp_tlv }, \ + .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, dir) | flags } +/* stereo volume with index */ +#define HDA_CODEC_VOLUME_IDX(xname, xcidx, nid, xindex, direction) \ + HDA_CODEC_VOLUME_MONO_IDX(xname, xcidx, nid, 3, xindex, direction, 0) +/* mono volume */ +#define HDA_CODEC_VOLUME_MONO(xname, nid, channel, xindex, direction) \ + HDA_CODEC_VOLUME_MONO_IDX(xname, 0, nid, channel, xindex, direction, 0) +/* stereo volume */ +#define HDA_CODEC_VOLUME(xname, nid, xindex, direction) \ + HDA_CODEC_VOLUME_MONO(xname, nid, 3, xindex, direction) +/* stereo volume with min=mute */ +#define HDA_CODEC_VOLUME_MIN_MUTE(xname, nid, xindex, direction) \ + HDA_CODEC_VOLUME_MONO_IDX(xname, 0, nid, 3, xindex, direction, \ + HDA_AMP_VAL_MIN_MUTE) +/* mono mute switch with index (index=0,1,...) (channel=1,2) */ +#define HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \ + .subdevice = HDA_SUBDEV_AMP_FLAG, \ + .info = snd_hda_mixer_amp_switch_info, \ + .get = snd_hda_mixer_amp_switch_get, \ + .put = snd_hda_mixer_amp_switch_put, \ + .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, direction) } +/* stereo mute switch with index */ +#define HDA_CODEC_MUTE_IDX(xname, xcidx, nid, xindex, direction) \ + HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, 3, xindex, direction) +/* mono mute switch */ +#define HDA_CODEC_MUTE_MONO(xname, nid, channel, xindex, direction) \ + HDA_CODEC_MUTE_MONO_IDX(xname, 0, nid, channel, xindex, direction) +/* stereo mute switch */ +#define HDA_CODEC_MUTE(xname, nid, xindex, direction) \ + HDA_CODEC_MUTE_MONO(xname, nid, 3, xindex, direction) +#ifdef CONFIG_SND_HDA_INPUT_BEEP +/* special beep mono mute switch with index (index=0,1,...) (channel=1,2) */ +#define HDA_CODEC_MUTE_BEEP_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \ + .subdevice = HDA_SUBDEV_AMP_FLAG, \ + .info = snd_hda_mixer_amp_switch_info, \ + .get = snd_hda_mixer_amp_switch_get_beep, \ + .put = snd_hda_mixer_amp_switch_put_beep, \ + .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, direction) } +#else +/* no digital beep - just the standard one */ +#define HDA_CODEC_MUTE_BEEP_MONO_IDX(xname, xcidx, nid, ch, xidx, dir) \ + HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, ch, xidx, dir) +#endif /* CONFIG_SND_HDA_INPUT_BEEP */ +/* special beep mono mute switch */ +#define HDA_CODEC_MUTE_BEEP_MONO(xname, nid, channel, xindex, direction) \ + HDA_CODEC_MUTE_BEEP_MONO_IDX(xname, 0, nid, channel, xindex, direction) +/* special beep stereo mute switch */ +#define HDA_CODEC_MUTE_BEEP(xname, nid, xindex, direction) \ + HDA_CODEC_MUTE_BEEP_MONO(xname, nid, 3, xindex, direction) + +extern const char *snd_hda_pcm_type_name[]; + +int snd_hda_mixer_amp_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo); +int snd_hda_mixer_amp_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_hda_mixer_amp_tlv(struct snd_kcontrol *kcontrol, int op_flag, + unsigned int size, unsigned int __user *_tlv); +int snd_hda_mixer_amp_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo); +int snd_hda_mixer_amp_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_hda_mixer_amp_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +#ifdef CONFIG_SND_HDA_INPUT_BEEP +int snd_hda_mixer_amp_switch_get_beep(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_hda_mixer_amp_switch_put_beep(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +#endif +/* lowlevel accessor with caching; use carefully */ +#define snd_hda_codec_amp_read(codec, nid, ch, dir, idx) \ + snd_hdac_regmap_get_amp(&(codec)->core, nid, ch, dir, idx) +int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid, + int ch, int dir, int idx, int mask, int val); +int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid, + int direction, int idx, int mask, int val); +int snd_hda_codec_amp_init(struct hda_codec *codec, hda_nid_t nid, int ch, + int direction, int idx, int mask, int val); +int snd_hda_codec_amp_init_stereo(struct hda_codec *codec, hda_nid_t nid, + int dir, int idx, int mask, int val); +void snd_hda_set_vmaster_tlv(struct hda_codec *codec, hda_nid_t nid, int dir, + unsigned int *tlv); +struct snd_kcontrol *snd_hda_find_mixer_ctl(struct hda_codec *codec, + const char *name); +int __snd_hda_add_vmaster(struct hda_codec *codec, char *name, + unsigned int *tlv, const char * const *followers, + const char *suffix, bool init_follower_vol, + unsigned int access, struct snd_kcontrol **ctl_ret); +#define snd_hda_add_vmaster(codec, name, tlv, followers, suffix, access) \ + __snd_hda_add_vmaster(codec, name, tlv, followers, suffix, true, access, NULL) +int snd_hda_codec_reset(struct hda_codec *codec); +void snd_hda_codec_disconnect_pcms(struct hda_codec *codec); + +#define snd_hda_regmap_sync(codec) snd_hdac_regmap_sync(&(codec)->core) + +struct hda_vmaster_mute_hook { + /* below two fields must be filled by the caller of + * snd_hda_add_vmaster_hook() beforehand + */ + struct snd_kcontrol *sw_kctl; + void (*hook)(void *, int); + /* below are initialized automatically */ + struct hda_codec *codec; +}; + +int snd_hda_add_vmaster_hook(struct hda_codec *codec, + struct hda_vmaster_mute_hook *hook); +void snd_hda_sync_vmaster_hook(struct hda_vmaster_mute_hook *hook); + +/* amp value bits */ +#define HDA_AMP_MUTE 0x80 +#define HDA_AMP_UNMUTE 0x00 +#define HDA_AMP_VOLMASK 0x7f + +/* + * SPDIF I/O + */ +int snd_hda_create_dig_out_ctls(struct hda_codec *codec, + hda_nid_t associated_nid, + hda_nid_t cvt_nid, int type); +#define snd_hda_create_spdif_out_ctls(codec, anid, cnid) \ + snd_hda_create_dig_out_ctls(codec, anid, cnid, HDA_PCM_TYPE_SPDIF) +int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid); + +/* + * input MUX helper + */ +#define HDA_MAX_NUM_INPUTS 36 +struct hda_input_mux_item { + char label[32]; + unsigned int index; +}; +struct hda_input_mux { + unsigned int num_items; + struct hda_input_mux_item items[HDA_MAX_NUM_INPUTS]; +}; + +int snd_hda_input_mux_info(const struct hda_input_mux *imux, + struct snd_ctl_elem_info *uinfo); +int snd_hda_input_mux_put(struct hda_codec *codec, + const struct hda_input_mux *imux, + struct snd_ctl_elem_value *ucontrol, hda_nid_t nid, + unsigned int *cur_val); +int snd_hda_add_imux_item(struct hda_codec *codec, + struct hda_input_mux *imux, const char *label, + int index, int *type_idx); + +/* + * Multi-channel / digital-out PCM helper + */ + +enum { HDA_FRONT, HDA_REAR, HDA_CLFE, HDA_SIDE }; /* index for dac_nidx */ +enum { HDA_DIG_NONE, HDA_DIG_EXCLUSIVE, HDA_DIG_ANALOG_DUP }; /* dig_out_used */ + +#define HDA_MAX_OUTS 5 + +struct hda_multi_out { + int num_dacs; /* # of DACs, must be more than 1 */ + const hda_nid_t *dac_nids; /* DAC list */ + hda_nid_t hp_nid; /* optional DAC for HP, 0 when not exists */ + hda_nid_t hp_out_nid[HDA_MAX_OUTS]; /* DACs for multiple HPs */ + hda_nid_t extra_out_nid[HDA_MAX_OUTS]; /* other (e.g. speaker) DACs */ + hda_nid_t dig_out_nid; /* digital out audio widget */ + const hda_nid_t *follower_dig_outs; + int max_channels; /* currently supported analog channels */ + int dig_out_used; /* current usage of digital out (HDA_DIG_XXX) */ + int no_share_stream; /* don't share a stream with multiple pins */ + int share_spdif; /* share SPDIF pin */ + /* PCM information for both analog and SPDIF DACs */ + unsigned int analog_rates; + unsigned int analog_maxbps; + u64 analog_formats; + unsigned int spdif_rates; + unsigned int spdif_maxbps; + u64 spdif_formats; +}; + +int snd_hda_create_spdif_share_sw(struct hda_codec *codec, + struct hda_multi_out *mout); +int snd_hda_multi_out_dig_open(struct hda_codec *codec, + struct hda_multi_out *mout); +int snd_hda_multi_out_dig_close(struct hda_codec *codec, + struct hda_multi_out *mout); +int snd_hda_multi_out_dig_prepare(struct hda_codec *codec, + struct hda_multi_out *mout, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream); +int snd_hda_multi_out_dig_cleanup(struct hda_codec *codec, + struct hda_multi_out *mout); +int snd_hda_multi_out_analog_open(struct hda_codec *codec, + struct hda_multi_out *mout, + struct snd_pcm_substream *substream, + struct hda_pcm_stream *hinfo); +int snd_hda_multi_out_analog_prepare(struct hda_codec *codec, + struct hda_multi_out *mout, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream); +int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec, + struct hda_multi_out *mout); + +/* + * generic proc interface + */ +#ifdef CONFIG_SND_PROC_FS +int snd_hda_codec_proc_new(struct hda_codec *codec); +#else +static inline int snd_hda_codec_proc_new(struct hda_codec *codec) { return 0; } +#endif + +#define SND_PRINT_BITS_ADVISED_BUFSIZE 16 +void snd_print_pcm_bits(int pcm, char *buf, int buflen); + +/* + * Misc + */ +int snd_hda_add_new_ctls(struct hda_codec *codec, + const struct snd_kcontrol_new *knew); + +/* + * Fix-up pin default configurations and add default verbs + */ + +struct hda_pintbl { + hda_nid_t nid; + u32 val; +}; + +struct hda_model_fixup { + const int id; + const char *name; +}; + +struct hda_fixup { + int type; + bool chained:1; /* call the chained fixup(s) after this */ + bool chained_before:1; /* call the chained fixup(s) before this */ + int chain_id; + union { + const struct hda_pintbl *pins; + const struct hda_verb *verbs; + void (*func)(struct hda_codec *codec, + const struct hda_fixup *fix, + int action); + } v; +}; + +struct snd_hda_pin_quirk { + unsigned int codec; /* Codec vendor/device ID */ + unsigned short subvendor; /* PCI subvendor ID */ + const struct hda_pintbl *pins; /* list of matching pins */ +#ifdef CONFIG_SND_DEBUG_VERBOSE + const char *name; +#endif + int value; /* quirk value */ +}; + +#ifdef CONFIG_SND_DEBUG_VERBOSE + +#define SND_HDA_PIN_QUIRK(_codec, _subvendor, _name, _value, _pins...) \ + { .codec = _codec,\ + .subvendor = _subvendor,\ + .name = _name,\ + .value = _value,\ + .pins = (const struct hda_pintbl[]) { _pins, {0, 0}} \ + } +#else + +#define SND_HDA_PIN_QUIRK(_codec, _subvendor, _name, _value, _pins...) \ + { .codec = _codec,\ + .subvendor = _subvendor,\ + .value = _value,\ + .pins = (const struct hda_pintbl[]) { _pins, {0, 0}} \ + } + +#endif + +#define HDA_FIXUP_ID_NOT_SET -1 +#define HDA_FIXUP_ID_NO_FIXUP -2 + +/* fixup types */ +enum { + HDA_FIXUP_INVALID, + HDA_FIXUP_PINS, + HDA_FIXUP_VERBS, + HDA_FIXUP_FUNC, + HDA_FIXUP_PINCTLS, +}; + +/* fixup action definitions */ +enum { + HDA_FIXUP_ACT_PRE_PROBE, + HDA_FIXUP_ACT_PROBE, + HDA_FIXUP_ACT_INIT, + HDA_FIXUP_ACT_BUILD, + HDA_FIXUP_ACT_FREE, +}; + +int snd_hda_add_verbs(struct hda_codec *codec, const struct hda_verb *list); +void snd_hda_apply_verbs(struct hda_codec *codec); +void snd_hda_apply_pincfgs(struct hda_codec *codec, + const struct hda_pintbl *cfg); +void snd_hda_apply_fixup(struct hda_codec *codec, int action); +void __snd_hda_apply_fixup(struct hda_codec *codec, int id, int action, int depth); +void snd_hda_pick_fixup(struct hda_codec *codec, + const struct hda_model_fixup *models, + const struct snd_pci_quirk *quirk, + const struct hda_fixup *fixlist); +void snd_hda_pick_pin_fixup(struct hda_codec *codec, + const struct snd_hda_pin_quirk *pin_quirk, + const struct hda_fixup *fixlist, + bool match_all_pins); + +/* helper macros to retrieve pin default-config values */ +#define get_defcfg_connect(cfg) \ + ((cfg & AC_DEFCFG_PORT_CONN) >> AC_DEFCFG_PORT_CONN_SHIFT) +#define get_defcfg_association(cfg) \ + ((cfg & AC_DEFCFG_DEF_ASSOC) >> AC_DEFCFG_ASSOC_SHIFT) +#define get_defcfg_location(cfg) \ + ((cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT) +#define get_defcfg_sequence(cfg) \ + (cfg & AC_DEFCFG_SEQUENCE) +#define get_defcfg_device(cfg) \ + ((cfg & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT) +#define get_defcfg_misc(cfg) \ + ((cfg & AC_DEFCFG_MISC) >> AC_DEFCFG_MISC_SHIFT) + +/* amp values */ +#define AMP_IN_MUTE(idx) (0x7080 | ((idx)<<8)) +#define AMP_IN_UNMUTE(idx) (0x7000 | ((idx)<<8)) +#define AMP_OUT_MUTE 0xb080 +#define AMP_OUT_UNMUTE 0xb000 +#define AMP_OUT_ZERO 0xb000 +/* pinctl values */ +#define PIN_IN (AC_PINCTL_IN_EN) +#define PIN_VREFHIZ (AC_PINCTL_IN_EN | AC_PINCTL_VREF_HIZ) +#define PIN_VREF50 (AC_PINCTL_IN_EN | AC_PINCTL_VREF_50) +#define PIN_VREFGRD (AC_PINCTL_IN_EN | AC_PINCTL_VREF_GRD) +#define PIN_VREF80 (AC_PINCTL_IN_EN | AC_PINCTL_VREF_80) +#define PIN_VREF100 (AC_PINCTL_IN_EN | AC_PINCTL_VREF_100) +#define PIN_OUT (AC_PINCTL_OUT_EN) +#define PIN_HP (AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN) +#define PIN_HP_AMP (AC_PINCTL_HP_EN) + +unsigned int snd_hda_get_default_vref(struct hda_codec *codec, hda_nid_t pin); +unsigned int snd_hda_correct_pin_ctl(struct hda_codec *codec, + hda_nid_t pin, unsigned int val); +int _snd_hda_set_pin_ctl(struct hda_codec *codec, hda_nid_t pin, + unsigned int val, bool cached); + +/** + * _snd_hda_set_pin_ctl - Set a pin-control value safely + * @codec: the codec instance + * @pin: the pin NID to set the control + * @val: the pin-control value (AC_PINCTL_* bits) + * + * This function sets the pin-control value to the given pin, but + * filters out the invalid pin-control bits when the pin has no such + * capabilities. For example, when PIN_HP is passed but the pin has no + * HP-drive capability, the HP bit is omitted. + * + * The function doesn't check the input VREF capability bits, though. + * Use snd_hda_get_default_vref() to guess the right value. + * Also, this function is only for analog pins, not for HDMI pins. + */ +static inline int +snd_hda_set_pin_ctl(struct hda_codec *codec, hda_nid_t pin, unsigned int val) +{ + return _snd_hda_set_pin_ctl(codec, pin, val, false); +} + +/** + * snd_hda_set_pin_ctl_cache - Set a pin-control value safely + * @codec: the codec instance + * @pin: the pin NID to set the control + * @val: the pin-control value (AC_PINCTL_* bits) + * + * Just like snd_hda_set_pin_ctl() but write to cache as well. + */ +static inline int +snd_hda_set_pin_ctl_cache(struct hda_codec *codec, hda_nid_t pin, + unsigned int val) +{ + return _snd_hda_set_pin_ctl(codec, pin, val, true); +} + +int snd_hda_codec_get_pin_target(struct hda_codec *codec, hda_nid_t nid); +int snd_hda_codec_set_pin_target(struct hda_codec *codec, hda_nid_t nid, + unsigned int val); + +#define for_each_hda_codec_node(nid, codec) \ + for ((nid) = (codec)->core.start_nid; (nid) < (codec)->core.end_nid; (nid)++) + +/* Set the codec power_state flag to indicate to allow unsol event handling; + * see hda_codec_unsol_event() in hda_bind.c. Calling this might confuse the + * state tracking, so use with care. + */ +static inline void snd_hda_codec_allow_unsol_events(struct hda_codec *codec) +{ + codec->core.dev.power.power_state = PMSG_ON; +} + +/* + * get widget capabilities + */ +static inline u32 get_wcaps(struct hda_codec *codec, hda_nid_t nid) +{ + if (nid < codec->core.start_nid || + nid >= codec->core.start_nid + codec->core.num_nodes) + return 0; + return codec->wcaps[nid - codec->core.start_nid]; +} + +/* get the widget type from widget capability bits */ +static inline int get_wcaps_type(unsigned int wcaps) +{ + if (!wcaps) + return -1; /* invalid type */ + return (wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT; +} + +static inline unsigned int get_wcaps_channels(u32 wcaps) +{ + unsigned int chans; + + chans = (wcaps & AC_WCAP_CHAN_CNT_EXT) >> 13; + chans = ((chans << 1) | 1) + 1; + + return chans; +} + +static inline void snd_hda_override_wcaps(struct hda_codec *codec, + hda_nid_t nid, u32 val) +{ + if (nid >= codec->core.start_nid && + nid < codec->core.start_nid + codec->core.num_nodes) + codec->wcaps[nid - codec->core.start_nid] = val; +} + +u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction); +int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir, + unsigned int caps); +/** + * snd_hda_query_pin_caps - Query PIN capabilities + * @codec: the HD-auio codec + * @nid: the NID to query + * + * Query PIN capabilities for the given widget. + * Returns the obtained capability bits. + * + * When cap bits have been already read, this doesn't read again but + * returns the cached value. + */ +static inline u32 +snd_hda_query_pin_caps(struct hda_codec *codec, hda_nid_t nid) +{ + return snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP); + +} + +/** + * snd_hda_override_pin_caps - Override the pin capabilities + * @codec: the CODEC + * @nid: the NID to override + * @caps: the capability bits to set + * + * Override the cached PIN capabilitiy bits value by the given one. + * + * Returns zero if successful or a negative error code. + */ +static inline int +snd_hda_override_pin_caps(struct hda_codec *codec, hda_nid_t nid, + unsigned int caps) +{ + return snd_hdac_override_parm(&codec->core, nid, AC_PAR_PIN_CAP, caps); +} + +bool snd_hda_check_amp_caps(struct hda_codec *codec, hda_nid_t nid, + int dir, unsigned int bits); + +#define nid_has_mute(codec, nid, dir) \ + snd_hda_check_amp_caps(codec, nid, dir, (AC_AMPCAP_MUTE | AC_AMPCAP_MIN_MUTE)) +#define nid_has_volume(codec, nid, dir) \ + snd_hda_check_amp_caps(codec, nid, dir, AC_AMPCAP_NUM_STEPS) + + +/* flags for hda_nid_item */ +#define HDA_NID_ITEM_AMP (1<<0) + +struct hda_nid_item { + struct snd_kcontrol *kctl; + unsigned int index; + hda_nid_t nid; + unsigned short flags; +}; + +int snd_hda_ctl_add(struct hda_codec *codec, hda_nid_t nid, + struct snd_kcontrol *kctl); +int snd_hda_add_nid(struct hda_codec *codec, struct snd_kcontrol *kctl, + unsigned int index, hda_nid_t nid); +void snd_hda_ctls_clear(struct hda_codec *codec); + +/* + * hwdep interface + */ +#ifdef CONFIG_SND_HDA_HWDEP +int snd_hda_create_hwdep(struct hda_codec *codec); +#else +static inline int snd_hda_create_hwdep(struct hda_codec *codec) { return 0; } +#endif + +void snd_hda_sysfs_init(struct hda_codec *codec); +void snd_hda_sysfs_clear(struct hda_codec *codec); + +extern const struct attribute_group *snd_hda_dev_attr_groups[]; + +#ifdef CONFIG_SND_HDA_RECONFIG +const char *snd_hda_get_hint(struct hda_codec *codec, const char *key); +int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key); +int snd_hda_get_int_hint(struct hda_codec *codec, const char *key, int *valp); +#else +static inline +const char *snd_hda_get_hint(struct hda_codec *codec, const char *key) +{ + return NULL; +} + +static inline +int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key) +{ + return -ENOENT; +} + +static inline +int snd_hda_get_int_hint(struct hda_codec *codec, const char *key, int *valp) +{ + return -ENOENT; +} +#endif + +/* + * power-management + */ + +void snd_hda_schedule_power_save(struct hda_codec *codec); + +struct hda_amp_list { + hda_nid_t nid; + unsigned char dir; + unsigned char idx; +}; + +struct hda_loopback_check { + const struct hda_amp_list *amplist; + int power_on; +}; + +int snd_hda_check_amp_list_power(struct hda_codec *codec, + struct hda_loopback_check *check, + hda_nid_t nid); + +/* check whether the actual power state matches with the target state */ +static inline bool +snd_hda_check_power_state(struct hda_codec *codec, hda_nid_t nid, + unsigned int target_state) +{ + return snd_hdac_check_power_state(&codec->core, nid, target_state); +} + +static inline unsigned int snd_hda_sync_power_state(struct hda_codec *codec, + hda_nid_t nid, + unsigned int target_state) +{ + return snd_hdac_sync_power_state(&codec->core, nid, target_state); +} +unsigned int snd_hda_codec_eapd_power_filter(struct hda_codec *codec, + hda_nid_t nid, + unsigned int power_state); + +void snd_hda_codec_shutdown(struct hda_codec *codec); + +/* + * AMP control callbacks + */ +/* retrieve parameters from private_value */ +#define get_amp_nid_(pv) ((pv) & 0xffff) +#define get_amp_nid(kc) get_amp_nid_((kc)->private_value) +#define get_amp_channels(kc) (((kc)->private_value >> 16) & 0x3) +#define get_amp_direction_(pv) (((pv) >> 18) & 0x1) +#define get_amp_direction(kc) get_amp_direction_((kc)->private_value) +#define get_amp_index_(pv) (((pv) >> 19) & 0xf) +#define get_amp_index(kc) get_amp_index_((kc)->private_value) +#define get_amp_offset(kc) (((kc)->private_value >> 23) & 0x3f) +#define get_amp_min_mute(kc) (((kc)->private_value >> 29) & 0x1) + +/* + * enum control helper + */ +int snd_hda_enum_helper_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo, + int num_items, const char * const *texts); +#define snd_hda_enum_bool_helper_info(kcontrol, uinfo) \ + snd_hda_enum_helper_info(kcontrol, uinfo, 0, NULL) + +/* + * CEA Short Audio Descriptor data + */ +struct cea_sad { + int channels; + int format; /* (format == 0) indicates invalid SAD */ + int rates; + int sample_bits; /* for LPCM */ + int max_bitrate; /* for AC3...ATRAC */ + int profile; /* for WMAPRO */ +}; + +#define ELD_FIXED_BYTES 20 +#define ELD_MAX_SIZE 256 +#define ELD_MAX_MNL 16 +#define ELD_MAX_SAD 16 + +/* + * ELD: EDID Like Data + */ +struct parsed_hdmi_eld { + /* + * all fields will be cleared before updating ELD + */ + int baseline_len; + int eld_ver; + int cea_edid_ver; + char monitor_name[ELD_MAX_MNL + 1]; + int manufacture_id; + int product_id; + u64 port_id; + int support_hdcp; + int support_ai; + int conn_type; + int aud_synch_delay; + int spk_alloc; + int sad_count; + struct cea_sad sad[ELD_MAX_SAD]; +}; + +struct hdmi_eld { + bool monitor_present; + bool eld_valid; + int eld_size; + char eld_buffer[ELD_MAX_SIZE]; + struct parsed_hdmi_eld info; +}; + +int snd_hdmi_get_eld_size(struct hda_codec *codec, hda_nid_t nid); +int snd_hdmi_get_eld(struct hda_codec *codec, hda_nid_t nid, + unsigned char *buf, int *eld_size); +int snd_hdmi_parse_eld(struct hda_codec *codec, struct parsed_hdmi_eld *e, + const unsigned char *buf, int size); +void snd_hdmi_show_eld(struct hda_codec *codec, struct parsed_hdmi_eld *e); +void snd_hdmi_eld_update_pcm_info(struct parsed_hdmi_eld *e, + struct hda_pcm_stream *hinfo); + +int snd_hdmi_get_eld_ati(struct hda_codec *codec, hda_nid_t nid, + unsigned char *buf, int *eld_size, + bool rev3_or_later); + +#ifdef CONFIG_SND_PROC_FS +void snd_hdmi_print_eld_info(struct hdmi_eld *eld, + struct snd_info_buffer *buffer, + hda_nid_t pin_nid, int dev_id, hda_nid_t cvt_nid); +void snd_hdmi_write_eld_info(struct hdmi_eld *eld, + struct snd_info_buffer *buffer); +#endif + +#define SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE 80 +void snd_print_channel_allocation(int spk_alloc, char *buf, int buflen); + +void snd_hda_codec_display_power(struct hda_codec *codec, bool enable); + +/* + */ +#define codec_err(codec, fmt, args...) \ + dev_err(hda_codec_dev(codec), fmt, ##args) +#define codec_warn(codec, fmt, args...) \ + dev_warn(hda_codec_dev(codec), fmt, ##args) +#define codec_info(codec, fmt, args...) \ + dev_info(hda_codec_dev(codec), fmt, ##args) +#define codec_dbg(codec, fmt, args...) \ + dev_dbg(hda_codec_dev(codec), fmt, ##args) + +#endif /* __SOUND_HDA_LOCAL_H */ diff --git a/sound/pci/hda/hda_proc.c b/sound/pci/hda/hda_proc.c new file mode 100644 index 000000000..00c2eeb2c --- /dev/null +++ b/sound/pci/hda/hda_proc.c @@ -0,0 +1,948 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Universal Interface for Intel High Definition Audio Codec + * + * Generic proc interface + * + * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <linux/module.h> +#include <sound/hda_codec.h> +#include "hda_local.h" + +static int dump_coef = -1; +module_param(dump_coef, int, 0644); +MODULE_PARM_DESC(dump_coef, "Dump processing coefficients in codec proc file (-1=auto, 0=disable, 1=enable)"); + +/* always use noncached version */ +#define param_read(codec, nid, parm) \ + snd_hdac_read_parm_uncached(&(codec)->core, nid, parm) + +static const char *get_wid_type_name(unsigned int wid_value) +{ + static const char * const names[16] = { + [AC_WID_AUD_OUT] = "Audio Output", + [AC_WID_AUD_IN] = "Audio Input", + [AC_WID_AUD_MIX] = "Audio Mixer", + [AC_WID_AUD_SEL] = "Audio Selector", + [AC_WID_PIN] = "Pin Complex", + [AC_WID_POWER] = "Power Widget", + [AC_WID_VOL_KNB] = "Volume Knob Widget", + [AC_WID_BEEP] = "Beep Generator Widget", + [AC_WID_VENDOR] = "Vendor Defined Widget", + }; + if (wid_value == -1) + return "UNKNOWN Widget"; + wid_value &= 0xf; + if (names[wid_value]) + return names[wid_value]; + else + return "UNKNOWN Widget"; +} + +static void print_nid_array(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid, + struct snd_array *array) +{ + int i; + struct hda_nid_item *items = array->list, *item; + struct snd_kcontrol *kctl; + for (i = 0; i < array->used; i++) { + item = &items[i]; + if (item->nid == nid) { + kctl = item->kctl; + snd_iprintf(buffer, + " Control: name=\"%s\", index=%i, device=%i\n", + kctl->id.name, kctl->id.index + item->index, + kctl->id.device); + if (item->flags & HDA_NID_ITEM_AMP) + snd_iprintf(buffer, + " ControlAmp: chs=%lu, dir=%s, " + "idx=%lu, ofs=%lu\n", + get_amp_channels(kctl), + get_amp_direction(kctl) ? "Out" : "In", + get_amp_index(kctl), + get_amp_offset(kctl)); + } + } +} + +static void print_nid_pcms(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid) +{ + int type; + struct hda_pcm *cpcm; + + list_for_each_entry(cpcm, &codec->pcm_list_head, list) { + for (type = 0; type < 2; type++) { + if (cpcm->stream[type].nid != nid || cpcm->pcm == NULL) + continue; + snd_iprintf(buffer, " Device: name=\"%s\", " + "type=\"%s\", device=%i\n", + cpcm->name, + snd_hda_pcm_type_name[cpcm->pcm_type], + cpcm->pcm->device); + } + } +} + +static void print_amp_caps(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid, int dir) +{ + unsigned int caps; + caps = param_read(codec, nid, dir == HDA_OUTPUT ? + AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP); + if (caps == -1 || caps == 0) { + snd_iprintf(buffer, "N/A\n"); + return; + } + snd_iprintf(buffer, "ofs=0x%02x, nsteps=0x%02x, stepsize=0x%02x, " + "mute=%x\n", + caps & AC_AMPCAP_OFFSET, + (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT, + (caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT, + (caps & AC_AMPCAP_MUTE) >> AC_AMPCAP_MUTE_SHIFT); +} + +/* is this a stereo widget or a stereo-to-mono mix? */ +static bool is_stereo_amps(struct hda_codec *codec, hda_nid_t nid, + int dir, unsigned int wcaps, int indices) +{ + hda_nid_t conn; + + if (wcaps & AC_WCAP_STEREO) + return true; + /* check for a stereo-to-mono mix; it must be: + * only a single connection, only for input, and only a mixer widget + */ + if (indices != 1 || dir != HDA_INPUT || + get_wcaps_type(wcaps) != AC_WID_AUD_MIX) + return false; + + if (snd_hda_get_raw_connections(codec, nid, &conn, 1) < 0) + return false; + /* the connection source is a stereo? */ + wcaps = snd_hda_param_read(codec, conn, AC_PAR_AUDIO_WIDGET_CAP); + return !!(wcaps & AC_WCAP_STEREO); +} + +static void print_amp_vals(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid, + int dir, unsigned int wcaps, int indices) +{ + unsigned int val; + bool stereo; + int i; + + stereo = is_stereo_amps(codec, nid, dir, wcaps, indices); + + dir = dir == HDA_OUTPUT ? AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT; + for (i = 0; i < indices; i++) { + snd_iprintf(buffer, " ["); + val = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_AMP_GAIN_MUTE, + AC_AMP_GET_LEFT | dir | i); + snd_iprintf(buffer, "0x%02x", val); + if (stereo) { + val = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_AMP_GAIN_MUTE, + AC_AMP_GET_RIGHT | dir | i); + snd_iprintf(buffer, " 0x%02x", val); + } + snd_iprintf(buffer, "]"); + } + snd_iprintf(buffer, "\n"); +} + +static void print_pcm_rates(struct snd_info_buffer *buffer, unsigned int pcm) +{ + static const unsigned int rates[] = { + 8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200, + 96000, 176400, 192000, 384000 + }; + int i; + + pcm &= AC_SUPPCM_RATES; + snd_iprintf(buffer, " rates [0x%x]:", pcm); + for (i = 0; i < ARRAY_SIZE(rates); i++) + if (pcm & (1 << i)) + snd_iprintf(buffer, " %d", rates[i]); + snd_iprintf(buffer, "\n"); +} + +static void print_pcm_bits(struct snd_info_buffer *buffer, unsigned int pcm) +{ + char buf[SND_PRINT_BITS_ADVISED_BUFSIZE]; + + snd_iprintf(buffer, " bits [0x%x]:", (pcm >> 16) & 0xff); + snd_print_pcm_bits(pcm, buf, sizeof(buf)); + snd_iprintf(buffer, "%s\n", buf); +} + +static void print_pcm_formats(struct snd_info_buffer *buffer, + unsigned int streams) +{ + snd_iprintf(buffer, " formats [0x%x]:", streams & 0xf); + if (streams & AC_SUPFMT_PCM) + snd_iprintf(buffer, " PCM"); + if (streams & AC_SUPFMT_FLOAT32) + snd_iprintf(buffer, " FLOAT"); + if (streams & AC_SUPFMT_AC3) + snd_iprintf(buffer, " AC3"); + snd_iprintf(buffer, "\n"); +} + +static void print_pcm_caps(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int pcm = param_read(codec, nid, AC_PAR_PCM); + unsigned int stream = param_read(codec, nid, AC_PAR_STREAM); + if (pcm == -1 || stream == -1) { + snd_iprintf(buffer, "N/A\n"); + return; + } + print_pcm_rates(buffer, pcm); + print_pcm_bits(buffer, pcm); + print_pcm_formats(buffer, stream); +} + +static const char *get_jack_connection(u32 cfg) +{ + static const char * const names[16] = { + "Unknown", "1/8", "1/4", "ATAPI", + "RCA", "Optical","Digital", "Analog", + "DIN", "XLR", "RJ11", "Comb", + NULL, NULL, NULL, "Other" + }; + cfg = (cfg & AC_DEFCFG_CONN_TYPE) >> AC_DEFCFG_CONN_TYPE_SHIFT; + if (names[cfg]) + return names[cfg]; + else + return "UNKNOWN"; +} + +static const char *get_jack_color(u32 cfg) +{ + static const char * const names[16] = { + "Unknown", "Black", "Grey", "Blue", + "Green", "Red", "Orange", "Yellow", + "Purple", "Pink", NULL, NULL, + NULL, NULL, "White", "Other", + }; + cfg = (cfg & AC_DEFCFG_COLOR) >> AC_DEFCFG_COLOR_SHIFT; + if (names[cfg]) + return names[cfg]; + else + return "UNKNOWN"; +} + +/* + * Parse the pin default config value and returns the string of the + * jack location, e.g. "Rear", "Front", etc. + */ +static const char *get_jack_location(u32 cfg) +{ + static const char * const bases[7] = { + "N/A", "Rear", "Front", "Left", "Right", "Top", "Bottom", + }; + static const unsigned char specials_idx[] = { + 0x07, 0x08, + 0x17, 0x18, 0x19, + 0x37, 0x38 + }; + static const char * const specials[] = { + "Rear Panel", "Drive Bar", + "Riser", "HDMI", "ATAPI", + "Mobile-In", "Mobile-Out" + }; + int i; + + cfg = (cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT; + if ((cfg & 0x0f) < 7) + return bases[cfg & 0x0f]; + for (i = 0; i < ARRAY_SIZE(specials_idx); i++) { + if (cfg == specials_idx[i]) + return specials[i]; + } + return "UNKNOWN"; +} + +/* + * Parse the pin default config value and returns the string of the + * jack connectivity, i.e. external or internal connection. + */ +static const char *get_jack_connectivity(u32 cfg) +{ + static const char * const jack_locations[4] = { + "Ext", "Int", "Sep", "Oth" + }; + + return jack_locations[(cfg >> (AC_DEFCFG_LOCATION_SHIFT + 4)) & 3]; +} + +/* + * Parse the pin default config value and returns the string of the + * jack type, i.e. the purpose of the jack, such as Line-Out or CD. + */ +static const char *get_jack_type(u32 cfg) +{ + static const char * const jack_types[16] = { + "Line Out", "Speaker", "HP Out", "CD", + "SPDIF Out", "Digital Out", "Modem Line", "Modem Hand", + "Line In", "Aux", "Mic", "Telephony", + "SPDIF In", "Digital In", "Reserved", "Other" + }; + + return jack_types[(cfg & AC_DEFCFG_DEVICE) + >> AC_DEFCFG_DEVICE_SHIFT]; +} + +static void print_pin_caps(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid, + int *supports_vref) +{ + static const char * const jack_conns[4] = { + "Jack", "N/A", "Fixed", "Both" + }; + unsigned int caps, val; + + caps = param_read(codec, nid, AC_PAR_PIN_CAP); + snd_iprintf(buffer, " Pincap 0x%08x:", caps); + if (caps & AC_PINCAP_IN) + snd_iprintf(buffer, " IN"); + if (caps & AC_PINCAP_OUT) + snd_iprintf(buffer, " OUT"); + if (caps & AC_PINCAP_HP_DRV) + snd_iprintf(buffer, " HP"); + if (caps & AC_PINCAP_EAPD) + snd_iprintf(buffer, " EAPD"); + if (caps & AC_PINCAP_PRES_DETECT) + snd_iprintf(buffer, " Detect"); + if (caps & AC_PINCAP_BALANCE) + snd_iprintf(buffer, " Balanced"); + if (caps & AC_PINCAP_HDMI) { + /* Realtek uses this bit as a different meaning */ + if ((codec->core.vendor_id >> 16) == 0x10ec) + snd_iprintf(buffer, " R/L"); + else { + if (caps & AC_PINCAP_HBR) + snd_iprintf(buffer, " HBR"); + snd_iprintf(buffer, " HDMI"); + } + } + if (caps & AC_PINCAP_DP) + snd_iprintf(buffer, " DP"); + if (caps & AC_PINCAP_TRIG_REQ) + snd_iprintf(buffer, " Trigger"); + if (caps & AC_PINCAP_IMP_SENSE) + snd_iprintf(buffer, " ImpSense"); + snd_iprintf(buffer, "\n"); + if (caps & AC_PINCAP_VREF) { + unsigned int vref = + (caps & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT; + snd_iprintf(buffer, " Vref caps:"); + if (vref & AC_PINCAP_VREF_HIZ) + snd_iprintf(buffer, " HIZ"); + if (vref & AC_PINCAP_VREF_50) + snd_iprintf(buffer, " 50"); + if (vref & AC_PINCAP_VREF_GRD) + snd_iprintf(buffer, " GRD"); + if (vref & AC_PINCAP_VREF_80) + snd_iprintf(buffer, " 80"); + if (vref & AC_PINCAP_VREF_100) + snd_iprintf(buffer, " 100"); + snd_iprintf(buffer, "\n"); + *supports_vref = 1; + } else + *supports_vref = 0; + if (caps & AC_PINCAP_EAPD) { + val = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_EAPD_BTLENABLE, 0); + snd_iprintf(buffer, " EAPD 0x%x:", val); + if (val & AC_EAPDBTL_BALANCED) + snd_iprintf(buffer, " BALANCED"); + if (val & AC_EAPDBTL_EAPD) + snd_iprintf(buffer, " EAPD"); + if (val & AC_EAPDBTL_LR_SWAP) + snd_iprintf(buffer, " R/L"); + snd_iprintf(buffer, "\n"); + } + caps = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONFIG_DEFAULT, 0); + snd_iprintf(buffer, " Pin Default 0x%08x: [%s] %s at %s %s\n", caps, + jack_conns[(caps & AC_DEFCFG_PORT_CONN) >> AC_DEFCFG_PORT_CONN_SHIFT], + get_jack_type(caps), + get_jack_connectivity(caps), + get_jack_location(caps)); + snd_iprintf(buffer, " Conn = %s, Color = %s\n", + get_jack_connection(caps), + get_jack_color(caps)); + /* Default association and sequence values refer to default grouping + * of pin complexes and their sequence within the group. This is used + * for priority and resource allocation. + */ + snd_iprintf(buffer, " DefAssociation = 0x%x, Sequence = 0x%x\n", + (caps & AC_DEFCFG_DEF_ASSOC) >> AC_DEFCFG_ASSOC_SHIFT, + caps & AC_DEFCFG_SEQUENCE); + if (((caps & AC_DEFCFG_MISC) >> AC_DEFCFG_MISC_SHIFT) & + AC_DEFCFG_MISC_NO_PRESENCE) { + /* Miscellaneous bit indicates external hardware does not + * support presence detection even if the pin complex + * indicates it is supported. + */ + snd_iprintf(buffer, " Misc = NO_PRESENCE\n"); + } +} + +static void print_pin_ctls(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid, + int supports_vref) +{ + unsigned int pinctls; + + pinctls = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_iprintf(buffer, " Pin-ctls: 0x%02x:", pinctls); + if (pinctls & AC_PINCTL_IN_EN) + snd_iprintf(buffer, " IN"); + if (pinctls & AC_PINCTL_OUT_EN) + snd_iprintf(buffer, " OUT"); + if (pinctls & AC_PINCTL_HP_EN) + snd_iprintf(buffer, " HP"); + if (supports_vref) { + int vref = pinctls & AC_PINCTL_VREFEN; + switch (vref) { + case AC_PINCTL_VREF_HIZ: + snd_iprintf(buffer, " VREF_HIZ"); + break; + case AC_PINCTL_VREF_50: + snd_iprintf(buffer, " VREF_50"); + break; + case AC_PINCTL_VREF_GRD: + snd_iprintf(buffer, " VREF_GRD"); + break; + case AC_PINCTL_VREF_80: + snd_iprintf(buffer, " VREF_80"); + break; + case AC_PINCTL_VREF_100: + snd_iprintf(buffer, " VREF_100"); + break; + } + } + snd_iprintf(buffer, "\n"); +} + +static void print_vol_knob(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int cap = param_read(codec, nid, AC_PAR_VOL_KNB_CAP); + snd_iprintf(buffer, " Volume-Knob: delta=%d, steps=%d, ", + (cap >> 7) & 1, cap & 0x7f); + cap = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_VOLUME_KNOB_CONTROL, 0); + snd_iprintf(buffer, "direct=%d, val=%d\n", + (cap >> 7) & 1, cap & 0x7f); +} + +static void print_audio_io(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid, + unsigned int wid_type) +{ + int conv = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONV, 0); + snd_iprintf(buffer, + " Converter: stream=%d, channel=%d\n", + (conv & AC_CONV_STREAM) >> AC_CONV_STREAM_SHIFT, + conv & AC_CONV_CHANNEL); + + if (wid_type == AC_WID_AUD_IN && (conv & AC_CONV_CHANNEL) == 0) { + int sdi = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_SDI_SELECT, 0); + snd_iprintf(buffer, " SDI-Select: %d\n", + sdi & AC_SDI_SELECT); + } +} + +static void print_digital_conv(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int digi1 = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_DIGI_CONVERT_1, 0); + unsigned char digi2 = digi1 >> 8; + unsigned char digi3 = digi1 >> 16; + + snd_iprintf(buffer, " Digital:"); + if (digi1 & AC_DIG1_ENABLE) + snd_iprintf(buffer, " Enabled"); + if (digi1 & AC_DIG1_V) + snd_iprintf(buffer, " Validity"); + if (digi1 & AC_DIG1_VCFG) + snd_iprintf(buffer, " ValidityCfg"); + if (digi1 & AC_DIG1_EMPHASIS) + snd_iprintf(buffer, " Preemphasis"); + if (digi1 & AC_DIG1_COPYRIGHT) + snd_iprintf(buffer, " Non-Copyright"); + if (digi1 & AC_DIG1_NONAUDIO) + snd_iprintf(buffer, " Non-Audio"); + if (digi1 & AC_DIG1_PROFESSIONAL) + snd_iprintf(buffer, " Pro"); + if (digi1 & AC_DIG1_LEVEL) + snd_iprintf(buffer, " GenLevel"); + if (digi3 & AC_DIG3_KAE) + snd_iprintf(buffer, " KAE"); + snd_iprintf(buffer, "\n"); + snd_iprintf(buffer, " Digital category: 0x%x\n", + digi2 & AC_DIG2_CC); + snd_iprintf(buffer, " IEC Coding Type: 0x%x\n", + digi3 & AC_DIG3_ICT); +} + +static const char *get_pwr_state(u32 state) +{ + static const char * const buf[] = { + "D0", "D1", "D2", "D3", "D3cold" + }; + if (state < ARRAY_SIZE(buf)) + return buf[state]; + return "UNKNOWN"; +} + +static void print_power_state(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid) +{ + static const char * const names[] = { + [ilog2(AC_PWRST_D0SUP)] = "D0", + [ilog2(AC_PWRST_D1SUP)] = "D1", + [ilog2(AC_PWRST_D2SUP)] = "D2", + [ilog2(AC_PWRST_D3SUP)] = "D3", + [ilog2(AC_PWRST_D3COLDSUP)] = "D3cold", + [ilog2(AC_PWRST_S3D3COLDSUP)] = "S3D3cold", + [ilog2(AC_PWRST_CLKSTOP)] = "CLKSTOP", + [ilog2(AC_PWRST_EPSS)] = "EPSS", + }; + + int sup = param_read(codec, nid, AC_PAR_POWER_STATE); + int pwr = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_POWER_STATE, 0); + if (sup != -1) { + int i; + + snd_iprintf(buffer, " Power states: "); + for (i = 0; i < ARRAY_SIZE(names); i++) { + if (sup & (1U << i)) + snd_iprintf(buffer, " %s", names[i]); + } + snd_iprintf(buffer, "\n"); + } + + snd_iprintf(buffer, " Power: setting=%s, actual=%s", + get_pwr_state(pwr & AC_PWRST_SETTING), + get_pwr_state((pwr & AC_PWRST_ACTUAL) >> + AC_PWRST_ACTUAL_SHIFT)); + if (pwr & AC_PWRST_ERROR) + snd_iprintf(buffer, ", Error"); + if (pwr & AC_PWRST_CLK_STOP_OK) + snd_iprintf(buffer, ", Clock-stop-OK"); + if (pwr & AC_PWRST_SETTING_RESET) + snd_iprintf(buffer, ", Setting-reset"); + snd_iprintf(buffer, "\n"); +} + +static void print_unsol_cap(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid) +{ + int unsol = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_UNSOLICITED_RESPONSE, 0); + snd_iprintf(buffer, + " Unsolicited: tag=%02x, enabled=%d\n", + unsol & AC_UNSOL_TAG, + (unsol & AC_UNSOL_ENABLED) ? 1 : 0); +} + +static inline bool can_dump_coef(struct hda_codec *codec) +{ + switch (dump_coef) { + case 0: return false; + case 1: return true; + default: return codec->dump_coef; + } +} + +static void print_proc_caps(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int i, ncoeff, oldindex; + unsigned int proc_caps = param_read(codec, nid, AC_PAR_PROC_CAP); + ncoeff = (proc_caps & AC_PCAP_NUM_COEF) >> AC_PCAP_NUM_COEF_SHIFT; + snd_iprintf(buffer, " Processing caps: benign=%d, ncoeff=%d\n", + proc_caps & AC_PCAP_BENIGN, ncoeff); + + if (!can_dump_coef(codec)) + return; + + /* Note: This is racy - another process could run in parallel and change + the coef index too. */ + oldindex = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_COEF_INDEX, 0); + for (i = 0; i < ncoeff; i++) { + unsigned int val; + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_COEF_INDEX, i); + val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_PROC_COEF, + 0); + snd_iprintf(buffer, " Coeff 0x%02x: 0x%04x\n", i, val); + } + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_COEF_INDEX, oldindex); +} + +static void print_conn_list(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid, + unsigned int wid_type, hda_nid_t *conn, + int conn_len) +{ + int c, curr = -1; + const hda_nid_t *list; + int cache_len; + + if (conn_len > 1 && + wid_type != AC_WID_AUD_MIX && + wid_type != AC_WID_VOL_KNB && + wid_type != AC_WID_POWER) + curr = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_CONNECT_SEL, 0); + snd_iprintf(buffer, " Connection: %d\n", conn_len); + if (conn_len > 0) { + snd_iprintf(buffer, " "); + for (c = 0; c < conn_len; c++) { + snd_iprintf(buffer, " 0x%02x", conn[c]); + if (c == curr) + snd_iprintf(buffer, "*"); + } + snd_iprintf(buffer, "\n"); + } + + /* Get Cache connections info */ + cache_len = snd_hda_get_conn_list(codec, nid, &list); + if (cache_len >= 0 && (cache_len != conn_len || + memcmp(list, conn, conn_len) != 0)) { + snd_iprintf(buffer, " In-driver Connection: %d\n", cache_len); + if (cache_len > 0) { + snd_iprintf(buffer, " "); + for (c = 0; c < cache_len; c++) + snd_iprintf(buffer, " 0x%02x", list[c]); + snd_iprintf(buffer, "\n"); + } + } +} + +static void print_gpio(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int gpio = + param_read(codec, codec->core.afg, AC_PAR_GPIO_CAP); + unsigned int enable, direction, wake, unsol, sticky, data; + int i, max; + snd_iprintf(buffer, "GPIO: io=%d, o=%d, i=%d, " + "unsolicited=%d, wake=%d\n", + gpio & AC_GPIO_IO_COUNT, + (gpio & AC_GPIO_O_COUNT) >> AC_GPIO_O_COUNT_SHIFT, + (gpio & AC_GPIO_I_COUNT) >> AC_GPIO_I_COUNT_SHIFT, + (gpio & AC_GPIO_UNSOLICITED) ? 1 : 0, + (gpio & AC_GPIO_WAKE) ? 1 : 0); + max = gpio & AC_GPIO_IO_COUNT; + if (!max || max > 8) + return; + enable = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_GPIO_MASK, 0); + direction = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_GPIO_DIRECTION, 0); + wake = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_GPIO_WAKE_MASK, 0); + unsol = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_GPIO_UNSOLICITED_RSP_MASK, 0); + sticky = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_GPIO_STICKY_MASK, 0); + data = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_GPIO_DATA, 0); + for (i = 0; i < max; ++i) + snd_iprintf(buffer, + " IO[%d]: enable=%d, dir=%d, wake=%d, " + "sticky=%d, data=%d, unsol=%d\n", i, + (enable & (1<<i)) ? 1 : 0, + (direction & (1<<i)) ? 1 : 0, + (wake & (1<<i)) ? 1 : 0, + (sticky & (1<<i)) ? 1 : 0, + (data & (1<<i)) ? 1 : 0, + (unsol & (1<<i)) ? 1 : 0); + /* FIXME: add GPO and GPI pin information */ + print_nid_array(buffer, codec, nid, &codec->mixers); + print_nid_array(buffer, codec, nid, &codec->nids); +} + +static void print_dpmst_connections(struct snd_info_buffer *buffer, struct hda_codec *codec, + hda_nid_t nid, int dev_num) +{ + int c, conn_len, curr, dev_id_saved; + hda_nid_t *conn; + + conn_len = snd_hda_get_num_raw_conns(codec, nid); + if (conn_len <= 0) + return; + + conn = kmalloc_array(conn_len, sizeof(hda_nid_t), GFP_KERNEL); + if (!conn) + return; + + dev_id_saved = snd_hda_get_dev_select(codec, nid); + + snd_hda_set_dev_select(codec, nid, dev_num); + curr = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONNECT_SEL, 0); + if (snd_hda_get_raw_connections(codec, nid, conn, conn_len) < 0) + goto out; + + for (c = 0; c < conn_len; c++) { + snd_iprintf(buffer, " 0x%02x", conn[c]); + if (c == curr) + snd_iprintf(buffer, "*"); + } + +out: + kfree(conn); + snd_hda_set_dev_select(codec, nid, dev_id_saved); +} + +static void print_device_list(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid) +{ + int i, curr = -1; + u8 dev_list[AC_MAX_DEV_LIST_LEN]; + int devlist_len; + + devlist_len = snd_hda_get_devices(codec, nid, dev_list, + AC_MAX_DEV_LIST_LEN); + snd_iprintf(buffer, " Devices: %d\n", devlist_len); + if (devlist_len <= 0) + return; + + curr = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_DEVICE_SEL, 0); + + for (i = 0; i < devlist_len; i++) { + if (i == curr) + snd_iprintf(buffer, " *"); + else + snd_iprintf(buffer, " "); + + snd_iprintf(buffer, + "Dev %02d: PD = %d, ELDV = %d, IA = %d, Connections [", i, + !!(dev_list[i] & AC_DE_PD), + !!(dev_list[i] & AC_DE_ELDV), + !!(dev_list[i] & AC_DE_IA)); + + print_dpmst_connections(buffer, codec, nid, i); + + snd_iprintf(buffer, " ]\n"); + } +} + +static void print_codec_core_info(struct hdac_device *codec, + struct snd_info_buffer *buffer) +{ + snd_iprintf(buffer, "Codec: "); + if (codec->vendor_name && codec->chip_name) + snd_iprintf(buffer, "%s %s\n", + codec->vendor_name, codec->chip_name); + else + snd_iprintf(buffer, "Not Set\n"); + snd_iprintf(buffer, "Address: %d\n", codec->addr); + if (codec->afg) + snd_iprintf(buffer, "AFG Function Id: 0x%x (unsol %u)\n", + codec->afg_function_id, codec->afg_unsol); + if (codec->mfg) + snd_iprintf(buffer, "MFG Function Id: 0x%x (unsol %u)\n", + codec->mfg_function_id, codec->mfg_unsol); + snd_iprintf(buffer, "Vendor Id: 0x%08x\n", codec->vendor_id); + snd_iprintf(buffer, "Subsystem Id: 0x%08x\n", codec->subsystem_id); + snd_iprintf(buffer, "Revision Id: 0x%x\n", codec->revision_id); + + if (codec->mfg) + snd_iprintf(buffer, "Modem Function Group: 0x%x\n", codec->mfg); + else + snd_iprintf(buffer, "No Modem Function Group found\n"); +} + +static void print_codec_info(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct hda_codec *codec = entry->private_data; + hda_nid_t nid, fg; + int i, nodes; + + print_codec_core_info(&codec->core, buffer); + fg = codec->core.afg; + if (!fg) + return; + snd_hda_power_up(codec); + snd_iprintf(buffer, "Default PCM:\n"); + print_pcm_caps(buffer, codec, fg); + snd_iprintf(buffer, "Default Amp-In caps: "); + print_amp_caps(buffer, codec, fg, HDA_INPUT); + snd_iprintf(buffer, "Default Amp-Out caps: "); + print_amp_caps(buffer, codec, fg, HDA_OUTPUT); + snd_iprintf(buffer, "State of AFG node 0x%02x:\n", fg); + print_power_state(buffer, codec, fg); + + nodes = snd_hda_get_sub_nodes(codec, fg, &nid); + if (! nid || nodes < 0) { + snd_iprintf(buffer, "Invalid AFG subtree\n"); + snd_hda_power_down(codec); + return; + } + + print_gpio(buffer, codec, fg); + if (codec->proc_widget_hook) + codec->proc_widget_hook(buffer, codec, fg); + + for (i = 0; i < nodes; i++, nid++) { + unsigned int wid_caps = + param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP); + unsigned int wid_type = get_wcaps_type(wid_caps); + hda_nid_t *conn = NULL; + int conn_len = 0; + + snd_iprintf(buffer, "Node 0x%02x [%s] wcaps 0x%x:", nid, + get_wid_type_name(wid_type), wid_caps); + if (wid_caps & AC_WCAP_STEREO) { + unsigned int chans = get_wcaps_channels(wid_caps); + if (chans == 2) + snd_iprintf(buffer, " Stereo"); + else + snd_iprintf(buffer, " %d-Channels", chans); + } else + snd_iprintf(buffer, " Mono"); + if (wid_caps & AC_WCAP_DIGITAL) + snd_iprintf(buffer, " Digital"); + if (wid_caps & AC_WCAP_IN_AMP) + snd_iprintf(buffer, " Amp-In"); + if (wid_caps & AC_WCAP_OUT_AMP) + snd_iprintf(buffer, " Amp-Out"); + if (wid_caps & AC_WCAP_STRIPE) + snd_iprintf(buffer, " Stripe"); + if (wid_caps & AC_WCAP_LR_SWAP) + snd_iprintf(buffer, " R/L"); + if (wid_caps & AC_WCAP_CP_CAPS) + snd_iprintf(buffer, " CP"); + snd_iprintf(buffer, "\n"); + + print_nid_array(buffer, codec, nid, &codec->mixers); + print_nid_array(buffer, codec, nid, &codec->nids); + print_nid_pcms(buffer, codec, nid); + + /* volume knob is a special widget that always have connection + * list + */ + if (wid_type == AC_WID_VOL_KNB) + wid_caps |= AC_WCAP_CONN_LIST; + + if (wid_caps & AC_WCAP_CONN_LIST) { + conn_len = snd_hda_get_num_raw_conns(codec, nid); + if (conn_len > 0) { + conn = kmalloc_array(conn_len, + sizeof(hda_nid_t), + GFP_KERNEL); + if (!conn) + return; + if (snd_hda_get_raw_connections(codec, nid, conn, + conn_len) < 0) + conn_len = 0; + } + } + + if (wid_caps & AC_WCAP_IN_AMP) { + snd_iprintf(buffer, " Amp-In caps: "); + print_amp_caps(buffer, codec, nid, HDA_INPUT); + snd_iprintf(buffer, " Amp-In vals: "); + if (wid_type == AC_WID_PIN || + (codec->single_adc_amp && + wid_type == AC_WID_AUD_IN)) + print_amp_vals(buffer, codec, nid, HDA_INPUT, + wid_caps, 1); + else + print_amp_vals(buffer, codec, nid, HDA_INPUT, + wid_caps, conn_len); + } + if (wid_caps & AC_WCAP_OUT_AMP) { + snd_iprintf(buffer, " Amp-Out caps: "); + print_amp_caps(buffer, codec, nid, HDA_OUTPUT); + snd_iprintf(buffer, " Amp-Out vals: "); + if (wid_type == AC_WID_PIN && + codec->pin_amp_workaround) + print_amp_vals(buffer, codec, nid, HDA_OUTPUT, + wid_caps, conn_len); + else + print_amp_vals(buffer, codec, nid, HDA_OUTPUT, + wid_caps, 1); + } + + switch (wid_type) { + case AC_WID_PIN: { + int supports_vref; + print_pin_caps(buffer, codec, nid, &supports_vref); + print_pin_ctls(buffer, codec, nid, supports_vref); + break; + } + case AC_WID_VOL_KNB: + print_vol_knob(buffer, codec, nid); + break; + case AC_WID_AUD_OUT: + case AC_WID_AUD_IN: + print_audio_io(buffer, codec, nid, wid_type); + if (wid_caps & AC_WCAP_DIGITAL) + print_digital_conv(buffer, codec, nid); + if (wid_caps & AC_WCAP_FORMAT_OVRD) { + snd_iprintf(buffer, " PCM:\n"); + print_pcm_caps(buffer, codec, nid); + } + break; + } + + if (wid_caps & AC_WCAP_UNSOL_CAP) + print_unsol_cap(buffer, codec, nid); + + if (wid_caps & AC_WCAP_POWER) + print_power_state(buffer, codec, nid); + + if (wid_caps & AC_WCAP_DELAY) + snd_iprintf(buffer, " Delay: %d samples\n", + (wid_caps & AC_WCAP_DELAY) >> + AC_WCAP_DELAY_SHIFT); + + if (wid_type == AC_WID_PIN && codec->dp_mst) + print_device_list(buffer, codec, nid); + + if (wid_caps & AC_WCAP_CONN_LIST) + print_conn_list(buffer, codec, nid, wid_type, + conn, conn_len); + + if (wid_caps & AC_WCAP_PROC_WID) + print_proc_caps(buffer, codec, nid); + + if (codec->proc_widget_hook) + codec->proc_widget_hook(buffer, codec, nid); + + kfree(conn); + } + snd_hda_power_down(codec); +} + +/* + * create a proc read + */ +int snd_hda_codec_proc_new(struct hda_codec *codec) +{ + char name[32]; + + snprintf(name, sizeof(name), "codec#%d", codec->core.addr); + return snd_card_ro_proc_new(codec->card, name, codec, print_codec_info); +} + diff --git a/sound/pci/hda/hda_sysfs.c b/sound/pci/hda/hda_sysfs.c new file mode 100644 index 000000000..69ebc37a4 --- /dev/null +++ b/sound/pci/hda/hda_sysfs.c @@ -0,0 +1,796 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * sysfs interface for HD-audio codec + * + * Copyright (c) 2014 Takashi Iwai <tiwai@suse.de> + * + * split from hda_hwdep.c + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/compat.h> +#include <linux/mutex.h> +#include <linux/ctype.h> +#include <linux/string.h> +#include <linux/export.h> +#include <sound/core.h> +#include <sound/hda_codec.h> +#include "hda_local.h" +#include <sound/hda_hwdep.h> +#include <sound/minors.h> + +/* hint string pair */ +struct hda_hint { + const char *key; + const char *val; /* contained in the same alloc as key */ +}; + +#ifdef CONFIG_PM +static ssize_t power_on_acct_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hda_codec *codec = dev_get_drvdata(dev); + snd_hda_update_power_acct(codec); + return sysfs_emit(buf, "%u\n", jiffies_to_msecs(codec->power_on_acct)); +} + +static ssize_t power_off_acct_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hda_codec *codec = dev_get_drvdata(dev); + snd_hda_update_power_acct(codec); + return sysfs_emit(buf, "%u\n", jiffies_to_msecs(codec->power_off_acct)); +} + +static DEVICE_ATTR_RO(power_on_acct); +static DEVICE_ATTR_RO(power_off_acct); +#endif /* CONFIG_PM */ + +#define CODEC_INFO_SHOW(type, field) \ +static ssize_t type##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct hda_codec *codec = dev_get_drvdata(dev); \ + return sysfs_emit(buf, "0x%x\n", codec->field); \ +} + +#define CODEC_INFO_STR_SHOW(type, field) \ +static ssize_t type##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct hda_codec *codec = dev_get_drvdata(dev); \ + return sysfs_emit(buf, "%s\n", \ + codec->field ? codec->field : ""); \ +} + +CODEC_INFO_SHOW(vendor_id, core.vendor_id); +CODEC_INFO_SHOW(subsystem_id, core.subsystem_id); +CODEC_INFO_SHOW(revision_id, core.revision_id); +CODEC_INFO_SHOW(afg, core.afg); +CODEC_INFO_SHOW(mfg, core.mfg); +CODEC_INFO_STR_SHOW(vendor_name, core.vendor_name); +CODEC_INFO_STR_SHOW(chip_name, core.chip_name); +CODEC_INFO_STR_SHOW(modelname, modelname); + +static ssize_t pin_configs_show(struct hda_codec *codec, + struct snd_array *list, + char *buf) +{ + const struct hda_pincfg *pin; + int i, len = 0; + mutex_lock(&codec->user_mutex); + snd_array_for_each(list, i, pin) { + len += sysfs_emit_at(buf, len, "0x%02x 0x%08x\n", + pin->nid, pin->cfg); + } + mutex_unlock(&codec->user_mutex); + return len; +} + +static ssize_t init_pin_configs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hda_codec *codec = dev_get_drvdata(dev); + return pin_configs_show(codec, &codec->init_pins, buf); +} + +static ssize_t driver_pin_configs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hda_codec *codec = dev_get_drvdata(dev); + return pin_configs_show(codec, &codec->driver_pins, buf); +} + +#ifdef CONFIG_SND_HDA_RECONFIG + +/* + * sysfs interface + */ + +static int clear_codec(struct hda_codec *codec) +{ + int err; + + err = snd_hda_codec_reset(codec); + if (err < 0) { + codec_err(codec, "The codec is being used, can't free.\n"); + return err; + } + snd_hda_sysfs_clear(codec); + return 0; +} + +static int reconfig_codec(struct hda_codec *codec) +{ + int err; + + snd_hda_power_up(codec); + codec_info(codec, "hda-codec: reconfiguring\n"); + err = snd_hda_codec_reset(codec); + if (err < 0) { + codec_err(codec, + "The codec is being used, can't reconfigure.\n"); + goto error; + } + err = device_reprobe(hda_codec_dev(codec)); + if (err < 0) + goto error; + err = snd_card_register(codec->card); + error: + snd_hda_power_down(codec); + return err; +} + +/* + * allocate a string at most len chars, and remove the trailing EOL + */ +static char *kstrndup_noeol(const char *src, size_t len) +{ + char *s = kstrndup(src, len, GFP_KERNEL); + char *p; + if (!s) + return NULL; + p = strchr(s, '\n'); + if (p) + *p = 0; + return s; +} + +#define CODEC_INFO_STORE(type, field) \ +static ssize_t type##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct hda_codec *codec = dev_get_drvdata(dev); \ + unsigned long val; \ + int err = kstrtoul(buf, 0, &val); \ + if (err < 0) \ + return err; \ + codec->field = val; \ + return count; \ +} + +#define CODEC_INFO_STR_STORE(type, field) \ +static ssize_t type##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct hda_codec *codec = dev_get_drvdata(dev); \ + char *s = kstrndup_noeol(buf, 64); \ + if (!s) \ + return -ENOMEM; \ + kfree(codec->field); \ + codec->field = s; \ + return count; \ +} + +CODEC_INFO_STORE(vendor_id, core.vendor_id); +CODEC_INFO_STORE(subsystem_id, core.subsystem_id); +CODEC_INFO_STORE(revision_id, core.revision_id); +CODEC_INFO_STR_STORE(vendor_name, core.vendor_name); +CODEC_INFO_STR_STORE(chip_name, core.chip_name); +CODEC_INFO_STR_STORE(modelname, modelname); + +#define CODEC_ACTION_STORE(type) \ +static ssize_t type##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct hda_codec *codec = dev_get_drvdata(dev); \ + int err = 0; \ + if (*buf) \ + err = type##_codec(codec); \ + return err < 0 ? err : count; \ +} + +CODEC_ACTION_STORE(reconfig); +CODEC_ACTION_STORE(clear); + +static ssize_t init_verbs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hda_codec *codec = dev_get_drvdata(dev); + const struct hda_verb *v; + int i, len = 0; + mutex_lock(&codec->user_mutex); + snd_array_for_each(&codec->init_verbs, i, v) { + len += sysfs_emit_at(buf, len, "0x%02x 0x%03x 0x%04x\n", + v->nid, v->verb, v->param); + } + mutex_unlock(&codec->user_mutex); + return len; +} + +static int parse_init_verbs(struct hda_codec *codec, const char *buf) +{ + struct hda_verb *v; + int nid, verb, param; + + if (sscanf(buf, "%i %i %i", &nid, &verb, ¶m) != 3) + return -EINVAL; + if (!nid || !verb) + return -EINVAL; + mutex_lock(&codec->user_mutex); + v = snd_array_new(&codec->init_verbs); + if (!v) { + mutex_unlock(&codec->user_mutex); + return -ENOMEM; + } + v->nid = nid; + v->verb = verb; + v->param = param; + mutex_unlock(&codec->user_mutex); + return 0; +} + +static ssize_t init_verbs_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hda_codec *codec = dev_get_drvdata(dev); + int err = parse_init_verbs(codec, buf); + if (err < 0) + return err; + return count; +} + +static ssize_t hints_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hda_codec *codec = dev_get_drvdata(dev); + const struct hda_hint *hint; + int i, len = 0; + mutex_lock(&codec->user_mutex); + snd_array_for_each(&codec->hints, i, hint) { + len += sysfs_emit_at(buf, len, "%s = %s\n", + hint->key, hint->val); + } + mutex_unlock(&codec->user_mutex); + return len; +} + +static struct hda_hint *get_hint(struct hda_codec *codec, const char *key) +{ + struct hda_hint *hint; + int i; + + snd_array_for_each(&codec->hints, i, hint) { + if (!strcmp(hint->key, key)) + return hint; + } + return NULL; +} + +static void remove_trail_spaces(char *str) +{ + char *p; + if (!*str) + return; + p = str + strlen(str) - 1; + for (; isspace(*p); p--) { + *p = 0; + if (p == str) + return; + } +} + +#define MAX_HINTS 1024 + +static int parse_hints(struct hda_codec *codec, const char *buf) +{ + char *key, *val; + struct hda_hint *hint; + int err = 0; + + buf = skip_spaces(buf); + if (!*buf || *buf == '#' || *buf == '\n') + return 0; + if (*buf == '=') + return -EINVAL; + key = kstrndup_noeol(buf, 1024); + if (!key) + return -ENOMEM; + /* extract key and val */ + val = strchr(key, '='); + if (!val) { + kfree(key); + return -EINVAL; + } + *val++ = 0; + val = skip_spaces(val); + remove_trail_spaces(key); + remove_trail_spaces(val); + mutex_lock(&codec->user_mutex); + hint = get_hint(codec, key); + if (hint) { + /* replace */ + kfree(hint->key); + hint->key = key; + hint->val = val; + goto unlock; + } + /* allocate a new hint entry */ + if (codec->hints.used >= MAX_HINTS) + hint = NULL; + else + hint = snd_array_new(&codec->hints); + if (hint) { + hint->key = key; + hint->val = val; + } else { + err = -ENOMEM; + } + unlock: + mutex_unlock(&codec->user_mutex); + if (err) + kfree(key); + return err; +} + +static ssize_t hints_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hda_codec *codec = dev_get_drvdata(dev); + int err = parse_hints(codec, buf); + if (err < 0) + return err; + return count; +} + +static ssize_t user_pin_configs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hda_codec *codec = dev_get_drvdata(dev); + return pin_configs_show(codec, &codec->user_pins, buf); +} + +static int parse_user_pin_configs(struct hda_codec *codec, const char *buf) +{ + int nid, cfg, err; + + if (sscanf(buf, "%i %i", &nid, &cfg) != 2) + return -EINVAL; + if (!nid) + return -EINVAL; + mutex_lock(&codec->user_mutex); + err = snd_hda_add_pincfg(codec, &codec->user_pins, nid, cfg); + mutex_unlock(&codec->user_mutex); + return err; +} + +static ssize_t user_pin_configs_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hda_codec *codec = dev_get_drvdata(dev); + int err = parse_user_pin_configs(codec, buf); + if (err < 0) + return err; + return count; +} + +/* sysfs attributes exposed only when CONFIG_SND_HDA_RECONFIG=y */ +static DEVICE_ATTR_RW(init_verbs); +static DEVICE_ATTR_RW(hints); +static DEVICE_ATTR_RW(user_pin_configs); +static DEVICE_ATTR_WO(reconfig); +static DEVICE_ATTR_WO(clear); + +/** + * snd_hda_get_hint - Look for hint string + * @codec: the HDA codec + * @key: the hint key string + * + * Look for a hint key/value pair matching with the given key string + * and returns the value string. If nothing found, returns NULL. + */ +const char *snd_hda_get_hint(struct hda_codec *codec, const char *key) +{ + struct hda_hint *hint = get_hint(codec, key); + return hint ? hint->val : NULL; +} +EXPORT_SYMBOL_GPL(snd_hda_get_hint); + +/** + * snd_hda_get_bool_hint - Get a boolean hint value + * @codec: the HDA codec + * @key: the hint key string + * + * Look for a hint key/value pair matching with the given key string + * and returns a boolean value parsed from the value. If no matching + * key is found, return a negative value. + */ +int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key) +{ + const char *p; + int ret; + + mutex_lock(&codec->user_mutex); + p = snd_hda_get_hint(codec, key); + if (!p || !*p) + ret = -ENOENT; + else { + switch (toupper(*p)) { + case 'T': /* true */ + case 'Y': /* yes */ + case '1': + ret = 1; + break; + default: + ret = 0; + break; + } + } + mutex_unlock(&codec->user_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(snd_hda_get_bool_hint); + +/** + * snd_hda_get_int_hint - Get an integer hint value + * @codec: the HDA codec + * @key: the hint key string + * @valp: pointer to store a value + * + * Look for a hint key/value pair matching with the given key string + * and stores the integer value to @valp. If no matching key is found, + * return a negative error code. Otherwise it returns zero. + */ +int snd_hda_get_int_hint(struct hda_codec *codec, const char *key, int *valp) +{ + const char *p; + unsigned long val; + int ret; + + mutex_lock(&codec->user_mutex); + p = snd_hda_get_hint(codec, key); + if (!p) + ret = -ENOENT; + else if (kstrtoul(p, 0, &val)) + ret = -EINVAL; + else { + *valp = val; + ret = 0; + } + mutex_unlock(&codec->user_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(snd_hda_get_int_hint); +#endif /* CONFIG_SND_HDA_RECONFIG */ + +/* + * common sysfs attributes + */ +#ifdef CONFIG_SND_HDA_RECONFIG +#define RECONFIG_DEVICE_ATTR(name) DEVICE_ATTR_RW(name) +#else +#define RECONFIG_DEVICE_ATTR(name) DEVICE_ATTR_RO(name) +#endif +static RECONFIG_DEVICE_ATTR(vendor_id); +static RECONFIG_DEVICE_ATTR(subsystem_id); +static RECONFIG_DEVICE_ATTR(revision_id); +static DEVICE_ATTR_RO(afg); +static DEVICE_ATTR_RO(mfg); +static RECONFIG_DEVICE_ATTR(vendor_name); +static RECONFIG_DEVICE_ATTR(chip_name); +static RECONFIG_DEVICE_ATTR(modelname); +static DEVICE_ATTR_RO(init_pin_configs); +static DEVICE_ATTR_RO(driver_pin_configs); + + +#ifdef CONFIG_SND_HDA_PATCH_LOADER + +/* parser mode */ +enum { + LINE_MODE_NONE, + LINE_MODE_CODEC, + LINE_MODE_MODEL, + LINE_MODE_PINCFG, + LINE_MODE_VERB, + LINE_MODE_HINT, + LINE_MODE_VENDOR_ID, + LINE_MODE_SUBSYSTEM_ID, + LINE_MODE_REVISION_ID, + LINE_MODE_CHIP_NAME, + NUM_LINE_MODES, +}; + +static inline int strmatch(const char *a, const char *b) +{ + return strncasecmp(a, b, strlen(b)) == 0; +} + +/* parse the contents after the line "[codec]" + * accept only the line with three numbers, and assign the current codec + */ +static void parse_codec_mode(char *buf, struct hda_bus *bus, + struct hda_codec **codecp) +{ + int vendorid, subid, caddr; + struct hda_codec *codec; + + *codecp = NULL; + if (sscanf(buf, "%i %i %i", &vendorid, &subid, &caddr) == 3) { + list_for_each_codec(codec, bus) { + if ((vendorid <= 0 || codec->core.vendor_id == vendorid) && + (subid <= 0 || codec->core.subsystem_id == subid) && + codec->core.addr == caddr) { + *codecp = codec; + break; + } + } + } +} + +/* parse the contents after the other command tags, [pincfg], [verb], + * [vendor_id], [subsystem_id], [revision_id], [chip_name], [hint] and [model] + * just pass to the sysfs helper (only when any codec was specified) + */ +static void parse_pincfg_mode(char *buf, struct hda_bus *bus, + struct hda_codec **codecp) +{ + parse_user_pin_configs(*codecp, buf); +} + +static void parse_verb_mode(char *buf, struct hda_bus *bus, + struct hda_codec **codecp) +{ + parse_init_verbs(*codecp, buf); +} + +static void parse_hint_mode(char *buf, struct hda_bus *bus, + struct hda_codec **codecp) +{ + parse_hints(*codecp, buf); +} + +static void parse_model_mode(char *buf, struct hda_bus *bus, + struct hda_codec **codecp) +{ + kfree((*codecp)->modelname); + (*codecp)->modelname = kstrdup(buf, GFP_KERNEL); +} + +static void parse_chip_name_mode(char *buf, struct hda_bus *bus, + struct hda_codec **codecp) +{ + snd_hda_codec_set_name(*codecp, buf); +} + +#define DEFINE_PARSE_ID_MODE(name) \ +static void parse_##name##_mode(char *buf, struct hda_bus *bus, \ + struct hda_codec **codecp) \ +{ \ + unsigned long val; \ + if (!kstrtoul(buf, 0, &val)) \ + (*codecp)->core.name = val; \ +} + +DEFINE_PARSE_ID_MODE(vendor_id); +DEFINE_PARSE_ID_MODE(subsystem_id); +DEFINE_PARSE_ID_MODE(revision_id); + + +struct hda_patch_item { + const char *tag; + const char *alias; + void (*parser)(char *buf, struct hda_bus *bus, struct hda_codec **retc); +}; + +static const struct hda_patch_item patch_items[NUM_LINE_MODES] = { + [LINE_MODE_CODEC] = { + .tag = "[codec]", + .parser = parse_codec_mode, + }, + [LINE_MODE_MODEL] = { + .tag = "[model]", + .parser = parse_model_mode, + }, + [LINE_MODE_VERB] = { + .tag = "[verb]", + .alias = "[init_verbs]", + .parser = parse_verb_mode, + }, + [LINE_MODE_PINCFG] = { + .tag = "[pincfg]", + .alias = "[user_pin_configs]", + .parser = parse_pincfg_mode, + }, + [LINE_MODE_HINT] = { + .tag = "[hint]", + .alias = "[hints]", + .parser = parse_hint_mode + }, + [LINE_MODE_VENDOR_ID] = { + .tag = "[vendor_id]", + .parser = parse_vendor_id_mode, + }, + [LINE_MODE_SUBSYSTEM_ID] = { + .tag = "[subsystem_id]", + .parser = parse_subsystem_id_mode, + }, + [LINE_MODE_REVISION_ID] = { + .tag = "[revision_id]", + .parser = parse_revision_id_mode, + }, + [LINE_MODE_CHIP_NAME] = { + .tag = "[chip_name]", + .parser = parse_chip_name_mode, + }, +}; + +/* check the line starting with '[' -- change the parser mode accodingly */ +static int parse_line_mode(char *buf, struct hda_bus *bus) +{ + int i; + for (i = 0; i < ARRAY_SIZE(patch_items); i++) { + if (!patch_items[i].tag) + continue; + if (strmatch(buf, patch_items[i].tag)) + return i; + if (patch_items[i].alias && strmatch(buf, patch_items[i].alias)) + return i; + } + return LINE_MODE_NONE; +} + +/* copy one line from the buffer in fw, and update the fields in fw + * return zero if it reaches to the end of the buffer, or non-zero + * if successfully copied a line + * + * the spaces at the beginning and the end of the line are stripped + */ +static int get_line_from_fw(char *buf, int size, size_t *fw_size_p, + const void **fw_data_p) +{ + int len; + size_t fw_size = *fw_size_p; + const char *p = *fw_data_p; + + while (isspace(*p) && fw_size) { + p++; + fw_size--; + } + if (!fw_size) + return 0; + + for (len = 0; len < fw_size; len++) { + if (!*p) + break; + if (*p == '\n') { + p++; + len++; + break; + } + if (len < size) + *buf++ = *p++; + } + *buf = 0; + *fw_size_p = fw_size - len; + *fw_data_p = p; + remove_trail_spaces(buf); + return 1; +} + +/** + * snd_hda_load_patch - load a "patch" firmware file and parse it + * @bus: HD-audio bus + * @fw_size: the firmware byte size + * @fw_buf: the firmware data + */ +int snd_hda_load_patch(struct hda_bus *bus, size_t fw_size, const void *fw_buf) +{ + char buf[128]; + struct hda_codec *codec; + int line_mode; + + line_mode = LINE_MODE_NONE; + codec = NULL; + while (get_line_from_fw(buf, sizeof(buf) - 1, &fw_size, &fw_buf)) { + if (!*buf || *buf == '#' || *buf == '\n') + continue; + if (*buf == '[') + line_mode = parse_line_mode(buf, bus); + else if (patch_items[line_mode].parser && + (codec || line_mode <= LINE_MODE_CODEC)) + patch_items[line_mode].parser(buf, bus, &codec); + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_load_patch); +#endif /* CONFIG_SND_HDA_PATCH_LOADER */ + +/* + * sysfs entries + */ +static struct attribute *hda_dev_attrs[] = { + &dev_attr_vendor_id.attr, + &dev_attr_subsystem_id.attr, + &dev_attr_revision_id.attr, + &dev_attr_afg.attr, + &dev_attr_mfg.attr, + &dev_attr_vendor_name.attr, + &dev_attr_chip_name.attr, + &dev_attr_modelname.attr, + &dev_attr_init_pin_configs.attr, + &dev_attr_driver_pin_configs.attr, +#ifdef CONFIG_PM + &dev_attr_power_on_acct.attr, + &dev_attr_power_off_acct.attr, +#endif +#ifdef CONFIG_SND_HDA_RECONFIG + &dev_attr_init_verbs.attr, + &dev_attr_hints.attr, + &dev_attr_user_pin_configs.attr, + &dev_attr_reconfig.attr, + &dev_attr_clear.attr, +#endif + NULL +}; + +static const struct attribute_group hda_dev_attr_group = { + .attrs = hda_dev_attrs, +}; + +const struct attribute_group *snd_hda_dev_attr_groups[] = { + &hda_dev_attr_group, + NULL +}; + +void snd_hda_sysfs_init(struct hda_codec *codec) +{ + mutex_init(&codec->user_mutex); +#ifdef CONFIG_SND_HDA_RECONFIG + snd_array_init(&codec->init_verbs, sizeof(struct hda_verb), 32); + snd_array_init(&codec->hints, sizeof(struct hda_hint), 32); + snd_array_init(&codec->user_pins, sizeof(struct hda_pincfg), 16); +#endif +} + +void snd_hda_sysfs_clear(struct hda_codec *codec) +{ +#ifdef CONFIG_SND_HDA_RECONFIG + struct hda_hint *hint; + int i; + + /* clear init verbs */ + snd_array_free(&codec->init_verbs); + /* clear hints */ + snd_array_for_each(&codec->hints, i, hint) { + kfree(hint->key); /* we don't need to free hint->val */ + } + snd_array_free(&codec->hints); + snd_array_free(&codec->user_pins); +#endif +} diff --git a/sound/pci/hda/hda_tegra.c b/sound/pci/hda/hda_tegra.c new file mode 100644 index 000000000..976a112c7 --- /dev/null +++ b/sound/pci/hda/hda_tegra.c @@ -0,0 +1,618 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Implementation of primary ALSA driver code base for NVIDIA Tegra HDA. + */ + +#include <linux/clk.h> +#include <linux/clocksource.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/mutex.h> +#include <linux/of_device.h> +#include <linux/reset.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/string.h> +#include <linux/pm_runtime.h> + +#include <sound/core.h> +#include <sound/initval.h> + +#include <sound/hda_codec.h> +#include "hda_controller.h" + +/* Defines for Nvidia Tegra HDA support */ +#define HDA_BAR0 0x8000 + +#define HDA_CFG_CMD 0x1004 +#define HDA_CFG_BAR0 0x1010 + +#define HDA_ENABLE_IO_SPACE (1 << 0) +#define HDA_ENABLE_MEM_SPACE (1 << 1) +#define HDA_ENABLE_BUS_MASTER (1 << 2) +#define HDA_ENABLE_SERR (1 << 8) +#define HDA_DISABLE_INTR (1 << 10) +#define HDA_BAR0_INIT_PROGRAM 0xFFFFFFFF +#define HDA_BAR0_FINAL_PROGRAM (1 << 14) + +/* IPFS */ +#define HDA_IPFS_CONFIG 0x180 +#define HDA_IPFS_EN_FPCI 0x1 + +#define HDA_IPFS_FPCI_BAR0 0x80 +#define HDA_FPCI_BAR0_START 0x40 + +#define HDA_IPFS_INTR_MASK 0x188 +#define HDA_IPFS_EN_INTR (1 << 16) + +/* FPCI */ +#define FPCI_DBG_CFG_2 0x10F4 +#define FPCI_GCAP_NSDO_SHIFT 18 +#define FPCI_GCAP_NSDO_MASK (0x3 << FPCI_GCAP_NSDO_SHIFT) + +/* max number of SDs */ +#define NUM_CAPTURE_SD 1 +#define NUM_PLAYBACK_SD 1 + +/* + * Tegra194 does not reflect correct number of SDO lines. Below macro + * is used to update the GCAP register to workaround the issue. + */ +#define TEGRA194_NUM_SDO_LINES 4 + +struct hda_tegra_soc { + bool has_hda2codec_2x_reset; + bool has_hda2hdmi; +}; + +struct hda_tegra { + struct azx chip; + struct device *dev; + struct reset_control_bulk_data resets[3]; + struct clk_bulk_data clocks[3]; + unsigned int nresets; + unsigned int nclocks; + void __iomem *regs; + struct work_struct probe_work; + const struct hda_tegra_soc *soc; +}; + +#ifdef CONFIG_PM +static int power_save = CONFIG_SND_HDA_POWER_SAVE_DEFAULT; +module_param(power_save, bint, 0644); +MODULE_PARM_DESC(power_save, + "Automatic power-saving timeout (in seconds, 0 = disable)."); +#else +#define power_save 0 +#endif + +static const struct hda_controller_ops hda_tegra_ops; /* nothing special */ + +static void hda_tegra_init(struct hda_tegra *hda) +{ + u32 v; + + /* Enable PCI access */ + v = readl(hda->regs + HDA_IPFS_CONFIG); + v |= HDA_IPFS_EN_FPCI; + writel(v, hda->regs + HDA_IPFS_CONFIG); + + /* Enable MEM/IO space and bus master */ + v = readl(hda->regs + HDA_CFG_CMD); + v &= ~HDA_DISABLE_INTR; + v |= HDA_ENABLE_MEM_SPACE | HDA_ENABLE_IO_SPACE | + HDA_ENABLE_BUS_MASTER | HDA_ENABLE_SERR; + writel(v, hda->regs + HDA_CFG_CMD); + + writel(HDA_BAR0_INIT_PROGRAM, hda->regs + HDA_CFG_BAR0); + writel(HDA_BAR0_FINAL_PROGRAM, hda->regs + HDA_CFG_BAR0); + writel(HDA_FPCI_BAR0_START, hda->regs + HDA_IPFS_FPCI_BAR0); + + v = readl(hda->regs + HDA_IPFS_INTR_MASK); + v |= HDA_IPFS_EN_INTR; + writel(v, hda->regs + HDA_IPFS_INTR_MASK); +} + +/* + * power management + */ +static int __maybe_unused hda_tegra_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + int rc; + + rc = pm_runtime_force_suspend(dev); + if (rc < 0) + return rc; + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + + return 0; +} + +static int __maybe_unused hda_tegra_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + int rc; + + rc = pm_runtime_force_resume(dev); + if (rc < 0) + return rc; + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + + return 0; +} + +static int __maybe_unused hda_tegra_runtime_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip = card->private_data; + struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip); + + if (chip && chip->running) { + /* enable controller wake up event */ + azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) | + STATESTS_INT_MASK); + + azx_stop_chip(chip); + azx_enter_link_reset(chip); + } + clk_bulk_disable_unprepare(hda->nclocks, hda->clocks); + + return 0; +} + +static int __maybe_unused hda_tegra_runtime_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip = card->private_data; + struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip); + int rc; + + if (!chip->running) { + rc = reset_control_bulk_assert(hda->nresets, hda->resets); + if (rc) + return rc; + } + + rc = clk_bulk_prepare_enable(hda->nclocks, hda->clocks); + if (rc != 0) + return rc; + if (chip->running) { + hda_tegra_init(hda); + azx_init_chip(chip, 1); + /* disable controller wake up event*/ + azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) & + ~STATESTS_INT_MASK); + } else { + usleep_range(10, 100); + + rc = reset_control_bulk_deassert(hda->nresets, hda->resets); + if (rc) + return rc; + } + + return 0; +} + +static const struct dev_pm_ops hda_tegra_pm = { + SET_SYSTEM_SLEEP_PM_OPS(hda_tegra_suspend, hda_tegra_resume) + SET_RUNTIME_PM_OPS(hda_tegra_runtime_suspend, + hda_tegra_runtime_resume, + NULL) +}; + +static int hda_tegra_dev_disconnect(struct snd_device *device) +{ + struct azx *chip = device->device_data; + + chip->bus.shutdown = 1; + return 0; +} + +/* + * destructor + */ +static int hda_tegra_dev_free(struct snd_device *device) +{ + struct azx *chip = device->device_data; + struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip); + + cancel_work_sync(&hda->probe_work); + if (azx_bus(chip)->chip_init) { + azx_stop_all_streams(chip); + azx_stop_chip(chip); + } + + azx_free_stream_pages(chip); + azx_free_streams(chip); + snd_hdac_bus_exit(azx_bus(chip)); + + return 0; +} + +static int hda_tegra_init_chip(struct azx *chip, struct platform_device *pdev) +{ + struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip); + struct hdac_bus *bus = azx_bus(chip); + struct resource *res; + + hda->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(hda->regs)) + return PTR_ERR(hda->regs); + + bus->remap_addr = hda->regs + HDA_BAR0; + bus->addr = res->start + HDA_BAR0; + + hda_tegra_init(hda); + + return 0; +} + +static int hda_tegra_first_init(struct azx *chip, struct platform_device *pdev) +{ + struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip); + struct hdac_bus *bus = azx_bus(chip); + struct snd_card *card = chip->card; + int err; + unsigned short gcap; + int irq_id = platform_get_irq(pdev, 0); + const char *sname, *drv_name = "tegra-hda"; + struct device_node *np = pdev->dev.of_node; + + if (irq_id < 0) + return irq_id; + + err = hda_tegra_init_chip(chip, pdev); + if (err) + return err; + + err = devm_request_irq(chip->card->dev, irq_id, azx_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip); + if (err) { + dev_err(chip->card->dev, + "unable to request IRQ %d, disabling device\n", + irq_id); + return err; + } + bus->irq = irq_id; + bus->dma_stop_delay = 100; + card->sync_irq = bus->irq; + + /* + * Tegra194 has 4 SDO lines and the STRIPE can be used to + * indicate how many of the SDO lines the stream should be + * striped. But GCAP register does not reflect the true + * capability of HW. Below workaround helps to fix this. + * + * GCAP_NSDO is bits 19:18 in T_AZA_DBG_CFG_2, + * 0 for 1 SDO, 1 for 2 SDO, 2 for 4 SDO lines. + */ + if (of_device_is_compatible(np, "nvidia,tegra194-hda")) { + u32 val; + + dev_info(card->dev, "Override SDO lines to %u\n", + TEGRA194_NUM_SDO_LINES); + + val = readl(hda->regs + FPCI_DBG_CFG_2) & ~FPCI_GCAP_NSDO_MASK; + val |= (TEGRA194_NUM_SDO_LINES >> 1) << FPCI_GCAP_NSDO_SHIFT; + writel(val, hda->regs + FPCI_DBG_CFG_2); + } + + gcap = azx_readw(chip, GCAP); + dev_dbg(card->dev, "chipset global capabilities = 0x%x\n", gcap); + + chip->align_buffer_size = 1; + + /* read number of streams from GCAP register instead of using + * hardcoded value + */ + chip->capture_streams = (gcap >> 8) & 0x0f; + + /* The GCAP register on Tegra234 implies no Input Streams(ISS) support, + * but the HW output stream descriptor programming should start with + * offset 0x20*4 from base stream descriptor address. This will be a + * problem while calculating the offset for output stream descriptor + * which will be considering input stream also. So here output stream + * starts with offset 0 which is wrong as HW register for output stream + * offset starts with 4. + */ + if (of_device_is_compatible(np, "nvidia,tegra234-hda")) + chip->capture_streams = 4; + + chip->playback_streams = (gcap >> 12) & 0x0f; + if (!chip->playback_streams && !chip->capture_streams) { + /* gcap didn't give any info, switching to old method */ + chip->playback_streams = NUM_PLAYBACK_SD; + chip->capture_streams = NUM_CAPTURE_SD; + } + chip->capture_index_offset = 0; + chip->playback_index_offset = chip->capture_streams; + chip->num_streams = chip->playback_streams + chip->capture_streams; + + /* initialize streams */ + err = azx_init_streams(chip); + if (err < 0) { + dev_err(card->dev, "failed to initialize streams: %d\n", err); + return err; + } + + err = azx_alloc_stream_pages(chip); + if (err < 0) { + dev_err(card->dev, "failed to allocate stream pages: %d\n", + err); + return err; + } + + /* initialize chip */ + azx_init_chip(chip, 1); + + /* + * Playback (for 44.1K/48K, 2-channel, 16-bps) fails with + * 4 SDO lines due to legacy design limitation. Following + * is, from HD Audio Specification (Revision 1.0a), used to + * control striping of the stream across multiple SDO lines + * for sample rates <= 48K. + * + * { ((num_channels * bits_per_sample) / number of SDOs) >= 8 } + * + * Due to legacy design issue it is recommended that above + * ratio must be greater than 8. Since number of SDO lines is + * in powers of 2, next available ratio is 16 which can be + * used as a limiting factor here. + */ + if (of_device_is_compatible(np, "nvidia,tegra30-hda")) + chip->bus.core.sdo_limit = 16; + + /* codec detection */ + if (!bus->codec_mask) { + dev_err(card->dev, "no codecs found!\n"); + return -ENODEV; + } + + /* driver name */ + strncpy(card->driver, drv_name, sizeof(card->driver)); + /* shortname for card */ + sname = of_get_property(np, "nvidia,model", NULL); + if (!sname) + sname = drv_name; + if (strlen(sname) > sizeof(card->shortname)) + dev_info(card->dev, "truncating shortname for card\n"); + strncpy(card->shortname, sname, sizeof(card->shortname)); + + /* longname for card */ + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx irq %i", + card->shortname, bus->addr, bus->irq); + + return 0; +} + +/* + * constructor + */ + +static void hda_tegra_probe_work(struct work_struct *work); + +static int hda_tegra_create(struct snd_card *card, + unsigned int driver_caps, + struct hda_tegra *hda) +{ + static const struct snd_device_ops ops = { + .dev_disconnect = hda_tegra_dev_disconnect, + .dev_free = hda_tegra_dev_free, + }; + struct azx *chip; + int err; + + chip = &hda->chip; + + mutex_init(&chip->open_mutex); + chip->card = card; + chip->ops = &hda_tegra_ops; + chip->driver_caps = driver_caps; + chip->driver_type = driver_caps & 0xff; + chip->dev_index = 0; + chip->jackpoll_interval = msecs_to_jiffies(5000); + INIT_LIST_HEAD(&chip->pcm_list); + + chip->codec_probe_mask = -1; + + chip->single_cmd = false; + chip->snoop = true; + + INIT_WORK(&hda->probe_work, hda_tegra_probe_work); + + err = azx_bus_init(chip, NULL); + if (err < 0) + return err; + + chip->bus.core.sync_write = 0; + chip->bus.core.needs_damn_long_delay = 1; + chip->bus.core.aligned_mmio = 1; + chip->bus.jackpoll_in_suspend = 1; + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); + if (err < 0) { + dev_err(card->dev, "Error creating device\n"); + return err; + } + + return 0; +} + +static const struct hda_tegra_soc tegra30_data = { + .has_hda2codec_2x_reset = true, + .has_hda2hdmi = true, +}; + +static const struct hda_tegra_soc tegra194_data = { + .has_hda2codec_2x_reset = false, + .has_hda2hdmi = true, +}; + +static const struct hda_tegra_soc tegra234_data = { + .has_hda2codec_2x_reset = true, + .has_hda2hdmi = false, +}; + +static const struct of_device_id hda_tegra_match[] = { + { .compatible = "nvidia,tegra30-hda", .data = &tegra30_data }, + { .compatible = "nvidia,tegra194-hda", .data = &tegra194_data }, + { .compatible = "nvidia,tegra234-hda", .data = &tegra234_data }, + {}, +}; +MODULE_DEVICE_TABLE(of, hda_tegra_match); + +static int hda_tegra_probe(struct platform_device *pdev) +{ + const unsigned int driver_flags = AZX_DCAPS_CORBRP_SELF_CLEAR | + AZX_DCAPS_PM_RUNTIME | + AZX_DCAPS_4K_BDLE_BOUNDARY; + struct snd_card *card; + struct azx *chip; + struct hda_tegra *hda; + int err; + + hda = devm_kzalloc(&pdev->dev, sizeof(*hda), GFP_KERNEL); + if (!hda) + return -ENOMEM; + hda->dev = &pdev->dev; + chip = &hda->chip; + + hda->soc = of_device_get_match_data(&pdev->dev); + + err = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + THIS_MODULE, 0, &card); + if (err < 0) { + dev_err(&pdev->dev, "Error creating card!\n"); + return err; + } + + hda->resets[hda->nresets++].id = "hda"; + + /* + * "hda2hdmi" is not applicable for Tegra234. This is because the + * codec is separate IP and not under display SOR partition now. + */ + if (hda->soc->has_hda2hdmi) + hda->resets[hda->nresets++].id = "hda2hdmi"; + + /* + * "hda2codec_2x" reset is not present on Tegra194. Though DT would + * be updated to reflect this, but to have backward compatibility + * below is necessary. + */ + if (hda->soc->has_hda2codec_2x_reset) + hda->resets[hda->nresets++].id = "hda2codec_2x"; + + err = devm_reset_control_bulk_get_exclusive(&pdev->dev, hda->nresets, + hda->resets); + if (err) + goto out_free; + + hda->clocks[hda->nclocks++].id = "hda"; + if (hda->soc->has_hda2hdmi) + hda->clocks[hda->nclocks++].id = "hda2hdmi"; + hda->clocks[hda->nclocks++].id = "hda2codec_2x"; + + err = devm_clk_bulk_get(&pdev->dev, hda->nclocks, hda->clocks); + if (err < 0) + goto out_free; + + err = hda_tegra_create(card, driver_flags, hda); + if (err < 0) + goto out_free; + card->private_data = chip; + + dev_set_drvdata(&pdev->dev, card); + + pm_runtime_enable(hda->dev); + if (!azx_has_pm_runtime(chip)) + pm_runtime_forbid(hda->dev); + + schedule_work(&hda->probe_work); + + return 0; + +out_free: + snd_card_free(card); + return err; +} + +static void hda_tegra_probe_work(struct work_struct *work) +{ + struct hda_tegra *hda = container_of(work, struct hda_tegra, probe_work); + struct azx *chip = &hda->chip; + struct platform_device *pdev = to_platform_device(hda->dev); + int err; + + pm_runtime_get_sync(hda->dev); + err = hda_tegra_first_init(chip, pdev); + if (err < 0) + goto out_free; + + /* create codec instances */ + err = azx_probe_codecs(chip, 8); + if (err < 0) + goto out_free; + + err = azx_codec_configure(chip); + if (err < 0) + goto out_free; + + err = snd_card_register(chip->card); + if (err < 0) + goto out_free; + + chip->running = 1; + snd_hda_set_power_save(&chip->bus, power_save * 1000); + + out_free: + pm_runtime_put(hda->dev); + return; /* no error return from async probe */ +} + +static int hda_tegra_remove(struct platform_device *pdev) +{ + int ret; + + ret = snd_card_free(dev_get_drvdata(&pdev->dev)); + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static void hda_tegra_shutdown(struct platform_device *pdev) +{ + struct snd_card *card = dev_get_drvdata(&pdev->dev); + struct azx *chip; + + if (!card) + return; + chip = card->private_data; + if (chip && chip->running) + azx_stop_chip(chip); +} + +static struct platform_driver tegra_platform_hda = { + .driver = { + .name = "tegra-hda", + .pm = &hda_tegra_pm, + .of_match_table = hda_tegra_match, + }, + .probe = hda_tegra_probe, + .remove = hda_tegra_remove, + .shutdown = hda_tegra_shutdown, +}; +module_platform_driver(tegra_platform_hda); + +MODULE_DESCRIPTION("Tegra HDA bus driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/pci/hda/hp_x360_helper.c b/sound/pci/hda/hp_x360_helper.c new file mode 100644 index 000000000..969542c57 --- /dev/null +++ b/sound/pci/hda/hp_x360_helper.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Fixes for HP X360 laptops with top B&O speakers + * to be included from codec driver + */ + +static void alc295_fixup_hp_top_speakers(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + static const struct hda_pintbl pincfgs[] = { + { 0x17, 0x90170110 }, + { } + }; + static const struct coef_fw alc295_hp_speakers_coefs[] = { + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0000), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x003f), WRITE_COEF(0x28, 0x1000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0004), WRITE_COEF(0x28, 0x0600), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006a), WRITE_COEF(0x28, 0x0006), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006c), WRITE_COEF(0x28, 0xc0c0), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0008), WRITE_COEF(0x28, 0xb000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x002e), WRITE_COEF(0x28, 0x0800), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006a), WRITE_COEF(0x28, 0x00c1), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006c), WRITE_COEF(0x28, 0x0320), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0039), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x003b), WRITE_COEF(0x28, 0xffff), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x003c), WRITE_COEF(0x28, 0xffd0), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x003a), WRITE_COEF(0x28, 0x1dfe), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0080), WRITE_COEF(0x28, 0x0880), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x003a), WRITE_COEF(0x28, 0x0dfe), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0018), WRITE_COEF(0x28, 0x0219), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006a), WRITE_COEF(0x28, 0x005d), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006c), WRITE_COEF(0x28, 0x9142), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c0), WRITE_COEF(0x28, 0x01ce), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c1), WRITE_COEF(0x28, 0xed0c), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c2), WRITE_COEF(0x28, 0x1c00), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c3), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c4), WRITE_COEF(0x28, 0x0200), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c5), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c6), WRITE_COEF(0x28, 0x0399), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c7), WRITE_COEF(0x28, 0x2330), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c8), WRITE_COEF(0x28, 0x1e5d), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00c9), WRITE_COEF(0x28, 0x6eff), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00ca), WRITE_COEF(0x28, 0x01c0), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00cb), WRITE_COEF(0x28, 0xed0c), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00cc), WRITE_COEF(0x28, 0x1c00), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00cd), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00ce), WRITE_COEF(0x28, 0x0200), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00cf), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00d0), WRITE_COEF(0x28, 0x0399), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00d1), WRITE_COEF(0x28, 0x2330), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00d2), WRITE_COEF(0x28, 0x1e5d), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x00d3), WRITE_COEF(0x28, 0x6eff), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0062), WRITE_COEF(0x28, 0x8000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0063), WRITE_COEF(0x28, 0x5f5f), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0064), WRITE_COEF(0x28, 0x1000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0065), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0066), WRITE_COEF(0x28, 0x4004), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0067), WRITE_COEF(0x28, 0x0802), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0068), WRITE_COEF(0x28, 0x890f), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0069), WRITE_COEF(0x28, 0xe021), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0070), WRITE_COEF(0x28, 0x8012), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0071), WRITE_COEF(0x28, 0x3450), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0072), WRITE_COEF(0x28, 0x0123), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0073), WRITE_COEF(0x28, 0x4543), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0074), WRITE_COEF(0x28, 0x2100), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0075), WRITE_COEF(0x28, 0x4321), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0076), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0050), WRITE_COEF(0x28, 0x8200), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x003a), WRITE_COEF(0x28, 0x1dfe), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0051), WRITE_COEF(0x28, 0x0707), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0052), WRITE_COEF(0x28, 0x4090), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006a), WRITE_COEF(0x28, 0x0090), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006c), WRITE_COEF(0x28, 0x721f), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0012), WRITE_COEF(0x28, 0xebeb), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x009e), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0060), WRITE_COEF(0x28, 0x2213), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006a), WRITE_COEF(0x28, 0x0006), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x006c), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x003f), WRITE_COEF(0x28, 0x3000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0004), WRITE_COEF(0x28, 0x0500), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0040), WRITE_COEF(0x28, 0x800c), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0046), WRITE_COEF(0x28, 0xc22e), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x004b), WRITE_COEF(0x28, 0x0000), WRITE_COEF(0x29, 0xb024), + WRITE_COEF(0x24, 0x0012), WRITE_COEF(0x26, 0x0050), WRITE_COEF(0x28, 0x82ec), WRITE_COEF(0x29, 0xb024), + }; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + snd_hda_apply_pincfgs(codec, pincfgs); + alc295_fixup_disable_dac3(codec, fix, action); + break; + case HDA_FIXUP_ACT_INIT: + alc_process_coef_fw(codec, alc295_hp_speakers_coefs); + break; + } +} diff --git a/sound/pci/hda/ideapad_s740_helper.c b/sound/pci/hda/ideapad_s740_helper.c new file mode 100644 index 000000000..564b9086e --- /dev/null +++ b/sound/pci/hda/ideapad_s740_helper.c @@ -0,0 +1,492 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Fixes for Lenovo Ideapad S740, to be included from codec driver */ + +static const struct hda_verb alc285_ideapad_s740_coefs[] = { +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x10 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0320 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0041 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0041 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x007f }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x007f }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x003c }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0011 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x003c }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0011 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x000c }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x001a }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x000c }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x001a }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x000f }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0042 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x000f }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0042 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0010 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0040 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0010 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0040 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0003 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0009 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0003 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0009 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x001c }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x004c }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x001c }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x004c }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x001d }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x004e }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x001d }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x004e }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x001b }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x001b }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0019 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0025 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0019 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0025 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0018 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0037 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0018 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0037 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x001a }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0040 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x001a }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0040 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0016 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0076 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0016 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0076 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0017 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0010 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0017 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0010 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0015 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0015 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0015 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0015 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0007 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0086 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0007 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0086 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0002 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0002 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0002 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0002 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0042 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0042 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x007f }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x007f }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x003c }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0011 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x003c }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0011 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x000c }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x002a }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x000c }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x002a }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x000f }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0046 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x000f }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0046 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0010 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0044 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0010 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0044 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0003 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0009 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0003 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0009 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x001c }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x004c }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x001c }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x004c }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x001b }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x001b }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0019 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0025 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0019 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0025 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0018 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0037 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0018 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0037 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x001a }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0040 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x001a }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0040 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0016 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0076 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0016 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0076 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0017 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0010 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0017 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0010 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0015 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0015 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0015 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0015 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0007 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0086 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0007 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0086 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0002 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0002 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0001 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x29 }, +{ 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0002 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, +{ 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, +{} +}; + +static void alc285_fixup_ideapad_s740_coef(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + snd_hda_add_verbs(codec, alc285_ideapad_s740_coefs); + break; + } +} diff --git a/sound/pci/hda/patch_analog.c b/sound/pci/hda/patch_analog.c new file mode 100644 index 000000000..8afe6000f --- /dev/null +++ b/sound/pci/hda/patch_analog.c @@ -0,0 +1,1180 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HD audio interface patch for AD1882, AD1884, AD1981HD, AD1983, AD1984, + * AD1986A, AD1988 + * + * Copyright (c) 2005-2007 Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> + +#include <sound/core.h> +#include <sound/hda_codec.h> +#include "hda_local.h" +#include "hda_auto_parser.h" +#include "hda_beep.h" +#include "hda_jack.h" +#include "hda_generic.h" + + +struct ad198x_spec { + struct hda_gen_spec gen; + + /* for auto parser */ + int smux_paths[4]; + unsigned int cur_smux; + hda_nid_t eapd_nid; + + unsigned int beep_amp; /* beep amp value, set via set_beep_amp() */ + int num_smux_conns; +}; + + +#ifdef CONFIG_SND_HDA_INPUT_BEEP +/* additional beep mixers; the actual parameters are overwritten at build */ +static const struct snd_kcontrol_new ad_beep_mixer[] = { + HDA_CODEC_VOLUME("Beep Playback Volume", 0, 0, HDA_OUTPUT), + HDA_CODEC_MUTE_BEEP("Beep Playback Switch", 0, 0, HDA_OUTPUT), + { } /* end */ +}; + +#define set_beep_amp(spec, nid, idx, dir) \ + ((spec)->beep_amp = HDA_COMPOSE_AMP_VAL(nid, 1, idx, dir)) /* mono */ +#else +#define set_beep_amp(spec, nid, idx, dir) /* NOP */ +#endif + +#ifdef CONFIG_SND_HDA_INPUT_BEEP +static int create_beep_ctls(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + const struct snd_kcontrol_new *knew; + + if (!spec->beep_amp) + return 0; + + for (knew = ad_beep_mixer ; knew->name; knew++) { + int err; + struct snd_kcontrol *kctl; + kctl = snd_ctl_new1(knew, codec); + if (!kctl) + return -ENOMEM; + kctl->private_value = spec->beep_amp; + err = snd_hda_ctl_add(codec, 0, kctl); + if (err < 0) + return err; + } + return 0; +} +#else +#define create_beep_ctls(codec) 0 +#endif + +#ifdef CONFIG_PM +static void ad198x_power_eapd_write(struct hda_codec *codec, hda_nid_t front, + hda_nid_t hp) +{ + if (snd_hda_query_pin_caps(codec, front) & AC_PINCAP_EAPD) + snd_hda_codec_write(codec, front, 0, AC_VERB_SET_EAPD_BTLENABLE, + !codec->inv_eapd ? 0x00 : 0x02); + if (snd_hda_query_pin_caps(codec, hp) & AC_PINCAP_EAPD) + snd_hda_codec_write(codec, hp, 0, AC_VERB_SET_EAPD_BTLENABLE, + !codec->inv_eapd ? 0x00 : 0x02); +} + +static void ad198x_power_eapd(struct hda_codec *codec) +{ + /* We currently only handle front, HP */ + switch (codec->core.vendor_id) { + case 0x11d41882: + case 0x11d4882a: + case 0x11d41884: + case 0x11d41984: + case 0x11d41883: + case 0x11d4184a: + case 0x11d4194a: + case 0x11d4194b: + case 0x11d41988: + case 0x11d4198b: + case 0x11d4989a: + case 0x11d4989b: + ad198x_power_eapd_write(codec, 0x12, 0x11); + break; + case 0x11d41981: + case 0x11d41983: + ad198x_power_eapd_write(codec, 0x05, 0x06); + break; + case 0x11d41986: + ad198x_power_eapd_write(codec, 0x1b, 0x1a); + break; + } +} + +static int ad198x_suspend(struct hda_codec *codec) +{ + snd_hda_shutup_pins(codec); + ad198x_power_eapd(codec); + return 0; +} +#endif + +/* follow EAPD via vmaster hook */ +static void ad_vmaster_eapd_hook(void *private_data, int enabled) +{ + struct hda_codec *codec = private_data; + struct ad198x_spec *spec = codec->spec; + + if (!spec->eapd_nid) + return; + if (codec->inv_eapd) + enabled = !enabled; + snd_hda_codec_write_cache(codec, spec->eapd_nid, 0, + AC_VERB_SET_EAPD_BTLENABLE, + enabled ? 0x02 : 0x00); +} + +/* + * Automatic parse of I/O pins from the BIOS configuration + */ + +static int ad198x_auto_build_controls(struct hda_codec *codec) +{ + int err; + + err = snd_hda_gen_build_controls(codec); + if (err < 0) + return err; + err = create_beep_ctls(codec); + if (err < 0) + return err; + return 0; +} + +static const struct hda_codec_ops ad198x_auto_patch_ops = { + .build_controls = ad198x_auto_build_controls, + .build_pcms = snd_hda_gen_build_pcms, + .init = snd_hda_gen_init, + .free = snd_hda_gen_free, + .unsol_event = snd_hda_jack_unsol_event, +#ifdef CONFIG_PM + .check_power_status = snd_hda_gen_check_power_status, + .suspend = ad198x_suspend, +#endif +}; + + +static int ad198x_parse_auto_config(struct hda_codec *codec, bool indep_hp) +{ + struct ad198x_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->gen.autocfg; + int err; + + codec->spdif_status_reset = 1; + codec->no_trigger_sense = 1; + codec->no_sticky_stream = 1; + + spec->gen.indep_hp = indep_hp; + if (!spec->gen.add_stereo_mix_input) + spec->gen.add_stereo_mix_input = HDA_HINT_STEREO_MIX_AUTO; + + err = snd_hda_parse_pin_defcfg(codec, cfg, NULL, 0); + if (err < 0) + return err; + err = snd_hda_gen_parse_auto_config(codec, cfg); + if (err < 0) + return err; + + return 0; +} + +/* + * AD1986A specific + */ + +static int alloc_ad_spec(struct hda_codec *codec) +{ + struct ad198x_spec *spec; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return -ENOMEM; + codec->spec = spec; + snd_hda_gen_spec_init(&spec->gen); + codec->patch_ops = ad198x_auto_patch_ops; + return 0; +} + +/* + * AD1986A fixup codes + */ + +/* Lenovo N100 seems to report the reversed bit for HP jack-sensing */ +static void ad_fixup_inv_jack_detect(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct ad198x_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + codec->inv_jack_detect = 1; + spec->gen.keep_eapd_on = 1; + spec->gen.vmaster_mute.hook = ad_vmaster_eapd_hook; + spec->eapd_nid = 0x1b; + } +} + +/* Toshiba Satellite L40 implements EAPD in a standard way unlike others */ +static void ad1986a_fixup_eapd(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct ad198x_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + codec->inv_eapd = 0; + spec->gen.keep_eapd_on = 1; + spec->eapd_nid = 0x1b; + } +} + +/* enable stereo-mix input for avoiding regression on KDE (bko#88251) */ +static void ad1986a_fixup_eapd_mix_in(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct ad198x_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + ad1986a_fixup_eapd(codec, fix, action); + spec->gen.add_stereo_mix_input = HDA_HINT_STEREO_MIX_ENABLE; + } +} + +enum { + AD1986A_FIXUP_INV_JACK_DETECT, + AD1986A_FIXUP_ULTRA, + AD1986A_FIXUP_SAMSUNG, + AD1986A_FIXUP_3STACK, + AD1986A_FIXUP_LAPTOP, + AD1986A_FIXUP_LAPTOP_IMIC, + AD1986A_FIXUP_EAPD, + AD1986A_FIXUP_EAPD_MIX_IN, + AD1986A_FIXUP_EASYNOTE, +}; + +static const struct hda_fixup ad1986a_fixups[] = { + [AD1986A_FIXUP_INV_JACK_DETECT] = { + .type = HDA_FIXUP_FUNC, + .v.func = ad_fixup_inv_jack_detect, + }, + [AD1986A_FIXUP_ULTRA] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1b, 0x90170110 }, /* speaker */ + { 0x1d, 0x90a7013e }, /* int mic */ + {} + }, + }, + [AD1986A_FIXUP_SAMSUNG] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1b, 0x90170110 }, /* speaker */ + { 0x1d, 0x90a7013e }, /* int mic */ + { 0x20, 0x411111f0 }, /* N/A */ + { 0x24, 0x411111f0 }, /* N/A */ + {} + }, + }, + [AD1986A_FIXUP_3STACK] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1a, 0x02214021 }, /* headphone */ + { 0x1b, 0x01014011 }, /* front */ + { 0x1c, 0x01813030 }, /* line-in */ + { 0x1d, 0x01a19020 }, /* rear mic */ + { 0x1e, 0x411111f0 }, /* N/A */ + { 0x1f, 0x02a190f0 }, /* mic */ + { 0x20, 0x411111f0 }, /* N/A */ + {} + }, + }, + [AD1986A_FIXUP_LAPTOP] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1a, 0x02214021 }, /* headphone */ + { 0x1b, 0x90170110 }, /* speaker */ + { 0x1c, 0x411111f0 }, /* N/A */ + { 0x1d, 0x411111f0 }, /* N/A */ + { 0x1e, 0x411111f0 }, /* N/A */ + { 0x1f, 0x02a191f0 }, /* mic */ + { 0x20, 0x411111f0 }, /* N/A */ + {} + }, + }, + [AD1986A_FIXUP_LAPTOP_IMIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1d, 0x90a7013e }, /* int mic */ + {} + }, + .chained_before = 1, + .chain_id = AD1986A_FIXUP_LAPTOP, + }, + [AD1986A_FIXUP_EAPD] = { + .type = HDA_FIXUP_FUNC, + .v.func = ad1986a_fixup_eapd, + }, + [AD1986A_FIXUP_EAPD_MIX_IN] = { + .type = HDA_FIXUP_FUNC, + .v.func = ad1986a_fixup_eapd_mix_in, + }, + [AD1986A_FIXUP_EASYNOTE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1a, 0x0421402f }, /* headphone */ + { 0x1b, 0x90170110 }, /* speaker */ + { 0x1c, 0x411111f0 }, /* N/A */ + { 0x1d, 0x90a70130 }, /* int mic */ + { 0x1e, 0x411111f0 }, /* N/A */ + { 0x1f, 0x04a19040 }, /* mic */ + { 0x20, 0x411111f0 }, /* N/A */ + { 0x21, 0x411111f0 }, /* N/A */ + { 0x22, 0x411111f0 }, /* N/A */ + { 0x23, 0x411111f0 }, /* N/A */ + { 0x24, 0x411111f0 }, /* N/A */ + { 0x25, 0x411111f0 }, /* N/A */ + {} + }, + .chained = true, + .chain_id = AD1986A_FIXUP_EAPD_MIX_IN, + }, +}; + +static const struct snd_pci_quirk ad1986a_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x30af, "HP B2800", AD1986A_FIXUP_LAPTOP_IMIC), + SND_PCI_QUIRK(0x1043, 0x1153, "ASUS M9V", AD1986A_FIXUP_LAPTOP_IMIC), + SND_PCI_QUIRK(0x1043, 0x1443, "ASUS Z99He", AD1986A_FIXUP_EAPD), + SND_PCI_QUIRK(0x1043, 0x1447, "ASUS A8JN", AD1986A_FIXUP_EAPD), + SND_PCI_QUIRK_MASK(0x1043, 0xff00, 0x8100, "ASUS P5", AD1986A_FIXUP_3STACK), + SND_PCI_QUIRK_MASK(0x1043, 0xff00, 0x8200, "ASUS M2", AD1986A_FIXUP_3STACK), + SND_PCI_QUIRK(0x10de, 0xcb84, "ASUS A8N-VM", AD1986A_FIXUP_3STACK), + SND_PCI_QUIRK(0x1179, 0xff40, "Toshiba Satellite L40", AD1986A_FIXUP_EAPD), + SND_PCI_QUIRK(0x144d, 0xc01e, "FSC V2060", AD1986A_FIXUP_LAPTOP), + SND_PCI_QUIRK_MASK(0x144d, 0xff00, 0xc000, "Samsung", AD1986A_FIXUP_SAMSUNG), + SND_PCI_QUIRK(0x144d, 0xc027, "Samsung Q1", AD1986A_FIXUP_ULTRA), + SND_PCI_QUIRK(0x1631, 0xc022, "PackardBell EasyNote MX65", AD1986A_FIXUP_EASYNOTE), + SND_PCI_QUIRK(0x17aa, 0x2066, "Lenovo N100", AD1986A_FIXUP_INV_JACK_DETECT), + SND_PCI_QUIRK(0x17aa, 0x1011, "Lenovo M55", AD1986A_FIXUP_3STACK), + SND_PCI_QUIRK(0x17aa, 0x1017, "Lenovo A60", AD1986A_FIXUP_3STACK), + {} +}; + +static const struct hda_model_fixup ad1986a_fixup_models[] = { + { .id = AD1986A_FIXUP_3STACK, .name = "3stack" }, + { .id = AD1986A_FIXUP_LAPTOP, .name = "laptop" }, + { .id = AD1986A_FIXUP_LAPTOP_IMIC, .name = "laptop-imic" }, + { .id = AD1986A_FIXUP_LAPTOP_IMIC, .name = "laptop-eapd" }, /* alias */ + { .id = AD1986A_FIXUP_EAPD, .name = "eapd" }, + {} +}; + +/* + */ +static int patch_ad1986a(struct hda_codec *codec) +{ + int err; + struct ad198x_spec *spec; + static const hda_nid_t preferred_pairs[] = { + 0x1a, 0x03, + 0x1b, 0x03, + 0x1c, 0x04, + 0x1d, 0x05, + 0x1e, 0x03, + 0 + }; + + err = alloc_ad_spec(codec); + if (err < 0) + return err; + spec = codec->spec; + + /* AD1986A has the inverted EAPD implementation */ + codec->inv_eapd = 1; + + spec->gen.mixer_nid = 0x07; + spec->gen.beep_nid = 0x19; + set_beep_amp(spec, 0x18, 0, HDA_OUTPUT); + + /* AD1986A has a hardware problem that it can't share a stream + * with multiple output pins. The copy of front to surrounds + * causes noisy or silent outputs at a certain timing, e.g. + * changing the volume. + * So, let's disable the shared stream. + */ + spec->gen.multiout.no_share_stream = 1; + /* give fixed DAC/pin pairs */ + spec->gen.preferred_dacs = preferred_pairs; + + /* AD1986A can't manage the dynamic pin on/off smoothly */ + spec->gen.auto_mute_via_amp = 1; + + snd_hda_pick_fixup(codec, ad1986a_fixup_models, ad1986a_fixup_tbl, + ad1986a_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + err = ad198x_parse_auto_config(codec, false); + if (err < 0) { + snd_hda_gen_free(codec); + return err; + } + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; +} + + +/* + * AD1983 specific + */ + +/* + * SPDIF mux control for AD1983 auto-parser + */ +static int ad1983_auto_smux_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + static const char * const texts2[] = { "PCM", "ADC" }; + static const char * const texts3[] = { "PCM", "ADC1", "ADC2" }; + int num_conns = spec->num_smux_conns; + + if (num_conns == 2) + return snd_hda_enum_helper_info(kcontrol, uinfo, 2, texts2); + else if (num_conns == 3) + return snd_hda_enum_helper_info(kcontrol, uinfo, 3, texts3); + else + return -EINVAL; +} + +static int ad1983_auto_smux_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->cur_smux; + return 0; +} + +static int ad1983_auto_smux_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + unsigned int val = ucontrol->value.enumerated.item[0]; + hda_nid_t dig_out = spec->gen.multiout.dig_out_nid; + int num_conns = spec->num_smux_conns; + + if (val >= num_conns) + return -EINVAL; + if (spec->cur_smux == val) + return 0; + spec->cur_smux = val; + snd_hda_codec_write_cache(codec, dig_out, 0, + AC_VERB_SET_CONNECT_SEL, val); + return 1; +} + +static const struct snd_kcontrol_new ad1983_auto_smux_mixer = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "IEC958 Playback Source", + .info = ad1983_auto_smux_enum_info, + .get = ad1983_auto_smux_enum_get, + .put = ad1983_auto_smux_enum_put, +}; + +static int ad1983_add_spdif_mux_ctl(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + hda_nid_t dig_out = spec->gen.multiout.dig_out_nid; + int num_conns; + + if (!dig_out) + return 0; + num_conns = snd_hda_get_num_conns(codec, dig_out); + if (num_conns != 2 && num_conns != 3) + return 0; + spec->num_smux_conns = num_conns; + if (!snd_hda_gen_add_kctl(&spec->gen, NULL, &ad1983_auto_smux_mixer)) + return -ENOMEM; + return 0; +} + +static int patch_ad1983(struct hda_codec *codec) +{ + static const hda_nid_t conn_0c[] = { 0x08 }; + static const hda_nid_t conn_0d[] = { 0x09 }; + struct ad198x_spec *spec; + int err; + + err = alloc_ad_spec(codec); + if (err < 0) + return err; + spec = codec->spec; + + spec->gen.mixer_nid = 0x0e; + spec->gen.beep_nid = 0x10; + set_beep_amp(spec, 0x10, 0, HDA_OUTPUT); + + /* limit the loopback routes not to confuse the parser */ + snd_hda_override_conn_list(codec, 0x0c, ARRAY_SIZE(conn_0c), conn_0c); + snd_hda_override_conn_list(codec, 0x0d, ARRAY_SIZE(conn_0d), conn_0d); + + err = ad198x_parse_auto_config(codec, false); + if (err < 0) + goto error; + err = ad1983_add_spdif_mux_ctl(codec); + if (err < 0) + goto error; + return 0; + + error: + snd_hda_gen_free(codec); + return err; +} + + +/* + * AD1981 HD specific + */ + +static void ad1981_fixup_hp_eapd(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct ad198x_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->gen.vmaster_mute.hook = ad_vmaster_eapd_hook; + spec->eapd_nid = 0x05; + } +} + +/* set the upper-limit for mixer amp to 0dB for avoiding the possible + * damage by overloading + */ +static void ad1981_fixup_amp_override(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) + snd_hda_override_amp_caps(codec, 0x11, HDA_INPUT, + (0x17 << AC_AMPCAP_OFFSET_SHIFT) | + (0x17 << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x05 << AC_AMPCAP_STEP_SIZE_SHIFT) | + (1 << AC_AMPCAP_MUTE_SHIFT)); +} + +enum { + AD1981_FIXUP_AMP_OVERRIDE, + AD1981_FIXUP_HP_EAPD, +}; + +static const struct hda_fixup ad1981_fixups[] = { + [AD1981_FIXUP_AMP_OVERRIDE] = { + .type = HDA_FIXUP_FUNC, + .v.func = ad1981_fixup_amp_override, + }, + [AD1981_FIXUP_HP_EAPD] = { + .type = HDA_FIXUP_FUNC, + .v.func = ad1981_fixup_hp_eapd, + .chained = true, + .chain_id = AD1981_FIXUP_AMP_OVERRIDE, + }, +}; + +static const struct snd_pci_quirk ad1981_fixup_tbl[] = { + SND_PCI_QUIRK_VENDOR(0x1014, "Lenovo", AD1981_FIXUP_AMP_OVERRIDE), + SND_PCI_QUIRK_VENDOR(0x103c, "HP", AD1981_FIXUP_HP_EAPD), + SND_PCI_QUIRK_VENDOR(0x17aa, "Lenovo", AD1981_FIXUP_AMP_OVERRIDE), + /* HP nx6320 (reversed SSID, H/W bug) */ + SND_PCI_QUIRK(0x30b0, 0x103c, "HP nx6320", AD1981_FIXUP_HP_EAPD), + {} +}; + +static int patch_ad1981(struct hda_codec *codec) +{ + struct ad198x_spec *spec; + int err; + + err = alloc_ad_spec(codec); + if (err < 0) + return -ENOMEM; + spec = codec->spec; + + spec->gen.mixer_nid = 0x0e; + spec->gen.beep_nid = 0x10; + set_beep_amp(spec, 0x0d, 0, HDA_OUTPUT); + + snd_hda_pick_fixup(codec, NULL, ad1981_fixup_tbl, ad1981_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + err = ad198x_parse_auto_config(codec, false); + if (err < 0) + goto error; + err = ad1983_add_spdif_mux_ctl(codec); + if (err < 0) + goto error; + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; + + error: + snd_hda_gen_free(codec); + return err; +} + + +/* + * AD1988 + * + * Output pins and routes + * + * Pin Mix Sel DAC (*) + * port-A 0x11 (mute/hp) <- 0x22 <- 0x37 <- 03/04/06 + * port-B 0x14 (mute/hp) <- 0x2b <- 0x30 <- 03/04/06 + * port-C 0x15 (mute) <- 0x2c <- 0x31 <- 05/0a + * port-D 0x12 (mute/hp) <- 0x29 <- 04 + * port-E 0x17 (mute/hp) <- 0x26 <- 0x32 <- 05/0a + * port-F 0x16 (mute) <- 0x2a <- 06 + * port-G 0x24 (mute) <- 0x27 <- 05 + * port-H 0x25 (mute) <- 0x28 <- 0a + * mono 0x13 (mute/amp)<- 0x1e <- 0x36 <- 03/04/06 + * + * DAC0 = 03h, DAC1 = 04h, DAC2 = 05h, DAC3 = 06h, DAC4 = 0ah + * (*) DAC2/3/4 are swapped to DAC3/4/2 on AD198A rev.2 due to a h/w bug. + * + * Input pins and routes + * + * pin boost mix input # / adc input # + * port-A 0x11 -> 0x38 -> mix 2, ADC 0 + * port-B 0x14 -> 0x39 -> mix 0, ADC 1 + * port-C 0x15 -> 0x3a -> 33:0 - mix 1, ADC 2 + * port-D 0x12 -> 0x3d -> mix 3, ADC 8 + * port-E 0x17 -> 0x3c -> 34:0 - mix 4, ADC 4 + * port-F 0x16 -> 0x3b -> mix 5, ADC 3 + * port-G 0x24 -> N/A -> 33:1 - mix 1, 34:1 - mix 4, ADC 6 + * port-H 0x25 -> N/A -> 33:2 - mix 1, 34:2 - mix 4, ADC 7 + * + * + * DAC assignment + * 6stack - front/surr/CLFE/side/opt DACs - 04/06/05/0a/03 + * 3stack - front/surr/CLFE/opt DACs - 04/05/0a/03 + * + * Inputs of Analog Mix (0x20) + * 0:Port-B (front mic) + * 1:Port-C/G/H (line-in) + * 2:Port-A + * 3:Port-D (line-in/2) + * 4:Port-E/G/H (mic-in) + * 5:Port-F (mic2-in) + * 6:CD + * 7:Beep + * + * ADC selection + * 0:Port-A + * 1:Port-B (front mic-in) + * 2:Port-C (line-in) + * 3:Port-F (mic2-in) + * 4:Port-E (mic-in) + * 5:CD + * 6:Port-G + * 7:Port-H + * 8:Port-D (line-in/2) + * 9:Mix + * + * Proposed pin assignments by the datasheet + * + * 6-stack + * Port-A front headphone + * B front mic-in + * C rear line-in + * D rear front-out + * E rear mic-in + * F rear surround + * G rear CLFE + * H rear side + * + * 3-stack + * Port-A front headphone + * B front mic + * C rear line-in/surround + * D rear front-out + * E rear mic-in/CLFE + * + * laptop + * Port-A headphone + * B mic-in + * C docking station + * D internal speaker (with EAPD) + * E/F quad mic array + */ + +static int ad1988_auto_smux_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + static const char * const texts[] = { + "PCM", "ADC1", "ADC2", "ADC3", + }; + int num_conns = spec->num_smux_conns; + + if (num_conns > 4) + num_conns = 4; + return snd_hda_enum_helper_info(kcontrol, uinfo, num_conns, texts); +} + +static int ad1988_auto_smux_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->cur_smux; + return 0; +} + +static int ad1988_auto_smux_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + unsigned int val = ucontrol->value.enumerated.item[0]; + struct nid_path *path; + int num_conns = spec->num_smux_conns; + + if (val >= num_conns) + return -EINVAL; + if (spec->cur_smux == val) + return 0; + + mutex_lock(&codec->control_mutex); + path = snd_hda_get_path_from_idx(codec, + spec->smux_paths[spec->cur_smux]); + if (path) + snd_hda_activate_path(codec, path, false, true); + path = snd_hda_get_path_from_idx(codec, spec->smux_paths[val]); + if (path) + snd_hda_activate_path(codec, path, true, true); + spec->cur_smux = val; + mutex_unlock(&codec->control_mutex); + return 1; +} + +static const struct snd_kcontrol_new ad1988_auto_smux_mixer = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "IEC958 Playback Source", + .info = ad1988_auto_smux_enum_info, + .get = ad1988_auto_smux_enum_get, + .put = ad1988_auto_smux_enum_put, +}; + +static int ad1988_auto_init(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + int i, err; + + err = snd_hda_gen_init(codec); + if (err < 0) + return err; + if (!spec->gen.autocfg.dig_outs) + return 0; + + for (i = 0; i < 4; i++) { + struct nid_path *path; + path = snd_hda_get_path_from_idx(codec, spec->smux_paths[i]); + if (path) + snd_hda_activate_path(codec, path, path->active, false); + } + + return 0; +} + +static int ad1988_add_spdif_mux_ctl(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + int i, num_conns; + /* we create four static faked paths, since AD codecs have odd + * widget connections regarding the SPDIF out source + */ + static const struct nid_path fake_paths[4] = { + { + .depth = 3, + .path = { 0x02, 0x1d, 0x1b }, + .idx = { 0, 0, 0 }, + .multi = { 0, 0, 0 }, + }, + { + .depth = 4, + .path = { 0x08, 0x0b, 0x1d, 0x1b }, + .idx = { 0, 0, 1, 0 }, + .multi = { 0, 1, 0, 0 }, + }, + { + .depth = 4, + .path = { 0x09, 0x0b, 0x1d, 0x1b }, + .idx = { 0, 1, 1, 0 }, + .multi = { 0, 1, 0, 0 }, + }, + { + .depth = 4, + .path = { 0x0f, 0x0b, 0x1d, 0x1b }, + .idx = { 0, 2, 1, 0 }, + .multi = { 0, 1, 0, 0 }, + }, + }; + + /* SPDIF source mux appears to be present only on AD1988A */ + if (!spec->gen.autocfg.dig_outs || + get_wcaps_type(get_wcaps(codec, 0x1d)) != AC_WID_AUD_MIX) + return 0; + + num_conns = snd_hda_get_num_conns(codec, 0x0b) + 1; + if (num_conns != 3 && num_conns != 4) + return 0; + spec->num_smux_conns = num_conns; + + for (i = 0; i < num_conns; i++) { + struct nid_path *path = snd_array_new(&spec->gen.paths); + if (!path) + return -ENOMEM; + *path = fake_paths[i]; + if (!i) + path->active = 1; + spec->smux_paths[i] = snd_hda_get_path_idx(codec, path); + } + + if (!snd_hda_gen_add_kctl(&spec->gen, NULL, &ad1988_auto_smux_mixer)) + return -ENOMEM; + + codec->patch_ops.init = ad1988_auto_init; + + return 0; +} + +/* + */ + +enum { + AD1988_FIXUP_6STACK_DIG, +}; + +static const struct hda_fixup ad1988_fixups[] = { + [AD1988_FIXUP_6STACK_DIG] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x11, 0x02214130 }, /* front-hp */ + { 0x12, 0x01014010 }, /* line-out */ + { 0x14, 0x02a19122 }, /* front-mic */ + { 0x15, 0x01813021 }, /* line-in */ + { 0x16, 0x01011012 }, /* line-out */ + { 0x17, 0x01a19020 }, /* mic */ + { 0x1b, 0x0145f1f0 }, /* SPDIF */ + { 0x24, 0x01016011 }, /* line-out */ + { 0x25, 0x01012013 }, /* line-out */ + { } + } + }, +}; + +static const struct hda_model_fixup ad1988_fixup_models[] = { + { .id = AD1988_FIXUP_6STACK_DIG, .name = "6stack-dig" }, + {} +}; + +static int patch_ad1988(struct hda_codec *codec) +{ + struct ad198x_spec *spec; + int err; + + err = alloc_ad_spec(codec); + if (err < 0) + return err; + spec = codec->spec; + + spec->gen.mixer_nid = 0x20; + spec->gen.mixer_merge_nid = 0x21; + spec->gen.beep_nid = 0x10; + set_beep_amp(spec, 0x10, 0, HDA_OUTPUT); + + snd_hda_pick_fixup(codec, ad1988_fixup_models, NULL, ad1988_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + err = ad198x_parse_auto_config(codec, true); + if (err < 0) + goto error; + err = ad1988_add_spdif_mux_ctl(codec); + if (err < 0) + goto error; + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; + + error: + snd_hda_gen_free(codec); + return err; +} + + +/* + * AD1884 / AD1984 + * + * port-B - front line/mic-in + * port-E - aux in/out + * port-F - aux in/out + * port-C - rear line/mic-in + * port-D - rear line/hp-out + * port-A - front line/hp-out + * + * AD1984 = AD1884 + two digital mic-ins + * + * AD1883 / AD1884A / AD1984A / AD1984B + * + * port-B (0x14) - front mic-in + * port-E (0x1c) - rear mic-in + * port-F (0x16) - CD / ext out + * port-C (0x15) - rear line-in + * port-D (0x12) - rear line-out + * port-A (0x11) - front hp-out + * + * AD1984A = AD1884A + digital-mic + * AD1883 = equivalent with AD1984A + * AD1984B = AD1984A + extra SPDIF-out + */ + +/* set the upper-limit for mixer amp to 0dB for avoiding the possible + * damage by overloading + */ +static void ad1884_fixup_amp_override(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) + snd_hda_override_amp_caps(codec, 0x20, HDA_INPUT, + (0x17 << AC_AMPCAP_OFFSET_SHIFT) | + (0x17 << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x05 << AC_AMPCAP_STEP_SIZE_SHIFT) | + (1 << AC_AMPCAP_MUTE_SHIFT)); +} + +/* toggle GPIO1 according to the mute state */ +static void ad1884_vmaster_hp_gpio_hook(void *private_data, int enabled) +{ + struct hda_codec *codec = private_data; + struct ad198x_spec *spec = codec->spec; + + if (spec->eapd_nid) + ad_vmaster_eapd_hook(private_data, enabled); + snd_hda_codec_write_cache(codec, 0x01, 0, + AC_VERB_SET_GPIO_DATA, + enabled ? 0x00 : 0x02); +} + +static void ad1884_fixup_hp_eapd(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct ad198x_spec *spec = codec->spec; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + spec->gen.vmaster_mute.hook = ad1884_vmaster_hp_gpio_hook; + spec->gen.own_eapd_ctl = 1; + snd_hda_codec_write_cache(codec, 0x01, 0, + AC_VERB_SET_GPIO_MASK, 0x02); + snd_hda_codec_write_cache(codec, 0x01, 0, + AC_VERB_SET_GPIO_DIRECTION, 0x02); + snd_hda_codec_write_cache(codec, 0x01, 0, + AC_VERB_SET_GPIO_DATA, 0x02); + break; + case HDA_FIXUP_ACT_PROBE: + if (spec->gen.autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT) + spec->eapd_nid = spec->gen.autocfg.line_out_pins[0]; + else + spec->eapd_nid = spec->gen.autocfg.speaker_pins[0]; + break; + } +} + +static void ad1884_fixup_thinkpad(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct ad198x_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->gen.keep_eapd_on = 1; + spec->gen.vmaster_mute.hook = ad_vmaster_eapd_hook; + spec->eapd_nid = 0x12; + /* Analog PC Beeper - allow firmware/ACPI beeps */ + spec->beep_amp = HDA_COMPOSE_AMP_VAL(0x20, 3, 3, HDA_INPUT); + spec->gen.beep_nid = 0; /* no digital beep */ + } +} + +/* set magic COEFs for dmic */ +static const struct hda_verb ad1884_dmic_init_verbs[] = { + {0x01, AC_VERB_SET_COEF_INDEX, 0x13f7}, + {0x01, AC_VERB_SET_PROC_COEF, 0x08}, + {} +}; + +enum { + AD1884_FIXUP_AMP_OVERRIDE, + AD1884_FIXUP_HP_EAPD, + AD1884_FIXUP_DMIC_COEF, + AD1884_FIXUP_THINKPAD, + AD1884_FIXUP_HP_TOUCHSMART, +}; + +static const struct hda_fixup ad1884_fixups[] = { + [AD1884_FIXUP_AMP_OVERRIDE] = { + .type = HDA_FIXUP_FUNC, + .v.func = ad1884_fixup_amp_override, + }, + [AD1884_FIXUP_HP_EAPD] = { + .type = HDA_FIXUP_FUNC, + .v.func = ad1884_fixup_hp_eapd, + .chained = true, + .chain_id = AD1884_FIXUP_AMP_OVERRIDE, + }, + [AD1884_FIXUP_DMIC_COEF] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = ad1884_dmic_init_verbs, + }, + [AD1884_FIXUP_THINKPAD] = { + .type = HDA_FIXUP_FUNC, + .v.func = ad1884_fixup_thinkpad, + .chained = true, + .chain_id = AD1884_FIXUP_DMIC_COEF, + }, + [AD1884_FIXUP_HP_TOUCHSMART] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = ad1884_dmic_init_verbs, + .chained = true, + .chain_id = AD1884_FIXUP_HP_EAPD, + }, +}; + +static const struct snd_pci_quirk ad1884_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x2a82, "HP Touchsmart", AD1884_FIXUP_HP_TOUCHSMART), + SND_PCI_QUIRK_VENDOR(0x103c, "HP", AD1884_FIXUP_HP_EAPD), + SND_PCI_QUIRK_VENDOR(0x17aa, "Lenovo Thinkpad", AD1884_FIXUP_THINKPAD), + {} +}; + + +static int patch_ad1884(struct hda_codec *codec) +{ + struct ad198x_spec *spec; + int err; + + err = alloc_ad_spec(codec); + if (err < 0) + return err; + spec = codec->spec; + + spec->gen.mixer_nid = 0x20; + spec->gen.mixer_merge_nid = 0x21; + spec->gen.beep_nid = 0x10; + set_beep_amp(spec, 0x10, 0, HDA_OUTPUT); + + snd_hda_pick_fixup(codec, NULL, ad1884_fixup_tbl, ad1884_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + err = ad198x_parse_auto_config(codec, true); + if (err < 0) + goto error; + err = ad1983_add_spdif_mux_ctl(codec); + if (err < 0) + goto error; + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; + + error: + snd_hda_gen_free(codec); + return err; +} + +/* + * AD1882 / AD1882A + * + * port-A - front hp-out + * port-B - front mic-in + * port-C - rear line-in, shared surr-out (3stack) + * port-D - rear line-out + * port-E - rear mic-in, shared clfe-out (3stack) + * port-F - rear surr-out (6stack) + * port-G - rear clfe-out (6stack) + */ + +static int patch_ad1882(struct hda_codec *codec) +{ + struct ad198x_spec *spec; + int err; + + err = alloc_ad_spec(codec); + if (err < 0) + return err; + spec = codec->spec; + + spec->gen.mixer_nid = 0x20; + spec->gen.mixer_merge_nid = 0x21; + spec->gen.beep_nid = 0x10; + set_beep_amp(spec, 0x10, 0, HDA_OUTPUT); + err = ad198x_parse_auto_config(codec, true); + if (err < 0) + goto error; + err = ad1988_add_spdif_mux_ctl(codec); + if (err < 0) + goto error; + return 0; + + error: + snd_hda_gen_free(codec); + return err; +} + + +/* + * patch entries + */ +static const struct hda_device_id snd_hda_id_analog[] = { + HDA_CODEC_ENTRY(0x11d4184a, "AD1884A", patch_ad1884), + HDA_CODEC_ENTRY(0x11d41882, "AD1882", patch_ad1882), + HDA_CODEC_ENTRY(0x11d41883, "AD1883", patch_ad1884), + HDA_CODEC_ENTRY(0x11d41884, "AD1884", patch_ad1884), + HDA_CODEC_ENTRY(0x11d4194a, "AD1984A", patch_ad1884), + HDA_CODEC_ENTRY(0x11d4194b, "AD1984B", patch_ad1884), + HDA_CODEC_ENTRY(0x11d41981, "AD1981", patch_ad1981), + HDA_CODEC_ENTRY(0x11d41983, "AD1983", patch_ad1983), + HDA_CODEC_ENTRY(0x11d41984, "AD1984", patch_ad1884), + HDA_CODEC_ENTRY(0x11d41986, "AD1986A", patch_ad1986a), + HDA_CODEC_ENTRY(0x11d41988, "AD1988", patch_ad1988), + HDA_CODEC_ENTRY(0x11d4198b, "AD1988B", patch_ad1988), + HDA_CODEC_ENTRY(0x11d4882a, "AD1882A", patch_ad1882), + HDA_CODEC_ENTRY(0x11d4989a, "AD1989A", patch_ad1988), + HDA_CODEC_ENTRY(0x11d4989b, "AD1989B", patch_ad1988), + {} /* terminator */ +}; +MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_analog); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Analog Devices HD-audio codec"); + +static struct hda_codec_driver analog_driver = { + .id = snd_hda_id_analog, +}; + +module_hda_codec_driver(analog_driver); diff --git a/sound/pci/hda/patch_ca0110.c b/sound/pci/hda/patch_ca0110.c new file mode 100644 index 000000000..1818ce67f --- /dev/null +++ b/sound/pci/hda/patch_ca0110.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HD audio interface patch for Creative X-Fi CA0110-IBG chip + * + * Copyright (c) 2008 Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/hda_codec.h> +#include "hda_local.h" +#include "hda_auto_parser.h" +#include "hda_jack.h" +#include "hda_generic.h" + + +static const struct hda_codec_ops ca0110_patch_ops = { + .build_controls = snd_hda_gen_build_controls, + .build_pcms = snd_hda_gen_build_pcms, + .init = snd_hda_gen_init, + .free = snd_hda_gen_free, + .unsol_event = snd_hda_jack_unsol_event, +}; + +static int ca0110_parse_auto_config(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + int err; + + err = snd_hda_parse_pin_defcfg(codec, &spec->autocfg, NULL, 0); + if (err < 0) + return err; + err = snd_hda_gen_parse_auto_config(codec, &spec->autocfg); + if (err < 0) + return err; + + return 0; +} + + +static int patch_ca0110(struct hda_codec *codec) +{ + struct hda_gen_spec *spec; + int err; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return -ENOMEM; + snd_hda_gen_spec_init(spec); + codec->spec = spec; + codec->patch_ops = ca0110_patch_ops; + + spec->multi_cap_vol = 1; + codec->bus->core.needs_damn_long_delay = 1; + + err = ca0110_parse_auto_config(codec); + if (err < 0) + goto error; + + return 0; + + error: + snd_hda_gen_free(codec); + return err; +} + + +/* + * patch entries + */ +static const struct hda_device_id snd_hda_id_ca0110[] = { + HDA_CODEC_ENTRY(0x1102000a, "CA0110-IBG", patch_ca0110), + HDA_CODEC_ENTRY(0x1102000b, "CA0110-IBG", patch_ca0110), + HDA_CODEC_ENTRY(0x1102000d, "SB0880 X-Fi", patch_ca0110), + {} /* terminator */ +}; +MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_ca0110); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Creative CA0110-IBG HD-audio codec"); + +static struct hda_codec_driver ca0110_driver = { + .id = snd_hda_id_ca0110, +}; + +module_hda_codec_driver(ca0110_driver); diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c new file mode 100644 index 000000000..748a3c409 --- /dev/null +++ b/sound/pci/hda/patch_ca0132.c @@ -0,0 +1,10123 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HD audio interface patch for Creative CA0132 chip + * + * Copyright (c) 2011, Creative Technology Ltd. + * + * Based on patch_ca0110.c + * Copyright (c) 2008 Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/firmware.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/io.h> +#include <linux/pci.h> +#include <asm/io.h> +#include <sound/core.h> +#include <sound/hda_codec.h> +#include "hda_local.h" +#include "hda_auto_parser.h" +#include "hda_jack.h" + +#include "ca0132_regs.h" + +/* Enable this to see controls for tuning purpose. */ +/*#define ENABLE_TUNING_CONTROLS*/ + +#ifdef ENABLE_TUNING_CONTROLS +#include <sound/tlv.h> +#endif + +#define FLOAT_ZERO 0x00000000 +#define FLOAT_ONE 0x3f800000 +#define FLOAT_TWO 0x40000000 +#define FLOAT_THREE 0x40400000 +#define FLOAT_FIVE 0x40a00000 +#define FLOAT_SIX 0x40c00000 +#define FLOAT_EIGHT 0x41000000 +#define FLOAT_MINUS_5 0xc0a00000 + +#define UNSOL_TAG_DSP 0x16 + +#define DSP_DMA_WRITE_BUFLEN_INIT (1UL<<18) +#define DSP_DMA_WRITE_BUFLEN_OVLY (1UL<<15) + +#define DMA_TRANSFER_FRAME_SIZE_NWORDS 8 +#define DMA_TRANSFER_MAX_FRAME_SIZE_NWORDS 32 +#define DMA_OVERLAY_FRAME_SIZE_NWORDS 2 + +#define MASTERCONTROL 0x80 +#define MASTERCONTROL_ALLOC_DMA_CHAN 10 +#define MASTERCONTROL_QUERY_SPEAKER_EQ_ADDRESS 60 + +#define WIDGET_CHIP_CTRL 0x15 +#define WIDGET_DSP_CTRL 0x16 + +#define MEM_CONNID_MICIN1 3 +#define MEM_CONNID_MICIN2 5 +#define MEM_CONNID_MICOUT1 12 +#define MEM_CONNID_MICOUT2 14 +#define MEM_CONNID_WUH 10 +#define MEM_CONNID_DSP 16 +#define MEM_CONNID_DMIC 100 + +#define SCP_SET 0 +#define SCP_GET 1 + +#define EFX_FILE "ctefx.bin" +#define DESKTOP_EFX_FILE "ctefx-desktop.bin" +#define R3DI_EFX_FILE "ctefx-r3di.bin" + +#ifdef CONFIG_SND_HDA_CODEC_CA0132_DSP +MODULE_FIRMWARE(EFX_FILE); +MODULE_FIRMWARE(DESKTOP_EFX_FILE); +MODULE_FIRMWARE(R3DI_EFX_FILE); +#endif + +static const char *const dirstr[2] = { "Playback", "Capture" }; + +#define NUM_OF_OUTPUTS 2 +static const char *const out_type_str[2] = { "Speakers", "Headphone" }; +enum { + SPEAKER_OUT, + HEADPHONE_OUT, +}; + +enum { + DIGITAL_MIC, + LINE_MIC_IN +}; + +/* Strings for Input Source Enum Control */ +static const char *const in_src_str[3] = { "Microphone", "Line In", "Front Microphone" }; +#define IN_SRC_NUM_OF_INPUTS 3 +enum { + REAR_MIC, + REAR_LINE_IN, + FRONT_MIC, +}; + +enum { +#define VNODE_START_NID 0x80 + VNID_SPK = VNODE_START_NID, /* Speaker vnid */ + VNID_MIC, + VNID_HP_SEL, + VNID_AMIC1_SEL, + VNID_HP_ASEL, + VNID_AMIC1_ASEL, + VNODE_END_NID, +#define VNODES_COUNT (VNODE_END_NID - VNODE_START_NID) + +#define EFFECT_START_NID 0x90 +#define OUT_EFFECT_START_NID EFFECT_START_NID + SURROUND = OUT_EFFECT_START_NID, + CRYSTALIZER, + DIALOG_PLUS, + SMART_VOLUME, + X_BASS, + EQUALIZER, + OUT_EFFECT_END_NID, +#define OUT_EFFECTS_COUNT (OUT_EFFECT_END_NID - OUT_EFFECT_START_NID) + +#define IN_EFFECT_START_NID OUT_EFFECT_END_NID + ECHO_CANCELLATION = IN_EFFECT_START_NID, + VOICE_FOCUS, + MIC_SVM, + NOISE_REDUCTION, + IN_EFFECT_END_NID, +#define IN_EFFECTS_COUNT (IN_EFFECT_END_NID - IN_EFFECT_START_NID) + + VOICEFX = IN_EFFECT_END_NID, + PLAY_ENHANCEMENT, + CRYSTAL_VOICE, + EFFECT_END_NID, + OUTPUT_SOURCE_ENUM, + INPUT_SOURCE_ENUM, + XBASS_XOVER, + EQ_PRESET_ENUM, + SMART_VOLUME_ENUM, + MIC_BOOST_ENUM, + AE5_HEADPHONE_GAIN_ENUM, + AE5_SOUND_FILTER_ENUM, + ZXR_HEADPHONE_GAIN, + SPEAKER_CHANNEL_CFG_ENUM, + SPEAKER_FULL_RANGE_FRONT, + SPEAKER_FULL_RANGE_REAR, + BASS_REDIRECTION, + BASS_REDIRECTION_XOVER, +#define EFFECTS_COUNT (EFFECT_END_NID - EFFECT_START_NID) +}; + +/* Effects values size*/ +#define EFFECT_VALS_MAX_COUNT 12 + +/* + * Default values for the effect slider controls, they are in order of their + * effect NID's. Surround, Crystalizer, Dialog Plus, Smart Volume, and then + * X-bass. + */ +static const unsigned int effect_slider_defaults[] = {67, 65, 50, 74, 50}; +/* Amount of effect level sliders for ca0132_alt controls. */ +#define EFFECT_LEVEL_SLIDERS 5 + +/* Latency introduced by DSP blocks in milliseconds. */ +#define DSP_CAPTURE_INIT_LATENCY 0 +#define DSP_CRYSTAL_VOICE_LATENCY 124 +#define DSP_PLAYBACK_INIT_LATENCY 13 +#define DSP_PLAY_ENHANCEMENT_LATENCY 30 +#define DSP_SPEAKER_OUT_LATENCY 7 + +struct ct_effect { + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + hda_nid_t nid; + int mid; /*effect module ID*/ + int reqs[EFFECT_VALS_MAX_COUNT]; /*effect module request*/ + int direct; /* 0:output; 1:input*/ + int params; /* number of default non-on/off params */ + /*effect default values, 1st is on/off. */ + unsigned int def_vals[EFFECT_VALS_MAX_COUNT]; +}; + +#define EFX_DIR_OUT 0 +#define EFX_DIR_IN 1 + +static const struct ct_effect ca0132_effects[EFFECTS_COUNT] = { + { .name = "Surround", + .nid = SURROUND, + .mid = 0x96, + .reqs = {0, 1}, + .direct = EFX_DIR_OUT, + .params = 1, + .def_vals = {0x3F800000, 0x3F2B851F} + }, + { .name = "Crystalizer", + .nid = CRYSTALIZER, + .mid = 0x96, + .reqs = {7, 8}, + .direct = EFX_DIR_OUT, + .params = 1, + .def_vals = {0x3F800000, 0x3F266666} + }, + { .name = "Dialog Plus", + .nid = DIALOG_PLUS, + .mid = 0x96, + .reqs = {2, 3}, + .direct = EFX_DIR_OUT, + .params = 1, + .def_vals = {0x00000000, 0x3F000000} + }, + { .name = "Smart Volume", + .nid = SMART_VOLUME, + .mid = 0x96, + .reqs = {4, 5, 6}, + .direct = EFX_DIR_OUT, + .params = 2, + .def_vals = {0x3F800000, 0x3F3D70A4, 0x00000000} + }, + { .name = "X-Bass", + .nid = X_BASS, + .mid = 0x96, + .reqs = {24, 23, 25}, + .direct = EFX_DIR_OUT, + .params = 2, + .def_vals = {0x3F800000, 0x42A00000, 0x3F000000} + }, + { .name = "Equalizer", + .nid = EQUALIZER, + .mid = 0x96, + .reqs = {9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20}, + .direct = EFX_DIR_OUT, + .params = 11, + .def_vals = {0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000} + }, + { .name = "Echo Cancellation", + .nid = ECHO_CANCELLATION, + .mid = 0x95, + .reqs = {0, 1, 2, 3}, + .direct = EFX_DIR_IN, + .params = 3, + .def_vals = {0x00000000, 0x3F3A9692, 0x00000000, 0x00000000} + }, + { .name = "Voice Focus", + .nid = VOICE_FOCUS, + .mid = 0x95, + .reqs = {6, 7, 8, 9}, + .direct = EFX_DIR_IN, + .params = 3, + .def_vals = {0x3F800000, 0x3D7DF3B6, 0x41F00000, 0x41F00000} + }, + { .name = "Mic SVM", + .nid = MIC_SVM, + .mid = 0x95, + .reqs = {44, 45}, + .direct = EFX_DIR_IN, + .params = 1, + .def_vals = {0x00000000, 0x3F3D70A4} + }, + { .name = "Noise Reduction", + .nid = NOISE_REDUCTION, + .mid = 0x95, + .reqs = {4, 5}, + .direct = EFX_DIR_IN, + .params = 1, + .def_vals = {0x3F800000, 0x3F000000} + }, + { .name = "VoiceFX", + .nid = VOICEFX, + .mid = 0x95, + .reqs = {10, 11, 12, 13, 14, 15, 16, 17, 18}, + .direct = EFX_DIR_IN, + .params = 8, + .def_vals = {0x00000000, 0x43C80000, 0x44AF0000, 0x44FA0000, + 0x3F800000, 0x3F800000, 0x3F800000, 0x00000000, + 0x00000000} + } +}; + +/* Tuning controls */ +#ifdef ENABLE_TUNING_CONTROLS + +enum { +#define TUNING_CTL_START_NID 0xC0 + WEDGE_ANGLE = TUNING_CTL_START_NID, + SVM_LEVEL, + EQUALIZER_BAND_0, + EQUALIZER_BAND_1, + EQUALIZER_BAND_2, + EQUALIZER_BAND_3, + EQUALIZER_BAND_4, + EQUALIZER_BAND_5, + EQUALIZER_BAND_6, + EQUALIZER_BAND_7, + EQUALIZER_BAND_8, + EQUALIZER_BAND_9, + TUNING_CTL_END_NID +#define TUNING_CTLS_COUNT (TUNING_CTL_END_NID - TUNING_CTL_START_NID) +}; + +struct ct_tuning_ctl { + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + hda_nid_t parent_nid; + hda_nid_t nid; + int mid; /*effect module ID*/ + int req; /*effect module request*/ + int direct; /* 0:output; 1:input*/ + unsigned int def_val;/*effect default values*/ +}; + +static const struct ct_tuning_ctl ca0132_tuning_ctls[] = { + { .name = "Wedge Angle", + .parent_nid = VOICE_FOCUS, + .nid = WEDGE_ANGLE, + .mid = 0x95, + .req = 8, + .direct = EFX_DIR_IN, + .def_val = 0x41F00000 + }, + { .name = "SVM Level", + .parent_nid = MIC_SVM, + .nid = SVM_LEVEL, + .mid = 0x95, + .req = 45, + .direct = EFX_DIR_IN, + .def_val = 0x3F3D70A4 + }, + { .name = "EQ Band0", + .parent_nid = EQUALIZER, + .nid = EQUALIZER_BAND_0, + .mid = 0x96, + .req = 11, + .direct = EFX_DIR_OUT, + .def_val = 0x00000000 + }, + { .name = "EQ Band1", + .parent_nid = EQUALIZER, + .nid = EQUALIZER_BAND_1, + .mid = 0x96, + .req = 12, + .direct = EFX_DIR_OUT, + .def_val = 0x00000000 + }, + { .name = "EQ Band2", + .parent_nid = EQUALIZER, + .nid = EQUALIZER_BAND_2, + .mid = 0x96, + .req = 13, + .direct = EFX_DIR_OUT, + .def_val = 0x00000000 + }, + { .name = "EQ Band3", + .parent_nid = EQUALIZER, + .nid = EQUALIZER_BAND_3, + .mid = 0x96, + .req = 14, + .direct = EFX_DIR_OUT, + .def_val = 0x00000000 + }, + { .name = "EQ Band4", + .parent_nid = EQUALIZER, + .nid = EQUALIZER_BAND_4, + .mid = 0x96, + .req = 15, + .direct = EFX_DIR_OUT, + .def_val = 0x00000000 + }, + { .name = "EQ Band5", + .parent_nid = EQUALIZER, + .nid = EQUALIZER_BAND_5, + .mid = 0x96, + .req = 16, + .direct = EFX_DIR_OUT, + .def_val = 0x00000000 + }, + { .name = "EQ Band6", + .parent_nid = EQUALIZER, + .nid = EQUALIZER_BAND_6, + .mid = 0x96, + .req = 17, + .direct = EFX_DIR_OUT, + .def_val = 0x00000000 + }, + { .name = "EQ Band7", + .parent_nid = EQUALIZER, + .nid = EQUALIZER_BAND_7, + .mid = 0x96, + .req = 18, + .direct = EFX_DIR_OUT, + .def_val = 0x00000000 + }, + { .name = "EQ Band8", + .parent_nid = EQUALIZER, + .nid = EQUALIZER_BAND_8, + .mid = 0x96, + .req = 19, + .direct = EFX_DIR_OUT, + .def_val = 0x00000000 + }, + { .name = "EQ Band9", + .parent_nid = EQUALIZER, + .nid = EQUALIZER_BAND_9, + .mid = 0x96, + .req = 20, + .direct = EFX_DIR_OUT, + .def_val = 0x00000000 + } +}; +#endif + +/* Voice FX Presets */ +#define VOICEFX_MAX_PARAM_COUNT 9 + +struct ct_voicefx { + char *name; + hda_nid_t nid; + int mid; + int reqs[VOICEFX_MAX_PARAM_COUNT]; /*effect module request*/ +}; + +struct ct_voicefx_preset { + char *name; /*preset name*/ + unsigned int vals[VOICEFX_MAX_PARAM_COUNT]; +}; + +static const struct ct_voicefx ca0132_voicefx = { + .name = "VoiceFX Capture Switch", + .nid = VOICEFX, + .mid = 0x95, + .reqs = {10, 11, 12, 13, 14, 15, 16, 17, 18} +}; + +static const struct ct_voicefx_preset ca0132_voicefx_presets[] = { + { .name = "Neutral", + .vals = { 0x00000000, 0x43C80000, 0x44AF0000, + 0x44FA0000, 0x3F800000, 0x3F800000, + 0x3F800000, 0x00000000, 0x00000000 } + }, + { .name = "Female2Male", + .vals = { 0x3F800000, 0x43C80000, 0x44AF0000, + 0x44FA0000, 0x3F19999A, 0x3F866666, + 0x3F800000, 0x00000000, 0x00000000 } + }, + { .name = "Male2Female", + .vals = { 0x3F800000, 0x43C80000, 0x44AF0000, + 0x450AC000, 0x4017AE14, 0x3F6B851F, + 0x3F800000, 0x00000000, 0x00000000 } + }, + { .name = "ScrappyKid", + .vals = { 0x3F800000, 0x43C80000, 0x44AF0000, + 0x44FA0000, 0x40400000, 0x3F28F5C3, + 0x3F800000, 0x00000000, 0x00000000 } + }, + { .name = "Elderly", + .vals = { 0x3F800000, 0x44324000, 0x44BB8000, + 0x44E10000, 0x3FB33333, 0x3FB9999A, + 0x3F800000, 0x3E3A2E43, 0x00000000 } + }, + { .name = "Orc", + .vals = { 0x3F800000, 0x43EA0000, 0x44A52000, + 0x45098000, 0x3F266666, 0x3FC00000, + 0x3F800000, 0x00000000, 0x00000000 } + }, + { .name = "Elf", + .vals = { 0x3F800000, 0x43C70000, 0x44AE6000, + 0x45193000, 0x3F8E147B, 0x3F75C28F, + 0x3F800000, 0x00000000, 0x00000000 } + }, + { .name = "Dwarf", + .vals = { 0x3F800000, 0x43930000, 0x44BEE000, + 0x45007000, 0x3F451EB8, 0x3F7851EC, + 0x3F800000, 0x00000000, 0x00000000 } + }, + { .name = "AlienBrute", + .vals = { 0x3F800000, 0x43BFC5AC, 0x44B28FDF, + 0x451F6000, 0x3F266666, 0x3FA7D945, + 0x3F800000, 0x3CF5C28F, 0x00000000 } + }, + { .name = "Robot", + .vals = { 0x3F800000, 0x43C80000, 0x44AF0000, + 0x44FA0000, 0x3FB2718B, 0x3F800000, + 0xBC07010E, 0x00000000, 0x00000000 } + }, + { .name = "Marine", + .vals = { 0x3F800000, 0x43C20000, 0x44906000, + 0x44E70000, 0x3F4CCCCD, 0x3F8A3D71, + 0x3F0A3D71, 0x00000000, 0x00000000 } + }, + { .name = "Emo", + .vals = { 0x3F800000, 0x43C80000, 0x44AF0000, + 0x44FA0000, 0x3F800000, 0x3F800000, + 0x3E4CCCCD, 0x00000000, 0x00000000 } + }, + { .name = "DeepVoice", + .vals = { 0x3F800000, 0x43A9C5AC, 0x44AA4FDF, + 0x44FFC000, 0x3EDBB56F, 0x3F99C4CA, + 0x3F800000, 0x00000000, 0x00000000 } + }, + { .name = "Munchkin", + .vals = { 0x3F800000, 0x43C80000, 0x44AF0000, + 0x44FA0000, 0x3F800000, 0x3F1A043C, + 0x3F800000, 0x00000000, 0x00000000 } + } +}; + +/* ca0132 EQ presets, taken from Windows Sound Blaster Z Driver */ + +#define EQ_PRESET_MAX_PARAM_COUNT 11 + +struct ct_eq { + char *name; + hda_nid_t nid; + int mid; + int reqs[EQ_PRESET_MAX_PARAM_COUNT]; /*effect module request*/ +}; + +struct ct_eq_preset { + char *name; /*preset name*/ + unsigned int vals[EQ_PRESET_MAX_PARAM_COUNT]; +}; + +static const struct ct_eq ca0132_alt_eq_enum = { + .name = "FX: Equalizer Preset Switch", + .nid = EQ_PRESET_ENUM, + .mid = 0x96, + .reqs = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} +}; + + +static const struct ct_eq_preset ca0132_alt_eq_presets[] = { + { .name = "Flat", + .vals = { 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000 } + }, + { .name = "Acoustic", + .vals = { 0x00000000, 0x00000000, 0x3F8CCCCD, + 0x40000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x40000000, + 0x40000000, 0x40000000 } + }, + { .name = "Classical", + .vals = { 0x00000000, 0x00000000, 0x40C00000, + 0x40C00000, 0x40466666, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x40466666, 0x40466666 } + }, + { .name = "Country", + .vals = { 0x00000000, 0xBF99999A, 0x00000000, + 0x3FA66666, 0x3FA66666, 0x3F8CCCCD, + 0x00000000, 0x00000000, 0x40000000, + 0x40466666, 0x40800000 } + }, + { .name = "Dance", + .vals = { 0x00000000, 0xBF99999A, 0x40000000, + 0x40466666, 0x40866666, 0xBF99999A, + 0xBF99999A, 0x00000000, 0x00000000, + 0x40800000, 0x40800000 } + }, + { .name = "Jazz", + .vals = { 0x00000000, 0x00000000, 0x00000000, + 0x3F8CCCCD, 0x40800000, 0x40800000, + 0x40800000, 0x00000000, 0x3F8CCCCD, + 0x40466666, 0x40466666 } + }, + { .name = "New Age", + .vals = { 0x00000000, 0x00000000, 0x40000000, + 0x40000000, 0x00000000, 0x00000000, + 0x00000000, 0x3F8CCCCD, 0x40000000, + 0x40000000, 0x40000000 } + }, + { .name = "Pop", + .vals = { 0x00000000, 0xBFCCCCCD, 0x00000000, + 0x40000000, 0x40000000, 0x00000000, + 0xBF99999A, 0xBF99999A, 0x00000000, + 0x40466666, 0x40C00000 } + }, + { .name = "Rock", + .vals = { 0x00000000, 0xBF99999A, 0xBF99999A, + 0x3F8CCCCD, 0x40000000, 0xBF99999A, + 0xBF99999A, 0x00000000, 0x00000000, + 0x40800000, 0x40800000 } + }, + { .name = "Vocal", + .vals = { 0x00000000, 0xC0000000, 0xBF99999A, + 0xBF99999A, 0x00000000, 0x40466666, + 0x40800000, 0x40466666, 0x00000000, + 0x00000000, 0x3F8CCCCD } + } +}; + +/* + * DSP reqs for handling full-range speakers/bass redirection. If a speaker is + * set as not being full range, and bass redirection is enabled, all + * frequencies below the crossover frequency are redirected to the LFE + * channel. If the surround configuration has no LFE channel, this can't be + * enabled. X-Bass must be disabled when using these. + */ +enum speaker_range_reqs { + SPEAKER_BASS_REDIRECT = 0x15, + SPEAKER_BASS_REDIRECT_XOVER_FREQ = 0x16, + /* Between 0x16-0x1a are the X-Bass reqs. */ + SPEAKER_FULL_RANGE_FRONT_L_R = 0x1a, + SPEAKER_FULL_RANGE_CENTER_LFE = 0x1b, + SPEAKER_FULL_RANGE_REAR_L_R = 0x1c, + SPEAKER_FULL_RANGE_SURROUND_L_R = 0x1d, + SPEAKER_BASS_REDIRECT_SUB_GAIN = 0x1e, +}; + +/* + * Definitions for the DSP req's to handle speaker tuning. These all belong to + * module ID 0x96, the output effects module. + */ +enum speaker_tuning_reqs { + /* + * Currently, this value is always set to 0.0f. However, on Windows, + * when selecting certain headphone profiles on the new Sound Blaster + * connect software, the QUERY_SPEAKER_EQ_ADDRESS req on mid 0x80 is + * sent. This gets the speaker EQ address area, which is then used to + * send over (presumably) an equalizer profile for the specific + * headphone setup. It is sent using the same method the DSP + * firmware is uploaded with, which I believe is why the 'ctspeq.bin' + * file exists in linux firmware tree but goes unused. It would also + * explain why the QUERY_SPEAKER_EQ_ADDRESS req is defined but unused. + * Once this profile is sent over, SPEAKER_TUNING_USE_SPEAKER_EQ is + * set to 1.0f. + */ + SPEAKER_TUNING_USE_SPEAKER_EQ = 0x1f, + SPEAKER_TUNING_ENABLE_CENTER_EQ = 0x20, + SPEAKER_TUNING_FRONT_LEFT_VOL_LEVEL = 0x21, + SPEAKER_TUNING_FRONT_RIGHT_VOL_LEVEL = 0x22, + SPEAKER_TUNING_CENTER_VOL_LEVEL = 0x23, + SPEAKER_TUNING_LFE_VOL_LEVEL = 0x24, + SPEAKER_TUNING_REAR_LEFT_VOL_LEVEL = 0x25, + SPEAKER_TUNING_REAR_RIGHT_VOL_LEVEL = 0x26, + SPEAKER_TUNING_SURROUND_LEFT_VOL_LEVEL = 0x27, + SPEAKER_TUNING_SURROUND_RIGHT_VOL_LEVEL = 0x28, + /* + * Inversion is used when setting headphone virtualization to line + * out. Not sure why this is, but it's the only place it's ever used. + */ + SPEAKER_TUNING_FRONT_LEFT_INVERT = 0x29, + SPEAKER_TUNING_FRONT_RIGHT_INVERT = 0x2a, + SPEAKER_TUNING_CENTER_INVERT = 0x2b, + SPEAKER_TUNING_LFE_INVERT = 0x2c, + SPEAKER_TUNING_REAR_LEFT_INVERT = 0x2d, + SPEAKER_TUNING_REAR_RIGHT_INVERT = 0x2e, + SPEAKER_TUNING_SURROUND_LEFT_INVERT = 0x2f, + SPEAKER_TUNING_SURROUND_RIGHT_INVERT = 0x30, + /* Delay is used when setting surround speaker distance in Windows. */ + SPEAKER_TUNING_FRONT_LEFT_DELAY = 0x31, + SPEAKER_TUNING_FRONT_RIGHT_DELAY = 0x32, + SPEAKER_TUNING_CENTER_DELAY = 0x33, + SPEAKER_TUNING_LFE_DELAY = 0x34, + SPEAKER_TUNING_REAR_LEFT_DELAY = 0x35, + SPEAKER_TUNING_REAR_RIGHT_DELAY = 0x36, + SPEAKER_TUNING_SURROUND_LEFT_DELAY = 0x37, + SPEAKER_TUNING_SURROUND_RIGHT_DELAY = 0x38, + /* Of these two, only mute seems to ever be used. */ + SPEAKER_TUNING_MAIN_VOLUME = 0x39, + SPEAKER_TUNING_MUTE = 0x3a, +}; + +/* Surround output channel count configuration structures. */ +#define SPEAKER_CHANNEL_CFG_COUNT 5 +enum { + SPEAKER_CHANNELS_2_0, + SPEAKER_CHANNELS_2_1, + SPEAKER_CHANNELS_4_0, + SPEAKER_CHANNELS_4_1, + SPEAKER_CHANNELS_5_1, +}; + +struct ca0132_alt_speaker_channel_cfg { + char *name; + unsigned int val; +}; + +static const struct ca0132_alt_speaker_channel_cfg speaker_channel_cfgs[] = { + { .name = "2.0", + .val = FLOAT_ONE + }, + { .name = "2.1", + .val = FLOAT_TWO + }, + { .name = "4.0", + .val = FLOAT_FIVE + }, + { .name = "4.1", + .val = FLOAT_SIX + }, + { .name = "5.1", + .val = FLOAT_EIGHT + } +}; + +/* + * DSP volume setting structs. Req 1 is left volume, req 2 is right volume, + * and I don't know what the third req is, but it's always zero. I assume it's + * some sort of update or set command to tell the DSP there's new volume info. + */ +#define DSP_VOL_OUT 0 +#define DSP_VOL_IN 1 + +struct ct_dsp_volume_ctl { + hda_nid_t vnid; + int mid; /* module ID*/ + unsigned int reqs[3]; /* scp req ID */ +}; + +static const struct ct_dsp_volume_ctl ca0132_alt_vol_ctls[] = { + { .vnid = VNID_SPK, + .mid = 0x32, + .reqs = {3, 4, 2} + }, + { .vnid = VNID_MIC, + .mid = 0x37, + .reqs = {2, 3, 1} + } +}; + +/* Values for ca0113_mmio_command_set for selecting output. */ +#define AE_CA0113_OUT_SET_COMMANDS 6 +struct ae_ca0113_output_set { + unsigned int group[AE_CA0113_OUT_SET_COMMANDS]; + unsigned int target[AE_CA0113_OUT_SET_COMMANDS]; + unsigned int vals[NUM_OF_OUTPUTS][AE_CA0113_OUT_SET_COMMANDS]; +}; + +static const struct ae_ca0113_output_set ae5_ca0113_output_presets = { + .group = { 0x30, 0x30, 0x48, 0x48, 0x48, 0x30 }, + .target = { 0x2e, 0x30, 0x0d, 0x17, 0x19, 0x32 }, + /* Speakers. */ + .vals = { { 0x00, 0x00, 0x40, 0x00, 0x00, 0x3f }, + /* Headphones. */ + { 0x3f, 0x3f, 0x00, 0x00, 0x00, 0x00 } }, +}; + +static const struct ae_ca0113_output_set ae7_ca0113_output_presets = { + .group = { 0x30, 0x30, 0x48, 0x48, 0x48, 0x30 }, + .target = { 0x2e, 0x30, 0x0d, 0x17, 0x19, 0x32 }, + /* Speakers. */ + .vals = { { 0x00, 0x00, 0x40, 0x00, 0x00, 0x3f }, + /* Headphones. */ + { 0x3f, 0x3f, 0x00, 0x00, 0x02, 0x00 } }, +}; + +/* ae5 ca0113 command sequences to set headphone gain levels. */ +#define AE5_HEADPHONE_GAIN_PRESET_MAX_COMMANDS 4 +struct ae5_headphone_gain_set { + char *name; + unsigned int vals[AE5_HEADPHONE_GAIN_PRESET_MAX_COMMANDS]; +}; + +static const struct ae5_headphone_gain_set ae5_headphone_gain_presets[] = { + { .name = "Low (16-31", + .vals = { 0xff, 0x2c, 0xf5, 0x32 } + }, + { .name = "Medium (32-149", + .vals = { 0x38, 0xa8, 0x3e, 0x4c } + }, + { .name = "High (150-600", + .vals = { 0xff, 0xff, 0xff, 0x7f } + } +}; + +struct ae5_filter_set { + char *name; + unsigned int val; +}; + +static const struct ae5_filter_set ae5_filter_presets[] = { + { .name = "Slow Roll Off", + .val = 0xa0 + }, + { .name = "Minimum Phase", + .val = 0xc0 + }, + { .name = "Fast Roll Off", + .val = 0x80 + } +}; + +/* + * Data structures for storing audio router remapping data. These are used to + * remap a currently active streams ports. + */ +struct chipio_stream_remap_data { + unsigned int stream_id; + unsigned int count; + + unsigned int offset[16]; + unsigned int value[16]; +}; + +static const struct chipio_stream_remap_data stream_remap_data[] = { + { .stream_id = 0x14, + .count = 0x04, + .offset = { 0x00, 0x04, 0x08, 0x0c }, + .value = { 0x0001f8c0, 0x0001f9c1, 0x0001fac6, 0x0001fbc7 }, + }, + { .stream_id = 0x0c, + .count = 0x0c, + .offset = { 0x00, 0x04, 0x08, 0x0c, 0x10, 0x14, 0x18, 0x1c, + 0x20, 0x24, 0x28, 0x2c }, + .value = { 0x0001e0c0, 0x0001e1c1, 0x0001e4c2, 0x0001e5c3, + 0x0001e2c4, 0x0001e3c5, 0x0001e8c6, 0x0001e9c7, + 0x0001ecc8, 0x0001edc9, 0x0001eaca, 0x0001ebcb }, + }, + { .stream_id = 0x0c, + .count = 0x08, + .offset = { 0x08, 0x0c, 0x10, 0x14, 0x20, 0x24, 0x28, 0x2c }, + .value = { 0x000140c2, 0x000141c3, 0x000150c4, 0x000151c5, + 0x000142c8, 0x000143c9, 0x000152ca, 0x000153cb }, + } +}; + +enum hda_cmd_vendor_io { + /* for DspIO node */ + VENDOR_DSPIO_SCP_WRITE_DATA_LOW = 0x000, + VENDOR_DSPIO_SCP_WRITE_DATA_HIGH = 0x100, + + VENDOR_DSPIO_STATUS = 0xF01, + VENDOR_DSPIO_SCP_POST_READ_DATA = 0x702, + VENDOR_DSPIO_SCP_READ_DATA = 0xF02, + VENDOR_DSPIO_DSP_INIT = 0x703, + VENDOR_DSPIO_SCP_POST_COUNT_QUERY = 0x704, + VENDOR_DSPIO_SCP_READ_COUNT = 0xF04, + + /* for ChipIO node */ + VENDOR_CHIPIO_ADDRESS_LOW = 0x000, + VENDOR_CHIPIO_ADDRESS_HIGH = 0x100, + VENDOR_CHIPIO_STREAM_FORMAT = 0x200, + VENDOR_CHIPIO_DATA_LOW = 0x300, + VENDOR_CHIPIO_DATA_HIGH = 0x400, + + VENDOR_CHIPIO_8051_WRITE_DIRECT = 0x500, + VENDOR_CHIPIO_8051_READ_DIRECT = 0xD00, + + VENDOR_CHIPIO_GET_PARAMETER = 0xF00, + VENDOR_CHIPIO_STATUS = 0xF01, + VENDOR_CHIPIO_HIC_POST_READ = 0x702, + VENDOR_CHIPIO_HIC_READ_DATA = 0xF03, + + VENDOR_CHIPIO_8051_DATA_WRITE = 0x707, + VENDOR_CHIPIO_8051_DATA_READ = 0xF07, + VENDOR_CHIPIO_8051_PMEM_READ = 0xF08, + VENDOR_CHIPIO_8051_IRAM_WRITE = 0x709, + VENDOR_CHIPIO_8051_IRAM_READ = 0xF09, + + VENDOR_CHIPIO_CT_EXTENSIONS_ENABLE = 0x70A, + VENDOR_CHIPIO_CT_EXTENSIONS_GET = 0xF0A, + + VENDOR_CHIPIO_PLL_PMU_WRITE = 0x70C, + VENDOR_CHIPIO_PLL_PMU_READ = 0xF0C, + VENDOR_CHIPIO_8051_ADDRESS_LOW = 0x70D, + VENDOR_CHIPIO_8051_ADDRESS_HIGH = 0x70E, + VENDOR_CHIPIO_FLAG_SET = 0x70F, + VENDOR_CHIPIO_FLAGS_GET = 0xF0F, + VENDOR_CHIPIO_PARAM_SET = 0x710, + VENDOR_CHIPIO_PARAM_GET = 0xF10, + + VENDOR_CHIPIO_PORT_ALLOC_CONFIG_SET = 0x711, + VENDOR_CHIPIO_PORT_ALLOC_SET = 0x712, + VENDOR_CHIPIO_PORT_ALLOC_GET = 0xF12, + VENDOR_CHIPIO_PORT_FREE_SET = 0x713, + + VENDOR_CHIPIO_PARAM_EX_ID_GET = 0xF17, + VENDOR_CHIPIO_PARAM_EX_ID_SET = 0x717, + VENDOR_CHIPIO_PARAM_EX_VALUE_GET = 0xF18, + VENDOR_CHIPIO_PARAM_EX_VALUE_SET = 0x718, + + VENDOR_CHIPIO_DMIC_CTL_SET = 0x788, + VENDOR_CHIPIO_DMIC_CTL_GET = 0xF88, + VENDOR_CHIPIO_DMIC_PIN_SET = 0x789, + VENDOR_CHIPIO_DMIC_PIN_GET = 0xF89, + VENDOR_CHIPIO_DMIC_MCLK_SET = 0x78A, + VENDOR_CHIPIO_DMIC_MCLK_GET = 0xF8A, + + VENDOR_CHIPIO_EAPD_SEL_SET = 0x78D +}; + +/* + * Control flag IDs + */ +enum control_flag_id { + /* Connection manager stream setup is bypassed/enabled */ + CONTROL_FLAG_C_MGR = 0, + /* DSP DMA is bypassed/enabled */ + CONTROL_FLAG_DMA = 1, + /* 8051 'idle' mode is disabled/enabled */ + CONTROL_FLAG_IDLE_ENABLE = 2, + /* Tracker for the SPDIF-in path is bypassed/enabled */ + CONTROL_FLAG_TRACKER = 3, + /* DigitalOut to Spdif2Out connection is disabled/enabled */ + CONTROL_FLAG_SPDIF2OUT = 4, + /* Digital Microphone is disabled/enabled */ + CONTROL_FLAG_DMIC = 5, + /* ADC_B rate is 48 kHz/96 kHz */ + CONTROL_FLAG_ADC_B_96KHZ = 6, + /* ADC_C rate is 48 kHz/96 kHz */ + CONTROL_FLAG_ADC_C_96KHZ = 7, + /* DAC rate is 48 kHz/96 kHz (affects all DACs) */ + CONTROL_FLAG_DAC_96KHZ = 8, + /* DSP rate is 48 kHz/96 kHz */ + CONTROL_FLAG_DSP_96KHZ = 9, + /* SRC clock is 98 MHz/196 MHz (196 MHz forces rate to 96 KHz) */ + CONTROL_FLAG_SRC_CLOCK_196MHZ = 10, + /* SRC rate is 48 kHz/96 kHz (48 kHz disabled when clock is 196 MHz) */ + CONTROL_FLAG_SRC_RATE_96KHZ = 11, + /* Decode Loop (DSP->SRC->DSP) is disabled/enabled */ + CONTROL_FLAG_DECODE_LOOP = 12, + /* De-emphasis filter on DAC-1 disabled/enabled */ + CONTROL_FLAG_DAC1_DEEMPHASIS = 13, + /* De-emphasis filter on DAC-2 disabled/enabled */ + CONTROL_FLAG_DAC2_DEEMPHASIS = 14, + /* De-emphasis filter on DAC-3 disabled/enabled */ + CONTROL_FLAG_DAC3_DEEMPHASIS = 15, + /* High-pass filter on ADC_B disabled/enabled */ + CONTROL_FLAG_ADC_B_HIGH_PASS = 16, + /* High-pass filter on ADC_C disabled/enabled */ + CONTROL_FLAG_ADC_C_HIGH_PASS = 17, + /* Common mode on Port_A disabled/enabled */ + CONTROL_FLAG_PORT_A_COMMON_MODE = 18, + /* Common mode on Port_D disabled/enabled */ + CONTROL_FLAG_PORT_D_COMMON_MODE = 19, + /* Impedance for ramp generator on Port_A 16 Ohm/10K Ohm */ + CONTROL_FLAG_PORT_A_10KOHM_LOAD = 20, + /* Impedance for ramp generator on Port_D, 16 Ohm/10K Ohm */ + CONTROL_FLAG_PORT_D_10KOHM_LOAD = 21, + /* ASI rate is 48kHz/96kHz */ + CONTROL_FLAG_ASI_96KHZ = 22, + /* DAC power settings able to control attached ports no/yes */ + CONTROL_FLAG_DACS_CONTROL_PORTS = 23, + /* Clock Stop OK reporting is disabled/enabled */ + CONTROL_FLAG_CONTROL_STOP_OK_ENABLE = 24, + /* Number of control flags */ + CONTROL_FLAGS_MAX = (CONTROL_FLAG_CONTROL_STOP_OK_ENABLE+1) +}; + +/* + * Control parameter IDs + */ +enum control_param_id { + /* 0: None, 1: Mic1In*/ + CONTROL_PARAM_VIP_SOURCE = 1, + /* 0: force HDA, 1: allow DSP if HDA Spdif1Out stream is idle */ + CONTROL_PARAM_SPDIF1_SOURCE = 2, + /* Port A output stage gain setting to use when 16 Ohm output + * impedance is selected*/ + CONTROL_PARAM_PORTA_160OHM_GAIN = 8, + /* Port D output stage gain setting to use when 16 Ohm output + * impedance is selected*/ + CONTROL_PARAM_PORTD_160OHM_GAIN = 10, + + /* + * This control param name was found in the 8051 memory, and makes + * sense given the fact the AE-5 uses it and has the ASI flag set. + */ + CONTROL_PARAM_ASI = 23, + + /* Stream Control */ + + /* Select stream with the given ID */ + CONTROL_PARAM_STREAM_ID = 24, + /* Source connection point for the selected stream */ + CONTROL_PARAM_STREAM_SOURCE_CONN_POINT = 25, + /* Destination connection point for the selected stream */ + CONTROL_PARAM_STREAM_DEST_CONN_POINT = 26, + /* Number of audio channels in the selected stream */ + CONTROL_PARAM_STREAMS_CHANNELS = 27, + /*Enable control for the selected stream */ + CONTROL_PARAM_STREAM_CONTROL = 28, + + /* Connection Point Control */ + + /* Select connection point with the given ID */ + CONTROL_PARAM_CONN_POINT_ID = 29, + /* Connection point sample rate */ + CONTROL_PARAM_CONN_POINT_SAMPLE_RATE = 30, + + /* Node Control */ + + /* Select HDA node with the given ID */ + CONTROL_PARAM_NODE_ID = 31 +}; + +/* + * Dsp Io Status codes + */ +enum hda_vendor_status_dspio { + /* Success */ + VENDOR_STATUS_DSPIO_OK = 0x00, + /* Busy, unable to accept new command, the host must retry */ + VENDOR_STATUS_DSPIO_BUSY = 0x01, + /* SCP command queue is full */ + VENDOR_STATUS_DSPIO_SCP_COMMAND_QUEUE_FULL = 0x02, + /* SCP response queue is empty */ + VENDOR_STATUS_DSPIO_SCP_RESPONSE_QUEUE_EMPTY = 0x03 +}; + +/* + * Chip Io Status codes + */ +enum hda_vendor_status_chipio { + /* Success */ + VENDOR_STATUS_CHIPIO_OK = 0x00, + /* Busy, unable to accept new command, the host must retry */ + VENDOR_STATUS_CHIPIO_BUSY = 0x01 +}; + +/* + * CA0132 sample rate + */ +enum ca0132_sample_rate { + SR_6_000 = 0x00, + SR_8_000 = 0x01, + SR_9_600 = 0x02, + SR_11_025 = 0x03, + SR_16_000 = 0x04, + SR_22_050 = 0x05, + SR_24_000 = 0x06, + SR_32_000 = 0x07, + SR_44_100 = 0x08, + SR_48_000 = 0x09, + SR_88_200 = 0x0A, + SR_96_000 = 0x0B, + SR_144_000 = 0x0C, + SR_176_400 = 0x0D, + SR_192_000 = 0x0E, + SR_384_000 = 0x0F, + + SR_COUNT = 0x10, + + SR_RATE_UNKNOWN = 0x1F +}; + +enum dsp_download_state { + DSP_DOWNLOAD_FAILED = -1, + DSP_DOWNLOAD_INIT = 0, + DSP_DOWNLOADING = 1, + DSP_DOWNLOADED = 2 +}; + +/* retrieve parameters from hda format */ +#define get_hdafmt_chs(fmt) (fmt & 0xf) +#define get_hdafmt_bits(fmt) ((fmt >> 4) & 0x7) +#define get_hdafmt_rate(fmt) ((fmt >> 8) & 0x7f) +#define get_hdafmt_type(fmt) ((fmt >> 15) & 0x1) + +/* + * CA0132 specific + */ + +struct ca0132_spec { + const struct snd_kcontrol_new *mixers[5]; + unsigned int num_mixers; + const struct hda_verb *base_init_verbs; + const struct hda_verb *base_exit_verbs; + const struct hda_verb *chip_init_verbs; + const struct hda_verb *desktop_init_verbs; + struct hda_verb *spec_init_verbs; + struct auto_pin_cfg autocfg; + + /* Nodes configurations */ + struct hda_multi_out multiout; + hda_nid_t out_pins[AUTO_CFG_MAX_OUTS]; + hda_nid_t dacs[AUTO_CFG_MAX_OUTS]; + unsigned int num_outputs; + hda_nid_t input_pins[AUTO_PIN_LAST]; + hda_nid_t adcs[AUTO_PIN_LAST]; + hda_nid_t dig_out; + hda_nid_t dig_in; + unsigned int num_inputs; + hda_nid_t shared_mic_nid; + hda_nid_t shared_out_nid; + hda_nid_t unsol_tag_hp; + hda_nid_t unsol_tag_front_hp; /* for desktop ca0132 codecs */ + hda_nid_t unsol_tag_amic1; + + /* chip access */ + struct mutex chipio_mutex; /* chip access mutex */ + u32 curr_chip_addx; + + /* DSP download related */ + enum dsp_download_state dsp_state; + unsigned int dsp_stream_id; + unsigned int wait_scp; + unsigned int wait_scp_header; + unsigned int wait_num_data; + unsigned int scp_resp_header; + unsigned int scp_resp_data[4]; + unsigned int scp_resp_count; + bool startup_check_entered; + bool dsp_reload; + + /* mixer and effects related */ + unsigned char dmic_ctl; + int cur_out_type; + int cur_mic_type; + long vnode_lvol[VNODES_COUNT]; + long vnode_rvol[VNODES_COUNT]; + long vnode_lswitch[VNODES_COUNT]; + long vnode_rswitch[VNODES_COUNT]; + long effects_switch[EFFECTS_COUNT]; + long voicefx_val; + long cur_mic_boost; + /* ca0132_alt control related values */ + unsigned char in_enum_val; + unsigned char out_enum_val; + unsigned char channel_cfg_val; + unsigned char speaker_range_val[2]; + unsigned char mic_boost_enum_val; + unsigned char smart_volume_setting; + unsigned char bass_redirection_val; + long bass_redirect_xover_freq; + long fx_ctl_val[EFFECT_LEVEL_SLIDERS]; + long xbass_xover_freq; + long eq_preset_val; + unsigned int tlv[4]; + struct hda_vmaster_mute_hook vmaster_mute; + /* AE-5 Control values */ + unsigned char ae5_headphone_gain_val; + unsigned char ae5_filter_val; + /* ZxR Control Values */ + unsigned char zxr_gain_set; + + struct hda_codec *codec; + struct delayed_work unsol_hp_work; + int quirk; + +#ifdef ENABLE_TUNING_CONTROLS + long cur_ctl_vals[TUNING_CTLS_COUNT]; +#endif + /* + * The Recon3D, Sound Blaster Z, Sound Blaster ZxR, and Sound Blaster + * AE-5 all use PCI region 2 to toggle GPIO and other currently unknown + * things. + */ + bool use_pci_mmio; + void __iomem *mem_base; + + /* + * Whether or not to use the alt functions like alt_select_out, + * alt_select_in, etc. Only used on desktop codecs for now, because of + * surround sound support. + */ + bool use_alt_functions; + + /* + * Whether or not to use alt controls: volume effect sliders, EQ + * presets, smart volume presets, and new control names with FX prefix. + * Renames PlayEnhancement and CrystalVoice too. + */ + bool use_alt_controls; +}; + +/* + * CA0132 quirks table + */ +enum { + QUIRK_NONE, + QUIRK_ALIENWARE, + QUIRK_ALIENWARE_M17XR4, + QUIRK_SBZ, + QUIRK_ZXR, + QUIRK_ZXR_DBPRO, + QUIRK_R3DI, + QUIRK_R3D, + QUIRK_AE5, + QUIRK_AE7, +}; + +#ifdef CONFIG_PCI +#define ca0132_quirk(spec) ((spec)->quirk) +#define ca0132_use_pci_mmio(spec) ((spec)->use_pci_mmio) +#define ca0132_use_alt_functions(spec) ((spec)->use_alt_functions) +#define ca0132_use_alt_controls(spec) ((spec)->use_alt_controls) +#else +#define ca0132_quirk(spec) ({ (void)(spec); QUIRK_NONE; }) +#define ca0132_use_alt_functions(spec) ({ (void)(spec); false; }) +#define ca0132_use_pci_mmio(spec) ({ (void)(spec); false; }) +#define ca0132_use_alt_controls(spec) ({ (void)(spec); false; }) +#endif + +static const struct hda_pintbl alienware_pincfgs[] = { + { 0x0b, 0x90170110 }, /* Builtin Speaker */ + { 0x0c, 0x411111f0 }, /* N/A */ + { 0x0d, 0x411111f0 }, /* N/A */ + { 0x0e, 0x411111f0 }, /* N/A */ + { 0x0f, 0x0321101f }, /* HP */ + { 0x10, 0x411111f0 }, /* Headset? disabled for now */ + { 0x11, 0x03a11021 }, /* Mic */ + { 0x12, 0xd5a30140 }, /* Builtin Mic */ + { 0x13, 0x411111f0 }, /* N/A */ + { 0x18, 0x411111f0 }, /* N/A */ + {} +}; + +/* Sound Blaster Z pin configs taken from Windows Driver */ +static const struct hda_pintbl sbz_pincfgs[] = { + { 0x0b, 0x01017010 }, /* Port G -- Lineout FRONT L/R */ + { 0x0c, 0x014510f0 }, /* SPDIF Out 1 */ + { 0x0d, 0x014510f0 }, /* Digital Out */ + { 0x0e, 0x01c510f0 }, /* SPDIF In */ + { 0x0f, 0x0221701f }, /* Port A -- BackPanel HP */ + { 0x10, 0x01017012 }, /* Port D -- Center/LFE or FP Hp */ + { 0x11, 0x01017014 }, /* Port B -- LineMicIn2 / Rear L/R */ + { 0x12, 0x01a170f0 }, /* Port C -- LineIn1 */ + { 0x13, 0x908700f0 }, /* What U Hear In*/ + { 0x18, 0x50d000f0 }, /* N/A */ + {} +}; + +/* Sound Blaster ZxR pin configs taken from Windows Driver */ +static const struct hda_pintbl zxr_pincfgs[] = { + { 0x0b, 0x01047110 }, /* Port G -- Lineout FRONT L/R */ + { 0x0c, 0x414510f0 }, /* SPDIF Out 1 - Disabled*/ + { 0x0d, 0x014510f0 }, /* Digital Out */ + { 0x0e, 0x41c520f0 }, /* SPDIF In - Disabled*/ + { 0x0f, 0x0122711f }, /* Port A -- BackPanel HP */ + { 0x10, 0x01017111 }, /* Port D -- Center/LFE */ + { 0x11, 0x01017114 }, /* Port B -- LineMicIn2 / Rear L/R */ + { 0x12, 0x01a271f0 }, /* Port C -- LineIn1 */ + { 0x13, 0x908700f0 }, /* What U Hear In*/ + { 0x18, 0x50d000f0 }, /* N/A */ + {} +}; + +/* Recon3D pin configs taken from Windows Driver */ +static const struct hda_pintbl r3d_pincfgs[] = { + { 0x0b, 0x01014110 }, /* Port G -- Lineout FRONT L/R */ + { 0x0c, 0x014510f0 }, /* SPDIF Out 1 */ + { 0x0d, 0x014510f0 }, /* Digital Out */ + { 0x0e, 0x01c520f0 }, /* SPDIF In */ + { 0x0f, 0x0221401f }, /* Port A -- BackPanel HP */ + { 0x10, 0x01016011 }, /* Port D -- Center/LFE or FP Hp */ + { 0x11, 0x01011014 }, /* Port B -- LineMicIn2 / Rear L/R */ + { 0x12, 0x02a090f0 }, /* Port C -- LineIn1 */ + { 0x13, 0x908700f0 }, /* What U Hear In*/ + { 0x18, 0x50d000f0 }, /* N/A */ + {} +}; + +/* Sound Blaster AE-5 pin configs taken from Windows Driver */ +static const struct hda_pintbl ae5_pincfgs[] = { + { 0x0b, 0x01017010 }, /* Port G -- Lineout FRONT L/R */ + { 0x0c, 0x014510f0 }, /* SPDIF Out 1 */ + { 0x0d, 0x014510f0 }, /* Digital Out */ + { 0x0e, 0x01c510f0 }, /* SPDIF In */ + { 0x0f, 0x01017114 }, /* Port A -- Rear L/R. */ + { 0x10, 0x01017012 }, /* Port D -- Center/LFE or FP Hp */ + { 0x11, 0x012170ff }, /* Port B -- LineMicIn2 / Rear Headphone */ + { 0x12, 0x01a170f0 }, /* Port C -- LineIn1 */ + { 0x13, 0x908700f0 }, /* What U Hear In*/ + { 0x18, 0x50d000f0 }, /* N/A */ + {} +}; + +/* Recon3D integrated pin configs taken from Windows Driver */ +static const struct hda_pintbl r3di_pincfgs[] = { + { 0x0b, 0x01014110 }, /* Port G -- Lineout FRONT L/R */ + { 0x0c, 0x014510f0 }, /* SPDIF Out 1 */ + { 0x0d, 0x014510f0 }, /* Digital Out */ + { 0x0e, 0x41c520f0 }, /* SPDIF In */ + { 0x0f, 0x0221401f }, /* Port A -- BackPanel HP */ + { 0x10, 0x01016011 }, /* Port D -- Center/LFE or FP Hp */ + { 0x11, 0x01011014 }, /* Port B -- LineMicIn2 / Rear L/R */ + { 0x12, 0x02a090f0 }, /* Port C -- LineIn1 */ + { 0x13, 0x908700f0 }, /* What U Hear In*/ + { 0x18, 0x500000f0 }, /* N/A */ + {} +}; + +static const struct hda_pintbl ae7_pincfgs[] = { + { 0x0b, 0x01017010 }, + { 0x0c, 0x014510f0 }, + { 0x0d, 0x414510f0 }, + { 0x0e, 0x01c520f0 }, + { 0x0f, 0x01017114 }, + { 0x10, 0x01017011 }, + { 0x11, 0x018170ff }, + { 0x12, 0x01a170f0 }, + { 0x13, 0x908700f0 }, + { 0x18, 0x500000f0 }, + {} +}; + +static const struct snd_pci_quirk ca0132_quirks[] = { + SND_PCI_QUIRK(0x1028, 0x057b, "Alienware M17x R4", QUIRK_ALIENWARE_M17XR4), + SND_PCI_QUIRK(0x1028, 0x0685, "Alienware 15 2015", QUIRK_ALIENWARE), + SND_PCI_QUIRK(0x1028, 0x0688, "Alienware 17 2015", QUIRK_ALIENWARE), + SND_PCI_QUIRK(0x1028, 0x0708, "Alienware 15 R2 2016", QUIRK_ALIENWARE), + SND_PCI_QUIRK(0x1102, 0x0010, "Sound Blaster Z", QUIRK_SBZ), + SND_PCI_QUIRK(0x1102, 0x0023, "Sound Blaster Z", QUIRK_SBZ), + SND_PCI_QUIRK(0x1102, 0x0027, "Sound Blaster Z", QUIRK_SBZ), + SND_PCI_QUIRK(0x1102, 0x0033, "Sound Blaster ZxR", QUIRK_SBZ), + SND_PCI_QUIRK(0x1458, 0xA016, "Recon3Di", QUIRK_R3DI), + SND_PCI_QUIRK(0x1458, 0xA026, "Gigabyte G1.Sniper Z97", QUIRK_R3DI), + SND_PCI_QUIRK(0x1458, 0xA036, "Gigabyte GA-Z170X-Gaming 7", QUIRK_R3DI), + SND_PCI_QUIRK(0x3842, 0x1038, "EVGA X99 Classified", QUIRK_R3DI), + SND_PCI_QUIRK(0x3842, 0x104b, "EVGA X299 Dark", QUIRK_R3DI), + SND_PCI_QUIRK(0x3842, 0x1055, "EVGA Z390 DARK", QUIRK_R3DI), + SND_PCI_QUIRK(0x1102, 0x0013, "Recon3D", QUIRK_R3D), + SND_PCI_QUIRK(0x1102, 0x0018, "Recon3D", QUIRK_R3D), + SND_PCI_QUIRK(0x1102, 0x0051, "Sound Blaster AE-5", QUIRK_AE5), + SND_PCI_QUIRK(0x1102, 0x0191, "Sound Blaster AE-5 Plus", QUIRK_AE5), + SND_PCI_QUIRK(0x1102, 0x0081, "Sound Blaster AE-7", QUIRK_AE7), + {} +}; + +/* Output selection quirk info structures. */ +#define MAX_QUIRK_MMIO_GPIO_SET_VALS 3 +#define MAX_QUIRK_SCP_SET_VALS 2 +struct ca0132_alt_out_set_info { + unsigned int dac2port; /* ParamID 0x0d value. */ + + bool has_hda_gpio; + char hda_gpio_pin; + char hda_gpio_set; + + unsigned int mmio_gpio_count; + char mmio_gpio_pin[MAX_QUIRK_MMIO_GPIO_SET_VALS]; + char mmio_gpio_set[MAX_QUIRK_MMIO_GPIO_SET_VALS]; + + unsigned int scp_cmds_count; + unsigned int scp_cmd_mid[MAX_QUIRK_SCP_SET_VALS]; + unsigned int scp_cmd_req[MAX_QUIRK_SCP_SET_VALS]; + unsigned int scp_cmd_val[MAX_QUIRK_SCP_SET_VALS]; + + bool has_chipio_write; + unsigned int chipio_write_addr; + unsigned int chipio_write_data; +}; + +struct ca0132_alt_out_set_quirk_data { + int quirk_id; + + bool has_headphone_gain; + bool is_ae_series; + + struct ca0132_alt_out_set_info out_set_info[NUM_OF_OUTPUTS]; +}; + +static const struct ca0132_alt_out_set_quirk_data quirk_out_set_data[] = { + { .quirk_id = QUIRK_R3DI, + .has_headphone_gain = false, + .is_ae_series = false, + .out_set_info = { + /* Speakers. */ + { .dac2port = 0x24, + .has_hda_gpio = true, + .hda_gpio_pin = 2, + .hda_gpio_set = 1, + .mmio_gpio_count = 0, + .scp_cmds_count = 0, + .has_chipio_write = false, + }, + /* Headphones. */ + { .dac2port = 0x21, + .has_hda_gpio = true, + .hda_gpio_pin = 2, + .hda_gpio_set = 0, + .mmio_gpio_count = 0, + .scp_cmds_count = 0, + .has_chipio_write = false, + } }, + }, + { .quirk_id = QUIRK_R3D, + .has_headphone_gain = false, + .is_ae_series = false, + .out_set_info = { + /* Speakers. */ + { .dac2port = 0x24, + .has_hda_gpio = false, + .mmio_gpio_count = 1, + .mmio_gpio_pin = { 1 }, + .mmio_gpio_set = { 1 }, + .scp_cmds_count = 0, + .has_chipio_write = false, + }, + /* Headphones. */ + { .dac2port = 0x21, + .has_hda_gpio = false, + .mmio_gpio_count = 1, + .mmio_gpio_pin = { 1 }, + .mmio_gpio_set = { 0 }, + .scp_cmds_count = 0, + .has_chipio_write = false, + } }, + }, + { .quirk_id = QUIRK_SBZ, + .has_headphone_gain = false, + .is_ae_series = false, + .out_set_info = { + /* Speakers. */ + { .dac2port = 0x18, + .has_hda_gpio = false, + .mmio_gpio_count = 3, + .mmio_gpio_pin = { 7, 4, 1 }, + .mmio_gpio_set = { 0, 1, 1 }, + .scp_cmds_count = 0, + .has_chipio_write = false, }, + /* Headphones. */ + { .dac2port = 0x12, + .has_hda_gpio = false, + .mmio_gpio_count = 3, + .mmio_gpio_pin = { 7, 4, 1 }, + .mmio_gpio_set = { 1, 1, 0 }, + .scp_cmds_count = 0, + .has_chipio_write = false, + } }, + }, + { .quirk_id = QUIRK_ZXR, + .has_headphone_gain = true, + .is_ae_series = false, + .out_set_info = { + /* Speakers. */ + { .dac2port = 0x24, + .has_hda_gpio = false, + .mmio_gpio_count = 3, + .mmio_gpio_pin = { 2, 3, 5 }, + .mmio_gpio_set = { 1, 1, 0 }, + .scp_cmds_count = 0, + .has_chipio_write = false, + }, + /* Headphones. */ + { .dac2port = 0x21, + .has_hda_gpio = false, + .mmio_gpio_count = 3, + .mmio_gpio_pin = { 2, 3, 5 }, + .mmio_gpio_set = { 0, 1, 1 }, + .scp_cmds_count = 0, + .has_chipio_write = false, + } }, + }, + { .quirk_id = QUIRK_AE5, + .has_headphone_gain = true, + .is_ae_series = true, + .out_set_info = { + /* Speakers. */ + { .dac2port = 0xa4, + .has_hda_gpio = false, + .mmio_gpio_count = 0, + .scp_cmds_count = 2, + .scp_cmd_mid = { 0x96, 0x96 }, + .scp_cmd_req = { SPEAKER_TUNING_FRONT_LEFT_INVERT, + SPEAKER_TUNING_FRONT_RIGHT_INVERT }, + .scp_cmd_val = { FLOAT_ZERO, FLOAT_ZERO }, + .has_chipio_write = true, + .chipio_write_addr = 0x0018b03c, + .chipio_write_data = 0x00000012 + }, + /* Headphones. */ + { .dac2port = 0xa1, + .has_hda_gpio = false, + .mmio_gpio_count = 0, + .scp_cmds_count = 2, + .scp_cmd_mid = { 0x96, 0x96 }, + .scp_cmd_req = { SPEAKER_TUNING_FRONT_LEFT_INVERT, + SPEAKER_TUNING_FRONT_RIGHT_INVERT }, + .scp_cmd_val = { FLOAT_ONE, FLOAT_ONE }, + .has_chipio_write = true, + .chipio_write_addr = 0x0018b03c, + .chipio_write_data = 0x00000012 + } }, + }, + { .quirk_id = QUIRK_AE7, + .has_headphone_gain = true, + .is_ae_series = true, + .out_set_info = { + /* Speakers. */ + { .dac2port = 0x58, + .has_hda_gpio = false, + .mmio_gpio_count = 1, + .mmio_gpio_pin = { 0 }, + .mmio_gpio_set = { 1 }, + .scp_cmds_count = 2, + .scp_cmd_mid = { 0x96, 0x96 }, + .scp_cmd_req = { SPEAKER_TUNING_FRONT_LEFT_INVERT, + SPEAKER_TUNING_FRONT_RIGHT_INVERT }, + .scp_cmd_val = { FLOAT_ZERO, FLOAT_ZERO }, + .has_chipio_write = true, + .chipio_write_addr = 0x0018b03c, + .chipio_write_data = 0x00000000 + }, + /* Headphones. */ + { .dac2port = 0x58, + .has_hda_gpio = false, + .mmio_gpio_count = 1, + .mmio_gpio_pin = { 0 }, + .mmio_gpio_set = { 1 }, + .scp_cmds_count = 2, + .scp_cmd_mid = { 0x96, 0x96 }, + .scp_cmd_req = { SPEAKER_TUNING_FRONT_LEFT_INVERT, + SPEAKER_TUNING_FRONT_RIGHT_INVERT }, + .scp_cmd_val = { FLOAT_ONE, FLOAT_ONE }, + .has_chipio_write = true, + .chipio_write_addr = 0x0018b03c, + .chipio_write_data = 0x00000010 + } }, + } +}; + +/* + * CA0132 codec access + */ +static unsigned int codec_send_command(struct hda_codec *codec, hda_nid_t nid, + unsigned int verb, unsigned int parm, unsigned int *res) +{ + unsigned int response; + response = snd_hda_codec_read(codec, nid, 0, verb, parm); + *res = response; + + return ((response == -1) ? -1 : 0); +} + +static int codec_set_converter_format(struct hda_codec *codec, hda_nid_t nid, + unsigned short converter_format, unsigned int *res) +{ + return codec_send_command(codec, nid, VENDOR_CHIPIO_STREAM_FORMAT, + converter_format & 0xffff, res); +} + +static int codec_set_converter_stream_channel(struct hda_codec *codec, + hda_nid_t nid, unsigned char stream, + unsigned char channel, unsigned int *res) +{ + unsigned char converter_stream_channel = 0; + + converter_stream_channel = (stream << 4) | (channel & 0x0f); + return codec_send_command(codec, nid, AC_VERB_SET_CHANNEL_STREAMID, + converter_stream_channel, res); +} + +/* Chip access helper function */ +static int chipio_send(struct hda_codec *codec, + unsigned int reg, + unsigned int data) +{ + unsigned int res; + unsigned long timeout = jiffies + msecs_to_jiffies(1000); + + /* send bits of data specified by reg */ + do { + res = snd_hda_codec_read(codec, WIDGET_CHIP_CTRL, 0, + reg, data); + if (res == VENDOR_STATUS_CHIPIO_OK) + return 0; + msleep(20); + } while (time_before(jiffies, timeout)); + + return -EIO; +} + +/* + * Write chip address through the vendor widget -- NOT protected by the Mutex! + */ +static int chipio_write_address(struct hda_codec *codec, + unsigned int chip_addx) +{ + struct ca0132_spec *spec = codec->spec; + int res; + + if (spec->curr_chip_addx == chip_addx) + return 0; + + /* send low 16 bits of the address */ + res = chipio_send(codec, VENDOR_CHIPIO_ADDRESS_LOW, + chip_addx & 0xffff); + + if (res != -EIO) { + /* send high 16 bits of the address */ + res = chipio_send(codec, VENDOR_CHIPIO_ADDRESS_HIGH, + chip_addx >> 16); + } + + spec->curr_chip_addx = (res < 0) ? ~0U : chip_addx; + + return res; +} + +/* + * Write data through the vendor widget -- NOT protected by the Mutex! + */ +static int chipio_write_data(struct hda_codec *codec, unsigned int data) +{ + struct ca0132_spec *spec = codec->spec; + int res; + + /* send low 16 bits of the data */ + res = chipio_send(codec, VENDOR_CHIPIO_DATA_LOW, data & 0xffff); + + if (res != -EIO) { + /* send high 16 bits of the data */ + res = chipio_send(codec, VENDOR_CHIPIO_DATA_HIGH, + data >> 16); + } + + /*If no error encountered, automatically increment the address + as per chip behaviour*/ + spec->curr_chip_addx = (res != -EIO) ? + (spec->curr_chip_addx + 4) : ~0U; + return res; +} + +/* + * Write multiple data through the vendor widget -- NOT protected by the Mutex! + */ +static int chipio_write_data_multiple(struct hda_codec *codec, + const u32 *data, + unsigned int count) +{ + int status = 0; + + if (data == NULL) { + codec_dbg(codec, "chipio_write_data null ptr\n"); + return -EINVAL; + } + + while ((count-- != 0) && (status == 0)) + status = chipio_write_data(codec, *data++); + + return status; +} + + +/* + * Read data through the vendor widget -- NOT protected by the Mutex! + */ +static int chipio_read_data(struct hda_codec *codec, unsigned int *data) +{ + struct ca0132_spec *spec = codec->spec; + int res; + + /* post read */ + res = chipio_send(codec, VENDOR_CHIPIO_HIC_POST_READ, 0); + + if (res != -EIO) { + /* read status */ + res = chipio_send(codec, VENDOR_CHIPIO_STATUS, 0); + } + + if (res != -EIO) { + /* read data */ + *data = snd_hda_codec_read(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_HIC_READ_DATA, + 0); + } + + /*If no error encountered, automatically increment the address + as per chip behaviour*/ + spec->curr_chip_addx = (res != -EIO) ? + (spec->curr_chip_addx + 4) : ~0U; + return res; +} + +/* + * Write given value to the given address through the chip I/O widget. + * protected by the Mutex + */ +static int chipio_write(struct hda_codec *codec, + unsigned int chip_addx, const unsigned int data) +{ + struct ca0132_spec *spec = codec->spec; + int err; + + mutex_lock(&spec->chipio_mutex); + + /* write the address, and if successful proceed to write data */ + err = chipio_write_address(codec, chip_addx); + if (err < 0) + goto exit; + + err = chipio_write_data(codec, data); + if (err < 0) + goto exit; + +exit: + mutex_unlock(&spec->chipio_mutex); + return err; +} + +/* + * Write given value to the given address through the chip I/O widget. + * not protected by the Mutex + */ +static int chipio_write_no_mutex(struct hda_codec *codec, + unsigned int chip_addx, const unsigned int data) +{ + int err; + + + /* write the address, and if successful proceed to write data */ + err = chipio_write_address(codec, chip_addx); + if (err < 0) + goto exit; + + err = chipio_write_data(codec, data); + if (err < 0) + goto exit; + +exit: + return err; +} + +/* + * Write multiple values to the given address through the chip I/O widget. + * protected by the Mutex + */ +static int chipio_write_multiple(struct hda_codec *codec, + u32 chip_addx, + const u32 *data, + unsigned int count) +{ + struct ca0132_spec *spec = codec->spec; + int status; + + mutex_lock(&spec->chipio_mutex); + status = chipio_write_address(codec, chip_addx); + if (status < 0) + goto error; + + status = chipio_write_data_multiple(codec, data, count); +error: + mutex_unlock(&spec->chipio_mutex); + + return status; +} + +/* + * Read the given address through the chip I/O widget + * protected by the Mutex + */ +static int chipio_read(struct hda_codec *codec, + unsigned int chip_addx, unsigned int *data) +{ + struct ca0132_spec *spec = codec->spec; + int err; + + mutex_lock(&spec->chipio_mutex); + + /* write the address, and if successful proceed to write data */ + err = chipio_write_address(codec, chip_addx); + if (err < 0) + goto exit; + + err = chipio_read_data(codec, data); + if (err < 0) + goto exit; + +exit: + mutex_unlock(&spec->chipio_mutex); + return err; +} + +/* + * Set chip control flags through the chip I/O widget. + */ +static void chipio_set_control_flag(struct hda_codec *codec, + enum control_flag_id flag_id, + bool flag_state) +{ + unsigned int val; + unsigned int flag_bit; + + flag_bit = (flag_state ? 1 : 0); + val = (flag_bit << 7) | (flag_id); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_FLAG_SET, val); +} + +/* + * Set chip parameters through the chip I/O widget. + */ +static void chipio_set_control_param(struct hda_codec *codec, + enum control_param_id param_id, int param_val) +{ + struct ca0132_spec *spec = codec->spec; + int val; + + if ((param_id < 32) && (param_val < 8)) { + val = (param_val << 5) | (param_id); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PARAM_SET, val); + } else { + mutex_lock(&spec->chipio_mutex); + if (chipio_send(codec, VENDOR_CHIPIO_STATUS, 0) == 0) { + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PARAM_EX_ID_SET, + param_id); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PARAM_EX_VALUE_SET, + param_val); + } + mutex_unlock(&spec->chipio_mutex); + } +} + +/* + * Set chip parameters through the chip I/O widget. NO MUTEX. + */ +static void chipio_set_control_param_no_mutex(struct hda_codec *codec, + enum control_param_id param_id, int param_val) +{ + int val; + + if ((param_id < 32) && (param_val < 8)) { + val = (param_val << 5) | (param_id); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PARAM_SET, val); + } else { + if (chipio_send(codec, VENDOR_CHIPIO_STATUS, 0) == 0) { + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PARAM_EX_ID_SET, + param_id); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PARAM_EX_VALUE_SET, + param_val); + } + } +} +/* + * Connect stream to a source point, and then connect + * that source point to a destination point. + */ +static void chipio_set_stream_source_dest(struct hda_codec *codec, + int streamid, int source_point, int dest_point) +{ + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAM_ID, streamid); + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAM_SOURCE_CONN_POINT, source_point); + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAM_DEST_CONN_POINT, dest_point); +} + +/* + * Set number of channels in the selected stream. + */ +static void chipio_set_stream_channels(struct hda_codec *codec, + int streamid, unsigned int channels) +{ + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAM_ID, streamid); + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAMS_CHANNELS, channels); +} + +/* + * Enable/Disable audio stream. + */ +static void chipio_set_stream_control(struct hda_codec *codec, + int streamid, int enable) +{ + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAM_ID, streamid); + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAM_CONTROL, enable); +} + +/* + * Get ChipIO audio stream's status. + */ +static void chipio_get_stream_control(struct hda_codec *codec, + int streamid, unsigned int *enable) +{ + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAM_ID, streamid); + *enable = snd_hda_codec_read(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PARAM_GET, + CONTROL_PARAM_STREAM_CONTROL); +} + +/* + * Set sampling rate of the connection point. NO MUTEX. + */ +static void chipio_set_conn_rate_no_mutex(struct hda_codec *codec, + int connid, enum ca0132_sample_rate rate) +{ + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_CONN_POINT_ID, connid); + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_CONN_POINT_SAMPLE_RATE, rate); +} + +/* + * Set sampling rate of the connection point. + */ +static void chipio_set_conn_rate(struct hda_codec *codec, + int connid, enum ca0132_sample_rate rate) +{ + chipio_set_control_param(codec, CONTROL_PARAM_CONN_POINT_ID, connid); + chipio_set_control_param(codec, CONTROL_PARAM_CONN_POINT_SAMPLE_RATE, + rate); +} + +/* + * Writes to the 8051's internal address space directly instead of indirectly, + * giving access to the special function registers located at addresses + * 0x80-0xFF. + */ +static void chipio_8051_write_direct(struct hda_codec *codec, + unsigned int addr, unsigned int data) +{ + unsigned int verb; + + verb = VENDOR_CHIPIO_8051_WRITE_DIRECT | data; + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, verb, addr); +} + +/* + * Writes to the 8051's exram, which has 16-bits of address space. + * Data at addresses 0x2000-0x7fff is mirrored to 0x8000-0xdfff. + * Data at 0x8000-0xdfff can also be used as program memory for the 8051 by + * setting the pmem bank selection SFR. + * 0xe000-0xffff is always mapped as program memory, with only 0xf000-0xffff + * being writable. + */ +static void chipio_8051_set_address(struct hda_codec *codec, unsigned int addr) +{ + unsigned int tmp; + + /* Lower 8-bits. */ + tmp = addr & 0xff; + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_8051_ADDRESS_LOW, tmp); + + /* Upper 8-bits. */ + tmp = (addr >> 8) & 0xff; + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_8051_ADDRESS_HIGH, tmp); +} + +static void chipio_8051_set_data(struct hda_codec *codec, unsigned int data) +{ + /* 8-bits of data. */ + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_8051_DATA_WRITE, data & 0xff); +} + +static unsigned int chipio_8051_get_data(struct hda_codec *codec) +{ + return snd_hda_codec_read(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_8051_DATA_READ, 0); +} + +/* PLL_PMU writes share the lower address register of the 8051 exram writes. */ +static void chipio_8051_set_data_pll(struct hda_codec *codec, unsigned int data) +{ + /* 8-bits of data. */ + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PLL_PMU_WRITE, data & 0xff); +} + +static void chipio_8051_write_exram(struct hda_codec *codec, + unsigned int addr, unsigned int data) +{ + struct ca0132_spec *spec = codec->spec; + + mutex_lock(&spec->chipio_mutex); + + chipio_8051_set_address(codec, addr); + chipio_8051_set_data(codec, data); + + mutex_unlock(&spec->chipio_mutex); +} + +static void chipio_8051_write_exram_no_mutex(struct hda_codec *codec, + unsigned int addr, unsigned int data) +{ + chipio_8051_set_address(codec, addr); + chipio_8051_set_data(codec, data); +} + +/* Readback data from the 8051's exram. No mutex. */ +static void chipio_8051_read_exram(struct hda_codec *codec, + unsigned int addr, unsigned int *data) +{ + chipio_8051_set_address(codec, addr); + *data = chipio_8051_get_data(codec); +} + +static void chipio_8051_write_pll_pmu(struct hda_codec *codec, + unsigned int addr, unsigned int data) +{ + struct ca0132_spec *spec = codec->spec; + + mutex_lock(&spec->chipio_mutex); + + chipio_8051_set_address(codec, addr & 0xff); + chipio_8051_set_data_pll(codec, data); + + mutex_unlock(&spec->chipio_mutex); +} + +static void chipio_8051_write_pll_pmu_no_mutex(struct hda_codec *codec, + unsigned int addr, unsigned int data) +{ + chipio_8051_set_address(codec, addr & 0xff); + chipio_8051_set_data_pll(codec, data); +} + +/* + * Enable clocks. + */ +static void chipio_enable_clocks(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + mutex_lock(&spec->chipio_mutex); + + chipio_8051_write_pll_pmu_no_mutex(codec, 0x00, 0xff); + chipio_8051_write_pll_pmu_no_mutex(codec, 0x05, 0x0b); + chipio_8051_write_pll_pmu_no_mutex(codec, 0x06, 0xff); + + mutex_unlock(&spec->chipio_mutex); +} + +/* + * CA0132 DSP IO stuffs + */ +static int dspio_send(struct hda_codec *codec, unsigned int reg, + unsigned int data) +{ + int res; + unsigned long timeout = jiffies + msecs_to_jiffies(1000); + + /* send bits of data specified by reg to dsp */ + do { + res = snd_hda_codec_read(codec, WIDGET_DSP_CTRL, 0, reg, data); + if ((res >= 0) && (res != VENDOR_STATUS_DSPIO_BUSY)) + return res; + msleep(20); + } while (time_before(jiffies, timeout)); + + return -EIO; +} + +/* + * Wait for DSP to be ready for commands + */ +static void dspio_write_wait(struct hda_codec *codec) +{ + int status; + unsigned long timeout = jiffies + msecs_to_jiffies(1000); + + do { + status = snd_hda_codec_read(codec, WIDGET_DSP_CTRL, 0, + VENDOR_DSPIO_STATUS, 0); + if ((status == VENDOR_STATUS_DSPIO_OK) || + (status == VENDOR_STATUS_DSPIO_SCP_RESPONSE_QUEUE_EMPTY)) + break; + msleep(1); + } while (time_before(jiffies, timeout)); +} + +/* + * Write SCP data to DSP + */ +static int dspio_write(struct hda_codec *codec, unsigned int scp_data) +{ + struct ca0132_spec *spec = codec->spec; + int status; + + dspio_write_wait(codec); + + mutex_lock(&spec->chipio_mutex); + status = dspio_send(codec, VENDOR_DSPIO_SCP_WRITE_DATA_LOW, + scp_data & 0xffff); + if (status < 0) + goto error; + + status = dspio_send(codec, VENDOR_DSPIO_SCP_WRITE_DATA_HIGH, + scp_data >> 16); + if (status < 0) + goto error; + + /* OK, now check if the write itself has executed*/ + status = snd_hda_codec_read(codec, WIDGET_DSP_CTRL, 0, + VENDOR_DSPIO_STATUS, 0); +error: + mutex_unlock(&spec->chipio_mutex); + + return (status == VENDOR_STATUS_DSPIO_SCP_COMMAND_QUEUE_FULL) ? + -EIO : 0; +} + +/* + * Write multiple SCP data to DSP + */ +static int dspio_write_multiple(struct hda_codec *codec, + unsigned int *buffer, unsigned int size) +{ + int status = 0; + unsigned int count; + + if (buffer == NULL) + return -EINVAL; + + count = 0; + while (count < size) { + status = dspio_write(codec, *buffer++); + if (status != 0) + break; + count++; + } + + return status; +} + +static int dspio_read(struct hda_codec *codec, unsigned int *data) +{ + int status; + + status = dspio_send(codec, VENDOR_DSPIO_SCP_POST_READ_DATA, 0); + if (status == -EIO) + return status; + + status = dspio_send(codec, VENDOR_DSPIO_STATUS, 0); + if (status == -EIO || + status == VENDOR_STATUS_DSPIO_SCP_RESPONSE_QUEUE_EMPTY) + return -EIO; + + *data = snd_hda_codec_read(codec, WIDGET_DSP_CTRL, 0, + VENDOR_DSPIO_SCP_READ_DATA, 0); + + return 0; +} + +static int dspio_read_multiple(struct hda_codec *codec, unsigned int *buffer, + unsigned int *buf_size, unsigned int size_count) +{ + int status = 0; + unsigned int size = *buf_size; + unsigned int count; + unsigned int skip_count; + unsigned int dummy; + + if (buffer == NULL) + return -1; + + count = 0; + while (count < size && count < size_count) { + status = dspio_read(codec, buffer++); + if (status != 0) + break; + count++; + } + + skip_count = count; + if (status == 0) { + while (skip_count < size) { + status = dspio_read(codec, &dummy); + if (status != 0) + break; + skip_count++; + } + } + *buf_size = count; + + return status; +} + +/* + * Construct the SCP header using corresponding fields + */ +static inline unsigned int +make_scp_header(unsigned int target_id, unsigned int source_id, + unsigned int get_flag, unsigned int req, + unsigned int device_flag, unsigned int resp_flag, + unsigned int error_flag, unsigned int data_size) +{ + unsigned int header = 0; + + header = (data_size & 0x1f) << 27; + header |= (error_flag & 0x01) << 26; + header |= (resp_flag & 0x01) << 25; + header |= (device_flag & 0x01) << 24; + header |= (req & 0x7f) << 17; + header |= (get_flag & 0x01) << 16; + header |= (source_id & 0xff) << 8; + header |= target_id & 0xff; + + return header; +} + +/* + * Extract corresponding fields from SCP header + */ +static inline void +extract_scp_header(unsigned int header, + unsigned int *target_id, unsigned int *source_id, + unsigned int *get_flag, unsigned int *req, + unsigned int *device_flag, unsigned int *resp_flag, + unsigned int *error_flag, unsigned int *data_size) +{ + if (data_size) + *data_size = (header >> 27) & 0x1f; + if (error_flag) + *error_flag = (header >> 26) & 0x01; + if (resp_flag) + *resp_flag = (header >> 25) & 0x01; + if (device_flag) + *device_flag = (header >> 24) & 0x01; + if (req) + *req = (header >> 17) & 0x7f; + if (get_flag) + *get_flag = (header >> 16) & 0x01; + if (source_id) + *source_id = (header >> 8) & 0xff; + if (target_id) + *target_id = header & 0xff; +} + +#define SCP_MAX_DATA_WORDS (16) + +/* Structure to contain any SCP message */ +struct scp_msg { + unsigned int hdr; + unsigned int data[SCP_MAX_DATA_WORDS]; +}; + +static void dspio_clear_response_queue(struct hda_codec *codec) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(1000); + unsigned int dummy = 0; + int status; + + /* clear all from the response queue */ + do { + status = dspio_read(codec, &dummy); + } while (status == 0 && time_before(jiffies, timeout)); +} + +static int dspio_get_response_data(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int data = 0; + unsigned int count; + + if (dspio_read(codec, &data) < 0) + return -EIO; + + if ((data & 0x00ffffff) == spec->wait_scp_header) { + spec->scp_resp_header = data; + spec->scp_resp_count = data >> 27; + count = spec->wait_num_data; + dspio_read_multiple(codec, spec->scp_resp_data, + &spec->scp_resp_count, count); + return 0; + } + + return -EIO; +} + +/* + * Send SCP message to DSP + */ +static int dspio_send_scp_message(struct hda_codec *codec, + unsigned char *send_buf, + unsigned int send_buf_size, + unsigned char *return_buf, + unsigned int return_buf_size, + unsigned int *bytes_returned) +{ + struct ca0132_spec *spec = codec->spec; + int status; + unsigned int scp_send_size = 0; + unsigned int total_size; + bool waiting_for_resp = false; + unsigned int header; + struct scp_msg *ret_msg; + unsigned int resp_src_id, resp_target_id; + unsigned int data_size, src_id, target_id, get_flag, device_flag; + + if (bytes_returned) + *bytes_returned = 0; + + /* get scp header from buffer */ + header = *((unsigned int *)send_buf); + extract_scp_header(header, &target_id, &src_id, &get_flag, NULL, + &device_flag, NULL, NULL, &data_size); + scp_send_size = data_size + 1; + total_size = (scp_send_size * 4); + + if (send_buf_size < total_size) + return -EINVAL; + + if (get_flag || device_flag) { + if (!return_buf || return_buf_size < 4 || !bytes_returned) + return -EINVAL; + + spec->wait_scp_header = *((unsigned int *)send_buf); + + /* swap source id with target id */ + resp_target_id = src_id; + resp_src_id = target_id; + spec->wait_scp_header &= 0xffff0000; + spec->wait_scp_header |= (resp_src_id << 8) | (resp_target_id); + spec->wait_num_data = return_buf_size/sizeof(unsigned int) - 1; + spec->wait_scp = 1; + waiting_for_resp = true; + } + + status = dspio_write_multiple(codec, (unsigned int *)send_buf, + scp_send_size); + if (status < 0) { + spec->wait_scp = 0; + return status; + } + + if (waiting_for_resp) { + unsigned long timeout = jiffies + msecs_to_jiffies(1000); + memset(return_buf, 0, return_buf_size); + do { + msleep(20); + } while (spec->wait_scp && time_before(jiffies, timeout)); + waiting_for_resp = false; + if (!spec->wait_scp) { + ret_msg = (struct scp_msg *)return_buf; + memcpy(&ret_msg->hdr, &spec->scp_resp_header, 4); + memcpy(&ret_msg->data, spec->scp_resp_data, + spec->wait_num_data); + *bytes_returned = (spec->scp_resp_count + 1) * 4; + status = 0; + } else { + status = -EIO; + } + spec->wait_scp = 0; + } + + return status; +} + +/** + * dspio_scp - Prepare and send the SCP message to DSP + * @codec: the HDA codec + * @mod_id: ID of the DSP module to send the command + * @src_id: ID of the source + * @req: ID of request to send to the DSP module + * @dir: SET or GET + * @data: pointer to the data to send with the request, request specific + * @len: length of the data, in bytes + * @reply: point to the buffer to hold data returned for a reply + * @reply_len: length of the reply buffer returned from GET + * + * Returns zero or a negative error code. + */ +static int dspio_scp(struct hda_codec *codec, + int mod_id, int src_id, int req, int dir, const void *data, + unsigned int len, void *reply, unsigned int *reply_len) +{ + int status = 0; + struct scp_msg scp_send, scp_reply; + unsigned int ret_bytes, send_size, ret_size; + unsigned int send_get_flag, reply_resp_flag, reply_error_flag; + unsigned int reply_data_size; + + memset(&scp_send, 0, sizeof(scp_send)); + memset(&scp_reply, 0, sizeof(scp_reply)); + + if ((len != 0 && data == NULL) || (len > SCP_MAX_DATA_WORDS)) + return -EINVAL; + + if (dir == SCP_GET && reply == NULL) { + codec_dbg(codec, "dspio_scp get but has no buffer\n"); + return -EINVAL; + } + + if (reply != NULL && (reply_len == NULL || (*reply_len == 0))) { + codec_dbg(codec, "dspio_scp bad resp buf len parms\n"); + return -EINVAL; + } + + scp_send.hdr = make_scp_header(mod_id, src_id, (dir == SCP_GET), req, + 0, 0, 0, len/sizeof(unsigned int)); + if (data != NULL && len > 0) { + len = min((unsigned int)(sizeof(scp_send.data)), len); + memcpy(scp_send.data, data, len); + } + + ret_bytes = 0; + send_size = sizeof(unsigned int) + len; + status = dspio_send_scp_message(codec, (unsigned char *)&scp_send, + send_size, (unsigned char *)&scp_reply, + sizeof(scp_reply), &ret_bytes); + + if (status < 0) { + codec_dbg(codec, "dspio_scp: send scp msg failed\n"); + return status; + } + + /* extract send and reply headers members */ + extract_scp_header(scp_send.hdr, NULL, NULL, &send_get_flag, + NULL, NULL, NULL, NULL, NULL); + extract_scp_header(scp_reply.hdr, NULL, NULL, NULL, NULL, NULL, + &reply_resp_flag, &reply_error_flag, + &reply_data_size); + + if (!send_get_flag) + return 0; + + if (reply_resp_flag && !reply_error_flag) { + ret_size = (ret_bytes - sizeof(scp_reply.hdr)) + / sizeof(unsigned int); + + if (*reply_len < ret_size*sizeof(unsigned int)) { + codec_dbg(codec, "reply too long for buf\n"); + return -EINVAL; + } else if (ret_size != reply_data_size) { + codec_dbg(codec, "RetLen and HdrLen .NE.\n"); + return -EINVAL; + } else if (!reply) { + codec_dbg(codec, "NULL reply\n"); + return -EINVAL; + } else { + *reply_len = ret_size*sizeof(unsigned int); + memcpy(reply, scp_reply.data, *reply_len); + } + } else { + codec_dbg(codec, "reply ill-formed or errflag set\n"); + return -EIO; + } + + return status; +} + +/* + * Set DSP parameters + */ +static int dspio_set_param(struct hda_codec *codec, int mod_id, + int src_id, int req, const void *data, unsigned int len) +{ + return dspio_scp(codec, mod_id, src_id, req, SCP_SET, data, len, NULL, + NULL); +} + +static int dspio_set_uint_param(struct hda_codec *codec, int mod_id, + int req, const unsigned int data) +{ + return dspio_set_param(codec, mod_id, 0x20, req, &data, + sizeof(unsigned int)); +} + +/* + * Allocate a DSP DMA channel via an SCP message + */ +static int dspio_alloc_dma_chan(struct hda_codec *codec, unsigned int *dma_chan) +{ + int status = 0; + unsigned int size = sizeof(*dma_chan); + + codec_dbg(codec, " dspio_alloc_dma_chan() -- begin\n"); + status = dspio_scp(codec, MASTERCONTROL, 0x20, + MASTERCONTROL_ALLOC_DMA_CHAN, SCP_GET, NULL, 0, + dma_chan, &size); + + if (status < 0) { + codec_dbg(codec, "dspio_alloc_dma_chan: SCP Failed\n"); + return status; + } + + if ((*dma_chan + 1) == 0) { + codec_dbg(codec, "no free dma channels to allocate\n"); + return -EBUSY; + } + + codec_dbg(codec, "dspio_alloc_dma_chan: chan=%d\n", *dma_chan); + codec_dbg(codec, " dspio_alloc_dma_chan() -- complete\n"); + + return status; +} + +/* + * Free a DSP DMA via an SCP message + */ +static int dspio_free_dma_chan(struct hda_codec *codec, unsigned int dma_chan) +{ + int status = 0; + unsigned int dummy = 0; + + codec_dbg(codec, " dspio_free_dma_chan() -- begin\n"); + codec_dbg(codec, "dspio_free_dma_chan: chan=%d\n", dma_chan); + + status = dspio_scp(codec, MASTERCONTROL, 0x20, + MASTERCONTROL_ALLOC_DMA_CHAN, SCP_SET, &dma_chan, + sizeof(dma_chan), NULL, &dummy); + + if (status < 0) { + codec_dbg(codec, "dspio_free_dma_chan: SCP Failed\n"); + return status; + } + + codec_dbg(codec, " dspio_free_dma_chan() -- complete\n"); + + return status; +} + +/* + * (Re)start the DSP + */ +static int dsp_set_run_state(struct hda_codec *codec) +{ + unsigned int dbg_ctrl_reg; + unsigned int halt_state; + int err; + + err = chipio_read(codec, DSP_DBGCNTL_INST_OFFSET, &dbg_ctrl_reg); + if (err < 0) + return err; + + halt_state = (dbg_ctrl_reg & DSP_DBGCNTL_STATE_MASK) >> + DSP_DBGCNTL_STATE_LOBIT; + + if (halt_state != 0) { + dbg_ctrl_reg &= ~((halt_state << DSP_DBGCNTL_SS_LOBIT) & + DSP_DBGCNTL_SS_MASK); + err = chipio_write(codec, DSP_DBGCNTL_INST_OFFSET, + dbg_ctrl_reg); + if (err < 0) + return err; + + dbg_ctrl_reg |= (halt_state << DSP_DBGCNTL_EXEC_LOBIT) & + DSP_DBGCNTL_EXEC_MASK; + err = chipio_write(codec, DSP_DBGCNTL_INST_OFFSET, + dbg_ctrl_reg); + if (err < 0) + return err; + } + + return 0; +} + +/* + * Reset the DSP + */ +static int dsp_reset(struct hda_codec *codec) +{ + unsigned int res; + int retry = 20; + + codec_dbg(codec, "dsp_reset\n"); + do { + res = dspio_send(codec, VENDOR_DSPIO_DSP_INIT, 0); + retry--; + } while (res == -EIO && retry); + + if (!retry) { + codec_dbg(codec, "dsp_reset timeout\n"); + return -EIO; + } + + return 0; +} + +/* + * Convert chip address to DSP address + */ +static unsigned int dsp_chip_to_dsp_addx(unsigned int chip_addx, + bool *code, bool *yram) +{ + *code = *yram = false; + + if (UC_RANGE(chip_addx, 1)) { + *code = true; + return UC_OFF(chip_addx); + } else if (X_RANGE_ALL(chip_addx, 1)) { + return X_OFF(chip_addx); + } else if (Y_RANGE_ALL(chip_addx, 1)) { + *yram = true; + return Y_OFF(chip_addx); + } + + return INVALID_CHIP_ADDRESS; +} + +/* + * Check if the DSP DMA is active + */ +static bool dsp_is_dma_active(struct hda_codec *codec, unsigned int dma_chan) +{ + unsigned int dma_chnlstart_reg; + + chipio_read(codec, DSPDMAC_CHNLSTART_INST_OFFSET, &dma_chnlstart_reg); + + return ((dma_chnlstart_reg & (1 << + (DSPDMAC_CHNLSTART_EN_LOBIT + dma_chan))) != 0); +} + +static int dsp_dma_setup_common(struct hda_codec *codec, + unsigned int chip_addx, + unsigned int dma_chan, + unsigned int port_map_mask, + bool ovly) +{ + int status = 0; + unsigned int chnl_prop; + unsigned int dsp_addx; + unsigned int active; + bool code, yram; + + codec_dbg(codec, "-- dsp_dma_setup_common() -- Begin ---------\n"); + + if (dma_chan >= DSPDMAC_DMA_CFG_CHANNEL_COUNT) { + codec_dbg(codec, "dma chan num invalid\n"); + return -EINVAL; + } + + if (dsp_is_dma_active(codec, dma_chan)) { + codec_dbg(codec, "dma already active\n"); + return -EBUSY; + } + + dsp_addx = dsp_chip_to_dsp_addx(chip_addx, &code, &yram); + + if (dsp_addx == INVALID_CHIP_ADDRESS) { + codec_dbg(codec, "invalid chip addr\n"); + return -ENXIO; + } + + chnl_prop = DSPDMAC_CHNLPROP_AC_MASK; + active = 0; + + codec_dbg(codec, " dsp_dma_setup_common() start reg pgm\n"); + + if (ovly) { + status = chipio_read(codec, DSPDMAC_CHNLPROP_INST_OFFSET, + &chnl_prop); + + if (status < 0) { + codec_dbg(codec, "read CHNLPROP Reg fail\n"); + return status; + } + codec_dbg(codec, "dsp_dma_setup_common() Read CHNLPROP\n"); + } + + if (!code) + chnl_prop &= ~(1 << (DSPDMAC_CHNLPROP_MSPCE_LOBIT + dma_chan)); + else + chnl_prop |= (1 << (DSPDMAC_CHNLPROP_MSPCE_LOBIT + dma_chan)); + + chnl_prop &= ~(1 << (DSPDMAC_CHNLPROP_DCON_LOBIT + dma_chan)); + + status = chipio_write(codec, DSPDMAC_CHNLPROP_INST_OFFSET, chnl_prop); + if (status < 0) { + codec_dbg(codec, "write CHNLPROP Reg fail\n"); + return status; + } + codec_dbg(codec, " dsp_dma_setup_common() Write CHNLPROP\n"); + + if (ovly) { + status = chipio_read(codec, DSPDMAC_ACTIVE_INST_OFFSET, + &active); + + if (status < 0) { + codec_dbg(codec, "read ACTIVE Reg fail\n"); + return status; + } + codec_dbg(codec, "dsp_dma_setup_common() Read ACTIVE\n"); + } + + active &= (~(1 << (DSPDMAC_ACTIVE_AAR_LOBIT + dma_chan))) & + DSPDMAC_ACTIVE_AAR_MASK; + + status = chipio_write(codec, DSPDMAC_ACTIVE_INST_OFFSET, active); + if (status < 0) { + codec_dbg(codec, "write ACTIVE Reg fail\n"); + return status; + } + + codec_dbg(codec, " dsp_dma_setup_common() Write ACTIVE\n"); + + status = chipio_write(codec, DSPDMAC_AUDCHSEL_INST_OFFSET(dma_chan), + port_map_mask); + if (status < 0) { + codec_dbg(codec, "write AUDCHSEL Reg fail\n"); + return status; + } + codec_dbg(codec, " dsp_dma_setup_common() Write AUDCHSEL\n"); + + status = chipio_write(codec, DSPDMAC_IRQCNT_INST_OFFSET(dma_chan), + DSPDMAC_IRQCNT_BICNT_MASK | DSPDMAC_IRQCNT_CICNT_MASK); + if (status < 0) { + codec_dbg(codec, "write IRQCNT Reg fail\n"); + return status; + } + codec_dbg(codec, " dsp_dma_setup_common() Write IRQCNT\n"); + + codec_dbg(codec, + "ChipA=0x%x,DspA=0x%x,dmaCh=%u, " + "CHSEL=0x%x,CHPROP=0x%x,Active=0x%x\n", + chip_addx, dsp_addx, dma_chan, + port_map_mask, chnl_prop, active); + + codec_dbg(codec, "-- dsp_dma_setup_common() -- Complete ------\n"); + + return 0; +} + +/* + * Setup the DSP DMA per-transfer-specific registers + */ +static int dsp_dma_setup(struct hda_codec *codec, + unsigned int chip_addx, + unsigned int count, + unsigned int dma_chan) +{ + int status = 0; + bool code, yram; + unsigned int dsp_addx; + unsigned int addr_field; + unsigned int incr_field; + unsigned int base_cnt; + unsigned int cur_cnt; + unsigned int dma_cfg = 0; + unsigned int adr_ofs = 0; + unsigned int xfr_cnt = 0; + const unsigned int max_dma_count = 1 << (DSPDMAC_XFRCNT_BCNT_HIBIT - + DSPDMAC_XFRCNT_BCNT_LOBIT + 1); + + codec_dbg(codec, "-- dsp_dma_setup() -- Begin ---------\n"); + + if (count > max_dma_count) { + codec_dbg(codec, "count too big\n"); + return -EINVAL; + } + + dsp_addx = dsp_chip_to_dsp_addx(chip_addx, &code, &yram); + if (dsp_addx == INVALID_CHIP_ADDRESS) { + codec_dbg(codec, "invalid chip addr\n"); + return -ENXIO; + } + + codec_dbg(codec, " dsp_dma_setup() start reg pgm\n"); + + addr_field = dsp_addx << DSPDMAC_DMACFG_DBADR_LOBIT; + incr_field = 0; + + if (!code) { + addr_field <<= 1; + if (yram) + addr_field |= (1 << DSPDMAC_DMACFG_DBADR_LOBIT); + + incr_field = (1 << DSPDMAC_DMACFG_AINCR_LOBIT); + } + + dma_cfg = addr_field + incr_field; + status = chipio_write(codec, DSPDMAC_DMACFG_INST_OFFSET(dma_chan), + dma_cfg); + if (status < 0) { + codec_dbg(codec, "write DMACFG Reg fail\n"); + return status; + } + codec_dbg(codec, " dsp_dma_setup() Write DMACFG\n"); + + adr_ofs = (count - 1) << (DSPDMAC_DSPADROFS_BOFS_LOBIT + + (code ? 0 : 1)); + + status = chipio_write(codec, DSPDMAC_DSPADROFS_INST_OFFSET(dma_chan), + adr_ofs); + if (status < 0) { + codec_dbg(codec, "write DSPADROFS Reg fail\n"); + return status; + } + codec_dbg(codec, " dsp_dma_setup() Write DSPADROFS\n"); + + base_cnt = (count - 1) << DSPDMAC_XFRCNT_BCNT_LOBIT; + + cur_cnt = (count - 1) << DSPDMAC_XFRCNT_CCNT_LOBIT; + + xfr_cnt = base_cnt | cur_cnt; + + status = chipio_write(codec, + DSPDMAC_XFRCNT_INST_OFFSET(dma_chan), xfr_cnt); + if (status < 0) { + codec_dbg(codec, "write XFRCNT Reg fail\n"); + return status; + } + codec_dbg(codec, " dsp_dma_setup() Write XFRCNT\n"); + + codec_dbg(codec, + "ChipA=0x%x, cnt=0x%x, DMACFG=0x%x, " + "ADROFS=0x%x, XFRCNT=0x%x\n", + chip_addx, count, dma_cfg, adr_ofs, xfr_cnt); + + codec_dbg(codec, "-- dsp_dma_setup() -- Complete ---------\n"); + + return 0; +} + +/* + * Start the DSP DMA + */ +static int dsp_dma_start(struct hda_codec *codec, + unsigned int dma_chan, bool ovly) +{ + unsigned int reg = 0; + int status = 0; + + codec_dbg(codec, "-- dsp_dma_start() -- Begin ---------\n"); + + if (ovly) { + status = chipio_read(codec, + DSPDMAC_CHNLSTART_INST_OFFSET, ®); + + if (status < 0) { + codec_dbg(codec, "read CHNLSTART reg fail\n"); + return status; + } + codec_dbg(codec, "-- dsp_dma_start() Read CHNLSTART\n"); + + reg &= ~(DSPDMAC_CHNLSTART_EN_MASK | + DSPDMAC_CHNLSTART_DIS_MASK); + } + + status = chipio_write(codec, DSPDMAC_CHNLSTART_INST_OFFSET, + reg | (1 << (dma_chan + DSPDMAC_CHNLSTART_EN_LOBIT))); + if (status < 0) { + codec_dbg(codec, "write CHNLSTART reg fail\n"); + return status; + } + codec_dbg(codec, "-- dsp_dma_start() -- Complete ---------\n"); + + return status; +} + +/* + * Stop the DSP DMA + */ +static int dsp_dma_stop(struct hda_codec *codec, + unsigned int dma_chan, bool ovly) +{ + unsigned int reg = 0; + int status = 0; + + codec_dbg(codec, "-- dsp_dma_stop() -- Begin ---------\n"); + + if (ovly) { + status = chipio_read(codec, + DSPDMAC_CHNLSTART_INST_OFFSET, ®); + + if (status < 0) { + codec_dbg(codec, "read CHNLSTART reg fail\n"); + return status; + } + codec_dbg(codec, "-- dsp_dma_stop() Read CHNLSTART\n"); + reg &= ~(DSPDMAC_CHNLSTART_EN_MASK | + DSPDMAC_CHNLSTART_DIS_MASK); + } + + status = chipio_write(codec, DSPDMAC_CHNLSTART_INST_OFFSET, + reg | (1 << (dma_chan + DSPDMAC_CHNLSTART_DIS_LOBIT))); + if (status < 0) { + codec_dbg(codec, "write CHNLSTART reg fail\n"); + return status; + } + codec_dbg(codec, "-- dsp_dma_stop() -- Complete ---------\n"); + + return status; +} + +/** + * dsp_allocate_router_ports - Allocate router ports + * + * @codec: the HDA codec + * @num_chans: number of channels in the stream + * @ports_per_channel: number of ports per channel + * @start_device: start device + * @port_map: pointer to the port list to hold the allocated ports + * + * Returns zero or a negative error code. + */ +static int dsp_allocate_router_ports(struct hda_codec *codec, + unsigned int num_chans, + unsigned int ports_per_channel, + unsigned int start_device, + unsigned int *port_map) +{ + int status = 0; + int res; + u8 val; + + status = chipio_send(codec, VENDOR_CHIPIO_STATUS, 0); + if (status < 0) + return status; + + val = start_device << 6; + val |= (ports_per_channel - 1) << 4; + val |= num_chans - 1; + + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PORT_ALLOC_CONFIG_SET, + val); + + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PORT_ALLOC_SET, + MEM_CONNID_DSP); + + status = chipio_send(codec, VENDOR_CHIPIO_STATUS, 0); + if (status < 0) + return status; + + res = snd_hda_codec_read(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PORT_ALLOC_GET, 0); + + *port_map = res; + + return (res < 0) ? res : 0; +} + +/* + * Free router ports + */ +static int dsp_free_router_ports(struct hda_codec *codec) +{ + int status = 0; + + status = chipio_send(codec, VENDOR_CHIPIO_STATUS, 0); + if (status < 0) + return status; + + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PORT_FREE_SET, + MEM_CONNID_DSP); + + status = chipio_send(codec, VENDOR_CHIPIO_STATUS, 0); + + return status; +} + +/* + * Allocate DSP ports for the download stream + */ +static int dsp_allocate_ports(struct hda_codec *codec, + unsigned int num_chans, + unsigned int rate_multi, unsigned int *port_map) +{ + int status; + + codec_dbg(codec, " dsp_allocate_ports() -- begin\n"); + + if ((rate_multi != 1) && (rate_multi != 2) && (rate_multi != 4)) { + codec_dbg(codec, "bad rate multiple\n"); + return -EINVAL; + } + + status = dsp_allocate_router_ports(codec, num_chans, + rate_multi, 0, port_map); + + codec_dbg(codec, " dsp_allocate_ports() -- complete\n"); + + return status; +} + +static int dsp_allocate_ports_format(struct hda_codec *codec, + const unsigned short fmt, + unsigned int *port_map) +{ + unsigned int num_chans; + + unsigned int sample_rate_div = ((get_hdafmt_rate(fmt) >> 0) & 3) + 1; + unsigned int sample_rate_mul = ((get_hdafmt_rate(fmt) >> 3) & 3) + 1; + unsigned int rate_multi = sample_rate_mul / sample_rate_div; + + if ((rate_multi != 1) && (rate_multi != 2) && (rate_multi != 4)) { + codec_dbg(codec, "bad rate multiple\n"); + return -EINVAL; + } + + num_chans = get_hdafmt_chs(fmt) + 1; + + return dsp_allocate_ports(codec, num_chans, rate_multi, port_map); +} + +/* + * free DSP ports + */ +static int dsp_free_ports(struct hda_codec *codec) +{ + int status; + + codec_dbg(codec, " dsp_free_ports() -- begin\n"); + + status = dsp_free_router_ports(codec); + if (status < 0) { + codec_dbg(codec, "free router ports fail\n"); + return status; + } + codec_dbg(codec, " dsp_free_ports() -- complete\n"); + + return status; +} + +/* + * HDA DMA engine stuffs for DSP code download + */ +struct dma_engine { + struct hda_codec *codec; + unsigned short m_converter_format; + struct snd_dma_buffer *dmab; + unsigned int buf_size; +}; + + +enum dma_state { + DMA_STATE_STOP = 0, + DMA_STATE_RUN = 1 +}; + +static int dma_convert_to_hda_format(struct hda_codec *codec, + unsigned int sample_rate, + unsigned short channels, + unsigned short *hda_format) +{ + unsigned int format_val; + + format_val = snd_hdac_calc_stream_format(sample_rate, + channels, SNDRV_PCM_FORMAT_S32_LE, 32, 0); + + if (hda_format) + *hda_format = (unsigned short)format_val; + + return 0; +} + +/* + * Reset DMA for DSP download + */ +static int dma_reset(struct dma_engine *dma) +{ + struct hda_codec *codec = dma->codec; + struct ca0132_spec *spec = codec->spec; + int status; + + if (dma->dmab->area) + snd_hda_codec_load_dsp_cleanup(codec, dma->dmab); + + status = snd_hda_codec_load_dsp_prepare(codec, + dma->m_converter_format, + dma->buf_size, + dma->dmab); + if (status < 0) + return status; + spec->dsp_stream_id = status; + return 0; +} + +static int dma_set_state(struct dma_engine *dma, enum dma_state state) +{ + bool cmd; + + switch (state) { + case DMA_STATE_STOP: + cmd = false; + break; + case DMA_STATE_RUN: + cmd = true; + break; + default: + return 0; + } + + snd_hda_codec_load_dsp_trigger(dma->codec, cmd); + return 0; +} + +static unsigned int dma_get_buffer_size(struct dma_engine *dma) +{ + return dma->dmab->bytes; +} + +static unsigned char *dma_get_buffer_addr(struct dma_engine *dma) +{ + return dma->dmab->area; +} + +static int dma_xfer(struct dma_engine *dma, + const unsigned int *data, + unsigned int count) +{ + memcpy(dma->dmab->area, data, count); + return 0; +} + +static void dma_get_converter_format( + struct dma_engine *dma, + unsigned short *format) +{ + if (format) + *format = dma->m_converter_format; +} + +static unsigned int dma_get_stream_id(struct dma_engine *dma) +{ + struct ca0132_spec *spec = dma->codec->spec; + + return spec->dsp_stream_id; +} + +struct dsp_image_seg { + u32 magic; + u32 chip_addr; + u32 count; + u32 data[]; +}; + +static const u32 g_magic_value = 0x4c46584d; +static const u32 g_chip_addr_magic_value = 0xFFFFFF01; + +static bool is_valid(const struct dsp_image_seg *p) +{ + return p->magic == g_magic_value; +} + +static bool is_hci_prog_list_seg(const struct dsp_image_seg *p) +{ + return g_chip_addr_magic_value == p->chip_addr; +} + +static bool is_last(const struct dsp_image_seg *p) +{ + return p->count == 0; +} + +static size_t dsp_sizeof(const struct dsp_image_seg *p) +{ + return struct_size(p, data, p->count); +} + +static const struct dsp_image_seg *get_next_seg_ptr( + const struct dsp_image_seg *p) +{ + return (struct dsp_image_seg *)((unsigned char *)(p) + dsp_sizeof(p)); +} + +/* + * CA0132 chip DSP transfer stuffs. For DSP download. + */ +#define INVALID_DMA_CHANNEL (~0U) + +/* + * Program a list of address/data pairs via the ChipIO widget. + * The segment data is in the format of successive pairs of words. + * These are repeated as indicated by the segment's count field. + */ +static int dspxfr_hci_write(struct hda_codec *codec, + const struct dsp_image_seg *fls) +{ + int status; + const u32 *data; + unsigned int count; + + if (fls == NULL || fls->chip_addr != g_chip_addr_magic_value) { + codec_dbg(codec, "hci_write invalid params\n"); + return -EINVAL; + } + + count = fls->count; + data = (u32 *)(fls->data); + while (count >= 2) { + status = chipio_write(codec, data[0], data[1]); + if (status < 0) { + codec_dbg(codec, "hci_write chipio failed\n"); + return status; + } + count -= 2; + data += 2; + } + return 0; +} + +/** + * dspxfr_one_seg - Write a block of data into DSP code or data RAM using pre-allocated DMA engine. + * + * @codec: the HDA codec + * @fls: pointer to a fast load image + * @reloc: Relocation address for loading single-segment overlays, or 0 for + * no relocation + * @dma_engine: pointer to DMA engine to be used for DSP download + * @dma_chan: The number of DMA channels used for DSP download + * @port_map_mask: port mapping + * @ovly: TRUE if overlay format is required + * + * Returns zero or a negative error code. + */ +static int dspxfr_one_seg(struct hda_codec *codec, + const struct dsp_image_seg *fls, + unsigned int reloc, + struct dma_engine *dma_engine, + unsigned int dma_chan, + unsigned int port_map_mask, + bool ovly) +{ + int status = 0; + bool comm_dma_setup_done = false; + const unsigned int *data; + unsigned int chip_addx; + unsigned int words_to_write; + unsigned int buffer_size_words; + unsigned char *buffer_addx; + unsigned short hda_format; + unsigned int sample_rate_div; + unsigned int sample_rate_mul; + unsigned int num_chans; + unsigned int hda_frame_size_words; + unsigned int remainder_words; + const u32 *data_remainder; + u32 chip_addx_remainder; + unsigned int run_size_words; + const struct dsp_image_seg *hci_write = NULL; + unsigned long timeout; + bool dma_active; + + if (fls == NULL) + return -EINVAL; + if (is_hci_prog_list_seg(fls)) { + hci_write = fls; + fls = get_next_seg_ptr(fls); + } + + if (hci_write && (!fls || is_last(fls))) { + codec_dbg(codec, "hci_write\n"); + return dspxfr_hci_write(codec, hci_write); + } + + if (fls == NULL || dma_engine == NULL || port_map_mask == 0) { + codec_dbg(codec, "Invalid Params\n"); + return -EINVAL; + } + + data = fls->data; + chip_addx = fls->chip_addr; + words_to_write = fls->count; + + if (!words_to_write) + return hci_write ? dspxfr_hci_write(codec, hci_write) : 0; + if (reloc) + chip_addx = (chip_addx & (0xFFFF0000 << 2)) + (reloc << 2); + + if (!UC_RANGE(chip_addx, words_to_write) && + !X_RANGE_ALL(chip_addx, words_to_write) && + !Y_RANGE_ALL(chip_addx, words_to_write)) { + codec_dbg(codec, "Invalid chip_addx Params\n"); + return -EINVAL; + } + + buffer_size_words = (unsigned int)dma_get_buffer_size(dma_engine) / + sizeof(u32); + + buffer_addx = dma_get_buffer_addr(dma_engine); + + if (buffer_addx == NULL) { + codec_dbg(codec, "dma_engine buffer NULL\n"); + return -EINVAL; + } + + dma_get_converter_format(dma_engine, &hda_format); + sample_rate_div = ((get_hdafmt_rate(hda_format) >> 0) & 3) + 1; + sample_rate_mul = ((get_hdafmt_rate(hda_format) >> 3) & 3) + 1; + num_chans = get_hdafmt_chs(hda_format) + 1; + + hda_frame_size_words = ((sample_rate_div == 0) ? 0 : + (num_chans * sample_rate_mul / sample_rate_div)); + + if (hda_frame_size_words == 0) { + codec_dbg(codec, "frmsz zero\n"); + return -EINVAL; + } + + buffer_size_words = min(buffer_size_words, + (unsigned int)(UC_RANGE(chip_addx, 1) ? + 65536 : 32768)); + buffer_size_words -= buffer_size_words % hda_frame_size_words; + codec_dbg(codec, + "chpadr=0x%08x frmsz=%u nchan=%u " + "rate_mul=%u div=%u bufsz=%u\n", + chip_addx, hda_frame_size_words, num_chans, + sample_rate_mul, sample_rate_div, buffer_size_words); + + if (buffer_size_words < hda_frame_size_words) { + codec_dbg(codec, "dspxfr_one_seg:failed\n"); + return -EINVAL; + } + + remainder_words = words_to_write % hda_frame_size_words; + data_remainder = data; + chip_addx_remainder = chip_addx; + + data += remainder_words; + chip_addx += remainder_words*sizeof(u32); + words_to_write -= remainder_words; + + while (words_to_write != 0) { + run_size_words = min(buffer_size_words, words_to_write); + codec_dbg(codec, "dspxfr (seg loop)cnt=%u rs=%u remainder=%u\n", + words_to_write, run_size_words, remainder_words); + dma_xfer(dma_engine, data, run_size_words*sizeof(u32)); + if (!comm_dma_setup_done) { + status = dsp_dma_stop(codec, dma_chan, ovly); + if (status < 0) + return status; + status = dsp_dma_setup_common(codec, chip_addx, + dma_chan, port_map_mask, ovly); + if (status < 0) + return status; + comm_dma_setup_done = true; + } + + status = dsp_dma_setup(codec, chip_addx, + run_size_words, dma_chan); + if (status < 0) + return status; + status = dsp_dma_start(codec, dma_chan, ovly); + if (status < 0) + return status; + if (!dsp_is_dma_active(codec, dma_chan)) { + codec_dbg(codec, "dspxfr:DMA did not start\n"); + return -EIO; + } + status = dma_set_state(dma_engine, DMA_STATE_RUN); + if (status < 0) + return status; + if (remainder_words != 0) { + status = chipio_write_multiple(codec, + chip_addx_remainder, + data_remainder, + remainder_words); + if (status < 0) + return status; + remainder_words = 0; + } + if (hci_write) { + status = dspxfr_hci_write(codec, hci_write); + if (status < 0) + return status; + hci_write = NULL; + } + + timeout = jiffies + msecs_to_jiffies(2000); + do { + dma_active = dsp_is_dma_active(codec, dma_chan); + if (!dma_active) + break; + msleep(20); + } while (time_before(jiffies, timeout)); + if (dma_active) + break; + + codec_dbg(codec, "+++++ DMA complete\n"); + dma_set_state(dma_engine, DMA_STATE_STOP); + status = dma_reset(dma_engine); + + if (status < 0) + return status; + + data += run_size_words; + chip_addx += run_size_words*sizeof(u32); + words_to_write -= run_size_words; + } + + if (remainder_words != 0) { + status = chipio_write_multiple(codec, chip_addx_remainder, + data_remainder, remainder_words); + } + + return status; +} + +/** + * dspxfr_image - Write the entire DSP image of a DSP code/data overlay to DSP memories + * + * @codec: the HDA codec + * @fls_data: pointer to a fast load image + * @reloc: Relocation address for loading single-segment overlays, or 0 for + * no relocation + * @sample_rate: sampling rate of the stream used for DSP download + * @channels: channels of the stream used for DSP download + * @ovly: TRUE if overlay format is required + * + * Returns zero or a negative error code. + */ +static int dspxfr_image(struct hda_codec *codec, + const struct dsp_image_seg *fls_data, + unsigned int reloc, + unsigned int sample_rate, + unsigned short channels, + bool ovly) +{ + struct ca0132_spec *spec = codec->spec; + int status; + unsigned short hda_format = 0; + unsigned int response; + unsigned char stream_id = 0; + struct dma_engine *dma_engine; + unsigned int dma_chan; + unsigned int port_map_mask; + + if (fls_data == NULL) + return -EINVAL; + + dma_engine = kzalloc(sizeof(*dma_engine), GFP_KERNEL); + if (!dma_engine) + return -ENOMEM; + + dma_engine->dmab = kzalloc(sizeof(*dma_engine->dmab), GFP_KERNEL); + if (!dma_engine->dmab) { + kfree(dma_engine); + return -ENOMEM; + } + + dma_engine->codec = codec; + dma_convert_to_hda_format(codec, sample_rate, channels, &hda_format); + dma_engine->m_converter_format = hda_format; + dma_engine->buf_size = (ovly ? DSP_DMA_WRITE_BUFLEN_OVLY : + DSP_DMA_WRITE_BUFLEN_INIT) * 2; + + dma_chan = ovly ? INVALID_DMA_CHANNEL : 0; + + status = codec_set_converter_format(codec, WIDGET_CHIP_CTRL, + hda_format, &response); + + if (status < 0) { + codec_dbg(codec, "set converter format fail\n"); + goto exit; + } + + status = snd_hda_codec_load_dsp_prepare(codec, + dma_engine->m_converter_format, + dma_engine->buf_size, + dma_engine->dmab); + if (status < 0) + goto exit; + spec->dsp_stream_id = status; + + if (ovly) { + status = dspio_alloc_dma_chan(codec, &dma_chan); + if (status < 0) { + codec_dbg(codec, "alloc dmachan fail\n"); + dma_chan = INVALID_DMA_CHANNEL; + goto exit; + } + } + + port_map_mask = 0; + status = dsp_allocate_ports_format(codec, hda_format, + &port_map_mask); + if (status < 0) { + codec_dbg(codec, "alloc ports fail\n"); + goto exit; + } + + stream_id = dma_get_stream_id(dma_engine); + status = codec_set_converter_stream_channel(codec, + WIDGET_CHIP_CTRL, stream_id, 0, &response); + if (status < 0) { + codec_dbg(codec, "set stream chan fail\n"); + goto exit; + } + + while ((fls_data != NULL) && !is_last(fls_data)) { + if (!is_valid(fls_data)) { + codec_dbg(codec, "FLS check fail\n"); + status = -EINVAL; + goto exit; + } + status = dspxfr_one_seg(codec, fls_data, reloc, + dma_engine, dma_chan, + port_map_mask, ovly); + if (status < 0) + break; + + if (is_hci_prog_list_seg(fls_data)) + fls_data = get_next_seg_ptr(fls_data); + + if ((fls_data != NULL) && !is_last(fls_data)) + fls_data = get_next_seg_ptr(fls_data); + } + + if (port_map_mask != 0) + status = dsp_free_ports(codec); + + if (status < 0) + goto exit; + + status = codec_set_converter_stream_channel(codec, + WIDGET_CHIP_CTRL, 0, 0, &response); + +exit: + if (ovly && (dma_chan != INVALID_DMA_CHANNEL)) + dspio_free_dma_chan(codec, dma_chan); + + if (dma_engine->dmab->area) + snd_hda_codec_load_dsp_cleanup(codec, dma_engine->dmab); + kfree(dma_engine->dmab); + kfree(dma_engine); + + return status; +} + +/* + * CA0132 DSP download stuffs. + */ +static void dspload_post_setup(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + codec_dbg(codec, "---- dspload_post_setup ------\n"); + if (!ca0132_use_alt_functions(spec)) { + /*set DSP speaker to 2.0 configuration*/ + chipio_write(codec, XRAM_XRAM_INST_OFFSET(0x18), 0x08080080); + chipio_write(codec, XRAM_XRAM_INST_OFFSET(0x19), 0x3f800000); + + /*update write pointer*/ + chipio_write(codec, XRAM_XRAM_INST_OFFSET(0x29), 0x00000002); + } +} + +/** + * dspload_image - Download DSP from a DSP Image Fast Load structure. + * + * @codec: the HDA codec + * @fls: pointer to a fast load image + * @ovly: TRUE if overlay format is required + * @reloc: Relocation address for loading single-segment overlays, or 0 for + * no relocation + * @autostart: TRUE if DSP starts after loading; ignored if ovly is TRUE + * @router_chans: number of audio router channels to be allocated (0 means use + * internal defaults; max is 32) + * + * Download DSP from a DSP Image Fast Load structure. This structure is a + * linear, non-constant sized element array of structures, each of which + * contain the count of the data to be loaded, the data itself, and the + * corresponding starting chip address of the starting data location. + * Returns zero or a negative error code. + */ +static int dspload_image(struct hda_codec *codec, + const struct dsp_image_seg *fls, + bool ovly, + unsigned int reloc, + bool autostart, + int router_chans) +{ + int status = 0; + unsigned int sample_rate; + unsigned short channels; + + codec_dbg(codec, "---- dspload_image begin ------\n"); + if (router_chans == 0) { + if (!ovly) + router_chans = DMA_TRANSFER_FRAME_SIZE_NWORDS; + else + router_chans = DMA_OVERLAY_FRAME_SIZE_NWORDS; + } + + sample_rate = 48000; + channels = (unsigned short)router_chans; + + while (channels > 16) { + sample_rate *= 2; + channels /= 2; + } + + do { + codec_dbg(codec, "Ready to program DMA\n"); + if (!ovly) + status = dsp_reset(codec); + + if (status < 0) + break; + + codec_dbg(codec, "dsp_reset() complete\n"); + status = dspxfr_image(codec, fls, reloc, sample_rate, channels, + ovly); + + if (status < 0) + break; + + codec_dbg(codec, "dspxfr_image() complete\n"); + if (autostart && !ovly) { + dspload_post_setup(codec); + status = dsp_set_run_state(codec); + } + + codec_dbg(codec, "LOAD FINISHED\n"); + } while (0); + + return status; +} + +#ifdef CONFIG_SND_HDA_CODEC_CA0132_DSP +static bool dspload_is_loaded(struct hda_codec *codec) +{ + unsigned int data = 0; + int status = 0; + + status = chipio_read(codec, 0x40004, &data); + if ((status < 0) || (data != 1)) + return false; + + return true; +} +#else +#define dspload_is_loaded(codec) false +#endif + +static bool dspload_wait_loaded(struct hda_codec *codec) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(2000); + + do { + if (dspload_is_loaded(codec)) { + codec_info(codec, "ca0132 DSP downloaded and running\n"); + return true; + } + msleep(20); + } while (time_before(jiffies, timeout)); + + codec_err(codec, "ca0132 failed to download DSP\n"); + return false; +} + +/* + * ca0113 related functions. The ca0113 acts as the HDA bus for the pci-e + * based cards, and has a second mmio region, region2, that's used for special + * commands. + */ + +/* + * For cards with PCI-E region2 (Sound Blaster Z/ZxR, Recon3D, and AE-5) + * the mmio address 0x320 is used to set GPIO pins. The format for the data + * The first eight bits are just the number of the pin. So far, I've only seen + * this number go to 7. + * AE-5 note: The AE-5 seems to use pins 2 and 3 to somehow set the color value + * of the on-card LED. It seems to use pin 2 for data, then toggles 3 to on and + * then off to send that bit. + */ +static void ca0113_mmio_gpio_set(struct hda_codec *codec, unsigned int gpio_pin, + bool enable) +{ + struct ca0132_spec *spec = codec->spec; + unsigned short gpio_data; + + gpio_data = gpio_pin & 0xF; + gpio_data |= ((enable << 8) & 0x100); + + writew(gpio_data, spec->mem_base + 0x320); +} + +/* + * Special pci region2 commands that are only used by the AE-5. They follow + * a set format, and require reads at certain points to seemingly 'clear' + * the response data. My first tests didn't do these reads, and would cause + * the card to get locked up until the memory was read. These commands + * seem to work with three distinct values that I've taken to calling group, + * target-id, and value. + */ +static void ca0113_mmio_command_set(struct hda_codec *codec, unsigned int group, + unsigned int target, unsigned int value) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int write_val; + + writel(0x0000007e, spec->mem_base + 0x210); + readl(spec->mem_base + 0x210); + writel(0x0000005a, spec->mem_base + 0x210); + readl(spec->mem_base + 0x210); + readl(spec->mem_base + 0x210); + + writel(0x00800005, spec->mem_base + 0x20c); + writel(group, spec->mem_base + 0x804); + + writel(0x00800005, spec->mem_base + 0x20c); + write_val = (target & 0xff); + write_val |= (value << 8); + + + writel(write_val, spec->mem_base + 0x204); + /* + * Need delay here or else it goes too fast and works inconsistently. + */ + msleep(20); + + readl(spec->mem_base + 0x860); + readl(spec->mem_base + 0x854); + readl(spec->mem_base + 0x840); + + writel(0x00800004, spec->mem_base + 0x20c); + writel(0x00000000, spec->mem_base + 0x210); + readl(spec->mem_base + 0x210); + readl(spec->mem_base + 0x210); +} + +/* + * This second type of command is used for setting the sound filter type. + */ +static void ca0113_mmio_command_set_type2(struct hda_codec *codec, + unsigned int group, unsigned int target, unsigned int value) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int write_val; + + writel(0x0000007e, spec->mem_base + 0x210); + readl(spec->mem_base + 0x210); + writel(0x0000005a, spec->mem_base + 0x210); + readl(spec->mem_base + 0x210); + readl(spec->mem_base + 0x210); + + writel(0x00800003, spec->mem_base + 0x20c); + writel(group, spec->mem_base + 0x804); + + writel(0x00800005, spec->mem_base + 0x20c); + write_val = (target & 0xff); + write_val |= (value << 8); + + + writel(write_val, spec->mem_base + 0x204); + msleep(20); + readl(spec->mem_base + 0x860); + readl(spec->mem_base + 0x854); + readl(spec->mem_base + 0x840); + + writel(0x00800004, spec->mem_base + 0x20c); + writel(0x00000000, spec->mem_base + 0x210); + readl(spec->mem_base + 0x210); + readl(spec->mem_base + 0x210); +} + +/* + * Setup GPIO for the other variants of Core3D. + */ + +/* + * Sets up the GPIO pins so that they are discoverable. If this isn't done, + * the card shows as having no GPIO pins. + */ +static void ca0132_gpio_init(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + switch (ca0132_quirk(spec)) { + case QUIRK_SBZ: + case QUIRK_AE5: + case QUIRK_AE7: + snd_hda_codec_write(codec, 0x01, 0, 0x793, 0x00); + snd_hda_codec_write(codec, 0x01, 0, 0x794, 0x53); + snd_hda_codec_write(codec, 0x01, 0, 0x790, 0x23); + break; + case QUIRK_R3DI: + snd_hda_codec_write(codec, 0x01, 0, 0x793, 0x00); + snd_hda_codec_write(codec, 0x01, 0, 0x794, 0x5B); + break; + default: + break; + } + +} + +/* Sets the GPIO for audio output. */ +static void ca0132_gpio_setup(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + switch (ca0132_quirk(spec)) { + case QUIRK_SBZ: + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DIRECTION, 0x07); + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_MASK, 0x07); + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DATA, 0x04); + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DATA, 0x06); + break; + case QUIRK_R3DI: + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DIRECTION, 0x1E); + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_MASK, 0x1F); + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DATA, 0x0C); + break; + default: + break; + } +} + +/* + * GPIO control functions for the Recon3D integrated. + */ + +enum r3di_gpio_bit { + /* Bit 1 - Switch between front/rear mic. 0 = rear, 1 = front */ + R3DI_MIC_SELECT_BIT = 1, + /* Bit 2 - Switch between headphone/line out. 0 = Headphone, 1 = Line */ + R3DI_OUT_SELECT_BIT = 2, + /* + * I dunno what this actually does, but it stays on until the dsp + * is downloaded. + */ + R3DI_GPIO_DSP_DOWNLOADING = 3, + /* + * Same as above, no clue what it does, but it comes on after the dsp + * is downloaded. + */ + R3DI_GPIO_DSP_DOWNLOADED = 4 +}; + +enum r3di_mic_select { + /* Set GPIO bit 1 to 0 for rear mic */ + R3DI_REAR_MIC = 0, + /* Set GPIO bit 1 to 1 for front microphone*/ + R3DI_FRONT_MIC = 1 +}; + +enum r3di_out_select { + /* Set GPIO bit 2 to 0 for headphone */ + R3DI_HEADPHONE_OUT = 0, + /* Set GPIO bit 2 to 1 for speaker */ + R3DI_LINE_OUT = 1 +}; +enum r3di_dsp_status { + /* Set GPIO bit 3 to 1 until DSP is downloaded */ + R3DI_DSP_DOWNLOADING = 0, + /* Set GPIO bit 4 to 1 once DSP is downloaded */ + R3DI_DSP_DOWNLOADED = 1 +}; + + +static void r3di_gpio_mic_set(struct hda_codec *codec, + enum r3di_mic_select cur_mic) +{ + unsigned int cur_gpio; + + /* Get the current GPIO Data setup */ + cur_gpio = snd_hda_codec_read(codec, 0x01, 0, AC_VERB_GET_GPIO_DATA, 0); + + switch (cur_mic) { + case R3DI_REAR_MIC: + cur_gpio &= ~(1 << R3DI_MIC_SELECT_BIT); + break; + case R3DI_FRONT_MIC: + cur_gpio |= (1 << R3DI_MIC_SELECT_BIT); + break; + } + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DATA, cur_gpio); +} + +static void r3di_gpio_dsp_status_set(struct hda_codec *codec, + enum r3di_dsp_status dsp_status) +{ + unsigned int cur_gpio; + + /* Get the current GPIO Data setup */ + cur_gpio = snd_hda_codec_read(codec, 0x01, 0, AC_VERB_GET_GPIO_DATA, 0); + + switch (dsp_status) { + case R3DI_DSP_DOWNLOADING: + cur_gpio |= (1 << R3DI_GPIO_DSP_DOWNLOADING); + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DATA, cur_gpio); + break; + case R3DI_DSP_DOWNLOADED: + /* Set DOWNLOADING bit to 0. */ + cur_gpio &= ~(1 << R3DI_GPIO_DSP_DOWNLOADING); + + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DATA, cur_gpio); + + cur_gpio |= (1 << R3DI_GPIO_DSP_DOWNLOADED); + break; + } + + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DATA, cur_gpio); +} + +/* + * PCM callbacks + */ +static int ca0132_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct ca0132_spec *spec = codec->spec; + + snd_hda_codec_setup_stream(codec, spec->dacs[0], stream_tag, 0, format); + + return 0; +} + +static int ca0132_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct ca0132_spec *spec = codec->spec; + + if (spec->dsp_state == DSP_DOWNLOADING) + return 0; + + /*If Playback effects are on, allow stream some time to flush + *effects tail*/ + if (spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]) + msleep(50); + + snd_hda_codec_cleanup_stream(codec, spec->dacs[0]); + + return 0; +} + +static unsigned int ca0132_playback_pcm_delay(struct hda_pcm_stream *info, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int latency = DSP_PLAYBACK_INIT_LATENCY; + struct snd_pcm_runtime *runtime = substream->runtime; + + if (spec->dsp_state != DSP_DOWNLOADED) + return 0; + + /* Add latency if playback enhancement and either effect is enabled. */ + if (spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]) { + if ((spec->effects_switch[SURROUND - EFFECT_START_NID]) || + (spec->effects_switch[DIALOG_PLUS - EFFECT_START_NID])) + latency += DSP_PLAY_ENHANCEMENT_LATENCY; + } + + /* Applying Speaker EQ adds latency as well. */ + if (spec->cur_out_type == SPEAKER_OUT) + latency += DSP_SPEAKER_OUT_LATENCY; + + return (latency * runtime->rate) / 1000; +} + +/* + * Digital out + */ +static int ca0132_dig_playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct ca0132_spec *spec = codec->spec; + return snd_hda_multi_out_dig_open(codec, &spec->multiout); +} + +static int ca0132_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct ca0132_spec *spec = codec->spec; + return snd_hda_multi_out_dig_prepare(codec, &spec->multiout, + stream_tag, format, substream); +} + +static int ca0132_dig_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct ca0132_spec *spec = codec->spec; + return snd_hda_multi_out_dig_cleanup(codec, &spec->multiout); +} + +static int ca0132_dig_playback_pcm_close(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct ca0132_spec *spec = codec->spec; + return snd_hda_multi_out_dig_close(codec, &spec->multiout); +} + +/* + * Analog capture + */ +static int ca0132_capture_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + snd_hda_codec_setup_stream(codec, hinfo->nid, + stream_tag, 0, format); + + return 0; +} + +static int ca0132_capture_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct ca0132_spec *spec = codec->spec; + + if (spec->dsp_state == DSP_DOWNLOADING) + return 0; + + snd_hda_codec_cleanup_stream(codec, hinfo->nid); + return 0; +} + +static unsigned int ca0132_capture_pcm_delay(struct hda_pcm_stream *info, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int latency = DSP_CAPTURE_INIT_LATENCY; + struct snd_pcm_runtime *runtime = substream->runtime; + + if (spec->dsp_state != DSP_DOWNLOADED) + return 0; + + if (spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID]) + latency += DSP_CRYSTAL_VOICE_LATENCY; + + return (latency * runtime->rate) / 1000; +} + +/* + * Controls stuffs. + */ + +/* + * Mixer controls helpers. + */ +#define CA0132_CODEC_VOL_MONO(xname, nid, channel, dir) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .subdevice = HDA_SUBDEV_AMP_FLAG, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, \ + .info = ca0132_volume_info, \ + .get = ca0132_volume_get, \ + .put = ca0132_volume_put, \ + .tlv = { .c = ca0132_volume_tlv }, \ + .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, 0, dir) } + +/* + * Creates a mixer control that uses defaults of HDA_CODEC_VOL except for the + * volume put, which is used for setting the DSP volume. This was done because + * the ca0132 functions were taking too much time and causing lag. + */ +#define CA0132_ALT_CODEC_VOL_MONO(xname, nid, channel, dir) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .subdevice = HDA_SUBDEV_AMP_FLAG, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, \ + .info = snd_hda_mixer_amp_volume_info, \ + .get = snd_hda_mixer_amp_volume_get, \ + .put = ca0132_alt_volume_put, \ + .tlv = { .c = snd_hda_mixer_amp_tlv }, \ + .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, 0, dir) } + +#define CA0132_CODEC_MUTE_MONO(xname, nid, channel, dir) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .subdevice = HDA_SUBDEV_AMP_FLAG, \ + .info = snd_hda_mixer_amp_switch_info, \ + .get = ca0132_switch_get, \ + .put = ca0132_switch_put, \ + .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, 0, dir) } + +/* stereo */ +#define CA0132_CODEC_VOL(xname, nid, dir) \ + CA0132_CODEC_VOL_MONO(xname, nid, 3, dir) +#define CA0132_ALT_CODEC_VOL(xname, nid, dir) \ + CA0132_ALT_CODEC_VOL_MONO(xname, nid, 3, dir) +#define CA0132_CODEC_MUTE(xname, nid, dir) \ + CA0132_CODEC_MUTE_MONO(xname, nid, 3, dir) + +/* lookup tables */ +/* + * Lookup table with decibel values for the DSP. When volume is changed in + * Windows, the DSP is also sent the dB value in floating point. In Windows, + * these values have decimal points, probably because the Windows driver + * actually uses floating point. We can't here, so I made a lookup table of + * values -90 to 9. -90 is the lowest decibel value for both the ADC's and the + * DAC's, and 9 is the maximum. + */ +static const unsigned int float_vol_db_lookup[] = { +0xC2B40000, 0xC2B20000, 0xC2B00000, 0xC2AE0000, 0xC2AC0000, 0xC2AA0000, +0xC2A80000, 0xC2A60000, 0xC2A40000, 0xC2A20000, 0xC2A00000, 0xC29E0000, +0xC29C0000, 0xC29A0000, 0xC2980000, 0xC2960000, 0xC2940000, 0xC2920000, +0xC2900000, 0xC28E0000, 0xC28C0000, 0xC28A0000, 0xC2880000, 0xC2860000, +0xC2840000, 0xC2820000, 0xC2800000, 0xC27C0000, 0xC2780000, 0xC2740000, +0xC2700000, 0xC26C0000, 0xC2680000, 0xC2640000, 0xC2600000, 0xC25C0000, +0xC2580000, 0xC2540000, 0xC2500000, 0xC24C0000, 0xC2480000, 0xC2440000, +0xC2400000, 0xC23C0000, 0xC2380000, 0xC2340000, 0xC2300000, 0xC22C0000, +0xC2280000, 0xC2240000, 0xC2200000, 0xC21C0000, 0xC2180000, 0xC2140000, +0xC2100000, 0xC20C0000, 0xC2080000, 0xC2040000, 0xC2000000, 0xC1F80000, +0xC1F00000, 0xC1E80000, 0xC1E00000, 0xC1D80000, 0xC1D00000, 0xC1C80000, +0xC1C00000, 0xC1B80000, 0xC1B00000, 0xC1A80000, 0xC1A00000, 0xC1980000, +0xC1900000, 0xC1880000, 0xC1800000, 0xC1700000, 0xC1600000, 0xC1500000, +0xC1400000, 0xC1300000, 0xC1200000, 0xC1100000, 0xC1000000, 0xC0E00000, +0xC0C00000, 0xC0A00000, 0xC0800000, 0xC0400000, 0xC0000000, 0xBF800000, +0x00000000, 0x3F800000, 0x40000000, 0x40400000, 0x40800000, 0x40A00000, +0x40C00000, 0x40E00000, 0x41000000, 0x41100000 +}; + +/* + * This table counts from float 0 to 1 in increments of .01, which is + * useful for a few different sliders. + */ +static const unsigned int float_zero_to_one_lookup[] = { +0x00000000, 0x3C23D70A, 0x3CA3D70A, 0x3CF5C28F, 0x3D23D70A, 0x3D4CCCCD, +0x3D75C28F, 0x3D8F5C29, 0x3DA3D70A, 0x3DB851EC, 0x3DCCCCCD, 0x3DE147AE, +0x3DF5C28F, 0x3E051EB8, 0x3E0F5C29, 0x3E19999A, 0x3E23D70A, 0x3E2E147B, +0x3E3851EC, 0x3E428F5C, 0x3E4CCCCD, 0x3E570A3D, 0x3E6147AE, 0x3E6B851F, +0x3E75C28F, 0x3E800000, 0x3E851EB8, 0x3E8A3D71, 0x3E8F5C29, 0x3E947AE1, +0x3E99999A, 0x3E9EB852, 0x3EA3D70A, 0x3EA8F5C3, 0x3EAE147B, 0x3EB33333, +0x3EB851EC, 0x3EBD70A4, 0x3EC28F5C, 0x3EC7AE14, 0x3ECCCCCD, 0x3ED1EB85, +0x3ED70A3D, 0x3EDC28F6, 0x3EE147AE, 0x3EE66666, 0x3EEB851F, 0x3EF0A3D7, +0x3EF5C28F, 0x3EFAE148, 0x3F000000, 0x3F028F5C, 0x3F051EB8, 0x3F07AE14, +0x3F0A3D71, 0x3F0CCCCD, 0x3F0F5C29, 0x3F11EB85, 0x3F147AE1, 0x3F170A3D, +0x3F19999A, 0x3F1C28F6, 0x3F1EB852, 0x3F2147AE, 0x3F23D70A, 0x3F266666, +0x3F28F5C3, 0x3F2B851F, 0x3F2E147B, 0x3F30A3D7, 0x3F333333, 0x3F35C28F, +0x3F3851EC, 0x3F3AE148, 0x3F3D70A4, 0x3F400000, 0x3F428F5C, 0x3F451EB8, +0x3F47AE14, 0x3F4A3D71, 0x3F4CCCCD, 0x3F4F5C29, 0x3F51EB85, 0x3F547AE1, +0x3F570A3D, 0x3F59999A, 0x3F5C28F6, 0x3F5EB852, 0x3F6147AE, 0x3F63D70A, +0x3F666666, 0x3F68F5C3, 0x3F6B851F, 0x3F6E147B, 0x3F70A3D7, 0x3F733333, +0x3F75C28F, 0x3F7851EC, 0x3F7AE148, 0x3F7D70A4, 0x3F800000 +}; + +/* + * This table counts from float 10 to 1000, which is the range of the x-bass + * crossover slider in Windows. + */ +static const unsigned int float_xbass_xover_lookup[] = { +0x41200000, 0x41A00000, 0x41F00000, 0x42200000, 0x42480000, 0x42700000, +0x428C0000, 0x42A00000, 0x42B40000, 0x42C80000, 0x42DC0000, 0x42F00000, +0x43020000, 0x430C0000, 0x43160000, 0x43200000, 0x432A0000, 0x43340000, +0x433E0000, 0x43480000, 0x43520000, 0x435C0000, 0x43660000, 0x43700000, +0x437A0000, 0x43820000, 0x43870000, 0x438C0000, 0x43910000, 0x43960000, +0x439B0000, 0x43A00000, 0x43A50000, 0x43AA0000, 0x43AF0000, 0x43B40000, +0x43B90000, 0x43BE0000, 0x43C30000, 0x43C80000, 0x43CD0000, 0x43D20000, +0x43D70000, 0x43DC0000, 0x43E10000, 0x43E60000, 0x43EB0000, 0x43F00000, +0x43F50000, 0x43FA0000, 0x43FF0000, 0x44020000, 0x44048000, 0x44070000, +0x44098000, 0x440C0000, 0x440E8000, 0x44110000, 0x44138000, 0x44160000, +0x44188000, 0x441B0000, 0x441D8000, 0x44200000, 0x44228000, 0x44250000, +0x44278000, 0x442A0000, 0x442C8000, 0x442F0000, 0x44318000, 0x44340000, +0x44368000, 0x44390000, 0x443B8000, 0x443E0000, 0x44408000, 0x44430000, +0x44458000, 0x44480000, 0x444A8000, 0x444D0000, 0x444F8000, 0x44520000, +0x44548000, 0x44570000, 0x44598000, 0x445C0000, 0x445E8000, 0x44610000, +0x44638000, 0x44660000, 0x44688000, 0x446B0000, 0x446D8000, 0x44700000, +0x44728000, 0x44750000, 0x44778000, 0x447A0000 +}; + +/* The following are for tuning of products */ +#ifdef ENABLE_TUNING_CONTROLS + +static const unsigned int voice_focus_vals_lookup[] = { +0x41A00000, 0x41A80000, 0x41B00000, 0x41B80000, 0x41C00000, 0x41C80000, +0x41D00000, 0x41D80000, 0x41E00000, 0x41E80000, 0x41F00000, 0x41F80000, +0x42000000, 0x42040000, 0x42080000, 0x420C0000, 0x42100000, 0x42140000, +0x42180000, 0x421C0000, 0x42200000, 0x42240000, 0x42280000, 0x422C0000, +0x42300000, 0x42340000, 0x42380000, 0x423C0000, 0x42400000, 0x42440000, +0x42480000, 0x424C0000, 0x42500000, 0x42540000, 0x42580000, 0x425C0000, +0x42600000, 0x42640000, 0x42680000, 0x426C0000, 0x42700000, 0x42740000, +0x42780000, 0x427C0000, 0x42800000, 0x42820000, 0x42840000, 0x42860000, +0x42880000, 0x428A0000, 0x428C0000, 0x428E0000, 0x42900000, 0x42920000, +0x42940000, 0x42960000, 0x42980000, 0x429A0000, 0x429C0000, 0x429E0000, +0x42A00000, 0x42A20000, 0x42A40000, 0x42A60000, 0x42A80000, 0x42AA0000, +0x42AC0000, 0x42AE0000, 0x42B00000, 0x42B20000, 0x42B40000, 0x42B60000, +0x42B80000, 0x42BA0000, 0x42BC0000, 0x42BE0000, 0x42C00000, 0x42C20000, +0x42C40000, 0x42C60000, 0x42C80000, 0x42CA0000, 0x42CC0000, 0x42CE0000, +0x42D00000, 0x42D20000, 0x42D40000, 0x42D60000, 0x42D80000, 0x42DA0000, +0x42DC0000, 0x42DE0000, 0x42E00000, 0x42E20000, 0x42E40000, 0x42E60000, +0x42E80000, 0x42EA0000, 0x42EC0000, 0x42EE0000, 0x42F00000, 0x42F20000, +0x42F40000, 0x42F60000, 0x42F80000, 0x42FA0000, 0x42FC0000, 0x42FE0000, +0x43000000, 0x43010000, 0x43020000, 0x43030000, 0x43040000, 0x43050000, +0x43060000, 0x43070000, 0x43080000, 0x43090000, 0x430A0000, 0x430B0000, +0x430C0000, 0x430D0000, 0x430E0000, 0x430F0000, 0x43100000, 0x43110000, +0x43120000, 0x43130000, 0x43140000, 0x43150000, 0x43160000, 0x43170000, +0x43180000, 0x43190000, 0x431A0000, 0x431B0000, 0x431C0000, 0x431D0000, +0x431E0000, 0x431F0000, 0x43200000, 0x43210000, 0x43220000, 0x43230000, +0x43240000, 0x43250000, 0x43260000, 0x43270000, 0x43280000, 0x43290000, +0x432A0000, 0x432B0000, 0x432C0000, 0x432D0000, 0x432E0000, 0x432F0000, +0x43300000, 0x43310000, 0x43320000, 0x43330000, 0x43340000 +}; + +static const unsigned int mic_svm_vals_lookup[] = { +0x00000000, 0x3C23D70A, 0x3CA3D70A, 0x3CF5C28F, 0x3D23D70A, 0x3D4CCCCD, +0x3D75C28F, 0x3D8F5C29, 0x3DA3D70A, 0x3DB851EC, 0x3DCCCCCD, 0x3DE147AE, +0x3DF5C28F, 0x3E051EB8, 0x3E0F5C29, 0x3E19999A, 0x3E23D70A, 0x3E2E147B, +0x3E3851EC, 0x3E428F5C, 0x3E4CCCCD, 0x3E570A3D, 0x3E6147AE, 0x3E6B851F, +0x3E75C28F, 0x3E800000, 0x3E851EB8, 0x3E8A3D71, 0x3E8F5C29, 0x3E947AE1, +0x3E99999A, 0x3E9EB852, 0x3EA3D70A, 0x3EA8F5C3, 0x3EAE147B, 0x3EB33333, +0x3EB851EC, 0x3EBD70A4, 0x3EC28F5C, 0x3EC7AE14, 0x3ECCCCCD, 0x3ED1EB85, +0x3ED70A3D, 0x3EDC28F6, 0x3EE147AE, 0x3EE66666, 0x3EEB851F, 0x3EF0A3D7, +0x3EF5C28F, 0x3EFAE148, 0x3F000000, 0x3F028F5C, 0x3F051EB8, 0x3F07AE14, +0x3F0A3D71, 0x3F0CCCCD, 0x3F0F5C29, 0x3F11EB85, 0x3F147AE1, 0x3F170A3D, +0x3F19999A, 0x3F1C28F6, 0x3F1EB852, 0x3F2147AE, 0x3F23D70A, 0x3F266666, +0x3F28F5C3, 0x3F2B851F, 0x3F2E147B, 0x3F30A3D7, 0x3F333333, 0x3F35C28F, +0x3F3851EC, 0x3F3AE148, 0x3F3D70A4, 0x3F400000, 0x3F428F5C, 0x3F451EB8, +0x3F47AE14, 0x3F4A3D71, 0x3F4CCCCD, 0x3F4F5C29, 0x3F51EB85, 0x3F547AE1, +0x3F570A3D, 0x3F59999A, 0x3F5C28F6, 0x3F5EB852, 0x3F6147AE, 0x3F63D70A, +0x3F666666, 0x3F68F5C3, 0x3F6B851F, 0x3F6E147B, 0x3F70A3D7, 0x3F733333, +0x3F75C28F, 0x3F7851EC, 0x3F7AE148, 0x3F7D70A4, 0x3F800000 +}; + +static const unsigned int equalizer_vals_lookup[] = { +0xC1C00000, 0xC1B80000, 0xC1B00000, 0xC1A80000, 0xC1A00000, 0xC1980000, +0xC1900000, 0xC1880000, 0xC1800000, 0xC1700000, 0xC1600000, 0xC1500000, +0xC1400000, 0xC1300000, 0xC1200000, 0xC1100000, 0xC1000000, 0xC0E00000, +0xC0C00000, 0xC0A00000, 0xC0800000, 0xC0400000, 0xC0000000, 0xBF800000, +0x00000000, 0x3F800000, 0x40000000, 0x40400000, 0x40800000, 0x40A00000, +0x40C00000, 0x40E00000, 0x41000000, 0x41100000, 0x41200000, 0x41300000, +0x41400000, 0x41500000, 0x41600000, 0x41700000, 0x41800000, 0x41880000, +0x41900000, 0x41980000, 0x41A00000, 0x41A80000, 0x41B00000, 0x41B80000, +0x41C00000 +}; + +static int tuning_ctl_set(struct hda_codec *codec, hda_nid_t nid, + const unsigned int *lookup, int idx) +{ + int i = 0; + + for (i = 0; i < TUNING_CTLS_COUNT; i++) + if (nid == ca0132_tuning_ctls[i].nid) + goto found; + + return -EINVAL; +found: + snd_hda_power_up(codec); + dspio_set_param(codec, ca0132_tuning_ctls[i].mid, 0x20, + ca0132_tuning_ctls[i].req, + &(lookup[idx]), sizeof(unsigned int)); + snd_hda_power_down(codec); + + return 1; +} + +static int tuning_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + long *valp = ucontrol->value.integer.value; + int idx = nid - TUNING_CTL_START_NID; + + *valp = spec->cur_ctl_vals[idx]; + return 0; +} + +static int voice_focus_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int chs = get_amp_channels(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = chs == 3 ? 2 : 1; + uinfo->value.integer.min = 20; + uinfo->value.integer.max = 180; + uinfo->value.integer.step = 1; + + return 0; +} + +static int voice_focus_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + long *valp = ucontrol->value.integer.value; + int idx; + + idx = nid - TUNING_CTL_START_NID; + /* any change? */ + if (spec->cur_ctl_vals[idx] == *valp) + return 0; + + spec->cur_ctl_vals[idx] = *valp; + + idx = *valp - 20; + tuning_ctl_set(codec, nid, voice_focus_vals_lookup, idx); + + return 1; +} + +static int mic_svm_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int chs = get_amp_channels(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = chs == 3 ? 2 : 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + uinfo->value.integer.step = 1; + + return 0; +} + +static int mic_svm_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + long *valp = ucontrol->value.integer.value; + int idx; + + idx = nid - TUNING_CTL_START_NID; + /* any change? */ + if (spec->cur_ctl_vals[idx] == *valp) + return 0; + + spec->cur_ctl_vals[idx] = *valp; + + idx = *valp; + tuning_ctl_set(codec, nid, mic_svm_vals_lookup, idx); + + return 0; +} + +static int equalizer_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int chs = get_amp_channels(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = chs == 3 ? 2 : 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 48; + uinfo->value.integer.step = 1; + + return 0; +} + +static int equalizer_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + long *valp = ucontrol->value.integer.value; + int idx; + + idx = nid - TUNING_CTL_START_NID; + /* any change? */ + if (spec->cur_ctl_vals[idx] == *valp) + return 0; + + spec->cur_ctl_vals[idx] = *valp; + + idx = *valp; + tuning_ctl_set(codec, nid, equalizer_vals_lookup, idx); + + return 1; +} + +static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(voice_focus_db_scale, 2000, 100, 0); +static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(eq_db_scale, -2400, 100, 0); + +static int add_tuning_control(struct hda_codec *codec, + hda_nid_t pnid, hda_nid_t nid, + const char *name, int dir) +{ + char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + int type = dir ? HDA_INPUT : HDA_OUTPUT; + struct snd_kcontrol_new knew = + HDA_CODEC_VOLUME_MONO(namestr, nid, 1, 0, type); + + knew.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ; + knew.tlv.c = 0; + knew.tlv.p = 0; + switch (pnid) { + case VOICE_FOCUS: + knew.info = voice_focus_ctl_info; + knew.get = tuning_ctl_get; + knew.put = voice_focus_ctl_put; + knew.tlv.p = voice_focus_db_scale; + break; + case MIC_SVM: + knew.info = mic_svm_ctl_info; + knew.get = tuning_ctl_get; + knew.put = mic_svm_ctl_put; + break; + case EQUALIZER: + knew.info = equalizer_ctl_info; + knew.get = tuning_ctl_get; + knew.put = equalizer_ctl_put; + knew.tlv.p = eq_db_scale; + break; + default: + return 0; + } + knew.private_value = + HDA_COMPOSE_AMP_VAL(nid, 1, 0, type); + sprintf(namestr, "%s %s Volume", name, dirstr[dir]); + return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec)); +} + +static int add_tuning_ctls(struct hda_codec *codec) +{ + int i; + int err; + + for (i = 0; i < TUNING_CTLS_COUNT; i++) { + err = add_tuning_control(codec, + ca0132_tuning_ctls[i].parent_nid, + ca0132_tuning_ctls[i].nid, + ca0132_tuning_ctls[i].name, + ca0132_tuning_ctls[i].direct); + if (err < 0) + return err; + } + + return 0; +} + +static void ca0132_init_tuning_defaults(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + int i; + + /* Wedge Angle defaults to 30. 10 below is 30 - 20. 20 is min. */ + spec->cur_ctl_vals[WEDGE_ANGLE - TUNING_CTL_START_NID] = 10; + /* SVM level defaults to 0.74. */ + spec->cur_ctl_vals[SVM_LEVEL - TUNING_CTL_START_NID] = 74; + + /* EQ defaults to 0dB. */ + for (i = 2; i < TUNING_CTLS_COUNT; i++) + spec->cur_ctl_vals[i] = 24; +} +#endif /*ENABLE_TUNING_CONTROLS*/ + +/* + * Select the active output. + * If autodetect is enabled, output will be selected based on jack detection. + * If jack inserted, headphone will be selected, else built-in speakers + * If autodetect is disabled, output will be selected based on selection. + */ +static int ca0132_select_out(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int pin_ctl; + int jack_present; + int auto_jack; + unsigned int tmp; + int err; + + codec_dbg(codec, "ca0132_select_out\n"); + + snd_hda_power_up_pm(codec); + + auto_jack = spec->vnode_lswitch[VNID_HP_ASEL - VNODE_START_NID]; + + if (auto_jack) + jack_present = snd_hda_jack_detect(codec, spec->unsol_tag_hp); + else + jack_present = + spec->vnode_lswitch[VNID_HP_SEL - VNODE_START_NID]; + + if (jack_present) + spec->cur_out_type = HEADPHONE_OUT; + else + spec->cur_out_type = SPEAKER_OUT; + + if (spec->cur_out_type == SPEAKER_OUT) { + codec_dbg(codec, "ca0132_select_out speaker\n"); + /*speaker out config*/ + tmp = FLOAT_ONE; + err = dspio_set_uint_param(codec, 0x80, 0x04, tmp); + if (err < 0) + goto exit; + /*enable speaker EQ*/ + tmp = FLOAT_ONE; + err = dspio_set_uint_param(codec, 0x8f, 0x00, tmp); + if (err < 0) + goto exit; + + /* Setup EAPD */ + snd_hda_codec_write(codec, spec->out_pins[1], 0, + VENDOR_CHIPIO_EAPD_SEL_SET, 0x02); + snd_hda_codec_write(codec, spec->out_pins[0], 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x00); + snd_hda_codec_write(codec, spec->out_pins[0], 0, + VENDOR_CHIPIO_EAPD_SEL_SET, 0x00); + snd_hda_codec_write(codec, spec->out_pins[0], 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x02); + + /* disable headphone node */ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[1], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, spec->out_pins[1], + pin_ctl & ~PIN_HP); + /* enable speaker node */ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[0], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, spec->out_pins[0], + pin_ctl | PIN_OUT); + } else { + codec_dbg(codec, "ca0132_select_out hp\n"); + /*headphone out config*/ + tmp = FLOAT_ZERO; + err = dspio_set_uint_param(codec, 0x80, 0x04, tmp); + if (err < 0) + goto exit; + /*disable speaker EQ*/ + tmp = FLOAT_ZERO; + err = dspio_set_uint_param(codec, 0x8f, 0x00, tmp); + if (err < 0) + goto exit; + + /* Setup EAPD */ + snd_hda_codec_write(codec, spec->out_pins[0], 0, + VENDOR_CHIPIO_EAPD_SEL_SET, 0x00); + snd_hda_codec_write(codec, spec->out_pins[0], 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x00); + snd_hda_codec_write(codec, spec->out_pins[1], 0, + VENDOR_CHIPIO_EAPD_SEL_SET, 0x02); + snd_hda_codec_write(codec, spec->out_pins[0], 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x02); + + /* disable speaker*/ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[0], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, spec->out_pins[0], + pin_ctl & ~PIN_HP); + /* enable headphone*/ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[1], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, spec->out_pins[1], + pin_ctl | PIN_HP); + } + +exit: + snd_hda_power_down_pm(codec); + + return err < 0 ? err : 0; +} + +static int ae5_headphone_gain_set(struct hda_codec *codec, long val); +static int zxr_headphone_gain_set(struct hda_codec *codec, long val); +static int ca0132_effects_set(struct hda_codec *codec, hda_nid_t nid, long val); + +static void ae5_mmio_select_out(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + const struct ae_ca0113_output_set *out_cmds; + unsigned int i; + + if (ca0132_quirk(spec) == QUIRK_AE5) + out_cmds = &ae5_ca0113_output_presets; + else + out_cmds = &ae7_ca0113_output_presets; + + for (i = 0; i < AE_CA0113_OUT_SET_COMMANDS; i++) + ca0113_mmio_command_set(codec, out_cmds->group[i], + out_cmds->target[i], + out_cmds->vals[spec->cur_out_type][i]); +} + +static int ca0132_alt_set_full_range_speaker(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + int quirk = ca0132_quirk(spec); + unsigned int tmp; + int err; + + /* 2.0/4.0 setup has no LFE channel, so setting full-range does nothing. */ + if (spec->channel_cfg_val == SPEAKER_CHANNELS_4_0 + || spec->channel_cfg_val == SPEAKER_CHANNELS_2_0) + return 0; + + /* Set front L/R full range. Zero for full-range, one for redirection. */ + tmp = spec->speaker_range_val[0] ? FLOAT_ZERO : FLOAT_ONE; + err = dspio_set_uint_param(codec, 0x96, + SPEAKER_FULL_RANGE_FRONT_L_R, tmp); + if (err < 0) + return err; + + /* When setting full-range rear, both rear and center/lfe are set. */ + tmp = spec->speaker_range_val[1] ? FLOAT_ZERO : FLOAT_ONE; + err = dspio_set_uint_param(codec, 0x96, + SPEAKER_FULL_RANGE_CENTER_LFE, tmp); + if (err < 0) + return err; + + err = dspio_set_uint_param(codec, 0x96, + SPEAKER_FULL_RANGE_REAR_L_R, tmp); + if (err < 0) + return err; + + /* + * Only the AE series cards set this value when setting full-range, + * and it's always 1.0f. + */ + if (quirk == QUIRK_AE5 || quirk == QUIRK_AE7) { + err = dspio_set_uint_param(codec, 0x96, + SPEAKER_FULL_RANGE_SURROUND_L_R, FLOAT_ONE); + if (err < 0) + return err; + } + + return 0; +} + +static int ca0132_alt_surround_set_bass_redirection(struct hda_codec *codec, + bool val) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp; + int err; + + if (val && spec->channel_cfg_val != SPEAKER_CHANNELS_4_0 && + spec->channel_cfg_val != SPEAKER_CHANNELS_2_0) + tmp = FLOAT_ONE; + else + tmp = FLOAT_ZERO; + + err = dspio_set_uint_param(codec, 0x96, SPEAKER_BASS_REDIRECT, tmp); + if (err < 0) + return err; + + /* If it is enabled, make sure to set the crossover frequency. */ + if (tmp) { + tmp = float_xbass_xover_lookup[spec->xbass_xover_freq]; + err = dspio_set_uint_param(codec, 0x96, + SPEAKER_BASS_REDIRECT_XOVER_FREQ, tmp); + if (err < 0) + return err; + } + + return 0; +} + +/* + * These are the commands needed to setup output on each of the different card + * types. + */ +static void ca0132_alt_select_out_get_quirk_data(struct hda_codec *codec, + const struct ca0132_alt_out_set_quirk_data **quirk_data) +{ + struct ca0132_spec *spec = codec->spec; + int quirk = ca0132_quirk(spec); + unsigned int i; + + *quirk_data = NULL; + for (i = 0; i < ARRAY_SIZE(quirk_out_set_data); i++) { + if (quirk_out_set_data[i].quirk_id == quirk) { + *quirk_data = &quirk_out_set_data[i]; + return; + } + } +} + +static int ca0132_alt_select_out_quirk_set(struct hda_codec *codec) +{ + const struct ca0132_alt_out_set_quirk_data *quirk_data; + const struct ca0132_alt_out_set_info *out_info; + struct ca0132_spec *spec = codec->spec; + unsigned int i, gpio_data; + int err; + + ca0132_alt_select_out_get_quirk_data(codec, &quirk_data); + if (!quirk_data) + return 0; + + out_info = &quirk_data->out_set_info[spec->cur_out_type]; + if (quirk_data->is_ae_series) + ae5_mmio_select_out(codec); + + if (out_info->has_hda_gpio) { + gpio_data = snd_hda_codec_read(codec, codec->core.afg, 0, + AC_VERB_GET_GPIO_DATA, 0); + + if (out_info->hda_gpio_set) + gpio_data |= (1 << out_info->hda_gpio_pin); + else + gpio_data &= ~(1 << out_info->hda_gpio_pin); + + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DATA, gpio_data); + } + + if (out_info->mmio_gpio_count) { + for (i = 0; i < out_info->mmio_gpio_count; i++) { + ca0113_mmio_gpio_set(codec, out_info->mmio_gpio_pin[i], + out_info->mmio_gpio_set[i]); + } + } + + if (out_info->scp_cmds_count) { + for (i = 0; i < out_info->scp_cmds_count; i++) { + err = dspio_set_uint_param(codec, + out_info->scp_cmd_mid[i], + out_info->scp_cmd_req[i], + out_info->scp_cmd_val[i]); + if (err < 0) + return err; + } + } + + chipio_set_control_param(codec, 0x0d, out_info->dac2port); + + if (out_info->has_chipio_write) { + chipio_write(codec, out_info->chipio_write_addr, + out_info->chipio_write_data); + } + + if (quirk_data->has_headphone_gain) { + if (spec->cur_out_type != HEADPHONE_OUT) { + if (quirk_data->is_ae_series) + ae5_headphone_gain_set(codec, 2); + else + zxr_headphone_gain_set(codec, 0); + } else { + if (quirk_data->is_ae_series) + ae5_headphone_gain_set(codec, + spec->ae5_headphone_gain_val); + else + zxr_headphone_gain_set(codec, + spec->zxr_gain_set); + } + } + + return 0; +} + +static void ca0132_set_out_node_pincfg(struct hda_codec *codec, hda_nid_t nid, + bool out_enable, bool hp_enable) +{ + unsigned int pin_ctl; + + pin_ctl = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + + pin_ctl = hp_enable ? pin_ctl | PIN_HP_AMP : pin_ctl & ~PIN_HP_AMP; + pin_ctl = out_enable ? pin_ctl | PIN_OUT : pin_ctl & ~PIN_OUT; + snd_hda_set_pin_ctl(codec, nid, pin_ctl); +} + +/* + * This function behaves similarly to the ca0132_select_out funciton above, + * except with a few differences. It adds the ability to select the current + * output with an enumerated control "output source" if the auto detect + * mute switch is set to off. If the auto detect mute switch is enabled, it + * will detect either headphone or lineout(SPEAKER_OUT) from jack detection. + * It also adds the ability to auto-detect the front headphone port. + */ +static int ca0132_alt_select_out(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp, outfx_set; + int jack_present; + int auto_jack; + int err; + /* Default Headphone is rear headphone */ + hda_nid_t headphone_nid = spec->out_pins[1]; + + codec_dbg(codec, "%s\n", __func__); + + snd_hda_power_up_pm(codec); + + auto_jack = spec->vnode_lswitch[VNID_HP_ASEL - VNODE_START_NID]; + + /* + * If headphone rear or front is plugged in, set to headphone. + * If neither is plugged in, set to rear line out. Only if + * hp/speaker auto detect is enabled. + */ + if (auto_jack) { + jack_present = snd_hda_jack_detect(codec, spec->unsol_tag_hp) || + snd_hda_jack_detect(codec, spec->unsol_tag_front_hp); + + if (jack_present) + spec->cur_out_type = HEADPHONE_OUT; + else + spec->cur_out_type = SPEAKER_OUT; + } else + spec->cur_out_type = spec->out_enum_val; + + outfx_set = spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]; + + /* Begin DSP output switch, mute DSP volume. */ + err = dspio_set_uint_param(codec, 0x96, SPEAKER_TUNING_MUTE, FLOAT_ONE); + if (err < 0) + goto exit; + + if (ca0132_alt_select_out_quirk_set(codec) < 0) + goto exit; + + switch (spec->cur_out_type) { + case SPEAKER_OUT: + codec_dbg(codec, "%s speaker\n", __func__); + + /* Enable EAPD */ + snd_hda_codec_write(codec, spec->out_pins[0], 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x01); + + /* Disable headphone node. */ + ca0132_set_out_node_pincfg(codec, spec->out_pins[1], 0, 0); + /* Set front L-R to output. */ + ca0132_set_out_node_pincfg(codec, spec->out_pins[0], 1, 0); + /* Set Center/LFE to output. */ + ca0132_set_out_node_pincfg(codec, spec->out_pins[2], 1, 0); + /* Set rear surround to output. */ + ca0132_set_out_node_pincfg(codec, spec->out_pins[3], 1, 0); + + /* + * Without PlayEnhancement being enabled, if we've got a 2.0 + * setup, set it to floating point eight to disable any DSP + * processing effects. + */ + if (!outfx_set && spec->channel_cfg_val == SPEAKER_CHANNELS_2_0) + tmp = FLOAT_EIGHT; + else + tmp = speaker_channel_cfgs[spec->channel_cfg_val].val; + + err = dspio_set_uint_param(codec, 0x80, 0x04, tmp); + if (err < 0) + goto exit; + + break; + case HEADPHONE_OUT: + codec_dbg(codec, "%s hp\n", __func__); + snd_hda_codec_write(codec, spec->out_pins[0], 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x00); + + /* Disable all speaker nodes. */ + ca0132_set_out_node_pincfg(codec, spec->out_pins[0], 0, 0); + ca0132_set_out_node_pincfg(codec, spec->out_pins[2], 0, 0); + ca0132_set_out_node_pincfg(codec, spec->out_pins[3], 0, 0); + + /* enable headphone, either front or rear */ + if (snd_hda_jack_detect(codec, spec->unsol_tag_front_hp)) + headphone_nid = spec->out_pins[2]; + else if (snd_hda_jack_detect(codec, spec->unsol_tag_hp)) + headphone_nid = spec->out_pins[1]; + + ca0132_set_out_node_pincfg(codec, headphone_nid, 1, 1); + + if (outfx_set) + err = dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_ONE); + else + err = dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_ZERO); + + if (err < 0) + goto exit; + break; + } + /* + * If output effects are enabled, set the X-Bass effect value again to + * make sure that it's properly enabled/disabled for speaker + * configurations with an LFE channel. + */ + if (outfx_set) + ca0132_effects_set(codec, X_BASS, + spec->effects_switch[X_BASS - EFFECT_START_NID]); + + /* Set speaker EQ bypass attenuation to 0. */ + err = dspio_set_uint_param(codec, 0x8f, 0x01, FLOAT_ZERO); + if (err < 0) + goto exit; + + /* + * Although unused on all cards but the AE series, this is always set + * to zero when setting the output. + */ + err = dspio_set_uint_param(codec, 0x96, + SPEAKER_TUNING_USE_SPEAKER_EQ, FLOAT_ZERO); + if (err < 0) + goto exit; + + if (spec->cur_out_type == SPEAKER_OUT) + err = ca0132_alt_surround_set_bass_redirection(codec, + spec->bass_redirection_val); + else + err = ca0132_alt_surround_set_bass_redirection(codec, 0); + + /* Unmute DSP now that we're done with output selection. */ + err = dspio_set_uint_param(codec, 0x96, + SPEAKER_TUNING_MUTE, FLOAT_ZERO); + if (err < 0) + goto exit; + + if (spec->cur_out_type == SPEAKER_OUT) { + err = ca0132_alt_set_full_range_speaker(codec); + if (err < 0) + goto exit; + } + +exit: + snd_hda_power_down_pm(codec); + + return err < 0 ? err : 0; +} + +static void ca0132_unsol_hp_delayed(struct work_struct *work) +{ + struct ca0132_spec *spec = container_of( + to_delayed_work(work), struct ca0132_spec, unsol_hp_work); + struct hda_jack_tbl *jack; + + if (ca0132_use_alt_functions(spec)) + ca0132_alt_select_out(spec->codec); + else + ca0132_select_out(spec->codec); + + jack = snd_hda_jack_tbl_get(spec->codec, spec->unsol_tag_hp); + if (jack) { + jack->block_report = 0; + snd_hda_jack_report_sync(spec->codec); + } +} + +static void ca0132_set_dmic(struct hda_codec *codec, int enable); +static int ca0132_mic_boost_set(struct hda_codec *codec, long val); +static void resume_mic1(struct hda_codec *codec, unsigned int oldval); +static int stop_mic1(struct hda_codec *codec); +static int ca0132_cvoice_switch_set(struct hda_codec *codec); +static int ca0132_alt_mic_boost_set(struct hda_codec *codec, long val); + +/* + * Select the active VIP source + */ +static int ca0132_set_vipsource(struct hda_codec *codec, int val) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp; + + if (spec->dsp_state != DSP_DOWNLOADED) + return 0; + + /* if CrystalVoice if off, vipsource should be 0 */ + if (!spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID] || + (val == 0)) { + chipio_set_control_param(codec, CONTROL_PARAM_VIP_SOURCE, 0); + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + if (spec->cur_mic_type == DIGITAL_MIC) + tmp = FLOAT_TWO; + else + tmp = FLOAT_ONE; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x80, 0x05, tmp); + } else { + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_16_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_16_000); + if (spec->cur_mic_type == DIGITAL_MIC) + tmp = FLOAT_TWO; + else + tmp = FLOAT_ONE; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + tmp = FLOAT_ONE; + dspio_set_uint_param(codec, 0x80, 0x05, tmp); + msleep(20); + chipio_set_control_param(codec, CONTROL_PARAM_VIP_SOURCE, val); + } + + return 1; +} + +static int ca0132_alt_set_vipsource(struct hda_codec *codec, int val) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp; + + if (spec->dsp_state != DSP_DOWNLOADED) + return 0; + + codec_dbg(codec, "%s\n", __func__); + + chipio_set_stream_control(codec, 0x03, 0); + chipio_set_stream_control(codec, 0x04, 0); + + /* if CrystalVoice is off, vipsource should be 0 */ + if (!spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID] || + (val == 0) || spec->in_enum_val == REAR_LINE_IN) { + codec_dbg(codec, "%s: off.", __func__); + chipio_set_control_param(codec, CONTROL_PARAM_VIP_SOURCE, 0); + + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x80, 0x05, tmp); + + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + if (ca0132_quirk(spec) == QUIRK_R3DI) + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + + + if (spec->in_enum_val == REAR_LINE_IN) + tmp = FLOAT_ZERO; + else { + if (ca0132_quirk(spec) == QUIRK_SBZ) + tmp = FLOAT_THREE; + else + tmp = FLOAT_ONE; + } + + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + } else { + codec_dbg(codec, "%s: on.", __func__); + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_16_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_16_000); + if (ca0132_quirk(spec) == QUIRK_R3DI) + chipio_set_conn_rate(codec, 0x0F, SR_16_000); + + if (spec->effects_switch[VOICE_FOCUS - EFFECT_START_NID]) + tmp = FLOAT_TWO; + else + tmp = FLOAT_ONE; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + tmp = FLOAT_ONE; + dspio_set_uint_param(codec, 0x80, 0x05, tmp); + + msleep(20); + chipio_set_control_param(codec, CONTROL_PARAM_VIP_SOURCE, val); + } + + chipio_set_stream_control(codec, 0x03, 1); + chipio_set_stream_control(codec, 0x04, 1); + + return 1; +} + +/* + * Select the active microphone. + * If autodetect is enabled, mic will be selected based on jack detection. + * If jack inserted, ext.mic will be selected, else built-in mic + * If autodetect is disabled, mic will be selected based on selection. + */ +static int ca0132_select_mic(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + int jack_present; + int auto_jack; + + codec_dbg(codec, "ca0132_select_mic\n"); + + snd_hda_power_up_pm(codec); + + auto_jack = spec->vnode_lswitch[VNID_AMIC1_ASEL - VNODE_START_NID]; + + if (auto_jack) + jack_present = snd_hda_jack_detect(codec, spec->unsol_tag_amic1); + else + jack_present = + spec->vnode_lswitch[VNID_AMIC1_SEL - VNODE_START_NID]; + + if (jack_present) + spec->cur_mic_type = LINE_MIC_IN; + else + spec->cur_mic_type = DIGITAL_MIC; + + if (spec->cur_mic_type == DIGITAL_MIC) { + /* enable digital Mic */ + chipio_set_conn_rate(codec, MEM_CONNID_DMIC, SR_32_000); + ca0132_set_dmic(codec, 1); + ca0132_mic_boost_set(codec, 0); + /* set voice focus */ + ca0132_effects_set(codec, VOICE_FOCUS, + spec->effects_switch + [VOICE_FOCUS - EFFECT_START_NID]); + } else { + /* disable digital Mic */ + chipio_set_conn_rate(codec, MEM_CONNID_DMIC, SR_96_000); + ca0132_set_dmic(codec, 0); + ca0132_mic_boost_set(codec, spec->cur_mic_boost); + /* disable voice focus */ + ca0132_effects_set(codec, VOICE_FOCUS, 0); + } + + snd_hda_power_down_pm(codec); + + return 0; +} + +/* + * Select the active input. + * Mic detection isn't used, because it's kind of pointless on the SBZ. + * The front mic has no jack-detection, so the only way to switch to it + * is to do it manually in alsamixer. + */ +static int ca0132_alt_select_in(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp; + + codec_dbg(codec, "%s\n", __func__); + + snd_hda_power_up_pm(codec); + + chipio_set_stream_control(codec, 0x03, 0); + chipio_set_stream_control(codec, 0x04, 0); + + spec->cur_mic_type = spec->in_enum_val; + + switch (spec->cur_mic_type) { + case REAR_MIC: + switch (ca0132_quirk(spec)) { + case QUIRK_SBZ: + case QUIRK_R3D: + ca0113_mmio_gpio_set(codec, 0, false); + tmp = FLOAT_THREE; + break; + case QUIRK_ZXR: + tmp = FLOAT_THREE; + break; + case QUIRK_R3DI: + r3di_gpio_mic_set(codec, R3DI_REAR_MIC); + tmp = FLOAT_ONE; + break; + case QUIRK_AE5: + ca0113_mmio_command_set(codec, 0x30, 0x28, 0x00); + tmp = FLOAT_THREE; + break; + case QUIRK_AE7: + ca0113_mmio_command_set(codec, 0x30, 0x28, 0x00); + tmp = FLOAT_THREE; + chipio_set_conn_rate(codec, MEM_CONNID_MICIN2, + SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT2, + SR_96_000); + dspio_set_uint_param(codec, 0x80, 0x01, FLOAT_ZERO); + break; + default: + tmp = FLOAT_ONE; + break; + } + + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + if (ca0132_quirk(spec) == QUIRK_R3DI) + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + chipio_set_stream_control(codec, 0x03, 1); + chipio_set_stream_control(codec, 0x04, 1); + switch (ca0132_quirk(spec)) { + case QUIRK_SBZ: + chipio_write(codec, 0x18B098, 0x0000000C); + chipio_write(codec, 0x18B09C, 0x0000000C); + break; + case QUIRK_ZXR: + chipio_write(codec, 0x18B098, 0x0000000C); + chipio_write(codec, 0x18B09C, 0x000000CC); + break; + case QUIRK_AE5: + chipio_write(codec, 0x18B098, 0x0000000C); + chipio_write(codec, 0x18B09C, 0x0000004C); + break; + default: + break; + } + ca0132_alt_mic_boost_set(codec, spec->mic_boost_enum_val); + break; + case REAR_LINE_IN: + ca0132_mic_boost_set(codec, 0); + switch (ca0132_quirk(spec)) { + case QUIRK_SBZ: + case QUIRK_R3D: + ca0113_mmio_gpio_set(codec, 0, false); + break; + case QUIRK_R3DI: + r3di_gpio_mic_set(codec, R3DI_REAR_MIC); + break; + case QUIRK_AE5: + ca0113_mmio_command_set(codec, 0x30, 0x28, 0x00); + break; + case QUIRK_AE7: + ca0113_mmio_command_set(codec, 0x30, 0x28, 0x3f); + chipio_set_conn_rate(codec, MEM_CONNID_MICIN2, + SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT2, + SR_96_000); + dspio_set_uint_param(codec, 0x80, 0x01, FLOAT_ZERO); + break; + default: + break; + } + + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + if (ca0132_quirk(spec) == QUIRK_R3DI) + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + + if (ca0132_quirk(spec) == QUIRK_AE7) + tmp = FLOAT_THREE; + else + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + switch (ca0132_quirk(spec)) { + case QUIRK_SBZ: + case QUIRK_AE5: + chipio_write(codec, 0x18B098, 0x00000000); + chipio_write(codec, 0x18B09C, 0x00000000); + break; + default: + break; + } + chipio_set_stream_control(codec, 0x03, 1); + chipio_set_stream_control(codec, 0x04, 1); + break; + case FRONT_MIC: + switch (ca0132_quirk(spec)) { + case QUIRK_SBZ: + case QUIRK_R3D: + ca0113_mmio_gpio_set(codec, 0, true); + ca0113_mmio_gpio_set(codec, 5, false); + tmp = FLOAT_THREE; + break; + case QUIRK_R3DI: + r3di_gpio_mic_set(codec, R3DI_FRONT_MIC); + tmp = FLOAT_ONE; + break; + case QUIRK_AE5: + ca0113_mmio_command_set(codec, 0x30, 0x28, 0x3f); + tmp = FLOAT_THREE; + break; + default: + tmp = FLOAT_ONE; + break; + } + + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + if (ca0132_quirk(spec) == QUIRK_R3DI) + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + chipio_set_stream_control(codec, 0x03, 1); + chipio_set_stream_control(codec, 0x04, 1); + + switch (ca0132_quirk(spec)) { + case QUIRK_SBZ: + chipio_write(codec, 0x18B098, 0x0000000C); + chipio_write(codec, 0x18B09C, 0x000000CC); + break; + case QUIRK_AE5: + chipio_write(codec, 0x18B098, 0x0000000C); + chipio_write(codec, 0x18B09C, 0x0000004C); + break; + default: + break; + } + ca0132_alt_mic_boost_set(codec, spec->mic_boost_enum_val); + break; + } + ca0132_cvoice_switch_set(codec); + + snd_hda_power_down_pm(codec); + return 0; +} + +/* + * Check if VNODE settings take effect immediately. + */ +static bool ca0132_is_vnode_effective(struct hda_codec *codec, + hda_nid_t vnid, + hda_nid_t *shared_nid) +{ + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid; + + switch (vnid) { + case VNID_SPK: + nid = spec->shared_out_nid; + break; + case VNID_MIC: + nid = spec->shared_mic_nid; + break; + default: + return false; + } + + if (shared_nid) + *shared_nid = nid; + + return true; +} + +/* +* The following functions are control change helpers. +* They return 0 if no changed. Return 1 if changed. +*/ +static int ca0132_voicefx_set(struct hda_codec *codec, int enable) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp; + + /* based on CrystalVoice state to enable VoiceFX. */ + if (enable) { + tmp = spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID] ? + FLOAT_ONE : FLOAT_ZERO; + } else { + tmp = FLOAT_ZERO; + } + + dspio_set_uint_param(codec, ca0132_voicefx.mid, + ca0132_voicefx.reqs[0], tmp); + + return 1; +} + +/* + * Set the effects parameters + */ +static int ca0132_effects_set(struct hda_codec *codec, hda_nid_t nid, long val) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int on, tmp, channel_cfg; + int num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT; + int err = 0; + int idx = nid - EFFECT_START_NID; + + if ((idx < 0) || (idx >= num_fx)) + return 0; /* no changed */ + + /* for out effect, qualify with PE */ + if ((nid >= OUT_EFFECT_START_NID) && (nid < OUT_EFFECT_END_NID)) { + /* if PE if off, turn off out effects. */ + if (!spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]) + val = 0; + if (spec->cur_out_type == SPEAKER_OUT && nid == X_BASS) { + channel_cfg = spec->channel_cfg_val; + if (channel_cfg != SPEAKER_CHANNELS_2_0 && + channel_cfg != SPEAKER_CHANNELS_4_0) + val = 0; + } + } + + /* for in effect, qualify with CrystalVoice */ + if ((nid >= IN_EFFECT_START_NID) && (nid < IN_EFFECT_END_NID)) { + /* if CrystalVoice if off, turn off in effects. */ + if (!spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID]) + val = 0; + + /* Voice Focus applies to 2-ch Mic, Digital Mic */ + if ((nid == VOICE_FOCUS) && (spec->cur_mic_type != DIGITAL_MIC)) + val = 0; + + /* If Voice Focus on SBZ, set to two channel. */ + if ((nid == VOICE_FOCUS) && ca0132_use_pci_mmio(spec) + && (spec->cur_mic_type != REAR_LINE_IN)) { + if (spec->effects_switch[CRYSTAL_VOICE - + EFFECT_START_NID]) { + + if (spec->effects_switch[VOICE_FOCUS - + EFFECT_START_NID]) { + tmp = FLOAT_TWO; + val = 1; + } else + tmp = FLOAT_ONE; + + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + } + } + /* + * For SBZ noise reduction, there's an extra command + * to module ID 0x47. No clue why. + */ + if ((nid == NOISE_REDUCTION) && ca0132_use_pci_mmio(spec) + && (spec->cur_mic_type != REAR_LINE_IN)) { + if (spec->effects_switch[CRYSTAL_VOICE - + EFFECT_START_NID]) { + if (spec->effects_switch[NOISE_REDUCTION - + EFFECT_START_NID]) + tmp = FLOAT_ONE; + else + tmp = FLOAT_ZERO; + } else + tmp = FLOAT_ZERO; + + dspio_set_uint_param(codec, 0x47, 0x00, tmp); + } + + /* If rear line in disable effects. */ + if (ca0132_use_alt_functions(spec) && + spec->in_enum_val == REAR_LINE_IN) + val = 0; + } + + codec_dbg(codec, "ca0132_effect_set: nid=0x%x, val=%ld\n", + nid, val); + + on = (val == 0) ? FLOAT_ZERO : FLOAT_ONE; + err = dspio_set_uint_param(codec, ca0132_effects[idx].mid, + ca0132_effects[idx].reqs[0], on); + + if (err < 0) + return 0; /* no changed */ + + return 1; +} + +/* + * Turn on/off Playback Enhancements + */ +static int ca0132_pe_switch_set(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid; + int i, ret = 0; + + codec_dbg(codec, "ca0132_pe_switch_set: val=%ld\n", + spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]); + + if (ca0132_use_alt_functions(spec)) + ca0132_alt_select_out(codec); + + i = OUT_EFFECT_START_NID - EFFECT_START_NID; + nid = OUT_EFFECT_START_NID; + /* PE affects all out effects */ + for (; nid < OUT_EFFECT_END_NID; nid++, i++) + ret |= ca0132_effects_set(codec, nid, spec->effects_switch[i]); + + return ret; +} + +/* Check if Mic1 is streaming, if so, stop streaming */ +static int stop_mic1(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int oldval = snd_hda_codec_read(codec, spec->adcs[0], 0, + AC_VERB_GET_CONV, 0); + if (oldval != 0) + snd_hda_codec_write(codec, spec->adcs[0], 0, + AC_VERB_SET_CHANNEL_STREAMID, + 0); + return oldval; +} + +/* Resume Mic1 streaming if it was stopped. */ +static void resume_mic1(struct hda_codec *codec, unsigned int oldval) +{ + struct ca0132_spec *spec = codec->spec; + /* Restore the previous stream and channel */ + if (oldval != 0) + snd_hda_codec_write(codec, spec->adcs[0], 0, + AC_VERB_SET_CHANNEL_STREAMID, + oldval); +} + +/* + * Turn on/off CrystalVoice + */ +static int ca0132_cvoice_switch_set(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid; + int i, ret = 0; + unsigned int oldval; + + codec_dbg(codec, "ca0132_cvoice_switch_set: val=%ld\n", + spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID]); + + i = IN_EFFECT_START_NID - EFFECT_START_NID; + nid = IN_EFFECT_START_NID; + /* CrystalVoice affects all in effects */ + for (; nid < IN_EFFECT_END_NID; nid++, i++) + ret |= ca0132_effects_set(codec, nid, spec->effects_switch[i]); + + /* including VoiceFX */ + ret |= ca0132_voicefx_set(codec, (spec->voicefx_val ? 1 : 0)); + + /* set correct vipsource */ + oldval = stop_mic1(codec); + if (ca0132_use_alt_functions(spec)) + ret |= ca0132_alt_set_vipsource(codec, 1); + else + ret |= ca0132_set_vipsource(codec, 1); + resume_mic1(codec, oldval); + return ret; +} + +static int ca0132_mic_boost_set(struct hda_codec *codec, long val) +{ + struct ca0132_spec *spec = codec->spec; + int ret = 0; + + if (val) /* on */ + ret = snd_hda_codec_amp_update(codec, spec->input_pins[0], 0, + HDA_INPUT, 0, HDA_AMP_VOLMASK, 3); + else /* off */ + ret = snd_hda_codec_amp_update(codec, spec->input_pins[0], 0, + HDA_INPUT, 0, HDA_AMP_VOLMASK, 0); + + return ret; +} + +static int ca0132_alt_mic_boost_set(struct hda_codec *codec, long val) +{ + struct ca0132_spec *spec = codec->spec; + int ret = 0; + + ret = snd_hda_codec_amp_update(codec, spec->input_pins[0], 0, + HDA_INPUT, 0, HDA_AMP_VOLMASK, val); + return ret; +} + +static int ae5_headphone_gain_set(struct hda_codec *codec, long val) +{ + unsigned int i; + + for (i = 0; i < 4; i++) + ca0113_mmio_command_set(codec, 0x48, 0x11 + i, + ae5_headphone_gain_presets[val].vals[i]); + return 0; +} + +/* + * gpio pin 1 is a relay that switches on/off, apparently setting the headphone + * amplifier to handle a 600 ohm load. + */ +static int zxr_headphone_gain_set(struct hda_codec *codec, long val) +{ + ca0113_mmio_gpio_set(codec, 1, val); + + return 0; +} + +static int ca0132_vnode_switch_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = get_amp_nid(kcontrol); + hda_nid_t shared_nid = 0; + bool effective; + int ret = 0; + struct ca0132_spec *spec = codec->spec; + int auto_jack; + + if (nid == VNID_HP_SEL) { + auto_jack = + spec->vnode_lswitch[VNID_HP_ASEL - VNODE_START_NID]; + if (!auto_jack) { + if (ca0132_use_alt_functions(spec)) + ca0132_alt_select_out(codec); + else + ca0132_select_out(codec); + } + return 1; + } + + if (nid == VNID_AMIC1_SEL) { + auto_jack = + spec->vnode_lswitch[VNID_AMIC1_ASEL - VNODE_START_NID]; + if (!auto_jack) + ca0132_select_mic(codec); + return 1; + } + + if (nid == VNID_HP_ASEL) { + if (ca0132_use_alt_functions(spec)) + ca0132_alt_select_out(codec); + else + ca0132_select_out(codec); + return 1; + } + + if (nid == VNID_AMIC1_ASEL) { + ca0132_select_mic(codec); + return 1; + } + + /* if effective conditions, then update hw immediately. */ + effective = ca0132_is_vnode_effective(codec, nid, &shared_nid); + if (effective) { + int dir = get_amp_direction(kcontrol); + int ch = get_amp_channels(kcontrol); + unsigned long pval; + + mutex_lock(&codec->control_mutex); + pval = kcontrol->private_value; + kcontrol->private_value = HDA_COMPOSE_AMP_VAL(shared_nid, ch, + 0, dir); + ret = snd_hda_mixer_amp_switch_put(kcontrol, ucontrol); + kcontrol->private_value = pval; + mutex_unlock(&codec->control_mutex); + } + + return ret; +} +/* End of control change helpers. */ + +static void ca0132_alt_bass_redirection_xover_set(struct hda_codec *codec, + long idx) +{ + snd_hda_power_up(codec); + + dspio_set_param(codec, 0x96, 0x20, SPEAKER_BASS_REDIRECT_XOVER_FREQ, + &(float_xbass_xover_lookup[idx]), sizeof(unsigned int)); + + snd_hda_power_down(codec); +} + +/* + * Below I've added controls to mess with the effect levels, I've only enabled + * them on the Sound Blaster Z, but they would probably also work on the + * Chromebook. I figured they were probably tuned specifically for it, and left + * out for a reason. + */ + +/* Sets DSP effect level from the sliders above the controls */ + +static int ca0132_alt_slider_ctl_set(struct hda_codec *codec, hda_nid_t nid, + const unsigned int *lookup, int idx) +{ + int i = 0; + unsigned int y; + /* + * For X_BASS, req 2 is actually crossover freq instead of + * effect level + */ + if (nid == X_BASS) + y = 2; + else + y = 1; + + snd_hda_power_up(codec); + if (nid == XBASS_XOVER) { + for (i = 0; i < OUT_EFFECTS_COUNT; i++) + if (ca0132_effects[i].nid == X_BASS) + break; + + dspio_set_param(codec, ca0132_effects[i].mid, 0x20, + ca0132_effects[i].reqs[1], + &(lookup[idx - 1]), sizeof(unsigned int)); + } else { + /* Find the actual effect structure */ + for (i = 0; i < OUT_EFFECTS_COUNT; i++) + if (nid == ca0132_effects[i].nid) + break; + + dspio_set_param(codec, ca0132_effects[i].mid, 0x20, + ca0132_effects[i].reqs[y], + &(lookup[idx]), sizeof(unsigned int)); + } + + snd_hda_power_down(codec); + + return 0; +} + +static int ca0132_alt_xbass_xover_slider_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + long *valp = ucontrol->value.integer.value; + hda_nid_t nid = get_amp_nid(kcontrol); + + if (nid == BASS_REDIRECTION_XOVER) + *valp = spec->bass_redirect_xover_freq; + else + *valp = spec->xbass_xover_freq; + + return 0; +} + +static int ca0132_alt_slider_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + long *valp = ucontrol->value.integer.value; + int idx = nid - OUT_EFFECT_START_NID; + + *valp = spec->fx_ctl_val[idx]; + return 0; +} + +/* + * The X-bass crossover starts at 10hz, so the min is 1. The + * frequency is set in multiples of 10. + */ +static int ca0132_alt_xbass_xover_slider_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 100; + uinfo->value.integer.step = 1; + + return 0; +} + +static int ca0132_alt_effect_slider_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int chs = get_amp_channels(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = chs == 3 ? 2 : 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + uinfo->value.integer.step = 1; + + return 0; +} + +static int ca0132_alt_xbass_xover_slider_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + long *valp = ucontrol->value.integer.value; + long *cur_val; + int idx; + + if (nid == BASS_REDIRECTION_XOVER) + cur_val = &spec->bass_redirect_xover_freq; + else + cur_val = &spec->xbass_xover_freq; + + /* any change? */ + if (*cur_val == *valp) + return 0; + + *cur_val = *valp; + + idx = *valp; + if (nid == BASS_REDIRECTION_XOVER) + ca0132_alt_bass_redirection_xover_set(codec, *cur_val); + else + ca0132_alt_slider_ctl_set(codec, nid, float_xbass_xover_lookup, idx); + + return 0; +} + +static int ca0132_alt_effect_slider_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + long *valp = ucontrol->value.integer.value; + int idx; + + idx = nid - EFFECT_START_NID; + /* any change? */ + if (spec->fx_ctl_val[idx] == *valp) + return 0; + + spec->fx_ctl_val[idx] = *valp; + + idx = *valp; + ca0132_alt_slider_ctl_set(codec, nid, float_zero_to_one_lookup, idx); + + return 0; +} + + +/* + * Mic Boost Enum for alternative ca0132 codecs. I didn't like that the original + * only has off or full 30 dB, and didn't like making a volume slider that has + * traditional 0-100 in alsamixer that goes in big steps. I like enum better. + */ +#define MIC_BOOST_NUM_OF_STEPS 4 +#define MIC_BOOST_ENUM_MAX_STRLEN 10 + +static int ca0132_alt_mic_boost_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + char *sfx = "dB"; + char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = MIC_BOOST_NUM_OF_STEPS; + if (uinfo->value.enumerated.item >= MIC_BOOST_NUM_OF_STEPS) + uinfo->value.enumerated.item = MIC_BOOST_NUM_OF_STEPS - 1; + sprintf(namestr, "%d %s", (uinfo->value.enumerated.item * 10), sfx); + strcpy(uinfo->value.enumerated.name, namestr); + return 0; +} + +static int ca0132_alt_mic_boost_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->mic_boost_enum_val; + return 0; +} + +static int ca0132_alt_mic_boost_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = MIC_BOOST_NUM_OF_STEPS; + + if (sel >= items) + return 0; + + codec_dbg(codec, "ca0132_alt_mic_boost: boost=%d\n", + sel); + + spec->mic_boost_enum_val = sel; + + if (spec->in_enum_val != REAR_LINE_IN) + ca0132_alt_mic_boost_set(codec, spec->mic_boost_enum_val); + + return 1; +} + +/* + * Sound BlasterX AE-5 Headphone Gain Controls. + */ +#define AE5_HEADPHONE_GAIN_MAX 3 +static int ae5_headphone_gain_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + char *sfx = " Ohms)"; + char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = AE5_HEADPHONE_GAIN_MAX; + if (uinfo->value.enumerated.item >= AE5_HEADPHONE_GAIN_MAX) + uinfo->value.enumerated.item = AE5_HEADPHONE_GAIN_MAX - 1; + sprintf(namestr, "%s %s", + ae5_headphone_gain_presets[uinfo->value.enumerated.item].name, + sfx); + strcpy(uinfo->value.enumerated.name, namestr); + return 0; +} + +static int ae5_headphone_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->ae5_headphone_gain_val; + return 0; +} + +static int ae5_headphone_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = AE5_HEADPHONE_GAIN_MAX; + + if (sel >= items) + return 0; + + codec_dbg(codec, "ae5_headphone_gain: boost=%d\n", + sel); + + spec->ae5_headphone_gain_val = sel; + + if (spec->out_enum_val == HEADPHONE_OUT) + ae5_headphone_gain_set(codec, spec->ae5_headphone_gain_val); + + return 1; +} + +/* + * Sound BlasterX AE-5 sound filter enumerated control. + */ +#define AE5_SOUND_FILTER_MAX 3 + +static int ae5_sound_filter_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = AE5_SOUND_FILTER_MAX; + if (uinfo->value.enumerated.item >= AE5_SOUND_FILTER_MAX) + uinfo->value.enumerated.item = AE5_SOUND_FILTER_MAX - 1; + sprintf(namestr, "%s", + ae5_filter_presets[uinfo->value.enumerated.item].name); + strcpy(uinfo->value.enumerated.name, namestr); + return 0; +} + +static int ae5_sound_filter_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->ae5_filter_val; + return 0; +} + +static int ae5_sound_filter_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = AE5_SOUND_FILTER_MAX; + + if (sel >= items) + return 0; + + codec_dbg(codec, "ae5_sound_filter: %s\n", + ae5_filter_presets[sel].name); + + spec->ae5_filter_val = sel; + + ca0113_mmio_command_set_type2(codec, 0x48, 0x07, + ae5_filter_presets[sel].val); + + return 1; +} + +/* + * Input Select Control for alternative ca0132 codecs. This exists because + * front microphone has no auto-detect, and we need a way to set the rear + * as line-in + */ +static int ca0132_alt_input_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = IN_SRC_NUM_OF_INPUTS; + if (uinfo->value.enumerated.item >= IN_SRC_NUM_OF_INPUTS) + uinfo->value.enumerated.item = IN_SRC_NUM_OF_INPUTS - 1; + strcpy(uinfo->value.enumerated.name, + in_src_str[uinfo->value.enumerated.item]); + return 0; +} + +static int ca0132_alt_input_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->in_enum_val; + return 0; +} + +static int ca0132_alt_input_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = IN_SRC_NUM_OF_INPUTS; + + /* + * The AE-7 has no front microphone, so limit items to 2: rear mic and + * line-in. + */ + if (ca0132_quirk(spec) == QUIRK_AE7) + items = 2; + + if (sel >= items) + return 0; + + codec_dbg(codec, "ca0132_alt_input_select: sel=%d, preset=%s\n", + sel, in_src_str[sel]); + + spec->in_enum_val = sel; + + ca0132_alt_select_in(codec); + + return 1; +} + +/* Sound Blaster Z Output Select Control */ +static int ca0132_alt_output_select_get_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = NUM_OF_OUTPUTS; + if (uinfo->value.enumerated.item >= NUM_OF_OUTPUTS) + uinfo->value.enumerated.item = NUM_OF_OUTPUTS - 1; + strcpy(uinfo->value.enumerated.name, + out_type_str[uinfo->value.enumerated.item]); + return 0; +} + +static int ca0132_alt_output_select_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->out_enum_val; + return 0; +} + +static int ca0132_alt_output_select_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = NUM_OF_OUTPUTS; + unsigned int auto_jack; + + if (sel >= items) + return 0; + + codec_dbg(codec, "ca0132_alt_output_select: sel=%d, preset=%s\n", + sel, out_type_str[sel]); + + spec->out_enum_val = sel; + + auto_jack = spec->vnode_lswitch[VNID_HP_ASEL - VNODE_START_NID]; + + if (!auto_jack) + ca0132_alt_select_out(codec); + + return 1; +} + +/* Select surround output type: 2.1, 4.0, 4.1, or 5.1. */ +static int ca0132_alt_speaker_channel_cfg_get_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int items = SPEAKER_CHANNEL_CFG_COUNT; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = items; + if (uinfo->value.enumerated.item >= items) + uinfo->value.enumerated.item = items - 1; + strcpy(uinfo->value.enumerated.name, + speaker_channel_cfgs[uinfo->value.enumerated.item].name); + return 0; +} + +static int ca0132_alt_speaker_channel_cfg_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->channel_cfg_val; + return 0; +} + +static int ca0132_alt_speaker_channel_cfg_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = SPEAKER_CHANNEL_CFG_COUNT; + + if (sel >= items) + return 0; + + codec_dbg(codec, "ca0132_alt_speaker_channels: sel=%d, channels=%s\n", + sel, speaker_channel_cfgs[sel].name); + + spec->channel_cfg_val = sel; + + if (spec->out_enum_val == SPEAKER_OUT) + ca0132_alt_select_out(codec); + + return 1; +} + +/* + * Smart Volume output setting control. Three different settings, Normal, + * which takes the value from the smart volume slider. The two others, loud + * and night, disregard the slider value and have uneditable values. + */ +#define NUM_OF_SVM_SETTINGS 3 +static const char *const out_svm_set_enum_str[3] = {"Normal", "Loud", "Night" }; + +static int ca0132_alt_svm_setting_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = NUM_OF_SVM_SETTINGS; + if (uinfo->value.enumerated.item >= NUM_OF_SVM_SETTINGS) + uinfo->value.enumerated.item = NUM_OF_SVM_SETTINGS - 1; + strcpy(uinfo->value.enumerated.name, + out_svm_set_enum_str[uinfo->value.enumerated.item]); + return 0; +} + +static int ca0132_alt_svm_setting_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->smart_volume_setting; + return 0; +} + +static int ca0132_alt_svm_setting_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = NUM_OF_SVM_SETTINGS; + unsigned int idx = SMART_VOLUME - EFFECT_START_NID; + unsigned int tmp; + + if (sel >= items) + return 0; + + codec_dbg(codec, "ca0132_alt_svm_setting: sel=%d, preset=%s\n", + sel, out_svm_set_enum_str[sel]); + + spec->smart_volume_setting = sel; + + switch (sel) { + case 0: + tmp = FLOAT_ZERO; + break; + case 1: + tmp = FLOAT_ONE; + break; + case 2: + tmp = FLOAT_TWO; + break; + default: + tmp = FLOAT_ZERO; + break; + } + /* Req 2 is the Smart Volume Setting req. */ + dspio_set_uint_param(codec, ca0132_effects[idx].mid, + ca0132_effects[idx].reqs[2], tmp); + return 1; +} + +/* Sound Blaster Z EQ preset controls */ +static int ca0132_alt_eq_preset_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int items = ARRAY_SIZE(ca0132_alt_eq_presets); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = items; + if (uinfo->value.enumerated.item >= items) + uinfo->value.enumerated.item = items - 1; + strcpy(uinfo->value.enumerated.name, + ca0132_alt_eq_presets[uinfo->value.enumerated.item].name); + return 0; +} + +static int ca0132_alt_eq_preset_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->eq_preset_val; + return 0; +} + +static int ca0132_alt_eq_preset_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int i, err = 0; + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = ARRAY_SIZE(ca0132_alt_eq_presets); + + if (sel >= items) + return 0; + + codec_dbg(codec, "%s: sel=%d, preset=%s\n", __func__, sel, + ca0132_alt_eq_presets[sel].name); + /* + * Idx 0 is default. + * Default needs to qualify with CrystalVoice state. + */ + for (i = 0; i < EQ_PRESET_MAX_PARAM_COUNT; i++) { + err = dspio_set_uint_param(codec, ca0132_alt_eq_enum.mid, + ca0132_alt_eq_enum.reqs[i], + ca0132_alt_eq_presets[sel].vals[i]); + if (err < 0) + break; + } + + if (err >= 0) + spec->eq_preset_val = sel; + + return 1; +} + +static int ca0132_voicefx_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int items = ARRAY_SIZE(ca0132_voicefx_presets); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = items; + if (uinfo->value.enumerated.item >= items) + uinfo->value.enumerated.item = items - 1; + strcpy(uinfo->value.enumerated.name, + ca0132_voicefx_presets[uinfo->value.enumerated.item].name); + return 0; +} + +static int ca0132_voicefx_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->voicefx_val; + return 0; +} + +static int ca0132_voicefx_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int i, err = 0; + int sel = ucontrol->value.enumerated.item[0]; + + if (sel >= ARRAY_SIZE(ca0132_voicefx_presets)) + return 0; + + codec_dbg(codec, "ca0132_voicefx_put: sel=%d, preset=%s\n", + sel, ca0132_voicefx_presets[sel].name); + + /* + * Idx 0 is default. + * Default needs to qualify with CrystalVoice state. + */ + for (i = 0; i < VOICEFX_MAX_PARAM_COUNT; i++) { + err = dspio_set_uint_param(codec, ca0132_voicefx.mid, + ca0132_voicefx.reqs[i], + ca0132_voicefx_presets[sel].vals[i]); + if (err < 0) + break; + } + + if (err >= 0) { + spec->voicefx_val = sel; + /* enable voice fx */ + ca0132_voicefx_set(codec, (sel ? 1 : 0)); + } + + return 1; +} + +static int ca0132_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + int ch = get_amp_channels(kcontrol); + long *valp = ucontrol->value.integer.value; + + /* vnode */ + if ((nid >= VNODE_START_NID) && (nid < VNODE_END_NID)) { + if (ch & 1) { + *valp = spec->vnode_lswitch[nid - VNODE_START_NID]; + valp++; + } + if (ch & 2) { + *valp = spec->vnode_rswitch[nid - VNODE_START_NID]; + valp++; + } + return 0; + } + + /* effects, include PE and CrystalVoice */ + if ((nid >= EFFECT_START_NID) && (nid < EFFECT_END_NID)) { + *valp = spec->effects_switch[nid - EFFECT_START_NID]; + return 0; + } + + /* mic boost */ + if (nid == spec->input_pins[0]) { + *valp = spec->cur_mic_boost; + return 0; + } + + if (nid == ZXR_HEADPHONE_GAIN) { + *valp = spec->zxr_gain_set; + return 0; + } + + if (nid == SPEAKER_FULL_RANGE_FRONT || nid == SPEAKER_FULL_RANGE_REAR) { + *valp = spec->speaker_range_val[nid - SPEAKER_FULL_RANGE_FRONT]; + return 0; + } + + if (nid == BASS_REDIRECTION) { + *valp = spec->bass_redirection_val; + return 0; + } + + return 0; +} + +static int ca0132_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + int ch = get_amp_channels(kcontrol); + long *valp = ucontrol->value.integer.value; + int changed = 1; + + codec_dbg(codec, "ca0132_switch_put: nid=0x%x, val=%ld\n", + nid, *valp); + + snd_hda_power_up(codec); + /* vnode */ + if ((nid >= VNODE_START_NID) && (nid < VNODE_END_NID)) { + if (ch & 1) { + spec->vnode_lswitch[nid - VNODE_START_NID] = *valp; + valp++; + } + if (ch & 2) { + spec->vnode_rswitch[nid - VNODE_START_NID] = *valp; + valp++; + } + changed = ca0132_vnode_switch_set(kcontrol, ucontrol); + goto exit; + } + + /* PE */ + if (nid == PLAY_ENHANCEMENT) { + spec->effects_switch[nid - EFFECT_START_NID] = *valp; + changed = ca0132_pe_switch_set(codec); + goto exit; + } + + /* CrystalVoice */ + if (nid == CRYSTAL_VOICE) { + spec->effects_switch[nid - EFFECT_START_NID] = *valp; + changed = ca0132_cvoice_switch_set(codec); + goto exit; + } + + /* out and in effects */ + if (((nid >= OUT_EFFECT_START_NID) && (nid < OUT_EFFECT_END_NID)) || + ((nid >= IN_EFFECT_START_NID) && (nid < IN_EFFECT_END_NID))) { + spec->effects_switch[nid - EFFECT_START_NID] = *valp; + changed = ca0132_effects_set(codec, nid, *valp); + goto exit; + } + + /* mic boost */ + if (nid == spec->input_pins[0]) { + spec->cur_mic_boost = *valp; + if (ca0132_use_alt_functions(spec)) { + if (spec->in_enum_val != REAR_LINE_IN) + changed = ca0132_mic_boost_set(codec, *valp); + } else { + /* Mic boost does not apply to Digital Mic */ + if (spec->cur_mic_type != DIGITAL_MIC) + changed = ca0132_mic_boost_set(codec, *valp); + } + + goto exit; + } + + if (nid == ZXR_HEADPHONE_GAIN) { + spec->zxr_gain_set = *valp; + if (spec->cur_out_type == HEADPHONE_OUT) + changed = zxr_headphone_gain_set(codec, *valp); + else + changed = 0; + + goto exit; + } + + if (nid == SPEAKER_FULL_RANGE_FRONT || nid == SPEAKER_FULL_RANGE_REAR) { + spec->speaker_range_val[nid - SPEAKER_FULL_RANGE_FRONT] = *valp; + if (spec->cur_out_type == SPEAKER_OUT) + ca0132_alt_set_full_range_speaker(codec); + + changed = 0; + } + + if (nid == BASS_REDIRECTION) { + spec->bass_redirection_val = *valp; + if (spec->cur_out_type == SPEAKER_OUT) + ca0132_alt_surround_set_bass_redirection(codec, *valp); + + changed = 0; + } + +exit: + snd_hda_power_down(codec); + return changed; +} + +/* + * Volume related + */ +/* + * Sets the internal DSP decibel level to match the DAC for output, and the + * ADC for input. Currently only the SBZ sets dsp capture volume level, and + * all alternative codecs set DSP playback volume. + */ +static void ca0132_alt_dsp_volume_put(struct hda_codec *codec, hda_nid_t nid) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int dsp_dir; + unsigned int lookup_val; + + if (nid == VNID_SPK) + dsp_dir = DSP_VOL_OUT; + else + dsp_dir = DSP_VOL_IN; + + lookup_val = spec->vnode_lvol[nid - VNODE_START_NID]; + + dspio_set_uint_param(codec, + ca0132_alt_vol_ctls[dsp_dir].mid, + ca0132_alt_vol_ctls[dsp_dir].reqs[0], + float_vol_db_lookup[lookup_val]); + + lookup_val = spec->vnode_rvol[nid - VNODE_START_NID]; + + dspio_set_uint_param(codec, + ca0132_alt_vol_ctls[dsp_dir].mid, + ca0132_alt_vol_ctls[dsp_dir].reqs[1], + float_vol_db_lookup[lookup_val]); + + dspio_set_uint_param(codec, + ca0132_alt_vol_ctls[dsp_dir].mid, + ca0132_alt_vol_ctls[dsp_dir].reqs[2], FLOAT_ZERO); +} + +static int ca0132_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + int ch = get_amp_channels(kcontrol); + int dir = get_amp_direction(kcontrol); + unsigned long pval; + int err; + + switch (nid) { + case VNID_SPK: + /* follow shared_out info */ + nid = spec->shared_out_nid; + mutex_lock(&codec->control_mutex); + pval = kcontrol->private_value; + kcontrol->private_value = HDA_COMPOSE_AMP_VAL(nid, ch, 0, dir); + err = snd_hda_mixer_amp_volume_info(kcontrol, uinfo); + kcontrol->private_value = pval; + mutex_unlock(&codec->control_mutex); + break; + case VNID_MIC: + /* follow shared_mic info */ + nid = spec->shared_mic_nid; + mutex_lock(&codec->control_mutex); + pval = kcontrol->private_value; + kcontrol->private_value = HDA_COMPOSE_AMP_VAL(nid, ch, 0, dir); + err = snd_hda_mixer_amp_volume_info(kcontrol, uinfo); + kcontrol->private_value = pval; + mutex_unlock(&codec->control_mutex); + break; + default: + err = snd_hda_mixer_amp_volume_info(kcontrol, uinfo); + } + return err; +} + +static int ca0132_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + int ch = get_amp_channels(kcontrol); + long *valp = ucontrol->value.integer.value; + + /* store the left and right volume */ + if (ch & 1) { + *valp = spec->vnode_lvol[nid - VNODE_START_NID]; + valp++; + } + if (ch & 2) { + *valp = spec->vnode_rvol[nid - VNODE_START_NID]; + valp++; + } + return 0; +} + +static int ca0132_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + int ch = get_amp_channels(kcontrol); + long *valp = ucontrol->value.integer.value; + hda_nid_t shared_nid = 0; + bool effective; + int changed = 1; + + /* store the left and right volume */ + if (ch & 1) { + spec->vnode_lvol[nid - VNODE_START_NID] = *valp; + valp++; + } + if (ch & 2) { + spec->vnode_rvol[nid - VNODE_START_NID] = *valp; + valp++; + } + + /* if effective conditions, then update hw immediately. */ + effective = ca0132_is_vnode_effective(codec, nid, &shared_nid); + if (effective) { + int dir = get_amp_direction(kcontrol); + unsigned long pval; + + snd_hda_power_up(codec); + mutex_lock(&codec->control_mutex); + pval = kcontrol->private_value; + kcontrol->private_value = HDA_COMPOSE_AMP_VAL(shared_nid, ch, + 0, dir); + changed = snd_hda_mixer_amp_volume_put(kcontrol, ucontrol); + kcontrol->private_value = pval; + mutex_unlock(&codec->control_mutex); + snd_hda_power_down(codec); + } + + return changed; +} + +/* + * This function is the same as the one above, because using an if statement + * inside of the above volume control for the DSP volume would cause too much + * lag. This is a lot more smooth. + */ +static int ca0132_alt_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + int ch = get_amp_channels(kcontrol); + long *valp = ucontrol->value.integer.value; + hda_nid_t vnid = 0; + int changed; + + switch (nid) { + case 0x02: + vnid = VNID_SPK; + break; + case 0x07: + vnid = VNID_MIC; + break; + } + + /* store the left and right volume */ + if (ch & 1) { + spec->vnode_lvol[vnid - VNODE_START_NID] = *valp; + valp++; + } + if (ch & 2) { + spec->vnode_rvol[vnid - VNODE_START_NID] = *valp; + valp++; + } + + snd_hda_power_up(codec); + ca0132_alt_dsp_volume_put(codec, vnid); + mutex_lock(&codec->control_mutex); + changed = snd_hda_mixer_amp_volume_put(kcontrol, ucontrol); + mutex_unlock(&codec->control_mutex); + snd_hda_power_down(codec); + + return changed; +} + +static int ca0132_volume_tlv(struct snd_kcontrol *kcontrol, int op_flag, + unsigned int size, unsigned int __user *tlv) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + int ch = get_amp_channels(kcontrol); + int dir = get_amp_direction(kcontrol); + unsigned long pval; + int err; + + switch (nid) { + case VNID_SPK: + /* follow shared_out tlv */ + nid = spec->shared_out_nid; + mutex_lock(&codec->control_mutex); + pval = kcontrol->private_value; + kcontrol->private_value = HDA_COMPOSE_AMP_VAL(nid, ch, 0, dir); + err = snd_hda_mixer_amp_tlv(kcontrol, op_flag, size, tlv); + kcontrol->private_value = pval; + mutex_unlock(&codec->control_mutex); + break; + case VNID_MIC: + /* follow shared_mic tlv */ + nid = spec->shared_mic_nid; + mutex_lock(&codec->control_mutex); + pval = kcontrol->private_value; + kcontrol->private_value = HDA_COMPOSE_AMP_VAL(nid, ch, 0, dir); + err = snd_hda_mixer_amp_tlv(kcontrol, op_flag, size, tlv); + kcontrol->private_value = pval; + mutex_unlock(&codec->control_mutex); + break; + default: + err = snd_hda_mixer_amp_tlv(kcontrol, op_flag, size, tlv); + } + return err; +} + +/* Add volume slider control for effect level */ +static int ca0132_alt_add_effect_slider(struct hda_codec *codec, hda_nid_t nid, + const char *pfx, int dir) +{ + char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + int type = dir ? HDA_INPUT : HDA_OUTPUT; + struct snd_kcontrol_new knew = + HDA_CODEC_VOLUME_MONO(namestr, nid, 1, 0, type); + + sprintf(namestr, "FX: %s %s Volume", pfx, dirstr[dir]); + + knew.tlv.c = NULL; + + switch (nid) { + case XBASS_XOVER: + knew.info = ca0132_alt_xbass_xover_slider_info; + knew.get = ca0132_alt_xbass_xover_slider_ctl_get; + knew.put = ca0132_alt_xbass_xover_slider_put; + break; + default: + knew.info = ca0132_alt_effect_slider_info; + knew.get = ca0132_alt_slider_ctl_get; + knew.put = ca0132_alt_effect_slider_put; + knew.private_value = + HDA_COMPOSE_AMP_VAL(nid, 1, 0, type); + break; + } + + return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec)); +} + +/* + * Added FX: prefix for the alternative codecs, because otherwise the surround + * effect would conflict with the Surround sound volume control. Also seems more + * clear as to what the switches do. Left alone for others. + */ +static int add_fx_switch(struct hda_codec *codec, hda_nid_t nid, + const char *pfx, int dir) +{ + struct ca0132_spec *spec = codec->spec; + char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + int type = dir ? HDA_INPUT : HDA_OUTPUT; + struct snd_kcontrol_new knew = + CA0132_CODEC_MUTE_MONO(namestr, nid, 1, type); + /* If using alt_controls, add FX: prefix. But, don't add FX: + * prefix to OutFX or InFX enable controls. + */ + if (ca0132_use_alt_controls(spec) && (nid <= IN_EFFECT_END_NID)) + sprintf(namestr, "FX: %s %s Switch", pfx, dirstr[dir]); + else + sprintf(namestr, "%s %s Switch", pfx, dirstr[dir]); + + return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec)); +} + +static int add_voicefx(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + HDA_CODEC_MUTE_MONO(ca0132_voicefx.name, + VOICEFX, 1, 0, HDA_INPUT); + knew.info = ca0132_voicefx_info; + knew.get = ca0132_voicefx_get; + knew.put = ca0132_voicefx_put; + return snd_hda_ctl_add(codec, VOICEFX, snd_ctl_new1(&knew, codec)); +} + +/* Create the EQ Preset control */ +static int add_ca0132_alt_eq_presets(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + HDA_CODEC_MUTE_MONO(ca0132_alt_eq_enum.name, + EQ_PRESET_ENUM, 1, 0, HDA_OUTPUT); + knew.info = ca0132_alt_eq_preset_info; + knew.get = ca0132_alt_eq_preset_get; + knew.put = ca0132_alt_eq_preset_put; + return snd_hda_ctl_add(codec, EQ_PRESET_ENUM, + snd_ctl_new1(&knew, codec)); +} + +/* + * Add enumerated control for the three different settings of the smart volume + * output effect. Normal just uses the slider value, and loud and night are + * their own things that ignore that value. + */ +static int ca0132_alt_add_svm_enum(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + HDA_CODEC_MUTE_MONO("FX: Smart Volume Setting", + SMART_VOLUME_ENUM, 1, 0, HDA_OUTPUT); + knew.info = ca0132_alt_svm_setting_info; + knew.get = ca0132_alt_svm_setting_get; + knew.put = ca0132_alt_svm_setting_put; + return snd_hda_ctl_add(codec, SMART_VOLUME_ENUM, + snd_ctl_new1(&knew, codec)); + +} + +/* + * Create an Output Select enumerated control for codecs with surround + * out capabilities. + */ +static int ca0132_alt_add_output_enum(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + HDA_CODEC_MUTE_MONO("Output Select", + OUTPUT_SOURCE_ENUM, 1, 0, HDA_OUTPUT); + knew.info = ca0132_alt_output_select_get_info; + knew.get = ca0132_alt_output_select_get; + knew.put = ca0132_alt_output_select_put; + return snd_hda_ctl_add(codec, OUTPUT_SOURCE_ENUM, + snd_ctl_new1(&knew, codec)); +} + +/* + * Add a control for selecting channel count on speaker output. Setting this + * allows the DSP to do bass redirection and channel upmixing on surround + * configurations. + */ +static int ca0132_alt_add_speaker_channel_cfg_enum(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + HDA_CODEC_MUTE_MONO("Surround Channel Config", + SPEAKER_CHANNEL_CFG_ENUM, 1, 0, HDA_OUTPUT); + knew.info = ca0132_alt_speaker_channel_cfg_get_info; + knew.get = ca0132_alt_speaker_channel_cfg_get; + knew.put = ca0132_alt_speaker_channel_cfg_put; + return snd_hda_ctl_add(codec, SPEAKER_CHANNEL_CFG_ENUM, + snd_ctl_new1(&knew, codec)); +} + +/* + * Full range front stereo and rear surround switches. When these are set to + * full range, the lower frequencies from these channels are no longer + * redirected to the LFE channel. + */ +static int ca0132_alt_add_front_full_range_switch(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + CA0132_CODEC_MUTE_MONO("Full-Range Front Speakers", + SPEAKER_FULL_RANGE_FRONT, 1, HDA_OUTPUT); + + return snd_hda_ctl_add(codec, SPEAKER_FULL_RANGE_FRONT, + snd_ctl_new1(&knew, codec)); +} + +static int ca0132_alt_add_rear_full_range_switch(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + CA0132_CODEC_MUTE_MONO("Full-Range Rear Speakers", + SPEAKER_FULL_RANGE_REAR, 1, HDA_OUTPUT); + + return snd_hda_ctl_add(codec, SPEAKER_FULL_RANGE_REAR, + snd_ctl_new1(&knew, codec)); +} + +/* + * Bass redirection redirects audio below the crossover frequency to the LFE + * channel on speakers that are set as not being full-range. On configurations + * without an LFE channel, it does nothing. Bass redirection seems to be the + * replacement for X-Bass on configurations with an LFE channel. + */ +static int ca0132_alt_add_bass_redirection_crossover(struct hda_codec *codec) +{ + const char *namestr = "Bass Redirection Crossover"; + struct snd_kcontrol_new knew = + HDA_CODEC_VOLUME_MONO(namestr, BASS_REDIRECTION_XOVER, 1, 0, + HDA_OUTPUT); + + knew.tlv.c = NULL; + knew.info = ca0132_alt_xbass_xover_slider_info; + knew.get = ca0132_alt_xbass_xover_slider_ctl_get; + knew.put = ca0132_alt_xbass_xover_slider_put; + + return snd_hda_ctl_add(codec, BASS_REDIRECTION_XOVER, + snd_ctl_new1(&knew, codec)); +} + +static int ca0132_alt_add_bass_redirection_switch(struct hda_codec *codec) +{ + const char *namestr = "Bass Redirection"; + struct snd_kcontrol_new knew = + CA0132_CODEC_MUTE_MONO(namestr, BASS_REDIRECTION, 1, + HDA_OUTPUT); + + return snd_hda_ctl_add(codec, BASS_REDIRECTION, + snd_ctl_new1(&knew, codec)); +} + +/* + * Create an Input Source enumerated control for the alternate ca0132 codecs + * because the front microphone has no auto-detect, and Line-in has to be set + * somehow. + */ +static int ca0132_alt_add_input_enum(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + HDA_CODEC_MUTE_MONO("Input Source", + INPUT_SOURCE_ENUM, 1, 0, HDA_INPUT); + knew.info = ca0132_alt_input_source_info; + knew.get = ca0132_alt_input_source_get; + knew.put = ca0132_alt_input_source_put; + return snd_hda_ctl_add(codec, INPUT_SOURCE_ENUM, + snd_ctl_new1(&knew, codec)); +} + +/* + * Add mic boost enumerated control. Switches through 0dB to 30dB. This adds + * more control than the original mic boost, which is either full 30dB or off. + */ +static int ca0132_alt_add_mic_boost_enum(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + HDA_CODEC_MUTE_MONO("Mic Boost Capture Switch", + MIC_BOOST_ENUM, 1, 0, HDA_INPUT); + knew.info = ca0132_alt_mic_boost_info; + knew.get = ca0132_alt_mic_boost_get; + knew.put = ca0132_alt_mic_boost_put; + return snd_hda_ctl_add(codec, MIC_BOOST_ENUM, + snd_ctl_new1(&knew, codec)); + +} + +/* + * Add headphone gain enumerated control for the AE-5. This switches between + * three modes, low, medium, and high. When non-headphone outputs are selected, + * it is automatically set to high. This is the same behavior as Windows. + */ +static int ae5_add_headphone_gain_enum(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + HDA_CODEC_MUTE_MONO("AE-5: Headphone Gain", + AE5_HEADPHONE_GAIN_ENUM, 1, 0, HDA_OUTPUT); + knew.info = ae5_headphone_gain_info; + knew.get = ae5_headphone_gain_get; + knew.put = ae5_headphone_gain_put; + return snd_hda_ctl_add(codec, AE5_HEADPHONE_GAIN_ENUM, + snd_ctl_new1(&knew, codec)); +} + +/* + * Add sound filter enumerated control for the AE-5. This adds three different + * settings: Slow Roll Off, Minimum Phase, and Fast Roll Off. From what I've + * read into it, it changes the DAC's interpolation filter. + */ +static int ae5_add_sound_filter_enum(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + HDA_CODEC_MUTE_MONO("AE-5: Sound Filter", + AE5_SOUND_FILTER_ENUM, 1, 0, HDA_OUTPUT); + knew.info = ae5_sound_filter_info; + knew.get = ae5_sound_filter_get; + knew.put = ae5_sound_filter_put; + return snd_hda_ctl_add(codec, AE5_SOUND_FILTER_ENUM, + snd_ctl_new1(&knew, codec)); +} + +static int zxr_add_headphone_gain_switch(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + CA0132_CODEC_MUTE_MONO("ZxR: 600 Ohm Gain", + ZXR_HEADPHONE_GAIN, 1, HDA_OUTPUT); + + return snd_hda_ctl_add(codec, ZXR_HEADPHONE_GAIN, + snd_ctl_new1(&knew, codec)); +} + +/* + * Need to create follower controls for the alternate codecs that have surround + * capabilities. + */ +static const char * const ca0132_alt_follower_pfxs[] = { + "Front", "Surround", "Center", "LFE", NULL, +}; + +/* + * Also need special channel map, because the default one is incorrect. + * I think this has to do with the pin for rear surround being 0x11, + * and the center/lfe being 0x10. Usually the pin order is the opposite. + */ +static const struct snd_pcm_chmap_elem ca0132_alt_chmaps[] = { + { .channels = 2, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } }, + { .channels = 4, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { .channels = 6, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { } +}; + +/* Add the correct chmap for streams with 6 channels. */ +static void ca0132_alt_add_chmap_ctls(struct hda_codec *codec) +{ + int err = 0; + struct hda_pcm *pcm; + + list_for_each_entry(pcm, &codec->pcm_list_head, list) { + struct hda_pcm_stream *hinfo = + &pcm->stream[SNDRV_PCM_STREAM_PLAYBACK]; + struct snd_pcm_chmap *chmap; + const struct snd_pcm_chmap_elem *elem; + + elem = ca0132_alt_chmaps; + if (hinfo->channels_max == 6) { + err = snd_pcm_add_chmap_ctls(pcm->pcm, + SNDRV_PCM_STREAM_PLAYBACK, + elem, hinfo->channels_max, 0, &chmap); + if (err < 0) + codec_dbg(codec, "snd_pcm_add_chmap_ctls failed!"); + } + } +} + +/* + * When changing Node IDs for Mixer Controls below, make sure to update + * Node IDs in ca0132_config() as well. + */ +static const struct snd_kcontrol_new ca0132_mixer[] = { + CA0132_CODEC_VOL("Master Playback Volume", VNID_SPK, HDA_OUTPUT), + CA0132_CODEC_MUTE("Master Playback Switch", VNID_SPK, HDA_OUTPUT), + CA0132_CODEC_VOL("Capture Volume", VNID_MIC, HDA_INPUT), + CA0132_CODEC_MUTE("Capture Switch", VNID_MIC, HDA_INPUT), + HDA_CODEC_VOLUME("Analog-Mic2 Capture Volume", 0x08, 0, HDA_INPUT), + HDA_CODEC_MUTE("Analog-Mic2 Capture Switch", 0x08, 0, HDA_INPUT), + HDA_CODEC_VOLUME("What U Hear Capture Volume", 0x0a, 0, HDA_INPUT), + HDA_CODEC_MUTE("What U Hear Capture Switch", 0x0a, 0, HDA_INPUT), + CA0132_CODEC_MUTE_MONO("Mic1-Boost (30dB) Capture Switch", + 0x12, 1, HDA_INPUT), + CA0132_CODEC_MUTE_MONO("HP/Speaker Playback Switch", + VNID_HP_SEL, 1, HDA_OUTPUT), + CA0132_CODEC_MUTE_MONO("AMic1/DMic Capture Switch", + VNID_AMIC1_SEL, 1, HDA_INPUT), + CA0132_CODEC_MUTE_MONO("HP/Speaker Auto Detect Playback Switch", + VNID_HP_ASEL, 1, HDA_OUTPUT), + CA0132_CODEC_MUTE_MONO("AMic1/DMic Auto Detect Capture Switch", + VNID_AMIC1_ASEL, 1, HDA_INPUT), + { } /* end */ +}; + +/* + * Desktop specific control mixer. Removes auto-detect for mic, and adds + * surround controls. Also sets both the Front Playback and Capture Volume + * controls to alt so they set the DSP's decibel level. + */ +static const struct snd_kcontrol_new desktop_mixer[] = { + CA0132_ALT_CODEC_VOL("Front Playback Volume", 0x02, HDA_OUTPUT), + CA0132_CODEC_MUTE("Front Playback Switch", VNID_SPK, HDA_OUTPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x04, 0, HDA_OUTPUT), + HDA_CODEC_MUTE("Surround Playback Switch", 0x04, 0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x03, 1, 0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x03, 1, 0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x03, 2, 0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x03, 2, 0, HDA_OUTPUT), + CA0132_ALT_CODEC_VOL("Capture Volume", 0x07, HDA_INPUT), + CA0132_CODEC_MUTE("Capture Switch", VNID_MIC, HDA_INPUT), + HDA_CODEC_VOLUME("What U Hear Capture Volume", 0x0a, 0, HDA_INPUT), + HDA_CODEC_MUTE("What U Hear Capture Switch", 0x0a, 0, HDA_INPUT), + CA0132_CODEC_MUTE_MONO("HP/Speaker Auto Detect Playback Switch", + VNID_HP_ASEL, 1, HDA_OUTPUT), + { } /* end */ +}; + +/* + * Same as the Sound Blaster Z, except doesn't use the alt volume for capture + * because it doesn't set decibel levels for the DSP for capture. + */ +static const struct snd_kcontrol_new r3di_mixer[] = { + CA0132_ALT_CODEC_VOL("Front Playback Volume", 0x02, HDA_OUTPUT), + CA0132_CODEC_MUTE("Front Playback Switch", VNID_SPK, HDA_OUTPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x04, 0, HDA_OUTPUT), + HDA_CODEC_MUTE("Surround Playback Switch", 0x04, 0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x03, 1, 0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x03, 1, 0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x03, 2, 0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x03, 2, 0, HDA_OUTPUT), + CA0132_CODEC_VOL("Capture Volume", VNID_MIC, HDA_INPUT), + CA0132_CODEC_MUTE("Capture Switch", VNID_MIC, HDA_INPUT), + HDA_CODEC_VOLUME("What U Hear Capture Volume", 0x0a, 0, HDA_INPUT), + HDA_CODEC_MUTE("What U Hear Capture Switch", 0x0a, 0, HDA_INPUT), + CA0132_CODEC_MUTE_MONO("HP/Speaker Auto Detect Playback Switch", + VNID_HP_ASEL, 1, HDA_OUTPUT), + { } /* end */ +}; + +static int ca0132_build_controls(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + int i, num_fx, num_sliders; + int err = 0; + + /* Add Mixer controls */ + for (i = 0; i < spec->num_mixers; i++) { + err = snd_hda_add_new_ctls(codec, spec->mixers[i]); + if (err < 0) + return err; + } + /* Setup vmaster with surround followers for desktop ca0132 devices */ + if (ca0132_use_alt_functions(spec)) { + snd_hda_set_vmaster_tlv(codec, spec->dacs[0], HDA_OUTPUT, + spec->tlv); + snd_hda_add_vmaster(codec, "Master Playback Volume", + spec->tlv, ca0132_alt_follower_pfxs, + "Playback Volume", 0); + err = __snd_hda_add_vmaster(codec, "Master Playback Switch", + NULL, ca0132_alt_follower_pfxs, + "Playback Switch", + true, 0, &spec->vmaster_mute.sw_kctl); + if (err < 0) + return err; + } + + /* Add in and out effects controls. + * VoiceFX, PE and CrystalVoice are added separately. + */ + num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT; + for (i = 0; i < num_fx; i++) { + /* Desktop cards break if Echo Cancellation is used. */ + if (ca0132_use_pci_mmio(spec)) { + if (i == (ECHO_CANCELLATION - IN_EFFECT_START_NID + + OUT_EFFECTS_COUNT)) + continue; + } + + err = add_fx_switch(codec, ca0132_effects[i].nid, + ca0132_effects[i].name, + ca0132_effects[i].direct); + if (err < 0) + return err; + } + /* + * If codec has use_alt_controls set to true, add effect level sliders, + * EQ presets, and Smart Volume presets. Also, change names to add FX + * prefix, and change PlayEnhancement and CrystalVoice to match. + */ + if (ca0132_use_alt_controls(spec)) { + err = ca0132_alt_add_svm_enum(codec); + if (err < 0) + return err; + + err = add_ca0132_alt_eq_presets(codec); + if (err < 0) + return err; + + err = add_fx_switch(codec, PLAY_ENHANCEMENT, + "Enable OutFX", 0); + if (err < 0) + return err; + + err = add_fx_switch(codec, CRYSTAL_VOICE, + "Enable InFX", 1); + if (err < 0) + return err; + + num_sliders = OUT_EFFECTS_COUNT - 1; + for (i = 0; i < num_sliders; i++) { + err = ca0132_alt_add_effect_slider(codec, + ca0132_effects[i].nid, + ca0132_effects[i].name, + ca0132_effects[i].direct); + if (err < 0) + return err; + } + + err = ca0132_alt_add_effect_slider(codec, XBASS_XOVER, + "X-Bass Crossover", EFX_DIR_OUT); + + if (err < 0) + return err; + } else { + err = add_fx_switch(codec, PLAY_ENHANCEMENT, + "PlayEnhancement", 0); + if (err < 0) + return err; + + err = add_fx_switch(codec, CRYSTAL_VOICE, + "CrystalVoice", 1); + if (err < 0) + return err; + } + err = add_voicefx(codec); + if (err < 0) + return err; + + /* + * If the codec uses alt_functions, you need the enumerated controls + * to select the new outputs and inputs, plus add the new mic boost + * setting control. + */ + if (ca0132_use_alt_functions(spec)) { + err = ca0132_alt_add_output_enum(codec); + if (err < 0) + return err; + err = ca0132_alt_add_speaker_channel_cfg_enum(codec); + if (err < 0) + return err; + err = ca0132_alt_add_front_full_range_switch(codec); + if (err < 0) + return err; + err = ca0132_alt_add_rear_full_range_switch(codec); + if (err < 0) + return err; + err = ca0132_alt_add_bass_redirection_crossover(codec); + if (err < 0) + return err; + err = ca0132_alt_add_bass_redirection_switch(codec); + if (err < 0) + return err; + err = ca0132_alt_add_mic_boost_enum(codec); + if (err < 0) + return err; + /* + * ZxR only has microphone input, there is no front panel + * header on the card, and aux-in is handled by the DBPro board. + */ + if (ca0132_quirk(spec) != QUIRK_ZXR) { + err = ca0132_alt_add_input_enum(codec); + if (err < 0) + return err; + } + } + + switch (ca0132_quirk(spec)) { + case QUIRK_AE5: + case QUIRK_AE7: + err = ae5_add_headphone_gain_enum(codec); + if (err < 0) + return err; + err = ae5_add_sound_filter_enum(codec); + if (err < 0) + return err; + break; + case QUIRK_ZXR: + err = zxr_add_headphone_gain_switch(codec); + if (err < 0) + return err; + break; + default: + break; + } + +#ifdef ENABLE_TUNING_CONTROLS + add_tuning_ctls(codec); +#endif + + err = snd_hda_jack_add_kctls(codec, &spec->autocfg); + if (err < 0) + return err; + + if (spec->dig_out) { + err = snd_hda_create_spdif_out_ctls(codec, spec->dig_out, + spec->dig_out); + if (err < 0) + return err; + err = snd_hda_create_spdif_share_sw(codec, &spec->multiout); + if (err < 0) + return err; + /* spec->multiout.share_spdif = 1; */ + } + + if (spec->dig_in) { + err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in); + if (err < 0) + return err; + } + + if (ca0132_use_alt_functions(spec)) + ca0132_alt_add_chmap_ctls(codec); + + return 0; +} + +static int dbpro_build_controls(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + int err = 0; + + if (spec->dig_out) { + err = snd_hda_create_spdif_out_ctls(codec, spec->dig_out, + spec->dig_out); + if (err < 0) + return err; + } + + if (spec->dig_in) { + err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in); + if (err < 0) + return err; + } + + return 0; +} + +/* + * PCM + */ +static const struct hda_pcm_stream ca0132_pcm_analog_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 6, + .ops = { + .prepare = ca0132_playback_pcm_prepare, + .cleanup = ca0132_playback_pcm_cleanup, + .get_delay = ca0132_playback_pcm_delay, + }, +}; + +static const struct hda_pcm_stream ca0132_pcm_analog_capture = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + .ops = { + .prepare = ca0132_capture_pcm_prepare, + .cleanup = ca0132_capture_pcm_cleanup, + .get_delay = ca0132_capture_pcm_delay, + }, +}; + +static const struct hda_pcm_stream ca0132_pcm_digital_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + .ops = { + .open = ca0132_dig_playback_pcm_open, + .close = ca0132_dig_playback_pcm_close, + .prepare = ca0132_dig_playback_pcm_prepare, + .cleanup = ca0132_dig_playback_pcm_cleanup + }, +}; + +static const struct hda_pcm_stream ca0132_pcm_digital_capture = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, +}; + +static int ca0132_build_pcms(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + struct hda_pcm *info; + + info = snd_hda_codec_pcm_new(codec, "CA0132 Analog"); + if (!info) + return -ENOMEM; + if (ca0132_use_alt_functions(spec)) { + info->own_chmap = true; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].chmap + = ca0132_alt_chmaps; + } + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = ca0132_pcm_analog_playback; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->dacs[0]; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = + spec->multiout.max_channels; + info->stream[SNDRV_PCM_STREAM_CAPTURE] = ca0132_pcm_analog_capture; + info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = 1; + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adcs[0]; + + /* With the DSP enabled, desktops don't use this ADC. */ + if (!ca0132_use_alt_functions(spec)) { + info = snd_hda_codec_pcm_new(codec, "CA0132 Analog Mic-In2"); + if (!info) + return -ENOMEM; + info->stream[SNDRV_PCM_STREAM_CAPTURE] = + ca0132_pcm_analog_capture; + info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = 1; + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adcs[1]; + } + + info = snd_hda_codec_pcm_new(codec, "CA0132 What U Hear"); + if (!info) + return -ENOMEM; + info->stream[SNDRV_PCM_STREAM_CAPTURE] = ca0132_pcm_analog_capture; + info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = 1; + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adcs[2]; + + if (!spec->dig_out && !spec->dig_in) + return 0; + + info = snd_hda_codec_pcm_new(codec, "CA0132 Digital"); + if (!info) + return -ENOMEM; + info->pcm_type = HDA_PCM_TYPE_SPDIF; + if (spec->dig_out) { + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = + ca0132_pcm_digital_playback; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->dig_out; + } + if (spec->dig_in) { + info->stream[SNDRV_PCM_STREAM_CAPTURE] = + ca0132_pcm_digital_capture; + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in; + } + + return 0; +} + +static int dbpro_build_pcms(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + struct hda_pcm *info; + + info = snd_hda_codec_pcm_new(codec, "CA0132 Alt Analog"); + if (!info) + return -ENOMEM; + info->stream[SNDRV_PCM_STREAM_CAPTURE] = ca0132_pcm_analog_capture; + info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = 1; + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adcs[0]; + + + if (!spec->dig_out && !spec->dig_in) + return 0; + + info = snd_hda_codec_pcm_new(codec, "CA0132 Digital"); + if (!info) + return -ENOMEM; + info->pcm_type = HDA_PCM_TYPE_SPDIF; + if (spec->dig_out) { + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = + ca0132_pcm_digital_playback; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->dig_out; + } + if (spec->dig_in) { + info->stream[SNDRV_PCM_STREAM_CAPTURE] = + ca0132_pcm_digital_capture; + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in; + } + + return 0; +} + +static void init_output(struct hda_codec *codec, hda_nid_t pin, hda_nid_t dac) +{ + if (pin) { + snd_hda_set_pin_ctl(codec, pin, PIN_HP); + if (get_wcaps(codec, pin) & AC_WCAP_OUT_AMP) + snd_hda_codec_write(codec, pin, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_OUT_UNMUTE); + } + if (dac && (get_wcaps(codec, dac) & AC_WCAP_OUT_AMP)) + snd_hda_codec_write(codec, dac, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO); +} + +static void init_input(struct hda_codec *codec, hda_nid_t pin, hda_nid_t adc) +{ + if (pin) { + snd_hda_set_pin_ctl(codec, pin, PIN_VREF80); + if (get_wcaps(codec, pin) & AC_WCAP_IN_AMP) + snd_hda_codec_write(codec, pin, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_UNMUTE(0)); + } + if (adc && (get_wcaps(codec, adc) & AC_WCAP_IN_AMP)) { + snd_hda_codec_write(codec, adc, 0, AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_UNMUTE(0)); + + /* init to 0 dB and unmute. */ + snd_hda_codec_amp_stereo(codec, adc, HDA_INPUT, 0, + HDA_AMP_VOLMASK, 0x5a); + snd_hda_codec_amp_stereo(codec, adc, HDA_INPUT, 0, + HDA_AMP_MUTE, 0); + } +} + +static void refresh_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir) +{ + unsigned int caps; + + caps = snd_hda_param_read(codec, nid, dir == HDA_OUTPUT ? + AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP); + snd_hda_override_amp_caps(codec, nid, dir, caps); +} + +/* + * Switch between Digital built-in mic and analog mic. + */ +static void ca0132_set_dmic(struct hda_codec *codec, int enable) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp; + u8 val; + unsigned int oldval; + + codec_dbg(codec, "ca0132_set_dmic: enable=%d\n", enable); + + oldval = stop_mic1(codec); + ca0132_set_vipsource(codec, 0); + if (enable) { + /* set DMic input as 2-ch */ + tmp = FLOAT_TWO; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + val = spec->dmic_ctl; + val |= 0x80; + snd_hda_codec_write(codec, spec->input_pins[0], 0, + VENDOR_CHIPIO_DMIC_CTL_SET, val); + + if (!(spec->dmic_ctl & 0x20)) + chipio_set_control_flag(codec, CONTROL_FLAG_DMIC, 1); + } else { + /* set AMic input as mono */ + tmp = FLOAT_ONE; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + val = spec->dmic_ctl; + /* clear bit7 and bit5 to disable dmic */ + val &= 0x5f; + snd_hda_codec_write(codec, spec->input_pins[0], 0, + VENDOR_CHIPIO_DMIC_CTL_SET, val); + + if (!(spec->dmic_ctl & 0x20)) + chipio_set_control_flag(codec, CONTROL_FLAG_DMIC, 0); + } + ca0132_set_vipsource(codec, 1); + resume_mic1(codec, oldval); +} + +/* + * Initialization for Digital Mic. + */ +static void ca0132_init_dmic(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + u8 val; + + /* Setup Digital Mic here, but don't enable. + * Enable based on jack detect. + */ + + /* MCLK uses MPIO1, set to enable. + * Bit 2-0: MPIO select + * Bit 3: set to disable + * Bit 7-4: reserved + */ + val = 0x01; + snd_hda_codec_write(codec, spec->input_pins[0], 0, + VENDOR_CHIPIO_DMIC_MCLK_SET, val); + + /* Data1 uses MPIO3. Data2 not use + * Bit 2-0: Data1 MPIO select + * Bit 3: set disable Data1 + * Bit 6-4: Data2 MPIO select + * Bit 7: set disable Data2 + */ + val = 0x83; + snd_hda_codec_write(codec, spec->input_pins[0], 0, + VENDOR_CHIPIO_DMIC_PIN_SET, val); + + /* Use Ch-0 and Ch-1. Rate is 48K, mode 1. Disable DMic first. + * Bit 3-0: Channel mask + * Bit 4: set for 48KHz, clear for 32KHz + * Bit 5: mode + * Bit 6: set to select Data2, clear for Data1 + * Bit 7: set to enable DMic, clear for AMic + */ + if (ca0132_quirk(spec) == QUIRK_ALIENWARE_M17XR4) + val = 0x33; + else + val = 0x23; + /* keep a copy of dmic ctl val for enable/disable dmic purpuse */ + spec->dmic_ctl = val; + snd_hda_codec_write(codec, spec->input_pins[0], 0, + VENDOR_CHIPIO_DMIC_CTL_SET, val); +} + +/* + * Initialization for Analog Mic 2 + */ +static void ca0132_init_analog_mic2(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + mutex_lock(&spec->chipio_mutex); + + chipio_8051_write_exram_no_mutex(codec, 0x1920, 0x00); + chipio_8051_write_exram_no_mutex(codec, 0x192d, 0x00); + + mutex_unlock(&spec->chipio_mutex); +} + +static void ca0132_refresh_widget_caps(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + int i; + + codec_dbg(codec, "ca0132_refresh_widget_caps.\n"); + snd_hda_codec_update_widgets(codec); + + for (i = 0; i < spec->multiout.num_dacs; i++) + refresh_amp_caps(codec, spec->dacs[i], HDA_OUTPUT); + + for (i = 0; i < spec->num_outputs; i++) + refresh_amp_caps(codec, spec->out_pins[i], HDA_OUTPUT); + + for (i = 0; i < spec->num_inputs; i++) { + refresh_amp_caps(codec, spec->adcs[i], HDA_INPUT); + refresh_amp_caps(codec, spec->input_pins[i], HDA_INPUT); + } +} + + +/* If there is an active channel for some reason, find it and free it. */ +static void ca0132_alt_free_active_dma_channels(struct hda_codec *codec) +{ + unsigned int i, tmp; + int status; + + /* Read active DSPDMAC channel register. */ + status = chipio_read(codec, DSPDMAC_CHNLSTART_MODULE_OFFSET, &tmp); + if (status >= 0) { + /* AND against 0xfff to get the active channel bits. */ + tmp = tmp & 0xfff; + + /* If there are no active channels, nothing to free. */ + if (!tmp) + return; + } else { + codec_dbg(codec, "%s: Failed to read active DSP DMA channel register.\n", + __func__); + return; + } + + /* + * Check each DSP DMA channel for activity, and if the channel is + * active, free it. + */ + for (i = 0; i < DSPDMAC_DMA_CFG_CHANNEL_COUNT; i++) { + if (dsp_is_dma_active(codec, i)) { + status = dspio_free_dma_chan(codec, i); + if (status < 0) + codec_dbg(codec, "%s: Failed to free active DSP DMA channel %d.\n", + __func__, i); + } + } +} + +/* + * In the case of CT_EXTENSIONS_ENABLE being set to 1, and the DSP being in + * use, audio is no longer routed directly to the DAC/ADC from the HDA stream. + * Instead, audio is now routed through the DSP's DMA controllers, which + * the DSP is tasked with setting up itself. Through debugging, it seems the + * cause of most of the no-audio on startup issues were due to improperly + * configured DSP DMA channels. + * + * Normally, the DSP configures these the first time an HDA audio stream is + * started post DSP firmware download. That is why creating a 'dummy' stream + * worked in fixing the audio in some cases. This works most of the time, but + * sometimes if a stream is started/stopped before the DSP can setup the DMA + * configuration registers, it ends up in a broken state. Issues can also + * arise if streams are started in an unusual order, i.e the audio output dma + * channel being sandwiched between the mic1 and mic2 dma channels. + * + * The solution to this is to make sure that the DSP has no DMA channels + * in use post DSP firmware download, and then to manually start each default + * DSP stream that uses the DMA channels. These are 0x0c, the audio output + * stream, 0x03, analog mic 1, and 0x04, analog mic 2. + */ +static void ca0132_alt_start_dsp_audio_streams(struct hda_codec *codec) +{ + static const unsigned int dsp_dma_stream_ids[] = { 0x0c, 0x03, 0x04 }; + struct ca0132_spec *spec = codec->spec; + unsigned int i, tmp; + + /* + * Check if any of the default streams are active, and if they are, + * stop them. + */ + mutex_lock(&spec->chipio_mutex); + + for (i = 0; i < ARRAY_SIZE(dsp_dma_stream_ids); i++) { + chipio_get_stream_control(codec, dsp_dma_stream_ids[i], &tmp); + + if (tmp) { + chipio_set_stream_control(codec, + dsp_dma_stream_ids[i], 0); + } + } + + mutex_unlock(&spec->chipio_mutex); + + /* + * If all DSP streams are inactive, there should be no active DSP DMA + * channels. Check and make sure this is the case, and if it isn't, + * free any active channels. + */ + ca0132_alt_free_active_dma_channels(codec); + + mutex_lock(&spec->chipio_mutex); + + /* Make sure stream 0x0c is six channels. */ + chipio_set_stream_channels(codec, 0x0c, 6); + + for (i = 0; i < ARRAY_SIZE(dsp_dma_stream_ids); i++) { + chipio_set_stream_control(codec, + dsp_dma_stream_ids[i], 1); + + /* Give the DSP some time to setup the DMA channel. */ + msleep(75); + } + + mutex_unlock(&spec->chipio_mutex); +} + +/* + * The region of ChipIO memory from 0x190000-0x1903fc is a sort of 'audio + * router', where each entry represents a 48khz audio channel, with a format + * of an 8-bit destination, an 8-bit source, and an unknown 2-bit number + * value. The 2-bit number value is seemingly 0 if inactive, 1 if active, + * and 3 if it's using Sample Rate Converter ports. + * An example is: + * 0x0001f8c0 + * In this case, f8 is the destination, and c0 is the source. The number value + * is 1. + * This region of memory is normally managed internally by the 8051, where + * the region of exram memory from 0x1477-0x1575 has each byte represent an + * entry within the 0x190000 range, and when a range of entries is in use, the + * ending value is overwritten with 0xff. + * 0x1578 in exram is a table of 0x25 entries, corresponding to the ChipIO + * streamID's, where each entry is a starting 0x190000 port offset. + * 0x159d in exram is the same as 0x1578, except it contains the ending port + * offset for the corresponding streamID. + * + * On certain cards, such as the SBZ/ZxR/AE7, these are originally setup by + * the 8051, then manually overwritten to remap the ports to work with the + * new DACs. + * + * Currently known portID's: + * 0x00-0x1f: HDA audio stream input/output ports. + * 0x80-0xbf: Sample rate converter input/outputs. Only valid ports seem to + * have the lower-nibble set to 0x1, 0x2, and 0x9. + * 0xc0-0xdf: DSP DMA input/output ports. Dynamically assigned. + * 0xe0-0xff: DAC/ADC audio input/output ports. + * + * Currently known streamID's: + * 0x03: Mic1 ADC to DSP. + * 0x04: Mic2 ADC to DSP. + * 0x05: HDA node 0x02 audio stream to DSP. + * 0x0f: DSP Mic exit to HDA node 0x07. + * 0x0c: DSP processed audio to DACs. + * 0x14: DAC0, front L/R. + * + * It is possible to route the HDA audio streams directly to the DAC and + * bypass the DSP entirely, with the only downside being that since the DSP + * does volume control, the only volume control you'll get is through PCM on + * the PC side, in the same way volume is handled for optical out. This may be + * useful for debugging. + */ +static void chipio_remap_stream(struct hda_codec *codec, + const struct chipio_stream_remap_data *remap_data) +{ + unsigned int i, stream_offset; + + /* Get the starting port for the stream to be remapped. */ + chipio_8051_read_exram(codec, 0x1578 + remap_data->stream_id, + &stream_offset); + + /* + * Check if the stream's port value is 0xff, because the 8051 may not + * have gotten around to setting up the stream yet. Wait until it's + * setup to remap it's ports. + */ + if (stream_offset == 0xff) { + for (i = 0; i < 5; i++) { + msleep(25); + + chipio_8051_read_exram(codec, 0x1578 + remap_data->stream_id, + &stream_offset); + + if (stream_offset != 0xff) + break; + } + } + + if (stream_offset == 0xff) { + codec_info(codec, "%s: Stream 0x%02x ports aren't allocated, remap failed!\n", + __func__, remap_data->stream_id); + return; + } + + /* Offset isn't in bytes, its in 32-bit words, so multiply it by 4. */ + stream_offset *= 0x04; + stream_offset += 0x190000; + + for (i = 0; i < remap_data->count; i++) { + chipio_write_no_mutex(codec, + stream_offset + remap_data->offset[i], + remap_data->value[i]); + } + + /* Update stream map configuration. */ + chipio_write_no_mutex(codec, 0x19042c, 0x00000001); +} + +/* + * Default speaker tuning values setup for alternative codecs. + */ +static const unsigned int sbz_default_delay_values[] = { + /* Non-zero values are floating point 0.000198. */ + 0x394f9e38, 0x394f9e38, 0x00000000, 0x00000000, 0x00000000, 0x00000000 +}; + +static const unsigned int zxr_default_delay_values[] = { + /* Non-zero values are floating point 0.000220. */ + 0x00000000, 0x00000000, 0x3966afcd, 0x3966afcd, 0x3966afcd, 0x3966afcd +}; + +static const unsigned int ae5_default_delay_values[] = { + /* Non-zero values are floating point 0.000100. */ + 0x00000000, 0x00000000, 0x38d1b717, 0x38d1b717, 0x38d1b717, 0x38d1b717 +}; + +/* + * If we never change these, probably only need them on initialization. + */ +static void ca0132_alt_init_speaker_tuning(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int i, tmp, start_req, end_req; + const unsigned int *values; + + switch (ca0132_quirk(spec)) { + case QUIRK_SBZ: + values = sbz_default_delay_values; + break; + case QUIRK_ZXR: + values = zxr_default_delay_values; + break; + case QUIRK_AE5: + case QUIRK_AE7: + values = ae5_default_delay_values; + break; + default: + values = sbz_default_delay_values; + break; + } + + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x96, SPEAKER_TUNING_ENABLE_CENTER_EQ, tmp); + + start_req = SPEAKER_TUNING_FRONT_LEFT_VOL_LEVEL; + end_req = SPEAKER_TUNING_REAR_RIGHT_VOL_LEVEL; + for (i = start_req; i < end_req + 1; i++) + dspio_set_uint_param(codec, 0x96, i, tmp); + + start_req = SPEAKER_TUNING_FRONT_LEFT_INVERT; + end_req = SPEAKER_TUNING_REAR_RIGHT_INVERT; + for (i = start_req; i < end_req + 1; i++) + dspio_set_uint_param(codec, 0x96, i, tmp); + + + for (i = 0; i < 6; i++) + dspio_set_uint_param(codec, 0x96, + SPEAKER_TUNING_FRONT_LEFT_DELAY + i, values[i]); +} + +/* + * Initialize mic for non-chromebook ca0132 implementations. + */ +static void ca0132_alt_init_analog_mics(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp; + + /* Mic 1 Setup */ + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + if (ca0132_quirk(spec) == QUIRK_R3DI) { + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + tmp = FLOAT_ONE; + } else + tmp = FLOAT_THREE; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + /* Mic 2 setup (not present on desktop cards) */ + chipio_set_conn_rate(codec, MEM_CONNID_MICIN2, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT2, SR_96_000); + if (ca0132_quirk(spec) == QUIRK_R3DI) + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x80, 0x01, tmp); +} + +/* + * Sets the source of stream 0x14 to connpointID 0x48, and the destination + * connpointID to 0x91. If this isn't done, the destination is 0x71, and + * you get no sound. I'm guessing this has to do with the Sound Blaster Z + * having an updated DAC, which changes the destination to that DAC. + */ +static void sbz_connect_streams(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + mutex_lock(&spec->chipio_mutex); + + codec_dbg(codec, "Connect Streams entered, mutex locked and loaded.\n"); + + /* This value is 0x43 for 96khz, and 0x83 for 192khz. */ + chipio_write_no_mutex(codec, 0x18a020, 0x00000043); + + /* Setup stream 0x14 with it's source and destination points */ + chipio_set_stream_source_dest(codec, 0x14, 0x48, 0x91); + chipio_set_conn_rate_no_mutex(codec, 0x48, SR_96_000); + chipio_set_conn_rate_no_mutex(codec, 0x91, SR_96_000); + chipio_set_stream_channels(codec, 0x14, 2); + chipio_set_stream_control(codec, 0x14, 1); + + codec_dbg(codec, "Connect Streams exited, mutex released.\n"); + + mutex_unlock(&spec->chipio_mutex); +} + +/* + * Write data through ChipIO to setup proper stream destinations. + * Not sure how it exactly works, but it seems to direct data + * to different destinations. Example is f8 to c0, e0 to c0. + * All I know is, if you don't set these, you get no sound. + */ +static void sbz_chipio_startup_data(struct hda_codec *codec) +{ + const struct chipio_stream_remap_data *dsp_out_remap_data; + struct ca0132_spec *spec = codec->spec; + + mutex_lock(&spec->chipio_mutex); + codec_dbg(codec, "Startup Data entered, mutex locked and loaded.\n"); + + /* Remap DAC0's output ports. */ + chipio_remap_stream(codec, &stream_remap_data[0]); + + /* Remap DSP audio output stream ports. */ + switch (ca0132_quirk(spec)) { + case QUIRK_SBZ: + dsp_out_remap_data = &stream_remap_data[1]; + break; + + case QUIRK_ZXR: + dsp_out_remap_data = &stream_remap_data[2]; + break; + + default: + dsp_out_remap_data = NULL; + break; + } + + if (dsp_out_remap_data) + chipio_remap_stream(codec, dsp_out_remap_data); + + codec_dbg(codec, "Startup Data exited, mutex released.\n"); + mutex_unlock(&spec->chipio_mutex); +} + +static void ca0132_alt_dsp_initial_mic_setup(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp; + + chipio_set_stream_control(codec, 0x03, 0); + chipio_set_stream_control(codec, 0x04, 0); + + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + + tmp = FLOAT_THREE; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + chipio_set_stream_control(codec, 0x03, 1); + chipio_set_stream_control(codec, 0x04, 1); + + switch (ca0132_quirk(spec)) { + case QUIRK_SBZ: + chipio_write(codec, 0x18b098, 0x0000000c); + chipio_write(codec, 0x18b09C, 0x0000000c); + break; + case QUIRK_AE5: + chipio_write(codec, 0x18b098, 0x0000000c); + chipio_write(codec, 0x18b09c, 0x0000004c); + break; + default: + break; + } +} + +static void ae5_post_dsp_register_set(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + chipio_8051_write_direct(codec, 0x93, 0x10); + chipio_8051_write_pll_pmu(codec, 0x44, 0xc2); + + writeb(0xff, spec->mem_base + 0x304); + writeb(0xff, spec->mem_base + 0x304); + writeb(0xff, spec->mem_base + 0x304); + writeb(0xff, spec->mem_base + 0x304); + writeb(0x00, spec->mem_base + 0x100); + writeb(0xff, spec->mem_base + 0x304); + writeb(0x00, spec->mem_base + 0x100); + writeb(0xff, spec->mem_base + 0x304); + writeb(0x00, spec->mem_base + 0x100); + writeb(0xff, spec->mem_base + 0x304); + writeb(0x00, spec->mem_base + 0x100); + writeb(0xff, spec->mem_base + 0x304); + + ca0113_mmio_command_set(codec, 0x30, 0x2b, 0x3f); + ca0113_mmio_command_set(codec, 0x30, 0x2d, 0x3f); + ca0113_mmio_command_set(codec, 0x48, 0x07, 0x83); +} + +static void ae5_post_dsp_param_setup(struct hda_codec *codec) +{ + /* + * Param3 in the 8051's memory is represented by the ascii string 'mch' + * which seems to be 'multichannel'. This is also mentioned in the + * AE-5's registry values in Windows. + */ + chipio_set_control_param(codec, 3, 0); + /* + * I believe ASI is 'audio serial interface' and that it's used to + * change colors on the external LED strip connected to the AE-5. + */ + chipio_set_control_flag(codec, CONTROL_FLAG_ASI_96KHZ, 1); + + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, 0x724, 0x83); + chipio_set_control_param(codec, CONTROL_PARAM_ASI, 0); + + chipio_8051_write_exram(codec, 0xfa92, 0x22); +} + +static void ae5_post_dsp_pll_setup(struct hda_codec *codec) +{ + chipio_8051_write_pll_pmu(codec, 0x41, 0xc8); + chipio_8051_write_pll_pmu(codec, 0x45, 0xcc); + chipio_8051_write_pll_pmu(codec, 0x40, 0xcb); + chipio_8051_write_pll_pmu(codec, 0x43, 0xc7); + chipio_8051_write_pll_pmu(codec, 0x51, 0x8d); +} + +static void ae5_post_dsp_stream_setup(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + mutex_lock(&spec->chipio_mutex); + + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, 0x725, 0x81); + + chipio_set_conn_rate_no_mutex(codec, 0x70, SR_96_000); + + chipio_set_stream_source_dest(codec, 0x5, 0x43, 0x0); + + chipio_set_stream_source_dest(codec, 0x18, 0x9, 0xd0); + chipio_set_conn_rate_no_mutex(codec, 0xd0, SR_96_000); + chipio_set_stream_channels(codec, 0x18, 6); + chipio_set_stream_control(codec, 0x18, 1); + + chipio_set_control_param_no_mutex(codec, CONTROL_PARAM_ASI, 4); + + chipio_8051_write_pll_pmu_no_mutex(codec, 0x43, 0xc7); + + ca0113_mmio_command_set(codec, 0x48, 0x01, 0x80); + + mutex_unlock(&spec->chipio_mutex); +} + +static void ae5_post_dsp_startup_data(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + mutex_lock(&spec->chipio_mutex); + + chipio_write_no_mutex(codec, 0x189000, 0x0001f101); + chipio_write_no_mutex(codec, 0x189004, 0x0001f101); + chipio_write_no_mutex(codec, 0x189024, 0x00014004); + chipio_write_no_mutex(codec, 0x189028, 0x0002000f); + + ca0113_mmio_command_set(codec, 0x48, 0x0a, 0x05); + chipio_set_control_param_no_mutex(codec, CONTROL_PARAM_ASI, 7); + ca0113_mmio_command_set(codec, 0x48, 0x0b, 0x12); + ca0113_mmio_command_set(codec, 0x48, 0x04, 0x00); + ca0113_mmio_command_set(codec, 0x48, 0x06, 0x48); + ca0113_mmio_command_set(codec, 0x48, 0x0a, 0x05); + ca0113_mmio_command_set(codec, 0x48, 0x07, 0x83); + ca0113_mmio_command_set(codec, 0x48, 0x0f, 0x00); + ca0113_mmio_command_set(codec, 0x48, 0x10, 0x00); + ca0113_mmio_gpio_set(codec, 0, true); + ca0113_mmio_gpio_set(codec, 1, true); + ca0113_mmio_command_set(codec, 0x48, 0x07, 0x80); + + chipio_write_no_mutex(codec, 0x18b03c, 0x00000012); + + ca0113_mmio_command_set(codec, 0x48, 0x0f, 0x00); + ca0113_mmio_command_set(codec, 0x48, 0x10, 0x00); + + mutex_unlock(&spec->chipio_mutex); +} + +static void ae7_post_dsp_setup_ports(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + mutex_lock(&spec->chipio_mutex); + + /* Seems to share the same port remapping as the SBZ. */ + chipio_remap_stream(codec, &stream_remap_data[1]); + + ca0113_mmio_command_set(codec, 0x30, 0x30, 0x00); + ca0113_mmio_command_set(codec, 0x48, 0x0d, 0x40); + ca0113_mmio_command_set(codec, 0x48, 0x17, 0x00); + ca0113_mmio_command_set(codec, 0x48, 0x19, 0x00); + ca0113_mmio_command_set(codec, 0x48, 0x11, 0xff); + ca0113_mmio_command_set(codec, 0x48, 0x12, 0xff); + ca0113_mmio_command_set(codec, 0x48, 0x13, 0xff); + ca0113_mmio_command_set(codec, 0x48, 0x14, 0x7f); + + mutex_unlock(&spec->chipio_mutex); +} + +static void ae7_post_dsp_asi_stream_setup(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + mutex_lock(&spec->chipio_mutex); + + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, 0x725, 0x81); + ca0113_mmio_command_set(codec, 0x30, 0x2b, 0x00); + + chipio_set_conn_rate_no_mutex(codec, 0x70, SR_96_000); + + chipio_set_stream_source_dest(codec, 0x05, 0x43, 0x00); + chipio_set_stream_source_dest(codec, 0x18, 0x09, 0xd0); + + chipio_set_conn_rate_no_mutex(codec, 0xd0, SR_96_000); + chipio_set_stream_channels(codec, 0x18, 6); + chipio_set_stream_control(codec, 0x18, 1); + + chipio_set_control_param_no_mutex(codec, CONTROL_PARAM_ASI, 4); + + mutex_unlock(&spec->chipio_mutex); +} + +static void ae7_post_dsp_pll_setup(struct hda_codec *codec) +{ + static const unsigned int addr[] = { + 0x41, 0x45, 0x40, 0x43, 0x51 + }; + static const unsigned int data[] = { + 0xc8, 0xcc, 0xcb, 0xc7, 0x8d + }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(addr); i++) + chipio_8051_write_pll_pmu_no_mutex(codec, addr[i], data[i]); +} + +static void ae7_post_dsp_asi_setup_ports(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + static const unsigned int target[] = { + 0x0b, 0x04, 0x06, 0x0a, 0x0c, 0x11, 0x12, 0x13, 0x14 + }; + static const unsigned int data[] = { + 0x12, 0x00, 0x48, 0x05, 0x5f, 0xff, 0xff, 0xff, 0x7f + }; + unsigned int i; + + mutex_lock(&spec->chipio_mutex); + + chipio_8051_write_pll_pmu_no_mutex(codec, 0x43, 0xc7); + + chipio_write_no_mutex(codec, 0x189000, 0x0001f101); + chipio_write_no_mutex(codec, 0x189004, 0x0001f101); + chipio_write_no_mutex(codec, 0x189024, 0x00014004); + chipio_write_no_mutex(codec, 0x189028, 0x0002000f); + + ae7_post_dsp_pll_setup(codec); + chipio_set_control_param_no_mutex(codec, CONTROL_PARAM_ASI, 7); + + for (i = 0; i < ARRAY_SIZE(target); i++) + ca0113_mmio_command_set(codec, 0x48, target[i], data[i]); + + ca0113_mmio_command_set_type2(codec, 0x48, 0x07, 0x83); + ca0113_mmio_command_set(codec, 0x48, 0x0f, 0x00); + ca0113_mmio_command_set(codec, 0x48, 0x10, 0x00); + + chipio_set_stream_source_dest(codec, 0x21, 0x64, 0x56); + chipio_set_stream_channels(codec, 0x21, 2); + chipio_set_conn_rate_no_mutex(codec, 0x56, SR_8_000); + + chipio_set_control_param_no_mutex(codec, CONTROL_PARAM_NODE_ID, 0x09); + /* + * In the 8051's memory, this param is referred to as 'n2sid', which I + * believe is 'node to streamID'. It seems to be a way to assign a + * stream to a given HDA node. + */ + chipio_set_control_param_no_mutex(codec, 0x20, 0x21); + + chipio_write_no_mutex(codec, 0x18b038, 0x00000088); + + /* + * Now, at this point on Windows, an actual stream is setup and + * seemingly sends data to the HDA node 0x09, which is the digital + * audio input node. This is left out here, because obviously I don't + * know what data is being sent. Interestingly, the AE-5 seems to go + * through the motions of getting here and never actually takes this + * step, but the AE-7 does. + */ + + ca0113_mmio_gpio_set(codec, 0, 1); + ca0113_mmio_gpio_set(codec, 1, 1); + + ca0113_mmio_command_set_type2(codec, 0x48, 0x07, 0x83); + chipio_write_no_mutex(codec, 0x18b03c, 0x00000000); + ca0113_mmio_command_set(codec, 0x48, 0x0f, 0x00); + ca0113_mmio_command_set(codec, 0x48, 0x10, 0x00); + + chipio_set_stream_source_dest(codec, 0x05, 0x43, 0x00); + chipio_set_stream_source_dest(codec, 0x18, 0x09, 0xd0); + + chipio_set_conn_rate_no_mutex(codec, 0xd0, SR_96_000); + chipio_set_stream_channels(codec, 0x18, 6); + + /* + * Runs again, this has been repeated a few times, but I'm just + * following what the Windows driver does. + */ + ae7_post_dsp_pll_setup(codec); + chipio_set_control_param_no_mutex(codec, CONTROL_PARAM_ASI, 7); + + mutex_unlock(&spec->chipio_mutex); +} + +/* + * The Windows driver has commands that seem to setup ASI, which I believe to + * be some sort of audio serial interface. My current speculation is that it's + * related to communicating with the new DAC. + */ +static void ae7_post_dsp_asi_setup(struct hda_codec *codec) +{ + chipio_8051_write_direct(codec, 0x93, 0x10); + + chipio_8051_write_pll_pmu(codec, 0x44, 0xc2); + + ca0113_mmio_command_set_type2(codec, 0x48, 0x07, 0x83); + ca0113_mmio_command_set(codec, 0x30, 0x2e, 0x3f); + + chipio_set_control_param(codec, 3, 3); + chipio_set_control_flag(codec, CONTROL_FLAG_ASI_96KHZ, 1); + + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, 0x724, 0x83); + chipio_set_control_param(codec, CONTROL_PARAM_ASI, 0); + snd_hda_codec_write(codec, 0x17, 0, 0x794, 0x00); + + chipio_8051_write_exram(codec, 0xfa92, 0x22); + + ae7_post_dsp_pll_setup(codec); + ae7_post_dsp_asi_stream_setup(codec); + + chipio_8051_write_pll_pmu(codec, 0x43, 0xc7); + + ae7_post_dsp_asi_setup_ports(codec); +} + +/* + * Setup default parameters for DSP + */ +static void ca0132_setup_defaults(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp; + int num_fx; + int idx, i; + + if (spec->dsp_state != DSP_DOWNLOADED) + return; + + /* out, in effects + voicefx */ + num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT + 1; + for (idx = 0; idx < num_fx; idx++) { + for (i = 0; i <= ca0132_effects[idx].params; i++) { + dspio_set_uint_param(codec, ca0132_effects[idx].mid, + ca0132_effects[idx].reqs[i], + ca0132_effects[idx].def_vals[i]); + } + } + + /*remove DSP headroom*/ + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x96, 0x3C, tmp); + + /*set speaker EQ bypass attenuation*/ + dspio_set_uint_param(codec, 0x8f, 0x01, tmp); + + /* set AMic1 and AMic2 as mono mic */ + tmp = FLOAT_ONE; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + dspio_set_uint_param(codec, 0x80, 0x01, tmp); + + /* set AMic1 as CrystalVoice input */ + tmp = FLOAT_ONE; + dspio_set_uint_param(codec, 0x80, 0x05, tmp); + + /* set WUH source */ + tmp = FLOAT_TWO; + dspio_set_uint_param(codec, 0x31, 0x00, tmp); +} + +/* + * Setup default parameters for Recon3D/Recon3Di DSP. + */ + +static void r3d_setup_defaults(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp; + int num_fx; + int idx, i; + + if (spec->dsp_state != DSP_DOWNLOADED) + return; + + ca0132_alt_init_analog_mics(codec); + ca0132_alt_start_dsp_audio_streams(codec); + + /*remove DSP headroom*/ + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x96, 0x3C, tmp); + + /* set WUH source */ + tmp = FLOAT_TWO; + dspio_set_uint_param(codec, 0x31, 0x00, tmp); + chipio_set_conn_rate(codec, MEM_CONNID_WUH, SR_48_000); + + /* Set speaker source? */ + dspio_set_uint_param(codec, 0x32, 0x00, tmp); + + if (ca0132_quirk(spec) == QUIRK_R3DI) + r3di_gpio_dsp_status_set(codec, R3DI_DSP_DOWNLOADED); + + /* Disable mute on Center/LFE. */ + if (ca0132_quirk(spec) == QUIRK_R3D) { + ca0113_mmio_gpio_set(codec, 2, false); + ca0113_mmio_gpio_set(codec, 4, true); + } + + /* Setup effect defaults */ + num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT + 1; + for (idx = 0; idx < num_fx; idx++) { + for (i = 0; i <= ca0132_effects[idx].params; i++) { + dspio_set_uint_param(codec, + ca0132_effects[idx].mid, + ca0132_effects[idx].reqs[i], + ca0132_effects[idx].def_vals[i]); + } + } +} + +/* + * Setup default parameters for the Sound Blaster Z DSP. A lot more going on + * than the Chromebook setup. + */ +static void sbz_setup_defaults(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp; + int num_fx; + int idx, i; + + if (spec->dsp_state != DSP_DOWNLOADED) + return; + + ca0132_alt_init_analog_mics(codec); + ca0132_alt_start_dsp_audio_streams(codec); + sbz_connect_streams(codec); + sbz_chipio_startup_data(codec); + + /* + * Sets internal input loopback to off, used to have a switch to + * enable input loopback, but turned out to be way too buggy. + */ + tmp = FLOAT_ONE; + dspio_set_uint_param(codec, 0x37, 0x08, tmp); + dspio_set_uint_param(codec, 0x37, 0x10, tmp); + + /*remove DSP headroom*/ + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x96, 0x3C, tmp); + + /* set WUH source */ + tmp = FLOAT_TWO; + dspio_set_uint_param(codec, 0x31, 0x00, tmp); + chipio_set_conn_rate(codec, MEM_CONNID_WUH, SR_48_000); + + /* Set speaker source? */ + dspio_set_uint_param(codec, 0x32, 0x00, tmp); + + ca0132_alt_dsp_initial_mic_setup(codec); + + /* out, in effects + voicefx */ + num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT + 1; + for (idx = 0; idx < num_fx; idx++) { + for (i = 0; i <= ca0132_effects[idx].params; i++) { + dspio_set_uint_param(codec, + ca0132_effects[idx].mid, + ca0132_effects[idx].reqs[i], + ca0132_effects[idx].def_vals[i]); + } + } + + ca0132_alt_init_speaker_tuning(codec); +} + +/* + * Setup default parameters for the Sound BlasterX AE-5 DSP. + */ +static void ae5_setup_defaults(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp; + int num_fx; + int idx, i; + + if (spec->dsp_state != DSP_DOWNLOADED) + return; + + ca0132_alt_init_analog_mics(codec); + ca0132_alt_start_dsp_audio_streams(codec); + + /* New, unknown SCP req's */ + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x96, 0x29, tmp); + dspio_set_uint_param(codec, 0x96, 0x2a, tmp); + dspio_set_uint_param(codec, 0x80, 0x0d, tmp); + dspio_set_uint_param(codec, 0x80, 0x0e, tmp); + + ca0113_mmio_command_set(codec, 0x30, 0x2e, 0x3f); + ca0113_mmio_gpio_set(codec, 0, false); + ca0113_mmio_command_set(codec, 0x30, 0x28, 0x00); + + /* Internal loopback off */ + tmp = FLOAT_ONE; + dspio_set_uint_param(codec, 0x37, 0x08, tmp); + dspio_set_uint_param(codec, 0x37, 0x10, tmp); + + /*remove DSP headroom*/ + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x96, 0x3C, tmp); + + /* set WUH source */ + tmp = FLOAT_TWO; + dspio_set_uint_param(codec, 0x31, 0x00, tmp); + chipio_set_conn_rate(codec, MEM_CONNID_WUH, SR_48_000); + + /* Set speaker source? */ + dspio_set_uint_param(codec, 0x32, 0x00, tmp); + + ca0132_alt_dsp_initial_mic_setup(codec); + ae5_post_dsp_register_set(codec); + ae5_post_dsp_param_setup(codec); + ae5_post_dsp_pll_setup(codec); + ae5_post_dsp_stream_setup(codec); + ae5_post_dsp_startup_data(codec); + + /* out, in effects + voicefx */ + num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT + 1; + for (idx = 0; idx < num_fx; idx++) { + for (i = 0; i <= ca0132_effects[idx].params; i++) { + dspio_set_uint_param(codec, + ca0132_effects[idx].mid, + ca0132_effects[idx].reqs[i], + ca0132_effects[idx].def_vals[i]); + } + } + + ca0132_alt_init_speaker_tuning(codec); +} + +/* + * Setup default parameters for the Sound Blaster AE-7 DSP. + */ +static void ae7_setup_defaults(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp; + int num_fx; + int idx, i; + + if (spec->dsp_state != DSP_DOWNLOADED) + return; + + ca0132_alt_init_analog_mics(codec); + ca0132_alt_start_dsp_audio_streams(codec); + ae7_post_dsp_setup_ports(codec); + + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x96, + SPEAKER_TUNING_FRONT_LEFT_INVERT, tmp); + dspio_set_uint_param(codec, 0x96, + SPEAKER_TUNING_FRONT_RIGHT_INVERT, tmp); + + ca0113_mmio_command_set(codec, 0x30, 0x2e, 0x3f); + + /* New, unknown SCP req's */ + dspio_set_uint_param(codec, 0x80, 0x0d, tmp); + dspio_set_uint_param(codec, 0x80, 0x0e, tmp); + + ca0113_mmio_gpio_set(codec, 0, false); + + /* Internal loopback off */ + tmp = FLOAT_ONE; + dspio_set_uint_param(codec, 0x37, 0x08, tmp); + dspio_set_uint_param(codec, 0x37, 0x10, tmp); + + /*remove DSP headroom*/ + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x96, 0x3C, tmp); + + /* set WUH source */ + tmp = FLOAT_TWO; + dspio_set_uint_param(codec, 0x31, 0x00, tmp); + chipio_set_conn_rate(codec, MEM_CONNID_WUH, SR_48_000); + + /* Set speaker source? */ + dspio_set_uint_param(codec, 0x32, 0x00, tmp); + ca0113_mmio_command_set(codec, 0x30, 0x28, 0x00); + + /* + * This is the second time we've called this, but this is seemingly + * what Windows does. + */ + ca0132_alt_init_analog_mics(codec); + + ae7_post_dsp_asi_setup(codec); + + /* + * Not sure why, but these are both set to 1. They're only set to 0 + * upon shutdown. + */ + ca0113_mmio_gpio_set(codec, 0, true); + ca0113_mmio_gpio_set(codec, 1, true); + + /* Volume control related. */ + ca0113_mmio_command_set(codec, 0x48, 0x0f, 0x04); + ca0113_mmio_command_set(codec, 0x48, 0x10, 0x04); + ca0113_mmio_command_set_type2(codec, 0x48, 0x07, 0x80); + + /* out, in effects + voicefx */ + num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT + 1; + for (idx = 0; idx < num_fx; idx++) { + for (i = 0; i <= ca0132_effects[idx].params; i++) { + dspio_set_uint_param(codec, + ca0132_effects[idx].mid, + ca0132_effects[idx].reqs[i], + ca0132_effects[idx].def_vals[i]); + } + } + + ca0132_alt_init_speaker_tuning(codec); +} + +/* + * Initialization of flags in chip + */ +static void ca0132_init_flags(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + if (ca0132_use_alt_functions(spec)) { + chipio_set_control_flag(codec, CONTROL_FLAG_DSP_96KHZ, 1); + chipio_set_control_flag(codec, CONTROL_FLAG_DAC_96KHZ, 1); + chipio_set_control_flag(codec, CONTROL_FLAG_ADC_B_96KHZ, 1); + chipio_set_control_flag(codec, CONTROL_FLAG_ADC_C_96KHZ, 1); + chipio_set_control_flag(codec, CONTROL_FLAG_SRC_RATE_96KHZ, 1); + chipio_set_control_flag(codec, CONTROL_FLAG_IDLE_ENABLE, 0); + chipio_set_control_flag(codec, CONTROL_FLAG_SPDIF2OUT, 0); + chipio_set_control_flag(codec, + CONTROL_FLAG_PORT_D_10KOHM_LOAD, 0); + chipio_set_control_flag(codec, + CONTROL_FLAG_PORT_A_10KOHM_LOAD, 1); + } else { + chipio_set_control_flag(codec, CONTROL_FLAG_IDLE_ENABLE, 0); + chipio_set_control_flag(codec, + CONTROL_FLAG_PORT_A_COMMON_MODE, 0); + chipio_set_control_flag(codec, + CONTROL_FLAG_PORT_D_COMMON_MODE, 0); + chipio_set_control_flag(codec, + CONTROL_FLAG_PORT_A_10KOHM_LOAD, 0); + chipio_set_control_flag(codec, + CONTROL_FLAG_PORT_D_10KOHM_LOAD, 0); + chipio_set_control_flag(codec, CONTROL_FLAG_ADC_C_HIGH_PASS, 1); + } +} + +/* + * Initialization of parameters in chip + */ +static void ca0132_init_params(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + if (ca0132_use_alt_functions(spec)) { + chipio_set_conn_rate(codec, MEM_CONNID_WUH, SR_48_000); + chipio_set_conn_rate(codec, 0x0B, SR_48_000); + chipio_set_control_param(codec, CONTROL_PARAM_SPDIF1_SOURCE, 0); + chipio_set_control_param(codec, 0, 0); + chipio_set_control_param(codec, CONTROL_PARAM_VIP_SOURCE, 0); + } + + chipio_set_control_param(codec, CONTROL_PARAM_PORTA_160OHM_GAIN, 6); + chipio_set_control_param(codec, CONTROL_PARAM_PORTD_160OHM_GAIN, 6); +} + +static void ca0132_set_dsp_msr(struct hda_codec *codec, bool is96k) +{ + chipio_set_control_flag(codec, CONTROL_FLAG_DSP_96KHZ, is96k); + chipio_set_control_flag(codec, CONTROL_FLAG_DAC_96KHZ, is96k); + chipio_set_control_flag(codec, CONTROL_FLAG_SRC_RATE_96KHZ, is96k); + chipio_set_control_flag(codec, CONTROL_FLAG_SRC_CLOCK_196MHZ, is96k); + chipio_set_control_flag(codec, CONTROL_FLAG_ADC_B_96KHZ, is96k); + chipio_set_control_flag(codec, CONTROL_FLAG_ADC_C_96KHZ, is96k); + + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_WUH, SR_48_000); +} + +static bool ca0132_download_dsp_images(struct hda_codec *codec) +{ + bool dsp_loaded = false; + struct ca0132_spec *spec = codec->spec; + const struct dsp_image_seg *dsp_os_image; + const struct firmware *fw_entry = NULL; + /* + * Alternate firmwares for different variants. The Recon3Di apparently + * can use the default firmware, but I'll leave the option in case + * it needs it again. + */ + switch (ca0132_quirk(spec)) { + case QUIRK_SBZ: + case QUIRK_R3D: + case QUIRK_AE5: + if (request_firmware(&fw_entry, DESKTOP_EFX_FILE, + codec->card->dev) != 0) + codec_dbg(codec, "Desktop firmware not found."); + else + codec_dbg(codec, "Desktop firmware selected."); + break; + case QUIRK_R3DI: + if (request_firmware(&fw_entry, R3DI_EFX_FILE, + codec->card->dev) != 0) + codec_dbg(codec, "Recon3Di alt firmware not detected."); + else + codec_dbg(codec, "Recon3Di firmware selected."); + break; + default: + break; + } + /* + * Use default ctefx.bin if no alt firmware is detected, or if none + * exists for your particular codec. + */ + if (!fw_entry) { + codec_dbg(codec, "Default firmware selected."); + if (request_firmware(&fw_entry, EFX_FILE, + codec->card->dev) != 0) + return false; + } + + dsp_os_image = (struct dsp_image_seg *)(fw_entry->data); + if (dspload_image(codec, dsp_os_image, 0, 0, true, 0)) { + codec_err(codec, "ca0132 DSP load image failed\n"); + goto exit_download; + } + + dsp_loaded = dspload_wait_loaded(codec); + +exit_download: + release_firmware(fw_entry); + + return dsp_loaded; +} + +static void ca0132_download_dsp(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + +#ifndef CONFIG_SND_HDA_CODEC_CA0132_DSP + return; /* NOP */ +#endif + + if (spec->dsp_state == DSP_DOWNLOAD_FAILED) + return; /* don't retry failures */ + + chipio_enable_clocks(codec); + if (spec->dsp_state != DSP_DOWNLOADED) { + spec->dsp_state = DSP_DOWNLOADING; + + if (!ca0132_download_dsp_images(codec)) + spec->dsp_state = DSP_DOWNLOAD_FAILED; + else + spec->dsp_state = DSP_DOWNLOADED; + } + + /* For codecs using alt functions, this is already done earlier */ + if (spec->dsp_state == DSP_DOWNLOADED && !ca0132_use_alt_functions(spec)) + ca0132_set_dsp_msr(codec, true); +} + +static void ca0132_process_dsp_response(struct hda_codec *codec, + struct hda_jack_callback *callback) +{ + struct ca0132_spec *spec = codec->spec; + + codec_dbg(codec, "ca0132_process_dsp_response\n"); + snd_hda_power_up_pm(codec); + if (spec->wait_scp) { + if (dspio_get_response_data(codec) >= 0) + spec->wait_scp = 0; + } + + dspio_clear_response_queue(codec); + snd_hda_power_down_pm(codec); +} + +static void hp_callback(struct hda_codec *codec, struct hda_jack_callback *cb) +{ + struct ca0132_spec *spec = codec->spec; + struct hda_jack_tbl *tbl; + + /* Delay enabling the HP amp, to let the mic-detection + * state machine run. + */ + tbl = snd_hda_jack_tbl_get(codec, cb->nid); + if (tbl) + tbl->block_report = 1; + schedule_delayed_work(&spec->unsol_hp_work, msecs_to_jiffies(500)); +} + +static void amic_callback(struct hda_codec *codec, struct hda_jack_callback *cb) +{ + struct ca0132_spec *spec = codec->spec; + + if (ca0132_use_alt_functions(spec)) + ca0132_alt_select_in(codec); + else + ca0132_select_mic(codec); +} + +static void ca0132_setup_unsol(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + snd_hda_jack_detect_enable_callback(codec, spec->unsol_tag_hp, hp_callback); + snd_hda_jack_detect_enable_callback(codec, spec->unsol_tag_amic1, + amic_callback); + snd_hda_jack_detect_enable_callback(codec, UNSOL_TAG_DSP, + ca0132_process_dsp_response); + /* Front headphone jack detection */ + if (ca0132_use_alt_functions(spec)) + snd_hda_jack_detect_enable_callback(codec, + spec->unsol_tag_front_hp, hp_callback); +} + +/* + * Verbs tables. + */ + +/* Sends before DSP download. */ +static const struct hda_verb ca0132_base_init_verbs[] = { + /*enable ct extension*/ + {0x15, VENDOR_CHIPIO_CT_EXTENSIONS_ENABLE, 0x1}, + {} +}; + +/* Send at exit. */ +static const struct hda_verb ca0132_base_exit_verbs[] = { + /*set afg to D3*/ + {0x01, AC_VERB_SET_POWER_STATE, 0x03}, + /*disable ct extension*/ + {0x15, VENDOR_CHIPIO_CT_EXTENSIONS_ENABLE, 0}, + {} +}; + +/* Other verbs tables. Sends after DSP download. */ + +static const struct hda_verb ca0132_init_verbs0[] = { + /* chip init verbs */ + {0x15, 0x70D, 0xF0}, + {0x15, 0x70E, 0xFE}, + {0x15, 0x707, 0x75}, + {0x15, 0x707, 0xD3}, + {0x15, 0x707, 0x09}, + {0x15, 0x707, 0x53}, + {0x15, 0x707, 0xD4}, + {0x15, 0x707, 0xEF}, + {0x15, 0x707, 0x75}, + {0x15, 0x707, 0xD3}, + {0x15, 0x707, 0x09}, + {0x15, 0x707, 0x02}, + {0x15, 0x707, 0x37}, + {0x15, 0x707, 0x78}, + {0x15, 0x53C, 0xCE}, + {0x15, 0x575, 0xC9}, + {0x15, 0x53D, 0xCE}, + {0x15, 0x5B7, 0xC9}, + {0x15, 0x70D, 0xE8}, + {0x15, 0x70E, 0xFE}, + {0x15, 0x707, 0x02}, + {0x15, 0x707, 0x68}, + {0x15, 0x707, 0x62}, + {0x15, 0x53A, 0xCE}, + {0x15, 0x546, 0xC9}, + {0x15, 0x53B, 0xCE}, + {0x15, 0x5E8, 0xC9}, + {} +}; + +/* Extra init verbs for desktop cards. */ +static const struct hda_verb ca0132_init_verbs1[] = { + {0x15, 0x70D, 0x20}, + {0x15, 0x70E, 0x19}, + {0x15, 0x707, 0x00}, + {0x15, 0x539, 0xCE}, + {0x15, 0x546, 0xC9}, + {0x15, 0x70D, 0xB7}, + {0x15, 0x70E, 0x09}, + {0x15, 0x707, 0x10}, + {0x15, 0x70D, 0xAF}, + {0x15, 0x70E, 0x09}, + {0x15, 0x707, 0x01}, + {0x15, 0x707, 0x05}, + {0x15, 0x70D, 0x73}, + {0x15, 0x70E, 0x09}, + {0x15, 0x707, 0x14}, + {0x15, 0x6FF, 0xC4}, + {} +}; + +static void ca0132_init_chip(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + int num_fx; + int i; + unsigned int on; + + mutex_init(&spec->chipio_mutex); + + /* + * The Windows driver always does this upon startup, which seems to + * clear out any previous configuration. This should help issues where + * a boot into Windows prior to a boot into Linux breaks things. Also, + * Windows always sends the reset twice. + */ + if (ca0132_use_alt_functions(spec)) { + chipio_set_control_flag(codec, CONTROL_FLAG_IDLE_ENABLE, 0); + chipio_write_no_mutex(codec, 0x18b0a4, 0x000000c2); + + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_CODEC_RESET, 0); + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_CODEC_RESET, 0); + } + + spec->cur_out_type = SPEAKER_OUT; + if (!ca0132_use_alt_functions(spec)) + spec->cur_mic_type = DIGITAL_MIC; + else + spec->cur_mic_type = REAR_MIC; + + spec->cur_mic_boost = 0; + + for (i = 0; i < VNODES_COUNT; i++) { + spec->vnode_lvol[i] = 0x5a; + spec->vnode_rvol[i] = 0x5a; + spec->vnode_lswitch[i] = 0; + spec->vnode_rswitch[i] = 0; + } + + /* + * Default states for effects are in ca0132_effects[]. + */ + num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT; + for (i = 0; i < num_fx; i++) { + on = (unsigned int)ca0132_effects[i].reqs[0]; + spec->effects_switch[i] = on ? 1 : 0; + } + /* + * Sets defaults for the effect slider controls, only for alternative + * ca0132 codecs. Also sets x-bass crossover frequency to 80hz. + */ + if (ca0132_use_alt_controls(spec)) { + /* Set speakers to default to full range. */ + spec->speaker_range_val[0] = 1; + spec->speaker_range_val[1] = 1; + + spec->xbass_xover_freq = 8; + for (i = 0; i < EFFECT_LEVEL_SLIDERS; i++) + spec->fx_ctl_val[i] = effect_slider_defaults[i]; + + spec->bass_redirect_xover_freq = 8; + } + + spec->voicefx_val = 0; + spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID] = 1; + spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID] = 0; + + /* + * The ZxR doesn't have a front panel header, and it's line-in is on + * the daughter board. So, there is no input enum control, and we need + * to make sure that spec->in_enum_val is set properly. + */ + if (ca0132_quirk(spec) == QUIRK_ZXR) + spec->in_enum_val = REAR_MIC; + +#ifdef ENABLE_TUNING_CONTROLS + ca0132_init_tuning_defaults(codec); +#endif +} + +/* + * Recon3Di exit specific commands. + */ +/* prevents popping noise on shutdown */ +static void r3di_gpio_shutdown(struct hda_codec *codec) +{ + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, 0x00); +} + +/* + * Sound Blaster Z exit specific commands. + */ +static void sbz_region2_exit(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int i; + + for (i = 0; i < 4; i++) + writeb(0x0, spec->mem_base + 0x100); + for (i = 0; i < 8; i++) + writeb(0xb3, spec->mem_base + 0x304); + + ca0113_mmio_gpio_set(codec, 0, false); + ca0113_mmio_gpio_set(codec, 1, false); + ca0113_mmio_gpio_set(codec, 4, true); + ca0113_mmio_gpio_set(codec, 5, false); + ca0113_mmio_gpio_set(codec, 7, false); +} + +static void sbz_set_pin_ctl_default(struct hda_codec *codec) +{ + static const hda_nid_t pins[] = {0x0B, 0x0C, 0x0E, 0x12, 0x13}; + unsigned int i; + + snd_hda_codec_write(codec, 0x11, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40); + + for (i = 0; i < ARRAY_SIZE(pins); i++) + snd_hda_codec_write(codec, pins[i], 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00); +} + +static void ca0132_clear_unsolicited(struct hda_codec *codec) +{ + static const hda_nid_t pins[] = {0x0B, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13}; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pins); i++) { + snd_hda_codec_write(codec, pins[i], 0, + AC_VERB_SET_UNSOLICITED_ENABLE, 0x00); + } +} + +/* On shutdown, sends commands in sets of three */ +static void sbz_gpio_shutdown_commands(struct hda_codec *codec, int dir, + int mask, int data) +{ + if (dir >= 0) + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DIRECTION, dir); + if (mask >= 0) + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_MASK, mask); + + if (data >= 0) + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DATA, data); +} + +static void zxr_dbpro_power_state_shutdown(struct hda_codec *codec) +{ + static const hda_nid_t pins[] = {0x05, 0x0c, 0x09, 0x0e, 0x08, 0x11, 0x01}; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pins); i++) + snd_hda_codec_write(codec, pins[i], 0, + AC_VERB_SET_POWER_STATE, 0x03); +} + +static void sbz_exit_chip(struct hda_codec *codec) +{ + chipio_set_stream_control(codec, 0x03, 0); + chipio_set_stream_control(codec, 0x04, 0); + + /* Mess with GPIO */ + sbz_gpio_shutdown_commands(codec, 0x07, 0x07, -1); + sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x05); + sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x01); + + chipio_set_stream_control(codec, 0x14, 0); + chipio_set_stream_control(codec, 0x0C, 0); + + chipio_set_conn_rate(codec, 0x41, SR_192_000); + chipio_set_conn_rate(codec, 0x91, SR_192_000); + + chipio_write(codec, 0x18a020, 0x00000083); + + sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x03); + sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x07); + sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x06); + + chipio_set_stream_control(codec, 0x0C, 0); + + chipio_set_control_param(codec, 0x0D, 0x24); + + ca0132_clear_unsolicited(codec); + sbz_set_pin_ctl_default(codec); + + snd_hda_codec_write(codec, 0x0B, 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x00); + + sbz_region2_exit(codec); +} + +static void r3d_exit_chip(struct hda_codec *codec) +{ + ca0132_clear_unsolicited(codec); + snd_hda_codec_write(codec, 0x01, 0, 0x793, 0x00); + snd_hda_codec_write(codec, 0x01, 0, 0x794, 0x5b); +} + +static void ae5_exit_chip(struct hda_codec *codec) +{ + chipio_set_stream_control(codec, 0x03, 0); + chipio_set_stream_control(codec, 0x04, 0); + + ca0113_mmio_command_set(codec, 0x30, 0x32, 0x3f); + ca0113_mmio_command_set(codec, 0x48, 0x07, 0x83); + ca0113_mmio_command_set(codec, 0x48, 0x07, 0x83); + ca0113_mmio_command_set(codec, 0x30, 0x30, 0x00); + ca0113_mmio_command_set(codec, 0x30, 0x2b, 0x00); + ca0113_mmio_command_set(codec, 0x30, 0x2d, 0x00); + ca0113_mmio_gpio_set(codec, 0, false); + ca0113_mmio_gpio_set(codec, 1, false); + + snd_hda_codec_write(codec, 0x01, 0, 0x793, 0x00); + snd_hda_codec_write(codec, 0x01, 0, 0x794, 0x53); + + chipio_set_control_param(codec, CONTROL_PARAM_ASI, 0); + + chipio_set_stream_control(codec, 0x18, 0); + chipio_set_stream_control(codec, 0x0c, 0); + + snd_hda_codec_write(codec, 0x01, 0, 0x724, 0x83); +} + +static void ae7_exit_chip(struct hda_codec *codec) +{ + chipio_set_stream_control(codec, 0x18, 0); + chipio_set_stream_source_dest(codec, 0x21, 0xc8, 0xc8); + chipio_set_stream_channels(codec, 0x21, 0); + chipio_set_control_param(codec, CONTROL_PARAM_NODE_ID, 0x09); + chipio_set_control_param(codec, 0x20, 0x01); + + chipio_set_control_param(codec, CONTROL_PARAM_ASI, 0); + + chipio_set_stream_control(codec, 0x18, 0); + chipio_set_stream_control(codec, 0x0c, 0); + + ca0113_mmio_command_set(codec, 0x30, 0x2b, 0x00); + snd_hda_codec_write(codec, 0x15, 0, 0x724, 0x83); + ca0113_mmio_command_set_type2(codec, 0x48, 0x07, 0x83); + ca0113_mmio_command_set(codec, 0x30, 0x30, 0x00); + ca0113_mmio_command_set(codec, 0x30, 0x2e, 0x00); + ca0113_mmio_gpio_set(codec, 0, false); + ca0113_mmio_gpio_set(codec, 1, false); + ca0113_mmio_command_set(codec, 0x30, 0x32, 0x3f); + + snd_hda_codec_write(codec, 0x01, 0, 0x793, 0x00); + snd_hda_codec_write(codec, 0x01, 0, 0x794, 0x53); +} + +static void zxr_exit_chip(struct hda_codec *codec) +{ + chipio_set_stream_control(codec, 0x03, 0); + chipio_set_stream_control(codec, 0x04, 0); + chipio_set_stream_control(codec, 0x14, 0); + chipio_set_stream_control(codec, 0x0C, 0); + + chipio_set_conn_rate(codec, 0x41, SR_192_000); + chipio_set_conn_rate(codec, 0x91, SR_192_000); + + chipio_write(codec, 0x18a020, 0x00000083); + + snd_hda_codec_write(codec, 0x01, 0, 0x793, 0x00); + snd_hda_codec_write(codec, 0x01, 0, 0x794, 0x53); + + ca0132_clear_unsolicited(codec); + sbz_set_pin_ctl_default(codec); + snd_hda_codec_write(codec, 0x0B, 0, AC_VERB_SET_EAPD_BTLENABLE, 0x00); + + ca0113_mmio_gpio_set(codec, 5, false); + ca0113_mmio_gpio_set(codec, 2, false); + ca0113_mmio_gpio_set(codec, 3, false); + ca0113_mmio_gpio_set(codec, 0, false); + ca0113_mmio_gpio_set(codec, 4, true); + ca0113_mmio_gpio_set(codec, 0, true); + ca0113_mmio_gpio_set(codec, 5, true); + ca0113_mmio_gpio_set(codec, 2, false); + ca0113_mmio_gpio_set(codec, 3, false); +} + +static void ca0132_exit_chip(struct hda_codec *codec) +{ + /* put any chip cleanup stuffs here. */ + + if (dspload_is_loaded(codec)) + dsp_reset(codec); +} + +/* + * This fixes a problem that was hard to reproduce. Very rarely, I would + * boot up, and there would be no sound, but the DSP indicated it had loaded + * properly. I did a few memory dumps to see if anything was different, and + * there were a few areas of memory uninitialized with a1a2a3a4. This function + * checks if those areas are uninitialized, and if they are, it'll attempt to + * reload the card 3 times. Usually it fixes by the second. + */ +static void sbz_dsp_startup_check(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int dsp_data_check[4]; + unsigned int cur_address = 0x390; + unsigned int i; + unsigned int failure = 0; + unsigned int reload = 3; + + if (spec->startup_check_entered) + return; + + spec->startup_check_entered = true; + + for (i = 0; i < 4; i++) { + chipio_read(codec, cur_address, &dsp_data_check[i]); + cur_address += 0x4; + } + for (i = 0; i < 4; i++) { + if (dsp_data_check[i] == 0xa1a2a3a4) + failure = 1; + } + + codec_dbg(codec, "Startup Check: %d ", failure); + if (failure) + codec_info(codec, "DSP not initialized properly. Attempting to fix."); + /* + * While the failure condition is true, and we haven't reached our + * three reload limit, continue trying to reload the driver and + * fix the issue. + */ + while (failure && (reload != 0)) { + codec_info(codec, "Reloading... Tries left: %d", reload); + sbz_exit_chip(codec); + spec->dsp_state = DSP_DOWNLOAD_INIT; + codec->patch_ops.init(codec); + failure = 0; + for (i = 0; i < 4; i++) { + chipio_read(codec, cur_address, &dsp_data_check[i]); + cur_address += 0x4; + } + for (i = 0; i < 4; i++) { + if (dsp_data_check[i] == 0xa1a2a3a4) + failure = 1; + } + reload--; + } + + if (!failure && reload < 3) + codec_info(codec, "DSP fixed."); + + if (!failure) + return; + + codec_info(codec, "DSP failed to initialize properly. Either try a full shutdown or a suspend to clear the internal memory."); +} + +/* + * This is for the extra volume verbs 0x797 (left) and 0x798 (right). These add + * extra precision for decibel values. If you had the dB value in floating point + * you would take the value after the decimal point, multiply by 64, and divide + * by 2. So for 8.59, it's (59 * 64) / 100. Useful if someone wanted to + * implement fixed point or floating point dB volumes. For now, I'll set them + * to 0 just incase a value has lingered from a boot into Windows. + */ +static void ca0132_alt_vol_setup(struct hda_codec *codec) +{ + snd_hda_codec_write(codec, 0x02, 0, 0x797, 0x00); + snd_hda_codec_write(codec, 0x02, 0, 0x798, 0x00); + snd_hda_codec_write(codec, 0x03, 0, 0x797, 0x00); + snd_hda_codec_write(codec, 0x03, 0, 0x798, 0x00); + snd_hda_codec_write(codec, 0x04, 0, 0x797, 0x00); + snd_hda_codec_write(codec, 0x04, 0, 0x798, 0x00); + snd_hda_codec_write(codec, 0x07, 0, 0x797, 0x00); + snd_hda_codec_write(codec, 0x07, 0, 0x798, 0x00); +} + +/* + * Extra commands that don't really fit anywhere else. + */ +static void sbz_pre_dsp_setup(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + writel(0x00820680, spec->mem_base + 0x01C); + writel(0x00820680, spec->mem_base + 0x01C); + + chipio_write(codec, 0x18b0a4, 0x000000c2); + + snd_hda_codec_write(codec, 0x11, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x44); +} + +static void r3d_pre_dsp_setup(struct hda_codec *codec) +{ + chipio_write(codec, 0x18b0a4, 0x000000c2); + + chipio_8051_write_exram(codec, 0x1c1e, 0x5b); + + snd_hda_codec_write(codec, 0x11, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x44); +} + +static void r3di_pre_dsp_setup(struct hda_codec *codec) +{ + chipio_write(codec, 0x18b0a4, 0x000000c2); + + chipio_8051_write_exram(codec, 0x1c1e, 0x5b); + chipio_8051_write_exram(codec, 0x1920, 0x00); + chipio_8051_write_exram(codec, 0x1921, 0x40); + + snd_hda_codec_write(codec, 0x11, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x04); +} + +/* + * The ZxR seems to use alternative DAC's for the surround channels, which + * require PLL PMU setup for the clock rate, I'm guessing. Without setting + * this up, we get no audio out of the surround jacks. + */ +static void zxr_pre_dsp_setup(struct hda_codec *codec) +{ + static const unsigned int addr[] = { 0x43, 0x40, 0x41, 0x42, 0x45 }; + static const unsigned int data[] = { 0x08, 0x0c, 0x0b, 0x07, 0x0d }; + unsigned int i; + + chipio_write(codec, 0x189000, 0x0001f100); + msleep(50); + chipio_write(codec, 0x18900c, 0x0001f100); + msleep(50); + + /* + * This writes a RET instruction at the entry point of the function at + * 0xfa92 in exram. This function seems to have something to do with + * ASI. Might be some way to prevent the card from reconfiguring the + * ASI stuff itself. + */ + chipio_8051_write_exram(codec, 0xfa92, 0x22); + + chipio_8051_write_pll_pmu(codec, 0x51, 0x98); + + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, 0x725, 0x82); + chipio_set_control_param(codec, CONTROL_PARAM_ASI, 3); + + chipio_write(codec, 0x18902c, 0x00000000); + msleep(50); + chipio_write(codec, 0x18902c, 0x00000003); + msleep(50); + + for (i = 0; i < ARRAY_SIZE(addr); i++) + chipio_8051_write_pll_pmu(codec, addr[i], data[i]); +} + +/* + * These are sent before the DSP is downloaded. Not sure + * what they do, or if they're necessary. Could possibly + * be removed. Figure they're better to leave in. + */ +static const unsigned int ca0113_mmio_init_address_sbz[] = { + 0x400, 0x408, 0x40c, 0x01c, 0xc0c, 0xc00, 0xc04, 0xc0c, 0xc0c, 0xc0c, + 0xc0c, 0xc08, 0xc08, 0xc08, 0xc08, 0xc08, 0xc04 +}; + +static const unsigned int ca0113_mmio_init_data_sbz[] = { + 0x00000030, 0x00000000, 0x00000003, 0x00000003, 0x00000003, + 0x00000003, 0x000000c1, 0x000000f1, 0x00000001, 0x000000c7, + 0x000000c1, 0x00000080 +}; + +static const unsigned int ca0113_mmio_init_data_zxr[] = { + 0x00000030, 0x00000000, 0x00000000, 0x00000003, 0x00000003, + 0x00000003, 0x00000001, 0x000000f1, 0x00000001, 0x000000c7, + 0x000000c1, 0x00000080 +}; + +static const unsigned int ca0113_mmio_init_address_ae5[] = { + 0x400, 0x42c, 0x46c, 0x4ac, 0x4ec, 0x43c, 0x47c, 0x4bc, 0x4fc, 0x408, + 0x100, 0x410, 0x40c, 0x100, 0x100, 0x830, 0x86c, 0x800, 0x86c, 0x800, + 0x804, 0x20c, 0x01c, 0xc0c, 0xc00, 0xc04, 0xc0c, 0xc0c, 0xc0c, 0xc0c, + 0xc08, 0xc08, 0xc08, 0xc08, 0xc08, 0xc04, 0x01c +}; + +static const unsigned int ca0113_mmio_init_data_ae5[] = { + 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, + 0x00000600, 0x00000014, 0x00000001, 0x0000060f, 0x0000070f, + 0x00000aff, 0x00000000, 0x0000006b, 0x00000001, 0x0000006b, + 0x00000057, 0x00800000, 0x00880680, 0x00000080, 0x00000030, + 0x00000000, 0x00000000, 0x00000003, 0x00000003, 0x00000003, + 0x00000001, 0x000000f1, 0x00000001, 0x000000c7, 0x000000c1, + 0x00000080, 0x00880680 +}; + +static void ca0132_mmio_init_sbz(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp[2], i, count, cur_addr; + const unsigned int *addr, *data; + + addr = ca0113_mmio_init_address_sbz; + for (i = 0; i < 3; i++) + writel(0x00000000, spec->mem_base + addr[i]); + + cur_addr = i; + switch (ca0132_quirk(spec)) { + case QUIRK_ZXR: + tmp[0] = 0x00880480; + tmp[1] = 0x00000080; + break; + case QUIRK_SBZ: + tmp[0] = 0x00820680; + tmp[1] = 0x00000083; + break; + case QUIRK_R3D: + tmp[0] = 0x00880680; + tmp[1] = 0x00000083; + break; + default: + tmp[0] = 0x00000000; + tmp[1] = 0x00000000; + break; + } + + for (i = 0; i < 2; i++) + writel(tmp[i], spec->mem_base + addr[cur_addr + i]); + + cur_addr += i; + + switch (ca0132_quirk(spec)) { + case QUIRK_ZXR: + count = ARRAY_SIZE(ca0113_mmio_init_data_zxr); + data = ca0113_mmio_init_data_zxr; + break; + default: + count = ARRAY_SIZE(ca0113_mmio_init_data_sbz); + data = ca0113_mmio_init_data_sbz; + break; + } + + for (i = 0; i < count; i++) + writel(data[i], spec->mem_base + addr[cur_addr + i]); +} + +static void ca0132_mmio_init_ae5(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + const unsigned int *addr, *data; + unsigned int i, count; + + addr = ca0113_mmio_init_address_ae5; + data = ca0113_mmio_init_data_ae5; + count = ARRAY_SIZE(ca0113_mmio_init_data_ae5); + + if (ca0132_quirk(spec) == QUIRK_AE7) { + writel(0x00000680, spec->mem_base + 0x1c); + writel(0x00880680, spec->mem_base + 0x1c); + } + + for (i = 0; i < count; i++) { + /* + * AE-7 shares all writes with the AE-5, except that it writes + * a different value to 0x20c. + */ + if (i == 21 && ca0132_quirk(spec) == QUIRK_AE7) { + writel(0x00800001, spec->mem_base + addr[i]); + continue; + } + + writel(data[i], spec->mem_base + addr[i]); + } + + if (ca0132_quirk(spec) == QUIRK_AE5) + writel(0x00880680, spec->mem_base + 0x1c); +} + +static void ca0132_mmio_init(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + switch (ca0132_quirk(spec)) { + case QUIRK_R3D: + case QUIRK_SBZ: + case QUIRK_ZXR: + ca0132_mmio_init_sbz(codec); + break; + case QUIRK_AE5: + ca0132_mmio_init_ae5(codec); + break; + default: + break; + } +} + +static const unsigned int ca0132_ae5_register_set_addresses[] = { + 0x304, 0x304, 0x304, 0x304, 0x100, 0x304, 0x100, 0x304, 0x100, 0x304, + 0x100, 0x304, 0x86c, 0x800, 0x86c, 0x800, 0x804 +}; + +static const unsigned char ca0132_ae5_register_set_data[] = { + 0x0f, 0x0e, 0x1f, 0x0c, 0x3f, 0x08, 0x7f, 0x00, 0xff, 0x00, 0x6b, + 0x01, 0x6b, 0x57 +}; + +/* + * This function writes to some SFR's, does some region2 writes, and then + * eventually resets the codec with the 0x7ff verb. Not quite sure why it does + * what it does. + */ +static void ae5_register_set(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int count = ARRAY_SIZE(ca0132_ae5_register_set_addresses); + const unsigned int *addr = ca0132_ae5_register_set_addresses; + const unsigned char *data = ca0132_ae5_register_set_data; + unsigned int i, cur_addr; + unsigned char tmp[3]; + + if (ca0132_quirk(spec) == QUIRK_AE7) + chipio_8051_write_pll_pmu(codec, 0x41, 0xc8); + + chipio_8051_write_direct(codec, 0x93, 0x10); + chipio_8051_write_pll_pmu(codec, 0x44, 0xc2); + + if (ca0132_quirk(spec) == QUIRK_AE7) { + tmp[0] = 0x03; + tmp[1] = 0x03; + tmp[2] = 0x07; + } else { + tmp[0] = 0x0f; + tmp[1] = 0x0f; + tmp[2] = 0x0f; + } + + for (i = cur_addr = 0; i < 3; i++, cur_addr++) + writeb(tmp[i], spec->mem_base + addr[cur_addr]); + + /* + * First writes are in single bytes, final are in 4 bytes. So, we use + * writeb, then writel. + */ + for (i = 0; cur_addr < 12; i++, cur_addr++) + writeb(data[i], spec->mem_base + addr[cur_addr]); + + for (; cur_addr < count; i++, cur_addr++) + writel(data[i], spec->mem_base + addr[cur_addr]); + + writel(0x00800001, spec->mem_base + 0x20c); + + if (ca0132_quirk(spec) == QUIRK_AE7) { + ca0113_mmio_command_set_type2(codec, 0x48, 0x07, 0x83); + ca0113_mmio_command_set(codec, 0x30, 0x2e, 0x3f); + } else { + ca0113_mmio_command_set(codec, 0x30, 0x2d, 0x3f); + } + + chipio_8051_write_direct(codec, 0x90, 0x00); + chipio_8051_write_direct(codec, 0x90, 0x10); + + if (ca0132_quirk(spec) == QUIRK_AE5) + ca0113_mmio_command_set(codec, 0x48, 0x07, 0x83); +} + +/* + * Extra init functions for alternative ca0132 codecs. Done + * here so they don't clutter up the main ca0132_init function + * anymore than they have to. + */ +static void ca0132_alt_init(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + ca0132_alt_vol_setup(codec); + + switch (ca0132_quirk(spec)) { + case QUIRK_SBZ: + codec_dbg(codec, "SBZ alt_init"); + ca0132_gpio_init(codec); + sbz_pre_dsp_setup(codec); + snd_hda_sequence_write(codec, spec->chip_init_verbs); + snd_hda_sequence_write(codec, spec->desktop_init_verbs); + break; + case QUIRK_R3DI: + codec_dbg(codec, "R3DI alt_init"); + ca0132_gpio_init(codec); + ca0132_gpio_setup(codec); + r3di_gpio_dsp_status_set(codec, R3DI_DSP_DOWNLOADING); + r3di_pre_dsp_setup(codec); + snd_hda_sequence_write(codec, spec->chip_init_verbs); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, 0x6FF, 0xC4); + break; + case QUIRK_R3D: + r3d_pre_dsp_setup(codec); + snd_hda_sequence_write(codec, spec->chip_init_verbs); + snd_hda_sequence_write(codec, spec->desktop_init_verbs); + break; + case QUIRK_AE5: + ca0132_gpio_init(codec); + chipio_8051_write_pll_pmu(codec, 0x49, 0x88); + chipio_write(codec, 0x18b030, 0x00000020); + snd_hda_sequence_write(codec, spec->chip_init_verbs); + snd_hda_sequence_write(codec, spec->desktop_init_verbs); + ca0113_mmio_command_set(codec, 0x30, 0x32, 0x3f); + break; + case QUIRK_AE7: + ca0132_gpio_init(codec); + chipio_8051_write_pll_pmu(codec, 0x49, 0x88); + snd_hda_sequence_write(codec, spec->chip_init_verbs); + snd_hda_sequence_write(codec, spec->desktop_init_verbs); + chipio_write(codec, 0x18b008, 0x000000f8); + chipio_write(codec, 0x18b008, 0x000000f0); + chipio_write(codec, 0x18b030, 0x00000020); + ca0113_mmio_command_set(codec, 0x30, 0x32, 0x3f); + break; + case QUIRK_ZXR: + chipio_8051_write_pll_pmu(codec, 0x49, 0x88); + snd_hda_sequence_write(codec, spec->chip_init_verbs); + snd_hda_sequence_write(codec, spec->desktop_init_verbs); + zxr_pre_dsp_setup(codec); + break; + default: + break; + } +} + +static int ca0132_init(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + int i; + bool dsp_loaded; + + /* + * If the DSP is already downloaded, and init has been entered again, + * there's only two reasons for it. One, the codec has awaken from a + * suspended state, and in that case dspload_is_loaded will return + * false, and the init will be ran again. The other reason it gets + * re entered is on startup for some reason it triggers a suspend and + * resume state. In this case, it will check if the DSP is downloaded, + * and not run the init function again. For codecs using alt_functions, + * it will check if the DSP is loaded properly. + */ + if (spec->dsp_state == DSP_DOWNLOADED) { + dsp_loaded = dspload_is_loaded(codec); + if (!dsp_loaded) { + spec->dsp_reload = true; + spec->dsp_state = DSP_DOWNLOAD_INIT; + } else { + if (ca0132_quirk(spec) == QUIRK_SBZ) + sbz_dsp_startup_check(codec); + return 0; + } + } + + if (spec->dsp_state != DSP_DOWNLOAD_FAILED) + spec->dsp_state = DSP_DOWNLOAD_INIT; + spec->curr_chip_addx = INVALID_CHIP_ADDRESS; + + if (ca0132_use_pci_mmio(spec)) + ca0132_mmio_init(codec); + + snd_hda_power_up_pm(codec); + + if (ca0132_quirk(spec) == QUIRK_AE5 || ca0132_quirk(spec) == QUIRK_AE7) + ae5_register_set(codec); + + ca0132_init_params(codec); + ca0132_init_flags(codec); + + snd_hda_sequence_write(codec, spec->base_init_verbs); + + if (ca0132_use_alt_functions(spec)) + ca0132_alt_init(codec); + + ca0132_download_dsp(codec); + + ca0132_refresh_widget_caps(codec); + + switch (ca0132_quirk(spec)) { + case QUIRK_R3DI: + case QUIRK_R3D: + r3d_setup_defaults(codec); + break; + case QUIRK_SBZ: + case QUIRK_ZXR: + sbz_setup_defaults(codec); + break; + case QUIRK_AE5: + ae5_setup_defaults(codec); + break; + case QUIRK_AE7: + ae7_setup_defaults(codec); + break; + default: + ca0132_setup_defaults(codec); + ca0132_init_analog_mic2(codec); + ca0132_init_dmic(codec); + break; + } + + for (i = 0; i < spec->num_outputs; i++) + init_output(codec, spec->out_pins[i], spec->dacs[0]); + + init_output(codec, cfg->dig_out_pins[0], spec->dig_out); + + for (i = 0; i < spec->num_inputs; i++) + init_input(codec, spec->input_pins[i], spec->adcs[i]); + + init_input(codec, cfg->dig_in_pin, spec->dig_in); + + if (!ca0132_use_alt_functions(spec)) { + snd_hda_sequence_write(codec, spec->chip_init_verbs); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PARAM_EX_ID_SET, 0x0D); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PARAM_EX_VALUE_SET, 0x20); + } + + if (ca0132_quirk(spec) == QUIRK_SBZ) + ca0132_gpio_setup(codec); + + snd_hda_sequence_write(codec, spec->spec_init_verbs); + if (ca0132_use_alt_functions(spec)) { + ca0132_alt_select_out(codec); + ca0132_alt_select_in(codec); + } else { + ca0132_select_out(codec); + ca0132_select_mic(codec); + } + + snd_hda_jack_report_sync(codec); + + /* + * Re set the PlayEnhancement switch on a resume event, because the + * controls will not be reloaded. + */ + if (spec->dsp_reload) { + spec->dsp_reload = false; + ca0132_pe_switch_set(codec); + } + + snd_hda_power_down_pm(codec); + + return 0; +} + +static int dbpro_init(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + unsigned int i; + + init_output(codec, cfg->dig_out_pins[0], spec->dig_out); + init_input(codec, cfg->dig_in_pin, spec->dig_in); + + for (i = 0; i < spec->num_inputs; i++) + init_input(codec, spec->input_pins[i], spec->adcs[i]); + + return 0; +} + +static void ca0132_free(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + cancel_delayed_work_sync(&spec->unsol_hp_work); + snd_hda_power_up(codec); + switch (ca0132_quirk(spec)) { + case QUIRK_SBZ: + sbz_exit_chip(codec); + break; + case QUIRK_ZXR: + zxr_exit_chip(codec); + break; + case QUIRK_R3D: + r3d_exit_chip(codec); + break; + case QUIRK_AE5: + ae5_exit_chip(codec); + break; + case QUIRK_AE7: + ae7_exit_chip(codec); + break; + case QUIRK_R3DI: + r3di_gpio_shutdown(codec); + break; + default: + break; + } + + snd_hda_sequence_write(codec, spec->base_exit_verbs); + ca0132_exit_chip(codec); + + snd_hda_power_down(codec); +#ifdef CONFIG_PCI + if (spec->mem_base) + pci_iounmap(codec->bus->pci, spec->mem_base); +#endif + kfree(spec->spec_init_verbs); + kfree(codec->spec); +} + +static void dbpro_free(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + zxr_dbpro_power_state_shutdown(codec); + + kfree(spec->spec_init_verbs); + kfree(codec->spec); +} + +#ifdef CONFIG_PM +static int ca0132_suspend(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + cancel_delayed_work_sync(&spec->unsol_hp_work); + return 0; +} +#endif + +static const struct hda_codec_ops ca0132_patch_ops = { + .build_controls = ca0132_build_controls, + .build_pcms = ca0132_build_pcms, + .init = ca0132_init, + .free = ca0132_free, + .unsol_event = snd_hda_jack_unsol_event, +#ifdef CONFIG_PM + .suspend = ca0132_suspend, +#endif +}; + +static const struct hda_codec_ops dbpro_patch_ops = { + .build_controls = dbpro_build_controls, + .build_pcms = dbpro_build_pcms, + .init = dbpro_init, + .free = dbpro_free, +}; + +static void ca0132_config(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + spec->dacs[0] = 0x2; + spec->dacs[1] = 0x3; + spec->dacs[2] = 0x4; + + spec->multiout.dac_nids = spec->dacs; + spec->multiout.num_dacs = 3; + + if (!ca0132_use_alt_functions(spec)) + spec->multiout.max_channels = 2; + else + spec->multiout.max_channels = 6; + + switch (ca0132_quirk(spec)) { + case QUIRK_ALIENWARE: + codec_dbg(codec, "%s: QUIRK_ALIENWARE applied.\n", __func__); + snd_hda_apply_pincfgs(codec, alienware_pincfgs); + break; + case QUIRK_SBZ: + codec_dbg(codec, "%s: QUIRK_SBZ applied.\n", __func__); + snd_hda_apply_pincfgs(codec, sbz_pincfgs); + break; + case QUIRK_ZXR: + codec_dbg(codec, "%s: QUIRK_ZXR applied.\n", __func__); + snd_hda_apply_pincfgs(codec, zxr_pincfgs); + break; + case QUIRK_R3D: + codec_dbg(codec, "%s: QUIRK_R3D applied.\n", __func__); + snd_hda_apply_pincfgs(codec, r3d_pincfgs); + break; + case QUIRK_R3DI: + codec_dbg(codec, "%s: QUIRK_R3DI applied.\n", __func__); + snd_hda_apply_pincfgs(codec, r3di_pincfgs); + break; + case QUIRK_AE5: + codec_dbg(codec, "%s: QUIRK_AE5 applied.\n", __func__); + snd_hda_apply_pincfgs(codec, ae5_pincfgs); + break; + case QUIRK_AE7: + codec_dbg(codec, "%s: QUIRK_AE7 applied.\n", __func__); + snd_hda_apply_pincfgs(codec, ae7_pincfgs); + break; + default: + break; + } + + switch (ca0132_quirk(spec)) { + case QUIRK_ALIENWARE: + spec->num_outputs = 2; + spec->out_pins[0] = 0x0b; /* speaker out */ + spec->out_pins[1] = 0x0f; + spec->shared_out_nid = 0x2; + spec->unsol_tag_hp = 0x0f; + + spec->adcs[0] = 0x7; /* digital mic / analog mic1 */ + spec->adcs[1] = 0x8; /* analog mic2 */ + spec->adcs[2] = 0xa; /* what u hear */ + + spec->num_inputs = 3; + spec->input_pins[0] = 0x12; + spec->input_pins[1] = 0x11; + spec->input_pins[2] = 0x13; + spec->shared_mic_nid = 0x7; + spec->unsol_tag_amic1 = 0x11; + break; + case QUIRK_SBZ: + case QUIRK_R3D: + spec->num_outputs = 2; + spec->out_pins[0] = 0x0B; /* Line out */ + spec->out_pins[1] = 0x0F; /* Rear headphone out */ + spec->out_pins[2] = 0x10; /* Front Headphone / Center/LFE*/ + spec->out_pins[3] = 0x11; /* Rear surround */ + spec->shared_out_nid = 0x2; + spec->unsol_tag_hp = spec->out_pins[1]; + spec->unsol_tag_front_hp = spec->out_pins[2]; + + spec->adcs[0] = 0x7; /* Rear Mic / Line-in */ + spec->adcs[1] = 0x8; /* Front Mic, but only if no DSP */ + spec->adcs[2] = 0xa; /* what u hear */ + + spec->num_inputs = 2; + spec->input_pins[0] = 0x12; /* Rear Mic / Line-in */ + spec->input_pins[1] = 0x13; /* What U Hear */ + spec->shared_mic_nid = 0x7; + spec->unsol_tag_amic1 = spec->input_pins[0]; + + /* SPDIF I/O */ + spec->dig_out = 0x05; + spec->multiout.dig_out_nid = spec->dig_out; + spec->dig_in = 0x09; + break; + case QUIRK_ZXR: + spec->num_outputs = 2; + spec->out_pins[0] = 0x0B; /* Line out */ + spec->out_pins[1] = 0x0F; /* Rear headphone out */ + spec->out_pins[2] = 0x10; /* Center/LFE */ + spec->out_pins[3] = 0x11; /* Rear surround */ + spec->shared_out_nid = 0x2; + spec->unsol_tag_hp = spec->out_pins[1]; + spec->unsol_tag_front_hp = spec->out_pins[2]; + + spec->adcs[0] = 0x7; /* Rear Mic / Line-in */ + spec->adcs[1] = 0x8; /* Not connected, no front mic */ + spec->adcs[2] = 0xa; /* what u hear */ + + spec->num_inputs = 2; + spec->input_pins[0] = 0x12; /* Rear Mic / Line-in */ + spec->input_pins[1] = 0x13; /* What U Hear */ + spec->shared_mic_nid = 0x7; + spec->unsol_tag_amic1 = spec->input_pins[0]; + break; + case QUIRK_ZXR_DBPRO: + spec->adcs[0] = 0x8; /* ZxR DBPro Aux In */ + + spec->num_inputs = 1; + spec->input_pins[0] = 0x11; /* RCA Line-in */ + + spec->dig_out = 0x05; + spec->multiout.dig_out_nid = spec->dig_out; + + spec->dig_in = 0x09; + break; + case QUIRK_AE5: + case QUIRK_AE7: + spec->num_outputs = 2; + spec->out_pins[0] = 0x0B; /* Line out */ + spec->out_pins[1] = 0x11; /* Rear headphone out */ + spec->out_pins[2] = 0x10; /* Front Headphone / Center/LFE*/ + spec->out_pins[3] = 0x0F; /* Rear surround */ + spec->shared_out_nid = 0x2; + spec->unsol_tag_hp = spec->out_pins[1]; + spec->unsol_tag_front_hp = spec->out_pins[2]; + + spec->adcs[0] = 0x7; /* Rear Mic / Line-in */ + spec->adcs[1] = 0x8; /* Front Mic, but only if no DSP */ + spec->adcs[2] = 0xa; /* what u hear */ + + spec->num_inputs = 2; + spec->input_pins[0] = 0x12; /* Rear Mic / Line-in */ + spec->input_pins[1] = 0x13; /* What U Hear */ + spec->shared_mic_nid = 0x7; + spec->unsol_tag_amic1 = spec->input_pins[0]; + + /* SPDIF I/O */ + spec->dig_out = 0x05; + spec->multiout.dig_out_nid = spec->dig_out; + break; + case QUIRK_R3DI: + spec->num_outputs = 2; + spec->out_pins[0] = 0x0B; /* Line out */ + spec->out_pins[1] = 0x0F; /* Rear headphone out */ + spec->out_pins[2] = 0x10; /* Front Headphone / Center/LFE*/ + spec->out_pins[3] = 0x11; /* Rear surround */ + spec->shared_out_nid = 0x2; + spec->unsol_tag_hp = spec->out_pins[1]; + spec->unsol_tag_front_hp = spec->out_pins[2]; + + spec->adcs[0] = 0x07; /* Rear Mic / Line-in */ + spec->adcs[1] = 0x08; /* Front Mic, but only if no DSP */ + spec->adcs[2] = 0x0a; /* what u hear */ + + spec->num_inputs = 2; + spec->input_pins[0] = 0x12; /* Rear Mic / Line-in */ + spec->input_pins[1] = 0x13; /* What U Hear */ + spec->shared_mic_nid = 0x7; + spec->unsol_tag_amic1 = spec->input_pins[0]; + + /* SPDIF I/O */ + spec->dig_out = 0x05; + spec->multiout.dig_out_nid = spec->dig_out; + break; + default: + spec->num_outputs = 2; + spec->out_pins[0] = 0x0b; /* speaker out */ + spec->out_pins[1] = 0x10; /* headphone out */ + spec->shared_out_nid = 0x2; + spec->unsol_tag_hp = spec->out_pins[1]; + + spec->adcs[0] = 0x7; /* digital mic / analog mic1 */ + spec->adcs[1] = 0x8; /* analog mic2 */ + spec->adcs[2] = 0xa; /* what u hear */ + + spec->num_inputs = 3; + spec->input_pins[0] = 0x12; + spec->input_pins[1] = 0x11; + spec->input_pins[2] = 0x13; + spec->shared_mic_nid = 0x7; + spec->unsol_tag_amic1 = spec->input_pins[0]; + + /* SPDIF I/O */ + spec->dig_out = 0x05; + spec->multiout.dig_out_nid = spec->dig_out; + spec->dig_in = 0x09; + break; + } +} + +static int ca0132_prepare_verbs(struct hda_codec *codec) +{ +/* Verbs + terminator (an empty element) */ +#define NUM_SPEC_VERBS 2 + struct ca0132_spec *spec = codec->spec; + + spec->chip_init_verbs = ca0132_init_verbs0; + /* + * Since desktop cards use pci_mmio, this can be used to determine + * whether or not to use these verbs instead of a separate bool. + */ + if (ca0132_use_pci_mmio(spec)) + spec->desktop_init_verbs = ca0132_init_verbs1; + spec->spec_init_verbs = kcalloc(NUM_SPEC_VERBS, + sizeof(struct hda_verb), + GFP_KERNEL); + if (!spec->spec_init_verbs) + return -ENOMEM; + + /* config EAPD */ + spec->spec_init_verbs[0].nid = 0x0b; + spec->spec_init_verbs[0].param = 0x78D; + spec->spec_init_verbs[0].verb = 0x00; + + /* Previously commented configuration */ + /* + spec->spec_init_verbs[2].nid = 0x0b; + spec->spec_init_verbs[2].param = AC_VERB_SET_EAPD_BTLENABLE; + spec->spec_init_verbs[2].verb = 0x02; + + spec->spec_init_verbs[3].nid = 0x10; + spec->spec_init_verbs[3].param = 0x78D; + spec->spec_init_verbs[3].verb = 0x02; + + spec->spec_init_verbs[4].nid = 0x10; + spec->spec_init_verbs[4].param = AC_VERB_SET_EAPD_BTLENABLE; + spec->spec_init_verbs[4].verb = 0x02; + */ + + /* Terminator: spec->spec_init_verbs[NUM_SPEC_VERBS-1] */ + return 0; +} + +/* + * The Sound Blaster ZxR shares the same PCI subsystem ID as some regular + * Sound Blaster Z cards. However, they have different HDA codec subsystem + * ID's. So, we check for the ZxR's subsystem ID, as well as the DBPro + * daughter boards ID. + */ +static void sbz_detect_quirk(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + switch (codec->core.subsystem_id) { + case 0x11020033: + spec->quirk = QUIRK_ZXR; + break; + case 0x1102003f: + spec->quirk = QUIRK_ZXR_DBPRO; + break; + default: + spec->quirk = QUIRK_SBZ; + break; + } +} + +static int patch_ca0132(struct hda_codec *codec) +{ + struct ca0132_spec *spec; + int err; + const struct snd_pci_quirk *quirk; + + codec_dbg(codec, "patch_ca0132\n"); + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return -ENOMEM; + codec->spec = spec; + spec->codec = codec; + + /* Detect codec quirk */ + quirk = snd_pci_quirk_lookup(codec->bus->pci, ca0132_quirks); + if (quirk) + spec->quirk = quirk->value; + else + spec->quirk = QUIRK_NONE; + if (ca0132_quirk(spec) == QUIRK_SBZ) + sbz_detect_quirk(codec); + + if (ca0132_quirk(spec) == QUIRK_ZXR_DBPRO) + codec->patch_ops = dbpro_patch_ops; + else + codec->patch_ops = ca0132_patch_ops; + + codec->pcm_format_first = 1; + codec->no_sticky_stream = 1; + + + spec->dsp_state = DSP_DOWNLOAD_INIT; + spec->num_mixers = 1; + + /* Set which mixers each quirk uses. */ + switch (ca0132_quirk(spec)) { + case QUIRK_SBZ: + spec->mixers[0] = desktop_mixer; + snd_hda_codec_set_name(codec, "Sound Blaster Z"); + break; + case QUIRK_ZXR: + spec->mixers[0] = desktop_mixer; + snd_hda_codec_set_name(codec, "Sound Blaster ZxR"); + break; + case QUIRK_ZXR_DBPRO: + break; + case QUIRK_R3D: + spec->mixers[0] = desktop_mixer; + snd_hda_codec_set_name(codec, "Recon3D"); + break; + case QUIRK_R3DI: + spec->mixers[0] = r3di_mixer; + snd_hda_codec_set_name(codec, "Recon3Di"); + break; + case QUIRK_AE5: + spec->mixers[0] = desktop_mixer; + snd_hda_codec_set_name(codec, "Sound BlasterX AE-5"); + break; + case QUIRK_AE7: + spec->mixers[0] = desktop_mixer; + snd_hda_codec_set_name(codec, "Sound Blaster AE-7"); + break; + default: + spec->mixers[0] = ca0132_mixer; + break; + } + + /* Setup whether or not to use alt functions/controls/pci_mmio */ + switch (ca0132_quirk(spec)) { + case QUIRK_SBZ: + case QUIRK_R3D: + case QUIRK_AE5: + case QUIRK_AE7: + case QUIRK_ZXR: + spec->use_alt_controls = true; + spec->use_alt_functions = true; + spec->use_pci_mmio = true; + break; + case QUIRK_R3DI: + spec->use_alt_controls = true; + spec->use_alt_functions = true; + spec->use_pci_mmio = false; + break; + default: + spec->use_alt_controls = false; + spec->use_alt_functions = false; + spec->use_pci_mmio = false; + break; + } + +#ifdef CONFIG_PCI + if (spec->use_pci_mmio) { + spec->mem_base = pci_iomap(codec->bus->pci, 2, 0xC20); + if (spec->mem_base == NULL) { + codec_warn(codec, "pci_iomap failed! Setting quirk to QUIRK_NONE."); + spec->quirk = QUIRK_NONE; + } + } +#endif + + spec->base_init_verbs = ca0132_base_init_verbs; + spec->base_exit_verbs = ca0132_base_exit_verbs; + + INIT_DELAYED_WORK(&spec->unsol_hp_work, ca0132_unsol_hp_delayed); + + ca0132_init_chip(codec); + + ca0132_config(codec); + + err = ca0132_prepare_verbs(codec); + if (err < 0) + goto error; + + err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL); + if (err < 0) + goto error; + + ca0132_setup_unsol(codec); + + return 0; + + error: + ca0132_free(codec); + return err; +} + +/* + * patch entries + */ +static const struct hda_device_id snd_hda_id_ca0132[] = { + HDA_CODEC_ENTRY(0x11020011, "CA0132", patch_ca0132), + {} /* terminator */ +}; +MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_ca0132); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Creative Sound Core3D codec"); + +static struct hda_codec_driver ca0132_driver = { + .id = snd_hda_id_ca0132, +}; + +module_hda_codec_driver(ca0132_driver); diff --git a/sound/pci/hda/patch_cirrus.c b/sound/pci/hda/patch_cirrus.c new file mode 100644 index 000000000..6807b4708 --- /dev/null +++ b/sound/pci/hda/patch_cirrus.c @@ -0,0 +1,1247 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HD audio interface patch for Cirrus Logic CS420x chip + * + * Copyright (c) 2009 Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <sound/core.h> +#include <linux/pci.h> +#include <sound/tlv.h> +#include <sound/hda_codec.h> +#include "hda_local.h" +#include "hda_auto_parser.h" +#include "hda_jack.h" +#include "hda_generic.h" + +/* + */ + +struct cs_spec { + struct hda_gen_spec gen; + + unsigned int gpio_mask; + unsigned int gpio_dir; + unsigned int gpio_data; + unsigned int gpio_eapd_hp; /* EAPD GPIO bit for headphones */ + unsigned int gpio_eapd_speaker; /* EAPD GPIO bit for speakers */ + + /* CS421x */ + unsigned int spdif_detect:1; + unsigned int spdif_present:1; + unsigned int sense_b:1; + hda_nid_t vendor_nid; + + /* for MBP SPDIF control */ + int (*spdif_sw_put)(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +}; + +/* available models with CS420x */ +enum { + CS420X_MBP53, + CS420X_MBP55, + CS420X_IMAC27, + CS420X_GPIO_13, + CS420X_GPIO_23, + CS420X_MBP101, + CS420X_MBP81, + CS420X_MBA42, + CS420X_AUTO, + /* aliases */ + CS420X_IMAC27_122 = CS420X_GPIO_23, + CS420X_APPLE = CS420X_GPIO_13, +}; + +/* CS421x boards */ +enum { + CS421X_CDB4210, + CS421X_SENSE_B, + CS421X_STUMPY, +}; + +/* Vendor-specific processing widget */ +#define CS420X_VENDOR_NID 0x11 +#define CS_DIG_OUT1_PIN_NID 0x10 +#define CS_DIG_OUT2_PIN_NID 0x15 +#define CS_DMIC1_PIN_NID 0x0e +#define CS_DMIC2_PIN_NID 0x12 + +/* coef indices */ +#define IDX_SPDIF_STAT 0x0000 +#define IDX_SPDIF_CTL 0x0001 +#define IDX_ADC_CFG 0x0002 +/* SZC bitmask, 4 modes below: + * 0 = immediate, + * 1 = digital immediate, analog zero-cross + * 2 = digtail & analog soft-ramp + * 3 = digital soft-ramp, analog zero-cross + */ +#define CS_COEF_ADC_SZC_MASK (3 << 0) +#define CS_COEF_ADC_MIC_SZC_MODE (3 << 0) /* SZC setup for mic */ +#define CS_COEF_ADC_LI_SZC_MODE (3 << 0) /* SZC setup for line-in */ +/* PGA mode: 0 = differential, 1 = signle-ended */ +#define CS_COEF_ADC_MIC_PGA_MODE (1 << 5) /* PGA setup for mic */ +#define CS_COEF_ADC_LI_PGA_MODE (1 << 6) /* PGA setup for line-in */ +#define IDX_DAC_CFG 0x0003 +/* SZC bitmask, 4 modes below: + * 0 = Immediate + * 1 = zero-cross + * 2 = soft-ramp + * 3 = soft-ramp on zero-cross + */ +#define CS_COEF_DAC_HP_SZC_MODE (3 << 0) /* nid 0x02 */ +#define CS_COEF_DAC_LO_SZC_MODE (3 << 2) /* nid 0x03 */ +#define CS_COEF_DAC_SPK_SZC_MODE (3 << 4) /* nid 0x04 */ + +#define IDX_BEEP_CFG 0x0004 +/* 0x0008 - test reg key */ +/* 0x0009 - 0x0014 -> 12 test regs */ +/* 0x0015 - visibility reg */ + +/* Cirrus Logic CS4208 */ +#define CS4208_VENDOR_NID 0x24 + +/* + * Cirrus Logic CS4210 + * + * 1 DAC => HP(sense) / Speakers, + * 1 ADC <= LineIn(sense) / MicIn / DMicIn, + * 1 SPDIF OUT => SPDIF Trasmitter(sense) + */ +#define CS4210_DAC_NID 0x02 +#define CS4210_ADC_NID 0x03 +#define CS4210_VENDOR_NID 0x0B +#define CS421X_DMIC_PIN_NID 0x09 /* Port E */ +#define CS421X_SPDIF_PIN_NID 0x0A /* Port H */ + +#define CS421X_IDX_DEV_CFG 0x01 +#define CS421X_IDX_ADC_CFG 0x02 +#define CS421X_IDX_DAC_CFG 0x03 +#define CS421X_IDX_SPK_CTL 0x04 + +/* Cirrus Logic CS4213 is like CS4210 but does not have SPDIF input/output */ +#define CS4213_VENDOR_NID 0x09 + + +static inline int cs_vendor_coef_get(struct hda_codec *codec, unsigned int idx) +{ + struct cs_spec *spec = codec->spec; + + snd_hda_codec_write(codec, spec->vendor_nid, 0, + AC_VERB_SET_COEF_INDEX, idx); + return snd_hda_codec_read(codec, spec->vendor_nid, 0, + AC_VERB_GET_PROC_COEF, 0); +} + +static inline void cs_vendor_coef_set(struct hda_codec *codec, unsigned int idx, + unsigned int coef) +{ + struct cs_spec *spec = codec->spec; + + snd_hda_codec_write(codec, spec->vendor_nid, 0, + AC_VERB_SET_COEF_INDEX, idx); + snd_hda_codec_write(codec, spec->vendor_nid, 0, + AC_VERB_SET_PROC_COEF, coef); +} + +/* + * auto-mute and auto-mic switching + * CS421x auto-output redirecting + * HP/SPK/SPDIF + */ + +static void cs_automute(struct hda_codec *codec) +{ + struct cs_spec *spec = codec->spec; + + /* mute HPs if spdif jack (SENSE_B) is present */ + spec->gen.master_mute = !!(spec->spdif_present && spec->sense_b); + + snd_hda_gen_update_outputs(codec); + + if (spec->gpio_eapd_hp || spec->gpio_eapd_speaker) { + if (spec->gen.automute_speaker) + spec->gpio_data = spec->gen.hp_jack_present ? + spec->gpio_eapd_hp : spec->gpio_eapd_speaker; + else + spec->gpio_data = + spec->gpio_eapd_hp | spec->gpio_eapd_speaker; + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DATA, spec->gpio_data); + } +} + +static bool is_active_pin(struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int val; + + val = snd_hda_codec_get_pincfg(codec, nid); + return (get_defcfg_connect(val) != AC_JACK_PORT_NONE); +} + +static void init_input_coef(struct hda_codec *codec) +{ + struct cs_spec *spec = codec->spec; + unsigned int coef; + + /* CS420x has multiple ADC, CS421x has single ADC */ + if (spec->vendor_nid == CS420X_VENDOR_NID) { + coef = cs_vendor_coef_get(codec, IDX_BEEP_CFG); + if (is_active_pin(codec, CS_DMIC2_PIN_NID)) + coef |= 1 << 4; /* DMIC2 2 chan on, GPIO1 off */ + if (is_active_pin(codec, CS_DMIC1_PIN_NID)) + coef |= 1 << 3; /* DMIC1 2 chan on, GPIO0 off + * No effect if SPDIF_OUT2 is + * selected in IDX_SPDIF_CTL. + */ + + cs_vendor_coef_set(codec, IDX_BEEP_CFG, coef); + } +} + +static const struct hda_verb cs_coef_init_verbs[] = { + {0x11, AC_VERB_SET_PROC_STATE, 1}, + {0x11, AC_VERB_SET_COEF_INDEX, IDX_DAC_CFG}, + {0x11, AC_VERB_SET_PROC_COEF, + (0x002a /* DAC1/2/3 SZCMode Soft Ramp */ + | 0x0040 /* Mute DACs on FIFO error */ + | 0x1000 /* Enable DACs High Pass Filter */ + | 0x0400 /* Disable Coefficient Auto increment */ + )}, + /* ADC1/2 - Digital and Analog Soft Ramp */ + {0x11, AC_VERB_SET_COEF_INDEX, IDX_ADC_CFG}, + {0x11, AC_VERB_SET_PROC_COEF, 0x000a}, + /* Beep */ + {0x11, AC_VERB_SET_COEF_INDEX, IDX_BEEP_CFG}, + {0x11, AC_VERB_SET_PROC_COEF, 0x0007}, /* Enable Beep thru DAC1/2/3 */ + + {} /* terminator */ +}; + +static const struct hda_verb cs4208_coef_init_verbs[] = { + {0x01, AC_VERB_SET_POWER_STATE, 0x00}, /* AFG: D0 */ + {0x24, AC_VERB_SET_PROC_STATE, 0x01}, /* VPW: processing on */ + {0x24, AC_VERB_SET_COEF_INDEX, 0x0033}, + {0x24, AC_VERB_SET_PROC_COEF, 0x0001}, /* A1 ICS */ + {0x24, AC_VERB_SET_COEF_INDEX, 0x0034}, + {0x24, AC_VERB_SET_PROC_COEF, 0x1C01}, /* A1 Enable, A Thresh = 300mV */ + {} /* terminator */ +}; + +/* Errata: CS4207 rev C0/C1/C2 Silicon + * + * http://www.cirrus.com/en/pubs/errata/ER880C3.pdf + * + * 6. At high temperature (TA > +85°C), the digital supply current (IVD) + * may be excessive (up to an additional 200 μA), which is most easily + * observed while the part is being held in reset (RESET# active low). + * + * Root Cause: At initial powerup of the device, the logic that drives + * the clock and write enable to the S/PDIF SRC RAMs is not properly + * initialized. + * Certain random patterns will cause a steady leakage current in those + * RAM cells. The issue will resolve once the SRCs are used (turned on). + * + * Workaround: The following verb sequence briefly turns on the S/PDIF SRC + * blocks, which will alleviate the issue. + */ + +static const struct hda_verb cs_errata_init_verbs[] = { + {0x01, AC_VERB_SET_POWER_STATE, 0x00}, /* AFG: D0 */ + {0x11, AC_VERB_SET_PROC_STATE, 0x01}, /* VPW: processing on */ + + {0x11, AC_VERB_SET_COEF_INDEX, 0x0008}, + {0x11, AC_VERB_SET_PROC_COEF, 0x9999}, + {0x11, AC_VERB_SET_COEF_INDEX, 0x0017}, + {0x11, AC_VERB_SET_PROC_COEF, 0xa412}, + {0x11, AC_VERB_SET_COEF_INDEX, 0x0001}, + {0x11, AC_VERB_SET_PROC_COEF, 0x0009}, + + {0x07, AC_VERB_SET_POWER_STATE, 0x00}, /* S/PDIF Rx: D0 */ + {0x08, AC_VERB_SET_POWER_STATE, 0x00}, /* S/PDIF Tx: D0 */ + + {0x11, AC_VERB_SET_COEF_INDEX, 0x0017}, + {0x11, AC_VERB_SET_PROC_COEF, 0x2412}, + {0x11, AC_VERB_SET_COEF_INDEX, 0x0008}, + {0x11, AC_VERB_SET_PROC_COEF, 0x0000}, + {0x11, AC_VERB_SET_COEF_INDEX, 0x0001}, + {0x11, AC_VERB_SET_PROC_COEF, 0x0008}, + {0x11, AC_VERB_SET_PROC_STATE, 0x00}, + {} /* terminator */ +}; + +/* SPDIF setup */ +static void init_digital_coef(struct hda_codec *codec) +{ + unsigned int coef; + + coef = 0x0002; /* SRC_MUTE soft-mute on SPDIF (if no lock) */ + coef |= 0x0008; /* Replace with mute on error */ + if (is_active_pin(codec, CS_DIG_OUT2_PIN_NID)) + coef |= 0x4000; /* RX to TX1 or TX2 Loopthru / SPDIF2 + * SPDIF_OUT2 is shared with GPIO1 and + * DMIC_SDA2. + */ + cs_vendor_coef_set(codec, IDX_SPDIF_CTL, coef); +} + +static int cs_init(struct hda_codec *codec) +{ + struct cs_spec *spec = codec->spec; + + if (spec->vendor_nid == CS420X_VENDOR_NID) { + /* init_verb sequence for C0/C1/C2 errata*/ + snd_hda_sequence_write(codec, cs_errata_init_verbs); + snd_hda_sequence_write(codec, cs_coef_init_verbs); + } else if (spec->vendor_nid == CS4208_VENDOR_NID) { + snd_hda_sequence_write(codec, cs4208_coef_init_verbs); + } + + snd_hda_gen_init(codec); + + if (spec->gpio_mask) { + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK, + spec->gpio_mask); + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION, + spec->gpio_dir); + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, + spec->gpio_data); + } + + if (spec->vendor_nid == CS420X_VENDOR_NID) { + init_input_coef(codec); + init_digital_coef(codec); + } + + return 0; +} + +static int cs_build_controls(struct hda_codec *codec) +{ + int err; + + err = snd_hda_gen_build_controls(codec); + if (err < 0) + return err; + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_BUILD); + return 0; +} + +#define cs_free snd_hda_gen_free + +static const struct hda_codec_ops cs_patch_ops = { + .build_controls = cs_build_controls, + .build_pcms = snd_hda_gen_build_pcms, + .init = cs_init, + .free = cs_free, + .unsol_event = snd_hda_jack_unsol_event, +}; + +static int cs_parse_auto_config(struct hda_codec *codec) +{ + struct cs_spec *spec = codec->spec; + int err; + int i; + + err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, NULL, 0); + if (err < 0) + return err; + + err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg); + if (err < 0) + return err; + + /* keep the ADCs powered up when it's dynamically switchable */ + if (spec->gen.dyn_adc_switch) { + unsigned int done = 0; + + for (i = 0; i < spec->gen.input_mux.num_items; i++) { + int idx = spec->gen.dyn_adc_idx[i]; + + if (done & (1 << idx)) + continue; + snd_hda_gen_fix_pin_power(codec, + spec->gen.adc_nids[idx]); + done |= 1 << idx; + } + } + + return 0; +} + +static const struct hda_model_fixup cs420x_models[] = { + { .id = CS420X_MBP53, .name = "mbp53" }, + { .id = CS420X_MBP55, .name = "mbp55" }, + { .id = CS420X_IMAC27, .name = "imac27" }, + { .id = CS420X_IMAC27_122, .name = "imac27_122" }, + { .id = CS420X_APPLE, .name = "apple" }, + { .id = CS420X_MBP101, .name = "mbp101" }, + { .id = CS420X_MBP81, .name = "mbp81" }, + { .id = CS420X_MBA42, .name = "mba42" }, + {} +}; + +static const struct snd_pci_quirk cs420x_fixup_tbl[] = { + SND_PCI_QUIRK(0x10de, 0x0ac0, "MacBookPro 5,3", CS420X_MBP53), + SND_PCI_QUIRK(0x10de, 0x0d94, "MacBookAir 3,1(2)", CS420X_MBP55), + SND_PCI_QUIRK(0x10de, 0xcb79, "MacBookPro 5,5", CS420X_MBP55), + SND_PCI_QUIRK(0x10de, 0xcb89, "MacBookPro 7,1", CS420X_MBP55), + /* this conflicts with too many other models */ + /*SND_PCI_QUIRK(0x8086, 0x7270, "IMac 27 Inch", CS420X_IMAC27),*/ + + /* codec SSID */ + SND_PCI_QUIRK(0x106b, 0x0600, "iMac 14,1", CS420X_IMAC27_122), + SND_PCI_QUIRK(0x106b, 0x0900, "iMac 12,1", CS420X_IMAC27_122), + SND_PCI_QUIRK(0x106b, 0x1c00, "MacBookPro 8,1", CS420X_MBP81), + SND_PCI_QUIRK(0x106b, 0x2000, "iMac 12,2", CS420X_IMAC27_122), + SND_PCI_QUIRK(0x106b, 0x2800, "MacBookPro 10,1", CS420X_MBP101), + SND_PCI_QUIRK(0x106b, 0x5600, "MacBookAir 5,2", CS420X_MBP81), + SND_PCI_QUIRK(0x106b, 0x5b00, "MacBookAir 4,2", CS420X_MBA42), + SND_PCI_QUIRK_VENDOR(0x106b, "Apple", CS420X_APPLE), + {} /* terminator */ +}; + +static const struct hda_pintbl mbp53_pincfgs[] = { + { 0x09, 0x012b4050 }, + { 0x0a, 0x90100141 }, + { 0x0b, 0x90100140 }, + { 0x0c, 0x018b3020 }, + { 0x0d, 0x90a00110 }, + { 0x0e, 0x400000f0 }, + { 0x0f, 0x01cbe030 }, + { 0x10, 0x014be060 }, + { 0x12, 0x400000f0 }, + { 0x15, 0x400000f0 }, + {} /* terminator */ +}; + +static const struct hda_pintbl mbp55_pincfgs[] = { + { 0x09, 0x012b4030 }, + { 0x0a, 0x90100121 }, + { 0x0b, 0x90100120 }, + { 0x0c, 0x400000f0 }, + { 0x0d, 0x90a00110 }, + { 0x0e, 0x400000f0 }, + { 0x0f, 0x400000f0 }, + { 0x10, 0x014be040 }, + { 0x12, 0x400000f0 }, + { 0x15, 0x400000f0 }, + {} /* terminator */ +}; + +static const struct hda_pintbl imac27_pincfgs[] = { + { 0x09, 0x012b4050 }, + { 0x0a, 0x90100140 }, + { 0x0b, 0x90100142 }, + { 0x0c, 0x018b3020 }, + { 0x0d, 0x90a00110 }, + { 0x0e, 0x400000f0 }, + { 0x0f, 0x01cbe030 }, + { 0x10, 0x014be060 }, + { 0x12, 0x01ab9070 }, + { 0x15, 0x400000f0 }, + {} /* terminator */ +}; + +static const struct hda_pintbl mbp101_pincfgs[] = { + { 0x0d, 0x40ab90f0 }, + { 0x0e, 0x90a600f0 }, + { 0x12, 0x50a600f0 }, + {} /* terminator */ +}; + +static const struct hda_pintbl mba42_pincfgs[] = { + { 0x09, 0x012b4030 }, /* HP */ + { 0x0a, 0x400000f0 }, + { 0x0b, 0x90100120 }, /* speaker */ + { 0x0c, 0x400000f0 }, + { 0x0d, 0x90a00110 }, /* mic */ + { 0x0e, 0x400000f0 }, + { 0x0f, 0x400000f0 }, + { 0x10, 0x400000f0 }, + { 0x12, 0x400000f0 }, + { 0x15, 0x400000f0 }, + {} /* terminator */ +}; + +static const struct hda_pintbl mba6_pincfgs[] = { + { 0x10, 0x032120f0 }, /* HP */ + { 0x11, 0x500000f0 }, + { 0x12, 0x90100010 }, /* Speaker */ + { 0x13, 0x500000f0 }, + { 0x14, 0x500000f0 }, + { 0x15, 0x770000f0 }, + { 0x16, 0x770000f0 }, + { 0x17, 0x430000f0 }, + { 0x18, 0x43ab9030 }, /* Mic */ + { 0x19, 0x770000f0 }, + { 0x1a, 0x770000f0 }, + { 0x1b, 0x770000f0 }, + { 0x1c, 0x90a00090 }, + { 0x1d, 0x500000f0 }, + { 0x1e, 0x500000f0 }, + { 0x1f, 0x500000f0 }, + { 0x20, 0x500000f0 }, + { 0x21, 0x430000f0 }, + { 0x22, 0x430000f0 }, + {} /* terminator */ +}; + +static void cs420x_fixup_gpio_13(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + struct cs_spec *spec = codec->spec; + + spec->gpio_eapd_hp = 2; /* GPIO1 = headphones */ + spec->gpio_eapd_speaker = 8; /* GPIO3 = speakers */ + spec->gpio_mask = spec->gpio_dir = + spec->gpio_eapd_hp | spec->gpio_eapd_speaker; + } +} + +static void cs420x_fixup_gpio_23(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + struct cs_spec *spec = codec->spec; + + spec->gpio_eapd_hp = 4; /* GPIO2 = headphones */ + spec->gpio_eapd_speaker = 8; /* GPIO3 = speakers */ + spec->gpio_mask = spec->gpio_dir = + spec->gpio_eapd_hp | spec->gpio_eapd_speaker; + } +} + +static const struct hda_fixup cs420x_fixups[] = { + [CS420X_MBP53] = { + .type = HDA_FIXUP_PINS, + .v.pins = mbp53_pincfgs, + .chained = true, + .chain_id = CS420X_APPLE, + }, + [CS420X_MBP55] = { + .type = HDA_FIXUP_PINS, + .v.pins = mbp55_pincfgs, + .chained = true, + .chain_id = CS420X_GPIO_13, + }, + [CS420X_IMAC27] = { + .type = HDA_FIXUP_PINS, + .v.pins = imac27_pincfgs, + .chained = true, + .chain_id = CS420X_GPIO_13, + }, + [CS420X_GPIO_13] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs420x_fixup_gpio_13, + }, + [CS420X_GPIO_23] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs420x_fixup_gpio_23, + }, + [CS420X_MBP101] = { + .type = HDA_FIXUP_PINS, + .v.pins = mbp101_pincfgs, + .chained = true, + .chain_id = CS420X_GPIO_13, + }, + [CS420X_MBP81] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* internal mic ADC2: right only, single ended */ + {0x11, AC_VERB_SET_COEF_INDEX, IDX_ADC_CFG}, + {0x11, AC_VERB_SET_PROC_COEF, 0x102a}, + {} + }, + .chained = true, + .chain_id = CS420X_GPIO_13, + }, + [CS420X_MBA42] = { + .type = HDA_FIXUP_PINS, + .v.pins = mba42_pincfgs, + .chained = true, + .chain_id = CS420X_GPIO_13, + }, +}; + +static struct cs_spec *cs_alloc_spec(struct hda_codec *codec, int vendor_nid) +{ + struct cs_spec *spec; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return NULL; + codec->spec = spec; + spec->vendor_nid = vendor_nid; + codec->power_save_node = 1; + snd_hda_gen_spec_init(&spec->gen); + + return spec; +} + +static int patch_cs420x(struct hda_codec *codec) +{ + struct cs_spec *spec; + int err; + + spec = cs_alloc_spec(codec, CS420X_VENDOR_NID); + if (!spec) + return -ENOMEM; + + codec->patch_ops = cs_patch_ops; + spec->gen.automute_hook = cs_automute; + codec->single_adc_amp = 1; + + snd_hda_pick_fixup(codec, cs420x_models, cs420x_fixup_tbl, + cs420x_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + err = cs_parse_auto_config(codec); + if (err < 0) + goto error; + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; + + error: + cs_free(codec); + return err; +} + +/* + * CS4208 support: + * Its layout is no longer compatible with CS4206/CS4207 + */ +enum { + CS4208_MAC_AUTO, + CS4208_MBA6, + CS4208_MBP11, + CS4208_MACMINI, + CS4208_GPIO0, +}; + +static const struct hda_model_fixup cs4208_models[] = { + { .id = CS4208_GPIO0, .name = "gpio0" }, + { .id = CS4208_MBA6, .name = "mba6" }, + { .id = CS4208_MBP11, .name = "mbp11" }, + { .id = CS4208_MACMINI, .name = "macmini" }, + {} +}; + +static const struct snd_pci_quirk cs4208_fixup_tbl[] = { + SND_PCI_QUIRK_VENDOR(0x106b, "Apple", CS4208_MAC_AUTO), + {} /* terminator */ +}; + +/* codec SSID matching */ +static const struct snd_pci_quirk cs4208_mac_fixup_tbl[] = { + SND_PCI_QUIRK(0x106b, 0x5e00, "MacBookPro 11,2", CS4208_MBP11), + SND_PCI_QUIRK(0x106b, 0x6c00, "MacMini 7,1", CS4208_MACMINI), + SND_PCI_QUIRK(0x106b, 0x7100, "MacBookAir 6,1", CS4208_MBA6), + SND_PCI_QUIRK(0x106b, 0x7200, "MacBookAir 6,2", CS4208_MBA6), + SND_PCI_QUIRK(0x106b, 0x7b00, "MacBookPro 12,1", CS4208_MBP11), + {} /* terminator */ +}; + +static void cs4208_fixup_gpio0(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + struct cs_spec *spec = codec->spec; + + spec->gpio_eapd_hp = 0; + spec->gpio_eapd_speaker = 1; + spec->gpio_mask = spec->gpio_dir = + spec->gpio_eapd_hp | spec->gpio_eapd_speaker; + } +} + +static const struct hda_fixup cs4208_fixups[]; + +/* remap the fixup from codec SSID and apply it */ +static void cs4208_fixup_mac(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + codec->fixup_id = HDA_FIXUP_ID_NOT_SET; + snd_hda_pick_fixup(codec, NULL, cs4208_mac_fixup_tbl, cs4208_fixups); + if (codec->fixup_id == HDA_FIXUP_ID_NOT_SET) + codec->fixup_id = CS4208_GPIO0; /* default fixup */ + snd_hda_apply_fixup(codec, action); +} + +/* MacMini 7,1 has the inverted jack detection */ +static void cs4208_fixup_macmini(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + static const struct hda_pintbl pincfgs[] = { + { 0x18, 0x00ab9150 }, /* mic (audio-in) jack: disable detect */ + { 0x21, 0x004be140 }, /* SPDIF: disable detect */ + { } + }; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + /* HP pin (0x10) has an inverted detection */ + codec->inv_jack_detect = 1; + /* disable the bogus Mic and SPDIF jack detections */ + snd_hda_apply_pincfgs(codec, pincfgs); + } +} + +static int cs4208_spdif_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct cs_spec *spec = codec->spec; + hda_nid_t pin = spec->gen.autocfg.dig_out_pins[0]; + int pinctl = ucontrol->value.integer.value[0] ? PIN_OUT : 0; + + snd_hda_set_pin_ctl_cache(codec, pin, pinctl); + return spec->spdif_sw_put(kcontrol, ucontrol); +} + +/* hook the SPDIF switch */ +static void cs4208_fixup_spdif_switch(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_BUILD) { + struct cs_spec *spec = codec->spec; + struct snd_kcontrol *kctl; + + if (!spec->gen.autocfg.dig_out_pins[0]) + return; + kctl = snd_hda_find_mixer_ctl(codec, "IEC958 Playback Switch"); + if (!kctl) + return; + spec->spdif_sw_put = kctl->put; + kctl->put = cs4208_spdif_sw_put; + } +} + +static const struct hda_fixup cs4208_fixups[] = { + [CS4208_MBA6] = { + .type = HDA_FIXUP_PINS, + .v.pins = mba6_pincfgs, + .chained = true, + .chain_id = CS4208_GPIO0, + }, + [CS4208_MBP11] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs4208_fixup_spdif_switch, + .chained = true, + .chain_id = CS4208_GPIO0, + }, + [CS4208_MACMINI] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs4208_fixup_macmini, + .chained = true, + .chain_id = CS4208_GPIO0, + }, + [CS4208_GPIO0] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs4208_fixup_gpio0, + }, + [CS4208_MAC_AUTO] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs4208_fixup_mac, + }, +}; + +/* correct the 0dB offset of input pins */ +static void cs4208_fix_amp_caps(struct hda_codec *codec, hda_nid_t adc) +{ + unsigned int caps; + + caps = query_amp_caps(codec, adc, HDA_INPUT); + caps &= ~(AC_AMPCAP_OFFSET); + caps |= 0x02; + snd_hda_override_amp_caps(codec, adc, HDA_INPUT, caps); +} + +static int patch_cs4208(struct hda_codec *codec) +{ + struct cs_spec *spec; + int err; + + spec = cs_alloc_spec(codec, CS4208_VENDOR_NID); + if (!spec) + return -ENOMEM; + + codec->patch_ops = cs_patch_ops; + spec->gen.automute_hook = cs_automute; + /* exclude NID 0x10 (HP) from output volumes due to different steps */ + spec->gen.out_vol_mask = 1ULL << 0x10; + + snd_hda_pick_fixup(codec, cs4208_models, cs4208_fixup_tbl, + cs4208_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + snd_hda_override_wcaps(codec, 0x18, + get_wcaps(codec, 0x18) | AC_WCAP_STEREO); + cs4208_fix_amp_caps(codec, 0x18); + cs4208_fix_amp_caps(codec, 0x1b); + cs4208_fix_amp_caps(codec, 0x1c); + + err = cs_parse_auto_config(codec); + if (err < 0) + goto error; + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; + + error: + cs_free(codec); + return err; +} + +/* + * Cirrus Logic CS4210 + * + * 1 DAC => HP(sense) / Speakers, + * 1 ADC <= LineIn(sense) / MicIn / DMicIn, + * 1 SPDIF OUT => SPDIF Trasmitter(sense) + */ + +/* CS4210 board names */ +static const struct hda_model_fixup cs421x_models[] = { + { .id = CS421X_CDB4210, .name = "cdb4210" }, + { .id = CS421X_STUMPY, .name = "stumpy" }, + {} +}; + +static const struct snd_pci_quirk cs421x_fixup_tbl[] = { + /* Test Intel board + CDB2410 */ + SND_PCI_QUIRK(0x8086, 0x5001, "DP45SG/CDB4210", CS421X_CDB4210), + {} /* terminator */ +}; + +/* CS4210 board pinconfigs */ +/* Default CS4210 (CDB4210)*/ +static const struct hda_pintbl cdb4210_pincfgs[] = { + { 0x05, 0x0321401f }, + { 0x06, 0x90170010 }, + { 0x07, 0x03813031 }, + { 0x08, 0xb7a70037 }, + { 0x09, 0xb7a6003e }, + { 0x0a, 0x034510f0 }, + {} /* terminator */ +}; + +/* Stumpy ChromeBox */ +static const struct hda_pintbl stumpy_pincfgs[] = { + { 0x05, 0x022120f0 }, + { 0x06, 0x901700f0 }, + { 0x07, 0x02a120f0 }, + { 0x08, 0x77a70037 }, + { 0x09, 0x77a6003e }, + { 0x0a, 0x434510f0 }, + {} /* terminator */ +}; + +/* Setup GPIO/SENSE for each board (if used) */ +static void cs421x_fixup_sense_b(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct cs_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) + spec->sense_b = 1; +} + +static const struct hda_fixup cs421x_fixups[] = { + [CS421X_CDB4210] = { + .type = HDA_FIXUP_PINS, + .v.pins = cdb4210_pincfgs, + .chained = true, + .chain_id = CS421X_SENSE_B, + }, + [CS421X_SENSE_B] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs421x_fixup_sense_b, + }, + [CS421X_STUMPY] = { + .type = HDA_FIXUP_PINS, + .v.pins = stumpy_pincfgs, + }, +}; + +static const struct hda_verb cs421x_coef_init_verbs[] = { + {0x0B, AC_VERB_SET_PROC_STATE, 1}, + {0x0B, AC_VERB_SET_COEF_INDEX, CS421X_IDX_DEV_CFG}, + /* + * Disable Coefficient Index Auto-Increment(DAI)=1, + * PDREF=0 + */ + {0x0B, AC_VERB_SET_PROC_COEF, 0x0001 }, + + {0x0B, AC_VERB_SET_COEF_INDEX, CS421X_IDX_ADC_CFG}, + /* ADC SZCMode = Digital Soft Ramp */ + {0x0B, AC_VERB_SET_PROC_COEF, 0x0002 }, + + {0x0B, AC_VERB_SET_COEF_INDEX, CS421X_IDX_DAC_CFG}, + {0x0B, AC_VERB_SET_PROC_COEF, + (0x0002 /* DAC SZCMode = Digital Soft Ramp */ + | 0x0004 /* Mute DAC on FIFO error */ + | 0x0008 /* Enable DAC High Pass Filter */ + )}, + {} /* terminator */ +}; + +/* Errata: CS4210 rev A1 Silicon + * + * http://www.cirrus.com/en/pubs/errata/ + * + * Description: + * 1. Performance degredation is present in the ADC. + * 2. Speaker output is not completely muted upon HP detect. + * 3. Noise is present when clipping occurs on the amplified + * speaker outputs. + * + * Workaround: + * The following verb sequence written to the registers during + * initialization will correct the issues listed above. + */ + +static const struct hda_verb cs421x_coef_init_verbs_A1_silicon_fixes[] = { + {0x0B, AC_VERB_SET_PROC_STATE, 0x01}, /* VPW: processing on */ + + {0x0B, AC_VERB_SET_COEF_INDEX, 0x0006}, + {0x0B, AC_VERB_SET_PROC_COEF, 0x9999}, /* Test mode: on */ + + {0x0B, AC_VERB_SET_COEF_INDEX, 0x000A}, + {0x0B, AC_VERB_SET_PROC_COEF, 0x14CB}, /* Chop double */ + + {0x0B, AC_VERB_SET_COEF_INDEX, 0x0011}, + {0x0B, AC_VERB_SET_PROC_COEF, 0xA2D0}, /* Increase ADC current */ + + {0x0B, AC_VERB_SET_COEF_INDEX, 0x001A}, + {0x0B, AC_VERB_SET_PROC_COEF, 0x02A9}, /* Mute speaker */ + + {0x0B, AC_VERB_SET_COEF_INDEX, 0x001B}, + {0x0B, AC_VERB_SET_PROC_COEF, 0X1006}, /* Remove noise */ + + {} /* terminator */ +}; + +/* Speaker Amp Gain is controlled by the vendor widget's coef 4 */ +static const DECLARE_TLV_DB_SCALE(cs421x_speaker_boost_db_scale, 900, 300, 0); + +static int cs421x_boost_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 3; + return 0; +} + +static int cs421x_boost_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = + cs_vendor_coef_get(codec, CS421X_IDX_SPK_CTL) & 0x0003; + return 0; +} + +static int cs421x_boost_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + + unsigned int vol = ucontrol->value.integer.value[0]; + unsigned int coef = + cs_vendor_coef_get(codec, CS421X_IDX_SPK_CTL); + unsigned int original_coef = coef; + + coef &= ~0x0003; + coef |= (vol & 0x0003); + if (original_coef != coef) { + cs_vendor_coef_set(codec, CS421X_IDX_SPK_CTL, coef); + return 1; + } + + return 0; +} + +static const struct snd_kcontrol_new cs421x_speaker_boost_ctl = { + + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Speaker Boost Playback Volume", + .info = cs421x_boost_vol_info, + .get = cs421x_boost_vol_get, + .put = cs421x_boost_vol_put, + .tlv = { .p = cs421x_speaker_boost_db_scale }, +}; + +static void cs4210_pinmux_init(struct hda_codec *codec) +{ + struct cs_spec *spec = codec->spec; + unsigned int def_conf, coef; + + /* GPIO, DMIC_SCL, DMIC_SDA and SENSE_B are multiplexed */ + coef = cs_vendor_coef_get(codec, CS421X_IDX_DEV_CFG); + + if (spec->gpio_mask) + coef |= 0x0008; /* B1,B2 are GPIOs */ + else + coef &= ~0x0008; + + if (spec->sense_b) + coef |= 0x0010; /* B2 is SENSE_B, not inverted */ + else + coef &= ~0x0010; + + cs_vendor_coef_set(codec, CS421X_IDX_DEV_CFG, coef); + + if ((spec->gpio_mask || spec->sense_b) && + is_active_pin(codec, CS421X_DMIC_PIN_NID)) { + + /* + * GPIO or SENSE_B forced - disconnect the DMIC pin. + */ + def_conf = snd_hda_codec_get_pincfg(codec, CS421X_DMIC_PIN_NID); + def_conf &= ~AC_DEFCFG_PORT_CONN; + def_conf |= (AC_JACK_PORT_NONE << AC_DEFCFG_PORT_CONN_SHIFT); + snd_hda_codec_set_pincfg(codec, CS421X_DMIC_PIN_NID, def_conf); + } +} + +static void cs4210_spdif_automute(struct hda_codec *codec, + struct hda_jack_callback *tbl) +{ + struct cs_spec *spec = codec->spec; + bool spdif_present = false; + hda_nid_t spdif_pin = spec->gen.autocfg.dig_out_pins[0]; + + /* detect on spdif is specific to CS4210 */ + if (!spec->spdif_detect || + spec->vendor_nid != CS4210_VENDOR_NID) + return; + + spdif_present = snd_hda_jack_detect(codec, spdif_pin); + if (spdif_present == spec->spdif_present) + return; + + spec->spdif_present = spdif_present; + /* SPDIF TX on/off */ + snd_hda_set_pin_ctl(codec, spdif_pin, spdif_present ? PIN_OUT : 0); + + cs_automute(codec); +} + +static void parse_cs421x_digital(struct hda_codec *codec) +{ + struct cs_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->gen.autocfg; + int i; + + for (i = 0; i < cfg->dig_outs; i++) { + hda_nid_t nid = cfg->dig_out_pins[i]; + + if (get_wcaps(codec, nid) & AC_WCAP_UNSOL_CAP) { + spec->spdif_detect = 1; + snd_hda_jack_detect_enable_callback(codec, nid, + cs4210_spdif_automute); + } + } +} + +static int cs421x_init(struct hda_codec *codec) +{ + struct cs_spec *spec = codec->spec; + + if (spec->vendor_nid == CS4210_VENDOR_NID) { + snd_hda_sequence_write(codec, cs421x_coef_init_verbs); + snd_hda_sequence_write(codec, cs421x_coef_init_verbs_A1_silicon_fixes); + cs4210_pinmux_init(codec); + } + + snd_hda_gen_init(codec); + + if (spec->gpio_mask) { + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK, + spec->gpio_mask); + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION, + spec->gpio_dir); + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, + spec->gpio_data); + } + + init_input_coef(codec); + + cs4210_spdif_automute(codec, NULL); + + return 0; +} + +static void fix_volume_caps(struct hda_codec *codec, hda_nid_t dac) +{ + unsigned int caps; + + /* set the upper-limit for mixer amp to 0dB */ + caps = query_amp_caps(codec, dac, HDA_OUTPUT); + caps &= ~(0x7f << AC_AMPCAP_NUM_STEPS_SHIFT); + caps |= ((caps >> AC_AMPCAP_OFFSET_SHIFT) & 0x7f) + << AC_AMPCAP_NUM_STEPS_SHIFT; + snd_hda_override_amp_caps(codec, dac, HDA_OUTPUT, caps); +} + +static int cs421x_parse_auto_config(struct hda_codec *codec) +{ + struct cs_spec *spec = codec->spec; + hda_nid_t dac = CS4210_DAC_NID; + int err; + + fix_volume_caps(codec, dac); + + err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, NULL, 0); + if (err < 0) + return err; + + err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg); + if (err < 0) + return err; + + parse_cs421x_digital(codec); + + if (spec->gen.autocfg.speaker_outs && + spec->vendor_nid == CS4210_VENDOR_NID) { + if (!snd_hda_gen_add_kctl(&spec->gen, NULL, + &cs421x_speaker_boost_ctl)) + return -ENOMEM; + } + + return 0; +} + +#ifdef CONFIG_PM +/* + * Manage PDREF, when transitioning to D3hot + * (DAC,ADC) -> D3, PDREF=1, AFG->D3 + */ +static int cs421x_suspend(struct hda_codec *codec) +{ + struct cs_spec *spec = codec->spec; + unsigned int coef; + + snd_hda_shutup_pins(codec); + + snd_hda_codec_write(codec, CS4210_DAC_NID, 0, + AC_VERB_SET_POWER_STATE, AC_PWRST_D3); + snd_hda_codec_write(codec, CS4210_ADC_NID, 0, + AC_VERB_SET_POWER_STATE, AC_PWRST_D3); + + if (spec->vendor_nid == CS4210_VENDOR_NID) { + coef = cs_vendor_coef_get(codec, CS421X_IDX_DEV_CFG); + coef |= 0x0004; /* PDREF */ + cs_vendor_coef_set(codec, CS421X_IDX_DEV_CFG, coef); + } + + return 0; +} +#endif + +static const struct hda_codec_ops cs421x_patch_ops = { + .build_controls = snd_hda_gen_build_controls, + .build_pcms = snd_hda_gen_build_pcms, + .init = cs421x_init, + .free = cs_free, + .unsol_event = snd_hda_jack_unsol_event, +#ifdef CONFIG_PM + .suspend = cs421x_suspend, +#endif +}; + +static int patch_cs4210(struct hda_codec *codec) +{ + struct cs_spec *spec; + int err; + + spec = cs_alloc_spec(codec, CS4210_VENDOR_NID); + if (!spec) + return -ENOMEM; + + codec->patch_ops = cs421x_patch_ops; + spec->gen.automute_hook = cs_automute; + + snd_hda_pick_fixup(codec, cs421x_models, cs421x_fixup_tbl, + cs421x_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + /* + * Update the GPIO/DMIC/SENSE_B pinmux before the configuration + * is auto-parsed. If GPIO or SENSE_B is forced, DMIC input + * is disabled. + */ + cs4210_pinmux_init(codec); + + err = cs421x_parse_auto_config(codec); + if (err < 0) + goto error; + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; + + error: + cs_free(codec); + return err; +} + +static int patch_cs4213(struct hda_codec *codec) +{ + struct cs_spec *spec; + int err; + + spec = cs_alloc_spec(codec, CS4213_VENDOR_NID); + if (!spec) + return -ENOMEM; + + codec->patch_ops = cs421x_patch_ops; + + err = cs421x_parse_auto_config(codec); + if (err < 0) + goto error; + + return 0; + + error: + cs_free(codec); + return err; +} + +/* + * patch entries + */ +static const struct hda_device_id snd_hda_id_cirrus[] = { + HDA_CODEC_ENTRY(0x10134206, "CS4206", patch_cs420x), + HDA_CODEC_ENTRY(0x10134207, "CS4207", patch_cs420x), + HDA_CODEC_ENTRY(0x10134208, "CS4208", patch_cs4208), + HDA_CODEC_ENTRY(0x10134210, "CS4210", patch_cs4210), + HDA_CODEC_ENTRY(0x10134213, "CS4213", patch_cs4213), + {} /* terminator */ +}; +MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_cirrus); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Cirrus Logic HD-audio codec"); + +static struct hda_codec_driver cirrus_driver = { + .id = snd_hda_id_cirrus, +}; + +module_hda_codec_driver(cirrus_driver); diff --git a/sound/pci/hda/patch_cmedia.c b/sound/pci/hda/patch_cmedia.c new file mode 100644 index 000000000..2ddd33f8d --- /dev/null +++ b/sound/pci/hda/patch_cmedia.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Universal Interface for Intel High Definition Audio Codec + * + * HD audio interface patch for C-Media CMI9880 + * + * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/hda_codec.h> +#include "hda_local.h" +#include "hda_auto_parser.h" +#include "hda_jack.h" +#include "hda_generic.h" + +struct cmi_spec { + struct hda_gen_spec gen; +}; + +/* + * stuff for auto-parser + */ +static const struct hda_codec_ops cmi_auto_patch_ops = { + .build_controls = snd_hda_gen_build_controls, + .build_pcms = snd_hda_gen_build_pcms, + .init = snd_hda_gen_init, + .free = snd_hda_gen_free, + .unsol_event = snd_hda_jack_unsol_event, +}; + +static int patch_cmi9880(struct hda_codec *codec) +{ + struct cmi_spec *spec; + struct auto_pin_cfg *cfg; + int err; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + codec->patch_ops = cmi_auto_patch_ops; + cfg = &spec->gen.autocfg; + snd_hda_gen_spec_init(&spec->gen); + + err = snd_hda_parse_pin_defcfg(codec, cfg, NULL, 0); + if (err < 0) + goto error; + err = snd_hda_gen_parse_auto_config(codec, cfg); + if (err < 0) + goto error; + + return 0; + + error: + snd_hda_gen_free(codec); + return err; +} + +static int patch_cmi8888(struct hda_codec *codec) +{ + struct cmi_spec *spec; + struct auto_pin_cfg *cfg; + int err; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return -ENOMEM; + + codec->spec = spec; + codec->patch_ops = cmi_auto_patch_ops; + cfg = &spec->gen.autocfg; + snd_hda_gen_spec_init(&spec->gen); + + /* mask NID 0x10 from the playback volume selection; + * it's a headphone boost volume handled manually below + */ + spec->gen.out_vol_mask = (1ULL << 0x10); + + err = snd_hda_parse_pin_defcfg(codec, cfg, NULL, 0); + if (err < 0) + goto error; + err = snd_hda_gen_parse_auto_config(codec, cfg); + if (err < 0) + goto error; + + if (get_defcfg_device(snd_hda_codec_get_pincfg(codec, 0x10)) == + AC_JACK_HP_OUT) { + static const struct snd_kcontrol_new amp_kctl = + HDA_CODEC_VOLUME("Headphone Amp Playback Volume", + 0x10, 0, HDA_OUTPUT); + if (!snd_hda_gen_add_kctl(&spec->gen, NULL, &_kctl)) { + err = -ENOMEM; + goto error; + } + } + + return 0; + + error: + snd_hda_gen_free(codec); + return err; +} + +/* + * patch entries + */ +static const struct hda_device_id snd_hda_id_cmedia[] = { + HDA_CODEC_ENTRY(0x13f68888, "CMI8888", patch_cmi8888), + HDA_CODEC_ENTRY(0x13f69880, "CMI9880", patch_cmi9880), + HDA_CODEC_ENTRY(0x434d4980, "CMI9880", patch_cmi9880), + {} /* terminator */ +}; +MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_cmedia); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("C-Media HD-audio codec"); + +static struct hda_codec_driver cmedia_driver = { + .id = snd_hda_id_cmedia, +}; + +module_hda_codec_driver(cmedia_driver); diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c new file mode 100644 index 000000000..a889cccdd --- /dev/null +++ b/sound/pci/hda/patch_conexant.c @@ -0,0 +1,1172 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HD audio interface patch for Conexant HDA audio codec + * + * Copyright (c) 2006 Pototskiy Akex <alex.pototskiy@gmail.com> + * Takashi Iwai <tiwai@suse.de> + * Tobin Davis <tdavis@dsl-only.net> + */ + +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/jack.h> + +#include <sound/hda_codec.h> +#include "hda_local.h" +#include "hda_auto_parser.h" +#include "hda_beep.h" +#include "hda_jack.h" +#include "hda_generic.h" + +struct conexant_spec { + struct hda_gen_spec gen; + + /* extra EAPD pins */ + unsigned int num_eapds; + hda_nid_t eapds[4]; + bool dynamic_eapd; + hda_nid_t mute_led_eapd; + + unsigned int parse_flags; /* flag for snd_hda_parse_pin_defcfg() */ + + /* OPLC XO specific */ + bool recording; + bool dc_enable; + unsigned int dc_input_bias; /* offset into olpc_xo_dc_bias */ + struct nid_path *dc_mode_path; + + int mute_led_polarity; + unsigned int gpio_led; + unsigned int gpio_mute_led_mask; + unsigned int gpio_mic_led_mask; + +}; + + +#ifdef CONFIG_SND_HDA_INPUT_BEEP +/* additional beep mixers; private_value will be overwritten */ +static const struct snd_kcontrol_new cxt_beep_mixer[] = { + HDA_CODEC_VOLUME_MONO("Beep Playback Volume", 0, 1, 0, HDA_OUTPUT), + HDA_CODEC_MUTE_BEEP_MONO("Beep Playback Switch", 0, 1, 0, HDA_OUTPUT), +}; + +static int set_beep_amp(struct conexant_spec *spec, hda_nid_t nid, + int idx, int dir) +{ + struct snd_kcontrol_new *knew; + unsigned int beep_amp = HDA_COMPOSE_AMP_VAL(nid, 1, idx, dir); + int i; + + spec->gen.beep_nid = nid; + for (i = 0; i < ARRAY_SIZE(cxt_beep_mixer); i++) { + knew = snd_hda_gen_add_kctl(&spec->gen, NULL, + &cxt_beep_mixer[i]); + if (!knew) + return -ENOMEM; + knew->private_value = beep_amp; + } + return 0; +} + +static int cx_auto_parse_beep(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + hda_nid_t nid; + + for_each_hda_codec_node(nid, codec) + if (get_wcaps_type(get_wcaps(codec, nid)) == AC_WID_BEEP) + return set_beep_amp(spec, nid, 0, HDA_OUTPUT); + return 0; +} +#else +#define cx_auto_parse_beep(codec) 0 +#endif + +/* + * Automatic parser for CX20641 & co + */ + +/* parse EAPDs */ +static void cx_auto_parse_eapd(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + hda_nid_t nid; + + for_each_hda_codec_node(nid, codec) { + if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN) + continue; + if (!(snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_EAPD)) + continue; + spec->eapds[spec->num_eapds++] = nid; + if (spec->num_eapds >= ARRAY_SIZE(spec->eapds)) + break; + } + + /* NOTE: below is a wild guess; if we have more than two EAPDs, + * it's a new chip, where EAPDs are supposed to be associated to + * pins, and we can control EAPD per pin. + * OTOH, if only one or two EAPDs are found, it's an old chip, + * thus it might control over all pins. + */ + if (spec->num_eapds > 2) + spec->dynamic_eapd = 1; +} + +static void cx_auto_turn_eapd(struct hda_codec *codec, int num_pins, + const hda_nid_t *pins, bool on) +{ + int i; + for (i = 0; i < num_pins; i++) { + if (snd_hda_query_pin_caps(codec, pins[i]) & AC_PINCAP_EAPD) + snd_hda_codec_write(codec, pins[i], 0, + AC_VERB_SET_EAPD_BTLENABLE, + on ? 0x02 : 0); + } +} + +/* turn on/off EAPD according to Master switch */ +static void cx_auto_vmaster_hook(void *private_data, int enabled) +{ + struct hda_codec *codec = private_data; + struct conexant_spec *spec = codec->spec; + + cx_auto_turn_eapd(codec, spec->num_eapds, spec->eapds, enabled); +} + +/* turn on/off EAPD according to Master switch (inversely!) for mute LED */ +static int cx_auto_vmaster_mute_led(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent); + struct conexant_spec *spec = codec->spec; + + snd_hda_codec_write(codec, spec->mute_led_eapd, 0, + AC_VERB_SET_EAPD_BTLENABLE, + brightness ? 0x02 : 0x00); + return 0; +} + +static void cxt_init_gpio_led(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + unsigned int mask = spec->gpio_mute_led_mask | spec->gpio_mic_led_mask; + + if (mask) { + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK, + mask); + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION, + mask); + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, + spec->gpio_led); + } +} + +static int cx_auto_init(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + snd_hda_gen_init(codec); + if (!spec->dynamic_eapd) + cx_auto_turn_eapd(codec, spec->num_eapds, spec->eapds, true); + + cxt_init_gpio_led(codec); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_INIT); + + return 0; +} + +static void cx_auto_shutdown(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + + /* Turn the problematic codec into D3 to avoid spurious noises + from the internal speaker during (and after) reboot */ + cx_auto_turn_eapd(codec, spec->num_eapds, spec->eapds, false); +} + +static void cx_auto_free(struct hda_codec *codec) +{ + cx_auto_shutdown(codec); + snd_hda_gen_free(codec); +} + +#ifdef CONFIG_PM +static int cx_auto_suspend(struct hda_codec *codec) +{ + cx_auto_shutdown(codec); + return 0; +} +#endif + +static const struct hda_codec_ops cx_auto_patch_ops = { + .build_controls = snd_hda_gen_build_controls, + .build_pcms = snd_hda_gen_build_pcms, + .init = cx_auto_init, + .free = cx_auto_free, + .unsol_event = snd_hda_jack_unsol_event, +#ifdef CONFIG_PM + .suspend = cx_auto_suspend, + .check_power_status = snd_hda_gen_check_power_status, +#endif +}; + +/* + * pin fix-up + */ +enum { + CXT_PINCFG_LENOVO_X200, + CXT_PINCFG_LENOVO_TP410, + CXT_PINCFG_LEMOTE_A1004, + CXT_PINCFG_LEMOTE_A1205, + CXT_PINCFG_COMPAQ_CQ60, + CXT_FIXUP_STEREO_DMIC, + CXT_PINCFG_LENOVO_NOTEBOOK, + CXT_FIXUP_INC_MIC_BOOST, + CXT_FIXUP_HEADPHONE_MIC_PIN, + CXT_FIXUP_HEADPHONE_MIC, + CXT_FIXUP_GPIO1, + CXT_FIXUP_ASPIRE_DMIC, + CXT_FIXUP_THINKPAD_ACPI, + CXT_FIXUP_OLPC_XO, + CXT_FIXUP_CAP_MIX_AMP, + CXT_FIXUP_TOSHIBA_P105, + CXT_FIXUP_HP_530, + CXT_FIXUP_CAP_MIX_AMP_5047, + CXT_FIXUP_MUTE_LED_EAPD, + CXT_FIXUP_HP_DOCK, + CXT_FIXUP_HP_SPECTRE, + CXT_FIXUP_HP_GATE_MIC, + CXT_FIXUP_MUTE_LED_GPIO, + CXT_FIXUP_HP_ZBOOK_MUTE_LED, + CXT_FIXUP_HEADSET_MIC, + CXT_FIXUP_HP_MIC_NO_PRESENCE, +}; + +/* for hda_fixup_thinkpad_acpi() */ +#include "thinkpad_helper.c" + +static void cxt_fixup_stereo_dmic(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct conexant_spec *spec = codec->spec; + spec->gen.inv_dmic_split = 1; +} + +static void cxt5066_increase_mic_boost(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + snd_hda_override_amp_caps(codec, 0x17, HDA_OUTPUT, + (0x3 << AC_AMPCAP_OFFSET_SHIFT) | + (0x4 << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x27 << AC_AMPCAP_STEP_SIZE_SHIFT) | + (0 << AC_AMPCAP_MUTE_SHIFT)); +} + +static void cxt_update_headset_mode(struct hda_codec *codec) +{ + /* The verbs used in this function were tested on a Conexant CX20751/2 codec. */ + int i; + bool mic_mode = false; + struct conexant_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->gen.autocfg; + + hda_nid_t mux_pin = spec->gen.imux_pins[spec->gen.cur_mux[0]]; + + for (i = 0; i < cfg->num_inputs; i++) + if (cfg->inputs[i].pin == mux_pin) { + mic_mode = !!cfg->inputs[i].is_headphone_mic; + break; + } + + if (mic_mode) { + snd_hda_codec_write_cache(codec, 0x1c, 0, 0x410, 0x7c); /* enable merged mode for analog int-mic */ + spec->gen.hp_jack_present = false; + } else { + snd_hda_codec_write_cache(codec, 0x1c, 0, 0x410, 0x54); /* disable merged mode for analog int-mic */ + spec->gen.hp_jack_present = snd_hda_jack_detect(codec, spec->gen.autocfg.hp_pins[0]); + } + + snd_hda_gen_update_outputs(codec); +} + +static void cxt_update_headset_mode_hook(struct hda_codec *codec, + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + cxt_update_headset_mode(codec); +} + +static void cxt_fixup_headphone_mic(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct conexant_spec *spec = codec->spec; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + spec->parse_flags |= HDA_PINCFG_HEADPHONE_MIC; + snd_hdac_regmap_add_vendor_verb(&codec->core, 0x410); + break; + case HDA_FIXUP_ACT_PROBE: + WARN_ON(spec->gen.cap_sync_hook); + spec->gen.cap_sync_hook = cxt_update_headset_mode_hook; + spec->gen.automute_hook = cxt_update_headset_mode; + break; + case HDA_FIXUP_ACT_INIT: + cxt_update_headset_mode(codec); + break; + } +} + +static void cxt_fixup_headset_mic(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct conexant_spec *spec = codec->spec; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + spec->parse_flags |= HDA_PINCFG_HEADSET_MIC; + break; + } +} + +/* OPLC XO 1.5 fixup */ + +/* OLPC XO-1.5 supports DC input mode (e.g. for use with analog sensors) + * through the microphone jack. + * When the user enables this through a mixer switch, both internal and + * external microphones are disabled. Gain is fixed at 0dB. In this mode, + * we also allow the bias to be configured through a separate mixer + * control. */ + +#define update_mic_pin(codec, nid, val) \ + snd_hda_codec_write_cache(codec, nid, 0, \ + AC_VERB_SET_PIN_WIDGET_CONTROL, val) + +static const struct hda_input_mux olpc_xo_dc_bias = { + .num_items = 3, + .items = { + { "Off", PIN_IN }, + { "50%", PIN_VREF50 }, + { "80%", PIN_VREF80 }, + }, +}; + +static void olpc_xo_update_mic_boost(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + int ch, val; + + for (ch = 0; ch < 2; ch++) { + val = AC_AMP_SET_OUTPUT | + (ch ? AC_AMP_SET_RIGHT : AC_AMP_SET_LEFT); + if (!spec->dc_enable) + val |= snd_hda_codec_amp_read(codec, 0x17, ch, HDA_OUTPUT, 0); + snd_hda_codec_write(codec, 0x17, 0, + AC_VERB_SET_AMP_GAIN_MUTE, val); + } +} + +static void olpc_xo_update_mic_pins(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + int cur_input, val; + struct nid_path *path; + + cur_input = spec->gen.input_paths[0][spec->gen.cur_mux[0]]; + + /* Set up mic pins for port-B, C and F dynamically as the recording + * LED is turned on/off by these pin controls + */ + if (!spec->dc_enable) { + /* disable DC bias path and pin for port F */ + update_mic_pin(codec, 0x1e, 0); + snd_hda_activate_path(codec, spec->dc_mode_path, false, false); + + /* update port B (ext mic) and C (int mic) */ + /* OLPC defers mic widget control until when capture is + * started because the microphone LED comes on as soon as + * these settings are put in place. if we did this before + * recording, it would give the false indication that + * recording is happening when it is not. + */ + update_mic_pin(codec, 0x1a, spec->recording ? + snd_hda_codec_get_pin_target(codec, 0x1a) : 0); + update_mic_pin(codec, 0x1b, spec->recording ? + snd_hda_codec_get_pin_target(codec, 0x1b) : 0); + /* enable normal mic path */ + path = snd_hda_get_path_from_idx(codec, cur_input); + if (path) + snd_hda_activate_path(codec, path, true, false); + } else { + /* disable normal mic path */ + path = snd_hda_get_path_from_idx(codec, cur_input); + if (path) + snd_hda_activate_path(codec, path, false, false); + + /* Even though port F is the DC input, the bias is controlled + * on port B. We also leave that port as an active input (but + * unselected) in DC mode just in case that is necessary to + * make the bias setting take effect. + */ + if (spec->recording) + val = olpc_xo_dc_bias.items[spec->dc_input_bias].index; + else + val = 0; + update_mic_pin(codec, 0x1a, val); + update_mic_pin(codec, 0x1b, 0); + /* enable DC bias path and pin */ + update_mic_pin(codec, 0x1e, spec->recording ? PIN_IN : 0); + snd_hda_activate_path(codec, spec->dc_mode_path, true, false); + } +} + +/* mic_autoswitch hook */ +static void olpc_xo_automic(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + struct conexant_spec *spec = codec->spec; + + /* in DC mode, we don't handle automic */ + if (!spec->dc_enable) + snd_hda_gen_mic_autoswitch(codec, jack); + olpc_xo_update_mic_pins(codec); + if (spec->dc_enable) + olpc_xo_update_mic_boost(codec); +} + +/* pcm_capture hook */ +static void olpc_xo_capture_hook(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream, + int action) +{ + struct conexant_spec *spec = codec->spec; + + /* toggle spec->recording flag and update mic pins accordingly + * for turning on/off LED + */ + switch (action) { + case HDA_GEN_PCM_ACT_PREPARE: + spec->recording = 1; + olpc_xo_update_mic_pins(codec); + break; + case HDA_GEN_PCM_ACT_CLEANUP: + spec->recording = 0; + olpc_xo_update_mic_pins(codec); + break; + } +} + +static int olpc_xo_dc_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct conexant_spec *spec = codec->spec; + ucontrol->value.integer.value[0] = spec->dc_enable; + return 0; +} + +static int olpc_xo_dc_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct conexant_spec *spec = codec->spec; + int dc_enable = !!ucontrol->value.integer.value[0]; + + if (dc_enable == spec->dc_enable) + return 0; + + spec->dc_enable = dc_enable; + olpc_xo_update_mic_pins(codec); + olpc_xo_update_mic_boost(codec); + return 1; +} + +static int olpc_xo_dc_bias_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct conexant_spec *spec = codec->spec; + ucontrol->value.enumerated.item[0] = spec->dc_input_bias; + return 0; +} + +static int olpc_xo_dc_bias_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_hda_input_mux_info(&olpc_xo_dc_bias, uinfo); +} + +static int olpc_xo_dc_bias_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct conexant_spec *spec = codec->spec; + const struct hda_input_mux *imux = &olpc_xo_dc_bias; + unsigned int idx; + + idx = ucontrol->value.enumerated.item[0]; + if (idx >= imux->num_items) + idx = imux->num_items - 1; + if (spec->dc_input_bias == idx) + return 0; + + spec->dc_input_bias = idx; + if (spec->dc_enable) + olpc_xo_update_mic_pins(codec); + return 1; +} + +static const struct snd_kcontrol_new olpc_xo_mixers[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DC Mode Enable Switch", + .info = snd_ctl_boolean_mono_info, + .get = olpc_xo_dc_mode_get, + .put = olpc_xo_dc_mode_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DC Input Bias Enum", + .info = olpc_xo_dc_bias_enum_info, + .get = olpc_xo_dc_bias_enum_get, + .put = olpc_xo_dc_bias_enum_put, + }, + {} +}; + +/* overriding mic boost put callback; update mic boost volume only when + * DC mode is disabled + */ +static int olpc_xo_mic_boost_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct conexant_spec *spec = codec->spec; + int ret = snd_hda_mixer_amp_volume_put(kcontrol, ucontrol); + if (ret > 0 && spec->dc_enable) + olpc_xo_update_mic_boost(codec); + return ret; +} + +static void cxt_fixup_olpc_xo(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct conexant_spec *spec = codec->spec; + struct snd_kcontrol_new *kctl; + int i; + + if (action != HDA_FIXUP_ACT_PROBE) + return; + + spec->gen.mic_autoswitch_hook = olpc_xo_automic; + spec->gen.pcm_capture_hook = olpc_xo_capture_hook; + spec->dc_mode_path = snd_hda_add_new_path(codec, 0x1e, 0x14, 0); + + snd_hda_add_new_ctls(codec, olpc_xo_mixers); + + /* OLPC's microphone port is DC coupled for use with external sensors, + * therefore we use a 50% mic bias in order to center the input signal + * with the DC input range of the codec. + */ + snd_hda_codec_set_pin_target(codec, 0x1a, PIN_VREF50); + + /* override mic boost control */ + snd_array_for_each(&spec->gen.kctls, i, kctl) { + if (!strcmp(kctl->name, "Mic Boost Volume")) { + kctl->put = olpc_xo_mic_boost_put; + break; + } + } +} + +static void cxt_fixup_mute_led_eapd(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct conexant_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->mute_led_eapd = 0x1b; + spec->dynamic_eapd = true; + snd_hda_gen_add_mute_led_cdev(codec, cx_auto_vmaster_mute_led); + } +} + +/* + * Fix max input level on mixer widget to 0dB + * (originally it has 0x2b steps with 0dB offset 0x14) + */ +static void cxt_fixup_cap_mix_amp(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + snd_hda_override_amp_caps(codec, 0x17, HDA_INPUT, + (0x14 << AC_AMPCAP_OFFSET_SHIFT) | + (0x14 << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x05 << AC_AMPCAP_STEP_SIZE_SHIFT) | + (1 << AC_AMPCAP_MUTE_SHIFT)); +} + +/* + * Fix max input level on mixer widget to 0dB + * (originally it has 0x1e steps with 0 dB offset 0x17) + */ +static void cxt_fixup_cap_mix_amp_5047(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + snd_hda_override_amp_caps(codec, 0x10, HDA_INPUT, + (0x17 << AC_AMPCAP_OFFSET_SHIFT) | + (0x17 << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x05 << AC_AMPCAP_STEP_SIZE_SHIFT) | + (1 << AC_AMPCAP_MUTE_SHIFT)); +} + +static void cxt_fixup_hp_gate_mic_jack(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + /* the mic pin (0x19) doesn't give an unsolicited event; + * probe the mic pin together with the headphone pin (0x16) + */ + if (action == HDA_FIXUP_ACT_PROBE) + snd_hda_jack_set_gating_jack(codec, 0x19, 0x16); +} + +/* update LED status via GPIO */ +static void cxt_update_gpio_led(struct hda_codec *codec, unsigned int mask, + bool led_on) +{ + struct conexant_spec *spec = codec->spec; + unsigned int oldval = spec->gpio_led; + + if (spec->mute_led_polarity) + led_on = !led_on; + + if (led_on) + spec->gpio_led |= mask; + else + spec->gpio_led &= ~mask; + codec_dbg(codec, "mask:%d enabled:%d gpio_led:%d\n", + mask, led_on, spec->gpio_led); + if (spec->gpio_led != oldval) + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, + spec->gpio_led); +} + +/* turn on/off mute LED via GPIO per vmaster hook */ +static int cxt_gpio_mute_update(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent); + struct conexant_spec *spec = codec->spec; + + cxt_update_gpio_led(codec, spec->gpio_mute_led_mask, brightness); + return 0; +} + +/* turn on/off mic-mute LED via GPIO per capture hook */ +static int cxt_gpio_micmute_update(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent); + struct conexant_spec *spec = codec->spec; + + cxt_update_gpio_led(codec, spec->gpio_mic_led_mask, brightness); + return 0; +} + +static void cxt_setup_mute_led(struct hda_codec *codec, + unsigned int mute, unsigned int mic_mute) +{ + struct conexant_spec *spec = codec->spec; + + spec->gpio_led = 0; + spec->mute_led_polarity = 0; + if (mute) { + snd_hda_gen_add_mute_led_cdev(codec, cxt_gpio_mute_update); + spec->gpio_mute_led_mask = mute; + } + if (mic_mute) { + snd_hda_gen_add_micmute_led_cdev(codec, cxt_gpio_micmute_update); + spec->gpio_mic_led_mask = mic_mute; + } +} + +static void cxt_fixup_mute_led_gpio(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) + cxt_setup_mute_led(codec, 0x01, 0x02); +} + +static void cxt_fixup_hp_zbook_mute_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) + cxt_setup_mute_led(codec, 0x10, 0x20); +} + +/* ThinkPad X200 & co with cxt5051 */ +static const struct hda_pintbl cxt_pincfg_lenovo_x200[] = { + { 0x16, 0x042140ff }, /* HP (seq# overridden) */ + { 0x17, 0x21a11000 }, /* dock-mic */ + { 0x19, 0x2121103f }, /* dock-HP */ + { 0x1c, 0x21440100 }, /* dock SPDIF out */ + {} +}; + +/* ThinkPad 410/420/510/520, X201 & co with cxt5066 */ +static const struct hda_pintbl cxt_pincfg_lenovo_tp410[] = { + { 0x19, 0x042110ff }, /* HP (seq# overridden) */ + { 0x1a, 0x21a190f0 }, /* dock-mic */ + { 0x1c, 0x212140ff }, /* dock-HP */ + {} +}; + +/* Lemote A1004/A1205 with cxt5066 */ +static const struct hda_pintbl cxt_pincfg_lemote[] = { + { 0x1a, 0x90a10020 }, /* Internal mic */ + { 0x1b, 0x03a11020 }, /* External mic */ + { 0x1d, 0x400101f0 }, /* Not used */ + { 0x1e, 0x40a701f0 }, /* Not used */ + { 0x20, 0x404501f0 }, /* Not used */ + { 0x22, 0x404401f0 }, /* Not used */ + { 0x23, 0x40a701f0 }, /* Not used */ + {} +}; + +static const struct hda_fixup cxt_fixups[] = { + [CXT_PINCFG_LENOVO_X200] = { + .type = HDA_FIXUP_PINS, + .v.pins = cxt_pincfg_lenovo_x200, + }, + [CXT_PINCFG_LENOVO_TP410] = { + .type = HDA_FIXUP_PINS, + .v.pins = cxt_pincfg_lenovo_tp410, + .chained = true, + .chain_id = CXT_FIXUP_THINKPAD_ACPI, + }, + [CXT_PINCFG_LEMOTE_A1004] = { + .type = HDA_FIXUP_PINS, + .chained = true, + .chain_id = CXT_FIXUP_INC_MIC_BOOST, + .v.pins = cxt_pincfg_lemote, + }, + [CXT_PINCFG_LEMOTE_A1205] = { + .type = HDA_FIXUP_PINS, + .v.pins = cxt_pincfg_lemote, + }, + [CXT_PINCFG_COMPAQ_CQ60] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + /* 0x17 was falsely set up as a mic, it should 0x1d */ + { 0x17, 0x400001f0 }, + { 0x1d, 0x97a70120 }, + { } + } + }, + [CXT_FIXUP_STEREO_DMIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_stereo_dmic, + }, + [CXT_PINCFG_LENOVO_NOTEBOOK] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1a, 0x05d71030 }, + { } + }, + .chain_id = CXT_FIXUP_STEREO_DMIC, + }, + [CXT_FIXUP_INC_MIC_BOOST] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt5066_increase_mic_boost, + }, + [CXT_FIXUP_HEADPHONE_MIC_PIN] = { + .type = HDA_FIXUP_PINS, + .chained = true, + .chain_id = CXT_FIXUP_HEADPHONE_MIC, + .v.pins = (const struct hda_pintbl[]) { + { 0x18, 0x03a1913d }, /* use as headphone mic, without its own jack detect */ + { } + } + }, + [CXT_FIXUP_HEADPHONE_MIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_headphone_mic, + }, + [CXT_FIXUP_GPIO1] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x01, AC_VERB_SET_GPIO_MASK, 0x01 }, + { 0x01, AC_VERB_SET_GPIO_DIRECTION, 0x01 }, + { 0x01, AC_VERB_SET_GPIO_DATA, 0x01 }, + { } + }, + }, + [CXT_FIXUP_ASPIRE_DMIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_stereo_dmic, + .chained = true, + .chain_id = CXT_FIXUP_GPIO1, + }, + [CXT_FIXUP_THINKPAD_ACPI] = { + .type = HDA_FIXUP_FUNC, + .v.func = hda_fixup_thinkpad_acpi, + }, + [CXT_FIXUP_OLPC_XO] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_olpc_xo, + }, + [CXT_FIXUP_CAP_MIX_AMP] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_cap_mix_amp, + }, + [CXT_FIXUP_TOSHIBA_P105] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x10, 0x961701f0 }, /* speaker/hp */ + { 0x12, 0x02a1901e }, /* ext mic */ + { 0x14, 0x95a70110 }, /* int mic */ + {} + }, + }, + [CXT_FIXUP_HP_530] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x12, 0x90a60160 }, /* int mic */ + {} + }, + .chained = true, + .chain_id = CXT_FIXUP_CAP_MIX_AMP, + }, + [CXT_FIXUP_CAP_MIX_AMP_5047] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_cap_mix_amp_5047, + }, + [CXT_FIXUP_MUTE_LED_EAPD] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_mute_led_eapd, + }, + [CXT_FIXUP_HP_DOCK] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x16, 0x21011020 }, /* line-out */ + { 0x18, 0x2181103f }, /* line-in */ + { } + }, + .chained = true, + .chain_id = CXT_FIXUP_MUTE_LED_GPIO, + }, + [CXT_FIXUP_HP_SPECTRE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + /* enable NID 0x1d for the speaker on top */ + { 0x1d, 0x91170111 }, + { } + } + }, + [CXT_FIXUP_HP_GATE_MIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_hp_gate_mic_jack, + }, + [CXT_FIXUP_MUTE_LED_GPIO] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_mute_led_gpio, + }, + [CXT_FIXUP_HP_ZBOOK_MUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_hp_zbook_mute_led, + }, + [CXT_FIXUP_HEADSET_MIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_headset_mic, + }, + [CXT_FIXUP_HP_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1a, 0x02a1113c }, + { } + }, + .chained = true, + .chain_id = CXT_FIXUP_HEADSET_MIC, + }, +}; + +static const struct snd_pci_quirk cxt5045_fixups[] = { + SND_PCI_QUIRK(0x103c, 0x30d5, "HP 530", CXT_FIXUP_HP_530), + SND_PCI_QUIRK(0x1179, 0xff31, "Toshiba P105", CXT_FIXUP_TOSHIBA_P105), + /* HP, Packard Bell, Fujitsu-Siemens & Lenovo laptops have + * really bad sound over 0dB on NID 0x17. + */ + SND_PCI_QUIRK_VENDOR(0x103c, "HP", CXT_FIXUP_CAP_MIX_AMP), + SND_PCI_QUIRK_VENDOR(0x1631, "Packard Bell", CXT_FIXUP_CAP_MIX_AMP), + SND_PCI_QUIRK_VENDOR(0x1734, "Fujitsu", CXT_FIXUP_CAP_MIX_AMP), + SND_PCI_QUIRK_VENDOR(0x17aa, "Lenovo", CXT_FIXUP_CAP_MIX_AMP), + {} +}; + +static const struct hda_model_fixup cxt5045_fixup_models[] = { + { .id = CXT_FIXUP_CAP_MIX_AMP, .name = "cap-mix-amp" }, + { .id = CXT_FIXUP_TOSHIBA_P105, .name = "toshiba-p105" }, + { .id = CXT_FIXUP_HP_530, .name = "hp-530" }, + {} +}; + +static const struct snd_pci_quirk cxt5047_fixups[] = { + /* HP laptops have really bad sound over 0 dB on NID 0x10. + */ + SND_PCI_QUIRK_VENDOR(0x103c, "HP", CXT_FIXUP_CAP_MIX_AMP_5047), + {} +}; + +static const struct hda_model_fixup cxt5047_fixup_models[] = { + { .id = CXT_FIXUP_CAP_MIX_AMP_5047, .name = "cap-mix-amp" }, + {} +}; + +static const struct snd_pci_quirk cxt5051_fixups[] = { + SND_PCI_QUIRK(0x103c, 0x360b, "Compaq CQ60", CXT_PINCFG_COMPAQ_CQ60), + SND_PCI_QUIRK(0x17aa, 0x20f2, "Lenovo X200", CXT_PINCFG_LENOVO_X200), + {} +}; + +static const struct hda_model_fixup cxt5051_fixup_models[] = { + { .id = CXT_PINCFG_LENOVO_X200, .name = "lenovo-x200" }, + {} +}; + +static const struct snd_pci_quirk cxt5066_fixups[] = { + SND_PCI_QUIRK(0x1025, 0x0543, "Acer Aspire One 522", CXT_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK(0x1025, 0x054c, "Acer Aspire 3830TG", CXT_FIXUP_ASPIRE_DMIC), + SND_PCI_QUIRK(0x1025, 0x054f, "Acer Aspire 4830T", CXT_FIXUP_ASPIRE_DMIC), + SND_PCI_QUIRK(0x103c, 0x8079, "HP EliteBook 840 G3", CXT_FIXUP_HP_DOCK), + SND_PCI_QUIRK(0x103c, 0x807C, "HP EliteBook 820 G3", CXT_FIXUP_HP_DOCK), + SND_PCI_QUIRK(0x103c, 0x80FD, "HP ProBook 640 G2", CXT_FIXUP_HP_DOCK), + SND_PCI_QUIRK(0x103c, 0x8115, "HP Z1 Gen3", CXT_FIXUP_HP_GATE_MIC), + SND_PCI_QUIRK(0x103c, 0x814f, "HP ZBook 15u G3", CXT_FIXUP_MUTE_LED_GPIO), + SND_PCI_QUIRK(0x103c, 0x8174, "HP Spectre x360", CXT_FIXUP_HP_SPECTRE), + SND_PCI_QUIRK(0x103c, 0x822e, "HP ProBook 440 G4", CXT_FIXUP_MUTE_LED_GPIO), + SND_PCI_QUIRK(0x103c, 0x828c, "HP EliteBook 840 G4", CXT_FIXUP_HP_DOCK), + SND_PCI_QUIRK(0x103c, 0x8299, "HP 800 G3 SFF", CXT_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x829a, "HP 800 G3 DM", CXT_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x82b4, "HP ProDesk 600 G3", CXT_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x836e, "HP ProBook 455 G5", CXT_FIXUP_MUTE_LED_GPIO), + SND_PCI_QUIRK(0x103c, 0x837f, "HP ProBook 470 G5", CXT_FIXUP_MUTE_LED_GPIO), + SND_PCI_QUIRK(0x103c, 0x83b2, "HP EliteBook 840 G5", CXT_FIXUP_HP_DOCK), + SND_PCI_QUIRK(0x103c, 0x83b3, "HP EliteBook 830 G5", CXT_FIXUP_HP_DOCK), + SND_PCI_QUIRK(0x103c, 0x83d3, "HP ProBook 640 G4", CXT_FIXUP_HP_DOCK), + SND_PCI_QUIRK(0x103c, 0x8402, "HP ProBook 645 G4", CXT_FIXUP_MUTE_LED_GPIO), + SND_PCI_QUIRK(0x103c, 0x8427, "HP ZBook Studio G5", CXT_FIXUP_HP_ZBOOK_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x844f, "HP ZBook Studio G5", CXT_FIXUP_HP_ZBOOK_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8455, "HP Z2 G4", CXT_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x8456, "HP Z2 G4 SFF", CXT_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x8457, "HP Z2 G4 mini", CXT_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x8458, "HP Z2 G4 mini premium", CXT_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x138d, "Asus", CXT_FIXUP_HEADPHONE_MIC_PIN), + SND_PCI_QUIRK(0x152d, 0x0833, "OLPC XO-1.5", CXT_FIXUP_OLPC_XO), + SND_PCI_QUIRK(0x17aa, 0x20f2, "Lenovo T400", CXT_PINCFG_LENOVO_TP410), + SND_PCI_QUIRK(0x17aa, 0x215e, "Lenovo T410", CXT_PINCFG_LENOVO_TP410), + SND_PCI_QUIRK(0x17aa, 0x215f, "Lenovo T510", CXT_PINCFG_LENOVO_TP410), + SND_PCI_QUIRK(0x17aa, 0x21ce, "Lenovo T420", CXT_PINCFG_LENOVO_TP410), + SND_PCI_QUIRK(0x17aa, 0x21cf, "Lenovo T520", CXT_PINCFG_LENOVO_TP410), + SND_PCI_QUIRK(0x17aa, 0x21d2, "Lenovo T420s", CXT_PINCFG_LENOVO_TP410), + SND_PCI_QUIRK(0x17aa, 0x21da, "Lenovo X220", CXT_PINCFG_LENOVO_TP410), + SND_PCI_QUIRK(0x17aa, 0x21db, "Lenovo X220-tablet", CXT_PINCFG_LENOVO_TP410), + SND_PCI_QUIRK(0x17aa, 0x38af, "Lenovo IdeaPad Z560", CXT_FIXUP_MUTE_LED_EAPD), + SND_PCI_QUIRK(0x17aa, 0x3905, "Lenovo G50-30", CXT_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK(0x17aa, 0x390b, "Lenovo G50-80", CXT_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK(0x17aa, 0x3975, "Lenovo U300s", CXT_FIXUP_STEREO_DMIC), + /* NOTE: we'd need to extend the quirk for 17aa:3977 as the same + * PCI SSID is used on multiple Lenovo models + */ + SND_PCI_QUIRK(0x17aa, 0x3977, "Lenovo IdeaPad U310", CXT_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK(0x17aa, 0x3978, "Lenovo G50-70", CXT_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK(0x17aa, 0x397b, "Lenovo S205", CXT_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad", CXT_FIXUP_THINKPAD_ACPI), + SND_PCI_QUIRK(0x1c06, 0x2011, "Lemote A1004", CXT_PINCFG_LEMOTE_A1004), + SND_PCI_QUIRK(0x1c06, 0x2012, "Lemote A1205", CXT_PINCFG_LEMOTE_A1205), + {} +}; + +static const struct hda_model_fixup cxt5066_fixup_models[] = { + { .id = CXT_FIXUP_STEREO_DMIC, .name = "stereo-dmic" }, + { .id = CXT_FIXUP_GPIO1, .name = "gpio1" }, + { .id = CXT_FIXUP_HEADPHONE_MIC_PIN, .name = "headphone-mic-pin" }, + { .id = CXT_PINCFG_LENOVO_TP410, .name = "tp410" }, + { .id = CXT_FIXUP_THINKPAD_ACPI, .name = "thinkpad" }, + { .id = CXT_PINCFG_LEMOTE_A1004, .name = "lemote-a1004" }, + { .id = CXT_PINCFG_LEMOTE_A1205, .name = "lemote-a1205" }, + { .id = CXT_FIXUP_OLPC_XO, .name = "olpc-xo" }, + { .id = CXT_FIXUP_MUTE_LED_EAPD, .name = "mute-led-eapd" }, + { .id = CXT_FIXUP_HP_DOCK, .name = "hp-dock" }, + { .id = CXT_FIXUP_MUTE_LED_GPIO, .name = "mute-led-gpio" }, + { .id = CXT_FIXUP_HP_ZBOOK_MUTE_LED, .name = "hp-zbook-mute-led" }, + { .id = CXT_FIXUP_HP_MIC_NO_PRESENCE, .name = "hp-mic-fix" }, + { .id = CXT_PINCFG_LENOVO_NOTEBOOK, .name = "lenovo-20149" }, + {} +}; + +/* add "fake" mute amp-caps to DACs on cx5051 so that mixer mute switches + * can be created (bko#42825) + */ +static void add_cx5051_fake_mutes(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + static const hda_nid_t out_nids[] = { + 0x10, 0x11, 0 + }; + const hda_nid_t *p; + + for (p = out_nids; *p; p++) + snd_hda_override_amp_caps(codec, *p, HDA_OUTPUT, + AC_AMPCAP_MIN_MUTE | + query_amp_caps(codec, *p, HDA_OUTPUT)); + spec->gen.dac_min_mute = true; +} + +static int patch_conexant_auto(struct hda_codec *codec) +{ + struct conexant_spec *spec; + int err; + + codec_info(codec, "%s: BIOS auto-probing.\n", codec->core.chip_name); + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return -ENOMEM; + snd_hda_gen_spec_init(&spec->gen); + codec->spec = spec; + codec->patch_ops = cx_auto_patch_ops; + + cx_auto_parse_eapd(codec); + spec->gen.own_eapd_ctl = 1; + + switch (codec->core.vendor_id) { + case 0x14f15045: + codec->single_adc_amp = 1; + spec->gen.mixer_nid = 0x17; + spec->gen.add_stereo_mix_input = HDA_HINT_STEREO_MIX_AUTO; + snd_hda_pick_fixup(codec, cxt5045_fixup_models, + cxt5045_fixups, cxt_fixups); + break; + case 0x14f15047: + codec->pin_amp_workaround = 1; + spec->gen.mixer_nid = 0x19; + spec->gen.add_stereo_mix_input = HDA_HINT_STEREO_MIX_AUTO; + snd_hda_pick_fixup(codec, cxt5047_fixup_models, + cxt5047_fixups, cxt_fixups); + break; + case 0x14f15051: + add_cx5051_fake_mutes(codec); + codec->pin_amp_workaround = 1; + snd_hda_pick_fixup(codec, cxt5051_fixup_models, + cxt5051_fixups, cxt_fixups); + break; + case 0x14f15098: + codec->pin_amp_workaround = 1; + spec->gen.mixer_nid = 0x22; + spec->gen.add_stereo_mix_input = HDA_HINT_STEREO_MIX_AUTO; + snd_hda_pick_fixup(codec, cxt5066_fixup_models, + cxt5066_fixups, cxt_fixups); + break; + case 0x14f150f2: + codec->power_save_node = 1; + fallthrough; + default: + codec->pin_amp_workaround = 1; + snd_hda_pick_fixup(codec, cxt5066_fixup_models, + cxt5066_fixups, cxt_fixups); + break; + } + + if (!spec->gen.vmaster_mute.hook && spec->dynamic_eapd) + spec->gen.vmaster_mute.hook = cx_auto_vmaster_hook; + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, NULL, + spec->parse_flags); + if (err < 0) + goto error; + + err = cx_auto_parse_beep(codec); + if (err < 0) + goto error; + + err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg); + if (err < 0) + goto error; + + /* Some laptops with Conexant chips show stalls in S3 resume, + * which falls into the single-cmd mode. + * Better to make reset, then. + */ + if (!codec->bus->core.sync_write) { + codec_info(codec, + "Enable sync_write for stable communication\n"); + codec->bus->core.sync_write = 1; + codec->bus->allow_bus_reset = 1; + } + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; + + error: + cx_auto_free(codec); + return err; +} + +/* + */ + +static const struct hda_device_id snd_hda_id_conexant[] = { + HDA_CODEC_ENTRY(0x14f11f86, "CX8070", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f11f87, "SN6140", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f12008, "CX8200", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f120d0, "CX11970", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f120d1, "SN6180", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f15045, "CX20549 (Venice)", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f15047, "CX20551 (Waikiki)", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f15051, "CX20561 (Hermosa)", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f15066, "CX20582 (Pebble)", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f15067, "CX20583 (Pebble HSF)", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f15068, "CX20584", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f15069, "CX20585", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f1506c, "CX20588", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f1506e, "CX20590", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f15097, "CX20631", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f15098, "CX20632", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f150a1, "CX20641", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f150a2, "CX20642", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f150ab, "CX20651", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f150ac, "CX20652", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f150b8, "CX20664", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f150b9, "CX20665", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f150f1, "CX21722", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f150f2, "CX20722", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f150f3, "CX21724", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f150f4, "CX20724", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f1510f, "CX20751/2", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f15110, "CX20751/2", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f15111, "CX20753/4", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f15113, "CX20755", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f15114, "CX20756", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f15115, "CX20757", patch_conexant_auto), + HDA_CODEC_ENTRY(0x14f151d7, "CX20952", patch_conexant_auto), + {} /* terminator */ +}; +MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_conexant); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Conexant HD-audio codec"); + +static struct hda_codec_driver conexant_driver = { + .id = snd_hda_id_conexant, +}; + +module_hda_codec_driver(conexant_driver); diff --git a/sound/pci/hda/patch_cs8409-tables.c b/sound/pci/hda/patch_cs8409-tables.c new file mode 100644 index 000000000..b288874e4 --- /dev/null +++ b/sound/pci/hda/patch_cs8409-tables.c @@ -0,0 +1,619 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * patch_cs8409-tables.c -- HD audio interface patch for Cirrus Logic CS8409 HDA bridge chip + * + * Copyright (C) 2021 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + * + * Author: Lucas Tanure <tanureal@opensource.cirrus.com> + */ + +#include "patch_cs8409.h" + +/****************************************************************************** + * CS42L42 Specific Data + * + ******************************************************************************/ + +static const DECLARE_TLV_DB_SCALE(cs42l42_dac_db_scale, CS42L42_HP_VOL_REAL_MIN * 100, 100, 1); + +static const DECLARE_TLV_DB_SCALE(cs42l42_adc_db_scale, CS42L42_AMIC_VOL_REAL_MIN * 100, 100, 1); + +const struct snd_kcontrol_new cs42l42_dac_volume_mixer = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .index = 0, + .subdevice = (HDA_SUBDEV_AMP_FLAG | HDA_SUBDEV_NID_FLAG), + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .info = cs42l42_volume_info, + .get = cs42l42_volume_get, + .put = cs42l42_volume_put, + .tlv = { .p = cs42l42_dac_db_scale }, + .private_value = HDA_COMPOSE_AMP_VAL_OFS(CS8409_PIN_ASP1_TRANSMITTER_A, 3, CS8409_CODEC0, + HDA_OUTPUT, CS42L42_VOL_DAC) | HDA_AMP_VAL_MIN_MUTE +}; + +const struct snd_kcontrol_new cs42l42_adc_volume_mixer = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .index = 0, + .subdevice = (HDA_SUBDEV_AMP_FLAG | HDA_SUBDEV_NID_FLAG), + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .info = cs42l42_volume_info, + .get = cs42l42_volume_get, + .put = cs42l42_volume_put, + .tlv = { .p = cs42l42_adc_db_scale }, + .private_value = HDA_COMPOSE_AMP_VAL_OFS(CS8409_PIN_ASP1_RECEIVER_A, 1, CS8409_CODEC0, + HDA_INPUT, CS42L42_VOL_ADC) | HDA_AMP_VAL_MIN_MUTE +}; + +const struct hda_pcm_stream cs42l42_48k_pcm_analog_playback = { + .rates = SNDRV_PCM_RATE_48000, /* fixed rate */ +}; + +const struct hda_pcm_stream cs42l42_48k_pcm_analog_capture = { + .rates = SNDRV_PCM_RATE_48000, /* fixed rate */ +}; + +/****************************************************************************** + * BULLSEYE / WARLOCK / CYBORG Specific Arrays + * CS8409/CS42L42 + ******************************************************************************/ + +const struct hda_verb cs8409_cs42l42_init_verbs[] = { + { CS8409_PIN_AFG, AC_VERB_SET_GPIO_WAKE_MASK, 0x0018 }, /* WAKE from GPIO 3,4 */ + { CS8409_PIN_VENDOR_WIDGET, AC_VERB_SET_PROC_STATE, 0x0001 }, /* Enable VPW processing */ + { CS8409_PIN_VENDOR_WIDGET, AC_VERB_SET_COEF_INDEX, 0x0002 }, /* Configure GPIO 6,7 */ + { CS8409_PIN_VENDOR_WIDGET, AC_VERB_SET_PROC_COEF, 0x0080 }, /* I2C mode */ + { CS8409_PIN_VENDOR_WIDGET, AC_VERB_SET_COEF_INDEX, 0x005b }, /* Set I2C bus speed */ + { CS8409_PIN_VENDOR_WIDGET, AC_VERB_SET_PROC_COEF, 0x0200 }, /* 100kHz I2C_STO = 2 */ + {} /* terminator */ +}; + +static const struct hda_pintbl cs8409_cs42l42_pincfgs[] = { + { CS8409_PIN_ASP1_TRANSMITTER_A, 0x042120f0 }, /* ASP-1-TX */ + { CS8409_PIN_ASP1_RECEIVER_A, 0x04a12050 }, /* ASP-1-RX */ + { CS8409_PIN_ASP2_TRANSMITTER_A, 0x901000f0 }, /* ASP-2-TX */ + { CS8409_PIN_DMIC1_IN, 0x90a00090 }, /* DMIC-1 */ + {} /* terminator */ +}; + +static const struct hda_pintbl cs8409_cs42l42_pincfgs_no_dmic[] = { + { CS8409_PIN_ASP1_TRANSMITTER_A, 0x042120f0 }, /* ASP-1-TX */ + { CS8409_PIN_ASP1_RECEIVER_A, 0x04a12050 }, /* ASP-1-RX */ + { CS8409_PIN_ASP2_TRANSMITTER_A, 0x901000f0 }, /* ASP-2-TX */ + {} /* terminator */ +}; + +/* Vendor specific HW configuration for CS42L42 */ +static const struct cs8409_i2c_param cs42l42_init_reg_seq[] = { + { CS42L42_I2C_TIMEOUT, 0xB0 }, + { CS42L42_ADC_CTL, 0x00 }, + { 0x1D02, 0x06 }, + { CS42L42_ADC_VOLUME, 0x9F }, + { CS42L42_OSC_SWITCH, 0x01 }, + { CS42L42_MCLK_CTL, 0x02 }, + { CS42L42_SRC_CTL, 0x03 }, + { CS42L42_MCLK_SRC_SEL, 0x00 }, + { CS42L42_ASP_FRM_CFG, 0x13 }, + { CS42L42_FSYNC_P_LOWER, 0xFF }, + { CS42L42_FSYNC_P_UPPER, 0x00 }, + { CS42L42_ASP_CLK_CFG, 0x20 }, + { CS42L42_SPDIF_CLK_CFG, 0x0D }, + { CS42L42_ASP_RX_DAI0_CH1_AP_RES, 0x02 }, + { CS42L42_ASP_RX_DAI0_CH1_BIT_MSB, 0x00 }, + { CS42L42_ASP_RX_DAI0_CH1_BIT_LSB, 0x00 }, + { CS42L42_ASP_RX_DAI0_CH2_AP_RES, 0x02 }, + { CS42L42_ASP_RX_DAI0_CH2_BIT_MSB, 0x00 }, + { CS42L42_ASP_RX_DAI0_CH2_BIT_LSB, 0x20 }, + { CS42L42_ASP_RX_DAI0_CH3_AP_RES, 0x02 }, + { CS42L42_ASP_RX_DAI0_CH3_BIT_MSB, 0x00 }, + { CS42L42_ASP_RX_DAI0_CH3_BIT_LSB, 0x80 }, + { CS42L42_ASP_RX_DAI0_CH4_AP_RES, 0x02 }, + { CS42L42_ASP_RX_DAI0_CH4_BIT_MSB, 0x00 }, + { CS42L42_ASP_RX_DAI0_CH4_BIT_LSB, 0xA0 }, + { CS42L42_ASP_RX_DAI0_EN, 0x0C }, + { CS42L42_ASP_TX_CH_EN, 0x01 }, + { CS42L42_ASP_TX_CH_AP_RES, 0x02 }, + { CS42L42_ASP_TX_CH1_BIT_MSB, 0x00 }, + { CS42L42_ASP_TX_CH1_BIT_LSB, 0x00 }, + { CS42L42_ASP_TX_SZ_EN, 0x01 }, + { CS42L42_PWR_CTL1, 0x0A }, + { CS42L42_PWR_CTL2, 0x84 }, + { CS42L42_MIXER_CHA_VOL, 0x3F }, + { CS42L42_MIXER_CHB_VOL, 0x3F }, + { CS42L42_MIXER_ADC_VOL, 0x3f }, + { CS42L42_HP_CTL, 0x03 }, + { CS42L42_MIC_DET_CTL1, 0xB6 }, + { CS42L42_TIPSENSE_CTL, 0xC2 }, + { CS42L42_HS_CLAMP_DISABLE, 0x01 }, + { CS42L42_HS_SWITCH_CTL, 0xF3 }, + { CS42L42_PWR_CTL3, 0x20 }, + { CS42L42_RSENSE_CTL2, 0x00 }, + { CS42L42_RSENSE_CTL3, 0x00 }, + { CS42L42_TSENSE_CTL, 0x80 }, + { CS42L42_HS_BIAS_CTL, 0xC0 }, + { CS42L42_PWR_CTL1, 0x02 }, + { CS42L42_ADC_OVFL_INT_MASK, 0xff }, + { CS42L42_MIXER_INT_MASK, 0xff }, + { CS42L42_SRC_INT_MASK, 0xff }, + { CS42L42_ASP_RX_INT_MASK, 0xff }, + { CS42L42_ASP_TX_INT_MASK, 0xff }, + { CS42L42_CODEC_INT_MASK, 0xff }, + { CS42L42_SRCPL_INT_MASK, 0xff }, + { CS42L42_VPMON_INT_MASK, 0xff }, + { CS42L42_PLL_LOCK_INT_MASK, 0xff }, + { CS42L42_TSRS_PLUG_INT_MASK, 0xff }, + { CS42L42_DET_INT1_MASK, 0xff }, + { CS42L42_DET_INT2_MASK, 0xff }, +}; + +/* Vendor specific hw configuration for CS8409 */ +const struct cs8409_cir_param cs8409_cs42l42_hw_cfg[] = { + /* +PLL1/2_EN, +I2C_EN */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_DEV_CFG1, 0xb008 }, + /* ASP1/2_EN=0, ASP1_STP=1 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_DEV_CFG2, 0x0002 }, + /* ASP1/2_BUS_IDLE=10, +GPIO_I2C */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_DEV_CFG3, 0x0a80 }, + /* ASP1.A: TX.LAP=0, TX.LSZ=24 bits, TX.LCS=0 */ + { CS8409_PIN_VENDOR_WIDGET, ASP1_A_TX_CTRL1, 0x0800 }, + /* ASP1.A: TX.RAP=0, TX.RSZ=24 bits, TX.RCS=32 */ + { CS8409_PIN_VENDOR_WIDGET, ASP1_A_TX_CTRL2, 0x0820 }, + /* ASP2.A: TX.LAP=0, TX.LSZ=24 bits, TX.LCS=0 */ + { CS8409_PIN_VENDOR_WIDGET, ASP2_A_TX_CTRL1, 0x0800 }, + /* ASP2.A: TX.RAP=1, TX.RSZ=24 bits, TX.RCS=0 */ + { CS8409_PIN_VENDOR_WIDGET, ASP2_A_TX_CTRL2, 0x2800 }, + /* ASP1.A: RX.LAP=0, RX.LSZ=24 bits, RX.LCS=0 */ + { CS8409_PIN_VENDOR_WIDGET, ASP1_A_RX_CTRL1, 0x0800 }, + /* ASP1.A: RX.RAP=0, RX.RSZ=24 bits, RX.RCS=0 */ + { CS8409_PIN_VENDOR_WIDGET, ASP1_A_RX_CTRL2, 0x0800 }, + /* ASP1: LCHI = 00h */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_ASP1_CLK_CTRL1, 0x8000 }, + /* ASP1: MC/SC_SRCSEL=PLL1, LCPR=FFh */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_ASP1_CLK_CTRL2, 0x28ff }, + /* ASP1: MCEN=0, FSD=011, SCPOL_IN/OUT=0, SCDIV=1:4 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_ASP1_CLK_CTRL3, 0x0062 }, + /* ASP2: LCHI=1Fh */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_ASP2_CLK_CTRL1, 0x801f }, + /* ASP2: MC/SC_SRCSEL=PLL1, LCPR=3Fh */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_ASP2_CLK_CTRL2, 0x283f }, + /* ASP2: 5050=1, MCEN=0, FSD=010, SCPOL_IN/OUT=1, SCDIV=1:16 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_ASP2_CLK_CTRL3, 0x805c }, + /* DMIC1_MO=10b, DMIC1/2_SR=1 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_DMIC_CFG, 0x0023 }, + /* ASP1/2_BEEP=0 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_BEEP_CFG, 0x0000 }, + /* ASP1/2_EN=1, ASP1_STP=1 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_DEV_CFG2, 0x0062 }, + /* -PLL2_EN */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_DEV_CFG1, 0x9008 }, + /* TX2.A: pre-scale att.=0 dB */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PRE_SCALE_ATTN2, 0x0000 }, + /* ASP1/2_xxx_EN=1, ASP1/2_MCLK_EN=0, DMIC1_SCL_EN=1 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PAD_CFG_SLW_RATE_CTRL, 0xfc03 }, + /* test mode on */ + { CS8409_PIN_VENDOR_WIDGET, 0xc0, 0x9999 }, + /* GPIO hysteresis = 30 us */ + { CS8409_PIN_VENDOR_WIDGET, 0xc5, 0x0000 }, + /* test mode off */ + { CS8409_PIN_VENDOR_WIDGET, 0xc0, 0x0000 }, + {} /* Terminator */ +}; + +const struct cs8409_cir_param cs8409_cs42l42_bullseye_atn[] = { + /* EQ_SEL=1, EQ1/2_EN=0 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_CTRL1, 0x4000 }, + /* +EQ_ACC */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0x4000 }, + /* +EQ2_EN */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_CTRL1, 0x4010 }, + /* EQ_DATA_HI=0x0647 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W1, 0x0647 }, + /* +EQ_WRT, +EQ_ACC, EQ_ADR=0, EQ_DATA_LO=0x67 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0xc0c7 }, + /* EQ_DATA_HI=0x0647 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W1, 0x0647 }, + /* +EQ_WRT, +EQ_ACC, EQ_ADR=1, EQ_DATA_LO=0x67 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0xc1c7 }, + /* EQ_DATA_HI=0xf370 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W1, 0xf370 }, + /* +EQ_WRT, +EQ_ACC, EQ_ADR=2, EQ_DATA_LO=0x71 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0xc271 }, + /* EQ_DATA_HI=0x1ef8 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W1, 0x1ef8 }, + /* +EQ_WRT, +EQ_ACC, EQ_ADR=3, EQ_DATA_LO=0x48 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0xc348 }, + /* EQ_DATA_HI=0xc110 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W1, 0xc110 }, + /* +EQ_WRT, +EQ_ACC, EQ_ADR=4, EQ_DATA_LO=0x5a */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0xc45a }, + /* EQ_DATA_HI=0x1f29 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W1, 0x1f29 }, + /* +EQ_WRT, +EQ_ACC, EQ_ADR=5, EQ_DATA_LO=0x74 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0xc574 }, + /* EQ_DATA_HI=0x1d7a */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W1, 0x1d7a }, + /* +EQ_WRT, +EQ_ACC, EQ_ADR=6, EQ_DATA_LO=0x53 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0xc653 }, + /* EQ_DATA_HI=0xc38c */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W1, 0xc38c }, + /* +EQ_WRT, +EQ_ACC, EQ_ADR=7, EQ_DATA_LO=0x14 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0xc714 }, + /* EQ_DATA_HI=0x1ca3 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W1, 0x1ca3 }, + /* +EQ_WRT, +EQ_ACC, EQ_ADR=8, EQ_DATA_LO=0xc7 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0xc8c7 }, + /* EQ_DATA_HI=0xc38c */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W1, 0xc38c }, + /* +EQ_WRT, +EQ_ACC, EQ_ADR=9, EQ_DATA_LO=0x14 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0xc914 }, + /* -EQ_ACC, -EQ_WRT */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0x0000 }, + {} /* Terminator */ +}; + +struct sub_codec cs8409_cs42l42_codec = { + .addr = CS42L42_I2C_ADDR, + .reset_gpio = CS8409_CS42L42_RESET, + .irq_mask = CS8409_CS42L42_INT, + .init_seq = cs42l42_init_reg_seq, + .init_seq_num = ARRAY_SIZE(cs42l42_init_reg_seq), + .hp_jack_in = 0, + .mic_jack_in = 0, + .paged = 1, + .suspended = 1, + .no_type_dect = 0, +}; + +/****************************************************************************** + * Dolphin Specific Arrays + * CS8409/ 2 X CS42L42 + ******************************************************************************/ + +const struct hda_verb dolphin_init_verbs[] = { + { 0x01, AC_VERB_SET_GPIO_WAKE_MASK, DOLPHIN_WAKE }, /* WAKE from GPIO 0,4 */ + { CS8409_PIN_VENDOR_WIDGET, AC_VERB_SET_PROC_STATE, 0x0001 }, /* Enable VPW processing */ + { CS8409_PIN_VENDOR_WIDGET, AC_VERB_SET_COEF_INDEX, 0x0002 }, /* Configure GPIO 6,7 */ + { CS8409_PIN_VENDOR_WIDGET, AC_VERB_SET_PROC_COEF, 0x0080 }, /* I2C mode */ + { CS8409_PIN_VENDOR_WIDGET, AC_VERB_SET_COEF_INDEX, 0x005b }, /* Set I2C bus speed */ + { CS8409_PIN_VENDOR_WIDGET, AC_VERB_SET_PROC_COEF, 0x0200 }, /* 100kHz I2C_STO = 2 */ + {} /* terminator */ +}; + +static const struct hda_pintbl dolphin_pincfgs[] = { + { 0x24, 0x022210f0 }, /* ASP-1-TX-A */ + { 0x25, 0x010240f0 }, /* ASP-1-TX-B */ + { 0x34, 0x02a21050 }, /* ASP-1-RX */ + {} /* terminator */ +}; + +/* Vendor specific HW configuration for CS42L42 */ +static const struct cs8409_i2c_param dolphin_c0_init_reg_seq[] = { + { CS42L42_I2C_TIMEOUT, 0xB0 }, + { CS42L42_ADC_CTL, 0x00 }, + { 0x1D02, 0x06 }, + { CS42L42_ADC_VOLUME, 0x9F }, + { CS42L42_OSC_SWITCH, 0x01 }, + { CS42L42_MCLK_CTL, 0x02 }, + { CS42L42_SRC_CTL, 0x03 }, + { CS42L42_MCLK_SRC_SEL, 0x00 }, + { CS42L42_ASP_FRM_CFG, 0x13 }, + { CS42L42_FSYNC_P_LOWER, 0xFF }, + { CS42L42_FSYNC_P_UPPER, 0x00 }, + { CS42L42_ASP_CLK_CFG, 0x20 }, + { CS42L42_SPDIF_CLK_CFG, 0x0D }, + { CS42L42_ASP_RX_DAI0_CH1_AP_RES, 0x02 }, + { CS42L42_ASP_RX_DAI0_CH1_BIT_MSB, 0x00 }, + { CS42L42_ASP_RX_DAI0_CH1_BIT_LSB, 0x00 }, + { CS42L42_ASP_RX_DAI0_CH2_AP_RES, 0x02 }, + { CS42L42_ASP_RX_DAI0_CH2_BIT_MSB, 0x00 }, + { CS42L42_ASP_RX_DAI0_CH2_BIT_LSB, 0x20 }, + { CS42L42_ASP_RX_DAI0_EN, 0x0C }, + { CS42L42_ASP_TX_CH_EN, 0x01 }, + { CS42L42_ASP_TX_CH_AP_RES, 0x02 }, + { CS42L42_ASP_TX_CH1_BIT_MSB, 0x00 }, + { CS42L42_ASP_TX_CH1_BIT_LSB, 0x00 }, + { CS42L42_ASP_TX_SZ_EN, 0x01 }, + { CS42L42_PWR_CTL1, 0x0A }, + { CS42L42_PWR_CTL2, 0x84 }, + { CS42L42_HP_CTL, 0x03 }, + { CS42L42_MIXER_CHA_VOL, 0x3F }, + { CS42L42_MIXER_CHB_VOL, 0x3F }, + { CS42L42_MIXER_ADC_VOL, 0x3f }, + { CS42L42_MIC_DET_CTL1, 0xB6 }, + { CS42L42_TIPSENSE_CTL, 0xC2 }, + { CS42L42_HS_CLAMP_DISABLE, 0x01 }, + { CS42L42_HS_SWITCH_CTL, 0xF3 }, + { CS42L42_PWR_CTL3, 0x20 }, + { CS42L42_RSENSE_CTL2, 0x00 }, + { CS42L42_RSENSE_CTL3, 0x00 }, + { CS42L42_TSENSE_CTL, 0x80 }, + { CS42L42_HS_BIAS_CTL, 0xC0 }, + { CS42L42_PWR_CTL1, 0x02 }, + { CS42L42_ADC_OVFL_INT_MASK, 0xff }, + { CS42L42_MIXER_INT_MASK, 0xff }, + { CS42L42_SRC_INT_MASK, 0xff }, + { CS42L42_ASP_RX_INT_MASK, 0xff }, + { CS42L42_ASP_TX_INT_MASK, 0xff }, + { CS42L42_CODEC_INT_MASK, 0xff }, + { CS42L42_SRCPL_INT_MASK, 0xff }, + { CS42L42_VPMON_INT_MASK, 0xff }, + { CS42L42_PLL_LOCK_INT_MASK, 0xff }, + { CS42L42_TSRS_PLUG_INT_MASK, 0xff }, + { CS42L42_DET_INT1_MASK, 0xff }, + { CS42L42_DET_INT2_MASK, 0xff } +}; + +static const struct cs8409_i2c_param dolphin_c1_init_reg_seq[] = { + { CS42L42_I2C_TIMEOUT, 0xB0 }, + { CS42L42_ADC_CTL, 0x00 }, + { 0x1D02, 0x06 }, + { CS42L42_ADC_VOLUME, 0x9F }, + { CS42L42_OSC_SWITCH, 0x01 }, + { CS42L42_MCLK_CTL, 0x02 }, + { CS42L42_SRC_CTL, 0x03 }, + { CS42L42_MCLK_SRC_SEL, 0x00 }, + { CS42L42_ASP_FRM_CFG, 0x13 }, + { CS42L42_FSYNC_P_LOWER, 0xFF }, + { CS42L42_FSYNC_P_UPPER, 0x00 }, + { CS42L42_ASP_CLK_CFG, 0x20 }, + { CS42L42_SPDIF_CLK_CFG, 0x0D }, + { CS42L42_ASP_RX_DAI0_CH1_AP_RES, 0x02 }, + { CS42L42_ASP_RX_DAI0_CH1_BIT_MSB, 0x00 }, + { CS42L42_ASP_RX_DAI0_CH1_BIT_LSB, 0x80 }, + { CS42L42_ASP_RX_DAI0_CH2_AP_RES, 0x02 }, + { CS42L42_ASP_RX_DAI0_CH2_BIT_MSB, 0x00 }, + { CS42L42_ASP_RX_DAI0_CH2_BIT_LSB, 0xA0 }, + { CS42L42_ASP_RX_DAI0_EN, 0x0C }, + { CS42L42_ASP_TX_CH_EN, 0x00 }, + { CS42L42_ASP_TX_CH_AP_RES, 0x02 }, + { CS42L42_ASP_TX_CH1_BIT_MSB, 0x00 }, + { CS42L42_ASP_TX_CH1_BIT_LSB, 0x00 }, + { CS42L42_ASP_TX_SZ_EN, 0x00 }, + { CS42L42_PWR_CTL1, 0x0E }, + { CS42L42_PWR_CTL2, 0x84 }, + { CS42L42_HP_CTL, 0x01 }, + { CS42L42_MIXER_CHA_VOL, 0x3F }, + { CS42L42_MIXER_CHB_VOL, 0x3F }, + { CS42L42_MIXER_ADC_VOL, 0x3f }, + { CS42L42_MIC_DET_CTL1, 0xB6 }, + { CS42L42_TIPSENSE_CTL, 0xC2 }, + { CS42L42_HS_CLAMP_DISABLE, 0x01 }, + { CS42L42_HS_SWITCH_CTL, 0xF3 }, + { CS42L42_PWR_CTL3, 0x20 }, + { CS42L42_RSENSE_CTL2, 0x00 }, + { CS42L42_RSENSE_CTL3, 0x00 }, + { CS42L42_TSENSE_CTL, 0x80 }, + { CS42L42_HS_BIAS_CTL, 0xC0 }, + { CS42L42_PWR_CTL1, 0x06 }, + { CS42L42_ADC_OVFL_INT_MASK, 0xff }, + { CS42L42_MIXER_INT_MASK, 0xff }, + { CS42L42_SRC_INT_MASK, 0xff }, + { CS42L42_ASP_RX_INT_MASK, 0xff }, + { CS42L42_ASP_TX_INT_MASK, 0xff }, + { CS42L42_CODEC_INT_MASK, 0xff }, + { CS42L42_SRCPL_INT_MASK, 0xff }, + { CS42L42_VPMON_INT_MASK, 0xff }, + { CS42L42_PLL_LOCK_INT_MASK, 0xff }, + { CS42L42_TSRS_PLUG_INT_MASK, 0xff }, + { CS42L42_DET_INT1_MASK, 0xff }, + { CS42L42_DET_INT2_MASK, 0xff } +}; + +/* Vendor specific hw configuration for CS8409 */ +const struct cs8409_cir_param dolphin_hw_cfg[] = { + /* +PLL1/2_EN, +I2C_EN */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_DEV_CFG1, 0xb008 }, + /* ASP1_EN=0, ASP1_STP=1 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_DEV_CFG2, 0x0002 }, + /* ASP1/2_BUS_IDLE=10, +GPIO_I2C */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_DEV_CFG3, 0x0a80 }, + /* ASP1.A: TX.LAP=0, TX.LSZ=24 bits, TX.LCS=0 */ + { CS8409_PIN_VENDOR_WIDGET, ASP1_A_TX_CTRL1, 0x0800 }, + /* ASP1.A: TX.RAP=0, TX.RSZ=24 bits, TX.RCS=32 */ + { CS8409_PIN_VENDOR_WIDGET, ASP1_A_TX_CTRL2, 0x0820 }, + /* ASP1.B: TX.LAP=0, TX.LSZ=24 bits, TX.LCS=128 */ + { CS8409_PIN_VENDOR_WIDGET, ASP1_B_TX_CTRL1, 0x0880 }, + /* ASP1.B: TX.RAP=0, TX.RSZ=24 bits, TX.RCS=160 */ + { CS8409_PIN_VENDOR_WIDGET, ASP1_B_TX_CTRL2, 0x08a0 }, + /* ASP1.A: RX.LAP=0, RX.LSZ=24 bits, RX.LCS=0 */ + { CS8409_PIN_VENDOR_WIDGET, ASP1_A_RX_CTRL1, 0x0800 }, + /* ASP1.A: RX.RAP=0, RX.RSZ=24 bits, RX.RCS=0 */ + { CS8409_PIN_VENDOR_WIDGET, ASP1_A_RX_CTRL2, 0x0800 }, + /* ASP1: LCHI = 00h */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_ASP1_CLK_CTRL1, 0x8000 }, + /* ASP1: MC/SC_SRCSEL=PLL1, LCPR=FFh */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_ASP1_CLK_CTRL2, 0x28ff }, + /* ASP1: MCEN=0, FSD=011, SCPOL_IN/OUT=0, SCDIV=1:4 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_ASP1_CLK_CTRL3, 0x0062 }, + /* ASP1/2_BEEP=0 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_BEEP_CFG, 0x0000 }, + /* ASP1_EN=1, ASP1_STP=1 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_DEV_CFG2, 0x0022 }, + /* -PLL2_EN */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_DEV_CFG1, 0x9008 }, + /* ASP1_xxx_EN=1, ASP1_MCLK_EN=0 */ + { CS8409_PIN_VENDOR_WIDGET, CS8409_PAD_CFG_SLW_RATE_CTRL, 0x5400 }, + /* test mode on */ + { CS8409_PIN_VENDOR_WIDGET, 0xc0, 0x9999 }, + /* GPIO hysteresis = 30 us */ + { CS8409_PIN_VENDOR_WIDGET, 0xc5, 0x0000 }, + /* test mode off */ + { CS8409_PIN_VENDOR_WIDGET, 0xc0, 0x0000 }, + {} /* Terminator */ +}; + +struct sub_codec dolphin_cs42l42_0 = { + .addr = DOLPHIN_C0_I2C_ADDR, + .reset_gpio = DOLPHIN_C0_RESET, + .irq_mask = DOLPHIN_C0_INT, + .init_seq = dolphin_c0_init_reg_seq, + .init_seq_num = ARRAY_SIZE(dolphin_c0_init_reg_seq), + .hp_jack_in = 0, + .mic_jack_in = 0, + .paged = 1, + .suspended = 1, + .no_type_dect = 0, +}; + +struct sub_codec dolphin_cs42l42_1 = { + .addr = DOLPHIN_C1_I2C_ADDR, + .reset_gpio = DOLPHIN_C1_RESET, + .irq_mask = DOLPHIN_C1_INT, + .init_seq = dolphin_c1_init_reg_seq, + .init_seq_num = ARRAY_SIZE(dolphin_c1_init_reg_seq), + .hp_jack_in = 0, + .mic_jack_in = 0, + .paged = 1, + .suspended = 1, + .no_type_dect = 1, +}; + +/****************************************************************************** + * CS8409 Patch Driver Structs + * Arrays Used for all projects using CS8409 + ******************************************************************************/ + +const struct snd_pci_quirk cs8409_fixup_tbl[] = { + SND_PCI_QUIRK(0x1028, 0x0A11, "Bullseye", CS8409_BULLSEYE), + SND_PCI_QUIRK(0x1028, 0x0A12, "Bullseye", CS8409_BULLSEYE), + SND_PCI_QUIRK(0x1028, 0x0A23, "Bullseye", CS8409_BULLSEYE), + SND_PCI_QUIRK(0x1028, 0x0A24, "Bullseye", CS8409_BULLSEYE), + SND_PCI_QUIRK(0x1028, 0x0A25, "Bullseye", CS8409_BULLSEYE), + SND_PCI_QUIRK(0x1028, 0x0A29, "Bullseye", CS8409_BULLSEYE), + SND_PCI_QUIRK(0x1028, 0x0A2A, "Bullseye", CS8409_BULLSEYE), + SND_PCI_QUIRK(0x1028, 0x0A2B, "Bullseye", CS8409_BULLSEYE), + SND_PCI_QUIRK(0x1028, 0x0A77, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0A78, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0A79, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0A7A, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0A7D, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0A7E, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0A7F, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0A80, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AB0, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0AB2, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0AB1, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0AB3, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0AB4, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0AB5, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0ACF, "Dolphin", CS8409_DOLPHIN), + SND_PCI_QUIRK(0x1028, 0x0AD0, "Dolphin", CS8409_DOLPHIN), + SND_PCI_QUIRK(0x1028, 0x0AD1, "Dolphin", CS8409_DOLPHIN), + SND_PCI_QUIRK(0x1028, 0x0AD2, "Dolphin", CS8409_DOLPHIN), + SND_PCI_QUIRK(0x1028, 0x0AD3, "Dolphin", CS8409_DOLPHIN), + SND_PCI_QUIRK(0x1028, 0x0AD9, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0ADA, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0ADB, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0ADC, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0ADF, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AE0, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AE1, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AE2, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AE9, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AEA, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AEB, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AEC, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AED, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AEE, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AEF, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AF0, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AF4, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0AF5, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0B92, "Warlock MLK", CS8409_WARLOCK_MLK), + SND_PCI_QUIRK(0x1028, 0x0B93, "Warlock MLK Dual Mic", CS8409_WARLOCK_MLK_DUAL_MIC), + SND_PCI_QUIRK(0x1028, 0x0B94, "Warlock MLK", CS8409_WARLOCK_MLK), + SND_PCI_QUIRK(0x1028, 0x0B95, "Warlock MLK Dual Mic", CS8409_WARLOCK_MLK_DUAL_MIC), + SND_PCI_QUIRK(0x1028, 0x0B96, "Warlock MLK", CS8409_WARLOCK_MLK), + SND_PCI_QUIRK(0x1028, 0x0B97, "Warlock MLK Dual Mic", CS8409_WARLOCK_MLK_DUAL_MIC), + SND_PCI_QUIRK(0x1028, 0x0BA5, "Odin", CS8409_ODIN), + SND_PCI_QUIRK(0x1028, 0x0BA6, "Odin", CS8409_ODIN), + SND_PCI_QUIRK(0x1028, 0x0BA8, "Odin", CS8409_ODIN), + SND_PCI_QUIRK(0x1028, 0x0BAA, "Odin", CS8409_ODIN), + SND_PCI_QUIRK(0x1028, 0x0BAE, "Odin", CS8409_ODIN), + SND_PCI_QUIRK(0x1028, 0x0BB2, "Warlock MLK", CS8409_WARLOCK_MLK), + SND_PCI_QUIRK(0x1028, 0x0BB3, "Warlock MLK", CS8409_WARLOCK_MLK), + SND_PCI_QUIRK(0x1028, 0x0BB4, "Warlock MLK", CS8409_WARLOCK_MLK), + SND_PCI_QUIRK(0x1028, 0x0BB5, "Warlock N3 15 TGL-U Nuvoton EC", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0BB6, "Warlock V3 15 TGL-U Nuvoton EC", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0BB8, "Warlock MLK", CS8409_WARLOCK_MLK), + SND_PCI_QUIRK(0x1028, 0x0BB9, "Warlock MLK Dual Mic", CS8409_WARLOCK_MLK_DUAL_MIC), + SND_PCI_QUIRK(0x1028, 0x0BBA, "Warlock MLK", CS8409_WARLOCK_MLK), + SND_PCI_QUIRK(0x1028, 0x0BBB, "Warlock MLK Dual Mic", CS8409_WARLOCK_MLK_DUAL_MIC), + SND_PCI_QUIRK(0x1028, 0x0BBC, "Warlock MLK", CS8409_WARLOCK_MLK), + SND_PCI_QUIRK(0x1028, 0x0BBD, "Warlock MLK Dual Mic", CS8409_WARLOCK_MLK_DUAL_MIC), + SND_PCI_QUIRK(0x1028, 0x0BD4, "Dolphin", CS8409_DOLPHIN), + SND_PCI_QUIRK(0x1028, 0x0BD5, "Dolphin", CS8409_DOLPHIN), + SND_PCI_QUIRK(0x1028, 0x0BD6, "Dolphin", CS8409_DOLPHIN), + SND_PCI_QUIRK(0x1028, 0x0BD7, "Dolphin", CS8409_DOLPHIN), + SND_PCI_QUIRK(0x1028, 0x0BD8, "Dolphin", CS8409_DOLPHIN), + SND_PCI_QUIRK(0x1028, 0x0C43, "Dolphin", CS8409_DOLPHIN), + SND_PCI_QUIRK(0x1028, 0x0C50, "Dolphin", CS8409_DOLPHIN), + SND_PCI_QUIRK(0x1028, 0x0C51, "Dolphin", CS8409_DOLPHIN), + SND_PCI_QUIRK(0x1028, 0x0C52, "Dolphin", CS8409_DOLPHIN), + {} /* terminator */ +}; + +/* Dell Inspiron models with cs8409/cs42l42 */ +const struct hda_model_fixup cs8409_models[] = { + { .id = CS8409_BULLSEYE, .name = "bullseye" }, + { .id = CS8409_WARLOCK, .name = "warlock" }, + { .id = CS8409_WARLOCK_MLK, .name = "warlock mlk" }, + { .id = CS8409_WARLOCK_MLK_DUAL_MIC, .name = "warlock mlk dual mic" }, + { .id = CS8409_CYBORG, .name = "cyborg" }, + { .id = CS8409_DOLPHIN, .name = "dolphin" }, + { .id = CS8409_ODIN, .name = "odin" }, + {} +}; + +const struct hda_fixup cs8409_fixups[] = { + [CS8409_BULLSEYE] = { + .type = HDA_FIXUP_PINS, + .v.pins = cs8409_cs42l42_pincfgs, + .chained = true, + .chain_id = CS8409_FIXUPS, + }, + [CS8409_WARLOCK] = { + .type = HDA_FIXUP_PINS, + .v.pins = cs8409_cs42l42_pincfgs, + .chained = true, + .chain_id = CS8409_FIXUPS, + }, + [CS8409_WARLOCK_MLK] = { + .type = HDA_FIXUP_PINS, + .v.pins = cs8409_cs42l42_pincfgs, + .chained = true, + .chain_id = CS8409_FIXUPS, + }, + [CS8409_WARLOCK_MLK_DUAL_MIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = cs8409_cs42l42_pincfgs, + .chained = true, + .chain_id = CS8409_FIXUPS, + }, + [CS8409_CYBORG] = { + .type = HDA_FIXUP_PINS, + .v.pins = cs8409_cs42l42_pincfgs, + .chained = true, + .chain_id = CS8409_FIXUPS, + }, + [CS8409_FIXUPS] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs8409_cs42l42_fixups, + }, + [CS8409_DOLPHIN] = { + .type = HDA_FIXUP_PINS, + .v.pins = dolphin_pincfgs, + .chained = true, + .chain_id = CS8409_DOLPHIN_FIXUPS, + }, + [CS8409_DOLPHIN_FIXUPS] = { + .type = HDA_FIXUP_FUNC, + .v.func = dolphin_fixups, + }, + [CS8409_ODIN] = { + .type = HDA_FIXUP_PINS, + .v.pins = cs8409_cs42l42_pincfgs_no_dmic, + .chained = true, + .chain_id = CS8409_FIXUPS, + }, +}; diff --git a/sound/pci/hda/patch_cs8409.c b/sound/pci/hda/patch_cs8409.c new file mode 100644 index 000000000..627899959 --- /dev/null +++ b/sound/pci/hda/patch_cs8409.c @@ -0,0 +1,1484 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HD audio interface patch for Cirrus Logic CS8409 HDA bridge chip + * + * Copyright (C) 2021 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <sound/core.h> +#include <linux/mutex.h> +#include <linux/iopoll.h> + +#include "patch_cs8409.h" + +/****************************************************************************** + * CS8409 Specific Functions + ******************************************************************************/ + +static int cs8409_parse_auto_config(struct hda_codec *codec) +{ + struct cs8409_spec *spec = codec->spec; + int err; + int i; + + err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, NULL, 0); + if (err < 0) + return err; + + err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg); + if (err < 0) + return err; + + /* keep the ADCs powered up when it's dynamically switchable */ + if (spec->gen.dyn_adc_switch) { + unsigned int done = 0; + + for (i = 0; i < spec->gen.input_mux.num_items; i++) { + int idx = spec->gen.dyn_adc_idx[i]; + + if (done & (1 << idx)) + continue; + snd_hda_gen_fix_pin_power(codec, spec->gen.adc_nids[idx]); + done |= 1 << idx; + } + } + + return 0; +} + +static void cs8409_disable_i2c_clock_worker(struct work_struct *work); + +static struct cs8409_spec *cs8409_alloc_spec(struct hda_codec *codec) +{ + struct cs8409_spec *spec; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return NULL; + codec->spec = spec; + spec->codec = codec; + codec->power_save_node = 1; + mutex_init(&spec->i2c_mux); + INIT_DELAYED_WORK(&spec->i2c_clk_work, cs8409_disable_i2c_clock_worker); + snd_hda_gen_spec_init(&spec->gen); + + return spec; +} + +static inline int cs8409_vendor_coef_get(struct hda_codec *codec, unsigned int idx) +{ + snd_hda_codec_write(codec, CS8409_PIN_VENDOR_WIDGET, 0, AC_VERB_SET_COEF_INDEX, idx); + return snd_hda_codec_read(codec, CS8409_PIN_VENDOR_WIDGET, 0, AC_VERB_GET_PROC_COEF, 0); +} + +static inline void cs8409_vendor_coef_set(struct hda_codec *codec, unsigned int idx, + unsigned int coef) +{ + snd_hda_codec_write(codec, CS8409_PIN_VENDOR_WIDGET, 0, AC_VERB_SET_COEF_INDEX, idx); + snd_hda_codec_write(codec, CS8409_PIN_VENDOR_WIDGET, 0, AC_VERB_SET_PROC_COEF, coef); +} + +/* + * cs8409_enable_i2c_clock - Disable I2C clocks + * @codec: the codec instance + * Disable I2C clocks. + * This must be called when the i2c mutex is unlocked. + */ +static void cs8409_disable_i2c_clock(struct hda_codec *codec) +{ + struct cs8409_spec *spec = codec->spec; + + mutex_lock(&spec->i2c_mux); + if (spec->i2c_clck_enabled) { + cs8409_vendor_coef_set(spec->codec, 0x0, + cs8409_vendor_coef_get(spec->codec, 0x0) & 0xfffffff7); + spec->i2c_clck_enabled = 0; + } + mutex_unlock(&spec->i2c_mux); +} + +/* + * cs8409_disable_i2c_clock_worker - Worker that disable the I2C Clock after 25ms without use + */ +static void cs8409_disable_i2c_clock_worker(struct work_struct *work) +{ + struct cs8409_spec *spec = container_of(work, struct cs8409_spec, i2c_clk_work.work); + + cs8409_disable_i2c_clock(spec->codec); +} + +/* + * cs8409_enable_i2c_clock - Enable I2C clocks + * @codec: the codec instance + * Enable I2C clocks. + * This must be called when the i2c mutex is locked. + */ +static void cs8409_enable_i2c_clock(struct hda_codec *codec) +{ + struct cs8409_spec *spec = codec->spec; + + /* Cancel the disable timer, but do not wait for any running disable functions to finish. + * If the disable timer runs out before cancel, the delayed work thread will be blocked, + * waiting for the mutex to become unlocked. This mutex will be locked for the duration of + * any i2c transaction, so the disable function will run to completion immediately + * afterwards in the scenario. The next enable call will re-enable the clock, regardless. + */ + cancel_delayed_work(&spec->i2c_clk_work); + + if (!spec->i2c_clck_enabled) { + cs8409_vendor_coef_set(codec, 0x0, cs8409_vendor_coef_get(codec, 0x0) | 0x8); + spec->i2c_clck_enabled = 1; + } + queue_delayed_work(system_power_efficient_wq, &spec->i2c_clk_work, msecs_to_jiffies(25)); +} + +/** + * cs8409_i2c_wait_complete - Wait for I2C transaction + * @codec: the codec instance + * + * Wait for I2C transaction to complete. + * Return -ETIMEDOUT if transaction wait times out. + */ +static int cs8409_i2c_wait_complete(struct hda_codec *codec) +{ + unsigned int retval; + + return read_poll_timeout(cs8409_vendor_coef_get, retval, retval & 0x18, + CS42L42_I2C_SLEEP_US, CS42L42_I2C_TIMEOUT_US, false, codec, CS8409_I2C_STS); +} + +/** + * cs8409_set_i2c_dev_addr - Set i2c address for transaction + * @codec: the codec instance + * @addr: I2C Address + */ +static void cs8409_set_i2c_dev_addr(struct hda_codec *codec, unsigned int addr) +{ + struct cs8409_spec *spec = codec->spec; + + if (spec->dev_addr != addr) { + cs8409_vendor_coef_set(codec, CS8409_I2C_ADDR, addr); + spec->dev_addr = addr; + } +} + +/** + * cs8409_i2c_set_page - CS8409 I2C set page register. + * @scodec: the codec instance + * @i2c_reg: Page register + * + * Returns negative on error. + */ +static int cs8409_i2c_set_page(struct sub_codec *scodec, unsigned int i2c_reg) +{ + struct hda_codec *codec = scodec->codec; + + if (scodec->paged && (scodec->last_page != (i2c_reg >> 8))) { + cs8409_vendor_coef_set(codec, CS8409_I2C_QWRITE, i2c_reg >> 8); + if (cs8409_i2c_wait_complete(codec) < 0) + return -EIO; + scodec->last_page = i2c_reg >> 8; + } + + return 0; +} + +/** + * cs8409_i2c_read - CS8409 I2C Read. + * @scodec: the codec instance + * @addr: Register to read + * + * Returns negative on error, otherwise returns read value in bits 0-7. + */ +static int cs8409_i2c_read(struct sub_codec *scodec, unsigned int addr) +{ + struct hda_codec *codec = scodec->codec; + struct cs8409_spec *spec = codec->spec; + unsigned int i2c_reg_data; + unsigned int read_data; + + if (scodec->suspended) + return -EPERM; + + mutex_lock(&spec->i2c_mux); + cs8409_enable_i2c_clock(codec); + cs8409_set_i2c_dev_addr(codec, scodec->addr); + + if (cs8409_i2c_set_page(scodec, addr)) + goto error; + + i2c_reg_data = (addr << 8) & 0x0ffff; + cs8409_vendor_coef_set(codec, CS8409_I2C_QREAD, i2c_reg_data); + if (cs8409_i2c_wait_complete(codec) < 0) + goto error; + + /* Register in bits 15-8 and the data in 7-0 */ + read_data = cs8409_vendor_coef_get(codec, CS8409_I2C_QREAD); + + mutex_unlock(&spec->i2c_mux); + + return read_data & 0x0ff; + +error: + mutex_unlock(&spec->i2c_mux); + codec_err(codec, "%s() Failed 0x%02x : 0x%04x\n", __func__, scodec->addr, addr); + return -EIO; +} + +/** + * cs8409_i2c_bulk_read - CS8409 I2C Read Sequence. + * @scodec: the codec instance + * @seq: Register Sequence to read + * @count: Number of registeres to read + * + * Returns negative on error, values are read into value element of cs8409_i2c_param sequence. + */ +static int cs8409_i2c_bulk_read(struct sub_codec *scodec, struct cs8409_i2c_param *seq, int count) +{ + struct hda_codec *codec = scodec->codec; + struct cs8409_spec *spec = codec->spec; + unsigned int i2c_reg_data; + int i; + + if (scodec->suspended) + return -EPERM; + + mutex_lock(&spec->i2c_mux); + cs8409_set_i2c_dev_addr(codec, scodec->addr); + + for (i = 0; i < count; i++) { + cs8409_enable_i2c_clock(codec); + if (cs8409_i2c_set_page(scodec, seq[i].addr)) + goto error; + + i2c_reg_data = (seq[i].addr << 8) & 0x0ffff; + cs8409_vendor_coef_set(codec, CS8409_I2C_QREAD, i2c_reg_data); + + if (cs8409_i2c_wait_complete(codec) < 0) + goto error; + + seq[i].value = cs8409_vendor_coef_get(codec, CS8409_I2C_QREAD) & 0xff; + } + + mutex_unlock(&spec->i2c_mux); + + return 0; + +error: + mutex_unlock(&spec->i2c_mux); + codec_err(codec, "I2C Bulk Write Failed 0x%02x\n", scodec->addr); + return -EIO; +} + +/** + * cs8409_i2c_write - CS8409 I2C Write. + * @scodec: the codec instance + * @addr: Register to write to + * @value: Data to write + * + * Returns negative on error, otherwise returns 0. + */ +static int cs8409_i2c_write(struct sub_codec *scodec, unsigned int addr, unsigned int value) +{ + struct hda_codec *codec = scodec->codec; + struct cs8409_spec *spec = codec->spec; + unsigned int i2c_reg_data; + + if (scodec->suspended) + return -EPERM; + + mutex_lock(&spec->i2c_mux); + + cs8409_enable_i2c_clock(codec); + cs8409_set_i2c_dev_addr(codec, scodec->addr); + + if (cs8409_i2c_set_page(scodec, addr)) + goto error; + + i2c_reg_data = ((addr << 8) & 0x0ff00) | (value & 0x0ff); + cs8409_vendor_coef_set(codec, CS8409_I2C_QWRITE, i2c_reg_data); + + if (cs8409_i2c_wait_complete(codec) < 0) + goto error; + + mutex_unlock(&spec->i2c_mux); + return 0; + +error: + mutex_unlock(&spec->i2c_mux); + codec_err(codec, "%s() Failed 0x%02x : 0x%04x\n", __func__, scodec->addr, addr); + return -EIO; +} + +/** + * cs8409_i2c_bulk_write - CS8409 I2C Write Sequence. + * @scodec: the codec instance + * @seq: Register Sequence to write + * @count: Number of registeres to write + * + * Returns negative on error. + */ +static int cs8409_i2c_bulk_write(struct sub_codec *scodec, const struct cs8409_i2c_param *seq, + int count) +{ + struct hda_codec *codec = scodec->codec; + struct cs8409_spec *spec = codec->spec; + unsigned int i2c_reg_data; + int i; + + if (scodec->suspended) + return -EPERM; + + mutex_lock(&spec->i2c_mux); + cs8409_set_i2c_dev_addr(codec, scodec->addr); + + for (i = 0; i < count; i++) { + cs8409_enable_i2c_clock(codec); + if (cs8409_i2c_set_page(scodec, seq[i].addr)) + goto error; + + i2c_reg_data = ((seq[i].addr << 8) & 0x0ff00) | (seq[i].value & 0x0ff); + cs8409_vendor_coef_set(codec, CS8409_I2C_QWRITE, i2c_reg_data); + + if (cs8409_i2c_wait_complete(codec) < 0) + goto error; + } + + mutex_unlock(&spec->i2c_mux); + + return 0; + +error: + mutex_unlock(&spec->i2c_mux); + codec_err(codec, "I2C Bulk Write Failed 0x%02x\n", scodec->addr); + return -EIO; +} + +static int cs8409_init(struct hda_codec *codec) +{ + int ret = snd_hda_gen_init(codec); + + if (!ret) + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_INIT); + + return ret; +} + +static int cs8409_build_controls(struct hda_codec *codec) +{ + int err; + + err = snd_hda_gen_build_controls(codec); + if (err < 0) + return err; + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_BUILD); + + return 0; +} + +/* Enable/Disable Unsolicited Response */ +static void cs8409_enable_ur(struct hda_codec *codec, int flag) +{ + struct cs8409_spec *spec = codec->spec; + unsigned int ur_gpios = 0; + int i; + + for (i = 0; i < spec->num_scodecs; i++) + ur_gpios |= spec->scodecs[i]->irq_mask; + + snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK, + flag ? ur_gpios : 0); + + snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_UNSOLICITED_ENABLE, + flag ? AC_UNSOL_ENABLED : 0); +} + +static void cs8409_fix_caps(struct hda_codec *codec, unsigned int nid) +{ + int caps; + + /* CS8409 is simple HDA bridge and intended to be used with a remote + * companion codec. Most of input/output PIN(s) have only basic + * capabilities. Receive and Transmit NID(s) have only OUTC and INC + * capabilities and no presence detect capable (PDC) and call to + * snd_hda_gen_build_controls() will mark them as non detectable + * phantom jacks. However, a companion codec may be + * connected to these pins which supports jack detect + * capabilities. We have to override pin capabilities, + * otherwise they will not be created as input devices. + */ + caps = snd_hdac_read_parm(&codec->core, nid, AC_PAR_PIN_CAP); + if (caps >= 0) + snd_hdac_override_parm(&codec->core, nid, AC_PAR_PIN_CAP, + (caps | (AC_PINCAP_IMP_SENSE | AC_PINCAP_PRES_DETECT))); + + snd_hda_override_wcaps(codec, nid, (get_wcaps(codec, nid) | AC_WCAP_UNSOL_CAP)); +} + +static int cs8409_spk_sw_gpio_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct cs8409_spec *spec = codec->spec; + + ucontrol->value.integer.value[0] = !!(spec->gpio_data & spec->speaker_pdn_gpio); + return 0; +} + +static int cs8409_spk_sw_gpio_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct cs8409_spec *spec = codec->spec; + unsigned int gpio_data; + + gpio_data = (spec->gpio_data & ~spec->speaker_pdn_gpio) | + (ucontrol->value.integer.value[0] ? spec->speaker_pdn_gpio : 0); + if (gpio_data == spec->gpio_data) + return 0; + spec->gpio_data = gpio_data; + snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_DATA, spec->gpio_data); + return 1; +} + +static const struct snd_kcontrol_new cs8409_spk_sw_ctrl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_ctl_boolean_mono_info, + .get = cs8409_spk_sw_gpio_get, + .put = cs8409_spk_sw_gpio_put, +}; + +/****************************************************************************** + * CS42L42 Specific Functions + ******************************************************************************/ + +int cs42l42_volume_info(struct snd_kcontrol *kctrl, struct snd_ctl_elem_info *uinfo) +{ + unsigned int ofs = get_amp_offset(kctrl); + u8 chs = get_amp_channels(kctrl); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->value.integer.step = 1; + uinfo->count = chs == 3 ? 2 : 1; + + switch (ofs) { + case CS42L42_VOL_DAC: + uinfo->value.integer.min = CS42L42_HP_VOL_REAL_MIN; + uinfo->value.integer.max = CS42L42_HP_VOL_REAL_MAX; + break; + case CS42L42_VOL_ADC: + uinfo->value.integer.min = CS42L42_AMIC_VOL_REAL_MIN; + uinfo->value.integer.max = CS42L42_AMIC_VOL_REAL_MAX; + break; + default: + break; + } + + return 0; +} + +int cs42l42_volume_get(struct snd_kcontrol *kctrl, struct snd_ctl_elem_value *uctrl) +{ + struct hda_codec *codec = snd_kcontrol_chip(kctrl); + struct cs8409_spec *spec = codec->spec; + struct sub_codec *cs42l42 = spec->scodecs[get_amp_index(kctrl)]; + int chs = get_amp_channels(kctrl); + unsigned int ofs = get_amp_offset(kctrl); + long *valp = uctrl->value.integer.value; + + switch (ofs) { + case CS42L42_VOL_DAC: + if (chs & BIT(0)) + *valp++ = cs42l42->vol[ofs]; + if (chs & BIT(1)) + *valp = cs42l42->vol[ofs+1]; + break; + case CS42L42_VOL_ADC: + if (chs & BIT(0)) + *valp = cs42l42->vol[ofs]; + break; + default: + break; + } + + return 0; +} + +static void cs42l42_mute(struct sub_codec *cs42l42, int vol_type, + unsigned int chs, bool mute) +{ + if (mute) { + if (vol_type == CS42L42_VOL_DAC) { + if (chs & BIT(0)) + cs8409_i2c_write(cs42l42, CS42L42_MIXER_CHA_VOL, 0x3f); + if (chs & BIT(1)) + cs8409_i2c_write(cs42l42, CS42L42_MIXER_CHB_VOL, 0x3f); + } else if (vol_type == CS42L42_VOL_ADC) { + if (chs & BIT(0)) + cs8409_i2c_write(cs42l42, CS42L42_ADC_VOLUME, 0x9f); + } + } else { + if (vol_type == CS42L42_VOL_DAC) { + if (chs & BIT(0)) + cs8409_i2c_write(cs42l42, CS42L42_MIXER_CHA_VOL, + -(cs42l42->vol[CS42L42_DAC_CH0_VOL_OFFSET]) + & CS42L42_MIXER_CH_VOL_MASK); + if (chs & BIT(1)) + cs8409_i2c_write(cs42l42, CS42L42_MIXER_CHB_VOL, + -(cs42l42->vol[CS42L42_DAC_CH1_VOL_OFFSET]) + & CS42L42_MIXER_CH_VOL_MASK); + } else if (vol_type == CS42L42_VOL_ADC) { + if (chs & BIT(0)) + cs8409_i2c_write(cs42l42, CS42L42_ADC_VOLUME, + cs42l42->vol[CS42L42_ADC_VOL_OFFSET] + & CS42L42_REG_AMIC_VOL_MASK); + } + } +} + +int cs42l42_volume_put(struct snd_kcontrol *kctrl, struct snd_ctl_elem_value *uctrl) +{ + struct hda_codec *codec = snd_kcontrol_chip(kctrl); + struct cs8409_spec *spec = codec->spec; + struct sub_codec *cs42l42 = spec->scodecs[get_amp_index(kctrl)]; + int chs = get_amp_channels(kctrl); + unsigned int ofs = get_amp_offset(kctrl); + long *valp = uctrl->value.integer.value; + + switch (ofs) { + case CS42L42_VOL_DAC: + if (chs & BIT(0)) + cs42l42->vol[ofs] = *valp; + if (chs & BIT(1)) { + valp++; + cs42l42->vol[ofs + 1] = *valp; + } + if (spec->playback_started) + cs42l42_mute(cs42l42, CS42L42_VOL_DAC, chs, false); + break; + case CS42L42_VOL_ADC: + if (chs & BIT(0)) + cs42l42->vol[ofs] = *valp; + if (spec->capture_started) + cs42l42_mute(cs42l42, CS42L42_VOL_ADC, chs, false); + break; + default: + break; + } + + return 0; +} + +static void cs42l42_playback_pcm_hook(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream, + int action) +{ + struct cs8409_spec *spec = codec->spec; + struct sub_codec *cs42l42; + int i; + bool mute; + + switch (action) { + case HDA_GEN_PCM_ACT_PREPARE: + mute = false; + spec->playback_started = 1; + break; + case HDA_GEN_PCM_ACT_CLEANUP: + mute = true; + spec->playback_started = 0; + break; + default: + return; + } + + for (i = 0; i < spec->num_scodecs; i++) { + cs42l42 = spec->scodecs[i]; + cs42l42_mute(cs42l42, CS42L42_VOL_DAC, 0x3, mute); + } +} + +static void cs42l42_capture_pcm_hook(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream, + int action) +{ + struct cs8409_spec *spec = codec->spec; + struct sub_codec *cs42l42; + int i; + bool mute; + + switch (action) { + case HDA_GEN_PCM_ACT_PREPARE: + mute = false; + spec->capture_started = 1; + break; + case HDA_GEN_PCM_ACT_CLEANUP: + mute = true; + spec->capture_started = 0; + break; + default: + return; + } + + for (i = 0; i < spec->num_scodecs; i++) { + cs42l42 = spec->scodecs[i]; + cs42l42_mute(cs42l42, CS42L42_VOL_ADC, 0x3, mute); + } +} + +/* Configure CS42L42 slave codec for jack autodetect */ +static void cs42l42_enable_jack_detect(struct sub_codec *cs42l42) +{ + cs8409_i2c_write(cs42l42, CS42L42_HSBIAS_SC_AUTOCTL, cs42l42->hsbias_hiz); + /* Clear WAKE# */ + cs8409_i2c_write(cs42l42, CS42L42_WAKE_CTL, 0x00C1); + /* Wait ~2.5ms */ + usleep_range(2500, 3000); + /* Set mode WAKE# output follows the combination logic directly */ + cs8409_i2c_write(cs42l42, CS42L42_WAKE_CTL, 0x00C0); + /* Clear interrupts status */ + cs8409_i2c_read(cs42l42, CS42L42_TSRS_PLUG_STATUS); + /* Enable interrupt */ + cs8409_i2c_write(cs42l42, CS42L42_TSRS_PLUG_INT_MASK, 0xF3); +} + +/* Enable and run CS42L42 slave codec jack auto detect */ +static void cs42l42_run_jack_detect(struct sub_codec *cs42l42) +{ + /* Clear interrupts */ + cs8409_i2c_read(cs42l42, CS42L42_CODEC_STATUS); + cs8409_i2c_read(cs42l42, CS42L42_DET_STATUS1); + cs8409_i2c_write(cs42l42, CS42L42_TSRS_PLUG_INT_MASK, 0xFF); + cs8409_i2c_read(cs42l42, CS42L42_TSRS_PLUG_STATUS); + + cs8409_i2c_write(cs42l42, CS42L42_PWR_CTL2, 0x87); + cs8409_i2c_write(cs42l42, CS42L42_DAC_CTL2, 0x86); + cs8409_i2c_write(cs42l42, CS42L42_MISC_DET_CTL, 0x07); + cs8409_i2c_write(cs42l42, CS42L42_CODEC_INT_MASK, 0xFD); + cs8409_i2c_write(cs42l42, CS42L42_HSDET_CTL2, 0x80); + /* Wait ~20ms*/ + usleep_range(20000, 25000); + cs8409_i2c_write(cs42l42, CS42L42_HSDET_CTL1, 0x77); + cs8409_i2c_write(cs42l42, CS42L42_HSDET_CTL2, 0xc0); +} + +static int cs42l42_manual_hs_det(struct sub_codec *cs42l42) +{ + unsigned int hs_det_status; + unsigned int hs_det_comp1; + unsigned int hs_det_comp2; + unsigned int hs_det_sw; + unsigned int hs_type; + + /* Set hs detect to manual, active mode */ + cs8409_i2c_write(cs42l42, CS42L42_HSDET_CTL2, + (1 << CS42L42_HSDET_CTRL_SHIFT) | + (0 << CS42L42_HSDET_SET_SHIFT) | + (0 << CS42L42_HSBIAS_REF_SHIFT) | + (0 << CS42L42_HSDET_AUTO_TIME_SHIFT)); + + /* Configure HS DET comparator reference levels. */ + cs8409_i2c_write(cs42l42, CS42L42_HSDET_CTL1, + (CS42L42_HSDET_COMP1_LVL_VAL << CS42L42_HSDET_COMP1_LVL_SHIFT) | + (CS42L42_HSDET_COMP2_LVL_VAL << CS42L42_HSDET_COMP2_LVL_SHIFT)); + + /* Open the SW_HSB_HS3 switch and close SW_HSB_HS4 for a Type 1 headset. */ + cs8409_i2c_write(cs42l42, CS42L42_HS_SWITCH_CTL, CS42L42_HSDET_SW_COMP1); + + msleep(100); + + hs_det_status = cs8409_i2c_read(cs42l42, CS42L42_HS_DET_STATUS); + + hs_det_comp1 = (hs_det_status & CS42L42_HSDET_COMP1_OUT_MASK) >> + CS42L42_HSDET_COMP1_OUT_SHIFT; + hs_det_comp2 = (hs_det_status & CS42L42_HSDET_COMP2_OUT_MASK) >> + CS42L42_HSDET_COMP2_OUT_SHIFT; + + /* Close the SW_HSB_HS3 switch for a Type 2 headset. */ + cs8409_i2c_write(cs42l42, CS42L42_HS_SWITCH_CTL, CS42L42_HSDET_SW_COMP2); + + msleep(100); + + hs_det_status = cs8409_i2c_read(cs42l42, CS42L42_HS_DET_STATUS); + + hs_det_comp1 |= ((hs_det_status & CS42L42_HSDET_COMP1_OUT_MASK) >> + CS42L42_HSDET_COMP1_OUT_SHIFT) << 1; + hs_det_comp2 |= ((hs_det_status & CS42L42_HSDET_COMP2_OUT_MASK) >> + CS42L42_HSDET_COMP2_OUT_SHIFT) << 1; + + /* Use Comparator 1 with 1.25V Threshold. */ + switch (hs_det_comp1) { + case CS42L42_HSDET_COMP_TYPE1: + hs_type = CS42L42_PLUG_CTIA; + hs_det_sw = CS42L42_HSDET_SW_TYPE1; + break; + case CS42L42_HSDET_COMP_TYPE2: + hs_type = CS42L42_PLUG_OMTP; + hs_det_sw = CS42L42_HSDET_SW_TYPE2; + break; + default: + /* Fallback to Comparator 2 with 1.75V Threshold. */ + switch (hs_det_comp2) { + case CS42L42_HSDET_COMP_TYPE1: + hs_type = CS42L42_PLUG_CTIA; + hs_det_sw = CS42L42_HSDET_SW_TYPE1; + break; + case CS42L42_HSDET_COMP_TYPE2: + hs_type = CS42L42_PLUG_OMTP; + hs_det_sw = CS42L42_HSDET_SW_TYPE2; + break; + case CS42L42_HSDET_COMP_TYPE3: + hs_type = CS42L42_PLUG_HEADPHONE; + hs_det_sw = CS42L42_HSDET_SW_TYPE3; + break; + default: + hs_type = CS42L42_PLUG_INVALID; + hs_det_sw = CS42L42_HSDET_SW_TYPE4; + break; + } + } + + /* Set Switches */ + cs8409_i2c_write(cs42l42, CS42L42_HS_SWITCH_CTL, hs_det_sw); + + /* Set HSDET mode to Manual—Disabled */ + cs8409_i2c_write(cs42l42, CS42L42_HSDET_CTL2, + (0 << CS42L42_HSDET_CTRL_SHIFT) | + (0 << CS42L42_HSDET_SET_SHIFT) | + (0 << CS42L42_HSBIAS_REF_SHIFT) | + (0 << CS42L42_HSDET_AUTO_TIME_SHIFT)); + + /* Configure HS DET comparator reference levels. */ + cs8409_i2c_write(cs42l42, CS42L42_HSDET_CTL1, + (CS42L42_HSDET_COMP1_LVL_DEFAULT << CS42L42_HSDET_COMP1_LVL_SHIFT) | + (CS42L42_HSDET_COMP2_LVL_DEFAULT << CS42L42_HSDET_COMP2_LVL_SHIFT)); + + return hs_type; +} + +static int cs42l42_handle_tip_sense(struct sub_codec *cs42l42, unsigned int reg_ts_status) +{ + int status_changed = 0; + + /* TIP_SENSE INSERT/REMOVE */ + switch (reg_ts_status) { + case CS42L42_TS_PLUG: + if (cs42l42->no_type_dect) { + status_changed = 1; + cs42l42->hp_jack_in = 1; + cs42l42->mic_jack_in = 0; + } else { + cs42l42_run_jack_detect(cs42l42); + } + break; + + case CS42L42_TS_UNPLUG: + status_changed = 1; + cs42l42->hp_jack_in = 0; + cs42l42->mic_jack_in = 0; + break; + default: + /* jack in transition */ + break; + } + + codec_dbg(cs42l42->codec, "Tip Sense Detection: (%d)\n", reg_ts_status); + + return status_changed; +} + +static int cs42l42_jack_unsol_event(struct sub_codec *cs42l42) +{ + int current_plug_status; + int status_changed = 0; + int reg_cdc_status; + int reg_hs_status; + int reg_ts_status; + int type; + + /* Read jack detect status registers */ + reg_cdc_status = cs8409_i2c_read(cs42l42, CS42L42_CODEC_STATUS); + reg_hs_status = cs8409_i2c_read(cs42l42, CS42L42_HS_DET_STATUS); + reg_ts_status = cs8409_i2c_read(cs42l42, CS42L42_TSRS_PLUG_STATUS); + + /* If status values are < 0, read error has occurred. */ + if (reg_cdc_status < 0 || reg_hs_status < 0 || reg_ts_status < 0) + return -EIO; + + current_plug_status = (reg_ts_status & (CS42L42_TS_PLUG_MASK | CS42L42_TS_UNPLUG_MASK)) + >> CS42L42_TS_PLUG_SHIFT; + + /* HSDET_AUTO_DONE */ + if (reg_cdc_status & CS42L42_HSDET_AUTO_DONE_MASK) { + + /* Disable HSDET_AUTO_DONE */ + cs8409_i2c_write(cs42l42, CS42L42_CODEC_INT_MASK, 0xFF); + + type = (reg_hs_status & CS42L42_HSDET_TYPE_MASK) >> CS42L42_HSDET_TYPE_SHIFT; + + /* Configure the HSDET mode. */ + cs8409_i2c_write(cs42l42, CS42L42_HSDET_CTL2, 0x80); + + if (cs42l42->no_type_dect) { + status_changed = cs42l42_handle_tip_sense(cs42l42, current_plug_status); + } else { + if (type == CS42L42_PLUG_INVALID || type == CS42L42_PLUG_HEADPHONE) { + codec_dbg(cs42l42->codec, + "Auto detect value not valid (%d), running manual det\n", + type); + type = cs42l42_manual_hs_det(cs42l42); + } + + switch (type) { + case CS42L42_PLUG_CTIA: + case CS42L42_PLUG_OMTP: + status_changed = 1; + cs42l42->hp_jack_in = 1; + cs42l42->mic_jack_in = 1; + break; + case CS42L42_PLUG_HEADPHONE: + status_changed = 1; + cs42l42->hp_jack_in = 1; + cs42l42->mic_jack_in = 0; + break; + default: + status_changed = 1; + cs42l42->hp_jack_in = 0; + cs42l42->mic_jack_in = 0; + break; + } + codec_dbg(cs42l42->codec, "Detection done (%d)\n", type); + } + + /* Enable the HPOUT ground clamp and configure the HP pull-down */ + cs8409_i2c_write(cs42l42, CS42L42_DAC_CTL2, 0x02); + /* Re-Enable Tip Sense Interrupt */ + cs8409_i2c_write(cs42l42, CS42L42_TSRS_PLUG_INT_MASK, 0xF3); + } else { + status_changed = cs42l42_handle_tip_sense(cs42l42, current_plug_status); + } + + return status_changed; +} + +static void cs42l42_resume(struct sub_codec *cs42l42) +{ + struct hda_codec *codec = cs42l42->codec; + struct cs8409_spec *spec = codec->spec; + struct cs8409_i2c_param irq_regs[] = { + { CS42L42_CODEC_STATUS, 0x00 }, + { CS42L42_DET_INT_STATUS1, 0x00 }, + { CS42L42_DET_INT_STATUS2, 0x00 }, + { CS42L42_TSRS_PLUG_STATUS, 0x00 }, + }; + int fsv_old, fsv_new; + + /* Bring CS42L42 out of Reset */ + spec->gpio_data = snd_hda_codec_read(codec, CS8409_PIN_AFG, 0, AC_VERB_GET_GPIO_DATA, 0); + spec->gpio_data |= cs42l42->reset_gpio; + snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_DATA, spec->gpio_data); + usleep_range(10000, 15000); + + cs42l42->suspended = 0; + + /* Initialize CS42L42 companion codec */ + cs8409_i2c_bulk_write(cs42l42, cs42l42->init_seq, cs42l42->init_seq_num); + msleep(CS42L42_INIT_TIMEOUT_MS); + + /* Clear interrupts, by reading interrupt status registers */ + cs8409_i2c_bulk_read(cs42l42, irq_regs, ARRAY_SIZE(irq_regs)); + + fsv_old = cs8409_i2c_read(cs42l42, CS42L42_HP_CTL); + if (cs42l42->full_scale_vol == CS42L42_FULL_SCALE_VOL_0DB) + fsv_new = fsv_old & ~CS42L42_FULL_SCALE_VOL_MASK; + else + fsv_new = fsv_old & CS42L42_FULL_SCALE_VOL_MASK; + if (fsv_new != fsv_old) + cs8409_i2c_write(cs42l42, CS42L42_HP_CTL, fsv_new); + + /* we have to explicitly allow unsol event handling even during the + * resume phase so that the jack event is processed properly + */ + snd_hda_codec_allow_unsol_events(cs42l42->codec); + + cs42l42_enable_jack_detect(cs42l42); +} + +#ifdef CONFIG_PM +static void cs42l42_suspend(struct sub_codec *cs42l42) +{ + struct hda_codec *codec = cs42l42->codec; + struct cs8409_spec *spec = codec->spec; + int reg_cdc_status = 0; + const struct cs8409_i2c_param cs42l42_pwr_down_seq[] = { + { CS42L42_DAC_CTL2, 0x02 }, + { CS42L42_HS_CLAMP_DISABLE, 0x00 }, + { CS42L42_MIXER_CHA_VOL, 0x3F }, + { CS42L42_MIXER_ADC_VOL, 0x3F }, + { CS42L42_MIXER_CHB_VOL, 0x3F }, + { CS42L42_HP_CTL, 0x0F }, + { CS42L42_ASP_RX_DAI0_EN, 0x00 }, + { CS42L42_ASP_CLK_CFG, 0x00 }, + { CS42L42_PWR_CTL1, 0xFE }, + { CS42L42_PWR_CTL2, 0x8C }, + { CS42L42_PWR_CTL1, 0xFF }, + }; + + cs8409_i2c_bulk_write(cs42l42, cs42l42_pwr_down_seq, ARRAY_SIZE(cs42l42_pwr_down_seq)); + + if (read_poll_timeout(cs8409_i2c_read, reg_cdc_status, + (reg_cdc_status & 0x1), CS42L42_PDN_SLEEP_US, CS42L42_PDN_TIMEOUT_US, + true, cs42l42, CS42L42_CODEC_STATUS) < 0) + codec_warn(codec, "Timeout waiting for PDN_DONE for CS42L42\n"); + + /* Power down CS42L42 ASP/EQ/MIX/HP */ + cs8409_i2c_write(cs42l42, CS42L42_PWR_CTL2, 0x9C); + cs42l42->suspended = 1; + cs42l42->last_page = 0; + cs42l42->hp_jack_in = 0; + cs42l42->mic_jack_in = 0; + + /* Put CS42L42 into Reset */ + spec->gpio_data = snd_hda_codec_read(codec, CS8409_PIN_AFG, 0, AC_VERB_GET_GPIO_DATA, 0); + spec->gpio_data &= ~cs42l42->reset_gpio; + snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_DATA, spec->gpio_data); +} +#endif + +static void cs8409_free(struct hda_codec *codec) +{ + struct cs8409_spec *spec = codec->spec; + + /* Cancel i2c clock disable timer, and disable clock if left enabled */ + cancel_delayed_work_sync(&spec->i2c_clk_work); + cs8409_disable_i2c_clock(codec); + + snd_hda_gen_free(codec); +} + +/****************************************************************************** + * BULLSEYE / WARLOCK / CYBORG Specific Functions + * CS8409/CS42L42 + ******************************************************************************/ + +/* + * In the case of CS8409 we do not have unsolicited events from NID's 0x24 + * and 0x34 where hs mic and hp are connected. Companion codec CS42L42 will + * generate interrupt via gpio 4 to notify jack events. We have to overwrite + * generic snd_hda_jack_unsol_event(), read CS42L42 jack detect status registers + * and then notify status via generic snd_hda_jack_unsol_event() call. + */ +static void cs8409_cs42l42_jack_unsol_event(struct hda_codec *codec, unsigned int res) +{ + struct cs8409_spec *spec = codec->spec; + struct sub_codec *cs42l42 = spec->scodecs[CS8409_CODEC0]; + struct hda_jack_tbl *jk; + + /* jack_unsol_event() will be called every time gpio line changing state. + * In this case gpio4 line goes up as a result of reading interrupt status + * registers in previous cs8409_jack_unsol_event() call. + * We don't need to handle this event, ignoring... + */ + if (res & cs42l42->irq_mask) + return; + + if (cs42l42_jack_unsol_event(cs42l42)) { + snd_hda_set_pin_ctl(codec, CS8409_CS42L42_SPK_PIN_NID, + cs42l42->hp_jack_in ? 0 : PIN_OUT); + /* Report jack*/ + jk = snd_hda_jack_tbl_get_mst(codec, CS8409_CS42L42_HP_PIN_NID, 0); + if (jk) + snd_hda_jack_unsol_event(codec, (jk->tag << AC_UNSOL_RES_TAG_SHIFT) & + AC_UNSOL_RES_TAG); + /* Report jack*/ + jk = snd_hda_jack_tbl_get_mst(codec, CS8409_CS42L42_AMIC_PIN_NID, 0); + if (jk) + snd_hda_jack_unsol_event(codec, (jk->tag << AC_UNSOL_RES_TAG_SHIFT) & + AC_UNSOL_RES_TAG); + } +} + +#ifdef CONFIG_PM +/* Manage PDREF, when transition to D3hot */ +static int cs8409_cs42l42_suspend(struct hda_codec *codec) +{ + struct cs8409_spec *spec = codec->spec; + int i; + + spec->init_done = 0; + + cs8409_enable_ur(codec, 0); + + for (i = 0; i < spec->num_scodecs; i++) + cs42l42_suspend(spec->scodecs[i]); + + /* Cancel i2c clock disable timer, and disable clock if left enabled */ + cancel_delayed_work_sync(&spec->i2c_clk_work); + cs8409_disable_i2c_clock(codec); + + snd_hda_shutup_pins(codec); + + return 0; +} +#endif + +/* Vendor specific HW configuration + * PLL, ASP, I2C, SPI, GPIOs, DMIC etc... + */ +static void cs8409_cs42l42_hw_init(struct hda_codec *codec) +{ + const struct cs8409_cir_param *seq = cs8409_cs42l42_hw_cfg; + const struct cs8409_cir_param *seq_bullseye = cs8409_cs42l42_bullseye_atn; + struct cs8409_spec *spec = codec->spec; + struct sub_codec *cs42l42 = spec->scodecs[CS8409_CODEC0]; + + if (spec->gpio_mask) { + snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_MASK, + spec->gpio_mask); + snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_DIRECTION, + spec->gpio_dir); + snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_DATA, + spec->gpio_data); + } + + for (; seq->nid; seq++) + cs8409_vendor_coef_set(codec, seq->cir, seq->coeff); + + if (codec->fixup_id == CS8409_BULLSEYE) { + for (; seq_bullseye->nid; seq_bullseye++) + cs8409_vendor_coef_set(codec, seq_bullseye->cir, seq_bullseye->coeff); + } + + switch (codec->fixup_id) { + case CS8409_CYBORG: + case CS8409_WARLOCK_MLK_DUAL_MIC: + /* DMIC1_MO=00b, DMIC1/2_SR=1 */ + cs8409_vendor_coef_set(codec, CS8409_DMIC_CFG, 0x0003); + break; + case CS8409_ODIN: + /* ASP1/2_xxx_EN=1, ASP1/2_MCLK_EN=0, DMIC1_SCL_EN=0 */ + cs8409_vendor_coef_set(codec, CS8409_PAD_CFG_SLW_RATE_CTRL, 0xfc00); + break; + default: + break; + } + + cs42l42_resume(cs42l42); + + /* Enable Unsolicited Response */ + cs8409_enable_ur(codec, 1); +} + +static const struct hda_codec_ops cs8409_cs42l42_patch_ops = { + .build_controls = cs8409_build_controls, + .build_pcms = snd_hda_gen_build_pcms, + .init = cs8409_init, + .free = cs8409_free, + .unsol_event = cs8409_cs42l42_jack_unsol_event, +#ifdef CONFIG_PM + .suspend = cs8409_cs42l42_suspend, +#endif +}; + +static int cs8409_cs42l42_exec_verb(struct hdac_device *dev, unsigned int cmd, unsigned int flags, + unsigned int *res) +{ + struct hda_codec *codec = container_of(dev, struct hda_codec, core); + struct cs8409_spec *spec = codec->spec; + struct sub_codec *cs42l42 = spec->scodecs[CS8409_CODEC0]; + + unsigned int nid = ((cmd >> 20) & 0x07f); + unsigned int verb = ((cmd >> 8) & 0x0fff); + + /* CS8409 pins have no AC_PINSENSE_PRESENCE + * capabilities. We have to intercept 2 calls for pins 0x24 and 0x34 + * and return correct pin sense values for read_pin_sense() call from + * hda_jack based on CS42L42 jack detect status. + */ + switch (nid) { + case CS8409_CS42L42_HP_PIN_NID: + if (verb == AC_VERB_GET_PIN_SENSE) { + *res = (cs42l42->hp_jack_in) ? AC_PINSENSE_PRESENCE : 0; + return 0; + } + break; + case CS8409_CS42L42_AMIC_PIN_NID: + if (verb == AC_VERB_GET_PIN_SENSE) { + *res = (cs42l42->mic_jack_in) ? AC_PINSENSE_PRESENCE : 0; + return 0; + } + break; + default: + break; + } + + return spec->exec_verb(dev, cmd, flags, res); +} + +void cs8409_cs42l42_fixups(struct hda_codec *codec, const struct hda_fixup *fix, int action) +{ + struct cs8409_spec *spec = codec->spec; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + snd_hda_add_verbs(codec, cs8409_cs42l42_init_verbs); + /* verb exec op override */ + spec->exec_verb = codec->core.exec_verb; + codec->core.exec_verb = cs8409_cs42l42_exec_verb; + + spec->scodecs[CS8409_CODEC0] = &cs8409_cs42l42_codec; + spec->num_scodecs = 1; + spec->scodecs[CS8409_CODEC0]->codec = codec; + codec->patch_ops = cs8409_cs42l42_patch_ops; + + spec->gen.suppress_auto_mute = 1; + spec->gen.no_primary_hp = 1; + spec->gen.suppress_vmaster = 1; + + spec->speaker_pdn_gpio = 0; + + /* GPIO 5 out, 3,4 in */ + spec->gpio_dir = spec->scodecs[CS8409_CODEC0]->reset_gpio; + spec->gpio_data = 0; + spec->gpio_mask = 0x03f; + + /* Basic initial sequence for specific hw configuration */ + snd_hda_sequence_write(codec, cs8409_cs42l42_init_verbs); + + cs8409_fix_caps(codec, CS8409_CS42L42_HP_PIN_NID); + cs8409_fix_caps(codec, CS8409_CS42L42_AMIC_PIN_NID); + + spec->scodecs[CS8409_CODEC0]->hsbias_hiz = 0x0020; + + switch (codec->fixup_id) { + case CS8409_CYBORG: + spec->scodecs[CS8409_CODEC0]->full_scale_vol = + CS42L42_FULL_SCALE_VOL_MINUS6DB; + spec->speaker_pdn_gpio = CS8409_CYBORG_SPEAKER_PDN; + break; + case CS8409_ODIN: + spec->scodecs[CS8409_CODEC0]->full_scale_vol = CS42L42_FULL_SCALE_VOL_0DB; + spec->speaker_pdn_gpio = CS8409_CYBORG_SPEAKER_PDN; + break; + case CS8409_WARLOCK_MLK: + case CS8409_WARLOCK_MLK_DUAL_MIC: + spec->scodecs[CS8409_CODEC0]->full_scale_vol = CS42L42_FULL_SCALE_VOL_0DB; + spec->speaker_pdn_gpio = CS8409_WARLOCK_SPEAKER_PDN; + break; + default: + spec->scodecs[CS8409_CODEC0]->full_scale_vol = + CS42L42_FULL_SCALE_VOL_MINUS6DB; + spec->speaker_pdn_gpio = CS8409_WARLOCK_SPEAKER_PDN; + break; + } + + if (spec->speaker_pdn_gpio > 0) { + spec->gpio_dir |= spec->speaker_pdn_gpio; + spec->gpio_data |= spec->speaker_pdn_gpio; + } + + break; + case HDA_FIXUP_ACT_PROBE: + /* Fix Sample Rate to 48kHz */ + spec->gen.stream_analog_playback = &cs42l42_48k_pcm_analog_playback; + spec->gen.stream_analog_capture = &cs42l42_48k_pcm_analog_capture; + /* add hooks */ + spec->gen.pcm_playback_hook = cs42l42_playback_pcm_hook; + spec->gen.pcm_capture_hook = cs42l42_capture_pcm_hook; + if (codec->fixup_id != CS8409_ODIN) + /* Set initial DMIC volume to -26 dB */ + snd_hda_codec_amp_init_stereo(codec, CS8409_CS42L42_DMIC_ADC_PIN_NID, + HDA_INPUT, 0, 0xff, 0x19); + snd_hda_gen_add_kctl(&spec->gen, "Headphone Playback Volume", + &cs42l42_dac_volume_mixer); + snd_hda_gen_add_kctl(&spec->gen, "Mic Capture Volume", + &cs42l42_adc_volume_mixer); + if (spec->speaker_pdn_gpio > 0) + snd_hda_gen_add_kctl(&spec->gen, "Speaker Playback Switch", + &cs8409_spk_sw_ctrl); + /* Disable Unsolicited Response during boot */ + cs8409_enable_ur(codec, 0); + snd_hda_codec_set_name(codec, "CS8409/CS42L42"); + break; + case HDA_FIXUP_ACT_INIT: + cs8409_cs42l42_hw_init(codec); + spec->init_done = 1; + if (spec->init_done && spec->build_ctrl_done + && !spec->scodecs[CS8409_CODEC0]->hp_jack_in) + cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]); + break; + case HDA_FIXUP_ACT_BUILD: + spec->build_ctrl_done = 1; + /* Run jack auto detect first time on boot + * after controls have been added, to check if jack has + * been already plugged in. + * Run immediately after init. + */ + if (spec->init_done && spec->build_ctrl_done + && !spec->scodecs[CS8409_CODEC0]->hp_jack_in) + cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]); + break; + default: + break; + } +} + +/****************************************************************************** + * Dolphin Specific Functions + * CS8409/ 2 X CS42L42 + ******************************************************************************/ + +/* + * In the case of CS8409 we do not have unsolicited events when + * hs mic and hp are connected. Companion codec CS42L42 will + * generate interrupt via irq_mask to notify jack events. We have to overwrite + * generic snd_hda_jack_unsol_event(), read CS42L42 jack detect status registers + * and then notify status via generic snd_hda_jack_unsol_event() call. + */ +static void dolphin_jack_unsol_event(struct hda_codec *codec, unsigned int res) +{ + struct cs8409_spec *spec = codec->spec; + struct sub_codec *cs42l42; + struct hda_jack_tbl *jk; + + cs42l42 = spec->scodecs[CS8409_CODEC0]; + if (!cs42l42->suspended && (~res & cs42l42->irq_mask) && + cs42l42_jack_unsol_event(cs42l42)) { + jk = snd_hda_jack_tbl_get_mst(codec, DOLPHIN_HP_PIN_NID, 0); + if (jk) + snd_hda_jack_unsol_event(codec, + (jk->tag << AC_UNSOL_RES_TAG_SHIFT) & + AC_UNSOL_RES_TAG); + + jk = snd_hda_jack_tbl_get_mst(codec, DOLPHIN_AMIC_PIN_NID, 0); + if (jk) + snd_hda_jack_unsol_event(codec, + (jk->tag << AC_UNSOL_RES_TAG_SHIFT) & + AC_UNSOL_RES_TAG); + } + + cs42l42 = spec->scodecs[CS8409_CODEC1]; + if (!cs42l42->suspended && (~res & cs42l42->irq_mask) && + cs42l42_jack_unsol_event(cs42l42)) { + jk = snd_hda_jack_tbl_get_mst(codec, DOLPHIN_LO_PIN_NID, 0); + if (jk) + snd_hda_jack_unsol_event(codec, + (jk->tag << AC_UNSOL_RES_TAG_SHIFT) & + AC_UNSOL_RES_TAG); + } +} + +/* Vendor specific HW configuration + * PLL, ASP, I2C, SPI, GPIOs, DMIC etc... + */ +static void dolphin_hw_init(struct hda_codec *codec) +{ + const struct cs8409_cir_param *seq = dolphin_hw_cfg; + struct cs8409_spec *spec = codec->spec; + struct sub_codec *cs42l42; + int i; + + if (spec->gpio_mask) { + snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_MASK, + spec->gpio_mask); + snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_DIRECTION, + spec->gpio_dir); + snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_DATA, + spec->gpio_data); + } + + for (; seq->nid; seq++) + cs8409_vendor_coef_set(codec, seq->cir, seq->coeff); + + for (i = 0; i < spec->num_scodecs; i++) { + cs42l42 = spec->scodecs[i]; + cs42l42_resume(cs42l42); + } + + /* Enable Unsolicited Response */ + cs8409_enable_ur(codec, 1); +} + +static const struct hda_codec_ops cs8409_dolphin_patch_ops = { + .build_controls = cs8409_build_controls, + .build_pcms = snd_hda_gen_build_pcms, + .init = cs8409_init, + .free = cs8409_free, + .unsol_event = dolphin_jack_unsol_event, +#ifdef CONFIG_PM + .suspend = cs8409_cs42l42_suspend, +#endif +}; + +static int dolphin_exec_verb(struct hdac_device *dev, unsigned int cmd, unsigned int flags, + unsigned int *res) +{ + struct hda_codec *codec = container_of(dev, struct hda_codec, core); + struct cs8409_spec *spec = codec->spec; + struct sub_codec *cs42l42 = spec->scodecs[CS8409_CODEC0]; + + unsigned int nid = ((cmd >> 20) & 0x07f); + unsigned int verb = ((cmd >> 8) & 0x0fff); + + /* CS8409 pins have no AC_PINSENSE_PRESENCE + * capabilities. We have to intercept calls for CS42L42 pins + * and return correct pin sense values for read_pin_sense() call from + * hda_jack based on CS42L42 jack detect status. + */ + switch (nid) { + case DOLPHIN_HP_PIN_NID: + case DOLPHIN_LO_PIN_NID: + if (nid == DOLPHIN_LO_PIN_NID) + cs42l42 = spec->scodecs[CS8409_CODEC1]; + if (verb == AC_VERB_GET_PIN_SENSE) { + *res = (cs42l42->hp_jack_in) ? AC_PINSENSE_PRESENCE : 0; + return 0; + } + break; + case DOLPHIN_AMIC_PIN_NID: + if (verb == AC_VERB_GET_PIN_SENSE) { + *res = (cs42l42->mic_jack_in) ? AC_PINSENSE_PRESENCE : 0; + return 0; + } + break; + default: + break; + } + + return spec->exec_verb(dev, cmd, flags, res); +} + +void dolphin_fixups(struct hda_codec *codec, const struct hda_fixup *fix, int action) +{ + struct cs8409_spec *spec = codec->spec; + struct snd_kcontrol_new *kctrl; + int i; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + snd_hda_add_verbs(codec, dolphin_init_verbs); + /* verb exec op override */ + spec->exec_verb = codec->core.exec_verb; + codec->core.exec_verb = dolphin_exec_verb; + + spec->scodecs[CS8409_CODEC0] = &dolphin_cs42l42_0; + spec->scodecs[CS8409_CODEC0]->codec = codec; + spec->scodecs[CS8409_CODEC1] = &dolphin_cs42l42_1; + spec->scodecs[CS8409_CODEC1]->codec = codec; + spec->num_scodecs = 2; + + codec->patch_ops = cs8409_dolphin_patch_ops; + + /* GPIO 1,5 out, 0,4 in */ + spec->gpio_dir = spec->scodecs[CS8409_CODEC0]->reset_gpio | + spec->scodecs[CS8409_CODEC1]->reset_gpio; + spec->gpio_data = 0; + spec->gpio_mask = 0x03f; + + /* Basic initial sequence for specific hw configuration */ + snd_hda_sequence_write(codec, dolphin_init_verbs); + + snd_hda_jack_add_kctl(codec, DOLPHIN_LO_PIN_NID, "Line Out", true, + SND_JACK_HEADPHONE, NULL); + + snd_hda_jack_add_kctl(codec, DOLPHIN_AMIC_PIN_NID, "Microphone", true, + SND_JACK_MICROPHONE, NULL); + + cs8409_fix_caps(codec, DOLPHIN_HP_PIN_NID); + cs8409_fix_caps(codec, DOLPHIN_LO_PIN_NID); + cs8409_fix_caps(codec, DOLPHIN_AMIC_PIN_NID); + + spec->scodecs[CS8409_CODEC0]->full_scale_vol = CS42L42_FULL_SCALE_VOL_MINUS6DB; + spec->scodecs[CS8409_CODEC1]->full_scale_vol = CS42L42_FULL_SCALE_VOL_MINUS6DB; + + break; + case HDA_FIXUP_ACT_PROBE: + /* Fix Sample Rate to 48kHz */ + spec->gen.stream_analog_playback = &cs42l42_48k_pcm_analog_playback; + spec->gen.stream_analog_capture = &cs42l42_48k_pcm_analog_capture; + /* add hooks */ + spec->gen.pcm_playback_hook = cs42l42_playback_pcm_hook; + spec->gen.pcm_capture_hook = cs42l42_capture_pcm_hook; + snd_hda_gen_add_kctl(&spec->gen, "Headphone Playback Volume", + &cs42l42_dac_volume_mixer); + snd_hda_gen_add_kctl(&spec->gen, "Mic Capture Volume", &cs42l42_adc_volume_mixer); + kctrl = snd_hda_gen_add_kctl(&spec->gen, "Line Out Playback Volume", + &cs42l42_dac_volume_mixer); + /* Update Line Out kcontrol template */ + kctrl->private_value = HDA_COMPOSE_AMP_VAL_OFS(DOLPHIN_HP_PIN_NID, 3, CS8409_CODEC1, + HDA_OUTPUT, CS42L42_VOL_DAC) | HDA_AMP_VAL_MIN_MUTE; + cs8409_enable_ur(codec, 0); + snd_hda_codec_set_name(codec, "CS8409/CS42L42"); + break; + case HDA_FIXUP_ACT_INIT: + dolphin_hw_init(codec); + spec->init_done = 1; + if (spec->init_done && spec->build_ctrl_done) { + for (i = 0; i < spec->num_scodecs; i++) { + if (!spec->scodecs[i]->hp_jack_in) + cs42l42_run_jack_detect(spec->scodecs[i]); + } + } + break; + case HDA_FIXUP_ACT_BUILD: + spec->build_ctrl_done = 1; + /* Run jack auto detect first time on boot + * after controls have been added, to check if jack has + * been already plugged in. + * Run immediately after init. + */ + if (spec->init_done && spec->build_ctrl_done) { + for (i = 0; i < spec->num_scodecs; i++) { + if (!spec->scodecs[i]->hp_jack_in) + cs42l42_run_jack_detect(spec->scodecs[i]); + } + } + break; + default: + break; + } +} + +static int patch_cs8409(struct hda_codec *codec) +{ + int err; + + if (!cs8409_alloc_spec(codec)) + return -ENOMEM; + + snd_hda_pick_fixup(codec, cs8409_models, cs8409_fixup_tbl, cs8409_fixups); + + codec_dbg(codec, "Picked ID=%d, VID=%08x, DEV=%08x\n", codec->fixup_id, + codec->bus->pci->subsystem_vendor, + codec->bus->pci->subsystem_device); + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + err = cs8409_parse_auto_config(codec); + if (err < 0) { + cs8409_free(codec); + return err; + } + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + return 0; +} + +static const struct hda_device_id snd_hda_id_cs8409[] = { + HDA_CODEC_ENTRY(0x10138409, "CS8409", patch_cs8409), + {} /* terminator */ +}; +MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_cs8409); + +static struct hda_codec_driver cs8409_driver = { + .id = snd_hda_id_cs8409, +}; +module_hda_codec_driver(cs8409_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Cirrus Logic HDA bridge"); diff --git a/sound/pci/hda/patch_cs8409.h b/sound/pci/hda/patch_cs8409.h new file mode 100644 index 000000000..937e9387a --- /dev/null +++ b/sound/pci/hda/patch_cs8409.h @@ -0,0 +1,374 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * HD audio interface patch for Cirrus Logic CS8409 HDA bridge chip + * + * Copyright (C) 2021 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#ifndef __CS8409_PATCH_H +#define __CS8409_PATCH_H + +#include <linux/pci.h> +#include <sound/tlv.h> +#include <linux/workqueue.h> +#include <sound/cs42l42.h> +#include <sound/hda_codec.h> +#include "hda_local.h" +#include "hda_auto_parser.h" +#include "hda_jack.h" +#include "hda_generic.h" + +/* CS8409 Specific Definitions */ + +enum cs8409_pins { + CS8409_PIN_ROOT, + CS8409_PIN_AFG, + CS8409_PIN_ASP1_OUT_A, + CS8409_PIN_ASP1_OUT_B, + CS8409_PIN_ASP1_OUT_C, + CS8409_PIN_ASP1_OUT_D, + CS8409_PIN_ASP1_OUT_E, + CS8409_PIN_ASP1_OUT_F, + CS8409_PIN_ASP1_OUT_G, + CS8409_PIN_ASP1_OUT_H, + CS8409_PIN_ASP2_OUT_A, + CS8409_PIN_ASP2_OUT_B, + CS8409_PIN_ASP2_OUT_C, + CS8409_PIN_ASP2_OUT_D, + CS8409_PIN_ASP2_OUT_E, + CS8409_PIN_ASP2_OUT_F, + CS8409_PIN_ASP2_OUT_G, + CS8409_PIN_ASP2_OUT_H, + CS8409_PIN_ASP1_IN_A, + CS8409_PIN_ASP1_IN_B, + CS8409_PIN_ASP1_IN_C, + CS8409_PIN_ASP1_IN_D, + CS8409_PIN_ASP1_IN_E, + CS8409_PIN_ASP1_IN_F, + CS8409_PIN_ASP1_IN_G, + CS8409_PIN_ASP1_IN_H, + CS8409_PIN_ASP2_IN_A, + CS8409_PIN_ASP2_IN_B, + CS8409_PIN_ASP2_IN_C, + CS8409_PIN_ASP2_IN_D, + CS8409_PIN_ASP2_IN_E, + CS8409_PIN_ASP2_IN_F, + CS8409_PIN_ASP2_IN_G, + CS8409_PIN_ASP2_IN_H, + CS8409_PIN_DMIC1, + CS8409_PIN_DMIC2, + CS8409_PIN_ASP1_TRANSMITTER_A, + CS8409_PIN_ASP1_TRANSMITTER_B, + CS8409_PIN_ASP1_TRANSMITTER_C, + CS8409_PIN_ASP1_TRANSMITTER_D, + CS8409_PIN_ASP1_TRANSMITTER_E, + CS8409_PIN_ASP1_TRANSMITTER_F, + CS8409_PIN_ASP1_TRANSMITTER_G, + CS8409_PIN_ASP1_TRANSMITTER_H, + CS8409_PIN_ASP2_TRANSMITTER_A, + CS8409_PIN_ASP2_TRANSMITTER_B, + CS8409_PIN_ASP2_TRANSMITTER_C, + CS8409_PIN_ASP2_TRANSMITTER_D, + CS8409_PIN_ASP2_TRANSMITTER_E, + CS8409_PIN_ASP2_TRANSMITTER_F, + CS8409_PIN_ASP2_TRANSMITTER_G, + CS8409_PIN_ASP2_TRANSMITTER_H, + CS8409_PIN_ASP1_RECEIVER_A, + CS8409_PIN_ASP1_RECEIVER_B, + CS8409_PIN_ASP1_RECEIVER_C, + CS8409_PIN_ASP1_RECEIVER_D, + CS8409_PIN_ASP1_RECEIVER_E, + CS8409_PIN_ASP1_RECEIVER_F, + CS8409_PIN_ASP1_RECEIVER_G, + CS8409_PIN_ASP1_RECEIVER_H, + CS8409_PIN_ASP2_RECEIVER_A, + CS8409_PIN_ASP2_RECEIVER_B, + CS8409_PIN_ASP2_RECEIVER_C, + CS8409_PIN_ASP2_RECEIVER_D, + CS8409_PIN_ASP2_RECEIVER_E, + CS8409_PIN_ASP2_RECEIVER_F, + CS8409_PIN_ASP2_RECEIVER_G, + CS8409_PIN_ASP2_RECEIVER_H, + CS8409_PIN_DMIC1_IN, + CS8409_PIN_DMIC2_IN, + CS8409_PIN_BEEP_GEN, + CS8409_PIN_VENDOR_WIDGET +}; + +enum cs8409_coefficient_index_registers { + CS8409_DEV_CFG1, + CS8409_DEV_CFG2, + CS8409_DEV_CFG3, + CS8409_ASP1_CLK_CTRL1, + CS8409_ASP1_CLK_CTRL2, + CS8409_ASP1_CLK_CTRL3, + CS8409_ASP2_CLK_CTRL1, + CS8409_ASP2_CLK_CTRL2, + CS8409_ASP2_CLK_CTRL3, + CS8409_DMIC_CFG, + CS8409_BEEP_CFG, + ASP1_RX_NULL_INS_RMV, + ASP1_Rx_RATE1, + ASP1_Rx_RATE2, + ASP1_Tx_NULL_INS_RMV, + ASP1_Tx_RATE1, + ASP1_Tx_RATE2, + ASP2_Rx_NULL_INS_RMV, + ASP2_Rx_RATE1, + ASP2_Rx_RATE2, + ASP2_Tx_NULL_INS_RMV, + ASP2_Tx_RATE1, + ASP2_Tx_RATE2, + ASP1_SYNC_CTRL, + ASP2_SYNC_CTRL, + ASP1_A_TX_CTRL1, + ASP1_A_TX_CTRL2, + ASP1_B_TX_CTRL1, + ASP1_B_TX_CTRL2, + ASP1_C_TX_CTRL1, + ASP1_C_TX_CTRL2, + ASP1_D_TX_CTRL1, + ASP1_D_TX_CTRL2, + ASP1_E_TX_CTRL1, + ASP1_E_TX_CTRL2, + ASP1_F_TX_CTRL1, + ASP1_F_TX_CTRL2, + ASP1_G_TX_CTRL1, + ASP1_G_TX_CTRL2, + ASP1_H_TX_CTRL1, + ASP1_H_TX_CTRL2, + ASP2_A_TX_CTRL1, + ASP2_A_TX_CTRL2, + ASP2_B_TX_CTRL1, + ASP2_B_TX_CTRL2, + ASP2_C_TX_CTRL1, + ASP2_C_TX_CTRL2, + ASP2_D_TX_CTRL1, + ASP2_D_TX_CTRL2, + ASP2_E_TX_CTRL1, + ASP2_E_TX_CTRL2, + ASP2_F_TX_CTRL1, + ASP2_F_TX_CTRL2, + ASP2_G_TX_CTRL1, + ASP2_G_TX_CTRL2, + ASP2_H_TX_CTRL1, + ASP2_H_TX_CTRL2, + ASP1_A_RX_CTRL1, + ASP1_A_RX_CTRL2, + ASP1_B_RX_CTRL1, + ASP1_B_RX_CTRL2, + ASP1_C_RX_CTRL1, + ASP1_C_RX_CTRL2, + ASP1_D_RX_CTRL1, + ASP1_D_RX_CTRL2, + ASP1_E_RX_CTRL1, + ASP1_E_RX_CTRL2, + ASP1_F_RX_CTRL1, + ASP1_F_RX_CTRL2, + ASP1_G_RX_CTRL1, + ASP1_G_RX_CTRL2, + ASP1_H_RX_CTRL1, + ASP1_H_RX_CTRL2, + ASP2_A_RX_CTRL1, + ASP2_A_RX_CTRL2, + ASP2_B_RX_CTRL1, + ASP2_B_RX_CTRL2, + ASP2_C_RX_CTRL1, + ASP2_C_RX_CTRL2, + ASP2_D_RX_CTRL1, + ASP2_D_RX_CTRL2, + ASP2_E_RX_CTRL1, + ASP2_E_RX_CTRL2, + ASP2_F_RX_CTRL1, + ASP2_F_RX_CTRL2, + ASP2_G_RX_CTRL1, + ASP2_G_RX_CTRL2, + ASP2_H_RX_CTRL1, + ASP2_H_RX_CTRL2, + CS8409_I2C_ADDR, + CS8409_I2C_DATA, + CS8409_I2C_CTRL, + CS8409_I2C_STS, + CS8409_I2C_QWRITE, + CS8409_I2C_QREAD, + CS8409_SPI_CTRL, + CS8409_SPI_TX_DATA, + CS8409_SPI_RX_DATA, + CS8409_SPI_STS, + CS8409_PFE_COEF_W1, /* Parametric filter engine coefficient write 1*/ + CS8409_PFE_COEF_W2, + CS8409_PFE_CTRL1, + CS8409_PFE_CTRL2, + CS8409_PRE_SCALE_ATTN1, + CS8409_PRE_SCALE_ATTN2, + CS8409_PFE_COEF_MON1, /* Parametric filter engine coefficient monitor 1*/ + CS8409_PFE_COEF_MON2, + CS8409_ASP1_INTRN_STS, + CS8409_ASP2_INTRN_STS, + CS8409_ASP1_RX_SCLK_COUNT, + CS8409_ASP1_TX_SCLK_COUNT, + CS8409_ASP2_RX_SCLK_COUNT, + CS8409_ASP2_TX_SCLK_COUNT, + CS8409_ASP_UNS_RESP_MASK, + CS8409_LOOPBACK_CTRL = 0x80, + CS8409_PAD_CFG_SLW_RATE_CTRL = 0x82, /* Pad Config and Slew Rate Control (CIR = 0x0082) */ +}; + +/* CS42L42 Specific Definitions */ + +#define CS8409_MAX_CODECS 8 +#define CS42L42_VOLUMES (4U) +#define CS42L42_HP_VOL_REAL_MIN (-63) +#define CS42L42_HP_VOL_REAL_MAX (0) +#define CS42L42_AMIC_VOL_REAL_MIN (-97) +#define CS42L42_AMIC_VOL_REAL_MAX (12) +#define CS42L42_REG_AMIC_VOL_MASK (0x00FF) +#define CS42L42_HSTYPE_MASK (0x03) +#define CS42L42_I2C_TIMEOUT_US (20000) +#define CS42L42_I2C_SLEEP_US (2000) +#define CS42L42_PDN_TIMEOUT_US (250000) +#define CS42L42_PDN_SLEEP_US (2000) +#define CS42L42_INIT_TIMEOUT_MS (45) +#define CS42L42_FULL_SCALE_VOL_MASK (2) +#define CS42L42_FULL_SCALE_VOL_0DB (1) +#define CS42L42_FULL_SCALE_VOL_MINUS6DB (0) + +/* Dell BULLSEYE / WARLOCK / CYBORG Specific Definitions */ + +#define CS42L42_I2C_ADDR (0x48 << 1) +#define CS8409_CS42L42_RESET GENMASK(5, 5) /* CS8409_GPIO5 */ +#define CS8409_CS42L42_INT GENMASK(4, 4) /* CS8409_GPIO4 */ +#define CS8409_CYBORG_SPEAKER_PDN GENMASK(2, 2) /* CS8409_GPIO2 */ +#define CS8409_WARLOCK_SPEAKER_PDN GENMASK(1, 1) /* CS8409_GPIO1 */ +#define CS8409_CS42L42_HP_PIN_NID CS8409_PIN_ASP1_TRANSMITTER_A +#define CS8409_CS42L42_SPK_PIN_NID CS8409_PIN_ASP2_TRANSMITTER_A +#define CS8409_CS42L42_AMIC_PIN_NID CS8409_PIN_ASP1_RECEIVER_A +#define CS8409_CS42L42_DMIC_PIN_NID CS8409_PIN_DMIC1_IN +#define CS8409_CS42L42_DMIC_ADC_PIN_NID CS8409_PIN_DMIC1 + +/* Dolphin */ + +#define DOLPHIN_C0_I2C_ADDR (0x48 << 1) +#define DOLPHIN_C1_I2C_ADDR (0x49 << 1) +#define DOLPHIN_HP_PIN_NID CS8409_PIN_ASP1_TRANSMITTER_A +#define DOLPHIN_LO_PIN_NID CS8409_PIN_ASP1_TRANSMITTER_B +#define DOLPHIN_AMIC_PIN_NID CS8409_PIN_ASP1_RECEIVER_A + +#define DOLPHIN_C0_INT GENMASK(4, 4) +#define DOLPHIN_C1_INT GENMASK(0, 0) +#define DOLPHIN_C0_RESET GENMASK(5, 5) +#define DOLPHIN_C1_RESET GENMASK(1, 1) +#define DOLPHIN_WAKE (DOLPHIN_C0_INT | DOLPHIN_C1_INT) + +enum { + CS8409_BULLSEYE, + CS8409_WARLOCK, + CS8409_WARLOCK_MLK, + CS8409_WARLOCK_MLK_DUAL_MIC, + CS8409_CYBORG, + CS8409_FIXUPS, + CS8409_DOLPHIN, + CS8409_DOLPHIN_FIXUPS, + CS8409_ODIN, +}; + +enum { + CS8409_CODEC0, + CS8409_CODEC1 +}; + +enum { + CS42L42_VOL_ADC, + CS42L42_VOL_DAC, +}; + +#define CS42L42_ADC_VOL_OFFSET (CS42L42_VOL_ADC) +#define CS42L42_DAC_CH0_VOL_OFFSET (CS42L42_VOL_DAC) +#define CS42L42_DAC_CH1_VOL_OFFSET (CS42L42_VOL_DAC + 1) + +struct cs8409_i2c_param { + unsigned int addr; + unsigned int value; +}; + +struct cs8409_cir_param { + unsigned int nid; + unsigned int cir; + unsigned int coeff; +}; + +struct sub_codec { + struct hda_codec *codec; + unsigned int addr; + unsigned int reset_gpio; + unsigned int irq_mask; + const struct cs8409_i2c_param *init_seq; + unsigned int init_seq_num; + + unsigned int hp_jack_in:1; + unsigned int mic_jack_in:1; + unsigned int suspended:1; + unsigned int paged:1; + unsigned int last_page; + unsigned int hsbias_hiz; + unsigned int full_scale_vol:1; + unsigned int no_type_dect:1; + + s8 vol[CS42L42_VOLUMES]; +}; + +struct cs8409_spec { + struct hda_gen_spec gen; + struct hda_codec *codec; + + struct sub_codec *scodecs[CS8409_MAX_CODECS]; + unsigned int num_scodecs; + + unsigned int gpio_mask; + unsigned int gpio_dir; + unsigned int gpio_data; + + int speaker_pdn_gpio; + + struct mutex i2c_mux; + unsigned int i2c_clck_enabled; + unsigned int dev_addr; + struct delayed_work i2c_clk_work; + + unsigned int playback_started:1; + unsigned int capture_started:1; + unsigned int init_done:1; + unsigned int build_ctrl_done:1; + + /* verb exec op override */ + int (*exec_verb)(struct hdac_device *dev, unsigned int cmd, unsigned int flags, + unsigned int *res); +}; + +extern const struct snd_kcontrol_new cs42l42_dac_volume_mixer; +extern const struct snd_kcontrol_new cs42l42_adc_volume_mixer; + +int cs42l42_volume_info(struct snd_kcontrol *kctrl, struct snd_ctl_elem_info *uinfo); +int cs42l42_volume_get(struct snd_kcontrol *kctrl, struct snd_ctl_elem_value *uctrl); +int cs42l42_volume_put(struct snd_kcontrol *kctrl, struct snd_ctl_elem_value *uctrl); + +extern const struct hda_pcm_stream cs42l42_48k_pcm_analog_playback; +extern const struct hda_pcm_stream cs42l42_48k_pcm_analog_capture; +extern const struct snd_pci_quirk cs8409_fixup_tbl[]; +extern const struct hda_model_fixup cs8409_models[]; +extern const struct hda_fixup cs8409_fixups[]; +extern const struct hda_verb cs8409_cs42l42_init_verbs[]; +extern const struct cs8409_cir_param cs8409_cs42l42_hw_cfg[]; +extern const struct cs8409_cir_param cs8409_cs42l42_bullseye_atn[]; +extern struct sub_codec cs8409_cs42l42_codec; + +extern const struct hda_verb dolphin_init_verbs[]; +extern const struct cs8409_cir_param dolphin_hw_cfg[]; +extern struct sub_codec dolphin_cs42l42_0; +extern struct sub_codec dolphin_cs42l42_1; + +void cs8409_cs42l42_fixups(struct hda_codec *codec, const struct hda_fixup *fix, int action); +void dolphin_fixups(struct hda_codec *codec, const struct hda_fixup *fix, int action); + +#endif diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c new file mode 100644 index 000000000..f460ac80c --- /dev/null +++ b/sound/pci/hda/patch_hdmi.c @@ -0,0 +1,4663 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * patch_hdmi.c - routines for HDMI/DisplayPort codecs + * + * Copyright(c) 2008-2010 Intel Corporation. All rights reserved. + * Copyright (c) 2006 ATI Technologies Inc. + * Copyright (c) 2008 NVIDIA Corp. All rights reserved. + * Copyright (c) 2008 Wei Ni <wni@nvidia.com> + * Copyright (c) 2013 Anssi Hannula <anssi.hannula@iki.fi> + * + * Authors: + * Wu Fengguang <wfg@linux.intel.com> + * + * Maintained by: + * Wu Fengguang <wfg@linux.intel.com> + */ + +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/asoundef.h> +#include <sound/tlv.h> +#include <sound/hdaudio.h> +#include <sound/hda_i915.h> +#include <sound/hda_chmap.h> +#include <sound/hda_codec.h> +#include "hda_local.h" +#include "hda_jack.h" +#include "hda_controller.h" + +static bool static_hdmi_pcm; +module_param(static_hdmi_pcm, bool, 0644); +MODULE_PARM_DESC(static_hdmi_pcm, "Don't restrict PCM parameters per ELD info"); + +static bool enable_acomp = true; +module_param(enable_acomp, bool, 0444); +MODULE_PARM_DESC(enable_acomp, "Enable audio component binding (default=yes)"); + +static bool enable_silent_stream = +IS_ENABLED(CONFIG_SND_HDA_INTEL_HDMI_SILENT_STREAM); +module_param(enable_silent_stream, bool, 0644); +MODULE_PARM_DESC(enable_silent_stream, "Enable Silent Stream for HDMI devices"); + +static bool enable_all_pins; +module_param(enable_all_pins, bool, 0444); +MODULE_PARM_DESC(enable_all_pins, "Forcibly enable all pins"); + +struct hdmi_spec_per_cvt { + hda_nid_t cvt_nid; + bool assigned; /* the stream has been assigned */ + bool silent_stream; /* silent stream activated */ + unsigned int channels_min; + unsigned int channels_max; + u32 rates; + u64 formats; + unsigned int maxbps; +}; + +/* max. connections to a widget */ +#define HDA_MAX_CONNECTIONS 32 + +struct hdmi_spec_per_pin { + hda_nid_t pin_nid; + int dev_id; + /* pin idx, different device entries on the same pin use the same idx */ + int pin_nid_idx; + int num_mux_nids; + hda_nid_t mux_nids[HDA_MAX_CONNECTIONS]; + int mux_idx; + hda_nid_t cvt_nid; + + struct hda_codec *codec; + struct hdmi_eld sink_eld; + struct mutex lock; + struct delayed_work work; + struct hdmi_pcm *pcm; /* pointer to spec->pcm_rec[n] dynamically*/ + int pcm_idx; /* which pcm is attached. -1 means no pcm is attached */ + int prev_pcm_idx; /* previously assigned pcm index */ + int repoll_count; + bool setup; /* the stream has been set up by prepare callback */ + bool silent_stream; + int channels; /* current number of channels */ + bool non_pcm; + bool chmap_set; /* channel-map override by ALSA API? */ + unsigned char chmap[8]; /* ALSA API channel-map */ +#ifdef CONFIG_SND_PROC_FS + struct snd_info_entry *proc_entry; +#endif +}; + +/* operations used by generic code that can be overridden by patches */ +struct hdmi_ops { + int (*pin_get_eld)(struct hda_codec *codec, hda_nid_t pin_nid, + int dev_id, unsigned char *buf, int *eld_size); + + void (*pin_setup_infoframe)(struct hda_codec *codec, hda_nid_t pin_nid, + int dev_id, + int ca, int active_channels, int conn_type); + + /* enable/disable HBR (HD passthrough) */ + int (*pin_hbr_setup)(struct hda_codec *codec, hda_nid_t pin_nid, + int dev_id, bool hbr); + + int (*setup_stream)(struct hda_codec *codec, hda_nid_t cvt_nid, + hda_nid_t pin_nid, int dev_id, u32 stream_tag, + int format); + + void (*pin_cvt_fixup)(struct hda_codec *codec, + struct hdmi_spec_per_pin *per_pin, + hda_nid_t cvt_nid); +}; + +struct hdmi_pcm { + struct hda_pcm *pcm; + struct snd_jack *jack; + struct snd_kcontrol *eld_ctl; +}; + +enum { + SILENT_STREAM_OFF = 0, + SILENT_STREAM_KAE, /* use standard HDA Keep-Alive */ + SILENT_STREAM_I915, /* Intel i915 extension */ +}; + +struct hdmi_spec { + struct hda_codec *codec; + int num_cvts; + struct snd_array cvts; /* struct hdmi_spec_per_cvt */ + hda_nid_t cvt_nids[4]; /* only for haswell fix */ + + /* + * num_pins is the number of virtual pins + * for example, there are 3 pins, and each pin + * has 4 device entries, then the num_pins is 12 + */ + int num_pins; + /* + * num_nids is the number of real pins + * In the above example, num_nids is 3 + */ + int num_nids; + /* + * dev_num is the number of device entries + * on each pin. + * In the above example, dev_num is 4 + */ + int dev_num; + struct snd_array pins; /* struct hdmi_spec_per_pin */ + struct hdmi_pcm pcm_rec[8]; + struct mutex pcm_lock; + struct mutex bind_lock; /* for audio component binding */ + /* pcm_bitmap means which pcms have been assigned to pins*/ + unsigned long pcm_bitmap; + int pcm_used; /* counter of pcm_rec[] */ + /* bitmap shows whether the pcm is opened in user space + * bit 0 means the first playback PCM (PCM3); + * bit 1 means the second playback PCM, and so on. + */ + unsigned long pcm_in_use; + + struct hdmi_eld temp_eld; + struct hdmi_ops ops; + + bool dyn_pin_out; + bool static_pcm_mapping; + /* hdmi interrupt trigger control flag for Nvidia codec */ + bool hdmi_intr_trig_ctrl; + bool nv_dp_workaround; /* workaround DP audio infoframe for Nvidia */ + + bool intel_hsw_fixup; /* apply Intel platform-specific fixups */ + /* + * Non-generic VIA/NVIDIA specific + */ + struct hda_multi_out multiout; + struct hda_pcm_stream pcm_playback; + + bool use_acomp_notifier; /* use eld_notify callback for hotplug */ + bool acomp_registered; /* audio component registered in this driver */ + bool force_connect; /* force connectivity */ + struct drm_audio_component_audio_ops drm_audio_ops; + int (*port2pin)(struct hda_codec *, int); /* reverse port/pin mapping */ + + struct hdac_chmap chmap; + hda_nid_t vendor_nid; + const int *port_map; + int port_num; + int silent_stream_type; +}; + +#ifdef CONFIG_SND_HDA_COMPONENT +static inline bool codec_has_acomp(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + return spec->use_acomp_notifier; +} +#else +#define codec_has_acomp(codec) false +#endif + +struct hdmi_audio_infoframe { + u8 type; /* 0x84 */ + u8 ver; /* 0x01 */ + u8 len; /* 0x0a */ + + u8 checksum; + + u8 CC02_CT47; /* CC in bits 0:2, CT in 4:7 */ + u8 SS01_SF24; + u8 CXT04; + u8 CA; + u8 LFEPBL01_LSV36_DM_INH7; +}; + +struct dp_audio_infoframe { + u8 type; /* 0x84 */ + u8 len; /* 0x1b */ + u8 ver; /* 0x11 << 2 */ + + u8 CC02_CT47; /* match with HDMI infoframe from this on */ + u8 SS01_SF24; + u8 CXT04; + u8 CA; + u8 LFEPBL01_LSV36_DM_INH7; +}; + +union audio_infoframe { + struct hdmi_audio_infoframe hdmi; + struct dp_audio_infoframe dp; + DECLARE_FLEX_ARRAY(u8, bytes); +}; + +/* + * HDMI routines + */ + +#define get_pin(spec, idx) \ + ((struct hdmi_spec_per_pin *)snd_array_elem(&spec->pins, idx)) +#define get_cvt(spec, idx) \ + ((struct hdmi_spec_per_cvt *)snd_array_elem(&spec->cvts, idx)) +/* obtain hdmi_pcm object assigned to idx */ +#define get_hdmi_pcm(spec, idx) (&(spec)->pcm_rec[idx]) +/* obtain hda_pcm object assigned to idx */ +#define get_pcm_rec(spec, idx) (get_hdmi_pcm(spec, idx)->pcm) + +static int pin_id_to_pin_index(struct hda_codec *codec, + hda_nid_t pin_nid, int dev_id) +{ + struct hdmi_spec *spec = codec->spec; + int pin_idx; + struct hdmi_spec_per_pin *per_pin; + + /* + * (dev_id == -1) means it is NON-MST pin + * return the first virtual pin on this port + */ + if (dev_id == -1) + dev_id = 0; + + for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { + per_pin = get_pin(spec, pin_idx); + if ((per_pin->pin_nid == pin_nid) && + (per_pin->dev_id == dev_id)) + return pin_idx; + } + + codec_warn(codec, "HDMI: pin NID 0x%x not registered\n", pin_nid); + return -EINVAL; +} + +static int hinfo_to_pcm_index(struct hda_codec *codec, + struct hda_pcm_stream *hinfo) +{ + struct hdmi_spec *spec = codec->spec; + int pcm_idx; + + for (pcm_idx = 0; pcm_idx < spec->pcm_used; pcm_idx++) + if (get_pcm_rec(spec, pcm_idx)->stream == hinfo) + return pcm_idx; + + codec_warn(codec, "HDMI: hinfo %p not tied to a PCM\n", hinfo); + return -EINVAL; +} + +static int hinfo_to_pin_index(struct hda_codec *codec, + struct hda_pcm_stream *hinfo) +{ + struct hdmi_spec *spec = codec->spec; + struct hdmi_spec_per_pin *per_pin; + int pin_idx; + + for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { + per_pin = get_pin(spec, pin_idx); + if (per_pin->pcm && + per_pin->pcm->pcm->stream == hinfo) + return pin_idx; + } + + codec_dbg(codec, "HDMI: hinfo %p (pcm %d) not registered\n", hinfo, + hinfo_to_pcm_index(codec, hinfo)); + return -EINVAL; +} + +static struct hdmi_spec_per_pin *pcm_idx_to_pin(struct hdmi_spec *spec, + int pcm_idx) +{ + int i; + struct hdmi_spec_per_pin *per_pin; + + for (i = 0; i < spec->num_pins; i++) { + per_pin = get_pin(spec, i); + if (per_pin->pcm_idx == pcm_idx) + return per_pin; + } + return NULL; +} + +static int cvt_nid_to_cvt_index(struct hda_codec *codec, hda_nid_t cvt_nid) +{ + struct hdmi_spec *spec = codec->spec; + int cvt_idx; + + for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++) + if (get_cvt(spec, cvt_idx)->cvt_nid == cvt_nid) + return cvt_idx; + + codec_warn(codec, "HDMI: cvt NID 0x%x not registered\n", cvt_nid); + return -EINVAL; +} + +static int hdmi_eld_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hdmi_spec *spec = codec->spec; + struct hdmi_spec_per_pin *per_pin; + struct hdmi_eld *eld; + int pcm_idx; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + + pcm_idx = kcontrol->private_value; + mutex_lock(&spec->pcm_lock); + per_pin = pcm_idx_to_pin(spec, pcm_idx); + if (!per_pin) { + /* no pin is bound to the pcm */ + uinfo->count = 0; + goto unlock; + } + eld = &per_pin->sink_eld; + uinfo->count = eld->eld_valid ? eld->eld_size : 0; + + unlock: + mutex_unlock(&spec->pcm_lock); + return 0; +} + +static int hdmi_eld_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hdmi_spec *spec = codec->spec; + struct hdmi_spec_per_pin *per_pin; + struct hdmi_eld *eld; + int pcm_idx; + int err = 0; + + pcm_idx = kcontrol->private_value; + mutex_lock(&spec->pcm_lock); + per_pin = pcm_idx_to_pin(spec, pcm_idx); + if (!per_pin) { + /* no pin is bound to the pcm */ + memset(ucontrol->value.bytes.data, 0, + ARRAY_SIZE(ucontrol->value.bytes.data)); + goto unlock; + } + + eld = &per_pin->sink_eld; + if (eld->eld_size > ARRAY_SIZE(ucontrol->value.bytes.data) || + eld->eld_size > ELD_MAX_SIZE) { + snd_BUG(); + err = -EINVAL; + goto unlock; + } + + memset(ucontrol->value.bytes.data, 0, + ARRAY_SIZE(ucontrol->value.bytes.data)); + if (eld->eld_valid) + memcpy(ucontrol->value.bytes.data, eld->eld_buffer, + eld->eld_size); + + unlock: + mutex_unlock(&spec->pcm_lock); + return err; +} + +static const struct snd_kcontrol_new eld_bytes_ctl = { + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE | + SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "ELD", + .info = hdmi_eld_ctl_info, + .get = hdmi_eld_ctl_get, +}; + +static int hdmi_create_eld_ctl(struct hda_codec *codec, int pcm_idx, + int device) +{ + struct snd_kcontrol *kctl; + struct hdmi_spec *spec = codec->spec; + int err; + + kctl = snd_ctl_new1(&eld_bytes_ctl, codec); + if (!kctl) + return -ENOMEM; + kctl->private_value = pcm_idx; + kctl->id.device = device; + + /* no pin nid is associated with the kctl now + * tbd: associate pin nid to eld ctl later + */ + err = snd_hda_ctl_add(codec, 0, kctl); + if (err < 0) + return err; + + get_hdmi_pcm(spec, pcm_idx)->eld_ctl = kctl; + return 0; +} + +#ifdef BE_PARANOID +static void hdmi_get_dip_index(struct hda_codec *codec, hda_nid_t pin_nid, + int *packet_index, int *byte_index) +{ + int val; + + val = snd_hda_codec_read(codec, pin_nid, 0, + AC_VERB_GET_HDMI_DIP_INDEX, 0); + + *packet_index = val >> 5; + *byte_index = val & 0x1f; +} +#endif + +static void hdmi_set_dip_index(struct hda_codec *codec, hda_nid_t pin_nid, + int packet_index, int byte_index) +{ + int val; + + val = (packet_index << 5) | (byte_index & 0x1f); + + snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_HDMI_DIP_INDEX, val); +} + +static void hdmi_write_dip_byte(struct hda_codec *codec, hda_nid_t pin_nid, + unsigned char val) +{ + snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_HDMI_DIP_DATA, val); +} + +static void hdmi_init_pin(struct hda_codec *codec, hda_nid_t pin_nid) +{ + struct hdmi_spec *spec = codec->spec; + int pin_out; + + /* Unmute */ + if (get_wcaps(codec, pin_nid) & AC_WCAP_OUT_AMP) + snd_hda_codec_write(codec, pin_nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE); + + if (spec->dyn_pin_out) + /* Disable pin out until stream is active */ + pin_out = 0; + else + /* Enable pin out: some machines with GM965 gets broken output + * when the pin is disabled or changed while using with HDMI + */ + pin_out = PIN_OUT; + + snd_hda_codec_write(codec, pin_nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, pin_out); +} + +/* + * ELD proc files + */ + +#ifdef CONFIG_SND_PROC_FS +static void print_eld_info(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct hdmi_spec_per_pin *per_pin = entry->private_data; + + mutex_lock(&per_pin->lock); + snd_hdmi_print_eld_info(&per_pin->sink_eld, buffer, per_pin->pin_nid, + per_pin->dev_id, per_pin->cvt_nid); + mutex_unlock(&per_pin->lock); +} + +static void write_eld_info(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct hdmi_spec_per_pin *per_pin = entry->private_data; + + mutex_lock(&per_pin->lock); + snd_hdmi_write_eld_info(&per_pin->sink_eld, buffer); + mutex_unlock(&per_pin->lock); +} + +static int eld_proc_new(struct hdmi_spec_per_pin *per_pin, int index) +{ + char name[32]; + struct hda_codec *codec = per_pin->codec; + struct snd_info_entry *entry; + int err; + + snprintf(name, sizeof(name), "eld#%d.%d", codec->addr, index); + err = snd_card_proc_new(codec->card, name, &entry); + if (err < 0) + return err; + + snd_info_set_text_ops(entry, per_pin, print_eld_info); + entry->c.text.write = write_eld_info; + entry->mode |= 0200; + per_pin->proc_entry = entry; + + return 0; +} + +static void eld_proc_free(struct hdmi_spec_per_pin *per_pin) +{ + if (!per_pin->codec->bus->shutdown) { + snd_info_free_entry(per_pin->proc_entry); + per_pin->proc_entry = NULL; + } +} +#else +static inline int eld_proc_new(struct hdmi_spec_per_pin *per_pin, + int index) +{ + return 0; +} +static inline void eld_proc_free(struct hdmi_spec_per_pin *per_pin) +{ +} +#endif + +/* + * Audio InfoFrame routines + */ + +/* + * Enable Audio InfoFrame Transmission + */ +static void hdmi_start_infoframe_trans(struct hda_codec *codec, + hda_nid_t pin_nid) +{ + hdmi_set_dip_index(codec, pin_nid, 0x0, 0x0); + snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_HDMI_DIP_XMIT, + AC_DIPXMIT_BEST); +} + +/* + * Disable Audio InfoFrame Transmission + */ +static void hdmi_stop_infoframe_trans(struct hda_codec *codec, + hda_nid_t pin_nid) +{ + hdmi_set_dip_index(codec, pin_nid, 0x0, 0x0); + snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_HDMI_DIP_XMIT, + AC_DIPXMIT_DISABLE); +} + +static void hdmi_debug_dip_size(struct hda_codec *codec, hda_nid_t pin_nid) +{ +#ifdef CONFIG_SND_DEBUG_VERBOSE + int i; + int size; + + size = snd_hdmi_get_eld_size(codec, pin_nid); + codec_dbg(codec, "HDMI: ELD buf size is %d\n", size); + + for (i = 0; i < 8; i++) { + size = snd_hda_codec_read(codec, pin_nid, 0, + AC_VERB_GET_HDMI_DIP_SIZE, i); + codec_dbg(codec, "HDMI: DIP GP[%d] buf size is %d\n", i, size); + } +#endif +} + +static void hdmi_clear_dip_buffers(struct hda_codec *codec, hda_nid_t pin_nid) +{ +#ifdef BE_PARANOID + int i, j; + int size; + int pi, bi; + for (i = 0; i < 8; i++) { + size = snd_hda_codec_read(codec, pin_nid, 0, + AC_VERB_GET_HDMI_DIP_SIZE, i); + if (size == 0) + continue; + + hdmi_set_dip_index(codec, pin_nid, i, 0x0); + for (j = 1; j < 1000; j++) { + hdmi_write_dip_byte(codec, pin_nid, 0x0); + hdmi_get_dip_index(codec, pin_nid, &pi, &bi); + if (pi != i) + codec_dbg(codec, "dip index %d: %d != %d\n", + bi, pi, i); + if (bi == 0) /* byte index wrapped around */ + break; + } + codec_dbg(codec, + "HDMI: DIP GP[%d] buf reported size=%d, written=%d\n", + i, size, j); + } +#endif +} + +static void hdmi_checksum_audio_infoframe(struct hdmi_audio_infoframe *hdmi_ai) +{ + u8 *bytes = (u8 *)hdmi_ai; + u8 sum = 0; + int i; + + hdmi_ai->checksum = 0; + + for (i = 0; i < sizeof(*hdmi_ai); i++) + sum += bytes[i]; + + hdmi_ai->checksum = -sum; +} + +static void hdmi_fill_audio_infoframe(struct hda_codec *codec, + hda_nid_t pin_nid, + u8 *dip, int size) +{ + int i; + + hdmi_debug_dip_size(codec, pin_nid); + hdmi_clear_dip_buffers(codec, pin_nid); /* be paranoid */ + + hdmi_set_dip_index(codec, pin_nid, 0x0, 0x0); + for (i = 0; i < size; i++) + hdmi_write_dip_byte(codec, pin_nid, dip[i]); +} + +static bool hdmi_infoframe_uptodate(struct hda_codec *codec, hda_nid_t pin_nid, + u8 *dip, int size) +{ + u8 val; + int i; + + hdmi_set_dip_index(codec, pin_nid, 0x0, 0x0); + if (snd_hda_codec_read(codec, pin_nid, 0, AC_VERB_GET_HDMI_DIP_XMIT, 0) + != AC_DIPXMIT_BEST) + return false; + + for (i = 0; i < size; i++) { + val = snd_hda_codec_read(codec, pin_nid, 0, + AC_VERB_GET_HDMI_DIP_DATA, 0); + if (val != dip[i]) + return false; + } + + return true; +} + +static int hdmi_pin_get_eld(struct hda_codec *codec, hda_nid_t nid, + int dev_id, unsigned char *buf, int *eld_size) +{ + snd_hda_set_dev_select(codec, nid, dev_id); + + return snd_hdmi_get_eld(codec, nid, buf, eld_size); +} + +static void hdmi_pin_setup_infoframe(struct hda_codec *codec, + hda_nid_t pin_nid, int dev_id, + int ca, int active_channels, + int conn_type) +{ + struct hdmi_spec *spec = codec->spec; + union audio_infoframe ai; + + memset(&ai, 0, sizeof(ai)); + if ((conn_type == 0) || /* HDMI */ + /* Nvidia DisplayPort: Nvidia HW expects same layout as HDMI */ + (conn_type == 1 && spec->nv_dp_workaround)) { + struct hdmi_audio_infoframe *hdmi_ai = &ai.hdmi; + + if (conn_type == 0) { /* HDMI */ + hdmi_ai->type = 0x84; + hdmi_ai->ver = 0x01; + hdmi_ai->len = 0x0a; + } else {/* Nvidia DP */ + hdmi_ai->type = 0x84; + hdmi_ai->ver = 0x1b; + hdmi_ai->len = 0x11 << 2; + } + hdmi_ai->CC02_CT47 = active_channels - 1; + hdmi_ai->CA = ca; + hdmi_checksum_audio_infoframe(hdmi_ai); + } else if (conn_type == 1) { /* DisplayPort */ + struct dp_audio_infoframe *dp_ai = &ai.dp; + + dp_ai->type = 0x84; + dp_ai->len = 0x1b; + dp_ai->ver = 0x11 << 2; + dp_ai->CC02_CT47 = active_channels - 1; + dp_ai->CA = ca; + } else { + codec_dbg(codec, "HDMI: unknown connection type at pin NID 0x%x\n", pin_nid); + return; + } + + snd_hda_set_dev_select(codec, pin_nid, dev_id); + + /* + * sizeof(ai) is used instead of sizeof(*hdmi_ai) or + * sizeof(*dp_ai) to avoid partial match/update problems when + * the user switches between HDMI/DP monitors. + */ + if (!hdmi_infoframe_uptodate(codec, pin_nid, ai.bytes, + sizeof(ai))) { + codec_dbg(codec, "%s: pin NID=0x%x channels=%d ca=0x%02x\n", + __func__, pin_nid, active_channels, ca); + hdmi_stop_infoframe_trans(codec, pin_nid); + hdmi_fill_audio_infoframe(codec, pin_nid, + ai.bytes, sizeof(ai)); + hdmi_start_infoframe_trans(codec, pin_nid); + } +} + +static void hdmi_setup_audio_infoframe(struct hda_codec *codec, + struct hdmi_spec_per_pin *per_pin, + bool non_pcm) +{ + struct hdmi_spec *spec = codec->spec; + struct hdac_chmap *chmap = &spec->chmap; + hda_nid_t pin_nid = per_pin->pin_nid; + int dev_id = per_pin->dev_id; + int channels = per_pin->channels; + int active_channels; + struct hdmi_eld *eld; + int ca; + + if (!channels) + return; + + snd_hda_set_dev_select(codec, pin_nid, dev_id); + + /* some HW (e.g. HSW+) needs reprogramming the amp at each time */ + if (get_wcaps(codec, pin_nid) & AC_WCAP_OUT_AMP) + snd_hda_codec_write(codec, pin_nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_OUT_UNMUTE); + + eld = &per_pin->sink_eld; + + ca = snd_hdac_channel_allocation(&codec->core, + eld->info.spk_alloc, channels, + per_pin->chmap_set, non_pcm, per_pin->chmap); + + active_channels = snd_hdac_get_active_channels(ca); + + chmap->ops.set_channel_count(&codec->core, per_pin->cvt_nid, + active_channels); + + /* + * always configure channel mapping, it may have been changed by the + * user in the meantime + */ + snd_hdac_setup_channel_mapping(&spec->chmap, + pin_nid, non_pcm, ca, channels, + per_pin->chmap, per_pin->chmap_set); + + spec->ops.pin_setup_infoframe(codec, pin_nid, dev_id, + ca, active_channels, eld->info.conn_type); + + per_pin->non_pcm = non_pcm; +} + +/* + * Unsolicited events + */ + +static void hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll); + +static void check_presence_and_report(struct hda_codec *codec, hda_nid_t nid, + int dev_id) +{ + struct hdmi_spec *spec = codec->spec; + int pin_idx = pin_id_to_pin_index(codec, nid, dev_id); + + if (pin_idx < 0) + return; + mutex_lock(&spec->pcm_lock); + hdmi_present_sense(get_pin(spec, pin_idx), 1); + mutex_unlock(&spec->pcm_lock); +} + +static void jack_callback(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + /* stop polling when notification is enabled */ + if (codec_has_acomp(codec)) + return; + + check_presence_and_report(codec, jack->nid, jack->dev_id); +} + +static void hdmi_intrinsic_event(struct hda_codec *codec, unsigned int res, + struct hda_jack_tbl *jack) +{ + jack->jack_dirty = 1; + + codec_dbg(codec, + "HDMI hot plug event: Codec=%d NID=0x%x Device=%d Inactive=%d Presence_Detect=%d ELD_Valid=%d\n", + codec->addr, jack->nid, jack->dev_id, !!(res & AC_UNSOL_RES_IA), + !!(res & AC_UNSOL_RES_PD), !!(res & AC_UNSOL_RES_ELDV)); + + check_presence_and_report(codec, jack->nid, jack->dev_id); +} + +static void hdmi_non_intrinsic_event(struct hda_codec *codec, unsigned int res) +{ + int tag = res >> AC_UNSOL_RES_TAG_SHIFT; + int subtag = (res & AC_UNSOL_RES_SUBTAG) >> AC_UNSOL_RES_SUBTAG_SHIFT; + int cp_state = !!(res & AC_UNSOL_RES_CP_STATE); + int cp_ready = !!(res & AC_UNSOL_RES_CP_READY); + + codec_info(codec, + "HDMI CP event: CODEC=%d TAG=%d SUBTAG=0x%x CP_STATE=%d CP_READY=%d\n", + codec->addr, + tag, + subtag, + cp_state, + cp_ready); + + /* TODO */ + if (cp_state) { + ; + } + if (cp_ready) { + ; + } +} + + +static void hdmi_unsol_event(struct hda_codec *codec, unsigned int res) +{ + int tag = res >> AC_UNSOL_RES_TAG_SHIFT; + int subtag = (res & AC_UNSOL_RES_SUBTAG) >> AC_UNSOL_RES_SUBTAG_SHIFT; + struct hda_jack_tbl *jack; + + if (codec_has_acomp(codec)) + return; + + if (codec->dp_mst) { + int dev_entry = + (res & AC_UNSOL_RES_DE) >> AC_UNSOL_RES_DE_SHIFT; + + jack = snd_hda_jack_tbl_get_from_tag(codec, tag, dev_entry); + } else { + jack = snd_hda_jack_tbl_get_from_tag(codec, tag, 0); + } + + if (!jack) { + codec_dbg(codec, "Unexpected HDMI event tag 0x%x\n", tag); + return; + } + + if (subtag == 0) + hdmi_intrinsic_event(codec, res, jack); + else + hdmi_non_intrinsic_event(codec, res); +} + +static void haswell_verify_D0(struct hda_codec *codec, + hda_nid_t cvt_nid, hda_nid_t nid) +{ + int pwr; + + /* For Haswell, the converter 1/2 may keep in D3 state after bootup, + * thus pins could only choose converter 0 for use. Make sure the + * converters are in correct power state */ + if (!snd_hda_check_power_state(codec, cvt_nid, AC_PWRST_D0)) + snd_hda_codec_write(codec, cvt_nid, 0, AC_VERB_SET_POWER_STATE, AC_PWRST_D0); + + if (!snd_hda_check_power_state(codec, nid, AC_PWRST_D0)) { + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_POWER_STATE, + AC_PWRST_D0); + msleep(40); + pwr = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_POWER_STATE, 0); + pwr = (pwr & AC_PWRST_ACTUAL) >> AC_PWRST_ACTUAL_SHIFT; + codec_dbg(codec, "Haswell HDMI audio: Power for NID 0x%x is now D%d\n", nid, pwr); + } +} + +/* + * Callbacks + */ + +/* HBR should be Non-PCM, 8 channels */ +#define is_hbr_format(format) \ + ((format & AC_FMT_TYPE_NON_PCM) && (format & AC_FMT_CHAN_MASK) == 7) + +static int hdmi_pin_hbr_setup(struct hda_codec *codec, hda_nid_t pin_nid, + int dev_id, bool hbr) +{ + int pinctl, new_pinctl; + + if (snd_hda_query_pin_caps(codec, pin_nid) & AC_PINCAP_HBR) { + snd_hda_set_dev_select(codec, pin_nid, dev_id); + pinctl = snd_hda_codec_read(codec, pin_nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + + if (pinctl < 0) + return hbr ? -EINVAL : 0; + + new_pinctl = pinctl & ~AC_PINCTL_EPT; + if (hbr) + new_pinctl |= AC_PINCTL_EPT_HBR; + else + new_pinctl |= AC_PINCTL_EPT_NATIVE; + + codec_dbg(codec, + "hdmi_pin_hbr_setup: NID=0x%x, %spinctl=0x%x\n", + pin_nid, + pinctl == new_pinctl ? "" : "new-", + new_pinctl); + + if (pinctl != new_pinctl) + snd_hda_codec_write(codec, pin_nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + new_pinctl); + } else if (hbr) + return -EINVAL; + + return 0; +} + +static int hdmi_setup_stream(struct hda_codec *codec, hda_nid_t cvt_nid, + hda_nid_t pin_nid, int dev_id, + u32 stream_tag, int format) +{ + struct hdmi_spec *spec = codec->spec; + unsigned int param; + int err; + + err = spec->ops.pin_hbr_setup(codec, pin_nid, dev_id, + is_hbr_format(format)); + + if (err) { + codec_dbg(codec, "hdmi_setup_stream: HBR is not supported\n"); + return err; + } + + if (spec->intel_hsw_fixup) { + + /* + * on recent platforms IEC Coding Type is required for HBR + * support, read current Digital Converter settings and set + * ICT bitfield if needed. + */ + param = snd_hda_codec_read(codec, cvt_nid, 0, + AC_VERB_GET_DIGI_CONVERT_1, 0); + + param = (param >> 16) & ~(AC_DIG3_ICT); + + /* on recent platforms ICT mode is required for HBR support */ + if (is_hbr_format(format)) + param |= 0x1; + + snd_hda_codec_write(codec, cvt_nid, 0, + AC_VERB_SET_DIGI_CONVERT_3, param); + } + + snd_hda_codec_setup_stream(codec, cvt_nid, stream_tag, 0, format); + return 0; +} + +/* Try to find an available converter + * If pin_idx is less then zero, just try to find an available converter. + * Otherwise, try to find an available converter and get the cvt mux index + * of the pin. + */ +static int hdmi_choose_cvt(struct hda_codec *codec, + int pin_idx, int *cvt_id, + bool silent) +{ + struct hdmi_spec *spec = codec->spec; + struct hdmi_spec_per_pin *per_pin; + struct hdmi_spec_per_cvt *per_cvt = NULL; + int cvt_idx, mux_idx = 0; + + /* pin_idx < 0 means no pin will be bound to the converter */ + if (pin_idx < 0) + per_pin = NULL; + else + per_pin = get_pin(spec, pin_idx); + + if (per_pin && per_pin->silent_stream) { + cvt_idx = cvt_nid_to_cvt_index(codec, per_pin->cvt_nid); + per_cvt = get_cvt(spec, cvt_idx); + if (per_cvt->assigned && !silent) + return -EBUSY; + if (cvt_id) + *cvt_id = cvt_idx; + return 0; + } + + /* Dynamically assign converter to stream */ + for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++) { + per_cvt = get_cvt(spec, cvt_idx); + + /* Must not already be assigned */ + if (per_cvt->assigned || per_cvt->silent_stream) + continue; + if (per_pin == NULL) + break; + /* Must be in pin's mux's list of converters */ + for (mux_idx = 0; mux_idx < per_pin->num_mux_nids; mux_idx++) + if (per_pin->mux_nids[mux_idx] == per_cvt->cvt_nid) + break; + /* Not in mux list */ + if (mux_idx == per_pin->num_mux_nids) + continue; + break; + } + + /* No free converters */ + if (cvt_idx == spec->num_cvts) + return -EBUSY; + + if (per_pin != NULL) + per_pin->mux_idx = mux_idx; + + if (cvt_id) + *cvt_id = cvt_idx; + + return 0; +} + +/* Assure the pin select the right convetor */ +static void intel_verify_pin_cvt_connect(struct hda_codec *codec, + struct hdmi_spec_per_pin *per_pin) +{ + hda_nid_t pin_nid = per_pin->pin_nid; + int mux_idx, curr; + + mux_idx = per_pin->mux_idx; + curr = snd_hda_codec_read(codec, pin_nid, 0, + AC_VERB_GET_CONNECT_SEL, 0); + if (curr != mux_idx) + snd_hda_codec_write_cache(codec, pin_nid, 0, + AC_VERB_SET_CONNECT_SEL, + mux_idx); +} + +/* get the mux index for the converter of the pins + * converter's mux index is the same for all pins on Intel platform + */ +static int intel_cvt_id_to_mux_idx(struct hdmi_spec *spec, + hda_nid_t cvt_nid) +{ + int i; + + for (i = 0; i < spec->num_cvts; i++) + if (spec->cvt_nids[i] == cvt_nid) + return i; + return -EINVAL; +} + +/* Intel HDMI workaround to fix audio routing issue: + * For some Intel display codecs, pins share the same connection list. + * So a conveter can be selected by multiple pins and playback on any of these + * pins will generate sound on the external display, because audio flows from + * the same converter to the display pipeline. Also muting one pin may make + * other pins have no sound output. + * So this function assures that an assigned converter for a pin is not selected + * by any other pins. + */ +static void intel_not_share_assigned_cvt(struct hda_codec *codec, + hda_nid_t pin_nid, + int dev_id, int mux_idx) +{ + struct hdmi_spec *spec = codec->spec; + hda_nid_t nid; + int cvt_idx, curr; + struct hdmi_spec_per_cvt *per_cvt; + struct hdmi_spec_per_pin *per_pin; + int pin_idx; + + /* configure the pins connections */ + for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { + int dev_id_saved; + int dev_num; + + per_pin = get_pin(spec, pin_idx); + /* + * pin not connected to monitor + * no need to operate on it + */ + if (!per_pin->pcm) + continue; + + if ((per_pin->pin_nid == pin_nid) && + (per_pin->dev_id == dev_id)) + continue; + + /* + * if per_pin->dev_id >= dev_num, + * snd_hda_get_dev_select() will fail, + * and the following operation is unpredictable. + * So skip this situation. + */ + dev_num = snd_hda_get_num_devices(codec, per_pin->pin_nid) + 1; + if (per_pin->dev_id >= dev_num) + continue; + + nid = per_pin->pin_nid; + + /* + * Calling this function should not impact + * on the device entry selection + * So let's save the dev id for each pin, + * and restore it when return + */ + dev_id_saved = snd_hda_get_dev_select(codec, nid); + snd_hda_set_dev_select(codec, nid, per_pin->dev_id); + curr = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_CONNECT_SEL, 0); + if (curr != mux_idx) { + snd_hda_set_dev_select(codec, nid, dev_id_saved); + continue; + } + + + /* choose an unassigned converter. The conveters in the + * connection list are in the same order as in the codec. + */ + for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++) { + per_cvt = get_cvt(spec, cvt_idx); + if (!per_cvt->assigned) { + codec_dbg(codec, + "choose cvt %d for pin NID 0x%x\n", + cvt_idx, nid); + snd_hda_codec_write_cache(codec, nid, 0, + AC_VERB_SET_CONNECT_SEL, + cvt_idx); + break; + } + } + snd_hda_set_dev_select(codec, nid, dev_id_saved); + } +} + +/* A wrapper of intel_not_share_asigned_cvt() */ +static void intel_not_share_assigned_cvt_nid(struct hda_codec *codec, + hda_nid_t pin_nid, int dev_id, hda_nid_t cvt_nid) +{ + int mux_idx; + struct hdmi_spec *spec = codec->spec; + + /* On Intel platform, the mapping of converter nid to + * mux index of the pins are always the same. + * The pin nid may be 0, this means all pins will not + * share the converter. + */ + mux_idx = intel_cvt_id_to_mux_idx(spec, cvt_nid); + if (mux_idx >= 0) + intel_not_share_assigned_cvt(codec, pin_nid, dev_id, mux_idx); +} + +/* skeleton caller of pin_cvt_fixup ops */ +static void pin_cvt_fixup(struct hda_codec *codec, + struct hdmi_spec_per_pin *per_pin, + hda_nid_t cvt_nid) +{ + struct hdmi_spec *spec = codec->spec; + + if (spec->ops.pin_cvt_fixup) + spec->ops.pin_cvt_fixup(codec, per_pin, cvt_nid); +} + +/* called in hdmi_pcm_open when no pin is assigned to the PCM */ +static int hdmi_pcm_open_no_pin(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hdmi_spec *spec = codec->spec; + struct snd_pcm_runtime *runtime = substream->runtime; + int cvt_idx, pcm_idx; + struct hdmi_spec_per_cvt *per_cvt = NULL; + int err; + + pcm_idx = hinfo_to_pcm_index(codec, hinfo); + if (pcm_idx < 0) + return -EINVAL; + + err = hdmi_choose_cvt(codec, -1, &cvt_idx, false); + if (err) + return err; + + per_cvt = get_cvt(spec, cvt_idx); + per_cvt->assigned = true; + hinfo->nid = per_cvt->cvt_nid; + + pin_cvt_fixup(codec, NULL, per_cvt->cvt_nid); + + set_bit(pcm_idx, &spec->pcm_in_use); + /* todo: setup spdif ctls assign */ + + /* Initially set the converter's capabilities */ + hinfo->channels_min = per_cvt->channels_min; + hinfo->channels_max = per_cvt->channels_max; + hinfo->rates = per_cvt->rates; + hinfo->formats = per_cvt->formats; + hinfo->maxbps = per_cvt->maxbps; + + /* Store the updated parameters */ + runtime->hw.channels_min = hinfo->channels_min; + runtime->hw.channels_max = hinfo->channels_max; + runtime->hw.formats = hinfo->formats; + runtime->hw.rates = hinfo->rates; + + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, 2); + return 0; +} + +/* + * HDA PCM callbacks + */ +static int hdmi_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hdmi_spec *spec = codec->spec; + struct snd_pcm_runtime *runtime = substream->runtime; + int pin_idx, cvt_idx, pcm_idx; + struct hdmi_spec_per_pin *per_pin; + struct hdmi_eld *eld; + struct hdmi_spec_per_cvt *per_cvt = NULL; + int err; + + /* Validate hinfo */ + pcm_idx = hinfo_to_pcm_index(codec, hinfo); + if (pcm_idx < 0) + return -EINVAL; + + mutex_lock(&spec->pcm_lock); + pin_idx = hinfo_to_pin_index(codec, hinfo); + /* no pin is assigned to the PCM + * PA need pcm open successfully when probe + */ + if (pin_idx < 0) { + err = hdmi_pcm_open_no_pin(hinfo, codec, substream); + goto unlock; + } + + err = hdmi_choose_cvt(codec, pin_idx, &cvt_idx, false); + if (err < 0) + goto unlock; + + per_cvt = get_cvt(spec, cvt_idx); + /* Claim converter */ + per_cvt->assigned = true; + + set_bit(pcm_idx, &spec->pcm_in_use); + per_pin = get_pin(spec, pin_idx); + per_pin->cvt_nid = per_cvt->cvt_nid; + hinfo->nid = per_cvt->cvt_nid; + + /* flip stripe flag for the assigned stream if supported */ + if (get_wcaps(codec, per_cvt->cvt_nid) & AC_WCAP_STRIPE) + azx_stream(get_azx_dev(substream))->stripe = 1; + + snd_hda_set_dev_select(codec, per_pin->pin_nid, per_pin->dev_id); + snd_hda_codec_write_cache(codec, per_pin->pin_nid, 0, + AC_VERB_SET_CONNECT_SEL, + per_pin->mux_idx); + + /* configure unused pins to choose other converters */ + pin_cvt_fixup(codec, per_pin, 0); + + snd_hda_spdif_ctls_assign(codec, pcm_idx, per_cvt->cvt_nid); + + /* Initially set the converter's capabilities */ + hinfo->channels_min = per_cvt->channels_min; + hinfo->channels_max = per_cvt->channels_max; + hinfo->rates = per_cvt->rates; + hinfo->formats = per_cvt->formats; + hinfo->maxbps = per_cvt->maxbps; + + eld = &per_pin->sink_eld; + /* Restrict capabilities by ELD if this isn't disabled */ + if (!static_hdmi_pcm && eld->eld_valid) { + snd_hdmi_eld_update_pcm_info(&eld->info, hinfo); + if (hinfo->channels_min > hinfo->channels_max || + !hinfo->rates || !hinfo->formats) { + per_cvt->assigned = false; + hinfo->nid = 0; + snd_hda_spdif_ctls_unassign(codec, pcm_idx); + err = -ENODEV; + goto unlock; + } + } + + /* Store the updated parameters */ + runtime->hw.channels_min = hinfo->channels_min; + runtime->hw.channels_max = hinfo->channels_max; + runtime->hw.formats = hinfo->formats; + runtime->hw.rates = hinfo->rates; + + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, 2); + unlock: + mutex_unlock(&spec->pcm_lock); + return err; +} + +/* + * HDA/HDMI auto parsing + */ +static int hdmi_read_pin_conn(struct hda_codec *codec, int pin_idx) +{ + struct hdmi_spec *spec = codec->spec; + struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx); + hda_nid_t pin_nid = per_pin->pin_nid; + int dev_id = per_pin->dev_id; + int conns; + + if (!(get_wcaps(codec, pin_nid) & AC_WCAP_CONN_LIST)) { + codec_warn(codec, + "HDMI: pin NID 0x%x wcaps %#x does not support connection list\n", + pin_nid, get_wcaps(codec, pin_nid)); + return -EINVAL; + } + + snd_hda_set_dev_select(codec, pin_nid, dev_id); + + if (spec->intel_hsw_fixup) { + conns = spec->num_cvts; + memcpy(per_pin->mux_nids, spec->cvt_nids, + sizeof(hda_nid_t) * conns); + } else { + conns = snd_hda_get_raw_connections(codec, pin_nid, + per_pin->mux_nids, + HDA_MAX_CONNECTIONS); + } + + /* all the device entries on the same pin have the same conn list */ + per_pin->num_mux_nids = conns; + + return 0; +} + +static int hdmi_find_pcm_slot(struct hdmi_spec *spec, + struct hdmi_spec_per_pin *per_pin) +{ + int i; + + for (i = 0; i < spec->pcm_used; i++) { + if (!test_bit(i, &spec->pcm_bitmap)) + return i; + } + return -EBUSY; +} + +static void hdmi_attach_hda_pcm(struct hdmi_spec *spec, + struct hdmi_spec_per_pin *per_pin) +{ + int idx; + + /* pcm already be attached to the pin */ + if (per_pin->pcm) + return; + /* try the previously used slot at first */ + idx = per_pin->prev_pcm_idx; + if (idx >= 0) { + if (!test_bit(idx, &spec->pcm_bitmap)) + goto found; + per_pin->prev_pcm_idx = -1; /* no longer valid, clear it */ + } + idx = hdmi_find_pcm_slot(spec, per_pin); + if (idx == -EBUSY) + return; + found: + per_pin->pcm_idx = idx; + per_pin->pcm = get_hdmi_pcm(spec, idx); + set_bit(idx, &spec->pcm_bitmap); +} + +static void hdmi_detach_hda_pcm(struct hdmi_spec *spec, + struct hdmi_spec_per_pin *per_pin) +{ + int idx; + + /* pcm already be detached from the pin */ + if (!per_pin->pcm) + return; + idx = per_pin->pcm_idx; + per_pin->pcm_idx = -1; + per_pin->prev_pcm_idx = idx; /* remember the previous index */ + per_pin->pcm = NULL; + if (idx >= 0 && idx < spec->pcm_used) + clear_bit(idx, &spec->pcm_bitmap); +} + +static int hdmi_get_pin_cvt_mux(struct hdmi_spec *spec, + struct hdmi_spec_per_pin *per_pin, hda_nid_t cvt_nid) +{ + int mux_idx; + + for (mux_idx = 0; mux_idx < per_pin->num_mux_nids; mux_idx++) + if (per_pin->mux_nids[mux_idx] == cvt_nid) + break; + return mux_idx; +} + +static bool check_non_pcm_per_cvt(struct hda_codec *codec, hda_nid_t cvt_nid); + +static void hdmi_pcm_setup_pin(struct hdmi_spec *spec, + struct hdmi_spec_per_pin *per_pin) +{ + struct hda_codec *codec = per_pin->codec; + struct hda_pcm *pcm; + struct hda_pcm_stream *hinfo; + struct snd_pcm_substream *substream; + int mux_idx; + bool non_pcm; + + if (per_pin->pcm_idx < 0 || per_pin->pcm_idx >= spec->pcm_used) + return; + pcm = get_pcm_rec(spec, per_pin->pcm_idx); + if (!pcm->pcm) + return; + if (!test_bit(per_pin->pcm_idx, &spec->pcm_in_use)) + return; + + /* hdmi audio only uses playback and one substream */ + hinfo = pcm->stream; + substream = pcm->pcm->streams[0].substream; + + per_pin->cvt_nid = hinfo->nid; + + mux_idx = hdmi_get_pin_cvt_mux(spec, per_pin, hinfo->nid); + if (mux_idx < per_pin->num_mux_nids) { + snd_hda_set_dev_select(codec, per_pin->pin_nid, + per_pin->dev_id); + snd_hda_codec_write_cache(codec, per_pin->pin_nid, 0, + AC_VERB_SET_CONNECT_SEL, + mux_idx); + } + snd_hda_spdif_ctls_assign(codec, per_pin->pcm_idx, hinfo->nid); + + non_pcm = check_non_pcm_per_cvt(codec, hinfo->nid); + if (substream->runtime) + per_pin->channels = substream->runtime->channels; + per_pin->setup = true; + per_pin->mux_idx = mux_idx; + + hdmi_setup_audio_infoframe(codec, per_pin, non_pcm); +} + +static void hdmi_pcm_reset_pin(struct hdmi_spec *spec, + struct hdmi_spec_per_pin *per_pin) +{ + if (per_pin->pcm_idx >= 0 && per_pin->pcm_idx < spec->pcm_used) + snd_hda_spdif_ctls_unassign(per_pin->codec, per_pin->pcm_idx); + + per_pin->chmap_set = false; + memset(per_pin->chmap, 0, sizeof(per_pin->chmap)); + + per_pin->setup = false; + per_pin->channels = 0; +} + +static struct snd_jack *pin_idx_to_pcm_jack(struct hda_codec *codec, + struct hdmi_spec_per_pin *per_pin) +{ + struct hdmi_spec *spec = codec->spec; + + if (per_pin->pcm_idx >= 0) + return spec->pcm_rec[per_pin->pcm_idx].jack; + else + return NULL; +} + +/* update per_pin ELD from the given new ELD; + * setup info frame and notification accordingly + * also notify ELD kctl and report jack status changes + */ +static void update_eld(struct hda_codec *codec, + struct hdmi_spec_per_pin *per_pin, + struct hdmi_eld *eld, + int repoll) +{ + struct hdmi_eld *pin_eld = &per_pin->sink_eld; + struct hdmi_spec *spec = codec->spec; + struct snd_jack *pcm_jack; + bool old_eld_valid = pin_eld->eld_valid; + bool eld_changed; + int pcm_idx; + + if (eld->eld_valid) { + if (eld->eld_size <= 0 || + snd_hdmi_parse_eld(codec, &eld->info, eld->eld_buffer, + eld->eld_size) < 0) { + eld->eld_valid = false; + if (repoll) { + schedule_delayed_work(&per_pin->work, + msecs_to_jiffies(300)); + return; + } + } + } + + if (!eld->eld_valid || eld->eld_size <= 0 || eld->info.sad_count <= 0) { + eld->eld_valid = false; + eld->eld_size = 0; + } + + /* for monitor disconnection, save pcm_idx firstly */ + pcm_idx = per_pin->pcm_idx; + + /* + * pcm_idx >=0 before update_eld() means it is in monitor + * disconnected event. Jack must be fetched before update_eld(). + */ + pcm_jack = pin_idx_to_pcm_jack(codec, per_pin); + + if (!spec->static_pcm_mapping) { + if (eld->eld_valid) { + hdmi_attach_hda_pcm(spec, per_pin); + hdmi_pcm_setup_pin(spec, per_pin); + } else { + hdmi_pcm_reset_pin(spec, per_pin); + hdmi_detach_hda_pcm(spec, per_pin); + } + } + + /* if pcm_idx == -1, it means this is in monitor connection event + * we can get the correct pcm_idx now. + */ + if (pcm_idx == -1) + pcm_idx = per_pin->pcm_idx; + if (!pcm_jack) + pcm_jack = pin_idx_to_pcm_jack(codec, per_pin); + + if (eld->eld_valid) + snd_hdmi_show_eld(codec, &eld->info); + + eld_changed = (pin_eld->eld_valid != eld->eld_valid); + eld_changed |= (pin_eld->monitor_present != eld->monitor_present); + if (!eld_changed && eld->eld_valid && pin_eld->eld_valid) + if (pin_eld->eld_size != eld->eld_size || + memcmp(pin_eld->eld_buffer, eld->eld_buffer, + eld->eld_size) != 0) + eld_changed = true; + + if (eld_changed) { + pin_eld->monitor_present = eld->monitor_present; + pin_eld->eld_valid = eld->eld_valid; + pin_eld->eld_size = eld->eld_size; + if (eld->eld_valid) + memcpy(pin_eld->eld_buffer, eld->eld_buffer, + eld->eld_size); + pin_eld->info = eld->info; + } + + /* + * Re-setup pin and infoframe. This is needed e.g. when + * - sink is first plugged-in + * - transcoder can change during stream playback on Haswell + * and this can make HW reset converter selection on a pin. + */ + if (eld->eld_valid && !old_eld_valid && per_pin->setup) { + pin_cvt_fixup(codec, per_pin, 0); + hdmi_setup_audio_infoframe(codec, per_pin, per_pin->non_pcm); + } + + if (eld_changed && pcm_idx >= 0) + snd_ctl_notify(codec->card, + SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, + &get_hdmi_pcm(spec, pcm_idx)->eld_ctl->id); + + if (eld_changed && pcm_jack) + snd_jack_report(pcm_jack, + (eld->monitor_present && eld->eld_valid) ? + SND_JACK_AVOUT : 0); +} + +/* update ELD and jack state via HD-audio verbs */ +static void hdmi_present_sense_via_verbs(struct hdmi_spec_per_pin *per_pin, + int repoll) +{ + struct hda_codec *codec = per_pin->codec; + struct hdmi_spec *spec = codec->spec; + struct hdmi_eld *eld = &spec->temp_eld; + struct device *dev = hda_codec_dev(codec); + hda_nid_t pin_nid = per_pin->pin_nid; + int dev_id = per_pin->dev_id; + /* + * Always execute a GetPinSense verb here, even when called from + * hdmi_intrinsic_event; for some NVIDIA HW, the unsolicited + * response's PD bit is not the real PD value, but indicates that + * the real PD value changed. An older version of the HD-audio + * specification worked this way. Hence, we just ignore the data in + * the unsolicited response to avoid custom WARs. + */ + int present; + int ret; + +#ifdef CONFIG_PM + if (dev->power.runtime_status == RPM_SUSPENDING) + return; +#endif + + ret = snd_hda_power_up_pm(codec); + if (ret < 0 && pm_runtime_suspended(dev)) + goto out; + + present = snd_hda_jack_pin_sense(codec, pin_nid, dev_id); + + mutex_lock(&per_pin->lock); + eld->monitor_present = !!(present & AC_PINSENSE_PRESENCE); + if (eld->monitor_present) + eld->eld_valid = !!(present & AC_PINSENSE_ELDV); + else + eld->eld_valid = false; + + codec_dbg(codec, + "HDMI status: Codec=%d NID=0x%x Presence_Detect=%d ELD_Valid=%d\n", + codec->addr, pin_nid, eld->monitor_present, eld->eld_valid); + + if (eld->eld_valid) { + if (spec->ops.pin_get_eld(codec, pin_nid, dev_id, + eld->eld_buffer, &eld->eld_size) < 0) + eld->eld_valid = false; + } + + update_eld(codec, per_pin, eld, repoll); + mutex_unlock(&per_pin->lock); + out: + snd_hda_power_down_pm(codec); +} + +#define I915_SILENT_RATE 48000 +#define I915_SILENT_CHANNELS 2 +#define I915_SILENT_FORMAT SNDRV_PCM_FORMAT_S16_LE +#define I915_SILENT_FORMAT_BITS 16 +#define I915_SILENT_FMT_MASK 0xf + +static void silent_stream_enable_i915(struct hda_codec *codec, + struct hdmi_spec_per_pin *per_pin) +{ + unsigned int format; + + snd_hdac_sync_audio_rate(&codec->core, per_pin->pin_nid, + per_pin->dev_id, I915_SILENT_RATE); + + /* trigger silent stream generation in hw */ + format = snd_hdac_calc_stream_format(I915_SILENT_RATE, I915_SILENT_CHANNELS, + I915_SILENT_FORMAT, I915_SILENT_FORMAT_BITS, 0); + snd_hda_codec_setup_stream(codec, per_pin->cvt_nid, + I915_SILENT_FMT_MASK, I915_SILENT_FMT_MASK, format); + usleep_range(100, 200); + snd_hda_codec_setup_stream(codec, per_pin->cvt_nid, I915_SILENT_FMT_MASK, 0, format); + + per_pin->channels = I915_SILENT_CHANNELS; + hdmi_setup_audio_infoframe(codec, per_pin, per_pin->non_pcm); +} + +static void silent_stream_set_kae(struct hda_codec *codec, + struct hdmi_spec_per_pin *per_pin, + bool enable) +{ + unsigned int param; + + codec_dbg(codec, "HDMI: KAE %d cvt-NID=0x%x\n", enable, per_pin->cvt_nid); + + param = snd_hda_codec_read(codec, per_pin->cvt_nid, 0, AC_VERB_GET_DIGI_CONVERT_1, 0); + param = (param >> 16) & 0xff; + + if (enable) + param |= AC_DIG3_KAE; + else + param &= ~AC_DIG3_KAE; + + snd_hda_codec_write(codec, per_pin->cvt_nid, 0, AC_VERB_SET_DIGI_CONVERT_3, param); +} + +static void silent_stream_enable(struct hda_codec *codec, + struct hdmi_spec_per_pin *per_pin) +{ + struct hdmi_spec *spec = codec->spec; + struct hdmi_spec_per_cvt *per_cvt; + int cvt_idx, pin_idx, err; + int keep_power = 0; + + /* + * Power-up will call hdmi_present_sense, so the PM calls + * have to be done without mutex held. + */ + + err = snd_hda_power_up_pm(codec); + if (err < 0 && err != -EACCES) { + codec_err(codec, + "Failed to power up codec for silent stream enable ret=[%d]\n", err); + snd_hda_power_down_pm(codec); + return; + } + + mutex_lock(&per_pin->lock); + + if (per_pin->setup) { + codec_dbg(codec, "hdmi: PCM already open, no silent stream\n"); + err = -EBUSY; + goto unlock_out; + } + + pin_idx = pin_id_to_pin_index(codec, per_pin->pin_nid, per_pin->dev_id); + err = hdmi_choose_cvt(codec, pin_idx, &cvt_idx, true); + if (err) { + codec_err(codec, "hdmi: no free converter to enable silent mode\n"); + goto unlock_out; + } + + per_cvt = get_cvt(spec, cvt_idx); + per_cvt->silent_stream = true; + per_pin->cvt_nid = per_cvt->cvt_nid; + per_pin->silent_stream = true; + + codec_dbg(codec, "hdmi: enabling silent stream pin-NID=0x%x cvt-NID=0x%x\n", + per_pin->pin_nid, per_cvt->cvt_nid); + + snd_hda_set_dev_select(codec, per_pin->pin_nid, per_pin->dev_id); + snd_hda_codec_write_cache(codec, per_pin->pin_nid, 0, + AC_VERB_SET_CONNECT_SEL, + per_pin->mux_idx); + + /* configure unused pins to choose other converters */ + pin_cvt_fixup(codec, per_pin, 0); + + switch (spec->silent_stream_type) { + case SILENT_STREAM_KAE: + silent_stream_enable_i915(codec, per_pin); + silent_stream_set_kae(codec, per_pin, true); + break; + case SILENT_STREAM_I915: + silent_stream_enable_i915(codec, per_pin); + keep_power = 1; + break; + default: + break; + } + + unlock_out: + mutex_unlock(&per_pin->lock); + + if (err || !keep_power) + snd_hda_power_down_pm(codec); +} + +static void silent_stream_disable(struct hda_codec *codec, + struct hdmi_spec_per_pin *per_pin) +{ + struct hdmi_spec *spec = codec->spec; + struct hdmi_spec_per_cvt *per_cvt; + int cvt_idx, err; + + err = snd_hda_power_up_pm(codec); + if (err < 0 && err != -EACCES) { + codec_err(codec, + "Failed to power up codec for silent stream disable ret=[%d]\n", + err); + snd_hda_power_down_pm(codec); + return; + } + + mutex_lock(&per_pin->lock); + if (!per_pin->silent_stream) + goto unlock_out; + + codec_dbg(codec, "HDMI: disable silent stream on pin-NID=0x%x cvt-NID=0x%x\n", + per_pin->pin_nid, per_pin->cvt_nid); + + cvt_idx = cvt_nid_to_cvt_index(codec, per_pin->cvt_nid); + if (cvt_idx >= 0 && cvt_idx < spec->num_cvts) { + per_cvt = get_cvt(spec, cvt_idx); + per_cvt->silent_stream = false; + } + + if (spec->silent_stream_type == SILENT_STREAM_I915) { + /* release ref taken in silent_stream_enable() */ + snd_hda_power_down_pm(codec); + } else if (spec->silent_stream_type == SILENT_STREAM_KAE) { + silent_stream_set_kae(codec, per_pin, false); + } + + per_pin->cvt_nid = 0; + per_pin->silent_stream = false; + + unlock_out: + mutex_unlock(&per_pin->lock); + + snd_hda_power_down_pm(codec); +} + +/* update ELD and jack state via audio component */ +static void sync_eld_via_acomp(struct hda_codec *codec, + struct hdmi_spec_per_pin *per_pin) +{ + struct hdmi_spec *spec = codec->spec; + struct hdmi_eld *eld = &spec->temp_eld; + bool monitor_prev, monitor_next; + + mutex_lock(&per_pin->lock); + eld->monitor_present = false; + monitor_prev = per_pin->sink_eld.monitor_present; + eld->eld_size = snd_hdac_acomp_get_eld(&codec->core, per_pin->pin_nid, + per_pin->dev_id, &eld->monitor_present, + eld->eld_buffer, ELD_MAX_SIZE); + eld->eld_valid = (eld->eld_size > 0); + update_eld(codec, per_pin, eld, 0); + monitor_next = per_pin->sink_eld.monitor_present; + mutex_unlock(&per_pin->lock); + + if (spec->silent_stream_type) { + if (!monitor_prev && monitor_next) + silent_stream_enable(codec, per_pin); + else if (monitor_prev && !monitor_next) + silent_stream_disable(codec, per_pin); + } +} + +static void hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll) +{ + struct hda_codec *codec = per_pin->codec; + + if (!codec_has_acomp(codec)) + hdmi_present_sense_via_verbs(per_pin, repoll); + else + sync_eld_via_acomp(codec, per_pin); +} + +static void hdmi_repoll_eld(struct work_struct *work) +{ + struct hdmi_spec_per_pin *per_pin = + container_of(to_delayed_work(work), struct hdmi_spec_per_pin, work); + struct hda_codec *codec = per_pin->codec; + struct hdmi_spec *spec = codec->spec; + struct hda_jack_tbl *jack; + + jack = snd_hda_jack_tbl_get_mst(codec, per_pin->pin_nid, + per_pin->dev_id); + if (jack) + jack->jack_dirty = 1; + + if (per_pin->repoll_count++ > 6) + per_pin->repoll_count = 0; + + mutex_lock(&spec->pcm_lock); + hdmi_present_sense(per_pin, per_pin->repoll_count); + mutex_unlock(&spec->pcm_lock); +} + +static int hdmi_add_pin(struct hda_codec *codec, hda_nid_t pin_nid) +{ + struct hdmi_spec *spec = codec->spec; + unsigned int caps, config; + int pin_idx; + struct hdmi_spec_per_pin *per_pin; + int err; + int dev_num, i; + + caps = snd_hda_query_pin_caps(codec, pin_nid); + if (!(caps & (AC_PINCAP_HDMI | AC_PINCAP_DP))) + return 0; + + /* + * For DP MST audio, Configuration Default is the same for + * all device entries on the same pin + */ + config = snd_hda_codec_get_pincfg(codec, pin_nid); + if (get_defcfg_connect(config) == AC_JACK_PORT_NONE && + !spec->force_connect) + return 0; + + /* + * To simplify the implementation, malloc all + * the virtual pins in the initialization statically + */ + if (spec->intel_hsw_fixup) { + /* + * On Intel platforms, device entries count returned + * by AC_PAR_DEVLIST_LEN is dynamic, and depends on + * the type of receiver that is connected. Allocate pin + * structures based on worst case. + */ + dev_num = spec->dev_num; + } else if (codec->dp_mst) { + dev_num = snd_hda_get_num_devices(codec, pin_nid) + 1; + /* + * spec->dev_num is the maxinum number of device entries + * among all the pins + */ + spec->dev_num = (spec->dev_num > dev_num) ? + spec->dev_num : dev_num; + } else { + /* + * If the platform doesn't support DP MST, + * manually set dev_num to 1. This means + * the pin has only one device entry. + */ + dev_num = 1; + spec->dev_num = 1; + } + + for (i = 0; i < dev_num; i++) { + pin_idx = spec->num_pins; + per_pin = snd_array_new(&spec->pins); + + if (!per_pin) + return -ENOMEM; + + per_pin->pcm = NULL; + per_pin->pcm_idx = -1; + per_pin->prev_pcm_idx = -1; + per_pin->pin_nid = pin_nid; + per_pin->pin_nid_idx = spec->num_nids; + per_pin->dev_id = i; + per_pin->non_pcm = false; + snd_hda_set_dev_select(codec, pin_nid, i); + err = hdmi_read_pin_conn(codec, pin_idx); + if (err < 0) + return err; + if (!is_jack_detectable(codec, pin_nid)) + codec_warn(codec, "HDMI: pin NID 0x%x - jack not detectable\n", pin_nid); + spec->num_pins++; + } + spec->num_nids++; + + return 0; +} + +static int hdmi_add_cvt(struct hda_codec *codec, hda_nid_t cvt_nid) +{ + struct hdmi_spec *spec = codec->spec; + struct hdmi_spec_per_cvt *per_cvt; + unsigned int chans; + int err; + + chans = get_wcaps(codec, cvt_nid); + chans = get_wcaps_channels(chans); + + per_cvt = snd_array_new(&spec->cvts); + if (!per_cvt) + return -ENOMEM; + + per_cvt->cvt_nid = cvt_nid; + per_cvt->channels_min = 2; + if (chans <= 16) { + per_cvt->channels_max = chans; + if (chans > spec->chmap.channels_max) + spec->chmap.channels_max = chans; + } + + err = snd_hda_query_supported_pcm(codec, cvt_nid, + &per_cvt->rates, + &per_cvt->formats, + &per_cvt->maxbps); + if (err < 0) + return err; + + if (spec->num_cvts < ARRAY_SIZE(spec->cvt_nids)) + spec->cvt_nids[spec->num_cvts] = cvt_nid; + spec->num_cvts++; + + return 0; +} + +static const struct snd_pci_quirk force_connect_list[] = { + SND_PCI_QUIRK(0x103c, 0x870f, "HP", 1), + SND_PCI_QUIRK(0x103c, 0x871a, "HP", 1), + SND_PCI_QUIRK(0x103c, 0x8711, "HP", 1), + SND_PCI_QUIRK(0x103c, 0x8715, "HP", 1), + SND_PCI_QUIRK(0x1043, 0x86ae, "ASUS", 1), /* Z170 PRO */ + SND_PCI_QUIRK(0x1043, 0x86c7, "ASUS", 1), /* Z170M PLUS */ + SND_PCI_QUIRK(0x1462, 0xec94, "MS-7C94", 1), + SND_PCI_QUIRK(0x8086, 0x2060, "Intel NUC5CPYB", 1), + SND_PCI_QUIRK(0x8086, 0x2081, "Intel NUC 10", 1), + {} +}; + +static int hdmi_parse_codec(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + hda_nid_t start_nid; + unsigned int caps; + int i, nodes; + const struct snd_pci_quirk *q; + + nodes = snd_hda_get_sub_nodes(codec, codec->core.afg, &start_nid); + if (!start_nid || nodes < 0) { + codec_warn(codec, "HDMI: failed to get afg sub nodes\n"); + return -EINVAL; + } + + if (enable_all_pins) + spec->force_connect = true; + + q = snd_pci_quirk_lookup(codec->bus->pci, force_connect_list); + + if (q && q->value) + spec->force_connect = true; + + /* + * hdmi_add_pin() assumes total amount of converters to + * be known, so first discover all converters + */ + for (i = 0; i < nodes; i++) { + hda_nid_t nid = start_nid + i; + + caps = get_wcaps(codec, nid); + + if (!(caps & AC_WCAP_DIGITAL)) + continue; + + if (get_wcaps_type(caps) == AC_WID_AUD_OUT) + hdmi_add_cvt(codec, nid); + } + + /* discover audio pins */ + for (i = 0; i < nodes; i++) { + hda_nid_t nid = start_nid + i; + + caps = get_wcaps(codec, nid); + + if (!(caps & AC_WCAP_DIGITAL)) + continue; + + if (get_wcaps_type(caps) == AC_WID_PIN) + hdmi_add_pin(codec, nid); + } + + return 0; +} + +/* + */ +static bool check_non_pcm_per_cvt(struct hda_codec *codec, hda_nid_t cvt_nid) +{ + struct hda_spdif_out *spdif; + bool non_pcm; + + mutex_lock(&codec->spdif_mutex); + spdif = snd_hda_spdif_out_of_nid(codec, cvt_nid); + /* Add sanity check to pass klockwork check. + * This should never happen. + */ + if (WARN_ON(spdif == NULL)) { + mutex_unlock(&codec->spdif_mutex); + return true; + } + non_pcm = !!(spdif->status & IEC958_AES0_NONAUDIO); + mutex_unlock(&codec->spdif_mutex); + return non_pcm; +} + +/* + * HDMI callbacks + */ + +static int generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + hda_nid_t cvt_nid = hinfo->nid; + struct hdmi_spec *spec = codec->spec; + int pin_idx; + struct hdmi_spec_per_pin *per_pin; + struct snd_pcm_runtime *runtime = substream->runtime; + bool non_pcm; + int pinctl, stripe; + int err = 0; + + mutex_lock(&spec->pcm_lock); + pin_idx = hinfo_to_pin_index(codec, hinfo); + if (pin_idx < 0) { + /* when pcm is not bound to a pin skip pin setup and return 0 + * to make audio playback be ongoing + */ + pin_cvt_fixup(codec, NULL, cvt_nid); + snd_hda_codec_setup_stream(codec, cvt_nid, + stream_tag, 0, format); + goto unlock; + } + + if (snd_BUG_ON(pin_idx < 0)) { + err = -EINVAL; + goto unlock; + } + per_pin = get_pin(spec, pin_idx); + + /* Verify pin:cvt selections to avoid silent audio after S3. + * After S3, the audio driver restores pin:cvt selections + * but this can happen before gfx is ready and such selection + * is overlooked by HW. Thus multiple pins can share a same + * default convertor and mute control will affect each other, + * which can cause a resumed audio playback become silent + * after S3. + */ + pin_cvt_fixup(codec, per_pin, 0); + + /* Call sync_audio_rate to set the N/CTS/M manually if necessary */ + /* Todo: add DP1.2 MST audio support later */ + if (codec_has_acomp(codec)) + snd_hdac_sync_audio_rate(&codec->core, per_pin->pin_nid, + per_pin->dev_id, runtime->rate); + + non_pcm = check_non_pcm_per_cvt(codec, cvt_nid); + mutex_lock(&per_pin->lock); + per_pin->channels = substream->runtime->channels; + per_pin->setup = true; + + if (get_wcaps(codec, cvt_nid) & AC_WCAP_STRIPE) { + stripe = snd_hdac_get_stream_stripe_ctl(&codec->bus->core, + substream); + snd_hda_codec_write(codec, cvt_nid, 0, + AC_VERB_SET_STRIPE_CONTROL, + stripe); + } + + hdmi_setup_audio_infoframe(codec, per_pin, non_pcm); + mutex_unlock(&per_pin->lock); + if (spec->dyn_pin_out) { + snd_hda_set_dev_select(codec, per_pin->pin_nid, + per_pin->dev_id); + pinctl = snd_hda_codec_read(codec, per_pin->pin_nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_codec_write(codec, per_pin->pin_nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + pinctl | PIN_OUT); + } + + /* snd_hda_set_dev_select() has been called before */ + err = spec->ops.setup_stream(codec, cvt_nid, per_pin->pin_nid, + per_pin->dev_id, stream_tag, format); + unlock: + mutex_unlock(&spec->pcm_lock); + return err; +} + +static int generic_hdmi_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + snd_hda_codec_cleanup_stream(codec, hinfo->nid); + return 0; +} + +static int hdmi_pcm_close(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hdmi_spec *spec = codec->spec; + int cvt_idx, pin_idx, pcm_idx; + struct hdmi_spec_per_cvt *per_cvt; + struct hdmi_spec_per_pin *per_pin; + int pinctl; + int err = 0; + + mutex_lock(&spec->pcm_lock); + if (hinfo->nid) { + pcm_idx = hinfo_to_pcm_index(codec, hinfo); + if (snd_BUG_ON(pcm_idx < 0)) { + err = -EINVAL; + goto unlock; + } + cvt_idx = cvt_nid_to_cvt_index(codec, hinfo->nid); + if (snd_BUG_ON(cvt_idx < 0)) { + err = -EINVAL; + goto unlock; + } + per_cvt = get_cvt(spec, cvt_idx); + per_cvt->assigned = false; + hinfo->nid = 0; + + azx_stream(get_azx_dev(substream))->stripe = 0; + + snd_hda_spdif_ctls_unassign(codec, pcm_idx); + clear_bit(pcm_idx, &spec->pcm_in_use); + pin_idx = hinfo_to_pin_index(codec, hinfo); + if (pin_idx < 0) + goto unlock; + + if (snd_BUG_ON(pin_idx < 0)) { + err = -EINVAL; + goto unlock; + } + per_pin = get_pin(spec, pin_idx); + + if (spec->dyn_pin_out) { + snd_hda_set_dev_select(codec, per_pin->pin_nid, + per_pin->dev_id); + pinctl = snd_hda_codec_read(codec, per_pin->pin_nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_codec_write(codec, per_pin->pin_nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + pinctl & ~PIN_OUT); + } + + mutex_lock(&per_pin->lock); + per_pin->chmap_set = false; + memset(per_pin->chmap, 0, sizeof(per_pin->chmap)); + + per_pin->setup = false; + per_pin->channels = 0; + mutex_unlock(&per_pin->lock); + } + +unlock: + mutex_unlock(&spec->pcm_lock); + + return err; +} + +static const struct hda_pcm_ops generic_ops = { + .open = hdmi_pcm_open, + .close = hdmi_pcm_close, + .prepare = generic_hdmi_playback_pcm_prepare, + .cleanup = generic_hdmi_playback_pcm_cleanup, +}; + +static int hdmi_get_spk_alloc(struct hdac_device *hdac, int pcm_idx) +{ + struct hda_codec *codec = hdac_to_hda_codec(hdac); + struct hdmi_spec *spec = codec->spec; + struct hdmi_spec_per_pin *per_pin = pcm_idx_to_pin(spec, pcm_idx); + + if (!per_pin) + return 0; + + return per_pin->sink_eld.info.spk_alloc; +} + +static void hdmi_get_chmap(struct hdac_device *hdac, int pcm_idx, + unsigned char *chmap) +{ + struct hda_codec *codec = hdac_to_hda_codec(hdac); + struct hdmi_spec *spec = codec->spec; + struct hdmi_spec_per_pin *per_pin = pcm_idx_to_pin(spec, pcm_idx); + + /* chmap is already set to 0 in caller */ + if (!per_pin) + return; + + memcpy(chmap, per_pin->chmap, ARRAY_SIZE(per_pin->chmap)); +} + +static void hdmi_set_chmap(struct hdac_device *hdac, int pcm_idx, + unsigned char *chmap, int prepared) +{ + struct hda_codec *codec = hdac_to_hda_codec(hdac); + struct hdmi_spec *spec = codec->spec; + struct hdmi_spec_per_pin *per_pin = pcm_idx_to_pin(spec, pcm_idx); + + if (!per_pin) + return; + mutex_lock(&per_pin->lock); + per_pin->chmap_set = true; + memcpy(per_pin->chmap, chmap, ARRAY_SIZE(per_pin->chmap)); + if (prepared) + hdmi_setup_audio_infoframe(codec, per_pin, per_pin->non_pcm); + mutex_unlock(&per_pin->lock); +} + +static bool is_hdmi_pcm_attached(struct hdac_device *hdac, int pcm_idx) +{ + struct hda_codec *codec = hdac_to_hda_codec(hdac); + struct hdmi_spec *spec = codec->spec; + struct hdmi_spec_per_pin *per_pin = pcm_idx_to_pin(spec, pcm_idx); + + return per_pin ? true:false; +} + +static int generic_hdmi_build_pcms(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + int idx, pcm_num; + + /* limit the PCM devices to the codec converters or available PINs */ + pcm_num = min(spec->num_cvts, spec->num_pins); + codec_dbg(codec, "hdmi: pcm_num set to %d\n", pcm_num); + + for (idx = 0; idx < pcm_num; idx++) { + struct hda_pcm *info; + struct hda_pcm_stream *pstr; + + info = snd_hda_codec_pcm_new(codec, "HDMI %d", idx); + if (!info) + return -ENOMEM; + + spec->pcm_rec[idx].pcm = info; + spec->pcm_used++; + info->pcm_type = HDA_PCM_TYPE_HDMI; + info->own_chmap = true; + + pstr = &info->stream[SNDRV_PCM_STREAM_PLAYBACK]; + pstr->substreams = 1; + pstr->ops = generic_ops; + /* pcm number is less than pcm_rec array size */ + if (spec->pcm_used >= ARRAY_SIZE(spec->pcm_rec)) + break; + /* other pstr fields are set in open */ + } + + return 0; +} + +static void free_hdmi_jack_priv(struct snd_jack *jack) +{ + struct hdmi_pcm *pcm = jack->private_data; + + pcm->jack = NULL; +} + +static int generic_hdmi_build_jack(struct hda_codec *codec, int pcm_idx) +{ + char hdmi_str[32] = "HDMI/DP"; + struct hdmi_spec *spec = codec->spec; + struct snd_jack *jack; + int pcmdev = get_pcm_rec(spec, pcm_idx)->device; + int err; + + if (pcmdev > 0) + sprintf(hdmi_str + strlen(hdmi_str), ",pcm=%d", pcmdev); + + err = snd_jack_new(codec->card, hdmi_str, SND_JACK_AVOUT, &jack, + true, false); + if (err < 0) + return err; + + spec->pcm_rec[pcm_idx].jack = jack; + jack->private_data = &spec->pcm_rec[pcm_idx]; + jack->private_free = free_hdmi_jack_priv; + return 0; +} + +static int generic_hdmi_build_controls(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + int dev, err; + int pin_idx, pcm_idx; + + for (pcm_idx = 0; pcm_idx < spec->pcm_used; pcm_idx++) { + if (!get_pcm_rec(spec, pcm_idx)->pcm) { + /* no PCM: mark this for skipping permanently */ + set_bit(pcm_idx, &spec->pcm_bitmap); + continue; + } + + err = generic_hdmi_build_jack(codec, pcm_idx); + if (err < 0) + return err; + + /* create the spdif for each pcm + * pin will be bound when monitor is connected + */ + err = snd_hda_create_dig_out_ctls(codec, + 0, spec->cvt_nids[0], + HDA_PCM_TYPE_HDMI); + if (err < 0) + return err; + snd_hda_spdif_ctls_unassign(codec, pcm_idx); + + dev = get_pcm_rec(spec, pcm_idx)->device; + if (dev != SNDRV_PCM_INVALID_DEVICE) { + /* add control for ELD Bytes */ + err = hdmi_create_eld_ctl(codec, pcm_idx, dev); + if (err < 0) + return err; + } + } + + for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { + struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx); + struct hdmi_eld *pin_eld = &per_pin->sink_eld; + + if (spec->static_pcm_mapping) { + hdmi_attach_hda_pcm(spec, per_pin); + hdmi_pcm_setup_pin(spec, per_pin); + } + + pin_eld->eld_valid = false; + hdmi_present_sense(per_pin, 0); + } + + /* add channel maps */ + for (pcm_idx = 0; pcm_idx < spec->pcm_used; pcm_idx++) { + struct hda_pcm *pcm; + + pcm = get_pcm_rec(spec, pcm_idx); + if (!pcm || !pcm->pcm) + break; + err = snd_hdac_add_chmap_ctls(pcm->pcm, pcm_idx, &spec->chmap); + if (err < 0) + return err; + } + + return 0; +} + +static int generic_hdmi_init_per_pins(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + int pin_idx; + + for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { + struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx); + + per_pin->codec = codec; + mutex_init(&per_pin->lock); + INIT_DELAYED_WORK(&per_pin->work, hdmi_repoll_eld); + eld_proc_new(per_pin, pin_idx); + } + return 0; +} + +static int generic_hdmi_init(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + int pin_idx; + + mutex_lock(&spec->bind_lock); + for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { + struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx); + hda_nid_t pin_nid = per_pin->pin_nid; + int dev_id = per_pin->dev_id; + + snd_hda_set_dev_select(codec, pin_nid, dev_id); + hdmi_init_pin(codec, pin_nid); + if (codec_has_acomp(codec)) + continue; + snd_hda_jack_detect_enable_callback_mst(codec, pin_nid, dev_id, + jack_callback); + } + mutex_unlock(&spec->bind_lock); + return 0; +} + +static void hdmi_array_init(struct hdmi_spec *spec, int nums) +{ + snd_array_init(&spec->pins, sizeof(struct hdmi_spec_per_pin), nums); + snd_array_init(&spec->cvts, sizeof(struct hdmi_spec_per_cvt), nums); +} + +static void hdmi_array_free(struct hdmi_spec *spec) +{ + snd_array_free(&spec->pins); + snd_array_free(&spec->cvts); +} + +static void generic_spec_free(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + + if (spec) { + hdmi_array_free(spec); + kfree(spec); + codec->spec = NULL; + } + codec->dp_mst = false; +} + +static void generic_hdmi_free(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + int pin_idx, pcm_idx; + + if (spec->acomp_registered) { + snd_hdac_acomp_exit(&codec->bus->core); + } else if (codec_has_acomp(codec)) { + snd_hdac_acomp_register_notifier(&codec->bus->core, NULL); + } + codec->relaxed_resume = 0; + + for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { + struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx); + cancel_delayed_work_sync(&per_pin->work); + eld_proc_free(per_pin); + } + + for (pcm_idx = 0; pcm_idx < spec->pcm_used; pcm_idx++) { + if (spec->pcm_rec[pcm_idx].jack == NULL) + continue; + snd_device_free(codec->card, spec->pcm_rec[pcm_idx].jack); + } + + generic_spec_free(codec); +} + +#ifdef CONFIG_PM +static int generic_hdmi_suspend(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + int pin_idx; + + for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { + struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx); + cancel_delayed_work_sync(&per_pin->work); + } + return 0; +} + +static int generic_hdmi_resume(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + int pin_idx; + + codec->patch_ops.init(codec); + snd_hda_regmap_sync(codec); + + for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { + struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx); + hdmi_present_sense(per_pin, 1); + } + return 0; +} +#endif + +static const struct hda_codec_ops generic_hdmi_patch_ops = { + .init = generic_hdmi_init, + .free = generic_hdmi_free, + .build_pcms = generic_hdmi_build_pcms, + .build_controls = generic_hdmi_build_controls, + .unsol_event = hdmi_unsol_event, +#ifdef CONFIG_PM + .suspend = generic_hdmi_suspend, + .resume = generic_hdmi_resume, +#endif +}; + +static const struct hdmi_ops generic_standard_hdmi_ops = { + .pin_get_eld = hdmi_pin_get_eld, + .pin_setup_infoframe = hdmi_pin_setup_infoframe, + .pin_hbr_setup = hdmi_pin_hbr_setup, + .setup_stream = hdmi_setup_stream, +}; + +/* allocate codec->spec and assign/initialize generic parser ops */ +static int alloc_generic_hdmi(struct hda_codec *codec) +{ + struct hdmi_spec *spec; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return -ENOMEM; + + spec->codec = codec; + spec->ops = generic_standard_hdmi_ops; + spec->dev_num = 1; /* initialize to 1 */ + mutex_init(&spec->pcm_lock); + mutex_init(&spec->bind_lock); + snd_hdac_register_chmap_ops(&codec->core, &spec->chmap); + + spec->chmap.ops.get_chmap = hdmi_get_chmap; + spec->chmap.ops.set_chmap = hdmi_set_chmap; + spec->chmap.ops.is_pcm_attached = is_hdmi_pcm_attached; + spec->chmap.ops.get_spk_alloc = hdmi_get_spk_alloc; + + codec->spec = spec; + hdmi_array_init(spec, 4); + + codec->patch_ops = generic_hdmi_patch_ops; + + return 0; +} + +/* generic HDMI parser */ +static int patch_generic_hdmi(struct hda_codec *codec) +{ + int err; + + err = alloc_generic_hdmi(codec); + if (err < 0) + return err; + + err = hdmi_parse_codec(codec); + if (err < 0) { + generic_spec_free(codec); + return err; + } + + generic_hdmi_init_per_pins(codec); + return 0; +} + +/* + * generic audio component binding + */ + +/* turn on / off the unsol event jack detection dynamically */ +static void reprogram_jack_detect(struct hda_codec *codec, hda_nid_t nid, + int dev_id, bool use_acomp) +{ + struct hda_jack_tbl *tbl; + + tbl = snd_hda_jack_tbl_get_mst(codec, nid, dev_id); + if (tbl) { + /* clear unsol even if component notifier is used, or re-enable + * if notifier is cleared + */ + unsigned int val = use_acomp ? 0 : (AC_USRSP_EN | tbl->tag); + snd_hda_codec_write_cache(codec, nid, 0, + AC_VERB_SET_UNSOLICITED_ENABLE, val); + } +} + +/* set up / clear component notifier dynamically */ +static void generic_acomp_notifier_set(struct drm_audio_component *acomp, + bool use_acomp) +{ + struct hdmi_spec *spec; + int i; + + spec = container_of(acomp->audio_ops, struct hdmi_spec, drm_audio_ops); + mutex_lock(&spec->bind_lock); + spec->use_acomp_notifier = use_acomp; + spec->codec->relaxed_resume = use_acomp; + spec->codec->bus->keep_power = 0; + /* reprogram each jack detection logic depending on the notifier */ + for (i = 0; i < spec->num_pins; i++) + reprogram_jack_detect(spec->codec, + get_pin(spec, i)->pin_nid, + get_pin(spec, i)->dev_id, + use_acomp); + mutex_unlock(&spec->bind_lock); +} + +/* enable / disable the notifier via master bind / unbind */ +static int generic_acomp_master_bind(struct device *dev, + struct drm_audio_component *acomp) +{ + generic_acomp_notifier_set(acomp, true); + return 0; +} + +static void generic_acomp_master_unbind(struct device *dev, + struct drm_audio_component *acomp) +{ + generic_acomp_notifier_set(acomp, false); +} + +/* check whether both HD-audio and DRM PCI devices belong to the same bus */ +static int match_bound_vga(struct device *dev, int subtype, void *data) +{ + struct hdac_bus *bus = data; + struct pci_dev *pci, *master; + + if (!dev_is_pci(dev) || !dev_is_pci(bus->dev)) + return 0; + master = to_pci_dev(bus->dev); + pci = to_pci_dev(dev); + return master->bus == pci->bus; +} + +/* audio component notifier for AMD/Nvidia HDMI codecs */ +static void generic_acomp_pin_eld_notify(void *audio_ptr, int port, int dev_id) +{ + struct hda_codec *codec = audio_ptr; + struct hdmi_spec *spec = codec->spec; + hda_nid_t pin_nid = spec->port2pin(codec, port); + + if (!pin_nid) + return; + if (get_wcaps_type(get_wcaps(codec, pin_nid)) != AC_WID_PIN) + return; + /* skip notification during system suspend (but not in runtime PM); + * the state will be updated at resume + */ + if (codec->core.dev.power.power_state.event == PM_EVENT_SUSPEND) + return; + + check_presence_and_report(codec, pin_nid, dev_id); +} + +/* set up the private drm_audio_ops from the template */ +static void setup_drm_audio_ops(struct hda_codec *codec, + const struct drm_audio_component_audio_ops *ops) +{ + struct hdmi_spec *spec = codec->spec; + + spec->drm_audio_ops.audio_ptr = codec; + /* intel_audio_codec_enable() or intel_audio_codec_disable() + * will call pin_eld_notify with using audio_ptr pointer + * We need make sure audio_ptr is really setup + */ + wmb(); + spec->drm_audio_ops.pin2port = ops->pin2port; + spec->drm_audio_ops.pin_eld_notify = ops->pin_eld_notify; + spec->drm_audio_ops.master_bind = ops->master_bind; + spec->drm_audio_ops.master_unbind = ops->master_unbind; +} + +/* initialize the generic HDMI audio component */ +static void generic_acomp_init(struct hda_codec *codec, + const struct drm_audio_component_audio_ops *ops, + int (*port2pin)(struct hda_codec *, int)) +{ + struct hdmi_spec *spec = codec->spec; + + if (!enable_acomp) { + codec_info(codec, "audio component disabled by module option\n"); + return; + } + + spec->port2pin = port2pin; + setup_drm_audio_ops(codec, ops); + if (!snd_hdac_acomp_init(&codec->bus->core, &spec->drm_audio_ops, + match_bound_vga, 0)) { + spec->acomp_registered = true; + } +} + +/* + * Intel codec parsers and helpers + */ + +#define INTEL_GET_VENDOR_VERB 0xf81 +#define INTEL_SET_VENDOR_VERB 0x781 +#define INTEL_EN_DP12 0x02 /* enable DP 1.2 features */ +#define INTEL_EN_ALL_PIN_CVTS 0x01 /* enable 2nd & 3rd pins and convertors */ + +static void intel_haswell_enable_all_pins(struct hda_codec *codec, + bool update_tree) +{ + unsigned int vendor_param; + struct hdmi_spec *spec = codec->spec; + + vendor_param = snd_hda_codec_read(codec, spec->vendor_nid, 0, + INTEL_GET_VENDOR_VERB, 0); + if (vendor_param == -1 || vendor_param & INTEL_EN_ALL_PIN_CVTS) + return; + + vendor_param |= INTEL_EN_ALL_PIN_CVTS; + vendor_param = snd_hda_codec_read(codec, spec->vendor_nid, 0, + INTEL_SET_VENDOR_VERB, vendor_param); + if (vendor_param == -1) + return; + + if (update_tree) + snd_hda_codec_update_widgets(codec); +} + +static void intel_haswell_fixup_enable_dp12(struct hda_codec *codec) +{ + unsigned int vendor_param; + struct hdmi_spec *spec = codec->spec; + + vendor_param = snd_hda_codec_read(codec, spec->vendor_nid, 0, + INTEL_GET_VENDOR_VERB, 0); + if (vendor_param == -1 || vendor_param & INTEL_EN_DP12) + return; + + /* enable DP1.2 mode */ + vendor_param |= INTEL_EN_DP12; + snd_hdac_regmap_add_vendor_verb(&codec->core, INTEL_SET_VENDOR_VERB); + snd_hda_codec_write_cache(codec, spec->vendor_nid, 0, + INTEL_SET_VENDOR_VERB, vendor_param); +} + +/* Haswell needs to re-issue the vendor-specific verbs before turning to D0. + * Otherwise you may get severe h/w communication errors. + */ +static void haswell_set_power_state(struct hda_codec *codec, hda_nid_t fg, + unsigned int power_state) +{ + if (power_state == AC_PWRST_D0) { + intel_haswell_enable_all_pins(codec, false); + intel_haswell_fixup_enable_dp12(codec); + } + + snd_hda_codec_read(codec, fg, 0, AC_VERB_SET_POWER_STATE, power_state); + snd_hda_codec_set_power_to_all(codec, fg, power_state); +} + +/* There is a fixed mapping between audio pin node and display port. + * on SNB, IVY, HSW, BSW, SKL, BXT, KBL: + * Pin Widget 5 - PORT B (port = 1 in i915 driver) + * Pin Widget 6 - PORT C (port = 2 in i915 driver) + * Pin Widget 7 - PORT D (port = 3 in i915 driver) + * + * on VLV, ILK: + * Pin Widget 4 - PORT B (port = 1 in i915 driver) + * Pin Widget 5 - PORT C (port = 2 in i915 driver) + * Pin Widget 6 - PORT D (port = 3 in i915 driver) + */ +static int intel_base_nid(struct hda_codec *codec) +{ + switch (codec->core.vendor_id) { + case 0x80860054: /* ILK */ + case 0x80862804: /* ILK */ + case 0x80862882: /* VLV */ + return 4; + default: + return 5; + } +} + +static int intel_pin2port(void *audio_ptr, int pin_nid) +{ + struct hda_codec *codec = audio_ptr; + struct hdmi_spec *spec = codec->spec; + int base_nid, i; + + if (!spec->port_num) { + base_nid = intel_base_nid(codec); + if (WARN_ON(pin_nid < base_nid || pin_nid >= base_nid + 3)) + return -1; + return pin_nid - base_nid + 1; + } + + /* + * looking for the pin number in the mapping table and return + * the index which indicate the port number + */ + for (i = 0; i < spec->port_num; i++) { + if (pin_nid == spec->port_map[i]) + return i; + } + + codec_info(codec, "Can't find the HDMI/DP port for pin NID 0x%x\n", pin_nid); + return -1; +} + +static int intel_port2pin(struct hda_codec *codec, int port) +{ + struct hdmi_spec *spec = codec->spec; + + if (!spec->port_num) { + /* we assume only from port-B to port-D */ + if (port < 1 || port > 3) + return 0; + return port + intel_base_nid(codec) - 1; + } + + if (port < 0 || port >= spec->port_num) + return 0; + return spec->port_map[port]; +} + +static void intel_pin_eld_notify(void *audio_ptr, int port, int pipe) +{ + struct hda_codec *codec = audio_ptr; + int pin_nid; + int dev_id = pipe; + + pin_nid = intel_port2pin(codec, port); + if (!pin_nid) + return; + /* skip notification during system suspend (but not in runtime PM); + * the state will be updated at resume + */ + if (codec->core.dev.power.power_state.event == PM_EVENT_SUSPEND) + return; + + snd_hdac_i915_set_bclk(&codec->bus->core); + check_presence_and_report(codec, pin_nid, dev_id); +} + +static const struct drm_audio_component_audio_ops intel_audio_ops = { + .pin2port = intel_pin2port, + .pin_eld_notify = intel_pin_eld_notify, +}; + +/* register i915 component pin_eld_notify callback */ +static void register_i915_notifier(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + + spec->use_acomp_notifier = true; + spec->port2pin = intel_port2pin; + setup_drm_audio_ops(codec, &intel_audio_ops); + snd_hdac_acomp_register_notifier(&codec->bus->core, + &spec->drm_audio_ops); + /* no need for forcible resume for jack check thanks to notifier */ + codec->relaxed_resume = 1; +} + +/* setup_stream ops override for HSW+ */ +static int i915_hsw_setup_stream(struct hda_codec *codec, hda_nid_t cvt_nid, + hda_nid_t pin_nid, int dev_id, u32 stream_tag, + int format) +{ + struct hdmi_spec *spec = codec->spec; + int pin_idx = pin_id_to_pin_index(codec, pin_nid, dev_id); + struct hdmi_spec_per_pin *per_pin; + int res; + + if (pin_idx < 0) + per_pin = NULL; + else + per_pin = get_pin(spec, pin_idx); + + haswell_verify_D0(codec, cvt_nid, pin_nid); + + if (spec->silent_stream_type == SILENT_STREAM_KAE && per_pin && per_pin->silent_stream) { + silent_stream_set_kae(codec, per_pin, false); + /* wait for pending transfers in codec to clear */ + usleep_range(100, 200); + } + + res = hdmi_setup_stream(codec, cvt_nid, pin_nid, dev_id, + stream_tag, format); + + if (spec->silent_stream_type == SILENT_STREAM_KAE && per_pin && per_pin->silent_stream) { + usleep_range(100, 200); + silent_stream_set_kae(codec, per_pin, true); + } + + return res; +} + +/* pin_cvt_fixup ops override for HSW+ and VLV+ */ +static void i915_pin_cvt_fixup(struct hda_codec *codec, + struct hdmi_spec_per_pin *per_pin, + hda_nid_t cvt_nid) +{ + if (per_pin) { + haswell_verify_D0(codec, per_pin->cvt_nid, per_pin->pin_nid); + snd_hda_set_dev_select(codec, per_pin->pin_nid, + per_pin->dev_id); + intel_verify_pin_cvt_connect(codec, per_pin); + intel_not_share_assigned_cvt(codec, per_pin->pin_nid, + per_pin->dev_id, per_pin->mux_idx); + } else { + intel_not_share_assigned_cvt_nid(codec, 0, 0, cvt_nid); + } +} + +#ifdef CONFIG_PM +static int i915_adlp_hdmi_suspend(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + bool silent_streams = false; + int pin_idx, res; + + res = generic_hdmi_suspend(codec); + + for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { + struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx); + + if (per_pin->silent_stream) { + silent_streams = true; + break; + } + } + + if (silent_streams && spec->silent_stream_type == SILENT_STREAM_KAE) { + /* + * stream-id should remain programmed when codec goes + * to runtime suspend + */ + codec->no_stream_clean_at_suspend = 1; + + /* + * the system might go to S3, in which case keep-alive + * must be reprogrammed upon resume + */ + codec->forced_resume = 1; + + codec_dbg(codec, "HDMI: KAE active at suspend\n"); + } else { + codec->no_stream_clean_at_suspend = 0; + codec->forced_resume = 0; + } + + return res; +} + +static int i915_adlp_hdmi_resume(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + int pin_idx, res; + + res = generic_hdmi_resume(codec); + + /* KAE not programmed at suspend, nothing to do here */ + if (!codec->no_stream_clean_at_suspend) + return res; + + for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { + struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx); + + /* + * If system was in suspend with monitor connected, + * the codec setting may have been lost. Re-enable + * keep-alive. + */ + if (per_pin->silent_stream) { + unsigned int param; + + param = snd_hda_codec_read(codec, per_pin->cvt_nid, 0, + AC_VERB_GET_CONV, 0); + if (!param) { + codec_dbg(codec, "HDMI: KAE: restore stream id\n"); + silent_stream_enable_i915(codec, per_pin); + } + + param = snd_hda_codec_read(codec, per_pin->cvt_nid, 0, + AC_VERB_GET_DIGI_CONVERT_1, 0); + if (!(param & (AC_DIG3_KAE << 16))) { + codec_dbg(codec, "HDMI: KAE: restore DIG3_KAE\n"); + silent_stream_set_kae(codec, per_pin, true); + } + } + } + + return res; +} +#endif + +/* precondition and allocation for Intel codecs */ +static int alloc_intel_hdmi(struct hda_codec *codec) +{ + int err; + + /* requires i915 binding */ + if (!codec->bus->core.audio_component) { + codec_info(codec, "No i915 binding for Intel HDMI/DP codec\n"); + /* set probe_id here to prevent generic fallback binding */ + codec->probe_id = HDA_CODEC_ID_SKIP_PROBE; + return -ENODEV; + } + + err = alloc_generic_hdmi(codec); + if (err < 0) + return err; + /* no need to handle unsol events */ + codec->patch_ops.unsol_event = NULL; + return 0; +} + +/* parse and post-process for Intel codecs */ +static int parse_intel_hdmi(struct hda_codec *codec) +{ + int err, retries = 3; + + do { + err = hdmi_parse_codec(codec); + } while (err < 0 && retries--); + + if (err < 0) { + generic_spec_free(codec); + return err; + } + + generic_hdmi_init_per_pins(codec); + register_i915_notifier(codec); + return 0; +} + +/* Intel Haswell and onwards; audio component with eld notifier */ +static int intel_hsw_common_init(struct hda_codec *codec, hda_nid_t vendor_nid, + const int *port_map, int port_num, int dev_num, + bool send_silent_stream) +{ + struct hdmi_spec *spec; + int err; + + err = alloc_intel_hdmi(codec); + if (err < 0) + return err; + spec = codec->spec; + codec->dp_mst = true; + spec->vendor_nid = vendor_nid; + spec->port_map = port_map; + spec->port_num = port_num; + spec->intel_hsw_fixup = true; + spec->dev_num = dev_num; + + intel_haswell_enable_all_pins(codec, true); + intel_haswell_fixup_enable_dp12(codec); + + codec->display_power_control = 1; + + codec->patch_ops.set_power_state = haswell_set_power_state; + codec->depop_delay = 0; + codec->auto_runtime_pm = 1; + + spec->ops.setup_stream = i915_hsw_setup_stream; + spec->ops.pin_cvt_fixup = i915_pin_cvt_fixup; + + /* + * Enable silent stream feature, if it is enabled via + * module param or Kconfig option + */ + if (send_silent_stream) + spec->silent_stream_type = SILENT_STREAM_I915; + + return parse_intel_hdmi(codec); +} + +static int patch_i915_hsw_hdmi(struct hda_codec *codec) +{ + return intel_hsw_common_init(codec, 0x08, NULL, 0, 3, + enable_silent_stream); +} + +static int patch_i915_glk_hdmi(struct hda_codec *codec) +{ + /* + * Silent stream calls audio component .get_power() from + * .pin_eld_notify(). On GLK this will deadlock in i915 due + * to the audio vs. CDCLK workaround. + */ + return intel_hsw_common_init(codec, 0x0b, NULL, 0, 3, false); +} + +static int patch_i915_icl_hdmi(struct hda_codec *codec) +{ + /* + * pin to port mapping table where the value indicate the pin number and + * the index indicate the port number. + */ + static const int map[] = {0x0, 0x4, 0x6, 0x8, 0xa, 0xb}; + + return intel_hsw_common_init(codec, 0x02, map, ARRAY_SIZE(map), 3, + enable_silent_stream); +} + +static int patch_i915_tgl_hdmi(struct hda_codec *codec) +{ + /* + * pin to port mapping table where the value indicate the pin number and + * the index indicate the port number. + */ + static const int map[] = {0x4, 0x6, 0x8, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}; + + return intel_hsw_common_init(codec, 0x02, map, ARRAY_SIZE(map), 4, + enable_silent_stream); +} + +static int patch_i915_adlp_hdmi(struct hda_codec *codec) +{ + struct hdmi_spec *spec; + int res; + + res = patch_i915_tgl_hdmi(codec); + if (!res) { + spec = codec->spec; + + if (spec->silent_stream_type) { + spec->silent_stream_type = SILENT_STREAM_KAE; + +#ifdef CONFIG_PM + codec->patch_ops.resume = i915_adlp_hdmi_resume; + codec->patch_ops.suspend = i915_adlp_hdmi_suspend; +#endif + } + } + + return res; +} + +/* Intel Baytrail and Braswell; with eld notifier */ +static int patch_i915_byt_hdmi(struct hda_codec *codec) +{ + struct hdmi_spec *spec; + int err; + + err = alloc_intel_hdmi(codec); + if (err < 0) + return err; + spec = codec->spec; + + /* For Valleyview/Cherryview, only the display codec is in the display + * power well and can use link_power ops to request/release the power. + */ + codec->display_power_control = 1; + + codec->depop_delay = 0; + codec->auto_runtime_pm = 1; + + spec->ops.pin_cvt_fixup = i915_pin_cvt_fixup; + + return parse_intel_hdmi(codec); +} + +/* Intel IronLake, SandyBridge and IvyBridge; with eld notifier */ +static int patch_i915_cpt_hdmi(struct hda_codec *codec) +{ + int err; + + err = alloc_intel_hdmi(codec); + if (err < 0) + return err; + return parse_intel_hdmi(codec); +} + +/* + * Shared non-generic implementations + */ + +static int simple_playback_build_pcms(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + struct hda_pcm *info; + unsigned int chans; + struct hda_pcm_stream *pstr; + struct hdmi_spec_per_cvt *per_cvt; + + per_cvt = get_cvt(spec, 0); + chans = get_wcaps(codec, per_cvt->cvt_nid); + chans = get_wcaps_channels(chans); + + info = snd_hda_codec_pcm_new(codec, "HDMI 0"); + if (!info) + return -ENOMEM; + spec->pcm_rec[0].pcm = info; + info->pcm_type = HDA_PCM_TYPE_HDMI; + pstr = &info->stream[SNDRV_PCM_STREAM_PLAYBACK]; + *pstr = spec->pcm_playback; + pstr->nid = per_cvt->cvt_nid; + if (pstr->channels_max <= 2 && chans && chans <= 16) + pstr->channels_max = chans; + + return 0; +} + +/* unsolicited event for jack sensing */ +static void simple_hdmi_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + snd_hda_jack_set_dirty_all(codec); + snd_hda_jack_report_sync(codec); +} + +/* generic_hdmi_build_jack can be used for simple_hdmi, too, + * as long as spec->pins[] is set correctly + */ +#define simple_hdmi_build_jack generic_hdmi_build_jack + +static int simple_playback_build_controls(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + struct hdmi_spec_per_cvt *per_cvt; + int err; + + per_cvt = get_cvt(spec, 0); + err = snd_hda_create_dig_out_ctls(codec, per_cvt->cvt_nid, + per_cvt->cvt_nid, + HDA_PCM_TYPE_HDMI); + if (err < 0) + return err; + return simple_hdmi_build_jack(codec, 0); +} + +static int simple_playback_init(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + struct hdmi_spec_per_pin *per_pin = get_pin(spec, 0); + hda_nid_t pin = per_pin->pin_nid; + + snd_hda_codec_write(codec, pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT); + /* some codecs require to unmute the pin */ + if (get_wcaps(codec, pin) & AC_WCAP_OUT_AMP) + snd_hda_codec_write(codec, pin, 0, AC_VERB_SET_AMP_GAIN_MUTE, + AMP_OUT_UNMUTE); + snd_hda_jack_detect_enable(codec, pin, per_pin->dev_id); + return 0; +} + +static void simple_playback_free(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + + hdmi_array_free(spec); + kfree(spec); +} + +/* + * Nvidia specific implementations + */ + +#define Nv_VERB_SET_Channel_Allocation 0xF79 +#define Nv_VERB_SET_Info_Frame_Checksum 0xF7A +#define Nv_VERB_SET_Audio_Protection_On 0xF98 +#define Nv_VERB_SET_Audio_Protection_Off 0xF99 + +#define nvhdmi_master_con_nid_7x 0x04 +#define nvhdmi_master_pin_nid_7x 0x05 + +static const hda_nid_t nvhdmi_con_nids_7x[4] = { + /*front, rear, clfe, rear_surr */ + 0x6, 0x8, 0xa, 0xc, +}; + +static const struct hda_verb nvhdmi_basic_init_7x_2ch[] = { + /* set audio protect on */ + { 0x1, Nv_VERB_SET_Audio_Protection_On, 0x1}, + /* enable digital output on pin widget */ + { 0x5, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 }, + {} /* terminator */ +}; + +static const struct hda_verb nvhdmi_basic_init_7x_8ch[] = { + /* set audio protect on */ + { 0x1, Nv_VERB_SET_Audio_Protection_On, 0x1}, + /* enable digital output on pin widget */ + { 0x5, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 }, + { 0x7, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 }, + { 0x9, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 }, + { 0xb, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 }, + { 0xd, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 }, + {} /* terminator */ +}; + +#ifdef LIMITED_RATE_FMT_SUPPORT +/* support only the safe format and rate */ +#define SUPPORTED_RATES SNDRV_PCM_RATE_48000 +#define SUPPORTED_MAXBPS 16 +#define SUPPORTED_FORMATS SNDRV_PCM_FMTBIT_S16_LE +#else +/* support all rates and formats */ +#define SUPPORTED_RATES \ + (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |\ + SNDRV_PCM_RATE_192000) +#define SUPPORTED_MAXBPS 24 +#define SUPPORTED_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) +#endif + +static int nvhdmi_7x_init_2ch(struct hda_codec *codec) +{ + snd_hda_sequence_write(codec, nvhdmi_basic_init_7x_2ch); + return 0; +} + +static int nvhdmi_7x_init_8ch(struct hda_codec *codec) +{ + snd_hda_sequence_write(codec, nvhdmi_basic_init_7x_8ch); + return 0; +} + +static const unsigned int channels_2_6_8[] = { + 2, 6, 8 +}; + +static const unsigned int channels_2_8[] = { + 2, 8 +}; + +static const struct snd_pcm_hw_constraint_list hw_constraints_2_6_8_channels = { + .count = ARRAY_SIZE(channels_2_6_8), + .list = channels_2_6_8, + .mask = 0, +}; + +static const struct snd_pcm_hw_constraint_list hw_constraints_2_8_channels = { + .count = ARRAY_SIZE(channels_2_8), + .list = channels_2_8, + .mask = 0, +}; + +static int simple_playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hdmi_spec *spec = codec->spec; + const struct snd_pcm_hw_constraint_list *hw_constraints_channels = NULL; + + switch (codec->preset->vendor_id) { + case 0x10de0002: + case 0x10de0003: + case 0x10de0005: + case 0x10de0006: + hw_constraints_channels = &hw_constraints_2_8_channels; + break; + case 0x10de0007: + hw_constraints_channels = &hw_constraints_2_6_8_channels; + break; + default: + break; + } + + if (hw_constraints_channels != NULL) { + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + hw_constraints_channels); + } else { + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, 2); + } + + return snd_hda_multi_out_dig_open(codec, &spec->multiout); +} + +static int simple_playback_pcm_close(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hdmi_spec *spec = codec->spec; + return snd_hda_multi_out_dig_close(codec, &spec->multiout); +} + +static int simple_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct hdmi_spec *spec = codec->spec; + return snd_hda_multi_out_dig_prepare(codec, &spec->multiout, + stream_tag, format, substream); +} + +static const struct hda_pcm_stream simple_pcm_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + .ops = { + .open = simple_playback_pcm_open, + .close = simple_playback_pcm_close, + .prepare = simple_playback_pcm_prepare + }, +}; + +static const struct hda_codec_ops simple_hdmi_patch_ops = { + .build_controls = simple_playback_build_controls, + .build_pcms = simple_playback_build_pcms, + .init = simple_playback_init, + .free = simple_playback_free, + .unsol_event = simple_hdmi_unsol_event, +}; + +static int patch_simple_hdmi(struct hda_codec *codec, + hda_nid_t cvt_nid, hda_nid_t pin_nid) +{ + struct hdmi_spec *spec; + struct hdmi_spec_per_cvt *per_cvt; + struct hdmi_spec_per_pin *per_pin; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return -ENOMEM; + + spec->codec = codec; + codec->spec = spec; + hdmi_array_init(spec, 1); + + spec->multiout.num_dacs = 0; /* no analog */ + spec->multiout.max_channels = 2; + spec->multiout.dig_out_nid = cvt_nid; + spec->num_cvts = 1; + spec->num_pins = 1; + per_pin = snd_array_new(&spec->pins); + per_cvt = snd_array_new(&spec->cvts); + if (!per_pin || !per_cvt) { + simple_playback_free(codec); + return -ENOMEM; + } + per_cvt->cvt_nid = cvt_nid; + per_pin->pin_nid = pin_nid; + spec->pcm_playback = simple_pcm_playback; + + codec->patch_ops = simple_hdmi_patch_ops; + + return 0; +} + +static void nvhdmi_8ch_7x_set_info_frame_parameters(struct hda_codec *codec, + int channels) +{ + unsigned int chanmask; + int chan = channels ? (channels - 1) : 1; + + switch (channels) { + default: + case 0: + case 2: + chanmask = 0x00; + break; + case 4: + chanmask = 0x08; + break; + case 6: + chanmask = 0x0b; + break; + case 8: + chanmask = 0x13; + break; + } + + /* Set the audio infoframe channel allocation and checksum fields. The + * channel count is computed implicitly by the hardware. */ + snd_hda_codec_write(codec, 0x1, 0, + Nv_VERB_SET_Channel_Allocation, chanmask); + + snd_hda_codec_write(codec, 0x1, 0, + Nv_VERB_SET_Info_Frame_Checksum, + (0x71 - chan - chanmask)); +} + +static int nvhdmi_8ch_7x_pcm_close(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hdmi_spec *spec = codec->spec; + int i; + + snd_hda_codec_write(codec, nvhdmi_master_con_nid_7x, + 0, AC_VERB_SET_CHANNEL_STREAMID, 0); + for (i = 0; i < 4; i++) { + /* set the stream id */ + snd_hda_codec_write(codec, nvhdmi_con_nids_7x[i], 0, + AC_VERB_SET_CHANNEL_STREAMID, 0); + /* set the stream format */ + snd_hda_codec_write(codec, nvhdmi_con_nids_7x[i], 0, + AC_VERB_SET_STREAM_FORMAT, 0); + } + + /* The audio hardware sends a channel count of 0x7 (8ch) when all the + * streams are disabled. */ + nvhdmi_8ch_7x_set_info_frame_parameters(codec, 8); + + return snd_hda_multi_out_dig_close(codec, &spec->multiout); +} + +static int nvhdmi_8ch_7x_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + int chs; + unsigned int dataDCC2, channel_id; + int i; + struct hdmi_spec *spec = codec->spec; + struct hda_spdif_out *spdif; + struct hdmi_spec_per_cvt *per_cvt; + + mutex_lock(&codec->spdif_mutex); + per_cvt = get_cvt(spec, 0); + spdif = snd_hda_spdif_out_of_nid(codec, per_cvt->cvt_nid); + + chs = substream->runtime->channels; + + dataDCC2 = 0x2; + + /* turn off SPDIF once; otherwise the IEC958 bits won't be updated */ + if (codec->spdif_status_reset && (spdif->ctls & AC_DIG1_ENABLE)) + snd_hda_codec_write(codec, + nvhdmi_master_con_nid_7x, + 0, + AC_VERB_SET_DIGI_CONVERT_1, + spdif->ctls & ~AC_DIG1_ENABLE & 0xff); + + /* set the stream id */ + snd_hda_codec_write(codec, nvhdmi_master_con_nid_7x, 0, + AC_VERB_SET_CHANNEL_STREAMID, (stream_tag << 4) | 0x0); + + /* set the stream format */ + snd_hda_codec_write(codec, nvhdmi_master_con_nid_7x, 0, + AC_VERB_SET_STREAM_FORMAT, format); + + /* turn on again (if needed) */ + /* enable and set the channel status audio/data flag */ + if (codec->spdif_status_reset && (spdif->ctls & AC_DIG1_ENABLE)) { + snd_hda_codec_write(codec, + nvhdmi_master_con_nid_7x, + 0, + AC_VERB_SET_DIGI_CONVERT_1, + spdif->ctls & 0xff); + snd_hda_codec_write(codec, + nvhdmi_master_con_nid_7x, + 0, + AC_VERB_SET_DIGI_CONVERT_2, dataDCC2); + } + + for (i = 0; i < 4; i++) { + if (chs == 2) + channel_id = 0; + else + channel_id = i * 2; + + /* turn off SPDIF once; + *otherwise the IEC958 bits won't be updated + */ + if (codec->spdif_status_reset && + (spdif->ctls & AC_DIG1_ENABLE)) + snd_hda_codec_write(codec, + nvhdmi_con_nids_7x[i], + 0, + AC_VERB_SET_DIGI_CONVERT_1, + spdif->ctls & ~AC_DIG1_ENABLE & 0xff); + /* set the stream id */ + snd_hda_codec_write(codec, + nvhdmi_con_nids_7x[i], + 0, + AC_VERB_SET_CHANNEL_STREAMID, + (stream_tag << 4) | channel_id); + /* set the stream format */ + snd_hda_codec_write(codec, + nvhdmi_con_nids_7x[i], + 0, + AC_VERB_SET_STREAM_FORMAT, + format); + /* turn on again (if needed) */ + /* enable and set the channel status audio/data flag */ + if (codec->spdif_status_reset && + (spdif->ctls & AC_DIG1_ENABLE)) { + snd_hda_codec_write(codec, + nvhdmi_con_nids_7x[i], + 0, + AC_VERB_SET_DIGI_CONVERT_1, + spdif->ctls & 0xff); + snd_hda_codec_write(codec, + nvhdmi_con_nids_7x[i], + 0, + AC_VERB_SET_DIGI_CONVERT_2, dataDCC2); + } + } + + nvhdmi_8ch_7x_set_info_frame_parameters(codec, chs); + + mutex_unlock(&codec->spdif_mutex); + return 0; +} + +static const struct hda_pcm_stream nvhdmi_pcm_playback_8ch_7x = { + .substreams = 1, + .channels_min = 2, + .channels_max = 8, + .nid = nvhdmi_master_con_nid_7x, + .rates = SUPPORTED_RATES, + .maxbps = SUPPORTED_MAXBPS, + .formats = SUPPORTED_FORMATS, + .ops = { + .open = simple_playback_pcm_open, + .close = nvhdmi_8ch_7x_pcm_close, + .prepare = nvhdmi_8ch_7x_pcm_prepare + }, +}; + +static int patch_nvhdmi_2ch(struct hda_codec *codec) +{ + struct hdmi_spec *spec; + int err = patch_simple_hdmi(codec, nvhdmi_master_con_nid_7x, + nvhdmi_master_pin_nid_7x); + if (err < 0) + return err; + + codec->patch_ops.init = nvhdmi_7x_init_2ch; + /* override the PCM rates, etc, as the codec doesn't give full list */ + spec = codec->spec; + spec->pcm_playback.rates = SUPPORTED_RATES; + spec->pcm_playback.maxbps = SUPPORTED_MAXBPS; + spec->pcm_playback.formats = SUPPORTED_FORMATS; + spec->nv_dp_workaround = true; + return 0; +} + +static int nvhdmi_7x_8ch_build_pcms(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + int err = simple_playback_build_pcms(codec); + if (!err) { + struct hda_pcm *info = get_pcm_rec(spec, 0); + info->own_chmap = true; + } + return err; +} + +static int nvhdmi_7x_8ch_build_controls(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + struct hda_pcm *info; + struct snd_pcm_chmap *chmap; + int err; + + err = simple_playback_build_controls(codec); + if (err < 0) + return err; + + /* add channel maps */ + info = get_pcm_rec(spec, 0); + err = snd_pcm_add_chmap_ctls(info->pcm, + SNDRV_PCM_STREAM_PLAYBACK, + snd_pcm_alt_chmaps, 8, 0, &chmap); + if (err < 0) + return err; + switch (codec->preset->vendor_id) { + case 0x10de0002: + case 0x10de0003: + case 0x10de0005: + case 0x10de0006: + chmap->channel_mask = (1U << 2) | (1U << 8); + break; + case 0x10de0007: + chmap->channel_mask = (1U << 2) | (1U << 6) | (1U << 8); + } + return 0; +} + +static int patch_nvhdmi_8ch_7x(struct hda_codec *codec) +{ + struct hdmi_spec *spec; + int err = patch_nvhdmi_2ch(codec); + if (err < 0) + return err; + spec = codec->spec; + spec->multiout.max_channels = 8; + spec->pcm_playback = nvhdmi_pcm_playback_8ch_7x; + codec->patch_ops.init = nvhdmi_7x_init_8ch; + codec->patch_ops.build_pcms = nvhdmi_7x_8ch_build_pcms; + codec->patch_ops.build_controls = nvhdmi_7x_8ch_build_controls; + + /* Initialize the audio infoframe channel mask and checksum to something + * valid */ + nvhdmi_8ch_7x_set_info_frame_parameters(codec, 8); + + return 0; +} + +/* + * NVIDIA codecs ignore ASP mapping for 2ch - confirmed on: + * - 0x10de0015 + * - 0x10de0040 + */ +static int nvhdmi_chmap_cea_alloc_validate_get_type(struct hdac_chmap *chmap, + struct hdac_cea_channel_speaker_allocation *cap, int channels) +{ + if (cap->ca_index == 0x00 && channels == 2) + return SNDRV_CTL_TLVT_CHMAP_FIXED; + + /* If the speaker allocation matches the channel count, it is OK. */ + if (cap->channels != channels) + return -1; + + /* all channels are remappable freely */ + return SNDRV_CTL_TLVT_CHMAP_VAR; +} + +static int nvhdmi_chmap_validate(struct hdac_chmap *chmap, + int ca, int chs, unsigned char *map) +{ + if (ca == 0x00 && (map[0] != SNDRV_CHMAP_FL || map[1] != SNDRV_CHMAP_FR)) + return -EINVAL; + + return 0; +} + +/* map from pin NID to port; port is 0-based */ +/* for Nvidia: assume widget NID starting from 4, with step 1 (4, 5, 6, ...) */ +static int nvhdmi_pin2port(void *audio_ptr, int pin_nid) +{ + return pin_nid - 4; +} + +/* reverse-map from port to pin NID: see above */ +static int nvhdmi_port2pin(struct hda_codec *codec, int port) +{ + return port + 4; +} + +static const struct drm_audio_component_audio_ops nvhdmi_audio_ops = { + .pin2port = nvhdmi_pin2port, + .pin_eld_notify = generic_acomp_pin_eld_notify, + .master_bind = generic_acomp_master_bind, + .master_unbind = generic_acomp_master_unbind, +}; + +static int patch_nvhdmi(struct hda_codec *codec) +{ + struct hdmi_spec *spec; + int err; + + err = alloc_generic_hdmi(codec); + if (err < 0) + return err; + codec->dp_mst = true; + + spec = codec->spec; + + err = hdmi_parse_codec(codec); + if (err < 0) { + generic_spec_free(codec); + return err; + } + + generic_hdmi_init_per_pins(codec); + + spec->dyn_pin_out = true; + + spec->chmap.ops.chmap_cea_alloc_validate_get_type = + nvhdmi_chmap_cea_alloc_validate_get_type; + spec->chmap.ops.chmap_validate = nvhdmi_chmap_validate; + spec->nv_dp_workaround = true; + + codec->link_down_at_suspend = 1; + + generic_acomp_init(codec, &nvhdmi_audio_ops, nvhdmi_port2pin); + + return 0; +} + +static int patch_nvhdmi_legacy(struct hda_codec *codec) +{ + struct hdmi_spec *spec; + int err; + + err = patch_generic_hdmi(codec); + if (err) + return err; + + spec = codec->spec; + spec->dyn_pin_out = true; + + spec->chmap.ops.chmap_cea_alloc_validate_get_type = + nvhdmi_chmap_cea_alloc_validate_get_type; + spec->chmap.ops.chmap_validate = nvhdmi_chmap_validate; + spec->nv_dp_workaround = true; + + codec->link_down_at_suspend = 1; + + return 0; +} + +/* + * The HDA codec on NVIDIA Tegra contains two scratch registers that are + * accessed using vendor-defined verbs. These registers can be used for + * interoperability between the HDA and HDMI drivers. + */ + +/* Audio Function Group node */ +#define NVIDIA_AFG_NID 0x01 + +/* + * The SCRATCH0 register is used to notify the HDMI codec of changes in audio + * format. On Tegra, bit 31 is used as a trigger that causes an interrupt to + * be raised in the HDMI codec. The remainder of the bits is arbitrary. This + * implementation stores the HDA format (see AC_FMT_*) in bits [15:0] and an + * additional bit (at position 30) to signal the validity of the format. + * + * | 31 | 30 | 29 16 | 15 0 | + * +---------+-------+--------+--------+ + * | TRIGGER | VALID | UNUSED | FORMAT | + * +-----------------------------------| + * + * Note that for the trigger bit to take effect it needs to change value + * (i.e. it needs to be toggled). The trigger bit is not applicable from + * TEGRA234 chip onwards, as new verb id 0xf80 will be used for interrupt + * trigger to hdmi. + */ +#define NVIDIA_SET_HOST_INTR 0xf80 +#define NVIDIA_GET_SCRATCH0 0xfa6 +#define NVIDIA_SET_SCRATCH0_BYTE0 0xfa7 +#define NVIDIA_SET_SCRATCH0_BYTE1 0xfa8 +#define NVIDIA_SET_SCRATCH0_BYTE2 0xfa9 +#define NVIDIA_SET_SCRATCH0_BYTE3 0xfaa +#define NVIDIA_SCRATCH_TRIGGER (1 << 7) +#define NVIDIA_SCRATCH_VALID (1 << 6) + +#define NVIDIA_GET_SCRATCH1 0xfab +#define NVIDIA_SET_SCRATCH1_BYTE0 0xfac +#define NVIDIA_SET_SCRATCH1_BYTE1 0xfad +#define NVIDIA_SET_SCRATCH1_BYTE2 0xfae +#define NVIDIA_SET_SCRATCH1_BYTE3 0xfaf + +/* + * The format parameter is the HDA audio format (see AC_FMT_*). If set to 0, + * the format is invalidated so that the HDMI codec can be disabled. + */ +static void tegra_hdmi_set_format(struct hda_codec *codec, + hda_nid_t cvt_nid, + unsigned int format) +{ + unsigned int value; + unsigned int nid = NVIDIA_AFG_NID; + struct hdmi_spec *spec = codec->spec; + + /* + * Tegra HDA codec design from TEGRA234 chip onwards support DP MST. + * This resulted in moving scratch registers from audio function + * group to converter widget context. So CVT NID should be used for + * scratch register read/write for DP MST supported Tegra HDA codec. + */ + if (codec->dp_mst) + nid = cvt_nid; + + /* bits [31:30] contain the trigger and valid bits */ + value = snd_hda_codec_read(codec, nid, 0, + NVIDIA_GET_SCRATCH0, 0); + value = (value >> 24) & 0xff; + + /* bits [15:0] are used to store the HDA format */ + snd_hda_codec_write(codec, nid, 0, + NVIDIA_SET_SCRATCH0_BYTE0, + (format >> 0) & 0xff); + snd_hda_codec_write(codec, nid, 0, + NVIDIA_SET_SCRATCH0_BYTE1, + (format >> 8) & 0xff); + + /* bits [16:24] are unused */ + snd_hda_codec_write(codec, nid, 0, + NVIDIA_SET_SCRATCH0_BYTE2, 0); + + /* + * Bit 30 signals that the data is valid and hence that HDMI audio can + * be enabled. + */ + if (format == 0) + value &= ~NVIDIA_SCRATCH_VALID; + else + value |= NVIDIA_SCRATCH_VALID; + + if (spec->hdmi_intr_trig_ctrl) { + /* + * For Tegra HDA Codec design from TEGRA234 onwards, the + * Interrupt to hdmi driver is triggered by writing + * non-zero values to verb 0xF80 instead of 31st bit of + * scratch register. + */ + snd_hda_codec_write(codec, nid, 0, + NVIDIA_SET_SCRATCH0_BYTE3, value); + snd_hda_codec_write(codec, nid, 0, + NVIDIA_SET_HOST_INTR, 0x1); + } else { + /* + * Whenever the 31st trigger bit is toggled, an interrupt is raised + * in the HDMI codec. The HDMI driver will use that as trigger + * to update its configuration. + */ + value ^= NVIDIA_SCRATCH_TRIGGER; + + snd_hda_codec_write(codec, nid, 0, + NVIDIA_SET_SCRATCH0_BYTE3, value); + } +} + +static int tegra_hdmi_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + int err; + + err = generic_hdmi_playback_pcm_prepare(hinfo, codec, stream_tag, + format, substream); + if (err < 0) + return err; + + /* notify the HDMI codec of the format change */ + tegra_hdmi_set_format(codec, hinfo->nid, format); + + return 0; +} + +static int tegra_hdmi_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + /* invalidate the format in the HDMI codec */ + tegra_hdmi_set_format(codec, hinfo->nid, 0); + + return generic_hdmi_playback_pcm_cleanup(hinfo, codec, substream); +} + +static struct hda_pcm *hda_find_pcm_by_type(struct hda_codec *codec, int type) +{ + struct hdmi_spec *spec = codec->spec; + unsigned int i; + + for (i = 0; i < spec->num_pins; i++) { + struct hda_pcm *pcm = get_pcm_rec(spec, i); + + if (pcm->pcm_type == type) + return pcm; + } + + return NULL; +} + +static int tegra_hdmi_build_pcms(struct hda_codec *codec) +{ + struct hda_pcm_stream *stream; + struct hda_pcm *pcm; + int err; + + err = generic_hdmi_build_pcms(codec); + if (err < 0) + return err; + + pcm = hda_find_pcm_by_type(codec, HDA_PCM_TYPE_HDMI); + if (!pcm) + return -ENODEV; + + /* + * Override ->prepare() and ->cleanup() operations to notify the HDMI + * codec about format changes. + */ + stream = &pcm->stream[SNDRV_PCM_STREAM_PLAYBACK]; + stream->ops.prepare = tegra_hdmi_pcm_prepare; + stream->ops.cleanup = tegra_hdmi_pcm_cleanup; + + return 0; +} + +static int tegra_hdmi_init(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + int i, err; + + err = hdmi_parse_codec(codec); + if (err < 0) { + generic_spec_free(codec); + return err; + } + + for (i = 0; i < spec->num_cvts; i++) + snd_hda_codec_write(codec, spec->cvt_nids[i], 0, + AC_VERB_SET_DIGI_CONVERT_1, + AC_DIG1_ENABLE); + + generic_hdmi_init_per_pins(codec); + + codec->depop_delay = 10; + codec->patch_ops.build_pcms = tegra_hdmi_build_pcms; + spec->chmap.ops.chmap_cea_alloc_validate_get_type = + nvhdmi_chmap_cea_alloc_validate_get_type; + spec->chmap.ops.chmap_validate = nvhdmi_chmap_validate; + + spec->chmap.ops.chmap_cea_alloc_validate_get_type = + nvhdmi_chmap_cea_alloc_validate_get_type; + spec->chmap.ops.chmap_validate = nvhdmi_chmap_validate; + spec->nv_dp_workaround = true; + + return 0; +} + +static int patch_tegra_hdmi(struct hda_codec *codec) +{ + int err; + + err = alloc_generic_hdmi(codec); + if (err < 0) + return err; + + return tegra_hdmi_init(codec); +} + +static int patch_tegra234_hdmi(struct hda_codec *codec) +{ + struct hdmi_spec *spec; + int err; + + err = alloc_generic_hdmi(codec); + if (err < 0) + return err; + + codec->dp_mst = true; + spec = codec->spec; + spec->dyn_pin_out = true; + spec->hdmi_intr_trig_ctrl = true; + + return tegra_hdmi_init(codec); +} + +/* + * ATI/AMD-specific implementations + */ + +#define is_amdhdmi_rev3_or_later(codec) \ + ((codec)->core.vendor_id == 0x1002aa01 && \ + ((codec)->core.revision_id & 0xff00) >= 0x0300) +#define has_amd_full_remap_support(codec) is_amdhdmi_rev3_or_later(codec) + +/* ATI/AMD specific HDA pin verbs, see the AMD HDA Verbs specification */ +#define ATI_VERB_SET_CHANNEL_ALLOCATION 0x771 +#define ATI_VERB_SET_DOWNMIX_INFO 0x772 +#define ATI_VERB_SET_MULTICHANNEL_01 0x777 +#define ATI_VERB_SET_MULTICHANNEL_23 0x778 +#define ATI_VERB_SET_MULTICHANNEL_45 0x779 +#define ATI_VERB_SET_MULTICHANNEL_67 0x77a +#define ATI_VERB_SET_HBR_CONTROL 0x77c +#define ATI_VERB_SET_MULTICHANNEL_1 0x785 +#define ATI_VERB_SET_MULTICHANNEL_3 0x786 +#define ATI_VERB_SET_MULTICHANNEL_5 0x787 +#define ATI_VERB_SET_MULTICHANNEL_7 0x788 +#define ATI_VERB_SET_MULTICHANNEL_MODE 0x789 +#define ATI_VERB_GET_CHANNEL_ALLOCATION 0xf71 +#define ATI_VERB_GET_DOWNMIX_INFO 0xf72 +#define ATI_VERB_GET_MULTICHANNEL_01 0xf77 +#define ATI_VERB_GET_MULTICHANNEL_23 0xf78 +#define ATI_VERB_GET_MULTICHANNEL_45 0xf79 +#define ATI_VERB_GET_MULTICHANNEL_67 0xf7a +#define ATI_VERB_GET_HBR_CONTROL 0xf7c +#define ATI_VERB_GET_MULTICHANNEL_1 0xf85 +#define ATI_VERB_GET_MULTICHANNEL_3 0xf86 +#define ATI_VERB_GET_MULTICHANNEL_5 0xf87 +#define ATI_VERB_GET_MULTICHANNEL_7 0xf88 +#define ATI_VERB_GET_MULTICHANNEL_MODE 0xf89 + +/* AMD specific HDA cvt verbs */ +#define ATI_VERB_SET_RAMP_RATE 0x770 +#define ATI_VERB_GET_RAMP_RATE 0xf70 + +#define ATI_OUT_ENABLE 0x1 + +#define ATI_MULTICHANNEL_MODE_PAIRED 0 +#define ATI_MULTICHANNEL_MODE_SINGLE 1 + +#define ATI_HBR_CAPABLE 0x01 +#define ATI_HBR_ENABLE 0x10 + +static int atihdmi_pin_get_eld(struct hda_codec *codec, hda_nid_t nid, + int dev_id, unsigned char *buf, int *eld_size) +{ + WARN_ON(dev_id != 0); + /* call hda_eld.c ATI/AMD-specific function */ + return snd_hdmi_get_eld_ati(codec, nid, buf, eld_size, + is_amdhdmi_rev3_or_later(codec)); +} + +static void atihdmi_pin_setup_infoframe(struct hda_codec *codec, + hda_nid_t pin_nid, int dev_id, int ca, + int active_channels, int conn_type) +{ + WARN_ON(dev_id != 0); + snd_hda_codec_write(codec, pin_nid, 0, ATI_VERB_SET_CHANNEL_ALLOCATION, ca); +} + +static int atihdmi_paired_swap_fc_lfe(int pos) +{ + /* + * ATI/AMD have automatic FC/LFE swap built-in + * when in pairwise mapping mode. + */ + + switch (pos) { + /* see channel_allocations[].speakers[] */ + case 2: return 3; + case 3: return 2; + default: break; + } + + return pos; +} + +static int atihdmi_paired_chmap_validate(struct hdac_chmap *chmap, + int ca, int chs, unsigned char *map) +{ + struct hdac_cea_channel_speaker_allocation *cap; + int i, j; + + /* check that only channel pairs need to be remapped on old pre-rev3 ATI/AMD */ + + cap = snd_hdac_get_ch_alloc_from_ca(ca); + for (i = 0; i < chs; ++i) { + int mask = snd_hdac_chmap_to_spk_mask(map[i]); + bool ok = false; + bool companion_ok = false; + + if (!mask) + continue; + + for (j = 0 + i % 2; j < 8; j += 2) { + int chan_idx = 7 - atihdmi_paired_swap_fc_lfe(j); + if (cap->speakers[chan_idx] == mask) { + /* channel is in a supported position */ + ok = true; + + if (i % 2 == 0 && i + 1 < chs) { + /* even channel, check the odd companion */ + int comp_chan_idx = 7 - atihdmi_paired_swap_fc_lfe(j + 1); + int comp_mask_req = snd_hdac_chmap_to_spk_mask(map[i+1]); + int comp_mask_act = cap->speakers[comp_chan_idx]; + + if (comp_mask_req == comp_mask_act) + companion_ok = true; + else + return -EINVAL; + } + break; + } + } + + if (!ok) + return -EINVAL; + + if (companion_ok) + i++; /* companion channel already checked */ + } + + return 0; +} + +static int atihdmi_pin_set_slot_channel(struct hdac_device *hdac, + hda_nid_t pin_nid, int hdmi_slot, int stream_channel) +{ + struct hda_codec *codec = hdac_to_hda_codec(hdac); + int verb; + int ati_channel_setup = 0; + + if (hdmi_slot > 7) + return -EINVAL; + + if (!has_amd_full_remap_support(codec)) { + hdmi_slot = atihdmi_paired_swap_fc_lfe(hdmi_slot); + + /* In case this is an odd slot but without stream channel, do not + * disable the slot since the corresponding even slot could have a + * channel. In case neither have a channel, the slot pair will be + * disabled when this function is called for the even slot. */ + if (hdmi_slot % 2 != 0 && stream_channel == 0xf) + return 0; + + hdmi_slot -= hdmi_slot % 2; + + if (stream_channel != 0xf) + stream_channel -= stream_channel % 2; + } + + verb = ATI_VERB_SET_MULTICHANNEL_01 + hdmi_slot/2 + (hdmi_slot % 2) * 0x00e; + + /* ati_channel_setup format: [7..4] = stream_channel_id, [1] = mute, [0] = enable */ + + if (stream_channel != 0xf) + ati_channel_setup = (stream_channel << 4) | ATI_OUT_ENABLE; + + return snd_hda_codec_write(codec, pin_nid, 0, verb, ati_channel_setup); +} + +static int atihdmi_pin_get_slot_channel(struct hdac_device *hdac, + hda_nid_t pin_nid, int asp_slot) +{ + struct hda_codec *codec = hdac_to_hda_codec(hdac); + bool was_odd = false; + int ati_asp_slot = asp_slot; + int verb; + int ati_channel_setup; + + if (asp_slot > 7) + return -EINVAL; + + if (!has_amd_full_remap_support(codec)) { + ati_asp_slot = atihdmi_paired_swap_fc_lfe(asp_slot); + if (ati_asp_slot % 2 != 0) { + ati_asp_slot -= 1; + was_odd = true; + } + } + + verb = ATI_VERB_GET_MULTICHANNEL_01 + ati_asp_slot/2 + (ati_asp_slot % 2) * 0x00e; + + ati_channel_setup = snd_hda_codec_read(codec, pin_nid, 0, verb, 0); + + if (!(ati_channel_setup & ATI_OUT_ENABLE)) + return 0xf; + + return ((ati_channel_setup & 0xf0) >> 4) + !!was_odd; +} + +static int atihdmi_paired_chmap_cea_alloc_validate_get_type( + struct hdac_chmap *chmap, + struct hdac_cea_channel_speaker_allocation *cap, + int channels) +{ + int c; + + /* + * Pre-rev3 ATI/AMD codecs operate in a paired channel mode, so + * we need to take that into account (a single channel may take 2 + * channel slots if we need to carry a silent channel next to it). + * On Rev3+ AMD codecs this function is not used. + */ + int chanpairs = 0; + + /* We only produce even-numbered channel count TLVs */ + if ((channels % 2) != 0) + return -1; + + for (c = 0; c < 7; c += 2) { + if (cap->speakers[c] || cap->speakers[c+1]) + chanpairs++; + } + + if (chanpairs * 2 != channels) + return -1; + + return SNDRV_CTL_TLVT_CHMAP_PAIRED; +} + +static void atihdmi_paired_cea_alloc_to_tlv_chmap(struct hdac_chmap *hchmap, + struct hdac_cea_channel_speaker_allocation *cap, + unsigned int *chmap, int channels) +{ + /* produce paired maps for pre-rev3 ATI/AMD codecs */ + int count = 0; + int c; + + for (c = 7; c >= 0; c--) { + int chan = 7 - atihdmi_paired_swap_fc_lfe(7 - c); + int spk = cap->speakers[chan]; + if (!spk) { + /* add N/A channel if the companion channel is occupied */ + if (cap->speakers[chan + (chan % 2 ? -1 : 1)]) + chmap[count++] = SNDRV_CHMAP_NA; + + continue; + } + + chmap[count++] = snd_hdac_spk_to_chmap(spk); + } + + WARN_ON(count != channels); +} + +static int atihdmi_pin_hbr_setup(struct hda_codec *codec, hda_nid_t pin_nid, + int dev_id, bool hbr) +{ + int hbr_ctl, hbr_ctl_new; + + WARN_ON(dev_id != 0); + + hbr_ctl = snd_hda_codec_read(codec, pin_nid, 0, ATI_VERB_GET_HBR_CONTROL, 0); + if (hbr_ctl >= 0 && (hbr_ctl & ATI_HBR_CAPABLE)) { + if (hbr) + hbr_ctl_new = hbr_ctl | ATI_HBR_ENABLE; + else + hbr_ctl_new = hbr_ctl & ~ATI_HBR_ENABLE; + + codec_dbg(codec, + "atihdmi_pin_hbr_setup: NID=0x%x, %shbr-ctl=0x%x\n", + pin_nid, + hbr_ctl == hbr_ctl_new ? "" : "new-", + hbr_ctl_new); + + if (hbr_ctl != hbr_ctl_new) + snd_hda_codec_write(codec, pin_nid, 0, + ATI_VERB_SET_HBR_CONTROL, + hbr_ctl_new); + + } else if (hbr) + return -EINVAL; + + return 0; +} + +static int atihdmi_setup_stream(struct hda_codec *codec, hda_nid_t cvt_nid, + hda_nid_t pin_nid, int dev_id, + u32 stream_tag, int format) +{ + if (is_amdhdmi_rev3_or_later(codec)) { + int ramp_rate = 180; /* default as per AMD spec */ + /* disable ramp-up/down for non-pcm as per AMD spec */ + if (format & AC_FMT_TYPE_NON_PCM) + ramp_rate = 0; + + snd_hda_codec_write(codec, cvt_nid, 0, ATI_VERB_SET_RAMP_RATE, ramp_rate); + } + + return hdmi_setup_stream(codec, cvt_nid, pin_nid, dev_id, + stream_tag, format); +} + + +static int atihdmi_init(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + int pin_idx, err; + + err = generic_hdmi_init(codec); + + if (err) + return err; + + for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { + struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx); + + /* make sure downmix information in infoframe is zero */ + snd_hda_codec_write(codec, per_pin->pin_nid, 0, ATI_VERB_SET_DOWNMIX_INFO, 0); + + /* enable channel-wise remap mode if supported */ + if (has_amd_full_remap_support(codec)) + snd_hda_codec_write(codec, per_pin->pin_nid, 0, + ATI_VERB_SET_MULTICHANNEL_MODE, + ATI_MULTICHANNEL_MODE_SINGLE); + } + codec->auto_runtime_pm = 1; + + return 0; +} + +/* map from pin NID to port; port is 0-based */ +/* for AMD: assume widget NID starting from 3, with step 2 (3, 5, 7, ...) */ +static int atihdmi_pin2port(void *audio_ptr, int pin_nid) +{ + return pin_nid / 2 - 1; +} + +/* reverse-map from port to pin NID: see above */ +static int atihdmi_port2pin(struct hda_codec *codec, int port) +{ + return port * 2 + 3; +} + +static const struct drm_audio_component_audio_ops atihdmi_audio_ops = { + .pin2port = atihdmi_pin2port, + .pin_eld_notify = generic_acomp_pin_eld_notify, + .master_bind = generic_acomp_master_bind, + .master_unbind = generic_acomp_master_unbind, +}; + +static int patch_atihdmi(struct hda_codec *codec) +{ + struct hdmi_spec *spec; + struct hdmi_spec_per_cvt *per_cvt; + int err, cvt_idx; + + err = patch_generic_hdmi(codec); + + if (err) + return err; + + codec->patch_ops.init = atihdmi_init; + + spec = codec->spec; + + spec->static_pcm_mapping = true; + + spec->ops.pin_get_eld = atihdmi_pin_get_eld; + spec->ops.pin_setup_infoframe = atihdmi_pin_setup_infoframe; + spec->ops.pin_hbr_setup = atihdmi_pin_hbr_setup; + spec->ops.setup_stream = atihdmi_setup_stream; + + spec->chmap.ops.pin_get_slot_channel = atihdmi_pin_get_slot_channel; + spec->chmap.ops.pin_set_slot_channel = atihdmi_pin_set_slot_channel; + + if (!has_amd_full_remap_support(codec)) { + /* override to ATI/AMD-specific versions with pairwise mapping */ + spec->chmap.ops.chmap_cea_alloc_validate_get_type = + atihdmi_paired_chmap_cea_alloc_validate_get_type; + spec->chmap.ops.cea_alloc_to_tlv_chmap = + atihdmi_paired_cea_alloc_to_tlv_chmap; + spec->chmap.ops.chmap_validate = atihdmi_paired_chmap_validate; + } + + /* ATI/AMD converters do not advertise all of their capabilities */ + for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++) { + per_cvt = get_cvt(spec, cvt_idx); + per_cvt->channels_max = max(per_cvt->channels_max, 8u); + per_cvt->rates |= SUPPORTED_RATES; + per_cvt->formats |= SUPPORTED_FORMATS; + per_cvt->maxbps = max(per_cvt->maxbps, 24u); + } + + spec->chmap.channels_max = max(spec->chmap.channels_max, 8u); + + /* AMD GPUs have neither EPSS nor CLKSTOP bits, hence preventing + * the link-down as is. Tell the core to allow it. + */ + codec->link_down_at_suspend = 1; + + generic_acomp_init(codec, &atihdmi_audio_ops, atihdmi_port2pin); + + return 0; +} + +/* VIA HDMI Implementation */ +#define VIAHDMI_CVT_NID 0x02 /* audio converter1 */ +#define VIAHDMI_PIN_NID 0x03 /* HDMI output pin1 */ + +static int patch_via_hdmi(struct hda_codec *codec) +{ + return patch_simple_hdmi(codec, VIAHDMI_CVT_NID, VIAHDMI_PIN_NID); +} + +static int patch_gf_hdmi(struct hda_codec *codec) +{ + int err; + + err = patch_generic_hdmi(codec); + if (err) + return err; + + /* + * Glenfly GPUs have two codecs, stream switches from one codec to + * another, need to do actual clean-ups in codec_cleanup_stream + */ + codec->no_sticky_stream = 1; + return 0; +} + +/* + * patch entries + */ +static const struct hda_device_id snd_hda_id_hdmi[] = { +HDA_CODEC_ENTRY(0x1002793c, "RS600 HDMI", patch_atihdmi), +HDA_CODEC_ENTRY(0x10027919, "RS600 HDMI", patch_atihdmi), +HDA_CODEC_ENTRY(0x1002791a, "RS690/780 HDMI", patch_atihdmi), +HDA_CODEC_ENTRY(0x1002aa01, "R6xx HDMI", patch_atihdmi), +HDA_CODEC_ENTRY(0x10951390, "SiI1390 HDMI", patch_generic_hdmi), +HDA_CODEC_ENTRY(0x10951392, "SiI1392 HDMI", patch_generic_hdmi), +HDA_CODEC_ENTRY(0x17e80047, "Chrontel HDMI", patch_generic_hdmi), +HDA_CODEC_ENTRY(0x10de0001, "MCP73 HDMI", patch_nvhdmi_2ch), +HDA_CODEC_ENTRY(0x10de0002, "MCP77/78 HDMI", patch_nvhdmi_8ch_7x), +HDA_CODEC_ENTRY(0x10de0003, "MCP77/78 HDMI", patch_nvhdmi_8ch_7x), +HDA_CODEC_ENTRY(0x10de0004, "GPU 04 HDMI", patch_nvhdmi_8ch_7x), +HDA_CODEC_ENTRY(0x10de0005, "MCP77/78 HDMI", patch_nvhdmi_8ch_7x), +HDA_CODEC_ENTRY(0x10de0006, "MCP77/78 HDMI", patch_nvhdmi_8ch_7x), +HDA_CODEC_ENTRY(0x10de0007, "MCP79/7A HDMI", patch_nvhdmi_8ch_7x), +HDA_CODEC_ENTRY(0x10de0008, "GPU 08 HDMI/DP", patch_nvhdmi_legacy), +HDA_CODEC_ENTRY(0x10de0009, "GPU 09 HDMI/DP", patch_nvhdmi_legacy), +HDA_CODEC_ENTRY(0x10de000a, "GPU 0a HDMI/DP", patch_nvhdmi_legacy), +HDA_CODEC_ENTRY(0x10de000b, "GPU 0b HDMI/DP", patch_nvhdmi_legacy), +HDA_CODEC_ENTRY(0x10de000c, "MCP89 HDMI", patch_nvhdmi_legacy), +HDA_CODEC_ENTRY(0x10de000d, "GPU 0d HDMI/DP", patch_nvhdmi_legacy), +HDA_CODEC_ENTRY(0x10de0010, "GPU 10 HDMI/DP", patch_nvhdmi_legacy), +HDA_CODEC_ENTRY(0x10de0011, "GPU 11 HDMI/DP", patch_nvhdmi_legacy), +HDA_CODEC_ENTRY(0x10de0012, "GPU 12 HDMI/DP", patch_nvhdmi_legacy), +HDA_CODEC_ENTRY(0x10de0013, "GPU 13 HDMI/DP", patch_nvhdmi_legacy), +HDA_CODEC_ENTRY(0x10de0014, "GPU 14 HDMI/DP", patch_nvhdmi_legacy), +HDA_CODEC_ENTRY(0x10de0015, "GPU 15 HDMI/DP", patch_nvhdmi_legacy), +HDA_CODEC_ENTRY(0x10de0016, "GPU 16 HDMI/DP", patch_nvhdmi_legacy), +/* 17 is known to be absent */ +HDA_CODEC_ENTRY(0x10de0018, "GPU 18 HDMI/DP", patch_nvhdmi_legacy), +HDA_CODEC_ENTRY(0x10de0019, "GPU 19 HDMI/DP", patch_nvhdmi_legacy), +HDA_CODEC_ENTRY(0x10de001a, "GPU 1a HDMI/DP", patch_nvhdmi_legacy), +HDA_CODEC_ENTRY(0x10de001b, "GPU 1b HDMI/DP", patch_nvhdmi_legacy), +HDA_CODEC_ENTRY(0x10de001c, "GPU 1c HDMI/DP", patch_nvhdmi_legacy), +HDA_CODEC_ENTRY(0x10de0020, "Tegra30 HDMI", patch_tegra_hdmi), +HDA_CODEC_ENTRY(0x10de0022, "Tegra114 HDMI", patch_tegra_hdmi), +HDA_CODEC_ENTRY(0x10de0028, "Tegra124 HDMI", patch_tegra_hdmi), +HDA_CODEC_ENTRY(0x10de0029, "Tegra210 HDMI/DP", patch_tegra_hdmi), +HDA_CODEC_ENTRY(0x10de002d, "Tegra186 HDMI/DP0", patch_tegra_hdmi), +HDA_CODEC_ENTRY(0x10de002e, "Tegra186 HDMI/DP1", patch_tegra_hdmi), +HDA_CODEC_ENTRY(0x10de002f, "Tegra194 HDMI/DP2", patch_tegra_hdmi), +HDA_CODEC_ENTRY(0x10de0030, "Tegra194 HDMI/DP3", patch_tegra_hdmi), +HDA_CODEC_ENTRY(0x10de0031, "Tegra234 HDMI/DP", patch_tegra234_hdmi), +HDA_CODEC_ENTRY(0x10de0040, "GPU 40 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0041, "GPU 41 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0042, "GPU 42 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0043, "GPU 43 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0044, "GPU 44 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0045, "GPU 45 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0050, "GPU 50 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0051, "GPU 51 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0052, "GPU 52 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0060, "GPU 60 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0061, "GPU 61 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0062, "GPU 62 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0067, "MCP67 HDMI", patch_nvhdmi_2ch), +HDA_CODEC_ENTRY(0x10de0070, "GPU 70 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0071, "GPU 71 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0072, "GPU 72 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0073, "GPU 73 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0074, "GPU 74 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0076, "GPU 76 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de007b, "GPU 7b HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de007c, "GPU 7c HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de007d, "GPU 7d HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de007e, "GPU 7e HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0080, "GPU 80 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0081, "GPU 81 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0082, "GPU 82 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0083, "GPU 83 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0084, "GPU 84 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0090, "GPU 90 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0091, "GPU 91 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0092, "GPU 92 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0093, "GPU 93 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0094, "GPU 94 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0095, "GPU 95 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0097, "GPU 97 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0098, "GPU 98 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0099, "GPU 99 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de009a, "GPU 9a HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de009d, "GPU 9d HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de009e, "GPU 9e HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de009f, "GPU 9f HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de00a0, "GPU a0 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de00a3, "GPU a3 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de00a4, "GPU a4 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de00a5, "GPU a5 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de00a6, "GPU a6 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de00a7, "GPU a7 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de8001, "MCP73 HDMI", patch_nvhdmi_2ch), +HDA_CODEC_ENTRY(0x10de8067, "MCP67/68 HDMI", patch_nvhdmi_2ch), +HDA_CODEC_ENTRY(0x67663d82, "Arise 82 HDMI/DP", patch_gf_hdmi), +HDA_CODEC_ENTRY(0x67663d83, "Arise 83 HDMI/DP", patch_gf_hdmi), +HDA_CODEC_ENTRY(0x67663d84, "Arise 84 HDMI/DP", patch_gf_hdmi), +HDA_CODEC_ENTRY(0x67663d85, "Arise 85 HDMI/DP", patch_gf_hdmi), +HDA_CODEC_ENTRY(0x67663d86, "Arise 86 HDMI/DP", patch_gf_hdmi), +HDA_CODEC_ENTRY(0x67663d87, "Arise 87 HDMI/DP", patch_gf_hdmi), +HDA_CODEC_ENTRY(0x11069f80, "VX900 HDMI/DP", patch_via_hdmi), +HDA_CODEC_ENTRY(0x11069f81, "VX900 HDMI/DP", patch_via_hdmi), +HDA_CODEC_ENTRY(0x11069f84, "VX11 HDMI/DP", patch_generic_hdmi), +HDA_CODEC_ENTRY(0x11069f85, "VX11 HDMI/DP", patch_generic_hdmi), +HDA_CODEC_ENTRY(0x80860054, "IbexPeak HDMI", patch_i915_cpt_hdmi), +HDA_CODEC_ENTRY(0x80862800, "Geminilake HDMI", patch_i915_glk_hdmi), +HDA_CODEC_ENTRY(0x80862801, "Bearlake HDMI", patch_generic_hdmi), +HDA_CODEC_ENTRY(0x80862802, "Cantiga HDMI", patch_generic_hdmi), +HDA_CODEC_ENTRY(0x80862803, "Eaglelake HDMI", patch_generic_hdmi), +HDA_CODEC_ENTRY(0x80862804, "IbexPeak HDMI", patch_i915_cpt_hdmi), +HDA_CODEC_ENTRY(0x80862805, "CougarPoint HDMI", patch_i915_cpt_hdmi), +HDA_CODEC_ENTRY(0x80862806, "PantherPoint HDMI", patch_i915_cpt_hdmi), +HDA_CODEC_ENTRY(0x80862807, "Haswell HDMI", patch_i915_hsw_hdmi), +HDA_CODEC_ENTRY(0x80862808, "Broadwell HDMI", patch_i915_hsw_hdmi), +HDA_CODEC_ENTRY(0x80862809, "Skylake HDMI", patch_i915_hsw_hdmi), +HDA_CODEC_ENTRY(0x8086280a, "Broxton HDMI", patch_i915_hsw_hdmi), +HDA_CODEC_ENTRY(0x8086280b, "Kabylake HDMI", patch_i915_hsw_hdmi), +HDA_CODEC_ENTRY(0x8086280c, "Cannonlake HDMI", patch_i915_glk_hdmi), +HDA_CODEC_ENTRY(0x8086280d, "Geminilake HDMI", patch_i915_glk_hdmi), +HDA_CODEC_ENTRY(0x8086280f, "Icelake HDMI", patch_i915_icl_hdmi), +HDA_CODEC_ENTRY(0x80862812, "Tigerlake HDMI", patch_i915_tgl_hdmi), +HDA_CODEC_ENTRY(0x80862814, "DG1 HDMI", patch_i915_tgl_hdmi), +HDA_CODEC_ENTRY(0x80862815, "Alderlake HDMI", patch_i915_tgl_hdmi), +HDA_CODEC_ENTRY(0x80862816, "Rocketlake HDMI", patch_i915_tgl_hdmi), +HDA_CODEC_ENTRY(0x80862818, "Raptorlake HDMI", patch_i915_tgl_hdmi), +HDA_CODEC_ENTRY(0x80862819, "DG2 HDMI", patch_i915_tgl_hdmi), +HDA_CODEC_ENTRY(0x8086281a, "Jasperlake HDMI", patch_i915_icl_hdmi), +HDA_CODEC_ENTRY(0x8086281b, "Elkhartlake HDMI", patch_i915_icl_hdmi), +HDA_CODEC_ENTRY(0x8086281c, "Alderlake-P HDMI", patch_i915_adlp_hdmi), +HDA_CODEC_ENTRY(0x8086281f, "Raptorlake-P HDMI", patch_i915_adlp_hdmi), +HDA_CODEC_ENTRY(0x8086281d, "Meteorlake HDMI", patch_i915_adlp_hdmi), +HDA_CODEC_ENTRY(0x80862880, "CedarTrail HDMI", patch_generic_hdmi), +HDA_CODEC_ENTRY(0x80862882, "Valleyview2 HDMI", patch_i915_byt_hdmi), +HDA_CODEC_ENTRY(0x80862883, "Braswell HDMI", patch_i915_byt_hdmi), +HDA_CODEC_ENTRY(0x808629fb, "Crestline HDMI", patch_generic_hdmi), +/* special ID for generic HDMI */ +HDA_CODEC_ENTRY(HDA_CODEC_ID_GENERIC_HDMI, "Generic HDMI", patch_generic_hdmi), +{} /* terminator */ +}; +MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_hdmi); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("HDMI HD-audio codec"); +MODULE_ALIAS("snd-hda-codec-intelhdmi"); +MODULE_ALIAS("snd-hda-codec-nvhdmi"); +MODULE_ALIAS("snd-hda-codec-atihdmi"); + +static struct hda_codec_driver hdmi_driver = { + .id = snd_hda_id_hdmi, +}; + +module_hda_codec_driver(hdmi_driver); diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c new file mode 100644 index 000000000..62f213704 --- /dev/null +++ b/sound/pci/hda/patch_realtek.c @@ -0,0 +1,12350 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Universal Interface for Intel High Definition Audio Codec + * + * HD audio interface patch for Realtek ALC codecs + * + * Copyright (c) 2004 Kailang Yang <kailang@realtek.com.tw> + * PeiSen Hou <pshou@realtek.com.tw> + * Takashi Iwai <tiwai@suse.de> + * Jonathan Woithe <jwoithe@just42.net> + */ + +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/dmi.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/leds.h> +#include <linux/ctype.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/hda_codec.h> +#include "hda_local.h" +#include "hda_auto_parser.h" +#include "hda_jack.h" +#include "hda_generic.h" +#include "hda_component.h" + +/* keep halting ALC5505 DSP, for power saving */ +#define HALT_REALTEK_ALC5505 + +/* extra amp-initialization sequence types */ +enum { + ALC_INIT_UNDEFINED, + ALC_INIT_NONE, + ALC_INIT_DEFAULT, +}; + +enum { + ALC_HEADSET_MODE_UNKNOWN, + ALC_HEADSET_MODE_UNPLUGGED, + ALC_HEADSET_MODE_HEADSET, + ALC_HEADSET_MODE_MIC, + ALC_HEADSET_MODE_HEADPHONE, +}; + +enum { + ALC_HEADSET_TYPE_UNKNOWN, + ALC_HEADSET_TYPE_CTIA, + ALC_HEADSET_TYPE_OMTP, +}; + +enum { + ALC_KEY_MICMUTE_INDEX, +}; + +struct alc_customize_define { + unsigned int sku_cfg; + unsigned char port_connectivity; + unsigned char check_sum; + unsigned char customization; + unsigned char external_amp; + unsigned int enable_pcbeep:1; + unsigned int platform_type:1; + unsigned int swap:1; + unsigned int override:1; + unsigned int fixup:1; /* Means that this sku is set by driver, not read from hw */ +}; + +struct alc_coef_led { + unsigned int idx; + unsigned int mask; + unsigned int on; + unsigned int off; +}; + +struct alc_spec { + struct hda_gen_spec gen; /* must be at head */ + + /* codec parameterization */ + struct alc_customize_define cdefine; + unsigned int parse_flags; /* flag for snd_hda_parse_pin_defcfg() */ + + /* GPIO bits */ + unsigned int gpio_mask; + unsigned int gpio_dir; + unsigned int gpio_data; + bool gpio_write_delay; /* add a delay before writing gpio_data */ + + /* mute LED for HP laptops, see vref_mute_led_set() */ + int mute_led_polarity; + int micmute_led_polarity; + hda_nid_t mute_led_nid; + hda_nid_t cap_mute_led_nid; + + unsigned int gpio_mute_led_mask; + unsigned int gpio_mic_led_mask; + struct alc_coef_led mute_led_coef; + struct alc_coef_led mic_led_coef; + struct mutex coef_mutex; + + hda_nid_t headset_mic_pin; + hda_nid_t headphone_mic_pin; + int current_headset_mode; + int current_headset_type; + + /* hooks */ + void (*init_hook)(struct hda_codec *codec); +#ifdef CONFIG_PM + void (*power_hook)(struct hda_codec *codec); +#endif + void (*shutup)(struct hda_codec *codec); + + int init_amp; + int codec_variant; /* flag for other variants */ + unsigned int has_alc5505_dsp:1; + unsigned int no_depop_delay:1; + unsigned int done_hp_init:1; + unsigned int no_shutup_pins:1; + unsigned int ultra_low_power:1; + unsigned int has_hs_key:1; + unsigned int no_internal_mic_pin:1; + unsigned int en_3kpull_low:1; + + /* for PLL fix */ + hda_nid_t pll_nid; + unsigned int pll_coef_idx, pll_coef_bit; + unsigned int coef0; + struct input_dev *kb_dev; + u8 alc_mute_keycode_map[1]; + + /* component binding */ + struct component_match *match; + struct hda_component comps[HDA_MAX_COMPONENTS]; +}; + +/* + * COEF access helper functions + */ + +static void coef_mutex_lock(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + snd_hda_power_up_pm(codec); + mutex_lock(&spec->coef_mutex); +} + +static void coef_mutex_unlock(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + mutex_unlock(&spec->coef_mutex); + snd_hda_power_down_pm(codec); +} + +static int __alc_read_coefex_idx(struct hda_codec *codec, hda_nid_t nid, + unsigned int coef_idx) +{ + unsigned int val; + + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_COEF_INDEX, coef_idx); + val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_PROC_COEF, 0); + return val; +} + +static int alc_read_coefex_idx(struct hda_codec *codec, hda_nid_t nid, + unsigned int coef_idx) +{ + unsigned int val; + + coef_mutex_lock(codec); + val = __alc_read_coefex_idx(codec, nid, coef_idx); + coef_mutex_unlock(codec); + return val; +} + +#define alc_read_coef_idx(codec, coef_idx) \ + alc_read_coefex_idx(codec, 0x20, coef_idx) + +static void __alc_write_coefex_idx(struct hda_codec *codec, hda_nid_t nid, + unsigned int coef_idx, unsigned int coef_val) +{ + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_COEF_INDEX, coef_idx); + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_PROC_COEF, coef_val); +} + +static void alc_write_coefex_idx(struct hda_codec *codec, hda_nid_t nid, + unsigned int coef_idx, unsigned int coef_val) +{ + coef_mutex_lock(codec); + __alc_write_coefex_idx(codec, nid, coef_idx, coef_val); + coef_mutex_unlock(codec); +} + +#define alc_write_coef_idx(codec, coef_idx, coef_val) \ + alc_write_coefex_idx(codec, 0x20, coef_idx, coef_val) + +static void __alc_update_coefex_idx(struct hda_codec *codec, hda_nid_t nid, + unsigned int coef_idx, unsigned int mask, + unsigned int bits_set) +{ + unsigned int val = __alc_read_coefex_idx(codec, nid, coef_idx); + + if (val != -1) + __alc_write_coefex_idx(codec, nid, coef_idx, + (val & ~mask) | bits_set); +} + +static void alc_update_coefex_idx(struct hda_codec *codec, hda_nid_t nid, + unsigned int coef_idx, unsigned int mask, + unsigned int bits_set) +{ + coef_mutex_lock(codec); + __alc_update_coefex_idx(codec, nid, coef_idx, mask, bits_set); + coef_mutex_unlock(codec); +} + +#define alc_update_coef_idx(codec, coef_idx, mask, bits_set) \ + alc_update_coefex_idx(codec, 0x20, coef_idx, mask, bits_set) + +/* a special bypass for COEF 0; read the cached value at the second time */ +static unsigned int alc_get_coef0(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + if (!spec->coef0) + spec->coef0 = alc_read_coef_idx(codec, 0); + return spec->coef0; +} + +/* coef writes/updates batch */ +struct coef_fw { + unsigned char nid; + unsigned char idx; + unsigned short mask; + unsigned short val; +}; + +#define UPDATE_COEFEX(_nid, _idx, _mask, _val) \ + { .nid = (_nid), .idx = (_idx), .mask = (_mask), .val = (_val) } +#define WRITE_COEFEX(_nid, _idx, _val) UPDATE_COEFEX(_nid, _idx, -1, _val) +#define WRITE_COEF(_idx, _val) WRITE_COEFEX(0x20, _idx, _val) +#define UPDATE_COEF(_idx, _mask, _val) UPDATE_COEFEX(0x20, _idx, _mask, _val) + +static void alc_process_coef_fw(struct hda_codec *codec, + const struct coef_fw *fw) +{ + coef_mutex_lock(codec); + for (; fw->nid; fw++) { + if (fw->mask == (unsigned short)-1) + __alc_write_coefex_idx(codec, fw->nid, fw->idx, fw->val); + else + __alc_update_coefex_idx(codec, fw->nid, fw->idx, + fw->mask, fw->val); + } + coef_mutex_unlock(codec); +} + +/* + * GPIO setup tables, used in initialization + */ + +/* Enable GPIO mask and set output */ +static void alc_setup_gpio(struct hda_codec *codec, unsigned int mask) +{ + struct alc_spec *spec = codec->spec; + + spec->gpio_mask |= mask; + spec->gpio_dir |= mask; + spec->gpio_data |= mask; +} + +static void alc_write_gpio_data(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, + spec->gpio_data); +} + +static void alc_update_gpio_data(struct hda_codec *codec, unsigned int mask, + bool on) +{ + struct alc_spec *spec = codec->spec; + unsigned int oldval = spec->gpio_data; + + if (on) + spec->gpio_data |= mask; + else + spec->gpio_data &= ~mask; + if (oldval != spec->gpio_data) + alc_write_gpio_data(codec); +} + +static void alc_write_gpio(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + if (!spec->gpio_mask) + return; + + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_MASK, spec->gpio_mask); + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DIRECTION, spec->gpio_dir); + if (spec->gpio_write_delay) + msleep(1); + alc_write_gpio_data(codec); +} + +static void alc_fixup_gpio(struct hda_codec *codec, int action, + unsigned int mask) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) + alc_setup_gpio(codec, mask); +} + +static void alc_fixup_gpio1(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc_fixup_gpio(codec, action, 0x01); +} + +static void alc_fixup_gpio2(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc_fixup_gpio(codec, action, 0x02); +} + +static void alc_fixup_gpio3(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc_fixup_gpio(codec, action, 0x03); +} + +static void alc_fixup_gpio4(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc_fixup_gpio(codec, action, 0x04); +} + +static void alc_fixup_micmute_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) + snd_hda_gen_add_micmute_led_cdev(codec, NULL); +} + +/* + * Fix hardware PLL issue + * On some codecs, the analog PLL gating control must be off while + * the default value is 1. + */ +static void alc_fix_pll(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + if (spec->pll_nid) + alc_update_coefex_idx(codec, spec->pll_nid, spec->pll_coef_idx, + 1 << spec->pll_coef_bit, 0); +} + +static void alc_fix_pll_init(struct hda_codec *codec, hda_nid_t nid, + unsigned int coef_idx, unsigned int coef_bit) +{ + struct alc_spec *spec = codec->spec; + spec->pll_nid = nid; + spec->pll_coef_idx = coef_idx; + spec->pll_coef_bit = coef_bit; + alc_fix_pll(codec); +} + +/* update the master volume per volume-knob's unsol event */ +static void alc_update_knob_master(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + unsigned int val; + struct snd_kcontrol *kctl; + struct snd_ctl_elem_value *uctl; + + kctl = snd_hda_find_mixer_ctl(codec, "Master Playback Volume"); + if (!kctl) + return; + uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); + if (!uctl) + return; + val = snd_hda_codec_read(codec, jack->nid, 0, + AC_VERB_GET_VOLUME_KNOB_CONTROL, 0); + val &= HDA_AMP_VOLMASK; + uctl->value.integer.value[0] = val; + uctl->value.integer.value[1] = val; + kctl->put(kctl, uctl); + kfree(uctl); +} + +static void alc880_unsol_event(struct hda_codec *codec, unsigned int res) +{ + /* For some reason, the res given from ALC880 is broken. + Here we adjust it properly. */ + snd_hda_jack_unsol_event(codec, res >> 2); +} + +/* Change EAPD to verb control */ +static void alc_fill_eapd_coef(struct hda_codec *codec) +{ + int coef; + + coef = alc_get_coef0(codec); + + switch (codec->core.vendor_id) { + case 0x10ec0262: + alc_update_coef_idx(codec, 0x7, 0, 1<<5); + break; + case 0x10ec0267: + case 0x10ec0268: + alc_update_coef_idx(codec, 0x7, 0, 1<<13); + break; + case 0x10ec0269: + if ((coef & 0x00f0) == 0x0010) + alc_update_coef_idx(codec, 0xd, 0, 1<<14); + if ((coef & 0x00f0) == 0x0020) + alc_update_coef_idx(codec, 0x4, 1<<15, 0); + if ((coef & 0x00f0) == 0x0030) + alc_update_coef_idx(codec, 0x10, 1<<9, 0); + break; + case 0x10ec0280: + case 0x10ec0284: + case 0x10ec0290: + case 0x10ec0292: + alc_update_coef_idx(codec, 0x4, 1<<15, 0); + break; + case 0x10ec0225: + case 0x10ec0295: + case 0x10ec0299: + alc_update_coef_idx(codec, 0x67, 0xf000, 0x3000); + fallthrough; + case 0x10ec0215: + case 0x10ec0230: + case 0x10ec0233: + case 0x10ec0235: + case 0x10ec0236: + case 0x10ec0245: + case 0x10ec0255: + case 0x10ec0256: + case 0x19e58326: + case 0x10ec0257: + case 0x10ec0282: + case 0x10ec0283: + case 0x10ec0286: + case 0x10ec0288: + case 0x10ec0285: + case 0x10ec0298: + case 0x10ec0289: + case 0x10ec0300: + alc_update_coef_idx(codec, 0x10, 1<<9, 0); + break; + case 0x10ec0275: + alc_update_coef_idx(codec, 0xe, 0, 1<<0); + break; + case 0x10ec0287: + alc_update_coef_idx(codec, 0x10, 1<<9, 0); + alc_write_coef_idx(codec, 0x8, 0x4ab7); + break; + case 0x10ec0293: + alc_update_coef_idx(codec, 0xa, 1<<13, 0); + break; + case 0x10ec0234: + case 0x10ec0274: + case 0x10ec0294: + case 0x10ec0700: + case 0x10ec0701: + case 0x10ec0703: + case 0x10ec0711: + alc_update_coef_idx(codec, 0x10, 1<<15, 0); + break; + case 0x10ec0662: + if ((coef & 0x00f0) == 0x0030) + alc_update_coef_idx(codec, 0x4, 1<<10, 0); /* EAPD Ctrl */ + break; + case 0x10ec0272: + case 0x10ec0273: + case 0x10ec0663: + case 0x10ec0665: + case 0x10ec0670: + case 0x10ec0671: + case 0x10ec0672: + alc_update_coef_idx(codec, 0xd, 0, 1<<14); /* EAPD Ctrl */ + break; + case 0x10ec0222: + case 0x10ec0623: + alc_update_coef_idx(codec, 0x19, 1<<13, 0); + break; + case 0x10ec0668: + alc_update_coef_idx(codec, 0x7, 3<<13, 0); + break; + case 0x10ec0867: + alc_update_coef_idx(codec, 0x4, 1<<10, 0); + break; + case 0x10ec0888: + if ((coef & 0x00f0) == 0x0020 || (coef & 0x00f0) == 0x0030) + alc_update_coef_idx(codec, 0x7, 1<<5, 0); + break; + case 0x10ec0892: + case 0x10ec0897: + alc_update_coef_idx(codec, 0x7, 1<<5, 0); + break; + case 0x10ec0899: + case 0x10ec0900: + case 0x10ec0b00: + case 0x10ec1168: + case 0x10ec1220: + alc_update_coef_idx(codec, 0x7, 1<<1, 0); + break; + } +} + +/* additional initialization for ALC888 variants */ +static void alc888_coef_init(struct hda_codec *codec) +{ + switch (alc_get_coef0(codec) & 0x00f0) { + /* alc888-VA */ + case 0x00: + /* alc888-VB */ + case 0x10: + alc_update_coef_idx(codec, 7, 0, 0x2030); /* Turn EAPD to High */ + break; + } +} + +/* turn on/off EAPD control (only if available) */ +static void set_eapd(struct hda_codec *codec, hda_nid_t nid, int on) +{ + if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN) + return; + if (snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_EAPD) + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_EAPD_BTLENABLE, + on ? 2 : 0); +} + +/* turn on/off EAPD controls of the codec */ +static void alc_auto_setup_eapd(struct hda_codec *codec, bool on) +{ + /* We currently only handle front, HP */ + static const hda_nid_t pins[] = { + 0x0f, 0x10, 0x14, 0x15, 0x17, 0 + }; + const hda_nid_t *p; + for (p = pins; *p; p++) + set_eapd(codec, *p, on); +} + +static int find_ext_mic_pin(struct hda_codec *codec); + +static void alc_headset_mic_no_shutup(struct hda_codec *codec) +{ + const struct hda_pincfg *pin; + int mic_pin = find_ext_mic_pin(codec); + int i; + + /* don't shut up pins when unloading the driver; otherwise it breaks + * the default pin setup at the next load of the driver + */ + if (codec->bus->shutdown) + return; + + snd_array_for_each(&codec->init_pins, i, pin) { + /* use read here for syncing after issuing each verb */ + if (pin->nid != mic_pin) + snd_hda_codec_read(codec, pin->nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0); + } + + codec->pins_shutup = 1; +} + +static void alc_shutup_pins(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + switch (codec->core.vendor_id) { + case 0x10ec0236: + case 0x10ec0256: + case 0x19e58326: + case 0x10ec0283: + case 0x10ec0286: + case 0x10ec0288: + case 0x10ec0298: + alc_headset_mic_no_shutup(codec); + break; + default: + if (!spec->no_shutup_pins) + snd_hda_shutup_pins(codec); + break; + } +} + +/* generic shutup callback; + * just turning off EAPD and a little pause for avoiding pop-noise + */ +static void alc_eapd_shutup(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + alc_auto_setup_eapd(codec, false); + if (!spec->no_depop_delay) + msleep(200); + alc_shutup_pins(codec); +} + +/* generic EAPD initialization */ +static void alc_auto_init_amp(struct hda_codec *codec, int type) +{ + alc_auto_setup_eapd(codec, true); + alc_write_gpio(codec); + switch (type) { + case ALC_INIT_DEFAULT: + switch (codec->core.vendor_id) { + case 0x10ec0260: + alc_update_coefex_idx(codec, 0x1a, 7, 0, 0x2010); + break; + case 0x10ec0880: + case 0x10ec0882: + case 0x10ec0883: + case 0x10ec0885: + alc_update_coef_idx(codec, 7, 0, 0x2030); + break; + case 0x10ec0888: + alc888_coef_init(codec); + break; + } + break; + } +} + +/* get a primary headphone pin if available */ +static hda_nid_t alc_get_hp_pin(struct alc_spec *spec) +{ + if (spec->gen.autocfg.hp_pins[0]) + return spec->gen.autocfg.hp_pins[0]; + if (spec->gen.autocfg.line_out_type == AC_JACK_HP_OUT) + return spec->gen.autocfg.line_out_pins[0]; + return 0; +} + +/* + * Realtek SSID verification + */ + +/* Could be any non-zero and even value. When used as fixup, tells + * the driver to ignore any present sku defines. + */ +#define ALC_FIXUP_SKU_IGNORE (2) + +static void alc_fixup_sku_ignore(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->cdefine.fixup = 1; + spec->cdefine.sku_cfg = ALC_FIXUP_SKU_IGNORE; + } +} + +static void alc_fixup_no_depop_delay(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PROBE) { + spec->no_depop_delay = 1; + codec->depop_delay = 0; + } +} + +static int alc_auto_parse_customize_define(struct hda_codec *codec) +{ + unsigned int ass, tmp, i; + unsigned nid = 0; + struct alc_spec *spec = codec->spec; + + spec->cdefine.enable_pcbeep = 1; /* assume always enabled */ + + if (spec->cdefine.fixup) { + ass = spec->cdefine.sku_cfg; + if (ass == ALC_FIXUP_SKU_IGNORE) + return -1; + goto do_sku; + } + + if (!codec->bus->pci) + return -1; + ass = codec->core.subsystem_id & 0xffff; + if (ass != codec->bus->pci->subsystem_device && (ass & 1)) + goto do_sku; + + nid = 0x1d; + if (codec->core.vendor_id == 0x10ec0260) + nid = 0x17; + ass = snd_hda_codec_get_pincfg(codec, nid); + + if (!(ass & 1)) { + codec_info(codec, "%s: SKU not ready 0x%08x\n", + codec->core.chip_name, ass); + return -1; + } + + /* check sum */ + tmp = 0; + for (i = 1; i < 16; i++) { + if ((ass >> i) & 1) + tmp++; + } + if (((ass >> 16) & 0xf) != tmp) + return -1; + + spec->cdefine.port_connectivity = ass >> 30; + spec->cdefine.enable_pcbeep = (ass & 0x100000) >> 20; + spec->cdefine.check_sum = (ass >> 16) & 0xf; + spec->cdefine.customization = ass >> 8; +do_sku: + spec->cdefine.sku_cfg = ass; + spec->cdefine.external_amp = (ass & 0x38) >> 3; + spec->cdefine.platform_type = (ass & 0x4) >> 2; + spec->cdefine.swap = (ass & 0x2) >> 1; + spec->cdefine.override = ass & 0x1; + + codec_dbg(codec, "SKU: Nid=0x%x sku_cfg=0x%08x\n", + nid, spec->cdefine.sku_cfg); + codec_dbg(codec, "SKU: port_connectivity=0x%x\n", + spec->cdefine.port_connectivity); + codec_dbg(codec, "SKU: enable_pcbeep=0x%x\n", spec->cdefine.enable_pcbeep); + codec_dbg(codec, "SKU: check_sum=0x%08x\n", spec->cdefine.check_sum); + codec_dbg(codec, "SKU: customization=0x%08x\n", spec->cdefine.customization); + codec_dbg(codec, "SKU: external_amp=0x%x\n", spec->cdefine.external_amp); + codec_dbg(codec, "SKU: platform_type=0x%x\n", spec->cdefine.platform_type); + codec_dbg(codec, "SKU: swap=0x%x\n", spec->cdefine.swap); + codec_dbg(codec, "SKU: override=0x%x\n", spec->cdefine.override); + + return 0; +} + +/* return the position of NID in the list, or -1 if not found */ +static int find_idx_in_nid_list(hda_nid_t nid, const hda_nid_t *list, int nums) +{ + int i; + for (i = 0; i < nums; i++) + if (list[i] == nid) + return i; + return -1; +} +/* return true if the given NID is found in the list */ +static bool found_in_nid_list(hda_nid_t nid, const hda_nid_t *list, int nums) +{ + return find_idx_in_nid_list(nid, list, nums) >= 0; +} + +/* check subsystem ID and set up device-specific initialization; + * return 1 if initialized, 0 if invalid SSID + */ +/* 32-bit subsystem ID for BIOS loading in HD Audio codec. + * 31 ~ 16 : Manufacture ID + * 15 ~ 8 : SKU ID + * 7 ~ 0 : Assembly ID + * port-A --> pin 39/41, port-E --> pin 14/15, port-D --> pin 35/36 + */ +static int alc_subsystem_id(struct hda_codec *codec, const hda_nid_t *ports) +{ + unsigned int ass, tmp, i; + unsigned nid; + struct alc_spec *spec = codec->spec; + + if (spec->cdefine.fixup) { + ass = spec->cdefine.sku_cfg; + if (ass == ALC_FIXUP_SKU_IGNORE) + return 0; + goto do_sku; + } + + ass = codec->core.subsystem_id & 0xffff; + if (codec->bus->pci && + ass != codec->bus->pci->subsystem_device && (ass & 1)) + goto do_sku; + + /* invalid SSID, check the special NID pin defcfg instead */ + /* + * 31~30 : port connectivity + * 29~21 : reserve + * 20 : PCBEEP input + * 19~16 : Check sum (15:1) + * 15~1 : Custom + * 0 : override + */ + nid = 0x1d; + if (codec->core.vendor_id == 0x10ec0260) + nid = 0x17; + ass = snd_hda_codec_get_pincfg(codec, nid); + codec_dbg(codec, + "realtek: No valid SSID, checking pincfg 0x%08x for NID 0x%x\n", + ass, nid); + if (!(ass & 1)) + return 0; + if ((ass >> 30) != 1) /* no physical connection */ + return 0; + + /* check sum */ + tmp = 0; + for (i = 1; i < 16; i++) { + if ((ass >> i) & 1) + tmp++; + } + if (((ass >> 16) & 0xf) != tmp) + return 0; +do_sku: + codec_dbg(codec, "realtek: Enabling init ASM_ID=0x%04x CODEC_ID=%08x\n", + ass & 0xffff, codec->core.vendor_id); + /* + * 0 : override + * 1 : Swap Jack + * 2 : 0 --> Desktop, 1 --> Laptop + * 3~5 : External Amplifier control + * 7~6 : Reserved + */ + tmp = (ass & 0x38) >> 3; /* external Amp control */ + if (spec->init_amp == ALC_INIT_UNDEFINED) { + switch (tmp) { + case 1: + alc_setup_gpio(codec, 0x01); + break; + case 3: + alc_setup_gpio(codec, 0x02); + break; + case 7: + alc_setup_gpio(codec, 0x04); + break; + case 5: + default: + spec->init_amp = ALC_INIT_DEFAULT; + break; + } + } + + /* is laptop or Desktop and enable the function "Mute internal speaker + * when the external headphone out jack is plugged" + */ + if (!(ass & 0x8000)) + return 1; + /* + * 10~8 : Jack location + * 12~11: Headphone out -> 00: PortA, 01: PortE, 02: PortD, 03: Resvered + * 14~13: Resvered + * 15 : 1 --> enable the function "Mute internal speaker + * when the external headphone out jack is plugged" + */ + if (!alc_get_hp_pin(spec)) { + hda_nid_t nid; + tmp = (ass >> 11) & 0x3; /* HP to chassis */ + nid = ports[tmp]; + if (found_in_nid_list(nid, spec->gen.autocfg.line_out_pins, + spec->gen.autocfg.line_outs)) + return 1; + spec->gen.autocfg.hp_pins[0] = nid; + } + return 1; +} + +/* Check the validity of ALC subsystem-id + * ports contains an array of 4 pin NIDs for port-A, E, D and I */ +static void alc_ssid_check(struct hda_codec *codec, const hda_nid_t *ports) +{ + if (!alc_subsystem_id(codec, ports)) { + struct alc_spec *spec = codec->spec; + if (spec->init_amp == ALC_INIT_UNDEFINED) { + codec_dbg(codec, + "realtek: Enable default setup for auto mode as fallback\n"); + spec->init_amp = ALC_INIT_DEFAULT; + } + } +} + +/* + */ + +static void alc_fixup_inv_dmic(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + spec->gen.inv_dmic_split = 1; +} + + +static int alc_build_controls(struct hda_codec *codec) +{ + int err; + + err = snd_hda_gen_build_controls(codec); + if (err < 0) + return err; + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_BUILD); + return 0; +} + + +/* + * Common callbacks + */ + +static void alc_pre_init(struct hda_codec *codec) +{ + alc_fill_eapd_coef(codec); +} + +#define is_s3_resume(codec) \ + ((codec)->core.dev.power.power_state.event == PM_EVENT_RESUME) +#define is_s4_resume(codec) \ + ((codec)->core.dev.power.power_state.event == PM_EVENT_RESTORE) + +static int alc_init(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + /* hibernation resume needs the full chip initialization */ + if (is_s4_resume(codec)) + alc_pre_init(codec); + + if (spec->init_hook) + spec->init_hook(codec); + + spec->gen.skip_verbs = 1; /* applied in below */ + snd_hda_gen_init(codec); + alc_fix_pll(codec); + alc_auto_init_amp(codec, spec->init_amp); + snd_hda_apply_verbs(codec); /* apply verbs here after own init */ + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_INIT); + + return 0; +} + +#define alc_free snd_hda_gen_free + +#ifdef CONFIG_PM +static inline void alc_shutup(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + if (!snd_hda_get_bool_hint(codec, "shutup")) + return; /* disabled explicitly by hints */ + + if (spec && spec->shutup) + spec->shutup(codec); + else + alc_shutup_pins(codec); +} + +static void alc_power_eapd(struct hda_codec *codec) +{ + alc_auto_setup_eapd(codec, false); +} + +static int alc_suspend(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + alc_shutup(codec); + if (spec && spec->power_hook) + spec->power_hook(codec); + return 0; +} + +static int alc_resume(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + if (!spec->no_depop_delay) + msleep(150); /* to avoid pop noise */ + codec->patch_ops.init(codec); + snd_hda_regmap_sync(codec); + hda_call_check_power_status(codec, 0x01); + return 0; +} +#endif + +/* + */ +static const struct hda_codec_ops alc_patch_ops = { + .build_controls = alc_build_controls, + .build_pcms = snd_hda_gen_build_pcms, + .init = alc_init, + .free = alc_free, + .unsol_event = snd_hda_jack_unsol_event, +#ifdef CONFIG_PM + .resume = alc_resume, + .suspend = alc_suspend, + .check_power_status = snd_hda_gen_check_power_status, +#endif +}; + + +#define alc_codec_rename(codec, name) snd_hda_codec_set_name(codec, name) + +/* + * Rename codecs appropriately from COEF value or subvendor id + */ +struct alc_codec_rename_table { + unsigned int vendor_id; + unsigned short coef_mask; + unsigned short coef_bits; + const char *name; +}; + +struct alc_codec_rename_pci_table { + unsigned int codec_vendor_id; + unsigned short pci_subvendor; + unsigned short pci_subdevice; + const char *name; +}; + +static const struct alc_codec_rename_table rename_tbl[] = { + { 0x10ec0221, 0xf00f, 0x1003, "ALC231" }, + { 0x10ec0269, 0xfff0, 0x3010, "ALC277" }, + { 0x10ec0269, 0xf0f0, 0x2010, "ALC259" }, + { 0x10ec0269, 0xf0f0, 0x3010, "ALC258" }, + { 0x10ec0269, 0x00f0, 0x0010, "ALC269VB" }, + { 0x10ec0269, 0xffff, 0xa023, "ALC259" }, + { 0x10ec0269, 0xffff, 0x6023, "ALC281X" }, + { 0x10ec0269, 0x00f0, 0x0020, "ALC269VC" }, + { 0x10ec0269, 0x00f0, 0x0030, "ALC269VD" }, + { 0x10ec0662, 0xffff, 0x4020, "ALC656" }, + { 0x10ec0887, 0x00f0, 0x0030, "ALC887-VD" }, + { 0x10ec0888, 0x00f0, 0x0030, "ALC888-VD" }, + { 0x10ec0888, 0xf0f0, 0x3020, "ALC886" }, + { 0x10ec0899, 0x2000, 0x2000, "ALC899" }, + { 0x10ec0892, 0xffff, 0x8020, "ALC661" }, + { 0x10ec0892, 0xffff, 0x8011, "ALC661" }, + { 0x10ec0892, 0xffff, 0x4011, "ALC656" }, + { } /* terminator */ +}; + +static const struct alc_codec_rename_pci_table rename_pci_tbl[] = { + { 0x10ec0280, 0x1028, 0, "ALC3220" }, + { 0x10ec0282, 0x1028, 0, "ALC3221" }, + { 0x10ec0283, 0x1028, 0, "ALC3223" }, + { 0x10ec0288, 0x1028, 0, "ALC3263" }, + { 0x10ec0292, 0x1028, 0, "ALC3226" }, + { 0x10ec0293, 0x1028, 0, "ALC3235" }, + { 0x10ec0255, 0x1028, 0, "ALC3234" }, + { 0x10ec0668, 0x1028, 0, "ALC3661" }, + { 0x10ec0275, 0x1028, 0, "ALC3260" }, + { 0x10ec0899, 0x1028, 0, "ALC3861" }, + { 0x10ec0298, 0x1028, 0, "ALC3266" }, + { 0x10ec0236, 0x1028, 0, "ALC3204" }, + { 0x10ec0256, 0x1028, 0, "ALC3246" }, + { 0x10ec0225, 0x1028, 0, "ALC3253" }, + { 0x10ec0295, 0x1028, 0, "ALC3254" }, + { 0x10ec0299, 0x1028, 0, "ALC3271" }, + { 0x10ec0670, 0x1025, 0, "ALC669X" }, + { 0x10ec0676, 0x1025, 0, "ALC679X" }, + { 0x10ec0282, 0x1043, 0, "ALC3229" }, + { 0x10ec0233, 0x1043, 0, "ALC3236" }, + { 0x10ec0280, 0x103c, 0, "ALC3228" }, + { 0x10ec0282, 0x103c, 0, "ALC3227" }, + { 0x10ec0286, 0x103c, 0, "ALC3242" }, + { 0x10ec0290, 0x103c, 0, "ALC3241" }, + { 0x10ec0668, 0x103c, 0, "ALC3662" }, + { 0x10ec0283, 0x17aa, 0, "ALC3239" }, + { 0x10ec0292, 0x17aa, 0, "ALC3232" }, + { } /* terminator */ +}; + +static int alc_codec_rename_from_preset(struct hda_codec *codec) +{ + const struct alc_codec_rename_table *p; + const struct alc_codec_rename_pci_table *q; + + for (p = rename_tbl; p->vendor_id; p++) { + if (p->vendor_id != codec->core.vendor_id) + continue; + if ((alc_get_coef0(codec) & p->coef_mask) == p->coef_bits) + return alc_codec_rename(codec, p->name); + } + + if (!codec->bus->pci) + return 0; + for (q = rename_pci_tbl; q->codec_vendor_id; q++) { + if (q->codec_vendor_id != codec->core.vendor_id) + continue; + if (q->pci_subvendor != codec->bus->pci->subsystem_vendor) + continue; + if (!q->pci_subdevice || + q->pci_subdevice == codec->bus->pci->subsystem_device) + return alc_codec_rename(codec, q->name); + } + + return 0; +} + + +/* + * Digital-beep handlers + */ +#ifdef CONFIG_SND_HDA_INPUT_BEEP + +/* additional beep mixers; private_value will be overwritten */ +static const struct snd_kcontrol_new alc_beep_mixer[] = { + HDA_CODEC_VOLUME("Beep Playback Volume", 0, 0, HDA_INPUT), + HDA_CODEC_MUTE_BEEP("Beep Playback Switch", 0, 0, HDA_INPUT), +}; + +/* set up and create beep controls */ +static int set_beep_amp(struct alc_spec *spec, hda_nid_t nid, + int idx, int dir) +{ + struct snd_kcontrol_new *knew; + unsigned int beep_amp = HDA_COMPOSE_AMP_VAL(nid, 3, idx, dir); + int i; + + for (i = 0; i < ARRAY_SIZE(alc_beep_mixer); i++) { + knew = snd_hda_gen_add_kctl(&spec->gen, NULL, + &alc_beep_mixer[i]); + if (!knew) + return -ENOMEM; + knew->private_value = beep_amp; + } + return 0; +} + +static const struct snd_pci_quirk beep_allow_list[] = { + SND_PCI_QUIRK(0x1043, 0x103c, "ASUS", 1), + SND_PCI_QUIRK(0x1043, 0x115d, "ASUS", 1), + SND_PCI_QUIRK(0x1043, 0x829f, "ASUS", 1), + SND_PCI_QUIRK(0x1043, 0x8376, "EeePC", 1), + SND_PCI_QUIRK(0x1043, 0x83ce, "EeePC", 1), + SND_PCI_QUIRK(0x1043, 0x831a, "EeePC", 1), + SND_PCI_QUIRK(0x1043, 0x834a, "EeePC", 1), + SND_PCI_QUIRK(0x1458, 0xa002, "GA-MA790X", 1), + SND_PCI_QUIRK(0x8086, 0xd613, "Intel", 1), + /* denylist -- no beep available */ + SND_PCI_QUIRK(0x17aa, 0x309e, "Lenovo ThinkCentre M73", 0), + SND_PCI_QUIRK(0x17aa, 0x30a3, "Lenovo ThinkCentre M93", 0), + {} +}; + +static inline int has_cdefine_beep(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + const struct snd_pci_quirk *q; + q = snd_pci_quirk_lookup(codec->bus->pci, beep_allow_list); + if (q) + return q->value; + return spec->cdefine.enable_pcbeep; +} +#else +#define set_beep_amp(spec, nid, idx, dir) 0 +#define has_cdefine_beep(codec) 0 +#endif + +/* parse the BIOS configuration and set up the alc_spec */ +/* return 1 if successful, 0 if the proper config is not found, + * or a negative error code + */ +static int alc_parse_auto_config(struct hda_codec *codec, + const hda_nid_t *ignore_nids, + const hda_nid_t *ssid_nids) +{ + struct alc_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->gen.autocfg; + int err; + + err = snd_hda_parse_pin_defcfg(codec, cfg, ignore_nids, + spec->parse_flags); + if (err < 0) + return err; + + if (ssid_nids) + alc_ssid_check(codec, ssid_nids); + + err = snd_hda_gen_parse_auto_config(codec, cfg); + if (err < 0) + return err; + + return 1; +} + +/* common preparation job for alc_spec */ +static int alc_alloc_spec(struct hda_codec *codec, hda_nid_t mixer_nid) +{ + struct alc_spec *spec = kzalloc(sizeof(*spec), GFP_KERNEL); + int err; + + if (!spec) + return -ENOMEM; + codec->spec = spec; + snd_hda_gen_spec_init(&spec->gen); + spec->gen.mixer_nid = mixer_nid; + spec->gen.own_eapd_ctl = 1; + codec->single_adc_amp = 1; + /* FIXME: do we need this for all Realtek codec models? */ + codec->spdif_status_reset = 1; + codec->forced_resume = 1; + codec->patch_ops = alc_patch_ops; + mutex_init(&spec->coef_mutex); + + err = alc_codec_rename_from_preset(codec); + if (err < 0) { + kfree(spec); + return err; + } + return 0; +} + +static int alc880_parse_auto_config(struct hda_codec *codec) +{ + static const hda_nid_t alc880_ignore[] = { 0x1d, 0 }; + static const hda_nid_t alc880_ssids[] = { 0x15, 0x1b, 0x14, 0 }; + return alc_parse_auto_config(codec, alc880_ignore, alc880_ssids); +} + +/* + * ALC880 fix-ups + */ +enum { + ALC880_FIXUP_GPIO1, + ALC880_FIXUP_GPIO2, + ALC880_FIXUP_MEDION_RIM, + ALC880_FIXUP_LG, + ALC880_FIXUP_LG_LW25, + ALC880_FIXUP_W810, + ALC880_FIXUP_EAPD_COEF, + ALC880_FIXUP_TCL_S700, + ALC880_FIXUP_VOL_KNOB, + ALC880_FIXUP_FUJITSU, + ALC880_FIXUP_F1734, + ALC880_FIXUP_UNIWILL, + ALC880_FIXUP_UNIWILL_DIG, + ALC880_FIXUP_Z71V, + ALC880_FIXUP_ASUS_W5A, + ALC880_FIXUP_3ST_BASE, + ALC880_FIXUP_3ST, + ALC880_FIXUP_3ST_DIG, + ALC880_FIXUP_5ST_BASE, + ALC880_FIXUP_5ST, + ALC880_FIXUP_5ST_DIG, + ALC880_FIXUP_6ST_BASE, + ALC880_FIXUP_6ST, + ALC880_FIXUP_6ST_DIG, + ALC880_FIXUP_6ST_AUTOMUTE, +}; + +/* enable the volume-knob widget support on NID 0x21 */ +static void alc880_fixup_vol_knob(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PROBE) + snd_hda_jack_detect_enable_callback(codec, 0x21, + alc_update_knob_master); +} + +static const struct hda_fixup alc880_fixups[] = { + [ALC880_FIXUP_GPIO1] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_gpio1, + }, + [ALC880_FIXUP_GPIO2] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_gpio2, + }, + [ALC880_FIXUP_MEDION_RIM] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x3060 }, + { } + }, + .chained = true, + .chain_id = ALC880_FIXUP_GPIO2, + }, + [ALC880_FIXUP_LG] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + /* disable bogus unused pins */ + { 0x16, 0x411111f0 }, + { 0x18, 0x411111f0 }, + { 0x1a, 0x411111f0 }, + { } + } + }, + [ALC880_FIXUP_LG_LW25] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1a, 0x0181344f }, /* line-in */ + { 0x1b, 0x0321403f }, /* headphone */ + { } + } + }, + [ALC880_FIXUP_W810] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + /* disable bogus unused pins */ + { 0x17, 0x411111f0 }, + { } + }, + .chained = true, + .chain_id = ALC880_FIXUP_GPIO2, + }, + [ALC880_FIXUP_EAPD_COEF] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* change to EAPD mode */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x3060 }, + {} + }, + }, + [ALC880_FIXUP_TCL_S700] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* change to EAPD mode */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x3070 }, + {} + }, + .chained = true, + .chain_id = ALC880_FIXUP_GPIO2, + }, + [ALC880_FIXUP_VOL_KNOB] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc880_fixup_vol_knob, + }, + [ALC880_FIXUP_FUJITSU] = { + /* override all pins as BIOS on old Amilo is broken */ + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x0121401f }, /* HP */ + { 0x15, 0x99030120 }, /* speaker */ + { 0x16, 0x99030130 }, /* bass speaker */ + { 0x17, 0x411111f0 }, /* N/A */ + { 0x18, 0x411111f0 }, /* N/A */ + { 0x19, 0x01a19950 }, /* mic-in */ + { 0x1a, 0x411111f0 }, /* N/A */ + { 0x1b, 0x411111f0 }, /* N/A */ + { 0x1c, 0x411111f0 }, /* N/A */ + { 0x1d, 0x411111f0 }, /* N/A */ + { 0x1e, 0x01454140 }, /* SPDIF out */ + { } + }, + .chained = true, + .chain_id = ALC880_FIXUP_VOL_KNOB, + }, + [ALC880_FIXUP_F1734] = { + /* almost compatible with FUJITSU, but no bass and SPDIF */ + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x0121401f }, /* HP */ + { 0x15, 0x99030120 }, /* speaker */ + { 0x16, 0x411111f0 }, /* N/A */ + { 0x17, 0x411111f0 }, /* N/A */ + { 0x18, 0x411111f0 }, /* N/A */ + { 0x19, 0x01a19950 }, /* mic-in */ + { 0x1a, 0x411111f0 }, /* N/A */ + { 0x1b, 0x411111f0 }, /* N/A */ + { 0x1c, 0x411111f0 }, /* N/A */ + { 0x1d, 0x411111f0 }, /* N/A */ + { 0x1e, 0x411111f0 }, /* N/A */ + { } + }, + .chained = true, + .chain_id = ALC880_FIXUP_VOL_KNOB, + }, + [ALC880_FIXUP_UNIWILL] = { + /* need to fix HP and speaker pins to be parsed correctly */ + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x0121411f }, /* HP */ + { 0x15, 0x99030120 }, /* speaker */ + { 0x16, 0x99030130 }, /* bass speaker */ + { } + }, + }, + [ALC880_FIXUP_UNIWILL_DIG] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + /* disable bogus unused pins */ + { 0x17, 0x411111f0 }, + { 0x19, 0x411111f0 }, + { 0x1b, 0x411111f0 }, + { 0x1f, 0x411111f0 }, + { } + } + }, + [ALC880_FIXUP_Z71V] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + /* set up the whole pins as BIOS is utterly broken */ + { 0x14, 0x99030120 }, /* speaker */ + { 0x15, 0x0121411f }, /* HP */ + { 0x16, 0x411111f0 }, /* N/A */ + { 0x17, 0x411111f0 }, /* N/A */ + { 0x18, 0x01a19950 }, /* mic-in */ + { 0x19, 0x411111f0 }, /* N/A */ + { 0x1a, 0x01813031 }, /* line-in */ + { 0x1b, 0x411111f0 }, /* N/A */ + { 0x1c, 0x411111f0 }, /* N/A */ + { 0x1d, 0x411111f0 }, /* N/A */ + { 0x1e, 0x0144111e }, /* SPDIF */ + { } + } + }, + [ALC880_FIXUP_ASUS_W5A] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + /* set up the whole pins as BIOS is utterly broken */ + { 0x14, 0x0121411f }, /* HP */ + { 0x15, 0x411111f0 }, /* N/A */ + { 0x16, 0x411111f0 }, /* N/A */ + { 0x17, 0x411111f0 }, /* N/A */ + { 0x18, 0x90a60160 }, /* mic */ + { 0x19, 0x411111f0 }, /* N/A */ + { 0x1a, 0x411111f0 }, /* N/A */ + { 0x1b, 0x411111f0 }, /* N/A */ + { 0x1c, 0x411111f0 }, /* N/A */ + { 0x1d, 0x411111f0 }, /* N/A */ + { 0x1e, 0xb743111e }, /* SPDIF out */ + { } + }, + .chained = true, + .chain_id = ALC880_FIXUP_GPIO1, + }, + [ALC880_FIXUP_3ST_BASE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x01014010 }, /* line-out */ + { 0x15, 0x411111f0 }, /* N/A */ + { 0x16, 0x411111f0 }, /* N/A */ + { 0x17, 0x411111f0 }, /* N/A */ + { 0x18, 0x01a19c30 }, /* mic-in */ + { 0x19, 0x0121411f }, /* HP */ + { 0x1a, 0x01813031 }, /* line-in */ + { 0x1b, 0x02a19c40 }, /* front-mic */ + { 0x1c, 0x411111f0 }, /* N/A */ + { 0x1d, 0x411111f0 }, /* N/A */ + /* 0x1e is filled in below */ + { 0x1f, 0x411111f0 }, /* N/A */ + { } + } + }, + [ALC880_FIXUP_3ST] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1e, 0x411111f0 }, /* N/A */ + { } + }, + .chained = true, + .chain_id = ALC880_FIXUP_3ST_BASE, + }, + [ALC880_FIXUP_3ST_DIG] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1e, 0x0144111e }, /* SPDIF */ + { } + }, + .chained = true, + .chain_id = ALC880_FIXUP_3ST_BASE, + }, + [ALC880_FIXUP_5ST_BASE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x01014010 }, /* front */ + { 0x15, 0x411111f0 }, /* N/A */ + { 0x16, 0x01011411 }, /* CLFE */ + { 0x17, 0x01016412 }, /* surr */ + { 0x18, 0x01a19c30 }, /* mic-in */ + { 0x19, 0x0121411f }, /* HP */ + { 0x1a, 0x01813031 }, /* line-in */ + { 0x1b, 0x02a19c40 }, /* front-mic */ + { 0x1c, 0x411111f0 }, /* N/A */ + { 0x1d, 0x411111f0 }, /* N/A */ + /* 0x1e is filled in below */ + { 0x1f, 0x411111f0 }, /* N/A */ + { } + } + }, + [ALC880_FIXUP_5ST] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1e, 0x411111f0 }, /* N/A */ + { } + }, + .chained = true, + .chain_id = ALC880_FIXUP_5ST_BASE, + }, + [ALC880_FIXUP_5ST_DIG] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1e, 0x0144111e }, /* SPDIF */ + { } + }, + .chained = true, + .chain_id = ALC880_FIXUP_5ST_BASE, + }, + [ALC880_FIXUP_6ST_BASE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x01014010 }, /* front */ + { 0x15, 0x01016412 }, /* surr */ + { 0x16, 0x01011411 }, /* CLFE */ + { 0x17, 0x01012414 }, /* side */ + { 0x18, 0x01a19c30 }, /* mic-in */ + { 0x19, 0x02a19c40 }, /* front-mic */ + { 0x1a, 0x01813031 }, /* line-in */ + { 0x1b, 0x0121411f }, /* HP */ + { 0x1c, 0x411111f0 }, /* N/A */ + { 0x1d, 0x411111f0 }, /* N/A */ + /* 0x1e is filled in below */ + { 0x1f, 0x411111f0 }, /* N/A */ + { } + } + }, + [ALC880_FIXUP_6ST] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1e, 0x411111f0 }, /* N/A */ + { } + }, + .chained = true, + .chain_id = ALC880_FIXUP_6ST_BASE, + }, + [ALC880_FIXUP_6ST_DIG] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1e, 0x0144111e }, /* SPDIF */ + { } + }, + .chained = true, + .chain_id = ALC880_FIXUP_6ST_BASE, + }, + [ALC880_FIXUP_6ST_AUTOMUTE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1b, 0x0121401f }, /* HP with jack detect */ + { } + }, + .chained_before = true, + .chain_id = ALC880_FIXUP_6ST_BASE, + }, +}; + +static const struct snd_pci_quirk alc880_fixup_tbl[] = { + SND_PCI_QUIRK(0x1019, 0x0f69, "Coeus G610P", ALC880_FIXUP_W810), + SND_PCI_QUIRK(0x1043, 0x10c3, "ASUS W5A", ALC880_FIXUP_ASUS_W5A), + SND_PCI_QUIRK(0x1043, 0x1964, "ASUS Z71V", ALC880_FIXUP_Z71V), + SND_PCI_QUIRK_VENDOR(0x1043, "ASUS", ALC880_FIXUP_GPIO1), + SND_PCI_QUIRK(0x147b, 0x1045, "ABit AA8XE", ALC880_FIXUP_6ST_AUTOMUTE), + SND_PCI_QUIRK(0x1558, 0x5401, "Clevo GPIO2", ALC880_FIXUP_GPIO2), + SND_PCI_QUIRK_VENDOR(0x1558, "Clevo", ALC880_FIXUP_EAPD_COEF), + SND_PCI_QUIRK(0x1584, 0x9050, "Uniwill", ALC880_FIXUP_UNIWILL_DIG), + SND_PCI_QUIRK(0x1584, 0x9054, "Uniwill", ALC880_FIXUP_F1734), + SND_PCI_QUIRK(0x1584, 0x9070, "Uniwill", ALC880_FIXUP_UNIWILL), + SND_PCI_QUIRK(0x1584, 0x9077, "Uniwill P53", ALC880_FIXUP_VOL_KNOB), + SND_PCI_QUIRK(0x161f, 0x203d, "W810", ALC880_FIXUP_W810), + SND_PCI_QUIRK(0x161f, 0x205d, "Medion Rim 2150", ALC880_FIXUP_MEDION_RIM), + SND_PCI_QUIRK(0x1631, 0xe011, "PB 13201056", ALC880_FIXUP_6ST_AUTOMUTE), + SND_PCI_QUIRK(0x1734, 0x107c, "FSC Amilo M1437", ALC880_FIXUP_FUJITSU), + SND_PCI_QUIRK(0x1734, 0x1094, "FSC Amilo M1451G", ALC880_FIXUP_FUJITSU), + SND_PCI_QUIRK(0x1734, 0x10ac, "FSC AMILO Xi 1526", ALC880_FIXUP_F1734), + SND_PCI_QUIRK(0x1734, 0x10b0, "FSC Amilo Pi1556", ALC880_FIXUP_FUJITSU), + SND_PCI_QUIRK(0x1854, 0x003b, "LG", ALC880_FIXUP_LG), + SND_PCI_QUIRK(0x1854, 0x005f, "LG P1 Express", ALC880_FIXUP_LG), + SND_PCI_QUIRK(0x1854, 0x0068, "LG w1", ALC880_FIXUP_LG), + SND_PCI_QUIRK(0x1854, 0x0077, "LG LW25", ALC880_FIXUP_LG_LW25), + SND_PCI_QUIRK(0x19db, 0x4188, "TCL S700", ALC880_FIXUP_TCL_S700), + + /* Below is the copied entries from alc880_quirks.c. + * It's not quite sure whether BIOS sets the correct pin-config table + * on these machines, thus they are kept to be compatible with + * the old static quirks. Once when it's confirmed to work without + * these overrides, it'd be better to remove. + */ + SND_PCI_QUIRK(0x1019, 0xa880, "ECS", ALC880_FIXUP_5ST_DIG), + SND_PCI_QUIRK(0x1019, 0xa884, "Acer APFV", ALC880_FIXUP_6ST), + SND_PCI_QUIRK(0x1025, 0x0070, "ULI", ALC880_FIXUP_3ST_DIG), + SND_PCI_QUIRK(0x1025, 0x0077, "ULI", ALC880_FIXUP_6ST_DIG), + SND_PCI_QUIRK(0x1025, 0x0078, "ULI", ALC880_FIXUP_6ST_DIG), + SND_PCI_QUIRK(0x1025, 0x0087, "ULI", ALC880_FIXUP_6ST_DIG), + SND_PCI_QUIRK(0x1025, 0xe309, "ULI", ALC880_FIXUP_3ST_DIG), + SND_PCI_QUIRK(0x1025, 0xe310, "ULI", ALC880_FIXUP_3ST), + SND_PCI_QUIRK(0x1039, 0x1234, NULL, ALC880_FIXUP_6ST_DIG), + SND_PCI_QUIRK(0x104d, 0x81a0, "Sony", ALC880_FIXUP_3ST), + SND_PCI_QUIRK(0x104d, 0x81d6, "Sony", ALC880_FIXUP_3ST), + SND_PCI_QUIRK(0x107b, 0x3032, "Gateway", ALC880_FIXUP_5ST), + SND_PCI_QUIRK(0x107b, 0x3033, "Gateway", ALC880_FIXUP_5ST), + SND_PCI_QUIRK(0x107b, 0x4039, "Gateway", ALC880_FIXUP_5ST), + SND_PCI_QUIRK(0x1297, 0xc790, "Shuttle ST20G5", ALC880_FIXUP_6ST_DIG), + SND_PCI_QUIRK(0x1458, 0xa102, "Gigabyte K8", ALC880_FIXUP_6ST_DIG), + SND_PCI_QUIRK(0x1462, 0x1150, "MSI", ALC880_FIXUP_6ST_DIG), + SND_PCI_QUIRK(0x1509, 0x925d, "FIC P4M", ALC880_FIXUP_6ST_DIG), + SND_PCI_QUIRK(0x1565, 0x8202, "Biostar", ALC880_FIXUP_5ST_DIG), + SND_PCI_QUIRK(0x1695, 0x400d, "EPoX", ALC880_FIXUP_5ST_DIG), + SND_PCI_QUIRK(0x1695, 0x4012, "EPox EP-5LDA", ALC880_FIXUP_5ST_DIG), + SND_PCI_QUIRK(0x2668, 0x8086, NULL, ALC880_FIXUP_6ST_DIG), /* broken BIOS */ + SND_PCI_QUIRK(0x8086, 0x2668, NULL, ALC880_FIXUP_6ST_DIG), + SND_PCI_QUIRK(0x8086, 0xa100, "Intel mobo", ALC880_FIXUP_5ST_DIG), + SND_PCI_QUIRK(0x8086, 0xd400, "Intel mobo", ALC880_FIXUP_5ST_DIG), + SND_PCI_QUIRK(0x8086, 0xd401, "Intel mobo", ALC880_FIXUP_5ST_DIG), + SND_PCI_QUIRK(0x8086, 0xd402, "Intel mobo", ALC880_FIXUP_3ST_DIG), + SND_PCI_QUIRK(0x8086, 0xe224, "Intel mobo", ALC880_FIXUP_5ST_DIG), + SND_PCI_QUIRK(0x8086, 0xe305, "Intel mobo", ALC880_FIXUP_3ST_DIG), + SND_PCI_QUIRK(0x8086, 0xe308, "Intel mobo", ALC880_FIXUP_3ST_DIG), + SND_PCI_QUIRK(0x8086, 0xe400, "Intel mobo", ALC880_FIXUP_5ST_DIG), + SND_PCI_QUIRK(0x8086, 0xe401, "Intel mobo", ALC880_FIXUP_5ST_DIG), + SND_PCI_QUIRK(0x8086, 0xe402, "Intel mobo", ALC880_FIXUP_5ST_DIG), + /* default Intel */ + SND_PCI_QUIRK_VENDOR(0x8086, "Intel mobo", ALC880_FIXUP_3ST), + SND_PCI_QUIRK(0xa0a0, 0x0560, "AOpen i915GMm-HFS", ALC880_FIXUP_5ST_DIG), + SND_PCI_QUIRK(0xe803, 0x1019, NULL, ALC880_FIXUP_6ST_DIG), + {} +}; + +static const struct hda_model_fixup alc880_fixup_models[] = { + {.id = ALC880_FIXUP_3ST, .name = "3stack"}, + {.id = ALC880_FIXUP_3ST_DIG, .name = "3stack-digout"}, + {.id = ALC880_FIXUP_5ST, .name = "5stack"}, + {.id = ALC880_FIXUP_5ST_DIG, .name = "5stack-digout"}, + {.id = ALC880_FIXUP_6ST, .name = "6stack"}, + {.id = ALC880_FIXUP_6ST_DIG, .name = "6stack-digout"}, + {.id = ALC880_FIXUP_6ST_AUTOMUTE, .name = "6stack-automute"}, + {} +}; + + +/* + * OK, here we have finally the patch for ALC880 + */ +static int patch_alc880(struct hda_codec *codec) +{ + struct alc_spec *spec; + int err; + + err = alc_alloc_spec(codec, 0x0b); + if (err < 0) + return err; + + spec = codec->spec; + spec->gen.need_dac_fix = 1; + spec->gen.beep_nid = 0x01; + + codec->patch_ops.unsol_event = alc880_unsol_event; + + alc_pre_init(codec); + + snd_hda_pick_fixup(codec, alc880_fixup_models, alc880_fixup_tbl, + alc880_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + /* automatic parse from the BIOS config */ + err = alc880_parse_auto_config(codec); + if (err < 0) + goto error; + + if (!spec->gen.no_analog) { + err = set_beep_amp(spec, 0x0b, 0x05, HDA_INPUT); + if (err < 0) + goto error; + } + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; + + error: + alc_free(codec); + return err; +} + + +/* + * ALC260 support + */ +static int alc260_parse_auto_config(struct hda_codec *codec) +{ + static const hda_nid_t alc260_ignore[] = { 0x17, 0 }; + static const hda_nid_t alc260_ssids[] = { 0x10, 0x15, 0x0f, 0 }; + return alc_parse_auto_config(codec, alc260_ignore, alc260_ssids); +} + +/* + * Pin config fixes + */ +enum { + ALC260_FIXUP_HP_DC5750, + ALC260_FIXUP_HP_PIN_0F, + ALC260_FIXUP_COEF, + ALC260_FIXUP_GPIO1, + ALC260_FIXUP_GPIO1_TOGGLE, + ALC260_FIXUP_REPLACER, + ALC260_FIXUP_HP_B1900, + ALC260_FIXUP_KN1, + ALC260_FIXUP_FSC_S7020, + ALC260_FIXUP_FSC_S7020_JWSE, + ALC260_FIXUP_VAIO_PINS, +}; + +static void alc260_gpio1_automute(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + alc_update_gpio_data(codec, 0x01, spec->gen.hp_jack_present); +} + +static void alc260_fixup_gpio1_toggle(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + if (action == HDA_FIXUP_ACT_PROBE) { + /* although the machine has only one output pin, we need to + * toggle GPIO1 according to the jack state + */ + spec->gen.automute_hook = alc260_gpio1_automute; + spec->gen.detect_hp = 1; + spec->gen.automute_speaker = 1; + spec->gen.autocfg.hp_pins[0] = 0x0f; /* copy it for automute */ + snd_hda_jack_detect_enable_callback(codec, 0x0f, + snd_hda_gen_hp_automute); + alc_setup_gpio(codec, 0x01); + } +} + +static void alc260_fixup_kn1(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + static const struct hda_pintbl pincfgs[] = { + { 0x0f, 0x02214000 }, /* HP/speaker */ + { 0x12, 0x90a60160 }, /* int mic */ + { 0x13, 0x02a19000 }, /* ext mic */ + { 0x18, 0x01446000 }, /* SPDIF out */ + /* disable bogus I/O pins */ + { 0x10, 0x411111f0 }, + { 0x11, 0x411111f0 }, + { 0x14, 0x411111f0 }, + { 0x15, 0x411111f0 }, + { 0x16, 0x411111f0 }, + { 0x17, 0x411111f0 }, + { 0x19, 0x411111f0 }, + { } + }; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + snd_hda_apply_pincfgs(codec, pincfgs); + spec->init_amp = ALC_INIT_NONE; + break; + } +} + +static void alc260_fixup_fsc_s7020(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + if (action == HDA_FIXUP_ACT_PRE_PROBE) + spec->init_amp = ALC_INIT_NONE; +} + +static void alc260_fixup_fsc_s7020_jwse(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->gen.add_jack_modes = 1; + spec->gen.hp_mic = 1; + } +} + +static const struct hda_fixup alc260_fixups[] = { + [ALC260_FIXUP_HP_DC5750] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x11, 0x90130110 }, /* speaker */ + { } + } + }, + [ALC260_FIXUP_HP_PIN_0F] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x0f, 0x01214000 }, /* HP */ + { } + } + }, + [ALC260_FIXUP_COEF] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x1a, AC_VERB_SET_COEF_INDEX, 0x07 }, + { 0x1a, AC_VERB_SET_PROC_COEF, 0x3040 }, + { } + }, + }, + [ALC260_FIXUP_GPIO1] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_gpio1, + }, + [ALC260_FIXUP_GPIO1_TOGGLE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc260_fixup_gpio1_toggle, + .chained = true, + .chain_id = ALC260_FIXUP_HP_PIN_0F, + }, + [ALC260_FIXUP_REPLACER] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x1a, AC_VERB_SET_COEF_INDEX, 0x07 }, + { 0x1a, AC_VERB_SET_PROC_COEF, 0x3050 }, + { } + }, + .chained = true, + .chain_id = ALC260_FIXUP_GPIO1_TOGGLE, + }, + [ALC260_FIXUP_HP_B1900] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc260_fixup_gpio1_toggle, + .chained = true, + .chain_id = ALC260_FIXUP_COEF, + }, + [ALC260_FIXUP_KN1] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc260_fixup_kn1, + }, + [ALC260_FIXUP_FSC_S7020] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc260_fixup_fsc_s7020, + }, + [ALC260_FIXUP_FSC_S7020_JWSE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc260_fixup_fsc_s7020_jwse, + .chained = true, + .chain_id = ALC260_FIXUP_FSC_S7020, + }, + [ALC260_FIXUP_VAIO_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + /* Pin configs are missing completely on some VAIOs */ + { 0x0f, 0x01211020 }, + { 0x10, 0x0001003f }, + { 0x11, 0x411111f0 }, + { 0x12, 0x01a15930 }, + { 0x13, 0x411111f0 }, + { 0x14, 0x411111f0 }, + { 0x15, 0x411111f0 }, + { 0x16, 0x411111f0 }, + { 0x17, 0x411111f0 }, + { 0x18, 0x411111f0 }, + { 0x19, 0x411111f0 }, + { } + } + }, +}; + +static const struct snd_pci_quirk alc260_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x007b, "Acer C20x", ALC260_FIXUP_GPIO1), + SND_PCI_QUIRK(0x1025, 0x007f, "Acer Aspire 9500", ALC260_FIXUP_COEF), + SND_PCI_QUIRK(0x1025, 0x008f, "Acer", ALC260_FIXUP_GPIO1), + SND_PCI_QUIRK(0x103c, 0x280a, "HP dc5750", ALC260_FIXUP_HP_DC5750), + SND_PCI_QUIRK(0x103c, 0x30ba, "HP Presario B1900", ALC260_FIXUP_HP_B1900), + SND_PCI_QUIRK(0x104d, 0x81bb, "Sony VAIO", ALC260_FIXUP_VAIO_PINS), + SND_PCI_QUIRK(0x104d, 0x81e2, "Sony VAIO TX", ALC260_FIXUP_HP_PIN_0F), + SND_PCI_QUIRK(0x10cf, 0x1326, "FSC LifeBook S7020", ALC260_FIXUP_FSC_S7020), + SND_PCI_QUIRK(0x1509, 0x4540, "Favorit 100XS", ALC260_FIXUP_GPIO1), + SND_PCI_QUIRK(0x152d, 0x0729, "Quanta KN1", ALC260_FIXUP_KN1), + SND_PCI_QUIRK(0x161f, 0x2057, "Replacer 672V", ALC260_FIXUP_REPLACER), + SND_PCI_QUIRK(0x1631, 0xc017, "PB V7900", ALC260_FIXUP_COEF), + {} +}; + +static const struct hda_model_fixup alc260_fixup_models[] = { + {.id = ALC260_FIXUP_GPIO1, .name = "gpio1"}, + {.id = ALC260_FIXUP_COEF, .name = "coef"}, + {.id = ALC260_FIXUP_FSC_S7020, .name = "fujitsu"}, + {.id = ALC260_FIXUP_FSC_S7020_JWSE, .name = "fujitsu-jwse"}, + {} +}; + +/* + */ +static int patch_alc260(struct hda_codec *codec) +{ + struct alc_spec *spec; + int err; + + err = alc_alloc_spec(codec, 0x07); + if (err < 0) + return err; + + spec = codec->spec; + /* as quite a few machines require HP amp for speaker outputs, + * it's easier to enable it unconditionally; even if it's unneeded, + * it's almost harmless. + */ + spec->gen.prefer_hp_amp = 1; + spec->gen.beep_nid = 0x01; + + spec->shutup = alc_eapd_shutup; + + alc_pre_init(codec); + + snd_hda_pick_fixup(codec, alc260_fixup_models, alc260_fixup_tbl, + alc260_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + /* automatic parse from the BIOS config */ + err = alc260_parse_auto_config(codec); + if (err < 0) + goto error; + + if (!spec->gen.no_analog) { + err = set_beep_amp(spec, 0x07, 0x05, HDA_INPUT); + if (err < 0) + goto error; + } + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; + + error: + alc_free(codec); + return err; +} + + +/* + * ALC882/883/885/888/889 support + * + * ALC882 is almost identical with ALC880 but has cleaner and more flexible + * configuration. Each pin widget can choose any input DACs and a mixer. + * Each ADC is connected from a mixer of all inputs. This makes possible + * 6-channel independent captures. + * + * In addition, an independent DAC for the multi-playback (not used in this + * driver yet). + */ + +/* + * Pin config fixes + */ +enum { + ALC882_FIXUP_ABIT_AW9D_MAX, + ALC882_FIXUP_LENOVO_Y530, + ALC882_FIXUP_PB_M5210, + ALC882_FIXUP_ACER_ASPIRE_7736, + ALC882_FIXUP_ASUS_W90V, + ALC889_FIXUP_CD, + ALC889_FIXUP_FRONT_HP_NO_PRESENCE, + ALC889_FIXUP_VAIO_TT, + ALC888_FIXUP_EEE1601, + ALC886_FIXUP_EAPD, + ALC882_FIXUP_EAPD, + ALC883_FIXUP_EAPD, + ALC883_FIXUP_ACER_EAPD, + ALC882_FIXUP_GPIO1, + ALC882_FIXUP_GPIO2, + ALC882_FIXUP_GPIO3, + ALC889_FIXUP_COEF, + ALC882_FIXUP_ASUS_W2JC, + ALC882_FIXUP_ACER_ASPIRE_4930G, + ALC882_FIXUP_ACER_ASPIRE_8930G, + ALC882_FIXUP_ASPIRE_8930G_VERBS, + ALC885_FIXUP_MACPRO_GPIO, + ALC889_FIXUP_DAC_ROUTE, + ALC889_FIXUP_MBP_VREF, + ALC889_FIXUP_IMAC91_VREF, + ALC889_FIXUP_MBA11_VREF, + ALC889_FIXUP_MBA21_VREF, + ALC889_FIXUP_MP11_VREF, + ALC889_FIXUP_MP41_VREF, + ALC882_FIXUP_INV_DMIC, + ALC882_FIXUP_NO_PRIMARY_HP, + ALC887_FIXUP_ASUS_BASS, + ALC887_FIXUP_BASS_CHMAP, + ALC1220_FIXUP_GB_DUAL_CODECS, + ALC1220_FIXUP_GB_X570, + ALC1220_FIXUP_CLEVO_P950, + ALC1220_FIXUP_CLEVO_PB51ED, + ALC1220_FIXUP_CLEVO_PB51ED_PINS, + ALC887_FIXUP_ASUS_AUDIO, + ALC887_FIXUP_ASUS_HMIC, + ALCS1200A_FIXUP_MIC_VREF, + ALC888VD_FIXUP_MIC_100VREF, +}; + +static void alc889_fixup_coef(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_INIT) + return; + alc_update_coef_idx(codec, 7, 0, 0x2030); +} + +/* set up GPIO at initialization */ +static void alc885_fixup_macpro_gpio(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + spec->gpio_write_delay = true; + alc_fixup_gpio3(codec, fix, action); +} + +/* Fix the connection of some pins for ALC889: + * At least, Acer Aspire 5935 shows the connections to DAC3/4 don't + * work correctly (bko#42740) + */ +static void alc889_fixup_dac_route(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + /* fake the connections during parsing the tree */ + static const hda_nid_t conn1[] = { 0x0c, 0x0d }; + static const hda_nid_t conn2[] = { 0x0e, 0x0f }; + snd_hda_override_conn_list(codec, 0x14, ARRAY_SIZE(conn1), conn1); + snd_hda_override_conn_list(codec, 0x15, ARRAY_SIZE(conn1), conn1); + snd_hda_override_conn_list(codec, 0x18, ARRAY_SIZE(conn2), conn2); + snd_hda_override_conn_list(codec, 0x1a, ARRAY_SIZE(conn2), conn2); + } else if (action == HDA_FIXUP_ACT_PROBE) { + /* restore the connections */ + static const hda_nid_t conn[] = { 0x0c, 0x0d, 0x0e, 0x0f, 0x26 }; + snd_hda_override_conn_list(codec, 0x14, ARRAY_SIZE(conn), conn); + snd_hda_override_conn_list(codec, 0x15, ARRAY_SIZE(conn), conn); + snd_hda_override_conn_list(codec, 0x18, ARRAY_SIZE(conn), conn); + snd_hda_override_conn_list(codec, 0x1a, ARRAY_SIZE(conn), conn); + } +} + +/* Set VREF on HP pin */ +static void alc889_fixup_mbp_vref(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + static const hda_nid_t nids[] = { 0x14, 0x15, 0x19 }; + struct alc_spec *spec = codec->spec; + int i; + + if (action != HDA_FIXUP_ACT_INIT) + return; + for (i = 0; i < ARRAY_SIZE(nids); i++) { + unsigned int val = snd_hda_codec_get_pincfg(codec, nids[i]); + if (get_defcfg_device(val) != AC_JACK_HP_OUT) + continue; + val = snd_hda_codec_get_pin_target(codec, nids[i]); + val |= AC_PINCTL_VREF_80; + snd_hda_set_pin_ctl(codec, nids[i], val); + spec->gen.keep_vref_in_automute = 1; + break; + } +} + +static void alc889_fixup_mac_pins(struct hda_codec *codec, + const hda_nid_t *nids, int num_nids) +{ + struct alc_spec *spec = codec->spec; + int i; + + for (i = 0; i < num_nids; i++) { + unsigned int val; + val = snd_hda_codec_get_pin_target(codec, nids[i]); + val |= AC_PINCTL_VREF_50; + snd_hda_set_pin_ctl(codec, nids[i], val); + } + spec->gen.keep_vref_in_automute = 1; +} + +/* Set VREF on speaker pins on imac91 */ +static void alc889_fixup_imac91_vref(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + static const hda_nid_t nids[] = { 0x18, 0x1a }; + + if (action == HDA_FIXUP_ACT_INIT) + alc889_fixup_mac_pins(codec, nids, ARRAY_SIZE(nids)); +} + +/* Set VREF on speaker pins on mba11 */ +static void alc889_fixup_mba11_vref(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + static const hda_nid_t nids[] = { 0x18 }; + + if (action == HDA_FIXUP_ACT_INIT) + alc889_fixup_mac_pins(codec, nids, ARRAY_SIZE(nids)); +} + +/* Set VREF on speaker pins on mba21 */ +static void alc889_fixup_mba21_vref(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + static const hda_nid_t nids[] = { 0x18, 0x19 }; + + if (action == HDA_FIXUP_ACT_INIT) + alc889_fixup_mac_pins(codec, nids, ARRAY_SIZE(nids)); +} + +/* Don't take HP output as primary + * Strangely, the speaker output doesn't work on Vaio Z and some Vaio + * all-in-one desktop PCs (for example VGC-LN51JGB) through DAC 0x05 + */ +static void alc882_fixup_no_primary_hp(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->gen.no_primary_hp = 1; + spec->gen.no_multi_io = 1; + } +} + +static void alc_fixup_bass_chmap(struct hda_codec *codec, + const struct hda_fixup *fix, int action); + +/* For dual-codec configuration, we need to disable some features to avoid + * conflicts of kctls and PCM streams + */ +static void alc_fixup_dual_codecs(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + /* disable vmaster */ + spec->gen.suppress_vmaster = 1; + /* auto-mute and auto-mic switch don't work with multiple codecs */ + spec->gen.suppress_auto_mute = 1; + spec->gen.suppress_auto_mic = 1; + /* disable aamix as well */ + spec->gen.mixer_nid = 0; + /* add location prefix to avoid conflicts */ + codec->force_pin_prefix = 1; +} + +static void rename_ctl(struct hda_codec *codec, const char *oldname, + const char *newname) +{ + struct snd_kcontrol *kctl; + + kctl = snd_hda_find_mixer_ctl(codec, oldname); + if (kctl) + snd_ctl_rename(codec->card, kctl, newname); +} + +static void alc1220_fixup_gb_dual_codecs(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + alc_fixup_dual_codecs(codec, fix, action); + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + /* override card longname to provide a unique UCM profile */ + strcpy(codec->card->longname, "HDAudio-Gigabyte-ALC1220DualCodecs"); + break; + case HDA_FIXUP_ACT_BUILD: + /* rename Capture controls depending on the codec */ + rename_ctl(codec, "Capture Volume", + codec->addr == 0 ? + "Rear-Panel Capture Volume" : + "Front-Panel Capture Volume"); + rename_ctl(codec, "Capture Switch", + codec->addr == 0 ? + "Rear-Panel Capture Switch" : + "Front-Panel Capture Switch"); + break; + } +} + +static void alc1220_fixup_gb_x570(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + static const hda_nid_t conn1[] = { 0x0c }; + static const struct coef_fw gb_x570_coefs[] = { + WRITE_COEF(0x07, 0x03c0), + WRITE_COEF(0x1a, 0x01c1), + WRITE_COEF(0x1b, 0x0202), + WRITE_COEF(0x43, 0x3005), + {} + }; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + snd_hda_override_conn_list(codec, 0x14, ARRAY_SIZE(conn1), conn1); + snd_hda_override_conn_list(codec, 0x1b, ARRAY_SIZE(conn1), conn1); + break; + case HDA_FIXUP_ACT_INIT: + alc_process_coef_fw(codec, gb_x570_coefs); + break; + } +} + +static void alc1220_fixup_clevo_p950(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + static const hda_nid_t conn1[] = { 0x0c }; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + alc_update_coef_idx(codec, 0x7, 0, 0x3c3); + /* We therefore want to make sure 0x14 (front headphone) and + * 0x1b (speakers) use the stereo DAC 0x02 + */ + snd_hda_override_conn_list(codec, 0x14, ARRAY_SIZE(conn1), conn1); + snd_hda_override_conn_list(codec, 0x1b, ARRAY_SIZE(conn1), conn1); +} + +static void alc_fixup_headset_mode_no_hp_mic(struct hda_codec *codec, + const struct hda_fixup *fix, int action); + +static void alc1220_fixup_clevo_pb51ed(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + alc1220_fixup_clevo_p950(codec, fix, action); + alc_fixup_headset_mode_no_hp_mic(codec, fix, action); +} + +static void alc887_asus_hp_automute_hook(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + struct alc_spec *spec = codec->spec; + unsigned int vref; + + snd_hda_gen_hp_automute(codec, jack); + + if (spec->gen.hp_jack_present) + vref = AC_PINCTL_VREF_80; + else + vref = AC_PINCTL_VREF_HIZ; + snd_hda_set_pin_ctl(codec, 0x19, PIN_HP | vref); +} + +static void alc887_fixup_asus_jack(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + if (action != HDA_FIXUP_ACT_PROBE) + return; + snd_hda_set_pin_ctl_cache(codec, 0x1b, PIN_HP); + spec->gen.hp_automute_hook = alc887_asus_hp_automute_hook; +} + +static const struct hda_fixup alc882_fixups[] = { + [ALC882_FIXUP_ABIT_AW9D_MAX] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x15, 0x01080104 }, /* side */ + { 0x16, 0x01011012 }, /* rear */ + { 0x17, 0x01016011 }, /* clfe */ + { } + } + }, + [ALC882_FIXUP_LENOVO_Y530] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x15, 0x99130112 }, /* rear int speakers */ + { 0x16, 0x99130111 }, /* subwoofer */ + { } + } + }, + [ALC882_FIXUP_PB_M5210] = { + .type = HDA_FIXUP_PINCTLS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, PIN_VREF50 }, + {} + } + }, + [ALC882_FIXUP_ACER_ASPIRE_7736] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_sku_ignore, + }, + [ALC882_FIXUP_ASUS_W90V] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x16, 0x99130110 }, /* fix sequence for CLFE */ + { } + } + }, + [ALC889_FIXUP_CD] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1c, 0x993301f0 }, /* CD */ + { } + } + }, + [ALC889_FIXUP_FRONT_HP_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1b, 0x02214120 }, /* Front HP jack is flaky, disable jack detect */ + { } + }, + .chained = true, + .chain_id = ALC889_FIXUP_CD, + }, + [ALC889_FIXUP_VAIO_TT] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x17, 0x90170111 }, /* hidden surround speaker */ + { } + } + }, + [ALC888_FIXUP_EEE1601] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x20, AC_VERB_SET_COEF_INDEX, 0x0b }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0838 }, + { } + } + }, + [ALC886_FIXUP_EAPD] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* change to EAPD mode */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0068 }, + { } + } + }, + [ALC882_FIXUP_EAPD] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* change to EAPD mode */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x3060 }, + { } + } + }, + [ALC883_FIXUP_EAPD] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* change to EAPD mode */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x3070 }, + { } + } + }, + [ALC883_FIXUP_ACER_EAPD] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* eanable EAPD on Acer laptops */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x3050 }, + { } + } + }, + [ALC882_FIXUP_GPIO1] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_gpio1, + }, + [ALC882_FIXUP_GPIO2] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_gpio2, + }, + [ALC882_FIXUP_GPIO3] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_gpio3, + }, + [ALC882_FIXUP_ASUS_W2JC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_gpio1, + .chained = true, + .chain_id = ALC882_FIXUP_EAPD, + }, + [ALC889_FIXUP_COEF] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc889_fixup_coef, + }, + [ALC882_FIXUP_ACER_ASPIRE_4930G] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x16, 0x99130111 }, /* CLFE speaker */ + { 0x17, 0x99130112 }, /* surround speaker */ + { } + }, + .chained = true, + .chain_id = ALC882_FIXUP_GPIO1, + }, + [ALC882_FIXUP_ACER_ASPIRE_8930G] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x16, 0x99130111 }, /* CLFE speaker */ + { 0x1b, 0x99130112 }, /* surround speaker */ + { } + }, + .chained = true, + .chain_id = ALC882_FIXUP_ASPIRE_8930G_VERBS, + }, + [ALC882_FIXUP_ASPIRE_8930G_VERBS] = { + /* additional init verbs for Acer Aspire 8930G */ + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* Enable all DACs */ + /* DAC DISABLE/MUTE 1? */ + /* setting bits 1-5 disables DAC nids 0x02-0x06 + * apparently. Init=0x38 */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x03 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, + /* DAC DISABLE/MUTE 2? */ + /* some bit here disables the other DACs. + * Init=0x4900 */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x08 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0000 }, + /* DMIC fix + * This laptop has a stereo digital microphone. + * The mics are only 1cm apart which makes the stereo + * useless. However, either the mic or the ALC889 + * makes the signal become a difference/sum signal + * instead of standard stereo, which is annoying. + * So instead we flip this bit which makes the + * codec replicate the sum signal to both channels, + * turning it into a normal mono mic. + */ + /* DMIC_CONTROL? Init value = 0x0001 */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x0b }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0003 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x3050 }, + { } + }, + .chained = true, + .chain_id = ALC882_FIXUP_GPIO1, + }, + [ALC885_FIXUP_MACPRO_GPIO] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc885_fixup_macpro_gpio, + }, + [ALC889_FIXUP_DAC_ROUTE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc889_fixup_dac_route, + }, + [ALC889_FIXUP_MBP_VREF] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc889_fixup_mbp_vref, + .chained = true, + .chain_id = ALC882_FIXUP_GPIO1, + }, + [ALC889_FIXUP_IMAC91_VREF] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc889_fixup_imac91_vref, + .chained = true, + .chain_id = ALC882_FIXUP_GPIO1, + }, + [ALC889_FIXUP_MBA11_VREF] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc889_fixup_mba11_vref, + .chained = true, + .chain_id = ALC889_FIXUP_MBP_VREF, + }, + [ALC889_FIXUP_MBA21_VREF] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc889_fixup_mba21_vref, + .chained = true, + .chain_id = ALC889_FIXUP_MBP_VREF, + }, + [ALC889_FIXUP_MP11_VREF] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc889_fixup_mba11_vref, + .chained = true, + .chain_id = ALC885_FIXUP_MACPRO_GPIO, + }, + [ALC889_FIXUP_MP41_VREF] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc889_fixup_mbp_vref, + .chained = true, + .chain_id = ALC885_FIXUP_MACPRO_GPIO, + }, + [ALC882_FIXUP_INV_DMIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_inv_dmic, + }, + [ALC882_FIXUP_NO_PRIMARY_HP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc882_fixup_no_primary_hp, + }, + [ALC887_FIXUP_ASUS_BASS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + {0x16, 0x99130130}, /* bass speaker */ + {} + }, + .chained = true, + .chain_id = ALC887_FIXUP_BASS_CHMAP, + }, + [ALC887_FIXUP_BASS_CHMAP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_bass_chmap, + }, + [ALC1220_FIXUP_GB_DUAL_CODECS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc1220_fixup_gb_dual_codecs, + }, + [ALC1220_FIXUP_GB_X570] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc1220_fixup_gb_x570, + }, + [ALC1220_FIXUP_CLEVO_P950] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc1220_fixup_clevo_p950, + }, + [ALC1220_FIXUP_CLEVO_PB51ED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc1220_fixup_clevo_pb51ed, + }, + [ALC1220_FIXUP_CLEVO_PB51ED_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + {} + }, + .chained = true, + .chain_id = ALC1220_FIXUP_CLEVO_PB51ED, + }, + [ALC887_FIXUP_ASUS_AUDIO] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x15, 0x02a14150 }, /* use as headset mic, without its own jack detect */ + { 0x19, 0x22219420 }, + {} + }, + }, + [ALC887_FIXUP_ASUS_HMIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc887_fixup_asus_jack, + .chained = true, + .chain_id = ALC887_FIXUP_ASUS_AUDIO, + }, + [ALCS1200A_FIXUP_MIC_VREF] = { + .type = HDA_FIXUP_PINCTLS, + .v.pins = (const struct hda_pintbl[]) { + { 0x18, PIN_VREF50 }, /* rear mic */ + { 0x19, PIN_VREF50 }, /* front mic */ + {} + } + }, + [ALC888VD_FIXUP_MIC_100VREF] = { + .type = HDA_FIXUP_PINCTLS, + .v.pins = (const struct hda_pintbl[]) { + { 0x18, PIN_VREF100 }, /* headset mic */ + {} + } + }, +}; + +static const struct snd_pci_quirk alc882_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x006c, "Acer Aspire 9810", ALC883_FIXUP_ACER_EAPD), + SND_PCI_QUIRK(0x1025, 0x0090, "Acer Aspire", ALC883_FIXUP_ACER_EAPD), + SND_PCI_QUIRK(0x1025, 0x0107, "Acer Aspire", ALC883_FIXUP_ACER_EAPD), + SND_PCI_QUIRK(0x1025, 0x010a, "Acer Ferrari 5000", ALC883_FIXUP_ACER_EAPD), + SND_PCI_QUIRK(0x1025, 0x0110, "Acer Aspire", ALC883_FIXUP_ACER_EAPD), + SND_PCI_QUIRK(0x1025, 0x0112, "Acer Aspire 9303", ALC883_FIXUP_ACER_EAPD), + SND_PCI_QUIRK(0x1025, 0x0121, "Acer Aspire 5920G", ALC883_FIXUP_ACER_EAPD), + SND_PCI_QUIRK(0x1025, 0x013e, "Acer Aspire 4930G", + ALC882_FIXUP_ACER_ASPIRE_4930G), + SND_PCI_QUIRK(0x1025, 0x013f, "Acer Aspire 5930G", + ALC882_FIXUP_ACER_ASPIRE_4930G), + SND_PCI_QUIRK(0x1025, 0x0145, "Acer Aspire 8930G", + ALC882_FIXUP_ACER_ASPIRE_8930G), + SND_PCI_QUIRK(0x1025, 0x0146, "Acer Aspire 6935G", + ALC882_FIXUP_ACER_ASPIRE_8930G), + SND_PCI_QUIRK(0x1025, 0x0142, "Acer Aspire 7730G", + ALC882_FIXUP_ACER_ASPIRE_4930G), + SND_PCI_QUIRK(0x1025, 0x0155, "Packard-Bell M5120", ALC882_FIXUP_PB_M5210), + SND_PCI_QUIRK(0x1025, 0x015e, "Acer Aspire 6930G", + ALC882_FIXUP_ACER_ASPIRE_4930G), + SND_PCI_QUIRK(0x1025, 0x0166, "Acer Aspire 6530G", + ALC882_FIXUP_ACER_ASPIRE_4930G), + SND_PCI_QUIRK(0x1025, 0x021e, "Acer Aspire 5739G", + ALC882_FIXUP_ACER_ASPIRE_4930G), + SND_PCI_QUIRK(0x1025, 0x0259, "Acer Aspire 5935", ALC889_FIXUP_DAC_ROUTE), + SND_PCI_QUIRK(0x1025, 0x026b, "Acer Aspire 8940G", ALC882_FIXUP_ACER_ASPIRE_8930G), + SND_PCI_QUIRK(0x1025, 0x0296, "Acer Aspire 7736z", ALC882_FIXUP_ACER_ASPIRE_7736), + SND_PCI_QUIRK(0x1043, 0x13c2, "Asus A7M", ALC882_FIXUP_EAPD), + SND_PCI_QUIRK(0x1043, 0x1873, "ASUS W90V", ALC882_FIXUP_ASUS_W90V), + SND_PCI_QUIRK(0x1043, 0x1971, "Asus W2JC", ALC882_FIXUP_ASUS_W2JC), + SND_PCI_QUIRK(0x1043, 0x2390, "Asus D700SA", ALC887_FIXUP_ASUS_HMIC), + SND_PCI_QUIRK(0x1043, 0x835f, "Asus Eee 1601", ALC888_FIXUP_EEE1601), + SND_PCI_QUIRK(0x1043, 0x84bc, "ASUS ET2700", ALC887_FIXUP_ASUS_BASS), + SND_PCI_QUIRK(0x1043, 0x8691, "ASUS ROG Ranger VIII", ALC882_FIXUP_GPIO3), + SND_PCI_QUIRK(0x1043, 0x8797, "ASUS TUF B550M-PLUS", ALCS1200A_FIXUP_MIC_VREF), + SND_PCI_QUIRK(0x104d, 0x9043, "Sony Vaio VGC-LN51JGB", ALC882_FIXUP_NO_PRIMARY_HP), + SND_PCI_QUIRK(0x104d, 0x9044, "Sony VAIO AiO", ALC882_FIXUP_NO_PRIMARY_HP), + SND_PCI_QUIRK(0x104d, 0x9047, "Sony Vaio TT", ALC889_FIXUP_VAIO_TT), + SND_PCI_QUIRK(0x104d, 0x905a, "Sony Vaio Z", ALC882_FIXUP_NO_PRIMARY_HP), + SND_PCI_QUIRK(0x104d, 0x9060, "Sony Vaio VPCL14M1R", ALC882_FIXUP_NO_PRIMARY_HP), + + /* All Apple entries are in codec SSIDs */ + SND_PCI_QUIRK(0x106b, 0x00a0, "MacBookPro 3,1", ALC889_FIXUP_MBP_VREF), + SND_PCI_QUIRK(0x106b, 0x00a1, "Macbook", ALC889_FIXUP_MBP_VREF), + SND_PCI_QUIRK(0x106b, 0x00a4, "MacbookPro 4,1", ALC889_FIXUP_MBP_VREF), + SND_PCI_QUIRK(0x106b, 0x0c00, "Mac Pro", ALC889_FIXUP_MP11_VREF), + SND_PCI_QUIRK(0x106b, 0x1000, "iMac 24", ALC885_FIXUP_MACPRO_GPIO), + SND_PCI_QUIRK(0x106b, 0x2800, "AppleTV", ALC885_FIXUP_MACPRO_GPIO), + SND_PCI_QUIRK(0x106b, 0x2c00, "MacbookPro rev3", ALC889_FIXUP_MBP_VREF), + SND_PCI_QUIRK(0x106b, 0x3000, "iMac", ALC889_FIXUP_MBP_VREF), + SND_PCI_QUIRK(0x106b, 0x3200, "iMac 7,1 Aluminum", ALC882_FIXUP_EAPD), + SND_PCI_QUIRK(0x106b, 0x3400, "MacBookAir 1,1", ALC889_FIXUP_MBA11_VREF), + SND_PCI_QUIRK(0x106b, 0x3500, "MacBookAir 2,1", ALC889_FIXUP_MBA21_VREF), + SND_PCI_QUIRK(0x106b, 0x3600, "Macbook 3,1", ALC889_FIXUP_MBP_VREF), + SND_PCI_QUIRK(0x106b, 0x3800, "MacbookPro 4,1", ALC889_FIXUP_MBP_VREF), + SND_PCI_QUIRK(0x106b, 0x3e00, "iMac 24 Aluminum", ALC885_FIXUP_MACPRO_GPIO), + SND_PCI_QUIRK(0x106b, 0x3f00, "Macbook 5,1", ALC889_FIXUP_IMAC91_VREF), + SND_PCI_QUIRK(0x106b, 0x4000, "MacbookPro 5,1", ALC889_FIXUP_IMAC91_VREF), + SND_PCI_QUIRK(0x106b, 0x4100, "Macmini 3,1", ALC889_FIXUP_IMAC91_VREF), + SND_PCI_QUIRK(0x106b, 0x4200, "Mac Pro 4,1/5,1", ALC889_FIXUP_MP41_VREF), + SND_PCI_QUIRK(0x106b, 0x4300, "iMac 9,1", ALC889_FIXUP_IMAC91_VREF), + SND_PCI_QUIRK(0x106b, 0x4600, "MacbookPro 5,2", ALC889_FIXUP_IMAC91_VREF), + SND_PCI_QUIRK(0x106b, 0x4900, "iMac 9,1 Aluminum", ALC889_FIXUP_IMAC91_VREF), + SND_PCI_QUIRK(0x106b, 0x4a00, "Macbook 5,2", ALC889_FIXUP_MBA11_VREF), + + SND_PCI_QUIRK(0x1071, 0x8258, "Evesham Voyaeger", ALC882_FIXUP_EAPD), + SND_PCI_QUIRK(0x10ec, 0x12d8, "iBase Elo Touch", ALC888VD_FIXUP_MIC_100VREF), + SND_PCI_QUIRK(0x13fe, 0x1009, "Advantech MIT-W101", ALC886_FIXUP_EAPD), + SND_PCI_QUIRK(0x1458, 0xa002, "Gigabyte EP45-DS3/Z87X-UD3H", ALC889_FIXUP_FRONT_HP_NO_PRESENCE), + SND_PCI_QUIRK(0x1458, 0xa0b8, "Gigabyte AZ370-Gaming", ALC1220_FIXUP_GB_DUAL_CODECS), + SND_PCI_QUIRK(0x1458, 0xa0cd, "Gigabyte X570 Aorus Master", ALC1220_FIXUP_GB_X570), + SND_PCI_QUIRK(0x1458, 0xa0ce, "Gigabyte X570 Aorus Xtreme", ALC1220_FIXUP_GB_X570), + SND_PCI_QUIRK(0x1458, 0xa0d5, "Gigabyte X570S Aorus Master", ALC1220_FIXUP_GB_X570), + SND_PCI_QUIRK(0x1462, 0x11f7, "MSI-GE63", ALC1220_FIXUP_CLEVO_P950), + SND_PCI_QUIRK(0x1462, 0x1228, "MSI-GP63", ALC1220_FIXUP_CLEVO_P950), + SND_PCI_QUIRK(0x1462, 0x1229, "MSI-GP73", ALC1220_FIXUP_CLEVO_P950), + SND_PCI_QUIRK(0x1462, 0x1275, "MSI-GL63", ALC1220_FIXUP_CLEVO_P950), + SND_PCI_QUIRK(0x1462, 0x1276, "MSI-GL73", ALC1220_FIXUP_CLEVO_P950), + SND_PCI_QUIRK(0x1462, 0x1293, "MSI-GP65", ALC1220_FIXUP_CLEVO_P950), + SND_PCI_QUIRK(0x1462, 0x7350, "MSI-7350", ALC889_FIXUP_CD), + SND_PCI_QUIRK(0x1462, 0xcc34, "MSI Godlike X570", ALC1220_FIXUP_GB_DUAL_CODECS), + SND_PCI_QUIRK(0x1462, 0xda57, "MSI Z270-Gaming", ALC1220_FIXUP_GB_DUAL_CODECS), + SND_PCI_QUIRK_VENDOR(0x1462, "MSI", ALC882_FIXUP_GPIO3), + SND_PCI_QUIRK(0x147b, 0x107a, "Abit AW9D-MAX", ALC882_FIXUP_ABIT_AW9D_MAX), + SND_PCI_QUIRK(0x1558, 0x3702, "Clevo X370SN[VW]", ALC1220_FIXUP_CLEVO_PB51ED_PINS), + SND_PCI_QUIRK(0x1558, 0x50d3, "Clevo PC50[ER][CDF]", ALC1220_FIXUP_CLEVO_PB51ED_PINS), + SND_PCI_QUIRK(0x1558, 0x65d1, "Clevo PB51[ER][CDF]", ALC1220_FIXUP_CLEVO_PB51ED_PINS), + SND_PCI_QUIRK(0x1558, 0x65d2, "Clevo PB51R[CDF]", ALC1220_FIXUP_CLEVO_PB51ED_PINS), + SND_PCI_QUIRK(0x1558, 0x65e1, "Clevo PB51[ED][DF]", ALC1220_FIXUP_CLEVO_PB51ED_PINS), + SND_PCI_QUIRK(0x1558, 0x65e5, "Clevo PC50D[PRS](?:-D|-G)?", ALC1220_FIXUP_CLEVO_PB51ED_PINS), + SND_PCI_QUIRK(0x1558, 0x65f1, "Clevo PC50HS", ALC1220_FIXUP_CLEVO_PB51ED_PINS), + SND_PCI_QUIRK(0x1558, 0x65f5, "Clevo PD50PN[NRT]", ALC1220_FIXUP_CLEVO_PB51ED_PINS), + SND_PCI_QUIRK(0x1558, 0x66a2, "Clevo PE60RNE", ALC1220_FIXUP_CLEVO_PB51ED_PINS), + SND_PCI_QUIRK(0x1558, 0x67d1, "Clevo PB71[ER][CDF]", ALC1220_FIXUP_CLEVO_PB51ED_PINS), + SND_PCI_QUIRK(0x1558, 0x67e1, "Clevo PB71[DE][CDF]", ALC1220_FIXUP_CLEVO_PB51ED_PINS), + SND_PCI_QUIRK(0x1558, 0x67e5, "Clevo PC70D[PRS](?:-D|-G)?", ALC1220_FIXUP_CLEVO_PB51ED_PINS), + SND_PCI_QUIRK(0x1558, 0x67f1, "Clevo PC70H[PRS]", ALC1220_FIXUP_CLEVO_PB51ED_PINS), + SND_PCI_QUIRK(0x1558, 0x67f5, "Clevo PD70PN[NRT]", ALC1220_FIXUP_CLEVO_PB51ED_PINS), + SND_PCI_QUIRK(0x1558, 0x70d1, "Clevo PC70[ER][CDF]", ALC1220_FIXUP_CLEVO_PB51ED_PINS), + SND_PCI_QUIRK(0x1558, 0x7714, "Clevo X170SM", ALC1220_FIXUP_CLEVO_PB51ED_PINS), + SND_PCI_QUIRK(0x1558, 0x7715, "Clevo X170KM-G", ALC1220_FIXUP_CLEVO_PB51ED), + SND_PCI_QUIRK(0x1558, 0x9501, "Clevo P950HR", ALC1220_FIXUP_CLEVO_P950), + SND_PCI_QUIRK(0x1558, 0x9506, "Clevo P955HQ", ALC1220_FIXUP_CLEVO_P950), + SND_PCI_QUIRK(0x1558, 0x950a, "Clevo P955H[PR]", ALC1220_FIXUP_CLEVO_P950), + SND_PCI_QUIRK(0x1558, 0x95e1, "Clevo P95xER", ALC1220_FIXUP_CLEVO_P950), + SND_PCI_QUIRK(0x1558, 0x95e2, "Clevo P950ER", ALC1220_FIXUP_CLEVO_P950), + SND_PCI_QUIRK(0x1558, 0x95e3, "Clevo P955[ER]T", ALC1220_FIXUP_CLEVO_P950), + SND_PCI_QUIRK(0x1558, 0x95e4, "Clevo P955ER", ALC1220_FIXUP_CLEVO_P950), + SND_PCI_QUIRK(0x1558, 0x95e5, "Clevo P955EE6", ALC1220_FIXUP_CLEVO_P950), + SND_PCI_QUIRK(0x1558, 0x95e6, "Clevo P950R[CDF]", ALC1220_FIXUP_CLEVO_P950), + SND_PCI_QUIRK(0x1558, 0x96e1, "Clevo P960[ER][CDFN]-K", ALC1220_FIXUP_CLEVO_P950), + SND_PCI_QUIRK(0x1558, 0x97e1, "Clevo P970[ER][CDFN]", ALC1220_FIXUP_CLEVO_P950), + SND_PCI_QUIRK(0x1558, 0x97e2, "Clevo P970RC-M", ALC1220_FIXUP_CLEVO_P950), + SND_PCI_QUIRK(0x1558, 0xd502, "Clevo PD50SNE", ALC1220_FIXUP_CLEVO_PB51ED_PINS), + SND_PCI_QUIRK_VENDOR(0x1558, "Clevo laptop", ALC882_FIXUP_EAPD), + SND_PCI_QUIRK(0x161f, 0x2054, "Medion laptop", ALC883_FIXUP_EAPD), + SND_PCI_QUIRK(0x17aa, 0x3a0d, "Lenovo Y530", ALC882_FIXUP_LENOVO_Y530), + SND_PCI_QUIRK(0x8086, 0x0022, "DX58SO", ALC889_FIXUP_COEF), + {} +}; + +static const struct hda_model_fixup alc882_fixup_models[] = { + {.id = ALC882_FIXUP_ABIT_AW9D_MAX, .name = "abit-aw9d"}, + {.id = ALC882_FIXUP_LENOVO_Y530, .name = "lenovo-y530"}, + {.id = ALC882_FIXUP_ACER_ASPIRE_7736, .name = "acer-aspire-7736"}, + {.id = ALC882_FIXUP_ASUS_W90V, .name = "asus-w90v"}, + {.id = ALC889_FIXUP_CD, .name = "cd"}, + {.id = ALC889_FIXUP_FRONT_HP_NO_PRESENCE, .name = "no-front-hp"}, + {.id = ALC889_FIXUP_VAIO_TT, .name = "vaio-tt"}, + {.id = ALC888_FIXUP_EEE1601, .name = "eee1601"}, + {.id = ALC882_FIXUP_EAPD, .name = "alc882-eapd"}, + {.id = ALC883_FIXUP_EAPD, .name = "alc883-eapd"}, + {.id = ALC882_FIXUP_GPIO1, .name = "gpio1"}, + {.id = ALC882_FIXUP_GPIO2, .name = "gpio2"}, + {.id = ALC882_FIXUP_GPIO3, .name = "gpio3"}, + {.id = ALC889_FIXUP_COEF, .name = "alc889-coef"}, + {.id = ALC882_FIXUP_ASUS_W2JC, .name = "asus-w2jc"}, + {.id = ALC882_FIXUP_ACER_ASPIRE_4930G, .name = "acer-aspire-4930g"}, + {.id = ALC882_FIXUP_ACER_ASPIRE_8930G, .name = "acer-aspire-8930g"}, + {.id = ALC883_FIXUP_ACER_EAPD, .name = "acer-aspire"}, + {.id = ALC885_FIXUP_MACPRO_GPIO, .name = "macpro-gpio"}, + {.id = ALC889_FIXUP_DAC_ROUTE, .name = "dac-route"}, + {.id = ALC889_FIXUP_MBP_VREF, .name = "mbp-vref"}, + {.id = ALC889_FIXUP_IMAC91_VREF, .name = "imac91-vref"}, + {.id = ALC889_FIXUP_MBA11_VREF, .name = "mba11-vref"}, + {.id = ALC889_FIXUP_MBA21_VREF, .name = "mba21-vref"}, + {.id = ALC889_FIXUP_MP11_VREF, .name = "mp11-vref"}, + {.id = ALC889_FIXUP_MP41_VREF, .name = "mp41-vref"}, + {.id = ALC882_FIXUP_INV_DMIC, .name = "inv-dmic"}, + {.id = ALC882_FIXUP_NO_PRIMARY_HP, .name = "no-primary-hp"}, + {.id = ALC887_FIXUP_ASUS_BASS, .name = "asus-bass"}, + {.id = ALC1220_FIXUP_GB_DUAL_CODECS, .name = "dual-codecs"}, + {.id = ALC1220_FIXUP_GB_X570, .name = "gb-x570"}, + {.id = ALC1220_FIXUP_CLEVO_P950, .name = "clevo-p950"}, + {} +}; + +static const struct snd_hda_pin_quirk alc882_pin_fixup_tbl[] = { + SND_HDA_PIN_QUIRK(0x10ec1220, 0x1043, "ASUS", ALC1220_FIXUP_CLEVO_P950, + {0x14, 0x01014010}, + {0x15, 0x01011012}, + {0x16, 0x01016011}, + {0x18, 0x01a19040}, + {0x19, 0x02a19050}, + {0x1a, 0x0181304f}, + {0x1b, 0x0221401f}, + {0x1e, 0x01456130}), + SND_HDA_PIN_QUIRK(0x10ec1220, 0x1462, "MS-7C35", ALC1220_FIXUP_CLEVO_P950, + {0x14, 0x01015010}, + {0x15, 0x01011012}, + {0x16, 0x01011011}, + {0x18, 0x01a11040}, + {0x19, 0x02a19050}, + {0x1a, 0x0181104f}, + {0x1b, 0x0221401f}, + {0x1e, 0x01451130}), + {} +}; + +/* + * BIOS auto configuration + */ +/* almost identical with ALC880 parser... */ +static int alc882_parse_auto_config(struct hda_codec *codec) +{ + static const hda_nid_t alc882_ignore[] = { 0x1d, 0 }; + static const hda_nid_t alc882_ssids[] = { 0x15, 0x1b, 0x14, 0 }; + return alc_parse_auto_config(codec, alc882_ignore, alc882_ssids); +} + +/* + */ +static int patch_alc882(struct hda_codec *codec) +{ + struct alc_spec *spec; + int err; + + err = alc_alloc_spec(codec, 0x0b); + if (err < 0) + return err; + + spec = codec->spec; + + switch (codec->core.vendor_id) { + case 0x10ec0882: + case 0x10ec0885: + case 0x10ec0900: + case 0x10ec0b00: + case 0x10ec1220: + break; + default: + /* ALC883 and variants */ + alc_fix_pll_init(codec, 0x20, 0x0a, 10); + break; + } + + alc_pre_init(codec); + + snd_hda_pick_fixup(codec, alc882_fixup_models, alc882_fixup_tbl, + alc882_fixups); + snd_hda_pick_pin_fixup(codec, alc882_pin_fixup_tbl, alc882_fixups, true); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + alc_auto_parse_customize_define(codec); + + if (has_cdefine_beep(codec)) + spec->gen.beep_nid = 0x01; + + /* automatic parse from the BIOS config */ + err = alc882_parse_auto_config(codec); + if (err < 0) + goto error; + + if (!spec->gen.no_analog && spec->gen.beep_nid) { + err = set_beep_amp(spec, 0x0b, 0x05, HDA_INPUT); + if (err < 0) + goto error; + } + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; + + error: + alc_free(codec); + return err; +} + + +/* + * ALC262 support + */ +static int alc262_parse_auto_config(struct hda_codec *codec) +{ + static const hda_nid_t alc262_ignore[] = { 0x1d, 0 }; + static const hda_nid_t alc262_ssids[] = { 0x15, 0x1b, 0x14, 0 }; + return alc_parse_auto_config(codec, alc262_ignore, alc262_ssids); +} + +/* + * Pin config fixes + */ +enum { + ALC262_FIXUP_FSC_H270, + ALC262_FIXUP_FSC_S7110, + ALC262_FIXUP_HP_Z200, + ALC262_FIXUP_TYAN, + ALC262_FIXUP_LENOVO_3000, + ALC262_FIXUP_BENQ, + ALC262_FIXUP_BENQ_T31, + ALC262_FIXUP_INV_DMIC, + ALC262_FIXUP_INTEL_BAYLEYBAY, +}; + +static const struct hda_fixup alc262_fixups[] = { + [ALC262_FIXUP_FSC_H270] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x99130110 }, /* speaker */ + { 0x15, 0x0221142f }, /* front HP */ + { 0x1b, 0x0121141f }, /* rear HP */ + { } + } + }, + [ALC262_FIXUP_FSC_S7110] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x15, 0x90170110 }, /* speaker */ + { } + }, + .chained = true, + .chain_id = ALC262_FIXUP_BENQ, + }, + [ALC262_FIXUP_HP_Z200] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x16, 0x99130120 }, /* internal speaker */ + { } + } + }, + [ALC262_FIXUP_TYAN] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x1993e1f0 }, /* int AUX */ + { } + } + }, + [ALC262_FIXUP_LENOVO_3000] = { + .type = HDA_FIXUP_PINCTLS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, PIN_VREF50 }, + {} + }, + .chained = true, + .chain_id = ALC262_FIXUP_BENQ, + }, + [ALC262_FIXUP_BENQ] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x3070 }, + {} + } + }, + [ALC262_FIXUP_BENQ_T31] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x3050 }, + {} + } + }, + [ALC262_FIXUP_INV_DMIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_inv_dmic, + }, + [ALC262_FIXUP_INTEL_BAYLEYBAY] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_no_depop_delay, + }, +}; + +static const struct snd_pci_quirk alc262_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x170b, "HP Z200", ALC262_FIXUP_HP_Z200), + SND_PCI_QUIRK(0x10cf, 0x1397, "Fujitsu Lifebook S7110", ALC262_FIXUP_FSC_S7110), + SND_PCI_QUIRK(0x10cf, 0x142d, "Fujitsu Lifebook E8410", ALC262_FIXUP_BENQ), + SND_PCI_QUIRK(0x10f1, 0x2915, "Tyan Thunder n6650W", ALC262_FIXUP_TYAN), + SND_PCI_QUIRK(0x1734, 0x1141, "FSC ESPRIMO U9210", ALC262_FIXUP_FSC_H270), + SND_PCI_QUIRK(0x1734, 0x1147, "FSC Celsius H270", ALC262_FIXUP_FSC_H270), + SND_PCI_QUIRK(0x17aa, 0x384e, "Lenovo 3000", ALC262_FIXUP_LENOVO_3000), + SND_PCI_QUIRK(0x17ff, 0x0560, "Benq ED8", ALC262_FIXUP_BENQ), + SND_PCI_QUIRK(0x17ff, 0x058d, "Benq T31-16", ALC262_FIXUP_BENQ_T31), + SND_PCI_QUIRK(0x8086, 0x7270, "BayleyBay", ALC262_FIXUP_INTEL_BAYLEYBAY), + {} +}; + +static const struct hda_model_fixup alc262_fixup_models[] = { + {.id = ALC262_FIXUP_INV_DMIC, .name = "inv-dmic"}, + {.id = ALC262_FIXUP_FSC_H270, .name = "fsc-h270"}, + {.id = ALC262_FIXUP_FSC_S7110, .name = "fsc-s7110"}, + {.id = ALC262_FIXUP_HP_Z200, .name = "hp-z200"}, + {.id = ALC262_FIXUP_TYAN, .name = "tyan"}, + {.id = ALC262_FIXUP_LENOVO_3000, .name = "lenovo-3000"}, + {.id = ALC262_FIXUP_BENQ, .name = "benq"}, + {.id = ALC262_FIXUP_BENQ_T31, .name = "benq-t31"}, + {.id = ALC262_FIXUP_INTEL_BAYLEYBAY, .name = "bayleybay"}, + {} +}; + +/* + */ +static int patch_alc262(struct hda_codec *codec) +{ + struct alc_spec *spec; + int err; + + err = alc_alloc_spec(codec, 0x0b); + if (err < 0) + return err; + + spec = codec->spec; + spec->gen.shared_mic_vref_pin = 0x18; + + spec->shutup = alc_eapd_shutup; + +#if 0 + /* pshou 07/11/05 set a zero PCM sample to DAC when FIFO is + * under-run + */ + alc_update_coefex_idx(codec, 0x1a, 7, 0, 0x80); +#endif + alc_fix_pll_init(codec, 0x20, 0x0a, 10); + + alc_pre_init(codec); + + snd_hda_pick_fixup(codec, alc262_fixup_models, alc262_fixup_tbl, + alc262_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + alc_auto_parse_customize_define(codec); + + if (has_cdefine_beep(codec)) + spec->gen.beep_nid = 0x01; + + /* automatic parse from the BIOS config */ + err = alc262_parse_auto_config(codec); + if (err < 0) + goto error; + + if (!spec->gen.no_analog && spec->gen.beep_nid) { + err = set_beep_amp(spec, 0x0b, 0x05, HDA_INPUT); + if (err < 0) + goto error; + } + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; + + error: + alc_free(codec); + return err; +} + +/* + * ALC268 + */ +/* bind Beep switches of both NID 0x0f and 0x10 */ +static int alc268_beep_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned long pval; + int err; + + mutex_lock(&codec->control_mutex); + pval = kcontrol->private_value; + kcontrol->private_value = (pval & ~0xff) | 0x0f; + err = snd_hda_mixer_amp_switch_put(kcontrol, ucontrol); + if (err >= 0) { + kcontrol->private_value = (pval & ~0xff) | 0x10; + err = snd_hda_mixer_amp_switch_put(kcontrol, ucontrol); + } + kcontrol->private_value = pval; + mutex_unlock(&codec->control_mutex); + return err; +} + +static const struct snd_kcontrol_new alc268_beep_mixer[] = { + HDA_CODEC_VOLUME("Beep Playback Volume", 0x1d, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Beep Playback Switch", + .subdevice = HDA_SUBDEV_AMP_FLAG, + .info = snd_hda_mixer_amp_switch_info, + .get = snd_hda_mixer_amp_switch_get, + .put = alc268_beep_switch_put, + .private_value = HDA_COMPOSE_AMP_VAL(0x0f, 3, 1, HDA_INPUT) + }, +}; + +/* set PCBEEP vol = 0, mute connections */ +static const struct hda_verb alc268_beep_init_verbs[] = { + {0x1d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + { } +}; + +enum { + ALC268_FIXUP_INV_DMIC, + ALC268_FIXUP_HP_EAPD, + ALC268_FIXUP_SPDIF, +}; + +static const struct hda_fixup alc268_fixups[] = { + [ALC268_FIXUP_INV_DMIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_inv_dmic, + }, + [ALC268_FIXUP_HP_EAPD] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + {0x15, AC_VERB_SET_EAPD_BTLENABLE, 0}, + {} + } + }, + [ALC268_FIXUP_SPDIF] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1e, 0x014b1180 }, /* enable SPDIF out */ + {} + } + }, +}; + +static const struct hda_model_fixup alc268_fixup_models[] = { + {.id = ALC268_FIXUP_INV_DMIC, .name = "inv-dmic"}, + {.id = ALC268_FIXUP_HP_EAPD, .name = "hp-eapd"}, + {.id = ALC268_FIXUP_SPDIF, .name = "spdif"}, + {} +}; + +static const struct snd_pci_quirk alc268_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x0139, "Acer TravelMate 6293", ALC268_FIXUP_SPDIF), + SND_PCI_QUIRK(0x1025, 0x015b, "Acer AOA 150 (ZG5)", ALC268_FIXUP_INV_DMIC), + /* below is codec SSID since multiple Toshiba laptops have the + * same PCI SSID 1179:ff00 + */ + SND_PCI_QUIRK(0x1179, 0xff06, "Toshiba P200", ALC268_FIXUP_HP_EAPD), + {} +}; + +/* + * BIOS auto configuration + */ +static int alc268_parse_auto_config(struct hda_codec *codec) +{ + static const hda_nid_t alc268_ssids[] = { 0x15, 0x1b, 0x14, 0 }; + return alc_parse_auto_config(codec, NULL, alc268_ssids); +} + +/* + */ +static int patch_alc268(struct hda_codec *codec) +{ + struct alc_spec *spec; + int i, err; + + /* ALC268 has no aa-loopback mixer */ + err = alc_alloc_spec(codec, 0); + if (err < 0) + return err; + + spec = codec->spec; + if (has_cdefine_beep(codec)) + spec->gen.beep_nid = 0x01; + + spec->shutup = alc_eapd_shutup; + + alc_pre_init(codec); + + snd_hda_pick_fixup(codec, alc268_fixup_models, alc268_fixup_tbl, alc268_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + /* automatic parse from the BIOS config */ + err = alc268_parse_auto_config(codec); + if (err < 0) + goto error; + + if (err > 0 && !spec->gen.no_analog && + spec->gen.autocfg.speaker_pins[0] != 0x1d) { + for (i = 0; i < ARRAY_SIZE(alc268_beep_mixer); i++) { + if (!snd_hda_gen_add_kctl(&spec->gen, NULL, + &alc268_beep_mixer[i])) { + err = -ENOMEM; + goto error; + } + } + snd_hda_add_verbs(codec, alc268_beep_init_verbs); + if (!query_amp_caps(codec, 0x1d, HDA_INPUT)) + /* override the amp caps for beep generator */ + snd_hda_override_amp_caps(codec, 0x1d, HDA_INPUT, + (0x0c << AC_AMPCAP_OFFSET_SHIFT) | + (0x0c << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x07 << AC_AMPCAP_STEP_SIZE_SHIFT) | + (0 << AC_AMPCAP_MUTE_SHIFT)); + } + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; + + error: + alc_free(codec); + return err; +} + +/* + * ALC269 + */ + +static const struct hda_pcm_stream alc269_44k_pcm_analog_playback = { + .rates = SNDRV_PCM_RATE_44100, /* fixed rate */ +}; + +static const struct hda_pcm_stream alc269_44k_pcm_analog_capture = { + .rates = SNDRV_PCM_RATE_44100, /* fixed rate */ +}; + +/* different alc269-variants */ +enum { + ALC269_TYPE_ALC269VA, + ALC269_TYPE_ALC269VB, + ALC269_TYPE_ALC269VC, + ALC269_TYPE_ALC269VD, + ALC269_TYPE_ALC280, + ALC269_TYPE_ALC282, + ALC269_TYPE_ALC283, + ALC269_TYPE_ALC284, + ALC269_TYPE_ALC293, + ALC269_TYPE_ALC286, + ALC269_TYPE_ALC298, + ALC269_TYPE_ALC255, + ALC269_TYPE_ALC256, + ALC269_TYPE_ALC257, + ALC269_TYPE_ALC215, + ALC269_TYPE_ALC225, + ALC269_TYPE_ALC245, + ALC269_TYPE_ALC287, + ALC269_TYPE_ALC294, + ALC269_TYPE_ALC300, + ALC269_TYPE_ALC623, + ALC269_TYPE_ALC700, +}; + +/* + * BIOS auto configuration + */ +static int alc269_parse_auto_config(struct hda_codec *codec) +{ + static const hda_nid_t alc269_ignore[] = { 0x1d, 0 }; + static const hda_nid_t alc269_ssids[] = { 0, 0x1b, 0x14, 0x21 }; + static const hda_nid_t alc269va_ssids[] = { 0x15, 0x1b, 0x14, 0 }; + struct alc_spec *spec = codec->spec; + const hda_nid_t *ssids; + + switch (spec->codec_variant) { + case ALC269_TYPE_ALC269VA: + case ALC269_TYPE_ALC269VC: + case ALC269_TYPE_ALC280: + case ALC269_TYPE_ALC284: + case ALC269_TYPE_ALC293: + ssids = alc269va_ssids; + break; + case ALC269_TYPE_ALC269VB: + case ALC269_TYPE_ALC269VD: + case ALC269_TYPE_ALC282: + case ALC269_TYPE_ALC283: + case ALC269_TYPE_ALC286: + case ALC269_TYPE_ALC298: + case ALC269_TYPE_ALC255: + case ALC269_TYPE_ALC256: + case ALC269_TYPE_ALC257: + case ALC269_TYPE_ALC215: + case ALC269_TYPE_ALC225: + case ALC269_TYPE_ALC245: + case ALC269_TYPE_ALC287: + case ALC269_TYPE_ALC294: + case ALC269_TYPE_ALC300: + case ALC269_TYPE_ALC623: + case ALC269_TYPE_ALC700: + ssids = alc269_ssids; + break; + default: + ssids = alc269_ssids; + break; + } + + return alc_parse_auto_config(codec, alc269_ignore, ssids); +} + +static const struct hda_jack_keymap alc_headset_btn_keymap[] = { + { SND_JACK_BTN_0, KEY_PLAYPAUSE }, + { SND_JACK_BTN_1, KEY_VOICECOMMAND }, + { SND_JACK_BTN_2, KEY_VOLUMEUP }, + { SND_JACK_BTN_3, KEY_VOLUMEDOWN }, + {} +}; + +static void alc_headset_btn_callback(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + int report = 0; + + if (jack->unsol_res & (7 << 13)) + report |= SND_JACK_BTN_0; + + if (jack->unsol_res & (1 << 16 | 3 << 8)) + report |= SND_JACK_BTN_1; + + /* Volume up key */ + if (jack->unsol_res & (7 << 23)) + report |= SND_JACK_BTN_2; + + /* Volume down key */ + if (jack->unsol_res & (7 << 10)) + report |= SND_JACK_BTN_3; + + snd_hda_jack_set_button_state(codec, jack->nid, report); +} + +static void alc_disable_headset_jack_key(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + if (!spec->has_hs_key) + return; + + switch (codec->core.vendor_id) { + case 0x10ec0215: + case 0x10ec0225: + case 0x10ec0285: + case 0x10ec0287: + case 0x10ec0295: + case 0x10ec0289: + case 0x10ec0299: + alc_write_coef_idx(codec, 0x48, 0x0); + alc_update_coef_idx(codec, 0x49, 0x0045, 0x0); + alc_update_coef_idx(codec, 0x44, 0x0045 << 8, 0x0); + break; + case 0x10ec0230: + case 0x10ec0236: + case 0x10ec0256: + case 0x10ec0257: + case 0x19e58326: + alc_write_coef_idx(codec, 0x48, 0x0); + alc_update_coef_idx(codec, 0x49, 0x0045, 0x0); + break; + } +} + +static void alc_enable_headset_jack_key(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + if (!spec->has_hs_key) + return; + + switch (codec->core.vendor_id) { + case 0x10ec0215: + case 0x10ec0225: + case 0x10ec0285: + case 0x10ec0287: + case 0x10ec0295: + case 0x10ec0289: + case 0x10ec0299: + alc_write_coef_idx(codec, 0x48, 0xd011); + alc_update_coef_idx(codec, 0x49, 0x007f, 0x0045); + alc_update_coef_idx(codec, 0x44, 0x007f << 8, 0x0045 << 8); + break; + case 0x10ec0230: + case 0x10ec0236: + case 0x10ec0256: + case 0x10ec0257: + case 0x19e58326: + alc_write_coef_idx(codec, 0x48, 0xd011); + alc_update_coef_idx(codec, 0x49, 0x007f, 0x0045); + break; + } +} + +static void alc_fixup_headset_jack(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t hp_pin; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + spec->has_hs_key = 1; + snd_hda_jack_detect_enable_callback(codec, 0x55, + alc_headset_btn_callback); + break; + case HDA_FIXUP_ACT_BUILD: + hp_pin = alc_get_hp_pin(spec); + if (!hp_pin || snd_hda_jack_bind_keymap(codec, 0x55, + alc_headset_btn_keymap, + hp_pin)) + snd_hda_jack_add_kctl(codec, 0x55, "Headset Jack", + false, SND_JACK_HEADSET, + alc_headset_btn_keymap); + + alc_enable_headset_jack_key(codec); + break; + } +} + +static void alc269vb_toggle_power_output(struct hda_codec *codec, int power_up) +{ + alc_update_coef_idx(codec, 0x04, 1 << 11, power_up ? (1 << 11) : 0); +} + +static void alc269_shutup(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + if (spec->codec_variant == ALC269_TYPE_ALC269VB) + alc269vb_toggle_power_output(codec, 0); + if (spec->codec_variant == ALC269_TYPE_ALC269VB && + (alc_get_coef0(codec) & 0x00ff) == 0x018) { + msleep(150); + } + alc_shutup_pins(codec); +} + +static const struct coef_fw alc282_coefs[] = { + WRITE_COEF(0x03, 0x0002), /* Power Down Control */ + UPDATE_COEF(0x05, 0xff3f, 0x0700), /* FIFO and filter clock */ + WRITE_COEF(0x07, 0x0200), /* DMIC control */ + UPDATE_COEF(0x06, 0x00f0, 0), /* Analog clock */ + UPDATE_COEF(0x08, 0xfffc, 0x0c2c), /* JD */ + WRITE_COEF(0x0a, 0xcccc), /* JD offset1 */ + WRITE_COEF(0x0b, 0xcccc), /* JD offset2 */ + WRITE_COEF(0x0e, 0x6e00), /* LDO1/2/3, DAC/ADC */ + UPDATE_COEF(0x0f, 0xf800, 0x1000), /* JD */ + UPDATE_COEF(0x10, 0xfc00, 0x0c00), /* Capless */ + WRITE_COEF(0x6f, 0x0), /* Class D test 4 */ + UPDATE_COEF(0x0c, 0xfe00, 0), /* IO power down directly */ + WRITE_COEF(0x34, 0xa0c0), /* ANC */ + UPDATE_COEF(0x16, 0x0008, 0), /* AGC MUX */ + UPDATE_COEF(0x1d, 0x00e0, 0), /* DAC simple content protection */ + UPDATE_COEF(0x1f, 0x00e0, 0), /* ADC simple content protection */ + WRITE_COEF(0x21, 0x8804), /* DAC ADC Zero Detection */ + WRITE_COEF(0x63, 0x2902), /* PLL */ + WRITE_COEF(0x68, 0xa080), /* capless control 2 */ + WRITE_COEF(0x69, 0x3400), /* capless control 3 */ + WRITE_COEF(0x6a, 0x2f3e), /* capless control 4 */ + WRITE_COEF(0x6b, 0x0), /* capless control 5 */ + UPDATE_COEF(0x6d, 0x0fff, 0x0900), /* class D test 2 */ + WRITE_COEF(0x6e, 0x110a), /* class D test 3 */ + UPDATE_COEF(0x70, 0x00f8, 0x00d8), /* class D test 5 */ + WRITE_COEF(0x71, 0x0014), /* class D test 6 */ + WRITE_COEF(0x72, 0xc2ba), /* classD OCP */ + UPDATE_COEF(0x77, 0x0f80, 0), /* classD pure DC test */ + WRITE_COEF(0x6c, 0xfc06), /* Class D amp control */ + {} +}; + +static void alc282_restore_default_value(struct hda_codec *codec) +{ + alc_process_coef_fw(codec, alc282_coefs); +} + +static void alc282_init(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t hp_pin = alc_get_hp_pin(spec); + bool hp_pin_sense; + int coef78; + + alc282_restore_default_value(codec); + + if (!hp_pin) + return; + hp_pin_sense = snd_hda_jack_detect(codec, hp_pin); + coef78 = alc_read_coef_idx(codec, 0x78); + + /* Index 0x78 Direct Drive HP AMP LPM Control 1 */ + /* Headphone capless set to high power mode */ + alc_write_coef_idx(codec, 0x78, 0x9004); + + if (hp_pin_sense) + msleep(2); + + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + + if (hp_pin_sense) + msleep(85); + + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT); + + if (hp_pin_sense) + msleep(100); + + /* Headphone capless set to normal mode */ + alc_write_coef_idx(codec, 0x78, coef78); +} + +static void alc282_shutup(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t hp_pin = alc_get_hp_pin(spec); + bool hp_pin_sense; + int coef78; + + if (!hp_pin) { + alc269_shutup(codec); + return; + } + + hp_pin_sense = snd_hda_jack_detect(codec, hp_pin); + coef78 = alc_read_coef_idx(codec, 0x78); + alc_write_coef_idx(codec, 0x78, 0x9004); + + if (hp_pin_sense) + msleep(2); + + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + + if (hp_pin_sense) + msleep(85); + + if (!spec->no_shutup_pins) + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0); + + if (hp_pin_sense) + msleep(100); + + alc_auto_setup_eapd(codec, false); + alc_shutup_pins(codec); + alc_write_coef_idx(codec, 0x78, coef78); +} + +static const struct coef_fw alc283_coefs[] = { + WRITE_COEF(0x03, 0x0002), /* Power Down Control */ + UPDATE_COEF(0x05, 0xff3f, 0x0700), /* FIFO and filter clock */ + WRITE_COEF(0x07, 0x0200), /* DMIC control */ + UPDATE_COEF(0x06, 0x00f0, 0), /* Analog clock */ + UPDATE_COEF(0x08, 0xfffc, 0x0c2c), /* JD */ + WRITE_COEF(0x0a, 0xcccc), /* JD offset1 */ + WRITE_COEF(0x0b, 0xcccc), /* JD offset2 */ + WRITE_COEF(0x0e, 0x6fc0), /* LDO1/2/3, DAC/ADC */ + UPDATE_COEF(0x0f, 0xf800, 0x1000), /* JD */ + UPDATE_COEF(0x10, 0xfc00, 0x0c00), /* Capless */ + WRITE_COEF(0x3a, 0x0), /* Class D test 4 */ + UPDATE_COEF(0x0c, 0xfe00, 0x0), /* IO power down directly */ + WRITE_COEF(0x22, 0xa0c0), /* ANC */ + UPDATE_COEFEX(0x53, 0x01, 0x000f, 0x0008), /* AGC MUX */ + UPDATE_COEF(0x1d, 0x00e0, 0), /* DAC simple content protection */ + UPDATE_COEF(0x1f, 0x00e0, 0), /* ADC simple content protection */ + WRITE_COEF(0x21, 0x8804), /* DAC ADC Zero Detection */ + WRITE_COEF(0x2e, 0x2902), /* PLL */ + WRITE_COEF(0x33, 0xa080), /* capless control 2 */ + WRITE_COEF(0x34, 0x3400), /* capless control 3 */ + WRITE_COEF(0x35, 0x2f3e), /* capless control 4 */ + WRITE_COEF(0x36, 0x0), /* capless control 5 */ + UPDATE_COEF(0x38, 0x0fff, 0x0900), /* class D test 2 */ + WRITE_COEF(0x39, 0x110a), /* class D test 3 */ + UPDATE_COEF(0x3b, 0x00f8, 0x00d8), /* class D test 5 */ + WRITE_COEF(0x3c, 0x0014), /* class D test 6 */ + WRITE_COEF(0x3d, 0xc2ba), /* classD OCP */ + UPDATE_COEF(0x42, 0x0f80, 0x0), /* classD pure DC test */ + WRITE_COEF(0x49, 0x0), /* test mode */ + UPDATE_COEF(0x40, 0xf800, 0x9800), /* Class D DC enable */ + UPDATE_COEF(0x42, 0xf000, 0x2000), /* DC offset */ + WRITE_COEF(0x37, 0xfc06), /* Class D amp control */ + UPDATE_COEF(0x1b, 0x8000, 0), /* HP JD control */ + {} +}; + +static void alc283_restore_default_value(struct hda_codec *codec) +{ + alc_process_coef_fw(codec, alc283_coefs); +} + +static void alc283_init(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t hp_pin = alc_get_hp_pin(spec); + bool hp_pin_sense; + + alc283_restore_default_value(codec); + + if (!hp_pin) + return; + + msleep(30); + hp_pin_sense = snd_hda_jack_detect(codec, hp_pin); + + /* Index 0x43 Direct Drive HP AMP LPM Control 1 */ + /* Headphone capless set to high power mode */ + alc_write_coef_idx(codec, 0x43, 0x9004); + + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + + if (hp_pin_sense) + msleep(85); + + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT); + + if (hp_pin_sense) + msleep(85); + /* Index 0x46 Combo jack auto switch control 2 */ + /* 3k pull low control for Headset jack. */ + alc_update_coef_idx(codec, 0x46, 3 << 12, 0); + /* Headphone capless set to normal mode */ + alc_write_coef_idx(codec, 0x43, 0x9614); +} + +static void alc283_shutup(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t hp_pin = alc_get_hp_pin(spec); + bool hp_pin_sense; + + if (!hp_pin) { + alc269_shutup(codec); + return; + } + + hp_pin_sense = snd_hda_jack_detect(codec, hp_pin); + + alc_write_coef_idx(codec, 0x43, 0x9004); + + /*depop hp during suspend*/ + alc_write_coef_idx(codec, 0x06, 0x2100); + + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + + if (hp_pin_sense) + msleep(100); + + if (!spec->no_shutup_pins) + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0); + + alc_update_coef_idx(codec, 0x46, 0, 3 << 12); + + if (hp_pin_sense) + msleep(100); + alc_auto_setup_eapd(codec, false); + alc_shutup_pins(codec); + alc_write_coef_idx(codec, 0x43, 0x9614); +} + +static void alc256_init(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t hp_pin = alc_get_hp_pin(spec); + bool hp_pin_sense; + + if (spec->ultra_low_power) { + alc_update_coef_idx(codec, 0x03, 1<<1, 1<<1); + alc_update_coef_idx(codec, 0x08, 3<<2, 3<<2); + alc_update_coef_idx(codec, 0x08, 7<<4, 0); + alc_update_coef_idx(codec, 0x3b, 1<<15, 0); + alc_update_coef_idx(codec, 0x0e, 7<<6, 7<<6); + msleep(30); + } + + if (!hp_pin) + hp_pin = 0x21; + + msleep(30); + + hp_pin_sense = snd_hda_jack_detect(codec, hp_pin); + + if (hp_pin_sense) + msleep(2); + + alc_update_coefex_idx(codec, 0x57, 0x04, 0x0007, 0x1); /* Low power */ + + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + + if (hp_pin_sense || spec->ultra_low_power) + msleep(85); + + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT); + + if (hp_pin_sense || spec->ultra_low_power) + msleep(100); + + alc_update_coef_idx(codec, 0x46, 3 << 12, 0); + alc_update_coefex_idx(codec, 0x57, 0x04, 0x0007, 0x4); /* Hight power */ + alc_update_coefex_idx(codec, 0x53, 0x02, 0x8000, 1 << 15); /* Clear bit */ + alc_update_coefex_idx(codec, 0x53, 0x02, 0x8000, 0 << 15); + /* + * Expose headphone mic (or possibly Line In on some machines) instead + * of PC Beep on 1Ah, and disable 1Ah loopback for all outputs. See + * Documentation/sound/hd-audio/realtek-pc-beep.rst for details of + * this register. + */ + alc_write_coef_idx(codec, 0x36, 0x5757); +} + +static void alc256_shutup(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t hp_pin = alc_get_hp_pin(spec); + bool hp_pin_sense; + + if (!hp_pin) + hp_pin = 0x21; + + alc_update_coefex_idx(codec, 0x57, 0x04, 0x0007, 0x1); /* Low power */ + hp_pin_sense = snd_hda_jack_detect(codec, hp_pin); + + if (hp_pin_sense) + msleep(2); + + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + + if (hp_pin_sense || spec->ultra_low_power) + msleep(85); + + /* 3k pull low control for Headset jack. */ + /* NOTE: call this before clearing the pin, otherwise codec stalls */ + /* If disable 3k pulldown control for alc257, the Mic detection will not work correctly + * when booting with headset plugged. So skip setting it for the codec alc257 + */ + if (spec->en_3kpull_low) + alc_update_coef_idx(codec, 0x46, 0, 3 << 12); + + if (!spec->no_shutup_pins) + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0); + + if (hp_pin_sense || spec->ultra_low_power) + msleep(100); + + alc_auto_setup_eapd(codec, false); + alc_shutup_pins(codec); + if (spec->ultra_low_power) { + msleep(50); + alc_update_coef_idx(codec, 0x03, 1<<1, 0); + alc_update_coef_idx(codec, 0x08, 7<<4, 7<<4); + alc_update_coef_idx(codec, 0x08, 3<<2, 0); + alc_update_coef_idx(codec, 0x3b, 1<<15, 1<<15); + alc_update_coef_idx(codec, 0x0e, 7<<6, 0); + msleep(30); + } +} + +static void alc285_hp_init(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t hp_pin = alc_get_hp_pin(spec); + int i, val; + int coef38, coef0d, coef36; + + alc_update_coef_idx(codec, 0x4a, 1<<15, 1<<15); /* Reset HP JD */ + coef38 = alc_read_coef_idx(codec, 0x38); /* Amp control */ + coef0d = alc_read_coef_idx(codec, 0x0d); /* Digital Misc control */ + coef36 = alc_read_coef_idx(codec, 0x36); /* Passthrough Control */ + alc_update_coef_idx(codec, 0x38, 1<<4, 0x0); + alc_update_coef_idx(codec, 0x0d, 0x110, 0x0); + + alc_update_coef_idx(codec, 0x67, 0xf000, 0x3000); + + if (hp_pin) + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + + msleep(130); + alc_update_coef_idx(codec, 0x36, 1<<14, 1<<14); + alc_update_coef_idx(codec, 0x36, 1<<13, 0x0); + + if (hp_pin) + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0); + msleep(10); + alc_write_coef_idx(codec, 0x67, 0x0); /* Set HP depop to manual mode */ + alc_write_coefex_idx(codec, 0x58, 0x00, 0x7880); + alc_write_coefex_idx(codec, 0x58, 0x0f, 0xf049); + alc_update_coefex_idx(codec, 0x58, 0x03, 0x00f0, 0x00c0); + + alc_write_coefex_idx(codec, 0x58, 0x00, 0xf888); /* HP depop procedure start */ + val = alc_read_coefex_idx(codec, 0x58, 0x00); + for (i = 0; i < 20 && val & 0x8000; i++) { + msleep(50); + val = alc_read_coefex_idx(codec, 0x58, 0x00); + } /* Wait for depop procedure finish */ + + alc_write_coefex_idx(codec, 0x58, 0x00, val); /* write back the result */ + alc_update_coef_idx(codec, 0x38, 1<<4, coef38); + alc_update_coef_idx(codec, 0x0d, 0x110, coef0d); + alc_update_coef_idx(codec, 0x36, 3<<13, coef36); + + msleep(50); + alc_update_coef_idx(codec, 0x4a, 1<<15, 0); +} + +static void alc225_init(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t hp_pin = alc_get_hp_pin(spec); + bool hp1_pin_sense, hp2_pin_sense; + + if (spec->ultra_low_power) { + alc_update_coef_idx(codec, 0x08, 0x0f << 2, 3<<2); + alc_update_coef_idx(codec, 0x0e, 7<<6, 7<<6); + alc_update_coef_idx(codec, 0x33, 1<<11, 0); + msleep(30); + } + + if (spec->codec_variant != ALC269_TYPE_ALC287 && + spec->codec_variant != ALC269_TYPE_ALC245) + /* required only at boot or S3 and S4 resume time */ + if (!spec->done_hp_init || + is_s3_resume(codec) || + is_s4_resume(codec)) { + alc285_hp_init(codec); + spec->done_hp_init = true; + } + + if (!hp_pin) + hp_pin = 0x21; + msleep(30); + + hp1_pin_sense = snd_hda_jack_detect(codec, hp_pin); + hp2_pin_sense = snd_hda_jack_detect(codec, 0x16); + + if (hp1_pin_sense || hp2_pin_sense) + msleep(2); + + alc_update_coefex_idx(codec, 0x57, 0x04, 0x0007, 0x1); /* Low power */ + + if (hp1_pin_sense || spec->ultra_low_power) + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + if (hp2_pin_sense) + snd_hda_codec_write(codec, 0x16, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + + if (hp1_pin_sense || hp2_pin_sense || spec->ultra_low_power) + msleep(85); + + if (hp1_pin_sense || spec->ultra_low_power) + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT); + if (hp2_pin_sense) + snd_hda_codec_write(codec, 0x16, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT); + + if (hp1_pin_sense || hp2_pin_sense || spec->ultra_low_power) + msleep(100); + + alc_update_coef_idx(codec, 0x4a, 3 << 10, 0); + alc_update_coefex_idx(codec, 0x57, 0x04, 0x0007, 0x4); /* Hight power */ +} + +static void alc225_shutup(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t hp_pin = alc_get_hp_pin(spec); + bool hp1_pin_sense, hp2_pin_sense; + + if (!hp_pin) + hp_pin = 0x21; + + alc_disable_headset_jack_key(codec); + /* 3k pull low control for Headset jack. */ + alc_update_coef_idx(codec, 0x4a, 0, 3 << 10); + + hp1_pin_sense = snd_hda_jack_detect(codec, hp_pin); + hp2_pin_sense = snd_hda_jack_detect(codec, 0x16); + + if (hp1_pin_sense || hp2_pin_sense) + msleep(2); + + if (hp1_pin_sense || spec->ultra_low_power) + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + if (hp2_pin_sense) + snd_hda_codec_write(codec, 0x16, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + + if (hp1_pin_sense || hp2_pin_sense || spec->ultra_low_power) + msleep(85); + + if (hp1_pin_sense || spec->ultra_low_power) + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0); + if (hp2_pin_sense) + snd_hda_codec_write(codec, 0x16, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0); + + if (hp1_pin_sense || hp2_pin_sense || spec->ultra_low_power) + msleep(100); + + alc_auto_setup_eapd(codec, false); + alc_shutup_pins(codec); + if (spec->ultra_low_power) { + msleep(50); + alc_update_coef_idx(codec, 0x08, 0x0f << 2, 0x0c << 2); + alc_update_coef_idx(codec, 0x0e, 7<<6, 0); + alc_update_coef_idx(codec, 0x33, 1<<11, 1<<11); + alc_update_coef_idx(codec, 0x4a, 3<<4, 2<<4); + msleep(30); + } + + alc_update_coef_idx(codec, 0x4a, 3 << 10, 0); + alc_enable_headset_jack_key(codec); +} + +static void alc_default_init(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t hp_pin = alc_get_hp_pin(spec); + bool hp_pin_sense; + + if (!hp_pin) + return; + + msleep(30); + + hp_pin_sense = snd_hda_jack_detect(codec, hp_pin); + + if (hp_pin_sense) + msleep(2); + + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + + if (hp_pin_sense) + msleep(85); + + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT); + + if (hp_pin_sense) + msleep(100); +} + +static void alc_default_shutup(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t hp_pin = alc_get_hp_pin(spec); + bool hp_pin_sense; + + if (!hp_pin) { + alc269_shutup(codec); + return; + } + + hp_pin_sense = snd_hda_jack_detect(codec, hp_pin); + + if (hp_pin_sense) + msleep(2); + + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + + if (hp_pin_sense) + msleep(85); + + if (!spec->no_shutup_pins) + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0); + + if (hp_pin_sense) + msleep(100); + + alc_auto_setup_eapd(codec, false); + alc_shutup_pins(codec); +} + +static void alc294_hp_init(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t hp_pin = alc_get_hp_pin(spec); + int i, val; + + if (!hp_pin) + return; + + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + + msleep(100); + + if (!spec->no_shutup_pins) + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0); + + alc_update_coef_idx(codec, 0x6f, 0x000f, 0);/* Set HP depop to manual mode */ + alc_update_coefex_idx(codec, 0x58, 0x00, 0x8000, 0x8000); /* HP depop procedure start */ + + /* Wait for depop procedure finish */ + val = alc_read_coefex_idx(codec, 0x58, 0x01); + for (i = 0; i < 20 && val & 0x0080; i++) { + msleep(50); + val = alc_read_coefex_idx(codec, 0x58, 0x01); + } + /* Set HP depop to auto mode */ + alc_update_coef_idx(codec, 0x6f, 0x000f, 0x000b); + msleep(50); +} + +static void alc294_init(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + /* required only at boot or S4 resume time */ + if (!spec->done_hp_init || + codec->core.dev.power.power_state.event == PM_EVENT_RESTORE) { + alc294_hp_init(codec); + spec->done_hp_init = true; + } + alc_default_init(codec); +} + +static void alc5505_coef_set(struct hda_codec *codec, unsigned int index_reg, + unsigned int val) +{ + snd_hda_codec_write(codec, 0x51, 0, AC_VERB_SET_COEF_INDEX, index_reg >> 1); + snd_hda_codec_write(codec, 0x51, 0, AC_VERB_SET_PROC_COEF, val & 0xffff); /* LSB */ + snd_hda_codec_write(codec, 0x51, 0, AC_VERB_SET_PROC_COEF, val >> 16); /* MSB */ +} + +static int alc5505_coef_get(struct hda_codec *codec, unsigned int index_reg) +{ + unsigned int val; + + snd_hda_codec_write(codec, 0x51, 0, AC_VERB_SET_COEF_INDEX, index_reg >> 1); + val = snd_hda_codec_read(codec, 0x51, 0, AC_VERB_GET_PROC_COEF, 0) + & 0xffff; + val |= snd_hda_codec_read(codec, 0x51, 0, AC_VERB_GET_PROC_COEF, 0) + << 16; + return val; +} + +static void alc5505_dsp_halt(struct hda_codec *codec) +{ + unsigned int val; + + alc5505_coef_set(codec, 0x3000, 0x000c); /* DSP CPU stop */ + alc5505_coef_set(codec, 0x880c, 0x0008); /* DDR enter self refresh */ + alc5505_coef_set(codec, 0x61c0, 0x11110080); /* Clock control for PLL and CPU */ + alc5505_coef_set(codec, 0x6230, 0xfc0d4011); /* Disable Input OP */ + alc5505_coef_set(codec, 0x61b4, 0x040a2b03); /* Stop PLL2 */ + alc5505_coef_set(codec, 0x61b0, 0x00005b17); /* Stop PLL1 */ + alc5505_coef_set(codec, 0x61b8, 0x04133303); /* Stop PLL3 */ + val = alc5505_coef_get(codec, 0x6220); + alc5505_coef_set(codec, 0x6220, (val | 0x3000)); /* switch Ringbuffer clock to DBUS clock */ +} + +static void alc5505_dsp_back_from_halt(struct hda_codec *codec) +{ + alc5505_coef_set(codec, 0x61b8, 0x04133302); + alc5505_coef_set(codec, 0x61b0, 0x00005b16); + alc5505_coef_set(codec, 0x61b4, 0x040a2b02); + alc5505_coef_set(codec, 0x6230, 0xf80d4011); + alc5505_coef_set(codec, 0x6220, 0x2002010f); + alc5505_coef_set(codec, 0x880c, 0x00000004); +} + +static void alc5505_dsp_init(struct hda_codec *codec) +{ + unsigned int val; + + alc5505_dsp_halt(codec); + alc5505_dsp_back_from_halt(codec); + alc5505_coef_set(codec, 0x61b0, 0x5b14); /* PLL1 control */ + alc5505_coef_set(codec, 0x61b0, 0x5b16); + alc5505_coef_set(codec, 0x61b4, 0x04132b00); /* PLL2 control */ + alc5505_coef_set(codec, 0x61b4, 0x04132b02); + alc5505_coef_set(codec, 0x61b8, 0x041f3300); /* PLL3 control*/ + alc5505_coef_set(codec, 0x61b8, 0x041f3302); + snd_hda_codec_write(codec, 0x51, 0, AC_VERB_SET_CODEC_RESET, 0); /* Function reset */ + alc5505_coef_set(codec, 0x61b8, 0x041b3302); + alc5505_coef_set(codec, 0x61b8, 0x04173302); + alc5505_coef_set(codec, 0x61b8, 0x04163302); + alc5505_coef_set(codec, 0x8800, 0x348b328b); /* DRAM control */ + alc5505_coef_set(codec, 0x8808, 0x00020022); /* DRAM control */ + alc5505_coef_set(codec, 0x8818, 0x00000400); /* DRAM control */ + + val = alc5505_coef_get(codec, 0x6200) >> 16; /* Read revision ID */ + if (val <= 3) + alc5505_coef_set(codec, 0x6220, 0x2002010f); /* I/O PAD Configuration */ + else + alc5505_coef_set(codec, 0x6220, 0x6002018f); + + alc5505_coef_set(codec, 0x61ac, 0x055525f0); /**/ + alc5505_coef_set(codec, 0x61c0, 0x12230080); /* Clock control */ + alc5505_coef_set(codec, 0x61b4, 0x040e2b02); /* PLL2 control */ + alc5505_coef_set(codec, 0x61bc, 0x010234f8); /* OSC Control */ + alc5505_coef_set(codec, 0x880c, 0x00000004); /* DRAM Function control */ + alc5505_coef_set(codec, 0x880c, 0x00000003); + alc5505_coef_set(codec, 0x880c, 0x00000010); + +#ifdef HALT_REALTEK_ALC5505 + alc5505_dsp_halt(codec); +#endif +} + +#ifdef HALT_REALTEK_ALC5505 +#define alc5505_dsp_suspend(codec) do { } while (0) /* NOP */ +#define alc5505_dsp_resume(codec) do { } while (0) /* NOP */ +#else +#define alc5505_dsp_suspend(codec) alc5505_dsp_halt(codec) +#define alc5505_dsp_resume(codec) alc5505_dsp_back_from_halt(codec) +#endif + +#ifdef CONFIG_PM +static int alc269_suspend(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + if (spec->has_alc5505_dsp) + alc5505_dsp_suspend(codec); + + return alc_suspend(codec); +} + +static int alc269_resume(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + if (spec->codec_variant == ALC269_TYPE_ALC269VB) + alc269vb_toggle_power_output(codec, 0); + if (spec->codec_variant == ALC269_TYPE_ALC269VB && + (alc_get_coef0(codec) & 0x00ff) == 0x018) { + msleep(150); + } + + codec->patch_ops.init(codec); + + if (spec->codec_variant == ALC269_TYPE_ALC269VB) + alc269vb_toggle_power_output(codec, 1); + if (spec->codec_variant == ALC269_TYPE_ALC269VB && + (alc_get_coef0(codec) & 0x00ff) == 0x017) { + msleep(200); + } + + snd_hda_regmap_sync(codec); + hda_call_check_power_status(codec, 0x01); + + /* on some machine, the BIOS will clear the codec gpio data when enter + * suspend, and won't restore the data after resume, so we restore it + * in the driver. + */ + if (spec->gpio_data) + alc_write_gpio_data(codec); + + if (spec->has_alc5505_dsp) + alc5505_dsp_resume(codec); + + return 0; +} +#endif /* CONFIG_PM */ + +static void alc269_fixup_pincfg_no_hp_to_lineout(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) + spec->parse_flags = HDA_PINCFG_NO_HP_FIXUP; +} + +static void alc269_fixup_pincfg_U7x7_headset_mic(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + unsigned int cfg_headphone = snd_hda_codec_get_pincfg(codec, 0x21); + unsigned int cfg_headset_mic = snd_hda_codec_get_pincfg(codec, 0x19); + + if (cfg_headphone && cfg_headset_mic == 0x411111f0) + snd_hda_codec_set_pincfg(codec, 0x19, + (cfg_headphone & ~AC_DEFCFG_DEVICE) | + (AC_JACK_MIC_IN << AC_DEFCFG_DEVICE_SHIFT)); +} + +static void alc269_fixup_hweq(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_INIT) + alc_update_coef_idx(codec, 0x1e, 0, 0x80); +} + +static void alc269_fixup_headset_mic(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) + spec->parse_flags |= HDA_PINCFG_HEADSET_MIC; +} + +static void alc271_fixup_dmic(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + static const struct hda_verb verbs[] = { + {0x20, AC_VERB_SET_COEF_INDEX, 0x0d}, + {0x20, AC_VERB_SET_PROC_COEF, 0x4000}, + {} + }; + unsigned int cfg; + + if (strcmp(codec->core.chip_name, "ALC271X") && + strcmp(codec->core.chip_name, "ALC269VB")) + return; + cfg = snd_hda_codec_get_pincfg(codec, 0x12); + if (get_defcfg_connect(cfg) == AC_JACK_PORT_FIXED) + snd_hda_sequence_write(codec, verbs); +} + +/* Fix the speaker amp after resume, etc */ +static void alc269vb_fixup_aspire_e1_coef(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + if (action == HDA_FIXUP_ACT_INIT) + alc_update_coef_idx(codec, 0x0d, 0x6000, 0x6000); +} + +static void alc269_fixup_pcm_44k(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + if (action != HDA_FIXUP_ACT_PROBE) + return; + + /* Due to a hardware problem on Lenovo Ideadpad, we need to + * fix the sample rate of analog I/O to 44.1kHz + */ + spec->gen.stream_analog_playback = &alc269_44k_pcm_analog_playback; + spec->gen.stream_analog_capture = &alc269_44k_pcm_analog_capture; +} + +static void alc269_fixup_stereo_dmic(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + /* The digital-mic unit sends PDM (differential signal) instead of + * the standard PCM, thus you can't record a valid mono stream as is. + * Below is a workaround specific to ALC269 to control the dmic + * signal source as mono. + */ + if (action == HDA_FIXUP_ACT_INIT) + alc_update_coef_idx(codec, 0x07, 0, 0x80); +} + +static void alc269_quanta_automute(struct hda_codec *codec) +{ + snd_hda_gen_update_outputs(codec); + + alc_write_coef_idx(codec, 0x0c, 0x680); + alc_write_coef_idx(codec, 0x0c, 0x480); +} + +static void alc269_fixup_quanta_mute(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + if (action != HDA_FIXUP_ACT_PROBE) + return; + spec->gen.automute_hook = alc269_quanta_automute; +} + +static void alc269_x101_hp_automute_hook(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + struct alc_spec *spec = codec->spec; + int vref; + msleep(200); + snd_hda_gen_hp_automute(codec, jack); + + vref = spec->gen.hp_jack_present ? PIN_VREF80 : 0; + msleep(100); + snd_hda_codec_write(codec, 0x18, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + vref); + msleep(500); + snd_hda_codec_write(codec, 0x18, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + vref); +} + +/* + * Magic sequence to make Huawei Matebook X right speaker working (bko#197801) + */ +struct hda_alc298_mbxinit { + unsigned char value_0x23; + unsigned char value_0x25; +}; + +static void alc298_huawei_mbx_stereo_seq(struct hda_codec *codec, + const struct hda_alc298_mbxinit *initval, + bool first) +{ + snd_hda_codec_write(codec, 0x06, 0, AC_VERB_SET_DIGI_CONVERT_3, 0x0); + alc_write_coef_idx(codec, 0x26, 0xb000); + + if (first) + snd_hda_codec_write(codec, 0x21, 0, AC_VERB_GET_PIN_SENSE, 0x0); + + snd_hda_codec_write(codec, 0x6, 0, AC_VERB_SET_DIGI_CONVERT_3, 0x80); + alc_write_coef_idx(codec, 0x26, 0xf000); + alc_write_coef_idx(codec, 0x23, initval->value_0x23); + + if (initval->value_0x23 != 0x1e) + alc_write_coef_idx(codec, 0x25, initval->value_0x25); + + snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_COEF_INDEX, 0x26); + snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, 0xb010); +} + +static void alc298_fixup_huawei_mbx_stereo(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + /* Initialization magic */ + static const struct hda_alc298_mbxinit dac_init[] = { + {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}, + {} + }; + const struct hda_alc298_mbxinit *seq; + + if (action != HDA_FIXUP_ACT_INIT) + return; + + /* Start */ + snd_hda_codec_write(codec, 0x06, 0, AC_VERB_SET_DIGI_CONVERT_3, 0x00); + snd_hda_codec_write(codec, 0x06, 0, AC_VERB_SET_DIGI_CONVERT_3, 0x80); + alc_write_coef_idx(codec, 0x26, 0xf000); + alc_write_coef_idx(codec, 0x22, 0x31); + alc_write_coef_idx(codec, 0x23, 0x0b); + alc_write_coef_idx(codec, 0x25, 0x00); + snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_COEF_INDEX, 0x26); + snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, 0xb010); + + for (seq = dac_init; seq->value_0x23; seq++) + alc298_huawei_mbx_stereo_seq(codec, seq, seq == dac_init); +} + +static void alc269_fixup_x101_headset_mic(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->parse_flags |= HDA_PINCFG_HEADSET_MIC; + spec->gen.hp_automute_hook = alc269_x101_hp_automute_hook; + } +} + +static void alc_update_vref_led(struct hda_codec *codec, hda_nid_t pin, + bool polarity, bool on) +{ + unsigned int pinval; + + if (!pin) + return; + if (polarity) + on = !on; + pinval = snd_hda_codec_get_pin_target(codec, pin); + pinval &= ~AC_PINCTL_VREFEN; + pinval |= on ? AC_PINCTL_VREF_80 : AC_PINCTL_VREF_HIZ; + /* temporarily power up/down for setting VREF */ + snd_hda_power_up_pm(codec); + snd_hda_set_pin_ctl_cache(codec, pin, pinval); + snd_hda_power_down_pm(codec); +} + +/* update mute-LED according to the speaker mute state via mic VREF pin */ +static int vref_mute_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent); + struct alc_spec *spec = codec->spec; + + alc_update_vref_led(codec, spec->mute_led_nid, + spec->mute_led_polarity, brightness); + return 0; +} + +/* Make sure the led works even in runtime suspend */ +static unsigned int led_power_filter(struct hda_codec *codec, + hda_nid_t nid, + unsigned int power_state) +{ + struct alc_spec *spec = codec->spec; + + if (power_state != AC_PWRST_D3 || nid == 0 || + (nid != spec->mute_led_nid && nid != spec->cap_mute_led_nid)) + return power_state; + + /* Set pin ctl again, it might have just been set to 0 */ + snd_hda_set_pin_ctl(codec, nid, + snd_hda_codec_get_pin_target(codec, nid)); + + return snd_hda_gen_path_power_filter(codec, nid, power_state); +} + +static void alc269_fixup_hp_mute_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + const struct dmi_device *dev = NULL; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) { + int pol, pin; + if (sscanf(dev->name, "HP_Mute_LED_%d_%x", &pol, &pin) != 2) + continue; + if (pin < 0x0a || pin >= 0x10) + break; + spec->mute_led_polarity = pol; + spec->mute_led_nid = pin - 0x0a + 0x18; + snd_hda_gen_add_mute_led_cdev(codec, vref_mute_led_set); + codec->power_filter = led_power_filter; + codec_dbg(codec, + "Detected mute LED for %x:%d\n", spec->mute_led_nid, + spec->mute_led_polarity); + break; + } +} + +static void alc269_fixup_hp_mute_led_micx(struct hda_codec *codec, + const struct hda_fixup *fix, + int action, hda_nid_t pin) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->mute_led_polarity = 0; + spec->mute_led_nid = pin; + snd_hda_gen_add_mute_led_cdev(codec, vref_mute_led_set); + codec->power_filter = led_power_filter; + } +} + +static void alc269_fixup_hp_mute_led_mic1(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc269_fixup_hp_mute_led_micx(codec, fix, action, 0x18); +} + +static void alc269_fixup_hp_mute_led_mic2(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc269_fixup_hp_mute_led_micx(codec, fix, action, 0x19); +} + +static void alc269_fixup_hp_mute_led_mic3(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc269_fixup_hp_mute_led_micx(codec, fix, action, 0x1b); +} + +/* update LED status via GPIO */ +static void alc_update_gpio_led(struct hda_codec *codec, unsigned int mask, + int polarity, bool enabled) +{ + if (polarity) + enabled = !enabled; + alc_update_gpio_data(codec, mask, !enabled); /* muted -> LED on */ +} + +/* turn on/off mute LED via GPIO per vmaster hook */ +static int gpio_mute_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent); + struct alc_spec *spec = codec->spec; + + alc_update_gpio_led(codec, spec->gpio_mute_led_mask, + spec->mute_led_polarity, !brightness); + return 0; +} + +/* turn on/off mic-mute LED via GPIO per capture hook */ +static int micmute_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent); + struct alc_spec *spec = codec->spec; + + alc_update_gpio_led(codec, spec->gpio_mic_led_mask, + spec->micmute_led_polarity, !brightness); + return 0; +} + +/* setup mute and mic-mute GPIO bits, add hooks appropriately */ +static void alc_fixup_hp_gpio_led(struct hda_codec *codec, + int action, + unsigned int mute_mask, + unsigned int micmute_mask) +{ + struct alc_spec *spec = codec->spec; + + alc_fixup_gpio(codec, action, mute_mask | micmute_mask); + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + if (mute_mask) { + spec->gpio_mute_led_mask = mute_mask; + snd_hda_gen_add_mute_led_cdev(codec, gpio_mute_led_set); + } + if (micmute_mask) { + spec->gpio_mic_led_mask = micmute_mask; + snd_hda_gen_add_micmute_led_cdev(codec, micmute_led_set); + } +} + +static void alc236_fixup_hp_gpio_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc_fixup_hp_gpio_led(codec, action, 0x02, 0x01); +} + +static void alc269_fixup_hp_gpio_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc_fixup_hp_gpio_led(codec, action, 0x08, 0x10); +} + +static void alc285_fixup_hp_gpio_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc_fixup_hp_gpio_led(codec, action, 0x04, 0x01); +} + +static void alc286_fixup_hp_gpio_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc_fixup_hp_gpio_led(codec, action, 0x02, 0x20); +} + +static void alc287_fixup_hp_gpio_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc_fixup_hp_gpio_led(codec, action, 0x10, 0); +} + +static void alc245_fixup_hp_gpio_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) + spec->micmute_led_polarity = 1; + alc_fixup_hp_gpio_led(codec, action, 0, 0x04); +} + +/* turn on/off mic-mute LED per capture hook via VREF change */ +static int vref_micmute_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent); + struct alc_spec *spec = codec->spec; + + alc_update_vref_led(codec, spec->cap_mute_led_nid, + spec->micmute_led_polarity, brightness); + return 0; +} + +static void alc269_fixup_hp_gpio_mic1_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + alc_fixup_hp_gpio_led(codec, action, 0x08, 0); + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + /* Like hp_gpio_mic1_led, but also needs GPIO4 low to + * enable headphone amp + */ + spec->gpio_mask |= 0x10; + spec->gpio_dir |= 0x10; + spec->cap_mute_led_nid = 0x18; + snd_hda_gen_add_micmute_led_cdev(codec, vref_micmute_led_set); + codec->power_filter = led_power_filter; + } +} + +static void alc280_fixup_hp_gpio4(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + alc_fixup_hp_gpio_led(codec, action, 0x08, 0); + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->cap_mute_led_nid = 0x18; + snd_hda_gen_add_micmute_led_cdev(codec, vref_micmute_led_set); + codec->power_filter = led_power_filter; + } +} + +/* HP Spectre x360 14 model needs a unique workaround for enabling the amp; + * it needs to toggle the GPIO0 once on and off at each time (bko#210633) + */ +static void alc245_fixup_hp_x360_amp(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + spec->gpio_mask |= 0x01; + spec->gpio_dir |= 0x01; + break; + case HDA_FIXUP_ACT_INIT: + /* need to toggle GPIO to enable the amp */ + alc_update_gpio_data(codec, 0x01, true); + msleep(100); + alc_update_gpio_data(codec, 0x01, false); + break; + } +} + +/* toggle GPIO2 at each time stream is started; we use PREPARE state instead */ +static void alc274_hp_envy_pcm_hook(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream, + int action) +{ + switch (action) { + case HDA_GEN_PCM_ACT_PREPARE: + alc_update_gpio_data(codec, 0x04, true); + break; + case HDA_GEN_PCM_ACT_CLEANUP: + alc_update_gpio_data(codec, 0x04, false); + break; + } +} + +static void alc274_fixup_hp_envy_gpio(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PROBE) { + spec->gpio_mask |= 0x04; + spec->gpio_dir |= 0x04; + spec->gen.pcm_playback_hook = alc274_hp_envy_pcm_hook; + } +} + +static void alc_update_coef_led(struct hda_codec *codec, + struct alc_coef_led *led, + bool polarity, bool on) +{ + if (polarity) + on = !on; + /* temporarily power up/down for setting COEF bit */ + alc_update_coef_idx(codec, led->idx, led->mask, + on ? led->on : led->off); +} + +/* update mute-LED according to the speaker mute state via COEF bit */ +static int coef_mute_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent); + struct alc_spec *spec = codec->spec; + + alc_update_coef_led(codec, &spec->mute_led_coef, + spec->mute_led_polarity, brightness); + return 0; +} + +static void alc285_fixup_hp_mute_led_coefbit(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->mute_led_polarity = 0; + spec->mute_led_coef.idx = 0x0b; + spec->mute_led_coef.mask = 1 << 3; + spec->mute_led_coef.on = 1 << 3; + spec->mute_led_coef.off = 0; + snd_hda_gen_add_mute_led_cdev(codec, coef_mute_led_set); + } +} + +static void alc236_fixup_hp_mute_led_coefbit(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->mute_led_polarity = 0; + spec->mute_led_coef.idx = 0x34; + spec->mute_led_coef.mask = 1 << 5; + spec->mute_led_coef.on = 0; + spec->mute_led_coef.off = 1 << 5; + snd_hda_gen_add_mute_led_cdev(codec, coef_mute_led_set); + } +} + +static void alc236_fixup_hp_mute_led_coefbit2(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->mute_led_polarity = 0; + spec->mute_led_coef.idx = 0x07; + spec->mute_led_coef.mask = 1; + spec->mute_led_coef.on = 1; + spec->mute_led_coef.off = 0; + snd_hda_gen_add_mute_led_cdev(codec, coef_mute_led_set); + } +} + +static void alc245_fixup_hp_mute_led_coefbit(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->mute_led_polarity = 0; + spec->mute_led_coef.idx = 0x0b; + spec->mute_led_coef.mask = 3 << 2; + spec->mute_led_coef.on = 2 << 2; + spec->mute_led_coef.off = 1 << 2; + snd_hda_gen_add_mute_led_cdev(codec, coef_mute_led_set); + } +} + +/* turn on/off mic-mute LED per capture hook by coef bit */ +static int coef_micmute_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent); + struct alc_spec *spec = codec->spec; + + alc_update_coef_led(codec, &spec->mic_led_coef, + spec->micmute_led_polarity, brightness); + return 0; +} + +static void alc285_fixup_hp_coef_micmute_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->mic_led_coef.idx = 0x19; + spec->mic_led_coef.mask = 1 << 13; + spec->mic_led_coef.on = 1 << 13; + spec->mic_led_coef.off = 0; + snd_hda_gen_add_micmute_led_cdev(codec, coef_micmute_led_set); + } +} + +static void alc285_fixup_hp_gpio_micmute_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) + spec->micmute_led_polarity = 1; + alc_fixup_hp_gpio_led(codec, action, 0, 0x04); +} + +static void alc236_fixup_hp_coef_micmute_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->mic_led_coef.idx = 0x35; + spec->mic_led_coef.mask = 3 << 2; + spec->mic_led_coef.on = 2 << 2; + spec->mic_led_coef.off = 1 << 2; + snd_hda_gen_add_micmute_led_cdev(codec, coef_micmute_led_set); + } +} + +static void alc285_fixup_hp_mute_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc285_fixup_hp_mute_led_coefbit(codec, fix, action); + alc285_fixup_hp_coef_micmute_led(codec, fix, action); +} + +static void alc285_fixup_hp_spectre_x360_mute_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc285_fixup_hp_mute_led_coefbit(codec, fix, action); + alc285_fixup_hp_gpio_micmute_led(codec, fix, action); +} + +static void alc236_fixup_hp_mute_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc236_fixup_hp_mute_led_coefbit(codec, fix, action); + alc236_fixup_hp_coef_micmute_led(codec, fix, action); +} + +static void alc236_fixup_hp_micmute_led_vref(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->cap_mute_led_nid = 0x1a; + snd_hda_gen_add_micmute_led_cdev(codec, vref_micmute_led_set); + codec->power_filter = led_power_filter; + } +} + +static void alc236_fixup_hp_mute_led_micmute_vref(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc236_fixup_hp_mute_led_coefbit(codec, fix, action); + alc236_fixup_hp_micmute_led_vref(codec, fix, action); +} + +static inline void alc298_samsung_write_coef_pack(struct hda_codec *codec, + const unsigned short coefs[2]) +{ + alc_write_coef_idx(codec, 0x23, coefs[0]); + alc_write_coef_idx(codec, 0x25, coefs[1]); + alc_write_coef_idx(codec, 0x26, 0xb011); +} + +struct alc298_samsung_amp_desc { + unsigned char nid; + unsigned short init_seq[2][2]; +}; + +static void alc298_fixup_samsung_amp(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + int i, j; + static const unsigned short init_seq[][2] = { + { 0x19, 0x00 }, { 0x20, 0xc0 }, { 0x22, 0x44 }, { 0x23, 0x08 }, + { 0x24, 0x85 }, { 0x25, 0x41 }, { 0x35, 0x40 }, { 0x36, 0x01 }, + { 0x38, 0x81 }, { 0x3a, 0x03 }, { 0x3b, 0x81 }, { 0x40, 0x3e }, + { 0x41, 0x07 }, { 0x400, 0x1 } + }; + static const struct alc298_samsung_amp_desc amps[] = { + { 0x3a, { { 0x18, 0x1 }, { 0x26, 0x0 } } }, + { 0x39, { { 0x18, 0x2 }, { 0x26, 0x1 } } } + }; + + if (action != HDA_FIXUP_ACT_INIT) + return; + + for (i = 0; i < ARRAY_SIZE(amps); i++) { + alc_write_coef_idx(codec, 0x22, amps[i].nid); + + for (j = 0; j < ARRAY_SIZE(amps[i].init_seq); j++) + alc298_samsung_write_coef_pack(codec, amps[i].init_seq[j]); + + for (j = 0; j < ARRAY_SIZE(init_seq); j++) + alc298_samsung_write_coef_pack(codec, init_seq[j]); + } +} + +#if IS_REACHABLE(CONFIG_INPUT) +static void gpio2_mic_hotkey_event(struct hda_codec *codec, + struct hda_jack_callback *event) +{ + struct alc_spec *spec = codec->spec; + + /* GPIO2 just toggles on a keypress/keyrelease cycle. Therefore + send both key on and key off event for every interrupt. */ + input_report_key(spec->kb_dev, spec->alc_mute_keycode_map[ALC_KEY_MICMUTE_INDEX], 1); + input_sync(spec->kb_dev); + input_report_key(spec->kb_dev, spec->alc_mute_keycode_map[ALC_KEY_MICMUTE_INDEX], 0); + input_sync(spec->kb_dev); +} + +static int alc_register_micmute_input_device(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int i; + + spec->kb_dev = input_allocate_device(); + if (!spec->kb_dev) { + codec_err(codec, "Out of memory (input_allocate_device)\n"); + return -ENOMEM; + } + + spec->alc_mute_keycode_map[ALC_KEY_MICMUTE_INDEX] = KEY_MICMUTE; + + spec->kb_dev->name = "Microphone Mute Button"; + spec->kb_dev->evbit[0] = BIT_MASK(EV_KEY); + spec->kb_dev->keycodesize = sizeof(spec->alc_mute_keycode_map[0]); + spec->kb_dev->keycodemax = ARRAY_SIZE(spec->alc_mute_keycode_map); + spec->kb_dev->keycode = spec->alc_mute_keycode_map; + for (i = 0; i < ARRAY_SIZE(spec->alc_mute_keycode_map); i++) + set_bit(spec->alc_mute_keycode_map[i], spec->kb_dev->keybit); + + if (input_register_device(spec->kb_dev)) { + codec_err(codec, "input_register_device failed\n"); + input_free_device(spec->kb_dev); + spec->kb_dev = NULL; + return -ENOMEM; + } + + return 0; +} + +/* GPIO1 = set according to SKU external amp + * GPIO2 = mic mute hotkey + * GPIO3 = mute LED + * GPIO4 = mic mute LED + */ +static void alc280_fixup_hp_gpio2_mic_hotkey(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + alc_fixup_hp_gpio_led(codec, action, 0x08, 0x10); + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->init_amp = ALC_INIT_DEFAULT; + if (alc_register_micmute_input_device(codec) != 0) + return; + + spec->gpio_mask |= 0x06; + spec->gpio_dir |= 0x02; + spec->gpio_data |= 0x02; + snd_hda_codec_write_cache(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK, 0x04); + snd_hda_jack_detect_enable_callback(codec, codec->core.afg, + gpio2_mic_hotkey_event); + return; + } + + if (!spec->kb_dev) + return; + + switch (action) { + case HDA_FIXUP_ACT_FREE: + input_unregister_device(spec->kb_dev); + spec->kb_dev = NULL; + } +} + +/* Line2 = mic mute hotkey + * GPIO2 = mic mute LED + */ +static void alc233_fixup_lenovo_line2_mic_hotkey(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + alc_fixup_hp_gpio_led(codec, action, 0, 0x04); + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->init_amp = ALC_INIT_DEFAULT; + if (alc_register_micmute_input_device(codec) != 0) + return; + + snd_hda_jack_detect_enable_callback(codec, 0x1b, + gpio2_mic_hotkey_event); + return; + } + + if (!spec->kb_dev) + return; + + switch (action) { + case HDA_FIXUP_ACT_FREE: + input_unregister_device(spec->kb_dev); + spec->kb_dev = NULL; + } +} +#else /* INPUT */ +#define alc280_fixup_hp_gpio2_mic_hotkey NULL +#define alc233_fixup_lenovo_line2_mic_hotkey NULL +#endif /* INPUT */ + +static void alc269_fixup_hp_line1_mic1_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + alc269_fixup_hp_mute_led_micx(codec, fix, action, 0x1a); + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->cap_mute_led_nid = 0x18; + snd_hda_gen_add_micmute_led_cdev(codec, vref_micmute_led_set); + } +} + +static const struct coef_fw alc225_pre_hsmode[] = { + UPDATE_COEF(0x4a, 1<<8, 0), + UPDATE_COEFEX(0x57, 0x05, 1<<14, 0), + UPDATE_COEF(0x63, 3<<14, 3<<14), + UPDATE_COEF(0x4a, 3<<4, 2<<4), + UPDATE_COEF(0x4a, 3<<10, 3<<10), + UPDATE_COEF(0x45, 0x3f<<10, 0x34<<10), + UPDATE_COEF(0x4a, 3<<10, 0), + {} +}; + +static void alc_headset_mode_unplugged(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + static const struct coef_fw coef0255[] = { + WRITE_COEF(0x1b, 0x0c0b), /* LDO and MISC control */ + WRITE_COEF(0x45, 0xd089), /* UAJ function set to menual mode */ + UPDATE_COEFEX(0x57, 0x05, 1<<14, 0), /* Direct Drive HP Amp control(Set to verb control)*/ + WRITE_COEF(0x06, 0x6104), /* Set MIC2 Vref gate with HP */ + WRITE_COEFEX(0x57, 0x03, 0x8aa6), /* Direct Drive HP Amp control */ + {} + }; + static const struct coef_fw coef0256[] = { + WRITE_COEF(0x1b, 0x0c4b), /* LDO and MISC control */ + WRITE_COEF(0x45, 0xd089), /* UAJ function set to menual mode */ + WRITE_COEF(0x06, 0x6104), /* Set MIC2 Vref gate with HP */ + WRITE_COEFEX(0x57, 0x03, 0x09a3), /* Direct Drive HP Amp control */ + UPDATE_COEFEX(0x57, 0x05, 1<<14, 0), /* Direct Drive HP Amp control(Set to verb control)*/ + {} + }; + static const struct coef_fw coef0233[] = { + WRITE_COEF(0x1b, 0x0c0b), + WRITE_COEF(0x45, 0xc429), + UPDATE_COEF(0x35, 0x4000, 0), + WRITE_COEF(0x06, 0x2104), + WRITE_COEF(0x1a, 0x0001), + WRITE_COEF(0x26, 0x0004), + WRITE_COEF(0x32, 0x42a3), + {} + }; + static const struct coef_fw coef0288[] = { + UPDATE_COEF(0x4f, 0xfcc0, 0xc400), + UPDATE_COEF(0x50, 0x2000, 0x2000), + UPDATE_COEF(0x56, 0x0006, 0x0006), + UPDATE_COEF(0x66, 0x0008, 0), + UPDATE_COEF(0x67, 0x2000, 0), + {} + }; + static const struct coef_fw coef0298[] = { + UPDATE_COEF(0x19, 0x1300, 0x0300), + {} + }; + static const struct coef_fw coef0292[] = { + WRITE_COEF(0x76, 0x000e), + WRITE_COEF(0x6c, 0x2400), + WRITE_COEF(0x18, 0x7308), + WRITE_COEF(0x6b, 0xc429), + {} + }; + static const struct coef_fw coef0293[] = { + UPDATE_COEF(0x10, 7<<8, 6<<8), /* SET Line1 JD to 0 */ + UPDATE_COEFEX(0x57, 0x05, 1<<15|1<<13, 0x0), /* SET charge pump by verb */ + UPDATE_COEFEX(0x57, 0x03, 1<<10, 1<<10), /* SET EN_OSW to 1 */ + UPDATE_COEF(0x1a, 1<<3, 1<<3), /* Combo JD gating with LINE1-VREFO */ + WRITE_COEF(0x45, 0xc429), /* Set to TRS type */ + UPDATE_COEF(0x4a, 0x000f, 0x000e), /* Combo Jack auto detect */ + {} + }; + static const struct coef_fw coef0668[] = { + WRITE_COEF(0x15, 0x0d40), + WRITE_COEF(0xb7, 0x802b), + {} + }; + static const struct coef_fw coef0225[] = { + UPDATE_COEF(0x63, 3<<14, 0), + {} + }; + static const struct coef_fw coef0274[] = { + UPDATE_COEF(0x4a, 0x0100, 0), + UPDATE_COEFEX(0x57, 0x05, 0x4000, 0), + UPDATE_COEF(0x6b, 0xf000, 0x5000), + UPDATE_COEF(0x4a, 0x0010, 0), + UPDATE_COEF(0x4a, 0x0c00, 0x0c00), + WRITE_COEF(0x45, 0x5289), + UPDATE_COEF(0x4a, 0x0c00, 0), + {} + }; + + if (spec->no_internal_mic_pin) { + alc_update_coef_idx(codec, 0x45, 0xf<<12 | 1<<10, 5<<12); + return; + } + + switch (codec->core.vendor_id) { + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + break; + case 0x10ec0230: + case 0x10ec0236: + case 0x10ec0256: + case 0x19e58326: + alc_process_coef_fw(codec, coef0256); + break; + case 0x10ec0234: + case 0x10ec0274: + case 0x10ec0294: + alc_process_coef_fw(codec, coef0274); + break; + case 0x10ec0233: + case 0x10ec0283: + alc_process_coef_fw(codec, coef0233); + break; + case 0x10ec0286: + case 0x10ec0288: + alc_process_coef_fw(codec, coef0288); + break; + case 0x10ec0298: + alc_process_coef_fw(codec, coef0298); + alc_process_coef_fw(codec, coef0288); + break; + case 0x10ec0292: + alc_process_coef_fw(codec, coef0292); + break; + case 0x10ec0293: + alc_process_coef_fw(codec, coef0293); + break; + case 0x10ec0668: + alc_process_coef_fw(codec, coef0668); + break; + case 0x10ec0215: + case 0x10ec0225: + case 0x10ec0285: + case 0x10ec0295: + case 0x10ec0289: + case 0x10ec0299: + alc_process_coef_fw(codec, alc225_pre_hsmode); + alc_process_coef_fw(codec, coef0225); + break; + case 0x10ec0867: + alc_update_coefex_idx(codec, 0x57, 0x5, 1<<14, 0); + break; + } + codec_dbg(codec, "Headset jack set to unplugged mode.\n"); +} + + +static void alc_headset_mode_mic_in(struct hda_codec *codec, hda_nid_t hp_pin, + hda_nid_t mic_pin) +{ + static const struct coef_fw coef0255[] = { + WRITE_COEFEX(0x57, 0x03, 0x8aa6), + WRITE_COEF(0x06, 0x6100), /* Set MIC2 Vref gate to normal */ + {} + }; + static const struct coef_fw coef0256[] = { + UPDATE_COEFEX(0x57, 0x05, 1<<14, 1<<14), /* Direct Drive HP Amp control(Set to verb control)*/ + WRITE_COEFEX(0x57, 0x03, 0x09a3), + WRITE_COEF(0x06, 0x6100), /* Set MIC2 Vref gate to normal */ + {} + }; + static const struct coef_fw coef0233[] = { + UPDATE_COEF(0x35, 0, 1<<14), + WRITE_COEF(0x06, 0x2100), + WRITE_COEF(0x1a, 0x0021), + WRITE_COEF(0x26, 0x008c), + {} + }; + static const struct coef_fw coef0288[] = { + UPDATE_COEF(0x4f, 0x00c0, 0), + UPDATE_COEF(0x50, 0x2000, 0), + UPDATE_COEF(0x56, 0x0006, 0), + UPDATE_COEF(0x4f, 0xfcc0, 0xc400), + UPDATE_COEF(0x66, 0x0008, 0x0008), + UPDATE_COEF(0x67, 0x2000, 0x2000), + {} + }; + static const struct coef_fw coef0292[] = { + WRITE_COEF(0x19, 0xa208), + WRITE_COEF(0x2e, 0xacf0), + {} + }; + static const struct coef_fw coef0293[] = { + UPDATE_COEFEX(0x57, 0x05, 0, 1<<15|1<<13), /* SET charge pump by verb */ + UPDATE_COEFEX(0x57, 0x03, 1<<10, 0), /* SET EN_OSW to 0 */ + UPDATE_COEF(0x1a, 1<<3, 0), /* Combo JD gating without LINE1-VREFO */ + {} + }; + static const struct coef_fw coef0688[] = { + WRITE_COEF(0xb7, 0x802b), + WRITE_COEF(0xb5, 0x1040), + UPDATE_COEF(0xc3, 0, 1<<12), + {} + }; + static const struct coef_fw coef0225[] = { + UPDATE_COEFEX(0x57, 0x05, 1<<14, 1<<14), + UPDATE_COEF(0x4a, 3<<4, 2<<4), + UPDATE_COEF(0x63, 3<<14, 0), + {} + }; + static const struct coef_fw coef0274[] = { + UPDATE_COEFEX(0x57, 0x05, 0x4000, 0x4000), + UPDATE_COEF(0x4a, 0x0010, 0), + UPDATE_COEF(0x6b, 0xf000, 0), + {} + }; + + switch (codec->core.vendor_id) { + case 0x10ec0255: + alc_write_coef_idx(codec, 0x45, 0xc489); + snd_hda_set_pin_ctl_cache(codec, hp_pin, 0); + alc_process_coef_fw(codec, coef0255); + snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50); + break; + case 0x10ec0230: + case 0x10ec0236: + case 0x10ec0256: + case 0x19e58326: + alc_write_coef_idx(codec, 0x45, 0xc489); + snd_hda_set_pin_ctl_cache(codec, hp_pin, 0); + alc_process_coef_fw(codec, coef0256); + snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50); + break; + case 0x10ec0234: + case 0x10ec0274: + case 0x10ec0294: + alc_write_coef_idx(codec, 0x45, 0x4689); + snd_hda_set_pin_ctl_cache(codec, hp_pin, 0); + alc_process_coef_fw(codec, coef0274); + snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50); + break; + case 0x10ec0233: + case 0x10ec0283: + alc_write_coef_idx(codec, 0x45, 0xc429); + snd_hda_set_pin_ctl_cache(codec, hp_pin, 0); + alc_process_coef_fw(codec, coef0233); + snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50); + break; + case 0x10ec0286: + case 0x10ec0288: + case 0x10ec0298: + snd_hda_set_pin_ctl_cache(codec, hp_pin, 0); + alc_process_coef_fw(codec, coef0288); + snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50); + break; + case 0x10ec0292: + snd_hda_set_pin_ctl_cache(codec, hp_pin, 0); + alc_process_coef_fw(codec, coef0292); + break; + case 0x10ec0293: + /* Set to TRS mode */ + alc_write_coef_idx(codec, 0x45, 0xc429); + snd_hda_set_pin_ctl_cache(codec, hp_pin, 0); + alc_process_coef_fw(codec, coef0293); + snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50); + break; + case 0x10ec0867: + alc_update_coefex_idx(codec, 0x57, 0x5, 0, 1<<14); + fallthrough; + case 0x10ec0221: + case 0x10ec0662: + snd_hda_set_pin_ctl_cache(codec, hp_pin, 0); + snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50); + break; + case 0x10ec0668: + alc_write_coef_idx(codec, 0x11, 0x0001); + snd_hda_set_pin_ctl_cache(codec, hp_pin, 0); + alc_process_coef_fw(codec, coef0688); + snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50); + break; + case 0x10ec0215: + case 0x10ec0225: + case 0x10ec0285: + case 0x10ec0295: + case 0x10ec0289: + case 0x10ec0299: + alc_process_coef_fw(codec, alc225_pre_hsmode); + alc_update_coef_idx(codec, 0x45, 0x3f<<10, 0x31<<10); + snd_hda_set_pin_ctl_cache(codec, hp_pin, 0); + alc_process_coef_fw(codec, coef0225); + snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50); + break; + } + codec_dbg(codec, "Headset jack set to mic-in mode.\n"); +} + +static void alc_headset_mode_default(struct hda_codec *codec) +{ + static const struct coef_fw coef0225[] = { + UPDATE_COEF(0x45, 0x3f<<10, 0x30<<10), + UPDATE_COEF(0x45, 0x3f<<10, 0x31<<10), + UPDATE_COEF(0x49, 3<<8, 0<<8), + UPDATE_COEF(0x4a, 3<<4, 3<<4), + UPDATE_COEF(0x63, 3<<14, 0), + UPDATE_COEF(0x67, 0xf000, 0x3000), + {} + }; + static const struct coef_fw coef0255[] = { + WRITE_COEF(0x45, 0xc089), + WRITE_COEF(0x45, 0xc489), + WRITE_COEFEX(0x57, 0x03, 0x8ea6), + WRITE_COEF(0x49, 0x0049), + {} + }; + static const struct coef_fw coef0256[] = { + WRITE_COEF(0x45, 0xc489), + WRITE_COEFEX(0x57, 0x03, 0x0da3), + WRITE_COEF(0x49, 0x0049), + UPDATE_COEFEX(0x57, 0x05, 1<<14, 0), /* Direct Drive HP Amp control(Set to verb control)*/ + WRITE_COEF(0x06, 0x6100), + {} + }; + static const struct coef_fw coef0233[] = { + WRITE_COEF(0x06, 0x2100), + WRITE_COEF(0x32, 0x4ea3), + {} + }; + static const struct coef_fw coef0288[] = { + UPDATE_COEF(0x4f, 0xfcc0, 0xc400), /* Set to TRS type */ + UPDATE_COEF(0x50, 0x2000, 0x2000), + UPDATE_COEF(0x56, 0x0006, 0x0006), + UPDATE_COEF(0x66, 0x0008, 0), + UPDATE_COEF(0x67, 0x2000, 0), + {} + }; + static const struct coef_fw coef0292[] = { + WRITE_COEF(0x76, 0x000e), + WRITE_COEF(0x6c, 0x2400), + WRITE_COEF(0x6b, 0xc429), + WRITE_COEF(0x18, 0x7308), + {} + }; + static const struct coef_fw coef0293[] = { + UPDATE_COEF(0x4a, 0x000f, 0x000e), /* Combo Jack auto detect */ + WRITE_COEF(0x45, 0xC429), /* Set to TRS type */ + UPDATE_COEF(0x1a, 1<<3, 0), /* Combo JD gating without LINE1-VREFO */ + {} + }; + static const struct coef_fw coef0688[] = { + WRITE_COEF(0x11, 0x0041), + WRITE_COEF(0x15, 0x0d40), + WRITE_COEF(0xb7, 0x802b), + {} + }; + static const struct coef_fw coef0274[] = { + WRITE_COEF(0x45, 0x4289), + UPDATE_COEF(0x4a, 0x0010, 0x0010), + UPDATE_COEF(0x6b, 0x0f00, 0), + UPDATE_COEF(0x49, 0x0300, 0x0300), + {} + }; + + switch (codec->core.vendor_id) { + case 0x10ec0215: + case 0x10ec0225: + case 0x10ec0285: + case 0x10ec0295: + case 0x10ec0289: + case 0x10ec0299: + alc_process_coef_fw(codec, alc225_pre_hsmode); + alc_process_coef_fw(codec, coef0225); + break; + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + break; + case 0x10ec0230: + case 0x10ec0236: + case 0x10ec0256: + case 0x19e58326: + alc_write_coef_idx(codec, 0x1b, 0x0e4b); + alc_write_coef_idx(codec, 0x45, 0xc089); + msleep(50); + alc_process_coef_fw(codec, coef0256); + break; + case 0x10ec0234: + case 0x10ec0274: + case 0x10ec0294: + alc_process_coef_fw(codec, coef0274); + break; + case 0x10ec0233: + case 0x10ec0283: + alc_process_coef_fw(codec, coef0233); + break; + case 0x10ec0286: + case 0x10ec0288: + case 0x10ec0298: + alc_process_coef_fw(codec, coef0288); + break; + case 0x10ec0292: + alc_process_coef_fw(codec, coef0292); + break; + case 0x10ec0293: + alc_process_coef_fw(codec, coef0293); + break; + case 0x10ec0668: + alc_process_coef_fw(codec, coef0688); + break; + case 0x10ec0867: + alc_update_coefex_idx(codec, 0x57, 0x5, 1<<14, 0); + break; + } + codec_dbg(codec, "Headset jack set to headphone (default) mode.\n"); +} + +/* Iphone type */ +static void alc_headset_mode_ctia(struct hda_codec *codec) +{ + int val; + + static const struct coef_fw coef0255[] = { + WRITE_COEF(0x45, 0xd489), /* Set to CTIA type */ + WRITE_COEF(0x1b, 0x0c2b), + WRITE_COEFEX(0x57, 0x03, 0x8ea6), + {} + }; + static const struct coef_fw coef0256[] = { + WRITE_COEF(0x45, 0xd489), /* Set to CTIA type */ + WRITE_COEF(0x1b, 0x0e6b), + {} + }; + static const struct coef_fw coef0233[] = { + WRITE_COEF(0x45, 0xd429), + WRITE_COEF(0x1b, 0x0c2b), + WRITE_COEF(0x32, 0x4ea3), + {} + }; + static const struct coef_fw coef0288[] = { + UPDATE_COEF(0x50, 0x2000, 0x2000), + UPDATE_COEF(0x56, 0x0006, 0x0006), + UPDATE_COEF(0x66, 0x0008, 0), + UPDATE_COEF(0x67, 0x2000, 0), + {} + }; + static const struct coef_fw coef0292[] = { + WRITE_COEF(0x6b, 0xd429), + WRITE_COEF(0x76, 0x0008), + WRITE_COEF(0x18, 0x7388), + {} + }; + static const struct coef_fw coef0293[] = { + WRITE_COEF(0x45, 0xd429), /* Set to ctia type */ + UPDATE_COEF(0x10, 7<<8, 7<<8), /* SET Line1 JD to 1 */ + {} + }; + static const struct coef_fw coef0688[] = { + WRITE_COEF(0x11, 0x0001), + WRITE_COEF(0x15, 0x0d60), + WRITE_COEF(0xc3, 0x0000), + {} + }; + static const struct coef_fw coef0225_1[] = { + UPDATE_COEF(0x45, 0x3f<<10, 0x35<<10), + UPDATE_COEF(0x63, 3<<14, 2<<14), + {} + }; + static const struct coef_fw coef0225_2[] = { + UPDATE_COEF(0x45, 0x3f<<10, 0x35<<10), + UPDATE_COEF(0x63, 3<<14, 1<<14), + {} + }; + + switch (codec->core.vendor_id) { + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + break; + case 0x10ec0230: + case 0x10ec0236: + case 0x10ec0256: + case 0x19e58326: + alc_process_coef_fw(codec, coef0256); + break; + case 0x10ec0234: + case 0x10ec0274: + case 0x10ec0294: + alc_write_coef_idx(codec, 0x45, 0xd689); + break; + case 0x10ec0233: + case 0x10ec0283: + alc_process_coef_fw(codec, coef0233); + break; + case 0x10ec0298: + val = alc_read_coef_idx(codec, 0x50); + if (val & (1 << 12)) { + alc_update_coef_idx(codec, 0x8e, 0x0070, 0x0020); + alc_update_coef_idx(codec, 0x4f, 0xfcc0, 0xd400); + msleep(300); + } else { + alc_update_coef_idx(codec, 0x8e, 0x0070, 0x0010); + alc_update_coef_idx(codec, 0x4f, 0xfcc0, 0xd400); + msleep(300); + } + break; + case 0x10ec0286: + case 0x10ec0288: + alc_update_coef_idx(codec, 0x4f, 0xfcc0, 0xd400); + msleep(300); + alc_process_coef_fw(codec, coef0288); + break; + case 0x10ec0292: + alc_process_coef_fw(codec, coef0292); + break; + case 0x10ec0293: + alc_process_coef_fw(codec, coef0293); + break; + case 0x10ec0668: + alc_process_coef_fw(codec, coef0688); + break; + case 0x10ec0215: + case 0x10ec0225: + case 0x10ec0285: + case 0x10ec0295: + case 0x10ec0289: + case 0x10ec0299: + val = alc_read_coef_idx(codec, 0x45); + if (val & (1 << 9)) + alc_process_coef_fw(codec, coef0225_2); + else + alc_process_coef_fw(codec, coef0225_1); + break; + case 0x10ec0867: + alc_update_coefex_idx(codec, 0x57, 0x5, 1<<14, 0); + break; + } + codec_dbg(codec, "Headset jack set to iPhone-style headset mode.\n"); +} + +/* Nokia type */ +static void alc_headset_mode_omtp(struct hda_codec *codec) +{ + static const struct coef_fw coef0255[] = { + WRITE_COEF(0x45, 0xe489), /* Set to OMTP Type */ + WRITE_COEF(0x1b, 0x0c2b), + WRITE_COEFEX(0x57, 0x03, 0x8ea6), + {} + }; + static const struct coef_fw coef0256[] = { + WRITE_COEF(0x45, 0xe489), /* Set to OMTP Type */ + WRITE_COEF(0x1b, 0x0e6b), + {} + }; + static const struct coef_fw coef0233[] = { + WRITE_COEF(0x45, 0xe429), + WRITE_COEF(0x1b, 0x0c2b), + WRITE_COEF(0x32, 0x4ea3), + {} + }; + static const struct coef_fw coef0288[] = { + UPDATE_COEF(0x50, 0x2000, 0x2000), + UPDATE_COEF(0x56, 0x0006, 0x0006), + UPDATE_COEF(0x66, 0x0008, 0), + UPDATE_COEF(0x67, 0x2000, 0), + {} + }; + static const struct coef_fw coef0292[] = { + WRITE_COEF(0x6b, 0xe429), + WRITE_COEF(0x76, 0x0008), + WRITE_COEF(0x18, 0x7388), + {} + }; + static const struct coef_fw coef0293[] = { + WRITE_COEF(0x45, 0xe429), /* Set to omtp type */ + UPDATE_COEF(0x10, 7<<8, 7<<8), /* SET Line1 JD to 1 */ + {} + }; + static const struct coef_fw coef0688[] = { + WRITE_COEF(0x11, 0x0001), + WRITE_COEF(0x15, 0x0d50), + WRITE_COEF(0xc3, 0x0000), + {} + }; + static const struct coef_fw coef0225[] = { + UPDATE_COEF(0x45, 0x3f<<10, 0x39<<10), + UPDATE_COEF(0x63, 3<<14, 2<<14), + {} + }; + + switch (codec->core.vendor_id) { + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + break; + case 0x10ec0230: + case 0x10ec0236: + case 0x10ec0256: + case 0x19e58326: + alc_process_coef_fw(codec, coef0256); + break; + case 0x10ec0234: + case 0x10ec0274: + case 0x10ec0294: + alc_write_coef_idx(codec, 0x45, 0xe689); + break; + case 0x10ec0233: + case 0x10ec0283: + alc_process_coef_fw(codec, coef0233); + break; + case 0x10ec0298: + alc_update_coef_idx(codec, 0x8e, 0x0070, 0x0010);/* Headset output enable */ + alc_update_coef_idx(codec, 0x4f, 0xfcc0, 0xe400); + msleep(300); + break; + case 0x10ec0286: + case 0x10ec0288: + alc_update_coef_idx(codec, 0x4f, 0xfcc0, 0xe400); + msleep(300); + alc_process_coef_fw(codec, coef0288); + break; + case 0x10ec0292: + alc_process_coef_fw(codec, coef0292); + break; + case 0x10ec0293: + alc_process_coef_fw(codec, coef0293); + break; + case 0x10ec0668: + alc_process_coef_fw(codec, coef0688); + break; + case 0x10ec0215: + case 0x10ec0225: + case 0x10ec0285: + case 0x10ec0295: + case 0x10ec0289: + case 0x10ec0299: + alc_process_coef_fw(codec, coef0225); + break; + } + codec_dbg(codec, "Headset jack set to Nokia-style headset mode.\n"); +} + +static void alc_determine_headset_type(struct hda_codec *codec) +{ + int val; + bool is_ctia = false; + struct alc_spec *spec = codec->spec; + static const struct coef_fw coef0255[] = { + WRITE_COEF(0x45, 0xd089), /* combo jack auto switch control(Check type)*/ + WRITE_COEF(0x49, 0x0149), /* combo jack auto switch control(Vref + conteol) */ + {} + }; + static const struct coef_fw coef0288[] = { + UPDATE_COEF(0x4f, 0xfcc0, 0xd400), /* Check Type */ + {} + }; + static const struct coef_fw coef0298[] = { + UPDATE_COEF(0x50, 0x2000, 0x2000), + UPDATE_COEF(0x56, 0x0006, 0x0006), + UPDATE_COEF(0x66, 0x0008, 0), + UPDATE_COEF(0x67, 0x2000, 0), + UPDATE_COEF(0x19, 0x1300, 0x1300), + {} + }; + static const struct coef_fw coef0293[] = { + UPDATE_COEF(0x4a, 0x000f, 0x0008), /* Combo Jack auto detect */ + WRITE_COEF(0x45, 0xD429), /* Set to ctia type */ + {} + }; + static const struct coef_fw coef0688[] = { + WRITE_COEF(0x11, 0x0001), + WRITE_COEF(0xb7, 0x802b), + WRITE_COEF(0x15, 0x0d60), + WRITE_COEF(0xc3, 0x0c00), + {} + }; + static const struct coef_fw coef0274[] = { + UPDATE_COEF(0x4a, 0x0010, 0), + UPDATE_COEF(0x4a, 0x8000, 0), + WRITE_COEF(0x45, 0xd289), + UPDATE_COEF(0x49, 0x0300, 0x0300), + {} + }; + + if (spec->no_internal_mic_pin) { + alc_update_coef_idx(codec, 0x45, 0xf<<12 | 1<<10, 5<<12); + return; + } + + switch (codec->core.vendor_id) { + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + msleep(300); + val = alc_read_coef_idx(codec, 0x46); + is_ctia = (val & 0x0070) == 0x0070; + break; + case 0x10ec0230: + case 0x10ec0236: + case 0x10ec0256: + case 0x19e58326: + alc_write_coef_idx(codec, 0x1b, 0x0e4b); + alc_write_coef_idx(codec, 0x06, 0x6104); + alc_write_coefex_idx(codec, 0x57, 0x3, 0x09a3); + + snd_hda_codec_write(codec, 0x21, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + msleep(80); + snd_hda_codec_write(codec, 0x21, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0); + + alc_process_coef_fw(codec, coef0255); + msleep(300); + val = alc_read_coef_idx(codec, 0x46); + is_ctia = (val & 0x0070) == 0x0070; + + alc_write_coefex_idx(codec, 0x57, 0x3, 0x0da3); + alc_update_coefex_idx(codec, 0x57, 0x5, 1<<14, 0); + + snd_hda_codec_write(codec, 0x21, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT); + msleep(80); + snd_hda_codec_write(codec, 0x21, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE); + break; + case 0x10ec0234: + case 0x10ec0274: + case 0x10ec0294: + alc_process_coef_fw(codec, coef0274); + msleep(850); + val = alc_read_coef_idx(codec, 0x46); + is_ctia = (val & 0x00f0) == 0x00f0; + break; + case 0x10ec0233: + case 0x10ec0283: + alc_write_coef_idx(codec, 0x45, 0xd029); + msleep(300); + val = alc_read_coef_idx(codec, 0x46); + is_ctia = (val & 0x0070) == 0x0070; + break; + case 0x10ec0298: + snd_hda_codec_write(codec, 0x21, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + msleep(100); + snd_hda_codec_write(codec, 0x21, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0); + msleep(200); + + val = alc_read_coef_idx(codec, 0x50); + if (val & (1 << 12)) { + alc_update_coef_idx(codec, 0x8e, 0x0070, 0x0020); + alc_process_coef_fw(codec, coef0288); + msleep(350); + val = alc_read_coef_idx(codec, 0x50); + is_ctia = (val & 0x0070) == 0x0070; + } else { + alc_update_coef_idx(codec, 0x8e, 0x0070, 0x0010); + alc_process_coef_fw(codec, coef0288); + msleep(350); + val = alc_read_coef_idx(codec, 0x50); + is_ctia = (val & 0x0070) == 0x0070; + } + alc_process_coef_fw(codec, coef0298); + snd_hda_codec_write(codec, 0x21, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP); + msleep(75); + snd_hda_codec_write(codec, 0x21, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE); + break; + case 0x10ec0286: + case 0x10ec0288: + alc_process_coef_fw(codec, coef0288); + msleep(350); + val = alc_read_coef_idx(codec, 0x50); + is_ctia = (val & 0x0070) == 0x0070; + break; + case 0x10ec0292: + alc_write_coef_idx(codec, 0x6b, 0xd429); + msleep(300); + val = alc_read_coef_idx(codec, 0x6c); + is_ctia = (val & 0x001c) == 0x001c; + break; + case 0x10ec0293: + alc_process_coef_fw(codec, coef0293); + msleep(300); + val = alc_read_coef_idx(codec, 0x46); + is_ctia = (val & 0x0070) == 0x0070; + break; + case 0x10ec0668: + alc_process_coef_fw(codec, coef0688); + msleep(300); + val = alc_read_coef_idx(codec, 0xbe); + is_ctia = (val & 0x1c02) == 0x1c02; + break; + case 0x10ec0215: + case 0x10ec0225: + case 0x10ec0285: + case 0x10ec0295: + case 0x10ec0289: + case 0x10ec0299: + snd_hda_codec_write(codec, 0x21, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + msleep(80); + snd_hda_codec_write(codec, 0x21, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0); + + alc_process_coef_fw(codec, alc225_pre_hsmode); + alc_update_coef_idx(codec, 0x67, 0xf000, 0x1000); + val = alc_read_coef_idx(codec, 0x45); + if (val & (1 << 9)) { + alc_update_coef_idx(codec, 0x45, 0x3f<<10, 0x34<<10); + alc_update_coef_idx(codec, 0x49, 3<<8, 2<<8); + msleep(800); + val = alc_read_coef_idx(codec, 0x46); + is_ctia = (val & 0x00f0) == 0x00f0; + } else { + alc_update_coef_idx(codec, 0x45, 0x3f<<10, 0x34<<10); + alc_update_coef_idx(codec, 0x49, 3<<8, 1<<8); + msleep(800); + val = alc_read_coef_idx(codec, 0x46); + is_ctia = (val & 0x00f0) == 0x00f0; + } + alc_update_coef_idx(codec, 0x4a, 7<<6, 7<<6); + alc_update_coef_idx(codec, 0x4a, 3<<4, 3<<4); + alc_update_coef_idx(codec, 0x67, 0xf000, 0x3000); + + snd_hda_codec_write(codec, 0x21, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT); + msleep(80); + snd_hda_codec_write(codec, 0x21, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE); + break; + case 0x10ec0867: + is_ctia = true; + break; + } + + codec_dbg(codec, "Headset jack detected iPhone-style headset: %s\n", + is_ctia ? "yes" : "no"); + spec->current_headset_type = is_ctia ? ALC_HEADSET_TYPE_CTIA : ALC_HEADSET_TYPE_OMTP; +} + +static void alc_update_headset_mode(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + hda_nid_t mux_pin = spec->gen.imux_pins[spec->gen.cur_mux[0]]; + hda_nid_t hp_pin = alc_get_hp_pin(spec); + + int new_headset_mode; + + if (!snd_hda_jack_detect(codec, hp_pin)) + new_headset_mode = ALC_HEADSET_MODE_UNPLUGGED; + else if (mux_pin == spec->headset_mic_pin) + new_headset_mode = ALC_HEADSET_MODE_HEADSET; + else if (mux_pin == spec->headphone_mic_pin) + new_headset_mode = ALC_HEADSET_MODE_MIC; + else + new_headset_mode = ALC_HEADSET_MODE_HEADPHONE; + + if (new_headset_mode == spec->current_headset_mode) { + snd_hda_gen_update_outputs(codec); + return; + } + + switch (new_headset_mode) { + case ALC_HEADSET_MODE_UNPLUGGED: + alc_headset_mode_unplugged(codec); + spec->current_headset_mode = ALC_HEADSET_MODE_UNKNOWN; + spec->current_headset_type = ALC_HEADSET_TYPE_UNKNOWN; + spec->gen.hp_jack_present = false; + break; + case ALC_HEADSET_MODE_HEADSET: + if (spec->current_headset_type == ALC_HEADSET_TYPE_UNKNOWN) + alc_determine_headset_type(codec); + if (spec->current_headset_type == ALC_HEADSET_TYPE_CTIA) + alc_headset_mode_ctia(codec); + else if (spec->current_headset_type == ALC_HEADSET_TYPE_OMTP) + alc_headset_mode_omtp(codec); + spec->gen.hp_jack_present = true; + break; + case ALC_HEADSET_MODE_MIC: + alc_headset_mode_mic_in(codec, hp_pin, spec->headphone_mic_pin); + spec->gen.hp_jack_present = false; + break; + case ALC_HEADSET_MODE_HEADPHONE: + alc_headset_mode_default(codec); + spec->gen.hp_jack_present = true; + break; + } + if (new_headset_mode != ALC_HEADSET_MODE_MIC) { + snd_hda_set_pin_ctl_cache(codec, hp_pin, + AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN); + if (spec->headphone_mic_pin && spec->headphone_mic_pin != hp_pin) + snd_hda_set_pin_ctl_cache(codec, spec->headphone_mic_pin, + PIN_VREFHIZ); + } + spec->current_headset_mode = new_headset_mode; + + snd_hda_gen_update_outputs(codec); +} + +static void alc_update_headset_mode_hook(struct hda_codec *codec, + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + alc_update_headset_mode(codec); +} + +static void alc_update_headset_jack_cb(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + snd_hda_gen_hp_automute(codec, jack); + alc_update_headset_mode(codec); +} + +static void alc_probe_headset_mode(struct hda_codec *codec) +{ + int i; + struct alc_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->gen.autocfg; + + /* Find mic pins */ + for (i = 0; i < cfg->num_inputs; i++) { + if (cfg->inputs[i].is_headset_mic && !spec->headset_mic_pin) + spec->headset_mic_pin = cfg->inputs[i].pin; + if (cfg->inputs[i].is_headphone_mic && !spec->headphone_mic_pin) + spec->headphone_mic_pin = cfg->inputs[i].pin; + } + + WARN_ON(spec->gen.cap_sync_hook); + spec->gen.cap_sync_hook = alc_update_headset_mode_hook; + spec->gen.automute_hook = alc_update_headset_mode; + spec->gen.hp_automute_hook = alc_update_headset_jack_cb; +} + +static void alc_fixup_headset_mode(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + spec->parse_flags |= HDA_PINCFG_HEADSET_MIC | HDA_PINCFG_HEADPHONE_MIC; + break; + case HDA_FIXUP_ACT_PROBE: + alc_probe_headset_mode(codec); + break; + case HDA_FIXUP_ACT_INIT: + if (is_s3_resume(codec) || is_s4_resume(codec)) { + spec->current_headset_mode = ALC_HEADSET_MODE_UNKNOWN; + spec->current_headset_type = ALC_HEADSET_TYPE_UNKNOWN; + } + alc_update_headset_mode(codec); + break; + } +} + +static void alc_fixup_headset_mode_no_hp_mic(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + struct alc_spec *spec = codec->spec; + spec->parse_flags |= HDA_PINCFG_HEADSET_MIC; + } + else + alc_fixup_headset_mode(codec, fix, action); +} + +static void alc255_set_default_jack_type(struct hda_codec *codec) +{ + /* Set to iphone type */ + static const struct coef_fw alc255fw[] = { + WRITE_COEF(0x1b, 0x880b), + WRITE_COEF(0x45, 0xd089), + WRITE_COEF(0x1b, 0x080b), + WRITE_COEF(0x46, 0x0004), + WRITE_COEF(0x1b, 0x0c0b), + {} + }; + static const struct coef_fw alc256fw[] = { + WRITE_COEF(0x1b, 0x884b), + WRITE_COEF(0x45, 0xd089), + WRITE_COEF(0x1b, 0x084b), + WRITE_COEF(0x46, 0x0004), + WRITE_COEF(0x1b, 0x0c4b), + {} + }; + switch (codec->core.vendor_id) { + case 0x10ec0255: + alc_process_coef_fw(codec, alc255fw); + break; + case 0x10ec0230: + case 0x10ec0236: + case 0x10ec0256: + case 0x19e58326: + alc_process_coef_fw(codec, alc256fw); + break; + } + msleep(30); +} + +static void alc_fixup_headset_mode_alc255(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + alc255_set_default_jack_type(codec); + } + alc_fixup_headset_mode(codec, fix, action); +} + +static void alc_fixup_headset_mode_alc255_no_hp_mic(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + struct alc_spec *spec = codec->spec; + spec->parse_flags |= HDA_PINCFG_HEADSET_MIC; + alc255_set_default_jack_type(codec); + } + else + alc_fixup_headset_mode(codec, fix, action); +} + +static void alc288_update_headset_jack_cb(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + struct alc_spec *spec = codec->spec; + + alc_update_headset_jack_cb(codec, jack); + /* Headset Mic enable or disable, only for Dell Dino */ + alc_update_gpio_data(codec, 0x40, spec->gen.hp_jack_present); +} + +static void alc_fixup_headset_mode_dell_alc288(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc_fixup_headset_mode(codec, fix, action); + if (action == HDA_FIXUP_ACT_PROBE) { + struct alc_spec *spec = codec->spec; + /* toggled via hp_automute_hook */ + spec->gpio_mask |= 0x40; + spec->gpio_dir |= 0x40; + spec->gen.hp_automute_hook = alc288_update_headset_jack_cb; + } +} + +static void alc_fixup_auto_mute_via_amp(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + struct alc_spec *spec = codec->spec; + spec->gen.auto_mute_via_amp = 1; + } +} + +static void alc_fixup_no_shutup(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + struct alc_spec *spec = codec->spec; + spec->no_shutup_pins = 1; + } +} + +static void alc_fixup_disable_aamix(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + struct alc_spec *spec = codec->spec; + /* Disable AA-loopback as it causes white noise */ + spec->gen.mixer_nid = 0; + } +} + +/* fixup for Thinkpad docks: add dock pins, avoid HP parser fixup */ +static void alc_fixup_tpt440_dock(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + static const struct hda_pintbl pincfgs[] = { + { 0x16, 0x21211010 }, /* dock headphone */ + { 0x19, 0x21a11010 }, /* dock mic */ + { } + }; + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->parse_flags = HDA_PINCFG_NO_HP_FIXUP; + codec->power_save_node = 0; /* avoid click noises */ + snd_hda_apply_pincfgs(codec, pincfgs); + } +} + +static void alc_fixup_tpt470_dock(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + static const struct hda_pintbl pincfgs[] = { + { 0x17, 0x21211010 }, /* dock headphone */ + { 0x19, 0x21a11010 }, /* dock mic */ + { } + }; + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->parse_flags = HDA_PINCFG_NO_HP_FIXUP; + snd_hda_apply_pincfgs(codec, pincfgs); + } else if (action == HDA_FIXUP_ACT_INIT) { + /* Enable DOCK device */ + snd_hda_codec_write(codec, 0x17, 0, + AC_VERB_SET_CONFIG_DEFAULT_BYTES_3, 0); + /* Enable DOCK device */ + snd_hda_codec_write(codec, 0x19, 0, + AC_VERB_SET_CONFIG_DEFAULT_BYTES_3, 0); + } +} + +static void alc_fixup_tpt470_dacs(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + /* Assure the speaker pin to be coupled with DAC NID 0x03; otherwise + * the speaker output becomes too low by some reason on Thinkpads with + * ALC298 codec + */ + static const hda_nid_t preferred_pairs[] = { + 0x14, 0x03, 0x17, 0x02, 0x21, 0x02, + 0 + }; + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) + spec->gen.preferred_dacs = preferred_pairs; +} + +static void alc295_fixup_asus_dacs(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + static const hda_nid_t preferred_pairs[] = { + 0x17, 0x02, 0x21, 0x03, 0 + }; + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) + spec->gen.preferred_dacs = preferred_pairs; +} + +static void alc_shutup_dell_xps13(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int hp_pin = alc_get_hp_pin(spec); + + /* Prevent pop noises when headphones are plugged in */ + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + msleep(20); +} + +static void alc_fixup_dell_xps13(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + struct hda_input_mux *imux = &spec->gen.input_mux; + int i; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + /* mic pin 0x19 must be initialized with Vref Hi-Z, otherwise + * it causes a click noise at start up + */ + snd_hda_codec_set_pin_target(codec, 0x19, PIN_VREFHIZ); + spec->shutup = alc_shutup_dell_xps13; + break; + case HDA_FIXUP_ACT_PROBE: + /* Make the internal mic the default input source. */ + for (i = 0; i < imux->num_items; i++) { + if (spec->gen.imux_pins[i] == 0x12) { + spec->gen.cur_mux[0] = i; + break; + } + } + break; + } +} + +static void alc_fixup_headset_mode_alc662(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->parse_flags |= HDA_PINCFG_HEADSET_MIC; + spec->gen.hp_mic = 1; /* Mic-in is same pin as headphone */ + + /* Disable boost for mic-in permanently. (This code is only called + from quirks that guarantee that the headphone is at NID 0x1b.) */ + snd_hda_codec_write(codec, 0x1b, 0, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000); + snd_hda_override_wcaps(codec, 0x1b, get_wcaps(codec, 0x1b) & ~AC_WCAP_IN_AMP); + } else + alc_fixup_headset_mode(codec, fix, action); +} + +static void alc_fixup_headset_mode_alc668(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + alc_write_coef_idx(codec, 0xc4, 0x8000); + alc_update_coef_idx(codec, 0xc2, ~0xfe, 0); + snd_hda_set_pin_ctl_cache(codec, 0x18, 0); + } + alc_fixup_headset_mode(codec, fix, action); +} + +/* Returns the nid of the external mic input pin, or 0 if it cannot be found. */ +static int find_ext_mic_pin(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->gen.autocfg; + hda_nid_t nid; + unsigned int defcfg; + int i; + + for (i = 0; i < cfg->num_inputs; i++) { + if (cfg->inputs[i].type != AUTO_PIN_MIC) + continue; + nid = cfg->inputs[i].pin; + defcfg = snd_hda_codec_get_pincfg(codec, nid); + if (snd_hda_get_input_pin_attr(defcfg) == INPUT_PIN_ATTR_INT) + continue; + return nid; + } + + return 0; +} + +static void alc271_hp_gate_mic_jack(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PROBE) { + int mic_pin = find_ext_mic_pin(codec); + int hp_pin = alc_get_hp_pin(spec); + + if (snd_BUG_ON(!mic_pin || !hp_pin)) + return; + snd_hda_jack_set_gating_jack(codec, mic_pin, hp_pin); + } +} + +static void alc269_fixup_limit_int_mic_boost(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + struct alc_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->gen.autocfg; + int i; + + /* The mic boosts on level 2 and 3 are too noisy + on the internal mic input. + Therefore limit the boost to 0 or 1. */ + + if (action != HDA_FIXUP_ACT_PROBE) + return; + + for (i = 0; i < cfg->num_inputs; i++) { + hda_nid_t nid = cfg->inputs[i].pin; + unsigned int defcfg; + if (cfg->inputs[i].type != AUTO_PIN_MIC) + continue; + defcfg = snd_hda_codec_get_pincfg(codec, nid); + if (snd_hda_get_input_pin_attr(defcfg) != INPUT_PIN_ATTR_INT) + continue; + + snd_hda_override_amp_caps(codec, nid, HDA_INPUT, + (0x00 << AC_AMPCAP_OFFSET_SHIFT) | + (0x01 << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x2f << AC_AMPCAP_STEP_SIZE_SHIFT) | + (0 << AC_AMPCAP_MUTE_SHIFT)); + } +} + +static void alc283_hp_automute_hook(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + struct alc_spec *spec = codec->spec; + int vref; + + msleep(200); + snd_hda_gen_hp_automute(codec, jack); + + vref = spec->gen.hp_jack_present ? PIN_VREF80 : 0; + + msleep(600); + snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + vref); +} + +static void alc283_fixup_chromebook(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + snd_hda_override_wcaps(codec, 0x03, 0); + /* Disable AA-loopback as it causes white noise */ + spec->gen.mixer_nid = 0; + break; + case HDA_FIXUP_ACT_INIT: + /* MIC2-VREF control */ + /* Set to manual mode */ + alc_update_coef_idx(codec, 0x06, 0x000c, 0); + /* Enable Line1 input control by verb */ + alc_update_coef_idx(codec, 0x1a, 0, 1 << 4); + break; + } +} + +static void alc283_fixup_sense_combo_jack(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + spec->gen.hp_automute_hook = alc283_hp_automute_hook; + break; + case HDA_FIXUP_ACT_INIT: + /* MIC2-VREF control */ + /* Set to manual mode */ + alc_update_coef_idx(codec, 0x06, 0x000c, 0); + break; + } +} + +/* mute tablet speaker pin (0x14) via dock plugging in addition */ +static void asus_tx300_automute(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + snd_hda_gen_update_outputs(codec); + if (snd_hda_jack_detect(codec, 0x1b)) + spec->gen.mute_bits |= (1ULL << 0x14); +} + +static void alc282_fixup_asus_tx300(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + static const struct hda_pintbl dock_pins[] = { + { 0x1b, 0x21114000 }, /* dock speaker pin */ + {} + }; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + spec->init_amp = ALC_INIT_DEFAULT; + /* TX300 needs to set up GPIO2 for the speaker amp */ + alc_setup_gpio(codec, 0x04); + snd_hda_apply_pincfgs(codec, dock_pins); + spec->gen.auto_mute_via_amp = 1; + spec->gen.automute_hook = asus_tx300_automute; + snd_hda_jack_detect_enable_callback(codec, 0x1b, + snd_hda_gen_hp_automute); + break; + case HDA_FIXUP_ACT_PROBE: + spec->init_amp = ALC_INIT_DEFAULT; + break; + case HDA_FIXUP_ACT_BUILD: + /* this is a bit tricky; give more sane names for the main + * (tablet) speaker and the dock speaker, respectively + */ + rename_ctl(codec, "Speaker Playback Switch", + "Dock Speaker Playback Switch"); + rename_ctl(codec, "Bass Speaker Playback Switch", + "Speaker Playback Switch"); + break; + } +} + +static void alc290_fixup_mono_speakers(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + /* DAC node 0x03 is giving mono output. We therefore want to + make sure 0x14 (front speaker) and 0x15 (headphones) use the + stereo DAC, while leaving 0x17 (bass speaker) for node 0x03. */ + static const hda_nid_t conn1[] = { 0x0c }; + snd_hda_override_conn_list(codec, 0x14, ARRAY_SIZE(conn1), conn1); + snd_hda_override_conn_list(codec, 0x15, ARRAY_SIZE(conn1), conn1); + } +} + +static void alc298_fixup_speaker_volume(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + /* The speaker is routed to the Node 0x06 by a mistake, as a result + we can't adjust the speaker's volume since this node does not has + Amp-out capability. we change the speaker's route to: + Node 0x02 (Audio Output) -> Node 0x0c (Audio Mixer) -> Node 0x17 ( + Pin Complex), since Node 0x02 has Amp-out caps, we can adjust + speaker's volume now. */ + + static const hda_nid_t conn1[] = { 0x0c }; + snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn1), conn1); + } +} + +/* disable DAC3 (0x06) selection on NID 0x17 as it has no volume amp control */ +static void alc295_fixup_disable_dac3(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + static const hda_nid_t conn[] = { 0x02, 0x03 }; + snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn), conn); + } +} + +/* force NID 0x17 (Bass Speaker) to DAC1 to share it with the main speaker */ +static void alc285_fixup_speaker2_to_dac1(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + static const hda_nid_t conn[] = { 0x02 }; + snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn), conn); + } +} + +/* Hook to update amp GPIO4 for automute */ +static void alc280_hp_gpio4_automute_hook(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + struct alc_spec *spec = codec->spec; + + snd_hda_gen_hp_automute(codec, jack); + /* mute_led_polarity is set to 0, so we pass inverted value here */ + alc_update_gpio_led(codec, 0x10, spec->mute_led_polarity, + !spec->gen.hp_jack_present); +} + +/* Manage GPIOs for HP EliteBook Folio 9480m. + * + * GPIO4 is the headphone amplifier power control + * GPIO3 is the audio output mute indicator LED + */ + +static void alc280_fixup_hp_9480m(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + struct alc_spec *spec = codec->spec; + + alc_fixup_hp_gpio_led(codec, action, 0x08, 0); + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + /* amp at GPIO4; toggled via alc280_hp_gpio4_automute_hook() */ + spec->gpio_mask |= 0x10; + spec->gpio_dir |= 0x10; + spec->gen.hp_automute_hook = alc280_hp_gpio4_automute_hook; + } +} + +static void alc275_fixup_gpio4_off(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->gpio_mask |= 0x04; + spec->gpio_dir |= 0x04; + /* set data bit low */ + } +} + +/* Quirk for Thinkpad X1 7th and 8th Gen + * The following fixed routing needed + * DAC1 (NID 0x02) -> Speaker (NID 0x14); some eq applied secretly + * DAC2 (NID 0x03) -> Bass (NID 0x17) & Headphone (NID 0x21); sharing a DAC + * DAC3 (NID 0x06) -> Unused, due to the lack of volume amp + */ +static void alc285_fixup_thinkpad_x1_gen7(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + static const hda_nid_t conn[] = { 0x02, 0x03 }; /* exclude 0x06 */ + static const hda_nid_t preferred_pairs[] = { + 0x14, 0x02, 0x17, 0x03, 0x21, 0x03, 0 + }; + struct alc_spec *spec = codec->spec; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn), conn); + spec->gen.preferred_dacs = preferred_pairs; + break; + case HDA_FIXUP_ACT_BUILD: + /* The generic parser creates somewhat unintuitive volume ctls + * with the fixed routing above, and the shared DAC2 may be + * confusing for PA. + * Rename those to unique names so that PA doesn't touch them + * and use only Master volume. + */ + rename_ctl(codec, "Front Playback Volume", "DAC1 Playback Volume"); + rename_ctl(codec, "Bass Speaker Playback Volume", "DAC2 Playback Volume"); + break; + } +} + +static void alc233_alc662_fixup_lenovo_dual_codecs(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + alc_fixup_dual_codecs(codec, fix, action); + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + /* override card longname to provide a unique UCM profile */ + strcpy(codec->card->longname, "HDAudio-Lenovo-DualCodecs"); + break; + case HDA_FIXUP_ACT_BUILD: + /* rename Capture controls depending on the codec */ + rename_ctl(codec, "Capture Volume", + codec->addr == 0 ? + "Rear-Panel Capture Volume" : + "Front-Panel Capture Volume"); + rename_ctl(codec, "Capture Switch", + codec->addr == 0 ? + "Rear-Panel Capture Switch" : + "Front-Panel Capture Switch"); + break; + } +} + +static void alc225_fixup_s3_pop_noise(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + codec->power_save_node = 1; +} + +/* Forcibly assign NID 0x03 to HP/LO while NID 0x02 to SPK for EQ */ +static void alc274_fixup_bind_dacs(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + static const hda_nid_t preferred_pairs[] = { + 0x21, 0x03, 0x1b, 0x03, 0x16, 0x02, + 0 + }; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + spec->gen.preferred_dacs = preferred_pairs; + spec->gen.auto_mute_via_amp = 1; + codec->power_save_node = 0; +} + +/* avoid DAC 0x06 for bass speaker 0x17; it has no volume control */ +static void alc289_fixup_asus_ga401(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + static const hda_nid_t preferred_pairs[] = { + 0x14, 0x02, 0x17, 0x02, 0x21, 0x03, 0 + }; + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->gen.preferred_dacs = preferred_pairs; + spec->gen.obey_preferred_dacs = 1; + } +} + +/* The DAC of NID 0x3 will introduce click/pop noise on headphones, so invalidate it */ +static void alc285_fixup_invalidate_dacs(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + snd_hda_override_wcaps(codec, 0x03, 0); +} + +static void alc_combo_jack_hp_jd_restart(struct hda_codec *codec) +{ + switch (codec->core.vendor_id) { + case 0x10ec0274: + case 0x10ec0294: + case 0x10ec0225: + case 0x10ec0295: + case 0x10ec0299: + alc_update_coef_idx(codec, 0x4a, 0x8000, 1 << 15); /* Reset HP JD */ + alc_update_coef_idx(codec, 0x4a, 0x8000, 0 << 15); + break; + case 0x10ec0230: + case 0x10ec0235: + case 0x10ec0236: + case 0x10ec0255: + case 0x10ec0256: + case 0x10ec0257: + case 0x19e58326: + alc_update_coef_idx(codec, 0x1b, 0x8000, 1 << 15); /* Reset HP JD */ + alc_update_coef_idx(codec, 0x1b, 0x8000, 0 << 15); + break; + } +} + +static void alc295_fixup_chromebook(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + spec->ultra_low_power = true; + break; + case HDA_FIXUP_ACT_INIT: + alc_combo_jack_hp_jd_restart(codec); + break; + } +} + +static void alc_fixup_disable_mic_vref(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) + snd_hda_codec_set_pin_target(codec, 0x19, PIN_VREFHIZ); +} + + +static void alc294_gx502_toggle_output(struct hda_codec *codec, + struct hda_jack_callback *cb) +{ + /* The Windows driver sets the codec up in a very different way where + * it appears to leave 0x10 = 0x8a20 set. For Linux we need to toggle it + */ + if (snd_hda_jack_detect_state(codec, 0x21) == HDA_JACK_PRESENT) + alc_write_coef_idx(codec, 0x10, 0x8a20); + else + alc_write_coef_idx(codec, 0x10, 0x0a20); +} + +static void alc294_fixup_gx502_hp(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + /* Pin 0x21: headphones/headset mic */ + if (!is_jack_detectable(codec, 0x21)) + return; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + snd_hda_jack_detect_enable_callback(codec, 0x21, + alc294_gx502_toggle_output); + break; + case HDA_FIXUP_ACT_INIT: + /* Make sure to start in a correct state, i.e. if + * headphones have been plugged in before powering up the system + */ + alc294_gx502_toggle_output(codec, NULL); + break; + } +} + +static void alc294_gu502_toggle_output(struct hda_codec *codec, + struct hda_jack_callback *cb) +{ + /* Windows sets 0x10 to 0x8420 for Node 0x20 which is + * responsible from changes between speakers and headphones + */ + if (snd_hda_jack_detect_state(codec, 0x21) == HDA_JACK_PRESENT) + alc_write_coef_idx(codec, 0x10, 0x8420); + else + alc_write_coef_idx(codec, 0x10, 0x0a20); +} + +static void alc294_fixup_gu502_hp(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (!is_jack_detectable(codec, 0x21)) + return; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + snd_hda_jack_detect_enable_callback(codec, 0x21, + alc294_gu502_toggle_output); + break; + case HDA_FIXUP_ACT_INIT: + alc294_gu502_toggle_output(codec, NULL); + break; + } +} + +static void alc285_fixup_hp_gpio_amp_init(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_INIT) + return; + + msleep(100); + alc_write_coef_idx(codec, 0x65, 0x0); +} + +static void alc274_fixup_hp_headset_mic(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + switch (action) { + case HDA_FIXUP_ACT_INIT: + alc_combo_jack_hp_jd_restart(codec); + break; + } +} + +static void alc_fixup_no_int_mic(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + /* Mic RING SLEEVE swap for combo jack */ + alc_update_coef_idx(codec, 0x45, 0xf<<12 | 1<<10, 5<<12); + spec->no_internal_mic_pin = true; + break; + case HDA_FIXUP_ACT_INIT: + alc_combo_jack_hp_jd_restart(codec); + break; + } +} + +/* GPIO1 = amplifier on/off + * GPIO3 = mic mute LED + */ +static void alc285_fixup_hp_spectre_x360_eb1(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + static const hda_nid_t conn[] = { 0x02 }; + + struct alc_spec *spec = codec->spec; + static const struct hda_pintbl pincfgs[] = { + { 0x14, 0x90170110 }, /* front/high speakers */ + { 0x17, 0x90170130 }, /* back/bass speakers */ + { } + }; + + //enable micmute led + alc_fixup_hp_gpio_led(codec, action, 0x00, 0x04); + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + spec->micmute_led_polarity = 1; + /* needed for amp of back speakers */ + spec->gpio_mask |= 0x01; + spec->gpio_dir |= 0x01; + snd_hda_apply_pincfgs(codec, pincfgs); + /* share DAC to have unified volume control */ + snd_hda_override_conn_list(codec, 0x14, ARRAY_SIZE(conn), conn); + snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn), conn); + break; + case HDA_FIXUP_ACT_INIT: + /* need to toggle GPIO to enable the amp of back speakers */ + alc_update_gpio_data(codec, 0x01, true); + msleep(100); + alc_update_gpio_data(codec, 0x01, false); + break; + } +} + +static void alc285_fixup_hp_spectre_x360(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + static const hda_nid_t conn[] = { 0x02 }; + static const struct hda_pintbl pincfgs[] = { + { 0x14, 0x90170110 }, /* rear speaker */ + { } + }; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + snd_hda_apply_pincfgs(codec, pincfgs); + /* force front speaker to DAC1 */ + snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn), conn); + break; + } +} + +/* for hda_fixup_thinkpad_acpi() */ +#include "thinkpad_helper.c" + +static void alc_fixup_thinkpad_acpi(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc_fixup_no_shutup(codec, fix, action); /* reduce click noise */ + hda_fixup_thinkpad_acpi(codec, fix, action); +} + +/* Fixup for Lenovo Legion 15IMHg05 speaker output on headset removal. */ +static void alc287_fixup_legion_15imhg05_speakers(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + struct alc_spec *spec = codec->spec; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + spec->gen.suppress_auto_mute = 1; + break; + } +} + +static int comp_bind(struct device *dev) +{ + struct hda_codec *cdc = dev_to_hda_codec(dev); + struct alc_spec *spec = cdc->spec; + + return component_bind_all(dev, spec->comps); +} + +static void comp_unbind(struct device *dev) +{ + struct hda_codec *cdc = dev_to_hda_codec(dev); + struct alc_spec *spec = cdc->spec; + + component_unbind_all(dev, spec->comps); +} + +static const struct component_master_ops comp_master_ops = { + .bind = comp_bind, + .unbind = comp_unbind, +}; + +static void comp_generic_playback_hook(struct hda_pcm_stream *hinfo, struct hda_codec *cdc, + struct snd_pcm_substream *sub, int action) +{ + struct alc_spec *spec = cdc->spec; + int i; + + for (i = 0; i < HDA_MAX_COMPONENTS; i++) { + if (spec->comps[i].dev) + spec->comps[i].playback_hook(spec->comps[i].dev, action); + } +} + +struct cs35l41_dev_name { + const char *bus; + const char *hid; + int index; +}; + +/* match the device name in a slightly relaxed manner */ +static int comp_match_cs35l41_dev_name(struct device *dev, void *data) +{ + struct cs35l41_dev_name *p = data; + const char *d = dev_name(dev); + int n = strlen(p->bus); + char tmp[32]; + + /* check the bus name */ + if (strncmp(d, p->bus, n)) + return 0; + /* skip the bus number */ + if (isdigit(d[n])) + n++; + /* the rest must be exact matching */ + snprintf(tmp, sizeof(tmp), "-%s:00-cs35l41-hda.%d", p->hid, p->index); + return !strcmp(d + n, tmp); +} + +static void cs35l41_generic_fixup(struct hda_codec *cdc, int action, const char *bus, + const char *hid, int count) +{ + struct device *dev = hda_codec_dev(cdc); + struct alc_spec *spec = cdc->spec; + struct cs35l41_dev_name *rec; + int ret, i; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + for (i = 0; i < count; i++) { + rec = devm_kmalloc(dev, sizeof(*rec), GFP_KERNEL); + if (!rec) + return; + rec->bus = bus; + rec->hid = hid; + rec->index = i; + spec->comps[i].codec = cdc; + component_match_add(dev, &spec->match, + comp_match_cs35l41_dev_name, rec); + } + ret = component_master_add_with_match(dev, &comp_master_ops, spec->match); + if (ret) + codec_err(cdc, "Fail to register component aggregator %d\n", ret); + else + spec->gen.pcm_playback_hook = comp_generic_playback_hook; + break; + } +} + +static void cs35l41_fixup_i2c_two(struct hda_codec *cdc, const struct hda_fixup *fix, int action) +{ + cs35l41_generic_fixup(cdc, action, "i2c", "CSC3551", 2); +} + +static void cs35l41_fixup_spi_two(struct hda_codec *codec, const struct hda_fixup *fix, int action) +{ + cs35l41_generic_fixup(codec, action, "spi", "CSC3551", 2); +} + +static void cs35l41_fixup_spi_four(struct hda_codec *codec, const struct hda_fixup *fix, int action) +{ + cs35l41_generic_fixup(codec, action, "spi", "CSC3551", 4); +} + +static void alc287_fixup_legion_16achg6_speakers(struct hda_codec *cdc, const struct hda_fixup *fix, + int action) +{ + cs35l41_generic_fixup(cdc, action, "i2c", "CLSA0100", 2); +} + +static void alc287_fixup_legion_16ithg6_speakers(struct hda_codec *cdc, const struct hda_fixup *fix, + int action) +{ + cs35l41_generic_fixup(cdc, action, "i2c", "CLSA0101", 2); +} + +/* for alc295_fixup_hp_top_speakers */ +#include "hp_x360_helper.c" + +/* for alc285_fixup_ideapad_s740_coef() */ +#include "ideapad_s740_helper.c" + +static const struct coef_fw alc256_fixup_set_coef_defaults_coefs[] = { + WRITE_COEF(0x10, 0x0020), WRITE_COEF(0x24, 0x0000), + WRITE_COEF(0x26, 0x0000), WRITE_COEF(0x29, 0x3000), + WRITE_COEF(0x37, 0xfe05), WRITE_COEF(0x45, 0x5089), + {} +}; + +static void alc256_fixup_set_coef_defaults(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + /* + * A certain other OS sets these coeffs to different values. On at least + * one TongFang barebone these settings might survive even a cold + * reboot. So to restore a clean slate the values are explicitly reset + * to default here. Without this, the external microphone is always in a + * plugged-in state, while the internal microphone is always in an + * unplugged state, breaking the ability to use the internal microphone. + */ + alc_process_coef_fw(codec, alc256_fixup_set_coef_defaults_coefs); +} + +static const struct coef_fw alc233_fixup_no_audio_jack_coefs[] = { + WRITE_COEF(0x1a, 0x9003), WRITE_COEF(0x1b, 0x0e2b), WRITE_COEF(0x37, 0xfe06), + WRITE_COEF(0x38, 0x4981), WRITE_COEF(0x45, 0xd489), WRITE_COEF(0x46, 0x0074), + WRITE_COEF(0x49, 0x0149), + {} +}; + +static void alc233_fixup_no_audio_jack(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + /* + * The audio jack input and output is not detected on the ASRock NUC Box + * 1100 series when cold booting without this fix. Warm rebooting from a + * certain other OS makes the audio functional, as COEF settings are + * preserved in this case. This fix sets these altered COEF values as + * the default. + */ + alc_process_coef_fw(codec, alc233_fixup_no_audio_jack_coefs); +} + +static void alc256_fixup_mic_no_presence_and_resume(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + /* + * The Clevo NJ51CU comes either with the ALC293 or the ALC256 codec, + * but uses the 0x8686 subproduct id in both cases. The ALC256 codec + * needs an additional quirk for sound working after suspend and resume. + */ + if (codec->core.vendor_id == 0x10ec0256) { + alc_update_coef_idx(codec, 0x10, 1<<9, 0); + snd_hda_codec_set_pincfg(codec, 0x19, 0x04a11120); + } else { + snd_hda_codec_set_pincfg(codec, 0x1a, 0x04a1113c); + } +} + +static void alc_fixup_dell4_mic_no_presence_quiet(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + struct alc_spec *spec = codec->spec; + struct hda_input_mux *imux = &spec->gen.input_mux; + int i; + + alc269_fixup_limit_int_mic_boost(codec, fix, action); + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + /** + * Set the vref of pin 0x19 (Headset Mic) and pin 0x1b (Headphone Mic) + * to Hi-Z to avoid pop noises at startup and when plugging and + * unplugging headphones. + */ + snd_hda_codec_set_pin_target(codec, 0x19, PIN_VREFHIZ); + snd_hda_codec_set_pin_target(codec, 0x1b, PIN_VREFHIZ); + break; + case HDA_FIXUP_ACT_PROBE: + /** + * Make the internal mic (0x12) the default input source to + * prevent pop noises on cold boot. + */ + for (i = 0; i < imux->num_items; i++) { + if (spec->gen.imux_pins[i] == 0x12) { + spec->gen.cur_mux[0] = i; + break; + } + } + break; + } +} + +static void alc287_fixup_yoga9_14iap7_bass_spk_pin(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + /* + * The Pin Complex 0x17 for the bass speakers is wrongly reported as + * unconnected. + */ + static const struct hda_pintbl pincfgs[] = { + { 0x17, 0x90170121 }, + { } + }; + /* + * Avoid DAC 0x06 and 0x08, as they have no volume controls. + * DAC 0x02 and 0x03 would be fine. + */ + static const hda_nid_t conn[] = { 0x02, 0x03 }; + /* + * Prefer both speakerbar (0x14) and bass speakers (0x17) connected to DAC 0x02. + * Headphones (0x21) are connected to DAC 0x03. + */ + static const hda_nid_t preferred_pairs[] = { + 0x14, 0x02, + 0x17, 0x02, + 0x21, 0x03, + 0 + }; + struct alc_spec *spec = codec->spec; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + snd_hda_apply_pincfgs(codec, pincfgs); + snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn), conn); + spec->gen.preferred_dacs = preferred_pairs; + break; + } +} + +static void alc295_fixup_dell_inspiron_top_speakers(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + static const struct hda_pintbl pincfgs[] = { + { 0x14, 0x90170151 }, + { 0x17, 0x90170150 }, + { } + }; + static const hda_nid_t conn[] = { 0x02, 0x03 }; + static const hda_nid_t preferred_pairs[] = { + 0x14, 0x02, + 0x17, 0x03, + 0x21, 0x02, + 0 + }; + struct alc_spec *spec = codec->spec; + + alc_fixup_no_shutup(codec, fix, action); + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + snd_hda_apply_pincfgs(codec, pincfgs); + snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn), conn); + spec->gen.preferred_dacs = preferred_pairs; + break; + } +} + +/* Forcibly assign NID 0x03 to HP while NID 0x02 to SPK */ +static void alc287_fixup_bind_dacs(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + static const hda_nid_t conn[] = { 0x02, 0x03 }; /* exclude 0x06 */ + static const hda_nid_t preferred_pairs[] = { + 0x17, 0x02, 0x21, 0x03, 0 + }; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn), conn); + spec->gen.preferred_dacs = preferred_pairs; + spec->gen.auto_mute_via_amp = 1; + if (spec->gen.autocfg.speaker_pins[0] != 0x14) { + snd_hda_codec_write_cache(codec, 0x14, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + 0x0); /* Make sure 0x14 was disable */ + } +} +/* Fix none verb table of Headset Mic pin */ +static void alc_fixup_headset_mic(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + static const struct hda_pintbl pincfgs[] = { + { 0x19, 0x03a1103c }, + { } + }; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + snd_hda_apply_pincfgs(codec, pincfgs); + alc_update_coef_idx(codec, 0x45, 0xf<<12 | 1<<10, 5<<12); + spec->parse_flags |= HDA_PINCFG_HEADSET_MIC; + break; + } +} + + +enum { + ALC269_FIXUP_GPIO2, + ALC269_FIXUP_SONY_VAIO, + ALC275_FIXUP_SONY_VAIO_GPIO2, + ALC269_FIXUP_DELL_M101Z, + ALC269_FIXUP_SKU_IGNORE, + ALC269_FIXUP_ASUS_G73JW, + ALC269_FIXUP_ASUS_N7601ZM_PINS, + ALC269_FIXUP_ASUS_N7601ZM, + ALC269_FIXUP_LENOVO_EAPD, + ALC275_FIXUP_SONY_HWEQ, + ALC275_FIXUP_SONY_DISABLE_AAMIX, + ALC271_FIXUP_DMIC, + ALC269_FIXUP_PCM_44K, + ALC269_FIXUP_STEREO_DMIC, + ALC269_FIXUP_HEADSET_MIC, + ALC269_FIXUP_QUANTA_MUTE, + ALC269_FIXUP_LIFEBOOK, + ALC269_FIXUP_LIFEBOOK_EXTMIC, + ALC269_FIXUP_LIFEBOOK_HP_PIN, + ALC269_FIXUP_LIFEBOOK_NO_HP_TO_LINEOUT, + ALC255_FIXUP_LIFEBOOK_U7x7_HEADSET_MIC, + ALC269_FIXUP_AMIC, + ALC269_FIXUP_DMIC, + ALC269VB_FIXUP_AMIC, + ALC269VB_FIXUP_DMIC, + ALC269_FIXUP_HP_MUTE_LED, + ALC269_FIXUP_HP_MUTE_LED_MIC1, + ALC269_FIXUP_HP_MUTE_LED_MIC2, + ALC269_FIXUP_HP_MUTE_LED_MIC3, + ALC269_FIXUP_HP_GPIO_LED, + ALC269_FIXUP_HP_GPIO_MIC1_LED, + ALC269_FIXUP_HP_LINE1_MIC1_LED, + ALC269_FIXUP_INV_DMIC, + ALC269_FIXUP_LENOVO_DOCK, + ALC269_FIXUP_LENOVO_DOCK_LIMIT_BOOST, + ALC269_FIXUP_NO_SHUTUP, + ALC286_FIXUP_SONY_MIC_NO_PRESENCE, + ALC269_FIXUP_PINCFG_NO_HP_TO_LINEOUT, + ALC269_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC269_FIXUP_DELL2_MIC_NO_PRESENCE, + ALC269_FIXUP_DELL3_MIC_NO_PRESENCE, + ALC269_FIXUP_DELL4_MIC_NO_PRESENCE, + ALC269_FIXUP_DELL4_MIC_NO_PRESENCE_QUIET, + ALC269_FIXUP_HEADSET_MODE, + ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC, + ALC269_FIXUP_ASPIRE_HEADSET_MIC, + ALC269_FIXUP_ASUS_X101_FUNC, + ALC269_FIXUP_ASUS_X101_VERB, + ALC269_FIXUP_ASUS_X101, + ALC271_FIXUP_AMIC_MIC2, + ALC271_FIXUP_HP_GATE_MIC_JACK, + ALC271_FIXUP_HP_GATE_MIC_JACK_E1_572, + ALC269_FIXUP_ACER_AC700, + ALC269_FIXUP_LIMIT_INT_MIC_BOOST, + ALC269VB_FIXUP_ASUS_ZENBOOK, + ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A, + ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE, + ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED, + ALC269VB_FIXUP_ORDISSIMO_EVE2, + ALC283_FIXUP_CHROME_BOOK, + ALC283_FIXUP_SENSE_COMBO_JACK, + ALC282_FIXUP_ASUS_TX300, + ALC283_FIXUP_INT_MIC, + ALC290_FIXUP_MONO_SPEAKERS, + ALC290_FIXUP_MONO_SPEAKERS_HSJACK, + ALC290_FIXUP_SUBWOOFER, + ALC290_FIXUP_SUBWOOFER_HSJACK, + ALC269_FIXUP_THINKPAD_ACPI, + ALC269_FIXUP_DMIC_THINKPAD_ACPI, + ALC269VB_FIXUP_CHUWI_COREBOOK_XPRO, + ALC255_FIXUP_ACER_MIC_NO_PRESENCE, + ALC255_FIXUP_ASUS_MIC_NO_PRESENCE, + ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC255_FIXUP_DELL2_MIC_NO_PRESENCE, + ALC255_FIXUP_HEADSET_MODE, + ALC255_FIXUP_HEADSET_MODE_NO_HP_MIC, + ALC293_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC292_FIXUP_TPT440_DOCK, + ALC292_FIXUP_TPT440, + ALC283_FIXUP_HEADSET_MIC, + ALC255_FIXUP_MIC_MUTE_LED, + ALC282_FIXUP_ASPIRE_V5_PINS, + ALC269VB_FIXUP_ASPIRE_E1_COEF, + ALC280_FIXUP_HP_GPIO4, + ALC286_FIXUP_HP_GPIO_LED, + ALC280_FIXUP_HP_GPIO2_MIC_HOTKEY, + ALC280_FIXUP_HP_DOCK_PINS, + ALC269_FIXUP_HP_DOCK_GPIO_MIC1_LED, + ALC280_FIXUP_HP_9480M, + ALC245_FIXUP_HP_X360_AMP, + ALC285_FIXUP_HP_SPECTRE_X360_EB1, + ALC288_FIXUP_DELL_HEADSET_MODE, + ALC288_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC288_FIXUP_DELL_XPS_13, + ALC288_FIXUP_DISABLE_AAMIX, + ALC292_FIXUP_DELL_E7X_AAMIX, + ALC292_FIXUP_DELL_E7X, + ALC292_FIXUP_DISABLE_AAMIX, + ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK, + ALC298_FIXUP_ALIENWARE_MIC_NO_PRESENCE, + ALC298_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC298_FIXUP_DELL_AIO_MIC_NO_PRESENCE, + ALC275_FIXUP_DELL_XPS, + ALC293_FIXUP_LENOVO_SPK_NOISE, + ALC233_FIXUP_LENOVO_LINE2_MIC_HOTKEY, + ALC255_FIXUP_DELL_SPK_NOISE, + ALC225_FIXUP_DISABLE_MIC_VREF, + ALC225_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC295_FIXUP_DISABLE_DAC3, + ALC285_FIXUP_SPEAKER2_TO_DAC1, + ALC285_FIXUP_ASUS_SPEAKER2_TO_DAC1, + ALC285_FIXUP_ASUS_HEADSET_MIC, + ALC285_FIXUP_ASUS_I2C_SPEAKER2_TO_DAC1, + ALC285_FIXUP_ASUS_I2C_HEADSET_MIC, + ALC280_FIXUP_HP_HEADSET_MIC, + ALC221_FIXUP_HP_FRONT_MIC, + ALC292_FIXUP_TPT460, + ALC298_FIXUP_SPK_VOLUME, + ALC298_FIXUP_LENOVO_SPK_VOLUME, + ALC256_FIXUP_DELL_INSPIRON_7559_SUBWOOFER, + ALC269_FIXUP_ATIV_BOOK_8, + ALC221_FIXUP_HP_288PRO_MIC_NO_PRESENCE, + ALC221_FIXUP_HP_MIC_NO_PRESENCE, + ALC256_FIXUP_ASUS_HEADSET_MODE, + ALC256_FIXUP_ASUS_MIC, + ALC256_FIXUP_ASUS_AIO_GPIO2, + ALC233_FIXUP_ASUS_MIC_NO_PRESENCE, + ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE, + ALC233_FIXUP_LENOVO_MULTI_CODECS, + ALC233_FIXUP_ACER_HEADSET_MIC, + ALC294_FIXUP_LENOVO_MIC_LOCATION, + ALC225_FIXUP_DELL_WYSE_MIC_NO_PRESENCE, + ALC225_FIXUP_S3_POP_NOISE, + ALC700_FIXUP_INTEL_REFERENCE, + ALC274_FIXUP_DELL_BIND_DACS, + ALC274_FIXUP_DELL_AIO_LINEOUT_VERB, + ALC298_FIXUP_TPT470_DOCK_FIX, + ALC298_FIXUP_TPT470_DOCK, + ALC255_FIXUP_DUMMY_LINEOUT_VERB, + ALC255_FIXUP_DELL_HEADSET_MIC, + ALC256_FIXUP_HUAWEI_MACH_WX9_PINS, + ALC298_FIXUP_HUAWEI_MBX_STEREO, + ALC295_FIXUP_HP_X360, + ALC221_FIXUP_HP_HEADSET_MIC, + ALC285_FIXUP_LENOVO_HEADPHONE_NOISE, + ALC295_FIXUP_HP_AUTO_MUTE, + ALC286_FIXUP_ACER_AIO_MIC_NO_PRESENCE, + ALC294_FIXUP_ASUS_MIC, + ALC294_FIXUP_ASUS_HEADSET_MIC, + ALC294_FIXUP_ASUS_SPK, + ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE, + ALC285_FIXUP_LENOVO_PC_BEEP_IN_NOISE, + ALC255_FIXUP_ACER_HEADSET_MIC, + ALC295_FIXUP_CHROME_BOOK, + ALC225_FIXUP_HEADSET_JACK, + ALC225_FIXUP_DELL_WYSE_AIO_MIC_NO_PRESENCE, + ALC225_FIXUP_WYSE_AUTO_MUTE, + ALC225_FIXUP_WYSE_DISABLE_MIC_VREF, + ALC286_FIXUP_ACER_AIO_HEADSET_MIC, + ALC256_FIXUP_ASUS_HEADSET_MIC, + ALC256_FIXUP_ASUS_MIC_NO_PRESENCE, + ALC299_FIXUP_PREDATOR_SPK, + ALC256_FIXUP_MEDION_HEADSET_NO_PRESENCE, + ALC289_FIXUP_DELL_SPK2, + ALC289_FIXUP_DUAL_SPK, + ALC294_FIXUP_SPK2_TO_DAC1, + ALC294_FIXUP_ASUS_DUAL_SPK, + ALC285_FIXUP_THINKPAD_X1_GEN7, + ALC285_FIXUP_THINKPAD_HEADSET_JACK, + ALC294_FIXUP_ASUS_ALLY, + ALC294_FIXUP_ASUS_ALLY_PINS, + ALC294_FIXUP_ASUS_ALLY_VERBS, + ALC294_FIXUP_ASUS_ALLY_SPEAKER, + ALC294_FIXUP_ASUS_HPE, + ALC294_FIXUP_ASUS_COEF_1B, + ALC294_FIXUP_ASUS_GX502_HP, + ALC294_FIXUP_ASUS_GX502_PINS, + ALC294_FIXUP_ASUS_GX502_VERBS, + ALC294_FIXUP_ASUS_GU502_HP, + ALC294_FIXUP_ASUS_GU502_PINS, + ALC294_FIXUP_ASUS_GU502_VERBS, + ALC294_FIXUP_ASUS_G513_PINS, + ALC285_FIXUP_ASUS_G533Z_PINS, + ALC285_FIXUP_HP_GPIO_LED, + ALC285_FIXUP_HP_MUTE_LED, + ALC285_FIXUP_HP_SPECTRE_X360_MUTE_LED, + ALC236_FIXUP_HP_MUTE_LED_COEFBIT2, + ALC236_FIXUP_HP_GPIO_LED, + ALC236_FIXUP_HP_MUTE_LED, + ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF, + ALC298_FIXUP_SAMSUNG_AMP, + ALC298_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, + ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, + ALC295_FIXUP_ASUS_MIC_NO_PRESENCE, + ALC269VC_FIXUP_ACER_VCOPPERBOX_PINS, + ALC269VC_FIXUP_ACER_HEADSET_MIC, + ALC269VC_FIXUP_ACER_MIC_NO_PRESENCE, + ALC289_FIXUP_ASUS_GA401, + ALC289_FIXUP_ASUS_GA502, + ALC256_FIXUP_ACER_MIC_NO_PRESENCE, + ALC285_FIXUP_HP_GPIO_AMP_INIT, + ALC269_FIXUP_CZC_B20, + ALC269_FIXUP_CZC_TMI, + ALC269_FIXUP_CZC_L101, + ALC269_FIXUP_LEMOTE_A1802, + ALC269_FIXUP_LEMOTE_A190X, + ALC256_FIXUP_INTEL_NUC8_RUGGED, + ALC233_FIXUP_INTEL_NUC8_DMIC, + ALC233_FIXUP_INTEL_NUC8_BOOST, + ALC256_FIXUP_INTEL_NUC10, + ALC255_FIXUP_XIAOMI_HEADSET_MIC, + ALC274_FIXUP_HP_MIC, + ALC274_FIXUP_HP_HEADSET_MIC, + ALC274_FIXUP_HP_ENVY_GPIO, + ALC256_FIXUP_ASUS_HPE, + ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK, + ALC287_FIXUP_HP_GPIO_LED, + ALC256_FIXUP_HP_HEADSET_MIC, + ALC245_FIXUP_HP_GPIO_LED, + ALC236_FIXUP_DELL_AIO_HEADSET_MIC, + ALC282_FIXUP_ACER_DISABLE_LINEOUT, + ALC255_FIXUP_ACER_LIMIT_INT_MIC_BOOST, + ALC256_FIXUP_ACER_HEADSET_MIC, + ALC285_FIXUP_IDEAPAD_S740_COEF, + ALC285_FIXUP_HP_LIMIT_INT_MIC_BOOST, + ALC295_FIXUP_ASUS_DACS, + ALC295_FIXUP_HP_OMEN, + ALC285_FIXUP_HP_SPECTRE_X360, + ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP, + ALC623_FIXUP_LENOVO_THINKSTATION_P340, + ALC255_FIXUP_ACER_HEADPHONE_AND_MIC, + ALC236_FIXUP_HP_LIMIT_INT_MIC_BOOST, + ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS, + ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE, + ALC287_FIXUP_YOGA7_14ITL_SPEAKERS, + ALC298_FIXUP_LENOVO_C940_DUET7, + ALC287_FIXUP_13S_GEN2_SPEAKERS, + ALC256_FIXUP_SET_COEF_DEFAULTS, + ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE, + ALC233_FIXUP_NO_AUDIO_JACK, + ALC256_FIXUP_MIC_NO_PRESENCE_AND_RESUME, + ALC285_FIXUP_LEGION_Y9000X_SPEAKERS, + ALC285_FIXUP_LEGION_Y9000X_AUTOMUTE, + ALC287_FIXUP_LEGION_16ACHG6, + ALC287_FIXUP_CS35L41_I2C_2, + ALC287_FIXUP_CS35L41_I2C_2_HP_GPIO_LED, + ALC245_FIXUP_CS35L41_SPI_2, + ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED, + ALC245_FIXUP_CS35L41_SPI_4, + ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED, + ALC285_FIXUP_HP_SPEAKERS_MICMUTE_LED, + ALC295_FIXUP_FRAMEWORK_LAPTOP_MIC_NO_PRESENCE, + ALC287_FIXUP_LEGION_16ITHG6, + ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK, + ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN, + ALC295_FIXUP_DELL_INSPIRON_TOP_SPEAKERS, + ALC236_FIXUP_DELL_DUAL_CODECS, + ALC287_FIXUP_CS35L41_I2C_2_THINKPAD_ACPI, + ALC245_FIXUP_HP_MUTE_LED_COEFBIT, + ALC245_FIXUP_HP_X360_MUTE_LEDS, + ALC287_FIXUP_THINKPAD_I2S_SPK, + ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD, + ALC2XX_FIXUP_HEADSET_MIC, +}; + +/* A special fixup for Lenovo C940 and Yoga Duet 7; + * both have the very same PCI SSID, and we need to apply different fixups + * depending on the codec ID + */ +static void alc298_fixup_lenovo_c940_duet7(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + int id; + + if (codec->core.vendor_id == 0x10ec0298) + id = ALC298_FIXUP_LENOVO_SPK_VOLUME; /* C940 */ + else + id = ALC287_FIXUP_YOGA7_14ITL_SPEAKERS; /* Duet 7 */ + __snd_hda_apply_fixup(codec, id, action, 0); +} + +static const struct hda_fixup alc269_fixups[] = { + [ALC269_FIXUP_GPIO2] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_gpio2, + }, + [ALC269_FIXUP_SONY_VAIO] = { + .type = HDA_FIXUP_PINCTLS, + .v.pins = (const struct hda_pintbl[]) { + {0x19, PIN_VREFGRD}, + {} + } + }, + [ALC275_FIXUP_SONY_VAIO_GPIO2] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc275_fixup_gpio4_off, + .chained = true, + .chain_id = ALC269_FIXUP_SONY_VAIO + }, + [ALC269_FIXUP_DELL_M101Z] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* Enables internal speaker */ + {0x20, AC_VERB_SET_COEF_INDEX, 13}, + {0x20, AC_VERB_SET_PROC_COEF, 0x4040}, + {} + } + }, + [ALC269_FIXUP_SKU_IGNORE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_sku_ignore, + }, + [ALC269_FIXUP_ASUS_G73JW] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x17, 0x99130111 }, /* subwoofer */ + { } + } + }, + [ALC269_FIXUP_ASUS_N7601ZM_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x03A11050 }, + { 0x1a, 0x03A11C30 }, + { 0x21, 0x03211420 }, + { } + } + }, + [ALC269_FIXUP_ASUS_N7601ZM] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + {0x20, AC_VERB_SET_COEF_INDEX, 0x62}, + {0x20, AC_VERB_SET_PROC_COEF, 0xa007}, + {0x20, AC_VERB_SET_COEF_INDEX, 0x10}, + {0x20, AC_VERB_SET_PROC_COEF, 0x8420}, + {0x20, AC_VERB_SET_COEF_INDEX, 0x0f}, + {0x20, AC_VERB_SET_PROC_COEF, 0x7774}, + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_ASUS_N7601ZM_PINS, + }, + [ALC269_FIXUP_LENOVO_EAPD] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + {0x14, AC_VERB_SET_EAPD_BTLENABLE, 0}, + {} + } + }, + [ALC275_FIXUP_SONY_HWEQ] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_hweq, + .chained = true, + .chain_id = ALC275_FIXUP_SONY_VAIO_GPIO2 + }, + [ALC275_FIXUP_SONY_DISABLE_AAMIX] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_disable_aamix, + .chained = true, + .chain_id = ALC269_FIXUP_SONY_VAIO + }, + [ALC271_FIXUP_DMIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc271_fixup_dmic, + }, + [ALC269_FIXUP_PCM_44K] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_pcm_44k, + .chained = true, + .chain_id = ALC269_FIXUP_QUANTA_MUTE + }, + [ALC269_FIXUP_STEREO_DMIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_stereo_dmic, + }, + [ALC269_FIXUP_HEADSET_MIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_headset_mic, + }, + [ALC269_FIXUP_QUANTA_MUTE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_quanta_mute, + }, + [ALC269_FIXUP_LIFEBOOK] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1a, 0x2101103f }, /* dock line-out */ + { 0x1b, 0x23a11040 }, /* dock mic-in */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_QUANTA_MUTE + }, + [ALC269_FIXUP_LIFEBOOK_EXTMIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1903c }, /* headset mic, with jack detect */ + { } + }, + }, + [ALC269_FIXUP_LIFEBOOK_HP_PIN] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x21, 0x0221102f }, /* HP out */ + { } + }, + }, + [ALC269_FIXUP_LIFEBOOK_NO_HP_TO_LINEOUT] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_pincfg_no_hp_to_lineout, + }, + [ALC255_FIXUP_LIFEBOOK_U7x7_HEADSET_MIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_pincfg_U7x7_headset_mic, + }, + [ALC269VB_FIXUP_CHUWI_COREBOOK_XPRO] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x18, 0x03a19020 }, /* headset mic */ + { 0x1b, 0x90170150 }, /* speaker */ + { } + }, + }, + [ALC269_FIXUP_AMIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x99130110 }, /* speaker */ + { 0x15, 0x0121401f }, /* HP out */ + { 0x18, 0x01a19c20 }, /* mic */ + { 0x19, 0x99a3092f }, /* int-mic */ + { } + }, + }, + [ALC269_FIXUP_DMIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x12, 0x99a3092f }, /* int-mic */ + { 0x14, 0x99130110 }, /* speaker */ + { 0x15, 0x0121401f }, /* HP out */ + { 0x18, 0x01a19c20 }, /* mic */ + { } + }, + }, + [ALC269VB_FIXUP_AMIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x99130110 }, /* speaker */ + { 0x18, 0x01a19c20 }, /* mic */ + { 0x19, 0x99a3092f }, /* int-mic */ + { 0x21, 0x0121401f }, /* HP out */ + { } + }, + }, + [ALC269VB_FIXUP_DMIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x12, 0x99a3092f }, /* int-mic */ + { 0x14, 0x99130110 }, /* speaker */ + { 0x18, 0x01a19c20 }, /* mic */ + { 0x21, 0x0121401f }, /* HP out */ + { } + }, + }, + [ALC269_FIXUP_HP_MUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_hp_mute_led, + }, + [ALC269_FIXUP_HP_MUTE_LED_MIC1] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_hp_mute_led_mic1, + }, + [ALC269_FIXUP_HP_MUTE_LED_MIC2] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_hp_mute_led_mic2, + }, + [ALC269_FIXUP_HP_MUTE_LED_MIC3] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_hp_mute_led_mic3, + .chained = true, + .chain_id = ALC295_FIXUP_HP_AUTO_MUTE + }, + [ALC269_FIXUP_HP_GPIO_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_hp_gpio_led, + }, + [ALC269_FIXUP_HP_GPIO_MIC1_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_hp_gpio_mic1_led, + }, + [ALC269_FIXUP_HP_LINE1_MIC1_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_hp_line1_mic1_led, + }, + [ALC269_FIXUP_INV_DMIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_inv_dmic, + }, + [ALC269_FIXUP_NO_SHUTUP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_no_shutup, + }, + [ALC269_FIXUP_LENOVO_DOCK] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x23a11040 }, /* dock mic */ + { 0x1b, 0x2121103f }, /* dock headphone */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_PINCFG_NO_HP_TO_LINEOUT + }, + [ALC269_FIXUP_LENOVO_DOCK_LIMIT_BOOST] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_limit_int_mic_boost, + .chained = true, + .chain_id = ALC269_FIXUP_LENOVO_DOCK, + }, + [ALC269_FIXUP_PINCFG_NO_HP_TO_LINEOUT] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_pincfg_no_hp_to_lineout, + .chained = true, + .chain_id = ALC269_FIXUP_THINKPAD_ACPI, + }, + [ALC269_FIXUP_DELL1_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { 0x1a, 0x01a1913d }, /* use as headphone mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE + }, + [ALC269_FIXUP_DELL2_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x16, 0x21014020 }, /* dock line out */ + { 0x19, 0x21a19030 }, /* dock mic */ + { 0x1a, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC + }, + [ALC269_FIXUP_DELL3_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1a, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC + }, + [ALC269_FIXUP_DELL4_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { 0x1b, 0x01a1913d }, /* use as headphone mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE + }, + [ALC269_FIXUP_HEADSET_MODE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode, + .chained = true, + .chain_id = ALC255_FIXUP_MIC_MUTE_LED + }, + [ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode_no_hp_mic, + }, + [ALC269_FIXUP_ASPIRE_HEADSET_MIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* headset mic w/o jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE, + }, + [ALC286_FIXUP_SONY_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x18, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MIC + }, + [ALC256_FIXUP_HUAWEI_MACH_WX9_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + {0x12, 0x90a60130}, + {0x13, 0x40000000}, + {0x14, 0x90170110}, + {0x18, 0x411111f0}, + {0x19, 0x04a11040}, + {0x1a, 0x411111f0}, + {0x1b, 0x90170112}, + {0x1d, 0x40759a05}, + {0x1e, 0x411111f0}, + {0x21, 0x04211020}, + { } + }, + .chained = true, + .chain_id = ALC255_FIXUP_MIC_MUTE_LED + }, + [ALC298_FIXUP_HUAWEI_MBX_STEREO] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc298_fixup_huawei_mbx_stereo, + .chained = true, + .chain_id = ALC255_FIXUP_MIC_MUTE_LED + }, + [ALC269_FIXUP_ASUS_X101_FUNC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_x101_headset_mic, + }, + [ALC269_FIXUP_ASUS_X101_VERB] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + {0x20, AC_VERB_SET_COEF_INDEX, 0x08}, + {0x20, AC_VERB_SET_PROC_COEF, 0x0310}, + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_ASUS_X101_FUNC + }, + [ALC269_FIXUP_ASUS_X101] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x18, 0x04a1182c }, /* Headset mic */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_ASUS_X101_VERB + }, + [ALC271_FIXUP_AMIC_MIC2] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x99130110 }, /* speaker */ + { 0x19, 0x01a19c20 }, /* mic */ + { 0x1b, 0x99a7012f }, /* int-mic */ + { 0x21, 0x0121401f }, /* HP out */ + { } + }, + }, + [ALC271_FIXUP_HP_GATE_MIC_JACK] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc271_hp_gate_mic_jack, + .chained = true, + .chain_id = ALC271_FIXUP_AMIC_MIC2, + }, + [ALC271_FIXUP_HP_GATE_MIC_JACK_E1_572] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_limit_int_mic_boost, + .chained = true, + .chain_id = ALC271_FIXUP_HP_GATE_MIC_JACK, + }, + [ALC269_FIXUP_ACER_AC700] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x12, 0x99a3092f }, /* int-mic */ + { 0x14, 0x99130110 }, /* speaker */ + { 0x18, 0x03a11c20 }, /* mic */ + { 0x1e, 0x0346101e }, /* SPDIF1 */ + { 0x21, 0x0321101f }, /* HP out */ + { } + }, + .chained = true, + .chain_id = ALC271_FIXUP_DMIC, + }, + [ALC269_FIXUP_LIMIT_INT_MIC_BOOST] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_limit_int_mic_boost, + .chained = true, + .chain_id = ALC269_FIXUP_THINKPAD_ACPI, + }, + [ALC269VB_FIXUP_ASUS_ZENBOOK] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_limit_int_mic_boost, + .chained = true, + .chain_id = ALC269VB_FIXUP_DMIC, + }, + [ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* class-D output amp +5dB */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x12 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2800 }, + {} + }, + .chained = true, + .chain_id = ALC269VB_FIXUP_ASUS_ZENBOOK, + }, + [ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x18, 0x01a110f0 }, /* use as headset mic */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MIC + }, + [ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_limit_int_mic_boost, + .chained = true, + .chain_id = ALC269_FIXUP_HP_MUTE_LED_MIC1, + }, + [ALC269VB_FIXUP_ORDISSIMO_EVE2] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x12, 0x99a3092f }, /* int-mic */ + { 0x18, 0x03a11d20 }, /* mic */ + { 0x19, 0x411111f0 }, /* Unused bogus pin */ + { } + }, + }, + [ALC283_FIXUP_CHROME_BOOK] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc283_fixup_chromebook, + }, + [ALC283_FIXUP_SENSE_COMBO_JACK] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc283_fixup_sense_combo_jack, + .chained = true, + .chain_id = ALC283_FIXUP_CHROME_BOOK, + }, + [ALC282_FIXUP_ASUS_TX300] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc282_fixup_asus_tx300, + }, + [ALC283_FIXUP_INT_MIC] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + {0x20, AC_VERB_SET_COEF_INDEX, 0x1a}, + {0x20, AC_VERB_SET_PROC_COEF, 0x0011}, + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_LIMIT_INT_MIC_BOOST + }, + [ALC290_FIXUP_SUBWOOFER_HSJACK] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x17, 0x90170112 }, /* subwoofer */ + { } + }, + .chained = true, + .chain_id = ALC290_FIXUP_MONO_SPEAKERS_HSJACK, + }, + [ALC290_FIXUP_SUBWOOFER] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x17, 0x90170112 }, /* subwoofer */ + { } + }, + .chained = true, + .chain_id = ALC290_FIXUP_MONO_SPEAKERS, + }, + [ALC290_FIXUP_MONO_SPEAKERS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc290_fixup_mono_speakers, + }, + [ALC290_FIXUP_MONO_SPEAKERS_HSJACK] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc290_fixup_mono_speakers, + .chained = true, + .chain_id = ALC269_FIXUP_DELL3_MIC_NO_PRESENCE, + }, + [ALC269_FIXUP_THINKPAD_ACPI] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_thinkpad_acpi, + .chained = true, + .chain_id = ALC269_FIXUP_SKU_IGNORE, + }, + [ALC269_FIXUP_DMIC_THINKPAD_ACPI] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_inv_dmic, + .chained = true, + .chain_id = ALC269_FIXUP_THINKPAD_ACPI, + }, + [ALC255_FIXUP_ACER_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC255_FIXUP_HEADSET_MODE + }, + [ALC255_FIXUP_ASUS_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC255_FIXUP_HEADSET_MODE + }, + [ALC255_FIXUP_DELL1_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { 0x1a, 0x01a1913d }, /* use as headphone mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC255_FIXUP_HEADSET_MODE + }, + [ALC255_FIXUP_DELL2_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC255_FIXUP_HEADSET_MODE_NO_HP_MIC + }, + [ALC255_FIXUP_HEADSET_MODE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode_alc255, + .chained = true, + .chain_id = ALC255_FIXUP_MIC_MUTE_LED + }, + [ALC255_FIXUP_HEADSET_MODE_NO_HP_MIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode_alc255_no_hp_mic, + }, + [ALC293_FIXUP_DELL1_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x18, 0x01a1913d }, /* use as headphone mic, without its own jack detect */ + { 0x1a, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE + }, + [ALC292_FIXUP_TPT440_DOCK] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_tpt440_dock, + .chained = true, + .chain_id = ALC269_FIXUP_LIMIT_INT_MIC_BOOST + }, + [ALC292_FIXUP_TPT440] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_disable_aamix, + .chained = true, + .chain_id = ALC292_FIXUP_TPT440_DOCK, + }, + [ALC283_FIXUP_HEADSET_MIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x04a110f0 }, + { }, + }, + }, + [ALC255_FIXUP_MIC_MUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_micmute_led, + }, + [ALC282_FIXUP_ASPIRE_V5_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x12, 0x90a60130 }, + { 0x14, 0x90170110 }, + { 0x17, 0x40000008 }, + { 0x18, 0x411111f0 }, + { 0x19, 0x01a1913c }, + { 0x1a, 0x411111f0 }, + { 0x1b, 0x411111f0 }, + { 0x1d, 0x40f89b2d }, + { 0x1e, 0x411111f0 }, + { 0x21, 0x0321101f }, + { }, + }, + }, + [ALC269VB_FIXUP_ASPIRE_E1_COEF] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269vb_fixup_aspire_e1_coef, + }, + [ALC280_FIXUP_HP_GPIO4] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc280_fixup_hp_gpio4, + }, + [ALC286_FIXUP_HP_GPIO_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc286_fixup_hp_gpio_led, + }, + [ALC280_FIXUP_HP_GPIO2_MIC_HOTKEY] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc280_fixup_hp_gpio2_mic_hotkey, + }, + [ALC280_FIXUP_HP_DOCK_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1b, 0x21011020 }, /* line-out */ + { 0x1a, 0x01a1903c }, /* headset mic */ + { 0x18, 0x2181103f }, /* line-in */ + { }, + }, + .chained = true, + .chain_id = ALC280_FIXUP_HP_GPIO4 + }, + [ALC269_FIXUP_HP_DOCK_GPIO_MIC1_LED] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1b, 0x21011020 }, /* line-out */ + { 0x18, 0x2181103f }, /* line-in */ + { }, + }, + .chained = true, + .chain_id = ALC269_FIXUP_HP_GPIO_MIC1_LED + }, + [ALC280_FIXUP_HP_9480M] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc280_fixup_hp_9480m, + }, + [ALC245_FIXUP_HP_X360_AMP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc245_fixup_hp_x360_amp, + .chained = true, + .chain_id = ALC245_FIXUP_HP_GPIO_LED + }, + [ALC288_FIXUP_DELL_HEADSET_MODE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode_dell_alc288, + .chained = true, + .chain_id = ALC255_FIXUP_MIC_MUTE_LED + }, + [ALC288_FIXUP_DELL1_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x18, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { 0x1a, 0x01a1913d }, /* use as headphone mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC288_FIXUP_DELL_HEADSET_MODE + }, + [ALC288_FIXUP_DISABLE_AAMIX] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_disable_aamix, + .chained = true, + .chain_id = ALC288_FIXUP_DELL1_MIC_NO_PRESENCE + }, + [ALC288_FIXUP_DELL_XPS_13] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_dell_xps13, + .chained = true, + .chain_id = ALC288_FIXUP_DISABLE_AAMIX + }, + [ALC292_FIXUP_DISABLE_AAMIX] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_disable_aamix, + .chained = true, + .chain_id = ALC269_FIXUP_DELL2_MIC_NO_PRESENCE + }, + [ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_disable_aamix, + .chained = true, + .chain_id = ALC293_FIXUP_DELL1_MIC_NO_PRESENCE + }, + [ALC292_FIXUP_DELL_E7X_AAMIX] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_dell_xps13, + .chained = true, + .chain_id = ALC292_FIXUP_DISABLE_AAMIX + }, + [ALC292_FIXUP_DELL_E7X] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_micmute_led, + /* micmute fixup must be applied at last */ + .chained_before = true, + .chain_id = ALC292_FIXUP_DELL_E7X_AAMIX, + }, + [ALC298_FIXUP_ALIENWARE_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x18, 0x01a1913c }, /* headset mic w/o jack detect */ + { } + }, + .chained_before = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE, + }, + [ALC298_FIXUP_DELL1_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x18, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { 0x1a, 0x01a1913d }, /* use as headphone mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE + }, + [ALC298_FIXUP_DELL_AIO_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x18, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE + }, + [ALC275_FIXUP_DELL_XPS] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* Enables internal speaker */ + {0x20, AC_VERB_SET_COEF_INDEX, 0x1f}, + {0x20, AC_VERB_SET_PROC_COEF, 0x00c0}, + {0x20, AC_VERB_SET_COEF_INDEX, 0x30}, + {0x20, AC_VERB_SET_PROC_COEF, 0x00b1}, + {} + } + }, + [ALC293_FIXUP_LENOVO_SPK_NOISE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_disable_aamix, + .chained = true, + .chain_id = ALC269_FIXUP_THINKPAD_ACPI + }, + [ALC233_FIXUP_LENOVO_LINE2_MIC_HOTKEY] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc233_fixup_lenovo_line2_mic_hotkey, + }, + [ALC233_FIXUP_INTEL_NUC8_DMIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_inv_dmic, + .chained = true, + .chain_id = ALC233_FIXUP_INTEL_NUC8_BOOST, + }, + [ALC233_FIXUP_INTEL_NUC8_BOOST] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_limit_int_mic_boost + }, + [ALC255_FIXUP_DELL_SPK_NOISE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_disable_aamix, + .chained = true, + .chain_id = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE + }, + [ALC225_FIXUP_DISABLE_MIC_VREF] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_disable_mic_vref, + .chained = true, + .chain_id = ALC269_FIXUP_DELL1_MIC_NO_PRESENCE + }, + [ALC225_FIXUP_DELL1_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* Disable pass-through path for FRONT 14h */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x36 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x57d7 }, + {} + }, + .chained = true, + .chain_id = ALC225_FIXUP_DISABLE_MIC_VREF + }, + [ALC280_FIXUP_HP_HEADSET_MIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_disable_aamix, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MIC, + }, + [ALC221_FIXUP_HP_FRONT_MIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x02a19020 }, /* Front Mic */ + { } + }, + }, + [ALC292_FIXUP_TPT460] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_tpt440_dock, + .chained = true, + .chain_id = ALC293_FIXUP_LENOVO_SPK_NOISE, + }, + [ALC298_FIXUP_SPK_VOLUME] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc298_fixup_speaker_volume, + .chained = true, + .chain_id = ALC298_FIXUP_DELL_AIO_MIC_NO_PRESENCE, + }, + [ALC298_FIXUP_LENOVO_SPK_VOLUME] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc298_fixup_speaker_volume, + }, + [ALC295_FIXUP_DISABLE_DAC3] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc295_fixup_disable_dac3, + }, + [ALC285_FIXUP_SPEAKER2_TO_DAC1] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_speaker2_to_dac1, + .chained = true, + .chain_id = ALC269_FIXUP_THINKPAD_ACPI + }, + [ALC285_FIXUP_ASUS_SPEAKER2_TO_DAC1] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_speaker2_to_dac1, + .chained = true, + .chain_id = ALC245_FIXUP_CS35L41_SPI_2 + }, + [ALC285_FIXUP_ASUS_HEADSET_MIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x03a11050 }, + { 0x1b, 0x03a11c30 }, + { } + }, + .chained = true, + .chain_id = ALC285_FIXUP_ASUS_SPEAKER2_TO_DAC1 + }, + [ALC285_FIXUP_ASUS_I2C_SPEAKER2_TO_DAC1] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_speaker2_to_dac1, + .chained = true, + .chain_id = ALC287_FIXUP_CS35L41_I2C_2 + }, + [ALC285_FIXUP_ASUS_I2C_HEADSET_MIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x03a11050 }, + { 0x1b, 0x03a11c30 }, + { } + }, + .chained = true, + .chain_id = ALC285_FIXUP_ASUS_I2C_SPEAKER2_TO_DAC1 + }, + [ALC256_FIXUP_DELL_INSPIRON_7559_SUBWOOFER] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1b, 0x90170151 }, + { } + }, + .chained = true, + .chain_id = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE + }, + [ALC269_FIXUP_ATIV_BOOK_8] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_auto_mute_via_amp, + .chained = true, + .chain_id = ALC269_FIXUP_NO_SHUTUP + }, + [ALC221_FIXUP_HP_288PRO_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { 0x1a, 0x01813030 }, /* use as headphone mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE + }, + [ALC221_FIXUP_HP_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x18, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { 0x1a, 0x01a1913d }, /* use as headphone mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE + }, + [ALC256_FIXUP_ASUS_HEADSET_MODE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode, + }, + [ALC256_FIXUP_ASUS_MIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x13, 0x90a60160 }, /* use as internal mic */ + { 0x19, 0x04a11120 }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC256_FIXUP_ASUS_HEADSET_MODE + }, + [ALC256_FIXUP_ASUS_AIO_GPIO2] = { + .type = HDA_FIXUP_FUNC, + /* Set up GPIO2 for the speaker amp */ + .v.func = alc_fixup_gpio4, + }, + [ALC233_FIXUP_ASUS_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MIC + }, + [ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* Enables internal speaker */ + {0x20, AC_VERB_SET_COEF_INDEX, 0x40}, + {0x20, AC_VERB_SET_PROC_COEF, 0x8800}, + {} + }, + .chained = true, + .chain_id = ALC233_FIXUP_ASUS_MIC_NO_PRESENCE + }, + [ALC233_FIXUP_LENOVO_MULTI_CODECS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc233_alc662_fixup_lenovo_dual_codecs, + .chained = true, + .chain_id = ALC269_FIXUP_GPIO2 + }, + [ALC233_FIXUP_ACER_HEADSET_MIC] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x20, AC_VERB_SET_COEF_INDEX, 0x45 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x5089 }, + { } + }, + .chained = true, + .chain_id = ALC233_FIXUP_ASUS_MIC_NO_PRESENCE + }, + [ALC294_FIXUP_LENOVO_MIC_LOCATION] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + /* Change the mic location from front to right, otherwise there are + two front mics with the same name, pulseaudio can't handle them. + This is just a temporary workaround, after applying this fixup, + there will be one "Front Mic" and one "Mic" in this machine. + */ + { 0x1a, 0x04a19040 }, + { } + }, + }, + [ALC225_FIXUP_DELL_WYSE_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x16, 0x0101102f }, /* Rear Headset HP */ + { 0x19, 0x02a1913c }, /* use as Front headset mic, without its own jack detect */ + { 0x1a, 0x01a19030 }, /* Rear Headset MIC */ + { 0x1b, 0x02011020 }, + { } + }, + .chained = true, + .chain_id = ALC225_FIXUP_S3_POP_NOISE + }, + [ALC225_FIXUP_S3_POP_NOISE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc225_fixup_s3_pop_noise, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC + }, + [ALC700_FIXUP_INTEL_REFERENCE] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* Enables internal speaker */ + {0x20, AC_VERB_SET_COEF_INDEX, 0x45}, + {0x20, AC_VERB_SET_PROC_COEF, 0x5289}, + {0x20, AC_VERB_SET_COEF_INDEX, 0x4A}, + {0x20, AC_VERB_SET_PROC_COEF, 0x001b}, + {0x58, AC_VERB_SET_COEF_INDEX, 0x00}, + {0x58, AC_VERB_SET_PROC_COEF, 0x3888}, + {0x20, AC_VERB_SET_COEF_INDEX, 0x6f}, + {0x20, AC_VERB_SET_PROC_COEF, 0x2c0b}, + {} + } + }, + [ALC274_FIXUP_DELL_BIND_DACS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc274_fixup_bind_dacs, + .chained = true, + .chain_id = ALC269_FIXUP_DELL1_MIC_NO_PRESENCE + }, + [ALC274_FIXUP_DELL_AIO_LINEOUT_VERB] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1b, 0x0401102f }, + { } + }, + .chained = true, + .chain_id = ALC274_FIXUP_DELL_BIND_DACS + }, + [ALC298_FIXUP_TPT470_DOCK_FIX] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_tpt470_dock, + .chained = true, + .chain_id = ALC293_FIXUP_LENOVO_SPK_NOISE + }, + [ALC298_FIXUP_TPT470_DOCK] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_tpt470_dacs, + .chained = true, + .chain_id = ALC298_FIXUP_TPT470_DOCK_FIX + }, + [ALC255_FIXUP_DUMMY_LINEOUT_VERB] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x0201101f }, + { } + }, + .chained = true, + .chain_id = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE + }, + [ALC255_FIXUP_DELL_HEADSET_MIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MIC + }, + [ALC295_FIXUP_HP_X360] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc295_fixup_hp_top_speakers, + .chained = true, + .chain_id = ALC269_FIXUP_HP_MUTE_LED_MIC3 + }, + [ALC221_FIXUP_HP_HEADSET_MIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x0181313f}, + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MIC + }, + [ALC285_FIXUP_LENOVO_HEADPHONE_NOISE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_invalidate_dacs, + .chained = true, + .chain_id = ALC269_FIXUP_THINKPAD_ACPI + }, + [ALC295_FIXUP_HP_AUTO_MUTE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_auto_mute_via_amp, + }, + [ALC286_FIXUP_ACER_AIO_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x18, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MIC + }, + [ALC294_FIXUP_ASUS_MIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x13, 0x90a60160 }, /* use as internal mic */ + { 0x19, 0x04a11120 }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MIC + }, + [ALC294_FIXUP_ASUS_HEADSET_MIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1103c }, /* use as headset mic */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MIC + }, + [ALC294_FIXUP_ASUS_SPK] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* Set EAPD high */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x40 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x8800 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x0f }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x7774 }, + { } + }, + .chained = true, + .chain_id = ALC294_FIXUP_ASUS_HEADSET_MIC + }, + [ALC295_FIXUP_CHROME_BOOK] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc295_fixup_chromebook, + .chained = true, + .chain_id = ALC225_FIXUP_HEADSET_JACK + }, + [ALC225_FIXUP_HEADSET_JACK] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_jack, + }, + [ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1a, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC + }, + [ALC285_FIXUP_LENOVO_PC_BEEP_IN_NOISE] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* Disable PCBEEP-IN passthrough */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x36 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x57d7 }, + { } + }, + .chained = true, + .chain_id = ALC285_FIXUP_LENOVO_HEADPHONE_NOISE + }, + [ALC255_FIXUP_ACER_HEADSET_MIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x03a11130 }, + { 0x1a, 0x90a60140 }, /* use as internal mic */ + { } + }, + .chained = true, + .chain_id = ALC255_FIXUP_HEADSET_MODE_NO_HP_MIC + }, + [ALC225_FIXUP_DELL_WYSE_AIO_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x16, 0x01011020 }, /* Rear Line out */ + { 0x19, 0x01a1913c }, /* use as Front headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC225_FIXUP_WYSE_AUTO_MUTE + }, + [ALC225_FIXUP_WYSE_AUTO_MUTE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_auto_mute_via_amp, + .chained = true, + .chain_id = ALC225_FIXUP_WYSE_DISABLE_MIC_VREF + }, + [ALC225_FIXUP_WYSE_DISABLE_MIC_VREF] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_disable_mic_vref, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC + }, + [ALC286_FIXUP_ACER_AIO_HEADSET_MIC] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x20, AC_VERB_SET_COEF_INDEX, 0x4f }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x5029 }, + { } + }, + .chained = true, + .chain_id = ALC286_FIXUP_ACER_AIO_MIC_NO_PRESENCE + }, + [ALC256_FIXUP_ASUS_HEADSET_MIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x03a11020 }, /* headset mic with jack detect */ + { } + }, + .chained = true, + .chain_id = ALC256_FIXUP_ASUS_HEADSET_MODE + }, + [ALC256_FIXUP_ASUS_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x04a11120 }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC256_FIXUP_ASUS_HEADSET_MODE + }, + [ALC299_FIXUP_PREDATOR_SPK] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x21, 0x90170150 }, /* use as headset mic, without its own jack detect */ + { } + } + }, + [ALC256_FIXUP_MEDION_HEADSET_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x04a11040 }, + { 0x21, 0x04211020 }, + { } + }, + .chained = true, + .chain_id = ALC256_FIXUP_ASUS_HEADSET_MODE + }, + [ALC289_FIXUP_DELL_SPK2] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x17, 0x90170130 }, /* bass spk */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_DELL4_MIC_NO_PRESENCE + }, + [ALC289_FIXUP_DUAL_SPK] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_speaker2_to_dac1, + .chained = true, + .chain_id = ALC289_FIXUP_DELL_SPK2 + }, + [ALC294_FIXUP_SPK2_TO_DAC1] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_speaker2_to_dac1, + .chained = true, + .chain_id = ALC294_FIXUP_ASUS_HEADSET_MIC + }, + [ALC294_FIXUP_ASUS_DUAL_SPK] = { + .type = HDA_FIXUP_FUNC, + /* The GPIO must be pulled to initialize the AMP */ + .v.func = alc_fixup_gpio4, + .chained = true, + .chain_id = ALC294_FIXUP_SPK2_TO_DAC1 + }, + [ALC294_FIXUP_ASUS_ALLY] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs35l41_fixup_i2c_two, + .chained = true, + .chain_id = ALC294_FIXUP_ASUS_ALLY_PINS + }, + [ALC294_FIXUP_ASUS_ALLY_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x03a11050 }, + { 0x1a, 0x03a11c30 }, + { 0x21, 0x03211420 }, + { } + }, + .chained = true, + .chain_id = ALC294_FIXUP_ASUS_ALLY_VERBS + }, + [ALC294_FIXUP_ASUS_ALLY_VERBS] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x20, AC_VERB_SET_COEF_INDEX, 0x45 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x5089 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x46 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0004 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x47 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xa47a }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x49 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0049}, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x4a }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x201b }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x6b }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x4278}, + { } + }, + .chained = true, + .chain_id = ALC294_FIXUP_ASUS_ALLY_SPEAKER + }, + [ALC294_FIXUP_ASUS_ALLY_SPEAKER] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_speaker2_to_dac1, + }, + [ALC285_FIXUP_THINKPAD_X1_GEN7] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_thinkpad_x1_gen7, + .chained = true, + .chain_id = ALC269_FIXUP_THINKPAD_ACPI + }, + [ALC285_FIXUP_THINKPAD_HEADSET_JACK] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_jack, + .chained = true, + .chain_id = ALC285_FIXUP_THINKPAD_X1_GEN7 + }, + [ALC294_FIXUP_ASUS_HPE] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* Set EAPD high */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x0f }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x7774 }, + { } + }, + .chained = true, + .chain_id = ALC294_FIXUP_ASUS_HEADSET_MIC + }, + [ALC294_FIXUP_ASUS_GX502_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x03a11050 }, /* front HP mic */ + { 0x1a, 0x01a11830 }, /* rear external mic */ + { 0x21, 0x03211020 }, /* front HP out */ + { } + }, + .chained = true, + .chain_id = ALC294_FIXUP_ASUS_GX502_VERBS + }, + [ALC294_FIXUP_ASUS_GX502_VERBS] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* set 0x15 to HP-OUT ctrl */ + { 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 }, + /* unmute the 0x15 amp */ + { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000 }, + { } + }, + .chained = true, + .chain_id = ALC294_FIXUP_ASUS_GX502_HP + }, + [ALC294_FIXUP_ASUS_GX502_HP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc294_fixup_gx502_hp, + }, + [ALC294_FIXUP_ASUS_GU502_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a11050 }, /* rear HP mic */ + { 0x1a, 0x01a11830 }, /* rear external mic */ + { 0x21, 0x012110f0 }, /* rear HP out */ + { } + }, + .chained = true, + .chain_id = ALC294_FIXUP_ASUS_GU502_VERBS + }, + [ALC294_FIXUP_ASUS_GU502_VERBS] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* set 0x15 to HP-OUT ctrl */ + { 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 }, + /* unmute the 0x15 amp */ + { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000 }, + /* set 0x1b to HP-OUT */ + { 0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + { } + }, + .chained = true, + .chain_id = ALC294_FIXUP_ASUS_GU502_HP + }, + [ALC294_FIXUP_ASUS_GU502_HP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc294_fixup_gu502_hp, + }, + [ALC294_FIXUP_ASUS_G513_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x03a11050 }, /* front HP mic */ + { 0x1a, 0x03a11c30 }, /* rear external mic */ + { 0x21, 0x03211420 }, /* front HP out */ + { } + }, + }, + [ALC285_FIXUP_ASUS_G533Z_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x90170152 }, /* Speaker Surround Playback Switch */ + { 0x19, 0x03a19020 }, /* Mic Boost Volume */ + { 0x1a, 0x03a11c30 }, /* Mic Boost Volume */ + { 0x1e, 0x90170151 }, /* Rear jack, IN OUT EAPD Detect */ + { 0x21, 0x03211420 }, + { } + }, + }, + [ALC294_FIXUP_ASUS_COEF_1B] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* Set bit 10 to correct noisy output after reboot from + * Windows 10 (due to pop noise reduction?) + */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x1b }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x4e4b }, + { } + }, + .chained = true, + .chain_id = ALC289_FIXUP_ASUS_GA401, + }, + [ALC285_FIXUP_HP_GPIO_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_hp_gpio_led, + }, + [ALC285_FIXUP_HP_MUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_hp_mute_led, + }, + [ALC285_FIXUP_HP_SPECTRE_X360_MUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_hp_spectre_x360_mute_led, + }, + [ALC236_FIXUP_HP_MUTE_LED_COEFBIT2] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc236_fixup_hp_mute_led_coefbit2, + }, + [ALC236_FIXUP_HP_GPIO_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc236_fixup_hp_gpio_led, + }, + [ALC236_FIXUP_HP_MUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc236_fixup_hp_mute_led, + }, + [ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc236_fixup_hp_mute_led_micmute_vref, + }, + [ALC298_FIXUP_SAMSUNG_AMP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc298_fixup_samsung_amp, + .chained = true, + .chain_id = ALC298_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET + }, + [ALC298_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc5 }, + { } + }, + }, + [ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x20, AC_VERB_SET_COEF_INDEX, 0x08}, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2fcf}, + { } + }, + }, + [ALC295_FIXUP_ASUS_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE + }, + [ALC269VC_FIXUP_ACER_VCOPPERBOX_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x90100120 }, /* use as internal speaker */ + { 0x18, 0x02a111f0 }, /* use as headset mic, without its own jack detect */ + { 0x1a, 0x01011020 }, /* use as line out */ + { }, + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MIC + }, + [ALC269VC_FIXUP_ACER_HEADSET_MIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x18, 0x02a11030 }, /* use as headset mic */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MIC + }, + [ALC269VC_FIXUP_ACER_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x18, 0x01a11130 }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MIC + }, + [ALC289_FIXUP_ASUS_GA401] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc289_fixup_asus_ga401, + .chained = true, + .chain_id = ALC289_FIXUP_ASUS_GA502, + }, + [ALC289_FIXUP_ASUS_GA502] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x03a11020 }, /* headset mic with jack detect */ + { } + }, + }, + [ALC256_FIXUP_ACER_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x02a11120 }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC256_FIXUP_ASUS_HEADSET_MODE + }, + [ALC285_FIXUP_HP_GPIO_AMP_INIT] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_hp_gpio_amp_init, + .chained = true, + .chain_id = ALC285_FIXUP_HP_GPIO_LED + }, + [ALC269_FIXUP_CZC_B20] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x12, 0x411111f0 }, + { 0x14, 0x90170110 }, /* speaker */ + { 0x15, 0x032f1020 }, /* HP out */ + { 0x17, 0x411111f0 }, + { 0x18, 0x03ab1040 }, /* mic */ + { 0x19, 0xb7a7013f }, + { 0x1a, 0x0181305f }, + { 0x1b, 0x411111f0 }, + { 0x1d, 0x411111f0 }, + { 0x1e, 0x411111f0 }, + { } + }, + .chain_id = ALC269_FIXUP_DMIC, + }, + [ALC269_FIXUP_CZC_TMI] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x12, 0x4000c000 }, + { 0x14, 0x90170110 }, /* speaker */ + { 0x15, 0x0421401f }, /* HP out */ + { 0x17, 0x411111f0 }, + { 0x18, 0x04a19020 }, /* mic */ + { 0x19, 0x411111f0 }, + { 0x1a, 0x411111f0 }, + { 0x1b, 0x411111f0 }, + { 0x1d, 0x40448505 }, + { 0x1e, 0x411111f0 }, + { 0x20, 0x8000ffff }, + { } + }, + .chain_id = ALC269_FIXUP_DMIC, + }, + [ALC269_FIXUP_CZC_L101] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x12, 0x40000000 }, + { 0x14, 0x01014010 }, /* speaker */ + { 0x15, 0x411111f0 }, /* HP out */ + { 0x16, 0x411111f0 }, + { 0x18, 0x01a19020 }, /* mic */ + { 0x19, 0x02a19021 }, + { 0x1a, 0x0181302f }, + { 0x1b, 0x0221401f }, + { 0x1c, 0x411111f0 }, + { 0x1d, 0x4044c601 }, + { 0x1e, 0x411111f0 }, + { } + }, + .chain_id = ALC269_FIXUP_DMIC, + }, + [ALC269_FIXUP_LEMOTE_A1802] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x12, 0x40000000 }, + { 0x14, 0x90170110 }, /* speaker */ + { 0x17, 0x411111f0 }, + { 0x18, 0x03a19040 }, /* mic1 */ + { 0x19, 0x90a70130 }, /* mic2 */ + { 0x1a, 0x411111f0 }, + { 0x1b, 0x411111f0 }, + { 0x1d, 0x40489d2d }, + { 0x1e, 0x411111f0 }, + { 0x20, 0x0003ffff }, + { 0x21, 0x03214020 }, + { } + }, + .chain_id = ALC269_FIXUP_DMIC, + }, + [ALC269_FIXUP_LEMOTE_A190X] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x99130110 }, /* speaker */ + { 0x15, 0x0121401f }, /* HP out */ + { 0x18, 0x01a19c20 }, /* rear mic */ + { 0x19, 0x99a3092f }, /* front mic */ + { 0x1b, 0x0201401f }, /* front lineout */ + { } + }, + .chain_id = ALC269_FIXUP_DMIC, + }, + [ALC256_FIXUP_INTEL_NUC8_RUGGED] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1b, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE + }, + [ALC256_FIXUP_INTEL_NUC10] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE + }, + [ALC255_FIXUP_XIAOMI_HEADSET_MIC] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x20, AC_VERB_SET_COEF_INDEX, 0x45 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x5089 }, + { } + }, + .chained = true, + .chain_id = ALC289_FIXUP_ASUS_GA502 + }, + [ALC274_FIXUP_HP_MIC] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x20, AC_VERB_SET_COEF_INDEX, 0x45 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x5089 }, + { } + }, + }, + [ALC274_FIXUP_HP_HEADSET_MIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc274_fixup_hp_headset_mic, + .chained = true, + .chain_id = ALC274_FIXUP_HP_MIC + }, + [ALC274_FIXUP_HP_ENVY_GPIO] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc274_fixup_hp_envy_gpio, + }, + [ALC256_FIXUP_ASUS_HPE] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* Set EAPD high */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x0f }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x7778 }, + { } + }, + .chained = true, + .chain_id = ALC294_FIXUP_ASUS_HEADSET_MIC + }, + [ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_jack, + .chained = true, + .chain_id = ALC269_FIXUP_THINKPAD_ACPI + }, + [ALC287_FIXUP_HP_GPIO_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_hp_gpio_led, + }, + [ALC256_FIXUP_HP_HEADSET_MIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc274_fixup_hp_headset_mic, + }, + [ALC236_FIXUP_DELL_AIO_HEADSET_MIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_no_int_mic, + .chained = true, + .chain_id = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE + }, + [ALC282_FIXUP_ACER_DISABLE_LINEOUT] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1b, 0x411111f0 }, + { 0x18, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { }, + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE + }, + [ALC255_FIXUP_ACER_LIMIT_INT_MIC_BOOST] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_limit_int_mic_boost, + .chained = true, + .chain_id = ALC255_FIXUP_ACER_MIC_NO_PRESENCE, + }, + [ALC256_FIXUP_ACER_HEADSET_MIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x02a1113c }, /* use as headset mic, without its own jack detect */ + { 0x1a, 0x90a1092f }, /* use as internal mic */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC + }, + [ALC285_FIXUP_IDEAPAD_S740_COEF] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_ideapad_s740_coef, + .chained = true, + .chain_id = ALC269_FIXUP_THINKPAD_ACPI, + }, + [ALC285_FIXUP_HP_LIMIT_INT_MIC_BOOST] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_limit_int_mic_boost, + .chained = true, + .chain_id = ALC285_FIXUP_HP_MUTE_LED, + }, + [ALC295_FIXUP_ASUS_DACS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc295_fixup_asus_dacs, + }, + [ALC295_FIXUP_HP_OMEN] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x12, 0xb7a60130 }, + { 0x13, 0x40000000 }, + { 0x14, 0x411111f0 }, + { 0x16, 0x411111f0 }, + { 0x17, 0x90170110 }, + { 0x18, 0x411111f0 }, + { 0x19, 0x02a11030 }, + { 0x1a, 0x411111f0 }, + { 0x1b, 0x04a19030 }, + { 0x1d, 0x40600001 }, + { 0x1e, 0x411111f0 }, + { 0x21, 0x03211020 }, + {} + }, + .chained = true, + .chain_id = ALC269_FIXUP_HP_LINE1_MIC1_LED, + }, + [ALC285_FIXUP_HP_SPECTRE_X360] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_hp_spectre_x360, + }, + [ALC285_FIXUP_HP_SPECTRE_X360_EB1] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_hp_spectre_x360_eb1 + }, + [ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_ideapad_s740_coef, + .chained = true, + .chain_id = ALC285_FIXUP_THINKPAD_HEADSET_JACK, + }, + [ALC623_FIXUP_LENOVO_THINKSTATION_P340] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_no_shutup, + .chained = true, + .chain_id = ALC283_FIXUP_HEADSET_MIC, + }, + [ALC255_FIXUP_ACER_HEADPHONE_AND_MIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x21, 0x03211030 }, /* Change the Headphone location to Left */ + { } + }, + .chained = true, + .chain_id = ALC255_FIXUP_XIAOMI_HEADSET_MIC + }, + [ALC236_FIXUP_HP_LIMIT_INT_MIC_BOOST] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_limit_int_mic_boost, + .chained = true, + .chain_id = ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF, + }, + [ALC285_FIXUP_LEGION_Y9000X_SPEAKERS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_ideapad_s740_coef, + .chained = true, + .chain_id = ALC285_FIXUP_LEGION_Y9000X_AUTOMUTE, + }, + [ALC285_FIXUP_LEGION_Y9000X_AUTOMUTE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_legion_15imhg05_speakers, + .chained = true, + .chain_id = ALC269_FIXUP_THINKPAD_ACPI, + }, + [ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS] = { + .type = HDA_FIXUP_VERBS, + //.v.verbs = legion_15imhg05_coefs, + .v.verbs = (const struct hda_verb[]) { + // set left speaker Legion 7i. + { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x41 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xc }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x1a }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + // set right speaker Legion 7i. + { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x42 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xc }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2a }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + {} + }, + .chained = true, + .chain_id = ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE, + }, + [ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_legion_15imhg05_speakers, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE, + }, + [ALC287_FIXUP_YOGA7_14ITL_SPEAKERS] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + // set left speaker Yoga 7i. + { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x41 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xc }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x1a }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + // set right speaker Yoga 7i. + { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x46 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xc }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2a }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + {} + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE, + }, + [ALC298_FIXUP_LENOVO_C940_DUET7] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc298_fixup_lenovo_c940_duet7, + }, + [ALC287_FIXUP_13S_GEN2_SPEAKERS] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x41 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x42 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + {} + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE, + }, + [ALC256_FIXUP_SET_COEF_DEFAULTS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc256_fixup_set_coef_defaults, + }, + [ALC245_FIXUP_HP_GPIO_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc245_fixup_hp_gpio_led, + }, + [ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x03a11120 }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC, + }, + [ALC233_FIXUP_NO_AUDIO_JACK] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc233_fixup_no_audio_jack, + }, + [ALC256_FIXUP_MIC_NO_PRESENCE_AND_RESUME] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc256_fixup_mic_no_presence_and_resume, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC + }, + [ALC287_FIXUP_LEGION_16ACHG6] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_legion_16achg6_speakers, + }, + [ALC287_FIXUP_CS35L41_I2C_2] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs35l41_fixup_i2c_two, + }, + [ALC287_FIXUP_CS35L41_I2C_2_HP_GPIO_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs35l41_fixup_i2c_two, + .chained = true, + .chain_id = ALC285_FIXUP_HP_MUTE_LED, + }, + [ALC245_FIXUP_CS35L41_SPI_2] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs35l41_fixup_spi_two, + }, + [ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs35l41_fixup_spi_two, + .chained = true, + .chain_id = ALC285_FIXUP_HP_GPIO_LED, + }, + [ALC245_FIXUP_CS35L41_SPI_4] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs35l41_fixup_spi_four, + }, + [ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs35l41_fixup_spi_four, + .chained = true, + .chain_id = ALC285_FIXUP_HP_GPIO_LED, + }, + [ALC285_FIXUP_HP_SPEAKERS_MICMUTE_LED] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x20, AC_VERB_SET_COEF_INDEX, 0x19 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x8e11 }, + { } + }, + .chained = true, + .chain_id = ALC285_FIXUP_HP_MUTE_LED, + }, + [ALC269_FIXUP_DELL4_MIC_NO_PRESENCE_QUIET] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_dell4_mic_no_presence_quiet, + .chained = true, + .chain_id = ALC269_FIXUP_DELL4_MIC_NO_PRESENCE, + }, + [ALC295_FIXUP_FRAMEWORK_LAPTOP_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x02a1112c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC + }, + [ALC287_FIXUP_LEGION_16ITHG6] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_legion_16ithg6_speakers, + }, + [ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + // enable left speaker + { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x41 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xc }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x1a }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xf }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x42 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x10 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x40 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + // enable right speaker + { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x46 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xc }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2a }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xf }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x46 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x10 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x44 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + { }, + }, + }, + [ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_yoga9_14iap7_bass_spk_pin, + .chained = true, + .chain_id = ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK, + }, + [ALC295_FIXUP_DELL_INSPIRON_TOP_SPEAKERS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc295_fixup_dell_inspiron_top_speakers, + .chained = true, + .chain_id = ALC269_FIXUP_DELL4_MIC_NO_PRESENCE, + }, + [ALC236_FIXUP_DELL_DUAL_CODECS] = { + .type = HDA_FIXUP_PINS, + .v.func = alc1220_fixup_gb_dual_codecs, + .chained = true, + .chain_id = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + }, + [ALC287_FIXUP_CS35L41_I2C_2_THINKPAD_ACPI] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs35l41_fixup_i2c_two, + .chained = true, + .chain_id = ALC269_FIXUP_THINKPAD_ACPI, + }, + [ALC245_FIXUP_HP_MUTE_LED_COEFBIT] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc245_fixup_hp_mute_led_coefbit, + }, + [ALC245_FIXUP_HP_X360_MUTE_LEDS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc245_fixup_hp_mute_led_coefbit, + .chained = true, + .chain_id = ALC245_FIXUP_HP_GPIO_LED + }, + [ALC287_FIXUP_THINKPAD_I2S_SPK] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_bind_dacs, + }, + [ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_bind_dacs, + .chained = true, + .chain_id = ALC287_FIXUP_CS35L41_I2C_2_THINKPAD_ACPI, + }, + [ALC2XX_FIXUP_HEADSET_MIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mic, + }, +}; + +static const struct snd_pci_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x0283, "Acer TravelMate 8371", ALC269_FIXUP_INV_DMIC), + SND_PCI_QUIRK(0x1025, 0x029b, "Acer 1810TZ", ALC269_FIXUP_INV_DMIC), + SND_PCI_QUIRK(0x1025, 0x0349, "Acer AOD260", ALC269_FIXUP_INV_DMIC), + SND_PCI_QUIRK(0x1025, 0x047c, "Acer AC700", ALC269_FIXUP_ACER_AC700), + SND_PCI_QUIRK(0x1025, 0x072d, "Acer Aspire V5-571G", ALC269_FIXUP_ASPIRE_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x0740, "Acer AO725", ALC271_FIXUP_HP_GATE_MIC_JACK), + SND_PCI_QUIRK(0x1025, 0x0742, "Acer AO756", ALC271_FIXUP_HP_GATE_MIC_JACK), + SND_PCI_QUIRK(0x1025, 0x0762, "Acer Aspire E1-472", ALC271_FIXUP_HP_GATE_MIC_JACK_E1_572), + SND_PCI_QUIRK(0x1025, 0x0775, "Acer Aspire E1-572", ALC271_FIXUP_HP_GATE_MIC_JACK_E1_572), + SND_PCI_QUIRK(0x1025, 0x079b, "Acer Aspire V5-573G", ALC282_FIXUP_ASPIRE_V5_PINS), + SND_PCI_QUIRK(0x1025, 0x080d, "Acer Aspire V5-122P", ALC269_FIXUP_ASPIRE_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x0840, "Acer Aspire E1", ALC269VB_FIXUP_ASPIRE_E1_COEF), + SND_PCI_QUIRK(0x1025, 0x101c, "Acer Veriton N2510G", ALC269_FIXUP_LIFEBOOK), + SND_PCI_QUIRK(0x1025, 0x102b, "Acer Aspire C24-860", ALC286_FIXUP_ACER_AIO_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1065, "Acer Aspire C20-820", ALC269VC_FIXUP_ACER_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x106d, "Acer Cloudbook 14", ALC283_FIXUP_CHROME_BOOK), + SND_PCI_QUIRK(0x1025, 0x1094, "Acer Aspire E5-575T", ALC255_FIXUP_ACER_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1025, 0x1099, "Acer Aspire E5-523G", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x110e, "Acer Aspire ES1-432", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1166, "Acer Veriton N4640G", ALC269_FIXUP_LIFEBOOK), + SND_PCI_QUIRK(0x1025, 0x1167, "Acer Veriton N6640G", ALC269_FIXUP_LIFEBOOK), + SND_PCI_QUIRK(0x1025, 0x1246, "Acer Predator Helios 500", ALC299_FIXUP_PREDATOR_SPK), + SND_PCI_QUIRK(0x1025, 0x1247, "Acer vCopperbox", ALC269VC_FIXUP_ACER_VCOPPERBOX_PINS), + SND_PCI_QUIRK(0x1025, 0x1248, "Acer Veriton N4660G", ALC269VC_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1269, "Acer SWIFT SF314-54", ALC256_FIXUP_ACER_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x128f, "Acer Veriton Z6860G", ALC286_FIXUP_ACER_AIO_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1290, "Acer Veriton Z4860G", ALC286_FIXUP_ACER_AIO_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1291, "Acer Veriton Z4660G", ALC286_FIXUP_ACER_AIO_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x129c, "Acer SWIFT SF314-55", ALC256_FIXUP_ACER_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x129d, "Acer SWIFT SF313-51", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1300, "Acer SWIFT SF314-56", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1308, "Acer Aspire Z24-890", ALC286_FIXUP_ACER_AIO_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x132a, "Acer TravelMate B114-21", ALC233_FIXUP_ACER_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1330, "Acer TravelMate X514-51T", ALC255_FIXUP_ACER_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x141f, "Acer Spin SP513-54N", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x142b, "Acer Swift SF314-42", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1430, "Acer TravelMate B311R-31", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1466, "Acer Aspire A515-56", ALC255_FIXUP_ACER_HEADPHONE_AND_MIC), + SND_PCI_QUIRK(0x1025, 0x1534, "Acer Predator PH315-54", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x0470, "Dell M101z", ALC269_FIXUP_DELL_M101Z), + SND_PCI_QUIRK(0x1028, 0x053c, "Dell Latitude E5430", ALC292_FIXUP_DELL_E7X), + SND_PCI_QUIRK(0x1028, 0x054b, "Dell XPS one 2710", ALC275_FIXUP_DELL_XPS), + SND_PCI_QUIRK(0x1028, 0x05bd, "Dell Latitude E6440", ALC292_FIXUP_DELL_E7X), + SND_PCI_QUIRK(0x1028, 0x05be, "Dell Latitude E6540", ALC292_FIXUP_DELL_E7X), + SND_PCI_QUIRK(0x1028, 0x05ca, "Dell Latitude E7240", ALC292_FIXUP_DELL_E7X), + SND_PCI_QUIRK(0x1028, 0x05cb, "Dell Latitude E7440", ALC292_FIXUP_DELL_E7X), + SND_PCI_QUIRK(0x1028, 0x05da, "Dell Vostro 5460", ALC290_FIXUP_SUBWOOFER), + SND_PCI_QUIRK(0x1028, 0x05f4, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x05f5, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x05f6, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x0615, "Dell Vostro 5470", ALC290_FIXUP_SUBWOOFER_HSJACK), + SND_PCI_QUIRK(0x1028, 0x0616, "Dell Vostro 5470", ALC290_FIXUP_SUBWOOFER_HSJACK), + SND_PCI_QUIRK(0x1028, 0x062c, "Dell Latitude E5550", ALC292_FIXUP_DELL_E7X), + SND_PCI_QUIRK(0x1028, 0x062e, "Dell Latitude E7450", ALC292_FIXUP_DELL_E7X), + SND_PCI_QUIRK(0x1028, 0x0638, "Dell Inspiron 5439", ALC290_FIXUP_MONO_SPEAKERS_HSJACK), + SND_PCI_QUIRK(0x1028, 0x064a, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x064b, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x0665, "Dell XPS 13", ALC288_FIXUP_DELL_XPS_13), + SND_PCI_QUIRK(0x1028, 0x0669, "Dell Optiplex 9020m", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x069a, "Dell Vostro 5480", ALC290_FIXUP_SUBWOOFER_HSJACK), + SND_PCI_QUIRK(0x1028, 0x06c7, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x06d9, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x06da, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x06db, "Dell", ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK), + SND_PCI_QUIRK(0x1028, 0x06dd, "Dell", ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK), + SND_PCI_QUIRK(0x1028, 0x06de, "Dell", ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK), + SND_PCI_QUIRK(0x1028, 0x06df, "Dell", ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK), + SND_PCI_QUIRK(0x1028, 0x06e0, "Dell", ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK), + SND_PCI_QUIRK(0x1028, 0x0706, "Dell Inspiron 7559", ALC256_FIXUP_DELL_INSPIRON_7559_SUBWOOFER), + SND_PCI_QUIRK(0x1028, 0x0725, "Dell Inspiron 3162", ALC255_FIXUP_DELL_SPK_NOISE), + SND_PCI_QUIRK(0x1028, 0x0738, "Dell Precision 5820", ALC269_FIXUP_NO_SHUTUP), + SND_PCI_QUIRK(0x1028, 0x075c, "Dell XPS 27 7760", ALC298_FIXUP_SPK_VOLUME), + SND_PCI_QUIRK(0x1028, 0x075d, "Dell AIO", ALC298_FIXUP_SPK_VOLUME), + SND_PCI_QUIRK(0x1028, 0x0798, "Dell Inspiron 17 7000 Gaming", ALC256_FIXUP_DELL_INSPIRON_7559_SUBWOOFER), + SND_PCI_QUIRK(0x1028, 0x07b0, "Dell Precision 7520", ALC295_FIXUP_DISABLE_DAC3), + SND_PCI_QUIRK(0x1028, 0x080c, "Dell WYSE", ALC225_FIXUP_DELL_WYSE_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x084b, "Dell", ALC274_FIXUP_DELL_AIO_LINEOUT_VERB), + SND_PCI_QUIRK(0x1028, 0x084e, "Dell", ALC274_FIXUP_DELL_AIO_LINEOUT_VERB), + SND_PCI_QUIRK(0x1028, 0x0871, "Dell Precision 3630", ALC255_FIXUP_DELL_HEADSET_MIC), + SND_PCI_QUIRK(0x1028, 0x0872, "Dell Precision 3630", ALC255_FIXUP_DELL_HEADSET_MIC), + SND_PCI_QUIRK(0x1028, 0x0873, "Dell Precision 3930", ALC255_FIXUP_DUMMY_LINEOUT_VERB), + SND_PCI_QUIRK(0x1028, 0x08ad, "Dell WYSE AIO", ALC225_FIXUP_DELL_WYSE_AIO_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x08ae, "Dell WYSE NB", ALC225_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x0935, "Dell", ALC274_FIXUP_DELL_AIO_LINEOUT_VERB), + SND_PCI_QUIRK(0x1028, 0x097d, "Dell Precision", ALC289_FIXUP_DUAL_SPK), + SND_PCI_QUIRK(0x1028, 0x097e, "Dell Precision", ALC289_FIXUP_DUAL_SPK), + SND_PCI_QUIRK(0x1028, 0x098d, "Dell Precision", ALC233_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x09bf, "Dell Precision", ALC233_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x0a2e, "Dell", ALC236_FIXUP_DELL_AIO_HEADSET_MIC), + SND_PCI_QUIRK(0x1028, 0x0a30, "Dell", ALC236_FIXUP_DELL_AIO_HEADSET_MIC), + SND_PCI_QUIRK(0x1028, 0x0a38, "Dell Latitude 7520", ALC269_FIXUP_DELL4_MIC_NO_PRESENCE_QUIET), + SND_PCI_QUIRK(0x1028, 0x0a58, "Dell", ALC255_FIXUP_DELL_HEADSET_MIC), + SND_PCI_QUIRK(0x1028, 0x0a61, "Dell XPS 15 9510", ALC289_FIXUP_DUAL_SPK), + SND_PCI_QUIRK(0x1028, 0x0a62, "Dell Precision 5560", ALC289_FIXUP_DUAL_SPK), + SND_PCI_QUIRK(0x1028, 0x0a9d, "Dell Latitude 5430", ALC269_FIXUP_DELL4_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x0a9e, "Dell Latitude 5430", ALC269_FIXUP_DELL4_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x0b19, "Dell XPS 15 9520", ALC289_FIXUP_DUAL_SPK), + SND_PCI_QUIRK(0x1028, 0x0b1a, "Dell Precision 5570", ALC289_FIXUP_DUAL_SPK), + SND_PCI_QUIRK(0x1028, 0x0b37, "Dell Inspiron 16 Plus 7620 2-in-1", ALC295_FIXUP_DELL_INSPIRON_TOP_SPEAKERS), + SND_PCI_QUIRK(0x1028, 0x0b71, "Dell Inspiron 16 Plus 7620", ALC295_FIXUP_DELL_INSPIRON_TOP_SPEAKERS), + SND_PCI_QUIRK(0x1028, 0x0c03, "Dell Precision 5340", ALC269_FIXUP_DELL4_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x0c19, "Dell Precision 3340", ALC236_FIXUP_DELL_DUAL_CODECS), + SND_PCI_QUIRK(0x1028, 0x0c1a, "Dell Precision 3340", ALC236_FIXUP_DELL_DUAL_CODECS), + SND_PCI_QUIRK(0x1028, 0x0c1b, "Dell Precision 3440", ALC236_FIXUP_DELL_DUAL_CODECS), + SND_PCI_QUIRK(0x1028, 0x0c1c, "Dell Precision 3540", ALC236_FIXUP_DELL_DUAL_CODECS), + SND_PCI_QUIRK(0x1028, 0x0c1d, "Dell Precision 3440", ALC236_FIXUP_DELL_DUAL_CODECS), + SND_PCI_QUIRK(0x1028, 0x0c1e, "Dell Precision 3540", ALC236_FIXUP_DELL_DUAL_CODECS), + SND_PCI_QUIRK(0x1028, 0x164a, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x164b, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x1586, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC2), + SND_PCI_QUIRK(0x103c, 0x18e6, "HP", ALC269_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x218b, "HP", ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x21f9, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2210, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2214, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x221b, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED), + SND_PCI_QUIRK(0x103c, 0x221c, "HP EliteBook 755 G2", ALC280_FIXUP_HP_HEADSET_MIC), + SND_PCI_QUIRK(0x103c, 0x2221, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED), + SND_PCI_QUIRK(0x103c, 0x2225, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED), + SND_PCI_QUIRK(0x103c, 0x2236, "HP", ALC269_FIXUP_HP_LINE1_MIC1_LED), + SND_PCI_QUIRK(0x103c, 0x2237, "HP", ALC269_FIXUP_HP_LINE1_MIC1_LED), + SND_PCI_QUIRK(0x103c, 0x2238, "HP", ALC269_FIXUP_HP_LINE1_MIC1_LED), + SND_PCI_QUIRK(0x103c, 0x2239, "HP", ALC269_FIXUP_HP_LINE1_MIC1_LED), + SND_PCI_QUIRK(0x103c, 0x224b, "HP", ALC269_FIXUP_HP_LINE1_MIC1_LED), + SND_PCI_QUIRK(0x103c, 0x2253, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED), + SND_PCI_QUIRK(0x103c, 0x2254, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED), + SND_PCI_QUIRK(0x103c, 0x2255, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED), + SND_PCI_QUIRK(0x103c, 0x2256, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED), + SND_PCI_QUIRK(0x103c, 0x2257, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED), + SND_PCI_QUIRK(0x103c, 0x2259, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED), + SND_PCI_QUIRK(0x103c, 0x225a, "HP", ALC269_FIXUP_HP_DOCK_GPIO_MIC1_LED), + SND_PCI_QUIRK(0x103c, 0x225f, "HP", ALC280_FIXUP_HP_GPIO2_MIC_HOTKEY), + SND_PCI_QUIRK(0x103c, 0x2260, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2263, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2264, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2265, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2268, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x226a, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x226b, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x226e, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2271, "HP", ALC286_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x2272, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED), + SND_PCI_QUIRK(0x103c, 0x2272, "HP", ALC280_FIXUP_HP_DOCK_PINS), + SND_PCI_QUIRK(0x103c, 0x2273, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED), + SND_PCI_QUIRK(0x103c, 0x2273, "HP", ALC280_FIXUP_HP_DOCK_PINS), + SND_PCI_QUIRK(0x103c, 0x2278, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED), + SND_PCI_QUIRK(0x103c, 0x227f, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2282, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x228b, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x228e, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x229e, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x22b2, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x22b7, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x22bf, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x22c4, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x22c5, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x22c7, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x22c8, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x22cf, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x22db, "HP", ALC280_FIXUP_HP_9480M), + SND_PCI_QUIRK(0x103c, 0x22dc, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED), + SND_PCI_QUIRK(0x103c, 0x22fb, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED), + SND_PCI_QUIRK(0x103c, 0x2334, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2335, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2336, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2337, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2b5e, "HP 288 Pro G2 MT", ALC221_FIXUP_HP_288PRO_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x802e, "HP Z240 SFF", ALC221_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x802f, "HP Z240", ALC221_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x8077, "HP", ALC256_FIXUP_HP_HEADSET_MIC), + SND_PCI_QUIRK(0x103c, 0x8158, "HP", ALC256_FIXUP_HP_HEADSET_MIC), + SND_PCI_QUIRK(0x103c, 0x820d, "HP Pavilion 15", ALC295_FIXUP_HP_X360), + SND_PCI_QUIRK(0x103c, 0x8256, "HP", ALC221_FIXUP_HP_FRONT_MIC), + SND_PCI_QUIRK(0x103c, 0x827e, "HP x360", ALC295_FIXUP_HP_X360), + SND_PCI_QUIRK(0x103c, 0x827f, "HP x360", ALC269_FIXUP_HP_MUTE_LED_MIC3), + SND_PCI_QUIRK(0x103c, 0x82bf, "HP G3 mini", ALC221_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x82c0, "HP G3 mini premium", ALC221_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x83b9, "HP Spectre x360", ALC269_FIXUP_HP_MUTE_LED_MIC3), + SND_PCI_QUIRK(0x103c, 0x841c, "HP Pavilion 15-CK0xx", ALC269_FIXUP_HP_MUTE_LED_MIC3), + SND_PCI_QUIRK(0x103c, 0x8497, "HP Envy x360", ALC269_FIXUP_HP_MUTE_LED_MIC3), + SND_PCI_QUIRK(0x103c, 0x84ae, "HP 15-db0403ng", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x84da, "HP OMEN dc0019-ur", ALC295_FIXUP_HP_OMEN), + SND_PCI_QUIRK(0x103c, 0x84e7, "HP Pavilion 15", ALC269_FIXUP_HP_MUTE_LED_MIC3), + SND_PCI_QUIRK(0x103c, 0x8519, "HP Spectre x360 15-df0xxx", ALC285_FIXUP_HP_SPECTRE_X360), + SND_PCI_QUIRK(0x103c, 0x8537, "HP ProBook 440 G6", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x860f, "HP ZBook 15 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x861f, "HP Elite Dragonfly G1", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x869d, "HP", ALC236_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x86c7, "HP Envy AiO 32", ALC274_FIXUP_HP_ENVY_GPIO), + SND_PCI_QUIRK(0x103c, 0x86e7, "HP Spectre x360 15-eb0xxx", ALC285_FIXUP_HP_SPECTRE_X360_EB1), + SND_PCI_QUIRK(0x103c, 0x86e8, "HP Spectre x360 15-eb0xxx", ALC285_FIXUP_HP_SPECTRE_X360_EB1), + SND_PCI_QUIRK(0x103c, 0x86f9, "HP Spectre x360 13-aw0xxx", ALC285_FIXUP_HP_SPECTRE_X360_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8716, "HP Elite Dragonfly G2 Notebook PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x8720, "HP EliteBook x360 1040 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x8724, "HP EliteBook 850 G7", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8728, "HP EliteBook 840 G7", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8729, "HP", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8730, "HP ProBook 445 G7", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8735, "HP ProBook 435 G7", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8736, "HP", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x8760, "HP", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x876e, "HP ENVY x360 Convertible 13-ay0xxx", ALC245_FIXUP_HP_X360_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x877a, "HP", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x877d, "HP", ALC236_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8780, "HP ZBook Fury 17 G7 Mobile Workstation", + ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x8783, "HP ZBook Fury 15 G7 Mobile Workstation", + ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x8786, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8787, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8788, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x87c8, "HP", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x87e5, "HP ProBook 440 G8 Notebook PC", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x87e7, "HP ProBook 450 G8 Notebook PC", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x87f1, "HP ProBook 630 G8 Notebook PC", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x87f2, "HP ProBook 640 G8 Notebook PC", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x87f4, "HP", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x87f5, "HP", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x87f6, "HP Spectre x360 14", ALC245_FIXUP_HP_X360_AMP), + SND_PCI_QUIRK(0x103c, 0x87f7, "HP Spectre x360 14", ALC245_FIXUP_HP_X360_AMP), + SND_PCI_QUIRK(0x103c, 0x87fe, "HP Laptop 15s-fq2xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8805, "HP ProBook 650 G8 Notebook PC", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x880d, "HP EliteBook 830 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8811, "HP Spectre x360 15-eb1xxx", ALC285_FIXUP_HP_SPECTRE_X360_EB1), + SND_PCI_QUIRK(0x103c, 0x8812, "HP Spectre x360 15-eb1xxx", ALC285_FIXUP_HP_SPECTRE_X360_EB1), + SND_PCI_QUIRK(0x103c, 0x881d, "HP 250 G8 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8846, "HP EliteBook 850 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8847, "HP EliteBook x360 830 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x884b, "HP EliteBook 840 Aero G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x884c, "HP EliteBook 840 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8862, "HP ProBook 445 G8 Notebook PC", ALC236_FIXUP_HP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x103c, 0x8863, "HP ProBook 445 G8 Notebook PC", ALC236_FIXUP_HP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x103c, 0x886d, "HP ZBook Fury 17.3 Inch G8 Mobile Workstation PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x8870, "HP ZBook Fury 15.6 Inch G8 Mobile Workstation PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x8873, "HP ZBook Studio 15.6 Inch G8 Mobile Workstation PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x887a, "HP Laptop 15s-eq2xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x888a, "HP ENVY x360 Convertible 15-eu0xxx", ALC245_FIXUP_HP_X360_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x888d, "HP ZBook Power 15.6 inch G8 Mobile Workstation PC", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8895, "HP EliteBook 855 G8 Notebook PC", ALC285_FIXUP_HP_SPEAKERS_MICMUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8896, "HP EliteBook 855 G8 Notebook PC", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8898, "HP EliteBook 845 G8 Notebook PC", ALC285_FIXUP_HP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x103c, 0x88d0, "HP Pavilion 15-eh1xxx (mainboard 88D0)", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8902, "HP OMEN 16", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x890e, "HP 255 G8 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8919, "HP Pavilion Aero Laptop 13-be0xxx", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x896d, "HP ZBook Firefly 16 G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x896e, "HP EliteBook x360 830 G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8971, "HP EliteBook 830 G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8972, "HP EliteBook 840 G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8973, "HP EliteBook 860 G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8974, "HP EliteBook 840 Aero G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8975, "HP EliteBook x360 840 Aero G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8981, "HP Elite Dragonfly G3", ALC245_FIXUP_CS35L41_SPI_4), + SND_PCI_QUIRK(0x103c, 0x898e, "HP EliteBook 835 G9", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x898f, "HP EliteBook 835 G9", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8991, "HP EliteBook 845 G9", ALC287_FIXUP_CS35L41_I2C_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8992, "HP EliteBook 845 G9", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8994, "HP EliteBook 855 G9", ALC287_FIXUP_CS35L41_I2C_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8995, "HP EliteBook 855 G9", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x89a4, "HP ProBook 440 G9", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x89a6, "HP ProBook 450 G9", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x89aa, "HP EliteBook 630 G9", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x89ac, "HP EliteBook 640 G9", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x89ae, "HP EliteBook 650 G9", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x89c0, "HP ZBook Power 15.6 G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x89c3, "Zbook Studio G9", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x89c6, "Zbook Fury 17 G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x89ca, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x89d3, "HP EliteBook 645 G9 (MB 89D2)", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8a0f, "HP Pavilion 14-ec1xxx", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8a20, "HP Laptop 15s-fq5xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8a25, "HP Victus 16-d1xxx (MB 8A25)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8a78, "HP Dev One", ALC285_FIXUP_HP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x103c, 0x8aa0, "HP ProBook 440 G9 (MB 8A9E)", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8aa3, "HP ProBook 450 G9 (MB 8AA1)", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8aa8, "HP EliteBook 640 G9 (MB 8AA6)", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8aab, "HP EliteBook 650 G9 (MB 8AA9)", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8abb, "HP ZBook Firefly 14 G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8ad1, "HP EliteBook 840 14 inch G9 Notebook PC", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8ad2, "HP EliteBook 860 16 inch G9 Notebook PC", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b2f, "HP 255 15.6 inch G10 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8b42, "HP", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b43, "HP", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b44, "HP", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b45, "HP", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b46, "HP", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b47, "HP", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b5d, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8b5e, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8b63, "HP Elite Dragonfly 13.5 inch G4", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b65, "HP ProBook 455 15.6 inch G10 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8b66, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8b70, "HP EliteBook 835 G10", ALC287_FIXUP_CS35L41_I2C_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b72, "HP EliteBook 845 G10", ALC287_FIXUP_CS35L41_I2C_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b74, "HP EliteBook 845W G10", ALC287_FIXUP_CS35L41_I2C_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b77, "HP ElieBook 865 G10", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8b7a, "HP", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b7d, "HP", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b87, "HP", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b8a, "HP", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b8b, "HP", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b8d, "HP", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b8f, "HP", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b92, "HP", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b96, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8bf0, "HP", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c46, "HP EliteBook 830 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c47, "HP EliteBook 840 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c48, "HP EliteBook 860 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c49, "HP Elite x360 830 2-in-1 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c70, "HP EliteBook 835 G11", ALC287_FIXUP_CS35L41_I2C_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c71, "HP EliteBook 845 G11", ALC287_FIXUP_CS35L41_I2C_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c72, "HP EliteBook 865 G11", ALC287_FIXUP_CS35L41_I2C_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c96, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8c97, "HP ZBook", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8ca4, "HP ZBook Fury", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8ca7, "HP ZBook Fury", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8cf5, "HP ZBook Studio 16", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), + SND_PCI_QUIRK(0x1043, 0x103e, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x103f, "ASUS TX300", ALC282_FIXUP_ASUS_TX300), + SND_PCI_QUIRK(0x1043, 0x106d, "Asus K53BE", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x10a1, "ASUS UX391UA", ALC294_FIXUP_ASUS_SPK), + SND_PCI_QUIRK(0x1043, 0x10c0, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x10d0, "ASUS X540LA/X540LJ", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x10d3, "ASUS K6500ZC", ALC294_FIXUP_ASUS_SPK), + SND_PCI_QUIRK(0x1043, 0x115d, "Asus 1015E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x11c0, "ASUS X556UR", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x125e, "ASUS Q524UQK", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1271, "ASUS X430UN", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1290, "ASUS X441SA", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x12a0, "ASUS X441UV", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x12a3, "Asus N7691ZM", ALC269_FIXUP_ASUS_N7601ZM), + SND_PCI_QUIRK(0x1043, 0x12af, "ASUS UX582ZS", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x12e0, "ASUS X541SA", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x12f0, "ASUS X541UV", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x1313, "Asus K42JZ", ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x13b0, "ASUS Z550SA", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x1427, "Asus Zenbook UX31E", ALC269VB_FIXUP_ASUS_ZENBOOK), + SND_PCI_QUIRK(0x1043, 0x1433, "ASUS GX650P", ALC285_FIXUP_ASUS_I2C_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1463, "Asus GA402X", ALC285_FIXUP_ASUS_I2C_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1473, "ASUS GU604V", ALC285_FIXUP_ASUS_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1483, "ASUS GU603V", ALC285_FIXUP_ASUS_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1517, "Asus Zenbook UX31A", ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A), + SND_PCI_QUIRK(0x1043, 0x1533, "ASUS GV302XA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x1573, "ASUS GZ301V", ALC285_FIXUP_ASUS_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1662, "ASUS GV301QH", ALC294_FIXUP_ASUS_DUAL_SPK), + SND_PCI_QUIRK(0x1043, 0x1663, "ASUS GU603ZV", ALC285_FIXUP_ASUS_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1683, "ASUS UM3402YAR", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x16b2, "ASUS GU603", ALC289_FIXUP_ASUS_GA401), + SND_PCI_QUIRK(0x1043, 0x16e3, "ASUS UX50", ALC269_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK(0x1043, 0x1740, "ASUS UX430UA", ALC295_FIXUP_ASUS_DACS), + SND_PCI_QUIRK(0x1043, 0x17d1, "ASUS UX431FL", ALC294_FIXUP_ASUS_DUAL_SPK), + SND_PCI_QUIRK(0x1043, 0x17f3, "ROG Ally RC71L_RC71L", ALC294_FIXUP_ASUS_ALLY), + SND_PCI_QUIRK(0x1043, 0x1881, "ASUS Zephyrus S/M", ALC294_FIXUP_ASUS_GX502_PINS), + SND_PCI_QUIRK(0x1043, 0x18b1, "Asus MJ401TA", ALC256_FIXUP_ASUS_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x18f1, "Asus FX505DT", ALC256_FIXUP_ASUS_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x194e, "ASUS UX563FD", ALC294_FIXUP_ASUS_HPE), + SND_PCI_QUIRK(0x1043, 0x1970, "ASUS UX550VE", ALC289_FIXUP_ASUS_GA401), + SND_PCI_QUIRK(0x1043, 0x1982, "ASUS B1400CEPE", ALC256_FIXUP_ASUS_HPE), + SND_PCI_QUIRK(0x1043, 0x19ce, "ASUS B9450FA", ALC294_FIXUP_ASUS_HPE), + SND_PCI_QUIRK(0x1043, 0x19e1, "ASUS UX581LV", ALC295_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1a13, "Asus G73Jw", ALC269_FIXUP_ASUS_G73JW), + SND_PCI_QUIRK(0x1043, 0x1a30, "ASUS X705UD", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x1a8f, "ASUS UX582ZS", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x1b11, "ASUS UX431DA", ALC294_FIXUP_ASUS_COEF_1B), + SND_PCI_QUIRK(0x1043, 0x1b13, "Asus U41SV", ALC269_FIXUP_INV_DMIC), + SND_PCI_QUIRK(0x1043, 0x1b93, "ASUS G614JVR/JIR", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x1bbd, "ASUS Z550MA", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1c23, "Asus X55U", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x1c62, "ASUS GU603", ALC289_FIXUP_ASUS_GA401), + SND_PCI_QUIRK(0x1043, 0x1c92, "ASUS ROG Strix G15", ALC285_FIXUP_ASUS_G533Z_PINS), + SND_PCI_QUIRK(0x1043, 0x1ccd, "ASUS X555UB", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x1d1f, "ASUS ROG Strix G17 2023 (G713PV)", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x1d42, "ASUS Zephyrus G14 2022", ALC289_FIXUP_ASUS_GA401), + SND_PCI_QUIRK(0x1043, 0x1d4e, "ASUS TM420", ALC256_FIXUP_ASUS_HPE), + SND_PCI_QUIRK(0x1043, 0x1da2, "ASUS UP6502ZA/ZD", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x1e02, "ASUS UX3402ZA", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x16a3, "ASUS UX3402VA", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x1f62, "ASUS UX7602ZM", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x1e11, "ASUS Zephyrus G15", ALC289_FIXUP_ASUS_GA502), + SND_PCI_QUIRK(0x1043, 0x1e12, "ASUS UM6702RA/RC", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x1e51, "ASUS Zephyrus M15", ALC294_FIXUP_ASUS_GU502_PINS), + SND_PCI_QUIRK(0x1043, 0x1e5e, "ASUS ROG Strix G513", ALC294_FIXUP_ASUS_G513_PINS), + SND_PCI_QUIRK(0x1043, 0x1e8e, "ASUS Zephyrus G15", ALC289_FIXUP_ASUS_GA401), + SND_PCI_QUIRK(0x1043, 0x1ee2, "ASUS UM3402", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x1c52, "ASUS Zephyrus G15 2022", ALC289_FIXUP_ASUS_GA401), + SND_PCI_QUIRK(0x1043, 0x1f11, "ASUS Zephyrus G14", ALC289_FIXUP_ASUS_GA401), + SND_PCI_QUIRK(0x1043, 0x1f12, "ASUS UM5302", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x1f62, "ASUS UX7602ZM", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x1f92, "ASUS ROG Flow X16", ALC289_FIXUP_ASUS_GA401), + SND_PCI_QUIRK(0x1043, 0x3030, "ASUS ZN270IE", ALC256_FIXUP_ASUS_AIO_GPIO2), + SND_PCI_QUIRK(0x1043, 0x3a20, "ASUS G614JZR", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x3a30, "ASUS G814JVR/JIR", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x3a40, "ASUS G814JZR", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x3a50, "ASUS G834JYR/JZR", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x3a60, "ASUS G634JYR/JZR", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x831a, "ASUS P901", ALC269_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK(0x1043, 0x834a, "ASUS S101", ALC269_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK(0x1043, 0x8398, "ASUS P1005", ALC269_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK(0x1043, 0x83ce, "ASUS P1005", ALC269_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK(0x1043, 0x8516, "ASUS X101CH", ALC269_FIXUP_ASUS_X101), + SND_PCI_QUIRK(0x104d, 0x9073, "Sony VAIO", ALC275_FIXUP_SONY_VAIO_GPIO2), + SND_PCI_QUIRK(0x104d, 0x907b, "Sony VAIO", ALC275_FIXUP_SONY_HWEQ), + SND_PCI_QUIRK(0x104d, 0x9084, "Sony VAIO", ALC275_FIXUP_SONY_HWEQ), + SND_PCI_QUIRK(0x104d, 0x9099, "Sony VAIO S13", ALC275_FIXUP_SONY_DISABLE_AAMIX), + SND_PCI_QUIRK(0x104d, 0x90b5, "Sony VAIO Pro 11", ALC286_FIXUP_SONY_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x104d, 0x90b6, "Sony VAIO Pro 13", ALC286_FIXUP_SONY_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x10cf, 0x1475, "Lifebook", ALC269_FIXUP_LIFEBOOK), + SND_PCI_QUIRK(0x10cf, 0x159f, "Lifebook E780", ALC269_FIXUP_LIFEBOOK_NO_HP_TO_LINEOUT), + SND_PCI_QUIRK(0x10cf, 0x15dc, "Lifebook T731", ALC269_FIXUP_LIFEBOOK_HP_PIN), + SND_PCI_QUIRK(0x10cf, 0x1629, "Lifebook U7x7", ALC255_FIXUP_LIFEBOOK_U7x7_HEADSET_MIC), + SND_PCI_QUIRK(0x10cf, 0x1757, "Lifebook E752", ALC269_FIXUP_LIFEBOOK_HP_PIN), + SND_PCI_QUIRK(0x10cf, 0x1845, "Lifebook U904", ALC269_FIXUP_LIFEBOOK_EXTMIC), + SND_PCI_QUIRK(0x10ec, 0x10f2, "Intel Reference board", ALC700_FIXUP_INTEL_REFERENCE), + SND_PCI_QUIRK(0x10ec, 0x118c, "Medion EE4254 MD62100", ALC256_FIXUP_MEDION_HEADSET_NO_PRESENCE), + SND_PCI_QUIRK(0x10ec, 0x1230, "Intel Reference board", ALC295_FIXUP_CHROME_BOOK), + SND_PCI_QUIRK(0x10ec, 0x124c, "Intel Reference board", ALC295_FIXUP_CHROME_BOOK), + SND_PCI_QUIRK(0x10ec, 0x1252, "Intel Reference board", ALC295_FIXUP_CHROME_BOOK), + SND_PCI_QUIRK(0x10ec, 0x1254, "Intel Reference board", ALC295_FIXUP_CHROME_BOOK), + SND_PCI_QUIRK(0x10ec, 0x12cc, "Intel Reference board", ALC295_FIXUP_CHROME_BOOK), + SND_PCI_QUIRK(0x10f7, 0x8338, "Panasonic CF-SZ6", ALC269_FIXUP_HEADSET_MODE), + SND_PCI_QUIRK(0x144d, 0xc109, "Samsung Ativ book 9 (NP900X3G)", ALC269_FIXUP_INV_DMIC), + SND_PCI_QUIRK(0x144d, 0xc169, "Samsung Notebook 9 Pen (NP930SBE-K01US)", ALC298_FIXUP_SAMSUNG_AMP), + SND_PCI_QUIRK(0x144d, 0xc176, "Samsung Notebook 9 Pro (NP930MBE-K04US)", ALC298_FIXUP_SAMSUNG_AMP), + SND_PCI_QUIRK(0x144d, 0xc189, "Samsung Galaxy Flex Book (NT950QCG-X716)", ALC298_FIXUP_SAMSUNG_AMP), + SND_PCI_QUIRK(0x144d, 0xc18a, "Samsung Galaxy Book Ion (NP930XCJ-K01US)", ALC298_FIXUP_SAMSUNG_AMP), + SND_PCI_QUIRK(0x144d, 0xc1a3, "Samsung Galaxy Book Pro (NP935XDB-KC1SE)", ALC298_FIXUP_SAMSUNG_AMP), + SND_PCI_QUIRK(0x144d, 0xc1a6, "Samsung Galaxy Book Pro 360 (NP930QBD)", ALC298_FIXUP_SAMSUNG_AMP), + SND_PCI_QUIRK(0x144d, 0xc740, "Samsung Ativ book 8 (NP870Z5G)", ALC269_FIXUP_ATIV_BOOK_8), + SND_PCI_QUIRK(0x144d, 0xc812, "Samsung Notebook Pen S (NT950SBE-X58)", ALC298_FIXUP_SAMSUNG_AMP), + SND_PCI_QUIRK(0x144d, 0xc830, "Samsung Galaxy Book Ion (NT950XCJ-X716A)", ALC298_FIXUP_SAMSUNG_AMP), + SND_PCI_QUIRK(0x144d, 0xc832, "Samsung Galaxy Book Flex Alpha (NP730QCJ)", ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET), + SND_PCI_QUIRK(0x144d, 0xca03, "Samsung Galaxy Book2 Pro 360 (NP930QED)", ALC298_FIXUP_SAMSUNG_AMP), + SND_PCI_QUIRK(0x144d, 0xc868, "Samsung Galaxy Book2 Pro (NP930XED)", ALC298_FIXUP_SAMSUNG_AMP), + SND_PCI_QUIRK(0x1458, 0xfa53, "Gigabyte BXBT-2807", ALC283_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1462, 0xb120, "MSI Cubi MS-B120", ALC283_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1462, 0xb171, "Cubi N 8GL (MS-B171)", ALC283_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x152d, 0x1082, "Quanta NL3", ALC269_FIXUP_LIFEBOOK), + SND_PCI_QUIRK(0x1558, 0x1323, "Clevo N130ZU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x1325, "Clevo N15[01][CW]U", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x1401, "Clevo L140[CZ]U", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x1403, "Clevo N140CU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x1404, "Clevo N150CU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x14a1, "Clevo L141MU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x4018, "Clevo NV40M[BE]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x4019, "Clevo NV40MZ", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x4020, "Clevo NV40MB", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x4041, "Clevo NV4[15]PZ", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x40a1, "Clevo NL40GU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x40c1, "Clevo NL40[CZ]U", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x40d1, "Clevo NL41DU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x5015, "Clevo NH5[58]H[HJK]Q", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x5017, "Clevo NH7[79]H[HJK]Q", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x50a3, "Clevo NJ51GU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x50b3, "Clevo NK50S[BEZ]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x50b6, "Clevo NK50S5", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x50b8, "Clevo NK50SZ", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x50d5, "Clevo NP50D5", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x50e1, "Clevo NH5[58]HPQ", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x50e2, "Clevo NH7[79]HPQ", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x50f0, "Clevo NH50A[CDF]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x50f2, "Clevo NH50E[PR]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x50f3, "Clevo NH58DPQ", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x50f5, "Clevo NH55EPY", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x50f6, "Clevo NH55DPQ", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x5101, "Clevo S510WU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x5157, "Clevo W517GU1", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x51a1, "Clevo NS50MU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x51b1, "Clevo NS50AU", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x51b3, "Clevo NS70AU", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x5630, "Clevo NP50RNJS", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x70a1, "Clevo NB70T[HJK]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x70b3, "Clevo NK70SB", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x70f2, "Clevo NH79EPY", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x70f3, "Clevo NH77DPQ", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x70f4, "Clevo NH77EPY", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x70f6, "Clevo NH77DPQ-Y", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x7716, "Clevo NS50PU", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x7717, "Clevo NS70PU", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x7718, "Clevo L140PU", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x7724, "Clevo L140AU", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x8228, "Clevo NR40BU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x8520, "Clevo NH50D[CD]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x8521, "Clevo NH77D[CD]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x8535, "Clevo NH50D[BE]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x8536, "Clevo NH79D[BE]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x8550, "Clevo NH[57][0-9][ER][ACDH]Q", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x8551, "Clevo NH[57][0-9][ER][ACDH]Q", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x8560, "Clevo NH[57][0-9][ER][ACDH]Q", ALC269_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1558, 0x8561, "Clevo NH[57][0-9][ER][ACDH]Q", ALC269_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1558, 0x8562, "Clevo NH[57][0-9]RZ[Q]", ALC269_FIXUP_DMIC), + SND_PCI_QUIRK(0x1558, 0x8668, "Clevo NP50B[BE]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x866d, "Clevo NP5[05]PN[HJK]", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x867c, "Clevo NP7[01]PNP", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x867d, "Clevo NP7[01]PN[HJK]", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x8680, "Clevo NJ50LU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x8686, "Clevo NH50[CZ]U", ALC256_FIXUP_MIC_NO_PRESENCE_AND_RESUME), + SND_PCI_QUIRK(0x1558, 0x8a20, "Clevo NH55DCQ-Y", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x8a51, "Clevo NH70RCQ-Y", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x8d50, "Clevo NH55RCQ-M", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x951d, "Clevo N950T[CDF]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x9600, "Clevo N960K[PR]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x961d, "Clevo N960S[CDF]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x971d, "Clevo N970T[CDF]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0xa500, "Clevo NL5[03]RU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0xa600, "Clevo NL50NU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0xa650, "Clevo NP[567]0SN[CD]", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0xa671, "Clevo NP70SN[CDE]", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0xb018, "Clevo NP50D[BE]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0xb019, "Clevo NH77D[BE]Q", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0xb022, "Clevo NH77D[DC][QW]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0xc018, "Clevo NP50D[BE]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0xc019, "Clevo NH77D[BE]Q", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0xc022, "Clevo NH77[DC][QW]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x17aa, 0x1036, "Lenovo P520", ALC233_FIXUP_LENOVO_MULTI_CODECS), + SND_PCI_QUIRK(0x17aa, 0x3882, "Lenovo Yoga Pro 7 14APH8", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x1048, "ThinkCentre Station", ALC623_FIXUP_LENOVO_THINKSTATION_P340), + SND_PCI_QUIRK(0x17aa, 0x20f2, "Thinkpad SL410/510", ALC269_FIXUP_SKU_IGNORE), + SND_PCI_QUIRK(0x17aa, 0x215e, "Thinkpad L512", ALC269_FIXUP_SKU_IGNORE), + SND_PCI_QUIRK(0x17aa, 0x21b8, "Thinkpad Edge 14", ALC269_FIXUP_SKU_IGNORE), + SND_PCI_QUIRK(0x17aa, 0x21ca, "Thinkpad L412", ALC269_FIXUP_SKU_IGNORE), + SND_PCI_QUIRK(0x17aa, 0x21e9, "Thinkpad Edge 15", ALC269_FIXUP_SKU_IGNORE), + SND_PCI_QUIRK(0x17aa, 0x21f3, "Thinkpad T430", ALC269_FIXUP_LENOVO_DOCK), + SND_PCI_QUIRK(0x17aa, 0x21f6, "Thinkpad T530", ALC269_FIXUP_LENOVO_DOCK_LIMIT_BOOST), + SND_PCI_QUIRK(0x17aa, 0x21fa, "Thinkpad X230", ALC269_FIXUP_LENOVO_DOCK), + SND_PCI_QUIRK(0x17aa, 0x21fb, "Thinkpad T430s", ALC269_FIXUP_LENOVO_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2203, "Thinkpad X230 Tablet", ALC269_FIXUP_LENOVO_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2208, "Thinkpad T431s", ALC269_FIXUP_LENOVO_DOCK), + SND_PCI_QUIRK(0x17aa, 0x220c, "Thinkpad T440s", ALC292_FIXUP_TPT440), + SND_PCI_QUIRK(0x17aa, 0x220e, "Thinkpad T440p", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2210, "Thinkpad T540p", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2211, "Thinkpad W541", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2212, "Thinkpad T440", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2214, "Thinkpad X240", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2215, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x2218, "Thinkpad X1 Carbon 2nd", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2223, "ThinkPad T550", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2226, "ThinkPad X250", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x222d, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x222e, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2231, "Thinkpad T560", ALC292_FIXUP_TPT460), + SND_PCI_QUIRK(0x17aa, 0x2233, "Thinkpad", ALC292_FIXUP_TPT460), + SND_PCI_QUIRK(0x17aa, 0x2245, "Thinkpad T470", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2246, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2247, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2249, "Thinkpad", ALC292_FIXUP_TPT460), + SND_PCI_QUIRK(0x17aa, 0x224b, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x224c, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x224d, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x225d, "Thinkpad T480", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x2292, "Thinkpad X1 Carbon 7th", ALC285_FIXUP_THINKPAD_HEADSET_JACK), + SND_PCI_QUIRK(0x17aa, 0x22be, "Thinkpad X1 Carbon 8th", ALC285_FIXUP_THINKPAD_HEADSET_JACK), + SND_PCI_QUIRK(0x17aa, 0x22c1, "Thinkpad P1 Gen 3", ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK), + SND_PCI_QUIRK(0x17aa, 0x22c2, "Thinkpad X1 Extreme Gen 3", ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK), + SND_PCI_QUIRK(0x17aa, 0x22f1, "Thinkpad", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + SND_PCI_QUIRK(0x17aa, 0x22f2, "Thinkpad", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + SND_PCI_QUIRK(0x17aa, 0x22f3, "Thinkpad", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + SND_PCI_QUIRK(0x17aa, 0x2316, "Thinkpad P1 Gen 6", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + SND_PCI_QUIRK(0x17aa, 0x2317, "Thinkpad P1 Gen 6", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + SND_PCI_QUIRK(0x17aa, 0x2318, "Thinkpad Z13 Gen2", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + SND_PCI_QUIRK(0x17aa, 0x2319, "Thinkpad Z16 Gen2", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + SND_PCI_QUIRK(0x17aa, 0x231a, "Thinkpad Z16 Gen2", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + SND_PCI_QUIRK(0x17aa, 0x30bb, "ThinkCentre AIO", ALC233_FIXUP_LENOVO_LINE2_MIC_HOTKEY), + SND_PCI_QUIRK(0x17aa, 0x30e2, "ThinkCentre AIO", ALC233_FIXUP_LENOVO_LINE2_MIC_HOTKEY), + SND_PCI_QUIRK(0x17aa, 0x310c, "ThinkCentre Station", ALC294_FIXUP_LENOVO_MIC_LOCATION), + SND_PCI_QUIRK(0x17aa, 0x3111, "ThinkCentre Station", ALC294_FIXUP_LENOVO_MIC_LOCATION), + SND_PCI_QUIRK(0x17aa, 0x312a, "ThinkCentre Station", ALC294_FIXUP_LENOVO_MIC_LOCATION), + SND_PCI_QUIRK(0x17aa, 0x312f, "ThinkCentre Station", ALC294_FIXUP_LENOVO_MIC_LOCATION), + SND_PCI_QUIRK(0x17aa, 0x313c, "ThinkCentre Station", ALC294_FIXUP_LENOVO_MIC_LOCATION), + SND_PCI_QUIRK(0x17aa, 0x3151, "ThinkCentre Station", ALC283_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x17aa, 0x3176, "ThinkCentre Station", ALC283_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x17aa, 0x3178, "ThinkCentre Station", ALC283_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x17aa, 0x31af, "ThinkCentre Station", ALC623_FIXUP_LENOVO_THINKSTATION_P340), + SND_PCI_QUIRK(0x17aa, 0x334b, "Lenovo ThinkCentre M70 Gen5", ALC283_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x17aa, 0x3801, "Lenovo Yoga9 14IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3802, "Lenovo Yoga DuetITL 2021", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3813, "Legion 7i 15IMHG05", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3818, "Lenovo C940 / Yoga Duet 7", ALC298_FIXUP_LENOVO_C940_DUET7), + SND_PCI_QUIRK(0x17aa, 0x3819, "Lenovo 13s Gen2 ITL", ALC287_FIXUP_13S_GEN2_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3820, "Yoga Duet 7 13ITL6", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3824, "Legion Y9000X 2020", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3827, "Ideapad S740", ALC285_FIXUP_IDEAPAD_S740_COEF), + SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3843, "Yoga 9i", ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP), + SND_PCI_QUIRK(0x17aa, 0x3847, "Legion 7 16ACHG6", ALC287_FIXUP_LEGION_16ACHG6), + SND_PCI_QUIRK(0x17aa, 0x384a, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3852, "Lenovo Yoga 7 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3853, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3855, "Legion 7 16ITHG6", ALC287_FIXUP_LEGION_16ITHG6), + SND_PCI_QUIRK(0x17aa, 0x3869, "Lenovo Yoga7 14IAL7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3902, "Lenovo E50-80", ALC269_FIXUP_DMIC_THINKPAD_ACPI), + SND_PCI_QUIRK(0x17aa, 0x3977, "IdeaPad S210", ALC283_FIXUP_INT_MIC), + SND_PCI_QUIRK(0x17aa, 0x3978, "Lenovo B50-70", ALC269_FIXUP_DMIC_THINKPAD_ACPI), + SND_PCI_QUIRK(0x17aa, 0x3bf8, "Quanta FL1", ALC269_FIXUP_PCM_44K), + SND_PCI_QUIRK(0x17aa, 0x5013, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x501a, "Thinkpad", ALC283_FIXUP_INT_MIC), + SND_PCI_QUIRK(0x17aa, 0x501e, "Thinkpad L440", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x5026, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x5034, "Thinkpad T450", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x5036, "Thinkpad T450s", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x503c, "Thinkpad L450", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x504a, "ThinkPad X260", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x504b, "Thinkpad", ALC293_FIXUP_LENOVO_SPK_NOISE), + SND_PCI_QUIRK(0x17aa, 0x5050, "Thinkpad T560p", ALC292_FIXUP_TPT460), + SND_PCI_QUIRK(0x17aa, 0x5051, "Thinkpad L460", ALC292_FIXUP_TPT460), + SND_PCI_QUIRK(0x17aa, 0x5053, "Thinkpad T460", ALC292_FIXUP_TPT460), + SND_PCI_QUIRK(0x17aa, 0x505d, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x505f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x5062, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x508b, "Thinkpad X12 Gen 1", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x5109, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x511e, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x511f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x9e54, "LENOVO NB", ALC269_FIXUP_LENOVO_EAPD), + SND_PCI_QUIRK(0x17aa, 0x9e56, "Lenovo ZhaoYang CF4620Z", ALC286_FIXUP_SONY_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1849, 0x1233, "ASRock NUC Box 1100", ALC233_FIXUP_NO_AUDIO_JACK), + SND_PCI_QUIRK(0x1849, 0xa233, "Positivo Master C6300", ALC269_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MACH-WX9", ALC256_FIXUP_HUAWEI_MACH_WX9_PINS), + SND_PCI_QUIRK(0x19e5, 0x320f, "Huawei WRT-WX9 ", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1b35, 0x1235, "CZC B20", ALC269_FIXUP_CZC_B20), + SND_PCI_QUIRK(0x1b35, 0x1236, "CZC TMI", ALC269_FIXUP_CZC_TMI), + SND_PCI_QUIRK(0x1b35, 0x1237, "CZC L101", ALC269_FIXUP_CZC_L101), + SND_PCI_QUIRK(0x1b7d, 0xa831, "Ordissimo EVE2 ", ALC269VB_FIXUP_ORDISSIMO_EVE2), /* Also known as Malata PC-B1303 */ + SND_PCI_QUIRK(0x1c06, 0x2013, "Lemote A1802", ALC269_FIXUP_LEMOTE_A1802), + SND_PCI_QUIRK(0x1c06, 0x2015, "Lemote A190X", ALC269_FIXUP_LEMOTE_A190X), + SND_PCI_QUIRK(0x1c6c, 0x1251, "Positivo N14KP6-TG", ALC288_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1d05, 0x1132, "TongFang PHxTxX1", ALC256_FIXUP_SET_COEF_DEFAULTS), + SND_PCI_QUIRK(0x1d05, 0x1096, "TongFang GMxMRxx", ALC269_FIXUP_NO_SHUTUP), + SND_PCI_QUIRK(0x1d05, 0x1100, "TongFang GKxNRxx", ALC269_FIXUP_NO_SHUTUP), + SND_PCI_QUIRK(0x1d05, 0x1111, "TongFang GMxZGxx", ALC269_FIXUP_NO_SHUTUP), + SND_PCI_QUIRK(0x1d05, 0x1119, "TongFang GMxZGxx", ALC269_FIXUP_NO_SHUTUP), + SND_PCI_QUIRK(0x1d05, 0x1129, "TongFang GMxZGxx", ALC269_FIXUP_NO_SHUTUP), + SND_PCI_QUIRK(0x1d05, 0x1147, "TongFang GMxTGxx", ALC269_FIXUP_NO_SHUTUP), + SND_PCI_QUIRK(0x1d05, 0x115c, "TongFang GMxTGxx", ALC269_FIXUP_NO_SHUTUP), + SND_PCI_QUIRK(0x1d05, 0x121b, "TongFang GMxAGxx", ALC269_FIXUP_NO_SHUTUP), + SND_PCI_QUIRK(0x1d72, 0x1602, "RedmiBook", ALC255_FIXUP_XIAOMI_HEADSET_MIC), + SND_PCI_QUIRK(0x1d72, 0x1701, "XiaomiNotebook Pro", ALC298_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1d72, 0x1901, "RedmiBook 14", ALC256_FIXUP_ASUS_HEADSET_MIC), + SND_PCI_QUIRK(0x1d72, 0x1945, "Redmi G", ALC256_FIXUP_ASUS_HEADSET_MIC), + SND_PCI_QUIRK(0x1d72, 0x1947, "RedmiBook Air", ALC255_FIXUP_XIAOMI_HEADSET_MIC), + SND_PCI_QUIRK(0x2782, 0x0232, "CHUWI CoreBook XPro", ALC269VB_FIXUP_CHUWI_COREBOOK_XPRO), + SND_PCI_QUIRK(0x8086, 0x2074, "Intel NUC 8", ALC233_FIXUP_INTEL_NUC8_DMIC), + SND_PCI_QUIRK(0x8086, 0x2080, "Intel NUC 8 Rugged", ALC256_FIXUP_INTEL_NUC8_RUGGED), + SND_PCI_QUIRK(0x8086, 0x2081, "Intel NUC 10", ALC256_FIXUP_INTEL_NUC10), + SND_PCI_QUIRK(0x8086, 0x3038, "Intel NUC 13", ALC295_FIXUP_CHROME_BOOK), + SND_PCI_QUIRK(0xf111, 0x0001, "Framework Laptop", ALC295_FIXUP_FRAMEWORK_LAPTOP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0xf111, 0x0005, "Framework Laptop", ALC295_FIXUP_FRAMEWORK_LAPTOP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0xf111, 0x0006, "Framework Laptop", ALC295_FIXUP_FRAMEWORK_LAPTOP_MIC_NO_PRESENCE), + +#if 0 + /* Below is a quirk table taken from the old code. + * Basically the device should work as is without the fixup table. + * If BIOS doesn't give a proper info, enable the corresponding + * fixup entry. + */ + SND_PCI_QUIRK(0x1043, 0x8330, "ASUS Eeepc P703 P900A", + ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x1013, "ASUS N61Da", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x1143, "ASUS B53f", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x1133, "ASUS UJ20ft", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x1183, "ASUS K72DR", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x11b3, "ASUS K52DR", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x11e3, "ASUS U33Jc", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x1273, "ASUS UL80Jt", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x1283, "ASUS U53Jc", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x12b3, "ASUS N82JV", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x12d3, "ASUS N61Jv", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x13a3, "ASUS UL30Vt", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x1373, "ASUS G73JX", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x1383, "ASUS UJ30Jc", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x13d3, "ASUS N61JA", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x1413, "ASUS UL50", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x1443, "ASUS UL30", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x1453, "ASUS M60Jv", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x1483, "ASUS UL80", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x14f3, "ASUS F83Vf", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x14e3, "ASUS UL20", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x1513, "ASUS UX30", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x1593, "ASUS N51Vn", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x15a3, "ASUS N60Jv", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x15b3, "ASUS N60Dp", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x15c3, "ASUS N70De", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x15e3, "ASUS F83T", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x1643, "ASUS M60J", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x1653, "ASUS U50", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x1693, "ASUS F50N", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x16a3, "ASUS F5Q", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x1723, "ASUS P80", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x1743, "ASUS U80", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x1773, "ASUS U20A", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x1043, 0x1883, "ASUS F81Se", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x152d, 0x1778, "Quanta ON1", ALC269_FIXUP_DMIC), + SND_PCI_QUIRK(0x17aa, 0x3be9, "Quanta Wistron", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x17aa, 0x3bf8, "Quanta FL1", ALC269_FIXUP_AMIC), + SND_PCI_QUIRK(0x17ff, 0x059a, "Quanta EL3", ALC269_FIXUP_DMIC), + SND_PCI_QUIRK(0x17ff, 0x059b, "Quanta JR1", ALC269_FIXUP_DMIC), +#endif + {} +}; + +static const struct snd_pci_quirk alc269_fixup_vendor_tbl[] = { + SND_PCI_QUIRK_VENDOR(0x1025, "Acer Aspire", ALC271_FIXUP_DMIC), + SND_PCI_QUIRK_VENDOR(0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK_VENDOR(0x104d, "Sony VAIO", ALC269_FIXUP_SONY_VAIO), + SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad", ALC269_FIXUP_THINKPAD_ACPI), + SND_PCI_QUIRK_VENDOR(0x19e5, "Huawei Matebook", ALC255_FIXUP_MIC_MUTE_LED), + {} +}; + +static const struct hda_model_fixup alc269_fixup_models[] = { + {.id = ALC269_FIXUP_AMIC, .name = "laptop-amic"}, + {.id = ALC269_FIXUP_DMIC, .name = "laptop-dmic"}, + {.id = ALC269_FIXUP_STEREO_DMIC, .name = "alc269-dmic"}, + {.id = ALC271_FIXUP_DMIC, .name = "alc271-dmic"}, + {.id = ALC269_FIXUP_INV_DMIC, .name = "inv-dmic"}, + {.id = ALC269_FIXUP_HEADSET_MIC, .name = "headset-mic"}, + {.id = ALC269_FIXUP_HEADSET_MODE, .name = "headset-mode"}, + {.id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC, .name = "headset-mode-no-hp-mic"}, + {.id = ALC269_FIXUP_LENOVO_DOCK, .name = "lenovo-dock"}, + {.id = ALC269_FIXUP_LENOVO_DOCK_LIMIT_BOOST, .name = "lenovo-dock-limit-boost"}, + {.id = ALC269_FIXUP_HP_GPIO_LED, .name = "hp-gpio-led"}, + {.id = ALC269_FIXUP_HP_DOCK_GPIO_MIC1_LED, .name = "hp-dock-gpio-mic1-led"}, + {.id = ALC269_FIXUP_DELL1_MIC_NO_PRESENCE, .name = "dell-headset-multi"}, + {.id = ALC269_FIXUP_DELL2_MIC_NO_PRESENCE, .name = "dell-headset-dock"}, + {.id = ALC269_FIXUP_DELL3_MIC_NO_PRESENCE, .name = "dell-headset3"}, + {.id = ALC269_FIXUP_DELL4_MIC_NO_PRESENCE, .name = "dell-headset4"}, + {.id = ALC283_FIXUP_CHROME_BOOK, .name = "alc283-dac-wcaps"}, + {.id = ALC283_FIXUP_SENSE_COMBO_JACK, .name = "alc283-sense-combo"}, + {.id = ALC292_FIXUP_TPT440_DOCK, .name = "tpt440-dock"}, + {.id = ALC292_FIXUP_TPT440, .name = "tpt440"}, + {.id = ALC292_FIXUP_TPT460, .name = "tpt460"}, + {.id = ALC298_FIXUP_TPT470_DOCK_FIX, .name = "tpt470-dock-fix"}, + {.id = ALC298_FIXUP_TPT470_DOCK, .name = "tpt470-dock"}, + {.id = ALC233_FIXUP_LENOVO_MULTI_CODECS, .name = "dual-codecs"}, + {.id = ALC700_FIXUP_INTEL_REFERENCE, .name = "alc700-ref"}, + {.id = ALC269_FIXUP_SONY_VAIO, .name = "vaio"}, + {.id = ALC269_FIXUP_DELL_M101Z, .name = "dell-m101z"}, + {.id = ALC269_FIXUP_ASUS_G73JW, .name = "asus-g73jw"}, + {.id = ALC269_FIXUP_LENOVO_EAPD, .name = "lenovo-eapd"}, + {.id = ALC275_FIXUP_SONY_HWEQ, .name = "sony-hweq"}, + {.id = ALC269_FIXUP_PCM_44K, .name = "pcm44k"}, + {.id = ALC269_FIXUP_LIFEBOOK, .name = "lifebook"}, + {.id = ALC269_FIXUP_LIFEBOOK_EXTMIC, .name = "lifebook-extmic"}, + {.id = ALC269_FIXUP_LIFEBOOK_HP_PIN, .name = "lifebook-hp-pin"}, + {.id = ALC255_FIXUP_LIFEBOOK_U7x7_HEADSET_MIC, .name = "lifebook-u7x7"}, + {.id = ALC269VB_FIXUP_AMIC, .name = "alc269vb-amic"}, + {.id = ALC269VB_FIXUP_DMIC, .name = "alc269vb-dmic"}, + {.id = ALC269_FIXUP_HP_MUTE_LED_MIC1, .name = "hp-mute-led-mic1"}, + {.id = ALC269_FIXUP_HP_MUTE_LED_MIC2, .name = "hp-mute-led-mic2"}, + {.id = ALC269_FIXUP_HP_MUTE_LED_MIC3, .name = "hp-mute-led-mic3"}, + {.id = ALC269_FIXUP_HP_GPIO_MIC1_LED, .name = "hp-gpio-mic1"}, + {.id = ALC269_FIXUP_HP_LINE1_MIC1_LED, .name = "hp-line1-mic1"}, + {.id = ALC269_FIXUP_NO_SHUTUP, .name = "noshutup"}, + {.id = ALC286_FIXUP_SONY_MIC_NO_PRESENCE, .name = "sony-nomic"}, + {.id = ALC269_FIXUP_ASPIRE_HEADSET_MIC, .name = "aspire-headset-mic"}, + {.id = ALC269_FIXUP_ASUS_X101, .name = "asus-x101"}, + {.id = ALC271_FIXUP_HP_GATE_MIC_JACK, .name = "acer-ao7xx"}, + {.id = ALC271_FIXUP_HP_GATE_MIC_JACK_E1_572, .name = "acer-aspire-e1"}, + {.id = ALC269_FIXUP_ACER_AC700, .name = "acer-ac700"}, + {.id = ALC269_FIXUP_LIMIT_INT_MIC_BOOST, .name = "limit-mic-boost"}, + {.id = ALC269VB_FIXUP_ASUS_ZENBOOK, .name = "asus-zenbook"}, + {.id = ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A, .name = "asus-zenbook-ux31a"}, + {.id = ALC269VB_FIXUP_ORDISSIMO_EVE2, .name = "ordissimo"}, + {.id = ALC282_FIXUP_ASUS_TX300, .name = "asus-tx300"}, + {.id = ALC283_FIXUP_INT_MIC, .name = "alc283-int-mic"}, + {.id = ALC290_FIXUP_MONO_SPEAKERS_HSJACK, .name = "mono-speakers"}, + {.id = ALC290_FIXUP_SUBWOOFER_HSJACK, .name = "alc290-subwoofer"}, + {.id = ALC269_FIXUP_THINKPAD_ACPI, .name = "thinkpad"}, + {.id = ALC269_FIXUP_DMIC_THINKPAD_ACPI, .name = "dmic-thinkpad"}, + {.id = ALC255_FIXUP_ACER_MIC_NO_PRESENCE, .name = "alc255-acer"}, + {.id = ALC255_FIXUP_ASUS_MIC_NO_PRESENCE, .name = "alc255-asus"}, + {.id = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, .name = "alc255-dell1"}, + {.id = ALC255_FIXUP_DELL2_MIC_NO_PRESENCE, .name = "alc255-dell2"}, + {.id = ALC293_FIXUP_DELL1_MIC_NO_PRESENCE, .name = "alc293-dell1"}, + {.id = ALC283_FIXUP_HEADSET_MIC, .name = "alc283-headset"}, + {.id = ALC255_FIXUP_MIC_MUTE_LED, .name = "alc255-dell-mute"}, + {.id = ALC282_FIXUP_ASPIRE_V5_PINS, .name = "aspire-v5"}, + {.id = ALC269VB_FIXUP_ASPIRE_E1_COEF, .name = "aspire-e1-coef"}, + {.id = ALC280_FIXUP_HP_GPIO4, .name = "hp-gpio4"}, + {.id = ALC286_FIXUP_HP_GPIO_LED, .name = "hp-gpio-led"}, + {.id = ALC280_FIXUP_HP_GPIO2_MIC_HOTKEY, .name = "hp-gpio2-hotkey"}, + {.id = ALC280_FIXUP_HP_DOCK_PINS, .name = "hp-dock-pins"}, + {.id = ALC269_FIXUP_HP_DOCK_GPIO_MIC1_LED, .name = "hp-dock-gpio-mic"}, + {.id = ALC280_FIXUP_HP_9480M, .name = "hp-9480m"}, + {.id = ALC288_FIXUP_DELL_HEADSET_MODE, .name = "alc288-dell-headset"}, + {.id = ALC288_FIXUP_DELL1_MIC_NO_PRESENCE, .name = "alc288-dell1"}, + {.id = ALC288_FIXUP_DELL_XPS_13, .name = "alc288-dell-xps13"}, + {.id = ALC292_FIXUP_DELL_E7X, .name = "dell-e7x"}, + {.id = ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK, .name = "alc293-dell"}, + {.id = ALC298_FIXUP_DELL1_MIC_NO_PRESENCE, .name = "alc298-dell1"}, + {.id = ALC298_FIXUP_DELL_AIO_MIC_NO_PRESENCE, .name = "alc298-dell-aio"}, + {.id = ALC275_FIXUP_DELL_XPS, .name = "alc275-dell-xps"}, + {.id = ALC293_FIXUP_LENOVO_SPK_NOISE, .name = "lenovo-spk-noise"}, + {.id = ALC233_FIXUP_LENOVO_LINE2_MIC_HOTKEY, .name = "lenovo-hotkey"}, + {.id = ALC255_FIXUP_DELL_SPK_NOISE, .name = "dell-spk-noise"}, + {.id = ALC225_FIXUP_DELL1_MIC_NO_PRESENCE, .name = "alc225-dell1"}, + {.id = ALC295_FIXUP_DISABLE_DAC3, .name = "alc295-disable-dac3"}, + {.id = ALC285_FIXUP_SPEAKER2_TO_DAC1, .name = "alc285-speaker2-to-dac1"}, + {.id = ALC280_FIXUP_HP_HEADSET_MIC, .name = "alc280-hp-headset"}, + {.id = ALC221_FIXUP_HP_FRONT_MIC, .name = "alc221-hp-mic"}, + {.id = ALC298_FIXUP_SPK_VOLUME, .name = "alc298-spk-volume"}, + {.id = ALC256_FIXUP_DELL_INSPIRON_7559_SUBWOOFER, .name = "dell-inspiron-7559"}, + {.id = ALC269_FIXUP_ATIV_BOOK_8, .name = "ativ-book"}, + {.id = ALC221_FIXUP_HP_MIC_NO_PRESENCE, .name = "alc221-hp-mic"}, + {.id = ALC256_FIXUP_ASUS_HEADSET_MODE, .name = "alc256-asus-headset"}, + {.id = ALC256_FIXUP_ASUS_MIC, .name = "alc256-asus-mic"}, + {.id = ALC256_FIXUP_ASUS_AIO_GPIO2, .name = "alc256-asus-aio"}, + {.id = ALC233_FIXUP_ASUS_MIC_NO_PRESENCE, .name = "alc233-asus"}, + {.id = ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE, .name = "alc233-eapd"}, + {.id = ALC294_FIXUP_LENOVO_MIC_LOCATION, .name = "alc294-lenovo-mic"}, + {.id = ALC225_FIXUP_DELL_WYSE_MIC_NO_PRESENCE, .name = "alc225-wyse"}, + {.id = ALC274_FIXUP_DELL_AIO_LINEOUT_VERB, .name = "alc274-dell-aio"}, + {.id = ALC255_FIXUP_DUMMY_LINEOUT_VERB, .name = "alc255-dummy-lineout"}, + {.id = ALC255_FIXUP_DELL_HEADSET_MIC, .name = "alc255-dell-headset"}, + {.id = ALC295_FIXUP_HP_X360, .name = "alc295-hp-x360"}, + {.id = ALC225_FIXUP_HEADSET_JACK, .name = "alc-headset-jack"}, + {.id = ALC295_FIXUP_CHROME_BOOK, .name = "alc-chrome-book"}, + {.id = ALC299_FIXUP_PREDATOR_SPK, .name = "predator-spk"}, + {.id = ALC298_FIXUP_HUAWEI_MBX_STEREO, .name = "huawei-mbx-stereo"}, + {.id = ALC256_FIXUP_MEDION_HEADSET_NO_PRESENCE, .name = "alc256-medion-headset"}, + {.id = ALC298_FIXUP_SAMSUNG_AMP, .name = "alc298-samsung-amp"}, + {.id = ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, .name = "alc256-samsung-headphone"}, + {.id = ALC255_FIXUP_XIAOMI_HEADSET_MIC, .name = "alc255-xiaomi-headset"}, + {.id = ALC274_FIXUP_HP_MIC, .name = "alc274-hp-mic-detect"}, + {.id = ALC245_FIXUP_HP_X360_AMP, .name = "alc245-hp-x360-amp"}, + {.id = ALC295_FIXUP_HP_OMEN, .name = "alc295-hp-omen"}, + {.id = ALC285_FIXUP_HP_SPECTRE_X360, .name = "alc285-hp-spectre-x360"}, + {.id = ALC285_FIXUP_HP_SPECTRE_X360_EB1, .name = "alc285-hp-spectre-x360-eb1"}, + {.id = ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP, .name = "alc287-ideapad-bass-spk-amp"}, + {.id = ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN, .name = "alc287-yoga9-bass-spk-pin"}, + {.id = ALC623_FIXUP_LENOVO_THINKSTATION_P340, .name = "alc623-lenovo-thinkstation-p340"}, + {.id = ALC255_FIXUP_ACER_HEADPHONE_AND_MIC, .name = "alc255-acer-headphone-and-mic"}, + {.id = ALC285_FIXUP_HP_GPIO_AMP_INIT, .name = "alc285-hp-amp-init"}, + {} +}; +#define ALC225_STANDARD_PINS \ + {0x21, 0x04211020} + +#define ALC256_STANDARD_PINS \ + {0x12, 0x90a60140}, \ + {0x14, 0x90170110}, \ + {0x21, 0x02211020} + +#define ALC282_STANDARD_PINS \ + {0x14, 0x90170110} + +#define ALC290_STANDARD_PINS \ + {0x12, 0x99a30130} + +#define ALC292_STANDARD_PINS \ + {0x14, 0x90170110}, \ + {0x15, 0x0221401f} + +#define ALC295_STANDARD_PINS \ + {0x12, 0xb7a60130}, \ + {0x14, 0x90170110}, \ + {0x21, 0x04211020} + +#define ALC298_STANDARD_PINS \ + {0x12, 0x90a60130}, \ + {0x21, 0x03211020} + +static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = { + SND_HDA_PIN_QUIRK(0x10ec0221, 0x103c, "HP Workstation", ALC221_FIXUP_HP_HEADSET_MIC, + {0x14, 0x01014020}, + {0x17, 0x90170110}, + {0x18, 0x02a11030}, + {0x19, 0x0181303F}, + {0x21, 0x0221102f}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1025, "Acer", ALC255_FIXUP_ACER_MIC_NO_PRESENCE, + {0x12, 0x90a601c0}, + {0x14, 0x90171120}, + {0x21, 0x02211030}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1043, "ASUS", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE, + {0x14, 0x90170110}, + {0x1b, 0x90a70130}, + {0x21, 0x03211020}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1043, "ASUS", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE, + {0x1a, 0x90a70130}, + {0x1b, 0x90170110}, + {0x21, 0x03211020}), + SND_HDA_PIN_QUIRK(0x10ec0225, 0x1028, "Dell", ALC225_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC225_STANDARD_PINS, + {0x12, 0xb7a60130}, + {0x14, 0x901701a0}), + SND_HDA_PIN_QUIRK(0x10ec0225, 0x1028, "Dell", ALC225_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC225_STANDARD_PINS, + {0x12, 0xb7a60130}, + {0x14, 0x901701b0}), + SND_HDA_PIN_QUIRK(0x10ec0225, 0x1028, "Dell", ALC225_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC225_STANDARD_PINS, + {0x12, 0xb7a60150}, + {0x14, 0x901701a0}), + SND_HDA_PIN_QUIRK(0x10ec0225, 0x1028, "Dell", ALC225_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC225_STANDARD_PINS, + {0x12, 0xb7a60150}, + {0x14, 0x901701b0}), + SND_HDA_PIN_QUIRK(0x10ec0225, 0x1028, "Dell", ALC225_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC225_STANDARD_PINS, + {0x12, 0xb7a60130}, + {0x1b, 0x90170110}), + SND_HDA_PIN_QUIRK(0x10ec0233, 0x8086, "Intel NUC Skull Canyon", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x1b, 0x01111010}, + {0x1e, 0x01451130}, + {0x21, 0x02211020}), + SND_HDA_PIN_QUIRK(0x10ec0235, 0x17aa, "Lenovo", ALC233_FIXUP_LENOVO_LINE2_MIC_HOTKEY, + {0x12, 0x90a60140}, + {0x14, 0x90170110}, + {0x19, 0x02a11030}, + {0x21, 0x02211020}), + SND_HDA_PIN_QUIRK(0x10ec0235, 0x17aa, "Lenovo", ALC294_FIXUP_LENOVO_MIC_LOCATION, + {0x14, 0x90170110}, + {0x19, 0x02a11030}, + {0x1a, 0x02a11040}, + {0x1b, 0x01014020}, + {0x21, 0x0221101f}), + SND_HDA_PIN_QUIRK(0x10ec0235, 0x17aa, "Lenovo", ALC294_FIXUP_LENOVO_MIC_LOCATION, + {0x14, 0x90170110}, + {0x19, 0x02a11030}, + {0x1a, 0x02a11040}, + {0x1b, 0x01011020}, + {0x21, 0x0221101f}), + SND_HDA_PIN_QUIRK(0x10ec0235, 0x17aa, "Lenovo", ALC294_FIXUP_LENOVO_MIC_LOCATION, + {0x14, 0x90170110}, + {0x19, 0x02a11020}, + {0x1a, 0x02a11030}, + {0x21, 0x0221101f}), + SND_HDA_PIN_QUIRK(0x10ec0236, 0x1028, "Dell", ALC236_FIXUP_DELL_AIO_HEADSET_MIC, + {0x21, 0x02211010}), + SND_HDA_PIN_QUIRK(0x10ec0236, 0x103c, "HP", ALC256_FIXUP_HP_HEADSET_MIC, + {0x14, 0x90170110}, + {0x19, 0x02a11020}, + {0x21, 0x02211030}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL2_MIC_NO_PRESENCE, + {0x14, 0x90170110}, + {0x21, 0x02211020}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x14, 0x90170130}, + {0x21, 0x02211040}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60140}, + {0x14, 0x90170110}, + {0x21, 0x02211020}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60160}, + {0x14, 0x90170120}, + {0x21, 0x02211030}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x14, 0x90170110}, + {0x1b, 0x02011020}, + {0x21, 0x0221101f}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x14, 0x90170110}, + {0x1b, 0x01011020}, + {0x21, 0x0221101f}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x14, 0x90170130}, + {0x1b, 0x01014020}, + {0x21, 0x0221103f}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x14, 0x90170130}, + {0x1b, 0x01011020}, + {0x21, 0x0221103f}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x14, 0x90170130}, + {0x1b, 0x02011020}, + {0x21, 0x0221103f}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x14, 0x90170150}, + {0x1b, 0x02011020}, + {0x21, 0x0221105f}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x14, 0x90170110}, + {0x1b, 0x01014020}, + {0x21, 0x0221101f}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60160}, + {0x14, 0x90170120}, + {0x17, 0x90170140}, + {0x21, 0x0321102f}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60160}, + {0x14, 0x90170130}, + {0x21, 0x02211040}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60160}, + {0x14, 0x90170140}, + {0x21, 0x02211050}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60170}, + {0x14, 0x90170120}, + {0x21, 0x02211030}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60170}, + {0x14, 0x90170130}, + {0x21, 0x02211040}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60170}, + {0x14, 0x90171130}, + {0x21, 0x02211040}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60170}, + {0x14, 0x90170140}, + {0x21, 0x02211050}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell Inspiron 5548", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60180}, + {0x14, 0x90170130}, + {0x21, 0x02211040}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell Inspiron 5565", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60180}, + {0x14, 0x90170120}, + {0x21, 0x02211030}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x1b, 0x01011020}, + {0x21, 0x02211010}), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC256_FIXUP_ASUS_MIC, + {0x14, 0x90170110}, + {0x1b, 0x90a70130}, + {0x21, 0x04211020}), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC256_FIXUP_ASUS_MIC, + {0x14, 0x90170110}, + {0x1b, 0x90a70130}, + {0x21, 0x03211020}), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE, + {0x12, 0x90a60130}, + {0x14, 0x90170110}, + {0x21, 0x03211020}), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE, + {0x12, 0x90a60130}, + {0x14, 0x90170110}, + {0x21, 0x04211020}), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE, + {0x1a, 0x90a70130}, + {0x1b, 0x90170110}, + {0x21, 0x03211020}), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x103c, "HP", ALC256_FIXUP_HP_HEADSET_MIC, + {0x14, 0x90170110}, + {0x19, 0x02a11020}, + {0x21, 0x0221101f}), + SND_HDA_PIN_QUIRK(0x10ec0274, 0x103c, "HP", ALC274_FIXUP_HP_HEADSET_MIC, + {0x17, 0x90170110}, + {0x19, 0x03a11030}, + {0x21, 0x03211020}), + SND_HDA_PIN_QUIRK(0x10ec0280, 0x103c, "HP", ALC280_FIXUP_HP_GPIO4, + {0x12, 0x90a60130}, + {0x14, 0x90170110}, + {0x15, 0x0421101f}, + {0x1a, 0x04a11020}), + SND_HDA_PIN_QUIRK(0x10ec0280, 0x103c, "HP", ALC269_FIXUP_HP_GPIO_MIC1_LED, + {0x12, 0x90a60140}, + {0x14, 0x90170110}, + {0x15, 0x0421101f}, + {0x18, 0x02811030}, + {0x1a, 0x04a1103f}, + {0x1b, 0x02011020}), + SND_HDA_PIN_QUIRK(0x10ec0282, 0x103c, "HP 15 Touchsmart", ALC269_FIXUP_HP_MUTE_LED_MIC1, + ALC282_STANDARD_PINS, + {0x12, 0x99a30130}, + {0x19, 0x03a11020}, + {0x21, 0x0321101f}), + SND_HDA_PIN_QUIRK(0x10ec0282, 0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1, + ALC282_STANDARD_PINS, + {0x12, 0x99a30130}, + {0x19, 0x03a11020}, + {0x21, 0x03211040}), + SND_HDA_PIN_QUIRK(0x10ec0282, 0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1, + ALC282_STANDARD_PINS, + {0x12, 0x99a30130}, + {0x19, 0x03a11030}, + {0x21, 0x03211020}), + SND_HDA_PIN_QUIRK(0x10ec0282, 0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1, + ALC282_STANDARD_PINS, + {0x12, 0x99a30130}, + {0x19, 0x04a11020}, + {0x21, 0x0421101f}), + SND_HDA_PIN_QUIRK(0x10ec0282, 0x103c, "HP", ALC269_FIXUP_HP_LINE1_MIC1_LED, + ALC282_STANDARD_PINS, + {0x12, 0x90a60140}, + {0x19, 0x04a11030}, + {0x21, 0x04211020}), + SND_HDA_PIN_QUIRK(0x10ec0282, 0x1025, "Acer", ALC282_FIXUP_ACER_DISABLE_LINEOUT, + ALC282_STANDARD_PINS, + {0x12, 0x90a609c0}, + {0x18, 0x03a11830}, + {0x19, 0x04a19831}, + {0x1a, 0x0481303f}, + {0x1b, 0x04211020}, + {0x21, 0x0321101f}), + SND_HDA_PIN_QUIRK(0x10ec0282, 0x1025, "Acer", ALC282_FIXUP_ACER_DISABLE_LINEOUT, + ALC282_STANDARD_PINS, + {0x12, 0x90a60940}, + {0x18, 0x03a11830}, + {0x19, 0x04a19831}, + {0x1a, 0x0481303f}, + {0x1b, 0x04211020}, + {0x21, 0x0321101f}), + SND_HDA_PIN_QUIRK(0x10ec0283, 0x1028, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC282_STANDARD_PINS, + {0x12, 0x90a60130}, + {0x21, 0x0321101f}), + SND_HDA_PIN_QUIRK(0x10ec0283, 0x1028, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60160}, + {0x14, 0x90170120}, + {0x21, 0x02211030}), + SND_HDA_PIN_QUIRK(0x10ec0283, 0x1028, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC282_STANDARD_PINS, + {0x12, 0x90a60130}, + {0x19, 0x03a11020}, + {0x21, 0x0321101f}), + SND_HDA_PIN_QUIRK(0x10ec0285, 0x17aa, "Lenovo", ALC285_FIXUP_LENOVO_PC_BEEP_IN_NOISE, + {0x12, 0x90a60130}, + {0x14, 0x90170110}, + {0x19, 0x04a11040}, + {0x21, 0x04211020}), + SND_HDA_PIN_QUIRK(0x10ec0285, 0x17aa, "Lenovo", ALC285_FIXUP_LENOVO_PC_BEEP_IN_NOISE, + {0x14, 0x90170110}, + {0x19, 0x04a11040}, + {0x1d, 0x40600001}, + {0x21, 0x04211020}), + SND_HDA_PIN_QUIRK(0x10ec0285, 0x17aa, "Lenovo", ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK, + {0x14, 0x90170110}, + {0x19, 0x04a11040}, + {0x21, 0x04211020}), + SND_HDA_PIN_QUIRK(0x10ec0287, 0x17aa, "Lenovo", ALC285_FIXUP_THINKPAD_HEADSET_JACK, + {0x14, 0x90170110}, + {0x17, 0x90170111}, + {0x19, 0x03a11030}, + {0x21, 0x03211020}), + SND_HDA_PIN_QUIRK(0x10ec0287, 0x17aa, "Lenovo", ALC287_FIXUP_THINKPAD_I2S_SPK, + {0x17, 0x90170110}, + {0x19, 0x03a11030}, + {0x21, 0x03211020}), + SND_HDA_PIN_QUIRK(0x10ec0286, 0x1025, "Acer", ALC286_FIXUP_ACER_AIO_MIC_NO_PRESENCE, + {0x12, 0x90a60130}, + {0x17, 0x90170110}, + {0x21, 0x02211020}), + SND_HDA_PIN_QUIRK(0x10ec0288, 0x1028, "Dell", ALC288_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60120}, + {0x14, 0x90170110}, + {0x21, 0x0321101f}), + SND_HDA_PIN_QUIRK(0x10ec0290, 0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1, + ALC290_STANDARD_PINS, + {0x15, 0x04211040}, + {0x18, 0x90170112}, + {0x1a, 0x04a11020}), + SND_HDA_PIN_QUIRK(0x10ec0290, 0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1, + ALC290_STANDARD_PINS, + {0x15, 0x04211040}, + {0x18, 0x90170110}, + {0x1a, 0x04a11020}), + SND_HDA_PIN_QUIRK(0x10ec0290, 0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1, + ALC290_STANDARD_PINS, + {0x15, 0x0421101f}, + {0x1a, 0x04a11020}), + SND_HDA_PIN_QUIRK(0x10ec0290, 0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1, + ALC290_STANDARD_PINS, + {0x15, 0x04211020}, + {0x1a, 0x04a11040}), + SND_HDA_PIN_QUIRK(0x10ec0290, 0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1, + ALC290_STANDARD_PINS, + {0x14, 0x90170110}, + {0x15, 0x04211020}, + {0x1a, 0x04a11040}), + SND_HDA_PIN_QUIRK(0x10ec0290, 0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1, + ALC290_STANDARD_PINS, + {0x14, 0x90170110}, + {0x15, 0x04211020}, + {0x1a, 0x04a11020}), + SND_HDA_PIN_QUIRK(0x10ec0290, 0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1, + ALC290_STANDARD_PINS, + {0x14, 0x90170110}, + {0x15, 0x0421101f}, + {0x1a, 0x04a11020}), + SND_HDA_PIN_QUIRK(0x10ec0292, 0x1028, "Dell", ALC269_FIXUP_DELL2_MIC_NO_PRESENCE, + ALC292_STANDARD_PINS, + {0x12, 0x90a60140}, + {0x16, 0x01014020}, + {0x19, 0x01a19030}), + SND_HDA_PIN_QUIRK(0x10ec0292, 0x1028, "Dell", ALC269_FIXUP_DELL2_MIC_NO_PRESENCE, + ALC292_STANDARD_PINS, + {0x12, 0x90a60140}, + {0x16, 0x01014020}, + {0x18, 0x02a19031}, + {0x19, 0x01a1903e}), + SND_HDA_PIN_QUIRK(0x10ec0292, 0x1028, "Dell", ALC269_FIXUP_DELL3_MIC_NO_PRESENCE, + ALC292_STANDARD_PINS, + {0x12, 0x90a60140}), + SND_HDA_PIN_QUIRK(0x10ec0293, 0x1028, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC292_STANDARD_PINS, + {0x13, 0x90a60140}, + {0x16, 0x21014020}, + {0x19, 0x21a19030}), + SND_HDA_PIN_QUIRK(0x10ec0293, 0x1028, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC292_STANDARD_PINS, + {0x13, 0x90a60140}), + SND_HDA_PIN_QUIRK(0x10ec0294, 0x1043, "ASUS", ALC294_FIXUP_ASUS_HPE, + {0x17, 0x90170110}, + {0x21, 0x04211020}), + SND_HDA_PIN_QUIRK(0x10ec0294, 0x1043, "ASUS", ALC294_FIXUP_ASUS_MIC, + {0x14, 0x90170110}, + {0x1b, 0x90a70130}, + {0x21, 0x04211020}), + SND_HDA_PIN_QUIRK(0x10ec0294, 0x1043, "ASUS", ALC294_FIXUP_ASUS_SPK, + {0x12, 0x90a60130}, + {0x17, 0x90170110}, + {0x21, 0x03211020}), + SND_HDA_PIN_QUIRK(0x10ec0294, 0x1043, "ASUS", ALC294_FIXUP_ASUS_SPK, + {0x12, 0x90a60130}, + {0x17, 0x90170110}, + {0x21, 0x04211020}), + SND_HDA_PIN_QUIRK(0x10ec0295, 0x1043, "ASUS", ALC294_FIXUP_ASUS_SPK, + {0x12, 0x90a60130}, + {0x17, 0x90170110}, + {0x21, 0x03211020}), + SND_HDA_PIN_QUIRK(0x10ec0295, 0x1043, "ASUS", ALC295_FIXUP_ASUS_MIC_NO_PRESENCE, + {0x12, 0x90a60120}, + {0x17, 0x90170110}, + {0x21, 0x04211030}), + SND_HDA_PIN_QUIRK(0x10ec0295, 0x1043, "ASUS", ALC295_FIXUP_ASUS_MIC_NO_PRESENCE, + {0x12, 0x90a60130}, + {0x17, 0x90170110}, + {0x21, 0x03211020}), + SND_HDA_PIN_QUIRK(0x10ec0295, 0x1043, "ASUS", ALC295_FIXUP_ASUS_MIC_NO_PRESENCE, + {0x12, 0x90a60130}, + {0x17, 0x90170110}, + {0x21, 0x03211020}), + SND_HDA_PIN_QUIRK(0x10ec0298, 0x1028, "Dell", ALC298_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC298_STANDARD_PINS, + {0x17, 0x90170110}), + SND_HDA_PIN_QUIRK(0x10ec0298, 0x1028, "Dell", ALC298_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC298_STANDARD_PINS, + {0x17, 0x90170140}), + SND_HDA_PIN_QUIRK(0x10ec0298, 0x1028, "Dell", ALC298_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC298_STANDARD_PINS, + {0x17, 0x90170150}), + SND_HDA_PIN_QUIRK(0x10ec0298, 0x1028, "Dell", ALC298_FIXUP_SPK_VOLUME, + {0x12, 0xb7a60140}, + {0x13, 0xb7a60150}, + {0x17, 0x90170110}, + {0x1a, 0x03011020}, + {0x21, 0x03211030}), + SND_HDA_PIN_QUIRK(0x10ec0298, 0x1028, "Dell", ALC298_FIXUP_ALIENWARE_MIC_NO_PRESENCE, + {0x12, 0xb7a60140}, + {0x17, 0x90170110}, + {0x1a, 0x03a11030}, + {0x21, 0x03211020}), + SND_HDA_PIN_QUIRK(0x10ec0299, 0x1028, "Dell", ALC269_FIXUP_DELL4_MIC_NO_PRESENCE, + ALC225_STANDARD_PINS, + {0x12, 0xb7a60130}, + {0x17, 0x90170110}), + SND_HDA_PIN_QUIRK(0x10ec0623, 0x17aa, "Lenovo", ALC283_FIXUP_HEADSET_MIC, + {0x14, 0x01014010}, + {0x17, 0x90170120}, + {0x18, 0x02a11030}, + {0x19, 0x02a1103f}, + {0x21, 0x0221101f}), + {} +}; + +/* This is the fallback pin_fixup_tbl for alc269 family, to make the tbl match + * more machines, don't need to match all valid pins, just need to match + * all the pins defined in the tbl. Just because of this reason, it is possible + * that a single machine matches multiple tbls, so there is one limitation: + * at most one tbl is allowed to define for the same vendor and same codec + */ +static const struct snd_hda_pin_quirk alc269_fallback_pin_fixup_tbl[] = { + SND_HDA_PIN_QUIRK(0x10ec0289, 0x1028, "Dell", ALC269_FIXUP_DELL4_MIC_NO_PRESENCE, + {0x19, 0x40000000}, + {0x1b, 0x40000000}), + SND_HDA_PIN_QUIRK(0x10ec0295, 0x1028, "Dell", ALC269_FIXUP_DELL4_MIC_NO_PRESENCE, + {0x19, 0x40000000}, + {0x1b, 0x40000000}), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x19, 0x40000000}, + {0x1a, 0x40000000}), + SND_HDA_PIN_QUIRK(0x10ec0236, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x19, 0x40000000}, + {0x1a, 0x40000000}), + SND_HDA_PIN_QUIRK(0x10ec0274, 0x1028, "Dell", ALC274_FIXUP_DELL_AIO_LINEOUT_VERB, + {0x19, 0x40000000}, + {0x1a, 0x40000000}), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC2XX_FIXUP_HEADSET_MIC, + {0x19, 0x40000000}), + {} +}; + +static void alc269_fill_coef(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int val; + + if (spec->codec_variant != ALC269_TYPE_ALC269VB) + return; + + if ((alc_get_coef0(codec) & 0x00ff) < 0x015) { + alc_write_coef_idx(codec, 0xf, 0x960b); + alc_write_coef_idx(codec, 0xe, 0x8817); + } + + if ((alc_get_coef0(codec) & 0x00ff) == 0x016) { + alc_write_coef_idx(codec, 0xf, 0x960b); + alc_write_coef_idx(codec, 0xe, 0x8814); + } + + if ((alc_get_coef0(codec) & 0x00ff) == 0x017) { + /* Power up output pin */ + alc_update_coef_idx(codec, 0x04, 0, 1<<11); + } + + if ((alc_get_coef0(codec) & 0x00ff) == 0x018) { + val = alc_read_coef_idx(codec, 0xd); + if (val != -1 && (val & 0x0c00) >> 10 != 0x1) { + /* Capless ramp up clock control */ + alc_write_coef_idx(codec, 0xd, val | (1<<10)); + } + val = alc_read_coef_idx(codec, 0x17); + if (val != -1 && (val & 0x01c0) >> 6 != 0x4) { + /* Class D power on reset */ + alc_write_coef_idx(codec, 0x17, val | (1<<7)); + } + } + + /* HP */ + alc_update_coef_idx(codec, 0x4, 0, 1<<11); +} + +/* + */ +static int patch_alc269(struct hda_codec *codec) +{ + struct alc_spec *spec; + int err; + + err = alc_alloc_spec(codec, 0x0b); + if (err < 0) + return err; + + spec = codec->spec; + spec->gen.shared_mic_vref_pin = 0x18; + codec->power_save_node = 0; + spec->en_3kpull_low = true; + +#ifdef CONFIG_PM + codec->patch_ops.suspend = alc269_suspend; + codec->patch_ops.resume = alc269_resume; +#endif + spec->shutup = alc_default_shutup; + spec->init_hook = alc_default_init; + + switch (codec->core.vendor_id) { + case 0x10ec0269: + spec->codec_variant = ALC269_TYPE_ALC269VA; + switch (alc_get_coef0(codec) & 0x00f0) { + case 0x0010: + if (codec->bus->pci && + codec->bus->pci->subsystem_vendor == 0x1025 && + spec->cdefine.platform_type == 1) + err = alc_codec_rename(codec, "ALC271X"); + spec->codec_variant = ALC269_TYPE_ALC269VB; + break; + case 0x0020: + if (codec->bus->pci && + codec->bus->pci->subsystem_vendor == 0x17aa && + codec->bus->pci->subsystem_device == 0x21f3) + err = alc_codec_rename(codec, "ALC3202"); + spec->codec_variant = ALC269_TYPE_ALC269VC; + break; + case 0x0030: + spec->codec_variant = ALC269_TYPE_ALC269VD; + break; + default: + alc_fix_pll_init(codec, 0x20, 0x04, 15); + } + if (err < 0) + goto error; + spec->shutup = alc269_shutup; + spec->init_hook = alc269_fill_coef; + alc269_fill_coef(codec); + break; + + case 0x10ec0280: + case 0x10ec0290: + spec->codec_variant = ALC269_TYPE_ALC280; + break; + case 0x10ec0282: + spec->codec_variant = ALC269_TYPE_ALC282; + spec->shutup = alc282_shutup; + spec->init_hook = alc282_init; + break; + case 0x10ec0233: + case 0x10ec0283: + spec->codec_variant = ALC269_TYPE_ALC283; + spec->shutup = alc283_shutup; + spec->init_hook = alc283_init; + break; + case 0x10ec0284: + case 0x10ec0292: + spec->codec_variant = ALC269_TYPE_ALC284; + break; + case 0x10ec0293: + spec->codec_variant = ALC269_TYPE_ALC293; + break; + case 0x10ec0286: + case 0x10ec0288: + spec->codec_variant = ALC269_TYPE_ALC286; + break; + case 0x10ec0298: + spec->codec_variant = ALC269_TYPE_ALC298; + break; + case 0x10ec0235: + case 0x10ec0255: + spec->codec_variant = ALC269_TYPE_ALC255; + spec->shutup = alc256_shutup; + spec->init_hook = alc256_init; + break; + case 0x10ec0230: + case 0x10ec0236: + case 0x10ec0256: + case 0x19e58326: + spec->codec_variant = ALC269_TYPE_ALC256; + spec->shutup = alc256_shutup; + spec->init_hook = alc256_init; + spec->gen.mixer_nid = 0; /* ALC256 does not have any loopback mixer path */ + if (codec->core.vendor_id == 0x10ec0236 && + codec->bus->pci->vendor != PCI_VENDOR_ID_AMD) + spec->en_3kpull_low = false; + break; + case 0x10ec0257: + spec->codec_variant = ALC269_TYPE_ALC257; + spec->shutup = alc256_shutup; + spec->init_hook = alc256_init; + spec->gen.mixer_nid = 0; + spec->en_3kpull_low = false; + break; + case 0x10ec0215: + case 0x10ec0245: + case 0x10ec0285: + case 0x10ec0289: + if (alc_get_coef0(codec) & 0x0010) + spec->codec_variant = ALC269_TYPE_ALC245; + else + spec->codec_variant = ALC269_TYPE_ALC215; + spec->shutup = alc225_shutup; + spec->init_hook = alc225_init; + spec->gen.mixer_nid = 0; + break; + case 0x10ec0225: + case 0x10ec0295: + case 0x10ec0299: + spec->codec_variant = ALC269_TYPE_ALC225; + spec->shutup = alc225_shutup; + spec->init_hook = alc225_init; + spec->gen.mixer_nid = 0; /* no loopback on ALC225, ALC295 and ALC299 */ + break; + case 0x10ec0287: + spec->codec_variant = ALC269_TYPE_ALC287; + spec->shutup = alc225_shutup; + spec->init_hook = alc225_init; + spec->gen.mixer_nid = 0; /* no loopback on ALC287 */ + break; + case 0x10ec0234: + case 0x10ec0274: + case 0x10ec0294: + spec->codec_variant = ALC269_TYPE_ALC294; + spec->gen.mixer_nid = 0; /* ALC2x4 does not have any loopback mixer path */ + alc_update_coef_idx(codec, 0x6b, 0x0018, (1<<4) | (1<<3)); /* UAJ MIC Vref control by verb */ + spec->init_hook = alc294_init; + break; + case 0x10ec0300: + spec->codec_variant = ALC269_TYPE_ALC300; + spec->gen.mixer_nid = 0; /* no loopback on ALC300 */ + break; + case 0x10ec0623: + spec->codec_variant = ALC269_TYPE_ALC623; + break; + case 0x10ec0700: + case 0x10ec0701: + case 0x10ec0703: + case 0x10ec0711: + spec->codec_variant = ALC269_TYPE_ALC700; + spec->gen.mixer_nid = 0; /* ALC700 does not have any loopback mixer path */ + alc_update_coef_idx(codec, 0x4a, 1 << 15, 0); /* Combo jack auto trigger control */ + spec->init_hook = alc294_init; + break; + + } + + if (snd_hda_codec_read(codec, 0x51, 0, AC_VERB_PARAMETERS, 0) == 0x10ec5505) { + spec->has_alc5505_dsp = 1; + spec->init_hook = alc5505_dsp_init; + } + + alc_pre_init(codec); + + snd_hda_pick_fixup(codec, alc269_fixup_models, + alc269_fixup_tbl, alc269_fixups); + /* FIXME: both TX300 and ROG Strix G17 have the same SSID, and + * the quirk breaks the latter (bko#214101). + * Clear the wrong entry. + */ + if (codec->fixup_id == ALC282_FIXUP_ASUS_TX300 && + codec->core.vendor_id == 0x10ec0294) { + codec_dbg(codec, "Clear wrong fixup for ASUS ROG Strix G17\n"); + codec->fixup_id = HDA_FIXUP_ID_NOT_SET; + } + + snd_hda_pick_pin_fixup(codec, alc269_pin_fixup_tbl, alc269_fixups, true); + snd_hda_pick_pin_fixup(codec, alc269_fallback_pin_fixup_tbl, alc269_fixups, false); + snd_hda_pick_fixup(codec, NULL, alc269_fixup_vendor_tbl, + alc269_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + alc_auto_parse_customize_define(codec); + + if (has_cdefine_beep(codec)) + spec->gen.beep_nid = 0x01; + + /* automatic parse from the BIOS config */ + err = alc269_parse_auto_config(codec); + if (err < 0) + goto error; + + if (!spec->gen.no_analog && spec->gen.beep_nid && spec->gen.mixer_nid) { + err = set_beep_amp(spec, spec->gen.mixer_nid, 0x04, HDA_INPUT); + if (err < 0) + goto error; + } + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; + + error: + alc_free(codec); + return err; +} + +/* + * ALC861 + */ + +static int alc861_parse_auto_config(struct hda_codec *codec) +{ + static const hda_nid_t alc861_ignore[] = { 0x1d, 0 }; + static const hda_nid_t alc861_ssids[] = { 0x0e, 0x0f, 0x0b, 0 }; + return alc_parse_auto_config(codec, alc861_ignore, alc861_ssids); +} + +/* Pin config fixes */ +enum { + ALC861_FIXUP_FSC_AMILO_PI1505, + ALC861_FIXUP_AMP_VREF_0F, + ALC861_FIXUP_NO_JACK_DETECT, + ALC861_FIXUP_ASUS_A6RP, + ALC660_FIXUP_ASUS_W7J, +}; + +/* On some laptops, VREF of pin 0x0f is abused for controlling the main amp */ +static void alc861_fixup_asus_amp_vref_0f(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + unsigned int val; + + if (action != HDA_FIXUP_ACT_INIT) + return; + val = snd_hda_codec_get_pin_target(codec, 0x0f); + if (!(val & (AC_PINCTL_IN_EN | AC_PINCTL_OUT_EN))) + val |= AC_PINCTL_IN_EN; + val |= AC_PINCTL_VREF_50; + snd_hda_set_pin_ctl(codec, 0x0f, val); + spec->gen.keep_vref_in_automute = 1; +} + +/* suppress the jack-detection */ +static void alc_fixup_no_jack_detect(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) + codec->no_jack_detect = 1; +} + +static const struct hda_fixup alc861_fixups[] = { + [ALC861_FIXUP_FSC_AMILO_PI1505] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x0b, 0x0221101f }, /* HP */ + { 0x0f, 0x90170310 }, /* speaker */ + { } + } + }, + [ALC861_FIXUP_AMP_VREF_0F] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc861_fixup_asus_amp_vref_0f, + }, + [ALC861_FIXUP_NO_JACK_DETECT] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_no_jack_detect, + }, + [ALC861_FIXUP_ASUS_A6RP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc861_fixup_asus_amp_vref_0f, + .chained = true, + .chain_id = ALC861_FIXUP_NO_JACK_DETECT, + }, + [ALC660_FIXUP_ASUS_W7J] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* ASUS W7J needs a magic pin setup on unused NID 0x10 + * for enabling outputs + */ + {0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24}, + { } + }, + } +}; + +static const struct snd_pci_quirk alc861_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1253, "ASUS W7J", ALC660_FIXUP_ASUS_W7J), + SND_PCI_QUIRK(0x1043, 0x1263, "ASUS Z35HL", ALC660_FIXUP_ASUS_W7J), + SND_PCI_QUIRK(0x1043, 0x1393, "ASUS A6Rp", ALC861_FIXUP_ASUS_A6RP), + SND_PCI_QUIRK_VENDOR(0x1043, "ASUS laptop", ALC861_FIXUP_AMP_VREF_0F), + SND_PCI_QUIRK(0x1462, 0x7254, "HP DX2200", ALC861_FIXUP_NO_JACK_DETECT), + SND_PCI_QUIRK_VENDOR(0x1584, "Haier/Uniwill", ALC861_FIXUP_AMP_VREF_0F), + SND_PCI_QUIRK(0x1734, 0x10c7, "FSC Amilo Pi1505", ALC861_FIXUP_FSC_AMILO_PI1505), + {} +}; + +/* + */ +static int patch_alc861(struct hda_codec *codec) +{ + struct alc_spec *spec; + int err; + + err = alc_alloc_spec(codec, 0x15); + if (err < 0) + return err; + + spec = codec->spec; + if (has_cdefine_beep(codec)) + spec->gen.beep_nid = 0x23; + +#ifdef CONFIG_PM + spec->power_hook = alc_power_eapd; +#endif + + alc_pre_init(codec); + + snd_hda_pick_fixup(codec, NULL, alc861_fixup_tbl, alc861_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + /* automatic parse from the BIOS config */ + err = alc861_parse_auto_config(codec); + if (err < 0) + goto error; + + if (!spec->gen.no_analog) { + err = set_beep_amp(spec, 0x23, 0, HDA_OUTPUT); + if (err < 0) + goto error; + } + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; + + error: + alc_free(codec); + return err; +} + +/* + * ALC861-VD support + * + * Based on ALC882 + * + * In addition, an independent DAC + */ +static int alc861vd_parse_auto_config(struct hda_codec *codec) +{ + static const hda_nid_t alc861vd_ignore[] = { 0x1d, 0 }; + static const hda_nid_t alc861vd_ssids[] = { 0x15, 0x1b, 0x14, 0 }; + return alc_parse_auto_config(codec, alc861vd_ignore, alc861vd_ssids); +} + +enum { + ALC660VD_FIX_ASUS_GPIO1, + ALC861VD_FIX_DALLAS, +}; + +/* exclude VREF80 */ +static void alc861vd_fixup_dallas(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + snd_hda_override_pin_caps(codec, 0x18, 0x00000734); + snd_hda_override_pin_caps(codec, 0x19, 0x0000073c); + } +} + +/* reset GPIO1 */ +static void alc660vd_fixup_asus_gpio1(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) + spec->gpio_mask |= 0x02; + alc_fixup_gpio(codec, action, 0x01); +} + +static const struct hda_fixup alc861vd_fixups[] = { + [ALC660VD_FIX_ASUS_GPIO1] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc660vd_fixup_asus_gpio1, + }, + [ALC861VD_FIX_DALLAS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc861vd_fixup_dallas, + }, +}; + +static const struct snd_pci_quirk alc861vd_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x30bf, "HP TX1000", ALC861VD_FIX_DALLAS), + SND_PCI_QUIRK(0x1043, 0x1339, "ASUS A7-K", ALC660VD_FIX_ASUS_GPIO1), + SND_PCI_QUIRK(0x1179, 0xff31, "Toshiba L30-149", ALC861VD_FIX_DALLAS), + {} +}; + +/* + */ +static int patch_alc861vd(struct hda_codec *codec) +{ + struct alc_spec *spec; + int err; + + err = alc_alloc_spec(codec, 0x0b); + if (err < 0) + return err; + + spec = codec->spec; + if (has_cdefine_beep(codec)) + spec->gen.beep_nid = 0x23; + + spec->shutup = alc_eapd_shutup; + + alc_pre_init(codec); + + snd_hda_pick_fixup(codec, NULL, alc861vd_fixup_tbl, alc861vd_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + /* automatic parse from the BIOS config */ + err = alc861vd_parse_auto_config(codec); + if (err < 0) + goto error; + + if (!spec->gen.no_analog) { + err = set_beep_amp(spec, 0x0b, 0x05, HDA_INPUT); + if (err < 0) + goto error; + } + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; + + error: + alc_free(codec); + return err; +} + +/* + * ALC662 support + * + * ALC662 is almost identical with ALC880 but has cleaner and more flexible + * configuration. Each pin widget can choose any input DACs and a mixer. + * Each ADC is connected from a mixer of all inputs. This makes possible + * 6-channel independent captures. + * + * In addition, an independent DAC for the multi-playback (not used in this + * driver yet). + */ + +/* + * BIOS auto configuration + */ + +static int alc662_parse_auto_config(struct hda_codec *codec) +{ + static const hda_nid_t alc662_ignore[] = { 0x1d, 0 }; + static const hda_nid_t alc663_ssids[] = { 0x15, 0x1b, 0x14, 0x21 }; + static const hda_nid_t alc662_ssids[] = { 0x15, 0x1b, 0x14, 0 }; + const hda_nid_t *ssids; + + if (codec->core.vendor_id == 0x10ec0272 || codec->core.vendor_id == 0x10ec0663 || + codec->core.vendor_id == 0x10ec0665 || codec->core.vendor_id == 0x10ec0670 || + codec->core.vendor_id == 0x10ec0671) + ssids = alc663_ssids; + else + ssids = alc662_ssids; + return alc_parse_auto_config(codec, alc662_ignore, ssids); +} + +static void alc272_fixup_mario(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + if (snd_hda_override_amp_caps(codec, 0x2, HDA_OUTPUT, + (0x3b << AC_AMPCAP_OFFSET_SHIFT) | + (0x3b << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x03 << AC_AMPCAP_STEP_SIZE_SHIFT) | + (0 << AC_AMPCAP_MUTE_SHIFT))) + codec_warn(codec, "failed to override amp caps for NID 0x2\n"); +} + +static const struct snd_pcm_chmap_elem asus_pcm_2_1_chmaps[] = { + { .channels = 2, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } }, + { .channels = 4, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_NA, SNDRV_CHMAP_LFE } }, /* LFE only on right */ + { } +}; + +/* override the 2.1 chmap */ +static void alc_fixup_bass_chmap(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_BUILD) { + struct alc_spec *spec = codec->spec; + spec->gen.pcm_rec[0]->stream[0].chmap = asus_pcm_2_1_chmaps; + } +} + +/* avoid D3 for keeping GPIO up */ +static unsigned int gpio_led_power_filter(struct hda_codec *codec, + hda_nid_t nid, + unsigned int power_state) +{ + struct alc_spec *spec = codec->spec; + if (nid == codec->core.afg && power_state == AC_PWRST_D3 && spec->gpio_data) + return AC_PWRST_D0; + return power_state; +} + +static void alc662_fixup_led_gpio1(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + alc_fixup_hp_gpio_led(codec, action, 0x01, 0); + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->mute_led_polarity = 1; + codec->power_filter = gpio_led_power_filter; + } +} + +static void alc662_usi_automute_hook(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + struct alc_spec *spec = codec->spec; + int vref; + msleep(200); + snd_hda_gen_hp_automute(codec, jack); + + vref = spec->gen.hp_jack_present ? PIN_VREF80 : 0; + msleep(100); + snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + vref); +} + +static void alc662_fixup_usi_headset_mic(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->parse_flags |= HDA_PINCFG_HEADSET_MIC; + spec->gen.hp_automute_hook = alc662_usi_automute_hook; + } +} + +static void alc662_aspire_ethos_mute_speakers(struct hda_codec *codec, + struct hda_jack_callback *cb) +{ + /* surround speakers at 0x1b already get muted automatically when + * headphones are plugged in, but we have to mute/unmute the remaining + * channels manually: + * 0x15 - front left/front right + * 0x18 - front center/ LFE + */ + if (snd_hda_jack_detect_state(codec, 0x1b) == HDA_JACK_PRESENT) { + snd_hda_set_pin_ctl_cache(codec, 0x15, 0); + snd_hda_set_pin_ctl_cache(codec, 0x18, 0); + } else { + snd_hda_set_pin_ctl_cache(codec, 0x15, PIN_OUT); + snd_hda_set_pin_ctl_cache(codec, 0x18, PIN_OUT); + } +} + +static void alc662_fixup_aspire_ethos_hp(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + /* Pin 0x1b: shared headphones jack and surround speakers */ + if (!is_jack_detectable(codec, 0x1b)) + return; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + snd_hda_jack_detect_enable_callback(codec, 0x1b, + alc662_aspire_ethos_mute_speakers); + /* subwoofer needs an extra GPIO setting to become audible */ + alc_setup_gpio(codec, 0x02); + break; + case HDA_FIXUP_ACT_INIT: + /* Make sure to start in a correct state, i.e. if + * headphones have been plugged in before powering up the system + */ + alc662_aspire_ethos_mute_speakers(codec, NULL); + break; + } +} + +static void alc671_fixup_hp_headset_mic2(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + static const struct hda_pintbl pincfgs[] = { + { 0x19, 0x02a11040 }, /* use as headset mic, with its own jack detect */ + { 0x1b, 0x0181304f }, + { } + }; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + spec->gen.mixer_nid = 0; + spec->parse_flags |= HDA_PINCFG_HEADSET_MIC; + snd_hda_apply_pincfgs(codec, pincfgs); + break; + case HDA_FIXUP_ACT_INIT: + alc_write_coef_idx(codec, 0x19, 0xa054); + break; + } +} + +static void alc897_hp_automute_hook(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + struct alc_spec *spec = codec->spec; + int vref; + + snd_hda_gen_hp_automute(codec, jack); + vref = spec->gen.hp_jack_present ? (PIN_HP | AC_PINCTL_VREF_100) : PIN_HP; + snd_hda_codec_write(codec, 0x1b, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + vref); +} + +static void alc897_fixup_lenovo_headset_mic(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->gen.hp_automute_hook = alc897_hp_automute_hook; + } +} + +static void alc897_fixup_lenovo_headset_mode(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->parse_flags |= HDA_PINCFG_HEADSET_MIC; + spec->gen.hp_automute_hook = alc897_hp_automute_hook; + } +} + +static const struct coef_fw alc668_coefs[] = { + WRITE_COEF(0x01, 0xbebe), WRITE_COEF(0x02, 0xaaaa), WRITE_COEF(0x03, 0x0), + WRITE_COEF(0x04, 0x0180), WRITE_COEF(0x06, 0x0), WRITE_COEF(0x07, 0x0f80), + WRITE_COEF(0x08, 0x0031), WRITE_COEF(0x0a, 0x0060), WRITE_COEF(0x0b, 0x0), + WRITE_COEF(0x0c, 0x7cf7), WRITE_COEF(0x0d, 0x1080), WRITE_COEF(0x0e, 0x7f7f), + WRITE_COEF(0x0f, 0xcccc), WRITE_COEF(0x10, 0xddcc), WRITE_COEF(0x11, 0x0001), + WRITE_COEF(0x13, 0x0), WRITE_COEF(0x14, 0x2aa0), WRITE_COEF(0x17, 0xa940), + WRITE_COEF(0x19, 0x0), WRITE_COEF(0x1a, 0x0), WRITE_COEF(0x1b, 0x0), + WRITE_COEF(0x1c, 0x0), WRITE_COEF(0x1d, 0x0), WRITE_COEF(0x1e, 0x7418), + WRITE_COEF(0x1f, 0x0804), WRITE_COEF(0x20, 0x4200), WRITE_COEF(0x21, 0x0468), + WRITE_COEF(0x22, 0x8ccc), WRITE_COEF(0x23, 0x0250), WRITE_COEF(0x24, 0x7418), + WRITE_COEF(0x27, 0x0), WRITE_COEF(0x28, 0x8ccc), WRITE_COEF(0x2a, 0xff00), + WRITE_COEF(0x2b, 0x8000), WRITE_COEF(0xa7, 0xff00), WRITE_COEF(0xa8, 0x8000), + WRITE_COEF(0xaa, 0x2e17), WRITE_COEF(0xab, 0xa0c0), WRITE_COEF(0xac, 0x0), + WRITE_COEF(0xad, 0x0), WRITE_COEF(0xae, 0x2ac6), WRITE_COEF(0xaf, 0xa480), + WRITE_COEF(0xb0, 0x0), WRITE_COEF(0xb1, 0x0), WRITE_COEF(0xb2, 0x0), + WRITE_COEF(0xb3, 0x0), WRITE_COEF(0xb4, 0x0), WRITE_COEF(0xb5, 0x1040), + WRITE_COEF(0xb6, 0xd697), WRITE_COEF(0xb7, 0x902b), WRITE_COEF(0xb8, 0xd697), + WRITE_COEF(0xb9, 0x902b), WRITE_COEF(0xba, 0xb8ba), WRITE_COEF(0xbb, 0xaaab), + WRITE_COEF(0xbc, 0xaaaf), WRITE_COEF(0xbd, 0x6aaa), WRITE_COEF(0xbe, 0x1c02), + WRITE_COEF(0xc0, 0x00ff), WRITE_COEF(0xc1, 0x0fa6), + {} +}; + +static void alc668_restore_default_value(struct hda_codec *codec) +{ + alc_process_coef_fw(codec, alc668_coefs); +} + +enum { + ALC662_FIXUP_ASPIRE, + ALC662_FIXUP_LED_GPIO1, + ALC662_FIXUP_IDEAPAD, + ALC272_FIXUP_MARIO, + ALC662_FIXUP_CZC_ET26, + ALC662_FIXUP_CZC_P10T, + ALC662_FIXUP_SKU_IGNORE, + ALC662_FIXUP_HP_RP5800, + ALC662_FIXUP_ASUS_MODE1, + ALC662_FIXUP_ASUS_MODE2, + ALC662_FIXUP_ASUS_MODE3, + ALC662_FIXUP_ASUS_MODE4, + ALC662_FIXUP_ASUS_MODE5, + ALC662_FIXUP_ASUS_MODE6, + ALC662_FIXUP_ASUS_MODE7, + ALC662_FIXUP_ASUS_MODE8, + ALC662_FIXUP_NO_JACK_DETECT, + ALC662_FIXUP_ZOTAC_Z68, + ALC662_FIXUP_INV_DMIC, + ALC662_FIXUP_DELL_MIC_NO_PRESENCE, + ALC668_FIXUP_DELL_MIC_NO_PRESENCE, + ALC662_FIXUP_HEADSET_MODE, + ALC668_FIXUP_HEADSET_MODE, + ALC662_FIXUP_BASS_MODE4_CHMAP, + ALC662_FIXUP_BASS_16, + ALC662_FIXUP_BASS_1A, + ALC662_FIXUP_BASS_CHMAP, + ALC668_FIXUP_AUTO_MUTE, + ALC668_FIXUP_DELL_DISABLE_AAMIX, + ALC668_FIXUP_DELL_XPS13, + ALC662_FIXUP_ASUS_Nx50, + ALC668_FIXUP_ASUS_Nx51_HEADSET_MODE, + ALC668_FIXUP_ASUS_Nx51, + ALC668_FIXUP_MIC_COEF, + ALC668_FIXUP_ASUS_G751, + ALC891_FIXUP_HEADSET_MODE, + ALC891_FIXUP_DELL_MIC_NO_PRESENCE, + ALC662_FIXUP_ACER_VERITON, + ALC892_FIXUP_ASROCK_MOBO, + ALC662_FIXUP_USI_FUNC, + ALC662_FIXUP_USI_HEADSET_MODE, + ALC662_FIXUP_LENOVO_MULTI_CODECS, + ALC669_FIXUP_ACER_ASPIRE_ETHOS, + ALC669_FIXUP_ACER_ASPIRE_ETHOS_HEADSET, + ALC671_FIXUP_HP_HEADSET_MIC2, + ALC662_FIXUP_ACER_X2660G_HEADSET_MODE, + ALC662_FIXUP_ACER_NITRO_HEADSET_MODE, + ALC668_FIXUP_ASUS_NO_HEADSET_MIC, + ALC668_FIXUP_HEADSET_MIC, + ALC668_FIXUP_MIC_DET_COEF, + ALC897_FIXUP_LENOVO_HEADSET_MIC, + ALC897_FIXUP_HEADSET_MIC_PIN, + ALC897_FIXUP_HP_HSMIC_VERB, + ALC897_FIXUP_LENOVO_HEADSET_MODE, + ALC897_FIXUP_HEADSET_MIC_PIN2, + ALC897_FIXUP_UNIS_H3C_X500S, +}; + +static const struct hda_fixup alc662_fixups[] = { + [ALC662_FIXUP_ASPIRE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x15, 0x99130112 }, /* subwoofer */ + { } + } + }, + [ALC662_FIXUP_LED_GPIO1] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc662_fixup_led_gpio1, + }, + [ALC662_FIXUP_IDEAPAD] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x17, 0x99130112 }, /* subwoofer */ + { } + }, + .chained = true, + .chain_id = ALC662_FIXUP_LED_GPIO1, + }, + [ALC272_FIXUP_MARIO] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc272_fixup_mario, + }, + [ALC662_FIXUP_CZC_ET26] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + {0x12, 0x403cc000}, + {0x14, 0x90170110}, /* speaker */ + {0x15, 0x411111f0}, + {0x16, 0x411111f0}, + {0x18, 0x01a19030}, /* mic */ + {0x19, 0x90a7013f}, /* int-mic */ + {0x1a, 0x01014020}, + {0x1b, 0x0121401f}, + {0x1c, 0x411111f0}, + {0x1d, 0x411111f0}, + {0x1e, 0x40478e35}, + {} + }, + .chained = true, + .chain_id = ALC662_FIXUP_SKU_IGNORE + }, + [ALC662_FIXUP_CZC_P10T] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + {0x14, AC_VERB_SET_EAPD_BTLENABLE, 0}, + {} + } + }, + [ALC662_FIXUP_SKU_IGNORE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_sku_ignore, + }, + [ALC662_FIXUP_HP_RP5800] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x0221201f }, /* HP out */ + { } + }, + .chained = true, + .chain_id = ALC662_FIXUP_SKU_IGNORE + }, + [ALC662_FIXUP_ASUS_MODE1] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x99130110 }, /* speaker */ + { 0x18, 0x01a19c20 }, /* mic */ + { 0x19, 0x99a3092f }, /* int-mic */ + { 0x21, 0x0121401f }, /* HP out */ + { } + }, + .chained = true, + .chain_id = ALC662_FIXUP_SKU_IGNORE + }, + [ALC662_FIXUP_ASUS_MODE2] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x99130110 }, /* speaker */ + { 0x18, 0x01a19820 }, /* mic */ + { 0x19, 0x99a3092f }, /* int-mic */ + { 0x1b, 0x0121401f }, /* HP out */ + { } + }, + .chained = true, + .chain_id = ALC662_FIXUP_SKU_IGNORE + }, + [ALC662_FIXUP_ASUS_MODE3] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x99130110 }, /* speaker */ + { 0x15, 0x0121441f }, /* HP */ + { 0x18, 0x01a19840 }, /* mic */ + { 0x19, 0x99a3094f }, /* int-mic */ + { 0x21, 0x01211420 }, /* HP2 */ + { } + }, + .chained = true, + .chain_id = ALC662_FIXUP_SKU_IGNORE + }, + [ALC662_FIXUP_ASUS_MODE4] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x99130110 }, /* speaker */ + { 0x16, 0x99130111 }, /* speaker */ + { 0x18, 0x01a19840 }, /* mic */ + { 0x19, 0x99a3094f }, /* int-mic */ + { 0x21, 0x0121441f }, /* HP */ + { } + }, + .chained = true, + .chain_id = ALC662_FIXUP_SKU_IGNORE + }, + [ALC662_FIXUP_ASUS_MODE5] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x99130110 }, /* speaker */ + { 0x15, 0x0121441f }, /* HP */ + { 0x16, 0x99130111 }, /* speaker */ + { 0x18, 0x01a19840 }, /* mic */ + { 0x19, 0x99a3094f }, /* int-mic */ + { } + }, + .chained = true, + .chain_id = ALC662_FIXUP_SKU_IGNORE + }, + [ALC662_FIXUP_ASUS_MODE6] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x99130110 }, /* speaker */ + { 0x15, 0x01211420 }, /* HP2 */ + { 0x18, 0x01a19840 }, /* mic */ + { 0x19, 0x99a3094f }, /* int-mic */ + { 0x1b, 0x0121441f }, /* HP */ + { } + }, + .chained = true, + .chain_id = ALC662_FIXUP_SKU_IGNORE + }, + [ALC662_FIXUP_ASUS_MODE7] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x99130110 }, /* speaker */ + { 0x17, 0x99130111 }, /* speaker */ + { 0x18, 0x01a19840 }, /* mic */ + { 0x19, 0x99a3094f }, /* int-mic */ + { 0x1b, 0x01214020 }, /* HP */ + { 0x21, 0x0121401f }, /* HP */ + { } + }, + .chained = true, + .chain_id = ALC662_FIXUP_SKU_IGNORE + }, + [ALC662_FIXUP_ASUS_MODE8] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x99130110 }, /* speaker */ + { 0x12, 0x99a30970 }, /* int-mic */ + { 0x15, 0x01214020 }, /* HP */ + { 0x17, 0x99130111 }, /* speaker */ + { 0x18, 0x01a19840 }, /* mic */ + { 0x21, 0x0121401f }, /* HP */ + { } + }, + .chained = true, + .chain_id = ALC662_FIXUP_SKU_IGNORE + }, + [ALC662_FIXUP_NO_JACK_DETECT] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_no_jack_detect, + }, + [ALC662_FIXUP_ZOTAC_Z68] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1b, 0x02214020 }, /* Front HP */ + { } + } + }, + [ALC662_FIXUP_INV_DMIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_inv_dmic, + }, + [ALC668_FIXUP_DELL_XPS13] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_dell_xps13, + .chained = true, + .chain_id = ALC668_FIXUP_DELL_DISABLE_AAMIX + }, + [ALC668_FIXUP_DELL_DISABLE_AAMIX] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_disable_aamix, + .chained = true, + .chain_id = ALC668_FIXUP_DELL_MIC_NO_PRESENCE + }, + [ALC668_FIXUP_AUTO_MUTE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_auto_mute_via_amp, + .chained = true, + .chain_id = ALC668_FIXUP_DELL_MIC_NO_PRESENCE + }, + [ALC662_FIXUP_DELL_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x03a1113c }, /* use as headset mic, without its own jack detect */ + /* headphone mic by setting pin control of 0x1b (headphone out) to in + vref_50 */ + { } + }, + .chained = true, + .chain_id = ALC662_FIXUP_HEADSET_MODE + }, + [ALC662_FIXUP_HEADSET_MODE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode_alc662, + }, + [ALC668_FIXUP_DELL_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x03a1913d }, /* use as headphone mic, without its own jack detect */ + { 0x1b, 0x03a1113c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC668_FIXUP_HEADSET_MODE + }, + [ALC668_FIXUP_HEADSET_MODE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode_alc668, + }, + [ALC662_FIXUP_BASS_MODE4_CHMAP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_bass_chmap, + .chained = true, + .chain_id = ALC662_FIXUP_ASUS_MODE4 + }, + [ALC662_FIXUP_BASS_16] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + {0x16, 0x80106111}, /* bass speaker */ + {} + }, + .chained = true, + .chain_id = ALC662_FIXUP_BASS_CHMAP, + }, + [ALC662_FIXUP_BASS_1A] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + {0x1a, 0x80106111}, /* bass speaker */ + {} + }, + .chained = true, + .chain_id = ALC662_FIXUP_BASS_CHMAP, + }, + [ALC662_FIXUP_BASS_CHMAP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_bass_chmap, + }, + [ALC662_FIXUP_ASUS_Nx50] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_auto_mute_via_amp, + .chained = true, + .chain_id = ALC662_FIXUP_BASS_1A + }, + [ALC668_FIXUP_ASUS_Nx51_HEADSET_MODE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode_alc668, + .chain_id = ALC662_FIXUP_BASS_CHMAP + }, + [ALC668_FIXUP_ASUS_Nx51] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x03a1913d }, /* use as headphone mic, without its own jack detect */ + { 0x1a, 0x90170151 }, /* bass speaker */ + { 0x1b, 0x03a1113c }, /* use as headset mic, without its own jack detect */ + {} + }, + .chained = true, + .chain_id = ALC668_FIXUP_ASUS_Nx51_HEADSET_MODE, + }, + [ALC668_FIXUP_MIC_COEF] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x20, AC_VERB_SET_COEF_INDEX, 0xc3 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x4000 }, + {} + }, + }, + [ALC668_FIXUP_ASUS_G751] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x16, 0x0421101f }, /* HP */ + {} + }, + .chained = true, + .chain_id = ALC668_FIXUP_MIC_COEF + }, + [ALC891_FIXUP_HEADSET_MODE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode, + }, + [ALC891_FIXUP_DELL_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x03a1913d }, /* use as headphone mic, without its own jack detect */ + { 0x1b, 0x03a1113c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC891_FIXUP_HEADSET_MODE + }, + [ALC662_FIXUP_ACER_VERITON] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x15, 0x50170120 }, /* no internal speaker */ + { } + } + }, + [ALC892_FIXUP_ASROCK_MOBO] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x15, 0x40f000f0 }, /* disabled */ + { 0x16, 0x40f000f0 }, /* disabled */ + { } + } + }, + [ALC662_FIXUP_USI_FUNC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc662_fixup_usi_headset_mic, + }, + [ALC662_FIXUP_USI_HEADSET_MODE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x02a1913c }, /* use as headset mic, without its own jack detect */ + { 0x18, 0x01a1903d }, + { } + }, + .chained = true, + .chain_id = ALC662_FIXUP_USI_FUNC + }, + [ALC662_FIXUP_LENOVO_MULTI_CODECS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc233_alc662_fixup_lenovo_dual_codecs, + }, + [ALC669_FIXUP_ACER_ASPIRE_ETHOS_HEADSET] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc662_fixup_aspire_ethos_hp, + }, + [ALC669_FIXUP_ACER_ASPIRE_ETHOS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x15, 0x92130110 }, /* front speakers */ + { 0x18, 0x99130111 }, /* center/subwoofer */ + { 0x1b, 0x11130012 }, /* surround plus jack for HP */ + { } + }, + .chained = true, + .chain_id = ALC669_FIXUP_ACER_ASPIRE_ETHOS_HEADSET + }, + [ALC671_FIXUP_HP_HEADSET_MIC2] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc671_fixup_hp_headset_mic2, + }, + [ALC662_FIXUP_ACER_X2660G_HEADSET_MODE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1a, 0x02a1113c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC662_FIXUP_USI_FUNC + }, + [ALC662_FIXUP_ACER_NITRO_HEADSET_MODE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1a, 0x01a11140 }, /* use as headset mic, without its own jack detect */ + { 0x1b, 0x0221144f }, + { } + }, + .chained = true, + .chain_id = ALC662_FIXUP_USI_FUNC + }, + [ALC668_FIXUP_ASUS_NO_HEADSET_MIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1b, 0x04a1112c }, + { } + }, + .chained = true, + .chain_id = ALC668_FIXUP_HEADSET_MIC + }, + [ALC668_FIXUP_HEADSET_MIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_headset_mic, + .chained = true, + .chain_id = ALC668_FIXUP_MIC_DET_COEF + }, + [ALC668_FIXUP_MIC_DET_COEF] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x20, AC_VERB_SET_COEF_INDEX, 0x15 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0d60 }, + {} + }, + }, + [ALC897_FIXUP_LENOVO_HEADSET_MIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc897_fixup_lenovo_headset_mic, + }, + [ALC897_FIXUP_HEADSET_MIC_PIN] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1a, 0x03a11050 }, + { } + }, + .chained = true, + .chain_id = ALC897_FIXUP_LENOVO_HEADSET_MIC + }, + [ALC897_FIXUP_HP_HSMIC_VERB] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + }, + [ALC897_FIXUP_LENOVO_HEADSET_MODE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc897_fixup_lenovo_headset_mode, + }, + [ALC897_FIXUP_HEADSET_MIC_PIN2] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1a, 0x01a11140 }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC897_FIXUP_LENOVO_HEADSET_MODE + }, + [ALC897_FIXUP_UNIS_H3C_X500S] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x14, AC_VERB_SET_EAPD_BTLENABLE, 0 }, + {} + }, + }, +}; + +static const struct snd_pci_quirk alc662_fixup_tbl[] = { + SND_PCI_QUIRK(0x1019, 0x9087, "ECS", ALC662_FIXUP_ASUS_MODE2), + SND_PCI_QUIRK(0x1025, 0x022f, "Acer Aspire One", ALC662_FIXUP_INV_DMIC), + SND_PCI_QUIRK(0x1025, 0x0241, "Packard Bell DOTS", ALC662_FIXUP_INV_DMIC), + SND_PCI_QUIRK(0x1025, 0x0308, "Acer Aspire 8942G", ALC662_FIXUP_ASPIRE), + SND_PCI_QUIRK(0x1025, 0x031c, "Gateway NV79", ALC662_FIXUP_SKU_IGNORE), + SND_PCI_QUIRK(0x1025, 0x0349, "eMachines eM250", ALC662_FIXUP_INV_DMIC), + SND_PCI_QUIRK(0x1025, 0x034a, "Gateway LT27", ALC662_FIXUP_INV_DMIC), + SND_PCI_QUIRK(0x1025, 0x038b, "Acer Aspire 8943G", ALC662_FIXUP_ASPIRE), + SND_PCI_QUIRK(0x1025, 0x0566, "Acer Aspire Ethos 8951G", ALC669_FIXUP_ACER_ASPIRE_ETHOS), + SND_PCI_QUIRK(0x1025, 0x123c, "Acer Nitro N50-600", ALC662_FIXUP_ACER_NITRO_HEADSET_MODE), + SND_PCI_QUIRK(0x1025, 0x124e, "Acer 2660G", ALC662_FIXUP_ACER_X2660G_HEADSET_MODE), + SND_PCI_QUIRK(0x1028, 0x05d8, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x05db, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x05fe, "Dell XPS 15", ALC668_FIXUP_DELL_XPS13), + SND_PCI_QUIRK(0x1028, 0x060a, "Dell XPS 13", ALC668_FIXUP_DELL_XPS13), + SND_PCI_QUIRK(0x1028, 0x060d, "Dell M3800", ALC668_FIXUP_DELL_XPS13), + SND_PCI_QUIRK(0x1028, 0x0625, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x0626, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x0696, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x0698, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x069f, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x1632, "HP RP5800", ALC662_FIXUP_HP_RP5800), + SND_PCI_QUIRK(0x103c, 0x870c, "HP", ALC897_FIXUP_HP_HSMIC_VERB), + SND_PCI_QUIRK(0x103c, 0x8719, "HP", ALC897_FIXUP_HP_HSMIC_VERB), + SND_PCI_QUIRK(0x103c, 0x872b, "HP", ALC897_FIXUP_HP_HSMIC_VERB), + SND_PCI_QUIRK(0x103c, 0x873e, "HP", ALC671_FIXUP_HP_HEADSET_MIC2), + SND_PCI_QUIRK(0x103c, 0x8768, "HP Slim Desktop S01", ALC671_FIXUP_HP_HEADSET_MIC2), + SND_PCI_QUIRK(0x103c, 0x877e, "HP 288 Pro G6", ALC671_FIXUP_HP_HEADSET_MIC2), + SND_PCI_QUIRK(0x103c, 0x885f, "HP 288 Pro G8", ALC671_FIXUP_HP_HEADSET_MIC2), + SND_PCI_QUIRK(0x1043, 0x1080, "Asus UX501VW", ALC668_FIXUP_HEADSET_MODE), + SND_PCI_QUIRK(0x1043, 0x11cd, "Asus N550", ALC662_FIXUP_ASUS_Nx50), + SND_PCI_QUIRK(0x1043, 0x129d, "Asus N750", ALC662_FIXUP_ASUS_Nx50), + SND_PCI_QUIRK(0x1043, 0x12ff, "ASUS G751", ALC668_FIXUP_ASUS_G751), + SND_PCI_QUIRK(0x1043, 0x13df, "Asus N550JX", ALC662_FIXUP_BASS_1A), + SND_PCI_QUIRK(0x1043, 0x1477, "ASUS N56VZ", ALC662_FIXUP_BASS_MODE4_CHMAP), + SND_PCI_QUIRK(0x1043, 0x15a7, "ASUS UX51VZH", ALC662_FIXUP_BASS_16), + SND_PCI_QUIRK(0x1043, 0x177d, "ASUS N551", ALC668_FIXUP_ASUS_Nx51), + SND_PCI_QUIRK(0x1043, 0x17bd, "ASUS N751", ALC668_FIXUP_ASUS_Nx51), + SND_PCI_QUIRK(0x1043, 0x185d, "ASUS G551JW", ALC668_FIXUP_ASUS_NO_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1963, "ASUS X71SL", ALC662_FIXUP_ASUS_MODE8), + SND_PCI_QUIRK(0x1043, 0x1b73, "ASUS N55SF", ALC662_FIXUP_BASS_16), + SND_PCI_QUIRK(0x1043, 0x1bf3, "ASUS N76VZ", ALC662_FIXUP_BASS_MODE4_CHMAP), + SND_PCI_QUIRK(0x1043, 0x8469, "ASUS mobo", ALC662_FIXUP_NO_JACK_DETECT), + SND_PCI_QUIRK(0x105b, 0x0cd6, "Foxconn", ALC662_FIXUP_ASUS_MODE2), + SND_PCI_QUIRK(0x144d, 0xc051, "Samsung R720", ALC662_FIXUP_IDEAPAD), + SND_PCI_QUIRK(0x14cd, 0x5003, "USI", ALC662_FIXUP_USI_HEADSET_MODE), + SND_PCI_QUIRK(0x17aa, 0x1036, "Lenovo P520", ALC662_FIXUP_LENOVO_MULTI_CODECS), + SND_PCI_QUIRK(0x17aa, 0x1057, "Lenovo P360", ALC897_FIXUP_HEADSET_MIC_PIN), + SND_PCI_QUIRK(0x17aa, 0x1064, "Lenovo P3 Tower", ALC897_FIXUP_HEADSET_MIC_PIN), + SND_PCI_QUIRK(0x17aa, 0x32ca, "Lenovo ThinkCentre M80", ALC897_FIXUP_HEADSET_MIC_PIN), + SND_PCI_QUIRK(0x17aa, 0x32cb, "Lenovo ThinkCentre M70", ALC897_FIXUP_HEADSET_MIC_PIN), + SND_PCI_QUIRK(0x17aa, 0x32cf, "Lenovo ThinkCentre M950", ALC897_FIXUP_HEADSET_MIC_PIN), + SND_PCI_QUIRK(0x17aa, 0x32f7, "Lenovo ThinkCentre M90", ALC897_FIXUP_HEADSET_MIC_PIN), + SND_PCI_QUIRK(0x17aa, 0x3321, "Lenovo ThinkCentre M70 Gen4", ALC897_FIXUP_HEADSET_MIC_PIN), + SND_PCI_QUIRK(0x17aa, 0x331b, "Lenovo ThinkCentre M90 Gen4", ALC897_FIXUP_HEADSET_MIC_PIN), + SND_PCI_QUIRK(0x17aa, 0x3364, "Lenovo ThinkCentre M90 Gen5", ALC897_FIXUP_HEADSET_MIC_PIN), + SND_PCI_QUIRK(0x17aa, 0x3742, "Lenovo TianYi510Pro-14IOB", ALC897_FIXUP_HEADSET_MIC_PIN2), + SND_PCI_QUIRK(0x17aa, 0x38af, "Lenovo Ideapad Y550P", ALC662_FIXUP_IDEAPAD), + SND_PCI_QUIRK(0x17aa, 0x3a0d, "Lenovo Ideapad Y550", ALC662_FIXUP_IDEAPAD), + SND_PCI_QUIRK(0x1849, 0x5892, "ASRock B150M", ALC892_FIXUP_ASROCK_MOBO), + SND_PCI_QUIRK(0x19da, 0xa130, "Zotac Z68", ALC662_FIXUP_ZOTAC_Z68), + SND_PCI_QUIRK(0x1b0a, 0x01b8, "ACER Veriton", ALC662_FIXUP_ACER_VERITON), + SND_PCI_QUIRK(0x1b35, 0x1234, "CZC ET26", ALC662_FIXUP_CZC_ET26), + SND_PCI_QUIRK(0x1b35, 0x2206, "CZC P10T", ALC662_FIXUP_CZC_P10T), + SND_PCI_QUIRK(0x1c6c, 0x1239, "Compaq N14JP6-V2", ALC897_FIXUP_HP_HSMIC_VERB), + +#if 0 + /* Below is a quirk table taken from the old code. + * Basically the device should work as is without the fixup table. + * If BIOS doesn't give a proper info, enable the corresponding + * fixup entry. + */ + SND_PCI_QUIRK(0x1043, 0x1000, "ASUS N50Vm", ALC662_FIXUP_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x1092, "ASUS NB", ALC662_FIXUP_ASUS_MODE3), + SND_PCI_QUIRK(0x1043, 0x1173, "ASUS K73Jn", ALC662_FIXUP_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x11c3, "ASUS M70V", ALC662_FIXUP_ASUS_MODE3), + SND_PCI_QUIRK(0x1043, 0x11d3, "ASUS NB", ALC662_FIXUP_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x11f3, "ASUS NB", ALC662_FIXUP_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x1203, "ASUS NB", ALC662_FIXUP_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x1303, "ASUS G60J", ALC662_FIXUP_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x1333, "ASUS G60Jx", ALC662_FIXUP_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x1339, "ASUS NB", ALC662_FIXUP_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x13e3, "ASUS N71JA", ALC662_FIXUP_ASUS_MODE7), + SND_PCI_QUIRK(0x1043, 0x1463, "ASUS N71", ALC662_FIXUP_ASUS_MODE7), + SND_PCI_QUIRK(0x1043, 0x14d3, "ASUS G72", ALC662_FIXUP_ASUS_MODE8), + SND_PCI_QUIRK(0x1043, 0x1563, "ASUS N90", ALC662_FIXUP_ASUS_MODE3), + SND_PCI_QUIRK(0x1043, 0x15d3, "ASUS N50SF F50SF", ALC662_FIXUP_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x16c3, "ASUS NB", ALC662_FIXUP_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x16f3, "ASUS K40C K50C", ALC662_FIXUP_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x1733, "ASUS N81De", ALC662_FIXUP_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x1753, "ASUS NB", ALC662_FIXUP_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x1763, "ASUS NB", ALC662_FIXUP_ASUS_MODE6), + SND_PCI_QUIRK(0x1043, 0x1765, "ASUS NB", ALC662_FIXUP_ASUS_MODE6), + SND_PCI_QUIRK(0x1043, 0x1783, "ASUS NB", ALC662_FIXUP_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x1793, "ASUS F50GX", ALC662_FIXUP_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x17b3, "ASUS F70SL", ALC662_FIXUP_ASUS_MODE3), + SND_PCI_QUIRK(0x1043, 0x17f3, "ASUS X58LE", ALC662_FIXUP_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x1813, "ASUS NB", ALC662_FIXUP_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x1823, "ASUS NB", ALC662_FIXUP_ASUS_MODE5), + SND_PCI_QUIRK(0x1043, 0x1833, "ASUS NB", ALC662_FIXUP_ASUS_MODE6), + SND_PCI_QUIRK(0x1043, 0x1843, "ASUS NB", ALC662_FIXUP_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x1853, "ASUS F50Z", ALC662_FIXUP_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x1864, "ASUS NB", ALC662_FIXUP_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x1876, "ASUS NB", ALC662_FIXUP_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x1893, "ASUS M50Vm", ALC662_FIXUP_ASUS_MODE3), + SND_PCI_QUIRK(0x1043, 0x1894, "ASUS X55", ALC662_FIXUP_ASUS_MODE3), + SND_PCI_QUIRK(0x1043, 0x18b3, "ASUS N80Vc", ALC662_FIXUP_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x18c3, "ASUS VX5", ALC662_FIXUP_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x18d3, "ASUS N81Te", ALC662_FIXUP_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x18f3, "ASUS N505Tp", ALC662_FIXUP_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x1903, "ASUS F5GL", ALC662_FIXUP_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x1913, "ASUS NB", ALC662_FIXUP_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x1933, "ASUS F80Q", ALC662_FIXUP_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x1943, "ASUS Vx3V", ALC662_FIXUP_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x1953, "ASUS NB", ALC662_FIXUP_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x1963, "ASUS X71C", ALC662_FIXUP_ASUS_MODE3), + SND_PCI_QUIRK(0x1043, 0x1983, "ASUS N5051A", ALC662_FIXUP_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x1993, "ASUS N20", ALC662_FIXUP_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x19b3, "ASUS F7Z", ALC662_FIXUP_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x19c3, "ASUS F5Z/F6x", ALC662_FIXUP_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x19e3, "ASUS NB", ALC662_FIXUP_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x19f3, "ASUS NB", ALC662_FIXUP_ASUS_MODE4), +#endif + {} +}; + +static const struct hda_model_fixup alc662_fixup_models[] = { + {.id = ALC662_FIXUP_ASPIRE, .name = "aspire"}, + {.id = ALC662_FIXUP_IDEAPAD, .name = "ideapad"}, + {.id = ALC272_FIXUP_MARIO, .name = "mario"}, + {.id = ALC662_FIXUP_HP_RP5800, .name = "hp-rp5800"}, + {.id = ALC662_FIXUP_ASUS_MODE1, .name = "asus-mode1"}, + {.id = ALC662_FIXUP_ASUS_MODE2, .name = "asus-mode2"}, + {.id = ALC662_FIXUP_ASUS_MODE3, .name = "asus-mode3"}, + {.id = ALC662_FIXUP_ASUS_MODE4, .name = "asus-mode4"}, + {.id = ALC662_FIXUP_ASUS_MODE5, .name = "asus-mode5"}, + {.id = ALC662_FIXUP_ASUS_MODE6, .name = "asus-mode6"}, + {.id = ALC662_FIXUP_ASUS_MODE7, .name = "asus-mode7"}, + {.id = ALC662_FIXUP_ASUS_MODE8, .name = "asus-mode8"}, + {.id = ALC662_FIXUP_ZOTAC_Z68, .name = "zotac-z68"}, + {.id = ALC662_FIXUP_INV_DMIC, .name = "inv-dmic"}, + {.id = ALC662_FIXUP_DELL_MIC_NO_PRESENCE, .name = "alc662-headset-multi"}, + {.id = ALC668_FIXUP_DELL_MIC_NO_PRESENCE, .name = "dell-headset-multi"}, + {.id = ALC662_FIXUP_HEADSET_MODE, .name = "alc662-headset"}, + {.id = ALC668_FIXUP_HEADSET_MODE, .name = "alc668-headset"}, + {.id = ALC662_FIXUP_BASS_16, .name = "bass16"}, + {.id = ALC662_FIXUP_BASS_1A, .name = "bass1a"}, + {.id = ALC668_FIXUP_AUTO_MUTE, .name = "automute"}, + {.id = ALC668_FIXUP_DELL_XPS13, .name = "dell-xps13"}, + {.id = ALC662_FIXUP_ASUS_Nx50, .name = "asus-nx50"}, + {.id = ALC668_FIXUP_ASUS_Nx51, .name = "asus-nx51"}, + {.id = ALC668_FIXUP_ASUS_G751, .name = "asus-g751"}, + {.id = ALC891_FIXUP_HEADSET_MODE, .name = "alc891-headset"}, + {.id = ALC891_FIXUP_DELL_MIC_NO_PRESENCE, .name = "alc891-headset-multi"}, + {.id = ALC662_FIXUP_ACER_VERITON, .name = "acer-veriton"}, + {.id = ALC892_FIXUP_ASROCK_MOBO, .name = "asrock-mobo"}, + {.id = ALC662_FIXUP_USI_HEADSET_MODE, .name = "usi-headset"}, + {.id = ALC662_FIXUP_LENOVO_MULTI_CODECS, .name = "dual-codecs"}, + {.id = ALC669_FIXUP_ACER_ASPIRE_ETHOS, .name = "aspire-ethos"}, + {.id = ALC897_FIXUP_UNIS_H3C_X500S, .name = "unis-h3c-x500s"}, + {} +}; + +static const struct snd_hda_pin_quirk alc662_pin_fixup_tbl[] = { + SND_HDA_PIN_QUIRK(0x10ec0867, 0x1028, "Dell", ALC891_FIXUP_DELL_MIC_NO_PRESENCE, + {0x17, 0x02211010}, + {0x18, 0x01a19030}, + {0x1a, 0x01813040}, + {0x21, 0x01014020}), + SND_HDA_PIN_QUIRK(0x10ec0867, 0x1028, "Dell", ALC891_FIXUP_DELL_MIC_NO_PRESENCE, + {0x16, 0x01813030}, + {0x17, 0x02211010}, + {0x18, 0x01a19040}, + {0x21, 0x01014020}), + SND_HDA_PIN_QUIRK(0x10ec0662, 0x1028, "Dell", ALC662_FIXUP_DELL_MIC_NO_PRESENCE, + {0x14, 0x01014010}, + {0x18, 0x01a19020}, + {0x1a, 0x0181302f}, + {0x1b, 0x0221401f}), + SND_HDA_PIN_QUIRK(0x10ec0668, 0x1028, "Dell", ALC668_FIXUP_AUTO_MUTE, + {0x12, 0x99a30130}, + {0x14, 0x90170110}, + {0x15, 0x0321101f}, + {0x16, 0x03011020}), + SND_HDA_PIN_QUIRK(0x10ec0668, 0x1028, "Dell", ALC668_FIXUP_AUTO_MUTE, + {0x12, 0x99a30140}, + {0x14, 0x90170110}, + {0x15, 0x0321101f}, + {0x16, 0x03011020}), + SND_HDA_PIN_QUIRK(0x10ec0668, 0x1028, "Dell", ALC668_FIXUP_AUTO_MUTE, + {0x12, 0x99a30150}, + {0x14, 0x90170110}, + {0x15, 0x0321101f}, + {0x16, 0x03011020}), + SND_HDA_PIN_QUIRK(0x10ec0668, 0x1028, "Dell", ALC668_FIXUP_AUTO_MUTE, + {0x14, 0x90170110}, + {0x15, 0x0321101f}, + {0x16, 0x03011020}), + SND_HDA_PIN_QUIRK(0x10ec0668, 0x1028, "Dell XPS 15", ALC668_FIXUP_AUTO_MUTE, + {0x12, 0x90a60130}, + {0x14, 0x90170110}, + {0x15, 0x0321101f}), + SND_HDA_PIN_QUIRK(0x10ec0671, 0x103c, "HP cPC", ALC671_FIXUP_HP_HEADSET_MIC2, + {0x14, 0x01014010}, + {0x17, 0x90170150}, + {0x19, 0x02a11060}, + {0x1b, 0x01813030}, + {0x21, 0x02211020}), + SND_HDA_PIN_QUIRK(0x10ec0671, 0x103c, "HP cPC", ALC671_FIXUP_HP_HEADSET_MIC2, + {0x14, 0x01014010}, + {0x18, 0x01a19040}, + {0x1b, 0x01813030}, + {0x21, 0x02211020}), + SND_HDA_PIN_QUIRK(0x10ec0671, 0x103c, "HP cPC", ALC671_FIXUP_HP_HEADSET_MIC2, + {0x14, 0x01014020}, + {0x17, 0x90170110}, + {0x18, 0x01a19050}, + {0x1b, 0x01813040}, + {0x21, 0x02211030}), + {} +}; + +/* + */ +static int patch_alc662(struct hda_codec *codec) +{ + struct alc_spec *spec; + int err; + + err = alc_alloc_spec(codec, 0x0b); + if (err < 0) + return err; + + spec = codec->spec; + + spec->shutup = alc_eapd_shutup; + + /* handle multiple HPs as is */ + spec->parse_flags = HDA_PINCFG_NO_HP_FIXUP; + + alc_fix_pll_init(codec, 0x20, 0x04, 15); + + switch (codec->core.vendor_id) { + case 0x10ec0668: + spec->init_hook = alc668_restore_default_value; + break; + } + + alc_pre_init(codec); + + snd_hda_pick_fixup(codec, alc662_fixup_models, + alc662_fixup_tbl, alc662_fixups); + snd_hda_pick_pin_fixup(codec, alc662_pin_fixup_tbl, alc662_fixups, true); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + alc_auto_parse_customize_define(codec); + + if (has_cdefine_beep(codec)) + spec->gen.beep_nid = 0x01; + + if ((alc_get_coef0(codec) & (1 << 14)) && + codec->bus->pci && codec->bus->pci->subsystem_vendor == 0x1025 && + spec->cdefine.platform_type == 1) { + err = alc_codec_rename(codec, "ALC272X"); + if (err < 0) + goto error; + } + + /* automatic parse from the BIOS config */ + err = alc662_parse_auto_config(codec); + if (err < 0) + goto error; + + if (!spec->gen.no_analog && spec->gen.beep_nid) { + switch (codec->core.vendor_id) { + case 0x10ec0662: + err = set_beep_amp(spec, 0x0b, 0x05, HDA_INPUT); + break; + case 0x10ec0272: + case 0x10ec0663: + case 0x10ec0665: + case 0x10ec0668: + err = set_beep_amp(spec, 0x0b, 0x04, HDA_INPUT); + break; + case 0x10ec0273: + err = set_beep_amp(spec, 0x0b, 0x03, HDA_INPUT); + break; + } + if (err < 0) + goto error; + } + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; + + error: + alc_free(codec); + return err; +} + +/* + * ALC680 support + */ + +static int alc680_parse_auto_config(struct hda_codec *codec) +{ + return alc_parse_auto_config(codec, NULL, NULL); +} + +/* + */ +static int patch_alc680(struct hda_codec *codec) +{ + int err; + + /* ALC680 has no aa-loopback mixer */ + err = alc_alloc_spec(codec, 0); + if (err < 0) + return err; + + /* automatic parse from the BIOS config */ + err = alc680_parse_auto_config(codec); + if (err < 0) { + alc_free(codec); + return err; + } + + return 0; +} + +/* + * patch entries + */ +static const struct hda_device_id snd_hda_id_realtek[] = { + HDA_CODEC_ENTRY(0x10ec0215, "ALC215", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0221, "ALC221", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0222, "ALC222", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0225, "ALC225", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0230, "ALC236", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0231, "ALC231", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0233, "ALC233", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0234, "ALC234", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0235, "ALC233", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0236, "ALC236", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0245, "ALC245", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0255, "ALC255", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0256, "ALC256", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0257, "ALC257", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0260, "ALC260", patch_alc260), + HDA_CODEC_ENTRY(0x10ec0262, "ALC262", patch_alc262), + HDA_CODEC_ENTRY(0x10ec0267, "ALC267", patch_alc268), + HDA_CODEC_ENTRY(0x10ec0268, "ALC268", patch_alc268), + HDA_CODEC_ENTRY(0x10ec0269, "ALC269", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0270, "ALC270", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0272, "ALC272", patch_alc662), + HDA_CODEC_ENTRY(0x10ec0274, "ALC274", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0275, "ALC275", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0276, "ALC276", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0280, "ALC280", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0282, "ALC282", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0283, "ALC283", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0284, "ALC284", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0285, "ALC285", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0286, "ALC286", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0287, "ALC287", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0288, "ALC288", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0289, "ALC289", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0290, "ALC290", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0292, "ALC292", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0293, "ALC293", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0294, "ALC294", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0295, "ALC295", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0298, "ALC298", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0299, "ALC299", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0300, "ALC300", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0623, "ALC623", patch_alc269), + HDA_CODEC_REV_ENTRY(0x10ec0861, 0x100340, "ALC660", patch_alc861), + HDA_CODEC_ENTRY(0x10ec0660, "ALC660-VD", patch_alc861vd), + HDA_CODEC_ENTRY(0x10ec0861, "ALC861", patch_alc861), + HDA_CODEC_ENTRY(0x10ec0862, "ALC861-VD", patch_alc861vd), + HDA_CODEC_REV_ENTRY(0x10ec0662, 0x100002, "ALC662 rev2", patch_alc882), + HDA_CODEC_REV_ENTRY(0x10ec0662, 0x100101, "ALC662 rev1", patch_alc662), + HDA_CODEC_REV_ENTRY(0x10ec0662, 0x100300, "ALC662 rev3", patch_alc662), + HDA_CODEC_ENTRY(0x10ec0663, "ALC663", patch_alc662), + HDA_CODEC_ENTRY(0x10ec0665, "ALC665", patch_alc662), + HDA_CODEC_ENTRY(0x10ec0667, "ALC667", patch_alc662), + HDA_CODEC_ENTRY(0x10ec0668, "ALC668", patch_alc662), + HDA_CODEC_ENTRY(0x10ec0670, "ALC670", patch_alc662), + HDA_CODEC_ENTRY(0x10ec0671, "ALC671", patch_alc662), + HDA_CODEC_ENTRY(0x10ec0680, "ALC680", patch_alc680), + HDA_CODEC_ENTRY(0x10ec0700, "ALC700", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0701, "ALC701", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0703, "ALC703", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0711, "ALC711", patch_alc269), + HDA_CODEC_ENTRY(0x10ec0867, "ALC891", patch_alc662), + HDA_CODEC_ENTRY(0x10ec0880, "ALC880", patch_alc880), + HDA_CODEC_ENTRY(0x10ec0882, "ALC882", patch_alc882), + HDA_CODEC_ENTRY(0x10ec0883, "ALC883", patch_alc882), + HDA_CODEC_REV_ENTRY(0x10ec0885, 0x100101, "ALC889A", patch_alc882), + HDA_CODEC_REV_ENTRY(0x10ec0885, 0x100103, "ALC889A", patch_alc882), + HDA_CODEC_ENTRY(0x10ec0885, "ALC885", patch_alc882), + HDA_CODEC_ENTRY(0x10ec0887, "ALC887", patch_alc882), + HDA_CODEC_REV_ENTRY(0x10ec0888, 0x100101, "ALC1200", patch_alc882), + HDA_CODEC_ENTRY(0x10ec0888, "ALC888", patch_alc882), + HDA_CODEC_ENTRY(0x10ec0889, "ALC889", patch_alc882), + HDA_CODEC_ENTRY(0x10ec0892, "ALC892", patch_alc662), + HDA_CODEC_ENTRY(0x10ec0897, "ALC897", patch_alc662), + HDA_CODEC_ENTRY(0x10ec0899, "ALC898", patch_alc882), + HDA_CODEC_ENTRY(0x10ec0900, "ALC1150", patch_alc882), + HDA_CODEC_ENTRY(0x10ec0b00, "ALCS1200A", patch_alc882), + HDA_CODEC_ENTRY(0x10ec1168, "ALC1220", patch_alc882), + HDA_CODEC_ENTRY(0x10ec1220, "ALC1220", patch_alc882), + HDA_CODEC_ENTRY(0x19e58326, "HW8326", patch_alc269), + {} /* terminator */ +}; +MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_realtek); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Realtek HD-audio codec"); + +static struct hda_codec_driver realtek_driver = { + .id = snd_hda_id_realtek, +}; + +module_hda_codec_driver(realtek_driver); diff --git a/sound/pci/hda/patch_si3054.c b/sound/pci/hda/patch_si3054.c new file mode 100644 index 000000000..763eae80a --- /dev/null +++ b/sound/pci/hda/patch_si3054.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Universal Interface for Intel High Definition Audio Codec + * + * HD audio interface patch for Silicon Labs 3054/5 modem codec + * + * Copyright (c) 2005 Sasha Khapyorsky <sashak@alsa-project.org> + * Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/hda_codec.h> +#include "hda_local.h" + +/* si3054 verbs */ +#define SI3054_VERB_READ_NODE 0x900 +#define SI3054_VERB_WRITE_NODE 0x100 + +/* si3054 nodes (registers) */ +#define SI3054_EXTENDED_MID 2 +#define SI3054_LINE_RATE 3 +#define SI3054_LINE_LEVEL 4 +#define SI3054_GPIO_CFG 5 +#define SI3054_GPIO_POLARITY 6 +#define SI3054_GPIO_STICKY 7 +#define SI3054_GPIO_WAKEUP 8 +#define SI3054_GPIO_STATUS 9 +#define SI3054_GPIO_CONTROL 10 +#define SI3054_MISC_AFE 11 +#define SI3054_CHIPID 12 +#define SI3054_LINE_CFG1 13 +#define SI3054_LINE_STATUS 14 +#define SI3054_DC_TERMINATION 15 +#define SI3054_LINE_CONFIG 16 +#define SI3054_CALLPROG_ATT 17 +#define SI3054_SQ_CONTROL 18 +#define SI3054_MISC_CONTROL 19 +#define SI3054_RING_CTRL1 20 +#define SI3054_RING_CTRL2 21 + +/* extended MID */ +#define SI3054_MEI_READY 0xf + +/* line level */ +#define SI3054_ATAG_MASK 0x00f0 +#define SI3054_DTAG_MASK 0xf000 + +/* GPIO bits */ +#define SI3054_GPIO_OH 0x0001 +#define SI3054_GPIO_CID 0x0002 + +/* chipid and revisions */ +#define SI3054_CHIPID_CODEC_REV_MASK 0x000f +#define SI3054_CHIPID_DAA_REV_MASK 0x00f0 +#define SI3054_CHIPID_INTERNATIONAL 0x0100 +#define SI3054_CHIPID_DAA_ID 0x0f00 +#define SI3054_CHIPID_CODEC_ID (1<<12) + +/* si3054 codec registers (nodes) access macros */ +#define GET_REG(codec,reg) (snd_hda_codec_read(codec,reg,0,SI3054_VERB_READ_NODE,0)) +#define SET_REG(codec,reg,val) (snd_hda_codec_write(codec,reg,0,SI3054_VERB_WRITE_NODE,val)) +#define SET_REG_CACHE(codec,reg,val) \ + snd_hda_codec_write_cache(codec,reg,0,SI3054_VERB_WRITE_NODE,val) + + +struct si3054_spec { + unsigned international; +}; + + +/* + * Modem mixer + */ + +#define PRIVATE_VALUE(reg,mask) ((reg<<16)|(mask&0xffff)) +#define PRIVATE_REG(val) ((val>>16)&0xffff) +#define PRIVATE_MASK(val) (val&0xffff) + +#define si3054_switch_info snd_ctl_boolean_mono_info + +static int si3054_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + u16 reg = PRIVATE_REG(kcontrol->private_value); + u16 mask = PRIVATE_MASK(kcontrol->private_value); + uvalue->value.integer.value[0] = (GET_REG(codec, reg)) & mask ? 1 : 0 ; + return 0; +} + +static int si3054_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + u16 reg = PRIVATE_REG(kcontrol->private_value); + u16 mask = PRIVATE_MASK(kcontrol->private_value); + if (uvalue->value.integer.value[0]) + SET_REG_CACHE(codec, reg, (GET_REG(codec, reg)) | mask); + else + SET_REG_CACHE(codec, reg, (GET_REG(codec, reg)) & ~mask); + return 0; +} + +#define SI3054_KCONTROL(kname,reg,mask) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = kname, \ + .subdevice = HDA_SUBDEV_NID_FLAG | reg, \ + .info = si3054_switch_info, \ + .get = si3054_switch_get, \ + .put = si3054_switch_put, \ + .private_value = PRIVATE_VALUE(reg,mask), \ +} + + +static const struct snd_kcontrol_new si3054_modem_mixer[] = { + SI3054_KCONTROL("Off-hook Switch", SI3054_GPIO_CONTROL, SI3054_GPIO_OH), + SI3054_KCONTROL("Caller ID Switch", SI3054_GPIO_CONTROL, SI3054_GPIO_CID), + {} +}; + +static int si3054_build_controls(struct hda_codec *codec) +{ + return snd_hda_add_new_ctls(codec, si3054_modem_mixer); +} + + +/* + * PCM callbacks + */ + +static int si3054_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + u16 val; + + SET_REG(codec, SI3054_LINE_RATE, substream->runtime->rate); + val = GET_REG(codec, SI3054_LINE_LEVEL); + val &= 0xff << (8 * (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)); + val |= ((stream_tag & 0xf) << 4) << (8 * (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)); + SET_REG(codec, SI3054_LINE_LEVEL, val); + + snd_hda_codec_setup_stream(codec, hinfo->nid, + stream_tag, 0, format); + return 0; +} + +static int si3054_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + static const unsigned int rates[] = { 8000, 9600, 16000 }; + static const struct snd_pcm_hw_constraint_list hw_constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, + }; + substream->runtime->hw.period_bytes_min = 80; + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates); +} + + +static const struct hda_pcm_stream si3054_pcm = { + .substreams = 1, + .channels_min = 1, + .channels_max = 1, + .nid = 0x1, + .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .maxbps = 16, + .ops = { + .open = si3054_pcm_open, + .prepare = si3054_pcm_prepare, + }, +}; + + +static int si3054_build_pcms(struct hda_codec *codec) +{ + struct hda_pcm *info; + + info = snd_hda_codec_pcm_new(codec, "Si3054 Modem"); + if (!info) + return -ENOMEM; + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = si3054_pcm; + info->stream[SNDRV_PCM_STREAM_CAPTURE] = si3054_pcm; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = codec->core.mfg; + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = codec->core.mfg; + info->pcm_type = HDA_PCM_TYPE_MODEM; + return 0; +} + + +/* + * Init part + */ + +static int si3054_init(struct hda_codec *codec) +{ + struct si3054_spec *spec = codec->spec; + unsigned wait_count; + u16 val; + + if (snd_hdac_regmap_add_vendor_verb(&codec->core, + SI3054_VERB_WRITE_NODE)) + return -ENOMEM; + + snd_hda_codec_write(codec, AC_NODE_ROOT, 0, AC_VERB_SET_CODEC_RESET, 0); + snd_hda_codec_write(codec, codec->core.mfg, 0, AC_VERB_SET_STREAM_FORMAT, 0); + SET_REG(codec, SI3054_LINE_RATE, 9600); + SET_REG(codec, SI3054_LINE_LEVEL, SI3054_DTAG_MASK|SI3054_ATAG_MASK); + SET_REG(codec, SI3054_EXTENDED_MID, 0); + + wait_count = 10; + do { + msleep(2); + val = GET_REG(codec, SI3054_EXTENDED_MID); + } while ((val & SI3054_MEI_READY) != SI3054_MEI_READY && wait_count--); + + if((val&SI3054_MEI_READY) != SI3054_MEI_READY) { + codec_err(codec, "si3054: cannot initialize. EXT MID = %04x\n", val); + /* let's pray that this is no fatal error */ + /* return -EACCES; */ + } + + SET_REG(codec, SI3054_GPIO_POLARITY, 0xffff); + SET_REG(codec, SI3054_GPIO_CFG, 0x0); + SET_REG(codec, SI3054_MISC_AFE, 0); + SET_REG(codec, SI3054_LINE_CFG1,0x200); + + if((GET_REG(codec,SI3054_LINE_STATUS) & (1<<6)) == 0) { + codec_dbg(codec, + "Link Frame Detect(FDT) is not ready (line status: %04x)\n", + GET_REG(codec,SI3054_LINE_STATUS)); + } + + spec->international = GET_REG(codec, SI3054_CHIPID) & SI3054_CHIPID_INTERNATIONAL; + + return 0; +} + +static void si3054_free(struct hda_codec *codec) +{ + kfree(codec->spec); +} + + +/* + */ + +static const struct hda_codec_ops si3054_patch_ops = { + .build_controls = si3054_build_controls, + .build_pcms = si3054_build_pcms, + .init = si3054_init, + .free = si3054_free, +}; + +static int patch_si3054(struct hda_codec *codec) +{ + struct si3054_spec *spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + codec->spec = spec; + codec->patch_ops = si3054_patch_ops; + return 0; +} + +/* + * patch entries + */ +static const struct hda_device_id snd_hda_id_si3054[] = { + HDA_CODEC_ENTRY(0x163c3055, "Si3054", patch_si3054), + HDA_CODEC_ENTRY(0x163c3155, "Si3054", patch_si3054), + HDA_CODEC_ENTRY(0x11c13026, "Si3054", patch_si3054), + HDA_CODEC_ENTRY(0x11c13055, "Si3054", patch_si3054), + HDA_CODEC_ENTRY(0x11c13155, "Si3054", patch_si3054), + HDA_CODEC_ENTRY(0x10573055, "Si3054", patch_si3054), + HDA_CODEC_ENTRY(0x10573057, "Si3054", patch_si3054), + HDA_CODEC_ENTRY(0x10573155, "Si3054", patch_si3054), + /* VIA HDA on Clevo m540 */ + HDA_CODEC_ENTRY(0x11063288, "Si3054", patch_si3054), + /* Asus A8J Modem (SM56) */ + HDA_CODEC_ENTRY(0x15433155, "Si3054", patch_si3054), + /* LG LW20 modem */ + HDA_CODEC_ENTRY(0x18540018, "Si3054", patch_si3054), + {} +}; +MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_si3054); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Si3054 HD-audio modem codec"); + +static struct hda_codec_driver si3054_driver = { + .id = snd_hda_id_si3054, +}; + +module_hda_codec_driver(si3054_driver); diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c new file mode 100644 index 000000000..61258b0aa --- /dev/null +++ b/sound/pci/hda/patch_sigmatel.c @@ -0,0 +1,5169 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Universal Interface for Intel High Definition Audio Codec + * + * HD audio interface patch for SigmaTel STAC92xx + * + * Copyright (c) 2005 Embedded Alley Solutions, Inc. + * Matt Porter <mporter@embeddedalley.com> + * + * Based on patch_cmedia.c and patch_realtek.c + * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/dmi.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/hda_codec.h> +#include "hda_local.h" +#include "hda_auto_parser.h" +#include "hda_beep.h" +#include "hda_jack.h" +#include "hda_generic.h" + +enum { + STAC_REF, + STAC_9200_OQO, + STAC_9200_DELL_D21, + STAC_9200_DELL_D22, + STAC_9200_DELL_D23, + STAC_9200_DELL_M21, + STAC_9200_DELL_M22, + STAC_9200_DELL_M23, + STAC_9200_DELL_M24, + STAC_9200_DELL_M25, + STAC_9200_DELL_M26, + STAC_9200_DELL_M27, + STAC_9200_M4, + STAC_9200_M4_2, + STAC_9200_PANASONIC, + STAC_9200_EAPD_INIT, + STAC_9200_MODELS +}; + +enum { + STAC_9205_REF, + STAC_9205_DELL_M42, + STAC_9205_DELL_M43, + STAC_9205_DELL_M44, + STAC_9205_EAPD, + STAC_9205_MODELS +}; + +enum { + STAC_92HD73XX_NO_JD, /* no jack-detection */ + STAC_92HD73XX_REF, + STAC_92HD73XX_INTEL, + STAC_DELL_M6_AMIC, + STAC_DELL_M6_DMIC, + STAC_DELL_M6_BOTH, + STAC_DELL_EQ, + STAC_ALIENWARE_M17X, + STAC_ELO_VUPOINT_15MX, + STAC_92HD89XX_HP_FRONT_JACK, + STAC_92HD89XX_HP_Z1_G2_RIGHT_MIC_JACK, + STAC_92HD73XX_ASUS_MOBO, + STAC_92HD73XX_MODELS +}; + +enum { + STAC_92HD83XXX_REF, + STAC_92HD83XXX_PWR_REF, + STAC_DELL_S14, + STAC_DELL_VOSTRO_3500, + STAC_92HD83XXX_HP_cNB11_INTQUAD, + STAC_HP_DV7_4000, + STAC_HP_ZEPHYR, + STAC_92HD83XXX_HP_LED, + STAC_92HD83XXX_HP_INV_LED, + STAC_92HD83XXX_HP_MIC_LED, + STAC_HP_LED_GPIO10, + STAC_92HD83XXX_HEADSET_JACK, + STAC_92HD83XXX_HP, + STAC_HP_ENVY_BASS, + STAC_HP_BNB13_EQ, + STAC_HP_ENVY_TS_BASS, + STAC_HP_ENVY_TS_DAC_BIND, + STAC_92HD83XXX_GPIO10_EAPD, + STAC_92HD83XXX_MODELS +}; + +enum { + STAC_92HD71BXX_REF, + STAC_DELL_M4_1, + STAC_DELL_M4_2, + STAC_DELL_M4_3, + STAC_HP_M4, + STAC_HP_DV4, + STAC_HP_DV5, + STAC_HP_HDX, + STAC_92HD71BXX_HP, + STAC_92HD71BXX_NO_DMIC, + STAC_92HD71BXX_NO_SMUX, + STAC_92HD71BXX_MODELS +}; + +enum { + STAC_92HD95_HP_LED, + STAC_92HD95_HP_BASS, + STAC_92HD95_MODELS +}; + +enum { + STAC_925x_REF, + STAC_M1, + STAC_M1_2, + STAC_M2, + STAC_M2_2, + STAC_M3, + STAC_M5, + STAC_M6, + STAC_925x_MODELS +}; + +enum { + STAC_D945_REF, + STAC_D945GTP3, + STAC_D945GTP5, + STAC_INTEL_MAC_V1, + STAC_INTEL_MAC_V2, + STAC_INTEL_MAC_V3, + STAC_INTEL_MAC_V4, + STAC_INTEL_MAC_V5, + STAC_INTEL_MAC_AUTO, + STAC_ECS_202, + STAC_922X_DELL_D81, + STAC_922X_DELL_D82, + STAC_922X_DELL_M81, + STAC_922X_DELL_M82, + STAC_922X_INTEL_MAC_GPIO, + STAC_922X_MODELS +}; + +enum { + STAC_D965_REF_NO_JD, /* no jack-detection */ + STAC_D965_REF, + STAC_D965_3ST, + STAC_D965_5ST, + STAC_D965_5ST_NO_FP, + STAC_D965_VERBS, + STAC_DELL_3ST, + STAC_DELL_BIOS, + STAC_NEMO_DEFAULT, + STAC_DELL_BIOS_AMIC, + STAC_DELL_BIOS_SPDIF, + STAC_927X_DELL_DMIC, + STAC_927X_VOLKNOB, + STAC_927X_MODELS +}; + +enum { + STAC_9872_VAIO, + STAC_9872_MODELS +}; + +struct sigmatel_spec { + struct hda_gen_spec gen; + + unsigned int eapd_switch: 1; + unsigned int linear_tone_beep:1; + unsigned int headset_jack:1; /* 4-pin headset jack (hp + mono mic) */ + unsigned int volknob_init:1; /* special volume-knob initialization */ + unsigned int powerdown_adcs:1; + unsigned int have_spdif_mux:1; + + /* gpio lines */ + unsigned int eapd_mask; + unsigned int gpio_mask; + unsigned int gpio_dir; + unsigned int gpio_data; + unsigned int gpio_mute; + unsigned int gpio_led; + unsigned int gpio_led_polarity; + unsigned int vref_mute_led_nid; /* pin NID for mute-LED vref control */ + unsigned int vref_led; + int default_polarity; + + unsigned int mic_mute_led_gpio; /* capture mute LED GPIO */ + unsigned int mic_enabled; /* current mic mute state (bitmask) */ + + /* stream */ + unsigned int stream_delay; + + /* analog loopback */ + const struct snd_kcontrol_new *aloopback_ctl; + unsigned int aloopback; + unsigned char aloopback_mask; + unsigned char aloopback_shift; + + /* power management */ + unsigned int power_map_bits; + unsigned int num_pwrs; + const hda_nid_t *pwr_nids; + unsigned int active_adcs; + + /* beep widgets */ + hda_nid_t anabeep_nid; + bool beep_power_on; + + /* SPDIF-out mux */ + const char * const *spdif_labels; + struct hda_input_mux spdif_mux; + unsigned int cur_smux[2]; +}; + +#define AC_VERB_IDT_SET_POWER_MAP 0x7ec +#define AC_VERB_IDT_GET_POWER_MAP 0xfec + +static const hda_nid_t stac92hd73xx_pwr_nids[8] = { + 0x0a, 0x0b, 0x0c, 0xd, 0x0e, + 0x0f, 0x10, 0x11 +}; + +static const hda_nid_t stac92hd83xxx_pwr_nids[7] = { + 0x0a, 0x0b, 0x0c, 0xd, 0x0e, + 0x0f, 0x10 +}; + +static const hda_nid_t stac92hd71bxx_pwr_nids[3] = { + 0x0a, 0x0d, 0x0f +}; + + +/* + * PCM hooks + */ +static void stac_playback_pcm_hook(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream, + int action) +{ + struct sigmatel_spec *spec = codec->spec; + if (action == HDA_GEN_PCM_ACT_OPEN && spec->stream_delay) + msleep(spec->stream_delay); +} + +static void stac_capture_pcm_hook(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream, + int action) +{ + struct sigmatel_spec *spec = codec->spec; + int i, idx = 0; + + if (!spec->powerdown_adcs) + return; + + for (i = 0; i < spec->gen.num_all_adcs; i++) { + if (spec->gen.all_adcs[i] == hinfo->nid) { + idx = i; + break; + } + } + + switch (action) { + case HDA_GEN_PCM_ACT_OPEN: + msleep(40); + snd_hda_codec_write(codec, hinfo->nid, 0, + AC_VERB_SET_POWER_STATE, AC_PWRST_D0); + spec->active_adcs |= (1 << idx); + break; + case HDA_GEN_PCM_ACT_CLOSE: + snd_hda_codec_write(codec, hinfo->nid, 0, + AC_VERB_SET_POWER_STATE, AC_PWRST_D3); + spec->active_adcs &= ~(1 << idx); + break; + } +} + +/* + * Early 2006 Intel Macintoshes with STAC9220X5 codecs seem to have a + * funky external mute control using GPIO pins. + */ + +static void stac_gpio_set(struct hda_codec *codec, unsigned int mask, + unsigned int dir_mask, unsigned int data) +{ + unsigned int gpiostate, gpiomask, gpiodir; + hda_nid_t fg = codec->core.afg; + + codec_dbg(codec, "%s msk %x dir %x gpio %x\n", __func__, mask, dir_mask, data); + + gpiostate = snd_hda_codec_read(codec, fg, 0, + AC_VERB_GET_GPIO_DATA, 0); + gpiostate = (gpiostate & ~dir_mask) | (data & dir_mask); + + gpiomask = snd_hda_codec_read(codec, fg, 0, + AC_VERB_GET_GPIO_MASK, 0); + gpiomask |= mask; + + gpiodir = snd_hda_codec_read(codec, fg, 0, + AC_VERB_GET_GPIO_DIRECTION, 0); + gpiodir |= dir_mask; + + /* Configure GPIOx as CMOS */ + snd_hda_codec_write(codec, fg, 0, 0x7e7, 0); + + snd_hda_codec_write(codec, fg, 0, + AC_VERB_SET_GPIO_MASK, gpiomask); + snd_hda_codec_read(codec, fg, 0, + AC_VERB_SET_GPIO_DIRECTION, gpiodir); /* sync */ + + msleep(1); + + snd_hda_codec_read(codec, fg, 0, + AC_VERB_SET_GPIO_DATA, gpiostate); /* sync */ +} + +/* hook for controlling mic-mute LED GPIO */ +static int stac_capture_led_update(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent); + struct sigmatel_spec *spec = codec->spec; + + if (brightness) + spec->gpio_data |= spec->mic_mute_led_gpio; + else + spec->gpio_data &= ~spec->mic_mute_led_gpio; + stac_gpio_set(codec, spec->gpio_mask, spec->gpio_dir, spec->gpio_data); + return 0; +} + +static int stac_vrefout_set(struct hda_codec *codec, + hda_nid_t nid, unsigned int new_vref) +{ + int error, pinctl; + + codec_dbg(codec, "%s, nid %x ctl %x\n", __func__, nid, new_vref); + pinctl = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + + if (pinctl < 0) + return pinctl; + + pinctl &= 0xff; + pinctl &= ~AC_PINCTL_VREFEN; + pinctl |= (new_vref & AC_PINCTL_VREFEN); + + error = snd_hda_set_pin_ctl_cache(codec, nid, pinctl); + if (error < 0) + return error; + + return 1; +} + +/* prevent codec AFG to D3 state when vref-out pin is used for mute LED */ +/* this hook is set in stac_setup_gpio() */ +static unsigned int stac_vref_led_power_filter(struct hda_codec *codec, + hda_nid_t nid, + unsigned int power_state) +{ + if (nid == codec->core.afg && power_state == AC_PWRST_D3) + return AC_PWRST_D1; + return snd_hda_gen_path_power_filter(codec, nid, power_state); +} + +/* update mute-LED accoring to the master switch */ +static void stac_update_led_status(struct hda_codec *codec, bool muted) +{ + struct sigmatel_spec *spec = codec->spec; + + if (!spec->gpio_led) + return; + + /* LED state is inverted on these systems */ + if (spec->gpio_led_polarity) + muted = !muted; + + if (!spec->vref_mute_led_nid) { + if (muted) + spec->gpio_data |= spec->gpio_led; + else + spec->gpio_data &= ~spec->gpio_led; + stac_gpio_set(codec, spec->gpio_mask, + spec->gpio_dir, spec->gpio_data); + } else { + spec->vref_led = muted ? AC_PINCTL_VREF_50 : AC_PINCTL_VREF_GRD; + stac_vrefout_set(codec, spec->vref_mute_led_nid, + spec->vref_led); + } +} + +/* vmaster hook to update mute LED */ +static int stac_vmaster_hook(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent); + + stac_update_led_status(codec, brightness); + return 0; +} + +/* automute hook to handle GPIO mute and EAPD updates */ +static void stac_update_outputs(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + + if (spec->gpio_mute) + spec->gen.master_mute = + !(snd_hda_codec_read(codec, codec->core.afg, 0, + AC_VERB_GET_GPIO_DATA, 0) & spec->gpio_mute); + + snd_hda_gen_update_outputs(codec); + + if (spec->eapd_mask && spec->eapd_switch) { + unsigned int val = spec->gpio_data; + if (spec->gen.speaker_muted) + val &= ~spec->eapd_mask; + else + val |= spec->eapd_mask; + if (spec->gpio_data != val) { + spec->gpio_data = val; + stac_gpio_set(codec, spec->gpio_mask, spec->gpio_dir, + val); + } + } +} + +static void stac_toggle_power_map(struct hda_codec *codec, hda_nid_t nid, + bool enable, bool do_write) +{ + struct sigmatel_spec *spec = codec->spec; + unsigned int idx, val; + + for (idx = 0; idx < spec->num_pwrs; idx++) { + if (spec->pwr_nids[idx] == nid) + break; + } + if (idx >= spec->num_pwrs) + return; + + idx = 1 << idx; + + val = spec->power_map_bits; + if (enable) + val &= ~idx; + else + val |= idx; + + /* power down unused output ports */ + if (val != spec->power_map_bits) { + spec->power_map_bits = val; + if (do_write) + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_IDT_SET_POWER_MAP, val); + } +} + +/* update power bit per jack plug/unplug */ +static void jack_update_power(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + struct sigmatel_spec *spec = codec->spec; + int i; + + if (!spec->num_pwrs) + return; + + if (jack && jack->nid) { + stac_toggle_power_map(codec, jack->nid, + snd_hda_jack_detect(codec, jack->nid), + true); + return; + } + + /* update all jacks */ + for (i = 0; i < spec->num_pwrs; i++) { + hda_nid_t nid = spec->pwr_nids[i]; + if (!snd_hda_jack_tbl_get(codec, nid)) + continue; + stac_toggle_power_map(codec, nid, + snd_hda_jack_detect(codec, nid), + false); + } + + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_IDT_SET_POWER_MAP, + spec->power_map_bits); +} + +static void stac_vref_event(struct hda_codec *codec, + struct hda_jack_callback *event) +{ + unsigned int data; + + data = snd_hda_codec_read(codec, codec->core.afg, 0, + AC_VERB_GET_GPIO_DATA, 0); + /* toggle VREF state based on GPIOx status */ + snd_hda_codec_write(codec, codec->core.afg, 0, 0x7e0, + !!(data & (1 << event->private_data))); +} + +/* initialize the power map and enable the power event to jacks that + * haven't been assigned to automute + */ +static void stac_init_power_map(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + int i; + + for (i = 0; i < spec->num_pwrs; i++) { + hda_nid_t nid = spec->pwr_nids[i]; + unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid); + def_conf = get_defcfg_connect(def_conf); + if (def_conf == AC_JACK_PORT_COMPLEX && + spec->vref_mute_led_nid != nid && + is_jack_detectable(codec, nid)) { + snd_hda_jack_detect_enable_callback(codec, nid, + jack_update_power); + } else { + if (def_conf == AC_JACK_PORT_NONE) + stac_toggle_power_map(codec, nid, false, false); + else + stac_toggle_power_map(codec, nid, true, false); + } + } +} + +/* + */ + +static inline bool get_int_hint(struct hda_codec *codec, const char *key, + int *valp) +{ + return !snd_hda_get_int_hint(codec, key, valp); +} + +/* override some hints from the hwdep entry */ +static void stac_store_hints(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + int val; + + if (get_int_hint(codec, "gpio_mask", &spec->gpio_mask)) { + spec->eapd_mask = spec->gpio_dir = spec->gpio_data = + spec->gpio_mask; + } + if (get_int_hint(codec, "gpio_dir", &spec->gpio_dir)) + spec->gpio_dir &= spec->gpio_mask; + if (get_int_hint(codec, "gpio_data", &spec->gpio_data)) + spec->gpio_data &= spec->gpio_mask; + if (get_int_hint(codec, "eapd_mask", &spec->eapd_mask)) + spec->eapd_mask &= spec->gpio_mask; + if (get_int_hint(codec, "gpio_mute", &spec->gpio_mute)) + spec->gpio_mute &= spec->gpio_mask; + val = snd_hda_get_bool_hint(codec, "eapd_switch"); + if (val >= 0) + spec->eapd_switch = val; +} + +/* + * loopback controls + */ + +#define stac_aloopback_info snd_ctl_boolean_mono_info + +static int stac_aloopback_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + struct sigmatel_spec *spec = codec->spec; + + ucontrol->value.integer.value[0] = !!(spec->aloopback & + (spec->aloopback_mask << idx)); + return 0; +} + +static int stac_aloopback_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + unsigned int dac_mode; + unsigned int val, idx_val; + + idx_val = spec->aloopback_mask << idx; + if (ucontrol->value.integer.value[0]) + val = spec->aloopback | idx_val; + else + val = spec->aloopback & ~idx_val; + if (spec->aloopback == val) + return 0; + + spec->aloopback = val; + + /* Only return the bits defined by the shift value of the + * first two bytes of the mask + */ + dac_mode = snd_hda_codec_read(codec, codec->core.afg, 0, + kcontrol->private_value & 0xFFFF, 0x0); + dac_mode >>= spec->aloopback_shift; + + if (spec->aloopback & idx_val) { + snd_hda_power_up(codec); + dac_mode |= idx_val; + } else { + snd_hda_power_down(codec); + dac_mode &= ~idx_val; + } + + snd_hda_codec_write_cache(codec, codec->core.afg, 0, + kcontrol->private_value >> 16, dac_mode); + + return 1; +} + +#define STAC_ANALOG_LOOPBACK(verb_read, verb_write, cnt) \ + { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = "Analog Loopback", \ + .count = cnt, \ + .info = stac_aloopback_info, \ + .get = stac_aloopback_get, \ + .put = stac_aloopback_put, \ + .private_value = verb_read | (verb_write << 16), \ + } + +/* + * Mute LED handling on HP laptops + */ + +/* check whether it's a HP laptop with a docking port */ +static bool hp_bnb2011_with_dock(struct hda_codec *codec) +{ + if (codec->core.vendor_id != 0x111d7605 && + codec->core.vendor_id != 0x111d76d1) + return false; + + switch (codec->core.subsystem_id) { + case 0x103c1618: + case 0x103c1619: + case 0x103c161a: + case 0x103c161b: + case 0x103c161c: + case 0x103c161d: + case 0x103c161e: + case 0x103c161f: + + case 0x103c162a: + case 0x103c162b: + + case 0x103c1630: + case 0x103c1631: + + case 0x103c1633: + case 0x103c1634: + case 0x103c1635: + + case 0x103c3587: + case 0x103c3588: + case 0x103c3589: + case 0x103c358a: + + case 0x103c3667: + case 0x103c3668: + case 0x103c3669: + + return true; + } + return false; +} + +static bool hp_blike_system(u32 subsystem_id) +{ + switch (subsystem_id) { + case 0x103c1473: /* HP ProBook 6550b */ + case 0x103c1520: + case 0x103c1521: + case 0x103c1523: + case 0x103c1524: + case 0x103c1525: + case 0x103c1722: + case 0x103c1723: + case 0x103c1724: + case 0x103c1725: + case 0x103c1726: + case 0x103c1727: + case 0x103c1728: + case 0x103c1729: + case 0x103c172a: + case 0x103c172b: + case 0x103c307e: + case 0x103c307f: + case 0x103c3080: + case 0x103c3081: + case 0x103c7007: + case 0x103c7008: + return true; + } + return false; +} + +static void set_hp_led_gpio(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + unsigned int gpio; + + if (spec->gpio_led) + return; + + gpio = snd_hda_param_read(codec, codec->core.afg, AC_PAR_GPIO_CAP); + gpio &= AC_GPIO_IO_COUNT; + if (gpio > 3) + spec->gpio_led = 0x08; /* GPIO 3 */ + else + spec->gpio_led = 0x01; /* GPIO 0 */ +} + +/* + * This method searches for the mute LED GPIO configuration + * provided as OEM string in SMBIOS. The format of that string + * is HP_Mute_LED_P_G or HP_Mute_LED_P + * where P can be 0 or 1 and defines mute LED GPIO control state (low/high) + * that corresponds to the NOT muted state of the master volume + * and G is the index of the GPIO to use as the mute LED control (0..9) + * If _G portion is missing it is assigned based on the codec ID + * + * So, HP B-series like systems may have HP_Mute_LED_0 (current models) + * or HP_Mute_LED_0_3 (future models) OEM SMBIOS strings + * + * + * The dv-series laptops don't seem to have the HP_Mute_LED* strings in + * SMBIOS - at least the ones I have seen do not have them - which include + * my own system (HP Pavilion dv6-1110ax) and my cousin's + * HP Pavilion dv9500t CTO. + * Need more information on whether it is true across the entire series. + * -- kunal + */ +static int find_mute_led_cfg(struct hda_codec *codec, int default_polarity) +{ + struct sigmatel_spec *spec = codec->spec; + const struct dmi_device *dev = NULL; + + if (get_int_hint(codec, "gpio_led", &spec->gpio_led)) { + get_int_hint(codec, "gpio_led_polarity", + &spec->gpio_led_polarity); + return 1; + } + + while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) { + if (sscanf(dev->name, "HP_Mute_LED_%u_%x", + &spec->gpio_led_polarity, + &spec->gpio_led) == 2) { + unsigned int max_gpio; + max_gpio = snd_hda_param_read(codec, codec->core.afg, + AC_PAR_GPIO_CAP); + max_gpio &= AC_GPIO_IO_COUNT; + if (spec->gpio_led < max_gpio) + spec->gpio_led = 1 << spec->gpio_led; + else + spec->vref_mute_led_nid = spec->gpio_led; + return 1; + } + if (sscanf(dev->name, "HP_Mute_LED_%u", + &spec->gpio_led_polarity) == 1) { + set_hp_led_gpio(codec); + return 1; + } + /* BIOS bug: unfilled OEM string */ + if (strstr(dev->name, "HP_Mute_LED_P_G")) { + set_hp_led_gpio(codec); + if (default_polarity >= 0) + spec->gpio_led_polarity = default_polarity; + else + spec->gpio_led_polarity = 1; + return 1; + } + } + + /* + * Fallback case - if we don't find the DMI strings, + * we statically set the GPIO - if not a B-series system + * and default polarity is provided + */ + if (!hp_blike_system(codec->core.subsystem_id) && + (default_polarity == 0 || default_polarity == 1)) { + set_hp_led_gpio(codec); + spec->gpio_led_polarity = default_polarity; + return 1; + } + return 0; +} + +/* check whether a built-in speaker is included in parsed pins */ +static bool has_builtin_speaker(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + const hda_nid_t *nid_pin; + int nids, i; + + if (spec->gen.autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT) { + nid_pin = spec->gen.autocfg.line_out_pins; + nids = spec->gen.autocfg.line_outs; + } else { + nid_pin = spec->gen.autocfg.speaker_pins; + nids = spec->gen.autocfg.speaker_outs; + } + + for (i = 0; i < nids; i++) { + unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid_pin[i]); + if (snd_hda_get_input_pin_attr(def_conf) == INPUT_PIN_ATTR_INT) + return true; + } + return false; +} + +/* + * PC beep controls + */ + +/* create PC beep volume controls */ +static int stac_auto_create_beep_ctls(struct hda_codec *codec, + hda_nid_t nid) +{ + struct sigmatel_spec *spec = codec->spec; + u32 caps = query_amp_caps(codec, nid, HDA_OUTPUT); + struct snd_kcontrol_new *knew; + static const struct snd_kcontrol_new abeep_mute_ctl = + HDA_CODEC_MUTE(NULL, 0, 0, 0); + static const struct snd_kcontrol_new dbeep_mute_ctl = + HDA_CODEC_MUTE_BEEP(NULL, 0, 0, 0); + static const struct snd_kcontrol_new beep_vol_ctl = + HDA_CODEC_VOLUME(NULL, 0, 0, 0); + + /* check for mute support for the amp */ + if ((caps & AC_AMPCAP_MUTE) >> AC_AMPCAP_MUTE_SHIFT) { + const struct snd_kcontrol_new *temp; + if (spec->anabeep_nid == nid) + temp = &abeep_mute_ctl; + else + temp = &dbeep_mute_ctl; + knew = snd_hda_gen_add_kctl(&spec->gen, + "Beep Playback Switch", temp); + if (!knew) + return -ENOMEM; + knew->private_value = + HDA_COMPOSE_AMP_VAL(nid, 1, 0, HDA_OUTPUT); + } + + /* check to see if there is volume support for the amp */ + if ((caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT) { + knew = snd_hda_gen_add_kctl(&spec->gen, + "Beep Playback Volume", + &beep_vol_ctl); + if (!knew) + return -ENOMEM; + knew->private_value = + HDA_COMPOSE_AMP_VAL(nid, 1, 0, HDA_OUTPUT); + } + return 0; +} + +#ifdef CONFIG_SND_HDA_INPUT_BEEP +#define stac_dig_beep_switch_info snd_ctl_boolean_mono_info + +static int stac_dig_beep_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = codec->beep->enabled; + return 0; +} + +static int stac_dig_beep_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + return snd_hda_enable_beep_device(codec, ucontrol->value.integer.value[0]); +} + +static const struct snd_kcontrol_new stac_dig_beep_ctrl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Beep Playback Switch", + .info = stac_dig_beep_switch_info, + .get = stac_dig_beep_switch_get, + .put = stac_dig_beep_switch_put, +}; + +static int stac_beep_switch_ctl(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + + if (!snd_hda_gen_add_kctl(&spec->gen, NULL, &stac_dig_beep_ctrl)) + return -ENOMEM; + return 0; +} +#endif + +/* + * SPDIF-out mux controls + */ + +static int stac_smux_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + return snd_hda_input_mux_info(&spec->spdif_mux, uinfo); +} + +static int stac_smux_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + unsigned int smux_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + + ucontrol->value.enumerated.item[0] = spec->cur_smux[smux_idx]; + return 0; +} + +static int stac_smux_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + unsigned int smux_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + + return snd_hda_input_mux_put(codec, &spec->spdif_mux, ucontrol, + spec->gen.autocfg.dig_out_pins[smux_idx], + &spec->cur_smux[smux_idx]); +} + +static const struct snd_kcontrol_new stac_smux_mixer = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "IEC958 Playback Source", + /* count set later */ + .info = stac_smux_enum_info, + .get = stac_smux_enum_get, + .put = stac_smux_enum_put, +}; + +static const char * const stac_spdif_labels[] = { + "Digital Playback", "Analog Mux 1", "Analog Mux 2", NULL +}; + +static int stac_create_spdif_mux_ctls(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->gen.autocfg; + const char * const *labels = spec->spdif_labels; + struct snd_kcontrol_new *kctl; + int i, num_cons; + + if (cfg->dig_outs < 1) + return 0; + + num_cons = snd_hda_get_num_conns(codec, cfg->dig_out_pins[0]); + if (num_cons <= 1) + return 0; + + if (!labels) + labels = stac_spdif_labels; + for (i = 0; i < num_cons; i++) { + if (snd_BUG_ON(!labels[i])) + return -EINVAL; + snd_hda_add_imux_item(codec, &spec->spdif_mux, labels[i], i, NULL); + } + + kctl = snd_hda_gen_add_kctl(&spec->gen, NULL, &stac_smux_mixer); + if (!kctl) + return -ENOMEM; + kctl->count = cfg->dig_outs; + + return 0; +} + +static const struct hda_verb stac9200_eapd_init[] = { + /* set dac0mux for dac converter */ + {0x07, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x08, AC_VERB_SET_EAPD_BTLENABLE, 0x02}, + {} +}; + +static const struct hda_verb dell_eq_core_init[] = { + /* set master volume to max value without distortion + * and direct control */ + { 0x1f, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xec}, + {} +}; + +static const struct hda_verb stac92hd73xx_core_init[] = { + /* set master volume and direct control */ + { 0x1f, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff}, + {} +}; + +static const struct hda_verb stac92hd83xxx_core_init[] = { + /* power state controls amps */ + { 0x01, AC_VERB_SET_EAPD, 1 << 2}, + {} +}; + +static const struct hda_verb stac92hd83xxx_hp_zephyr_init[] = { + { 0x22, 0x785, 0x43 }, + { 0x22, 0x782, 0xe0 }, + { 0x22, 0x795, 0x00 }, + {} +}; + +static const struct hda_verb stac92hd71bxx_core_init[] = { + /* set master volume and direct control */ + { 0x28, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff}, + {} +}; + +static const hda_nid_t stac92hd71bxx_unmute_nids[] = { + /* unmute right and left channels for nodes 0x0f, 0xa, 0x0d */ + 0x0f, 0x0a, 0x0d, 0 +}; + +static const struct hda_verb stac925x_core_init[] = { + /* set dac0mux for dac converter */ + { 0x06, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* mute the master volume */ + { 0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + {} +}; + +static const struct hda_verb stac922x_core_init[] = { + /* set master volume and direct control */ + { 0x16, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff}, + {} +}; + +static const struct hda_verb d965_core_init[] = { + /* unmute node 0x1b */ + { 0x1b, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000}, + /* select node 0x03 as DAC */ + { 0x0b, AC_VERB_SET_CONNECT_SEL, 0x01}, + {} +}; + +static const struct hda_verb dell_3st_core_init[] = { + /* don't set delta bit */ + {0x24, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0x7f}, + /* unmute node 0x1b */ + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000}, + /* select node 0x03 as DAC */ + {0x0b, AC_VERB_SET_CONNECT_SEL, 0x01}, + {} +}; + +static const struct hda_verb stac927x_core_init[] = { + /* set master volume and direct control */ + { 0x24, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff}, + /* enable analog pc beep path */ + { 0x01, AC_VERB_SET_DIGI_CONVERT_2, 1 << 5}, + {} +}; + +static const struct hda_verb stac927x_volknob_core_init[] = { + /* don't set delta bit */ + {0x24, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0x7f}, + /* enable analog pc beep path */ + {0x01, AC_VERB_SET_DIGI_CONVERT_2, 1 << 5}, + {} +}; + +static const struct hda_verb stac9205_core_init[] = { + /* set master volume and direct control */ + { 0x24, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff}, + /* enable analog pc beep path */ + { 0x01, AC_VERB_SET_DIGI_CONVERT_2, 1 << 5}, + {} +}; + +static const struct snd_kcontrol_new stac92hd73xx_6ch_loopback = + STAC_ANALOG_LOOPBACK(0xFA0, 0x7A1, 3); + +static const struct snd_kcontrol_new stac92hd73xx_8ch_loopback = + STAC_ANALOG_LOOPBACK(0xFA0, 0x7A1, 4); + +static const struct snd_kcontrol_new stac92hd73xx_10ch_loopback = + STAC_ANALOG_LOOPBACK(0xFA0, 0x7A1, 5); + +static const struct snd_kcontrol_new stac92hd71bxx_loopback = + STAC_ANALOG_LOOPBACK(0xFA0, 0x7A0, 2); + +static const struct snd_kcontrol_new stac9205_loopback = + STAC_ANALOG_LOOPBACK(0xFE0, 0x7E0, 1); + +static const struct snd_kcontrol_new stac927x_loopback = + STAC_ANALOG_LOOPBACK(0xFEB, 0x7EB, 1); + +static const struct hda_pintbl ref9200_pin_configs[] = { + { 0x08, 0x01c47010 }, + { 0x09, 0x01447010 }, + { 0x0d, 0x0221401f }, + { 0x0e, 0x01114010 }, + { 0x0f, 0x02a19020 }, + { 0x10, 0x01a19021 }, + { 0x11, 0x90100140 }, + { 0x12, 0x01813122 }, + {} +}; + +static const struct hda_pintbl gateway9200_m4_pin_configs[] = { + { 0x08, 0x400000fe }, + { 0x09, 0x404500f4 }, + { 0x0d, 0x400100f0 }, + { 0x0e, 0x90110010 }, + { 0x0f, 0x400100f1 }, + { 0x10, 0x02a1902e }, + { 0x11, 0x500000f2 }, + { 0x12, 0x500000f3 }, + {} +}; + +static const struct hda_pintbl gateway9200_m4_2_pin_configs[] = { + { 0x08, 0x400000fe }, + { 0x09, 0x404500f4 }, + { 0x0d, 0x400100f0 }, + { 0x0e, 0x90110010 }, + { 0x0f, 0x400100f1 }, + { 0x10, 0x02a1902e }, + { 0x11, 0x500000f2 }, + { 0x12, 0x500000f3 }, + {} +}; + +/* + STAC 9200 pin configs for + 102801A8 + 102801DE + 102801E8 +*/ +static const struct hda_pintbl dell9200_d21_pin_configs[] = { + { 0x08, 0x400001f0 }, + { 0x09, 0x400001f1 }, + { 0x0d, 0x02214030 }, + { 0x0e, 0x01014010 }, + { 0x0f, 0x02a19020 }, + { 0x10, 0x01a19021 }, + { 0x11, 0x90100140 }, + { 0x12, 0x01813122 }, + {} +}; + +/* + STAC 9200 pin configs for + 102801C0 + 102801C1 +*/ +static const struct hda_pintbl dell9200_d22_pin_configs[] = { + { 0x08, 0x400001f0 }, + { 0x09, 0x400001f1 }, + { 0x0d, 0x0221401f }, + { 0x0e, 0x01014010 }, + { 0x0f, 0x01813020 }, + { 0x10, 0x02a19021 }, + { 0x11, 0x90100140 }, + { 0x12, 0x400001f2 }, + {} +}; + +/* + STAC 9200 pin configs for + 102801C4 (Dell Dimension E310) + 102801C5 + 102801C7 + 102801D9 + 102801DA + 102801E3 +*/ +static const struct hda_pintbl dell9200_d23_pin_configs[] = { + { 0x08, 0x400001f0 }, + { 0x09, 0x400001f1 }, + { 0x0d, 0x0221401f }, + { 0x0e, 0x01014010 }, + { 0x0f, 0x01813020 }, + { 0x10, 0x01a19021 }, + { 0x11, 0x90100140 }, + { 0x12, 0x400001f2 }, + {} +}; + + +/* + STAC 9200-32 pin configs for + 102801B5 (Dell Inspiron 630m) + 102801D8 (Dell Inspiron 640m) +*/ +static const struct hda_pintbl dell9200_m21_pin_configs[] = { + { 0x08, 0x40c003fa }, + { 0x09, 0x03441340 }, + { 0x0d, 0x0321121f }, + { 0x0e, 0x90170310 }, + { 0x0f, 0x408003fb }, + { 0x10, 0x03a11020 }, + { 0x11, 0x401003fc }, + { 0x12, 0x403003fd }, + {} +}; + +/* + STAC 9200-32 pin configs for + 102801C2 (Dell Latitude D620) + 102801C8 + 102801CC (Dell Latitude D820) + 102801D4 + 102801D6 +*/ +static const struct hda_pintbl dell9200_m22_pin_configs[] = { + { 0x08, 0x40c003fa }, + { 0x09, 0x0144131f }, + { 0x0d, 0x0321121f }, + { 0x0e, 0x90170310 }, + { 0x0f, 0x90a70321 }, + { 0x10, 0x03a11020 }, + { 0x11, 0x401003fb }, + { 0x12, 0x40f000fc }, + {} +}; + +/* + STAC 9200-32 pin configs for + 102801CE (Dell XPS M1710) + 102801CF (Dell Precision M90) +*/ +static const struct hda_pintbl dell9200_m23_pin_configs[] = { + { 0x08, 0x40c003fa }, + { 0x09, 0x01441340 }, + { 0x0d, 0x0421421f }, + { 0x0e, 0x90170310 }, + { 0x0f, 0x408003fb }, + { 0x10, 0x04a1102e }, + { 0x11, 0x90170311 }, + { 0x12, 0x403003fc }, + {} +}; + +/* + STAC 9200-32 pin configs for + 102801C9 + 102801CA + 102801CB (Dell Latitude 120L) + 102801D3 +*/ +static const struct hda_pintbl dell9200_m24_pin_configs[] = { + { 0x08, 0x40c003fa }, + { 0x09, 0x404003fb }, + { 0x0d, 0x0321121f }, + { 0x0e, 0x90170310 }, + { 0x0f, 0x408003fc }, + { 0x10, 0x03a11020 }, + { 0x11, 0x401003fd }, + { 0x12, 0x403003fe }, + {} +}; + +/* + STAC 9200-32 pin configs for + 102801BD (Dell Inspiron E1505n) + 102801EE + 102801EF +*/ +static const struct hda_pintbl dell9200_m25_pin_configs[] = { + { 0x08, 0x40c003fa }, + { 0x09, 0x01441340 }, + { 0x0d, 0x0421121f }, + { 0x0e, 0x90170310 }, + { 0x0f, 0x408003fb }, + { 0x10, 0x04a11020 }, + { 0x11, 0x401003fc }, + { 0x12, 0x403003fd }, + {} +}; + +/* + STAC 9200-32 pin configs for + 102801F5 (Dell Inspiron 1501) + 102801F6 +*/ +static const struct hda_pintbl dell9200_m26_pin_configs[] = { + { 0x08, 0x40c003fa }, + { 0x09, 0x404003fb }, + { 0x0d, 0x0421121f }, + { 0x0e, 0x90170310 }, + { 0x0f, 0x408003fc }, + { 0x10, 0x04a11020 }, + { 0x11, 0x401003fd }, + { 0x12, 0x403003fe }, + {} +}; + +/* + STAC 9200-32 + 102801CD (Dell Inspiron E1705/9400) +*/ +static const struct hda_pintbl dell9200_m27_pin_configs[] = { + { 0x08, 0x40c003fa }, + { 0x09, 0x01441340 }, + { 0x0d, 0x0421121f }, + { 0x0e, 0x90170310 }, + { 0x0f, 0x90170310 }, + { 0x10, 0x04a11020 }, + { 0x11, 0x90170310 }, + { 0x12, 0x40f003fc }, + {} +}; + +static const struct hda_pintbl oqo9200_pin_configs[] = { + { 0x08, 0x40c000f0 }, + { 0x09, 0x404000f1 }, + { 0x0d, 0x0221121f }, + { 0x0e, 0x02211210 }, + { 0x0f, 0x90170111 }, + { 0x10, 0x90a70120 }, + { 0x11, 0x400000f2 }, + { 0x12, 0x400000f3 }, + {} +}; + +/* + * STAC 92HD700 + * 18881000 Amigaone X1000 + */ +static const struct hda_pintbl nemo_pin_configs[] = { + { 0x0a, 0x02214020 }, /* Front panel HP socket */ + { 0x0b, 0x02a19080 }, /* Front Mic */ + { 0x0c, 0x0181304e }, /* Line in */ + { 0x0d, 0x01014010 }, /* Line out */ + { 0x0e, 0x01a19040 }, /* Rear Mic */ + { 0x0f, 0x01011012 }, /* Rear speakers */ + { 0x10, 0x01016011 }, /* Center speaker */ + { 0x11, 0x01012014 }, /* Side speakers (7.1) */ + { 0x12, 0x103301f0 }, /* Motherboard CD line in connector */ + { 0x13, 0x411111f0 }, /* Unused */ + { 0x14, 0x411111f0 }, /* Unused */ + { 0x21, 0x01442170 }, /* S/PDIF line out */ + { 0x22, 0x411111f0 }, /* Unused */ + { 0x23, 0x411111f0 }, /* Unused */ + {} +}; + +static void stac9200_fixup_panasonic(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->gpio_mask = spec->gpio_dir = 0x09; + spec->gpio_data = 0x00; + /* CF-74 has no headphone detection, and the driver should *NOT* + * do detection and HP/speaker toggle because the hardware does it. + */ + spec->gen.suppress_auto_mute = 1; + } +} + + +static const struct hda_fixup stac9200_fixups[] = { + [STAC_REF] = { + .type = HDA_FIXUP_PINS, + .v.pins = ref9200_pin_configs, + }, + [STAC_9200_OQO] = { + .type = HDA_FIXUP_PINS, + .v.pins = oqo9200_pin_configs, + .chained = true, + .chain_id = STAC_9200_EAPD_INIT, + }, + [STAC_9200_DELL_D21] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell9200_d21_pin_configs, + }, + [STAC_9200_DELL_D22] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell9200_d22_pin_configs, + }, + [STAC_9200_DELL_D23] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell9200_d23_pin_configs, + }, + [STAC_9200_DELL_M21] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell9200_m21_pin_configs, + }, + [STAC_9200_DELL_M22] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell9200_m22_pin_configs, + }, + [STAC_9200_DELL_M23] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell9200_m23_pin_configs, + }, + [STAC_9200_DELL_M24] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell9200_m24_pin_configs, + }, + [STAC_9200_DELL_M25] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell9200_m25_pin_configs, + }, + [STAC_9200_DELL_M26] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell9200_m26_pin_configs, + }, + [STAC_9200_DELL_M27] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell9200_m27_pin_configs, + }, + [STAC_9200_M4] = { + .type = HDA_FIXUP_PINS, + .v.pins = gateway9200_m4_pin_configs, + .chained = true, + .chain_id = STAC_9200_EAPD_INIT, + }, + [STAC_9200_M4_2] = { + .type = HDA_FIXUP_PINS, + .v.pins = gateway9200_m4_2_pin_configs, + .chained = true, + .chain_id = STAC_9200_EAPD_INIT, + }, + [STAC_9200_PANASONIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac9200_fixup_panasonic, + }, + [STAC_9200_EAPD_INIT] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + {0x08, AC_VERB_SET_EAPD_BTLENABLE, 0x02}, + {} + }, + }, +}; + +static const struct hda_model_fixup stac9200_models[] = { + { .id = STAC_REF, .name = "ref" }, + { .id = STAC_9200_OQO, .name = "oqo" }, + { .id = STAC_9200_DELL_D21, .name = "dell-d21" }, + { .id = STAC_9200_DELL_D22, .name = "dell-d22" }, + { .id = STAC_9200_DELL_D23, .name = "dell-d23" }, + { .id = STAC_9200_DELL_M21, .name = "dell-m21" }, + { .id = STAC_9200_DELL_M22, .name = "dell-m22" }, + { .id = STAC_9200_DELL_M23, .name = "dell-m23" }, + { .id = STAC_9200_DELL_M24, .name = "dell-m24" }, + { .id = STAC_9200_DELL_M25, .name = "dell-m25" }, + { .id = STAC_9200_DELL_M26, .name = "dell-m26" }, + { .id = STAC_9200_DELL_M27, .name = "dell-m27" }, + { .id = STAC_9200_M4, .name = "gateway-m4" }, + { .id = STAC_9200_M4_2, .name = "gateway-m4-2" }, + { .id = STAC_9200_PANASONIC, .name = "panasonic" }, + {} +}; + +static const struct snd_pci_quirk stac9200_fixup_tbl[] = { + /* SigmaTel reference board */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, + "DFI LanParty", STAC_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101, + "DFI LanParty", STAC_REF), + /* Dell laptops have BIOS problem */ + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01a8, + "unknown Dell", STAC_9200_DELL_D21), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01b5, + "Dell Inspiron 630m", STAC_9200_DELL_M21), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01bd, + "Dell Inspiron E1505n", STAC_9200_DELL_M25), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c0, + "unknown Dell", STAC_9200_DELL_D22), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c1, + "unknown Dell", STAC_9200_DELL_D22), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c2, + "Dell Latitude D620", STAC_9200_DELL_M22), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c5, + "unknown Dell", STAC_9200_DELL_D23), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c7, + "unknown Dell", STAC_9200_DELL_D23), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c8, + "unknown Dell", STAC_9200_DELL_M22), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c9, + "unknown Dell", STAC_9200_DELL_M24), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ca, + "unknown Dell", STAC_9200_DELL_M24), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cb, + "Dell Latitude 120L", STAC_9200_DELL_M24), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cc, + "Dell Latitude D820", STAC_9200_DELL_M22), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cd, + "Dell Inspiron E1705/9400", STAC_9200_DELL_M27), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ce, + "Dell XPS M1710", STAC_9200_DELL_M23), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cf, + "Dell Precision M90", STAC_9200_DELL_M23), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d3, + "unknown Dell", STAC_9200_DELL_M22), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d4, + "unknown Dell", STAC_9200_DELL_M22), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d6, + "unknown Dell", STAC_9200_DELL_M22), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d8, + "Dell Inspiron 640m", STAC_9200_DELL_M21), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d9, + "unknown Dell", STAC_9200_DELL_D23), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01da, + "unknown Dell", STAC_9200_DELL_D23), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01de, + "unknown Dell", STAC_9200_DELL_D21), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01e3, + "unknown Dell", STAC_9200_DELL_D23), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01e8, + "unknown Dell", STAC_9200_DELL_D21), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ee, + "unknown Dell", STAC_9200_DELL_M25), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ef, + "unknown Dell", STAC_9200_DELL_M25), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f5, + "Dell Inspiron 1501", STAC_9200_DELL_M26), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f6, + "unknown Dell", STAC_9200_DELL_M26), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0201, + "Dell Latitude D430", STAC_9200_DELL_M22), + /* Panasonic */ + SND_PCI_QUIRK(0x10f7, 0x8338, "Panasonic CF-74", STAC_9200_PANASONIC), + /* Gateway machines needs EAPD to be set on resume */ + SND_PCI_QUIRK(0x107b, 0x0205, "Gateway S-7110M", STAC_9200_M4), + SND_PCI_QUIRK(0x107b, 0x0317, "Gateway MT3423, MX341*", STAC_9200_M4_2), + SND_PCI_QUIRK(0x107b, 0x0318, "Gateway ML3019, MT3707", STAC_9200_M4_2), + /* OQO Mobile */ + SND_PCI_QUIRK(0x1106, 0x3288, "OQO Model 2", STAC_9200_OQO), + {} /* terminator */ +}; + +static const struct hda_pintbl ref925x_pin_configs[] = { + { 0x07, 0x40c003f0 }, + { 0x08, 0x424503f2 }, + { 0x0a, 0x01813022 }, + { 0x0b, 0x02a19021 }, + { 0x0c, 0x90a70320 }, + { 0x0d, 0x02214210 }, + { 0x10, 0x01019020 }, + { 0x11, 0x9033032e }, + {} +}; + +static const struct hda_pintbl stac925xM1_pin_configs[] = { + { 0x07, 0x40c003f4 }, + { 0x08, 0x424503f2 }, + { 0x0a, 0x400000f3 }, + { 0x0b, 0x02a19020 }, + { 0x0c, 0x40a000f0 }, + { 0x0d, 0x90100210 }, + { 0x10, 0x400003f1 }, + { 0x11, 0x9033032e }, + {} +}; + +static const struct hda_pintbl stac925xM1_2_pin_configs[] = { + { 0x07, 0x40c003f4 }, + { 0x08, 0x424503f2 }, + { 0x0a, 0x400000f3 }, + { 0x0b, 0x02a19020 }, + { 0x0c, 0x40a000f0 }, + { 0x0d, 0x90100210 }, + { 0x10, 0x400003f1 }, + { 0x11, 0x9033032e }, + {} +}; + +static const struct hda_pintbl stac925xM2_pin_configs[] = { + { 0x07, 0x40c003f4 }, + { 0x08, 0x424503f2 }, + { 0x0a, 0x400000f3 }, + { 0x0b, 0x02a19020 }, + { 0x0c, 0x40a000f0 }, + { 0x0d, 0x90100210 }, + { 0x10, 0x400003f1 }, + { 0x11, 0x9033032e }, + {} +}; + +static const struct hda_pintbl stac925xM2_2_pin_configs[] = { + { 0x07, 0x40c003f4 }, + { 0x08, 0x424503f2 }, + { 0x0a, 0x400000f3 }, + { 0x0b, 0x02a19020 }, + { 0x0c, 0x40a000f0 }, + { 0x0d, 0x90100210 }, + { 0x10, 0x400003f1 }, + { 0x11, 0x9033032e }, + {} +}; + +static const struct hda_pintbl stac925xM3_pin_configs[] = { + { 0x07, 0x40c003f4 }, + { 0x08, 0x424503f2 }, + { 0x0a, 0x400000f3 }, + { 0x0b, 0x02a19020 }, + { 0x0c, 0x40a000f0 }, + { 0x0d, 0x90100210 }, + { 0x10, 0x400003f1 }, + { 0x11, 0x503303f3 }, + {} +}; + +static const struct hda_pintbl stac925xM5_pin_configs[] = { + { 0x07, 0x40c003f4 }, + { 0x08, 0x424503f2 }, + { 0x0a, 0x400000f3 }, + { 0x0b, 0x02a19020 }, + { 0x0c, 0x40a000f0 }, + { 0x0d, 0x90100210 }, + { 0x10, 0x400003f1 }, + { 0x11, 0x9033032e }, + {} +}; + +static const struct hda_pintbl stac925xM6_pin_configs[] = { + { 0x07, 0x40c003f4 }, + { 0x08, 0x424503f2 }, + { 0x0a, 0x400000f3 }, + { 0x0b, 0x02a19020 }, + { 0x0c, 0x40a000f0 }, + { 0x0d, 0x90100210 }, + { 0x10, 0x400003f1 }, + { 0x11, 0x90330320 }, + {} +}; + +static const struct hda_fixup stac925x_fixups[] = { + [STAC_REF] = { + .type = HDA_FIXUP_PINS, + .v.pins = ref925x_pin_configs, + }, + [STAC_M1] = { + .type = HDA_FIXUP_PINS, + .v.pins = stac925xM1_pin_configs, + }, + [STAC_M1_2] = { + .type = HDA_FIXUP_PINS, + .v.pins = stac925xM1_2_pin_configs, + }, + [STAC_M2] = { + .type = HDA_FIXUP_PINS, + .v.pins = stac925xM2_pin_configs, + }, + [STAC_M2_2] = { + .type = HDA_FIXUP_PINS, + .v.pins = stac925xM2_2_pin_configs, + }, + [STAC_M3] = { + .type = HDA_FIXUP_PINS, + .v.pins = stac925xM3_pin_configs, + }, + [STAC_M5] = { + .type = HDA_FIXUP_PINS, + .v.pins = stac925xM5_pin_configs, + }, + [STAC_M6] = { + .type = HDA_FIXUP_PINS, + .v.pins = stac925xM6_pin_configs, + }, +}; + +static const struct hda_model_fixup stac925x_models[] = { + { .id = STAC_REF, .name = "ref" }, + { .id = STAC_M1, .name = "m1" }, + { .id = STAC_M1_2, .name = "m1-2" }, + { .id = STAC_M2, .name = "m2" }, + { .id = STAC_M2_2, .name = "m2-2" }, + { .id = STAC_M3, .name = "m3" }, + { .id = STAC_M5, .name = "m5" }, + { .id = STAC_M6, .name = "m6" }, + {} +}; + +static const struct snd_pci_quirk stac925x_fixup_tbl[] = { + /* SigmaTel reference board */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, "DFI LanParty", STAC_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101, "DFI LanParty", STAC_REF), + SND_PCI_QUIRK(0x8384, 0x7632, "Stac9202 Reference Board", STAC_REF), + + /* Default table for unknown ID */ + SND_PCI_QUIRK(0x1002, 0x437b, "Gateway mobile", STAC_M2_2), + + /* gateway machines are checked via codec ssid */ + SND_PCI_QUIRK(0x107b, 0x0316, "Gateway M255", STAC_M2), + SND_PCI_QUIRK(0x107b, 0x0366, "Gateway MP6954", STAC_M5), + SND_PCI_QUIRK(0x107b, 0x0461, "Gateway NX560XL", STAC_M1), + SND_PCI_QUIRK(0x107b, 0x0681, "Gateway NX860", STAC_M2), + SND_PCI_QUIRK(0x107b, 0x0367, "Gateway MX6453", STAC_M1_2), + /* Not sure about the brand name for those */ + SND_PCI_QUIRK(0x107b, 0x0281, "Gateway mobile", STAC_M1), + SND_PCI_QUIRK(0x107b, 0x0507, "Gateway mobile", STAC_M3), + SND_PCI_QUIRK(0x107b, 0x0281, "Gateway mobile", STAC_M6), + SND_PCI_QUIRK(0x107b, 0x0685, "Gateway mobile", STAC_M2_2), + {} /* terminator */ +}; + +static const struct hda_pintbl ref92hd73xx_pin_configs[] = { + // Port A-H + { 0x0a, 0x02214030 }, + { 0x0b, 0x02a19040 }, + { 0x0c, 0x01a19020 }, + { 0x0d, 0x02214030 }, + { 0x0e, 0x0181302e }, + { 0x0f, 0x01014010 }, + { 0x10, 0x01014020 }, + { 0x11, 0x01014030 }, + // CD in + { 0x12, 0x02319040 }, + // Digial Mic ins + { 0x13, 0x90a000f0 }, + { 0x14, 0x90a000f0 }, + // Digital outs + { 0x22, 0x01452050 }, + { 0x23, 0x01452050 }, + {} +}; + +static const struct hda_pintbl dell_m6_pin_configs[] = { + { 0x0a, 0x0321101f }, + { 0x0b, 0x4f00000f }, + { 0x0c, 0x4f0000f0 }, + { 0x0d, 0x90170110 }, + { 0x0e, 0x03a11020 }, + { 0x0f, 0x0321101f }, + { 0x10, 0x4f0000f0 }, + { 0x11, 0x4f0000f0 }, + { 0x12, 0x4f0000f0 }, + { 0x13, 0x90a60160 }, + { 0x14, 0x4f0000f0 }, + { 0x22, 0x4f0000f0 }, + { 0x23, 0x4f0000f0 }, + {} +}; + +static const struct hda_pintbl alienware_m17x_pin_configs[] = { + { 0x0a, 0x0321101f }, + { 0x0b, 0x0321101f }, + { 0x0c, 0x03a11020 }, + { 0x0d, 0x03014020 }, + { 0x0e, 0x90170110 }, + { 0x0f, 0x4f0000f0 }, + { 0x10, 0x4f0000f0 }, + { 0x11, 0x4f0000f0 }, + { 0x12, 0x4f0000f0 }, + { 0x13, 0x90a60160 }, + { 0x14, 0x4f0000f0 }, + { 0x22, 0x4f0000f0 }, + { 0x23, 0x904601b0 }, + {} +}; + +static const struct hda_pintbl intel_dg45id_pin_configs[] = { + // Analog outputs + { 0x0a, 0x02214230 }, + { 0x0b, 0x02A19240 }, + { 0x0c, 0x01013214 }, + { 0x0d, 0x01014210 }, + { 0x0e, 0x01A19250 }, + { 0x0f, 0x01011212 }, + { 0x10, 0x01016211 }, + // Digital output + { 0x22, 0x01451380 }, + { 0x23, 0x40f000f0 }, + {} +}; + +static const struct hda_pintbl stac92hd89xx_hp_front_jack_pin_configs[] = { + { 0x0a, 0x02214030 }, + { 0x0b, 0x02A19010 }, + {} +}; + +static const struct hda_pintbl stac92hd89xx_hp_z1_g2_right_mic_jack_pin_configs[] = { + { 0x0e, 0x400000f0 }, + {} +}; + +static void stac92hd73xx_fixup_ref(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + snd_hda_apply_pincfgs(codec, ref92hd73xx_pin_configs); + spec->gpio_mask = spec->gpio_dir = spec->gpio_data = 0; +} + +static void stac92hd73xx_fixup_dell(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + + snd_hda_apply_pincfgs(codec, dell_m6_pin_configs); + spec->eapd_switch = 0; +} + +static void stac92hd73xx_fixup_dell_eq(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + stac92hd73xx_fixup_dell(codec); + snd_hda_add_verbs(codec, dell_eq_core_init); + spec->volknob_init = 1; +} + +/* Analog Mics */ +static void stac92hd73xx_fixup_dell_m6_amic(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + stac92hd73xx_fixup_dell(codec); + snd_hda_codec_set_pincfg(codec, 0x0b, 0x90A70170); +} + +/* Digital Mics */ +static void stac92hd73xx_fixup_dell_m6_dmic(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + stac92hd73xx_fixup_dell(codec); + snd_hda_codec_set_pincfg(codec, 0x13, 0x90A60160); +} + +/* Both */ +static void stac92hd73xx_fixup_dell_m6_both(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + stac92hd73xx_fixup_dell(codec); + snd_hda_codec_set_pincfg(codec, 0x0b, 0x90A70170); + snd_hda_codec_set_pincfg(codec, 0x13, 0x90A60160); +} + +static void stac92hd73xx_fixup_alienware_m17x(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + snd_hda_apply_pincfgs(codec, alienware_m17x_pin_configs); + spec->eapd_switch = 0; +} + +static void stac92hd73xx_fixup_no_jd(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) + codec->no_jack_detect = 1; +} + + +static void stac92hd73xx_disable_automute(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + spec->gen.suppress_auto_mute = 1; +} + +static const struct hda_fixup stac92hd73xx_fixups[] = { + [STAC_92HD73XX_REF] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd73xx_fixup_ref, + }, + [STAC_DELL_M6_AMIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd73xx_fixup_dell_m6_amic, + }, + [STAC_DELL_M6_DMIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd73xx_fixup_dell_m6_dmic, + }, + [STAC_DELL_M6_BOTH] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd73xx_fixup_dell_m6_both, + }, + [STAC_DELL_EQ] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd73xx_fixup_dell_eq, + }, + [STAC_ALIENWARE_M17X] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd73xx_fixup_alienware_m17x, + }, + [STAC_ELO_VUPOINT_15MX] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd73xx_disable_automute, + }, + [STAC_92HD73XX_INTEL] = { + .type = HDA_FIXUP_PINS, + .v.pins = intel_dg45id_pin_configs, + }, + [STAC_92HD73XX_NO_JD] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd73xx_fixup_no_jd, + }, + [STAC_92HD89XX_HP_FRONT_JACK] = { + .type = HDA_FIXUP_PINS, + .v.pins = stac92hd89xx_hp_front_jack_pin_configs, + }, + [STAC_92HD89XX_HP_Z1_G2_RIGHT_MIC_JACK] = { + .type = HDA_FIXUP_PINS, + .v.pins = stac92hd89xx_hp_z1_g2_right_mic_jack_pin_configs, + }, + [STAC_92HD73XX_ASUS_MOBO] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + /* enable 5.1 and SPDIF out */ + { 0x0c, 0x01014411 }, + { 0x0d, 0x01014410 }, + { 0x0e, 0x01014412 }, + { 0x22, 0x014b1180 }, + { } + } + }, +}; + +static const struct hda_model_fixup stac92hd73xx_models[] = { + { .id = STAC_92HD73XX_NO_JD, .name = "no-jd" }, + { .id = STAC_92HD73XX_REF, .name = "ref" }, + { .id = STAC_92HD73XX_INTEL, .name = "intel" }, + { .id = STAC_DELL_M6_AMIC, .name = "dell-m6-amic" }, + { .id = STAC_DELL_M6_DMIC, .name = "dell-m6-dmic" }, + { .id = STAC_DELL_M6_BOTH, .name = "dell-m6" }, + { .id = STAC_DELL_EQ, .name = "dell-eq" }, + { .id = STAC_ALIENWARE_M17X, .name = "alienware" }, + { .id = STAC_ELO_VUPOINT_15MX, .name = "elo-vupoint-15mx" }, + { .id = STAC_92HD73XX_ASUS_MOBO, .name = "asus-mobo" }, + {} +}; + +static const struct snd_pci_quirk stac92hd73xx_fixup_tbl[] = { + /* SigmaTel reference board */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, + "DFI LanParty", STAC_92HD73XX_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101, + "DFI LanParty", STAC_92HD73XX_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5001, + "Intel DP45SG", STAC_92HD73XX_INTEL), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5002, + "Intel DG45ID", STAC_92HD73XX_INTEL), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5003, + "Intel DG45FC", STAC_92HD73XX_INTEL), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0254, + "Dell Studio 1535", STAC_DELL_M6_DMIC), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0255, + "unknown Dell", STAC_DELL_M6_DMIC), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0256, + "unknown Dell", STAC_DELL_M6_BOTH), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0257, + "unknown Dell", STAC_DELL_M6_BOTH), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x025e, + "unknown Dell", STAC_DELL_M6_AMIC), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x025f, + "unknown Dell", STAC_DELL_M6_AMIC), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0271, + "unknown Dell", STAC_DELL_M6_DMIC), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0272, + "unknown Dell", STAC_DELL_M6_DMIC), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x029f, + "Dell Studio 1537", STAC_DELL_M6_DMIC), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02a0, + "Dell Studio 17", STAC_DELL_M6_DMIC), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02be, + "Dell Studio 1555", STAC_DELL_M6_DMIC), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02bd, + "Dell Studio 1557", STAC_DELL_M6_DMIC), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02fe, + "Dell Studio XPS 1645", STAC_DELL_M6_DMIC), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0413, + "Dell Studio 1558", STAC_DELL_M6_DMIC), + /* codec SSID matching */ + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02a1, + "Alienware M17x", STAC_ALIENWARE_M17X), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x043a, + "Alienware M17x", STAC_ALIENWARE_M17X), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0490, + "Alienware M17x R3", STAC_DELL_EQ), + SND_PCI_QUIRK(0x1059, 0x1011, + "ELO VuPoint 15MX", STAC_ELO_VUPOINT_15MX), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1927, + "HP Z1 G2", STAC_92HD89XX_HP_Z1_G2_RIGHT_MIC_JACK), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2b17, + "unknown HP", STAC_92HD89XX_HP_FRONT_JACK), + SND_PCI_QUIRK(PCI_VENDOR_ID_ASUSTEK, 0x83f8, "ASUS AT4NM10", + STAC_92HD73XX_ASUS_MOBO), + {} /* terminator */ +}; + +static const struct hda_pintbl ref92hd83xxx_pin_configs[] = { + { 0x0a, 0x02214030 }, + { 0x0b, 0x02211010 }, + { 0x0c, 0x02a19020 }, + { 0x0d, 0x02170130 }, + { 0x0e, 0x01014050 }, + { 0x0f, 0x01819040 }, + { 0x10, 0x01014020 }, + { 0x11, 0x90a3014e }, + { 0x1f, 0x01451160 }, + { 0x20, 0x98560170 }, + {} +}; + +static const struct hda_pintbl dell_s14_pin_configs[] = { + { 0x0a, 0x0221403f }, + { 0x0b, 0x0221101f }, + { 0x0c, 0x02a19020 }, + { 0x0d, 0x90170110 }, + { 0x0e, 0x40f000f0 }, + { 0x0f, 0x40f000f0 }, + { 0x10, 0x40f000f0 }, + { 0x11, 0x90a60160 }, + { 0x1f, 0x40f000f0 }, + { 0x20, 0x40f000f0 }, + {} +}; + +static const struct hda_pintbl dell_vostro_3500_pin_configs[] = { + { 0x0a, 0x02a11020 }, + { 0x0b, 0x0221101f }, + { 0x0c, 0x400000f0 }, + { 0x0d, 0x90170110 }, + { 0x0e, 0x400000f1 }, + { 0x0f, 0x400000f2 }, + { 0x10, 0x400000f3 }, + { 0x11, 0x90a60160 }, + { 0x1f, 0x400000f4 }, + { 0x20, 0x400000f5 }, + {} +}; + +static const struct hda_pintbl hp_dv7_4000_pin_configs[] = { + { 0x0a, 0x03a12050 }, + { 0x0b, 0x0321201f }, + { 0x0c, 0x40f000f0 }, + { 0x0d, 0x90170110 }, + { 0x0e, 0x40f000f0 }, + { 0x0f, 0x40f000f0 }, + { 0x10, 0x90170110 }, + { 0x11, 0xd5a30140 }, + { 0x1f, 0x40f000f0 }, + { 0x20, 0x40f000f0 }, + {} +}; + +static const struct hda_pintbl hp_zephyr_pin_configs[] = { + { 0x0a, 0x01813050 }, + { 0x0b, 0x0421201f }, + { 0x0c, 0x04a1205e }, + { 0x0d, 0x96130310 }, + { 0x0e, 0x96130310 }, + { 0x0f, 0x0101401f }, + { 0x10, 0x1111611f }, + { 0x11, 0xd5a30130 }, + {} +}; + +static const struct hda_pintbl hp_cNB11_intquad_pin_configs[] = { + { 0x0a, 0x40f000f0 }, + { 0x0b, 0x0221101f }, + { 0x0c, 0x02a11020 }, + { 0x0d, 0x92170110 }, + { 0x0e, 0x40f000f0 }, + { 0x0f, 0x92170110 }, + { 0x10, 0x40f000f0 }, + { 0x11, 0xd5a30130 }, + { 0x1f, 0x40f000f0 }, + { 0x20, 0x40f000f0 }, + {} +}; + +static void stac92hd83xxx_fixup_hp(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + if (hp_bnb2011_with_dock(codec)) { + snd_hda_codec_set_pincfg(codec, 0xa, 0x2101201f); + snd_hda_codec_set_pincfg(codec, 0xf, 0x2181205e); + } + + if (find_mute_led_cfg(codec, spec->default_polarity)) + codec_dbg(codec, "mute LED gpio %d polarity %d\n", + spec->gpio_led, + spec->gpio_led_polarity); + + /* allow auto-switching of dock line-in */ + spec->gen.line_in_auto_switch = true; +} + +static void stac92hd83xxx_fixup_hp_zephyr(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + snd_hda_apply_pincfgs(codec, hp_zephyr_pin_configs); + snd_hda_add_verbs(codec, stac92hd83xxx_hp_zephyr_init); +} + +static void stac92hd83xxx_fixup_hp_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) + spec->default_polarity = 0; +} + +static void stac92hd83xxx_fixup_hp_inv_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) + spec->default_polarity = 1; +} + +static void stac92hd83xxx_fixup_hp_mic_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->mic_mute_led_gpio = 0x08; /* GPIO3 */ +#ifdef CONFIG_PM + /* resetting controller clears GPIO, so we need to keep on */ + codec->core.power_caps &= ~AC_PWRST_CLKSTOP; +#endif + } +} + +static void stac92hd83xxx_fixup_hp_led_gpio10(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->gpio_led = 0x10; /* GPIO4 */ + spec->default_polarity = 0; + } +} + +static void stac92hd83xxx_fixup_headset_jack(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) + spec->headset_jack = 1; +} + +static void stac92hd83xxx_fixup_gpio10_eapd(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + spec->eapd_mask = spec->gpio_mask = spec->gpio_dir = + spec->gpio_data = 0x10; + spec->eapd_switch = 0; +} + +static void hp_envy_ts_fixup_dac_bind(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + struct sigmatel_spec *spec = codec->spec; + static const hda_nid_t preferred_pairs[] = { + 0xd, 0x13, + 0 + }; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + spec->gen.preferred_dacs = preferred_pairs; +} + +static const struct hda_verb hp_bnb13_eq_verbs[] = { + /* 44.1KHz base */ + { 0x22, 0x7A6, 0x3E }, + { 0x22, 0x7A7, 0x68 }, + { 0x22, 0x7A8, 0x17 }, + { 0x22, 0x7A9, 0x3E }, + { 0x22, 0x7AA, 0x68 }, + { 0x22, 0x7AB, 0x17 }, + { 0x22, 0x7AC, 0x00 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x83 }, + { 0x22, 0x7A7, 0x2F }, + { 0x22, 0x7A8, 0xD1 }, + { 0x22, 0x7A9, 0x83 }, + { 0x22, 0x7AA, 0x2F }, + { 0x22, 0x7AB, 0xD1 }, + { 0x22, 0x7AC, 0x01 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x3E }, + { 0x22, 0x7A7, 0x68 }, + { 0x22, 0x7A8, 0x17 }, + { 0x22, 0x7A9, 0x3E }, + { 0x22, 0x7AA, 0x68 }, + { 0x22, 0x7AB, 0x17 }, + { 0x22, 0x7AC, 0x02 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x7C }, + { 0x22, 0x7A7, 0xC6 }, + { 0x22, 0x7A8, 0x0C }, + { 0x22, 0x7A9, 0x7C }, + { 0x22, 0x7AA, 0xC6 }, + { 0x22, 0x7AB, 0x0C }, + { 0x22, 0x7AC, 0x03 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0xC3 }, + { 0x22, 0x7A7, 0x25 }, + { 0x22, 0x7A8, 0xAF }, + { 0x22, 0x7A9, 0xC3 }, + { 0x22, 0x7AA, 0x25 }, + { 0x22, 0x7AB, 0xAF }, + { 0x22, 0x7AC, 0x04 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x3E }, + { 0x22, 0x7A7, 0x85 }, + { 0x22, 0x7A8, 0x73 }, + { 0x22, 0x7A9, 0x3E }, + { 0x22, 0x7AA, 0x85 }, + { 0x22, 0x7AB, 0x73 }, + { 0x22, 0x7AC, 0x05 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x85 }, + { 0x22, 0x7A7, 0x39 }, + { 0x22, 0x7A8, 0xC7 }, + { 0x22, 0x7A9, 0x85 }, + { 0x22, 0x7AA, 0x39 }, + { 0x22, 0x7AB, 0xC7 }, + { 0x22, 0x7AC, 0x06 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x3C }, + { 0x22, 0x7A7, 0x90 }, + { 0x22, 0x7A8, 0xB0 }, + { 0x22, 0x7A9, 0x3C }, + { 0x22, 0x7AA, 0x90 }, + { 0x22, 0x7AB, 0xB0 }, + { 0x22, 0x7AC, 0x07 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x7A }, + { 0x22, 0x7A7, 0xC6 }, + { 0x22, 0x7A8, 0x39 }, + { 0x22, 0x7A9, 0x7A }, + { 0x22, 0x7AA, 0xC6 }, + { 0x22, 0x7AB, 0x39 }, + { 0x22, 0x7AC, 0x08 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0xC4 }, + { 0x22, 0x7A7, 0xE9 }, + { 0x22, 0x7A8, 0xDC }, + { 0x22, 0x7A9, 0xC4 }, + { 0x22, 0x7AA, 0xE9 }, + { 0x22, 0x7AB, 0xDC }, + { 0x22, 0x7AC, 0x09 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x3D }, + { 0x22, 0x7A7, 0xE1 }, + { 0x22, 0x7A8, 0x0D }, + { 0x22, 0x7A9, 0x3D }, + { 0x22, 0x7AA, 0xE1 }, + { 0x22, 0x7AB, 0x0D }, + { 0x22, 0x7AC, 0x0A }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x89 }, + { 0x22, 0x7A7, 0xB6 }, + { 0x22, 0x7A8, 0xEB }, + { 0x22, 0x7A9, 0x89 }, + { 0x22, 0x7AA, 0xB6 }, + { 0x22, 0x7AB, 0xEB }, + { 0x22, 0x7AC, 0x0B }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x39 }, + { 0x22, 0x7A7, 0x9D }, + { 0x22, 0x7A8, 0xFE }, + { 0x22, 0x7A9, 0x39 }, + { 0x22, 0x7AA, 0x9D }, + { 0x22, 0x7AB, 0xFE }, + { 0x22, 0x7AC, 0x0C }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x76 }, + { 0x22, 0x7A7, 0x49 }, + { 0x22, 0x7A8, 0x15 }, + { 0x22, 0x7A9, 0x76 }, + { 0x22, 0x7AA, 0x49 }, + { 0x22, 0x7AB, 0x15 }, + { 0x22, 0x7AC, 0x0D }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0xC8 }, + { 0x22, 0x7A7, 0x80 }, + { 0x22, 0x7A8, 0xF5 }, + { 0x22, 0x7A9, 0xC8 }, + { 0x22, 0x7AA, 0x80 }, + { 0x22, 0x7AB, 0xF5 }, + { 0x22, 0x7AC, 0x0E }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x40 }, + { 0x22, 0x7A7, 0x00 }, + { 0x22, 0x7A8, 0x00 }, + { 0x22, 0x7A9, 0x40 }, + { 0x22, 0x7AA, 0x00 }, + { 0x22, 0x7AB, 0x00 }, + { 0x22, 0x7AC, 0x0F }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x90 }, + { 0x22, 0x7A7, 0x68 }, + { 0x22, 0x7A8, 0xF1 }, + { 0x22, 0x7A9, 0x90 }, + { 0x22, 0x7AA, 0x68 }, + { 0x22, 0x7AB, 0xF1 }, + { 0x22, 0x7AC, 0x10 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x34 }, + { 0x22, 0x7A7, 0x47 }, + { 0x22, 0x7A8, 0x6C }, + { 0x22, 0x7A9, 0x34 }, + { 0x22, 0x7AA, 0x47 }, + { 0x22, 0x7AB, 0x6C }, + { 0x22, 0x7AC, 0x11 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x6F }, + { 0x22, 0x7A7, 0x97 }, + { 0x22, 0x7A8, 0x0F }, + { 0x22, 0x7A9, 0x6F }, + { 0x22, 0x7AA, 0x97 }, + { 0x22, 0x7AB, 0x0F }, + { 0x22, 0x7AC, 0x12 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0xCB }, + { 0x22, 0x7A7, 0xB8 }, + { 0x22, 0x7A8, 0x94 }, + { 0x22, 0x7A9, 0xCB }, + { 0x22, 0x7AA, 0xB8 }, + { 0x22, 0x7AB, 0x94 }, + { 0x22, 0x7AC, 0x13 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x40 }, + { 0x22, 0x7A7, 0x00 }, + { 0x22, 0x7A8, 0x00 }, + { 0x22, 0x7A9, 0x40 }, + { 0x22, 0x7AA, 0x00 }, + { 0x22, 0x7AB, 0x00 }, + { 0x22, 0x7AC, 0x14 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x95 }, + { 0x22, 0x7A7, 0x76 }, + { 0x22, 0x7A8, 0x5B }, + { 0x22, 0x7A9, 0x95 }, + { 0x22, 0x7AA, 0x76 }, + { 0x22, 0x7AB, 0x5B }, + { 0x22, 0x7AC, 0x15 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x31 }, + { 0x22, 0x7A7, 0xAC }, + { 0x22, 0x7A8, 0x31 }, + { 0x22, 0x7A9, 0x31 }, + { 0x22, 0x7AA, 0xAC }, + { 0x22, 0x7AB, 0x31 }, + { 0x22, 0x7AC, 0x16 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x6A }, + { 0x22, 0x7A7, 0x89 }, + { 0x22, 0x7A8, 0xA5 }, + { 0x22, 0x7A9, 0x6A }, + { 0x22, 0x7AA, 0x89 }, + { 0x22, 0x7AB, 0xA5 }, + { 0x22, 0x7AC, 0x17 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0xCE }, + { 0x22, 0x7A7, 0x53 }, + { 0x22, 0x7A8, 0xCF }, + { 0x22, 0x7A9, 0xCE }, + { 0x22, 0x7AA, 0x53 }, + { 0x22, 0x7AB, 0xCF }, + { 0x22, 0x7AC, 0x18 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x40 }, + { 0x22, 0x7A7, 0x00 }, + { 0x22, 0x7A8, 0x00 }, + { 0x22, 0x7A9, 0x40 }, + { 0x22, 0x7AA, 0x00 }, + { 0x22, 0x7AB, 0x00 }, + { 0x22, 0x7AC, 0x19 }, + { 0x22, 0x7AD, 0x80 }, + /* 48KHz base */ + { 0x22, 0x7A6, 0x3E }, + { 0x22, 0x7A7, 0x88 }, + { 0x22, 0x7A8, 0xDC }, + { 0x22, 0x7A9, 0x3E }, + { 0x22, 0x7AA, 0x88 }, + { 0x22, 0x7AB, 0xDC }, + { 0x22, 0x7AC, 0x1A }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x82 }, + { 0x22, 0x7A7, 0xEE }, + { 0x22, 0x7A8, 0x46 }, + { 0x22, 0x7A9, 0x82 }, + { 0x22, 0x7AA, 0xEE }, + { 0x22, 0x7AB, 0x46 }, + { 0x22, 0x7AC, 0x1B }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x3E }, + { 0x22, 0x7A7, 0x88 }, + { 0x22, 0x7A8, 0xDC }, + { 0x22, 0x7A9, 0x3E }, + { 0x22, 0x7AA, 0x88 }, + { 0x22, 0x7AB, 0xDC }, + { 0x22, 0x7AC, 0x1C }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x7D }, + { 0x22, 0x7A7, 0x09 }, + { 0x22, 0x7A8, 0x28 }, + { 0x22, 0x7A9, 0x7D }, + { 0x22, 0x7AA, 0x09 }, + { 0x22, 0x7AB, 0x28 }, + { 0x22, 0x7AC, 0x1D }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0xC2 }, + { 0x22, 0x7A7, 0xE5 }, + { 0x22, 0x7A8, 0xB4 }, + { 0x22, 0x7A9, 0xC2 }, + { 0x22, 0x7AA, 0xE5 }, + { 0x22, 0x7AB, 0xB4 }, + { 0x22, 0x7AC, 0x1E }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x3E }, + { 0x22, 0x7A7, 0xA3 }, + { 0x22, 0x7A8, 0x1F }, + { 0x22, 0x7A9, 0x3E }, + { 0x22, 0x7AA, 0xA3 }, + { 0x22, 0x7AB, 0x1F }, + { 0x22, 0x7AC, 0x1F }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x84 }, + { 0x22, 0x7A7, 0xCA }, + { 0x22, 0x7A8, 0xF1 }, + { 0x22, 0x7A9, 0x84 }, + { 0x22, 0x7AA, 0xCA }, + { 0x22, 0x7AB, 0xF1 }, + { 0x22, 0x7AC, 0x20 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x3C }, + { 0x22, 0x7A7, 0xD5 }, + { 0x22, 0x7A8, 0x9C }, + { 0x22, 0x7A9, 0x3C }, + { 0x22, 0x7AA, 0xD5 }, + { 0x22, 0x7AB, 0x9C }, + { 0x22, 0x7AC, 0x21 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x7B }, + { 0x22, 0x7A7, 0x35 }, + { 0x22, 0x7A8, 0x0F }, + { 0x22, 0x7A9, 0x7B }, + { 0x22, 0x7AA, 0x35 }, + { 0x22, 0x7AB, 0x0F }, + { 0x22, 0x7AC, 0x22 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0xC4 }, + { 0x22, 0x7A7, 0x87 }, + { 0x22, 0x7A8, 0x45 }, + { 0x22, 0x7A9, 0xC4 }, + { 0x22, 0x7AA, 0x87 }, + { 0x22, 0x7AB, 0x45 }, + { 0x22, 0x7AC, 0x23 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x3E }, + { 0x22, 0x7A7, 0x0A }, + { 0x22, 0x7A8, 0x78 }, + { 0x22, 0x7A9, 0x3E }, + { 0x22, 0x7AA, 0x0A }, + { 0x22, 0x7AB, 0x78 }, + { 0x22, 0x7AC, 0x24 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x88 }, + { 0x22, 0x7A7, 0xE2 }, + { 0x22, 0x7A8, 0x05 }, + { 0x22, 0x7A9, 0x88 }, + { 0x22, 0x7AA, 0xE2 }, + { 0x22, 0x7AB, 0x05 }, + { 0x22, 0x7AC, 0x25 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x3A }, + { 0x22, 0x7A7, 0x1A }, + { 0x22, 0x7A8, 0xA3 }, + { 0x22, 0x7A9, 0x3A }, + { 0x22, 0x7AA, 0x1A }, + { 0x22, 0x7AB, 0xA3 }, + { 0x22, 0x7AC, 0x26 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x77 }, + { 0x22, 0x7A7, 0x1D }, + { 0x22, 0x7A8, 0xFB }, + { 0x22, 0x7A9, 0x77 }, + { 0x22, 0x7AA, 0x1D }, + { 0x22, 0x7AB, 0xFB }, + { 0x22, 0x7AC, 0x27 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0xC7 }, + { 0x22, 0x7A7, 0xDA }, + { 0x22, 0x7A8, 0xE5 }, + { 0x22, 0x7A9, 0xC7 }, + { 0x22, 0x7AA, 0xDA }, + { 0x22, 0x7AB, 0xE5 }, + { 0x22, 0x7AC, 0x28 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x40 }, + { 0x22, 0x7A7, 0x00 }, + { 0x22, 0x7A8, 0x00 }, + { 0x22, 0x7A9, 0x40 }, + { 0x22, 0x7AA, 0x00 }, + { 0x22, 0x7AB, 0x00 }, + { 0x22, 0x7AC, 0x29 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x8E }, + { 0x22, 0x7A7, 0xD7 }, + { 0x22, 0x7A8, 0x22 }, + { 0x22, 0x7A9, 0x8E }, + { 0x22, 0x7AA, 0xD7 }, + { 0x22, 0x7AB, 0x22 }, + { 0x22, 0x7AC, 0x2A }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x35 }, + { 0x22, 0x7A7, 0x26 }, + { 0x22, 0x7A8, 0xC6 }, + { 0x22, 0x7A9, 0x35 }, + { 0x22, 0x7AA, 0x26 }, + { 0x22, 0x7AB, 0xC6 }, + { 0x22, 0x7AC, 0x2B }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x71 }, + { 0x22, 0x7A7, 0x28 }, + { 0x22, 0x7A8, 0xDE }, + { 0x22, 0x7A9, 0x71 }, + { 0x22, 0x7AA, 0x28 }, + { 0x22, 0x7AB, 0xDE }, + { 0x22, 0x7AC, 0x2C }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0xCA }, + { 0x22, 0x7A7, 0xD9 }, + { 0x22, 0x7A8, 0x3A }, + { 0x22, 0x7A9, 0xCA }, + { 0x22, 0x7AA, 0xD9 }, + { 0x22, 0x7AB, 0x3A }, + { 0x22, 0x7AC, 0x2D }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x40 }, + { 0x22, 0x7A7, 0x00 }, + { 0x22, 0x7A8, 0x00 }, + { 0x22, 0x7A9, 0x40 }, + { 0x22, 0x7AA, 0x00 }, + { 0x22, 0x7AB, 0x00 }, + { 0x22, 0x7AC, 0x2E }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x93 }, + { 0x22, 0x7A7, 0x5E }, + { 0x22, 0x7A8, 0xD8 }, + { 0x22, 0x7A9, 0x93 }, + { 0x22, 0x7AA, 0x5E }, + { 0x22, 0x7AB, 0xD8 }, + { 0x22, 0x7AC, 0x2F }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x32 }, + { 0x22, 0x7A7, 0xB7 }, + { 0x22, 0x7A8, 0xB1 }, + { 0x22, 0x7A9, 0x32 }, + { 0x22, 0x7AA, 0xB7 }, + { 0x22, 0x7AB, 0xB1 }, + { 0x22, 0x7AC, 0x30 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x6C }, + { 0x22, 0x7A7, 0xA1 }, + { 0x22, 0x7A8, 0x28 }, + { 0x22, 0x7A9, 0x6C }, + { 0x22, 0x7AA, 0xA1 }, + { 0x22, 0x7AB, 0x28 }, + { 0x22, 0x7AC, 0x31 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0xCD }, + { 0x22, 0x7A7, 0x48 }, + { 0x22, 0x7A8, 0x4F }, + { 0x22, 0x7A9, 0xCD }, + { 0x22, 0x7AA, 0x48 }, + { 0x22, 0x7AB, 0x4F }, + { 0x22, 0x7AC, 0x32 }, + { 0x22, 0x7AD, 0x80 }, + { 0x22, 0x7A6, 0x40 }, + { 0x22, 0x7A7, 0x00 }, + { 0x22, 0x7A8, 0x00 }, + { 0x22, 0x7A9, 0x40 }, + { 0x22, 0x7AA, 0x00 }, + { 0x22, 0x7AB, 0x00 }, + { 0x22, 0x7AC, 0x33 }, + { 0x22, 0x7AD, 0x80 }, + /* common */ + { 0x22, 0x782, 0xC1 }, + { 0x22, 0x771, 0x2C }, + { 0x22, 0x772, 0x2C }, + { 0x22, 0x788, 0x04 }, + { 0x01, 0x7B0, 0x08 }, + {} +}; + +static const struct hda_fixup stac92hd83xxx_fixups[] = { + [STAC_92HD83XXX_REF] = { + .type = HDA_FIXUP_PINS, + .v.pins = ref92hd83xxx_pin_configs, + }, + [STAC_92HD83XXX_PWR_REF] = { + .type = HDA_FIXUP_PINS, + .v.pins = ref92hd83xxx_pin_configs, + }, + [STAC_DELL_S14] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell_s14_pin_configs, + }, + [STAC_DELL_VOSTRO_3500] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell_vostro_3500_pin_configs, + }, + [STAC_92HD83XXX_HP_cNB11_INTQUAD] = { + .type = HDA_FIXUP_PINS, + .v.pins = hp_cNB11_intquad_pin_configs, + .chained = true, + .chain_id = STAC_92HD83XXX_HP, + }, + [STAC_92HD83XXX_HP] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd83xxx_fixup_hp, + }, + [STAC_HP_DV7_4000] = { + .type = HDA_FIXUP_PINS, + .v.pins = hp_dv7_4000_pin_configs, + .chained = true, + .chain_id = STAC_92HD83XXX_HP, + }, + [STAC_HP_ZEPHYR] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd83xxx_fixup_hp_zephyr, + .chained = true, + .chain_id = STAC_92HD83XXX_HP, + }, + [STAC_92HD83XXX_HP_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd83xxx_fixup_hp_led, + .chained = true, + .chain_id = STAC_92HD83XXX_HP, + }, + [STAC_92HD83XXX_HP_INV_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd83xxx_fixup_hp_inv_led, + .chained = true, + .chain_id = STAC_92HD83XXX_HP, + }, + [STAC_92HD83XXX_HP_MIC_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd83xxx_fixup_hp_mic_led, + .chained = true, + .chain_id = STAC_92HD83XXX_HP, + }, + [STAC_HP_LED_GPIO10] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd83xxx_fixup_hp_led_gpio10, + .chained = true, + .chain_id = STAC_92HD83XXX_HP, + }, + [STAC_92HD83XXX_HEADSET_JACK] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd83xxx_fixup_headset_jack, + }, + [STAC_HP_ENVY_BASS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x0f, 0x90170111 }, + {} + }, + }, + [STAC_HP_BNB13_EQ] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = hp_bnb13_eq_verbs, + .chained = true, + .chain_id = STAC_92HD83XXX_HP_MIC_LED, + }, + [STAC_HP_ENVY_TS_BASS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x10, 0x92170111 }, + {} + }, + }, + [STAC_HP_ENVY_TS_DAC_BIND] = { + .type = HDA_FIXUP_FUNC, + .v.func = hp_envy_ts_fixup_dac_bind, + .chained = true, + .chain_id = STAC_HP_ENVY_TS_BASS, + }, + [STAC_92HD83XXX_GPIO10_EAPD] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd83xxx_fixup_gpio10_eapd, + }, +}; + +static const struct hda_model_fixup stac92hd83xxx_models[] = { + { .id = STAC_92HD83XXX_REF, .name = "ref" }, + { .id = STAC_92HD83XXX_PWR_REF, .name = "mic-ref" }, + { .id = STAC_DELL_S14, .name = "dell-s14" }, + { .id = STAC_DELL_VOSTRO_3500, .name = "dell-vostro-3500" }, + { .id = STAC_92HD83XXX_HP_cNB11_INTQUAD, .name = "hp_cNB11_intquad" }, + { .id = STAC_HP_DV7_4000, .name = "hp-dv7-4000" }, + { .id = STAC_HP_ZEPHYR, .name = "hp-zephyr" }, + { .id = STAC_92HD83XXX_HP_LED, .name = "hp-led" }, + { .id = STAC_92HD83XXX_HP_INV_LED, .name = "hp-inv-led" }, + { .id = STAC_92HD83XXX_HP_MIC_LED, .name = "hp-mic-led" }, + { .id = STAC_92HD83XXX_HEADSET_JACK, .name = "headset-jack" }, + { .id = STAC_HP_ENVY_BASS, .name = "hp-envy-bass" }, + { .id = STAC_HP_BNB13_EQ, .name = "hp-bnb13-eq" }, + { .id = STAC_HP_ENVY_TS_BASS, .name = "hp-envy-ts-bass" }, + {} +}; + +static const struct snd_pci_quirk stac92hd83xxx_fixup_tbl[] = { + /* SigmaTel reference board */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, + "DFI LanParty", STAC_92HD83XXX_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101, + "DFI LanParty", STAC_92HD83XXX_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02ba, + "unknown Dell", STAC_DELL_S14), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0532, + "Dell Latitude E6230", STAC_92HD83XXX_HEADSET_JACK), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0533, + "Dell Latitude E6330", STAC_92HD83XXX_HEADSET_JACK), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0534, + "Dell Latitude E6430", STAC_92HD83XXX_HEADSET_JACK), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0535, + "Dell Latitude E6530", STAC_92HD83XXX_HEADSET_JACK), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x053c, + "Dell Latitude E5430", STAC_92HD83XXX_HEADSET_JACK), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x053d, + "Dell Latitude E5530", STAC_92HD83XXX_HEADSET_JACK), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0549, + "Dell Latitude E5430", STAC_92HD83XXX_HEADSET_JACK), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x057d, + "Dell Latitude E6430s", STAC_92HD83XXX_HEADSET_JACK), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0584, + "Dell Latitude E6430U", STAC_92HD83XXX_HEADSET_JACK), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x1028, + "Dell Vostro 3500", STAC_DELL_VOSTRO_3500), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1656, + "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1657, + "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1658, + "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1659, + "HP Pavilion dv7", STAC_HP_DV7_4000), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x165A, + "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x165B, + "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1888, + "HP Envy Spectre", STAC_HP_ENVY_BASS), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1899, + "HP Folio 13", STAC_HP_LED_GPIO10), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x18df, + "HP Folio", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x18F8, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1909, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x190A, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x190e, + "HP ENVY TS", STAC_HP_ENVY_TS_BASS), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1967, + "HP ENVY TS", STAC_HP_ENVY_TS_DAC_BIND), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1940, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1941, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1942, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1943, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1944, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1945, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1946, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1948, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1949, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x194A, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x194B, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x194C, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x194E, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x194F, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1950, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1951, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x195A, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x195B, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x195C, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1991, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2103, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2104, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2105, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2106, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2107, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2108, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2109, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x210A, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x210B, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x211C, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x211D, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x211E, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x211F, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2120, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2121, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2122, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2123, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x213E, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x213F, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x2140, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x21B2, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x21B3, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x21B5, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x21B6, + "HP bNB13", STAC_HP_BNB13_EQ), + SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xff00, 0x1900, + "HP", STAC_92HD83XXX_HP_MIC_LED), + SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xff00, 0x2000, + "HP", STAC_92HD83XXX_HP_MIC_LED), + SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xff00, 0x2100, + "HP", STAC_92HD83XXX_HP_MIC_LED), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3388, + "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3389, + "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x355B, + "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x355C, + "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x355D, + "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x355E, + "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x355F, + "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3560, + "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x358B, + "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x358C, + "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x358D, + "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3591, + "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3592, + "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3593, + "HP", STAC_92HD83XXX_HP_cNB11_INTQUAD), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3561, + "HP", STAC_HP_ZEPHYR), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3660, + "HP Mini", STAC_92HD83XXX_HP_LED), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x144E, + "HP Pavilion dv5", STAC_92HD83XXX_HP_INV_LED), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x148a, + "HP Mini", STAC_92HD83XXX_HP_LED), + SND_PCI_QUIRK_VENDOR(PCI_VENDOR_ID_HP, "HP", STAC_92HD83XXX_HP), + /* match both for 0xfa91 and 0xfa93 */ + SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_TOSHIBA, 0xfffd, 0xfa91, + "Toshiba Satellite S50D", STAC_92HD83XXX_GPIO10_EAPD), + {} /* terminator */ +}; + +/* HP dv7 bass switch - GPIO5 */ +#define stac_hp_bass_gpio_info snd_ctl_boolean_mono_info +static int stac_hp_bass_gpio_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + ucontrol->value.integer.value[0] = !!(spec->gpio_data & 0x20); + return 0; +} + +static int stac_hp_bass_gpio_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + unsigned int gpio_data; + + gpio_data = (spec->gpio_data & ~0x20) | + (ucontrol->value.integer.value[0] ? 0x20 : 0); + if (gpio_data == spec->gpio_data) + return 0; + spec->gpio_data = gpio_data; + stac_gpio_set(codec, spec->gpio_mask, spec->gpio_dir, spec->gpio_data); + return 1; +} + +static const struct snd_kcontrol_new stac_hp_bass_sw_ctrl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = stac_hp_bass_gpio_info, + .get = stac_hp_bass_gpio_get, + .put = stac_hp_bass_gpio_put, +}; + +static int stac_add_hp_bass_switch(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + + if (!snd_hda_gen_add_kctl(&spec->gen, "Bass Speaker Playback Switch", + &stac_hp_bass_sw_ctrl)) + return -ENOMEM; + + spec->gpio_mask |= 0x20; + spec->gpio_dir |= 0x20; + spec->gpio_data |= 0x20; + return 0; +} + +static const struct hda_pintbl ref92hd71bxx_pin_configs[] = { + { 0x0a, 0x02214030 }, + { 0x0b, 0x02a19040 }, + { 0x0c, 0x01a19020 }, + { 0x0d, 0x01014010 }, + { 0x0e, 0x0181302e }, + { 0x0f, 0x01014010 }, + { 0x14, 0x01019020 }, + { 0x18, 0x90a000f0 }, + { 0x19, 0x90a000f0 }, + { 0x1e, 0x01452050 }, + { 0x1f, 0x01452050 }, + {} +}; + +static const struct hda_pintbl dell_m4_1_pin_configs[] = { + { 0x0a, 0x0421101f }, + { 0x0b, 0x04a11221 }, + { 0x0c, 0x40f000f0 }, + { 0x0d, 0x90170110 }, + { 0x0e, 0x23a1902e }, + { 0x0f, 0x23014250 }, + { 0x14, 0x40f000f0 }, + { 0x18, 0x90a000f0 }, + { 0x19, 0x40f000f0 }, + { 0x1e, 0x4f0000f0 }, + { 0x1f, 0x4f0000f0 }, + {} +}; + +static const struct hda_pintbl dell_m4_2_pin_configs[] = { + { 0x0a, 0x0421101f }, + { 0x0b, 0x04a11221 }, + { 0x0c, 0x90a70330 }, + { 0x0d, 0x90170110 }, + { 0x0e, 0x23a1902e }, + { 0x0f, 0x23014250 }, + { 0x14, 0x40f000f0 }, + { 0x18, 0x40f000f0 }, + { 0x19, 0x40f000f0 }, + { 0x1e, 0x044413b0 }, + { 0x1f, 0x044413b0 }, + {} +}; + +static const struct hda_pintbl dell_m4_3_pin_configs[] = { + { 0x0a, 0x0421101f }, + { 0x0b, 0x04a11221 }, + { 0x0c, 0x90a70330 }, + { 0x0d, 0x90170110 }, + { 0x0e, 0x40f000f0 }, + { 0x0f, 0x40f000f0 }, + { 0x14, 0x40f000f0 }, + { 0x18, 0x90a000f0 }, + { 0x19, 0x40f000f0 }, + { 0x1e, 0x044413b0 }, + { 0x1f, 0x044413b0 }, + {} +}; + +static void stac92hd71bxx_fixup_ref(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + snd_hda_apply_pincfgs(codec, ref92hd71bxx_pin_configs); + spec->gpio_mask = spec->gpio_dir = spec->gpio_data = 0; +} + +static void stac92hd71bxx_fixup_hp_m4(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + struct hda_jack_callback *jack; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + /* Enable VREF power saving on GPIO1 detect */ + snd_hda_codec_write_cache(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK, 0x02); + jack = snd_hda_jack_detect_enable_callback(codec, codec->core.afg, + stac_vref_event); + if (!IS_ERR(jack)) + jack->private_data = 0x02; + + spec->gpio_mask |= 0x02; + + /* enable internal microphone */ + snd_hda_codec_set_pincfg(codec, 0x0e, 0x01813040); +} + +static void stac92hd71bxx_fixup_hp_dv4(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + spec->gpio_led = 0x01; +} + +static void stac92hd71bxx_fixup_hp_dv5(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + unsigned int cap; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + snd_hda_codec_set_pincfg(codec, 0x0d, 0x90170010); + break; + + case HDA_FIXUP_ACT_PROBE: + /* enable bass on HP dv7 */ + cap = snd_hda_param_read(codec, 0x1, AC_PAR_GPIO_CAP); + cap &= AC_GPIO_IO_COUNT; + if (cap >= 6) + stac_add_hp_bass_switch(codec); + break; + } +} + +static void stac92hd71bxx_fixup_hp_hdx(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + spec->gpio_led = 0x08; +} + +static bool is_hp_output(struct hda_codec *codec, hda_nid_t pin) +{ + unsigned int pin_cfg = snd_hda_codec_get_pincfg(codec, pin); + + /* count line-out, too, as BIOS sets often so */ + return get_defcfg_connect(pin_cfg) != AC_JACK_PORT_NONE && + (get_defcfg_device(pin_cfg) == AC_JACK_LINE_OUT || + get_defcfg_device(pin_cfg) == AC_JACK_HP_OUT); +} + +static void fixup_hp_headphone(struct hda_codec *codec, hda_nid_t pin) +{ + unsigned int pin_cfg = snd_hda_codec_get_pincfg(codec, pin); + + /* It was changed in the BIOS to just satisfy MS DTM. + * Lets turn it back into follower HP + */ + pin_cfg = (pin_cfg & (~AC_DEFCFG_DEVICE)) | + (AC_JACK_HP_OUT << AC_DEFCFG_DEVICE_SHIFT); + pin_cfg = (pin_cfg & (~(AC_DEFCFG_DEF_ASSOC | AC_DEFCFG_SEQUENCE))) | + 0x1f; + snd_hda_codec_set_pincfg(codec, pin, pin_cfg); +} + +static void stac92hd71bxx_fixup_hp(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + /* when both output A and F are assigned, these are supposedly + * dock and built-in headphones; fix both pin configs + */ + if (is_hp_output(codec, 0x0a) && is_hp_output(codec, 0x0f)) { + fixup_hp_headphone(codec, 0x0a); + fixup_hp_headphone(codec, 0x0f); + } + + if (find_mute_led_cfg(codec, 1)) + codec_dbg(codec, "mute LED gpio %d polarity %d\n", + spec->gpio_led, + spec->gpio_led_polarity); + +} + +static const struct hda_fixup stac92hd71bxx_fixups[] = { + [STAC_92HD71BXX_REF] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd71bxx_fixup_ref, + }, + [STAC_DELL_M4_1] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell_m4_1_pin_configs, + }, + [STAC_DELL_M4_2] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell_m4_2_pin_configs, + }, + [STAC_DELL_M4_3] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell_m4_3_pin_configs, + }, + [STAC_HP_M4] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd71bxx_fixup_hp_m4, + .chained = true, + .chain_id = STAC_92HD71BXX_HP, + }, + [STAC_HP_DV4] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd71bxx_fixup_hp_dv4, + .chained = true, + .chain_id = STAC_HP_DV5, + }, + [STAC_HP_DV5] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd71bxx_fixup_hp_dv5, + .chained = true, + .chain_id = STAC_92HD71BXX_HP, + }, + [STAC_HP_HDX] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd71bxx_fixup_hp_hdx, + .chained = true, + .chain_id = STAC_92HD71BXX_HP, + }, + [STAC_92HD71BXX_HP] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd71bxx_fixup_hp, + }, +}; + +static const struct hda_model_fixup stac92hd71bxx_models[] = { + { .id = STAC_92HD71BXX_REF, .name = "ref" }, + { .id = STAC_DELL_M4_1, .name = "dell-m4-1" }, + { .id = STAC_DELL_M4_2, .name = "dell-m4-2" }, + { .id = STAC_DELL_M4_3, .name = "dell-m4-3" }, + { .id = STAC_HP_M4, .name = "hp-m4" }, + { .id = STAC_HP_DV4, .name = "hp-dv4" }, + { .id = STAC_HP_DV5, .name = "hp-dv5" }, + { .id = STAC_HP_HDX, .name = "hp-hdx" }, + { .id = STAC_HP_DV4, .name = "hp-dv4-1222nr" }, + {} +}; + +static const struct snd_pci_quirk stac92hd71bxx_fixup_tbl[] = { + /* SigmaTel reference board */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, + "DFI LanParty", STAC_92HD71BXX_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101, + "DFI LanParty", STAC_92HD71BXX_REF), + SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x1720, + "HP", STAC_HP_DV5), + SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x3080, + "HP", STAC_HP_DV5), + SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x30f0, + "HP dv4-7", STAC_HP_DV4), + SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x3600, + "HP dv4-7", STAC_HP_DV5), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3610, + "HP HDX", STAC_HP_HDX), /* HDX18 */ + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x361a, + "HP mini 1000", STAC_HP_M4), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x361b, + "HP HDX", STAC_HP_HDX), /* HDX16 */ + SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x3620, + "HP dv6", STAC_HP_DV5), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3061, + "HP dv6", STAC_HP_DV5), /* HP dv6-1110ax */ + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x363e, + "HP DV6", STAC_HP_DV5), + SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x7010, + "HP", STAC_HP_DV5), + SND_PCI_QUIRK_VENDOR(PCI_VENDOR_ID_HP, "HP", STAC_92HD71BXX_HP), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0233, + "unknown Dell", STAC_DELL_M4_1), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0234, + "unknown Dell", STAC_DELL_M4_1), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0250, + "unknown Dell", STAC_DELL_M4_1), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x024f, + "unknown Dell", STAC_DELL_M4_1), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x024d, + "unknown Dell", STAC_DELL_M4_1), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0251, + "unknown Dell", STAC_DELL_M4_1), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0277, + "unknown Dell", STAC_DELL_M4_1), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0263, + "unknown Dell", STAC_DELL_M4_2), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0265, + "unknown Dell", STAC_DELL_M4_2), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0262, + "unknown Dell", STAC_DELL_M4_2), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0264, + "unknown Dell", STAC_DELL_M4_2), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02aa, + "unknown Dell", STAC_DELL_M4_3), + {} /* terminator */ +}; + +static const struct hda_pintbl ref922x_pin_configs[] = { + { 0x0a, 0x01014010 }, + { 0x0b, 0x01016011 }, + { 0x0c, 0x01012012 }, + { 0x0d, 0x0221401f }, + { 0x0e, 0x01813122 }, + { 0x0f, 0x01011014 }, + { 0x10, 0x01441030 }, + { 0x11, 0x01c41030 }, + { 0x15, 0x40000100 }, + { 0x1b, 0x40000100 }, + {} +}; + +/* + STAC 922X pin configs for + 102801A7 + 102801AB + 102801A9 + 102801D1 + 102801D2 +*/ +static const struct hda_pintbl dell_922x_d81_pin_configs[] = { + { 0x0a, 0x02214030 }, + { 0x0b, 0x01a19021 }, + { 0x0c, 0x01111012 }, + { 0x0d, 0x01114010 }, + { 0x0e, 0x02a19020 }, + { 0x0f, 0x01117011 }, + { 0x10, 0x400001f0 }, + { 0x11, 0x400001f1 }, + { 0x15, 0x01813122 }, + { 0x1b, 0x400001f2 }, + {} +}; + +/* + STAC 922X pin configs for + 102801AC + 102801D0 +*/ +static const struct hda_pintbl dell_922x_d82_pin_configs[] = { + { 0x0a, 0x02214030 }, + { 0x0b, 0x01a19021 }, + { 0x0c, 0x01111012 }, + { 0x0d, 0x01114010 }, + { 0x0e, 0x02a19020 }, + { 0x0f, 0x01117011 }, + { 0x10, 0x01451140 }, + { 0x11, 0x400001f0 }, + { 0x15, 0x01813122 }, + { 0x1b, 0x400001f1 }, + {} +}; + +/* + STAC 922X pin configs for + 102801BF +*/ +static const struct hda_pintbl dell_922x_m81_pin_configs[] = { + { 0x0a, 0x0321101f }, + { 0x0b, 0x01112024 }, + { 0x0c, 0x01111222 }, + { 0x0d, 0x91174220 }, + { 0x0e, 0x03a11050 }, + { 0x0f, 0x01116221 }, + { 0x10, 0x90a70330 }, + { 0x11, 0x01452340 }, + { 0x15, 0x40C003f1 }, + { 0x1b, 0x405003f0 }, + {} +}; + +/* + STAC 9221 A1 pin configs for + 102801D7 (Dell XPS M1210) +*/ +static const struct hda_pintbl dell_922x_m82_pin_configs[] = { + { 0x0a, 0x02211211 }, + { 0x0b, 0x408103ff }, + { 0x0c, 0x02a1123e }, + { 0x0d, 0x90100310 }, + { 0x0e, 0x408003f1 }, + { 0x0f, 0x0221121f }, + { 0x10, 0x03451340 }, + { 0x11, 0x40c003f2 }, + { 0x15, 0x508003f3 }, + { 0x1b, 0x405003f4 }, + {} +}; + +static const struct hda_pintbl d945gtp3_pin_configs[] = { + { 0x0a, 0x0221401f }, + { 0x0b, 0x01a19022 }, + { 0x0c, 0x01813021 }, + { 0x0d, 0x01014010 }, + { 0x0e, 0x40000100 }, + { 0x0f, 0x40000100 }, + { 0x10, 0x40000100 }, + { 0x11, 0x40000100 }, + { 0x15, 0x02a19120 }, + { 0x1b, 0x40000100 }, + {} +}; + +static const struct hda_pintbl d945gtp5_pin_configs[] = { + { 0x0a, 0x0221401f }, + { 0x0b, 0x01011012 }, + { 0x0c, 0x01813024 }, + { 0x0d, 0x01014010 }, + { 0x0e, 0x01a19021 }, + { 0x0f, 0x01016011 }, + { 0x10, 0x01452130 }, + { 0x11, 0x40000100 }, + { 0x15, 0x02a19320 }, + { 0x1b, 0x40000100 }, + {} +}; + +static const struct hda_pintbl intel_mac_v1_pin_configs[] = { + { 0x0a, 0x0121e21f }, + { 0x0b, 0x400000ff }, + { 0x0c, 0x9017e110 }, + { 0x0d, 0x400000fd }, + { 0x0e, 0x400000fe }, + { 0x0f, 0x0181e020 }, + { 0x10, 0x1145e030 }, + { 0x11, 0x11c5e240 }, + { 0x15, 0x400000fc }, + { 0x1b, 0x400000fb }, + {} +}; + +static const struct hda_pintbl intel_mac_v2_pin_configs[] = { + { 0x0a, 0x0121e21f }, + { 0x0b, 0x90a7012e }, + { 0x0c, 0x9017e110 }, + { 0x0d, 0x400000fd }, + { 0x0e, 0x400000fe }, + { 0x0f, 0x0181e020 }, + { 0x10, 0x1145e230 }, + { 0x11, 0x500000fa }, + { 0x15, 0x400000fc }, + { 0x1b, 0x400000fb }, + {} +}; + +static const struct hda_pintbl intel_mac_v3_pin_configs[] = { + { 0x0a, 0x0121e21f }, + { 0x0b, 0x90a7012e }, + { 0x0c, 0x9017e110 }, + { 0x0d, 0x400000fd }, + { 0x0e, 0x400000fe }, + { 0x0f, 0x0181e020 }, + { 0x10, 0x1145e230 }, + { 0x11, 0x11c5e240 }, + { 0x15, 0x400000fc }, + { 0x1b, 0x400000fb }, + {} +}; + +static const struct hda_pintbl intel_mac_v4_pin_configs[] = { + { 0x0a, 0x0321e21f }, + { 0x0b, 0x03a1e02e }, + { 0x0c, 0x9017e110 }, + { 0x0d, 0x9017e11f }, + { 0x0e, 0x400000fe }, + { 0x0f, 0x0381e020 }, + { 0x10, 0x1345e230 }, + { 0x11, 0x13c5e240 }, + { 0x15, 0x400000fc }, + { 0x1b, 0x400000fb }, + {} +}; + +static const struct hda_pintbl intel_mac_v5_pin_configs[] = { + { 0x0a, 0x0321e21f }, + { 0x0b, 0x03a1e02e }, + { 0x0c, 0x9017e110 }, + { 0x0d, 0x9017e11f }, + { 0x0e, 0x400000fe }, + { 0x0f, 0x0381e020 }, + { 0x10, 0x1345e230 }, + { 0x11, 0x13c5e240 }, + { 0x15, 0x400000fc }, + { 0x1b, 0x400000fb }, + {} +}; + +static const struct hda_pintbl ecs202_pin_configs[] = { + { 0x0a, 0x0221401f }, + { 0x0b, 0x02a19020 }, + { 0x0c, 0x01a19020 }, + { 0x0d, 0x01114010 }, + { 0x0e, 0x408000f0 }, + { 0x0f, 0x01813022 }, + { 0x10, 0x074510a0 }, + { 0x11, 0x40c400f1 }, + { 0x15, 0x9037012e }, + { 0x1b, 0x40e000f2 }, + {} +}; + +/* codec SSIDs for Intel Mac sharing the same PCI SSID 8384:7680 */ +static const struct snd_pci_quirk stac922x_intel_mac_fixup_tbl[] = { + SND_PCI_QUIRK(0x0000, 0x0100, "Mac Mini", STAC_INTEL_MAC_V3), + SND_PCI_QUIRK(0x106b, 0x0800, "Mac", STAC_INTEL_MAC_V1), + SND_PCI_QUIRK(0x106b, 0x0600, "Mac", STAC_INTEL_MAC_V2), + SND_PCI_QUIRK(0x106b, 0x0700, "Mac", STAC_INTEL_MAC_V2), + SND_PCI_QUIRK(0x106b, 0x0e00, "Mac", STAC_INTEL_MAC_V3), + SND_PCI_QUIRK(0x106b, 0x0f00, "Mac", STAC_INTEL_MAC_V3), + SND_PCI_QUIRK(0x106b, 0x1600, "Mac", STAC_INTEL_MAC_V3), + SND_PCI_QUIRK(0x106b, 0x1700, "Mac", STAC_INTEL_MAC_V3), + SND_PCI_QUIRK(0x106b, 0x0200, "Mac", STAC_INTEL_MAC_V3), + SND_PCI_QUIRK(0x106b, 0x1e00, "Mac", STAC_INTEL_MAC_V3), + SND_PCI_QUIRK(0x106b, 0x1a00, "Mac", STAC_INTEL_MAC_V4), + SND_PCI_QUIRK(0x106b, 0x0a00, "Mac", STAC_INTEL_MAC_V5), + SND_PCI_QUIRK(0x106b, 0x2200, "Mac", STAC_INTEL_MAC_V5), + {} +}; + +static const struct hda_fixup stac922x_fixups[]; + +/* remap the fixup from codec SSID and apply it */ +static void stac922x_fixup_intel_mac_auto(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + codec->fixup_id = HDA_FIXUP_ID_NOT_SET; + snd_hda_pick_fixup(codec, NULL, stac922x_intel_mac_fixup_tbl, + stac922x_fixups); + if (codec->fixup_id != HDA_FIXUP_ID_NOT_SET) + snd_hda_apply_fixup(codec, action); +} + +static void stac922x_fixup_intel_mac_gpio(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->gpio_mask = spec->gpio_dir = 0x03; + spec->gpio_data = 0x03; + } +} + +static const struct hda_fixup stac922x_fixups[] = { + [STAC_D945_REF] = { + .type = HDA_FIXUP_PINS, + .v.pins = ref922x_pin_configs, + }, + [STAC_D945GTP3] = { + .type = HDA_FIXUP_PINS, + .v.pins = d945gtp3_pin_configs, + }, + [STAC_D945GTP5] = { + .type = HDA_FIXUP_PINS, + .v.pins = d945gtp5_pin_configs, + }, + [STAC_INTEL_MAC_AUTO] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac922x_fixup_intel_mac_auto, + }, + [STAC_INTEL_MAC_V1] = { + .type = HDA_FIXUP_PINS, + .v.pins = intel_mac_v1_pin_configs, + .chained = true, + .chain_id = STAC_922X_INTEL_MAC_GPIO, + }, + [STAC_INTEL_MAC_V2] = { + .type = HDA_FIXUP_PINS, + .v.pins = intel_mac_v2_pin_configs, + .chained = true, + .chain_id = STAC_922X_INTEL_MAC_GPIO, + }, + [STAC_INTEL_MAC_V3] = { + .type = HDA_FIXUP_PINS, + .v.pins = intel_mac_v3_pin_configs, + .chained = true, + .chain_id = STAC_922X_INTEL_MAC_GPIO, + }, + [STAC_INTEL_MAC_V4] = { + .type = HDA_FIXUP_PINS, + .v.pins = intel_mac_v4_pin_configs, + .chained = true, + .chain_id = STAC_922X_INTEL_MAC_GPIO, + }, + [STAC_INTEL_MAC_V5] = { + .type = HDA_FIXUP_PINS, + .v.pins = intel_mac_v5_pin_configs, + .chained = true, + .chain_id = STAC_922X_INTEL_MAC_GPIO, + }, + [STAC_922X_INTEL_MAC_GPIO] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac922x_fixup_intel_mac_gpio, + }, + [STAC_ECS_202] = { + .type = HDA_FIXUP_PINS, + .v.pins = ecs202_pin_configs, + }, + [STAC_922X_DELL_D81] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell_922x_d81_pin_configs, + }, + [STAC_922X_DELL_D82] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell_922x_d82_pin_configs, + }, + [STAC_922X_DELL_M81] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell_922x_m81_pin_configs, + }, + [STAC_922X_DELL_M82] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell_922x_m82_pin_configs, + }, +}; + +static const struct hda_model_fixup stac922x_models[] = { + { .id = STAC_D945_REF, .name = "ref" }, + { .id = STAC_D945GTP5, .name = "5stack" }, + { .id = STAC_D945GTP3, .name = "3stack" }, + { .id = STAC_INTEL_MAC_V1, .name = "intel-mac-v1" }, + { .id = STAC_INTEL_MAC_V2, .name = "intel-mac-v2" }, + { .id = STAC_INTEL_MAC_V3, .name = "intel-mac-v3" }, + { .id = STAC_INTEL_MAC_V4, .name = "intel-mac-v4" }, + { .id = STAC_INTEL_MAC_V5, .name = "intel-mac-v5" }, + { .id = STAC_INTEL_MAC_AUTO, .name = "intel-mac-auto" }, + { .id = STAC_ECS_202, .name = "ecs202" }, + { .id = STAC_922X_DELL_D81, .name = "dell-d81" }, + { .id = STAC_922X_DELL_D82, .name = "dell-d82" }, + { .id = STAC_922X_DELL_M81, .name = "dell-m81" }, + { .id = STAC_922X_DELL_M82, .name = "dell-m82" }, + /* for backward compatibility */ + { .id = STAC_INTEL_MAC_V3, .name = "macmini" }, + { .id = STAC_INTEL_MAC_V5, .name = "macbook" }, + { .id = STAC_INTEL_MAC_V3, .name = "macbook-pro-v1" }, + { .id = STAC_INTEL_MAC_V3, .name = "macbook-pro" }, + { .id = STAC_INTEL_MAC_V2, .name = "imac-intel" }, + { .id = STAC_INTEL_MAC_V3, .name = "imac-intel-20" }, + {} +}; + +static const struct snd_pci_quirk stac922x_fixup_tbl[] = { + /* SigmaTel reference board */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, + "DFI LanParty", STAC_D945_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101, + "DFI LanParty", STAC_D945_REF), + /* Intel 945G based systems */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0101, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0202, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0606, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0601, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0111, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1115, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1116, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1117, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1118, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1119, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x8826, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5049, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5055, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5048, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0110, + "Intel D945G", STAC_D945GTP3), + /* Intel D945G 5-stack systems */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0404, + "Intel D945G", STAC_D945GTP5), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0303, + "Intel D945G", STAC_D945GTP5), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0013, + "Intel D945G", STAC_D945GTP5), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0417, + "Intel D945G", STAC_D945GTP5), + /* Intel 945P based systems */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0b0b, + "Intel D945P", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0112, + "Intel D945P", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0d0d, + "Intel D945P", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0909, + "Intel D945P", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0505, + "Intel D945P", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0707, + "Intel D945P", STAC_D945GTP5), + /* other intel */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0204, + "Intel D945", STAC_D945_REF), + /* other systems */ + + /* Apple Intel Mac (Mac Mini, MacBook, MacBook Pro...) */ + SND_PCI_QUIRK(0x8384, 0x7680, "Mac", STAC_INTEL_MAC_AUTO), + + /* Dell systems */ + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01a7, + "unknown Dell", STAC_922X_DELL_D81), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01a9, + "unknown Dell", STAC_922X_DELL_D81), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ab, + "unknown Dell", STAC_922X_DELL_D81), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ac, + "unknown Dell", STAC_922X_DELL_D82), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01bf, + "unknown Dell", STAC_922X_DELL_M81), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d0, + "unknown Dell", STAC_922X_DELL_D82), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d1, + "unknown Dell", STAC_922X_DELL_D81), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d2, + "unknown Dell", STAC_922X_DELL_D81), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d7, + "Dell XPS M1210", STAC_922X_DELL_M82), + /* ECS/PC Chips boards */ + SND_PCI_QUIRK_MASK(0x1019, 0xf000, 0x2000, + "ECS/PC chips", STAC_ECS_202), + {} /* terminator */ +}; + +static const struct hda_pintbl ref927x_pin_configs[] = { + { 0x0a, 0x02214020 }, + { 0x0b, 0x02a19080 }, + { 0x0c, 0x0181304e }, + { 0x0d, 0x01014010 }, + { 0x0e, 0x01a19040 }, + { 0x0f, 0x01011012 }, + { 0x10, 0x01016011 }, + { 0x11, 0x0101201f }, + { 0x12, 0x183301f0 }, + { 0x13, 0x18a001f0 }, + { 0x14, 0x18a001f0 }, + { 0x21, 0x01442070 }, + { 0x22, 0x01c42190 }, + { 0x23, 0x40000100 }, + {} +}; + +static const struct hda_pintbl d965_3st_pin_configs[] = { + { 0x0a, 0x0221401f }, + { 0x0b, 0x02a19120 }, + { 0x0c, 0x40000100 }, + { 0x0d, 0x01014011 }, + { 0x0e, 0x01a19021 }, + { 0x0f, 0x01813024 }, + { 0x10, 0x40000100 }, + { 0x11, 0x40000100 }, + { 0x12, 0x40000100 }, + { 0x13, 0x40000100 }, + { 0x14, 0x40000100 }, + { 0x21, 0x40000100 }, + { 0x22, 0x40000100 }, + { 0x23, 0x40000100 }, + {} +}; + +static const struct hda_pintbl d965_5st_pin_configs[] = { + { 0x0a, 0x02214020 }, + { 0x0b, 0x02a19080 }, + { 0x0c, 0x0181304e }, + { 0x0d, 0x01014010 }, + { 0x0e, 0x01a19040 }, + { 0x0f, 0x01011012 }, + { 0x10, 0x01016011 }, + { 0x11, 0x40000100 }, + { 0x12, 0x40000100 }, + { 0x13, 0x40000100 }, + { 0x14, 0x40000100 }, + { 0x21, 0x01442070 }, + { 0x22, 0x40000100 }, + { 0x23, 0x40000100 }, + {} +}; + +static const struct hda_pintbl d965_5st_no_fp_pin_configs[] = { + { 0x0a, 0x40000100 }, + { 0x0b, 0x40000100 }, + { 0x0c, 0x0181304e }, + { 0x0d, 0x01014010 }, + { 0x0e, 0x01a19040 }, + { 0x0f, 0x01011012 }, + { 0x10, 0x01016011 }, + { 0x11, 0x40000100 }, + { 0x12, 0x40000100 }, + { 0x13, 0x40000100 }, + { 0x14, 0x40000100 }, + { 0x21, 0x01442070 }, + { 0x22, 0x40000100 }, + { 0x23, 0x40000100 }, + {} +}; + +static const struct hda_pintbl dell_3st_pin_configs[] = { + { 0x0a, 0x02211230 }, + { 0x0b, 0x02a11220 }, + { 0x0c, 0x01a19040 }, + { 0x0d, 0x01114210 }, + { 0x0e, 0x01111212 }, + { 0x0f, 0x01116211 }, + { 0x10, 0x01813050 }, + { 0x11, 0x01112214 }, + { 0x12, 0x403003fa }, + { 0x13, 0x90a60040 }, + { 0x14, 0x90a60040 }, + { 0x21, 0x404003fb }, + { 0x22, 0x40c003fc }, + { 0x23, 0x40000100 }, + {} +}; + +static void stac927x_fixup_ref_no_jd(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + /* no jack detecion for ref-no-jd model */ + if (action == HDA_FIXUP_ACT_PRE_PROBE) + codec->no_jack_detect = 1; +} + +static void stac927x_fixup_ref(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + snd_hda_apply_pincfgs(codec, ref927x_pin_configs); + spec->eapd_mask = spec->gpio_mask = 0; + spec->gpio_dir = spec->gpio_data = 0; + } +} + +static void stac927x_fixup_dell_dmic(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + if (codec->core.subsystem_id != 0x1028022f) { + /* GPIO2 High = Enable EAPD */ + spec->eapd_mask = spec->gpio_mask = 0x04; + spec->gpio_dir = spec->gpio_data = 0x04; + } + + snd_hda_add_verbs(codec, dell_3st_core_init); + spec->volknob_init = 1; +} + +static void stac927x_fixup_volknob(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + snd_hda_add_verbs(codec, stac927x_volknob_core_init); + spec->volknob_init = 1; + } +} + +static const struct hda_fixup stac927x_fixups[] = { + [STAC_D965_REF_NO_JD] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac927x_fixup_ref_no_jd, + .chained = true, + .chain_id = STAC_D965_REF, + }, + [STAC_D965_REF] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac927x_fixup_ref, + }, + [STAC_D965_3ST] = { + .type = HDA_FIXUP_PINS, + .v.pins = d965_3st_pin_configs, + .chained = true, + .chain_id = STAC_D965_VERBS, + }, + [STAC_D965_5ST] = { + .type = HDA_FIXUP_PINS, + .v.pins = d965_5st_pin_configs, + .chained = true, + .chain_id = STAC_D965_VERBS, + }, + [STAC_D965_VERBS] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = d965_core_init, + }, + [STAC_D965_5ST_NO_FP] = { + .type = HDA_FIXUP_PINS, + .v.pins = d965_5st_no_fp_pin_configs, + }, + [STAC_NEMO_DEFAULT] = { + .type = HDA_FIXUP_PINS, + .v.pins = nemo_pin_configs, + }, + [STAC_DELL_3ST] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell_3st_pin_configs, + .chained = true, + .chain_id = STAC_927X_DELL_DMIC, + }, + [STAC_DELL_BIOS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + /* correct the front output jack as a hp out */ + { 0x0f, 0x0221101f }, + /* correct the front input jack as a mic */ + { 0x0e, 0x02a79130 }, + {} + }, + .chained = true, + .chain_id = STAC_927X_DELL_DMIC, + }, + [STAC_DELL_BIOS_AMIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + /* configure the analog microphone on some laptops */ + { 0x0c, 0x90a79130 }, + {} + }, + .chained = true, + .chain_id = STAC_DELL_BIOS, + }, + [STAC_DELL_BIOS_SPDIF] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + /* correct the device field to SPDIF out */ + { 0x21, 0x01442070 }, + {} + }, + .chained = true, + .chain_id = STAC_DELL_BIOS, + }, + [STAC_927X_DELL_DMIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac927x_fixup_dell_dmic, + }, + [STAC_927X_VOLKNOB] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac927x_fixup_volknob, + }, +}; + +static const struct hda_model_fixup stac927x_models[] = { + { .id = STAC_D965_REF_NO_JD, .name = "ref-no-jd" }, + { .id = STAC_D965_REF, .name = "ref" }, + { .id = STAC_D965_3ST, .name = "3stack" }, + { .id = STAC_D965_5ST, .name = "5stack" }, + { .id = STAC_D965_5ST_NO_FP, .name = "5stack-no-fp" }, + { .id = STAC_DELL_3ST, .name = "dell-3stack" }, + { .id = STAC_DELL_BIOS, .name = "dell-bios" }, + { .id = STAC_NEMO_DEFAULT, .name = "nemo-default" }, + { .id = STAC_DELL_BIOS_AMIC, .name = "dell-bios-amic" }, + { .id = STAC_927X_VOLKNOB, .name = "volknob" }, + {} +}; + +static const struct snd_pci_quirk stac927x_fixup_tbl[] = { + /* SigmaTel reference board */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, + "DFI LanParty", STAC_D965_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101, + "DFI LanParty", STAC_D965_REF), + /* Intel 946 based systems */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x3d01, "Intel D946", STAC_D965_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0xa301, "Intel D946", STAC_D965_3ST), + /* 965 based 3 stack systems */ + SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_INTEL, 0xff00, 0x2100, + "Intel D965", STAC_D965_3ST), + SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_INTEL, 0xff00, 0x2000, + "Intel D965", STAC_D965_3ST), + /* Dell 3 stack systems */ + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01dd, "Dell Dimension E520", STAC_DELL_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ed, "Dell ", STAC_DELL_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f4, "Dell ", STAC_DELL_3ST), + /* Dell 3 stack systems with verb table in BIOS */ + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f3, "Dell Inspiron 1420", STAC_DELL_BIOS), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f7, "Dell XPS M1730", STAC_DELL_BIOS), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0227, "Dell Vostro 1400 ", STAC_DELL_BIOS), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x022e, "Dell ", STAC_DELL_BIOS_SPDIF), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x022f, "Dell Inspiron 1525", STAC_DELL_BIOS), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0242, "Dell ", STAC_DELL_BIOS), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0243, "Dell ", STAC_DELL_BIOS), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02ff, "Dell ", STAC_DELL_BIOS), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0209, "Dell XPS 1330", STAC_DELL_BIOS_SPDIF), + /* 965 based 5 stack systems */ + SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_INTEL, 0xff00, 0x2300, + "Intel D965", STAC_D965_5ST), + SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_INTEL, 0xff00, 0x2500, + "Intel D965", STAC_D965_5ST), + /* Nemo */ + SND_PCI_QUIRK(0x1888, 0x1000, "AmigaOne X1000", STAC_NEMO_DEFAULT), + /* volume-knob fixes */ + SND_PCI_QUIRK_VENDOR(0x10cf, "FSC", STAC_927X_VOLKNOB), + {} /* terminator */ +}; + +static const struct hda_pintbl ref9205_pin_configs[] = { + { 0x0a, 0x40000100 }, + { 0x0b, 0x40000100 }, + { 0x0c, 0x01016011 }, + { 0x0d, 0x01014010 }, + { 0x0e, 0x01813122 }, + { 0x0f, 0x01a19021 }, + { 0x14, 0x01019020 }, + { 0x16, 0x40000100 }, + { 0x17, 0x90a000f0 }, + { 0x18, 0x90a000f0 }, + { 0x21, 0x01441030 }, + { 0x22, 0x01c41030 }, + {} +}; + +/* + STAC 9205 pin configs for + 102801F1 + 102801F2 + 102801FC + 102801FD + 10280204 + 1028021F + 10280228 (Dell Vostro 1500) + 10280229 (Dell Vostro 1700) +*/ +static const struct hda_pintbl dell_9205_m42_pin_configs[] = { + { 0x0a, 0x0321101F }, + { 0x0b, 0x03A11020 }, + { 0x0c, 0x400003FA }, + { 0x0d, 0x90170310 }, + { 0x0e, 0x400003FB }, + { 0x0f, 0x400003FC }, + { 0x14, 0x400003FD }, + { 0x16, 0x40F000F9 }, + { 0x17, 0x90A60330 }, + { 0x18, 0x400003FF }, + { 0x21, 0x0144131F }, + { 0x22, 0x40C003FE }, + {} +}; + +/* + STAC 9205 pin configs for + 102801F9 + 102801FA + 102801FE + 102801FF (Dell Precision M4300) + 10280206 + 10280200 + 10280201 +*/ +static const struct hda_pintbl dell_9205_m43_pin_configs[] = { + { 0x0a, 0x0321101f }, + { 0x0b, 0x03a11020 }, + { 0x0c, 0x90a70330 }, + { 0x0d, 0x90170310 }, + { 0x0e, 0x400000fe }, + { 0x0f, 0x400000ff }, + { 0x14, 0x400000fd }, + { 0x16, 0x40f000f9 }, + { 0x17, 0x400000fa }, + { 0x18, 0x400000fc }, + { 0x21, 0x0144131f }, + { 0x22, 0x40c003f8 }, + /* Enable SPDIF in/out */ + { 0x1f, 0x01441030 }, + { 0x20, 0x1c410030 }, + {} +}; + +static const struct hda_pintbl dell_9205_m44_pin_configs[] = { + { 0x0a, 0x0421101f }, + { 0x0b, 0x04a11020 }, + { 0x0c, 0x400003fa }, + { 0x0d, 0x90170310 }, + { 0x0e, 0x400003fb }, + { 0x0f, 0x400003fc }, + { 0x14, 0x400003fd }, + { 0x16, 0x400003f9 }, + { 0x17, 0x90a60330 }, + { 0x18, 0x400003ff }, + { 0x21, 0x01441340 }, + { 0x22, 0x40c003fe }, + {} +}; + +static void stac9205_fixup_ref(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + snd_hda_apply_pincfgs(codec, ref9205_pin_configs); + /* SPDIF-In enabled */ + spec->eapd_mask = spec->gpio_mask = spec->gpio_dir = 0; + } +} + +static void stac9205_fixup_dell_m43(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + struct hda_jack_callback *jack; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + snd_hda_apply_pincfgs(codec, dell_9205_m43_pin_configs); + + /* Enable unsol response for GPIO4/Dock HP connection */ + snd_hda_codec_write_cache(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK, 0x10); + jack = snd_hda_jack_detect_enable_callback(codec, codec->core.afg, + stac_vref_event); + if (!IS_ERR(jack)) + jack->private_data = 0x01; + + spec->gpio_dir = 0x0b; + spec->eapd_mask = 0x01; + spec->gpio_mask = 0x1b; + spec->gpio_mute = 0x10; + /* GPIO0 High = EAPD, GPIO1 Low = Headphone Mute, + * GPIO3 Low = DRM + */ + spec->gpio_data = 0x01; + } +} + +static void stac9205_fixup_eapd(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) + spec->eapd_switch = 0; +} + +static const struct hda_fixup stac9205_fixups[] = { + [STAC_9205_REF] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac9205_fixup_ref, + }, + [STAC_9205_DELL_M42] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell_9205_m42_pin_configs, + }, + [STAC_9205_DELL_M43] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac9205_fixup_dell_m43, + }, + [STAC_9205_DELL_M44] = { + .type = HDA_FIXUP_PINS, + .v.pins = dell_9205_m44_pin_configs, + }, + [STAC_9205_EAPD] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac9205_fixup_eapd, + }, + {} +}; + +static const struct hda_model_fixup stac9205_models[] = { + { .id = STAC_9205_REF, .name = "ref" }, + { .id = STAC_9205_DELL_M42, .name = "dell-m42" }, + { .id = STAC_9205_DELL_M43, .name = "dell-m43" }, + { .id = STAC_9205_DELL_M44, .name = "dell-m44" }, + { .id = STAC_9205_EAPD, .name = "eapd" }, + {} +}; + +static const struct snd_pci_quirk stac9205_fixup_tbl[] = { + /* SigmaTel reference board */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, + "DFI LanParty", STAC_9205_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0xfb30, + "SigmaTel", STAC_9205_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101, + "DFI LanParty", STAC_9205_REF), + /* Dell */ + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f1, + "unknown Dell", STAC_9205_DELL_M42), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f2, + "unknown Dell", STAC_9205_DELL_M42), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f8, + "Dell Precision", STAC_9205_DELL_M43), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f9, + "Dell Precision", STAC_9205_DELL_M43), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01fa, + "Dell Precision", STAC_9205_DELL_M43), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01fc, + "unknown Dell", STAC_9205_DELL_M42), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01fd, + "unknown Dell", STAC_9205_DELL_M42), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01fe, + "Dell Precision", STAC_9205_DELL_M43), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ff, + "Dell Precision M4300", STAC_9205_DELL_M43), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0204, + "unknown Dell", STAC_9205_DELL_M42), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0206, + "Dell Precision", STAC_9205_DELL_M43), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x021b, + "Dell Precision", STAC_9205_DELL_M43), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x021c, + "Dell Precision", STAC_9205_DELL_M43), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x021f, + "Dell Inspiron", STAC_9205_DELL_M44), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0228, + "Dell Vostro 1500", STAC_9205_DELL_M42), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0229, + "Dell Vostro 1700", STAC_9205_DELL_M42), + /* Gateway */ + SND_PCI_QUIRK(0x107b, 0x0560, "Gateway T6834c", STAC_9205_EAPD), + SND_PCI_QUIRK(0x107b, 0x0565, "Gateway T1616", STAC_9205_EAPD), + {} /* terminator */ +}; + +static void stac92hd95_fixup_hp_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + if (find_mute_led_cfg(codec, spec->default_polarity)) + codec_dbg(codec, "mute LED gpio %d polarity %d\n", + spec->gpio_led, + spec->gpio_led_polarity); +} + +static const struct hda_fixup stac92hd95_fixups[] = { + [STAC_92HD95_HP_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd95_fixup_hp_led, + }, + [STAC_92HD95_HP_BASS] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + {0x1a, 0x795, 0x00}, /* HPF to 100Hz */ + {} + }, + .chained = true, + .chain_id = STAC_92HD95_HP_LED, + }, +}; + +static const struct snd_pci_quirk stac92hd95_fixup_tbl[] = { + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1911, "HP Spectre 13", STAC_92HD95_HP_BASS), + {} /* terminator */ +}; + +static const struct hda_model_fixup stac92hd95_models[] = { + { .id = STAC_92HD95_HP_LED, .name = "hp-led" }, + { .id = STAC_92HD95_HP_BASS, .name = "hp-bass" }, + {} +}; + + +static int stac_parse_auto_config(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + int err; + int flags = 0; + + if (spec->headset_jack) + flags |= HDA_PINCFG_HEADSET_MIC; + + err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, NULL, flags); + if (err < 0) + return err; + + /* add hooks */ + spec->gen.pcm_playback_hook = stac_playback_pcm_hook; + spec->gen.pcm_capture_hook = stac_capture_pcm_hook; + + spec->gen.automute_hook = stac_update_outputs; + + if (spec->gpio_led) + snd_hda_gen_add_mute_led_cdev(codec, stac_vmaster_hook); + + err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg); + if (err < 0) + return err; + + if (spec->vref_mute_led_nid) { + err = snd_hda_gen_fix_pin_power(codec, spec->vref_mute_led_nid); + if (err < 0) + return err; + } + + /* setup analog beep controls */ + if (spec->anabeep_nid > 0) { + err = stac_auto_create_beep_ctls(codec, + spec->anabeep_nid); + if (err < 0) + return err; + } + + /* setup digital beep controls and input device */ +#ifdef CONFIG_SND_HDA_INPUT_BEEP + if (spec->gen.beep_nid) { + hda_nid_t nid = spec->gen.beep_nid; + unsigned int caps; + + err = stac_auto_create_beep_ctls(codec, nid); + if (err < 0) + return err; + if (codec->beep) { + /* IDT/STAC codecs have linear beep tone parameter */ + codec->beep->linear_tone = spec->linear_tone_beep; + /* keep power up while beep is enabled */ + codec->beep->keep_power_at_enable = 1; + /* if no beep switch is available, make its own one */ + caps = query_amp_caps(codec, nid, HDA_OUTPUT); + if (!(caps & AC_AMPCAP_MUTE)) { + err = stac_beep_switch_ctl(codec); + if (err < 0) + return err; + } + } + } +#endif + + if (spec->aloopback_ctl && + snd_hda_get_bool_hint(codec, "loopback") == 1) { + unsigned int wr_verb = + spec->aloopback_ctl->private_value >> 16; + if (snd_hdac_regmap_add_vendor_verb(&codec->core, wr_verb)) + return -ENOMEM; + if (!snd_hda_gen_add_kctl(&spec->gen, NULL, spec->aloopback_ctl)) + return -ENOMEM; + } + + if (spec->have_spdif_mux) { + err = stac_create_spdif_mux_ctls(codec); + if (err < 0) + return err; + } + + stac_init_power_map(codec); + + return 0; +} + +static int stac_init(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + int i; + + /* override some hints */ + stac_store_hints(codec); + + /* set up GPIO */ + /* turn on EAPD statically when spec->eapd_switch isn't set. + * otherwise, unsol event will turn it on/off dynamically + */ + if (!spec->eapd_switch) + spec->gpio_data |= spec->eapd_mask; + stac_gpio_set(codec, spec->gpio_mask, spec->gpio_dir, spec->gpio_data); + + snd_hda_gen_init(codec); + + /* sync the power-map */ + if (spec->num_pwrs) + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_IDT_SET_POWER_MAP, + spec->power_map_bits); + + /* power down inactive ADCs */ + if (spec->powerdown_adcs) { + for (i = 0; i < spec->gen.num_all_adcs; i++) { + if (spec->active_adcs & (1 << i)) + continue; + snd_hda_codec_write(codec, spec->gen.all_adcs[i], 0, + AC_VERB_SET_POWER_STATE, + AC_PWRST_D3); + } + } + + return 0; +} + +#define stac_free snd_hda_gen_free + +#ifdef CONFIG_SND_PROC_FS +static void stac92hd_proc_hook(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid) +{ + if (nid == codec->core.afg) + snd_iprintf(buffer, "Power-Map: 0x%02x\n", + snd_hda_codec_read(codec, nid, 0, + AC_VERB_IDT_GET_POWER_MAP, 0)); +} + +static void analog_loop_proc_hook(struct snd_info_buffer *buffer, + struct hda_codec *codec, + unsigned int verb) +{ + snd_iprintf(buffer, "Analog Loopback: 0x%02x\n", + snd_hda_codec_read(codec, codec->core.afg, 0, verb, 0)); +} + +/* stac92hd71bxx, stac92hd73xx */ +static void stac92hd7x_proc_hook(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid) +{ + stac92hd_proc_hook(buffer, codec, nid); + if (nid == codec->core.afg) + analog_loop_proc_hook(buffer, codec, 0xfa0); +} + +static void stac9205_proc_hook(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid) +{ + if (nid == codec->core.afg) + analog_loop_proc_hook(buffer, codec, 0xfe0); +} + +static void stac927x_proc_hook(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid) +{ + if (nid == codec->core.afg) + analog_loop_proc_hook(buffer, codec, 0xfeb); +} +#else +#define stac92hd_proc_hook NULL +#define stac92hd7x_proc_hook NULL +#define stac9205_proc_hook NULL +#define stac927x_proc_hook NULL +#endif + +#ifdef CONFIG_PM +static int stac_suspend(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + + snd_hda_shutup_pins(codec); + + if (spec->eapd_mask) + stac_gpio_set(codec, spec->gpio_mask, + spec->gpio_dir, spec->gpio_data & + ~spec->eapd_mask); + + return 0; +} +#else +#define stac_suspend NULL +#endif /* CONFIG_PM */ + +static const struct hda_codec_ops stac_patch_ops = { + .build_controls = snd_hda_gen_build_controls, + .build_pcms = snd_hda_gen_build_pcms, + .init = stac_init, + .free = stac_free, + .unsol_event = snd_hda_jack_unsol_event, +#ifdef CONFIG_PM + .suspend = stac_suspend, +#endif +}; + +static int alloc_stac_spec(struct hda_codec *codec) +{ + struct sigmatel_spec *spec; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return -ENOMEM; + snd_hda_gen_spec_init(&spec->gen); + codec->spec = spec; + codec->no_trigger_sense = 1; /* seems common with STAC/IDT codecs */ + spec->gen.dac_min_mute = true; + codec->patch_ops = stac_patch_ops; + return 0; +} + +static int patch_stac9200(struct hda_codec *codec) +{ + struct sigmatel_spec *spec; + int err; + + err = alloc_stac_spec(codec); + if (err < 0) + return err; + + spec = codec->spec; + spec->linear_tone_beep = 1; + spec->gen.own_eapd_ctl = 1; + + codec->power_filter = snd_hda_codec_eapd_power_filter; + + snd_hda_add_verbs(codec, stac9200_eapd_init); + + snd_hda_pick_fixup(codec, stac9200_models, stac9200_fixup_tbl, + stac9200_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + err = stac_parse_auto_config(codec); + if (err < 0) { + stac_free(codec); + return err; + } + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; +} + +static int patch_stac925x(struct hda_codec *codec) +{ + struct sigmatel_spec *spec; + int err; + + err = alloc_stac_spec(codec); + if (err < 0) + return err; + + spec = codec->spec; + spec->linear_tone_beep = 1; + spec->gen.own_eapd_ctl = 1; + + snd_hda_add_verbs(codec, stac925x_core_init); + + snd_hda_pick_fixup(codec, stac925x_models, stac925x_fixup_tbl, + stac925x_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + err = stac_parse_auto_config(codec); + if (err < 0) { + stac_free(codec); + return err; + } + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; +} + +static int patch_stac92hd73xx(struct hda_codec *codec) +{ + struct sigmatel_spec *spec; + int err; + int num_dacs; + + err = alloc_stac_spec(codec); + if (err < 0) + return err; + + spec = codec->spec; + /* enable power_save_node only for new 92HD89xx chips, as it causes + * click noises on old 92HD73xx chips. + */ + if ((codec->core.vendor_id & 0xfffffff0) != 0x111d7670) + codec->power_save_node = 1; + spec->linear_tone_beep = 0; + spec->gen.mixer_nid = 0x1d; + spec->have_spdif_mux = 1; + + num_dacs = snd_hda_get_num_conns(codec, 0x0a) - 1; + if (num_dacs < 3 || num_dacs > 5) { + codec_warn(codec, + "Could not determine number of channels defaulting to DAC count\n"); + num_dacs = 5; + } + + switch (num_dacs) { + case 0x3: /* 6 Channel */ + spec->aloopback_ctl = &stac92hd73xx_6ch_loopback; + break; + case 0x4: /* 8 Channel */ + spec->aloopback_ctl = &stac92hd73xx_8ch_loopback; + break; + case 0x5: /* 10 Channel */ + spec->aloopback_ctl = &stac92hd73xx_10ch_loopback; + break; + } + + spec->aloopback_mask = 0x01; + spec->aloopback_shift = 8; + + spec->gen.beep_nid = 0x1c; /* digital beep */ + + /* GPIO0 High = Enable EAPD */ + spec->eapd_mask = spec->gpio_mask = spec->gpio_dir = 0x1; + spec->gpio_data = 0x01; + + spec->eapd_switch = 1; + + spec->num_pwrs = ARRAY_SIZE(stac92hd73xx_pwr_nids); + spec->pwr_nids = stac92hd73xx_pwr_nids; + + spec->gen.own_eapd_ctl = 1; + spec->gen.power_down_unused = 1; + + snd_hda_pick_fixup(codec, stac92hd73xx_models, stac92hd73xx_fixup_tbl, + stac92hd73xx_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + if (!spec->volknob_init) + snd_hda_add_verbs(codec, stac92hd73xx_core_init); + + err = stac_parse_auto_config(codec); + if (err < 0) { + stac_free(codec); + return err; + } + + /* Don't GPIO-mute speakers if there are no internal speakers, because + * the GPIO might be necessary for Headphone + */ + if (spec->eapd_switch && !has_builtin_speaker(codec)) + spec->eapd_switch = 0; + + codec->proc_widget_hook = stac92hd7x_proc_hook; + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; +} + +static void stac_setup_gpio(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + + spec->gpio_mask |= spec->eapd_mask; + if (spec->gpio_led) { + if (!spec->vref_mute_led_nid) { + spec->gpio_mask |= spec->gpio_led; + spec->gpio_dir |= spec->gpio_led; + spec->gpio_data |= spec->gpio_led; + } else { + codec->power_filter = stac_vref_led_power_filter; + } + } + + if (spec->mic_mute_led_gpio) { + spec->gpio_mask |= spec->mic_mute_led_gpio; + spec->gpio_dir |= spec->mic_mute_led_gpio; + spec->mic_enabled = 0; + spec->gpio_data |= spec->mic_mute_led_gpio; + snd_hda_gen_add_micmute_led_cdev(codec, stac_capture_led_update); + } +} + +static int patch_stac92hd83xxx(struct hda_codec *codec) +{ + struct sigmatel_spec *spec; + int err; + + err = alloc_stac_spec(codec); + if (err < 0) + return err; + + /* longer delay needed for D3 */ + codec->core.power_caps &= ~AC_PWRST_EPSS; + + spec = codec->spec; + codec->power_save_node = 1; + spec->linear_tone_beep = 0; + spec->gen.own_eapd_ctl = 1; + spec->gen.power_down_unused = 1; + spec->gen.mixer_nid = 0x1b; + + spec->gen.beep_nid = 0x21; /* digital beep */ + spec->pwr_nids = stac92hd83xxx_pwr_nids; + spec->num_pwrs = ARRAY_SIZE(stac92hd83xxx_pwr_nids); + spec->default_polarity = -1; /* no default cfg */ + + snd_hda_add_verbs(codec, stac92hd83xxx_core_init); + + snd_hda_pick_fixup(codec, stac92hd83xxx_models, stac92hd83xxx_fixup_tbl, + stac92hd83xxx_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + stac_setup_gpio(codec); + + err = stac_parse_auto_config(codec); + if (err < 0) { + stac_free(codec); + return err; + } + + codec->proc_widget_hook = stac92hd_proc_hook; + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; +} + +static const hda_nid_t stac92hd95_pwr_nids[] = { + 0x0a, 0x0b, 0x0c, 0x0d +}; + +static int patch_stac92hd95(struct hda_codec *codec) +{ + struct sigmatel_spec *spec; + int err; + + err = alloc_stac_spec(codec); + if (err < 0) + return err; + + /* longer delay needed for D3 */ + codec->core.power_caps &= ~AC_PWRST_EPSS; + + spec = codec->spec; + codec->power_save_node = 1; + spec->linear_tone_beep = 0; + spec->gen.own_eapd_ctl = 1; + spec->gen.power_down_unused = 1; + + spec->gen.beep_nid = 0x19; /* digital beep */ + spec->pwr_nids = stac92hd95_pwr_nids; + spec->num_pwrs = ARRAY_SIZE(stac92hd95_pwr_nids); + spec->default_polarity = 0; + + snd_hda_pick_fixup(codec, stac92hd95_models, stac92hd95_fixup_tbl, + stac92hd95_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + stac_setup_gpio(codec); + + err = stac_parse_auto_config(codec); + if (err < 0) { + stac_free(codec); + return err; + } + + codec->proc_widget_hook = stac92hd_proc_hook; + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; +} + +static int patch_stac92hd71bxx(struct hda_codec *codec) +{ + struct sigmatel_spec *spec; + const hda_nid_t *unmute_nids = stac92hd71bxx_unmute_nids; + int err; + + err = alloc_stac_spec(codec); + if (err < 0) + return err; + + spec = codec->spec; + /* disabled power_save_node since it causes noises on a Dell machine */ + /* codec->power_save_node = 1; */ + spec->linear_tone_beep = 0; + spec->gen.own_eapd_ctl = 1; + spec->gen.power_down_unused = 1; + spec->gen.mixer_nid = 0x17; + spec->have_spdif_mux = 1; + + /* GPIO0 = EAPD */ + spec->gpio_mask = 0x01; + spec->gpio_dir = 0x01; + spec->gpio_data = 0x01; + + switch (codec->core.vendor_id) { + case 0x111d76b6: /* 4 Port without Analog Mixer */ + case 0x111d76b7: + unmute_nids++; + break; + case 0x111d7608: /* 5 Port with Analog Mixer */ + if ((codec->core.revision_id & 0xf) == 0 || + (codec->core.revision_id & 0xf) == 1) + spec->stream_delay = 40; /* 40 milliseconds */ + + /* disable VSW */ + unmute_nids++; + snd_hda_codec_set_pincfg(codec, 0x0f, 0x40f000f0); + snd_hda_codec_set_pincfg(codec, 0x19, 0x40f000f3); + break; + case 0x111d7603: /* 6 Port with Analog Mixer */ + if ((codec->core.revision_id & 0xf) == 1) + spec->stream_delay = 40; /* 40 milliseconds */ + + break; + } + + if (get_wcaps_type(get_wcaps(codec, 0x28)) == AC_WID_VOL_KNB) + snd_hda_add_verbs(codec, stac92hd71bxx_core_init); + + if (get_wcaps(codec, 0xa) & AC_WCAP_IN_AMP) { + const hda_nid_t *p; + for (p = unmute_nids; *p; p++) + snd_hda_codec_amp_init_stereo(codec, *p, HDA_INPUT, 0, + 0xff, 0x00); + } + + spec->aloopback_ctl = &stac92hd71bxx_loopback; + spec->aloopback_mask = 0x50; + spec->aloopback_shift = 0; + + spec->powerdown_adcs = 1; + spec->gen.beep_nid = 0x26; /* digital beep */ + spec->num_pwrs = ARRAY_SIZE(stac92hd71bxx_pwr_nids); + spec->pwr_nids = stac92hd71bxx_pwr_nids; + + snd_hda_pick_fixup(codec, stac92hd71bxx_models, stac92hd71bxx_fixup_tbl, + stac92hd71bxx_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + stac_setup_gpio(codec); + + err = stac_parse_auto_config(codec); + if (err < 0) { + stac_free(codec); + return err; + } + + codec->proc_widget_hook = stac92hd7x_proc_hook; + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; +} + +static int patch_stac922x(struct hda_codec *codec) +{ + struct sigmatel_spec *spec; + int err; + + err = alloc_stac_spec(codec); + if (err < 0) + return err; + + spec = codec->spec; + spec->linear_tone_beep = 1; + spec->gen.own_eapd_ctl = 1; + + snd_hda_add_verbs(codec, stac922x_core_init); + + /* Fix Mux capture level; max to 2 */ + snd_hda_override_amp_caps(codec, 0x12, HDA_OUTPUT, + (0 << AC_AMPCAP_OFFSET_SHIFT) | + (2 << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x27 << AC_AMPCAP_STEP_SIZE_SHIFT) | + (0 << AC_AMPCAP_MUTE_SHIFT)); + + snd_hda_pick_fixup(codec, stac922x_models, stac922x_fixup_tbl, + stac922x_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + err = stac_parse_auto_config(codec); + if (err < 0) { + stac_free(codec); + return err; + } + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; +} + +static const char * const stac927x_spdif_labels[] = { + "Digital Playback", "ADAT", "Analog Mux 1", + "Analog Mux 2", "Analog Mux 3", NULL +}; + +static int patch_stac927x(struct hda_codec *codec) +{ + struct sigmatel_spec *spec; + int err; + + err = alloc_stac_spec(codec); + if (err < 0) + return err; + + spec = codec->spec; + spec->linear_tone_beep = 1; + spec->gen.own_eapd_ctl = 1; + spec->have_spdif_mux = 1; + spec->spdif_labels = stac927x_spdif_labels; + + spec->gen.beep_nid = 0x23; /* digital beep */ + + /* GPIO0 High = Enable EAPD */ + spec->eapd_mask = spec->gpio_mask = 0x01; + spec->gpio_dir = spec->gpio_data = 0x01; + + spec->aloopback_ctl = &stac927x_loopback; + spec->aloopback_mask = 0x40; + spec->aloopback_shift = 0; + spec->eapd_switch = 1; + + snd_hda_pick_fixup(codec, stac927x_models, stac927x_fixup_tbl, + stac927x_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + if (!spec->volknob_init) + snd_hda_add_verbs(codec, stac927x_core_init); + + err = stac_parse_auto_config(codec); + if (err < 0) { + stac_free(codec); + return err; + } + + codec->proc_widget_hook = stac927x_proc_hook; + + /* + * !!FIXME!! + * The STAC927x seem to require fairly long delays for certain + * command sequences. With too short delays (even if the answer + * is set to RIRB properly), it results in the silence output + * on some hardwares like Dell. + * + * The below flag enables the longer delay (see get_response + * in hda_intel.c). + */ + codec->bus->core.needs_damn_long_delay = 1; + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; +} + +static int patch_stac9205(struct hda_codec *codec) +{ + struct sigmatel_spec *spec; + int err; + + err = alloc_stac_spec(codec); + if (err < 0) + return err; + + spec = codec->spec; + spec->linear_tone_beep = 1; + spec->gen.own_eapd_ctl = 1; + spec->have_spdif_mux = 1; + + spec->gen.beep_nid = 0x23; /* digital beep */ + + snd_hda_add_verbs(codec, stac9205_core_init); + spec->aloopback_ctl = &stac9205_loopback; + + spec->aloopback_mask = 0x40; + spec->aloopback_shift = 0; + + /* GPIO0 High = EAPD */ + spec->eapd_mask = spec->gpio_mask = spec->gpio_dir = 0x1; + spec->gpio_data = 0x01; + + /* Turn on/off EAPD per HP plugging */ + spec->eapd_switch = 1; + + snd_hda_pick_fixup(codec, stac9205_models, stac9205_fixup_tbl, + stac9205_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + err = stac_parse_auto_config(codec); + if (err < 0) { + stac_free(codec); + return err; + } + + codec->proc_widget_hook = stac9205_proc_hook; + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; +} + +/* + * STAC9872 hack + */ + +static const struct hda_verb stac9872_core_init[] = { + {0x15, AC_VERB_SET_CONNECT_SEL, 0x1}, /* mic-sel: 0a,0d,14,02 */ + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, /* Mic-in -> 0x9 */ + {} +}; + +static const struct hda_pintbl stac9872_vaio_pin_configs[] = { + { 0x0a, 0x03211020 }, + { 0x0b, 0x411111f0 }, + { 0x0c, 0x411111f0 }, + { 0x0d, 0x03a15030 }, + { 0x0e, 0x411111f0 }, + { 0x0f, 0x90170110 }, + { 0x11, 0x411111f0 }, + { 0x13, 0x411111f0 }, + { 0x14, 0x90a7013e }, + {} +}; + +static const struct hda_model_fixup stac9872_models[] = { + { .id = STAC_9872_VAIO, .name = "vaio" }, + {} +}; + +static const struct hda_fixup stac9872_fixups[] = { + [STAC_9872_VAIO] = { + .type = HDA_FIXUP_PINS, + .v.pins = stac9872_vaio_pin_configs, + }, +}; + +static const struct snd_pci_quirk stac9872_fixup_tbl[] = { + SND_PCI_QUIRK_MASK(0x104d, 0xfff0, 0x81e0, + "Sony VAIO F/S", STAC_9872_VAIO), + {} /* terminator */ +}; + +static int patch_stac9872(struct hda_codec *codec) +{ + struct sigmatel_spec *spec; + int err; + + err = alloc_stac_spec(codec); + if (err < 0) + return err; + + spec = codec->spec; + spec->linear_tone_beep = 1; + spec->gen.own_eapd_ctl = 1; + + snd_hda_add_verbs(codec, stac9872_core_init); + + snd_hda_pick_fixup(codec, stac9872_models, stac9872_fixup_tbl, + stac9872_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + err = stac_parse_auto_config(codec); + if (err < 0) { + stac_free(codec); + return -EINVAL; + } + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; +} + + +/* + * patch entries + */ +static const struct hda_device_id snd_hda_id_sigmatel[] = { + HDA_CODEC_ENTRY(0x83847690, "STAC9200", patch_stac9200), + HDA_CODEC_ENTRY(0x83847882, "STAC9220 A1", patch_stac922x), + HDA_CODEC_ENTRY(0x83847680, "STAC9221 A1", patch_stac922x), + HDA_CODEC_ENTRY(0x83847880, "STAC9220 A2", patch_stac922x), + HDA_CODEC_ENTRY(0x83847681, "STAC9220D/9223D A2", patch_stac922x), + HDA_CODEC_ENTRY(0x83847682, "STAC9221 A2", patch_stac922x), + HDA_CODEC_ENTRY(0x83847683, "STAC9221D A2", patch_stac922x), + HDA_CODEC_ENTRY(0x83847618, "STAC9227", patch_stac927x), + HDA_CODEC_ENTRY(0x83847619, "STAC9227", patch_stac927x), + HDA_CODEC_ENTRY(0x83847638, "STAC92HD700", patch_stac927x), + HDA_CODEC_ENTRY(0x83847616, "STAC9228", patch_stac927x), + HDA_CODEC_ENTRY(0x83847617, "STAC9228", patch_stac927x), + HDA_CODEC_ENTRY(0x83847614, "STAC9229", patch_stac927x), + HDA_CODEC_ENTRY(0x83847615, "STAC9229", patch_stac927x), + HDA_CODEC_ENTRY(0x83847620, "STAC9274", patch_stac927x), + HDA_CODEC_ENTRY(0x83847621, "STAC9274D", patch_stac927x), + HDA_CODEC_ENTRY(0x83847622, "STAC9273X", patch_stac927x), + HDA_CODEC_ENTRY(0x83847623, "STAC9273D", patch_stac927x), + HDA_CODEC_ENTRY(0x83847624, "STAC9272X", patch_stac927x), + HDA_CODEC_ENTRY(0x83847625, "STAC9272D", patch_stac927x), + HDA_CODEC_ENTRY(0x83847626, "STAC9271X", patch_stac927x), + HDA_CODEC_ENTRY(0x83847627, "STAC9271D", patch_stac927x), + HDA_CODEC_ENTRY(0x83847628, "STAC9274X5NH", patch_stac927x), + HDA_CODEC_ENTRY(0x83847629, "STAC9274D5NH", patch_stac927x), + HDA_CODEC_ENTRY(0x83847632, "STAC9202", patch_stac925x), + HDA_CODEC_ENTRY(0x83847633, "STAC9202D", patch_stac925x), + HDA_CODEC_ENTRY(0x83847634, "STAC9250", patch_stac925x), + HDA_CODEC_ENTRY(0x83847635, "STAC9250D", patch_stac925x), + HDA_CODEC_ENTRY(0x83847636, "STAC9251", patch_stac925x), + HDA_CODEC_ENTRY(0x83847637, "STAC9250D", patch_stac925x), + HDA_CODEC_ENTRY(0x83847645, "92HD206X", patch_stac927x), + HDA_CODEC_ENTRY(0x83847646, "92HD206D", patch_stac927x), + /* The following does not take into account .id=0x83847661 when subsys = + * 104D0C00 which is STAC9225s. Because of this, some SZ Notebooks are + * currently not fully supported. + */ + HDA_CODEC_ENTRY(0x83847661, "CXD9872RD/K", patch_stac9872), + HDA_CODEC_ENTRY(0x83847662, "STAC9872AK", patch_stac9872), + HDA_CODEC_ENTRY(0x83847664, "CXD9872AKD", patch_stac9872), + HDA_CODEC_ENTRY(0x83847698, "STAC9205", patch_stac9205), + HDA_CODEC_ENTRY(0x838476a0, "STAC9205", patch_stac9205), + HDA_CODEC_ENTRY(0x838476a1, "STAC9205D", patch_stac9205), + HDA_CODEC_ENTRY(0x838476a2, "STAC9204", patch_stac9205), + HDA_CODEC_ENTRY(0x838476a3, "STAC9204D", patch_stac9205), + HDA_CODEC_ENTRY(0x838476a4, "STAC9255", patch_stac9205), + HDA_CODEC_ENTRY(0x838476a5, "STAC9255D", patch_stac9205), + HDA_CODEC_ENTRY(0x838476a6, "STAC9254", patch_stac9205), + HDA_CODEC_ENTRY(0x838476a7, "STAC9254D", patch_stac9205), + HDA_CODEC_ENTRY(0x111d7603, "92HD75B3X5", patch_stac92hd71bxx), + HDA_CODEC_ENTRY(0x111d7604, "92HD83C1X5", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d76d4, "92HD83C1C5", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d7605, "92HD81B1X5", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d76d5, "92HD81B1C5", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d76d1, "92HD87B1/3", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d76d9, "92HD87B2/4", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d7666, "92HD88B3", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d7667, "92HD88B1", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d7668, "92HD88B2", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d7669, "92HD88B4", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d7608, "92HD75B2X5", patch_stac92hd71bxx), + HDA_CODEC_ENTRY(0x111d7674, "92HD73D1X5", patch_stac92hd73xx), + HDA_CODEC_ENTRY(0x111d7675, "92HD73C1X5", patch_stac92hd73xx), + HDA_CODEC_ENTRY(0x111d7676, "92HD73E1X5", patch_stac92hd73xx), + HDA_CODEC_ENTRY(0x111d7695, "92HD95", patch_stac92hd95), + HDA_CODEC_ENTRY(0x111d76b0, "92HD71B8X", patch_stac92hd71bxx), + HDA_CODEC_ENTRY(0x111d76b1, "92HD71B8X", patch_stac92hd71bxx), + HDA_CODEC_ENTRY(0x111d76b2, "92HD71B7X", patch_stac92hd71bxx), + HDA_CODEC_ENTRY(0x111d76b3, "92HD71B7X", patch_stac92hd71bxx), + HDA_CODEC_ENTRY(0x111d76b4, "92HD71B6X", patch_stac92hd71bxx), + HDA_CODEC_ENTRY(0x111d76b5, "92HD71B6X", patch_stac92hd71bxx), + HDA_CODEC_ENTRY(0x111d76b6, "92HD71B5X", patch_stac92hd71bxx), + HDA_CODEC_ENTRY(0x111d76b7, "92HD71B5X", patch_stac92hd71bxx), + HDA_CODEC_ENTRY(0x111d76c0, "92HD89C3", patch_stac92hd73xx), + HDA_CODEC_ENTRY(0x111d76c1, "92HD89C2", patch_stac92hd73xx), + HDA_CODEC_ENTRY(0x111d76c2, "92HD89C1", patch_stac92hd73xx), + HDA_CODEC_ENTRY(0x111d76c3, "92HD89B3", patch_stac92hd73xx), + HDA_CODEC_ENTRY(0x111d76c4, "92HD89B2", patch_stac92hd73xx), + HDA_CODEC_ENTRY(0x111d76c5, "92HD89B1", patch_stac92hd73xx), + HDA_CODEC_ENTRY(0x111d76c6, "92HD89E3", patch_stac92hd73xx), + HDA_CODEC_ENTRY(0x111d76c7, "92HD89E2", patch_stac92hd73xx), + HDA_CODEC_ENTRY(0x111d76c8, "92HD89E1", patch_stac92hd73xx), + HDA_CODEC_ENTRY(0x111d76c9, "92HD89D3", patch_stac92hd73xx), + HDA_CODEC_ENTRY(0x111d76ca, "92HD89D2", patch_stac92hd73xx), + HDA_CODEC_ENTRY(0x111d76cb, "92HD89D1", patch_stac92hd73xx), + HDA_CODEC_ENTRY(0x111d76cc, "92HD89F3", patch_stac92hd73xx), + HDA_CODEC_ENTRY(0x111d76cd, "92HD89F2", patch_stac92hd73xx), + HDA_CODEC_ENTRY(0x111d76ce, "92HD89F1", patch_stac92hd73xx), + HDA_CODEC_ENTRY(0x111d76df, "92HD93BXX", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d76e0, "92HD91BXX", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d76e3, "92HD98BXX", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d76e5, "92HD99BXX", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d76e7, "92HD90BXX", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d76e8, "92HD66B1X5", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d76e9, "92HD66B2X5", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d76ea, "92HD66B3X5", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d76eb, "92HD66C1X5", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d76ec, "92HD66C2X5", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d76ed, "92HD66C3X5", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d76ee, "92HD66B1X3", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d76ef, "92HD66B2X3", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d76f0, "92HD66B3X3", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d76f1, "92HD66C1X3", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d76f2, "92HD66C2X3", patch_stac92hd83xxx), + HDA_CODEC_ENTRY(0x111d76f3, "92HD66C3/65", patch_stac92hd83xxx), + {} /* terminator */ +}; +MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_sigmatel); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("IDT/Sigmatel HD-audio codec"); + +static struct hda_codec_driver sigmatel_driver = { + .id = snd_hda_id_sigmatel, +}; + +module_hda_codec_driver(sigmatel_driver); diff --git a/sound/pci/hda/patch_via.c b/sound/pci/hda/patch_via.c new file mode 100644 index 000000000..2994f85bc --- /dev/null +++ b/sound/pci/hda/patch_via.c @@ -0,0 +1,1253 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Universal Interface for Intel High Definition Audio Codec + * + * HD audio interface patch for VIA VT17xx/VT18xx/VT20xx codec + * + * (C) 2006-2009 VIA Technology, Inc. + * (C) 2006-2008 Takashi Iwai <tiwai@suse.de> + */ + +/* * * * * * * * * * * * * * Release History * * * * * * * * * * * * * * * * */ +/* */ +/* 2006-03-03 Lydia Wang Create the basic patch to support VT1708 codec */ +/* 2006-03-14 Lydia Wang Modify hard code for some pin widget nid */ +/* 2006-08-02 Lydia Wang Add support to VT1709 codec */ +/* 2006-09-08 Lydia Wang Fix internal loopback recording source select bug */ +/* 2007-09-12 Lydia Wang Add EAPD enable during driver initialization */ +/* 2007-09-17 Lydia Wang Add VT1708B codec support */ +/* 2007-11-14 Lydia Wang Add VT1708A codec HP and CD pin connect config */ +/* 2008-02-03 Lydia Wang Fix Rear channels and Back channels inverse issue */ +/* 2008-03-06 Lydia Wang Add VT1702 codec and VT1708S codec support */ +/* 2008-04-09 Lydia Wang Add mute front speaker when HP plugin */ +/* 2008-04-09 Lydia Wang Add Independent HP feature */ +/* 2008-05-28 Lydia Wang Add second S/PDIF Out support for VT1702 */ +/* 2008-09-15 Logan Li Add VT1708S Mic Boost workaround/backdoor */ +/* 2009-02-16 Logan Li Add support for VT1718S */ +/* 2009-03-13 Logan Li Add support for VT1716S */ +/* 2009-04-14 Lydai Wang Add support for VT1828S and VT2020 */ +/* 2009-07-08 Lydia Wang Add support for VT2002P */ +/* 2009-07-21 Lydia Wang Add support for VT1812 */ +/* 2009-09-19 Lydia Wang Add support for VT1818S */ +/* */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/asoundef.h> +#include <sound/hda_codec.h> +#include "hda_local.h" +#include "hda_auto_parser.h" +#include "hda_jack.h" +#include "hda_generic.h" + +/* Pin Widget NID */ +#define VT1708_HP_PIN_NID 0x20 +#define VT1708_CD_PIN_NID 0x24 + +enum VIA_HDA_CODEC { + UNKNOWN = -1, + VT1708, + VT1709_10CH, + VT1709_6CH, + VT1708B_8CH, + VT1708B_4CH, + VT1708S, + VT1708BCE, + VT1702, + VT1718S, + VT1716S, + VT2002P, + VT1812, + VT1802, + VT1705CF, + VT1808, + CODEC_TYPES, +}; + +#define VT2002P_COMPATIBLE(spec) \ + ((spec)->codec_type == VT2002P ||\ + (spec)->codec_type == VT1812 ||\ + (spec)->codec_type == VT1802) + +struct via_spec { + struct hda_gen_spec gen; + + /* HP mode source */ + unsigned int dmic_enabled; + enum VIA_HDA_CODEC codec_type; + + /* analog low-power control */ + bool alc_mode; + + /* work to check hp jack state */ + int hp_work_active; + int vt1708_jack_detect; +}; + +static enum VIA_HDA_CODEC get_codec_type(struct hda_codec *codec); +static void via_playback_pcm_hook(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream, + int action); + +static const struct hda_codec_ops via_patch_ops; /* defined below */ + +static struct via_spec *via_new_spec(struct hda_codec *codec) +{ + struct via_spec *spec; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return NULL; + + codec->spec = spec; + snd_hda_gen_spec_init(&spec->gen); + spec->codec_type = get_codec_type(codec); + /* VT1708BCE & VT1708S are almost same */ + if (spec->codec_type == VT1708BCE) + spec->codec_type = VT1708S; + spec->gen.indep_hp = 1; + spec->gen.keep_eapd_on = 1; + spec->gen.dac_min_mute = 1; + spec->gen.pcm_playback_hook = via_playback_pcm_hook; + spec->gen.add_stereo_mix_input = HDA_HINT_STEREO_MIX_AUTO; + codec->power_save_node = 1; + spec->gen.power_down_unused = 1; + codec->patch_ops = via_patch_ops; + return spec; +} + +static enum VIA_HDA_CODEC get_codec_type(struct hda_codec *codec) +{ + u32 vendor_id = codec->core.vendor_id; + u16 ven_id = vendor_id >> 16; + u16 dev_id = vendor_id & 0xffff; + enum VIA_HDA_CODEC codec_type; + + /* get codec type */ + if (ven_id != 0x1106) + codec_type = UNKNOWN; + else if (dev_id >= 0x1708 && dev_id <= 0x170b) + codec_type = VT1708; + else if (dev_id >= 0xe710 && dev_id <= 0xe713) + codec_type = VT1709_10CH; + else if (dev_id >= 0xe714 && dev_id <= 0xe717) + codec_type = VT1709_6CH; + else if (dev_id >= 0xe720 && dev_id <= 0xe723) { + codec_type = VT1708B_8CH; + if (snd_hda_param_read(codec, 0x16, AC_PAR_CONNLIST_LEN) == 0x7) + codec_type = VT1708BCE; + } else if (dev_id >= 0xe724 && dev_id <= 0xe727) + codec_type = VT1708B_4CH; + else if ((dev_id & 0xfff) == 0x397 + && (dev_id >> 12) < 8) + codec_type = VT1708S; + else if ((dev_id & 0xfff) == 0x398 + && (dev_id >> 12) < 8) + codec_type = VT1702; + else if ((dev_id & 0xfff) == 0x428 + && (dev_id >> 12) < 8) + codec_type = VT1718S; + else if (dev_id == 0x0433 || dev_id == 0xa721) + codec_type = VT1716S; + else if (dev_id == 0x0441 || dev_id == 0x4441) + codec_type = VT1718S; + else if (dev_id == 0x0438 || dev_id == 0x4438) + codec_type = VT2002P; + else if (dev_id == 0x0448) + codec_type = VT1812; + else if (dev_id == 0x0440) + codec_type = VT1708S; + else if ((dev_id & 0xfff) == 0x446) + codec_type = VT1802; + else if (dev_id == 0x4760) + codec_type = VT1705CF; + else if (dev_id == 0x4761 || dev_id == 0x4762) + codec_type = VT1808; + else + codec_type = UNKNOWN; + return codec_type; +}; + +static void analog_low_current_mode(struct hda_codec *codec); +static bool is_aa_path_mute(struct hda_codec *codec); + +#define hp_detect_with_aa(codec) \ + (snd_hda_get_bool_hint(codec, "analog_loopback_hp_detect") == 1 && \ + !is_aa_path_mute(codec)) + +static void vt1708_stop_hp_work(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + if (spec->codec_type != VT1708 || !spec->gen.autocfg.hp_outs) + return; + if (spec->hp_work_active) { + snd_hda_codec_write(codec, 0x1, 0, 0xf81, 1); + codec->jackpoll_interval = 0; + cancel_delayed_work_sync(&codec->jackpoll_work); + spec->hp_work_active = false; + } +} + +static void vt1708_update_hp_work(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + if (spec->codec_type != VT1708 || !spec->gen.autocfg.hp_outs) + return; + if (spec->vt1708_jack_detect) { + if (!spec->hp_work_active) { + codec->jackpoll_interval = msecs_to_jiffies(100); + snd_hda_codec_write(codec, 0x1, 0, 0xf81, 0); + schedule_delayed_work(&codec->jackpoll_work, 0); + spec->hp_work_active = true; + } + } else if (!hp_detect_with_aa(codec)) + vt1708_stop_hp_work(codec); +} + +static int via_pin_power_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_hda_enum_bool_helper_info(kcontrol, uinfo); +} + +static int via_pin_power_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct via_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->gen.power_down_unused; + return 0; +} + +static int via_pin_power_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct via_spec *spec = codec->spec; + bool val = !!ucontrol->value.enumerated.item[0]; + + if (val == spec->gen.power_down_unused) + return 0; + /* codec->power_save_node = val; */ /* widget PM seems yet broken */ + spec->gen.power_down_unused = val; + analog_low_current_mode(codec); + return 1; +} + +static const struct snd_kcontrol_new via_pin_power_ctl_enum = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Dynamic Power-Control", + .info = via_pin_power_ctl_info, + .get = via_pin_power_ctl_get, + .put = via_pin_power_ctl_put, +}; + +#ifdef CONFIG_SND_HDA_INPUT_BEEP +/* additional beep mixers; the actual parameters are overwritten at build */ +static const struct snd_kcontrol_new via_beep_mixer[] = { + HDA_CODEC_VOLUME_MONO("Beep Playback Volume", 0, 1, 0, HDA_OUTPUT), + HDA_CODEC_MUTE_BEEP_MONO("Beep Playback Switch", 0, 1, 0, HDA_OUTPUT), +}; + +static int set_beep_amp(struct via_spec *spec, hda_nid_t nid, + int idx, int dir) +{ + struct snd_kcontrol_new *knew; + unsigned int beep_amp = HDA_COMPOSE_AMP_VAL(nid, 1, idx, dir); + int i; + + spec->gen.beep_nid = nid; + for (i = 0; i < ARRAY_SIZE(via_beep_mixer); i++) { + knew = snd_hda_gen_add_kctl(&spec->gen, NULL, + &via_beep_mixer[i]); + if (!knew) + return -ENOMEM; + knew->private_value = beep_amp; + } + return 0; +} + +static int auto_parse_beep(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + hda_nid_t nid; + + for_each_hda_codec_node(nid, codec) + if (get_wcaps_type(get_wcaps(codec, nid)) == AC_WID_BEEP) + return set_beep_amp(spec, nid, 0, HDA_OUTPUT); + return 0; +} +#else +#define auto_parse_beep(codec) 0 +#endif + +/* check AA path's mute status */ +static bool is_aa_path_mute(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + const struct hda_amp_list *p; + int ch, v; + + p = spec->gen.loopback.amplist; + if (!p) + return true; + for (; p->nid; p++) { + for (ch = 0; ch < 2; ch++) { + v = snd_hda_codec_amp_read(codec, p->nid, ch, p->dir, + p->idx); + if (!(v & HDA_AMP_MUTE) && v > 0) + return false; + } + } + return true; +} + +/* enter/exit analog low-current mode */ +static void __analog_low_current_mode(struct hda_codec *codec, bool force) +{ + struct via_spec *spec = codec->spec; + bool enable; + unsigned int verb, parm; + + if (!codec->power_save_node) + enable = false; + else + enable = is_aa_path_mute(codec) && !spec->gen.active_streams; + if (enable == spec->alc_mode && !force) + return; + spec->alc_mode = enable; + + /* decide low current mode's verb & parameter */ + switch (spec->codec_type) { + case VT1708B_8CH: + case VT1708B_4CH: + verb = 0xf70; + parm = enable ? 0x02 : 0x00; /* 0x02: 2/3x, 0x00: 1x */ + break; + case VT1708S: + case VT1718S: + case VT1716S: + verb = 0xf73; + parm = enable ? 0x51 : 0xe1; /* 0x51: 4/28x, 0xe1: 1x */ + break; + case VT1702: + verb = 0xf73; + parm = enable ? 0x01 : 0x1d; /* 0x01: 4/40x, 0x1d: 1x */ + break; + case VT2002P: + case VT1812: + case VT1802: + verb = 0xf93; + parm = enable ? 0x00 : 0xe0; /* 0x00: 4/40x, 0xe0: 1x */ + break; + case VT1705CF: + case VT1808: + verb = 0xf82; + parm = enable ? 0x00 : 0xe0; /* 0x00: 4/40x, 0xe0: 1x */ + break; + default: + return; /* other codecs are not supported */ + } + /* send verb */ + snd_hda_codec_write(codec, codec->core.afg, 0, verb, parm); +} + +static void analog_low_current_mode(struct hda_codec *codec) +{ + return __analog_low_current_mode(codec, false); +} + +static void via_playback_pcm_hook(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream, + int action) +{ + analog_low_current_mode(codec); + vt1708_update_hp_work(codec); +} + +static void via_free(struct hda_codec *codec) +{ + vt1708_stop_hp_work(codec); + snd_hda_gen_free(codec); +} + +#ifdef CONFIG_PM +static int via_suspend(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + vt1708_stop_hp_work(codec); + + /* Fix pop noise on headphones */ + if (spec->codec_type == VT1802) + snd_hda_shutup_pins(codec); + + return 0; +} + +static int via_resume(struct hda_codec *codec) +{ + /* some delay here to make jack detection working (bko#98921) */ + msleep(10); + codec->patch_ops.init(codec); + snd_hda_regmap_sync(codec); + return 0; +} +#endif + +#ifdef CONFIG_PM +static int via_check_power_status(struct hda_codec *codec, hda_nid_t nid) +{ + struct via_spec *spec = codec->spec; + analog_low_current_mode(codec); + vt1708_update_hp_work(codec); + return snd_hda_check_amp_list_power(codec, &spec->gen.loopback, nid); +} +#endif + +/* + */ + +static int via_init(struct hda_codec *codec); + +static const struct hda_codec_ops via_patch_ops = { + .build_controls = snd_hda_gen_build_controls, + .build_pcms = snd_hda_gen_build_pcms, + .init = via_init, + .free = via_free, + .unsol_event = snd_hda_jack_unsol_event, +#ifdef CONFIG_PM + .suspend = via_suspend, + .resume = via_resume, + .check_power_status = via_check_power_status, +#endif +}; + + +static const struct hda_verb vt1708_init_verbs[] = { + /* power down jack detect function */ + {0x1, 0xf81, 0x1}, + { } +}; +static void vt1708_set_pinconfig_connect(struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int def_conf; + unsigned char seqassoc; + + def_conf = snd_hda_codec_get_pincfg(codec, nid); + seqassoc = (unsigned char) get_defcfg_association(def_conf); + seqassoc = (seqassoc << 4) | get_defcfg_sequence(def_conf); + if (get_defcfg_connect(def_conf) == AC_JACK_PORT_NONE + && (seqassoc == 0xf0 || seqassoc == 0xff)) { + def_conf = def_conf & (~(AC_JACK_PORT_BOTH << 30)); + snd_hda_codec_set_pincfg(codec, nid, def_conf); + } +} + +static int vt1708_jack_detect_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct via_spec *spec = codec->spec; + + if (spec->codec_type != VT1708) + return 0; + ucontrol->value.integer.value[0] = spec->vt1708_jack_detect; + return 0; +} + +static int vt1708_jack_detect_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct via_spec *spec = codec->spec; + int val; + + if (spec->codec_type != VT1708) + return 0; + val = !!ucontrol->value.integer.value[0]; + if (spec->vt1708_jack_detect == val) + return 0; + spec->vt1708_jack_detect = val; + vt1708_update_hp_work(codec); + return 1; +} + +static const struct snd_kcontrol_new vt1708_jack_detect_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Jack Detect", + .count = 1, + .info = snd_ctl_boolean_mono_info, + .get = vt1708_jack_detect_get, + .put = vt1708_jack_detect_put, +}; + +static const struct badness_table via_main_out_badness = { + .no_primary_dac = 0x10000, + .no_dac = 0x4000, + .shared_primary = 0x10000, + .shared_surr = 0x20, + .shared_clfe = 0x20, + .shared_surr_main = 0x20, +}; +static const struct badness_table via_extra_out_badness = { + .no_primary_dac = 0x4000, + .no_dac = 0x4000, + .shared_primary = 0x12, + .shared_surr = 0x20, + .shared_clfe = 0x20, + .shared_surr_main = 0x10, +}; + +static int via_parse_auto_config(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + int err; + + spec->gen.main_out_badness = &via_main_out_badness; + spec->gen.extra_out_badness = &via_extra_out_badness; + + err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, NULL, 0); + if (err < 0) + return err; + + err = auto_parse_beep(codec); + if (err < 0) + return err; + + err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg); + if (err < 0) + return err; + + if (!snd_hda_gen_add_kctl(&spec->gen, NULL, &via_pin_power_ctl_enum)) + return -ENOMEM; + + /* disable widget PM at start for compatibility */ + codec->power_save_node = 0; + spec->gen.power_down_unused = 0; + return 0; +} + +static int via_init(struct hda_codec *codec) +{ + /* init power states */ + __analog_low_current_mode(codec, true); + + snd_hda_gen_init(codec); + + vt1708_update_hp_work(codec); + + return 0; +} + +static int vt1708_build_controls(struct hda_codec *codec) +{ + /* In order not to create "Phantom Jack" controls, + temporary enable jackpoll */ + int err; + int old_interval = codec->jackpoll_interval; + codec->jackpoll_interval = msecs_to_jiffies(100); + err = snd_hda_gen_build_controls(codec); + codec->jackpoll_interval = old_interval; + return err; +} + +static int vt1708_build_pcms(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + int i, err; + + err = snd_hda_gen_build_pcms(codec); + if (err < 0 || codec->core.vendor_id != 0x11061708) + return err; + + /* We got noisy outputs on the right channel on VT1708 when + * 24bit samples are used. Until any workaround is found, + * disable the 24bit format, so far. + */ + for (i = 0; i < ARRAY_SIZE(spec->gen.pcm_rec); i++) { + struct hda_pcm *info = spec->gen.pcm_rec[i]; + if (!info) + continue; + if (!info->stream[SNDRV_PCM_STREAM_PLAYBACK].substreams || + info->pcm_type != HDA_PCM_TYPE_AUDIO) + continue; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].formats = + SNDRV_PCM_FMTBIT_S16_LE; + } + + return 0; +} + +static int patch_vt1708(struct hda_codec *codec) +{ + struct via_spec *spec; + int err; + + /* create a codec specific record */ + spec = via_new_spec(codec); + if (spec == NULL) + return -ENOMEM; + + /* override some patch_ops */ + codec->patch_ops.build_controls = vt1708_build_controls; + codec->patch_ops.build_pcms = vt1708_build_pcms; + spec->gen.mixer_nid = 0x17; + + /* set jackpoll_interval while parsing the codec */ + codec->jackpoll_interval = msecs_to_jiffies(100); + spec->vt1708_jack_detect = 1; + + /* don't support the input jack switching due to lack of unsol event */ + /* (it may work with polling, though, but it needs testing) */ + spec->gen.suppress_auto_mic = 1; + /* Some machines show the broken speaker mute */ + spec->gen.auto_mute_via_amp = 1; + + /* Add HP and CD pin config connect bit re-config action */ + vt1708_set_pinconfig_connect(codec, VT1708_HP_PIN_NID); + vt1708_set_pinconfig_connect(codec, VT1708_CD_PIN_NID); + + err = snd_hda_add_verbs(codec, vt1708_init_verbs); + if (err < 0) + goto error; + + /* automatic parse from the BIOS config */ + err = via_parse_auto_config(codec); + if (err < 0) + goto error; + + /* add jack detect on/off control */ + if (!snd_hda_gen_add_kctl(&spec->gen, NULL, &vt1708_jack_detect_ctl)) { + err = -ENOMEM; + goto error; + } + + /* clear jackpoll_interval again; it's set dynamically */ + codec->jackpoll_interval = 0; + + return 0; + + error: + via_free(codec); + return err; +} + +static int patch_vt1709(struct hda_codec *codec) +{ + struct via_spec *spec; + int err; + + /* create a codec specific record */ + spec = via_new_spec(codec); + if (spec == NULL) + return -ENOMEM; + + spec->gen.mixer_nid = 0x18; + + err = via_parse_auto_config(codec); + if (err < 0) + goto error; + + return 0; + + error: + via_free(codec); + return err; +} + +static int patch_vt1708S(struct hda_codec *codec); +static int patch_vt1708B(struct hda_codec *codec) +{ + struct via_spec *spec; + int err; + + if (get_codec_type(codec) == VT1708BCE) + return patch_vt1708S(codec); + + /* create a codec specific record */ + spec = via_new_spec(codec); + if (spec == NULL) + return -ENOMEM; + + spec->gen.mixer_nid = 0x16; + + /* automatic parse from the BIOS config */ + err = via_parse_auto_config(codec); + if (err < 0) + goto error; + + return 0; + + error: + via_free(codec); + return err; +} + +/* Patch for VT1708S */ +static const struct hda_verb vt1708S_init_verbs[] = { + /* Enable Mic Boost Volume backdoor */ + {0x1, 0xf98, 0x1}, + /* don't bybass mixer */ + {0x1, 0xf88, 0xc0}, + { } +}; + +static void override_mic_boost(struct hda_codec *codec, hda_nid_t pin, + int offset, int num_steps, int step_size) +{ + snd_hda_override_wcaps(codec, pin, + get_wcaps(codec, pin) | AC_WCAP_IN_AMP); + snd_hda_override_amp_caps(codec, pin, HDA_INPUT, + (offset << AC_AMPCAP_OFFSET_SHIFT) | + (num_steps << AC_AMPCAP_NUM_STEPS_SHIFT) | + (step_size << AC_AMPCAP_STEP_SIZE_SHIFT) | + (0 << AC_AMPCAP_MUTE_SHIFT)); +} + +static int patch_vt1708S(struct hda_codec *codec) +{ + struct via_spec *spec; + int err; + + /* create a codec specific record */ + spec = via_new_spec(codec); + if (spec == NULL) + return -ENOMEM; + + spec->gen.mixer_nid = 0x16; + override_mic_boost(codec, 0x1a, 0, 3, 40); + override_mic_boost(codec, 0x1e, 0, 3, 40); + + /* correct names for VT1708BCE */ + if (get_codec_type(codec) == VT1708BCE) + snd_hda_codec_set_name(codec, "VT1708BCE"); + /* correct names for VT1705 */ + if (codec->core.vendor_id == 0x11064397) + snd_hda_codec_set_name(codec, "VT1705"); + + err = snd_hda_add_verbs(codec, vt1708S_init_verbs); + if (err < 0) + goto error; + + /* automatic parse from the BIOS config */ + err = via_parse_auto_config(codec); + if (err < 0) + goto error; + + return 0; + + error: + via_free(codec); + return err; +} + +/* Patch for VT1702 */ + +static const struct hda_verb vt1702_init_verbs[] = { + /* mixer enable */ + {0x1, 0xF88, 0x3}, + /* GPIO 0~2 */ + {0x1, 0xF82, 0x3F}, + { } +}; + +static int patch_vt1702(struct hda_codec *codec) +{ + struct via_spec *spec; + int err; + + /* create a codec specific record */ + spec = via_new_spec(codec); + if (spec == NULL) + return -ENOMEM; + + spec->gen.mixer_nid = 0x1a; + + /* limit AA path volume to 0 dB */ + snd_hda_override_amp_caps(codec, 0x1A, HDA_INPUT, + (0x17 << AC_AMPCAP_OFFSET_SHIFT) | + (0x17 << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x5 << AC_AMPCAP_STEP_SIZE_SHIFT) | + (1 << AC_AMPCAP_MUTE_SHIFT)); + + err = snd_hda_add_verbs(codec, vt1702_init_verbs); + if (err < 0) + goto error; + + /* automatic parse from the BIOS config */ + err = via_parse_auto_config(codec); + if (err < 0) + goto error; + + return 0; + + error: + via_free(codec); + return err; +} + +/* Patch for VT1718S */ + +static const struct hda_verb vt1718S_init_verbs[] = { + /* Enable MW0 adjust Gain 5 */ + {0x1, 0xfb2, 0x10}, + /* Enable Boost Volume backdoor */ + {0x1, 0xf88, 0x8}, + + { } +}; + +/* Add a connection to the primary DAC from AA-mixer for some codecs + * This isn't listed from the raw info, but the chip has a secret connection. + */ +static int add_secret_dac_path(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + int i, nums; + hda_nid_t conn[8]; + hda_nid_t nid; + + if (!spec->gen.mixer_nid) + return 0; + nums = snd_hda_get_connections(codec, spec->gen.mixer_nid, conn, + ARRAY_SIZE(conn) - 1); + if (nums < 0) + return nums; + + for (i = 0; i < nums; i++) { + if (get_wcaps_type(get_wcaps(codec, conn[i])) == AC_WID_AUD_OUT) + return 0; + } + + /* find the primary DAC and add to the connection list */ + for_each_hda_codec_node(nid, codec) { + unsigned int caps = get_wcaps(codec, nid); + if (get_wcaps_type(caps) == AC_WID_AUD_OUT && + !(caps & AC_WCAP_DIGITAL)) { + conn[nums++] = nid; + return snd_hda_override_conn_list(codec, + spec->gen.mixer_nid, + nums, conn); + } + } + return 0; +} + + +static int patch_vt1718S(struct hda_codec *codec) +{ + struct via_spec *spec; + int err; + + /* create a codec specific record */ + spec = via_new_spec(codec); + if (spec == NULL) + return -ENOMEM; + + spec->gen.mixer_nid = 0x21; + override_mic_boost(codec, 0x2b, 0, 3, 40); + override_mic_boost(codec, 0x29, 0, 3, 40); + add_secret_dac_path(codec); + + err = snd_hda_add_verbs(codec, vt1718S_init_verbs); + if (err < 0) + goto error; + + /* automatic parse from the BIOS config */ + err = via_parse_auto_config(codec); + if (err < 0) + goto error; + + return 0; + + error: + via_free(codec); + return err; +} + +/* Patch for VT1716S */ + +static int vt1716s_dmic_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int vt1716s_dmic_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + int index = 0; + + index = snd_hda_codec_read(codec, 0x26, 0, + AC_VERB_GET_CONNECT_SEL, 0); + if (index != -1) + *ucontrol->value.integer.value = index; + + return 0; +} + +static int vt1716s_dmic_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct via_spec *spec = codec->spec; + int index = *ucontrol->value.integer.value; + + snd_hda_codec_write(codec, 0x26, 0, + AC_VERB_SET_CONNECT_SEL, index); + spec->dmic_enabled = index; + return 1; +} + +static const struct snd_kcontrol_new vt1716s_dmic_mixer_vol = + HDA_CODEC_VOLUME("Digital Mic Capture Volume", 0x22, 0x0, HDA_INPUT); +static const struct snd_kcontrol_new vt1716s_dmic_mixer_sw = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Digital Mic Capture Switch", + .subdevice = HDA_SUBDEV_NID_FLAG | 0x26, + .count = 1, + .info = vt1716s_dmic_info, + .get = vt1716s_dmic_get, + .put = vt1716s_dmic_put, +}; + + +/* mono-out mixer elements */ +static const struct snd_kcontrol_new vt1716S_mono_out_mixer = + HDA_CODEC_MUTE("Mono Playback Switch", 0x2a, 0x0, HDA_OUTPUT); + +static const struct hda_verb vt1716S_init_verbs[] = { + /* Enable Boost Volume backdoor */ + {0x1, 0xf8a, 0x80}, + /* don't bybass mixer */ + {0x1, 0xf88, 0xc0}, + /* Enable mono output */ + {0x1, 0xf90, 0x08}, + { } +}; + +static int patch_vt1716S(struct hda_codec *codec) +{ + struct via_spec *spec; + int err; + + /* create a codec specific record */ + spec = via_new_spec(codec); + if (spec == NULL) + return -ENOMEM; + + spec->gen.mixer_nid = 0x16; + override_mic_boost(codec, 0x1a, 0, 3, 40); + override_mic_boost(codec, 0x1e, 0, 3, 40); + + err = snd_hda_add_verbs(codec, vt1716S_init_verbs); + if (err < 0) + goto error; + + /* automatic parse from the BIOS config */ + err = via_parse_auto_config(codec); + if (err < 0) + goto error; + + if (!snd_hda_gen_add_kctl(&spec->gen, NULL, &vt1716s_dmic_mixer_vol) || + !snd_hda_gen_add_kctl(&spec->gen, NULL, &vt1716s_dmic_mixer_sw) || + !snd_hda_gen_add_kctl(&spec->gen, NULL, &vt1716S_mono_out_mixer)) { + err = -ENOMEM; + goto error; + } + + return 0; + + error: + via_free(codec); + return err; +} + +/* for vt2002P */ + +static const struct hda_verb vt2002P_init_verbs[] = { + /* Class-D speaker related verbs */ + {0x1, 0xfe0, 0x4}, + {0x1, 0xfe9, 0x80}, + {0x1, 0xfe2, 0x22}, + /* Enable Boost Volume backdoor */ + {0x1, 0xfb9, 0x24}, + /* Enable AOW0 to MW9 */ + {0x1, 0xfb8, 0x88}, + { } +}; + +static const struct hda_verb vt1802_init_verbs[] = { + /* Enable Boost Volume backdoor */ + {0x1, 0xfb9, 0x24}, + /* Enable AOW0 to MW9 */ + {0x1, 0xfb8, 0x88}, + { } +}; + +/* + * pin fix-up + */ +enum { + VIA_FIXUP_INTMIC_BOOST, + VIA_FIXUP_ASUS_G75, + VIA_FIXUP_POWER_SAVE, +}; + +static void via_fixup_intmic_boost(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) + override_mic_boost(codec, 0x30, 0, 2, 40); +} + +static void via_fixup_power_save(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) + codec->power_save_node = 0; +} + +static const struct hda_fixup via_fixups[] = { + [VIA_FIXUP_INTMIC_BOOST] = { + .type = HDA_FIXUP_FUNC, + .v.func = via_fixup_intmic_boost, + }, + [VIA_FIXUP_ASUS_G75] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + /* set 0x24 and 0x33 as speakers */ + { 0x24, 0x991301f0 }, + { 0x33, 0x991301f1 }, /* subwoofer */ + { } + } + }, + [VIA_FIXUP_POWER_SAVE] = { + .type = HDA_FIXUP_FUNC, + .v.func = via_fixup_power_save, + }, +}; + +static const struct snd_pci_quirk vt2002p_fixups[] = { + SND_PCI_QUIRK(0x1043, 0x13f7, "Asus B23E", VIA_FIXUP_POWER_SAVE), + SND_PCI_QUIRK(0x1043, 0x1487, "Asus G75", VIA_FIXUP_ASUS_G75), + SND_PCI_QUIRK(0x1043, 0x8532, "Asus X202E", VIA_FIXUP_INTMIC_BOOST), + SND_PCI_QUIRK_VENDOR(0x1558, "Clevo", VIA_FIXUP_POWER_SAVE), + {} +}; + +/* NIDs 0x24 and 0x33 on VT1802 have connections to non-existing NID 0x3e + * Replace this with mixer NID 0x1c + */ +static void fix_vt1802_connections(struct hda_codec *codec) +{ + static const hda_nid_t conn_24[] = { 0x14, 0x1c }; + static const hda_nid_t conn_33[] = { 0x1c }; + + snd_hda_override_conn_list(codec, 0x24, ARRAY_SIZE(conn_24), conn_24); + snd_hda_override_conn_list(codec, 0x33, ARRAY_SIZE(conn_33), conn_33); +} + +/* patch for vt2002P */ +static int patch_vt2002P(struct hda_codec *codec) +{ + struct via_spec *spec; + int err; + + /* create a codec specific record */ + spec = via_new_spec(codec); + if (spec == NULL) + return -ENOMEM; + + spec->gen.mixer_nid = 0x21; + override_mic_boost(codec, 0x2b, 0, 3, 40); + override_mic_boost(codec, 0x29, 0, 3, 40); + if (spec->codec_type == VT1802) + fix_vt1802_connections(codec); + add_secret_dac_path(codec); + + snd_hda_pick_fixup(codec, NULL, vt2002p_fixups, via_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + if (spec->codec_type == VT1802) + err = snd_hda_add_verbs(codec, vt1802_init_verbs); + else + err = snd_hda_add_verbs(codec, vt2002P_init_verbs); + if (err < 0) + goto error; + + /* automatic parse from the BIOS config */ + err = via_parse_auto_config(codec); + if (err < 0) + goto error; + + return 0; + + error: + via_free(codec); + return err; +} + +/* for vt1812 */ + +static const struct hda_verb vt1812_init_verbs[] = { + /* Enable Boost Volume backdoor */ + {0x1, 0xfb9, 0x24}, + /* Enable AOW0 to MW9 */ + {0x1, 0xfb8, 0xa8}, + { } +}; + +/* patch for vt1812 */ +static int patch_vt1812(struct hda_codec *codec) +{ + struct via_spec *spec; + int err; + + /* create a codec specific record */ + spec = via_new_spec(codec); + if (spec == NULL) + return -ENOMEM; + + spec->gen.mixer_nid = 0x21; + override_mic_boost(codec, 0x2b, 0, 3, 40); + override_mic_boost(codec, 0x29, 0, 3, 40); + add_secret_dac_path(codec); + + err = snd_hda_add_verbs(codec, vt1812_init_verbs); + if (err < 0) + goto error; + + /* automatic parse from the BIOS config */ + err = via_parse_auto_config(codec); + if (err < 0) + goto error; + + return 0; + + error: + via_free(codec); + return err; +} + +/* patch for vt3476 */ + +static const struct hda_verb vt3476_init_verbs[] = { + /* Enable DMic 8/16/32K */ + {0x1, 0xF7B, 0x30}, + /* Enable Boost Volume backdoor */ + {0x1, 0xFB9, 0x20}, + /* Enable AOW-MW9 path */ + {0x1, 0xFB8, 0x10}, + { } +}; + +static int patch_vt3476(struct hda_codec *codec) +{ + struct via_spec *spec; + int err; + + /* create a codec specific record */ + spec = via_new_spec(codec); + if (spec == NULL) + return -ENOMEM; + + spec->gen.mixer_nid = 0x3f; + add_secret_dac_path(codec); + + err = snd_hda_add_verbs(codec, vt3476_init_verbs); + if (err < 0) + goto error; + + /* automatic parse from the BIOS config */ + err = via_parse_auto_config(codec); + if (err < 0) + goto error; + + return 0; + + error: + via_free(codec); + return err; +} + +/* + * patch entries + */ +static const struct hda_device_id snd_hda_id_via[] = { + HDA_CODEC_ENTRY(0x11061708, "VT1708", patch_vt1708), + HDA_CODEC_ENTRY(0x11061709, "VT1708", patch_vt1708), + HDA_CODEC_ENTRY(0x1106170a, "VT1708", patch_vt1708), + HDA_CODEC_ENTRY(0x1106170b, "VT1708", patch_vt1708), + HDA_CODEC_ENTRY(0x1106e710, "VT1709 10-Ch", patch_vt1709), + HDA_CODEC_ENTRY(0x1106e711, "VT1709 10-Ch", patch_vt1709), + HDA_CODEC_ENTRY(0x1106e712, "VT1709 10-Ch", patch_vt1709), + HDA_CODEC_ENTRY(0x1106e713, "VT1709 10-Ch", patch_vt1709), + HDA_CODEC_ENTRY(0x1106e714, "VT1709 6-Ch", patch_vt1709), + HDA_CODEC_ENTRY(0x1106e715, "VT1709 6-Ch", patch_vt1709), + HDA_CODEC_ENTRY(0x1106e716, "VT1709 6-Ch", patch_vt1709), + HDA_CODEC_ENTRY(0x1106e717, "VT1709 6-Ch", patch_vt1709), + HDA_CODEC_ENTRY(0x1106e720, "VT1708B 8-Ch", patch_vt1708B), + HDA_CODEC_ENTRY(0x1106e721, "VT1708B 8-Ch", patch_vt1708B), + HDA_CODEC_ENTRY(0x1106e722, "VT1708B 8-Ch", patch_vt1708B), + HDA_CODEC_ENTRY(0x1106e723, "VT1708B 8-Ch", patch_vt1708B), + HDA_CODEC_ENTRY(0x1106e724, "VT1708B 4-Ch", patch_vt1708B), + HDA_CODEC_ENTRY(0x1106e725, "VT1708B 4-Ch", patch_vt1708B), + HDA_CODEC_ENTRY(0x1106e726, "VT1708B 4-Ch", patch_vt1708B), + HDA_CODEC_ENTRY(0x1106e727, "VT1708B 4-Ch", patch_vt1708B), + HDA_CODEC_ENTRY(0x11060397, "VT1708S", patch_vt1708S), + HDA_CODEC_ENTRY(0x11061397, "VT1708S", patch_vt1708S), + HDA_CODEC_ENTRY(0x11062397, "VT1708S", patch_vt1708S), + HDA_CODEC_ENTRY(0x11063397, "VT1708S", patch_vt1708S), + HDA_CODEC_ENTRY(0x11064397, "VT1705", patch_vt1708S), + HDA_CODEC_ENTRY(0x11065397, "VT1708S", patch_vt1708S), + HDA_CODEC_ENTRY(0x11066397, "VT1708S", patch_vt1708S), + HDA_CODEC_ENTRY(0x11067397, "VT1708S", patch_vt1708S), + HDA_CODEC_ENTRY(0x11060398, "VT1702", patch_vt1702), + HDA_CODEC_ENTRY(0x11061398, "VT1702", patch_vt1702), + HDA_CODEC_ENTRY(0x11062398, "VT1702", patch_vt1702), + HDA_CODEC_ENTRY(0x11063398, "VT1702", patch_vt1702), + HDA_CODEC_ENTRY(0x11064398, "VT1702", patch_vt1702), + HDA_CODEC_ENTRY(0x11065398, "VT1702", patch_vt1702), + HDA_CODEC_ENTRY(0x11066398, "VT1702", patch_vt1702), + HDA_CODEC_ENTRY(0x11067398, "VT1702", patch_vt1702), + HDA_CODEC_ENTRY(0x11060428, "VT1718S", patch_vt1718S), + HDA_CODEC_ENTRY(0x11064428, "VT1718S", patch_vt1718S), + HDA_CODEC_ENTRY(0x11060441, "VT2020", patch_vt1718S), + HDA_CODEC_ENTRY(0x11064441, "VT1828S", patch_vt1718S), + HDA_CODEC_ENTRY(0x11060433, "VT1716S", patch_vt1716S), + HDA_CODEC_ENTRY(0x1106a721, "VT1716S", patch_vt1716S), + HDA_CODEC_ENTRY(0x11060438, "VT2002P", patch_vt2002P), + HDA_CODEC_ENTRY(0x11064438, "VT2002P", patch_vt2002P), + HDA_CODEC_ENTRY(0x11060448, "VT1812", patch_vt1812), + HDA_CODEC_ENTRY(0x11060440, "VT1818S", patch_vt1708S), + HDA_CODEC_ENTRY(0x11060446, "VT1802", patch_vt2002P), + HDA_CODEC_ENTRY(0x11068446, "VT1802", patch_vt2002P), + HDA_CODEC_ENTRY(0x11064760, "VT1705CF", patch_vt3476), + HDA_CODEC_ENTRY(0x11064761, "VT1708SCE", patch_vt3476), + HDA_CODEC_ENTRY(0x11064762, "VT1808", patch_vt3476), + {} /* terminator */ +}; +MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_via); + +static struct hda_codec_driver via_driver = { + .id = snd_hda_id_via, +}; + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("VIA HD-audio codec"); + +module_hda_codec_driver(via_driver); diff --git a/sound/pci/hda/thinkpad_helper.c b/sound/pci/hda/thinkpad_helper.c new file mode 100644 index 000000000..de4d8deed --- /dev/null +++ b/sound/pci/hda/thinkpad_helper.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Helper functions for Thinkpad LED control; + * to be included from codec driver + */ + +#if IS_ENABLED(CONFIG_THINKPAD_ACPI) + +#include <linux/acpi.h> +#include <linux/leds.h> + +static bool is_thinkpad(struct hda_codec *codec) +{ + return (codec->core.subsystem_id >> 16 == 0x17aa) && + (acpi_dev_found("LEN0068") || acpi_dev_found("LEN0268") || + acpi_dev_found("IBM0068")); +} + +static void hda_fixup_thinkpad_acpi(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + if (!is_thinkpad(codec)) + return; + snd_hda_gen_add_mute_led_cdev(codec, NULL); + snd_hda_gen_add_micmute_led_cdev(codec, NULL); + } +} + +#else /* CONFIG_THINKPAD_ACPI */ + +static void hda_fixup_thinkpad_acpi(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ +} + +#endif /* CONFIG_THINKPAD_ACPI */ |