/*******************************************************************************
 *
 *  azx.c - Implementation of primary alsa driver code base for Intel HD Audio.
 *
 *  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.
 *
 *  CONTACTS:
 *
 *  Matt Jared		matt.jared@intel.com
 *  Andy Kopp		andy.kopp@intel.com
 *  Dan Kogan		dan.d.kogan@intel.com
 *
 *
 *  azx.c,v 1.11 2004/11/10 09:05:50 cladisch Exp
 *
 *  azx.c,v
 *  cladisch
 *  2004/11/10 09:05:50
 *
 ******************************************************************************/

#include <sound/driver.h>
#include <asm/io.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/info.h>
#include <sound/initval.h>
#include <sound/control.h>

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


static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;

module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for Intel HD audio interface.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for Intel HD audio interface.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable Intel HD audio interface.");

MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("{{Intel, ICH6},"
			 "{Intel, ICH6M}}");
MODULE_DESCRIPTION("Azalia driver");

#define AZX_REG_SIZE 1024*16
#define MAX_ICH6_DEV 8
#define AZX_MAX_FRAG 32	// can be any number no restriction...it is a matter of memory usage
#define AZX_CORB_SIZE 1024
#define AZX_RIRB_SIZE 2048
#define BUFFSIZE 1024*32
#define POS_BUFF_SIZE 8*8

// for mixer support
#define INPUT 0
#define OUTPUT 1
#define MASTER_FRONT_PLAYBACK_PATH 0x0
#define CD_PLAYBACK_PATH 0x1
#define LINE_PLAYBACK_PATH 0x2
#define MIC1_PLAYBACK_PATH 0x3
#define CAPTURE_GAIN 0x4
#define MASTER_FRONT_MUTE 0x5
#define PCM_FRONT_PATH 0x6
#define PCM_REAR_PATH 0x7
#define PCM_CLFE_PATH 0x8

// for pcm support
#define get_azx_dev(substream) (azx_dev_t*)(substream->runtime->private_data)
#define AZX_START_DMA 0x02

// stream tags
#define OUT_STREAM0_TAG 0x100000
#define OUT_STREAM1_TAG 0x200000
#define OUT_STREAM2_TAG 0x300000
#define OUT_STREAM3_TAG 0x400000
#define IN_STREAM0_TAG 0xA00000
#define IN_STREAM1_TAG 0xB00000
#define IN_STREAM2_TAG 0xC00000
#define IN_STREAM3_TAG 0xD00000

// frequency supported
#define FREQENCY_192K 0x1800
#define FREQENCY_96K 0x0800
#define FREQENCY_48K 0x0000
#define FREQENCY_44K 0x4000

// bits per sample
#define BITS_PER_SAMPLE_8 0x00
#define BITS_PER_SAMPLE_16 0x10
#define BITS_PER_SAMPLE_20 0x20
#define BITS_PER_SAMPLE_24 0x30
#define BITS_PER_SAMPLE_32 0x40

// register offset for each stream
#define IN_STREAM0_REG_OFF 0x80
#define IN_STREAM1_REG_OFF 0xA0
#define IN_STREAM2_REG_OFF 0xC0
#define IN_STREAM3_REG_OFF 0xE0
#define OUT_STREAM0_REG_OFF 0x100
#define OUT_STREAM1_REG_OFF 0x120
#define OUT_STREAM2_REG_OFF 0x140
#define OUT_STREAM3_REG_OFF 0x160

// interrupt mask for each stream for global interrupt register
#define IN_STREAM0_INT_MASK 0x1
#define IN_STREAM1_INT_MASK 0x2
#define IN_STREAM2_INT_MASK 0x4
#define IN_STREAM3_INT_MASK 0x8
#define OUT_STREAM0_INT_MASK 0x10
#define OUT_STREAM1_INT_MASK 0x20
#define OUT_STREAM2_INT_MASK 0x40
#define OUT_STREAM3_INT_MASK 0x80

// stream int mask
#define STREAM_INT_MASK 0x1C

// RIRB int mask
#define RIRB_INT_MASK 0x5

// STATESTS int mask
#define STATESTS_INT_MASK 0x7F

// stream reset bit
#define STREAM_REST_BIT 0x01

// stream interrupt enable bit in the stream interrupt enable register
#define IN_STREAM0_INT_EN_BIT 0x01
#define IN_STREAM1_INT_EN_BIT 0x02
#define IN_STREAM2_INT_EN_BIT 0x04
#define IN_STREAM3_INT_EN_BIT 0x08
#define OUT_STREAM0_INT_EN_BIT 0x10
#define OUT_STREAM1_INT_EN_BIT 0x20
#define OUT_STREAM2_INT_EN_BIT 0x40
#define OUT_STREAM3_INT_EN_BIT 0x80
#define ALL_STREAM_INT_EN 0xFF

// stream controller int mask
#define CONTROLLER_INT_MASK 0x7FFFFFFF

// stream DMA start bit
#define DMA_START_BIT 0x02

// Ring buffer DMA run bit
#define RINGBUFFER_DMA_EN 0x02

// Ring buffer hw read pointer clear
#define RINGBUFFER_RP_CLR 0x80

// Ring buffer hw write pointer clear
#define RINGBUFFER_WP_CLR 0x80

// controller interrupt enable bit
#define CONTROLLER_INT_EN_BIT 0x40000000

// global interrupt enable bit
#define GLOBAL_INT_EN_BIT 0x80000000

// number of channels for format value
#define FORMAT_VAL_1CH 0x00
#define FORMAT_VAL_2CH 0x01
		
// amplifier select parameter for amp control
#define PUT_OUT_AMP_L_SELECT 0xA000
#define PUT_OUT_AMP_R_SELECT 0x9000
#define PUT_OUT_AMP_LR_SELECT 0xB000
#define PUT_IN_AMP_L_SELECT 0x6000
#define PUT_IN_AMP_R_SELECT 0x5000
#define PUT_IN_AMP_LR_SELECT 0x7000

#define GET_OUT_AMP_L_SELECT 0xA000
#define GET_OUT_AMP_R_SELECT 0x8000
#define GET_IN_AMP_L_SELECT 0x2000
#define GET_IN_AMP_R_SELECT 0x0000

typedef struct {

        u32* bdl_v_addr;	// virtual address of the BDL
        dma_addr_t bdl_p_addr;		// physical address of the BDL

	dma_addr_t playbuff_p_addr;	// physical address of the playbuffer used for programming the BDL entries

	unsigned int size;		// size of the play buffer
	unsigned int fragsize;		// size of each period
	unsigned int position;		// position in the play buffer
	unsigned int frags;		// number for period in the play buffer

	unsigned int sd_off;		// stream descriptor offset relative to chip->remap_addr

	u32 sd_int_sta_mask;	// stream int status mask for int status register - offset 0x24h

	// pcm support
	snd_pcm_substream_t* substream;
	unsigned int lvi;		// last valid index of the BDL
	unsigned int cvi;		// current valid index of the BDL
	u32 format_val;		// format value to be set in the controller and the codec
	unsigned int lvi_frag;		// this keeps track of the last fragment that was programmed into the BDL entry

	unsigned int device;
	// for single dac/adc setup
	u32 stream_tag;


} azx_dev_t;

typedef struct {
	// ring buffer indices
	unsigned int wp;		// write pointer
	unsigned int rp;		// read pointer

	// data about the ring buffer
	unsigned int	max_entries;	// number of entries
	unsigned int	entry_size;	// bytes/entry 2 or 4

	// ring buffer addresses
	u32*		rb_v_addr;
	dma_addr_t	rb_p_addr;

	// offsets for ring buffer-specific registers
	unsigned int	ctl_off;	// control reg offset relative to chip->rempa_addr
	unsigned int	sts_off;	// status reg offset relative to chip->rempa_addr
} azx_rb_t;


struct snd_azx {
	snd_card_t* card;
	struct pci_dev* pci;

	// pci resources
	unsigned long addr;
	void __iomem *remap_addr;
	struct resource* res;
	int irq;

	// spin lock
	spinlock_t reg_lock;

	// Azalia specific
	azx_rb_t* corb;
	azx_rb_t* rirb;

	u32* bdl_v_addrs;
	dma_addr_t bdl_p_addrs;

	azx_dev_t azx_dev[MAX_ICH6_DEV];

	u32 int_sta_mask;	// interrupt status mask for int status register - offset 0x24h

	u16 codec_mask;
	unsigned int num_codecs;

	// mixer stuff
	unsigned int current_capture_source_val;
	
	cap_source_t cap_source_info;
	
	azx_mix_elem_t mix_elements[AZX_MAX_MIX_ELEMS];
};


enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 };

// prototypes
static int __devinit snd_azx_probe(struct pci_dev* pci, const struct pci_device_id* pci_id);
static void __devexit snd_azx_remove(struct pci_dev* pci);
static int snd_azx_init_chip(azx_t* chip);

static azx_rb_t* snd_azx_rb_create(azx_t* chip, int size);
static void snd_azx_rb_delete(azx_t* chip, int size);
static int snd_azx_rb_init(azx_t* chip, int size, azx_rb_t* rb);

static int snd_azx_corb_add_cmd(azx_t* chip, u32 cad, u32 nid, u32 direct, u32 verb, u32 parameter);
static int snd_azx_corb_update_wp(azx_t* chip);

