[2/2] ASoC: imx-wm8958: add headphone detected gpio support
diff mbox

Message ID f803a02b2e80bc750f67e4eced99093ab9f66100.1449567072.git.zidan.wang@freescale.com
State New
Headers show

Commit Message

Zidan Wang Dec. 8, 2015, 9:40 a.m. UTC
Add headphone detected gpio support for sound card.
When plug in headphone Jack, it will enable headphone audio route and
disable speaker audio route.

Signed-off-by: Zidan Wang <zidan.wang@freescale.com>
---
 .../devicetree/bindings/sound/imx-audio-wm8958.txt |   5 +
 sound/soc/fsl/imx-wm8958.c                         | 121 ++++++++++++++++++++-
 2 files changed, 125 insertions(+), 1 deletion(-)

Patch
diff mbox

diff --git a/Documentation/devicetree/bindings/sound/imx-audio-wm8958.txt b/Documentation/devicetree/bindings/sound/imx-audio-wm8958.txt
index 81775e4..7164ec8 100644
--- a/Documentation/devicetree/bindings/sound/imx-audio-wm8958.txt
+++ b/Documentation/devicetree/bindings/sound/imx-audio-wm8958.txt
@@ -42,6 +42,10 @@  Required properties:
 			     (disabled) when the dai is not sending or
 			     receiving PCM data in a frame.
 
