mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-08-28 09:22:08 +00:00

Convert the HDMI codec drivers to use the new hda_codec_ops probe. The Intel and Nvidia-MCP HDMI drivers needed slightly more changes to deal with the unified callbacks among all models. Also another non-trivial change is Intel driver's set_power_state callback. An additional NULL check of codec->spec is needed there since the set_power_state() may be called before the probe gets called (e.g. in ASoC hda codec hda_codec_probe()). Other than that, no functional changes. Signed-off-by: Takashi Iwai <tiwai@suse.de> Link: https://patch.msgid.link/20250709160434.1859-24-tiwai@suse.de
385 lines
9.9 KiB
C
385 lines
9.9 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Legacy Nvidia HDMI codec support
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/module.h>
|
|
#include <sound/core.h>
|
|
#include <sound/hdaudio.h>
|
|
#include <sound/hda_codec.h>
|
|
#include "hda_local.h"
|
|
#include "hdmi_local.h"
|
|
|
|
enum { MODEL_2CH, MODEL_8CH };
|
|
|
|
#define Nv_VERB_SET_Channel_Allocation 0xF79
|
|
#define Nv_VERB_SET_Info_Frame_Checksum 0xF7A
|
|
#define Nv_VERB_SET_Audio_Protection_On 0xF98
|
|
#define Nv_VERB_SET_Audio_Protection_Off 0xF99
|
|
|
|
#define nvhdmi_master_con_nid_7x 0x04
|
|
#define nvhdmi_master_pin_nid_7x 0x05
|
|
|
|
static const hda_nid_t nvhdmi_con_nids_7x[4] = {
|
|
/*front, rear, clfe, rear_surr */
|
|
0x6, 0x8, 0xa, 0xc,
|
|
};
|
|
|
|
static const struct hda_verb nvhdmi_basic_init_7x_2ch[] = {
|
|
/* set audio protect on */
|
|
{ 0x1, Nv_VERB_SET_Audio_Protection_On, 0x1},
|
|
/* enable digital output on pin widget */
|
|
{ 0x5, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
|
|
{} /* terminator */
|
|
};
|
|
|
|
static const struct hda_verb nvhdmi_basic_init_7x_8ch[] = {
|
|
/* set audio protect on */
|
|
{ 0x1, Nv_VERB_SET_Audio_Protection_On, 0x1},
|
|
/* enable digital output on pin widget */
|
|
{ 0x5, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
|
|
{ 0x7, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
|
|
{ 0x9, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
|
|
{ 0xb, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
|
|
{ 0xd, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
|
|
{} /* terminator */
|
|
};
|
|
|
|
static int nvhdmi_mcp_init(struct hda_codec *codec)
|
|
{
|
|
struct hdmi_spec *spec = codec->spec;
|
|
|
|
if (spec->multiout.max_channels == 2)
|
|
snd_hda_sequence_write(codec, nvhdmi_basic_init_7x_2ch);
|
|
else
|
|
snd_hda_sequence_write(codec, nvhdmi_basic_init_7x_8ch);
|
|
return 0;
|
|
}
|
|
|
|
static void nvhdmi_8ch_7x_set_info_frame_parameters(struct hda_codec *codec,
|
|
int channels)
|
|
{
|
|
unsigned int chanmask;
|
|
int chan = channels ? (channels - 1) : 1;
|
|
|
|
switch (channels) {
|
|
default:
|
|
case 0:
|
|
case 2:
|
|
chanmask = 0x00;
|
|
break;
|
|
case 4:
|
|
chanmask = 0x08;
|
|
break;
|
|
case 6:
|
|
chanmask = 0x0b;
|
|
break;
|
|
case 8:
|
|
chanmask = 0x13;
|
|
break;
|
|
}
|
|
|
|
/* Set the audio infoframe channel allocation and checksum fields. The
|
|
* channel count is computed implicitly by the hardware.
|
|
*/
|
|
snd_hda_codec_write(codec, 0x1, 0,
|
|
Nv_VERB_SET_Channel_Allocation, chanmask);
|
|
|
|
snd_hda_codec_write(codec, 0x1, 0,
|
|
Nv_VERB_SET_Info_Frame_Checksum,
|
|
(0x71 - chan - chanmask));
|
|
}
|
|
|
|
static int nvhdmi_8ch_7x_pcm_close(struct hda_pcm_stream *hinfo,
|
|
struct hda_codec *codec,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct hdmi_spec *spec = codec->spec;
|
|
int i;
|
|
|
|
snd_hda_codec_write(codec, nvhdmi_master_con_nid_7x,
|
|
0, AC_VERB_SET_CHANNEL_STREAMID, 0);
|
|
for (i = 0; i < 4; i++) {
|
|
/* set the stream id */
|
|
snd_hda_codec_write(codec, nvhdmi_con_nids_7x[i], 0,
|
|
AC_VERB_SET_CHANNEL_STREAMID, 0);
|
|
/* set the stream format */
|
|
snd_hda_codec_write(codec, nvhdmi_con_nids_7x[i], 0,
|
|
AC_VERB_SET_STREAM_FORMAT, 0);
|
|
}
|
|
|
|
/* The audio hardware sends a channel count of 0x7 (8ch) when all the
|
|
* streams are disabled.
|
|
*/
|
|
nvhdmi_8ch_7x_set_info_frame_parameters(codec, 8);
|
|
|
|
return snd_hda_multi_out_dig_close(codec, &spec->multiout);
|
|
}
|
|
|
|
static int nvhdmi_8ch_7x_pcm_prepare(struct hda_pcm_stream *hinfo,
|
|
struct hda_codec *codec,
|
|
unsigned int stream_tag,
|
|
unsigned int format,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
int chs;
|
|
unsigned int dataDCC2, channel_id;
|
|
int i;
|
|
struct hdmi_spec *spec = codec->spec;
|
|
struct hda_spdif_out *spdif;
|
|
struct hdmi_spec_per_cvt *per_cvt;
|
|
|
|
mutex_lock(&codec->spdif_mutex);
|
|
per_cvt = get_cvt(spec, 0);
|
|
spdif = snd_hda_spdif_out_of_nid(codec, per_cvt->cvt_nid);
|
|
|
|
chs = substream->runtime->channels;
|
|
|
|
dataDCC2 = 0x2;
|
|
|
|
/* turn off SPDIF once; otherwise the IEC958 bits won't be updated */
|
|
if (codec->spdif_status_reset && (spdif->ctls & AC_DIG1_ENABLE))
|
|
snd_hda_codec_write(codec,
|
|
nvhdmi_master_con_nid_7x,
|
|
0,
|
|
AC_VERB_SET_DIGI_CONVERT_1,
|
|
spdif->ctls & ~AC_DIG1_ENABLE & 0xff);
|
|
|
|
/* set the stream id */
|
|
snd_hda_codec_write(codec, nvhdmi_master_con_nid_7x, 0,
|
|
AC_VERB_SET_CHANNEL_STREAMID, (stream_tag << 4) | 0x0);
|
|
|
|
/* set the stream format */
|
|
snd_hda_codec_write(codec, nvhdmi_master_con_nid_7x, 0,
|
|
AC_VERB_SET_STREAM_FORMAT, format);
|
|
|
|
/* turn on again (if needed) */
|
|
/* enable and set the channel status audio/data flag */
|
|
if (codec->spdif_status_reset && (spdif->ctls & AC_DIG1_ENABLE)) {
|
|
snd_hda_codec_write(codec,
|
|
nvhdmi_master_con_nid_7x,
|
|
0,
|
|
AC_VERB_SET_DIGI_CONVERT_1,
|
|
spdif->ctls & 0xff);
|
|
snd_hda_codec_write(codec,
|
|
nvhdmi_master_con_nid_7x,
|
|
0,
|
|
AC_VERB_SET_DIGI_CONVERT_2, dataDCC2);
|
|
}
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
if (chs == 2)
|
|
channel_id = 0;
|
|
else
|
|
channel_id = i * 2;
|
|
|
|
/* turn off SPDIF once;
|
|
*otherwise the IEC958 bits won't be updated
|
|
*/
|
|
if (codec->spdif_status_reset &&
|
|
(spdif->ctls & AC_DIG1_ENABLE))
|
|
snd_hda_codec_write(codec,
|
|
nvhdmi_con_nids_7x[i],
|
|
0,
|
|
AC_VERB_SET_DIGI_CONVERT_1,
|
|
spdif->ctls & ~AC_DIG1_ENABLE & 0xff);
|
|
/* set the stream id */
|
|
snd_hda_codec_write(codec,
|
|
nvhdmi_con_nids_7x[i],
|
|
0,
|
|
AC_VERB_SET_CHANNEL_STREAMID,
|
|
(stream_tag << 4) | channel_id);
|
|
/* set the stream format */
|
|
snd_hda_codec_write(codec,
|
|
nvhdmi_con_nids_7x[i],
|
|
0,
|
|
AC_VERB_SET_STREAM_FORMAT,
|
|
format);
|
|
/* turn on again (if needed) */
|
|
/* enable and set the channel status audio/data flag */
|
|
if (codec->spdif_status_reset &&
|
|
(spdif->ctls & AC_DIG1_ENABLE)) {
|
|
snd_hda_codec_write(codec,
|
|
nvhdmi_con_nids_7x[i],
|
|
0,
|
|
AC_VERB_SET_DIGI_CONVERT_1,
|
|
spdif->ctls & 0xff);
|
|
snd_hda_codec_write(codec,
|
|
nvhdmi_con_nids_7x[i],
|
|
0,
|
|
AC_VERB_SET_DIGI_CONVERT_2, dataDCC2);
|
|
}
|
|
}
|
|
|
|
nvhdmi_8ch_7x_set_info_frame_parameters(codec, chs);
|
|
|
|
mutex_unlock(&codec->spdif_mutex);
|
|
return 0;
|
|
}
|
|
|
|
static const struct hda_pcm_stream nvhdmi_pcm_playback_8ch_7x = {
|
|
.substreams = 1,
|
|
.channels_min = 2,
|
|
.channels_max = 8,
|
|
.nid = nvhdmi_master_con_nid_7x,
|
|
.rates = SUPPORTED_RATES,
|
|
.maxbps = SUPPORTED_MAXBPS,
|
|
.formats = SUPPORTED_FORMATS,
|
|
.ops = {
|
|
.open = snd_hda_hdmi_simple_pcm_open,
|
|
.close = nvhdmi_8ch_7x_pcm_close,
|
|
.prepare = nvhdmi_8ch_7x_pcm_prepare
|
|
},
|
|
};
|
|
|
|
static int nvhdmi_mcp_build_pcms(struct hda_codec *codec)
|
|
{
|
|
struct hdmi_spec *spec = codec->spec;
|
|
int err;
|
|
|
|
err = snd_hda_hdmi_simple_build_pcms(codec);
|
|
if (!err && spec->multiout.max_channels == 8) {
|
|
struct hda_pcm *info = get_pcm_rec(spec, 0);
|
|
|
|
info->own_chmap = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static int nvhdmi_mcp_build_controls(struct hda_codec *codec)
|
|
{
|
|
struct hdmi_spec *spec = codec->spec;
|
|
struct hda_pcm *info;
|
|
struct snd_pcm_chmap *chmap;
|
|
int err;
|
|
|
|
err = snd_hda_hdmi_simple_build_controls(codec);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (spec->multiout.max_channels != 8)
|
|
return 0;
|
|
|
|
/* add channel maps */
|
|
info = get_pcm_rec(spec, 0);
|
|
err = snd_pcm_add_chmap_ctls(info->pcm,
|
|
SNDRV_PCM_STREAM_PLAYBACK,
|
|
snd_pcm_alt_chmaps, 8, 0, &chmap);
|
|
if (err < 0)
|
|
return err;
|
|
switch (codec->preset->vendor_id) {
|
|
case 0x10de0002:
|
|
case 0x10de0003:
|
|
case 0x10de0005:
|
|
case 0x10de0006:
|
|
chmap->channel_mask = (1U << 2) | (1U << 8);
|
|
break;
|
|
case 0x10de0007:
|
|
chmap->channel_mask = (1U << 2) | (1U << 6) | (1U << 8);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const unsigned int channels_2_6_8[] = {
|
|
2, 6, 8
|
|
};
|
|
|
|
static const unsigned int channels_2_8[] = {
|
|
2, 8
|
|
};
|
|
|
|
static const struct snd_pcm_hw_constraint_list hw_constraints_2_6_8_channels = {
|
|
.count = ARRAY_SIZE(channels_2_6_8),
|
|
.list = channels_2_6_8,
|
|
.mask = 0,
|
|
};
|
|
|
|
static const struct snd_pcm_hw_constraint_list hw_constraints_2_8_channels = {
|
|
.count = ARRAY_SIZE(channels_2_8),
|
|
.list = channels_2_8,
|
|
.mask = 0,
|
|
};
|
|
|
|
static int nvhdmi_mcp_probe(struct hda_codec *codec,
|
|
const struct hda_device_id *id)
|
|
{
|
|
struct hdmi_spec *spec;
|
|
int err;
|
|
|
|
err = snd_hda_hdmi_simple_probe(codec, nvhdmi_master_con_nid_7x,
|
|
nvhdmi_master_pin_nid_7x);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
/* override the PCM rates, etc, as the codec doesn't give full list */
|
|
spec = codec->spec;
|
|
spec->pcm_playback.rates = SUPPORTED_RATES;
|
|
spec->pcm_playback.maxbps = SUPPORTED_MAXBPS;
|
|
spec->pcm_playback.formats = SUPPORTED_FORMATS;
|
|
spec->nv_dp_workaround = true;
|
|
|
|
if (id->driver_data == MODEL_2CH)
|
|
return 0;
|
|
|
|
spec->multiout.max_channels = 8;
|
|
spec->pcm_playback = nvhdmi_pcm_playback_8ch_7x;
|
|
|
|
switch (codec->preset->vendor_id) {
|
|
case 0x10de0002:
|
|
case 0x10de0003:
|
|
case 0x10de0005:
|
|
case 0x10de0006:
|
|
spec->hw_constraints_channels = &hw_constraints_2_8_channels;
|
|
break;
|
|
case 0x10de0007:
|
|
spec->hw_constraints_channels = &hw_constraints_2_6_8_channels;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Initialize the audio infoframe channel mask and checksum to something
|
|
* valid
|
|
*/
|
|
nvhdmi_8ch_7x_set_info_frame_parameters(codec, 8);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct hda_codec_ops nvhdmi_mcp_codec_ops = {
|
|
.probe = nvhdmi_mcp_probe,
|
|
.remove = snd_hda_hdmi_simple_remove,
|
|
.build_controls = nvhdmi_mcp_build_pcms,
|
|
.build_pcms = nvhdmi_mcp_build_controls,
|
|
.init = nvhdmi_mcp_init,
|
|
.unsol_event = snd_hda_hdmi_simple_unsol_event,
|
|
};
|
|
|
|
static const struct hda_device_id snd_hda_id_nvhdmi_mcp[] = {
|
|
HDA_CODEC_ID_MODEL(0x10de0001, "MCP73 HDMI", MODEL_2CH),
|
|
HDA_CODEC_ID_MODEL(0x10de0002, "MCP77/78 HDMI", MODEL_8CH),
|
|
HDA_CODEC_ID_MODEL(0x10de0003, "MCP77/78 HDMI", MODEL_8CH),
|
|
HDA_CODEC_ID_MODEL(0x10de0004, "GPU 04 HDMI", MODEL_8CH),
|
|
HDA_CODEC_ID_MODEL(0x10de0005, "MCP77/78 HDMI", MODEL_8CH),
|
|
HDA_CODEC_ID_MODEL(0x10de0006, "MCP77/78 HDMI", MODEL_8CH),
|
|
HDA_CODEC_ID_MODEL(0x10de0007, "MCP79/7A HDMI", MODEL_8CH),
|
|
HDA_CODEC_ID_MODEL(0x10de0067, "MCP67 HDMI", MODEL_2CH),
|
|
HDA_CODEC_ID_MODEL(0x10de8001, "MCP73 HDMI", MODEL_2CH),
|
|
HDA_CODEC_ID_MODEL(0x10de8067, "MCP67/68 HDMI", MODEL_2CH),
|
|
{} /* terminator */
|
|
};
|
|
MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_nvhdmi_mcp);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("Legacy Nvidia HDMI HD-audio codec");
|
|
MODULE_IMPORT_NS("SND_HDA_CODEC_HDMI");
|
|
|
|
static struct hda_codec_driver nvhdmi_mcp_driver = {
|
|
.id = snd_hda_id_nvhdmi_mcp,
|
|
.ops = &nvhdmi_mcp_codec_ops,
|
|
};
|
|
|
|
module_hda_codec_driver(nvhdmi_mcp_driver);
|