aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/imx/imx-cs42888.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/imx/imx-cs42888.c')
-rw-r--r--sound/soc/imx/imx-cs42888.c444
1 files changed, 444 insertions, 0 deletions
diff --git a/sound/soc/imx/imx-cs42888.c b/sound/soc/imx/imx-cs42888.c
new file mode 100644
index 00000000..62d221fb
--- /dev/null
+++ b/sound/soc/imx/imx-cs42888.c
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) 2010-2012 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * 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/moduleparam.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+#include <linux/fsl_devices.h>
+#include <linux/gpio.h>
+#include <linux/mxc_asrc.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/soc-dai.h>
+#include <sound/pcm_params.h>
+
+#include <mach/hardware.h>
+#include <mach/clock.h>
+#include <asm/mach-types.h>
+
+#include "imx-esai.h"
+#include "../codecs/cs42888.h"
+#include "imx-pcm.h"
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_IMX_HAVE_PLATFORM_IMX_ASRC)
+
+static enum asrc_word_width get_asrc_input_width(
+ struct snd_pcm_hw_params *params)
+{
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_U16:
+ case SNDRV_PCM_FORMAT_S16_LE:
+ case SNDRV_PCM_FORMAT_S16_BE:
+ return ASRC_WIDTH_16_BIT;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ case SNDRV_PCM_FORMAT_S20_3BE:
+ case SNDRV_PCM_FORMAT_S24_3LE:
+ case SNDRV_PCM_FORMAT_S24_3BE:
+ case SNDRV_PCM_FORMAT_S24_BE:
+ case SNDRV_PCM_FORMAT_S24_LE:
+ case SNDRV_PCM_FORMAT_U24_BE:
+ case SNDRV_PCM_FORMAT_U24_LE:
+ case SNDRV_PCM_FORMAT_U24_3BE:
+ case SNDRV_PCM_FORMAT_U24_3LE:
+ return ASRC_WIDTH_24_BIT;
+ case SNDRV_PCM_FORMAT_S8:
+ case SNDRV_PCM_FORMAT_U8:
+ case SNDRV_PCM_FORMAT_S32:
+ case SNDRV_PCM_FORMAT_U32:
+ default:
+ pr_err("Format is not support!\n");
+ return -EINVAL;
+ }
+}
+
+static int config_asrc(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ unsigned int rate = params_rate(params);
+ unsigned int channel = params_channels(params);
+ struct imx_pcm_runtime_data *iprtd =
+ substream->runtime->private_data;
+ struct asrc_config config = {0};
+ int ret = 0;
+
+ if ((channel != 2) && (channel != 4) && (channel != 6))
+ return -EINVAL;
+
+ ret = iprtd->asrc_pcm_p2p_ops_ko->
+ asrc_p2p_req_pair(channel, &iprtd->asrc_index);
+ if (ret < 0) {
+ pr_err("Fail to request asrc pair\n");
+ return -EINVAL;
+ }
+
+ config.input_word_width = get_asrc_input_width(params);
+ config.output_word_width = iprtd->p2p->p2p_width;
+ config.pair = iprtd->asrc_index;
+ config.channel_num = channel;
+ config.input_sample_rate = rate;
+ config.output_sample_rate = iprtd->p2p->p2p_rate;
+ config.inclk = INCLK_NONE;
+ config.outclk = OUTCLK_ESAI_TX;
+
+ ret = iprtd->asrc_pcm_p2p_ops_ko->asrc_p2p_config_pair(&config);
+ if (ret < 0) {
+ pr_err("Fail to config asrc\n");
+ return ret;
+ }
+
+ return 0;
+}
+#endif
+
+struct imx_priv_state {
+ int hw;
+};
+
+static struct asrc_p2p_params *esai_asrc;
+static struct imx_priv_state hw_state;
+unsigned int mclk_freq;
+
+static int imx_3stack_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+ if (!cpu_dai->active) {
+ hw_state.hw = 0;
+ }
+
+ return 0;
+}
+
+static void imx_3stack_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+ if (!cpu_dai->active)
+ hw_state.hw = 0;
+}
+
+static int imx_3stack_surround_hw_free(struct snd_pcm_substream *substream)
+{
+ struct imx_pcm_runtime_data *iprtd = substream->runtime->private_data;
+
+ if (iprtd->asrc_enable) {
+ if (iprtd->asrc_index != -1) {
+ iprtd->asrc_pcm_p2p_ops_ko->
+ asrc_p2p_release_pair(
+ iprtd->asrc_index);
+ iprtd->asrc_pcm_p2p_ops_ko->
+ asrc_p2p_finish_conv(iprtd->asrc_index);
+ }
+ iprtd->asrc_index = -1;
+ }
+
+ return 0;
+}
+static int imx_3stack_surround_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 *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_pcm_runtime_data *iprtd = substream->runtime->private_data;
+ unsigned int rate = params_rate(params);
+ u32 dai_format;
+ unsigned int lrclk_ratio = 0;
+ int err = 0;
+
+ if (iprtd->asrc_enable) {
+ err = config_asrc(substream, params);
+ if (err < 0)
+ return err;
+ rate = iprtd->p2p->p2p_rate;
+ }
+
+ if (hw_state.hw)
+ return 0;
+ hw_state.hw = 1;
+
+ if (cpu_is_mx53() || machine_is_mx6q_sabreauto()) {
+ switch (rate) {
+ case 32000:
+ lrclk_ratio = 3;
+ break;
+ case 48000:
+ lrclk_ratio = 3;
+ break;
+ case 64000:
+ lrclk_ratio = 1;
+ break;
+ case 96000:
+ lrclk_ratio = 1;
+ break;
+ case 128000:
+ lrclk_ratio = 1;
+ break;
+ case 44100:
+ lrclk_ratio = 3;
+ break;
+ case 88200:
+ lrclk_ratio = 1;
+ break;
+ case 176400:
+ lrclk_ratio = 0;
+ break;
+ case 192000:
+ lrclk_ratio = 0;
+ break;
+ default:
+ pr_info("Rate not support.\n");
+ return -EINVAL;;
+ }
+ } else if (cpu_is_mx6q() || cpu_is_mx6dl()) {
+ switch (rate) {
+ case 32000:
+ lrclk_ratio = 5;
+ break;
+ case 48000:
+ lrclk_ratio = 5;
+ break;
+ case 64000:
+ lrclk_ratio = 2;
+ break;
+ case 96000:
+ lrclk_ratio = 2;
+ break;
+ case 128000:
+ lrclk_ratio = 2;
+ break;
+ case 44100:
+ lrclk_ratio = 5;
+ break;
+ case 88200:
+ lrclk_ratio = 2;
+ break;
+ case 176400:
+ lrclk_ratio = 0;
+ break;
+ case 192000:
+ lrclk_ratio = 0;
+ break;
+ default:
+ pr_info("Rate not support.\n");
+ return -EINVAL;;
+ }
+ }
+ dai_format = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ /* set cpu DAI configuration */
+ snd_soc_dai_set_fmt(cpu_dai, dai_format);
+ /* set i.MX active slot mask */
+ snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 32);
+ /* set the ESAI system clock as output */
+ if (cpu_is_mx53() || machine_is_mx6q_sabreauto()) {
+ snd_soc_dai_set_sysclk(cpu_dai, ESAI_CLK_EXTAL,
+ mclk_freq, SND_SOC_CLOCK_OUT);
+ } else if (cpu_is_mx6q() || cpu_is_mx6dl()) {
+ snd_soc_dai_set_sysclk(cpu_dai, ESAI_CLK_EXTAL_DIV,
+ mclk_freq, SND_SOC_CLOCK_OUT);
+ }
+ /* set the ratio */
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_TX_DIV_PSR, 1);
+ if (cpu_is_mx53() || machine_is_mx6q_sabreauto())
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_TX_DIV_PM, 0);
+ else if (cpu_is_mx6q() || cpu_is_mx6dl())
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_TX_DIV_PM, 2);
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_TX_DIV_FP, lrclk_ratio);
+
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_RX_DIV_PSR, 1);
+ if (cpu_is_mx53() || machine_is_mx6q_sabreauto())
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_RX_DIV_PM, 0);
+ else if (cpu_is_mx6q() || cpu_is_mx6dl())
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_RX_DIV_PM, 2);
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_RX_DIV_FP, lrclk_ratio);
+
+ /* set codec DAI configuration */
+ snd_soc_dai_set_fmt(codec_dai, dai_format);
+ /* set codec Master clock */
+ snd_soc_dai_set_sysclk(codec_dai, 0, mclk_freq, SND_SOC_CLOCK_IN);
+
+ return 0;
+}
+
+static struct snd_soc_ops imx_3stack_surround_ops = {
+ .startup = imx_3stack_startup,
+ .shutdown = imx_3stack_shutdown,
+ .hw_params = imx_3stack_surround_hw_params,
+ .hw_free = imx_3stack_surround_hw_free,
+};
+
+static const struct snd_soc_dapm_widget imx_3stack_dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("Line Out Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Line out jack */
+ {"Line Out Jack", NULL, "AOUT1L"},
+ {"Line Out Jack", NULL, "AOUT1R"},
+ {"Line Out Jack", NULL, "AOUT2L"},
+ {"Line Out Jack", NULL, "AOUT2R"},
+ {"Line Out Jack", NULL, "AOUT3L"},
+ {"Line Out Jack", NULL, "AOUT3R"},
+ {"Line Out Jack", NULL, "AOUT4L"},
+ {"Line Out Jack", NULL, "AOUT4R"},
+ {"AIN1L", NULL, "Line In Jack"},
+ {"AIN1R", NULL, "Line In Jack"},
+ {"AIN2L", NULL, "Line In Jack"},
+ {"AIN2R", NULL, "Line In Jack"},
+};
+
+static int imx_3stack_cs42888_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+
+ snd_soc_dapm_new_controls(&codec->dapm, imx_3stack_dapm_widgets,
+ ARRAY_SIZE(imx_3stack_dapm_widgets));
+ snd_soc_dapm_add_routes(&codec->dapm, audio_map, ARRAY_SIZE(audio_map));
+ snd_soc_dapm_sync(&codec->dapm);
+ return 0;
+}
+static int imx_asrc_cs42888_init(struct snd_soc_pcm_runtime *rtd)
+{
+
+ snd_soc_pcm_set_drvdata(rtd, (void *)esai_asrc);
+ return 0;
+}
+
+static struct snd_soc_dai_link imx_3stack_dai[] = {
+ {
+ .name = "HiFi",
+ .stream_name = "HiFi",
+ .codec_dai_name = "CS42888",
+#ifdef CONFIG_SOC_IMX53
+ .codec_name = "cs42888.1-0048",
+#endif
+#ifdef CONFIG_SOC_IMX6Q
+ .codec_name = "cs42888.0-0048",
+#endif
+ .cpu_dai_name = "imx-esai.0",
+ .platform_name = "imx-pcm-audio.0",
+ .init = imx_3stack_cs42888_init,
+ .ops = &imx_3stack_surround_ops,
+ },
+ {
+ .name = "HiFi_ASRC",
+ .stream_name = "HIFI_ASRC",
+ .codec_dai_name = "CS42888_ASRC",
+#ifdef CONFIG_SOC_IMX53
+ .codec_name = "cs42888.1-0048",
+#endif
+#ifdef CONFIG_SOC_IMX6Q
+ .codec_name = "cs42888.0-0048",
+#endif
+ .cpu_dai_name = "imx-esai.0",
+ .platform_name = "imx-pcm-audio.0",
+ .init = imx_asrc_cs42888_init,
+ .ops = &imx_3stack_surround_ops,
+ },
+};
+
+static struct snd_soc_card snd_soc_card_imx_3stack = {
+ .name = "cs42888-audio",
+ .dai_link = imx_3stack_dai,
+ .num_links = ARRAY_SIZE(imx_3stack_dai),
+};
+
+/*
+ * This function will register the snd_soc_pcm_link drivers.
+ */
+static int __devinit imx_3stack_cs42888_probe(struct platform_device *pdev)
+{
+ struct mxc_audio_platform_data *plat_data = pdev->dev.platform_data;
+
+ if (!plat_data) {
+ dev_err(&pdev->dev, "plat_data is missing\n");
+ return -EINVAL;
+ }
+ mclk_freq = plat_data->sysclk;
+ if (plat_data->codec_name) {
+ imx_3stack_dai[0].codec_name = plat_data->codec_name;
+ imx_3stack_dai[1].codec_name = plat_data->codec_name;
+ }
+ esai_asrc = kzalloc(sizeof(struct asrc_p2p_params), GFP_KERNEL);
+ if (plat_data->priv)
+ memcpy(esai_asrc, plat_data->priv,
+ sizeof(struct asrc_p2p_params));
+ return 0;
+}
+
+static int __devexit imx_3stack_cs42888_remove(struct platform_device *pdev)
+{
+ if (esai_asrc)
+ kfree(esai_asrc);
+ return 0;
+}
+
+static struct platform_driver imx_3stack_cs42888_driver = {
+ .probe = imx_3stack_cs42888_probe,
+ .remove = __devexit_p(imx_3stack_cs42888_remove),
+ .driver = {
+ .name = "imx-cs42888",
+ .owner = THIS_MODULE,
+ },
+};
+
+static struct platform_device *imx_3stack_snd_device;
+
+static int __init imx_3stack_asoc_init(void)
+{
+ int ret;
+ pr_info("imx_3stack asoc driver\n");
+ ret = platform_driver_register(&imx_3stack_cs42888_driver);
+ if (ret < 0)
+ goto exit;
+
+ imx_3stack_snd_device = platform_device_alloc("soc-audio", 2);
+ if (!imx_3stack_snd_device)
+ goto err_device_alloc;
+ platform_set_drvdata(imx_3stack_snd_device, &snd_soc_card_imx_3stack);
+ ret = platform_device_add(imx_3stack_snd_device);
+ if (0 == ret)
+ goto exit;
+
+ platform_device_unregister(imx_3stack_snd_device);
+err_device_alloc:
+ platform_driver_unregister(&imx_3stack_cs42888_driver);
+exit:
+ return ret;
+}
+
+static void __exit imx_3stack_asoc_exit(void)
+{
+ platform_driver_unregister(&imx_3stack_cs42888_driver);
+ platform_device_unregister(imx_3stack_snd_device);
+}
+
+module_init(imx_3stack_asoc_init);
+module_exit(imx_3stack_asoc_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("ALSA SoC cs42888 Machine Layer Driver");
+MODULE_LICENSE("GPL");