/* 2009-08-20: File added and changed by Sony Corporation */
/*
 * linux/sound/mp200-mixer.c -- sound codec support for mp200 chip.
 *
 * Created:	Oct 09, 2006
 *
 */
#include <linux/errno.h>
#include <linux/i2c.h>
#include <asm/arch/pwc.h>
#include <asm/arch/smu.h>
#include <asm/io.h>
#include <sound/driver.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/pcm.h>
#include <sound/initval.h>

#ifdef CONFIG_PM
#include <linux/pm.h>
#endif

#include "mp200-pcm.h"
#include "mp200-mixer.h"

#ifdef AUDIO_MAKING_DEBUG
#include <linux/moduleparam.h>
#endif

/*** debug code by the making ->*/
#ifdef AUDIO_MAKING_DEBUG
#define FNC_ENTRY	\
	if (debug == 1 || debug >= 9) {	\
		printk(KERN_DEBUG "entry:%s\n", __FUNCTION__); \
	}
#define FNC_EXIT	\
	if (debug == 1 || debug >= 9) {	\
		printk(KERN_DEBUG "exit:%s:%d\n", __FUNCTION__ , __LINE__); \
	}
#define d0b(fmt, args...)	\
		printk(KERN_DEBUG "%s:%d: " fmt , __FUNCTION__ , __LINE__ , ## args);
#define d1b(fmt, args...)	\
	if (debug == 1 || debug >= 9) {	\
		printk(KERN_DEBUG "%s:%d: " fmt , __FUNCTION__ , __LINE__ , ## args); \
	}
#define d7b(fmt, args...)	\
	if (debug == 7 || debug >= 9) {	\
		printk(KERN_DEBUG "%s:%d: " fmt , __FUNCTION__ , __LINE__ , ## args); \
	}
#define d8b(fmt, args...)	\
	if (debug == 8 || debug >= 9) {	\
		printk(KERN_DEBUG "%s:%d: " fmt , __FUNCTION__ , __LINE__ , ## args); \
	}
#define d9b(fmt, args...)	\
	if (debug == 9 || debug >= 9) {	\
		printk(KERN_DEBUG fmt , ## args); \
	}
static unsigned int ddata;
#else
#define FNC_ENTRY
#define FNC_EXIT
#define d0b(fmt, args...)
#define d1b(fmt, args...)
#define d7b(fmt, args...)
#define d8b(fmt, args...)
#define d9b(fmt, args...)
#endif
/*<- debug code by the making ***/

#include <linux/delay.h>

#define CODEC_WRITE(a, d, m)	i2c_codec_write(a, d, m)

#define TYPE_VOL	0x01
#define TYPE_SW		0x02
#define TYPE_BL		0x04

#define IDX(a)	(a & 0xffff)

/* VOL(integer) */
enum {
  MIXER_VOL_PLAYBACK = TYPE_VOL << 16 | 0x0000,
  MIXER_VOL_CAPTURE,

  MIXER_INT_NUM = IDX(MIXER_VOL_CAPTURE) + 1,
};

/* SW (enum) */
enum {
  MIXER_SW_CAPTURE_SOURCE = TYPE_SW << 16 | 0x0000,
  MIXER_SW_CAPTURE_CHANNEL_MODE,
  MIXER_SW_SAMPLING_RATE,
  MIXER_SW_PLAYBACK,

  MIXER_ENUM_NUM = IDX(MIXER_SW_PLAYBACK) + 1,
};

/* SW (boolean) */
enum {
  MIXER_SW_CODEC_POWER_BL = TYPE_BL << 16 | 0x0000,

  MIXER_BL_NUM = IDX(MIXER_SW_CODEC_POWER_BL) + 1,
};

static const char *in_sw_control_texts[] = {
  "MIC", "Line"
};

static const char *pmad_sw_control_texts[] = {
  "Stereo", "Mono Lch", "Mono Rch", "MIX"
};

static const char *fs_sw_control_texts[] = {
  "7.35kHz", "8kHz", "11.025kHz", "12kHz", "14.7kHz", "16kHz",
  "22.05kHz", "24kHz", "29.4kHz", "32kHz", "44.1kHz", "48kHz"
};

static const char *out_sw_control_texts[] = {
  "Line", "Headphone"
};

struct integer_info {
	unsigned int count;	/* count of values */
	long val_int_min;	/* R: minimum value */
	long val_int_max;	/* R: maximum value */
	long val_int_step;	/* R: step (0 variable) */
	int value[2];
};

struct enum_info {
	char** texts;
	unsigned int items;
	int value;
};

struct boolean_info {
	int value;
};

struct mp200_codec_mixer {
	struct snd_card *card;
	spinlock_t mixer_lock;
	struct integer_info * vol_info;
	struct enum_info * enum_info;
	struct boolean_info * bl_info;
	int power_on;
	struct mutex power_mutex;
};

static struct integer_info volume_info[MIXER_INT_NUM] = {
  { /* MIXER_VOL_PLAYBACK */
    .count = 2,
    .val_int_min = 0,
    .val_int_max = 100,
    .val_int_step = 1,
  },
  { /* MIXER_VOL_CAPTURE */
    .count = 2,
    .val_int_min = 0,
    .val_int_max = 100,
    .val_int_step = 1,
  },
};

#define NUM_OF(v) (sizeof(v)/sizeof(v[0]))
static struct enum_info enum_info[MIXER_ENUM_NUM] = {
  { /* MIXER_SW_CAPTURE_SOURCE */
    .texts = (char**)in_sw_control_texts,
    .items = NUM_OF(in_sw_control_texts),
  },
  { /* MIXER_SW_CAPTURE_CHANNEL_MODE */
    .texts = (char**)pmad_sw_control_texts,
    .items = NUM_OF(pmad_sw_control_texts),
  },
  { /* MIXER_SW_SAMPLING_RATE */
    .texts = (char**)fs_sw_control_texts,
    .items = NUM_OF(fs_sw_control_texts),
  },
  { /* MIXER_SW_PLAYBACK */
    .texts = (char**)out_sw_control_texts,
    .items = NUM_OF(out_sw_control_texts),
  },
};

static struct boolean_info boolean_info[MIXER_BL_NUM];

static struct mp200_codec_mixer codec_mixer = {
  .vol_info = &volume_info[0],
  .enum_info = &enum_info[0],
  .bl_info = &boolean_info[0],
  .power_on = 0, /* off */
};

static pcm_ctrl_t pcm_sett = {
#if !defined(MP200_PCM_USE_PDMA) && defined(MP200_PCM_FMT16)
	.func = {
		.mode_sel	= PCM_MODE_3,
		.m_s		= PCM_SLAVE_MODE,
		.tx_tim		= PCM_TX_04_WORD,
		},
	.cyc = {
		.cyc_val	= 0x0f,
		.sib		= 0x0f,
		.rx_pd		= PCM_PADDING_ON,
		.sob		= 0x0f,
		.tx_pd		= PCM_PADDING_ON,
		},
	.cyc2 = {
		.cyc_val2	= 0x0f,
		.sib2		= 0x0f,
		.sob2		= 0x0f,
		},
#else
	.func = {
		.mode_sel	= PCM_MODE_3,
		.m_s		= PCM_SLAVE_MODE,
		.tx_tim		= PCM_TX_04_WORD,
		},
	.cyc = {
		.cyc_val	= 0x1f,
		.sib		= 0x1f,
		.rx_pd		= PCM_PADDING_ON,
		.sob		= 0x1f,
		.tx_pd		= PCM_PADDING_ON,
		},
	.cyc2 = {
		.cyc_val2	= 0x1f,
		.sib2		= 0x1f,
		.sob2		= 0x1f,
		},
#endif
};


/* Local prototypes */
#ifdef CODEC_TEST_DEBUG
static int i2c_codec_read(unsigned char reg, unsigned char *data);
#endif /* CODEC_TEST_DEBUG */
static int codec_i2c_probe(struct i2c_adapter *adapter);
static int codec_i2c_detach(struct i2c_client *client);
static int codec_i2c_command(struct i2c_client *client, unsigned int cmd,
			      void *arg);


static unsigned char i2c_codec_reg[AK4648REG_MAX];
static unsigned short normal_i2c[] = { AK4648_DEVICE , I2C_CLIENT_END };
static unsigned short ignore = I2C_CLIENT_END;
static struct i2c_client_address_data codec_i2c_addr_data = {
	.normal_i2c	= normal_i2c,
	.probe		= &ignore,
	.ignore		= &ignore,
};
static struct i2c_driver i2c_codec_driver = {
	.driver.name	= "i2c for codec",
	.id		= I2C_DRIVERID_I2CDEV, /* Fake ID */
	.attach_adapter = codec_i2c_probe,
	.detach_client  = codec_i2c_detach,
	.command	= codec_i2c_command,
};
static struct i2c_client *i2c_codec_client = NULL;


/*
 * i2c functions
 */
static int codec_i2c_found_proc(struct i2c_adapter *adapter,
						int addr, int kind)
{
	i2c_codec_client = kzalloc(sizeof(*i2c_codec_client), GFP_KERNEL);

	if (NULL == i2c_codec_client) {
		printk(KERN_ERR "codec_i2c_client allocation failed!\n");
		return -ENOMEM;
	}

	sprintf(i2c_codec_client->name, "stereo_codec");
	i2c_codec_client->adapter = adapter;
	i2c_codec_client->addr = addr;
	i2c_codec_client->driver = &i2c_codec_driver;

	return i2c_attach_client(i2c_codec_client);
}

static int codec_i2c_probe(struct i2c_adapter *adapter)
{
	return i2c_probe(adapter, &codec_i2c_addr_data,
				codec_i2c_found_proc);
}

static int codec_i2c_detach(struct i2c_client *client)
{
	i2c_detach_client(client);
	kfree(client);
	i2c_codec_client = NULL;

	return 0;
}

static int codec_i2c_command(struct i2c_client *client,
				unsigned int cmd, void *arg)
{
	return 0;
}

static int i2c_codec_cleanup(void)
{
	i2c_del_driver(&i2c_codec_driver);

	return 0;
}

static int i2c_codec_inserted(void)
{
	return i2c_add_driver(&i2c_codec_driver);
}

static int i2c_codec_init(void)
{
	int res = 0;
	unsigned char reg = 0;

	res = i2c_codec_inserted();
	if (res == 0) {
		if(i2c_codec_client == NULL) {
			i2c_codec_cleanup();
			printk(KERN_ERR "codec_i2c_found_proc() not called!\n");
			return -EIO;
		}
		res = i2c_master_send(i2c_codec_client, &reg, 1);
		if (res > 0) {
			res =
			    i2c_master_recv(i2c_codec_client, i2c_codec_reg,
					    AK4648REG_MAX);
			if (res > 0) {
				res = 0;
			}
		}
		if (res != 0) {
			i2c_codec_cleanup();
			printk(KERN_ERR "send or recv failed!\n");
		}
	} else {
		printk(KERN_ERR "i2c codec inserted failed!\n");
	}

	return res;
}

static int i2c_codec_write(unsigned char reg, unsigned char data,
			    unsigned char mask)
{
	int res = 0;
	unsigned char buf[2];

	if ((AK4648REG_MAX <= reg) || ((data & ~(mask)) != 0)) {
		return -EINVAL;
	}

	data = (i2c_codec_reg[reg] & ~(mask)) | (data & mask);

	i2c_codec_reg[reg] = data;

	buf[0] = reg;
	buf[1] = data;

	res = i2c_master_send(i2c_codec_client, buf, 2);
	if (res > 0) {
		res = 0;
	} else {
		printk(KERN_ERR "i2c codec write failed!\n");
	}

	return res;
}

#ifdef CODEC_TEST_DEBUG
static int i2c_codec_read(unsigned char reg, unsigned char *data)
{
	int res = 0;
	unsigned char buf = 0;

	if (i2c_codec_client == NULL) {
		printk(KERN_ERR "i2c codec not available!\n");
		return -EIO;
	}

	if (AK4648REG_MAX <= reg) {
		return -EINVAL;
	}

	res = i2c_master_send(i2c_codec_client, &buf, 1);
	if (res <= 0) {
		printk(KERN_ERR "i2c codec send failed!\n");
		return res;
	}

	res = i2c_master_recv(i2c_codec_client, i2c_codec_reg, AK4648REG_MAX);
	if (res > 0) {
		*data = i2c_codec_reg[reg];
		res = 0;
	} else {
		printk(KERN_ERR "i2c codec recv failed!\n");
	}

	return res;
}
#endif /* CODEC_TEST_DEBUG */

static int codec_init(void)
{
	struct mp200_codec_mixer *codec = &codec_mixer;
	int i,j;
	FNC_ENTRY

	/* volume(integer) */
	for (i = 0; i < MIXER_INT_NUM; i++) {
		for (j = 0; j < (codec->vol_info[IDX(i)].count); j++) {
			/* 80 */
			codec->vol_info[IDX(i)].value[j] = 80;
		}
	}

	/* switch control(enum) */
	/* MIC */
	codec->enum_info[IDX(MIXER_SW_CAPTURE_SOURCE)].value = 0;
	/* Stereo */
	codec->enum_info[IDX(MIXER_SW_CAPTURE_CHANNEL_MODE)].value = 0;
	/* 8kHz */
	codec->enum_info[IDX(MIXER_SW_SAMPLING_RATE)].value = 1;
	/* Line */
	codec->enum_info[IDX(MIXER_SW_PLAYBACK)].value = 0;

	/* switch control(boolean) */
	/* "OFF" */
	codec->bl_info[IDX(MIXER_SW_CODEC_POWER_BL)].value = 0;

	FNC_EXIT return 0;
}

static int codec_power_on(void)
{
	struct mp200_codec_mixer *codec = &codec_mixer;
	int res = 0;
	int power_value = 0;

	power_value = codec->bl_info[IDX(MIXER_SW_CODEC_POWER_BL)].value;
	codec_init();
	codec->bl_info[IDX(MIXER_SW_CODEC_POWER_BL)].value = power_value;

	mutex_lock(&codec->power_mutex);
	if(codec->power_on) {
		mutex_unlock(&codec->power_mutex);
		return 0;
	}

	/* CHG_PINSEL_REFCLK0 : REFCLKO(D1-0) */
	outl(0x0,CHG_PINSEL_REFCLK0);
	/* CHG_CTRL_OSC : OSC_EN(D0) */
	outl(0x1, CHG_CTRL_OSC);
#if 0
	/* 5T735:VGP5SET(3.1V) VOUTSET3 D4 */
	pwc_write(PWC_VOUTSET3, 0x10, 0x10);
	/* 5T735:PSCNTGP5("REGGP5" -> "ON") PSCNT2 D3 */
	pwc_write(PWC_PSCNT2, 0x08, 0x08);
	udelay(500);
#endif
 	/* codec CSN/CAD0 pin "L" */
	/* SNDCDA CODEC I2C addr 5T735:GPIO8 IOOUT2 D0 */
	pwc_write(PWC_IOOUT2, 0x0, 0x01);
	/* codec PDN pin "L" -> "H" */
	/* SNDPDN CODEC Reset 5T735:GPIO9 IOOUT2 D1 */
	pwc_write(PWC_IOOUT2, 0xff, 0x02);
	/* PDN pin "L" needs 150ns */

	res = i2c_codec_init();
	if (res != 0) {
		goto err1;
	}

	/* Clock Set Up Sequence */
	/* M_S */
	if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_2,
				AK4648BIT_M_S, AK4648BIT_M_S)) < 0)
		goto err1;
	/* PLL BCKO DIF */
#if !defined(MP200_PCM_USE_PDMA) && defined(MP200_PCM_FMT16)
 #if 1	/* PLL mode 11.2896MHz */
    if ((res = CODEC_WRITE(AK4648REG_MODE_CONTROL_1,
    			(AK4648BIT_PLL2 | AK4648BIT_DIF1 ),
    			(AK4648BIT_PLL_MASK | AK4648BIT_DIF_MASK))) < 0)
 #else	/* PLL mode 12MHz */
    if ((res = CODEC_WRITE(AK4648REG_MODE_CONTROL_1,
    			(AK4648BIT_PLL2 | AK4648BIT_PLL1 | AK4648BIT_DIF1 | AK4648BIT_BCKO),
    			(AK4648BIT_PLL_MASK | AK4648BIT_BCKO | AK4648BIT_DIF_MASK))) < 0)
 #endif
#else
 #if 1	/* PLL mode 11.2896MHz */
    if ((res = CODEC_WRITE(AK4648REG_MODE_CONTROL_1,
    			(AK4648BIT_PLL2 | AK4648BIT_DIF1 | AK4648BIT_BCKO),
    			(AK4648BIT_PLL_MASK | AK4648BIT_BCKO | AK4648BIT_DIF_MASK))) < 0)
 #else	/* PLL mode 12MHz */
    if ((res = CODEC_WRITE(AK4648REG_MODE_CONTROL_1,
    			(AK4648BIT_PLL2 | AK4648BIT_PLL1 | AK4648BIT_DIF1 | AK4648BIT_BCKO),
    			(AK4648BIT_PLL_MASK | AK4648BIT_BCKO |
    			 AK4648BIT_DIF_MASK))) < 0)
 #endif
#endif
		goto err1;
	/* FS */
	if ((res = CODEC_WRITE(AK4648REG_MODE_CONTROL_2,
				AK4648BIT_FS_8KHZ, AK4648BIT_FS_MASK)) < 0)
		goto err1;
	/* PMVCM */
	if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_1,
				AK4648BIT_PMVCM, AK4648BIT_PMVCM)) < 0)
		goto err1;
	/* PMPLL */
	if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_2,
				AK4648BIT_PMPLL, AK4648BIT_PMPLL)) < 0)
		goto err1;
	schedule_timeout_uninterruptible(AK4648_WAIT_PLL_LOCK);

	/* Stereo Lineout Sequence */
	/* DACL */
	if ((res = CODEC_WRITE(AK4648REG_SIGNAL_SELECT_1,
				AK4648BIT_DACL, AK4648BIT_DACL)) < 0)
		goto err1;
	/* IVL */
	if ((res = CODEC_WRITE(AK4648REG_LCH_INPUT_VOLUME_CONTROL,
				0x91, AK4648_INPUT_VOL_MAX)) < 0)
		goto err1;
	/* IVR */
	if ((res = CODEC_WRITE(AK4648REG_RCH_INPUT_VOLUME_CONTROL,
				0x91, AK4648_INPUT_VOL_MAX)) < 0)
		goto err1;
	/* DVL */
	if ((res = CODEC_WRITE(AK4648REG_LCH_DIGITAL_VOLUME_CONTROL,
				((AK4648_OUTPUT_VOL_MAX * (100 - 80)) / 100),
				AK4648_OUTPUT_VOL_MAX)) < 0)
		goto err1;
	/* DVR */
	if ((res = CODEC_WRITE(AK4648REG_RCH_DIGITAL_VOLUME_CONTROL,
				((AK4648_OUTPUT_VOL_MAX * (100 - 80)) / 100),
				AK4648_OUTPUT_VOL_MAX)) < 0)
		goto err1;
	/* DVOLC */
	if ((res = CODEC_WRITE(AK4648REG_MODE_CONTROL_3,
				0x00, AK4648BIT_DVOLC)) < 0)
		goto err1;
	/* LOPS */
	if ((res = CODEC_WRITE(AK4648REG_SIGNAL_SELECT_2,
				AK4648BIT_LOPS, AK4648BIT_LOPS)) < 0)
		goto err1;
	/* PMLO PMDAC */
	if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_1,
				(AK4648BIT_PMLO | AK4648BIT_PMDAC),
				(AK4648BIT_PMLO | AK4648BIT_PMDAC))) < 0)
		goto err1;
	schedule_timeout_uninterruptible(AK4648_WAIT_LO_LOCK);
	/* LOPS */
	if ((res = CODEC_WRITE(AK4648REG_SIGNAL_SELECT_2,
				0x00, AK4648BIT_LOPS)) < 0)
		goto err1;

	/* Mic Input Recording Sequence */
	/* PMMP MGAIN0 */
	if ((res = CODEC_WRITE(AK4648REG_SIGNAL_SELECT_1, AK4648BIT_PMMP,
				(AK4648BIT_PMMP | AK4648BIT_MGAIN0))) < 0)
		goto err1;
	/* IN */
	if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_3,
				0x00, AK4648BIT_IN_MASK)) < 0)
		goto err1;
	/* INL */
	if ((res = CODEC_WRITE(AK4648REG_LCH_INPUT_VOLUME_CONTROL,
				((AK4648_INPUT_VOL_MAX * 80) / 100),
				AK4648_INPUT_VOL_MAX)) < 0)
		goto err1;
	/* INR */
	if ((res = CODEC_WRITE(AK4648REG_RCH_INPUT_VOLUME_CONTROL,
				((AK4648_INPUT_VOL_MAX * 80) / 100),
				AK4648_INPUT_VOL_MAX)) < 0)
		goto err1;
	/* IVOLC */
	if ((res = CODEC_WRITE(AK4648REG_MODE_CONTROL_4,
				0x00, AK4648BIT_IVOLC)) < 0)
		goto err1;
	/* ZTM WTM */
	if ((res = CODEC_WRITE(AK4648REG_TIMER_SELECT,
				(AK4648BIT_ZTM1 | AK4648BIT_WTM1),
				(AK4648BIT_ZTM_MASK | AK4648BIT_WTM_MASK))) < 0)
		goto err1;
	/* REF */
	if ((res = CODEC_WRITE(AK4648REG_ALC_MODE_CONTROL_2,
				((AK4648_INPUT_VOL_MAX * 80) / 100),
				AK4648_INPUT_VOL_MAX)) < 0)
		goto err1;
	/* RGAIN1 LMTH1 */
	if ((res = CODEC_WRITE(AK4648REG_ALC_MODE_CONTROL_3, 0x00,
				(AK4648BIT_RGAIN1 | AK4648BIT_LMTH1))) < 0)
		goto err1;
	/* ALC ZELMN RGAIN0 LMTH0 */
	if ((res = CODEC_WRITE(AK4648REG_ALC_MODE_CONTROL_1,
				(AK4648BIT_ALC | AK4648BIT_LMTH0),
				(AK4648BIT_ALC | AK4648BIT_ZELMN |
				 AK4648BIT_LMAT_MASK |
				 AK4648BIT_RGAIN0 | AK4648BIT_LMTH0))) < 0)
		goto err1;
	/* PMADL */
	if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_1,
				AK4648BIT_PMADL, AK4648BIT_PMADL)) < 0)
		goto err1;
	/* PMADR */
	if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_3,
				AK4648BIT_PMADR, AK4648BIT_PMADR)) < 0)
		goto err1;
	/* MIX */
	if ((res = CODEC_WRITE(AK4648REG_MODE_CONTROL_5,
				0x00, AK4648BIT_MIX)) < 0)
		goto err1;

	codec->power_on++;
	mutex_unlock(&codec->power_mutex);

	return res;

