[alsa-devel] [PATCH 1/2] ASoC: imx-wm8958: add imx-wm8958 machine driver
This is the initial imx-wm8958 device-tree-only machine driver working with fsl_sai driver. This sound card has three dai link, hifi, voice and bt. Hifi dai link will support codec dai master and slave mode. Voice an bt dai link have dummy cpu dai, and just support codec dai master mode.
Signed-off-by: Zidan Wang zidan.wang@freescale.com --- .../devicetree/bindings/sound/imx-audio-wm8958.txt | 74 ++++ sound/soc/fsl/Kconfig | 14 + sound/soc/fsl/Makefile | 2 + sound/soc/fsl/imx-wm8958.c | 418 +++++++++++++++++++++ 4 files changed, 508 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/imx-audio-wm8958.txt create mode 100644 sound/soc/fsl/imx-wm8958.c
diff --git a/Documentation/devicetree/bindings/sound/imx-audio-wm8958.txt b/Documentation/devicetree/bindings/sound/imx-audio-wm8958.txt new file mode 100644 index 0000000..81775e4 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/imx-audio-wm8958.txt @@ -0,0 +1,74 @@ +Freescale i.MX audio complex with WM8958 codec + +Required properties: + + - compatible : "fsl,imx-audio-wm8958" + + - model : The user-visible name of this sound complex + + - cpu-dai : The phandle of an CPU DAI controller + + - audio-codec : The phandle of the WM8958 audio codec + + - audio-routing : A list of the connections between audio components. + Each entry is a pair of strings, the first being + the connection's sink, the second being the + connection's source. Valid names could be power + supplies, WM8958 pins, and the jacks on the board: + + Power supplies: + * MICBIAS1 + * MICBIAS2 + + Board connectors: + * Headphone Jack + * Ext Spk + + - aifx-format : set format for aifx, such as "i2s", "left_j", + "dsp_a", "dsp_b". + + - aifx-bitclock-master : If present, aifx wroks as bit clock master. + Otherwise, aifx works as bit clock slave. + + - aifx-frame-master : If present, aifx wroks as frame clock master. + Otherwise, aifx works as frame clock slave. + + - aifx-bitclock-inversion : If present, bit clock will invert polarity. + + - aifx-frame-inversion : If present, frame clock will invert polarity. + + - aifx-continuous-clock : If present, bit clock will be continuous. + Otherwise, dai bit clocks can be be gated + (disabled) when the dai is not sending or + receiving PCM data in a frame. + +Example: + +sound { + compatible = "fsl,imx6ul-evk-wm8958", + "fsl,imx-audio-wm8958"; + model = "wm8960-audio"; + cpu-dai = <&sai1>; + audio-codec = <&codec>; + audio-routing = + "Headphone Jack", "HPOUT1L", + "Headphone Jack", "HPOUT1R", + "Ext Spk", "SPKOUTLP", + "Ext Spk", "SPKOUTLN", + "Ext Spk", "SPKOUTRP", + "Ext Spk", "SPKOUTRN", + "IN1LN", "MICBIAS2"; + + /* set aif1 dai format */ + aif1-format = "i2s"; + aif1-bitclock-master; + aif1-frame-master; + /* set aif2 dai format */ + aif2-format = "i2s"; + aif2-bitclock-master; + aif2-frame-master; + /* set aif3 dai format */ + aif3-format = "i2s"; + aif3-bitclock-master; + aif3-frame-master; +}; diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 14dfdee..53041cc 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -229,6 +229,20 @@ config SND_SOC_EUKREA_TLV320 Enable I2S based access to the TLV320AIC23B codec attached to the SSI interface
+config SND_SOC_IMX_WM8958 + tristate "SoC Audio support for i.MX boards with wm8958" + depends on OF && I2C + select MFD_WM8994 + select SND_SOC_WM8994 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + select SND_KCTL_JACK + help + SoC Audio support for i.MX boards with WM8958 + Say Y if you want to add support for SoC audio on an i.MX board with + a wm8958 codec. + config SND_SOC_IMX_WM8962 tristate "SoC Audio support for i.MX boards with wm8962" depends on OF && I2C && INPUT diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index d28dc25..2a781c0 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -54,6 +54,7 @@ snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o snd-soc-wm1133-ev1-objs := wm1133-ev1.o snd-soc-imx-es8328-objs := imx-es8328.o snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o +snd-soc-imx-wm8958-objs := imx-wm8958.o snd-soc-imx-wm8962-objs := imx-wm8962.o snd-soc-imx-spdif-objs := imx-spdif.o snd-soc-imx-mc13783-objs := imx-mc13783.o @@ -64,6 +65,7 @@ obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o obj-$(CONFIG_SND_SOC_IMX_ES8328) += snd-soc-imx-es8328.o obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o +obj-$(CONFIG_SND_SOC_IMX_WM8958) += snd-soc-imx-wm8958.o obj-$(CONFIG_SND_SOC_IMX_WM8962) += snd-soc-imx-wm8962.o obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o obj-$(CONFIG_SND_SOC_IMX_MC13783) += snd-soc-imx-mc13783.o diff --git a/sound/soc/fsl/imx-wm8958.c b/sound/soc/fsl/imx-wm8958.c new file mode 100644 index 0000000..920bc9e --- /dev/null +++ b/sound/soc/fsl/imx-wm8958.c @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2015 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/mfd/wm8994/registers.h> +#include "../fsl/fsl_sai.h" +#include "../codecs/wm8994.h" + +#define DAI_NAME_SIZE 32 + +#define DAI_LINK_NUM (3) +#define HIFI_DAI (0) +#define VOICE_DAI (1) +#define BT_DAI (2) + +#define WM8958_MCLK_MAX (2) + +#define WM8994_FLL(id) (id == HIFI_DAI ? WM8994_FLL1 : WM8994_FLL2) +#define WM8994_SYSCLK_FLL(id) (id == HIFI_DAI ? WM8994_SYSCLK_FLL1 : WM8994_SYSCLK_FLL2) +#define WM8994_FLL_SRC_MCLK(id) (id == HIFI_DAI ? WM8994_FLL_SRC_MCLK1: WM8994_FLL_SRC_MCLK2) + +struct imx_wm8958_data { + struct snd_soc_dai_link *dai_link;; + struct snd_soc_card card; + struct clk *mclk[WM8958_MCLK_MAX]; + u32 mclk_freq[WM8958_MCLK_MAX]; + bool is_hifi_dai_master; + bool is_stream_in_use[DAI_LINK_NUM][2]; +}; + +static const struct snd_soc_dapm_widget imx_wm8958_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), +}; + +static int imx_wm8958_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + bool hifi_dai_sysclk_dir = SND_SOC_CLOCK_OUT; + u32 mclk_id, id = codec_dai->id - 1; + u32 pll_out; + int ret; + + data->is_stream_in_use[id][tx] = true; + + if (data->mclk_freq[id]) + mclk_id = WM8994_FLL_SRC_MCLK(id); + else if (id == HIFI_DAI) + mclk_id = WM8994_FLL_SRC_MCLK2; + else + mclk_id = WM8994_FLL_SRC_MCLK1; + + if (id == HIFI_DAI) { + /* + * Set GPIO1 pin function to reserve, so that DAC1 and ADC1 + * using shared LRCLK from DACLRCK1. + */ + snd_soc_update_bits(codec, WM8994_GPIO_1, 0x1f, 0x2); + + if (!data->is_hifi_dai_master) + 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; + } + + if (!data->is_hifi_dai_master) { + ret = snd_soc_dai_set_sysclk(codec_dai, mclk_id, + data->mclk_freq[mclk_id - 1], + hifi_dai_sysclk_dir); + if (ret) { + dev_err(dev, "failed to set codec sysclk: %d\n", ret); + return ret; + } + + return 0; + } + } else if (id == VOICE_DAI) { + /* + * Set GPIO6 pin function to reserve, so that DAC2 and ADC2 + * using shared LRCLK from DACLRCK2. + */ + snd_soc_update_bits(codec, WM8994_GPIO_6, 0x1f, 0x2); + } + + if (params_width(params) == 24) + pll_out = params_rate(params) * 384; + else + pll_out = params_rate(params) * 256; + + ret = snd_soc_dai_set_pll(codec_dai, + WM8994_FLL(id), + mclk_id, + data->mclk_freq[mclk_id - 1], + pll_out); + if (ret) { + dev_err(dev, "failed to set codec pll: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, + WM8994_SYSCLK_FLL(id), + pll_out, + SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(dev, "failed to set codec sysclk: %d\n", ret); + return ret; + } + + return 0; +} + +static int imx_wm8958_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 snd_soc_card *card = rtd->card; + struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int id = codec_dai->id - 1; + + data->is_stream_in_use[id][tx] = false; + + if (id == HIFI_DAI && !data->is_hifi_dai_master) + return 0; + + if (!data->is_stream_in_use[id][!tx]) { + /* + * We should connect AIFxCLK source to FLL after enable FLL, and + * disconnet AIF1CLK source to FLL before disable FLL, otherwise + * FLL worked abnormal. + */ + snd_soc_dai_set_sysclk(codec_dai, WM8994_FLL_SRC_MCLK(id), + data->mclk_freq[id], SND_SOC_CLOCK_OUT); + + /* Disable FLL1 after all stream finished. */ + snd_soc_dai_set_pll(codec_dai, WM8994_FLL(id), 0, 0, 0); + } + + return 0; +} + +static int imx_wm8958_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card); + int ret, i; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); + codec_dai = rtd->codec_dai; + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_STANDBY: + if (card->dapm.bias_level == SND_SOC_BIAS_OFF) { + /* need to enable mclk to write/read wm8958 register */ + for (i = 0; i < WM8958_MCLK_MAX; i++) { + if (!IS_ERR(data->mclk[i])) { + ret = clk_prepare_enable(data->mclk[i]); + if (ret) + dev_warn(card->dev, "Failed to enable MCLK%d: %d\n", i + 1, ret); + } + } + } + break; + default: + break; + } + + return 0; +} + +static int imx_wm8958_set_bias_level_post(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card); + int i; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); + codec_dai = rtd->codec_dai; + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_OFF: + if (card->dapm.bias_level == SND_SOC_BIAS_STANDBY) + for (i = 0; i < WM8958_MCLK_MAX; i++) { + if (!IS_ERR(data->mclk[i])) + clk_disable_unprepare(data->mclk[i]); + } + break; + default: + break; + } + + card->dapm.bias_level = level; + + return 0; +} + +static struct snd_soc_ops imx_hifi_ops = { + .hw_params = imx_wm8958_hw_params, + .hw_free = imx_wm8958_hw_free, +}; + +static struct snd_soc_ops imx_voice_ops = { + .hw_params = imx_wm8958_hw_params, + .hw_free = imx_wm8958_hw_free, +}; + +static struct snd_soc_dai_link imx_wm8958_dai_link[] = { + [HIFI_DAI] = { + .name = "HiFi", + .stream_name = "HiFi", + .codec_name = "wm8994-codec", + .codec_dai_name = "wm8994-aif1", + .ops = &imx_hifi_ops, + }, + [VOICE_DAI] = { + .name = "Voice", + .stream_name = "Voice", + .cpu_dai_name = "snd-soc-dummy-dai", + .codec_name = "wm8994-codec", + .codec_dai_name = "wm8994-aif2", + .platform_name = "snd-soc-dummy", + .ignore_pmdown_time = 1, + .ops = &imx_voice_ops, + }, + [BT_DAI] = { + .name = "Bluetooth", + .stream_name = "Bluetooth", + .cpu_dai_name = "snd-soc-dummy-dai", + .codec_name = "wm8994-codec", + .codec_dai_name = "wm8994-aif3", + .platform_name = "snd-soc-dummy", + .ignore_pmdown_time = 1, + }, +}; + +static int imx_wm8958_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np, *codec_np; + struct device_node *np = pdev->dev.of_node; + struct platform_device *cpu_pdev; + struct i2c_client *codec_dev; + struct imx_wm8958_data *data; + char tmp[8]; + u32 dai_fmt; + int ret, i; + + cpu_np = of_parse_phandle(np, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + return -EINVAL; + } + + codec_np = of_parse_phandle(np, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find cpu dai platform device\n"); + ret = -EINVAL; + goto fail; + } + + codec_dev = of_find_i2c_device_by_node(codec_np); + if (!codec_dev || !codec_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dai_link = imx_wm8958_dai_link; + + /* + * AIF1 support codec master and slave mode + * AIF2 and AIF3 just support codec master mode + */ + for (i = 0; i < DAI_LINK_NUM; i++) { + sprintf(tmp, "aif%d-", i + 1); + dai_fmt = snd_soc_of_parse_daifmt(np, tmp, NULL, NULL); + + if (!(dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK)) { + dev_warn(&pdev->dev, + "failed to get format, default to i2s mode\n"); + dai_fmt |= SND_SOC_DAIFMT_I2S; + } + + if (i == HIFI_DAI) { + switch (dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + data->is_hifi_dai_master = false; + break; + default: + data->is_hifi_dai_master = true; + break; + } + } else { + dai_fmt &= ~SND_SOC_DAIFMT_MASTER_MASK; + dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + } + data->dai_link[i].dai_fmt = dai_fmt; + } + + for (i = 0; i < WM8958_MCLK_MAX; i++) { + sprintf(tmp, "mclk%d", i + 1); + data->mclk[i] = devm_clk_get(&codec_dev->dev, tmp); + if (IS_ERR(data->mclk[i])) { + ret = PTR_ERR(data->mclk[i]); + dev_err(&pdev->dev, "failed to get mclk%d clock: %d\n", + i + 1, ret); + } else { + data->mclk_freq[i] = clk_get_rate(data->mclk[i]); + } + } + + data->dai_link[HIFI_DAI].cpu_dai_name = dev_name(&cpu_pdev->dev); + data->dai_link[HIFI_DAI].platform_of_node = cpu_np; + + data->card.set_bias_level = imx_wm8958_set_bias_level; + data->card.set_bias_level_post = imx_wm8958_set_bias_level_post; + data->card.dev = &pdev->dev; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + + data->card.num_links = DAI_LINK_NUM; + data->card.dai_link = data->dai_link; + data->card.dapm_widgets = imx_wm8958_dapm_widgets; + data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8958_dapm_widgets); + + ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); + if (ret) + goto fail; + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + +fail: + of_node_put(cpu_np); + of_node_put(codec_np); + + return ret; +} + +static const struct of_device_id imx_wm8958_dt_ids[] = { + { .compatible = "fsl,imx-audio-wm8958", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_wm8958_dt_ids); + +static struct platform_driver imx_wm8958_driver = { + .driver = { + .name = "imx-wm8958", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_wm8958_dt_ids, + }, + .probe = imx_wm8958_probe, +}; +module_platform_driver(imx_wm8958_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX WM8958 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-wm8958");
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(-)
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);
On Tue, Dec 08, 2015 at 05:40:13PM +0800, Zidan Wang wrote:
- aifx-format : set format for aifx, such as "i2s", "left_j",
"dsp_a", "dsp_b".
- aifx-bitclock-master : If present, aifx wroks as bit clock master.
Otherwise, aifx works as bit clock slave.
- aifx-frame-master : If present, aifx wroks as frame clock master.
Otherwise, aifx works as frame clock slave.
- aifx-bitclock-inversion : If present, bit clock will invert polarity.
- aifx-frame-inversion : If present, frame clock will invert polarity.
- aifx-continuous-clock : If present, bit clock will be continuous.
Otherwise, dai bit clocks can be be gated
(disabled) when the dai is not sending or
receiving PCM data in a frame.
Why are these selectable in the machine driver?
model = "wm8960-audio";
Cut'n'paste error here.
On Tue, Dec 08, 2015 at 12:47:13PM +0000, Mark Brown wrote:
On Tue, Dec 08, 2015 at 05:40:13PM +0800, Zidan Wang wrote:
- aifx-format : set format for aifx, such as "i2s", "left_j",
"dsp_a", "dsp_b".
- aifx-bitclock-master : If present, aifx wroks as bit clock master.
Otherwise, aifx works as bit clock slave.
- aifx-frame-master : If present, aifx wroks as frame clock master.
Otherwise, aifx works as frame clock slave.
- aifx-bitclock-inversion : If present, bit clock will invert polarity.
- aifx-frame-inversion : If present, frame clock will invert polarity.
- aifx-continuous-clock : If present, bit clock will be continuous.
Otherwise, dai bit clocks can be be gated
(disabled) when the dai is not sending or
receiving PCM data in a frame.
Why are these selectable in the machine driver?
I have tested and found that sai<->aif1 can support "i2s" "left_j" "dsp_a" "dsp_b" format, and can support cpu dai master and slave mode. But I haven't tested voice dai and bt dai because of hardware limitation. I just using sai<->aif2 to test aif2.
So I want to configure the dai format from device tree. And there is a help function snd_soc_of_parse_daifmt for me to do this, so I add these selectable to the machine driver.
model = "wm8960-audio";
Cut'n'paste error here.
it should be "wm8958-audio", i will modify it.
Best Regards, Zidan Wang
On Wed, Dec 09, 2015 at 11:34:52AM +0800, Zidan Wang wrote:
On Tue, Dec 08, 2015 at 12:47:13PM +0000, Mark Brown wrote:
On Tue, Dec 08, 2015 at 05:40:13PM +0800, Zidan Wang wrote:
- aifx-continuous-clock : If present, bit clock will be continuous.
Otherwise, dai bit clocks can be be gated
(disabled) when the dai is not sending or
receiving PCM data in a frame.
Why are these selectable in the machine driver?
I have tested and found that sai<->aif1 can support "i2s" "left_j" "dsp_a" "dsp_b" format, and can support cpu dai master and slave mode. But I haven't tested voice dai and bt dai because of hardware limitation. I just using sai<->aif2 to test aif2.
So I want to configure the dai format from device tree. And there is a help function snd_soc_of_parse_daifmt for me to do this, so I add these selectable to the machine driver.
This really sounds like the best thing to do here is to just leave this out of the upstream submission then add the support for the voice and BT link when you've got the configuration worked out. You'll most likely find there's only one possible configuration anyway.
participants (2)
-
Mark Brown
-
Zidan Wang