snd_soc_card_jack_new() allowed to create jack kcontrol without pins, but did not create kcontrols. The jack would not have kcontrols if pins were not going to be added. This renames the old snd_soc_card_jack_new() to snd_soc_card_jack_new_pins() for use when pins are provided or will be added later. The new snd_soc_card_jack_new() appropriately creates a jack for use without pins and adds a kcontrol. Signed-off-by: Akihiko Odaki <akihiko.odaki@gmail.com> Link: https://lore.kernel.org/r/20220408041114.6024-1-akihiko.odaki@gmail.com Signed-off-by: Mark Brown <broonie@kernel.org>
707 lines
19 KiB
C
707 lines
19 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
// Copyright(c) 2021 Intel Corporation.
|
|
|
|
/*
|
|
* Intel SOF Machine Driver with es8336 Codec
|
|
*/
|
|
|
|
#include <linux/device.h>
|
|
#include <linux/dmi.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/gpio/machine.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/input.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/slab.h>
|
|
#include <sound/jack.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/soc.h>
|
|
#include <sound/soc-acpi.h>
|
|
#include "hda_dsp_common.h"
|
|
|
|
/* jd-inv + terminating entry */
|
|
#define MAX_NO_PROPS 2
|
|
|
|
#define SOF_ES8336_SSP_CODEC(quirk) ((quirk) & GENMASK(3, 0))
|
|
#define SOF_ES8336_SSP_CODEC_MASK (GENMASK(3, 0))
|
|
|
|
#define SOF_ES8336_SPEAKERS_EN_GPIO1_QUIRK BIT(4)
|
|
#define SOF_ES8336_ENABLE_DMIC BIT(5)
|
|
#define SOF_ES8336_JD_INVERTED BIT(6)
|
|
#define SOF_ES8336_HEADPHONE_GPIO BIT(7)
|
|
#define SOC_ES8336_HEADSET_MIC1 BIT(8)
|
|
|
|
static unsigned long quirk;
|
|
|
|
static int quirk_override = -1;
|
|
module_param_named(quirk, quirk_override, int, 0444);
|
|
MODULE_PARM_DESC(quirk, "Board-specific quirk override");
|
|
|
|
struct sof_es8336_private {
|
|
struct device *codec_dev;
|
|
struct gpio_desc *gpio_speakers, *gpio_headphone;
|
|
struct snd_soc_jack jack;
|
|
struct list_head hdmi_pcm_list;
|
|
bool speaker_en;
|
|
};
|
|
|
|
struct sof_hdmi_pcm {
|
|
struct list_head head;
|
|
struct snd_soc_dai *codec_dai;
|
|
int device;
|
|
};
|
|
|
|
static const struct acpi_gpio_params enable_gpio0 = { 0, 0, true };
|
|
static const struct acpi_gpio_params enable_gpio1 = { 1, 0, true };
|
|
|
|
static const struct acpi_gpio_mapping acpi_speakers_enable_gpio0[] = {
|
|
{ "speakers-enable-gpios", &enable_gpio0, 1 },
|
|
{ }
|
|
};
|
|
|
|
static const struct acpi_gpio_mapping acpi_speakers_enable_gpio1[] = {
|
|
{ "speakers-enable-gpios", &enable_gpio1, 1 },
|
|
};
|
|
|
|
static const struct acpi_gpio_mapping acpi_enable_both_gpios[] = {
|
|
{ "speakers-enable-gpios", &enable_gpio0, 1 },
|
|
{ "headphone-enable-gpios", &enable_gpio1, 1 },
|
|
{ }
|
|
};
|
|
|
|
static const struct acpi_gpio_mapping acpi_enable_both_gpios_rev_order[] = {
|
|
{ "speakers-enable-gpios", &enable_gpio1, 1 },
|
|
{ "headphone-enable-gpios", &enable_gpio0, 1 },
|
|
{ }
|
|
};
|
|
|
|
static const struct acpi_gpio_mapping *gpio_mapping = acpi_speakers_enable_gpio0;
|
|
|
|
static void log_quirks(struct device *dev)
|
|
{
|
|
dev_info(dev, "quirk mask %#lx\n", quirk);
|
|
dev_info(dev, "quirk SSP%ld\n", SOF_ES8336_SSP_CODEC(quirk));
|
|
if (quirk & SOF_ES8336_ENABLE_DMIC)
|
|
dev_info(dev, "quirk DMIC enabled\n");
|
|
if (quirk & SOF_ES8336_SPEAKERS_EN_GPIO1_QUIRK)
|
|
dev_info(dev, "Speakers GPIO1 quirk enabled\n");
|
|
if (quirk & SOF_ES8336_HEADPHONE_GPIO)
|
|
dev_info(dev, "quirk headphone GPIO enabled\n");
|
|
if (quirk & SOF_ES8336_JD_INVERTED)
|
|
dev_info(dev, "quirk JD inverted enabled\n");
|
|
if (quirk & SOC_ES8336_HEADSET_MIC1)
|
|
dev_info(dev, "quirk headset at mic1 port enabled\n");
|
|
}
|
|
|
|
static int sof_es8316_speaker_power_event(struct snd_soc_dapm_widget *w,
|
|
struct snd_kcontrol *kcontrol, int event)
|
|
{
|
|
struct snd_soc_card *card = w->dapm->card;
|
|
struct sof_es8336_private *priv = snd_soc_card_get_drvdata(card);
|
|
|
|
if (priv->speaker_en == !SND_SOC_DAPM_EVENT_ON(event))
|
|
return 0;
|
|
|
|
priv->speaker_en = !SND_SOC_DAPM_EVENT_ON(event);
|
|
|
|
if (SND_SOC_DAPM_EVENT_ON(event))
|
|
msleep(70);
|
|
|
|
gpiod_set_value_cansleep(priv->gpio_speakers, priv->speaker_en);
|
|
|
|
if (!(quirk & SOF_ES8336_HEADPHONE_GPIO))
|
|
return 0;
|
|
|
|
if (SND_SOC_DAPM_EVENT_ON(event))
|
|
msleep(70);
|
|
|
|
gpiod_set_value_cansleep(priv->gpio_headphone, priv->speaker_en);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_soc_dapm_widget sof_es8316_widgets[] = {
|
|
SND_SOC_DAPM_SPK("Speaker", NULL),
|
|
SND_SOC_DAPM_HP("Headphone", NULL),
|
|
SND_SOC_DAPM_MIC("Headset Mic", NULL),
|
|
SND_SOC_DAPM_MIC("Internal Mic", NULL),
|
|
|
|
SND_SOC_DAPM_SUPPLY("Speaker Power", SND_SOC_NOPM, 0, 0,
|
|
sof_es8316_speaker_power_event,
|
|
SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU),
|
|
};
|
|
|
|
static const struct snd_soc_dapm_widget dmic_widgets[] = {
|
|
SND_SOC_DAPM_MIC("SoC DMIC", NULL),
|
|
};
|
|
|
|
static const struct snd_soc_dapm_route sof_es8316_audio_map[] = {
|
|
{"Headphone", NULL, "HPOL"},
|
|
{"Headphone", NULL, "HPOR"},
|
|
|
|
/*
|
|
* There is no separate speaker output instead the speakers are muxed to
|
|
* the HP outputs. The mux is controlled Speaker and/or headphone switch.
|
|
*/
|
|
{"Speaker", NULL, "HPOL"},
|
|
{"Speaker", NULL, "HPOR"},
|
|
{"Speaker", NULL, "Speaker Power"},
|
|
};
|
|
|
|
static const struct snd_soc_dapm_route sof_es8316_headset_mic2_map[] = {
|
|
{"MIC1", NULL, "Internal Mic"},
|
|
{"MIC2", NULL, "Headset Mic"},
|
|
};
|
|
|
|
static const struct snd_soc_dapm_route sof_es8316_headset_mic1_map[] = {
|
|
{"MIC2", NULL, "Internal Mic"},
|
|
{"MIC1", NULL, "Headset Mic"},
|
|
};
|
|
|
|
static const struct snd_soc_dapm_route dmic_map[] = {
|
|
/* digital mics */
|
|
{"DMic", NULL, "SoC DMIC"},
|
|
};
|
|
|
|
static const struct snd_kcontrol_new sof_es8316_controls[] = {
|
|
SOC_DAPM_PIN_SWITCH("Speaker"),
|
|
SOC_DAPM_PIN_SWITCH("Headphone"),
|
|
SOC_DAPM_PIN_SWITCH("Headset Mic"),
|
|
SOC_DAPM_PIN_SWITCH("Internal Mic"),
|
|
};
|
|
|
|
static struct snd_soc_jack_pin sof_es8316_jack_pins[] = {
|
|
{
|
|
.pin = "Headphone",
|
|
.mask = SND_JACK_HEADPHONE,
|
|
},
|
|
{
|
|
.pin = "Headset Mic",
|
|
.mask = SND_JACK_MICROPHONE,
|
|
},
|
|
};
|
|
|
|
static int dmic_init(struct snd_soc_pcm_runtime *runtime)
|
|
{
|
|
struct snd_soc_card *card = runtime->card;
|
|
int ret;
|
|
|
|
ret = snd_soc_dapm_new_controls(&card->dapm, dmic_widgets,
|
|
ARRAY_SIZE(dmic_widgets));
|
|
if (ret) {
|
|
dev_err(card->dev, "DMic widget addition failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = snd_soc_dapm_add_routes(&card->dapm, dmic_map,
|
|
ARRAY_SIZE(dmic_map));
|
|
if (ret)
|
|
dev_err(card->dev, "DMic map addition failed: %d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sof_hdmi_init(struct snd_soc_pcm_runtime *runtime)
|
|
{
|
|
struct sof_es8336_private *priv = snd_soc_card_get_drvdata(runtime->card);
|
|
struct snd_soc_dai *dai = asoc_rtd_to_codec(runtime, 0);
|
|
struct sof_hdmi_pcm *pcm;
|
|
|
|
pcm = devm_kzalloc(runtime->card->dev, sizeof(*pcm), GFP_KERNEL);
|
|
if (!pcm)
|
|
return -ENOMEM;
|
|
|
|
/* dai_link id is 1:1 mapped to the PCM device */
|
|
pcm->device = runtime->dai_link->id;
|
|
pcm->codec_dai = dai;
|
|
|
|
list_add_tail(&pcm->head, &priv->hdmi_pcm_list);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sof_es8316_init(struct snd_soc_pcm_runtime *runtime)
|
|
{
|
|
struct snd_soc_component *codec = asoc_rtd_to_codec(runtime, 0)->component;
|
|
struct snd_soc_card *card = runtime->card;
|
|
struct sof_es8336_private *priv = snd_soc_card_get_drvdata(card);
|
|
const struct snd_soc_dapm_route *custom_map;
|
|
int num_routes;
|
|
int ret;
|
|
|
|
card->dapm.idle_bias_off = true;
|
|
|
|
if (quirk & SOC_ES8336_HEADSET_MIC1) {
|
|
custom_map = sof_es8316_headset_mic1_map;
|
|
num_routes = ARRAY_SIZE(sof_es8316_headset_mic1_map);
|
|
} else {
|
|
custom_map = sof_es8316_headset_mic2_map;
|
|
num_routes = ARRAY_SIZE(sof_es8316_headset_mic2_map);
|
|
}
|
|
|
|
ret = snd_soc_dapm_add_routes(&card->dapm, custom_map, num_routes);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = snd_soc_card_jack_new_pins(card, "Headset",
|
|
SND_JACK_HEADSET | SND_JACK_BTN_0,
|
|
&priv->jack, sof_es8316_jack_pins,
|
|
ARRAY_SIZE(sof_es8316_jack_pins));
|
|
if (ret) {
|
|
dev_err(card->dev, "jack creation failed %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
snd_jack_set_key(priv->jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
|
|
|
|
snd_soc_component_set_jack(codec, &priv->jack, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sof_es8316_exit(struct snd_soc_pcm_runtime *rtd)
|
|
{
|
|
struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
|
|
|
|
snd_soc_component_set_jack(component, NULL, NULL);
|
|
}
|
|
|
|
static int sof_es8336_quirk_cb(const struct dmi_system_id *id)
|
|
{
|
|
quirk = (unsigned long)id->driver_data;
|
|
|
|
if (quirk & SOF_ES8336_HEADPHONE_GPIO) {
|
|
if (quirk & SOF_ES8336_SPEAKERS_EN_GPIO1_QUIRK)
|
|
gpio_mapping = acpi_enable_both_gpios;
|
|
else
|
|
gpio_mapping = acpi_enable_both_gpios_rev_order;
|
|
} else if (quirk & SOF_ES8336_SPEAKERS_EN_GPIO1_QUIRK) {
|
|
gpio_mapping = acpi_speakers_enable_gpio1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* this table should only be used to add GPIO or jack-detection quirks
|
|
* that cannot be detected from ACPI tables. The SSP and DMIC
|
|
* information are providing by the platform driver and are aligned
|
|
* with the topology used.
|
|
*
|
|
* If the GPIO support is missing, the quirk parameter can be used to
|
|
* enable speakers. In that case it's recommended to keep the SSP and DMIC
|
|
* information consistent, overriding the SSP and DMIC can only be done
|
|
* if the topology file is modified as well.
|
|
*/
|
|
static const struct dmi_system_id sof_es8336_quirk_table[] = {
|
|
{
|
|
.callback = sof_es8336_quirk_cb,
|
|
.matches = {
|
|
DMI_MATCH(DMI_SYS_VENDOR, "IP3 tech"),
|
|
DMI_MATCH(DMI_BOARD_NAME, "WN1"),
|
|
},
|
|
.driver_data = (void *)(SOF_ES8336_SPEAKERS_EN_GPIO1_QUIRK)
|
|
},
|
|
{
|
|
.callback = sof_es8336_quirk_cb,
|
|
.matches = {
|
|
DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI"),
|
|
DMI_MATCH(DMI_BOARD_NAME, "BOHB-WAX9-PCB-B2"),
|
|
},
|
|
.driver_data = (void *)(SOF_ES8336_HEADPHONE_GPIO |
|
|
SOC_ES8336_HEADSET_MIC1)
|
|
},
|
|
{}
|
|
};
|
|
|
|
static int sof_es8336_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
|
struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
|
|
const int sysclk = 19200000;
|
|
int ret;
|
|
|
|
ret = snd_soc_dai_set_sysclk(codec_dai, 1, sysclk, SND_SOC_CLOCK_OUT);
|
|
if (ret < 0) {
|
|
dev_err(rtd->dev, "%s, Failed to set ES8336 SYSCLK: %d\n",
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* machine stream operations */
|
|
static struct snd_soc_ops sof_es8336_ops = {
|
|
.hw_params = sof_es8336_hw_params,
|
|
};
|
|
|
|
static struct snd_soc_dai_link_component platform_component[] = {
|
|
{
|
|
/* name might be overridden during probe */
|
|
.name = "0000:00:1f.3"
|
|
}
|
|
};
|
|
|
|
SND_SOC_DAILINK_DEF(es8336_codec,
|
|
DAILINK_COMP_ARRAY(COMP_CODEC("i2c-ESSX8336:00", "ES8316 HiFi")));
|
|
|
|
static struct snd_soc_dai_link_component dmic_component[] = {
|
|
{
|
|
.name = "dmic-codec",
|
|
.dai_name = "dmic-hifi",
|
|
}
|
|
};
|
|
|
|
static int sof_es8336_late_probe(struct snd_soc_card *card)
|
|
{
|
|
struct sof_es8336_private *priv = snd_soc_card_get_drvdata(card);
|
|
struct sof_hdmi_pcm *pcm;
|
|
|
|
if (list_empty(&priv->hdmi_pcm_list))
|
|
return -ENOENT;
|
|
|
|
pcm = list_first_entry(&priv->hdmi_pcm_list, struct sof_hdmi_pcm, head);
|
|
|
|
return hda_dsp_hdmi_build_controls(card, pcm->codec_dai->component);
|
|
}
|
|
|
|
/* SoC card */
|
|
static struct snd_soc_card sof_es8336_card = {
|
|
.name = "essx8336", /* sof- prefix added automatically */
|
|
.owner = THIS_MODULE,
|
|
.dapm_widgets = sof_es8316_widgets,
|
|
.num_dapm_widgets = ARRAY_SIZE(sof_es8316_widgets),
|
|
.dapm_routes = sof_es8316_audio_map,
|
|
.num_dapm_routes = ARRAY_SIZE(sof_es8316_audio_map),
|
|
.controls = sof_es8316_controls,
|
|
.num_controls = ARRAY_SIZE(sof_es8316_controls),
|
|
.fully_routed = true,
|
|
.late_probe = sof_es8336_late_probe,
|
|
.num_links = 1,
|
|
};
|
|
|
|
static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev,
|
|
int ssp_codec,
|
|
int dmic_be_num,
|
|
int hdmi_num)
|
|
{
|
|
struct snd_soc_dai_link_component *cpus;
|
|
struct snd_soc_dai_link *links;
|
|
struct snd_soc_dai_link_component *idisp_components;
|
|
int hdmi_id_offset = 0;
|
|
int id = 0;
|
|
int i;
|
|
|
|
links = devm_kcalloc(dev, sof_es8336_card.num_links,
|
|
sizeof(struct snd_soc_dai_link), GFP_KERNEL);
|
|
cpus = devm_kcalloc(dev, sof_es8336_card.num_links,
|
|
sizeof(struct snd_soc_dai_link_component), GFP_KERNEL);
|
|
if (!links || !cpus)
|
|
goto devm_err;
|
|
|
|
/* codec SSP */
|
|
links[id].name = devm_kasprintf(dev, GFP_KERNEL,
|
|
"SSP%d-Codec", ssp_codec);
|
|
if (!links[id].name)
|
|
goto devm_err;
|
|
|
|
links[id].id = id;
|
|
links[id].codecs = es8336_codec;
|
|
links[id].num_codecs = ARRAY_SIZE(es8336_codec);
|
|
links[id].platforms = platform_component;
|
|
links[id].num_platforms = ARRAY_SIZE(platform_component);
|
|
links[id].init = sof_es8316_init;
|
|
links[id].exit = sof_es8316_exit;
|
|
links[id].ops = &sof_es8336_ops;
|
|
links[id].nonatomic = true;
|
|
links[id].dpcm_playback = 1;
|
|
links[id].dpcm_capture = 1;
|
|
links[id].no_pcm = 1;
|
|
links[id].cpus = &cpus[id];
|
|
links[id].num_cpus = 1;
|
|
|
|
links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL,
|
|
"SSP%d Pin",
|
|
ssp_codec);
|
|
if (!links[id].cpus->dai_name)
|
|
goto devm_err;
|
|
|
|
id++;
|
|
|
|
/* dmic */
|
|
if (dmic_be_num > 0) {
|
|
/* at least we have dmic01 */
|
|
links[id].name = "dmic01";
|
|
links[id].cpus = &cpus[id];
|
|
links[id].cpus->dai_name = "DMIC01 Pin";
|
|
links[id].init = dmic_init;
|
|
if (dmic_be_num > 1) {
|
|
/* set up 2 BE links at most */
|
|
links[id + 1].name = "dmic16k";
|
|
links[id + 1].cpus = &cpus[id + 1];
|
|
links[id + 1].cpus->dai_name = "DMIC16k Pin";
|
|
dmic_be_num = 2;
|
|
}
|
|
} else {
|
|
/* HDMI dai link starts at 3 according to current topology settings */
|
|
hdmi_id_offset = 2;
|
|
}
|
|
|
|
for (i = 0; i < dmic_be_num; i++) {
|
|
links[id].id = id;
|
|
links[id].num_cpus = 1;
|
|
links[id].codecs = dmic_component;
|
|
links[id].num_codecs = ARRAY_SIZE(dmic_component);
|
|
links[id].platforms = platform_component;
|
|
links[id].num_platforms = ARRAY_SIZE(platform_component);
|
|
links[id].ignore_suspend = 1;
|
|
links[id].dpcm_capture = 1;
|
|
links[id].no_pcm = 1;
|
|
|
|
id++;
|
|
}
|
|
|
|
/* HDMI */
|
|
if (hdmi_num > 0) {
|
|
idisp_components = devm_kzalloc(dev,
|
|
sizeof(struct snd_soc_dai_link_component) *
|
|
hdmi_num, GFP_KERNEL);
|
|
if (!idisp_components)
|
|
goto devm_err;
|
|
}
|
|
|
|
for (i = 1; i <= hdmi_num; i++) {
|
|
links[id].name = devm_kasprintf(dev, GFP_KERNEL,
|
|
"iDisp%d", i);
|
|
if (!links[id].name)
|
|
goto devm_err;
|
|
|
|
links[id].id = id + hdmi_id_offset;
|
|
links[id].cpus = &cpus[id];
|
|
links[id].num_cpus = 1;
|
|
links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL,
|
|
"iDisp%d Pin", i);
|
|
if (!links[id].cpus->dai_name)
|
|
goto devm_err;
|
|
|
|
idisp_components[i - 1].name = "ehdaudio0D2";
|
|
idisp_components[i - 1].dai_name = devm_kasprintf(dev,
|
|
GFP_KERNEL,
|
|
"intel-hdmi-hifi%d",
|
|
i);
|
|
if (!idisp_components[i - 1].dai_name)
|
|
goto devm_err;
|
|
|
|
links[id].codecs = &idisp_components[i - 1];
|
|
links[id].num_codecs = 1;
|
|
links[id].platforms = platform_component;
|
|
links[id].num_platforms = ARRAY_SIZE(platform_component);
|
|
links[id].init = sof_hdmi_init;
|
|
links[id].dpcm_playback = 1;
|
|
links[id].no_pcm = 1;
|
|
|
|
id++;
|
|
}
|
|
|
|
return links;
|
|
|
|
devm_err:
|
|
return NULL;
|
|
}
|
|
|
|
static char soc_components[30];
|
|
|
|
/* i2c-<HID>:00 with HID being 8 chars */
|
|
static char codec_name[SND_ACPI_I2C_ID_LEN];
|
|
|
|
static int sof_es8336_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct snd_soc_card *card;
|
|
struct snd_soc_acpi_mach *mach = pdev->dev.platform_data;
|
|
struct property_entry props[MAX_NO_PROPS] = {};
|
|
struct sof_es8336_private *priv;
|
|
struct fwnode_handle *fwnode;
|
|
struct acpi_device *adev;
|
|
struct snd_soc_dai_link *dai_links;
|
|
struct device *codec_dev;
|
|
unsigned int cnt = 0;
|
|
int dmic_be_num = 0;
|
|
int hdmi_num = 3;
|
|
int ret;
|
|
|
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
card = &sof_es8336_card;
|
|
card->dev = dev;
|
|
|
|
/* check GPIO DMI quirks */
|
|
dmi_check_system(sof_es8336_quirk_table);
|
|
|
|
if (!mach->mach_params.i2s_link_mask) {
|
|
dev_warn(dev, "No I2S link information provided, using SSP0. This may need to be modified with the quirk module parameter\n");
|
|
} else {
|
|
/*
|
|
* Set configuration based on platform NHLT.
|
|
* In this machine driver, we can only support one SSP for the
|
|
* ES8336 link, the else-if below are intentional.
|
|
* In some cases multiple SSPs can be reported by NHLT, starting MSB-first
|
|
* seems to pick the right connection.
|
|
*/
|
|
unsigned long ssp = 0;
|
|
|
|
if (mach->mach_params.i2s_link_mask & BIT(2))
|
|
ssp = SOF_ES8336_SSP_CODEC(2);
|
|
else if (mach->mach_params.i2s_link_mask & BIT(1))
|
|
ssp = SOF_ES8336_SSP_CODEC(1);
|
|
else if (mach->mach_params.i2s_link_mask & BIT(0))
|
|
ssp = SOF_ES8336_SSP_CODEC(0);
|
|
|
|
quirk |= ssp;
|
|
}
|
|
|
|
if (mach->mach_params.dmic_num)
|
|
quirk |= SOF_ES8336_ENABLE_DMIC;
|
|
|
|
if (quirk_override != -1) {
|
|
dev_info(dev, "Overriding quirk 0x%lx => 0x%x\n",
|
|
quirk, quirk_override);
|
|
quirk = quirk_override;
|
|
}
|
|
log_quirks(dev);
|
|
|
|
if (quirk & SOF_ES8336_ENABLE_DMIC)
|
|
dmic_be_num = 2;
|
|
|
|
sof_es8336_card.num_links += dmic_be_num + hdmi_num;
|
|
dai_links = sof_card_dai_links_create(dev,
|
|
SOF_ES8336_SSP_CODEC(quirk),
|
|
dmic_be_num, hdmi_num);
|
|
if (!dai_links)
|
|
return -ENOMEM;
|
|
|
|
sof_es8336_card.dai_link = dai_links;
|
|
|
|
/* fixup codec name based on HID */
|
|
adev = acpi_dev_get_first_match_dev(mach->id, NULL, -1);
|
|
if (adev) {
|
|
snprintf(codec_name, sizeof(codec_name),
|
|
"i2c-%s", acpi_dev_name(adev));
|
|
put_device(&adev->dev);
|
|
dai_links[0].codecs->name = codec_name;
|
|
|
|
/* also fixup codec dai name if relevant */
|
|
if (!strncmp(mach->id, "ESSX8326", SND_ACPI_I2C_ID_LEN))
|
|
dai_links[0].codecs->dai_name = "ES8326 HiFi";
|
|
} else {
|
|
dev_err(dev, "Error cannot find '%s' dev\n", mach->id);
|
|
return -ENXIO;
|
|
}
|
|
|
|
ret = snd_soc_fixup_dai_links_platform_name(&sof_es8336_card,
|
|
mach->mach_params.platform);
|
|
if (ret)
|
|
return ret;
|
|
|
|
codec_dev = acpi_get_first_physical_node(adev);
|
|
if (!codec_dev)
|
|
return -EPROBE_DEFER;
|
|
priv->codec_dev = get_device(codec_dev);
|
|
|
|
if (quirk & SOF_ES8336_JD_INVERTED)
|
|
props[cnt++] = PROPERTY_ENTRY_BOOL("everest,jack-detect-inverted");
|
|
|
|
if (cnt) {
|
|
fwnode = fwnode_create_software_node(props, NULL);
|
|
if (IS_ERR(fwnode)) {
|
|
put_device(codec_dev);
|
|
return PTR_ERR(fwnode);
|
|
}
|
|
|
|
ret = device_add_software_node(codec_dev, to_software_node(fwnode));
|
|
|
|
fwnode_handle_put(fwnode);
|
|
|
|
if (ret) {
|
|
put_device(codec_dev);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* get speaker enable GPIO */
|
|
ret = devm_acpi_dev_add_driver_gpios(codec_dev, gpio_mapping);
|
|
if (ret)
|
|
dev_warn(codec_dev, "unable to add GPIO mapping table\n");
|
|
|
|
priv->gpio_speakers = gpiod_get_optional(codec_dev, "speakers-enable", GPIOD_OUT_LOW);
|
|
if (IS_ERR(priv->gpio_speakers)) {
|
|
ret = dev_err_probe(dev, PTR_ERR(priv->gpio_speakers),
|
|
"could not get speakers-enable GPIO\n");
|
|
goto err_put_codec;
|
|
}
|
|
|
|
priv->gpio_headphone = gpiod_get_optional(codec_dev, "headphone-enable", GPIOD_OUT_LOW);
|
|
if (IS_ERR(priv->gpio_headphone)) {
|
|
ret = dev_err_probe(dev, PTR_ERR(priv->gpio_headphone),
|
|
"could not get headphone-enable GPIO\n");
|
|
goto err_put_codec;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&priv->hdmi_pcm_list);
|
|
|
|
snd_soc_card_set_drvdata(card, priv);
|
|
|
|
if (mach->mach_params.dmic_num > 0) {
|
|
snprintf(soc_components, sizeof(soc_components),
|
|
"cfg-dmics:%d", mach->mach_params.dmic_num);
|
|
card->components = soc_components;
|
|
}
|
|
|
|
ret = devm_snd_soc_register_card(dev, card);
|
|
if (ret) {
|
|
gpiod_put(priv->gpio_speakers);
|
|
dev_err(dev, "snd_soc_register_card failed: %d\n", ret);
|
|
goto err_put_codec;
|
|
}
|
|
platform_set_drvdata(pdev, &sof_es8336_card);
|
|
return 0;
|
|
|
|
err_put_codec:
|
|
device_remove_software_node(priv->codec_dev);
|
|
put_device(codec_dev);
|
|
return ret;
|
|
}
|
|
|
|
static int sof_es8336_remove(struct platform_device *pdev)
|
|
{
|
|
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
|
struct sof_es8336_private *priv = snd_soc_card_get_drvdata(card);
|
|
|
|
gpiod_put(priv->gpio_speakers);
|
|
device_remove_software_node(priv->codec_dev);
|
|
put_device(priv->codec_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver sof_es8336_driver = {
|
|
.driver = {
|
|
.name = "sof-essx8336",
|
|
.pm = &snd_soc_pm_ops,
|
|
},
|
|
.probe = sof_es8336_probe,
|
|
.remove = sof_es8336_remove,
|
|
};
|
|
module_platform_driver(sof_es8336_driver);
|
|
|
|
MODULE_DESCRIPTION("ASoC Intel(R) SOF + ES8336 Machine driver");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS("platform:sof-essx8336");
|
|
MODULE_IMPORT_NS(SND_SOC_INTEL_HDA_DSP_COMMON);
|