]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
ENGR00330403-2: ASoC: fsl_hdmi: port hdmi audio driver from imx_3.10.y
authorShengjiu Wang <shengjiu.wang@freescale.com>
Wed, 27 Aug 2014 06:52:50 +0000 (14:52 +0800)
committerNitin Garg <nitin.garg@freescale.com>
Fri, 16 Jan 2015 03:17:12 +0000 (21:17 -0600)
Port HDMI audio driver (CPU driver, machine driver, platform driver) from
imx_3.10.y.

Signed-off-by: Shengjiu Wang <shengjiu.wang@freescale.com>
sound/soc/fsl/Kconfig
sound/soc/fsl/Makefile
sound/soc/fsl/fsl_hdmi.c [new file with mode: 0644]
sound/soc/fsl/hdmi_pcm.S [new file with mode: 0644]
sound/soc/fsl/imx-hdmi-dma.c [new file with mode: 0644]
sound/soc/fsl/imx-hdmi.c [new file with mode: 0644]
sound/soc/fsl/imx-hdmi.h [new file with mode: 0644]

index 59f8ef263e675dffc09eff3eb14dbc21ce4e0ed4..08f11f50234468d31ff67888431cb268306732eb 100644 (file)
@@ -58,6 +58,9 @@ config SND_SOC_FSL_ESAI
 config SND_SOC_FSL_UTILS
        tristate
 
+config SND_SOC_FSL_HDMI
+       tristate
+
 config SND_SOC_IMX_PCM_DMA
        tristate
        select SND_SOC_GENERIC_DMAENGINE_PCM
@@ -180,6 +183,11 @@ config SND_SOC_IMX_PCM_FIQ
        tristate
        select FIQ
 
+config SND_SOC_IMX_HDMI_DMA
+       bool
+       select SND_SOC_GENERIC_DMAENGINE_PCM
+       select SND_SOC_IMX_PCM_DMA
+
 comment "SoC Audio support for Freescale i.MX boards:"
 
 config SND_MXC_SOC_WM1133_EV1
@@ -283,6 +291,17 @@ config SND_SOC_IMX_MC13783
        select SND_SOC_MC13783
        select SND_SOC_IMX_PCM_DMA
 
+config SND_SOC_IMX_HDMI
+       tristate "SoC Audio support for i.MX boards with HDMI port"
+       depends on MFD_MXC_HDMI
+       select SND_SOC_IMX_HDMI_DMA
+       select SND_SOC_FSL_HDMI
+       select SND_SOC_HDMI_CODEC
+       help
+         SoC Audio support for i.MX boards with HDMI audio
+         Say Y if you want to add support for SoC audio on an i.MX board with
+         IMX HDMI.
+
 endif # SND_IMX_SOC
 
 endmenu
index 59f3c3d57f7b274f3cf099e383a192f165877349..5fb199b7f7ac9c4dea68d72feb647c5c3f40c55f 100644 (file)
@@ -19,12 +19,14 @@ snd-soc-fsl-spdif-objs := fsl_spdif.o
 snd-soc-fsl-esai-objs := fsl_esai.o
 snd-soc-fsl-utils-objs := fsl_utils.o
 snd-soc-fsl-dma-objs := fsl_dma.o
+snd-soc-fsl-hdmi-objs := fsl_hdmi.o
 obj-$(CONFIG_SND_SOC_FSL_ASRC) += snd-soc-fsl-asrc.o
 obj-$(CONFIG_SND_SOC_FSL_SAI) += snd-soc-fsl-sai.o
 obj-$(CONFIG_SND_SOC_FSL_SSI) += snd-soc-fsl-ssi.o
 obj-$(CONFIG_SND_SOC_FSL_SPDIF) += snd-soc-fsl-spdif.o
 obj-$(CONFIG_SND_SOC_FSL_ESAI) += snd-soc-fsl-esai.o
 obj-$(CONFIG_SND_SOC_FSL_UTILS) += snd-soc-fsl-utils.o
+obj-$(CONFIG_SND_SOC_FSL_HDMI) += snd-soc-fsl-hdmi.o
 obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o
 
 # MPC5200 Platform Support
@@ -44,6 +46,7 @@ obj-$(CONFIG_SND_SOC_IMX_AUDMUX) += snd-soc-imx-audmux.o
 
 obj-$(CONFIG_SND_SOC_IMX_PCM_FIQ) += imx-pcm-fiq.o
 obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o
+obj-$(CONFIG_SND_SOC_IMX_HDMI_DMA) += imx-hdmi-dma.o hdmi_pcm.o
 
 # i.MX Machine Support
 snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o
@@ -55,6 +58,7 @@ snd-soc-imx-sgtl5000-objs := imx-sgtl5000.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
+snd-soc-imx-hdmi-objs := imx-hdmi.o
 
 obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o
 obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o
