2017-11-03 13:28:30 +03:00
// SPDX-License-Identifier: GPL-2.0
2012-03-13 04:04:47 +04:00
/*
* Generic platform ohci driver
*
* Copyright 2007 Michael Buesch < m @ bues . ch >
* Copyright 2011 - 2012 Hauke Mehrtens < hauke @ hauke - m . de >
2014-02-07 19:36:40 +04:00
* Copyright 2014 Hans de Goede < hdegoede @ redhat . com >
2012-03-13 04:04:47 +04:00
*
* Derived from the OCHI - SSB driver
* Derived from the OHCI - PCI driver
* Copyright 1999 Roman Weissgaerber
* Copyright 2000 - 2002 David Brownell
* Copyright 1999 Linus Torvalds
* Copyright 1999 Gregory P . Smith
*/
2013-06-03 19:16:08 +04:00
2014-02-07 19:36:40 +04:00
# include <linux/clk.h>
# include <linux/dma-mapping.h>
2013-06-03 19:16:08 +04:00
# include <linux/hrtimer.h>
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/module.h>
2013-01-21 14:09:22 +04:00
# include <linux/err.h>
2018-03-04 00:43:08 +03:00
# include <linux/of.h>
2012-03-13 04:04:47 +04:00
# include <linux/platform_device.h>
2017-05-25 19:13:31 +03:00
# include <linux/pm_runtime.h>
2014-05-13 19:44:20 +04:00
# include <linux/reset.h>
2012-03-13 04:04:47 +04:00
# include <linux/usb/ohci_pdriver.h>
2013-06-03 19:16:08 +04:00
# include <linux/usb.h>
# include <linux/usb/hcd.h>
# include "ohci.h"
# define DRIVER_DESC "OHCI generic platform driver"
2014-02-07 19:36:40 +04:00
# define OHCI_MAX_CLKS 3
# define hcd_to_ohci_priv(h) ((struct ohci_platform_priv *)hcd_to_ohci(h)->priv)
struct ohci_platform_priv {
struct clk * clks [ OHCI_MAX_CLKS ] ;
2017-11-01 19:01:59 +03:00
struct reset_control * resets ;
2014-02-07 19:36:40 +04:00
} ;
2013-06-03 19:16:08 +04:00
static const char hcd_name [ ] = " ohci-platform " ;
2012-03-13 04:04:47 +04:00
2014-02-07 19:36:40 +04:00
static int ohci_platform_power_on ( struct platform_device * dev )
{
struct usb_hcd * hcd = platform_get_drvdata ( dev ) ;
struct ohci_platform_priv * priv = hcd_to_ohci_priv ( hcd ) ;
2018-03-04 00:43:08 +03:00
int clk , ret ;
2014-02-07 19:36:40 +04:00
for ( clk = 0 ; clk < OHCI_MAX_CLKS & & priv - > clks [ clk ] ; clk + + ) {
ret = clk_prepare_enable ( priv - > clks [ clk ] ) ;
if ( ret )
goto err_disable_clks ;
}
return 0 ;
err_disable_clks :
while ( - - clk > = 0 )
clk_disable_unprepare ( priv - > clks [ clk ] ) ;
return ret ;
}
static void ohci_platform_power_off ( struct platform_device * dev )
{
struct usb_hcd * hcd = platform_get_drvdata ( dev ) ;
struct ohci_platform_priv * priv = hcd_to_ohci_priv ( hcd ) ;
2018-03-04 00:43:08 +03:00
int clk ;
2014-02-07 19:36:40 +04:00
for ( clk = OHCI_MAX_CLKS - 1 ; clk > = 0 ; clk - - )
if ( priv - > clks [ clk ] )
clk_disable_unprepare ( priv - > clks [ clk ] ) ;
}
2013-06-03 19:16:08 +04:00
static struct hc_driver __read_mostly ohci_platform_hc_driver ;
2012-03-13 04:04:47 +04:00
2013-06-03 19:16:08 +04:00
static const struct ohci_driver_overrides platform_overrides __initconst = {
2014-02-07 19:36:40 +04:00
. product_desc = " Generic Platform OHCI controller " ,
. extra_priv_size = sizeof ( struct ohci_platform_priv ) ,
} ;
static struct usb_ohci_pdata ohci_platform_defaults = {
. power_on = ohci_platform_power_on ,
. power_suspend = ohci_platform_power_off ,
. power_off = ohci_platform_power_off ,
2012-03-13 04:04:47 +04:00
} ;
2012-11-19 22:21:48 +04:00
static int ohci_platform_probe ( struct platform_device * dev )
2012-03-13 04:04:47 +04:00
{
struct usb_hcd * hcd ;
struct resource * res_mem ;
2013-07-30 14:59:40 +04:00
struct usb_ohci_pdata * pdata = dev_get_platdata ( & dev - > dev ) ;
2014-02-07 19:36:40 +04:00
struct ohci_platform_priv * priv ;
2014-02-07 19:36:42 +04:00
struct ohci_hcd * ohci ;
2018-03-04 00:43:08 +03:00
int err , irq , clk = 0 ;
2012-03-13 04:04:47 +04:00
if ( usb_disabled ( ) )
return - ENODEV ;
2014-02-07 19:36:40 +04:00
/*
* Use reasonable defaults so platforms don ' t have to provide these
* with DT probing on ARM .
*/
if ( ! pdata )
pdata = & ohci_platform_defaults ;
err = dma_coerce_mask_and_coherent ( & dev - > dev , DMA_BIT_MASK ( 32 ) ) ;
if ( err )
return err ;
2012-03-13 04:04:47 +04:00
irq = platform_get_irq ( dev , 0 ) ;
2019-07-30 21:15:46 +03:00
if ( irq < 0 )
2012-03-13 04:04:47 +04:00
return irq ;
2014-02-07 19:36:40 +04:00
hcd = usb_create_hcd ( & ohci_platform_hc_driver , & dev - > dev ,
dev_name ( & dev - > dev ) ) ;
if ( ! hcd )
return - ENOMEM ;
platform_set_drvdata ( dev , hcd ) ;
dev - > dev . platform_data = pdata ;
priv = hcd_to_ohci_priv ( hcd ) ;
2014-02-07 19:36:42 +04:00
ohci = hcd_to_ohci ( hcd ) ;
2014-02-07 19:36:40 +04:00
if ( pdata = = & ohci_platform_defaults & & dev - > dev . of_node ) {
2014-02-07 19:36:42 +04:00
if ( of_property_read_bool ( dev - > dev . of_node , " big-endian-regs " ) )
ohci - > flags | = OHCI_QUIRK_BE_MMIO ;
if ( of_property_read_bool ( dev - > dev . of_node , " big-endian-desc " ) )
ohci - > flags | = OHCI_QUIRK_BE_DESC ;
if ( of_property_read_bool ( dev - > dev . of_node , " big-endian " ) )
ohci - > flags | = OHCI_QUIRK_BE_MMIO | OHCI_QUIRK_BE_DESC ;
2014-10-11 22:10:48 +04:00
if ( of_property_read_bool ( dev - > dev . of_node , " no-big-frame-no " ) )
ohci - > flags | = OHCI_QUIRK_FRAME_NO ;
2017-05-25 19:13:32 +03:00
if ( of_property_read_bool ( dev - > dev . of_node ,
" remote-wakeup-connected " ) )
ohci - > hc_control = OHCI_CTRL_RWC ;
2014-10-11 22:10:48 +04:00
of_property_read_u32 ( dev - > dev . of_node , " num-ports " ,
& ohci - > num_ports ) ;
2014-02-07 19:36:40 +04:00
for ( clk = 0 ; clk < OHCI_MAX_CLKS ; clk + + ) {
priv - > clks [ clk ] = of_clk_get ( dev - > dev . of_node , clk ) ;
if ( IS_ERR ( priv - > clks [ clk ] ) ) {
err = PTR_ERR ( priv - > clks [ clk ] ) ;
if ( err = = - EPROBE_DEFER )
goto err_put_clks ;
priv - > clks [ clk ] = NULL ;
break ;
}
}
2017-11-01 19:01:59 +03:00
priv - > resets = devm_reset_control_array_get_optional_shared (
& dev - > dev ) ;
if ( IS_ERR ( priv - > resets ) ) {
err = PTR_ERR ( priv - > resets ) ;
goto err_put_clks ;
2016-06-02 18:14:06 +03:00
}
2017-11-01 19:01:59 +03:00
err = reset_control_deassert ( priv - > resets ) ;
if ( err )
goto err_put_clks ;
2014-02-07 19:36:40 +04:00
}
2014-02-11 20:26:00 +04:00
if ( pdata - > big_endian_desc )
ohci - > flags | = OHCI_QUIRK_BE_DESC ;
if ( pdata - > big_endian_mmio )
ohci - > flags | = OHCI_QUIRK_BE_MMIO ;
2014-10-11 22:10:49 +04:00
if ( pdata - > no_big_frame_no )
ohci - > flags | = OHCI_QUIRK_FRAME_NO ;
if ( pdata - > num_ports )
ohci - > num_ports = pdata - > num_ports ;
2014-02-11 20:26:00 +04:00
# ifndef CONFIG_USB_OHCI_BIG_ENDIAN_MMIO
if ( ohci - > flags & OHCI_QUIRK_BE_MMIO ) {
dev_err ( & dev - > dev ,
" Error: CONFIG_USB_OHCI_BIG_ENDIAN_MMIO not set \n " ) ;
err = - EINVAL ;
2014-05-13 19:44:20 +04:00
goto err_reset ;
2014-02-11 20:26:00 +04:00
}
# endif
# ifndef CONFIG_USB_OHCI_BIG_ENDIAN_DESC
if ( ohci - > flags & OHCI_QUIRK_BE_DESC ) {
dev_err ( & dev - > dev ,
" Error: CONFIG_USB_OHCI_BIG_ENDIAN_DESC not set \n " ) ;
err = - EINVAL ;
2014-05-13 19:44:20 +04:00
goto err_reset ;
2014-02-11 20:26:00 +04:00
}
# endif
2017-05-25 19:13:31 +03:00
pm_runtime_set_active ( & dev - > dev ) ;
pm_runtime_enable ( & dev - > dev ) ;
2012-08-07 05:09:10 +04:00
if ( pdata - > power_on ) {
err = pdata - > power_on ( dev ) ;
if ( err < 0 )
2014-05-13 19:44:20 +04:00
goto err_reset ;
2012-08-07 05:09:10 +04:00
}
2012-03-13 04:04:47 +04:00
2014-11-04 05:21:16 +03:00
res_mem = platform_get_resource ( dev , IORESOURCE_MEM , 0 ) ;
2013-01-21 14:09:22 +04:00
hcd - > regs = devm_ioremap_resource ( & dev - > dev , res_mem ) ;
if ( IS_ERR ( hcd - > regs ) ) {
err = PTR_ERR ( hcd - > regs ) ;
2014-02-07 19:36:40 +04:00
goto err_power ;
2012-08-14 10:47:37 +04:00
}
2014-11-04 05:21:16 +03:00
hcd - > rsrc_start = res_mem - > start ;
hcd - > rsrc_len = resource_size ( res_mem ) ;
2012-03-13 04:04:47 +04:00
err = usb_add_hcd ( hcd , irq , IRQF_SHARED ) ;
if ( err )
2014-02-07 19:36:40 +04:00
goto err_power ;
2012-03-13 04:04:47 +04:00
2013-11-05 06:46:02 +04:00
device_wakeup_enable ( hcd - > self . controller ) ;
2012-03-13 04:04:47 +04:00
platform_set_drvdata ( dev , hcd ) ;
return err ;
2012-08-07 05:09:10 +04:00
err_power :
if ( pdata - > power_off )
pdata - > power_off ( dev ) ;
2014-05-13 19:44:20 +04:00
err_reset :
2017-05-25 19:13:31 +03:00
pm_runtime_disable ( & dev - > dev ) ;
2017-11-01 19:01:59 +03:00
reset_control_assert ( priv - > resets ) ;
2014-02-07 19:36:40 +04:00
err_put_clks :
while ( - - clk > = 0 )
clk_put ( priv - > clks [ clk ] ) ;
2018-03-04 00:43:08 +03:00
2014-02-07 19:36:40 +04:00
if ( pdata = = & ohci_platform_defaults )
dev - > dev . platform_data = NULL ;
usb_put_hcd ( hcd ) ;
2012-08-07 05:09:10 +04:00
2012-03-13 04:04:47 +04:00
return err ;
}
2012-11-19 22:26:20 +04:00
static int ohci_platform_remove ( struct platform_device * dev )
2012-03-13 04:04:47 +04:00
{
struct usb_hcd * hcd = platform_get_drvdata ( dev ) ;
2013-07-30 14:59:40 +04:00
struct usb_ohci_pdata * pdata = dev_get_platdata ( & dev - > dev ) ;
2014-02-07 19:36:40 +04:00
struct ohci_platform_priv * priv = hcd_to_ohci_priv ( hcd ) ;
2017-11-01 19:01:59 +03:00
int clk ;
2012-03-13 04:04:47 +04:00
2017-05-25 19:13:31 +03:00
pm_runtime_get_sync ( & dev - > dev ) ;
2012-03-13 04:04:47 +04:00
usb_remove_hcd ( hcd ) ;
2012-08-07 05:09:10 +04:00
if ( pdata - > power_off )
pdata - > power_off ( dev ) ;
2017-11-01 19:01:59 +03:00
reset_control_assert ( priv - > resets ) ;
2014-05-13 19:44:20 +04:00
2014-02-07 19:36:40 +04:00
for ( clk = 0 ; clk < OHCI_MAX_CLKS & & priv - > clks [ clk ] ; clk + + )
clk_put ( priv - > clks [ clk ] ) ;
usb_put_hcd ( hcd ) ;
2017-05-25 19:13:31 +03:00
pm_runtime_put_sync ( & dev - > dev ) ;
pm_runtime_disable ( & dev - > dev ) ;
2014-02-07 19:36:40 +04:00
if ( pdata = = & ohci_platform_defaults )
dev - > dev . platform_data = NULL ;
2012-03-13 04:04:47 +04:00
return 0 ;
}
2014-10-24 08:45:47 +04:00
# ifdef CONFIG_PM_SLEEP
2012-03-13 04:04:47 +04:00
static int ohci_platform_suspend ( struct device * dev )
{
2013-10-04 08:28:14 +04:00
struct usb_hcd * hcd = dev_get_drvdata ( dev ) ;
struct usb_ohci_pdata * pdata = dev - > platform_data ;
2015-12-23 16:26:52 +03:00
struct platform_device * pdev = to_platform_device ( dev ) ;
2013-10-04 08:28:14 +04:00
bool do_wakeup = device_may_wakeup ( dev ) ;
int ret ;
ret = ohci_suspend ( hcd , do_wakeup ) ;
if ( ret )
return ret ;
2012-08-07 05:09:10 +04:00
if ( pdata - > power_suspend )
pdata - > power_suspend ( pdev ) ;
2013-10-04 08:28:14 +04:00
return ret ;
2012-03-13 04:04:47 +04:00
}
static int ohci_platform_resume ( struct device * dev )
{
struct usb_hcd * hcd = dev_get_drvdata ( dev ) ;
2013-07-30 14:59:40 +04:00
struct usb_ohci_pdata * pdata = dev_get_platdata ( dev ) ;
2015-12-23 16:26:52 +03:00
struct platform_device * pdev = to_platform_device ( dev ) ;
2012-08-07 05:09:10 +04:00
if ( pdata - > power_on ) {
int err = pdata - > power_on ( pdev ) ;
if ( err < 0 )
return err ;
}
2012-03-13 04:04:47 +04:00
2012-10-08 17:11:29 +04:00
ohci_resume ( hcd , false ) ;
2020-05-18 18:49:29 +03:00
pm_runtime_disable ( dev ) ;
pm_runtime_set_active ( dev ) ;
pm_runtime_enable ( dev ) ;
2012-03-13 04:04:47 +04:00
return 0 ;
}
2014-10-24 08:45:47 +04:00
# endif /* CONFIG_PM_SLEEP */
2012-03-13 04:04:47 +04:00
2014-02-07 19:36:40 +04:00
static const struct of_device_id ohci_platform_ids [ ] = {
2014-02-11 20:35:28 +04:00
{ . compatible = " generic-ohci " , } ,
2015-01-06 15:48:56 +03:00
{ . compatible = " cavium,octeon-6335-ohci " , } ,
2017-05-25 19:13:32 +03:00
{ . compatible = " ti,ohci-omap3 " , } ,
2014-02-07 19:36:40 +04:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , ohci_platform_ids ) ;
2012-03-13 04:04:47 +04:00
static const struct platform_device_id ohci_platform_table [ ] = {
{ " ohci-platform " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( platform , ohci_platform_table ) ;
2014-10-24 08:45:47 +04:00
static SIMPLE_DEV_PM_OPS ( ohci_platform_pm_ops , ohci_platform_suspend ,
ohci_platform_resume ) ;
2012-03-13 04:04:47 +04:00
static struct platform_driver ohci_platform_driver = {
. id_table = ohci_platform_table ,
. probe = ohci_platform_probe ,
2012-11-19 22:21:08 +04:00
. remove = ohci_platform_remove ,
2012-03-13 04:04:47 +04:00
. shutdown = usb_hcd_platform_shutdown ,
. driver = {
. name = " ohci-platform " ,
. pm = & ohci_platform_pm_ops ,
2014-02-07 19:36:40 +04:00
. of_match_table = ohci_platform_ids ,
2012-03-13 04:04:47 +04:00
}
} ;
2013-06-03 19:16:08 +04:00
static int __init ohci_platform_init ( void )
{
if ( usb_disabled ( ) )
return - ENODEV ;
pr_info ( " %s: " DRIVER_DESC " \n " , hcd_name ) ;
ohci_init_driver ( & ohci_platform_hc_driver , & platform_overrides ) ;
return platform_driver_register ( & ohci_platform_driver ) ;
}
module_init ( ohci_platform_init ) ;
static void __exit ohci_platform_cleanup ( void )
{
platform_driver_unregister ( & ohci_platform_driver ) ;
}
module_exit ( ohci_platform_cleanup ) ;
MODULE_DESCRIPTION ( DRIVER_DESC ) ;
MODULE_AUTHOR ( " Hauke Mehrtens " ) ;
MODULE_AUTHOR ( " Alan Stern " ) ;
MODULE_LICENSE ( " GPL " ) ;