2017-11-03 13:28:30 +03:00
// SPDX-License-Identifier: GPL-2.0+
2011-12-23 06:20:54 +04:00
/*
* SAMSUNG EXYNOS USB HOST OHCI Controller
*
* Copyright ( C ) 2011 Samsung Electronics Co . Ltd
* Author : Jingoo Han < jg1 . han @ samsung . com >
*/
# include <linux/clk.h>
2013-09-21 15:08:38 +04:00
# include <linux/dma-mapping.h>
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/module.h>
2012-07-16 09:55:36 +04:00
# include <linux/of.h>
2011-12-23 06:20:54 +04:00
# include <linux/platform_device.h>
2014-05-05 09:02:57 +04:00
# include <linux/phy/phy.h>
2013-09-21 15:08:38 +04:00
# include <linux/usb.h>
# include <linux/usb/hcd.h>
# include "ohci.h"
2020-01-04 18:20:55 +03:00
# define DRIVER_DESC "OHCI Exynos driver"
2013-09-21 15:08:38 +04:00
static const char hcd_name [ ] = " ohci-exynos " ;
static struct hc_driver __read_mostly exynos_ohci_hc_driver ;
# define to_exynos_ohci(hcd) (struct exynos_ohci_hcd *)(hcd_to_ohci(hcd)->priv)
2011-12-23 06:20:54 +04:00
2014-05-05 09:02:57 +04:00
# define PHY_NUMBER 3
2011-12-23 06:20:54 +04:00
struct exynos_ohci_hcd {
struct clk * clk ;
2019-05-20 12:08:23 +03:00
struct device_node * of_node ;
2014-09-22 09:46:19 +04:00
struct phy * phy [ PHY_NUMBER ] ;
2019-07-26 11:14:52 +03:00
bool legacy_phy ;
2011-12-23 06:20:54 +04:00
} ;
2014-05-05 09:02:57 +04:00
static int exynos_ohci_get_phy ( struct device * dev ,
struct exynos_ohci_hcd * exynos_ohci )
{
struct device_node * child ;
struct phy * phy ;
2019-07-26 11:14:52 +03:00
int phy_number , num_phys ;
2014-09-22 09:46:19 +04:00
int ret ;
2014-05-05 09:02:57 +04:00
2014-09-22 09:46:19 +04:00
/* Get PHYs for the controller */
2019-07-26 11:14:52 +03:00
num_phys = of_count_phandle_with_args ( dev - > of_node , " phys " ,
" #phy-cells " ) ;
for ( phy_number = 0 ; phy_number < num_phys ; phy_number + + ) {
phy = devm_of_phy_get_by_index ( dev , dev - > of_node , phy_number ) ;
if ( IS_ERR ( phy ) )
return PTR_ERR ( phy ) ;
exynos_ohci - > phy [ phy_number ] = phy ;
}
if ( num_phys > 0 )
return 0 ;
/* Get PHYs using legacy bindings */
2014-05-05 09:02:57 +04:00
for_each_available_child_of_node ( dev - > of_node , child ) {
ret = of_property_read_u32 ( child , " reg " , & phy_number ) ;
if ( ret ) {
dev_err ( dev , " Failed to parse device tree \n " ) ;
of_node_put ( child ) ;
return ret ;
}
if ( phy_number > = PHY_NUMBER ) {
dev_err ( dev , " Invalid number of PHYs \n " ) ;
of_node_put ( child ) ;
return - EINVAL ;
}
2014-06-06 12:43:44 +04:00
phy = devm_of_phy_get ( dev , child , NULL ) ;
2014-09-22 09:46:19 +04:00
exynos_ohci - > phy [ phy_number ] = phy ;
if ( IS_ERR ( phy ) ) {
ret = PTR_ERR ( phy ) ;
if ( ret = = - EPROBE_DEFER ) {
2017-01-07 11:41:41 +03:00
of_node_put ( child ) ;
2014-09-22 09:46:19 +04:00
return ret ;
} else if ( ret ! = - ENOSYS & & ret ! = - ENODEV ) {
dev_err ( dev ,
" Error retrieving usb2 phy: %d \n " , ret ) ;
2017-01-07 11:41:41 +03:00
of_node_put ( child ) ;
2014-09-22 09:46:19 +04:00
return ret ;
}
2014-08-05 14:39:08 +04:00
}
2014-05-05 09:02:57 +04:00
}
2019-07-26 11:14:52 +03:00
exynos_ohci - > legacy_phy = true ;
2014-09-22 09:46:19 +04:00
return 0 ;
2014-05-05 09:02:57 +04:00
}
static int exynos_ohci_phy_enable ( struct device * dev )
2013-01-22 17:00:43 +04:00
{
2014-05-05 09:03:42 +04:00
struct usb_hcd * hcd = dev_get_drvdata ( dev ) ;
2013-09-21 15:08:38 +04:00
struct exynos_ohci_hcd * exynos_ohci = to_exynos_ohci ( hcd ) ;
2014-05-05 09:02:57 +04:00
int i ;
int ret = 0 ;
for ( i = 0 ; ret = = 0 & & i < PHY_NUMBER ; i + + )
2014-09-22 09:46:19 +04:00
if ( ! IS_ERR ( exynos_ohci - > phy [ i ] ) )
ret = phy_power_on ( exynos_ohci - > phy [ i ] ) ;
2014-05-05 09:02:57 +04:00
if ( ret )
for ( i - - ; i > = 0 ; i - - )
2014-09-22 09:46:19 +04:00
if ( ! IS_ERR ( exynos_ohci - > phy [ i ] ) )
phy_power_off ( exynos_ohci - > phy [ i ] ) ;
2014-05-05 09:02:57 +04:00
return ret ;
2013-01-22 17:00:43 +04:00
}
2014-05-05 09:03:42 +04:00
static void exynos_ohci_phy_disable ( struct device * dev )
2013-01-22 17:00:43 +04:00
{
2014-05-05 09:03:42 +04:00
struct usb_hcd * hcd = dev_get_drvdata ( dev ) ;
2013-09-21 15:08:38 +04:00
struct exynos_ohci_hcd * exynos_ohci = to_exynos_ohci ( hcd ) ;
2014-05-05 09:02:57 +04:00
int i ;
2013-01-22 17:00:43 +04:00
2014-05-05 09:02:57 +04:00
for ( i = 0 ; i < PHY_NUMBER ; i + + )
2014-09-22 09:46:19 +04:00
if ( ! IS_ERR ( exynos_ohci - > phy [ i ] ) )
phy_power_off ( exynos_ohci - > phy [ i ] ) ;
2013-01-22 17:00:43 +04:00
}
2012-11-19 22:21:48 +04:00
static int exynos_ohci_probe ( struct platform_device * pdev )
2011-12-23 06:20:54 +04:00
{
struct exynos_ohci_hcd * exynos_ohci ;
struct usb_hcd * hcd ;
struct resource * res ;
int irq ;
int err ;
2012-07-16 09:55:36 +04:00
/*
* Right now device - tree probed devices don ' t get dma_mask set .
* Since shared usb code relies on it , set it here for now .
* Once we move to full device tree support this will vanish off .
*/
2013-06-27 15:36:37 +04:00
err = dma_coerce_mask_and_coherent ( & pdev - > dev , DMA_BIT_MASK ( 32 ) ) ;
2013-06-10 19:28:49 +04:00
if ( err )
return err ;
2012-07-16 09:55:36 +04:00
2013-09-21 15:08:38 +04:00
hcd = usb_create_hcd ( & exynos_ohci_hc_driver ,
& pdev - > dev , dev_name ( & pdev - > dev ) ) ;
if ( ! hcd ) {
dev_err ( & pdev - > dev , " Unable to create HCD \n " ) ;
2011-12-23 06:20:54 +04:00
return - ENOMEM ;
2013-09-21 15:08:38 +04:00
}
exynos_ohci = to_exynos_ohci ( hcd ) ;
2011-12-23 06:20:54 +04:00
2014-05-05 09:02:57 +04:00
err = exynos_ohci_get_phy ( & pdev - > dev , exynos_ohci ) ;
if ( err )
goto fail_clk ;
2013-01-22 17:00:43 +04:00
2012-10-04 11:11:50 +04:00
exynos_ohci - > clk = devm_clk_get ( & pdev - > dev , " usbhost " ) ;
2011-12-23 06:20:54 +04:00
if ( IS_ERR ( exynos_ohci - > clk ) ) {
dev_err ( & pdev - > dev , " Failed to get usbhost clock \n " ) ;
err = PTR_ERR ( exynos_ohci - > clk ) ;
goto fail_clk ;
}
2012-10-03 03:41:37 +04:00
err = clk_prepare_enable ( exynos_ohci - > clk ) ;
2011-12-23 06:20:54 +04:00
if ( err )
2012-10-04 11:11:50 +04:00
goto fail_clk ;
2011-12-23 06:20:54 +04:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2014-05-10 16:00:10 +04:00
hcd - > regs = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( hcd - > regs ) ) {
err = PTR_ERR ( hcd - > regs ) ;
2011-12-23 06:20:54 +04:00
goto fail_io ;
}
2014-11-04 05:21:13 +03:00
hcd - > rsrc_start = res - > start ;
hcd - > rsrc_len = resource_size ( res ) ;
2011-12-23 06:20:54 +04:00
irq = platform_get_irq ( pdev , 0 ) ;
2020-08-26 17:49:31 +03:00
if ( irq < 0 ) {
err = irq ;
2012-06-28 11:30:30 +04:00
goto fail_io ;
2011-12-23 06:20:54 +04:00
}
2013-09-21 15:08:38 +04:00
platform_set_drvdata ( pdev , hcd ) ;
2011-12-23 06:20:54 +04:00
2014-05-05 09:02:57 +04:00
err = exynos_ohci_phy_enable ( & pdev - > dev ) ;
if ( err ) {
dev_err ( & pdev - > dev , " Failed to enable USB phy \n " ) ;
goto fail_io ;
}
2011-12-23 06:20:54 +04:00
2019-05-20 12:08:23 +03:00
/*
2019-07-26 11:14:52 +03:00
* Workaround : reset of_node pointer to avoid conflict between legacy
* Exynos OHCI port subnodes and generic USB device bindings
2019-05-20 12:08:23 +03:00
*/
exynos_ohci - > of_node = pdev - > dev . of_node ;
2019-07-26 11:14:52 +03:00
if ( exynos_ohci - > legacy_phy )
pdev - > dev . of_node = NULL ;
2019-05-20 12:08:23 +03:00
2011-12-23 06:20:54 +04:00
err = usb_add_hcd ( hcd , irq , IRQF_SHARED ) ;
if ( err ) {
dev_err ( & pdev - > dev , " Failed to add USB HCD \n " ) ;
2013-01-22 17:00:43 +04:00
goto fail_add_hcd ;
2011-12-23 06:20:54 +04:00
}
2013-11-05 06:46:02 +04:00
device_wakeup_enable ( hcd - > self . controller ) ;
2011-12-23 06:20:54 +04:00
return 0 ;
2013-01-22 17:00:43 +04:00
fail_add_hcd :
2014-05-05 09:03:42 +04:00
exynos_ohci_phy_disable ( & pdev - > dev ) ;
2019-05-20 12:08:23 +03:00
pdev - > dev . of_node = exynos_ohci - > of_node ;
2011-12-23 06:20:54 +04:00
fail_io :
2012-10-03 03:41:37 +04:00
clk_disable_unprepare ( exynos_ohci - > clk ) ;
2011-12-23 06:20:54 +04:00
fail_clk :
usb_put_hcd ( hcd ) ;
return err ;
}
2012-11-19 22:26:20 +04:00
static int exynos_ohci_remove ( struct platform_device * pdev )
2011-12-23 06:20:54 +04:00
{
2013-09-21 15:08:38 +04:00
struct usb_hcd * hcd = platform_get_drvdata ( pdev ) ;
struct exynos_ohci_hcd * exynos_ohci = to_exynos_ohci ( hcd ) ;
2011-12-23 06:20:54 +04:00
2019-05-20 12:08:23 +03:00
pdev - > dev . of_node = exynos_ohci - > of_node ;
2011-12-23 06:20:54 +04:00
usb_remove_hcd ( hcd ) ;
2014-05-05 09:03:42 +04:00
exynos_ohci_phy_disable ( & pdev - > dev ) ;
2011-12-23 06:20:54 +04:00
2012-10-03 03:41:37 +04:00
clk_disable_unprepare ( exynos_ohci - > clk ) ;
2011-12-23 06:20:54 +04:00
usb_put_hcd ( hcd ) ;
return 0 ;
}
static void exynos_ohci_shutdown ( struct platform_device * pdev )
{
2013-09-21 15:08:38 +04:00
struct usb_hcd * hcd = platform_get_drvdata ( pdev ) ;
2011-12-23 06:20:54 +04:00
if ( hcd - > driver - > shutdown )
hcd - > driver - > shutdown ( hcd ) ;
}
# ifdef CONFIG_PM
static int exynos_ohci_suspend ( struct device * dev )
{
2013-09-21 15:08:38 +04:00
struct usb_hcd * hcd = dev_get_drvdata ( dev ) ;
struct exynos_ohci_hcd * exynos_ohci = to_exynos_ohci ( hcd ) ;
2013-11-13 16:10:20 +04:00
bool do_wakeup = device_may_wakeup ( dev ) ;
int rc = ohci_suspend ( hcd , do_wakeup ) ;
2011-12-23 06:20:54 +04:00
2013-11-13 16:10:20 +04:00
if ( rc )
return rc ;
2013-10-14 21:16:58 +04:00
2014-05-05 09:03:42 +04:00
exynos_ohci_phy_disable ( dev ) ;
2012-06-28 11:49:42 +04:00
2012-10-03 03:41:37 +04:00
clk_disable_unprepare ( exynos_ohci - > clk ) ;
2012-06-28 11:49:42 +04:00
2013-11-13 16:10:20 +04:00
return 0 ;
2011-12-23 06:20:54 +04:00
}
static int exynos_ohci_resume ( struct device * dev )
{
2013-09-21 15:08:38 +04:00
struct usb_hcd * hcd = dev_get_drvdata ( dev ) ;
struct exynos_ohci_hcd * exynos_ohci = to_exynos_ohci ( hcd ) ;
2014-05-05 09:02:57 +04:00
int ret ;
2011-12-23 06:20:54 +04:00
2012-10-03 03:41:37 +04:00
clk_prepare_enable ( exynos_ohci - > clk ) ;
2012-06-28 11:49:42 +04:00
2014-05-05 09:02:57 +04:00
ret = exynos_ohci_phy_enable ( dev ) ;
if ( ret ) {
dev_err ( dev , " Failed to enable USB phy \n " ) ;
clk_disable_unprepare ( exynos_ohci - > clk ) ;
return ret ;
}
2011-12-23 06:20:54 +04:00
2012-10-08 17:11:29 +04:00
ohci_resume ( hcd , false ) ;
2011-12-23 06:20:54 +04:00
return 0 ;
}
# else
# define exynos_ohci_suspend NULL
# define exynos_ohci_resume NULL
# endif
2013-09-21 15:08:38 +04:00
static const struct ohci_driver_overrides exynos_overrides __initconst = {
. extra_priv_size = sizeof ( struct exynos_ohci_hcd ) ,
} ;
2011-12-23 06:20:54 +04:00
static const struct dev_pm_ops exynos_ohci_pm_ops = {
. suspend = exynos_ohci_suspend ,
. resume = exynos_ohci_resume ,
} ;
2012-07-16 09:55:36 +04:00
# ifdef CONFIG_OF
static const struct of_device_id exynos_ohci_match [ ] = {
2013-01-24 17:45:29 +04:00
{ . compatible = " samsung,exynos4210-ohci " } ,
2012-07-16 09:55:36 +04:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , exynos_ohci_match ) ;
# endif
2011-12-23 06:20:54 +04:00
static struct platform_driver exynos_ohci_driver = {
. probe = exynos_ohci_probe ,
2012-11-19 22:21:08 +04:00
. remove = exynos_ohci_remove ,
2011-12-23 06:20:54 +04:00
. shutdown = exynos_ohci_shutdown ,
. driver = {
. name = " exynos-ohci " ,
. pm = & exynos_ohci_pm_ops ,
2012-07-16 09:55:36 +04:00
. of_match_table = of_match_ptr ( exynos_ohci_match ) ,
2011-12-23 06:20:54 +04:00
}
} ;
2013-09-21 15:08:38 +04:00
static int __init ohci_exynos_init ( void )
{
if ( usb_disabled ( ) )
return - ENODEV ;
pr_info ( " %s: " DRIVER_DESC " \n " , hcd_name ) ;
ohci_init_driver ( & exynos_ohci_hc_driver , & exynos_overrides ) ;
return platform_driver_register ( & exynos_ohci_driver ) ;
}
module_init ( ohci_exynos_init ) ;
static void __exit ohci_exynos_cleanup ( void )
{
platform_driver_unregister ( & exynos_ohci_driver ) ;
}
module_exit ( ohci_exynos_cleanup ) ;
2011-12-23 06:20:54 +04:00
MODULE_ALIAS ( " platform:exynos-ohci " ) ;
MODULE_AUTHOR ( " Jingoo Han <jg1.han@samsung.com> " ) ;
2013-09-21 15:08:38 +04:00
MODULE_LICENSE ( " GPL v2 " ) ;