/*******************************************************************************
 *  azx-alsa-drop-driver-1.1.2.tgz is a driver for hardware using Azaila arch.
 *  Copyright (C) 2004 Intel Corporation
 *
 *  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.
 *
 *
 *  codec_enum.c - Implementation of codec enumeration functions.
 *
 *  Copyright(c) 2004 Intel Corporation. All rights reserved.
 *
 *  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.
 *
 *  The full GNU General Public License is included in this distribution in the
 *  file called LICENSE.
 *
 *  CONTACTS:
 *
 *  Matt Jared		matt.jared@intel.com
 *  Andy Kopp		andy.kopp@intel.com
 *  Dan Kogan		dan.d.kogan@intel.com
 *
 *  codec_enum.c,v 1.3 2004/07/29 06:37:34 cladisch Exp
 *
 *  codec_enum.c,v
 *  cladisch
 *  2004/07/29 06:37:34
 *
 ******************************************************************************/

#define __NO_VERSION__
#include <sound/driver.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/list.h>


#include "azx_controller.h"
#include "azx_ringbuffer.h"
#include "azx.h"
#include "codec_enum.h"




// ***********************************************************************
// ** ADTs
// ***********************************************************************

typedef struct {
	u32 stero        : 1;
	u32 in_amp       : 1;
	u32 out_amp      : 1;
	u32 amp_p_ovrd   : 1;
	u32 format_ovrd  : 1;
	u32 stripe       : 1;
	u32 proc_wid     : 1;
	u32 unsol_cap    : 1;
	u32 conn_list    : 1;
	u32 digital      : 1;
	u32 pwr_ctl      : 1;
	u32 L_R_swap     : 1;
	u32 rsvd_a       : 4;
	u32 delay        : 4;
	u32 type         : 4;
	u32 rsvd_b       : 8;
} s_WID_CAPs;

typedef struct {
	unsigned int VRefEn       : 3;
	unsigned int rsvd         : 2;
	unsigned int in_en        : 1;
        unsigned int out_en       : 1;
	unsigned int h_phn_en     : 1;
} s_PW_CNTL;

typedef struct {
	unsigned int imp_sense_cap  : 1;
	unsigned int trig_req       : 1;
	unsigned int presence_detct : 1;
	unsigned int head_drv_cap   : 1;
	unsigned int out_cap        : 1;
	unsigned int in_cap         : 1;
	unsigned int bal_io_pin     : 1;
	unsigned int rsvd           : 1;
	unsigned int vref_ctl       : 8;
	unsigned int eapd_cap       : 1;
	unsigned int rsvd_2         : 14;
} s_PIN_CAPs;


typedef union {
	u32 resp_data;
	struct {
		u32 sequence             : 4;
		u32 default_assoc        : 4;
		u32 misc                 : 4;
		u32 color                : 4;	
		u32 conn_type            : 4;
		u32 default_device       : 4;
		u32 location             : 6;
		u32 port_conn            : 2;
	} s_config_default;
} u_WID_CONF_DEF;

typedef struct {
	u32 offset               : 7;
	u32 rsvd1                : 1;
	u32 num_steps            : 7;
	u32 rsvd2                : 1;
	u32 step_size            : 7;
	u32 rsvd3                : 8;
	u32 mute_cap             : 1;
} s_AMP_CAPS;


// generic widget type for recursion
typedef struct {
	u8 NID;
	u8 type;
	u32 cl_present;
} s_WID;

// stores critical NIDs found during path search
typedef struct {
	u8 DAC_NID;
	u8 ADC_NID;
} path_nid_t;


// mixer control table head
static LIST_HEAD(mix_ctl_table); 


// function group types
#define AUD_FUNC_GROUP           0x1


// widget types
#define AUD_OUT_WID              0x0
#define AUD_IN_WID               0x1
#define AUD_MIX_WID              0x2
#define AUD_SEL_WID              0x3
#define AUD_PIN_WID              0x4
#define AUD_POW_WID              0x5
#define AUD_VOL_WID              0x6
#define AUD_BEEP_WID             0x7
#define AUD_VEND_WID             0xF

// jack types
#define JACK_LINE_OUT            0x0
#define JACK_SPEAKER             0x1
#define JACK_HP_OUT              0x2
#define JACK_CD                  0x3
#define JACK_SPDIF_OUT           0x4
#define JACK_DIG_OTHER_OUT       0x5
#define JACK_MODEM_LINE_SIDE     0x6
#define JACK_MODEM_HAND_SIDE     0x7
#define JACK_LINE_IN             0x8
#define JACK_AUX                 0x9
#define JACK_MIC_IN              0xA
#define JACK_TELEPHONY           0xB
#define JACK_SPDIF_IN            0xC
#define JACK_DIG_OTHER_IN        0xD
#define JACK_OTHER               0xF

// jack colors
#define JACK_COLOR_UNKNOWN       0x0
#define JACK_COLOR_BLACK         0x1
#define JACK_COLOR_GREY          0x2
#define JACK_COLOR_BLUE          0x3
#define JACK_COLOR_GREENE        0x4
#define JACK_COLOR_RED           0x5
#define JACK_COLOR_ORANGE        0x6
#define JACK_COLOR_YELLOW        0x7
#define JACK_COLOR_PURPLE        0x8
#define JACK_COLOR_PINK          0x9
#define JACK_COLOR_WHITE         0xE
#define JACK_COLOR_OTHER         0xF



// ***********************************************************************
// ** Function Prototypes
// ***********************************************************************