static int snd_azx_rirb_get_single_response(azx_t* chip, u32* lower_response, u32* upper_response);

static int snd_azx_util_reset_controller(azx_t* chip);
// static void snd_azx_stream_start(void __iomem *azx_base, unsigned int sd_base);
static void snd_azx_stream_stop(void __iomem *azx_base, unsigned int sd_base);

// static int snd_azx_controller_setup_sdi0(azx_t* chip);
// static int snd_azx_controller_setup_sdo0(azx_t* chip);

static int snd_azx_corb_add_cmd(azx_t* chip, u32 cad, u32 nid, u32 direct, u32 verb, u32 parameter);
static int snd_azx_corb_update_wp(azx_t* chip);
static int snd_azx_rirb_get_single_response(azx_t* chip, u32* lower_response, u32* upper_response);

static void snd_azx_int_display(azx_t* chip);
static void snd_azx_int_disable(azx_t* chip);
static void snd_azx_int_enable(azx_t* chip);
static void snd_azx_int_clear(azx_t* chip);

// pcm support
static int __devinit snd_azx_new_pcm(azx_t* chip);

static int snd_azx_new_cap_select_control(azx_t* chip);

// mixer call backs
static int snd_azx_cap_select_callback_put(snd_kcontrol_t* kcontrol, snd_ctl_elem_value_t* ucontrol);
static int snd_azx_cap_select_callback_get(snd_kcontrol_t* kcontrol, snd_ctl_elem_value_t* ucontrol);

/* Get the upper 32bit of the given dma_addr_t
 * Compiler should optimize and eliminate the code if dma_addr_t is 32bit
 */
#define upper_32bit(addr) (sizeof(addr) > 4 ? (u32)((addr) >> 32) : (u32)0)

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


// PCI IDssnd_azx
static struct pci_device_id snd_azx_ids[] = {
	{0x8086, 0x2668, PCI_ANY_ID, PCI_ANY_ID, 0, 0 , 0x8086},
	{0,}
};
MODULE_DEVICE_TABLE(pci, snd_azx_ids);

// pci_driver definition
static struct pci_driver driver = {
	.name = "ICH6 Azalia HDA",
	.id_table = snd_azx_ids,
	.probe = snd_azx_probe,
	.remove = __devexit_p(snd_azx_remove),
};

static inline void snd_azx_update(azx_t *chip, azx_dev_t *azx_dev)
{
	u32* bdl_v_addr = azx_dev->bdl_v_addr;
	dma_addr_t new_addr;

	azx_dev->position += azx_dev->fragsize;
	azx_dev->position %= azx_dev->size;

	azx_dev->lvi %= AZX_MAX_FRAG;
	azx_dev->lvi_frag %= azx_dev->frags;

	// update the BDL entry that just got processed with a new address
	new_addr = azx_dev->playbuff_p_addr + azx_dev->lvi_frag * azx_dev->fragsize;
	bdl_v_addr[azx_dev->lvi * 4] = cpu_to_le32((u32)new_addr);
	bdl_v_addr[azx_dev->lvi * 4 + 1] = cpu_to_le32(upper_32bit(new_addr));

	azx_dev->lvi++;
	azx_dev->lvi_frag++;

	if (azx_dev->substream) {
		spin_unlock(&chip->reg_lock);
		snd_pcm_period_elapsed(azx_dev->substream);
		spin_lock(&chip->reg_lock);

	}
}


static irqreturn_t snd_azx_interrupt(int irq, void* dev_id, struct pt_regs *regs)
{
	azx_t* chip = dev_id;
	void __iomem *azx_base = chip->remap_addr;
	azx_dev_t* azx_dev;

	u32 intstatus;
	u8 testval;
	unsigned int count;
	u8 streamval;

	spin_lock(&chip->reg_lock);

	intstatus = readl(azx_base + AZX_REG_L_INTSTS);

	if (intstatus == 0) {
		spin_unlock(&chip->reg_lock);
		return  IRQ_RETVAL(intstatus);
	}
	
	for (count = 0; count < MAX_ICH6_DEV; count++) {
		azx_dev = &chip->azx_dev[count];
		if (intstatus & azx_dev->sd_int_sta_mask) {
			streamval = STREAM_INT_MASK;
//			chip->isr_count++;
			writeb(streamval, azx_base + azx_dev->sd_off + AZX_REG_B_SD_STS);
			snd_azx_update(chip, azx_dev);
		}
	}

	// clear rirb int
	testval = readb(azx_base + AZX_REG_B_RIRBSTS) & RIRB_INT_MASK;
        if (testval) {
		writeb(RIRB_INT_MASK, azx_base + AZX_REG_B_RIRBSTS);
        }

/*
	// clear state status int
	testval = readw(azx_base + AZX_REG_W_STATESTS) & 0x4;
	if (testval) {
		snd_printdd("clearing statests\n");
		u8 stateval = 0x4;
		writeb(stateval, azx_base + AZX_REG_W_STATESTS);
        }
*/
	spin_unlock(&chip->reg_lock);
	
	return IRQ_HANDLED;
}

static int snd_azx_free(azx_t* chip)
{
	unsigned int size;

	if (chip->remap_addr) {
		// stop playback stream
		azx_dev_t* azx_dev = &chip->azx_dev[SDO0];
		snd_azx_stream_stop(chip->remap_addr, azx_dev->sd_off);

		// stop capture stream
		azx_dev = &chip->azx_dev[SDI0];
		snd_azx_stream_stop(chip->remap_addr, azx_dev->sd_off);

		// disable interrupts
		snd_azx_int_disable(chip);
		snd_azx_int_clear(chip);

		// wait a little for interrupts to finish
		msleep(10);

		iounmap(chip->remap_addr);
	}

	if (chip->irq >= 0) {
		free_irq(chip->irq, (void*)chip);
	}

	if (chip->corb) {
		snd_azx_rb_delete(chip, AZX_CORB_SIZE);
	}

	if (chip->rirb) {
		snd_azx_rb_delete(chip, AZX_RIRB_SIZE);
	}

	if (chip->bdl_v_addrs) {
		size = 16 * AZX_MAX_FRAG * MAX_ICH6_DEV;  // size in bytes
		pci_free_consistent(chip->pci, size, chip->bdl_v_addrs, chip->bdl_p_addrs);
	}

	pci_release_regions(chip->pci);
	kfree(chip);

	return 0;
}

static int snd_azx_dev_free(snd_device_t* device)
{
	return snd_azx_free(device->device_data);
}

static int __devinit snd_azx_create(snd_card_t* card, struct pci_dev* pci, azx_t** rchip)
{
	azx_t *chip;
	int err = 0;
	static snd_device_ops_t ops = {
		.dev_free = snd_azx_dev_free,
	};

	*rchip = NULL;
	
	// PCI stuff here
	if ((err = pci_enable_device(pci)) < 0) {
		return err;
	}

	chip = kcalloc(1, sizeof(*chip), GFP_KERNEL);
	
	if (NULL == chip) {
		printk(KERN_ERR "Can not allocate memory for chip structure size %i\n", sizeof(azx_t));
		return -ENOMEM;
	}

	spin_lock_init(&chip->reg_lock);

	chip->card = card;

	chip->pci = pci;
	chip->irq = -1;

	// Get PCI resources
	if ((err = pci_request_regions(pci, "ICH HD audio")) < 0) {
		kfree(chip);
		return err;
	}
	chip->addr = pci_resource_start(pci,0);
	chip->remap_addr = ioremap_nocache(chip->addr, pci_resource_len(pci,0));
	if (chip->remap_addr == NULL) {
		printk(KERN_ERR "FAILD: ioremap_nocache(chip->addr, AZX_REG_SIZE)\n");
		snd_azx_free(chip);
	}

	if (request_irq(pci->irq, snd_azx_interrupt, SA_INTERRUPT|SA_SHIRQ, card->shortname, (void*)chip)) {
		printk(KERN_ERR "FAILED: unable to grab IRQ %d\n", pci->irq);
		snd_azx_free(chip);
		return -EBUSY;
	}
	chip->irq = pci->irq;

	pci_set_master(pci);
	synchronize_irq(chip->irq);

	// make call to initialize codec and controller
	snd_azx_init_chip(chip);

	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) <0) {
		printk(KERN_ERR "Error creating device [card]!\n");
		snd_azx_free(chip);
		return err;
	}

	*rchip = chip;

	return err;
}

static int __devinit snd_azx_create_mixer_vol(azx_t* chip, azx_mix_elem_t mix_elem)
{
	int err;
	
	snd_kcontrol_new_t mix_control = AZX_VOLUME(mix_elem.label, mix_elem.nid, mix_elem.index, mix_elem.direction, mix_elem.max_vol, mix_elem.type, mix_elem.path);
	
	if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&mix_control, chip))) < 0) {
		printk(KERN_ERR "Error: Cannot add mixer vol control [%i]: %i\n", mix_elem.path, err);
		return err;
	}

	return err;
}	


static int __devinit snd_azx_create_mixer_sw(azx_t* chip, azx_mix_elem_t mix_elem)
{
	int err;
	
	snd_kcontrol_new_t mix_control = AZX_MUTE(mix_elem.label, mix_elem.nid, mix_elem.index, mix_elem.direction, mix_elem.max_vol, mix_elem.type, mix_elem.path);
	
	if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&mix_control, chip))) < 0) {
		printk(KERN_ERR "Error: Cannot add mixer sw control [%i]: %i\n", mix_elem.path, err);
		return err;
	}

	return err;
}	

