| /* | |
| * Copyright 2012-15 Advanced Micro Devices, Inc. | |
| * | |
| * Permission is hereby granted, free of charge, to any person obtaining a | |
| * copy of this software and associated documentation files (the "Software"), | |
| * to deal in the Software without restriction, including without limitation | |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
| * and/or sell copies of the Software, and to permit persons to whom the | |
| * Software is furnished to do so, subject to the following conditions: | |
| * | |
| * The above copyright notice and this permission notice shall be included in | |
| * all copies or substantial portions of the Software. | |
| * | |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
| * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
| * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
| * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
| * OTHER DEALINGS IN THE SOFTWARE. | |
| * | |
| * Authors: AMD | |
| * | |
| */ | |
| #include "reg_helper.h" | |
| #include "dce_audio.h" | |
| #include "dce/dce_11_0_d.h" | |
| #include "dce/dce_11_0_sh_mask.h" | |
| #define DCE_AUD(audio)\ | |
| container_of(audio, struct dce_audio, base) | |
| #define CTX \ | |
| aud->base.ctx | |
| #define REG(reg)\ | |
| (aud->regs->reg) | |
| #undef FN | |
| #define FN(reg_name, field_name) \ | |
| aud->shifts->field_name, aud->masks->field_name | |
| #define IX_REG(reg)\ | |
| ix ## reg | |
| #define AZ_REG_READ(reg_name) \ | |
| read_indirect_azalia_reg(audio, IX_REG(reg_name)) | |
| #define AZ_REG_WRITE(reg_name, value) \ | |
| write_indirect_azalia_reg(audio, IX_REG(reg_name), value) | |
| static void write_indirect_azalia_reg(struct audio *audio, | |
| uint32_t reg_index, | |
| uint32_t reg_data) | |
| { | |
| struct dce_audio *aud = DCE_AUD(audio); | |
| /* AZALIA_F0_CODEC_ENDPOINT_INDEX endpoint index */ | |
| REG_SET(AZALIA_F0_CODEC_ENDPOINT_INDEX, 0, | |
| AZALIA_ENDPOINT_REG_INDEX, reg_index); | |
| /* AZALIA_F0_CODEC_ENDPOINT_DATA endpoint data */ | |
| REG_SET(AZALIA_F0_CODEC_ENDPOINT_DATA, 0, | |
| AZALIA_ENDPOINT_REG_DATA, reg_data); | |
| dm_logger_write(CTX->logger, LOG_HW_AUDIO, | |
| "AUDIO:write_indirect_azalia_reg: index: %u data: %u\n", | |
| reg_index, reg_data); | |
| } | |
| static uint32_t read_indirect_azalia_reg(struct audio *audio, uint32_t reg_index) | |
| { | |
| struct dce_audio *aud = DCE_AUD(audio); | |
| uint32_t value = 0; | |
| /* AZALIA_F0_CODEC_ENDPOINT_INDEX endpoint index */ | |
| REG_SET(AZALIA_F0_CODEC_ENDPOINT_INDEX, 0, | |
| AZALIA_ENDPOINT_REG_INDEX, reg_index); | |
| /* AZALIA_F0_CODEC_ENDPOINT_DATA endpoint data */ | |
| value = REG_READ(AZALIA_F0_CODEC_ENDPOINT_DATA); | |
| dm_logger_write(CTX->logger, LOG_HW_AUDIO, | |
| "AUDIO:read_indirect_azalia_reg: index: %u data: %u\n", | |
| reg_index, value); | |
| return value; | |
| } | |
| static bool is_audio_format_supported( | |
| const struct audio_info *audio_info, | |
| enum audio_format_code audio_format_code, | |
| uint32_t *format_index) | |
| { | |
| uint32_t index; | |
| uint32_t max_channe_index = 0; | |
| bool found = false; | |
| if (audio_info == NULL) | |
| return found; | |
| /* pass through whole array */ | |
| for (index = 0; index < audio_info->mode_count; index++) { | |
| if (audio_info->modes[index].format_code == audio_format_code) { | |
| if (found) { | |
| /* format has multiply entries, choose one with | |
| * highst number of channels */ | |
| if (audio_info->modes[index].channel_count > | |
| audio_info->modes[max_channe_index].channel_count) { | |
| max_channe_index = index; | |
| } | |
| } else { | |
| /* format found, save it's index */ | |
| found = true; | |
| max_channe_index = index; | |
| } | |
| } | |
| } | |
| /* return index */ | |
| if (found && format_index != NULL) | |
| *format_index = max_channe_index; | |
| return found; | |
| } | |
| /*For HDMI, calculate if specified sample rates can fit into a given timing */ | |
| static void check_audio_bandwidth_hdmi( | |
| const struct audio_crtc_info *crtc_info, | |
| uint32_t channel_count, | |
| union audio_sample_rates *sample_rates) | |
| { | |
| uint32_t samples; | |
| uint32_t h_blank; | |
| bool limit_freq_to_48_khz = false; | |
| bool limit_freq_to_88_2_khz = false; | |
| bool limit_freq_to_96_khz = false; | |
| bool limit_freq_to_174_4_khz = false; | |
| /* For two channels supported return whatever sink support,unmodified*/ | |
| if (channel_count > 2) { | |
| /* Based on HDMI spec 1.3 Table 7.5 */ | |
| if ((crtc_info->requested_pixel_clock <= 27000) && | |
| (crtc_info->v_active <= 576) && | |
| !(crtc_info->interlaced) && | |
| !(crtc_info->pixel_repetition == 2 || | |
| crtc_info->pixel_repetition == 4)) { | |
| limit_freq_to_48_khz = true; | |
| } else if ((crtc_info->requested_pixel_clock <= 27000) && | |
| (crtc_info->v_active <= 576) && | |
| (crtc_info->interlaced) && | |
| (crtc_info->pixel_repetition == 2)) { | |
| limit_freq_to_88_2_khz = true; | |
| } else if ((crtc_info->requested_pixel_clock <= 54000) && | |
| (crtc_info->v_active <= 576) && | |
| !(crtc_info->interlaced)) { | |
| limit_freq_to_174_4_khz = true; | |
| } | |
| } | |
| /* Also do some calculation for the available Audio Bandwidth for the | |
| * 8 ch (i.e. for the Layout 1 => ch > 2) | |
| */ | |
| h_blank = crtc_info->h_total - crtc_info->h_active; | |
| if (crtc_info->pixel_repetition) | |
| h_blank *= crtc_info->pixel_repetition; | |
| /*based on HDMI spec 1.3 Table 7.5 */ | |
| h_blank -= 58; | |
| /*for Control Period */ | |
| h_blank -= 16; | |
| samples = h_blank * 10; | |
| /* Number of Audio Packets (multiplied by 10) per Line (for 8 ch number | |
| * of Audio samples per line multiplied by 10 - Layout 1) | |
| */ | |
| samples /= 32; | |
| samples *= crtc_info->v_active; | |
| /*Number of samples multiplied by 10, per second */ | |
| samples *= crtc_info->refresh_rate; | |
| /*Number of Audio samples per second */ | |
| samples /= 10; | |
| /* @todo do it after deep color is implemented | |
| * 8xx - deep color bandwidth scaling | |
| * Extra bandwidth is avaliable in deep color b/c link runs faster than | |
| * pixel rate. This has the effect of allowing more tmds characters to | |
| * be transmitted during blank | |
| */ | |
| switch (crtc_info->color_depth) { | |
| case COLOR_DEPTH_888: | |
| samples *= 4; | |
| break; | |
| case COLOR_DEPTH_101010: | |
| samples *= 5; | |
| break; | |
| case COLOR_DEPTH_121212: | |
| samples *= 6; | |
| break; | |
| default: | |
| samples *= 4; | |
| break; | |
| } | |
| samples /= 4; | |
| /*check limitation*/ | |
| if (samples < 88200) | |
| limit_freq_to_48_khz = true; | |
| else if (samples < 96000) | |
| limit_freq_to_88_2_khz = true; | |
| else if (samples < 176400) | |
| limit_freq_to_96_khz = true; | |
| else if (samples < 192000) | |
| limit_freq_to_174_4_khz = true; | |
| if (sample_rates != NULL) { | |
| /* limit frequencies */ | |
| if (limit_freq_to_174_4_khz) | |
| sample_rates->rate.RATE_192 = 0; | |
| if (limit_freq_to_96_khz) { | |
| sample_rates->rate.RATE_192 = 0; | |
| sample_rates->rate.RATE_176_4 = 0; | |
| } | |
| if (limit_freq_to_88_2_khz) { | |
| sample_rates->rate.RATE_192 = 0; | |
| sample_rates->rate.RATE_176_4 = 0; | |
| sample_rates->rate.RATE_96 = 0; | |
| } | |
| if (limit_freq_to_48_khz) { | |
| sample_rates->rate.RATE_192 = 0; | |
| sample_rates->rate.RATE_176_4 = 0; | |
| sample_rates->rate.RATE_96 = 0; | |
| sample_rates->rate.RATE_88_2 = 0; | |
| } | |
| } | |
| } | |
| /*For DP SST, calculate if specified sample rates can fit into a given timing */ | |
| static void check_audio_bandwidth_dpsst( | |
| const struct audio_crtc_info *crtc_info, | |
| uint32_t channel_count, | |
| union audio_sample_rates *sample_rates) | |
| { | |
| /* do nothing */ | |
| } | |
| /*For DP MST, calculate if specified sample rates can fit into a given timing */ | |
| static void check_audio_bandwidth_dpmst( | |
| const struct audio_crtc_info *crtc_info, | |
| uint32_t channel_count, | |
| union audio_sample_rates *sample_rates) | |
| { | |
| /* do nothing */ | |
| } | |
| static void check_audio_bandwidth( | |
| const struct audio_crtc_info *crtc_info, | |
| uint32_t channel_count, | |
| enum signal_type signal, | |
| union audio_sample_rates *sample_rates) | |
| { | |
| switch (signal) { | |
| case SIGNAL_TYPE_HDMI_TYPE_A: | |
| check_audio_bandwidth_hdmi( | |
| crtc_info, channel_count, sample_rates); | |
| break; | |
| case SIGNAL_TYPE_EDP: | |
| case SIGNAL_TYPE_DISPLAY_PORT: | |
| check_audio_bandwidth_dpsst( | |
| crtc_info, channel_count, sample_rates); | |
| break; | |
| case SIGNAL_TYPE_DISPLAY_PORT_MST: | |
| check_audio_bandwidth_dpmst( | |
| crtc_info, channel_count, sample_rates); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| /* expose/not expose HBR capability to Audio driver */ | |
| static void set_high_bit_rate_capable( | |
| struct audio *audio, | |
| bool capable) | |
| { | |
| uint32_t value = 0; | |
| /* set high bit rate audio capable*/ | |
| value = AZ_REG_READ(AZALIA_F0_CODEC_PIN_CONTROL_RESPONSE_HBR); | |
| set_reg_field_value(value, capable, | |
| AZALIA_F0_CODEC_PIN_CONTROL_RESPONSE_HBR, | |
| HBR_CAPABLE); | |
| AZ_REG_WRITE(AZALIA_F0_CODEC_PIN_CONTROL_RESPONSE_HBR, value); | |
| } | |
| /* set video latency in in ms/2+1 */ | |
| static void set_video_latency( | |
| struct audio *audio, | |
| int latency_in_ms) | |
| { | |
| uint32_t value = 0; | |
| if ((latency_in_ms < 0) || (latency_in_ms > 255)) | |
| return; | |
| value = AZ_REG_READ(AZALIA_F0_CODEC_PIN_CONTROL_RESPONSE_LIPSYNC); | |
| set_reg_field_value(value, latency_in_ms, | |
| AZALIA_F0_CODEC_PIN_CONTROL_RESPONSE_LIPSYNC, | |
| VIDEO_LIPSYNC); | |
| AZ_REG_WRITE(AZALIA_F0_CODEC_PIN_CONTROL_RESPONSE_LIPSYNC, | |
| value); | |
| } | |
| /* set audio latency in in ms/2+1 */ | |
| static void set_audio_latency( | |
| struct audio *audio, | |
| int latency_in_ms) | |
| { | |
| uint32_t value = 0; | |
| if (latency_in_ms < 0) | |
| latency_in_ms = 0; | |
| if (latency_in_ms > 255) | |
| latency_in_ms = 255; | |
| value = AZ_REG_READ(AZALIA_F0_CODEC_PIN_CONTROL_RESPONSE_LIPSYNC); | |
| set_reg_field_value(value, latency_in_ms, | |
| AZALIA_F0_CODEC_PIN_CONTROL_RESPONSE_LIPSYNC, | |
| AUDIO_LIPSYNC); | |
| AZ_REG_WRITE(AZALIA_F0_CODEC_PIN_CONTROL_RESPONSE_LIPSYNC, | |
| value); | |
| } | |
| void dce_aud_az_enable(struct audio *audio) | |
| { | |
| struct dce_audio *aud = DCE_AUD(audio); | |
| uint32_t value = AZ_REG_READ(AZALIA_F0_CODEC_PIN_CONTROL_HOT_PLUG_CONTROL); | |
| set_reg_field_value(value, 1, | |
| AZALIA_F0_CODEC_PIN_CONTROL_HOT_PLUG_CONTROL, | |
| CLOCK_GATING_DISABLE); | |
| set_reg_field_value(value, 1, | |
| AZALIA_F0_CODEC_PIN_CONTROL_HOT_PLUG_CONTROL, | |
| AUDIO_ENABLED); | |
| AZ_REG_WRITE(AZALIA_F0_CODEC_PIN_CONTROL_HOT_PLUG_CONTROL, value); | |
| value = AZ_REG_READ(AZALIA_F0_CODEC_PIN_CONTROL_HOT_PLUG_CONTROL); | |
| dm_logger_write(CTX->logger, LOG_HW_AUDIO, | |
| "\n\t========= AUDIO:dce_aud_az_enable: index: %u data: 0x%x\n", | |
| audio->inst, value); | |
| } | |
| void dce_aud_az_disable(struct audio *audio) | |
| { | |
| uint32_t value; | |
| struct dce_audio *aud = DCE_AUD(audio); | |
| value = AZ_REG_READ(AZALIA_F0_CODEC_PIN_CONTROL_HOT_PLUG_CONTROL); | |
| set_reg_field_value(value, 0, | |
| AZALIA_F0_CODEC_PIN_CONTROL_HOT_PLUG_CONTROL, | |
| AUDIO_ENABLED); | |
| AZ_REG_WRITE(AZALIA_F0_CODEC_PIN_CONTROL_HOT_PLUG_CONTROL, value); | |
| set_reg_field_value(value, 0, | |
| AZALIA_F0_CODEC_PIN_CONTROL_HOT_PLUG_CONTROL, | |
| CLOCK_GATING_DISABLE); | |
| AZ_REG_WRITE(AZALIA_F0_CODEC_PIN_CONTROL_HOT_PLUG_CONTROL, value); | |
| value = AZ_REG_READ(AZALIA_F0_CODEC_PIN_CONTROL_HOT_PLUG_CONTROL); | |
| dm_logger_write(CTX->logger, LOG_HW_AUDIO, | |
| "\n\t========= AUDIO:dce_aud_az_disable: index: %u data: 0x%x\n", | |
| audio->inst, value); | |
| } | |
| void dce_aud_az_configure( | |
| struct audio *audio, | |
| enum signal_type signal, | |
| const struct audio_crtc_info *crtc_info, | |
| const struct audio_info *audio_info) | |
| { | |
| struct dce_audio *aud = DCE_AUD(audio); | |
| uint32_t speakers = audio_info->flags.info.ALLSPEAKERS; | |
| uint32_t value; | |
| uint32_t field = 0; | |
| enum audio_format_code audio_format_code; | |
| uint32_t format_index; | |
| uint32_t index; | |
| bool is_ac3_supported = false; | |
| union audio_sample_rates sample_rate; | |
| uint32_t strlen = 0; | |
| value = AZ_REG_READ(AZALIA_F0_CODEC_PIN_CONTROL_HOT_PLUG_CONTROL); | |
| set_reg_field_value(value, 1, | |
| AZALIA_F0_CODEC_PIN_CONTROL_HOT_PLUG_CONTROL, | |
| CLOCK_GATING_DISABLE); | |
| AZ_REG_WRITE(AZALIA_F0_CODEC_PIN_CONTROL_HOT_PLUG_CONTROL, value); | |
| /* Speaker Allocation */ | |
| /* | |
| uint32_t value; | |
| uint32_t field = 0;*/ | |
| value = AZ_REG_READ(AZALIA_F0_CODEC_PIN_CONTROL_CHANNEL_SPEAKER); | |
| set_reg_field_value(value, | |
| speakers, | |
| AZALIA_F0_CODEC_PIN_CONTROL_CHANNEL_SPEAKER, | |
| SPEAKER_ALLOCATION); | |
| /* LFE_PLAYBACK_LEVEL = LFEPBL | |
| * LFEPBL = 0 : Unknown or refer to other information | |
| * LFEPBL = 1 : 0dB playback | |
| * LFEPBL = 2 : +10dB playback | |
| * LFE_BL = 3 : Reserved | |
| */ | |
| set_reg_field_value(value, | |
| 0, | |
| AZALIA_F0_CODEC_PIN_CONTROL_CHANNEL_SPEAKER, | |
| LFE_PLAYBACK_LEVEL); | |
| /* todo: according to reg spec LFE_PLAYBACK_LEVEL is read only. | |
| * why are we writing to it? DCE8 does not write this */ | |
| set_reg_field_value(value, | |
| 0, | |
| AZALIA_F0_CODEC_PIN_CONTROL_CHANNEL_SPEAKER, | |
| HDMI_CONNECTION); | |
| set_reg_field_value(value, | |
| 0, | |
| AZALIA_F0_CODEC_PIN_CONTROL_CHANNEL_SPEAKER, | |
| DP_CONNECTION); | |
| field = get_reg_field_value(value, | |
| AZALIA_F0_CODEC_PIN_CONTROL_CHANNEL_SPEAKER, | |
| EXTRA_CONNECTION_INFO); | |
| field &= ~0x1; | |
| set_reg_field_value(value, | |
| field, | |
| AZALIA_F0_CODEC_PIN_CONTROL_CHANNEL_SPEAKER, | |
| EXTRA_CONNECTION_INFO); | |
| /* set audio for output signal */ | |
| switch (signal) { | |
| case SIGNAL_TYPE_HDMI_TYPE_A: | |
| set_reg_field_value(value, | |
| 1, | |
| AZALIA_F0_CODEC_PIN_CONTROL_CHANNEL_SPEAKER, | |
| HDMI_CONNECTION); | |
| break; | |
| case SIGNAL_TYPE_EDP: | |
| case SIGNAL_TYPE_DISPLAY_PORT: | |
| case SIGNAL_TYPE_DISPLAY_PORT_MST: | |
| set_reg_field_value(value, | |
| 1, | |
| AZALIA_F0_CODEC_PIN_CONTROL_CHANNEL_SPEAKER, | |
| DP_CONNECTION); | |
| break; | |
| default: | |
| BREAK_TO_DEBUGGER(); | |
| break; | |
| } | |
| AZ_REG_WRITE(AZALIA_F0_CODEC_PIN_CONTROL_CHANNEL_SPEAKER, value); | |
| /* Audio Descriptors */ | |
| /* pass through all formats */ | |
| for (format_index = 0; format_index < AUDIO_FORMAT_CODE_COUNT; | |
| format_index++) { | |
| audio_format_code = | |
| (AUDIO_FORMAT_CODE_FIRST + format_index); | |
| /* those are unsupported, skip programming */ | |
| if (audio_format_code == AUDIO_FORMAT_CODE_1BITAUDIO || | |
| audio_format_code == AUDIO_FORMAT_CODE_DST) | |
| continue; | |
| value = 0; | |
| /* check if supported */ | |
| if (is_audio_format_supported( | |
| audio_info, audio_format_code, &index)) { | |
| const struct audio_mode *audio_mode = | |
| &audio_info->modes[index]; | |
| union audio_sample_rates sample_rates = | |
| audio_mode->sample_rates; | |
| uint8_t byte2 = audio_mode->max_bit_rate; | |
| /* adjust specific properties */ | |
| switch (audio_format_code) { | |
| case AUDIO_FORMAT_CODE_LINEARPCM: { | |
| check_audio_bandwidth( | |
| crtc_info, | |
| audio_mode->channel_count, | |
| signal, | |
| &sample_rates); | |
| byte2 = audio_mode->sample_size; | |
| set_reg_field_value(value, | |
| sample_rates.all, | |
| AZALIA_F0_CODEC_PIN_CONTROL_AUDIO_DESCRIPTOR0, | |
| SUPPORTED_FREQUENCIES_STEREO); | |
| } | |
| break; | |
| case AUDIO_FORMAT_CODE_AC3: | |
| is_ac3_supported = true; | |
| break; | |
| case AUDIO_FORMAT_CODE_DOLBYDIGITALPLUS: | |
| case AUDIO_FORMAT_CODE_DTS_HD: | |
| case AUDIO_FORMAT_CODE_MAT_MLP: | |
| case AUDIO_FORMAT_CODE_DST: | |
| case AUDIO_FORMAT_CODE_WMAPRO: | |
| byte2 = audio_mode->vendor_specific; | |
| break; | |
| default: | |
| break; | |
| } | |
| /* fill audio format data */ | |
| set_reg_field_value(value, | |
| audio_mode->channel_count - 1, | |
| AZALIA_F0_CODEC_PIN_CONTROL_AUDIO_DESCRIPTOR0, | |
| MAX_CHANNELS); | |
| set_reg_field_value(value, | |
| sample_rates.all, | |
| AZALIA_F0_CODEC_PIN_CONTROL_AUDIO_DESCRIPTOR0, | |
| SUPPORTED_FREQUENCIES); | |
| set_reg_field_value(value, | |
| byte2, | |
| AZALIA_F0_CODEC_PIN_CONTROL_AUDIO_DESCRIPTOR0, | |
| DESCRIPTOR_BYTE_2); | |
| } /* if */ | |
| AZ_REG_WRITE( | |
| AZALIA_F0_CODEC_PIN_CONTROL_AUDIO_DESCRIPTOR0 + format_index, | |
| value); | |
| } /* for */ | |
| if (is_ac3_supported) | |
| /* todo: this reg global. why program global register? */ | |
| REG_WRITE(AZALIA_F0_CODEC_FUNCTION_PARAMETER_STREAM_FORMATS, | |
| 0x05); | |
| /* check for 192khz/8-Ch support for HBR requirements */ | |
| sample_rate.all = 0; | |
| sample_rate.rate.RATE_192 = 1; | |
| check_audio_bandwidth( | |
| crtc_info, | |
| 8, | |
| signal, | |
| &sample_rate); | |
| set_high_bit_rate_capable(audio, sample_rate.rate.RATE_192); | |
| /* Audio and Video Lipsync */ | |
| set_video_latency(audio, audio_info->video_latency); | |
| set_audio_latency(audio, audio_info->audio_latency); | |
| value = 0; | |
| set_reg_field_value(value, audio_info->manufacture_id, | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO0, | |
| MANUFACTURER_ID); | |
| set_reg_field_value(value, audio_info->product_id, | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO0, | |
| PRODUCT_ID); | |
| AZ_REG_WRITE(AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO0, | |
| value); | |
| value = 0; | |
| /*get display name string length */ | |
| while (audio_info->display_name[strlen++] != '\0') { | |
| if (strlen >= | |
| MAX_HW_AUDIO_INFO_DISPLAY_NAME_SIZE_IN_CHARS) | |
| break; | |
| } | |
| set_reg_field_value(value, strlen, | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO1, | |
| SINK_DESCRIPTION_LEN); | |
| AZ_REG_WRITE(AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO1, | |
| value); | |
| /* | |
| *write the port ID: | |
| *PORT_ID0 = display index | |
| *PORT_ID1 = 16bit BDF | |
| *(format MSB->LSB: 8bit Bus, 5bit Device, 3bit Function) | |
| */ | |
| value = 0; | |
| set_reg_field_value(value, audio_info->port_id[0], | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO2, | |
| PORT_ID0); | |
| AZ_REG_WRITE(AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO2, value); | |
| value = 0; | |
| set_reg_field_value(value, audio_info->port_id[1], | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO3, | |
| PORT_ID1); | |
| AZ_REG_WRITE(AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO3, value); | |
| /*write the 18 char monitor string */ | |
| value = 0; | |
| set_reg_field_value(value, audio_info->display_name[0], | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO4, | |
| DESCRIPTION0); | |
| set_reg_field_value(value, audio_info->display_name[1], | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO4, | |
| DESCRIPTION1); | |
| set_reg_field_value(value, audio_info->display_name[2], | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO4, | |
| DESCRIPTION2); | |
| set_reg_field_value(value, audio_info->display_name[3], | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO4, | |
| DESCRIPTION3); | |
| AZ_REG_WRITE(AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO4, value); | |
| value = 0; | |
| set_reg_field_value(value, audio_info->display_name[4], | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO5, | |
| DESCRIPTION4); | |
| set_reg_field_value(value, audio_info->display_name[5], | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO5, | |
| DESCRIPTION5); | |
| set_reg_field_value(value, audio_info->display_name[6], | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO5, | |
| DESCRIPTION6); | |
| set_reg_field_value(value, audio_info->display_name[7], | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO5, | |
| DESCRIPTION7); | |
| AZ_REG_WRITE(AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO5, value); | |
| value = 0; | |
| set_reg_field_value(value, audio_info->display_name[8], | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO6, | |
| DESCRIPTION8); | |
| set_reg_field_value(value, audio_info->display_name[9], | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO6, | |
| DESCRIPTION9); | |
| set_reg_field_value(value, audio_info->display_name[10], | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO6, | |
| DESCRIPTION10); | |
| set_reg_field_value(value, audio_info->display_name[11], | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO6, | |
| DESCRIPTION11); | |
| AZ_REG_WRITE(AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO6, value); | |
| value = 0; | |
| set_reg_field_value(value, audio_info->display_name[12], | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO7, | |
| DESCRIPTION12); | |
| set_reg_field_value(value, audio_info->display_name[13], | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO7, | |
| DESCRIPTION13); | |
| set_reg_field_value(value, audio_info->display_name[14], | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO7, | |
| DESCRIPTION14); | |
| set_reg_field_value(value, audio_info->display_name[15], | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO7, | |
| DESCRIPTION15); | |
| AZ_REG_WRITE(AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO7, value); | |
| value = 0; | |
| set_reg_field_value(value, audio_info->display_name[16], | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO8, | |
| DESCRIPTION16); | |
| set_reg_field_value(value, audio_info->display_name[17], | |
| AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO8, | |
| DESCRIPTION17); | |
| AZ_REG_WRITE(AZALIA_F0_CODEC_PIN_CONTROL_SINK_INFO8, value); | |
| } | |
| /* | |
| * todo: wall clk related functionality probably belong to clock_src. | |
| */ | |
| /* search pixel clock value for Azalia HDMI Audio */ | |
| static void get_azalia_clock_info_hdmi( | |
| uint32_t crtc_pixel_clock_in_khz, | |
| uint32_t actual_pixel_clock_in_khz, | |
| struct azalia_clock_info *azalia_clock_info) | |
| { | |
| /* audio_dto_phase= 24 * 10,000; | |
| * 24MHz in [100Hz] units */ | |
| azalia_clock_info->audio_dto_phase = | |
| 24 * 10000; | |
| /* audio_dto_module = PCLKFrequency * 10,000; | |
| * [khz] -> [100Hz] */ | |
| azalia_clock_info->audio_dto_module = | |
| actual_pixel_clock_in_khz * 10; | |
| } | |
| static void get_azalia_clock_info_dp( | |
| uint32_t requested_pixel_clock_in_khz, | |
| const struct audio_pll_info *pll_info, | |
| struct azalia_clock_info *azalia_clock_info) | |
| { | |
| /* Reported dpDtoSourceClockInkhz value for | |
| * DCE8 already adjusted for SS, do not need any | |
| * adjustment here anymore | |
| */ | |
| /*audio_dto_phase = 24 * 10,000; | |
| * 24MHz in [100Hz] units */ | |
| azalia_clock_info->audio_dto_phase = 24 * 10000; | |
| /*audio_dto_module = dpDtoSourceClockInkhz * 10,000; | |
| * [khz] ->[100Hz] */ | |
| azalia_clock_info->audio_dto_module = | |
| pll_info->dp_dto_source_clock_in_khz * 10; | |
| } | |
| void dce_aud_wall_dto_setup( | |
| struct audio *audio, | |
| enum signal_type signal, | |
| const struct audio_crtc_info *crtc_info, | |
| const struct audio_pll_info *pll_info) | |
| { | |
| struct dce_audio *aud = DCE_AUD(audio); | |
| struct azalia_clock_info clock_info = { 0 }; | |
| if (dc_is_hdmi_signal(signal)) { | |
| uint32_t src_sel; | |
| /*DTO0 Programming goal: | |
| -generate 24MHz, 128*Fs from 24MHz | |
| -use DTO0 when an active HDMI port is connected | |
| (optionally a DP is connected) */ | |
| /* calculate DTO settings */ | |
| get_azalia_clock_info_hdmi( | |
| crtc_info->requested_pixel_clock, | |
| crtc_info->calculated_pixel_clock, | |
| &clock_info); | |
| dm_logger_write(audio->ctx->logger, LOG_HW_AUDIO,\ | |
| "\n%s:Input::requested_pixel_clock = %d"\ | |
| "calculated_pixel_clock =%d\n"\ | |
| "audio_dto_module = %d audio_dto_phase =%d \n\n", __func__,\ | |
| crtc_info->requested_pixel_clock,\ | |
| crtc_info->calculated_pixel_clock,\ | |
| clock_info.audio_dto_module,\ | |
| clock_info.audio_dto_phase); | |
| /* On TN/SI, Program DTO source select and DTO select before | |
| programming DTO modulo and DTO phase. These bits must be | |
| programmed first, otherwise there will be no HDMI audio at boot | |
| up. This is a HW sequence change (different from old ASICs). | |
| Caution when changing this programming sequence. | |
| HDMI enabled, using DTO0 | |
| program master CRTC for DTO0 */ | |
| src_sel = pll_info->dto_source - DTO_SOURCE_ID0; | |
| REG_UPDATE_2(DCCG_AUDIO_DTO_SOURCE, | |
| DCCG_AUDIO_DTO0_SOURCE_SEL, src_sel, | |
| DCCG_AUDIO_DTO_SEL, 0); | |
| /* module */ | |
| REG_UPDATE(DCCG_AUDIO_DTO0_MODULE, | |
| DCCG_AUDIO_DTO0_MODULE, clock_info.audio_dto_module); | |
| /* phase */ | |
| REG_UPDATE(DCCG_AUDIO_DTO0_PHASE, | |
| DCCG_AUDIO_DTO0_PHASE, clock_info.audio_dto_phase); | |
| } else { | |
| /*DTO1 Programming goal: | |
| -generate 24MHz, 512*Fs, 128*Fs from 24MHz | |
| -default is to used DTO1, and switch to DTO0 when an audio | |
| master HDMI port is connected | |
| -use as default for DP | |
| calculate DTO settings */ | |
| get_azalia_clock_info_dp( | |
| crtc_info->requested_pixel_clock, | |
| pll_info, | |
| &clock_info); | |
| /* Program DTO select before programming DTO modulo and DTO | |
| phase. default to use DTO1 */ | |
| REG_UPDATE(DCCG_AUDIO_DTO_SOURCE, | |
| DCCG_AUDIO_DTO_SEL, 1); | |
| REG_UPDATE(DCCG_AUDIO_DTO_SOURCE, | |
| DCCG_AUDIO_DTO_SEL, 1); | |
| /* DCCG_AUDIO_DTO2_USE_512FBR_DTO, 1) | |
| * Select 512fs for DP TODO: web register definition | |
| * does not match register header file | |
| * DCE11 version it's commented out while DCE8 it's set to 1 | |
| */ | |
| /* module */ | |
| REG_UPDATE(DCCG_AUDIO_DTO1_MODULE, | |
| DCCG_AUDIO_DTO1_MODULE, clock_info.audio_dto_module); | |
| /* phase */ | |
| REG_UPDATE(DCCG_AUDIO_DTO1_PHASE, | |
| DCCG_AUDIO_DTO1_PHASE, clock_info.audio_dto_phase); | |
| REG_UPDATE(DCCG_AUDIO_DTO_SOURCE, | |
| DCCG_AUDIO_DTO2_USE_512FBR_DTO, 1); | |
| } | |
| } | |
| static bool dce_aud_endpoint_valid(struct audio *audio) | |
| { | |
| uint32_t value; | |
| uint32_t port_connectivity; | |
| value = AZ_REG_READ( | |
| AZALIA_F0_CODEC_PIN_CONTROL_RESPONSE_CONFIGURATION_DEFAULT); | |
| port_connectivity = get_reg_field_value(value, | |
| AZALIA_F0_CODEC_PIN_CONTROL_RESPONSE_CONFIGURATION_DEFAULT, | |
| PORT_CONNECTIVITY); | |
| return !(port_connectivity == 1); | |
| } | |
| /* initialize HW state */ | |
| void dce_aud_hw_init( | |
| struct audio *audio) | |
| { | |
| uint32_t value; | |
| struct dce_audio *aud = DCE_AUD(audio); | |
| /* we only need to program the following registers once, so we only do | |
| it for the inst 0*/ | |
| if (audio->inst != 0) | |
| return; | |
| /* Suport R5 - 32khz | |
| * Suport R6 - 44.1khz | |
| * Suport R7 - 48khz | |
| */ | |
| /*disable clock gating before write to endpoint register*/ | |
| value = AZ_REG_READ(AZALIA_F0_CODEC_PIN_CONTROL_HOT_PLUG_CONTROL); | |
| set_reg_field_value(value, 1, | |
| AZALIA_F0_CODEC_PIN_CONTROL_HOT_PLUG_CONTROL, | |
| CLOCK_GATING_DISABLE); | |
| AZ_REG_WRITE(AZALIA_F0_CODEC_PIN_CONTROL_HOT_PLUG_CONTROL, value); | |
| REG_UPDATE(AZALIA_F0_CODEC_FUNCTION_PARAMETER_SUPPORTED_SIZE_RATES, | |
| AUDIO_RATE_CAPABILITIES, 0x70); | |
| /*Keep alive bit to verify HW block in BU. */ | |
| REG_UPDATE_2(AZALIA_F0_CODEC_FUNCTION_PARAMETER_POWER_STATES, | |
| CLKSTOP, 1, | |
| EPSS, 1); | |
| } | |
| static const struct audio_funcs funcs = { | |
| .endpoint_valid = dce_aud_endpoint_valid, | |
| .hw_init = dce_aud_hw_init, | |
| .wall_dto_setup = dce_aud_wall_dto_setup, | |
| .az_enable = dce_aud_az_enable, | |
| .az_disable = dce_aud_az_disable, | |
| .az_configure = dce_aud_az_configure, | |
| .destroy = dce_aud_destroy, | |
| }; | |
| void dce_aud_destroy(struct audio **audio) | |
| { | |
| struct dce_audio *aud = DCE_AUD(*audio); | |
| kfree(aud); | |
| *audio = NULL; | |
| } | |
| struct audio *dce_audio_create( | |
| struct dc_context *ctx, | |
| unsigned int inst, | |
| const struct dce_audio_registers *reg, | |
| const struct dce_audio_shift *shifts, | |
| const struct dce_aduio_mask *masks | |
| ) | |
| { | |
| struct dce_audio *audio = kzalloc(sizeof(*audio), GFP_KERNEL); | |
| if (audio == NULL) { | |
| ASSERT_CRITICAL(audio); | |
| return NULL; | |
| } | |
| audio->base.ctx = ctx; | |
| audio->base.inst = inst; | |
| audio->base.funcs = &funcs; | |
| audio->regs = reg; | |
| audio->shifts = shifts; | |
| audio->masks = masks; | |
| return &audio->base; | |
| } | |