/* 2008-12-02: File added and changed by Sony Corporation */
/*
 * sound/arm/omap/omap2-audio-aic23.c
 *
 * Codec driver for AIC23 for OMAP processors
 *
 * Copyright (C) 2007 Texas Instruments, Inc.
 *
 * This package is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * History:
 *  -------
 *  2008-04-03 Anuj Aggarwal - Created
 */

/***************************** INCLUDES ************************************/

#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/errno.h>
#include <linux/sound.h>
#include <linux/soundcard.h>
#include <linux/bitops.h>
#include <linux/interrupt.h>
#include <linux/device.h>

#include <asm/semaphore.h>
#include <asm/uaccess.h>
#include <asm/hardware.h>
#include <asm/arch/dma.h>
#include <asm/io.h>

#include <asm/arch/mux.h>
#include <asm/arch/prcm.h>
#include <asm/arch/clock.h>
#include <asm/arch/io.h>
#include <asm/mach-types.h>

#include <sound/driver.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/control.h>

#include "omap2-audio_if.h"

#if defined(CONFIG_ARCH_OMAP3430)
#include <asm/arch/omap2_mcbsp.h>
#include <asm/arch/clock.h>
#else
#error "Unsupported configuration"
#endif

#include <asm/arch/omap35xdc.h>

#include "omap2-audio-aic23.h"

/*********** Debug Macros ********/
/* Change to define if required */
/* To Generate a rather shrill tone -test the entire path */
#undef TONE_GEN

/* To dump the twl registers for debug */
#undef AIC23_DUMP_REGISTERS

/* To dump the McBSP registers for debug */
#undef AIC_DUMP_REGISTERS_MCBSP

#undef DEBUG
#undef DPRINTK

#ifdef DEBUG
#define DPRINTK(ARGS...)  printk(KERN_INFO "<%s>: ",__FUNCTION__);printk(ARGS)
#define FN_IN printk(KERN_INFO "[%s]: start\n", __FUNCTION__)
#define FN_OUT(n) printk(KERN_INFO "[%s]: end(%u)\n",__FUNCTION__, n)

#define FN_MIXER_IN printk(KERN_INFO "[%s]: start\n", __FUNCTION__)
#define FN_MIXER_OUT(n) printk(KERN_INFO "[%s]: end(%u)\n",__FUNCTION__, n)
#else
#define DPRINTK( x... )
#define FN_IN
#define FN_OUT(n)

#define FN_MIXER_IN
#define FN_MIXER_OUT(n)
#endif	/* DEBUG */

#ifdef AIC23_DUMP_REGISTERS
static void aic23_dumpRegisters(void);
#endif	/* AIC23_DUMP_REGISTERS */

#ifdef TONE_GEN
void toneGen(void);
#endif	/* TONE_GEN */

#define CONTROL_PADCONF_MCBSP2_FSX_REG		(0x4800213Cu)
#define CONTROL_PADCONF_MCBSP2_DR_REG		(0x48002140u)
#define CONTROL_PADCONF_SYS_NIRQ_REG		(0x480021E0u)
#define AIC23_CM_CLKOUT_CONTROL			(0x48004D70)

static int mcbsp_interface_acquired = 0;

static u16 aic23_regValues[AIC23_REG_RESET] = {0};

static u16 aic23_regDefValues[AIC23_NUM_REGS] =
				{
				0x0097, 0x0097, 0x00F9, 0x00F9,
				0x0002, 0x0008, 0x00FF, 0x0001,
				0x0020, 0x0000, 0x0000
				};

/****************                                          **************
 *
 ***************************** COMM APIS ********************************
 *
 */

/**
 * @brief audio_aic23_write
 *
 * @param address
 * @param data
 *
 * @return  0 if successful
 */
static inline int audio_aic23_write(u8 address, u16 data)
{
	int ret = 0;
	u16 new_data = 0;
	u8 new_addr = 0;

	FN_IN;

	if (address > AIC23_REG_RESET) {
		printk ("audio_aic23_write: Invalid Address\n");
		ret = -EPERM;
	} else {

		/* DEBUG */
		DPRINTK("original add[0x%02x]<=0x%04x\n", address, data);

		/*
		* Input data is coming as 9 bits [D0-D8].
		* Input address is coming as 7 bits [A0-A6]. A7
		* should be discarded and all other bits should
		* be left shifted by 1. It means A0->A1, A1->A2,
		* and so on A6->A7.
		* Bit D8 should become Bit A0.
		* Actual data would be bits D0-D7 only.
		*/
		data &= 0x1FF;
		address &= 0x7F;

		new_addr = ((address << 1) | (u8)(data >> 8));
		new_data = (u8)data;

		DPRINTK("modified add[0x%02x]<=0x%04x\n", new_addr, new_data);

		/* Store the original values */
		aic23_regValues[address] = data;

		ret = omap35xdc_i2c_write_u8(OMAP35XDC_MODULE_AIC23,
						new_addr, new_data);
		if (ret >= 0) {
			ret = 0;
		} else {
			DPRINTK("AIC23:Audio:Write[0x%x]"
				" Error %d\n", address, ret);
		}
	}

	FN_OUT(ret);

	return ret;
}


static inline u16 audio_aic23_read(u8 address)
{
	return aic23_regValues[address];
}


/****************                                          **************
 *
 ***************************** CODEC UTIL APIS **************************
 *
 */

/*
 *  Omap MCBSP clock configuration and Power Management
 *
 *  Here we have some functions that allows clock to be enabled and
 *   disabled only when needed. Besides doing clock configuration
 *   it allows turn on/turn off audio when necessary.
 */
static void aic23_clock_setup(void)
{
	FN_IN;

	/* enable EN_AIC_McBSP2 using the GPMC CS4 */
	omap35xdc_set_mux(MUX_AIC23_MCBSP2, DISABLE_MUX);

	FN_OUT(0);
}


/*
 * Turn the AIC23 clock ON.
 */
static int aic23_clock_on(void)
{
	u32 err=0;
 	struct clk* aic23_clk;
 	struct clk* cm_96m_clk;

	FN_IN;

 	cm_96m_clk = clk_get(NULL, "cm_96m_ck\0");
 	if (!cm_96m_clk) {
 		printk (KERN_ERR "clk_get failed to acquire cm_96m_clk\n\n");
 		err = (-EINVAL);
 		goto aic23_clock_out;
	}

 	aic23_clk = clk_get(NULL, "sys_clkout2\0");
 	if (!aic23_clk) {
 		printk (KERN_ERR "clk_get failed to acquire sys_clkout2\n\n");
 		err = (-EINVAL);

		/* release the acquired clocks */
		clk_put(cm_96m_clk);

 		goto aic23_clock_out;
 	}

 	err = clk_set_parent(aic23_clk, cm_96m_clk);
 	if (err) {
 		printk (KERN_ERR "clk_set_parent failed for aic23_clk,err=%d\n",err);

		/* release the acquired clocks */
		clk_put(aic23_clk);
		clk_put(cm_96m_clk);

 		goto aic23_clock_out;
 	}

 	err = clk_set_rate(aic23_clk, CODEC_CLOCK/*(clk_get_rate(aic23_clk))/8*/);
 	if (err) {
 		printk (KERN_ERR "clk_set_rate failed for aic23_clk, err=%d\n",err);

		/* release the acquired clocks */
		clk_put(aic23_clk);
		clk_put(cm_96m_clk);

 		goto aic23_clock_out;
 	}

	err = clk_enable(aic23_clk);
 	if (err) {
 		printk (KERN_ERR "clk_enable failed for aic23_clk, err=%d\n\n",err);

		/* release the acquired clocks */
		clk_put(aic23_clk);
		clk_put(cm_96m_clk);

 		goto aic23_clock_out;
 	} else {
 		/* Save the clocks for future reference */
		aic23_clocks.parent_clk = cm_96m_clk;
		aic23_clocks.aic23_clk = aic23_clk;
 	}

      aic23_clock_out:

	FN_OUT(err);
	return err;

}

/*
 * Do some sanity check, turn clock off and then turn
 *  codec audio off
 */
static void aic23_clock_off(void)
{
	FN_IN;

	clk_disable(aic23_clocks.aic23_clk);

	/* release both the acquired clocks */
	clk_put(aic23_clocks.aic23_clk);
	clk_put(aic23_clocks.parent_clk);

	/* Reset both the pointers */
	aic23_clocks.aic23_clk = NULL;
	aic23_clocks.parent_clk = NULL;

	FN_OUT(0);
}


/**
 * @brief aic23_codec_on
 *
 * @return 0 if success, else error value
 */
static inline int aic23_codec_on(void)
{
	FN_IN;

	/* reset the codec first */
	audio_aic23_write(AIC23_REG_RESET, 0x0);

	/* Now turn the audio codec on, only those modules
	 * which are required.
	 */
	audio_aic23_write(AIC23_REG_POWER_DOWN, AIC23_REG_PWRDN_MIC);

	FN_OUT(0);

	return 0;
}


/**
 * @brief aic23_codec_off - Switch off the codec
 *
 * @return 0 if success, else error value
 */
