HDMI-Audio: Ported to R3
authorVaibhav Agarwal <vaibhav.agarwal@intel.com>
Wed, 25 Jan 2012 20:44:42 +0000 (02:14 +0530)
committerbuildbot <buildbot@intel.com>
Fri, 17 Feb 2012 16:47:36 +0000 (08:47 -0800)
BZ: 18092

Some HAD patches were not ported from R2 to R3.

This patch includes Patch#26712, 30304, 30338, 31988,
3216 and 31452(only HAD related changes) alongwith some
code cleaning changes.

Change-Id: Ifedf346ca85f5e6a7c0c760341e5dec0ea762a00
Signed-off-by: Vaibhav Agarwal <vaibhav.agarwal@intel.com>
Reviewed-on: http://android.intel.com:8080/33720
Reviewed-by: Babu, Ramesh <ramesh.babu@intel.com>
Reviewed-by: Koul, Vinod <vinod.koul@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>
sound/drivers/intel_mid_hdmi/intel_mid_hdmi_audio.c
sound/drivers/intel_mid_hdmi/intel_mid_hdmi_audio.h
sound/drivers/intel_mid_hdmi/intel_mid_hdmi_audio_if.c

index be6fd03..1ba9682 100644 (file)
@@ -37,6 +37,8 @@
 #include <sound/initval.h>
 #include "intel_mid_hdmi_audio.h"
 
+#include <psb_powermgmt.h>
+
 #define PCM_INDEX              0
 #define MAX_PB_STREAMS         1
 #define MAX_CAP_STREAMS                0
@@ -46,7 +48,7 @@ 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;
+static struct snd_intelhad *had_data;
 
 module_param(hdmi_card_index, int, 0444);
 MODULE_PARM_DESC(hdmi_card_index,
@@ -106,6 +108,99 @@ static const struct snd_pcm_hardware snd_intel_hadstream = {
        .fifo_size = HAD_FIFO_SIZE,
 };
 
+/* Register access functions */
+
+inline int had_get_hwstate(struct snd_intelhad *intelhaddata)
+{
+       /* Check for device presence -SW state */
+       if (intelhaddata->drv_status == HAD_DRV_DISCONNECTED) {
+               pr_debug("%s:Device not connected:%d\n", __func__,
+                               intelhaddata->drv_status);
+               return -ENODEV;
+       }
+
+       /* Check for device presence -HW state */
+       if (!ospm_power_using_hw_begin(OSPM_DISPLAY_ISLAND,
+                               OSPM_UHB_ONLY_IF_ON)) {
+               pr_err("%s:Device not connected\n", __func__);
+               dump_stack();
+               /* HOT_UNPLUG event can be sent to
+                * maintain correct state within HAD
+                * had_event_handler(HAD_EVENT_HOT_UNPLUG, intelhaddata);
+                * Drop all acuired locks before executing this.
+                */
+               return -ENODEV;
+       }
+
+       ospm_power_using_hw_end(OSPM_DISPLAY_ISLAND);
+       return 0;
+}
+
+inline int had_get_caps(enum had_caps_list query, void *caps)
+{
+       int retval;
+       struct snd_intelhad *intelhaddata = had_data;
+
+       retval = had_get_hwstate(intelhaddata);
+       if (!retval)
+               retval = intelhaddata->query_ops.hdmi_audio_get_caps(query,
+                               caps);
+
+       return retval;
+}
+
+inline int had_set_caps(enum had_caps_list set_element , void *caps)
+{
+       int retval;
+       struct snd_intelhad *intelhaddata = had_data;
+
+       retval = had_get_hwstate(intelhaddata);
+       if (!retval)
+               retval = intelhaddata->query_ops.hdmi_audio_set_caps(
+                               set_element, caps);
+
+       return retval;
+}
+
+inline int had_read_register(uint32_t reg_addr, uint32_t *data)
+{
+       int retval;
+       struct snd_intelhad *intelhaddata = had_data;
+
+       retval = had_get_hwstate(intelhaddata);
+       if (!retval)
+               retval = intelhaddata->reg_ops.hdmi_audio_read_register(
+                               reg_addr, data);
+
+       return retval;
+}
+
+inline int had_write_register(uint32_t reg_addr, uint32_t data)
+{
+       int retval;
+       struct snd_intelhad *intelhaddata = had_data;
+
+       retval = had_get_hwstate(intelhaddata);
+       if (!retval)
+               retval = intelhaddata->reg_ops.hdmi_audio_write_register(
+                               reg_addr, data);
+
+       return retval;
+}
+
+inline int had_read_modify(uint32_t reg_addr, uint32_t data, uint32_t mask)
+{
+       int retval;
+       struct snd_intelhad *intelhaddata = had_data;
+
+       retval = had_get_hwstate(intelhaddata);
+       if (!retval)
+               retval = intelhaddata->reg_ops.hdmi_audio_read_modify(
+                               reg_addr, data, mask);
+
+       return retval;
+}
+
 /**
  * snd_intelhad_init_audio_ctrl - to initialize audio channel status
  * registers and confgiuration registers
@@ -157,8 +252,7 @@ int snd_intelhad_init_audio_ctrl(struct snd_pcm_substream *substream,
        break;
 
        }
-       intelhaddata->reg_ops.hdmi_audio_write_register(
-                               AUD_CH_STATUS_0, ch_stat0.status_0_regval);
+       had_write_register(AUD_CH_STATUS_0, ch_stat0.status_0_regval);
 
        if (flag_silence)
                format = SNDRV_PCM_FORMAT_S24_LE;
@@ -175,13 +269,11 @@ int snd_intelhad_init_audio_ctrl(struct snd_pcm_substream *substream,
                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);
+       had_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);
+       had_write_register(AUD_BUF_CONFIG, buf_cfg.buf_cfgval);
 
        if (flag_silence)
                channels = HAD_MIN_CHANNEL;
@@ -217,8 +309,7 @@ int snd_intelhad_init_audio_ctrl(struct snd_pcm_substream *substream,
        }
 
        cfg_val.cfg_regx.val_bit = 1;
-       intelhaddata->reg_ops.hdmi_audio_write_register(
-                                               AUD_CONFIG, cfg_val.cfg_regval);
+       had_write_register(AUD_CONFIG, cfg_val.cfg_regval);
        return 0;
 }
 
@@ -240,8 +331,7 @@ static void snd_intelhad_prog_dip(struct snd_pcm_substream *substream,
        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);
+       had_write_register(AUD_CNTL_ST, ctrl_state.ctrl_val);
 
        if (flag_silence)
                frame2.fr2_regx.chnl_cnt = HAD_MIN_CHANNEL - 1;
@@ -261,21 +351,17 @@ static void snd_intelhad_prog_dip(struct snd_pcm_substream *substream,
 
        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);
+       had_write_register(AUD_HDMIW_INFOFR, INFO_FRAME_WORD1);
+       had_write_register(AUD_HDMIW_INFOFR, frame2.fr2_val);
+       had_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);
+               had_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);
+       had_write_register(AUD_CNTL_ST, ctrl_state.ctrl_val);
 }
 
 /**
@@ -295,21 +381,16 @@ int snd_intelhad_prog_buffer(struct snd_intelhad *intelhaddata,
        struct snd_pcm_substream *substream;
 
        substream = intelhaddata->stream_info.had_substream;
-       ring_buf_addr = virt_to_phys(substream->runtime->dma_area);
+       ring_buf_addr = substream->runtime->dma_addr;
        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)
@@ -325,33 +406,54 @@ int snd_intelhad_prog_buffer(struct snd_intelhad *intelhaddata,
                        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),
+               had_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),
+               had_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);
        }
+       pr_debug("%s:buf[%d-%d] addr=%#x  and size=%d\n", __func__, start, end,
+                       intelhaddata->buf_info[start].buf_addr,
+                       intelhaddata->buf_info[start].buf_size);
        intelhaddata->valid_buf_cnt = num_periods;
        return 0;
 }
 
-inline void snd_intelhad_read_len(struct snd_intelhad *intelhaddata)
+int snd_intelhad_invd_buffer(int start, int end)
 {
        int i;
-       u32 len;
-       for (i = 0; i < 4 ; i++) {
+
+       /* 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->reg_ops.hdmi_audio_read_register(
-                                       AUD_BUF_A_LENGTH + (i * HAD_REG_WIDTH),
-                                       &len);
-               pr_debug("buf[%d] size=%d\n", i, len);
+               had_write_register(AUD_BUF_A_ADDR + (i * HAD_REG_WIDTH), 0);
+               had_write_register(AUD_BUF_A_LENGTH + (i * HAD_REG_WIDTH), 0);
+       }
+       pr_debug("buf[%d-%d] invalidated\n", start, end);
+       return 0;
+}
+
+inline int snd_intelhad_read_len(struct snd_intelhad *intelhaddata)
+{
+       int i, retval = 0;
+       u32 len[4];
+
+       for (i = 0; i < 4 ; i++) {
+               had_read_register(AUD_BUF_A_LENGTH + (i * HAD_REG_WIDTH),
+                                       &len[i]);
+               if (!len[i])
+                       retval++;
        }
+       if (retval != 1) {
+               for (i = 0; i < 4 ; i++)
+                       pr_debug("buf[%d] size=%d\n", i, len[i]);
+       }
+
+       return retval;
 }
 
 /**
@@ -374,9 +476,9 @@ static void snd_intelhad_prog_cts(u32 aud_samp_freq, u32 tmds, u32 n_param,
        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));
+       pr_debug("TMDS value=%d, N value=%d, CTS Value=%d\n",
+                       tmds, n_param, cts_val);
+       had_write_register(AUD_HDMI_CTS, (BIT(20) | cts_val));
 }
 
 /**
@@ -432,8 +534,7 @@ static int snd_intelhad_prog_n(u32 aud_samp_freq, u32 *n_param,
        }
        if (retval)
                return retval;
-       intelhaddata->reg_ops.hdmi_audio_write_register(
-                               AUD_N_ENABLE, (BIT(20) | n_val));
+       had_write_register(AUD_N_ENABLE, (BIT(20) | n_val));
        *n_param = n_val;
        return retval;
 }
@@ -449,28 +550,30 @@ static int snd_intelhad_open(struct snd_pcm_substream *substream)
        struct snd_intelhad *intelhaddata;
        struct snd_pcm_runtime *runtime;
        struct had_stream_pvt *stream;
+       struct had_pvt_data *had_stream;
        int retval;
 
        pr_debug("snd_intelhad_open called\n");
        intelhaddata = snd_pcm_substream_chip(substream);
+       had_stream = intelhaddata->private_data;
+       if (had_stream->process_trigger != NO_TRIGGER) {
+               pr_err("%s:Yet to process some trigger\n", __func__);
+               return -ENODEV;
+       }
 
-       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);
+       if (had_get_hwstate(intelhaddata)) {
+               pr_err("%s: HDMI cable plugged-out\n", __func__);
                return -ENODEV;
        }
 
-       if (intelhaddata->playback_cnt > 0) {
-               mutex_unlock(&intelhaddata->had_lock);
-               return -EBUSY;
-       } else
-               intelhaddata->playback_cnt++;
+       runtime = substream->runtime;
 
-       mutex_unlock(&intelhaddata->had_lock);
+       /* Check, if device already in use */
+       if (runtime->private_data) {
+               pr_err("Device already in use\n");
+               return -EBUSY;
+       }
 
-       runtime = substream->runtime;
        /* set the runtime hw parameter with local snd_pcm_hardware struct */
        runtime->hw = snd_intel_hadstream;
 
@@ -481,20 +584,17 @@ static int snd_intelhad_open(struct snd_pcm_substream *substream)
        }
        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);