err1:
	/* codec PDN pin "H" -> "L" */
	/* SNDPDN CODEC Reset 5T735:GPIO9 IOOUT2 D1 */
	pwc_write(PWC_IOOUT2, 0x00, 0x02);
	/* PDN pin "L" needs 150ns */
	udelay(1);
#if 0
	/* 5T735:PSCNTGP5("REGGP5" -> "OFF") PSCNT2 D3 */
	pwc_write(PWC_PSCNT2, 0x00, 0x08);
	/* 5T735:VGP5SET(2.85V) VOUTSET3 D4 */
	pwc_write(PWC_VOUTSET3, 0x00, 0x10);
	udelay(500);
#endif
	/* CHG_CTRL_OSC : OSC_EN(D0) */
	outl(0x0, CHG_CTRL_OSC);

	mutex_unlock(&codec->power_mutex);

	return res;
}

static int codec_power_off(void)
{
	struct mp200_codec_mixer *codec = &codec_mixer;
	int res = 0;

	mutex_lock(&codec->power_mutex);
	if(codec->power_on == 0) {
		mutex_unlock(&codec->power_mutex);
		return 0;
	}

	/* Mic Input Recording Sequence */
	/* PMADR */
	if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_3,
				0x00, AK4648BIT_PMADR)) < 0)
		goto out;
	/* PMADL */
	if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_1,
				0x00, AK4648BIT_PMADL)) < 0)
		goto out;
	/* PMMP */
	if ((res = CODEC_WRITE(AK4648REG_SIGNAL_SELECT_1,
				0x00, AK4648BIT_PMMP)) < 0)
		goto out;

	/* Headphone Sequence */
	/* HPMTN */
	if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_2,
				0x00, AK4648BIT_HPMTN)) < 0)
		goto out;
	schedule_timeout_uninterruptible(AK4648_WAIT_HPMTN_DIS_LOCK);
	/* PMHPL PMHPR */
	if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_2, 0x00,
				(AK4648BIT_PMHPL | AK4648BIT_PMHPR))) < 0)
		goto out;
	/* PMDAC */
	if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_1,
				0x00, AK4648BIT_PMDAC)) < 0)
		goto out;
	/* DACH */
	if ((res = CODEC_WRITE(AK4648REG_MODE_CONTROL_4,
				0x00, AK4648BIT_DACH)) < 0)
		goto out;

	/* Stereo Lineout Sequence */
	/* LOPS */
	if ((res = CODEC_WRITE(AK4648REG_SIGNAL_SELECT_2,
				AK4648BIT_LOPS, AK4648BIT_LOPS)) < 0)
		goto out;
	/* PMLO PMDAC */
	if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_1,
				0x00, (AK4648BIT_PMLO | AK4648BIT_PMDAC))) < 0)
		goto out;
	schedule_timeout_uninterruptible(AK4648_WAIT_LO_LOCK);
	/* DACL */
	if ((res = CODEC_WRITE(AK4648REG_SIGNAL_SELECT_1,
				0x00, AK4648BIT_DACL)) < 0)
		goto out;
	/* LOPS */
	if ((res = CODEC_WRITE(AK4648REG_SIGNAL_SELECT_2,
				0x00, AK4648BIT_LOPS)) < 0)
		goto out;

	/* Stop of Clock Sequence */
	/* PMPLL */
	if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_2,
				0x00, AK4648BIT_PMPLL)) < 0)
		goto out;
	schedule_timeout_uninterruptible(AK4648_WAIT_PLL_LOCK);
	if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_1,
				0x00, AK4648BIT_PMVCM)) < 0)
		goto out;


	i2c_codec_cleanup();
	/* codec PDN pin "H" -> "L" */
	/* SNDPDN CODEC_Reset 5T735:GPIO9 IOOUT2 D1 */
	pwc_write(PWC_IOOUT2, 0x00, 0x02);
	/* PDN pin "L" needs 150ns */
	udelay(1);