static inline int aic23_codec_off(void)
{
	FN_IN;

	audio_aic23_write(AIC23_REG_POWER_DOWN,
			  (AIC23_REG_PWRDN_ALL));

	/* do a reset too */
	audio_aic23_write(AIC23_REG_RESET, AIC23_REG_RST_RESET);

	FN_OUT(0);

	return 0;
}


/**
 * @brief aic23_mcbsp_irq - the Interrupt handler for mcbsp events
 *
 * @param arg  - ignored
 * @param irq_status -mcbsp interrupt status
 * @param regs -ignored
 */
static void aic23_mcbsp_irq(void *arg, u32 irq_status)
{
#ifdef DEBUG
	DPRINTK(KERN_ERR "ERROR IN TRANSFER 0x%08x ", irq_status);
	if (irq_status & OMAP2_MCBSP_IRQSTAT_XOVFL) {
		DPRINTK(KERN_INFO "XOVFLSTAT ");
	}
	if (irq_status & OMAP2_MCBSP_IRQSTAT_XUNDFL) {
		DPRINTK(KERN_INFO "XUNDFLFSTAT ");
	}
	if (irq_status & OMAP2_MCBSP_IRQSTAT_XRDY) {
		DPRINTK(KERN_INFO "XRDY ");
	}
	if (irq_status & OMAP2_MCBSP_IRQSTAT_XEOF) {
		DPRINTK(KERN_INFO "XEOF ");
	}
	if (irq_status & OMAP2_MCBSP_IRQSTAT_XFSX) {
		DPRINTK(KERN_INFO "XFSX ");
	}
	if (irq_status & OMAP2_MCBSP_IRQSTAT_XSYNCERR) {
		DPRINTK(KERN_INFO "XSYNCERR ");
	}
	if (irq_status & OMAP2_MCBSP_IRQSTAT_ROVFL) {
		DPRINTK(KERN_INFO "ROVFLSTAT ");
	}
	if (irq_status & OMAP2_MCBSP_IRQSTAT_RUNDFL) {
		DPRINTK(KERN_INFO "RUNDFLFSTAT ");
	}
	if (irq_status & OMAP2_MCBSP_IRQSTAT_RRDY) {
		DPRINTK(KERN_INFO "RRDY ");
	}
	if (irq_status & OMAP2_MCBSP_IRQSTAT_REOF) {
		DPRINTK(KERN_INFO "REOF ");
	}
	if (irq_status & OMAP2_MCBSP_IRQSTAT_RFSX) {
		DPRINTK(KERN_INFO "RFSR ");
	}
	if (irq_status & OMAP2_MCBSP_IRQSTAT_RSYNCERR) {
		DPRINTK(KERN_INFO "RSYNCERR ");
	}
	DPRINTK(KERN_INFO "\n");
#endif

	/* In case of errors - we need to reset the reciever to ensure
	 * recovery
	 */
	if ((irq_status &
		(OMAP2_MCBSP_IRQSTAT_XSYNCERR | OMAP2_MCBSP_IRQSTAT_XUNDFL |
		 OMAP2_MCBSP_IRQSTAT_XOVFL))) {
		/* We will handle this when we attempt a transfer */
		tx_err = 1;
	}
	if ((irq_status &
		(OMAP2_MCBSP_IRQSTAT_RSYNCERR | OMAP2_MCBSP_IRQSTAT_RUNDFL |
		 OMAP2_MCBSP_IRQSTAT_ROVFL))) {
		/* We will handle this when we attempt a transfer */
		rx_err = 1;
	}
}


/**
 * @brief aic23_conf_data_interface
 * *NOTE* Call only from dsp device
 *
 * @return  0 if successful
 */
static int aic23_conf_data_interface(void)
{
	int ret = 0;
	int line = 0;
	int frame_length1 = OMAP2_MCBSP_FRAMELEN_2;
	int frame_polarity = OMAP2_MCBSP_FS_ACTIVE_LOW;
	int skip_alt = OMAP2_MCBSP_SKIP_NONE;
	int data_delay = OMAP2_MCBSP_DATADELAY1;

	FN_IN;
	/* Check sample width */
	if (current_bitspersample == AUDIO_SAMPLE_DATA_WIDTH_16) {
		if (current_stereomode == STEREO_MODE) {
			frame_length1 = OMAP2_MCBSP_FRAMELEN_2;
			frame_polarity = OMAP2_MCBSP_FS_ACTIVE_HIGH;
			data_delay = OMAP2_MCBSP_DATADELAY1;
		} else {
			/* Mono Mode */
			frame_length1 = OMAP2_MCBSP_FRAMELEN_1;
			data_delay = OMAP2_MCBSP_DATADELAY0;
		}
	} else if (current_bitspersample == AUDIO_SAMPLE_DATA_WIDTH_24) {
		if (current_stereomode == MONO_MODE) {
			/* mono Mode */
			/* use 32 bits dma and do doubleindex */
			skip_alt = OMAP2_MCBSP_SKIP_SECOND;
		}
		/* 2 words */
		frame_length1 = OMAP2_MCBSP_FRAMELEN_2;
	} else {
		DPRINTK(KERN_ERR "Unknown sample width %d\n",
		       current_bitspersample);
		return -EPERM;
	}

	/* reset the McBSP registers so that we can
	 * configure it
	 */
	if (unlikely(ret = omap2_mcbsp_interface_reset(AUDIO_MCBSP))) {
		DPRINTK(KERN_ERR "conf_data Reset for MCBSP Failed[%d]\n", ret);
		/* Dont care abt result */
		return ret;
	}

	/* setup the new params */
	plat_mcbsp_config.tx_params.frame_length1 = frame_length1;
	plat_mcbsp_config.tx_params.frame_length2 = frame_length1;
	plat_mcbsp_config.tx_params.data_delay = data_delay;

	plat_mcbsp_config.rx_params.frame_length1 = frame_length1;
	plat_mcbsp_config.rx_params.frame_length2 = frame_length1;
	plat_mcbsp_config.rx_params.data_delay = data_delay;

	plat_mcbsp_config.fs_clk_pol = frame_polarity;
	plat_mcbsp_config.tx_polarity = frame_polarity;
	plat_mcbsp_config.rx_polarity = frame_polarity;

	ret = omap2_mcbsp_set_srg(AUDIO_MCBSP, OMAP2_MCBSP_SRG_DISABLE);
	if (unlikely(ret != 0)) {
		line = __LINE__;
		goto mcbsp_config_exit;
	}

	ret = omap2_mcbsp_set_fsg(AUDIO_MCBSP, OMAP2_MCBSP_FSG_DISABLE);
	if (unlikely(ret != 0)) {
		line = __LINE__;
		goto mcbsp_config_exit;
	}

	DPRINTK("mcbsp_id=%d samplerate=%ld,"
		"bits=%d clk_src=%d clk=%d, sync=%d pol=%d\n",
		AUDIO_MCBSP,
		audio_samplerate,
		current_bitspersample,
		plat_mcbsp_config.srg_clk_src,
		1,
		plat_mcbsp_config.srg_clk_sync, plat_mcbsp_config.srg_clk_pol);

	/* Set the sample rate in mcbsp */
	/* PRCM Clock used - dont care abt clock - mcbsp, find it out */
	ret = omap2_mcbsp_srg_cfg(AUDIO_MCBSP,
				  audio_samplerate,
				  current_bitspersample,
				  plat_mcbsp_config.srg_clk_src,
				  1,
				  plat_mcbsp_config.srg_clk_sync,
				  plat_mcbsp_config.srg_clk_pol);
	if (unlikely(ret < 0)) {
		line = __LINE__;
		goto mcbsp_config_exit;
	}

	/* Setup the framesync clocks */
	DPRINTK("fsync cfg rxsrc=%d, txsrc=%d tx_pol=%d rx_pol=%d "
		"period=%d pulsewidth=%d\n",
		plat_mcbsp_config.tx_clk_src,
		plat_mcbsp_config.rx_clk_src,
		plat_mcbsp_config.tx_polarity,
		plat_mcbsp_config.rx_polarity,
		current_bitspersample * 2 - 1, current_bitspersample - 1);

	ret =
	    omap2_mcbsp_fsync_cfg(AUDIO_MCBSP,
				  plat_mcbsp_config.tx_clk_src,
				  plat_mcbsp_config.rx_clk_src,
				  plat_mcbsp_config.tx_polarity,
				  plat_mcbsp_config.rx_polarity, 0, 0, 0);
	if (unlikely(ret != 0)) {
		line = __LINE__;
		goto mcbsp_config_exit;
	}

	DPRINTK("IIItx_pol=%d, %d\n", plat_mcbsp_config.tx_clk_pol,
		plat_mcbsp_config.rx_clk_pol);

	ret =
	    omap2_mcbsp_txclk_cfg(AUDIO_MCBSP,
				  plat_mcbsp_config.tx_ip_clk,
				  plat_mcbsp_config.tx_clk_pol);
	if (unlikely(ret != 0)) {
		line = __LINE__;
		goto mcbsp_config_exit;
	}

	ret =
	    omap2_mcbsp_rxclk_cfg(AUDIO_MCBSP,
				  plat_mcbsp_config.rx_ip_clk,
				  plat_mcbsp_config.rx_clk_pol);
	if (unlikely(ret != 0)) {
		line = __LINE__;
		goto mcbsp_config_exit;
	}

      mcbsp_config_exit:
	if (unlikely(ret != 0)) {
		DPRINTK(KERN_ERR
		       "Unable to configure Mcbsp ret=%d @ line %d.", ret,
		       line);
	}

	FN_OUT(ret);

	return ret;
}