// Controller command wrapper functions
static u8   get_wid_type               (azx_t* chip, u8 widget_nid);
static u8   get_wid_amp_out            (azx_t* chip, u8 wid_nid);
static u8   get_wid_amp_in             (azx_t* chip, u8 wid_nid);
static u32  get_wid_caps               (azx_t* chip, u8 w_nid, s_WID_CAPs* w_caps,  u32* resp);
static u32  get_amp_caps               (azx_t* chip, u8 direction, u8 w_nid, s_AMP_CAPS* amp_caps,  u32* resp);
static u32  get_pin_caps               (azx_t* chip, u8 pin_nid, s_PIN_CAPs* pin_caps,  u32* resp);
static u32  get_pin_ctl_caps           (azx_t* chip, u8 pin_nid, s_PW_CNTL* pin_cntl_caps,  u32* resp);
static u32  get_conn_list_len          (azx_t* chip, u8 w_nid, u32* is_long_form);
static u32  get_conn_list_entry        (azx_t* chip, s_WID* wid_list, u32 wid_size, u8 host_nid, u8 index_nid);
static u32  get_sub_node_count         (azx_t* chip, u8 nid, u8* start_nid, u8* end_nid, u32* total_nodes);
static u32  get_func_grp_type          (azx_t* chip, u8 nid);
static u32  get_default_device_value   (azx_t* chip, u8 pin_wid, u32* default_dev, u32* color);
static u32  set_stream_channel         (azx_t* chip, u8 wid_nid, u16 stream_id, u16 channels);
static u32  set_stream_format          (azx_t* chip, u8 wid_nid, u32 stream_format);
static u32  unmute_widget_output       (azx_t* chip, u8 wid_nid);
static u32  unmute_widget_input        (azx_t* chip, u8 wid_nid);
static u32  set_connection_select      (azx_t* chip, u8 wid_nid, u8 list_index);
static u32  set_pin_wid_in_enable      (azx_t* chip, u8 wid_nid);
static void get_wid_type_string        (u8 wid_value, char* wid_str);

// Codec enumeration
static u32  set_output_wid_connection  (azx_t* chip, s_WID widget, u32 stream_id, u32 channels, u32 stream_format);
static u32  set_input_wid_connection   (azx_t* chip, s_WID widget, u32 stream_id, u32 channels, u32 stream_format);

// Util functions
static u32  get_bit_value              (u32 pos, u32 target);

// ***********************************************************************
// ** GLOBALS
// ***********************************************************************
static u8   g_ADC_NID = 0;
static u32  g_audio_codec_addr = 0;

static path_nid_t g_path_nids = {
	.DAC_NID = 0,
	.ADC_NID = 0
};


// ***********************************************************************
// ** PROC FILE SYSTEM STUFF
// ***********************************************************************


#ifdef USE_PROC_CODEC_INFO
static struct seq_operations proc_codecinf_seq_ops = {
	.start = codecinf_seq_start,
	.next = codecinf_seq_next,
	.stop = codecinf_seq_stop,
	.show = codecinf_seq_show,
};


// codec info list head
static LIST_HEAD(codec_info_head);

struct codec_info_entry
{
	char str_entry[500];
	struct list_head codec_info_list;
}; 
#endif



// ***********************************************************************
// ** API Function Implementations
// ***********************************************************************

u32 set_output_path(azx_t* chip, u16 stream_id, u16 channels, u32 stream_format)
{

  	u8 node_type      = 0;
  	u8 start_node_id  = 0;
  	u8 end_node_id    = 0;
  	u32 total_nodes   = 0;
  	u32 nid           = 0;

  	int i;
  	s_PIN_CAPs pin_caps;
  	s_WID list_wid;// = NULL;

  	u32 default_device = 0;
  	u32 jack_color     = 0;

	// zero value for channels is acceptible

	if( chip == NULL || stream_id == 0 || stream_format   == 0){
     		printk(KERN_ERR "CODEC-ERROR: one or more of the parameters passed to set_output_path() are invalid!\n");
      		return E_AZX_INVALID_PARAM;
    	}


  	// **** test to make sure root node is AFG ****

  	// ** get AFG node NID **

  	// use total number of nodes area of response to determine function group
  	// type
  	get_sub_node_count(chip, nid, &start_node_id, &end_node_id, &total_nodes);
  	nid = total_nodes;

  	// ** Test for AFG type **
  	node_type = get_func_grp_type(chip, nid);

  	if(AUD_FUNC_GROUP != node_type){
      		snd_printdd(" ################### Node type is not AFG\n");
      		return E_AZX_INVALID_FUNC_GROUP;
    	}

  	// **** get node count for AFG node ****
  	get_sub_node_count(chip, node_type, &start_node_id, &end_node_id, &total_nodes);

  	// Iterate through all the widgets, looking for target
  	// path.

  	for(i = start_node_id; i <= end_node_id; i++){
      		u8 wid_type = get_wid_type(chip, nid);
      		nid = i;
      		snd_printdd(" ################### DBG: wid_type is: 0x%x\n", wid_type);

      		if(wid_type == AUD_PIN_WID){
	  		printk(" ################### ** PW FOUND: NID is: 0x%x **\n", nid);

	  		get_default_device_value(chip, nid, &default_device, &jack_color);

	  		// log color of jack and jack type for future reporting
	  		printk(" ################### DBG: type of jack is: 0x%x\n", default_device);
	  		printk(" ################### DBG: color of line out jack is: 0x%x\n", jack_color);


	  		// get pin caps
	  		get_pin_caps(chip, nid, &pin_caps, NULL);
	  
	  		if(pin_caps.out_cap){
	      			snd_printdd(" ################### DBG: Pin Complex is set for output\n");
	      			list_wid.NID = nid;
	      			list_wid.type = wid_type;
				
	      			if(set_output_wid_connection(chip, list_wid, stream_id, channels, stream_format)){
		  			snd_printdd(" ################### DBG: Successfully set widget connection\n");
		  			break;
				}
	    		}
		}

      		snd_printdd(" ################### DBG: current nid is: 0x%x\n", nid);

    	}



  	// test mast vol incr

  	return E_AZX_CODEC_OK;
}