static int __devinit snd_azx_create_mixer(azx_t* chip)
{
	int err = 0;

	int idx;
	azx_mix_elem_t temp_elem;	
	
	for (idx = 0; idx < AZX_MAX_MIX_ELEMS; idx++) {
		temp_elem = chip->mix_elements[idx];
		if (temp_elem.max_vol > 0) {
			if (temp_elem.type) {
				err = snd_azx_create_mixer_sw(chip, temp_elem);
			} else {
				err = snd_azx_create_mixer_vol(chip, temp_elem);
			}
		}
	}

	// caputure source
	if ((err = snd_azx_new_cap_select_control(chip)) < 0) {
		printk(KERN_ERR "Error returned from snd_azx_new_cap_select_control: %i\n", err);
		return err;
	}
	
	return err;
}

static int __devinit snd_azx_probe(struct pci_dev* pci, const struct pci_device_id* pci_id)
{
	static int dev;
	snd_card_t *card;
	azx_t *chip;
	snd_info_entry_t *entry;
	int err = 0;

	if (dev >= SNDRV_CARDS)
		return -ENODEV;
	if (!enable[dev]) {
		dev++;
		return -ENOENT;
	}

	card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
	if (NULL == card) {
		printk(KERN_ERR "Error creating card!\n");
		return -ENOMEM;
	}

	if ((err = snd_azx_create(card, pci, &chip)) < 0) {
		printk(KERN_ERR "Error returned from snd_azx_create: %i\n", err);
		snd_card_free(card);
		return err;
	}

	strcpy(card->driver, "Azalia");
	strcpy(card->shortname, "ICH6 HDA");
	sprintf(card->longname, "%s at 0x%lx irq %i", card->shortname, chip->addr, chip->irq);

	// create other devices here (ie PCM, Mixer, etc)
	if ((err = snd_azx_new_pcm(chip)) < 0) {
		printk(KERN_ERR KERN_ERR "Error returned from snd_azx_new_pcm: %i\n", err);
		snd_card_free(card);
		return err;
	}

	// mixer control
	if ((err = snd_azx_create_mixer(chip)) < 0) {
		printk(KERN_ERR "Error returned from snd_azx_create_mixer: %i\n", err);
		snd_card_free(card);
		return err;
	}

	if (! snd_card_proc_new(card, CODEC_INFO_PROC_FILENAME, &entry))
		snd_info_set_text_ops(entry, chip, 1024, snd_azx_print_codec_info);

	if ((err = snd_card_register(card)) < 0) {
		printk(KERN_ERR "Error registering card!\n");
		return err;
	}

	pci_set_drvdata(pci, card);
	dev++;

	return err;
}

static void __devexit snd_azx_remove(struct pci_dev* pci)
{
	snd_card_free(pci_get_drvdata(pci));
	pci_set_drvdata(pci, NULL);
}

static int __init alsa_card_azx_init(void)
{
	int err = 0;

	if ((err = pci_module_init(&driver)) < 0) {
		printk("Azalia chip not found!!!\n");
		return err;
	}

	return err;
}

static void __exit alsa_card_azx_exit(void)
{
	pci_unregister_driver(&driver);
}

module_init(alsa_card_azx_init)
module_exit(alsa_card_azx_exit)

struct azx_sd_info {
	u32	sd_offset;		// interrupt mask for register 0x24h (Int register)
	u32	sd_int_sta_mask;	// stream descriptor offset relative to chip->remap_addr
};

static int snd_azx_init_chip(azx_t* chip)
{
	unsigned int index;
	unsigned int size;
	azx_dev_t* azx_dev;
	u32* bdl_v_base;
	dma_addr_t bdl_p_base;
	
	static struct azx_sd_info azx_sd_regs[8] = {
		{ IN_STREAM0_REG_OFF, IN_STREAM0_INT_MASK},
		{ IN_STREAM1_REG_OFF, IN_STREAM1_INT_MASK},
		{ IN_STREAM2_REG_OFF, IN_STREAM2_INT_MASK},
		{ IN_STREAM3_REG_OFF, IN_STREAM3_INT_MASK},
		{ OUT_STREAM0_REG_OFF, OUT_STREAM0_INT_MASK},
		{ OUT_STREAM1_REG_OFF, OUT_STREAM1_INT_MASK},
		{ OUT_STREAM2_REG_OFF, OUT_STREAM2_INT_MASK},
		{ OUT_STREAM3_REG_OFF, OUT_STREAM3_INT_MASK},
	};

	// reset controller
	snd_azx_util_reset_controller(chip);

	// create ringbuffer structure for corb and rirb here
	chip->corb = snd_azx_rb_create(chip, AZX_CORB_SIZE);
	chip->rirb = snd_azx_rb_create(chip, AZX_RIRB_SIZE);

	// allocate memory for the BDL for each stream
	// size = entry size * number of entries per bdl * number of bdls
	size = 16 * AZX_MAX_FRAG * MAX_ICH6_DEV;  // size in bytes

	chip->bdl_v_addrs = (u32*)pci_alloc_consistent(chip->pci, size, &chip->bdl_p_addrs);
	if (NULL == chip->bdl_v_addrs) {
		printk(KERN_ERR "Error: cannot allocate memory for BDLs size [0x%x]\n", size);
		return -1;
	}
	bdl_v_base = chip->bdl_v_addrs;
	bdl_p_base = chip->bdl_p_addrs;

	// initialize each stream (aka device)
	// assign the starting bdl address to each stream (device) and initialize
	for (index = 0; index < MAX_ICH6_DEV; index++) {
		azx_dev = &chip->azx_dev[index];
		azx_dev->bdl_v_addr = bdl_v_base + (index * AZX_MAX_FRAG * 4);
		azx_dev->bdl_p_addr = bdl_p_base + (index * sizeof(u32) * AZX_MAX_FRAG * 4);

		azx_dev->playbuff_p_addr = 0;
		azx_dev->size = 0;
		azx_dev->fragsize = 0;
		azx_dev->position = 0;
		azx_dev->frags = 0;
		azx_dev->lvi = 0;

		azx_dev->sd_off = azx_sd_regs[index].sd_offset;
		azx_dev->sd_int_sta_mask = azx_sd_regs[index].sd_int_sta_mask;
	}

	// set the stream tag for each device
	// this is used for programming the controller stream descriptor registers
	// FIXME: right now we are just decoding from 1 stream for 6ch
	chip->azx_dev[SDI0].stream_tag = IN_STREAM0_TAG;	// ADC for Line In
	chip->azx_dev[SDI1].stream_tag = IN_STREAM1_TAG;	// ADC for Mic1
	chip->azx_dev[SDI2].stream_tag = IN_STREAM2_TAG;	// ADC for CD
	chip->azx_dev[SDI3].stream_tag = IN_STREAM3_TAG;	// ADC not supported in codec
	chip->azx_dev[SDO0].stream_tag = OUT_STREAM0_TAG;	// DAC for front
	chip->azx_dev[SDO1].stream_tag = OUT_STREAM0_TAG;	// DAC for rear
	chip->azx_dev[SDO2].stream_tag = OUT_STREAM0_TAG;	// DAC for c/lfe
	chip->azx_dev[SDO3].stream_tag = OUT_STREAM0_TAG;	// DAC for rear center L/R (used for hp output)

	// note mute values are inverted (1 = unmute and 0 = mute)

	chip->cap_source_info.num_items = 0;
	
	snd_azx_int_clear(chip);

	snd_azx_int_enable(chip);
	
	// setup sdi0 controller
//	snd_azx_controller_setup_sdi0(chip);

/*	take this out for now...just focus on output

	azx_dev = &chip->azx_dev[SDI0];
	snd_azx_stream_start(chip->remap_addr, azx_dev->sd_off);
*/
// *****************************************


	// enable codec
	set_audio_codec_address(chip);

	// line in path setup
	set_input_path(chip, IN_STREAM0_TAG >> 20, 0x0, &(chip->cap_source_info), chip->mix_elements);
	
	// line out path setup
	set_output_path(chip, OUT_STREAM0_TAG >> 20, 0x0, chip->mix_elements);
	
	// cd analog loop back setup
	set_cd_analog_loopback_path(chip, chip->mix_elements);
	
	// mic input for capture
	if (chip->cap_source_info.num_items) {
		set_mic_rp_path(chip, &(chip->cap_source_info));
		chip->current_capture_source_val = chip->cap_source_info.init_val;
	}
		
	// debug caputered mixer information
//	print_mix_table();

	return 0;
}

static azx_rb_t* snd_azx_rb_create(azx_t* chip, int size)
{
	azx_rb_t* rb;
	rb = kmalloc(sizeof(*rb), GFP_KERNEL);
	if (NULL == rb) {
		printk(KERN_ERR "Error: cannot allocate memory for ring buffer structure [0x%lx]", (unsigned long)sizeof(*rb));
		return 0;
	}

	snd_azx_rb_init(chip, size, rb);

	return rb;
}