#if 0
	/* 5T735:PSCNTGP5("REGGP5" -> "OFF") PSCNT2 D3 */
	pwc_write(PWC_PSCNT2, 0x00, 0x08);
	/* 5T735:VGP5SET(2.85V) VOUTSET3 D4 */
	pwc_write(PWC_VOUTSET3, 0x00, 0x10);
	udelay(500);
#endif
	/* CHG_CTRL_OSC : OSC_EN(D0) */
	outl(0x0, CHG_CTRL_OSC);

	codec->power_on--;
out:
	mutex_unlock(&codec->power_mutex);

	return res;
}

int  mp200_codec_reset(void)
{

	printk("DAC HANG UP mp200_codec_reset call \n");
        return 0;

}

static inline int mp200_codec_playback_volume(int* val)
{
	unsigned char reg;
	int res = 0;

	d7b("lch:%d rch:%d\n", *(val), *(val+1));

	if (*(val) > 100) {
		*(val) = 100;
	}
	if (*(val+1) > 100) {
		*(val+1) = 100;
	}

	reg = (AK4648_OUTPUT_VOL_MAX * (100 - *(val))) / 100;

	/* DVL */
	if ((res = CODEC_WRITE(AK4648REG_LCH_DIGITAL_VOLUME_CONTROL,
				reg, 0xff)) < 0)
		return res;

	reg = (AK4648_OUTPUT_VOL_MAX * (100 - *(val+1))) / 100;

	/* DVR */
	if ((res = CODEC_WRITE(AK4648REG_RCH_DIGITAL_VOLUME_CONTROL,
				reg, 0xff)) < 0)
		return res;

	return 0;
}