u32 set_input_path(azx_t* chip, u16 stream_id, u16 channels, u32 stream_format)
{
  	u8 node_type      = 0;
  	u8 start_node_id  = 0;
  	u8 end_node_id    = 0;
  	u32 total_nodes   = 0;
  	u32 nid           = 0;

  	int i;
  	s_WID list_wid;// = NULL;

  	// zero value for channels is acceptible

  	if( chip == NULL || stream_id == 0 || stream_format   == 0){
      		printk(KERN_ERR "CODEC-ERROR: one or more of the parameters passed to set_input_path() are invalid!\n");
      		return E_AZX_INVALID_PARAM;
    	}

  	// **** test to make sure root node is AFG ****

  	// ** get AFG node NID **

  	// use total number of nodes area of response to determine function group
  	// type
  	get_sub_node_count(chip, nid, &start_node_id, &end_node_id, &total_nodes);
  	nid = total_nodes;
  	// TODO: Add logic to check for modem function group

  	// ** Test for AFG type **
  	node_type = get_func_grp_type(chip, nid);

  	if(AUD_FUNC_GROUP != node_type){
      		snd_printdd(" ################### Node type is not AFG\n");
      		return E_AZX_INVALID_FUNC_GROUP;
    	}

  	// **** get node count for AFG node ****
  	get_sub_node_count(chip, node_type, &start_node_id, &end_node_id, &total_nodes);

  	// Iterate through all the widgets, looking for target
  	// path.
  	for(i = start_node_id; i <= end_node_id; i++){
      		u8 wid_type = get_wid_type(chip, nid);
      		nid = i;

      		snd_printdd(" ################### DBG: wid_type is: 0x%x\n", wid_type);

      		if(wid_type == AUD_IN_WID){// ADC
	
	  		snd_printdd(" ################### ** ADC FOUND: NID is: 0x%x **\n", nid);

	  		g_path_nids.ADC_NID = nid;

	  		set_stream_channel(chip, nid, stream_id, channels);
			set_stream_format(chip, nid, stream_format);
			unmute_widget_input(chip, nid);

	  		// used for setting connection list later on
	  		g_ADC_NID = nid;

	  		list_wid.NID = nid;
	  		list_wid.type = wid_type;

	  		if(set_input_wid_connection(chip, list_wid, stream_id, channels, stream_format)){
	      			snd_printdd(" ################### DBG: Successfully set widget connection\n");
	      			break;
	    		}

		}	

      		snd_printdd(" ################### DBG: current nid is: 0x%x\n", nid);

    	}

  	return E_AZX_CODEC_OK;
}




// ***********************************************************************
// ** Internal Codec Discovery Function Implementations
// ***********************************************************************

/**
 * Recursive function used to traverse widget
 * hierarchy for potential connections out.
*/
static u32 set_output_wid_connection(azx_t* chip, s_WID widget, u32 stream_id, u32 channels, u32 stream_format)
{
  	u32 has_cl        = 0;
  	u32 is_long_form  = 0;
  	u32 idx           = 0;

  	u32 conn_len = get_conn_list_len(chip, widget.NID, &is_long_form);

  	snd_printdd(" ################### ***** set_output_wid_connection() *****\n");

  	snd_printdd(" ################### DBG: widget.NID is: 0x%x\n", widget.NID);
  	snd_printdd(" ################### DBG: widget.type is: 0x%x\n", widget.type);

	
  	if(is_long_form){
      		snd_printdd(" ################### DBG: has long form\n");
      		idx = 2;
    	}else{
      		snd_printdd(" ################### DBG: has short form\n");
      		idx = 4;
   	}

  	if(conn_len > 1) { // > 1 == more than one (hardwired) input
        	snd_printdd(" ################### DBG: has connection length > 1\n");
      		has_cl = 1;
      		widget.cl_present = has_cl;
    	}

  	// look at each widget too see if it has amp out
  	// capability.  If it does, un-mute it.
  	if(get_wid_amp_out(chip, widget.NID)){
      		// create new amp entry for mixer interaction later on
      		struct mix_ctl_table_entry * me;
      		s_AMP_CAPS amp_caps;
      
      		snd_printdd(" ################################################### ADDING TO AMP_OUT LIST NID: 0x%x ##\n", widget.NID);

      		if (!(me = kmalloc (sizeof (struct mix_ctl_table_entry), GFP_ATOMIC))){
	  		printk (KERN_ERR "failed to allocate memory for mix_ctl_tbl_entry (ao)! \n");
	  		return E_AZX_MEM_ALLOC_FAIL;
		}

      		get_amp_caps(chip, 1, widget.NID, &amp_caps, NULL);

     		if(amp_caps.num_steps != 0){
	  		strcpy(me->ctl_name, "PCM VOLUME");
	  		me->mix_type = PCM_VOLUME;
		} else if(amp_caps.mute_cap == 1){
	  		strcpy(me->ctl_name, "PCM MUTE");
	  		me->mix_type = PCM_MUTE;
		}else{
	  		strcpy(me->ctl_name, "UNKNOWN");
	  		me->mix_type = PCM_UNKNOWN;
		}

      		me->NID = widget.NID;
     	 	me->index = 0;
      		me->amp_direction = 1;
      		me->data_direction = 1;
      		me->amp_type = 1;
      		me->amp_max_steps = amp_caps.step_size;
      

      		list_add(&me->mix_list, &mix_ctl_table);

      		unmute_widget_output(chip, widget.NID);
    	}



  	if( (widget.type == AUD_OUT_WID) && (widget.NID != 0x0) ){ // DACprintk(" ################### DBG: DAC FOUND!\n");
      		set_stream_channel(chip, widget.NID, stream_id, channels);
      		set_stream_format(chip, widget.NID, stream_format);
      		g_path_nids.DAC_NID = widget.NID;
      		return 1; // DONE
    	}else if(has_cl){
     		// get next entry on list

      		int i,j;
      		int n = 0;

      		s_WID wids[idx];

      		int total_count = 0;

      		snd_printdd(" ################### DBG: widget has a connection list \n");
      
      		for(i = 0; i < conn_len; i+=idx){
	  		
			get_conn_list_entry(chip, wids, idx, widget.NID, n);

	  		for (j = 0; j < idx; j++){
		
	      			if(total_count >= conn_len)
					break;
		      
		      		widget.NID = wids[j].NID;
	      			widget.type = wids[j].type;

	      			if(set_output_wid_connection(chip, widget, stream_id, channels, stream_format)){
		  			// check to make sure widget previously called on has a connection list.
		  			// set_wid_connection could have returned true because a hardwired connection
		  			// was found.
		  			if(widget.cl_present)
						set_connection_select(chip, widget.NID, 0);

		  			return 1;
				}

	      			total_count++;

	    		}

	  		// iterate offset
	  		n += idx;

		}

    	} else {
      		snd_printdd(" ################### DBG: widget has hard wired connection\n");

      		// get connection entry --> hard wired input
      		get_conn_list_entry(chip, &widget, 0, widget.NID, 0);


      		if(set_output_wid_connection(chip, widget, stream_id, channels, stream_format))
			return 1;
    	}

  	return 0;
}