static int snd_azx_rb_init(azx_t* chip, int size, azx_rb_t* rb)
{
	void __iomem *azx_base = chip->remap_addr;

	rb->rb_v_addr = NULL;
	rb->rb_p_addr = 0;

	if (size == AZX_CORB_SIZE) {
		rb->rb_v_addr = pci_alloc_consistent(chip->pci, AZX_CORB_SIZE, &rb->rb_p_addr);
		if (NULL == rb->rb_v_addr) {
			printk(KERN_ERR "Error: Cannot allocate memory for corb ring buffer [%i]\n", AZX_CORB_SIZE);
			return -1;
		}

		rb->rp = 0;
		rb->wp = 0;
		rb->max_entries = 256;
		rb->entry_size = 2;	// in long words
		rb->ctl_off = AZX_REG_B_CORBCTL;	// control reg offset
		rb->sts_off = AZX_REG_B_CORBSTS;	// status reg offset

		snd_printdd("corb virt addr = 0x%lx\n", (unsigned long)rb->rb_v_addr);
		snd_printdd("corb phys addr = 0x%lx\n", (unsigned long)rb->rb_p_addr);

		chip->corb = rb;

		// program the corb address
		// lower 32 bit
		writel((u32)rb->rb_p_addr, azx_base + AZX_REG_L_CORBLBASE);
		// upper 32 bit
		writel(upper_32bit(rb->rb_p_addr), azx_base + AZX_REG_L_CORBUBASE);

		// reset the corb hw read pointer
		writew(RINGBUFFER_RP_CLR, azx_base + AZX_REG_W_CORBRP);

		// set the corb write pointer to 0
		writew(0x0, azx_base + AZX_REG_W_CORBWP);

		// enable corb dma
		writeb(RINGBUFFER_DMA_EN, azx_base + AZX_REG_B_CORBCTL);
	}
	if (size == AZX_RIRB_SIZE) {
		rb->rb_v_addr = pci_alloc_consistent(chip->pci, AZX_RIRB_SIZE, &rb->rb_p_addr);
		if (NULL == rb->rb_v_addr) {
			printk(KERN_ERR "Error: Cannot allocate memory for rirb ring buffer [%i]\n", AZX_CORB_SIZE);
			return -1;
		}

		rb->rp = 0;
		rb->wp = 0;
		rb->max_entries = 256;
		rb->entry_size = 4;	// in long words
		rb->ctl_off = AZX_REG_B_RIRBCTL;	// control reg offset
		rb->sts_off = AZX_REG_B_RIRBSTS;	// status reg offset

		snd_printdd("rirb virt addr = 0x%lx\n", (unsigned long)rb->rb_v_addr);
		snd_printdd("rirb phys addr = 0x%lx\n", (unsigned long)rb->rb_p_addr);

		chip->rirb = rb;

		// lower 32 bit
		writel((u32)rb->rb_p_addr, azx_base + AZX_REG_L_RIRBLBASE);
		// upper 32 bit
		writel(upper_32bit(rb->rb_p_addr), azx_base + AZX_REG_L_RIRBUBASE);

		// reset the rirb hw write pointer
		writew(RINGBUFFER_WP_CLR, azx_base + AZX_REG_W_RIRBWP);

		// enable rirb dma
		writeb(RINGBUFFER_DMA_EN, azx_base + AZX_REG_B_RIRBCTL);
	}
	return 0;
}

static void snd_azx_rb_delete(azx_t* chip, int size)
{
	azx_rb_t* rb;
	if (size == AZX_CORB_SIZE) {
		rb = chip->corb;

		if (NULL != rb->rb_v_addr) {
			pci_free_consistent(chip->pci, AZX_CORB_SIZE, rb->rb_v_addr, rb->rb_p_addr);
		}
		kfree(rb);
	}
	if (size == AZX_RIRB_SIZE) {
		rb = chip->rirb;

		if (NULL != rb->rb_v_addr) {
			pci_free_consistent(chip->pci, AZX_RIRB_SIZE, rb->rb_v_addr, rb->rb_p_addr);
		}
		kfree(rb);
	}

}

static int snd_azx_corb_add_cmd(azx_t* chip, u32 cad, u32 nid, u32 direct, u32 verb, u32 parameter)
{
	azx_rb_t* rb;
	u32 command;
	u32 *addr;

	rb = chip->corb;
	command = (cad << 28) | (direct << 27) | (nid << 20) | (verb << 8) | parameter;

	if (rb->wp == rb->max_entries-1) {
		rb->wp = 0;
	} else {
		rb->wp++;
	}

	// add command to corb
	addr = (u32 *)rb->rb_v_addr + rb->wp;
	*addr = cpu_to_le32(command);

	return 0;
}

static int snd_azx_corb_update_wp(azx_t* chip)
{
	u16 wp = chip->corb->wp;

	writel(wp, chip->remap_addr + AZX_REG_W_CORBWP);

	return 0;
}

static int snd_azx_rirb_get_single_response(azx_t* chip, u32* lower_response, u32* upper_response)
{
	void __iomem *azx_base = chip->remap_addr;
	unsigned int offset = readb(azx_base + AZX_REG_W_RIRBWP) * 2;
	u32 *addr;

	addr = (u32*)chip->rirb->rb_v_addr + offset;
	*lower_response = le32_to_cpu(*addr);
	addr++;
	*upper_response = le32_to_cpu(*addr);

	return 0;
}


int snd_azx_send_corb_cmd(azx_t* chip, u32 cad, u32 nid, u32 direct, u32 verb, u32 parameter, u32* lower_response, u32* upper_response)
{
        // keep these independent function calls, in case we need to 
        // break up corb/rirb communication in the future.
        snd_azx_corb_add_cmd(chip, cad, nid, direct, verb, parameter);
        snd_azx_corb_update_wp(chip);
	/* FIXME: better to msleep if possible */
        mdelay(1);
	snd_azx_rirb_get_single_response(chip, lower_response, upper_response);

	return 0;
}


static int snd_azx_util_reset_controller(azx_t* chip)
{
	u32 resetval;
	unsigned int count = 0;
	u16 statests;
	unsigned int codec_ack;
	void __iomem *azx_base = chip->remap_addr;

	// ** Reset controller
	resetval = readl(azx_base + AZX_REG_L_GCTL) & ~(GCTL_CONTROLLER_RESET_BIT_MASK);
	writel(resetval, azx_base + AZX_REG_L_GCTL);

	//delay a little bit for controller to enter reset
	//SWAG: just a guess of 10 ms should be long enough
	msleep(10);
	while ((count < 500) && readb(azx_base + AZX_REG_L_GCTL)) {
		count++;
	}
/*
	if (0 < count) {
		snd_printdd("   Entered while loop...%u\n", count);
	}
	snd_printdd("   Controller entered reset...\n");
*/
	// delay for >= 100us for codec PLL to settle per spec
	// Rev 0.9 section 5.5.1
	udelay(100);

	// Bring controller out of reset
	resetval = readl(azx_base + AZX_REG_L_GCTL) | GCTL_CONTROLLER_RESET_BIT_MASK;
	writeb(resetval, azx_base + AZX_REG_L_GCTL);

	//delay a little bit for controller to exit reset
	//SWAG: just a guess of 10 ms should be long enough
	msleep(10);
	count = 0;
	while ((count < 500) && (!(readb(azx_base + AZX_REG_L_GCTL)))) {
		count++;
	}
/*
	if (0 < count) {
		snd_printdd("   Entered while loop...%u\n", count);
	}
*/
	// Brent Chartrand said to wait >= 540us for codecs to intialize
	msleep(1);

	// get the number of codec connected
	statests = readw(azx_base + AZX_REG_W_STATESTS);

	for (count = 0; count < 16; count++) {
		codec_ack = statests & (1 << count);
		
		if (codec_ack) {
			chip->num_codecs++;
		}
	}

	chip->codec_mask = statests & STATESTS_INT_MASK;

	// check to see if controller is ready
	// TODO: replace literal with #define symbol
	if (readb(azx_base + AZX_REG_L_GCTL)) {
	       return -1;
        } else {
	       return 0;
	}
}


u32 snd_azx_get_codec_mask(azx_t* chip)
{
	return chip->codec_mask;
}

