]>
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> | |
668e3b01 | 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 | ||
668e3b01 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 | { | |
dde7b00e 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 | { | |
152 | struct drm_i915_private *dev_priv = d->chip_data; | |
153 | unsigned long irqflags; | |
154 | u32 val = (I915_LPE_PIPE_A_INTERRUPT | | |
155 | I915_LPE_PIPE_B_INTERRUPT); | |
156 | ||
157 | if (IS_CHERRYVIEW(dev_priv)) | |
158 | val |= I915_LPE_PIPE_C_INTERRUPT; | |
159 | ||
160 | spin_lock_irqsave(&dev_priv->irq_lock, irqflags); | |
161 | ||
162 | dev_priv->irq_mask &= ~val; | |
163 | I915_WRITE(VLV_IIR, val); | |
164 | I915_WRITE(VLV_IIR, val); | |
165 | I915_WRITE(VLV_IMR, dev_priv->irq_mask); | |
166 | POSTING_READ(VLV_IMR); | |
167 | ||
168 | spin_unlock_irqrestore(&dev_priv->irq_lock, irqflags); | |
169 | } | |
170 | ||
171 | static void lpe_audio_irq_mask(struct irq_data *d) | |
172 | { | |
173 | struct drm_i915_private *dev_priv = d->chip_data; | |
174 | unsigned long irqflags; | |
175 | u32 val = (I915_LPE_PIPE_A_INTERRUPT | | |
176 | I915_LPE_PIPE_B_INTERRUPT); | |
177 | ||
178 | if (IS_CHERRYVIEW(dev_priv)) | |
179 | val |= I915_LPE_PIPE_C_INTERRUPT; | |
180 | ||
181 | spin_lock_irqsave(&dev_priv->irq_lock, irqflags); | |
182 | ||
183 | dev_priv->irq_mask |= val; | |
184 | I915_WRITE(VLV_IMR, dev_priv->irq_mask); | |
185 | I915_WRITE(VLV_IIR, val); | |
186 | I915_WRITE(VLV_IIR, val); | |
187 | POSTING_READ(VLV_IIR); | |
188 | ||
189 | spin_unlock_irqrestore(&dev_priv->irq_lock, irqflags); | |
190 | } | |
191 | ||
192 | static struct irq_chip lpe_audio_irqchip = { | |
193 | .name = "hdmi_lpe_audio_irqchip", | |
194 | .irq_mask = lpe_audio_irq_mask, | |
195 | .irq_unmask = lpe_audio_irq_unmask, | |
196 | }; | |
197 | ||
198 | static int lpe_audio_irq_init(struct drm_i915_private *dev_priv) | |
199 | { | |
200 | int irq = dev_priv->lpe_audio.irq; | |
201 | ||
202 | WARN_ON(!intel_irqs_enabled(dev_priv)); | |
203 | irq_set_chip_and_handler_name(irq, | |
204 | &lpe_audio_irqchip, | |
205 | handle_simple_irq, | |
206 | "hdmi_lpe_audio_irq_handler"); | |
207 | ||
208 | return irq_set_chip_data(irq, dev_priv); | |
209 | } | |
210 | ||
211 | static bool lpe_audio_detect(struct drm_i915_private *dev_priv) | |
212 | { | |
213 | int lpe_present = false; | |
214 | ||
215 | if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { | |
216 | static const struct pci_device_id atom_hdaudio_ids[] = { | |
217 | /* Baytrail */ | |
218 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0f04)}, | |
219 | /* Braswell */ | |
220 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x2284)}, | |
221 | {} | |
222 | }; | |
223 | ||
224 | if (!pci_dev_present(atom_hdaudio_ids)) { | |
225 | DRM_INFO("%s\n", "HDaudio controller not detected, using LPE audio instead\n"); | |
226 | lpe_present = true; | |
227 | } | |
228 | } | |
229 | return lpe_present; | |
230 | } | |
231 | ||
232 | static int lpe_audio_setup(struct drm_i915_private *dev_priv) | |
233 | { | |
234 | int ret; | |
235 | ||
236 | dev_priv->lpe_audio.irq = irq_alloc_desc(0); | |
237 | if (dev_priv->lpe_audio.irq < 0) { | |
238 | DRM_ERROR("Failed to allocate IRQ desc: %d\n", | |
239 | dev_priv->lpe_audio.irq); | |
240 | ret = dev_priv->lpe_audio.irq; | |
241 | goto err; | |
242 | } | |
243 | ||
244 | DRM_DEBUG("irq = %d\n", dev_priv->lpe_audio.irq); | |
245 | ||
246 | ret = lpe_audio_irq_init(dev_priv); | |
247 | ||
248 | if (ret) { | |
249 | DRM_ERROR("Failed to initialize irqchip for lpe audio: %d\n", | |
250 | ret); | |
251 | goto err_free_irq; | |
252 | } | |
253 | ||
254 | dev_priv->lpe_audio.platdev = lpe_audio_platdev_create(dev_priv); | |
255 | ||
256 | if (IS_ERR(dev_priv->lpe_audio.platdev)) { | |
257 | ret = PTR_ERR(dev_priv->lpe_audio.platdev); | |
258 | DRM_ERROR("Failed to create lpe audio platform device: %d\n", | |
259 | ret); | |
260 | goto err_free_irq; | |
261 | } | |
262 | ||
9db13e5f TI |
263 | /* enable chicken bit; at least this is required for Dell Wyse 3040 |
264 | * with DP outputs (but only sometimes by some reason!) | |
265 | */ | |
266 | I915_WRITE(VLV_AUD_CHICKEN_BIT_REG, VLV_CHICKEN_BIT_DBG_ENABLE); | |
267 | ||
eef57324 JA |
268 | return 0; |
269 | err_free_irq: | |
270 | irq_free_desc(dev_priv->lpe_audio.irq); | |
271 | err: | |
272 | dev_priv->lpe_audio.irq = -1; | |
273 | dev_priv->lpe_audio.platdev = NULL; | |
274 | return ret; | |
275 | } | |
276 | ||
277 | /** | |
278 | * intel_lpe_audio_irq_handler() - forwards the LPE audio irq | |
279 | * @dev_priv: the i915 drm device private data | |
280 | * | |
281 | * the LPE Audio irq is forwarded to the irq handler registered by LPE audio | |
282 | * driver. | |
283 | */ | |
284 | void intel_lpe_audio_irq_handler(struct drm_i915_private *dev_priv) | |
285 | { | |
286 | int ret; | |
287 | ||
288 | if (!HAS_LPE_AUDIO(dev_priv)) | |
289 | return; | |
290 | ||
291 | ret = generic_handle_irq(dev_priv->lpe_audio.irq); | |
292 | if (ret) | |
293 | DRM_ERROR_RATELIMITED("error handling LPE audio irq: %d\n", | |
294 | ret); | |
295 | } | |
296 | ||
297 | /** | |
298 | * intel_lpe_audio_init() - detect and setup the bridge between HDMI LPE Audio | |
299 | * driver and i915 | |
300 | * @dev_priv: the i915 drm device private data | |
301 | * | |
302 | * Return: 0 if successful. non-zero if detection or | |
303 | * llocation/initialization fails | |
304 | */ | |
305 | int intel_lpe_audio_init(struct drm_i915_private *dev_priv) | |
306 | { | |
307 | int ret = -ENODEV; | |
308 | ||
309 | if (lpe_audio_detect(dev_priv)) { | |
310 | ret = lpe_audio_setup(dev_priv); | |
311 | if (ret < 0) | |
312 | DRM_ERROR("failed to setup LPE Audio bridge\n"); | |
313 | } | |
314 | return ret; | |
315 | } | |
316 | ||
317 | /** | |
318 | * intel_lpe_audio_teardown() - destroy the bridge between HDMI LPE | |
319 | * audio driver and i915 | |
320 | * @dev_priv: the i915 drm device private data | |
321 | * | |
322 | * release all the resources for LPE audio <-> i915 bridge. | |
323 | */ | |
324 | void intel_lpe_audio_teardown(struct drm_i915_private *dev_priv) | |
325 | { | |
326 | struct irq_desc *desc; | |
327 | ||
328 | if (!HAS_LPE_AUDIO(dev_priv)) | |
329 | return; | |
330 | ||
331 | desc = irq_to_desc(dev_priv->lpe_audio.irq); | |
332 | ||
333 | lpe_audio_irq_mask(&desc->irq_data); | |
334 | ||
335 | lpe_audio_platdev_destroy(dev_priv); | |
336 | ||
337 | irq_free_desc(dev_priv->lpe_audio.irq); | |
338 | } | |
46d196ec JA |
339 | |
340 | ||
341 | /** | |
342 | * intel_lpe_audio_notify() - notify lpe audio event | |
343 | * audio driver and i915 | |
344 | * @dev_priv: the i915 drm device private data | |
345 | * @eld : ELD data | |
31581b60 | 346 | * @pipe: pipe id |
46d196ec JA |
347 | * @port: port id |
348 | * @tmds_clk_speed: tmds clock frequency in Hz | |
349 | * | |
350 | * Notify lpe audio driver of eld change. | |
351 | */ | |
352 | void intel_lpe_audio_notify(struct drm_i915_private *dev_priv, | |
f95e29b9 | 353 | void *eld, int port, int pipe, int tmds_clk_speed, |
b5f2be9a | 354 | bool dp_output, int link_rate) |
46d196ec JA |
355 | { |
356 | unsigned long irq_flags; | |
357 | struct intel_hdmi_lpe_audio_pdata *pdata = NULL; | |
d5d8c3a1 | 358 | u32 audio_enable; |
46d196ec JA |
359 | |
360 | if (!HAS_LPE_AUDIO(dev_priv)) | |
361 | return; | |
362 | ||
363 | pdata = dev_get_platdata( | |
364 | &(dev_priv->lpe_audio.platdev->dev)); | |
365 | ||
366 | spin_lock_irqsave(&pdata->lpe_audio_slock, irq_flags); | |
367 | ||
d5d8c3a1 PLB |
368 | audio_enable = I915_READ(VLV_AUD_PORT_EN_DBG(port)); |
369 | ||
46d196ec JA |
370 | if (eld != NULL) { |
371 | memcpy(pdata->eld.eld_data, eld, | |
372 | HDMI_MAX_ELD_BYTES); | |
373 | pdata->eld.port_id = port; | |
f95e29b9 | 374 | pdata->eld.pipe_id = pipe; |
46d196ec JA |
375 | pdata->hdmi_connected = true; |
376 | ||
b5f2be9a | 377 | pdata->dp_output = dp_output; |
46d196ec JA |
378 | if (tmds_clk_speed) |
379 | pdata->tmds_clock_speed = tmds_clk_speed; | |
b5f2be9a PLB |
380 | if (link_rate) |
381 | pdata->link_rate = link_rate; | |
d5d8c3a1 PLB |
382 | |
383 | /* Unmute the amp for both DP and HDMI */ | |
384 | I915_WRITE(VLV_AUD_PORT_EN_DBG(port), | |
385 | audio_enable & ~VLV_AMP_MUTE); | |
386 | ||
46d196ec JA |
387 | } else { |
388 | memset(pdata->eld.eld_data, 0, | |
389 | HDMI_MAX_ELD_BYTES); | |
390 | pdata->hdmi_connected = false; | |
b5f2be9a | 391 | pdata->dp_output = false; |
d5d8c3a1 PLB |
392 | |
393 | /* Mute the amp for both DP and HDMI */ | |
394 | I915_WRITE(VLV_AUD_PORT_EN_DBG(port), | |
395 | audio_enable | VLV_AMP_MUTE); | |
46d196ec JA |
396 | } |
397 | ||
398 | if (pdata->notify_audio_lpe) | |
b1c01f4d | 399 | pdata->notify_audio_lpe(dev_priv->lpe_audio.platdev); |
46d196ec JA |
400 | else |
401 | pdata->notify_pending = true; | |
402 | ||
403 | spin_unlock_irqrestore(&pdata->lpe_audio_slock, | |
404 | irq_flags); | |
405 | } |