/**
 * @brief aic23_set_samplerate - set the sample rate of the codec
 *  and communication media (mcbsp)
 *  * NOTE* Shut down the codec to change sample rate
 *          Cannot reprogram the Codec APLL while codec is powered
 *
 * @param sample_rate  - rate we wish to set
 *
 * @return  0 if successful
 */
static int aic23_set_samplerate(long sample_rate)
{
	int ret = 0;
	int count;
	u16 data = 0;

	FN_IN;

	/* validate if rate is proper */
	for (count = 0; count < NUMBER_OF_RATES_SUPPORTED; count++) {
		if (aic23_sample_rates[count] == sample_rate) {
			break;
		}
	}

	if (count >= NUMBER_OF_RATES_SUPPORTED) {
		DPRINTK(KERN_ERR "[%d] Unsupported sample rate!!\n",
		       (u32) sample_rate);
		ret = -EPERM;
	} else {

		data = (aic23_rate_reg_info[count].divider << AIC23_REG_SRATE_CLKIN_SHIFT)
			|(aic23_rate_reg_info[count].control << AIC23_REG_SRATE_BOSR_SHIFT)
			| AIC23_REG_SRATE_CLOCK_MODE_USB;

		audio_aic23_write(AIC23_REG_SAMPLE_RATE, data);

		/* Print the value written */
		DPRINTK("Reg[0x%.2x] = 0x%.4x\n", AIC23_REG_SAMPLE_RATE,
				data);
	}

	/* change the sample rate if we are successful */
	if (!ret) {
		audio_samplerate = sample_rate;
	}

	FN_OUT(ret);
	return ret;
}


/**
 * @brief aic23_stereomode_set - setup the stereo mode
 * in mono mode only the LEFT data on I2S will be valid.
 * This is same for RX and TX. mode is common for both rx and tx
 *
 * @param mode  - stereo or mono
 *
 * @param dsp  - called from dsp context?
 *
 * @return 0 if success/return error code
 */
static int aic23_stereomode_set(int mode, int dsp)
{
	int ret = 0;

	FN_IN;

	if (current_stereomode == mode) {
		/* nothing to do at all */
		DPRINTK ("nothing to do in stereomode setting\n");
		return 0;
	}

	current_stereomode = mode;

	/* Let the Mcbsp work its magic */
	if (dsp) {
		ret = aic23_conf_data_interface();
		if (ret) {
			DPRINTK(KERN_ERR "Configure data interface failed\n");
			goto set_stereo_mode_exit;
		}
		ret = omap2_mcbsp_set_recv_params(AUDIO_MCBSP,
					&(plat_mcbsp_config.rx_params));
		if (ret < 0) {
			DPRINTK(KERN_ERR "MONO/STEREO RX params failed");
			goto set_stereo_mode_exit;
		}
		ret = omap2_mcbsp_set_trans_params(AUDIO_MCBSP,
					&(plat_mcbsp_config.tx_params));
		if (ret < 0) {
			DPRINTK(KERN_ERR "MONO/STEREO TX params failed");
			goto set_stereo_mode_exit;
		}
	}

       set_stereo_mode_exit:
	if (ret) {
		DPRINTK(KERN_ERR "Setting Stereo mode failed[0x%x]\n", mode);
	}

	FN_OUT(ret);

	return ret;
}


/**
 * @brief aic23_unconfigure
 */
static void aic23_unconfigure(void)
{
	FN_IN;

	if (1 == aic23_configured) {
		aic23_codec_off();
	}
	if (aic23_configured > 0)
		aic23_configured--;

	FN_OUT(0);
}


/**
 * @brief aic23_configure
 * *NOTE* should be called with codec off
 *
 * @return  0 if successful
 */
static int aic23_configure(void)
{
	int ret = 0;
	u16 dig_format=0;
	u16 curr_val = 0;

	FN_IN;

	if (0 == aic23_configured) {
		/* Enable digital interface */
		audio_aic23_write(AIC23_REG_DIGITAL_INTERFACE_ENABLE,
					AIC23_REG_DIGINT_ENABLE);
		/* Digital audio path control */
		audio_aic23_write(AIC23_REG_DIGITAL_PATH, 0x0);

		/*
		 * Based on the current input source, enable it
		 * and set the volume control.
		 */
		if (current_input == INPUT_LINEIN) {
			u16 linein_l = 0;
			u16 linein_r = 0;

			/*
			 * Switch on the line in input and disable other
			 * input sources.
			 */
			curr_val = audio_aic23_read(AIC23_REG_ANALOG_PATH);
			/* Switch on the DAC, linein input and mute mic */
			curr_val &= ~AIC23_REG_ANAP_INPUT_USE_MIC;
			curr_val |= AIC23_REG_ANAP_DAC_ENABLE;
			if (aic23_local.mic_muted)
				curr_val |= AIC23_REG_ANAP_MIC_MUTE;

			audio_aic23_write(AIC23_REG_ANALOG_PATH, curr_val);

			/* Do the vol control too, chk for mute also */
			linein_l |= aic23_local.linein_volume_l;
			if (!aic23_local.linein_enabled)
				linein_l |= AIC23_REG_INVOL_LIM_MUTED;

			linein_r |= aic23_local.linein_volume_r;
			if (!aic23_local.linein_enabled)
				linein_r |= AIC23_REG_INVOL_RIM_MUTED;

			audio_aic23_write(AIC23_REG_LEFT_INPUT_VOLUME,
					linein_l);
			audio_aic23_write(AIC23_REG_RIGHT_INPUT_VOLUME,
					linein_r);

		} else {
			if ((current_input == INPUT_MIC)) {
				/* Read the current value */
				curr_val = audio_aic23_read(AIC23_REG_ANALOG_PATH);

				curr_val |= (AIC23_REG_ANAP_DAC_ENABLE |
							AIC23_REG_ANAP_INPUT_USE_MIC);

				if (aic23_local.mic_boost)
					curr_val |= AIC23_REG_ANAP_MIC_BOOST_20DB;
				if (aic23_local.mic_muted)
					curr_val |= AIC23_REG_ANAP_MIC_MUTE;

				audio_aic23_write(AIC23_REG_ANALOG_PATH, curr_val);
			}
		}

		/*
		 * Based on the current output source, enable it
		 * and set the volume control.
		 */
		if (current_output == OUTPUT_HEADPHONE) {
			u16 headphone_l = AIC23_REG_HPVOL_LZC_DETECT;
			u16 headphone_r = AIC23_REG_HPVOL_LZC_DETECT;

			/* Read the current value */
			curr_val = audio_aic23_read(AIC23_REG_ANALOG_PATH);
			/* Switch on the DAC, linein input and mute mic */
			curr_val |= AIC23_REG_ANAP_DAC_ENABLE;

			audio_aic23_write(AIC23_REG_ANALOG_PATH,
						curr_val);

			/* Do the vol control too, chk for mute also */
			headphone_l |= (aic23_local.headphone_volume_l &
							AIC23_MAX_OUTPUT_VOLUME);
			headphone_r |= (aic23_local.headphone_volume_r &
							AIC23_MAX_OUTPUT_VOLUME);

			audio_aic23_write(AIC23_REG_LEFT_OUTPUT_VOLUME,
					headphone_l);
			audio_aic23_write(AIC23_REG_RIGHT_OUTPUT_VOLUME,
					headphone_r);
			}

		/* Digital audio interface, master mode, DSP, 16 bit */
		dig_format = (AIC23_REG_DIGFMT_MASTER
				|AIC23_REG_DIGFMT_DAC_PHASE_1
				|AIC23_REG_DIGFMT_INPUT_16BIT
				|AIC23_REG_DIGFMT_FORMAT_DSP);
		audio_aic23_write(AIC23_REG_DIGITAL_INTERFACE_FORMAT, dig_format);

	}

	aic23_configured++;

	FN_OUT(ret);

	return ret;
}

/**
 * @brief aic23_mcbsp_dma_cb - the mcbsp call back
 *
 * @param ch_status - dma channel status value
 * @param arg - the stream_callback structure
 */
static void aic23_mcbsp_dma_cb(u16 ch_status, void *arg)
{
	FN_IN;

	if (ch_status) {
		DPRINTK(KERN_ERR "Error happend[%d 0x%x]!!\n", ch_status,
		       ch_status);
		return;

	}

#if defined (CONFIG_SND_OMAP3EVM)
	audio_period_handler(arg);
#endif

	FN_OUT(0);
}

/****************                                          **************
 *
 ***************************** CODEC  APIS ******************************
 *
 */

/**
 * @brief omap_aic23_transfer [hw_transfer]
 *  - Do the transfer to a said stream
 * @param s - stream for which the transfer occurs
 * @param buffer_phy - the physical address of the buffer
 * @param size - num bytes to transfer
 *
 * @return 0 if success else return value -EBUSY implies retry later..
 */