// ********************** controller programming here **********************
static int snd_azx_controller_setup(azx_t* chip, azx_dev_t* azx_dev)
{
	unsigned int val;
	int test = 0;

	void __iomem *azx_base = chip->remap_addr;
	unsigned int sd_base = azx_dev->sd_off;

	// **** The next thing is to setup the controller for streaming
	// make sure the run bit is zero for SDO 0
	val = readb(azx_base + sd_base + AZX_REG_3B_SD_CTL) & (~DMA_START_BIT);
	writeb(val, azx_base + sd_base + AZX_REG_3B_SD_CTL);
//	snd_printdd("   Just set SDO 0 to stop...\n");

	// reset output stream 0

	val = readb(azx_base + sd_base + AZX_REG_3B_SD_CTL) | STREAM_REST_BIT;
	writeb(val, azx_base + sd_base + AZX_REG_3B_SD_CTL);

	udelay(3);

	while ((!(val = readb(azx_base + sd_base + AZX_REG_3B_SD_CTL) & STREAM_REST_BIT)) && (test < 300)) {
		//waiting for hardware to report that the stream is in reset
		test++;
	};
	if (test) {
//		snd_printdd("  Entered reset enter loop...%i\n", test);
	}
	val = val & (~STREAM_REST_BIT);
	writel(val, azx_base + sd_base + AZX_REG_3B_SD_CTL);

	udelay(3);

	test = 0;

	while ((val = readb(azx_base + sd_base + AZX_REG_3B_SD_CTL) & STREAM_REST_BIT) && (test < 300)) {
		//waiting for hardware to report that the stream is out of reset
		test++;
	};
	if (test) {
//		snd_printdd("  Entered reset exit loop...%i\n", test);
	}

	// program the stream_tag
	val = readl(azx_base + sd_base + AZX_REG_3B_SD_CTL) | azx_dev->stream_tag;
//	snd_printdd("   val = %lx\n", (unsigned long)val);
	writel(val, azx_base + sd_base + AZX_REG_3B_SD_CTL);

	udelay(2);

	val = readl(azx_base + sd_base + AZX_REG_3B_SD_CTL);
	snd_printdd("   Programmed with tag value: 0x%lx\n", (unsigned long)val);

	// program the length of samples in cyclic buffer in register offset 108h
	val = azx_dev->fragsize;
	writel(val, azx_base + sd_base + AZX_REG_L_SD_CBL);

	udelay(2);
	val = readl(azx_base + sd_base + AZX_REG_L_SD_CBL);
	snd_printdd("   Programmed cyclic buffer length to %i\n", val);


	// this needs to be done during pcm_hw_param
	// program the stream format...this value needs to be the same as the one programmed
	// in the converter...which was 4010h
	val = azx_dev->format_val;
	writew(val, azx_base + sd_base + AZX_REG_W_SD_FORMAT);

	udelay(2);
	val = readw(azx_base + sd_base + AZX_REG_W_SD_FORMAT);
	snd_printdd("   Programmed format register to 0x%lx\n", (unsigned long)val);


	// program the stream LVI (last valid index) of the BDL
	val = AZX_MAX_FRAG - 1;
	writew(val, azx_base + sd_base + AZX_REG_W_SD_LVI);

	udelay(2);

	val = readw(azx_base + sd_base + AZX_REG_W_SD_LVI);
//	snd_printdd("Programmed SDO0 LVI register to %i\n", val);

	// program the BDL address
	// lower BDL address
	writel((u32)azx_dev->bdl_p_addr, azx_base + sd_base + AZX_REG_L_SD_BDLPL);

	udelay(2);

	val = readl(azx_base + sd_base + AZX_REG_L_SD_BDLPL);
	snd_printdd("Lower BDL base = 0x%lx\n", (unsigned long)val);
	// upper BDL address
	writel(upper_32bit(azx_dev->bdl_p_addr), azx_base + sd_base + AZX_REG_L_SD_BDLPU);

	udelay(2);

	val = readl(azx_base + sd_base + AZX_REG_L_SD_BDLPU);
	snd_printdd("Upper BDL base = 0x%lx\n", (unsigned long)val);

	// set the interrupt enable bits in the descriptor control register
	val = readl(azx_base + sd_base + AZX_REG_3B_SD_CTL) | STREAM_INT_MASK;
	snd_printdd("   val = %lx\n", (unsigned long)val);
	writel(val, azx_base + sd_base + AZX_REG_3B_SD_CTL);

	udelay(2);

	val = readl(azx_base + sd_base + AZX_REG_3B_SD_CTL);
	snd_printdd("   Enabling interrupts on with value: 0x%lx\n", (unsigned long)val);

	return 0;
}

static void snd_azx_int_display(azx_t* chip)
{
	void __iomem *azx_base = chip->remap_addr;
	unsigned int val;
	val = readl(azx_base + AZX_REG_L_INTSTS);
	snd_printdd("INT status = %x\n", val);

	val = readb(azx_base + AZX_REG_B_RIRBSTS);
	snd_printdd("RIRB status = %x\n", val);

	val = readw(azx_base + AZX_REG_W_STATESTS);
	snd_printdd("STATE status = %x\n", val);

	val = readb(azx_base + AZX_REG_B_SDO0STS);
	snd_printdd("SDO 0 status = %x\n", val);

}

static void snd_azx_int_disable(azx_t* chip)
{
	void __iomem *azx_base = chip->remap_addr;
	u8 val8;
	u32 val32;

	// disable SDO 0 in stream descriptor
	// bit 4 = DEIE - Descriptor Error Interrupt
	// bit 3 = FIFO - First in First out interrupt
	// bit 2 = IOCE - Interrupt on completion
	val8 = readl(azx_base + AZX_REG_3B_SDO0CTL) & (~STREAM_INT_MASK);
	writeb(val8, azx_base + AZX_REG_3B_SDO0CTL);

	// disable SIE for all streams
	writeb(0x0, azx_base + AZX_REG_L_INTCTL);

	// disable controller CIE
	val32 = readl(azx_base + AZX_REG_L_INTCTL) & (~CONTROLLER_INT_EN_BIT);
	writel(val8, azx_base + AZX_REG_L_INTCTL);

	// disable global GIE
	val32 = readl(azx_base + AZX_REG_L_INTCTL) & (~GLOBAL_INT_EN_BIT);
	writel(val8, azx_base + AZX_REG_L_INTCTL);
}

static void snd_azx_int_enable(azx_t* chip)
{
	void __iomem *azx_base = chip->remap_addr;
	u8 val8;
	u32 val32;

	// enable SDO 0 in stream descriptor
	// bit 4 = DEIE - Descriptor Error Interrupt
	// bit 3 = FIFO - First in First out interrupt
	// bit 2 = IOCE - Interrupt on completion
	val8 = readl(azx_base + AZX_REG_3B_SDO0CTL) | 0x1c;
	writeb(val8, azx_base + AZX_REG_3B_SDO0CTL);

	val32 = readl(azx_base + AZX_REG_3B_SDO0CTL);
	snd_printdd("SDO 0 ctl = %lx\n", (unsigned long)val32);

	// enable SIE for all streams
	writeb(ALL_STREAM_INT_EN, azx_base + AZX_REG_L_INTCTL);

	// enable controller CIE
	val32 = readl(azx_base + AZX_REG_L_INTCTL) | CONTROLLER_INT_EN_BIT;
	writel(val32, azx_base + AZX_REG_L_INTCTL);

	// enable global GIE
	val32 = readl(azx_base + AZX_REG_L_INTCTL) | GLOBAL_INT_EN_BIT;
	writel(val32, azx_base + AZX_REG_L_INTCTL);
}

static void snd_azx_int_clear(azx_t* chip)
{
	void __iomem *azx_base = chip->remap_addr;

	// clear stream SDO status
	writeb(STREAM_INT_MASK, azx_base + AZX_REG_B_SDO0STS);

	// clear stream SDI status
	writeb(STREAM_INT_MASK, azx_base + AZX_REG_B_SDI0STS);

	// clear STATESTS
	writeb(STATESTS_INT_MASK, azx_base + AZX_REG_W_STATESTS);

	// clear rirb status
	writeb(RIRB_INT_MASK, azx_base + AZX_REG_B_RIRBSTS);

	// clear int status
	writel(CONTROLLER_INT_MASK, azx_base + AZX_REG_L_INTSTS);

}

#if 0 /* not used */
static void snd_azx_stream_start(void __iomem *azx_base, unsigned int sd_base)
{
	u32 val ;
	void __iomem *addr;

	// try to stop the stream
	addr = azx_base + sd_base + AZX_REG_3B_SD_CTL;
	//snd_printdd("Writing 0x2 to addr[0x%lx]\n", (unsigned long)addr);
	val = readb(addr) | 0x02;
	writeb(val, addr);
	udelay(3);
	val - readl(addr);
	//snd_printdd("val at addr[0x%lx] = 0x%lx\n", (unsigned long)addr, (unsigned long)val);
	//snd_printdd("Just started stream at offset [0x%lx]\n", (unsigned long)sd_base);

	udelay(3);
}
#endif /* not used */

static void snd_azx_stream_stop(void __iomem *azx_base, unsigned int sd_base)
{
	u32 val;

	// try to stop the stream
	val = readb(azx_base + sd_base + AZX_REG_3B_SD_CTL) & (~DMA_START_BIT);	
	writeb(val, azx_base + sd_base + AZX_REG_3B_SD_CTL);

	//snd_printdd("Just stopped stream at offset [0x%lx]\n", (unsigned long)sd_base);

	udelay(3);
}

// ********************************** START PCM STUFF *****************************
static snd_pcm_hardware_t snd_azx_playback_hw = {
	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
				 SNDRV_PCM_INFO_MMAP_VALID |
				 SNDRV_PCM_INFO_PAUSE |
				 SNDRV_PCM_INFO_RESUME),
	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
	.rates =		(SNDRV_PCM_RATE_192000 | SNDRV_PCM_RATE_96000 |
				 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100),
	.rate_min =		44100,
	.rate_max =		192000,
	.channels_min =		2,
	.channels_max =		6,
	.buffer_bytes_max =	64 * 1024,
	.period_bytes_min =	128,
	.period_bytes_max =	4096,
	.periods_min =		4,
	.periods_max =		16,
	.fifo_size =		0,
};
static snd_pcm_hardware_t snd_azx_capture_hw = {
	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
				 SNDRV_PCM_INFO_MMAP_VALID),
	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
	.rates =		(SNDRV_PCM_RATE_96000 |
				 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100),
	.rate_min =		44100,
	.rate_max =		96000,
	.channels_min =		2,
	.channels_max =		2,
	.buffer_bytes_max =	128 * 1024,
	.period_bytes_min =	32,
	.period_bytes_max =	128 * 1024,
	.periods_min =		1,
	.periods_max =		1024,
	.fifo_size =		0,
};

