]>
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 | ||
8a2d6ae1 VS |
114 | pdata->num_pipes = INTEL_INFO(dev_priv)->num_pipes; |
115 | pdata->num_ports = IS_CHERRYVIEW(dev_priv) ? 3 : 2; /* B,C,D or B,C */ | |
116 | pdata->port[0].pipe = -1; | |
117 | pdata->port[1].pipe = -1; | |
118 | pdata->port[2].pipe = -1; | |
eef57324 JA |
119 | spin_lock_init(&pdata->lpe_audio_slock); |
120 | ||
121 | platdev = platform_device_register_full(&pinfo); | |
122 | if (IS_ERR(platdev)) { | |
123 | ret = PTR_ERR(platdev); | |
124 | DRM_ERROR("Failed to allocate LPE audio platform device\n"); | |
125 | goto err; | |
126 | } | |
127 | ||
128 | kfree(rsc); | |
129 | ||
183c0035 VS |
130 | pm_runtime_forbid(&platdev->dev); |
131 | pm_runtime_set_active(&platdev->dev); | |
132 | pm_runtime_enable(&platdev->dev); | |
133 | ||
eef57324 JA |
134 | return platdev; |
135 | ||
136 | err: | |
137 | kfree(rsc); | |
138 | kfree(pdata); | |
139 | return ERR_PTR(ret); | |
140 | } | |
141 | ||
142 | static void lpe_audio_platdev_destroy(struct drm_i915_private *dev_priv) | |
143 | { | |
48ae8074 CW |
144 | /* XXX Note that platform_device_register_full() allocates a dma_mask |
145 | * and never frees it. We can't free it here as we cannot guarantee | |
146 | * this is the last reference (i.e. that the dma_mask will not be | |
147 | * used after our unregister). So ee choose to leak the sizeof(u64) | |
148 | * allocation here - it should be fixed in the platform_device rather | |
149 | * than us fiddle with its internals. | |
150 | */ | |
151 | ||
eef57324 | 152 | platform_device_unregister(dev_priv->lpe_audio.platdev); |
eef57324 JA |
153 | } |
154 | ||
155 | static void lpe_audio_irq_unmask(struct irq_data *d) | |
156 | { | |
eef57324 JA |
157 | } |
158 | ||
159 | static void lpe_audio_irq_mask(struct irq_data *d) | |
160 | { | |
eef57324 JA |
161 | } |
162 | ||
163 | static struct irq_chip lpe_audio_irqchip = { | |
164 | .name = "hdmi_lpe_audio_irqchip", | |
165 | .irq_mask = lpe_audio_irq_mask, | |
166 | .irq_unmask = lpe_audio_irq_unmask, | |
167 | }; | |
168 | ||
169 | static int lpe_audio_irq_init(struct drm_i915_private *dev_priv) | |
170 | { | |
171 | int irq = dev_priv->lpe_audio.irq; | |
172 | ||
173 | WARN_ON(!intel_irqs_enabled(dev_priv)); | |
174 | irq_set_chip_and_handler_name(irq, | |
175 | &lpe_audio_irqchip, | |
176 | handle_simple_irq, | |
177 | "hdmi_lpe_audio_irq_handler"); | |
178 | ||
179 | return irq_set_chip_data(irq, dev_priv); | |
180 | } | |
181 | ||
182 | static bool lpe_audio_detect(struct drm_i915_private *dev_priv) | |
183 | { | |
184 | int lpe_present = false; | |
185 | ||
186 | if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { | |
187 | static const struct pci_device_id atom_hdaudio_ids[] = { | |
188 | /* Baytrail */ | |
189 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0f04)}, | |
190 | /* Braswell */ | |
191 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x2284)}, | |
192 | {} | |
193 | }; | |
194 | ||
195 | if (!pci_dev_present(atom_hdaudio_ids)) { | |
e0795606 | 196 | DRM_INFO("HDaudio controller not detected, using LPE audio instead\n"); |
eef57324 JA |
197 | lpe_present = true; |
198 | } | |
199 | } | |
200 | return lpe_present; | |
201 | } | |
202 | ||
203 | static int lpe_audio_setup(struct drm_i915_private *dev_priv) | |
204 | { | |
205 | int ret; | |
206 | ||
207 | dev_priv->lpe_audio.irq = irq_alloc_desc(0); | |
208 | if (dev_priv->lpe_audio.irq < 0) { | |
209 | DRM_ERROR("Failed to allocate IRQ desc: %d\n", | |
210 | dev_priv->lpe_audio.irq); | |
211 | ret = dev_priv->lpe_audio.irq; | |
212 | goto err; | |
213 | } | |
214 | ||
215 | DRM_DEBUG("irq = %d\n", dev_priv->lpe_audio.irq); | |
216 | ||
217 | ret = lpe_audio_irq_init(dev_priv); | |
218 | ||
219 | if (ret) { | |
220 | DRM_ERROR("Failed to initialize irqchip for lpe audio: %d\n", | |
221 | ret); | |
222 | goto err_free_irq; | |
223 | } | |
224 | ||
225 | dev_priv->lpe_audio.platdev = lpe_audio_platdev_create(dev_priv); | |
226 | ||
227 | if (IS_ERR(dev_priv->lpe_audio.platdev)) { | |
228 | ret = PTR_ERR(dev_priv->lpe_audio.platdev); | |
229 | DRM_ERROR("Failed to create lpe audio platform device: %d\n", | |
230 | ret); | |
231 | goto err_free_irq; | |
232 | } | |
233 | ||
9db13e5f TI |
234 | /* enable chicken bit; at least this is required for Dell Wyse 3040 |
235 | * with DP outputs (but only sometimes by some reason!) | |
236 | */ | |
237 | I915_WRITE(VLV_AUD_CHICKEN_BIT_REG, VLV_CHICKEN_BIT_DBG_ENABLE); | |
238 | ||
eef57324 JA |
239 | return 0; |
240 | err_free_irq: | |
241 | irq_free_desc(dev_priv->lpe_audio.irq); | |
242 | err: | |
243 | dev_priv->lpe_audio.irq = -1; | |
244 | dev_priv->lpe_audio.platdev = NULL; | |
245 | return ret; | |
246 | } | |
247 | ||
248 | /** | |
249 | * intel_lpe_audio_irq_handler() - forwards the LPE audio irq | |
250 | * @dev_priv: the i915 drm device private data | |
251 | * | |
252 | * the LPE Audio irq is forwarded to the irq handler registered by LPE audio | |
253 | * driver. | |
254 | */ | |
255 | void intel_lpe_audio_irq_handler(struct drm_i915_private *dev_priv) | |
256 | { | |
257 | int ret; | |
258 | ||
259 | if (!HAS_LPE_AUDIO(dev_priv)) | |
260 | return; | |
261 | ||
262 | ret = generic_handle_irq(dev_priv->lpe_audio.irq); | |
263 | if (ret) | |
264 | DRM_ERROR_RATELIMITED("error handling LPE audio irq: %d\n", | |
265 | ret); | |
266 | } | |
267 | ||
268 | /** | |
269 | * intel_lpe_audio_init() - detect and setup the bridge between HDMI LPE Audio | |
270 | * driver and i915 | |
271 | * @dev_priv: the i915 drm device private data | |
272 | * | |
273 | * Return: 0 if successful. non-zero if detection or | |
274 | * llocation/initialization fails | |
275 | */ | |
276 | int intel_lpe_audio_init(struct drm_i915_private *dev_priv) | |
277 | { | |
278 | int ret = -ENODEV; | |
279 | ||
280 | if (lpe_audio_detect(dev_priv)) { | |
281 | ret = lpe_audio_setup(dev_priv); | |
282 | if (ret < 0) | |
283 | DRM_ERROR("failed to setup LPE Audio bridge\n"); | |
284 | } | |
285 | return ret; | |
286 | } | |
287 | ||
288 | /** | |
289 | * intel_lpe_audio_teardown() - destroy the bridge between HDMI LPE | |
290 | * audio driver and i915 | |
291 | * @dev_priv: the i915 drm device private data | |
292 | * | |
293 | * release all the resources for LPE audio <-> i915 bridge. | |
294 | */ | |
295 | void intel_lpe_audio_teardown(struct drm_i915_private *dev_priv) | |
296 | { | |
297 | struct irq_desc *desc; | |
298 | ||
299 | if (!HAS_LPE_AUDIO(dev_priv)) | |
300 | return; | |
301 | ||
302 | desc = irq_to_desc(dev_priv->lpe_audio.irq); | |
303 | ||
eef57324 JA |
304 | lpe_audio_platdev_destroy(dev_priv); |
305 | ||
306 | irq_free_desc(dev_priv->lpe_audio.irq); | |
307 | } | |
46d196ec JA |
308 | |
309 | ||
310 | /** | |
311 | * intel_lpe_audio_notify() - notify lpe audio event | |
312 | * audio driver and i915 | |
313 | * @dev_priv: the i915 drm device private data | |
20be551e VS |
314 | * @pipe: pipe |
315 | * @port: port | |
46d196ec | 316 | * @eld : ELD data |
c98ec5ba VS |
317 | * @ls_clock: Link symbol clock in kHz |
318 | * @dp_output: Driving a DP output? | |
46d196ec JA |
319 | * |
320 | * Notify lpe audio driver of eld change. | |
321 | */ | |
322 | void intel_lpe_audio_notify(struct drm_i915_private *dev_priv, | |
20be551e VS |
323 | enum pipe pipe, enum port port, |
324 | const void *eld, int ls_clock, bool dp_output) | |
46d196ec | 325 | { |
8a2d6ae1 | 326 | unsigned long irqflags; |
a8562e4d VS |
327 | struct intel_hdmi_lpe_audio_pdata *pdata; |
328 | struct intel_hdmi_lpe_audio_port_pdata *ppdata; | |
d5d8c3a1 | 329 | u32 audio_enable; |
46d196ec JA |
330 | |
331 | if (!HAS_LPE_AUDIO(dev_priv)) | |
332 | return; | |
333 | ||
a8562e4d | 334 | pdata = dev_get_platdata(&dev_priv->lpe_audio.platdev->dev); |
8a2d6ae1 | 335 | ppdata = &pdata->port[port - PORT_B]; |
46d196ec | 336 | |
8a2d6ae1 | 337 | spin_lock_irqsave(&pdata->lpe_audio_slock, irqflags); |
46d196ec | 338 | |
d5d8c3a1 PLB |
339 | audio_enable = I915_READ(VLV_AUD_PORT_EN_DBG(port)); |
340 | ||
46d196ec | 341 | if (eld != NULL) { |
a8562e4d VS |
342 | memcpy(ppdata->eld, eld, HDMI_MAX_ELD_BYTES); |
343 | ppdata->pipe = pipe; | |
344 | ppdata->ls_clock = ls_clock; | |
345 | ppdata->dp_output = dp_output; | |
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); | |
46d196ec | 350 | } else { |
a8562e4d VS |
351 | memset(ppdata->eld, 0, HDMI_MAX_ELD_BYTES); |
352 | ppdata->pipe = -1; | |
353 | ppdata->ls_clock = 0; | |
354 | ppdata->dp_output = false; | |
d5d8c3a1 PLB |
355 | |
356 | /* Mute the amp for both DP and HDMI */ | |
357 | I915_WRITE(VLV_AUD_PORT_EN_DBG(port), | |
358 | audio_enable | VLV_AMP_MUTE); | |
46d196ec JA |
359 | } |
360 | ||
361 | if (pdata->notify_audio_lpe) | |
8a2d6ae1 | 362 | pdata->notify_audio_lpe(dev_priv->lpe_audio.platdev, port - PORT_B); |
46d196ec | 363 | |
8a2d6ae1 | 364 | spin_unlock_irqrestore(&pdata->lpe_audio_slock, irqflags); |
46d196ec | 365 | } |