mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-10-28 23:08:52 +01:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
251
sound/pci/hda/Kconfig
Normal file
251
sound/pci/hda/Kconfig
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
menu "HD-Audio"
|
||||
|
||||
config SND_HDA
|
||||
tristate
|
||||
select SND_PCM
|
||||
select SND_VMASTER
|
||||
select SND_KCTL_JACK
|
||||
|
||||
config SND_HDA_INTEL
|
||||
tristate "HD Audio PCI"
|
||||
depends on SND_PCI
|
||||
select SND_HDA
|
||||
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
|
||||
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_DSP_LOADER
|
||||
bool
|
||||
|
||||
config SND_HDA_PREALLOC_SIZE
|
||||
int "Pre-allocated buffer size for HD-audio driver"
|
||||
range 0 32768
|
||||
default 64
|
||||
help
|
||||
Specifies the default pre-allocated buffer-size in kB for the
|
||||
HD-audio driver. A larger buffer (e.g. 2048) is preferred
|
||||
for systems using PulseAudio. The default 64 is chosen just
|
||||
for compatibility reasons.
|
||||
|
||||
Note that the pre-allocation size can be changed dynamically
|
||||
via a proc file (/proc/asound/card*/pcm*/sub*/prealloc), too.
|
||||
|
||||
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.
|
||||
This adds the sysfs interfaces to allow user to clear the whole
|
||||
codec configuration, change the codec setup, add extra verbs,
|
||||
and re-configure the codec dynamically.
|
||||
|
||||
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_INPUT_JACK
|
||||
bool "Support jack plugging notification via input layer"
|
||||
depends on INPUT=y || INPUT=SND
|
||||
select SND_JACK
|
||||
help
|
||||
Say Y here to enable the jack plugging notification via
|
||||
input layer.
|
||||
|
||||
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_CODEC_REALTEK
|
||||
tristate "Build Realtek HD-audio codec support"
|
||||
select SND_HDA_GENERIC
|
||||
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 Device HD-audio codec support"
|
||||
select SND_HDA_GENERIC
|
||||
help
|
||||
Say Y or M here to include Analog Device 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
|
||||
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"
|
||||
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.
|
||||
|
||||
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_I915
|
||||
bool
|
||||
default y
|
||||
depends on DRM_I915
|
||||
|
||||
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_CONEXANT
|
||||
tristate "Build Conexant HD-audio codec support"
|
||||
select SND_HDA_GENERIC
|
||||
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
|
||||
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"
|
||||
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.
|
||||
|
||||
endif
|
||||
|
||||
endmenu
|
||||
51
sound/pci/hda/Makefile
Normal file
51
sound/pci/hda/Makefile
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
snd-hda-intel-objs := hda_intel.o
|
||||
snd-hda-controller-objs := hda_controller.o
|
||||
snd-hda-tegra-objs := hda_tegra.o
|
||||
# for haswell power well
|
||||
snd-hda-intel-$(CONFIG_SND_HDA_I915) += hda_i915.o
|
||||
|
||||
snd-hda-codec-y := hda_codec.o hda_jack.o hda_auto_parser.o hda_sysfs.o
|
||||
snd-hda-codec-$(CONFIG_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_codec.o := -I$(src)
|
||||
CFLAGS_hda_controller.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-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
|
||||
|
||||
# common driver
|
||||
obj-$(CONFIG_SND_HDA) := snd-hda-codec.o
|
||||
obj-$(CONFIG_SND_HDA) += snd-hda-controller.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_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
|
||||
|
||||
# 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
|
||||
409
sound/pci/hda/ca0132_regs.h
Normal file
409
sound/pci/hda/ca0132_regs.h
Normal file
|
|
@ -0,0 +1,409 @@
|
|||
/*
|
||||
* HD audio interface patch for Creative CA0132 chip.
|
||||
* CA0132 registers defines.
|
||||
*
|
||||
* Copyright (c) 2011, Creative Technology Ltd.
|
||||
*
|
||||
* This driver is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This driver is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#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
|
||||
76
sound/pci/hda/dell_wmi_helper.c
Normal file
76
sound/pci/hda/dell_wmi_helper.c
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/* Helper functions for Dell Mic Mute LED control;
|
||||
* to be included from codec driver
|
||||
*/
|
||||
|
||||
#if IS_ENABLED(CONFIG_LEDS_DELL_NETBOOKS)
|
||||
#include <linux/dell-led.h>
|
||||
|
||||
static int dell_led_value;
|
||||
static int (*dell_led_set_func)(int, int);
|
||||
static void (*dell_old_cap_hook)(struct hda_codec *,
|
||||
struct snd_kcontrol *,
|
||||
struct snd_ctl_elem_value *);
|
||||
|
||||
static void update_dell_wmi_micmute_led(struct hda_codec *codec,
|
||||
struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
if (dell_old_cap_hook)
|
||||
dell_old_cap_hook(codec, kcontrol, ucontrol);
|
||||
|
||||
if (!ucontrol || !dell_led_set_func)
|
||||
return;
|
||||
if (strcmp("Capture Switch", ucontrol->id.name) == 0 && ucontrol->id.index == 0) {
|
||||
/* TODO: How do I verify if it's a mono or stereo here? */
|
||||
int val = (ucontrol->value.integer.value[0] || ucontrol->value.integer.value[1]) ? 0 : 1;
|
||||
if (val == dell_led_value)
|
||||
return;
|
||||
dell_led_value = val;
|
||||
if (dell_led_set_func)
|
||||
dell_led_set_func(DELL_LED_MICMUTE, dell_led_value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void alc_fixup_dell_wmi(struct hda_codec *codec,
|
||||
const struct hda_fixup *fix, int action)
|
||||
{
|
||||
struct alc_spec *spec = codec->spec;
|
||||
bool removefunc = false;
|
||||
|
||||
if (action == HDA_FIXUP_ACT_PROBE) {
|
||||
if (!dell_led_set_func)
|
||||
dell_led_set_func = symbol_request(dell_app_wmi_led_set);
|
||||
if (!dell_led_set_func) {
|
||||
codec_warn(codec, "Failed to find dell wmi symbol dell_app_wmi_led_set\n");
|
||||
return;
|
||||
}
|
||||
|
||||
removefunc = true;
|
||||
if (dell_led_set_func(DELL_LED_MICMUTE, false) >= 0) {
|
||||
dell_led_value = 0;
|
||||
if (spec->gen.num_adc_nids > 1)
|
||||
codec_dbg(codec, "Skipping micmute LED control due to several ADCs");
|
||||
else {
|
||||
dell_old_cap_hook = spec->gen.cap_sync_hook;
|
||||
spec->gen.cap_sync_hook = update_dell_wmi_micmute_led;
|
||||
removefunc = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (dell_led_set_func && (action == HDA_FIXUP_ACT_FREE || removefunc)) {
|
||||
symbol_put(dell_app_wmi_led_set);
|
||||
dell_led_set_func = NULL;
|
||||
dell_old_cap_hook = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#else /* CONFIG_LEDS_DELL_NETBOOKS */
|
||||
static void alc_fixup_dell_wmi(struct hda_codec *codec,
|
||||
const struct hda_fixup *fix, int action)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* CONFIG_LEDS_DELL_NETBOOKS */
|
||||
945
sound/pci/hda/hda_auto_parser.c
Normal file
945
sound/pci/hda/hda_auto_parser.c
Normal file
|
|
@ -0,0 +1,945 @@
|
|||
/*
|
||||
* BIOS auto-parser helper functions for HD-audio
|
||||
*
|
||||
* Copyright (c) 2012 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This driver is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/sort.h>
|
||||
#include <sound/core.h>
|
||||
#include "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);
|
||||
|
||||
/* 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)
|
||||
{
|
||||
hda_nid_t nid;
|
||||
|
||||
switch (nums) {
|
||||
case 3:
|
||||
case 4:
|
||||
nid = pins[1];
|
||||
pins[1] = pins[2];
|
||||
pins[2] = nid;
|
||||
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, end_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;
|
||||
|
||||
end_nid = codec->start_nid + codec->num_nodes;
|
||||
for (nid = codec->start_nid; nid < end_nid; nid++) {
|
||||
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)) {
|
||||
int 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: line_outs=%d (0x%x/0x%x/0x%x/0x%x/0x%x) type:%s\n",
|
||||
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);
|
||||
|
||||
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
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* 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);
|
||||
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
|
||||
*
|
||||
* 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;
|
||||
strlcpy(label, name, maxlen);
|
||||
return 1;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_get_pin_label);
|
||||
|
||||
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);
|
||||
|
||||
void snd_hda_apply_verbs(struct hda_codec *codec)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < codec->verbs.used; i++) {
|
||||
struct hda_verb **v = snd_array_elem(&codec->verbs, i);
|
||||
snd_hda_sequence_write(codec, *v);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_apply_verbs);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static void 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 (fix->chained_before)
|
||||
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->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->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->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->chip_name, modelname);
|
||||
set_pin_targets(codec, fix->v.pins);
|
||||
break;
|
||||
default:
|
||||
codec_err(codec, "%s: Invalid fixup type %d\n",
|
||||
codec->chip_name, fix->type);
|
||||
break;
|
||||
}
|
||||
if (!fix->chained || fix->chained_before)
|
||||
break;
|
||||
if (++depth > 10)
|
||||
break;
|
||||
id = fix->chain_id;
|
||||
}
|
||||
}
|
||||
|
||||
void snd_hda_apply_fixup(struct hda_codec *codec, int action)
|
||||
{
|
||||
if (codec->fixup_list)
|
||||
apply_fixup(codec, codec->fixup_id, action, 0);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_apply_fixup);
|
||||
|
||||
static bool pin_config_match(struct hda_codec *codec,
|
||||
const struct hda_pintbl *pins)
|
||||
{
|
||||
for (; pins->nid; pins++) {
|
||||
u32 def_conf = snd_hda_codec_get_pincfg(codec, pins->nid);
|
||||
if (pins->val != def_conf)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void snd_hda_pick_pin_fixup(struct hda_codec *codec,
|
||||
const struct snd_hda_pin_quirk *pin_quirk,
|
||||
const struct hda_fixup *fixlist)
|
||||
{
|
||||
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->subsystem_id & 0xffff0000) != (pq->subvendor << 16))
|
||||
continue;
|
||||
if (codec->vendor_id != pq->codec)
|
||||
continue;
|
||||
if (pin_config_match(codec, pq->pins)) {
|
||||
codec->fixup_id = pq->value;
|
||||
#ifdef CONFIG_SND_DEBUG_VERBOSE
|
||||
codec->fixup_name = pq->name;
|
||||
#endif
|
||||
codec->fixup_list = fixlist;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_pick_pin_fixup);
|
||||
|
||||
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;
|
||||
|
||||
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")) {
|
||||
codec->fixup_list = NULL;
|
||||
codec->fixup_name = NULL;
|
||||
codec->fixup_id = HDA_FIXUP_ID_NO_FIXUP;
|
||||
return;
|
||||
}
|
||||
|
||||
if (codec->modelname && models) {
|
||||
while (models->name) {
|
||||
if (!strcmp(codec->modelname, models->name)) {
|
||||
codec->fixup_id = models->id;
|
||||
codec->fixup_name = models->name;
|
||||
codec->fixup_list = fixlist;
|
||||
return;
|
||||
}
|
||||
models++;
|
||||
}
|
||||
}
|
||||
if (quirk) {
|
||||
q = snd_pci_quirk_lookup(codec->bus->pci, quirk);
|
||||
if (q) {
|
||||
id = q->value;
|
||||
#ifdef CONFIG_SND_DEBUG_VERBOSE
|
||||
name = q->name;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (id < 0 && quirk) {
|
||||
for (q = quirk; q->subvendor || q->subdevice; q++) {
|
||||
unsigned int vendorid =
|
||||
q->subdevice | (q->subvendor << 16);
|
||||
unsigned int mask = 0xffff0000 | q->subdevice_mask;
|
||||
if ((codec->subsystem_id & mask) == (vendorid & mask)) {
|
||||
id = q->value;
|
||||
#ifdef CONFIG_SND_DEBUG_VERBOSE
|
||||
name = q->name;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
codec->fixup_id = id;
|
||||
if (id >= 0) {
|
||||
codec->fixup_list = fixlist;
|
||||
codec->fixup_name = name;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_pick_fixup);
|
||||
119
sound/pci/hda/hda_auto_parser.h
Normal file
119
sound/pci/hda/hda_auto_parser.h
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* BIOS auto-parser helper functions for HD-audio
|
||||
*
|
||||
* Copyright (c) 2012 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This driver is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#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 8
|
||||
|
||||
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 */
|
||||
307
sound/pci/hda/hda_beep.c
Normal file
307
sound/pci/hda/hda_beep.c
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
/*
|
||||
* Digital Beep Input Interface for HD-audio codec
|
||||
*
|
||||
* Author: Matthew Ranostay <mranostay@embeddedalley.com>
|
||||
* Copyright (c) 2008 Embedded Alley Solutions Inc
|
||||
*
|
||||
* This driver is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This driver is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#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 */
|
||||
};
|
||||
|
||||
static void snd_hda_generate_beep(struct work_struct *work)
|
||||
{
|
||||
struct hda_beep *beep =
|
||||
container_of(work, struct hda_beep, beep_work);
|
||||
struct hda_codec *codec = beep->codec;
|
||||
int tone;
|
||||
|
||||
if (!beep->enabled)
|
||||
return;
|
||||
|
||||
tone = beep->tone;
|
||||
if (tone && !beep->playing) {
|
||||
snd_hda_power_up(codec);
|
||||
beep->playing = 1;
|
||||
}
|
||||
/* generate tone */
|
||||
snd_hda_codec_write(codec, beep->nid, 0,
|
||||
AC_VERB_SET_BEEP_CONTROL, tone);
|
||||
if (!tone && beep->playing) {
|
||||
beep->playing = 0;
|
||||
snd_hda_power_down(codec);
|
||||
}
|
||||
}
|
||||
|
||||
/* (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;
|
||||
/* fallthru */
|
||||
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_off_beep(struct hda_beep *beep)
|
||||
{
|
||||
cancel_work_sync(&beep->beep_work);
|
||||
if (beep->playing) {
|
||||
/* turn off beep */
|
||||
snd_hda_codec_write(beep->codec, beep->nid, 0,
|
||||
AC_VERB_SET_BEEP_CONTROL, 0);
|
||||
beep->playing = 0;
|
||||
snd_hda_power_down(beep->codec);
|
||||
}
|
||||
}
|
||||
|
||||
static void snd_hda_do_detach(struct hda_beep *beep)
|
||||
{
|
||||
if (beep->registered)
|
||||
input_unregister_device(beep->dev);
|
||||
else
|
||||
input_free_device(beep->dev);
|
||||
beep->dev = NULL;
|
||||
turn_off_beep(beep);
|
||||
}
|
||||
|
||||
static int snd_hda_do_attach(struct hda_beep *beep)
|
||||
{
|
||||
struct input_dev *input_dev;
|
||||
struct hda_codec *codec = beep->codec;
|
||||
|
||||
input_dev = input_allocate_device();
|
||||
if (!input_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
/* setup digital beep device */
|
||||
input_dev->name = "HDA Digital PCBeep";
|
||||
input_dev->phys = beep->phys;
|
||||
input_dev->id.bustype = BUS_PCI;
|
||||
|
||||
input_dev->id.vendor = codec->vendor_id >> 16;
|
||||
input_dev->id.product = codec->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_dev->dev.parent = &codec->dev;
|
||||
input_set_drvdata(input_dev, beep);
|
||||
|
||||
beep->dev = input_dev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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_off_beep(beep);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_enable_beep_device);
|
||||
|
||||
int snd_hda_attach_beep_device(struct hda_codec *codec, int nid)
|
||||
{
|
||||
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->bus->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);
|
||||
|
||||
err = snd_hda_do_attach(beep);
|
||||
if (err < 0) {
|
||||
kfree(beep);
|
||||
codec->beep = NULL;
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_attach_beep_device);
|
||||
|
||||
void snd_hda_detach_beep_device(struct hda_codec *codec)
|
||||
{
|
||||
struct hda_beep *beep = codec->beep;
|
||||
if (beep) {
|
||||
if (beep->dev)
|
||||
snd_hda_do_detach(beep);
|
||||
codec->beep = NULL;
|
||||
kfree(beep);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_detach_beep_device);
|
||||
|
||||
int snd_hda_register_beep_device(struct hda_codec *codec)
|
||||
{
|
||||
struct hda_beep *beep = codec->beep;
|
||||
int err;
|
||||
|
||||
if (!beep || !beep->dev)
|
||||
return 0;
|
||||
|
||||
err = input_register_device(beep->dev);
|
||||
if (err < 0) {
|
||||
codec_err(codec, "hda_beep: unable to register input device\n");
|
||||
input_free_device(beep->dev);
|
||||
codec->beep = NULL;
|
||||
kfree(beep);
|
||||
return err;
|
||||
}
|
||||
beep->registered = true;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_register_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 */
|
||||
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;
|
||||
if (beep && (!beep->enabled || !ctl_has_mute(kcontrol))) {
|
||||
ucontrol->value.integer.value[0] =
|
||||
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);
|
||||
|
||||
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);
|
||||
63
sound/pci/hda/hda_beep.h
Normal file
63
sound/pci/hda/hda_beep.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Digital Beep Input Interface for HD-audio codec
|
||||
*
|
||||
* Author: Matthew Ranostay <mranostay@embeddedalley.com>
|
||||
* Copyright (c) 2008 Embedded Alley Solutions Inc
|
||||
*
|
||||
* This driver is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This driver is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef __SOUND_HDA_BEEP_H
|
||||
#define __SOUND_HDA_BEEP_H
|
||||
|
||||
#include "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;
|
||||
struct work_struct beep_work; /* scheduled task for beep event */
|
||||
struct mutex mutex;
|
||||
};
|
||||
|
||||
#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);
|
||||
int snd_hda_register_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)
|
||||
{
|
||||
}
|
||||
static inline int snd_hda_register_beep_device(struct hda_codec *codec)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
5760
sound/pci/hda/hda_codec.c
Normal file
5760
sound/pci/hda/hda_codec.c
Normal file
File diff suppressed because it is too large
Load diff
690
sound/pci/hda/hda_codec.h
Normal file
690
sound/pci/hda/hda_codec.h
Normal file
|
|
@ -0,0 +1,690 @@
|
|||
/*
|
||||
* Universal Interface for Intel High Definition Audio Codec
|
||||
*
|
||||
* Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __SOUND_HDA_CODEC_H
|
||||
#define __SOUND_HDA_CODEC_H
|
||||
|
||||
#include <sound/info.h>
|
||||
#include <sound/control.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/hwdep.h>
|
||||
#include <sound/hda_verbs.h>
|
||||
|
||||
/*
|
||||
* generic arrays
|
||||
*/
|
||||
struct snd_array {
|
||||
unsigned int used;
|
||||
unsigned int alloced;
|
||||
unsigned int elem_size;
|
||||
unsigned int alloc_align;
|
||||
void *list;
|
||||
};
|
||||
|
||||
void *snd_array_new(struct snd_array *array);
|
||||
void snd_array_free(struct snd_array *array);
|
||||
static inline void snd_array_init(struct snd_array *array, unsigned int size,
|
||||
unsigned int align)
|
||||
{
|
||||
array->elem_size = size;
|
||||
array->alloc_align = align;
|
||||
}
|
||||
|
||||
static inline void *snd_array_elem(struct snd_array *array, unsigned int idx)
|
||||
{
|
||||
return array->list + idx * array->elem_size;
|
||||
}
|
||||
|
||||
static inline unsigned int snd_array_index(struct snd_array *array, void *ptr)
|
||||
{
|
||||
return (unsigned long)(ptr - array->list) / array->elem_size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Structures
|
||||
*/
|
||||
|
||||
struct hda_bus;
|
||||
struct hda_beep;
|
||||
struct hda_codec;
|
||||
struct hda_pcm;
|
||||
struct hda_pcm_stream;
|
||||
struct hda_bus_unsolicited;
|
||||
|
||||
/* NID type */
|
||||
typedef u16 hda_nid_t;
|
||||
|
||||
/* bus operators */
|
||||
struct hda_bus_ops {
|
||||
/* send a single command */
|
||||
int (*command)(struct hda_bus *bus, unsigned int cmd);
|
||||
/* get a response from the last command */
|
||||
unsigned int (*get_response)(struct hda_bus *bus, unsigned int addr);
|
||||
/* free the private data */
|
||||
void (*private_free)(struct hda_bus *);
|
||||
/* attach a PCM stream */
|
||||
int (*attach_pcm)(struct hda_bus *bus, struct hda_codec *codec,
|
||||
struct hda_pcm *pcm);
|
||||
/* reset bus for retry verb */
|
||||
void (*bus_reset)(struct hda_bus *bus);
|
||||
#ifdef CONFIG_PM
|
||||
/* notify power-up/down from codec to controller */
|
||||
void (*pm_notify)(struct hda_bus *bus, bool power_up);
|
||||
#endif
|
||||
#ifdef CONFIG_SND_HDA_DSP_LOADER
|
||||
/* prepare DSP transfer */
|
||||
int (*load_dsp_prepare)(struct hda_bus *bus, unsigned int format,
|
||||
unsigned int byte_size,
|
||||
struct snd_dma_buffer *bufp);
|
||||
/* start/stop DSP transfer */
|
||||
void (*load_dsp_trigger)(struct hda_bus *bus, bool start);
|
||||
/* clean up DSP transfer */
|
||||
void (*load_dsp_cleanup)(struct hda_bus *bus,
|
||||
struct snd_dma_buffer *dmab);
|
||||
#endif
|
||||
};
|
||||
|
||||
/* template to pass to the bus constructor */
|
||||
struct hda_bus_template {
|
||||
void *private_data;
|
||||
struct pci_dev *pci;
|
||||
const char *modelname;
|
||||
int *power_save;
|
||||
struct hda_bus_ops ops;
|
||||
};
|
||||
|
||||
/*
|
||||
* codec bus
|
||||
*
|
||||
* each controller needs to creata a hda_bus to assign the accessor.
|
||||
* A hda_bus contains several codecs in the list codec_list.
|
||||
*/
|
||||
struct hda_bus {
|
||||
struct snd_card *card;
|
||||
|
||||
/* copied from template */
|
||||
void *private_data;
|
||||
struct pci_dev *pci;
|
||||
const char *modelname;
|
||||
int *power_save;
|
||||
struct hda_bus_ops ops;
|
||||
|
||||
/* codec linked list */
|
||||
struct list_head codec_list;
|
||||
unsigned int num_codecs;
|
||||
/* link caddr -> codec */
|
||||
struct hda_codec *caddr_tbl[HDA_MAX_CODEC_ADDRESS + 1];
|
||||
|
||||
struct mutex cmd_mutex;
|
||||
struct mutex prepare_mutex;
|
||||
|
||||
/* unsolicited event queue */
|
||||
struct hda_bus_unsolicited *unsol;
|
||||
char workq_name[16];
|
||||
struct workqueue_struct *workq; /* common workqueue for codecs */
|
||||
|
||||
/* assigned PCMs */
|
||||
DECLARE_BITMAP(pcm_dev_bits, SNDRV_PCM_DEVICES);
|
||||
|
||||
/* misc op flags */
|
||||
unsigned int needs_damn_long_delay :1;
|
||||
unsigned int allow_bus_reset:1; /* allow bus reset at fatal error */
|
||||
unsigned int sync_write:1; /* sync after verb write */
|
||||
/* status for codec/controller */
|
||||
unsigned int shutdown :1; /* being unloaded */
|
||||
unsigned int rirb_error:1; /* error in codec communication */
|
||||
unsigned int response_reset:1; /* controller was reset */
|
||||
unsigned int in_reset:1; /* during reset operation */
|
||||
unsigned int power_keep_link_on:1; /* don't power off HDA link */
|
||||
unsigned int no_response_fallback:1; /* don't fallback at RIRB error */
|
||||
|
||||
int primary_dig_out_type; /* primary digital out PCM type */
|
||||
};
|
||||
|
||||
/*
|
||||
* codec preset
|
||||
*
|
||||
* Known codecs have the patch to build and set up the controls/PCMs
|
||||
* better than the generic parser.
|
||||
*/
|
||||
struct hda_codec_preset {
|
||||
unsigned int id;
|
||||
unsigned int mask;
|
||||
unsigned int subs;
|
||||
unsigned int subs_mask;
|
||||
unsigned int rev;
|
||||
hda_nid_t afg, mfg;
|
||||
const char *name;
|
||||
int (*patch)(struct hda_codec *codec);
|
||||
};
|
||||
|
||||
struct hda_codec_preset_list {
|
||||
const struct hda_codec_preset *preset;
|
||||
struct module *owner;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
/* initial hook */
|
||||
int snd_hda_add_codec_preset(struct hda_codec_preset_list *preset);
|
||||
int snd_hda_delete_codec_preset(struct hda_codec_preset_list *preset);
|
||||
|
||||
/* ops set by the preset patch */
|
||||
struct hda_codec_ops {
|
||||
int (*build_controls)(struct hda_codec *codec);
|
||||
int (*build_pcms)(struct hda_codec *codec);
|
||||
int (*init)(struct hda_codec *codec);
|
||||
void (*free)(struct hda_codec *codec);
|
||||
void (*unsol_event)(struct hda_codec *codec, unsigned int res);
|
||||
void (*set_power_state)(struct hda_codec *codec, hda_nid_t fg,
|
||||
unsigned int power_state);
|
||||
#ifdef CONFIG_PM
|
||||
int (*suspend)(struct hda_codec *codec);
|
||||
int (*resume)(struct hda_codec *codec);
|
||||
int (*check_power_status)(struct hda_codec *codec, hda_nid_t nid);
|
||||
#endif
|
||||
void (*reboot_notify)(struct hda_codec *codec);
|
||||
};
|
||||
|
||||
/* record for amp information cache */
|
||||
struct hda_cache_head {
|
||||
u32 key:31; /* hash key */
|
||||
u32 dirty:1;
|
||||
u16 val; /* assigned value */
|
||||
u16 next;
|
||||
};
|
||||
|
||||
struct hda_amp_info {
|
||||
struct hda_cache_head head;
|
||||
u32 amp_caps; /* amp capabilities */
|
||||
u16 vol[2]; /* current volume & mute */
|
||||
};
|
||||
|
||||
struct hda_cache_rec {
|
||||
u16 hash[64]; /* hash table for index */
|
||||
struct snd_array buf; /* record entries */
|
||||
};
|
||||
|
||||
/* PCM callbacks */
|
||||
struct hda_pcm_ops {
|
||||
int (*open)(struct hda_pcm_stream *info, struct hda_codec *codec,
|
||||
struct snd_pcm_substream *substream);
|
||||
int (*close)(struct hda_pcm_stream *info, struct hda_codec *codec,
|
||||
struct snd_pcm_substream *substream);
|
||||
int (*prepare)(struct hda_pcm_stream *info, struct hda_codec *codec,
|
||||
unsigned int stream_tag, unsigned int format,
|
||||
struct snd_pcm_substream *substream);
|
||||
int (*cleanup)(struct hda_pcm_stream *info, struct hda_codec *codec,
|
||||
struct snd_pcm_substream *substream);
|
||||
unsigned int (*get_delay)(struct hda_pcm_stream *info,
|
||||
struct hda_codec *codec,
|
||||
struct snd_pcm_substream *substream);
|
||||
};
|
||||
|
||||
/* PCM information for each substream */
|
||||
struct hda_pcm_stream {
|
||||
unsigned int substreams; /* number of substreams, 0 = not exist*/
|
||||
unsigned int channels_min; /* min. number of channels */
|
||||
unsigned int channels_max; /* max. number of channels */
|
||||
hda_nid_t nid; /* default NID to query rates/formats/bps, or set up */
|
||||
u32 rates; /* supported rates */
|
||||
u64 formats; /* supported formats (SNDRV_PCM_FMTBIT_) */
|
||||
unsigned int maxbps; /* supported max. bit per sample */
|
||||
const struct snd_pcm_chmap_elem *chmap; /* chmap to override */
|
||||
struct hda_pcm_ops ops;
|
||||
};
|
||||
|
||||
/* PCM types */
|
||||
enum {
|
||||
HDA_PCM_TYPE_AUDIO,
|
||||
HDA_PCM_TYPE_SPDIF,
|
||||
HDA_PCM_TYPE_HDMI,
|
||||
HDA_PCM_TYPE_MODEM,
|
||||
HDA_PCM_NTYPES
|
||||
};
|
||||
|
||||
/* for PCM creation */
|
||||
struct hda_pcm {
|
||||
char *name;
|
||||
struct hda_pcm_stream stream[2];
|
||||
unsigned int pcm_type; /* HDA_PCM_TYPE_XXX */
|
||||
int device; /* device number to assign */
|
||||
struct snd_pcm *pcm; /* assigned PCM instance */
|
||||
bool own_chmap; /* codec driver provides own channel maps */
|
||||
};
|
||||
|
||||
/* codec information */
|
||||
struct hda_codec {
|
||||
struct device dev;
|
||||
struct hda_bus *bus;
|
||||
unsigned int addr; /* codec addr*/
|
||||
struct list_head list; /* list point */
|
||||
|
||||
hda_nid_t afg; /* AFG node id */
|
||||
hda_nid_t mfg; /* MFG node id */
|
||||
|
||||
/* ids */
|
||||
u8 afg_function_id;
|
||||
u8 mfg_function_id;
|
||||
u8 afg_unsol;
|
||||
u8 mfg_unsol;
|
||||
u32 vendor_id;
|
||||
u32 subsystem_id;
|
||||
u32 revision_id;
|
||||
|
||||
/* detected preset */
|
||||
const struct hda_codec_preset *preset;
|
||||
struct module *owner;
|
||||
int (*parser)(struct hda_codec *codec);
|
||||
const char *vendor_name; /* codec vendor name */
|
||||
const char *chip_name; /* codec chip name */
|
||||
const char *modelname; /* model name for preset */
|
||||
|
||||
/* set by patch */
|
||||
struct hda_codec_ops patch_ops;
|
||||
|
||||
/* PCM to create, set by patch_ops.build_pcms callback */
|
||||
unsigned int num_pcms;
|
||||
struct hda_pcm *pcm_info;
|
||||
|
||||
/* codec specific info */
|
||||
void *spec;
|
||||
|
||||
/* beep device */
|
||||
struct hda_beep *beep;
|
||||
unsigned int beep_mode;
|
||||
|
||||
/* widget capabilities cache */
|
||||
unsigned int num_nodes;
|
||||
hda_nid_t start_nid;
|
||||
u32 *wcaps;
|
||||
|
||||
struct snd_array mixers; /* list of assigned mixer elements */
|
||||
struct snd_array nids; /* list of mapped mixer elements */
|
||||
|
||||
struct hda_cache_rec amp_cache; /* cache for amp access */
|
||||
struct hda_cache_rec cmd_cache; /* cache for other commands */
|
||||
|
||||
struct list_head conn_list; /* linked-list of connection-list */
|
||||
|
||||
struct mutex spdif_mutex;
|
||||
struct mutex control_mutex;
|
||||
struct mutex hash_mutex;
|
||||
struct snd_array spdif_out;
|
||||
unsigned int spdif_in_enable; /* SPDIF input enable? */
|
||||
const hda_nid_t *slave_dig_outs; /* optional digital out slave widgets */
|
||||
struct snd_array init_pins; /* initial (BIOS) pin configurations */
|
||||
struct snd_array driver_pins; /* pin configs set by codec parser */
|
||||
struct snd_array cvt_setups; /* audio convert setups */
|
||||
|
||||
struct mutex user_mutex;
|
||||
#ifdef CONFIG_SND_HDA_RECONFIG
|
||||
struct snd_array init_verbs; /* additional init verbs */
|
||||
struct snd_array hints; /* additional hints */
|
||||
struct snd_array user_pins; /* default pin configs to override */
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SND_HDA_HWDEP
|
||||
struct snd_hwdep *hwdep; /* assigned hwdep device */
|
||||
#endif
|
||||
|
||||
/* misc flags */
|
||||
unsigned int spdif_status_reset :1; /* needs to toggle SPDIF for each
|
||||
* status change
|
||||
* (e.g. Realtek codecs)
|
||||
*/
|
||||
unsigned int pin_amp_workaround:1; /* pin out-amp takes index
|
||||
* (e.g. Conexant codecs)
|
||||
*/
|
||||
unsigned int single_adc_amp:1; /* adc in-amp takes no index
|
||||
* (e.g. CX20549 codec)
|
||||
*/
|
||||
unsigned int no_sticky_stream:1; /* no sticky-PCM stream assignment */
|
||||
unsigned int pins_shutup:1; /* pins are shut up */
|
||||
unsigned int no_trigger_sense:1; /* don't trigger at pin-sensing */
|
||||
unsigned int no_jack_detect:1; /* Machine has no jack-detection */
|
||||
unsigned int inv_eapd:1; /* broken h/w: inverted EAPD control */
|
||||
unsigned int inv_jack_detect:1; /* broken h/w: inverted detection bit */
|
||||
unsigned int pcm_format_first:1; /* PCM format must be set first */
|
||||
unsigned int epss:1; /* supporting EPSS? */
|
||||
unsigned int cached_write:1; /* write only to caches */
|
||||
unsigned int dp_mst:1; /* support DP1.2 Multi-stream transport */
|
||||
unsigned int dump_coef:1; /* dump processing coefs in codec proc file */
|
||||
#ifdef CONFIG_PM
|
||||
unsigned int power_on :1; /* current (global) power-state */
|
||||
unsigned int d3_stop_clk:1; /* support D3 operation without BCLK */
|
||||
unsigned int pm_up_notified:1; /* PM notified to controller */
|
||||
unsigned int in_pm:1; /* suspend/resume being performed */
|
||||
int power_transition; /* power-state in transition */
|
||||
int power_count; /* current (global) power refcount */
|
||||
struct delayed_work power_work; /* delayed task for powerdown */
|
||||
unsigned long power_on_acct;
|
||||
unsigned long power_off_acct;
|
||||
unsigned long power_jiffies;
|
||||
spinlock_t power_lock;
|
||||
#endif
|
||||
|
||||
/* filter the requested power state per nid */
|
||||
unsigned int (*power_filter)(struct hda_codec *codec, hda_nid_t nid,
|
||||
unsigned int power_state);
|
||||
|
||||
/* codec-specific additional proc output */
|
||||
void (*proc_widget_hook)(struct snd_info_buffer *buffer,
|
||||
struct hda_codec *codec, hda_nid_t nid);
|
||||
|
||||
/* jack detection */
|
||||
struct snd_array jacktbl;
|
||||
unsigned long jackpoll_interval; /* In jiffies. Zero means no poll, rely on unsol events */
|
||||
struct delayed_work jackpoll_work;
|
||||
|
||||
#ifdef CONFIG_SND_HDA_INPUT_JACK
|
||||
/* jack detection */
|
||||
struct snd_array jacks;
|
||||
#endif
|
||||
|
||||
int depop_delay; /* depop delay in ms, -1 for default delay time */
|
||||
|
||||
/* fix-up list */
|
||||
int fixup_id;
|
||||
const struct hda_fixup *fixup_list;
|
||||
const char *fixup_name;
|
||||
|
||||
/* additional init verbs */
|
||||
struct snd_array verbs;
|
||||
};
|
||||
|
||||
/* direction */
|
||||
enum {
|
||||
HDA_INPUT, HDA_OUTPUT
|
||||
};
|
||||
|
||||
/* snd_hda_codec_read/write optional flags */
|
||||
#define HDA_RW_NO_RESPONSE_FALLBACK (1 << 0)
|
||||
|
||||
/*
|
||||
* constructors
|
||||
*/
|
||||
int snd_hda_bus_new(struct snd_card *card, const struct hda_bus_template *temp,
|
||||
struct hda_bus **busp);
|
||||
int snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr,
|
||||
struct hda_codec **codecp);
|
||||
int snd_hda_codec_configure(struct hda_codec *codec);
|
||||
int snd_hda_codec_update_widgets(struct hda_codec *codec);
|
||||
|
||||
/*
|
||||
* low level functions
|
||||
*/
|
||||
unsigned int snd_hda_codec_read(struct hda_codec *codec, hda_nid_t nid,
|
||||
int flags,
|
||||
unsigned int verb, unsigned int parm);
|
||||
int snd_hda_codec_write(struct hda_codec *codec, hda_nid_t nid, int flags,
|
||||
unsigned int verb, unsigned int parm);
|
||||
#define snd_hda_param_read(codec, nid, param) \
|
||||
snd_hda_codec_read(codec, nid, 0, AC_VERB_PARAMETERS, param)
|
||||
int snd_hda_get_sub_nodes(struct hda_codec *codec, hda_nid_t nid,
|
||||
hda_nid_t *start_id);
|
||||
int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid,
|
||||
hda_nid_t *conn_list, int max_conns);
|
||||
static inline int
|
||||
snd_hda_get_num_conns(struct hda_codec *codec, hda_nid_t nid)
|
||||
{
|
||||
return snd_hda_get_connections(codec, nid, NULL, 0);
|
||||
}
|
||||
int snd_hda_get_num_raw_conns(struct hda_codec *codec, hda_nid_t nid);
|
||||
int snd_hda_get_raw_connections(struct hda_codec *codec, hda_nid_t nid,
|
||||
hda_nid_t *conn_list, int max_conns);
|
||||
int snd_hda_get_conn_list(struct hda_codec *codec, hda_nid_t nid,
|
||||
const hda_nid_t **listp);
|
||||
int snd_hda_override_conn_list(struct hda_codec *codec, hda_nid_t nid, int nums,
|
||||
const hda_nid_t *list);
|
||||
int snd_hda_get_conn_index(struct hda_codec *codec, hda_nid_t mux,
|
||||
hda_nid_t nid, int recursive);
|
||||
int snd_hda_get_devices(struct hda_codec *codec, hda_nid_t nid,
|
||||
u8 *dev_list, int max_devices);
|
||||
int snd_hda_query_supported_pcm(struct hda_codec *codec, hda_nid_t nid,
|
||||
u32 *ratesp, u64 *formatsp, unsigned int *bpsp);
|
||||
|
||||
struct hda_verb {
|
||||
hda_nid_t nid;
|
||||
u32 verb;
|
||||
u32 param;
|
||||
};
|
||||
|
||||
void snd_hda_sequence_write(struct hda_codec *codec,
|
||||
const struct hda_verb *seq);
|
||||
|
||||
/* unsolicited event */
|
||||
int snd_hda_queue_unsol_event(struct hda_bus *bus, u32 res, u32 res_ex);
|
||||
|
||||
/* cached write */
|
||||
int snd_hda_codec_write_cache(struct hda_codec *codec, hda_nid_t nid,
|
||||
int flags, unsigned int verb, unsigned int parm);
|
||||
void snd_hda_sequence_write_cache(struct hda_codec *codec,
|
||||
const struct hda_verb *seq);
|
||||
int snd_hda_codec_update_cache(struct hda_codec *codec, hda_nid_t nid,
|
||||
int flags, unsigned int verb, unsigned int parm);
|
||||
void snd_hda_codec_resume_cache(struct hda_codec *codec);
|
||||
/* both for cmd & amp caches */
|
||||
void snd_hda_codec_flush_cache(struct hda_codec *codec);
|
||||
|
||||
/* the struct for codec->pin_configs */
|
||||
struct hda_pincfg {
|
||||
hda_nid_t nid;
|
||||
unsigned char ctrl; /* original pin control value */
|
||||
unsigned char target; /* target pin control value */
|
||||
unsigned int cfg; /* default configuration */
|
||||
};
|
||||
|
||||
unsigned int snd_hda_codec_get_pincfg(struct hda_codec *codec, hda_nid_t nid);
|
||||
int snd_hda_codec_set_pincfg(struct hda_codec *codec, hda_nid_t nid,
|
||||
unsigned int cfg);
|
||||
int snd_hda_add_pincfg(struct hda_codec *codec, struct snd_array *list,
|
||||
hda_nid_t nid, unsigned int cfg); /* for hwdep */
|
||||
void snd_hda_shutup_pins(struct hda_codec *codec);
|
||||
|
||||
/* SPDIF controls */
|
||||
struct hda_spdif_out {
|
||||
hda_nid_t nid; /* Converter nid values relate to */
|
||||
unsigned int status; /* IEC958 status bits */
|
||||
unsigned short ctls; /* SPDIF control bits */
|
||||
};
|
||||
struct hda_spdif_out *snd_hda_spdif_out_of_nid(struct hda_codec *codec,
|
||||
hda_nid_t nid);
|
||||
void snd_hda_spdif_ctls_unassign(struct hda_codec *codec, int idx);
|
||||
void snd_hda_spdif_ctls_assign(struct hda_codec *codec, int idx, hda_nid_t nid);
|
||||
|
||||
/*
|
||||
* Mixer
|
||||
*/
|
||||
int snd_hda_build_controls(struct hda_bus *bus);
|
||||
int snd_hda_codec_build_controls(struct hda_codec *codec);
|
||||
|
||||
/*
|
||||
* PCM
|
||||
*/
|
||||
int snd_hda_build_pcms(struct hda_bus *bus);
|
||||
int snd_hda_codec_build_pcms(struct hda_codec *codec);
|
||||
|
||||
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);
|
||||
void snd_hda_codec_cleanup(struct hda_codec *codec,
|
||||
struct hda_pcm_stream *hinfo,
|
||||
struct snd_pcm_substream *substream);
|
||||
|
||||
void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid,
|
||||
u32 stream_tag,
|
||||
int channel_id, int format);
|
||||
void __snd_hda_codec_cleanup_stream(struct hda_codec *codec, hda_nid_t nid,
|
||||
int do_now);
|
||||
#define snd_hda_codec_cleanup_stream(codec, nid) \
|
||||
__snd_hda_codec_cleanup_stream(codec, nid, 0)
|
||||
unsigned int snd_hda_calc_stream_format(struct hda_codec *codec,
|
||||
unsigned int rate,
|
||||
unsigned int channels,
|
||||
unsigned int format,
|
||||
unsigned int maxbps,
|
||||
unsigned short spdif_ctls);
|
||||
int snd_hda_is_supported_format(struct hda_codec *codec, hda_nid_t nid,
|
||||
unsigned int format);
|
||||
|
||||
extern const struct snd_pcm_chmap_elem snd_pcm_2_1_chmaps[];
|
||||
|
||||
/*
|
||||
* Misc
|
||||
*/
|
||||
void snd_hda_get_codec_name(struct hda_codec *codec, char *name, int namelen);
|
||||
void snd_hda_bus_reboot_notify(struct hda_bus *bus);
|
||||
void snd_hda_codec_set_power_to_all(struct hda_codec *codec, hda_nid_t fg,
|
||||
unsigned int power_state);
|
||||
|
||||
int snd_hda_lock_devices(struct hda_bus *bus);
|
||||
void snd_hda_unlock_devices(struct hda_bus *bus);
|
||||
|
||||
/*
|
||||
* power management
|
||||
*/
|
||||
#ifdef CONFIG_PM
|
||||
int snd_hda_suspend(struct hda_bus *bus);
|
||||
int snd_hda_resume(struct hda_bus *bus);
|
||||
#endif
|
||||
|
||||
static inline
|
||||
int hda_call_check_power_status(struct hda_codec *codec, hda_nid_t nid)
|
||||
{
|
||||
#ifdef CONFIG_PM
|
||||
if (codec->patch_ops.check_power_status)
|
||||
return codec->patch_ops.check_power_status(codec, nid);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* get widget information
|
||||
*/
|
||||
const char *snd_hda_get_jack_connectivity(u32 cfg);
|
||||
const char *snd_hda_get_jack_type(u32 cfg);
|
||||
const char *snd_hda_get_jack_location(u32 cfg);
|
||||
|
||||
/*
|
||||
* power saving
|
||||
*/
|
||||
#ifdef CONFIG_PM
|
||||
void snd_hda_power_save(struct hda_codec *codec, int delta, bool d3wait);
|
||||
void snd_hda_update_power_acct(struct hda_codec *codec);
|
||||
#else
|
||||
static inline void snd_hda_power_save(struct hda_codec *codec, int delta,
|
||||
bool d3wait) {}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* snd_hda_power_up - Power-up the codec
|
||||
* @codec: HD-audio codec
|
||||
*
|
||||
* Increment the power-up counter and power up the hardware really when
|
||||
* not turned on yet.
|
||||
*/
|
||||
static inline void snd_hda_power_up(struct hda_codec *codec)
|
||||
{
|
||||
snd_hda_power_save(codec, 1, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hda_power_up_d3wait - Power-up the codec after waiting for any pending
|
||||
* D3 transition to complete. This differs from snd_hda_power_up() when
|
||||
* power_transition == -1. snd_hda_power_up sees this case as a nop,
|
||||
* snd_hda_power_up_d3wait waits for the D3 transition to complete then powers
|
||||
* back up.
|
||||
* @codec: HD-audio codec
|
||||
*
|
||||
* Cancel any power down operation hapenning on the work queue, then power up.
|
||||
*/
|
||||
static inline void snd_hda_power_up_d3wait(struct hda_codec *codec)
|
||||
{
|
||||
snd_hda_power_save(codec, 1, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hda_power_down - Power-down the codec
|
||||
* @codec: HD-audio codec
|
||||
*
|
||||
* Decrement the power-up counter and schedules the power-off work if
|
||||
* the counter rearches to zero.
|
||||
*/
|
||||
static inline void snd_hda_power_down(struct hda_codec *codec)
|
||||
{
|
||||
snd_hda_power_save(codec, -1, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hda_power_sync - Synchronize the power-save status
|
||||
* @codec: HD-audio codec
|
||||
*
|
||||
* Synchronize the actual power state with the power account;
|
||||
* called when power_save parameter is changed
|
||||
*/
|
||||
static inline void snd_hda_power_sync(struct hda_codec *codec)
|
||||
{
|
||||
snd_hda_power_save(codec, 0, false);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SND_HDA_PATCH_LOADER
|
||||
/*
|
||||
* patch firmware
|
||||
*/
|
||||
int snd_hda_load_patch(struct hda_bus *bus, size_t size, const void *buf);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SND_HDA_DSP_LOADER
|
||||
static inline int
|
||||
snd_hda_codec_load_dsp_prepare(struct hda_codec *codec, unsigned int format,
|
||||
unsigned int size,
|
||||
struct snd_dma_buffer *bufp)
|
||||
{
|
||||
return codec->bus->ops.load_dsp_prepare(codec->bus, format, size, bufp);
|
||||
}
|
||||
static inline void
|
||||
snd_hda_codec_load_dsp_trigger(struct hda_codec *codec, bool start)
|
||||
{
|
||||
return codec->bus->ops.load_dsp_trigger(codec->bus, start);
|
||||
}
|
||||
static inline void
|
||||
snd_hda_codec_load_dsp_cleanup(struct hda_codec *codec,
|
||||
struct snd_dma_buffer *dmab)
|
||||
{
|
||||
return codec->bus->ops.load_dsp_cleanup(codec->bus, dmab);
|
||||
}
|
||||
#else
|
||||
static inline int
|
||||
snd_hda_codec_load_dsp_prepare(struct hda_codec *codec, unsigned int format,
|
||||
unsigned int size,
|
||||
struct snd_dma_buffer *bufp)
|
||||
{
|
||||
return -ENOSYS;
|
||||
}
|
||||
static inline void
|
||||
snd_hda_codec_load_dsp_trigger(struct hda_codec *codec, bool start) {}
|
||||
static inline void
|
||||
snd_hda_codec_load_dsp_cleanup(struct hda_codec *codec,
|
||||
struct snd_dma_buffer *dmab) {}
|
||||
#endif
|
||||
|
||||
#endif /* __SOUND_HDA_CODEC_H */
|
||||
1971
sound/pci/hda/hda_controller.c
Normal file
1971
sound/pci/hda/hda_controller.c
Normal file
File diff suppressed because it is too large
Load diff
56
sound/pci/hda/hda_controller.h
Normal file
56
sound/pci/hda/hda_controller.h
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Common functionality for the alsa driver code base for HD Audio.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*/
|
||||
|
||||
#ifndef __SOUND_HDA_CONTROLLER_H
|
||||
#define __SOUND_HDA_CONTROLLER_H
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/initval.h>
|
||||
#include "hda_codec.h"
|
||||
#include "hda_priv.h"
|
||||
|
||||
/* 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_stream_stop(struct azx *chip, struct azx_dev *azx_dev);
|
||||
|
||||
/* Allocation functions. */
|
||||
int azx_alloc_stream_pages(struct azx *chip);
|
||||
void azx_free_stream_pages(struct azx *chip);
|
||||
|
||||
/* Low level azx interface */
|
||||
void azx_init_chip(struct azx *chip, bool full_reset);
|
||||
void azx_stop_chip(struct azx *chip);
|
||||
void azx_enter_link_reset(struct azx *chip);
|
||||
irqreturn_t azx_interrupt(int irq, void *dev_id);
|
||||
|
||||
/* Codec interface */
|
||||
int azx_codec_create(struct azx *chip, const char *model,
|
||||
unsigned int max_slots,
|
||||
int *power_save_to);
|
||||
int azx_codec_configure(struct azx *chip);
|
||||
int azx_mixer_create(struct azx *chip);
|
||||
int azx_init_stream(struct azx *chip);
|
||||
|
||||
void azx_notifier_register(struct azx *chip);
|
||||
void azx_notifier_unregister(struct azx *chip);
|
||||
|
||||
#endif /* __SOUND_HDA_CONTROLLER_H */
|
||||
804
sound/pci/hda/hda_eld.c
Normal file
804
sound/pci/hda/hda_eld.c
Normal file
|
|
@ -0,0 +1,804 @@
|
|||
/*
|
||||
* 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>
|
||||
*
|
||||
* This driver is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This driver is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <sound/core.h>
|
||||
#include <asm/unaligned.h>
|
||||
#include "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 char *cea_speaker_allocation_names[] = {
|
||||
/* 0 */ "FL/FR",
|
||||
/* 1 */ "LFE",
|
||||
/* 2 */ "FC",
|
||||
/* 3 */ "RL/RR",
|
||||
/* 4 */ "RC",
|
||||
/* 5 */ "FLC/FRC",
|
||||
/* 6 */ "RLC/RRC",
|
||||
/* 7 */ "FLW/FRW",
|
||||
/* 8 */ "FLH/FRH",
|
||||
/* 9 */ "TC",
|
||||
/* 10 */ "FCH",
|
||||
};
|
||||
|
||||
static char *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 char *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 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 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;
|
||||
|
||||
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
|
||||
strlcpy(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 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 += snprintf(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_print_channel_allocation(int spk_alloc, char *buf, int buflen)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
for (i = 0, j = 0; i < ARRAY_SIZE(cea_speaker_allocation_names); i++) {
|
||||
if (spk_alloc & (1 << i))
|
||||
j += snprintf(buf + j, buflen - j, " %s",
|
||||
cea_speaker_allocation_names[i]);
|
||||
}
|
||||
buf[j] = '\0'; /* necessary when j == 0 */
|
||||
}
|
||||
|
||||
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_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_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)
|
||||
{
|
||||
struct parsed_hdmi_eld *e = &eld->info;
|
||||
char buf[SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE];
|
||||
int i;
|
||||
static char *eld_version_names[32] = {
|
||||
"reserved",
|
||||
"reserved",
|
||||
"CEA-861D or below",
|
||||
[3 ... 30] = "reserved",
|
||||
[31] = "partial"
|
||||
};
|
||||
static char *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);
|
||||
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_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_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;
|
||||
}
|
||||
5443
sound/pci/hda/hda_generic.c
Normal file
5443
sound/pci/hda/hda_generic.c
Normal file
File diff suppressed because it is too large
Load diff
337
sound/pci/hda/hda_generic.h
Normal file
337
sound/pci/hda/hda_generic.h
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
/*
|
||||
* Generic BIOS auto-parser helper functions for HD-audio
|
||||
*
|
||||
* Copyright (c) 2012 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This driver is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef __SOUND_HDA_GENERIC_H
|
||||
#define __SOUND_HDA_GENERIC_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;
|
||||
};
|
||||
|
||||
/* 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 slave_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_geneic.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_enum:1; /* add vmaster mute mode enum */
|
||||
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:1; /* 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 */
|
||||
|
||||
/* 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;
|
||||
|
||||
/* 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);
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
struct nid_path *snd_hda_get_nid_path(struct hda_codec *codec,
|
||||
hda_nid_t from_nid, hda_nid_t to_nid);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
|
||||
#endif /* __SOUND_HDA_GENERIC_H */
|
||||
124
sound/pci/hda/hda_hwdep.c
Normal file
124
sound/pci/hda/hda_hwdep.c
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* HWDEP Interface for HD-audio codec
|
||||
*
|
||||
* Copyright (c) 2007 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This driver is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This driver is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/compat.h>
|
||||
#include <sound/core.h>
|
||||
#include "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;
|
||||
res = get_wcaps(codec, verb >> 24);
|
||||
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->bus->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->groups = snd_hda_dev_attr_groups;
|
||||
|
||||
hwdep->ops.open = hda_hwdep_open;
|
||||
hwdep->ops.ioctl = hda_hwdep_ioctl;
|
||||
#ifdef CONFIG_COMPAT
|
||||
hwdep->ops.ioctl_compat = hda_hwdep_ioctl_compat;
|
||||
#endif
|
||||
|
||||
/* link to codec */
|
||||
hwdep->dev = &codec->dev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
130
sound/pci/hda/hda_i915.c
Normal file
130
sound/pci/hda/hda_i915.c
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* hda_i915.c - routines for Haswell HDA controller power well support
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <sound/core.h>
|
||||
#include <drm/i915_powerwell.h>
|
||||
#include "hda_priv.h"
|
||||
#include "hda_i915.h"
|
||||
|
||||
/* Intel HSW/BDW display HDA controller Extended Mode registers.
|
||||
* EM4 (M value) and EM5 (N Value) are used to convert CDClk (Core Display
|
||||
* Clock) to 24MHz BCLK: BCLK = CDCLK * M / N
|
||||
* The values will be lost when the display power well is disabled.
|
||||
*/
|
||||
#define AZX_REG_EM4 0x100c
|
||||
#define AZX_REG_EM5 0x1010
|
||||
|
||||
static int (*get_power)(void);
|
||||
static int (*put_power)(void);
|
||||
static int (*get_cdclk)(void);
|
||||
|
||||
int hda_display_power(bool enable)
|
||||
{
|
||||
if (!get_power || !put_power)
|
||||
return -ENODEV;
|
||||
|
||||
pr_debug("HDA display power %s \n",
|
||||
enable ? "Enable" : "Disable");
|
||||
if (enable)
|
||||
return get_power();
|
||||
else
|
||||
return put_power();
|
||||
}
|
||||
|
||||
void haswell_set_bclk(struct azx *chip)
|
||||
{
|
||||
int cdclk_freq;
|
||||
unsigned int bclk_m, bclk_n;
|
||||
|
||||
if (!get_cdclk)
|
||||
return;
|
||||
|
||||
cdclk_freq = get_cdclk();
|
||||
switch (cdclk_freq) {
|
||||
case 337500:
|
||||
bclk_m = 16;
|
||||
bclk_n = 225;
|
||||
break;
|
||||
|
||||
case 450000:
|
||||
default: /* default CDCLK 450MHz */
|
||||
bclk_m = 4;
|
||||
bclk_n = 75;
|
||||
break;
|
||||
|
||||
case 540000:
|
||||
bclk_m = 4;
|
||||
bclk_n = 90;
|
||||
break;
|
||||
|
||||
case 675000:
|
||||
bclk_m = 8;
|
||||
bclk_n = 225;
|
||||
break;
|
||||
}
|
||||
|
||||
azx_writew(chip, EM4, bclk_m);
|
||||
azx_writew(chip, EM5, bclk_n);
|
||||
}
|
||||
|
||||
|
||||
int hda_i915_init(void)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
get_power = symbol_request(i915_request_power_well);
|
||||
if (!get_power) {
|
||||
pr_warn("hda-i915: get_power symbol get fail\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
put_power = symbol_request(i915_release_power_well);
|
||||
if (!put_power) {
|
||||
symbol_put(i915_request_power_well);
|
||||
get_power = NULL;
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
get_cdclk = symbol_request(i915_get_cdclk_freq);
|
||||
if (!get_cdclk) /* may have abnormal BCLK and audio playback rate */
|
||||
pr_warn("hda-i915: get_cdclk symbol get fail\n");
|
||||
|
||||
pr_debug("HDA driver get symbol successfully from i915 module\n");
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int hda_i915_exit(void)
|
||||
{
|
||||
if (get_power) {
|
||||
symbol_put(i915_request_power_well);
|
||||
get_power = NULL;
|
||||
}
|
||||
if (put_power) {
|
||||
symbol_put(i915_release_power_well);
|
||||
put_power = NULL;
|
||||
}
|
||||
if (get_cdclk) {
|
||||
symbol_put(i915_get_cdclk_freq);
|
||||
get_cdclk = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
37
sound/pci/hda/hda_i915.h
Normal file
37
sound/pci/hda/hda_i915.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
#ifndef __SOUND_HDA_I915_H
|
||||
#define __SOUND_HDA_I915_H
|
||||
|
||||
#ifdef CONFIG_SND_HDA_I915
|
||||
int hda_display_power(bool enable);
|
||||
void haswell_set_bclk(struct azx *chip);
|
||||
int hda_i915_init(void);
|
||||
int hda_i915_exit(void);
|
||||
#else
|
||||
static inline int hda_display_power(bool enable) { return 0; }
|
||||
static inline void haswell_set_bclk(struct azx *chip) { return; }
|
||||
static inline int hda_i915_init(void)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
static inline int hda_i915_exit(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
2216
sound/pci/hda/hda_intel.c
Normal file
2216
sound/pci/hda/hda_intel.c
Normal file
File diff suppressed because it is too large
Load diff
62
sound/pci/hda/hda_intel_trace.h
Normal file
62
sound/pci/hda/hda_intel_trace.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#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>
|
||||
|
||||
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)->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)->index;
|
||||
__entry->pos = pos;
|
||||
__entry->delay = delay;
|
||||
),
|
||||
|
||||
TP_printk("[%d:%d] pos=%u, delay=%u", __entry->card, __entry->idx, __entry->pos, __entry->delay)
|
||||
);
|
||||
|
||||
#endif /* _TRACE_HDA_INTEL_H */
|
||||
|
||||
/* This part must be outside protection */
|
||||
#undef TRACE_INCLUDE_PATH
|
||||
#define TRACE_INCLUDE_PATH .
|
||||
#include <trace/define_trace.h>
|
||||
569
sound/pci/hda/hda_jack.c
Normal file
569
sound/pci/hda/hda_jack.c
Normal file
|
|
@ -0,0 +1,569 @@
|
|||
/*
|
||||
* Jack-detection handling for HD-audio
|
||||
*
|
||||
* Copyright (c) 2011 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This driver is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#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 "hda_codec.h"
|
||||
#include "hda_local.h"
|
||||
#include "hda_auto_parser.h"
|
||||
#include "hda_jack.h"
|
||||
|
||||
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)
|
||||
{
|
||||
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, 0);
|
||||
if (codec->inv_jack_detect)
|
||||
val ^= AC_PINSENSE_PRESENCE;
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hda_jack_tbl_get - query the jack-table entry for the given NID
|
||||
*/
|
||||
struct hda_jack_tbl *
|
||||
snd_hda_jack_tbl_get(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;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get);
|
||||
|
||||
/**
|
||||
* snd_hda_jack_tbl_get_from_tag - query the jack-table entry for the given tag
|
||||
*/
|
||||
struct hda_jack_tbl *
|
||||
snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec, unsigned char tag)
|
||||
{
|
||||
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)
|
||||
return jack;
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get_from_tag);
|
||||
|
||||
/**
|
||||
* snd_hda_jack_tbl_new - create a jack-table entry for the given NID
|
||||
*/
|
||||
static struct hda_jack_tbl *
|
||||
snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid)
|
||||
{
|
||||
struct hda_jack_tbl *jack = snd_hda_jack_tbl_get(codec, nid);
|
||||
if (jack)
|
||||
return jack;
|
||||
jack = snd_array_new(&codec->jacktbl);
|
||||
if (!jack)
|
||||
return NULL;
|
||||
jack->nid = nid;
|
||||
jack->jack_dirty = 1;
|
||||
jack->tag = codec->jacktbl.used;
|
||||
return 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;
|
||||
#ifdef CONFIG_SND_HDA_INPUT_JACK
|
||||
/* free jack instances manually when clearing/reconfiguring */
|
||||
if (!codec->bus->shutdown && jack->jack)
|
||||
snd_device_free(codec->bus->card, jack->jack);
|
||||
#endif
|
||||
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);
|
||||
|
||||
/* A gating jack indicates the jack is invalid if gating is unplugged */
|
||||
if (jack->gating_jack && !snd_hda_jack_detect(codec, jack->gating_jack))
|
||||
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(codec, jack->gated_jack);
|
||||
if (gated) {
|
||||
gated->jack_dirty = 1;
|
||||
jack_detect_update(codec, gated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hda_set_dirty_all - Mark all the cached as dirty
|
||||
*
|
||||
* 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_pin_sense - execute pin sense measurement
|
||||
* @codec: the CODEC to sense
|
||||
* @nid: the pin NID to sense
|
||||
*
|
||||
* Execute necessary pin sense measurement and return its Presence Detect,
|
||||
* Impedance, ELD Valid etc. status bits.
|
||||
*/
|
||||
u32 snd_hda_pin_sense(struct hda_codec *codec, hda_nid_t nid)
|
||||
{
|
||||
struct hda_jack_tbl *jack = snd_hda_jack_tbl_get(codec, nid);
|
||||
if (jack) {
|
||||
jack_detect_update(codec, jack);
|
||||
return jack->pin_sense;
|
||||
}
|
||||
return read_pin_sense(codec, nid);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_pin_sense);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
int snd_hda_jack_detect_state(struct hda_codec *codec, hda_nid_t nid)
|
||||
{
|
||||
struct hda_jack_tbl *jack = snd_hda_jack_tbl_get(codec, nid);
|
||||
if (jack && jack->phantom_jack)
|
||||
return HDA_JACK_PHANTOM;
|
||||
else if (snd_hda_pin_sense(codec, nid) & AC_PINSENSE_PRESENCE)
|
||||
return HDA_JACK_PRESENT;
|
||||
else
|
||||
return HDA_JACK_NOT_PRESENT;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_jack_detect_state);
|
||||
|
||||
/**
|
||||
* snd_hda_jack_detect_enable - enable the jack-detection
|
||||
*
|
||||
* 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(struct hda_codec *codec, hda_nid_t nid,
|
||||
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);
|
||||
if (!jack)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
if (func) {
|
||||
callback = kzalloc(sizeof(*callback), GFP_KERNEL);
|
||||
if (!callback)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
callback->func = func;
|
||||
callback->tbl = jack;
|
||||
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);
|
||||
|
||||
int snd_hda_jack_detect_enable(struct hda_codec *codec, hda_nid_t nid)
|
||||
{
|
||||
return PTR_ERR_OR_ZERO(snd_hda_jack_detect_enable_callback(codec, nid, NULL));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_jack_detect_enable);
|
||||
|
||||
/**
|
||||
* snd_hda_jack_set_gating_jack - Set gating jack.
|
||||
*
|
||||
* 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);
|
||||
struct hda_jack_tbl *gating = snd_hda_jack_tbl_new(codec, gating_nid);
|
||||
|
||||
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_report_sync - sync the states of all jacks and report if changed
|
||||
*/
|
||||
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->kctl || jack->block_report)
|
||||
continue;
|
||||
state = get_jack_plug_state(jack->pin_sense);
|
||||
snd_kctl_jack_report(codec->bus->card, jack->kctl, state);
|
||||
#ifdef CONFIG_SND_HDA_INPUT_JACK
|
||||
if (jack->jack)
|
||||
snd_jack_report(jack->jack,
|
||||
state ? jack->type : 0);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_jack_report_sync);
|
||||
|
||||
#ifdef CONFIG_SND_HDA_INPUT_JACK
|
||||
/* 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;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* snd_hda_jack_add_kctl - Add a kctl for the given pin
|
||||
*
|
||||
* This assigns a jack-detection kctl to the given pin. The kcontrol
|
||||
* will have the given name and index.
|
||||
*/
|
||||
static int __snd_hda_jack_add_kctl(struct hda_codec *codec, hda_nid_t nid,
|
||||
const char *name, int idx, bool phantom_jack)
|
||||
{
|
||||
struct hda_jack_tbl *jack;
|
||||
struct snd_kcontrol *kctl;
|
||||
int err, state;
|
||||
|
||||
jack = snd_hda_jack_tbl_new(codec, nid);
|
||||
if (!jack)
|
||||
return 0;
|
||||
if (jack->kctl)
|
||||
return 0; /* already created */
|
||||
kctl = snd_kctl_jack_new(name, idx, codec);
|
||||
if (!kctl)
|
||||
return -ENOMEM;
|
||||
err = snd_hda_ctl_add(codec, nid, kctl);
|
||||
if (err < 0)
|
||||
return err;
|
||||
jack->kctl = kctl;
|
||||
jack->phantom_jack = !!phantom_jack;
|
||||
|
||||
state = snd_hda_jack_detect(codec, nid);
|
||||
snd_kctl_jack_report(codec->bus->card, kctl, state);
|
||||
#ifdef CONFIG_SND_HDA_INPUT_JACK
|
||||
if (!phantom_jack) {
|
||||
jack->type = get_input_jack_type(codec, nid);
|
||||
err = snd_jack_new(codec->bus->card, name, jack->type,
|
||||
&jack->jack);
|
||||
if (err < 0)
|
||||
return err;
|
||||
jack->jack->private_data = jack;
|
||||
jack->jack->private_free = hda_free_jack_priv;
|
||||
snd_jack_report(jack->jack, state ? jack->type : 0);
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int snd_hda_jack_add_kctl(struct hda_codec *codec, hda_nid_t nid,
|
||||
const char *name, int idx)
|
||||
{
|
||||
return __snd_hda_jack_add_kctl(codec, nid, name, idx, false);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_jack_add_kctl);
|
||||
|
||||
/* get the unique index number for the given kctl name */
|
||||
static int get_unique_index(struct hda_codec *codec, const char *name, int idx)
|
||||
{
|
||||
struct hda_jack_tbl *jack;
|
||||
int i, len = strlen(name);
|
||||
again:
|
||||
jack = codec->jacktbl.list;
|
||||
for (i = 0; i < codec->jacktbl.used; i++, jack++) {
|
||||
/* jack->kctl.id contains "XXX Jack" name string with index */
|
||||
if (jack->kctl &&
|
||||
!strncmp(name, jack->kctl->id.name, len) &&
|
||||
!strcmp(" Jack", jack->kctl->id.name + len) &&
|
||||
jack->kctl->id.index == idx) {
|
||||
idx++;
|
||||
goto again;
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
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 idx, err;
|
||||
bool phantom_jack;
|
||||
|
||||
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) {
|
||||
strlcpy(name, base_name, sizeof(name));
|
||||
idx = 0;
|
||||
} else
|
||||
snd_hda_get_pin_label(codec, nid, cfg, name, sizeof(name), &idx);
|
||||
if (phantom_jack)
|
||||
/* Example final name: "Internal Mic Phantom Jack" */
|
||||
strncat(name, " Phantom", sizeof(name) - strlen(name) - 1);
|
||||
idx = get_unique_index(codec, name, idx);
|
||||
err = __snd_hda_jack_add_kctl(codec, nid, name, idx, phantom_jack);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (!phantom_jack)
|
||||
return snd_hda_jack_detect_enable(codec, nid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hda_jack_add_kctls - Add kctls for all pins included in the given pincfg
|
||||
*/
|
||||
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,
|
||||
struct hda_jack_tbl *jack)
|
||||
{
|
||||
struct hda_jack_callback *cb;
|
||||
|
||||
for (cb = jack->callback; cb; cb = cb->next)
|
||||
cb->func(codec, cb);
|
||||
if (jack->gated_jack) {
|
||||
struct hda_jack_tbl *gated =
|
||||
snd_hda_jack_tbl_get(codec, jack->gated_jack);
|
||||
if (gated) {
|
||||
for (cb = gated->callback; cb; cb = cb->next)
|
||||
cb->func(codec, cb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_SHIFT) & 0x7f;
|
||||
|
||||
event = snd_hda_jack_tbl_get_from_tag(codec, tag);
|
||||
if (!event)
|
||||
return;
|
||||
event->jack_dirty = 1;
|
||||
|
||||
call_jack_callback(codec, event);
|
||||
snd_hda_jack_report_sync(codec);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_jack_unsol_event);
|
||||
|
||||
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, jack);
|
||||
}
|
||||
if (changes)
|
||||
snd_hda_jack_report_sync(codec);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_jack_poll_all);
|
||||
|
||||
93
sound/pci/hda/hda_jack.h
Normal file
93
sound/pci/hda/hda_jack.h
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Jack-detection handling for HD-audio
|
||||
*
|
||||
* Copyright (c) 2011 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This driver is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef __SOUND_HDA_JACK_H
|
||||
#define __SOUND_HDA_JACK_H
|
||||
|
||||
#include <linux/err.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 {
|
||||
struct hda_jack_tbl *tbl;
|
||||
hda_jack_callback_fn func;
|
||||
unsigned int private_data; /* arbitrary data */
|
||||
struct hda_jack_callback *next;
|
||||
};
|
||||
|
||||
struct hda_jack_tbl {
|
||||
hda_nid_t nid;
|
||||
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 */
|
||||
struct snd_kcontrol *kctl; /* assigned kctl for jack-detection */
|
||||
#ifdef CONFIG_SND_HDA_INPUT_JACK
|
||||
int type;
|
||||
struct snd_jack *jack;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct hda_jack_tbl *
|
||||
snd_hda_jack_tbl_get(struct hda_codec *codec, hda_nid_t nid);
|
||||
struct hda_jack_tbl *
|
||||
snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec, unsigned char tag);
|
||||
|
||||
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);
|
||||
struct hda_jack_callback *
|
||||
snd_hda_jack_detect_enable_callback(struct hda_codec *codec, hda_nid_t nid,
|
||||
hda_jack_callback_fn cb);
|
||||
|
||||
int snd_hda_jack_set_gating_jack(struct hda_codec *codec, hda_nid_t gated_nid,
|
||||
hda_nid_t gating_nid);
|
||||
|
||||
u32 snd_hda_pin_sense(struct hda_codec *codec, hda_nid_t nid);
|
||||
|
||||
/* 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(struct hda_codec *codec, hda_nid_t nid);
|
||||
|
||||
static inline bool snd_hda_jack_detect(struct hda_codec *codec, hda_nid_t nid)
|
||||
{
|
||||
return snd_hda_jack_detect_state(codec, nid) != HDA_JACK_NOT_PRESENT;
|
||||
}
|
||||
|
||||
bool is_jack_detectable(struct hda_codec *codec, hda_nid_t nid);
|
||||
|
||||
int snd_hda_jack_add_kctl(struct hda_codec *codec, hda_nid_t nid,
|
||||
const char *name, int idx);
|
||||
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 */
|
||||
808
sound/pci/hda/hda_local.h
Normal file
808
sound/pci/hda/hda_local.h
Normal file
|
|
@ -0,0 +1,808 @@
|
|||
/*
|
||||
* Universal Interface for Intel High Definition Audio Codec
|
||||
*
|
||||
* Local helper functions
|
||||
*
|
||||
* Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#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 */
|
||||
int snd_hda_codec_amp_read(struct hda_codec *codec, hda_nid_t nid, int ch,
|
||||
int direction, int index);
|
||||
int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid, int ch,
|
||||
int direction, int idx, int mask, int val);
|
||||
int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid,
|
||||
int dir, 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_codec_resume_amp(struct hda_codec *codec);
|
||||
|
||||
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 *slaves,
|
||||
const char *suffix, bool init_slave_vol,
|
||||
struct snd_kcontrol **ctl_ret);
|
||||
#define snd_hda_add_vmaster(codec, name, tlv, slaves, suffix) \
|
||||
__snd_hda_add_vmaster(codec, name, tlv, slaves, suffix, true, NULL)
|
||||
int snd_hda_codec_reset(struct hda_codec *codec);
|
||||
|
||||
enum {
|
||||
HDA_VMUTE_OFF,
|
||||
HDA_VMUTE_ON,
|
||||
HDA_VMUTE_FOLLOW_MASTER,
|
||||
};
|
||||
|
||||
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 */
|
||||
unsigned int mute_mode; /* HDA_VMUTE_XXX */
|
||||
struct hda_codec *codec;
|
||||
};
|
||||
|
||||
int snd_hda_add_vmaster_hook(struct hda_codec *codec,
|
||||
struct hda_vmaster_mute_hook *hook,
|
||||
bool expose_enum_ctl);
|
||||
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
|
||||
|
||||
/* mono switch binding multiple inputs */
|
||||
#define HDA_BIND_MUTE_MONO(xname, nid, channel, indices, direction) \
|
||||
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \
|
||||
.info = snd_hda_mixer_amp_switch_info, \
|
||||
.get = snd_hda_mixer_bind_switch_get, \
|
||||
.put = snd_hda_mixer_bind_switch_put, \
|
||||
.private_value = HDA_COMPOSE_AMP_VAL(nid, channel, indices, direction) }
|
||||
|
||||
/* stereo switch binding multiple inputs */
|
||||
#define HDA_BIND_MUTE(xname,nid,indices,dir) \
|
||||
HDA_BIND_MUTE_MONO(xname,nid,3,indices,dir)
|
||||
|
||||
int snd_hda_mixer_bind_switch_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol);
|
||||
int snd_hda_mixer_bind_switch_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol);
|
||||
|
||||
/* more generic bound controls */
|
||||
struct hda_ctl_ops {
|
||||
snd_kcontrol_info_t *info;
|
||||
snd_kcontrol_get_t *get;
|
||||
snd_kcontrol_put_t *put;
|
||||
snd_kcontrol_tlv_rw_t *tlv;
|
||||
};
|
||||
|
||||
extern struct hda_ctl_ops snd_hda_bind_vol; /* for bind-volume with TLV */
|
||||
extern struct hda_ctl_ops snd_hda_bind_sw; /* for bind-switch */
|
||||
|
||||
struct hda_bind_ctls {
|
||||
struct hda_ctl_ops *ops;
|
||||
unsigned long values[];
|
||||
};
|
||||
|
||||
int snd_hda_mixer_bind_ctls_info(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo);
|
||||
int snd_hda_mixer_bind_ctls_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol);
|
||||
int snd_hda_mixer_bind_ctls_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol);
|
||||
int snd_hda_mixer_bind_tlv(struct snd_kcontrol *kcontrol, int op_flag,
|
||||
unsigned int size, unsigned int __user *tlv);
|
||||
|
||||
#define HDA_BIND_VOL(xname, bindrec) \
|
||||
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
|
||||
.name = xname, \
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |\
|
||||
SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
|
||||
SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK,\
|
||||
.info = snd_hda_mixer_bind_ctls_info,\
|
||||
.get = snd_hda_mixer_bind_ctls_get,\
|
||||
.put = snd_hda_mixer_bind_ctls_put,\
|
||||
.tlv = { .c = snd_hda_mixer_bind_tlv },\
|
||||
.private_value = (long) (bindrec) }
|
||||
#define HDA_BIND_SW(xname, bindrec) \
|
||||
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,\
|
||||
.name = xname, \
|
||||
.info = snd_hda_mixer_bind_ctls_info,\
|
||||
.get = snd_hda_mixer_bind_ctls_get,\
|
||||
.put = snd_hda_mixer_bind_ctls_put,\
|
||||
.private_value = (long) (bindrec) }
|
||||
|
||||
/*
|
||||
* 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 16
|
||||
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_index_ret);
|
||||
|
||||
/*
|
||||
* Channel mode helper
|
||||
*/
|
||||
struct hda_channel_mode {
|
||||
int channels;
|
||||
const struct hda_verb *sequence;
|
||||
};
|
||||
|
||||
int snd_hda_ch_mode_info(struct hda_codec *codec,
|
||||
struct snd_ctl_elem_info *uinfo,
|
||||
const struct hda_channel_mode *chmode,
|
||||
int num_chmodes);
|
||||
int snd_hda_ch_mode_get(struct hda_codec *codec,
|
||||
struct snd_ctl_elem_value *ucontrol,
|
||||
const struct hda_channel_mode *chmode,
|
||||
int num_chmodes,
|
||||
int max_channels);
|
||||
int snd_hda_ch_mode_put(struct hda_codec *codec,
|
||||
struct snd_ctl_elem_value *ucontrol,
|
||||
const struct hda_channel_mode *chmode,
|
||||
int num_chmodes,
|
||||
int *max_channelsp);
|
||||
|
||||
/*
|
||||
* 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 *slave_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 codec parser
|
||||
*/
|
||||
int snd_hda_parse_generic_codec(struct hda_codec *codec);
|
||||
int snd_hda_parse_hdmi_codec(struct hda_codec *codec);
|
||||
|
||||
/*
|
||||
* generic proc interface
|
||||
*/
|
||||
#ifdef CONFIG_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_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);
|
||||
|
||||
|
||||
/*
|
||||
* unsolicited event handler
|
||||
*/
|
||||
|
||||
#define HDA_UNSOL_QUEUE_SIZE 64
|
||||
|
||||
struct hda_bus_unsolicited {
|
||||
/* ring buffer */
|
||||
u32 queue[HDA_UNSOL_QUEUE_SIZE * 2];
|
||||
unsigned int rp, wp;
|
||||
|
||||
/* workqueue */
|
||||
struct work_struct work;
|
||||
struct hda_bus *bus;
|
||||
};
|
||||
|
||||
/* 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);
|
||||
|
||||
/*
|
||||
* get widget capabilities
|
||||
*/
|
||||
static inline u32 get_wcaps(struct hda_codec *codec, hda_nid_t nid)
|
||||
{
|
||||
if (nid < codec->start_nid ||
|
||||
nid >= codec->start_nid + codec->num_nodes)
|
||||
return 0;
|
||||
return codec->wcaps[nid - codec->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->start_nid &&
|
||||
nid < codec->start_nid + codec->num_nodes)
|
||||
codec->wcaps[nid - codec->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);
|
||||
u32 snd_hda_query_pin_caps(struct hda_codec *codec, hda_nid_t nid);
|
||||
int snd_hda_override_pin_caps(struct hda_codec *codec, hda_nid_t nid,
|
||||
unsigned int 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)
|
||||
{
|
||||
unsigned int state = snd_hda_codec_read(codec, nid, 0,
|
||||
AC_VERB_GET_POWER_STATE, 0);
|
||||
if (state & AC_PWRST_ERROR)
|
||||
return true;
|
||||
state = (state >> 4) & 0x0f;
|
||||
return (state == target_state);
|
||||
}
|
||||
|
||||
unsigned int snd_hda_codec_eapd_power_filter(struct hda_codec *codec,
|
||||
hda_nid_t nid,
|
||||
unsigned int power_state);
|
||||
|
||||
/*
|
||||
* 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_entries, 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_PROC_FS
|
||||
void snd_hdmi_print_eld_info(struct hdmi_eld *eld,
|
||||
struct snd_info_buffer *buffer);
|
||||
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);
|
||||
|
||||
/*
|
||||
*/
|
||||
#define codec_err(codec, fmt, args...) dev_err(&(codec)->dev, fmt, ##args)
|
||||
#define codec_warn(codec, fmt, args...) dev_warn(&(codec)->dev, fmt, ##args)
|
||||
#define codec_info(codec, fmt, args...) dev_info(&(codec)->dev, fmt, ##args)
|
||||
#define codec_dbg(codec, fmt, args...) dev_dbg(&(codec)->dev, fmt, ##args)
|
||||
|
||||
#endif /* __SOUND_HDA_LOCAL_H */
|
||||
399
sound/pci/hda/hda_priv.h
Normal file
399
sound/pci/hda/hda_priv.h
Normal file
|
|
@ -0,0 +1,399 @@
|
|||
/*
|
||||
* Common defines for the alsa driver code base for HD Audio.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*/
|
||||
|
||||
#ifndef __SOUND_HDA_PRIV_H
|
||||
#define __SOUND_HDA_PRIV_H
|
||||
|
||||
#include <linux/clocksource.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
|
||||
/*
|
||||
* registers
|
||||
*/
|
||||
#define AZX_REG_GCAP 0x00
|
||||
#define AZX_GCAP_64OK (1 << 0) /* 64bit address support */
|
||||
#define AZX_GCAP_NSDO (3 << 1) /* # of serial data out signals */
|
||||
#define AZX_GCAP_BSS (31 << 3) /* # of bidirectional streams */
|
||||
#define AZX_GCAP_ISS (15 << 8) /* # of input streams */
|
||||
#define AZX_GCAP_OSS (15 << 12) /* # of output streams */
|
||||
#define AZX_REG_VMIN 0x02
|
||||
#define AZX_REG_VMAJ 0x03
|
||||
#define AZX_REG_OUTPAY 0x04
|
||||
#define AZX_REG_INPAY 0x06
|
||||
#define AZX_REG_GCTL 0x08
|
||||
#define AZX_GCTL_RESET (1 << 0) /* controller reset */
|
||||
#define AZX_GCTL_FCNTRL (1 << 1) /* flush control */
|
||||
#define AZX_GCTL_UNSOL (1 << 8) /* accept unsol. response enable */
|
||||
#define AZX_REG_WAKEEN 0x0c
|
||||
#define AZX_REG_STATESTS 0x0e
|
||||
#define AZX_REG_GSTS 0x10
|
||||
#define AZX_GSTS_FSTS (1 << 1) /* flush status */
|
||||
#define AZX_REG_INTCTL 0x20
|
||||
#define AZX_REG_INTSTS 0x24
|
||||
#define AZX_REG_WALLCLK 0x30 /* 24Mhz source */
|
||||
#define AZX_REG_OLD_SSYNC 0x34 /* SSYNC for old ICH */
|
||||
#define AZX_REG_SSYNC 0x38
|
||||
#define AZX_REG_CORBLBASE 0x40
|
||||
#define AZX_REG_CORBUBASE 0x44
|
||||
#define AZX_REG_CORBWP 0x48
|
||||
#define AZX_REG_CORBRP 0x4a
|
||||
#define AZX_CORBRP_RST (1 << 15) /* read pointer reset */
|
||||
#define AZX_REG_CORBCTL 0x4c
|
||||
#define AZX_CORBCTL_RUN (1 << 1) /* enable DMA */
|
||||
#define AZX_CORBCTL_CMEIE (1 << 0) /* enable memory error irq */
|
||||
#define AZX_REG_CORBSTS 0x4d
|
||||
#define AZX_CORBSTS_CMEI (1 << 0) /* memory error indication */
|
||||
#define AZX_REG_CORBSIZE 0x4e
|
||||
|
||||
#define AZX_REG_RIRBLBASE 0x50
|
||||
#define AZX_REG_RIRBUBASE 0x54
|
||||
#define AZX_REG_RIRBWP 0x58
|
||||
#define AZX_RIRBWP_RST (1 << 15) /* write pointer reset */
|
||||
#define AZX_REG_RINTCNT 0x5a
|
||||
#define AZX_REG_RIRBCTL 0x5c
|
||||
#define AZX_RBCTL_IRQ_EN (1 << 0) /* enable IRQ */
|
||||
#define AZX_RBCTL_DMA_EN (1 << 1) /* enable DMA */
|
||||
#define AZX_RBCTL_OVERRUN_EN (1 << 2) /* enable overrun irq */
|
||||
#define AZX_REG_RIRBSTS 0x5d
|
||||
#define AZX_RBSTS_IRQ (1 << 0) /* response irq */
|
||||
#define AZX_RBSTS_OVERRUN (1 << 2) /* overrun irq */
|
||||
#define AZX_REG_RIRBSIZE 0x5e
|
||||
|
||||
#define AZX_REG_IC 0x60
|
||||
#define AZX_REG_IR 0x64
|
||||
#define AZX_REG_IRS 0x68
|
||||
#define AZX_IRS_VALID (1<<1)
|
||||
#define AZX_IRS_BUSY (1<<0)
|
||||
|
||||
#define AZX_REG_DPLBASE 0x70
|
||||
#define AZX_REG_DPUBASE 0x74
|
||||
#define AZX_DPLBASE_ENABLE 0x1 /* Enable position buffer */
|
||||
|
||||
/* SD offset: SDI0=0x80, SDI1=0xa0, ... SDO3=0x160 */
|
||||
enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 };
|
||||
|
||||
/* stream register offsets from stream base */
|
||||
#define AZX_REG_SD_CTL 0x00
|
||||
#define AZX_REG_SD_STS 0x03
|
||||
#define AZX_REG_SD_LPIB 0x04
|
||||
#define AZX_REG_SD_CBL 0x08
|
||||
#define AZX_REG_SD_LVI 0x0c
|
||||
#define AZX_REG_SD_FIFOW 0x0e
|
||||
#define AZX_REG_SD_FIFOSIZE 0x10
|
||||
#define AZX_REG_SD_FORMAT 0x12
|
||||
#define AZX_REG_SD_BDLPL 0x18
|
||||
#define AZX_REG_SD_BDLPU 0x1c
|
||||
|
||||
/* PCI space */
|
||||
#define AZX_PCIREG_TCSEL 0x44
|
||||
|
||||
/*
|
||||
* other constants
|
||||
*/
|
||||
|
||||
/* max number of fragments - we may use more if allocating more pages for BDL */
|
||||
#define BDL_SIZE 4096
|
||||
#define AZX_MAX_BDL_ENTRIES (BDL_SIZE / 16)
|
||||
#define AZX_MAX_FRAG 32
|
||||
/* max buffer size - no h/w limit, you can increase as you like */
|
||||
#define AZX_MAX_BUF_SIZE (1024*1024*1024)
|
||||
|
||||
/* RIRB int mask: overrun[2], response[0] */
|
||||
#define RIRB_INT_RESPONSE 0x01
|
||||
#define RIRB_INT_OVERRUN 0x04
|
||||
#define RIRB_INT_MASK 0x05
|
||||
|
||||
/* STATESTS int mask: S3,SD2,SD1,SD0 */
|
||||
#define AZX_MAX_CODECS 8
|
||||
#define AZX_DEFAULT_CODECS 4
|
||||
#define STATESTS_INT_MASK ((1 << AZX_MAX_CODECS) - 1)
|
||||
|
||||
/* SD_CTL bits */
|
||||
#define SD_CTL_STREAM_RESET 0x01 /* stream reset bit */
|
||||
#define SD_CTL_DMA_START 0x02 /* stream DMA start bit */
|
||||
#define SD_CTL_STRIPE (3 << 16) /* stripe control */
|
||||
#define SD_CTL_TRAFFIC_PRIO (1 << 18) /* traffic priority */
|
||||
#define SD_CTL_DIR (1 << 19) /* bi-directional stream */
|
||||
#define SD_CTL_STREAM_TAG_MASK (0xf << 20)
|
||||
#define SD_CTL_STREAM_TAG_SHIFT 20
|
||||
|
||||
/* SD_CTL and SD_STS */
|
||||
#define SD_INT_DESC_ERR 0x10 /* descriptor error interrupt */
|
||||
#define SD_INT_FIFO_ERR 0x08 /* FIFO error interrupt */
|
||||
#define SD_INT_COMPLETE 0x04 /* completion interrupt */
|
||||
#define SD_INT_MASK (SD_INT_DESC_ERR|SD_INT_FIFO_ERR|\
|
||||
SD_INT_COMPLETE)
|
||||
|
||||
/* SD_STS */
|
||||
#define SD_STS_FIFO_READY 0x20 /* FIFO ready */
|
||||
|
||||
/* INTCTL and INTSTS */
|
||||
#define AZX_INT_ALL_STREAM 0xff /* all stream interrupts */
|
||||
#define AZX_INT_CTRL_EN 0x40000000 /* controller interrupt enable bit */
|
||||
#define AZX_INT_GLOBAL_EN 0x80000000 /* global interrupt enable bit */
|
||||
|
||||
/* below are so far hardcoded - should read registers in future */
|
||||
#define AZX_MAX_CORB_ENTRIES 256
|
||||
#define AZX_MAX_RIRB_ENTRIES 256
|
||||
|
||||
/* 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_ATI_SNOOP (1 << 10) /* ATI snoop enable */
|
||||
#define AZX_DCAPS_NVIDIA_SNOOP (1 << 11) /* Nvidia snoop enable */
|
||||
#define AZX_DCAPS_SCH_SNOOP (1 << 12) /* SCH/PCH snoop enable */
|
||||
#define AZX_DCAPS_RIRB_DELAY (1 << 13) /* Long delay in read loop */
|
||||
#define AZX_DCAPS_RIRB_PRE_DELAY (1 << 14) /* Put a delay before read */
|
||||
#define AZX_DCAPS_CTX_WORKAROUND (1 << 15) /* X-Fi workaround */
|
||||
#define AZX_DCAPS_POSFIX_LPIB (1 << 16) /* Use LPIB as default */
|
||||
#define AZX_DCAPS_POSFIX_VIA (1 << 17) /* Use VIACOMBO as default */
|
||||
#define AZX_DCAPS_NO_64BIT (1 << 18) /* No 64bit address */
|
||||
#define AZX_DCAPS_SYNC_WRITE (1 << 19) /* sync each cmd write */
|
||||
#define AZX_DCAPS_OLD_SSYNC (1 << 20) /* Old SSYNC reg for ICH */
|
||||
#define AZX_DCAPS_BUFSIZE (1 << 21) /* no buffer size alignment */
|
||||
#define AZX_DCAPS_ALIGN_BUFSIZE (1 << 22) /* buffer size alignment */
|
||||
#define AZX_DCAPS_4K_BDLE_BOUNDARY (1 << 23) /* BDLE in 4k boundary */
|
||||
#define AZX_DCAPS_REVERSE_ASSIGN (1 << 24) /* Assign devices in reverse order */
|
||||
#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_I915_POWERWELL (1 << 27) /* HSW i915 powerwell support */
|
||||
#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 */
|
||||
|
||||
/* HD Audio class code */
|
||||
#define PCI_CLASS_MULTIMEDIA_HD_AUDIO 0x0403
|
||||
|
||||
struct azx_dev {
|
||||
struct snd_dma_buffer bdl; /* BDL buffer */
|
||||
u32 *posbuf; /* position buffer pointer */
|
||||
|
||||
unsigned int bufsize; /* size of the play buffer in bytes */
|
||||
unsigned int period_bytes; /* size of the period in bytes */
|
||||
unsigned int frags; /* number for period in the play buffer */
|
||||
unsigned int fifo_size; /* FIFO size */
|
||||
unsigned long start_wallclk; /* start + minimum wallclk */
|
||||
unsigned long period_wallclk; /* wallclk for period */
|
||||
|
||||
void __iomem *sd_addr; /* stream descriptor pointer */
|
||||
|
||||
u32 sd_int_sta_mask; /* stream int status mask */
|
||||
|
||||
/* pcm support */
|
||||
struct snd_pcm_substream *substream; /* assigned substream,
|
||||
* set in PCM open
|
||||
*/
|
||||
unsigned int format_val; /* format value to be set in the
|
||||
* controller and the codec
|
||||
*/
|
||||
unsigned char stream_tag; /* assigned stream */
|
||||
unsigned char index; /* stream index */
|
||||
int assigned_key; /* last device# key assigned to */
|
||||
|
||||
unsigned int opened:1;
|
||||
unsigned int running:1;
|
||||
unsigned int irq_pending:1;
|
||||
unsigned int prepared:1;
|
||||
unsigned int locked:1;
|
||||
/*
|
||||
* For VIA:
|
||||
* A flag to ensure DMA position is 0
|
||||
* when link position is not greater than FIFO size
|
||||
*/
|
||||
unsigned int insufficient:1;
|
||||
unsigned int wc_marked:1;
|
||||
unsigned int no_period_wakeup:1;
|
||||
|
||||
struct timecounter azx_tc;
|
||||
struct cyclecounter azx_cc;
|
||||
|
||||
int delay_negative_threshold;
|
||||
|
||||
#ifdef CONFIG_SND_HDA_DSP_LOADER
|
||||
/* Allows dsp load to have sole access to the playback stream. */
|
||||
struct mutex dsp_mutex;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* CORB/RIRB */
|
||||
struct azx_rb {
|
||||
u32 *buf; /* CORB/RIRB buffer
|
||||
* Each CORB entry is 4byte, RIRB is 8byte
|
||||
*/
|
||||
dma_addr_t addr; /* physical address of CORB/RIRB buffer */
|
||||
/* for RIRB */
|
||||
unsigned short rp, wp; /* read/write pointers */
|
||||
int cmds[AZX_MAX_CODECS]; /* number of pending requests */
|
||||
u32 res[AZX_MAX_CODECS]; /* last read value */
|
||||
};
|
||||
|
||||
struct azx;
|
||||
|
||||
/* Functions to read/write to hda registers. */
|
||||
struct hda_controller_ops {
|
||||
/* Register Access */
|
||||
void (*reg_writel)(u32 value, u32 __iomem *addr);
|
||||
u32 (*reg_readl)(u32 __iomem *addr);
|
||||
void (*reg_writew)(u16 value, u16 __iomem *addr);
|
||||
u16 (*reg_readw)(u16 __iomem *addr);
|
||||
void (*reg_writeb)(u8 value, u8 __iomem *addr);
|
||||
u8 (*reg_readb)(u8 __iomem *addr);
|
||||
/* Disable msi if supported, PCI only */
|
||||
int (*disable_msi_reset_irq)(struct azx *);
|
||||
/* Allocation ops */
|
||||
int (*dma_alloc_pages)(struct azx *chip,
|
||||
int type,
|
||||
size_t size,
|
||||
struct snd_dma_buffer *buf);
|
||||
void (*dma_free_pages)(struct azx *chip, struct snd_dma_buffer *buf);
|
||||
int (*substream_alloc_pages)(struct azx *chip,
|
||||
struct snd_pcm_substream *substream,
|
||||
size_t size);
|
||||
int (*substream_free_pages)(struct azx *chip,
|
||||
struct snd_pcm_substream *substream);
|
||||
void (*pcm_mmap_prepare)(struct snd_pcm_substream *substream,
|
||||
struct vm_area_struct *area);
|
||||
/* Check if current position is acceptable */
|
||||
int (*position_check)(struct azx *chip, struct azx_dev *azx_dev);
|
||||
};
|
||||
|
||||
struct azx_pcm {
|
||||
struct azx *chip;
|
||||
struct snd_pcm *pcm;
|
||||
struct hda_codec *codec;
|
||||
struct hda_pcm_stream *hinfo[2];
|
||||
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 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;
|
||||
const int *jackpoll_ms; /* per-card jack poll interval */
|
||||
|
||||
/* 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];
|
||||
|
||||
/* pci resources */
|
||||
unsigned long addr;
|
||||
void __iomem *remap_addr;
|
||||
int irq;
|
||||
|
||||
/* locks */
|
||||
spinlock_t reg_lock;
|
||||
struct mutex open_mutex; /* Prevents concurrent open/close operations */
|
||||
|
||||
/* streams (x num_streams) */
|
||||
struct azx_dev *azx_dev;
|
||||
|
||||
/* PCM */
|
||||
struct list_head pcm_list; /* azx_pcm list */
|
||||
|
||||
/* HD codec */
|
||||
unsigned short codec_mask;
|
||||
int codec_probe_mask; /* copied from probe_mask option */
|
||||
struct hda_bus *bus;
|
||||
unsigned int beep_mode;
|
||||
|
||||
/* CORB/RIRB */
|
||||
struct azx_rb corb;
|
||||
struct azx_rb rirb;
|
||||
|
||||
/* CORB/RIRB and position buffers */
|
||||
struct snd_dma_buffer rb;
|
||||
struct snd_dma_buffer posbuf;
|
||||
|
||||
#ifdef CONFIG_SND_HDA_PATCH_LOADER
|
||||
const struct firmware *fw;
|
||||
#endif
|
||||
|
||||
/* flags */
|
||||
const int *bdl_pos_adj;
|
||||
int poll_count;
|
||||
unsigned int running:1;
|
||||
unsigned int initialized:1;
|
||||
unsigned int single_cmd:1;
|
||||
unsigned int polling_mode:1;
|
||||
unsigned int msi:1;
|
||||
unsigned int probing:1; /* codec probing phase */
|
||||
unsigned int snoop:1;
|
||||
unsigned int align_buffer_size:1;
|
||||
unsigned int region_requested:1;
|
||||
unsigned int disabled:1; /* disabled by VGA-switcher */
|
||||
|
||||
/* for debugging */
|
||||
unsigned int last_cmd[AZX_MAX_CODECS];
|
||||
|
||||
/* reboot notifier (for mysterious hangup problem at power-down) */
|
||||
struct notifier_block reboot_notifier;
|
||||
|
||||
#ifdef CONFIG_SND_HDA_DSP_LOADER
|
||||
struct azx_dev saved_azx_dev;
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef CONFIG_X86
|
||||
#define azx_snoop(chip) ((chip)->snoop)
|
||||
#else
|
||||
#define azx_snoop(chip) true
|
||||
#endif
|
||||
|
||||
/*
|
||||
* macros for easy use
|
||||
*/
|
||||
|
||||
#define azx_writel(chip, reg, value) \
|
||||
((chip)->ops->reg_writel(value, (chip)->remap_addr + AZX_REG_##reg))
|
||||
#define azx_readl(chip, reg) \
|
||||
((chip)->ops->reg_readl((chip)->remap_addr + AZX_REG_##reg))
|
||||
#define azx_writew(chip, reg, value) \
|
||||
((chip)->ops->reg_writew(value, (chip)->remap_addr + AZX_REG_##reg))
|
||||
#define azx_readw(chip, reg) \
|
||||
((chip)->ops->reg_readw((chip)->remap_addr + AZX_REG_##reg))
|
||||
#define azx_writeb(chip, reg, value) \
|
||||
((chip)->ops->reg_writeb(value, (chip)->remap_addr + AZX_REG_##reg))
|
||||
#define azx_readb(chip, reg) \
|
||||
((chip)->ops->reg_readb((chip)->remap_addr + AZX_REG_##reg))
|
||||
|
||||
#define azx_sd_writel(chip, dev, reg, value) \
|
||||
((chip)->ops->reg_writel(value, (dev)->sd_addr + AZX_REG_##reg))
|
||||
#define azx_sd_readl(chip, dev, reg) \
|
||||
((chip)->ops->reg_readl((dev)->sd_addr + AZX_REG_##reg))
|
||||
#define azx_sd_writew(chip, dev, reg, value) \
|
||||
((chip)->ops->reg_writew(value, (dev)->sd_addr + AZX_REG_##reg))
|
||||
#define azx_sd_readw(chip, dev, reg) \
|
||||
((chip)->ops->reg_readw((dev)->sd_addr + AZX_REG_##reg))
|
||||
#define azx_sd_writeb(chip, dev, reg, value) \
|
||||
((chip)->ops->reg_writeb(value, (dev)->sd_addr + AZX_REG_##reg))
|
||||
#define azx_sd_readb(chip, dev, reg) \
|
||||
((chip)->ops->reg_readb((dev)->sd_addr + AZX_REG_##reg))
|
||||
|
||||
#endif /* __SOUND_HDA_PRIV_H */
|
||||
871
sound/pci/hda/hda_proc.c
Normal file
871
sound/pci/hda/hda_proc.c
Normal file
|
|
@ -0,0 +1,871 @@
|
|||
/*
|
||||
* Universal Interface for Intel High Definition Audio Codec
|
||||
*
|
||||
* Generic proc interface
|
||||
*
|
||||
* Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
*
|
||||
* This driver is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This driver is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <sound/core.h>
|
||||
#include <linux/module.h>
|
||||
#include "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)");
|
||||
|
||||
static char *bits_names(unsigned int bits, char *names[], int size)
|
||||
{
|
||||
int i, n;
|
||||
static char buf[128];
|
||||
|
||||
for (i = 0, n = 0; i < size; i++) {
|
||||
if (bits & (1U<<i) && names[i])
|
||||
n += snprintf(buf + n, sizeof(buf) - n, " %s",
|
||||
names[i]);
|
||||
}
|
||||
buf[n] = '\0';
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *get_wid_type_name(unsigned int wid_value)
|
||||
{
|
||||
static char *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 pcm, type;
|
||||
struct hda_pcm *cpcm;
|
||||
for (pcm = 0; pcm < codec->num_pcms; pcm++) {
|
||||
cpcm = &codec->pcm_info[pcm];
|
||||
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 = snd_hda_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 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 = snd_hda_param_read(codec, nid, AC_PAR_PCM);
|
||||
unsigned int stream = snd_hda_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 char *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 char *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";
|
||||
}
|
||||
|
||||
static void print_pin_caps(struct snd_info_buffer *buffer,
|
||||
struct hda_codec *codec, hda_nid_t nid,
|
||||
int *supports_vref)
|
||||
{
|
||||
static char *jack_conns[4] = { "Jack", "N/A", "Fixed", "Both" };
|
||||
unsigned int caps, val;
|
||||
|
||||
caps = snd_hda_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->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],
|
||||
snd_hda_get_jack_type(caps),
|
||||
snd_hda_get_jack_connectivity(caps),
|
||||
snd_hda_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 = snd_hda_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 char *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 = snd_hda_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)
|
||||
snd_iprintf(buffer, " Power states: %s\n",
|
||||
bits_names(sup, names, ARRAY_SIZE(names)));
|
||||
|
||||
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 = snd_hda_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 != conn_len
|
||||
|| memcmp(list, conn, conn_len)) {
|
||||
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 =
|
||||
snd_hda_param_read(codec, codec->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_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\n", i,
|
||||
!!(dev_list[i] & AC_DE_PD),
|
||||
!!(dev_list[i] & AC_DE_ELDV),
|
||||
!!(dev_list[i] & AC_DE_IA));
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
int i, nodes;
|
||||
|
||||
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");
|
||||
|
||||
if (! codec->afg)
|
||||
return;
|
||||
snd_hda_power_up(codec);
|
||||
snd_iprintf(buffer, "Default PCM:\n");
|
||||
print_pcm_caps(buffer, codec, codec->afg);
|
||||
snd_iprintf(buffer, "Default Amp-In caps: ");
|
||||
print_amp_caps(buffer, codec, codec->afg, HDA_INPUT);
|
||||
snd_iprintf(buffer, "Default Amp-Out caps: ");
|
||||
print_amp_caps(buffer, codec, codec->afg, HDA_OUTPUT);
|
||||
snd_iprintf(buffer, "State of AFG node 0x%02x:\n", codec->afg);
|
||||
print_power_state(buffer, codec, codec->afg);
|
||||
|
||||
nodes = snd_hda_get_sub_nodes(codec, codec->afg, &nid);
|
||||
if (! nid || nodes < 0) {
|
||||
snd_iprintf(buffer, "Invalid AFG subtree\n");
|
||||
snd_hda_power_down(codec);
|
||||
return;
|
||||
}
|
||||
|
||||
print_gpio(buffer, codec, codec->afg);
|
||||
if (codec->proc_widget_hook)
|
||||
codec->proc_widget_hook(buffer, codec, codec->afg);
|
||||
|
||||
for (i = 0; i < nodes; i++, nid++) {
|
||||
unsigned int wid_caps =
|
||||
snd_hda_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(sizeof(hda_nid_t) * conn_len,
|
||||
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];
|
||||
struct snd_info_entry *entry;
|
||||
int err;
|
||||
|
||||
snprintf(name, sizeof(name), "codec#%d", codec->addr);
|
||||
err = snd_card_proc_new(codec->bus->card, name, &entry);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
snd_info_set_text_ops(entry, codec, print_codec_info);
|
||||
return 0;
|
||||
}
|
||||
|
||||
780
sound/pci/hda/hda_sysfs.c
Normal file
780
sound/pci/hda/hda_sysfs.c
Normal file
|
|
@ -0,0 +1,780 @@
|
|||
/*
|
||||
* 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 "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 sprintf(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 sprintf(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) \
|
||||
static ssize_t type##_show(struct device *dev, \
|
||||
struct device_attribute *attr, \
|
||||
char *buf) \
|
||||
{ \
|
||||
struct hda_codec *codec = dev_get_drvdata(dev); \
|
||||
return sprintf(buf, "0x%x\n", codec->type); \
|
||||
}
|
||||
|
||||
#define CODEC_INFO_STR_SHOW(type) \
|
||||
static ssize_t type##_show(struct device *dev, \
|
||||
struct device_attribute *attr, \
|
||||
char *buf) \
|
||||
{ \
|
||||
struct hda_codec *codec = dev_get_drvdata(dev); \
|
||||
return sprintf(buf, "%s\n", \
|
||||
codec->type ? codec->type : ""); \
|
||||
}
|
||||
|
||||
CODEC_INFO_SHOW(vendor_id);
|
||||
CODEC_INFO_SHOW(subsystem_id);
|
||||
CODEC_INFO_SHOW(revision_id);
|
||||
CODEC_INFO_SHOW(afg);
|
||||
CODEC_INFO_SHOW(mfg);
|
||||
CODEC_INFO_STR_SHOW(vendor_name);
|
||||
CODEC_INFO_STR_SHOW(chip_name);
|
||||
CODEC_INFO_STR_SHOW(modelname);
|
||||
|
||||
static ssize_t pin_configs_show(struct hda_codec *codec,
|
||||
struct snd_array *list,
|
||||
char *buf)
|
||||
{
|
||||
int i, len = 0;
|
||||
mutex_lock(&codec->user_mutex);
|
||||
for (i = 0; i < list->used; i++) {
|
||||
struct hda_pincfg *pin = snd_array_elem(list, i);
|
||||
len += sprintf(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 = snd_hda_codec_configure(codec);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
/* rebuild PCMs */
|
||||
err = snd_hda_codec_build_pcms(codec);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
/* rebuild mixers */
|
||||
err = snd_hda_codec_build_controls(codec);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
err = snd_card_register(codec->bus->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) \
|
||||
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->type = val; \
|
||||
return count; \
|
||||
}
|
||||
|
||||
#define CODEC_INFO_STR_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); \
|
||||
char *s = kstrndup_noeol(buf, 64); \
|
||||
if (!s) \
|
||||
return -ENOMEM; \
|
||||
kfree(codec->type); \
|
||||
codec->type = s; \
|
||||
return count; \
|
||||
}
|
||||
|
||||
CODEC_INFO_STORE(vendor_id);
|
||||
CODEC_INFO_STORE(subsystem_id);
|
||||
CODEC_INFO_STORE(revision_id);
|
||||
CODEC_INFO_STR_STORE(vendor_name);
|
||||
CODEC_INFO_STR_STORE(chip_name);
|
||||
CODEC_INFO_STR_STORE(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);
|
||||
int i, len = 0;
|
||||
mutex_lock(&codec->user_mutex);
|
||||
for (i = 0; i < codec->init_verbs.used; i++) {
|
||||
struct hda_verb *v = snd_array_elem(&codec->init_verbs, i);
|
||||
len += snprintf(buf + len, PAGE_SIZE - 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);
|
||||
int i, len = 0;
|
||||
mutex_lock(&codec->user_mutex);
|
||||
for (i = 0; i < codec->hints.used; i++) {
|
||||
struct hda_hint *hint = snd_array_elem(&codec->hints, i);
|
||||
len += snprintf(buf + len, PAGE_SIZE - 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)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < codec->hints.used; i++) {
|
||||
struct hda_hint *hint = snd_array_elem(&codec->hints, i);
|
||||
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);
|
||||
}
|
||||
|
||||
#define MAX_PIN_CONFIGS 32
|
||||
|
||||
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);
|
||||
|
||||
/*
|
||||
* Look for hint string
|
||||
*/
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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_entry(codec, &bus->codec_list, list) {
|
||||
if ((vendorid <= 0 || codec->vendor_id == vendorid) &&
|
||||
(subid <= 0 || codec->subsystem_id == subid) &&
|
||||
codec->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)
|
||||
{
|
||||
kfree((*codecp)->chip_name);
|
||||
(*codecp)->chip_name = kstrdup(buf, GFP_KERNEL);
|
||||
}
|
||||
|
||||
#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)->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 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;
|
||||
}
|
||||
|
||||
/*
|
||||
* load a "patch" firmware file and parse it
|
||||
*/
|
||||
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 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
|
||||
int i;
|
||||
|
||||
/* clear init verbs */
|
||||
snd_array_free(&codec->init_verbs);
|
||||
/* clear hints */
|
||||
for (i = 0; i < codec->hints.used; i++) {
|
||||
struct hda_hint *hint = snd_array_elem(&codec->hints, i);
|
||||
kfree(hint->key); /* we don't need to free hint->val */
|
||||
}
|
||||
snd_array_free(&codec->hints);
|
||||
snd_array_free(&codec->user_pins);
|
||||
#endif
|
||||
}
|
||||
556
sound/pci/hda/hda_tegra.c
Normal file
556
sound/pci/hda/hda_tegra.c
Normal file
|
|
@ -0,0 +1,556 @@
|
|||
/*
|
||||
*
|
||||
* Implementation of primary ALSA driver code base for NVIDIA Tegra HDA.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#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/slab.h>
|
||||
#include <linux/time.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/initval.h>
|
||||
|
||||
#include "hda_codec.h"
|
||||
#include "hda_controller.h"
|
||||
#include "hda_priv.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)
|
||||
|
||||
/* max number of SDs */
|
||||
#define NUM_CAPTURE_SD 1
|
||||
#define NUM_PLAYBACK_SD 1
|
||||
|
||||
struct hda_tegra {
|
||||
struct azx chip;
|
||||
struct device *dev;
|
||||
struct clk *hda_clk;
|
||||
struct clk *hda2codec_2x_clk;
|
||||
struct clk *hda2hdmi_clk;
|
||||
void __iomem *regs;
|
||||
};
|
||||
|
||||
#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
|
||||
static int power_save = 0;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* DMA page allocation ops.
|
||||
*/
|
||||
static int dma_alloc_pages(struct azx *chip, int type, size_t size,
|
||||
struct snd_dma_buffer *buf)
|
||||
{
|
||||
return snd_dma_alloc_pages(type, chip->card->dev, size, buf);
|
||||
}
|
||||
|
||||
static void dma_free_pages(struct azx *chip, struct snd_dma_buffer *buf)
|
||||
{
|
||||
snd_dma_free_pages(buf);
|
||||
}
|
||||
|
||||
static int substream_alloc_pages(struct azx *chip,
|
||||
struct snd_pcm_substream *substream,
|
||||
size_t size)
|
||||
{
|
||||
struct azx_dev *azx_dev = get_azx_dev(substream);
|
||||
|
||||
azx_dev->bufsize = 0;
|
||||
azx_dev->period_bytes = 0;
|
||||
azx_dev->format_val = 0;
|
||||
return snd_pcm_lib_malloc_pages(substream, size);
|
||||
}
|
||||
|
||||
static int substream_free_pages(struct azx *chip,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
return snd_pcm_lib_free_pages(substream);
|
||||
}
|
||||
|
||||
/*
|
||||
* Register access ops. Tegra HDA register access is DWORD only.
|
||||
*/
|
||||
static void hda_tegra_writel(u32 value, u32 *addr)
|
||||
{
|
||||
writel(value, addr);
|
||||
}
|
||||
|
||||
static u32 hda_tegra_readl(u32 *addr)
|
||||
{
|
||||
return readl(addr);
|
||||
}
|
||||
|
||||
static void hda_tegra_writew(u16 value, u16 *addr)
|
||||
{
|
||||
unsigned int shift = ((unsigned long)(addr) & 0x3) << 3;
|
||||
void *dword_addr = (void *)((unsigned long)(addr) & ~0x3);
|
||||
u32 v;
|
||||
|
||||
v = readl(dword_addr);
|
||||
v &= ~(0xffff << shift);
|
||||
v |= value << shift;
|
||||
writel(v, dword_addr);
|
||||
}
|
||||
|
||||
static u16 hda_tegra_readw(u16 *addr)
|
||||
{
|
||||
unsigned int shift = ((unsigned long)(addr) & 0x3) << 3;
|
||||
void *dword_addr = (void *)((unsigned long)(addr) & ~0x3);
|
||||
u32 v;
|
||||
|
||||
v = readl(dword_addr);
|
||||
return (v >> shift) & 0xffff;
|
||||
}
|
||||
|
||||
static void hda_tegra_writeb(u8 value, u8 *addr)
|
||||
{
|
||||
unsigned int shift = ((unsigned long)(addr) & 0x3) << 3;
|
||||
void *dword_addr = (void *)((unsigned long)(addr) & ~0x3);
|
||||
u32 v;
|
||||
|
||||
v = readl(dword_addr);
|
||||
v &= ~(0xff << shift);
|
||||
v |= value << shift;
|
||||
writel(v, dword_addr);
|
||||
}
|
||||
|
||||
static u8 hda_tegra_readb(u8 *addr)
|
||||
{
|
||||
unsigned int shift = ((unsigned long)(addr) & 0x3) << 3;
|
||||
void *dword_addr = (void *)((unsigned long)(addr) & ~0x3);
|
||||
u32 v;
|
||||
|
||||
v = readl(dword_addr);
|
||||
return (v >> shift) & 0xff;
|
||||
}
|
||||
|
||||
static const struct hda_controller_ops hda_tegra_ops = {
|
||||
.reg_writel = hda_tegra_writel,
|
||||
.reg_readl = hda_tegra_readl,
|
||||
.reg_writew = hda_tegra_writew,
|
||||
.reg_readw = hda_tegra_readw,
|
||||
.reg_writeb = hda_tegra_writeb,
|
||||
.reg_readb = hda_tegra_readb,
|
||||
.dma_alloc_pages = dma_alloc_pages,
|
||||
.dma_free_pages = dma_free_pages,
|
||||
.substream_alloc_pages = substream_alloc_pages,
|
||||
.substream_free_pages = substream_free_pages,
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static int hda_tegra_enable_clocks(struct hda_tegra *data)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = clk_prepare_enable(data->hda_clk);
|
||||
if (rc)
|
||||
return rc;
|
||||
rc = clk_prepare_enable(data->hda2codec_2x_clk);
|
||||
if (rc)
|
||||
goto disable_hda;
|
||||
rc = clk_prepare_enable(data->hda2hdmi_clk);
|
||||
if (rc)
|
||||
goto disable_codec_2x;
|
||||
|
||||
return 0;
|
||||
|
||||
disable_codec_2x:
|
||||
clk_disable_unprepare(data->hda2codec_2x_clk);
|
||||
disable_hda:
|
||||
clk_disable_unprepare(data->hda_clk);
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static void hda_tegra_disable_clocks(struct hda_tegra *data)
|
||||
{
|
||||
clk_disable_unprepare(data->hda2hdmi_clk);
|
||||
clk_disable_unprepare(data->hda2codec_2x_clk);
|
||||
clk_disable_unprepare(data->hda_clk);
|
||||
}
|
||||
|
||||
/*
|
||||
* power management
|
||||
*/
|
||||
static int hda_tegra_suspend(struct device *dev)
|
||||
{
|
||||
struct snd_card *card = dev_get_drvdata(dev);
|
||||
struct azx *chip = card->private_data;
|
||||
struct azx_pcm *p;
|
||||
struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip);
|
||||
|
||||
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
|
||||
list_for_each_entry(p, &chip->pcm_list, list)
|
||||
snd_pcm_suspend_all(p->pcm);
|
||||
if (chip->initialized)
|
||||
snd_hda_suspend(chip->bus);
|
||||
|
||||
azx_stop_chip(chip);
|
||||
azx_enter_link_reset(chip);
|
||||
hda_tegra_disable_clocks(hda);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hda_tegra_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);
|
||||
|
||||
hda_tegra_enable_clocks(hda);
|
||||
|
||||
hda_tegra_init(hda);
|
||||
|
||||
azx_init_chip(chip, 1);
|
||||
|
||||
snd_hda_resume(chip->bus);
|
||||
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_PM_SLEEP */
|
||||
|
||||
static const struct dev_pm_ops hda_tegra_pm = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(hda_tegra_suspend, hda_tegra_resume)
|
||||
};
|
||||
|
||||
/*
|
||||
* destructor
|
||||
*/
|
||||
static int hda_tegra_dev_free(struct snd_device *device)
|
||||
{
|
||||
int i;
|
||||
struct azx *chip = device->device_data;
|
||||
|
||||
azx_notifier_unregister(chip);
|
||||
|
||||
if (chip->initialized) {
|
||||
for (i = 0; i < chip->num_streams; i++)
|
||||
azx_stream_stop(chip, &chip->azx_dev[i]);
|
||||
azx_stop_chip(chip);
|
||||
}
|
||||
|
||||
azx_free_stream_pages(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 device *dev = hda->dev;
|
||||
struct resource *res;
|
||||
int err;
|
||||
|
||||
hda->hda_clk = devm_clk_get(dev, "hda");
|
||||
if (IS_ERR(hda->hda_clk))
|
||||
return PTR_ERR(hda->hda_clk);
|
||||
hda->hda2codec_2x_clk = devm_clk_get(dev, "hda2codec_2x");
|
||||
if (IS_ERR(hda->hda2codec_2x_clk))
|
||||
return PTR_ERR(hda->hda2codec_2x_clk);
|
||||
hda->hda2hdmi_clk = devm_clk_get(dev, "hda2hdmi");
|
||||
if (IS_ERR(hda->hda2hdmi_clk))
|
||||
return PTR_ERR(hda->hda2hdmi_clk);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
hda->regs = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(chip->remap_addr))
|
||||
return PTR_ERR(chip->remap_addr);
|
||||
|
||||
chip->remap_addr = hda->regs + HDA_BAR0;
|
||||
chip->addr = res->start + HDA_BAR0;
|
||||
|
||||
err = hda_tegra_enable_clocks(hda);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
hda_tegra_init(hda);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The codecs were powered up in snd_hda_codec_new().
|
||||
* Now all initialization done, so turn them down if possible
|
||||
*/
|
||||
static void power_down_all_codecs(struct azx *chip)
|
||||
{
|
||||
struct hda_codec *codec;
|
||||
list_for_each_entry(codec, &chip->bus->codec_list, list)
|
||||
snd_hda_power_down(codec);
|
||||
}
|
||||
|
||||
static int hda_tegra_first_init(struct azx *chip, struct platform_device *pdev)
|
||||
{
|
||||
struct snd_card *card = chip->card;
|
||||
int err;
|
||||
unsigned short gcap;
|
||||
int irq_id = platform_get_irq(pdev, 0);
|
||||
|
||||
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;
|
||||
}
|
||||
chip->irq = irq_id;
|
||||
|
||||
synchronize_irq(chip->irq);
|
||||
|
||||
gcap = azx_readw(chip, GCAP);
|
||||
dev_dbg(card->dev, "chipset global capabilities = 0x%x\n", gcap);
|
||||
|
||||
/* 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 */
|
||||
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;
|
||||
chip->azx_dev = devm_kcalloc(card->dev, chip->num_streams,
|
||||
sizeof(*chip->azx_dev), GFP_KERNEL);
|
||||
if (!chip->azx_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
err = azx_alloc_stream_pages(chip);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* initialize streams */
|
||||
azx_init_stream(chip);
|
||||
|
||||
/* initialize chip */
|
||||
azx_init_chip(chip, 1);
|
||||
|
||||
/* codec detection */
|
||||
if (!chip->codec_mask) {
|
||||
dev_err(card->dev, "no codecs found!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
strcpy(card->driver, "tegra-hda");
|
||||
strcpy(card->shortname, "tegra-hda");
|
||||
snprintf(card->longname, sizeof(card->longname),
|
||||
"%s at 0x%lx irq %i",
|
||||
card->shortname, chip->addr, chip->irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* constructor
|
||||
*/
|
||||
static int hda_tegra_create(struct snd_card *card,
|
||||
unsigned int driver_caps,
|
||||
const struct hda_controller_ops *hda_ops,
|
||||
struct hda_tegra *hda)
|
||||
{
|
||||
static struct snd_device_ops ops = {
|
||||
.dev_free = hda_tegra_dev_free,
|
||||
};
|
||||
struct azx *chip;
|
||||
int err;
|
||||
|
||||
chip = &hda->chip;
|
||||
|
||||
spin_lock_init(&chip->reg_lock);
|
||||
mutex_init(&chip->open_mutex);
|
||||
chip->card = card;
|
||||
chip->ops = hda_ops;
|
||||
chip->irq = -1;
|
||||
chip->driver_caps = driver_caps;
|
||||
chip->driver_type = driver_caps & 0xff;
|
||||
chip->dev_index = 0;
|
||||
INIT_LIST_HEAD(&chip->pcm_list);
|
||||
|
||||
chip->codec_probe_mask = -1;
|
||||
|
||||
chip->single_cmd = false;
|
||||
chip->snoop = true;
|
||||
|
||||
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 of_device_id hda_tegra_match[] = {
|
||||
{ .compatible = "nvidia,tegra30-hda" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, hda_tegra_match);
|
||||
|
||||
static int hda_tegra_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_card *card;
|
||||
struct azx *chip;
|
||||
struct hda_tegra *hda;
|
||||
int err;
|
||||
const unsigned int driver_flags = AZX_DCAPS_RIRB_DELAY;
|
||||
|
||||
hda = devm_kzalloc(&pdev->dev, sizeof(*hda), GFP_KERNEL);
|
||||
if (!hda)
|
||||
return -ENOMEM;
|
||||
hda->dev = &pdev->dev;
|
||||
chip = &hda->chip;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
err = hda_tegra_create(card, driver_flags, &hda_tegra_ops, hda);
|
||||
if (err < 0)
|
||||
goto out_free;
|
||||
card->private_data = chip;
|
||||
|
||||
dev_set_drvdata(&pdev->dev, card);
|
||||
|
||||
err = hda_tegra_first_init(chip, pdev);
|
||||
if (err < 0)
|
||||
goto out_free;
|
||||
|
||||
/* create codec instances */
|
||||
err = azx_codec_create(chip, NULL, 0, &power_save);
|
||||
if (err < 0)
|
||||
goto out_free;
|
||||
|
||||
err = azx_codec_configure(chip);
|
||||
if (err < 0)
|
||||
goto out_free;
|
||||
|
||||
/* create PCM streams */
|
||||
err = snd_hda_build_pcms(chip->bus);
|
||||
if (err < 0)
|
||||
goto out_free;
|
||||
|
||||
/* create mixer controls */
|
||||
err = azx_mixer_create(chip);
|
||||
if (err < 0)
|
||||
goto out_free;
|
||||
|
||||
err = snd_card_register(chip->card);
|
||||
if (err < 0)
|
||||
goto out_free;
|
||||
|
||||
chip->running = 1;
|
||||
power_down_all_codecs(chip);
|
||||
azx_notifier_register(chip);
|
||||
|
||||
return 0;
|
||||
|
||||
out_free:
|
||||
snd_card_free(card);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int hda_tegra_remove(struct platform_device *pdev)
|
||||
{
|
||||
return snd_card_free(dev_get_drvdata(&pdev->dev));
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
module_platform_driver(tegra_platform_hda);
|
||||
|
||||
MODULE_DESCRIPTION("Tegra HDA bus driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
143
sound/pci/hda/hda_trace.h
Normal file
143
sound/pci/hda/hda_trace.h
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
#undef TRACE_SYSTEM
|
||||
#define TRACE_SYSTEM hda
|
||||
#define TRACE_INCLUDE_FILE hda_trace
|
||||
|
||||
#if !defined(_TRACE_HDA_H) || defined(TRACE_HEADER_MULTI_READ)
|
||||
#define _TRACE_HDA_H
|
||||
|
||||
#include <linux/tracepoint.h>
|
||||
|
||||
struct hda_bus;
|
||||
struct hda_codec;
|
||||
|
||||
DECLARE_EVENT_CLASS(hda_cmd,
|
||||
|
||||
TP_PROTO(struct hda_codec *codec, unsigned int val),
|
||||
|
||||
TP_ARGS(codec, val),
|
||||
|
||||
TP_STRUCT__entry(
|
||||
__field( unsigned int, card )
|
||||
__field( unsigned int, addr )
|
||||
__field( unsigned int, val )
|
||||
),
|
||||
|
||||
TP_fast_assign(
|
||||
__entry->card = (codec)->bus->card->number;
|
||||
__entry->addr = (codec)->addr;
|
||||
__entry->val = (val);
|
||||
),
|
||||
|
||||
TP_printk("[%d:%d] val=%x", __entry->card, __entry->addr, __entry->val)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(hda_cmd, hda_send_cmd,
|
||||
TP_PROTO(struct hda_codec *codec, unsigned int val),
|
||||
TP_ARGS(codec, val)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(hda_cmd, hda_get_response,
|
||||
TP_PROTO(struct hda_codec *codec, unsigned int val),
|
||||
TP_ARGS(codec, val)
|
||||
);
|
||||
|
||||
TRACE_EVENT(hda_bus_reset,
|
||||
|
||||
TP_PROTO(struct hda_bus *bus),
|
||||
|
||||
TP_ARGS(bus),
|
||||
|
||||
TP_STRUCT__entry(
|
||||
__field( unsigned int, card )
|
||||
),
|
||||
|
||||
TP_fast_assign(
|
||||
__entry->card = (bus)->card->number;
|
||||
),
|
||||
|
||||
TP_printk("[%d]", __entry->card)
|
||||
);
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
DECLARE_EVENT_CLASS(hda_power,
|
||||
|
||||
TP_PROTO(struct hda_codec *codec),
|
||||
|
||||
TP_ARGS(codec),
|
||||
|
||||
TP_STRUCT__entry(
|
||||
__field( unsigned int, card )
|
||||
__field( unsigned int, addr )
|
||||
),
|
||||
|
||||
TP_fast_assign(
|
||||
__entry->card = (codec)->bus->card->number;
|
||||
__entry->addr = (codec)->addr;
|
||||
),
|
||||
|
||||
TP_printk("[%d:%d]", __entry->card, __entry->addr)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(hda_power, hda_power_down,
|
||||
TP_PROTO(struct hda_codec *codec),
|
||||
TP_ARGS(codec)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(hda_power, hda_power_up,
|
||||
TP_PROTO(struct hda_codec *codec),
|
||||
TP_ARGS(codec)
|
||||
);
|
||||
|
||||
TRACE_EVENT(hda_power_count,
|
||||
TP_PROTO(struct hda_codec *codec),
|
||||
TP_ARGS(codec),
|
||||
TP_STRUCT__entry(
|
||||
__field( unsigned int, card )
|
||||
__field( unsigned int, addr )
|
||||
__field( int, power_count )
|
||||
__field( int, power_on )
|
||||
__field( int, power_transition )
|
||||
),
|
||||
|
||||
TP_fast_assign(
|
||||
__entry->card = (codec)->bus->card->number;
|
||||
__entry->addr = (codec)->addr;
|
||||
__entry->power_count = (codec)->power_count;
|
||||
__entry->power_on = (codec)->power_on;
|
||||
__entry->power_transition = (codec)->power_transition;
|
||||
),
|
||||
|
||||
TP_printk("[%d:%d] power_count=%d, power_on=%d, power_transition=%d",
|
||||
__entry->card, __entry->addr, __entry->power_count,
|
||||
__entry->power_on, __entry->power_transition)
|
||||
);
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
TRACE_EVENT(hda_unsol_event,
|
||||
|
||||
TP_PROTO(struct hda_bus *bus, u32 res, u32 res_ex),
|
||||
|
||||
TP_ARGS(bus, res, res_ex),
|
||||
|
||||
TP_STRUCT__entry(
|
||||
__field( unsigned int, card )
|
||||
__field( u32, res )
|
||||
__field( u32, res_ex )
|
||||
),
|
||||
|
||||
TP_fast_assign(
|
||||
__entry->card = (bus)->card->number;
|
||||
__entry->res = res;
|
||||
__entry->res_ex = res_ex;
|
||||
),
|
||||
|
||||
TP_printk("[%d] res=%x, res_ex=%x", __entry->card,
|
||||
__entry->res, __entry->res_ex)
|
||||
);
|
||||
|
||||
#endif /* _TRACE_HDA_H */
|
||||
|
||||
/* This part must be outside protection */
|
||||
#undef TRACE_INCLUDE_PATH
|
||||
#define TRACE_INCLUDE_PATH .
|
||||
#include <trace/define_trace.h>
|
||||
1205
sound/pci/hda/patch_analog.c
Normal file
1205
sound/pci/hda/patch_analog.c
Normal file
File diff suppressed because it is too large
Load diff
117
sound/pci/hda/patch_ca0110.c
Normal file
117
sound/pci/hda/patch_ca0110.c
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* HD audio interface patch for Creative X-Fi CA0110-IBG chip
|
||||
*
|
||||
* Copyright (c) 2008 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This driver is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This driver is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <sound/core.h>
|
||||
#include "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;
|
||||
|
||||
spec->multi_cap_vol = 1;
|
||||
codec->bus->needs_damn_long_delay = 1;
|
||||
|
||||
err = ca0110_parse_auto_config(codec);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
codec->patch_ops = ca0110_patch_ops;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
snd_hda_gen_free(codec);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* patch entries
|
||||
*/
|
||||
static const struct hda_codec_preset snd_hda_preset_ca0110[] = {
|
||||
{ .id = 0x1102000a, .name = "CA0110-IBG", .patch = patch_ca0110 },
|
||||
{ .id = 0x1102000b, .name = "CA0110-IBG", .patch = patch_ca0110 },
|
||||
{ .id = 0x1102000d, .name = "SB0880 X-Fi", .patch = patch_ca0110 },
|
||||
{} /* terminator */
|
||||
};
|
||||
|
||||
MODULE_ALIAS("snd-hda-codec-id:1102000a");
|
||||
MODULE_ALIAS("snd-hda-codec-id:1102000b");
|
||||
MODULE_ALIAS("snd-hda-codec-id:1102000d");
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Creative CA0110-IBG HD-audio codec");
|
||||
|
||||
static struct hda_codec_preset_list ca0110_list = {
|
||||
.preset = snd_hda_preset_ca0110,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int __init patch_ca0110_init(void)
|
||||
{
|
||||
return snd_hda_add_codec_preset(&ca0110_list);
|
||||
}
|
||||
|
||||
static void __exit patch_ca0110_exit(void)
|
||||
{
|
||||
snd_hda_delete_codec_preset(&ca0110_list);
|
||||
}
|
||||
|
||||
module_init(patch_ca0110_init)
|
||||
module_exit(patch_ca0110_exit)
|
||||
4720
sound/pci/hda/patch_ca0132.c
Normal file
4720
sound/pci/hda/patch_ca0132.c
Normal file
File diff suppressed because it is too large
Load diff
1240
sound/pci/hda/patch_cirrus.c
Normal file
1240
sound/pci/hda/patch_cirrus.c
Normal file
File diff suppressed because it is too large
Load diff
156
sound/pci/hda/patch_cmedia.c
Normal file
156
sound/pci/hda/patch_cmedia.c
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* Universal Interface for Intel High Definition Audio Codec
|
||||
*
|
||||
* HD audio interface patch for C-Media CMI9880
|
||||
*
|
||||
* Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
*
|
||||
* This driver is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This driver is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <sound/core.h>
|
||||
#include "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;
|
||||
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;
|
||||
|
||||
codec->patch_ops = cmi_auto_patch_ops;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
codec->patch_ops = cmi_auto_patch_ops;
|
||||
return 0;
|
||||
|
||||
error:
|
||||
snd_hda_gen_free(codec);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* patch entries
|
||||
*/
|
||||
static const struct hda_codec_preset snd_hda_preset_cmedia[] = {
|
||||
{ .id = 0x13f68888, .name = "CMI8888", .patch = patch_cmi8888 },
|
||||
{ .id = 0x13f69880, .name = "CMI9880", .patch = patch_cmi9880 },
|
||||
{ .id = 0x434d4980, .name = "CMI9880", .patch = patch_cmi9880 },
|
||||
{} /* terminator */
|
||||
};
|
||||
|
||||
MODULE_ALIAS("snd-hda-codec-id:13f68888");
|
||||
MODULE_ALIAS("snd-hda-codec-id:13f69880");
|
||||
MODULE_ALIAS("snd-hda-codec-id:434d4980");
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("C-Media HD-audio codec");
|
||||
|
||||
static struct hda_codec_preset_list cmedia_list = {
|
||||
.preset = snd_hda_preset_cmedia,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int __init patch_cmedia_init(void)
|
||||
{
|
||||
return snd_hda_add_codec_preset(&cmedia_list);
|
||||
}
|
||||
|
||||
static void __exit patch_cmedia_exit(void)
|
||||
{
|
||||
snd_hda_delete_codec_preset(&cmedia_list);
|
||||
}
|
||||
|
||||
module_init(patch_cmedia_init)
|
||||
module_exit(patch_cmedia_exit)
|
||||
1037
sound/pci/hda/patch_conexant.c
Normal file
1037
sound/pci/hda/patch_conexant.c
Normal file
File diff suppressed because it is too large
Load diff
3457
sound/pci/hda/patch_hdmi.c
Normal file
3457
sound/pci/hda/patch_hdmi.c
Normal file
File diff suppressed because it is too large
Load diff
6435
sound/pci/hda/patch_realtek.c
Normal file
6435
sound/pci/hda/patch_realtek.c
Normal file
File diff suppressed because it is too large
Load diff
338
sound/pci/hda/patch_si3054.c
Normal file
338
sound/pci/hda/patch_si3054.c
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
/*
|
||||
* 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>
|
||||
*
|
||||
*
|
||||
* This driver is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This driver is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <sound/core.h>
|
||||
#include "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;
|
||||
struct hda_pcm pcm;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* 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 unsigned int rates[] = { 8000, 9600, 16000 };
|
||||
static 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 si3054_spec *spec = codec->spec;
|
||||
struct hda_pcm *info = &spec->pcm;
|
||||
codec->num_pcms = 1;
|
||||
codec->pcm_info = info;
|
||||
info->name = "Si3054 Modem";
|
||||
info->stream[SNDRV_PCM_STREAM_PLAYBACK] = si3054_pcm;
|
||||
info->stream[SNDRV_PCM_STREAM_CAPTURE] = si3054_pcm;
|
||||
info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = codec->mfg;
|
||||
info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = codec->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;
|
||||
|
||||
snd_hda_codec_write(codec, AC_NODE_ROOT, 0, AC_VERB_SET_CODEC_RESET, 0);
|
||||
snd_hda_codec_write(codec, codec->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_codec_preset snd_hda_preset_si3054[] = {
|
||||
{ .id = 0x163c3055, .name = "Si3054", .patch = patch_si3054 },
|
||||
{ .id = 0x163c3155, .name = "Si3054", .patch = patch_si3054 },
|
||||
{ .id = 0x11c13026, .name = "Si3054", .patch = patch_si3054 },
|
||||
{ .id = 0x11c13055, .name = "Si3054", .patch = patch_si3054 },
|
||||
{ .id = 0x11c13155, .name = "Si3054", .patch = patch_si3054 },
|
||||
{ .id = 0x10573055, .name = "Si3054", .patch = patch_si3054 },
|
||||
{ .id = 0x10573057, .name = "Si3054", .patch = patch_si3054 },
|
||||
{ .id = 0x10573155, .name = "Si3054", .patch = patch_si3054 },
|
||||
/* VIA HDA on Clevo m540 */
|
||||
{ .id = 0x11063288, .name = "Si3054", .patch = patch_si3054 },
|
||||
/* Asus A8J Modem (SM56) */
|
||||
{ .id = 0x15433155, .name = "Si3054", .patch = patch_si3054 },
|
||||
/* LG LW20 modem */
|
||||
{ .id = 0x18540018, .name = "Si3054", .patch = patch_si3054 },
|
||||
{}
|
||||
};
|
||||
|
||||
MODULE_ALIAS("snd-hda-codec-id:163c3055");
|
||||
MODULE_ALIAS("snd-hda-codec-id:163c3155");
|
||||
MODULE_ALIAS("snd-hda-codec-id:11c13026");
|
||||
MODULE_ALIAS("snd-hda-codec-id:11c13055");
|
||||
MODULE_ALIAS("snd-hda-codec-id:11c13155");
|
||||
MODULE_ALIAS("snd-hda-codec-id:10573055");
|
||||
MODULE_ALIAS("snd-hda-codec-id:10573057");
|
||||
MODULE_ALIAS("snd-hda-codec-id:10573155");
|
||||
MODULE_ALIAS("snd-hda-codec-id:11063288");
|
||||
MODULE_ALIAS("snd-hda-codec-id:15433155");
|
||||
MODULE_ALIAS("snd-hda-codec-id:18540018");
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Si3054 HD-audio modem codec");
|
||||
|
||||
static struct hda_codec_preset_list si3054_list = {
|
||||
.preset = snd_hda_preset_si3054,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int __init patch_si3054_init(void)
|
||||
{
|
||||
return snd_hda_add_codec_preset(&si3054_list);
|
||||
}
|
||||
|
||||
static void __exit patch_si3054_exit(void)
|
||||
{
|
||||
snd_hda_delete_codec_preset(&si3054_list);
|
||||
}
|
||||
|
||||
module_init(patch_si3054_init)
|
||||
module_exit(patch_si3054_exit)
|
||||
5110
sound/pci/hda/patch_sigmatel.c
Normal file
5110
sound/pci/hda/patch_sigmatel.c
Normal file
File diff suppressed because it is too large
Load diff
1906
sound/pci/hda/patch_via.c
Normal file
1906
sound/pci/hda/patch_via.c
Normal file
File diff suppressed because it is too large
Load diff
103
sound/pci/hda/thinkpad_helper.c
Normal file
103
sound/pci/hda/thinkpad_helper.c
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/* Helper functions for Thinkpad LED control;
|
||||
* to be included from codec driver
|
||||
*/
|
||||
|
||||
#if IS_ENABLED(CONFIG_THINKPAD_ACPI)
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/thinkpad_acpi.h>
|
||||
|
||||
static int (*led_set_func)(int, bool);
|
||||
static void (*old_vmaster_hook)(void *, int);
|
||||
|
||||
static acpi_status acpi_check_cb(acpi_handle handle, u32 lvl, void *context,
|
||||
void **rv)
|
||||
{
|
||||
bool *found = context;
|
||||
*found = true;
|
||||
return AE_OK;
|
||||
}
|
||||
|
||||
static bool is_thinkpad(struct hda_codec *codec)
|
||||
{
|
||||
bool found = false;
|
||||
if (codec->subsystem_id >> 16 != 0x17aa)
|
||||
return false;
|
||||
if (ACPI_SUCCESS(acpi_get_devices("LEN0068", acpi_check_cb, &found, NULL)) && found)
|
||||
return true;
|
||||
found = false;
|
||||
return ACPI_SUCCESS(acpi_get_devices("IBM0068", acpi_check_cb, &found, NULL)) && found;
|
||||
}
|
||||
|
||||
static void update_tpacpi_mute_led(void *private_data, int enabled)
|
||||
{
|
||||
if (old_vmaster_hook)
|
||||
old_vmaster_hook(private_data, enabled);
|
||||
|
||||
if (led_set_func)
|
||||
led_set_func(TPACPI_LED_MUTE, !enabled);
|
||||
}
|
||||
|
||||
static void update_tpacpi_micmute_led(struct hda_codec *codec,
|
||||
struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
if (!ucontrol || !led_set_func)
|
||||
return;
|
||||
if (strcmp("Capture Switch", ucontrol->id.name) == 0 && ucontrol->id.index == 0) {
|
||||
/* TODO: How do I verify if it's a mono or stereo here? */
|
||||
bool val = ucontrol->value.integer.value[0] || ucontrol->value.integer.value[1];
|
||||
led_set_func(TPACPI_LED_MICMUTE, !val);
|
||||
}
|
||||
}
|
||||
|
||||
static void hda_fixup_thinkpad_acpi(struct hda_codec *codec,
|
||||
const struct hda_fixup *fix, int action)
|
||||
{
|
||||
struct hda_gen_spec *spec = codec->spec;
|
||||
bool removefunc = false;
|
||||
|
||||
if (action == HDA_FIXUP_ACT_PROBE) {
|
||||
if (!is_thinkpad(codec))
|
||||
return;
|
||||
if (!led_set_func)
|
||||
led_set_func = symbol_request(tpacpi_led_set);
|
||||
if (!led_set_func) {
|
||||
codec_warn(codec,
|
||||
"Failed to find thinkpad-acpi symbol tpacpi_led_set\n");
|
||||
return;
|
||||
}
|
||||
|
||||
removefunc = true;
|
||||
if (led_set_func(TPACPI_LED_MUTE, false) >= 0) {
|
||||
old_vmaster_hook = spec->vmaster_mute.hook;
|
||||
spec->vmaster_mute.hook = update_tpacpi_mute_led;
|
||||
spec->vmaster_mute_enum = 1;
|
||||
removefunc = false;
|
||||
}
|
||||
if (led_set_func(TPACPI_LED_MICMUTE, false) >= 0) {
|
||||
if (spec->num_adc_nids > 1)
|
||||
codec_dbg(codec,
|
||||
"Skipping micmute LED control due to several ADCs");
|
||||
else {
|
||||
spec->cap_sync_hook = update_tpacpi_micmute_led;
|
||||
removefunc = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (led_set_func && (action == HDA_FIXUP_ACT_FREE || removefunc)) {
|
||||
symbol_put(tpacpi_led_set);
|
||||
led_set_func = NULL;
|
||||
old_vmaster_hook = 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 */
|
||||
Loading…
Add table
Add a link
Reference in a new issue