static int snd_azx_pcm_open(snd_pcm_substream_t* substream, azx_dev_t* azx_dev)
{
	snd_pcm_runtime_t *runtime = substream->runtime;
	azx_t* chip = snd_pcm_substream_chip(substream);
	int err;
	unsigned int  val;
	void __iomem *azx_base = chip->remap_addr;
	unsigned int sd_base = azx_dev->sd_off;

	azx_dev->substream = substream;
	if (azx_dev->sd_off == OUT_STREAM0_REG_OFF)
		runtime->hw = snd_azx_playback_hw;
	if (azx_dev->sd_off == IN_STREAM0_REG_OFF)
		runtime->hw = snd_azx_capture_hw;
//	runtime->hw.rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000;

	// get the fifo size of the controller
	// I am making an assumption that the context/meaning of fifo size is the same for
	// Azalia (spec Rev. 0.9 section 3.3.38) and ALSA...
	val = readw(azx_base + sd_base + AZX_REG_W_SD_FIFOSIZE);
	snd_printdd("FIFO size of controller = 0x%X\n", val);
	runtime->hw.fifo_size = val;

	snd_pcm_limit_hw_rates(runtime);

	if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0) {
		printk(KERN_ERR "Error: snd_pcm_hw_constraint_integer()\n");
		return err;
	}
	runtime->private_data = azx_dev;

	return 0;
}

static int snd_azx_playback_open(snd_pcm_substream_t* substream)
{
	azx_t* chip = snd_pcm_substream_chip(substream);
	int err;

	// here we want to setup the runtime fields according to our hardware support
	err = snd_azx_pcm_open(substream, &chip->azx_dev[SDO0]);
	if (err < 0) {
		printk(KERN_ERR "Error: snd_azx_pcm_open!!!\n");
		return err;
	}

	return 0;
}

static int snd_azx_playback_close(snd_pcm_substream_t* substream)
{
	azx_t *chip = snd_pcm_substream_chip(substream);

	chip->azx_dev[SDO0].substream = NULL;

	return 0;
}

static int snd_azx_pcm_hw_params(snd_pcm_substream_t* substream, snd_pcm_hw_params_t* hw_params)
{
	size_t size = params_buffer_bytes(hw_params);
	int err = 0;

	// here we are allocating the play buffer of size specified by the Middle layer
	// and assigning the addrss to runtime fields (dma_area, dma_addr, dma_size) via this helper function
	err = snd_pcm_lib_malloc_pages(substream, size);
	if (err < 0) {
		printk(KERN_ERR "Error: allocating play buffer of size [%i]\n", size);
		return err;
	}
	// check the dma addresses
	snd_printdd("runtime->dma_area (play buffer virtual address) = 0x%lx\n", (unsigned long)substream->runtime->dma_area);
	snd_printdd("runtime->dma_addr (play buffer physical address) = 0x%lx\n", (unsigned long)substream->runtime->dma_addr);
	snd_printdd("runtime->dma_bytes (play buffer size in bytes) = %i\n", substream->runtime->dma_bytes);

	return err;
}

static int snd_azx_pcm_hw_free(snd_pcm_substream_t* substream)
{
	// here we are releasing the memory that was the play back buffer

	return snd_pcm_lib_free_pages(substream);;
}

static void snd_azx_setup_periods(azx_t *chip, azx_dev_t *azx_dev)
{
	unsigned int  idx;
	u32* bdl_v_addr = (u32*)azx_dev->bdl_v_addr;
	unsigned long bdl_p_addr = azx_dev->bdl_p_addr;
	unsigned int sd_base = azx_dev->sd_off;
	void __iomem *azx_base = chip->remap_addr;
	dma_addr_t playback_buff_p_addr;

	// program the BDL address into the stream register
	writel(bdl_p_addr, azx_base + sd_base + AZX_REG_L_SD_BDLPL);
	writel(0x0, azx_base + sd_base + AZX_REG_L_SD_BDLPU);

	// program the initial BDL entries
	for (idx = 0; idx < AZX_MAX_FRAG * 4; idx += 4) {
		// program the address field of the BDL entry
		playback_buff_p_addr = azx_dev->playbuff_p_addr + (((idx >> 2) * azx_dev->fragsize) % azx_dev->size);
		bdl_v_addr[idx] = cpu_to_le32((u32)playback_buff_p_addr);
		bdl_v_addr[idx+1] = cpu_to_le32(upper_32bit(playback_buff_p_addr));

		// program the size field of the BDL entry
		bdl_v_addr[idx+2] = cpu_to_le32(azx_dev->fragsize);

		// program the IOC to enable interrupt when buffer completes
		bdl_v_addr[idx+3] = cpu_to_le32(0x01);
	}
	azx_dev->frags = azx_dev->size / azx_dev->fragsize;
	azx_dev->cvi = 0;
	azx_dev->lvi = 0;
	azx_dev->position = 0;
	azx_dev->lvi_frag = (AZX_MAX_FRAG - 1) % azx_dev->frags;
	snd_printdd("lvi_frag = %i\n", azx_dev->lvi_frag);
}

static void snd_azx_pcm_setup_controller(azx_t* chip, azx_dev_t* azx_dev)
{
	void __iomem *azx_base = chip->remap_addr;
	unsigned int sd_base = azx_dev->sd_off;
	snd_pcm_runtime_t *runtime = azx_dev->substream->runtime;

	unsigned int  rate = runtime->rate;
	unsigned int  num_channels = runtime->channels;
	unsigned int  bits_per_sample = runtime->sample_bits;

	unsigned int  val = 0;

//	snd_printdd("stream info:\n");
//	snd_printdd("rate = %i\n", rate);
//	snd_printdd("num_channels = %i\n", num_channels);
//	snd_printdd("bits per sample = %i\n", bits_per_sample);

	// set the cyclic buffer size using fragsize
	writel(azx_dev->fragsize, azx_base + sd_base + AZX_REG_L_SD_CBL);

	// set the format
	if (44100 == rate) {
		snd_printdd("setting controller to 44.1kHz\n");
		val = FREQENCY_44K;
	}

	if (48000 == rate) {
		snd_printdd("setting controller to 48kHz\n");
		val = FREQENCY_48K;
	}
	if (96000 == rate) {
		snd_printdd("setting controller to 96kHz\n");
		val = FREQENCY_96K;
	}
	if (192000 == rate) {
		snd_printdd("setting controller to 192kHz\n");
		val = FREQENCY_192K;
	}

	if (2 == num_channels) {
		snd_printdd("setting controller to 2 channels\n");
		val = val | FORMAT_VAL_2CH;
	}

	switch (bits_per_sample) {
		case 8:
			snd_printdd("setting controller to 8 bits per sample\n");
			val = val | BITS_PER_SAMPLE_8;
			break;
		case 16:
			snd_printdd("setting controller to 16 bits per sample\n");
			val = val | BITS_PER_SAMPLE_16;
			break;
		case 20:
			snd_printdd("setting controller to 20 bits per sample\n");
			val = val | BITS_PER_SAMPLE_20;
			break;
		case 24:
			snd_printdd("setting controller to 24 bits per sample\n");
			val = val | BITS_PER_SAMPLE_24;
			break;
		case 32:
			snd_printdd("setting controller to 32 bits per sample\n");
			val = val | BITS_PER_SAMPLE_32;
			break;
		default:
			printk(KERN_ERR "Error...not supported bits per sample!!!\n");
			printk(KERN_ERR "   defaulting to 8 bits\n");
	}

	azx_dev->format_val = val; // save the value to program the controller and the codec

	snd_azx_controller_setup(chip, azx_dev);
}

static void snd_azx_pcm_setup_codec(azx_t* chip, azx_dev_t* azx_dev)
{
	u8 cad = get_audio_codec_address();
	u8 nid = 0;
	u8 direct = 0;
	u32 parameter;

	u32 playback_format = azx_dev->format_val;
	u32 capture_format = azx_dev->format_val;

	u32 lower_response;
	u32 upper_response;

	u8 aow_nid = get_dac_nid();
	u8 aiw_nid = get_adc_nid();

	if  (azx_dev->sd_off == OUT_STREAM0_REG_OFF) {
		nid = aow_nid;
		snd_printdd("Programming DAC to the correct format...\n");
		// **** setup format ***********************************************
		// set output converter format...for 1 channel, 44.1Khz sample rate, 16bit per sample
		// the format value for this setting is 4010
		// verb for set converter is 200h, for get converter is A00h
		nid = aow_nid;
		parameter = playback_format;
		snd_azx_send_corb_cmd(chip, cad, nid, direct, SET_STREAM_FORMAT, parameter, &lower_response, &upper_response);

		snd_printdd("*** Programmed the AOW[%x] to format value: 0x%lx\n", nid, (unsigned long)parameter);

		// get output converter format...check to see if the value got programmed
		parameter = 0x0;
		snd_azx_send_corb_cmd(chip, cad, nid, direct, GET_STREAM_FORMAT, parameter, &lower_response, &upper_response);

		snd_printdd("*** Checking AOW[%x] format value = 0x%lx\n", nid, (unsigned long)lower_response);
		// End of output converter (AOW) setup
	}

	if (azx_dev->sd_off == IN_STREAM0_REG_OFF) {
		nid = aiw_nid;
		snd_printdd("Programming ADC to the correct format...\n");
		// set input converter format...for 2 channel, 44.1Khz sample rate, 16bit per sample
		//   the format value for this setting is 4011
		// verb for set converter is 200h, for get converter is A00h
		parameter = capture_format;
		snd_azx_send_corb_cmd(chip, cad, nid, direct, SET_STREAM_FORMAT, parameter, &lower_response, &upper_response);
		
		snd_printdd("Response for setting AIW[%x] stream/channel to %lx = %lx\n", nid, (unsigned long)parameter, (unsigned long)lower_response);


		// get input converter format...check to see if the value got programmed
		parameter = 0x0;
		snd_azx_send_corb_cmd(chip, cad, nid, direct, GET_STREAM_FORMAT, parameter, &lower_response, &upper_response);

		snd_printdd("Checking AIW[%x] format value = 0x%lx\n", nid, (unsigned long)lower_response);
	}

}

