2018-09-20 13:08:30 +00:00
// SPDX-License-Identifier: GPL-2.0
/*
* Platform UFS Host driver for Cadence controller
*
* Copyright ( C ) 2018 Cadence Design Systems , Inc .
*
* Authors :
* Jan Kotas < jank @ cadence . com >
*
*/
2022-04-19 15:58:08 -07:00
# include <linux/clk.h>
2018-09-20 13:08:30 +00:00
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/of.h>
# include <linux/time.h>
# include "ufshcd-pltfrm.h"
2019-03-27 14:44:05 +00:00
# define CDNS_UFS_REG_HCLKDIV 0xFC
# define CDNS_UFS_REG_PHY_XCFGD1 0x113C
2019-12-06 05:25:03 +01:00
# define CDNS_UFS_MAX_L4_ATTRS 12
struct cdns_ufs_host {
/**
* cdns_ufs_dme_attr_val - for storing L4 attributes
*/
u32 cdns_ufs_dme_attr_val [ CDNS_UFS_MAX_L4_ATTRS ] ;
} ;
/**
* cdns_ufs_get_l4_attr - get L4 attributes on local side
* @ hba : per adapter instance
*
*/
static void cdns_ufs_get_l4_attr ( struct ufs_hba * hba )
{
struct cdns_ufs_host * host = ufshcd_get_variant ( hba ) ;
ufshcd_dme_get ( hba , UIC_ARG_MIB ( T_PEERDEVICEID ) ,
& host - > cdns_ufs_dme_attr_val [ 0 ] ) ;
ufshcd_dme_get ( hba , UIC_ARG_MIB ( T_PEERCPORTID ) ,
& host - > cdns_ufs_dme_attr_val [ 1 ] ) ;
ufshcd_dme_get ( hba , UIC_ARG_MIB ( T_TRAFFICCLASS ) ,
& host - > cdns_ufs_dme_attr_val [ 2 ] ) ;
ufshcd_dme_get ( hba , UIC_ARG_MIB ( T_PROTOCOLID ) ,
& host - > cdns_ufs_dme_attr_val [ 3 ] ) ;
ufshcd_dme_get ( hba , UIC_ARG_MIB ( T_CPORTFLAGS ) ,
& host - > cdns_ufs_dme_attr_val [ 4 ] ) ;
ufshcd_dme_get ( hba , UIC_ARG_MIB ( T_TXTOKENVALUE ) ,
& host - > cdns_ufs_dme_attr_val [ 5 ] ) ;
ufshcd_dme_get ( hba , UIC_ARG_MIB ( T_RXTOKENVALUE ) ,
& host - > cdns_ufs_dme_attr_val [ 6 ] ) ;
ufshcd_dme_get ( hba , UIC_ARG_MIB ( T_LOCALBUFFERSPACE ) ,
& host - > cdns_ufs_dme_attr_val [ 7 ] ) ;
ufshcd_dme_get ( hba , UIC_ARG_MIB ( T_PEERBUFFERSPACE ) ,
& host - > cdns_ufs_dme_attr_val [ 8 ] ) ;
ufshcd_dme_get ( hba , UIC_ARG_MIB ( T_CREDITSTOSEND ) ,
& host - > cdns_ufs_dme_attr_val [ 9 ] ) ;
ufshcd_dme_get ( hba , UIC_ARG_MIB ( T_CPORTMODE ) ,
& host - > cdns_ufs_dme_attr_val [ 10 ] ) ;
ufshcd_dme_get ( hba , UIC_ARG_MIB ( T_CONNECTIONSTATE ) ,
& host - > cdns_ufs_dme_attr_val [ 11 ] ) ;
}
/**
* cdns_ufs_set_l4_attr - set L4 attributes on local side
* @ hba : per adapter instance
*
*/
static void cdns_ufs_set_l4_attr ( struct ufs_hba * hba )
{
struct cdns_ufs_host * host = ufshcd_get_variant ( hba ) ;
ufshcd_dme_set ( hba , UIC_ARG_MIB ( T_CONNECTIONSTATE ) , 0 ) ;
ufshcd_dme_set ( hba , UIC_ARG_MIB ( T_PEERDEVICEID ) ,
host - > cdns_ufs_dme_attr_val [ 0 ] ) ;
ufshcd_dme_set ( hba , UIC_ARG_MIB ( T_PEERCPORTID ) ,
host - > cdns_ufs_dme_attr_val [ 1 ] ) ;
ufshcd_dme_set ( hba , UIC_ARG_MIB ( T_TRAFFICCLASS ) ,
host - > cdns_ufs_dme_attr_val [ 2 ] ) ;
ufshcd_dme_set ( hba , UIC_ARG_MIB ( T_PROTOCOLID ) ,
host - > cdns_ufs_dme_attr_val [ 3 ] ) ;
ufshcd_dme_set ( hba , UIC_ARG_MIB ( T_CPORTFLAGS ) ,
host - > cdns_ufs_dme_attr_val [ 4 ] ) ;
ufshcd_dme_set ( hba , UIC_ARG_MIB ( T_TXTOKENVALUE ) ,
host - > cdns_ufs_dme_attr_val [ 5 ] ) ;
ufshcd_dme_set ( hba , UIC_ARG_MIB ( T_RXTOKENVALUE ) ,
host - > cdns_ufs_dme_attr_val [ 6 ] ) ;
ufshcd_dme_set ( hba , UIC_ARG_MIB ( T_LOCALBUFFERSPACE ) ,
host - > cdns_ufs_dme_attr_val [ 7 ] ) ;
ufshcd_dme_set ( hba , UIC_ARG_MIB ( T_PEERBUFFERSPACE ) ,
host - > cdns_ufs_dme_attr_val [ 8 ] ) ;
ufshcd_dme_set ( hba , UIC_ARG_MIB ( T_CREDITSTOSEND ) ,
host - > cdns_ufs_dme_attr_val [ 9 ] ) ;
ufshcd_dme_set ( hba , UIC_ARG_MIB ( T_CPORTMODE ) ,
host - > cdns_ufs_dme_attr_val [ 10 ] ) ;
ufshcd_dme_set ( hba , UIC_ARG_MIB ( T_CONNECTIONSTATE ) ,
host - > cdns_ufs_dme_attr_val [ 11 ] ) ;
}
2018-09-20 13:08:30 +00:00
/**
2021-03-12 09:47:24 +00:00
* cdns_ufs_set_hclkdiv ( )
2018-09-20 13:08:30 +00:00
* Sets HCLKDIV register value based on the core_clk
* @ hba : host controller instance
*
* Return zero for success and non - zero for failure
*/
static int cdns_ufs_set_hclkdiv ( struct ufs_hba * hba )
{
struct ufs_clk_info * clki ;
struct list_head * head = & hba - > clk_list_head ;
unsigned long core_clk_rate = 0 ;
u32 core_clk_div = 0 ;
if ( list_empty ( head ) )
return 0 ;
list_for_each_entry ( clki , head , list ) {
if ( IS_ERR_OR_NULL ( clki - > clk ) )
continue ;
if ( ! strcmp ( clki - > name , " core_clk " ) )
core_clk_rate = clk_get_rate ( clki - > clk ) ;
}
if ( ! core_clk_rate ) {
dev_err ( hba - > dev , " %s: unable to find core_clk rate \n " ,
__func__ ) ;
return - EINVAL ;
}
core_clk_div = core_clk_rate / USEC_PER_SEC ;
ufshcd_writel ( hba , core_clk_div , CDNS_UFS_REG_HCLKDIV ) ;
/**
* Make sure the register was updated ,
* UniPro layer will not work with an incorrect value .
*/
mb ( ) ;
return 0 ;
}
/**
2021-03-12 09:47:24 +00:00
* cdns_ufs_hce_enable_notify ( )
2019-08-02 12:21:12 +01:00
* Called before and after HCE enable bit is set .
2018-09-20 13:08:30 +00:00
* @ hba : host controller instance
* @ status : notify stage ( pre , post change )
*
* Return zero for success and non - zero for failure
*/
2019-08-02 12:21:12 +01:00
static int cdns_ufs_hce_enable_notify ( struct ufs_hba * hba ,
enum ufs_notify_change_status status )
2018-09-20 13:08:30 +00:00
{
2019-08-02 12:21:12 +01:00
if ( status ! = PRE_CHANGE )
2018-09-20 13:08:30 +00:00
return 0 ;
return cdns_ufs_set_hclkdiv ( hba ) ;
}
2019-12-06 05:25:03 +01:00
/**
2021-03-12 09:47:24 +00:00
* cdns_ufs_hibern8_notify ( )
2019-12-06 05:25:03 +01:00
* Called around hibern8 enter / exit .
* @ hba : host controller instance
* @ cmd : UIC Command
* @ status : notify stage ( pre , post change )
*
*/
static void cdns_ufs_hibern8_notify ( struct ufs_hba * hba , enum uic_cmd_dme cmd ,
enum ufs_notify_change_status status )
{
if ( status = = PRE_CHANGE & & cmd = = UIC_CMD_DME_HIBER_ENTER )
cdns_ufs_get_l4_attr ( hba ) ;
if ( status = = POST_CHANGE & & cmd = = UIC_CMD_DME_HIBER_EXIT )
cdns_ufs_set_l4_attr ( hba ) ;
}
2019-08-13 08:42:50 +01:00
/**
2021-03-12 09:47:24 +00:00
* cdns_ufs_link_startup_notify ( )
2019-08-13 08:42:50 +01:00
* Called before and after Link startup is carried out .
* @ hba : host controller instance
* @ status : notify stage ( pre , post change )
*
* Return zero for success and non - zero for failure
*/
static int cdns_ufs_link_startup_notify ( struct ufs_hba * hba ,
enum ufs_notify_change_status status )
{
if ( status ! = PRE_CHANGE )
return 0 ;
/*
* Some UFS devices have issues if LCC is enabled .
* So we are setting PA_Local_TX_LCC_Enable to 0
* before link startup which will make sure that both host
* and device TX LCC are disabled once link startup is
* completed .
*/
2020-02-07 15:03:57 +08:00
ufshcd_disable_host_tx_lcc ( hba ) ;
2019-08-13 08:42:50 +01:00
2019-12-03 11:07:15 +01:00
/*
* Disabling Autohibern8 feature in cadence UFS
* to mask unexpected interrupt trigger .
*/
hba - > ahit = 0 ;
2019-08-13 08:42:50 +01:00
return 0 ;
}
2019-03-27 14:44:05 +00:00
/**
* cdns_ufs_init - performs additional ufs initialization
* @ hba : host controller instance
*
* Returns status of initialization
*/
static int cdns_ufs_init ( struct ufs_hba * hba )
{
int status = 0 ;
2019-12-06 05:25:03 +01:00
struct cdns_ufs_host * host ;
struct device * dev = hba - > dev ;
host = devm_kzalloc ( dev , sizeof ( * host ) , GFP_KERNEL ) ;
if ( ! host )
return - ENOMEM ;
ufshcd_set_variant ( hba , host ) ;
2019-03-27 14:44:05 +00:00
2020-12-05 20:00:40 +08:00
status = ufshcd_vops_phy_initialization ( hba ) ;
2019-03-27 14:44:05 +00:00
return status ;
}
/**
* cdns_ufs_m31_16nm_phy_initialization - performs m31 phy initialization
* @ hba : host controller instance
*
* Always returns 0
*/
static int cdns_ufs_m31_16nm_phy_initialization ( struct ufs_hba * hba )
{
u32 data ;
/* Increase RX_Advanced_Min_ActivateTime_Capability */
data = ufshcd_readl ( hba , CDNS_UFS_REG_PHY_XCFGD1 ) ;
data | = BIT ( 24 ) ;
ufshcd_writel ( hba , data , CDNS_UFS_REG_PHY_XCFGD1 ) ;
return 0 ;
}
static const struct ufs_hba_variant_ops cdns_ufs_pltfm_hba_vops = {
. name = " cdns-ufs-pltfm " ,
2019-12-06 05:25:03 +01:00
. init = cdns_ufs_init ,
2019-08-02 12:21:12 +01:00
. hce_enable_notify = cdns_ufs_hce_enable_notify ,
2019-08-13 08:42:50 +01:00
. link_startup_notify = cdns_ufs_link_startup_notify ,
2019-12-06 05:25:03 +01:00
. hibern8_notify = cdns_ufs_hibern8_notify ,
2019-03-27 14:44:05 +00:00
} ;
static const struct ufs_hba_variant_ops cdns_ufs_m31_16nm_pltfm_hba_vops = {
2018-09-20 13:08:30 +00:00
. name = " cdns-ufs-pltfm " ,
2019-03-27 14:44:05 +00:00
. init = cdns_ufs_init ,
2019-08-02 12:21:12 +01:00
. hce_enable_notify = cdns_ufs_hce_enable_notify ,
2019-08-13 08:42:50 +01:00
. link_startup_notify = cdns_ufs_link_startup_notify ,
2019-03-27 14:44:05 +00:00
. phy_initialization = cdns_ufs_m31_16nm_phy_initialization ,
2019-12-06 05:25:03 +01:00
. hibern8_notify = cdns_ufs_hibern8_notify ,
2019-03-27 14:44:05 +00:00
} ;
static const struct of_device_id cdns_ufs_of_match [ ] = {
{
. compatible = " cdns,ufshc " ,
. data = & cdns_ufs_pltfm_hba_vops ,
} ,
{
. compatible = " cdns,ufshc-m31-16nm " ,
. data = & cdns_ufs_m31_16nm_pltfm_hba_vops ,
} ,
{ } ,
2018-09-20 13:08:30 +00:00
} ;
2019-03-27 14:44:05 +00:00
MODULE_DEVICE_TABLE ( of , cdns_ufs_of_match ) ;
2018-09-20 13:08:30 +00:00
/**
* cdns_ufs_pltfrm_probe - probe routine of the driver
* @ pdev : pointer to platform device handle
*
* Return zero for success and non - zero for failure
*/
static int cdns_ufs_pltfrm_probe ( struct platform_device * pdev )
{
int err ;
2019-03-27 14:44:05 +00:00
const struct of_device_id * of_id ;
struct ufs_hba_variant_ops * vops ;
2018-09-20 13:08:30 +00:00
struct device * dev = & pdev - > dev ;
2019-03-27 14:44:05 +00:00
of_id = of_match_node ( cdns_ufs_of_match , dev - > of_node ) ;
vops = ( struct ufs_hba_variant_ops * ) of_id - > data ;
2018-09-20 13:08:30 +00:00
/* Perform generic probe */
2019-03-27 14:44:05 +00:00
err = ufshcd_pltfrm_init ( pdev , vops ) ;
2018-09-20 13:08:30 +00:00
if ( err )
dev_err ( dev , " ufshcd_pltfrm_init() failed %d \n " , err ) ;
return err ;
}
/**
* cdns_ufs_pltfrm_remove - removes the ufs driver
* @ pdev : pointer to platform device handle
*
* Always returns 0
*/
static int cdns_ufs_pltfrm_remove ( struct platform_device * pdev )
{
struct ufs_hba * hba = platform_get_drvdata ( pdev ) ;
ufshcd_remove ( hba ) ;
return 0 ;
}
static const struct dev_pm_ops cdns_ufs_dev_pm_ops = {
2021-07-21 20:34:23 -07:00
SET_SYSTEM_SLEEP_PM_OPS ( ufshcd_system_suspend , ufshcd_system_resume )
SET_RUNTIME_PM_OPS ( ufshcd_runtime_suspend , ufshcd_runtime_resume , NULL )
2021-04-23 17:20:16 -07:00
. prepare = ufshcd_suspend_prepare ,
. complete = ufshcd_resume_complete ,
2018-09-20 13:08:30 +00:00
} ;
static struct platform_driver cdns_ufs_pltfrm_driver = {
. probe = cdns_ufs_pltfrm_probe ,
. remove = cdns_ufs_pltfrm_remove ,
2019-12-16 11:17:12 +01:00
. shutdown = ufshcd_pltfrm_shutdown ,
2018-09-20 13:08:30 +00:00
. driver = {
. name = " cdns-ufshcd " ,
. pm = & cdns_ufs_dev_pm_ops ,
. of_match_table = cdns_ufs_of_match ,
} ,
} ;
module_platform_driver ( cdns_ufs_pltfrm_driver ) ;
MODULE_AUTHOR ( " Jan Kotas <jank@cadence.com> " ) ;
MODULE_DESCRIPTION ( " Cadence UFS host controller platform driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;