From: Jaroslav Kysela Date: Tue, 10 Aug 2010 07:00:31 +0000 (+0200) Subject: initial commit - hda-analyzer + hda-verb-0.3 X-Git-Url: https://git.alsa-project.org/?a=commitdiff_plain;h=41f2cb11df30f9f449b9ca3bb361bb994d072b45;p=alsa-hda-tools.git initial commit - hda-analyzer + hda-verb-0.3 Signed-off-by: Jaroslav Kysela --- 41f2cb11df30f9f449b9ca3bb361bb994d072b45 diff --git a/hda-analyzer/hda_analyzer.py b/hda-analyzer/hda_analyzer.py new file mode 100755 index 0000000..5c78ca9 --- /dev/null +++ b/hda-analyzer/hda_analyzer.py @@ -0,0 +1,483 @@ +#!/usr/bin/env python +# +# Copyright (c) 2008-2010 by Jaroslav Kysela +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +""" +hda_analyzer - a tool to analyze HDA codecs widgets and connections + +Usage: hda_analyzer [[codec_proc] ...] + or: hda_analyzer --monitor + + codec_proc might specify multiple codec files per card: + codec_proc_file1+codec_proc_file2 + or codec_proc might be a dump from alsa-info.sh + or codec_proc might be a hash for codec database at www.alsa-project.org + or codec_proc might be a URL for codec dump or alsa-info.sh dump + + Monitor mode: check for codec changes in realtime and dump diffs. +""" + +import os +import sys +import gobject +import gtk +import pango + +from hda_codec import HDACodec, HDA_card_list, \ + EAPDBTL_BITS, PIN_WIDGET_CONTROL_BITS, \ + PIN_WIDGET_CONTROL_VREF, DIG1_BITS, GPIO_IDS, \ + HDA_INPUT, HDA_OUTPUT +from hda_proc import DecodeProcFile, DecodeAlsaInfoFile, HDACodecProc +from hda_guilib import * +from hda_graph import create_graph + +def gethttpfile(url, size=1024*1024): + from urllib import splithost + from httplib import HTTP + if not url.startswith('http:'): + raise ValueError, "URL %s" % url + host, selector = splithost(url[5:]) + h = HTTP(host) + h.putrequest('GET', url) + h.endheaders() + h.getreply() + res = h.getfile().read(size) + h.close() + return res + +def read_nodes2(card, codec): + try: + c = HDACodec(card, codec) + except OSError, msg: + if msg[0] == 13: + print "Codec %i/%i unavailable - permissions..." % (card, codec) + elif msg[0] == 16: + print "Codec %i/%i is busy..." % (card, codec) + elif msg[0] != 2: + print "Codec %i/%i access problem (%s)" % repr(msg) + return + c.analyze() + if not card in CODEC_TREE: + CODEC_TREE[card] = {} + DIFF_TREE[card] = {} + CODEC_TREE[card][c.device] = c + DIFF_TREE[card][c.device] = c.dump() + +def read_nodes3(card, codec, proc_file): + c = HDACodecProc(card, codec, proc_file) + c.analyze() + if not card in CODEC_TREE: + CODEC_TREE[card] = {} + DIFF_TREE[card] = {} + CODEC_TREE[card][c.device] = c + DIFF_TREE[card][c.device] = c.dump() + +def read_nodes(proc_files): + l = HDA_card_list() + for c in l: + for i in range(4): + read_nodes2(c.card, i) + card = 1000 + for f in proc_files: + a = f.split('+') + idx = 0 + if len(a) == 1: + if a[0].startswith('http://'): + proc_file = gethttpfile(a[0]) + elif len(a[0]) == 40 and not os.path.exists(a[0]): + url = 'http://www.alsa-project.org/db/?f=' + a[0] + print 'Downloading contents from %s' % url + proc_file = gethttpfile(url) + if not proc_file: + print "HASH %s cannot be downloaded..." % a[0] + continue + else: + print ' Success' + else: + proc_file = DecodeProcFile(a[0]) + proc_file = DecodeAlsaInfoFile(proc_file) + for i in proc_file: + read_nodes3(card, idx, i) + card += 1 + a = [] + for i in a: + proc_file = DecodeProcFile(i) + read_nodes3(card, idx, proc_file) + idx += 1 + card += 1 + cnt = 0 + for c in CODEC_TREE: + if len(CODEC_TREE[c]) > 0: + cnt += 1 + return cnt + +( + TITLE_COLUMN, + CARD_COLUMN, + CODEC_COLUMN, + NODE_COLUMN, + ITALIC_COLUMN +) = range(5) + +class HDAAnalyzer(gtk.Window): + info_buffer = None + node_window = None + codec = None + node = None + + def __init__(self): + gtk.Window.__init__(self) + self.connect('destroy', self.__destroy) + self.set_default_size(800, 400) + self.set_title(self.__class__.__name__) + self.set_border_width(10) + + self.tooltips = gtk.Tooltips() + + hbox = gtk.HBox(False, 3) + self.add(hbox) + + vbox = gtk.VBox(False, 0) + 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) + vbox.pack_start(scrolled_window) + hbox1 = gtk.HBox(False, 0) + button = gtk.Button("About") + button.connect("clicked", self.__about_clicked) + self.tooltips.set_tip(button, "README! Show the purpose of this program.") + hbox1.pack_start(button) + button = gtk.Button("Revert") + button.connect("clicked", self.__revert_clicked) + self.tooltips.set_tip(button, "Revert settings for selected codec.") + hbox1.pack_start(button) + button = gtk.Button("Diff") + button.connect("clicked", self.__diff_clicked) + self.tooltips.set_tip(button, "Show settings diff for selected codec.") + hbox1.pack_start(button) + button = gtk.Button("Graph") + button.connect("clicked", self.__graph_clicked) + self.tooltips.set_tip(button, "Show graph for selected codec.") + hbox1.pack_start(button) + vbox.pack_start(hbox1, False, False) + hbox.pack_start(vbox, False, False) + + self.notebook = gtk.Notebook() + hbox.pack_start(self.notebook, expand=True) + + self.node_window = gtk.Table() + self._new_notebook_page(self.node_window, '_Node editor') + + scrolled_window, self.info_buffer = self.__create_text(self.__dump_visibility) + self._new_notebook_page(scrolled_window, '_Text dump') + + self.show_all() + TRACKER.add(self) + + def __destroy(self, widget): + TRACKER.close(self) + + def simple_dialog(self, type, msg): + dialog = gtk.MessageDialog(self, + gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, + type, gtk.BUTTONS_OK, msg) + dialog.run() + dialog.destroy() + + def __about_clicked(self, button): + dialog = gtk.Dialog('About', self, + gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_OK, gtk.RESPONSE_OK)) + text_view = gtk.TextView() + text_view.set_border_width(4) + str = """\ +HDA Analyzer + +This tool allows change the HDA codec setting using direct hardware access +bypassing driver's mixer layer. + +To learn more about HDA (High Definition Audio), see +http://www.intel.com/standards/hdaudio/ for more details. + +Please, if you find how your codec work, send this information to alsa-devel +mailing list - http://www.alsa-project.org . + +Bugs, ideas, comments about this program should be sent to alsa-devel +mailing list, too. +""" + buffer = gtk.TextBuffer(None) + iter = buffer.get_iter_at_offset(0) + buffer.insert(iter, str[:-1]) + text_view.set_buffer(buffer) + text_view.set_editable(False) + text_view.set_cursor_visible(False) + dialog.vbox.pack_start(text_view, False, False) + dialog.show_all() + dialog.run() + dialog.destroy() + + def __revert_clicked(self, button): + if not self.codec: + msg = "Please, select a codec in left codec/node tree." + type = gtk.MESSAGE_WARNING + else: + self.codec.revert() + self.__refresh() + msg = "Setting for codec %s/%s (%s) was reverted!" % (self.codec.card, self.codec.device, self.codec.name) + type = gtk.MESSAGE_INFO + + self.simple_dialog(type, msg) + + def __diff_clicked(self, button): + if not self.codec: + self.simple_dialog(gtk.MESSAGE_WARNING, "Please, select a codec in left codec/node tree.") + return + dialog = gtk.Dialog('Diff', self, + gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_OK, gtk.RESPONSE_OK)) + text_view = gtk.TextView() + text_view.set_border_width(4) + fontName = get_fixed_font() + text_view.modify_font(fontName) + str = do_diff1(self.codec, DIFF_TREE[self.card][self.codec.device]) + if str == '': + str = 'No changes' + buffer = gtk.TextBuffer(None) + iter = buffer.get_iter_at_offset(0) + buffer.insert(iter, str[:-1]) + text_view.set_buffer(buffer) + text_view.set_editable(False) + text_view.set_cursor_visible(False) + dialog.vbox.pack_start(text_view, False, False) + dialog.show_all() + dialog.run() + dialog.destroy() + + def __graph_clicked(self, button): + if not self.codec: + self.simple_dialog(gtk.MESSAGE_WARNING, "Please, select a codec in left codec/node tree.") + return + create_graph(self.codec) + + def __refresh(self): + self.load() + self.__dump_visibility(None, None) + + def __dump_visibility(self, textview, event): + codec = self.codec + node = self.node + if not codec: + txt = 'Show some card info here...' + elif codec and self.node < 0: + txt = codec.dump(skip_nodes=True) + else: + n = codec.get_node(node) + txt = codec.dump_node(n) + 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) + + 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) + self.card = card + self.codec = None + if codec >= 0: + self.codec = CODEC_TREE[card][codec] + self.node = node + self.__refresh() + + def load(self): + codec = self.codec + node = self.node + n = None + if not codec: + txt = 'Show some card info here...' + elif codec and node < 0: + txt = codec.dump(skip_nodes=True) + else: + n = codec.get_node(node) + + for child in self.node_window.get_children()[:]: + self.node_window.remove(child) + child.destroy() + + if not n: + if not codec: + for i in CODEC_TREE[self.card]: + card = CODEC_TREE[self.card][i].mcard + break + self.node_window.add(NodeGui(card=card)) + elif codec: + self.node_window.add(NodeGui(codec=codec)) + else: + return + else: + self.node_window.add(NodeGui(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) + for nid in codec.nodes: + viter = model.append(citer) + node = codec.get_node(nid) + model.set(viter, + TITLE_COLUMN, 'Node[0x%02x] %s' % (nid, node.wtype_id), + 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('Nodes', 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, callback): + 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() + fontName = get_fixed_font() + text_view.modify_font(fontName) + 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.connect("visibility-notify-event", callback) + + text_view.set_wrap_mode(True) + + return scrolled_window, buffer + +def monitor(): + from time import sleep + print "Watching %s cards" % len(CODEC_TREE) + dumps = {} + while 1: + ok = False + for card in CODEC_TREE: + if not card in dumps: + dumps[card] = {} + for codec in CODEC_TREE[card]: + if not codec in dumps[card]: + dumps[card][codec] = '' + c = CODEC_TREE[card][codec] + if c.hwaccess: + ok = True + c.reread() + diff = '' + dump1 = c.dump() + if dumps[card][codec]: + diff = do_diff1(c, dumps[card][codec]) + dumps[card][codec] = dump1 + if diff: + print "======================================" + print diff + if not ok: + print "Nothing to monitor (no hwdep access)" + break + sleep(1) + +def main(argv): + cmd = None + if len(argv) > 1 and argv[1] in ('-h', '-help', '--help'): + print __doc__ % globals() + return 0 + if len(argv) > 1 and argv[1] in ('-m', '-monitor', '--monitor'): + cmd = 'monitor' + del argv[1] + if len(argv) > 1 and argv[1] in ('-g', '-graph', '--graph'): + cmd = 'graph' + del argv[1] + if read_nodes(sys.argv[1:]) == 0: + print "No HDA codecs were found or insufficient priviledges for " + print "/dev/snd/controlC* and /dev/snd/hwdepC*D* device files." + print + print "You may also check, if you compiled HDA driver with HWDEP" + print "interface as well or close all application using HWDEP." + print + print "Try run this program as root user." + return 0 + else: + if cmd == 'monitor': + monitor() + return 1 + if cmd == 'graph': + for card in CODEC_TREE: + for codec in CODEC_TREE: + create_graph(CODEC_TREE[card][codec]) + else: + HDAAnalyzer() + gtk.main() + return 1 + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/hda-analyzer/hda_codec.py b/hda-analyzer/hda_codec.py new file mode 100644 index 0000000..1036a56 --- /dev/null +++ b/hda-analyzer/hda_codec.py @@ -0,0 +1,1645 @@ +#!/usr/bin/env python +# +# Copyright (c) 2008-2010 by Jaroslav Kysela +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +import os +import struct +from fcntl import ioctl +from hda_mixer import AlsaMixer, AlsaMixerElem, AlsaMixerElemId + +def __ioctl_val(val): + # workaround for OverFlow bug in python 2.4 + if val & 0x80000000: + return -((val^0xffffffff)+1) + return val + +IOCTL_INFO = __ioctl_val(0x80dc4801) +IOCTL_PVERSION = __ioctl_val(0x80044810) +IOCTL_VERB_WRITE = __ioctl_val(0xc0084811) +IOCTL_GET_WCAPS = __ioctl_val(0xc0084812) + +CTL_IOCTL_CARD_INFO = __ioctl_val(0x81785501) + +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', + 'DP': 'Display Port', + 'HBR': 'Hight Bit Rate', +} + +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 +} + +POWER_STATES = ["D0", "D1", "D2", "D3", "D3cold", "S3D3cold", "CLKSTOP", "EPSS"] + +class HDAAmpCaps: + + def __init__(self, codec, nid, dir): + self.codec = codec + self.nid = nid + self.dir = dir + self.cloned = False + 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: + if self.dir == HDA_INPUT: + ccaps = self.codec.amp_caps_in + else: + ccaps = self.codec.amp_caps_out + if ccaps: + ccaps.clone(self) + else: + self.ofs = self.nsteps = self.stepsize = 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 + + def clone(self, ampcaps): + ampcaps.ofs = self.ofs + ampcaps.nsteps = self.nsteps + ampcaps.stepsize = self.stepsize + ampcaps.mute = self.mute + ampcaps.cloned = True + + def get_val_db(self, val): + if self.ofs is None: + return None + if val & 0x80: + return -999999 + range = (self.stepsize + 1) * 25 + off = -self.ofs * range + if val > self.nsteps: + db = off + self.nsteps * range + if val != 0 or self.nsteps != 0: + print "val > nsteps? for nid 0x%02x" % self.nid, val, self.nsteps + else: + db = off + val * range + return db + + def get_val_perc(self, val): + if self.ofs is None: + return None + if self.nsteps == 0: + return 0 + return (val * 100) / self.nsteps + + def get_val_str(self, val): + if self.ofs is None: + return "0x%02x" % val + else: + db = self.get_val_db(val & 0x7f) + res = val & 0x80 and "{mute-" or "{" + res += "0x%02x" % (val & 0x7f) + res += ":%02i.%idB" % (db / 100, db % 100) + res += ":%i%%}" % (self.get_val_perc(val & 0x7f)) + return res + +class HDAAmpVal: + + def __init__(self, codec, node, dir, caps): + self.codec = codec + self.node = node + self.dir = dir + if caps.ofs == None: + self.caps = dir == HDA_INPUT and codec.amp_caps_in or codec.amp_caps_out + else: + self.caps = caps + self.nid = node.nid + self.stereo = node.stereo + self.indices = 1 + self.origin_vals = None + 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: + changed = (self.vals[idx] & 0x80) == 0 + self.vals[idx] |= 0x80 + else: + changed = (self.vals[idx] & 0x80) == 0x80 + self.vals[idx] &= ~0x80 + self.__write_val(idx) + return changed + + def set_value(self, idx, val): + changed = (self.vals[idx] & 0x7f) != val + self.vals[idx] &= ~0x7f + self.vals[idx] |= val & 0x7f + self.__write_val(idx) + return changed + + 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) + if self.origin_vals == None: + self.origin_vals = self.vals[:] + + def revert(self): + self.vals = self.origin_vals[:] + for idx in range(len(self.vals)): + self.__write_val(idx) + + def get_val(self, idx): + if self.stereo: + return [self.vals[idx*2], self.vals[idx*2+1]] + return self.vals[idx] + + def get_val_db(self, idx): + vals = self.get_val(idx) + if type(vals) != type([]): + vals = [vals] + res = [] + for val in vals: + res.append(self.caps.get_val_db(val)) + return res + + def get_val_str(self, idx): + + def niceval(val): + return self.caps.get_val_str(val) + + if self.stereo: + return '[' + niceval(self.vals[idx*2]) + ' ' + niceval(self.vals[idx*2+1]) + ']' + return niceval(self.vals[idx]) + +class HDARootNode: + + def __init__(self, codec, _name): + self.codec = codec + self._name = _name + + def name(self): + return self._name + +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.origin_active_connection = None + self.origin_pwr = None + self.origin_digi1 = None + self.origin_pincap_eapdbtls = None + self.origin_pinctls = None + self.origin_vol_knb = None + self.origin_sdi_select = None + 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): + changed = False + if self.active_connection != None: + changed = self.active_connection != val + self.codec.rw(self.nid, VERBS['SET_CONNECT_SEL'], val) + self.active_connection = self.codec.rw(self.nid, VERBS['GET_CONNECT_SEL'], 0) + return changed + + 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 not self.wtype_id in ['AUD_MIX', 'VOL_KNB', 'POWER']: + self.active_connection = self.codec.rw(self.nid, VERBS['GET_CONNECT_SEL'], 0) + if self.origin_active_connection == None: + self.origin_active_connection = self.active_connection + 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, self.amp_caps_in) + 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, self.amp_caps_out) + 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') + if caps & (1 << 24): self.pincap.append('DP') # display port + if caps & (1 << 27): self.pincap.append('HBR') + 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) + if self.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: + pwr = self.codec.param_read(self.nid, PARAMS['POWER_STATE']) + self.pwr_state = pwr + self.pwr_states = [] + for a in range(len(POWER_STATES)): + if pwr & (1 << a): + self.pwr_states.append(POWER_STATES[a]) + pwr = self.codec.rw(self.nid, VERBS['GET_POWER_STATE'], 0) + self.pwr = pwr + if self.origin_pwr == None: + self.origin_pwr = pwr + self.pwr_setting = pwr & 0x0f + self.pwr_actual = (pwr >> 4) & 0x0f + self.pwr_setting_name = self.pwr_setting < 4 and POWER_STATES[self.pwr_setting] or "UNKNOWN" + self.pwr_actual_name = self.pwr_actual < 4 and POWER_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 + if self.origin_pincap_eapdbtls == None: + self.origin_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] + value = value and True or False + changed = (self.pincap_eapdbtls & mask) and not value or value + 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() + return changed + + def reread_pin_widget_control(self): + pinctls = self.codec.rw(self.nid, VERBS['GET_PIN_WIDGET_CONTROL'], 0) + self.pinctls = pinctls + if self.origin_pinctls == None: + self.origin_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] + value = value and True or False + changed = (self.pinctls & mask) and not value or value + if value: + self.pinctls |= mask + else: + self.pinctls &= ~mask + elif name == 'vref' and self.pincap_vref: + idx = PIN_WIDGET_CONTROL_VREF.index(value) + changed = (self.pinctls & 0x07) != idx + self.pinctls &= ~0x07 + self.pinctls |= idx + self.codec.rw(self.nid, VERBS['SET_PIN_WIDGET_CONTROL'], self.pinctls) + self.reread_pin_widget_control() + return changed + + def reread_vol_knb(self): + cap = self.codec.rw(self.nid, VERBS['GET_VOLUME_KNOB_CONTROL'], 0) + self.vol_knb = cap + if self.origin_vol_knb == None: + self.origin_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': + value = value and True or False + changed = (self.vol_knb & (1 << 7)) and not value or value + if value: + self.vol_knb |= (1 << 7) + else: + self.vol_knb &= ~(1 << 7) + elif name == 'value': + changed = (self.vol_knb & 0x7f) != 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() + return changed + + 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 + if self.origin_sdi_select == None: + self.origin_sdi_select = sdi + + def sdi_select_set_value(self, value): + changed = False + if self.sdi_select != None: + changed = (self.sdi_select & 0x0f) != value + self.sdi_select = value & 0x0f + self.codec.rw(self.nid, VERBS['SET_SDI_SELECT'], self.sdi_select) + self.reread_sdi_select() + return changed + + 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 + if self.origin_digi1 == None: + self.origin_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': + changed = ((self.digi1 >> 8) & 0x7f) != (value & 0x7f) + self.digi1 &= ~0x7f00 + self.digi1 |= (value & 0x7f) << 8 + self.codec.rw(self.nid, VERBS['SET_DIGI_CONVERT_2'], (self.digi1 >> 8) & 0xff) + else: + mask = 1 << DIG1_BITS[name] + value = value and True or False + changed = (self.digi1 & mask) and not value or value + if value: + self.digi1 |= mask + else: + self.digi1 &= ~mask + self.codec.rw(self.nid, VERBS['SET_DIGI_CONVERT_1'], self.digi1 & 0xff) + self.reread_dig1() + return changed + + def revert(self): + if self.origin_active_connection != None: + self.set_active_connection(self.origin_active_connection) + if self.origin_pwr != None: + self.codec.rw(self.nid, VERBS['SET_POWER_STATE'], self.origin_pwr) + if self.in_amp: + self.amp_vals_in.revert() + if self.out_amp: + self.amp_vals_out.revert() + if self.origin_pincap_eapdbtls != None: + self.codec.rw(self.nid, VERBS['SET_EAPD_BTLENABLE'], self.origin_pincap_eapdbtls) + if self.origin_vol_knb != None: + self.codec.rw(self.nid, VERBS['SET_VOLUME_KNOB_CONTROL'], self.origin_vol_knb) + if self.origin_sdi_select != None: + self.codec.rw(self.nid, VERBS['SET_SDI_SELECT'], self.origin_sdi_select) + if self.origin_digi1 != None: + self.codec.rw(self.nid, VERBS['SET_DIGI_CONVERT_1'], self.origin_digi1 & 0xff) + self.codec.rw(self.nid, VERBS['SET_DIGI_CONVERT_2'], (self.origin_digi1 >> 8) & 0xff) + self.reread() + + def get_device(self): + return self.codec.get_device(self.nid) + + def get_controls(self): + return self.codec.get_controls(self.nid) + + def get_mixercontrols(self): + if not self.codec.mixer: + return [] + ctls = self.get_controls() + res = [] + for ctl in ctls: + id = AlsaMixerElemId(name=ctl.name, index=ctl.index, device=ctl.device) + e = AlsaMixerElem(self.codec.mixer, id) + e.hdactl = ctl + res.append(e) + return res + + def get_conn_amp_vals_str(self, dst_node): + # return amp values for connection between this and dst_node + res = [] + if self.out_amp: + res.append(self.amp_vals_out.get_val_str(0)) + else: + res.append(None) + if dst_node.in_amp: + idx = 0 + if dst_node.amp_vals_in.indices == dst_node.connections: + if not self.nid in dst_node.connections: + raise ValueError, "nid 0x%02x is not connected to nid 0x%02x (%s, %s)" % (dst_node.nid, self.nid, repr(self.connections), repr(dst_node.connections)) + idx = dst_node.connections.index(self.nid) + res.append(dst_node.amp_vals_in.get_val_str(idx)) + else: + res.append(None) + return res + + def is_conn_active(self, dst_node): + # disabled route for PIN widgets + if self.wtype_id == 'PIN' and not 'IN' in self.pinctl: + return None + if dst_node.wtype_id == 'PIN' and not 'OUT' in dst_node.pinctl: + return None + res = [None, None] + if self.out_amp: + vals = self.amp_vals_out.get_val_db(0) + for idx in range(len(vals)): + if res[idx]: + res[idx] += vals[idx] + else: + res[idx] = vals[idx] + if dst_node.in_amp: + idx = 0 + if dst_node.amp_vals_in.indices == dst_node.connections: + if not self.nid in dst_node.connections: + raise ValueError, "nid 0x%02x is not connected to nid 0x%02x (%s, %s)" % (dst_node.nid, self.nid, repr(self.connections), repr(dst_node.connections)) + idx = dst_node.connections.index(self.nid) + vals = dst_node.amp_vals_in.get_val_db(idx) + for idx in range(len(vals)): + if res[idx]: + res[idx] += vals[idx] + else: + res[idx] = vals[idx] + if res[0] is None and res[1] is None: + return True + for r in res: + if r >= -1200: + return True + return False + +class HDAGPIO: + + def __init__(self, codec, nid): + self.codec = codec + self.nid = nid + self.originval = None + 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) + if self.originval == None: + self.originval = self.val.copy() + + def test(self, name, bit): + return (self.val[name] & (1 << bit)) and True or False + + def read(self, name): + self.val[name] = self.codec.rw(self.nid, GPIO_IDS[name][0], 0) + + def write(self, name): + self.codec.rw(self.nid, GPIO_IDS[name][1], self.val[name]) + self.read(name) + + 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 False + self.write(name) + return True + + def revert(self): + for i in GPIO_IDS: + self.val[i] = self.originval[i] + self.write(i) + +class HDACard: + + def __init__(self, card, ctl_fd=None): + self.card = card + if ctl_fd is None: + self.fd = ctl_fd = os.open("/dev/snd/controlC%i" % card, os.O_RDONLY) + else: + self.fd = os.dup(ctl_fd) + info = struct.pack('ii16s16s32s80s16s80s128s', 0, 0, '', '', '', '', '', '', '') + res = ioctl(ctl_fd, CTL_IOCTL_CARD_INFO, info) + a = struct.unpack('ii16s16s32s80s16s80s128s', res) + self.id = a[2].replace('\x00', '') + self.driver = a[3].replace('\x00', '') + self.name = a[4].replace('\x00', '') + self.longname = a[5].replace('\x00', '') + self.components = a[8].replace('\x00', '') + + def __del__(self): + if not self.fd is None: + os.close(self.fd) + +class HDACodec: + + afg = None + mfg = None + vendor_id = None + subsystem_id = None + revision_id = None + + def __init__(self, card=0, device=0, clonefd=None): + self.fd = None + self.hwaccess = True + ctl_fd = None + if type(1) == type(card): + self.device = device + self.card = card + self.mcard = HDACard(card) + ctl_fd = self.mcard.fd + else: + self.device = device + self.mcard = card + self.card = card.card + if not clonefd: + self.fd = os.open("/dev/snd/hwC%sD%s" % (card, device), os.O_RDWR) + else: + self.fd = os.dup(clonefd) + 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" + self.mixer = AlsaMixer(self.card, ctl_fd=ctl_fd) + self.parse_proc() + + def __del__(self): + if not self.fd is None: + os.close(self.fd) + + 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 revert(self): + if not self.gpio is None: + self.gpio.revert() + for nid in self.nodes: + self.nodes[nid].revert() + + def get_node(self, nid): + if nid == self.afg: + return HDARootNode(self, "Audio Root Node") + return self.nodes[nid] + + def parse_proc(self): + from hda_proc import HDACodecProc, DecodeProcFile + file = "/proc/asound/card%i/codec#%i" % (self.card, self.device) + if os.path.exists(file): + file = DecodeProcFile(file) + self.proc_codec = HDACodecProc(self.card, self.device, file) + else: + self.proc_codec = None + print "Unable to find proc file '%s'" % file + + def analyze(self): + self.afg = None + self.mfg = None + self.nodes = {} + self.gpio = None + self.afg_function_id = 0 # invalid + self.mfg_function_id = 0 # invalid + self.afg_unsol = 0 + self.mfg_unsol = 0 + 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']) + self.name = "0x%08x" % self.vendor_id # FIXME + self.pcm_rates = [] + self.pcm_bits = [] + self.pcm_streams = [] + self.amp_caps_in = None + self.amp_caps_out = None + self.gpio_max = 0 + self.gpio_o = 0 + self.gpio_i = 0 + self.gpio_unsol = 0 + self.gpio_wake = 0 + + 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_function_id = func & 0xff + self.afg_unsol = (func & 0x100) and True or False + self.afg = nid + elif (func & 0xff) == 0x02: # modem group + self.mfg_function_id = func & 0xff + self.mfg_unsol = (func & 0x100) and True or False + 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) + + # parse only audio function group + if self.afg == None: + return + + 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) + + nodes_count, nid = self.get_sub_nodes(self.afg) + self.base_nid = nid + for i in range(nodes_count): + self.nodes[nid] = HDANode(self, nid) + nid += 1 + + def reread(self): + if not self.gpio is None: + self.gpio.reread() + for node in self.nodes: + self.nodes[node].reread() + + 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, skip_nodes=False): + + 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() + str = 'Codec: %s\n' % self.name + str += 'Address: %i\n' % self.device + if not self.afg is None: + str += 'AFG Function Id: 0x%x (unsol %u)\n' % (self.afg_function_id, self.afg_unsol) + if not self.mfg is None: + str += 'MFG Function Id: 0x%x (unsol %u)\n' % (self.mfg_function_id, self.mfg_unsol) + 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 self.afg is None: 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) + + if not skip_nodes: + for i in self.nodes: + str += self.dump_node(self.nodes[i]) + + return str + + def dump_node(self, node): + + 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: + if 'HBR' in node.pincap: + str += " HBR" + str += " HDMI" + if 'DP' in node.pincap: str += " DP" + 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): + str = "" + if node.pwr_states: + str = " Power states: %s\n" % ' '.join(node.pwr_states) + 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_numcoef) + + 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 + + str = "Node 0x%02x [%s] wcaps 0x%x:" % (node.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' + str += self.dump_node_extra(node) + 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.proc_wid: + str += print_proc_caps(node) + if hasattr(node, 'realtek_coeff_proc'): + str += print_realtek_coef(node) + return str + + def dump_node_extra(self, node): + if self.proc_codec: + return self.proc_codec.dump_node_extra(node) + return '' + + def get_device(self, nid): + if self.proc_codec: + return self.proc_codec.get_device(nid) + return None + + def get_controls(self, nid): + if self.proc_codec: + return self.proc_codec.get_controls(nid) + return None + + def connections(self, nid, dir=0): + if dir == 0: + if nid in self.nodes: + conn = self.nodes[nid].connections + if conn: + return len(conn) + return 0 + res = 0 + for nid in self.nodes: + node = self.nodes[nid] + if nid != nid and node.connections and nid in node.connections: + res += 1 + return res + + def graph(self, dump=False, prefer_x=None, prefer_y=None): + + def mfind(nid): + for y in range(len(res)): + if nid in res[y]: + return (y, res[y].index(nid)) + return None, None + + def doplace(nid, y, x): + node = self.nodes[nid] + if node.wtype_id == 'AUD_MIX': + if y == 0: + y += 1 + while 1: + x += 1 + if x >= len(res[0]) - 1: + x = 1 + y += 1 + if y >= len(res) - 1: + return False + if res[y][x+1] is None and \ + res[y][x-1] is None and \ + res[y+1][x] is None and \ + res[y-1][x] is None and \ + res[y][x] is None: + res[y][x] = nid + return True + if y == 0: + for idx in range(len(res)-2): + if res[idx+1][x] is None: + res[idx+1][x] = nid + return True + elif y == len(res)-1: + for idx in reversed(range(len(res)-2)): + if res[idx+1][x] is None: + res[idx+1][x] = nid + return True + elif x == 0: + for idx in range(len(res[0])-2): + if res[y][idx+1] is None: + res[y][idx+1] = nid + return True + elif x == len(res)-1: + for idx in range(len(res[0])-2): + if res[y][idx+1] is None: + res[y][idx+1] = nid + return True + else: + if y+1 < len(res) and res[y+1][x] is None: + res[y+1][x] = nid + return True + if y-1 != 0 and res[y-1][x] is None: + res[y-1][x] = nid + return True + if x+1 < len(res[0]) and res[y][x+1] is None: + res[y][x+1] = nid + return True + if x-1 != 0 and res[y][x-1] is None: + res[y][x-1] = nid + return True + if y+1 < len(res): + return doplace(nid, y+1, 1) + if x+1 < len(res[0]): + return doplace(nid, 1, x+1) + return False + return None + + error = 0 + res = [] + unplaced = [] + # determine all termination widgets + terms = {'AUD_IN':[], 'AUD_OUT':[], 'PIN_IN':[], 'PIN_OUT':[]} + mixes = 0 + for nid in self.nodes: + node = self.nodes[nid] + if node.wtype_id == 'AUD_MIX': + mixes += 1 + if node.wtype_id in ['AUD_IN', 'AUD_OUT', 'PIN']: + id = node.wtype_id + if id == 'PIN': + id = 'IN' in node.pinctl and 'PIN_IN' or 'PIN_OUT' + terms[id].append(nid) + else: + unplaced.append(nid) + for id in terms: + terms[id].sort() + # build the matrix + if prefer_x: + x = prefer_x + else: + x = max(len(terms['AUD_IN']), len(terms['AUD_OUT'])) + 2 + if prefer_y: + y = prefer_y + else: + y = max(len(terms['PIN_IN']), len(terms['PIN_OUT'])) + 2 + if x <= 2 and y <= 2: + return None + while (x - 2) * (y - 2) < mixes * 9: + if x <= y: + x += 1 + else: + y += 1 + for a in range(y): + res.append([None]*x) + if 'PIN_IN' in terms: + for idx in range(len(terms['PIN_IN'])): + res[idx+1][0] = terms['PIN_IN'][idx] + if 'PIN_OUT' in terms: + for idx in range(len(terms['PIN_OUT'])): + res[idx+1][-1] = terms['PIN_OUT'][idx] + if 'AUD_IN' in terms: + idx = 1 + for nid in terms['AUD_IN']: + res[0][idx] = nid + idx += 1 + if 'AUD_OUT' in terms: + idx = 1 + for nid in terms['AUD_OUT']: + res[-1][idx] = nid + idx += 1 + # check and resize the matrix for unplaced nodes + while len(res)**len(res[0]) < len(unplaced): + res.insert(-2, [None]*x) + # assign unplaced nids - check connections + unplaced.sort() + while unplaced and not error: + change = len(unplaced) + for idx in range(len(res)): + for idx1 in range(len(res[idx])): + nid = res[idx][idx1] + if nid is None: + continue + node = self.nodes[nid] + if not node or not node.connections: + continue + for conn in node.connections: + if conn in unplaced: + pl = doplace(conn, idx, idx1) + if pl is True: + unplaced.remove(conn) + elif pl is None: + error += 1 + break + if error: + break + for nid in unplaced: + node = self.nodes[nid] + if not node.connections: + continue + for conn in node.connections: + placed = False + y, x = mfind(nid) + if not y or not x: + continue + pl = doplace(nid, y, x) + if pl is True: + unplaced.remove(nid) + break + elif pl is None: + error += 1 + break + if error: + break + if len(unplaced) == change: + break + y = len(res) + x = 0 + for nid in unplaced: + if y >= len(res): + res.append([None]*len(res[0])) + res[y][x] = nid + x += 1 + if x >= len(res[0]): + y += 1 + x = 0 + if error: + return self.graph(dump=dump, prefer_x=x+2, prefer_y=y+2) + # do extra check + check = [] + for y in range(len(res)): + for x in range(len(res[0])): + nid = res[y][x] + if not nid is None: + if nid in check: + raise ValueError, "double nid in graph matrix" + if not nid in self.nodes: + raise ValueError, "unknown nid in graph matrix" + check.append(nid) + if len(check) != len(self.nodes): + raise ValueError, "not all nids in graph matrix" + # do addition dump + if dump: + print "****", len(self.nodes) + for nodes in res: + str = '' + for node2 in nodes: + str += node2 is None and '-- ' or '%02x ' % node2 + print str + print "****" + return res + +def HDA_card_list(): + from dircache import listdir + result = [] + for name in listdir('/dev/snd/'): + if name.startswith('controlC'): + try: + fd = os.open("/dev/snd/%s" % name, os.O_RDONLY) + except OSError, msg: + continue + info = struct.pack('ii16s16s32s80s16s80s128s', 0, 0, '', '', '', '', '', '', '') + res = ioctl(fd, CTL_IOCTL_CARD_INFO, info) + a = struct.unpack('ii16s16s32s80s16s80s128s', res) + card = a[0] + components = a[8].replace('\x00', '') + if components.find('HDA:') >= 0: + result.append(HDACard(card, ctl_fd=fd)) + os.close(fd) + return result + +if __name__ == '__main__': + v = HDACodec() + v.analyze() + 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_graph.py b/hda-analyzer/hda_graph.py new file mode 100755 index 0000000..06b5c99 --- /dev/null +++ b/hda-analyzer/hda_graph.py @@ -0,0 +1,932 @@ +4#!/usr/bin/env python +# +# Copyright (c) 2008-2010 by Jaroslav Kysela +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +import gobject +import gtk +from gtk import gdk +import pango +import cairo +from hda_guilib import * + +from hda_codec import EAPDBTL_BITS, PIN_WIDGET_CONTROL_BITS, \ + PIN_WIDGET_CONTROL_VREF, DIG1_BITS, GPIO_IDS, \ + HDA_INPUT, HDA_OUTPUT + +GRAPH_WINDOWS = {} + +class DummyScrollEvent: + + def __init__(self, dir): + self.direction = dir + +class Node: + + def __init__(self, codec, node, x, y, nodesize, extra): + self.codec = codec + self.node = node + self.extra = extra + sx = sy = nodesize + self.myarea = [extra+x*(sx+extra), extra+y*(sy+extra), sx, sy] + self.src_routes = [] + self.dst_routes = [] + self.win = None + + def longdesc(self): + return "0x%02x" % self.node.nid + + def expose(self, cr, event, graph): + + width = self.myarea[2] + height = self.myarea[3] + + cr.select_font_face("Misc Fixed", + cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) + cr.set_font_size(14) + if graph.startnode == self: + cr.set_line_width(1.8) + cr.set_source_rgb(0, 0, 1) + elif graph.endnode == self: + cr.set_line_width(1.8) + cr.set_source_rgb(1, 0, 0) + else: + cr.set_line_width(0.8) + cr.set_source_rgb(0, 0, 0) + cr.rectangle(*self.myarea) + cr.stroke() + + cr.set_line_width(0.4) + cr.move_to(self.myarea[0]+5, self.myarea[1]+13) + cr.text_path("0x%02x: %s" % (self.node.nid, self.node.wtype_id)) + cr.stroke() + + cr.set_line_width(0.2) + cr.rectangle(self.myarea[0], self.myarea[1] + (height/4)*3, width/2, height/4) + cr.rectangle(self.myarea[0]+width/2, self.myarea[1] + (height/4)*3, width/2, height/4) + cr.stroke() + + cr.set_font_size(11) + cr.move_to(self.myarea[0]+20, self.myarea[1] + (height/4)*3+15) + cr.text_path('IN') + cr.stroke() + cr.move_to(self.myarea[0]+width/2+20, self.myarea[1] + (height/4)*3+15) + cr.text_path('OUT') + cr.stroke() + + def has_x(self, x): + x1 = self.myarea[0] + x2 = x1 + self.myarea[2] + return x >= x1 and x <= x2 + + def compressx(self, first, size): + if self.myarea[0] > first: + self.myarea[0] -= size + + def has_y(self, y): + y1 = self.myarea[1] + y2 = y1 + self.myarea[3] + return y >= y1 and y <= y2 + + def compressy(self, first, size): + if self.myarea[1] > first: + self.myarea[1] -= size + + def in_area(self, x, y): + if x >= self.myarea[0] and \ + y >= self.myarea[1] and \ + x < self.myarea[0] + self.myarea[2] and \ + y < self.myarea[1] + self.myarea[3]: + wherex = x - self.myarea[0] + wherey = y - self.myarea[1] + if wherey >= (self.myarea[3]/4) * 3: + if wherex >= self.myarea[2]/2: + return "dst" + else: + return "src" + else: + return "body" + + def mouse_move(self, x, y, graph): + what = self.in_area(x, y) + if not what is None: + if what == "dst": + for r in self.dst_routes: + r.highlight = True + elif what == "src": + for r in self.src_routes: + r.highlight = True + else: + graph.popup = self.codec.dump_node(self.node) + return True + +class Route: + + def __init__(self, codec, src_node, dst_node, routes, nodes): + self.codec = codec + self.src = src_node + self.dst = dst_node + self.lines = [] + self.wronglines = [] + self.analyze_routes(routes, nodes) + src_node.dst_routes.append(self) + dst_node.src_routes.append(self) + self.highlight = False + self.marked = False + + def shortdesc(self): + return "0x%02x->0x%02x" % (self.src.node.nid, self.dst.node.nid) + + def longdesc(self): + src = self.src.node + dst = self.dst.node + return "%s 0x%02x -> %s 0x%02x" % (src.wtype_id.replace('_', '-'), + src.nid, dst.wtype_id.replace('_', '-'), dst.nid) + + def statusdesc(self): + + def niceprint(prefix, val): + if val is None: + return prefix + return ' ' + prefix + ' ' + val + + src = self.src.node + dst = self.dst.node + vals = src.get_conn_amp_vals_str(dst) + src = "%s 0x%02x" % (src.wtype_id.replace('_', '-'), src.nid) + dst = "%s 0x%02x" % (dst.wtype_id.replace('_', '-'), dst.nid) + res = niceprint("SRC " + src, vals[0]) + ' -> ' + \ + niceprint("DST " + dst, vals[1]) + return res + + def expose(self, cr, event): + width = self.src.myarea[2] + height = self.src.myarea[3] + + if 0: # direct green lines + cr.set_line_width(0.3) + cr.set_source_rgb(0.2, 1.0, 0.2) + cr.move_to(self.src.myarea[0]+(width/4)*3, self.src.myarea[1]+height) + cr.line_to(self.dst.myarea[0]+width/4, self.dst.myarea[1]+height) + cr.stroke() + + for line in self.lines: + if self.marked: + cr.set_line_width(1.8) + cr.set_source_rgb(1, 0, 1) + elif self.highlight: + cr.set_line_width(1.5) + cr.set_source_rgb(1, 0, 0) + else: + inactive = self.src.node.is_conn_active(self.dst.node) + if inactive is None: + cr.set_line_width(0.35) + cr.set_source_rgb(0, 0, 0) + elif inactive is False: + cr.set_line_width(0.35) + cr.set_source_rgb(0, 0, 1) + else: + cr.set_line_width(1.5) + cr.set_source_rgb(0, 0, 1) + cr.move_to(line[0], line[1]) + cr.line_to(line[2], line[3]) + cr.stroke() + + for line in self.wronglines: + cr.set_line_width(1.5) + cr.set_source_rgb(1, 0, 0) + cr.move_to(line[0], line[1]) + cr.line_to(line[2], line[3]) + cr.stroke() + + def select_line(self, routes, nodes, possible): + + def check_dot(posx, posy, line): + if posx == line[0] and posx == line[2]: + if line[1] < line[3]: + if posy >= line[1] and posy <= line[3]: + #print "Clash1", posx, posy, line + return True + else: + if posy >= line[3] and posy <= line[1]: + #print "Clash2", posx, posy, line + return True + if posy == line[1] and posy == line[3]: + if line[0] < line[2]: + if posx >= line[0] and posx <= line[2]: + #print "Clash3", posx, posy, line + return True + else: + if posx >= line[2] and posx <= line[0]: + #print "Clash4", posx, posy, line + return True + if posx == line[0] and posy == line[1]: + #print "Clash5", posx, posy, line + return True + if posx == line[2] and posy == line[3]: + #print "Clash6", posx, posy, line + return True + return False + + for p in possible: + found = False + for route in routes: + if found: + break + for line in route.lines: + if check_dot(line[0], line[1], p) or \ + check_dot(line[2], line[3], p) or \ + check_dot(p[0], p[1], line) or \ + check_dot(p[2], p[3], line): + #print "Found1", p + found = True + break + if nodes and not found: + x1, y1, x2, y2 = p + if x1 > x2 or y1 > y2: + x2, y2, x1, y1 = p + for node in nodes: + xx1, yy1, xx2, yy2 = node.myarea + xx2 += xx1 + yy2 += yy1 + if x1 < xx2 and x2 >= xx1 and y1 < yy2 and y2 >= yy1: + #print "Found2", x1, y1, x2, y2, xx1, yy1, xx2, yy2 + found = True + break + if not found: + #print "OK x1=%s,y1=%s,x2=%s,y2=%s" % (p[0], p[1], p[2], p[3]) + return p + + def analyze_routes(self, routes, nodes): + posx, posy, width, height = self.src.myarea + dposx, dposy, dwidth, dheight = self.dst.myarea + extra = self.src.extra + + possible = [] + startx = posx >= dposx and posx - extra or posx + width + xrange = range(5, extra-1, 5) + if posx >= dposx: + xrange.reverse() + a = range(width+extra+5, width+extra*2-1, 5) + a.reverse() + xrange = xrange + a + for i in range(2, 10): + a = range(width*i+extra*i+5, width*i+extra*(i+1)-1, 5) + a.reverse() + xrange = xrange + a + else: + xrange += range(width+extra+5, width+extra*2-1, 5) + for i in range(2, 10): + xrange += range(width*i+extra*i+5, width*i+extra*(i+1)-1, 5) + for j in xrange: + possible.append([startx + j, posy + height + 5, + startx + j, dposy + height + 5]) + sel = self.select_line(routes, None, possible) + if not sel: + raise ValueError, "unable to route" + + self.lines.append(sel) + + def finish(self, routes, nodes): + + if not self.lines: + return + + posx, posy, width, height = self.src.myarea + dposx, dposy, dwidth, dheight = self.dst.myarea + extra = self.src.extra + sel = self.lines[0] + res = True + + x = posx+(width/2) + y = posy+height + for tryit in range(3): + possible = [] + fixup = sel[0] > posx and -1 or 1 + r = range(tryit*extra, (tryit+1)*extra-5-1, 5) + if tryit == 2: + r = range(-height-extra+5, -height-5, 5) + r.reverse() + x1 = x + 5 + fixup + x2 = sel[0] - fixup + if x1 > x2: + sub = width/2 + x1 = x + sub + fixup + sub -= 5 + else: + sub = 0 + for i in range(tryit*extra, (tryit+1)*extra-5-1, 5): + possible.append([x1, sel[1]+i, x2, sel[1]+i]) + sel1 = self.select_line(routes, nodes, possible) + if sel1: + sel1[0] -= fixup + sub + sel1[2] += fixup + possible = [] + for j in range(0, width/2-10, 5): + possible.append([sel1[0]+j, y, sel1[0]+j, sel1[1]]) + sel2 = self.select_line(routes, nodes, possible) + if sel2: + sel1[0] = sel2[0] + self.lines[0][1] = sel1[1] + self.lines.append(sel1) + self.lines.append(sel2) + tryit = -1 + break + if tryit >= 0: + self.wronglines.append([x+5, y, sel[0], sel[1]]) + print "[1] displaced route 0x%x->0x%x %s %s" % (self.src.node.nid, self.dst.node.nid, repr(self.lines[-1]), repr(sel)) + res = False + + x = dposx + y = dposy+height + for tryit in range(3): + possible = [] + fixup = sel[2] > posx and -1 or 1 + r = range(tryit * extra, (tryit+1)*extra-5-1, 5) + if tryit == 2: + r = range(-height-extra+5, -height-5, 5) + r.reverse() + sub = width/2 + x1 = x + sub + fixup + x2 = sel[2] - fixup + if x1 < x2: + x1 = x + 5 + fixup + sub = 0 + else: + sub -= 5 + for i in r: + possible.append([x1, sel[3]+i, x2, sel[3]+i]) + sel1 = self.select_line(routes, nodes, possible) + if sel1: + sel1[0] -= fixup + sub + sel1[2] += fixup + possible = [] + for j in range(0, width/2-10, 5): + possible.append([sel1[0]+j, y, sel1[0]+j, sel1[1]]) + sel2 = self.select_line(routes, nodes, possible) + if sel2: + sel1[0] = sel2[0] + self.lines[0][3] = sel1[3] + self.lines.append(sel1) + self.lines.append(sel2) + tryit = -1 + break + if tryit >= 0: + self.wronglines.append([x+5, y, sel[2], sel[3]]) + print "[2] displaced route 0x%x->0x%x %s %s" % (self.src.node.nid, self.dst.node.nid, repr(self.lines[-1]), repr(sel)) + res = False + + return res + + def has_x(self, x): + for line in self.lines: + if line[0] == x or line[2] == x: + return True + return False + + def compressx(self, first, size): + idx = 0 + while idx < len(self.lines): + line = self.lines[idx] + if line[0] > first: + line[0] -= size + self.lines[idx] = line + if line[2] > first: + line[2] -= size + self.lines[idx] = line + idx += 1 + + def has_y(self, y): + for line in self.lines: + if line[1] == y or line[3] == y: + return True + return False + + def compressy(self, first, size): + idx = 0 + while idx < len(self.lines): + line = self.lines[idx] + if line[1] > first: + line[1] -= size + self.lines[idx] = line + if line[3] > first: + line[3] -= size + self.lines[idx] = line + idx += 1 + + def in_area(self, x, y): + for line in self.lines: + x1, y1, x2, y2 = line + if x1 > x2 or y1 > y2: + x2, y2, x1, y1 = line + if x1 == x2 and abs(x1 - x) < 3: + if y1 <= y and y2 >= y: + return True + elif y1 == y2 and abs(y1 - y) < 3: + if x1 <= x and x2 >= x: + return True + + def mouse_move(self, x, y, graph): + if self.in_area(x, y): + self.highlight = True + return True + +class CodecGraphLayout(gtk.Layout): + + def __init__(self, adj1, adj2, codec, mytitle, statusbar): + gtk.Layout.__init__(self, adj1, adj2) + self.set_events(0) + self.add_events(gtk.gdk.EXPOSURE_MASK | gtk.gdk.POINTER_MOTION_MASK | + gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | + gtk.gdk.LEAVE_NOTIFY_MASK | gtk.gdk.SCROLL_MASK) + self.expose_handler = self.connect("expose-event", self.expose) + self.click_handler = self.connect("button-press-event", self.button_click) + self.release_handler = self.connect("button-release-event", self.button_release) + self.motion_handler = self.connect("motion-notify-event", self.mouse_move) + self.mouse_leave_handler = self.connect("leave-notify-event", self.mouse_leave) + self.scroll_me_handler = self.connect("scroll-event", self.scroll_me) + + self.popup_win = None + self.popup = None + self.statusbar = statusbar + + self.codec = codec + self.mytitle = mytitle + self.graph = codec.graph(dump=False) + self.startnode = None + self.endnode = None + + self.changed_handler = HDA_SIGNAL.connect("hda-node-changed", self.hda_node_changed) + + ok = False + for extra in [150, 200, 300]: + if self.build(extra): + ok = True + break + break + if not ok: + print "Not all routes are placed correctly!!!" + + def __destroy(self, widget): + if self.popup_win: + self.popup_win.destroy() + + def __build(self, extra=50): + self.nodes = [] + self.routes = [] + maxconns = 0 + for nid in self.codec.nodes: + node = self.codec.nodes[nid] + conns = max(self.codec.connections(nid, 0), + self.codec.connections(nid, 1)) + if conns > maxconns: + maxconns = conns + nodesize = max((maxconns * 5 + 10) * 2, 100) + if self.graph: + for y in range(len(self.graph)): + for x in range(len(self.graph[0])): + nid = self.graph[y][x] + if not nid is None: + node = self.codec.nodes[nid] + w = Node(self.codec, node, x, y, nodesize, extra) + self.nodes.append(w) + sx = len(self.graph[0])*(nodesize+extra)+extra + sy = len(self.graph)*(nodesize+extra)+extra + self.set_size(sx, sy) + total = 0 + for node in self.nodes: + if not node.node.connections: + continue + for conn in node.node.connections: + for node1 in self.nodes: + if node1.node.nid == conn: + total += 1 + break + total *= 2 + total += 1 + position = 0 + for node in self.nodes: + if not node.node.connections: + continue + for conn in node.node.connections: + for node1 in self.nodes: + if node1.node.nid == conn: + r = Route(self.codec, node1, node, self.routes, self.nodes) + self.routes.append(r) + position += 1 + self.pdialog.set_fraction(float(position) / total) + break + res = True + for route in self.routes: + if not route.finish(self.routes, self.nodes): + res = False + break + position += 1 + self.pdialog.set_fraction(float(position) / total) + if not res: + return + # final step - optimize drawings + while 1: + size = self.compressx(sx) + if not size: + break + sx -= size + position += 1 + self.pdialog.set_fraction(float(position) / total) + while 1: + size = self.compressy(sy) + if not size: + break + sy -= size + self.set_size(sx, sy) + return res + + def build(self, extra=50): + self.pdialog = SimpleProgressDialog("Rendering routes") + self.pdialog.show_all() + res = self.__build(extra) + self.pdialog.destroy() + self.pdialog = None + return res + + def expose(self, widget, event): + if not self.flags() & gtk.REALIZED: + return + + # background + cr = self.bin_window.cairo_create() + cr.set_source_rgb(1.0, 1.0, 1.0) + cr.rectangle(event.area.x, event.area.y, + event.area.width, event.area.height) + cr.clip() + cr.paint() + + # draw nodes + for node in self.nodes: + node.expose(cr, event, self) + + # draw routes + for route in self.routes: + route.expose(cr, event) + + def compressx(self, sx): + first = None + for a in range(15, sx, 5): + found = False + for node in self.nodes: + if node.has_x(a): + found = True + break + if not found: + for route in self.routes: + if route.has_x(a): + found = True + break + if not found: + if first is None: + first = a + last = a + elif first is not None: + size = (last - first) + 5 + for node in self.nodes: + node.compressx(first, size) + for route in self.routes: + route.compressx(first, size) + return size + return None + + def compressy(self, sy): + first = None + for a in range(15, sy, 5): + found = False + for node in self.nodes: + if node.has_y(a): + found = True + break + if not found: + for route in self.routes: + if route.has_y(a): + found = True + break + if not found: + if first is None: + first = a + last = a + elif first is not None: + size = (last - first) + 5 + for node in self.nodes: + node.compressy(first, size) + for route in self.routes: + route.compressy(first, size) + return size + return None + + def hda_node_changed(self, obj, widget, node): + if widget != self: + self.queue_draw() + + def find_node(self, event): + for node in self.nodes: + what = node.in_area(event.x, event.y) + if not what is None: + return (node, what) + return (None, None) + + def find_route(self, event): + for route in self.routes: + if route.in_area(event.x, event.y): + return route + + def show_popup(self, event): + screen_width = gtk.gdk.screen_width() + screen_height = gtk.gdk.screen_height() + + if self.popup_win: + self.popup_win.destroy() + self.popup_win = gtk.Window(gtk.WINDOW_POPUP) + label = gtk.Label() + label.modify_font(get_fixed_font()) + label.set_text(self.popup) + self.popup_win.add(label) + self.popup_win.move(screen_width + 10, screen_height + 10) + self.popup_win.show_all() + popup_width, popup_height = self.popup_win.get_size() + + #rootwin = self.get_screen().get_root_window() + #x, y, mods = rootwin.get_pointer() + + pos_x = screen_width - popup_width + if pos_x < 0: + pos_x = 0 + pos_y = screen_height - popup_height + if pos_y < 0: + pox_y = 0 + + self.popup_win.move(int(pos_x), int(pos_y)) + #self.popup_win.show_all() + + def mouse_move(self, widget, event): + oldpopup = self.popup + self.popup = None + redraw = False + found = False + for route in self.routes: + if route.highlight: + redraw = True + route.highlight = False + for node in self.nodes: + if node.mouse_move(event.x, event.y, self): + self.statusbar.pop(1) + self.statusbar.push(1, node.longdesc()) + found = redraw = True + break + if not found: + for route in self.routes: + if route.mouse_move(event.x, event.y, self): + self.statusbar.pop(1) + self.statusbar.push(1, route.statusdesc()) + found = redraw = True + break + if not found: + self.statusbar.pop(1) + if redraw: + self.queue_draw() + if self.popup: + if oldpopup != self.popup: + self.show_popup(event) + else: + if self.popup_win: + self.popup_win.destroy() + self.popup_win = None + + def mouse_leave(self, widget, data=None): + for route in self.routes: + if route.highlight: + redraw = True + route.highlight = False + if self.popup_win: + self.popup_win.destroy() + self.popup_win = None + + def mark_it(self, widget, node, what, enable): + if what == "start": + if enable: + if not self.startnode: + self.startnode = node + self.queue_draw() + else: + if self.startnode: + self.startnode = None + self.queue_draw() + elif what == "end": + if enable: + if not self.endnode: + self.endnode = node + self.queue_draw() + else: + if self.endnode: + self.endnode = None + self.queue_draw() + + def mark_route(self, widget, route, what, enable): + if what == "mark": + if enable: + if not route.marked: + route.marked = enable + self.queue_draw() + else: + if route.marked: + route.marked = False + self.queue_draw() + + def node_win_destroy(self, widget, node): + TRACKER.close(node.win) + node.win = None + + def open_node(self, widget, node): + if self.popup_win: + self.popup_win.destroy() + if not node.win: + win = gtk.Window() + win.set_default_size(500, 600) + gui = NodeGui(node=node.node) + win.set_title(self.mytitle + ' ' + gui.mytitle) + win.add(gui) + win.connect("destroy", self.node_win_destroy, node) + win.show_all() + node.win = win + TRACKER.add(win) + else: + node.win.present() + + def button_click(self, widget, event): + if event.button != 3: + if event.button == 8: + self.scroll_me(self, DummyScrollEvent(gtk.gdk.SCROLL_LEFT)) + elif event.button == 9: + self.scroll_me(self, DummyScrollEvent(gtk.gdk.SCROLL_RIGHT)) + return False + node, what = self.find_node(event) + m = None + if node: + m = gtk.Menu() + i = gtk.MenuItem("Open") + i.connect("activate", self.open_node, node) + i.show() + m.append(i) + if what in ["src", "dst"]: + routes1 = node.src_routes + text = "Mark Route From" + if what == "dst": + routes1 = node.dst_routes + text = "Mark Route To" + routes = [] + for route in routes1: + if not route.marked: + routes.append(route) + if routes: + i = None + if len(routes) == 1: + i = gtk.MenuItem(text + ' ' + routes[0].longdesc()) + i.connect("activate", self.mark_route, routes[0], "mark", True) + else: + menu = gtk.Menu() + for route in routes: + i = gtk.MenuItem(route.longdesc()) + i.connect("activate", self.mark_route, route, "mark", True) + i.show() + menu.append(i) + i = gtk.MenuItem(text) + i.set_submenu(menu) + if i: + i.show() + m.append(i) + if what in ["src", "dst"]: + routes1 = node.src_routes + text = "Unmark Route From" + if what == "dst": + routes1 = node.dst_routes + text = "Unmark Route To" + routes = [] + for route in routes1: + if route.marked: + routes.append(route) + if routes: + i = None + if len(routes) == 1: + i = gtk.MenuItem(text + ' ' + routes[0].longdesc()) + i.connect("activate", self.mark_route, routes[0], "mark", False) + else: + menu = gtk.Menu() + for route in routes: + i = gtk.MenuItem(route.longdesc()) + i.connect("activate", self.mark_route, route, "mark", False) + i.show() + menu.append(i) + i = gtk.MenuItem(text) + i.set_submenu(menu) + if i: + i.show() + m.append(i) + if not self.startnode: + i = gtk.MenuItem("Mark as start point") + i.connect("activate", self.mark_it, node, "start", True) + else: + i = gtk.MenuItem("Clear start point") + i.connect("activate", self.mark_it, None, "start", False) + i.show() + m.append(i) + if not self.endnode: + i = gtk.MenuItem("Mark as finish point") + i.connect("activate", self.mark_it, node, "end", True) + else: + i = gtk.MenuItem("Clear finish point") + i.connect("activate", self.mark_it, None, "end", False) + i.show() + m.append(i) + else: + route = self.find_route(event) + if route: + m = gtk.Menu() + if not route.marked: + i = gtk.MenuItem("Mark selected route %s" % route.shortdesc()) + i.connect("activate", self.mark_route, route, "mark", True) + else: + i = gtk.MenuItem("Clear selected route %s" % route.shortdesc()) + i.connect("activate", self.mark_route, route, "mark", False) + i.show() + m.append(i) + if m: + m.popup(None, None, None, event.button, event.time, None) + return False + + def button_release(self, widget, event): + pass + + def scroll_me(self, widget, event): + if event.direction == gtk.gdk.SCROLL_UP: + adj = self.get_vadjustment() + adj.set_value(adj.get_value()-40) + elif event.direction == gtk.gdk.SCROLL_DOWN: + adj = self.get_vadjustment() + adj.set_value(adj.get_value()+40) + elif event.direction == gtk.gdk.SCROLL_LEFT: + adj = self.get_hadjustment() + adj.set_value(adj.get_value()-40) + elif event.direction == gtk.gdk.SCROLL_RIGHT: + adj = self.get_hadjustment() + adj.set_value(adj.get_value()+40) + +gobject.type_register(CodecGraphLayout) + +class CodecGraph(gtk.Window): + + def __init__(self, codec): + gtk.Window.__init__(self) + self.codec = codec + self.connect('destroy', self.__destroy) + self.set_default_size(800, 600) + self.set_title(self.__class__.__name__ + ' ' + self.codec.name) + self.set_border_width(0) + + table = gtk.Table(2, 3, False) + self.add(table) + + statusbar = gtk.Statusbar() + self.layout = CodecGraphLayout(None, None, codec, self.get_title(), statusbar) + table.attach(self.layout, 0, 1, 0, 1, gtk.FILL|gtk.EXPAND, + gtk.FILL|gtk.EXPAND, 0, 0) + vScrollbar = gtk.VScrollbar(None) + table.attach(vScrollbar, 1, 2, 0, 1, gtk.FILL|gtk.SHRINK, + gtk.FILL|gtk.SHRINK, 0, 0) + hScrollbar = gtk.HScrollbar(None) + table.attach(hScrollbar, 0, 1, 1, 2, gtk.FILL|gtk.SHRINK, + gtk.FILL|gtk.SHRINK, 0, 0) + vAdjust = self.layout.get_vadjustment() + vScrollbar.set_adjustment(vAdjust) + hAdjust = self.layout.get_hadjustment() + hScrollbar.set_adjustment(hAdjust) + table.attach(statusbar, 0, 2, 2, 3, gtk.FILL|gtk.SHRINK, + gtk.FILL|gtk.SHRINK, 0, 0) + self.show_all() + GRAPH_WINDOWS[codec] = self + TRACKER.add(self) + + def __destroy(self, widget): + del GRAPH_WINDOWS[self.codec] + TRACKER.close(self) + +def create_graph(codec): + if codec in GRAPH_WINDOWS: + GRAPH_WINDOWS[codec].present() + else: + CodecGraph(codec) diff --git a/hda-analyzer/hda_guilib.py b/hda-analyzer/hda_guilib.py new file mode 100644 index 0000000..dbe81f3 --- /dev/null +++ b/hda-analyzer/hda_guilib.py @@ -0,0 +1,874 @@ +#!/usr/bin/env python +# +# Copyright (c) 2008-2010 by Jaroslav Kysela +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +import gobject +import gtk +import pango + +from hda_codec import HDACodec, HDA_card_list, \ + EAPDBTL_BITS, PIN_WIDGET_CONTROL_BITS, \ + PIN_WIDGET_CONTROL_VREF, DIG1_BITS, GPIO_IDS, \ + HDA_INPUT, HDA_OUTPUT + +DIFF_FILE = "/tmp/hda-analyze.diff" + +CODEC_TREE = {} +DIFF_TREE = {} + +def get_fixed_font(): + return pango.FontDescription("Misc Fixed,Courier Bold 9") + +class HDASignal(gobject.GObject): + + def __init__(self): + self.__gobject_init__() + +gobject.signal_new("hda-codec-changed", HDASignal, + gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,gobject.TYPE_PYOBJECT)) +gobject.signal_new("hda-node-changed", HDASignal, + gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,gobject.TYPE_PYOBJECT)) + +HDA_SIGNAL = HDASignal() + +def do_diff1(codec, diff1): + from difflib import unified_diff + diff = unified_diff(diff1.split('\n'), codec.dump().split('\n'), n=8, lineterm='') + diff = '\n'.join(list(diff)) + if len(diff) > 0: + diff = 'Diff for codec %i/%i (%s):\n' % (codec.card, codec.device, codec.name) + diff + return diff + +def do_diff(): + diff = '' + hw = 0 + for card in CODEC_TREE: + for codec in CODEC_TREE[card]: + c = CODEC_TREE[card][codec] + if c.hwaccess: + hw += 1 + diff += do_diff1(c, DIFF_TREE[card][codec]) + if len(diff) > 0: + open(DIFF_FILE, "w+").write(diff + '\n') + print "Diff was stored to: %s" % DIFF_FILE + return (diff and hw > 0) and True or False + +class NodeGui(gtk.ScrolledWindow): + + def __init__(self, card=None, codec=None, node=None, doframe=False): + gtk.ScrolledWindow.__init__(self) + self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.set_shadow_type(gtk.SHADOW_IN) + self.read_all = self.__read_all_none + self.node = None + self.codec = None + self.popups = [] + self.tooltips = gtk.Tooltips() + if card and not codec and not node: + self.__build_card(card, doframe) + elif codec and not card and not node: + self.__build_codec(codec, doframe) + elif node and not card and not codec: + self.__build_node(node, doframe) + self.connect("destroy", self.__destroy) + self.codec_changed = HDA_SIGNAL.connect("hda-codec-changed", self.hda_codec_changed) + self.node_changed = HDA_SIGNAL.connect("hda-node-changed", self.hda_node_changed) + self.read_all() + self.show_all() + + def __destroy(self, widget): + HDA_SIGNAL.handler_disconnect(self.codec_changed) + HDA_SIGNAL.handler_disconnect(self.node_changed) + + def __read_all_none(self): + pass + + def hda_codec_changed(self, obj, widget, codec): + if widget != self: + if self.read_all and self.codec == codec: + self.read_all() + + def hda_node_changed(self, obj, widget, node): + if widget != self: + if self.read_all and self.node == node: + self.read_all() + + def show_popup(self, text): + screen_width = gtk.gdk.screen_width() + screen_height = gtk.gdk.screen_height() + + popup_win = gtk.Window(gtk.WINDOW_POPUP) + popup_win.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0xffff, 0xd700, 0)) + frame = gtk.Frame() + popup_win.add(frame) + label = gtk.Label() + label.modify_font(get_fixed_font()) + if text[-1] == '\n': + text = text[:-1] + label.set_text(text) + frame.add(label) + popup_win.move(screen_width + 10, screen_height + 10) + popup_win.show_all() + popup_width, popup_height = popup_win.get_size() + + rootwin = self.get_screen().get_root_window() + x, y, mods = rootwin.get_pointer() + + pos_x = x - popup_width/2 + if pos_x < 0: + pos_x = 0 + if pos_x + popup_width > screen_width: + pos_x = screen_width - popup_width + pos_y = y + 16 + if pos_y < 0: + pox_y = 0 + if pos_y + popup_height > screen_height: + pos_y = screen_height - popup_height + + popup_win.move(int(pos_x), int(pos_y)) + return popup_win + + def __popup_motion_notify(self, widget, event=None): + for popup in self.popups: + if popup[1] == widget and not popup[0]: + popup[0] = self.show_popup(popup[2](*popup[3])) + + def __popup_leave_notify(self, widget, event=None): + for popup in self.popups: + if popup[1] == widget and popup[0]: + popup[0].destroy() + popup[0] = None + + def make_popup(self, widget, gettext, data): + widget.connect("motion-notify-event", self.__popup_motion_notify) + widget.connect("leave-notify-event", self.__popup_leave_notify) + self.popups.append([None, widget, gettext, data]) + + def __create_text(self, callback): + 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() + fontName = get_fixed_font() + text_view.modify_font(fontName) + 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.connect("visibility-notify-event", callback) + + text_view.set_wrap_mode(True) + + return scrolled_window, buffer + + def __new_text_view(self, text=None): + text_view = gtk.TextView() + text_view.set_border_width(4) + fontName = get_fixed_font() + text_view.modify_font(fontName) + if not text is None: + buffer = gtk.TextBuffer(None) + iter = buffer.get_iter_at_offset(0) + if text[-1] == '\n': + text = text[:-1] + buffer.insert(iter, text) + text_view.set_buffer(buffer) + text_view.set_editable(False) + text_view.set_cursor_visible(False) + return text_view + + def __build_node_caps(self, node): + frame = gtk.Frame('Node Caps') + frame.set_border_width(4) + if len(node.wcaps_list) == 0: + return frame + str = '' + for i in node.wcaps_list: + str += node.wcap_name(i) + '\n' + frame.add(self.__new_text_view(text=str)) + return frame + + def __node_connection_toggled(self, widget, row, data): + model, node = data + if not model[row][0]: + if node.set_active_connection(int(row)): + HDA_SIGNAL.emit("hda-node-changed", self, node) + 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 and node.connections: + model = gtk.ListStore( + gobject.TYPE_BOOLEAN, + gobject.TYPE_STRING + ) + idx = 0 + for i in node.connections: + iter = model.append() + node1 = node.codec.get_node(node.connections[idx]) + model.set(iter, 0, node.active_connection == idx, + 1, node1.name()) + idx += 1 + self.connection_model = model + treeview = gtk.TreeView(model) + treeview.set_rules_hint(True) + treeview.get_selection().set_mode(gtk.SELECTION_SINGLE) + treeview.set_size_request(300, 30 + len(node.connections) * 25) + renderer = gtk.CellRendererToggle() + renderer.set_radio(True) + if not node.active_connection is 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("Source 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() + if vals.set_mute(idx, val): + HDA_SIGNAL.emit("hda-node-changed", self, vals.node) + button.set_active(vals.vals[idx] & 0x80) + + def __amp_value_changed(self, adj, data): + caps, vals, idx = data + val = int(adj.get_value()) + if vals.set_value(idx, val): + HDA_SIGNAL.emit("hda-node-changed", self, vals.node) + adj.set_value(vals.vals[idx] & 0x7f) + + def __ctl_mute_toggled(self, adj, data): + ctl, idx = data + + def __ctl_value_changed(self, adj, data): + ctl, idx = data + + def __popup_show_ctl(self, ctl, idx): + return ctl.get_text_info(idx - ctl.hdactl.amp_idx) + + def __build_amps(self, node): + + def build_caps(title, caps, vals): + if caps and caps.cloned: + title += ' (Global)' + frame = gtk.Frame(title) + frame.set_border_width(4) + vbox = gtk.VBox(False, 0) + if caps: + str = 'Offset: %d\n' % caps.ofs + str += 'Number of steps: %d\n' % caps.nsteps + str += 'Step size: %d\n' % caps.stepsize + str += 'Mute: %s\n' % (caps.mute and "True" or "False") + vbox.pack_start(self.__new_text_view(text=str), True, True, 0) + idx = 0 + frame1 = None + vbox1 = None + self.amp_checkbuttons[caps.dir] = [] + self.amp_adjs[caps.dir] = [] + self.mixer_elems[caps.dir] = [] + ctls = node.get_mixercontrols() + 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.connect("toggled", self.__amp_mute_toggled, (caps, vals, idx)) + self.amp_checkbuttons[caps.dir].append(checkbutton) + hbox.pack_start(checkbutton, False, False) + else: + self.amp_checkbuttons[caps.dir].append(None) + if caps.stepsize > 0: + adj = gtk.Adjustment((val & 0x7f) % (caps.nsteps+1), 0.0, caps.nsteps+1, 1.0, 1.0, 1.0) + scale = gtk.HScale(adj) + scale.set_digits(0) + scale.set_value_pos(gtk.POS_RIGHT) + adj.connect("value_changed", self.__amp_value_changed, (caps, vals, idx)) + self.amp_adjs[caps.dir].append(adj) + hbox.pack_start(scale, True, True) + else: + self.amp_adjs[caps.dir].append(None) + sep = False + for ctl in ctls: + if ctl.hdactl.amp_index_match(idx): + if ctl.stype == 'boolean': + if not sep: + hbox.pack_start(gtk.VSeparator(), False, False) + hbox.pack_start(gtk.Label('CTLIFC'), False, False) + sep = True + checkbutton = gtk.CheckButton('Mute') + checkbutton.connect("toggled", self.__ctl_mute_toggled, (ctl, idx)) + self.make_popup(checkbutton, self.__popup_show_ctl, (ctl, idx)) + hbox.pack_start(checkbutton, False, False) + for ctl in ctls: + if ctl.hdactl.amp_index_match(idx): + if ctl.stype.startswith('integer'): + if not sep: + hbox.pack_start(gtk.VSeparator(), False, False) + sep = True + adj = gtk.Adjustment(0, ctl.min, ctl.max, ctl.step, ctl.step, ctl.step) + scale = gtk.HScale(adj) + scale.set_digits(0) + scale.set_value_pos(gtk.POS_RIGHT) + adj.connect("value_changed", self.__ctl_value_changed, (ctl, idx)) + self.make_popup(scale, self.__popup_show_ctl, (ctl, 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 + + self.amp_checkbuttons = {} + self.amp_adjs = {} + self.mixer_elems = {} + 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 + if node.eapdbtl_set_value(name, button.get_active()): + HDA_SIGNAL.emit("hda-node-changed", self, node) + button.set_active(name in node.pincap_eapdbtl) + + def __pinctls_toggled(self, button, data): + node, name = data + if node.pin_widget_control_set_value(name, button.get_active()): + HDA_SIGNAL.emit("hda-node-changed", self, node) + button.set_active(name in node.pinctl) + + 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: + if node.pin_widget_control_set_value('vref', name): + HDA_SIGNAL.emit("hda-node-changed", self, node) + break + idx1 += 1 + idx = idx1 = 0 + for name in PIN_WIDGET_CONTROL_VREF: + if name == node.pinctl_vref: + combobox.set_active(idx1) + break + if name != None: + 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) + str = '' + for i in node.pincap: + str += node.pincap_name(i) + '\n' + for i in node.pincap_vref: + str += 'VREF_%s\n' % i + frame.add(self.__new_text_view(text=str)) + vbox.pack_start(frame) + if 'EAPD' in node.pincap: + frame = gtk.Frame('EAPD') + frame.set_border_width(4) + vbox1 = gtk.VBox(False, 0) + self.pincap_eapdbtls_checkbuttons = [] + for name in EAPDBTL_BITS: + checkbutton = gtk.CheckButton(name) + checkbutton.connect("toggled", self.__pincap_eapdbtl_toggled, (node, name)) + self.pincap_eapdbtls_checkbuttons.append(checkbutton) + 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) + str = 'Jack connection: %s\n' % node.jack_conn_name + str += 'Jack type: %s\n' % node.jack_type_name + str += 'Jack location: %s\n' % node.jack_location_name + str += 'Jack location2: %s\n' % node.jack_location2_name + str += 'Jack connector: %s\n' % node.jack_connector_name + str += 'Jack color: %s\n' % node.jack_color_name + if 'NO_PRESENCE' in node.defcfg_misc: + str += 'No presence\n' + frame.add(self.__new_text_view(text=str)) + vbox.pack_start(frame) + + frame = gtk.Frame('Widget Control') + frame.set_border_width(4) + vbox1 = gtk.VBox(False, 0) + self.pin_checkbuttons = [] + for name in PIN_WIDGET_CONTROL_BITS: + checkbutton = gtk.CheckButton(name) + checkbutton.connect("toggled", self.__pinctls_toggled, (node, name)) + self.pin_checkbuttons.append(checkbutton) + vbox1.pack_start(checkbutton, False, False) + if node.pincap_vref: + combobox = gtk.combo_box_new_text() + for name in PIN_WIDGET_CONTROL_VREF: + if name: + combobox.append_text(name) + combobox.connect("changed", self.__pinctls_vref_change, node) + self.pincap_vref_combobox = combobox + 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_mix(self, node): + hbox = gtk.HBox(False, 0) + return hbox + + def __sdi_select_changed(self, adj, node): + val = int(adj.get_value()) + if node.sdi_select_set_value(val): + HDA_SIGNAL.emit("hda-node-changed", self, node) + adj.set_value(node.sdi_select) + + def __dig1_toggled(self, button, data): + node, name = data + val = button.get_active() + if node.dig1_set_value(name, val): + HDA_SIGNAL.emit("hda-node-changed", self, node) + button.set_active(name in node.dig1) + + def __dig1_category_activate(self, entry, node): + val = entry.get_text() + if val.lower().startswith('0x'): + val = int(val[2:], 16) + else: + try: + val = int(val) + except: + print "Unknown category value '%s'" % val + return + if node.dig1_set_value('category', val): + HDA_SIGNAL.emit("hda-node-changed", self, node) + entry.set_text("0x%02x" % node.dig1_category) + + def __build_aud(self, node): + vbox = gtk.VBox(False, 0) + + frame = gtk.Frame('Converter') + frame.set_border_width(4) + str = 'Audio Stream:\t%s\n' % node.aud_stream + str += 'Audio Channel:\t%s\n' % node.aud_channel + if node.format_ovrd: + str += 'Rates:\t\t%s\n' % node.pcm_rates[:6] + if len(node.pcm_rates) > 6: + str += '\t\t\t\t%s\n' % node.pcm_rates[6:] + str += 'Bits:\t\t%s\n' % node.pcm_bits + str += 'Streams:\t%s\n' % node.pcm_streams + else: + str += 'Global Rates:\t%s\n' % node.codec.pcm_rates[:6] + if len(node.codec.pcm_rates) > 6: + str += '\t\t%s\n' % node.codec.pcm_rates[6:] + str += 'Global Bits:\t%s\n' % node.codec.pcm_bits + str += 'Global Streams:\t%s\n' % node.codec.pcm_streams + frame.add(self.__new_text_view(text=str)) + vbox.pack_start(frame) + + if not node.sdi_select is None: + hbox1 = gtk.HBox(False, 0) + frame = gtk.Frame('SDI Select') + adj = gtk.Adjustment(node.sdi_select, 0.0, 16.0, 1.0, 1.0, 1.0) + self.sdi_select_adj = adj + scale = gtk.HScale(adj) + scale.set_digits(0) + scale.set_value_pos(gtk.POS_LEFT) + scale.set_size_request(200, 16) + adj.connect("value_changed", self.__sdi_select_changed, node) + frame.add(scale) + hbox1.pack_start(frame, False, False) + vbox.pack_start(hbox1, False, False) + + if node.digital: + hbox1 = gtk.HBox(False, 0) + frame = gtk.Frame('Digital Converter') + vbox1 = gtk.VBox(False, 0) + self.digital_checkbuttons = [] + for name in DIG1_BITS: + checkbutton = gtk.CheckButton(name) + checkbutton.connect("toggled", self.__dig1_toggled, (node, name)) + self.digital_checkbuttons.append(checkbutton) + vbox1.pack_start(checkbutton, False, False) + frame.add(vbox1) + hbox1.pack_start(frame) + frame = gtk.Frame('Digital Converter Category') + entry = gtk.Entry() + self.dig_category_entry = entry + entry.set_width_chars(4) + entry.connect("activate", self.__dig1_category_activate, node) + frame.add(entry) + hbox1.pack_start(frame) + vbox.pack_start(hbox1, False, False) + + return vbox + + def __build_device(self, device): + vbox = gtk.VBox(False, 0) + frame = gtk.Frame('Device') + frame.set_border_width(4) + hbox = gtk.HBox(False, 0) + s = 'name=' + str(device.name) + ', type=' + \ + str(device.type) + ', device=' + str(device.device) + label = gtk.Label(s) + hbox.pack_start(label, False, False) + frame.add(hbox) + vbox.pack_start(frame) + return vbox + + def __build_controls(self, ctrls): + vbox = gtk.VBox(False, 0) + frame = gtk.Frame('Controls') + frame.set_border_width(4) + vbox1 = gtk.VBox(False, 0) + for ctrl in ctrls: + hbox1 = gtk.HBox(False, 0) + vbox1.pack_start(hbox1, False, False) + s = 'name=' + str(ctrl.name) + ', index=' + str(ctrl.index) + \ + ', device=' + str(ctrl.device) + label = gtk.Label(s) + hbox1.pack_start(label, False, False) + if ctrl.amp_chs: + hbox1 = gtk.HBox(False, 0) + vbox1.pack_start(hbox1, False, False) + s = ' chs=' + str(ctrl.amp_chs) + ', dir=' + str(ctrl.amp_dir) + \ + ', idx=' + str(ctrl.amp_idx) + ', ofs=' + str(ctrl.amp_ofs) + label = gtk.Label(s) + hbox1.pack_start(label, False, False) + frame.add(vbox1) + vbox.pack_start(frame) + return vbox + + def __build_proc(self, node): + frame = gtk.Frame('Processing Caps') + frame.set_border_width(4) + str = 'benign=%i\nnumcoef=%i\n' % (node.proc_benign, node.proc_numcoef) + frame.add(self.__new_text_view(text=str)) + return frame + + def __read_all_node(self): + node = self.node + if node.wtype_id in ['AUD_IN', 'AUD_OUT']: + if not node.sdi_select is None: + self.sdi_select_adj.set_value(node.sdi_select) + if node.digital: + idx = 0 + for name in DIG1_BITS: + checkbutton = self.digital_checkbuttons[idx] + checkbutton.set_active(node.digi1 & (1 << DIG1_BITS[name])) + idx += 1 + self.dig_category_entry.set_text("0x%x" % node.dig1_category) + elif node.wtype_id == 'PIN': + if 'EAPD' in node.pincap: + idx = 0 + for name in EAPDBTL_BITS: + checkbutton = self.pincap_eapdbtls_checkbuttons[idx] + checkbutton.set_active(node.pincap_eapdbtls & (1 << EAPDBTL_BITS[name])) + idx += 1 + idx = 0 + for name in PIN_WIDGET_CONTROL_BITS: + checkbutton = self.pin_checkbuttons[idx] + checkbutton.set_active(node.pinctls & (1 << PIN_WIDGET_CONTROL_BITS[name])) + idx += 1 + idx = active = 0 + for name in PIN_WIDGET_CONTROL_VREF: + if name == node.pinctl_vref: active = idx + if name: idx += 1 + if node.pincap_vref: + self.pincap_vref_combobox.set_active(active) + a = [] + if node.in_amp: + a.append((HDA_INPUT, node.amp_caps_in, node.amp_vals_in)) + if node.out_amp: + a.append((HDA_OUTPUT, node.amp_caps_out, node.amp_vals_out)) + for dir, caps, vals in a: + for idx in range(len(vals.vals)): + val = vals.vals[idx] + checkbutton = self.amp_checkbuttons[dir][idx] + if checkbutton: + checkbutton.set_active(val & 0x80 and True or False) + adj = self.amp_adjs[dir][idx] + if adj: + adj.set_value((val & 0x7f) % (caps.nsteps+1)) + idx += 1 + if hasattr(self, 'connection_model'): + for r in self.connection_model: + r[0] = False + idx = 0 + for r in self.connection_model: + r[0] = node.active_connection == idx + idx += 1 + + def __build_node(self, node, doframe=False): + self.node = node + self.mytitle = node.name() + if doframe: + mframe = gtk.Frame(self.mytitle) + mframe.set_border_width(4) + else: + mframe = gtk.Table() + + vbox = gtk.VBox(False, 0) + dev = node.get_device() + if not dev is None: + vbox.pack_start(self.__build_device(dev), False, False) + ctrls = node.get_controls() + if ctrls: + vbox.pack_start(self.__build_controls(ctrls), False, False) + 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) + if node.in_amp or node.out_amp: + vbox.pack_start(self.__build_amps(node), False, False) + if node.wtype_id == 'PIN': + vbox.pack_start(self.__build_pin(node), False, False) + elif node.wtype_id in ['AUD_IN', 'AUD_OUT']: + vbox.pack_start(self.__build_aud(node), False, False) + else: + if not node.wtype_id in ['AUD_MIX', 'BEEP', 'AUD_SEL']: + print 'Node type %s has no GUI support' % node.wtype_id + if node.proc_wid: + vbox.pack_start(self.__build_proc(node), False, False) + + mframe.add(vbox) + self.add_with_viewport(mframe) + + self.read_all = self.__read_all_node + + def __build_codec_info(self, codec): + vbox = gtk.VBox(False, 0) + + frame = gtk.Frame('Codec Identification') + frame.set_border_width(4) + str = 'Audio Fcn Group: %s\n' % (codec.afg and "0x%02x" % codec.afg or "N/A") + if codec.afg: + str += 'AFG Function Id: 0x%02x (unsol %u)\n' % (codec.afg_function_id, codec.afg_unsol) + str += 'Modem Fcn Group: %s\n' % (codec.mfg and "0x%02x" % codec.mfg or "N/A") + if codec.mfg: + str += 'MFG Function Id: 0x%02x (unsol %u)\n' % (codec.mfg_function_id, codec.mfg_unsol) + str += 'Vendor ID:\t 0x%08x\n' % codec.vendor_id + str += 'Subsystem ID:\t 0x%08x\n' % codec.subsystem_id + str += 'Revision ID:\t 0x%08x\n' % codec.revision_id + frame.add(self.__new_text_view(text=str)) + vbox.pack_start(frame, False, False) + + frame = gtk.Frame('PCM Global Capabilities') + frame.set_border_width(4) + str = 'Rates:\t\t %s\n' % codec.pcm_rates[:6] + if len(codec.pcm_rates) > 6: + str += '\t\t %s\n' % codec.pcm_rates[6:] + str += 'Bits:\t\t %s\n' % codec.pcm_bits + str += 'Streams:\t %s\n' % codec.pcm_streams + frame.add(self.__new_text_view(text=str)) + vbox.pack_start(frame, False, False) + + return vbox + + def __build_codec_amps(self, codec): + + def build_caps(title, caps): + frame = gtk.Frame(title) + frame.set_border_width(4) + if caps and caps.ofs != None: + str = 'Offset:\t\t %d\n' % caps.ofs + str += 'Number of steps: %d\n' % caps.nsteps + str += 'Step size:\t %d\n' % caps.stepsize + str += 'Mute:\t\t %s\n' % (caps.mute and "True" or "False") + frame.add(self.__new_text_view(text=str)) + return frame + + hbox = gtk.HBox(False, 0) + c = build_caps('Global Input Amplifier Caps', codec.amp_caps_in) + hbox.pack_start(c) + c = build_caps('Global Output Amplifier Caps', codec.amp_caps_out) + hbox.pack_start(c) + + return hbox + + def __gpio_toggled(self, button, (codec, id, idx)): + if codec.gpio.set(id, idx, button.get_active()): + HDA_SIGNAL.emit("hda-codec-changed", self, codec) + button.set_active(codec.gpio.test(id, idx)) + + def __build_codec_gpio(self, codec): + frame = gtk.Frame('GPIO') + frame.set_border_width(4) + hbox = gtk.HBox(False, 0) + str = 'IO Count: %d\n' % codec.gpio_max + str += 'O Count: %d\n' % codec.gpio_o + str += 'I Count: %d\n' % codec.gpio_i + str += 'Unsolicited: %s\n' % (codec.gpio_unsol and "True" or "False") + str += 'Wake: %s\n' % (codec.gpio_wake and "True" or "False") + hbox.pack_start(self.__new_text_view(text=str), False, False) + frame.add(hbox) + self.gpio_checkbuttons = [] + for id in GPIO_IDS: + id1 = id == 'direction' and 'out-dir' or id + frame1 = gtk.Frame(id1) + frame1.set_border_width(4) + vbox1 = gtk.VBox(False, 0) + self.gpio_checkbuttons.append([]) + for i in range(codec.gpio_max): + checkbutton = checkbutton = gtk.CheckButton('[%d]' % i) + checkbutton.connect("toggled", self.__gpio_toggled, (codec, id, i)) + self.gpio_checkbuttons[-1].append(checkbutton) + vbox1.pack_start(checkbutton, False, False) + frame1.add(vbox1) + hbox.pack_start(frame1, False, False) + return frame + + def __read_all_codec(self): + idx = 0 + for id in GPIO_IDS: + for i in range(self.codec.gpio_max): + self.gpio_checkbuttons[idx][i].set_active(self.codec.gpio.test(id, i)) + idx += 1 + + def __build_codec(self, codec, doframe=False): + self.codec = codec + self.mytitle = codec.name + if doframe: + mframe = gtk.Frame(self.mytitle) + mframe.set_border_width(4) + else: + mframe = gtk.Table() + + vbox = gtk.VBox(False, 0) + vbox.pack_start(self.__build_codec_info(codec), False, False) + vbox.pack_start(self.__build_codec_amps(codec), False, False) + vbox.pack_start(self.__build_codec_gpio(codec), False, False) + mframe.add(vbox) + self.add_with_viewport(mframe) + self.read_all = self.__read_all_codec + + def __build_card_info(self, card): + str = 'Card: %s\n' % card.card + str += 'Id: %s\n' % card.id + str += 'Driver: %s\n' % card.driver + str += 'Name: %s\n' % card.name + str += 'LongName: %s\n' % card.longname + return self.__new_text_view(text=str) + + def __build_card(self, card, doframe=False): + self.mytitle = card.name + if doframe: + mframe = gtk.Frame(self.mytitle) + mframe.set_border_width(4) + else: + mframe = gtk.Table() + + vbox = gtk.VBox(False, 0) + vbox.pack_start(self.__build_card_info(card), False, False) + mframe.add(vbox) + self.add_with_viewport(mframe) + +class SimpleProgressDialog(gtk.Dialog): + + def __init__(self, title): + gtk.Dialog.__init__(self, title, None, gtk.DIALOG_MODAL, None) + self.set_deletable(False) + + box = self.get_child() + + box.pack_start(gtk.Label(), False, False, 0) + self.progressbar = gtk.ProgressBar() + box.pack_start(self.progressbar, False, False, 0) + + def set_fraction(self, fraction): + self.progressbar.set_fraction(fraction) + while gtk.events_pending(): + gtk.main_iteration_do(False) + +class TrackWindows: + + def __init__(self): + self.windows = [] + + def add(self, win): + if not win in self.windows: + self.windows.append(win) + + def close(self, win): + if win in self.windows: + self.windows.remove(win) + if not self.windows: + self.do_diff(win) + gtk.main_quit() + + def do_diff(self, widget): + if do_diff(): + dialog = gtk.MessageDialog(widget, + gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, + gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, + "HDA-Analyzer: Would you like to revert\n" + "settings for all HDA codecs?") + response = dialog.run() + dialog.destroy() + + if response == gtk.RESPONSE_YES: + for card in CODEC_TREE: + for codec in CODEC_TREE[card]: + CODEC_TREE[card][codec].revert() + print "Settings for all codecs were reverted..." + +TRACKER = TrackWindows() diff --git a/hda-analyzer/hda_mixer.py b/hda-analyzer/hda_mixer.py new file mode 100644 index 0000000..af12d5c --- /dev/null +++ b/hda-analyzer/hda_mixer.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python +# +# Copyright (c) 2008-2010 by Jaroslav Kysela +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +import os +import struct +from fcntl import ioctl + +def __ioctl_val1(val): + # workaround for OverFlow bug in python 2.4 + if val & 0x80000000: + return -((val^0xffffffff)+1) + return val + +CTL_IOCTL_CARD_INFO = __ioctl_val1(0x81785501) +CTL_IOCTL_ELEM_INFO = __ioctl_val1(0xc1105511) +CTL_IOCTL_ELEM_READ = __ioctl_val1(0xc4c85512) +CTL_IOCTL_ELEM_WRITE = __ioctl_val1(0xc4c85513) + +CTL_ELEM_TYPE_BOOLEAN = 1 +CTL_ELEM_TYPE_INTEGER = 2 +CTL_ELEM_TYPE_ENUMERATED = 3 +CTL_ELEM_TYPE_BYTES = 4 +CTL_ELEM_TYPE_IEC958 = 5 +CTL_ELEM_TYPE_INTEGER64 = 6 + +CTL_ELEM_TYPEs = { + 'boolean': CTL_ELEM_TYPE_BOOLEAN, + 'integer': CTL_ELEM_TYPE_INTEGER, + 'enumerated': CTL_ELEM_TYPE_ENUMERATED, + 'bytes':CTL_ELEM_TYPE_BYTES, + 'iec958':CTL_ELEM_TYPE_IEC958, + 'integer64':CTL_ELEM_TYPE_INTEGER64 +} +CTL_ELEM_RTYPEs = {} +for i in CTL_ELEM_TYPEs: + CTL_ELEM_RTYPEs[CTL_ELEM_TYPEs[i]] = i + +CTL_ELEM_IFACE_MIXER = 2 +CTL_ELEM_IFACEs = { + "mixer": 2, +} +CTL_ELEM_RIFACEs = {} +for i in CTL_ELEM_IFACEs: + CTL_ELEM_RIFACEs[CTL_ELEM_IFACEs[i]] = i + +CTL_ELEM_ACCESS_READ = (1<<0) +CTL_ELEM_ACCESS_WRITE = (1<<1) +CTL_ELEM_ACCESS_VOLATILE = (1<<2) +CTL_ELEM_ACCESS_TIMESTAMP = (1<<3) +CTL_ELEM_ACCESS_TLV_READ = (1<<4) +CTL_ELEM_ACCESS_TLV_WRITE = (1<<5) +CTL_ELEM_ACCESS_TLV_COMMAND = (1<<6) +CTL_ELEM_ACCESS_INACTIVE = (1<<8) +CTL_ELEM_ACCESS_LOCK = (1<<9) +CTL_ELEM_ACCESS_OWNER = (1<<10) + +CTL_ELEM_ACCESSs = { + 'read': CTL_ELEM_ACCESS_READ, + 'write': CTL_ELEM_ACCESS_WRITE, + 'volatile': CTL_ELEM_ACCESS_VOLATILE, + 'timestamp': CTL_ELEM_ACCESS_TIMESTAMP, + 'tlv_read': CTL_ELEM_ACCESS_TLV_READ, + 'tlv_write': CTL_ELEM_ACCESS_TLV_WRITE, + 'tlv_command': CTL_ELEM_ACCESS_TLV_COMMAND, + 'inactive': CTL_ELEM_ACCESS_INACTIVE, + 'lock': CTL_ELEM_ACCESS_LOCK, + 'owner': CTL_ELEM_ACCESS_OWNER +} + +UINTSIZE = len(struct.pack("I", 0)) +LONGSIZE = len(struct.pack("l", 0)) +LONGLONGSIZE = len(struct.pack("q", 0)) + +class AlsaMixerElemId: + + def __init__(self, + numid=0, + iface=CTL_ELEM_IFACE_MIXER, + device=0, + subdevice=0, + name=None, + index=0): + self.numid = numid + self.iface = iface + self.device = device + self.subdevice = subdevice + self.name = name + self.index = index + self.binsize = len(self.pack()) + + def pack(self): + return struct.pack('IiII44sI', + self.numid, self.iface, self.device, self.subdevice, + self.name, self.index) + + def unpack(self, binid): + self.numid, self.iface, self.device, self.subdevice, \ + self.name, self.index = struct.unpack('IiII44sI', binid) + self.name = self.name.replace('\x00', '') + + def get_text_info(self): + return 'iface="%s",name="%s",index=%s,device=%s,subdevice=%s' % \ + (CTL_ELEM_RIFACEs[self.iface], self.name, self.index, + self.device, self.subdevice) + +class AlsaMixerElem: + + def __init__(self, mixer, id): + self.mixer = mixer + self.id = id + info = self.__info() + self.type = info['type'] + self.stype = CTL_ELEM_RTYPEs[self.type] + self.access = info['access'] + self.count = info['count'] + self.owner = info['owner'] + if info['type'] in [CTL_ELEM_TYPE_INTEGER, CTL_ELEM_TYPE_INTEGER64]: + self.min = info['min'] + self.max = info['max'] + self.step = info['step'] + elif info['type'] == CTL_ELEM_TYPE_ENUMERATED: + self.items = info['items'] + self.dimen = info['dimen'] + + def __info(self): + bin = self.id.pack()+struct.pack('iIIi128s8s64s', 0, 0, 0, 0, '', '', '') + res = ioctl(self.mixer.fd, CTL_IOCTL_ELEM_INFO, bin) + self.id.unpack(res[:self.id.binsize]) + a = struct.unpack('iIIi128s8s64s', res[self.id.binsize:]) + b = {} + b['id'] = self.id + b['type'] = a[0] + b['access'] = [] + for i in CTL_ELEM_ACCESSs: + if CTL_ELEM_ACCESSs[i] & a[1]: + b['access'].append(i) + b['count'] = a[2] + b['owner'] = a[3] + if b['type'] == CTL_ELEM_TYPE_INTEGER: + b['min'], b['max'], b['step'] = \ + struct.unpack("lll", a[4][:LONGSIZE*3]) + elif b['type'] == CTL_ELEM_TYPE_INTEGER64: + b['min'], b['max'], b['step'] = \ + struct.unpack("qqq", a[4][:LONGLONGSIZE*3]) + elif b['type'] == CTL_ELEM_TYPE_ENUMERATED: + b['items'], b['item'], b['name'] = \ + struct.unpack("II64s", a[4][:UINTSIZE*2+64]) + b['dimen'] = struct.unpack("HHHH", a[5]) + return b + + def read(self): + bin = self.id.pack() + struct.pack('I512s128s', 0, '', '') + startoff = self.id.binsize + UINTSIZE + if LONGSIZE == 8: + bin += '\x00\x00\x00\x00' + startoff += 4 + res = ioctl(self.mixer.fd, CTL_IOCTL_ELEM_READ, bin) + if self.type == CTL_ELEM_TYPE_BOOLEAN: + return map(lambda x: x != 0, struct.unpack("l"*self.count, res[startoff:startoff+self.count*LONGSIZE])) + elif self.type == CTL_ELEM_TYPE_INTEGER: + return struct.unpack("l"*self.count, res[startoff:startoff+self.count*LONGSIZE]) + elif self.type == CTL_ELEM_TYPE_INTEGER64: + return struct.unpack("q"*self.count, res[startoff:startoff+self.count*LONGLONGSIZE]) + elif self.type == CTL_ELEM_TYPE_ENUMERATED: + return struct.unpack("I"*self.count, res[startoff:startoff+self.count*UINTSIZE]) + elif self.type == CTL_ELEM_TYPE_BYTES: + return res[startoff:startoff+self.count] + else: + raise ValueError, "Unsupported type %s" % CTL_ELEM_RTYPEs[self.type] + + def get_text_info(self, idx=None): + res = self.id.get_text_info() + '\n' + res += ' type="%s",access=%s,count=%s,owner=%s,dimen=%s\n' % \ + (self.stype, repr(self.access), self.count, self.owner, self.dimen) + if self.stype.startswith('integer'): + res += ' min=%s,max=%s,step=%s\n' % (self.min, self.max, self.step) + elif self.stype == 'enumerated': + res += ' items=%s\n' % (self.items) + return res + +class AlsaMixer: + + def __init__(self, card, ctl_fd=None): + self.card = card + if ctl_fd is None: + self.fd = os.open("/dev/snd/controlC%s" % card, os.O_RDONLY) + else: + self.fd = os.dup(ctl_fd) + + def __del__(self): + if not self.fd is None: + os.close(self.fd) + +if __name__ == '__main__': + mixer = AlsaMixer(0) + elem = AlsaMixerElem(mixer, AlsaMixerElemId(name="Mic Boost")) + print elem.read() + elem = AlsaMixerElem(mixer, AlsaMixerElemId(name="Capture Volume")) + print elem.read() diff --git a/hda-analyzer/hda_proc.py b/hda-analyzer/hda_proc.py new file mode 100644 index 0000000..3363b09 --- /dev/null +++ b/hda-analyzer/hda_proc.py @@ -0,0 +1,841 @@ +#!/usr/bin/env python +# +# Copyright (c) 2008-2010 by Jaroslav Kysela +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +from hda_codec import * + +SET_VERBS = { + VERBS['SET_SDI_SELECT']: VERBS['GET_SDI_SELECT'], + VERBS['SET_PIN_WIDGET_CONTROL']: VERBS['GET_PIN_WIDGET_CONTROL'], + VERBS['SET_CONNECT_SEL']: VERBS['GET_CONNECT_SEL'], + VERBS['SET_EAPD_BTLENABLE']: VERBS['GET_EAPD_BTLENABLE'], + VERBS['SET_POWER_STATE']: VERBS['GET_POWER_STATE'], +} + +def DecodeProcFile(proc_file): + if len(proc_file) < 256: + fd = open(proc_file) + proc_file = fd.read(1024*1024) + fd.close() + if proc_file.find('Subsystem Id:') < 0: + p = None + try: + from gzip import GzipFile + from StringIO import StringIO + s = StringIO(proc_file) + gz = GzipFile(mode='r', fileobj=s) + p = gz.read(1024*1024) + gz.close() + except: + pass + if p is None: + try: + from bz2 import decompress + p = decompress(proc_file) + except: + pass + if not p is None: + proc_file = p + return proc_file + +def DecodeAlsaInfoFile(proc_file): + if proc_file.find("ALSA Information Script") < 0: + return [proc_file] + pos = proc_file.find('HDA-Intel Codec information') + if pos < 0: + return [proc_file] + proc_file = proc_file[pos:] + pos = proc_file.find('--startcollapse--') + proc_file = proc_file[pos+18:] + pos = proc_file.find('--endcollapse--') + proc_file = proc_file[:pos] + res = [] + while 1: + pos = proc_file.find('\nCodec: ') + if pos < 0: + break + proc_file = proc_file[pos:] + pos = proc_file[1:].find('\nCodec: ') + if pos < 0: + pos = len(proc_file)-2 + res.append(proc_file[:pos+2]) + proc_file = proc_file[pos:] + return res + +class HDACardProc: + + def __init__(self, card): + self.card = card + self.id = 'ProcId' + self.driver = 'ProcDriver' + self.name = 'ProcName' + self.longname = 'ProcLongName' + self.components = 'ProcComponents' + +class HDABaseProc: + + delim = ' "[],:*' + + def decodestrw(self, str, prefix): + if str.startswith(prefix): + res = str[len(prefix):].strip() + delim = self.delim + if res[0] == '"': + res = res[1:] + delim = '"' + rem = res + for a in delim: + pos = res.find(a) + if pos >= 0: + if rem == res: + rem = rem[pos:] + res = res[:pos] + if rem == res: + rem = '' + ok = True + while ok: + ok = False + for a in self.delim: + if rem and rem[0] == a: + rem = rem[1:] + ok = True + return rem.strip(), res.strip() + self.wrongfile('string decode %s' % repr(str)) + + def decodeintw(self, str, prefix='', forcehex=False): + if str.startswith(prefix): + res = str[len(prefix):].strip() + rem = res + for a in self.delim: + pos = res.find(a) + if pos >= 0: + if rem == res: + rem = rem[pos:] + res = res[:pos] + if rem == res: + rem = '' + ok = True + while ok: + ok = False + for a in self.delim: + if rem and rem[0] == a: + rem = rem[1:] + ok = True + if res and forcehex: + if not res.startswith('0x'): + res = '0x' + res + if res.startswith('0x'): + return rem.strip(), int(res[2:], 16) + return rem.strip(), int(res) + self.wrongfile('integer decode %s (%s)' % (repr(str), repr(prefix))) + + def wrongfile(self, msg=''): + raise ValueError, "wrong proc file format (%s)" % msg + +class HDApcmDevice: + + def __init__(self, name, type, device): + self.name = name + self.type = type + self.device = device + + def dump_extra(self): + return ' Device: name="%s", type="%s", device=%s\n' % (self.name, self.type, self.device) + +class HDApcmControl: + + def __init__(self, name, index, device): + self.name = name + self.index = index + self.device = device + self.amp_chs = None + + def dump_extra(self): + str = ' Control: name="%s", index=%s, device=%s\n' % (self.name, self.index, self.device) + if not self.amp_chs is None: + str += ' ControlAmp: chs=%s, dir=%s, idx=%s, ofs=%s\n' % (self.amp_chs, self.amp_dir, self.amp_idx, self.amp_ofs) + return str + + def amp_index_match(self, idx): + if not self.amp_chs is None: + count = (self.amp_chs & 1) + ((self.amp_chs >> 1) & 1) + return idx >= self.amp_idx and idx < self.amp_idx + count + else: + return False + +class ProcNode(HDABaseProc): + + def __init__(self, codec, nid, wcaps): + self.codec = codec + codec.proc_nids[nid] = self + self.nid = nid + self.wcaps = wcaps + self.wtype = (self.wcaps >> 20) & 0x0f + self.wtype_id = WIDGET_TYPE_IDS[self.wtype] + self.device = None + self.amp_vals = [[], []] + self.connections = [] + self.params = {} + self.verbs = {} + self.controls = [] + if wcaps & (1 << 6): + self.add_param(PARAMS['PROC_CAP'], 0) + if wcaps & (1 << 7): + self.add_verb(VERBS['GET_UNSOLICITED_RESPONSE'], 0) + if wcaps & (1 << 9): + self.add_verb(VERBS['GET_DIGI_CONVERT_1'], 0) + if wcaps & (1 << 10): + self.add_param(PARAMS['POWER_STATE'], 0) + if self.wtype_id in ['AUD_IN', 'AUD_OUT']: + self.add_verb(VERBS['GET_CONV' ], 0) + if self.wtype_id == 'AUD_IN': + self.add_verb(VERBS['GET_SDI_SELECT'], 0) + + def rw(self, verb, param): + if verb in self.verbs: + return self.verbs[verb] + elif verb == VERBS['GET_CONNECT_LIST']: + f1 = self.connections[param] + f2 = 0 + if param + 1 < len(self.connections): + f2 = self.connections[param+1] + return f1 | (f2 << 16) + elif verb == VERBS['GET_AMP_GAIN_MUTE']: + dir = param & (1<<15) and HDA_OUTPUT or HDA_INPUT + idx = param & (1<<13) and 1 or 0 + val = param & 0x7f + if val >= len(self.amp_vals[dir]): + print "AMP index out of range (%s >= %s)" % (val, len(self.amp_vals[dir])) + while val >= len(self.amp_vals[dir]): + self.amp_vals[dir].append([128, 128]) + return self.amp_vals[dir][val][idx] + elif verb == VERBS['SET_AMP_GAIN_MUTE']: + dir = param & (1<<15) and HDA_OUTPUT or HDA_INPUT + idx = (param >> 8) & 0x0f + if param & (1<<12): + self.amp_vals[dir][idx][0] = param & 0xff + if param & (1<<13) and len(self.amp_vals[dir][idx]) > 1: + self.amp_vals[dir][idx][1] = param & 0xff + return param + elif verb == VERBS['SET_DIGI_CONVERT_1']: + self.verbs[VERBS['GET_DIGI_CONVERT_1']] &= ~0xff + self.verbs[VERBS['GET_DIGI_CONVERT_1']] |= param & 0xff + return param + elif verb == VERBS['SET_DIGI_CONVERT_2']: + self.verbs[VERBS['GET_DIGI_CONVERT_1']] &= ~0xff00 + self.verbs[VERBS['GET_DIGI_CONVERT_1']] |= (param & 0xff) << 8 + return param + elif verb in SET_VERBS: + self.verbs[SET_VERBS[verb]] = param + return param + raise ValueError, "unimplemented node rw (0x%x, 0x%x, 0x%x)" % (self.nid, verb, param) + + def param_read(self, param): + if param in self.params: + return self.params[param] + elif param == PARAMS['CONNLIST_LEN']: + return len(self.connections) + (1 << 7) # use long format + raise ValueError, "unimplemented node param read (0x%x, 0x%x)" % (self.nid, param) + + def add_verb(self, verb, param, do_or=False): + if do_or and verb in self.verbs: + self.verbs[verb] |= param + else: + self.verbs[verb] = param + + def add_param(self, param, value): + self.params[param] = value + + def add_device(self, line): + line, name = self.decodestrw(line, 'name=') + line, type = self.decodestrw(line, 'type=') + line, device = self.decodeintw(line, 'device=') + if self.device: + self.wrongfile('more than one PCM device?') + self.device = HDApcmDevice(name, type, device) + + def get_device(self): + return self.device + + def add_converter(self, line): + line, stream = self.decodeintw(line, 'stream=') + line, channel = self.decodeintw(line, 'channel=') + self.add_verb(VERBS['GET_CONV'], + ((stream & 0x0f) << 4) | (channel & 0x0f)) + + def add_digital(self, line): + bits = { + 'Enabled': DIG1_BITS['ENABLE'], + 'Validity': DIG1_BITS['VALIDITY'], + 'ValidityCfg': DIG1_BITS['VALIDITYCFG'], + 'Preemphasis': DIG1_BITS['EMPHASIS'], + 'Copyright': DIG1_BITS['COPYRIGHT'], + 'Non-Audio': DIG1_BITS['NONAUDIO'], + 'Pro': DIG1_BITS['PROFESSIONAL'], + 'GenLevel': DIG1_BITS['LEVEL'] + } + xbits = 0 + a = line.split(' ') + for b in a: + b = b.strip() + if not b: + return + if not b in bits: + self.wrongfile('unknown dig1 bit %s' % repr(b)) + xbits |= 1 << bits[b] + self.add_verb(VERBS['GET_DIGI_CONVERT_1'], xbits) + + def add_digitalcategory(self, line): + line, res = self.decodeintw(line) + self.add_verb(VERBS['GET_DIGI_CONVERT_1'], (res & 0x7f) << 8, do_or=True) + + def add_sdiselect(self, line): + line, res = self.decodeintw(line) + self.add_verb(VERBS['GET_SDI_SELECT'], res) + + def add_pcm(self, line1, line2, line3): + line1, tmp1 = self.decodeintw(line1, ' rates [') + line2, tmp2 = self.decodeintw(line2, ' bits [') + self.add_param(PARAMS['PCM'], + (tmp1 & 0xffff) | ((tmp2 & 0xffff) << 16)) + line3, tmp1 = self.decodeintw(line3, ' formats [') + self.add_param(PARAMS['STREAM'], tmp1) + + def add_control(self, line): + line, name = self.decodestrw(line, 'name=') + line, index = self.decodeintw(line, 'index=') + line, device = self.decodeintw(line, 'device=') + self.controls.append(HDApcmControl(name, index, device)) + + def add_controlamp(self, line): + ctl = self.controls[-1] + line, ctl.amp_chs = self.decodeintw(line, 'chs=') + line, ctl.amp_dir = self.decodestrw(line, 'dir=') + line, ctl.amp_idx = self.decodeintw(line, 'idx=') + line, ctl.amp_ofs = self.decodeintw(line, 'ofs=') + ctl.amp_dir = ctl.amp_dir == 'In' and HDA_INPUT or HDA_OUTPUT + + def get_controls(self): + return self.controls + + def add_ampcaps(self, line, dir): + line = line.strip() + par = PARAMS[dir == HDA_INPUT and 'AMP_IN_CAP' or 'AMP_OUT_CAP'] + if line == 'N/A': + self.add_param(par, 0) + return + line, ofs = self.decodeintw(line, 'ofs=') + line, nsteps = self.decodeintw(line, 'nsteps=') + line, stepsize = self.decodeintw(line, 'stepsize=') + line, mute = self.decodeintw(line, 'mute=') + self.add_param(par, + (ofs & 0x7f) | ((nsteps & 0x7f) << 8) | \ + ((stepsize & 0x7f) << 16) | ((mute & 1) << 31)) + + def add_ampvals(self, line, dir): + line = line.strip() + self.amp_vals[dir] = [] + while len(line): + if not line[0] == '[': + self.wrongfile('amp vals [') + pos = line.find(']') + if pos <= 0: + self.wrongfile('amp vals ]') + str = line[1:pos] + line = line[len(str)+2:].strip() + val = [] + while str.startswith('0x'): + str, val1 = self.decodeintw(str) + val.append(val1) + self.amp_vals[dir].append(val) + + def add_connection(self, line, conn): + line, count = self.decodeintw(line) + if count == -22: # driver was not able to read connections + count = 0 + res = 0 + if conn.startswith(' '): + conn = conn.strip() + res = 1 + else: + conn = '' + conns = [] + sel = -1 + while len(conn): + if len(conn) > 4 and conn[4] == '*': + sel = len(conns) + conn, val = self.decodeintw(conn) + conns.append(val) + if count != len(conns): + self.wrongfile('connections %s != %s' % (count, len(conns))) + self.connections = conns + self.add_verb(VERBS['GET_CONNECT_SEL'], sel) + return res + + def add_unsolicited(self, line): + line, tag = self.decodeintw(line, 'tag=', forcehex=True) + line, enabled = self.decodeintw(line, 'enabled=') + self.add_verb(VERBS['GET_UNSOLICITED_RESPONSE'], + (tag & 0x3f) | ((enabled & 1) << 7)) + + def add_pincap(self, line): + line = line.strip() + # workaround for bad 0x08%x format string in hda_proc.c + a = line.split(':') + if line.startswith('0x08') and len(a[0]) != 10: + line = "0x" + line[4:] + line, tmp1 = self.decodeintw(line, '') + self.add_param(PARAMS['PIN_CAP'], tmp1) + + def add_pindefault(self, line): + line, tmp1 = self.decodeintw(line, '') + self.add_verb(VERBS['GET_CONFIG_DEFAULT'], tmp1) + + def add_pinctls(self, line): + line, tmp1 = self.decodeintw(line, '') + self.add_verb(VERBS['GET_PIN_WIDGET_CONTROL'], tmp1) + + def add_eapd(self, line): + line, tmp1 = self.decodeintw(line, '') + self.add_verb(VERBS['GET_EAPD_BTLENABLE'], tmp1) + + def add_power(self, line): + line, setting = self.decodestrw(line, 'setting=') + line, actual = self.decodestrw(line, 'actual=') + if setting in POWER_STATES: + setting = POWER_STATES.index(setting) + else: + self.wrongfile('power setting %s' % setting) + if actual in POWER_STATES: + actual = POWER_STATES.index(actual) + else: + self.wrongfile('power actual %s' % actual) + self.add_verb(VERBS['GET_POWER_STATE'], (setting & 0x0f) | ((actual & 0x0f) << 4)) + + def add_powerstates(self, line): + a = line.strip().split(' ') + tmp1 = 0 + for b in a: + if b in POWER_STATES: + tmp1 |= 1 << POWER_STATES.index(b) + self.add_param(PARAMS['POWER_STATE'], tmp1) + + def add_processcaps(self, line): + line, benign = self.decodeintw(line, 'benign=') + line, ncoeff = self.decodeintw(line, 'ncoeff=') + self.add_param(PARAMS['PROC_CAP'], + (benign & 1) | ((ncoeff & 0xff) << 8)) + + def add_processcoef(self, line): + line, coef = self.decodeintw(line, '') + self.add_verb(VERBS['GET_PROC_COEF'], coef) + + def add_processindex(self, line): + line, idx = self.decodeintw(line, '') + self.add_verb(VERBS['GET_COEF_INDEX'], idx) + + def add_volknob(self, line): + line, delta = self.decodeintw(line, 'delta=') + line, steps = self.decodeintw(line, 'steps=') + line, direct = self.decodeintw(line, 'direct=') + line, val = self.decodeintw(line, 'val=') + self.add_param(PARAMS['VOL_KNB_CAP'], ((delta & 1) << 7) | (steps & 0x7f)) + self.add_verb(VERBS['GET_VOLUME_KNOB_CONTROL'], ((direct & 1) << 7) | (val & 0x7f)) + + def dump_extra(self): + str = '' + if self.device: + str += self.device.dump_extra() + for c in self.controls: + str += c.dump_extra() + return str + +class HDACodecProc(HDACodec, HDABaseProc): + + def __init__(self, card, device, proc_file): + self.hwaccess = False + self.fd = None + self.proc_codec = None + self.card = card + self.device = device + self.mcard = HDACardProc(card) + self.proc_codec_id = None + self.mixer = None + self.parse(proc_file) + if self.proc_codec_id: + self.mcard.name = self.proc_codec_id + + def parse(self, str): + + def lookfor(idx, prefix): + while idx < len(lines) and not lines[idx].startswith(prefix): + idx += 1 + if idx >= len(lines): + return idx, None + idx += 1 + return idx, lines[idx-1][len(prefix):].strip() + + def lookforint(idx, prefix): + idx, res = lookfor(idx, prefix) + if res: + if res.startswith('0x'): + return idx, int(res[2:], 16) + return idx, int(res) + + def decodeint(idx, prefix): + str, res = self.decodeintw(lines[idx], prefix) + return idx+1, res + + def decodefcnid(idx, prefix): + if lines[idx].startswith(prefix): + str, res = self.decodeintw(lines[idx][len(prefix):]) + if str.startswith('(unsol '): + str, res1 = self.decodeintw(str[:-1], '(unsol ') + else: + res1 = 0 + return idx + 1, ((res1 & 1) << 8) | (res & 0xff) + return idx, 0 + + def decodeampcap(idx, prefix): + if lines[idx].startswith(prefix): + res = lines[idx][len(prefix):].strip() + if res == 'N/A': + return idx+1, 0 + res, ofs = self.decodeintw(res, 'ofs=') + res, nsteps = self.decodeintw(res, 'nsteps=') + res, stepsize = self.decodeintw(res, 'stepsize=') + res, mute = self.decodeintw(res, 'mute=') + return idx+1, \ + (ofs & 0x7f) | ((nsteps & 0x7f) << 8) | \ + ((stepsize & 0x7f) << 16) | ((mute & 1) << 31) + self.wrongfile('amp caps expected') + + def decodegpiocap(idx, prefix): + if lines[idx].startswith(prefix): + res = lines[idx][len(prefix):].strip() + res, io = self.decodeintw(res, 'io=') + res, o = self.decodeintw(res, 'o=') + res, i = self.decodeintw(res, 'i=') + res, unsol = self.decodeintw(res, 'unsolicited=') + res, wake = self.decodeintw(res, 'wake=') + return idx+1, \ + (io & 0xff) | ((o & 0xff) << 8) | \ + ((i & 0xff) << 16) | ((unsol & 1) << 30) | ((wake & 1) << 31) + self.wrongfile('gpio caps expected') + + def decodegpio(idx, prefix): + + def writeval(str, idx, var): + res, val = self.decodeintw(str, var + '=') + if val: + self.proc_gpio[var] |= 1 << idx + else: + self.proc_gpio[var] &= ~(1 << idx) + return res + + res = lines[idx] + res, idx1 = self.decodeintw(res, prefix) + if res.startswith(': '): + res = res[2:] + for a in ['enable', 'dir', 'wake', 'sticky', 'data']: + res = writeval(res, idx1, a) + if res.find('unsol=') >= 0: + res = writeval(res, idx1, 'unsol') + return idx + 1 + + self.proc_afg = -1 + self.proc_mfg = -1 + self.proc_nids = {} + self.proc_afg_function_id = 0 + self.proc_mfg_function_id = 0 + self.proc_vendor_id = 0 + self.proc_subsystem_id = 0 + self.proc_revision_id =0 + function_id = 0 + lines = str.splitlines() + idx = 0 + idx, self.proc_codec_id = lookfor(idx, 'Codec: ') + if not self.proc_codec_id: + print "Proc Text Contents is not valid" + return + idx, tmp = lookforint(idx, 'Address: ') + self.device = tmp # really? + idx, function_id = decodefcnid(idx, 'Function Id: ') + idx, self.proc_afg_function_id = decodefcnid(idx, 'AFG Function Id: ') + idx, self.proc_mfg_function_id = decodefcnid(idx, 'MFG Function Id: ') + idx, self.proc_vendor_id = lookforint(idx, 'Vendor Id: ') + idx, self.proc_subsystem_id = lookforint(idx, 'Subsystem Id: ') + idx, self.proc_revision_id = lookforint(idx, 'Revision Id:' ) + if idx >= len(lines): + self.wrongfile('id strings expected') + nomfg = lines[idx].strip() == 'No Modem Function Group found' + if nomfg: + self.proc_afg = 1 + if self.proc_afg_function_id == 0: + self.proc_afg_function_id = function_id + idx += 1 + elif lines[idx].startswith('Default PCM:'): + self.proc_afg = 1 + if self.proc_afg_function_id == 0: + self.proc_afg_function_id = function_id + else: + idx, self.proc_mfg = lookforint(idx, 'Modem Function Group: ') + if not self.proc_mfg is None: + if self.proc_mfg_function_id == 0: + self.proc_mfg_function_id = function_id + else: + self.proc_mfg = -1 + if idx < len(lines) and lines[idx].startswith('Default PCM:'): + self.proc_afg = 1 + if self.proc_afg >= 0 and self.proc_afg_function_id == 0: + self.proc_afg_function_id = 1 + if self.proc_mfg >= 0 and self.proc_mfg_function_id == 0: + self.proc_mfg_function_id = 2 + if idx >= len(lines): + return # probably only modem codec + if not lines[idx].startswith('Default PCM:'): + self.wrongfile('default pcm expected') + if lines[idx+1].strip() == "N/A": + idx += 2 + self.proc_pcm_bits = 0 + self.proc_pcm_stream = 0 + else: + idx, tmp1 = decodeint(idx+1, ' rates [') + idx, tmp2 = decodeint(idx, ' bits [') + self.proc_pcm_bits = (tmp1 & 0xffff) | ((tmp2 & 0xffff) << 16) + idx, self.proc_pcm_stream = decodeint(idx, ' formats [') + idx, self.proc_amp_caps_in = decodeampcap(idx, 'Default Amp-In caps: ') + idx, self.proc_amp_caps_out = decodeampcap(idx, 'Default Amp-Out caps: ') + self.proc_gpio = { + 'enable': 0, + 'dir': 0, + 'wake': 0, + 'sticky': 0, + 'data': 0, + 'unsol': 0 + } + self.proc_gpio_cap = 0 + if lines[idx].startswith('GPIO: '): + idx, self.proc_gpio_cap = decodegpiocap(idx, 'GPIO: ') + while idx < len(lines) and lines[idx].startswith(' IO['): + idx = decodegpio(idx, ' IO[') + if idx >= len(lines): + return + line = lines[idx].strip() + if line == 'Invalid AFG subtree': + print "Invalid AFG subtree for codec %s?" % self.proc_codec_id + return + while line.startswith('Power-Map: ') or \ + line.startswith('Analog Loopback: '): + print 'Sigmatel specific "%s" verb ignored for the moment' % line + idx += 1 + line = lines[idx].strip() + node = None + while idx < len(lines): + line = lines[idx] + idx += 1 + line, nid = self.decodeintw(line, 'Node ') + pos = line.find('wcaps ') + if pos < 0: + self.wrongfile('node wcaps expected') + line, wcaps = self.decodeintw(line[pos:], 'wcaps ') + node = ProcNode(self, nid, wcaps) + if idx >= len(lines): + break + line = lines[idx] + while not line.startswith('Node '): + if line.startswith(' Device: '): + node.add_device(line[10:]) + elif line.startswith(' Control: '): + node.add_control(line[11:]) + elif line.startswith(' ControlAmp: '): + node.add_controlamp(line[16:]) + elif line.startswith(' Converter: '): + node.add_converter(line[13:]) + elif line.startswith(' SDI-Select: '): + node.add_sdiselect(line[14:]) + elif line.startswith(' Digital:'): + node.add_digital(line[11:]) + elif line.startswith(' Unsolicited:'): + node.add_unsolicited(line[15:]) + elif line.startswith(' Digital category:'): + node.add_digitalcategory(line[20:]) + elif line.startswith(' Amp-In caps: '): + node.add_ampcaps(line[15:], HDA_INPUT) + elif line.startswith(' Amp-Out caps: '): + node.add_ampcaps(line[16:], HDA_OUTPUT) + elif line.startswith(' Amp-In vals: '): + node.add_ampvals(line[15:], HDA_INPUT) + elif line.startswith(' Amp-Out vals: '): + node.add_ampvals(line[17:], HDA_OUTPUT) + elif line.startswith(' Connection: '): + if idx + 1 < len(lines): + idx += node.add_connection(line[13:], lines[idx+1]) + else: + idx += node.add_connection(line[13:], '') + elif line == ' PCM:': + node.add_pcm(lines[idx+1], lines[idx+2], lines[idx+3]) + idx += 3 + elif line.startswith(' Pincap '): + node.add_pincap(line[9:]) + elif line.startswith(' Vref caps: '): + pass + elif line.startswith(' Pin Default '): + node.add_pindefault(line[14:]) + elif line.startswith(' Conn = '): + pass + elif line.startswith(' DefAssociation = '): + pass + elif line.startswith(' Misc = '): + pass + elif line.startswith(' Delay: '): + pass + elif line.startswith(' Pin-ctls: '): + node.add_pinctls(line[12:]) + elif line.startswith(' EAPD '): + node.add_eapd(line[7:]) + elif line.startswith(' Power states: '): + node.add_powerstates(line[16:]) + elif line.startswith(' Power: '): + node.add_power(line[9:]) + elif line.startswith(' Processing caps: '): + node.add_processcaps(line[19:]) + elif line.startswith(' Processing Coefficient: '): + node.add_processcoef(line[26:]) + elif line.startswith(' Coefficient Index: '): + node.add_processindex(line[21:]) + elif line.startswith(' Volume-Knob: '): + node.add_volknob(line[15:]) + else: + self.wrongfile(line) + idx += 1 + if idx < len(lines): + line = lines[idx] + else: + break + + def param_read(self, nid, param): + if nid == AC_NODE_ROOT: + if param == PARAMS['VENDOR_ID']: + return self.proc_vendor_id + elif param == PARAMS['SUBSYSTEM_ID']: + return self.proc_subsystem_id + elif param == PARAMS['REV_ID']: + return self.proc_revision_id + elif nid == self.proc_afg: + if param == PARAMS['FUNCTION_TYPE']: + return self.proc_afg_function_id + elif param == PARAMS['PCM']: + return self.proc_pcm_bits + elif param == PARAMS['STREAM']: + return self.proc_pcm_stream + elif param == PARAMS['AMP_OUT_CAP']: + return self.proc_amp_caps_out + elif param == PARAMS['AMP_IN_CAP']: + return self.proc_amp_caps_in + elif param == PARAMS['GPIO_CAP']: + return self.proc_gpio_cap + elif nid == self.proc_mfg: + if param == PARAMS['FUNCTION_TYPE']: + return self.proc_mfg_function_id + else: + if nid is None: + return 0 + node = self.proc_nids[nid] + return node.param_read(param) + raise ValueError, "unimplemented param_read(0x%x, 0x%x)" % (nid, param) + + def get_sub_nodes(self, nid): + if nid == AC_NODE_ROOT: + if self.proc_mfg >= 0 and self.proc_afg >= 0: + return 2, self.proc_afg + if self.proc_mfg >= 0: + return 1, self.proc_mfg + return 1, self.proc_afg + elif nid == self.proc_afg: + if self.proc_nids: + return len(self.proc_nids), self.proc_nids.keys()[0] + return 0, 0 + elif nid is None: + return 0, 0 + raise ValueError, "unimplemented get_sub_nodes(0x%x)" % nid + + def get_wcap(self, nid): + node = self.proc_nids[nid] + return node.wcaps + + def get_raw_wcap(self, nid): + return get_wcap(self, nid) + + def rw(self, nid, verb, param): + if nid == self.proc_afg: + for i, j in GPIO_IDS.iteritems(): + if verb == j[0] or verb == j[1]: + if i == 'direction': + i = 'dir' + return self.proc_gpio[i] + if verb == VERBS['GET_SUBSYSTEM_ID']: + return self.proc_subsystem_id + else: + if nid is None: + return 0 + node = self.proc_nids[nid] + return node.rw(verb, param) + raise ValueError, "unimplemented rw(0x%x, 0x%x, 0x%x)" % (nid, verb, param) + + def dump_node_extra(self, node): + if not node or not node.nid in self.proc_nids: + return '' + node = self.proc_nids[node.nid] + return node.dump_extra() + + def get_device(self, nid): + if not nid in self.proc_nids: + return None + node = self.proc_nids[nid] + return node.get_device() + + def get_controls(self, nid): + if not nid in self.proc_nids: + return None + node = self.proc_nids[nid] + return node.get_controls() + +# +# test section +# + +def dotest1(base): + l = listdir(base) + for f in l: + file = base + '/' + f + if os.path.isdir(file): + dotest1(file) + else: + print file + file = DecodeProcFile(file) + file = DecodeAlsaInfoFile(file) + for a in file: + c = HDACodecProc(0, 0, a) + c.analyze() + +def dotest(): + import sys + if len(sys.argv) < 2: + raise ValueError, "Specify directory with codec dumps" + dotest1(sys.argv[1]) + +if __name__ == '__main__': + import os + from dircache import listdir + dotest() diff --git a/hda-analyzer/monitor.py b/hda-analyzer/monitor.py new file mode 100755 index 0000000..4637007 --- /dev/null +++ b/hda-analyzer/monitor.py @@ -0,0 +1,296 @@ +#!/usr/bin/env python +# +# Copyright (c) 2008-2010 by Jaroslav Kysela +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +import gobject +import gtk +import pango +from errno import EAGAIN +from subprocess import Popen, PIPE, STDOUT +from fcntl import fcntl, F_SETFL, F_GETFL +from signal import SIGKILL +import os + +CHANNELS = [ + "Front Left", + "Front Right", + "Rear Left", + "Rear Right", + "Center", + "LFE/Woofer", + "Side Left", + "Side Right" +] + +def set_fd_nonblocking(fd): + flags = fcntl(fd, F_GETFL) + fcntl(fd, F_SETFL, flags | os.O_NONBLOCK) + +class Monitor(gtk.Window): + + channel = 0 + channels = 0 + device = '' + generate_num = 0 + + def __init__(self, parent=None, device=None): + self.device = device + if not self.device: + self.device = 'plughw:0' + gtk.Window.__init__(self) + try: + self.set_screen(parent.get_screen()) + except AttributeError: + pass + self.connect('destroy', self.__destroy) + self.set_default_size(600, 400) + self.set_title(self.__class__.__name__) + self.set_border_width(10) + vbox = gtk.VBox(False, 0) + text_view = gtk.TextView() + fontName = pango.FontDescription("Misc Fixed,Courier 10") + text_view.modify_font(fontName) + text_view.set_border_width(4) + text_view.set_size_request(580, 350) + buffer = gtk.TextBuffer(None) + self.info_buffer = buffer + iter = buffer.get_iter_at_offset(0) + buffer.insert(iter, 'Please, select channel to play or number channels to record...') + text_view.set_buffer(buffer) + text_view.set_editable(False) + text_view.set_cursor_visible(False) + vbox.pack_start(text_view) + self.statusbar = gtk.Statusbar() + vbox.pack_start(self.statusbar, True, False) + separator = gtk.HSeparator() + vbox.pack_start(separator, expand=False) + frame = gtk.Frame('Playback') + frame.set_border_width(4) + hbox = gtk.HBox(False, 0) + hbox.set_border_width(4) + idx = 0 + for name in CHANNELS: + button = gtk.Button(name) + button.connect("clicked", self.__channel_change, idx) + hbox.pack_start(button, False, False) + idx += 1 + frame.add(hbox) + vbox.pack_start(frame, False, False) + frame = gtk.Frame('Capture') + frame.set_border_width(4) + hbox = gtk.HBox(False, 0) + hbox.set_border_width(4) + for idx in [2, 4, 6, 8]: + button = gtk.Button("%s channels" % idx) + button.connect("clicked", self.__channels_change, idx) + hbox.pack_start(button, False, False) + idx += 1 + frame.add(hbox) + vbox.pack_start(frame, False, False) + self.add(vbox) + self.generate_p = None + self.record_p = None + self.set_title('ALSA Monitor for %s' % self.device) + self.show_all() + + def __destroy(self, e): + self.generate_cleanup() + self.record_cleanup() + gtk.main_quit() + + def __channel_change(self, button, idx): + if self.channel != idx or self.generate_p == None: + self.set_text('Switching to playback...') + self.channel = idx + self.record_cleanup() + self.generate_cleanup() + self.generate_sound() + + def __channels_change(self, button, idx): + if self.channels != idx or self.record_p == None: + self.set_text('Switching to record...') + self.channels = idx + self.generate_cleanup() + self.record_cleanup() + self.record_sound() + + def set_text(self, text): + buffer = self.info_buffer + start, end = buffer.get_bounds() + buffer.delete(start, end) + if not text: return + iter = buffer.get_iter_at_offset(0) + buffer.insert(iter, text) + + def set_status(self, text): + context_id = self.statusbar.get_context_id("SCTX") + self.statusbar.pop(context_id) + self.statusbar.push(context_id, text) + + def generate_sound(self): + self.set_status('Playing sound #%s on channel %s...' % \ + (self.generate_num, CHANNELS[self.channel])) + self.generate_num += 1 + channels = 2 + if self.channel >= 6: + channels = 8 + elif self.channel >= 4: + channels = 6 + elif self.channel >= 2: + channels = 4 + self.cmd = ["speaker-test", "-D", self.device, + "-c", str(channels), + "-s", str(self.channel + 1)] + p = Popen(self.cmd, + shell=False, bufsize=0, stdin=None, stdout=PIPE, stderr=PIPE, + close_fds=True) + for fd in [p.stdout.fileno(), p.stderr.fileno()]: + set_fd_nonblocking(fd) + self.generate_p = p + self.generate_stdout_id = gobject.io_add_watch(p.stdout, gobject.IO_IN|gobject.IO_HUP|gobject.IO_NVAL, self.generate_io_stdout) + self.generate_stderr_id = gobject.io_add_watch(p.stderr, gobject.IO_IN|gobject.IO_HUP|gobject.IO_NVAL, self.generate_io_stderr) + self.generate_timeout_id = gobject.timeout_add(5000, self.generate_timeout) + self.generate_stdout = '' + self.generate_stderr = '' + + def generate_cleanup(self): + if not self.generate_p: + return + if self.generate_p.poll() == None: + try: + os.kill(self.generate_p.pid, SIGKILL) + except: + pass + self.generate_p.wait() + gobject.source_remove(self.generate_timeout_id) + gobject.source_remove(self.generate_stdout_id) + gobject.source_remove(self.generate_stderr_id) + del self.generate_p + self.generate_p = None + + def generate_timeout(self): + if self.generate_stdout == '' or self.generate_p.poll() != None: + if self.generate_stdout == '': + self.set_text('Cannot play. Device is busy...') + else: + self.set_text(' '.join(self.cmd) + '\n\n' + self.generate_stdout) + self.generate_cleanup() + self.generate_sound() + return False + return True + + def generate_io_stdout(self, source, condition): + if condition & gobject.IO_IN: + self.generate_stdout += source.read(1024) + self.set_text(' '.join(self.cmd) + '\n\n' + self.generate_stdout) + return True + + def generate_io_stderr(self, source, condition): + if condition & gobject.IO_IN: + self.generate_stderr += source.read(1024) + return True + + def record_sound(self): + self.set_status('Recording sound - %s channels...' % self.channels) + self.cmd = ["arecord", "-D", self.device, + "-f", "dat", "-c", str(self.channels), + "-t", "raw", "-vvv", "/dev/null"] + p = Popen(self.cmd, + shell=False, bufsize=0, stdin=None, stdout=PIPE, stderr=PIPE, + close_fds=True) + for fd in [p.stdout.fileno(), p.stderr.fileno()]: + set_fd_nonblocking(fd) + self.record_p = p + self.record_stdout_id = gobject.io_add_watch(p.stdout, gobject.IO_IN|gobject.IO_HUP|gobject.IO_NVAL, self.record_io_stdout) + self.record_stderr_id = gobject.io_add_watch(p.stderr, gobject.IO_IN|gobject.IO_HUP|gobject.IO_NVAL, self.record_io_stderr) + self.record_timeout_id = gobject.timeout_add(5000, self.record_timeout) + self.record_stdout = '' + self.record_stderr = '' + self.record_count = 0 + self.record_vols = [] + self.record_data = '' + + def record_cleanup(self): + if not self.record_p: + return + if self.record_p.poll() == None: + try: + os.kill(self.record_p.pid, SIGKILL) + except: + pass + self.record_p.wait() + gobject.source_remove(self.record_timeout_id) + gobject.source_remove(self.record_stdout_id) + gobject.source_remove(self.record_stderr_id) + del self.record_p + self.record_p = None + + def record_timeout(self): + if self.record_count == 0 or self.record_p.poll() != None: + if self.record_count == '': + self.set_text('Cannot record. Device is busy...') + else: + self.set_text(' '.join(self.cmd) + '\n\n' + self.record_stdout) + self.record_cleanup() + self.record_sound() + return False + return True + + def record_io_stdout(self, source, condition): + if condition & gobject.IO_IN: + while 1: + try: + data = source.read(128) + except IOError, e: + if e.errno == EAGAIN: + self.show_record_vols() + break + raise IOError, e + self.record_data += data + self.record_count += len(data) + pos = self.record_data.find('\n') + if pos >= 0: + line = self.record_data[:pos] + self.record_data = self.record_data[pos+1:] + pos = line.find('%') + if pos >= 0: + pos1 = pos - 1 + while line[pos1] >= '0' and line[pos1] <= '9': + pos1 -= 1 + self.record_vols.append(int(line[pos1:pos])) + if len(self.record_vols) > 24: + del self.record_vols[0] + #print data + return True + + def record_io_stderr(self, source, condition): + if condition & gobject.IO_IN: + self.record_stderr += source.read(1024) + return True + + def show_record_vols(self): + txt = 'Volume bars (# = volume, . = empty)\n' + max = 60 + for i in self.record_vols: + limit = (i * max) / 100 + for c in range(max): + txt += c < limit and '#' or '.' + txt += '\n' + self.set_text(txt) + +def main(): + Monitor() + gtk.main() + +if __name__ == '__main__': + main() diff --git a/hda-analyzer/run.py b/hda-analyzer/run.py new file mode 100755 index 0000000..0456bf2 --- /dev/null +++ b/hda-analyzer/run.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python + +URL="http://git.alsa-project.org/?p=alsa.git;a=blob_plain;f=hda-analyzer/" +FILES=["hda_analyzer.py", "hda_guilib.py", "hda_codec.py", "hda_proc.py", + "hda_graph.py", "hda_mixer.py"] + +try: + import gobject + import gtk + import pango +except: + print "Please, install pygtk2 or python-gtk package" + +import os +import sys +from urllib import splithost +from httplib import HTTP + +if os.path.exists("/dev/shm"): + TMPDIR="/dev/shm" +else: + TMPDIR="/tmp" +TMPDIR += "/hda-analyzer" +print "Using temporary directory: %s" % TMPDIR +print "You may remove this directory when finished or if you like to" +print "download the most recent copy of hda-analyzer tool." +if not os.path.exists(TMPDIR): + os.mkdir(TMPDIR) +for f in FILES: + dest = TMPDIR + '/' + f + if os.path.exists(dest): + print "File cached " + dest + continue + print "Downloading file %s" % f + host, selector = splithost(URL[5:]) + h = HTTP(host) + h.putrequest('GET', URL + f) + h.endheaders() + h.getreply() + contents = h.getfile().read(2*1024*1024) + h.close() + open(dest, "w+").write(contents) +print "Downloaded all files, executing %s" % FILES[0] +os.system("python %s" % TMPDIR + '/' + FILES[0] + ' ' + ' '.join(sys.argv[1:])) diff --git a/hda-verb/COPYING b/hda-verb/COPYING new file mode 100644 index 0000000..60549be --- /dev/null +++ b/hda-verb/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/hda-verb/ChangeLog b/hda-verb/ChangeLog new file mode 100644 index 0000000..de767d7 --- /dev/null +++ b/hda-verb/ChangeLog @@ -0,0 +1,9 @@ +version 0.3: + - Add a brief description about hwdep in README + - Add COPYING before someone blaming me + +version 0.2 + - public release + +version 0.1 + - embryo diff --git a/hda-verb/Makefile b/hda-verb/Makefile new file mode 100644 index 0000000..e5a4f00 --- /dev/null +++ b/hda-verb/Makefile @@ -0,0 +1,15 @@ +VERSION = 0.3 + +CC = gcc +CFLAGS = -Wall -O2 -g + +hda-verb: hda-verb.o + $(CC) -o $@ hda-verb.o + +clean: + rm -f hda-verb *.o + +dist: + cd ..; mv hda-verb hda-verb-$(VERSION); \ + tar cfz hda-verb-$(VERSION).tar.gz --exclude='.git*' hda-verb-$(VERSION); \ + mv hda-verb-$(VERSION) hda-verb diff --git a/hda-verb/README b/hda-verb/README new file mode 100644 index 0000000..2ec65b5 --- /dev/null +++ b/hda-verb/README @@ -0,0 +1,36 @@ +HDA-VERB -- Send a HD-audio command + +hda-verb is a small program to send HD-audio commands to the given +ALSA hwdep device on the hd-audio interface. + +First off, build HD-audio driver with hwdep support. For the kernel +config, set CONFIG_SND_HDA_HWDEP=y. When you build ALSA drivers from +alsa-driver tarball, usually this is set automatically. + +Once snd-hda-intel driver is built with the hwdep support, you should +have a hwdep device such as /dev/snd/hwC0D0. + +The program takes four arguments, the hwdep device name, the widget NID, +the verb and the parameter. For example, + + % hda-verb /dev/snd/hwC0D0 0x12 0x701 2 + +The verb argument can be a string like "PARAMETERS". Also the +parameter argument can be a string like "VENDOR_ID" as well. + + % hda-verb /dev/snd/hwC0D0 0x0 PARAMETERS VENDOR_ID + +The string is case insensitive. Also, it doesn't have to be the full +string but only has to be unique. E.g. "par" is enough to mean +"PARAMETER", and "set_a" is enough as "SET_AMP_GAIN_MUTE". + + % hda-verb /dev/snd/hwC0D0 2 set_a 0xb080 + +The program executs the given verb, shows the result and quits. +Usually you need to be root to run this command. + + +*WARNING* +Use this program carefully. Sending an invalid verb may screw up the +codec communication, which requires either a reboot or reloading of +the sound driver eventually. diff --git a/hda-verb/hda-verb.c b/hda-verb/hda-verb.c new file mode 100644 index 0000000..64293c9 --- /dev/null +++ b/hda-verb/hda-verb.c @@ -0,0 +1,313 @@ +/* + * Accessing HD-audio verbs via hwdep interface + * Version 0.3 + * + * Copyright (c) 2008 Takashi Iwai + * + * Licensed under GPL v2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +#include "hda_hwdep.h" + +#define AC_VERB_GET_STREAM_FORMAT 0x0a00 +#define AC_VERB_GET_AMP_GAIN_MUTE 0x0b00 +#define AC_VERB_GET_PROC_COEF 0x0c00 +#define AC_VERB_GET_COEF_INDEX 0x0d00 +#define AC_VERB_PARAMETERS 0x0f00 +#define AC_VERB_GET_CONNECT_SEL 0x0f01 +#define AC_VERB_GET_CONNECT_LIST 0x0f02 +#define AC_VERB_GET_PROC_STATE 0x0f03 +#define AC_VERB_GET_SDI_SELECT 0x0f04 +#define AC_VERB_GET_POWER_STATE 0x0f05 +#define AC_VERB_GET_CONV 0x0f06 +#define AC_VERB_GET_PIN_WIDGET_CONTROL 0x0f07 +#define AC_VERB_GET_UNSOLICITED_RESPONSE 0x0f08 +#define AC_VERB_GET_PIN_SENSE 0x0f09 +#define AC_VERB_GET_BEEP_CONTROL 0x0f0a +#define AC_VERB_GET_EAPD_BTLENABLE 0x0f0c +#define AC_VERB_GET_DIGI_CONVERT_1 0x0f0d +#define AC_VERB_GET_DIGI_CONVERT_2 0x0f0e +#define AC_VERB_GET_VOLUME_KNOB_CONTROL 0x0f0f +#define AC_VERB_GET_GPIO_DATA 0x0f15 +#define AC_VERB_GET_GPIO_MASK 0x0f16 +#define AC_VERB_GET_GPIO_DIRECTION 0x0f17 +#define AC_VERB_GET_GPIO_WAKE_MASK 0x0f18 +#define AC_VERB_GET_GPIO_UNSOLICITED_RSP_MASK 0x0f19 +#define AC_VERB_GET_GPIO_STICKY_MASK 0x0f1a +#define AC_VERB_GET_CONFIG_DEFAULT 0x0f1c +#define AC_VERB_GET_SUBSYSTEM_ID 0x0f20 + +#define AC_VERB_SET_STREAM_FORMAT 0x200 +#define AC_VERB_SET_AMP_GAIN_MUTE 0x300 +#define AC_VERB_SET_PROC_COEF 0x400 +#define AC_VERB_SET_COEF_INDEX 0x500 +#define AC_VERB_SET_CONNECT_SEL 0x701 +#define AC_VERB_SET_PROC_STATE 0x703 +#define AC_VERB_SET_SDI_SELECT 0x704 +#define AC_VERB_SET_POWER_STATE 0x705 +#define AC_VERB_SET_CHANNEL_STREAMID 0x706 +#define AC_VERB_SET_PIN_WIDGET_CONTROL 0x707 +#define AC_VERB_SET_UNSOLICITED_ENABLE 0x708 +#define AC_VERB_SET_PIN_SENSE 0x709 +#define AC_VERB_SET_BEEP_CONTROL 0x70a +#define AC_VERB_SET_EAPD_BTLENABLE 0x70c +#define AC_VERB_SET_DIGI_CONVERT_1 0x70d +#define AC_VERB_SET_DIGI_CONVERT_2 0x70e +#define AC_VERB_SET_VOLUME_KNOB_CONTROL 0x70f +#define AC_VERB_SET_GPIO_DATA 0x715 +#define AC_VERB_SET_GPIO_MASK 0x716 +#define AC_VERB_SET_GPIO_DIRECTION 0x717 +#define AC_VERB_SET_GPIO_WAKE_MASK 0x718 +#define AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK 0x719 +#define AC_VERB_SET_GPIO_STICKY_MASK 0x71a +#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_0 0x71c +#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_1 0x71d +#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_2 0x71e +#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_3 0x71f +#define AC_VERB_SET_CODEC_RESET 0x7ff + +#define AC_PAR_VENDOR_ID 0x00 +#define AC_PAR_SUBSYSTEM_ID 0x01 +#define AC_PAR_REV_ID 0x02 +#define AC_PAR_NODE_COUNT 0x04 +#define AC_PAR_FUNCTION_TYPE 0x05 +#define AC_PAR_AUDIO_FG_CAP 0x08 +#define AC_PAR_AUDIO_WIDGET_CAP 0x09 +#define AC_PAR_PCM 0x0a +#define AC_PAR_STREAM 0x0b +#define AC_PAR_PIN_CAP 0x0c +#define AC_PAR_AMP_IN_CAP 0x0d +#define AC_PAR_CONNLIST_LEN 0x0e +#define AC_PAR_POWER_STATE 0x0f +#define AC_PAR_PROC_CAP 0x10 +#define AC_PAR_GPIO_CAP 0x11 +#define AC_PAR_AMP_OUT_CAP 0x12 +#define AC_PAR_VOL_KNB_CAP 0x13 + +/* + */ +#define VERBSTR(x) { .val = AC_VERB_##x, .str = #x } +#define PARMSTR(x) { .val = AC_PAR_##x, .str = #x } + +struct strtbl { + int val; + const char *str; +}; + +static struct strtbl hda_verbs[] = { + VERBSTR(GET_STREAM_FORMAT), + VERBSTR(GET_AMP_GAIN_MUTE), + VERBSTR(GET_PROC_COEF), + VERBSTR(GET_COEF_INDEX), + VERBSTR(PARAMETERS), + VERBSTR(GET_CONNECT_SEL), + VERBSTR(GET_CONNECT_LIST), + VERBSTR(GET_PROC_STATE), + VERBSTR(GET_SDI_SELECT), + VERBSTR(GET_POWER_STATE), + VERBSTR(GET_CONV), + VERBSTR(GET_PIN_WIDGET_CONTROL), + VERBSTR(GET_UNSOLICITED_RESPONSE), + VERBSTR(GET_PIN_SENSE), + VERBSTR(GET_BEEP_CONTROL), + VERBSTR(GET_EAPD_BTLENABLE), + VERBSTR(GET_DIGI_CONVERT_1), + VERBSTR(GET_DIGI_CONVERT_2), + VERBSTR(GET_VOLUME_KNOB_CONTROL), + VERBSTR(GET_GPIO_DATA), + VERBSTR(GET_GPIO_MASK), + VERBSTR(GET_GPIO_DIRECTION), + VERBSTR(GET_GPIO_WAKE_MASK), + VERBSTR(GET_GPIO_UNSOLICITED_RSP_MASK), + VERBSTR(GET_GPIO_STICKY_MASK), + VERBSTR(GET_CONFIG_DEFAULT), + VERBSTR(GET_SUBSYSTEM_ID), + + VERBSTR(SET_STREAM_FORMAT), + VERBSTR(SET_AMP_GAIN_MUTE), + VERBSTR(SET_PROC_COEF), + VERBSTR(SET_COEF_INDEX), + VERBSTR(SET_CONNECT_SEL), + VERBSTR(SET_PROC_STATE), + VERBSTR(SET_SDI_SELECT), + VERBSTR(SET_POWER_STATE), + VERBSTR(SET_CHANNEL_STREAMID), + VERBSTR(SET_PIN_WIDGET_CONTROL), + VERBSTR(SET_UNSOLICITED_ENABLE), + VERBSTR(SET_PIN_SENSE), + VERBSTR(SET_BEEP_CONTROL), + VERBSTR(SET_EAPD_BTLENABLE), + VERBSTR(SET_DIGI_CONVERT_1), + VERBSTR(SET_DIGI_CONVERT_2), + VERBSTR(SET_VOLUME_KNOB_CONTROL), + VERBSTR(SET_GPIO_DATA), + VERBSTR(SET_GPIO_MASK), + VERBSTR(SET_GPIO_DIRECTION), + VERBSTR(SET_GPIO_WAKE_MASK), + VERBSTR(SET_GPIO_UNSOLICITED_RSP_MASK), + VERBSTR(SET_GPIO_STICKY_MASK), + VERBSTR(SET_CONFIG_DEFAULT_BYTES_0), + VERBSTR(SET_CONFIG_DEFAULT_BYTES_1), + VERBSTR(SET_CONFIG_DEFAULT_BYTES_2), + VERBSTR(SET_CONFIG_DEFAULT_BYTES_3), + VERBSTR(SET_CODEC_RESET), + { }, /* end */ +}; + +static struct strtbl hda_params[] = { + PARMSTR(VENDOR_ID), + PARMSTR(SUBSYSTEM_ID), + PARMSTR(REV_ID), + PARMSTR(NODE_COUNT), + PARMSTR(FUNCTION_TYPE), + PARMSTR(AUDIO_FG_CAP), + PARMSTR(AUDIO_WIDGET_CAP), + PARMSTR(PCM), + PARMSTR(STREAM), + PARMSTR(PIN_CAP), + PARMSTR(AMP_IN_CAP), + PARMSTR(CONNLIST_LEN), + PARMSTR(POWER_STATE), + PARMSTR(PROC_CAP), + PARMSTR(GPIO_CAP), + PARMSTR(AMP_OUT_CAP), + PARMSTR(VOL_KNB_CAP), + { }, /* end */ +}; + +static void list_keys(struct strtbl *tbl) +{ + int c = 0; + for (; tbl->str; tbl++) { + int len = strlen(tbl->str) + 2; + if (c + len >= 80) { + fprintf(stderr, "\n"); + c = 0; + } + if (!c) + fprintf(stderr, " %s", tbl->str); + else + fprintf(stderr, ", %s", tbl->str); + c += 2 + len; + } + fprintf(stderr, "\n"); +} + +/* look up a value from the given string table */ +static int lookup_str(struct strtbl *tbl, const char *str) +{ + struct strtbl *p, *found; + int len = strlen(str); + + found = NULL; + for (p = tbl; p->str; p++) { + if (!strncmp(str, p->str, len)) { + if (found) { + fprintf(stderr, "No unique key '%s'\n", str); + return -1; + } + found = p; + } + } + if (!found) { + fprintf(stderr, "No key matching with '%s'\n", str); + return -1; + } + return found->val; +} + +/* convert a string to upper letters */ +static void strtoupper(char *str) +{ + for (; *str; str++) + *str = toupper(*str); +} + +int main(int argc, char **argv) +{ + int version; + int fd; + int nid, verb, param; + struct hda_verb_ioctl val; + + if (argc < 5) { + fprintf(stderr, "usage: hda-verb hwdep-device nid verb param\n"); + fprintf(stderr, "known verbs:\n"); + list_keys(hda_verbs); + fprintf(stderr, "known parameters:\n"); + list_keys(hda_params); + return 1; + } + fd = open(argv[1], O_RDWR); + if (fd < 0) { + perror("open"); + return 1; + } + version = 0; + if (ioctl(fd, HDA_IOCTL_PVERSION, &version) < 0) { + perror("ioctl(PVERSION)"); + fprintf(stderr, "Looks like an invalid hwdep device...\n"); + return 1; + } + if (version < HDA_HWDEP_VERSION) { + fprintf(stderr, "Invalid version number 0x%x\n", version); + fprintf(stderr, "Looks like an invalid hwdep device...\n"); + return 1; + } + + nid = strtol(argv[2], NULL, 0); + if (nid < 0 || nid > 0xff) { + fprintf(stderr, "invalid nid 0x%x\n", nid); + return 1; + } + + if (!isdigit(*argv[3])) { + strtoupper(argv[3]); + verb = lookup_str(hda_verbs, argv[3]); + if (verb < 0) + return 1; + } else { + verb = strtol(argv[3], NULL, 0); + if (verb < 0 || verb > 0xfff) { + fprintf(stderr, "invalid verb 0x%x\n", verb); + return 1; + } + } + if (!isdigit(*argv[4])) { + strtoupper(argv[4]); + param = lookup_str(hda_params, argv[4]); + if (param < 0) + return 1; + } else { + param = strtol(argv[4], NULL, 0); + if (param < 0 || param > 0xffff) { + fprintf(stderr, "invalid param 0x%x\n", param); + return 1; + } + } + fprintf(stderr, "nid = 0x%x, verb = 0x%x, param = 0x%x\n", + nid, verb, param); + + val.verb = HDA_VERB(nid, verb, param); + if (ioctl(fd, HDA_IOCTL_VERB_WRITE, &val) < 0) + perror("ioctl"); + printf("value = 0x%x\n", val.res); + close(fd); + return 0; +} diff --git a/hda-verb/hda_hwdep.h b/hda-verb/hda_hwdep.h new file mode 100644 index 0000000..1c0034e --- /dev/null +++ b/hda-verb/hda_hwdep.h @@ -0,0 +1,44 @@ +/* + * HWDEP Interface for HD-audio codec + * + * Copyright (c) 2007 Takashi Iwai + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SOUND_HDA_HWDEP_H +#define __SOUND_HDA_HWDEP_H + +#define HDA_HWDEP_VERSION ((1 << 16) | (0 << 8) | (0 << 0)) /* 1.0.0 */ + +/* verb */ +#define HDA_REG_NID_SHIFT 24 +#define HDA_REG_VERB_SHIFT 8 +#define HDA_REG_VAL_SHIFT 0 +#define HDA_VERB(nid,verb,param) ((nid)<<24 | (verb)<<8 | (param)) + +struct hda_verb_ioctl { + u32 verb; /* HDA_VERB() */ + u32 res; /* response */ +}; + +/* + * ioctls + */ +#define HDA_IOCTL_PVERSION _IOR('H', 0x10, int) +#define HDA_IOCTL_VERB_WRITE _IOWR('H', 0x11, struct hda_verb_ioctl) +#define HDA_IOCTL_GET_WCAP _IOWR('H', 0x12, struct hda_verb_ioctl) + +#endif