diff options
author | Shengjiu Wang <shengjiu.wang@freescale.com> | 2014-09-29 10:56:03 +0800 |
---|---|---|
committer | Nitin Garg <nitin.garg@nxp.com> | 2016-01-14 10:59:58 -0600 |
commit | 4e39a4d9c9d1d6a1307ed6a43622ecfaf4353b07 (patch) | |
tree | b9cb0656a42a8d6939191db8ccfeae7ba2dd5e78 | |
parent | 90edebd7a9674434b7914a75d6b37b9ef6074d37 (diff) |
MLK-9723-4: ASoC: fsl_mqs: add mqs codec driver
Implement codec driver for mqs. mqs is a very simple IP. which support:
Word length: 16bit.
DAI format: Left-Justified, slave mode.
Signed-off-by: Shengjiu Wang <shengjiu.wang@freescale.com>
(cherry picked from commit 9da6bdd2072b850e9bb910512123eff7d80a0e2f)
-rw-r--r-- | Documentation/devicetree/bindings/sound/fsl,mqs.txt | 22 | ||||
-rw-r--r-- | sound/soc/codecs/Kconfig | 3 | ||||
-rw-r--r-- | sound/soc/codecs/Makefile | 2 | ||||
-rw-r--r-- | sound/soc/codecs/fsl_mqs.c | 228 |
4 files changed, 255 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/sound/fsl,mqs.txt b/Documentation/devicetree/bindings/sound/fsl,mqs.txt new file mode 100644 index 000000000000..7c288461e9ce --- /dev/null +++ b/Documentation/devicetree/bindings/sound/fsl,mqs.txt @@ -0,0 +1,22 @@ +fsl,mqs audio CODEC + +Required properties: + + - compatible : must contain one of "fsl,imx6sx-mqs" and "fsl,codec-mqs" + + - clocks : a list of phandles + clock-specifiers, one for each entry in + clock-names + + - clock-names : must contain "mclk" + + - gpr : the gpr node. + +Example: + +mqs: mqs { + compatible = "fsl,imx6sx-mqs"; + gpr = <&gpr>; + clocks = <&clks IMX6SX_CLK_SAI1>; + clock-names = "mclk"; + status = "disabled"; +}; diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 061c46587628..7b52b1d31c85 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -444,6 +444,9 @@ config SND_SOC_ES8328_SPI tristate select SND_SOC_ES8328 +config SND_SOC_FSL_MQS + tristate + config SND_SOC_ISABELLE tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index abe2d7edf65c..8898ea4fde7d 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -55,6 +55,7 @@ snd-soc-dmic-objs := dmic.o snd-soc-es8328-objs := es8328.o snd-soc-es8328-i2c-objs := es8328-i2c.o snd-soc-es8328-spi-objs := es8328-spi.o +snd-soc-fsl-mqs-objs := fsl_mqs.o snd-soc-isabelle-objs := isabelle.o snd-soc-jz4740-codec-objs := jz4740.o snd-soc-l3-objs := l3.o @@ -240,6 +241,7 @@ obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o obj-$(CONFIG_SND_SOC_ES8328) += snd-soc-es8328.o obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o +obj-$(CONFIG_SND_SOC_FSL_MQS) += snd-soc-fsl-mqs.o obj-$(CONFIG_SND_SOC_ISABELLE) += snd-soc-isabelle.o obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o diff --git a/sound/soc/codecs/fsl_mqs.c b/sound/soc/codecs/fsl_mqs.c new file mode 100644 index 000000000000..b4fc940c930c --- /dev/null +++ b/sound/soc/codecs/fsl_mqs.c @@ -0,0 +1,228 @@ +/* + * ALSA SoC IMX MQS driver + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/initval.h> + + +/* codec private data */ +struct fsl_mqs { + struct platform_device *pdev; + struct regmap *gpr; + struct clk *mclk; + + unsigned long mclk_rate; + + int sysclk_rate; + int bclk; + int lrclk; + char name[32]; +}; + +#define FSL_MQS_RATES SNDRV_PCM_RATE_8000_192000 +#define FSL_MQS_FORMATS SNDRV_PCM_FMTBIT_S16_LE + +static int fsl_mqs_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct fsl_mqs *mqs_priv = snd_soc_codec_get_drvdata(codec); + int div, res; + + mqs_priv->bclk = snd_soc_params_to_bclk(params); + mqs_priv->lrclk = params_rate(params); + + /* + * mclk_rate / (oversample(32,64) * FS * 2 * divider ) = repeat_rate; + * if repeat_rate is 8, mqs can achieve better quality. + * oversample rate is fix to 32 currently. + */ + div = mqs_priv->mclk_rate / (32 * 2 * mqs_priv->lrclk * 8); + res = mqs_priv->mclk_rate % (32 * 2 * mqs_priv->lrclk * 8); + + if (res == 0 && div > 0 && div <= 256) { + regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2, + IMX6SX_GPR2_MQS_CLK_DIV_MASK, (div-1) << IMX6SX_GPR2_MQS_CLK_DIV_SHIFT); + regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2, + IMX6SX_GPR2_MQS_OVERSAMPLE_MASK, 0 << IMX6SX_GPR2_MQS_OVERSAMPLE_SHIFT); + } else + dev_err(&mqs_priv->pdev->dev, "can't get proper divider\n"); + + return 0; +} + +static int fsl_mqs_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + return 0; +} + +static int fsl_mqs_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct fsl_mqs *mqs_priv = snd_soc_codec_get_drvdata(codec); + + mqs_priv->sysclk_rate = freq; + + return 0; +} + +static int fsl_mqs_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct fsl_mqs *mqs_priv = snd_soc_codec_get_drvdata(codec); + + regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2, IMX6SX_GPR2_MQS_EN_MASK, + 1 << IMX6SX_GPR2_MQS_EN_SHIFT); + return 0; +} + +static void fsl_mqs_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct fsl_mqs *mqs_priv = snd_soc_codec_get_drvdata(codec); + + regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2, + IMX6SX_GPR2_MQS_EN_MASK, 0); + +} + + +static struct snd_soc_codec_driver soc_codec_fsl_mqs; + +static const struct snd_soc_dai_ops fsl_mqs_dai_ops = { + .startup = fsl_mqs_startup, + .shutdown = fsl_mqs_shutdown, + .hw_params = fsl_mqs_hw_params, + .set_fmt = fsl_mqs_set_dai_fmt, + .set_sysclk = fsl_mqs_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver fsl_mqs_dai = { + .name = "fsl-mqs-dai", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = FSL_MQS_RATES, + .formats = FSL_MQS_FORMATS, + }, + .ops = &fsl_mqs_dai_ops, +}; + +static int fsl_mqs_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *gpr_np; + struct fsl_mqs *mqs_priv; + int ret = 0; + + mqs_priv = devm_kzalloc(&pdev->dev, sizeof(*mqs_priv), GFP_KERNEL); + if (!mqs_priv) + return -ENOMEM; + + mqs_priv->pdev = pdev; + strcpy(mqs_priv->name, np->name); + + gpr_np = of_parse_phandle(np, "gpr", 0); + if (IS_ERR(gpr_np)) { + dev_err(&pdev->dev, "failed to get gpr node by phandle\n"); + ret = PTR_ERR(gpr_np); + goto out; + } + + mqs_priv->gpr = syscon_node_to_regmap(gpr_np); + if (IS_ERR(mqs_priv->gpr)) { + dev_err(&pdev->dev, "failed to get gpr regmap\n"); + ret = PTR_ERR(mqs_priv->gpr); + goto out; + } + + mqs_priv->mclk = devm_clk_get(&pdev->dev, "mclk"); + if (IS_ERR(mqs_priv->mclk)) { + dev_err(&pdev->dev, "failed to get the clock: %ld\n", + PTR_ERR(mqs_priv->mclk)); + goto out; + } + + mqs_priv->mclk_rate = clk_get_rate(mqs_priv->mclk); + + dev_set_drvdata(&pdev->dev, mqs_priv); + + return snd_soc_register_codec(&pdev->dev, &soc_codec_fsl_mqs, + &fsl_mqs_dai, 1); + +out: + if (!IS_ERR(gpr_np)) + of_node_put(gpr_np); + + return ret; +} + +static int fsl_mqs_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +static const struct of_device_id fsl_mqs_dt_ids[] = { + { .compatible = "fsl,imx6sx-mqs", }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_mqs_dt_ids); + + +static struct platform_driver fsl_mqs_driver = { + .probe = fsl_mqs_probe, + .remove = fsl_mqs_remove, + .driver = { + .name = "fsl-mqs", + .of_match_table = fsl_mqs_dt_ids, + }, +}; + +module_platform_driver(fsl_mqs_driver); + +MODULE_AUTHOR("shengjiu wang <shengjiu.wang@freescale.com>"); +MODULE_DESCRIPTION("MQS dummy codec driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform: fsl-mqs"); |