diff options
Diffstat (limited to 'sound/hda/codecs/hdmi/simplehdmi.c')
-rw-r--r-- | sound/hda/codecs/hdmi/simplehdmi.c | 251 |
1 files changed, 251 insertions, 0 deletions
diff --git a/sound/hda/codecs/hdmi/simplehdmi.c b/sound/hda/codecs/hdmi/simplehdmi.c new file mode 100644 index 000000000000..193c8dc882af --- /dev/null +++ b/sound/hda/codecs/hdmi/simplehdmi.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Non-generic simple HDMI codec support + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include "hdmi_local.h" +#include "hda_jack.h" + +int snd_hda_hdmi_simple_build_pcms(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + struct hda_pcm *info; + unsigned int chans; + struct hda_pcm_stream *pstr; + struct hdmi_spec_per_cvt *per_cvt; + + per_cvt = get_cvt(spec, 0); + chans = get_wcaps(codec, per_cvt->cvt_nid); + chans = get_wcaps_channels(chans); + + info = snd_hda_codec_pcm_new(codec, "HDMI 0"); + if (!info) + return -ENOMEM; + spec->pcm_rec[0].pcm = info; + info->pcm_type = HDA_PCM_TYPE_HDMI; + pstr = &info->stream[SNDRV_PCM_STREAM_PLAYBACK]; + *pstr = spec->pcm_playback; + pstr->nid = per_cvt->cvt_nid; + if (pstr->channels_max <= 2 && chans && chans <= 16) + pstr->channels_max = chans; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_simple_build_pcms, "SND_HDA_CODEC_HDMI"); + +/* unsolicited event for jack sensing */ +void snd_hda_hdmi_simple_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + snd_hda_jack_set_dirty_all(codec); + snd_hda_jack_report_sync(codec); +} +EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_simple_unsol_event, "SND_HDA_CODEC_HDMI"); + +static void free_hdmi_jack_priv(struct snd_jack *jack) +{ + struct hdmi_pcm *pcm = jack->private_data; + + pcm->jack = NULL; +} + +static int simple_hdmi_build_jack(struct hda_codec *codec) +{ + char hdmi_str[32] = "HDMI/DP"; + struct hdmi_spec *spec = codec->spec; + struct snd_jack *jack; + struct hdmi_pcm *pcmp = get_hdmi_pcm(spec, 0); + int pcmdev = pcmp->pcm->device; + int err; + + if (pcmdev > 0) + sprintf(hdmi_str + strlen(hdmi_str), ",pcm=%d", pcmdev); + + err = snd_jack_new(codec->card, hdmi_str, SND_JACK_AVOUT, &jack, + true, false); + if (err < 0) + return err; + + pcmp->jack = jack; + jack->private_data = pcmp; + jack->private_free = free_hdmi_jack_priv; + return 0; +} + +int snd_hda_hdmi_simple_build_controls(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + struct hdmi_spec_per_cvt *per_cvt; + int err; + + per_cvt = get_cvt(spec, 0); + err = snd_hda_create_dig_out_ctls(codec, per_cvt->cvt_nid, + per_cvt->cvt_nid, + HDA_PCM_TYPE_HDMI); + if (err < 0) + return err; + return simple_hdmi_build_jack(codec); +} +EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_simple_build_controls, "SND_HDA_CODEC_HDMI"); + +int snd_hda_hdmi_simple_init(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + struct hdmi_spec_per_pin *per_pin = get_pin(spec, 0); + hda_nid_t pin = per_pin->pin_nid; + + snd_hda_codec_write(codec, pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT); + /* some codecs require to unmute the pin */ + if (get_wcaps(codec, pin) & AC_WCAP_OUT_AMP) + snd_hda_codec_write(codec, pin, 0, AC_VERB_SET_AMP_GAIN_MUTE, + AMP_OUT_UNMUTE); + snd_hda_jack_detect_enable(codec, pin, per_pin->dev_id); + return 0; +} +EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_simple_init, "SND_HDA_CODEC_HDMI"); + +void snd_hda_hdmi_simple_remove(struct hda_codec *codec) +{ + struct hdmi_spec *spec = codec->spec; + + snd_array_free(&spec->pins); + snd_array_free(&spec->cvts); + kfree(spec); +} +EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_simple_remove, "SND_HDA_CODEC_HDMI"); + +int snd_hda_hdmi_simple_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hdmi_spec *spec = codec->spec; + + if (spec->hw_constraints_channels) { + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + spec->hw_constraints_channels); + } else { + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, 2); + } + + return snd_hda_multi_out_dig_open(codec, &spec->multiout); +} +EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_simple_pcm_open, "SND_HDA_CODEC_HDMI"); + +static int simple_playback_pcm_close(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hdmi_spec *spec = codec->spec; + + return snd_hda_multi_out_dig_close(codec, &spec->multiout); +} + +static int simple_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct hdmi_spec *spec = codec->spec; + + return snd_hda_multi_out_dig_prepare(codec, &spec->multiout, + stream_tag, format, substream); +} + +static const struct hda_pcm_stream simple_pcm_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + .ops = { + .open = snd_hda_hdmi_simple_pcm_open, + .close = simple_playback_pcm_close, + .prepare = simple_playback_pcm_prepare + }, +}; + +int snd_hda_hdmi_simple_probe(struct hda_codec *codec, + hda_nid_t cvt_nid, hda_nid_t pin_nid) +{ + struct hdmi_spec *spec; + struct hdmi_spec_per_cvt *per_cvt; + struct hdmi_spec_per_pin *per_pin; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return -ENOMEM; + + spec->codec = codec; + codec->spec = spec; + snd_array_init(&spec->pins, sizeof(struct hdmi_spec_per_pin), 1); + snd_array_init(&spec->cvts, sizeof(struct hdmi_spec_per_cvt), 1); + + spec->multiout.num_dacs = 0; /* no analog */ + spec->multiout.max_channels = 2; + spec->multiout.dig_out_nid = cvt_nid; + spec->num_cvts = 1; + spec->num_pins = 1; + per_pin = snd_array_new(&spec->pins); + per_cvt = snd_array_new(&spec->cvts); + if (!per_pin || !per_cvt) { + snd_hda_hdmi_simple_remove(codec); + return -ENOMEM; + } + per_cvt->cvt_nid = cvt_nid; + per_pin->pin_nid = pin_nid; + spec->pcm_playback = simple_pcm_playback; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_simple_probe, "SND_HDA_CODEC_HDMI"); + +/* + * driver entries + */ + +enum { MODEL_VIA }; + +/* VIA HDMI Implementation */ +#define VIAHDMI_CVT_NID 0x02 /* audio converter1 */ +#define VIAHDMI_PIN_NID 0x03 /* HDMI output pin1 */ + +static int simplehdmi_probe(struct hda_codec *codec, + const struct hda_device_id *id) +{ + switch (id->driver_data) { + case MODEL_VIA: + return snd_hda_hdmi_simple_probe(codec, VIAHDMI_CVT_NID, + VIAHDMI_PIN_NID); + default: + return -EINVAL; + } +} + +static const struct hda_codec_ops simplehdmi_codec_ops = { + .probe = simplehdmi_probe, + .remove = snd_hda_hdmi_simple_remove, + .build_controls = snd_hda_hdmi_simple_build_controls, + .build_pcms = snd_hda_hdmi_simple_build_pcms, + .init = snd_hda_hdmi_simple_init, + .unsol_event = snd_hda_hdmi_simple_unsol_event, +}; + +static const struct hda_device_id snd_hda_id_simplehdmi[] = { + HDA_CODEC_ID_MODEL(0x11069f80, "VX900 HDMI/DP", MODEL_VIA), + HDA_CODEC_ID_MODEL(0x11069f81, "VX900 HDMI/DP", MODEL_VIA), + {} /* terminator */ +}; + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Simple HDMI HD-audio codec support"); + +static struct hda_codec_driver simplehdmi_driver = { + .id = snd_hda_id_simplehdmi, + .ops = &simplehdmi_codec_ops, +}; + +module_hda_codec_driver(simplehdmi_driver); |