2019-03-12 00:10:41 +02:00
// SPDX-License-Identifier: GPL-2.0
2014-01-16 00:58:35 +02:00
/*
2020-02-23 22:44:19 +02:00
* Copyright ( c ) 2013 - 2020 , Intel Corporation . All rights reserved .
2014-01-16 00:58:35 +02:00
* Intel Management Engine Interface ( Intel MEI ) Linux driver
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/device.h>
# include <linux/errno.h>
# include <linux/types.h>
# include <linux/pci.h>
# include <linux/init.h>
# include <linux/sched.h>
# include <linux/interrupt.h>
# include <linux/workqueue.h>
2016-01-07 16:46:13 +01:00
# include <linux/pm_domain.h>
2014-03-18 22:52:03 +02:00
# include <linux/pm_runtime.h>
2014-01-16 00:58:35 +02:00
# include <linux/mei.h>
# include "mei_dev.h"
# include "hw-txe.h"
2014-03-16 14:35:58 +02:00
static const struct pci_device_id mei_txe_pci_tbl [ ] = {
2014-09-29 16:31:45 +03:00
{ PCI_VDEVICE ( INTEL , 0x0F18 ) } , /* Baytrail */
2014-11-18 00:13:21 +02:00
{ PCI_VDEVICE ( INTEL , 0x2298 ) } , /* Cherrytrail */
2014-09-29 16:31:45 +03:00
2014-01-16 00:58:35 +02:00
{ 0 , }
} ;
MODULE_DEVICE_TABLE ( pci , mei_txe_pci_tbl ) ;
2014-12-04 22:43:07 +01:00
# ifdef CONFIG_PM
2014-03-18 22:52:06 +02:00
static inline void mei_txe_set_pm_domain ( struct mei_device * dev ) ;
static inline void mei_txe_unset_pm_domain ( struct mei_device * dev ) ;
# else
static inline void mei_txe_set_pm_domain ( struct mei_device * dev ) { }
static inline void mei_txe_unset_pm_domain ( struct mei_device * dev ) { }
2014-12-04 22:43:07 +01:00
# endif /* CONFIG_PM */
2014-01-16 00:58:35 +02:00
/**
2015-02-10 10:39:35 +02:00
* mei_txe_probe - Device Initialization Routine
2014-01-16 00:58:35 +02:00
*
* @ pdev : PCI device structure
* @ ent : entry in mei_txe_pci_tbl
*
2014-09-29 16:31:49 +03:00
* Return : 0 on success , < 0 on failure .
2014-01-16 00:58:35 +02:00
*/
static int mei_txe_probe ( struct pci_dev * pdev , const struct pci_device_id * ent )
{
struct mei_device * dev ;
struct mei_txe_hw * hw ;
2017-01-26 17:16:26 +02:00
const int mask = BIT ( SEC_BAR ) | BIT ( BRIDGE_BAR ) ;
2014-01-16 00:58:35 +02:00
int err ;
/* enable pci dev */
2017-01-26 17:16:26 +02:00
err = pcim_enable_device ( pdev ) ;
2014-01-16 00:58:35 +02:00
if ( err ) {
dev_err ( & pdev - > dev , " failed to enable pci device. \n " ) ;
goto end ;
}
/* set PCI host mastering */
pci_set_master ( pdev ) ;
2017-01-26 17:16:26 +02:00
/* pci request regions and mapping IO device memory for mei driver */
err = pcim_iomap_regions ( pdev , mask , KBUILD_MODNAME ) ;
2014-01-16 00:58:35 +02:00
if ( err ) {
dev_err ( & pdev - > dev , " failed to get pci regions. \n " ) ;
2017-01-26 17:16:26 +02:00
goto end ;
2014-01-16 00:58:35 +02:00
}
err = pci_set_dma_mask ( pdev , DMA_BIT_MASK ( 36 ) ) ;
if ( err ) {
err = pci_set_dma_mask ( pdev , DMA_BIT_MASK ( 32 ) ) ;
if ( err ) {
dev_err ( & pdev - > dev , " No suitable DMA available. \n " ) ;
2017-01-26 17:16:26 +02:00
goto end ;
2014-01-16 00:58:35 +02:00
}
}
/* allocates and initializes the mei dev structure */
2014-09-29 16:31:45 +03:00
dev = mei_txe_dev_init ( pdev ) ;
2014-01-16 00:58:35 +02:00
if ( ! dev ) {
err = - ENOMEM ;
2017-01-26 17:16:26 +02:00
goto end ;
2014-01-16 00:58:35 +02:00
}
hw = to_txe_hw ( dev ) ;
2017-01-26 17:16:26 +02:00
hw - > mem_addr = pcim_iomap_table ( pdev ) ;
2014-01-16 00:58:35 +02:00
pci_enable_msi ( pdev ) ;
/* clear spurious interrupts */
mei_clear_interrupts ( dev ) ;
/* request and enable interrupt */
if ( pci_dev_msi_enabled ( pdev ) )
err = request_threaded_irq ( pdev - > irq ,
NULL ,
mei_txe_irq_thread_handler ,
IRQF_ONESHOT , KBUILD_MODNAME , dev ) ;
else
err = request_threaded_irq ( pdev - > irq ,
mei_txe_irq_quick_handler ,
mei_txe_irq_thread_handler ,
IRQF_SHARED , KBUILD_MODNAME , dev ) ;
if ( err ) {
dev_err ( & pdev - > dev , " mei: request_threaded_irq failure. irq = %d \n " ,
pdev - > irq ) ;
2017-01-26 17:16:26 +02:00
goto end ;
2014-01-16 00:58:35 +02:00
}
if ( mei_start ( dev ) ) {
dev_err ( & pdev - > dev , " init hw failure. \n " ) ;
err = - ENODEV ;
goto release_irq ;
}
2014-03-18 22:52:03 +02:00
pm_runtime_set_autosuspend_delay ( & pdev - > dev , MEI_TXI_RPM_TIMEOUT ) ;
pm_runtime_use_autosuspend ( & pdev - > dev ) ;
2014-06-23 15:10:35 +03:00
err = mei_register ( dev , & pdev - > dev ) ;
2014-01-16 00:58:35 +02:00
if ( err )
2016-02-07 23:35:20 +02:00
goto stop ;
2014-01-16 00:58:35 +02:00
pci_set_drvdata ( pdev , dev ) ;
2017-08-03 17:30:19 +03:00
/*
* MEI requires to resume from runtime suspend mode
* in order to perform link reset flow upon system suspend .
*/
2020-04-18 18:53:01 +02:00
dev_pm_set_driver_flags ( & pdev - > dev , DPM_FLAG_NO_DIRECT_COMPLETE ) ;
2017-08-03 17:30:19 +03:00
2014-03-18 22:52:06 +02:00
/*
2017-09-26 09:18:27 +03:00
* TXE maps runtime suspend / resume to own power gating states ,
* hence we need to go around native PCI runtime service which
* eventually brings the device into D3cold / hot state .
* But the TXE device cannot wake up from D3 unlike from own
* power gating . To get around PCI device native runtime pm ,
* TXE uses runtime pm domain handlers which take precedence .
*/
mei_txe_set_pm_domain ( dev ) ;
2014-03-18 22:52:06 +02:00
2014-03-18 22:52:03 +02:00
pm_runtime_put_noidle ( & pdev - > dev ) ;
2014-01-16 00:58:35 +02:00
return 0 ;
2016-02-07 23:35:20 +02:00
stop :
mei_stop ( dev ) ;
2014-01-16 00:58:35 +02:00
release_irq :
mei_cancel_work ( dev ) ;
mei_disable_interrupts ( dev ) ;
free_irq ( pdev - > irq , dev ) ;
end :
dev_err ( & pdev - > dev , " initialization failed. \n " ) ;
return err ;
}
2017-03-20 15:04:02 +02:00
/**
* mei_txe_remove - Device Shutdown Routine
*
* @ pdev : PCI device structure
*
* mei_txe_shutdown is called from the reboot notifier
* it ' s a simplified version of remove so we go down
* faster .
*/
static void mei_txe_shutdown ( struct pci_dev * pdev )
{
struct mei_device * dev ;
dev = pci_get_drvdata ( pdev ) ;
if ( ! dev )
return ;
dev_dbg ( & pdev - > dev , " shutdown \n " ) ;
mei_stop ( dev ) ;
2017-09-26 09:18:27 +03:00
mei_txe_unset_pm_domain ( dev ) ;
2017-03-20 15:04:02 +02:00
mei_disable_interrupts ( dev ) ;
free_irq ( pdev - > irq , dev ) ;
}
2014-01-16 00:58:35 +02:00
/**
2015-02-10 10:39:35 +02:00
* mei_txe_remove - Device Removal Routine
2014-01-16 00:58:35 +02:00
*
* @ pdev : PCI device structure
*
* mei_remove is called by the PCI subsystem to alert the driver
* that it should release a PCI device .
*/
static void mei_txe_remove ( struct pci_dev * pdev )
{
struct mei_device * dev ;
dev = pci_get_drvdata ( pdev ) ;
if ( ! dev ) {
2017-01-26 17:16:26 +02:00
dev_err ( & pdev - > dev , " mei: dev == NULL \n " ) ;
2014-01-16 00:58:35 +02:00
return ;
}
2014-03-18 22:52:03 +02:00
pm_runtime_get_noresume ( & pdev - > dev ) ;
2014-01-16 00:58:35 +02:00
mei_stop ( dev ) ;
2017-09-26 09:18:27 +03:00
mei_txe_unset_pm_domain ( dev ) ;
2014-03-18 22:52:06 +02:00
2014-01-16 00:58:35 +02:00
mei_disable_interrupts ( dev ) ;
free_irq ( pdev - > irq , dev ) ;
mei_deregister ( dev ) ;
}
2014-02-18 14:31:09 +02:00
# ifdef CONFIG_PM_SLEEP
2014-01-16 00:58:35 +02:00
static int mei_txe_pci_suspend ( struct device * device )
{
struct pci_dev * pdev = to_pci_dev ( device ) ;
struct mei_device * dev = pci_get_drvdata ( pdev ) ;
if ( ! dev )
return - ENODEV ;
dev_dbg ( & pdev - > dev , " suspend \n " ) ;
mei_stop ( dev ) ;
mei_disable_interrupts ( dev ) ;
free_irq ( pdev - > irq , dev ) ;
pci_disable_msi ( pdev ) ;
return 0 ;
}
static int mei_txe_pci_resume ( struct device * device )
{
struct pci_dev * pdev = to_pci_dev ( device ) ;
struct mei_device * dev ;
int err ;
dev = pci_get_drvdata ( pdev ) ;
if ( ! dev )
return - ENODEV ;
pci_enable_msi ( pdev ) ;
mei_clear_interrupts ( dev ) ;
/* request and enable interrupt */
if ( pci_dev_msi_enabled ( pdev ) )
err = request_threaded_irq ( pdev - > irq ,
NULL ,
mei_txe_irq_thread_handler ,
IRQF_ONESHOT , KBUILD_MODNAME , dev ) ;
else
err = request_threaded_irq ( pdev - > irq ,
mei_txe_irq_quick_handler ,
mei_txe_irq_thread_handler ,
IRQF_SHARED , KBUILD_MODNAME , dev ) ;
if ( err ) {
dev_err ( & pdev - > dev , " request_threaded_irq failed: irq = %d. \n " ,
pdev - > irq ) ;
return err ;
}
err = mei_restart ( dev ) ;
return err ;
}
2014-03-18 22:52:03 +02:00
# endif /* CONFIG_PM_SLEEP */
2014-12-04 22:43:07 +01:00
# ifdef CONFIG_PM
2014-03-18 22:52:03 +02:00
static int mei_txe_pm_runtime_idle ( struct device * device )
{
struct mei_device * dev ;
2019-07-23 20:46:27 +08:00
dev_dbg ( device , " rpm: txe: runtime_idle \n " ) ;
2014-03-18 22:52:03 +02:00
2019-07-23 20:46:27 +08:00
dev = dev_get_drvdata ( device ) ;
2014-03-18 22:52:03 +02:00
if ( ! dev )
return - ENODEV ;
if ( mei_write_is_idle ( dev ) )
2014-07-17 10:53:36 +03:00
pm_runtime_autosuspend ( device ) ;
2014-03-18 22:52:03 +02:00
return - EBUSY ;
}
static int mei_txe_pm_runtime_suspend ( struct device * device )
{
struct mei_device * dev ;
int ret ;
2019-07-23 20:46:27 +08:00
dev_dbg ( device , " rpm: txe: runtime suspend \n " ) ;
2014-03-18 22:52:03 +02:00
2019-07-23 20:46:27 +08:00
dev = dev_get_drvdata ( device ) ;
2014-03-18 22:52:03 +02:00
if ( ! dev )
return - ENODEV ;
2014-01-16 00:58:35 +02:00
2014-03-18 22:52:03 +02:00
mutex_lock ( & dev - > device_lock ) ;
if ( mei_write_is_idle ( dev ) )
ret = mei_txe_aliveness_set_sync ( dev , 0 ) ;
else
ret = - EAGAIN ;
2017-09-26 09:18:27 +03:00
/* keep irq on we are staying in D0 */
2014-03-18 22:52:03 +02:00
2019-07-23 20:46:27 +08:00
dev_dbg ( device , " rpm: txe: runtime suspend ret=%d \n " , ret ) ;
2014-03-18 22:52:03 +02:00
mutex_unlock ( & dev - > device_lock ) ;
2016-06-16 17:58:52 +03:00
if ( ret & & ret ! = - EAGAIN )
schedule_work ( & dev - > reset_work ) ;
2014-03-18 22:52:03 +02:00
return ret ;
}
static int mei_txe_pm_runtime_resume ( struct device * device )
{
struct mei_device * dev ;
int ret ;
2019-07-23 20:46:27 +08:00
dev_dbg ( device , " rpm: txe: runtime resume \n " ) ;
2014-03-18 22:52:03 +02:00
2019-07-23 20:46:27 +08:00
dev = dev_get_drvdata ( device ) ;
2014-03-18 22:52:03 +02:00
if ( ! dev )
return - ENODEV ;
mutex_lock ( & dev - > device_lock ) ;
mei_enable_interrupts ( dev ) ;
ret = mei_txe_aliveness_set_sync ( dev , 1 ) ;
mutex_unlock ( & dev - > device_lock ) ;
2019-07-23 20:46:27 +08:00
dev_dbg ( device , " rpm: txe: runtime resume ret = %d \n " , ret ) ;
2014-03-18 22:52:03 +02:00
2016-06-16 17:58:52 +03:00
if ( ret )
schedule_work ( & dev - > reset_work ) ;
2014-03-18 22:52:03 +02:00
return ret ;
}
2014-03-18 22:52:06 +02:00
/**
2014-09-16 22:00:47 +02:00
* mei_txe_set_pm_domain - fill and set pm domain structure for device
2014-03-18 22:52:06 +02:00
*
* @ dev : mei_device
*/
static inline void mei_txe_set_pm_domain ( struct mei_device * dev )
{
2014-09-29 16:31:44 +03:00
struct pci_dev * pdev = to_pci_dev ( dev - > dev ) ;
2014-03-18 22:52:06 +02:00
if ( pdev - > dev . bus & & pdev - > dev . bus - > pm ) {
dev - > pg_domain . ops = * pdev - > dev . bus - > pm ;
dev - > pg_domain . ops . runtime_suspend = mei_txe_pm_runtime_suspend ;
dev - > pg_domain . ops . runtime_resume = mei_txe_pm_runtime_resume ;
dev - > pg_domain . ops . runtime_idle = mei_txe_pm_runtime_idle ;
2016-01-07 16:46:13 +01:00
dev_pm_domain_set ( & pdev - > dev , & dev - > pg_domain ) ;
2014-03-18 22:52:06 +02:00
}
}
/**
2014-09-16 22:00:47 +02:00
* mei_txe_unset_pm_domain - clean pm domain structure for device
2014-03-18 22:52:06 +02:00
*
* @ dev : mei_device
*/
static inline void mei_txe_unset_pm_domain ( struct mei_device * dev )
{
/* stop using pm callbacks if any */
2016-01-07 16:46:13 +01:00
dev_pm_domain_set ( dev - > dev , NULL ) ;
2014-03-18 22:52:06 +02:00
}
2014-03-18 22:52:03 +02:00
static const struct dev_pm_ops mei_txe_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS ( mei_txe_pci_suspend ,
mei_txe_pci_resume )
SET_RUNTIME_PM_OPS (
mei_txe_pm_runtime_suspend ,
mei_txe_pm_runtime_resume ,
mei_txe_pm_runtime_idle )
} ;
2014-01-16 00:58:35 +02:00
# define MEI_TXE_PM_OPS (&mei_txe_pm_ops)
# else
# define MEI_TXE_PM_OPS NULL
2014-03-18 22:52:03 +02:00
# endif /* CONFIG_PM */
2014-01-16 00:58:35 +02:00
/*
* PCI driver structure
*/
static struct pci_driver mei_txe_driver = {
. name = KBUILD_MODNAME ,
. id_table = mei_txe_pci_tbl ,
. probe = mei_txe_probe ,
. remove = mei_txe_remove ,
2017-03-20 15:04:02 +02:00
. shutdown = mei_txe_shutdown ,
2014-01-16 00:58:35 +02:00
. driver . pm = MEI_TXE_PM_OPS ,
} ;
module_pci_driver ( mei_txe_driver ) ;
MODULE_AUTHOR ( " Intel Corporation " ) ;
MODULE_DESCRIPTION ( " Intel(R) Trusted Execution Environment Interface " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;