/**
 * Recursive function used to traverse widget
 * hierarchy for potential connections in.
*/
static u32 set_input_wid_connection(azx_t* chip, s_WID widget, u32 stream_id, u32 channels, u32 stream_format)
{
  	u32 has_cl        = 0;
  	u32 is_long_form  = 0;
  	u32 idx           = 0;

  	u32 default_device = 0;
 	u32 jack_color     = 0;

  	// 0x0 == line out
  	// 0x8 == line in
  	// 0x3 == blue
  	// 0x4 == green


  	u32 conn_len = get_conn_list_len(chip, widget.NID, &is_long_form);

	snd_printdd(" ################### ***** set_input_wid_connection() *****\n");

  	snd_printdd(" ################### DBG: widget.NID is: 0x%x\n", widget.NID);
  	snd_printdd(" ################### DBG: widget.type is: 0x%x\n", widget.type);

  	snd_printdd(" ################### DBG: conn_len is %d\n", conn_len);


  	if(is_long_form){
      		snd_printdd(" ################### DBG: has long form\n");
      		idx = 2;
    	}else{
      		snd_printdd(" ################### DBG: has short form\n");
      		idx = 4;
    	}

  	if(conn_len > 1){ // > 1 == more than one (hardwired) input
        	snd_printdd(" ################### DBG: has connection length > 1\n");
      		has_cl = 1;
      		widget.cl_present = has_cl;
    	}

  	// look at each widget too see if it has amp in
  	// capability.  If it does, un-mute it.
  	if(get_wid_amp_in(chip, widget.NID)){
	
      		// create new amp entry for mixer interaction later on
      		struct mix_ctl_table_entry * me;
      		s_AMP_CAPS amp_caps;
      
      		if (!(me = kmalloc (sizeof (struct mix_ctl_table_entry), GFP_ATOMIC))){
		 	printk ("failed to allocate memory for mix_ctl_tbl_entry (ao)! \n");
	  		return E_AZX_MEM_ALLOC_FAIL;
		}

      		get_amp_caps(chip, 1, widget.NID, &amp_caps, NULL);

      		strcpy(me->ctl_name, "PCM CAPTURE");
      		me->mix_type = PCM_CAPTURE;
      		me->NID = widget.NID;
      		me->index = 0;
      		me->amp_direction = 0;
      		me->data_direction = 0;
      		me->amp_type = 0;
      		me->amp_max_steps = amp_caps.step_size;
      
      		list_add(&me->mix_list, &mix_ctl_table);

      		unmute_widget_input(chip, widget.NID);
    	}

 	if( (widget.type == AUD_PIN_WID) && (widget.NID != 0x0) ){ // end point
    		
		snd_printdd(" ################### DBG: PIN WIDGET FOUND!\n");
      		get_default_device_value(chip, widget.NID, &default_device, &jack_color);


		// log color of jack and jack type for future reporting
  		snd_printdd(" ################### DBG: type of jack is: 0x%x\n", default_device);
  		snd_printdd(" ################### DBG: color of line in jack is: 0x%x\n", jack_color);

  		set_pin_wid_in_enable(chip, widget.NID);

  		//set_stream_channel(chip, widget.NID, stream_id, channels);
  		set_stream_format(chip, widget.NID, stream_format);
  		
		return 1; // DONE
		
	}else if(has_cl){
      		
      		// get next entry on list

      		int i,j;
      		int n = 0;
      		s_WID wids[idx];

      		int total_count = 0;

		snd_printdd(" ################### DBG: widget has a connection list \n");
      		snd_printdd(" ################### DBG: idx is: %d\n", idx);

      		for(i = 0; i < conn_len; i+=idx){

	  		get_conn_list_entry(chip, wids, idx, widget.NID, n);

	  		for (j = 0; j < idx; j++){

	      			if(total_count >= conn_len)
					break;

	      			widget.NID = wids[j].NID;
	      			widget.type = wids[j].type;

	      			if(set_input_wid_connection(chip, widget, stream_id, channels, stream_format)){
		  			// check to make sure widget previously called on has a
		  			// connection list.
		  			// set_wid_connection could have returned true because a
		  			// hardwired connection was found.
		  			if(widget.cl_present){
		      				set_connection_select(chip, g_ADC_NID, n+j);
		    			}
		  
		  			return 1;
				}

	      			total_count++;

	    		}


	  	// iterate offset
	  	n += idx;

		}

    	}else{
      		snd_printdd(" ################### DBG: widget has hard wired connection\n");

      		// get connection entry --> hard wired input
      		get_conn_list_entry(chip, &widget, 0, widget.NID, 0);
      		if(set_input_wid_connection(chip, widget, stream_id, channels, stream_format))
      			return 1;
	}

  	return 0;
}


// ***********************************************************************
// ** Utility Function Implementations
// ***********************************************************************

static u32 get_wid_caps(azx_t* chip, u8 w_nid, s_WID_CAPs* w_caps,  u32* resp)
{
	u32 cad = g_audio_codec_addr;
  	u32 direct = 0x0;
  	u32 resp0 = 0;
  	u32 resp0_ex = 0;
  	u32 parameter = 0x09;

	snd_azx_send_corb_cmd(chip, cad, w_nid, direct, GET_PARAMETER, parameter, &resp0, &resp0_ex);
	
  	if(resp != NULL)
    		*resp = resp0;

  	*((u32*)(w_caps)) = resp0;
  	return 0;
}

static u32 get_amp_caps(azx_t* chip, u8 direction, u8 w_nid, s_AMP_CAPS* amp_caps, u32* resp)
{
  	u32 cad = g_audio_codec_addr;
  	u32 direct = 0x0;
  	u32 resp0 = 0;
  	u32 resp0_ex = 0;
  	u32 parameter = 0;

  	if(direction)
    		parameter = 0x12; // out
  	else
    		parameter = 0x0D; // in

	snd_azx_send_corb_cmd(chip, cad, w_nid, direct, GET_PARAMETER, parameter, &resp0, &resp0_ex);

  	if(resp != NULL)
    		*resp = resp0;


  	*((u32*)(amp_caps)) = resp0;

  	return 0;
}

static u32 get_pin_caps(azx_t* chip, u8 pin_nid, s_PIN_CAPs* pin_caps, u32* resp )
{
  	u32 cad = g_audio_codec_addr;
  	u32 direct = 0x0;
  	u32 resp0 = 0;
  	u32 resp0_ex = 0;
  	u32 parameter = 0x0C;

	snd_azx_send_corb_cmd(chip, cad, pin_nid, direct, GET_PARAMETER, parameter, &resp0, &resp0_ex);

  	if(resp != NULL)
    		*resp = resp0;

  	*((u32*)(pin_caps)) = resp0;
  	return 0;
}