static int snd_azx_pcm_prepare(snd_pcm_substream_t *substream)
{
	azx_t* chip = snd_pcm_substream_chip(substream);
	snd_pcm_runtime_t *runtime = substream->runtime;
	azx_dev_t* azx_dev = get_azx_dev(substream);

	azx_dev->playbuff_p_addr = runtime->dma_addr;
	azx_dev->size = snd_pcm_lib_buffer_bytes(substream);
	azx_dev->fragsize = snd_pcm_lib_period_bytes(substream);

	// verify the information passed in...dma_addr, size and fragsize
	snd_printdd("azx_dev->physbuf (play back physical address) = 0x%lx\n", (unsigned long)azx_dev->playbuff_p_addr);
	snd_printdd("azx_dev->size (play back buffer size in bytes) = %i\n", azx_dev->size);
	snd_printdd("azx_dev->fragsize (period size in bytes used in the BDL entries) = %i\n", azx_dev->fragsize);

	// here we setup the controller and codec:
	// format
	// channel
	// bit rate
	// initial bdl entries
	// controller CBL
	// lets create a function to do this!!!...snd_azx_setup_periods(chip, azx_dev);
	// TODO: above and implement setup_period()

//	spin_lock(&chip->reg_lock);

	snd_azx_pcm_setup_controller(chip, azx_dev);

	snd_azx_pcm_setup_codec(chip, azx_dev);

//	spin_lock(&chip->reg_lock);

	// the BDL address and BDL entries are programmed in setup_periods()
	snd_azx_setup_periods(chip, azx_dev); // to be implemented
	return 0;
}

static int snd_azx_pcm_trigger(snd_pcm_substream_t* substream, int cmd)
{
	azx_t* chip = snd_pcm_substream_chip(substream);
	azx_dev_t* azx_dev = get_azx_dev(substream);
	unsigned int sd_base = azx_dev->sd_off;
	void __iomem *azx_base = chip->remap_addr;
	unsigned int val = readb(azx_base + sd_base + AZX_REG_3B_SD_CTL);

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
	case SNDRV_PCM_TRIGGER_RESUME:
	case SNDRV_PCM_TRIGGER_START:
		snd_azx_int_display(chip);
		snd_azx_int_clear(chip);
		snd_azx_int_enable(chip);
		val = val | AZX_START_DMA;

		// gonna wait a little for data to be buffered
		mdelay(5);
		break;
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
	case SNDRV_PCM_TRIGGER_STOP:
		val = val & (~AZX_START_DMA);
		break;
	default:
		return -EINVAL;
	}

	writeb(val, azx_base + sd_base + AZX_REG_3B_SD_CTL);
	udelay(2);
//	snd_printdd("sd ctl val = %lx\n", (unsigned long)readb(azx_base + sd_base + AZX_REG_3B_SD_CTL));
//	snd_printdd("sd sts val = %lx\n", (unsigned long)readb(azx_base + sd_base + AZX_REG_B_SD_STS));
	if (cmd == SNDRV_PCM_TRIGGER_STOP) {
		snd_azx_int_clear(chip);
//		snd_printdd("delaying a little after stop...\n");
		udelay(5);
	}

	return 0;
}


static snd_pcm_uframes_t snd_azx_pcm_pointer(snd_pcm_substream_t* substream)
{
	//unsigned int current_ptr;
	azx_t* chip = snd_pcm_substream_chip(substream);
	azx_dev_t* azx_dev = get_azx_dev(substream);
	//u32 val;
	void __iomem *azx_base = chip->remap_addr;
	unsigned int sd_base = azx_dev->sd_off;
	unsigned int ptr1, ptr;
	unsigned long flags;
	
	ptr1 = readl(azx_base + sd_base + AZX_REG_L_SD_LPIB);

//	snd_printdd("position = %d\n", azx_dev->position);

	spin_lock_irqsave(&chip->reg_lock, flags);
	ptr = azx_dev->position + ptr1;

//	snd_printdd("ptr = %i\n", ptr);
	
	spin_unlock_irqrestore(&chip->reg_lock, flags);

	if (ptr >= azx_dev->size) {
		printk(KERN_ERR "Error in Pointer calc...\n");
		return 0;
	}

	return bytes_to_frames(substream->runtime, ptr);
}

static snd_pcm_uframes_t snd_azx_pcm_cap_pointer(snd_pcm_substream_t* substream)
{
	azx_t* chip = snd_pcm_substream_chip(substream);
	azx_dev_t* azx_dev = get_azx_dev(substream);
	void __iomem *azx_base = chip->remap_addr;
	unsigned int sd_base = azx_dev->sd_off;
	unsigned int ptr1, ptr;
	unsigned long flags;

	// TODO: put in the code to read the position register associated with this stream
	//       need to also add support for the position register setup in snd_azx_init_chip()

	// FIXME: for now just return the last updated position
	ptr1 = readl(azx_base + sd_base + AZX_REG_L_SD_LPIB);

	spin_lock_irqsave(&chip->reg_lock, flags);

	if (ptr1 != 0) {
		ptr = azx_dev->fragsize - ptr1;
	} else {
		ptr = 0;
	}

	ptr += azx_dev->position;

	spin_unlock_irqrestore(&chip->reg_lock, flags);
		
	if (ptr >= azx_dev->size) {
		printk(KERN_ERR "Error in Pointer calc...\n");
		return 0;
	}

	return bytes_to_frames(substream->runtime, ptr);
}

static snd_pcm_ops_t snd_azx_playback_ops = {
	.open = snd_azx_playback_open,
	.close = snd_azx_playback_close,
	.ioctl = snd_pcm_lib_ioctl,
	.hw_params = snd_azx_pcm_hw_params,
	.hw_free = snd_azx_pcm_hw_free,
	.prepare = snd_azx_pcm_prepare,
	.trigger = snd_azx_pcm_trigger,
	.pointer = snd_azx_pcm_pointer,
};

// ***************************** CAPTURE code **************************************
static int snd_azx_capture_open(snd_pcm_substream_t* substream)
{
	azx_t* chip = snd_pcm_substream_chip(substream);
	int err;

	// here we want to setup the runtime fields according to our hardware support
	err = snd_azx_pcm_open(substream, &chip->azx_dev[SDI0]);
	if (err < 0) {
		printk(KERN_ERR "Error: snd_azx_pcm_open!!!\n");
		return err;
	}

	return 0;
}

static int snd_azx_capture_close(snd_pcm_substream_t* substream)
{
	azx_t *chip = snd_pcm_substream_chip(substream);

	chip->azx_dev[SDI0].substream = NULL;

	return 0;
}

static snd_pcm_ops_t snd_azx_capture_ops = {
	.open = snd_azx_capture_open,
	.close = snd_azx_capture_close,
	.ioctl = snd_pcm_lib_ioctl,
	.hw_params = snd_azx_pcm_hw_params,
	.hw_free = snd_azx_pcm_hw_free,
	.prepare = snd_azx_pcm_prepare,
	.trigger = snd_azx_pcm_trigger,
	.pointer = snd_azx_pcm_cap_pointer,
};

//static int __devinit snd_azx_new_playback_pcm(azx_t* chip)
static int __devinit snd_azx_new_pcm(azx_t* chip)
{
	snd_pcm_t* pcm;
	int err;

	if ((err = snd_pcm_new(chip->card, "ICH6 HDA", 0, 1, 1, &pcm)) < 0) {
		printk(KERN_ERR "Error: snd_pcm_new() failed!!!\n");
		return err;
	}

	pcm->private_data = chip;
	strcpy(pcm->name, "ICH6 HDA");

	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_azx_playback_ops);
	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_azx_capture_ops);

	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci),
					      1024 * 64, 1024 * 128);



	return 0;
}

// ************************************** MIXER/CONTROL ***************************
// **************
// Capture select
// **************
static int snd_azx_cap_select_info(snd_kcontrol_t* kcontrol, snd_ctl_elem_info_t* uinfo)
{
	azx_t* chip = snd_kcontrol_chip(kcontrol);
	
	snd_printdd("*-*-* Entered snd_azx_cap_select_info *-*-*\n");

	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
	uinfo->count = 1;
	uinfo->value.enumerated.items = chip->cap_source_info.num_items;
	strcpy(uinfo->value.enumerated.name, chip->cap_source_info.cap_labels[uinfo->value.enumerated.item]);
	
	return 0;
}

