From 7baec294f008749db8f2bc198270dfb75591fb3a Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 28 Nov 2008 11:08:23 +0100 Subject: [PATCH] initial commit for hda-analyzer --- hda-analyzer/hda_analyzer.py | 475 ++++++++++++++++ hda-analyzer/hda_codec.py | 1021 ++++++++++++++++++++++++++++++++++ hda-analyzer/hda_proc.py | 37 ++ 3 files changed, 1533 insertions(+) create mode 100755 hda-analyzer/hda_analyzer.py create mode 100644 hda-analyzer/hda_codec.py create mode 100644 hda-analyzer/hda_proc.py diff --git a/hda-analyzer/hda_analyzer.py b/hda-analyzer/hda_analyzer.py new file mode 100755 index 0000000..3208f30 --- /dev/null +++ b/hda-analyzer/hda_analyzer.py @@ -0,0 +1,475 @@ +#!/usr/bin/env python + +import gobject +import gtk +import pango + +from dircache import listdir +from hda_codec import HDACodec, HDANode, EAPDBTL_BITS, PIN_WIDGET_CONTROL_BITS, PIN_WIDGET_CONTROL_VREF + +PROC_DIR = '/proc/asound' + +CODEC_TREE = {} + +def read_verbs2(card, codec): + c = HDACodec(card, codec) + c.analyze_root_nodes() + CODEC_TREE[card][codec] = c + +def read_verbs1(card): + CODEC_TREE[card] = {} + for l in listdir('%s/card%s' % (PROC_DIR, card)): + if l.startswith('codec#') and l[6] >= '0' and l[6] <= '9': + read_verbs2(card, int(l[6:])) + +def read_verbs(): + for l in listdir(PROC_DIR): + if l.startswith('card') and l[4] >= '0' and l[4] <= '9': + read_verbs1(int(l[4:])) + +( + TITLE_COLUMN, + CARD_COLUMN, + CODEC_COLUMN, + NODE_COLUMN, + ITALIC_COLUMN +) = range(5) + +class HDAAnalyzer(gtk.Window): + info_buffer = None + node_window = None + + def __init__(self, parent=None): + gtk.Window.__init__(self) + try: + self.set_screen(parent.get_screen()) + except AttributeError: + self.connect('destroy', lambda *w: gtk.main_quit()) + self.set_default_size(800, 400) + self.set_title(self.__class__.__name__) + self.set_border_width(10) + + hbox = gtk.HBox(False, 3) + self.add(hbox) + + scrolled_window = gtk.ScrolledWindow() + scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolled_window.set_shadow_type(gtk.SHADOW_IN) + treeview = self.__create_treeview() + treeview.set_size_request(250, 600) + scrolled_window.add(treeview) + hbox.pack_start(scrolled_window, False, False) + + self.notebook = gtk.Notebook() + hbox.pack_start(self.notebook, expand=True) + + self.node_window = self.__create_node() + self._new_notebook_page(self.node_window, '_Node') + + scrolled_window, self.info_buffer = self.__create_text() + self._new_notebook_page(scrolled_window, '_Text info') + + self.show_all() + + def selection_changed_cb(self, selection): + model, iter = selection.get_selected() + if not iter: + return False + card = model.get_value(iter, CARD_COLUMN) + codec = model.get_value(iter, CODEC_COLUMN) + node = model.get_value(iter, NODE_COLUMN) + c = None + if codec >= 0: + c = CODEC_TREE[card][codec] + self.load(c, node) + + def load(self, codec, node): + n = None + if not codec: + txt = 'Show some card info here...' + elif codec and node < 0: + txt = codec.dump() + else: + txt, n = codec.dump_node(node) + buffer = self.info_buffer + start, end = buffer.get_bounds() + buffer.delete(start, end) + if not txt: return + iter = buffer.get_iter_at_offset(0) + buffer.insert(iter, txt) + + for child in self.node_window.get_children(): + self.node_window.remove(child) + + if not n: + return + + self.__build_node(n) + self.node_window.show_all() + + def _new_notebook_page(self, widget, label): + l = gtk.Label('') + l.set_text_with_mnemonic(label) + self.notebook.append_page(widget, l) + + def __create_treeview(self): + model = gtk.TreeStore( + gobject.TYPE_STRING, + gobject.TYPE_INT, + gobject.TYPE_INT, + gobject.TYPE_INT, + gobject.TYPE_BOOLEAN + ) + + treeview = gtk.TreeView(model) + selection = treeview.get_selection() + selection.set_mode(gtk.SELECTION_BROWSE) + treeview.set_size_request(200, -1) + + for card in CODEC_TREE: + iter = model.append(None) + model.set(iter, + TITLE_COLUMN, 'card-%s' % card, + CARD_COLUMN, card, + CODEC_COLUMN, -1, + NODE_COLUMN, -1, + ITALIC_COLUMN, False) + for codec in CODEC_TREE[card]: + citer = model.append(iter) + codec = CODEC_TREE[card][codec] + model.set(citer, + TITLE_COLUMN, 'codec-%s' % codec.device, + CARD_COLUMN, card, + CODEC_COLUMN, codec.device, + NODE_COLUMN, -1, + ITALIC_COLUMN, False) + nid = codec.base_nid + for verb in range(codec.nodes): + viter = model.append(citer) + node = None + if type(1) == type(nid): + node = HDANode(codec, nid) + model.set(viter, + TITLE_COLUMN, node and + 'Node[0x%02x] %s' % (nid, node.wtype_id) or nid, + CARD_COLUMN, card, + CODEC_COLUMN, codec.device, + NODE_COLUMN, nid, + ITALIC_COLUMN, False) + nid += 1 + + cell = gtk.CellRendererText() + cell.set_property('style', pango.STYLE_ITALIC) + + column = gtk.TreeViewColumn('Verb', cell, text=TITLE_COLUMN, + style_set=ITALIC_COLUMN) + + treeview.append_column(column) + + selection.connect('changed', self.selection_changed_cb) + + treeview.expand_all() + + return treeview + + def __create_text(self): + scrolled_window = gtk.ScrolledWindow() + scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolled_window.set_shadow_type(gtk.SHADOW_IN) + + text_view = gtk.TextView() + scrolled_window.add(text_view) + + buffer = gtk.TextBuffer(None) + text_view.set_buffer(buffer) + text_view.set_editable(False) + text_view.set_cursor_visible(False) + + text_view.set_wrap_mode(True) + + return scrolled_window, buffer + + def __create_node(self): + scrolled_window = gtk.ScrolledWindow() + scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolled_window.set_shadow_type(gtk.SHADOW_IN) + return scrolled_window + + def __build_node_caps(self, node): + frame = gtk.Frame('Node Caps') + frame.set_border_width(4) + if len(node.wcaps_list) == 0: + return frame + text_view = gtk.TextView() + text_view.set_border_width(4) + str = '' + for i in node.wcaps_list: + str += node.wcap_name(i) + '\n' + buffer = gtk.TextBuffer(None) + iter = buffer.get_iter_at_offset(0) + buffer.insert(iter, str) + text_view.set_buffer(buffer) + text_view.set_editable(False) + text_view.set_cursor_visible(False) + frame.add(text_view) + return frame + + def __node_connection_toggled(self, widget, row, data): + model, node = data + if not model[row][0]: + node.set_active_connection(int(row)) + for r in model: + r[0] = False + idx = 0 + for r in model: + r[0] = node.active_connection == idx + idx += 1 + + def __build_connection_list(self, node): + frame = gtk.Frame('Connection List') + frame.set_border_width(4) + sw = gtk.ScrolledWindow() + #sw.set_shadow_type(gtk.SHADOW_ETCHED_IN) + sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + frame.add(sw) + if node.conn_list: + model = gtk.ListStore( + gobject.TYPE_BOOLEAN, + gobject.TYPE_STRING + ) + idx = 0 + for i in node.connections: + iter = model.append() + node1 = HDANode(node.codec, node.connections[idx]) + model.set(iter, 0, node.active_connection == idx, + 1, node1.name()) + idx += 1 + treeview = gtk.TreeView(model) + treeview.set_rules_hint(True) + treeview.get_selection().set_mode(gtk.SELECTION_SINGLE) + renderer = gtk.CellRendererToggle() + renderer.set_radio(True) + if node.active_connection != None: + renderer.connect("toggled", self.__node_connection_toggled, (model, node)) + column = gtk.TreeViewColumn("Active", renderer, active=0) + treeview.append_column(column) + renderer = gtk.CellRendererText() + column = gtk.TreeViewColumn("Destination Node", renderer, text=1, editable=False) + treeview.append_column(column) + sw.add(treeview) + return frame + + def __amp_mute_toggled(self, button, data): + caps, vals, idx = data + val = button.get_active() + vals.set_mute(idx, val) + + def __amp_value_changed(self, adj, data): + caps, vals, idx = data + val = int(adj.get_value()) + vals.set_value(idx, val) + adj.set_value(val) + + def __build_amps(self, node): + + def build_caps(title, caps, vals): + frame = gtk.Frame(title) + frame.set_border_width(4) + vbox = gtk.VBox(False, 0) + if caps: + text_view = gtk.TextView() + text_view.set_border_width(4) + str = 'Offset:\t\t\t%d\n' % caps.ofs + str += 'Number of steps:\t%d\n' % caps.nsteps + str += 'Step size:\t\t%d\n' % caps.stepsize + str += 'Mute:\t\t\t%s\n' % (caps.mute and "True" or "False") + buffer = gtk.TextBuffer(None) + iter = buffer.get_iter_at_offset(0) + buffer.insert(iter, str) + text_view.set_buffer(buffer) + text_view.set_editable(False) + text_view.set_cursor_visible(False) + vbox.pack_start(text_view, True, True, 0) + idx = 0 + frame1 = None + vbox1 = None + for val in vals.vals: + if vals.stereo and idx & 1 == 0: + frame1 = gtk.Frame() + vbox.pack_start(frame1, False, False) + vbox1 = gtk.VBox(False, 0) + frame1.add(vbox1) + hbox = gtk.HBox(False, 0) + label = gtk.Label('Val[%d]' % idx) + hbox.pack_start(label, False, False) + if caps.mute: + checkbutton = gtk.CheckButton('Mute') + checkbutton.set_active(val & 0x80 and True or False) + checkbutton.connect("toggled", self.__amp_mute_toggled, (caps, vals, idx)) + hbox.pack_start(checkbutton, False, False) + if caps.stepsize > 0: + adj = gtk.Adjustment((val & 0x7f) % caps.nsteps, 0.0, caps.nsteps, 1.0, 1.0, 1.0) + scale = gtk.HScale(adj) + scale.set_value_pos(gtk.POS_RIGHT) + adj.connect("value_changed", self.__amp_value_changed, (caps, vals, idx)) + hbox.pack_start(scale, True, True) + if vbox1: + vbox1.pack_start(hbox, False, False) + else: + vbox.pack_start(hbox, False, False) + idx += 1 + frame.add(vbox) + return frame + + hbox = gtk.HBox(False, 0) + c = build_caps('Input Amplifier', + node.in_amp and node.amp_caps_in or None, + node.in_amp and node.amp_vals_in or None) + hbox.pack_start(c) + c = build_caps('Output Amplifier', + node.out_amp and node.amp_caps_out or None, + node.out_amp and node.amp_vals_out or None) + hbox.pack_start(c) + + return hbox + + def __pincap_eapdbtl_toggled(self, button, data): + node, name = data + node.eapdbtl_set_value(name, button.get_active()) + + def __pinctls_toggled(self, button, data): + node, name = data + node.pin_widget_control_set_value(name, button.get_active()) + + def __pinctls_vref_change(self, combobox, node): + index = combobox.get_active() + idx1 = 0 + for name in PIN_WIDGET_CONTROL_VREF: + if not name: continue + if idx1 == index: + node.pin_widget_control_set_value('vref', name) + break + idx1 += 1 + + def __build_pin(self, node): + hbox = gtk.HBox(False, 0) + + if node.pincap or node.pincap_vref or node.pincap_eapdbtl: + vbox = gtk.VBox(False, 0) + if node.pincap or node.pincap_vref: + frame = gtk.Frame('PIN Caps') + frame.set_border_width(4) + text_view = gtk.TextView() + text_view.set_border_width(4) + str = '' + for i in node.pincap: + str += node.pincap_name(i) + '\n' + for i in node.pincap_vref: + str += 'VREF_%s\n' % i + buffer = gtk.TextBuffer(None) + iter = buffer.get_iter_at_offset(0) + buffer.insert(iter, str) + text_view.set_buffer(buffer) + text_view.set_editable(False) + text_view.set_cursor_visible(False) + frame.add(text_view) + vbox.pack_start(frame) + if 'EAPD' in node.pincap: + frame = gtk.Frame('EAPD') + frame.set_border_width(4) + vbox1 = gtk.VBox(False, 0) + for name in EAPDBTL_BITS: + checkbutton = gtk.CheckButton(name) + checkbutton.set_active(node.pincap_eapdbtls & (1 << EAPDBTL_BITS[name])) + checkbutton.connect("toggled", self.__pincap_eapdbtl_toggled, (node, name)) + vbox1.pack_start(checkbutton, False, False) + frame.add(vbox1) + vbox.pack_start(frame, False, False) + hbox.pack_start(vbox) + + vbox = gtk.VBox(False, 0) + + frame = gtk.Frame('Config Default') + frame.set_border_width(4) + text_view = gtk.TextView() + text_view.set_border_width(4) + str = 'Jack connection:\t%s\n' % node.jack_conn_name + str += 'Jack type:\t\t%s\n' % node.jack_type_name + str += 'Jack location:\t\t%s\n' % node.jack_location_name + str += 'Jack location2:\t%s\n' % node.jack_location2_name + str += 'Jack connector:\t%s\n' % node.jack_connector_name + str += 'Jack color:\t\t%s\n' % node.jack_color_name + if 'NO_PRESENCE' in node.defcfg_misc: + str += 'No presence\n' + buffer = gtk.TextBuffer(None) + iter = buffer.get_iter_at_offset(0) + buffer.insert(iter, str) + text_view.set_buffer(buffer) + text_view.set_editable(False) + text_view.set_cursor_visible(False) + frame.add(text_view) + vbox.pack_start(frame) + + frame = gtk.Frame('Widget Control') + frame.set_border_width(4) + vbox1 = gtk.VBox(False, 0) + for name in PIN_WIDGET_CONTROL_BITS: + checkbutton = gtk.CheckButton(name) + checkbutton.set_active(node.pinctls & (1 << PIN_WIDGET_CONTROL_BITS[name])) + checkbutton.connect("toggled", self.__pinctls_toggled, (node, name)) + vbox1.pack_start(checkbutton, False, False) + if node.pincap_vref: + combobox = gtk.combo_box_new_text() + idx = idx1 = active = 0 + for name in PIN_WIDGET_CONTROL_VREF: + if name == node.pinctl_vref: active = idx1 + if name: + combobox.append_text(name) + idx1 += 1 + idx += 1 + combobox.set_active(active) + combobox.connect("changed", self.__pinctls_vref_change, node) + hbox1 = gtk.HBox(False, 0) + label = gtk.Label('VREF') + hbox1.pack_start(label, False, False) + hbox1.pack_start(combobox) + vbox1.pack_start(hbox1, False, False) + frame.add(vbox1) + vbox.pack_start(frame, False, False) + + hbox.pack_start(vbox) + return hbox + + def __build_node(self, node): + w = self.node_window + + mframe = gtk.Frame(node.name()) + mframe.set_border_width(4) + + vbox = gtk.VBox(False, 0) + hbox = gtk.HBox(False, 0) + hbox.pack_start(self.__build_node_caps(node)) + hbox.pack_start(self.__build_connection_list(node)) + vbox.pack_start(hbox, False, False) + vbox.pack_start(self.__build_amps(node), False, False) + if node.wtype_id == 'PIN': + vbox.pack_start(self.__build_pin(node), False, False) + + #mtable = gtk.Table(2, 2, True) + # + #mtable.attach(self.__build_node_caps(node), 0, 1, 0, 1) + #mtable.attach(self.__build_connection_list(node), 1, 2, 0, 1) + #mtable.attach(self.__build_amps(node), 0, 2, 1, 2) + #if node.wtype_id == 'PIN': + # mtable.attach(self.__build_pin(node), 0, 2, 2, 3) + + mframe.add(vbox) + w.add_with_viewport(mframe) + +def main(): + read_verbs() + HDAAnalyzer() + gtk.main() + +if __name__ == '__main__': + main() diff --git a/hda-analyzer/hda_codec.py b/hda-analyzer/hda_codec.py new file mode 100644 index 0000000..083ac16 --- /dev/null +++ b/hda-analyzer/hda_codec.py @@ -0,0 +1,1021 @@ +#!/usr/bin/env python + +import os +import struct +from fcntl import ioctl + +IOCTL_INFO = 0x80dc4801 +IOCTL_PVERSION = 0x80044810 +IOCTL_VERB_WRITE = 0xc0084811 +IOCTL_GET_WCAPS = 0xc0084812 + +AC_NODE_ROOT = 0 + +( + HDA_INPUT, + HDA_OUTPUT +) = range(2) + +VERBS = { + 'GET_STREAM_FORMAT': 0x0a00, + 'GET_AMP_GAIN_MUTE': 0x0b00, + 'GET_PROC_COEF': 0x0c00, + 'GET_COEF_INDEX': 0x0d00, + 'PARAMETERS': 0x0f00, + 'GET_CONNECT_SEL': 0x0f01, + 'GET_CONNECT_LIST': 0x0f02, + 'GET_PROC_STATE': 0x0f03, + 'GET_SDI_SELECT': 0x0f04, + 'GET_POWER_STATE': 0x0f05, + 'GET_CONV': 0x0f06, + 'GET_PIN_WIDGET_CONTROL': 0x0f07, + 'GET_UNSOLICITED_RESPONSE': 0x0f08, + 'GET_PIN_SENSE': 0x0f09, + 'GET_BEEP_CONTROL': 0x0f0a, + 'GET_EAPD_BTLENABLE': 0x0f0c, + 'GET_DIGI_CONVERT_1': 0x0f0d, + 'GET_DIGI_CONVERT_2': 0x0f0e, + 'GET_VOLUME_KNOB_CONTROL': 0x0f0f, + 'GET_GPIO_DATA': 0x0f15, + 'GET_GPIO_MASK': 0x0f16, + 'GET_GPIO_DIRECTION': 0x0f17, + 'GET_GPIO_WAKE_MASK': 0x0f18, + 'GET_GPIO_UNSOLICITED_RSP_MASK': 0x0f19, + 'GET_GPIO_STICKY_MASK': 0x0f1a, + 'GET_CONFIG_DEFAULT': 0x0f1c, + 'GET_SUBSYSTEM_ID': 0x0f20, + + 'SET_STREAM_FORMAT': 0x200, + 'SET_AMP_GAIN_MUTE': 0x300, + 'SET_PROC_COEF': 0x400, + 'SET_COEF_INDEX': 0x500, + 'SET_CONNECT_SEL': 0x701, + 'SET_PROC_STATE': 0x703, + 'SET_SDI_SELECT': 0x704, + 'SET_POWER_STATE': 0x705, + 'SET_CHANNEL_STREAMID': 0x706, + 'SET_PIN_WIDGET_CONTROL': 0x707, + 'SET_UNSOLICITED_ENABLE': 0x708, + 'SET_PIN_SENSE': 0x709, + 'SET_BEEP_CONTROL': 0x70a, + 'SET_EAPD_BTLENABLE': 0x70c, + 'SET_DIGI_CONVERT_1': 0x70d, + 'SET_DIGI_CONVERT_2': 0x70e, + 'SET_VOLUME_KNOB_CONTROL': 0x70f, + 'SET_GPIO_DATA': 0x715, + 'SET_GPIO_MASK': 0x716, + 'SET_GPIO_DIRECTION': 0x717, + 'SET_GPIO_WAKE_MASK': 0x718, + 'SET_GPIO_UNSOLICITED_RSP_MASK': 0x719, + 'SET_GPIO_STICKY_MASK': 0x71a, + 'SET_CONFIG_DEFAULT_BYTES_0': 0x71c, + 'SET_CONFIG_DEFAULT_BYTES_1': 0x71d, + 'SET_CONFIG_DEFAULT_BYTES_2': 0x71e, + 'SET_CONFIG_DEFAULT_BYTES_3': 0x71f, + 'SET_CODEC_RESET': 0x7ff +} + +PARAMS = { + 'VENDOR_ID': 0x00, + 'SUBSYSTEM_ID': 0x01, + 'REV_ID': 0x02, + 'NODE_COUNT': 0x04, + 'FUNCTION_TYPE': 0x05, + 'AUDIO_FG_CAP': 0x08, + 'AUDIO_WIDGET_CAP': 0x09, + 'PCM': 0x0a, + 'STREAM': 0x0b, + 'PIN_CAP': 0x0c, + 'AMP_IN_CAP': 0x0d, + 'CONNLIST_LEN': 0x0e, + 'POWER_STATE': 0x0f, + 'PROC_CAP': 0x10, + 'GPIO_CAP': 0x11, + 'AMP_OUT_CAP': 0x12, + 'VOL_KNB_CAP': 0x13 +} + +WIDGET_TYPES = { + 'AUD_OUT': 0x00, + 'AUD_IN': 0x01, + 'AUD_MIX': 0x02, + 'AUD_SEL': 0x03, + 'PIN': 0x04, + 'POWER': 0x05, + 'VOL_KNB': 0x06, + 'BEEP': 0x07, + 'VENDOR': 0x0f +} + +WIDGET_TYPE_NAMES = [ + "Audio Output", + "Audio Input", + "Audio Mixer", + "Audio Selector", + "Pin Complex", + "Power Widget", + "Volume Knob Widget", + "Beep Generator Widget", + None, + None, + None, + None, + None, + None, + None, + "Vendor Defined Widget" +] + +WIDGET_TYPE_IDS = [ + "AUD_OUT", + "AUD_IN", + "AUD_MIX", + "AUD_SEL", + "PIN", + "POWER", + "VOL_KNB", + "BEEP", + None, + None, + None, + None, + None, + None, + None, + "VENDOR" +] + +WIDGET_CAP_NAMES = { + 'STEREO': 'Stereo', + 'IN_AMP': 'Input Amplifier', + 'OUT_AMP': 'Output Amplifier', + 'AMP_OVRD': 'Amplifier Override', + 'FORMAT_OVRD': 'Format Override', + 'STRIPE': 'Stripe', + 'PROC_WID': 'Proc Widget', + 'CONN_LIST': 'Connection List', + 'UNSOL_CAP': 'Unsolicited Capabilities', + 'DIGITAL': 'Digital', + 'POWER': 'Power', + 'LR_SWAP': 'L/R Swap', + 'CP_CAPS': 'CP Capabilities' +} + +WIDGET_PINCAP_NAMES = { + 'IMP_SENSE': 'Input Sense', + 'TRIG_REQ': 'Trigger Request', + 'PRES_DETECT': 'Press Detect', + 'HP_DRV': 'Headphone Drive', + 'OUT': 'Output', + 'IN': 'Input', + 'BALANCE': 'Balance', + 'HDMI': 'HDMI', + 'EAPD': 'EAPD' +} + +GPIO_IDS = { + 'enable': (VERBS['GET_GPIO_MASK'], VERBS['SET_GPIO_MASK']), + 'direction': (VERBS['GET_GPIO_DIRECTION'], VERBS['SET_GPIO_DIRECTION']), + 'wake': (VERBS['GET_GPIO_WAKE_MASK'], VERBS['SET_GPIO_WAKE_MASK']), + 'unsol': (VERBS['GET_GPIO_UNSOLICITED_RSP_MASK'], VERBS['SET_GPIO_UNSOLICITED_RSP_MASK']), + 'sticky': (VERBS['GET_GPIO_STICKY_MASK'], VERBS['SET_GPIO_STICKY_MASK']), + 'data': (VERBS['GET_GPIO_DATA'], VERBS['SET_GPIO_DATA']) +} + +EAPDBTL_BITS = { + 'BALANCED': 0, + 'EAPD': 1, + 'R/L': 2 +} + +PIN_WIDGET_CONTROL_BITS = { + 'IN': 5, + 'OUT': 6, + 'HP': 7 +} + +PIN_WIDGET_CONTROL_VREF = [ + "HIZ", "50", "GRD", None, "80", "100", None, None +] + +DIG1_BITS = { + 'ENABLE': 0, + 'VALIDITY': 1, + 'VALIDITYCFG': 2, + 'EMPHASIS': 3, + 'COPYRIGHT': 4, + 'NONAUDIO': 5, + 'PROFESSIONAL': 6, + 'LEVEL': 7 +} + +class HDAAmpCaps: + + def __init__(self, codec, nid, dir): + self.codec = codec + self.nid = nid + self.dir = dir + self.reread() + + def reread(self): + caps = self.codec.param_read(self.nid, + PARAMS[self.dir == HDA_OUTPUT and 'AMP_OUT_CAP' or 'AMP_IN_CAP']) + if caps == ~0 or caps == 0: + self.ofs = None + self.nsteps = None + self.stepsize = None + self.mute = None + else: + self.ofs = caps & 0x7f + self.nsteps = (caps >> 8) & 0x7f + self.stepsize = (caps >> 16) & 0x7f + self.mute = (caps >> 31) & 1 and True or False + +class HDAAmpVal: + + def __init__(self, codec, node, dir): + self.codec = codec + self.node = node + self.dir = dir + self.nid = node.nid + self.stereo = node.stereo + self.indices = 1 + if dir == HDA_INPUT: + self.indices = node.wtype_id == 'PIN' and 1 or len(node.connections) + self.reread() + + def __write_val(self, idx): + dir = self.dir == HDA_OUTPUT and (1<<15) or (1<<14) + verb = VERBS['SET_AMP_GAIN_MUTE'] + if self.stereo: + indice = idx / 2 + dir |= idx & 1 and (1 << 12) or (1 << 13) + else: + indice = idx + dir |= (1 << 12) | (1 << 13) + self.codec.rw(self.nid, verb, dir | (indice << 8) | self.vals[idx]) + + def set_mute(self, idx, mute): + val = self.vals[idx] + if mute: + self.vals[idx] |= 0x80 + else: + self.vals[idx] &= ~0x80 + self.__write_val(idx) + + def set_value(self, idx, val): + self.vals[idx] &= ~0x7f + self.vals[idx] |= val & 0x7f + self.__write_val(idx) + + def reread(self): + dir = self.dir == HDA_OUTPUT and (1<<15) or (0<<15) + self.vals = [] + verb = VERBS['GET_AMP_GAIN_MUTE'] + for i in range(self.indices): + if self.stereo: + val = self.codec.rw(self.nid, verb, (1 << 13) | dir | i) + self.vals.append(val) + val = self.codec.rw(self.nid, verb, (0 << 13) | dir | i) + self.vals.append(val) + +class HDANode: + + def __init__(self, codec, nid, cache=True): + self.codec = codec + self.nid = nid + self.wcaps = cache and codec.get_wcap(nid) or codec.get_raw_wcap(nid) + self.stereo = (self.wcaps & (1 << 0)) and True or False + self.in_amp = (self.wcaps & (1 << 1)) and True or False + self.out_amp = (self.wcaps & (1 << 2)) and True or False + self.amp_ovrd = (self.wcaps & (1 << 3)) and True or False + self.format_ovrd = (self.wcaps & (1 << 4)) and True or False + self.stripe = (self.wcaps & (1 << 5)) and True or False + self.proc_wid = (self.wcaps & (1 << 6)) and True or False + self.unsol_cap = (self.wcaps & (1 << 7)) and True or False + self.conn_list = (self.wcaps & (1 << 8)) and True or False + self.digital = (self.wcaps & (1 << 9)) and True or False + self.power = (self.wcaps & (1 << 10)) and True or False + self.lr_swap = (self.wcaps & (1 << 11)) and True or False + self.cp_caps = (self.wcaps & (1 << 12)) and True or False + self.chan_cnt_ext = (self.wcaps >> 13) & 7 + self.wdelay = (self.wcaps >> 16) & 0x0f + self.wtype = (self.wcaps >> 20) & 0x0f + self.channels = ((self.chan_cnt_ext << 1) | 1) + 1 + self.wtype_id = WIDGET_TYPE_IDS[self.wtype] + if self.wtype_id == 'VOL_KNB': self.conn_list = True + + self.wcaps_list = [] + if self.stereo: self.wcaps_list.append('STEREO') + if self.in_amp: self.wcaps_list.append('IN_AMP') + if self.out_amp: self.wcaps_list.append('OUT_AMP') + if self.amp_ovrd: self.wcaps_list.append('AMP_OVRD') + if self.format_ovrd: self.wcaps_list.append('FORMAT_OVRD') + if self.stripe: self.wcaps_list.append('STRIPE') + if self.proc_wid: self.wcaps_list.append('PROC_WID') + if self.unsol_cap: self.wcaps_list.append('UNSOL_CAP') + if self.conn_list: self.wcaps_list.append('CONN_LIST') + if self.digital: self.wcaps_list.append('DIGITAL') + if self.power: self.wcaps_list.append('POWER') + if self.lr_swap: self.wcaps_list.append('LR_SWAP') + if self.cp_caps: self.wcaps_list.append('CP_CAPS') + + self.reread() + + def wtype_name(self): + name = WIDGET_TYPE_NAMES[self.wtype] + if not name: + return "UNKNOWN Widget 0x%x" % self.wtype + return name + + def wcap_name(self, id): + return WIDGET_CAP_NAMES[id] + + def pincap_name(self, id): + return WIDGET_PINCAP_NAMES[id] + + def name(self): + return self.wtype_name() + " [0x%02x]" % self.nid + + def set_active_connection(self, val): + if self.active_connection != None: + self.codec.rw(self.nid, VERBS['SET_CONNECT_SEL'], val) + self.active_connection = self.codec.rw(self.nid, VERBS['GET_CONNECT_SEL'], 0) + + def reread(self): + + def get_jack_location(cfg): + bases = ["N/A", "Rear", "Front", "Left", "Right", "Top", "Bottom"] + specials = {0x07: "Rear Panel", 0x08: "Drive Bar", + 0x17: "Riser", 0x18: "HDMI", 0x19: "ATAPI", + 0x37: "Mobile-In", 0x38: "Mobile-Out"} + cfg = (cfg >> 24) & 0x3f + if cfg & 0x0f < 7: + return bases[cfg & 0x0f] + if cfg in specials: + return specials[cfg] + return "UNKNOWN" + + def get_jack_connector(cfg): + names = ["Unknown", "1/8", "1/4", "ATAPI", "RCA", "Optical", + "Digital", "Analog", "DIN", "XLR", "RJ11", "Comb", + None, None, None, "Oth[6~er"] + cfg = (cfg >> 16) & 0x0f + return names[cfg] and names[cfg] or "UNKNOWN" + + def get_jack_color(cfg): + names = ["Unknown", "Black", "Grey", "Blue", "Green", "Red", "Orange", + "Yellow", "Purple", "Pink", None, None, None, None, "White", + "Other"] + cfg = (cfg >> 12) & 0x0f + return names[cfg] and names[cfg] or "UNKNOWN" + + self.connections = None + self.active_connection = None + if self.conn_list: + self.connections = self.codec.get_connections(self.nid) + if self.wtype_id != 'AUD_MIX': + self.active_connection = self.codec.rw(self.nid, VERBS['GET_CONNECT_SEL'], 0) + if self.in_amp: + self.amp_caps_in = HDAAmpCaps(self.codec, self.nid, HDA_INPUT) + self.amp_vals_in = HDAAmpVal(self.codec, self, HDA_INPUT) + if self.out_amp: + self.amp_caps_out = HDAAmpCaps(self.codec, self.nid, HDA_OUTPUT) + self.amp_vals_out = HDAAmpVal(self.codec, self, HDA_OUTPUT) + if self.wtype_id == 'PIN': + jack_conns = ["Jack", "N/A", "Fixed", "Both"] + jack_types = ["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"] + jack_locations = ["Ext", "Int", "Sep", "Oth"] + + caps = self.codec.param_read(self.nid, PARAMS['PIN_CAP']) + self.pincaps = caps + self.pincap = [] + if caps & (1 << 0): self.pincap.append('IMP_SENSE') + if caps & (1 << 1): self.pincap.append('TRIG_REQ') + if caps & (1 << 2): self.pincap.append('PRES_DETECT') + if caps & (1 << 3): self.pincap.append('HP_DRV') + if caps & (1 << 4): self.pincap.append('OUT') + if caps & (1 << 5): self.pincap.append('IN') + if caps & (1 << 6): self.pincap.append('BALANCE') + if caps & (1 << 7): self.pincap.append('HDMI') + if caps & (1 << 16): self.pincap.append('EAPD') + self.pincap_vref = [] + if caps & (1 << 8): self.pincap_vref.append('HIZ') + if caps & (1 << 9): self.pincap_vref.append('50') + if caps & (1 << 10): self.pincap_vref.append('GRD') + if caps & (1 << 12): self.pincap_vref.append('80') + if caps & (1 << 13): self.pincap_vref.append('100') + self.reread_eapdbtl() + caps = self.codec.rw(self.nid, VERBS['GET_CONFIG_DEFAULT'], 0) + self.defcfg_pincaps = caps + self.jack_conn_name = jack_conns[(caps >> 30) & 0x03] + self.jack_type_name = jack_types[(caps >> 20) & 0x0f] + self.jack_location_name = jack_locations[(caps >> 28) & 0x03] + self.jack_location2_name = get_jack_location(caps) + self.jack_connector_name = get_jack_connector(caps) + self.jack_color_name = get_jack_color(caps) + self.defcfg_assoc = (caps >> 4) & 0x0f + self.defcfg_sequence = (caps >> 0) & 0x0f + self.defcfg_misc = [] + if caps & (1 << 8): self.defcfg_misc.append('NO_PRESENCE') + self.reread_pin_widget_control() + elif self.wtype_id == 'VOL_KNB': + cap = self.codec.param_read(self.nid, PARAMS['VOL_KNB_CAP']) + self.vol_knb_delta = (cap >> 7) & 1 + self.vol_knb_steps = cap & 0x7f + self.reread_vol_knb() + elif self.wtype_id in ['AUD_IN', 'AUD_OUT']: + conv = self.codec.rw(self.nid, VERBS['GET_CONV'], 0) + self.aud_stream = (conv >> 4) & 0x0f + self.aud_channel = (conv >> 0) & 0x0f + self.reread_sdi_select() + self.reread_dig1() + if self.format_ovrd: + pcm = self.codec.param_read(self.nid, PARAMS['PCM']) + stream = self.codec.param_read(self.nid, PARAMS['STREAM']) + self.pcm_rate = pcm & 0xffff + self.pcm_rates = self.codec.analyze_pcm_rates(self.pcm_rate) + self.pcm_bit = pcm >> 16 + self.pcm_bits = self.codec.analyze_pcm_bits(self.pcm_bit) + self.pcm_stream = stream + self.pcm_streams = self.codec.analyze_pcm_streams(self.pcm_stream) + elif self.wtype_id in ['PROC_WID']: + proc_caps = self.codec.param_read(self.nid, PARAMS['PROC_CAP']) + self.proc_benign = proc_caps & 1 and True or False + self.proc_numcoef = (proc_caps >> 8) & 0xff + if self.unsol_cap: + unsol = self.codec.rw(self.nid, VERBS['GET_UNSOLICITED_RESPONSE'], 0) + self.unsol_tag = unsol & 0x3f + self.unsol_enabled = (unsol & (1 << 7)) and True or False + if self.power: + states = ["D0", "D1", "D2", "D3"] + pwr = self.codec.rw(self.nid, VERBS['GET_POWER_STATE'], 0) + self.pwr_setting = pwr & 0x0f + self.pwr_actual = (pwr >> 4) & 0x0f + self.pwr_setting_name = self.pwr_setting < 4 and states[self.pwr_setting] or "UNKNOWN" + self.pwr_actual_name = self.pwr_actual < 4 and states[self.pwr_actual] or "UNKNOWN" + # NID 0x20 == Realtek Define Registers + if self.codec.vendor_id == 0x10ec and self.nid == 0x20: + self.realtek_coeff_proc = self.codec.rw(self.nid, VERBS['GET_PROC_COEF'], 0) + self.realtek_coeff_index = self.codec.rw(self.nid, VERBS['GET_COEF_INDEX'], 0) + + def reread_eapdbtl(self): + self.pincap_eapdbtl = [] + self.pincap_eapdbtls = 0 + if not 'EAPD' in self.pincap: + return + val = self.codec.rw(self.nid, VERBS['GET_EAPD_BTLENABLE'], 0) + self.pincap_eapdbtls = val + for name in EAPDBTL_BITS: + bit = EAPDBTL_BITS[name] + if val & (1 << bit): self.pincap_eapdbtl.append(name) + + def eapdbtl_set_value(self, name, value): + mask = 1 << EAPDBTL_BITS[name] + if value: + self.pincap_eapdbtls |= mask + else: + self.pincap_eapdbtls &= ~mask + self.codec.rw(self.nid, VERBS['SET_EAPD_BTLENABLE'], self.pincap_eapdbtls) + self.reread_eapdbtl() + + def reread_pin_widget_control(self): + pinctls = self.codec.rw(self.nid, VERBS['GET_PIN_WIDGET_CONTROL'], 0) + self.pinctls = pinctls + self.pinctl = [] + for name in PIN_WIDGET_CONTROL_BITS: + bit = PIN_WIDGET_CONTROL_BITS[name] + if pinctls & (1 << bit): self.pinctl.append(name) + self.pinctl_vref = None + if self.pincap_vref: + self.pinctl_vref = PIN_WIDGET_CONTROL_VREF[pinctls & 0x07] + + def pin_widget_control_set_value(self, name, value): + if name in PIN_WIDGET_CONTROL_BITS: + mask = 1 << PIN_WIDGET_CONTROL_BITS[name] + if value: + self.pinctls |= mask + else: + self.pinctls &= ~mask + elif name == 'vref' and self.pincap_vref: + idx = PIN_WIDGET_CONTROL_VREF.index(value) + self.pinctls &= ~0x07 + self.pinctls |= idx + self.codec.rw(self.nid, VERBS['SET_PIN_WIDGET_CONTROL'], self.pinctls) + self.reread_pin_widget_control() + + def reread_vol_knb(self): + cap = self.codec.rw(self.nid, VERBS['GET_VOLUME_KNOB_CONTROL'], 0) + self.vol_knb = cap + self.vol_knb_direct = (cap >> 7) & 1 + self.vol_knb_val = cap & 0x7f + + def vol_knb_set_value(self, name, value): + if name == 'direct': + if value: + self.vol_knb |= (1 << 7) + else: + self.vol_knb &= ~(1 << 7) + elif name == 'value': + self.vol_knb &= 0x7f + self.vol_knb |= value + self.codec.rw(self.nid, VERBS['SET_VOLUME_KNOB_CONTROL'], self.vol_knb) + self.reread_vol_knb() + + def reread_sdi_select(self): + self.sdi_select = None + if self.wtype_id == 'AUD_IN' and self.aud_channel == 0: + sdi = self.codec.rw(self.nid, VERBS['GET_SDI_SELECT'], 0) + self.sdi_select = sdi & 0x0f + + def sdi_select_set_value(self, value): + if self.sdi_select != None: + self.sdi_select = value & 0x0f + self.codec.rw(self.nid, VERBS['SET_SDI_SELECT'], self.sdi_select) + self.reread_sdi_select() + + def reread_dig1(self): + self.dig1 = [] + self.dig1_category = None + if not self.digital: + return + digi1 = self.codec.rw(self.nid, VERBS['GET_DIGI_CONVERT_1'], 0) + self.digi1 = digi1 + for name in DIG1_BITS: + bit = DIG1_BITS[name] + if digi1 & (1 << bit): self.dig1.append(name) + self.dig1_category = (digi1 >> 8) & 0x7f + + def dig1_set_value(self, name, value): + if name == 'category': + self.digi1 &= 0x7f00 + self.digi1 |= (value & 0x7f) << 8 + else: + mask = DIG1_BITS[name] + if value: + self.digi1 |= mask + else: + self.digi1 &= ~mask + self.codec.rw(self.nid, VERBS['SET_DIGI_CONVERT_1'], self.digi1) + + +class HDAGPIO: + + def __init__(self, codec, nid): + self.codec = codec + self.nid = nid + self.reread() + + def reread(self): + self.val = {} + for i in GPIO_IDS: + self.val[i] = self.codec.rw(self.nid, GPIO_IDS[i][0], 0) + + def test(self, name, bit): + return (self.val[name] & (1 << bit)) and True or False + + def read(self, name): + self.val[i] = self.codec.rw(nid, GPIO_IDS[name][0], 0) + + def write(self, name): + self.val[i] = self.codec.rw(nid, GPIO_IDS[name][1], self.val[i]) + + def set(self, name, bit, val): + old = self.test(name, bit) + if val: + self.val[name] |= 1 << bit + else: + self.val[name] &= 1 << bit + if old == self.test(name, bit): + return + self.write(name) + +class HDACodec: + + afg = None + mfg = None + vendor_id = None + subsystem_id = None + revision_id = None + + def __init__(self, card=0, device=0): + self.card = card + self.device = device + self.fd = os.open("/dev/snd/hwC%sD%s" % (card, device), os.O_RDWR) + info = struct.pack('Ii64s80si64s', 0, 0, '', '', 0, '') + res = ioctl(self.fd, IOCTL_INFO, info) + name = struct.unpack('Ii64s80si64s', res)[3] + if not name.startswith('HDA Codec'): + raise IOError, "unknown HDA hwdep interface" + res = ioctl(self.fd, IOCTL_PVERSION, struct.pack('I', 0)) + self.version = struct.unpack('I', res) + if self.version < 0x00010000: # 1.0.0 + raise IOError, "unknown HDA hwdep version" + + def rw(self, nid, verb, param): + """do elementary read/write operation""" + verb = (nid << 24) | (verb << 8) | param + res = ioctl(self.fd, IOCTL_VERB_WRITE, struct.pack('II', verb, 0)) + return struct.unpack('II', res)[1] + + def get_wcap(self, nid): + """get cached widget capabilities""" + res = ioctl(self.fd, IOCTL_GET_WCAPS, struct.pack('II', nid << 24, 0)) + return struct.unpack('II', res)[1] + + def get_raw_wcap(self, nid): + """get raw widget capabilities""" + return self.rw(nid, VERBS['PARAMETERS'], PARAMS['AUDIO_WIDGET_CAP']) + + def param_read(self, nid, param): + """read perameters""" + return self.rw(nid, VERBS['PARAMETERS'], param) + + def get_sub_nodes(self, nid): + """return sub-nodes count (returns count and start NID)""" + res = self.param_read(nid, PARAMS['NODE_COUNT']) + return res & 0x7fff, (res >> 16) & 0x7fff + + def get_connections(self, nid): + """parses connection list and returns the array of NIDs""" + parm = self.param_read(nid, PARAMS['CONNLIST_LEN']) + if parm & (1 << 7): # long + shift = 16 + num_elems = 2 + else: # short + shift = 8 + num_elems = 4 + conn_len = parm & 0x7f + mask = (1 << (shift - 1)) - 1 + if not conn_len: + return None + if conn_len == 1: + parm = self.rw(nid, VERBS['GET_CONNECT_LIST'], 0) + return [parm & mask] + res = [] + prev_nid = 0 + for i in range(conn_len): + if i % num_elems == 0: + parm = self.rw(nid, VERBS['GET_CONNECT_LIST'], i) + range_val = parm & (1 << (shift - 1)) + val = parm & mask + parm >>= shift + if range_val: + if not prev_nid or prev_nid >= val: + raise IOError, "invalid dep_range_val 0x%x:0x%x\n" % (prev_nid, val) + n = prev_nid + 1 + while n <= val: + res.append(n) + n += 1 + else: + res.append(val) + prev_nid = val + return res + + def analyze_root_nodes(self): + self.afg = None + self.mfg = None + self.vendor_id = self.param_read(AC_NODE_ROOT, PARAMS['VENDOR_ID']) + self.subsystem_id = self.param_read(AC_NODE_ROOT, PARAMS['SUBSYSTEM_ID']) + self.revision_id = self.param_read(AC_NODE_ROOT, PARAMS['REV_ID']) + + total, nid = self.get_sub_nodes(AC_NODE_ROOT) + for i in range(total): + func = self.param_read(nid, PARAMS['FUNCTION_TYPE']) + if (func & 0xff) == 0x01: # audio group + self.afg = nid + elif (func & 0xff) == 0x02: # modem group + self.mfg = nid + else: + break + nid += 1 + + if self.subsystem_id == 0: + self.subsystem_id = self.rw(self.afg and self.afg or self.mfg, + VERBS['GET_SUBSYSTEM_ID'], 0) + + pcm = self.param_read(self.afg, PARAMS['PCM']) + self.pcm_rate = pcm & 0xffff + self.pcm_rates = self.analyze_pcm_rates(self.pcm_rate) + self.pcm_bit = pcm >> 16 + self.pcm_bits = self.analyze_pcm_bits(self.pcm_bit) + + self.pcm_stream = self.param_read(self.afg, PARAMS['STREAM']) + self.pcm_streams = self.analyze_pcm_streams(self.pcm_stream) + + self.amp_caps_in = HDAAmpCaps(self, self.afg, HDA_INPUT) + self.amp_caps_out = HDAAmpCaps(self, self.afg, HDA_OUTPUT) + + self.gpio_cap = self.param_read(self.afg, PARAMS['GPIO_CAP']) + self.gpio_max = self.gpio_cap & 0xff + self.gpio_o = (self.gpio_cap >> 8) & 0xff + self.gpio_i = (self.gpio_cap >> 16) & 0xff + self.gpio_unsol = (self.gpio_cap >> 30) & 1 and True or False + self.gpio_wake = (self.gpio_cap >> 31) & 1 and True or False + self.gpio = HDAGPIO(self, self.afg) + + self.nodes, self.base_nid = self.get_sub_nodes(self.afg) + + def analyze_pcm_rates(self, pcm): + rates = [8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200, + 96000, 176400, 192000, 384000] + res = [] + for i in range(len(rates)): + if pcm & (1 << i): + res.append(rates[i]) + return res + + def analyze_pcm_bits(self, bit): + bits = [8, 16, 20, 24, 32] + res = [] + for i in range(len(bits)): + if bit & (1 << i): + res.append(bits[i]) + return res + + def analyze_pcm_streams(self, stream): + res = [] + if stream & 0x01: res.append("PCM") + if stream & 0x02: res.append("FLOAT") + if stream & 0x04: res.append("AC3") + return res + + def dump(self): + + def print_pcm_rates(node): + s = '' + for i in node.pcm_rates: + s += " %d" % i + return " rates [0x%x]:%s\n" % (node.pcm_rate, s) + + def print_pcm_bits(node): + s = '' + for i in node.pcm_bits: + s += " %d" % i + return " bits [0x%x]:%s\n" % (node.pcm_bit, s) + + def print_pcm_formats(node): + str = " formats [0x%x]:" % node.pcm_stream + for i in node.pcm_streams: + str += " %s" % i + return str + "\n" + + def print_pcm_caps(node): + str = print_pcm_rates(node) + str += print_pcm_bits(node) + return str + print_pcm_formats(node) + + def print_gpio(node): + gpio = node.gpio_cap + str = 'GPIO: io=%d, o=%d, i=%d, unsolicited=%d, wake=%d\n' % \ + (node.gpio_max, node.gpio_o, node.gpio_i, + node.gpio_unsol and 1 or 0, node.gpio_wake and 1 or 0) + for i in range(node.gpio_max): + str += ' IO[%d]: enable=%d, dir=%d, wake=%d, sticky=%d, ' \ + 'data=%d, unsol=%d\n' % (i, + node.gpio.test('enable', i) and 1 or 0, + node.gpio.test('direction', i) and 1 or 0, + node.gpio.test('wake', i) and 1 or 0, + node.gpio.test('sticky', i) and 1 or 0, + node.gpio.test('data', i) and 1 or 0, + node.gpio.test('unsol', i) and 1 or 0) + return str + + def print_amp_caps(caps): + if caps.ofs == None: + return "N/A\n" + return "ofs=0x%02x, nsteps=0x%02x, stepsize=0x%02x, mute=%x\n" % \ + (caps.ofs, caps.nsteps, caps.stepsize, caps.mute and 1 or 0) + + if not self.afg and not self.mfg: + self.analyze_root_nodes() + str = 'Vendor Id: 0x%x\n' % self.vendor_id + str += 'Subsystem Id: 0x%x\n' % self.subsystem_id + str += 'Revision Id: 0x%x\n' % self.revision_id + if self.mfg: + str += 'Modem Function Group: 0x%x\n' % self.mfg + else: + str += 'No Modem Function Group found\n' + if not self.afg: return str + str += 'Default PCM:\n' + str += print_pcm_caps(self) + str += 'Default Amp-In caps: ' + str += print_amp_caps(self.amp_caps_in) + str += 'Default Amp-Out caps: ' + str += print_amp_caps(self.amp_caps_out) + + if self.base_nid == 0 or self.nodes < 0: + str += 'Invalid AFG subtree\n' + return str + + str += print_gpio(self) + + nid = self.base_nid + for i in range(self.nodes): + s, n = self.dump_node(nid) + str += s + nid += 1 + + return str + + def dump_node(self, nid): + + def print_pcm_rates(node): + s = '' + for i in node.pcm_rates: + s += " %d" % i + return " rates [0x%x]:%s\n" % (node.pcm_rate, s) + + def print_pcm_bits(node): + s = '' + for i in node.pcm_bits: + s += " %d" % i + return " bits [0x%x]:%s\n" % (node.pcm_bit, s) + + def print_pcm_formats(node): + str = " formats [0x%x]:" % node.pcm_stream + for i in node.pcm_streams: + str += " %s" % i + return str + "\n" + + def print_pcm_caps(node): + str = print_pcm_rates(node) + str += print_pcm_bits(node) + return str + print_pcm_formats(node) + + def print_audio_io(node): + str = " Converter: stream=%d, channel=%d\n" % (node.aud_stream, node.aud_channel) + if node.sdi_select != None: + str += " SDI-Select: %d\n" % node.sdi_select + return str + + def print_amp_caps(caps): + if caps.ofs == None: + return "N/A\n" + return "ofs=0x%02x, nsteps=0x%02x, stepsize=0x%02x, mute=%x\n" % \ + (caps.ofs, caps.nsteps, caps.stepsize, caps.mute and 1 or 0) + + def print_amp_vals(vals): + str = '' + idx = 0 + for val in vals.vals: + if vals.stereo and (idx & 1) == 0: + str += " [0x%02x" % val + else: + str += " 0x%02x" % val + if vals.stereo and (idx & 1) != 0: str += "]" + idx += 1 + return str + '\n' + + def print_pin_caps(node): + str = " Pincap 0x%08x:" % node.pincaps + if 'IN' in node.pincap: str += " IN" + if 'OUT' in node.pincap: str += " OUT" + if 'HP_DRV' in node.pincap: str += " HP" + if 'EAPD' in node.pincap: str += " EAPD" + if 'PRES_DETECT' in node.pincap: str += " Detect" + if 'BALANCE' in node.pincap: str += " Balance" + if 'HDMI' in node.pincap: + if (self.vendor_id >> 16) == 0x10ec: # Realtek has different meaning + str += " (Realtek)R/L" + else: + str += " HDMI" + if 'TRIG_REQ' in node.pincap: str += " Trigger" + if 'IMP_SENSE' in node.pincap: str += " ImpSense" + str += '\n' + if node.pincap_vref: + str += " Vref caps:" + if 'HIZ' in node.pincap_vref: str += " HIZ" + if '50' in node.pincap_vref: str += " 50" + if 'GRD' in node.pincap_vref: str += " GRD" + if '80' in node.pincap_vref: str += " 80" + if '100' in node.pincap_vref: str += " 100" + str += '\n' + if 'EAPD' in node.pincap: + str += " EAPD 0x%x:" % node.pincap_eapdbtls + if 'BALANCED' in node.pincap_eapdbtl: str += " BALANCED" + if 'EAPD' in node.pincap_eapdbtl: str += " EAPD" + if 'LR_SWAP' in node.pincap_eapdbtl: str += " R/L" + str += '\n' + str += " Pin Default 0x%08x: [%s] %s at %s %s\n" % (node.defcfg_pincaps, + node.jack_conn_name, + node.jack_type_name, + node.jack_location_name, + node.jack_location2_name) + str += " Conn = %s, Color = %s\n" % (node.jack_connector_name, + node.jack_color_name) + str += " DefAssociation = 0x%x, Sequence = 0x%x\n" % \ + (node.defcfg_assoc, node.defcfg_sequence) + if 'NO_PRESENCE' in node.defcfg_misc: + str += " Misc = NO_PRESENCE\n" + if node.pinctl: + str += " Pin-ctls: 0x%02x:" % node.pinctls + if 'IN' in node.pinctl: str += " IN" + if 'OUT' in node.pinctl: str += " OUT" + if 'HP' in node.pinctl: str += " HP" + if node.pincap_vref: + str += " VREF_%s" % node.pinctl_vref + str += '\n' + return str + + def print_vol_knob(node): + str = " Volume-Knob: delta=%d, steps=%d, " % (node.vol_knb_delta, node.vol_knb_steps) + return str + "direct=%d, val=%d\n" % (node.vol_knb_direct, node.vol_knb_val) + + def print_unsol_cap(node): + return " Unsolicited: tag=0x%02x, enabled=%d\n" % (node.unsol_tag, node.unsol_enabled and 1 or 0) + + def print_power_state(node): + return " Power: setting=%s, actual=%s\n" % (node.pwr_setting_name, node.pwr_actual_name) + + def print_digital_conv(node): + str = " Digital:" + if 'ENABLE' in node.dig1: str += " Enabled" + if 'VALIDITY' in node.dig1: str += " Validity" + if 'VALIDITYCFG' in node.dig1: str += " ValidityCfg" + if 'EMPHASIS' in node.dig1: str += " Preemphasis" + if 'COPYRIGHT' in node.dig1: str += " Copyright" + if 'NONAUDIO' in node.dig1: str += " Non-Audio" + if 'PROFESSIONAL' in node.dig1: str += " Pro" + if 'GENLEVEL' in node.dig1: str += " GetLevel" + str += "\n" + return str + " Digital category: 0x%x\n" % ((node.dig1_category >> 8) & 0x7f) + + def print_conn_list(node): + str = " Connection: %d\n" % (node.connections and len(node.connections) or 0) + if node.connections: + str += " " + for i in range(len(node.connections)): + str += " 0x%02x" % node.connections[i] + if i == node.active_connection and len(node.connections) > 1: + str += "*" + str += '\n' + return str + + def print_proc_caps(node): + return " Processing caps: benign=%d, ncoeff=%d\n" % (node.proc_benign, node.proc_nuncoef) + + def print_realtek_coef(node): + str = " Processing Coefficient: 0x%02x\n" % node.realtek_coeff_proc + return str + " Coefficient Index: 0x%02x\n" % node.realtek_coeff_index + + node = HDANode(self, nid) + str = "Node 0x%02x [%s] wcaps 0x%x:" % (nid, node.wtype_name(), node.wcaps) + if node.stereo: + str += node.channels == 2 and " Stereo" or " %d-Channels" % node.channels + else: + str += " Mono" + if node.digital: str += " Digital" + if node.in_amp: str += " Amp-In" + if node.out_amp: str += " Amp-Out" + if node.stripe: str += " Stripe" + if node.lr_swap: str += " R/L" + if node.cp_caps: str += " CP" + str += '\n' + if node.in_amp: + str += " Amp-In caps: " + str += print_amp_caps(node.amp_caps_in) + str += " Amp-In vals:" + str += print_amp_vals(node.amp_vals_in) + if node.out_amp: + str += " Amp-Out caps: " + str += print_amp_caps(node.amp_caps_out) + str += " Amp-Out vals:" + str += print_amp_vals(node.amp_vals_out) + + if node.wtype_id == 'PIN': + str += print_pin_caps(node) + elif node.wtype_id == 'VOL_KNB': + str += print_vol_knob(node) + elif node.wtype_id in ['AUD_IN', 'AUD_OUT']: + str += print_audio_io(node) + if node.digital: + str += print_digital_conv(node) + if node.format_ovrd: + str += " PCM:\n" + str += print_pcm_caps(node) + if node.unsol_cap: + str += print_unsol_cap(node) + if node.power: + str += print_power_state(node) + if node.wdelay: + str += " Delay: %d samples\n" % node.wdelay + if node.conn_list: + str += print_conn_list(node) + if node.wtype_id == 'PROC_WID': + str += print_proc_caps(node) + if hasattr(node, 'realtek_coeff_proc'): + str += print_realtek_coef(node) + return str, node + +if __name__ == '__main__': + v = HDACodec() + v.analyze_root_nodes() + print "vendor_id = 0x%x, subsystem_id = 0x%x, revision_id = 0x%x" % (v.vendor_id, v.subsystem_id, v.revision_id) + print "afg = %s, mfg = %s" % (v.afg and "0x%x" % v.afg or 'None', v.mfg and "0x%x" % v.mfg or 'None') + print + print + print v.dump()[:-1] diff --git a/hda-analyzer/hda_proc.py b/hda-analyzer/hda_proc.py new file mode 100644 index 0000000..0628a59 --- /dev/null +++ b/hda-analyzer/hda_proc.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python + +from dircache import listdir + +PROC_DIR = '/proc/asound' + +CODEC_TREE = {} + +def read_verbs2(card, codec): + CODEC_TREE[card][codec] = {} + info = {} + fp = open("%s/card%s/codec#%s" % (PROC_DIR, card, codec)) + src = fp.read(1024*1024) + CODEC_TREE[card][codec]['src'] = src + node = -1 + for line in src.split('\n'): + if line.startswith('Node '): + if node >= 0: + CODEC_TREE[card][codec][node] = data + data = line + '\n' + a = line.split(' ') + node = a[1].startswith('0x') and int(a[1][2:], 16) or int(a[1]) + elif node >= 0: + data += line + '\n' + if node >= 0: + CODEC_TREE[card][codec][node] = data + +def read_verbs1(card): + CODEC_TREE[card] = {} + for l in listdir('%s/card%s' % (PROC_DIR, card)): + if l.startswith('codec#') and l[6] >= '0' and l[6] <= '9': + read_verbs2(card, int(l[6:])) + +def read_verbs(): + for l in listdir(PROC_DIR): + if l.startswith('card') and l[4] >= '0' and l[4] <= '9': + read_verbs1(int(l[4:])) -- 2.47.1