+       runtime->private_data = NULL;
        return retval;
 }
 
@@ -548,21 +648,21 @@ static int snd_intelhad_init_stream(struct snd_pcm_substream *substream)
 static int snd_intelhad_close(struct snd_pcm_substream *substream)
 {
        struct snd_intelhad *intelhaddata;
+       struct snd_pcm_runtime *runtime;
 
        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--;
+       runtime = substream->runtime;
 
        intelhaddata->stream_info.buffer_rendered = 0;
        intelhaddata->stream_info.buffer_ptr = 0;
        intelhaddata->stream_info.str_id = 0;
 
+       /* Check if following drv_status modification is required - VA */
        if (intelhaddata->drv_status != HAD_DRV_DISCONNECTED)
                intelhaddata->drv_status = HAD_DRV_CONNECTED;
-       mutex_unlock(&intelhaddata->had_lock);
-       kfree(substream->runtime->private_data);
+       kfree(runtime->private_data);
+       runtime->private_data = NULL;
        return 0;
 }
 
@@ -582,7 +682,6 @@ static int snd_intelhad_hw_params(struct snd_pcm_substream *substream,
        u32 buf_size;
 
        BUG_ON(!hw_params);
-       pr_debug("snd_intelhad_hw_params called\n");
 
        buf_size = params_buffer_bytes(hw_params);
 
@@ -594,10 +693,9 @@ static int snd_intelhad_hw_params(struct snd_pcm_substream *substream,
        retval = snd_pcm_lib_malloc_pages(substream, buf_size);
        if (retval < 0)
                return retval;
-       pr_debug("allocated memory = %d\n", buf_size);
+       pr_debug("%s:allocated memory = %d\n", __func__, buf_size);
        memset(substream->runtime->dma_area, 0, buf_size);
 
-       pr_debug("snd_intelhad_hw_params exited\n");
        return retval;
 }
 
@@ -621,15 +719,12 @@ 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);
+       retval = had_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);
@@ -651,8 +746,8 @@ int snd_intelhad_start_silence(struct snd_intelhad *intelhaddata)
 {
        int i, retval = 0;
        u32 buf_addr;
-
-       pr_debug("Enter %s\n", __func__);
+       unsigned long flag_irqs;
+       struct had_pvt_data *had_stream;
 
        buf_addr = virt_to_phys(intelhaddata->flat_data);
 
@@ -662,22 +757,24 @@ int snd_intelhad_start_silence(struct snd_intelhad *intelhaddata)
                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),
+               had_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),
+               had_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);
        }
