123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365 |
- /*
- * Copyright © 2016 Intel Corporation
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the next
- * paragraph) shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- * IN THE SOFTWARE.
- *
- * Authors:
- * Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
- * Jerome Anand <jerome.anand@intel.com>
- * based on VED patches
- *
- */
- /**
- * DOC: LPE Audio integration for HDMI or DP playback
- *
- * Motivation:
- * Atom platforms (e.g. valleyview and cherryTrail) integrates a DMA-based
- * interface as an alternative to the traditional HDaudio path. While this
- * mode is unrelated to the LPE aka SST audio engine, the documentation refers
- * to this mode as LPE so we keep this notation for the sake of consistency.
- *
- * The interface is handled by a separate standalone driver maintained in the
- * ALSA subsystem for simplicity. To minimize the interaction between the two
- * subsystems, a bridge is setup between the hdmi-lpe-audio and i915:
- * 1. Create a platform device to share MMIO/IRQ resources
- * 2. Make the platform device child of i915 device for runtime PM.
- * 3. Create IRQ chip to forward the LPE audio irqs.
- * the hdmi-lpe-audio driver probes the lpe audio device and creates a new
- * sound card
- *
- * Threats:
- * Due to the restriction in Linux platform device model, user need manually
- * uninstall the hdmi-lpe-audio driver before uninstalling i915 module,
- * otherwise we might run into use-after-free issues after i915 removes the
- * platform device: even though hdmi-lpe-audio driver is released, the modules
- * is still in "installed" status.
- *
- * Implementation:
- * The MMIO/REG platform resources are created according to the registers
- * specification.
- * When forwarding LPE audio irqs, the flow control handler selection depends
- * on the platform, for example on valleyview handle_simple_irq is enough.
- *
- */
- #include <linux/acpi.h>
- #include <linux/device.h>
- #include <linux/pci.h>
- #include <linux/pm_runtime.h>
- #include "i915_drv.h"
- #include <linux/delay.h>
- #include <drm/intel_lpe_audio.h>
- #define HAS_LPE_AUDIO(dev_priv) ((dev_priv)->lpe_audio.platdev != NULL)
- static struct platform_device *
- lpe_audio_platdev_create(struct drm_i915_private *dev_priv)
- {
- int ret;
- struct drm_device *dev = &dev_priv->drm;
- struct platform_device_info pinfo = {};
- struct resource *rsc;
- struct platform_device *platdev;
- struct intel_hdmi_lpe_audio_pdata *pdata;
- pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
- if (!pdata)
- return ERR_PTR(-ENOMEM);
- rsc = kcalloc(2, sizeof(*rsc), GFP_KERNEL);
- if (!rsc) {
- kfree(pdata);
- return ERR_PTR(-ENOMEM);
- }
- rsc[0].start = rsc[0].end = dev_priv->lpe_audio.irq;
- rsc[0].flags = IORESOURCE_IRQ;
- rsc[0].name = "hdmi-lpe-audio-irq";
- rsc[1].start = pci_resource_start(dev->pdev, 0) +
- I915_HDMI_LPE_AUDIO_BASE;
- rsc[1].end = pci_resource_start(dev->pdev, 0) +
- I915_HDMI_LPE_AUDIO_BASE + I915_HDMI_LPE_AUDIO_SIZE - 1;
- rsc[1].flags = IORESOURCE_MEM;
- rsc[1].name = "hdmi-lpe-audio-mmio";
- pinfo.parent = dev->dev;
- pinfo.name = "hdmi-lpe-audio";
- pinfo.id = -1;
- pinfo.res = rsc;
- pinfo.num_res = 2;
- pinfo.data = pdata;
- pinfo.size_data = sizeof(*pdata);
- pinfo.dma_mask = DMA_BIT_MASK(32);
- pdata->num_pipes = INTEL_INFO(dev_priv)->num_pipes;
- pdata->num_ports = IS_CHERRYVIEW(dev_priv) ? 3 : 2; /* B,C,D or B,C */
- pdata->port[0].pipe = -1;
- pdata->port[1].pipe = -1;
- pdata->port[2].pipe = -1;
- spin_lock_init(&pdata->lpe_audio_slock);
- platdev = platform_device_register_full(&pinfo);
- if (IS_ERR(platdev)) {
- ret = PTR_ERR(platdev);
- DRM_ERROR("Failed to allocate LPE audio platform device\n");
- goto err;
- }
- kfree(rsc);
- pm_runtime_forbid(&platdev->dev);
- pm_runtime_set_active(&platdev->dev);
- pm_runtime_enable(&platdev->dev);
- return platdev;
- err:
- kfree(rsc);
- kfree(pdata);
- return ERR_PTR(ret);
- }
- static void lpe_audio_platdev_destroy(struct drm_i915_private *dev_priv)
- {
- /* XXX Note that platform_device_register_full() allocates a dma_mask
- * and never frees it. We can't free it here as we cannot guarantee
- * this is the last reference (i.e. that the dma_mask will not be
- * used after our unregister). So ee choose to leak the sizeof(u64)
- * allocation here - it should be fixed in the platform_device rather
- * than us fiddle with its internals.
- */
- platform_device_unregister(dev_priv->lpe_audio.platdev);
- }
- static void lpe_audio_irq_unmask(struct irq_data *d)
- {
- }
- static void lpe_audio_irq_mask(struct irq_data *d)
- {
- }
- static struct irq_chip lpe_audio_irqchip = {
- .name = "hdmi_lpe_audio_irqchip",
- .irq_mask = lpe_audio_irq_mask,
- .irq_unmask = lpe_audio_irq_unmask,
- };
- static int lpe_audio_irq_init(struct drm_i915_private *dev_priv)
- {
- int irq = dev_priv->lpe_audio.irq;
- WARN_ON(!intel_irqs_enabled(dev_priv));
- irq_set_chip_and_handler_name(irq,
- &lpe_audio_irqchip,
- handle_simple_irq,
- "hdmi_lpe_audio_irq_handler");
- return irq_set_chip_data(irq, dev_priv);
- }
- static bool lpe_audio_detect(struct drm_i915_private *dev_priv)
- {
- int lpe_present = false;
- if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) {
- static const struct pci_device_id atom_hdaudio_ids[] = {
- /* Baytrail */
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0f04)},
- /* Braswell */
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x2284)},
- {}
- };
- if (!pci_dev_present(atom_hdaudio_ids)) {
- DRM_INFO("%s\n", "HDaudio controller not detected, using LPE audio instead\n");
- lpe_present = true;
- }
- }
- return lpe_present;
- }
- static int lpe_audio_setup(struct drm_i915_private *dev_priv)
- {
- int ret;
- dev_priv->lpe_audio.irq = irq_alloc_desc(0);
- if (dev_priv->lpe_audio.irq < 0) {
- DRM_ERROR("Failed to allocate IRQ desc: %d\n",
- dev_priv->lpe_audio.irq);
- ret = dev_priv->lpe_audio.irq;
- goto err;
- }
- DRM_DEBUG("irq = %d\n", dev_priv->lpe_audio.irq);
- ret = lpe_audio_irq_init(dev_priv);
- if (ret) {
- DRM_ERROR("Failed to initialize irqchip for lpe audio: %d\n",
- ret);
- goto err_free_irq;
- }
- dev_priv->lpe_audio.platdev = lpe_audio_platdev_create(dev_priv);
- if (IS_ERR(dev_priv->lpe_audio.platdev)) {
- ret = PTR_ERR(dev_priv->lpe_audio.platdev);
- DRM_ERROR("Failed to create lpe audio platform device: %d\n",
- ret);
- goto err_free_irq;
- }
- /* enable chicken bit; at least this is required for Dell Wyse 3040
- * with DP outputs (but only sometimes by some reason!)
- */
- I915_WRITE(VLV_AUD_CHICKEN_BIT_REG, VLV_CHICKEN_BIT_DBG_ENABLE);
- return 0;
- err_free_irq:
- irq_free_desc(dev_priv->lpe_audio.irq);
- err:
- dev_priv->lpe_audio.irq = -1;
- dev_priv->lpe_audio.platdev = NULL;
- return ret;
- }
- /**
- * intel_lpe_audio_irq_handler() - forwards the LPE audio irq
- * @dev_priv: the i915 drm device private data
- *
- * the LPE Audio irq is forwarded to the irq handler registered by LPE audio
- * driver.
- */
- void intel_lpe_audio_irq_handler(struct drm_i915_private *dev_priv)
- {
- int ret;
- if (!HAS_LPE_AUDIO(dev_priv))
- return;
- ret = generic_handle_irq(dev_priv->lpe_audio.irq);
- if (ret)
- DRM_ERROR_RATELIMITED("error handling LPE audio irq: %d\n",
- ret);
- }
- /**
- * intel_lpe_audio_init() - detect and setup the bridge between HDMI LPE Audio
- * driver and i915
- * @dev_priv: the i915 drm device private data
- *
- * Return: 0 if successful. non-zero if detection or
- * llocation/initialization fails
- */
- int intel_lpe_audio_init(struct drm_i915_private *dev_priv)
- {
- int ret = -ENODEV;
- if (lpe_audio_detect(dev_priv)) {
- ret = lpe_audio_setup(dev_priv);
- if (ret < 0)
- DRM_ERROR("failed to setup LPE Audio bridge\n");
- }
- return ret;
- }
- /**
- * intel_lpe_audio_teardown() - destroy the bridge between HDMI LPE
- * audio driver and i915
- * @dev_priv: the i915 drm device private data
- *
- * release all the resources for LPE audio <-> i915 bridge.
- */
- void intel_lpe_audio_teardown(struct drm_i915_private *dev_priv)
- {
- struct irq_desc *desc;
- if (!HAS_LPE_AUDIO(dev_priv))
- return;
- desc = irq_to_desc(dev_priv->lpe_audio.irq);
- lpe_audio_platdev_destroy(dev_priv);
- irq_free_desc(dev_priv->lpe_audio.irq);
- }
- /**
- * intel_lpe_audio_notify() - notify lpe audio event
- * audio driver and i915
- * @dev_priv: the i915 drm device private data
- * @pipe: pipe
- * @port: port
- * @eld : ELD data
- * @ls_clock: Link symbol clock in kHz
- * @dp_output: Driving a DP output?
- *
- * Notify lpe audio driver of eld change.
- */
- void intel_lpe_audio_notify(struct drm_i915_private *dev_priv,
- enum pipe pipe, enum port port,
- const void *eld, int ls_clock, bool dp_output)
- {
- unsigned long irqflags;
- struct intel_hdmi_lpe_audio_pdata *pdata;
- struct intel_hdmi_lpe_audio_port_pdata *ppdata;
- u32 audio_enable;
- if (!HAS_LPE_AUDIO(dev_priv))
- return;
- pdata = dev_get_platdata(&dev_priv->lpe_audio.platdev->dev);
- ppdata = &pdata->port[port - PORT_B];
- spin_lock_irqsave(&pdata->lpe_audio_slock, irqflags);
- audio_enable = I915_READ(VLV_AUD_PORT_EN_DBG(port));
- if (eld != NULL) {
- memcpy(ppdata->eld, eld, HDMI_MAX_ELD_BYTES);
- ppdata->pipe = pipe;
- ppdata->ls_clock = ls_clock;
- ppdata->dp_output = dp_output;
- /* Unmute the amp for both DP and HDMI */
- I915_WRITE(VLV_AUD_PORT_EN_DBG(port),
- audio_enable & ~VLV_AMP_MUTE);
- } else {
- memset(ppdata->eld, 0, HDMI_MAX_ELD_BYTES);
- ppdata->pipe = -1;
- ppdata->ls_clock = 0;
- ppdata->dp_output = false;
- /* Mute the amp for both DP and HDMI */
- I915_WRITE(VLV_AUD_PORT_EN_DBG(port),
- audio_enable | VLV_AMP_MUTE);
- }
- if (pdata->notify_audio_lpe)
- pdata->notify_audio_lpe(dev_priv->lpe_audio.platdev, port - PORT_B);
- spin_unlock_irqrestore(&pdata->lpe_audio_slock, irqflags);
- }
|