static int __devinit aic3008_probe(struct snd_soc_codec *codec)
{
	AUD_INFO("aic3008_probe() start... aic3008_codec:%p", codec);
	int ret = 0;

	struct aic3008_priv *aic3008 = snd_soc_codec_get_drvdata(codec);
	aic3008->codec = codec;
	aic3008->codec->control_data = (void *)codec_spi_dev;

	if (!aic3008) {
		AUD_ERR("%s: Codec not registered, SPI device not yet probed\n",
				&aic3008->codec->name);
		return -ENODEV;
	}
	aic3008_sw_reset(codec); // local call to reset codec
	aic3008_set_bias_level(codec, SND_SOC_BIAS_STANDBY);

	// request space for SPI commands data of AIC3008
	aic3008_uplink = init_2d_array(IO_CTL_ROW_MAX, IO_CTL_COL_MAX);
	aic3008_downlink = init_2d_array(IO_CTL_ROW_MAX, IO_CTL_COL_MAX);
	aic3008_minidsp = init_2d_array(MINIDSP_ROW_MAX, MINIDSP_COL_MAX);
	bulk_tx = kcalloc(MINIDSP_COL_MAX * 2 , sizeof(uint8_t), GFP_KERNEL);

	AUD_INFO("Audio Codec Driver init complete in %lld ms\n",
			 ktime_to_ms(ktime_get()) - drv_up_time);
	return ret;
}
int route_tx_enable(int path, int en)
{
	AUD_DBG("[TX] (%d, %d) uses aic3008 default TX setting\n", path, en);
	if (en) {
		/* Uplink_Wakeup */
		AUD_INFO("[TX] route_tx_enable call Uplink_Wakeup");
		aic3008_config(CODEC_UPLINK_ON, ARRAY_SIZE(CODEC_UPLINK_ON));
		/* Path switching */
		switch (path) {
		case CALL_UPLINK_IMIC_RECEIVER:
		case CALL_UPLINK_IMIC_HEADPHONE:
		case CALL_UPLINK_IMIC_SPEAKER:
		case VOICERECORD_IMIC:
			/* By pass */
			AUD_INFO("[TX] route_tx_enable call MECHA_UPLINK_IMIC");
			aic3008_config(MECHA_UPLINK_IMIC, ARRAY_SIZE(MECHA_UPLINK_IMIC));
			break;
		case CALL_UPLINK_EMIC_HEADPHONE:
		case VOICERECORD_EMIC:
			AUD_INFO("[TX] route_tx_enable call UPLINK_EMIC,");
			aic3008_config(UPLINK_EMIC, ARRAY_SIZE(UPLINK_EMIC));
			break;
		}
	} else {
		/* Uplink_Off */
		AUD_INFO("[TX] route_tx_enable call CODEC_UPLINK_OFF");
		aic3008_config(CODEC_UPLINK_OFF, ARRAY_SIZE(CODEC_UPLINK_OFF));
	}
	return 0;
}
void power_config(const char *name, int pin, int method)
{
	int ret = 0;

	switch (method) {
	case REGULATOR_METHOD:
		audio_regulator = regulator_get(NULL, name);
		if (IS_ERR_OR_NULL(audio_regulator)) {
			AUD_ERR("[PWR] couldn't get regulator %s, pin = %d, addr = %p.\n", name, pin, &audio_regulator);
			return;
		}

		ret = regulator_is_enabled(audio_regulator);
		if (ret > 0) {
			AUD_DBG("[PWR] regulator %s was enabled, pin = %d.\n", name, pin);
			return;
		} else if (ret < 0) {
			AUD_ERR("[PWR] regulator_is_enable error.\n");
			return;
		}

		ret = regulator_enable(audio_regulator);
		if (ret < 0) {
			AUD_ERR("[PWR] couldn't enable regulator %s, pin = %d, ret = %d.\n", name, pin, ret);
		}

		AUD_INFO("[PWR] ***** regulator %s %d enable *****\n", name, pin);
		break;
	case GPIO_OUTPUT:
		gpio_direction_output(pin, 1);
		tegra_gpio_enable(pin);
		gpio_set_value(pin, 1);
		AUD_INFO("[PWR] ***** gpio %s %d output enable *****\n", name, pin);
		break;
	case GPIO_INPUT:
		gpio_direction_input(pin);
		tegra_gpio_enable(pin);
		AUD_INFO("[PWR] ***** gpio %s %d input enable *****\n", name, pin);
		break;
	case INIT_OUTPUT_LOW:
		gpio_request(pin, name);
		gpio_direction_output(pin, 0);
		break;
	case INIT_OUTPUT_HIGH:
		gpio_request(pin, name);
		gpio_direction_output(pin, 1);
		break;
	case INIT_INPUT:
		gpio_request(pin, name);
		gpio_direction_input(pin);
		break;
	default:
		AUD_ERR("[PWR] ***** power_configure nothing *****\n");
	}

	return;
}
Ejemplo n.º 4
0
static __devinit int tegra_aic3008_driver_probe(struct platform_device *pdev)
{
	struct snd_soc_card *card = &snd_soc_tegra_aic3008;
	struct tegra_aic3008 *machine;
	struct htc_asoc_platform_data *pdata;
	int ret;

	AUD_INFO("starting tegra_aic3008_driver_probe...\n");

	pdata = pdev->dev.platform_data;
	if (!pdata) {
		dev_err(&pdev->dev, "No platform data supplied\n");
		return -EINVAL;
	}
	AUD_INFO("starting tegra_aic3008_driver_probe...%p %p\n", &pdata->aic3008_power, &aic3008_power_ctl);
	aic3008_power_ctl = &pdata->aic3008_power;

	machine = kzalloc(sizeof(struct tegra_aic3008), GFP_KERNEL);
	if (!machine) {
		AUD_ERR("Can't allocate tegra_aic3008 struct\n");
		return -ENOMEM;
	}

	machine->pdata = pdata;

	ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev, card);
	util_data = &machine->util_data;
	if (ret)
		goto err_free_machine;
	AUD_DBG("DONE tegra_asoc_utils_init()\n");

	card->dev = &pdev->dev;
	platform_set_drvdata(pdev, card);
	snd_soc_card_set_drvdata(card, machine);

	ret = snd_soc_register_card(card);
	if (ret) {
		AUD_ERR("snd_soc_register_card failed %d\n",ret);
		goto err_unregister_switch;
	}
	AUD_DBG("DONE snd_soc_register_card()\n");

	return 0;