static inline int mp200_codec_capture_volume(int* val)
{
	unsigned char reg1,reg2;
	int res = 0;

	d7b("lch:%d rch:%d\n", *(val), *(val+1));

	if (*(val) > 100) {
		*(val) = 100;
	}
	if (*(val+1) > 100) {
		*(val+1) = 100;
	}

	reg1 = (AK4648_INPUT_VOL_MAX * *(val)) / 100;

	reg2 = (AK4648_INPUT_VOL_MAX * *(val+1)) / 100;

	/* IVL */
	if ((res = CODEC_WRITE(AK4648REG_LCH_INPUT_VOLUME_CONTROL,
				reg1, 0xff)) < 0)
		return res;

	/* IVR */
	if ((res = CODEC_WRITE(AK4648REG_RCH_INPUT_VOLUME_CONTROL,
				reg2, 0xff)) < 0)
		return res;

	if ((res = CODEC_WRITE(AK4648REG_ALC_MODE_CONTROL_1,
				0x00, AK4648BIT_ALC)) < 0)
		return res;

	if ((res = CODEC_WRITE(AK4648REG_ALC_MODE_CONTROL_2, reg1, 0xff)) < 0)
		return res;

	schedule_timeout_uninterruptible(AK4648_WAIT_ALC_LOCK);

	if ((res = CODEC_WRITE(AK4648REG_ALC_MODE_CONTROL_1,
				AK4648BIT_ALC, AK4648BIT_ALC)) < 0)
		return res;

	return 0;
}

