2020-01-09 12:35:58 +03:00
// SPDX-License-Identifier: GPL-2.0
2021-05-26 16:00:23 +03:00
/*
2020-01-09 12:35:58 +03:00
* cdns3 - imx . c - NXP i . MX specific Glue layer for Cadence USB Controller
*
* Copyright ( C ) 2019 NXP
*/
# include <linux/bits.h>
# include <linux/clk.h>
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/interrupt.h>
# include <linux/platform_device.h>
# include <linux/dma-mapping.h>
# include <linux/io.h>
# include <linux/of_platform.h>
# include <linux/iopoll.h>
2020-09-02 12:57:33 +03:00
# include <linux/pm_runtime.h>
# include "core.h"
2020-01-09 12:35:58 +03:00
# define USB3_CORE_CTRL1 0x00
# define USB3_CORE_CTRL2 0x04
# define USB3_INT_REG 0x08
# define USB3_CORE_STATUS 0x0c
# define XHCI_DEBUG_LINK_ST 0x10
# define XHCI_DEBUG_BUS 0x14
# define USB3_SSPHY_CTRL1 0x40
# define USB3_SSPHY_CTRL2 0x44
# define USB3_SSPHY_STATUS 0x4c
# define USB2_PHY_CTRL1 0x50
# define USB2_PHY_CTRL2 0x54
# define USB2_PHY_STATUS 0x5c
/* Register bits definition */
/* USB3_CORE_CTRL1 */
2020-09-02 12:57:33 +03:00
# define SW_RESET_MASK GENMASK(31, 26)
2020-01-09 12:35:58 +03:00
# define PWR_SW_RESET BIT(31)
# define APB_SW_RESET BIT(30)
# define AXI_SW_RESET BIT(29)
# define RW_SW_RESET BIT(28)
# define PHY_SW_RESET BIT(27)
# define PHYAHB_SW_RESET BIT(26)
# define ALL_SW_RESET (PWR_SW_RESET | APB_SW_RESET | AXI_SW_RESET | \
RW_SW_RESET | PHY_SW_RESET | PHYAHB_SW_RESET )
# define OC_DISABLE BIT(9)
# define MDCTRL_CLK_SEL BIT(7)
# define MODE_STRAP_MASK (0x7)
# define DEV_MODE (1 << 2)
# define HOST_MODE (1 << 1)
# define OTG_MODE (1 << 0)
/* USB3_INT_REG */
# define CLK_125_REQ BIT(29)
# define LPM_CLK_REQ BIT(28)
# define DEVU3_WAEKUP_EN BIT(14)
# define OTG_WAKEUP_EN BIT(12)
2020-09-02 12:57:33 +03:00
# define DEV_INT_EN (3 << 8) /* DEV INT b9:8 */
# define HOST_INT1_EN (1 << 0) /* HOST INT b7:0 */
2020-01-09 12:35:58 +03:00
/* USB3_CORE_STATUS */
# define MDCTRL_CLK_STATUS BIT(15)
# define DEV_POWER_ON_READY BIT(13)
# define HOST_POWER_ON_READY BIT(12)
/* USB3_SSPHY_STATUS */
# define CLK_VALID_MASK (0x3f << 26)
# define CLK_VALID_COMPARE_BITS (0xf << 28)
# define PHY_REFCLK_REQ (1 << 0)
2020-09-02 12:57:33 +03:00
/* OTG registers definition */
# define OTGSTS 0x4
/* OTGSTS */
# define OTG_NRDY BIT(11)
/* xHCI registers definition */
# define XECP_PM_PMCSR 0x8018
# define XECP_AUX_CTRL_REG1 0x8120
/* Register bits definition */
/* XECP_AUX_CTRL_REG1 */
# define CFG_RXDET_P3_EN BIT(15)
/* XECP_PM_PMCSR */
# define PS_MASK GENMASK(1, 0)
# define PS_D0 0
# define PS_D1 1
2020-01-09 12:35:58 +03:00
struct cdns_imx {
struct device * dev ;
void __iomem * noncore ;
struct clk_bulk_data * clks ;
int num_clks ;
2020-09-02 12:57:33 +03:00
struct platform_device * cdns3_pdev ;
2020-01-09 12:35:58 +03:00
} ;
static inline u32 cdns_imx_readl ( struct cdns_imx * data , u32 offset )
{
return readl ( data - > noncore + offset ) ;
}
static inline void cdns_imx_writel ( struct cdns_imx * data , u32 offset , u32 value )
{
writel ( value , data - > noncore + offset ) ;
}
static const struct clk_bulk_data imx_cdns3_core_clks [ ] = {
{ . id = " usb3_lpm_clk " } ,
{ . id = " usb3_bus_clk " } ,
{ . id = " usb3_aclk " } ,
{ . id = " usb3_ipg_clk " } ,
{ . id = " usb3_core_pclk " } ,
} ;
static int cdns_imx_noncore_init ( struct cdns_imx * data )
{
u32 value ;
int ret ;
struct device * dev = data - > dev ;
cdns_imx_writel ( data , USB3_SSPHY_STATUS , CLK_VALID_MASK ) ;
udelay ( 1 ) ;
ret = readl_poll_timeout ( data - > noncore + USB3_SSPHY_STATUS , value ,
( value & CLK_VALID_COMPARE_BITS ) = = CLK_VALID_COMPARE_BITS ,
10 , 100000 ) ;
if ( ret ) {
dev_err ( dev , " wait clkvld timeout \n " ) ;
return ret ;
}
value = cdns_imx_readl ( data , USB3_CORE_CTRL1 ) ;
value | = ALL_SW_RESET ;
cdns_imx_writel ( data , USB3_CORE_CTRL1 , value ) ;
udelay ( 1 ) ;
value = cdns_imx_readl ( data , USB3_CORE_CTRL1 ) ;
value = ( value & ~ MODE_STRAP_MASK ) | OTG_MODE | OC_DISABLE ;
cdns_imx_writel ( data , USB3_CORE_CTRL1 , value ) ;
value = cdns_imx_readl ( data , USB3_INT_REG ) ;
value | = HOST_INT1_EN | DEV_INT_EN ;
cdns_imx_writel ( data , USB3_INT_REG , value ) ;
value = cdns_imx_readl ( data , USB3_CORE_CTRL1 ) ;
value & = ~ ALL_SW_RESET ;
cdns_imx_writel ( data , USB3_CORE_CTRL1 , value ) ;
return ret ;
}
2020-09-02 12:57:33 +03:00
static int cdns_imx_platform_suspend ( struct device * dev ,
bool suspend , bool wakeup ) ;
static struct cdns3_platform_data cdns_imx_pdata = {
. platform_suspend = cdns_imx_platform_suspend ,
2020-09-28 10:34:19 +03:00
. quirks = CDNS3_DEFAULT_PM_RUNTIME_ALLOW ,
2020-09-02 12:57:33 +03:00
} ;
static const struct of_dev_auxdata cdns_imx_auxdata [ ] = {
{
. compatible = " cdns,usb3 " ,
. platform_data = & cdns_imx_pdata ,
} ,
{ } ,
} ;
2020-01-09 12:35:58 +03:00
static int cdns_imx_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct device_node * node = dev - > of_node ;
struct cdns_imx * data ;
int ret ;
if ( ! node )
return - ENODEV ;
data = devm_kzalloc ( dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
platform_set_drvdata ( pdev , data ) ;
data - > dev = dev ;
data - > noncore = devm_platform_ioremap_resource ( pdev , 0 ) ;
if ( IS_ERR ( data - > noncore ) ) {
dev_err ( dev , " can't map IOMEM resource \n " ) ;
return PTR_ERR ( data - > noncore ) ;
}
data - > num_clks = ARRAY_SIZE ( imx_cdns3_core_clks ) ;
usb: cdns3: imx: fix writing read-only memory issue
The memory for struct clk_bulk_data should not be static which will be written
during the clk_bulk_get. It fixed below oops when loading cdns3-imx as module.
[ 17.272605] Unable to handle kernel write to read-only memory at virtual address ffff8000092a5398
[ 17.299730] Mem abort info:
[ 17.313542] unregister ISI channel: mxc_isi.4
[ 17.324076] ESR = 0x9600004f
[ 17.344658] EC = 0x25: DABT (current EL), IL = 32 bits
[ 17.402055] SET = 0, FnV = 0
[ 17.404321] mxs_phy 5b100000.usbphy: supply phy-3p0 not found, using dummy regulator
[ 17.405121] EA = 0, S1PTW = 0
[ 17.405133] Data abort info:
[ 17.496231] ISV = 0, ISS = 0x0000004f
[ 17.510871] CM = 0, WnR = 1
[ 17.533542] swapper pgtable: 4k pages, 48-bit VAs, pgdp=0000000081ea5000
[ 17.545709] [ffff8000092a5398] pgd=00000008bffff003, p4d=00000008bffff003, pud=00000008bfffe003, pmd=0000000885041003, pte=006000088513b783
[ 17.573521] Internal error: Oops: 9600004f [#1] PREEMPT SMP
[ 17.579113] Modules linked in: usbmisc_imx phy_mxs_usb phy_cadence_salvo cdns3_imx(+) tcpci imx8_media_dev(C) caam error
[ 17.590044] CPU: 2 PID: 253 Comm: systemd-udevd Tainted: G C 5.10.0-rc4-04445-g11f3c3a29d0-dirty #19
[ 17.600488] Hardware name: Freescale i.MX8QXP MEK (DT)
[ 17.605633] pstate: 20000005 (nzCv daif -PAN -UAO -TCO BTYPE=--)
[ 17.611662] pc : __clk_bulk_get+0x48/0x130
[ 17.615786] lr : clk_bulk_get+0x18/0x20
[ 17.619634] sp : ffff80001369b880
[ 17.622953] x29: ffff80001369b880 x28: 0000000000000013
[ 17.628277] x27: 0000000000000100 x26: ffff00080553b100
[ 17.633602] x25: ffff80001229b4d8 x24: 0000000000000000
[ 17.638928] x23: ffff000800665410 x22: 0000000000000005
[ 17.644275] x21: ffff8000092a5390 x20: ffff000800665400
[ 17.649605] x19: ffff000804e6f980 x18: 000000005b110000
[ 17.654946] x17: 0000000000000000 x16: 0000000000000000
[ 17.660274] x15: ffff800011989100 x14: 0000000000000000
[ 17.665599] x13: ffff800013ce1000 x12: ffff800013ca1000
[ 17.670924] x11: 000000005b110000 x10: 0000000000000000
[ 17.676249] x9 : ffff8000106c5a30 x8 : ffff000804e6fa00
[ 17.681575] x7 : 0000000000000000 x6 : 000000000000003f
[ 17.686901] x5 : 0000000000000040 x4 : ffff80001369b8b0
[ 17.692228] x3 : ffff8000092a5398 x2 : ffff8000092a5390
[ 17.697574] x1 : ffff8000092a53e8 x0 : 0000000000000004
[ 17.702905] Call trace:
[ 17.705366] __clk_bulk_get+0x48/0x130
[ 17.709125] clk_bulk_get+0x18/0x20
[ 17.712620] devm_clk_bulk_get+0x58/0xb8
[ 17.716563] cdns_imx_probe+0x84/0x1f0 [cdns3_imx]
[ 17.721363] platform_drv_probe+0x58/0xa8
[ 17.725381] really_probe+0xec/0x4c8
[ 17.728967] driver_probe_device+0xf4/0x160
[ 17.733160] device_driver_attach+0x74/0x80
[ 17.737355] __driver_attach+0xa4/0x170
[ 17.741202] bus_for_each_dev+0x74/0xc8
[ 17.745043] driver_attach+0x28/0x30
[ 17.748620] bus_add_driver+0x144/0x228
[ 17.752462] driver_register+0x68/0x118
[ 17.756308] __platform_driver_register+0x4c/0x58
[ 17.761022] cdns_imx_driver_init+0x24/0x1000 [cdns3_imx]
[ 17.766434] do_one_initcall+0x48/0x2c0
[ 17.770280] do_init_module+0x5c/0x220
[ 17.774029] load_module+0x210c/0x2858
[ 17.777784] __do_sys_finit_module+0xb8/0x120
[ 17.782148] __arm64_sys_finit_module+0x24/0x30
[ 17.786691] el0_svc_common.constprop.0+0x70/0x168
[ 17.791497] do_el0_svc+0x28/0x88
[ 17.794822] el0_sync_handler+0x158/0x160
[ 17.798833] el0_sync+0x140/0x180
[ 17.802158] Code: aa0203f5 91002043 8b205021 a90153f3 (f801047f)
Cc: <stable@vger.kernel.org>
Fixes: 1e056efab993 ("usb: cdns3: add NXP imx8qm glue layer")
Signed-off-by: Peter Chen <peter.chen@nxp.com>
2020-12-10 16:31:36 +03:00
data - > clks = devm_kmemdup ( dev , imx_cdns3_core_clks ,
sizeof ( imx_cdns3_core_clks ) , GFP_KERNEL ) ;
if ( ! data - > clks )
return - ENOMEM ;
2020-01-09 12:35:58 +03:00
ret = devm_clk_bulk_get ( dev , data - > num_clks , data - > clks ) ;
if ( ret )
return ret ;
ret = clk_bulk_prepare_enable ( data - > num_clks , data - > clks ) ;
if ( ret )
return ret ;
ret = cdns_imx_noncore_init ( data ) ;
if ( ret )
goto err ;
2020-09-02 12:57:33 +03:00
ret = of_platform_populate ( node , NULL , cdns_imx_auxdata , dev ) ;
2020-01-09 12:35:58 +03:00
if ( ret ) {
dev_err ( dev , " failed to create children: %d \n " , ret ) ;
goto err ;
}
2020-09-02 12:57:33 +03:00
device_set_wakeup_capable ( dev , true ) ;
pm_runtime_set_active ( dev ) ;
pm_runtime_enable ( dev ) ;
2020-01-09 12:35:58 +03:00
2020-09-02 12:57:33 +03:00
return ret ;
2020-01-09 12:35:58 +03:00
err :
clk_bulk_disable_unprepare ( data - > num_clks , data - > clks ) ;
return ret ;
}
static int cdns_imx_remove ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
2020-12-10 16:33:21 +03:00
struct cdns_imx * data = dev_get_drvdata ( dev ) ;
2020-01-09 12:35:58 +03:00
2020-12-10 16:33:21 +03:00
pm_runtime_get_sync ( dev ) ;
2020-12-10 16:31:37 +03:00
of_platform_depopulate ( dev ) ;
2020-12-10 16:33:21 +03:00
clk_bulk_disable_unprepare ( data - > num_clks , data - > clks ) ;
pm_runtime_disable ( dev ) ;
pm_runtime_put_noidle ( dev ) ;
2020-01-09 12:35:58 +03:00
platform_set_drvdata ( pdev , NULL ) ;
return 0 ;
}
2020-09-02 12:57:33 +03:00
# ifdef CONFIG_PM
static void cdns3_set_wakeup ( struct cdns_imx * data , bool enable )
{
u32 value ;
value = cdns_imx_readl ( data , USB3_INT_REG ) ;
if ( enable )
value | = OTG_WAKEUP_EN | DEVU3_WAEKUP_EN ;
else
value & = ~ ( OTG_WAKEUP_EN | DEVU3_WAEKUP_EN ) ;
cdns_imx_writel ( data , USB3_INT_REG , value ) ;
}
static int cdns_imx_platform_suspend ( struct device * dev ,
bool suspend , bool wakeup )
{
2020-12-07 13:32:21 +03:00
struct cdns * cdns = dev_get_drvdata ( dev ) ;
2020-09-02 12:57:33 +03:00
struct device * parent = dev - > parent ;
struct cdns_imx * data = dev_get_drvdata ( parent ) ;
void __iomem * otg_regs = ( void __iomem * ) ( cdns - > otg_regs ) ;
void __iomem * xhci_regs = cdns - > xhci_regs ;
u32 value ;
int ret = 0 ;
if ( cdns - > role ! = USB_ROLE_HOST )
return 0 ;
if ( suspend ) {
/* SW request low power when all usb ports allow to it ??? */
value = readl ( xhci_regs + XECP_PM_PMCSR ) ;
value & = ~ PS_MASK ;
value | = PS_D1 ;
writel ( value , xhci_regs + XECP_PM_PMCSR ) ;
/* mdctrl_clk_sel */
value = cdns_imx_readl ( data , USB3_CORE_CTRL1 ) ;
value | = MDCTRL_CLK_SEL ;
cdns_imx_writel ( data , USB3_CORE_CTRL1 , value ) ;
/* wait for mdctrl_clk_status */
value = cdns_imx_readl ( data , USB3_CORE_STATUS ) ;
ret = readl_poll_timeout ( data - > noncore + USB3_CORE_STATUS , value ,
( value & MDCTRL_CLK_STATUS ) = = MDCTRL_CLK_STATUS ,
10 , 100000 ) ;
if ( ret )
dev_warn ( parent , " wait mdctrl_clk_status timeout \n " ) ;
/* wait lpm_clk_req to be 0 */
value = cdns_imx_readl ( data , USB3_INT_REG ) ;
ret = readl_poll_timeout ( data - > noncore + USB3_INT_REG , value ,
( value & LPM_CLK_REQ ) ! = LPM_CLK_REQ ,
10 , 100000 ) ;
if ( ret )
dev_warn ( parent , " wait lpm_clk_req timeout \n " ) ;
/* wait phy_refclk_req to be 0 */
value = cdns_imx_readl ( data , USB3_SSPHY_STATUS ) ;
ret = readl_poll_timeout ( data - > noncore + USB3_SSPHY_STATUS , value ,
( value & PHY_REFCLK_REQ ) ! = PHY_REFCLK_REQ ,
10 , 100000 ) ;
if ( ret )
dev_warn ( parent , " wait phy_refclk_req timeout \n " ) ;
cdns3_set_wakeup ( data , wakeup ) ;
} else {
cdns3_set_wakeup ( data , false ) ;
/* SW request D0 */
value = readl ( xhci_regs + XECP_PM_PMCSR ) ;
value & = ~ PS_MASK ;
value | = PS_D0 ;
writel ( value , xhci_regs + XECP_PM_PMCSR ) ;
/* clr CFG_RXDET_P3_EN */
value = readl ( xhci_regs + XECP_AUX_CTRL_REG1 ) ;
value & = ~ CFG_RXDET_P3_EN ;
writel ( value , xhci_regs + XECP_AUX_CTRL_REG1 ) ;
/* clear mdctrl_clk_sel */
value = cdns_imx_readl ( data , USB3_CORE_CTRL1 ) ;
value & = ~ MDCTRL_CLK_SEL ;
cdns_imx_writel ( data , USB3_CORE_CTRL1 , value ) ;
/* wait CLK_125_REQ to be 1 */
value = cdns_imx_readl ( data , USB3_INT_REG ) ;
ret = readl_poll_timeout ( data - > noncore + USB3_INT_REG , value ,
( value & CLK_125_REQ ) = = CLK_125_REQ ,
10 , 100000 ) ;
if ( ret )
dev_warn ( parent , " wait CLK_125_REQ timeout \n " ) ;
/* wait for mdctrl_clk_status is cleared */
value = cdns_imx_readl ( data , USB3_CORE_STATUS ) ;
ret = readl_poll_timeout ( data - > noncore + USB3_CORE_STATUS , value ,
( value & MDCTRL_CLK_STATUS ) ! = MDCTRL_CLK_STATUS ,
10 , 100000 ) ;
if ( ret )
dev_warn ( parent , " wait mdctrl_clk_status cleared timeout \n " ) ;
/* Wait until OTG_NRDY is 0 */
value = readl ( otg_regs + OTGSTS ) ;
ret = readl_poll_timeout ( otg_regs + OTGSTS , value ,
( value & OTG_NRDY ) ! = OTG_NRDY ,
10 , 100000 ) ;
if ( ret )
dev_warn ( parent , " wait OTG ready timeout \n " ) ;
}
return ret ;
}
static int cdns_imx_resume ( struct device * dev )
{
struct cdns_imx * data = dev_get_drvdata ( dev ) ;
return clk_bulk_prepare_enable ( data - > num_clks , data - > clks ) ;
}
static int cdns_imx_suspend ( struct device * dev )
{
struct cdns_imx * data = dev_get_drvdata ( dev ) ;
clk_bulk_disable_unprepare ( data - > num_clks , data - > clks ) ;
return 0 ;
}
2021-02-19 01:51:09 +03:00
/* Indicate if the controller was power lost before */
static inline bool cdns_imx_is_power_lost ( struct cdns_imx * data )
{
u32 value ;
value = cdns_imx_readl ( data , USB3_CORE_CTRL1 ) ;
if ( ( value & SW_RESET_MASK ) = = ALL_SW_RESET )
return true ;
else
return false ;
}
2021-03-10 13:46:54 +03:00
static int __maybe_unused cdns_imx_system_resume ( struct device * dev )
2021-02-19 01:51:09 +03:00
{
struct cdns_imx * data = dev_get_drvdata ( dev ) ;
int ret ;
ret = cdns_imx_resume ( dev ) ;
if ( ret )
return ret ;
if ( cdns_imx_is_power_lost ( data ) ) {
dev_dbg ( dev , " resume from power lost \n " ) ;
ret = cdns_imx_noncore_init ( data ) ;
if ( ret )
cdns_imx_suspend ( dev ) ;
}
return ret ;
}
2020-09-02 12:57:33 +03:00
# else
static int cdns_imx_platform_suspend ( struct device * dev ,
bool suspend , bool wakeup )
{
return 0 ;
}
# endif /* CONFIG_PM */
static const struct dev_pm_ops cdns_imx_pm_ops = {
SET_RUNTIME_PM_OPS ( cdns_imx_suspend , cdns_imx_resume , NULL )
2021-02-19 01:51:09 +03:00
SET_SYSTEM_SLEEP_PM_OPS ( cdns_imx_suspend , cdns_imx_system_resume )
2020-09-02 12:57:33 +03:00
} ;
2020-01-09 12:35:58 +03:00
static const struct of_device_id cdns_imx_of_match [ ] = {
{ . compatible = " fsl,imx8qm-usb3 " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , cdns_imx_of_match ) ;
static struct platform_driver cdns_imx_driver = {
. probe = cdns_imx_probe ,
. remove = cdns_imx_remove ,
. driver = {
. name = " cdns3-imx " ,
. of_match_table = cdns_imx_of_match ,
2020-09-02 12:57:33 +03:00
. pm = & cdns_imx_pm_ops ,
2020-01-09 12:35:58 +03:00
} ,
} ;
module_platform_driver ( cdns_imx_driver ) ;
MODULE_ALIAS ( " platform:cdns3-imx " ) ;
MODULE_AUTHOR ( " Peter Chen <peter.chen@nxp.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_DESCRIPTION ( " Cadence USB3 i.MX Glue Layer " ) ;