+       pr_debug("%s:buf[%d-%d] addr=%#x  and size=%d\n", __func__,
+                       HAD_BUF_TYPE_C, HAD_BUF_TYPE_D,
+                       intelhaddata->buf_info[HAD_BUF_TYPE_C].buf_addr,
+                       intelhaddata->buf_info[HAD_BUF_TYPE_C].buf_size);
+       spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs);
        intelhaddata->valid_buf_cnt = HAD_MAX_PERIODS;
        intelhaddata->curr_buf = HAD_BUF_TYPE_C;
+       had_stream = intelhaddata->private_data;
+       had_stream->stream_status = HAD_RUNNING_SILENCE;
+       spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
 
-       intelhaddata->reg_ops.hdmi_audio_read_modify(AUD_CONFIG, 1,
-                               BIT(0));
+       had_read_modify(AUD_CONFIG, 1, BIT(0));
 
        return retval;
 }
@@ -685,39 +782,37 @@ int snd_intelhad_start_silence(struct snd_intelhad *intelhaddata)
 int snd_intelhad_stop_silence(struct snd_intelhad *intelhaddata)
 {
        int i, retval = 0;
+       unsigned long flag_irqs;
+       struct had_pvt_data *had_stream;
 
        pr_debug("Enter %s\n", __func__);
 
-       intelhaddata->reg_ops.hdmi_audio_read_modify(AUD_CONFIG, 0,
-                               BIT(0));
+       spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs);
+       had_stream = intelhaddata->private_data;
+       had_stream->stream_status = HAD_INIT;
+       spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+
+       had_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);
+               had_write_register(AUD_BUF_A_ADDR + (i * HAD_REG_WIDTH), 0);
+               had_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);
+       had_write_register(AUD_HDMI_STATUS, 1);
+       had_write_register(AUD_HDMI_STATUS, 0);
 
        return retval;
 }
 
 void snd_process_stop_trigger(struct snd_intelhad *intelhaddata)
 {
-       int buf_id, buff_done, i;
+       int buf_id, 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;
+       buf_id = intelhaddata->curr_buf;
 
        pr_debug("%s:buf_id=%d\n", __func__, buf_id);
        /* Invalidate Audio buffers */
@@ -727,12 +822,10 @@ void snd_process_stop_trigger(struct snd_intelhad *intelhaddata)
                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);
+                       had_write_register(AUD_BUF_A_ADDR +
+                                       (i * HAD_REG_WIDTH), 0);
+                       had_write_register(AUD_BUF_A_LENGTH +
+                                       (i * HAD_REG_WIDTH), 0);
                } else {
                        /* Program with silence buffer */
                        buf_addr = virt_to_phys(intelhaddata->flat_data);
@@ -741,10 +834,10 @@ void snd_process_stop_trigger(struct snd_intelhad *intelhaddata)
                        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(
+                       had_write_register(
                                        AUD_BUF_A_ADDR + (i * HAD_REG_WIDTH),
                                        buf_addr | BIT(0) | BIT(1));
-                       intelhaddata->reg_ops.hdmi_audio_write_register(
+                       had_write_register(
                                        AUD_BUF_A_LENGTH + (i * HAD_REG_WIDTH),
                                        (MAX_SZ_ZERO_BUF));
                        intelhaddata->buf_info[i].is_valid = true;
@@ -766,64 +859,55 @@ 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;
+       struct had_pvt_data *had_stream;
 
        intelhaddata = snd_pcm_substream_chip(substream);
        stream = substream->runtime->private_data;
-       reg_ops = intelhaddata->reg_ops;
+       had_stream = intelhaddata->private_data;
 
        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);
+               if (had_get_hwstate(intelhaddata)) {
+                       pr_err("_START: HDMI cable plugged-out\n");
+                       retval = -ENODEV;
+                       break;
                }
-               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_lock_irqsave(&intelhaddata->had_spinlock, flag_irq);
+               had_stream->process_trigger = PRE_START;
                spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irq);
-               pr_debug("Processed _Start, buf_id = %d\n", buf_id);
+               pr_debug("Processed _Start\n");
 
                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;
+               had_stream->process_trigger = STOP_TRIGGER;
                /* 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);
-
+               if (had_stream->stream_status == HAD_RUNNING_DUMMY) {
+                       spin_unlock_irqrestore(&intelhaddata->had_spinlock,
+                                       flag_irq);
+                       cancel_delayed_work(&intelhaddata->dummy_audio);
+               } else if (intelhaddata->drv_status != HAD_DRV_DISCONNECTED) {
+                       spin_unlock_irqrestore(&intelhaddata->had_spinlock,
+                                       flag_irq);
+                       if (!had_get_hwstate(intelhaddata))
+                               snd_process_stop_trigger(intelhaddata);
+               } else
+                       spin_unlock_irqrestore(&intelhaddata->had_spinlock,
+                                       flag_irq);
                stream->stream_status = STREAM_DROPPED;
                break;
 
@@ -846,10 +930,23 @@ static int snd_intelhad_pcm_prepare(struct snd_pcm_substream *substream)
        u32 disp_samp_freq, n_param;
        struct snd_intelhad *intelhaddata;
        struct snd_pcm_runtime *runtime;
+       struct had_pvt_data *had_stream;
+       unsigned long flag_irqs;
 
        pr_debug("pcm_prepare called\n");
 
+       intelhaddata = snd_pcm_substream_chip(substream);
        runtime = substream->runtime;
+       had_stream = intelhaddata->private_data;
+
+       spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs);
+       if (had_stream->process_trigger != NO_TRIGGER) {
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+               pr_err("%s:Yet to process some trigger\n", __func__);
+               return -EBUSY;
+       }
+       spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+
        pr_debug("period_size=%d\n",
                                frames_to_bytes(runtime, runtime->period_size));
        pr_debug("periods=%d\n", runtime->periods);
@@ -866,20 +963,22 @@ static int snd_intelhad_pcm_prepare(struct snd_pcm_substream *substream)
                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;
+
+       if (had_get_hwstate(intelhaddata)) {
+               pr_err("%s: HDMI cable plugged-out\n", __func__);
+               retval = -ENODEV;
+               goto prep_end;
+       }
+
        /* Get N value in KHz */
-       retval = intelhaddata->query_ops.hdmi_audio_get_caps(
-                               HAD_GET_SAMPLING_FREQ, &disp_samp_freq);
+       retval = had_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);
@@ -908,7 +1007,7 @@ static snd_pcm_uframes_t snd_intelhad_pcm_pointer(
                                        struct snd_pcm_substream *substream)
 {
        struct snd_intelhad *intelhaddata;
-       u32 bytes_rendered;
+       u32 bytes_rendered = 0;
 
        intelhaddata = snd_pcm_substream_chip(substream);
 
@@ -917,15 +1016,36 @@ static snd_pcm_uframes_t snd_intelhad_pcm_pointer(
                return SNDRV_PCM_POS_XRUN;
        }
 
-       div_u64_rem(intelhaddata->stream_info.buffer_rendered,
+       if (intelhaddata->stream_info.buffer_rendered)
+               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;
 }
 
+/**
+* snd_intelhad_pcm_mmap- mmaps a kernel buffer to user space for copying data
+*
+* @substream:  substream for which the function is called
+* @vma:                struct instance of memory VMM memory area
+*
+* This function is called by OS when a user space component
+* tries to get mmap memory from driver
+*/
+static int snd_intelhad_pcm_mmap(struct snd_pcm_substream *substream,
+       struct vm_area_struct *vma)
+{
+       pr_debug("entry with prot:%s\n", __func__);
+       vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+       return remap_pfn_range(vma, vma->vm_start,
+                       substream->dma_buffer.addr >> PAGE_SHIFT,
+                       vma->vm_end - vma->vm_start, vma->vm_page_prot);
+}
+
 int hdmi_audio_mode_change(struct snd_pcm_substream *substream)
 {
        int retval = 0;
@@ -935,17 +1055,14 @@ int hdmi_audio_mode_change(struct snd_pcm_substream *substream)
        intelhaddata = snd_pcm_substream_chip(substream);
 
        /* Disable Audio */
-       intelhaddata->reg_ops.hdmi_audio_read_modify(
-                       AUD_CONFIG, 0, BIT(0));
+       had_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);
+       retval = had_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);
@@ -957,8 +1074,7 @@ int hdmi_audio_mode_change(struct snd_pcm_substream *substream)
                                        disp_samp_freq, n_param, intelhaddata);
 
        /* Enable Audio */
