/*
* tegra_rt5640.c - Tegra machine ASoC driver for boards using ALC5640 codec.
*
* Author: Johnny Qiu <joqiu@nvidia.com>
* Copyright (C) 2011-2012, NVIDIA, Inc.
*
* Based on code copyright/by:
*
* Copyright 2007 Wolfson Microelectronics PLC.
* Author: Graeme Gregory
* graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
*
* 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.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
#include <asm/mach-types.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_rt5640_pdata.h>
#include <sound/core.h>
#include <sound/jack.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "../codecs/rt5639.h"
#include "../codecs/rt5640.h"
#include "tegra_pcm.h"
#include "tegra_asoc_utils.h"
#define DRV_NAME "tegra-snd-rt5640"
#define GPIO_SPKR_EN BIT(0)
#define GPIO_HP_MUTE BIT(1)
#define GPIO_INT_MIC_EN BIT(2)
#define GPIO_EXT_MIC_EN BIT(3)
#define GPIO_HP_DET BIT(4)
struct tegra_rt5640 {
struct tegra_asoc_utils_data util_data;
struct tegra_rt5640_platform_data *pdata;
struct regulator *spk_reg;
struct regulator *dmic_reg;
struct regulator *cdc_en;
int gpio_requested;
#ifdef CONFIG_SWITCH
int jack_status;
#endif
};
static int tegra_rt5640_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_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_card *card = codec->card;
struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card);
int srate, mclk, i2s_daifmt;
int err;
srate = params_rate(params);
mclk = 256 * srate;
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_CBS_CFS;
i2s_daifmt |= SND_SOC_DAIFMT_I2S;
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;
}
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;
}
err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk,
SND_SOC_CLOCK_IN);
if (err < 0) {
dev_err(card->dev, "codec_dai clock not set\n");
return err;
}
return 0;
}
static int tegra_bt_sco_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 *cpu_dai = rtd->cpu_dai;
struct snd_soc_card *card = rtd->card;
struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card);
int srate, mclk, min_mclk;
int err;
srate = params_rate(params);
switch (srate) {
case 11025:
case 22050:
case 44100:
case 88200:
mclk = 11289600;
break;
case 8000:
case 16000:
case 32000:
case 48000:
case 64000:
case 96000:
mclk = 12288000;
break;
default:
return -EINVAL;
}
min_mclk = 64 * srate;
err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk);
if (err < 0) {
if (!(machine->util_data.set_mclk % min_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);
err = snd_soc_dai_set_fmt(cpu_dai,
SND_SOC_DAIFMT_DSP_A |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS);
if (err < 0) {
dev_err(card->dev, "cpu_dai fmt not set\n");
return err;
}
return 0;
}
static int tegra_spdif_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_card *card = rtd->card;
struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card);
int srate, mclk, min_mclk;
int err;
srate = params_rate(params);
switch (srate) {
case 11025:
case 22050:
case 44100:
case 88200:
mclk = 11289600;
break;
case 8000:
case 16000:
case 32000:
case 48000:
case 64000:
case 96000:
mclk = 12288000;
break;
default:
return -EINVAL;
}
min_mclk = 128 * srate;
err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk);
if (err < 0) {
if (!(machine->util_data.set_mclk % min_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);
return 0;
}
static int tegra_hw_free(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(rtd->card);
tegra_asoc_utils_lock_clk_rate(&machine->util_data, 0);
return 0;
}
static struct snd_soc_ops tegra_rt5640_ops = {
.hw_params = tegra_rt5640_hw_params,
.hw_free = tegra_hw_free,
};
static struct snd_soc_ops tegra_rt5640_bt_sco_ops = {
.hw_params = tegra_bt_sco_hw_params,
.hw_free = tegra_hw_free,
};
static struct snd_soc_ops tegra_spdif_ops = {
.hw_params = tegra_spdif_hw_params,
.hw_free = tegra_hw_free,
};
static struct snd_soc_jack tegra_rt5640_hp_jack;
static struct snd_soc_jack_gpio tegra_rt5640_hp_jack_gpio = {
.name = "headphone detect",
.report = SND_JACK_HEADPHONE,
.debounce_time = 150,
.invert = 1,
};
#ifdef CONFIG_SWITCH
/* These values are copied from Android WiredAccessoryObserver */
enum headset_state {
BIT_NO_HEADSET = 0,
BIT_HEADSET = (1 << 0),
BIT_HEADSET_NO_MIC = (1 << 1),
};
static struct switch_dev tegra_rt5640_headset_switch = {
.name = "h2w",
};
static int tegra_rt5640_jack_notifier(struct notifier_block *self,
unsigned long action, void *dev)
{
struct snd_soc_jack *jack = dev;
struct snd_soc_codec *codec = jack->codec;
struct snd_soc_card *card = codec->card;
struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card);
enum headset_state state = BIT_NO_HEADSET;
unsigned char status_jack;
if (jack == &tegra_rt5640_hp_jack) {
if (action) {
if (!strncmp(machine->pdata->codec_name, "rt5639", 6))
status_jack = rt5639_headset_detect(codec, 1);
else if (!strncmp(machine->pdata->codec_name, "rt5640",
6))
status_jack = rt5640_headset_detect(codec, 1);
machine->jack_status &= ~SND_JACK_HEADPHONE;
machine->jack_status &= ~SND_JACK_MICROPHONE;
if (status_jack == RT5639_HEADPHO_DET ||
status_jack == RT5640_HEADPHO_DET)
machine->jack_status |=
SND_JACK_HEADPHONE;
else if (status_jack == RT5639_HEADSET_DET ||
status_jack == RT5640_HEADSET_DET) {
machine->jack_status |=
SND_JACK_HEADPHONE;
machine->jack_status |=
SND_JACK_MICROPHONE;
}
} else {
if (!strncmp(machine->pdata->codec_name, "rt5639", 6))
rt5639_headset_detect(codec, 0);
else if (!strncmp(machine->pdata->codec_name, "rt5640",
6))
rt5640_headset_detect(codec, 0);
machine->jack_status &= ~SND_JACK_HEADPHONE;
machine->jack_status &= ~SND_JACK_MICROPHONE;
}
}
switch (machine->jack_status) {
case SND_JACK_HEADPHONE:
state = BIT_HEADSET_NO_MIC;
break;
case SND_JACK_HEADSET:
state = BIT_HEADSET;
break;
case SND_JACK_MICROPHONE:
/* mic: would not report */
default:
state = BIT_NO_HEADSET;
}
switch_set_state(&tegra_rt5640_headset_switch, state);
return NOTIFY_OK;
}
static struct notifier_block tegra_rt5640_jack_detect_nb = {
.notifier_call = tegra_rt5640_jack_notifier,
};
#else
static struct snd_soc_jack_pin tegra_rt5640_hp_jack_pins[] = {
{
.pin = "Headphone Jack",
.mask = SND_JACK_HEADPHONE,
},
};
#endif
static int tegra_rt5640_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_rt5640 *machine = snd_soc_card_get_drvdata(card);
struct tegra_rt5640_platform_data *pdata = machine->pdata;
if (machine->spk_reg) {
if (SND_SOC_DAPM_EVENT_ON(event))
regulator_enable(machine->spk_reg);
else
regulator_disable(machine->spk_reg);
}
if (!(machine->gpio_requested & GPIO_SPKR_EN))
return 0;
gpio_set_value_cansleep(pdata->gpio_spkr_en,
SND_SOC_DAPM_EVENT_ON(event));
return 0;
}
static int tegra_rt5640_event_hp(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_rt5640 *machine = snd_soc_card_get_drvdata(card);
struct tegra_rt5640_platform_data *pdata = machine->pdata;
if (!(machine->gpio_requested & GPIO_HP_MUTE))
return 0;
gpio_set_value_cansleep(pdata->gpio_hp_mute,
!SND_SOC_DAPM_EVENT_ON(event));
return 0;
}
static int tegra_rt5640_event_int_mic(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_rt5640 *machine = snd_soc_card_get_drvdata(card);
struct tegra_rt5640_platform_data *pdata = machine->pdata;
if (machine->dmic_reg) {
if (SND_SOC_DAPM_EVENT_ON(event))
regulator_enable(machine->dmic_reg);
else
regulator_disable(machine->dmic_reg);
}
if (!(machine->gpio_requested & GPIO_INT_MIC_EN))
return 0;
gpio_set_value_cansleep(pdata->gpio_int_mic_en,
SND_SOC_DAPM_EVENT_ON(event));
return 0;
}
static int tegra_rt5640_event_ext_mic(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_rt5640 *machine = snd_soc_card_get_drvdata(card);
struct tegra_rt5640_platform_data *pdata = machine->pdata;
if (!(machine->gpio_requested & GPIO_EXT_MIC_EN))
return 0;
gpio_set_value_cansleep(pdata->gpio_ext_mic_en,
!SND_SOC_DAPM_EVENT_ON(event));
return 0;
}
static const struct snd_soc_dapm_widget cardhu_dapm_widgets[] = {
SND_SOC_DAPM_SPK("Int Spk", tegra_rt5640_event_int_spk),
SND_SOC_DAPM_HP("Headphone Jack", tegra_rt5640_event_hp),
SND_SOC_DAPM_MIC("Mic Jack", tegra_rt5640_event_ext_mic),
SND_SOC_DAPM_MIC("Int Mic", tegra_rt5640_event_int_mic),
};
static const struct snd_soc_dapm_route cardhu_audio_map[] = {
{"Headphone Jack", NULL, "HPOR"},
{"Headphone Jack", NULL, "HPOL"},
{"Int Spk", NULL, "SPORP"},
{"Int Spk", NULL, "SPORN"},
{"Int Spk", NULL, "SPOLP"},
{"Int Spk", NULL, "SPOLN"},
{"micbias1", NULL, "Mic Jack"},
{"IN1P", NULL, "micbias1"},
{"IN1N", NULL, "micbias1"},
{"micbias1", NULL, "Int Mic"},
{"IN2P", NULL, "micbias1"},
};
static const struct snd_kcontrol_new cardhu_controls[] = {
SOC_DAPM_PIN_SWITCH("Int Spk"),
SOC_DAPM_PIN_SWITCH("Headphone Jack"),
SOC_DAPM_PIN_SWITCH("Mic Jack"),
SOC_DAPM_PIN_SWITCH("Int Mic"),
};
static int tegra_rt5640_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_dapm_context *dapm = &codec->dapm;
struct snd_soc_card *card = codec->card;
struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card);
struct tegra_rt5640_platform_data *pdata = machine->pdata;
int ret;
if (gpio_is_valid(pdata->gpio_spkr_en)) {
ret = gpio_request(pdata->gpio_spkr_en, "spkr_en");
if (ret) {
dev_err(card->dev, "cannot get spkr_en gpio\n");
return ret;
}
machine->gpio_requested |= GPIO_SPKR_EN;
gpio_direction_output(pdata->gpio_spkr_en, 0);
}
if (gpio_is_valid(pdata->gpio_hp_mute)) {
ret = gpio_request(pdata->gpio_hp_mute, "hp_mute");
if (ret) {
dev_err(card->dev, "cannot get hp_mute gpio\n");
return ret;
}
machine->gpio_requested |= GPIO_HP_MUTE;
gpio_direction_output(pdata->gpio_hp_mute, 0);
}
if (gpio_is_valid(pdata->gpio_int_mic_en)) {
ret = gpio_request(pdata->gpio_int_mic_en, "int_mic_en");
if (ret) {
dev_err(card->dev, "cannot get int_mic_en gpio\n");
return ret;
}
machine->gpio_requested |= GPIO_INT_MIC_EN;
/* Disable int mic; enable signal is active-high */
gpio_direction_output(pdata->gpio_int_mic_en, 0);
}
if (gpio_is_valid(pdata->gpio_ext_mic_en)) {
ret = gpio_request(pdata->gpio_ext_mic_en, "ext_mic_en");
if (ret) {
dev_err(card->dev, "cannot get ext_mic_en gpio\n");
return ret;
}
machine->gpio_requested |= GPIO_EXT_MIC_EN;
/* Enable ext mic; enable signal is active-low */
gpio_direction_output(pdata->gpio_ext_mic_en, 0);
}
if (gpio_is_valid(pdata->gpio_hp_det)) {
tegra_rt5640_hp_jack_gpio.gpio = pdata->gpio_hp_det;
snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE,
&tegra_rt5640_hp_jack);
#ifndef CONFIG_SWITCH
snd_soc_jack_add_pins(&tegra_rt5640_hp_jack,
ARRAY_SIZE(tegra_rt5640_hp_jack_pins),
tegra_rt5640_hp_jack_pins);
#else
snd_soc_jack_notifier_register(&tegra_rt5640_hp_jack,
&tegra_rt5640_jack_detect_nb);
#endif
snd_soc_jack_add_gpios(&tegra_rt5640_hp_jack,
1,
&tegra_rt5640_hp_jack_gpio);
machine->gpio_requested |= GPIO_HP_DET;
}
ret = snd_soc_add_controls(codec, cardhu_controls,
ARRAY_SIZE(cardhu_controls));
if (ret < 0)
return ret;
snd_soc_dapm_new_controls(dapm, cardhu_dapm_widgets,
ARRAY_SIZE(cardhu_dapm_widgets));
snd_soc_dapm_add_routes(dapm, cardhu_audio_map,
ARRAY_SIZE(cardhu_audio_map));
/* FIXME: Calculate automatically based on DAPM routes? */
snd_soc_dapm_nc_pin(dapm, "LOUTL");
snd_soc_dapm_nc_pin(dapm, "LOUTR");
snd_soc_dapm_sync(dapm);
return 0;
}
static struct snd_soc_dai_link tegra_rt5640_dai[] = {
{
.name = "RT5640",
.stream_name = "RT5640 PCM",
.codec_name = "rt5640.4-001c",
.platform_name = "tegra-pcm-audio",
.cpu_dai_name = "tegra30-i2s.1",
.codec_dai_name = "rt5640-aif1",
.init = tegra_rt5640_init,
.ops = &tegra_rt5640_ops,
},
{
.name = "SPDIF",
.stream_name = "SPDIF PCM",
.codec_name = "spdif-dit.0",
.platform_name = "tegra-pcm-audio",
.cpu_dai_name = "tegra30-spdif",
.codec_dai_name = "dit-hifi",
.ops = &tegra_spdif_ops,
},
{
.name = "BT-SCO",
.stream_name = "BT SCO PCM",
.codec_name = "spdif-dit.1",
.platform_name = "tegra-pcm-audio",
.cpu_dai_name = "tegra30-i2s.3",
.codec_dai_name = "dit-hifi",
.ops = &tegra_rt5640_bt_sco_ops,
},
};
static struct snd_soc_card snd_soc_tegra_rt5640 = {
.name = "tegra-rt5640",
.dai_link = tegra_rt5640_dai,
.num_links = ARRAY_SIZE(tegra_rt5640_dai),
};
static __devinit int tegra_rt5640_driver_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &snd_soc_tegra_rt5640;
struct tegra_rt5640 *machine;
struct tegra_rt5640_platform_data *pdata;
int ret;
pdata = pdev->dev.platform_data;
if (!pdata) {
dev_err(&pdev->dev, "No platform data supplied\n");
return -EINVAL;
}
if (pdata->codec_name)
card->dai_link->codec_name = pdata->codec_name;
if (pdata->codec_dai_name)
card->dai_link->codec_dai_name = pdata->codec_dai_name;
machine = kzalloc(sizeof(struct tegra_rt5640), GFP_KERNEL);
if (!machine) {
dev_err(&pdev->dev, "Can't allocate tegra_rt5640 struct\n");
return -ENOMEM;
}
machine->pdata = pdata;
ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev);
if (ret)
goto err_free_machine;
machine->cdc_en = regulator_get(NULL, "cdc_en");
if (WARN_ON(IS_ERR(machine->cdc_en))) {
dev_err(&pdev->dev, "Couldn't get regulator cdc_en: %ld\n",
PTR_ERR(machine->cdc_en));
machine->cdc_en = 0;
} else {
regulator_enable(machine->cdc_en);
}
machine->spk_reg = regulator_get(&pdev->dev, "vdd_spk_amp");
if (IS_ERR(machine->spk_reg)) {
dev_info(&pdev->dev, "No speaker regulator found\n");
machine->spk_reg = 0;
}
#ifdef CONFIG_SWITCH
/* Addd h2w swith class support */
ret = switch_dev_register(&tegra_rt5640_headset_switch);
if (ret < 0)
goto err_fini_utils;
#endif
card->dev = &pdev->dev;
platform_set_drvdata(pdev, card);
snd_soc_card_set_drvdata(card, machine);
ret = snd_soc_register_card(card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n",
ret);
goto err_unregister_switch;
}
if (!card->instantiated) {
ret = -ENODEV;
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n",
ret);
goto err_unregister_card;
}
return 0;
err_unregister_card:
snd_soc_unregister_card(card);
err_unregister_switch:
#ifdef CONFIG_SWITCH
switch_dev_unregister(&tegra_rt5640_headset_switch);
err_fini_utils:
#endif
tegra_asoc_utils_fini(&machine->util_data);
err_free_machine:
kfree(machine);
return ret;
}
static int __devexit tegra_rt5640_driver_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card);
struct tegra_rt5640_platform_data *pdata = machine->pdata;
if (machine->gpio_requested & GPIO_HP_DET)
snd_soc_jack_free_gpios(&tegra_rt5640_hp_jack,
1,
&tegra_rt5640_hp_jack_gpio);
if (machine->gpio_requested & GPIO_EXT_MIC_EN)
gpio_free(pdata->gpio_ext_mic_en);
if (machine->gpio_requested & GPIO_INT_MIC_EN)
gpio_free(pdata->gpio_int_mic_en);
if (machine->gpio_requested & GPIO_HP_MUTE)
gpio_free(pdata->gpio_hp_mute);
if (machine->gpio_requested & GPIO_SPKR_EN)
gpio_free(pdata->gpio_spkr_en);
machine->gpio_requested = 0;
if (machine->spk_reg)
regulator_put(machine->spk_reg);
if (machine->dmic_reg)
regulator_put(machine->dmic_reg);
if (machine->cdc_en) {
regulator_disable(machine->cdc_en);
regulator_put(machine->cdc_en);
}
snd_soc_unregister_card(card);
tegra_asoc_utils_fini(&machine->util_data);
#ifdef CONFIG_SWITCH
switch_dev_unregister(&tegra_rt5640_headset_switch);
#endif
kfree(machine);
return 0;
}
static struct platform_driver tegra_rt5640_driver = {
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = tegra_rt5640_driver_probe,
.remove = __devexit_p(tegra_rt5640_driver_remove),
};
static int __init tegra_rt5640_modinit(void)
{
return platform_driver_register(&tegra_rt5640_driver);
}
module_init(tegra_rt5640_modinit);
static void __exit tegra_rt5640_modexit(void)
{
platform_driver_unregister(&tegra_rt5640_driver);
}
module_exit(tegra_rt5640_modexit);
MODULE_AUTHOR("Johnny Qiu <joqiu@nvidia.com>");
MODULE_DESCRIPTION("Tegra+RT5640 machine ASoC driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRV_NAME);