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>
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>
2012-08-24 17:22:12 +04:00
# include <linux/platform_data/usb-exynos.h>
2011-12-23 06:20:54 +04:00
# include <plat/usb-phy.h>
struct exynos_ohci_hcd {
struct device * dev ;
struct usb_hcd * hcd ;
struct clk * clk ;
} ;
2012-11-01 22:05:28 +04:00
static int ohci_exynos_reset ( struct usb_hcd * hcd )
{
return ohci_init ( hcd_to_ohci ( hcd ) ) ;
}
2011-12-23 06:20:54 +04:00
static int ohci_exynos_start ( struct usb_hcd * hcd )
{
struct ohci_hcd * ohci = hcd_to_ohci ( hcd ) ;
int ret ;
ohci_dbg ( ohci , " ohci_exynos_start, ohci:%p " , ohci ) ;
ret = ohci_run ( ohci ) ;
if ( ret < 0 ) {
2012-04-27 22:24:41 +04:00
dev_err ( hcd - > self . controller , " can't start %s \n " ,
hcd - > self . bus_name ) ;
2011-12-23 06:20:54 +04:00
ohci_stop ( hcd ) ;
return ret ;
}
return 0 ;
}
static const struct hc_driver exynos_ohci_hc_driver = {
. description = hcd_name ,
. product_desc = " EXYNOS OHCI Host Controller " ,
. hcd_priv_size = sizeof ( struct ohci_hcd ) ,
. irq = ohci_irq ,
. flags = HCD_MEMORY | HCD_USB11 ,
2012-11-01 22:05:28 +04:00
. reset = ohci_exynos_reset ,
2011-12-23 06:20:54 +04:00
. start = ohci_exynos_start ,
. stop = ohci_stop ,
. shutdown = ohci_shutdown ,
. get_frame_number = ohci_get_frame ,
. urb_enqueue = ohci_urb_enqueue ,
. urb_dequeue = ohci_urb_dequeue ,
. endpoint_disable = ohci_endpoint_disable ,
. hub_status_data = ohci_hub_status_data ,
. hub_control = ohci_hub_control ,
# ifdef CONFIG_PM
. bus_suspend = ohci_bus_suspend ,
. bus_resume = ohci_bus_resume ,
# endif
. start_port_reset = ohci_start_port_reset ,
} ;
2012-07-16 09:55:36 +04:00
static u64 ohci_exynos_dma_mask = DMA_BIT_MASK ( 32 ) ;
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 exynos4_ohci_platdata * pdata ;
struct exynos_ohci_hcd * exynos_ohci ;
struct usb_hcd * hcd ;
struct ohci_hcd * ohci ;
struct resource * res ;
int irq ;
int err ;
pdata = pdev - > dev . platform_data ;
if ( ! pdata ) {
dev_err ( & pdev - > dev , " No platform data defined \n " ) ;
return - EINVAL ;
}
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 .
*/
if ( ! pdev - > dev . dma_mask )
pdev - > dev . dma_mask = & ohci_exynos_dma_mask ;
if ( ! pdev - > dev . coherent_dma_mask )
pdev - > dev . coherent_dma_mask = DMA_BIT_MASK ( 32 ) ;
2012-06-28 11:30:30 +04:00
exynos_ohci = devm_kzalloc ( & pdev - > dev , sizeof ( struct exynos_ohci_hcd ) ,
GFP_KERNEL ) ;
2011-12-23 06:20:54 +04:00
if ( ! exynos_ohci )
return - ENOMEM ;
exynos_ohci - > dev = & pdev - > dev ;
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 " ) ;
2012-06-28 11:30:30 +04:00
return - ENOMEM ;
2011-12-23 06:20:54 +04:00
}
exynos_ohci - > hcd = hcd ;
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
}
if ( pdata - > phy_init )
pdata - > phy_init ( pdev , S5P_USB_PHY_HOST ) ;
ohci = hcd_to_ohci ( hcd ) ;
ohci_hcd_init ( ohci ) ;
err = usb_add_hcd ( hcd , irq , IRQF_SHARED ) ;
if ( err ) {
dev_err ( & pdev - > dev , " Failed to add USB HCD \n " ) ;
2012-06-28 11:30:30 +04:00
goto fail_io ;
2011-12-23 06:20:54 +04:00
}
platform_set_drvdata ( pdev , exynos_ohci ) ;
return 0 ;
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
{
struct exynos4_ohci_platdata * pdata = pdev - > dev . platform_data ;
struct exynos_ohci_hcd * exynos_ohci = platform_get_drvdata ( pdev ) ;
struct usb_hcd * hcd = exynos_ohci - > hcd ;
usb_remove_hcd ( hcd ) ;
if ( pdata & & pdata - > phy_exit )
pdata - > phy_exit ( pdev , S5P_USB_PHY_HOST ) ;
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 )
{
struct exynos_ohci_hcd * exynos_ohci = platform_get_drvdata ( pdev ) ;
struct usb_hcd * hcd = exynos_ohci - > hcd ;
if ( hcd - > driver - > shutdown )
hcd - > driver - > shutdown ( hcd ) ;
}
# ifdef CONFIG_PM
static int exynos_ohci_suspend ( struct device * dev )
{
struct exynos_ohci_hcd * exynos_ohci = dev_get_drvdata ( dev ) ;
struct usb_hcd * hcd = exynos_ohci - > hcd ;
struct ohci_hcd * ohci = hcd_to_ohci ( hcd ) ;
struct platform_device * pdev = to_platform_device ( dev ) ;
struct exynos4_ohci_platdata * pdata = pdev - > dev . platform_data ;
unsigned long flags ;
int rc = 0 ;
/*
* Root hub was already suspended . Disable irq emission and
* mark HW unaccessible , bail out if RH has been resumed . Use
* the spinlock to properly synchronize with possible pending
* RH suspend or resume activity .
*/
spin_lock_irqsave ( & ohci - > lock , flags ) ;
2012-02-23 12:26:33 +04:00
if ( ohci - > rh_state ! = OHCI_RH_SUSPENDED & &
ohci - > rh_state ! = OHCI_RH_HALTED ) {
2011-12-23 06:20:54 +04:00
rc = - EINVAL ;
goto fail ;
}
clear_bit ( HCD_FLAG_HW_ACCESSIBLE , & hcd - > flags ) ;
if ( pdata & & pdata - > phy_exit )
pdata - > phy_exit ( pdev , S5P_USB_PHY_HOST ) ;
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
fail :
spin_unlock_irqrestore ( & ohci - > lock , flags ) ;
return rc ;
}
static int exynos_ohci_resume ( struct device * dev )
{
struct exynos_ohci_hcd * exynos_ohci = dev_get_drvdata ( dev ) ;
struct usb_hcd * hcd = exynos_ohci - > hcd ;
struct platform_device * pdev = to_platform_device ( dev ) ;
struct exynos4_ohci_platdata * pdata = pdev - > dev . platform_data ;
2012-10-03 03:41:37 +04:00
clk_prepare_enable ( exynos_ohci - > clk ) ;
2012-06-28 11:49:42 +04:00
2011-12-23 06:20:54 +04:00
if ( pdata & & pdata - > phy_init )
pdata - > phy_init ( pdev , S5P_USB_PHY_HOST ) ;
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
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 [ ] = {
{ . compatible = " samsung,exynos-ohci " } ,
{ } ,
} ;
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
}
} ;
MODULE_ALIAS ( " platform:exynos-ohci " ) ;
MODULE_AUTHOR ( " Jingoo Han <jg1.han@samsung.com> " ) ;