-       intelhaddata->reg_ops.hdmi_audio_read_modify(
-                       AUD_CONFIG, 1, BIT(0));
+       had_read_modify(AUD_CONFIG, 1, BIT(0));
 
 out:
        return retval;
@@ -966,23 +1082,27 @@ out:
 
 void dummy_audio_play(struct work_struct *work)
 {
-       struct snd_intelhad *intelhaddata = had_pvt_data;
+       struct snd_intelhad *intelhaddata = had_data;
+       struct had_pvt_data *had_stream;
+       unsigned long flag_irqs;
 
-       mutex_lock(&intelhaddata->had_lock);
+       spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs);
        if (intelhaddata->drv_status != HAD_DRV_DISCONNECTED) {
-               mutex_unlock(&intelhaddata->had_lock);
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
                pr_debug("HDMI device still connected\n");
                return;
        }
+       had_stream = intelhaddata->private_data;
 
-       if (!intelhaddata->send_data) {
-               mutex_unlock(&intelhaddata->had_lock);
+       /* In case _STOP, silence data, return */
+       if (had_stream->stream_status < HAD_RUNNING_STREAM) {
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
                pr_debug("HDMI device_flag is reset\n");
                return;
        }
 
+       spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
        had_event_handler(HAD_EVENT_AUDIO_BUFFER_DONE, intelhaddata);
-       mutex_unlock(&intelhaddata->had_lock);
        schedule_delayed_work(&intelhaddata->dummy_audio, intelhaddata->timer);
 }
 
@@ -996,6 +1116,7 @@ struct snd_pcm_ops snd_intelhad_playback_ops = {
        .prepare =      snd_intelhad_pcm_prepare,
        .trigger =      snd_intelhad_pcm_trigger,
        .pointer =      snd_intelhad_pcm_pointer,
+       .mmap = snd_intelhad_pcm_mmap,
 };
 
 /**
@@ -1043,14 +1164,12 @@ 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,
@@ -1073,13 +1192,10 @@ static int had_iec958_put(struct snd_kcontrol *kcontrol,
                (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;
 }
 
@@ -1122,6 +1238,7 @@ static int __devinit hdmi_audio_probe(struct platform_device *devptr)
        struct snd_card *card;
        struct had_callback_ops ops_cb;
        struct snd_intelhad *intelhaddata;
+       struct had_pvt_data *had_stream;
 
        pr_debug("Enter %s\n", __func__);
 
@@ -1132,7 +1249,14 @@ static int __devinit hdmi_audio_probe(struct platform_device *devptr)
                return -ENOMEM;
        }
 
-       had_pvt_data = intelhaddata;
+       had_stream = kzalloc(sizeof(*had_stream), GFP_KERNEL);
+       if (!had_stream) {
+               pr_err("mem alloc failed\n");
+               retval = -ENOMEM;
+               goto free_haddata;
+       }
+
+       had_data = intelhaddata;
        ops_cb.intel_had_event_call_back = had_event_handler;
 
        /* registering with display driver to get access to display APIs */
@@ -1143,10 +1267,9 @@ static int __devinit hdmi_audio_probe(struct platform_device *devptr)
                        &(intelhaddata->query_ops));
        if (retval) {
                pr_err("querying display driver APIs failed %#x\n", retval);
-               goto free_haddata;
+               goto free_hadstream;
        }
        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 */
@@ -1157,7 +1280,7 @@ static int __devinit hdmi_audio_probe(struct platform_device *devptr)
        intelhaddata->card = card;
        intelhaddata->card_id = hdmi_card_id;
        intelhaddata->card_index = card->number;
-       intelhaddata->playback_cnt = 0;
+       intelhaddata->private_data = had_stream;
        intelhaddata->flag_underrun = 0;
        intelhaddata->aes_bits = SNDRV_PCM_DEFAULT_CON_SPDIF;
        strncpy(card->driver, INTEL_HAD, strlen(INTEL_HAD));
@@ -1178,8 +1301,7 @@ static int __devinit hdmi_audio_probe(struct platform_device *devptr)
                            &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),
+                       SNDRV_DMA_TYPE_DEV, card->dev,
                        HAD_MIN_BUFFER, HAD_MAX_BUFFER);
        if (retval)
                goto err;
@@ -1219,18 +1341,20 @@ static int __devinit hdmi_audio_probe(struct platform_device *devptr)
        if (retval) {
                pr_err("registering with display driver failed %#x\n", retval);
                snd_card_free(card);
-               goto free_haddata;
+               goto free_hadstream;
        }
 
        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_hadstream:
+       kfree(had_stream);
 free_haddata:
        kfree(intelhaddata);
        intelhaddata = NULL;
+       pr_err("Error returned from %s api %#x\n", __func__, retval);
        return retval;
 }
 
@@ -1244,7 +1368,7 @@ free_haddata:
  */
 static int __devexit hdmi_audio_remove(struct platform_device *devptr)
 {
-       struct snd_intelhad *intelhaddata = had_pvt_data;
+       struct snd_intelhad *intelhaddata = had_data;
        int caps;
 
        pr_debug("Enter %s\n", __func__);
@@ -1252,18 +1376,15 @@ static int __devexit hdmi_audio_remove(struct platform_device *devptr)
        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);
+               had_set_caps(HAD_SET_DISABLE_AUDIO_INT, &caps);
+               had_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->private_data);
        kfree(intelhaddata);
        return 0;
 }
index d2bef78..be20b26 100644 (file)
@@ -41,8 +41,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_BUFFER         (64*1024)
+#define HAD_MIN_BUFFER         (32*1024)
 #define HAD_MAX_PERIODS                4
 #define HAD_MIN_PERIODS                4
 #define HAD_MAX_PERIOD_BYTES   HAD_MAX_BUFFER
@@ -91,6 +91,28 @@ enum had_stream_status {
        STREAM_DROPPED = 3
 };
 
