Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
# SPDX-License-Identifier: GPL-2.0-only
config SND_HDA
tristate
select SND_PCM
select SND_VMASTER
select SND_JACK
select SND_HDA_CORE
if SND_HDA
config SND_HDA_HWDEP
bool "Build hwdep interface for HD-audio driver"
select SND_HWDEP
help
Say Y here to build a hwdep interface for HD-audio driver.
This interface can be used for out-of-band communication
with codecs for debugging purposes.
config SND_HDA_RECONFIG
bool "Allow dynamic codec reconfiguration"
help
Say Y here to enable the HD-audio codec re-configuration feature.
It allows user to clear the whole codec configuration, change the
codec setup, add extra verbs, and re-configure the codec dynamically.
Note that this item alone doesn't provide the sysfs interface, but
enables the feature just for the patch loader below.
If you need the traditional sysfs entries for the manual interaction,
turn on CONFIG_SND_HDA_HWDEP as well.
config SND_HDA_INPUT_BEEP
bool "Support digital beep via input layer"
depends on INPUT=y || INPUT=SND_HDA
help
Say Y here to build a digital beep interface for HD-audio
driver. This interface is used to generate digital beeps.
config SND_HDA_INPUT_BEEP_MODE
int "Digital beep registration mode (0=off, 1=on)"
depends on SND_HDA_INPUT_BEEP=y
default "1"
range 0 1
help
Set 0 to disable the digital beep interface for HD-audio by default.
Set 1 to always enable the digital beep interface for HD-audio by
default.
config SND_HDA_PATCH_LOADER
bool "Support initialization patch loading for HD-audio"
select FW_LOADER
select SND_HDA_RECONFIG
help
Say Y here to allow the HD-audio driver to load a pseudo
firmware file ("patch") for overriding the BIOS setup at
start up. The "patch" file can be specified via patch module
option, such as patch=hda-init.
config SND_HDA_POWER_SAVE_DEFAULT
int "Default time-out for HD-audio power-save mode"
depends on PM
default 0
help
The default time-out value in seconds for HD-audio automatic
power-save mode. 0 means to disable the power-save mode.
config SND_HDA_CTL_DEV_ID
bool "Use the device identifier field for controls"
depends on SND_HDA_INTEL
help
Say Y to use the device identifier field for (mixer)
controls (old behaviour until this option is available).
When enabled, the multiple HDA codecs may set the device
field in control (mixer) element identifiers. The use
of this field is not recommended and defined for mixer controls.
The old behaviour (Y) is obsolete and will be removed. Consider
to not enable this option.
config SND_HDA_PREALLOC_SIZE
int "Pre-allocated buffer size for HD-audio driver"
range 0 32768
default 0 if SND_DMA_SGBUF
default 64 if !SND_DMA_SGBUF
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.
On x86 systems, the default is zero as S/G allocation works
and no preallocation is needed in most cases.
Note that the pre-allocation size can be changed dynamically
via a proc file (/proc/asound/card*/pcm*/sub*/prealloc), too.
endif
+13
View File
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: GPL-2.0
snd-hda-codec-y := bind.o codec.o jack.o auto_parser.o sysfs.o
snd-hda-codec-y += controller.o
snd-hda-codec-$(CONFIG_SND_PROC_FS) += proc.o
snd-hda-codec-$(CONFIG_SND_HDA_HWDEP) += hwdep.o
snd-hda-codec-$(CONFIG_SND_HDA_INPUT_BEEP) += beep.o
# for trace-points
CFLAGS_controller.o := -I$(src)
# common driver
obj-$(CONFIG_SND_HDA) := snd-hda-codec.o
File diff suppressed because it is too large Load Diff
+345
View File
@@ -0,0 +1,345 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Digital Beep Input Interface for HD-audio codec
*
* Author: Matt Ranostay <matt.ranostay@konsulko.com>
* Copyright (c) 2008 Embedded Alley Solutions Inc
*/
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/export.h>
#include <sound/core.h>
#include "hda_beep.h"
#include "hda_local.h"
enum {
DIGBEEP_HZ_STEP = 46875, /* 46.875 Hz */
DIGBEEP_HZ_MIN = 93750, /* 93.750 Hz */
DIGBEEP_HZ_MAX = 12000000, /* 12 KHz */
};
/* generate or stop tone */
static void generate_tone(struct hda_beep *beep, int tone)
{
struct hda_codec *codec = beep->codec;
if (tone && !beep->playing) {
snd_hda_power_up(codec);
if (beep->power_hook)
beep->power_hook(beep, true);
beep->playing = 1;
}
if (!codec->beep_just_power_on)
snd_hda_codec_write(codec, beep->nid, 0,
AC_VERB_SET_BEEP_CONTROL, tone);
if (!tone && beep->playing) {
beep->playing = 0;
if (beep->power_hook)
beep->power_hook(beep, false);
snd_hda_power_down(codec);
}
}
static void snd_hda_generate_beep(struct work_struct *work)
{
struct hda_beep *beep =
container_of(work, struct hda_beep, beep_work);
if (beep->enabled)
generate_tone(beep, beep->tone);
}
/* (non-standard) Linear beep tone calculation for IDT/STAC codecs
*
* The tone frequency of beep generator on IDT/STAC codecs is
* defined from the 8bit tone parameter, in Hz,
* freq = 48000 * (257 - tone) / 1024
* that is from 12kHz to 93.75Hz in steps of 46.875 Hz
*/
static int beep_linear_tone(struct hda_beep *beep, int hz)
{
if (hz <= 0)
return 0;
hz *= 1000; /* fixed point */
hz = hz - DIGBEEP_HZ_MIN
+ DIGBEEP_HZ_STEP / 2; /* round to nearest step */
if (hz < 0)
hz = 0; /* turn off PC beep*/
else if (hz >= (DIGBEEP_HZ_MAX - DIGBEEP_HZ_MIN))
hz = 1; /* max frequency */
else {
hz /= DIGBEEP_HZ_STEP;
hz = 255 - hz;
}
return hz;
}
/* HD-audio standard beep tone parameter calculation
*
* The tone frequency in Hz is calculated as
* freq = 48000 / (tone * 4)
* from 47Hz to 12kHz
*/
static int beep_standard_tone(struct hda_beep *beep, int hz)
{
if (hz <= 0)
return 0; /* disabled */
hz = 12000 / hz;
if (hz > 0xff)
return 0xff;
if (hz <= 0)
return 1;
return hz;
}
static int snd_hda_beep_event(struct input_dev *dev, unsigned int type,
unsigned int code, int hz)
{
struct hda_beep *beep = input_get_drvdata(dev);
switch (code) {
case SND_BELL:
if (hz)
hz = 1000;
fallthrough;
case SND_TONE:
if (beep->linear_tone)
beep->tone = beep_linear_tone(beep, hz);
else
beep->tone = beep_standard_tone(beep, hz);
break;
default:
return -1;
}
/* schedule beep event */
schedule_work(&beep->beep_work);
return 0;
}
static void turn_on_beep(struct hda_beep *beep)
{
if (beep->keep_power_at_enable)
snd_hda_power_up_pm(beep->codec);
}
static void turn_off_beep(struct hda_beep *beep)
{
cancel_work_sync(&beep->beep_work);
if (beep->playing) {
/* turn off beep */
generate_tone(beep, 0);
}
if (beep->keep_power_at_enable)
snd_hda_power_down_pm(beep->codec);
}
/**
* snd_hda_enable_beep_device - Turn on/off beep sound
* @codec: the HDA codec
* @enable: flag to turn on/off
*/
int snd_hda_enable_beep_device(struct hda_codec *codec, int enable)
{
struct hda_beep *beep = codec->beep;
if (!beep)
return 0;
enable = !!enable;
if (beep->enabled != enable) {
beep->enabled = enable;
if (enable)
turn_on_beep(beep);
else
turn_off_beep(beep);
return 1;
}
return 0;
}
EXPORT_SYMBOL_GPL(snd_hda_enable_beep_device);
static int beep_dev_register(struct snd_device *device)
{
struct hda_beep *beep = device->device_data;
int err;
err = input_register_device(beep->dev);
if (!err)
beep->registered = true;
return err;
}
static int beep_dev_disconnect(struct snd_device *device)
{
struct hda_beep *beep = device->device_data;
if (beep->registered)
input_unregister_device(beep->dev);
else
input_free_device(beep->dev);
if (beep->enabled)
turn_off_beep(beep);
return 0;
}
static int beep_dev_free(struct snd_device *device)
{
struct hda_beep *beep = device->device_data;
beep->codec->beep = NULL;
kfree(beep);
return 0;
}
/**
* snd_hda_attach_beep_device - Attach a beep input device
* @codec: the HDA codec
* @nid: beep NID
*
* Attach a beep object to the given widget. If beep hint is turned off
* explicitly or beep_mode of the codec is turned off, this doesn't nothing.
*
* Currently, only one beep device is allowed to each codec.
*/
int snd_hda_attach_beep_device(struct hda_codec *codec, int nid)
{
static const struct snd_device_ops ops = {
.dev_register = beep_dev_register,
.dev_disconnect = beep_dev_disconnect,
.dev_free = beep_dev_free,
};
struct input_dev *input_dev;
struct hda_beep *beep;
int err;
if (!codec->beep_just_power_on) {
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_obj(*beep);
if (beep == NULL)
return -ENOMEM;
snprintf(beep->phys, sizeof(beep->phys),
"card%d/codec#%d/beep0", codec->card->number, codec->addr);
/* enable linear scale */
snd_hda_codec_write_cache(codec, nid, 0,
AC_VERB_SET_DIGI_CONVERT_2, 0x01);
beep->nid = nid;
beep->codec = codec;
codec->beep = beep;
INIT_WORK(&beep->beep_work, &snd_hda_generate_beep);
input_dev = input_allocate_device();
if (!input_dev) {
err = -ENOMEM;
goto err_free;
}
/* setup digital beep device */
input_dev->name = "HDA Digital PCBeep";
input_dev->phys = beep->phys;
input_dev->id.bustype = BUS_PCI;
input_dev->dev.parent = &codec->card->card_dev;
input_dev->id.vendor = codec->core.vendor_id >> 16;
input_dev->id.product = codec->core.vendor_id & 0xffff;
input_dev->id.version = 0x01;
input_dev->evbit[0] = BIT_MASK(EV_SND);
input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE);
input_dev->event = snd_hda_beep_event;
input_set_drvdata(input_dev, beep);
beep->dev = input_dev;
err = snd_device_new(codec->card, SNDRV_DEV_JACK, beep, &ops);
if (err < 0)
goto err_input;
return 0;
err_input:
input_free_device(beep->dev);
err_free:
kfree(beep);
codec->beep = NULL;
return err;
}
EXPORT_SYMBOL_GPL(snd_hda_attach_beep_device);
/**
* snd_hda_detach_beep_device - Detach the beep device
* @codec: the HDA codec
*/
void snd_hda_detach_beep_device(struct hda_codec *codec)
{
if (!codec->bus->shutdown && codec->beep)
snd_device_free(codec->card, codec->beep);
}
EXPORT_SYMBOL_GPL(snd_hda_detach_beep_device);
static bool ctl_has_mute(struct snd_kcontrol *kcontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
return query_amp_caps(codec, get_amp_nid(kcontrol),
get_amp_direction(kcontrol)) & AC_AMPCAP_MUTE;
}
/* get/put callbacks for beep mute mixer switches */
/**
* snd_hda_mixer_amp_switch_get_beep - Get callback for beep controls
* @kcontrol: ctl element
* @ucontrol: pointer to get/store the data
*/
int snd_hda_mixer_amp_switch_get_beep(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct hda_beep *beep = codec->beep;
int chs = get_amp_channels(kcontrol);
if (beep && (!beep->enabled || !ctl_has_mute(kcontrol))) {
if (chs & 1)
ucontrol->value.integer.value[0] = beep->enabled;
if (chs & 2)
ucontrol->value.integer.value[1] = beep->enabled;
return 0;
}
return snd_hda_mixer_amp_switch_get(kcontrol, ucontrol);
}
EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_switch_get_beep);
/**
* snd_hda_mixer_amp_switch_put_beep - Put callback for beep controls
* @kcontrol: ctl element
* @ucontrol: pointer to get/store the data
*/
int snd_hda_mixer_amp_switch_put_beep(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct hda_beep *beep = codec->beep;
if (beep) {
u8 chs = get_amp_channels(kcontrol);
int enable = 0;
long *valp = ucontrol->value.integer.value;
if (chs & 1) {
enable |= *valp;
valp++;
}
if (chs & 2)
enable |= *valp;
snd_hda_enable_beep_device(codec, enable);
}
if (!ctl_has_mute(kcontrol))
return 0;
return snd_hda_mixer_amp_switch_put(kcontrol, ucontrol);
}
EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_switch_put_beep);
+345
View File
@@ -0,0 +1,345 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* HD-audio codec driver binding
* Copyright (c) Takashi Iwai <tiwai@suse.de>
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/module.h>
#include <linux/export.h>
#include <linux/pm.h>
#include <sound/core.h>
#include <sound/hda_codec.h>
#include "hda_local.h"
#include "hda_jack.h"
/*
* find a matching codec id
*/
static int hda_codec_match(struct hdac_device *dev, const struct hdac_driver *drv)
{
struct hda_codec *codec = container_of(dev, struct hda_codec, core);
const struct hda_codec_driver *driver =
container_of(drv, struct hda_codec_driver, core);
const struct hda_device_id *list;
/* check probe_id instead of vendor_id if set */
u32 id = codec->probe_id ? codec->probe_id : codec->core.vendor_id;
u32 rev_id = codec->core.revision_id;
for (list = driver->id; list->vendor_id; list++) {
if (list->vendor_id == id &&
(!list->rev_id || list->rev_id == rev_id)) {
codec->preset = list;
return 1;
}
}
return 0;
}
/* process an unsolicited event */
static void hda_codec_unsol_event(struct hdac_device *dev, unsigned int ev)
{
struct hda_codec *codec = container_of(dev, struct hda_codec, core);
struct hda_codec_driver *driver = hda_codec_to_driver(codec);
/* ignore unsol events during shutdown */
if (codec->card->shutdown || codec->bus->shutdown)
return;
/* ignore unsol events during system suspend/resume */
if (codec->core.dev.power.power_state.event != PM_EVENT_ON)
return;
if (driver->ops->unsol_event)
driver->ops->unsol_event(codec, ev);
}
/**
* snd_hda_codec_set_name - set the codec name
* @codec: the HDA codec
* @name: name string to set
*/
int snd_hda_codec_set_name(struct hda_codec *codec, const char *name)
{
int err;
if (!name)
return 0;
err = snd_hdac_device_set_chip_name(&codec->core, name);
if (err < 0)
return err;
/* update the mixer name */
if (!*codec->card->mixername ||
codec->bus->mixer_assigned >= codec->core.addr) {
snprintf(codec->card->mixername,
sizeof(codec->card->mixername), "%s %s",
codec->core.vendor_name, codec->core.chip_name);
codec->bus->mixer_assigned = codec->core.addr;
}
return 0;
}
EXPORT_SYMBOL_GPL(snd_hda_codec_set_name);
static int hda_codec_driver_probe(struct device *dev)
{
struct hda_codec *codec = dev_to_hda_codec(dev);
struct module *owner = dev->driver->owner;
struct hda_codec_driver *driver = hda_codec_to_driver(codec);
int err;
if (codec->bus->core.ext_ops) {
if (WARN_ON(!codec->bus->core.ext_ops->hdev_attach))
return -EINVAL;
return codec->bus->core.ext_ops->hdev_attach(&codec->core);
}
if (WARN_ON(!codec->preset))
return -EINVAL;
err = snd_hda_codec_set_name(codec, codec->preset->name);
if (err < 0)
goto error;
err = snd_hdac_regmap_init(&codec->core);
if (err < 0)
goto error;
if (!try_module_get(owner)) {
err = -EINVAL;
goto error;
}
if (WARN_ON(!(driver->ops && driver->ops->probe))) {
err = -EINVAL;
goto error_module_put;
}
err = driver->ops->probe(codec, codec->preset);
if (err < 0)
goto error_module_put;
err = snd_hda_codec_build_pcms(codec);
if (err < 0)
goto error_module;
err = snd_hda_codec_build_controls(codec);
if (err < 0)
goto error_module;
/* only register after the bus probe finished; otherwise it's racy */
if (!codec->bus->bus_probing && codec->card->registered) {
err = snd_card_register(codec->card);
if (err < 0)
goto error_module;
snd_hda_codec_register(codec);
}
codec->core.lazy_cache = true;
return 0;
error_module:
if (driver->ops->remove)
driver->ops->remove(codec);
error_module_put:
module_put(owner);
error:
snd_hda_codec_cleanup_for_unbind(codec);
codec->preset = NULL;
return err;
}
static int hda_codec_driver_remove(struct device *dev)
{
struct hda_codec *codec = dev_to_hda_codec(dev);
struct hda_codec_driver *driver = hda_codec_to_driver(codec);
if (codec->bus->core.ext_ops) {
if (WARN_ON(!codec->bus->core.ext_ops->hdev_detach))
return -EINVAL;
return codec->bus->core.ext_ops->hdev_detach(&codec->core);
}
snd_hda_codec_disconnect_pcms(codec);
snd_hda_jack_tbl_disconnect(codec);
snd_refcount_sync(&codec->pcm_ref);
snd_power_sync_ref(codec->bus->card);
if (driver->ops->remove)
driver->ops->remove(codec);
snd_hda_codec_cleanup_for_unbind(codec);
codec->preset = NULL;
module_put(dev->driver->owner);
return 0;
}
static void hda_codec_driver_shutdown(struct device *dev)
{
snd_hda_codec_shutdown(dev_to_hda_codec(dev));
}
int __hda_codec_driver_register(struct hda_codec_driver *drv, const char *name,
struct module *owner)
{
drv->core.driver.name = name;
drv->core.driver.owner = owner;
drv->core.driver.bus = &snd_hda_bus_type;
drv->core.driver.probe = hda_codec_driver_probe;
drv->core.driver.remove = hda_codec_driver_remove;
drv->core.driver.shutdown = hda_codec_driver_shutdown;
drv->core.driver.pm = pm_ptr(&hda_codec_driver_pm);
drv->core.type = HDA_DEV_LEGACY;
drv->core.match = hda_codec_match;
drv->core.unsol_event = hda_codec_unsol_event;
return driver_register(&drv->core.driver);
}
EXPORT_SYMBOL_GPL(__hda_codec_driver_register);
void hda_codec_driver_unregister(struct hda_codec_driver *drv)
{
driver_unregister(&drv->core.driver);
}
EXPORT_SYMBOL_GPL(hda_codec_driver_unregister);
static inline bool codec_probed(struct hda_codec *codec)
{
return device_attach(hda_codec_dev(codec)) > 0 && codec->preset;
}
/* try to auto-load codec module */
static void request_codec_module(struct hda_codec *codec)
{
#ifdef MODULE
char modalias[32];
const char *mod = NULL;
switch (codec->probe_id) {
case HDA_CODEC_ID_GENERIC_HDMI:
#if IS_MODULE(CONFIG_SND_HDA_CODEC_HDMI)
mod = "snd-hda-codec-hdmi";
#endif
break;
case HDA_CODEC_ID_GENERIC:
#if IS_MODULE(CONFIG_SND_HDA_GENERIC)
mod = "snd-hda-codec-generic";
#endif
break;
default:
snd_hdac_codec_modalias(&codec->core, modalias, sizeof(modalias));
mod = modalias;
break;
}
if (mod)
request_module(mod);
#endif /* MODULE */
}
/* try to auto-load and bind the codec module */
static void codec_bind_module(struct hda_codec *codec)
{
#ifdef MODULE
request_codec_module(codec);
if (codec_probed(codec))
return;
#endif
}
#if IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI)
/* if all audio out widgets are digital, let's assume the codec as a HDMI/DP */
static bool is_likely_hdmi_codec(struct hda_codec *codec)
{
hda_nid_t nid;
/*
* For ASoC users, if snd_hda_hdmi_codec module is denylisted and any
* event causes i915 enumeration to fail, ->wcaps remains uninitialized.
*/
if (!codec->wcaps)
return true;
for_each_hda_codec_node(nid, codec) {
unsigned int wcaps = get_wcaps(codec, nid);
switch (get_wcaps_type(wcaps)) {
case AC_WID_AUD_IN:
return false; /* HDMI parser supports only HDMI out */
case AC_WID_AUD_OUT:
if (!(wcaps & AC_WCAP_DIGITAL))
return false;
break;
}
}
return true;
}
#else
/* no HDMI codec parser support */
#define is_likely_hdmi_codec(codec) false
#endif /* CONFIG_SND_HDA_CODEC_HDMI */
static int codec_bind_generic(struct hda_codec *codec)
{
if (codec->probe_id)
return -ENODEV;
if (is_likely_hdmi_codec(codec)) {
codec->probe_id = HDA_CODEC_ID_GENERIC_HDMI;
request_codec_module(codec);
if (codec_probed(codec))
return 0;
}
codec->probe_id = HDA_CODEC_ID_GENERIC;
request_codec_module(codec);
if (codec_probed(codec))
return 0;
return -ENODEV;
}
#if IS_ENABLED(CONFIG_SND_HDA_GENERIC)
#define is_generic_config(codec) \
(codec->modelname && !strcmp(codec->modelname, "generic"))
#else
#define is_generic_config(codec) 0
#endif
/**
* snd_hda_codec_configure - (Re-)configure the HD-audio codec
* @codec: the HDA codec
*
* Start parsing of the given codec tree and (re-)initialize the whole
* codec driver binding.
*
* Returns 0 if successful or a negative error code.
*/
int snd_hda_codec_configure(struct hda_codec *codec)
{
int err;
if (codec->configured)
return 0;
if (is_generic_config(codec))
codec->probe_id = HDA_CODEC_ID_GENERIC;
else
codec->probe_id = 0;
if (!device_is_registered(&codec->core.dev)) {
err = snd_hdac_device_register(&codec->core);
if (err < 0)
return err;
}
if (!codec->preset)
codec_bind_module(codec);
if (!codec->preset) {
err = codec_bind_generic(codec);
if (err < 0) {
codec_dbg(codec, "Unable to bind the codec\n");
return err;
}
}
codec->configured = 1;
return 0;
}
EXPORT_SYMBOL_GPL(snd_hda_codec_configure);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,99 @@
/* SPDX-License-Identifier: GPL-2.0 */
#undef TRACE_SYSTEM
#define TRACE_SYSTEM hda_controller
#define TRACE_INCLUDE_FILE controller_trace
#if !defined(_TRACE_HDA_CONTROLLER_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_HDA_CONTROLLER_H
#include <linux/tracepoint.h>
struct azx;
struct azx_dev;
TRACE_EVENT(azx_pcm_trigger,
TP_PROTO(struct azx *chip, struct azx_dev *dev, int cmd),
TP_ARGS(chip, dev, cmd),
TP_STRUCT__entry(
__field( int, card )
__field( int, idx )
__field( int, cmd )
),
TP_fast_assign(
__entry->card = (chip)->card->number;
__entry->idx = (dev)->core.index;
__entry->cmd = cmd;
),
TP_printk("[%d:%d] cmd=%d", __entry->card, __entry->idx, __entry->cmd)
);
TRACE_EVENT(azx_get_position,
TP_PROTO(struct azx *chip, struct azx_dev *dev, unsigned int pos, unsigned int delay),
TP_ARGS(chip, dev, pos, delay),
TP_STRUCT__entry(
__field( int, card )
__field( int, idx )
__field( unsigned int, pos )
__field( unsigned int, delay )
),
TP_fast_assign(
__entry->card = (chip)->card->number;
__entry->idx = (dev)->core.index;
__entry->pos = pos;
__entry->delay = delay;
),
TP_printk("[%d:%d] pos=%u, delay=%u", __entry->card, __entry->idx, __entry->pos, __entry->delay)
);
DECLARE_EVENT_CLASS(azx_pcm,
TP_PROTO(struct azx *chip, struct azx_dev *azx_dev),
TP_ARGS(chip, azx_dev),
TP_STRUCT__entry(
__field( unsigned char, stream_tag )
),
TP_fast_assign(
__entry->stream_tag = (azx_dev)->core.stream_tag;
),
TP_printk("stream_tag: %d", __entry->stream_tag)
);
DEFINE_EVENT(azx_pcm, azx_pcm_open,
TP_PROTO(struct azx *chip, struct azx_dev *azx_dev),
TP_ARGS(chip, azx_dev)
);
DEFINE_EVENT(azx_pcm, azx_pcm_close,
TP_PROTO(struct azx *chip, struct azx_dev *azx_dev),
TP_ARGS(chip, azx_dev)
);
DEFINE_EVENT(azx_pcm, azx_pcm_hw_params,
TP_PROTO(struct azx *chip, struct azx_dev *azx_dev),
TP_ARGS(chip, azx_dev)
);
DEFINE_EVENT(azx_pcm, azx_pcm_prepare,
TP_PROTO(struct azx *chip, struct azx_dev *azx_dev),
TP_ARGS(chip, azx_dev)
);
#endif /* _TRACE_HDA_CONTROLLER_H */
/* This part must be outside protection */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#include <trace/define_trace.h>
@@ -0,0 +1,118 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* BIOS auto-parser helper functions for HD-audio
*
* Copyright (c) 2012 Takashi Iwai <tiwai@suse.de>
*/
#ifndef __SOUND_HDA_AUTO_PARSER_H
#define __SOUND_HDA_AUTO_PARSER_H
#include "hda_local.h"
/*
* Helper for automatic pin configuration
*/
enum {
AUTO_PIN_MIC,
AUTO_PIN_LINE_IN,
AUTO_PIN_CD,
AUTO_PIN_AUX,
AUTO_PIN_LAST
};
enum {
AUTO_PIN_LINE_OUT,
AUTO_PIN_SPEAKER_OUT,
AUTO_PIN_HP_OUT
};
#define AUTO_CFG_MAX_OUTS HDA_MAX_OUTS
#define AUTO_CFG_MAX_INS 18
struct auto_pin_cfg_item {
hda_nid_t pin;
int type;
unsigned int is_headset_mic:1;
unsigned int is_headphone_mic:1; /* Mic-only in headphone jack */
unsigned int has_boost_on_pin:1;
int order;
};
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 */
+46
View File
@@ -0,0 +1,46 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Digital Beep Input Interface for HD-audio codec
*
* Author: Matt Ranostay <matt.ranostay@konsulko.com>
* Copyright (c) 2008 Embedded Alley Solutions Inc
*/
#ifndef __SOUND_HDA_BEEP_H
#define __SOUND_HDA_BEEP_H
#include <sound/hda_codec.h>
#define HDA_BEEP_MODE_OFF 0
#define HDA_BEEP_MODE_ON 1
/* beep information */
struct hda_beep {
struct input_dev *dev;
struct hda_codec *codec;
char phys[32];
int tone;
hda_nid_t nid;
unsigned int registered:1;
unsigned int enabled:1;
unsigned int linear_tone:1; /* linear tone for IDT/STAC codec */
unsigned int playing:1;
unsigned int keep_power_at_enable:1; /* set by driver */
struct work_struct beep_work; /* scheduled task for beep event */
void (*power_hook)(struct hda_beep *beep, bool on);
};
#ifdef CONFIG_SND_HDA_INPUT_BEEP
int snd_hda_enable_beep_device(struct hda_codec *codec, int enable);
int snd_hda_attach_beep_device(struct hda_codec *codec, int nid);
void snd_hda_detach_beep_device(struct hda_codec *codec);
#else
static inline int snd_hda_attach_beep_device(struct hda_codec *codec, int nid)
{
return 0;
}
static inline void snd_hda_detach_beep_device(struct hda_codec *codec)
{
}
#endif
#endif
@@ -0,0 +1,221 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Common functionality for the alsa driver code base for HD Audio.
*/
#ifndef __SOUND_HDA_CONTROLLER_H
#define __SOUND_HDA_CONTROLLER_H
#include <linux/timecounter.h>
#include <linux/interrupt.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/hda_codec.h>
#include <sound/hda_register.h>
#define AZX_MAX_CODECS HDA_MAX_CODECS
#define AZX_DEFAULT_CODECS 4
/* driver quirks (capabilities) */
/* bits 0-7 are used for indicating driver type */
#define AZX_DCAPS_NO_TCSEL (1 << 8) /* No Intel TCSEL bit */
#define AZX_DCAPS_NO_MSI (1 << 9) /* No MSI support */
#define AZX_DCAPS_SNOOP_MASK (3 << 10) /* snoop type mask */
#define AZX_DCAPS_SNOOP_OFF (1 << 12) /* snoop default off */
#ifdef CONFIG_SND_HDA_I915
#define AZX_DCAPS_I915_COMPONENT (1 << 13) /* bind with i915 gfx */
#else
#define AZX_DCAPS_I915_COMPONENT 0 /* NOP */
#endif
/* 14 unused */
#define AZX_DCAPS_CTX_WORKAROUND (1 << 15) /* X-Fi workaround */
#define AZX_DCAPS_POSFIX_LPIB (1 << 16) /* Use LPIB as default */
#define AZX_DCAPS_AMD_WORKAROUND (1 << 17) /* AMD-specific workaround */
#define AZX_DCAPS_NO_64BIT (1 << 18) /* No 64bit address */
/* 19 unused */
#define AZX_DCAPS_OLD_SSYNC (1 << 20) /* Old SSYNC reg for ICH */
#define AZX_DCAPS_NO_ALIGN_BUFSIZE (1 << 21) /* no buffer size alignment */
/* 22 unused */
#define AZX_DCAPS_4K_BDLE_BOUNDARY (1 << 23) /* BDLE in 4k boundary */
/* 24 unused */
#define AZX_DCAPS_COUNT_LPIB_DELAY (1 << 25) /* Take LPIB as delay */
#define AZX_DCAPS_PM_RUNTIME (1 << 26) /* runtime PM support */
#define AZX_DCAPS_RETRY_PROBE (1 << 27) /* retry probe if no codec is configured */
#define AZX_DCAPS_CORBRP_SELF_CLEAR (1 << 28) /* CORBRP clears itself after reset */
#define AZX_DCAPS_NO_MSI64 (1 << 29) /* Stick to 32-bit MSIs */
#define AZX_DCAPS_SEPARATE_STREAM_TAG (1 << 30) /* capture and playback use separate stream tag */
#define AZX_DCAPS_PIO_COMMANDS (1 << 31) /* Use PIO instead of CORB for commands */
enum {
AZX_SNOOP_TYPE_NONE,
AZX_SNOOP_TYPE_SCH,
AZX_SNOOP_TYPE_ATI,
AZX_SNOOP_TYPE_NVIDIA,
};
struct azx_dev {
struct hdac_stream core;
/*
* For VIA:
* A flag to ensure DMA position is 0
* when link position is not greater than FIFO size
*/
bool insufficient;
};
#define azx_stream(dev) (&(dev)->core)
#define stream_to_azx_dev(s) container_of(s, struct azx_dev, core)
struct azx;
/* Functions to read/write to hda registers. */
struct hda_controller_ops {
/* Disable msi if supported, PCI only */
int (*disable_msi_reset_irq)(struct azx *);
/* Check if current position is acceptable */
int (*position_check)(struct azx *chip, struct azx_dev *azx_dev);
/* enable/disable the link power */
int (*link_power)(struct azx *chip, bool enable);
/* additional hook for PCM */
void (*pcm_close)(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 *info;
struct list_head list;
};
typedef unsigned int (*azx_get_pos_callback_t)(struct azx *, struct azx_dev *);
typedef int (*azx_get_delay_callback_t)(struct azx *, struct azx_dev *, unsigned int pos);
struct azx {
struct hda_bus bus;
struct snd_card *card;
struct pci_dev *pci;
int dev_index;
/* chip type specific */
int driver_type;
unsigned int driver_caps;
int playback_streams;
int playback_index_offset;
int capture_streams;
int capture_index_offset;
int num_streams;
int jackpoll_interval; /* jack poll interval in jiffies */
/* Register interaction. */
const struct hda_controller_ops *ops;
/* position adjustment callbacks */
azx_get_pos_callback_t get_position[2];
azx_get_delay_callback_t get_delay[2];
/* locks */
struct mutex open_mutex; /* Prevents concurrent open/close operations */
/* PCM */
struct list_head pcm_list; /* azx_pcm list */
/* HD codec */
int codec_probe_mask; /* copied from probe_mask option */
unsigned int beep_mode;
bool ctl_dev_id;
/* flags */
int bdl_pos_adj;
unsigned int running:1;
unsigned int fallback_to_single_cmd:1;
unsigned int single_cmd:1;
unsigned int msi:1;
unsigned int probing:1; /* codec probing phase */
unsigned int snoop:1;
unsigned int uc_buffer:1; /* non-cached pages for stream buffers */
unsigned int align_buffer_size:1;
unsigned int disabled:1; /* disabled by vga_switcheroo */
unsigned int pm_prepared:1;
/* GTS present */
unsigned int gts_present:1;
#ifdef CONFIG_SND_HDA_DSP_LOADER
struct azx_dev saved_azx_dev;
#endif
};
#define azx_bus(chip) (&(chip)->bus.core)
#define bus_to_azx(_bus) container_of(_bus, struct azx, bus.core)
static inline bool azx_snoop(struct azx *chip)
{
return !IS_ENABLED(CONFIG_X86) || chip->snoop;
}
/*
* macros for easy use
*/
#define azx_writel(chip, reg, value) \
snd_hdac_chip_writel(azx_bus(chip), reg, value)
#define azx_readl(chip, reg) \
snd_hdac_chip_readl(azx_bus(chip), reg)
#define azx_writew(chip, reg, value) \
snd_hdac_chip_writew(azx_bus(chip), reg, value)
#define azx_readw(chip, reg) \
snd_hdac_chip_readw(azx_bus(chip), reg)
#define azx_writeb(chip, reg, value) \
snd_hdac_chip_writeb(azx_bus(chip), reg, value)
#define azx_readb(chip, reg) \
snd_hdac_chip_readb(azx_bus(chip), reg)
#define azx_has_pm_runtime(chip) \
((chip)->driver_caps & AZX_DCAPS_PM_RUNTIME)
/* PCM setup */
static inline struct azx_dev *get_azx_dev(struct snd_pcm_substream *substream)
{
return substream->runtime->private_data;
}
unsigned int azx_get_position(struct azx *chip, struct azx_dev *azx_dev);
unsigned int azx_get_pos_lpib(struct azx *chip, struct azx_dev *azx_dev);
unsigned int azx_get_pos_posbuf(struct azx *chip, struct azx_dev *azx_dev);
/* Stream control. */
void azx_stop_all_streams(struct azx *chip);
/* Allocation functions. */
#define azx_alloc_stream_pages(chip) \
snd_hdac_bus_alloc_stream_pages(azx_bus(chip))
#define azx_free_stream_pages(chip) \
snd_hdac_bus_free_stream_pages(azx_bus(chip))
/* Low level azx interface */
void azx_init_chip(struct azx *chip, bool full_reset);
void azx_stop_chip(struct azx *chip);
#define azx_enter_link_reset(chip) \
snd_hdac_bus_enter_link_reset(azx_bus(chip))
irqreturn_t azx_interrupt(int irq, void *dev_id);
/* Codec interface */
int azx_bus_init(struct azx *chip, const char *model);
int azx_probe_codecs(struct azx *chip, unsigned int max_slots);
int azx_codec_configure(struct azx *chip);
int azx_init_streams(struct azx *chip);
void azx_add_stream(struct azx *chip, struct azx_dev *s, int idx, int tag);
void azx_free_streams(struct azx *chip);
static inline int azx_stream_direction(struct azx *chip, unsigned char index)
{
if (index >= chip->capture_index_offset &&
index < chip->capture_index_offset + chip->capture_streams)
return SNDRV_PCM_STREAM_CAPTURE;
return SNDRV_PCM_STREAM_PLAYBACK;
}
#endif /* __SOUND_HDA_CONTROLLER_H */
+195
View File
@@ -0,0 +1,195 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Jack-detection handling for HD-audio
*
* Copyright (c) 2011 Takashi Iwai <tiwai@suse.de>
*/
#ifndef __SOUND_HDA_JACK_H
#define __SOUND_HDA_JACK_H
#include <linux/err.h>
#include <sound/jack.h>
struct auto_pin_cfg;
struct hda_jack_tbl;
struct hda_jack_callback;
typedef void (*hda_jack_callback_fn) (struct hda_codec *, struct hda_jack_callback *);
struct hda_jack_callback {
hda_nid_t nid;
int dev_id;
hda_jack_callback_fn func;
unsigned int private_data; /* arbitrary data */
unsigned int unsol_res; /* unsolicited event bits */
struct hda_jack_tbl *jack; /* associated jack entry */
struct hda_jack_callback *next;
};
struct hda_jack_tbl {
hda_nid_t nid;
int dev_id;
unsigned char tag; /* unsol event tag */
struct hda_jack_callback *callback;
/* jack-detection stuff */
unsigned int pin_sense; /* cached pin-sense value */
unsigned int jack_detect:1; /* capable of jack-detection? */
unsigned int jack_dirty:1; /* needs to update? */
unsigned int phantom_jack:1; /* a fixed, always present port? */
unsigned int block_report:1; /* in a transitional state - do not report to userspace */
hda_nid_t gating_jack; /* valid when gating jack plugged */
hda_nid_t gated_jack; /* gated is dependent on this jack */
hda_nid_t key_report_jack; /* key reports to this jack */
int type;
int button_state;
struct snd_jack *jack;
};
struct hda_jack_keymap {
enum snd_jack_types type;
int key;
};
struct hda_jack_tbl *
snd_hda_jack_tbl_get_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id);
/**
* snd_hda_jack_tbl_get - query the jack-table entry for the given NID
* @codec: the HDA codec
* @nid: pin NID to refer to
*/
static inline struct hda_jack_tbl *
snd_hda_jack_tbl_get(struct hda_codec *codec, hda_nid_t nid)
{
return snd_hda_jack_tbl_get_mst(codec, nid, 0);
}
struct hda_jack_tbl *
snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec,
unsigned char tag, int dev_id);
void snd_hda_jack_tbl_disconnect(struct hda_codec *codec);
void snd_hda_jack_tbl_clear(struct hda_codec *codec);
void snd_hda_jack_set_dirty_all(struct hda_codec *codec);
int snd_hda_jack_detect_enable(struct hda_codec *codec, hda_nid_t nid,
int dev_id);
struct hda_jack_callback *
snd_hda_jack_detect_enable_callback_mst(struct hda_codec *codec, hda_nid_t nid,
int dev_id, hda_jack_callback_fn func);
/**
* snd_hda_jack_detect_enable_callback - enable the jack-detection
* @codec: the HDA codec
* @nid: pin NID to enable
* @cb: callback function to register
*
* In the case of error, the return value will be a pointer embedded with
* errno. Check and handle the return value appropriately with standard
* macros such as @IS_ERR() and @PTR_ERR().
*/
static inline struct hda_jack_callback *
snd_hda_jack_detect_enable_callback(struct hda_codec *codec, hda_nid_t nid,
hda_jack_callback_fn cb)
{
return snd_hda_jack_detect_enable_callback_mst(codec, nid, 0, cb);
}
int snd_hda_jack_set_gating_jack(struct hda_codec *codec, hda_nid_t gated_nid,
hda_nid_t gating_nid);
int snd_hda_jack_bind_keymap(struct hda_codec *codec, hda_nid_t key_nid,
const struct hda_jack_keymap *keymap,
hda_nid_t jack_nid);
void snd_hda_jack_set_button_state(struct hda_codec *codec, hda_nid_t jack_nid,
int button_state);
u32 snd_hda_jack_pin_sense(struct hda_codec *codec, hda_nid_t nid, int dev_id);
/* the jack state returned from snd_hda_jack_detect_state() */
enum {
HDA_JACK_NOT_PRESENT, HDA_JACK_PRESENT, HDA_JACK_PHANTOM,
};
int snd_hda_jack_detect_state_mst(struct hda_codec *codec, hda_nid_t nid,
int dev_id);
/**
* snd_hda_jack_detect_state - query pin Presence Detect status
* @codec: the CODEC to sense
* @nid: the pin NID to sense
*
* Query and return the pin's Presence Detect status, as either
* HDA_JACK_NOT_PRESENT, HDA_JACK_PRESENT or HDA_JACK_PHANTOM.
*/
static inline int
snd_hda_jack_detect_state(struct hda_codec *codec, hda_nid_t nid)
{
return snd_hda_jack_detect_state_mst(codec, nid, 0);
}
/**
* snd_hda_jack_detect_mst - Detect the jack
* @codec: the HDA codec
* @nid: pin NID to check jack detection
* @dev_id: pin device entry id
*/
static inline bool
snd_hda_jack_detect_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id)
{
return snd_hda_jack_detect_state_mst(codec, nid, dev_id) !=
HDA_JACK_NOT_PRESENT;
}
/**
* snd_hda_jack_detect - Detect the jack
* @codec: the HDA codec
* @nid: pin NID to check jack detection
*/
static inline bool
snd_hda_jack_detect(struct hda_codec *codec, hda_nid_t nid)
{
return snd_hda_jack_detect_mst(codec, nid, 0);
}
bool is_jack_detectable(struct hda_codec *codec, hda_nid_t nid);
int snd_hda_jack_add_kctl_mst(struct hda_codec *codec, hda_nid_t nid,
int dev_id, const char *name, bool phantom_jack,
int type, const struct hda_jack_keymap *keymap);
/**
* snd_hda_jack_add_kctl - Add a kctl for the given pin
* @codec: the HDA codec
* @nid: pin NID to assign
* @name: string name for the jack
* @phantom_jack: flag to deal as a phantom jack
* @type: jack type bits to be reported, 0 for guessing from pincfg
* @keymap: optional jack / key mapping
*
* This assigns a jack-detection kctl to the given pin. The kcontrol
* will have the given name and index.
*/
static inline int
snd_hda_jack_add_kctl(struct hda_codec *codec, hda_nid_t nid,
const char *name, bool phantom_jack,
int type, const struct hda_jack_keymap *keymap)
{
return snd_hda_jack_add_kctl_mst(codec, nid, 0,
name, phantom_jack, type, keymap);
}
int snd_hda_jack_add_kctls(struct hda_codec *codec,
const struct auto_pin_cfg *cfg);
void snd_hda_jack_report_sync(struct hda_codec *codec);
void snd_hda_jack_unsol_event(struct hda_codec *codec, unsigned int res);
void snd_hda_jack_poll_all(struct hda_codec *codec);
#endif /* __SOUND_HDA_JACK_H */
+726
View File
@@ -0,0 +1,726 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Universal Interface for Intel High Definition Audio Codec
*
* Local helper functions
*
* Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
*/
#ifndef __SOUND_HDA_LOCAL_H
#define __SOUND_HDA_LOCAL_H
#include <sound/pcm_drm_eld.h>
/* We abuse kcontrol_new.subdev field to pass the NID corresponding to
* the given new control. If id.subdev has a bit flag HDA_SUBDEV_NID_FLAG,
* snd_hda_ctl_add() takes the lower-bit subdev value as a valid NID.
*
* Note that the subdevice field is cleared again before the real registration
* in snd_hda_ctl_add(), so that this value won't appear in the outside.
*/
#define HDA_SUBDEV_NID_FLAG (1U << 31)
#define HDA_SUBDEV_AMP_FLAG (1U << 30)
/*
* for mixer controls
*/
#define HDA_COMPOSE_AMP_VAL_OFS(nid,chs,idx,dir,ofs) \
((nid) | ((chs)<<16) | ((dir)<<18) | ((idx)<<19) | ((ofs)<<23))
#define HDA_AMP_VAL_MIN_MUTE (1<<29)
#define HDA_COMPOSE_AMP_VAL(nid,chs,idx,dir) \
HDA_COMPOSE_AMP_VAL_OFS(nid, chs, idx, dir, 0)
/* mono volume with index (index=0,1,...) (channel=1,2) */
#define HDA_CODEC_VOLUME_MONO_IDX(xname, xcidx, nid, channel, xindex, dir, flags) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \
.subdevice = HDA_SUBDEV_AMP_FLAG, \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, \
.info = snd_hda_mixer_amp_volume_info, \
.get = snd_hda_mixer_amp_volume_get, \
.put = snd_hda_mixer_amp_volume_put, \
.tlv = { .c = snd_hda_mixer_amp_tlv }, \
.private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, dir) | flags }
/* stereo volume with index */
#define HDA_CODEC_VOLUME_IDX(xname, xcidx, nid, xindex, direction) \
HDA_CODEC_VOLUME_MONO_IDX(xname, xcidx, nid, 3, xindex, direction, 0)
/* mono volume */
#define HDA_CODEC_VOLUME_MONO(xname, nid, channel, xindex, direction) \
HDA_CODEC_VOLUME_MONO_IDX(xname, 0, nid, channel, xindex, direction, 0)
/* stereo volume */
#define HDA_CODEC_VOLUME(xname, nid, xindex, direction) \
HDA_CODEC_VOLUME_MONO(xname, nid, 3, xindex, direction)
/* stereo volume with min=mute */
#define HDA_CODEC_VOLUME_MIN_MUTE(xname, nid, xindex, direction) \
HDA_CODEC_VOLUME_MONO_IDX(xname, 0, nid, 3, xindex, direction, \
HDA_AMP_VAL_MIN_MUTE)
/* mono mute switch with index (index=0,1,...) (channel=1,2) */
#define HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \
.subdevice = HDA_SUBDEV_AMP_FLAG, \
.info = snd_hda_mixer_amp_switch_info, \
.get = snd_hda_mixer_amp_switch_get, \
.put = snd_hda_mixer_amp_switch_put, \
.private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, direction) }
/* stereo mute switch with index */
#define HDA_CODEC_MUTE_IDX(xname, xcidx, nid, xindex, direction) \
HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, 3, xindex, direction)
/* mono mute switch */
#define HDA_CODEC_MUTE_MONO(xname, nid, channel, xindex, direction) \
HDA_CODEC_MUTE_MONO_IDX(xname, 0, nid, channel, xindex, direction)
/* stereo mute switch */
#define HDA_CODEC_MUTE(xname, nid, xindex, direction) \
HDA_CODEC_MUTE_MONO(xname, nid, 3, xindex, direction)
#ifdef CONFIG_SND_HDA_INPUT_BEEP
/* special beep mono mute switch with index (index=0,1,...) (channel=1,2) */
#define HDA_CODEC_MUTE_BEEP_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \
.subdevice = HDA_SUBDEV_AMP_FLAG, \
.info = snd_hda_mixer_amp_switch_info, \
.get = snd_hda_mixer_amp_switch_get_beep, \
.put = snd_hda_mixer_amp_switch_put_beep, \
.private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, direction) }
#else
/* no digital beep - just the standard one */
#define HDA_CODEC_MUTE_BEEP_MONO_IDX(xname, xcidx, nid, ch, xidx, dir) \
HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, ch, xidx, dir)
#endif /* CONFIG_SND_HDA_INPUT_BEEP */
/* special beep mono mute switch */
#define HDA_CODEC_MUTE_BEEP_MONO(xname, nid, channel, xindex, direction) \
HDA_CODEC_MUTE_BEEP_MONO_IDX(xname, 0, nid, channel, xindex, direction)
/* special beep stereo mute switch */
#define HDA_CODEC_MUTE_BEEP(xname, nid, xindex, direction) \
HDA_CODEC_MUTE_BEEP_MONO(xname, nid, 3, xindex, direction)
extern const char *snd_hda_pcm_type_name[];
int snd_hda_mixer_amp_volume_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo);
int snd_hda_mixer_amp_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int snd_hda_mixer_amp_tlv(struct snd_kcontrol *kcontrol, int op_flag,
unsigned int size, unsigned int __user *_tlv);
int snd_hda_mixer_amp_switch_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo);
int snd_hda_mixer_amp_switch_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int snd_hda_mixer_amp_switch_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
#ifdef CONFIG_SND_HDA_INPUT_BEEP
int snd_hda_mixer_amp_switch_get_beep(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int snd_hda_mixer_amp_switch_put_beep(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
#endif
/* lowlevel accessor with caching; use carefully */
#define snd_hda_codec_amp_read(codec, nid, ch, dir, idx) \
snd_hdac_regmap_get_amp(&(codec)->core, nid, ch, dir, idx)
int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid,
int ch, int dir, int idx, int mask, int val);
int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid,
int direction, int idx, int mask, int val);
int snd_hda_codec_amp_init(struct hda_codec *codec, hda_nid_t nid, int ch,
int direction, int idx, int mask, int val);
int snd_hda_codec_amp_init_stereo(struct hda_codec *codec, hda_nid_t nid,
int dir, int idx, int mask, int val);
void snd_hda_set_vmaster_tlv(struct hda_codec *codec, hda_nid_t nid, int dir,
unsigned int *tlv);
struct snd_kcontrol *snd_hda_find_mixer_ctl(struct hda_codec *codec,
const char *name);
int __snd_hda_add_vmaster(struct hda_codec *codec, char *name,
unsigned int *tlv, const char * const *followers,
const char *suffix, bool init_follower_vol,
unsigned int access, struct snd_kcontrol **ctl_ret);
#define snd_hda_add_vmaster(codec, name, tlv, followers, suffix, access) \
__snd_hda_add_vmaster(codec, name, tlv, followers, suffix, true, access, NULL)
int snd_hda_codec_reset(struct hda_codec *codec);
void snd_hda_codec_disconnect_pcms(struct hda_codec *codec);
#define snd_hda_regmap_sync(codec) snd_hdac_regmap_sync(&(codec)->core)
struct hda_vmaster_mute_hook {
/* below two fields must be filled by the caller of
* snd_hda_add_vmaster_hook() beforehand
*/
struct snd_kcontrol *sw_kctl;
void (*hook)(void *, int);
/* below are initialized automatically */
struct hda_codec *codec;
};
int snd_hda_add_vmaster_hook(struct hda_codec *codec,
struct hda_vmaster_mute_hook *hook);
void snd_hda_sync_vmaster_hook(struct hda_vmaster_mute_hook *hook);
/* amp value bits */
#define HDA_AMP_MUTE 0x80
#define HDA_AMP_UNMUTE 0x00
#define HDA_AMP_VOLMASK 0x7f
/*
* SPDIF I/O
*/
int snd_hda_create_dig_out_ctls(struct hda_codec *codec,
hda_nid_t associated_nid,
hda_nid_t cvt_nid, int type);
#define snd_hda_create_spdif_out_ctls(codec, anid, cnid) \
snd_hda_create_dig_out_ctls(codec, anid, cnid, HDA_PCM_TYPE_SPDIF)
int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid);
/*
* input MUX helper
*/
#define HDA_MAX_NUM_INPUTS 36
struct hda_input_mux_item {
char label[32];
unsigned int index;
};
struct hda_input_mux {
unsigned int num_items;
struct hda_input_mux_item items[HDA_MAX_NUM_INPUTS];
};
int snd_hda_input_mux_info(const struct hda_input_mux *imux,
struct snd_ctl_elem_info *uinfo);
int snd_hda_input_mux_put(struct hda_codec *codec,
const struct hda_input_mux *imux,
struct snd_ctl_elem_value *ucontrol, hda_nid_t nid,
unsigned int *cur_val);
int snd_hda_add_imux_item(struct hda_codec *codec,
struct hda_input_mux *imux, const char *label,
int index, int *type_idx);
/*
* Multi-channel / digital-out PCM helper
*/
enum { HDA_FRONT, HDA_REAR, HDA_CLFE, HDA_SIDE }; /* index for dac_nidx */
enum { HDA_DIG_NONE, HDA_DIG_EXCLUSIVE, HDA_DIG_ANALOG_DUP }; /* dig_out_used */
#define HDA_MAX_OUTS 5
struct hda_multi_out {
int num_dacs; /* # of DACs, must be more than 1 */
const hda_nid_t *dac_nids; /* DAC list */
hda_nid_t hp_nid; /* optional DAC for HP, 0 when not exists */
hda_nid_t hp_out_nid[HDA_MAX_OUTS]; /* DACs for multiple HPs */
hda_nid_t extra_out_nid[HDA_MAX_OUTS]; /* other (e.g. speaker) DACs */
hda_nid_t dig_out_nid; /* digital out audio widget */
const hda_nid_t *follower_dig_outs;
int max_channels; /* currently supported analog channels */
int dig_out_used; /* current usage of digital out (HDA_DIG_XXX) */
int no_share_stream; /* don't share a stream with multiple pins */
int share_spdif; /* share SPDIF pin */
/* PCM information for both analog and SPDIF DACs */
unsigned int analog_rates;
unsigned int analog_maxbps;
u64 analog_formats;
unsigned int spdif_rates;
unsigned int spdif_maxbps;
u64 spdif_formats;
struct snd_kcontrol *share_spdif_kctl; /* cached shared SPDIF switch */
};
int snd_hda_create_spdif_share_sw(struct hda_codec *codec,
struct hda_multi_out *mout);
int snd_hda_multi_out_dig_open(struct hda_codec *codec,
struct hda_multi_out *mout);
int snd_hda_multi_out_dig_close(struct hda_codec *codec,
struct hda_multi_out *mout);
int snd_hda_multi_out_dig_prepare(struct hda_codec *codec,
struct hda_multi_out *mout,
unsigned int stream_tag,
unsigned int format,
struct snd_pcm_substream *substream);
int snd_hda_multi_out_dig_cleanup(struct hda_codec *codec,
struct hda_multi_out *mout);
int snd_hda_multi_out_analog_open(struct hda_codec *codec,
struct hda_multi_out *mout,
struct snd_pcm_substream *substream,
struct hda_pcm_stream *hinfo);
int snd_hda_multi_out_analog_prepare(struct hda_codec *codec,
struct hda_multi_out *mout,
unsigned int stream_tag,
unsigned int format,
struct snd_pcm_substream *substream);
int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec,
struct hda_multi_out *mout);
/*
* generic proc interface
*/
#ifdef CONFIG_SND_PROC_FS
int snd_hda_codec_proc_new(struct hda_codec *codec);
#else
static inline int snd_hda_codec_proc_new(struct hda_codec *codec) { return 0; }
#endif
#define SND_PRINT_BITS_ADVISED_BUFSIZE 16
void snd_print_pcm_bits(int pcm, char *buf, int buflen);
/*
* Misc
*/
int snd_hda_add_new_ctls(struct hda_codec *codec,
const struct snd_kcontrol_new *knew);
/*
* Fix-up pin default configurations and add default verbs
*/
struct hda_pintbl {
hda_nid_t nid;
u32 val;
};
struct hda_model_fixup {
const int id;
const char *name;
};
struct hda_fixup {
int type;
bool chained:1; /* call the chained fixup(s) after this */
bool chained_before:1; /* call the chained fixup(s) before this */
int chain_id;
union {
const struct hda_pintbl *pins;
const struct hda_verb *verbs;
void (*func)(struct hda_codec *codec,
const struct hda_fixup *fix,
int action);
} v;
};
/*
* extended form of snd_pci_quirk:
* for PCI SSID matching, use SND_PCI_QUIRK() like before;
* for codec SSID matching, use the new HDA_CODEC_QUIRK() instead
*/
struct hda_quirk {
unsigned short subvendor; /* PCI subvendor ID */
unsigned short subdevice; /* PCI subdevice ID */
unsigned short subdevice_mask; /* bitmask to match */
bool match_codec_ssid; /* match only with codec SSID */
int value; /* value */
#ifdef CONFIG_SND_DEBUG_VERBOSE
const char *name; /* name of the device (optional) */
#endif
};
#ifdef CONFIG_SND_DEBUG_VERBOSE
#define HDA_CODEC_QUIRK(vend, dev, xname, val) \
{ _SND_PCI_QUIRK_ID(vend, dev), .value = (val), .name = (xname),\
.match_codec_ssid = true }
#else
#define HDA_CODEC_QUIRK(vend, dev, xname, val) \
{ _SND_PCI_QUIRK_ID(vend, dev), .value = (val), \
.match_codec_ssid = true }
#endif
struct snd_hda_pin_quirk {
unsigned int codec; /* Codec vendor/device ID */
unsigned short subvendor; /* PCI subvendor ID */
const struct hda_pintbl *pins; /* list of matching pins */
#ifdef CONFIG_SND_DEBUG_VERBOSE
const char *name;
#endif
int value; /* quirk value */
};
#ifdef CONFIG_SND_DEBUG_VERBOSE
#define SND_HDA_PIN_QUIRK(_codec, _subvendor, _name, _value, _pins...) \
{ .codec = _codec,\
.subvendor = _subvendor,\
.name = _name,\
.value = _value,\
.pins = (const struct hda_pintbl[]) { _pins, {0, 0}} \
}
#else
#define SND_HDA_PIN_QUIRK(_codec, _subvendor, _name, _value, _pins...) \
{ .codec = _codec,\
.subvendor = _subvendor,\
.value = _value,\
.pins = (const struct hda_pintbl[]) { _pins, {0, 0}} \
}
#endif
#define HDA_FIXUP_ID_NOT_SET -1
#define HDA_FIXUP_ID_NO_FIXUP -2
/* fixup types */
enum {
HDA_FIXUP_INVALID,
HDA_FIXUP_PINS,
HDA_FIXUP_VERBS,
HDA_FIXUP_FUNC,
HDA_FIXUP_PINCTLS,
};
/* fixup action definitions */
enum {
HDA_FIXUP_ACT_PRE_PROBE,
HDA_FIXUP_ACT_PROBE,
HDA_FIXUP_ACT_INIT,
HDA_FIXUP_ACT_BUILD,
HDA_FIXUP_ACT_FREE,
};
int snd_hda_add_verbs(struct hda_codec *codec, const struct hda_verb *list);
void snd_hda_apply_verbs(struct hda_codec *codec);
void snd_hda_apply_pincfgs(struct hda_codec *codec,
const struct hda_pintbl *cfg);
void snd_hda_apply_fixup(struct hda_codec *codec, int action);
void __snd_hda_apply_fixup(struct hda_codec *codec, int id, int action, int depth);
void snd_hda_pick_fixup(struct hda_codec *codec,
const struct hda_model_fixup *models,
const struct hda_quirk *quirk,
const struct hda_fixup *fixlist);
void snd_hda_pick_pin_fixup(struct hda_codec *codec,
const struct snd_hda_pin_quirk *pin_quirk,
const struct hda_fixup *fixlist,
bool match_all_pins);
/* helper macros to retrieve pin default-config values */
#define get_defcfg_connect(cfg) \
((cfg & AC_DEFCFG_PORT_CONN) >> AC_DEFCFG_PORT_CONN_SHIFT)
#define get_defcfg_association(cfg) \
((cfg & AC_DEFCFG_DEF_ASSOC) >> AC_DEFCFG_ASSOC_SHIFT)
#define get_defcfg_location(cfg) \
((cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT)
#define get_defcfg_sequence(cfg) \
(cfg & AC_DEFCFG_SEQUENCE)
#define get_defcfg_device(cfg) \
((cfg & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT)
#define get_defcfg_misc(cfg) \
((cfg & AC_DEFCFG_MISC) >> AC_DEFCFG_MISC_SHIFT)
/* amp values */
#define AMP_IN_MUTE(idx) (0x7080 | ((idx)<<8))
#define AMP_IN_UNMUTE(idx) (0x7000 | ((idx)<<8))
#define AMP_OUT_MUTE 0xb080
#define AMP_OUT_UNMUTE 0xb000
#define AMP_OUT_ZERO 0xb000
/* pinctl values */
#define PIN_IN (AC_PINCTL_IN_EN)
#define PIN_VREFHIZ (AC_PINCTL_IN_EN | AC_PINCTL_VREF_HIZ)
#define PIN_VREF50 (AC_PINCTL_IN_EN | AC_PINCTL_VREF_50)
#define PIN_VREFGRD (AC_PINCTL_IN_EN | AC_PINCTL_VREF_GRD)
#define PIN_VREF80 (AC_PINCTL_IN_EN | AC_PINCTL_VREF_80)
#define PIN_VREF100 (AC_PINCTL_IN_EN | AC_PINCTL_VREF_100)
#define PIN_OUT (AC_PINCTL_OUT_EN)
#define PIN_HP (AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN)
#define PIN_HP_AMP (AC_PINCTL_HP_EN)
unsigned int snd_hda_get_default_vref(struct hda_codec *codec, hda_nid_t pin);
unsigned int snd_hda_correct_pin_ctl(struct hda_codec *codec,
hda_nid_t pin, unsigned int val);
int _snd_hda_set_pin_ctl(struct hda_codec *codec, hda_nid_t pin,
unsigned int val, bool cached);
/**
* snd_hda_set_pin_ctl - Set a pin-control value safely
* @codec: the codec instance
* @pin: the pin NID to set the control
* @val: the pin-control value (AC_PINCTL_* bits)
*
* This function sets the pin-control value to the given pin, but
* filters out the invalid pin-control bits when the pin has no such
* capabilities. For example, when PIN_HP is passed but the pin has no
* HP-drive capability, the HP bit is omitted.
*
* The function doesn't check the input VREF capability bits, though.
* Use snd_hda_get_default_vref() to guess the right value.
* Also, this function is only for analog pins, not for HDMI pins.
*/
static inline int
snd_hda_set_pin_ctl(struct hda_codec *codec, hda_nid_t pin, unsigned int val)
{
return _snd_hda_set_pin_ctl(codec, pin, val, false);
}
/**
* snd_hda_set_pin_ctl_cache - Set a pin-control value safely
* @codec: the codec instance
* @pin: the pin NID to set the control
* @val: the pin-control value (AC_PINCTL_* bits)
*
* Just like snd_hda_set_pin_ctl() but write to cache as well.
*/
static inline int
snd_hda_set_pin_ctl_cache(struct hda_codec *codec, hda_nid_t pin,
unsigned int val)
{
return _snd_hda_set_pin_ctl(codec, pin, val, true);
}
int snd_hda_codec_get_pin_target(struct hda_codec *codec, hda_nid_t nid);
int snd_hda_codec_set_pin_target(struct hda_codec *codec, hda_nid_t nid,
unsigned int val);
#define for_each_hda_codec_node(nid, codec) \
for ((nid) = (codec)->core.start_nid; (nid) < (codec)->core.end_nid; (nid)++)
/* Set the codec power_state flag to indicate to allow unsol event handling;
* see hda_codec_unsol_event() in hda_bind.c. Calling this might confuse the
* state tracking, so use with care.
*/
static inline void snd_hda_codec_allow_unsol_events(struct hda_codec *codec)
{
codec->core.dev.power.power_state = PMSG_ON;
}
/*
* get widget capabilities
*/
static inline u32 get_wcaps(struct hda_codec *codec, hda_nid_t nid)
{
if (nid < codec->core.start_nid ||
nid >= codec->core.start_nid + codec->core.num_nodes)
return 0;
return codec->wcaps[nid - codec->core.start_nid];
}
/* get the widget type from widget capability bits */
static inline int get_wcaps_type(unsigned int wcaps)
{
if (!wcaps)
return -1; /* invalid type */
return (wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
}
static inline unsigned int get_wcaps_channels(u32 wcaps)
{
unsigned int chans;
chans = (wcaps & AC_WCAP_CHAN_CNT_EXT) >> 13;
chans = ((chans << 1) | 1) + 1;
return chans;
}
static inline void snd_hda_override_wcaps(struct hda_codec *codec,
hda_nid_t nid, u32 val)
{
if (nid >= codec->core.start_nid &&
nid < codec->core.start_nid + codec->core.num_nodes)
codec->wcaps[nid - codec->core.start_nid] = val;
}
u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction);
int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir,
unsigned int caps);
/**
* snd_hda_query_pin_caps - Query PIN capabilities
* @codec: the HD-auio codec
* @nid: the NID to query
*
* Query PIN capabilities for the given widget.
* Returns the obtained capability bits.
*
* When cap bits have been already read, this doesn't read again but
* returns the cached value.
*/
static inline u32
snd_hda_query_pin_caps(struct hda_codec *codec, hda_nid_t nid)
{
return snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP);
}
/**
* snd_hda_override_pin_caps - Override the pin capabilities
* @codec: the CODEC
* @nid: the NID to override
* @caps: the capability bits to set
*
* Override the cached PIN capabilitiy bits value by the given one.
*
* Returns zero if successful or a negative error code.
*/
static inline int
snd_hda_override_pin_caps(struct hda_codec *codec, hda_nid_t nid,
unsigned int caps)
{
return snd_hdac_override_parm(&codec->core, nid, AC_PAR_PIN_CAP, caps);
}
bool snd_hda_check_amp_caps(struct hda_codec *codec, hda_nid_t nid,
int dir, unsigned int bits);
#define nid_has_mute(codec, nid, dir) \
snd_hda_check_amp_caps(codec, nid, dir, (AC_AMPCAP_MUTE | AC_AMPCAP_MIN_MUTE))
#define nid_has_volume(codec, nid, dir) \
snd_hda_check_amp_caps(codec, nid, dir, AC_AMPCAP_NUM_STEPS)
/* flags for hda_nid_item */
#define HDA_NID_ITEM_AMP (1<<0)
struct hda_nid_item {
struct snd_kcontrol *kctl;
unsigned int index;
hda_nid_t nid;
unsigned short flags;
};
int snd_hda_ctl_add(struct hda_codec *codec, hda_nid_t nid,
struct snd_kcontrol *kctl);
void snd_hda_ctls_clear(struct hda_codec *codec);
/*
* hwdep interface
*/
#ifdef CONFIG_SND_HDA_HWDEP
int snd_hda_create_hwdep(struct hda_codec *codec);
#else
static inline int snd_hda_create_hwdep(struct hda_codec *codec) { return 0; }
#endif
void snd_hda_sysfs_init(struct hda_codec *codec);
void snd_hda_sysfs_clear(struct hda_codec *codec);
extern const struct attribute_group *snd_hda_dev_attr_groups[];
#ifdef CONFIG_SND_HDA_RECONFIG
const char *snd_hda_get_hint(struct hda_codec *codec, const char *key);
int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key);
int snd_hda_get_int_hint(struct hda_codec *codec, const char *key, int *valp);
#else
static inline
const char *snd_hda_get_hint(struct hda_codec *codec, const char *key)
{
return NULL;
}
static inline
int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key)
{
return -ENOENT;
}
static inline
int snd_hda_get_int_hint(struct hda_codec *codec, const char *key, int *valp)
{
return -ENOENT;
}
#endif
/*
* power-management
*/
void snd_hda_schedule_power_save(struct hda_codec *codec);
struct hda_amp_list {
hda_nid_t nid;
unsigned char dir;
unsigned char idx;
};
struct hda_loopback_check {
const struct hda_amp_list *amplist;
int power_on;
};
int snd_hda_check_amp_list_power(struct hda_codec *codec,
struct hda_loopback_check *check,
hda_nid_t nid);
/* check whether the actual power state matches with the target state */
static inline bool
snd_hda_check_power_state(struct hda_codec *codec, hda_nid_t nid,
unsigned int target_state)
{
return snd_hdac_check_power_state(&codec->core, nid, target_state);
}
static inline unsigned int snd_hda_sync_power_state(struct hda_codec *codec,
hda_nid_t nid,
unsigned int target_state)
{
return snd_hdac_sync_power_state(&codec->core, nid, target_state);
}
unsigned int snd_hda_codec_eapd_power_filter(struct hda_codec *codec,
hda_nid_t nid,
unsigned int power_state);
void snd_hda_codec_shutdown(struct hda_codec *codec);
static inline int snd_hda_codec_init(struct hda_codec *codec)
{
struct hda_codec_driver *driver = hda_codec_to_driver(codec);
if (driver->ops->init)
return driver->ops->init(codec);
return 0;
}
/*
* AMP control callbacks
*/
/* retrieve parameters from private_value */
#define get_amp_nid_(pv) ((pv) & 0xffff)
#define get_amp_nid(kc) get_amp_nid_((kc)->private_value)
#define get_amp_channels(kc) (((kc)->private_value >> 16) & 0x3)
#define get_amp_direction_(pv) (((pv) >> 18) & 0x1)
#define get_amp_direction(kc) get_amp_direction_((kc)->private_value)
#define get_amp_index_(pv) (((pv) >> 19) & 0xf)
#define get_amp_index(kc) get_amp_index_((kc)->private_value)
#define get_amp_offset(kc) (((kc)->private_value >> 23) & 0x3f)
#define get_amp_min_mute(kc) (((kc)->private_value >> 29) & 0x1)
/*
* enum control helper
*/
int snd_hda_enum_helper_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo,
int num_items, const char * const *texts);
#define snd_hda_enum_bool_helper_info(kcontrol, uinfo) \
snd_hda_enum_helper_info(kcontrol, uinfo, 0, NULL)
struct hdmi_eld {
bool monitor_present;
bool eld_valid;
int eld_size;
char eld_buffer[ELD_MAX_SIZE];
struct snd_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);
void snd_hdmi_eld_update_pcm_info(struct snd_parsed_hdmi_eld *e,
struct hda_pcm_stream *hinfo);
#ifdef CONFIG_SND_PROC_FS
void snd_hdmi_print_eld_info(struct hdmi_eld *eld,
struct snd_info_buffer *buffer,
hda_nid_t pin_nid, int dev_id, hda_nid_t cvt_nid);
void snd_hdmi_write_eld_info(struct hdmi_eld *eld,
struct snd_info_buffer *buffer);
#endif
#define SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE 80
void snd_print_channel_allocation(int spk_alloc, char *buf, int buflen);
void snd_hda_codec_display_power(struct hda_codec *codec, bool enable);
/*
*/
#define codec_err(codec, fmt, args...) \
dev_err(hda_codec_dev(codec), fmt, ##args)
#define codec_warn(codec, fmt, args...) \
dev_warn(hda_codec_dev(codec), fmt, ##args)
#define codec_info(codec, fmt, args...) \
dev_info(hda_codec_dev(codec), fmt, ##args)
#define codec_dbg(codec, fmt, args...) \
dev_dbg(hda_codec_dev(codec), fmt, ##args)
#endif /* __SOUND_HDA_LOCAL_H */
+119
View File
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* HWDEP Interface for HD-audio codec
*
* Copyright (c) 2007 Takashi Iwai <tiwai@suse.de>
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/nospec.h>
#include <sound/core.h>
#include <sound/hda_codec.h>
#include "hda_local.h"
#include <sound/hda_hwdep.h>
#include <sound/minors.h>
/*
* write/read an out-of-bound verb
*/
static int verb_write_ioctl(struct hda_codec *codec,
struct hda_verb_ioctl __user *arg)
{
u32 verb, res;
if (get_user(verb, &arg->verb))
return -EFAULT;
res = snd_hda_codec_read(codec, verb >> 24, 0,
(verb >> 8) & 0xffff, verb & 0xff);
if (put_user(res, &arg->res))
return -EFAULT;
return 0;
}
static int get_wcap_ioctl(struct hda_codec *codec,
struct hda_verb_ioctl __user *arg)
{
u32 verb, res;
if (get_user(verb, &arg->verb))
return -EFAULT;
/* open-code get_wcaps(verb>>24) with nospec */
verb >>= 24;
if (verb < codec->core.start_nid ||
verb >= codec->core.start_nid + codec->core.num_nodes) {
res = 0;
} else {
verb -= codec->core.start_nid;
verb = array_index_nospec(verb, codec->core.num_nodes);
res = codec->wcaps[verb];
}
if (put_user(res, &arg->res))
return -EFAULT;
return 0;
}
/*
*/
static int hda_hwdep_ioctl(struct snd_hwdep *hw, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct hda_codec *codec = hw->private_data;
void __user *argp = (void __user *)arg;
switch (cmd) {
case HDA_IOCTL_PVERSION:
return put_user(HDA_HWDEP_VERSION, (int __user *)argp);
case HDA_IOCTL_VERB_WRITE:
return verb_write_ioctl(codec, argp);
case HDA_IOCTL_GET_WCAP:
return get_wcap_ioctl(codec, argp);
}
return -ENOIOCTLCMD;
}
#ifdef CONFIG_COMPAT
static int hda_hwdep_ioctl_compat(struct snd_hwdep *hw, struct file *file,
unsigned int cmd, unsigned long arg)
{
return hda_hwdep_ioctl(hw, file, cmd, (unsigned long)compat_ptr(arg));
}
#endif
static int hda_hwdep_open(struct snd_hwdep *hw, struct file *file)
{
if (!capable(CAP_SYS_RAWIO))
return -EACCES;
return 0;
}
int snd_hda_create_hwdep(struct hda_codec *codec)
{
char hwname[16];
struct snd_hwdep *hwdep;
int err;
sprintf(hwname, "HDA Codec %d", codec->addr);
err = snd_hwdep_new(codec->card, hwname, codec->addr, &hwdep);
if (err < 0)
return err;
codec->hwdep = hwdep;
sprintf(hwdep->name, "HDA Codec %d", codec->addr);
hwdep->iface = SNDRV_HWDEP_IFACE_HDA;
hwdep->private_data = codec;
hwdep->exclusive = 1;
hwdep->ops.open = hda_hwdep_open;
hwdep->ops.ioctl = hda_hwdep_ioctl;
#ifdef CONFIG_COMPAT
hwdep->ops.ioctl_compat = hda_hwdep_ioctl_compat;
#endif
/* for sysfs */
hwdep->dev->groups = snd_hda_dev_attr_groups;
dev_set_drvdata(hwdep->dev, codec);
return 0;
}
+776
View File
@@ -0,0 +1,776 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Jack-detection handling for HD-audio
*
* Copyright (c) 2011 Takashi Iwai <tiwai@suse.de>
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/export.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/jack.h>
#include <sound/hda_codec.h>
#include "hda_local.h"
#include "hda_auto_parser.h"
#include "hda_jack.h"
/**
* is_jack_detectable - Check whether the given pin is jack-detectable
* @codec: the HDA codec
* @nid: pin NID
*
* Check whether the given pin is capable to report the jack detection.
* The jack detection might not work by various reasons, e.g. the jack
* detection is prohibited in the codec level, the pin config has
* AC_DEFCFG_MISC_NO_PRESENCE bit, no unsol support, etc.
*/
bool is_jack_detectable(struct hda_codec *codec, hda_nid_t nid)
{
if (codec->no_jack_detect)
return false;
if (!(snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_PRES_DETECT))
return false;
if (get_defcfg_misc(snd_hda_codec_get_pincfg(codec, nid)) &
AC_DEFCFG_MISC_NO_PRESENCE)
return false;
if (!(get_wcaps(codec, nid) & AC_WCAP_UNSOL_CAP) &&
!codec->jackpoll_interval)
return false;
return true;
}
EXPORT_SYMBOL_GPL(is_jack_detectable);
/* execute pin sense measurement */
static u32 read_pin_sense(struct hda_codec *codec, hda_nid_t nid, int dev_id)
{
u32 pincap;
u32 val;
if (!codec->no_trigger_sense) {
pincap = snd_hda_query_pin_caps(codec, nid);
if (pincap & AC_PINCAP_TRIG_REQ) /* need trigger? */
snd_hda_codec_read(codec, nid, 0,
AC_VERB_SET_PIN_SENSE, 0);
}
val = snd_hda_codec_read(codec, nid, 0,
AC_VERB_GET_PIN_SENSE, dev_id);
if (codec->inv_jack_detect)
val ^= AC_PINSENSE_PRESENCE;
if (codec->eld_jack_detect) {
if (val & AC_PINSENSE_ELDV)
val |= AC_PINSENSE_PRESENCE;
else
val &= ~AC_PINSENSE_PRESENCE;
}
return val;
}
/**
* snd_hda_jack_tbl_get_mst - query the jack-table entry for the given NID
* @codec: the HDA codec
* @nid: pin NID to refer to
* @dev_id: pin device entry id
*/
struct hda_jack_tbl *
snd_hda_jack_tbl_get_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id)
{
struct hda_jack_tbl *jack = codec->jacktbl.list;
int i;
if (!nid || !jack)
return NULL;
for (i = 0; i < codec->jacktbl.used; i++, jack++)
if (jack->nid == nid && jack->dev_id == dev_id)
return jack;
return NULL;
}
EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get_mst);
/**
* snd_hda_jack_tbl_get_from_tag - query the jack-table entry for the given tag
* @codec: the HDA codec
* @tag: tag value to refer to
* @dev_id: pin device entry id
*/
struct hda_jack_tbl *
snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec,
unsigned char tag, int dev_id)
{
struct hda_jack_tbl *jack = codec->jacktbl.list;
int i;
if (!tag || !jack)
return NULL;
for (i = 0; i < codec->jacktbl.used; i++, jack++)
if (jack->tag == tag && jack->dev_id == dev_id)
return jack;
return NULL;
}
EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get_from_tag);
static struct hda_jack_tbl *
any_jack_tbl_get_from_nid(struct hda_codec *codec, hda_nid_t nid)
{
struct hda_jack_tbl *jack = codec->jacktbl.list;
int i;
if (!nid || !jack)
return NULL;
for (i = 0; i < codec->jacktbl.used; i++, jack++)
if (jack->nid == nid)
return jack;
return NULL;
}
/**
* snd_hda_jack_tbl_new - create a jack-table entry for the given NID
* @codec: the HDA codec
* @nid: pin NID to assign
* @dev_id: pin device entry id
*/
static struct hda_jack_tbl *
snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid, int dev_id)
{
struct hda_jack_tbl *jack =
snd_hda_jack_tbl_get_mst(codec, nid, dev_id);
struct hda_jack_tbl *existing_nid_jack =
any_jack_tbl_get_from_nid(codec, nid);
WARN_ON(dev_id != 0 && !codec->dp_mst);
if (jack)
return jack;
jack = snd_array_new(&codec->jacktbl);
if (!jack)
return NULL;
jack->nid = nid;
jack->dev_id = dev_id;
jack->jack_dirty = 1;
if (existing_nid_jack) {
jack->tag = existing_nid_jack->tag;
/*
* Copy jack_detect from existing_nid_jack to avoid
* snd_hda_jack_detect_enable_callback_mst() making multiple
* SET_UNSOLICITED_ENABLE calls on the same pin.
*/
jack->jack_detect = existing_nid_jack->jack_detect;
} else {
jack->tag = codec->jacktbl.used;
}
return jack;
}
void snd_hda_jack_tbl_disconnect(struct hda_codec *codec)
{
struct hda_jack_tbl *jack = codec->jacktbl.list;
int i;
for (i = 0; i < codec->jacktbl.used; i++, jack++) {
if (!codec->bus->shutdown && jack->jack)
snd_device_disconnect(codec->card, jack->jack);
}
}
void snd_hda_jack_tbl_clear(struct hda_codec *codec)
{
struct hda_jack_tbl *jack = codec->jacktbl.list;
int i;
for (i = 0; i < codec->jacktbl.used; i++, jack++) {
struct hda_jack_callback *cb, *next;
/* free jack instances manually when clearing/reconfiguring */
if (!codec->bus->shutdown && jack->jack)
snd_device_free(codec->card, jack->jack);
for (cb = jack->callback; cb; cb = next) {
next = cb->next;
kfree(cb);
}
}
snd_array_free(&codec->jacktbl);
}
#define get_jack_plug_state(sense) !!(sense & AC_PINSENSE_PRESENCE)
/* update the cached value and notification flag if needed */
static void jack_detect_update(struct hda_codec *codec,
struct hda_jack_tbl *jack)
{
if (!jack->jack_dirty)
return;
if (jack->phantom_jack)
jack->pin_sense = AC_PINSENSE_PRESENCE;
else
jack->pin_sense = read_pin_sense(codec, jack->nid,
jack->dev_id);
/* A gating jack indicates the jack is invalid if gating is unplugged */
if (jack->gating_jack &&
!snd_hda_jack_detect_mst(codec, jack->gating_jack, jack->dev_id))
jack->pin_sense &= ~AC_PINSENSE_PRESENCE;
jack->jack_dirty = 0;
/* If a jack is gated by this one update it. */
if (jack->gated_jack) {
struct hda_jack_tbl *gated =
snd_hda_jack_tbl_get_mst(codec, jack->gated_jack,
jack->dev_id);
if (gated) {
gated->jack_dirty = 1;
jack_detect_update(codec, gated);
}
}
}
/**
* snd_hda_jack_set_dirty_all - Mark all the cached as dirty
* @codec: the HDA codec
*
* This function sets the dirty flag to all entries of jack table.
* It's called from the resume path in hda_codec.c.
*/
void snd_hda_jack_set_dirty_all(struct hda_codec *codec)
{
struct hda_jack_tbl *jack = codec->jacktbl.list;
int i;
for (i = 0; i < codec->jacktbl.used; i++, jack++)
if (jack->nid)
jack->jack_dirty = 1;
}
EXPORT_SYMBOL_GPL(snd_hda_jack_set_dirty_all);
/**
* snd_hda_jack_pin_sense - execute pin sense measurement
* @codec: the CODEC to sense
* @nid: the pin NID to sense
* @dev_id: pin device entry id
*
* Execute necessary pin sense measurement and return its Presence Detect,
* Impedance, ELD Valid etc. status bits.
*/
u32 snd_hda_jack_pin_sense(struct hda_codec *codec, hda_nid_t nid, int dev_id)
{
struct hda_jack_tbl *jack =
snd_hda_jack_tbl_get_mst(codec, nid, dev_id);
if (jack) {
jack_detect_update(codec, jack);
return jack->pin_sense;
}
return read_pin_sense(codec, nid, dev_id);
}
EXPORT_SYMBOL_GPL(snd_hda_jack_pin_sense);
/**
* snd_hda_jack_detect_state_mst - query pin Presence Detect status
* @codec: the CODEC to sense
* @nid: the pin NID to sense
* @dev_id: pin device entry id
*
* Query and return the pin's Presence Detect status, as either
* HDA_JACK_NOT_PRESENT, HDA_JACK_PRESENT or HDA_JACK_PHANTOM.
*/
int snd_hda_jack_detect_state_mst(struct hda_codec *codec,
hda_nid_t nid, int dev_id)
{
struct hda_jack_tbl *jack =
snd_hda_jack_tbl_get_mst(codec, nid, dev_id);
if (jack && jack->phantom_jack)
return HDA_JACK_PHANTOM;
else if (snd_hda_jack_pin_sense(codec, nid, dev_id) &
AC_PINSENSE_PRESENCE)
return HDA_JACK_PRESENT;
else
return HDA_JACK_NOT_PRESENT;
}
EXPORT_SYMBOL_GPL(snd_hda_jack_detect_state_mst);
static struct hda_jack_callback *
find_callback_from_list(struct hda_jack_tbl *jack,
hda_jack_callback_fn func)
{
struct hda_jack_callback *cb;
if (!func)
return NULL;
for (cb = jack->callback; cb; cb = cb->next) {
if (cb->func == func)
return cb;
}
return NULL;
}
/**
* snd_hda_jack_detect_enable_callback_mst - enable the jack-detection
* @codec: the HDA codec
* @nid: pin NID to enable
* @func: callback function to register
* @dev_id: pin device entry id
*
* In the case of error, the return value will be a pointer embedded with
* errno. Check and handle the return value appropriately with standard
* macros such as @IS_ERR() and @PTR_ERR().
*/
struct hda_jack_callback *
snd_hda_jack_detect_enable_callback_mst(struct hda_codec *codec, hda_nid_t nid,
int dev_id, hda_jack_callback_fn func)
{
struct hda_jack_tbl *jack;
struct hda_jack_callback *callback = NULL;
int err;
jack = snd_hda_jack_tbl_new(codec, nid, dev_id);
if (!jack)
return ERR_PTR(-ENOMEM);
callback = find_callback_from_list(jack, func);
if (func && !callback) {
callback = kzalloc_obj(*callback);
if (!callback)
return ERR_PTR(-ENOMEM);
callback->func = func;
callback->nid = jack->nid;
callback->dev_id = jack->dev_id;
callback->next = jack->callback;
jack->callback = callback;
}
if (jack->jack_detect)
return callback; /* already registered */
jack->jack_detect = 1;
if (codec->jackpoll_interval > 0)
return callback; /* No unsol if we're polling instead */
err = snd_hda_codec_write_cache(codec, nid, 0,
AC_VERB_SET_UNSOLICITED_ENABLE,
AC_USRSP_EN | jack->tag);
if (err < 0)
return ERR_PTR(err);
return callback;
}
EXPORT_SYMBOL_GPL(snd_hda_jack_detect_enable_callback_mst);
/**
* snd_hda_jack_detect_enable - Enable the jack detection on the given pin
* @codec: the HDA codec
* @nid: pin NID to enable jack detection
* @dev_id: pin device entry id
*
* Enable the jack detection with the default callback. Returns zero if
* successful or a negative error code.
*/
int snd_hda_jack_detect_enable(struct hda_codec *codec, hda_nid_t nid,
int dev_id)
{
return PTR_ERR_OR_ZERO(snd_hda_jack_detect_enable_callback_mst(codec,
nid,
dev_id,
NULL));
}
EXPORT_SYMBOL_GPL(snd_hda_jack_detect_enable);
/**
* snd_hda_jack_set_gating_jack - Set gating jack.
* @codec: the HDA codec
* @gated_nid: gated pin NID
* @gating_nid: gating pin NID
*
* Indicates the gated jack is only valid when the gating jack is plugged.
*/
int snd_hda_jack_set_gating_jack(struct hda_codec *codec, hda_nid_t gated_nid,
hda_nid_t gating_nid)
{
struct hda_jack_tbl *gated = snd_hda_jack_tbl_new(codec, gated_nid, 0);
struct hda_jack_tbl *gating =
snd_hda_jack_tbl_new(codec, gating_nid, 0);
WARN_ON(codec->dp_mst);
if (!gated || !gating)
return -EINVAL;
gated->gating_jack = gating_nid;
gating->gated_jack = gated_nid;
return 0;
}
EXPORT_SYMBOL_GPL(snd_hda_jack_set_gating_jack);
/**
* snd_hda_jack_bind_keymap - bind keys generated from one NID to another jack.
* @codec: the HDA codec
* @key_nid: key event is generated by this pin NID
* @keymap: map of key type and key code
* @jack_nid: key reports to the jack of this pin NID
*
* This function is used in the case of key is generated from one NID while is
* reported to the jack of another NID.
*/
int snd_hda_jack_bind_keymap(struct hda_codec *codec, hda_nid_t key_nid,
const struct hda_jack_keymap *keymap,
hda_nid_t jack_nid)
{
const struct hda_jack_keymap *map;
struct hda_jack_tbl *key_gen = snd_hda_jack_tbl_get(codec, key_nid);
struct hda_jack_tbl *report_to = snd_hda_jack_tbl_get(codec, jack_nid);
WARN_ON(codec->dp_mst);
if (!key_gen || !report_to || !report_to->jack)
return -EINVAL;
key_gen->key_report_jack = jack_nid;
if (keymap)
for (map = keymap; map->type; map++)
snd_jack_set_key(report_to->jack, map->type, map->key);
return 0;
}
EXPORT_SYMBOL_GPL(snd_hda_jack_bind_keymap);
/**
* snd_hda_jack_set_button_state - report button event to the hda_jack_tbl button_state.
* @codec: the HDA codec
* @jack_nid: the button event reports to the jack_tbl of this NID
* @button_state: the button event captured by codec
*
* Codec driver calls this function to report the button event.
*/
void snd_hda_jack_set_button_state(struct hda_codec *codec, hda_nid_t jack_nid,
int button_state)
{
struct hda_jack_tbl *jack = snd_hda_jack_tbl_get(codec, jack_nid);
if (!jack)
return;
if (jack->key_report_jack) {
struct hda_jack_tbl *report_to =
snd_hda_jack_tbl_get(codec, jack->key_report_jack);
if (report_to) {
report_to->button_state = button_state;
return;
}
}
jack->button_state = button_state;
}
EXPORT_SYMBOL_GPL(snd_hda_jack_set_button_state);
/**
* snd_hda_jack_report_sync - sync the states of all jacks and report if changed
* @codec: the HDA codec
*/
void snd_hda_jack_report_sync(struct hda_codec *codec)
{
struct hda_jack_tbl *jack;
int i, state;
/* update all jacks at first */
jack = codec->jacktbl.list;
for (i = 0; i < codec->jacktbl.used; i++, jack++)
if (jack->nid)
jack_detect_update(codec, jack);
/* report the updated jacks; it's done after updating all jacks
* to make sure that all gating jacks properly have been set
*/
jack = codec->jacktbl.list;
for (i = 0; i < codec->jacktbl.used; i++, jack++)
if (jack->nid) {
if (!jack->jack || jack->block_report)
continue;
state = jack->button_state;
if (get_jack_plug_state(jack->pin_sense))
state |= jack->type;
snd_jack_report(jack->jack, state);
if (jack->button_state) {
snd_jack_report(jack->jack,
state & ~jack->button_state);
jack->button_state = 0; /* button released */
}
}
}
EXPORT_SYMBOL_GPL(snd_hda_jack_report_sync);
/* guess the jack type from the pin-config */
static int get_input_jack_type(struct hda_codec *codec, hda_nid_t nid)
{
unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid);
switch (get_defcfg_device(def_conf)) {
case AC_JACK_LINE_OUT:
case AC_JACK_SPEAKER:
return SND_JACK_LINEOUT;
case AC_JACK_HP_OUT:
return SND_JACK_HEADPHONE;
case AC_JACK_SPDIF_OUT:
case AC_JACK_DIG_OTHER_OUT:
return SND_JACK_AVOUT;
case AC_JACK_MIC_IN:
return SND_JACK_MICROPHONE;
default:
return SND_JACK_LINEIN;
}
}
static void hda_free_jack_priv(struct snd_jack *jack)
{
struct hda_jack_tbl *jacks = jack->private_data;
jacks->nid = 0;
jacks->jack = NULL;
}
/**
* snd_hda_jack_add_kctl_mst - Add a kctl for the given pin
* @codec: the HDA codec
* @nid: pin NID to assign
* @dev_id : pin device entry id
* @name: string name for the jack
* @phantom_jack: flag to deal as a phantom jack
* @type: jack type bits to be reported, 0 for guessing from pincfg
* @keymap: optional jack / key mapping
*
* This assigns a jack-detection kctl to the given pin. The kcontrol
* will have the given name and index.
*/
int snd_hda_jack_add_kctl_mst(struct hda_codec *codec, hda_nid_t nid,
int dev_id, const char *name, bool phantom_jack,
int type, const struct hda_jack_keymap *keymap)
{
struct hda_jack_tbl *jack;
const struct hda_jack_keymap *map;
int err, state, buttons;
jack = snd_hda_jack_tbl_new(codec, nid, dev_id);
if (!jack)
return 0;
if (jack->jack)
return 0; /* already created */
if (!type)
type = get_input_jack_type(codec, nid);
buttons = 0;
if (keymap) {
for (map = keymap; map->type; map++)
buttons |= map->type;
}
err = snd_jack_new(codec->card, name, type | buttons,
&jack->jack, true, phantom_jack);
if (err < 0)
return err;
jack->phantom_jack = !!phantom_jack;
jack->type = type;
jack->button_state = 0;
jack->jack->private_data = jack;
jack->jack->private_free = hda_free_jack_priv;
if (keymap) {
for (map = keymap; map->type; map++)
snd_jack_set_key(jack->jack, map->type, map->key);
}
state = snd_hda_jack_detect_mst(codec, nid, dev_id);
snd_jack_report(jack->jack, state ? jack->type : 0);
return 0;
}
EXPORT_SYMBOL_GPL(snd_hda_jack_add_kctl_mst);
static int add_jack_kctl(struct hda_codec *codec, hda_nid_t nid,
const struct auto_pin_cfg *cfg,
const char *base_name)
{
unsigned int def_conf, conn;
char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
int err;
bool phantom_jack;
WARN_ON(codec->dp_mst);
if (!nid)
return 0;
def_conf = snd_hda_codec_get_pincfg(codec, nid);
conn = get_defcfg_connect(def_conf);
if (conn == AC_JACK_PORT_NONE)
return 0;
phantom_jack = (conn != AC_JACK_PORT_COMPLEX) ||
!is_jack_detectable(codec, nid);
if (base_name)
strscpy(name, base_name, sizeof(name));
else
snd_hda_get_pin_label(codec, nid, cfg, name, sizeof(name), NULL);
if (phantom_jack)
/* Example final name: "Internal Mic Phantom Jack" */
strncat(name, " Phantom", sizeof(name) - strlen(name) - 1);
err = snd_hda_jack_add_kctl(codec, nid, name, phantom_jack, 0, NULL);
if (err < 0)
return err;
if (!phantom_jack)
return snd_hda_jack_detect_enable(codec, nid, 0);
return 0;
}
/**
* snd_hda_jack_add_kctls - Add kctls for all pins included in the given pincfg
* @codec: the HDA codec
* @cfg: pin config table to parse
*/
int snd_hda_jack_add_kctls(struct hda_codec *codec,
const struct auto_pin_cfg *cfg)
{
const hda_nid_t *p;
int i, err;
for (i = 0; i < cfg->num_inputs; i++) {
/* If we have headphone mics; make sure they get the right name
before grabbed by output pins */
if (cfg->inputs[i].is_headphone_mic) {
if (auto_cfg_hp_outs(cfg) == 1)
err = add_jack_kctl(codec, auto_cfg_hp_pins(cfg)[0],
cfg, "Headphone Mic");
else
err = add_jack_kctl(codec, cfg->inputs[i].pin,
cfg, "Headphone Mic");
} else
err = add_jack_kctl(codec, cfg->inputs[i].pin, cfg,
NULL);
if (err < 0)
return err;
}
for (i = 0, p = cfg->line_out_pins; i < cfg->line_outs; i++, p++) {
err = add_jack_kctl(codec, *p, cfg, NULL);
if (err < 0)
return err;
}
for (i = 0, p = cfg->hp_pins; i < cfg->hp_outs; i++, p++) {
if (*p == *cfg->line_out_pins) /* might be duplicated */
break;
err = add_jack_kctl(codec, *p, cfg, NULL);
if (err < 0)
return err;
}
for (i = 0, p = cfg->speaker_pins; i < cfg->speaker_outs; i++, p++) {
if (*p == *cfg->line_out_pins) /* might be duplicated */
break;
err = add_jack_kctl(codec, *p, cfg, NULL);
if (err < 0)
return err;
}
for (i = 0, p = cfg->dig_out_pins; i < cfg->dig_outs; i++, p++) {
err = add_jack_kctl(codec, *p, cfg, NULL);
if (err < 0)
return err;
}
err = add_jack_kctl(codec, cfg->dig_in_pin, cfg, NULL);
if (err < 0)
return err;
err = add_jack_kctl(codec, cfg->mono_out_pin, cfg, NULL);
if (err < 0)
return err;
return 0;
}
EXPORT_SYMBOL_GPL(snd_hda_jack_add_kctls);
static void call_jack_callback(struct hda_codec *codec, unsigned int res,
struct hda_jack_tbl *jack)
{
struct hda_jack_callback *cb;
for (cb = jack->callback; cb; cb = cb->next) {
cb->jack = jack;
cb->unsol_res = res;
cb->func(codec, cb);
}
if (jack->gated_jack) {
struct hda_jack_tbl *gated =
snd_hda_jack_tbl_get_mst(codec, jack->gated_jack,
jack->dev_id);
if (gated) {
for (cb = gated->callback; cb; cb = cb->next) {
cb->jack = gated;
cb->unsol_res = res;
cb->func(codec, cb);
}
}
}
}
/**
* snd_hda_jack_unsol_event - Handle an unsolicited event
* @codec: the HDA codec
* @res: the unsolicited event data
*/
void snd_hda_jack_unsol_event(struct hda_codec *codec, unsigned int res)
{
struct hda_jack_tbl *event;
int tag = (res & AC_UNSOL_RES_TAG) >> AC_UNSOL_RES_TAG_SHIFT;
if (codec->dp_mst) {
int dev_entry =
(res & AC_UNSOL_RES_DE) >> AC_UNSOL_RES_DE_SHIFT;
event = snd_hda_jack_tbl_get_from_tag(codec, tag, dev_entry);
} else {
event = snd_hda_jack_tbl_get_from_tag(codec, tag, 0);
}
if (!event)
return;
if (event->key_report_jack) {
struct hda_jack_tbl *report_to =
snd_hda_jack_tbl_get_mst(codec, event->key_report_jack,
event->dev_id);
if (report_to)
report_to->jack_dirty = 1;
} else
event->jack_dirty = 1;
call_jack_callback(codec, res, event);
snd_hda_jack_report_sync(codec);
}
EXPORT_SYMBOL_GPL(snd_hda_jack_unsol_event);
/**
* snd_hda_jack_poll_all - Poll all jacks
* @codec: the HDA codec
*
* Poll all detectable jacks with dirty flag, update the status, call
* callbacks and call snd_hda_jack_report_sync() if any changes are found.
*/
void snd_hda_jack_poll_all(struct hda_codec *codec)
{
struct hda_jack_tbl *jack = codec->jacktbl.list;
int i, changes = 0;
for (i = 0; i < codec->jacktbl.used; i++, jack++) {
unsigned int old_sense;
if (!jack->nid || !jack->jack_dirty || jack->phantom_jack)
continue;
old_sense = get_jack_plug_state(jack->pin_sense);
jack_detect_update(codec, jack);
if (old_sense == get_jack_plug_state(jack->pin_sense))
continue;
changes = 1;
call_jack_callback(codec, 0, jack);
}
if (changes)
snd_hda_jack_report_sync(codec);
}
EXPORT_SYMBOL_GPL(snd_hda_jack_poll_all);
+979
View File
@@ -0,0 +1,979 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Universal Interface for Intel High Definition Audio Codec
*
* Generic proc interface
*
* Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <linux/module.h>
#include <sound/hda_codec.h>
#include "hda_local.h"
static int dump_coef = -1;
module_param(dump_coef, int, 0644);
MODULE_PARM_DESC(dump_coef, "Dump processing coefficients in codec proc file (-1=auto, 0=disable, 1=enable)");
/* always use noncached version */
#define param_read(codec, nid, parm) \
snd_hdac_read_parm_uncached(&(codec)->core, nid, parm)
static const char *get_wid_type_name(unsigned int wid_value)
{
static const char * const names[16] = {
[AC_WID_AUD_OUT] = "Audio Output",
[AC_WID_AUD_IN] = "Audio Input",
[AC_WID_AUD_MIX] = "Audio Mixer",
[AC_WID_AUD_SEL] = "Audio Selector",
[AC_WID_PIN] = "Pin Complex",
[AC_WID_POWER] = "Power Widget",
[AC_WID_VOL_KNB] = "Volume Knob Widget",
[AC_WID_BEEP] = "Beep Generator Widget",
[AC_WID_VENDOR] = "Vendor Defined Widget",
};
if (wid_value == -1)
return "UNKNOWN Widget";
wid_value &= 0xf;
if (names[wid_value])
return names[wid_value];
else
return "UNKNOWN Widget";
}
static void print_nid_array(struct snd_info_buffer *buffer,
struct hda_codec *codec, hda_nid_t nid,
struct snd_array *array)
{
int i;
struct hda_nid_item *items = array->list, *item;
struct snd_kcontrol *kctl;
for (i = 0; i < array->used; i++) {
item = &items[i];
if (item->nid == nid) {
kctl = item->kctl;
snd_iprintf(buffer,
" Control: name=\"%s\", index=%i, device=%i\n",
kctl->id.name, kctl->id.index + item->index,
kctl->id.device);
if (item->flags & HDA_NID_ITEM_AMP)
snd_iprintf(buffer,
" ControlAmp: chs=%lu, dir=%s, "
"idx=%lu, ofs=%lu\n",
get_amp_channels(kctl),
get_amp_direction(kctl) ? "Out" : "In",
get_amp_index(kctl),
get_amp_offset(kctl));
}
}
}
static void print_nid_pcms(struct snd_info_buffer *buffer,
struct hda_codec *codec, hda_nid_t nid)
{
int type;
struct hda_pcm *cpcm;
list_for_each_entry(cpcm, &codec->pcm_list_head, list) {
for (type = 0; type < 2; type++) {
if (cpcm->stream[type].nid != nid || cpcm->pcm == NULL)
continue;
snd_iprintf(buffer, " Device: name=\"%s\", "
"type=\"%s\", device=%i\n",
cpcm->name,
snd_hda_pcm_type_name[cpcm->pcm_type],
cpcm->pcm->device);
}
}
}
static void print_amp_caps(struct snd_info_buffer *buffer,
struct hda_codec *codec, hda_nid_t nid, int dir)
{
unsigned int caps;
caps = param_read(codec, nid, dir == HDA_OUTPUT ?
AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP);
if (caps == -1 || caps == 0) {
snd_iprintf(buffer, "N/A\n");
return;
}
snd_iprintf(buffer, "ofs=0x%02x, nsteps=0x%02x, stepsize=0x%02x, "
"mute=%x\n",
caps & AC_AMPCAP_OFFSET,
(caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT,
(caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT,
(caps & AC_AMPCAP_MUTE) >> AC_AMPCAP_MUTE_SHIFT);
}
/* is this a stereo widget or a stereo-to-mono mix? */
static bool is_stereo_amps(struct hda_codec *codec, hda_nid_t nid,
int dir, unsigned int wcaps, int indices)
{
hda_nid_t conn;
if (wcaps & AC_WCAP_STEREO)
return true;
/* check for a stereo-to-mono mix; it must be:
* only a single connection, only for input, and only a mixer widget
*/
if (indices != 1 || dir != HDA_INPUT ||
get_wcaps_type(wcaps) != AC_WID_AUD_MIX)
return false;
if (snd_hda_get_raw_connections(codec, nid, &conn, 1) < 0)
return false;
/* the connection source is a stereo? */
wcaps = snd_hda_param_read(codec, conn, AC_PAR_AUDIO_WIDGET_CAP);
return !!(wcaps & AC_WCAP_STEREO);
}
static void print_amp_vals(struct snd_info_buffer *buffer,
struct hda_codec *codec, hda_nid_t nid,
int dir, unsigned int wcaps, int indices)
{
unsigned int val;
bool stereo;
int i;
stereo = is_stereo_amps(codec, nid, dir, wcaps, indices);
dir = dir == HDA_OUTPUT ? AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT;
for (i = 0; i < indices; i++) {
snd_iprintf(buffer, " [");
val = snd_hda_codec_read(codec, nid, 0,
AC_VERB_GET_AMP_GAIN_MUTE,
AC_AMP_GET_LEFT | dir | i);
snd_iprintf(buffer, "0x%02x", val);
if (stereo) {
val = snd_hda_codec_read(codec, nid, 0,
AC_VERB_GET_AMP_GAIN_MUTE,
AC_AMP_GET_RIGHT | dir | i);
snd_iprintf(buffer, " 0x%02x", val);
}
snd_iprintf(buffer, "]");
}
snd_iprintf(buffer, "\n");
}
static void print_pcm_rates(struct snd_info_buffer *buffer, unsigned int pcm)
{
static const unsigned int rates[] = {
8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200,
96000, 176400, 192000, 384000
};
int i;
pcm &= AC_SUPPCM_RATES;
snd_iprintf(buffer, " rates [0x%x]:", pcm);
for (i = 0; i < ARRAY_SIZE(rates); i++)
if (pcm & (1 << i))
snd_iprintf(buffer, " %d", rates[i]);
snd_iprintf(buffer, "\n");
}
static void print_pcm_bits(struct snd_info_buffer *buffer, unsigned int pcm)
{
char buf[SND_PRINT_BITS_ADVISED_BUFSIZE];
snd_iprintf(buffer, " bits [0x%x]:", (pcm >> 16) & 0xff);
snd_print_pcm_bits(pcm, buf, sizeof(buf));
snd_iprintf(buffer, "%s\n", buf);
}
static void print_pcm_formats(struct snd_info_buffer *buffer,
unsigned int streams)
{
snd_iprintf(buffer, " formats [0x%x]:", streams & 0xf);
if (streams & AC_SUPFMT_PCM)
snd_iprintf(buffer, " PCM");
if (streams & AC_SUPFMT_FLOAT32)
snd_iprintf(buffer, " FLOAT");
if (streams & AC_SUPFMT_AC3)
snd_iprintf(buffer, " AC3");
snd_iprintf(buffer, "\n");
}
static void print_pcm_caps(struct snd_info_buffer *buffer,
struct hda_codec *codec, hda_nid_t nid)
{
unsigned int pcm = param_read(codec, nid, AC_PAR_PCM);
unsigned int stream = param_read(codec, nid, AC_PAR_STREAM);
if (pcm == -1 || stream == -1) {
snd_iprintf(buffer, "N/A\n");
return;
}
print_pcm_rates(buffer, pcm);
print_pcm_bits(buffer, pcm);
print_pcm_formats(buffer, stream);
}
static const char *get_jack_connection(u32 cfg)
{
static const char * const names[16] = {
"Unknown", "1/8", "1/4", "ATAPI",
"RCA", "Optical","Digital", "Analog",
"DIN", "XLR", "RJ11", "Comb",
NULL, NULL, NULL, "Other"
};
cfg = (cfg & AC_DEFCFG_CONN_TYPE) >> AC_DEFCFG_CONN_TYPE_SHIFT;
if (names[cfg])
return names[cfg];
else
return "UNKNOWN";
}
static const char *get_jack_color(u32 cfg)
{
static const char * const names[16] = {
"Unknown", "Black", "Grey", "Blue",
"Green", "Red", "Orange", "Yellow",
"Purple", "Pink", NULL, NULL,
NULL, NULL, "White", "Other",
};
cfg = (cfg & AC_DEFCFG_COLOR) >> AC_DEFCFG_COLOR_SHIFT;
if (names[cfg])
return names[cfg];
else
return "UNKNOWN";
}
/*
* Parse the pin default config value and returns the string of the
* jack location, e.g. "Rear", "Front", etc.
*/
static const char *get_jack_location(u32 cfg)
{
static const char * const bases[7] = {
"N/A", "Rear", "Front", "Left", "Right", "Top", "Bottom",
};
static const unsigned char specials_idx[] = {
0x07, 0x08,
0x17, 0x18, 0x19,
0x37, 0x38
};
static const char * const specials[] = {
"Rear Panel", "Drive Bar",
"Riser", "HDMI", "ATAPI",
"Mobile-In", "Mobile-Out"
};
int i;
cfg = (cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT;
if ((cfg & 0x0f) < 7)
return bases[cfg & 0x0f];
for (i = 0; i < ARRAY_SIZE(specials_idx); i++) {
if (cfg == specials_idx[i])
return specials[i];
}
return "UNKNOWN";
}
/*
* Parse the pin default config value and returns the string of the
* jack connectivity, i.e. external or internal connection.
*/
static const char *get_jack_connectivity(u32 cfg)
{
static const char * const jack_locations[4] = {
"Ext", "Int", "Sep", "Oth"
};
return jack_locations[(cfg >> (AC_DEFCFG_LOCATION_SHIFT + 4)) & 3];
}
/*
* Parse the pin default config value and returns the string of the
* jack type, i.e. the purpose of the jack, such as Line-Out or CD.
*/
static const char *get_jack_type(u32 cfg)
{
static const char * const jack_types[16] = {
"Line Out", "Speaker", "HP Out", "CD",
"SPDIF Out", "Digital Out", "Modem Line", "Modem Hand",
"Line In", "Aux", "Mic", "Telephony",
"SPDIF In", "Digital In", "Reserved", "Other"
};
return jack_types[(cfg & AC_DEFCFG_DEVICE)
>> AC_DEFCFG_DEVICE_SHIFT];
}
static void print_pin_caps(struct snd_info_buffer *buffer,
struct hda_codec *codec, hda_nid_t nid,
int *supports_vref)
{
static const char * const jack_conns[4] = {
"Jack", "N/A", "Fixed", "Both"
};
unsigned int caps, val;
caps = param_read(codec, nid, AC_PAR_PIN_CAP);
snd_iprintf(buffer, " Pincap 0x%08x:", caps);
if (caps & AC_PINCAP_IN)
snd_iprintf(buffer, " IN");
if (caps & AC_PINCAP_OUT)
snd_iprintf(buffer, " OUT");
if (caps & AC_PINCAP_HP_DRV)
snd_iprintf(buffer, " HP");
if (caps & AC_PINCAP_EAPD)
snd_iprintf(buffer, " EAPD");
if (caps & AC_PINCAP_PRES_DETECT)
snd_iprintf(buffer, " Detect");
if (caps & AC_PINCAP_BALANCE)
snd_iprintf(buffer, " Balanced");
if (caps & AC_PINCAP_HDMI) {
/* Realtek uses this bit as a different meaning */
if ((codec->core.vendor_id >> 16) == 0x10ec)
snd_iprintf(buffer, " R/L");
else {
if (caps & AC_PINCAP_HBR)
snd_iprintf(buffer, " HBR");
snd_iprintf(buffer, " HDMI");
}
}
if (caps & AC_PINCAP_DP)
snd_iprintf(buffer, " DP");
if (caps & AC_PINCAP_TRIG_REQ)
snd_iprintf(buffer, " Trigger");
if (caps & AC_PINCAP_IMP_SENSE)
snd_iprintf(buffer, " ImpSense");
snd_iprintf(buffer, "\n");
if (caps & AC_PINCAP_VREF) {
unsigned int vref =
(caps & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT;
snd_iprintf(buffer, " Vref caps:");
if (vref & AC_PINCAP_VREF_HIZ)
snd_iprintf(buffer, " HIZ");
if (vref & AC_PINCAP_VREF_50)
snd_iprintf(buffer, " 50");
if (vref & AC_PINCAP_VREF_GRD)
snd_iprintf(buffer, " GRD");
if (vref & AC_PINCAP_VREF_80)
snd_iprintf(buffer, " 80");
if (vref & AC_PINCAP_VREF_100)
snd_iprintf(buffer, " 100");
snd_iprintf(buffer, "\n");
*supports_vref = 1;
} else
*supports_vref = 0;
if (caps & AC_PINCAP_EAPD) {
val = snd_hda_codec_read(codec, nid, 0,
AC_VERB_GET_EAPD_BTLENABLE, 0);
snd_iprintf(buffer, " EAPD 0x%x:", val);
if (val & AC_EAPDBTL_BALANCED)
snd_iprintf(buffer, " BALANCED");
if (val & AC_EAPDBTL_EAPD)
snd_iprintf(buffer, " EAPD");
if (val & AC_EAPDBTL_LR_SWAP)
snd_iprintf(buffer, " R/L");
snd_iprintf(buffer, "\n");
}
caps = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONFIG_DEFAULT, 0);
snd_iprintf(buffer, " Pin Default 0x%08x: [%s] %s at %s %s\n", caps,
jack_conns[(caps & AC_DEFCFG_PORT_CONN) >> AC_DEFCFG_PORT_CONN_SHIFT],
get_jack_type(caps),
get_jack_connectivity(caps),
get_jack_location(caps));
snd_iprintf(buffer, " Conn = %s, Color = %s\n",
get_jack_connection(caps),
get_jack_color(caps));
/* Default association and sequence values refer to default grouping
* of pin complexes and their sequence within the group. This is used
* for priority and resource allocation.
*/
snd_iprintf(buffer, " DefAssociation = 0x%x, Sequence = 0x%x\n",
(caps & AC_DEFCFG_DEF_ASSOC) >> AC_DEFCFG_ASSOC_SHIFT,
caps & AC_DEFCFG_SEQUENCE);
if (((caps & AC_DEFCFG_MISC) >> AC_DEFCFG_MISC_SHIFT) &
AC_DEFCFG_MISC_NO_PRESENCE) {
/* Miscellaneous bit indicates external hardware does not
* support presence detection even if the pin complex
* indicates it is supported.
*/
snd_iprintf(buffer, " Misc = NO_PRESENCE\n");
}
}
static void print_pin_ctls(struct snd_info_buffer *buffer,
struct hda_codec *codec, hda_nid_t nid,
int supports_vref)
{
unsigned int pinctls;
pinctls = snd_hda_codec_read(codec, nid, 0,
AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
snd_iprintf(buffer, " Pin-ctls: 0x%02x:", pinctls);
if (pinctls & AC_PINCTL_IN_EN)
snd_iprintf(buffer, " IN");
if (pinctls & AC_PINCTL_OUT_EN)
snd_iprintf(buffer, " OUT");
if (pinctls & AC_PINCTL_HP_EN)
snd_iprintf(buffer, " HP");
if (supports_vref) {
int vref = pinctls & AC_PINCTL_VREFEN;
switch (vref) {
case AC_PINCTL_VREF_HIZ:
snd_iprintf(buffer, " VREF_HIZ");
break;
case AC_PINCTL_VREF_50:
snd_iprintf(buffer, " VREF_50");
break;
case AC_PINCTL_VREF_GRD:
snd_iprintf(buffer, " VREF_GRD");
break;
case AC_PINCTL_VREF_80:
snd_iprintf(buffer, " VREF_80");
break;
case AC_PINCTL_VREF_100:
snd_iprintf(buffer, " VREF_100");
break;
}
}
snd_iprintf(buffer, "\n");
}
static void print_vol_knob(struct snd_info_buffer *buffer,
struct hda_codec *codec, hda_nid_t nid)
{
unsigned int cap = param_read(codec, nid, AC_PAR_VOL_KNB_CAP);
snd_iprintf(buffer, " Volume-Knob: delta=%d, steps=%d, ",
(cap >> 7) & 1, cap & 0x7f);
cap = snd_hda_codec_read(codec, nid, 0,
AC_VERB_GET_VOLUME_KNOB_CONTROL, 0);
snd_iprintf(buffer, "direct=%d, val=%d\n",
(cap >> 7) & 1, cap & 0x7f);
}
static void print_audio_io(struct snd_info_buffer *buffer,
struct hda_codec *codec, hda_nid_t nid,
unsigned int wid_type)
{
int conv = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONV, 0);
snd_iprintf(buffer,
" Converter: stream=%d, channel=%d\n",
(conv & AC_CONV_STREAM) >> AC_CONV_STREAM_SHIFT,
conv & AC_CONV_CHANNEL);
if (wid_type == AC_WID_AUD_IN && (conv & AC_CONV_CHANNEL) == 0) {
int sdi = snd_hda_codec_read(codec, nid, 0,
AC_VERB_GET_SDI_SELECT, 0);
snd_iprintf(buffer, " SDI-Select: %d\n",
sdi & AC_SDI_SELECT);
}
}
static void print_digital_conv(struct snd_info_buffer *buffer,
struct hda_codec *codec, hda_nid_t nid)
{
unsigned int digi1 = snd_hda_codec_read(codec, nid, 0,
AC_VERB_GET_DIGI_CONVERT_1, 0);
unsigned char digi2 = digi1 >> 8;
unsigned char digi3 = digi1 >> 16;
snd_iprintf(buffer, " Digital:");
if (digi1 & AC_DIG1_ENABLE)
snd_iprintf(buffer, " Enabled");
if (digi1 & AC_DIG1_V)
snd_iprintf(buffer, " Validity");
if (digi1 & AC_DIG1_VCFG)
snd_iprintf(buffer, " ValidityCfg");
if (digi1 & AC_DIG1_EMPHASIS)
snd_iprintf(buffer, " Preemphasis");
if (digi1 & AC_DIG1_COPYRIGHT)
snd_iprintf(buffer, " Non-Copyright");
if (digi1 & AC_DIG1_NONAUDIO)
snd_iprintf(buffer, " Non-Audio");
if (digi1 & AC_DIG1_PROFESSIONAL)
snd_iprintf(buffer, " Pro");
if (digi1 & AC_DIG1_LEVEL)
snd_iprintf(buffer, " GenLevel");
if (digi3 & AC_DIG3_KAE)
snd_iprintf(buffer, " KAE");
snd_iprintf(buffer, "\n");
snd_iprintf(buffer, " Digital category: 0x%x\n",
digi2 & AC_DIG2_CC);
snd_iprintf(buffer, " IEC Coding Type: 0x%x\n",
digi3 & AC_DIG3_ICT);
}
static const char *get_pwr_state(u32 state)
{
static const char * const buf[] = {
"D0", "D1", "D2", "D3", "D3cold"
};
if (state < ARRAY_SIZE(buf))
return buf[state];
return "UNKNOWN";
}
static void print_power_state(struct snd_info_buffer *buffer,
struct hda_codec *codec, hda_nid_t nid)
{
static const char * const names[] = {
[ilog2(AC_PWRST_D0SUP)] = "D0",
[ilog2(AC_PWRST_D1SUP)] = "D1",
[ilog2(AC_PWRST_D2SUP)] = "D2",
[ilog2(AC_PWRST_D3SUP)] = "D3",
[ilog2(AC_PWRST_D3COLDSUP)] = "D3cold",
[ilog2(AC_PWRST_S3D3COLDSUP)] = "S3D3cold",
[ilog2(AC_PWRST_CLKSTOP)] = "CLKSTOP",
[ilog2(AC_PWRST_EPSS)] = "EPSS",
};
int sup = param_read(codec, nid, AC_PAR_POWER_STATE);
int pwr = snd_hda_codec_read(codec, nid, 0,
AC_VERB_GET_POWER_STATE, 0);
if (sup != -1) {
int i;
snd_iprintf(buffer, " Power states: ");
for (i = 0; i < ARRAY_SIZE(names); i++) {
if (sup & (1U << i))
snd_iprintf(buffer, " %s", names[i]);
}
snd_iprintf(buffer, "\n");
}
snd_iprintf(buffer, " Power: setting=%s, actual=%s",
get_pwr_state(pwr & AC_PWRST_SETTING),
get_pwr_state((pwr & AC_PWRST_ACTUAL) >>
AC_PWRST_ACTUAL_SHIFT));
if (pwr & AC_PWRST_ERROR)
snd_iprintf(buffer, ", Error");
if (pwr & AC_PWRST_CLK_STOP_OK)
snd_iprintf(buffer, ", Clock-stop-OK");
if (pwr & AC_PWRST_SETTING_RESET)
snd_iprintf(buffer, ", Setting-reset");
snd_iprintf(buffer, "\n");
}
static void print_unsol_cap(struct snd_info_buffer *buffer,
struct hda_codec *codec, hda_nid_t nid)
{
int unsol = snd_hda_codec_read(codec, nid, 0,
AC_VERB_GET_UNSOLICITED_RESPONSE, 0);
snd_iprintf(buffer,
" Unsolicited: tag=%02x, enabled=%d\n",
unsol & AC_UNSOL_TAG,
(unsol & AC_UNSOL_ENABLED) ? 1 : 0);
}
static inline bool can_dump_coef(struct hda_codec *codec)
{
switch (dump_coef) {
case 0: return false;
case 1: return true;
default: return codec->dump_coef;
}
}
static void print_proc_caps(struct snd_info_buffer *buffer,
struct hda_codec *codec, hda_nid_t nid)
{
unsigned int i, ncoeff, oldindex;
unsigned int proc_caps = param_read(codec, nid, AC_PAR_PROC_CAP);
ncoeff = (proc_caps & AC_PCAP_NUM_COEF) >> AC_PCAP_NUM_COEF_SHIFT;
snd_iprintf(buffer, " Processing caps: benign=%d, ncoeff=%d\n",
proc_caps & AC_PCAP_BENIGN, ncoeff);
if (!can_dump_coef(codec))
return;
/* Note: This is racy - another process could run in parallel and change
the coef index too. */
oldindex = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_COEF_INDEX, 0);
for (i = 0; i < ncoeff; i++) {
unsigned int val;
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_COEF_INDEX, i);
val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_PROC_COEF,
0);
snd_iprintf(buffer, " Coeff 0x%02x: 0x%04x\n", i, val);
}
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_COEF_INDEX, oldindex);
}
static void print_conn_list(struct snd_info_buffer *buffer,
struct hda_codec *codec, hda_nid_t nid,
unsigned int wid_type, hda_nid_t *conn,
int conn_len)
{
int c, curr = -1;
const hda_nid_t *list;
int cache_len;
if (conn_len > 1 &&
wid_type != AC_WID_AUD_MIX &&
wid_type != AC_WID_VOL_KNB &&
wid_type != AC_WID_POWER)
curr = snd_hda_codec_read(codec, nid, 0,
AC_VERB_GET_CONNECT_SEL, 0);
snd_iprintf(buffer, " Connection: %d\n", conn_len);
if (conn_len > 0) {
snd_iprintf(buffer, " ");
for (c = 0; c < conn_len; c++) {
snd_iprintf(buffer, " 0x%02x", conn[c]);
if (c == curr)
snd_iprintf(buffer, "*");
}
snd_iprintf(buffer, "\n");
}
/* Get Cache connections info */
cache_len = snd_hda_get_conn_list(codec, nid, &list);
if (cache_len >= 0 && (cache_len != conn_len ||
memcmp(list, conn, conn_len) != 0)) {
snd_iprintf(buffer, " In-driver Connection: %d\n", cache_len);
if (cache_len > 0) {
snd_iprintf(buffer, " ");
for (c = 0; c < cache_len; c++)
snd_iprintf(buffer, " 0x%02x", list[c]);
snd_iprintf(buffer, "\n");
}
}
}
static void print_gpio(struct snd_info_buffer *buffer,
struct hda_codec *codec, hda_nid_t nid)
{
unsigned int gpio =
param_read(codec, codec->core.afg, AC_PAR_GPIO_CAP);
int i, gpio_max, gpo_max, gpi_max;
gpio_max = gpio & AC_GPIO_IO_COUNT;
gpo_max = (gpio & AC_GPIO_O_COUNT) >> AC_GPIO_O_COUNT_SHIFT;
gpi_max = (gpio & AC_GPIO_I_COUNT) >> AC_GPIO_I_COUNT_SHIFT;
snd_iprintf(buffer, "GPIO: io=%d, o=%d, i=%d, "
"unsolicited=%d, wake=%d\n",
gpio_max, gpo_max, gpi_max,
(gpio & AC_GPIO_UNSOLICITED) ? 1 : 0,
(gpio & AC_GPIO_WAKE) ? 1 : 0);
if (gpio_max && gpio_max <= 8) {
unsigned int enable, direction, wake, unsol, sticky, data;
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 < gpio_max; ++i) {
snd_iprintf(buffer,
" IO[%d]: enable=%d, dir=%d, wake=%d, ",
i, (enable & (1 << i)) ? 1 : 0,
(direction & (1 << i)) ? 1 : 0,
(wake & (1 << i)) ? 1 : 0);
snd_iprintf(buffer,
"sticky=%d, data=%d, unsol=%d\n",
(sticky & (1 << i)) ? 1 : 0,
(data & (1 << i)) ? 1 : 0,
(unsol & (1 << i)) ? 1 : 0);
}
}
if (gpo_max && gpo_max <= 8) {
unsigned int gpo_data;
gpo_data = snd_hda_codec_read(codec, nid, 0,
AC_VERB_GET_GPO_DATA, 0);
for (i = 0; i < gpo_max; ++i)
snd_iprintf(buffer, " GPO[%d]: data=%d\n", i,
(gpo_data & (1 << i)) ? 1 : 0);
}
if (gpi_max && gpi_max <= 8) {
unsigned int wake, unsol, sticky, data;
wake = snd_hda_codec_read(codec, nid, 0,
AC_VERB_GET_GPI_WAKE_MASK, 0);
unsol = snd_hda_codec_read(codec, nid, 0,
AC_VERB_GET_GPI_UNSOLICITED_RSP_MASK,
0);
sticky = snd_hda_codec_read(codec, nid, 0,
AC_VERB_GET_GPI_STICKY_MASK, 0);
data = snd_hda_codec_read(codec, nid, 0,
AC_VERB_GET_GPI_DATA, 0);
for (i = 0; i < gpi_max; ++i)
snd_iprintf(buffer, " GPI[%d]: wake=%d, sticky=%d, data=%d, unsol=%d\n",
i, (wake & (1 << i)) ? 1 : 0,
(sticky & (1 << i)) ? 1 : 0,
(data & (1 << i)) ? 1 : 0,
(unsol & (1 << i)) ? 1 : 0);
}
print_nid_array(buffer, codec, nid, &codec->mixers);
print_nid_array(buffer, codec, nid, &codec->nids);
}
static void print_dpmst_connections(struct snd_info_buffer *buffer, struct hda_codec *codec,
hda_nid_t nid, int dev_num)
{
int c, conn_len, curr, dev_id_saved;
hda_nid_t *conn;
conn_len = snd_hda_get_num_raw_conns(codec, nid);
if (conn_len <= 0)
return;
conn = kmalloc_objs(hda_nid_t, conn_len);
if (!conn)
return;
dev_id_saved = snd_hda_get_dev_select(codec, nid);
snd_hda_set_dev_select(codec, nid, dev_num);
curr = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONNECT_SEL, 0);
if (snd_hda_get_raw_connections(codec, nid, conn, conn_len) < 0)
goto out;
for (c = 0; c < conn_len; c++) {
snd_iprintf(buffer, " 0x%02x", conn[c]);
if (c == curr)
snd_iprintf(buffer, "*");
}
out:
kfree(conn);
snd_hda_set_dev_select(codec, nid, dev_id_saved);
}
static void print_device_list(struct snd_info_buffer *buffer,
struct hda_codec *codec, hda_nid_t nid)
{
int i, curr = -1;
u8 dev_list[AC_MAX_DEV_LIST_LEN];
unsigned int devlist_len;
devlist_len = snd_hda_get_devices(codec, nid, dev_list,
AC_MAX_DEV_LIST_LEN);
snd_iprintf(buffer, " Devices: %u\n", devlist_len);
if (devlist_len == 0)
return;
curr = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_DEVICE_SEL, 0);
for (i = 0; i < devlist_len; i++) {
if (i == curr)
snd_iprintf(buffer, " *");
else
snd_iprintf(buffer, " ");
snd_iprintf(buffer,
"Dev %02d: PD = %d, ELDV = %d, IA = %d, Connections [", i,
!!(dev_list[i] & AC_DE_PD),
!!(dev_list[i] & AC_DE_ELDV),
!!(dev_list[i] & AC_DE_IA));
print_dpmst_connections(buffer, codec, nid, i);
snd_iprintf(buffer, " ]\n");
}
}
static void print_codec_core_info(struct hdac_device *codec,
struct snd_info_buffer *buffer)
{
snd_iprintf(buffer, "Codec: ");
if (codec->vendor_name && codec->chip_name)
snd_iprintf(buffer, "%s %s\n",
codec->vendor_name, codec->chip_name);
else
snd_iprintf(buffer, "Not Set\n");
snd_iprintf(buffer, "Address: %d\n", codec->addr);
if (codec->afg)
snd_iprintf(buffer, "AFG Function Id: 0x%x (unsol %u)\n",
codec->afg_function_id, codec->afg_unsol);
if (codec->mfg)
snd_iprintf(buffer, "MFG Function Id: 0x%x (unsol %u)\n",
codec->mfg_function_id, codec->mfg_unsol);
snd_iprintf(buffer, "Vendor Id: 0x%08x\n", codec->vendor_id);
snd_iprintf(buffer, "Subsystem Id: 0x%08x\n", codec->subsystem_id);
snd_iprintf(buffer, "Revision Id: 0x%x\n", codec->revision_id);
if (codec->mfg)
snd_iprintf(buffer, "Modem Function Group: 0x%x\n", codec->mfg);
else
snd_iprintf(buffer, "No Modem Function Group found\n");
}
static void print_codec_info(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct hda_codec *codec = entry->private_data;
hda_nid_t nid, fg;
int i, nodes;
print_codec_core_info(&codec->core, buffer);
fg = codec->core.afg;
if (!fg)
return;
CLASS(snd_hda_power, pm)(codec);
snd_iprintf(buffer, "Default PCM:\n");
print_pcm_caps(buffer, codec, fg);
snd_iprintf(buffer, "Default Amp-In caps: ");
print_amp_caps(buffer, codec, fg, HDA_INPUT);
snd_iprintf(buffer, "Default Amp-Out caps: ");
print_amp_caps(buffer, codec, fg, HDA_OUTPUT);
snd_iprintf(buffer, "State of AFG node 0x%02x:\n", fg);
print_power_state(buffer, codec, fg);
nodes = snd_hda_get_sub_nodes(codec, fg, &nid);
if (! nid || nodes < 0) {
snd_iprintf(buffer, "Invalid AFG subtree\n");
return;
}
print_gpio(buffer, codec, fg);
if (codec->proc_widget_hook)
codec->proc_widget_hook(buffer, codec, fg);
for (i = 0; i < nodes; i++, nid++) {
unsigned int wid_caps =
param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP);
unsigned int wid_type = get_wcaps_type(wid_caps);
hda_nid_t *conn = NULL;
int conn_len = 0;
snd_iprintf(buffer, "Node 0x%02x [%s] wcaps 0x%x:", nid,
get_wid_type_name(wid_type), wid_caps);
if (wid_caps & AC_WCAP_STEREO) {
unsigned int chans = get_wcaps_channels(wid_caps);
if (chans == 2)
snd_iprintf(buffer, " Stereo");
else
snd_iprintf(buffer, " %d-Channels", chans);
} else
snd_iprintf(buffer, " Mono");
if (wid_caps & AC_WCAP_DIGITAL)
snd_iprintf(buffer, " Digital");
if (wid_caps & AC_WCAP_IN_AMP)
snd_iprintf(buffer, " Amp-In");
if (wid_caps & AC_WCAP_OUT_AMP)
snd_iprintf(buffer, " Amp-Out");
if (wid_caps & AC_WCAP_STRIPE)
snd_iprintf(buffer, " Stripe");
if (wid_caps & AC_WCAP_LR_SWAP)
snd_iprintf(buffer, " R/L");
if (wid_caps & AC_WCAP_CP_CAPS)
snd_iprintf(buffer, " CP");
snd_iprintf(buffer, "\n");
print_nid_array(buffer, codec, nid, &codec->mixers);
print_nid_array(buffer, codec, nid, &codec->nids);
print_nid_pcms(buffer, codec, nid);
/* volume knob is a special widget that always have connection
* list
*/
if (wid_type == AC_WID_VOL_KNB)
wid_caps |= AC_WCAP_CONN_LIST;
if (wid_caps & AC_WCAP_CONN_LIST) {
conn_len = snd_hda_get_num_raw_conns(codec, nid);
if (conn_len > 0) {
conn = kmalloc_objs(hda_nid_t, conn_len);
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);
}
}
/*
* create a proc read
*/
int snd_hda_codec_proc_new(struct hda_codec *codec)
{
char name[32];
snprintf(name, sizeof(name), "codec#%d", codec->core.addr);
return snd_card_ro_proc_new(codec->card, name, codec, print_codec_info);
}
+765
View File
@@ -0,0 +1,765 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* sysfs interface for HD-audio codec
*
* Copyright (c) 2014 Takashi Iwai <tiwai@suse.de>
*
* split from hda_hwdep.c
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/mutex.h>
#include <linux/ctype.h>
#include <linux/string.h>
#include <linux/export.h>
#include <sound/core.h>
#include <sound/hda_codec.h>
#include "hda_local.h"
#include <sound/hda_hwdep.h>
#include <sound/minors.h>
/* hint string pair */
struct hda_hint {
const char *key;
const char *val; /* contained in the same alloc as key */
};
static ssize_t power_on_acct_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hda_codec *codec = dev_get_drvdata(dev);
snd_hda_update_power_acct(codec);
return sysfs_emit(buf, "%u\n", jiffies_to_msecs(codec->power_on_acct));
}
static ssize_t power_off_acct_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hda_codec *codec = dev_get_drvdata(dev);
snd_hda_update_power_acct(codec);
return sysfs_emit(buf, "%u\n", jiffies_to_msecs(codec->power_off_acct));
}
static DEVICE_ATTR_RO(power_on_acct);
static DEVICE_ATTR_RO(power_off_acct);
#define CODEC_INFO_SHOW(type, field) \
static ssize_t type##_show(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
struct hda_codec *codec = dev_get_drvdata(dev); \
return sysfs_emit(buf, "0x%x\n", codec->field); \
}
#define CODEC_INFO_STR_SHOW(type, field) \
static ssize_t type##_show(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
struct hda_codec *codec = dev_get_drvdata(dev); \
return sysfs_emit(buf, "%s\n", \
codec->field ? codec->field : ""); \
}
CODEC_INFO_SHOW(vendor_id, core.vendor_id);
CODEC_INFO_SHOW(subsystem_id, core.subsystem_id);
CODEC_INFO_SHOW(revision_id, core.revision_id);
CODEC_INFO_SHOW(afg, core.afg);
CODEC_INFO_SHOW(mfg, core.mfg);
CODEC_INFO_STR_SHOW(vendor_name, core.vendor_name);
CODEC_INFO_STR_SHOW(chip_name, core.chip_name);
CODEC_INFO_STR_SHOW(modelname, modelname);
static ssize_t pin_configs_show(struct hda_codec *codec,
struct snd_array *list,
char *buf)
{
const struct hda_pincfg *pin;
int i, len = 0;
guard(mutex)(&codec->user_mutex);
snd_array_for_each(list, i, pin) {
len += sysfs_emit_at(buf, len, "0x%02x 0x%08x\n",
pin->nid, pin->cfg);
}
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;
CLASS(snd_hda_power, pm)(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");
return err;
}
err = device_reprobe(hda_codec_dev(codec));
if (err < 0)
return err;
return snd_card_register(codec->card);
}
/*
* allocate a string at most len chars, and remove the trailing EOL
*/
static char *kstrndup_noeol(const char *src, size_t len)
{
char *s = kstrndup(src, len, GFP_KERNEL);
char *p;
if (!s)
return NULL;
p = strchr(s, '\n');
if (p)
*p = 0;
return s;
}
#define CODEC_INFO_STORE(type, field) \
static ssize_t type##_store(struct device *dev, \
struct device_attribute *attr, \
const char *buf, size_t count) \
{ \
struct hda_codec *codec = dev_get_drvdata(dev); \
unsigned long val; \
int err = kstrtoul(buf, 0, &val); \
if (err < 0) \
return err; \
codec->field = val; \
return count; \
}
#define CODEC_INFO_STR_STORE(type, field) \
static ssize_t type##_store(struct device *dev, \
struct device_attribute *attr, \
const char *buf, size_t count) \
{ \
struct hda_codec *codec = dev_get_drvdata(dev); \
char *s = kstrndup_noeol(buf, 64); \
if (!s) \
return -ENOMEM; \
kfree(codec->field); \
codec->field = s; \
return count; \
}
CODEC_INFO_STORE(vendor_id, core.vendor_id);
CODEC_INFO_STORE(subsystem_id, core.subsystem_id);
CODEC_INFO_STORE(revision_id, core.revision_id);
CODEC_INFO_STR_STORE(vendor_name, core.vendor_name);
CODEC_INFO_STR_STORE(chip_name, core.chip_name);
CODEC_INFO_STR_STORE(modelname, modelname);
#define CODEC_ACTION_STORE(type) \
static ssize_t type##_store(struct device *dev, \
struct device_attribute *attr, \
const char *buf, size_t count) \
{ \
struct hda_codec *codec = dev_get_drvdata(dev); \
int err = 0; \
if (*buf) \
err = type##_codec(codec); \
return err < 0 ? err : count; \
}
CODEC_ACTION_STORE(reconfig);
CODEC_ACTION_STORE(clear);
static ssize_t init_verbs_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hda_codec *codec = dev_get_drvdata(dev);
const struct hda_verb *v;
int i, len = 0;
guard(mutex)(&codec->user_mutex);
snd_array_for_each(&codec->init_verbs, i, v) {
len += sysfs_emit_at(buf, len, "0x%02x 0x%03x 0x%04x\n",
v->nid, v->verb, v->param);
}
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, &param) != 3)
return -EINVAL;
if (!nid || !verb)
return -EINVAL;
guard(mutex)(&codec->user_mutex);
v = snd_array_new(&codec->init_verbs);
if (!v)
return -ENOMEM;
v->nid = nid;
v->verb = verb;
v->param = param;
return 0;
}
static ssize_t init_verbs_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct hda_codec *codec = dev_get_drvdata(dev);
int err = parse_init_verbs(codec, buf);
if (err < 0)
return err;
return count;
}
static ssize_t hints_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hda_codec *codec = dev_get_drvdata(dev);
const struct hda_hint *hint;
int i, len = 0;
guard(mutex)(&codec->user_mutex);
snd_array_for_each(&codec->hints, i, hint) {
len += sysfs_emit_at(buf, len, "%s = %s\n",
hint->key, hint->val);
}
return len;
}
static struct hda_hint *get_hint(struct hda_codec *codec, const char *key)
{
struct hda_hint *hint;
int i;
snd_array_for_each(&codec->hints, i, hint) {
if (!strcmp(hint->key, key))
return hint;
}
return NULL;
}
static void remove_trail_spaces(char *str)
{
char *p;
if (!*str)
return;
p = str + strlen(str) - 1;
for (; isspace(*p); p--) {
*p = 0;
if (p == str)
return;
}
}
#define MAX_HINTS 1024
static int parse_hints(struct hda_codec *codec, const char *buf)
{
char *val;
struct hda_hint *hint;
buf = skip_spaces(buf);
if (!*buf || *buf == '#' || *buf == '\n')
return 0;
if (*buf == '=')
return -EINVAL;
char *key __free(kfree) =
kstrndup_noeol(buf, 1024);
if (!key)
return -ENOMEM;
/* extract key and val */
val = strchr(key, '=');
if (!val)
return -EINVAL;
*val++ = 0;
val = skip_spaces(val);
remove_trail_spaces(key);
remove_trail_spaces(val);
guard(mutex)(&codec->user_mutex);
hint = get_hint(codec, key);
if (hint) {
/* replace */
kfree(hint->key);
goto replace;
}
/* allocate a new hint entry */
if (codec->hints.used >= MAX_HINTS)
return -ENOMEM;
hint = snd_array_new(&codec->hints);
if (!hint)
return -ENOMEM;
replace:
hint->key = no_free_ptr(key);
hint->val = val;
return 0;
}
static ssize_t hints_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct hda_codec *codec = dev_get_drvdata(dev);
int err = parse_hints(codec, buf);
if (err < 0)
return err;
return count;
}
static ssize_t user_pin_configs_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hda_codec *codec = dev_get_drvdata(dev);
return pin_configs_show(codec, &codec->user_pins, buf);
}
static int parse_user_pin_configs(struct hda_codec *codec, const char *buf)
{
int nid, cfg;
if (sscanf(buf, "%i %i", &nid, &cfg) != 2)
return -EINVAL;
if (!nid)
return -EINVAL;
guard(mutex)(&codec->user_mutex);
return snd_hda_add_pincfg(codec, &codec->user_pins, nid, cfg);
}
static ssize_t user_pin_configs_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct hda_codec *codec = dev_get_drvdata(dev);
int err = parse_user_pin_configs(codec, buf);
if (err < 0)
return err;
return count;
}
/* sysfs attributes exposed only when CONFIG_SND_HDA_RECONFIG=y */
static DEVICE_ATTR_RW(init_verbs);
static DEVICE_ATTR_RW(hints);
static DEVICE_ATTR_RW(user_pin_configs);
static DEVICE_ATTR_WO(reconfig);
static DEVICE_ATTR_WO(clear);
/**
* snd_hda_get_hint - Look for hint string
* @codec: the HDA codec
* @key: the hint key string
*
* Look for a hint key/value pair matching with the given key string
* and returns the value string. If nothing found, returns NULL.
*/
const char *snd_hda_get_hint(struct hda_codec *codec, const char *key)
{
struct hda_hint *hint = get_hint(codec, key);
return hint ? hint->val : NULL;
}
EXPORT_SYMBOL_GPL(snd_hda_get_hint);
/**
* snd_hda_get_bool_hint - Get a boolean hint value
* @codec: the HDA codec
* @key: the hint key string
*
* Look for a hint key/value pair matching with the given key string
* and returns a boolean value parsed from the value. If no matching
* key is found, return a negative value.
*/
int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key)
{
const char *p;
guard(mutex)(&codec->user_mutex);
p = snd_hda_get_hint(codec, key);
if (!p || !*p)
return -ENOENT;
switch (toupper(*p)) {
case 'T': /* true */
case 'Y': /* yes */
case '1':
return 1;
default:
return 0;
}
}
EXPORT_SYMBOL_GPL(snd_hda_get_bool_hint);
/**
* snd_hda_get_int_hint - Get an integer hint value
* @codec: the HDA codec
* @key: the hint key string
* @valp: pointer to store a value
*
* Look for a hint key/value pair matching with the given key string
* and stores the integer value to @valp. If no matching key is found,
* return a negative error code. Otherwise it returns zero.
*/
int snd_hda_get_int_hint(struct hda_codec *codec, const char *key, int *valp)
{
const char *p;
unsigned long val;
guard(mutex)(&codec->user_mutex);
p = snd_hda_get_hint(codec, key);
if (!p)
return -ENOENT;
else if (kstrtoul(p, 0, &val))
return -EINVAL;
else {
*valp = val;
return 0;
}
}
EXPORT_SYMBOL_GPL(snd_hda_get_int_hint);
#endif /* CONFIG_SND_HDA_RECONFIG */
/*
* common sysfs attributes
*/
#ifdef CONFIG_SND_HDA_RECONFIG
#define RECONFIG_DEVICE_ATTR(name) DEVICE_ATTR_RW(name)
#else
#define RECONFIG_DEVICE_ATTR(name) DEVICE_ATTR_RO(name)
#endif
static RECONFIG_DEVICE_ATTR(vendor_id);
static RECONFIG_DEVICE_ATTR(subsystem_id);
static RECONFIG_DEVICE_ATTR(revision_id);
static DEVICE_ATTR_RO(afg);
static DEVICE_ATTR_RO(mfg);
static RECONFIG_DEVICE_ATTR(vendor_name);
static RECONFIG_DEVICE_ATTR(chip_name);
static RECONFIG_DEVICE_ATTR(modelname);
static DEVICE_ATTR_RO(init_pin_configs);
static DEVICE_ATTR_RO(driver_pin_configs);
#ifdef CONFIG_SND_HDA_PATCH_LOADER
/* parser mode */
enum {
LINE_MODE_NONE,
LINE_MODE_CODEC,
LINE_MODE_MODEL,
LINE_MODE_PINCFG,
LINE_MODE_VERB,
LINE_MODE_HINT,
LINE_MODE_VENDOR_ID,
LINE_MODE_SUBSYSTEM_ID,
LINE_MODE_REVISION_ID,
LINE_MODE_CHIP_NAME,
NUM_LINE_MODES,
};
static inline int strmatch(const char *a, const char *b)
{
return strncasecmp(a, b, strlen(b)) == 0;
}
/* parse the contents after the line "[codec]"
* accept only the line with three numbers, and assign the current codec
*/
static void parse_codec_mode(char *buf, struct hda_bus *bus,
struct hda_codec **codecp)
{
int vendorid, subid, caddr;
struct hda_codec *codec;
*codecp = NULL;
if (sscanf(buf, "%i %i %i", &vendorid, &subid, &caddr) == 3) {
list_for_each_codec(codec, bus) {
if ((vendorid <= 0 || codec->core.vendor_id == vendorid) &&
(subid <= 0 || codec->core.subsystem_id == subid) &&
codec->core.addr == caddr) {
*codecp = codec;
break;
}
}
}
}
/* parse the contents after the other command tags, [pincfg], [verb],
* [vendor_id], [subsystem_id], [revision_id], [chip_name], [hint] and [model]
* just pass to the sysfs helper (only when any codec was specified)
*/
static void parse_pincfg_mode(char *buf, struct hda_bus *bus,
struct hda_codec **codecp)
{
parse_user_pin_configs(*codecp, buf);
}
static void parse_verb_mode(char *buf, struct hda_bus *bus,
struct hda_codec **codecp)
{
parse_init_verbs(*codecp, buf);
}
static void parse_hint_mode(char *buf, struct hda_bus *bus,
struct hda_codec **codecp)
{
parse_hints(*codecp, buf);
}
static void parse_model_mode(char *buf, struct hda_bus *bus,
struct hda_codec **codecp)
{
kfree((*codecp)->modelname);
(*codecp)->modelname = kstrdup(buf, GFP_KERNEL);
}
static void parse_chip_name_mode(char *buf, struct hda_bus *bus,
struct hda_codec **codecp)
{
snd_hda_codec_set_name(*codecp, buf);
}
#define DEFINE_PARSE_ID_MODE(name) \
static void parse_##name##_mode(char *buf, struct hda_bus *bus, \
struct hda_codec **codecp) \
{ \
unsigned long val; \
if (!kstrtoul(buf, 0, &val)) \
(*codecp)->core.name = val; \
}
DEFINE_PARSE_ID_MODE(vendor_id);
DEFINE_PARSE_ID_MODE(subsystem_id);
DEFINE_PARSE_ID_MODE(revision_id);
struct hda_patch_item {
const char *tag;
const char *alias;
void (*parser)(char *buf, struct hda_bus *bus, struct hda_codec **retc);
};
static const struct hda_patch_item patch_items[NUM_LINE_MODES] = {
[LINE_MODE_CODEC] = {
.tag = "[codec]",
.parser = parse_codec_mode,
},
[LINE_MODE_MODEL] = {
.tag = "[model]",
.parser = parse_model_mode,
},
[LINE_MODE_VERB] = {
.tag = "[verb]",
.alias = "[init_verbs]",
.parser = parse_verb_mode,
},
[LINE_MODE_PINCFG] = {
.tag = "[pincfg]",
.alias = "[user_pin_configs]",
.parser = parse_pincfg_mode,
},
[LINE_MODE_HINT] = {
.tag = "[hint]",
.alias = "[hints]",
.parser = parse_hint_mode
},
[LINE_MODE_VENDOR_ID] = {
.tag = "[vendor_id]",
.parser = parse_vendor_id_mode,
},
[LINE_MODE_SUBSYSTEM_ID] = {
.tag = "[subsystem_id]",
.parser = parse_subsystem_id_mode,
},
[LINE_MODE_REVISION_ID] = {
.tag = "[revision_id]",
.parser = parse_revision_id_mode,
},
[LINE_MODE_CHIP_NAME] = {
.tag = "[chip_name]",
.parser = parse_chip_name_mode,
},
};
/* check the line starting with '[' -- change the parser mode accordingly */
static int parse_line_mode(char *buf, struct hda_bus *bus)
{
int i;
for (i = 0; i < ARRAY_SIZE(patch_items); i++) {
if (!patch_items[i].tag)
continue;
if (strmatch(buf, patch_items[i].tag))
return i;
if (patch_items[i].alias && strmatch(buf, patch_items[i].alias))
return i;
}
return LINE_MODE_NONE;
}
/* copy one line from the buffer in fw, and update the fields in fw
* return zero if it reaches to the end of the buffer, or non-zero
* if successfully copied a line
*
* the spaces at the beginning and the end of the line are stripped
*/
static int get_line_from_fw(char *buf, int size, size_t *fw_size_p,
const void **fw_data_p)
{
int len;
size_t fw_size = *fw_size_p;
const char *p = *fw_data_p;
while (isspace(*p) && fw_size) {
p++;
fw_size--;
}
if (!fw_size)
return 0;
for (len = 0; len < fw_size; len++) {
if (!*p)
break;
if (*p == '\n') {
p++;
len++;
break;
}
if (len < size)
*buf++ = *p++;
}
*buf = 0;
*fw_size_p = fw_size - len;
*fw_data_p = p;
remove_trail_spaces(buf);
return 1;
}
/**
* snd_hda_load_patch - load a "patch" firmware file and parse it
* @bus: HD-audio bus
* @fw_size: the firmware byte size
* @fw_buf: the firmware data
*/
int snd_hda_load_patch(struct hda_bus *bus, size_t fw_size, const void *fw_buf)
{
char buf[128];
struct hda_codec *codec;
int line_mode;
line_mode = LINE_MODE_NONE;
codec = NULL;
while (get_line_from_fw(buf, sizeof(buf) - 1, &fw_size, &fw_buf)) {
if (!*buf || *buf == '#' || *buf == '\n')
continue;
if (*buf == '[')
line_mode = parse_line_mode(buf, bus);
else if (patch_items[line_mode].parser &&
(codec || line_mode <= LINE_MODE_CODEC))
patch_items[line_mode].parser(buf, bus, &codec);
}
return 0;
}
EXPORT_SYMBOL_GPL(snd_hda_load_patch);
#endif /* CONFIG_SND_HDA_PATCH_LOADER */
/*
* sysfs entries
*/
static struct attribute *hda_dev_attrs[] = {
&dev_attr_vendor_id.attr,
&dev_attr_subsystem_id.attr,
&dev_attr_revision_id.attr,
&dev_attr_afg.attr,
&dev_attr_mfg.attr,
&dev_attr_vendor_name.attr,
&dev_attr_chip_name.attr,
&dev_attr_modelname.attr,
&dev_attr_init_pin_configs.attr,
&dev_attr_driver_pin_configs.attr,
&dev_attr_power_on_acct.attr,
&dev_attr_power_off_acct.attr,
#ifdef CONFIG_SND_HDA_RECONFIG
&dev_attr_init_verbs.attr,
&dev_attr_hints.attr,
&dev_attr_user_pin_configs.attr,
&dev_attr_reconfig.attr,
&dev_attr_clear.attr,
#endif
NULL
};
static const struct attribute_group hda_dev_attr_group = {
.attrs = hda_dev_attrs,
};
const struct attribute_group *snd_hda_dev_attr_groups[] = {
&hda_dev_attr_group,
NULL
};
void snd_hda_sysfs_init(struct hda_codec *codec)
{
mutex_init(&codec->user_mutex);
#ifdef CONFIG_SND_HDA_RECONFIG
snd_array_init(&codec->init_verbs, sizeof(struct hda_verb), 32);
snd_array_init(&codec->hints, sizeof(struct hda_hint), 32);
snd_array_init(&codec->user_pins, sizeof(struct hda_pincfg), 16);
#endif
}
void snd_hda_sysfs_clear(struct hda_codec *codec)
{
#ifdef CONFIG_SND_HDA_RECONFIG
struct hda_hint *hint;
int i;
/* clear init verbs */
snd_array_free(&codec->init_verbs);
/* clear hints */
snd_array_for_each(&codec->hints, i, hint) {
kfree(hint->key); /* we don't need to free hint->val */
}
snd_array_free(&codec->hints);
snd_array_free(&codec->user_pins);
#endif
}