1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Non-generic simple HDMI codec support
4 */
5
6 #include <linux/slab.h>
7 #include <linux/module.h>
8 #include "hdmi_local.h"
9 #include "hda_jack.h"
10
snd_hda_hdmi_simple_build_pcms(struct hda_codec * codec)11 int snd_hda_hdmi_simple_build_pcms(struct hda_codec *codec)
12 {
13 struct hdmi_spec *spec = codec->spec;
14 struct hda_pcm *info;
15 unsigned int chans;
16 struct hda_pcm_stream *pstr;
17 struct hdmi_spec_per_cvt *per_cvt;
18
19 per_cvt = get_cvt(spec, 0);
20 chans = get_wcaps(codec, per_cvt->cvt_nid);
21 chans = get_wcaps_channels(chans);
22
23 info = snd_hda_codec_pcm_new(codec, "HDMI 0");
24 if (!info)
25 return -ENOMEM;
26 spec->pcm_rec[0].pcm = info;
27 info->pcm_type = HDA_PCM_TYPE_HDMI;
28 pstr = &info->stream[SNDRV_PCM_STREAM_PLAYBACK];
29 *pstr = spec->pcm_playback;
30 pstr->nid = per_cvt->cvt_nid;
31 if (pstr->channels_max <= 2 && chans && chans <= 16)
32 pstr->channels_max = chans;
33
34 return 0;
35 }
36 EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_simple_build_pcms, "SND_HDA_CODEC_HDMI");
37
38 /* unsolicited event for jack sensing */
snd_hda_hdmi_simple_unsol_event(struct hda_codec * codec,unsigned int res)39 void snd_hda_hdmi_simple_unsol_event(struct hda_codec *codec,
40 unsigned int res)
41 {
42 snd_hda_jack_set_dirty_all(codec);
43 snd_hda_jack_report_sync(codec);
44 }
45 EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_simple_unsol_event, "SND_HDA_CODEC_HDMI");
46
free_hdmi_jack_priv(struct snd_jack * jack)47 static void free_hdmi_jack_priv(struct snd_jack *jack)
48 {
49 struct hdmi_pcm *pcm = jack->private_data;
50
51 pcm->jack = NULL;
52 }
53
simple_hdmi_build_jack(struct hda_codec * codec)54 static int simple_hdmi_build_jack(struct hda_codec *codec)
55 {
56 char hdmi_str[32] = "HDMI/DP";
57 struct hdmi_spec *spec = codec->spec;
58 struct snd_jack *jack;
59 struct hdmi_pcm *pcmp = get_hdmi_pcm(spec, 0);
60 int pcmdev = pcmp->pcm->device;
61 int err;
62
63 if (pcmdev > 0)
64 sprintf(hdmi_str + strlen(hdmi_str), ",pcm=%d", pcmdev);
65
66 err = snd_jack_new(codec->card, hdmi_str, SND_JACK_AVOUT, &jack,
67 true, false);
68 if (err < 0)
69 return err;
70
71 pcmp->jack = jack;
72 jack->private_data = pcmp;
73 jack->private_free = free_hdmi_jack_priv;
74 return 0;
75 }
76
snd_hda_hdmi_simple_build_controls(struct hda_codec * codec)77 int snd_hda_hdmi_simple_build_controls(struct hda_codec *codec)
78 {
79 struct hdmi_spec *spec = codec->spec;
80 struct hdmi_spec_per_cvt *per_cvt;
81 int err;
82
83 per_cvt = get_cvt(spec, 0);
84 err = snd_hda_create_dig_out_ctls(codec, per_cvt->cvt_nid,
85 per_cvt->cvt_nid,
86 HDA_PCM_TYPE_HDMI);
87 if (err < 0)
88 return err;
89 return simple_hdmi_build_jack(codec);
90 }
91 EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_simple_build_controls, "SND_HDA_CODEC_HDMI");
92
snd_hda_hdmi_simple_init(struct hda_codec * codec)93 int snd_hda_hdmi_simple_init(struct hda_codec *codec)
94 {
95 struct hdmi_spec *spec = codec->spec;
96 struct hdmi_spec_per_pin *per_pin = get_pin(spec, 0);
97 hda_nid_t pin = per_pin->pin_nid;
98
99 snd_hda_codec_write(codec, pin, 0,
100 AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
101 /* some codecs require to unmute the pin */
102 if (get_wcaps(codec, pin) & AC_WCAP_OUT_AMP)
103 snd_hda_codec_write(codec, pin, 0, AC_VERB_SET_AMP_GAIN_MUTE,
104 AMP_OUT_UNMUTE);
105 snd_hda_jack_detect_enable(codec, pin, per_pin->dev_id);
106 return 0;
107 }
108 EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_simple_init, "SND_HDA_CODEC_HDMI");
109
snd_hda_hdmi_simple_remove(struct hda_codec * codec)110 void snd_hda_hdmi_simple_remove(struct hda_codec *codec)
111 {
112 struct hdmi_spec *spec = codec->spec;
113
114 snd_array_free(&spec->pins);
115 snd_array_free(&spec->cvts);
116 kfree(spec);
117 }
118 EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_simple_remove, "SND_HDA_CODEC_HDMI");
119
snd_hda_hdmi_simple_pcm_open(struct hda_pcm_stream * hinfo,struct hda_codec * codec,struct snd_pcm_substream * substream)120 int snd_hda_hdmi_simple_pcm_open(struct hda_pcm_stream *hinfo,
121 struct hda_codec *codec,
122 struct snd_pcm_substream *substream)
123 {
124 struct hdmi_spec *spec = codec->spec;
125
126 if (spec->hw_constraints_channels) {
127 snd_pcm_hw_constraint_list(substream->runtime, 0,
128 SNDRV_PCM_HW_PARAM_CHANNELS,
129 spec->hw_constraints_channels);
130 } else {
131 snd_pcm_hw_constraint_step(substream->runtime, 0,
132 SNDRV_PCM_HW_PARAM_CHANNELS, 2);
133 }
134
135 return snd_hda_multi_out_dig_open(codec, &spec->multiout);
136 }
137 EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_simple_pcm_open, "SND_HDA_CODEC_HDMI");
138
simple_playback_pcm_close(struct hda_pcm_stream * hinfo,struct hda_codec * codec,struct snd_pcm_substream * substream)139 static int simple_playback_pcm_close(struct hda_pcm_stream *hinfo,
140 struct hda_codec *codec,
141 struct snd_pcm_substream *substream)
142 {
143 struct hdmi_spec *spec = codec->spec;
144
145 return snd_hda_multi_out_dig_close(codec, &spec->multiout);
146 }
147
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)148 static int simple_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
149 struct hda_codec *codec,
150 unsigned int stream_tag,
151 unsigned int format,
152 struct snd_pcm_substream *substream)
153 {
154 struct hdmi_spec *spec = codec->spec;
155
156 return snd_hda_multi_out_dig_prepare(codec, &spec->multiout,
157 stream_tag, format, substream);
158 }
159
160 static const struct hda_pcm_stream simple_pcm_playback = {
161 .substreams = 1,
162 .channels_min = 2,
163 .channels_max = 2,
164 .ops = {
165 .open = snd_hda_hdmi_simple_pcm_open,
166 .close = simple_playback_pcm_close,
167 .prepare = simple_playback_pcm_prepare
168 },
169 };
170
snd_hda_hdmi_simple_probe(struct hda_codec * codec,hda_nid_t cvt_nid,hda_nid_t pin_nid)171 int snd_hda_hdmi_simple_probe(struct hda_codec *codec,
172 hda_nid_t cvt_nid, hda_nid_t pin_nid)
173 {
174 struct hdmi_spec *spec;
175 struct hdmi_spec_per_cvt *per_cvt;
176 struct hdmi_spec_per_pin *per_pin;
177
178 spec = kzalloc(sizeof(*spec), GFP_KERNEL);
179 if (!spec)
180 return -ENOMEM;
181
182 spec->codec = codec;
183 codec->spec = spec;
184 snd_array_init(&spec->pins, sizeof(struct hdmi_spec_per_pin), 1);
185 snd_array_init(&spec->cvts, sizeof(struct hdmi_spec_per_cvt), 1);
186
187 spec->multiout.num_dacs = 0; /* no analog */
188 spec->multiout.max_channels = 2;
189 spec->multiout.dig_out_nid = cvt_nid;
190 spec->num_cvts = 1;
191 spec->num_pins = 1;
192 per_pin = snd_array_new(&spec->pins);
193 per_cvt = snd_array_new(&spec->cvts);
194 if (!per_pin || !per_cvt) {
195 snd_hda_hdmi_simple_remove(codec);
196 return -ENOMEM;
197 }
198 per_cvt->cvt_nid = cvt_nid;
199 per_pin->pin_nid = pin_nid;
200 spec->pcm_playback = simple_pcm_playback;
201
202 return 0;
203 }
204 EXPORT_SYMBOL_NS_GPL(snd_hda_hdmi_simple_probe, "SND_HDA_CODEC_HDMI");
205
206 /*
207 * driver entries
208 */
209
210 enum { MODEL_VIA };
211
212 /* VIA HDMI Implementation */
213 #define VIAHDMI_CVT_NID 0x02 /* audio converter1 */
214 #define VIAHDMI_PIN_NID 0x03 /* HDMI output pin1 */
215
simplehdmi_probe(struct hda_codec * codec,const struct hda_device_id * id)216 static int simplehdmi_probe(struct hda_codec *codec,
217 const struct hda_device_id *id)
218 {
219 switch (id->driver_data) {
220 case MODEL_VIA:
221 return snd_hda_hdmi_simple_probe(codec, VIAHDMI_CVT_NID,
222 VIAHDMI_PIN_NID);
223 default:
224 return -EINVAL;
225 }
226 }
227
228 static const struct hda_codec_ops simplehdmi_codec_ops = {
229 .probe = simplehdmi_probe,
230 .remove = snd_hda_hdmi_simple_remove,
231 .build_controls = snd_hda_hdmi_simple_build_controls,
232 .build_pcms = snd_hda_hdmi_simple_build_pcms,
233 .init = snd_hda_hdmi_simple_init,
234 .unsol_event = snd_hda_hdmi_simple_unsol_event,
235 };
236
237 static const struct hda_device_id snd_hda_id_simplehdmi[] = {
238 HDA_CODEC_ID_MODEL(0x11069f80, "VX900 HDMI/DP", MODEL_VIA),
239 HDA_CODEC_ID_MODEL(0x11069f81, "VX900 HDMI/DP", MODEL_VIA),
240 {} /* terminator */
241 };
242
243 MODULE_LICENSE("GPL");
244 MODULE_DESCRIPTION("Simple HDMI HD-audio codec support");
245
246 static struct hda_codec_driver simplehdmi_driver = {
247 .id = snd_hda_id_simplehdmi,
248 .ops = &simplehdmi_codec_ops,
249 };
250
251 module_hda_codec_driver(simplehdmi_driver);
252