+/**
+ * enum had_status_stream - HAD stream states
+ * */
+enum had_status_stream {
+       HAD_INIT = 0,
+       HAD_RUNNING_SILENCE = 1,
+       HAD_RUNNING_STREAM = 2,
+       HAD_RUNNING_DUMMY = 3,
+};
+
+/**
+ * enum had_process_trigger - Audio stream states
+ * _START is processed in 2 halves.
+ * _PRESTART & _START_TRIGGER
+ * */
+enum had_process_trigger {
+       NO_TRIGGER = 0,
+       PRE_START = 1,
+       START_TRIGGER = 2,
+       STOP_TRIGGER = 3,
+};
+
 enum had_drv_status {
        HAD_DRV_CONNECTED,
        HAD_DRV_RUNNING,
@@ -407,6 +429,11 @@ struct had_stream_pvt {
        ssize_t                         dbg_cum_bytes;
 };
 
+struct had_pvt_data {
+       enum had_stream_status          stream_status;
+       enum had_process_trigger        process_trigger;
+};
+
 struct had_callback_ops {
        had_event_call_back intel_had_event_call_back;
 };
@@ -420,13 +447,12 @@ struct had_callback_ops {
  * @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
+ * @had_spinlock: driver lock
  * @aes_bits: IEC958 status bits
  */
 struct snd_intelhad {
@@ -436,24 +462,19 @@ struct snd_intelhad {
        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;
+       struct had_pvt_data *private_data;
        /* 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;
 };
@@ -472,5 +493,14 @@ int snd_intelhad_init_audio_ctrl(struct snd_pcm_substream *substream,
                                        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);
+int snd_intelhad_invd_buffer(int start, int end);
+inline int snd_intelhad_read_len(struct snd_intelhad *intelhaddata);
+
+/* Register access functions */
+inline int had_get_hwstate(struct snd_intelhad *intelhaddata);
+inline int had_get_caps(enum had_caps_list query_element , void *capabilties);
+inline int had_set_caps(enum had_caps_list set_element , void *capabilties);
+inline int had_read_register(uint32_t reg_addr, uint32_t *data);
+inline int had_write_register(uint32_t reg_addr, uint32_t data);
+inline int had_read_modify(uint32_t reg_addr, uint32_t data, uint32_t mask);
 #endif
index d8e475f..772da21 100644 (file)
@@ -48,29 +48,40 @@ static int flag_en_allbufs;
 int hdmi_audio_suspend(void *haddata, pm_event_t event)
 {
        int caps, retval = 0;
+       struct had_pvt_data *had_stream;
+       unsigned long flag_irqs;
        struct snd_intelhad *intelhaddata = (struct snd_intelhad *)haddata;
+
        pr_debug("Enter:%s", __func__);
 
-       if (intelhaddata->playback_cnt > 0) {
+       had_stream = intelhaddata->private_data;
+
+       spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs);
+       if (had_stream->stream_status > HAD_RUNNING_SILENCE) {
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
                pr_err("audio stream is active\n");
-               return -EBUSY;
+               return -EAGAIN;
        }
 
-       mutex_lock(&intelhaddata->had_lock);
        if (intelhaddata->drv_status == HAD_DRV_DISCONNECTED) {
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
                pr_debug("had not connected\n");
-               mutex_unlock(&intelhaddata->had_lock);
+               return retval;
+       }
+
+       if (intelhaddata->drv_status == HAD_DRV_SUSPENDED) {
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+               pr_debug("had already suspended\n");
                return retval;
        }
 
        intelhaddata->drv_status = HAD_DRV_SUSPENDED;
+       spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
        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);
+       had_set_caps(HAD_SET_DISABLE_AUDIO_INT, &caps);
+       had_set_caps(HAD_SET_DISABLE_AUDIO, NULL);
        retval = snd_intelhad_stop_silence(intelhaddata);
-       mutex_unlock(&intelhaddata->had_lock);
+       pr_debug("Exit:%s", __func__);
        return retval;
 }
 
@@ -86,151 +97,382 @@ int hdmi_audio_resume(void *haddata)
 {
        int caps, retval = 0;
        struct snd_intelhad *intelhaddata = (struct snd_intelhad *)haddata;
+       unsigned long flag_irqs;
+
        pr_debug("Enter:%s", __func__);
 
-       mutex_lock(&intelhaddata->had_lock);
-       if (HAD_DRV_DISCONNECTED == intelhaddata->drv_status) {
+       spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs);
+       if (intelhaddata->drv_status == HAD_DRV_DISCONNECTED) {
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
                pr_debug("had not connected\n");
-               mutex_unlock(&intelhaddata->had_lock);
                return 0;
        }
 
        if (HAD_DRV_SUSPENDED != intelhaddata->drv_status) {
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
                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;
+       if (had_get_hwstate(intelhaddata)) {
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+               pr_err("Failed to resume. Device not accessible\n");
+               return -ENODEV;
+       }
+
+       intelhaddata->drv_status = HAD_DRV_CONNECTED;
+       spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+       caps = HDMI_AUDIO_UNDERRUN | HDMI_AUDIO_BUFFER_DONE;
+       retval = had_set_caps(HAD_SET_ENABLE_AUDIO_INT, &caps);
+       retval = had_set_caps(HAD_SET_ENABLE_AUDIO, NULL);
+       retval = snd_intelhad_configure_silence(intelhaddata);
+       retval = snd_intelhad_start_silence(intelhaddata);
+       pr_debug("Exit:%s", __func__);
+       return retval;
+}
+
+static inline int had_chk_intrmiss(struct snd_intelhad *intelhaddata,
+               enum intel_had_aud_buf_type buf_id)
+{
+       int i, intr_count = 0;
+       enum intel_had_aud_buf_type buff_done;
+       u32 buf_size, buf_addr;
+       struct had_pvt_data *had_stream;
+       unsigned long flag_irqs;
+
+       had_stream = intelhaddata->private_data;
+
+       buff_done = buf_id;
+
+       intr_count = snd_intelhad_read_len(intelhaddata);
+       if (intr_count > 1) {
+               /* In case of active playback */
+               pr_err("Driver detected %d missed buffer done interrupt(s)!!!!\n",
+                               (intr_count - 1));
+               if (intr_count > 3)
+                       return intr_count;
+
+               buf_id += (intr_count - 1);
+               /* Reprogram registers*/
+               for (i = buff_done; i < buf_id; i++) {
+                       i = i % 4;
+                       buf_size = intelhaddata->buf_info[i].buf_size;
+                       buf_addr = intelhaddata->buf_info[i].buf_addr;
+                       had_write_register(AUD_BUF_A_LENGTH +
+                                       (i * HAD_REG_WIDTH), buf_size);
+                       had_write_register(
+                                       AUD_BUF_A_ADDR+(i * HAD_REG_WIDTH),
+                                       (buf_addr | BIT(0) | BIT(1)));
+               }
+               buf_id = buf_id % 4;
+               spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs);
+               intelhaddata->buff_done = buf_id;
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+       }
+
+       return intr_count;
+}
+
+static inline int had_start_dummy_playback(struct snd_intelhad *intelhaddata)
+{
+       int retval = 0;
+       enum intel_had_aud_buf_type buf_id;
+       u32 buf_size;
+       struct snd_pcm_substream *substream;
+       struct had_pvt_data *had_stream;
+       u32 msecs, rate, channels;
+       unsigned long flag_irqs;
+
+       pr_debug("Enter:%s", __func__);
+
+       spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs);
+       had_stream = intelhaddata->private_data;
+       buf_id = intelhaddata->curr_buf;
+       substream = intelhaddata->stream_info.had_substream;
+
+       if ((had_stream->stream_status < HAD_RUNNING_DUMMY) && substream
+                       && substream->runtime) {
+               had_stream->stream_status = HAD_RUNNING_DUMMY;
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+               pr_debug("Steam active, start dummy_playback\n");
+               /* In case substream is active, start reporting
+                * dummy buffer_done interrupts to above ALSA layer.
+                */
+               buf_size =  intelhaddata->buf_info[buf_id].buf_size;
+               rate = substream->runtime->rate;
+               channels = substream->runtime->channels;
+               msecs = (buf_size*1000)/(rate*channels*4);
+               intelhaddata->timer = msecs_to_jiffies(msecs);
+               schedule_delayed_work(
+                               &intelhaddata->dummy_audio,
+                               intelhaddata->timer);
+       }
+
+       return retval;
+}
+
+static inline int had_process_pre_start(struct snd_intelhad *intelhaddata,
+               enum intel_had_aud_buf_type buf_id)
+{
+       int retval = 0;
+       struct had_pvt_data *had_stream;
+       enum intel_had_aud_buf_type prg_start, prg_end;
+       enum intel_had_aud_buf_type inv_start;
+       unsigned long flag_irqs;
+
+       had_stream = intelhaddata->private_data;
+
+       if (intelhaddata->valid_buf_cnt-1 == buf_id) {
+               if (had_stream->stream_status >= HAD_RUNNING_STREAM)
+                       buf_id = HAD_BUF_TYPE_A;
+               else    /* Use only silence buffers C & D */
+                       buf_id = HAD_BUF_TYPE_C;
+       } else
+               buf_id = buf_id + 1;
+
+       /* Program all buffers in case of Buf == A */
+       if (buf_id == HAD_BUF_TYPE_A) {
+               prg_start = buf_id + 1;
+               prg_end = HAD_BUF_TYPE_D;
+       } else {
+               prg_start = HAD_BUF_TYPE_A;
+               prg_end = buf_id - 1;
+       }
+       retval = snd_intelhad_prog_buffer(intelhaddata,
+                       prg_start, prg_end);
+       /* Invalidate all buffs after buf_id */
+       inv_start = buf_id + 1;
+       if ((buf_id != HAD_BUF_TYPE_A) && (buf_id != HAD_BUF_TYPE_D))
+               snd_intelhad_invd_buffer(inv_start, HAD_BUF_TYPE_D);
+
+       /* Start reporting BUFFER_DONE/UNDERRUN to above layers*/
+       spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs);
+       intelhaddata->curr_buf = buf_id;
+
+       if (buf_id != HAD_BUF_TYPE_A)
+               had_stream->process_trigger = START_TRIGGER;
        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);
+               had_stream->stream_status = HAD_RUNNING_STREAM;
+               had_stream->process_trigger = NO_TRIGGER;
+       }
+       spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+
+       pr_debug("Processed %s, buf_id = %d\n", __func__, buf_id);
+
+       return retval;
+}
+
+static inline int had_process_start_trigger(struct snd_intelhad *intelhaddata)
+{
+       int retval = 0;
+       struct had_pvt_data *had_stream;
+       enum intel_had_aud_buf_type prg_start;
+       unsigned long flag_irqs;
+
+       had_stream = intelhaddata->private_data;
+       spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs);
+       /* Check for device state
+        * In case, _DISCONNECTED, start dummy_playback
+        * if not done
+        */
+       if (intelhaddata->drv_status == HAD_DRV_DISCONNECTED) {
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+               pr_debug("Cable plugged-out\n");
+               retval = had_start_dummy_playback(intelhaddata);
+               spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs);
+               had_stream->process_trigger = NO_TRIGGER;
+               intelhaddata->curr_buf = HAD_BUF_TYPE_A;
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+               return retval;
+       }
+
+       had_stream->stream_status = HAD_RUNNING_STREAM;
+       had_stream->process_trigger = NO_TRIGGER;
+       intelhaddata->curr_buf = HAD_BUF_TYPE_A;
+       prg_start = intelhaddata->buff_done;
+       spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+       retval = snd_intelhad_prog_buffer(intelhaddata,
+                       prg_start, HAD_BUF_TYPE_D);
+
+       return retval;
+}
+
+static inline int had_process_stop_trigger(struct snd_intelhad *intelhaddata)
+{
+       int i, retval = 0;
+       struct had_pvt_data *had_stream;
+       enum intel_had_aud_buf_type buf_id;
+       u32 buf_addr;
+       unsigned long flag_irqs;
+
+       had_stream = intelhaddata->private_data;
+
+       buf_id = intelhaddata->curr_buf;
+       /* If device disconnected, ignore this interrupt */
+       if (had_stream->stream_status == HAD_RUNNING_DUMMY) {
+               had_stream->stream_status = HAD_INIT;
+               return retval;
+       }
+
+       /* All successive intr for Silence stream */
+       had_stream->stream_status = HAD_RUNNING_SILENCE;
+       had_stream->process_trigger = NO_TRIGGER;
+
+       /* If buf_id < HAD_BUF_TYPE_C, ignore */
+       if (buf_id < HAD_BUF_TYPE_C) {
+               intelhaddata->curr_buf = HAD_BUF_TYPE_C;
+               return retval;
        }
-       mutex_unlock(&intelhaddata->had_lock);
+       if (buf_id == HAD_BUF_TYPE_C)
+               intelhaddata->curr_buf = HAD_BUF_TYPE_D;
+       else
+               intelhaddata->curr_buf = HAD_BUF_TYPE_C;
+       spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+
+       /* _STOP -> _START -> _STOP occured
+        * invalidate Buff_A, Buff_B
+        */
+       snd_intelhad_invd_buffer(HAD_BUF_TYPE_A, HAD_BUF_TYPE_B);
+       i = buf_id;
+       buf_addr = virt_to_phys(intelhaddata->flat_data);
+       /* Program the buf registers with addr and len */
+       had_write_register(AUD_BUF_A_ADDR + (i * HAD_REG_WIDTH),
+                       buf_addr | BIT(0) | BIT(1));
+       had_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;
+       pr_debug("buf[%d] addr=%#x  and size=%d\n", i,
+                       intelhaddata->buf_info[i].buf_addr,
+                       intelhaddata->buf_info[i].buf_size);
+       spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs);
+
        return retval;
 }
 
 int had_process_buffer_done(struct snd_intelhad *intelhaddata)
 {
-       int i = 0, retval = 0;
+       int retval = 0;
+       u32 len = 1;
        enum intel_had_aud_buf_type buf_id;
+       enum intel_had_aud_buf_type buff_done;
        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;
+       struct had_pvt_data *had_stream;
+       int intr_count;
+       enum had_stream_status          stream_status;
+       enum had_process_trigger        process_trigger;
+       unsigned long flag_irqs;
+
+       had_stream = intelhaddata->private_data;
+       stream = &intelhaddata->stream_info;
+       intr_count = 1;
 
-       spin_lock(&intelhaddata->had_spinlock);
+       spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs);
        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;