static u32 get_pin_ctl_caps(azx_t* chip, u8 pin_nid, s_PW_CNTL* pin_cntl_caps, u32* resp)
{
  	u32 cad = g_audio_codec_addr;
  	u32 direct = 0x0;
  	u32 resp0 = 0;
  	u32 resp0_ex = 0;
  	u32 parameter = 0x0;

	snd_azx_send_corb_cmd(chip, cad, pin_nid, direct, GET_PIN_WIDGET_CONTROL, parameter, &resp0, &resp0_ex);

 	pin_cntl_caps = (s_PW_CNTL*) &resp0;

 	if(resp != NULL)
    		*resp = resp0;

  	*((u32*)(pin_cntl_caps)) = resp0;
  	return 0;
}

static u8 get_wid_type(azx_t* chip, u8 widget_nid)
{
  	u8 type = 0;
  	s_WID_CAPs w_caps;
  	get_wid_caps(chip, widget_nid, &w_caps, NULL);
  	type = w_caps.type;
  	return type;
}

static u8 get_wid_amp_out(azx_t* chip, u8 wid_nid)
{
  	u8 out_amp = 0;
  	s_WID_CAPs w_caps;
  	snd_printdd(" ################### ***** get_wid_amp_out() *****\n");
  	get_wid_caps(chip, wid_nid, &w_caps, NULL);
  	out_amp = w_caps.out_amp;
  	snd_printdd(" ################### out_amp is: 0x%x\n", out_amp);
  	return out_amp;
}

static u8 get_wid_amp_in(azx_t* chip, u8 wid_nid)
{
  	u8 in_amp = 0;
  	s_WID_CAPs w_caps;
  	snd_printdd(" ################### ***** get_wid_amp_in() *****\n");
  	get_wid_caps(chip, wid_nid, &w_caps, NULL);
  	in_amp = w_caps.in_amp;
  	snd_printdd(" ################### in_amp is: 0x%x\n", in_amp);
  	return in_amp;
}



static u32 get_conn_list_len(azx_t* chip, u8 w_nid, u32* is_long_form)
{
  	u32 cad = g_audio_codec_addr;
  	u32 direct = 0x0;
  	u32 resp0 = 0;
  	u32 resp0_ex = 0;
  	u32 parameter = 0x0E;

	snd_azx_send_corb_cmd(chip, cad, w_nid, direct, GET_PARAMETER, parameter, &resp0, &resp0_ex);

  	// get long form bit
  	*is_long_form = get_bit_value(7, resp0);

  	return resp0;
}

static unsigned int get_conn_list_entry(azx_t* chip, s_WID* wid, u32 wid_size, u8 host_nid, u8 index_nid)
{
  	u32 cad = g_audio_codec_addr;
  	u32 direct = 0x0;
  	u32 resp0 = 0;
  	u32 resp0_ex = 0;
  	u8 dep_range_val = 0;
  	u32 parameter = index_nid;

  	snd_printdd(" ################### ***** get_conn_list_entry() *****\n");
  
  	snd_printdd(" ################### DBG: wid_size is: %d\n", wid_size);

	snd_azx_send_corb_cmd(chip, cad, host_nid, direct, GET_CONNECTION_LIST_ENTRY, parameter, &resp0, &resp0_ex);

  	snd_printdd(" ################### DBG: resp0 is: 0x%x\n", resp0);

  	// check for independant or range NID
  
  	if(wid_size == 2)
    		dep_range_val = get_bit_value(15, resp0);
  	else if(wid_size == 4)
    		dep_range_val = get_bit_value(7, resp0);

  	snd_printdd(" ################### DBG: dep_range_val is: 0x%x\n", dep_range_val);

  	if(wid_size == 0){ // zero connection list
        	snd_printdd(" ################### DBG: inside \"if(wid_size == 0)\"\n");
      		wid->NID = resp0;
      		wid->type = get_wid_type(chip, wid->NID);

      		snd_printdd(" ################### DBG: wid->NID is: 0x%x\n", wid->NID);
      		snd_printdd(" ################### DBG: wid->type is: 0x%x\n", wid->type);
    	}else if(dep_range_val){ // nids in a range 
      		// because a range was identified, need to create an array
      		// and ignore passed in array;
      		u8 nid_start = resp0;
      		u8 nid_end = resp0 >> 8;
      		int size = nid_start + nid_end;
      		int i;

      		s_WID wid_ar[size];
      
    		snd_printdd(" ################### DBG: RANGE BIT IS SET, CALCULATING ALL NIDs IN RANGE!!!\n");

      		for(i = nid_start; i > nid_end; i++){
	  		wid_ar[i].NID = i;
	  		wid_ar[i].type = get_wid_type(chip, i);
		}
      
      		wid = wid_ar;
    	}else if(wid_size == 2){ // long form connection list
    		wid[0].NID = resp0;
      		wid[0].type = get_wid_type(chip, wid->NID);
  	
      		wid[1].NID = resp0 >> 8;
      		wid[1].type = get_wid_type(chip,  wid->NID);

    	}else if(wid_size == 4){ // short form connection list
        	wid[0].NID = resp0;
      		wid[0].type = get_wid_type(chip, wid->NID);

      		wid[1].NID = resp0 >> 8;
      		wid[1].type = get_wid_type(chip, wid->NID);

      		wid[2].NID = resp0 >> 16;
      		wid[2].type = get_wid_type(chip, wid->NID);

      		wid[3].NID = resp0 >> 24;
      		wid[3].type = get_wid_type(chip, wid->NID);

    	}

  	snd_printdd(" ################### ***** LEAVING get_conn_list_entry() *****\n");

  	return 0;
}

static u32 get_sub_node_count(azx_t* chip, u8 nid, u8* start_nid, u8* end_nid, u32* total_nodes)
{
  	u32 cad = g_audio_codec_addr;
  	u32 direct = 0x0;
  	u32 resp0 = 0;
  	u32 resp0_ex = 0;
  	u32 parameter = 0x04;

	snd_azx_send_corb_cmd(chip, cad, nid, direct, GET_PARAMETER, parameter, &resp0, &resp0_ex);

 	*start_nid     = resp0 & 0x00FF0000;
  	*total_nodes   = resp0 & 0x000000FF;
  	*end_nid       = *start_nid+*total_nodes;
  
  	return 0;
}

static u32 get_func_grp_type(azx_t* chip, u8 nid)
{
  	u32 cad = g_audio_codec_addr;
  	u32 direct = 0x0;
  	u32 resp0 = 0;
  	u32 resp0_ex = 0;
  	u32 parameter = 0x05;
  	
	snd_azx_send_corb_cmd(chip, cad, nid, direct, GET_PARAMETER, parameter, &resp0, &resp0_ex);

	return resp0;
}

