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); }
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 tegra_audio_route(struct aic3008_priv *audio_data, int cmd, int idx) { switch (cmd) { case AIC3008_IO_CONFIG_RX: AUD_DBG("tegra_audio_route, AIC3008_IO_CONFIG_RX, idx = %d, call mode %d \n", idx, audio_data->is_call_mode); aic3008_setMode(cmd, idx, audio_data->is_call_mode); break; case AIC3008_IO_CONFIG_TX: AUD_DBG("tegra_audio_route, AIC3008_IO_CONFIG_TX, idx = %d, call mode %d \n", idx, audio_data->is_call_mode); aic3008_setMode(cmd, idx, audio_data->is_call_mode); break; case AIC3008_IO_CONFIG_MEDIA: AUD_DBG("tegra_audio_route, AIC3008_IO_CONFIG_MEDIA, idx = %d, call mode %d \n", idx, audio_data->is_call_mode); aic3008_setMode(cmd, idx, audio_data->is_call_mode); break; #if (defined(CONFIG_TEGRA_ES305)) case ES305_SET_CONFIG: AUD_DBG("tegra_audio_route, ES305_SET_CONFIG, idx = %d, call mode %d \n", idx, audio_data->is_call_mode); es305_set_config(idx, ES305_CONFIG_FULL); break; #endif } }
static int tegra_call_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); struct aic3008_priv *audio_data = snd_soc_codec_get_drvdata(codec); int is_call_mode_new; if (audio_data) { is_call_mode_new = ucontrol->value.integer.value[0]; if (is_call_mode_new == true) { AUD_DBG("in_call_mode.\n"); } else { #if (defined(CONFIG_TEGRA_ES305)) if (audio_data->is_call_mode != is_call_mode_new) { AUD_DBG("not_in_call_mode. put es305 into sleep mode\n"); es305_sleep(); audio_data->es305_cfg_id = -1; } #endif } audio_data->is_call_mode = is_call_mode_new; return 0; } return -EINVAL; }
void aic3008_CodecInit() { AUD_DBG("aic3008_CodecInit: start\n"); pwr_up_time = ktime_to_ms(ktime_get()); pr_device_power_on(); // aic3008 power up aic3008_power_ctl->powerinit(); // close mic aic3008_MicSwitch(0); spi_write_table_parsepage(POWER_UP_SEQ, ARRAY_SIZE(POWER_UP_SEQ)); AUD_DBG("***** SPI CMD: Power Up Seq *****\n"); AUD_DBG("Audio Codec Power Up takes %lld ms\n", ktime_to_ms(ktime_get()) - pwr_up_time); #if AUDIO_DEBUG_BEEP mdelay(100); spi_write_table_parsepage(GENERATE_BEEP_LEFT_SPK, ARRAY_SIZE(GENERATE_BEEP_LEFT_SPK)); AUD_DBG("***** SPI CMD: BEEP *****\n"); mdelay(300); spi_write_table_parsepage(CODEC_SW_RESET, ARRAY_SIZE(CODEC_SW_RESET)); #endif aic3008_rx_mode = DOWNLINK_PATH_OFF; aic3008_tx_mode = UPLINK_PATH_OFF; }
static int codec_spi_write(unsigned char addr, unsigned char data, bool flag) { unsigned char buffer[2]; int rc; if (!codec_spi_dev) return 0; if(addr == 0x00 && flag) { AUD_DBG("------ write page: 0x%02X ------\n", data); } else if(addr == 0x7F && flag) { AUD_DBG("------ write book: 0x%02X ------\n", data); } else if(flag) { AUD_DBG("write : reg: 0x%02X data: 0x%02X\n", addr, data); } codec_spi_dev->bits_per_word = 16; /* addr and data swapped around because 16bit word */ buffer[1] = addr << 1; buffer[0] = data; rc = spi_write(codec_spi_dev, buffer, 2); return rc; }
/* 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 __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; }
/* 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 __init aic3008_modinit(void) { AUD_DBG("Start aic3008_modinit\n"); drv_up_time = ktime_to_ms(ktime_get()); spi_aic3008_init(); return 0; }
static inline void spi_aic3008_exit(void) { AUD_DBG("exiting aic3008 spi driver...\n"); spi_unregister_driver(&aic3008_spi_driver); misc_deregister(&aic3008_misc); }
static int aic3008_suspend(struct snd_soc_codec *codec, pm_message_t state) { //AUD_DBG("call set_bias_level(SND_SOC_BIAS_OFF)\n"); //aic3008_set_bias_level(codec, SND_SOC_BIAS_OFF); AUD_DBG("aic3008_suspend\n"); return 0; }
static int codec_spi_read(unsigned char addr, unsigned char *data, bool flag) { int rc; u8 buffer[2] = { 0, 0 }; u8 result[2] = { 0, 0 }; codec_spi_dev->bits_per_word = 16; buffer[1] = addr << 1 | 1; /* high byte because 16bit word */ /*AUD_DBG("before read: buf[1]:0x%02X buf[0]:0x%02X res[1]:0x%02X res[0]:0x%02X \n", buffer[1], buffer[0], result[1], result[0]);*/ /* because aic3008 does symmetric SPI write and read */ rc = spi_write_then_read(codec_spi_dev, buffer, 2, result, 2); if (rc < 0) return rc; if(flag) { AUD_DBG("read: reg: 0x%02X , data: 0x%02X \n", addr, result[0]); } *data = result[0]; /* seems address on high byte, data on low byte */ return 0; }
static int aic3008_resume(struct snd_soc_codec *codec) { //AUD_DBG("call set_bias_level(SND_SOC_BIAS_STANDBY)\n"); //aic3008_set_bias_level(codec, SND_SOC_BIAS_STANDBY); AUD_DBG("aic3008_resume\n"); return 0; }
static int spi_aic3008_remove(struct spi_device *spi_aic3008) { AUD_DBG("Remove the SPI driver for aic3008.\n"); snd_soc_unregister_codec(&spi_aic3008->dev); kfree(spi_get_drvdata(spi_aic3008)); return 0; }
static void aic3008_i2s_control(int dsp_enum) { AUD_DBG("%s %d\n", __func__, dsp_enum); switch (dsp_enum) { case Phone_Default: case Phone_BT: case VOIP_BT: case VOIP_BT_HW_AEC: if (pcbid >= PROJECT_PHASE_XB || board_get_sku_tag() == 0x34600) { power_config("AUD_BT_SEL", TEGRA_GPIO_PK5, GPIO_OUTPUT); } break; case FM_Headset: case FM_Speaker: power_config("AUD_FM_SEL", TEGRA_GPIO_PK6, GPIO_OUTPUT); break; default: if (pcbid >= PROJECT_PHASE_XB || board_get_sku_tag() == 0x34600) { power_deconfig("AUD_BT_SEL", TEGRA_GPIO_PK5, GPIO_OUTPUT); } power_deconfig("AUD_FM_SEL", TEGRA_GPIO_PK6, GPIO_OUTPUT); break; } return; }
static int aic3008_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_codec *codec = rtd->codec; struct aic3008_priv *aic3008 = snd_soc_codec_get_drvdata(codec); struct spi_device *spi = codec->control_data; struct snd_pcm_runtime *master_runtime; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) aic3008->playback_active++; else aic3008->capture_active++; /* The DAI has shared clocks so if we already have a playback or * capture going then constrain this substream to match it. */ if (aic3008->master_substream) { master_runtime = aic3008->master_substream->runtime; /* dev_dbg(&spi->dev, "Constraining to %d bits\n", master_runtime->sample_bits); */ AUD_DBG("%p Constraining to %d bits\n", &spi->dev, master_runtime->sample_bits); snd_pcm_hw_constraint_minmax(substream->runtime, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, master_runtime->sample_bits, master_runtime->sample_bits); aic3008->slave_substream = substream; } else aic3008->master_substream = substream; return 0; }
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; }
/* This fn is very important!!!. We need to control the audio codec according to the different bias levels. i.e. to turn on/off essential audio components for different bias level */ static int aic3008_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { /* struct i2c_client *i2c = codec->control_data; */ /* u16 reg, reg2; */ switch (level) { case SND_SOC_BIAS_ON: case SND_SOC_BIAS_PREPARE: /* Preparing to playback */ /* FIXME: more actions here before playback or recording */ break; case SND_SOC_BIAS_STANDBY: /* when done playback / recording */ /* bias_level is set to SND_SOC_BIAS_OFF when powering down */ /* So will run this if-block only once when powering up */ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { /* first time powering up the audio codec */ /*AUD_DBG("First time STANDBY block\n");*/ /*aic3008_CodecInit();*/ } /* FIXME: should return to HEADPHONE if Headphone is plugged */ AUD_DBG("common STANDBY block\n"); break; case SND_SOC_BIAS_OFF: /* power down the audio codec */ /*AUD_DBG("SND_SOC_BIAS_OFF\n");*/ /* power down the audio codec */ /*aic3008_powerdown();*/ break; } codec->dapm.bias_level = level; return 0; }
int aic3008_setMode(int cmd, int idx, int is_call_mode) { int ret; AUD_DBG("setMode: cmd:%d, idx:%d, call_mode:%d\n", cmd, idx, is_call_mode); if (aic3008_power_ctl->mic_switch) { // set int/ext mic power off aic3008_MicSwitch(0); if (cmd == AIC3008_IO_CONFIG_TX && (idx == VOICERECORD_IMIC || idx == VOIP_DOWNLINK_IMIC_SPEAKER)) { AUD_DBG("[PWR] aic3008_setMode aic3008_MicSwitch"); aic3008_MicSwitch(1); } } ret = aic3008_set_config(cmd, idx, 1); return ret; }
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; }
static int aic3008_release(struct inode *inode, struct file *pfile) { mutex_lock(&lock); aic3008_opened = 0; mutex_unlock(&lock); AUD_DBG("IOCTL release\n"); return 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_DBG("[PWR] ***** regulator %s %d disable *****\n", name, pin); break; case GPIO_OUTPUT: gpio_set_value(pin, 0); AUD_DBG("[PWR] ***** gpio %s %d disable *****\n", name, pin); break; default: AUD_ERR("[PWR] ***** power_deconfig nothing *****\n"); } return; }
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_DBG("[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_DBG("[PWR] ***** gpio %s %d disable *****\n", name, pin); } return; }
static inline int spi_aic3008_init(void) { int ret = 0; AUD_DBG("starting aic3008 spi driver init...\n"); mutex_init(&lock); ret = spi_register_driver(&aic3008_spi_driver); AUD_DBG("DONE spi_register_driver(&aic3008_spi_driver).\n"); if (ret < 0) { AUD_ERR("failed to register spi driver(%d)\n", ret); return ret; } ret = misc_register(&aic3008_misc); AUD_DBG("DONE misc_register(&aic3008_misc).\n"); if (ret < 0) { AUD_ERR("failed to register misc device(%d)\n", ret); spi_unregister_driver(&aic3008_spi_driver); return ret; } return 0; }
static int tegra_aic3008_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_codec *codec = rtd->codec; int err = 0; AUD_DBG("tegra_codec_init().\n"); /* calls tegra_controls_init() in tegra_soc_controls.c * to set up playback, capture, mode, i2s loop back * routes controls. tegra_soc_controls handles the ALSA * IOCTL calls issued from ALSA HAL */ // Add controls err = snd_soc_add_controls(codec, tegra_controls, ARRAY_SIZE(tegra_controls)); if (err < 0) return err; aic3008_CodecInit(); AUD_DBG("DONE aic3008_CodecInit().\n"); return err; }
static int aic3008_open(struct inode *inode, struct file *pfile) { int ret = 0; AUD_DBG("IOCTL open\n"); mutex_lock(&lock); if (aic3008_opened) { AUD_ERR("busy\n"); ret = -EBUSY; } else aic3008_opened = 1; mutex_unlock(&lock); 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 int tegra_config_es305_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); struct aic3008_priv *audio_data = snd_soc_codec_get_drvdata(codec); int new_es305_cfg_id; if (audio_data) { new_es305_cfg_id = ucontrol->value.integer.value[0]; if (audio_data->es305_cfg_id != new_es305_cfg_id) { AUD_DBG("tegra_config_es305_put, %d", new_es305_cfg_id); tegra_audio_route(audio_data, ES305_SET_CONFIG, new_es305_cfg_id); audio_data->es305_cfg_id = new_es305_cfg_id; } return 0; } return -EINVAL; }
static int tegra_capture_route_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); struct aic3008_priv *audio_data = snd_soc_codec_get_drvdata(codec); int uplink; AUD_DBG("tegra_capture_route_put\n"); if (audio_data) { uplink = ucontrol->value.integer.value[0]; if (audio_data->uplink_id != uplink) audio_data->uplink_id = uplink; tegra_audio_route(audio_data, AIC3008_IO_CONFIG_TX, uplink); return 0; } return -EINVAL; }