+       buff_done = intelhaddata->buff_done;
+       buf_size = intelhaddata->buf_info[buf_id].buf_size;
+       stream_status = had_stream->stream_status;
+       process_trigger = had_stream->process_trigger;
+       spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
 
        pr_debug("Enter:%s buf_id=%d", __func__, buf_id);
-       stream = &intelhaddata->stream_info;
-       buf_size =  intelhaddata->buf_info[buf_id].buf_size;
+
+       /* Every debug statement has an implication
+        * of ~5msec. Thus, avoid having >3 debug statements
+        * for each buffer_done handling.
+        */
+
+       /* Check for any intr_miss in case of active playback */
+       if ((stream_status == HAD_RUNNING_STREAM) &&
+                       (process_trigger == NO_TRIGGER) &&
+                       !flag_en_allbufs) {
+               intr_count = had_chk_intrmiss(intelhaddata, buf_id);
+               if (!intr_count || (intr_count > 3)) {
+                       pr_err("HAD SW state in non-recoverable!!! mode\n");
+                       pr_err("Already played stale data\n");
+                       return retval;
+               }
+               buf_id += (intr_count - 1);
+               buf_id = buf_id % 4;
+       }
+
+       spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs);
+       /* Acknowledge _START trigger recieved */
+       if (had_stream->process_trigger == PRE_START) {
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+               retval = had_process_pre_start(intelhaddata, buf_id);
+               return retval;
+       }
 
        /* 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);
+       if (had_stream->process_trigger == START_TRIGGER) {
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+               retval = had_process_start_trigger(intelhaddata);
                return retval;
        }
 
        /* Program Silence buffer, in case _STOP trigger recieved */