static u32 get_default_device_value(azx_t* chip, u8 pin_wid, u32* default_dev, u32* color)
{
  	u32 cad = g_audio_codec_addr;
  	u32 direct = 0x0;
  	u32 resp0 = 0;
  	u32 resp0_ex = 0;
  	u32 parameter = 0x0;
  	u_WID_CONF_DEF conf;

	snd_azx_send_corb_cmd(chip, cad, pin_wid, direct, GET_CONFIG_DEFAULT_BYTES, parameter, &resp0, &resp0_ex);

  	conf.resp_data = resp0;
  	*default_dev = conf.s_config_default.default_device;
  	*color = conf.s_config_default.color;
  	
	return 0;
}


static u32 set_stream_channel(azx_t* chip, u8 wid_nid, u16 stream_id, u16 channels)
{
  	u32 cad = g_audio_codec_addr;
  	u32 direct = 0x0;
  	u32 resp0 = 0;
  	u32 resp0_ex = 0;
  	u16 parameter = (stream_id << 4) | channels;

  	snd_printdd(" ################### ***** set_stream_channel() *****\n");
  	snd_printdd(" ################### NID is: 0x%x\n", wid_nid);

	snd_azx_send_corb_cmd(chip, cad, wid_nid, direct, SET_CHANNEL_STREAMID, parameter, &resp0, &resp0_ex);

  	return 0;
}

static u32 set_stream_format(azx_t* chip, u8 wid_nid, u32 stream_format)
{
  	u32 cad = g_audio_codec_addr;
  	u32 direct = 0x0;
  	u32 resp0 = 0;
  	u32 resp0_ex = 0;
  	u32 parameter = stream_format;

  	snd_printdd(" ################### ***** set_stream_output_format() *****\n");
  	snd_printdd(" ################### NID is: 0x%x\n", wid_nid);
  	snd_printdd(" ################### stream_format is: 0x%x\n", stream_format);

	snd_azx_send_corb_cmd(chip, cad, wid_nid, direct, SET_STREAM_FORMAT, parameter, &resp0, &resp0_ex);

  	return 0;
}

static u32 unmute_widget_output(azx_t* chip, u8 wid_nid)
{
  	// determine if mix or pin widget, to derive mute mask
  	u8 wid_type = get_wid_type(chip, wid_nid);

	u32 cad = g_audio_codec_addr;
  	u32 direct = 0x0;
  	u32 resp0 = 0;
  	u32 resp0_ex = 0;
  	u32 parameter = 0;
	
  	snd_printdd(" ################### ***** unmute_widget() *****\n");
  	snd_printdd(" ################### NID is: 0x%x\n", wid_nid);

  	// unmute left
 	snd_printdd(" ################### unmute left\n");

  	parameter = 0xB000;

  	if(wid_type == 0x2){ // mixer
        	snd_printdd(" ################### mix wid found, parameter = 0xA040 \n");
      		parameter = 0xB040;
    	}


	snd_azx_send_corb_cmd(chip, cad, wid_nid, direct, SET_AMPLIFIER_GAIN_MUTE, parameter, &resp0, &resp0_ex);

 	return 0;
}


static u32 unmute_widget_input(azx_t* chip, u8 wid_nid)
{
  	// determine if mix or pin widget, to derive mute mask
  	u32 cad = g_audio_codec_addr;
  	u32 direct = 0x0;
  	u32 resp0 = 0;
  	u32 resp0_ex = 0;
  	u32 parameter = 0;

  	snd_printdd(" ################### ***** unmute_widget_input() *****\n");
  	snd_printdd(" ################### NID is: 0x%x\n", wid_nid);

  	// *** unmute left ***
  	parameter = 0xE043;

	snd_azx_send_corb_cmd(chip, cad, wid_nid, direct, SET_AMPLIFIER_GAIN_MUTE, parameter, &resp0, &resp0_ex);

  	return 0;
}

static u32 set_connection_select(azx_t* chip, u8 wid_nid, u8 list_index)
{
  	u32 cad = g_audio_codec_addr;
  	u32 direct = 0x0;
  	u32 resp0 = 0;
  	u32 resp0_ex = 0;
  	u32 parameter = list_index;

  	snd_printdd(" ################### ***** set_connection_select() *****\n");
  	snd_printdd(" ################### NID is: 0x%x\n", wid_nid);
  	snd_printdd(" ################### list_index is: 0x%x\n", list_index);

	snd_azx_send_corb_cmd(chip, cad, wid_nid, direct, SET_CONNECTION_SELECT, parameter, &resp0, &resp0_ex);

  	return 0;
}


static u32 get_bit_value(u32 pos, u32 target)
{
  	unsigned int mask = 1 << pos;
  	unsigned int value = target & mask;
  	return value;
}


static u32  set_pin_wid_in_enable(azx_t* chip, u8 wid_nid)
{
  	u32 cad = g_audio_codec_addr;
  	u32 direct = 0x0;
  	u32 resp0 = 0;
  	u32 resp0_ex = 0;
  	u32 parameter = 0x20; // set 'in enable' bit
  	
  	snd_printdd(" ################### ***** set_pin_wid_in_enabled() *****\n");
	snd_printdd(" ################### NID is: 0x%x\n", wid_nid);

	snd_azx_send_corb_cmd(chip, cad, wid_nid, direct, SET_PIN_WIDGET_CONTROL, parameter, &resp0, &resp0_ex);

	return 0;
}

u32 codec_mem_cleanup(void)
{
  	// cleanup out amp lists
  	struct list_head *list;     /* pointer to list head object */
  	struct list_head *tmp;      /* temporary list head for safe deletion */


  	if (list_empty (&mix_ctl_table)){
      		return E_AZX_CODEC_FAIL;
    	}


  	list_for_each_safe (list, tmp, &mix_ctl_table){
      		struct mix_ctl_table_entry* me = list_entry (list, struct mix_ctl_table_entry, mix_list);
      		list_del (&me->mix_list);
    		kfree(me);
    	}


  	if (list_empty (&mix_ctl_table)) 
    		printk (KERN_ERR "mixer control table is NOT empty! \n");
  
  	return E_AZX_CODEC_OK;
}