+Optional properties:
+
+- hp-det-gpios		   : set headphone detected gpio for sound card.
+
 Example:
 
 sound {
@@ -50,6 +54,7 @@  sound {
 		model = "wm8960-audio";
 		cpu-dai = <&sai1>;
 		audio-codec = <&codec>;
+		hp-det-gpios = <&gpio1 12 GPIO_ACTIVE_LOW>;
 		audio-routing =
 			"Headphone Jack", "HPOUT1L",
 			"Headphone Jack", "HPOUT1R",
diff --git a/sound/soc/fsl/imx-wm8958.c b/sound/soc/fsl/imx-wm8958.c
index 920bc9e..c3c8677 100644
--- a/sound/soc/fsl/imx-wm8958.c
+++ b/sound/soc/fsl/imx-wm8958.c
@@ -21,8 +21,11 @@ 
 #include <sound/pcm_params.h>
 #include <sound/soc-dapm.h>
 #include <linux/mfd/wm8994/registers.h>
+#include <linux/gpio/machine.h>
+#include <linux/bitops.h>
 #include "../fsl/fsl_sai.h"
 #include "../codecs/wm8994.h"
+#include "../../../drivers/gpio/gpiolib.h"
 
 #define DAI_NAME_SIZE	32
 
@@ -51,6 +54,99 @@  static const struct snd_soc_dapm_widget imx_wm8958_dapm_widgets[] = {
 	SND_SOC_DAPM_SPK("Ext Spk", NULL),
 };
 
+struct gpio_data {
+	int gpio;
+	unsigned long flags;
+};
+
+struct imx_priv {
+	struct gpio_data hp_gpio;
+};
+
+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 int hpjack_status_check(void *data)
+{
+	struct snd_soc_jack *jack = &imx_hp_jack;
+	int enable, ret;
+
+	enable = gpiod_get_value_cansleep(imx_hp_jack_gpio.desc);
+
+	if (enable) {
+		snd_soc_dapm_disable_pin(&jack->card->dapm, "Ext Spk");
+		ret = imx_hp_jack_gpio.report;
+	} else {
+		snd_soc_dapm_enable_pin(&jack->card->dapm, "Ext Spk");
+		ret = 0;
+	}
+
+	return ret;
+}
+
+static int imx_wm8958_gpio_init(struct snd_soc_card *card)
+{
+	struct imx_priv *priv = &card_priv;
+	int ret;
+
+	imx_hp_jack_gpio.gpio = priv->hp_gpio.gpio;
+	imx_hp_jack_gpio.jack_status_check = hpjack_status_check;
+
+	ret = snd_soc_card_jack_new(card, "Headphone Jack",
+			SND_JACK_HEADPHONE, &imx_hp_jack,
+			imx_hp_jack_pins, ARRAY_SIZE(imx_hp_jack_pins));
+	if (ret) {
+		dev_err(card->dev,
+			"failed to create Headphone Jack (%d)\n", ret);
+		return ret;
+	}
+
+	ret = snd_soc_jack_add_gpios(&imx_hp_jack, 1, &imx_hp_jack_gpio);
+	if (ret) {
+		dev_err(card->dev,
+			"failed to add Headphone Jack gpio (%d)\n", ret);
+		return ret;
+	}
+
+	if (priv->hp_gpio.flags & GPIO_ACTIVE_LOW)
+		set_bit(FLAG_ACTIVE_LOW, &imx_hp_jack_gpio.desc->flags);
+	else
+		clear_bit(FLAG_ACTIVE_LOW, &imx_hp_jack_gpio.desc->flags);
+
+	return 0;
+}
+
+static ssize_t show_headphone(struct device_driver *driver, char *buf)
+{
+	struct imx_priv *priv = &card_priv;
+	int enable;
+
+	/* Check if headphone is plugged in */
+	enable = gpiod_get_value_cansleep(gpio_to_desc(priv->hp_gpio.gpio));
+
+	if (enable)
+		strcpy(buf, "headphone\n");
+	else
+		strcpy(buf, "speaker\n");
+
+	return strlen(buf);
+}
+
+static DRIVER_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL);
+
 static int imx_wm8958_hw_params(struct snd_pcm_substream *substream,
 				     struct snd_pcm_hw_params *params)
 {
@@ -87,7 +183,6 @@  static int imx_wm8958_hw_params(struct snd_pcm_substream *substream,
 			hifi_dai_sysclk_dir = SND_SOC_CLOCK_IN;
 
 		ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, !hifi_dai_sysclk_dir);
-
 		if (ret) {
 			dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
 			return ret;
@@ -282,6 +377,7 @@  static int imx_wm8958_probe(struct platform_device *pdev)
 	struct platform_device *cpu_pdev;
 	struct i2c_client *codec_dev;
 	struct imx_wm8958_data *data;
+	struct imx_priv *priv = &card_priv;
 	char tmp[8];
 	u32 dai_fmt;
 	int ret, i;
@@ -389,6 +485,21 @@  static int imx_wm8958_probe(struct platform_device *pdev)
 		goto fail;
 	}
 
+	priv->hp_gpio.gpio = of_get_named_gpio_flags(np, "hp-det-gpios", 0,
+			(enum of_gpio_flags *)&priv->hp_gpio.flags);
+
+	if (!gpio_is_valid(priv->hp_gpio.gpio))
+		goto out;
+
+	imx_wm8958_gpio_init(&data->card);
+
+	ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone);
+	if (ret) {
+		dev_warn(&pdev->dev, "create hp attr failed (%d)\n", ret);
+		goto out;
+	}
+out:
+	ret = 0;
 fail:
 	of_node_put(cpu_np);
 	of_node_put(codec_np);
@@ -396,6 +507,13 @@  fail:
 	return ret;
 }
 
+static int imx_wm8958_remove(struct platform_device *pdev)
+{
+	driver_remove_file(pdev->dev.driver, &driver_attr_headphone);
+	snd_soc_jack_free_gpios(&imx_hp_jack, 1, &imx_hp_jack_gpio);
+	return 0;
+}
+
 static const struct of_device_id imx_wm8958_dt_ids[] = {
 	{ .compatible = "fsl,imx-audio-wm8958", },
 	{ /* sentinel */ }
@@ -409,6 +527,7 @@  static struct platform_driver imx_wm8958_driver = {
 		.of_match_table = imx_wm8958_dt_ids,
 	},
 	.probe = imx_wm8958_probe,
+	.remove = imx_wm8958_remove,
 };
 module_platform_driver(imx_wm8958_driver);