static int omap_aic23_transfer(int mode, void *buffer_phy, u32 size, void *arg)
{
	int ret = 0;
	FN_IN;

#ifdef AIC_DUMP_REGISTERS_MCBSP
	DPRINTK(KERN_INFO "TRANSFER");
	omap_mcbsp_dump_reg(AUDIO_MCBSP);
#endif

	/* Error Recovery Strategy -
	 * 1. stop codec
	 * 2. stop mcbsp tx/rx
	 * 3. stop dma tx/rx
	 * ---
	 * Restart:
	 * 1. send/recievedata (starts mcbsp,dma)
	 * 2. start codec
	 */
	if (mcbsp_interface_acquired > 0) {
		if (mode == SNDRV_PCM_STREAM_CAPTURE) {
			DPRINTK("RX-%d", size);
			/* Capture Path to be implemented */
			if (rx_err) {
				rx_err = 0;
			}
			ret = omap2_mcbsp_receive_data(AUDIO_MCBSP, arg, (dma_addr_t) buffer_phy, size);
			DPRINTK("rx-%d\n", ret);
		} else {
			DPRINTK("TX-%d", size);
			if (tx_err) {
				tx_err = 0;
			}
			ret = omap2_mcbsp_send_data(AUDIO_MCBSP, arg, (dma_addr_t) buffer_phy, size);
		}
	}

	FN_OUT(ret);
	return ret;
}


/**
 * @brief omap_aic23_transfer_stop [hw_transfer_stop]
 *  - Stop Transfers on the current stream
 * @param s - stream for which the transfer occurs
 *
 * @return 0 if success else return value
 */
static int omap_aic23_transfer_stop(int mode)
{
	int ret = 0;
	FN_IN;

	if (mcbsp_interface_acquired > 0) {
		if (mode == SNDRV_PCM_STREAM_CAPTURE) {
			ret = omap2_mcbsp_stop_datarx(AUDIO_MCBSP);
			(void)omap2_mcbsp_set_rrst(AUDIO_MCBSP,
						   OMAP2_MCBSP_RRST_DISABLE);
		} else {
			ret = omap2_mcbsp_stop_datatx(AUDIO_MCBSP);
			(void)omap2_mcbsp_set_xrst(AUDIO_MCBSP,
						   OMAP2_MCBSP_XRST_DISABLE);
		}
	}

	FN_OUT(ret);
	return ret;
}


static inline int element_size(int mcbsp_wordlen)
{
	if (mcbsp_wordlen == OMAP2_MCBSP_WORDLEN_32)
		return 4;
	if (mcbsp_wordlen == OMAP2_MCBSP_WORDLEN_16)
		return 2;
	/* we don't allow any other word lengths */
	return -1;
}


/**
 * @brief omap_aic23_transfer_posn [hw_transfer_posn]
 *  - Where am i?
 * @param s - stream for which the transfer occurs
 *
 * @return posn of the transfer
 */
static int omap_aic23_transfer_posn(int mode)
{
	int ret = 0;
	int fi, ei;
	FN_IN;

	/* we always ask only one frame to transmit/recieve,
	 * variant is the element num
	 */
	if (mcbsp_interface_acquired > 0) {
		if (mode == SNDRV_PCM_STREAM_CAPTURE) {
			ret = omap2_mcbsp_receiver_index(AUDIO_MCBSP, &ei, &fi);
			ret = ei * element_size(plat_mcbsp_config.rx_params.word_length1);
		} else {
			ret = omap2_mcbsp_transmitter_index(AUDIO_MCBSP, &ei, &fi);
			ret = ei * element_size(plat_mcbsp_config.tx_params.word_length1);
		}
		if (ret < 0) {
			DPRINTK(KERN_ERR
			       "aic23_transfer_posn: Unable to find index of "
			       "transfer\n");
		}
	}
	FN_OUT(ret);
	return ret;
}


/**
 * @brief omap_aic23_transfer_init [hw_transfer_init]
 *  - initialize the current stream
 * @param state -codec state for which this transfer is to be done
 * @param s - stream for which the transfer occurs
 * @param callback - call me back when I am done with the transfer
 *
 * @return 0 if success else return value
 */
static int omap_aic23_transfer_init(int mode)
{
	int ret = 0;
	FN_IN;

	if (mcbsp_interface_acquired > 0) {
		if (mode == SNDRV_PCM_STREAM_CAPTURE) {
			ret =
			    omap2_mcbsp_set_recv_params(AUDIO_MCBSP,
							&(plat_mcbsp_config.rx_params));
			if (ret < 0) {
				DPRINTK(KERN_ERR "RECV params failed");
				goto transfer_exit;
			}
		} else {
			ret =
			    omap2_mcbsp_set_trans_params(AUDIO_MCBSP,
							 &(plat_mcbsp_config.
							   tx_params));
			if (ret < 0) {
				DPRINTK(KERN_ERR "TX params failed");
				goto transfer_exit;
			}
		}
	}

      transfer_exit:
	FN_OUT(ret);
	return ret;
}


/**
 * @brief omap_aic23_initialize
 *
 * @param dummy
 *
 * @return  0 if successful
 */
static int omap_aic23_initialize()
{
	int ret = 0;

	FN_IN;

	mcbsp_interface_acquired++;
	if (mcbsp_interface_acquired != 1) {
		ret = 0;
		goto initialize_exit_path1;
	}

	if (unlikely
	    (ret = omap2_mcbsp_request_interface(AUDIO_MCBSP,
						   OMAP2_MCBSP_SLAVE,
						   OMAP2_MCBSP_FCLKSRC_PRCM))) {
		DPRINTK(KERN_ERR " Request for MCBSP Failed[%d]\n", ret);
		goto initialize_exit_path1;
	}

	aic23_clock_setup();

	/* switch on the codec clock */
	if (unlikely(ret = aic23_clock_on())) {
		DPRINTK(KERN_ERR "aic23_clock_on failed [%d]\n", ret);
		goto initialize_exit_path2;
	}

	/* switch on the codec */
	if (unlikely(ret = aic23_codec_on())) {
		DPRINTK(KERN_ERR "aic23_codec_on failed [%d]\n", ret);

		/* switch off the codec clock first */
		aic23_clock_off();

		goto initialize_exit_path2;
	}

	/* configure the codec */
	if (unlikely(ret = aic23_configure())) {
		DPRINTK(KERN_ERR " aic23_configure_device failed [%d]\n", ret);

		/* switch off the codec first */
		aic23_codec_off();
		/* then switch off the codec clock */
		aic23_clock_off();

		goto initialize_exit_path2;
	}

 	/* Sample rate configuration - set APLLs to setup regs */
 	ret = aic23_set_samplerate(audio_samplerate);
 	if (ret) {
 		DPRINTK(KERN_ERR "Sample rate setting failed [%d]\n", ret);

		/* unconfigure codec first */
		aic23_unconfigure();
		/* then switch off the codec clock */
		aic23_clock_off();

 		goto initialize_exit_path2;
 	}

 	ret = aic23_conf_data_interface();
 	if (ret) {
 		DPRINTK(KERN_ERR "Codec Data init failed [%d]\n", ret);

		/* unconfigure codec first */
		aic23_unconfigure();
		/* then switch off the codec clock */
		aic23_clock_off();

 		goto initialize_exit_path2;
 	}

	/* register ISR */
	ret =
	    omap2_mcbsp_register_isr(AUDIO_MCBSP, aic23_mcbsp_irq, NULL,
				     OMAP2_MCBSP_IRQEN_XOVFL |
				     OMAP2_MCBSP_IRQEN_XUNDFL |
				     OMAP2_MCBSP_IRQEN_ROVFL |
				     OMAP2_MCBSP_IRQEN_RUNDFL);
	if (ret) {
		DPRINTK(KERN_ERR "register of ISR failed [%d]\n", ret);
		goto initialize_exit_path3;
	}

#ifdef AIC_DUMP_REGISTERS_MCBSP
	DPRINTK(KERN_INFO "CONFIG");
	omap_mcbsp_dump_reg(AUDIO_MCBSP);
#endif

#ifdef DEBUG
#ifdef TONE_GEN
	toneGen();
#endif
#endif		/* DEBUG */

	FN_OUT(0);
	return 0;

      initialize_exit_path3:
	aic23_unconfigure();
	aic23_clock_off();
	(void)omap2_mcbsp_interface_reset(AUDIO_MCBSP);

      initialize_exit_path2:
	/* Dont care abt result */
	(void)omap2_mcbsp_release_interface(AUDIO_MCBSP);

      initialize_exit_path1:

    	FN_OUT(ret);

	return ret;
}


/**
 * @brief omap_aic23_shutdown
 *
 * @param dummy
 */
static void omap_aic23_shutdown()
{
	FN_IN;

	mcbsp_interface_acquired--;

	if (!mcbsp_interface_acquired) {
		/* Reset/release McBSP */
		omap2_mcbsp_unregister_isr(AUDIO_MCBSP);
		(void)omap2_mcbsp_interface_reset(AUDIO_MCBSP);
		omap2_mcbsp_release_interface(AUDIO_MCBSP);

		/* Reset Codec */
		aic23_unconfigure();

		/* Release the clocks... */
		aic23_clock_off();
	}

	FN_OUT(0);
}


