2011-04-08 14:08:50 +09:00
/*
2013-10-10 16:42:47 +09:00
* SAMSUNG EXYNOS USB HOST EHCI Controller
2011-04-08 14:08:50 +09:00
*
* Copyright ( C ) 2011 Samsung Electronics Co . Ltd
* Author : Jingoo Han < jg1 . han @ samsung . com >
* Author : Joonyoung Shim < jy0922 . shim @ samsung . com >
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation ; either version 2 of the License , or ( at your
* option ) any later version .
*
*/
# include <linux/clk.h>
2013-04-02 18:24:01 +02:00
# include <linux/dma-mapping.h>
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/module.h>
2012-07-16 11:25:37 +05:30
# include <linux/of.h>
2012-07-17 10:10:50 +05:30
# include <linux/of_gpio.h>
2014-05-05 10:32:28 +05:30
# include <linux/phy/phy.h>
2013-04-02 18:24:01 +02:00
# include <linux/platform_device.h>
2013-01-22 18:30:42 +05:30
# include <linux/usb/phy.h>
2013-01-22 18:30:40 +05:30
# include <linux/usb/samsung_usb_phy.h>
2013-04-02 18:24:01 +02:00
# include <linux/usb.h>
# include <linux/usb/hcd.h>
# include <linux/usb/otg.h>
# include "ehci.h"
2013-10-10 16:42:47 +09:00
# define DRIVER_DESC "EHCI EXYNOS driver"
2011-04-08 14:08:50 +09:00
2012-03-05 10:40:14 +09:00
# define EHCI_INSNREG00(base) (base + 0x90)
# define EHCI_INSNREG00_ENA_INCR16 (0x1 << 25)
# define EHCI_INSNREG00_ENA_INCR8 (0x1 << 24)
# define EHCI_INSNREG00_ENA_INCR4 (0x1 << 23)
# define EHCI_INSNREG00_ENA_INCRX_ALIGN (0x1 << 22)
# define EHCI_INSNREG00_ENABLE_DMA_BURST \
( EHCI_INSNREG00_ENA_INCR16 | EHCI_INSNREG00_ENA_INCR8 | \
EHCI_INSNREG00_ENA_INCR4 | EHCI_INSNREG00_ENA_INCRX_ALIGN )
2013-10-10 16:42:47 +09:00
static const char hcd_name [ ] = " ehci-exynos " ;
static struct hc_driver __read_mostly exynos_ehci_hc_driver ;
2013-04-02 18:24:01 +02:00
2014-05-05 10:32:28 +05:30
# define PHY_NUMBER 3
2013-10-10 16:42:47 +09:00
struct exynos_ehci_hcd {
2011-04-08 14:08:50 +09:00
struct clk * clk ;
2013-01-22 18:30:42 +05:30
struct usb_phy * phy ;
struct usb_otg * otg ;
2014-05-05 10:32:28 +05:30
struct phy * phy_g [ PHY_NUMBER ] ;
2011-04-08 14:08:50 +09:00
} ;
2013-10-10 16:42:47 +09:00
# define to_exynos_ehci(hcd) (struct exynos_ehci_hcd *)(hcd_to_ehci(hcd)->priv)
2013-01-22 18:30:42 +05:30
2014-05-05 10:32:28 +05:30
static int exynos_ehci_get_phy ( struct device * dev ,
struct exynos_ehci_hcd * exynos_ehci )
{
struct device_node * child ;
struct phy * phy ;
int phy_number ;
int ret = 0 ;
exynos_ehci - > phy = devm_usb_get_phy ( dev , USB_PHY_TYPE_USB2 ) ;
if ( IS_ERR ( exynos_ehci - > phy ) ) {
ret = PTR_ERR ( exynos_ehci - > phy ) ;
if ( ret ! = - ENXIO & & ret ! = - ENODEV ) {
dev_err ( dev , " no usb2 phy configured \n " ) ;
return ret ;
}
dev_dbg ( dev , " Failed to get usb2 phy \n " ) ;
} else {
exynos_ehci - > otg = exynos_ehci - > phy - > otg ;
}
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 ;
}
phy = devm_of_phy_get ( dev , child , 0 ) ;
of_node_put ( child ) ;
if ( IS_ERR ( phy ) ) {
ret = PTR_ERR ( phy ) ;
if ( ret ! = - ENOSYS & & ret ! = - ENODEV ) {
dev_err ( dev , " no usb2 phy configured \n " ) ;
return ret ;
}
dev_dbg ( dev , " Failed to get usb2 phy \n " ) ;
}
exynos_ehci - > phy_g [ phy_number ] = phy ;
}
return ret ;
}
static int exynos_ehci_phy_enable ( struct device * dev )
{
struct usb_hcd * hcd = dev_get_drvdata ( dev ) ;
struct exynos_ehci_hcd * exynos_ehci = to_exynos_ehci ( hcd ) ;
int i ;
int ret = 0 ;
if ( ! IS_ERR ( exynos_ehci - > phy ) )
return usb_phy_init ( exynos_ehci - > phy ) ;
for ( i = 0 ; ret = = 0 & & i < PHY_NUMBER ; i + + )
if ( ! IS_ERR ( exynos_ehci - > phy_g [ i ] ) )
ret = phy_power_on ( exynos_ehci - > phy_g [ i ] ) ;
if ( ret )
for ( i - - ; i > = 0 ; i - - )
if ( ! IS_ERR ( exynos_ehci - > phy_g [ i ] ) )
phy_power_off ( exynos_ehci - > phy_g [ i ] ) ;
return ret ;
}
static void exynos_ehci_phy_disable ( struct device * dev )
{
struct usb_hcd * hcd = dev_get_drvdata ( dev ) ;
struct exynos_ehci_hcd * exynos_ehci = to_exynos_ehci ( hcd ) ;
int i ;
if ( ! IS_ERR ( exynos_ehci - > phy ) ) {
usb_phy_shutdown ( exynos_ehci - > phy ) ;
return ;
}
for ( i = 0 ; i < PHY_NUMBER ; i + + )
if ( ! IS_ERR ( exynos_ehci - > phy_g [ i ] ) )
phy_power_off ( exynos_ehci - > phy_g [ i ] ) ;
}
2014-05-05 10:34:25 +05:30
static void exynos_setup_vbus_gpio ( struct device * dev )
2012-07-17 10:10:50 +05:30
{
int err ;
int gpio ;
2013-03-14 20:15:37 -07:00
if ( ! dev - > of_node )
2012-07-17 10:10:50 +05:30
return ;
2013-03-14 20:15:37 -07:00
gpio = of_get_named_gpio ( dev - > of_node , " samsung,vbus-gpio " , 0 ) ;
2012-07-17 10:10:50 +05:30
if ( ! gpio_is_valid ( gpio ) )
return ;
2013-03-14 20:15:37 -07:00
err = devm_gpio_request_one ( dev , gpio , GPIOF_OUT_INIT_HIGH ,
" ehci_vbus_gpio " ) ;
2012-07-17 10:10:50 +05:30
if ( err )
2013-03-14 20:15:37 -07:00
dev_err ( dev , " can't request ehci vbus gpio %d " , gpio ) ;
2012-07-17 10:10:50 +05:30
}
2013-10-10 16:42:47 +09:00
static int exynos_ehci_probe ( struct platform_device * pdev )
2011-04-08 14:08:50 +09:00
{
2013-10-10 16:42:47 +09:00
struct exynos_ehci_hcd * exynos_ehci ;
2011-04-08 14:08:50 +09:00
struct usb_hcd * hcd ;
struct ehci_hcd * ehci ;
struct resource * res ;
int irq ;
int err ;
2012-07-16 11:25:37 +05:30
/*
* 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 12:36:37 +01:00
err = dma_coerce_mask_and_coherent ( & pdev - > dev , DMA_BIT_MASK ( 32 ) ) ;
2013-06-10 16:28:49 +01:00
if ( err )
return err ;
2012-07-16 11:25:37 +05:30
2014-05-05 10:34:25 +05:30
exynos_setup_vbus_gpio ( & pdev - > dev ) ;
2012-07-17 10:10:50 +05:30
2013-10-10 16:42:47 +09:00
hcd = usb_create_hcd ( & exynos_ehci_hc_driver ,
2013-04-02 18:24:01 +02:00
& pdev - > dev , dev_name ( & pdev - > dev ) ) ;
if ( ! hcd ) {
dev_err ( & pdev - > dev , " Unable to create HCD \n " ) ;
2011-04-08 14:08:50 +09:00
return - ENOMEM ;
2013-04-02 18:24:01 +02:00
}
2013-10-10 16:42:47 +09:00
exynos_ehci = to_exynos_ehci ( hcd ) ;
2013-05-27 18:42:12 +09:00
if ( of_device_is_compatible ( pdev - > dev . of_node ,
2013-10-10 16:41:19 +09:00
" samsung,exynos5440-ehci " ) )
2013-05-27 18:42:12 +09:00
goto skip_phy ;
2014-05-05 10:32:28 +05:30
err = exynos_ehci_get_phy ( & pdev - > dev , exynos_ehci ) ;
if ( err )
goto fail_clk ;
2013-01-22 18:30:42 +05:30
2013-05-27 18:42:12 +09:00
skip_phy :
2013-10-10 16:42:47 +09:00
exynos_ehci - > clk = devm_clk_get ( & pdev - > dev , " usbhost " ) ;
2011-04-08 14:08:50 +09:00
2013-10-10 16:42:47 +09:00
if ( IS_ERR ( exynos_ehci - > clk ) ) {
2011-04-08 14:08:50 +09:00
dev_err ( & pdev - > dev , " Failed to get usbhost clock \n " ) ;
2013-10-10 16:42:47 +09:00
err = PTR_ERR ( exynos_ehci - > clk ) ;
2011-04-08 14:08:50 +09:00
goto fail_clk ;
}
2013-10-10 16:42:47 +09:00
err = clk_prepare_enable ( exynos_ehci - > clk ) ;
2011-04-08 14:08:50 +09:00
if ( err )
2012-07-30 16:43:44 +02:00
goto fail_clk ;
2011-04-08 14:08:50 +09:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! res ) {
dev_err ( & pdev - > dev , " Failed to get I/O memory \n " ) ;
err = - ENXIO ;
goto fail_io ;
}
hcd - > rsrc_start = res - > start ;
hcd - > rsrc_len = resource_size ( res ) ;
2014-05-10 17:30:05 +05:30
hcd - > regs = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( hcd - > regs ) ) {
err = PTR_ERR ( hcd - > regs ) ;
2011-04-08 14:08:50 +09:00
goto fail_io ;
}
irq = platform_get_irq ( pdev , 0 ) ;
if ( ! irq ) {
dev_err ( & pdev - > dev , " Failed to get IRQ \n " ) ;
err = - ENODEV ;
2012-06-28 16:29:46 +09:00
goto fail_io ;
2011-04-08 14:08:50 +09:00
}
2013-10-10 16:42:47 +09:00
if ( exynos_ehci - > otg )
exynos_ehci - > otg - > set_host ( exynos_ehci - > otg , & hcd - > self ) ;
2013-01-22 18:30:42 +05:30
2014-05-05 10:32:28 +05:30
err = exynos_ehci_phy_enable ( & pdev - > dev ) ;
if ( err ) {
dev_err ( & pdev - > dev , " Failed to enable USB phy \n " ) ;
goto fail_io ;
}
2011-04-08 14:08:50 +09:00
ehci = hcd_to_ehci ( hcd ) ;
ehci - > caps = hcd - > regs ;
2012-03-05 10:40:14 +09:00
/* DMA burst Enable */
writel ( EHCI_INSNREG00_ENABLE_DMA_BURST , EHCI_INSNREG00 ( hcd - > regs ) ) ;
2011-09-07 16:10:52 +08:00
err = usb_add_hcd ( hcd , irq , IRQF_SHARED ) ;
2011-04-08 14:08:50 +09:00
if ( err ) {
dev_err ( & pdev - > dev , " Failed to add USB HCD \n " ) ;
2013-01-22 18:30:42 +05:30
goto fail_add_hcd ;
2011-04-08 14:08:50 +09:00
}
2013-11-05 10:46:02 +08:00
device_wakeup_enable ( hcd - > self . controller ) ;
2011-04-08 14:08:50 +09:00
2013-04-09 18:42:11 +05:30
platform_set_drvdata ( pdev , hcd ) ;
2011-04-08 14:08:50 +09:00
return 0 ;
2013-01-22 18:30:42 +05:30
fail_add_hcd :
2014-05-05 10:32:28 +05:30
exynos_ehci_phy_disable ( & pdev - > dev ) ;
2011-04-08 14:08:50 +09:00
fail_io :
2013-10-10 16:42:47 +09:00
clk_disable_unprepare ( exynos_ehci - > clk ) ;
2011-04-08 14:08:50 +09:00
fail_clk :
usb_put_hcd ( hcd ) ;
return err ;
}
2013-10-10 16:42:47 +09:00
static int exynos_ehci_remove ( struct platform_device * pdev )
2011-04-08 14:08:50 +09:00
{
2013-04-02 18:24:01 +02:00
struct usb_hcd * hcd = platform_get_drvdata ( pdev ) ;
2013-10-10 16:42:47 +09:00
struct exynos_ehci_hcd * exynos_ehci = to_exynos_ehci ( hcd ) ;
2011-04-08 14:08:50 +09:00
usb_remove_hcd ( hcd ) ;
2013-10-10 16:42:47 +09:00
if ( exynos_ehci - > otg )
exynos_ehci - > otg - > set_host ( exynos_ehci - > otg , & hcd - > self ) ;
2013-01-22 18:30:42 +05:30
2014-05-05 10:32:28 +05:30
exynos_ehci_phy_disable ( & pdev - > dev ) ;
2011-04-08 14:08:50 +09:00
2013-10-10 16:42:47 +09:00
clk_disable_unprepare ( exynos_ehci - > clk ) ;
2011-04-08 14:08:50 +09:00
usb_put_hcd ( hcd ) ;
return 0 ;
}
2011-05-20 20:48:33 +09:00
# ifdef CONFIG_PM
2013-10-10 16:42:47 +09:00
static int exynos_ehci_suspend ( struct device * dev )
2011-05-20 20:48:33 +09:00
{
2013-04-02 18:24:01 +02:00
struct usb_hcd * hcd = dev_get_drvdata ( dev ) ;
2013-10-10 16:42:47 +09:00
struct exynos_ehci_hcd * exynos_ehci = to_exynos_ehci ( hcd ) ;
2013-04-02 18:24:01 +02:00
2012-06-28 11:19:02 -04:00
bool do_wakeup = device_may_wakeup ( dev ) ;
int rc ;
2011-05-20 20:48:33 +09:00
2012-06-28 11:19:02 -04:00
rc = ehci_suspend ( hcd , do_wakeup ) ;
2014-04-10 15:58:01 +05:30
if ( rc )
return rc ;
2011-05-20 20:48:33 +09:00
2013-10-10 16:42:47 +09:00
if ( exynos_ehci - > otg )
exynos_ehci - > otg - > set_host ( exynos_ehci - > otg , & hcd - > self ) ;
2013-01-22 18:30:42 +05:30
2014-05-05 10:32:28 +05:30
exynos_ehci_phy_disable ( dev ) ;
2011-05-20 20:48:33 +09:00
2013-10-10 16:42:47 +09:00
clk_disable_unprepare ( exynos_ehci - > clk ) ;
2012-04-13 11:06:36 +09:00
2011-05-20 20:48:33 +09:00
return rc ;
}
2013-10-10 16:42:47 +09:00
static int exynos_ehci_resume ( struct device * dev )
2011-05-20 20:48:33 +09:00
{
2013-04-02 18:24:01 +02:00
struct usb_hcd * hcd = dev_get_drvdata ( dev ) ;
2013-10-10 16:42:47 +09:00
struct exynos_ehci_hcd * exynos_ehci = to_exynos_ehci ( hcd ) ;
2014-05-05 10:32:28 +05:30
int ret ;
2011-05-20 20:48:33 +09:00
2013-10-10 16:42:47 +09:00
clk_prepare_enable ( exynos_ehci - > clk ) ;
2012-04-13 11:06:36 +09:00
2013-10-10 16:42:47 +09:00
if ( exynos_ehci - > otg )
exynos_ehci - > otg - > set_host ( exynos_ehci - > otg , & hcd - > self ) ;
2013-01-22 18:30:42 +05:30
2014-05-05 10:32:28 +05:30
ret = exynos_ehci_phy_enable ( dev ) ;
if ( ret ) {
dev_err ( dev , " Failed to enable USB phy \n " ) ;
clk_disable_unprepare ( exynos_ehci - > clk ) ;
return ret ;
}
2011-05-20 20:48:33 +09:00
2012-03-05 10:40:14 +09:00
/* DMA burst Enable */
writel ( EHCI_INSNREG00_ENABLE_DMA_BURST , EHCI_INSNREG00 ( hcd - > regs ) ) ;
2012-06-28 11:19:02 -04:00
ehci_resume ( hcd , false ) ;
2011-05-20 20:48:33 +09:00
return 0 ;
}
# else
2013-10-10 16:42:47 +09:00
# define exynos_ehci_suspend NULL
# define exynos_ehci_resume NULL
2011-05-20 20:48:33 +09:00
# endif
2013-10-10 16:42:47 +09:00
static const struct dev_pm_ops exynos_ehci_pm_ops = {
. suspend = exynos_ehci_suspend ,
. resume = exynos_ehci_resume ,
2011-05-20 20:48:33 +09:00
} ;
2012-07-16 11:25:37 +05:30
# ifdef CONFIG_OF
static const struct of_device_id exynos_ehci_match [ ] = {
2013-01-24 19:15:29 +05:30
{ . compatible = " samsung,exynos4210-ehci " } ,
2013-05-27 18:42:12 +09:00
{ . compatible = " samsung,exynos5440-ehci " } ,
2012-07-16 11:25:37 +05:30
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , exynos_ehci_match ) ;
# endif
2013-10-10 16:42:47 +09:00
static struct platform_driver exynos_ehci_driver = {
. probe = exynos_ehci_probe ,
. remove = exynos_ehci_remove ,
2013-07-22 15:04:50 +03:00
. shutdown = usb_hcd_platform_shutdown ,
2011-04-08 14:08:50 +09:00
. driver = {
2013-10-10 16:42:47 +09:00
. name = " exynos-ehci " ,
2011-04-08 14:08:50 +09:00
. owner = THIS_MODULE ,
2013-10-10 16:42:47 +09:00
. pm = & exynos_ehci_pm_ops ,
2012-07-16 11:25:37 +05:30
. of_match_table = of_match_ptr ( exynos_ehci_match ) ,
2011-04-08 14:08:50 +09:00
}
} ;
2013-10-10 16:42:47 +09:00
static const struct ehci_driver_overrides exynos_overrides __initdata = {
. extra_priv_size = sizeof ( struct exynos_ehci_hcd ) ,
2013-04-02 18:24:01 +02:00
} ;
2013-10-10 16:42:47 +09:00
static int __init ehci_exynos_init ( void )
2013-04-02 18:24:01 +02:00
{
if ( usb_disabled ( ) )
return - ENODEV ;
pr_info ( " %s: " DRIVER_DESC " \n " , hcd_name ) ;
2013-10-10 16:42:47 +09:00
ehci_init_driver ( & exynos_ehci_hc_driver , & exynos_overrides ) ;
return platform_driver_register ( & exynos_ehci_driver ) ;
2013-04-02 18:24:01 +02:00
}
2013-10-10 16:42:47 +09:00
module_init ( ehci_exynos_init ) ;
2013-04-02 18:24:01 +02:00
2013-10-10 16:42:47 +09:00
static void __exit ehci_exynos_cleanup ( void )
2013-04-02 18:24:01 +02:00
{
2013-10-10 16:42:47 +09:00
platform_driver_unregister ( & exynos_ehci_driver ) ;
2013-04-02 18:24:01 +02:00
}
2013-10-10 16:42:47 +09:00
module_exit ( ehci_exynos_cleanup ) ;
2011-04-08 14:08:50 +09:00
2013-04-02 18:24:01 +02:00
MODULE_DESCRIPTION ( DRIVER_DESC ) ;
2013-10-10 16:42:47 +09:00
MODULE_ALIAS ( " platform:exynos-ehci " ) ;
2013-04-02 18:24:01 +02:00
MODULE_AUTHOR ( " Jingoo Han " ) ;
MODULE_AUTHOR ( " Joonyoung Shim " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;