diff options
Diffstat (limited to 'sound/x86/intel_hdmi_audio.c')
-rw-r--r-- | sound/x86/intel_hdmi_audio.c | 391 |
1 files changed, 375 insertions, 16 deletions
diff --git a/sound/x86/intel_hdmi_audio.c b/sound/x86/intel_hdmi_audio.c index 1594f826cf31..effe93b58273 100644 --- a/sound/x86/intel_hdmi_audio.c +++ b/sound/x86/intel_hdmi_audio.c @@ -157,8 +157,7 @@ static const struct snd_pcm_hardware snd_intel_hadstream = { }; /* Register access functions */ - -int had_get_hwstate(struct snd_intelhad *intelhaddata) +static int had_get_hwstate(struct snd_intelhad *intelhaddata) { /* Check for device presence -SW state */ if (intelhaddata->drv_status == HAD_DRV_DISCONNECTED) { @@ -182,7 +181,8 @@ mid_hdmi_audio_write(struct snd_intelhad *ctx, u32 reg, u32 val) iowrite32(val, ctx->mmio_start + ctx->had_config_offset + reg); } -int had_read_register(struct snd_intelhad *intelhaddata, u32 offset, u32 *data) +static int had_read_register(struct snd_intelhad *intelhaddata, + u32 offset, u32 *data) { int retval; @@ -203,7 +203,8 @@ static void fixup_dp_config(struct snd_intelhad *intelhaddata, } } -int had_write_register(struct snd_intelhad *intelhaddata, u32 offset, u32 data) +static int had_write_register(struct snd_intelhad *intelhaddata, + u32 offset, u32 data) { int retval; @@ -216,8 +217,8 @@ int had_write_register(struct snd_intelhad *intelhaddata, u32 offset, u32 data) return 0; } -int had_read_modify(struct snd_intelhad *intelhaddata, u32 offset, - u32 data, u32 mask) +static int had_read_modify(struct snd_intelhad *intelhaddata, u32 offset, + u32 data, u32 mask) { u32 val_tmp; int retval; @@ -280,7 +281,7 @@ static int had_read_modify_aud_config_v2(struct snd_intelhad *intelhaddata, return had_read_modify(intelhaddata, AUD_CONFIG, data, mask); } -void snd_intelhad_enable_audio_int(struct snd_intelhad *ctx, bool enable) +static void snd_intelhad_enable_audio_int(struct snd_intelhad *ctx, bool enable) { u32 status_reg; @@ -292,8 +293,8 @@ void snd_intelhad_enable_audio_int(struct snd_intelhad *ctx, bool enable) } } -void snd_intelhad_enable_audio(struct snd_intelhad *intelhaddata, - bool enable) +static void snd_intelhad_enable_audio(struct snd_intelhad *intelhaddata, + bool enable) { had_read_modify_aud_config_v2(intelhaddata, enable ? BIT(0) : 0, BIT(0)); @@ -488,7 +489,7 @@ static int spk_to_chmap(int spk) return 0; } -void had_build_channel_allocation_map(struct snd_intelhad *intelhaddata) +static void had_build_channel_allocation_map(struct snd_intelhad *intelhaddata) { int i = 0, c = 0; int spk_mask = 0; @@ -675,7 +676,7 @@ static void snd_intelhad_prog_dip(struct snd_pcm_substream *substream, * * This function programs ring buffer address and length into registers. */ -int snd_intelhad_prog_buffer(struct snd_intelhad *intelhaddata, +static int snd_intelhad_prog_buffer(struct snd_intelhad *intelhaddata, int start, int end) { u32 ring_buf_addr, ring_buf_size, period_bytes; @@ -732,7 +733,7 @@ int snd_intelhad_prog_buffer(struct snd_intelhad *intelhaddata, return 0; } -int snd_intelhad_read_len(struct snd_intelhad *intelhaddata) +static int snd_intelhad_read_len(struct snd_intelhad *intelhaddata) { int i, retval = 0; u32 len[4]; @@ -939,7 +940,7 @@ static int snd_intelhad_prog_n(u32 aud_samp_freq, u32 *n_param, return 0; } -void snd_intelhad_handle_underrun(struct snd_intelhad *intelhaddata) +static void snd_intelhad_handle_underrun(struct snd_intelhad *intelhaddata) { u32 hdmi_status, i = 0; @@ -1459,8 +1460,364 @@ out: return retval; } -/*PCM operations structure and the calls back for the same */ -struct snd_pcm_ops snd_intelhad_playback_ops = { +/* + * hdmi_lpe_audio_suspend - power management suspend function + * + * @pdev: platform device + * + * This function is called by client driver to suspend the + * hdmi audio. + */ +static int hdmi_lpe_audio_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct had_stream_data *had_stream; + unsigned long flag_irqs; + struct snd_pcm_substream *substream; + struct snd_intelhad *intelhaddata = platform_get_drvdata(pdev); + + pr_debug("Enter:%s\n", __func__); + + had_stream = &intelhaddata->stream_data; + substream = intelhaddata->stream_info.had_substream; + + if (intelhaddata->dev->power.runtime_status != RPM_SUSPENDED) { + pr_err("audio stream is active\n"); + return -EAGAIN; + } + + + 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"); + return 0; + } + + if (intelhaddata->drv_status == HAD_DRV_SUSPENDED) { + spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); + pr_debug("had already suspended\n"); + return 0; + } + + intelhaddata->drv_status = HAD_DRV_SUSPENDED; + pr_debug("%s @ %d:DEBUG PLUG/UNPLUG : HAD_DRV_SUSPENDED\n", + __func__, __LINE__); + + spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); + snd_intelhad_enable_audio_int(intelhaddata, false); + pr_debug("Exit:%s", __func__); + return 0; +} + +/* + * hdmi_lpe_audio_resume - power management resume function + * + *@pdev: platform device + * + * This function is called by client driver to resume the + * hdmi audio. + */ +static int hdmi_lpe_audio_resume(struct platform_device *pdev) +{ + struct snd_intelhad *intelhaddata = platform_get_drvdata(pdev); + unsigned long flag_irqs; + + pr_debug("Enter:%s\n", __func__); + + 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"); + return 0; + } + + if (intelhaddata->drv_status != HAD_DRV_SUSPENDED) { + spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); + pr_err("had is not in suspended state\n"); + return 0; + } + + 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; + pr_debug("%s @ %d:DEBUG PLUG/UNPLUG : HAD_DRV_DISCONNECTED\n", + __func__, __LINE__); + spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); + snd_intelhad_enable_audio_int(intelhaddata, true); + pr_debug("Exit:%s", __func__); + return 0; +} + +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_stream_data *had_stream; + unsigned long flag_irqs; + + had_stream = &intelhaddata->stream_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++) { + int j = i % 4; + + buf_size = intelhaddata->buf_info[j].buf_size; + buf_addr = intelhaddata->buf_info[j].buf_addr; + had_write_register(intelhaddata, + AUD_BUF_A_LENGTH + + (j * HAD_REG_WIDTH), buf_size); + had_write_register(intelhaddata, + AUD_BUF_A_ADDR+(j * 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 int had_process_buffer_done(struct snd_intelhad *intelhaddata) +{ + 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 had_stream_data *had_stream; + int intr_count; + enum had_status_stream stream_type; + unsigned long flag_irqs; + + had_stream = &intelhaddata->stream_data; + stream = &intelhaddata->stream_info; + intr_count = 1; + + spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs); + if (intelhaddata->drv_status == HAD_DRV_DISCONNECTED) { + spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); + pr_err("%s:Device already disconnected\n", __func__); + return 0; + } + buf_id = intelhaddata->curr_buf; + intelhaddata->buff_done = buf_id; + buff_done = intelhaddata->buff_done; + buf_size = intelhaddata->buf_info[buf_id].buf_size; + stream_type = had_stream->stream_type; + + pr_debug("Enter:%s buf_id=%d\n", __func__, buf_id); + + /* 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 (had_stream->stream_type == HAD_RUNNING_STREAM) { + spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); + 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 0; + } + buf_id += (intr_count - 1); + buf_id = buf_id % 4; + spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs); + } + + intelhaddata->buf_info[buf_id].is_valid = true; + if (intelhaddata->valid_buf_cnt-1 == buf_id) { + if (had_stream->stream_type >= HAD_RUNNING_STREAM) + intelhaddata->curr_buf = HAD_BUF_TYPE_A; + } else + intelhaddata->curr_buf = buf_id + 1; + + spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); + + if (had_get_hwstate(intelhaddata)) { + pr_err("HDMI cable plugged-out\n"); + return 0; + } + + /*Reprogram the registers with addr and length*/ + had_write_register(intelhaddata, + AUD_BUF_A_LENGTH + (buf_id * HAD_REG_WIDTH), + buf_size); + had_write_register(intelhaddata, + AUD_BUF_A_ADDR + (buf_id * HAD_REG_WIDTH), + intelhaddata->buf_info[buf_id].buf_addr | + BIT(0) | BIT(1)); + + had_read_register(intelhaddata, + AUD_BUF_A_LENGTH + (buf_id * HAD_REG_WIDTH), + &len); + pr_debug("%s:Enabled buf[%d]\n", __func__, buf_id); + + /* In case of actual data, + * report buffer_done to above ALSA layer + */ + buf_size = intelhaddata->buf_info[buf_id].buf_size; + if (stream_type >= HAD_RUNNING_STREAM) { + intelhaddata->stream_info.buffer_rendered += + (intr_count * buf_size); + stream->period_elapsed(stream->had_substream); + } + + return 0; +} + +static int had_process_buffer_underrun(struct snd_intelhad *intelhaddata) +{ + enum intel_had_aud_buf_type buf_id; + struct pcm_stream_info *stream; + struct had_stream_data *had_stream; + enum had_status_stream stream_type; + unsigned long flag_irqs; + int drv_status; + + had_stream = &intelhaddata->stream_data; + stream = &intelhaddata->stream_info; + + spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs); + buf_id = intelhaddata->curr_buf; + stream_type = had_stream->stream_type; + intelhaddata->buff_done = buf_id; + drv_status = intelhaddata->drv_status; + if (stream_type == HAD_RUNNING_STREAM) + intelhaddata->curr_buf = HAD_BUF_TYPE_A; + + spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); + + pr_debug("Enter:%s buf_id=%d, stream_type=%d\n", + __func__, buf_id, stream_type); + + snd_intelhad_handle_underrun(intelhaddata); + + if (drv_status == HAD_DRV_DISCONNECTED) { + pr_err("%s:Device already disconnected\n", __func__); + return 0; + } + + if (stream_type == HAD_RUNNING_STREAM) { + /* Report UNDERRUN error to above layers */ + intelhaddata->flag_underrun = 1; + stream->period_elapsed(stream->had_substream); + } + + return 0; +} + +static int had_process_hot_plug(struct snd_intelhad *intelhaddata) +{ + enum intel_had_aud_buf_type buf_id; + struct snd_pcm_substream *substream; + struct had_stream_data *had_stream; + unsigned long flag_irqs; + + pr_debug("Enter:%s\n", __func__); + + substream = intelhaddata->stream_info.had_substream; + had_stream = &intelhaddata->stream_data; + + 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 0; + } + buf_id = intelhaddata->curr_buf; + intelhaddata->buff_done = buf_id; + intelhaddata->drv_status = HAD_DRV_CONNECTED; + pr_debug("%s @ %d:DEBUG PLUG/UNPLUG : HAD_DRV_CONNECTED\n", + __func__, __LINE__); + spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); + + pr_debug("Processing HOT_PLUG, buf_id = %d\n", buf_id); + + /* Safety check */ + if (substream) { + pr_debug("There should not be active PB from ALSA\n"); + pr_debug("Signifies, cable is plugged-in even before\n"); + pr_debug("processing snd_pcm_disconnect\n"); + /* Set runtime->state to hw_params done */ + snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + } + + had_build_channel_allocation_map(intelhaddata); + + return 0; +} + +static int had_process_hot_unplug(struct snd_intelhad *intelhaddata) +{ + enum intel_had_aud_buf_type buf_id; + struct had_stream_data *had_stream; + unsigned long flag_irqs; + + pr_debug("Enter:%s\n", __func__); + + had_stream = &intelhaddata->stream_data; + buf_id = intelhaddata->curr_buf; + + 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 0; + + } else { + /* Disable Audio */ + snd_intelhad_enable_audio_int(intelhaddata, false); + snd_intelhad_enable_audio(intelhaddata, false); + } + + intelhaddata->drv_status = HAD_DRV_DISCONNECTED; + pr_debug("%s @ %d:DEBUG PLUG/UNPLUG : HAD_DRV_DISCONNECTED\n", + __func__, __LINE__); + + /* Report to above ALSA layer */ + if (intelhaddata->stream_info.had_substream != NULL) { + spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); + pr_debug("%s: unlock -> sending pcm_stop -> lock\n", __func__); + snd_pcm_stop(intelhaddata->stream_info.had_substream, + SNDRV_PCM_STATE_SETUP); + spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs); + } + + had_stream->stream_type = HAD_INIT; + spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); + kfree(intelhaddata->chmap->chmap); + intelhaddata->chmap->chmap = NULL; + intelhaddata->audio_reg_base = NULL; + pr_debug("%s: unlocked -> returned\n", __func__); + + return 0; +} + +/* PCM operations structure and the calls back for the same */ +static struct snd_pcm_ops snd_intelhad_playback_ops = { .open = snd_intelhad_open, .close = snd_intelhad_close, .ioctl = snd_pcm_lib_ioctl, @@ -1472,7 +1829,7 @@ struct snd_pcm_ops snd_intelhad_playback_ops = { .mmap = snd_intelhad_pcm_mmap, }; -/** +/* * snd_intelhad_pcm_free - to free the memory allocated * * @pcm: pointer to pcm instance @@ -1505,6 +1862,7 @@ static int had_iec958_get(struct snd_kcontrol *kcontrol, (intelhaddata->aes_bits >> 24) & 0xff; return 0; } + static int had_iec958_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -1514,6 +1872,7 @@ static int had_iec958_mask_get(struct snd_kcontrol *kcontrol, ucontrol->value.iec958.status[3] = 0xff; return 0; } + static int had_iec958_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { |