summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorAlan Tull <alan.tull@freescale.com>2011-10-25 15:41:55 -0500
committerAlan Tull <alan.tull@freescale.com>2011-10-28 10:16:55 -0500
commit9ea5b20802d0246bf8de3a8d915ed0cc7cf65e3a (patch)
tree14c31a7cde158f495afe8b4134d369f278ed920b /sound
parent2f161b93baebacc99f5a454f4a58dc82d12084e3 (diff)
ENGR00160860-2 hdmi audio driver
Audio driver for i.Mx built-in HDMI Transmitter. * Uses HDMI Transmitter's built-in DMA. * Adds IEC958-style digital audio header info to the raw audio. * Gets pixel clock from the IPU driver and calculates clock regenerator values (cts and N). * Move ipu_id, and disp_id from the HDMI's platform data to the HDMI mfd's platform data. Saves them in the hdmi mfd. * Add mfd functionality to update the clock regenerator values when the hdmi changes the pixel clock rate or when requested from the audio driver with a new audio sample rate. Signed-off-by: Alan Tull <alan.tull@freescale.com>
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/codecs/Kconfig3
-rw-r--r--sound/soc/codecs/Makefile2
-rw-r--r--sound/soc/codecs/mxc_hdmi.c331
-rw-r--r--sound/soc/imx/Kconfig8
-rw-r--r--sound/soc/imx/Makefile2
-rw-r--r--sound/soc/imx/imx-hdmi-dai.c126
-rw-r--r--sound/soc/imx/imx-hdmi-dma.c836
-rw-r--r--sound/soc/imx/imx-hdmi.c87
-rw-r--r--sound/soc/imx/imx-hdmi.h89
9 files changed, 1484 insertions, 0 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 3ec3eb136d01..6224dfcd9ca2 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -175,6 +175,9 @@ config SND_SOC_DMIC
config SND_SOC_MAX98088
tristate
+config SND_SOC_MXC_HDMI
+ tristate
+
config SND_SOC_MXC_SPDIF
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 86cb162a0056..02a558257ac7 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -17,6 +17,7 @@ snd-soc-da7210-objs := da7210.o
snd-soc-dmic-objs := dmic.o
snd-soc-l3-objs := l3.o
snd-soc-max98088-objs := max98088.o
+snd-soc-mxc-hdmi-objs := mxc_hdmi.o
snd-soc-mxc-spdif-objs := mxc_spdif.o
snd-soc-pcm3008-objs := pcm3008.o
snd-soc-sgtl5000-objs := sgtl5000.o
@@ -100,6 +101,7 @@ obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o
obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o
obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o
obj-$(CONFIG_SND_SOC_MAX98088) += snd-soc-max98088.o
+obj-$(CONFIG_SND_SOC_MXC_HDMI) += snd-soc-mxc-hdmi.o
obj-$(CONFIG_SND_SOC_MXC_SPDIF) += snd-soc-mxc-spdif.o
obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o
obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o
diff --git a/sound/soc/codecs/mxc_hdmi.c b/sound/soc/codecs/mxc_hdmi.c
new file mode 100644
index 000000000000..c6a6bcbdbfd9
--- /dev/null
+++ b/sound/soc/codecs/mxc_hdmi.c
@@ -0,0 +1,331 @@
+/*
+ * MXC HDMI ALSA Soc Codec Driver
+ *
+ * Copyright (C) 2011 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 as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/fsl_devices.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/asoundef.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <mach/hardware.h>
+
+#include <linux/mfd/mxc-hdmi-core.h>
+#include <mach/mxc_hdmi.h>
+#include "../imx/imx-hdmi.h"
+
+struct mxc_hdmi_priv {
+ struct snd_soc_codec codec;
+ struct snd_card *card; /* ALSA HDMI sound card handle */
+ struct snd_pcm *pcm; /* ALSA hdmi driver type handle */
+ struct clk *isfr_clk;
+ struct clk *iahb_clk;
+};
+
+#ifdef DEBUG
+static void dumpregs(void)
+{
+ int n, cts;
+
+ cts = (hdmi_readb(HDMI_AUD_CTS3) << 16) |
+ (hdmi_readb(HDMI_AUD_CTS2) << 8) |
+ hdmi_readb(HDMI_AUD_CTS1);
+
+ n = (hdmi_readb(HDMI_AUD_N3) << 16) |
+ (hdmi_readb(HDMI_AUD_N2) << 8) |
+ hdmi_readb(HDMI_AUD_N1);
+
+ pr_debug("\n");
+ pr_debug("HDMI_PHY_CONF0 0x%02x\n", hdmi_readb(HDMI_PHY_CONF0));
+ pr_debug("HDMI_MC_CLKDIS 0x%02x\n", hdmi_readb(HDMI_MC_CLKDIS));
+ pr_debug("HDMI_AUD_N[1-3] 0x%06x (decimal %d)\n", n, n);
+ pr_debug("HDMI_AUD_CTS[1-3] 0x%06x (decimal %d)\n", cts, cts);
+ pr_debug("HDMI_FC_AUDSCONF 0x%02x\n", hdmi_readb(HDMI_FC_AUDSCONF));
+}
+#else
+static void dumpregs(void) {}
+#endif
+
+static void hdmi_set_audio_flat(u8 value)
+{
+ /* Indicates the subpacket represents a flatline sample */
+ hdmi_mask_writeb(value, HDMI_FC_AUDSCONF,
+ HDMI_FC_AUDSCONF_AUD_PACKET_SAMPFIT_OFFSET,
+ HDMI_FC_AUDSCONF_AUD_PACKET_SAMPFIT_MASK);
+}
+
+static void hdmi_set_layout(unsigned int channels)
+{
+ hdmi_mask_writeb((channels > 2) ? 1 : 0, HDMI_FC_AUDSCONF,
+ HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_OFFSET,
+ HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_MASK);
+}
+
+static void hdmi_set_audio_infoframe(void)
+{
+ /* set to 0: means "refer to stream header" */
+ hdmi_writeb(0x00, HDMI_FC_AUDICONF0);
+ hdmi_writeb(0x00, HDMI_FC_AUDICONF1);
+ hdmi_writeb(0x00, HDMI_FC_AUDICONF2);
+}
+
+static unsigned int hdmi_playback_rates[] = { 32000, 44100, 48000, 88200, 96000 };
+
+static struct snd_pcm_hw_constraint_list playback_rate_constraints = {
+ .count = ARRAY_SIZE(hdmi_playback_rates),
+ .list = hdmi_playback_rates,
+ .mask = 0,
+};
+
+static int mxc_hdmi_codec_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct mxc_hdmi_priv *hdmi_priv = snd_soc_dai_get_drvdata(dai);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int ret;
+
+ clk_enable(hdmi_priv->isfr_clk);
+ clk_enable(hdmi_priv->iahb_clk);
+
+ pr_debug("%s hdmi clks: isfr:%d iahb:%d\n", __func__,
+ (int)clk_get_rate(hdmi_priv->isfr_clk),
+ (int)clk_get_rate(hdmi_priv->iahb_clk));
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &playback_rate_constraints);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ hdmi_set_audio_flat(0);
+ hdmi_set_audio_infoframe();
+
+ return 0;
+}
+
+static int mxc_hdmi_codec_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ hdmi_set_layout(runtime->channels);
+ hdmi_set_sample_rate(runtime->rate);
+ dumpregs();
+
+ return 0;
+}
+
+static void mxc_hdmi_codec_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct mxc_hdmi_priv *hdmi_priv = snd_soc_dai_get_drvdata(dai);
+
+ clk_disable(hdmi_priv->iahb_clk);
+ clk_disable(hdmi_priv->isfr_clk);
+}
+
+/*
+ * IEC60958 status functions
+ */
+static int mxc_hdmi_iec_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+ uinfo->count = 1;
+
+ return 0;
+}
+
+static int mxc_hdmi_iec_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int i;
+
+ for (i = 0 ; i < 4 ; i++)
+ uvalue->value.iec958.status[i] = iec_header.status[i];
+
+ return 0;
+}
+
+static int mxc_hdmi_iec_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int i;
+
+ /* Do not allow professional mode */
+ if (uvalue->value.iec958.status[0] & IEC958_AES0_PROFESSIONAL)
+ return -EPERM;
+
+ for (i = 0 ; i < 4 ; i++) {
+ iec_header.status[i] = uvalue->value.iec958.status[i];
+ pr_debug("%s status[%d]=0x%02x\n", __func__, i,
+ iec_header.status[i]);
+ }
+
+ return 0;
+}
+
+static struct snd_kcontrol_new mxc_hdmi_ctrls[] = {
+ /* status cchanel controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = mxc_hdmi_iec_info,
+ .get = mxc_hdmi_iec_get,
+ .put = mxc_hdmi_iec_put,
+ },
+};
+
+static struct snd_soc_dai_ops mxc_hdmi_codec_dai_ops = {
+ .startup = mxc_hdmi_codec_startup,
+ .prepare = mxc_hdmi_codec_prepare,
+ .shutdown = mxc_hdmi_codec_shutdown,
+};
+
+static struct snd_soc_dai_driver mxc_hdmi_codec_dai = {
+ .name = "mxc-hdmi-soc",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 8,
+ .rates = MXC_HDMI_RATES_PLAYBACK,
+ .formats = MXC_HDMI_FORMATS_PLAYBACK,
+ },
+ .ops = &mxc_hdmi_codec_dai_ops,
+};
+
+static int mxc_hdmi_codec_soc_probe(struct snd_soc_codec *codec)
+{
+ struct mxc_hdmi_priv *hdmi_priv;
+ int ret = 0;
+
+ hdmi_priv = kzalloc(sizeof(struct mxc_hdmi_priv), GFP_KERNEL);
+ if (hdmi_priv == NULL)
+ return -ENOMEM;
+
+ hdmi_priv->isfr_clk = clk_get(NULL, "hdmi_isfr_clk");
+ if (IS_ERR(hdmi_priv->isfr_clk)) {
+ ret = PTR_ERR(hdmi_priv->isfr_clk);
+ pr_err("%s Unable to get HDMI isfr clk: %d\n", __func__, ret);
+ goto e_clk_get1;
+ }
+
+ hdmi_priv->iahb_clk = clk_get(NULL, "hdmi_iahb_clk");
+ if (IS_ERR(hdmi_priv->iahb_clk)) {
+ ret = PTR_ERR(hdmi_priv->iahb_clk);
+ pr_err("%s Unable to get HDMI iahb clk: %d\n", __func__, ret);
+ goto e_clk_get2;
+ }
+
+ ret = snd_soc_add_controls(codec, mxc_hdmi_ctrls,
+ ARRAY_SIZE(mxc_hdmi_ctrls));
+ if (ret)
+ goto e_add_ctrls;
+
+ snd_soc_codec_set_drvdata(codec, hdmi_priv);
+
+ return 0;
+
+e_add_ctrls:
+ clk_put(hdmi_priv->iahb_clk);
+e_clk_get2:
+ clk_put(hdmi_priv->isfr_clk);
+e_clk_get1:
+ kfree(hdmi_priv);
+ return ret;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_hdmi = {
+ .probe = mxc_hdmi_codec_soc_probe,
+};
+
+static int __devinit mxc_hdmi_codec_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+
+ dev_info(&pdev->dev, "MXC HDMI Audio\n");
+
+ ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_hdmi,
+ &mxc_hdmi_codec_dai, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register codec\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __devexit mxc_hdmi_codec_remove(struct platform_device *pdev)
+{
+ struct mxc_hdmi_priv *hdmi_priv = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_codec(&pdev->dev);
+ platform_set_drvdata(pdev, NULL);
+ kfree(hdmi_priv);
+
+ return 0;
+}
+
+struct platform_driver mxc_hdmi_driver = {
+ .driver = {
+ .name = "mxc_hdmi_soc",
+ .owner = THIS_MODULE,
+ },
+ .probe = mxc_hdmi_codec_probe,
+ .remove = __devexit_p(mxc_hdmi_codec_remove),
+};
+
+static int __init mxc_hdmi_codec_init(void)
+{
+ return platform_driver_register(&mxc_hdmi_driver);
+}
+
+static void __exit mxc_hdmi_codec_exit(void)
+{
+ return platform_driver_unregister(&mxc_hdmi_driver);
+}
+
+module_init(mxc_hdmi_codec_init);
+module_exit(mxc_hdmi_codec_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MXC HDMI Audio");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig
index db0848a5c973..cccf021b6927 100644
--- a/sound/soc/imx/Kconfig
+++ b/sound/soc/imx/Kconfig
@@ -87,4 +87,12 @@ config SND_SOC_IMX_SPDIF
Say Y if you want to add support for SoC audio on a IMX development
board with S/PDIF.
+config SND_SOC_IMX_HDMI
+ tristate "SoC Audio support for IMX - HDMI"
+ default n
+ select SND_SOC_MXC_HDMI
+ select MFD_MXC_HDMI
+ help
+ Say Y if you want to add support for SoC audio through IMX HDMI.
+
endif # SND_IMX_SOC
diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile
index a364c0faaff4..b4de1d882a4d 100644
--- a/sound/soc/imx/Makefile
+++ b/sound/soc/imx/Makefile
@@ -16,6 +16,7 @@ snd-soc-wm1133-ev1-objs := wm1133-ev1.o
snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o
snd-soc-imx-cs42888-objs := imx-cs42888.o
snd-soc-imx-spdif-objs := imx-spdif.o
+snd-soc-imx-hdmi-objs := imx-hdmi.o imx-hdmi-dai.o imx-hdmi-dma.o
obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o
obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o
@@ -23,3 +24,4 @@ obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o
obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o
obj-$(CONFIG_SND_SOC_IMX_CS42888) += snd-soc-imx-cs42888.o
obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o
+obj-$(CONFIG_SND_SOC_IMX_HDMI) += snd-soc-imx-hdmi.o
diff --git a/sound/soc/imx/imx-hdmi-dai.c b/sound/soc/imx/imx-hdmi-dai.c
new file mode 100644
index 000000000000..71cb3a56530f
--- /dev/null
+++ b/sound/soc/imx/imx-hdmi-dai.c
@@ -0,0 +1,126 @@
+/*
+ * ALSA SoC HDMI Audio Layer for MXS
+ *
+ * Copyright (C) 2011 Freescale Semiconductor, Inc.
+ *
+ * Based on stmp3xxx_spdif_dai.c
+ * Vladimir Barinov <vbarinov@embeddedalley.com>
+ * Copyright 2008 SigmaTel, Inc
+ * Copyright 2008 Embedded Alley Solutions, Inc
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <mach/dma.h>
+#include <mach/hardware.h>
+
+#include <linux/mfd/mxc-hdmi-core.h>
+#include "imx-hdmi.h"
+
+static struct snd_soc_dai_driver imx_hdmi_dai = {
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 8,
+ .rates = MXC_HDMI_RATES_PLAYBACK,
+ .formats = MXC_HDMI_FORMATS_PLAYBACK,
+ },
+};
+
+static int imx_hdmi_dai_probe(struct platform_device *pdev)
+{
+ struct imx_hdmi *hdmi_data;
+ int ret = 0;
+
+ hdmi_data = kzalloc(sizeof(*hdmi_data), GFP_KERNEL);
+ if (!hdmi_data)
+ return -ENOMEM;
+
+ dev_set_drvdata(&pdev->dev, hdmi_data);
+ hdmi_data->pdev = pdev;
+
+ hdmi_data->irq = platform_get_irq(pdev, 0);
+ if (hdmi_data->irq <= 0) {
+ dev_err(&pdev->dev, "MXC hdmi: invalid irq number (%d)\n",
+ hdmi_data->irq);
+ goto e_reg_dai;
+ }
+
+ ret = snd_soc_register_dai(&pdev->dev, &imx_hdmi_dai);
+ if (ret) {
+ dev_err(&pdev->dev, "register DAI failed\n");
+ goto e_reg_dai;
+ }
+
+ hdmi_data->soc_platform_pdev =
+ platform_device_alloc("imx-hdmi-soc-audio", 0);
+
+ if (!hdmi_data->soc_platform_pdev) {
+ dev_err(&pdev->dev, "failed platform_device_alloc\n");
+ goto e_alloc_dai;
+ }
+
+ platform_set_drvdata(hdmi_data->soc_platform_pdev, hdmi_data);
+
+ ret = platform_device_add(hdmi_data->soc_platform_pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add platform device\n");
+ platform_device_put(hdmi_data->soc_platform_pdev);
+ goto e_pdev_add;
+ }
+
+ return 0;
+e_pdev_add:
+ platform_device_put(hdmi_data->soc_platform_pdev);
+e_alloc_dai:
+ snd_soc_unregister_dai(&pdev->dev);
+e_reg_dai:
+ kfree(hdmi_data);
+ return ret;
+}
+
+static int __devexit imx_hdmi_dai_remove(struct platform_device *pdev)
+{
+ struct imx_hdmi *hdmi_data = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_dai(&pdev->dev);
+ kfree(hdmi_data);
+
+ return 0;
+}
+
+static struct platform_driver imx_hdmi_driver = {
+ .probe = imx_hdmi_dai_probe,
+ .remove = __devexit_p(imx_hdmi_dai_remove),
+ .driver = {
+ .name = "imx-hdmi-soc-dai",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init imx_hdmi_dai_init(void)
+{
+ return platform_driver_register(&imx_hdmi_driver);
+}
+
+static void __exit imx_hdmi_dai_exit(void)
+{
+ platform_driver_unregister(&imx_hdmi_driver);
+}
+
+module_init(imx_hdmi_dai_init);
+module_exit(imx_hdmi_dai_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("IMX HDMI TX DAI");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-hdmi-dma.c b/sound/soc/imx/imx-hdmi-dma.c
new file mode 100644
index 000000000000..1c875a2e058d
--- /dev/null
+++ b/sound/soc/imx/imx-hdmi-dma.c
@@ -0,0 +1,836 @@
+/*
+ * imx-hdmi-dma.c -- HDMI DMA driver for ALSA Soc Audio Layer
+ *
+ * Copyright (C) 2011 Freescale Semiconductor, Inc.
+ *
+ * based on imx-pcm-dma-mx2.c
+ * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This code is based on code copyrighted by Freescale,
+ * Liam Girdwood, Javier Martin and probably others.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <linux/mfd/mxc-hdmi-core.h>
+#include <mach/mxc_hdmi.h>
+#include "imx-hdmi.h"
+
+struct imx_hdmi_dma_runtime_data {
+ struct snd_pcm_substream *tx_substream;
+
+ unsigned long buffer_bytes;
+ void *buf;
+ int period_time;
+
+ int periods;
+ int period_bytes;
+ int dma_period_bytes;
+ int buffer_ratio;
+
+ unsigned long offset;
+
+ int channels;
+ snd_pcm_format_t format;
+ int rate;
+ int sample_align;
+ int sample_bits;
+
+ int frame_idx;
+
+ int irq;
+ struct clk *isfr_clk;
+ struct clk *iahb_clk;
+
+ bool tx_active;
+ spinlock_t irq_lock;
+};
+
+hdmi_audio_header_t iec_header;
+
+/*
+ * Note that the period size for DMA != period size for ALSA because the
+ * driver adds iec frame info to the audio samples (in hdmi_dma_copy).
+ *
+ * Each 4 byte subframe = 1 byte of iec data + 3 byte audio sample.
+ *
+ * A 16 bit audio sample becomes 32 bits including the frame info. Ratio=2
+ * A 24 bit audio sample becomes 32 bits including the frame info. Ratio=3:4
+ * If the 24 bit raw audio is in 32 bit words, the
+ *
+ * Original Packed into subframe Ratio of size Format
+ * sample how many size of DMA buffer
+ * (bits) bits to ALSA buffer
+ * -------- ----------- -------- -------------- ------------------------
+ * 16 16 32 2 SNDRV_PCM_FORMAT_S16_LE
+ * 24 24 32 1.33 SNDRV_PCM_FORMAT_S24_3LE*
+ * 24 32 32 1 SNDRV_PCM_FORMAT_S24_LE
+ *
+ * *so SNDRV_PCM_FORMAT_S24_3LE is not supported.
+ */
+
+/*
+ * The minimum dma period is one IEC audio frame (192 * 4 * channels).
+ * The maximum dma period for the HDMI DMA is 8K.
+ *
+ * channels minimum maximum
+ * dma period dma period
+ * -------- ------------------ ----------
+ * 2 192 * 4 * 2 = 1536 * 4 = 6144
+ * 4 192 * 4 * 4 = 3072 * 2 = 6144
+ * 6 192 * 4 * 6 = 4608 * 1 = 4608
+ * 8 192 * 4 * 8 = 6144 * 1 = 6144
+ *
+ * Bottom line:
+ * 1. Must keep the ratio of DMA buffer to ALSA buffer consistent.
+ * 2. frame_idx is saved in the private data, so even if a frame cannot be
+ * transmitted in a period, it can be continued in the next period. This
+ * is necessary for 6 ch.
+ */
+#define HDMI_DMA_PERIOD_BYTES (6144)
+#define HDMI_DMA_BUF_SIZE (64 * 1024)
+
+struct imx_hdmi_dma_runtime_data *hdmi_dma_priv;
+
+#ifdef DEBUG
+static void dumpregs(void)
+{
+ pr_debug("\n");
+ pr_debug("HDMI_AHB_DMA_CONF0 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_CONF0));
+ pr_debug("HDMI_AHB_DMA_START 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_START));
+ pr_debug("HDMI_AHB_DMA_STOP 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_STOP));
+ pr_debug("HDMI_AHB_DMA_THRSLD 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_THRSLD));
+ pr_debug("HDMI_AHB_DMA_STRADDR[0-3] 0x%08x\n", hdmi_read4(HDMI_AHB_DMA_STRADDR0));
+ pr_debug("HDMI_AHB_DMA_STPADDR[0-3] 0x%08x\n", hdmi_read4(HDMI_AHB_DMA_STPADDR0));
+ pr_debug("HDMI_AHB_DMA_BSTADDR[0-3] 0x%08x\n", hdmi_read4(HDMI_AHB_DMA_BSTADDR0));
+ pr_debug("HDMI_AHB_DMA_MBLENGTH0 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_MBLENGTH0));
+ pr_debug("HDMI_AHB_DMA_MBLENGTH1 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_MBLENGTH1));
+ pr_debug("HDMI_AHB_DMA_STAT 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_STAT));
+ pr_debug("HDMI_AHB_DMA_INT 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_INT));
+ pr_debug("HDMI_AHB_DMA_MASK 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_MASK));
+ pr_debug("HDMI_AHB_DMA_POL 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_POL));
+ pr_debug("HDMI_AHB_DMA_CONF1 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_CONF1));
+ pr_debug("HDMI_AHB_DMA_BUFFSTAT 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_BUFFSTAT));
+ pr_debug("HDMI_AHB_DMA_BUFFINT 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_BUFFINT));
+ pr_debug("HDMI_AHB_DMA_BUFFMASK 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_BUFFMASK));
+ pr_debug("HDMI_AHB_DMA_BUFFPOL 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_BUFFPOL));
+ pr_debug("HDMI_IH_MUTE_AHBDMAAUD_STAT0 0x%02x\n", hdmi_readb(HDMI_IH_MUTE_AHBDMAAUD_STAT0));
+ pr_debug("HDMI_IH_AHBDMAAUD_STAT0 0x%02x\n", hdmi_readb(HDMI_IH_AHBDMAAUD_STAT0));
+ pr_debug("HDMI_IH_MUTE 0x%02x\n", hdmi_readb(HDMI_IH_MUTE));
+ pr_debug("\n");
+}
+
+static void dumprtd(struct imx_hdmi_dma_runtime_data *rtd)
+{
+ pr_debug("\n");
+ pr_debug("channels = %d\n", rtd->channels);
+ pr_debug("periods = %d\n", rtd->periods);
+ pr_debug("period_bytes = %d\n", rtd->period_bytes);
+ pr_debug("dma period_bytes = %d\n", rtd->dma_period_bytes);
+ pr_debug("buffer_ratio = %d\n", rtd->buffer_ratio);
+ pr_debug("dma buf addr = 0x%08x\n", (int)rtd->buf);
+ pr_debug("dma buf size = %d\n", (int)rtd->buffer_bytes);
+ pr_debug("sample_rate = %d\n", (int)rtd->rate);
+}
+#else
+static void dumpregs(void)
+{
+}
+
+static void dumprtd(struct imx_hdmi_dma_runtime_data *rtd)
+{
+}
+#endif
+
+static void hdmi_dma_start(void)
+{
+ hdmi_mask_writeb(1, HDMI_AHB_DMA_START,
+ HDMI_AHB_DMA_START_START_OFFSET,
+ HDMI_AHB_DMA_START_START_MASK);
+}
+
+static void hdmi_dma_stop(void)
+{
+ hdmi_mask_writeb(1, HDMI_AHB_DMA_STOP,
+ HDMI_AHB_DMA_STOP_STOP_OFFSET,
+ HDMI_AHB_DMA_STOP_STOP_MASK);
+}
+
+static void hdmi_fifo_reset(void)
+{
+ hdmi_mask_writeb(1, HDMI_AHB_DMA_CONF0,
+ HDMI_AHB_DMA_CONF0_SW_FIFO_RST_OFFSET,
+ HDMI_AHB_DMA_CONF0_SW_FIFO_RST_MASK);
+}
+
+/*
+ * Conditions for DMA to work:
+ * ((final_addr - initial_addr)>>2)+1) < 2k. So max period is 8k.
+ * (inital_addr & 0x3) == 0
+ * (final_addr & 0x3) == 0x3
+ *
+ * The DMA Period should be an integer multiple of the IEC 60958 audio
+ * frame size, which is 768 bytes (192 * 4).
+ */
+static void hdmi_dma_set_addr(int start_addr, int dma_period_bytes)
+{
+ int final_addr = start_addr + dma_period_bytes - 1;
+
+ hdmi_write4(start_addr, HDMI_AHB_DMA_STRADDR0);
+ hdmi_write4(final_addr, HDMI_AHB_DMA_STPADDR0);
+}
+
+static u8 hdmi_dma_get_irq_status(void)
+{
+ return hdmi_readb(HDMI_IH_AHBDMAAUD_STAT0);
+}
+
+static void hdmi_dma_clear_irq_status(u8 status)
+{
+ hdmi_writeb(status, HDMI_IH_AHBDMAAUD_STAT0);
+}
+
+static void hdmi_dma_irq_mask(int mask)
+{
+ if (mask)
+ hdmi_writeb(0xff, HDMI_AHB_DMA_MASK);
+ else
+ hdmi_writeb((u8)~HDMI_AHB_DMA_DONE, HDMI_AHB_DMA_MASK);
+}
+
+static void hdmi_dma_irq_mute(int mute)
+{
+ if (mute)
+ hdmi_writeb(0xff, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
+ else
+ hdmi_writeb(0x00, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
+}
+
+static irqreturn_t hdmi_dma_isr(int irq, void *dev_id)
+{
+ struct imx_hdmi_dma_runtime_data *rtd = dev_id;
+ struct snd_pcm_substream *substream = rtd->tx_substream;
+ struct snd_dma_buffer *dma_buffer;
+ unsigned int status;
+ unsigned int dma_offset;
+ unsigned long flags;
+
+ spin_lock_irqsave(&rtd->irq_lock, flags);
+
+ if (!rtd->tx_active) {
+ spin_unlock_irqrestore(&rtd->irq_lock, flags);
+ return IRQ_HANDLED;
+ }
+
+ hdmi_dma_irq_mute(1);
+ status = hdmi_dma_get_irq_status();
+ hdmi_dma_clear_irq_status(status);
+
+ if (status & HDMI_IH_AHBDMAAUD_STAT0_DONE) {
+ rtd->offset += rtd->period_bytes;
+ rtd->offset %= rtd->period_bytes * rtd->periods;
+
+ dma_offset = rtd->offset * rtd->buffer_ratio;
+
+ snd_pcm_period_elapsed(substream);
+
+ pr_debug("HDMI DMA Done interrupt: period_bytes=%d offset=%d\n",
+ rtd->period_bytes, (int)rtd->offset);
+
+ dma_buffer = &substream->dma_buffer;
+
+ hdmi_dma_set_addr(dma_buffer->addr + dma_offset,
+ rtd->dma_period_bytes);
+
+ hdmi_dma_start();
+ }
+
+ hdmi_dma_irq_mute(0);
+
+ spin_unlock_irqrestore(&rtd->irq_lock, flags);
+ return IRQ_HANDLED;
+}
+
+static void hdmi_dma_enable_hlock(int enable)
+{
+ hdmi_mask_writeb(enable, HDMI_AHB_DMA_CONF0,
+ HDMI_AHB_DMA_CONF0_EN_HLOCK_OFFSET,
+ HDMI_AHB_DMA_CONF0_EN_HLOCK_MASK);
+}
+
+static void hdmi_dma_set_incr_type(int incr_type)
+{
+ u8 value = hdmi_readb(HDMI_AHB_DMA_CONF0) &
+ ~(HDMI_AHB_DMA_CONF0_BURST_MODE |
+ HDMI_AHB_DMA_CONF0_INCR_TYPE_MASK);
+
+ switch (incr_type) {
+ case 1:
+ break;
+ case 4:
+ value |= HDMI_AHB_DMA_CONF0_BURST_MODE;
+ break;
+ case 8:
+ value |= HDMI_AHB_DMA_CONF0_BURST_MODE |
+ HDMI_AHB_DMA_CONF0_INCR8;
+ break;
+ case 16:
+ value |= HDMI_AHB_DMA_CONF0_BURST_MODE |
+ HDMI_AHB_DMA_CONF0_INCR16;
+ break;
+ default:
+ pr_err("%s: invalid increment type: %d", __func__, incr_type);
+ BUG();
+ return;
+ }
+
+ hdmi_writeb(value, HDMI_AHB_DMA_CONF0);
+}
+
+static void hdmi_dma_enable_channels(int channels)
+{
+ switch (channels) {
+ case 2:
+ hdmi_writeb(0x03, HDMI_AHB_DMA_CONF1);
+ break;
+ case 4:
+ hdmi_writeb(0x0f, HDMI_AHB_DMA_CONF1);
+ break;
+ case 6:
+ hdmi_writeb(0x3f, HDMI_AHB_DMA_CONF1);
+ break;
+ case 8:
+ hdmi_writeb(0xff, HDMI_AHB_DMA_CONF1);
+ break;
+ default:
+ WARN(1, "%s - invalid audio channels number: %d\n",
+ __func__, channels);
+ break;
+ }
+}
+
+static void hdmi_dma_configure_dma(int channels)
+{
+ hdmi_dma_enable_hlock(1);
+ hdmi_dma_set_incr_type(4);
+ hdmi_writeb(64, HDMI_AHB_DMA_THRSLD);
+ hdmi_dma_enable_channels(channels);
+}
+
+
+static void hdmi_dma_init_iec_header(void)
+{
+ iec_header.U = 0;
+
+ iec_header.B.consumer = 0; /* Consumer use */
+ iec_header.B.linear_pcm = 0; /* linear pcm audio */
+ iec_header.B.copyright = 1; /* no copyright */
+ iec_header.B.pre_emphasis = 0; /* 2 channels without pre-emphasis */
+ iec_header.B.mode = 0; /* Mode 0 */
+
+ iec_header.B.category_code = 0;
+
+ iec_header.B.source = 2; /* stereo */
+ iec_header.B.channel = 0;
+
+ iec_header.B.sample_freq = 0x02; /* 48 KHz */
+ iec_header.B.clock_acc = 0; /* Level II */
+
+ iec_header.B.word_length = 0x02; /* 16 bits */
+ iec_header.B.org_sample_freq = 0x0D; /* 48 KHz */
+
+ iec_header.B.cgms_a = 0; /* Copying is permitted without restriction */
+}
+
+static int hdmi_dma_update_iec_header(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_hdmi_dma_runtime_data *rtd = runtime->private_data;
+
+ iec_header.B.source = rtd->channels;
+
+ switch (rtd->rate) {
+ case 32000:
+ iec_header.B.sample_freq = 0x03;
+ iec_header.B.org_sample_freq = 0x0C;
+ break;
+ case 44100:
+ iec_header.B.sample_freq = 0x00;
+ iec_header.B.org_sample_freq = 0x0F;
+ break;
+ case 48000:
+ iec_header.B.sample_freq = 0x02;
+ iec_header.B.org_sample_freq = 0x0D;
+ break;
+ case 96000:
+ iec_header.B.sample_freq = 0x0A;
+ iec_header.B.org_sample_freq = 0x05;
+ break;
+ case 192000:
+ iec_header.B.sample_freq = 0x0E;
+ iec_header.B.org_sample_freq = 0x01;
+ break;
+ default:
+ pr_err("HDMI Audio sample rate error");
+ return -EFAULT;
+ }
+
+ switch (rtd->format) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ iec_header.B.word_length = 0x02;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iec_header.B.word_length = 0x0b;
+ break;
+ default:
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+/*
+ * The HDMI block transmits the audio data without adding any of the audio
+ * frame bits. So we have to copy the raw dma data from the ALSA buffer
+ * to the DMA buffer, adding the frame information.
+ */
+static int hdmi_dma_copy(struct snd_pcm_substream *substream, int channel,
+ snd_pcm_uframes_t pos, void __user *buf,
+ snd_pcm_uframes_t frames)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_hdmi_dma_runtime_data *rtd = runtime->private_data;
+ unsigned int count = frames_to_bytes(runtime, frames);
+ unsigned int pos_bytes = frames_to_bytes(runtime, pos);
+
+ u8 *addr_start;
+ u8 *addr_end;
+ u32 *addr_cur;
+ int subframe_idx, i;
+ hdmi_audio_dma_data_t hdmi_audio_dma_data;
+ u32 pcm_data;
+
+ addr_start = rtd->buf + (pos_bytes * rtd->buffer_ratio);
+ addr_end = addr_start + (frames * rtd->channels * 4);
+ addr_cur = (u32 *)addr_start;
+
+ while ((count > 0) && ((u32)addr_cur < (u32)addr_end)) {
+ for (subframe_idx = 1 ; subframe_idx <= rtd->channels ; subframe_idx++) {
+ pcm_data = 0;
+ if (copy_from_user(&pcm_data, buf, rtd->sample_align))
+ return -EFAULT;
+ buf += rtd->sample_align;
+ count -= rtd->sample_align;
+
+ hdmi_audio_dma_data.U = 0;
+ iec_header.B.channel = subframe_idx;
+
+ /* fill b (start-of-block) */
+ hdmi_audio_dma_data.B.b = (rtd->frame_idx == 0) ? 1 : 0;
+
+ /* fill c (channel status) */
+ if (rtd->frame_idx < 42)
+ hdmi_audio_dma_data.B.c =
+ (iec_header.U >> rtd->frame_idx) & 0x1;
+ else
+ hdmi_audio_dma_data.B.c = 0;
+
+ /* fill p (parity) */
+ for (i = 0 ; i < rtd->sample_bits ; i++)
+ hdmi_audio_dma_data.B.p ^= (pcm_data >> i) & 0x01;
+ hdmi_audio_dma_data.B.p ^= hdmi_audio_dma_data.B.c;
+ hdmi_audio_dma_data.B.p ^= hdmi_audio_dma_data.B.u;
+ hdmi_audio_dma_data.B.p ^= hdmi_audio_dma_data.B.v;
+
+ /* fill data */
+ if (rtd->sample_bits == 16)
+ hdmi_audio_dma_data.B.data = pcm_data << 8;
+ else
+ hdmi_audio_dma_data.B.data = pcm_data;
+
+ *addr_cur = hdmi_audio_dma_data.U;
+ addr_cur++;
+ }
+
+ rtd->frame_idx++;
+ if (rtd->frame_idx == 192)
+ rtd->frame_idx = 0;
+ }
+
+ return 0;
+}
+
+static int hdmi_dma_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_hdmi_dma_runtime_data *rtd = runtime->private_data;
+ struct snd_dma_buffer *dma_buffer = &substream->dma_buffer;
+
+ rtd->buffer_bytes = params_buffer_bytes(params);
+ rtd->periods = params_periods(params);
+ rtd->period_bytes = params_period_bytes(params);
+ rtd->channels = params_channels(params);
+ rtd->format = params_format(params);
+ rtd->rate = params_rate(params);
+
+ rtd->offset = 0;
+ rtd->period_time = HZ / (params_rate(params) / params_period_size(params));
+ rtd->buf = (unsigned int *)dma_buffer->area;
+
+ switch (rtd->format) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ rtd->buffer_ratio = 2;
+ rtd->sample_align = 2;
+ rtd->sample_bits = 16;
+ break;
+
+ case SNDRV_PCM_FORMAT_S24_LE: /* 24 bit audio in 32 bit word */
+ rtd->buffer_ratio = 1;
+ rtd->sample_align = 4;
+ rtd->sample_bits = 24;
+ break;
+ default:
+ pr_err("%s HDMI Audio invalid sample format (%d)\n",
+ __func__, rtd->format);
+ return -EINVAL;
+ }
+
+ rtd->dma_period_bytes = rtd->period_bytes * rtd->buffer_ratio;
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ hdmi_dma_configure_dma(rtd->channels);
+ hdmi_dma_set_addr(dma_buffer->addr, rtd->dma_period_bytes);
+
+ dumprtd(rtd);
+
+ hdmi_dma_update_iec_header(substream);
+
+ return 0;
+}
+
+static int hdmi_dma_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_hdmi_dma_runtime_data *rtd = runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ rtd->frame_idx = 0;
+ dumpregs();
+ hdmi_dma_start();
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ hdmi_dma_stop();
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t hdmi_dma_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_hdmi_dma_runtime_data *rtd = runtime->private_data;
+
+ return bytes_to_frames(substream->runtime, rtd->offset);
+}
+
+static struct snd_pcm_hardware snd_imx_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .formats = MXC_HDMI_FORMATS_PLAYBACK,
+ .rate_min = 32000,
+ .channels_min = 2,
+ .channels_max = 8,
+ .buffer_bytes_max = HDMI_DMA_BUF_SIZE / 2,
+ .period_bytes_min = HDMI_DMA_PERIOD_BYTES / 2,
+ .period_bytes_max = HDMI_DMA_PERIOD_BYTES / 2,
+ .periods_min = 4,
+ .periods_max = 255,
+ .fifo_size = 0,
+};
+
+static unsigned int hw_channels[] = {2, 4, 6, 8};
+
+static struct snd_pcm_hw_constraint_list hw_constraint_channels = {
+ .count = ARRAY_SIZE(hw_channels),
+ .list = hw_channels,
+ .mask = 0,
+};
+
+static void hdmi_dma_irq_enable(struct imx_hdmi_dma_runtime_data *rtd)
+{
+ unsigned long flags;
+
+ hdmi_writeb(0xff, HDMI_AHB_DMA_POL);
+ hdmi_writeb(0xff, HDMI_AHB_DMA_BUFFPOL);
+
+ spin_lock_irqsave(&hdmi_dma_priv->irq_lock, flags);
+
+ hdmi_dma_clear_irq_status(0xff);
+ hdmi_irq_enable(hdmi_dma_priv->irq);
+ hdmi_dma_irq_mute(0);
+ hdmi_dma_irq_mask(0);
+ hdmi_dma_priv->tx_active = true;
+
+ spin_unlock_irqrestore(&hdmi_dma_priv->irq_lock, flags);
+
+}
+
+static void hdmi_dma_irq_disable(struct imx_hdmi_dma_runtime_data *rtd)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&rtd->irq_lock, flags);
+
+ hdmi_dma_irq_mask(1);
+ hdmi_dma_irq_mute(1);
+ hdmi_irq_disable(rtd->irq);
+ hdmi_dma_clear_irq_status(0xff);
+ rtd->tx_active = false;
+
+ spin_unlock_irqrestore(&rtd->irq_lock, flags);
+}
+
+static int hdmi_dma_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int ret;
+
+ runtime->private_data = hdmi_dma_priv;
+
+ clk_enable(hdmi_dma_priv->isfr_clk);
+ clk_enable(hdmi_dma_priv->iahb_clk);
+
+ pr_debug("%s hdmi clks: isfr:%d iahb:%d\n", __func__,
+ (int)clk_get_rate(hdmi_dma_priv->isfr_clk),
+ (int)clk_get_rate(hdmi_dma_priv->iahb_clk));
+
+ hdmi_fifo_reset();
+
+ ret = snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ &hw_constraint_channels);
+ if (ret < 0)
+ return ret;
+
+ snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
+
+ hdmi_dma_irq_enable(hdmi_dma_priv);
+
+ return 0;
+}
+
+static int hdmi_dma_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_hdmi_dma_runtime_data *rtd = runtime->private_data;
+
+ hdmi_dma_irq_disable(rtd);
+
+ clk_disable(rtd->iahb_clk);
+ clk_disable(rtd->isfr_clk);
+
+ return 0;
+}
+
+static struct snd_pcm_ops imx_hdmi_dma_pcm_ops = {
+ .open = hdmi_dma_open,
+ .close = hdmi_dma_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = hdmi_dma_hw_params,
+ .trigger = hdmi_dma_trigger,
+ .pointer = hdmi_dma_pointer,
+ .copy = hdmi_dma_copy,
+};
+
+static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size = HDMI_DMA_BUF_SIZE;
+
+ buf->area = dma_alloc_writecombine(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area)
+ return -ENOMEM;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+ buf->bytes = size;
+
+ hdmi_dma_priv->tx_substream = substream;
+
+ return 0;
+}
+
+static u64 hdmi_dmamask = DMA_BIT_MASK(32);
+
+static int imx_hdmi_dma_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &hdmi_dmamask;
+
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ ret = imx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+out:
+ return ret;
+}
+
+static void imx_hdmi_dma_pcm_free(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+
+ dma_free_writecombine(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+ }
+}
+
+static struct snd_soc_platform_driver imx_soc_platform_mx2 = {
+ .ops = &imx_hdmi_dma_pcm_ops,
+ .pcm_new = imx_hdmi_dma_pcm_new,
+ .pcm_free = imx_hdmi_dma_pcm_free,
+};
+
+static int __devinit imx_soc_platform_probe(struct platform_device *pdev)
+{
+ struct imx_hdmi *hdmi_drvdata = platform_get_drvdata(pdev);
+ int ret = 0;
+
+ hdmi_dma_priv = kzalloc(sizeof(*hdmi_dma_priv), GFP_KERNEL);
+ if (hdmi_dma_priv == NULL)
+ return -ENOMEM;
+
+ hdmi_dma_priv->tx_active = false;
+ spin_lock_init(&hdmi_dma_priv->irq_lock);
+ hdmi_dma_priv->irq = hdmi_drvdata->irq;
+
+ hdmi_dma_init_iec_header();
+
+ /* Initialize IRQ at HDMI core level */
+ hdmi_irq_init();
+
+ hdmi_dma_priv->isfr_clk = clk_get(&pdev->dev, "hdmi_isfr_clk");
+ if (IS_ERR(hdmi_dma_priv->isfr_clk)) {
+ ret = PTR_ERR(hdmi_dma_priv->isfr_clk);
+ dev_err(&pdev->dev, "Unable to get HDMI isfr clk: %d\n", ret);
+ goto e_clk_get1;
+ }
+
+ hdmi_dma_priv->iahb_clk = clk_get(&pdev->dev, "hdmi_iahb_clk");
+ if (IS_ERR(hdmi_dma_priv->iahb_clk)) {
+ ret = PTR_ERR(hdmi_dma_priv->iahb_clk);
+ dev_err(&pdev->dev, "Unable to get HDMI ahb clk: %d\n", ret);
+ goto e_clk_get2;
+ }
+
+ /* The HDMI block's irq line is shared with HDMI video. */
+ if (request_irq(hdmi_dma_priv->irq, hdmi_dma_isr, IRQF_SHARED,
+ "hdmi dma", hdmi_dma_priv)) {
+ dev_err(&pdev->dev, "MXC hdmi: failed to request irq %d\n",
+ hdmi_dma_priv->irq);
+ ret = -EBUSY;
+ goto e_irq;
+ }
+
+ ret = snd_soc_register_platform(&pdev->dev, &imx_soc_platform_mx2);
+ if (ret)
+ goto e_irq;
+
+ return 0;
+
+e_irq:
+ clk_put(hdmi_dma_priv->iahb_clk);
+e_clk_get2:
+ clk_put(hdmi_dma_priv->isfr_clk);
+e_clk_get1:
+ kfree(hdmi_dma_priv);
+
+ return ret;
+}
+
+static int __devexit imx_soc_platform_remove(struct platform_device *pdev)
+{
+ free_irq(hdmi_dma_priv->irq, hdmi_dma_priv);
+ snd_soc_unregister_platform(&pdev->dev);
+ kfree(hdmi_dma_priv);
+ return 0;
+}
+
+static struct platform_driver imx_hdmi_dma_driver = {
+ .driver = {
+ .name = "imx-hdmi-soc-audio",
+ .owner = THIS_MODULE,
+ },
+ .probe = imx_soc_platform_probe,
+ .remove = __devexit_p(imx_soc_platform_remove),
+};
+
+static int __init hdmi_dma_init(void)
+{
+ return platform_driver_register(&imx_hdmi_dma_driver);
+}
+module_init(hdmi_dma_init);
+
+static void __exit hdmi_dma_exit(void)
+{
+ platform_driver_unregister(&imx_hdmi_dma_driver);
+}
+module_exit(hdmi_dma_exit);
diff --git a/sound/soc/imx/imx-hdmi.c b/sound/soc/imx/imx-hdmi.c
new file mode 100644
index 000000000000..f2316c1fe081
--- /dev/null
+++ b/sound/soc/imx/imx-hdmi.c
@@ -0,0 +1,87 @@
+/*
+ * ASoC HDMI Transmitter driver for IMX development boards
+ *
+ * Copyright (C) 2011 Freescale Semiconductor, Inc.
+ *
+ * based on stmp3780_devb_spdif.c
+ *
+ * Vladimir Barinov <vbarinov@embeddedalley.com>
+ *
+ * Copyright 2008 SigmaTel, Inc
+ * Copyright 2008 Embedded Alley Solutions, Inc
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <asm/dma.h>
+#include <mach/hardware.h>
+
+#include <mach/mxc_hdmi.h>
+#include <linux/mfd/mxc-hdmi-core.h>
+#include "imx-hdmi.h"
+
+/* imx digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link imx_hdmi_dai_link = {
+ .name = "IMX HDMI TX",
+ .stream_name = "IMX HDMI TX",
+ .codec_dai_name = "mxc-hdmi-soc",
+ .codec_name = "mxc_hdmi_soc.0",
+ .cpu_dai_name = "imx-hdmi-soc-dai.0",
+ .platform_name = "imx-hdmi-soc-audio.0",
+};
+
+static struct snd_soc_card snd_soc_card_imx_hdmi = {
+ .name = "imx-hdmi-soc",
+ .dai_link = &imx_hdmi_dai_link,
+ .num_links = 1,
+};
+
+static struct platform_device *imx_hdmi_snd_device;
+
+static int __init imx_hdmi_init(void)
+{
+ int ret = 0;
+
+ imx_hdmi_snd_device = platform_device_alloc("soc-audio", 4);
+ if (!imx_hdmi_snd_device) {
+ pr_err("%s - failed platform_device_alloc\n", __func__);
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(imx_hdmi_snd_device, &snd_soc_card_imx_hdmi);
+
+ ret = platform_device_add(imx_hdmi_snd_device);
+ if (ret) {
+ pr_err("ASoC HDMI TX: Platform device allocation failed\n");
+ platform_device_put(imx_hdmi_snd_device);
+ }
+
+ return ret;
+}
+
+static void __exit imx_hdmi_exit(void)
+{
+ platform_device_unregister(imx_hdmi_snd_device);
+}
+
+module_init(imx_hdmi_init);
+module_exit(imx_hdmi_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("IMX HDMI TX ASoC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-hdmi.h b/sound/soc/imx/imx-hdmi.h
new file mode 100644
index 000000000000..52c58dd516d5
--- /dev/null
+++ b/sound/soc/imx/imx-hdmi.h
@@ -0,0 +1,89 @@
+/*
+ * MXC HDMI ALSA Soc Codec Driver
+ *
+ * Copyright (C) 2011 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 as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __IMX_HDMI_H
+#define __IMX_HDMI_H
+
+#define DRV_NAME "imx-hdmi"
+
+struct imx_hdmi {
+ int irq;
+ struct platform_device *pdev;
+ struct platform_device *soc_platform_pdev;
+};
+
+#define MXC_HDMI_RATES_PLAYBACK (SNDRV_PCM_RATE_32000 | \
+ SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | \
+ SNDRV_PCM_RATE_88200 | \
+ SNDRV_PCM_RATE_96000)
+
+#define MXC_HDMI_FORMATS_PLAYBACK (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+typedef union {
+ uint64_t U;
+ struct {
+ unsigned consumer:1;
+ unsigned linear_pcm:1;
+ unsigned copyright:1;
+ unsigned pre_emphasis:3;
+ unsigned mode:2;
+
+ unsigned category_code:8;
+
+ unsigned source:4;
+ unsigned channel:4;
+
+ unsigned sample_freq:4;
+ unsigned clock_acc:2;
+ unsigned reserved0:2;
+
+ unsigned word_length:4;
+ unsigned org_sample_freq:4;
+
+ unsigned cgms_a:2;
+ unsigned reserved1:6;
+
+ unsigned reserved2:8;
+
+ unsigned reserved3:8;
+ } B;
+ unsigned char status[8];
+} hdmi_audio_header_t;
+
+typedef union {
+ uint32_t U;
+ struct {
+ unsigned data:24;
+ unsigned v:1;
+ unsigned u:1;
+ unsigned c:1;
+ unsigned p:1;
+ unsigned b:1;
+ unsigned reserved:3;
+ } B;
+} hdmi_audio_dma_data_t;
+
+extern hdmi_audio_header_t iec_header;
+
+#endif /* __IMX_HDMI_H */