static int mp200_codec_capture_source_sw(int* val)
{
	unsigned char inx = 0;
	unsigned char pmmp = 0;
	int res = 0;

	switch (val[0]) {
	case 0: /* MIC */
		inx = 0x00;
		pmmp = AK4648BIT_PMMP;
		break;
	case 1: /* Line */
		inx = AK4648BIT_INL0 | AK4648BIT_INR0;
		pmmp = AK4648BIT_PMMP;
		break;
	}

	/* INL INR */
	if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_3,
				inx, AK4648BIT_IN_MASK)) < 0)
		return res;

	/* PMMP */
	if ((res = CODEC_WRITE(AK4648REG_SIGNAL_SELECT_1,
				pmmp, AK4648BIT_PMMP)) < 0)
		return res;

	return 0;
}

static int mp200_codec_capture_channel_mode_sw(int* val)
{
	unsigned char pmadl = 0;
	unsigned char pmadr = 0;
	unsigned char mix = 0;
	int res = 0;

	switch (val[0]) {
	case 0: /* Stereo */
		pmadl = AK4648BIT_PMADL;
		pmadr = AK4648BIT_PMADR;
		mix = 0x00;
		break;
	case 1: /* Mono Lch */
		pmadl = AK4648BIT_PMADL;
		pmadr = 0x00;
		mix = 0x00;
		break;
	case 2: /* Mono Rch */
		pmadl = 0x00;
		pmadr = AK4648BIT_PMADR;
		mix = 0x00;
		break;
	case 3: /* MIX */
		pmadl = AK4648BIT_PMADL;
		pmadr = AK4648BIT_PMADR;
		mix = AK4648BIT_MIX;
		break;
	}

	/* PMADL */
	if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_1,
				pmadl, AK4648BIT_PMADL)) < 0)
		return res;
	/* PMADR */
	if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_3,
				pmadr, AK4648BIT_PMADR)) < 0)
		return res;
	/* MIX */
	if ((res = CODEC_WRITE(AK4648REG_MODE_CONTROL_5,
				mix, AK4648BIT_MIX)) < 0)
		return res;

	return 0;
}

static int mp200_codec_sampling_rate_sw(long val)
{
	unsigned char fs = 0;
	int res = 0;
	struct enum_info *info = &enum_info[IDX(MIXER_SW_SAMPLING_RATE)];

	if (val < 8000) {
		/* 7.35kHz */
		fs = AK4648BIT_FS_7_35KHZ;
		info->value = 0;
	} else if (val < 11025) {
		/* 8kHz */
		fs = AK4648BIT_FS_8KHZ;
		info->value = 1;
	} else if (val < 12000) {
		/* 11.025kHz */
		fs = AK4648BIT_FS_11_025KHZ;
		info->value = 2;
	} else if (val < 14700) {
		/* 12kHz */
		fs = AK4648BIT_FS_12KHZ;
		info->value = 3;
	} else if (val < 16000) {
		/* 14.7kHz */
		fs = AK4648BIT_FS_14_7KHZ;
		info->value = 4;
	} else if (val < 22050) {
		/* 16kHz */
		fs = AK4648BIT_FS_16KHZ;
		info->value = 5;
	} else if (val < 24000) {
		/* 22.05kHz */
		fs = AK4648BIT_FS_22_05KHZ;
		info->value = 6;
	} else if (val < 29400) {
		/* 24kHz */
		fs = AK4648BIT_FS_24KHZ;
		info->value = 7;
	} else if (val < 32000) {
		/* 29.4kHz */
		fs = AK4648BIT_FS_29_4KHZ;
		info->value = 8;
	} else if (val < 44100) {
		/* 32kHz */
		fs = AK4648BIT_FS_32KHZ;
		info->value = 9;
	} else if (val < 48000) {
		/* 44.1kHz */
		fs = AK4648BIT_FS_44_1KHZ;
		info->value = 10;
	} else {
		/* 48kHz */
		fs = AK4648BIT_FS_48KHZ;
		info->value = 11;
	}

	/* FS */
	res = CODEC_WRITE(AK4648REG_MODE_CONTROL_2, fs, AK4648BIT_FS_MASK);

	return res;
}

