2017-01-25 04:27:49 +05:30
/*
* 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>
2017-04-27 19:02:20 +03:00
# include <linux/pm_runtime.h>
2017-01-25 04:27:49 +05:30
# 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 ) ;
2017-04-27 19:02:25 +03:00
pdata - > pipe = - 1 ;
2017-01-25 04:27:49 +05:30
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 ) ;
2017-04-27 19:02:20 +03:00
pm_runtime_forbid ( & platdev - > dev ) ;
pm_runtime_set_active ( & platdev - > dev ) ;
pm_runtime_enable ( & platdev - > dev ) ;
2017-01-25 04:27:49 +05:30
return platdev ;
err :
kfree ( rsc ) ;
kfree ( pdata ) ;
return ERR_PTR ( ret ) ;
}
static void lpe_audio_platdev_destroy ( struct drm_i915_private * dev_priv )
{
2017-04-12 09:02:51 +01:00
/* 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 .
*/
2017-01-25 04:27:49 +05:30
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 ;
}
2017-02-02 11:03:48 +01:00
/* 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 ) ;
2017-01-25 04:27:49 +05:30
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 ) ;
}
2017-01-25 04:27:50 +05:30
/**
* intel_lpe_audio_notify ( ) - notify lpe audio event
* audio driver and i915
* @ dev_priv : the i915 drm device private data
2017-04-27 19:02:26 +03:00
* @ pipe : pipe
* @ port : port
2017-01-25 04:27:50 +05:30
* @ eld : ELD data
2017-04-27 19:02:24 +03:00
* @ ls_clock : Link symbol clock in kHz
* @ dp_output : Driving a DP output ?
2017-01-25 04:27:50 +05:30
*
* Notify lpe audio driver of eld change .
*/
void intel_lpe_audio_notify ( struct drm_i915_private * dev_priv ,
2017-04-27 19:02:26 +03:00
enum pipe pipe , enum port port ,
const void * eld , int ls_clock , bool dp_output )
2017-01-25 04:27:50 +05:30
{
unsigned long irq_flags ;
struct intel_hdmi_lpe_audio_pdata * pdata = NULL ;
2017-01-31 14:16:49 -06:00
u32 audio_enable ;
2017-01-25 04:27:50 +05:30
if ( ! HAS_LPE_AUDIO ( dev_priv ) )
return ;
pdata = dev_get_platdata (
& ( dev_priv - > lpe_audio . platdev - > dev ) ) ;
spin_lock_irqsave ( & pdata - > lpe_audio_slock , irq_flags ) ;
2017-01-31 14:16:49 -06:00
audio_enable = I915_READ ( VLV_AUD_PORT_EN_DBG ( port ) ) ;
2017-04-27 19:02:25 +03:00
pdata - > eld . port_id = port ;
2017-01-25 04:27:50 +05:30
if ( eld ! = NULL ) {
memcpy ( pdata - > eld . eld_data , eld ,
HDMI_MAX_ELD_BYTES ) ;
2017-04-27 19:02:25 +03:00
pdata - > pipe = pipe ;
2017-04-27 19:02:24 +03:00
pdata - > ls_clock = ls_clock ;
2017-01-31 14:16:48 -06:00
pdata - > dp_output = dp_output ;
2017-01-31 14:16:49 -06:00
/* Unmute the amp for both DP and HDMI */
I915_WRITE ( VLV_AUD_PORT_EN_DBG ( port ) ,
audio_enable & ~ VLV_AMP_MUTE ) ;
2017-01-25 04:27:50 +05:30
} else {
memset ( pdata - > eld . eld_data , 0 ,
HDMI_MAX_ELD_BYTES ) ;
2017-04-27 19:02:25 +03:00
pdata - > pipe = - 1 ;
2017-04-27 19:02:24 +03:00
pdata - > ls_clock = 0 ;
2017-01-31 14:16:48 -06:00
pdata - > dp_output = false ;
2017-01-31 14:16:49 -06:00
/* Mute the amp for both DP and HDMI */
I915_WRITE ( VLV_AUD_PORT_EN_DBG ( port ) ,
audio_enable | VLV_AMP_MUTE ) ;
2017-01-25 04:27:50 +05:30
}
if ( pdata - > notify_audio_lpe )
2017-01-30 17:56:39 +01:00
pdata - > notify_audio_lpe ( dev_priv - > lpe_audio . platdev ) ;
2017-01-25 04:27:50 +05:30
spin_unlock_irqrestore ( & pdata - > lpe_audio_slock ,
irq_flags ) ;
}