u32 print_mix_table(void)
{
  	struct list_head *list;
  	struct list_head *tmp; 

  	if (list_empty (&mix_ctl_table)){
      		printk (KERN_INFO "mixer table is empty! \n");
      		return E_AZX_CODEC_FAIL;
    	}


  	printk(KERN_INFO " ######################### mixer table content ############################ \n");
  	printk(KERN_INFO " ## MIX-TYPE | CTL NAME | NID | IDX | AMP-DIR | DATA-DIR | AMP-TYPE | AMP-MAX-STEPS ## \n");
  
  	list_for_each_safe (list, tmp, &mix_ctl_table){
      		struct mix_ctl_table_entry* me = list_entry (list, struct mix_ctl_table_entry, mix_list);
      		printk(KERN_INFO " %d   |  %s  |   0x%x   |   %d   |   %d   |    %d    |    %d    |       %d       \n",me->mix_type,  me->ctl_name, me->NID, me->index, me->amp_direction, me->data_direction, me->amp_type, me->amp_max_steps);
    	}

  	printk(KERN_INFO " ########################################################################## \n");

 	return E_AZX_CODEC_OK;
}


struct mix_ctl_table_entry* get_mix_entry(u32 mix_type)
{
  	struct list_head *list;
  	struct list_head *tmp; 
  	struct mix_ctl_table_entry* me = NULL;

  	if (list_empty (&mix_ctl_table)){
     		snd_printdd("mixer table is empty! \n");
      		return NULL;
	}

  	list_for_each_safe (list, tmp, &mix_ctl_table){
      		me = list_entry (list, struct mix_ctl_table_entry, mix_list);
      		if(me->mix_type == mix_type)
	 		return me;
    	}

  	return NULL;
}

u32 set_audio_codec_address(azx_t* chip)
{
  	int i;
  	u8 val = 0;

  	u32 codec_mask = snd_azx_get_codec_mask(chip);

  	for(i = 0; i < codec_mask; i++){
      		val = get_bit_value(i, codec_mask);

      		// NOTE: currently only allowing one codec to be used

      		if(val){ // codec found
			g_audio_codec_addr = i;
	  		break;	  
		}
    	}
	return E_AZX_CODEC_OK;
}


u8 get_dac_nid(void)
{
	return g_path_nids.DAC_NID;
}

u8 get_adc_nid(void)
{
	return g_path_nids.ADC_NID;
}

u32 get_audio_codec_address(void)
{
  	return g_audio_codec_addr;
}