static int mp200_codec_playback_sw(int* val)
{
	int res = 0;

	switch (val[0]) {
	case 0: /* Line */
		/* Headphone power off */
		/* HPMTN */
		if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_2,
				0x00, AK4648BIT_HPMTN)) < 0)
			return res;
		schedule_timeout_uninterruptible(AK4648_WAIT_HPMTN_DIS_LOCK);
		/* PMHPL PMHPR */
		if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_2, 0x00,
				(AK4648BIT_PMHPL | AK4648BIT_PMHPR))) < 0)
			return res;
		/* PMDAC */
		if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_1,
				0x00, AK4648BIT_PMDAC)) < 0)
			return res;
		/* DACH */
		if ((res = CODEC_WRITE(AK4648REG_MODE_CONTROL_4,
				0x00, AK4648BIT_DACH)) < 0)
			return res;

		/* Stereo Lineout power on */
		/* DACL */
		if ((res = CODEC_WRITE(AK4648REG_SIGNAL_SELECT_1,
				AK4648BIT_DACL, AK4648BIT_DACL)) < 0)
			return res;
		/* LOPS */
		if ((res = CODEC_WRITE(AK4648REG_SIGNAL_SELECT_2,
				AK4648BIT_LOPS, AK4648BIT_LOPS)) < 0)
			return res;
		/* PMLO PMDAC */
		if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_1,
				(AK4648BIT_PMLO | AK4648BIT_PMDAC),
				(AK4648BIT_PMLO | AK4648BIT_PMDAC))) < 0)
			return res;
		schedule_timeout_uninterruptible(AK4648_WAIT_LO_LOCK);
		/* LOPS */
		if ((res = CODEC_WRITE(AK4648REG_SIGNAL_SELECT_2,
				0x00, AK4648BIT_LOPS)) < 0)
			return res;
		break;

	case 1: /* Headphone */
		/* Stereo Lineout power off */
		/* LOPS */
		if ((res = CODEC_WRITE(AK4648REG_SIGNAL_SELECT_2,
				AK4648BIT_LOPS, AK4648BIT_LOPS)) < 0)
			return res;
		/* PMLO PMDAC */
		if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_1,
				0x00, (AK4648BIT_PMLO | AK4648BIT_PMDAC))) < 0)
			return res;
		schedule_timeout_uninterruptible(AK4648_WAIT_LO_LOCK);
		/* LOPS */
		if ((res = CODEC_WRITE(AK4648REG_SIGNAL_SELECT_2,
				0x00, AK4648BIT_LOPS)) < 0)
			return res;

		/* Headphone power on */
		/* DACH */
		if ((res = CODEC_WRITE(AK4648REG_MODE_CONTROL_4,
				AK4648BIT_DACH, AK4648BIT_DACH)) < 0)
			return res;
		/* HPG vol -> 0dB(defult) */

		/* PMDAC */
		if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_1,
				AK4648BIT_PMDAC, AK4648BIT_PMDAC)) < 0)
			return res;
		/* PMHPL PMHPR */
		if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_2,
				(AK4648BIT_PMHPL | AK4648BIT_PMHPR),
				(AK4648BIT_PMHPL | AK4648BIT_PMHPR))) < 0)
			return res;
		/* HPMTN */
		if ((res = CODEC_WRITE(AK4648REG_POWER_MANAGEMENT_2,
				AK4648BIT_HPMTN, AK4648BIT_HPMTN)) < 0)
			return res;
		schedule_timeout_uninterruptible(AK4648_WAIT_HPMTN_EN_LOCK);
		break;
	}

	return 0;
}

static int mp200_codec_mixer_write(int addr, struct mp200_codec_mixer* codec)
{
	int res = 0;
	FNC_ENTRY

	switch (addr) {
	case MIXER_VOL_PLAYBACK:
		res = mp200_codec_playback_volume(
			&codec->vol_info[IDX(addr)].value[0]);
		break;
	case MIXER_VOL_CAPTURE:
		res = mp200_codec_capture_volume(
			&codec->vol_info[IDX(addr)].value[0]);
		break;
	case MIXER_SW_CAPTURE_SOURCE:
		res = mp200_codec_capture_source_sw(
			&codec->enum_info[IDX(addr)].value);
		break;
	case MIXER_SW_CAPTURE_CHANNEL_MODE:
		res = mp200_codec_capture_channel_mode_sw(
			&codec->enum_info[IDX(addr)].value);
		break;
	case MIXER_SW_SAMPLING_RATE:
	{
		long val = 0;

		switch (codec->enum_info[IDX(addr)].value) {
		case 0:  val = 7350;  break;
		case 1:  val = 8000;  break;
		case 2:  val = 11025; break;
		case 3:  val = 12000; break;
		case 4:  val = 14700; break;
		case 5:  val = 16000; break;
		case 6:  val = 22050; break;
		case 7:  val = 24000; break;
		case 8:  val = 29400; break;
		case 9:  val = 32000; break;
		case 10: val = 44100; break;
		case 11: val = 48000; break;
		}

		res = mp200_codec_sampling_rate_sw(val);

		break;
	}
	case MIXER_SW_PLAYBACK:
		res = mp200_codec_playback_sw(
			&codec->enum_info[IDX(addr)].value);
		break;
	case MIXER_SW_CODEC_POWER_BL:
		if(codec->bl_info[IDX(addr)].value) {
			res = codec_power_on();
		} else {
			res = codec_power_off();
		}
		break;
	}

	FNC_EXIT return res;
}

#define MP200_CODEC_INTEGER(xname, xindex, addr) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
  .info = mp200_codec_integer_info, .get = mp200_codec_integer_get, \
  .put = mp200_codec_integer_put, .private_value = addr }

static int mp200_codec_integer_info(struct snd_kcontrol * kcontrol, struct snd_ctl_elem_info * uinfo)
{
	struct mp200_codec_mixer *codec = snd_kcontrol_chip(kcontrol);
	int addr = kcontrol->private_value;
	FNC_ENTRY

	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = codec->vol_info[IDX(addr)].count;
	uinfo->value.integer.min  = codec->vol_info[IDX(addr)].val_int_min;
	uinfo->value.integer.max  = codec->vol_info[IDX(addr)].val_int_max;
	uinfo->value.integer.step = codec->vol_info[IDX(addr)].val_int_step;

	FNC_EXIT return 0;
}

static int mp200_codec_integer_get(struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol)
{
	struct mp200_codec_mixer *codec = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	int addr = kcontrol->private_value;
	int cnt = codec->vol_info[IDX(addr)].count;
	int i;
	FNC_ENTRY

	spin_lock_irqsave(&codec->mixer_lock, flags);
	for (i=0; i<cnt; i++) {
		ucontrol->value.integer.value[i]  =
			codec->vol_info[IDX(addr)].value[i];
	}
	spin_unlock_irqrestore(&codec->mixer_lock, flags);

	FNC_EXIT return 0;
}

static int mp200_codec_integer_put(struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol)
{
	struct mp200_codec_mixer *codec = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	int addr = kcontrol->private_value;
	int min  = codec->vol_info[IDX(addr)].val_int_min;
	int max  = codec->vol_info[IDX(addr)].val_int_max;
	int step = codec->vol_info[IDX(addr)].val_int_step;
	int i, change = 0;
	int cnt = codec->vol_info[IDX(addr)].count;
	int volume[2];
	int res = 0;
	FNC_ENTRY

	if (codec->power_on == 0) {
		FNC_EXIT return -EINVAL;
	}

	for (i=0; i<cnt; i++) {
		d8b("value.integer.value[%d]=%ld\n",
			i, ucontrol->value.integer.value[i]);
		volume[i] = (ucontrol->value.integer.value[i]/step)*step;
		if (volume[i] < min) volume[i] = min;
		if (volume[i] > max) volume[i] = max;
	}

	spin_lock_irqsave(&codec->mixer_lock, flags);
	for (i=0; i<cnt; i++) {
		change |= (codec->vol_info[IDX(addr)].value[i] != volume[i]);
		codec->vol_info[IDX(addr)].value[i] = volume[i];
		d8b("volume[%d]=%d\n", i, volume[i]);
	}
	spin_unlock_irqrestore(&codec->mixer_lock, flags);

	if (change) {
		if ((res = mp200_codec_mixer_write(addr, codec)) < 0) {
			FNC_EXIT return res;
		}
	}

	FNC_EXIT return change;
}