-       if (intelhaddata->stop_trigger) {
-               pr_debug("processing stop trigger in hanlder:bufid=%d\n",
+       if (had_stream->process_trigger == STOP_TRIGGER) {
+               /* Process with spin_lock acquired
+                * Can be optimized later
+                */
+               retval = had_process_stop_trigger(intelhaddata);
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+               pr_debug("Processed 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)
+               if (had_stream->stream_status >= HAD_RUNNING_STREAM)
                        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++;
+               intelhaddata->curr_buf = buf_id + 1;
+
+       if (had_stream->stream_status == HAD_RUNNING_DUMMY) {
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+               goto exit;
+       }
+
+       spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
 
-       if (intelhaddata->send_data)
+       if (had_get_hwstate(intelhaddata)) {
+               pr_err("HDMI cable plugged-out\n");
                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));
-               }
+       if (flag_en_allbufs) {
+               pr_debug("Enabling Top half buffers after _PLUG\n");
+               retval = snd_intelhad_prog_buffer(intelhaddata,
+                               HAD_BUF_TYPE_A, (buf_id-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));
+       }
+       /*Reprogram the registers with addr and length*/
+       had_write_register(AUD_BUF_A_LENGTH +
+                       (buf_id * HAD_REG_WIDTH), buf_size);
+       had_write_register(AUD_BUF_A_ADDR+(buf_id * HAD_REG_WIDTH),
+                       intelhaddata->buf_info[buf_id].buf_addr|
+                       BIT(0) | BIT(1));
+
+       had_read_register(AUD_BUF_A_LENGTH + (buf_id * HAD_REG_WIDTH),
+                                       &len);
+       pr_debug("%s:Enabled buf[%d]\n", __func__, buf_id);
+exit:
+       /* In case of actual/dummy data,
+        * report buffer_done to above ALSA layer
+        */
+       buf_size =  intelhaddata->buf_info[buf_id].buf_size;
+       if (stream_status >= HAD_RUNNING_STREAM) {
+               intelhaddata->stream_info.buffer_rendered +=
+                       (intr_count * buf_size);
+               stream->period_elapsed(stream->had_substream);
        }
 
        return retval;
@@ -238,74 +480,109 @@ int had_process_buffer_done(struct snd_intelhad *intelhaddata)
 
 int had_process_buffer_underrun(struct snd_intelhad *intelhaddata)
 {
-       int caps, i = 0, retval = 0;
+       int 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;
+       struct had_pvt_data *had_stream;
+       enum had_stream_status          stream_status;
+       enum had_process_trigger        process_trigger;
        u32 hdmi_status;
-       int pcm_active;
+       unsigned long flag_irqs;
 
-       spin_lock(&intelhaddata->had_spinlock);
+       had_stream = intelhaddata->private_data;
+       stream = &intelhaddata->stream_info;
+
+       spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs);
        buf_id = intelhaddata->curr_buf;
+       stream_status = had_stream->stream_status;
+       process_trigger = had_stream->process_trigger;
        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;
+       if (stream_status == HAD_RUNNING_STREAM)
+               intelhaddata->curr_buf = HAD_BUF_TYPE_A;
+       else    /* Use only silence buffers C & D */
+               intelhaddata->curr_buf = HAD_BUF_TYPE_C;
 
+       spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
 
-       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");
+       pr_debug("Enter:%s buf_id=%d, stream_status=%d, process_trigger=%d\n",
+                       __func__, buf_id, stream_status, process_trigger);
+
+       if (stream_status == HAD_RUNNING_DUMMY) {
+               pr_err("_UNDERRUN occured during dummy playback\n");
+               return retval;
+       }
 
        /* Handle Underrun interrupt within Audio Unit */
-       reg_ops.hdmi_audio_write_register(
-                       AUD_CONFIG, 0);
+       had_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;
-
+       had_write_register(AUD_HDMI_STATUS, 1);
+       had_write_register(AUD_HDMI_STATUS, 0);
        /**
         * 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);
+               had_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);
+                       had_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);
+       if (stream_status == HAD_RUNNING_STREAM) {
+               if (process_trigger == NO_TRIGGER) {
+                       /* Report UNDERRUN error to above layers */
+                       intelhaddata->flag_underrun = 1;
+                       stream->period_elapsed(stream->had_substream);
+                       had_read_modify(AUD_CONFIG, 1, BIT(0));
+                       retval = snd_intelhad_prog_buffer(intelhaddata,
+                                       HAD_BUF_TYPE_A, HAD_BUF_TYPE_D);
+               } else if (process_trigger == STOP_TRIGGER) {
+                       spin_lock_irqsave(&intelhaddata->had_spinlock,
+                                       flag_irqs);
+                       had_stream->process_trigger = NO_TRIGGER;
+                       spin_unlock_irqrestore(&intelhaddata->had_spinlock,
+                                       flag_irqs);
+                       /* Invalidate A & B */
+                       snd_intelhad_invd_buffer(HAD_BUF_TYPE_A,
+                                       HAD_BUF_TYPE_B);
+                       /* Start sending silence data */
+                       retval = snd_intelhad_start_silence(intelhaddata);
+                       return retval;
+               } else {
+                       pr_err("%s:Should never come here\n", __func__);
+                       pr_err("stream_status=%d,process_trigger=%d\n",
+                                       stream_status, process_trigger);
+                       return retval;
+               }
+       } else if (stream_status == HAD_RUNNING_SILENCE) {
+               if (process_trigger < START_TRIGGER)
+                       retval = snd_intelhad_start_silence(intelhaddata);
+               else if (process_trigger == START_TRIGGER) {
+                       spin_lock_irqsave(&intelhaddata->had_spinlock,
+                                       flag_irqs);
+                       intelhaddata->curr_buf = HAD_BUF_TYPE_A;
+                       had_stream->process_trigger = NO_TRIGGER;
+                       had_stream->stream_status = HAD_RUNNING_STREAM;
+                       spin_unlock_irqrestore(&intelhaddata->had_spinlock,
+                                       flag_irqs);
+                       had_read_modify(AUD_CONFIG, 1, BIT(0));
+                       retval = snd_intelhad_prog_buffer(intelhaddata,
+                               HAD_BUF_TYPE_A, HAD_BUF_TYPE_D);
+                       /* Pre start already processed */
+               } else {
+                       pr_err("%s:Should never come here\n", __func__);
+                       pr_err("stream_status=%d,process_trigger=%d\n",
+                                       stream_status, process_trigger);
+                       return retval;
+               }
        }
 
        return retval;
