Commit | Line | Data |
---|---|---|
18382ead SP |
1 | /* |
2 | * hdac_hdmi.c - ASoc HDA-HDMI codec driver for Intel platforms | |
3 | * | |
4 | * Copyright (C) 2014-2015 Intel Corp | |
5 | * Author: Samreen Nilofer <samreen.nilofer@intel.com> | |
6 | * Subhransu S. Prusty <subhransu.s.prusty@intel.com> | |
7 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License as published by | |
11 | * the Free Software Foundation; version 2 of the License. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, but | |
14 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | * General Public License for more details. | |
17 | * | |
18 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
19 | */ | |
20 | #include <linux/init.h> | |
21 | #include <linux/delay.h> | |
22 | #include <linux/module.h> | |
23 | #include <linux/pm_runtime.h> | |
24 | #include <sound/pcm_params.h> | |
25 | #include <sound/soc.h> | |
26 | #include <sound/hdaudio_ext.h> | |
27 | #include "../../hda/local.h" | |
28 | ||
29 | #define PIN_OUT (AC_PINCTL_OUT_EN) | |
30 | #define HDA_MAX_CONNECTIONS 32 | |
31 | ||
32 | struct hdac_hdmi_cvt_params { | |
33 | unsigned int channels_min; | |
34 | unsigned int channels_max; | |
35 | u32 rates; | |
36 | u64 formats; | |
37 | unsigned int maxbps; | |
38 | }; | |
39 | ||
40 | struct hdac_hdmi_cvt { | |
41 | hda_nid_t nid; | |
42 | struct hdac_hdmi_cvt_params params; | |
43 | }; | |
44 | ||
45 | struct hdac_hdmi_pin { | |
46 | hda_nid_t nid; | |
47 | int num_mux_nids; | |
48 | hda_nid_t mux_nids[HDA_MAX_CONNECTIONS]; | |
49 | }; | |
50 | ||
51 | struct hdac_hdmi_dai_pin_map { | |
52 | int dai_id; | |
53 | struct hdac_hdmi_pin pin; | |
54 | struct hdac_hdmi_cvt cvt; | |
55 | }; | |
56 | ||
57 | struct hdac_hdmi_priv { | |
58 | hda_nid_t pin_nid[3]; | |
59 | hda_nid_t cvt_nid[3]; | |
60 | struct hdac_hdmi_dai_pin_map dai_map[3]; | |
61 | }; | |
62 | ||
63 | static int | |
64 | hdac_hdmi_query_cvt_params(struct hdac_device *hdac, struct hdac_hdmi_cvt *cvt) | |
65 | { | |
66 | int err; | |
67 | ||
68 | /* Only stereo supported as of now */ | |
69 | cvt->params.channels_min = cvt->params.channels_max = 2; | |
70 | ||
71 | err = snd_hdac_query_supported_pcm(hdac, cvt->nid, | |
72 | &cvt->params.rates, | |
73 | &cvt->params.formats, | |
74 | &cvt->params.maxbps); | |
75 | if (err < 0) | |
76 | dev_err(&hdac->dev, | |
77 | "Failed to query pcm params for nid %d: %d\n", | |
78 | cvt->nid, err); | |
79 | ||
80 | return err; | |
81 | } | |
82 | ||
83 | static int hdac_hdmi_query_pin_connlist(struct hdac_ext_device *hdac, | |
84 | struct hdac_hdmi_pin *pin) | |
85 | { | |
86 | if (!(get_wcaps(&hdac->hdac, pin->nid) & AC_WCAP_CONN_LIST)) { | |
87 | dev_warn(&hdac->hdac.dev, | |
88 | "HDMI: pin %d wcaps %#x does not support connection list\n", | |
89 | pin->nid, get_wcaps(&hdac->hdac, pin->nid)); | |
90 | return -EINVAL; | |
91 | } | |
92 | ||
93 | pin->num_mux_nids = snd_hdac_get_connections(&hdac->hdac, pin->nid, | |
94 | pin->mux_nids, HDA_MAX_CONNECTIONS); | |
95 | if (pin->num_mux_nids == 0) { | |
96 | dev_err(&hdac->hdac.dev, "No connections found\n"); | |
97 | return -ENODEV; | |
98 | } | |
99 | ||
100 | return pin->num_mux_nids; | |
101 | } | |
102 | ||
103 | static void hdac_hdmi_fill_widget_info(struct snd_soc_dapm_widget *w, | |
104 | enum snd_soc_dapm_type id, | |
105 | const char *wname, const char *stream) | |
106 | { | |
107 | w->id = id; | |
108 | w->name = wname; | |
109 | w->sname = stream; | |
110 | w->reg = SND_SOC_NOPM; | |
111 | w->shift = 0; | |
112 | w->kcontrol_news = NULL; | |
113 | w->num_kcontrols = 0; | |
114 | w->priv = NULL; | |
115 | } | |
116 | ||
117 | static void hdac_hdmi_fill_route(struct snd_soc_dapm_route *route, | |
118 | const char *sink, const char *control, const char *src) | |
119 | { | |
120 | route->sink = sink; | |
121 | route->source = src; | |
122 | route->control = control; | |
123 | route->connected = NULL; | |
124 | } | |
125 | ||
126 | static void create_fill_widget_route_map(struct snd_soc_dapm_context *dapm, | |
127 | struct hdac_hdmi_dai_pin_map *dai_map) | |
128 | { | |
129 | struct snd_soc_dapm_route route[1]; | |
130 | struct snd_soc_dapm_widget widgets[2] = { {0} }; | |
131 | ||
132 | memset(&route, 0, sizeof(route)); | |
133 | ||
134 | hdac_hdmi_fill_widget_info(&widgets[0], snd_soc_dapm_output, | |
135 | "hif1 Output", NULL); | |
136 | hdac_hdmi_fill_widget_info(&widgets[1], snd_soc_dapm_aif_in, | |
137 | "Coverter 1", "hif1"); | |
138 | ||
139 | hdac_hdmi_fill_route(&route[0], "hif1 Output", NULL, "Coverter 1"); | |
140 | ||
141 | snd_soc_dapm_new_controls(dapm, widgets, ARRAY_SIZE(widgets)); | |
142 | snd_soc_dapm_add_routes(dapm, route, ARRAY_SIZE(route)); | |
143 | } | |
144 | ||
145 | static int hdac_hdmi_init_dai_map(struct hdac_ext_device *edev, | |
146 | struct hdac_hdmi_dai_pin_map *dai_map, | |
147 | hda_nid_t pin_nid, hda_nid_t cvt_nid, int dai_id) | |
148 | { | |
149 | int ret; | |
150 | ||
151 | dai_map->dai_id = dai_id; | |
152 | dai_map->pin.nid = pin_nid; | |
153 | ||
154 | ret = hdac_hdmi_query_pin_connlist(edev, &dai_map->pin); | |
155 | if (ret < 0) { | |
156 | dev_err(&edev->hdac.dev, | |
157 | "Error querying connection list: %d\n", ret); | |
158 | return ret; | |
159 | } | |
160 | ||
161 | dai_map->cvt.nid = cvt_nid; | |
162 | ||
163 | /* Enable out path for this pin widget */ | |
164 | snd_hdac_codec_write(&edev->hdac, pin_nid, 0, | |
165 | AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT); | |
166 | ||
167 | /* Enable transmission */ | |
168 | snd_hdac_codec_write(&edev->hdac, cvt_nid, 0, | |
169 | AC_VERB_SET_DIGI_CONVERT_1, 1); | |
170 | ||
171 | /* Category Code (CC) to zero */ | |
172 | snd_hdac_codec_write(&edev->hdac, cvt_nid, 0, | |
173 | AC_VERB_SET_DIGI_CONVERT_2, 0); | |
174 | ||
175 | snd_hdac_codec_write(&edev->hdac, pin_nid, 0, | |
176 | AC_VERB_SET_CONNECT_SEL, 0); | |
177 | ||
178 | return hdac_hdmi_query_cvt_params(&edev->hdac, &dai_map->cvt); | |
179 | } | |
180 | ||
181 | /* | |
182 | * Parse all nodes and store the cvt/pin nids in array | |
183 | * Add one time initialization for pin and cvt widgets | |
184 | */ | |
185 | static int hdac_hdmi_parse_and_map_nid(struct hdac_ext_device *edev) | |
186 | { | |
187 | hda_nid_t nid; | |
188 | int i; | |
189 | struct hdac_device *hdac = &edev->hdac; | |
190 | struct hdac_hdmi_priv *hdmi = edev->private_data; | |
191 | int cvt_nid = 0, pin_nid = 0; | |
192 | ||
193 | hdac->num_nodes = snd_hdac_get_sub_nodes(hdac, hdac->afg, &nid); | |
194 | if (!nid || hdac->num_nodes < 0) { | |
195 | dev_warn(&hdac->dev, "HDMI: failed to get afg sub nodes\n"); | |
196 | return -EINVAL; | |
197 | } | |
198 | ||
199 | hdac->start_nid = nid; | |
200 | ||
201 | for (i = 0; i < hdac->num_nodes; i++, nid++) { | |
202 | unsigned int caps; | |
203 | unsigned int type; | |
204 | ||
205 | caps = get_wcaps(hdac, nid); | |
206 | type = get_wcaps_type(caps); | |
207 | ||
208 | if (!(caps & AC_WCAP_DIGITAL)) | |
209 | continue; | |
210 | ||
211 | switch (type) { | |
212 | ||
213 | case AC_WID_AUD_OUT: | |
214 | hdmi->cvt_nid[cvt_nid] = nid; | |
215 | cvt_nid++; | |
216 | break; | |
217 | ||
218 | case AC_WID_PIN: | |
219 | hdmi->pin_nid[pin_nid] = nid; | |
220 | pin_nid++; | |
221 | break; | |
222 | } | |
223 | } | |
224 | ||
225 | hdac->end_nid = nid; | |
226 | ||
227 | if (!pin_nid || !cvt_nid) | |
228 | return -EIO; | |
229 | ||
230 | /* | |
231 | * Currently on board only 1 pin and 1 converter is enabled for | |
232 | * simplification, more will be added eventually | |
233 | * So using fixed map for dai_id:pin:cvt | |
234 | */ | |
235 | return hdac_hdmi_init_dai_map(edev, &hdmi->dai_map[0], hdmi->pin_nid[0], | |
236 | hdmi->cvt_nid[0], 0); | |
237 | } | |
238 | ||
239 | static int hdmi_codec_probe(struct snd_soc_codec *codec) | |
240 | { | |
241 | struct hdac_ext_device *edev = snd_soc_codec_get_drvdata(codec); | |
242 | struct hdac_hdmi_priv *hdmi = edev->private_data; | |
243 | struct snd_soc_dapm_context *dapm = | |
244 | snd_soc_component_get_dapm(&codec->component); | |
245 | ||
246 | edev->scodec = codec; | |
247 | ||
248 | create_fill_widget_route_map(dapm, &hdmi->dai_map[0]); | |
249 | ||
250 | /* Imp: Store the card pointer in hda_codec */ | |
251 | edev->card = dapm->card->snd_card; | |
252 | ||
253 | return 0; | |
254 | } | |
255 | ||
256 | static struct snd_soc_codec_driver hdmi_hda_codec = { | |
257 | .probe = hdmi_codec_probe, | |
258 | .idle_bias_off = true, | |
259 | }; | |
260 | ||
261 | static struct snd_soc_dai_driver hdmi_dais[] = { | |
262 | { .name = "intel-hdmi-hif1", | |
263 | .playback = { | |
264 | .stream_name = "hif1", | |
265 | .channels_min = 2, | |
266 | .channels_max = 2, | |
267 | .rates = SNDRV_PCM_RATE_32000 | | |
268 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | | |
269 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | | |
270 | SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000, | |
271 | .formats = SNDRV_PCM_FMTBIT_S16_LE | | |
272 | SNDRV_PCM_FMTBIT_S20_3LE | | |
273 | SNDRV_PCM_FMTBIT_S24_LE | | |
274 | SNDRV_PCM_FMTBIT_S32_LE, | |
275 | ||
276 | }, | |
277 | }, | |
278 | }; | |
279 | ||
280 | static int hdac_hdmi_dev_probe(struct hdac_ext_device *edev) | |
281 | { | |
282 | struct hdac_device *codec = &edev->hdac; | |
283 | struct hdac_hdmi_priv *hdmi_priv; | |
284 | int ret = 0; | |
285 | ||
286 | hdmi_priv = devm_kzalloc(&codec->dev, sizeof(*hdmi_priv), GFP_KERNEL); | |
287 | if (hdmi_priv == NULL) | |
288 | return -ENOMEM; | |
289 | ||
290 | edev->private_data = hdmi_priv; | |
291 | ||
292 | dev_set_drvdata(&codec->dev, edev); | |
293 | ||
294 | ret = hdac_hdmi_parse_and_map_nid(edev); | |
295 | if (ret < 0) | |
296 | return ret; | |
297 | ||
298 | /* ASoC specific initialization */ | |
299 | return snd_soc_register_codec(&codec->dev, &hdmi_hda_codec, | |
300 | hdmi_dais, ARRAY_SIZE(hdmi_dais)); | |
301 | } | |
302 | ||
303 | static int hdac_hdmi_dev_remove(struct hdac_ext_device *edev) | |
304 | { | |
305 | snd_soc_unregister_codec(&edev->hdac.dev); | |
306 | ||
307 | return 0; | |
308 | } | |
309 | ||
310 | static const struct hda_device_id hdmi_list[] = { | |
311 | HDA_CODEC_EXT_ENTRY(0x80862809, 0x100000, "Skylake HDMI", 0), | |
312 | {} | |
313 | }; | |
314 | ||
315 | MODULE_DEVICE_TABLE(hdaudio, hdmi_list); | |
316 | ||
317 | static struct hdac_ext_driver hdmi_driver = { | |
318 | . hdac = { | |
319 | .driver = { | |
320 | .name = "HDMI HDA Codec", | |
321 | }, | |
322 | .id_table = hdmi_list, | |
323 | }, | |
324 | .probe = hdac_hdmi_dev_probe, | |
325 | .remove = hdac_hdmi_dev_remove, | |
326 | }; | |
327 | ||
328 | static int __init hdmi_init(void) | |
329 | { | |
330 | return snd_hda_ext_driver_register(&hdmi_driver); | |
331 | } | |
332 | ||
333 | static void __exit hdmi_exit(void) | |
334 | { | |
335 | snd_hda_ext_driver_unregister(&hdmi_driver); | |
336 | } | |
337 | ||
338 | module_init(hdmi_init); | |
339 | module_exit(hdmi_exit); | |
340 | ||
341 | MODULE_LICENSE("GPL v2"); | |
342 | MODULE_DESCRIPTION("HDMI HD codec"); | |
343 | MODULE_AUTHOR("Samreen Nilofer<samreen.nilofer@intel.com>"); | |
344 | MODULE_AUTHOR("Subhransu S. Prusty<subhransu.s.prusty@intel.com>"); |