// SPDX-License-Identifier: GPL-2.0-or-later /* * Nvidia Tegra HDMI codec support */ #include #include #include #include #include #include #include #include "hda_local.h" #include "hdmi_local.h" enum { MODEL_TEGRA, MODEL_TEGRA234, }; /* * The HDA codec on NVIDIA Tegra contains two scratch registers that are * accessed using vendor-defined verbs. These registers can be used for * interoperability between the HDA and HDMI drivers. */ /* Audio Function Group node */ #define NVIDIA_AFG_NID 0x01 /* * The SCRATCH0 register is used to notify the HDMI codec of changes in audio * format. On Tegra, bit 31 is used as a trigger that causes an interrupt to * be raised in the HDMI codec. The remainder of the bits is arbitrary. This * implementation stores the HDA format (see AC_FMT_*) in bits [15:0] and an * additional bit (at position 30) to signal the validity of the format. * * | 31 | 30 | 29 16 | 15 0 | * +---------+-------+--------+--------+ * | TRIGGER | VALID | UNUSED | FORMAT | * +-----------------------------------| * * Note that for the trigger bit to take effect it needs to change value * (i.e. it needs to be toggled). The trigger bit is not applicable from * TEGRA234 chip onwards, as new verb id 0xf80 will be used for interrupt * trigger to hdmi. */ #define NVIDIA_SET_HOST_INTR 0xf80 #define NVIDIA_GET_SCRATCH0 0xfa6 #define NVIDIA_SET_SCRATCH0_BYTE0 0xfa7 #define NVIDIA_SET_SCRATCH0_BYTE1 0xfa8 #define NVIDIA_SET_SCRATCH0_BYTE2 0xfa9 #define NVIDIA_SET_SCRATCH0_BYTE3 0xfaa #define NVIDIA_SCRATCH_TRIGGER (1 << 7) #define NVIDIA_SCRATCH_VALID (1 << 6) #define NVIDIA_GET_SCRATCH1 0xfab #define NVIDIA_SET_SCRATCH1_BYTE0 0xfac #define NVIDIA_SET_SCRATCH1_BYTE1 0xfad #define NVIDIA_SET_SCRATCH1_BYTE2 0xfae #define NVIDIA_SET_SCRATCH1_BYTE3 0xfaf /* * The format parameter is the HDA audio format (see AC_FMT_*). If set to 0, * the format is invalidated so that the HDMI codec can be disabled. */ static void tegra_hdmi_set_format(struct hda_codec *codec, hda_nid_t cvt_nid, unsigned int format) { unsigned int value; unsigned int nid = NVIDIA_AFG_NID; struct hdmi_spec *spec = codec->spec; /* * Tegra HDA codec design from TEGRA234 chip onwards support DP MST. * This resulted in moving scratch registers from audio function * group to converter widget context. So CVT NID should be used for * scratch register read/write for DP MST supported Tegra HDA codec. */ if (codec->dp_mst) nid = cvt_nid; /* bits [31:30] contain the trigger and valid bits */ value = snd_hda_codec_read(codec, nid, 0, NVIDIA_GET_SCRATCH0, 0); value = (value >> 24) & 0xff; /* bits [15:0] are used to store the HDA format */ snd_hda_codec_write(codec, nid, 0, NVIDIA_SET_SCRATCH0_BYTE0, (format >> 0) & 0xff); snd_hda_codec_write(codec, nid, 0, NVIDIA_SET_SCRATCH0_BYTE1, (format >> 8) & 0xff); /* bits [16:24] are unused */ snd_hda_codec_write(codec, nid, 0, NVIDIA_SET_SCRATCH0_BYTE2, 0); /* * Bit 30 signals that the data is valid and hence that HDMI audio can * be enabled. */ if (format == 0) value &= ~NVIDIA_SCRATCH_VALID; else value |= NVIDIA_SCRATCH_VALID; if (spec->hdmi_intr_trig_ctrl) { /* * For Tegra HDA Codec design from TEGRA234 onwards, the * Interrupt to hdmi driver is triggered by writing * non-zero values to verb 0xF80 instead of 31st bit of * scratch register. */ snd_hda_codec_write(codec, nid, 0, NVIDIA_SET_SCRATCH0_BYTE3, value); snd_hda_codec_write(codec, nid, 0, NVIDIA_SET_HOST_INTR, 0x1); } else { /* * Whenever the 31st trigger bit is toggled, an interrupt is raised * in the HDMI codec. The HDMI driver will use that as trigger * to update its configuration. */ value ^= NVIDIA_SCRATCH_TRIGGER; snd_hda_codec_write(codec, nid, 0, NVIDIA_SET_SCRATCH0_BYTE3, value); } } static int tegra_hdmi_pcm_prepare(struct hda_pcm_stream *hinfo, struct hda_codec *codec, unsigned int stream_tag, unsigned int format, struct snd_pcm_substream *substream) { int err; err = snd_hda_hdmi_generic_pcm_prepare(hinfo, codec, stream_tag, format, substream); if (err < 0) return err; /* notify the HDMI codec of the format change */ tegra_hdmi_set_format(codec, hinfo->nid, format); return 0; } static int tegra_hdmi_pcm_cleanup(struct hda_pcm_stream *hinfo, struct hda_codec *codec, struct snd_pcm_substream *substream) { /* invalidate the format in the HDMI codec */ tegra_hdmi_set_format(codec, hinfo->nid, 0); return snd_hda_hdmi_generic_pcm_cleanup(hinfo, codec, substream); } static struct hda_pcm *hda_find_pcm_by_type(struct hda_codec *codec, int type) { struct hdmi_spec *spec = codec->spec; unsigned int i; for (i = 0; i < spec->num_pins; i++) { struct hda_pcm *pcm = get_pcm_rec(spec, i); if (pcm->pcm_type == type) return pcm; } return NULL; } static int tegra_hdmi_build_pcms(struct hda_codec *codec) { struct hda_pcm_stream *stream; struct hda_pcm *pcm; int err; err = snd_hda_hdmi_generic_build_pcms(codec); if (err < 0) return err; pcm = hda_find_pcm_by_type(codec, HDA_PCM_TYPE_HDMI); if (!pcm) return -ENODEV; /* * Override ->prepare() and ->cleanup() operations to notify the HDMI * codec about format changes. */ stream = &pcm->stream[SNDRV_PCM_STREAM_PLAYBACK]; stream->ops.prepare = tegra_hdmi_pcm_prepare; stream->ops.cleanup = tegra_hdmi_pcm_cleanup; return 0; } /* * NVIDIA codecs ignore ASP mapping for 2ch - confirmed on: * - 0x10de0015 * - 0x10de0040 */ static int nvhdmi_chmap_cea_alloc_validate_get_type(struct hdac_chmap *chmap, struct hdac_cea_channel_speaker_allocation *cap, int channels) { if (cap->ca_index == 0x00 && channels == 2) return SNDRV_CTL_TLVT_CHMAP_FIXED; /* If the speaker allocation matches the channel count, it is OK. */ if (cap->channels != channels) return -1; /* all channels are remappable freely */ return SNDRV_CTL_TLVT_CHMAP_VAR; } static int nvhdmi_chmap_validate(struct hdac_chmap *chmap, int ca, int chs, unsigned char *map) { if (ca == 0x00 && (map[0] != SNDRV_CHMAP_FL || map[1] != SNDRV_CHMAP_FR)) return -EINVAL; return 0; } static int tegra_hdmi_init(struct hda_codec *codec) { struct hdmi_spec *spec = codec->spec; int i, err; err = snd_hda_hdmi_parse_codec(codec); if (err < 0) { snd_hda_hdmi_generic_spec_free(codec); return err; } for (i = 0; i < spec->num_cvts; i++) snd_hda_codec_write(codec, spec->cvt_nids[i], 0, AC_VERB_SET_DIGI_CONVERT_1, AC_DIG1_ENABLE); snd_hda_hdmi_generic_init_per_pins(codec); codec->depop_delay = 10; spec->chmap.ops.chmap_cea_alloc_validate_get_type = nvhdmi_chmap_cea_alloc_validate_get_type; spec->chmap.ops.chmap_validate = nvhdmi_chmap_validate; spec->chmap.ops.chmap_cea_alloc_validate_get_type = nvhdmi_chmap_cea_alloc_validate_get_type; spec->chmap.ops.chmap_validate = nvhdmi_chmap_validate; spec->nv_dp_workaround = true; return 0; } static int tegrahdmi_probe(struct hda_codec *codec, const struct hda_device_id *id) { struct hdmi_spec *spec; int err; err = snd_hda_hdmi_generic_alloc(codec); if (err < 0) return err; if (id->driver_data == MODEL_TEGRA234) { codec->dp_mst = true; spec = codec->spec; spec->dyn_pin_out = true; spec->hdmi_intr_trig_ctrl = true; } return tegra_hdmi_init(codec); } static const struct hda_codec_ops tegrahdmi_codec_ops = { .probe = tegrahdmi_probe, .remove = snd_hda_hdmi_generic_remove, .init = snd_hda_hdmi_generic_init, .build_pcms = tegra_hdmi_build_pcms, .build_controls = snd_hda_hdmi_generic_build_controls, .unsol_event = snd_hda_hdmi_generic_unsol_event, .suspend = snd_hda_hdmi_generic_suspend, .resume = snd_hda_hdmi_generic_resume, }; static const struct hda_device_id snd_hda_id_tegrahdmi[] = { HDA_CODEC_ID_MODEL(0x10de0020, "Tegra30 HDMI", MODEL_TEGRA), HDA_CODEC_ID_MODEL(0x10de0022, "Tegra114 HDMI", MODEL_TEGRA), HDA_CODEC_ID_MODEL(0x10de0028, "Tegra124 HDMI", MODEL_TEGRA), HDA_CODEC_ID_MODEL(0x10de0029, "Tegra210 HDMI/DP", MODEL_TEGRA), HDA_CODEC_ID_MODEL(0x10de002d, "Tegra186 HDMI/DP0", MODEL_TEGRA), HDA_CODEC_ID_MODEL(0x10de002e, "Tegra186 HDMI/DP1", MODEL_TEGRA), HDA_CODEC_ID_MODEL(0x10de002f, "Tegra194 HDMI/DP2", MODEL_TEGRA), HDA_CODEC_ID_MODEL(0x10de0030, "Tegra194 HDMI/DP3", MODEL_TEGRA), HDA_CODEC_ID_MODEL(0x10de0031, "Tegra234 HDMI/DP", MODEL_TEGRA234), HDA_CODEC_ID_MODEL(0x10de0034, "Tegra264 HDMI/DP", MODEL_TEGRA234), {} /* terminator */ }; MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_tegrahdmi); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Nvidia Tegra HDMI HD-audio codec"); MODULE_IMPORT_NS("SND_HDA_CODEC_HDMI"); static struct hda_codec_driver tegrahdmi_driver = { .id = snd_hda_id_tegrahdmi, .ops = &tegrahdmi_codec_ops, }; module_hda_codec_driver(tegrahdmi_driver);