#define MP200_CODEC_ENUM(xname, xindex, addr) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
  .info = mp200_codec_enum_info, .get = mp200_codec_enum_get, \
  .put = mp200_codec_enum_put, .private_value = addr }

static int mp200_codec_enum_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info * uinfo)
{
	struct mp200_codec_mixer *codec = snd_kcontrol_chip(kcontrol);
	int addr = kcontrol->private_value;
	int items = codec->enum_info[IDX(addr)].items;
	FNC_ENTRY

	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
	uinfo->count = 1;
	uinfo->value.enumerated.items = items;
	if (uinfo->value.enumerated.item > (items-1))
		uinfo->value.enumerated.item = (items-1);
	strcpy(uinfo->value.enumerated.name,
		codec->enum_info[IDX(addr)].texts[uinfo->value.enumerated.item]);

	FNC_EXIT return 0;
}

static int mp200_codec_enum_get(struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol)
{
	struct mp200_codec_mixer *codec = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	int addr = kcontrol->private_value;
	FNC_ENTRY

	spin_lock_irqsave(&codec->mixer_lock, flags);
	ucontrol->value.enumerated.item[0] = codec->enum_info[IDX(addr)].value;
	spin_unlock_irqrestore(&codec->mixer_lock, flags);

	FNC_EXIT return 0;
}

static int mp200_codec_enum_put(struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol)
{
	struct mp200_codec_mixer *codec = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	int change, val;
	int addr = kcontrol->private_value;
	int items = codec->enum_info[IDX(addr)].items;
	int res = 0;
	FNC_ENTRY

	if (codec->power_on == 0) {
		FNC_EXIT return -EINVAL;
	}

	d8b("value.enumerated.item[0]=%d\n", ucontrol->value.enumerated.item[0]);
	if (ucontrol->value.enumerated.item[0] > (items-1))
		return -EINVAL;
	val = ucontrol->value.enumerated.item[0];

	spin_lock_irqsave(&codec->mixer_lock, flags);
	change = (codec->enum_info[IDX(addr)].value != val);
	codec->enum_info[IDX(addr)].value = val;
	d8b("val=%d\n", val);
	spin_unlock_irqrestore(&codec->mixer_lock, flags);

	if (change) {
		if ((res = mp200_codec_mixer_write(addr, codec)) < 0) {
			FNC_EXIT return res;
		}
	}

	FNC_EXIT return change;
}

#define MP200_CODEC_BOOLEAN(xname, xindex, addr) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
  .info = mp200_codec_boolean_info, .get = mp200_codec_boolean_get, \
  .put = mp200_codec_boolean_put, .private_value = addr }

static int mp200_codec_boolean_info(struct snd_kcontrol * kcontrol, struct snd_ctl_elem_info * uinfo)
{
	FNC_ENTRY

	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
	uinfo->count = 1;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = 1;

	FNC_EXIT return 0;
}

static int mp200_codec_boolean_get(struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol)
{
	struct mp200_codec_mixer *codec = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	int addr = kcontrol->private_value;
	FNC_ENTRY

	spin_lock_irqsave(&codec->mixer_lock, flags);
	ucontrol->value.integer.value[0] = codec->bl_info[IDX(addr)].value;
	spin_unlock_irqrestore(&codec->mixer_lock, flags);

	FNC_EXIT return 0;
}

static int mp200_codec_boolean_put(struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol)
{
	struct mp200_codec_mixer *codec = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	int change, addr = kcontrol->private_value;
	int val;
	int res = 0;
	FNC_ENTRY

	if ((codec->power_on == 0) && (addr != MIXER_SW_CODEC_POWER_BL)) {
		FNC_EXIT return -EINVAL;
	}

	d8b("value.integer.value[0]=%ld\n", ucontrol->value.integer.value[0]);
	val = ucontrol->value.integer.value[0] & 1;
	spin_lock_irqsave(&codec->mixer_lock, flags);
	change = (codec->bl_info[IDX(addr)].value != val);
	codec->bl_info[IDX(addr)].value = val;
	d8b("val=%d\n", val);
	spin_unlock_irqrestore(&codec->mixer_lock, flags);

	if (change) {
		if ((res = mp200_codec_mixer_write(addr, codec)) < 0) {
			FNC_EXIT return res;
		}
	}

	FNC_EXIT return change;
}

static struct snd_kcontrol_new mp200_codec_controls[] = {
	MP200_CODEC_INTEGER(
		"Playback Volume", 0, MIXER_VOL_PLAYBACK),
	MP200_CODEC_INTEGER(
		"Capture Volume", 0, MIXER_VOL_CAPTURE),
	MP200_CODEC_ENUM(
		"Capture Source Switch", 0, MIXER_SW_CAPTURE_SOURCE),
	MP200_CODEC_ENUM(
		"Capture Channel Mode Switch", 0, MIXER_SW_CAPTURE_CHANNEL_MODE),
	MP200_CODEC_ENUM(
		"Sampling Rate Switch", 0, MIXER_SW_SAMPLING_RATE),
	MP200_CODEC_ENUM(
		"Playback Switch", 0, MIXER_SW_PLAYBACK),
	MP200_CODEC_BOOLEAN(
		"CODEC Power Switch", 0, MIXER_SW_CODEC_POWER_BL),
};

static int __init mp200_codec_mixer_new(struct snd_card *card)
{
	unsigned int idx;
	int err;
	FNC_ENTRY

	snd_assert(card != NULL, return -EINVAL);
	spin_lock_init(&codec_mixer.mixer_lock);
	mutex_init(&codec_mixer.power_mutex);
	strcpy(card->mixername, "mp200 mixer");

	codec_mixer.card = card;

	for (idx = 0; idx < ARRAY_SIZE(mp200_codec_controls); idx++) {
		if ((err = snd_ctl_add(card, snd_ctl_new1(
			&mp200_codec_controls[idx], &codec_mixer))) < 0) {
			FNC_EXIT return err;
		}
	}

	return 0;
}


static struct snd_pcm *mp200_codec;

static unsigned int rates[] = {
	7350, 8000, 11025, 12000, 14700, 16000,
	22050, 24000, 29400, 32000, 44100, 48000,
};

static struct snd_pcm_hw_constraint_list hw_constraints_rates = {
	.count	= ARRAY_SIZE(rates),
	.list	= rates,
	.mask	= 0,
};

