2020-12-07 11:32:19 +01:00
// SPDX-License-Identifier: GPL-2.0
/*
* Cadence USBSS DRD Driver .
*
* Copyright ( C ) 2018 - 2020 Cadence .
* Copyright ( C ) 2017 - 2018 NXP
* Copyright ( C ) 2019 Texas Instruments
*
*
* Author : Peter Chen < peter . chen @ nxp . com >
* Pawel Laszczak < pawell @ cadence . com >
* Roger Quadros < rogerq @ ti . com >
*/
# include <linux/module.h>
2021-12-20 01:04:10 +00:00
# include <linux/irq.h>
2020-12-07 11:32:19 +01:00
# include <linux/kernel.h>
# include <linux/platform_device.h>
# include <linux/pm_runtime.h>
# include "core.h"
2020-12-07 11:32:20 +01:00
# include "gadget-export.h"
2021-02-18 16:51:08 -06:00
# include "drd.h"
2020-12-07 11:32:19 +01:00
2020-12-07 11:32:21 +01:00
static int set_phy_power_on ( struct cdns * cdns )
2020-12-07 11:32:19 +01:00
{
int ret ;
ret = phy_power_on ( cdns - > usb2_phy ) ;
if ( ret )
return ret ;
ret = phy_power_on ( cdns - > usb3_phy ) ;
if ( ret )
phy_power_off ( cdns - > usb2_phy ) ;
return ret ;
}
2020-12-07 11:32:21 +01:00
static void set_phy_power_off ( struct cdns * cdns )
2020-12-07 11:32:19 +01:00
{
phy_power_off ( cdns - > usb3_phy ) ;
phy_power_off ( cdns - > usb2_phy ) ;
}
/**
* cdns3_plat_probe - probe for cdns3 core device
* @ pdev : Pointer to cdns3 core platform device
*
* Returns 0 on success otherwise negative errno
*/
static int cdns3_plat_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct resource * res ;
2020-12-07 11:32:21 +01:00
struct cdns * cdns ;
2020-12-07 11:32:19 +01:00
void __iomem * regs ;
int ret ;
cdns = devm_kzalloc ( dev , sizeof ( * cdns ) , GFP_KERNEL ) ;
if ( ! cdns )
return - ENOMEM ;
cdns - > dev = dev ;
cdns - > pdata = dev_get_platdata ( dev ) ;
platform_set_drvdata ( pdev , cdns ) ;
2021-12-20 01:04:10 +00:00
ret = platform_get_irq_byname ( pdev , " host " ) ;
if ( ret < 0 )
return ret ;
2020-12-07 11:32:19 +01:00
2021-12-20 01:04:10 +00:00
cdns - > xhci_res [ 0 ] . start = ret ;
cdns - > xhci_res [ 0 ] . end = ret ;
cdns - > xhci_res [ 0 ] . flags = IORESOURCE_IRQ | irq_get_trigger_type ( ret ) ;
cdns - > xhci_res [ 0 ] . name = " host " ;
2020-12-07 11:32:19 +01:00
res = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " xhci " ) ;
if ( ! res ) {
dev_err ( dev , " couldn't get xhci resource \n " ) ;
return - ENXIO ;
}
cdns - > xhci_res [ 1 ] = * res ;
cdns - > dev_irq = platform_get_irq_byname ( pdev , " peripheral " ) ;
if ( cdns - > dev_irq < 0 )
return cdns - > dev_irq ;
regs = devm_platform_ioremap_resource_byname ( pdev , " dev " ) ;
if ( IS_ERR ( regs ) )
return PTR_ERR ( regs ) ;
cdns - > dev_regs = regs ;
cdns - > otg_irq = platform_get_irq_byname ( pdev , " otg " ) ;
if ( cdns - > otg_irq < 0 )
return cdns - > otg_irq ;
res = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " otg " ) ;
if ( ! res ) {
dev_err ( dev , " couldn't get otg resource \n " ) ;
return - ENXIO ;
}
cdns - > phyrst_a_enable = device_property_read_bool ( dev , " cdns,phyrst-a-enable " ) ;
cdns - > otg_res = * res ;
cdns - > wakeup_irq = platform_get_irq_byname_optional ( pdev , " wakeup " ) ;
if ( cdns - > wakeup_irq = = - EPROBE_DEFER )
return cdns - > wakeup_irq ;
else if ( cdns - > wakeup_irq = = 0 )
return - EINVAL ;
if ( cdns - > wakeup_irq < 0 ) {
dev_dbg ( dev , " couldn't get wakeup irq \n " ) ;
cdns - > wakeup_irq = 0x0 ;
}
cdns - > usb2_phy = devm_phy_optional_get ( dev , " cdns3,usb2-phy " ) ;
if ( IS_ERR ( cdns - > usb2_phy ) )
return PTR_ERR ( cdns - > usb2_phy ) ;
ret = phy_init ( cdns - > usb2_phy ) ;
if ( ret )
return ret ;
cdns - > usb3_phy = devm_phy_optional_get ( dev , " cdns3,usb3-phy " ) ;
if ( IS_ERR ( cdns - > usb3_phy ) )
return PTR_ERR ( cdns - > usb3_phy ) ;
ret = phy_init ( cdns - > usb3_phy ) ;
if ( ret )
goto err_phy3_init ;
ret = set_phy_power_on ( cdns ) ;
if ( ret )
goto err_phy_power_on ;
2020-12-07 11:32:20 +01:00
cdns - > gadget_init = cdns3_gadget_init ;
2020-12-07 11:32:21 +01:00
ret = cdns_init ( cdns ) ;
2020-12-07 11:32:19 +01:00
if ( ret )
goto err_cdns_init ;
device_set_wakeup_capable ( dev , true ) ;
pm_runtime_set_active ( dev ) ;
pm_runtime_enable ( dev ) ;
if ( ! ( cdns - > pdata & & ( cdns - > pdata - > quirks & CDNS3_DEFAULT_PM_RUNTIME_ALLOW ) ) )
pm_runtime_forbid ( dev ) ;
/*
* The controller needs less time between bus and controller suspend ,
* and we also needs a small delay to avoid frequently entering low
* power mode .
*/
pm_runtime_set_autosuspend_delay ( dev , 20 ) ;
pm_runtime_mark_last_busy ( dev ) ;
pm_runtime_use_autosuspend ( dev ) ;
return 0 ;
err_cdns_init :
set_phy_power_off ( cdns ) ;
err_phy_power_on :
phy_exit ( cdns - > usb3_phy ) ;
err_phy3_init :
phy_exit ( cdns - > usb2_phy ) ;
return ret ;
}
/**
2021-05-26 14:00:17 +01:00
* cdns3_plat_remove ( ) - unbind drd driver and clean up
2020-12-07 11:32:19 +01:00
* @ pdev : Pointer to Linux platform device
*
* Returns 0 on success otherwise negative errno
*/
static int cdns3_plat_remove ( struct platform_device * pdev )
{
2020-12-07 11:32:21 +01:00
struct cdns * cdns = platform_get_drvdata ( pdev ) ;
2020-12-07 11:32:19 +01:00
struct device * dev = cdns - > dev ;
pm_runtime_get_sync ( dev ) ;
pm_runtime_disable ( dev ) ;
pm_runtime_put_noidle ( dev ) ;
2020-12-07 11:32:21 +01:00
cdns_remove ( cdns ) ;
2020-12-07 11:32:19 +01:00
set_phy_power_off ( cdns ) ;
phy_exit ( cdns - > usb2_phy ) ;
phy_exit ( cdns - > usb3_phy ) ;
return 0 ;
}
# ifdef CONFIG_PM
static int cdns3_set_platform_suspend ( struct device * dev ,
bool suspend , bool wakeup )
{
2020-12-07 11:32:21 +01:00
struct cdns * cdns = dev_get_drvdata ( dev ) ;
2020-12-07 11:32:19 +01:00
int ret = 0 ;
if ( cdns - > pdata & & cdns - > pdata - > platform_suspend )
ret = cdns - > pdata - > platform_suspend ( dev , suspend , wakeup ) ;
return ret ;
}
static int cdns3_controller_suspend ( struct device * dev , pm_message_t msg )
{
2020-12-07 11:32:21 +01:00
struct cdns * cdns = dev_get_drvdata ( dev ) ;
2020-12-07 11:32:19 +01:00
bool wakeup ;
unsigned long flags ;
if ( cdns - > in_lpm )
return 0 ;
if ( PMSG_IS_AUTO ( msg ) )
wakeup = true ;
else
wakeup = device_may_wakeup ( dev ) ;
cdns3_set_platform_suspend ( cdns - > dev , true , wakeup ) ;
set_phy_power_off ( cdns ) ;
spin_lock_irqsave ( & cdns - > lock , flags ) ;
cdns - > in_lpm = true ;
spin_unlock_irqrestore ( & cdns - > lock , flags ) ;
dev_dbg ( cdns - > dev , " %s ends \n " , __func__ ) ;
return 0 ;
}
static int cdns3_controller_resume ( struct device * dev , pm_message_t msg )
{
2020-12-07 11:32:21 +01:00
struct cdns * cdns = dev_get_drvdata ( dev ) ;
2020-12-07 11:32:19 +01:00
int ret ;
unsigned long flags ;
if ( ! cdns - > in_lpm )
return 0 ;
2021-02-18 16:51:08 -06:00
if ( cdns_power_is_lost ( cdns ) ) {
phy_exit ( cdns - > usb2_phy ) ;
ret = phy_init ( cdns - > usb2_phy ) ;
if ( ret )
return ret ;
phy_exit ( cdns - > usb3_phy ) ;
ret = phy_init ( cdns - > usb3_phy ) ;
if ( ret )
return ret ;
}
2020-12-07 11:32:19 +01:00
ret = set_phy_power_on ( cdns ) ;
if ( ret )
return ret ;
cdns3_set_platform_suspend ( cdns - > dev , false , false ) ;
spin_lock_irqsave ( & cdns - > lock , flags ) ;
2020-12-07 11:32:21 +01:00
cdns_resume ( cdns , ! PMSG_IS_AUTO ( msg ) ) ;
2020-12-07 11:32:19 +01:00
cdns - > in_lpm = false ;
spin_unlock_irqrestore ( & cdns - > lock , flags ) ;
if ( cdns - > wakeup_pending ) {
cdns - > wakeup_pending = false ;
enable_irq ( cdns - > wakeup_irq ) ;
}
dev_dbg ( cdns - > dev , " %s ends \n " , __func__ ) ;
return ret ;
}
static int cdns3_plat_runtime_suspend ( struct device * dev )
{
return cdns3_controller_suspend ( dev , PMSG_AUTO_SUSPEND ) ;
}
static int cdns3_plat_runtime_resume ( struct device * dev )
{
return cdns3_controller_resume ( dev , PMSG_AUTO_RESUME ) ;
}
# ifdef CONFIG_PM_SLEEP
static int cdns3_plat_suspend ( struct device * dev )
{
2020-12-07 11:32:21 +01:00
struct cdns * cdns = dev_get_drvdata ( dev ) ;
2021-02-18 16:51:08 -06:00
int ret ;
2020-12-07 11:32:19 +01:00
2020-12-07 11:32:21 +01:00
cdns_suspend ( cdns ) ;
2020-12-07 11:32:19 +01:00
2021-02-18 16:51:08 -06:00
ret = cdns3_controller_suspend ( dev , PMSG_SUSPEND ) ;
if ( ret )
return ret ;
if ( device_may_wakeup ( dev ) & & cdns - > wakeup_irq )
enable_irq_wake ( cdns - > wakeup_irq ) ;
return ret ;
2020-12-07 11:32:19 +01:00
}
static int cdns3_plat_resume ( struct device * dev )
{
return cdns3_controller_resume ( dev , PMSG_RESUME ) ;
}
# endif /* CONFIG_PM_SLEEP */
# endif /* CONFIG_PM */
static const struct dev_pm_ops cdns3_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS ( cdns3_plat_suspend , cdns3_plat_resume )
SET_RUNTIME_PM_OPS ( cdns3_plat_runtime_suspend ,
cdns3_plat_runtime_resume , NULL )
} ;
# ifdef CONFIG_OF
static const struct of_device_id of_cdns3_match [ ] = {
{ . compatible = " cdns,usb3 " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , of_cdns3_match ) ;
# endif
static struct platform_driver cdns3_driver = {
. probe = cdns3_plat_probe ,
. remove = cdns3_plat_remove ,
. driver = {
. name = " cdns-usb3 " ,
. of_match_table = of_match_ptr ( of_cdns3_match ) ,
. pm = & cdns3_pm_ops ,
} ,
} ;
module_platform_driver ( cdns3_driver ) ;
MODULE_ALIAS ( " platform:cdns3 " ) ;
MODULE_AUTHOR ( " Pawel Laszczak <pawell@cadence.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_DESCRIPTION ( " Cadence USB3 DRD Controller Driver " ) ;