static snd_kcontrol_new_t azx_cap_select __devinitdata = {
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.name = "Capture Source",
	.index = 0,
	.info = snd_azx_cap_select_info,
	.get = snd_azx_cap_select_callback_get,
	.put = snd_azx_cap_select_callback_put,
	.private_value = 0x0
};

static int snd_azx_new_cap_select_control(azx_t* chip)
{
	int err = 0;
	snd_printdd("**************************************************\n");
	snd_printdd("*-*-* Entered snd_azx_new_cap_select_control *-*-*\n");
	snd_printdd("**************************************************\n");

	if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&azx_cap_select, chip))) < 0) {
		printk("Error returned from snd_ctl_add(card, snd_ctl_new1()) for capture select...%i\n", err);
		return err;
	}

	return err;
}

#if 0 /* not used */
static int snd_azx_amp_get(azx_t* chip, u8 nid, u8* left_val, u8* right_val, u8 direction, u32 index)
{
	u32 cad = get_audio_codec_address();
	u32 direct = 0;
	u32 l_parameter = 0;
	u32 r_parameter = 0;
	u32 parameter = 0;
	u32 lower_response = 0;
	u32 upper_response = 0;
	
	// need to call a function to get the val from the codec
	if (direction) {
		// output direction = 1
		l_parameter = GET_OUT_AMP_L_SELECT;
		r_parameter = GET_OUT_AMP_R_SELECT;
	} else {
		// input direction = 0
		l_parameter = GET_IN_AMP_L_SELECT;
		r_parameter = GET_IN_AMP_R_SELECT;
	}

//	snd_printdd("Getting the left channel value...\n");

	// get left value
	parameter = l_parameter | index;
	snd_azx_send_corb_cmd(chip, cad, nid, direct, GET_AMPLIFIER_GAIN_MUTE, parameter, &lower_response, &upper_response);

//	snd_printdd("NID [%lx] amp value (left) = %lx\n", (unsigned long)nid, (unsigned long)lower_response);
	*left_val = lower_response & 0xFF;

//	snd_printdd("Getting the right channel value...\n");
	parameter = r_parameter | index;
	snd_azx_send_corb_cmd(chip, cad, nid, direct, GET_AMPLIFIER_GAIN_MUTE, parameter, &lower_response, &upper_response);

//	snd_printdd("NID [%lx] amp value (right) = %lx\n", (unsigned long)nid, (unsigned long)lower_response);
	*right_val = lower_response & 0xFF;

	return 0;

}
#endif

static int snd_azx_cap_select_callback_put(snd_kcontrol_t* kcontrol, snd_ctl_elem_value_t* ucontrol)
{
	azx_t* chip = snd_kcontrol_chip(kcontrol);
	u32 index = (u8)ucontrol->value.integer.value[0];

	u32 nid = chip->cap_source_info.nids[index];
	
	u32 cad = get_audio_codec_address();
	u32 direct = 0;
	u32 parameter = chip->cap_source_info.cap_index[index];
	int changed = 1;
	u32 lower_response = 0;
	u32 upper_response = 0;

	snd_azx_send_corb_cmd(chip, cad, nid, direct, SET_CONNECTION_SELECT, parameter, &lower_response, &upper_response);

	chip->current_capture_source_val = index;
	
	return changed;

}

static int snd_azx_cap_select_callback_get(snd_kcontrol_t* kcontrol, snd_ctl_elem_value_t* ucontrol)
{
	azx_t* chip = snd_kcontrol_chip(kcontrol);
	
  	//u32 nid = kcontrol->private_value & 0xFF;
  	//u8 path = (kcontrol->private_value & 0x1F000) >> 12;
 
  	ucontrol->value.integer.value[0] = chip->current_capture_source_val; // get capture source
  
  	return 0;
}

int snd_azx_volume_info(snd_kcontrol_t* kcontrol, snd_ctl_elem_info_t* uinfo)
{
	int max_val = (kcontrol->private_value >> 13) & 0xFF;
	int type = (kcontrol->private_value >> 21) & 0x01;
	
	if (type) {
		uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
	} else {
		uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	}
	uinfo->count = 2;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = max_val;
	return 0;		
}

int snd_azx_volume_get(snd_kcontrol_t* kcontrol, snd_ctl_elem_value_t* ucontrol)
{
	azx_t* chip = snd_kcontrol_chip(kcontrol);
	int array_idx = (kcontrol->private_value >> 22) & 0xFF;
	
	ucontrol->value.integer.value[0] = chip->mix_elements[array_idx].vol[0];
	ucontrol->value.integer.value[1] = chip->mix_elements[array_idx].vol[1];
	
	return 0;
}
int snd_azx_volume_put(snd_kcontrol_t* kcontrol, snd_ctl_elem_value_t* ucontrol)
{
	azx_t* chip = snd_kcontrol_chip(kcontrol);
	u32 cad = get_audio_codec_address();
	u32 direction = (kcontrol->private_value >> 12) & 0x01;
	u32 index = (kcontrol->private_value >> 8) & 0x0F;
	u32 nid = kcontrol->private_value & 0xFF;
	u32 parameter;
	u32 direct = 0;
	
	int array_idx = (kcontrol->private_value >> 22) & 0xFF;
	
	u32 left_val;
	u32 right_val;
	u32 lower_response;
	u32 upper_response;
	u32 left_mute; 
	u32 right_mute;
	 
	if (array_idx == AZX_MIX_CAP) {
		left_mute = 1; 
		right_mute = 1;
	} else {	
		left_mute = chip->mix_elements[array_idx + 1].sw[1];
		right_mute = chip->mix_elements[array_idx + 1].sw[0];
	}

	if (direction) {
		left_val = 0xA000 | ucontrol->value.integer.value[0];
		right_val = 0x9000 | ucontrol->value.integer.value[1];
	} else {
		left_val = 0x6000 | ucontrol->value.integer.value[0];
		right_val = 0x5000 | ucontrol->value.integer.value[1];
	}
	
	if (left_mute) {
		left_val = left_val & 0xFF7F;
	} else {
		left_val = left_val | 0x80;
	}
	
	if (right_mute) {
		right_val = right_val & 0xFF7F;
	} else {
		right_val = right_val | 0x80;
	}
	
	// left value
	parameter = left_val | (index << 8);
	snd_azx_send_corb_cmd(chip, cad, nid, direct, SET_AMPLIFIER_GAIN_MUTE, parameter, &lower_response, &upper_response);
	chip->mix_elements[array_idx].vol[0] = ucontrol->value.integer.value[0];
	
	// right value	
	parameter = right_val | (index << 8);
	snd_azx_send_corb_cmd(chip, cad, nid, direct, SET_AMPLIFIER_GAIN_MUTE, parameter, &lower_response, &upper_response);
	chip->mix_elements[array_idx].vol[1] = ucontrol->value.integer.value[1];
	
	return 1;
}

int snd_azx_switch_get(snd_kcontrol_t* kcontrol, snd_ctl_elem_value_t* ucontrol)
{
	azx_t* chip = snd_kcontrol_chip(kcontrol);
	int array_idx = (kcontrol->private_value >> 22) & 0xFF;
	
	ucontrol->value.integer.value[0] = chip->mix_elements[array_idx].sw[0];
	ucontrol->value.integer.value[1] = chip->mix_elements[array_idx].sw[1];
	
	return 0;
}
int snd_azx_switch_put(snd_kcontrol_t* kcontrol, snd_ctl_elem_value_t* ucontrol)
{
	azx_t* chip = snd_kcontrol_chip(kcontrol);
	u32 cad = get_audio_codec_address();
	u32 direction = (kcontrol->private_value >> 12) & 0x01;
	u32 index = (kcontrol->private_value >> 8) & 0x0F;
	u32 nid = kcontrol->private_value & 0xFF;
	u32 parameter;
	u32 direct = 0;
	
	int array_idx = (kcontrol->private_value >> 22) & 0xFF;
	
	u32 left_val;
	u32 right_val;
	u32 lower_response;
	u32 upper_response;
	u32 left_vol = chip->mix_elements[array_idx - 1].vol[0];
	u32 right_vol = chip->mix_elements[array_idx - 1].vol[1];

	char left_mute = ucontrol->value.integer.value[0];
	char right_mute = ucontrol->value.integer.value[1];
	
	if (direction) {
		left_val = 0xA000 | left_vol;
		right_val = 0x9000 | right_vol;
	} else {
		left_val = 0x6000 | left_vol;
		right_val = 0x5000 | right_vol;
	}
	
	if (left_mute) {
		left_val = left_val & 0xFF7F;
	} else {
		left_val = left_val | 0x80;
	}
	
	if (right_mute) {
		right_val = right_val & 0xFF7F;
	} else {
		right_val = right_val | 0x80;
	}
	
	// left value
	parameter = left_val | (index << 8);
	snd_azx_send_corb_cmd(chip, cad, nid, direct, SET_AMPLIFIER_GAIN_MUTE, parameter, &lower_response, &upper_response);
	chip->mix_elements[array_idx].sw[0] = ucontrol->value.integer.value[0];
	
	// right value	
	parameter = right_val | (index << 8);
	snd_azx_send_corb_cmd(chip, cad, nid, direct, SET_AMPLIFIER_GAIN_MUTE, parameter, &lower_response, &upper_response);
	chip->mix_elements[array_idx].sw[1] = ucontrol->value.integer.value[1];
	
	return 1;
}

EXPORT_NO_SYMBOLS; /* for older kernels */