/**
 * @brief omap_aic23_sidle
 *
 * @return  0 if successful
 */
static int omap_aic23_sidle(u32 idle_state)
{
	return 0;
}


/**
 * @brief omap_aic23_probe - check if the device is in real present or not
 *  If present then register the mixer device. else return failure
 *
 * @return  0 if chip is present and intialized properly
 */
static int omap_aic23_probe()
{
	int ret = 0;
	FN_IN;

	/*
	* 1. Do pin-muxing required for SYC CLOCK OUT2, required by AIC23.
	* 2. Select the clock, set the divider and enable it.
	*/
	aic23_clock_setup();

	ret = aic23_clock_on();
	if (ret != 0)	{
		DPRINTK(KERN_INFO "AIC23 Clock Not Initialized\n");
	}

	FN_OUT(ret);

	return ret;
}


/************************** Mixer Controls ******************************/
/**
 * PCM Playback Switch
 */
static int __pcm_playback_switch_info(struct snd_kcontrol *kcontrol,
						struct snd_ctl_elem_info *uinfo)
{
	uinfo->type 				= SNDRV_CTL_ELEM_TYPE_BOOLEAN;
	uinfo->count				= 1;
	uinfo->value.integer.min	= 0;
	uinfo->value.integer.max	= 1;

	return 0;
}

static int __pcm_playback_switch_get(struct snd_kcontrol *kcontrol,
					  struct snd_ctl_elem_value *ucontrol)
{
	/* Switch is by default on. */
	/* Read it from the local info struct. */
	ucontrol->value.integer.value[0] = aic23_local.pcm_enabled;

	return 0;
}

static int __pcm_playback_switch_put(struct snd_kcontrol *kcontrol,
					struct snd_ctl_elem_value *ucontrol)
{
	int changed = 0;
	u16 l_reg = 0;
	u16 r_reg = 0;

	/* Mute/unmute pcm playback. */
	if (ucontrol->value.integer.value[0] !=
									aic23_local.pcm_enabled) {
		if (ucontrol->value.integer.value[0]) {
			/* Enable pcm switch, write the default values. */
			audio_aic23_write(AIC23_REG_LEFT_OUTPUT_VOLUME,
								aic23_local.headphone_volume_l);
			audio_aic23_write(AIC23_REG_RIGHT_OUTPUT_VOLUME,
								aic23_local.headphone_volume_r);

			/* Update the local info. */
			aic23_local.pcm_enabled = 1;
		} else {
			/* Disable pcm switch, write 0 on volume registers. */
			l_reg = audio_aic23_read(AIC23_REG_LEFT_OUTPUT_VOLUME);
			l_reg &= ~AIC23_REG_HPVOL_LHV_MAX;
			audio_aic23_write(AIC23_REG_LEFT_OUTPUT_VOLUME, l_reg);

			r_reg = audio_aic23_read(AIC23_REG_RIGHT_OUTPUT_VOLUME);
			r_reg &= ~AIC23_REG_HPVOL_RHV_MAX;
			audio_aic23_write(AIC23_REG_RIGHT_OUTPUT_VOLUME, r_reg);

			/* Update the local info */
			aic23_local.pcm_enabled = 0;
		}

	changed = 1;
	}

	return changed;
}


/**
 * PCM Playback Volume
 */
static int __pcm_playback_volume_info(struct snd_kcontrol *kcontrol,
						struct snd_ctl_elem_info *uinfo)
{
	uinfo->type 				= SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count				= 2;
	uinfo->value.integer.min	= 0;
	uinfo->value.integer.max	= AIC23_REG_HPVOL_LHV_MAX;

	return 0;
}

static int __pcm_playback_volume_get(struct snd_kcontrol *kcontrol,
					  struct snd_ctl_elem_value *ucontrol)
{
	/* Read it from the local info struct. */
	ucontrol->value.integer.value[0] =
		aic23_local.headphone_volume_l; /* L */
	ucontrol->value.integer.value[1] =
		aic23_local.headphone_volume_r;/* R */

	return 0;
}

static int __pcm_playback_volume_put(struct snd_kcontrol *kcontrol,
					struct snd_ctl_elem_value *ucontrol)
{
	int changed = 0;
	u16 l_reg = ucontrol->value.integer.value[0] & AIC23_REG_HPVOL_LHV_MAX;
	u16 r_reg = ucontrol->value.integer.value[1] & AIC23_REG_HPVOL_RHV_MAX;

	/* Change pcm playback (HP) volume. */
	if ((aic23_local.headphone_volume_l != l_reg) ||
	    (aic23_local.headphone_volume_r != r_reg)) {
		/* Volume changed. */
		audio_aic23_write(AIC23_REG_LEFT_OUTPUT_VOLUME, l_reg);
		audio_aic23_write(AIC23_REG_RIGHT_OUTPUT_VOLUME, r_reg);

		/* Update the local info. */
		aic23_local.headphone_volume_l = l_reg;
		aic23_local.headphone_volume_r = r_reg;

		changed = 1;
	}

	return changed;
}

/**
 * Line Playback Switch (Bypass)
 */
static int __line_playback_switch_info(struct snd_kcontrol *kcontrol,
						struct snd_ctl_elem_info *uinfo)
{
	uinfo->type 				= SNDRV_CTL_ELEM_TYPE_BOOLEAN;
	uinfo->count				= 1;
	uinfo->value.integer.min	= 0;
	uinfo->value.integer.max	= 1;

	return 0;
}

static int __line_playback_switch_get(struct snd_kcontrol *kcontrol,
					  struct snd_ctl_elem_value *ucontrol)
{
	/* Switch is by default on. */
	/* Read it from the local info struct. */
	ucontrol->value.integer.value[0] = aic23_local.bypass_enabled;

	return 0;
}

static int __line_playback_switch_put(struct snd_kcontrol *kcontrol,
					struct snd_ctl_elem_value *ucontrol)
{
	int changed = 0;
	u16 analog_reg = 0;

	/* Enable/disable line playback (bypass). */
	if (ucontrol->value.integer.value[0] !=
									aic23_local.bypass_enabled) {
		if (ucontrol->value.integer.value[0]) {
			/* Enable line playback */
			analog_reg = audio_aic23_read(AIC23_REG_ANALOG_PATH);
			analog_reg |= AIC23_REG_ANAP_BYPASS;
			audio_aic23_write(AIC23_REG_ANALOG_PATH,
								analog_reg);

			/* Update the local info. */
			aic23_local.bypass_enabled = 1;
		} else {
			/* Disable line playback*/
			analog_reg = audio_aic23_read(AIC23_REG_ANALOG_PATH);
			analog_reg &= ~AIC23_REG_ANAP_BYPASS;
			audio_aic23_write(AIC23_REG_ANALOG_PATH,
								analog_reg);

			/* Update the local info. */
			aic23_local.bypass_enabled = 0;
		}

	changed = 1;
	}

	return changed;
}

/**
 * Line Capture Switch
 */
static int __line_capture_switch_info(struct snd_kcontrol *kcontrol,
						struct snd_ctl_elem_info *uinfo)
{
	uinfo->type 				= SNDRV_CTL_ELEM_TYPE_BOOLEAN;
	uinfo->count				= 1;
	uinfo->value.integer.min	= 0;
	uinfo->value.integer.max	= 1;

	return 0;
}

static int __line_capture_switch_get(struct snd_kcontrol *kcontrol,
					  struct snd_ctl_elem_value *ucontrol)
{
	/* Switch is by default on. */
	/* Read it from the local info struct. */
	ucontrol->value.integer.value[0] = aic23_local.linein_enabled;

	return 0;
}

static int __line_capture_switch_put(struct snd_kcontrol *kcontrol,
					struct snd_ctl_elem_value *ucontrol)
{
	int changed = 0;
	u16 l_reg = 0;
	u16 r_reg = 0;

	/* Mute/unmute linein capture */
	if (ucontrol->value.integer.value[0] !=
									aic23_local.linein_enabled) {
		if (ucontrol->value.integer.value[0]) {
			/* Enable linein capture, unmute it */
			l_reg = audio_aic23_read(AIC23_REG_LEFT_INPUT_VOLUME);
			l_reg &= ~AIC23_REG_INVOL_LIM_MUTED;
			audio_aic23_write(AIC23_REG_LEFT_INPUT_VOLUME,
								l_reg);

			r_reg = audio_aic23_read(AIC23_REG_RIGHT_INPUT_VOLUME);
			r_reg &= ~AIC23_REG_INVOL_RIM_MUTED;
			audio_aic23_write(AIC23_REG_RIGHT_INPUT_VOLUME,
								r_reg);

			/* Update the local info. */
			aic23_local.linein_enabled = 1;
		} else {
			/* Disable linein capture, mute it */
			l_reg = audio_aic23_read(AIC23_REG_LEFT_INPUT_VOLUME);
			l_reg |= AIC23_REG_INVOL_LIM_MUTED;
			audio_aic23_write(AIC23_REG_LEFT_INPUT_VOLUME,
								l_reg);

			r_reg = audio_aic23_read(AIC23_REG_RIGHT_INPUT_VOLUME);
			r_reg |= AIC23_REG_INVOL_RIM_MUTED;
			audio_aic23_write(AIC23_REG_RIGHT_INPUT_VOLUME,
								r_reg);

			/* Update the local info. */
			aic23_local.linein_enabled = 0;
		}

	changed = 1;
	}