static int mp200_codec_startup(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct mp200_codec_mixer *codec = &codec_mixer;
	int err;
	FNC_ENTRY

	if (codec->power_on == 0) {
		FNC_EXIT return -EINVAL;
	}
#if defined(MP200_PCM_USE_PDMA) && defined(MP200_PCM_FMT16)
	runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE;	/* ilegal setting */
#else
	runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
#endif

	runtime->hw.rates = (SNDRV_PCM_RATE_8000 |
			     SNDRV_PCM_RATE_11025 |
			     SNDRV_PCM_RATE_16000 |
			     SNDRV_PCM_RATE_22050 |
			     SNDRV_PCM_RATE_32000 |
			     SNDRV_PCM_RATE_44100 |
			     SNDRV_PCM_RATE_48000 |
			     SNDRV_PCM_RATE_KNOT);
	runtime->hw.rate_min = 8000;
	runtime->hw.rate_max = 48000;
	runtime->hw.channels_min = 2;
	runtime->hw.channels_max = 2;

	if ((err = snd_pcm_hw_constraint_integer(
			runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0) {
		FNC_EXIT return err;
	}
	if ((err = snd_pcm_hw_constraint_list(
			runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
			&hw_constraints_rates)) < 0) {
		FNC_EXIT return err;
	}

	FNC_EXIT return 0;
}

static void mp200_codec_shutdown(struct snd_pcm_substream *substream)
{
	FNC_ENTRY
	FNC_EXIT return;
}

static int mp200_codec_prepare(struct snd_pcm_substream *substream)
{
	int ret = 0;
	struct snd_pcm_runtime *runtime = substream->runtime;
	FNC_ENTRY

	ret = mp200_codec_sampling_rate_sw(runtime->rate);

	FNC_EXIT return ret;
}

static struct audio_stream mp200_codec_out = {
	.id			= "voice out dma m2p lch9",
	.dma_ch			= MP200_DMAC_M2P_LCH9,
};

static struct audio_stream mp200_codec_in = {
	.id			= "voice in dma m2p lch9",
	.dma_ch			= MP200_DMAC_P2M_LCH9,
};

static struct mp200_pcm_client mp200_codec_client = {
	.pcm_ch			= MP200_PCM_CH0,
	.startup		= mp200_codec_startup,
	.shutdown		= mp200_codec_shutdown,
	.prepare		= mp200_codec_prepare,
};


/* codec sound card */

struct snd_card *mp200_codec_card;

static char *id = NULL;	/* ID for this card */
module_param(id, charp, 0444);

#ifdef CONFIG_PM
static int mp200_codec_suspend(struct platform_device *dev, pm_message_t state)
{
	struct snd_card *card = platform_get_drvdata(dev);
#if 0
	struct mp200_codec_mixer *codec = &codec_mixer;
#endif
	int ret = 0;

	switch (state.event) {
	case DEV_SUSPEND_IDLE_1:
	case PM_EVENT_SUSPEND:
#if 0
		if (codec->power_on != 0) {
			return -EBUSY;
		}
#endif
		break;
	default:
		break;
	}

	if (card)
		ret = mp200_pcm_suspend(dev, state);

	return ret;
}

static int mp200_codec_resume(struct platform_device *dev)
{
	struct snd_card *card = platform_get_drvdata(dev);
	int ret = 0;

	if (card)
		ret = mp200_pcm_resume(dev);

	return ret;
}
#else
#define mp200_codec_suspend	NULL
#define mp200_codec_resume	NULL
#endif

static int mp200_codec_probe(struct platform_device *devptr)
{
	struct snd_card *card = NULL;
	int ret;
	FNC_ENTRY

	codec_init();

	card = snd_card_new(SNDRV_DEFAULT_IDX1, id, THIS_MODULE, 0);
	if (card == NULL) {
		FNC_EXIT return -ENOMEM;
	}

	snd_card_set_dev(card, &devptr->dev);

	mp200_codec_card = card;

	mp200_codec_client.s[SNDRV_PCM_STREAM_PLAYBACK] = &mp200_codec_out;
	mp200_codec_client.s[SNDRV_PCM_STREAM_CAPTURE] = &mp200_codec_in;
	mp200_codec_client.sett = &pcm_sett;

	ret = mp200_pcm_new(card, &mp200_codec_client, &mp200_codec);
	if (ret)
		goto err;

	ret = mp200_pcm_set_ctrl(mp200_codec_client.pcm_regs, &pcm_sett);
	if (ret )
		goto err;

	ret = mp200_codec_mixer_new(card);
	if (ret)
		goto err;

	snprintf(card->shortname, sizeof(card->shortname),
		 "%s", "mp200-codec");
	snprintf(card->longname, sizeof(card->longname),
		 "%s (%s)", "sound codec", card->mixername);

	if ((ret = snd_card_register(card)) == 0) {
#ifdef AUDIO_MAKING_DEBUG
		printk(KERN_INFO "Starting sound codec. with debug : M%d\n",debug);
#else
		printk(KERN_INFO "Starting sound codec.\n");
#endif
		platform_set_drvdata(devptr, card);
		FNC_EXIT return 0;
	}

 err:
	if (card)
		snd_card_free(card);

	FNC_EXIT return ret;
}

static int mp200_codec_remove(struct platform_device *devptr)
{
	FNC_ENTRY
	mp200_pcm_free(&mp200_codec_client);
	snd_card_free(platform_get_drvdata(devptr));
	platform_set_drvdata(devptr, NULL);
	FNC_EXIT return 0;
}

#define MP200_PCM_DRIVER	"mp200 pcm"

static struct platform_device *device;
static struct platform_driver mp200_codec_driver = {
	.probe		= mp200_codec_probe,
	.remove		= __devexit_p(mp200_codec_remove),
#ifdef CONFIG_PM
	.suspend	= mp200_codec_suspend,
	.resume		= mp200_codec_resume,
#endif
	.driver		= {
		.name	= MP200_PCM_DRIVER,
	},
};

static int __init mp200_codec_init(void)
{
	int err;

	if ((err = platform_driver_register(&mp200_codec_driver)) < 0)
		return err;
	device = platform_device_register_simple(MP200_PCM_DRIVER, -1, NULL, 0);
	if (!IS_ERR(device)) {
		if (platform_get_drvdata(device))
			return 0;
		platform_device_unregister(device);
		err = -ENODEV;
	} else
		err = PTR_ERR(device);
	platform_driver_unregister(&mp200_codec_driver);
	return err;
}

static void __exit mp200_codec_exit(void)
{
	platform_device_unregister(device);
	platform_driver_unregister(&mp200_codec_driver);

}

#ifdef CODEC_TEST_DEBUG
EXPORT_SYMBOL(i2c_codec_read);
#endif /* CODEC_TEST_DEBUG */

#ifdef MP200_DAC_HANGUP_PDMA
EXPORT_SYMBOL(mp200_codec_reset);
#endif

module_init(mp200_codec_init);
module_exit(mp200_codec_exit);

MODULE_DESCRIPTION("sound codec driver for mp200 chip");
MODULE_LICENSE("GPL");

