blob: 6dd862db267077af257a448f7d57684911d3ec5f [file] [log] [blame]
/*
* hda-emu - simple HD-audio codec emulator for debugging snd-hda-intel driver
*
* Emulate the communication of HD-audio codec
*
* Copyright (c) Takashi Iwai <tiwai@suse.de>
*
* This driver is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This driver is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include "hda-types.h"
#include "hda-log.h"
#define AC_AMP_GET_LEFT (1<<13)
#define AC_AMP_GET_RIGHT (0<<13)
#define AC_AMP_GET_OUTPUT (1<<15)
#define AC_AMP_GET_INPUT (0<<15)
#define AC_AMP_SET_INDEX (0xf<<8)
#define AC_AMP_SET_INDEX_SHIFT 8
#define AC_AMP_SET_RIGHT (1<<12)
#define AC_AMP_SET_LEFT (1<<13)
#define AC_AMP_SET_INPUT (1<<14)
#define AC_AMP_SET_OUTPUT (1<<15)
/* Audio Widget Capabilities */
#define AC_WCAP_STEREO (1<<0) /* stereo I/O */
#define AC_WCAP_IN_AMP (1<<1) /* AMP-in present */
#define AC_WCAP_OUT_AMP (1<<2) /* AMP-out present */
#define AC_WCAP_AMP_OVRD (1<<3) /* AMP-parameter override */
#define AC_WCAP_FORMAT_OVRD (1<<4) /* format override */
#define AC_WCAP_STRIPE (1<<5) /* stripe */
#define AC_WCAP_PROC_WID (1<<6) /* Proc Widget */
#define AC_WCAP_UNSOL_CAP (1<<7) /* Unsol capable */
#define AC_WCAP_CONN_LIST (1<<8) /* connection list */
#define AC_WCAP_DIGITAL (1<<9) /* digital I/O */
#define AC_WCAP_POWER (1<<10) /* power control */
#define AC_WCAP_LR_SWAP (1<<11) /* L/R swap */
#define AC_WCAP_DELAY (0xf<<16)
#define AC_WCAP_DELAY_SHIFT 16
#define AC_WCAP_TYPE (0xf<<20)
#define AC_WCAP_TYPE_SHIFT 20
/* Pin widget capabilies */
#define AC_PINCAP_IMP_SENSE (1<<0) /* impedance sense capable */
#define AC_PINCAP_TRIG_REQ (1<<1) /* trigger required */
#define AC_PINCAP_PRES_DETECT (1<<2) /* presence detect capable */
#define AC_PINCAP_HP_DRV (1<<3) /* headphone drive capable */
#define AC_PINCAP_OUT (1<<4) /* output capable */
#define AC_PINCAP_IN (1<<5) /* input capable */
#define AC_PINCAP_BALANCE (1<<6) /* balanced I/O capable */
/* Note: This LR_SWAP pincap is defined in the Realtek ALC883 specification,
* but is marked reserved in the Intel HDA specification.
*/
#define AC_PINCAP_LR_SWAP (1<<7) /* L/R swap */
/* Note: The same bit as LR_SWAP is newly defined as HDMI capability
* in HD-audio specification
*/
#define AC_PINCAP_HDMI (1<<7) /* HDMI pin */
#define AC_PINCAP_VREF (0x37<<8)
#define AC_PINCAP_VREF_SHIFT 8
#define AC_PINCAP_EAPD (1<<16) /* EAPD capable */
/* Vref status (used in pin cap) */
#define AC_PINCAP_VREF_HIZ (1<<0) /* Hi-Z */
#define AC_PINCAP_VREF_50 (1<<1) /* 50% */
#define AC_PINCAP_VREF_GRD (1<<2) /* ground */
#define AC_PINCAP_VREF_80 (1<<4) /* 80% */
#define AC_PINCAP_VREF_100 (1<<5) /* 100% */
/* Pin widget control - 8bit */
#define AC_PINCTL_EPT (0x3<<0)
#define AC_PINCTL_EPT_NATIVE 0
#define AC_PINCTL_EPT_HBR 3
#define AC_PINCTL_VREFEN (0x7<<0)
#define AC_PINCTL_VREF_HIZ 0 /* Hi-Z */
#define AC_PINCTL_VREF_50 1 /* 50% */
#define AC_PINCTL_VREF_GRD 2 /* ground */
#define AC_PINCTL_VREF_80 4 /* 80% */
#define AC_PINCTL_VREF_100 5 /* 100% */
#define AC_PINCTL_IN_EN (1<<5)
#define AC_PINCTL_OUT_EN (1<<6)
#define AC_PINCTL_HP_EN (1<<7)
/*
* widget types
*/
enum {
AC_WID_AUD_OUT, /* Audio Out */
AC_WID_AUD_IN, /* Audio In */
AC_WID_AUD_MIX, /* Audio Mixer */
AC_WID_AUD_SEL, /* Audio Selector */
AC_WID_PIN, /* Pin Complex */
AC_WID_POWER, /* Power */
AC_WID_VOL_KNB, /* Volume Knob */
AC_WID_BEEP, /* Beep Generator */
AC_WID_VENDOR = 0x0f /* Vendor specific */
};
/*
*/
static const struct xhda_verb_table *
find_verb(unsigned int verb, const struct xhda_verb_table *tbl)
{
for (; tbl->verb || tbl->func; tbl++)
if (tbl->verb == verb)
return tbl;
return NULL;
}
/*
*/
static int set_stream_format(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node)
return 0;
node->stream_format = cmd & 0xffff;
return 0;
}
static int get_stream_format(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node)
return 0;
return node->stream_format;
}
static int set_amp(struct xhda_node *node,
struct xhda_amp_vals *vals, unsigned int idx,
unsigned int ampcap, unsigned int val)
{
unsigned int nsteps = (ampcap >> 8) & 0xff;
unsigned int has_mute = (ampcap >> 31) & 1;
unsigned int ampval;
ampval = val & 0xff;
if ((ampval & ~0x80) > nsteps) {
hda_log(HDA_LOG_ERR, "invalid amp value 0x%x (nsteps = 0x%x), NID=0x%x\n",
ampval, nsteps, node->nid);
ampval = (ampval & 0x80) | (nsteps - 1);
}
if ((ampval & 0x80) && !has_mute) {
hda_log(HDA_LOG_ERR, "turn on non-existing mute, NODE=0x%x\n",
node->nid);
ampval &= ~0x80;
}
if (val & AC_AMP_SET_LEFT)
vals->vals[idx][0] = ampval;
if (val & AC_AMP_SET_RIGHT)
vals->vals[idx][1] = ampval;
return 0;
}
static int par_amp_in_cap(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd);
static int par_amp_out_cap(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd);
static int set_amp_gain_mute(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
unsigned int ampval;
unsigned int idx, type;
if (!node)
return 0;
ampval = cmd & 0xffff;
idx = (ampval & AC_AMP_SET_INDEX) >> AC_AMP_SET_INDEX_SHIFT;
type = (node->wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
if (ampval & AC_AMP_SET_OUTPUT) {
if (!(node->wcaps & AC_WCAP_OUT_AMP))
hda_log(HDA_LOG_ERR, "no output-amp for node 0x%x\n",
node->nid);
if (idx) {
if (type != AC_WID_PIN ||
!codec->pin_amp_workaround) {
hda_log(HDA_LOG_ERR,
"invalid amp index %d for output\n",
idx);
idx = 0;
} else if (idx >= node->num_nodes) {
hda_log(HDA_LOG_ERR,
"invalid pin out-amp index %d "
"(conns=%d)\n",
idx, node->num_nodes);
idx = 0;
}
}
set_amp(node, &node->amp_out_vals, idx,
par_amp_out_cap(codec, node, 0),
ampval);
}
if (ampval & AC_AMP_SET_INPUT) {
if (!(node->wcaps & AC_WCAP_IN_AMP))
hda_log(HDA_LOG_ERR, "no input-amp for node 0x%x\n",
node->nid);
type = (node->wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
if ((type == AC_WID_PIN && idx != 0) ||
(type != AC_WID_PIN && idx >= node->num_nodes)) {
hda_log(HDA_LOG_ERR,
"invalid amp index %d (conns=%d)\n", idx,
node->num_nodes);
idx = 0;
}
set_amp(node, &node->amp_in_vals, idx,
par_amp_in_cap(codec, node, 0),
ampval);
}
return 0;
}
static int get_amp(struct xhda_amp_vals *vals, unsigned int idx,
unsigned int ampval)
{
if (ampval & AC_AMP_GET_LEFT)
return vals->vals[idx][0];
else
return vals->vals[idx][1];
}
static int get_amp_gain_mute(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
unsigned int ampval;
unsigned int idx, type;
if (!node)
return 0;
ampval = cmd & 0xffff;
idx = ampval & 0x1f;
type = (node->wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
if (ampval & AC_AMP_GET_OUTPUT) {
if (!(node->wcaps & AC_WCAP_OUT_AMP))
hda_log(HDA_LOG_ERR, "no output-amp for node 0x%x\n",
node->nid);
if (idx) {
if (type != AC_WID_PIN ||
!codec->pin_amp_workaround) {
hda_log(HDA_LOG_ERR,
"invalid amp index %d for output\n",
idx);
idx = 0;
} else if (idx >= node->num_nodes) {
hda_log(HDA_LOG_ERR,
"invalid pin out-amp index %d "
"(conns=%d)\n",
idx, node->num_nodes);
idx = 0;
}
}
return get_amp(&node->amp_out_vals, idx, ampval);
} else {
if (!(node->wcaps & AC_WCAP_IN_AMP))
hda_log(HDA_LOG_ERR, "no input-amp for node 0x%x\n",
node->nid);
if ((type == AC_WID_PIN && idx != 0) ||
(type != AC_WID_PIN && idx >= node->num_nodes)) {
hda_log(HDA_LOG_ERR,
"invalid amp index %d (conns=%d)\n", idx,
node->num_nodes);
idx = 0;
}
return get_amp(&node->amp_in_vals, idx, ampval);
}
}
static int set_connect_sel(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
unsigned int wid_type;
unsigned int sel;
if (!node)
return 0;
wid_type = (node->wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
if (wid_type == AC_WID_AUD_MIX || wid_type == AC_WID_VOL_KNB) {
hda_log(HDA_LOG_ERR, "invalid connect select for node 0x%x\n",
node->nid);
return 0;
}
sel = cmd & 0xff;
if (sel >= node->num_nodes)
hda_log(HDA_LOG_ERR,
"invalid connection index %d (conns=%d), NID=0x%x\n",
sel, node->num_nodes, node->nid);
else
node->curr_conn = sel;
return 0;
}
static int get_connect_sel(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node)
return 0;
return node->curr_conn;
}
static int get_connect_list(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
unsigned int idx, i;
unsigned int val;
if (!node)
return 0;
idx = cmd & 0xff;
if (idx >= node->num_nodes)
return 0;
val = 0;
for (i = 0; i < 4; i++, idx++) {
if (idx >= node->num_nodes)
break;
val |= node->node[idx] << (i * 8);
}
return val;
}
static int set_proc_state(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
node->proc_state = cmd & 0xff;
return 0;
}
static int get_proc_state(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
return node->proc_state;
}
static int set_power_state(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node)
return 0;
if (node->nid != 0x01 &&
!(node->wcaps & AC_WCAP_POWER))
return 0;
node->power_setting = cmd & 0x0f;
node->power_current = node->power_setting;
return 0;
}
static int get_power_state(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node)
return 0;
return node->power_setting | (node->power_current << 4);
}
static int set_sdi_select(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
node->sdi = cmd & 0x0f;
return 0;
}
static int get_sdi_select(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
return node->sdi;
}
static int set_channel_streamid(struct xhda_codec *codec,
struct xhda_node *node, unsigned int cmd)
{
if (!node)
return 0;
node->streamid = cmd & 0xff;
return 0;
}
static int get_channel_streamid(struct xhda_codec *codec,
struct xhda_node *node, unsigned int cmd)
{
if (!node)
return 0;
return node->streamid;
}
void hda_verify_pin_ctl(struct xhda_node *node, int log,
unsigned int *sanified_pinctl)
{
int pinctl = node->pinctl;
/* sanity checks */
if ((node->pinctl & AC_PINCTL_OUT_EN) &&
!(node->pincap & AC_PINCAP_OUT)) {
if (log)
hda_log(HDA_LOG_ERR,
"setting OUT_EN to pin 0x%x without caps\n",
node->nid);
pinctl &= ~AC_PINCTL_OUT_EN;
}
if ((node->pinctl & AC_PINCTL_HP_EN) &&
!(node->pincap & AC_PINCAP_HP_DRV)) {
if (log)
hda_log(HDA_LOG_ERR,
"setting HP_EN to pin 0x%x without caps\n",
node->nid);
pinctl &= ~AC_PINCTL_HP_EN;
}
if ((node->pinctl & AC_PINCTL_IN_EN) &&
!(node->pincap & AC_PINCAP_IN)) {
if (log)
hda_log(HDA_LOG_ERR,
"setting IN_EN to pin 0x%x without caps\n",
node->nid);
pinctl &= ~AC_PINCTL_IN_EN;
}
if (node->pinctl & AC_PINCTL_IN_EN) {
unsigned int cap_set, cap_check;
const char *vref;
switch (node->pinctl & AC_PINCTL_VREFEN) {
case AC_PINCTL_VREF_HIZ:
cap_check = AC_PINCAP_VREF_HIZ;
vref = "HIZ";
break;
case AC_PINCTL_VREF_50:
cap_check = AC_PINCAP_VREF_50;
vref = "50";
break;
case AC_PINCTL_VREF_GRD:
cap_check = AC_PINCAP_VREF_GRD;
vref = "GRD";
break;
case AC_PINCTL_VREF_80:
cap_check = AC_PINCAP_VREF_80;
vref = "80";
break;
case AC_PINCTL_VREF_100:
cap_check = AC_PINCAP_VREF_100;
vref = "100";
break;
default:
cap_check = 0;
break;
}
cap_set = (node->pincap & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT;
if (!cap_set)
cap_set = AC_PINCAP_VREF_HIZ;
if (cap_check && !(cap_set & cap_check)) {
if (log)
hda_log(HDA_LOG_ERR,
"setting VREF %s to pin 0x%x without caps 0x%x\n",
vref, node->nid, node->pincap);
pinctl &= ~AC_PINCTL_VREFEN;
}
}
if (sanified_pinctl)
*sanified_pinctl = pinctl;
}
static int set_pin_ctl(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node)
return 0;
node->pinctl = cmd & 0xff;
hda_verify_pin_ctl(node, 1, NULL);
return 0;
}
static int get_pin_ctl(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node)
return 0;
return node->pinctl;
}
static int set_unsol_enable(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node)
return 0;
node->unsol = cmd & 0xff;
return 0;
}
static int get_unsol_resp(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node) {
hda_log(HDA_LOG_ERR, "Invalid unsol widget");
return 0;
}
return node->unsol;
}
static int exec_pin_sense(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
return 0;
}
static int get_pin_sense(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node) {
hda_log(HDA_LOG_ERR, "Invalid pin node\n");
return 0;
}
return node->jack_state ? (1 << 31) : 0;
}
static int set_eapd_btl(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node) {
hda_log(HDA_LOG_ERR, "Invalid EAPD node\n");
return 0;
}
node->eapd = cmd & 0xff;
if ((cmd & 0x02) && !(node->pincap & AC_PINCAP_EAPD))
hda_log(HDA_LOG_ERR, "EAPD command to non-capable pin 0x%x\n",
node->nid);
return 0;
}
static int get_eapd_btl(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node) {
hda_log(HDA_LOG_ERR, "Invalid EAPD node\n");
return 0;
}
return node->eapd;
}
static int set_digi_cvt_1(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node) {
hda_log(HDA_LOG_ERR, "Invalid DIGI1 node\n");
return 0;
}
node->dig_conv = cmd & 0xff;
return 0;
}
static int set_digi_cvt_2(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node) {
hda_log(HDA_LOG_ERR, "Invalid DIGI2 node\n");
return 0;
}
node->dig_category = cmd & 0xff;
return 0;
}
static int get_digi_cvt(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node) {
hda_log(HDA_LOG_ERR, "Invalid DIGI node\n");
return 0;
}
return (node->dig_category << 8) | node->dig_conv;
}
static int set_config_def_0(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node)
return 0;
node->pin_default &= ~0xff;
node->pin_default |= (cmd & 0xff);
return 0;
}
static int set_config_def_1(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node)
return 0;
node->pin_default &= ~0xff00;
node->pin_default |= (cmd & 0xff) << 8;
return 0;
}
static int set_config_def_2(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node)
return 0;
node->pin_default &= ~0xff0000;
node->pin_default |= (cmd & 0xff) << 16;
return 0;
}
static int set_config_def_3(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node)
return 0;
node->pin_default &= ~0xff000000;
node->pin_default |= (cmd & 0xff) << 24;
return 0;
}
static int get_config_default(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node)
return 0;
return node->pin_default;
}
static int get_ssid(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
return codec->subsystem_id;
}
static int set_codec_reset(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
hda_log(HDA_LOG_INFO, "CODEC RESET\n");
return 0;
}
static int set_coef_index(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
node->coef_idx = cmd & 0xffff;
return 0;
}
static int get_coef_index(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
return node->coef_idx;
}
static struct xhda_coef_table *find_proc_coef(struct xhda_node *node,
unsigned int idx)
{
struct xhda_coef_table *tbl;
for (tbl = node->coef_tbl; tbl; tbl = tbl->next) {
if (tbl->idx == idx)
return tbl;
}
return NULL;
}
static struct xhda_coef_table *create_proc_coef(struct xhda_node *node,
unsigned int idx)
{
struct xhda_coef_table *tbl;
tbl = xalloc(sizeof(*tbl));
tbl->idx = idx;
tbl->next = node->coef_tbl;
node->coef_tbl = tbl;
return tbl;
}
static int set_proc_coef(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
struct xhda_coef_table *tbl;
tbl = find_proc_coef(node, node->coef_idx);
if (!tbl) {
tbl = create_proc_coef(node, node->coef_idx);
if (!tbl)
return 0;
}
tbl->value = cmd & 0xffff;
node->coef_idx++;
return 0;
}
static int get_proc_coef(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
struct xhda_coef_table *tbl;
tbl = find_proc_coef(node, node->coef_idx);
node->coef_idx++;
return tbl ? tbl->value : 0;
}
static int set_beep_ctl(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
node->beep_div = cmd & 0xff;
return 0;
}
static int get_beep_ctl(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
return node->beep_div;
}
static int set_volknob_ctl(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
node->volknob_ctl = cmd & 0xff;
return 0;
}
static int get_volknob_ctl(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
return node->volknob_ctl;
}
static int set_gpio_data(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
node->gpio_data = cmd & 0xff;
return 0;
}
static int get_gpio_data(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
return node->gpio_data;
}
static int set_gpio_mask(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
node->gpio_mask = cmd & 0xff;
return 0;
}
static int get_gpio_mask(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
return node->gpio_mask;
}
static int set_gpio_dir(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
node->gpio_dir = cmd & 0xff;
return 0;
}
static int get_gpio_dir(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
return node->gpio_dir;
}
static int set_gpio_wake_mask(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
node->gpio_wake = cmd & 0xff;
return 0;
}
static int get_gpio_wake_mask(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
return node->gpio_wake;
}
static int set_gpio_unsol_rsp(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
node->gpio_unsol = cmd & 0xff;
return 0;
}
static int get_gpio_unsol_rsp(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
return node->gpio_unsol;
}
static int set_gpio_sticky_mask(struct xhda_codec *codec,
struct xhda_node *node, unsigned int cmd)
{
node->gpio_sticky = cmd & 0xff;
return 0;
}
static int get_gpio_sticky_mask(struct xhda_codec *codec,
struct xhda_node *node, unsigned int cmd)
{
return node->gpio_sticky;
}
static int set_cvt_channel_count(struct xhda_codec *codec,
struct xhda_node *node, unsigned int cmd)
{
node->cvt_channel_count = cmd & 0xff;
return 0;
}
static int get_cvt_channel_count(struct xhda_codec *codec,
struct xhda_node *node, unsigned int cmd)
{
return node->cvt_channel_count;
}
static int get_asp_channel_slot(struct xhda_codec *codec,
struct xhda_node *node, unsigned int cmd)
{
return (node->asp_channel_slot[cmd & 0xf] << 4) + (cmd & 0xf);
}
static int set_asp_channel_slot(struct xhda_codec *codec,
struct xhda_node *node, unsigned int cmd)
{
node->asp_channel_slot[cmd & 0xf] = (cmd & 0xf0) >> 4;
return 0;
}
static int set_dip_index(struct xhda_codec *codec,
struct xhda_node *node, unsigned int cmd)
{
return 0; /* FIXME */
}
static int set_dip_data(struct xhda_codec *codec,
struct xhda_node *node, unsigned int cmd)
{
return 0; /* FIXME */
}
static int get_dip_size(struct xhda_codec *codec,
struct xhda_node *node, unsigned int cmd)
{
if (cmd & 0x8)
return 128; /* ELD buffer size */
return 32; /* DIP buffer size */
}
static int set_dip_xmitctrl(struct xhda_codec *codec,
struct xhda_node *node, unsigned int cmd)
{
node->dip_xmitctrl = cmd & 0xc0;
return 0;
}
static int get_dip_xmitctrl(struct xhda_codec *codec,
struct xhda_node *node, unsigned int cmd)
{
return node->dip_xmitctrl;
}
/*
* parameters
*/
static int par_vendor_id(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
return codec->vendor_id;
}
static int par_subsystem_id(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
return codec->subsystem_id;
}
static int par_revision_id(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
return codec->revision_id;
}
static int par_node_count(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
unsigned int start_nid;
if (!node)
return codec->num_widgets | (0x01 << 16);
if (!codec->afg.next)
return 0;
start_nid = codec->afg.next->nid;
return (codec->num_widgets - start_nid + 1) | (start_nid << 16);
}
static int par_function_type(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (node) {
if (!codec->function_id && node->nid == 0x01)
return 0x01; /* FIXME */
if (node->nid == codec->afg.nid)
return codec->function_id ? codec->function_id : 0x01;
if (node->nid == codec->mfg.nid)
return codec->modem_function_id ? codec->modem_function_id : 0x02;
}
return 0;
}
static int par_fg_cap(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
return 0; /* FIXME */
}
static int par_audio_widget_cap(struct xhda_codec *codec,
struct xhda_node *node, unsigned int cmd)
{
if (!node)
return 0;
return node->wcaps;
}
static int par_pcm(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node)
return 0;
return node->pcm.rates | (node->pcm.bits << 16);
}
static int par_stream(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node)
return 0;
return node->pcm.formats;
}
static int par_pin_cap(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node)
return 0;
return node->pincap;
}
static int par_amp_cap(struct xhda_amp_caps *node_cap,
struct xhda_amp_caps *afg_cap)
{
if (node_cap->override)
return node_cap->ofs |
(node_cap->nsteps << 8) |
(node_cap->stepsize << 16) |
(node_cap->mute << 31);
else
return afg_cap->ofs |
(afg_cap->nsteps << 8) |
(afg_cap->stepsize << 16) |
(afg_cap->mute << 31);
}
static int par_amp_in_cap(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node)
return 0;
return par_amp_cap(&node->amp_in_caps, &codec->afg.amp_in_caps);
}
static int par_connlist_len(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node)
return 0;
return node->num_nodes;
}
static int par_power_state(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node)
return 0;
if (node->nid == 0x01)
return 0x0f;
if (node->wcaps & AC_WCAP_POWER)
return 0x0f;
return 0; /* FIXME */
}
static int par_proc_cap(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node)
return 0;
return node->coef_benign | (node->coef_num << 8);
}
static int par_gpio_cap(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
return node->gpio_cap;
}
static int par_amp_out_cap(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
if (!node)
return 0;
return par_amp_cap(&node->amp_out_caps, &codec->afg.amp_out_caps);
}
static int par_vol_knb_cap(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
return 0; /* FIXME */
}
static struct xhda_verb_table par_tbl[] = {
{ 0x00, par_vendor_id, "vendor id" },
{ 0x01, par_subsystem_id, "subsystem_id" },
{ 0x02, par_revision_id, "revision_id" },
{ 0x04, par_node_count, "node_count" },
{ 0x05, par_function_type, "function_type" },
{ 0x08, par_fg_cap, "FG_cap" },
{ 0x09, par_audio_widget_cap, "audio_wid_cap" },
{ 0x0a, par_pcm, "PCM" },
{ 0x0b, par_stream, "stream" },
{ 0x0c, par_pin_cap, "pin_cap" },
{ 0x0d, par_amp_in_cap, "amp_in_cap" },
{ 0x0e, par_connlist_len, "connect_len" },
{ 0x0f, par_power_state, "power_state" },
{ 0x10, par_proc_cap, "proc_cap" },
{ 0x11, par_gpio_cap, "GPIO_cap" },
{ 0x12, par_amp_out_cap, "amp_out_cap" },
{ 0x13, par_vol_knb_cap, "volknob_cap" },
{}
};
static const struct xhda_verb_table *
find_matching_param(struct xhda_codec *codec, unsigned int parm)
{
const struct xhda_verb_table *tbl;
tbl = find_verb(parm, par_tbl);
if (!tbl && codec && codec->extended_parameters)
tbl = find_verb(parm, codec->extended_parameters);
return tbl;
}
static int get_parameters(struct xhda_codec *codec, struct xhda_node *node,
unsigned int cmd)
{
const struct xhda_verb_table *tbl;
tbl = find_matching_param(codec, cmd & 0xff);
if (tbl && tbl->func)
return tbl->func(codec, node, cmd);
return 0;
}
/*
*/
static struct xhda_verb_table verb_class[] = {
{ 2, set_stream_format, "set_stream_format" },
{ 3, set_amp_gain_mute, "set_amp_gain_mute" },
{ 4, set_proc_coef, "set_proc_coef" },
{ 5, set_coef_index, "set_coef_index" },
{ 10, get_stream_format, "get_stream_format" },
{ 11, get_amp_gain_mute, "get_amp_gain_mute" },
{ 12, get_proc_coef, "get_proc_coef" },
{ 13, get_coef_index, "get_coef_index" },
{}
};
static struct xhda_verb_table verb_tbl[] = {
{ 0x701, set_connect_sel, "set_connect_sel" },
{ 0x703, set_proc_state, "set_proc_state" },
{ 0x704, set_sdi_select, "set_sdi_select" },
{ 0x705, set_power_state, "set_power_state" },
{ 0x706, set_channel_streamid, "set_channel_streamid" },
{ 0x707, set_pin_ctl, "set_pin_ctl" },
{ 0x708, set_unsol_enable, "set_unsol_enable" },
{ 0x709, exec_pin_sense, "exec_pin_sense" },
{ 0x70a, set_beep_ctl, "set_beep_ctl" },
{ 0x70c, set_eapd_btl, "set_eapd_btl" },
{ 0x70d, set_digi_cvt_1, "set_digi_cvt_1" },
{ 0x70e, set_digi_cvt_2, "set_digi_cvt_2" },
{ 0x70f, set_volknob_ctl, "set_volknob_ctl" },
{ 0x715, set_gpio_data, "set_gpio_data" },
{ 0x716, set_gpio_mask, "set_gpio_mask" },
{ 0x717, set_gpio_dir, "set_gpio_dir" },
{ 0x718, set_gpio_wake_mask, "set_gpio_wake_mask" },
{ 0x719, set_gpio_unsol_rsp, "set_gpio_unsol_rsp" },
{ 0x71a, set_gpio_sticky_mask, "set_gpio_sticky_mask" },
{ 0x71c, set_config_def_0, "set_config_def_0" },
{ 0x71d, set_config_def_1, "set_config_def_1" },
{ 0x71e, set_config_def_2, "set_config_def_2" },
{ 0x71f, set_config_def_3, "set_config_def_3" },
{ 0x72d, set_cvt_channel_count, "set_cvt_channel_count" },
{ 0x730, set_dip_index, "set_dip_index" },
{ 0x731, set_dip_data, "set_dip_data" },
{ 0x732, set_dip_xmitctrl, "set_dip_xmitctrl" },
{ 0x734, set_asp_channel_slot, "set_asp_channel_slot" },
{ 0x7ff, set_codec_reset, "set_codec_reset" },
{ 0xf00, get_parameters, "get_parameters" },
{ 0xf01, get_connect_sel, "get_connect_sel" },
{ 0xf02, get_connect_list, "get_connect_list" },
{ 0xf03, get_proc_state, "get_proc_state" },
{ 0xf04, get_sdi_select, "get_sdi_select" },
{ 0xf05, get_power_state, "get_power_state" },
{ 0xf06, get_channel_streamid, "get_channel_streamid" },
{ 0xf07, get_pin_ctl, "get_pin_ctl" },
{ 0xf08, get_unsol_resp, "get_unsol_resp" },
{ 0xf09, get_pin_sense, "get_pin_sense" },
{ 0xf0a, get_beep_ctl, "get_beep_ctl" },
{ 0xf0c, get_eapd_btl, "get_eapd_btl" },
{ 0xf0d, get_digi_cvt, "get_digi_cvt" },
{ 0xf0f, get_volknob_ctl, "get_volknob_ctl" },
{ 0xf15, get_gpio_data, "get_gpio_data" },
{ 0xf16, get_gpio_mask, "get_gpio_mask" },
{ 0xf17, get_gpio_dir, "get_gpio_dir" },
{ 0xf18, get_gpio_wake_mask, "get_gpio_wake_mask" },
{ 0xf19, get_gpio_unsol_rsp, "get_gpio_unsol_rsp" },
{ 0xf1a, get_gpio_sticky_mask, "get_gpio_sticky_mask" },
{ 0xf1c, get_config_default, "get_config_default" },
{ 0xf20, get_ssid, "get_ssid" },
{ 0xf2d, get_cvt_channel_count, "get_cvt_channel_count" },
{ 0xf2e, get_dip_size, "get_dip_size" },
{ 0xf32, get_dip_xmitctrl, "get_dip_xmitctrl" },
{ 0xf34, get_asp_channel_slot, "get_asp_channel_slot" },
{}
};
static const struct xhda_verb_table *
find_matching_verb(struct xhda_codec *codec, unsigned int verb)
{
const struct xhda_verb_table *tbl;
tbl = find_verb((verb >> 8) & 0xf, verb_class);
if (!tbl) {
tbl = find_verb(verb, verb_tbl);
if (!tbl && codec && codec->extended_verbs)
tbl = find_verb(verb, codec->extended_verbs);
}
return tbl;
}
static struct xhda_node *find_node(struct xhda_codec *codec, unsigned int nid)
{
struct xhda_node *node;
for (node = &codec->afg; node; node = node->next)
if (node->nid == nid)
return node;
if (codec->mfg.nid) {
for (node = &codec->mfg; node; node = node->next)
if (node->nid == nid)
return node;
}
return NULL;
}
int hda_cmd(struct xhda_codec *codec, unsigned int cmd)
{
const struct xhda_verb_table *tbl;
struct xhda_node *node;
unsigned int nid = (cmd >> 20) & 0x7f;
unsigned int verb = (cmd >> 8) & 0xfff;
if (codec->extended_cmd) {
int r = codec->extended_cmd(codec, cmd);
if (r != -ENXIO)
return r;
}
tbl = find_matching_verb(codec, verb);
if (!tbl)
return -ENXIO;
if (!tbl->func)
return 0;
if (!nid)
node = NULL;
else {
node = find_node(codec, nid);
if (!node)
return -EINVAL;
}
codec->rc = tbl->func(codec, node, cmd);
return 0;
}
int hda_get_jack_state(struct xhda_codec *codec, int nid)
{
struct xhda_node *node = find_node(codec, nid);
if (node)
return node->jack_state;
return -1;
}
int hda_set_jack_state(struct xhda_codec *codec, int nid, int val)
{
struct xhda_node *node = find_node(codec, nid);
if (!node)
return -1;
val = !!val;
if (node->jack_state != val) {
node->jack_state = val;
return 1;
}
return 0;
}
int hda_get_unsol_state(struct xhda_codec *codec, int nid)
{
struct xhda_node *node = find_node(codec, nid);
if (!node)
return 0;
return node->unsol;
}
const char *get_verb_name(struct xhda_codec *codec, unsigned int cmd)
{
unsigned int verb = (cmd >> 8) & 0xfff;
const struct xhda_verb_table *tbl;
tbl = find_matching_verb(codec, verb);
return (tbl && tbl->name) ? tbl->name : "unknown";
}
const char *get_parameter_name(struct xhda_codec *codec, unsigned int cmd)
{
const struct xhda_verb_table *tbl;
tbl = find_matching_param(codec, cmd & 0xff);
return (tbl && tbl->name) ? tbl->name : "unknown";
}
void hda_set_proc_coef(struct xhda_codec *codec, int nid, int idx, int val)
{
struct xhda_node *node = find_node(codec, nid);
struct xhda_coef_table *tbl;
if (!node) {
hda_log(HDA_LOG_ERR, "Invalid NID 0x%x for proc\n", nid);
return;
}
tbl = find_proc_coef(node, idx);
if (!tbl) {
tbl = create_proc_coef(node, idx);
if (!tbl)
return;
}
tbl->value = val;
hda_log(HDA_LOG_INFO, "Set static coef idx 0x%x val 0x%x for NID 0x%x\n",
tbl->idx, tbl->value, nid);
}
/*
* for hda-decode-verb
*/
unsigned int hda_decode_verb_parm(struct xhda_codec *codec,
unsigned int verb, unsigned int parm)
{
hda_log(HDA_LOG_INFO, "raw value: verb = 0x%x, parm = 0x%x\n",
verb, parm);
hda_log(HDA_LOG_INFO, "verbname = %s\n",
get_verb_name(codec, verb << 8));
if ((verb >> 8) == 0x03) { /* set_amp_gain_mute */
unsigned int ampval = verb & 0xff;
ampval = (ampval << 8) | parm;
hda_log(HDA_LOG_INFO, "amp raw val = 0x%x\n", ampval);
hda_log(HDA_LOG_INFO, "%s%s%s%sidx=%d, mute=%d, val=%d\n",
((ampval >> 15) & 1) ? "output, " : "",
((ampval >> 14) & 1) ? "input, " : "",
((ampval >> 13) & 1) ? "left, " : "",
((ampval >> 12) & 1) ? "right, " : "",
(ampval >> 8) & 0xf,
(ampval >> 7) & 1,
ampval & 0x7f);
}
if ((verb >> 8) == 0x0b) { /* get_amp_gain_mute */
unsigned int ampval = verb & 0xff;
ampval = (ampval << 8) | parm;
hda_log(HDA_LOG_INFO, "amp raw val = 0x%x\n", ampval);
hda_log(HDA_LOG_INFO, "%s, %s, idx=%d\n",
((ampval >> 15) & 1) ? "output" : "input",
((ampval >> 13) & 1) ? "left" : "right",
ampval & 0xff);
}
if (verb == 0xf00) {
hda_log(HDA_LOG_INFO,
"parameter = %s\n", get_parameter_name(codec, parm));
}
return 0;
}
static int strmatch(const char *a, const char *b)
{
for (; *a && *b; a++, b++) {
if (toupper(*a) != toupper(*b))
return 0;
}
return 1;
}
/*
* for hda-encode-verb
*/
static const struct xhda_verb_table *
lookup_verb_name(const char *name, const struct xhda_verb_table *tbl)
{
for (; tbl->verb || tbl->func; tbl++) {
if (strmatch(name, tbl->name))
return tbl;
}
return NULL;
}
int hda_encode_verb_parm(const char *verb, const char *parm,
unsigned int *verb_ret, unsigned int *parm_ret)
{
const struct xhda_verb_table *tbl;
tbl = lookup_verb_name(verb, verb_class);
if (tbl)
*verb_ret = tbl->verb << 8;
else {
tbl = lookup_verb_name(verb, verb_tbl);
if (tbl)
*verb_ret = tbl->verb;
else {
errno = 0;
*verb_ret = strtoul(verb, NULL, 0);
if (errno)
return -errno;
}
}
tbl = lookup_verb_name(parm, par_tbl);
if (tbl)
*parm_ret = tbl->verb;
else {
errno = 0;
*parm_ret = strtoul(parm, NULL, 0);
if (errno)
return -errno;
}
return 0;
}
/*
* routing
*/
static struct xhda_route_list *create_route(struct xhda_node **route,
int depth, int revert)
{
struct xhda_route_list *list = xalloc(sizeof(*list));
int i;
list->num_nodes = depth;
for (i = 0; i < depth; i++) {
int j = revert ? (depth - i - 1) : i;
list->node[j] = route[i];
}
return list;
}
static int is_active_pin(struct xhda_node *node, unsigned int ctl_bit)
{
unsigned int cfg;
cfg = node->pin_default >> 30;
if (cfg == 1)
return 0;
cfg = node->pinctl;
if (!(cfg & ctl_bit))
return 0;
return 1;
}
static struct xhda_route_list *
routes_connected_to(struct xhda_codec *codec, struct xhda_node *node,
int depth, struct xhda_node **route,
unsigned int flags, struct xhda_route_list *list)
{
int i, type;
if (depth > MAX_ROUTE_DEPTH)
return list;
route[depth] = node;
type = (node->wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
for (i = 0; i < node->num_nodes; i++) {
struct xhda_node *src;
int src_type;
if (!(flags & SHOW_ALL) && type != AC_WID_AUD_MIX &&
i != node->curr_conn)
continue;
src = find_node(codec, node->node[i]);
if (!src)
continue;
src_type = (src->wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
if (src_type == AC_WID_AUD_OUT || src_type == AC_WID_PIN) {
struct xhda_route_list *nlist;
if (src_type == AC_WID_PIN &&
!(flags & SHOW_INACTIVE) &&
!is_active_pin(src, 1 << 5)) /* IN_EN */
continue;
route[depth + 1] = src;
nlist = create_route(route, depth + 2, 1);
nlist->next = list;
list = nlist;
continue;
}
list = routes_connected_to(codec, src, depth + 1, route,
flags, list);
}
return list;
}
struct xhda_route_list *
hda_routes_connected_to(struct xhda_codec *codec, int nid, unsigned int flags)
{
struct xhda_node *route[MAX_ROUTE_DEPTH + 1];
struct xhda_node *node;
node = find_node(codec, nid);
if (!node)
return NULL;
return routes_connected_to(codec, node, 0, route, flags, NULL);
}
static struct xhda_route_list *
routes_connected_from(struct xhda_codec *codec, struct xhda_node *node,
int depth, struct xhda_node **route,
unsigned int flags, struct xhda_route_list *list)
{
int i, type;
struct xhda_node *dest;
if (depth > MAX_ROUTE_DEPTH)
return list;
route[depth] = node;
for (dest = &codec->afg; dest; dest = dest->next) {
if (dest == node)
continue;
type = (dest->wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
for (i = 0; i < dest->num_nodes; i++) {
if (dest->node[i] != node->nid)
continue;
if (!(flags & SHOW_ALL) && type != AC_WID_AUD_MIX &&
i != dest->curr_conn)
continue;
route[depth + 1] = dest;
if (type == AC_WID_AUD_IN || type == AC_WID_PIN) {
struct xhda_route_list *nlist;
if (type == AC_WID_PIN &&
!(flags & SHOW_INACTIVE) &&
!is_active_pin(dest, 1 << 6)) /* OUT_EN */
continue;
route[depth + 1] = dest;
nlist = create_route(route, depth + 2, 0);
nlist->next = list;
list = nlist;
continue;
}
list = routes_connected_from(codec, dest, depth + 1,
route, flags, list);
}
}
return list;
}
struct xhda_route_list *
hda_routes_connected_from(struct xhda_codec *codec, int nid, unsigned int flags)
{
struct xhda_node *route[MAX_ROUTE_DEPTH + 1];
struct xhda_node *node;
node = find_node(codec, nid);
if (!node)
return NULL;
return routes_connected_from(codec, node, 0, route, flags, NULL);
}
void hda_free_route_lists(struct xhda_route_list *list)
{
struct xhda_route_list *next;
for (; list; list = next) {
next = list->next;
free(list);
}
}