]>
Commit | Line | Data |
---|---|---|
eef57324 JA |
1 | /* |
2 | * Copyright © 2016 Intel Corporation | |
3 | * | |
4 | * Permission is hereby granted, free of charge, to any person obtaining a | |
5 | * copy of this software and associated documentation files (the "Software"), | |
6 | * to deal in the Software without restriction, including without limitation | |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
8 | * and/or sell copies of the Software, and to permit persons to whom the | |
9 | * Software is furnished to do so, subject to the following conditions: | |
10 | * | |
11 | * The above copyright notice and this permission notice (including the next | |
12 | * paragraph) shall be included in all copies or substantial portions of the | |
13 | * Software. | |
14 | * | |
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
21 | * IN THE SOFTWARE. | |
22 | * | |
23 | * Authors: | |
24 | * Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> | |
25 | * Jerome Anand <jerome.anand@intel.com> | |
26 | * based on VED patches | |
27 | * | |
28 | */ | |
29 | ||
30 | /** | |
31 | * DOC: LPE Audio integration for HDMI or DP playback | |
32 | * | |
33 | * Motivation: | |
34 | * Atom platforms (e.g. valleyview and cherryTrail) integrates a DMA-based | |
35 | * interface as an alternative to the traditional HDaudio path. While this | |
36 | * mode is unrelated to the LPE aka SST audio engine, the documentation refers | |
37 | * to this mode as LPE so we keep this notation for the sake of consistency. | |
38 | * | |
39 | * The interface is handled by a separate standalone driver maintained in the | |
40 | * ALSA subsystem for simplicity. To minimize the interaction between the two | |
41 | * subsystems, a bridge is setup between the hdmi-lpe-audio and i915: | |
42 | * 1. Create a platform device to share MMIO/IRQ resources | |
43 | * 2. Make the platform device child of i915 device for runtime PM. | |
44 | * 3. Create IRQ chip to forward the LPE audio irqs. | |
45 | * the hdmi-lpe-audio driver probes the lpe audio device and creates a new | |
46 | * sound card | |
47 | * | |
48 | * Threats: | |
49 | * Due to the restriction in Linux platform device model, user need manually | |
50 | * uninstall the hdmi-lpe-audio driver before uninstalling i915 module, | |
51 | * otherwise we might run into use-after-free issues after i915 removes the | |
52 | * platform device: even though hdmi-lpe-audio driver is released, the modules | |
53 | * is still in "installed" status. | |
54 | * | |
55 | * Implementation: | |
56 | * The MMIO/REG platform resources are created according to the registers | |
57 | * specification. | |
58 | * When forwarding LPE audio irqs, the flow control handler selection depends | |
59 | * on the platform, for example on valleyview handle_simple_irq is enough. | |
60 | * | |
61 | */ | |
62 | ||
63 | #include <linux/acpi.h> | |
64 | #include <linux/device.h> | |
65 | #include <linux/pci.h> | |
183c0035 | 66 | #include <linux/pm_runtime.h> |
eef57324 JA |
67 | |
68 | #include "i915_drv.h" | |
69 | #include <linux/delay.h> | |
70 | #include <drm/intel_lpe_audio.h> | |
71 | ||
72 | #define HAS_LPE_AUDIO(dev_priv) ((dev_priv)->lpe_audio.platdev != NULL) | |
73 | ||
74 | static struct platform_device * | |
75 | lpe_audio_platdev_create(struct drm_i915_private *dev_priv) | |
76 | { | |
77 | int ret; | |
78 | struct drm_device *dev = &dev_priv->drm; | |
79 | struct platform_device_info pinfo = {}; | |
80 | struct resource *rsc; | |
81 | struct platform_device *platdev; | |
82 | struct intel_hdmi_lpe_audio_pdata *pdata; | |
83 | ||
84 | pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); | |
85 | if (!pdata) | |
86 | return ERR_PTR(-ENOMEM); | |
87 | ||
88 | rsc = kcalloc(2, sizeof(*rsc), GFP_KERNEL); | |
89 | if (!rsc) { | |
90 | kfree(pdata); | |
91 | return ERR_PTR(-ENOMEM); | |
92 | } | |
93 | ||
94 | rsc[0].start = rsc[0].end = dev_priv->lpe_audio.irq; | |
95 | rsc[0].flags = IORESOURCE_IRQ; | |
96 | rsc[0].name = "hdmi-lpe-audio-irq"; | |
97 | ||
98 | rsc[1].start = pci_resource_start(dev->pdev, 0) + | |
99 | I915_HDMI_LPE_AUDIO_BASE; | |
100 | rsc[1].end = pci_resource_start(dev->pdev, 0) + | |
101 | I915_HDMI_LPE_AUDIO_BASE + I915_HDMI_LPE_AUDIO_SIZE - 1; | |
102 | rsc[1].flags = IORESOURCE_MEM; | |
103 | rsc[1].name = "hdmi-lpe-audio-mmio"; | |
104 | ||
105 | pinfo.parent = dev->dev; | |
106 | pinfo.name = "hdmi-lpe-audio"; | |
107 | pinfo.id = -1; | |
108 | pinfo.res = rsc; | |
109 | pinfo.num_res = 2; | |
110 | pinfo.data = pdata; | |
111 | pinfo.size_data = sizeof(*pdata); | |
112 | pinfo.dma_mask = DMA_BIT_MASK(32); | |
113 | ||
114 | spin_lock_init(&pdata->lpe_audio_slock); | |
115 | ||
116 | platdev = platform_device_register_full(&pinfo); | |
117 | if (IS_ERR(platdev)) { | |
118 | ret = PTR_ERR(platdev); | |
119 | DRM_ERROR("Failed to allocate LPE audio platform device\n"); | |
120 | goto err; | |
121 | } | |
122 | ||
123 | kfree(rsc); | |
124 | ||
183c0035 VS |
125 | pm_runtime_forbid(&platdev->dev); |
126 | pm_runtime_set_active(&platdev->dev); | |
127 | pm_runtime_enable(&platdev->dev); | |
128 | ||
eef57324 JA |
129 | return platdev; |
130 | ||
131 | err: | |
132 | kfree(rsc); | |
133 | kfree(pdata); | |
134 | return ERR_PTR(ret); | |
135 | } | |
136 | ||
137 | static void lpe_audio_platdev_destroy(struct drm_i915_private *dev_priv) | |
138 | { | |
48ae8074 CW |
139 | /* XXX Note that platform_device_register_full() allocates a dma_mask |
140 | * and never frees it. We can't free it here as we cannot guarantee | |
141 | * this is the last reference (i.e. that the dma_mask will not be | |
142 | * used after our unregister). So ee choose to leak the sizeof(u64) | |
143 | * allocation here - it should be fixed in the platform_device rather | |
144 | * than us fiddle with its internals. | |
145 | */ | |
146 | ||
eef57324 | 147 | platform_device_unregister(dev_priv->lpe_audio.platdev); |
eef57324 JA |
148 | } |
149 | ||
150 | static void lpe_audio_irq_unmask(struct irq_data *d) | |
151 | { | |
eef57324 JA |
152 | } |
153 | ||
154 | static void lpe_audio_irq_mask(struct irq_data *d) | |
155 | { | |
eef57324 JA |
156 | } |
157 | ||
158 | static struct irq_chip lpe_audio_irqchip = { | |
159 | .name = "hdmi_lpe_audio_irqchip", | |
160 | .irq_mask = lpe_audio_irq_mask, | |
161 | .irq_unmask = lpe_audio_irq_unmask, | |
162 | }; | |
163 | ||
164 | static int lpe_audio_irq_init(struct drm_i915_private *dev_priv) | |
165 | { | |
166 | int irq = dev_priv->lpe_audio.irq; | |
167 | ||
168 | WARN_ON(!intel_irqs_enabled(dev_priv)); | |
169 | irq_set_chip_and_handler_name(irq, | |
170 | &lpe_audio_irqchip, | |
171 | handle_simple_irq, | |
172 | "hdmi_lpe_audio_irq_handler"); | |
173 | ||
174 | return irq_set_chip_data(irq, dev_priv); | |
175 | } | |
176 | ||
177 | static bool lpe_audio_detect(struct drm_i915_private *dev_priv) | |
178 | { | |
179 | int lpe_present = false; | |
180 | ||
181 | if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { | |
182 | static const struct pci_device_id atom_hdaudio_ids[] = { | |
183 | /* Baytrail */ | |
184 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0f04)}, | |
185 | /* Braswell */ | |
186 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x2284)}, | |
187 | {} | |
188 | }; | |
189 | ||
190 | if (!pci_dev_present(atom_hdaudio_ids)) { | |
191 | DRM_INFO("%s\n", "HDaudio controller not detected, using LPE audio instead\n"); | |
192 | lpe_present = true; | |
193 | } | |
194 | } | |
195 | return lpe_present; | |
196 | } | |
197 | ||
198 | static int lpe_audio_setup(struct drm_i915_private *dev_priv) | |
199 | { | |
200 | int ret; | |
201 | ||
202 | dev_priv->lpe_audio.irq = irq_alloc_desc(0); | |
203 | if (dev_priv->lpe_audio.irq < 0) { | |
204 | DRM_ERROR("Failed to allocate IRQ desc: %d\n", | |
205 | dev_priv->lpe_audio.irq); | |
206 | ret = dev_priv->lpe_audio.irq; | |
207 | goto err; | |
208 | } | |
209 | ||
210 | DRM_DEBUG("irq = %d\n", dev_priv->lpe_audio.irq); | |
211 | ||
212 | ret = lpe_audio_irq_init(dev_priv); | |
213 | ||
214 | if (ret) { | |
215 | DRM_ERROR("Failed to initialize irqchip for lpe audio: %d\n", | |
216 | ret); | |
217 | goto err_free_irq; | |
218 | } | |
219 | ||
220 | dev_priv->lpe_audio.platdev = lpe_audio_platdev_create(dev_priv); | |
221 | ||
222 | if (IS_ERR(dev_priv->lpe_audio.platdev)) { | |
223 | ret = PTR_ERR(dev_priv->lpe_audio.platdev); | |
224 | DRM_ERROR("Failed to create lpe audio platform device: %d\n", | |
225 | ret); | |
226 | goto err_free_irq; | |
227 | } | |
228 | ||
9db13e5f TI |
229 | /* enable chicken bit; at least this is required for Dell Wyse 3040 |
230 | * with DP outputs (but only sometimes by some reason!) | |
231 | */ | |
232 | I915_WRITE(VLV_AUD_CHICKEN_BIT_REG, VLV_CHICKEN_BIT_DBG_ENABLE); | |
233 | ||
eef57324 JA |
234 | return 0; |
235 | err_free_irq: | |
236 | irq_free_desc(dev_priv->lpe_audio.irq); | |
237 | err: | |
238 | dev_priv->lpe_audio.irq = -1; | |
239 | dev_priv->lpe_audio.platdev = NULL; | |
240 | return ret; | |
241 | } | |
242 | ||
243 | /** | |
244 | * intel_lpe_audio_irq_handler() - forwards the LPE audio irq | |
245 | * @dev_priv: the i915 drm device private data | |
246 | * | |
247 | * the LPE Audio irq is forwarded to the irq handler registered by LPE audio | |
248 | * driver. | |
249 | */ | |
250 | void intel_lpe_audio_irq_handler(struct drm_i915_private *dev_priv) | |
251 | { | |
252 | int ret; | |
253 | ||
254 | if (!HAS_LPE_AUDIO(dev_priv)) | |
255 | return; | |
256 | ||
257 | ret = generic_handle_irq(dev_priv->lpe_audio.irq); | |
258 | if (ret) | |
259 | DRM_ERROR_RATELIMITED("error handling LPE audio irq: %d\n", | |
260 | ret); | |
261 | } | |
262 | ||
263 | /** | |
264 | * intel_lpe_audio_init() - detect and setup the bridge between HDMI LPE Audio | |
265 | * driver and i915 | |
266 | * @dev_priv: the i915 drm device private data | |
267 | * | |
268 | * Return: 0 if successful. non-zero if detection or | |
269 | * llocation/initialization fails | |
270 | */ | |
271 | int intel_lpe_audio_init(struct drm_i915_private *dev_priv) | |
272 | { | |
273 | int ret = -ENODEV; | |
274 | ||
275 | if (lpe_audio_detect(dev_priv)) { | |
276 | ret = lpe_audio_setup(dev_priv); | |
277 | if (ret < 0) | |
278 | DRM_ERROR("failed to setup LPE Audio bridge\n"); | |
279 | } | |
280 | return ret; | |
281 | } | |
282 | ||
283 | /** | |
284 | * intel_lpe_audio_teardown() - destroy the bridge between HDMI LPE | |
285 | * audio driver and i915 | |
286 | * @dev_priv: the i915 drm device private data | |
287 | * | |
288 | * release all the resources for LPE audio <-> i915 bridge. | |
289 | */ | |
290 | void intel_lpe_audio_teardown(struct drm_i915_private *dev_priv) | |
291 | { | |
292 | struct irq_desc *desc; | |
293 | ||
294 | if (!HAS_LPE_AUDIO(dev_priv)) | |
295 | return; | |
296 | ||
297 | desc = irq_to_desc(dev_priv->lpe_audio.irq); | |
298 | ||
eef57324 JA |
299 | lpe_audio_platdev_destroy(dev_priv); |
300 | ||
301 | irq_free_desc(dev_priv->lpe_audio.irq); | |
302 | } | |
46d196ec JA |
303 | |
304 | ||
305 | /** | |
306 | * intel_lpe_audio_notify() - notify lpe audio event | |
307 | * audio driver and i915 | |
308 | * @dev_priv: the i915 drm device private data | |
309 | * @eld : ELD data | |
31581b60 | 310 | * @pipe: pipe id |
46d196ec JA |
311 | * @port: port id |
312 | * @tmds_clk_speed: tmds clock frequency in Hz | |
313 | * | |
314 | * Notify lpe audio driver of eld change. | |
315 | */ | |
316 | void intel_lpe_audio_notify(struct drm_i915_private *dev_priv, | |
f95e29b9 | 317 | void *eld, int port, int pipe, int tmds_clk_speed, |
b5f2be9a | 318 | bool dp_output, int link_rate) |
46d196ec JA |
319 | { |
320 | unsigned long irq_flags; | |
321 | struct intel_hdmi_lpe_audio_pdata *pdata = NULL; | |
d5d8c3a1 | 322 | u32 audio_enable; |
46d196ec JA |
323 | |
324 | if (!HAS_LPE_AUDIO(dev_priv)) | |
325 | return; | |
326 | ||
327 | pdata = dev_get_platdata( | |
328 | &(dev_priv->lpe_audio.platdev->dev)); | |
329 | ||
330 | spin_lock_irqsave(&pdata->lpe_audio_slock, irq_flags); | |
331 | ||
d5d8c3a1 PLB |
332 | audio_enable = I915_READ(VLV_AUD_PORT_EN_DBG(port)); |
333 | ||
46d196ec JA |
334 | if (eld != NULL) { |
335 | memcpy(pdata->eld.eld_data, eld, | |
336 | HDMI_MAX_ELD_BYTES); | |
337 | pdata->eld.port_id = port; | |
f95e29b9 | 338 | pdata->eld.pipe_id = pipe; |
46d196ec JA |
339 | pdata->hdmi_connected = true; |
340 | ||
b5f2be9a | 341 | pdata->dp_output = dp_output; |
46d196ec JA |
342 | if (tmds_clk_speed) |
343 | pdata->tmds_clock_speed = tmds_clk_speed; | |
b5f2be9a PLB |
344 | if (link_rate) |
345 | pdata->link_rate = link_rate; | |
d5d8c3a1 PLB |
346 | |
347 | /* Unmute the amp for both DP and HDMI */ | |
348 | I915_WRITE(VLV_AUD_PORT_EN_DBG(port), | |
349 | audio_enable & ~VLV_AMP_MUTE); | |
350 | ||
46d196ec JA |
351 | } else { |
352 | memset(pdata->eld.eld_data, 0, | |
353 | HDMI_MAX_ELD_BYTES); | |
354 | pdata->hdmi_connected = false; | |
b5f2be9a | 355 | pdata->dp_output = false; |
d5d8c3a1 PLB |
356 | |
357 | /* Mute the amp for both DP and HDMI */ | |
358 | I915_WRITE(VLV_AUD_PORT_EN_DBG(port), | |
359 | audio_enable | VLV_AMP_MUTE); | |
46d196ec JA |
360 | } |
361 | ||
362 | if (pdata->notify_audio_lpe) | |
b1c01f4d | 363 | pdata->notify_audio_lpe(dev_priv->lpe_audio.platdev); |
46d196ec JA |
364 | |
365 | spin_unlock_irqrestore(&pdata->lpe_audio_slock, | |
366 | irq_flags); | |
367 | } |