| /* |
| * 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 <linux/slab.h> |
| |
| #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 DC_LOGGER_INIT() |
| |
| #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); |
| |
| DC_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); |
| |
| DC_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; |
| if (!crtc_info) |
| return; |
| |
| /* 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_100Hz <= 270000) && |
| (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_100Hz <= 270000) && |
| (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_100Hz <= 540000) && |
| (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) |
| { |
| uint32_t value = AZ_REG_READ(AZALIA_F0_CODEC_PIN_CONTROL_HOT_PLUG_CONTROL); |
| DC_LOGGER_INIT(); |
| |
| 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); |
| 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); |
| |
| DC_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; |
| DC_LOGGER_INIT(); |
| |
| 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); |
| |
| 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); |
| DC_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); |
| DC_LOG_HW_AUDIO("\n\tAUDIO:az_configure: index: %u data, 0x%x, displayName %s: \n", |
| audio->inst, value, audio_info->display_name); |
| |
| /* |
| *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); |
| 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, |
| CLOCK_GATING_DISABLE); |
| AZ_REG_WRITE(AZALIA_F0_CODEC_PIN_CONTROL_HOT_PLUG_CONTROL, 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_100hz, |
| uint32_t actual_pixel_clock_100Hz, |
| 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_100Hz; |
| } |
| |
| static void get_azalia_clock_info_dp( |
| uint32_t requested_pixel_clock_100Hz, |
| 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_tmds_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_100Hz, |
| crtc_info->calculated_pixel_clock_100Hz, |
| &clock_info); |
| |
| DC_LOG_HW_AUDIO("\n%s:Input::requested_pixel_clock_100Hz = %d"\ |
| "calculated_pixel_clock_100Hz =%d\n"\ |
| "audio_dto_module = %d audio_dto_phase =%d \n\n", __func__,\ |
| crtc_info->requested_pixel_clock_100Hz,\ |
| crtc_info->calculated_pixel_clock_100Hz,\ |
| 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_100Hz, |
| 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); |
| |
| /* 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); |
| |
| } |
| } |
| |
| #if defined(CONFIG_DRM_AMD_DC_SI) |
| static void dce60_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_100Hz, |
| crtc_info->calculated_pixel_clock_100Hz, |
| &clock_info); |
| |
| DC_LOG_HW_AUDIO("\n%s:Input::requested_pixel_clock_100Hz = %d"\ |
| "calculated_pixel_clock_100Hz =%d\n"\ |
| "audio_dto_module = %d audio_dto_phase =%d \n\n", __func__,\ |
| crtc_info->requested_pixel_clock_100Hz,\ |
| crtc_info->calculated_pixel_clock_100Hz,\ |
| 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, 128*Fs from 24MHz (DCE6 does not support 512*Fs) |
| -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_100Hz, |
| 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); |
| |
| /* DCCG_AUDIO_DTO2_USE_512FBR_DTO, 1) |
| * Cannot select 512fs for DP |
| * |
| * DCE6 has no DCCG_AUDIO_DTO2_USE_512FBR_DTO mask |
| */ |
| |
| /* 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); |
| |
| /* DCE6 has no DCCG_AUDIO_DTO2_USE_512FBR_DTO mask in DCCG_AUDIO_DTO_SOURCE reg */ |
| |
| } |
| } |
| #endif |
| |
| 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); |
| 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); |
| } |
| |
| 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, |
| }; |
| |
| #if defined(CONFIG_DRM_AMD_DC_SI) |
| static const struct audio_funcs dce60_funcs = { |
| .endpoint_valid = dce_aud_endpoint_valid, |
| .hw_init = dce_aud_hw_init, |
| .wall_dto_setup = dce60_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, |
| }; |
| #endif |
| |
| 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_audio_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; |
| } |
| |
| #if defined(CONFIG_DRM_AMD_DC_SI) |
| struct audio *dce60_audio_create( |
| struct dc_context *ctx, |
| unsigned int inst, |
| const struct dce_audio_registers *reg, |
| const struct dce_audio_shift *shifts, |
| const struct dce_audio_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 = &dce60_funcs; |
| |
| audio->regs = reg; |
| audio->shifts = shifts; |
| audio->masks = masks; |
| return &audio->base; |
| } |
| #endif |