	return changed;
}


/**
 * Line Capture Volume
 */
static int __line_capture_volume_info(struct snd_kcontrol *kcontrol,
						struct snd_ctl_elem_info *uinfo)
{
	uinfo->type 				= SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count				= 2;
	uinfo->value.integer.min	= 0;
	uinfo->value.integer.max	= AIC23_REG_INVOL_LIV_MAX;

	return 0;
}

static int __line_capture_volume_get(struct snd_kcontrol *kcontrol,
					  struct snd_ctl_elem_value *ucontrol)
{
	/* Read it from the local info struct. */
	ucontrol->value.integer.value[0] =
		aic23_local.linein_volume_l;	/* L */
	ucontrol->value.integer.value[1] =
		aic23_local.linein_volume_r;	/* R */

	return 0;
}

static int __line_capture_volume_put(struct snd_kcontrol *kcontrol,
					struct snd_ctl_elem_value *ucontrol)
{
	int changed = 0;
	u16 l_reg = (ucontrol->value.integer.value[0]
										& AIC23_REG_INVOL_LIV_MAX);
	u16 r_reg = (ucontrol->value.integer.value[1]
										& AIC23_REG_INVOL_RIV_MAX);

	/* Change linein volume */
	if ((aic23_local.linein_volume_l != l_reg) ||
	    (aic23_local.linein_volume_r != r_reg)) {
		/* Volume needs to be changed */
		l_reg = audio_aic23_read(AIC23_REG_LEFT_INPUT_VOLUME);
		l_reg &= ~AIC23_REG_INVOL_LIV_MAX;
		l_reg |= (ucontrol->value.integer.value[0] & AIC23_REG_INVOL_LIV_MAX);

		r_reg = audio_aic23_read(AIC23_REG_RIGHT_INPUT_VOLUME);
		r_reg &= ~AIC23_REG_INVOL_RIV_MAX;
		r_reg |= (ucontrol->value.integer.value[1] & AIC23_REG_INVOL_RIV_MAX);

		audio_aic23_write(AIC23_REG_LEFT_INPUT_VOLUME, l_reg);
		audio_aic23_write(AIC23_REG_RIGHT_INPUT_VOLUME, r_reg);

		/* Update the local info. */
		aic23_local.linein_volume_l = (ucontrol->value.integer.value[0]
										& AIC23_REG_INVOL_LIV_MAX);
		aic23_local.linein_volume_r= (ucontrol->value.integer.value[1]
										& AIC23_REG_INVOL_RIV_MAX);

		changed = 1;
	}

	return changed;
}

/**
 * Mic Playback Switch (Sidetone)
 */
static int __mic_playback_switch_info(struct snd_kcontrol *kcontrol,
						struct snd_ctl_elem_info *uinfo)
{
	uinfo->type 				= SNDRV_CTL_ELEM_TYPE_BOOLEAN;
	uinfo->count				= 1;
	uinfo->value.integer.min	= 0;
	uinfo->value.integer.max	= 1;

	return 0;
}

static int __mic_playback_switch_get(struct snd_kcontrol *kcontrol,
					  struct snd_ctl_elem_value *ucontrol)
{
	/* Sidetone is by default off. */
	/* Read it from the local info struct. */
	ucontrol->value.integer.value[0] = aic23_local.sidetone_enabled;

	return 0;
}

static int __mic_playback_switch_put(struct snd_kcontrol *kcontrol,
					struct snd_ctl_elem_value *ucontrol)
{
	int changed = 0;
	u16 analog_reg = 0;

	/* Enable/disable mic playback (sidetone) */
	if (ucontrol->value.integer.value[0] !=
									aic23_local.sidetone_enabled) {
		if (ucontrol->value.integer.value[0]) {
			/* Enable mic playback (sidetone) */
			analog_reg = audio_aic23_read(AIC23_REG_ANALOG_PATH);
			analog_reg |= AIC23_REG_ANAP_STE_ENABLED;
			audio_aic23_write(AIC23_REG_ANALOG_PATH,
								analog_reg);

			/* Update the local info. */
			aic23_local.sidetone_enabled = 1;
		} else {
			/* Disable mic playback (sidetone) */
			analog_reg = audio_aic23_read(AIC23_REG_ANALOG_PATH);
			analog_reg &= ~AIC23_REG_ANAP_STE_ENABLED;
			audio_aic23_write(AIC23_REG_ANALOG_PATH,
								analog_reg);

			/* Update the local info. */
			aic23_local.sidetone_enabled = 0;
		}

	changed = 1;
	}

	return changed;
}

/**
 * Mic Playback (Sidetone) Volume
 */
static int __mic_playback_volume_info(struct snd_kcontrol *kcontrol,
						struct snd_ctl_elem_info *uinfo)
{
	uinfo->type 				= SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count				= 1;
	uinfo->value.integer.min	= 0;
	uinfo->value.integer.max	= AIC23_REG_ANAP_SIDETONE_COUNT;

	return 0;
}

static int __mic_playback_volume_get(struct snd_kcontrol *kcontrol,
					  struct snd_ctl_elem_value *ucontrol)
{
	/* Read it from the local info struct */
	ucontrol->value.integer.value[0] = aic23_local.sidetone_volume;

	return 0;
}

static int __mic_playback_volume_put(struct snd_kcontrol *kcontrol,
					struct snd_ctl_elem_value *ucontrol)
{
	int changed = 0;
	u16 analog_reg = 0;
	u8 input = ucontrol->value.integer.value[0];

	/* Restrict the input to the max volume */
	if (input >= AIC23_REG_ANAP_SIDETONE_COUNT)
		input = AIC23_REG_ANAP_SIDETONE_COUNT;

	/* Change mic playback (sidetone) volume */
	if (input != aic23_local.sidetone_volume) {
		/* Volume needs to be changed */
		analog_reg = audio_aic23_read(AIC23_REG_ANALOG_PATH);

		/* If volume requested is 0, then disable it too */
		if (!input) {
			analog_reg &= ~AIC23_REG_ANAP_STE_ENABLED;
			/* Update the local info. */
			aic23_local.sidetone_enabled = 0;
			aic23_local.sidetone_volume = 0;
		} else {
			analog_reg &= ~AIC23_REG_ANAP_SIDETONE_MASK;
			analog_reg |= aic23_snd_sidetone[input];
			/* Update the local info. */
			aic23_local.sidetone_enabled = 1;
			aic23_local.sidetone_volume = input;
		}

		audio_aic23_write(AIC23_REG_ANALOG_PATH, analog_reg);

	changed = 1;
	}

	return changed;
}


/**
 * Mic Capture Switch (input is inverted)
 */
static int __mic_capture_switch_info(struct snd_kcontrol *kcontrol,
						struct snd_ctl_elem_info *uinfo)
{
	uinfo->type 				= SNDRV_CTL_ELEM_TYPE_BOOLEAN;
	uinfo->count				= 1;
	uinfo->value.integer.min	= 0;
	uinfo->value.integer.max	= 1;

	return 0;
}

static int __mic_capture_switch_get(struct snd_kcontrol *kcontrol,
					  struct snd_ctl_elem_value *ucontrol)
{
	/*
	 * Mic capture is by default off, i.e. mic input
	 * is by default disabled (mic muted).
	 * Read it from the local info struct.
	 */
	ucontrol->value.integer.value[0] =
			(aic23_local.mic_muted ? 0 : 1);

	return 0;
}

static int __mic_capture_switch_put(struct snd_kcontrol *kcontrol,
					struct snd_ctl_elem_value *ucontrol)
{
	int changed = 0;
	u16 analog_reg = 0;

	/*
	 * Enable/disable mic capture.
	 * Note that the input is inverted wrt the register settings,
	 * i.e. input 1 means enable mic capture (unmute)
	 * and input 0 means disable mic capture (mute).
	 */
	if (ucontrol->value.integer.value[0] ==
									aic23_local.mic_muted) {
		if (ucontrol->value.integer.value[0]) {
			/* Enable mic capture (unmute) */
			analog_reg = audio_aic23_read(AIC23_REG_ANALOG_PATH);
			analog_reg &= ~AIC23_REG_ANAP_MIC_MUTE;
			audio_aic23_write(AIC23_REG_ANALOG_PATH,
								analog_reg);

			/* Update the local info. */
			aic23_local.mic_muted = 0;
		} else {
			/* Disable mic capture (mute) */
			analog_reg = audio_aic23_read(AIC23_REG_ANALOG_PATH);
			analog_reg |= AIC23_REG_ANAP_MIC_MUTE;
			audio_aic23_write(AIC23_REG_ANALOG_PATH,
								analog_reg);

			/* Update the local info. */
			aic23_local.mic_muted = 1;
		}

	changed = 1;
	}

	return changed;
}

/**
 * Mic Booster Switch
 */
static int __mic_booster_switch_info(struct snd_kcontrol *kcontrol,
						struct snd_ctl_elem_info *uinfo)
{
	uinfo->type 				= SNDRV_CTL_ELEM_TYPE_BOOLEAN;
	uinfo->count				= 1;
	uinfo->value.integer.min	= 0;
	uinfo->value.integer.max	= 1;

