1
0
Fork 0

MLK-11479-06 ASoC: fsl: Add WM8962 jack detecting support

cherry-pick below patch from v3.14.y:
ENGR00277715-3 ASoC: fsl: Add WM8962 jack detecting support

There're two GPIOs connected to the headphone jack and microphone jack,
thus add the states detection.

Reviewed-by: Wang Shengjiu <b02247@freescale.com>
Signed-off-by: Nicolin Chen <b42378@freescale.com>
5.4-rM2-2.2.x-imx-squashed
Nicolin Chen 2013-09-03 12:45:05 +08:00 committed by Dong Aisheng
parent 02bac8135e
commit be98d73d36
3 changed files with 262 additions and 2 deletions

View File

@ -31,6 +31,12 @@ Required properties:
Note: The AUDMUX port numbering should start at 1, which is consistent with
hardware manual.
Optional properties:
- hp-det-gpios : The gpio pin to detect plug in/out event that happens to
Headphone jack.
- mic-det-gpios: The gpio pin to detect plug in/out event that happens to
Microphone jack.
Example:
sound {
@ -50,4 +56,6 @@ sound {
"DMICDAT", "DMIC";
mux-int-port = <2>;
mux-ext-port = <3>;
hp-det-gpios = <&gpio7 8 1>;
mic-det-gpios = <&gpio1 9 1>;
};

View File

@ -13,6 +13,14 @@ Optional properties:
of R51 (Class D Control 2) gets set, indicating that the speaker is
in mono mode.
- amic-mono: This is a boolean property. If present, indicating that the
analog micphone is hardware mono input, the driver would enable monomix
for it.
- dmic-mono: This is a boolean property. If present, indicating that the
digital micphone is hardware mono input, the driver would enable monomix
for it.
- mic-cfg : Default register value for R48 (Additional Control 4).
If absent, the default should be the register default.

View File

@ -16,9 +16,12 @@
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/i2c.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/clk.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include <sound/pcm_params.h>
#include <sound/soc-dapm.h>
#include <linux/pinctrl/consumer.h>
@ -37,12 +40,130 @@ struct imx_wm8962_data {
};
struct imx_priv {
int hp_gpio;
int hp_active_low;
int mic_gpio;
int mic_active_low;
bool amic_mono;
bool dmic_mono;
struct snd_soc_codec *codec;
struct platform_device *pdev;
struct snd_pcm_substream *first_stream;
struct snd_pcm_substream *second_stream;
};
static struct imx_priv card_priv;
static struct snd_soc_jack imx_hp_jack;
static struct snd_soc_jack_pin imx_hp_jack_pins[] = {
{
.pin = "Headphone Jack",
.mask = SND_JACK_HEADPHONE,
},
};
static struct snd_soc_jack_gpio imx_hp_jack_gpio = {
.name = "headphone detect",
.report = SND_JACK_HEADPHONE,
.debounce_time = 250,
.invert = 0,
};
static struct snd_soc_jack imx_mic_jack;
static struct snd_soc_jack_pin imx_mic_jack_pins[] = {
{
.pin = "AMIC",
.mask = SND_JACK_MICROPHONE,
},
};
static struct snd_soc_jack_gpio imx_mic_jack_gpio = {
.name = "microphone detect",
.report = SND_JACK_MICROPHONE,
.debounce_time = 250,
.invert = 0,
};
static int hpjack_status_check(void *data)
{
struct imx_priv *priv = &card_priv;
struct platform_device *pdev = priv->pdev;
char *envp[3], *buf;
int hp_status, ret;
if (!gpio_is_valid(priv->hp_gpio))
return 0;
hp_status = gpio_get_value(priv->hp_gpio) ? 1 : 0;
buf = kmalloc(32, GFP_ATOMIC);
if (!buf) {
dev_err(&pdev->dev, "%s kmalloc failed\n", __func__);
return -ENOMEM;
}
if (hp_status != priv->hp_active_low) {
snprintf(buf, 32, "STATE=%d", 2);
snd_soc_dapm_disable_pin(snd_soc_codec_get_dapm(priv->codec), "Ext Spk");
ret = imx_hp_jack_gpio.report;
} else {
snprintf(buf, 32, "STATE=%d", 0);
snd_soc_dapm_enable_pin(snd_soc_codec_get_dapm(priv->codec), "Ext Spk");
ret = 0;
}
envp[0] = "NAME=headphone";
envp[1] = buf;
envp[2] = NULL;
kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp);
kfree(buf);
return ret;
}
static int micjack_status_check(void *data)
{
struct imx_priv *priv = &card_priv;
struct platform_device *pdev = priv->pdev;
char *envp[3], *buf;
int mic_status, ret;
if (!gpio_is_valid(priv->mic_gpio))
return 0;
mic_status = gpio_get_value(priv->mic_gpio) ? 1 : 0;
if ((mic_status != priv->mic_active_low && priv->amic_mono)
|| (mic_status == priv->mic_active_low && priv->dmic_mono))
snd_soc_update_bits(priv->codec, WM8962_THREED1,
WM8962_ADC_MONOMIX_MASK, WM8962_ADC_MONOMIX);
else
snd_soc_update_bits(priv->codec, WM8962_THREED1,
WM8962_ADC_MONOMIX_MASK, 0);
buf = kmalloc(32, GFP_ATOMIC);
if (!buf) {
dev_err(&pdev->dev, "%s kmalloc failed\n", __func__);
return -ENOMEM;
}
if (mic_status != priv->mic_active_low) {
snprintf(buf, 32, "STATE=%d", 2);
snd_soc_dapm_disable_pin(snd_soc_codec_get_dapm(priv->codec), "DMIC");
ret = imx_mic_jack_gpio.report;
} else {
snprintf(buf, 32, "STATE=%d", 0);
snd_soc_dapm_enable_pin(snd_soc_codec_get_dapm(priv->codec), "DMIC");
ret = 0;
}
envp[0] = "NAME=microphone";
envp[1] = buf;
envp[2] = NULL;
kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp);
kfree(buf);
return ret;
}
static const struct snd_soc_dapm_widget imx_wm8962_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_SPK("Ext Spk", NULL),
@ -110,8 +231,6 @@ static int imx_hifi_hw_free(struct snd_pcm_substream *substream)
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct imx_priv *priv = &card_priv;
struct snd_soc_card *card = platform_get_drvdata(priv->pdev);
struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card);
struct device *dev = &priv->pdev->dev;
int ret;
@ -159,6 +278,92 @@ static struct snd_soc_ops imx_hifi_ops = {
.hw_free = imx_hifi_hw_free,
};
static int imx_wm8962_gpio_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_card *card = rtd->card;
struct snd_soc_codec *codec = rtd->codec;
struct imx_priv *priv = &card_priv;
priv->codec = codec;
if (gpio_is_valid(priv->hp_gpio)) {
imx_hp_jack_gpio.gpio = priv->hp_gpio;
imx_hp_jack_gpio.jack_status_check = hpjack_status_check;
snd_soc_card_jack_new(card, "Headphone Jack",
SND_JACK_HEADPHONE, &imx_hp_jack,
imx_hp_jack_pins, ARRAY_SIZE(imx_hp_jack_pins));
snd_soc_jack_add_gpios(&imx_hp_jack, 1, &imx_hp_jack_gpio);
}
if (gpio_is_valid(priv->mic_gpio)) {
imx_mic_jack_gpio.gpio = priv->mic_gpio;
imx_mic_jack_gpio.jack_status_check = micjack_status_check;
snd_soc_card_jack_new(card, "AMIC",
SND_JACK_MICROPHONE, &imx_mic_jack,
imx_mic_jack_pins, ARRAY_SIZE(imx_mic_jack_pins));
snd_soc_jack_add_gpios(&imx_mic_jack, 1, &imx_mic_jack_gpio);
} else if (priv->amic_mono || priv->dmic_mono) {
/*
* Permanent set monomix bit if only one microphone
* is present on the board while it needs monomix.
*/
snd_soc_update_bits(priv->codec, WM8962_THREED1,
WM8962_ADC_MONOMIX_MASK, WM8962_ADC_MONOMIX);
}
return 0;
}
static ssize_t headphone_show(struct device_driver *dev, char *buf)
{
struct imx_priv *priv = &card_priv;
int hp_status;
if (!gpio_is_valid(priv->hp_gpio)) {
strcpy(buf, "no detect gpio connected\n");
return strlen(buf);
}
/* Check if headphone is plugged in */
hp_status = gpio_get_value(priv->hp_gpio) ? 1 : 0;
if (hp_status != priv->hp_active_low)
strcpy(buf, "headphone\n");
else
strcpy(buf, "speaker\n");
return strlen(buf);
}
static DRIVER_ATTR_RO(headphone);
static ssize_t microphone_show(struct device_driver *dev, char *buf)
{
struct imx_priv *priv = &card_priv;
int mic_status;
if (!gpio_is_valid(priv->mic_gpio)) {
strcpy(buf, "no detect gpio connected\n");
return strlen(buf);
}
/* Check if analog microphone is plugged in */
mic_status = gpio_get_value(priv->mic_gpio) ? 1 : 0;
if (mic_status != priv->mic_active_low)
strcpy(buf, "amic\n");
else
strcpy(buf, "dmic\n");
return strlen(buf);
}
static DRIVER_ATTR_RO(microphone);
static int imx_wm8962_late_probe(struct snd_soc_card *card)
{
struct snd_soc_pcm_runtime *rtd;
@ -268,6 +473,14 @@ static int imx_wm8962_probe(struct platform_device *pdev)
data->clk_frequency = clk_get_rate(codec_clk);
clk_put(codec_clk);
priv->amic_mono = of_property_read_bool(codec_np, "amic-mono");
priv->dmic_mono = of_property_read_bool(codec_np, "dmic-mono");
priv->hp_gpio = of_get_named_gpio_flags(np, "hp-det-gpios", 0,
(enum of_gpio_flags *)&priv->hp_active_low);
priv->mic_gpio = of_get_named_gpio_flags(np, "mic-det-gpios", 0,
(enum of_gpio_flags *)&priv->mic_active_low);
data->dai.name = "HiFi";
data->dai.stream_name = "HiFi";
data->dai.codec_dai_name = "wm8962";
@ -275,6 +488,7 @@ static int imx_wm8962_probe(struct platform_device *pdev)
data->dai.cpu_dai_name = dev_name(&ssi_pdev->dev);
data->dai.platform_of_node = ssi_np;
data->dai.ops = &imx_hifi_ops;
data->dai.init = &imx_wm8962_gpio_init;
data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM;
@ -302,6 +516,27 @@ static int imx_wm8962_probe(struct platform_device *pdev)
goto fail;
}
if (gpio_is_valid(priv->hp_gpio)) {
ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone);
if (ret) {
dev_err(&pdev->dev, "create hp attr failed (%d)\n", ret);
goto fail_hp;
}
}
if (gpio_is_valid(priv->mic_gpio)) {
ret = driver_create_file(pdev->dev.driver, &driver_attr_microphone);
if (ret) {
dev_err(&pdev->dev, "create mic attr failed (%d)\n", ret);
goto fail_mic;
}
}
goto fail;
fail_mic:
driver_remove_file(pdev->dev.driver, &driver_attr_headphone);
fail_hp:
fail:
of_node_put(ssi_np);
of_node_put(codec_np);
@ -309,6 +544,14 @@ fail:
return ret;
}
static int imx_wm8962_remove(struct platform_device *pdev)
{
driver_remove_file(pdev->dev.driver, &driver_attr_microphone);
driver_remove_file(pdev->dev.driver, &driver_attr_headphone);
return 0;
}
static const struct of_device_id imx_wm8962_dt_ids[] = {
{ .compatible = "fsl,imx-audio-wm8962", },
{ /* sentinel */ }
@ -322,6 +565,7 @@ static struct platform_driver imx_wm8962_driver = {
.of_match_table = imx_wm8962_dt_ids,
},
.probe = imx_wm8962_probe,
.remove = imx_wm8962_remove,
};
module_platform_driver(imx_wm8962_driver);