]> git.alsa-project.org Git - alsa-hda-tools.git/commitdiff
initial commit - hda-analyzer + hda-verb-0.3 master
authorJaroslav Kysela <perex@perex.cz>
Tue, 10 Aug 2010 07:00:31 +0000 (09:00 +0200)
committerJaroslav Kysela <perex@perex.cz>
Tue, 10 Aug 2010 07:00:31 +0000 (09:00 +0200)
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
14 files changed:
hda-analyzer/hda_analyzer.py [new file with mode: 0755]
hda-analyzer/hda_codec.py [new file with mode: 0644]
hda-analyzer/hda_graph.py [new file with mode: 0755]
hda-analyzer/hda_guilib.py [new file with mode: 0644]
hda-analyzer/hda_mixer.py [new file with mode: 0644]
hda-analyzer/hda_proc.py [new file with mode: 0644]
hda-analyzer/monitor.py [new file with mode: 0755]
hda-analyzer/run.py [new file with mode: 0755]
hda-verb/COPYING [new file with mode: 0644]
hda-verb/ChangeLog [new file with mode: 0644]
hda-verb/Makefile [new file with mode: 0644]
hda-verb/README [new file with mode: 0644]
hda-verb/hda-verb.c [new file with mode: 0644]
hda-verb/hda_hwdep.h [new file with mode: 0644]

diff --git a/hda-analyzer/hda_analyzer.py b/hda-analyzer/hda_analyzer.py
new file mode 100755 (executable)
index 0000000..5c78ca9
--- /dev/null
@@ -0,0 +1,483 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2008-2010 by Jaroslav Kysela <perex@perex.cz>
+#
+#   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 (file)
index 0000000..1036a56
--- /dev/null
@@ -0,0 +1,1645 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2008-2010 by Jaroslav Kysela <perex@perex.cz>
+#
+#   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 (executable)
index 0000000..06b5c99
--- /dev/null
@@ -0,0 +1,932 @@
+4#!/usr/bin/env python
+#
+# Copyright (c) 2008-2010 by Jaroslav Kysela <perex@perex.cz>
+#
+#   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 (file)
index 0000000..dbe81f3
--- /dev/null
@@ -0,0 +1,874 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2008-2010 by Jaroslav Kysela <perex@perex.cz>
+#
+#   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 (file)
index 0000000..af12d5c
--- /dev/null
@@ -0,0 +1,210 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2008-2010 by Jaroslav Kysela <perex@perex.cz>
+#
+#   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 (file)
index 0000000..3363b09
--- /dev/null
@@ -0,0 +1,841 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2008-2010 by Jaroslav Kysela <perex@perex.cz>
+#
+#   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 (executable)
index 0000000..4637007
--- /dev/null
@@ -0,0 +1,296 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2008-2010 by Jaroslav Kysela <perex@perex.cz>
+#
+#   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 (executable)
index 0000000..0456bf2
--- /dev/null
@@ -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 (file)
index 0000000..60549be
--- /dev/null
@@ -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.
+\f
+                   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.)
+\f
+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.
+\f
+  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.
+\f
+  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
+\f
+           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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    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.
+
+  <signature of Ty Coon>, 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 (file)
index 0000000..de767d7
--- /dev/null
@@ -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 (file)
index 0000000..e5a4f00
--- /dev/null
@@ -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 (file)
index 0000000..2ec65b5
--- /dev/null
@@ -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 (file)
index 0000000..64293c9
--- /dev/null
@@ -0,0 +1,313 @@
+/*
+ * Accessing HD-audio verbs via hwdep interface
+ * Version 0.3
+ *
+ * Copyright (c) 2008 Takashi Iwai <tiwai@suse.de>
+ *
+ * Licensed under GPL v2 or later.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/io.h>
+#include <sys/types.h>
+#include <sys/fcntl.h>
+
+#include <stdint.h>
+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 (file)
index 0000000..1c0034e
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * HWDEP Interface for HD-audio codec
+ *
+ * Copyright (c) 2007 Takashi Iwai <tiwai@suse.de>
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#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