* Author: Graeme Gregory
* graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
*
+ * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
#include <asm/mach-types.h>
+#include <linux/clk.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/regulator/consumer.h>
+#ifdef CONFIG_SWITCH
+#include <linux/switch.h>
+#endif
-#include <mach/tegra_wm8753_pdata.h>
+#include <mach/tegra_asoc_pdata.h>
#include <sound/core.h>
#include <sound/jack.h>
#define GPIO_INT_MIC_EN BIT(2)
#define GPIO_EXT_MIC_EN BIT(3)
+extern int g_is_call_mode;
+
struct tegra_wm8753 {
struct tegra_asoc_utils_data util_data;
- struct tegra_wm8753_platform_data *pdata;
+ struct tegra_asoc_platform_data *pdata;
struct regulator *audio_reg;
int gpio_requested;
+ int is_call_mode;
+ int is_call_mode_bt;
};
static int tegra_wm8753_hw_params(struct snd_pcm_substream *substream,
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_card *card = codec->card;
struct tegra_wm8753 *machine = snd_soc_card_get_drvdata(card);
+ struct tegra_asoc_platform_data *pdata = machine->pdata;
int srate, mclk, i2s_daifmt;
- int err;
+ int err, rate;
+
srate = params_rate(params);
switch (srate) {
case 8000:
tegra_asoc_utils_lock_clk_rate(&machine->util_data, 1);
- i2s_daifmt = SND_SOC_DAIFMT_NB_NF |
- SND_SOC_DAIFMT_CBS_CFS;
+ rate = clk_get_rate(machine->util_data.clk_cdev1);
+
+ i2s_daifmt = SND_SOC_DAIFMT_NB_NF;
+ i2s_daifmt |= pdata->i2s_param[HIFI_CODEC].is_i2s_master ?
+ SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBM_CFM;
/* Use DSP mode for mono on Tegra20 */
- if ((params_channels(params) != 2) && machine_is_whistler())
+ if ((params_channels(params) != 2) && machine_is_whistler()) {
i2s_daifmt |= SND_SOC_DAIFMT_DSP_A;
- else
- i2s_daifmt |= SND_SOC_DAIFMT_I2S;
+ } else {
+ switch (pdata->i2s_param[HIFI_CODEC].i2s_mode) {
+ case TEGRA_DAIFMT_I2S :
+ i2s_daifmt |= SND_SOC_DAIFMT_I2S;
+ break;
+ case TEGRA_DAIFMT_DSP_A :
+ i2s_daifmt |= SND_SOC_DAIFMT_DSP_A;
+ break;
+ case TEGRA_DAIFMT_DSP_B :
+ i2s_daifmt |= SND_SOC_DAIFMT_DSP_B;
+ break;
+ case TEGRA_DAIFMT_LEFT_J :
+ i2s_daifmt |= SND_SOC_DAIFMT_LEFT_J;
+ break;
+ case TEGRA_DAIFMT_RIGHT_J :
+ i2s_daifmt |= SND_SOC_DAIFMT_RIGHT_J;
+ break;
+ default :
+ dev_err(card->dev,
+ "Can't configure i2s format\n");
+ return -EINVAL;
+ }
+ }
err = snd_soc_dai_set_fmt(codec_dai, i2s_daifmt);
if (err < 0) {
return err;
}
- err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk,
- SND_SOC_CLOCK_IN);
+ err = snd_soc_dai_set_sysclk(codec_dai, 0, rate, SND_SOC_CLOCK_IN);
if (err < 0) {
dev_err(card->dev, "codec_dai clock not set\n");
return err;
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_card *card = codec->card;
struct tegra_wm8753 *machine = snd_soc_card_get_drvdata(card);
- int srate, mclk, min_mclk;
+ struct tegra_asoc_platform_data *pdata = machine->pdata;
+ int srate, mclk, min_mclk, i2s_daifmt;
int err;
srate = params_rate(params);
tegra_asoc_utils_lock_clk_rate(&machine->util_data, 1);
- err = snd_soc_dai_set_fmt(cpu_dai,
- SND_SOC_DAIFMT_DSP_A |
- SND_SOC_DAIFMT_NB_NF |
- SND_SOC_DAIFMT_CBS_CFS);
+ i2s_daifmt = SND_SOC_DAIFMT_NB_NF;
+ i2s_daifmt |= pdata->i2s_param[BT_SCO].is_i2s_master ?
+ SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBM_CFM;
+
+ switch (pdata->i2s_param[BT_SCO].i2s_mode) {
+ case TEGRA_DAIFMT_I2S :
+ i2s_daifmt |= SND_SOC_DAIFMT_I2S;
+ break;
+ case TEGRA_DAIFMT_DSP_A :
+ i2s_daifmt |= SND_SOC_DAIFMT_DSP_A;
+ break;
+ case TEGRA_DAIFMT_DSP_B :
+ i2s_daifmt |= SND_SOC_DAIFMT_DSP_B;
+ break;
+ case TEGRA_DAIFMT_LEFT_J :
+ i2s_daifmt |= SND_SOC_DAIFMT_LEFT_J;
+ break;
+ case TEGRA_DAIFMT_RIGHT_J :
+ i2s_daifmt |= SND_SOC_DAIFMT_RIGHT_J;
+ break;
+ default :
+ dev_err(card->dev, "Can't configure i2s format\n");
+ return -EINVAL;
+ }
+
+ err = snd_soc_dai_set_fmt(cpu_dai, i2s_daifmt);
if (err < 0) {
dev_err(card->dev, "cpu_dai fmt not set\n");
return err;
return 0;
}
+static int tegra_wm8753_voice_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_card *card = codec->card;
+ struct tegra_wm8753 *machine = snd_soc_card_get_drvdata(card);
+ int srate, mclk, i2s_daifmt, sys_clk;
+ int err, pcmdiv, vxclkdiv;
+
+ srate = params_rate(params);
+ switch (srate) {
+ case 8000:
+ case 16000:
+ case 24000:
+ case 32000:
+ case 48000:
+ case 64000:
+ case 96000:
+ mclk = 12288000;
+ break;
+ case 11025:
+ case 22050:
+ case 44100:
+ case 88200:
+ mclk = 11289600;
+ break;
+ default:
+ mclk = 12000000;
+ break;
+ }
+
+ err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk);
+ if (err < 0) {
+ if (!(machine->util_data.set_mclk % mclk))
+ mclk = machine->util_data.set_mclk;
+ else {
+ dev_err(card->dev, "Can't configure clocks\n");
+ return err;
+ }
+ }
+
+ tegra_asoc_utils_lock_clk_rate(&machine->util_data, 1);
+
+ i2s_daifmt = SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+
+ i2s_daifmt |= SND_SOC_DAIFMT_DSP_A;
+
+ err = snd_soc_dai_set_fmt(codec_dai, i2s_daifmt);
+ if (err < 0) {
+ dev_err(card->dev, "codec_dai fmt not set\n");
+ return err;
+ }
+
+ sys_clk = machine->util_data.set_mclk;
+
+ err = snd_soc_dai_set_sysclk(codec_dai, WM8753_PCMCLK, sys_clk,
+ SND_SOC_CLOCK_IN);
+ if (err < 0) {
+ dev_err(card->dev, "codec_dai clock not set\n");
+ return err;
+ }
+
+ err = snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0,
+ sys_clk, 12288000);
+
+ if (err < 0) {
+ dev_err(card->dev, "codec_dai pll not set\n");
+ return err;
+ }
+
+ if (params_rate(params) == 8000) {
+ pcmdiv = WM8753_PCM_DIV_6;
+ /* BB expecting 2048Khz bclk */
+ vxclkdiv = WM8753_VXCLK_DIV_1;
+ } else if (params_rate(params) == 16000) {
+ pcmdiv = WM8753_PCM_DIV_3;
+ /* BB expecting 2048Khz bclk */
+ vxclkdiv = WM8753_VXCLK_DIV_2;
+ } else {
+ dev_err(card->dev, "codec_dai unsupported voice rate\n");
+ return -EINVAL;
+ }
+
+ snd_soc_dai_set_clkdiv(codec_dai, WM8753_VXCLKDIV, vxclkdiv);
+ snd_soc_dai_set_clkdiv(codec_dai, WM8753_PCMDIV, pcmdiv);
+
+ machine->is_call_mode_bt = 0;
+
+ return 0;
+}
+
+static int tegra_bt_call_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_card *card = codec->card;
+ struct tegra_wm8753 *machine = snd_soc_card_get_drvdata(card);
+ int srate, mclk;
+ int err;
+
+ srate = params_rate(params);
+ switch (srate) {
+ case 8000:
+ case 16000:
+ case 24000:
+ case 32000:
+ case 48000:
+ case 64000:
+ case 96000:
+ mclk = 12288000;
+ break;
+ case 11025:
+ case 22050:
+ case 44100:
+ case 88200:
+ mclk = 11289600;
+ break;
+ default:
+ mclk = 12000000;
+ break;
+ }
+
+ err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk);
+ if (err < 0) {
+ if (!(machine->util_data.set_mclk % mclk))
+ mclk = machine->util_data.set_mclk;
+ else {
+ dev_err(card->dev, "Can't configure clocks\n");
+ return err;
+ }
+ }
+
+ tegra_asoc_utils_lock_clk_rate(&machine->util_data, 1);
+
+ machine->is_call_mode_bt = 1;
+
+ return 0;
+}
+
+
static int tegra_hw_free(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
return 0;
}
+static int tegra_bt_call_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct tegra_wm8753 *machine = snd_soc_card_get_drvdata(rtd->card);
+
+ tegra_asoc_utils_lock_clk_rate(&machine->util_data, 0);
+ machine->is_call_mode_bt = 0;
+
+ return 0;
+}
+
+static int tegra_wm8753_voice_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct tegra_wm8753 *machine = snd_soc_card_get_drvdata(rtd->card);
+
+ tegra_asoc_utils_lock_clk_rate(&machine->util_data, 0);
+ machine->is_call_mode_bt = 0;
+
+ return 0;
+}
+
+static int tegra_call_mode_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+static int tegra_call_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tegra_wm8753 *machine = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = machine->is_call_mode;
+
+ return 0;
+}
+
+static int tegra_call_mode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tegra_wm8753 *machine = snd_kcontrol_chip(kcontrol);
+ int is_call_mode_new = ucontrol->value.integer.value[0];
+ int codec_dap_id, codec_dap_sel, bb_dap_id, bb_dap_sel;
+
+ if (machine->is_call_mode == is_call_mode_new)
+ return 0;
+
+ bb_dap_id = TEGRA20_DAS_DAP_ID_3;
+ bb_dap_sel = TEGRA20_DAS_DAP_SEL_DAP3;
+
+ if (machine->is_call_mode_bt) {
+ codec_dap_id = TEGRA20_DAS_DAP_ID_4;
+ codec_dap_sel = TEGRA20_DAS_DAP_SEL_DAP4;
+ }
+ else {
+ codec_dap_id = TEGRA20_DAS_DAP_ID_2;
+ codec_dap_sel = TEGRA20_DAS_DAP_SEL_DAP2;
+ }
+
+ if (is_call_mode_new) {
+#ifdef CONFIG_ARCH_TEGRA_2x_SOC
+ tegra20_das_set_tristate(codec_dap_id, 1);
+ tegra20_das_set_tristate(bb_dap_id, 1);
+ tegra20_das_connect_dap_to_dap(codec_dap_id,
+ bb_dap_sel, 0, 0, 0);
+ tegra20_das_connect_dap_to_dap(bb_dap_id,
+ codec_dap_sel, 1, 0, 0);
+ tegra20_das_set_tristate(codec_dap_id, 0);
+ tegra20_das_set_tristate(bb_dap_id, 0);
+#endif
+ } else {
+#ifdef CONFIG_ARCH_TEGRA_2x_SOC
+ tegra20_das_set_tristate(codec_dap_id, 1);
+ tegra20_das_set_tristate(bb_dap_id, 1);
+ tegra20_das_connect_dap_to_dap(bb_dap_id,
+ bb_dap_sel, 0, 0, 0);
+ tegra20_das_connect_dap_to_dap(codec_dap_id,
+ codec_dap_sel, 0, 0, 0);
+ tegra20_das_set_tristate(codec_dap_id, 0);
+ tegra20_das_set_tristate(bb_dap_id, 0);
+#endif
+ }
+
+ machine->is_call_mode = is_call_mode_new;
+ g_is_call_mode = machine->is_call_mode;
+
+ return 1;
+}
+
+struct snd_kcontrol_new tegra_call_mode_control = {
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Call Mode Switch",
+ .private_value = 0xffff,
+ .info = tegra_call_mode_info,
+ .get = tegra_call_mode_get,
+ .put = tegra_call_mode_put
+};
+
static struct snd_soc_ops tegra_wm8753_ops = {
.hw_params = tegra_wm8753_hw_params,
.hw_free = tegra_hw_free,
};
+static struct snd_soc_ops tegra_wm8753_voice_ops = {
+ .hw_params = tegra_wm8753_voice_hw_params,
+ .hw_free = tegra_wm8753_voice_hw_free,
+};
+
+static struct snd_soc_ops tegra_bt_call_ops = {
+ .hw_params = tegra_bt_call_hw_params,
+ .hw_free = tegra_bt_call_hw_free,
+};
+
static struct snd_soc_ops tegra_bt_sco_ops = {
.hw_params = tegra_bt_sco_hw_params,
.hw_free = tegra_hw_free,
static struct snd_soc_jack tegra_wm8753_hp_jack;
+#ifdef CONFIG_SWITCH
+static struct switch_dev wired_switch_dev = {
+ .name = "h2w",
+};
+
+/* These values are copied from WiredAccessoryObserver */
+enum headset_state {
+ BIT_NO_HEADSET = 0,
+ BIT_HEADSET = (1 << 0),
+ BIT_HEADSET_NO_MIC = (1 << 1),
+};
+
+static int headset_switch_notify(struct notifier_block *self,
+ unsigned long action, void *dev)
+{
+ switch (action) {
+ case SND_JACK_HEADPHONE:
+ switch_set_state(&wired_switch_dev, BIT_HEADSET_NO_MIC);
+ break;
+ case SND_JACK_HEADSET:
+ switch_set_state(&wired_switch_dev, BIT_HEADSET);
+ break;
+ default:
+ switch_set_state(&wired_switch_dev, BIT_NO_HEADSET);
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block headset_switch_nb = {
+ .notifier_call = headset_switch_notify,
+};
+#else
static struct snd_soc_jack_pin tegra_wm8753_hp_jack_pins[] = {
{
.pin = "Headphone Jack",
.mask = SND_JACK_HEADPHONE,
},
};
+#endif
static int tegra_wm8753_event_int_spk(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
struct snd_soc_dapm_context *dapm = w->dapm;
struct snd_soc_card *card = dapm->card;
struct tegra_wm8753 *machine = snd_soc_card_get_drvdata(card);
- struct tegra_wm8753_platform_data *pdata = machine->pdata;
+ struct tegra_asoc_platform_data *pdata = machine->pdata;
if (!(machine->gpio_requested & GPIO_SPKR_EN))
return 0;
struct snd_soc_dapm_context *dapm = w->dapm;
struct snd_soc_card *card = dapm->card;
struct tegra_wm8753 *machine = snd_soc_card_get_drvdata(card);
- struct tegra_wm8753_platform_data *pdata = machine->pdata;
+ struct tegra_asoc_platform_data *pdata = machine->pdata;
if (!(machine->gpio_requested & GPIO_HP_MUTE))
return 0;
struct snd_soc_dapm_context *dapm = &codec->dapm;
struct snd_soc_card *card = codec->card;
struct tegra_wm8753 *machine = snd_soc_card_get_drvdata(card);
- struct tegra_wm8753_platform_data *pdata = machine->pdata;
+ struct tegra_asoc_platform_data *pdata = machine->pdata;
int ret;
if (machine_is_whistler()) {
gpio_direction_output(pdata->gpio_ext_mic_en, 0);
}
- ret = snd_soc_add_controls(codec, tegra_wm8753_controls,
+ ret = snd_soc_add_card_controls(card, tegra_wm8753_controls,
ARRAY_SIZE(tegra_wm8753_controls));
if (ret < 0)
return ret;
&tegra_wm8753_hp_jack);
wm8753_headphone_detect(codec, &tegra_wm8753_hp_jack,
SND_JACK_HEADPHONE, pdata->debounce_time_hp);
+#ifdef CONFIG_SWITCH
+ snd_soc_jack_notifier_register(&tegra_wm8753_hp_jack,
+ &headset_switch_nb);
+#else
snd_soc_jack_add_pins(&tegra_wm8753_hp_jack,
ARRAY_SIZE(tegra_wm8753_hp_jack_pins),
tegra_wm8753_hp_jack_pins);
+#endif
+
+ /* Add call mode switch control */
+ ret = snd_ctl_add(codec->card->snd_card,
+ snd_ctl_new1(&tegra_call_mode_control, machine));
+ if (ret < 0)
+ return ret;
+
+ ret = tegra_asoc_utils_register_ctls(&machine->util_data);
+ if (ret < 0)
+ return ret;
snd_soc_dapm_nc_pin(dapm, "ACIN");
snd_soc_dapm_nc_pin(dapm, "ACOP");
{
.name = "WM8753",
.stream_name = "WM8753 PCM HIFI",
- .codec_name = "wm8753-codec.4-001a",
+ .codec_name = "wm8753.4-001a",
.platform_name = "tegra-pcm-audio",
.cpu_dai_name = "tegra20-i2s.0",
.codec_dai_name = "wm8753-hifi",
.ops = &tegra_bt_sco_ops,
},
#endif
+ {
+ .name = "VOICE CALL",
+ .stream_name = "VOICE CALL PCM",
+ .codec_name = "wm8753.4-001a",
+ .platform_name = "tegra-pcm-audio",
+ .cpu_dai_name = "dit-hifi",
+ .codec_dai_name = "wm8753-voice",
+ .ops = &tegra_wm8753_voice_ops,
+ },
+ {
+ .name = "BT VOICE CALL",
+ .stream_name = "BT VOICE CALL PCM",
+ .codec_name = "spdif-dit.2",
+ .platform_name = "tegra-pcm-audio",
+ .cpu_dai_name = "dit-hifi",
+ .codec_dai_name = "dit-hifi",
+ .ops = &tegra_bt_call_ops,
+ },
};
static struct snd_soc_card snd_soc_tegra_wm8753 = {
.name = "tegra-wm8753",
+ .owner = THIS_MODULE,
.dai_link = tegra_wm8753_dai,
.num_links = ARRAY_SIZE(tegra_wm8753_dai),
};
{
struct snd_soc_card *card = &snd_soc_tegra_wm8753;
struct tegra_wm8753 *machine;
- struct tegra_wm8753_platform_data *pdata;
+ struct tegra_asoc_platform_data *pdata;
int ret;
return -ENOMEM;
}
- machine->pdata = pdata;
-
- ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev);
+ ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev, card);
if (ret)
goto err_free_machine;
goto err_unregister_card;
}
+#ifdef CONFIG_SWITCH
+ /* Add h2w swith class support */
+ ret = tegra_asoc_switch_register(&wired_switch_dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "not able to register switch device\n");
+ goto err_unregister_card;
+ }
+#endif
+
+#ifndef CONFIG_ARCH_TEGRA_2x_SOC
+ ret = tegra_asoc_utils_set_parent(&machine->util_data,
+ pdata->i2s_param[HIFI_CODEC].is_i2s_master);
+ if (ret) {
+ dev_err(&pdev->dev, "tegra_asoc_utils_set_parent failed (%d)\n",
+ ret);
+ goto err_unregister_card;
+ }
+#endif
+
return 0;
+err_unregister_card:
+ snd_soc_unregister_card(card);
err_fini_utils:
tegra_asoc_utils_fini(&machine->util_data);
err_free_machine:
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct tegra_wm8753 *machine = snd_soc_card_get_drvdata(card);
- struct tegra_wm8753_platform_data *pdata = machine->pdata;
+ struct tegra_asoc_platform_data *pdata = machine->pdata;
snd_soc_unregister_card(card);
+#ifdef CONFIG_SWITCH
+ tegra_asoc_switch_unregister(&wired_switch_dev);
+#endif
+
tegra_asoc_utils_fini(&machine->util_data);
if (machine->gpio_requested & GPIO_EXT_MIC_EN)