diff options
Diffstat (limited to 'sound/soc/samsung')
-rw-r--r-- | sound/soc/samsung/Kconfig | 2 | ||||
-rw-r--r-- | sound/soc/samsung/aries_wm8994.c | 2 | ||||
-rw-r--r-- | sound/soc/samsung/arndale.c | 2 | ||||
-rw-r--r-- | sound/soc/samsung/i2s.c | 2 | ||||
-rw-r--r-- | sound/soc/samsung/midas_wm1811.c | 348 | ||||
-rw-r--r-- | sound/soc/samsung/odroid.c | 2 | ||||
-rw-r--r-- | sound/soc/samsung/pcm.c | 2 | ||||
-rw-r--r-- | sound/soc/samsung/snow.c | 2 | ||||
-rw-r--r-- | sound/soc/samsung/spdif.c | 2 |
9 files changed, 290 insertions, 74 deletions
diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig index 93c2b1b08d0a..4b1ea7b2c796 100644 --- a/sound/soc/samsung/Kconfig +++ b/sound/soc/samsung/Kconfig @@ -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 diff --git a/sound/soc/samsung/aries_wm8994.c b/sound/soc/samsung/aries_wm8994.c index a548ac33dd94..01716df0c842 100644 --- a/sound/soc/samsung/aries_wm8994.c +++ b/sound/soc/samsung/aries_wm8994.c @@ -1,11 +1,11 @@ // SPDX-License-Identifier: GPL-2.0+ #include <linux/extcon.h> +#include <linux/gpio/consumer.h> #include <linux/iio/consumer.h> #include <linux/input-event-codes.h> #include <linux/mfd/wm8994/registers.h> #include <linux/module.h> #include <linux/of.h> -#include <linux/of_gpio.h> #include <linux/regulator/consumer.h> #include <sound/jack.h> #include <sound/pcm_params.h> diff --git a/sound/soc/samsung/arndale.c b/sound/soc/samsung/arndale.c index f02873b6ce7f..9619f550608c 100644 --- a/sound/soc/samsung/arndale.c +++ b/sound/soc/samsung/arndale.c @@ -207,7 +207,7 @@ static struct platform_driver arndale_audio_driver = { .of_match_table = arndale_audio_of_match, }, .probe = arndale_audio_probe, - .remove_new = arndale_audio_remove, + .remove = arndale_audio_remove, }; module_platform_driver(arndale_audio_driver); diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index 1bcabb114e29..8f6deb06e234 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -1741,7 +1741,7 @@ static const struct dev_pm_ops samsung_i2s_pm = { static struct platform_driver samsung_i2s_driver = { .probe = samsung_i2s_probe, - .remove_new = samsung_i2s_remove, + .remove = samsung_i2s_remove, .id_table = samsung_i2s_driver_ids, .driver = { .name = "samsung-i2s", diff --git a/sound/soc/samsung/midas_wm1811.c b/sound/soc/samsung/midas_wm1811.c index 0841e2e6f8ce..bbfe5fef59af 100644 --- a/sound/soc/samsung/midas_wm1811.c +++ b/sound/soc/samsung/midas_wm1811.c @@ -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) + 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 -EINVAL; + } + + 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"); diff --git a/sound/soc/samsung/odroid.c b/sound/soc/samsung/odroid.c index 110ae14dd7ea..ed865cc07e2e 100644 --- a/sound/soc/samsung/odroid.c +++ b/sound/soc/samsung/odroid.c @@ -341,7 +341,7 @@ static struct platform_driver odroid_audio_driver = { .pm = &snd_soc_pm_ops, }, .probe = odroid_audio_probe, - .remove_new = odroid_audio_remove, + .remove = odroid_audio_remove, }; module_platform_driver(odroid_audio_driver); diff --git a/sound/soc/samsung/pcm.c b/sound/soc/samsung/pcm.c index 573b2dee7f07..a03ba9374c2e 100644 --- a/sound/soc/samsung/pcm.c +++ b/sound/soc/samsung/pcm.c @@ -590,7 +590,7 @@ static void s3c_pcm_dev_remove(struct platform_device *pdev) static struct platform_driver s3c_pcm_driver = { .probe = s3c_pcm_dev_probe, - .remove_new = s3c_pcm_dev_remove, + .remove = s3c_pcm_dev_remove, .driver = { .name = "samsung-pcm", }, diff --git a/sound/soc/samsung/snow.c b/sound/soc/samsung/snow.c index aad0f9b4d4fc..4bbe7bcdb845 100644 --- a/sound/soc/samsung/snow.c +++ b/sound/soc/samsung/snow.c @@ -245,7 +245,7 @@ static struct platform_driver snow_driver = { .of_match_table = snow_of_match, }, .probe = snow_probe, - .remove_new = snow_remove, + .remove = snow_remove, }; module_platform_driver(snow_driver); diff --git a/sound/soc/samsung/spdif.c b/sound/soc/samsung/spdif.c index f44e3180e8d3..235d0063d1b3 100644 --- a/sound/soc/samsung/spdif.c +++ b/sound/soc/samsung/spdif.c @@ -476,7 +476,7 @@ static void spdif_remove(struct platform_device *pdev) static struct platform_driver samsung_spdif_driver = { .probe = spdif_probe, - .remove_new = spdif_remove, + .remove = spdif_remove, .driver = { .name = "samsung-spdif", }, |