| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * dw-hdmi-i2s-audio.c |
|---|
| 3 | 4 | * |
|---|
| 4 | 5 | * Copyright (c) 2017 Renesas Solutions Corp. |
|---|
| 5 | 6 | * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> |
|---|
| 6 | | - * |
|---|
| 7 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 8 | | - * it under the terms of the GNU General Public License version 2 as |
|---|
| 9 | | - * published by the Free Software Foundation. |
|---|
| 10 | 7 | */ |
|---|
| 8 | + |
|---|
| 9 | +#include <linux/dma-mapping.h> |
|---|
| 10 | +#include <linux/module.h> |
|---|
| 11 | + |
|---|
| 11 | 12 | #include <drm/bridge/dw_hdmi.h> |
|---|
| 13 | +#include <drm/drm_crtc.h> |
|---|
| 12 | 14 | |
|---|
| 13 | 15 | #include <sound/hdmi-codec.h> |
|---|
| 14 | 16 | |
|---|
| .. | .. |
|---|
| 48 | 50 | struct dw_hdmi *hdmi = audio->hdmi; |
|---|
| 49 | 51 | u8 conf0 = 0; |
|---|
| 50 | 52 | u8 conf1 = 0; |
|---|
| 53 | + u8 conf2 = 0; |
|---|
| 51 | 54 | u8 inputclkfs = 0; |
|---|
| 52 | | - u8 val; |
|---|
| 53 | 55 | |
|---|
| 54 | 56 | /* it cares I2S only */ |
|---|
| 55 | | - if ((fmt->fmt != HDMI_I2S) || |
|---|
| 56 | | - (fmt->bit_clk_master | fmt->frame_clk_master)) { |
|---|
| 57 | | - dev_err(dev, "unsupported format/settings\n"); |
|---|
| 57 | + if (fmt->bit_clk_master | fmt->frame_clk_master) { |
|---|
| 58 | + dev_err(dev, "unsupported clock settings\n"); |
|---|
| 58 | 59 | return -EINVAL; |
|---|
| 59 | 60 | } |
|---|
| 60 | 61 | |
|---|
| 61 | | - inputclkfs = HDMI_AUD_INPUTCLKFS_128FS; |
|---|
| 62 | + /* Reset the FIFOs before applying new params */ |
|---|
| 63 | + hdmi_update_bits(audio, HDMI_AUD_CONF0_SW_RESET, |
|---|
| 64 | + HDMI_AUD_CONF0_SW_RESET, HDMI_AUD_CONF0); |
|---|
| 65 | + hdmi_write(audio, (u8)~HDMI_MC_SWRSTZ_I2SSWRST_REQ, HDMI_MC_SWRSTZ); |
|---|
| 66 | + |
|---|
| 67 | + inputclkfs = HDMI_AUD_INPUTCLKFS_64FS; |
|---|
| 68 | + conf0 = (HDMI_AUD_CONF0_I2S_SELECT | HDMI_AUD_CONF0_I2S_EN0); |
|---|
| 69 | + |
|---|
| 70 | + /* Enable the required i2s lanes */ |
|---|
| 71 | + switch (hparms->channels) { |
|---|
| 72 | + case 7 ... 8: |
|---|
| 73 | + conf0 |= HDMI_AUD_CONF0_I2S_EN3; |
|---|
| 74 | + fallthrough; |
|---|
| 75 | + case 5 ... 6: |
|---|
| 76 | + conf0 |= HDMI_AUD_CONF0_I2S_EN2; |
|---|
| 77 | + fallthrough; |
|---|
| 78 | + case 3 ... 4: |
|---|
| 79 | + conf0 |= HDMI_AUD_CONF0_I2S_EN1; |
|---|
| 80 | + /* Fall-thru */ |
|---|
| 81 | + } |
|---|
| 62 | 82 | |
|---|
| 63 | 83 | switch (hparms->sample_width) { |
|---|
| 64 | 84 | case 16: |
|---|
| .. | .. |
|---|
| 68 | 88 | case 32: |
|---|
| 69 | 89 | conf1 = HDMI_AUD_CONF1_WIDTH_24; |
|---|
| 70 | 90 | break; |
|---|
| 91 | + } |
|---|
| 92 | + |
|---|
| 93 | + switch (fmt->fmt) { |
|---|
| 94 | + case HDMI_I2S: |
|---|
| 95 | + conf1 |= HDMI_AUD_CONF1_MODE_I2S; |
|---|
| 96 | + break; |
|---|
| 97 | + case HDMI_RIGHT_J: |
|---|
| 98 | + conf1 |= HDMI_AUD_CONF1_MODE_RIGHT_J; |
|---|
| 99 | + break; |
|---|
| 100 | + case HDMI_LEFT_J: |
|---|
| 101 | + conf1 |= HDMI_AUD_CONF1_MODE_LEFT_J; |
|---|
| 102 | + break; |
|---|
| 103 | + case HDMI_DSP_A: |
|---|
| 104 | + conf1 |= HDMI_AUD_CONF1_MODE_BURST_1; |
|---|
| 105 | + break; |
|---|
| 106 | + case HDMI_DSP_B: |
|---|
| 107 | + conf1 |= HDMI_AUD_CONF1_MODE_BURST_2; |
|---|
| 108 | + break; |
|---|
| 71 | 109 | default: |
|---|
| 72 | | - dev_err(dev, "unsupported sample width [%d]\n", hparms->sample_width); |
|---|
| 110 | + dev_err(dev, "unsupported format\n"); |
|---|
| 73 | 111 | return -EINVAL; |
|---|
| 74 | 112 | } |
|---|
| 75 | 113 | |
|---|
| 76 | | - switch (hparms->channels) { |
|---|
| 77 | | - case 2: |
|---|
| 78 | | - conf0 = HDMI_AUD_CONF0_I2S_2CHANNEL_ENABLE; |
|---|
| 79 | | - break; |
|---|
| 80 | | - case 4: |
|---|
| 81 | | - conf0 = HDMI_AUD_CONF0_I2S_4CHANNEL_ENABLE; |
|---|
| 82 | | - break; |
|---|
| 83 | | - case 6: |
|---|
| 84 | | - conf0 = HDMI_AUD_CONF0_I2S_6CHANNEL_ENABLE; |
|---|
| 85 | | - break; |
|---|
| 86 | | - case 8: |
|---|
| 87 | | - conf0 = HDMI_AUD_CONF0_I2S_8CHANNEL_ENABLE; |
|---|
| 114 | + switch (fmt->bit_fmt) { |
|---|
| 115 | + case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE: |
|---|
| 116 | + conf1 = HDMI_AUD_CONF1_WIDTH_21; |
|---|
| 117 | + conf2 = (hparms->channels == 8) ? HDMI_AUD_CONF2_HBR : HDMI_AUD_CONF2_NLPCM; |
|---|
| 88 | 118 | break; |
|---|
| 89 | 119 | default: |
|---|
| 90 | | - dev_err(dev, "unsupported channels [%d]\n", hparms->channels); |
|---|
| 91 | | - return -EINVAL; |
|---|
| 92 | | - } |
|---|
| 93 | | - |
|---|
| 94 | | - /* |
|---|
| 95 | | - * dw-hdmi introduced insert_pcuv bit in version 2.10a. |
|---|
| 96 | | - * When set (1'b1), this bit enables the insertion of the PCUV |
|---|
| 97 | | - * (Parity, Channel Status, User bit and Validity) bits on the |
|---|
| 98 | | - * incoming audio stream (support limited to Linear PCM audio) |
|---|
| 99 | | - */ |
|---|
| 100 | | - val = 0; |
|---|
| 101 | | - if (hdmi_read(audio, HDMI_DESIGN_ID) >= 0x21) |
|---|
| 102 | | - val = HDMI_AUD_CONF2_INSERT_PCUV; |
|---|
| 103 | | - |
|---|
| 104 | | - /*Mask fifo empty and full int and reset fifo*/ |
|---|
| 105 | | - hdmi_update_bits(audio, |
|---|
| 106 | | - HDMI_AUD_INT_FIFO_EMPTY_MSK | |
|---|
| 107 | | - HDMI_AUD_INT_FIFO_FULL_MSK, |
|---|
| 108 | | - HDMI_AUD_INT_FIFO_EMPTY_MSK | |
|---|
| 109 | | - HDMI_AUD_INT_FIFO_FULL_MSK, HDMI_AUD_INT); |
|---|
| 110 | | - hdmi_update_bits(audio, HDMI_AUD_CONF0_SW_RESET, |
|---|
| 111 | | - HDMI_AUD_CONF0_SW_RESET, HDMI_AUD_CONF0); |
|---|
| 112 | | - hdmi_update_bits(audio, HDMI_MC_SWRSTZ_I2S_RESET_MSK, |
|---|
| 113 | | - HDMI_MC_SWRSTZ_I2S_RESET_MSK, HDMI_MC_SWRSTZ); |
|---|
| 114 | | - |
|---|
| 115 | | - switch (hparms->mode) { |
|---|
| 116 | | - case NLPCM: |
|---|
| 117 | | - hdmi_write(audio, HDMI_AUD_CONF2_NLPCM, HDMI_AUD_CONF2); |
|---|
| 118 | | - conf1 = HDMI_AUD_CONF1_WIDTH_21; |
|---|
| 119 | | - break; |
|---|
| 120 | | - case HBR: |
|---|
| 121 | | - hdmi_write(audio, HDMI_AUD_CONF2_HBR, HDMI_AUD_CONF2); |
|---|
| 122 | | - conf1 = HDMI_AUD_CONF1_WIDTH_21; |
|---|
| 123 | | - break; |
|---|
| 124 | | - default: |
|---|
| 125 | | - hdmi_write(audio, val, HDMI_AUD_CONF2); |
|---|
| 120 | + /* |
|---|
| 121 | + * dw-hdmi introduced insert_pcuv bit in version 2.10a. |
|---|
| 122 | + * When set (1'b1), this bit enables the insertion of the PCUV |
|---|
| 123 | + * (Parity, Channel Status, User bit and Validity) bits on the |
|---|
| 124 | + * incoming audio stream (support limited to Linear PCM audio) |
|---|
| 125 | + */ |
|---|
| 126 | + if (hdmi_read(audio, HDMI_DESIGN_ID) >= 0x21) |
|---|
| 127 | + conf2 = HDMI_AUD_CONF2_INSERT_PCUV; |
|---|
| 126 | 128 | break; |
|---|
| 127 | 129 | } |
|---|
| 128 | 130 | |
|---|
| 129 | 131 | dw_hdmi_set_sample_rate(hdmi, hparms->sample_rate); |
|---|
| 132 | + dw_hdmi_set_channel_status(hdmi, hparms->iec.status); |
|---|
| 133 | + dw_hdmi_set_channel_count(hdmi, hparms->channels); |
|---|
| 134 | + dw_hdmi_set_channel_allocation(hdmi, hparms->cea.channel_allocation); |
|---|
| 130 | 135 | |
|---|
| 131 | 136 | hdmi_write(audio, inputclkfs, HDMI_AUD_INPUTCLKFS); |
|---|
| 132 | 137 | hdmi_write(audio, conf0, HDMI_AUD_CONF0); |
|---|
| 133 | 138 | hdmi_write(audio, conf1, HDMI_AUD_CONF1); |
|---|
| 139 | + hdmi_write(audio, conf2, HDMI_AUD_CONF2); |
|---|
| 134 | 140 | |
|---|
| 135 | | - val = HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_LAYOUT0; |
|---|
| 136 | | - if (hparms->channels > 2) |
|---|
| 137 | | - val = HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_LAYOUT1; |
|---|
| 138 | | - hdmi_update_bits(audio, val, HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_MASK, |
|---|
| 139 | | - HDMI_FC_AUDSCONF); |
|---|
| 141 | + return 0; |
|---|
| 142 | +} |
|---|
| 140 | 143 | |
|---|
| 141 | | - switch (hparms->sample_rate) { |
|---|
| 142 | | - case 32000: |
|---|
| 143 | | - val = HDMI_FC_AUDSCHNLS_32K; |
|---|
| 144 | | - break; |
|---|
| 145 | | - case 44100: |
|---|
| 146 | | - val = HDMI_FC_AUDSCHNLS_441K; |
|---|
| 147 | | - break; |
|---|
| 148 | | - case 48000: |
|---|
| 149 | | - val = HDMI_FC_AUDSCHNLS_48K; |
|---|
| 150 | | - break; |
|---|
| 151 | | - case 88200: |
|---|
| 152 | | - val = HDMI_FC_AUDSCHNLS_882K; |
|---|
| 153 | | - break; |
|---|
| 154 | | - case 96000: |
|---|
| 155 | | - val = HDMI_FC_AUDSCHNLS_96K; |
|---|
| 156 | | - break; |
|---|
| 157 | | - case 176400: |
|---|
| 158 | | - val = HDMI_FC_AUDSCHNLS_1764K; |
|---|
| 159 | | - break; |
|---|
| 160 | | - case 192000: |
|---|
| 161 | | - val = HDMI_FC_AUDSCHNLS_192K; |
|---|
| 162 | | - break; |
|---|
| 163 | | - default: |
|---|
| 164 | | - val = HDMI_FC_AUDSCHNLS_441K; |
|---|
| 165 | | - break; |
|---|
| 166 | | - } |
|---|
| 144 | +static int dw_hdmi_i2s_prepare(struct device *dev, void *data, |
|---|
| 145 | + struct hdmi_codec_daifmt *fmt, |
|---|
| 146 | + struct hdmi_codec_params *hparms) |
|---|
| 147 | +{ |
|---|
| 148 | + return dw_hdmi_i2s_hw_params(dev, data, fmt, hparms); |
|---|
| 149 | +} |
|---|
| 167 | 150 | |
|---|
| 168 | | - /* set channel status register */ |
|---|
| 169 | | - hdmi_update_bits(audio, val, |
|---|
| 170 | | - HDMI_FC_AUDSCHNLS7_SAMPFREQ_MASK, |
|---|
| 171 | | - HDMI_FC_AUDSCHNLS7); |
|---|
| 172 | | - hdmi_write(audio, |
|---|
| 173 | | - (((u8)~val) << HDMI_FC_AUDSCHNLS8_ORIGSAMPFREQ_OFFSET), |
|---|
| 174 | | - HDMI_FC_AUDSCHNLS8); |
|---|
| 175 | | - |
|---|
| 176 | | - /* Refer to CEA861-E Audio infoFrame |
|---|
| 177 | | - * Set both Audio Channel Count and Audio Coding |
|---|
| 178 | | - * Type Refer to Stream Head for HDMI |
|---|
| 179 | | - */ |
|---|
| 180 | | - hdmi_update_bits(audio, |
|---|
| 181 | | - (hparms->channels - 1) << HDMI_FC_AUDICONF0_CC_OFFSET, |
|---|
| 182 | | - HDMI_FC_AUDICONF0_CC_MASK, HDMI_FC_AUDICONF0); |
|---|
| 183 | | - |
|---|
| 184 | | - /* Set both Audio Sample Size and Sample Frequency |
|---|
| 185 | | - * Refer to Stream Head for HDMI |
|---|
| 186 | | - */ |
|---|
| 187 | | - hdmi_write(audio, 0x00, HDMI_FC_AUDICONF1); |
|---|
| 188 | | - |
|---|
| 189 | | - /* Set Channel Allocation */ |
|---|
| 190 | | - hdmi_write(audio, 0x00, HDMI_FC_AUDICONF2); |
|---|
| 191 | | - |
|---|
| 192 | | - /* Set LFEPBLDOWN-MIX INH and LSV */ |
|---|
| 193 | | - hdmi_write(audio, 0x00, HDMI_FC_AUDICONF3); |
|---|
| 151 | +static int dw_hdmi_i2s_audio_startup(struct device *dev, void *data) |
|---|
| 152 | +{ |
|---|
| 153 | + struct dw_hdmi_i2s_audio_data *audio = data; |
|---|
| 154 | + struct dw_hdmi *hdmi = audio->hdmi; |
|---|
| 194 | 155 | |
|---|
| 195 | 156 | dw_hdmi_audio_enable(hdmi); |
|---|
| 196 | | - |
|---|
| 197 | | - hdmi_update_bits(audio, HDMI_AUD_CONF0_SW_RESET, |
|---|
| 198 | | - HDMI_AUD_CONF0_SW_RESET, HDMI_AUD_CONF0); |
|---|
| 199 | | - hdmi_update_bits(audio, HDMI_MC_SWRSTZ_I2S_RESET_MSK, |
|---|
| 200 | | - HDMI_MC_SWRSTZ_I2S_RESET_MSK, HDMI_MC_SWRSTZ); |
|---|
| 201 | 157 | |
|---|
| 202 | 158 | return 0; |
|---|
| 203 | 159 | } |
|---|
| .. | .. |
|---|
| 208 | 164 | struct dw_hdmi *hdmi = audio->hdmi; |
|---|
| 209 | 165 | |
|---|
| 210 | 166 | dw_hdmi_audio_disable(hdmi); |
|---|
| 167 | +} |
|---|
| 211 | 168 | |
|---|
| 212 | | - hdmi_update_bits(audio, |
|---|
| 213 | | - HDMI_AUD_CONF0_SW_RESET, |
|---|
| 214 | | - HDMI_AUD_CONF0_SW_RESET | |
|---|
| 215 | | - (HDMI_AUD_CONF0_I2S_ALL_ENABLE ^ |
|---|
| 216 | | - HDMI_AUD_CONF0_I2S_SELECT_MASK), |
|---|
| 217 | | - HDMI_AUD_CONF0); |
|---|
| 169 | +static int dw_hdmi_i2s_get_eld(struct device *dev, void *data, uint8_t *buf, |
|---|
| 170 | + size_t len) |
|---|
| 171 | +{ |
|---|
| 172 | + struct dw_hdmi_i2s_audio_data *audio = data; |
|---|
| 173 | + u8 *eld; |
|---|
| 174 | + |
|---|
| 175 | + eld = audio->get_eld(audio->hdmi); |
|---|
| 176 | + if (eld) |
|---|
| 177 | + memcpy(buf, eld, min_t(size_t, MAX_ELD_BYTES, len)); |
|---|
| 178 | + else |
|---|
| 179 | + /* Pass en empty ELD if connector not available */ |
|---|
| 180 | + memset(buf, 0, len); |
|---|
| 181 | + |
|---|
| 182 | + return 0; |
|---|
| 218 | 183 | } |
|---|
| 219 | 184 | |
|---|
| 220 | 185 | static int dw_hdmi_i2s_get_dai_id(struct snd_soc_component *component, |
|---|
| .. | .. |
|---|
| 249 | 214 | |
|---|
| 250 | 215 | static struct hdmi_codec_ops dw_hdmi_i2s_ops = { |
|---|
| 251 | 216 | .hw_params = dw_hdmi_i2s_hw_params, |
|---|
| 217 | + .prepare = dw_hdmi_i2s_prepare, |
|---|
| 218 | + .audio_startup = dw_hdmi_i2s_audio_startup, |
|---|
| 252 | 219 | .audio_shutdown = dw_hdmi_i2s_audio_shutdown, |
|---|
| 220 | + .get_eld = dw_hdmi_i2s_get_eld, |
|---|
| 253 | 221 | .get_dai_id = dw_hdmi_i2s_get_dai_id, |
|---|
| 254 | 222 | .hook_plugged_cb = dw_hdmi_i2s_hook_plugged_cb, |
|---|
| 255 | 223 | }; |
|---|