err_unregister_switch:

	tegra_asoc_utils_fini(&machine->util_data);
err_free_machine:
	kfree(machine);
	return ret;
}
/*
 * This function arranges the address and data bytes in a large command list
 * and use kernel SPI API,
 * i.e. use single spi_write() to write spi commands to the audio codec.
 */
static int32_t spi_read_list(CODEC_SPI_CMD *cmds, int num)
{
	int i;
	int rc;
	unsigned char page = 0;
	unsigned char buffer[2] = { 0, 0 };
	unsigned char result[2] = { 0, 0 };

	if (!codec_spi_dev)
		return 0;

	codec_spi_dev->bits_per_word = 16;
	for (i = 0; i < num; i++) {
		udelay(20);
		/* if writing page, then don't read its value */
		if(i==2)
		{
			AUD_INFO("Skip software reset cmd when dump spi! %02X\n", cmds[i].reg);
			continue;
		}
		if (cmds[i].reg == 0x00) {
			buffer[1] = cmds[i].reg << 1;
			buffer[0] = cmds[i].data;
			rc = spi_write(codec_spi_dev, buffer, sizeof(buffer));
			if (rc < 0)
			{
				return rc;
			}
			AUD_INFO("====== write page %02X ======", buffer[0]);
			page = buffer[0];
		} else if (cmds[i].reg == 0x7F && page == 0x00) {
			buffer[1] = cmds[i].reg << 1;
			buffer[0] = cmds[i].data;
			rc = spi_write(codec_spi_dev, buffer, sizeof(buffer));
			if (rc < 0)
			{
				return rc;
			}
			AUD_INFO("====== write book %02X ======", buffer[0]);
		} else {
			buffer[1] = cmds[i].reg << 1 | 1;
			rc = spi_write_then_read(codec_spi_dev, buffer, 2, result, 2);
			if (rc < 0)
				return rc;
			AUD_INFO("read: reg: 0x%02X , data: 0x%02X\n", cmds[i].reg, result[0]);
		}
	}
	return 0;
}
static void aic3008_rx_config(int mode)
{
	/* use default setting when rx table doesn't be updated*/
	if (aic3008_downlink == NULL) {
		AUD_DBG("[RX] use default setting since rx table doesn't be updated");
		if (mode == DOWNLINK_PATH_OFF)
			route_rx_enable(mode, 0);
		else
			route_rx_enable(mode, 1);
		return;
	}

	if (mode != DOWNLINK_PATH_OFF) {
		/* Downlink Wakeup */
		AUD_DBG("[RX] ----- downlink wakeup len(%d) -----\n",
				(aic3008_downlink[DOWNLINK_WAKEUP][0].data-1));
		aic3008_config(&aic3008_downlink[DOWNLINK_WAKEUP][1],
				aic3008_downlink[DOWNLINK_WAKEUP][0].data);
	}

	/* route rx device */
	AUD_INFO("[RX] ----- change i/o downlink RX %d len(%d) -----\n", mode,
			(aic3008_downlink[mode][0].data-1));

	aic3008_config(&aic3008_downlink[mode][1], aic3008_downlink[mode][0].data);
}
static void aic3008_tx_config(int mode)
{
	/* mode = 0 for initialisation */

	/* use default setting when tx table doesn't be updated*/
	if (aic3008_uplink == NULL) {
		AUD_DBG("[TX] use default setting since tx table doesn't be updated");
		if (mode == UPLINK_PATH_OFF)
			route_tx_enable(mode, 0); /* uploink off */
		else
			route_tx_enable(mode, 1); /* if no mem for aic3008_uplink + on */
		return;
	}

	/* if not uplink off or power off */
	if (mode != UPLINK_PATH_OFF && mode != POWER_OFF) {
		/* uplink_Wakeup */
		AUD_DBG("[TX] ----- uplink wakeup len(%d) -----\n",
				(aic3008_uplink[UPLINK_WAKEUP][0].data-1));

		aic3008_config(&aic3008_uplink[UPLINK_WAKEUP][1],
				aic3008_uplink[UPLINK_WAKEUP][0].data);
	}

	/* route tx device */
	AUD_INFO("[TX] ----- change i/o uplink TX %d len(%d) -----\n", mode,
			(aic3008_uplink[mode][0].data-1));

	aic3008_config(&aic3008_uplink[mode][1], aic3008_uplink[mode][0].data);
}
/* Access function pointed by ctl_ops to call control operations */
static int aic3008_config(CODEC_SPI_CMD *cmds, int size)
{
	int i, retry, ret;
	unsigned char data;
	if(!aic3008_power_ctl->isPowerOn)
	{
		AUD_INFO("aic3008_config: AIC3008 is power off now");
		return -EINVAL;
	}

	if (!codec_spi_dev) {
		AUD_ERR("no spi device\n");
		return -EFAULT;
	}

	if (cmds == NULL) {
		AUD_ERR("invalid spi parameters\n");
		return -EINVAL;
	}

	/* large dsp image use bulk mode to transfer */
	if (size < 1000) {
		for (i = 0; i < size; i++) {
			switch (cmds[i].act) {
			case 'w':
				codec_spi_write(cmds[i].reg, cmds[i].data, true);
				break;
			case 'r':
				for (retry = AIC3008_MAX_RETRY; retry > 0; retry--) {
					ret = codec_spi_read(cmds[i].reg, &data, true);
					if (ret < 0) {
						AUD_ERR("read fail %d, retry\n", ret);
						hr_msleep(1);
					} else if (data == cmds[i].data) {
						AUD_DBG("data == cmds\n");
						break;
					}
				}
				if (retry <= 0)
					AUD_DBG("3008 power down procedure,"
							" flag 0x%02X=0x%02X(0x%02X)\n",
							cmds[i].reg, ret, cmds[i].data);
				break;
			case 'd':
				msleep(cmds[i].data);
				break;
			default:
				break;
			}
		}
	} else {
		/* use bulk to transfer large data */
		spi_write_table_parsepage(cmds, size);
		AUD_DBG("Here is bulk mode\n");
	}
	return 0;
}
static void aic3008_powerup(void)
{
	int64_t t1, t2;

	t1 = ktime_to_ms(ktime_get());

	aic3008_power_ctl->resume();

	t2 = ktime_to_ms(ktime_get()) - t1;
	AUD_INFO("[PWR] power on AIC3008 %lldms\n", t2);

	return;
}
Ejemplo n.º 10
0
void power_deconfig(const char *name, int pin, int method)
{
	int ret = 0;

	switch (method) {
	case REGULATOR_METHOD:
		audio_regulator = regulator_get(NULL, name);
		if (IS_ERR_OR_NULL(audio_regulator)) {
			AUD_ERR("[PWR] couldn't get regulator %s %d, addr =  %p.\n", name, pin, &audio_regulator);
			return;
		}

		ret = regulator_is_enabled(audio_regulator);
		if (ret == 0)	{
			AUD_DBG("[PWR] regulator %s was disabled, pin = %d.\n", name, pin);
			return;
		} else if (ret < 0) {
			AUD_ERR("[PWR] regulator_is_enable error.\n");
			return;
		}

		ret = regulator_disable(audio_regulator);
		if (ret < 0) {
			AUD_ERR("[PWR] couldn't enable regulator %s %d, ret = %d.\n", name, pin, ret);
		}

		AUD_INFO("[PWR] ***** regulator %s %d disable *****\n", name, pin);
		break;
	case GPIO_OUTPUT:
		gpio_set_value(pin, 0);
		AUD_INFO("[PWR] ***** gpio %s %d disable *****\n", name, pin);
		break;
	default:
		AUD_ERR("[PWR] ***** power_deconfig nothing *****\n");
	}

	return;
}
Ejemplo n.º 11
0
void dock_config(const char *name, int pin, bool output, bool out_val)
{
	int ret = 0;

	if (output) {
		ret = gpio_direction_output(pin, out_val);
		if (ret < 0) {
			AUD_ERR("[PWR] set %s %d output direction failed\n", name, pin);
			return;
		}
		tegra_gpio_enable(pin);
		AUD_INFO("[PWR] ***** gpio %s %d enable *****\n", name, pin);
	} else {
		ret = gpio_direction_input(pin);
		if (ret < 0) {
			AUD_ERR("[PWR] set %s %d input direction failed\n", name, pin);
			return;
		}
		tegra_gpio_enable(pin);
		AUD_INFO("[PWR] ***** gpio %s %d disable *****\n", name, pin);
	}
	return;
}
void aic3008_set_mic_bias(int on)
{
	if (on) {
		AUD_INFO("[PWR] enalbe_AUD_HPMIC_BIAS\n");
		if (!aic3008_power_ctl->isPowerOn) {
			AUD_INFO("[PWR] codec was power off wakeup first\n");
			aic3008_powerup();
			aic3008_config(ENABLE_AUD_HPMIC_EXT, ARRAY_SIZE(ENABLE_AUD_HPMIC_EXT));
			aic3008_powerdown();
		} else aic3008_config(ENABLE_AUD_HPMIC_EXT, ARRAY_SIZE(ENABLE_AUD_HPMIC_EXT));
	} else {
		AUD_INFO("[PWR] disalbe_AUD_HPMIC_BIAS\n");
		if (aic3008_tx_mode == CALL_UPLINK_IMIC_SPEAKER ||
				aic3008_tx_mode == CALL_UPLINK_IMIC_SPEAKER_DUALMIC ||
				aic3008_tx_mode == VOIP_UPLINK_IMIC_SPEAKER ||
				aic3008_tx_mode == CALL_UPLINK_IMIC_SPEAKER_DUALMIC_WB
				)
		{
			if (!aic3008_power_ctl->isPowerOn) {
				AUD_INFO("[PWR] codec was power off wakeup first\n");
				aic3008_powerup();
				aic3008_config(DISABLE_AUD_HPMIC_EXT_ONLY, ARRAY_SIZE(DISABLE_AUD_HPMIC_EXT_ONLY));
				aic3008_powerdown();
			} else aic3008_config(DISABLE_AUD_HPMIC_EXT_ONLY, ARRAY_SIZE(DISABLE_AUD_HPMIC_EXT_ONLY));
		}
		else
		{
			if (!aic3008_power_ctl->isPowerOn) {
				AUD_INFO("[PWR] codec was power off wakeup first\n");
				aic3008_powerup();
				aic3008_config(DISABLE_AUD_HPMIC_EXT, ARRAY_SIZE(DISABLE_AUD_HPMIC_EXT));
				aic3008_powerdown();
			} else aic3008_config(DISABLE_AUD_HPMIC_EXT, ARRAY_SIZE(DISABLE_AUD_HPMIC_EXT));
		}
	}
}
int route_rx_enable(int path, int en)
{
	AUD_DBG("[RX] (%d, %d) uses AIC3008 default RX setting...\n", path, en);
	if (en) {
		/* Downlink_Wakeup */
		AUD_INFO("[RX] route_rx_enable call Downlink_Wakeup");
		aic3008_config(CODEC_DOWNLINK_ON, ARRAY_SIZE(CODEC_DOWNLINK_ON));
		/* Path switching */
		switch (path) {
		default:
			/* By pass */
			AUD_INFO("[RX] route_rx_enable call DOWNLINK_IMIC_RECEIVER");
			aic3008_config(DOWNLINK_IMIC_RECEIVER,
					ARRAY_SIZE(DOWNLINK_IMIC_RECEIVER));
			break;
		}
	} else {
		/* Downlink_Off */
		AUD_INFO("[RX] route_rx_enable call CODEC_DOWNLINK_OFF");
		aic3008_config(CODEC_DOWNLINK_OFF, ARRAY_SIZE(CODEC_DOWNLINK_OFF));
	}

	return 0;
}
/* write a register then read a register, compare them ! */
static int32_t spi_write_read_list(CODEC_SPI_CMD *cmds, int num)
{
	int i;
	int rc;
	unsigned char write_buffer[2];
	unsigned char read_result[2] = { 0, 0 };

	if (!codec_spi_dev)
		return 0;

	codec_spi_dev->bits_per_word = 16;
	for (i = 0; i < num; i++) {
		/*if writing page, then don't read its value */
		switch (cmds[i].act) {
		case 'w':
			write_buffer[1] = cmds[i].reg << 1;
			write_buffer[0] = cmds[i].data;
			if (cmds[i].reg == 0x00 || cmds[i].reg == 0x7F) {
				rc = spi_write(codec_spi_dev, write_buffer, sizeof(write_buffer));
				if (rc < 0)
					return rc;
				if(cmds[i].reg == 0x00)
				{
					AUD_DBG("------ write page: 0x%02X ------\n", cmds[i].data);
				}
				else if(cmds[i].reg == 0x7F)
				{
					AUD_DBG("------ write book: 0x%02X ------\n", cmds[i].data);
				}
			} else {
				rc = spi_write_then_read(codec_spi_dev, write_buffer, 2, read_result, 2);
				if (rc < 0)
					return rc;

				if (read_result[0] != cmds[i].data)
					AUD_INFO("incorrect value,reg 0x%02x, write 0x%02x, read 0x%02x",
						cmds[i].reg, cmds[i].data, read_result[0]);
			}
			break;
		case 'd':
			msleep(cmds[i].data);
			break;
		default:
			break;
		}
	}
	return 0;
}
static int spi_aic3008_probe(struct spi_device *spi_aic3008)
{
	AUD_DBG("spi device: %s, addr = 0x%p. YAY! ***** Start to Test *****\n",
		spi_aic3008->modalias, spi_aic3008);
	int ret = 0;
	codec_spi_dev = spi_aic3008; /* assign global pointer to SPI device. */

	struct aic3008_priv *aic3008 = kzalloc(sizeof(struct aic3008_priv), GFP_KERNEL);;
	if (aic3008 == NULL)
		return -ENOMEM;
	
	spi_set_drvdata(spi_aic3008, aic3008);

	ret = snd_soc_register_codec(&spi_aic3008->dev,
			&soc_codec_dev_aic3008, &aic3008_dai, 1);
	if (ret < 0)
	{
		AUD_ERR("snd_soc_register_codec() Failed\n");
		kfree(aic3008);
	}
	AUD_INFO("SPI control for AIC3008 started successfully...\n");

	return ret;
}
static void aic3008_powerdown(void)
{
	int64_t t1, t2;

	t1 = ktime_to_ms(ktime_get());

	if (aic3008_uplink != NULL) {
		AUD_DBG("[PWR] power off AIC3008 by table len(%d)\n",
				(aic3008_uplink[POWER_OFF][0].data-1));
		aic3008_config(&aic3008_uplink[POWER_OFF][1],
				aic3008_uplink[POWER_OFF][0].data);
	} else {
		AUD_DBG("[PWR] power off AIC3008 by default len(%d)\n",
				(ARRAY_SIZE(CODEC_POWER_OFF)));
		aic3008_config(CODEC_POWER_OFF, ARRAY_SIZE(CODEC_POWER_OFF));
	}

	aic3008_power_ctl->suspend();

	t2 = ktime_to_ms(ktime_get()) - t1;
	AUD_INFO("[PWR] power down AIC3008 %lldms\n", t2);

	return;
}
static long aic3008_ioctl(struct file *file, unsigned int cmd,
		unsigned long argc)
{
	struct AIC3008_PARAM para;
	void *table;
	int ret = 0, i = 0, mem_size, volume = 0;
	CODEC_SPI_CMD reg[4];
	unsigned char data;
	int len= 0; /* for dump dsp length. */
	int pcbid = 0;
	
	AUD_DBG("IOCTL command:0x%02X, argc:%ld\n", cmd, argc);

	if (aic3008_uplink == NULL || aic3008_downlink == NULL || aic3008_minidsp
			== NULL) {
		AUD_ERR("cmd 0x%x, invalid pointers\n", cmd);
		return -EFAULT;
	}

	switch (cmd) {
	/* first IO command from HAL */
	case AIC3008_IO_SET_TX_PARAM:
	/* second IO command from HAL */
	case AIC3008_IO_SET_RX_PARAM:
		if (copy_from_user(&para, (void *) argc, sizeof(para))) {
			AUD_ERR("failed on copy_from_user\n");
			return -EFAULT;
		}

		AUD_DBG("parameters(%d, %d, %p)\n",
				para.row_num, para.col_num, para.cmd_data);

		if (cmd == AIC3008_IO_SET_TX_PARAM)
			table = aic3008_uplink[0];
		else
			table = aic3008_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) {
			AUD_ERR("data size mismatch with allocated memory (%d,%d)\n",
					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)) {
			AUD_ERR("failed on copy_from_user\n");
			return -EFAULT;
		}

		/* invoking initialization procedure of AIC3008 */
		if (cmd == AIC3008_IO_SET_TX_PARAM)
			aic3008_tx_config(INITIAL);

		AUD_INFO("update RX/TX tables(%d, %d) successfully\n",
				para.row_num, para.col_num);
		break;

		/* third io command from HAL */
	case AIC3008_IO_SET_DSP_PARAM:
		if (copy_from_user(&para, (void *) argc, sizeof(para))) {
			AUD_ERR("failed on copy_from_user\n");
			return -EFAULT;
		}

		AUD_DBG("parameters(%d, %d, %p)\n",
				para.row_num, para.col_num, para.cmd_data);

		table = aic3008_minidsp[0];

		/* confirm indicated size doesn't exceed the allocated one */
		if (para.row_num > MINIDSP_ROW_MAX ||
				para.col_num != MINIDSP_COL_MAX) {
			AUD_ERR("data size mismatch with allocated memory (%d, %d)\n",
					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)) {
			AUD_ERR("failed on copy_from_user\n");
			return -EFAULT;
		}

		AUD_INFO("update dsp table(%d, %d) successfully\n",
				para.row_num, para.col_num);
		break;

		/* these IO commands are called to set path */
	case AIC3008_IO_CONFIG_TX:
	case AIC3008_IO_CONFIG_RX:
	case AIC3008_IO_CONFIG_MEDIA:
		if (copy_from_user(&i, (void *) argc, sizeof(int))) {
			AUD_ERR("failed on copy_from_user\n");
			return -EFAULT;
		}
		/* call aic3008_set_config() to issue SPI commands */
		ret = aic3008_set_config(cmd, i, 1);
		if (ret < 0)
			AUD_ERR("configure(%d) error %d\n", i, ret);
		break;

	case AIC3008_IO_CONFIG_VOLUME_L:
		if (copy_from_user(&volume, (void *) argc, sizeof(int))) {
			AUD_ERR("failed on copy_from_user\n");
			return -EFAULT;
		}

		if (volume < -127 || volume > 48) {
			AUD_ERR("volume out of range\n");
			return -EFAULT;
		}

		AUD_DBG("AIC3008 config left volume %d\n", volume);

		CODEC_SET_VOLUME_L[1].data = volume;

		/* call extended aic3008_config_ex() to set up volume */
		aic3008_config_ex(CODEC_SET_VOLUME_L, ARRAY_SIZE(CODEC_SET_VOLUME_L));
		break;

	case AIC3008_IO_CONFIG_VOLUME_R:
		if (copy_from_user(&volume, (void *) argc, sizeof(int))) {
			AUD_ERR("failed on copy_from_user\n");
			return -EFAULT;
		}

		if (volume < -127 || volume > 48) {
			AUD_ERR("volume out of range\n");
			return -EFAULT;
		}

		AUD_DBG("AIC3008 config right volume %d\n", volume);

		CODEC_SET_VOLUME_R[1].data = volume;

		/* call extended aic3008_config_ex() to set up volume */
		aic3008_config_ex(CODEC_SET_VOLUME_R, ARRAY_SIZE(CODEC_SET_VOLUME_R));
		break;

		/* dump specific audio codec page */
	case AIC3008_IO_DUMP_PAGES:
		if (copy_from_user(&i, (void *) argc, sizeof(int))) {
			AUD_ERR("failed on copy_from_user\n");
			return -EFAULT;
		}
		if (i > AIC3008_MAX_PAGES) {
			AUD_ERR("invalid page number %d\n", i);
			return -EINVAL;
		}

		AUD_DBG("========== dump page %d ==========\n", i);
		/* indicated page number of AIC3008 */
		codec_spi_write(0x00, i, true);
		for (i = 0; i < AIC3008_MAX_REGS; i++) {
			ret = codec_spi_read(i, &data, true);
			if (ret < 0) {
				AUD_ERR("read fail on register 0x%X\n", i);
				0;
			} else {
				AUD_DBG("(addr:0x%02X, data:0x%02X)\n", i, data);
				0;
			}
		}
		AUD_DBG("=============================================\n");
		break;
	case AIC3008_IO_DUMP_DSP:
		AUD_DBG("========== dump dsp %d ==========\n", aic3008_dsp_mode);

		/* indicated dsp number of AIC3008 */
		len = (aic3008_minidsp[aic3008_dsp_mode][0].reg << 8) | aic3008_minidsp[aic3008_dsp_mode][0].data;
		AUD_DBG("len = %d", len);
		spi_read_list(&aic3008_minidsp[aic3008_dsp_mode][1], len);
		AUD_DBG("=============================================\n");
		break;

		/* write specific audio codec register */
	case AIC3008_IO_WRITE_REG:
		AUD_INFO("========== WRITE_REG ==========\n");
		if (copy_from_user(&reg, (void *) argc, sizeof(CODEC_SPI_CMD) * 4)) {
			AUD_ERR("failed on copy_from_user\n");
			return -EFAULT;
		}
		AUD_DBG("command list (%c,%02X,%02X) (%c,%02X,%02X) (%c,%02X,%02X) (%c,%02X,%02X)\n",
				reg[0].act, reg[0].reg, reg[0].data,
				reg[1].act, reg[1].reg, reg[1].data,
				reg[2].act, reg[2].reg, reg[2].data,
				reg[3].act, reg[3].reg, reg[3].data);
//		aic3008_config_ex(reg, 4);
		for (i = 0; i < 4; i++) {
			if (reg[i].act == 'r' || reg[i].act == 'R')
				codec_spi_read(reg[i].reg, &reg[i].data, true);
			else if (reg[i].act == 'w' || reg[i].act == 'W')
				codec_spi_write(reg[i].reg, reg[i].data, true);
			else
				return -EINVAL;
		}
		AUD_INFO("========== WRITE_REG end ==========\n");
		break;

		/* read specific audio codec register */
	case AIC3008_IO_READ_REG:
		AUD_INFO("========== READ_REG ==========\n");
		if (copy_from_user(&reg, (void *) argc, sizeof(CODEC_SPI_CMD) * 4)) {
			AUD_ERR("failed on copy_from_user\n");
			return -EFAULT;
		}
		for (i = 0; i < 4; i++) {
			if (reg[i].act == 'r' || reg[i].act == 'R')
				codec_spi_read(reg[i].reg, &reg[i].data, true);
			else if (reg[i].act == 'w' || reg[i].act == 'W')
				codec_spi_write(reg[i].reg, reg[i].data, true);
			else
				return -EINVAL;
		}
		if (copy_to_user((void *) argc, &reg, sizeof(CODEC_SPI_CMD) * 2)) {
			AUD_ERR("failed on copy_to_user\n");
			return -EFAULT;
		}
		AUD_INFO("========== READ_REG end==========\n");
		break;

	case AIC3008_IO_POWERDOWN: /* power down IO command */
		mutex_lock(&lock);
		aic3008_powerdown();
		mutex_unlock(&lock);
		break;

	case AIC3008_IO_LOOPBACK: /* loopback IO command */
		if (copy_from_user(&i, (void *) argc, sizeof(int))) {
			AUD_ERR("failed on copy_from_user\n");
			return -EFAULT;
		}
		AUD_DBG("index %d for LOOPBACK\n", i);

		/* set up the loopback with specific id */
		aic3008_set_loopback(i);
		break;

	case AIC3008_IO_GET_PCBID: /* get pcbid */
		pcbid = htc_get_pcbid_info();
		if (copy_to_user((void *) argc, &pcbid, sizeof(int))) {
			AUD_ERR("failed on copy_to_user\n");
			return -EFAULT;
		}
		break;

	default:
		AUD_ERR("invalid command %d\n", _IOC_NR(cmd));
		ret = -EINVAL;
	}

	return ret;
}
static int aic3008_set_config(int config_tbl, int idx, int en)
{
	int rc = 0, len = 0;
	int64_t t1, t2;

	mutex_lock(&lock);
/*	spi_aic3008_prevent_sleep(); */

	switch (config_tbl) {
	case AIC3008_IO_CONFIG_TX:
		/* TX */
		aic3008_AmpSwitch(aic3008_tx_mode, 0);
		if(!aic3008_power_ctl->isPowerOn)
		{
			AUD_ERR("[TX] AIC3008 is power off now, can't do IO CONFIG TX = %d, please check this condition!!", idx);
			AUD_ERR("[TX] Since IO CONFIG TX = %d can't be done, it maybe no sound on device");
			break;
		}
		if (en) {
			AUD_INFO("[TX] AIC3008_IO_CONFIG_TX: UPLINK idx = %d",idx);
			aic3008_tx_config(idx);
			aic3008_tx_mode = idx;
			aic3008_AmpSwitch(idx, 1);
		} else {
			AUD_INFO("[TX] AIC3008_IO_CONFIG_TX: UPLINK_PATH_OFF");
			aic3008_tx_config(UPLINK_PATH_OFF);
			aic3008_tx_mode = UPLINK_PATH_OFF;
		}

		if ((aic3008_tx_mode == UPLINK_PATH_OFF) && (aic3008_rx_mode == DOWNLINK_PATH_OFF))
		{
			AUD_INFO("[TX] AIC3008_IO_CONFIG_TX: PATH OFF Call aic3008_powerdown()");
			aic3008_powerdown();
		}
		break;
	case AIC3008_IO_CONFIG_RX:
		/* RX */
		aic3008_AmpSwitch(aic3008_rx_mode, 0);
		if(!aic3008_power_ctl->isPowerOn)
		{
			AUD_ERR("[RX] AIC3008 is power off now, can't do IO CONFIG RX = %d, please check this condition!!", idx);
			AUD_ERR("[RX] Since IO CONFIG RX = %d can't be done, it maybe no sound on device");
			break;
		}
		if(!first_boot_path && idx == 10)
		{
			AUD_INFO("[RX] AIC3008_IO_CONFIG_RX: first_boot_path = 10\n");
			idx = DOWNLINK_PATH_OFF;
			aic3008_rx_mode = DOWNLINK_PATH_OFF;
			first_boot_path = true;
		}
		if (en) {
			AUD_INFO("[RX] AIC3008_IO_CONFIG_RX: DOWNLINK idx = %d",idx);
			aic3008_rx_config(idx);
			aic3008_rx_mode = idx;
			aic3008_AmpSwitch(idx, 1);
		} else {
			AUD_INFO("[RX] AIC3008_IO_CONFIG_RX: DOWNLINK_PATH_OFF");
			aic3008_rx_config(DOWNLINK_PATH_OFF);
			aic3008_rx_mode = DOWNLINK_PATH_OFF;
		}
		if ((aic3008_tx_mode == UPLINK_PATH_OFF) && (aic3008_rx_mode == DOWNLINK_PATH_OFF))
		{
			AUD_INFO("[RX] AIC3008_IO_CONFIG_RX: PATH OFF Call aic3008_powerdown()");
			aic3008_powerdown();
		}
		break;
	case AIC3008_IO_CONFIG_MEDIA:
		if(idx == 20 && !aic3008_power_ctl->isPowerOn)
		{
			aic3008_powerup();
			break;
		}
		else if(idx == 49)
		{
			AUD_DBG("[DSP] idx = %d, Mic Mute!!", idx);
			if (aic3008_tx_mode == VOIP_UPLINK_BT ||
					aic3008_tx_mode == UPLINK_BT_AP ||
					aic3008_tx_mode == UPLINK_BT_BB ){
				aic3008_config(BT_MIC_MUTE, ARRAY_SIZE(BT_MIC_MUTE));		// mute mic
			}
			else{
				aic3008_config(ADC_MUTE, ARRAY_SIZE(ADC_MUTE));		// mute mic
			}
			break;
		}
		else if(idx == 50)
		{
			AUD_DBG("[DSP] idx = %d, Mic unMute!!", idx);
			if (aic3008_tx_mode == VOIP_UPLINK_BT ||
					aic3008_tx_mode == UPLINK_BT_AP ||
					aic3008_tx_mode == UPLINK_BT_BB ){
				aic3008_config(BT_MIC_UNMUTE, ARRAY_SIZE(BT_MIC_UNMUTE));		// mute mic
			}
			else{
				aic3008_config(ADC_UNMUTE, ARRAY_SIZE(ADC_UNMUTE));		// mute mic
			}
			break;
		}
		else if(idx == 51)
		{
			AUD_DBG("[DSP] idx = %d, Output Mute!!", idx);
			aic3008_config(DAC_MUTE, ARRAY_SIZE(DAC_MUTE));		// mute output
			break;
		}
		else if(idx == 52)
		{
			AUD_DBG("[DSP] idx = %d, Output unMute!!", idx);
			aic3008_config(DAC_UNMUTE, ARRAY_SIZE(DAC_UNMUTE));	// unmute output
			break;
		}
		else if(idx == 53)
		{
			AUD_INFO("[DSP] idx = %d, BEATS_ON!!", idx);
			aic3008_config(BEATS_ON, ARRAY_SIZE(BEATS_ON));	// Increase the gain for BEATS_EFFECT_ON
			break;
		}
		else if(idx == 54)
		{
			AUD_INFO("[DSP] idx = %d, BEATS_OFF!!", idx);
			aic3008_config(BEATS_OFF, ARRAY_SIZE(BEATS_OFF)); // Decrease the gain for BEATS_EFFECT_OFF
			break;
		}
		if(!aic3008_power_ctl->isPowerOn)
		{
//			AUD_ERR("[DSP] AIC3008 is power off now, do you want change DSP = %d??", idx);
//			AUD_ERR("[DSP] If DSP %d must to be done, please make sure I/O config won't be forgot!!", idx);
			aic3008_powerup();
			AUD_INFO("[DSP] Recovery this condition, AIC3008 is power up now and DSP %d will be config", idx);
		}
		if (aic3008_minidsp == NULL) {
			AUD_INFO("[DSP] AIC3008_IO_CONFIG_MEDIA: aic3008_minidsp == NULL");
			rc = -EFAULT;
			break;
		}
		/* we use this value to dump dsp. */
		aic3008_dsp_mode = idx;

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

		AUD_INFO("[DSP] AIC3008_IO_CONFIG_MEDIA: Original RX %d, TX %d. start DSP = %d, len = %d ++. ",
				aic3008_rx_mode, aic3008_tx_mode, idx, len);

		t1 = ktime_to_ms(ktime_get());
		/* step 1: path off first */
		if (aic3008_rx_mode != DOWNLINK_PATH_OFF)
			aic3008_rx_config(DOWNLINK_PATH_OFF);

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

		t2 = ktime_to_ms(ktime_get()) - t1;

		AUD_INFO("[DSP] AIC3008_IO_CONFIG_MEDIA: configure miniDSP index(%d) time: %lldms --\n", idx, (t2));
		break;
	}

/*	spi_aic3008_allow_sleep(); */
	mutex_unlock(&lock);
	return rc;
}