	return 0;
}

static int __mic_booster_switch_get(struct snd_kcontrol *kcontrol,
					  struct snd_ctl_elem_value *ucontrol)
{
	/* Mic boost is by default off. */
	/* Read it from the local info struct. */
	ucontrol->value.integer.value[0] = aic23_local.mic_boost;

	return 0;
}

static int __mic_booster_switch_put(struct snd_kcontrol *kcontrol,
					struct snd_ctl_elem_value *ucontrol)
{
	int changed = 0;
	u16 analog_reg = 0;
	u8 input;

	/* Enable/disable mic boost */
	if (ucontrol->value.integer.value[0] !=
									aic23_local.mic_boost) {
		/* Check the input first */
		input = ((ucontrol->value.integer.value[0]) ? 1 : 0);

		analog_reg = audio_aic23_read(AIC23_REG_ANALOG_PATH);
		analog_reg &= ~AIC23_REG_ANAP_MIC_BOOST_20DB;
		analog_reg |= input;
		audio_aic23_write(AIC23_REG_ANALOG_PATH,
							analog_reg);

		/* Update the local info. */
		aic23_local.mic_boost = input;

		changed = 1;
	}

	return changed;
}


/**
 * Capture Source
 */
static int __capture_source_info(struct snd_kcontrol *kcontrol,
						struct snd_ctl_elem_info *uinfo)
{
	/* Mic = 0
	* Line = 1 */
	static char *texts[2] =	{ "Line", "Mic" };

	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
	uinfo->count = 1;
	uinfo->value.enumerated.items = 2;

	if (uinfo->value.enumerated.item > 1)
			uinfo->value.enumerated.item = 1;

	strcpy(uinfo->value.enumerated.name,
			texts[uinfo->value.enumerated.item]);

	return 0;
}

static int __capture_source_get(struct snd_kcontrol *kcontrol,
					  struct snd_ctl_elem_value *ucontrol)
{
	/* Read it from the local info struct. */
	ucontrol->value.enumerated.item[0] =
		(aic23_local.rec_src == INPUT_LINEIN) ? 0 /* Line */ : 1 /* Mic */;

	return 0;
}

static int __capture_source_put(struct snd_kcontrol *kcontrol,
					struct snd_ctl_elem_value *ucontrol)
{
	int changed = 0;
	u16 analog_reg = 0;
	u8 curr_source = 0;	/* Default linein*/

	if (aic23_local.rec_src == INPUT_MIC)
		curr_source = 1;	/* Mic */

	/* Change the capture source */
	if (ucontrol->value.integer.value[0] != curr_source) {
		analog_reg = audio_aic23_read(AIC23_REG_ANALOG_PATH);
		if (curr_source) {
			/* Current is Mic, change it to Linein */
			analog_reg &= ~AIC23_REG_ANAP_INPUT_USE_MIC;

			/* Update the local info. */
			aic23_local.rec_src = INPUT_LINEIN;
			current_input = INPUT_LINEIN;
		} else {
			/* Current is Line In, change it to Mic */
			analog_reg |= AIC23_REG_ANAP_INPUT_USE_MIC;

			/* Update the local info. */
			aic23_local.rec_src = INPUT_MIC;
			current_input = INPUT_MIC;
		}

		audio_aic23_write(AIC23_REG_ANALOG_PATH,
							analog_reg);

		changed = 1;
	}

	return changed;
}


/* Controls Registered */
static struct snd_kcontrol_new aic23_control[] __devinitdata = {
 	{
		.name  = "PCM Playback Switch",
		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
		.index = 0,
		.access= SNDRV_CTL_ELEM_ACCESS_READWRITE,
		.info  = __pcm_playback_switch_info,
		.get   = __pcm_playback_switch_get,
		.put   = __pcm_playback_switch_put,
	},
 	{
		.name  = "PCM Playback Volume",
		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
		.index = 0,
		.access= SNDRV_CTL_ELEM_ACCESS_READWRITE,
		.info  = __pcm_playback_volume_info,
		.get   = __pcm_playback_volume_get,
		.put   = __pcm_playback_volume_put,
	},
 	{
		.name  = "Line Playback Switch",
		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
		.index = 0,
		.access= SNDRV_CTL_ELEM_ACCESS_READWRITE,
		.info  = __line_playback_switch_info,
		.get   = __line_playback_switch_get,
		.put   = __line_playback_switch_put,
	},
 	{
		.name  = "Line Capture Switch",
		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
		.index = 0,
		.access= SNDRV_CTL_ELEM_ACCESS_READWRITE,
		.info  = __line_capture_switch_info,
		.get   = __line_capture_switch_get,
		.put   = __line_capture_switch_put,
	},
 	{
		.name  = "Line Capture Volume",
		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
		.index = 0,
		.access= SNDRV_CTL_ELEM_ACCESS_READWRITE,
		.info  = __line_capture_volume_info,
		.get   = __line_capture_volume_get,
		.put   = __line_capture_volume_put,
	},
 	{
		.name  = "Mic Playback Switch",
		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
		.index = 0,
		.access= SNDRV_CTL_ELEM_ACCESS_READWRITE,
		.info  = __mic_playback_switch_info,
		.get   = __mic_playback_switch_get,
		.put   = __mic_playback_switch_put,
	},
 	{
		.name  = "Mic Playback Volume",
		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
		.index = 0,
		.access= SNDRV_CTL_ELEM_ACCESS_READWRITE,
		.info  = __mic_playback_volume_info,
		.get   = __mic_playback_volume_get,
		.put   = __mic_playback_volume_put,
	},
 	{
		.name  = "Mic Capture Switch",
		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
		.index = 0,
		.access= SNDRV_CTL_ELEM_ACCESS_READWRITE,
		.info  = __mic_capture_switch_info,
		.get   = __mic_capture_switch_get,
		.put   = __mic_capture_switch_put,
	},
 	{
		.name  = "Mic Booster Switch",
		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
		.index = 0,
		.access= SNDRV_CTL_ELEM_ACCESS_READWRITE,
		.info  = __mic_booster_switch_info,
		.get   = __mic_booster_switch_get,
		.put   = __mic_booster_switch_put,
	},
 	{
		.name  = "Capture Source",
		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
		.index = 0,
		.access= SNDRV_CTL_ELEM_ACCESS_READWRITE,
		.info  = __capture_source_info,
		.get   = __capture_source_get,
		.put   = __capture_source_put,
	},

};

static int omap_aic23_mixer_init(struct snd_card *card)
{
	int i=0;
	int err=0;

	snd_assert(card != NULL, return -EINVAL);

	strcpy(card->mixername, MIXER_NAME);

	for (i=0; i < ARRAY_SIZE(aic23_control); i++) {
		if ((err = snd_ctl_add(card,
			snd_ctl_new1(&aic23_control[i], card))) < 0) {
			return err;
		}
	}

	return 0;
}


static int omap_aic23_mixer_shutdown(struct snd_card *card)
{
	return 0;
}


/****************                                          **************
 *
 ***************************** MODULE APIS ******************************
 *
 */

/**
 * @brief aic23_init
 *
 * @return  0 if successful
 */
static int __init aic23_init(void)
{
	u32 val;
	int err = 0;
	u8 i;

	FN_IN;

	/* Do the necessary pin-muxings required for
	* McBSP2 and SYS_CLOCKOUT2.
	*/

	/* Enable pin mux for SYS_CLKOUT2 [16:31].
	* No pull up/down, disable input, mode 0.
	* It is muxed with GPIO186.
	*/
	val = __raw_readl(IO_ADDRESS(CONTROL_PADCONF_SYS_NIRQ_REG));
	val &= 0x0000FFFF;
	__raw_writel(val, (IO_ADDRESS(CONTROL_PADCONF_SYS_NIRQ_REG)));
	DPRINTK ("CONTROL_PADCONF_SYS_NIRQ_REG = 0x%.8x\n",
		__raw_readl(IO_ADDRESS(CONTROL_PADCONF_SYS_NIRQ_REG)));

	/* Enable pin mux for McBSP2_FSX [15:0] & McBSP2_CLKX [16:31].
	* No pull up/down, input enable, mode 0.
	* It is muxed with GPIO116 and GPIO117.
	*/
	val = __raw_readl(IO_ADDRESS(CONTROL_PADCONF_MCBSP2_FSX_REG));
	val = 0x01000100;
	__raw_writel(val, (IO_ADDRESS(CONTROL_PADCONF_MCBSP2_FSX_REG)));
	DPRINTK ("CONTROL_PADCONF_MCBSP2_FSX_REG = 0x%.8x\n",
		__raw_readl(IO_ADDRESS(CONTROL_PADCONF_MCBSP2_FSX_REG)));

	/* Enable pin mux for McBSP2_DR [15:0] & McBSP2_DX [16:31].
	* No pull up/down, input enable, mode 0.
	* It is muxed with GPIO118 and GPIO119.
	*/
	val = __raw_readl(IO_ADDRESS(CONTROL_PADCONF_MCBSP2_DR_REG));
	val = 0x01000100;
	__raw_writel(val, (IO_ADDRESS(CONTROL_PADCONF_MCBSP2_DR_REG)));
	DPRINTK ("CONTROL_PADCONF_MCBSP2_DR_REG = 0x%.8x\n",
		__raw_readl(IO_ADDRESS(CONTROL_PADCONF_MCBSP2_DR_REG)));

	/* Pin Muxing Complete. */

	/* register the codec with the audio driver */
	if ((err = audio_register_codec(&omap_aic23_codec))) {
		DPRINTK(KERN_ERR "Failed to register AIC23 driver"
		       " with Audio ALSA Driver\n");
	}


#ifdef AIC23_DUMP_REGISTERS
	DPRINTK(KERN_INFO "Pre aic23_init \n");
	aic23_dumpRegisters();

	/* The register dump would be:
	*
	* AIC23 Register dump...
	* Register[0x00]=0x0000
	* Register[0x01]=0x0000
	* Register[0x02]=0x0000
	* Register[0x03]=0x0000
	* Register[0x04]=0x0000
	* Register[0x05]=0x0000
	* Register[0x06]=0x0000
	* Register[0x07]=0x0000
	* Register[0x08]=0x0000
	* Register[0x09]=0x0000
	*/
#endif

	/* Write the default values on the AIC23. */
	for (i=0 ; i < AIC23_NUM_REGS ; ++i)	{
		audio_aic23_write(i, aic23_regDefValues[i]);
	}

#ifdef AIC23_DUMP_REGISTERS
	DPRINTK(KERN_INFO "Post aic23_init \n");
	aic23_dumpRegisters();


	/* The register dump would be:
	*
	* AIC23 Register dump...
	* Register[0x00]=0x0097
	* Register[0x01]=0x0097
	* Register[0x02]=0x00f9
	* Register[0x03]=0x00f9
	* Register[0x04]=0x000a
	* Register[0x05]=0x0008
	* Register[0x06]=0x00ff
	* Register[0x07]=0x0001
	* Register[0x08]=0x0020
	* Register[0x09]=0x0000
	*/
#endif

	FN_OUT(err);
	return err;
}

