linux-stable/sound/soc/samsung/lowland.c
Alper Nebi Yasak c9d3401844
ASoC: samsung: lowland: Split Line Out jack kcontrol from Headphone
This driver has correctly mapped jack kcontrols for Headphone and
Headset Mic. However, it is also mapping Line Out jack detection events
to the Headphone kcontrol.

The WM5100 codec used here can distinguish Line Out connections from
Headphone connections. Decouple the two, expose Line Out to userspace as
an independent kcontrol and add the necessary widget.

Signed-off-by: Alper Nebi Yasak <alpernebiyasak@gmail.com>
Link: https://lore.kernel.org/r/20230802175737.263412-27-alpernebiyasak@gmail.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2023-08-07 14:33:08 +01:00

213 lines
5.3 KiB
C

// SPDX-License-Identifier: GPL-2.0+
//
// Lowland audio support
//
// Copyright 2011 Wolfson Microelectronics
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/jack.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include "../codecs/wm5100.h"
#include "../codecs/wm9081.h"
#define MCLK1_RATE (44100 * 512)
#define CLKOUT_RATE (44100 * 256)
static struct snd_soc_jack lowland_headset;
/* Headset jack detection DAPM pins */
static struct snd_soc_jack_pin lowland_headset_pins[] = {
{
.pin = "Headphone",
.mask = SND_JACK_HEADPHONE,
},
{
.pin = "Headset Mic",
.mask = SND_JACK_MICROPHONE,
},
{
.pin = "Line Out",
.mask = SND_JACK_LINEOUT,
},
};
static int lowland_wm5100_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
int ret;
ret = snd_soc_component_set_sysclk(component, WM5100_CLK_SYSCLK,
WM5100_CLKSRC_MCLK1, MCLK1_RATE,
SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to set SYSCLK clock source: %d\n", ret);
return ret;
}
/* Clock OPCLK, used by the other audio components. */
ret = snd_soc_component_set_sysclk(component, WM5100_CLK_OPCLK, 0,
CLKOUT_RATE, 0);
if (ret < 0) {
pr_err("Failed to set OPCLK rate: %d\n", ret);
return ret;
}
ret = snd_soc_card_jack_new_pins(rtd->card, "Headset",
SND_JACK_LINEOUT | SND_JACK_HEADSET |
SND_JACK_BTN_0,
&lowland_headset, lowland_headset_pins,
ARRAY_SIZE(lowland_headset_pins));
if (ret)
return ret;
wm5100_detect(component, &lowland_headset);
return 0;
}
static int lowland_wm9081_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
snd_soc_dapm_nc_pin(&rtd->card->dapm, "LINEOUT");
/* At any time the WM9081 is active it will have this clock */
return snd_soc_component_set_sysclk(component, WM9081_SYSCLK_MCLK, 0,
CLKOUT_RATE, 0);
}
static const struct snd_soc_pcm_stream sub_params = {
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rate_min = 44100,
.rate_max = 44100,
.channels_min = 2,
.channels_max = 2,
};
SND_SOC_DAILINK_DEFS(cpu,
DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm5100.1-001a", "wm5100-aif1")),
DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
SND_SOC_DAILINK_DEFS(baseband,
DAILINK_COMP_ARRAY(COMP_CPU("wm5100-aif2")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm1250-ev1.1-0027", "wm1250-ev1")));
SND_SOC_DAILINK_DEFS(speaker,
DAILINK_COMP_ARRAY(COMP_CPU("wm5100-aif3")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm9081.1-006c", "wm9081-hifi")));
static struct snd_soc_dai_link lowland_dai[] = {
{
.name = "CPU",
.stream_name = "CPU",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
.init = lowland_wm5100_init,
SND_SOC_DAILINK_REG(cpu),
},
{
.name = "Baseband",
.stream_name = "Baseband",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
.ignore_suspend = 1,
SND_SOC_DAILINK_REG(baseband),
},
{
.name = "Sub Speaker",
.stream_name = "Sub Speaker",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
.ignore_suspend = 1,
.c2c_params = &sub_params,
.num_c2c_params = 1,
.init = lowland_wm9081_init,
SND_SOC_DAILINK_REG(speaker),
},
};
static struct snd_soc_codec_conf lowland_codec_conf[] = {
{
.dlc = COMP_CODEC_CONF("wm9081.1-006c"),
.name_prefix = "Sub",
},
};
static const struct snd_kcontrol_new controls[] = {
SOC_DAPM_PIN_SWITCH("Main Speaker"),
SOC_DAPM_PIN_SWITCH("Main DMIC"),
SOC_DAPM_PIN_SWITCH("Main AMIC"),
SOC_DAPM_PIN_SWITCH("WM1250 Input"),
SOC_DAPM_PIN_SWITCH("WM1250 Output"),
SOC_DAPM_PIN_SWITCH("Headphone"),
SOC_DAPM_PIN_SWITCH("Line Out"),
};
static const struct snd_soc_dapm_widget widgets[] = {
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_LINE("Line Out", NULL),
SND_SOC_DAPM_SPK("Main Speaker", NULL),
SND_SOC_DAPM_MIC("Main AMIC", NULL),
SND_SOC_DAPM_MIC("Main DMIC", NULL),
};
static const struct snd_soc_dapm_route audio_paths[] = {
{ "Sub IN1", NULL, "HPOUT2L" },
{ "Sub IN2", NULL, "HPOUT2R" },
{ "Main Speaker", NULL, "Sub SPKN" },
{ "Main Speaker", NULL, "Sub SPKP" },
{ "Main Speaker", NULL, "SPKDAT1" },
};
static struct snd_soc_card lowland = {
.name = "Lowland",
.owner = THIS_MODULE,
.dai_link = lowland_dai,
.num_links = ARRAY_SIZE(lowland_dai),
.codec_conf = lowland_codec_conf,
.num_configs = ARRAY_SIZE(lowland_codec_conf),
.controls = controls,
.num_controls = ARRAY_SIZE(controls),
.dapm_widgets = widgets,
.num_dapm_widgets = ARRAY_SIZE(widgets),
.dapm_routes = audio_paths,
.num_dapm_routes = ARRAY_SIZE(audio_paths),
};
static int lowland_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &lowland;
int ret;
card->dev = &pdev->dev;
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret)
dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n");
return ret;
}
static struct platform_driver lowland_driver = {
.driver = {
.name = "lowland",
.pm = &snd_soc_pm_ops,
},
.probe = lowland_probe,
};
module_platform_driver(lowland_driver);
MODULE_DESCRIPTION("Lowland audio support");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:lowland");