HDMI-Audio: Ported to ICS
authorVaibhav Agarwal <vaibhav.agarwal@intel.com>
Wed, 7 Dec 2011 15:33:51 +0000 (21:03 +0530)
committerbuildbot <buildbot@intel.com>
Sun, 25 Dec 2011 10:31:47 +0000 (02:31 -0800)
BZ: 17078

Steps to verify:
1. Ensure device is not in sleep state
2. alsa_aplay -l
3. alsa_aplay -Dplughw:IntelHDMI,0 -F 23220 <file_name>

Change-Id: I48626a0dec8fdccfdec90a2b9a8747bfe11ea250
Signed-off-by: Vaibhav Agarwal <vaibhav.agarwal@intel.com>
Reviewed-on: http://android.intel.com:8080/26982
Reviewed-by: Koul, Vinod <vinod.koul@intel.com>
Reviewed-by: Centelles, Sylvain <sylvain.centelles@intel.com>
Reviewed-by: Babu, Ramesh <ramesh.babu@intel.com>
Reviewed-by: Gupta, ArvindX K <arvindx.k.gupta@intel.com>
Reviewed-by: M, Arulselvan <arulselvan.m@intel.com>
Tested-by: M, Arulselvan <arulselvan.m@intel.com>
Reviewed-by: buildbot <buildbot@intel.com>
Tested-by: buildbot <buildbot@intel.com>
arch/x86/platform/mrst/mrst.c
sound/drivers/Kconfig
sound/drivers/Makefile
sound/drivers/intel_mid_hdmi/Makefile [new file with mode: 0644]
sound/drivers/intel_mid_hdmi/intel_mid_hdmi_audio.c [new file with mode: 0644]
sound/drivers/intel_mid_hdmi/intel_mid_hdmi_audio.h [new file with mode: 0644]
sound/drivers/intel_mid_hdmi/intel_mid_hdmi_audio_if.c [new file with mode: 0644]

index c84cede..838699c 100644 (file)
@@ -976,6 +976,19 @@ static void *msic_audio_platform_data(void *info)
                return NULL;
        }
 
+       pdev = platform_device_alloc("hdmi-audio", -1);
+       if (!pdev) {
+               pr_err("failed to allocate hdmi-audio platform device\n");
+               return NULL;
+       }
+
+       ret = platform_device_add(pdev);
+       if (ret) {
+               pr_err("failed to add hdmi-audio platform device\n");
+               platform_device_put(pdev);
+               return NULL;
+       }
+
        return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_AUDIO);
 }
 
index c896116..c7273db 100644 (file)
@@ -219,4 +219,15 @@ config SND_AC97_POWER_SAVE_DEFAULT
 
          See SND_AC97_POWER_SAVE for more details.
 
+config SND_INTELMID_HDMI_AUDIO
+        tristate "Intel MID based HDMI AUDIO driver"
+        select SND_PCM
+        depends on DRM_MDFLD && MDFD_HDMI
+        default n
+        help
+           Intel MID HDMI Audio Driver (HAD) enables audio playback through
+           HDMI interface on Intel Medfield MID platform.  This is an ALSA
+           driver.  This driver programs HDMI audio sub-system registers
+           according to the HDMI 1.3a spec.
+
 endif  # SND_DRIVERS
index 1a8440c..f1c4008 100644 (file)
@@ -23,3 +23,4 @@ obj-$(CONFIG_SND_PORTMAN2X4) += snd-portman2x4.o
 obj-$(CONFIG_SND_ML403_AC97CR) += snd-ml403-ac97cr.o
 
 obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/ pcsp/