@@ -65,3 +69,6 @@ obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.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
+obj-$(CONFIG_SND_SOC_IMX_HDMI) += snd-soc-imx-hdmi.o
+
+AFLAGS_hdmi_pcm.o := -march=armv7-a -mtune=cortex-a9 -mfpu=neon -mfloat-abi=softfp
diff --git a/sound/soc/fsl/fsl_hdmi.c b/sound/soc/fsl/fsl_hdmi.c
new file mode 100644 (file)
index 0000000..4f2ac27
--- /dev/null
@@ -0,0 +1,721 @@
+/*
+ * ALSA SoC HDMI Audio Layer for Freescale i.MX
+ *
+ * Copyright (C) 2011-2014 Freescale Semiconductor, Inc.
+ *
+ * Some code from patch_hdmi.c
+ *  Copyright (c) 2008-2010 Intel Corporation. All rights reserved.
+ *  Copyright (c) 2006 ATI Technologies Inc.
+ *  Copyright (c) 2008 NVIDIA Corp.  All rights reserved.
+ *  Copyright (c) 2008 Wei Ni <wni@nvidia.com>
+ *
+ * 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/init.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/mfd/mxc-hdmi-core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/asoundef.h>
+
+#include <video/mxc_hdmi.h>
+
+#include "imx-hdmi.h"
+
+
+static struct mxc_edid_cfg edid_cfg;
+
+static u32 playback_rates[HDMI_MAX_RATES];
+static u32 playback_sample_size[HDMI_MAX_SAMPLE_SIZE];
+static u32 playback_channels[HDMI_MAX_CHANNEL_CONSTRAINTS];
+
+static struct snd_pcm_hw_constraint_list playback_constraint_rates;
+static struct snd_pcm_hw_constraint_list playback_constraint_bits;
+static struct snd_pcm_hw_constraint_list playback_constraint_channels;
+
+#ifdef DEBUG
+static void dumpregs(struct snd_soc_dai *dai)
+{
+       u32 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);
+
+       dev_debug(dai->dev, "HDMI_PHY_CONF0      0x%02x\n",
+                       hdmi_readb(HDMI_PHY_CONF0));
+       dev_debug(dai->dev, "HDMI_MC_CLKDIS      0x%02x\n",
+                       hdmi_readb(HDMI_MC_CLKDIS));
+       dev_debug(dai->dev, "HDMI_AUD_N[1-3]     0x%06x (%d)\n",
+                       n, n);
+       dev_debug(dai->dev, "HDMI_AUD_CTS[1-3]   0x%06x (%d)\n",
+                       cts, cts);
+       dev_debug(dai->dev, "HDMI_FC_AUDSCONF    0x%02x\n",
+                       hdmi_readb(HDMI_FC_AUDSCONF));
+}
+#else
+static void dumpregs(struct snd_soc_dai *dai) {}
+#endif
+
+enum cea_speaker_placement {
+       FL  = (1 <<  0),        /* Front Left           */
+       FC  = (1 <<  1),        /* Front Center         */
+       FR  = (1 <<  2),        /* Front Right          */
+       FLC = (1 <<  3),        /* Front Left Center    */
+       FRC = (1 <<  4),        /* Front Right Center   */
+       RL  = (1 <<  5),        /* Rear Left            */
+       RC  = (1 <<  6),        /* Rear Center          */
+       RR  = (1 <<  7),        /* Rear Right           */
+       RLC = (1 <<  8),        /* Rear Left Center     */
+       RRC = (1 <<  9),        /* Rear Right Center    */
+       LFE = (1 << 10),        /* Low Frequency Effect */
+       FLW = (1 << 11),        /* Front Left Wide      */
+       FRW = (1 << 12),        /* Front Right Wide     */
+       FLH = (1 << 13),        /* Front Left High      */
+       FCH = (1 << 14),        /* Front Center High    */
+       FRH = (1 << 15),        /* Front Right High     */
+       TC  = (1 << 16),        /* Top Center           */
+};
+
+/*
+ * EDID SA bits in the CEA Speaker Allocation data block
+ */
+static int edid_speaker_allocation_bits[] = {
+       [0] = FL | FR,
+       [1] = LFE,
+       [2] = FC,
+       [3] = RL | RR,
+       [4] = RC,
+       [5] = FLC | FRC,
+       [6] = RLC | RRC,
+       [7] = FLW | FRW,
+       [8] = FLH | FRH,
+       [9] = TC,
+       [10] = FCH,
+};
+
+struct cea_channel_speaker_allocation {
+       int ca_index;
+       int speakers[8];
+
+       /* Derived values, just for convenience */
+       int channels;
+       int spk_mask;
+};
+
+/*
+ * This is an ordered list!
+ *
+ * The preceding ones have better chances to be selected by
+ * hdmi_channel_allocation().
+ */
+static struct cea_channel_speaker_allocation channel_allocations[] = {
+       /*                        channel:  7     6    5    4    3     2    1    0  */
+       { .ca_index = 0x00, .speakers = {   0,    0,   0,   0,   0,    0,  FR,  FL },},
+                                        /* 2.1 */
+       { .ca_index = 0x01, .speakers = {   0,    0,   0,   0,   0,  LFE,  FR,  FL },},
+                                        /* Dolby Surround */
+       { .ca_index = 0x02, .speakers = {   0,    0,   0,   0,  FC,    0,  FR,  FL },},
+       { .ca_index = 0x03, .speakers = {   0,    0,   0,   0,  FC,  LFE,  FR,  FL },},
+       { .ca_index = 0x04, .speakers = {   0,    0,   0,  RC,   0,    0,  FR,  FL },},
+       { .ca_index = 0x05, .speakers = {   0,    0,   0,  RC,   0,  LFE,  FR,  FL },},
+       { .ca_index = 0x06, .speakers = {   0,    0,   0,  RC,  FC,    0,  FR,  FL },},
+       { .ca_index = 0x07, .speakers = {   0,    0,   0,  RC,  FC,  LFE,  FR,  FL },},
+       { .ca_index = 0x08, .speakers = {   0,    0,  RR,  RL,   0,    0,  FR,  FL },},
+       { .ca_index = 0x09, .speakers = {   0,    0,  RR,  RL,   0,  LFE,  FR,  FL },},
+       { .ca_index = 0x0a, .speakers = {   0,    0,  RR,  RL,  FC,    0,  FR,  FL },},
+                                        /* surround51 */
+       { .ca_index = 0x0b, .speakers = {   0,    0,  RR,  RL,  FC,  LFE,  FR,  FL },},
+       { .ca_index = 0x0c, .speakers = {   0,   RC,  RR,  RL,   0,    0,  FR,  FL },},
+       { .ca_index = 0x0d, .speakers = {   0,   RC,  RR,  RL,   0,  LFE,  FR,  FL },},
+       { .ca_index = 0x0e, .speakers = {   0,   RC,  RR,  RL,  FC,    0,  FR,  FL },},
+                                        /* 6.1 */
+       { .ca_index = 0x0f, .speakers = {   0,   RC,  RR,  RL,  FC,  LFE,  FR,  FL },},
+       { .ca_index = 0x10, .speakers = { RRC,  RLC,  RR,  RL,   0,    0,  FR,  FL },},
+       { .ca_index = 0x11, .speakers = { RRC,  RLC,  RR,  RL,   0,  LFE,  FR,  FL },},
+       { .ca_index = 0x12, .speakers = { RRC,  RLC,  RR,  RL,  FC,    0,  FR,  FL },},
+                                        /* surround71 */
+       { .ca_index = 0x13, .speakers = { RRC,  RLC,  RR,  RL,  FC,  LFE,  FR,  FL },},
+       { .ca_index = 0x14, .speakers = { FRC,  FLC,   0,   0,   0,    0,  FR,  FL },},
+       { .ca_index = 0x15, .speakers = { FRC,  FLC,   0,   0,   0,  LFE,  FR,  FL },},
+       { .ca_index = 0x16, .speakers = { FRC,  FLC,   0,   0,  FC,    0,  FR,  FL },},
+       { .ca_index = 0x17, .speakers = { FRC,  FLC,   0,   0,  FC,  LFE,  FR,  FL },},
+       { .ca_index = 0x18, .speakers = { FRC,  FLC,   0,  RC,   0,    0,  FR,  FL },},
+       { .ca_index = 0x19, .speakers = { FRC,  FLC,   0,  RC,   0,  LFE,  FR,  FL },},
+       { .ca_index = 0x1a, .speakers = { FRC,  FLC,   0,  RC,  FC,    0,  FR,  FL },},
+       { .ca_index = 0x1b, .speakers = { FRC,  FLC,   0,  RC,  FC,  LFE,  FR,  FL },},
+       { .ca_index = 0x1c, .speakers = { FRC,  FLC,  RR,  RL,   0,    0,  FR,  FL },},
+       { .ca_index = 0x1d, .speakers = { FRC,  FLC,  RR,  RL,   0,  LFE,  FR,  FL },},
+       { .ca_index = 0x1e, .speakers = { FRC,  FLC,  RR,  RL,  FC,    0,  FR,  FL },},
+       { .ca_index = 0x1f, .speakers = { FRC,  FLC,  RR,  RL,  FC,  LFE,  FR,  FL },},
+       { .ca_index = 0x20, .speakers = {   0,  FCH,  RR,  RL,  FC,    0,  FR,  FL },},
+       { .ca_index = 0x21, .speakers = {   0,  FCH,  RR,  RL,  FC,  LFE,  FR,  FL },},
+       { .ca_index = 0x22, .speakers = {  TC,    0,  RR,  RL,  FC,    0,  FR,  FL },},
+       { .ca_index = 0x23, .speakers = {  TC,    0,  RR,  RL,  FC,  LFE,  FR,  FL },},
+       { .ca_index = 0x24, .speakers = { FRH,  FLH,  RR,  RL,   0,    0,  FR,  FL },},
+       { .ca_index = 0x25, .speakers = { FRH,  FLH,  RR,  RL,   0,  LFE,  FR,  FL },},
+       { .ca_index = 0x26, .speakers = { FRW,  FLW,  RR,  RL,   0,    0,  FR,  FL },},
+       { .ca_index = 0x27, .speakers = { FRW,  FLW,  RR,  RL,   0,  LFE,  FR,  FL },},
+       { .ca_index = 0x28, .speakers = {  TC,   RC,  RR,  RL,  FC,    0,  FR,  FL },},
+       { .ca_index = 0x29, .speakers = {  TC,   RC,  RR,  RL,  FC,  LFE,  FR,  FL },},
+       { .ca_index = 0x2a, .speakers = { FCH,   RC,  RR,  RL,  FC,    0,  FR,  FL },},
+       { .ca_index = 0x2b, .speakers = { FCH,   RC,  RR,  RL,  FC,  LFE,  FR,  FL },},
+       { .ca_index = 0x2c, .speakers = {  TC,  FCH,  RR,  RL,  FC,    0,  FR,  FL },},
+       { .ca_index = 0x2d, .speakers = {  TC,  FCH,  RR,  RL,  FC,  LFE,  FR,  FL },},
+       { .ca_index = 0x2e, .speakers = { FRH,  FLH,  RR,  RL,  FC,    0,  FR,  FL },},
+       { .ca_index = 0x2f, .speakers = { FRH,  FLH,  RR,  RL,  FC,  LFE,  FR,  FL },},
+       { .ca_index = 0x30, .speakers = { FRW,  FLW,  RR,  RL,  FC,    0,  FR,  FL },},
+       { .ca_index = 0x31, .speakers = { FRW,  FLW,  RR,  RL,  FC,  LFE,  FR,  FL },},
+};
+
+/* Compute derived values in channel_allocations[] */
+static void init_channel_allocations(void)
+{
+       struct cea_channel_speaker_allocation *p;
+       int i, j;
+
+       for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
+               p = channel_allocations + i;
+               p->channels = 0;
+               p->spk_mask = 0;
+               for (j = 0; j < ARRAY_SIZE(p->speakers); j++)
+                       if (p->speakers[j]) {
+                               p->channels++;
+                               p->spk_mask |= p->speakers[j];
+                       }
+       }
+}
+
+/*
+ * The transformation takes two steps:
+ *
+ * speaker_alloc => (edid_speaker_allocation_bits[]) => spk_mask
+ * spk_mask      => (channel_allocations[])         => CA
+ *
+ * TODO: it could select the wrong CA from multiple candidates.
+*/
+static int hdmi_channel_allocation(int channels)
+{
+       int spk_mask = 0, ca = 0, i, tmpchn, tmpspk;
+
+       /* CA defaults to 0 for basic stereo audio */
+       if (channels <= 2)
+               return 0;
+
+       /*
+        * Expand EDID's speaker allocation mask
+        *
+        * EDID tells the speaker mask in a compact(paired) form,
+        * expand EDID's notions to match the ones used by Audio InfoFrame.
+        */
+       for (i = 0; i < ARRAY_SIZE(edid_speaker_allocation_bits); i++) {
+               if (edid_cfg.speaker_alloc & (1 << i))
+                       spk_mask |= edid_speaker_allocation_bits[i];
+       }
+
+       /* Search for the first working match in the CA table */
+       for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
+               tmpchn = channel_allocations[i].channels;
+               tmpspk = channel_allocations[i].spk_mask;
+
+               if (channels == tmpchn && (spk_mask & tmpspk) == tmpspk) {
+                       ca = channel_allocations[i].ca_index;
+                       break;
+               }
+       }
+
+       return ca;
+}
+
+static void hdmi_set_audio_infoframe(unsigned int channels)
+{
+       u8 audiconf0, audiconf2;
+
+       /*
+        * From CEA-861-D spec:
+        * HDMI requires the CT, SS and SF fields to be set to 0 ("Refer
+        * to Stream Header") as these items are carried in the audio stream.
+        *
+        * So we only set the CC and CA fields.
+        */
+       audiconf0 = ((channels - 1) << HDMI_FC_AUDICONF0_CC_OFFSET) &
+               HDMI_FC_AUDICONF0_CC_MASK;
+
+       audiconf2 = hdmi_channel_allocation(channels);
+
+       hdmi_writeb(audiconf0, HDMI_FC_AUDICONF0);
+       hdmi_writeb(0, HDMI_FC_AUDICONF1);
+       hdmi_writeb(audiconf2, HDMI_FC_AUDICONF2);
+       hdmi_writeb(0, HDMI_FC_AUDICONF3);
+}
+
+static int cea_audio_rates[HDMI_MAX_RATES] = {
+       32000, 44100, 48000, 88200, 96000, 176400, 192000,
+};
+
+static void fsl_hdmi_get_playback_rates(void)
+{
+       int i, count = 0;
+       u8 rates;
+
+       /* Always assume basic audio support */
+       rates = edid_cfg.sample_rates | 0x7;
+
+       for (i = 0 ; i < HDMI_MAX_RATES ; i++)
+               if ((rates & (1 << i)) != 0)
+                       playback_rates[count++] = cea_audio_rates[i];
+
+       playback_constraint_rates.list = playback_rates;
+       playback_constraint_rates.count = count;
+
+       for (i = 0 ; i < playback_constraint_rates.count ; i++)
+               pr_debug("%s: constraint = %d Hz\n", __func__, playback_rates[i]);
+}
+
+static void fsl_hdmi_get_playback_sample_size(void)
+{
+       int i = 0;
+
+       /* Always assume basic audio support */
+       playback_sample_size[i++] = 16;
+
+       if (edid_cfg.sample_sizes & 0x4)
+               playback_sample_size[i++] = 24;
+
+       playback_constraint_bits.list = playback_sample_size;
+       playback_constraint_bits.count = i;
+
+       for (i = 0 ; i < playback_constraint_bits.count ; i++)
+               pr_debug("%s: constraint = %d bits\n", __func__, playback_sample_size[i]);
+}
+
+static void fsl_hdmi_get_playback_channels(void)
+{
+       int channels = 2, i = 0;
+
+       /* Always assume basic audio support */
+       playback_channels[i++] = channels;
+       channels += 2;
+
+       while ((i < HDMI_MAX_CHANNEL_CONSTRAINTS) &&
+                       (channels <= edid_cfg.max_channels)) {
+               playback_channels[i++] = channels;
+               channels += 2;
+       }
+
+       playback_constraint_channels.list = playback_channels;
+       playback_constraint_channels.count = i;
+
+       for (i = 0 ; i < playback_constraint_channels.count ; i++)
+               pr_debug("%s: constraint = %d channels\n", __func__, playback_channels[i]);
+}
+
+static int fsl_hdmi_update_constraints(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       int ret;
+
+       hdmi_get_edid_cfg(&edid_cfg);
+
+       fsl_hdmi_get_playback_rates();
+       ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+                       &playback_constraint_rates);
+       if (ret)
+               return ret;
+
+       fsl_hdmi_get_playback_sample_size();
+       ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+                       &playback_constraint_bits);
+       if (ret)
+               return ret;
+
+       fsl_hdmi_get_playback_channels();
+       ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+                       &playback_constraint_channels);
+       if (ret)
+               return ret;
+
+       ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static int fsl_hdmi_soc_startup(struct snd_pcm_substream *substream,
+                               struct snd_soc_dai *dai)
+{
+       struct imx_hdmi *hdmi_data = snd_soc_dai_get_drvdata(dai);
+       int ret;
+
+       clk_prepare_enable(hdmi_data->mipi_core_clk);
+       clk_prepare_enable(hdmi_data->isfr_clk);
+       clk_prepare_enable(hdmi_data->iahb_clk);
+
+       dev_dbg(dai->dev, "%s hdmi clks: mipi_core: %d isfr:%d iahb:%d\n", __func__,
+                       (int)clk_get_rate(hdmi_data->mipi_core_clk),
+                       (int)clk_get_rate(hdmi_data->isfr_clk),
+                       (int)clk_get_rate(hdmi_data->iahb_clk));
+
+       ret = fsl_hdmi_update_constraints(substream);
+       if (ret < 0)
+               return ret;
+
+       /* Indicates the subpacket represents a flatline sample */
+       hdmi_audio_writeb(FC_AUDSCONF, AUD_PACKET_SAMPFIT, 0x0);
+
+       return 0;
+}
+
+static void fsl_hdmi_soc_shutdown(struct snd_pcm_substream *substream,
+                               struct snd_soc_dai *dai)
+{
+       struct imx_hdmi *hdmi_data = snd_soc_dai_get_drvdata(dai);
+
+       clk_disable_unprepare(hdmi_data->iahb_clk);
+       clk_disable_unprepare(hdmi_data->isfr_clk);
+       clk_disable_unprepare(hdmi_data->mipi_core_clk);
+}
+
+static int fsl_hdmi_soc_prepare(struct snd_pcm_substream *substream,
+               struct snd_soc_dai *dai)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+
+       hdmi_set_audio_infoframe(runtime->channels);
+       hdmi_audio_writeb(FC_AUDSCONF, AUD_PACKET_LAYOUT,
+                       (runtime->channels > 2) ? 0x1 : 0x0);
+       hdmi_set_sample_rate(runtime->rate);
+       dumpregs(dai);
+
+       return 0;
+}
+
+static struct snd_soc_dai_ops fsl_hdmi_soc_dai_ops = {
+       .startup = fsl_hdmi_soc_startup,
+       .shutdown = fsl_hdmi_soc_shutdown,
+       .prepare = fsl_hdmi_soc_prepare,
+};
+
+/* IEC60958 status functions */
+static int fsl_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 fsl_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 fsl_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 int fsl_hdmi_channels_info(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_info *uinfo)
+{
+       hdmi_get_edid_cfg(&edid_cfg);
+       fsl_hdmi_get_playback_channels();
+
+       uinfo->type  = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = playback_constraint_channels.count;
+
+       return 0;
+}
+
+
+static int fsl_hdmi_channels_get(struct snd_kcontrol *kcontrol,
+                           struct snd_ctl_elem_value *uvalue)
+{
+       int i;
+       hdmi_get_edid_cfg(&edid_cfg);
+       fsl_hdmi_get_playback_channels();
+
+       for (i = 0 ; i < playback_constraint_channels.count ; i++)
+               uvalue->value.integer.value[i] = playback_channels[i];
+
+       return 0;
+}
+
+static int fsl_hdmi_rates_info(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_info *uinfo)
+{
+       hdmi_get_edid_cfg(&edid_cfg);
+       fsl_hdmi_get_playback_rates();
+
+       uinfo->type  = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = playback_constraint_rates.count;
+
+       return 0;
+}
+
+static int fsl_hdmi_rates_get(struct snd_kcontrol *kcontrol,
+                           struct snd_ctl_elem_value *uvalue)
+{
+       int i;
+       hdmi_get_edid_cfg(&edid_cfg);
+       fsl_hdmi_get_playback_rates();
+
+       for (i = 0 ; i < playback_constraint_rates.count ; i++)
+               uvalue->value.integer.value[i] = playback_rates[i];
+
+       return 0;
+}
+
+static int fsl_hdmi_formats_info(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_info *uinfo)
+{
+       hdmi_get_edid_cfg(&edid_cfg);
+       fsl_hdmi_get_playback_sample_size();
+
+       uinfo->type  = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = playback_constraint_bits.count;
+
+       return 0;
+}
+
+static int fsl_hdmi_formats_get(struct snd_kcontrol *kcontrol,
+                           struct snd_ctl_elem_value *uvalue)
+{
+       int i;
+       hdmi_get_edid_cfg(&edid_cfg);
+       fsl_hdmi_get_playback_sample_size();
+
+       for (i = 0 ; i < playback_constraint_bits.count ; i++)
+               uvalue->value.integer.value[i] = playback_sample_size[i];
+
+       return 0;
+}
+
+static struct snd_kcontrol_new fsl_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 = fsl_hdmi_iec_info,
+               .get = fsl_hdmi_iec_get,
+               .put = fsl_hdmi_iec_put,
+       },
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name = "HDMI Support Channels",
+               .access = SNDRV_CTL_ELEM_ACCESS_READ |
+                       SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+               .info = fsl_hdmi_channels_info,
+               .get = fsl_hdmi_channels_get,
+       },
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name = "HDMI Support Rates",
+               .access = SNDRV_CTL_ELEM_ACCESS_READ |
+                       SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+               .info = fsl_hdmi_rates_info,
+               .get = fsl_hdmi_rates_get,
+       },
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name = "HDMI Support Formats",
+               .access = SNDRV_CTL_ELEM_ACCESS_READ |
+                       SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+               .info = fsl_hdmi_formats_info,
+               .get = fsl_hdmi_formats_get,
+       },
+};
+
+static int fsl_hdmi_soc_dai_probe(struct snd_soc_dai *dai)
+{
+       int ret;
+
+       init_channel_allocations();
+
+       ret = snd_soc_add_dai_controls(dai, fsl_hdmi_ctrls,
+                       ARRAY_SIZE(fsl_hdmi_ctrls));
+       if (ret)
+               dev_warn(dai->dev, "failed to add dai controls\n");
+
+       return 0;
+}
+
+static struct snd_soc_dai_driver fsl_hdmi_dai = {
+       .probe = &fsl_hdmi_soc_dai_probe,
+       .playback = {
+               .channels_min = 2,
+               .channels_max = 8,
+               .rates = MXC_HDMI_RATES_PLAYBACK,
+               .formats = MXC_HDMI_FORMATS_PLAYBACK,
+       },
+       .ops = &fsl_hdmi_soc_dai_ops,
+};
+
+static const struct snd_soc_component_driver fsl_hdmi_component = {
+       .name           = "fsl-hdmi",
+};
+
+static int fsl_hdmi_dai_probe(struct platform_device *pdev)
+{
+       struct device_node *np = pdev->dev.of_node;
+       struct imx_hdmi *hdmi_data;
+       int ret = 0;
+
+       if (!np)
+               return -ENODEV;
+
+       if (!hdmi_get_registered()) {
+               dev_err(&pdev->dev, "failed to probe. Load HDMI-video first.\n");
+               return -ENOMEM;
+       }
+
+       hdmi_data = devm_kzalloc(&pdev->dev, sizeof(*hdmi_data), GFP_KERNEL);
+       if (!hdmi_data) {
+               dev_err(&pdev->dev, "failed to alloc hdmi_data\n");
+               return -ENOMEM;
+       }
+
+       hdmi_data->pdev = pdev;
+
+       memcpy(&hdmi_data->cpu_dai_drv, &fsl_hdmi_dai, sizeof(fsl_hdmi_dai));
+       hdmi_data->cpu_dai_drv.name = np->name;
+
+       hdmi_data->mipi_core_clk = devm_clk_get(&pdev->dev, "mipi_core");
+       if (IS_ERR(hdmi_data->mipi_core_clk)) {
+               ret = PTR_ERR(hdmi_data->mipi_core_clk);
+               dev_err(&pdev->dev, "failed to get mipi core clk: %d\n", ret);
+               return -EINVAL;
+       }
+
+       hdmi_data->isfr_clk = devm_clk_get(&pdev->dev, "hdmi_isfr");
+       if (IS_ERR(hdmi_data->isfr_clk)) {
+               ret = PTR_ERR(hdmi_data->isfr_clk);
+               dev_err(&pdev->dev, "failed to get HDMI isfr clk: %d\n", ret);
+               return -EINVAL;
+       }
+
+       hdmi_data->iahb_clk = devm_clk_get(&pdev->dev, "hdmi_iahb");
+       if (IS_ERR(hdmi_data->iahb_clk)) {
+               ret = PTR_ERR(hdmi_data->iahb_clk);
+               dev_err(&pdev->dev, "failed to get HDMI ahb clk: %d\n", ret);
+               return -EINVAL;
+       }
+
+       dev_set_drvdata(&pdev->dev, hdmi_data);
+       ret = snd_soc_register_component(&pdev->dev, &fsl_hdmi_component,
+                                       &hdmi_data->cpu_dai_drv, 1);
+       if (ret) {
+               dev_err(&pdev->dev, "register DAI failed\n");
+               return ret;
+       }
+
+       hdmi_data->codec_dev = platform_device_register_simple(
+                       "hdmi-audio-codec", -1, NULL, 0);
+       if (IS_ERR(hdmi_data->codec_dev)) {
+               dev_err(&pdev->dev, "failed to register HDMI audio codec\n");
+               ret = PTR_ERR(hdmi_data->codec_dev);
+               goto fail;
+       }
+
+       hdmi_data->dma_dev = platform_device_alloc("imx-hdmi-audio", -1);
+       if (IS_ERR(hdmi_data->dma_dev)) {
+               ret = PTR_ERR(hdmi_data->dma_dev);
+               goto fail_dma;
+       }
+
+       platform_set_drvdata(hdmi_data->dma_dev, hdmi_data);
+
+       ret = platform_device_add(hdmi_data->dma_dev);
+       if (ret) {
+               platform_device_put(hdmi_data->dma_dev);
+               goto fail_dma;
+       }
+
+       return 0;
+
+fail_dma:
+       platform_device_unregister(hdmi_data->codec_dev);
+fail:
+       snd_soc_unregister_component(&pdev->dev);
+
+       return ret;
+}
+
+static int fsl_hdmi_dai_remove(struct platform_device *pdev)
+{
+       struct imx_hdmi *hdmi_data = platform_get_drvdata(pdev);
+
+       platform_device_unregister(hdmi_data->dma_dev);
+       platform_device_unregister(hdmi_data->codec_dev);
+       snd_soc_unregister_component(&pdev->dev);
+
+       return 0;
+}
+
+static const struct of_device_id fsl_hdmi_dai_dt_ids[] = {
+       { .compatible = "fsl,imx6dl-hdmi-audio", },
+       { .compatible = "fsl,imx6q-hdmi-audio", },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, fsl_hdmi_dai_dt_ids);
+
+static struct platform_driver fsl_hdmi_driver = {
+       .probe = fsl_hdmi_dai_probe,
+       .remove = fsl_hdmi_dai_remove,
+       .driver = {
+               .name = "fsl-hdmi-dai",
+               .owner = THIS_MODULE,
+               .of_match_table = fsl_hdmi_dai_dt_ids,
+       },
+};
+module_platform_driver(fsl_hdmi_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("IMX HDMI TX DAI");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:fsl-hdmi-dai");
diff --git a/sound/soc/fsl/hdmi_pcm.S b/sound/soc/fsl/hdmi_pcm.S
new file mode 100644 (file)
index 0000000..d8d95fd
--- /dev/null
@@ -0,0 +1,246 @@
+/**
+ * Copyright (C) 2010-2014 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * 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.
+ */
+
+.section .text
+
+.global hdmi_dma_copy_16_neon_lut
+.global hdmi_dma_copy_16_neon_fast
+.global hdmi_dma_copy_24_neon_lut
+.global hdmi_dma_copy_24_neon_fast
+
+
+/**
+ * hdmi_dma_copy_16_neon_lut
+ * Convert pcm sample to iec sample. Pcm sample is 16 bits.
+ * Frame index's between 0 and 47 inclusively. Channel count can be 1, 2, 4, 8.
+ * Frame count should be multipliable by 4, and Sample count by 8.
+ *
+ * C Prototype
+ *   void hdmi_dma_copy_16_neon_lut(unsigned short *src, unsigned int *dst,
+ *                     int samples, unsigned char *lookup_table);
+ * Return value
+ *   None
+ * Parameters
+ *   src               Source PCM16 samples
+ *   dst               Dest buffer to store pcm with header
+ *   samples           Contains sample count (=frame_count * channel_count)
+ *   lookup_table      Preconstructed header table. Channels interleaved.
+ */
+
+hdmi_dma_copy_16_neon_lut:
+       mov     r12, #1         /* construct vector(1) */
+       vdup.8 d6, r12
+
+hdmi_dma_copy_16_neon_lut_start:
+
+       /* get 8 samples to q0 */
+       vld1.16 {d0, d1}, [r0]! /* TODO: aligned */
+
+       /* pld [r1, #(64*4)] */
+
+       /* xor every bit */
+       vcnt.8     q1, q0       /* count of 1s */
+       vpadd.i8 d2, d2, d3     /* only care about the LST in every element */
+       vand       d2, d2, d6   /* clear other bits while keep the least bit */
+       vshl.u8    d2, d2, #3   /* bit p: d2 = d2 << 3 */
+
+       /* get packet header */
+       vld1.8       {d5}, [r3]!
+       veor        d4, d5, d2  /* xor bit c */
+
+       /* store: (d4 << 16 | q0) << 8 */
+       vmovl.u8  q2, d4        /* expand from char to short */
+       vzip.16     q0, q2
+       vshl.u32   q0, q0, #8
+       vshl.u32   q1, q2, #8
+       vst1.32     {d0, d1, d2, d3}, [r1]!
+
+       /* decrease sample count */
+       subs  r2, r2, #8
+       bne   hdmi_dma_copy_16_neon_lut_start
+
+       mov pc, lr
+
+/**
+ * hdmi_dma_copy_16_neon_fast
+ * Convert pcm sample to iec sample. Pcm sample is 16 bits.
+ * Frame index's between 48 and 191 inclusively.
+ * Channel count can be 1, 2, 4 or 8.
+ * Frame count should be multipliable by 4, and Sample count by 8.
+ *
+ * C Prototype
+ * void hdmi_dma_copy_16_neon_fast(unsigned short *src,
+ *             unsigned int *dst, int samples);
+ * Return value
+ *   None
+ * Parameters
+ *   src               Source PCM16 samples
+ *   dst               Dest buffer to store pcm with header
+ *   samples           Contains sample count (=frame_count * channel_count)
+ */
+
+hdmi_dma_copy_16_neon_fast:
+       mov     r12, #1         /* construct vector(1) */
+       vdup.8 d6, r12
+
+hdmi_dma_copy_16_neon_fast_start:
+       /* get 8 samples to q0 */
+       vld1.16 {d0, d1}, [r0]! /* TODO: aligned */
+
+       /* pld [r1, #(64*4)] */
+
+       /* xor every bit */
+       vcnt.8     q1, q0       /* count of 1s */
+       vpadd.i8 d2, d2, d3
+       vand       d2, d2, d6   /* clear other bits while keep the LST */
+       /* finally we construct packet header */
+       vshl.u8    d4, d2, #3   /* bit p: d2 = d2 << 3 */
+
+       /* get packet header: always 0 */
+
+       /* store: (d4 << 16 | q0) << 8 */
+       vmovl.u8  q2, d4        /* expand from char to short */
+       vzip.16     q0, q2
+       vshl.u32   q0, q0, #8
+       vshl.u32   q1, q2, #8
+       vst1.32     {d0, d1, d2, d3}, [r1]!
+
+       /* decrease sample count */
+       subs  r2, r2, #8
+       bne   hdmi_dma_copy_16_neon_fast_start
+
+       mov pc, lr
+
+
+
+/**
+ * hdmi_dma_copy_24_neon_lut
+ * Convert pcm sample to iec sample. Pcm sample is 24 bits.
+ * Frame index's between 0 and 47 inclusively. Channel count can be 1, 2, 4, 8.
+ * Frame count should be multipliable by 4, and Sample count by 8.
+ *
+ * C Prototype
+ *   void hdmi_dma_copy_24_neon_lut(unsigned int *src, unsigned int *dst,
+ *                     int samples, unsigned char *lookup_table);
+ * Return value
+ *   None
+ * Parameters
+ *   src               Source PCM24 samples
+ *   dst               Dest buffer to store pcm with header
+ *   samples           Contains sample count (=frame_count * channel_count)
+ *   lookup_table      Preconstructed header table. Channels interleaved.
+ */
+
+hdmi_dma_copy_24_neon_lut:
+       vpush   {d8}
+
+       mov     r12, #1         /* construct vector(1) */
+       vdup.8 d8, r12
+
+hdmi_dma_copy_24_neon_lut_start:
+
+       /* get 8 samples to q0 and q1 */
+       vld1.32 {d0, d1, d2, d3}, [r0]! /* TODO: aligned */
+
+       /* pld [r1, #(64*4)] */
+
+       /* xor every bit */
+       vcnt.8     q2, q0       /* count of 1s */
+       vpadd.i8 d4, d4, d5     /* only care about the LSB in every element */
+       vcnt.8     q3, q1
+       vpadd.i8 d6, d6, d7
+       vpadd.i8 d4, d4, d6     /* d4: contains xor result and other dirty bits */
+       vand       d4, d4, d8   /* clear other bits while keep the least bit */
+       vshl.u8    d4, d4, #3   /* bit p: d4 = d4 << 3 */
+
+       /* get packet header */
+       vld1.8       {d5}, [r3]!/* d5: original header */
+       veor        d5, d5, d4  /* fix bit p */
+
+       /* store: (d5 << 24 | q0) */
+       vmovl.u8  q3, d5        /* expand from char to short */
+       vmovl.u16 q2, d6        /* expand from short to int */
+       vmovl.u16 q3, d7
+       vshl.u32    q2, q2, #24
+       vshl.u32    q3, q3, #24
+       vorr          q0, q0, q2
+       vorr          q1, q1, q3
+       vst1.32     {d0, d1, d2, d3}, [r1]!
+
+       /* decrease sample count */
+       subs  r2, r2, #8
+       bne   hdmi_dma_copy_24_neon_lut_start
+
+       vpop {d8}
+       mov pc, lr
+
+/**
+ * hdmi_dma_copy_24_neon_fast
+ * Convert pcm sample to iec sample. Pcm sample is 24 bits.
+ * Frame index's between 48 and 191 inclusively.
+ * Channel count can be 1, 2, 4 or 8.
+ * Frame count should be multipliable by 4, and Sample count by 8.
+ *
+ * C Prototype
+ * void hdmi_dma_copy_24_neon_fast(unsigned int *src,
+ *             unsigned int *dst, int samples);
+ * Return value
+ *   None
+ * Parameters
+ *   src               Source PCM24 samples
+ *   dst               Dest buffer to store pcm with header
+ *   samples           Contains sample count (=frame_count * channel_count)
+ */
+
+hdmi_dma_copy_24_neon_fast:
+       vpush   {d8}
+
+       mov     r12, #1         /* construct vector(1) */
+       vdup.8 d8, r12
+
+hdmi_dma_copy_24_neon_fast_start:
+       /* get 8 samples to q0 and q1 */
+       vld1.32 {d0, d1, d2, d3}, [r0]! /* TODO: aligned */
+
+       /* pld [r1, #(64*4)] */
+
+       /* xor every bit */
+       vcnt.8     q2, q0       /* count of 1s */
+       vpadd.i8 d4, d4, d5     /* only care about the LSB in every element */
+       vcnt.8     q3, q1
+       vpadd.i8 d6, d6, d7
+       vpadd.i8 d4, d4, d6     /* d4: contains xor result and other dirty bits */
+       vand       d4, d4, d8   /* clear other bits while keep the least bit */
+       vshl.u8    d4, d4, #3   /* bit p: d4 = d4 << 3 */
+
+       /* store: (d4 << 24 | q0)  */
+       vmovl.u8  q3, d4        /* expand from char to short */
+       vmovl.u16 q2, d6        /* expand from short to int */
+       vmovl.u16 q3, d7
+       vshl.u32    q2, q2, #24
+       vshl.u32    q3, q3, #24
+       vorr          q0, q0, q2
+       vorr          q1, q1, q3
+       vst1.32     {d0, d1, d2, d3}, [r1]!
+
+       /* decrease sample count */
+       subs  r2, r2, #8
+       bne   hdmi_dma_copy_24_neon_fast_start
+
+       vpop {d8}
+       mov pc, lr
diff --git a/sound/soc/fsl/imx-hdmi-dma.c b/sound/soc/fsl/imx-hdmi-dma.c
new file mode 100644 (file)
index 0000000..8eeb009
--- /dev/null
@@ -0,0 +1,1171 @@
+/*
+ * imx-hdmi-dma.c  --  HDMI DMA driver for ALSA Soc Audio Layer
+ *
+ * Copyright (C) 2011-2014 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/module.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/mfd/mxc-hdmi-core.h>
+#include <linux/platform_data/dma-imx.h>
+
+#include <video/mxc_hdmi.h>
+
+#include "imx-hdmi.h"
+
+#define HDMI_DMA_BURST_UNSPECIFIED_LEGNTH      0
+#define HDMI_DMA_BURST_INCR4                   1
+#define HDMI_DMA_BURST_INCR8                   2
+#define HDMI_DMA_BURST_INCR16                  3
+
+#define HDMI_BASE_ADDR 0x00120000
+
+struct hdmi_sdma_script {
+       int control_reg_addr;
+       int status_reg_addr;
+       int dma_start_addr;
+       u32 buffer[20];
+};
+
+struct hdmi_dma_priv {
+       struct snd_pcm_substream *substream;
+       struct platform_device *pdev;
+
+       struct snd_dma_buffer hw_buffer;
+       unsigned long buffer_bytes;
+       unsigned long appl_bytes;
+
+       int periods;
+       int period_time;
+       int period_bytes;
+       int dma_period_bytes;
+       int buffer_ratio;
+
+       unsigned long offset;
+
+       snd_pcm_format_t format;
+       int sample_align;
+       int sample_bits;
+       int channels;
+       int rate;
+
+       int frame_idx;
+
+       bool tx_active;
+       spinlock_t irq_lock;
+
+       /* SDMA part */
+       dma_addr_t phy_hdmi_sdma_t;
+       struct hdmi_sdma_script *hdmi_sdma_t;
+       struct dma_chan *dma_channel;
+       struct dma_async_tx_descriptor *desc;
+       struct imx_hdmi_sdma_params sdma_params;
+};
+
+/* bit 0:0:0:b:p(0):c:(u)0:(v)0 */
+/* max 8 channels supported; channels are interleaved */
+static u8 g_packet_head_table[48 * 8];
+
+void hdmi_dma_copy_16_neon_lut(unsigned short *src, unsigned int *dst,
+               int samples, unsigned char *lookup_table);
+void hdmi_dma_copy_16_neon_fast(unsigned short *src, unsigned int *dst,
+               int samples);
+void hdmi_dma_copy_24_neon_lut(unsigned int *src, unsigned int *dst,
+               int samples, unsigned char *lookup_table);
+void hdmi_dma_copy_24_neon_fast(unsigned int *src, unsigned int *dst,
+               int samples);
+static void hdmi_dma_irq_enable(struct hdmi_dma_priv *priv);
+static void hdmi_dma_irq_disable(struct hdmi_dma_priv *priv);
+
+union hdmi_audio_header_t iec_header;
+EXPORT_SYMBOL(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          (12288)
+#define HDMI_DMA_BUF_SIZE              (128 * 1024)
+#define HDMI_PCM_BUF_SIZE              (128 * 1024)
+
+#define hdmi_audio_debug(dev, reg) \
+       dev_dbg(dev, #reg ": 0x%02x\n", hdmi_readb(reg))
+
+#ifdef DEBUG
+static void dumpregs(struct device *dev)
+{
+       hdmi_audio_debug(dev, HDMI_AHB_DMA_CONF0);
+       hdmi_audio_debug(dev, HDMI_AHB_DMA_START);
+       hdmi_audio_debug(dev, HDMI_AHB_DMA_STOP);
+       hdmi_audio_debug(dev, HDMI_AHB_DMA_THRSLD);
+       hdmi_audio_debug(dev, HDMI_AHB_DMA_STRADDR0);
+       hdmi_audio_debug(dev, HDMI_AHB_DMA_STPADDR0);
+       hdmi_audio_debug(dev, HDMI_AHB_DMA_BSTADDR0);
+       hdmi_audio_debug(dev, HDMI_AHB_DMA_MBLENGTH0);
+       hdmi_audio_debug(dev, HDMI_AHB_DMA_MBLENGTH1);
+       hdmi_audio_debug(dev, HDMI_AHB_DMA_STAT);
+       hdmi_audio_debug(dev, HDMI_AHB_DMA_INT);
+       hdmi_audio_debug(dev, HDMI_AHB_DMA_MASK);
+       hdmi_audio_debug(dev, HDMI_AHB_DMA_POL);
+       hdmi_audio_debug(dev, HDMI_AHB_DMA_CONF1);
+       hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFSTAT);
+       hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFINT);
+       hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFMASK);
+       hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFPOL);
+       hdmi_audio_debug(dev, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
+       hdmi_audio_debug(dev, HDMI_IH_AHBDMAAUD_STAT0);
+       hdmi_audio_debug(dev, HDMI_IH_MUTE);
+}
+
+static void dumppriv(struct device *dev, struct hdmi_dma_priv *priv)
+{
+       dev_dbg(dev, "channels         = %d\n", priv->channels);
+       dev_dbg(dev, "periods          = %d\n", priv->periods);
+       dev_dbg(dev, "period_bytes     = %d\n", priv->period_bytes);
+       dev_dbg(dev, "dma period_bytes = %d\n", priv->dma_period_bytes);
+       dev_dbg(dev, "buffer_ratio     = %d\n", priv->buffer_ratio);
+       dev_dbg(dev, "hw dma buffer    = 0x%08x\n", (int)priv->hw_buffer.addr);
+       dev_dbg(dev, "dma buf size     = %d\n", (int)priv->buffer_bytes);
+       dev_dbg(dev, "sample_rate      = %d\n", (int)priv->rate);
+}
+#else
+static void dumpregs(struct device *dev) {}
+static void dumppriv(struct device *dev, struct hdmi_dma_priv *priv) {}
+#endif
+
+/*
+ * 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 void hdmi_dma_irq_set(bool set)
+{
+       u8 val = hdmi_readb(HDMI_AHB_DMA_MASK);
+
+       if (set)
+               val |= HDMI_AHB_DMA_DONE;
+       else
+               val &= (u8)~HDMI_AHB_DMA_DONE;
+
+       hdmi_writeb(val, HDMI_AHB_DMA_MASK);
+}
+
+static void hdmi_mask(int mask)
+{
+       u8 regval = hdmi_readb(HDMI_AHB_DMA_MASK);
+
+       if (mask)
+               regval |= HDMI_AHB_DMA_ERROR | HDMI_AHB_DMA_FIFO_EMPTY;
+       else
+               regval &= (u8)~(HDMI_AHB_DMA_ERROR | HDMI_AHB_DMA_FIFO_EMPTY);
+
+       hdmi_writeb(regval, HDMI_AHB_DMA_MASK);
+}
+
+int odd_ones(unsigned a)
+{
+       a ^= a >> 8;
+       a ^= a >> 4;
+       a ^= a >> 2;
+       a ^= a >> 1;
+
+       return a & 1;
+}
+
+/* Add frame information for one pcm subframe */
+static u32 hdmi_dma_add_frame_info(struct hdmi_dma_priv *priv,
+                                  u32 pcm_data, int subframe_idx)
+{
+       union hdmi_audio_dma_data_t subframe;
+
+       subframe.U = 0;
+       iec_header.B.channel = subframe_idx;
+
+       /* fill b (start-of-block) */
+       subframe.B.b = (priv->frame_idx == 0) ? 1 : 0;
+
+       /* fill c (channel status) */
+       if (priv->frame_idx < 42)
+               subframe.B.c = (iec_header.U >> priv->frame_idx) & 0x1;
+       else
+               subframe.B.c = 0;
+
+       subframe.B.p = odd_ones(pcm_data);
+       subframe.B.p ^= subframe.B.c;
+       subframe.B.p ^= subframe.B.u;
+       subframe.B.p ^= subframe.B.v;
+
+       /* fill data */
+       if (priv->sample_bits == 16)
+               subframe.B.data = pcm_data << 8;
+       else
+               subframe.B.data = pcm_data;
+
+       return subframe.U;
+}
+
+static void init_table(int channels)
+{
+       unsigned char *p = g_packet_head_table;
+       int i, ch = 0;
+
+       for (i = 0; i < 48; i++) {
+               int b = 0;
+               if (i == 0)
+                       b = 1;
+
+               for (ch = 0; ch < channels; ch++) {
+                       int c = 0;
+                       if (i < 42) {
+                               iec_header.B.channel = ch+1;
+                               c = (iec_header.U >> i) & 0x1;
+                       }
+                       /* preset bit p as c */
+                       *p++ = (b << 4) | (c << 2) | (c << 3);
+               }
+       }
+}
+
+/*
+ * FIXME: Disable NEON Optimization in hdmi, or it will cause crash of
+ * pulseaudio in the userspace. There is no issue for the Optimization
+ * implemenation, if only use "VPUSH, VPOP" in the function, the pulseaudio
+ * will crash also. So for my assumption, we can't use the NEON in the
+ * interrupt.(tasklet is implemented by softirq.)
+ * Disable SMP, preempt, change the dma buffer to nocached, add protection of
+ * Dn register and fpscr, all these operation have no effect to the result.
+ */
+#define HDMI_DMA_NO_NEON
+
+#ifdef HDMI_DMA_NO_NEON
+/* Optimization for IEC head */
+static void hdmi_dma_copy_16_c_lut(u16 *src, u32 *dst, int samples,
+                               u8 *lookup_table)
+{
+       u32 sample, head, p;
+       int i;
+
+       for (i = 0; i < samples; i++) {
+               /* get source sample */
+               sample = *src++;
+
+               /* xor every bit */
+               p = sample ^ (sample >> 8);
+               p ^= (p >> 4);
+               p ^= (p >> 2);
+               p ^= (p >> 1);
+               p &= 1; /* only want last bit */
+               p <<= 3; /* bit p */
+
+               /* get packet header */
+               head = *lookup_table++;
+
+               /* fix head */
+               head ^= p;
+
+               /* store */
+               *dst++ = (head << 24) | (sample << 8);
+       }
+}
+
+static void hdmi_dma_copy_16_c_fast(u16 *src, u32 *dst, int samples)
+{
+       u32 sample, p;
+       int i;
+
+       for (i = 0; i < samples; i++) {
+               /* get source sample */
+               sample = *src++;
+
+               /* xor every bit */
+               p = sample ^ (sample >> 8);
+               p ^= (p >> 4);
+               p ^= (p >> 2);
+               p ^= (p >> 1);
+               p &= 1; /* only want last bit */
+               p <<= 3; /* bit p */
+
+               /* store */
+               *dst++ = (p << 24) | (sample << 8);
+       }
+}
+
+static void hdmi_dma_copy_16(u16 *src, u32 *dst, int framecnt, int channelcnt)
+{
+       /* split input frames into 192-frame each */
+       int count_in_192 = (framecnt + 191) / 192;
+       int i;
+
+       for (i = 0; i < count_in_192; i++) {
+               int count, samples;
+
+               /* handles frame index [0, 48) */
+               count = (framecnt < 48) ? framecnt : 48;
+               samples = count * channelcnt;
+               hdmi_dma_copy_16_c_lut(src, dst, samples, g_packet_head_table);
+               framecnt -= count;
+               if (framecnt == 0)
+                       break;
+
+               src  += samples;
+               dst += samples;
+
+               /* handles frame index [48, 192) */
+               count = (framecnt < 192 - 48) ? framecnt : 192 - 48;
+               samples = count * channelcnt;
+               hdmi_dma_copy_16_c_fast(src, dst, samples);
+               framecnt -= count;
+               src  += samples;
+               dst += samples;
+       }
+}
+#else
+/* NEON optimization for IEC head*/
+
+/**
+ * Convert pcm samples to iec samples suitable for HDMI transfer.
+ * PCM sample is 16 bits length.
+ * Frame index always starts from 0.
+ * Channel count can be 1, 2, 4, 6, or 8
+ * Sample count (frame_count * channel_count) is multipliable by 8.
+ */
+static void hdmi_dma_copy_16(u16 *src, u32 *dst, int framecount, int channelcount)
+{
+       /* split input frames into 192-frame each */
+       int i, count_in_192 = (framecount + 191) / 192;
+
+       for (i = 0; i < count_in_192; i++) {
+               int count, samples;
+
+               /* handles frame index [0, 48) */
+               count = (framecount < 48) ? framecount : 48;
+               samples = count * channelcount;
+               hdmi_dma_copy_16_neon_lut(src, dst, samples, g_packet_head_table);
+               framecount -= count;
+               if (framecount == 0)
+                       break;
+
+               src += samples;
+               dst += samples;
+
+               /* handles frame index [48, 192) */
+               count = (framecount < 192 - 48) ? framecount : (192 - 48);
+               samples = count * channelcount;
+               hdmi_dma_copy_16_neon_fast(src, dst, samples);
+               framecount -= count;
+               src += samples;
+               dst += samples;
+       }
+}
+#endif
+
+static void hdmi_dma_mmap_copy(struct snd_pcm_substream *substream,
+                               int offset, int count)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct hdmi_dma_priv *priv = runtime->private_data;
+       struct device *dev = rtd->platform->dev;
+       u32 framecount, *dst;
+       u16 *src16;
+
+       framecount = count / (priv->sample_align * priv->channels);
+
+       /* hw_buffer is the destination for pcm data plus frame info. */
+       dst = (u32 *)(priv->hw_buffer.area + (offset * priv->buffer_ratio));
+
+       switch (priv->format) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               /* dma_buffer is the mmapped buffer we are copying pcm from. */
+               src16 = (u16 *)(runtime->dma_area + offset);
+               hdmi_dma_copy_16(src16, dst, framecount, priv->channels);
+               break;
+       default:
+               dev_err(dev, "unsupported sample format %s\n",
+                               snd_pcm_format_name(priv->format));
+               return;
+       }
+}
+
+static void hdmi_dma_data_copy(struct snd_pcm_substream *substream,
+                               struct hdmi_dma_priv *priv, char type)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       unsigned long offset, count, appl_bytes, space_to_end;
+
+       if (runtime->access != SNDRV_PCM_ACCESS_MMAP_INTERLEAVED)
+               return;
+
+       appl_bytes = frames_to_bytes(runtime, runtime->status->hw_ptr);
+       if (type == 'p')
+               appl_bytes += 2 * priv->period_bytes;
+       offset = appl_bytes % priv->buffer_bytes;
+
+       switch (type) {
+       case 'p':
+               count = priv->period_bytes;
+               space_to_end = priv->period_bytes;
+               break;
+       case 'b':
+               count = priv->buffer_bytes;
+               space_to_end = priv->buffer_bytes - offset;
+
+               break;
+       default:
+               return;
+       }
+
+       if (count <= space_to_end) {
+               hdmi_dma_mmap_copy(substream, offset, count);
+       } else {
+               hdmi_dma_mmap_copy(substream, offset, space_to_end);
+               hdmi_dma_mmap_copy(substream, 0, count - space_to_end);
+       }
+}
+
+static void hdmi_sdma_callback(void *data)
+{
+       struct hdmi_dma_priv *priv = (struct hdmi_dma_priv *)data;
+       struct snd_pcm_substream *substream = priv->substream;
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       unsigned long flags;
+
+       spin_lock_irqsave(&priv->irq_lock, flags);
+
+       if (runtime && runtime->dma_area && priv->tx_active) {
+               priv->offset += priv->period_bytes;
+               priv->offset %= priv->period_bytes * priv->periods;
+
+               /* Copy data by period_bytes */
+               hdmi_dma_data_copy(substream, priv, 'p');
+
+               snd_pcm_period_elapsed(substream);
+       }
+
+       spin_unlock_irqrestore(&priv->irq_lock, flags);
+
+       return;
+}
+
+static int hdmi_dma_set_thrsld_incrtype(struct device *dev, int channels)
+{
+       u8 mask = HDMI_AHB_DMA_CONF0_BURST_MODE | HDMI_AHB_DMA_CONF0_INCR_TYPE_MASK;
+       u8 val = hdmi_readb(HDMI_AHB_DMA_CONF0) & ~mask;
+       int incr_type, threshold;
+
+       switch (hdmi_readb(HDMI_REVISION_ID)) {
+       case 0x0a:
+               incr_type = HDMI_DMA_BURST_INCR4;
+               if (channels == 2)
+                       threshold = 126;
+               else
+                       threshold = 124;
+               break;
+       case 0x1a:
+               incr_type = HDMI_DMA_BURST_INCR8;
+               threshold = 128;
+               break;
+       default:
+               dev_err(dev, "unknown hdmi controller!\n");
+               return -ENODEV;
+       }
+
+       hdmi_writeb(threshold, HDMI_AHB_DMA_THRSLD);
+
+       switch (incr_type) {
+       case HDMI_DMA_BURST_UNSPECIFIED_LEGNTH:
+               break;
+       case HDMI_DMA_BURST_INCR4:
+               val |= HDMI_AHB_DMA_CONF0_BURST_MODE;
+               break;
+       case HDMI_DMA_BURST_INCR8:
+               val |= HDMI_AHB_DMA_CONF0_BURST_MODE |
+                        HDMI_AHB_DMA_CONF0_INCR8;
+               break;
+       case HDMI_DMA_BURST_INCR16:
+               val |= HDMI_AHB_DMA_CONF0_BURST_MODE |
+                        HDMI_AHB_DMA_CONF0_INCR16;
+               break;
+       default:
+               dev_err(dev, "invalid increment type: %d!", incr_type);
+               return -EINVAL;
+       }
+
+       hdmi_writeb(val, HDMI_AHB_DMA_CONF0);
+
+       hdmi_audio_debug(dev, HDMI_AHB_DMA_THRSLD);
+
+       return 0;
+}
+
+static int hdmi_dma_configure_dma(struct device *dev, int channels)
+{
+       u8 i, val = 0;
+       int ret;
+
+       if (channels <= 0 || channels > 8 || channels % 2 != 0) {
+               dev_err(dev, "unsupported channel number: %d\n", channels);
+               return -EINVAL;
+       }
+
+       hdmi_audio_writeb(AHB_DMA_CONF0, EN_HLOCK, 0x1);
+
+       ret = hdmi_dma_set_thrsld_incrtype(dev, channels);
+       if (ret)
+               return ret;
+
+       for (i = 0; i < channels; i += 2)
+               val |= 0x3 << i;
+
+       hdmi_writeb(val, HDMI_AHB_DMA_CONF1);
+
+       return 0;
+}
+
+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_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct hdmi_dma_priv *priv = runtime->private_data;
+       struct device *dev = rtd->platform->dev;
+
+       iec_header.B.source = priv->channels;
+
+       switch (priv->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 88200:
+               iec_header.B.sample_freq = 0x08;
+               iec_header.B.org_sample_freq = 0x07;
+               break;
+       case 96000:
+               iec_header.B.sample_freq = 0x0A;
+               iec_header.B.org_sample_freq = 0x05;
+               break;
+       case 176400:
+               iec_header.B.sample_freq = 0x0C;
+               iec_header.B.org_sample_freq = 0x03;
+               break;
+       case 192000:
+               iec_header.B.sample_freq = 0x0E;
+               iec_header.B.org_sample_freq = 0x01;
+               break;
+       default:
+               dev_err(dev, "unsupported sample rate\n");
+               return -EFAULT;
+       }
+
+       switch (priv->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 hdmi_dma_priv *priv = runtime->private_data;
+       unsigned int count = frames_to_bytes(runtime, frames);
+       unsigned int pos_bytes = frames_to_bytes(runtime, pos);
+       u32 *hw_buf;
+       int subframe_idx;
+       u32 pcm_data;
+
+       /* Adding frame info to pcm data from userspace and copy to hw_buffer */
+       hw_buf = (u32 *)(priv->hw_buffer.area + (pos_bytes * priv->buffer_ratio));
+
+       while (count > 0) {
+               for (subframe_idx = 1 ; subframe_idx <= priv->channels ; subframe_idx++) {
+                       if (copy_from_user(&pcm_data, buf, priv->sample_align))
+                               return -EFAULT;
+
+                       buf += priv->sample_align;
+                       count -= priv->sample_align;
+
+                       /* Save the header info to the audio dma buffer */
+                       *hw_buf++ = hdmi_dma_add_frame_info(priv, pcm_data, subframe_idx);
+               }
+
+               priv->frame_idx++;
+               if (priv->frame_idx == 192)
+                       priv->frame_idx = 0;
+       }
+
+       return 0;
+}
+
+static int hdmi_sdma_initbuf(struct device *dev, struct hdmi_dma_priv *priv)
+{
+       struct hdmi_sdma_script *hdmi_sdma_t = priv->hdmi_sdma_t;
+       u32 *head, *tail, i;
+
+       if (!hdmi_sdma_t) {
+               dev_err(dev, "hdmi private addr invalid!!!\n");
+               return -EINVAL;
+       }
+
+       hdmi_sdma_t->control_reg_addr = HDMI_BASE_ADDR + HDMI_AHB_DMA_START;
+       hdmi_sdma_t->status_reg_addr = HDMI_BASE_ADDR + HDMI_IH_AHBDMAAUD_STAT0;
+       hdmi_sdma_t->dma_start_addr = HDMI_BASE_ADDR + HDMI_AHB_DMA_STRADDR0;
+
+       head = &hdmi_sdma_t->buffer[0];
+       tail = &hdmi_sdma_t->buffer[1];
+
+       for (i = 0; i < priv->sdma_params.buffer_num; i++) {
+               *head = priv->hw_buffer.addr + i * priv->period_bytes * priv->buffer_ratio;
+               *tail = *head + priv->dma_period_bytes - 1;
+               head += 2;
+               tail += 2;
+       }
+
+       return 0;
+}
+
+static int hdmi_sdma_config(struct snd_pcm_substream *substream,
+                       struct hdmi_dma_priv *priv)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct device *dai_dev = &priv->pdev->dev;
+       struct device *dev = rtd->platform->dev;
+       struct dma_slave_config slave_config;
+       int ret;
+
+       priv->dma_channel = dma_request_slave_channel(dai_dev, "tx");
+       if (priv->dma_channel == NULL) {
+               dev_err(dev, "failed to alloc dma channel\n");
+               return -EBUSY;
+       }
+
+       slave_config.direction = DMA_TRANS_NONE;
+       slave_config.src_addr = (dma_addr_t)priv->sdma_params.buffer_num;
+       slave_config.dst_addr = (dma_addr_t)priv->sdma_params.phyaddr;
+
+       ret = dmaengine_slave_config(priv->dma_channel, &slave_config);
+       if (ret) {
+               dev_err(dev, "failed to config slave dma\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int hdmi_dma_hw_free(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct hdmi_dma_priv *priv = runtime->private_data;
+
+       if (priv->dma_channel) {
+               dma_release_channel(priv->dma_channel);
+               priv->dma_channel = NULL;
+       }
+
+       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 hdmi_dma_priv *priv = runtime->private_data;
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct device *dev = rtd->platform->dev;
+       int ret;
+
+       priv->buffer_bytes = params_buffer_bytes(params);
+       priv->periods = params_periods(params);
+       priv->period_bytes = params_period_bytes(params);
+       priv->channels = params_channels(params);
+       priv->format = params_format(params);
+       priv->rate = params_rate(params);
+
+       priv->offset = 0;
+       priv->period_time = HZ / (priv->rate / params_period_size(params));
+
+       switch (priv->format) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               priv->buffer_ratio = 2;
+               priv->sample_align = 2;
+               priv->sample_bits = 16;
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               /* 24 bit audio in 32 bit word */
+               priv->buffer_ratio = 1;
+               priv->sample_align = 4;
+               priv->sample_bits = 24;
+               break;
+       default:
+               dev_err(dev, "unsupported sample format: %d\n", priv->format);
+               return -EINVAL;
+       }
+
+       priv->dma_period_bytes = priv->period_bytes * priv->buffer_ratio;
+       priv->sdma_params.buffer_num = priv->periods;
+       priv->sdma_params.phyaddr = priv->phy_hdmi_sdma_t;
+
+       ret = hdmi_sdma_initbuf(dev, priv);
+       if (ret)
+               return ret;
+
+       ret = hdmi_sdma_config(substream, priv);
+       if (ret)
+               return ret;
+
+       snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+       ret = hdmi_dma_configure_dma(dev, priv->channels);
+       if (ret)
+               return ret;
+
+       hdmi_dma_set_addr(priv->hw_buffer.addr, priv->dma_period_bytes);
+
+       dumppriv(dev, priv);
+
+       hdmi_dma_update_iec_header(substream);
+
+       /* Init par for mmap optimizate */
+       init_table(priv->channels);
+
+       priv->appl_bytes = 0;
+
+       return 0;
+}
+
+static void hdmi_dma_trigger_init(struct snd_pcm_substream *substream,
+                               struct hdmi_dma_priv *priv)
+{
+       unsigned long status;
+
+       priv->offset = 0;
+       priv->frame_idx = 0;
+
+       /* Copy data by buffer_bytes */
+       hdmi_dma_data_copy(substream, priv, 'b');
+
+       hdmi_audio_writeb(AHB_DMA_CONF0, SW_FIFO_RST, 0x1);
+
+       /* Delay after reset */
+       udelay(1);
+
+       status = hdmi_readb(HDMI_IH_AHBDMAAUD_STAT0);
+       hdmi_writeb(status, HDMI_IH_AHBDMAAUD_STAT0);
+}
+
+static int hdmi_dma_prepare_and_submit(struct snd_pcm_substream *substream,
+                                       struct hdmi_dma_priv *priv)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct device *dev = rtd->platform->dev;
+
+       priv->desc = dmaengine_prep_dma_cyclic(priv->dma_channel, 0, 0, 0,
+                                               DMA_TRANS_NONE, 0);
+       if (!priv->desc) {
+               dev_err(dev, "failed to prepare slave dma\n");
+               return -EINVAL;
+       }
+
+       priv->desc->callback = hdmi_sdma_callback;
+       priv->desc->callback_param = (void *)priv;
+       dmaengine_submit(priv->desc);
+
+       return 0;
+}
+
+static int hdmi_dma_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct hdmi_dma_priv *priv = runtime->private_data;
+       struct device *dev = rtd->platform->dev;
+       int ret;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               if (!check_hdmi_state())
+                       return 0;
+               hdmi_dma_trigger_init(substream, priv);
+
+               dumpregs(dev);
+
+               priv->tx_active = true;
+               hdmi_audio_writeb(AHB_DMA_START, START, 0x1);
+               hdmi_dma_irq_set(false);
+               hdmi_set_dma_mode(1);
+               ret = hdmi_dma_prepare_and_submit(substream, priv);
+               if (ret)
+                       return ret;
+               dma_async_issue_pending(priv->desc->chan);
+               break;
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               dmaengine_terminate_all(priv->dma_channel);
+               hdmi_set_dma_mode(0);
+               hdmi_dma_irq_set(true);
+               hdmi_audio_writeb(AHB_DMA_STOP, STOP, 0x1);
+               priv->tx_active = false;
+               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 hdmi_dma_priv *priv = runtime->private_data;
+
+       return bytes_to_frames(runtime, priv->offset);
+}
+
+static struct snd_pcm_hardware snd_imx_hardware = {
+       .info = SNDRV_PCM_INFO_INTERLEAVED |
+               SNDRV_PCM_INFO_BLOCK_TRANSFER |
+               SNDRV_PCM_INFO_MMAP |
+               SNDRV_PCM_INFO_MMAP_VALID |
+               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_PCM_BUF_SIZE,
+       .period_bytes_min = HDMI_DMA_PERIOD_BYTES / 2,
+       .period_bytes_max = HDMI_DMA_PERIOD_BYTES / 2,
+       .periods_min = 8,
+       .periods_max = 8,
+       .fifo_size = 0,
+};
+
+static void hdmi_dma_irq_enable(struct hdmi_dma_priv *priv)
+{
+       unsigned long flags;
+
+       hdmi_writeb(0xff, HDMI_AHB_DMA_POL);
+       hdmi_writeb(0xff, HDMI_AHB_DMA_BUFFPOL);
+
+       spin_lock_irqsave(&priv->irq_lock, flags);
+
+       hdmi_writeb(0xff, HDMI_IH_AHBDMAAUD_STAT0);
+       hdmi_writeb(0xff, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
+       hdmi_dma_irq_set(false);
+       hdmi_mask(0);
+
+       spin_unlock_irqrestore(&priv->irq_lock, flags);
+}
+
+static void hdmi_dma_irq_disable(struct hdmi_dma_priv *priv)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&priv->irq_lock, flags);
+
+       hdmi_dma_irq_set(true);
+       hdmi_writeb(0x0, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
+       hdmi_writeb(0xff, HDMI_IH_AHBDMAAUD_STAT0);
+       hdmi_mask(1);
+
+       spin_unlock_irqrestore(&priv->irq_lock, flags);
+}
+
+static int hdmi_dma_open(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct device *dev = rtd->platform->dev;
+       struct hdmi_dma_priv *priv = dev_get_drvdata(dev);
+       int ret;
+
+       runtime->private_data = priv;
+
+       ret = mxc_hdmi_register_audio(substream);
+       if (ret < 0) {
+               dev_err(dev, "HDMI Video is not ready!\n");
+               return ret;
+       }
+
+       hdmi_audio_writeb(AHB_DMA_CONF0, SW_FIFO_RST, 0x1);
+
+       ret = snd_pcm_hw_constraint_integer(substream->runtime,
+                       SNDRV_PCM_HW_PARAM_PERIODS);
+       if (ret < 0)
+               return ret;
+
+       snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
+
+       hdmi_dma_irq_enable(priv);
+
+       return 0;
+}
+
+static int hdmi_dma_close(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct hdmi_dma_priv *priv = runtime->private_data;
+
+       hdmi_dma_irq_disable(priv);
+       mxc_hdmi_unregister_audio(substream);
+
+       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,
+       .hw_free        = hdmi_dma_hw_free,
+       .trigger        = hdmi_dma_trigger,
+       .pointer        = hdmi_dma_pointer,
+       .copy           = hdmi_dma_copy,
+};
+
+static int imx_hdmi_dma_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+       struct hdmi_dma_priv *priv = dev_get_drvdata(rtd->platform->dev);
+       struct snd_card *card = rtd->card->snd_card;
+       struct snd_pcm_substream *substream;
+       struct snd_pcm *pcm = rtd->pcm;
+       u64 dma_mask = DMA_BIT_MASK(32);
+       int ret = 0;
+
+       if (!card->dev->dma_mask)
+               card->dev->dma_mask = &dma_mask;
+       if (!card->dev->coherent_dma_mask)
+               card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+       substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+
+       ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
+                       HDMI_PCM_BUF_SIZE, &substream->dma_buffer);
+       if (ret) {
+               dev_err(card->dev, "failed to alloc playback dma buffer\n");
+               return ret;
+       }
+
+       priv->substream = substream;
+
+       /* Alloc the hw_buffer */
+       ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
+                       HDMI_DMA_BUF_SIZE, &priv->hw_buffer);
+       if (ret) {
+               dev_err(card->dev, "failed to alloc hw dma buffer\n");
+               return ret;
+       }
+
+       return ret;
+}
+
+static void imx_hdmi_dma_pcm_free(struct snd_pcm *pcm)
+{
+       int stream = SNDRV_PCM_STREAM_PLAYBACK;
+       struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+       struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+       struct hdmi_dma_priv *priv = dev_get_drvdata(rtd->platform->dev);
+
+       if (substream) {
+               snd_dma_free_pages(&substream->dma_buffer);
+               substream->dma_buffer.area = NULL;
+               substream->dma_buffer.addr = 0;
+       }
+
+       /* Free the hw_buffer */
+       snd_dma_free_pages(&priv->hw_buffer);
+       priv->hw_buffer.area = NULL;
+       priv->hw_buffer.addr = 0;
+}
+
+static struct snd_soc_platform_driver imx_hdmi_platform = {
+       .ops            = &imx_hdmi_dma_pcm_ops,
+       .pcm_new        = imx_hdmi_dma_pcm_new,
+       .pcm_free       = imx_hdmi_dma_pcm_free,
+};
+
+static int imx_soc_platform_probe(struct platform_device *pdev)
+{
+       struct imx_hdmi *hdmi_drvdata = platform_get_drvdata(pdev);
+       struct hdmi_dma_priv *priv;
+       int ret = 0;
+
+       priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv) {
+               dev_err(&pdev->dev, "Failed to alloc hdmi_dma\n");
+               return -ENOMEM;
+       }
+
+       priv->hdmi_sdma_t = dma_alloc_coherent(NULL,
+                       sizeof(struct hdmi_sdma_script),
+                       &priv->phy_hdmi_sdma_t, GFP_KERNEL);
+       if (!priv->hdmi_sdma_t) {
+               dev_err(&pdev->dev, "Failed to alloc hdmi_sdma_t\n");
+               return -ENOMEM;
+       }
+
+       priv->tx_active = false;
+       spin_lock_init(&priv->irq_lock);
+
+       priv->pdev = hdmi_drvdata->pdev;
+
+       hdmi_dma_init_iec_header();
+
+       dev_set_drvdata(&pdev->dev, priv);
+
+       switch (hdmi_readb(HDMI_REVISION_ID)) {
+       case 0x0a:
+               snd_imx_hardware.period_bytes_max = HDMI_DMA_PERIOD_BYTES / 4;
+               snd_imx_hardware.period_bytes_min = HDMI_DMA_PERIOD_BYTES / 4;
+               break;
+       default:
+               break;
+       }
+
+       ret = snd_soc_register_platform(&pdev->dev, &imx_hdmi_platform);
+       if (ret)
+               goto err_plat;
+
+       return 0;
+
+err_plat:
+       dma_free_coherent(NULL, sizeof(struct hdmi_sdma_script),
+                       priv->hdmi_sdma_t, priv->phy_hdmi_sdma_t);
+
+       return ret;
+}
+
+static int imx_soc_platform_remove(struct platform_device *pdev)
+{
+       struct hdmi_dma_priv *priv = dev_get_drvdata(&pdev->dev);
+
+       dma_free_coherent(NULL, sizeof(struct hdmi_sdma_script),
+                       priv->hdmi_sdma_t, priv->phy_hdmi_sdma_t);
+
+       snd_soc_unregister_platform(&pdev->dev);
+
+       return 0;
+}
+
+static struct platform_driver imx_hdmi_dma_driver = {
+       .driver = {
+               .name = "imx-hdmi-audio",
+               .owner = THIS_MODULE,
+       },
+       .probe = imx_soc_platform_probe,
+       .remove = imx_soc_platform_remove,
+};
+
+module_platform_driver(imx_hdmi_dma_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("i.MX HDMI audio DMA");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/imx-hdmi.c b/sound/soc/fsl/imx-hdmi.c
new file mode 100644 (file)
index 0000000..9733e67
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * ASoC HDMI Transmitter driver for IMX development boards
+ *
+ * Copyright (C) 2011-2014 Freescale Semiconductor, Inc.
+ *
+ * based on stmp3780_devb_hdmi.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/of_platform.h>
+#include <linux/mfd/mxc-hdmi-core.h>
+#include <sound/soc.h>
+
+#include "imx-hdmi.h"
+
+/* imx digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link imx_hdmi_dai_link = {
+       .name = "i.MX HDMI Audio Tx",
+       .stream_name = "i.MX HDMI Audio Tx",
+       .codec_dai_name = "hdmi-hifi",
+       .codec_name = "hdmi-audio-codec",
+       .platform_name = "imx-hdmi-audio",
+};
+
+static struct snd_soc_card snd_soc_card_imx_hdmi = {
+       .name = "imx-hdmi-soc",
+       .dai_link = &imx_hdmi_dai_link,
+       .num_links = 1,
+};
+
+static int imx_hdmi_audio_probe(struct platform_device *pdev)
+{
+       struct device_node *hdmi_np, *np = pdev->dev.of_node;
+       struct snd_soc_card *card = &snd_soc_card_imx_hdmi;
+       struct platform_device *hdmi_pdev;
+       int ret = 0;
+
+       if (!hdmi_get_registered()) {
+               dev_err(&pdev->dev, "initialize HDMI-audio failed. load HDMI-video first!\n");
+               return -ENODEV;
+       }
+
+       hdmi_np = of_parse_phandle(np, "hdmi-controller", 0);
+       if (!hdmi_np) {
+               dev_err(&pdev->dev, "failed to find hdmi-audio cpudai\n");
+               ret = -EINVAL;
+               goto end;
+       }
+
+       hdmi_pdev = of_find_device_by_node(hdmi_np);
+       if (!hdmi_pdev) {
+               dev_err(&pdev->dev, "failed to find SSI platform device\n");
+               ret = -EINVAL;
+               goto end;
+       }
+
+       card->dev = &pdev->dev;
+       card->dai_link->cpu_dai_name = dev_name(&hdmi_pdev->dev);
+
+       platform_set_drvdata(pdev, card);
+
+       ret = snd_soc_register_card(card);
+       if (ret)
+               dev_err(&pdev->dev, "failed to register card: %d\n", ret);
+
+end:
+       if (hdmi_np)
+               of_node_put(hdmi_np);
+
+       return ret;
+}
+
+static int imx_hdmi_audio_remove(struct platform_device *pdev)
+{
+       struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+       snd_soc_unregister_card(card);
+
+       return 0;
+}
+
+static const struct of_device_id imx_hdmi_dt_ids[] = {
+       { .compatible = "fsl,imx-audio-hdmi", },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_hdmi_dt_ids);
+
+static struct platform_driver imx_hdmi_audio_driver = {
+       .probe = imx_hdmi_audio_probe,
+       .remove = imx_hdmi_audio_remove,
+       .driver = {
+               .of_match_table = imx_hdmi_dt_ids,
+               .name = "imx-audio-hdmi",
+               .owner = THIS_MODULE,
+               .pm = &snd_soc_pm_ops,
+       },
+};
+
+module_platform_driver(imx_hdmi_audio_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("IMX HDMI TX ASoC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-audio-hdmi");
diff --git a/sound/soc/fsl/imx-hdmi.h b/sound/soc/fsl/imx-hdmi.h
new file mode 100644 (file)
index 0000000..d06ce9c
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2011-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 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
+
+struct imx_hdmi_sdma_params {
+       dma_addr_t phyaddr;
+       u32 buffer_num;
+       int dma;
+};
+
+struct imx_hdmi {
+       struct snd_soc_dai_driver cpu_dai_drv;
+       struct platform_device *codec_dev;
+       struct platform_device *dma_dev;
+       struct platform_device *pdev;
+       struct clk *isfr_clk;
+       struct clk *iahb_clk;
+       struct clk *mipi_core_clk;
+};
+
+#define HDMI_MAX_RATES 7
+#define HDMI_MAX_SAMPLE_SIZE 3
+#define HDMI_MAX_CHANNEL_CONSTRAINTS 4
+
+#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 | \
+        SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000)
+
+#define MXC_HDMI_FORMATS_PLAYBACK \
+       (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+union hdmi_audio_header_t {
+       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];
+};
+
+union hdmi_audio_dma_data_t {
+       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;
+};
+
+extern union hdmi_audio_header_t iec_header;
+
+#define hdmi_audio_writeb(reg, bit, val) \
+       do { \
+               hdmi_mask_writeb(val, HDMI_ ## reg, \
+                       HDMI_ ## reg ## _ ## bit ## _OFFSET, \
+                       HDMI_ ## reg ## _ ## bit ## _MASK); \
+               pr_debug("Set reg: HDMI_" #reg " (0x%x) "\
+                       "bit: HDMI_" #reg "_" #bit " (%d) to val: %x\n", \
+                       HDMI_ ## reg, HDMI_ ## reg ## _ ## bit ## _OFFSET, val); \
+       } while (0)
+
+#endif /* __IMX_HDMI_H */