/**
 * @brief aic23_exit
 *
 */
static void __exit aic23_exit(void)
{

	FN_IN;

	(void)audio_unregister_codec(&omap_aic23_codec);

	FN_OUT(0);
	return;
}

/****************                                          **************
 *
 ***************************** DEBUG APIS *******************************
 *
 */

/*******************************************************************************
 * TONEGEN:
 * This is a test to generate a rather unpleasant sound..
 * verifies if the mcbsp is active
 *
 ******************************************************************************/
#ifdef TONE_GEN
/* Generates a shrill tone */
u16 tone[] = {
	0x0ce4, 0x0ce4, 0x1985, 0x1985, 0x25A1, 0x25A1, 0x30FD, 0x30FE,
	0x3B56, 0x3B55, 0x447A, 0x447A, 0x4C3B, 0x4C3C, 0x526D, 0x526C,
	0x56F1, 0x56F1, 0x59B1, 0x59B1, 0x5A9E, 0x5A9D, 0x59B1, 0x59B2,
	0x56F3, 0x56F2, 0x526D, 0x526D, 0x4C3B, 0x4C3B, 0x447C, 0x447C,
	0x3B5A, 0x3B59, 0x30FE, 0x30FE, 0x25A5, 0x25A6, 0x1989, 0x198A,
	0x0CE5, 0x0CE3, 0x0000, 0x0000, 0xF31C, 0xF31C, 0xE677, 0xE676,
	0xDA5B, 0xDA5B, 0xCF03, 0xCF03, 0xC4AA, 0xC4AA, 0xBB83, 0xBB83,
	0xB3C5, 0xB3C5, 0xAD94, 0xAD94, 0xA90D, 0xA90E, 0xA64F, 0xA64E,
	0xA562, 0xA563, 0xA64F, 0xA64F, 0xA910, 0xA90F, 0xAD93, 0xAD94,
	0xB3C4, 0xB3C4, 0xBB87, 0xBB86, 0xC4AB, 0xC4AB, 0xCF03, 0xCF03,
	0xDA5B, 0xDA5A, 0xE67B, 0xE67B, 0xF31B, 0xF3AC, 0x0000, 0x0000,
	0x0CE4, 0x0CE4, 0x1985, 0x1985, 0x25A1, 0x25A1, 0x30FD, 0x30FE,
	0x3B56, 0x3B55, 0x447A, 0x447A, 0x4C3B, 0x4C3C, 0x526D, 0x526C,
	0x56F1, 0x56F1, 0x59B1, 0x59B1, 0x5A9E, 0x5A9D, 0x59B1, 0x59B2,
	0x56F3, 0x56F2, 0x526D, 0x526D, 0x4C3B, 0x4C3B, 0x447C, 0x447C,
	0x3B5A, 0x3B59, 0x30FE, 0x30FE, 0x25A5, 0x25A6, 0x1989, 0x198A,
	0x0CE5, 0x0CE3, 0x0000, 0x0000, 0xF31C, 0xF31C, 0xE677, 0xE676,
	0xDA5B, 0xDA5B, 0xCF03, 0xCF03, 0xC4AA, 0xC4AA, 0xBB83, 0xBB83,
	0xB3C5, 0xB3C5, 0xAD94, 0xAD94, 0xA90D, 0xA90E, 0xA64F, 0xA64E,
	0xA562, 0xA563, 0xA64F, 0xA64F, 0xA910, 0xA90F, 0xAD93, 0xAD94,
	0xB3C4, 0xB3C4, 0xBB87, 0xBB86, 0xC4AB, 0xC4AB, 0xCF03, 0xCF03,
	0xDA5B, 0xDA5A, 0xE67B, 0xE67B, 0xF31B, 0xF3AC, 0x0000, 0x0000,
	0x0CE4, 0x0CE4, 0x1985, 0x1985, 0x25A1, 0x25A1, 0x30FD, 0x30FE,
	0x3B56, 0x3B55, 0x447A, 0x447A, 0x4C3B, 0x4C3C, 0x526D, 0x526C,
	0x56F1, 0x56F1, 0x59B1, 0x59B1, 0x5A9E, 0x5A9D, 0x59B1, 0x59B2,
	0x56F3, 0x56F2, 0x526D, 0x526D, 0x4C3B, 0x4C3B, 0x447C, 0x447C,
	0x3B5A, 0x3B59, 0x30FE, 0x30FE, 0x25A5, 0x25A6, 0x1989, 0x198A,
	0x0CE5, 0x0CE3, 0x0000, 0x0000, 0xF31C, 0xF31C, 0xE677, 0xE676,
	0xDA5B, 0xDA5B, 0xCF03, 0xCF03, 0xC4AA, 0xC4AA, 0xBB83, 0xBB83,
	0xB3C5, 0xB3C5, 0xAD94, 0xAD94, 0xA90D, 0xA90E, 0xA64F, 0xA64E,
	0xA562, 0xA563, 0xA64F, 0xA64F, 0xA910, 0xA90F, 0xAD93, 0xAD94,
	0xB3C4, 0xB3C4, 0xBB87, 0xBB86, 0xC4AB, 0xC4AB, 0xCF03, 0xCF03,
	0xDA5B, 0xDA5A, 0xE67B, 0xE67B, 0xF31B, 0xF3AC, 0x0000, 0x0000
};

void toneGen(void)
{
	int count = 0;
	int ret = 0;

	DPRINTK(KERN_INFO "TONE GEN TEST :\n");

#ifdef AIC23_DUMP_REGISTERS
	aic23_dumpRegisters();
#endif

	for (count = 0; count < 5000; count++) {
		int bytes;
		for (bytes = 0; bytes < sizeof(tone) / 2; bytes++) {
			ret = omap_mcbsp_pollwrite(AUDIO_MCBSP, tone[bytes]);
			if (ret == -1) {
				/* retry */
				bytes--;
			} else if (ret == -2) {
				DPRINTK(KERN_INFO "ERROR:bytes=%d\n", bytes);
				return;
			}
		}
	}

	DPRINTK(KERN_INFO "SUCCESS\n");
}

#endif				/* End of TONE_GEN */


/*******************************************************************************
 *
 * AIC23_DUMP_REGISTERS:
 * This will dump the entire register set of AIC23, which were previously
 * saved in aic23_regValues[].
 * Useful for major goof ups
 *
 ******************************************************************************/
#ifdef AIC23_DUMP_REGISTERS
/**
 * @brief twl4030_dumpRegisters
 */
static void aic23_dumpRegisters(void)
{
	u8 i = 0;
	u16 data = 0;

	DPRINTK(KERN_INFO "AIC23 Register dump...\n");

	for (i = AIC23_REG_LEFT_INPUT_VOLUME;
		i <= AIC23_REG_DIGITAL_INTERFACE_ENABLE; i++) {
		data = audio_aic23_read(i);
		DPRINTK(KERN_INFO "Register[0x%02x]=0x%04x\n", i, data);
	}
}
#endif				/* End of #ifdef AIC23_DUMP_REGISTERS */


module_init(aic23_init);
module_exit(aic23_exit);

MODULE_AUTHOR("Texas Instruments");
MODULE_DESCRIPTION("Codec audio driver for the TI AIC23 codec.");
MODULE_LICENSE("GPL");


