void aic3254_set_mode(int config, int mode)
{
	mutex_lock(&lock);
	spi_aic3254_prevent_sleep();

	switch (config) {
	case AIC3254_CONFIG_TX:
		/* TX */
		pr_aud_info("%s: AIC3254_CONFIG_TX mode = %d\n",
			__func__, mode);
		aic3254_tx_config(mode);
		aic3254_tx_mode = mode;
		break;
	case AIC3254_CONFIG_RX:
		/* RX */
		pr_aud_info("%s: AIC3254_CONFIG_RX mode = %d\n",
			__func__, mode);
		aic3254_rx_config(mode);
		if (mode == FM_OUT_SPEAKER)
			aic3254_tx_config(FM_IN_SPEAKER);
		else if (mode == FM_OUT_HEADSET)
			aic3254_tx_config(FM_IN_HEADSET);
		aic3254_rx_mode = mode;
		break;
	}

	aic3254_powerdown();

	spi_aic3254_allow_sleep();
	mutex_unlock(&lock);
}
void aic3254_set_mode(int config, int mode)
{
	pr_aud_info("%s: aic3254_set_mode %d mode = %d\n", __func__, config, mode);
	mutex_lock(&lock);
	switch (config) {
	case AIC3254_CONFIG_TX:
		/* TX */
		aic3254_tx_config(mode);
		aic3254_tx_mode = mode;
		break;
	case AIC3254_CONFIG_RX:
		/* RX */
		aic3254_rx_config(mode);
		if (mode == FM_OUT_SPEAKER)
			aic3254_tx_config(FM_IN_SPEAKER);
		else if (mode == FM_OUT_HEADSET)
			aic3254_tx_config(FM_IN_HEADSET);
		else if (mode == DOWNLINK_OFF && (mode == FM_OUT_HEADSET
						|| mode == FM_OUT_HEADSET))
			aic3254_tx_config(POWER_OFF);
		aic3254_rx_mode = mode;
		break;
	}
	aic3254_powerdown();
	mutex_unlock(&lock);
}
void aic3254_force_powerdown(void)
{
	aic3254_rx_config(DOWNLINK_OFF);
	aic3254_rx_mode = DOWNLINK_OFF;
	aic3254_tx_config(UPLINK_OFF);
	aic3254_tx_mode = UPLINK_OFF;
	aic3254_powerdown();
}
static long aic3254_ioctl(struct file *file, unsigned int cmd,
	   unsigned long argc)
{
	struct AIC3254_PARAM para;
	void *table;
	int ret = 0, i = 0, mem_size, volume = 0;
	CODEC_SPI_CMD reg[2];
	unsigned char data;

	if (aic3254_uplink == NULL ||
		aic3254_downlink == NULL ||
		aic3254_minidsp == NULL) {
		pr_aud_err("%s: cmd 0x%x, invalid pointers\n", __func__, cmd);
		return -EFAULT;
	}

	switch (cmd) {
	case AIC3254_SET_TX_PARAM:
	case AIC3254_SET_RX_PARAM:
		if (copy_from_user(&para, (void *)argc, sizeof(para))) {
			pr_aud_err("%s: failed on copy_from_user\n", __func__);
			return -EFAULT;
		}

		pr_aud_info("%s: parameters(%d, %d, %p)\n", __func__,
				para.row_num, para.col_num, para.cmd_data);
		if (cmd == AIC3254_SET_TX_PARAM)
			table = aic3254_uplink[0];
		else
			table = aic3254_downlink[0];

		/* confirm indicated size doesn't exceed the allocated one */
		if (para.row_num > IO_CTL_ROW_MAX
				|| para.col_num != IO_CTL_COL_MAX) {
			pr_aud_err("%s: data size mismatch with allocated"
					" memory (%d,%d)\n", __func__,
					IO_CTL_ROW_MAX, IO_CTL_COL_MAX);
			return -EFAULT;
		}

		mem_size = para.row_num * para.col_num * sizeof(CODEC_SPI_CMD);
		if (copy_from_user(table, para.cmd_data, mem_size)) {
			pr_aud_err("%s: failed on copy_from_user\n", __func__);
			return -EFAULT;
		}

		/* invoking initialization procedure of AIC3254 */
		if (cmd == AIC3254_SET_TX_PARAM)
			aic3254_tx_config(INITIAL);

		pr_aud_info("%s: update table(%d,%d) successfully\n",
				__func__, para.row_num, para.col_num);
			break;
	case AIC3254_SET_DSP_PARAM:
		if (copy_from_user(&para, (void *)argc, sizeof(para))) {
			pr_aud_err("%s: failed on copy_from_user\n", __func__);
			return -EFAULT;
		}

		pr_aud_info("%s: parameters(%d, %d, %p)\n", __func__,
				para.row_num, para.col_num, para.cmd_data);

		table = aic3254_minidsp[0];

		/* confirm indicated size doesn't exceed the allocated one */
		if (para.row_num > MINIDSP_ROW_MAX
				|| para.col_num != MINIDSP_COL_MAX) {
			pr_aud_err("%s: data size mismatch with allocated"
					" memory (%d,%d)\n", __func__,
					MINIDSP_ROW_MAX, MINIDSP_COL_MAX);
			return -EFAULT;
			}

		mem_size = para.row_num * para.col_num * sizeof(CODEC_SPI_CMD);
		if (copy_from_user(table, para.cmd_data, mem_size)) {
			pr_aud_err("%s: failed on copy_from_user\n", __func__);
			return -EFAULT;
		}

		pr_aud_info("%s: update table(%d,%d) successfully\n",
				__func__, para.row_num, para.col_num);
		break;
	case AIC3254_CONFIG_TX:
	case AIC3254_CONFIG_RX:
	case AIC3254_CONFIG_MEDIA:
		if (copy_from_user(&i, (void *)argc, sizeof(int))) {
			pr_aud_err("%s: failed on copy_from_user\n", __func__);
			return -EFAULT;
		}
		ret = aic3254_set_config(cmd, i, 1);
		if (ret < 0)
			pr_aud_err("%s: configure(%d) error %d\n",
				__func__, i, ret);
		break;
	case AIC3254_CONFIG_VOLUME_L:
		if (copy_from_user(&volume, (void *)argc, sizeof(int))) {
			pr_aud_err("%s: failed on copy_from_user\n", __func__);
			return -EFAULT;
		}

		if (volume < -127 || volume > 48) {
			pr_aud_err("%s: volume out of range\n", __func__);
			return -EFAULT;
		}

		pr_aud_info("%s: AIC3254 config left volume %d\n",
				__func__, volume);

		CODEC_SET_VOLUME_L[1].data = volume;
		aic3254_config_ex(CODEC_SET_VOLUME_L, ARRAY_SIZE(CODEC_SET_VOLUME_L));
		break;
	case AIC3254_CONFIG_VOLUME_R:
		if (copy_from_user(&volume, (void *)argc, sizeof(int))) {
			pr_aud_err("%s: failed on copy_from_user\n", __func__);
			return -EFAULT;
		}

		if (volume < -127 || volume > 48) {
			pr_aud_err("%s: volume out of range\n", __func__);
			return -EFAULT;
		}

		pr_aud_info("%s: AIC3254 config right volume %d\n",
				__func__, volume);

		CODEC_SET_VOLUME_R[1].data = volume;
		aic3254_config_ex(CODEC_SET_VOLUME_R, ARRAY_SIZE(CODEC_SET_VOLUME_R));
		break;
	case AIC3254_DUMP_PAGES:
		if (copy_from_user(&i, (void *)argc, sizeof(int))) {
			pr_aud_err("%s: failed on copy_from_user\n", __func__);
			return -EFAULT;
		}
		if (i > AIC3254_MAX_PAGES) {
			pr_aud_err("%s: invalid page number %d\n", __func__, i);
			return -EINVAL;
		}

		pr_aud_info("========== %s: dump page %d ==========\n",
				__func__, i);
		/* indicated page number to AIC3254 */
		if (ctl_ops->rx_amp_enable)
			ctl_ops->rx_amp_enable(1);
		codec_spi_write(0x00, i);
		for (i = 0; i < AIC3254_MAX_REGS; i++) {
			ret = codec_spi_read(i, &data);
			if (ret < 0)
				pr_aud_err("read fail on register 0x%X\n", i);
			else
				pr_aud_info("(0x%02X, 0x%02X)\n", i, data);
		}
		if (ctl_ops->rx_amp_enable)
			ctl_ops->rx_amp_enable(0);
		pr_aud_info("=============================================\n");
		break;
	case AIC3254_WRITE_REG:
		if (copy_from_user(&reg, (void *)argc,
					sizeof(CODEC_SPI_CMD)*2)) {
			pr_aud_err("%s: failed on copy_from_user\n", __func__);
			return -EFAULT;
		}
		pr_aud_info("%s: command list (%c,%02X,%02X) (%c,%02X,%02X)\n",
				__func__, reg[0].act, reg[0].reg, reg[0].data,
				reg[1].act, reg[1].reg, reg[1].data);
		aic3254_config_ex(reg, 2);
		break;
	case AIC3254_READ_REG:
		if (copy_from_user(&reg, (void *)argc,
					sizeof(CODEC_SPI_CMD)*2)) {
			pr_aud_err("%s: failed on copy_from_user\n", __func__);
			return -EFAULT;
		}
		if (ctl_ops->spibus_enable)
			ctl_ops->spibus_enable(1);
		for (i = 0; i < 2; i++) {
			if (reg[i].act == 'r' || reg[i].act == 'R')
				codec_spi_read(reg[i].reg, &reg[i].data);
			else if (reg[i].act == 'w' || reg[i].act == 'W')
				codec_spi_write(reg[i].reg, reg[i].data);
			else
				return -EINVAL;
		}
		if (ctl_ops->spibus_enable)
			ctl_ops->spibus_enable(0);
		if (copy_to_user((void *)argc, &reg, sizeof(CODEC_SPI_CMD)*2)) {
			pr_aud_err("%s: failed on copy_to_user\n", __func__);
			return -EFAULT;
		}
		break;
	case AIC3254_POWERDOWN:
		mutex_lock(&lock);
		aic3254_powerdown();
		mutex_unlock(&lock);
		break;
	case AIC3254_LOOPBACK:
		if (copy_from_user(&i, (void *)argc, sizeof(int))) {
			pr_aud_err("%s: failed on copy_from_user\n", __func__);
			return -EFAULT;
		}
		pr_aud_info("%s: index %d for LOOPBACK\n", __func__, i);
		aic3254_loopback(i);
		break;
	default:
		pr_aud_err("%s: invalid command %d\n", __func__, _IOC_NR(cmd));
		ret = -EINVAL;
	}

	return ret;
}
static int aic3254_set_config(int config_tbl, int idx, int en)
{
	int rc = 0, len = 0;
	int64_t t1, t2;
#if defined(CONFIG_ARCH_MSM7X30)
	struct ecodec_aic3254_state *drv = &codec_clk;
#endif

	mutex_lock(&lock);
	spi_aic3254_prevent_sleep();

#if defined(CONFIG_ARCH_MSM7X30)
	if (drv->enabled == 0) {
		/* enable MI2S RX master block */
		/* enable MI2S RX bit clock */
		clk_enable(drv->rx_mclk);
		clk_enable(drv->rx_sclk);
		pr_aud_info("%s: enable CLK\n", __func__);
		drv->enabled = 1;
	}
#endif

	switch (config_tbl) {
	case AIC3254_CONFIG_TX:
		/* TX */
		pr_aud_info("%s: enable tx\n", __func__);
		if (en) {
			if (ctl_ops->tx_amp_enable)
				ctl_ops->tx_amp_enable(0);

			aic3254_tx_config(idx);
			aic3254_tx_mode = idx;

			if (ctl_ops->tx_amp_enable)
				ctl_ops->tx_amp_enable(1);
		} else {
			aic3254_tx_config(UPLINK_OFF);
			aic3254_tx_mode = UPLINK_OFF;
		}
		break;
	case AIC3254_CONFIG_RX:
		/* RX */
		pr_aud_info("%s: enable rx\n", __func__);
		if (en) {
			if (ctl_ops->rx_amp_enable)
				ctl_ops->rx_amp_enable(0);

			aic3254_rx_config(idx);
			aic3254_rx_mode = idx;

			if (ctl_ops->rx_amp_enable)
				ctl_ops->rx_amp_enable(1);
		} else {
			aic3254_rx_config(DOWNLINK_OFF);
			aic3254_rx_mode = DOWNLINK_OFF;
		}
		break;
	case AIC3254_CONFIG_MEDIA:
		if (aic3254_minidsp == NULL) {
			rc = -EFAULT;
			break;
		}

		len = (aic3254_minidsp[idx][0].reg << 8)
			| aic3254_minidsp[idx][0].data;

		pr_aud_info("%s: configure miniDSP index(%d) len = %d ++\n",
			__func__, idx, len);
		pr_aud_info("%s: rx mode %d, tx mode %d\n",
			__func__, aic3254_rx_mode, aic3254_tx_mode);

		t1 = ktime_to_ms(ktime_get());

		if (ctl_ops->rx_amp_enable)
			ctl_ops->rx_amp_enable(0);

		/* step 1: power off first */
		if (aic3254_rx_mode != DOWNLINK_OFF)
			aic3254_rx_config(DOWNLINK_OFF);

		/* step 2: config DSP */
		aic3254_config(&aic3254_minidsp[idx][1], len);

		/* step 3: switch back to original path */
		if (aic3254_rx_mode != DOWNLINK_OFF)
			aic3254_rx_config(aic3254_rx_mode);
		if (aic3254_tx_mode != UPLINK_OFF)
			aic3254_tx_config(aic3254_tx_mode);

		t2 = ktime_to_ms(ktime_get())-t1;

		if (ctl_ops->rx_amp_enable)
			ctl_ops->rx_amp_enable(1);

		pr_aud_info("%s: configure miniDSP index(%d) time: %lldms --\n",
			__func__, idx, (t2));
		break;
	}

	spi_aic3254_allow_sleep();
	mutex_unlock(&lock);
	return rc;
}
static int aic3254_set_config(int config_tbl, int idx, int en)
{
	int len;
	struct ecodec_aic3254_state *drv = &codec_clk;
	pr_aud_info("%s: table(0x%X) index(%d)\n", __func__, config_tbl, idx);

	wake_lock(&drv->idlelock);

#if defined(CONFIG_ARCH_MSM7X30)
	if (drv->enabled == 0) {
		/* enable MI2S RX master block */
		/* enable MI2S RX bit clock */
		clk_enable(drv->rx_mclk);
		clk_enable(drv->rx_sclk);
		printk("%s: enable CLK\n", __func__);
		drv->enabled = 1;
	}
#endif

	switch (config_tbl) {
	case AIC3254_CONFIG_TX:
		/* TX */
		pr_aud_info("%s: enable tx\n", __func__);
		if (en) {
			if (ctl_ops->tx_amp_enable)
				ctl_ops->tx_amp_enable(0);

			aic3254_tx_config(idx);
			aic3254_tx_mode = idx;

			if (ctl_ops->tx_amp_enable)
				ctl_ops->tx_amp_enable(1);
		} else {
			aic3254_tx_config(UPLINK_OFF);
			aic3254_tx_mode = UPLINK_OFF;
		}
		break;
	case AIC3254_CONFIG_RX:
		/* RX */
		pr_aud_info("%s: enable rx\n", __func__);
		if (en) {
			if (ctl_ops->rx_amp_enable)
				ctl_ops->rx_amp_enable(0);

			aic3254_rx_config(idx);
			aic3254_rx_mode = idx;

			if (ctl_ops->rx_amp_enable)
				ctl_ops->rx_amp_enable(1);
		} else {
			aic3254_rx_config(DOWNLINK_OFF);
			aic3254_rx_mode = DOWNLINK_OFF;
		}
		break;
	case AIC3254_CONFIG_MEDIA:
		if (aic3254_minidsp == NULL)
			return -EFAULT;
		len = (aic3254_minidsp[idx][0].reg << 8)
			| aic3254_minidsp[idx][0].data;

		pr_aud_info("%s: miniDSP command len = %d\n", __func__, len);
		pr_aud_info("%s: rx mode %d, tx mode %d\n",
			__func__, aic3254_rx_mode, aic3254_tx_mode);

		if (ctl_ops->rx_amp_enable)
			ctl_ops->rx_amp_enable(0);

		/* step 1: power off first */
		if (aic3254_rx_mode != DOWNLINK_OFF)
			aic3254_rx_config(DOWNLINK_OFF);

		/* step 2: config DSP */
		aic3254_config(&aic3254_minidsp[idx][1], len);

		/* step 3: switch back to original path */
		if (aic3254_rx_mode != DOWNLINK_OFF)
			aic3254_rx_config(aic3254_rx_mode);
		if (aic3254_tx_mode != UPLINK_OFF)
			aic3254_tx_config(aic3254_tx_mode);

		if (ctl_ops->rx_amp_enable)
			ctl_ops->rx_amp_enable(1);

		pr_aud_info("%s: configure minidsp done\n", __func__);
		break;
	}

	wake_unlock(&drv->idlelock);
	return 0;
}