// SPDX-License-Identifier: GPL-2.0-only
/*
* tegra_asoc_machine.c - Universal ASoC machine driver for NVIDIA Tegra boards.
*/
#include <linux/clk.h>
#include <linux/export.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/jack.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "tegra_asoc_machine.h"
/* Headphones Jack */
static struct snd_soc_jack tegra_machine_hp_jack;
static struct snd_soc_jack_pin tegra_machine_hp_jack_pins[] = {
{ .pin = "Headphone", .mask = SND_JACK_HEADPHONE },
{ .pin = "Headphones", .mask = SND_JACK_HEADPHONE },
};
static struct snd_soc_jack_gpio tegra_machine_hp_jack_gpio = {
.name = "Headphones detection",
.report = SND_JACK_HEADPHONE,
.debounce_time = 150,
};
/* Headset Jack */
static struct snd_soc_jack tegra_machine_headset_jack;
static struct snd_soc_jack_pin tegra_machine_headset_jack_pins[] = {
{ .pin = "Headset Mic", .mask = SND_JACK_MICROPHONE },
{ .pin = "Headset Stereophone", .mask = SND_JACK_HEADPHONE },
};
static struct snd_soc_jack_gpio tegra_machine_headset_jack_gpio = {
.name = "Headset detection",
.report = SND_JACK_HEADSET,
.debounce_time = 150,
};
/* Mic Jack */
static int coupled_mic_hp_check(void *data)
{
struct tegra_machine *machine = (struct tegra_machine *)data;
/* Detect mic insertion only if 3.5 jack is in */
if (gpiod_get_value_cansleep(machine->gpiod_hp_det) &&
gpiod_get_value_cansleep(machine->gpiod_mic_det))
return SND_JACK_MICROPHONE;
return 0;
}
static struct snd_soc_jack tegra_machine_mic_jack;
static struct snd_soc_jack_pin tegra_machine_mic_jack_pins[] = {
{ .pin = "Mic Jack", .mask = SND_JACK_MICROPHONE },
{ .pin = "Headset Mic", .mask = SND_JACK_MICROPHONE },
};
static struct snd_soc_jack_gpio tegra_machine_mic_jack_gpio = {
.name = "Mic detection",
.report = SND_JACK_MICROPHONE,
.debounce_time = 150,
};
static int tegra_machine_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
{
struct snd_soc_dapm_context *dapm = w->dapm;
struct tegra_machine *machine = snd_soc_card_get_drvdata(dapm->card);
if (!snd_soc_dapm_widget_name_cmp(w, "Int Spk") ||
!snd_soc_dapm_widget_name_cmp(w, "Speakers"))
gpiod_set_value_cansleep(machine->gpiod_spkr_en,
SND_SOC_DAPM_EVENT_ON(event));
if (!snd_soc_dapm_widget_name_cmp(w, "Mic Jack") ||
!snd_soc_dapm_widget_name_cmp(w, "Headset Mic"))
gpiod_set_value_cansleep(machine->gpiod_ext_mic_en,
SND_SOC_DAPM_EVENT_ON(event));
if (!snd_soc_dapm_widget_name_cmp(w, "Int Mic") ||
!snd_soc_dapm_widget_name_cmp(w, "Internal Mic 2"))
gpiod_set_value_cansleep(machine->gpiod_int_mic_en,
SND_SOC_DAPM_EVENT_ON(event));
if (!snd_soc_dapm_widget_name_cmp(w, "Headphone") ||
!snd_soc_dapm_widget_name_cmp(w, "Headphone Jack"))
gpiod_set_value_cansleep(machine->gpiod_hp_mute,
!SND_SOC_DAPM_EVENT_ON(event));
return 0;
}
static const struct snd_soc_dapm_widget tegra_machine_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", tegra_machine_event),
SND_SOC_DAPM_HP("Headphone", tegra_machine_event),
SND_SOC_DAPM_HP("Headset Stereophone", NULL),
SND_SOC_DAPM_HP("Headphones", NULL),
SND_SOC_DAPM_SPK("Speakers", tegra_machine_event),
SND_SOC_DAPM_SPK("Int Spk", tegra_machine_event),
SND_SOC_DAPM_SPK("Earpiece", NULL),
SND_SOC_DAPM_MIC("Int Mic", tegra_machine_event),
SND_SOC_DAPM_MIC("Mic Jack", tegra_machine_event),
SND_SOC_DAPM_MIC("Internal Mic 1", NULL),
SND_SOC_DAPM_MIC("Internal Mic 2", tegra_machine_event),
SND_SOC_DAPM_MIC("Headset Mic", tegra_machine_event),
SND_SOC_DAPM_MIC("Digital Mic", NULL),
SND_SOC_DAPM_MIC("Mic", NULL),
SND_SOC_DAPM_LINE("Line In Jack", NULL),
SND_SOC_DAPM_LINE("Line In", NULL),
SND_SOC_DAPM_LINE("LineIn", NULL),
};
static const struct snd_kcontrol_new tegra_machine_controls[] = {
SOC_DAPM_PIN_SWITCH("Speakers"),
SOC_DAPM_PIN_SWITCH("Int Spk"),
SOC_DAPM_PIN_SWITCH("Earpiece"),
SOC_DAPM_PIN_SWITCH("Int Mic"),
SOC_DAPM_PIN_SWITCH("Headset Mic"),
SOC_DAPM_PIN_SWITCH("Internal Mic 1"),
SOC_DAPM_PIN_SWITCH("Internal Mic 2"),
SOC_DAPM_PIN_SWITCH("Headphones"),
SOC_DAPM_PIN_SWITCH("Mic Jack"),
};
int tegra_asoc_machine_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_card *card = rtd->card;
struct tegra_machine *machine = snd_soc_card_get_drvdata(card);
const char *jack_name;
int err;
if (machine->gpiod_hp_det && machine->asoc->add_hp_jack) {
if (machine->asoc->hp_jack_name)
jack_name = machine->asoc->hp_jack_name;
else
jack_name = "Headphones Jack";
err = snd_soc_card_jack_new_pins(card, jack_name,
SND_JACK_HEADPHONE,
&tegra_machine_hp_jack,
tegra_machine_hp_jack_pins,
ARRAY_SIZE(tegra_machine_hp_jack_pins));
if (err) {
dev_err(rtd->dev,
"Headphones Jack creation failed: %d\n", err);
return err;
}
tegra_machine_hp_jack_gpio.desc = machine->gpiod_hp_det;
err = snd_soc_jack_add_gpios(&tegra_machine_hp_jack, 1,
&tegra_machine_hp_jack_gpio);
if (err)
dev_err(rtd->dev, "HP GPI