1
0
Fork 0

MLK-12057 ASoC: imx-wm8960: add headphone/micphone/headset jack support

Add headphone/micphone/headset jack support for different board.

There are headphone detect gpio and microphone detect gpio which
can be configured from device tree.
If headphone and microphone using the same gpio for jack detect,
it suppose to be a headset and will register a headphone jack for it.
If headphone and microphone using different gpio for jack detect,
it suppose not to be a headset, and will register headphone jack
and microphone jack respectively.

Is't not appropriate to set the adc data output in machine driver.
It will not be compatibly when we change hardware connection.
wm8960 codec driver has added "ADC Data Output Select" kcontrol,
so that we can set the ADC data output from user space.

Signed-off-by: Zidan Wang <zidan.wang@freescale.com>
(cherry picked from commit 92f65b4bee51fabdfa3a3c191f511c2ec7cb18a1)
pull/10/head
Zidan Wang 2015-12-22 14:04:04 +08:00 committed by Jason Liu
parent 0b98afe67f
commit cc084747af
1 changed files with 151 additions and 145 deletions

View File

@ -40,10 +40,10 @@ struct imx_wm8960_data {
};
struct imx_priv {
int hp_set_gpio;
int hp_active_low;
struct snd_kcontrol *headset_kctl;
struct snd_soc_codec *codec;
enum of_gpio_flags hp_active_low;
enum of_gpio_flags mic_active_low;
bool is_headset_jack;
struct snd_kcontrol *headphone_kctl;
struct platform_device *pdev;
struct platform_device *asrc_pdev;
struct snd_card *snd_card;
@ -51,106 +51,102 @@ struct imx_priv {
static struct imx_priv card_priv;
static struct snd_soc_jack imx_hp_set;
static struct snd_soc_jack_pin imx_hp_set_pins[] = {
{
.pin = "Headset Jack",
.mask = SND_JACK_HEADSET,
},
static struct snd_soc_jack imx_hp_jack;
static struct snd_soc_jack_pin imx_hp_jack_pin = {
.pin = "Headphone Jack",
.mask = SND_JACK_HEADPHONE,
};
static struct snd_soc_jack_gpio imx_hp_set_gpio = {
.name = "headset detect",
.report = SND_JACK_HEADSET,
static struct snd_soc_jack_gpio imx_hp_jack_gpio = {
.name = "headphone detect",
.report = SND_JACK_HEADPHONE,
.debounce_time = 250,
.invert = 1,
.invert = 0,
};
static int hp_set_status_check(void *data)
static struct snd_soc_jack imx_mic_jack;
static struct snd_soc_jack_pin imx_mic_jack_pins = {
.pin = "Mic Jack",
.mask = SND_JACK_MICROPHONE,
};
static struct snd_soc_jack_gpio imx_mic_jack_gpio = {
.name = "mic detect",
.report = SND_JACK_MICROPHONE,
.debounce_time = 250,
.invert = 0,
};
static int hp_jack_status_check(void *data)
{
struct imx_priv *priv = &card_priv;
struct platform_device *pdev = priv->pdev;
char *envp[3], *buf;
struct snd_soc_jack *jack = data;
struct snd_soc_dapm_context *dapm = &jack->card->dapm;
int hp_status, ret;
if (!gpio_is_valid(priv->hp_set_gpio))
return 0;
hp_status = gpio_get_value(priv->hp_set_gpio);
buf = kmalloc(32, GFP_ATOMIC);
if (!buf) {
dev_err(&pdev->dev, "%s kmalloc failed\n", __func__);
return -ENOMEM;
}
hp_status = gpio_get_value(imx_hp_jack_gpio.gpio);
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");
snd_soc_dapm_disable_pin(snd_soc_codec_get_dapm(priv->codec), "Main MIC");
ret = imx_hp_set_gpio.report;
/*
* As the hp MIC only connect the input for left channel, we
* need to route it for right channel.
*/
snd_soc_update_bits(priv->codec, WM8960_ADDCTL1, 3<<2, 1<<2);
snd_kctl_jack_report(priv->snd_card, priv->headset_kctl, 1);
snd_soc_dapm_disable_pin(dapm, "Ext Spk");
if (priv->is_headset_jack) {
snd_soc_dapm_enable_pin(dapm, "Mic Jack");
snd_soc_dapm_disable_pin(dapm, "Main MIC");
}
ret = imx_hp_jack_gpio.report;
snd_kctl_jack_report(priv->snd_card, priv->headphone_kctl, 1);
} else {
snprintf(buf, 32, "STATE=%d", 0);
snd_soc_dapm_enable_pin(snd_soc_codec_get_dapm(priv->codec), "Ext Spk");
snd_soc_dapm_enable_pin(snd_soc_codec_get_dapm(priv->codec), "Main MIC");
snd_soc_dapm_enable_pin(dapm, "Ext Spk");
if (priv->is_headset_jack) {
snd_soc_dapm_disable_pin(dapm, "Mic Jack");
snd_soc_dapm_enable_pin(dapm, "Main MIC");
}
ret = 0;
/*
* As the Main MIC only connect the input for right channel,
* we need to route it for left channel.
*/
snd_soc_update_bits(priv->codec, WM8960_ADDCTL1, 3<<2, 2<<2);
snd_kctl_jack_report(priv->snd_card, priv->headset_kctl, 0);
snd_kctl_jack_report(priv->snd_card, priv->headphone_kctl, 0);
}
envp[0] = "NAME=headset";
envp[1] = buf;
envp[2] = NULL;
kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp);
kfree(buf);
return ret;
}
static int mic_jack_status_check(void *data)
{
struct imx_priv *priv = &card_priv;
struct snd_soc_jack *jack = data;
struct snd_soc_dapm_context *dapm = &jack->card->dapm;
int mic_status, ret;
mic_status = gpio_get_value(imx_mic_jack_gpio.gpio);
if (mic_status != priv->mic_active_low) {
snd_soc_dapm_disable_pin(dapm, "Main MIC");
ret = imx_mic_jack_gpio.report;
} else {
snd_soc_dapm_enable_pin(dapm, "Main MIC");
ret = 0;
}
return ret;
}
static const struct snd_soc_dapm_widget imx_wm8960_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headset Jack", NULL),
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_SPK("Ext Spk", NULL),
SND_SOC_DAPM_MIC("Hp MIC", NULL),
SND_SOC_DAPM_MIC("Mic Jack", NULL),
SND_SOC_DAPM_MIC("Main MIC", NULL),
};
static int imx_wm8960_gpio_init(struct snd_soc_card *card)
static int imx_wm8960_jack_init(struct snd_soc_card *card,
struct snd_soc_jack *jack, struct snd_soc_jack_pin *pin,
struct snd_soc_jack_gpio *gpio)
{
struct snd_soc_pcm_runtime *rtd = list_first_entry(
&card->rtd_list, struct snd_soc_pcm_runtime, list);
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_codec *codec = codec_dai->codec;
struct imx_priv *priv = &card_priv;
int ret;
priv->codec = codec;
if (gpio_is_valid(priv->hp_set_gpio)) {
imx_hp_set_gpio.gpio = priv->hp_set_gpio;
imx_hp_set_gpio.jack_status_check = hp_set_status_check;
ret = snd_soc_card_jack_new(card, "Headset Jack",
SND_JACK_HEADSET, &imx_hp_set,
imx_hp_set_pins, ARRAY_SIZE(imx_hp_set_pins));
if (ret)
return ret;
ret = snd_soc_jack_add_gpios(&imx_hp_set, 1,
&imx_hp_set_gpio);
if (ret)
return ret;
ret = snd_soc_card_jack_new(card, pin->pin, pin->mask, jack, pin, 1);
if (ret) {
return ret;
}
ret = snd_soc_jack_add_gpios(jack, 1, gpio);
if (ret)
return ret;
return 0;
}
@ -159,13 +155,8 @@ 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_set_gpio)) {
strcpy(buf, "no detect gpio connected\n");
return strlen(buf);
}
/* Check if headphone is plugged in */
hp_status = gpio_get_value(priv->hp_set_gpio);
hp_status = gpio_get_value(imx_hp_jack_gpio.gpio);
if (hp_status != priv->hp_active_low)
strcpy(buf, "Headphone\n");
@ -178,18 +169,13 @@ static ssize_t headphone_show(struct device_driver *dev, char *buf)
static ssize_t micphone_show(struct device_driver *dev, char *buf)
{
struct imx_priv *priv = &card_priv;
int hp_status;
if (!gpio_is_valid(priv->hp_set_gpio)) {
strcpy(buf, "no detect gpio connected\n");
return strlen(buf);
}
int mic_status;
/* Check if headphone is plugged in */
hp_status = gpio_get_value(priv->hp_set_gpio);
mic_status = gpio_get_value(imx_mic_jack_gpio.gpio);
if (hp_status != priv->hp_active_low)
strcpy(buf, "Hp MIC\n");
if (mic_status != priv->mic_active_low)
strcpy(buf, "Mic Jack\n");
else
strcpy(buf, "Main MIC\n");
@ -198,39 +184,6 @@ static ssize_t micphone_show(struct device_driver *dev, char *buf)
static DRIVER_ATTR_RO(headphone);
static DRIVER_ATTR_RO(micphone);
static void wm8960_init(struct snd_soc_card *card)
{
struct snd_soc_pcm_runtime *rtd = list_first_entry(
&card->rtd_list, struct snd_soc_pcm_runtime, list);
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_codec *codec = codec_dai->codec;
struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
/*
* codec ADCLRC pin configured as GPIO, DACLRC pin is used as a frame
* clock for ADCs and DACs
*/
snd_soc_update_bits(codec, WM8960_IFACE2, 1<<6, 1<<6);
/*
* GPIO1 used as headphone detect output
*/
snd_soc_update_bits(codec, WM8960_ADDCTL4, 7<<4, 3<<4);
/*
* Enable headphone jack detect
*/
snd_soc_update_bits(codec, WM8960_ADDCTL2, 1<<6, 1<<6);
snd_soc_update_bits(codec, WM8960_ADDCTL2, 1<<5, data->hp_det[1]<<5);
snd_soc_update_bits(codec, WM8960_ADDCTL4, 3<<2, data->hp_det[0]<<2);
snd_soc_update_bits(codec, WM8960_ADDCTL1, 3, 3);
/*
* route left channel to right channel in default.
*/
snd_soc_update_bits(codec, WM8960_ADDCTL1, 3<<2, 1<<2);
}
static int imx_hifi_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
@ -391,15 +344,27 @@ static struct snd_soc_ops imx_hifi_ops = {
static int imx_wm8960_late_probe(struct snd_soc_card *card)
{
struct snd_soc_pcm_runtime *rtd = list_first_entry(
&card->rtd_list, struct snd_soc_pcm_runtime, list);
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_codec *codec = codec_dai->codec;
struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
/*
* set SAI2_MCLK_DIR to enable codec MCLK
* codec ADCLRC pin configured as GPIO, DACLRC pin is used as a frame
* clock for ADCs and DACs
*/
if (data->gpr)
regmap_update_bits(data->gpr, 4, 1<<20, 1<<20);
snd_soc_update_bits(codec, WM8960_IFACE2, 1<<6, 1<<6);
/* GPIO1 used as headphone detect output */
snd_soc_update_bits(codec, WM8960_ADDCTL4, 7<<4, 3<<4);
/* Enable headphone jack detect */
snd_soc_update_bits(codec, WM8960_ADDCTL2, 1<<6, 1<<6);
snd_soc_update_bits(codec, WM8960_ADDCTL2, 1<<5, data->hp_det[1]<<5);
snd_soc_update_bits(codec, WM8960_ADDCTL4, 3<<2, data->hp_det[0]<<2);
snd_soc_update_bits(codec, WM8960_ADDCTL1, 3, 3);
wm8960_init(card);
return 0;
}
@ -523,6 +488,9 @@ static int imx_wm8960_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "failed to get gpr regmap\n");
goto fail;
}
/* set SAI2_MCLK_DIR to enable codec MCLK for imx7d */
regmap_update_bits(data->gpr, 4, 1<<20, 1<<20);
}
of_property_read_u32_array(pdev->dev.of_node, "hp-det", data->hp_det, 2);
@ -592,30 +560,59 @@ static int imx_wm8960_probe(struct platform_device *pdev)
priv->snd_card = data->card.snd_card;
priv->hp_set_gpio = of_get_named_gpio_flags(pdev->dev.of_node, "hp-det-gpios", 0,
(enum of_gpio_flags *)&priv->hp_active_low);
if (IS_ERR(ERR_PTR(priv->hp_set_gpio)))
goto fail;
imx_hp_jack_gpio.gpio = of_get_named_gpio_flags(pdev->dev.of_node,
"hp-det-gpios", 0, &priv->hp_active_low);
priv->headset_kctl = snd_kctl_jack_new("Headset", priv->snd_card);
ret = snd_ctl_add(data->card.snd_card, priv->headset_kctl);
if (ret)
goto fail;
imx_mic_jack_gpio.gpio = of_get_named_gpio_flags(pdev->dev.of_node,
"mic-det-gpios", 0, &priv->mic_active_low);
ret = imx_wm8960_gpio_init(&data->card);
if (gpio_is_valid(imx_hp_jack_gpio.gpio) &&
gpio_is_valid(imx_mic_jack_gpio.gpio) &&
imx_hp_jack_gpio.gpio == imx_mic_jack_gpio.gpio)
priv->is_headset_jack = true;
if (gpio_is_valid(priv->hp_set_gpio)) {
ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone);
if (gpio_is_valid(imx_hp_jack_gpio.gpio)) {
priv->headphone_kctl = snd_kctl_jack_new("Headphone", NULL);
ret = snd_ctl_add(priv->snd_card, priv->headphone_kctl);
if (ret)
dev_warn(&pdev->dev, "failed to create headphone jack kctl\n");
if (priv->is_headset_jack) {
imx_hp_jack_pin.mask |= SND_JACK_MICROPHONE;
imx_hp_jack_gpio.report |= SND_JACK_MICROPHONE;
}
imx_hp_jack_gpio.jack_status_check = hp_jack_status_check;
imx_hp_jack_gpio.data = &imx_hp_jack;
ret = imx_wm8960_jack_init(&data->card, &imx_hp_jack,
&imx_hp_jack_pin, &imx_hp_jack_gpio);
if (ret) {
dev_err(&pdev->dev, "create hp attr failed (%d)\n", ret);
goto fail;
dev_warn(&pdev->dev, "hp jack init failed (%d)\n", ret);
goto out;
}
ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone);
if (ret)
dev_warn(&pdev->dev, "create hp attr failed (%d)\n", ret);
}
if (gpio_is_valid(imx_mic_jack_gpio.gpio)) {
if (!priv->is_headset_jack) {
imx_mic_jack_gpio.jack_status_check = mic_jack_status_check;
imx_mic_jack_gpio.data = &imx_mic_jack;
ret = imx_wm8960_jack_init(&data->card, &imx_mic_jack,
&imx_mic_jack_pins, &imx_mic_jack_gpio);
if (ret) {
dev_warn(&pdev->dev, "mic jack init failed (%d)\n", ret);
goto out;
}
}
ret = driver_create_file(pdev->dev.driver, &driver_attr_micphone);
if (ret) {
dev_err(&pdev->dev, "create mic attr failed (%d)\n", ret);
goto fail;
}
if (ret)
dev_warn(&pdev->dev, "create mic attr failed (%d)\n", ret);
}
out:
ret = 0;
fail:
if (cpu_np)
of_node_put(cpu_np);
@ -625,6 +622,14 @@ fail:
return ret;
}
static int imx_wm8960_remove(struct platform_device *pdev)
{
driver_remove_file(pdev->dev.driver, &driver_attr_micphone);
driver_remove_file(pdev->dev.driver, &driver_attr_headphone);
return 0;
}
static const struct of_device_id imx_wm8960_dt_ids[] = {
{ .compatible = "fsl,imx-audio-wm8960", },
{ /* sentinel */ }
@ -638,6 +643,7 @@ static struct platform_driver imx_wm8960_driver = {
.of_match_table = imx_wm8960_dt_ids,
},
.probe = imx_wm8960_probe,
.remove = imx_wm8960_remove,
};
module_platform_driver(imx_wm8960_driver);