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 >
*
* 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-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>
2013-01-22 17:00:43 +04:00
# include <linux/usb/phy.h>
2013-01-22 17:00:40 +04:00
# include <linux/usb/samsung_usb_phy.h>
2013-09-21 15:08:38 +04:00
# include <linux/usb.h>
# include <linux/usb/hcd.h>
# include <linux/usb/otg.h>
# include "ohci.h"
# define DRIVER_DESC "OHCI EXYNOS driver"
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
struct exynos_ohci_hcd {
struct clk * clk ;
2013-01-22 17:00:43 +04:00
struct usb_phy * phy ;
struct usb_otg * otg ;
2011-12-23 06:20:54 +04:00
} ;
2013-09-21 15:08:38 +04:00
static void exynos_ohci_phy_enable ( struct platform_device * pdev )
2013-01-22 17:00:43 +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 ) ;
2013-01-22 17:00:43 +04:00
if ( exynos_ohci - > phy )
usb_phy_init ( exynos_ohci - > phy ) ;
}
2013-09-21 15:08:38 +04:00
static void exynos_ohci_phy_disable ( struct platform_device * pdev )
2013-01-22 17:00:43 +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 ) ;
2013-01-22 17:00:43 +04:00
if ( exynos_ohci - > phy )
usb_phy_shutdown ( exynos_ohci - > phy ) ;
}
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 ;
2013-01-22 17:00:43 +04:00
struct usb_phy * phy ;
2011-12-23 06:20:54 +04:00
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
2013-04-11 15:42:30 +04:00
if ( of_device_is_compatible ( pdev - > dev . of_node ,
" samsung,exynos5440-ohci " ) )
goto skip_phy ;
2013-01-22 17:00:43 +04:00
phy = devm_usb_get_phy ( & pdev - > dev , USB_PHY_TYPE_USB2 ) ;
2013-03-15 13:05:03 +04:00
if ( IS_ERR ( phy ) ) {
2013-10-14 04:18:54 +04:00
usb_put_hcd ( hcd ) ;
dev_warn ( & pdev - > dev , " no platform data or transceiver defined \n " ) ;
return - EPROBE_DEFER ;
2013-01-22 17:00:43 +04:00
} else {
exynos_ohci - > phy = phy ;
exynos_ohci - > otg = phy - > otg ;
}
2013-04-11 15:42:30 +04:00
skip_phy :
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 ) ;
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 ) ;
2012-06-28 11:30:30 +04:00
hcd - > regs = devm_ioremap ( & pdev - > dev , res - > start , hcd - > rsrc_len ) ;
2011-12-23 06:20:54 +04:00
if ( ! hcd - > regs ) {
dev_err ( & pdev - > dev , " Failed to remap I/O memory \n " ) ;
err = - ENOMEM ;
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 11:30:30 +04:00
goto fail_io ;
2011-12-23 06:20:54 +04:00
}
2013-01-22 17:00:43 +04:00
if ( exynos_ohci - > otg )
2013-09-21 15:08:38 +04:00
exynos_ohci - > otg - > set_host ( exynos_ohci - > otg , & hcd - > self ) ;
2013-01-22 17:00:43 +04:00
2013-09-21 15:08:38 +04:00
platform_set_drvdata ( pdev , hcd ) ;
2011-12-23 06:20:54 +04:00
2013-09-21 15:08:38 +04:00
exynos_ohci_phy_enable ( pdev ) ;
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 :
2013-09-21 15:08:38 +04:00
exynos_ohci_phy_disable ( pdev ) ;
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
usb_remove_hcd ( hcd ) ;
2013-01-22 17:00:43 +04:00
if ( exynos_ohci - > otg )
2013-09-21 15:08:38 +04:00
exynos_ohci - > otg - > set_host ( exynos_ohci - > otg , & hcd - > self ) ;
2013-01-22 17:00:43 +04:00
2013-09-21 15:08:38 +04:00
exynos_ohci_phy_disable ( pdev ) ;
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 ) ;
2011-12-23 06:20:54 +04:00
struct ohci_hcd * ohci = hcd_to_ohci ( hcd ) ;
2013-09-21 15:08:38 +04:00
struct platform_device * pdev = to_platform_device ( dev ) ;
2013-11-13 16:10:20 +04:00
bool do_wakeup = device_may_wakeup ( dev ) ;
2011-12-23 06:20:54 +04:00
unsigned long flags ;
2013-11-13 16:10:20 +04:00
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
2013-11-13 16:10:20 +04:00
spin_lock_irqsave ( & ohci - > lock , flags ) ;
2013-10-14 21:16:58 +04:00
2013-01-22 17:00:43 +04:00
if ( exynos_ohci - > otg )
2013-09-21 15:08:38 +04:00
exynos_ohci - > otg - > set_host ( exynos_ohci - > otg , & hcd - > self ) ;
2013-01-22 17:00:43 +04:00
2013-09-21 15:08:38 +04:00
exynos_ohci_phy_disable ( pdev ) ;
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
2011-12-23 06:20:54 +04:00
spin_unlock_irqrestore ( & ohci - > lock , flags ) ;
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 ) ;
struct platform_device * pdev = to_platform_device ( dev ) ;
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
2013-01-22 17:00:43 +04:00
if ( exynos_ohci - > otg )
2013-09-21 15:08:38 +04:00
exynos_ohci - > otg - > set_host ( exynos_ohci - > otg , & hcd - > self ) ;
2013-01-22 17:00:43 +04:00
2013-09-21 15:08:38 +04:00
exynos_ohci_phy_enable ( pdev ) ;
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 " } ,
2013-04-11 15:42:30 +04:00
{ . compatible = " samsung,exynos5440-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 " ,
. owner = THIS_MODULE ,
. 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 " ) ;