2017-11-03 13:28:30 +03:00
// SPDX-License-Identifier: GPL-2.0+
2011-12-20 09:20:23 +04:00
/*
* Copyright ( C ) 2011 Marvell International Ltd . All rights reserved .
* Author : Chao Xie < chao . xie @ marvell . com >
* Neil Zhang < zhangwm @ marvell . com >
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/clk.h>
2012-06-26 16:10:32 +04:00
# include <linux/err.h>
2011-12-20 09:20:23 +04:00
# include <linux/usb/otg.h>
2019-12-21 09:50:06 +03:00
# include <linux/usb/of.h>
2011-12-20 09:20:23 +04:00
# include <linux/platform_data/mv_usb.h>
2018-08-22 23:42:56 +03:00
# include <linux/io.h>
# include <linux/usb/hcd.h>
# include "ehci.h"
2011-12-20 09:20:23 +04:00
2018-08-22 23:43:01 +03:00
/* registers */
# define U2x_CAPREGS_OFFSET 0x100
2011-12-20 09:20:23 +04:00
# define CAPLENGTH_MASK (0xff)
2018-08-22 23:42:56 +03:00
# define hcd_to_ehci_hcd_mv(h) ((struct ehci_hcd_mv *)hcd_to_ehci(h)->priv)
2011-12-20 09:20:23 +04:00
2018-08-22 23:42:56 +03:00
struct ehci_hcd_mv {
2011-12-20 09:20:23 +04:00
/* Which mode does this ehci running OTG/Host ? */
int mode ;
2018-08-22 23:43:01 +03:00
void __iomem * base ;
2011-12-20 09:20:23 +04:00
void __iomem * cap_regs ;
void __iomem * op_regs ;
2012-02-13 15:24:02 +04:00
struct usb_phy * otg ;
2018-08-22 23:43:04 +03:00
struct clk * clk ;
2011-12-20 09:20:23 +04:00
2018-08-22 23:43:01 +03:00
struct phy * phy ;
2011-12-20 09:20:23 +04:00
2018-08-22 23:43:04 +03:00
int ( * set_vbus ) ( unsigned int vbus ) ;
2011-12-20 09:20:23 +04:00
} ;
2021-07-08 11:30:56 +03:00
static int mv_ehci_enable ( struct ehci_hcd_mv * ehci_mv )
2011-12-20 09:20:23 +04:00
{
2021-07-08 11:30:56 +03:00
int retval ;
2011-12-20 09:20:23 +04:00
2021-07-08 11:30:56 +03:00
retval = clk_prepare_enable ( ehci_mv - > clk ) ;
if ( retval )
return retval ;
2011-12-20 09:20:23 +04:00
2021-07-08 11:30:56 +03:00
retval = phy_init ( ehci_mv - > phy ) ;
if ( retval )
clk_disable_unprepare ( ehci_mv - > clk ) ;
return retval ;
2011-12-20 09:20:23 +04:00
}
static void mv_ehci_disable ( struct ehci_hcd_mv * ehci_mv )
{
2018-08-22 23:43:01 +03:00
phy_exit ( ehci_mv - > phy ) ;
2021-07-08 11:30:56 +03:00
clk_disable_unprepare ( ehci_mv - > clk ) ;
2011-12-20 09:20:23 +04:00
}
static int mv_ehci_reset ( struct usb_hcd * hcd )
{
struct device * dev = hcd - > self . controller ;
2018-08-22 23:42:56 +03:00
struct ehci_hcd_mv * ehci_mv = hcd_to_ehci_hcd_mv ( hcd ) ;
2019-12-21 09:50:06 +03:00
struct ehci_hcd * ehci = hcd_to_ehci ( hcd ) ;
u32 status ;
2011-12-20 09:20:23 +04:00
int retval ;
if ( ehci_mv = = NULL ) {
dev_err ( dev , " Can not find private ehci data \n " ) ;
return - ENODEV ;
}
hcd - > has_tt = 1 ;
2012-07-09 23:55:14 +04:00
retval = ehci_setup ( hcd ) ;
if ( retval )
dev_err ( dev , " ehci_setup failed %d \n " , retval ) ;
2011-12-20 09:20:23 +04:00
2019-12-21 09:50:06 +03:00
if ( of_usb_get_phy_mode ( dev - > of_node ) = = USBPHY_INTERFACE_MODE_HSIC ) {
status = ehci_readl ( ehci , & ehci - > regs - > port_status [ 0 ] ) ;
status | = PORT_TEST_FORCE ;
ehci_writel ( ehci , status , & ehci - > regs - > port_status [ 0 ] ) ;
status & = ~ PORT_TEST_FORCE ;
ehci_writel ( ehci , status , & ehci - > regs - > port_status [ 0 ] ) ;
}
2012-07-09 23:55:14 +04:00
return retval ;
2011-12-20 09:20:23 +04:00
}
2018-08-22 23:42:56 +03:00
static struct hc_driver __read_mostly ehci_platform_hc_driver ;
static const struct ehci_driver_overrides platform_overrides __initconst = {
. reset = mv_ehci_reset ,
. extra_priv_size = sizeof ( struct ehci_hcd_mv ) ,
2011-12-20 09:20:23 +04:00
} ;
static int mv_ehci_probe ( struct platform_device * pdev )
{
2013-07-30 14:59:40 +04:00
struct mv_usb_platform_data * pdata = dev_get_platdata ( & pdev - > dev ) ;
2011-12-20 09:20:23 +04:00
struct usb_hcd * hcd ;
struct ehci_hcd * ehci ;
struct ehci_hcd_mv * ehci_mv ;
struct resource * r ;
2020-05-08 17:21:36 +03:00
int retval ;
2011-12-20 09:20:23 +04:00
u32 offset ;
2020-03-09 16:00:13 +03:00
u32 status ;
2011-12-20 09:20:23 +04:00
if ( usb_disabled ( ) )
return - ENODEV ;
2020-03-09 16:00:14 +03:00
hcd = usb_create_hcd ( & ehci_platform_hc_driver , & pdev - > dev , dev_name ( & pdev - > dev ) ) ;
2011-12-20 09:20:23 +04:00
if ( ! hcd )
return - ENOMEM ;
2018-08-22 23:42:56 +03:00
platform_set_drvdata ( pdev , hcd ) ;
ehci_mv = hcd_to_ehci_hcd_mv ( hcd ) ;
2018-08-22 23:43:04 +03:00
ehci_mv - > mode = MV_USB_MODE_HOST ;
if ( pdata ) {
ehci_mv - > mode = pdata - > mode ;
ehci_mv - > set_vbus = pdata - > set_vbus ;
}
2011-12-20 09:20:23 +04:00
2019-12-21 09:50:07 +03:00
ehci_mv - > phy = devm_phy_optional_get ( & pdev - > dev , " usb " ) ;
2018-08-22 23:43:01 +03:00
if ( IS_ERR ( ehci_mv - > phy ) ) {
retval = PTR_ERR ( ehci_mv - > phy ) ;
if ( retval ! = - EPROBE_DEFER )
dev_err ( & pdev - > dev , " Failed to get phy. \n " ) ;
goto err_put_hcd ;
}
2013-03-25 11:06:54 +04:00
ehci_mv - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( ehci_mv - > clk ) ) {
dev_err ( & pdev - > dev , " error getting clock \n " ) ;
retval = PTR_ERR ( ehci_mv - > clk ) ;
2013-05-06 14:05:54 +04:00
goto err_put_hcd ;
2011-12-20 09:20:23 +04:00
}
2023-07-26 14:38:12 +03:00
ehci_mv - > base = devm_platform_get_and_ioremap_resource ( pdev , 0 , & r ) ;
2018-08-22 23:43:01 +03:00
if ( IS_ERR ( ehci_mv - > base ) ) {
retval = PTR_ERR ( ehci_mv - > base ) ;
2013-05-06 14:05:54 +04:00
goto err_put_hcd ;
2011-12-20 09:20:23 +04:00
}
retval = mv_ehci_enable ( ehci_mv ) ;
if ( retval ) {
dev_err ( & pdev - > dev , " init phy error %d \n " , retval ) ;
2013-05-06 14:05:54 +04:00
goto err_put_hcd ;
2011-12-20 09:20:23 +04:00
}
2018-08-22 23:43:01 +03:00
ehci_mv - > cap_regs =
( void __iomem * ) ( ( unsigned long ) ehci_mv - > base + U2x_CAPREGS_OFFSET ) ;
2011-12-20 09:20:23 +04:00
offset = readl ( ehci_mv - > cap_regs ) & CAPLENGTH_MASK ;
ehci_mv - > op_regs =
( void __iomem * ) ( ( unsigned long ) ehci_mv - > cap_regs + offset ) ;
hcd - > rsrc_start = r - > start ;
2013-03-10 17:52:02 +04:00
hcd - > rsrc_len = resource_size ( r ) ;
2011-12-20 09:20:23 +04:00
hcd - > regs = ehci_mv - > op_regs ;
2020-05-15 19:54:53 +03:00
retval = platform_get_irq ( pdev , 0 ) ;
if ( retval < 0 )
2011-12-20 09:20:23 +04:00
goto err_disable_clk ;
2020-05-15 19:54:53 +03:00
hcd - > irq = retval ;
2011-12-20 09:20:23 +04:00
ehci = hcd_to_ehci ( hcd ) ;
2020-01-03 19:40:31 +03:00
ehci - > caps = ( struct ehci_caps __iomem * ) ehci_mv - > cap_regs ;
2011-12-20 09:20:23 +04:00
if ( ehci_mv - > mode = = MV_USB_MODE_OTG ) {
2012-07-29 23:46:12 +04:00
ehci_mv - > otg = devm_usb_get_phy ( & pdev - > dev , USB_PHY_TYPE_USB2 ) ;
2013-03-15 13:03:30 +04:00
if ( IS_ERR ( ehci_mv - > otg ) ) {
retval = PTR_ERR ( ehci_mv - > otg ) ;
if ( retval = = - ENXIO )
dev_info ( & pdev - > dev , " MV_USB_MODE_OTG "
" must have CONFIG_USB_PHY enabled \n " ) ;
else
dev_err ( & pdev - > dev ,
" unable to find transceiver \n " ) ;
2011-12-20 09:20:23 +04:00
goto err_disable_clk ;
}
2012-02-13 15:24:20 +04:00
retval = otg_set_host ( ehci_mv - > otg - > otg , & hcd - > self ) ;
2011-12-20 09:20:23 +04:00
if ( retval < 0 ) {
dev_err ( & pdev - > dev ,
" unable to register with transceiver \n " ) ;
retval = - ENODEV ;
2012-07-29 23:46:12 +04:00
goto err_disable_clk ;
2011-12-20 09:20:23 +04:00
}
/* otg will enable clock before use as host */
mv_ehci_disable ( ehci_mv ) ;
} else {
2018-08-22 23:43:04 +03:00
if ( ehci_mv - > set_vbus )
ehci_mv - > set_vbus ( 1 ) ;
2011-12-20 09:20:23 +04:00
retval = usb_add_hcd ( hcd , hcd - > irq , IRQF_SHARED ) ;
if ( retval ) {
dev_err ( & pdev - > dev ,
" failed to add hcd with err %d \n " , retval ) ;
goto err_set_vbus ;
}
2013-11-05 06:46:02 +04:00
device_wakeup_enable ( hcd - > self . controller ) ;
2011-12-20 09:20:23 +04:00
}
2020-03-09 16:00:13 +03:00
if ( of_usb_get_phy_mode ( pdev - > dev . of_node ) = = USBPHY_INTERFACE_MODE_HSIC ) {
status = ehci_readl ( ehci , & ehci - > regs - > port_status [ 0 ] ) ;
/* These "reserved" bits actually enable HSIC mode. */
status | = BIT ( 25 ) ;
status & = ~ GENMASK ( 31 , 30 ) ;
ehci_writel ( ehci , status , & ehci - > regs - > port_status [ 0 ] ) ;
}
2011-12-20 09:20:23 +04:00
dev_info ( & pdev - > dev ,
" successful find EHCI device with regs 0x%p irq %d "
" working in %s mode \n " , hcd - > regs , hcd - > irq ,
ehci_mv - > mode = = MV_USB_MODE_OTG ? " OTG " : " Host " ) ;
return 0 ;
err_set_vbus :
2018-08-22 23:43:04 +03:00
if ( ehci_mv - > set_vbus )
ehci_mv - > set_vbus ( 0 ) ;
2011-12-20 09:20:23 +04:00
err_disable_clk :
mv_ehci_disable ( ehci_mv ) ;
err_put_hcd :
usb_put_hcd ( hcd ) ;
return retval ;
}
2023-05-18 02:01:52 +03:00
static void mv_ehci_remove ( struct platform_device * pdev )
2011-12-20 09:20:23 +04:00
{
2018-08-22 23:42:56 +03:00
struct usb_hcd * hcd = platform_get_drvdata ( pdev ) ;
struct ehci_hcd_mv * ehci_mv = hcd_to_ehci_hcd_mv ( hcd ) ;
2011-12-20 09:20:23 +04:00
if ( hcd - > rh_registered )
usb_remove_hcd ( hcd ) ;
2012-07-29 23:46:12 +04:00
if ( ! IS_ERR_OR_NULL ( ehci_mv - > otg ) )
2012-02-13 15:24:20 +04:00
otg_set_host ( ehci_mv - > otg - > otg , NULL ) ;
2011-12-20 09:20:23 +04:00
if ( ehci_mv - > mode = = MV_USB_MODE_HOST ) {
2018-08-22 23:43:04 +03:00
if ( ehci_mv - > set_vbus )
ehci_mv - > set_vbus ( 0 ) ;
2011-12-20 09:20:23 +04:00
mv_ehci_disable ( ehci_mv ) ;
}
usb_put_hcd ( hcd ) ;
}
static const struct platform_device_id ehci_id_table [ ] = {
2019-12-21 09:50:08 +03:00
{ " pxa-u2oehci " , 0 } ,
{ " pxa-sph " , 0 } ,
2011-12-20 09:20:23 +04:00
{ } ,
} ;
static void mv_ehci_shutdown ( struct platform_device * pdev )
{
2018-08-22 23:42:56 +03:00
struct usb_hcd * hcd = platform_get_drvdata ( pdev ) ;
2011-12-20 09:20:23 +04:00
if ( ! hcd - > rh_registered )
return ;
if ( hcd - > driver - > shutdown )
hcd - > driver - > shutdown ( hcd ) ;
}
2018-08-22 23:43:04 +03:00
static const struct of_device_id ehci_mv_dt_ids [ ] = {
{ . compatible = " marvell,pxau2o-ehci " , } ,
{ } ,
} ;
2011-12-20 09:20:23 +04:00
static struct platform_driver ehci_mv_driver = {
. probe = mv_ehci_probe ,
2023-05-18 02:01:52 +03:00
. remove_new = mv_ehci_remove ,
2011-12-20 09:20:23 +04:00
. shutdown = mv_ehci_shutdown ,
. driver = {
2018-08-22 23:43:04 +03:00
. name = " mv-ehci " ,
. bus = & platform_bus_type ,
. of_match_table = ehci_mv_dt_ids ,
} ,
2011-12-20 09:20:23 +04:00
. id_table = ehci_id_table ,
} ;
2018-08-22 23:42:56 +03:00
static int __init ehci_platform_init ( void )
{
if ( usb_disabled ( ) )
return - ENODEV ;
ehci_init_driver ( & ehci_platform_hc_driver , & platform_overrides ) ;
return platform_driver_register ( & ehci_mv_driver ) ;
}
module_init ( ehci_platform_init ) ;
static void __exit ehci_platform_cleanup ( void )
{
platform_driver_unregister ( & ehci_mv_driver ) ;
}
module_exit ( ehci_platform_cleanup ) ;
MODULE_DESCRIPTION ( " Marvell EHCI driver " ) ;
MODULE_AUTHOR ( " Chao Xie <chao.xie@marvell.com> " ) ;
MODULE_AUTHOR ( " Neil Zhang <zhangwm@marvell.com> " ) ;
MODULE_ALIAS ( " mv-ehci " ) ;
MODULE_LICENSE ( " GPL " ) ;
2019-01-14 23:16:08 +03:00
MODULE_DEVICE_TABLE ( of , ehci_mv_dt_ids ) ;