#ifdef USE_PROC_CODEC_INFO
u32 collect_codec_info(azx_t* chip)
{
  	u32 cad           = g_audio_codec_addr;
  	u32 nid           = 0;
  	u32 direct        = 0;
  	u32 parameter     = 0;
  	u32 resp0         = 0;
  	u32 resp0_ex      = 0;
  	u32 total_nodes   = 0;
  	u32 conn_len      = 0;
  	u32 idx           = 0;
  	u8  node_type     = 0;
  	u8  start_node_id = 0;
  	u8  end_node_id   = 0;
  	int vend_id       = 0;
  	int rev_id        = 0;
	  
  	int i;

  	s_WID_CAPs wid_caps;
  	s_AMP_CAPS amp_caps;
  	s_PIN_CAPs pin_caps;
  	s_PW_CNTL pin_ctl_caps;
  	u32 is_long_form = 0;

  	char wid_type_str[80];

  	// used to store in list
  	char proc_str_entry[500];

  	snd_printdd ("***** collect_codec_info() *****\n");

  	if(g_audio_codec_addr == 0)
     		snd_printdd("######### ERROR -- CODEC ADDRESS NOT SET ##########\n");

  	// output initial XML
  	store_codec_info_entry("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
  
 
  	// get codec vendor ID
	snd_azx_send_corb_cmd(chip, cad, nid, direct, GET_PARAMETER, parameter, &resp0, &resp0_ex);

  	vend_id = resp0 & 0x0000FFFF;
	

  	// get codec revision number
  	parameter = 0x02;
	snd_azx_send_corb_cmd(chip, cad, nid, direct, GET_PARAMETER, parameter, &resp0, &resp0_ex);

  	rev_id = resp0 & 0x0000FFFF;

  	snprintf(proc_str_entry, sizeof(proc_str_entry),
		 "<codec address=\"0x%x\" vendor=\"0x%X\" revision=\"0x%X\">",
		 g_audio_codec_addr, vend_id, rev_id);
  	store_codec_info_entry(proc_str_entry);

  	store_codec_info_entry("<function_group>");
  
  	// **** test to make sure root node is AFG ****

  	// ** get AFG node NID **

  	// use total number of nodes area of response to determine function group 
  	// type
  	get_sub_node_count(chip, nid, &start_node_id, &end_node_id, &total_nodes);
  	nid = total_nodes;    
 
  	// ** Test for AFG type **
  	node_type = get_func_grp_type(chip, nid);

  	if(0x1 != node_type){
      		snd_printdd("Node type is not AFG\n");
      		return 0;
    	}
  
    	// **** get node count for AFG node ****
  	get_sub_node_count(chip, node_type, &start_node_id, &end_node_id, &total_nodes);
  
  	// widget IDs will most likely never start at zero
  	if(start_node_id == 0)
    		start_node_id++;

  	snprintf(proc_str_entry, sizeof(proc_str_entry),
		 "<audio_function_group start_node_id=\"0x%X\" end_node_id=\"0x%X\" total_nodes=\"%d\">",
		 start_node_id, end_node_id, total_nodes);
  	store_codec_info_entry(proc_str_entry);

  	// Iterate through all the widgets, looking for output
  	// path.
  	for(i = start_node_id+1; i <= end_node_id; i++){
      		u32 wid_caps_resp = 0;
      		u32 amp_caps_resp = 0;
      		u32 pin_caps_resp = 0;
      		u32 pin_ctl_caps_resp = 0;

      		nid = i;

      		// retrieve string representation of widget type
      		get_wid_type_string(get_wid_type(chip, nid), wid_type_str);

      		// get wid caps
      		get_wid_caps(chip, nid, &wid_caps, &wid_caps_resp);

      		// get amp caps
      		get_amp_caps(chip, 1, nid, &amp_caps, &amp_caps_resp);

      		// get pin caps
      		get_pin_caps(chip, nid, &pin_caps, &pin_caps_resp);

      		// get pin ctl caps
      		get_pin_ctl_caps(chip, nid, &pin_ctl_caps, &pin_ctl_caps_resp);

      		snprintf(proc_str_entry, sizeof(proc_str_entry),
			 "<widget nid=\"0x%X\" type=\"%s\" wid_caps=\"0x%X\" amp_caps=\"0x%X\" pin_caps=\"0x%X\" pin_ctl_caps=\"0x%X\">",
			 nid, wid_type_str, wid_caps_resp, amp_caps_resp, pin_caps_resp, pin_ctl_caps_resp);
      		store_codec_info_entry(proc_str_entry);
     
      		// get conn list len
		if (wid_caps.conn_list)
	      		conn_len = get_conn_list_len(chip, nid, &is_long_form);
		else
			conn_len = 0;
      		if(is_long_form)
			idx = 2;
     		else
			idx = 4;

      		snprintf(proc_str_entry, sizeof(proc_str_entry),
			 "<connection_list length=\"%d\" long_form=\"%d\" index=\"%d\">",
			 conn_len, is_long_form, idx);
      		store_codec_info_entry(proc_str_entry);

      
      		if(conn_len){
	  		s_WID wids[idx];

	  		int i,j;
	  		int n = 0;
	  		u8 search_nid = nid;
	  		int total_count = 0;
	  
	  		// get conn list entry nids
	  		for(i = 0; i < conn_len; i+=idx){

	      			// NOTE: search_nid does not need to be updated.
	      			//       The index (n) controls what elements in
	      			//       the connection list to grab

	      			get_conn_list_entry(chip, wids, idx, search_nid, n);
	  
	     			for (j = 0; j < idx; j++){
		  
		  			if(total_count >= conn_len)
		    				break;

		  			snprintf(proc_str_entry, sizeof(proc_str_entry),
						 "<entry nid=\"0x%X\"/>", wids[j].NID);
		  			store_codec_info_entry(proc_str_entry);

		  			total_count++;
				}


	      		// iterate offset
	      		n+=idx;

	    		}

	
		}
      
      
      		store_codec_info_entry("</connection_list>");

      		store_codec_info_entry("</widget>");
    
    	}

  	store_codec_info_entry("</audio_function_group>");

  	store_codec_info_entry("</function_group>");

  	store_codec_info_entry("</codec>");

  	return 0;
}
#endif


void get_wid_type_string(u8 wid_value, char* type_str)
{
  	switch(wid_value){
    		case 0x0:
      			strcpy(type_str, "Audio Output");
      			break;
    		case 0x1:
      			strcpy(type_str, "Audio Input");
      			break;
    		case 0x2:
      			strcpy(type_str, "Audio Mixer");
      			break;
    		case 0x3:
      			strcpy(type_str, "Audio Selector");
      			break;
    		case 0x4:
      			strcpy(type_str, "Pin Complex");
      			break;
    		case 0x5:
      			strcpy(type_str, "Power Widget");
      			break;
    		case 0x6:
      			strcpy(type_str, "Volume Knob Widget");
      			break;
    		case 0x7:
      			strcpy(type_str, "Beep Generator Widget");
      			break;
    		case 0xf:
      			strcpy(type_str, "Vendor Defined Widget");
      			break;
    		default:
      			strcpy(type_str, "UNKOWN Widget");
      			break;
    	}
}


#ifdef USE_PROC_CODEC_INFO
u32 store_codec_info_entry(char* str_codec_info)
{
  	struct codec_info_entry * ce;
      
  	if (!(ce = kmalloc (sizeof (struct codec_info_entry), GFP_ATOMIC))){
      		printk (KERN_ERR "failed to allocate memory for codec_info_entry! \n");
      		return 0;
    	}

  	strcpy(ce->str_entry, str_codec_info);
  
  	list_add(&ce->codec_info_list, &codec_info_head);

  	return 0;
}

u32 cleanup_codec_info_list(void)
{
  	// cleanup out amp lists
  	struct list_head *list;     /* pointer to list head object */
  	struct list_head *tmp;      /* temporary list head for safe deletion */


  	if (list_empty (&codec_info_head)){
      		return 0;
    	}


  	list_for_each_safe (list, tmp, &codec_info_head){
      		struct codec_info_entry* ce = list_entry (list, struct codec_info_entry, codec_info_list);
      		list_del (&ce->codec_info_list);
      		kfree(ce);
    	}


  	if (list_empty (&codec_info_head)) 
    		printk (KERN_ERR "codec_info_head is NOT empty! \n");
  
  	return 0;
}

void print_codec_info_list(void)
{
  	struct list_head *list;
  	struct list_head *tmp; 

  	if (list_empty (&codec_info_head)){
      		printk (KERN_INFO "codec_info_head is empty! \n");
    	}

  	list_for_each_safe (list, tmp, &codec_info_head){
      		struct codec_info_entry* ce = list_entry (list, struct codec_info_entry, codec_info_list);
      		printk (KERN_INFO "str entry is: %s\n", ce->str_entry);
    	}

}


// ***********************************************************************
// ** PROC FILE SYSTEM INTERACTION
// ***********************************************************************
loff_t items = 1;

void *codecinf_seq_start (struct seq_file *sf, loff_t * pos)
{
  	void *results = NULL;

    
  	if (*pos < items){
      		results = (void *) &jiffies;
    	}else{
      		results = NULL;
    	}

  	return (results);
}

void *codecinf_seq_next (struct seq_file *sf, void *v, loff_t * pos)
{
  	void *results;
  
  	(*pos)++;
  	if (*pos < items) {
    		unsigned long const future = jiffies + (HZ / 3);
  
    	while (time_before (jiffies, future)){
		/* Eeech, busy waiting */
      	}
    	results = (void *) &jiffies;
	
  	}else{
      		results = NULL;
    	}

  	return (results);
}

// stop place holder
void codecinf_seq_stop (struct seq_file *sf, void *v)
{
  /* Nothing to do here */
}

int codecinf_seq_show (struct seq_file *sf, void *v    /* jiffies in disquise */)
{
  	int results;
  
  	struct list_head *list;
  	//struct list_head *tmp; 
  
  	if (list_empty (&codec_info_head)){
      		// printk ("codec_info_head is empty! \n");
      		return 0;
    	}

  	list_for_each_prev (list, &codec_info_head){
      		struct codec_info_entry* ce = list_entry (list, struct codec_info_entry, codec_info_list);
      		seq_printf (sf, "%s\n", ce->str_entry);
    	}
        
  	results = 0;
  	return (results);
}

int proc_codecinf_open (struct inode *inode, struct file *file)
{
	return (seq_open (file, &proc_codecinf_seq_ops));
}
#endif