+obj-$(CONFIG_SND_INTELMID_HDMI_AUDIO)   += intel_mid_hdmi/
diff --git a/sound/drivers/intel_mid_hdmi/Makefile b/sound/drivers/intel_mid_hdmi/Makefile
new file mode 100644 (file)
index 0000000..a7ac25d
--- /dev/null
@@ -0,0 +1,3 @@
+EXTRA_CFLAGS += -Idrivers/staging/mrst/drv
+obj-$(CONFIG_SND_INTELMID_HDMI_AUDIO)  += intel_mid_had.o
+intel_mid_had-objs := intel_mid_hdmi_audio.o intel_mid_hdmi_audio_if.o
diff --git a/sound/drivers/intel_mid_hdmi/intel_mid_hdmi_audio.c b/sound/drivers/intel_mid_hdmi/intel_mid_hdmi_audio.c
new file mode 100644 (file)
index 0000000..be6fd03
--- /dev/null
@@ -0,0 +1,1314 @@
+/*
+ *   intel_mid_hdmi_audio.c - Intel HDMI audio driver for MID
+ *
+ *  Copyright (C) 2010 Intel Corp
+ *  Authors:   Sailaja Bandarupalli <sailaja.bandarupalli@intel.com>
+ *             Ramesh Babu K V <ramesh.babu@intel.com>
+ *             Vaibhav Agarwal <vaibhav.agarwal@intel.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; version 2 of the License.
+ *
+ *  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.,
+ *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * ALSA driver for Intel MID HDMI audio controller
+ */
+
+#define pr_fmt(fmt)    "had: " fmt
+
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/core.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+#include "intel_mid_hdmi_audio.h"
+
+#define PCM_INDEX              0
+#define MAX_PB_STREAMS         1
+#define MAX_CAP_STREAMS                0
+#define HDMI_AUDIO_DRIVER      "hdmi-audio"
+static DEFINE_MUTEX(had_mutex);
+
+/*standard module options for ALSA. This module supports only one card*/
+static int hdmi_card_index = SNDRV_DEFAULT_IDX1;
+static char *hdmi_card_id = SNDRV_DEFAULT_STR1;
+static struct snd_intelhad *had_pvt_data;
+
+module_param(hdmi_card_index, int, 0444);
+MODULE_PARM_DESC(hdmi_card_index,
+               "Index value for INTEL Intel HDMI Audio controller.");
+module_param(hdmi_card_id, charp, 0444);
+MODULE_PARM_DESC(hdmi_card_id,
+               "ID string for INTEL Intel HDMI Audio controller.");
+MODULE_AUTHOR("Sailaja Bandarupalli <sailaja.bandarupalli@intel.com>");
+MODULE_AUTHOR("Ramesh Babu K V <ramesh.babu@intel.com>");
+MODULE_AUTHOR("Vaibhav Agarwal <vaibhav.agarwal@intel.com>");
+MODULE_DESCRIPTION("Intel HDMI Audio driver");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{Intel,Intel_HAD}");
+MODULE_VERSION(HAD_DRIVER_VERSION);
+
+#define INFO_FRAME_WORD1       0x000a0184
+#define FIFO_THRESHOLD         0xFE
+#define BYTES_PER_WORD         0x4
+#define CH_STATUS_MAP_32KHZ    0x3
+#define CH_STATUS_MAP_44KHZ    0x0
+#define CH_STATUS_MAP_48KHZ    0x2
+#define MAX_SMPL_WIDTH_20      0x0
+#define MAX_SMPL_WIDTH_24      0x1
+#define SMPL_WIDTH_16BITS      0x1
+#define SMPL_WIDTH_24BITS      0x5
+#define CHANNEL_ALLOCATION     0x1F
+#define MASK_BYTE0             0x000000FF
+#define VALID_DIP_WORDS                3
+#define LAYOUT0                        0
+#define LAYOUT1                        1
+
+/* hardware capability structure */
+static const struct snd_pcm_hardware snd_intel_hadstream = {
+       .info = (SNDRV_PCM_INFO_INTERLEAVED |
+               SNDRV_PCM_INFO_DOUBLE |
+               SNDRV_PCM_INFO_MMAP|
+               SNDRV_PCM_INFO_MMAP_VALID |
+               SNDRV_PCM_INFO_BATCH),
+       .formats = (SNDRV_PCM_FMTBIT_S24 |
+               SNDRV_PCM_FMTBIT_U24),
+       .rates = 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,
+       .rate_min = HAD_MIN_RATE,
+       .rate_max = HAD_MAX_RATE,
+       .channels_min = HAD_MIN_CHANNEL,
+       .channels_max = HAD_MAX_CHANNEL,
+       .buffer_bytes_max = HAD_MAX_PERIOD_BYTES,
+       .period_bytes_min = HAD_MIN_PERIOD_BYTES,
+       .period_bytes_max = HAD_MAX_BUFFER,
+       .periods_min = HAD_MIN_PERIODS,
+       .periods_max = HAD_MAX_PERIODS,
+       .fifo_size = HAD_FIFO_SIZE,
+};
+
+/**
+ * snd_intelhad_init_audio_ctrl - to initialize audio channel status
+ * registers and confgiuration registers
+ *
+ * @substream:substream for which the prepare function is called
+ * @intelhaddata:substream private data
+ *
+ * This function is called in the prepare callback
+ */
+int snd_intelhad_init_audio_ctrl(struct snd_pcm_substream *substream,
+                                       struct snd_intelhad *intelhaddata,
+                                       int flag_silence)
+{
+       union aud_cfg cfg_val = {.cfg_regval = 0};
+       union aud_ch_status_0 ch_stat0 = {.status_0_regval = 0};
+       union aud_ch_status_1 ch_stat1 = {.status_1_regval = 0};
+       union aud_buf_config buf_cfg = {.buf_cfgval = 0};
+       u8 channels;
+       int rate, format;
+
+       ch_stat0.status_0_regx.lpcm_id = (intelhaddata->aes_bits &
+                                               IEC958_AES0_NONAUDIO)>>1;
+       ch_stat0.status_0_regx.clk_acc = (intelhaddata->aes_bits &
+                                               IEC958_AES3_CON_CLOCK)>>4;
+       if (flag_silence)
+               rate = AUD_SAMPLE_RATE_44_1;
+       else
+               rate = substream->runtime->rate;
+
+       switch (rate) {
+       case AUD_SAMPLE_RATE_32:
+               ch_stat0.status_0_regx.samp_freq = CH_STATUS_MAP_32KHZ;
+       break;
+
+       case AUD_SAMPLE_RATE_44_1:
+       case AUD_SAMPLE_RATE_88_2:
+       case AUD_SAMPLE_RATE_176_4:
+               ch_stat0.status_0_regx.samp_freq = CH_STATUS_MAP_44KHZ;
+       break;
+
+       case AUD_SAMPLE_RATE_48:
+       case AUD_SAMPLE_RATE_96:
+       case HAD_MAX_RATE:
+               ch_stat0.status_0_regx.samp_freq = CH_STATUS_MAP_48KHZ;
+       break;
+
+       default:
+               return -EINVAL;
+       break;
+
+       }
+       intelhaddata->reg_ops.hdmi_audio_write_register(
+                               AUD_CH_STATUS_0, ch_stat0.status_0_regval);
+
+       if (flag_silence)
+               format = SNDRV_PCM_FORMAT_S24_LE;
+       else
+               format = substream->runtime->format;
+
+       if (format == SNDRV_PCM_FORMAT_S16_LE) {
+               ch_stat1.status_1_regx.max_wrd_len = MAX_SMPL_WIDTH_20;
+               ch_stat1.status_1_regx.wrd_len = SMPL_WIDTH_16BITS;
+       } else if (format == SNDRV_PCM_FORMAT_S24_LE) {
+               ch_stat1.status_1_regx.max_wrd_len = MAX_SMPL_WIDTH_24;
+               ch_stat1.status_1_regx.wrd_len = SMPL_WIDTH_24BITS;
+       } else {
+               ch_stat1.status_1_regx.max_wrd_len = 0;
+               ch_stat1.status_1_regx.wrd_len = 0;
+       }
+       intelhaddata->reg_ops.hdmi_audio_write_register(
+                               AUD_CH_STATUS_1, ch_stat1.status_1_regval);
+
+       buf_cfg.buf_cfg_regx.fifo_width = FIFO_THRESHOLD;
+       buf_cfg.buf_cfg_regx.aud_delay = 0;
+       intelhaddata->reg_ops.hdmi_audio_write_register(
+                                       AUD_BUF_CONFIG, buf_cfg.buf_cfgval);
+
+       if (flag_silence)
+               channels = HAD_MIN_CHANNEL;
+       else
+               channels = substream->runtime->channels;
+
+
+       switch (channels) {
+       case 1:
+       case 2:
+               cfg_val.cfg_regx.num_ch = CH_STEREO;
+               cfg_val.cfg_regx.layout = LAYOUT0;
+       break;
+
+       case 3:
+       case 4:
+               cfg_val.cfg_regx.num_ch = CH_THREE_FOUR;
+               cfg_val.cfg_regx.layout = LAYOUT1;
+       break;
+
+       case 5:
+       case 6:
+               cfg_val.cfg_regx.num_ch = CH_FIVE_SIX;
+               cfg_val.cfg_regx.layout = LAYOUT1;
+       break;
+
+       case 7:
+       case 8:
+               cfg_val.cfg_regx.num_ch = CH_SEVEN_EIGHT;
+               cfg_val.cfg_regx.layout = LAYOUT1;
+       break;
+
+       }
+
+       cfg_val.cfg_regx.val_bit = 1;
+       intelhaddata->reg_ops.hdmi_audio_write_register(
+                                               AUD_CONFIG, cfg_val.cfg_regval);
+       return 0;
+}
+
+/**
+ * snd_intelhad_prog_dip - to initialize Data Island Packets registers
+ *
+ * @substream:substream for which the prepare function is called
+ * @intelhaddata:substream private data
+ *
+ * This function is called in the prepare callback
+ */
+static void snd_intelhad_prog_dip(struct snd_pcm_substream *substream,
+                               struct snd_intelhad *intelhaddata,
+                               int flag_silence)
+{
+       int i;
+       union aud_ctrl_st ctrl_state = {.ctrl_val = 0};
+       union aud_info_frame2 frame2 = {.fr2_val = 0};
+       union aud_info_frame3 frame3 = {.fr3_val = 0};
+       u8 checksum = 0;
+
+       intelhaddata->reg_ops.hdmi_audio_write_register(
+                                       AUD_CNTL_ST, ctrl_state.ctrl_val);
+
+       if (flag_silence)
+               frame2.fr2_regx.chnl_cnt = HAD_MIN_CHANNEL - 1;
+       else
+               frame2.fr2_regx.chnl_cnt = substream->runtime->channels - 1;
+
+       /* Set to stereo */
+       frame3.fr3_regx.chnl_alloc = 0;
+
+       /*Calculte the byte wide checksum for all valid DIP words*/
+       for (i = 0; i < BYTES_PER_WORD; i++)
+               checksum += (INFO_FRAME_WORD1 >> i*BITS_PER_BYTE) & MASK_BYTE0;
+       for (i = 0; i < BYTES_PER_WORD; i++)
+               checksum += (frame2.fr2_val >> i*BITS_PER_BYTE) & MASK_BYTE0;
+       for (i = 0; i < BYTES_PER_WORD; i++)
+               checksum += (frame3.fr3_val >> i*BITS_PER_BYTE) & MASK_BYTE0;
+
+       frame2.fr2_regx.chksum = -(checksum);
+
+       intelhaddata->reg_ops.hdmi_audio_write_register(
+                                       AUD_HDMIW_INFOFR, INFO_FRAME_WORD1);
+       intelhaddata->reg_ops.hdmi_audio_write_register(
+                                       AUD_HDMIW_INFOFR, frame2.fr2_val);
+       intelhaddata->reg_ops.hdmi_audio_write_register(
+                                       AUD_HDMIW_INFOFR, frame3.fr3_val);
+       /* program remaining DIP words with zero */
+       for (i = 0; i < HAD_MAX_DIP_WORDS-VALID_DIP_WORDS; i++)
+               intelhaddata->reg_ops.hdmi_audio_write_register(
+                                       AUD_HDMIW_INFOFR, 0x0);
+
+       ctrl_state.ctrl_regx.dip_freq = 1;
+       ctrl_state.ctrl_regx.dip_en_sta = 1;
+       intelhaddata->reg_ops.hdmi_audio_write_register(
+                                       AUD_CNTL_ST, ctrl_state.ctrl_val);
+}
+
+/**
+ * snd_intelhad_prog_buffer - programs buffer
+ * address and length registers
+ *
+ * @substream:substream for which the prepare function is called
+ * @intelhaddata:substream private data
+ *
+ * This function programs ring buffer address and length into registers.
+ */
+int snd_intelhad_prog_buffer(struct snd_intelhad *intelhaddata,
+                                       int start, int end)
+{
+       u32 ring_buf_addr, ring_buf_size, period_bytes;
+       u8 i, num_periods;
+       struct snd_pcm_substream *substream;
+
+       substream = intelhaddata->stream_info.had_substream;
+       ring_buf_addr = virt_to_phys(substream->runtime->dma_area);
+       ring_buf_size = snd_pcm_lib_buffer_bytes(substream);
+       intelhaddata->stream_info.ring_buf_size = ring_buf_size;
+       period_bytes = frames_to_bytes(substream->runtime,
+                               substream->runtime->period_size);
+       num_periods = substream->runtime->periods;
+
+       pr_debug("Ring buffer address = %#x\n", ring_buf_addr);
+       pr_debug("Ring buffer size = %#x\n", ring_buf_size);
+       pr_debug("period size in bytes = %d\n", period_bytes);
+
+       /* buffer addr should  be 64 byte aligned, period bytes
+        will be used to calculate addr offset*/
+       period_bytes &= ~0x3F;
+       pr_debug("period size in bytes after align = %d\n", period_bytes);
+
+       /* Hardware supports MAX_PERIODS buffers */
+       if (end >= HAD_MAX_PERIODS)
+               return -EINVAL;
+
+       for (i = start; i <= end; i++) {
+               /* Program the buf registers with addr and len */
+               intelhaddata->buf_info[i].buf_addr = ring_buf_addr +
+                                                        (i * period_bytes);
+               if (i < num_periods-1)
+                       intelhaddata->buf_info[i].buf_size = period_bytes;
+               else
+                       intelhaddata->buf_info[i].buf_size = ring_buf_size -
+                                                       (period_bytes*i);
+
+               intelhaddata->reg_ops.hdmi_audio_write_register(
+                                       AUD_BUF_A_ADDR + (i * HAD_REG_WIDTH),
+                                       intelhaddata->buf_info[i].buf_addr |
+                                       BIT(0) | BIT(1));
+               intelhaddata->reg_ops.hdmi_audio_write_register(
+                                       AUD_BUF_A_LENGTH + (i * HAD_REG_WIDTH),
+                                       period_bytes);
+               intelhaddata->buf_info[i].is_valid = true;
+               pr_debug("buf[%d] addr=%#x  and size=%d\n", i,
+                               intelhaddata->buf_info[i].buf_addr,
+                               intelhaddata->buf_info[i].buf_size);
+       }
+       intelhaddata->valid_buf_cnt = num_periods;
+       return 0;
+}
+
+inline void snd_intelhad_read_len(struct snd_intelhad *intelhaddata)
+{
+       int i;
+       u32 len;
+       for (i = 0; i < 4 ; i++) {
+               /* Program the buf registers with addr and len */
+               intelhaddata->reg_ops.hdmi_audio_read_register(
+                                       AUD_BUF_A_LENGTH + (i * HAD_REG_WIDTH),
+                                       &len);
+               pr_debug("buf[%d] size=%d\n", i, len);
+       }
+}
+
+/**
+ * snd_intelhad_prog_cts - Program HDMI audio CTS value
+ *
+ * @aud_samp_freq: sampling frequency of audio data
+ * @tmds: sampling frequency of the display data
+ * @n_param: N value, depends on aud_samp_freq
+ * @intelhaddata:substream private data
+ *
+ * Program CTS register based on the audio and display sampling frequency
+ */
+static void snd_intelhad_prog_cts(u32 aud_samp_freq, u32 tmds, u32 n_param,
+                               struct snd_intelhad *intelhaddata)
+{
+       u32 cts_val;
+       u64 dividend, divisor;
+
+       /* Calculate CTS according to HDMI 1.3a spec*/
+       dividend = (u64)tmds * n_param*1000;
+       divisor = 128 * aud_samp_freq;
+       cts_val = div64_u64(dividend, divisor);
+       pr_debug("CTS Value=%d\n", cts_val);
+       intelhaddata->reg_ops.hdmi_audio_write_register(
+                               AUD_HDMI_CTS, (BIT(20) | cts_val));
+}
+
+/**
+ * snd_intelhad_prog_n - Program HDMI audio N value
+ *
+ * @aud_samp_freq: sampling frequency of audio data
+ * @n_param: N value, depends on aud_samp_freq
+ * @intelhaddata:substream private data
+ *
+ * This function is called in the prepare callback.
+ * It programs based on the audio and display sampling frequency
+ */
+static int snd_intelhad_prog_n(u32 aud_samp_freq, u32 *n_param,
+                               struct snd_intelhad *intelhaddata)
+{
+       u32 n_val;
+       int retval = 0;
+
+       /* Select N according to HDMI 1.3a spec*/
+       switch (aud_samp_freq) {
+       case AUD_SAMPLE_RATE_32:
+               n_val = 4096;
+       break;
+
+       case AUD_SAMPLE_RATE_44_1:
+               n_val = 6272;
+       break;
+
+       case AUD_SAMPLE_RATE_48:
+               n_val = 6144;
+       break;
+
+       case AUD_SAMPLE_RATE_88_2:
+               n_val = 12544;
+       break;
+
+       case AUD_SAMPLE_RATE_96:
+               n_val = 12288;
+       break;
+
+       case AUD_SAMPLE_RATE_176_4:
+               n_val = 25088;
+       break;
+
+       case HAD_MAX_RATE:
+               n_val = 24576;
+       break;
+
+       default:
+               retval = -EINVAL;
+       break;
+
+       }
+       if (retval)
+               return retval;
+       intelhaddata->reg_ops.hdmi_audio_write_register(
+                               AUD_N_ENABLE, (BIT(20) | n_val));
+       *n_param = n_val;
+       return retval;
+}
+
+/**
+* snd_intelhad_open - stream initializations are done here
+* @substream:substream for which the stream function is called
+*
+* This function is called whenever a PCM stream is opened
+*/
+static int snd_intelhad_open(struct snd_pcm_substream *substream)
+{
+       struct snd_intelhad *intelhaddata;
+       struct snd_pcm_runtime *runtime;
+       struct had_stream_pvt *stream;
+       int retval;
+
+       pr_debug("snd_intelhad_open called\n");
+       intelhaddata = snd_pcm_substream_chip(substream);
+
+       mutex_lock(&intelhaddata->had_lock);
+       if (intelhaddata->drv_status != HAD_DRV_CONNECTED) {
+               pr_err("had in disconnected/suspended state :%d\n",
+                               intelhaddata->drv_status);
+               mutex_unlock(&intelhaddata->had_lock);
+               return -ENODEV;
+       }
+
+       if (intelhaddata->playback_cnt > 0) {
+               mutex_unlock(&intelhaddata->had_lock);
+               return -EBUSY;
+       } else
+               intelhaddata->playback_cnt++;
+
+       mutex_unlock(&intelhaddata->had_lock);
+
+       runtime = substream->runtime;
+       /* set the runtime hw parameter with local snd_pcm_hardware struct */
+       runtime->hw = snd_intel_hadstream;
+
+       stream = kzalloc(sizeof(*stream), GFP_KERNEL);
+       if (!stream) {
+               retval = -ENOMEM;
+               goto exit_err;
+       }
+       stream->stream_status = STREAM_INIT;
+       runtime->private_data = stream;
+       retval = snd_pcm_hw_constraint_integer(runtime,
+                        SNDRV_PCM_HW_PARAM_PERIODS);
+       if (retval < 0) {
+               kfree(stream);
+               goto exit_err;
+       }
+       mutex_lock(&intelhaddata->had_lock);
+       intelhaddata->drv_status = HAD_DRV_RUNNING;
+       mutex_unlock(&intelhaddata->had_lock);
+       return retval;
+exit_err:
+       mutex_lock(&intelhaddata->had_lock);
+       intelhaddata->playback_cnt--;
+       mutex_unlock(&intelhaddata->had_lock);
+       return retval;
+}
+
+/**
+* had_period_elapsed - updates the hardware pointer status
+* @had_substream:substream for which the stream function is called
+*
+*/
+static void had_period_elapsed(void *had_substream)
+{
+       struct snd_pcm_substream *substream = had_substream;
+       struct had_stream_pvt *stream;
+
+       if (!substream || !substream->runtime)
+               return;
+       stream = substream->runtime->private_data;
+       if (!stream)
+               return;
+
+       if (stream->stream_status != STREAM_RUNNING)
+               return;
+       snd_pcm_period_elapsed(substream);
+       return;
+}
+
+/**
+* snd_intelhad_init_stream - internal function to initialize stream info
+* @substream:substream for which the stream function is called
+*
+*/
+static int snd_intelhad_init_stream(struct snd_pcm_substream *substream)
+{
+       struct snd_intelhad *intelhaddata = snd_pcm_substream_chip(substream);
+
+       pr_debug("setting buffer ptr param\n");
+       intelhaddata->stream_info.period_elapsed = had_period_elapsed;
+       intelhaddata->stream_info.had_substream = substream;
+       intelhaddata->stream_info.buffer_ptr = 0;
+       intelhaddata->stream_info.buffer_rendered = 0;
+       intelhaddata->stream_info.sfreq = substream->runtime->rate;
+       return 0;
+}
+
+/**
+ * snd_intelhad_close- to free parameteres when stream is stopped
+ *
+ * @substream:  substream for which the function is called
+ *
+ * This function is called by ALSA framework when stream is stopped
+ */
+static int snd_intelhad_close(struct snd_pcm_substream *substream)
+{
+       struct snd_intelhad *intelhaddata;
+
+       pr_debug("snd_intelhad_close called\n");
+       intelhaddata = snd_pcm_substream_chip(substream);
+       mutex_lock(&intelhaddata->had_lock);
+       if (intelhaddata->playback_cnt)
+               intelhaddata->playback_cnt--;
+
+       intelhaddata->stream_info.buffer_rendered = 0;
+       intelhaddata->stream_info.buffer_ptr = 0;
+       intelhaddata->stream_info.str_id = 0;
+
+       if (intelhaddata->drv_status != HAD_DRV_DISCONNECTED)
+               intelhaddata->drv_status = HAD_DRV_CONNECTED;
+       mutex_unlock(&intelhaddata->had_lock);
+       kfree(substream->runtime->private_data);
+       return 0;
+}
+
+/**
+ * snd_intelhad_hw_params- to setup the hardware parameters
+ * like allocating the buffers
+ *
+ * @substream:  substream for which the function is called
+ * @hw_params: hardware parameters
+ *
+ * This function is called by ALSA framework when hardware params are set
+ */
+static int snd_intelhad_hw_params(struct snd_pcm_substream *substream,
+                                   struct snd_pcm_hw_params *hw_params)
+{
+       int retval;
+       u32 buf_size;
+
+       BUG_ON(!hw_params);
+       pr_debug("snd_intelhad_hw_params called\n");
+
+       buf_size = params_buffer_bytes(hw_params);
+
+       if (buf_size % 64) {
+               pr_err("Invalid buffer size\n");
+               return -EINVAL;
+       }
+
+       retval = snd_pcm_lib_malloc_pages(substream, buf_size);
+       if (retval < 0)
+               return retval;
+       pr_debug("allocated memory = %d\n", buf_size);
+       memset(substream->runtime->dma_area, 0, buf_size);
+
+       pr_debug("snd_intelhad_hw_params exited\n");
+       return retval;
+}
+
+/**
+ * snd_intelhad_hw_free- to release the resources allocated during
+ * hardware params setup
+ *
+ * @substream:  substream for which the function is called
+ *
+ * This function is called by ALSA framework before close callback.
+ *
+ */
+static int snd_intelhad_hw_free(struct snd_pcm_substream *substream)
+{
+       pr_debug("snd_intelhad_hw_free called\n");
+       return snd_pcm_lib_free_pages(substream);
+}
+
+int snd_intelhad_configure_silence(struct snd_intelhad *intelhaddata)
+{
+       int retval = 0;
+       u32 disp_samp_freq, n_param;
+
+       pr_debug("Enter %s\n", __func__);
+       /* Get N value in KHz */
+       retval = intelhaddata->query_ops.hdmi_audio_get_caps(
+                               HAD_GET_SAMPLING_FREQ, &disp_samp_freq);
+       if (retval) {
+               pr_err("querying display sampling freq failed %#x\n", retval);
+               goto out;
+       } else
+               pr_debug("%s:TMDS freq = %d kHz\n", __func__, disp_samp_freq);
+
+       retval = snd_intelhad_prog_n(AUD_SAMPLE_RATE_44_1, &n_param,
+                                                               intelhaddata);
+       if (retval) {
+               pr_err("programming N value failed %#x\n", retval);
+               goto out;
+       }
+       snd_intelhad_prog_cts(AUD_SAMPLE_RATE_44_1, disp_samp_freq, n_param,
+                       intelhaddata);
+
+       snd_intelhad_prog_dip(NULL, intelhaddata, 1);
+       retval = snd_intelhad_init_audio_ctrl(NULL, intelhaddata, 1);
+
+out:
+       return retval;
+}
+
+int snd_intelhad_start_silence(struct snd_intelhad *intelhaddata)
+{
+       int i, retval = 0;
+       u32 buf_addr;
+
+       pr_debug("Enter %s\n", __func__);
+
+       buf_addr = virt_to_phys(intelhaddata->flat_data);
+
+       /* Program buff C, D */
+       for (i = HAD_BUF_TYPE_C; i < HAD_MAX_PERIODS; i++) {
+               /* Program the buf registers with addr and len */
+               intelhaddata->buf_info[i].buf_addr = buf_addr;
+               intelhaddata->buf_info[i].buf_size = MAX_SZ_ZERO_BUF;
+
+               intelhaddata->reg_ops.hdmi_audio_write_register(
+                                       AUD_BUF_A_ADDR + (i * HAD_REG_WIDTH),
+                                       buf_addr | BIT(0) | BIT(1));
+               intelhaddata->reg_ops.hdmi_audio_write_register(
+                                       AUD_BUF_A_LENGTH + (i * HAD_REG_WIDTH),
+                                       MAX_SZ_ZERO_BUF);
+               intelhaddata->buf_info[i].is_valid = true;
+               pr_debug("buf[%d] addr=%#x  and size=%d\n", i,
+                               intelhaddata->buf_info[i].buf_addr,
+                               intelhaddata->buf_info[i].buf_size);
+       }
+       intelhaddata->valid_buf_cnt = HAD_MAX_PERIODS;
+       intelhaddata->curr_buf = HAD_BUF_TYPE_C;
+
+       intelhaddata->reg_ops.hdmi_audio_read_modify(AUD_CONFIG, 1,
+                               BIT(0));
+
+       return retval;
+}
+
+int snd_intelhad_stop_silence(struct snd_intelhad *intelhaddata)
+{
+       int i, retval = 0;
+
+       pr_debug("Enter %s\n", __func__);
+
+       intelhaddata->reg_ops.hdmi_audio_read_modify(AUD_CONFIG, 0,
+                               BIT(0));
+
+       /* Invalidate Audio buffers C & D */
+       for (i = HAD_BUF_TYPE_C; i < HAD_MAX_PERIODS; i++) {
+               /* Program the buf registers with addr and len */
+               intelhaddata->reg_ops.hdmi_audio_write_register(
+                               AUD_BUF_A_ADDR + (i * HAD_REG_WIDTH),
+                               0);
+               intelhaddata->reg_ops.hdmi_audio_write_register(
+                               AUD_BUF_A_LENGTH + (i * HAD_REG_WIDTH),
+                               0);
+       }
+       /* Reset buffer pointers */
+       intelhaddata->reg_ops.hdmi_audio_write_register(AUD_HDMI_STATUS, 1);
+       intelhaddata->reg_ops.hdmi_audio_write_register(AUD_HDMI_STATUS, 0);
+
+       return retval;
+}
+
+void snd_process_stop_trigger(struct snd_intelhad *intelhaddata)
+{
+       int buf_id, buff_done, i;
+       u32 buf_addr;
+
+       buff_done = intelhaddata->buff_done;
+       if (intelhaddata->valid_buf_cnt-1 == buff_done)
+               buf_id = HAD_BUF_TYPE_A;
+       else
+               buf_id = buff_done + 1;
+
+       pr_debug("%s:buf_id=%d\n", __func__, buf_id);
+       /* Invalidate Audio buffers */
+       for (i = 0; i < HAD_MAX_PERIODS; i++) {
+               if (i == buf_id)
+                       continue;
+               if (i < HAD_BUF_TYPE_C) {
+                       /* invalidate */
+                       intelhaddata->buf_info[i].buf_size = 0;
+                       intelhaddata->reg_ops.hdmi_audio_write_register(
+                                       AUD_BUF_A_ADDR + (i * HAD_REG_WIDTH),
+                                       0);
+                       intelhaddata->reg_ops.hdmi_audio_write_register(
+                                       AUD_BUF_A_LENGTH + (i * HAD_REG_WIDTH),
+                                       0);
+               } else {
+                       /* Program with silence buffer */
+                       buf_addr = virt_to_phys(intelhaddata->flat_data);
+
+                       /* Program the buf registers with addr and len */
+                       intelhaddata->buf_info[i].buf_addr = buf_addr;
+                       intelhaddata->buf_info[i].buf_size = MAX_SZ_ZERO_BUF;
+
+                       intelhaddata->reg_ops.hdmi_audio_write_register(
+                                       AUD_BUF_A_ADDR + (i * HAD_REG_WIDTH),
+                                       buf_addr | BIT(0) | BIT(1));
+                       intelhaddata->reg_ops.hdmi_audio_write_register(
+                                       AUD_BUF_A_LENGTH + (i * HAD_REG_WIDTH),
+                                       (MAX_SZ_ZERO_BUF));
+                       intelhaddata->buf_info[i].is_valid = true;
+                       pr_debug("buf[%d] addr=%#x  and size=%d\n", i,
+                                       intelhaddata->buf_info[i].buf_addr,
+                                       intelhaddata->buf_info[i].buf_size);
+
+               }
+       }
+}
+
+/**
+* snd_intelhad_pcm_trigger - stream activities are handled here
+* @substream:substream for which the stream function is called
+* @cmd:the stream commamd thats requested from upper layer
+* This function is called whenever an a stream activity is invoked
+*/
+static int snd_intelhad_pcm_trigger(struct snd_pcm_substream *substream,
+                                       int cmd)
+{
+       int retval = 0;
+       int buf_id;
+       unsigned long flag_irq;
+       struct snd_intelhad *intelhaddata;
+       struct had_stream_pvt *stream;
+       struct hdmi_audio_registers_ops reg_ops;
+
+       intelhaddata = snd_pcm_substream_chip(substream);
+       stream = substream->runtime->private_data;
+       reg_ops = intelhaddata->reg_ops;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+               pr_debug("Trigger Start\n");
+               stream->substream = substream;
+               stream->stream_status = STREAM_RUNNING;
+               snd_intelhad_read_len(intelhaddata);
+
+               /* Disable local INTRs till register prgmng is done */
+               spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irq);
+               if (intelhaddata->buff_done == HAD_BUF_TYPE_D)
+                       buf_id = HAD_BUF_TYPE_C;
+               else
+                       buf_id = HAD_BUF_TYPE_D;
+
+               /* Re-program silence buffers */
+               if (buf_id == HAD_BUF_TYPE_C) {
+                       /* Disable Buffer D*/
+                       reg_ops.hdmi_audio_write_register(AUD_BUF_A_ADDR +
+                                       (HAD_BUF_TYPE_D * HAD_REG_WIDTH), 0);
+                       reg_ops.hdmi_audio_write_register(AUD_BUF_A_LENGTH +
+                                       (HAD_BUF_TYPE_D * HAD_REG_WIDTH), 0);
+               }
+               retval = snd_intelhad_prog_buffer(intelhaddata, HAD_BUF_TYPE_A,
+                               HAD_BUF_TYPE_B);
+               /* Start reporting BUFFER_DONE/UNDERRUN to above layers*/
+               intelhaddata->pcm_active = 1;
+               intelhaddata->start_trigger = 1;
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irq);
+               pr_debug("Processed _Start, buf_id = %d\n", buf_id);
+
+               break;
+
+       case SNDRV_PCM_TRIGGER_STOP:
+               pr_debug("Trigger Stop\n");
+               snd_intelhad_read_len(intelhaddata);
+               spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irq);
+               intelhaddata->stream_info.str_id = 0;
+               intelhaddata->send_data = 0;
+
+               /* Stop reporting BUFFER_DONE/UNDERRUN to above layers*/
+               intelhaddata->pcm_active = 0;
+               intelhaddata->stop_trigger = 1;
+               /* Send zero filled data */
+               if (intelhaddata->drv_status != HAD_DRV_DISCONNECTED)
+                       snd_process_stop_trigger(intelhaddata);
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irq);
+               cancel_delayed_work(&intelhaddata->dummy_audio);
+
+               stream->stream_status = STREAM_DROPPED;
+               break;
+
+       default:
+               retval = -EINVAL;
+       }
+       return retval;
+}
+
+/**
+* snd_intelhad_pcm_prepare- internal preparation before starting a stream
+*
+* @substream:  substream for which the function is called
+*
+* This function is called when a stream is started for internal preparation.
+*/
+static int snd_intelhad_pcm_prepare(struct snd_pcm_substream *substream)
+{
+       int retval;
+       u32 disp_samp_freq, n_param;
+       struct snd_intelhad *intelhaddata;
+       struct snd_pcm_runtime *runtime;
+
+       pr_debug("pcm_prepare called\n");
+
+       runtime = substream->runtime;
+       pr_debug("period_size=%d\n",
+                               frames_to_bytes(runtime, runtime->period_size));
+       pr_debug("periods=%d\n", runtime->periods);
+       pr_debug("buffer_size=%d\n", snd_pcm_lib_buffer_bytes(substream));
+       pr_debug("rate=%d\n", runtime->rate);
+       pr_debug("channels=%d\n", runtime->channels);
+
+       intelhaddata = snd_pcm_substream_chip(substream);
+       if (intelhaddata->stream_info.str_id) {
+               pr_debug("_prepare is called for existing str_id#%d\n",
+                                       intelhaddata->stream_info.str_id);
+               retval = snd_intelhad_pcm_trigger(substream,
+                                               SNDRV_PCM_TRIGGER_STOP);
+               return retval;
+       }
+
+       intelhaddata->stream_info.str_id = intelhaddata->playback_cnt;
+       snprintf(substream->pcm->id, sizeof(substream->pcm->id),
+                       "%d", intelhaddata->stream_info.str_id);
+       retval = snd_intelhad_init_stream(substream);
+       if (retval)
+               goto prep_end;
+       /* Get N value in KHz */
+       retval = intelhaddata->query_ops.hdmi_audio_get_caps(
+                               HAD_GET_SAMPLING_FREQ, &disp_samp_freq);
+       if (retval) {
+               pr_err("querying display sampling freq failed %#x\n", retval);
+               goto prep_end;
+       } else
+               pr_debug("TMDS freq = %d kHz\n", disp_samp_freq);
+
+       retval = snd_intelhad_prog_n(substream->runtime->rate, &n_param,
+                                                               intelhaddata);
+       if (retval) {
+               pr_err("programming N value failed %#x\n", retval);
+               goto prep_end;
+       }
+       snd_intelhad_prog_cts(substream->runtime->rate,
+                                       disp_samp_freq, n_param, intelhaddata);
+
+       snd_intelhad_prog_dip(substream, intelhaddata, 0);
+
+prep_end:
+       return retval;
+}
+
+/**
+ * snd_intelhad_pcm_pointer- to send the current buffer pointerprocessed by hw
+ *
+ * @substream:  substream for which the function is called
+ *
+ * This function is called by ALSA framework to get the current hw buffer ptr
+ * when a period is elapsed
+ */
+static snd_pcm_uframes_t snd_intelhad_pcm_pointer(
+                                       struct snd_pcm_substream *substream)
+{
+       struct snd_intelhad *intelhaddata;
+       u32 bytes_rendered;
+
+       intelhaddata = snd_pcm_substream_chip(substream);
+
+       if (intelhaddata->flag_underrun) {
+               intelhaddata->flag_underrun = 0;
+               return SNDRV_PCM_POS_XRUN;
+       }
+
+       div_u64_rem(intelhaddata->stream_info.buffer_rendered,
+                       intelhaddata->stream_info.ring_buf_size,
+                       &(bytes_rendered));
+       intelhaddata->stream_info.buffer_ptr = bytes_to_frames(
+                                               substream->runtime,
+                                               bytes_rendered);
+       return intelhaddata->stream_info.buffer_ptr;
+}
+
+int hdmi_audio_mode_change(struct snd_pcm_substream *substream)
+{
+       int retval = 0;
+       u32 disp_samp_freq, n_param;
+       struct snd_intelhad *intelhaddata;
+
+       intelhaddata = snd_pcm_substream_chip(substream);
+
+       /* Disable Audio */
+       intelhaddata->reg_ops.hdmi_audio_read_modify(
+                       AUD_CONFIG, 0, BIT(0));
+
+       /* Update CTS value */
+       retval = intelhaddata->query_ops.hdmi_audio_get_caps(
+                               HAD_GET_SAMPLING_FREQ, &disp_samp_freq);
+       if (retval) {
+               pr_err("querying display sampling freq failed %#x\n", retval);
+               goto out;
+       } else
+               pr_debug("TMDS freq = %d kHz\n", disp_samp_freq);
+
+       retval = snd_intelhad_prog_n(substream->runtime->rate, &n_param,
+                                                               intelhaddata);
+       if (retval) {
+               pr_err("programming N value failed %#x\n", retval);
+               goto out;
+       }
+       snd_intelhad_prog_cts(substream->runtime->rate,
+                                       disp_samp_freq, n_param, intelhaddata);
+
+       /* Enable Audio */
+       intelhaddata->reg_ops.hdmi_audio_read_modify(
+                       AUD_CONFIG, 1, BIT(0));
+
+out:
+       return retval;
+}
+
+void dummy_audio_play(struct work_struct *work)
+{
+       struct snd_intelhad *intelhaddata = had_pvt_data;
+
+       mutex_lock(&intelhaddata->had_lock);
+       if (intelhaddata->drv_status != HAD_DRV_DISCONNECTED) {
+               mutex_unlock(&intelhaddata->had_lock);
+               pr_debug("HDMI device still connected\n");
+               return;
+       }
+
+       if (!intelhaddata->send_data) {
+               mutex_unlock(&intelhaddata->had_lock);
+               pr_debug("HDMI device_flag is reset\n");
+               return;
+       }
+
+       had_event_handler(HAD_EVENT_AUDIO_BUFFER_DONE, intelhaddata);
+       mutex_unlock(&intelhaddata->had_lock);
+       schedule_delayed_work(&intelhaddata->dummy_audio, intelhaddata->timer);
+}
+
+/*PCM operations structure and the calls back for the same */
+struct snd_pcm_ops snd_intelhad_playback_ops = {
+       .open =         snd_intelhad_open,
+       .close =        snd_intelhad_close,
+       .ioctl =        snd_pcm_lib_ioctl,
+       .hw_params =    snd_intelhad_hw_params,
+       .hw_free =      snd_intelhad_hw_free,
+       .prepare =      snd_intelhad_pcm_prepare,
+       .trigger =      snd_intelhad_pcm_trigger,
+       .pointer =      snd_intelhad_pcm_pointer,
+};
+
+/**
+ * snd_intelhad_create - to crete alsa card instance
+ *
+ * @intelhaddata: pointer to internal context
+ * @card: pointer to card
+ *
+ * This function is called when the hdmi cable is plugged in
+ */
+static int __devinit snd_intelhad_create(
+               struct snd_intelhad *intelhaddata,
+               struct snd_card *card)
+{
+       int retval;
+       static struct snd_device_ops ops = {
+       };
+
+       BUG_ON(!intelhaddata);
+       /* ALSA api to register the device */
+       retval = snd_device_new(card, SNDRV_DEV_LOWLEVEL, intelhaddata, &ops);
+       return retval;
+}
+/**
+ * snd_intelhad_pcm_free - to free the memory allocated
+ *
+ * @pcm: pointer to pcm instance
+ * This function is called when the device is removed
+ */
+static void snd_intelhad_pcm_free(struct snd_pcm *pcm)
+{
+       pr_debug("Freeing PCM preallocated pages\n");
+       snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static int had_iec958_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 had_iec958_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_intelhad *intelhaddata = snd_kcontrol_chip(kcontrol);
+       mutex_lock(&intelhaddata->had_lock);
+       ucontrol->value.iec958.status[0] = (intelhaddata->aes_bits >> 0) & 0xff;
+       ucontrol->value.iec958.status[1] = (intelhaddata->aes_bits >> 8) & 0xff;
+       ucontrol->value.iec958.status[2] =
+                                       (intelhaddata->aes_bits >> 16) & 0xff;
+       ucontrol->value.iec958.status[3] =
+                                       (intelhaddata->aes_bits >> 24) & 0xff;
+       mutex_unlock(&intelhaddata->had_lock);
+       return 0;
+}
+static int had_iec958_mask_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       ucontrol->value.iec958.status[0] = 0xff;
+       ucontrol->value.iec958.status[1] = 0xff;
+       ucontrol->value.iec958.status[2] = 0xff;
+       ucontrol->value.iec958.status[3] = 0xff;
+       return 0;
+}
+static int had_iec958_put(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       unsigned int val;
+       struct snd_intelhad *intelhaddata = snd_kcontrol_chip(kcontrol);
+
+       pr_debug("entered had_iec958_put\n");
+       val = (ucontrol->value.iec958.status[0] << 0) |
+               (ucontrol->value.iec958.status[1] << 8) |
+               (ucontrol->value.iec958.status[2] << 16) |
+               (ucontrol->value.iec958.status[3] << 24);
+       mutex_lock(&intelhaddata->had_lock);
+       if (intelhaddata->aes_bits != val) {
+               intelhaddata->aes_bits = val;
+               mutex_unlock(&intelhaddata->had_lock);
+               return 1;
+       }
+       mutex_unlock(&intelhaddata->had_lock);
+       return 1;
+}
+
+static struct snd_kcontrol_new had_control_iec958_mask = {
+       .access =   SNDRV_CTL_ELEM_ACCESS_READ,
+       .iface =    SNDRV_CTL_ELEM_IFACE_PCM,
+       .name =     SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK),
+       .info =     had_iec958_info, /* shared */
+       .get =      had_iec958_mask_get,
+};
+
+static struct snd_kcontrol_new had_control_iec958 = {
+       .iface =    SNDRV_CTL_ELEM_IFACE_PCM,
+       .name =         SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+       .info =         had_iec958_info,
+       .get =          had_iec958_get,
+       .put =          had_iec958_put
+};
+
+static struct snd_intel_had_interface had_interface = {
+       .name =         "hdmi-audio",
+       .suspend =      hdmi_audio_suspend,
+       .resume =       hdmi_audio_resume,
+};
+
+/**
+ * hdmi_audio_probe - to create sound card instance for HDMI audio playabck
+ *
+ *@haddata: pointer to HAD private data
+ *@card_id: card for which probe is called
+ *
+ * This function is called when the hdmi cable is plugged in. This function
+ * creates and registers the sound card with ALSA
+ */
+static int __devinit hdmi_audio_probe(struct platform_device *devptr)
+{
+
+       int retval;
+       struct snd_pcm *pcm;
+       struct snd_card *card;
+       struct had_callback_ops ops_cb;
+       struct snd_intelhad *intelhaddata;
+
+       pr_debug("Enter %s\n", __func__);
+
+       /* allocate memory for saving internal context and working */
+       intelhaddata = kzalloc(sizeof(*intelhaddata), GFP_KERNEL);
+       if (!intelhaddata) {
+               pr_err("mem alloc failed\n");
+               return -ENOMEM;
+       }
+
+       had_pvt_data = intelhaddata;
+       ops_cb.intel_had_event_call_back = had_event_handler;
+
+       /* registering with display driver to get access to display APIs */
+
+       retval = intel_hdmi_audio_query_capabilities(
+                       ops_cb.intel_had_event_call_back,
+                       &(intelhaddata->reg_ops),
+                       &(intelhaddata->query_ops));
+       if (retval) {
+               pr_err("querying display driver APIs failed %#x\n", retval);
+               goto free_haddata;
+       }
+       mutex_lock(&had_mutex);
+       mutex_init(&intelhaddata->had_lock);
+       spin_lock_init(&intelhaddata->had_spinlock);
+       intelhaddata->drv_status = HAD_DRV_DISCONNECTED;
+       /* create a card instance with ALSA framework */
+       retval = snd_card_create(hdmi_card_index, hdmi_card_id,
+                               THIS_MODULE, 0, &card);
+       if (retval)
+               goto unlock_mutex;
+       intelhaddata->card = card;
+       intelhaddata->card_id = hdmi_card_id;
+       intelhaddata->card_index = card->number;
+       intelhaddata->playback_cnt = 0;
+       intelhaddata->flag_underrun = 0;
+       intelhaddata->aes_bits = SNDRV_PCM_DEFAULT_CON_SPDIF;
+       strncpy(card->driver, INTEL_HAD, strlen(INTEL_HAD));
+       strncpy(card->shortname, INTEL_HAD, strlen(INTEL_HAD));
+
+       retval = snd_pcm_new(card, INTEL_HAD, PCM_INDEX, MAX_PB_STREAMS,
+                                               MAX_CAP_STREAMS, &pcm);
+       if (retval)
+               goto err;
+
+       /* setup private data which can be retrieved when required */
+       pcm->private_data = intelhaddata;
+       pcm->private_free = snd_intelhad_pcm_free;
+       pcm->info_flags = 0;
+       strncpy(pcm->name, card->shortname, strlen(card->shortname));
+       /* setup the ops for palyabck */
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+                           &snd_intelhad_playback_ops);
+       /* allocate dma pages for ALSA stream operations */
+       retval = snd_pcm_lib_preallocate_pages_for_all(pcm,
+                       SNDRV_DMA_TYPE_CONTINUOUS,
+                       snd_dma_continuous_data(GFP_KERNEL),
+                       HAD_MIN_BUFFER, HAD_MAX_BUFFER);
+       if (retval)
+               goto err;
+
+       /* internal function call to register device with ALSA */
+       retval = snd_intelhad_create(intelhaddata, card);
+       if (retval)
+               goto err;
+
+       card->private_data = &intelhaddata;
+       retval = snd_card_register(card);
+       if (retval)
+               goto err;
+
+       /* IEC958 controls */
+       retval = snd_ctl_add(card, snd_ctl_new1(&had_control_iec958_mask,
+                                               intelhaddata));
+       if (retval < 0)
+               goto err;
+       retval = snd_ctl_add(card, snd_ctl_new1(&had_control_iec958,
+                                               intelhaddata));
+       if (retval < 0)
+               goto err;
+
+       /* Initialize dummy audio workqueue */
+       INIT_DELAYED_WORK(&intelhaddata->dummy_audio, dummy_audio_play);
+
+       /* Allocate memory for flat data */
+       intelhaddata->flat_data = kzalloc((MAX_SZ_ZERO_BUF), GFP_KERNEL);
+       if (!intelhaddata->flat_data) {
+               retval = -ENOMEM;
+               goto err;
+       }
+
+       mutex_unlock(&had_mutex);
+       retval = display_register(&had_interface, intelhaddata);
+       if (retval) {
+               pr_err("registering with display driver failed %#x\n", retval);
+               snd_card_free(card);
+               goto free_haddata;
+       }
+
+       return retval;
+err:
+       pr_err("Error returned from %s api %#x\n", __func__, retval);
+       snd_card_free(card);
+unlock_mutex:
+       mutex_unlock(&had_mutex);
+free_haddata:
+       kfree(intelhaddata);
+       intelhaddata = NULL;
+       return retval;
+}
+
+/**
+ * hdmi_audio_remove - removes the alsa card
+ *
+ *@haddata: pointer to HAD private data
+ *
+ * This function is called when the hdmi cable is un-plugged. This function
+ * free the sound card.
+ */
+static int __devexit hdmi_audio_remove(struct platform_device *devptr)
+{
+       struct snd_intelhad *intelhaddata = had_pvt_data;
+       int caps;
+
+       pr_debug("Enter %s\n", __func__);
+
+       if (!intelhaddata)
+               return 0;
+
+       mutex_lock(&intelhaddata->had_lock);
+       if (intelhaddata->drv_status != HAD_DRV_DISCONNECTED) {
+               caps = HDMI_AUDIO_UNDERRUN | HDMI_AUDIO_BUFFER_DONE;
+               intelhaddata->query_ops.hdmi_audio_set_caps(
+                               HAD_SET_DISABLE_AUDIO_INT, &caps);
+               intelhaddata->query_ops.hdmi_audio_set_caps(
+                               HAD_SET_DISABLE_AUDIO, NULL);
+               snd_intelhad_stop_silence(intelhaddata);
+       }
+       kfree(intelhaddata->flat_data);
+       snd_card_free(intelhaddata->card);
+       mutex_unlock(&intelhaddata->had_lock);
+       kfree(intelhaddata);
+       return 0;
+}
+
+static struct platform_driver had_driver = {
+       .probe =        hdmi_audio_probe,
+       .remove         = __devexit_p(hdmi_audio_remove),
+       .suspend =      NULL,
+       .resume =       NULL,
+       .driver         = {
+               .name   = HDMI_AUDIO_DRIVER
+       },
+};
+
+/*
+* alsa_card_intelhad_init- driver init function
+* This function is called when driver module is inserted
+*/
+static int __init alsa_card_intelhad_init(void)
+{
+       int retval;
+
+       pr_debug("Enter %s\n", __func__);
+
+       pr_info("******** HAD DRIVER loading.. Ver: %s\n",
+                                       HAD_DRIVER_VERSION);
+
+       retval = platform_driver_register(&had_driver);
+       if (retval < 0) {
+               pr_err("Platform driver register failed\n");
+               return retval;
+       }
+
+       pr_debug("init complete\n");
+       return retval;
+}
+
+/**
+* alsa_card_intelhad_exit- driver exit function
+* This function is called when driver module is removed
+*/
+static void __exit alsa_card_intelhad_exit(void)
+{
+       pr_debug("had_exit called\n");
+       platform_driver_unregister(&had_driver);
+}
+late_initcall(alsa_card_intelhad_init);
+module_exit(alsa_card_intelhad_exit);
diff --git a/sound/drivers/intel_mid_hdmi/intel_mid_hdmi_audio.h b/sound/drivers/intel_mid_hdmi/intel_mid_hdmi_audio.h
new file mode 100644 (file)
index 0000000..d2bef78
--- /dev/null
@@ -0,0 +1,476 @@
+/*
+ *   intel_mid_hdmi_audio.h - Intel HDMI audio driver for MID
+ *
+ *  Copyright (C) 2010 Intel Corp
+ *  Authors:   Sailaja Bandarupalli <sailaja.bandarupalli@intel.com>
+ *             Ramesh Babu K V <ramesh.babu@intel.com>
+ *             Vaibhav Agarwal <vaibhav.agarwal@intel.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; version 2 of the License.
+ *
+ *  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.,
+ *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * ALSA driver for Intel MID HDMI audio controller
+ */
+#ifndef __INTEL_MID_HDMI_AUDIO_H
+#define __INTEL_MID_HDMI_AUDIO_H
+
+#include <linux/types.h>
+#include <sound/initval.h>
+#include <linux/version.h>
+#include <sound/asoundef.h>
+#include <sound/control.h>
+#include <psb_intel_hdmi.h>
+#include <mdfld_hdmi_audio_if.h>
+
+#define HAD_DRIVER_VERSION     "0.01.003"
+#define HAD_MAX_DEVICES                1
+#define HAD_MIN_CHANNEL                2
+#define HAD_MAX_CHANNEL                8
+#define HAD_NUM_OF_RING_BUFS   4
+#define HAD_MIN_RATE           32000
+#define HAD_MAX_RATE           192000
+#define HAD_MAX_BUFFER         (800*1024)
+#define HAD_MIN_BUFFER         (800*1024)
+#define HAD_MAX_PERIODS                4
+#define HAD_MIN_PERIODS                4
+#define HAD_MAX_PERIOD_BYTES   HAD_MAX_BUFFER
+#define HAD_MIN_PERIOD_BYTES   256
+#define HAD_FIFO_SIZE          0 /* fifo not being used */
+
+#define AUD_SAMPLE_RATE_32     HAD_MIN_RATE
+#define AUD_SAMPLE_RATE_44_1   44100
+#define AUD_SAMPLE_RATE_48     48000
+#define AUD_SAMPLE_RATE_88_2   88200
+#define AUD_SAMPLE_RATE_96     96000
+#define AUD_SAMPLE_RATE_176_4  176400
+#define AUD_SAMPLE_RATE_192    HAD_MAX_RATE
+
+#define DRIVER_NAME            "intelmid_hdmi_audio"
+#define DIS_SAMPLE_RATE_25_2   25200
+#define DIS_SAMPLE_RATE_27     27000
+#define DIS_SAMPLE_RATE_54     54000
+#define DIS_SAMPLE_RATE_74_25  74250
+#define DIS_SAMPLE_RATE_148_5  148500
+#define HAD_REG_WIDTH          0x08
+#define HAD_MAX_HW_BUFS                0x04
+#define HAD_MAX_DIP_WORDS              16
+#define INTEL_HAD              "IntelHDMI"
+
+/* _AUD_CONFIG register MASK */
+#define AUD_CONFIG_MASK_UNDERRUN       0xC0000000
+#define AUD_CONFIG_MASK_SRDBG          0x00000002
+#define AUD_CONFIG_MASK_FUNCRST                0x00000001
+
+#define MAX_CNT                        0xFF
+#define MAX_SZ_ZERO_BUF                (1024*8)
+
+/**
+ * enum had_status - Audio stream states
+ *
+ * @STREAM_INIT: Stream initialized
+ * @STREAM_RUNNING: Stream running
+ * @STREAM_PAUSED: Stream paused
+ * @STREAM_DROPPED: Stream dropped
+ * */
+enum had_stream_status {
+       STREAM_INIT = 0,
+       STREAM_RUNNING = 1,
+       STREAM_PAUSED = 2,
+       STREAM_DROPPED = 3
+};
+
+enum had_drv_status {
+       HAD_DRV_CONNECTED,
+       HAD_DRV_RUNNING,
+       HAD_DRV_DISCONNECTED,
+       HAD_DRV_SUSPENDED,
+       HAD_DRV_ERR,
+};
+
+/* enum intel_had_aud_buf_type - HDMI controller ring buffer types */
+enum intel_had_aud_buf_type {
+       HAD_BUF_TYPE_A = 0,
+       HAD_BUF_TYPE_B = 1,
+       HAD_BUF_TYPE_C = 2,
+       HAD_BUF_TYPE_D = 3,
+};
+
+enum num_aud_ch {
+       CH_STEREO = 0,
+       CH_THREE_FOUR = 1,
+       CH_FIVE_SIX = 2,
+       CH_SEVEN_EIGHT = 3
+};
+
+/*HDMI controller register offsets*/
+enum hdmi_ctrl_reg {
+       AUD_CONFIG              = 0x69000,
+       AUD_CH_STATUS_0         = 0x69008,
+       AUD_CH_STATUS_1         = 0x6900C,
+       AUD_HDMI_CTS            = 0x69010,
+       AUD_N_ENABLE            = 0x69014,
+       AUD_SAMPLE_RATE         = 0x69018,
+       AUD_BUF_CONFIG          = 0x69020,
+       AUD_BUF_CH_SWAP         = 0x69024,
+       AUD_BUF_A_ADDR          = 0x69040,
+       AUD_BUF_A_LENGTH        = 0x69044,
+       AUD_BUF_B_ADDR          = 0x69048,
+       AUD_BUF_B_LENGTH        = 0x6904c,
+       AUD_BUF_C_ADDR          = 0x69050,
+       AUD_BUF_C_LENGTH        = 0x69054,
+       AUD_BUF_D_ADDR          = 0x69058,
+       AUD_BUF_D_LENGTH        = 0x6905c,
+       AUD_CNTL_ST             = 0x69060,
+       AUD_HDMI_STATUS         = 0x69068,
+       AUD_HDMIW_INFOFR        = 0x69114,
+};
+
+/**
+ * union aud_cfg - Audio configuration offset - 69000
+ *
+ * @cfg_regx: individual register bits
+ * @cfg_regval: full register value
+ *
+ * */
+union aud_cfg {
+       struct {
+               u32 aud_en:1;
+               u32 layout:1;
+               u32 fmt:2;
+               u32 num_ch:2;
+               u32 rsvd0:1;
+               u32 set:1;
+               u32 flat:1;
+               u32 val_bit:1;
+               u32 user_bit:1;
+               u32 underrun:1;
+               u32 rsvd1:20;
+       } cfg_regx;
+       u32 cfg_regval;
+};
+
+/**
+ * union aud_ch_status_0 - Audio Channel Status 0 Attributes offset - 0x69008
+ *
+ * @status_0_regx:individual register bits
+ * @status_0_regval:full register value
+ *
+ * */
+union aud_ch_status_0 {
+       struct {
+               u32 ch_status:1;
+               u32 lpcm_id:1;
+               u32 cp_info:1;
+               u32 format:3;
+               u32 mode:2;
+               u32 ctg_code:8;
+               u32 src_num:4;
+               u32 ch_num:4;
+               u32 samp_freq:4;
+               u32 clk_acc:2;
+               u32 rsvd:2;
+       } status_0_regx;
+       u32 status_0_regval;
+};
+
+/**
+ * union aud_ch_status_1 - Audio Channel Status 1 Attributes offset - 0x6900c
+ *
+ * @status_1_regx: individual register bits
+ * @status_1_regval: full register value
+ *
+ **/
+union aud_ch_status_1 {
+       struct {
+               u32 max_wrd_len:1;
+               u32 wrd_len:3;
+               u32 rsvd:28;
+               } status_1_regx;
+       u32 status_1_regval;
+};
+
+/**
+ * union aud_hdmi_cts - CTS register offset -0x69010
+ *
+ * @cts_regx: individual register bits
+ * @cts_regval: full register value
+ *
+ * */
+union aud_hdmi_cts {
+       struct {
+               u32 cts_val:20;
+               u32 en_cts_prog:1;
+               u32 rsvd:11;
+       } cts_regx;
+       u32 cts_regval;
+};
+
+/**
+ * union aud_hdmi_n_enable - N register offset -0x69014
+ *
+ * @n_regx: individual register bits
+ * @n_regval: full register value
+ *
+*/
+union aud_hdmi_n_enable {
+       struct {
+               u32 n_val:20;
+               u32 en_n_prog:1;
+               u32 rsvd:11;
+       } n_regx;
+       u32 n_regval;
+};
+
+/**
+ * union aud_buf_config -  Audio Buffer configurations offset -0x69020
+ *
+ * @buf_cfg_regx: individual register bits
+ * @buf_cfgval: full register value
+ *
+*/
+union aud_buf_config {
+       struct {
+               u32 fifo_width:8;
+               u32 rsvd0:8;
+               u32 aud_delay:8;
+               u32 rsvd1:8;
+       } buf_cfg_regx;
+       u32 buf_cfgval;
+};
+
+/**
+ * union aud_buf_ch_swap - Audio Sample Swapping offset - 0x69024
+ *
+ * @buf_ch_swap_regx: individual register bits
+ * @buf_ch_swap_val: full register value
+ *
+ * */
+union aud_buf_ch_swap {
+       struct {
+               u32 first_0:3;
+               u32 second_0:3;
+               u32 first_1:3;
+               u32 second_1:3;
+               u32 first_2:3;
+               u32 second_2:3;
+               u32 first_3:3;
+               u32 second_3:3;
+               u32 rsvd:8;
+       } buf_ch_swap_regx;
+       u32 buf_ch_swap_val;
+};
+
+/**
+ * union aud_buf_addr - Address for Audio Buffer
+ *
+ * @buf_addr_regx: individual register bits
+ * @buf_addr_val: full register value
+ *
+ * */
+union aud_buf_addr {
+       struct {
+               u32 valid:1;
+               u32 intr_en:1;
+               u32 rsvd:4;
+               u32 addr:26;
+       } buf_addr_regx;
+       u32 buf_addr_val;
+};
+
+/**
+ * union aud_buf_len - Length of Audio Buffer
+ *
+ * @buf_len_regx: individual register bits
+ * @buf_len_val: full register value
+ *
+ * */
+union aud_buf_len {
+       struct {
+               u32 buf_len:20;
+               u32 rsvd:12;
+       } buf_len_regx;
+       u32 buf_len_val;
+};
+
+/**
+ * union aud_ctrl_st - Audio Control State Register offset 0x69060
+ *
+ * @ctrl_regx: individual register bits
+ * @ctrl_val: full register value
+ *
+ * */
+union aud_ctrl_st {
+       struct {
+               u32 ram_addr:4;
+               u32 eld_ack:1;
+               u32 eld_addr:4;
+               u32 eld_buf_size:5;
+               u32 eld_valid:1;
+               u32 cp_ready:1;
+               u32 dip_freq:2;
+               u32 dip_idx:3;
+               u32 dip_en_sta:4;
+               u32 rsvd:7;
+       } ctrl_regx;
+       u32 ctrl_val;
+};
+
+/**
+ * union aud_info_frame1 - Audio HDMI Widget Data Island Packet offset 0x69114
+ *
+ * @fr1_regx: individual register bits
+ * @fr1_val: full register value
+ *
+ * */
+union aud_info_frame1 {
+       struct {
+               u32 pkt_type:8;
+               u32 ver_num:8;
+               u32 len:5;
+               u32 rsvd:11;
+       } fr1_regx;
+       u32 fr1_val;
+};
+
+/**
+ * union aud_info_frame2 - DIP frame 2
+ *
+ * @fr2_regx: individual register bits
+ * @fr2_val: full register value
+ *
+ */
+union aud_info_frame2 {
+       struct {
+               u32 chksum:8;
+               u32 chnl_cnt:3;
+               u32 rsvd0:1;
+               u32 coding_type:4;
+               u32 smpl_size:2;
+               u32 smpl_freq:3;
+               u32 rsvd1:3;
+               u32 format:8;
+       } fr2_regx;
+       u32 fr2_val;
+};
+
+/**
+ * union aud_info_frame3 - DIP frame 3
+ *
+ * @fr3_regx: individual register bits
+ * @fr3_val: full register value
+ *
+ */
+union aud_info_frame3 {
+       struct {
+               u32 chnl_alloc:8;
+               u32 rsvd0:3;
+               u32 lsv:4;
+               u32 dm_inh:1;
+               u32 rsvd1:16;
+       } fr3_regx;
+       u32 fr3_val;
+};
+
+
+struct pcm_stream_info {
+       int             str_id;
+       void            *had_substream;
+       void            (*period_elapsed) (void *had_substream);
+       u32             buffer_ptr;
+       u64             buffer_rendered;
+       u32             ring_buf_size;
+       int             sfreq;
+};
+
+struct ring_buf_info {
+       uint32_t        buf_addr;
+       uint32_t        buf_size;
+       uint8_t         is_valid;
+};
+
+struct had_stream_pvt {
+       enum had_stream_status          stream_status;
+       int                             stream_ops;
+       struct snd_pcm_substream        *substream;
+       ssize_t                         dbg_cum_bytes;
+};
+
+struct had_callback_ops {
+       had_event_call_back intel_had_event_call_back;
+};
+
+/**
+ * struct snd_intelhad - intelhad driver structure
+ *
+ * @card: ptr to hold card details
+ * @card_index: sound card index
+ * @card_id: detected sound card id
+ * @reg_ops: register operations to program registers
+ * @query_ops: caps call backs for get/set operations
+ * @drv_status: driver status
+ * @playback_cnt: active playback streams
+ * @buf_info: ring buffer info
+ * @stream_info: stream information
+ * @eeld: holds EELD info
+ * @curr_buf: pointer to hold current active ring buf
+ * @valid_buf_cnt: ring buffer count for stream
+ * @had_lock: driver mutex lock
+ * @aes_bits: IEC958 status bits
+ */
+struct snd_intelhad {
+       struct snd_card *card;
+       int             card_index;
+       char            *card_id;
+       struct hdmi_audio_registers_ops reg_ops;
+       struct hdmi_audio_query_set_ops query_ops;
+       enum had_drv_status     drv_status;
+       int             playback_cnt;
+       struct          ring_buf_info buf_info[HAD_NUM_OF_RING_BUFS];
+       struct          pcm_stream_info stream_info;
+       hdmi_eeld_t     eeld;
+       enum            intel_had_aud_buf_type curr_buf;
+       int             valid_buf_cnt;
+       struct          mutex had_lock;
+       unsigned int    aes_bits;
+       int flag_underrun;
+       /* Related to sending dummy data */
+       struct delayed_work dummy_audio;
+       int send_data;
+       unsigned long timer;
+       /*  Related to sending silence data */
+       char *flat_data;
+       int pcm_active;
+       int start_trigger;
+       int stop_trigger;
+       spinlock_t had_spinlock;
+       enum            intel_had_aud_buf_type buff_done;
+};
+
+int had_event_handler(enum had_event_type event_type, void *data);
+
+int hdmi_audio_suspend(void *drv_data, pm_event_t event);
+int hdmi_audio_resume(void *drv_data);
+int hdmi_audio_mode_change(struct snd_pcm_substream *substream);
+extern struct snd_pcm_ops snd_intelhad_playback_ops;
+int snd_intelhad_start_silence(struct snd_intelhad *intelhaddata);
+int snd_intelhad_stop_silence(struct snd_intelhad *intelhaddata);
+int snd_intelhad_configure_silence(struct snd_intelhad *intelhaddata);
+int snd_intelhad_init_audio_ctrl(struct snd_pcm_substream *substream,
+                                       struct snd_intelhad *intelhaddata,
+                                       int flag_silence);
+int snd_intelhad_prog_buffer(struct snd_intelhad *intelhaddata,
+                                       int start, int end);
+inline void snd_intelhad_read_len(struct snd_intelhad *intelhaddata);
+#endif
diff --git a/sound/drivers/intel_mid_hdmi/intel_mid_hdmi_audio_if.c b/sound/drivers/intel_mid_hdmi/intel_mid_hdmi_audio_if.c
new file mode 100644 (file)
index 0000000..d8e475f
--- /dev/null
@@ -0,0 +1,492 @@
+/*
+ *   intel_mid_hdmi_audio_if.c - Intel HDMI audio driver for MID
+ *
+ *  Copyright (C) 2010 Intel Corp
+ *  Authors:   Sailaja Bandarupalli <sailaja.bandarupalli@intel.com>
+ *             Ramesh Babu K V <ramesh.babu@intel.com>
+ *             Vaibhav Agarwal <vaibhav.agarwal@intel.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; version 2 of the License.
+ *
+ *  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.,
+ *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * ALSA driver for Intel MID HDMI audio controller.  This file contains
+ * interface functions exposed to HDMI Display driver and code to register
+ * with ALSA framework..
+ */
+
+#define pr_fmt(fmt)            "had: " fmt
+
+#include <linux/io.h>
+#include <linux/jiffies.h>
+#include <sound/pcm.h>
+#include <sound/core.h>
+#include "intel_mid_hdmi_audio.h"
+
+static int flag_en_allbufs;
+
+/**
+ * hdmi_audio_suspend - power management suspend function
+ *
+ *@haddata: pointer to HAD private data
+ *@event: pm event for which this method is invoked
+ *
+ * This function is called by client driver to suspend the
+ * hdmi audio.
+ */
+int hdmi_audio_suspend(void *haddata, pm_event_t event)
+{
+       int caps, retval = 0;
+       struct snd_intelhad *intelhaddata = (struct snd_intelhad *)haddata;
+       pr_debug("Enter:%s", __func__);
+
+       if (intelhaddata->playback_cnt > 0) {
+               pr_err("audio stream is active\n");
+               return -EBUSY;
+       }
+
+       mutex_lock(&intelhaddata->had_lock);
+       if (intelhaddata->drv_status == HAD_DRV_DISCONNECTED) {
+               pr_debug("had not connected\n");
+               mutex_unlock(&intelhaddata->had_lock);
+               return retval;
+       }
+
+       intelhaddata->drv_status = HAD_DRV_SUSPENDED;
+       caps = HDMI_AUDIO_UNDERRUN | HDMI_AUDIO_BUFFER_DONE;
+       intelhaddata->query_ops.hdmi_audio_set_caps(
+                       HAD_SET_DISABLE_AUDIO_INT, &caps);
+       intelhaddata->query_ops.hdmi_audio_set_caps(
+                       HAD_SET_DISABLE_AUDIO, NULL);
+       retval = snd_intelhad_stop_silence(intelhaddata);
+       mutex_unlock(&intelhaddata->had_lock);
+       return retval;
+}
+
+/**
+ * hdmi_audio_resume - power management resume function
+ *
+ *@haddata: pointer to HAD private data
+ *
+ * This function is called by client driver to resume the
+ * hdmi audio.
+ */
+int hdmi_audio_resume(void *haddata)
+{
+       int caps, retval = 0;
+       struct snd_intelhad *intelhaddata = (struct snd_intelhad *)haddata;
+       pr_debug("Enter:%s", __func__);
+
+       mutex_lock(&intelhaddata->had_lock);
+       if (HAD_DRV_DISCONNECTED == intelhaddata->drv_status) {
+               pr_debug("had not connected\n");
+               mutex_unlock(&intelhaddata->had_lock);
+               return 0;
+       }
+
+       if (HAD_DRV_SUSPENDED != intelhaddata->drv_status) {
+               pr_err("had is not in suspended state\n");
+               mutex_unlock(&intelhaddata->had_lock);
+               return 0;
+       }
+
+       if (SNDRV_DEFAULT_IDX1 ==  intelhaddata->card_index)
+               intelhaddata->drv_status = HAD_DRV_DISCONNECTED;
+       else {
+               intelhaddata->drv_status = HAD_DRV_CONNECTED;
+               caps = HDMI_AUDIO_UNDERRUN | HDMI_AUDIO_BUFFER_DONE;
+               retval = intelhaddata->query_ops.hdmi_audio_set_caps(
+                               HAD_SET_ENABLE_AUDIO_INT, &caps);
+               retval = intelhaddata->query_ops.hdmi_audio_set_caps(
+                               HAD_SET_ENABLE_AUDIO, NULL);
+               retval = snd_intelhad_configure_silence(intelhaddata);
+               retval = snd_intelhad_start_silence(intelhaddata);
+       }
+       mutex_unlock(&intelhaddata->had_lock);
+       return retval;
+}
+
+int had_process_buffer_done(struct snd_intelhad *intelhaddata)
+{
+       int i = 0, retval = 0;
+       enum intel_had_aud_buf_type buf_id;
+       struct pcm_stream_info *stream;
+       u32 buf_size;
+       struct snd_pcm_substream *substream;
+       struct hdmi_audio_registers_ops reg_ops;
+       struct hdmi_audio_query_set_ops query_ops;
+       u32 buf_addr;
+       int pcm_active;
+
+       spin_lock(&intelhaddata->had_spinlock);
+       buf_id = intelhaddata->curr_buf;
+       intelhaddata->buff_done = buf_id;
+       pcm_active = intelhaddata->pcm_active;
+       spin_unlock(&intelhaddata->had_spinlock);
+
+       substream = intelhaddata->stream_info.had_substream;
+       reg_ops = intelhaddata->reg_ops;
+       query_ops = intelhaddata->query_ops;
+
+       pr_debug("Enter:%s buf_id=%d", __func__, buf_id);
+       stream = &intelhaddata->stream_info;
+       buf_size =  intelhaddata->buf_info[buf_id].buf_size;
+
+       /* Program actual data buffer, in case _START trigger recieved */
+       if (intelhaddata->start_trigger) {
+               spin_lock(&intelhaddata->had_spinlock);
+               intelhaddata->start_trigger = 0;
+               intelhaddata->curr_buf = HAD_BUF_TYPE_A;
+               spin_unlock(&intelhaddata->had_spinlock);
+               retval = snd_intelhad_prog_buffer(intelhaddata,
+                               HAD_BUF_TYPE_C, HAD_BUF_TYPE_D);
+               return retval;
+       }
+
+       /* Program Silence buffer, in case _STOP trigger recieved */
+       if (intelhaddata->stop_trigger) {
+               pr_debug("processing stop trigger in hanlder:bufid=%d\n",
+                               buf_id);
+               spin_lock(&intelhaddata->had_spinlock);
+               intelhaddata->stop_trigger = 0;
+               /* If buf_id < HAD_BUF_TYPE_C, ignore */
+               if (buf_id < HAD_BUF_TYPE_C) {
+                       intelhaddata->curr_buf = HAD_BUF_TYPE_C;
+                       spin_unlock(&intelhaddata->had_spinlock);
+                       return retval;
+               }
+               spin_unlock(&intelhaddata->had_spinlock);
+               i = buf_id;
+               buf_addr = virt_to_phys(intelhaddata->flat_data);
+               /* Program the buf registers with addr and len */
+               intelhaddata->reg_ops.hdmi_audio_write_register(
+                               AUD_BUF_A_ADDR + (i * HAD_REG_WIDTH),
+                               buf_addr | BIT(0) | BIT(1));
+               intelhaddata->reg_ops.hdmi_audio_write_register(
+                               AUD_BUF_A_LENGTH + (i * HAD_REG_WIDTH),
+                               (MAX_SZ_ZERO_BUF));
+
+               intelhaddata->buf_info[i].buf_addr = buf_addr;
+               intelhaddata->buf_info[i].buf_size = MAX_SZ_ZERO_BUF;
+               intelhaddata->buf_info[i].is_valid = true;
+               if (buf_id == HAD_BUF_TYPE_C)
+                       intelhaddata->curr_buf = HAD_BUF_TYPE_D;
+               else
+                       intelhaddata->curr_buf = HAD_BUF_TYPE_C;
+               pr_debug("buf[%d] addr=%#x  and size=%d\n", i,
+                               intelhaddata->buf_info[i].buf_addr,
+                               intelhaddata->buf_info[i].buf_size);
+               return retval;
+       }
+
+       /* In case of actual data, report buffer_done to above ALSA layer */
+       if (pcm_active) {
+               intelhaddata->stream_info.buffer_rendered += buf_size;
+               stream->period_elapsed(stream->had_substream);
+       }
+
+       intelhaddata->buf_info[buf_id].is_valid = true;
+       if (intelhaddata->valid_buf_cnt-1 == buf_id) {
+               if (pcm_active)
+                       intelhaddata->curr_buf = HAD_BUF_TYPE_A;
+               else    /* Use only silence buffers C & D */
+                       intelhaddata->curr_buf = HAD_BUF_TYPE_C;
+       } else
+               intelhaddata->curr_buf++;
+
+       if (intelhaddata->send_data)
+               return retval;
+
+       if (flag_en_allbufs &&
+                       intelhaddata->curr_buf == HAD_BUF_TYPE_A) {
+               pr_debug("special case:enable all bufs\n");
+               for (i = 0; i < intelhaddata->valid_buf_cnt; i++) {
+                       pr_debug("Enabling buf[%d]\n", i);
+                       reg_ops.hdmi_audio_write_register(
+                                       AUD_BUF_A_LENGTH +
+                                       (i * HAD_REG_WIDTH), buf_size);
+                       reg_ops.hdmi_audio_write_register(
+                                       AUD_BUF_A_ADDR+(i * HAD_REG_WIDTH),
+                                       intelhaddata->buf_info[i].buf_addr |
+                                       BIT(0) | BIT(1));
+               }
+               flag_en_allbufs = 0;
+       } else {
+               /*Reprogram the registers with addr and length*/
+               reg_ops.hdmi_audio_write_register(
+                               AUD_BUF_A_LENGTH +
+                               (buf_id * HAD_REG_WIDTH), buf_size);
+               reg_ops.hdmi_audio_write_register(
+                               AUD_BUF_A_ADDR+(buf_id * HAD_REG_WIDTH),
+                               intelhaddata->buf_info[buf_id].buf_addr|
+                               BIT(0) | BIT(1));
+       }
+
+       return retval;
+}
+
+int had_process_buffer_underrun(struct snd_intelhad *intelhaddata)
+{
+       int caps, i = 0, retval = 0;
+       enum intel_had_aud_buf_type buf_id;
+       struct pcm_stream_info *stream;
+       struct hdmi_audio_registers_ops reg_ops;
+       struct hdmi_audio_query_set_ops query_ops;
+       u32 hdmi_status;
+       int pcm_active;
+
+       spin_lock(&intelhaddata->had_spinlock);
+       buf_id = intelhaddata->curr_buf;
+       intelhaddata->buff_done = buf_id;
+       pcm_active = intelhaddata->pcm_active;
+       spin_unlock(&intelhaddata->had_spinlock);
+       reg_ops = intelhaddata->reg_ops;
+       query_ops = intelhaddata->query_ops;
+
+
+       pr_debug("Enter:%s buf_id=%d", __func__, buf_id);
+       stream = &intelhaddata->stream_info;
+       if (intelhaddata->pcm_active) {
+               /* Report UNDERRUN error to above layers */
+               intelhaddata->flag_underrun = 1;
+               stream->period_elapsed(stream->had_substream);
+       } else
+               pr_debug("_UNDERRUN occured during _STOP\n");
+
+       /* Handle Underrun interrupt within Audio Unit */
+       reg_ops.hdmi_audio_write_register(
+                       AUD_CONFIG, 0);
+       /* Reset buffer pointers */
+       reg_ops.hdmi_audio_write_register(AUD_HDMI_STATUS, 1);
+       reg_ops.hdmi_audio_write_register(AUD_HDMI_STATUS, 0);
+       if (pcm_active)
+               intelhaddata->curr_buf = HAD_BUF_TYPE_A;
+       else    /* Use only silence buffers C & D */
+               intelhaddata->curr_buf = HAD_BUF_TYPE_C;
+
+       /**
+        * The interrupt status 'sticky' bits might not be cleared by
+        * setting '1' to that bit once...
+        */
+       do { /* clear bit30, 31 AUD_HDMI_STATUS */
+               reg_ops.hdmi_audio_read_register(
+                               AUD_HDMI_STATUS, &hdmi_status);
+               pr_debug("HDMI status =0x%x\n", hdmi_status);
+               if (hdmi_status & AUD_CONFIG_MASK_UNDERRUN) {
+                       i++;
+                       hdmi_status &= (AUD_CONFIG_MASK_SRDBG |
+                                       AUD_CONFIG_MASK_FUNCRST);
+                       hdmi_status |= ~AUD_CONFIG_MASK_UNDERRUN;
+                       reg_ops.hdmi_audio_write_register(
+                                       AUD_HDMI_STATUS, hdmi_status);
+               } else
+                       break;
+       } while (i < MAX_CNT);
+       if (i >= MAX_CNT)
+               pr_err("Unable to clear UNDERRUN bits\n");
+
+       if (!intelhaddata->pcm_active) {
+               /* In case _STOP=1, send silence data */
+               caps = HDMI_AUDIO_UNDERRUN | HDMI_AUDIO_BUFFER_DONE;
+               retval = query_ops.hdmi_audio_set_caps(
+                               HAD_SET_ENABLE_AUDIO_INT, &caps);
+               retval = query_ops.hdmi_audio_set_caps(
+                               HAD_SET_ENABLE_AUDIO, NULL);
+
+               retval = snd_intelhad_configure_silence(intelhaddata);
+               retval = snd_intelhad_start_silence(intelhaddata);
+       }
+
+       return retval;
+}
+
+int had_process_hot_plug(struct snd_intelhad *intelhaddata)
+{
+       int caps, i = 0, retval = 0;
+       enum intel_had_aud_buf_type buf_id;
+       u32 buf_size;
+       struct snd_pcm_substream *substream;
+       struct hdmi_audio_registers_ops reg_ops;
+       struct hdmi_audio_query_set_ops query_ops;
+
+       spin_lock(&intelhaddata->had_spinlock);
+       buf_id = intelhaddata->curr_buf;
+       intelhaddata->buff_done = buf_id;
+       spin_unlock(&intelhaddata->had_spinlock);
+       substream = intelhaddata->stream_info.had_substream;
+       reg_ops = intelhaddata->reg_ops;
+       query_ops = intelhaddata->query_ops;
+
+
+       pr_debug("Enter:%s", __func__);
+       mutex_lock(&intelhaddata->had_lock);
+       intelhaddata->drv_status = HAD_DRV_CONNECTED;
+       buf_size =  intelhaddata->buf_info[buf_id].buf_size;
+       buf_id = intelhaddata->curr_buf;
+       intelhaddata->send_data = 0;
+       caps = HDMI_AUDIO_UNDERRUN | HDMI_AUDIO_BUFFER_DONE;
+       retval = query_ops.hdmi_audio_set_caps(
+                       HAD_SET_ENABLE_AUDIO_INT, &caps);
+       retval = query_ops.hdmi_audio_set_caps(
+                       HAD_SET_ENABLE_AUDIO, NULL);
+
+       if (!intelhaddata->stream_info.str_id) {
+               retval = snd_intelhad_configure_silence(intelhaddata);
+               retval = snd_intelhad_start_silence(intelhaddata);
+               mutex_unlock(&intelhaddata->had_lock);
+               pr_err("nothing to do in hot plug\n");
+               return retval;
+       }
+
+       mutex_unlock(&intelhaddata->had_lock);
+       cancel_delayed_work(&intelhaddata->dummy_audio);
+
+       /* Reset HW within Audio unit */
+       reg_ops.hdmi_audio_write_register(
+                       AUD_CONFIG, 0);
+       reg_ops.hdmi_audio_write_register(
+                       AUD_HDMI_STATUS, 1);
+       reg_ops.hdmi_audio_write_register(
+                       AUD_HDMI_STATUS, 0);
+
+       /*Reprogram the registers with addr and length*/
+       for (i = buf_id; i < intelhaddata->valid_buf_cnt; i++) {
+               pr_debug("Enabling buf[%d]\n", i);
+               reg_ops.hdmi_audio_write_register(
+                               AUD_BUF_A_LENGTH +
+                               (buf_id * HAD_REG_WIDTH), buf_size);
+               reg_ops.hdmi_audio_write_register(
+                               AUD_BUF_A_ADDR+(buf_id * HAD_REG_WIDTH),
+                               intelhaddata->buf_info[buf_id].buf_addr|
+                               BIT(0) | BIT(1));
+       }
+       if (substream) { /* continue transfer */
+               snd_intelhad_init_audio_ctrl(substream,
+                               intelhaddata, 0);
+               hdmi_audio_mode_change(substream);
+       }
+       flag_en_allbufs = 1;
+
+
+       return retval;
+}
+
+int had_process_hot_unplug(struct snd_intelhad *intelhaddata)
+{
+       int caps, retval = 0;
+       enum intel_had_aud_buf_type buf_id;
+       u32 buf_size;
+       struct snd_pcm_substream *substream;
+       struct snd_pcm_runtime *runtime;
+       struct hdmi_audio_query_set_ops query_ops;
+       u32 msecs, rate, channels;
+
+       spin_lock(&intelhaddata->had_spinlock);
+       buf_id = intelhaddata->curr_buf;
+       intelhaddata->buff_done = buf_id;
+       spin_unlock(&intelhaddata->had_spinlock);
+
+
+       substream = intelhaddata->stream_info.had_substream;
+       query_ops = intelhaddata->query_ops;
+       pr_debug("Enter:%s", __func__);
+       mutex_lock(&intelhaddata->had_lock);
+       if (intelhaddata->stream_info.str_id) {
+               buf_size =  intelhaddata->buf_info[buf_id].buf_size;
+               /* In case substream is active, start reporting
+                * dummy buffer_done interrupts to above ALSA layer.
+                */
+               if (substream) { /* substream is active */
+                       runtime = substream->runtime;
+                       rate = runtime->rate;
+                       channels = runtime->channels;
+                       msecs = (buf_size*1000)/(rate*channels*4);
+                       intelhaddata->timer = msecs_to_jiffies(msecs);
+                       intelhaddata->send_data = 1;
+                       schedule_delayed_work(
+                                       &intelhaddata->dummy_audio,
+                                       intelhaddata->timer);
+               }
+       } else {
+               caps = HDMI_AUDIO_UNDERRUN | HDMI_AUDIO_BUFFER_DONE;
+               retval = query_ops.hdmi_audio_set_caps(
+                               HAD_SET_DISABLE_AUDIO_INT, &caps);
+               retval = query_ops.hdmi_audio_set_caps(
+                               HAD_SET_DISABLE_AUDIO, NULL);
+               retval = snd_intelhad_stop_silence(intelhaddata);
+       }
+       intelhaddata->drv_status = HAD_DRV_DISCONNECTED;
+       mutex_unlock(&intelhaddata->had_lock);
+
+       return retval;
+}
+
+/**
+ * had_event_handler - Call back function to handle events
+ *
+ * @event_type: Event type to handle
+ * @data: data related to the event_type
+ *
+ * This function is invoked to handle HDMI events from client driver.
+ */
+int had_event_handler(enum had_event_type event_type, void *data)
+{
+       int retval = 0;
+       struct snd_intelhad *intelhaddata = data;
+       enum intel_had_aud_buf_type buf_id;
+       struct snd_pcm_substream *substream;
+       struct hdmi_audio_registers_ops reg_ops;
+       struct hdmi_audio_query_set_ops query_ops;
+
+       spin_lock(&intelhaddata->had_spinlock);
+       buf_id = intelhaddata->curr_buf;
+       intelhaddata->buff_done = buf_id;
+       spin_unlock(&intelhaddata->had_spinlock);
+
+       substream = intelhaddata->stream_info.had_substream;
+       reg_ops = intelhaddata->reg_ops;
+       query_ops = intelhaddata->query_ops;
+       switch (event_type) {
+       case HAD_EVENT_AUDIO_BUFFER_DONE:
+               retval = had_process_buffer_done(intelhaddata);
+       break;
+
+       case HAD_EVENT_AUDIO_BUFFER_UNDERRUN:
+               retval = had_process_buffer_underrun(intelhaddata);
+       break;
+
+       case HAD_EVENT_HOT_PLUG:
+               retval = had_process_hot_plug(intelhaddata);
+       break;
+
+       case HAD_EVENT_HOT_UNPLUG:
+               retval = had_process_hot_unplug(intelhaddata);
+       break;
+
+       case HAD_EVENT_MODE_CHANGING:
+               pr_debug(" called _event_handler with _MODE_CHANGE event\n");
+               mutex_lock(&intelhaddata->had_lock);
+               if (intelhaddata->stream_info.str_id && substream)
+                       retval = hdmi_audio_mode_change(substream);
+               mutex_unlock(&intelhaddata->had_lock);
+       break;
+
+       default:
+               pr_debug("error un-handled event !!\n");
+               retval = -EINVAL;
+       break;
+
+       }
+       return retval;
+}