2020-06-05 22:05:19 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
2013-02-25 21:44:33 +05:30
/*
* Universal Flash Storage Host controller PCI glue driver
*
* This code is based on drivers / scsi / ufs / ufshcd - pci . c
* Copyright ( C ) 2011 - 2013 Samsung India Software Operations
*
* Authors :
* Santosh Yaraganavi < santosh . sy @ samsung . com >
* Vinayak Holikatti < h . vinayak @ samsung . com >
*/
# include "ufshcd.h"
# include <linux/pci.h>
2013-07-30 00:36:00 +05:30
# include <linux/pm_runtime.h>
2020-08-27 10:20:30 +03:00
# include <linux/pm_qos.h>
# include <linux/debugfs.h>
struct intel_host {
u32 active_ltr ;
u32 idle_ltr ;
struct dentry * debugfs_root ;
} ;
2013-02-25 21:44:33 +05:30
2017-06-06 14:35:31 +03:00
static int ufs_intel_disable_lcc ( struct ufs_hba * hba )
{
u32 attr = UIC_ARG_MIB ( PA_LOCAL_TX_LCC_ENABLE ) ;
u32 lcc_enable = 0 ;
ufshcd_dme_get ( hba , attr , & lcc_enable ) ;
if ( lcc_enable )
2020-02-07 15:03:57 +08:00
ufshcd_disable_host_tx_lcc ( hba ) ;
2017-06-06 14:35:31 +03:00
return 0 ;
}
static int ufs_intel_link_startup_notify ( struct ufs_hba * hba ,
enum ufs_notify_change_status status )
{
int err = 0 ;
switch ( status ) {
case PRE_CHANGE :
err = ufs_intel_disable_lcc ( hba ) ;
break ;
case POST_CHANGE :
break ;
default :
break ;
}
return err ;
}
2020-08-27 10:20:30 +03:00
# define INTEL_ACTIVELTR 0x804
# define INTEL_IDLELTR 0x808
# define INTEL_LTR_REQ BIT(15)
# define INTEL_LTR_SCALE_MASK GENMASK(11, 10)
# define INTEL_LTR_SCALE_1US (2 << 10)
# define INTEL_LTR_SCALE_32US (3 << 10)
# define INTEL_LTR_VALUE_MASK GENMASK(9, 0)
static void intel_cache_ltr ( struct ufs_hba * hba )
{
struct intel_host * host = ufshcd_get_variant ( hba ) ;
host - > active_ltr = readl ( hba - > mmio_base + INTEL_ACTIVELTR ) ;
host - > idle_ltr = readl ( hba - > mmio_base + INTEL_IDLELTR ) ;
}
static void intel_ltr_set ( struct device * dev , s32 val )
{
struct ufs_hba * hba = dev_get_drvdata ( dev ) ;
struct intel_host * host = ufshcd_get_variant ( hba ) ;
u32 ltr ;
pm_runtime_get_sync ( dev ) ;
/*
* Program latency tolerance ( LTR ) accordingly what has been asked
* by the PM QoS layer or disable it in case we were passed
* negative value or PM_QOS_LATENCY_ANY .
*/
ltr = readl ( hba - > mmio_base + INTEL_ACTIVELTR ) ;
if ( val = = PM_QOS_LATENCY_ANY | | val < 0 ) {
ltr & = ~ INTEL_LTR_REQ ;
} else {
ltr | = INTEL_LTR_REQ ;
ltr & = ~ INTEL_LTR_SCALE_MASK ;
ltr & = ~ INTEL_LTR_VALUE_MASK ;
if ( val > INTEL_LTR_VALUE_MASK ) {
val > > = 5 ;
if ( val > INTEL_LTR_VALUE_MASK )
val = INTEL_LTR_VALUE_MASK ;
ltr | = INTEL_LTR_SCALE_32US | val ;
} else {
ltr | = INTEL_LTR_SCALE_1US | val ;
}
}
if ( ltr = = host - > active_ltr )
goto out ;
writel ( ltr , hba - > mmio_base + INTEL_ACTIVELTR ) ;
writel ( ltr , hba - > mmio_base + INTEL_IDLELTR ) ;
/* Cache the values into intel_host structure */
intel_cache_ltr ( hba ) ;
out :
pm_runtime_put ( dev ) ;
}
static void intel_ltr_expose ( struct device * dev )
{
dev - > power . set_latency_tolerance = intel_ltr_set ;
dev_pm_qos_expose_latency_tolerance ( dev ) ;
}
static void intel_ltr_hide ( struct device * dev )
{
dev_pm_qos_hide_latency_tolerance ( dev ) ;
dev - > power . set_latency_tolerance = NULL ;
}
static void intel_add_debugfs ( struct ufs_hba * hba )
{
struct dentry * dir = debugfs_create_dir ( dev_name ( hba - > dev ) , NULL ) ;
struct intel_host * host = ufshcd_get_variant ( hba ) ;
intel_cache_ltr ( hba ) ;
host - > debugfs_root = dir ;
debugfs_create_x32 ( " active_ltr " , 0444 , dir , & host - > active_ltr ) ;
debugfs_create_x32 ( " idle_ltr " , 0444 , dir , & host - > idle_ltr ) ;
}
static void intel_remove_debugfs ( struct ufs_hba * hba )
{
struct intel_host * host = ufshcd_get_variant ( hba ) ;
debugfs_remove_recursive ( host - > debugfs_root ) ;
}
static int ufs_intel_common_init ( struct ufs_hba * hba )
{
struct intel_host * host ;
2020-12-07 10:31:20 +02:00
hba - > caps | = UFSHCD_CAP_RPM_AUTOSUSPEND ;
2020-08-27 10:20:30 +03:00
host = devm_kzalloc ( hba - > dev , sizeof ( * host ) , GFP_KERNEL ) ;
if ( ! host )
return - ENOMEM ;
ufshcd_set_variant ( hba , host ) ;
intel_ltr_expose ( hba - > dev ) ;
intel_add_debugfs ( hba ) ;
return 0 ;
}
static void ufs_intel_common_exit ( struct ufs_hba * hba )
{
intel_remove_debugfs ( hba ) ;
intel_ltr_hide ( hba - > dev ) ;
}
2020-12-07 10:31:17 +02:00
static int ufs_intel_resume ( struct ufs_hba * hba , enum ufs_pm_op op )
{
/*
* To support S4 ( suspend - to - disk ) with spm_lvl other than 5 , the base
* address registers must be restored because the restore kernel can
* have used different addresses .
*/
ufshcd_writel ( hba , lower_32_bits ( hba - > utrdl_dma_addr ) ,
REG_UTP_TRANSFER_REQ_LIST_BASE_L ) ;
ufshcd_writel ( hba , upper_32_bits ( hba - > utrdl_dma_addr ) ,
REG_UTP_TRANSFER_REQ_LIST_BASE_H ) ;
ufshcd_writel ( hba , lower_32_bits ( hba - > utmrdl_dma_addr ) ,
REG_UTP_TASK_REQ_LIST_BASE_L ) ;
ufshcd_writel ( hba , upper_32_bits ( hba - > utmrdl_dma_addr ) ,
REG_UTP_TASK_REQ_LIST_BASE_H ) ;
2020-12-07 10:31:19 +02:00
if ( ufshcd_is_link_hibern8 ( hba ) ) {
int ret = ufshcd_uic_hibern8_exit ( hba ) ;
if ( ! ret ) {
ufshcd_set_link_active ( hba ) ;
} else {
dev_err ( hba - > dev , " %s: hibern8 exit failed %d \n " ,
__func__ , ret ) ;
/*
* Force reset and restore . Any other actions can lead
* to an unrecoverable state .
*/
ufshcd_set_link_off ( hba ) ;
}
}
2020-12-07 10:31:17 +02:00
return 0 ;
}
2020-08-10 17:10:24 +03:00
static int ufs_intel_ehl_init ( struct ufs_hba * hba )
{
hba - > quirks | = UFSHCD_QUIRK_BROKEN_AUTO_HIBERN8 ;
2020-08-27 10:20:30 +03:00
return ufs_intel_common_init ( hba ) ;
2020-08-10 17:10:24 +03:00
}
2017-06-06 14:35:31 +03:00
static struct ufs_hba_variant_ops ufs_intel_cnl_hba_vops = {
. name = " intel-pci " ,
2020-08-27 10:20:30 +03:00
. init = ufs_intel_common_init ,
. exit = ufs_intel_common_exit ,
2017-06-06 14:35:31 +03:00
. link_startup_notify = ufs_intel_link_startup_notify ,
2020-12-07 10:31:17 +02:00
. resume = ufs_intel_resume ,
2017-06-06 14:35:31 +03:00
} ;
2020-08-10 17:10:24 +03:00
static struct ufs_hba_variant_ops ufs_intel_ehl_hba_vops = {
. name = " intel-pci " ,
. init = ufs_intel_ehl_init ,
2020-08-27 10:20:30 +03:00
. exit = ufs_intel_common_exit ,
2020-08-10 17:10:24 +03:00
. link_startup_notify = ufs_intel_link_startup_notify ,
2020-12-07 10:31:17 +02:00
. resume = ufs_intel_resume ,
2020-08-10 17:10:24 +03:00
} ;
2017-06-06 14:35:30 +03:00
# ifdef CONFIG_PM_SLEEP
2013-02-25 21:44:33 +05:30
/**
* ufshcd_pci_suspend - suspend power management function
2018-03-01 15:07:20 -08:00
* @ dev : pointer to PCI device handle
2013-02-25 21:44:33 +05:30
*
2014-09-25 15:32:30 +03:00
* Returns 0 if successful
* Returns non - zero otherwise
2013-02-25 21:44:33 +05:30
*/
2013-07-30 00:36:00 +05:30
static int ufshcd_pci_suspend ( struct device * dev )
2013-02-25 21:44:33 +05:30
{
2014-09-25 15:32:30 +03:00
return ufshcd_system_suspend ( dev_get_drvdata ( dev ) ) ;
2013-02-25 21:44:33 +05:30
}
/**
* ufshcd_pci_resume - resume power management function
2018-03-01 15:07:20 -08:00
* @ dev : pointer to PCI device handle
2013-02-25 21:44:33 +05:30
*
2014-09-25 15:32:30 +03:00
* Returns 0 if successful
* Returns non - zero otherwise
2013-02-25 21:44:33 +05:30
*/
2013-07-30 00:36:00 +05:30
static int ufshcd_pci_resume ( struct device * dev )
2013-02-25 21:44:33 +05:30
{
2014-09-25 15:32:30 +03:00
return ufshcd_system_resume ( dev_get_drvdata ( dev ) ) ;
2013-02-25 21:44:33 +05:30
}
2020-12-07 10:31:18 +02:00
/**
* ufshcd_pci_poweroff - suspend - to - disk poweroff function
* @ dev : pointer to PCI device handle
*
* Returns 0 if successful
* Returns non - zero otherwise
*/
static int ufshcd_pci_poweroff ( struct device * dev )
{
struct ufs_hba * hba = dev_get_drvdata ( dev ) ;
int spm_lvl = hba - > spm_lvl ;
int ret ;
/*
* For poweroff we need to set the UFS device to PowerDown mode .
* Force spm_lvl to ensure that .
*/
hba - > spm_lvl = 5 ;
ret = ufshcd_system_suspend ( hba ) ;
hba - > spm_lvl = spm_lvl ;
return ret ;
}
2017-06-06 14:35:30 +03:00
# endif /* !CONFIG_PM_SLEEP */
2013-02-25 21:44:33 +05:30
2017-06-06 14:35:30 +03:00
# ifdef CONFIG_PM
2013-07-30 00:36:00 +05:30
static int ufshcd_pci_runtime_suspend ( struct device * dev )
{
2014-09-25 15:32:30 +03:00
return ufshcd_runtime_suspend ( dev_get_drvdata ( dev ) ) ;
2013-07-30 00:36:00 +05:30
}
static int ufshcd_pci_runtime_resume ( struct device * dev )
{
2014-09-25 15:32:30 +03:00
return ufshcd_runtime_resume ( dev_get_drvdata ( dev ) ) ;
2013-07-30 00:36:00 +05:30
}
static int ufshcd_pci_runtime_idle ( struct device * dev )
{
2014-09-25 15:32:30 +03:00
return ufshcd_runtime_idle ( dev_get_drvdata ( dev ) ) ;
2013-07-30 00:36:00 +05:30
}
2017-06-06 14:35:30 +03:00
# endif /* !CONFIG_PM */
2013-07-30 00:36:00 +05:30
2013-02-25 21:44:33 +05:30
/**
* ufshcd_pci_shutdown - main function to put the controller in reset state
* @ pdev : pointer to PCI device handle
*/
static void ufshcd_pci_shutdown ( struct pci_dev * pdev )
{
2014-09-25 15:32:30 +03:00
ufshcd_shutdown ( ( struct ufs_hba * ) pci_get_drvdata ( pdev ) ) ;
2013-02-25 21:44:33 +05:30
}
/**
* ufshcd_pci_remove - de - allocate PCI / SCSI host and host memory space
* data structure memory
2018-03-01 15:07:20 -08:00
* @ pdev : pointer to PCI handle
2013-02-25 21:44:33 +05:30
*/
static void ufshcd_pci_remove ( struct pci_dev * pdev )
{
struct ufs_hba * hba = pci_get_drvdata ( pdev ) ;
2013-07-30 00:36:00 +05:30
pm_runtime_forbid ( & pdev - > dev ) ;
pm_runtime_get_noresume ( & pdev - > dev ) ;
2013-02-25 21:44:33 +05:30
ufshcd_remove ( hba ) ;
2016-10-27 17:25:58 -07:00
ufshcd_dealloc_host ( hba ) ;
2013-02-25 21:44:33 +05:30
}
/**
* ufshcd_pci_probe - probe routine of the driver
* @ pdev : pointer to PCI device handle
* @ id : PCI device id
*
* Returns 0 on success , non - zero value on failure
*/
static int
ufshcd_pci_probe ( struct pci_dev * pdev , const struct pci_device_id * id )
{
struct ufs_hba * hba ;
void __iomem * mmio_base ;
int err ;
2013-07-30 00:36:01 +05:30
err = pcim_enable_device ( pdev ) ;
2013-02-25 21:44:33 +05:30
if ( err ) {
2013-07-30 00:36:01 +05:30
dev_err ( & pdev - > dev , " pcim_enable_device failed \n " ) ;
return err ;
2013-02-25 21:44:33 +05:30
}
pci_set_master ( pdev ) ;
2013-07-30 00:36:01 +05:30
err = pcim_iomap_regions ( pdev , 1 < < 0 , UFSHCD ) ;
2013-02-25 21:44:33 +05:30
if ( err < 0 ) {
2013-07-30 00:36:01 +05:30
dev_err ( & pdev - > dev , " request and iomap failed \n " ) ;
return err ;
2013-02-25 21:44:33 +05:30
}
2013-07-30 00:36:01 +05:30
mmio_base = pcim_iomap_table ( pdev ) [ 0 ] ;
2013-02-25 21:44:33 +05:30
2014-09-25 15:32:21 +03:00
err = ufshcd_alloc_host ( & pdev - > dev , & hba ) ;
if ( err ) {
dev_err ( & pdev - > dev , " Allocation failed \n " ) ;
return err ;
}
2020-08-27 10:20:30 +03:00
pci_set_drvdata ( pdev , hba ) ;
2017-06-06 14:35:31 +03:00
hba - > vops = ( struct ufs_hba_variant_ops * ) id - > driver_data ;
2014-09-25 15:32:21 +03:00
err = ufshcd_init ( hba , mmio_base , pdev - > irq ) ;
2013-02-25 21:44:33 +05:30
if ( err ) {
dev_err ( & pdev - > dev , " Initialization failed \n " ) ;
2016-10-27 17:25:58 -07:00
ufshcd_dealloc_host ( hba ) ;
2013-07-30 00:36:01 +05:30
return err ;
2013-02-25 21:44:33 +05:30
}
2013-07-30 00:36:00 +05:30
pm_runtime_put_noidle ( & pdev - > dev ) ;
pm_runtime_allow ( & pdev - > dev ) ;
2013-02-25 21:44:33 +05:30
return 0 ;
}
2013-07-30 00:36:00 +05:30
static const struct dev_pm_ops ufshcd_pci_pm_ops = {
2020-12-07 10:31:18 +02:00
# ifdef CONFIG_PM_SLEEP
. suspend = ufshcd_pci_suspend ,
. resume = ufshcd_pci_resume ,
. freeze = ufshcd_pci_suspend ,
. thaw = ufshcd_pci_resume ,
. poweroff = ufshcd_pci_poweroff ,
. restore = ufshcd_pci_resume ,
# endif
2017-06-06 14:35:30 +03:00
SET_RUNTIME_PM_OPS ( ufshcd_pci_runtime_suspend ,
ufshcd_pci_runtime_resume ,
ufshcd_pci_runtime_idle )
2013-07-30 00:36:00 +05:30
} ;
2014-08-08 15:56:03 +02:00
static const struct pci_device_id ufshcd_pci_tbl [ ] = {
2013-02-25 21:44:33 +05:30
{ PCI_VENDOR_ID_SAMSUNG , 0xC00C , PCI_ANY_ID , PCI_ANY_ID , 0 , 0 , 0 } ,
2017-06-06 14:35:31 +03:00
{ PCI_VDEVICE ( INTEL , 0x9DFA ) , ( kernel_ulong_t ) & ufs_intel_cnl_hba_vops } ,
2020-08-10 17:10:24 +03:00
{ PCI_VDEVICE ( INTEL , 0x4B41 ) , ( kernel_ulong_t ) & ufs_intel_ehl_hba_vops } ,
{ PCI_VDEVICE ( INTEL , 0x4B43 ) , ( kernel_ulong_t ) & ufs_intel_ehl_hba_vops } ,
2013-02-25 21:44:33 +05:30
{ } /* terminate list */
} ;
MODULE_DEVICE_TABLE ( pci , ufshcd_pci_tbl ) ;
static struct pci_driver ufshcd_pci_driver = {
. name = UFSHCD ,
. id_table = ufshcd_pci_tbl ,
. probe = ufshcd_pci_probe ,
. remove = ufshcd_pci_remove ,
. shutdown = ufshcd_pci_shutdown ,
2013-07-30 00:36:00 +05:30
. driver = {
. pm = & ufshcd_pci_pm_ops
} ,
2013-02-25 21:44:33 +05:30
} ;
module_pci_driver ( ufshcd_pci_driver ) ;
MODULE_AUTHOR ( " Santosh Yaragnavi <santosh.sy@samsung.com> " ) ;
MODULE_AUTHOR ( " Vinayak Holikatti <h.vinayak@samsung.com> " ) ;
MODULE_DESCRIPTION ( " UFS host controller PCI glue driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_VERSION ( UFSHCD_DRIVER_VERSION ) ;