@@ -313,71 +590,78 @@ int had_process_buffer_underrun(struct snd_intelhad *intelhaddata)
 
 int had_process_hot_plug(struct snd_intelhad *intelhaddata)
 {
-       int caps, i = 0, retval = 0;
+       int caps, 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;
+       struct had_pvt_data *had_stream;
+       enum had_stream_status          stream_status;
+       unsigned long flag_irqs;
 
-       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__);
 
+       substream = intelhaddata->stream_info.had_substream;
+       had_stream = intelhaddata->private_data;
 
-       pr_debug("Enter:%s", __func__);
-       mutex_lock(&intelhaddata->had_lock);
+       spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs);
+       if (intelhaddata->drv_status == HAD_DRV_CONNECTED) {
+               pr_debug("Device already connected\n");
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+               return retval;
+       }
+       buf_id = intelhaddata->curr_buf;
+       intelhaddata->buff_done = buf_id;
        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);
+       stream_status = had_stream->stream_status;
+       spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
 
-       if (!intelhaddata->stream_info.str_id) {
+       pr_debug("Processing HOT_PLUG, buf_id = %d\n", buf_id);
+       if (stream_status == HAD_INIT) {
+               /* Start sending silence data */
+               pr_debug("Start sending SILENCE\n");
+               caps = HDMI_AUDIO_UNDERRUN | HDMI_AUDIO_BUFFER_DONE;
+               retval = had_set_caps(HAD_SET_ENABLE_AUDIO_INT, &caps);
+               retval = had_set_caps(HAD_SET_ENABLE_AUDIO, NULL);
                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);
+       if (stream_status == HAD_RUNNING_DUMMY) {
+               spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs);
+               had_stream->stream_status = HAD_RUNNING_STREAM;
+               flag_en_allbufs = 1;
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+               cancel_delayed_work(&intelhaddata->dummy_audio);
+       }
+       caps = HDMI_AUDIO_UNDERRUN | HDMI_AUDIO_BUFFER_DONE;
+       retval = had_set_caps(HAD_SET_ENABLE_AUDIO_INT, &caps);
+       retval = had_set_caps(HAD_SET_ENABLE_AUDIO, NULL);
 
        /* 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);
+       had_write_register(AUD_CONFIG, 0);
+       had_write_register(AUD_HDMI_STATUS, 1);
+       had_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));
-       }
+       pr_debug("Enabling Bottom half buffers after _PLUG\n");
+       /* If all buffs already enabled,
+        * No need to enable Top half buffers
+        */
+       if (buf_id == HAD_BUF_TYPE_A)
+               flag_en_allbufs = 0;
+       retval = snd_intelhad_prog_buffer(intelhaddata, buf_id, HAD_BUF_TYPE_D);
+
        if (substream) { /* continue transfer */
                snd_intelhad_init_audio_ctrl(substream,
                                intelhaddata, 0);
                hdmi_audio_mode_change(substream);
        }
-       flag_en_allbufs = 1;
-
+       if (buf_id == HAD_BUF_TYPE_D) { /* Enable all remaining buffs now*/
+               retval = snd_intelhad_prog_buffer(intelhaddata, HAD_BUF_TYPE_A,
+                               (buf_id-1));
+               flag_en_allbufs = 0;
+       }
 
        return retval;
 }
@@ -386,48 +670,38 @@ 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);
+       struct had_pvt_data *had_stream;
+       unsigned long flag_irqs;
 
+       pr_debug("Enter:%s", __func__);
 
+       had_stream = intelhaddata->private_data;
+       buf_id = intelhaddata->curr_buf;
        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);
-               }
+
+       spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs);
+       if (intelhaddata->drv_status == HAD_DRV_DISCONNECTED) {
+               pr_debug("Device already disconnected\n");
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+               return retval;
        } else {
+               /* Disable Audio */
                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);
+               retval = had_set_caps(HAD_SET_DISABLE_AUDIO_INT, &caps);
+               retval = had_set_caps(HAD_SET_DISABLE_AUDIO, NULL);
+               had_read_modify(AUD_CONFIG, 0, BIT(0));
        }
+
        intelhaddata->drv_status = HAD_DRV_DISCONNECTED;
-       mutex_unlock(&intelhaddata->had_lock);
+
+       if (had_stream->stream_status == HAD_RUNNING_STREAM) {
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+               had_start_dummy_playback(intelhaddata);
+       } else {
+               had_stream->stream_status = HAD_INIT;
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+       }
 
        return retval;
 }
@@ -446,17 +720,20 @@ int had_event_handler(enum had_event_type event_type, void *data)
        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;
+       struct had_pvt_data *had_stream;
+       unsigned long flag_irqs;
 
-       spin_lock(&intelhaddata->had_spinlock);
        buf_id = intelhaddata->curr_buf;
-       intelhaddata->buff_done = buf_id;
-       spin_unlock(&intelhaddata->had_spinlock);
-
+       had_stream = intelhaddata->private_data;
+
+       /* Switching to a function can drop atomicity even in INTR context.
+        * Thus, a big lock is acquired to maintain atomicity.
+        * This can be optimized later.
+        * Currently, only buffer_done/_underrun executes in INTR context.
+        * Also, locking is implemented seperately to avoid real contention
+        * of data(struct intelhaddata) between IRQ/SOFT_IRQ/PROCESS context.
+        */
        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);
@@ -476,10 +753,17 @@ int had_event_handler(enum had_event_type event_type, void *data)
 
        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)
+               /* Process only if stream is active & cable Plugged-in */
+               spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs);
+               if (intelhaddata->drv_status >= HAD_DRV_DISCONNECTED) {
+                       spin_unlock_irqrestore(&intelhaddata->had_spinlock,
+                                       flag_irqs);
+                       break;
+               }
+               spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs);
+               if ((had_stream->stream_status == HAD_RUNNING_STREAM)
+                               && substream)
                        retval = hdmi_audio_mode_change(substream);
-               mutex_unlock(&intelhaddata->had_lock);
        break;
 
        default: