ASoC: samsung: midas-audio: Add GPIO-based headset

Merge series from Artur Weber <aweber.kernel@gmail.com>:

Many of Samsung's Exynos 4 devices share the same midas-audio driver
to handle the codec setup. While most of these devices, including the
Midas itself, use the jack detection provided by the WM8994 driver,
other devices such as the Samsung Galaxy Tab 3 8.0 (lt01) use two GPIOs
and an ADC channel to determine jack insertion, the jack's type, and
button presses (for headsets with volume up/down/play buttons).

In the downstream kernel, this behavior is implemented in the sec-jack
driver[1], and the per-device settings are configured in *-jack.c files
in the mach folder (see e.g. the Tab 3's implementation[2]).

This patchset implements this mechanism in the midas_wm1811.c driver,
and adds new DTS options to allow for its configuration. It also
enables jack detection for the Samsung Galaxy Tab 3 8.0.

A very similar mechanism was already present in the aries_wm8994.c
driver[3]; this implementation heavily borrows from it, though there
are a few extra cleanups as well.
This commit is contained in:
Mark Brown 2024-05-29 16:32:22 +01:00
commit 52100401c1
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
3 changed files with 317 additions and 68 deletions

View File

@ -53,6 +53,9 @@ properties:
submic-bias-supply:
description: Supply for the micbias on the Sub microphone
headset-mic-bias-supply:
description: Supply for the micbias on the Headset microphone
fm-sel-gpios:
maxItems: 1
description: GPIO pin for FM selection
@ -61,6 +64,36 @@ properties:
maxItems: 1
description: GPIO pin for line out selection
headset-detect-gpios:
maxItems: 1
description: GPIO for detection of headset insertion
headset-key-gpios:
maxItems: 1
description: GPIO for detection of headset key press
io-channels:
maxItems: 1
description: IO channel to read micbias voltage for headset detection
io-channel-names:
const: headset-detect
samsung,headset-4pole-threshold-microvolt:
minItems: 2
maxItems: 2
description:
Array containing minimum and maximum IO channel value for 4-pole
(with microphone/button) headsets. If the IO channel value is
outside of this range, a 3-pole headset is assumed.
samsung,headset-button-threshold-microvolt:
minItems: 3
maxItems: 3
description: |
Array of minimum (inclusive) IO channel values for headset button
detection, in order: "Media", "Volume Up" and "Volume Down".
required:
- compatible
- cpu

View File

@ -140,7 +140,7 @@ config SND_SOC_SAMSUNG_ARIES_WM8994
config SND_SOC_SAMSUNG_MIDAS_WM1811
tristate "SoC I2S Audio support for Midas boards"
depends on SND_SOC_SAMSUNG
depends on SND_SOC_SAMSUNG && IIO
select SND_SAMSUNG_I2S
select SND_SOC_WM8994
help

View File

@ -7,10 +7,11 @@
#include <linux/clk.h>
#include <linux/gpio/consumer.h>
#include <linux/iio/consumer.h>
#include <linux/mfd/wm8994/registers.h>
#include <linux/input-event-codes.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regulator/consumer.h>
#include <sound/jack.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
@ -27,10 +28,11 @@
#define DEFAULT_FLL1_RATE 11289600U
struct midas_priv {
struct regulator *reg_mic_bias;
struct regulator *reg_submic_bias;
struct gpio_desc *gpio_fm_sel;
struct gpio_desc *gpio_lineout_sel;
struct gpio_desc *gpio_headset_detect;
struct gpio_desc *gpio_headset_key;
struct iio_channel *adc_headset_detect;
unsigned int fll1_rate;
struct snd_soc_jack headset_jack;
@ -47,6 +49,117 @@ static struct snd_soc_jack_pin headset_jack_pins[] = {
},
};
/*
* min_mv/max_mv values in this struct are set up based on DT values.
*/
static struct snd_soc_jack_zone headset_jack_zones[] = {
{ .jack_type = SND_JACK_HEADPHONE, },
{ .jack_type = SND_JACK_HEADSET, },
{ .jack_type = SND_JACK_HEADPHONE, },
};
/*
* This is used for manual detection in headset_key_check, we reuse the
* structure since it's convenient.
*
* min_mv/max_mv values in this struct are set up based on DT values.
*/
static struct snd_soc_jack_zone headset_key_zones[] = {
{ .jack_type = SND_JACK_BTN_0, }, /* Media */
{ .jack_type = SND_JACK_BTN_1, }, /* Volume Up */
{ .jack_type = SND_JACK_BTN_2, }, /* Volume Down */
};
static int headset_jack_check(void *data)
{
struct snd_soc_component *codec = data;
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(codec);
struct midas_priv *priv = snd_soc_card_get_drvdata(codec->card);
int adc, ret;
int jack_type = 0;
if (!gpiod_get_value_cansleep(priv->gpio_headset_detect))
return 0;
/* Enable headset mic bias regulator so that the ADC reading works */
ret = snd_soc_dapm_force_enable_pin(dapm, "headset-mic-bias");
if (ret < 0) {
pr_err("%s: Failed to enable headset mic bias regulator (%d), assuming headphones\n",
__func__, ret);
return SND_JACK_HEADPHONE;
}
snd_soc_dapm_sync(dapm);
/* Sleep for a small amount of time to get the value to stabilize */
msleep(20);
ret = iio_read_channel_processed(priv->adc_headset_detect, &adc);
if (ret) {
pr_err("%s: Failed to read ADC (%d), assuming headphones\n",
__func__, ret);
jack_type = SND_JACK_HEADPHONE;
goto out;
}
pr_debug("%s: ADC value is %d\n", __func__, adc);
jack_type = snd_soc_jack_get_type(&priv->headset_jack, adc);
out:
ret = snd_soc_dapm_disable_pin(dapm, "headset-mic-bias");
if (ret < 0)
pr_err("%s: Failed to disable headset mic bias regulator (%d)\n",
__func__, ret);
snd_soc_dapm_sync(dapm);
return jack_type;
}
static int headset_key_check(void *data)
{
struct snd_soc_component *codec = data;
struct midas_priv *priv = snd_soc_card_get_drvdata(codec->card);
int adc, i, ret;
if (!gpiod_get_value_cansleep(priv->gpio_headset_key))
return 0;
/* Filter out keypresses when 4 pole jack not detected */
if (!(priv->headset_jack.status & SND_JACK_MICROPHONE))
return 0;
ret = iio_read_channel_processed(priv->adc_headset_detect, &adc);
if (ret) {
pr_err("%s: Failed to read ADC (%d), can't detect key type\n",
__func__, ret);
return 0;
}
pr_debug("%s: ADC value is %d\n", __func__, adc);
for (i = 0; i < ARRAY_SIZE(headset_key_zones); i++) {
if (adc >= headset_key_zones[i].min_mv &&
adc <= headset_key_zones[i].max_mv) {
return headset_key_zones[i].jack_type;
}
}
return 0;
}
static struct snd_soc_jack_gpio headset_gpio[] = {
{
.name = "Headset Jack",
.report = SND_JACK_HEADSET,
.debounce_time = 150,
.jack_status_check = headset_jack_check,
},
{
.name = "Headset Key",
.report = SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2,
.debounce_time = 30,
.jack_status_check = headset_key_check,
},
};
static int midas_start_fll1(struct snd_soc_pcm_runtime *rtd, unsigned int rate)
{
struct snd_soc_card *card = rtd->card;
@ -169,38 +282,6 @@ static int midas_ext_spkmode(struct snd_soc_dapm_widget *w,
return ret;
}
static int midas_mic_bias(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_card *card = w->dapm->card;
struct midas_priv *priv = snd_soc_card_get_drvdata(card);
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
return regulator_enable(priv->reg_mic_bias);
case SND_SOC_DAPM_POST_PMD:
return regulator_disable(priv->reg_mic_bias);
}
return 0;
}
static int midas_submic_bias(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_card *card = w->dapm->card;
struct midas_priv *priv = snd_soc_card_get_drvdata(card);
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
return regulator_enable(priv->reg_submic_bias);
case SND_SOC_DAPM_POST_PMD:
return regulator_disable(priv->reg_submic_bias);
}
return 0;
}
static int midas_fm_set(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
@ -272,8 +353,19 @@ static const struct snd_soc_dapm_widget midas_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_MIC("Main Mic", midas_mic_bias),
SND_SOC_DAPM_MIC("Sub Mic", midas_submic_bias),
SND_SOC_DAPM_REGULATOR_SUPPLY("headset-mic-bias", 0, 0),
SND_SOC_DAPM_MIC("Main Mic", NULL),
SND_SOC_DAPM_REGULATOR_SUPPLY("mic-bias", 0, 0),
SND_SOC_DAPM_MIC("Sub Mic", NULL),
SND_SOC_DAPM_REGULATOR_SUPPLY("submic-bias", 0, 0),
};
/* Default routing; supplemented by audio-routing DT property */
static const struct snd_soc_dapm_route midas_dapm_routes[] = {
/* Bind microphones with their respective regulator supplies */
{"Main Mic", NULL, "mic-bias"},
{"Sub Mic", NULL, "submic-bias"},
{"Headset Mic", NULL, "headset-mic-bias"},
};
static int midas_set_bias_level(struct snd_soc_card *card,
@ -315,18 +407,67 @@ static int midas_late_probe(struct snd_soc_card *card)
return ret;
}
ret = snd_soc_card_jack_new_pins(card, "Headset",
SND_JACK_HEADSET | SND_JACK_MECHANICAL |
SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2 |
SND_JACK_BTN_3 | SND_JACK_BTN_4 | SND_JACK_BTN_5,
&priv->headset_jack,
headset_jack_pins,
ARRAY_SIZE(headset_jack_pins));
if (ret)
return ret;
if (!priv->gpio_headset_detect) {
ret = snd_soc_card_jack_new_pins(card, "Headset",
SND_JACK_HEADSET | SND_JACK_MECHANICAL |
SND_JACK_BTN_0 | SND_JACK_BTN_1 |
SND_JACK_BTN_2 | SND_JACK_BTN_3 |
SND_JACK_BTN_4 | SND_JACK_BTN_5,
&priv->headset_jack,
headset_jack_pins,
ARRAY_SIZE(headset_jack_pins));
if (ret)
return ret;
wm8958_mic_detect(aif1_dai->component, &priv->headset_jack,
NULL, NULL, NULL, NULL);
} else {
/* Some devices (n8000, t310) use a GPIO to detect the jack. */
ret = snd_soc_card_jack_new_pins(card, "Headset",
SND_JACK_HEADSET | SND_JACK_BTN_0 |
SND_JACK_BTN_1 | SND_JACK_BTN_2,
&priv->headset_jack,
headset_jack_pins,
ARRAY_SIZE(headset_jack_pins));
if (ret) {
dev_err(card->dev,
"Failed to set up headset pins: %d\n", ret);
return ret;
}
ret = snd_soc_jack_add_zones(&priv->headset_jack,
ARRAY_SIZE(headset_jack_zones),
headset_jack_zones);
if (ret) {
dev_err(card->dev,
"Failed to set up headset zones: %d\n", ret);
return ret;
}
headset_gpio[0].data = aif1_dai->component;
headset_gpio[0].desc = priv->gpio_headset_detect;
headset_gpio[1].data = aif1_dai->component;
headset_gpio[1].desc = priv->gpio_headset_key;
snd_jack_set_key(priv->headset_jack.jack,
SND_JACK_BTN_0, KEY_MEDIA);
snd_jack_set_key(priv->headset_jack.jack,
SND_JACK_BTN_1, KEY_VOLUMEUP);
snd_jack_set_key(priv->headset_jack.jack,
SND_JACK_BTN_2, KEY_VOLUMEDOWN);
ret = snd_soc_jack_add_gpios(&priv->headset_jack,
ARRAY_SIZE(headset_gpio),
headset_gpio);
if (ret)
dev_err(card->dev,
"Failed to set up headset jack GPIOs: %d\n",
ret);
return ret;
}
wm8958_mic_detect(aif1_dai->component, &priv->headset_jack,
NULL, NULL, NULL, NULL);
return 0;
}
@ -421,6 +562,8 @@ static struct snd_soc_card midas_card = {
.num_controls = ARRAY_SIZE(midas_controls),
.dapm_widgets = midas_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(midas_dapm_widgets),
.dapm_routes = midas_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(midas_dapm_routes),
.set_bias_level = midas_set_bias_level,
.late_probe = midas_late_probe,
@ -433,6 +576,9 @@ static int midas_probe(struct platform_device *pdev)
struct snd_soc_card *card = &midas_card;
struct device *dev = &pdev->dev;
static struct snd_soc_dai_link *dai_link;
enum iio_chan_type channel_type;
u32 fourpole_threshold[2];
u32 button_threshold[3];
struct midas_priv *priv;
int ret, i;
@ -443,29 +589,99 @@ static int midas_probe(struct platform_device *pdev)
snd_soc_card_set_drvdata(card, priv);
card->dev = dev;
priv->reg_mic_bias = devm_regulator_get(dev, "mic-bias");
if (IS_ERR(priv->reg_mic_bias)) {
dev_err(dev, "Failed to get mic bias regulator\n");
return PTR_ERR(priv->reg_mic_bias);
}
priv->reg_submic_bias = devm_regulator_get(dev, "submic-bias");
if (IS_ERR(priv->reg_submic_bias)) {
dev_err(dev, "Failed to get submic bias regulator\n");
return PTR_ERR(priv->reg_submic_bias);
}
priv->gpio_fm_sel = devm_gpiod_get_optional(dev, "fm-sel", GPIOD_OUT_HIGH);
if (IS_ERR(priv->gpio_fm_sel)) {
dev_err(dev, "Failed to get FM selection GPIO\n");
return PTR_ERR(priv->gpio_fm_sel);
}
if (IS_ERR(priv->gpio_fm_sel))
return dev_err_probe(dev, PTR_ERR(priv->gpio_fm_sel),
"Failed to get FM selection GPIO\n");
priv->gpio_lineout_sel = devm_gpiod_get_optional(dev, "lineout-sel",
GPIOD_OUT_HIGH);
if (IS_ERR(priv->gpio_lineout_sel)) {
dev_err(dev, "Failed to get line out selection GPIO\n");
return PTR_ERR(priv->gpio_lineout_sel);
if (IS_ERR(priv->gpio_lineout_sel))
return dev_err_probe(dev, PTR_ERR(priv->gpio_lineout_sel),
"Failed to get line out selection GPIO\n");
priv->gpio_headset_detect = devm_gpiod_get_optional(dev,
"headset-detect", GPIOD_IN);
if (IS_ERR(priv->gpio_headset_detect))
return dev_err_probe(dev, PTR_ERR(priv->gpio_headset_detect),
"Failed to get headset jack detect GPIO\n");
if (priv->gpio_headset_detect) {
priv->adc_headset_detect = devm_iio_channel_get(dev,
"headset-detect");
if (IS_ERR(priv->adc_headset_detect))
return dev_err_probe(dev,
PTR_ERR(priv->adc_headset_detect),
"Failed to get ADC channel\n");
ret = iio_get_channel_type(priv->adc_headset_detect,
&channel_type);
if (ret) {
dev_err(dev, "Failed to get ADC channel type\n");
return ret;
}
if (channel_type != IIO_VOLTAGE) {
dev_err(dev, "ADC channel is not voltage\n");
return ret;
}
priv->gpio_headset_key = devm_gpiod_get(dev, "headset-key",
GPIOD_IN);
if (IS_ERR(priv->gpio_headset_key))
return dev_err_probe(dev,
PTR_ERR(priv->gpio_headset_key),
"Failed to get headset key GPIO\n");
ret = of_property_read_u32_array(dev->of_node,
"samsung,headset-4pole-threshold-microvolt",
fourpole_threshold,
ARRAY_SIZE(fourpole_threshold));
if (ret) {
dev_err(dev, "Failed to get 4-pole jack detection threshold\n");
return ret;
}
if (fourpole_threshold[0] > fourpole_threshold[1]) {
dev_err(dev, "Invalid 4-pole jack detection threshold value\n");
return -EINVAL;
}
headset_jack_zones[0].max_mv = (fourpole_threshold[0]);
headset_jack_zones[1].min_mv = (fourpole_threshold[0] + 1);
headset_jack_zones[1].max_mv = (fourpole_threshold[1]);
headset_jack_zones[2].min_mv = (fourpole_threshold[1] + 1);
ret = of_property_read_u32_array(dev->of_node,
"samsung,headset-button-threshold-microvolt",
button_threshold,
ARRAY_SIZE(button_threshold));
if (ret) {
dev_err(dev, "Failed to get headset button detection threshold\n");
return ret;
}
if (button_threshold[0] > button_threshold[1] ||
button_threshold[1] > button_threshold[2]) {
dev_err(dev, "Invalid headset button detection threshold value\n");
return -EINVAL;
}
for (i = 0; i < 3; i++) {
if (i != 0 && button_threshold[i] <= 0) {
dev_err(dev, "Invalid headset button detection threshold value\n");
return -EINVAL;
}
headset_key_zones[i].min_mv = button_threshold[i];
if (i == 2)
headset_key_zones[i].max_mv = UINT_MAX;
else
headset_key_zones[i].max_mv = \
(button_threshold[i+1] - 1);
}
}
ret = snd_soc_of_parse_card_name(card, "model");