2011-04-08 14:08:50 +09:00
/*
* SAMSUNG S5P USB HOST EHCI Controller
*
* 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>
2012-07-16 11:25:37 +05:30
# include <linux/of.h>
2011-04-08 14:08:50 +09:00
# include <linux/platform_device.h>
2012-07-17 10:10:50 +05:30
# include <linux/of_gpio.h>
2012-08-24 15:22:12 +02:00
# include <linux/platform_data/usb-ehci-s5p.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>
2011-04-08 14:08:50 +09:00
# include <plat/usb-phy.h>
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 )
2011-04-08 14:08:50 +09:00
struct s5p_ehci_hcd {
struct device * dev ;
struct usb_hcd * hcd ;
struct clk * clk ;
2013-01-22 18:30:42 +05:30
struct usb_phy * phy ;
struct usb_otg * otg ;
struct s5p_ehci_platdata * pdata ;
2011-04-08 14:08:50 +09:00
} ;
static const struct hc_driver s5p_ehci_hc_driver = {
. description = hcd_name ,
. product_desc = " S5P EHCI Host Controller " ,
. hcd_priv_size = sizeof ( struct ehci_hcd ) ,
. irq = ehci_irq ,
. flags = HCD_MEMORY | HCD_USB2 ,
2012-07-09 15:55:14 -04:00
. reset = ehci_setup ,
2011-04-08 14:08:50 +09:00
. start = ehci_run ,
. stop = ehci_stop ,
. shutdown = ehci_shutdown ,
. get_frame_number = ehci_get_frame ,
. urb_enqueue = ehci_urb_enqueue ,
. urb_dequeue = ehci_urb_dequeue ,
. endpoint_disable = ehci_endpoint_disable ,
. endpoint_reset = ehci_endpoint_reset ,
. hub_status_data = ehci_hub_status_data ,
. hub_control = ehci_hub_control ,
. bus_suspend = ehci_bus_suspend ,
. bus_resume = ehci_bus_resume ,
. relinquish_port = ehci_relinquish_port ,
. port_handed_over = ehci_port_handed_over ,
. clear_tt_buffer_complete = ehci_clear_tt_buffer_complete ,
} ;
2013-01-22 18:30:42 +05:30
static void s5p_ehci_phy_enable ( struct s5p_ehci_hcd * s5p_ehci )
{
struct platform_device * pdev = to_platform_device ( s5p_ehci - > dev ) ;
if ( s5p_ehci - > phy )
usb_phy_init ( s5p_ehci - > phy ) ;
else if ( s5p_ehci - > pdata - > phy_init )
s5p_ehci - > pdata - > phy_init ( pdev , USB_PHY_TYPE_HOST ) ;
}
static void s5p_ehci_phy_disable ( struct s5p_ehci_hcd * s5p_ehci )
{
struct platform_device * pdev = to_platform_device ( s5p_ehci - > dev ) ;
if ( s5p_ehci - > phy )
usb_phy_shutdown ( s5p_ehci - > phy ) ;
else if ( s5p_ehci - > pdata - > phy_exit )
s5p_ehci - > pdata - > phy_exit ( pdev , USB_PHY_TYPE_HOST ) ;
}
2012-07-17 10:10:50 +05:30
static void s5p_setup_vbus_gpio ( struct platform_device * pdev )
{
2013-03-14 20:15:37 -07:00
struct device * dev = & pdev - > 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
}
2012-07-16 11:25:37 +05:30
static u64 ehci_s5p_dma_mask = DMA_BIT_MASK ( 32 ) ;
2012-11-19 13:21:48 -05:00
static int s5p_ehci_probe ( struct platform_device * pdev )
2011-04-08 14:08:50 +09:00
{
2013-01-22 18:30:42 +05:30
struct s5p_ehci_platdata * pdata = pdev - > dev . platform_data ;
2011-04-08 14:08:50 +09:00
struct s5p_ehci_hcd * s5p_ehci ;
struct usb_hcd * hcd ;
struct ehci_hcd * ehci ;
struct resource * res ;
2013-01-22 18:30:42 +05:30
struct usb_phy * phy ;
2011-04-08 14:08:50 +09:00
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 .
*/
if ( ! pdev - > dev . dma_mask )
pdev - > dev . dma_mask = & ehci_s5p_dma_mask ;
if ( ! pdev - > dev . coherent_dma_mask )
pdev - > dev . coherent_dma_mask = DMA_BIT_MASK ( 32 ) ;
2012-07-17 10:10:50 +05:30
s5p_setup_vbus_gpio ( pdev ) ;
2012-06-28 16:29:46 +09:00
s5p_ehci = devm_kzalloc ( & pdev - > dev , sizeof ( struct s5p_ehci_hcd ) ,
GFP_KERNEL ) ;
2011-04-08 14:08:50 +09:00
if ( ! s5p_ehci )
return - ENOMEM ;
2013-01-22 18:30:42 +05:30
phy = devm_usb_get_phy ( & pdev - > dev , USB_PHY_TYPE_USB2 ) ;
2013-03-15 11:04:15 +02:00
if ( IS_ERR ( phy ) ) {
2013-01-22 18:30:42 +05:30
/* Fallback to pdata */
if ( ! pdata ) {
dev_warn ( & pdev - > dev , " no platform data or transceiver defined \n " ) ;
return - EPROBE_DEFER ;
} else {
s5p_ehci - > pdata = pdata ;
}
} else {
s5p_ehci - > phy = phy ;
s5p_ehci - > otg = phy - > otg ;
}
2011-04-08 14:08:50 +09:00
s5p_ehci - > dev = & pdev - > dev ;
hcd = usb_create_hcd ( & s5p_ehci_hc_driver , & pdev - > dev ,
dev_name ( & pdev - > dev ) ) ;
if ( ! hcd ) {
dev_err ( & pdev - > dev , " Unable to create HCD \n " ) ;
2012-06-28 16:29:46 +09:00
return - ENOMEM ;
2011-04-08 14:08:50 +09:00
}
2011-08-18 14:02:45 +09:00
s5p_ehci - > hcd = hcd ;
2012-07-30 16:43:44 +02:00
s5p_ehci - > clk = devm_clk_get ( & pdev - > dev , " usbhost " ) ;
2011-04-08 14:08:50 +09:00
if ( IS_ERR ( s5p_ehci - > clk ) ) {
dev_err ( & pdev - > dev , " Failed to get usbhost clock \n " ) ;
err = PTR_ERR ( s5p_ehci - > clk ) ;
goto fail_clk ;
}
2012-10-03 08:40:42 +09:00
err = clk_prepare_enable ( s5p_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 ) ;
2012-06-28 16:29:46 +09:00
hcd - > regs = devm_ioremap ( & pdev - > dev , res - > start , hcd - > rsrc_len ) ;
2011-04-08 14:08:50 +09: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 16:29:46 +09:00
goto fail_io ;
2011-04-08 14:08:50 +09:00
}
2013-01-22 18:30:42 +05:30
if ( s5p_ehci - > otg )
s5p_ehci - > otg - > set_host ( s5p_ehci - > otg , & s5p_ehci - > hcd - > self ) ;
s5p_ehci_phy_enable ( s5p_ehci ) ;
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
}
platform_set_drvdata ( pdev , s5p_ehci ) ;
return 0 ;
2013-01-22 18:30:42 +05:30
fail_add_hcd :
s5p_ehci_phy_disable ( s5p_ehci ) ;
2011-04-08 14:08:50 +09:00
fail_io :
2012-10-03 08:40:42 +09:00
clk_disable_unprepare ( s5p_ehci - > clk ) ;
2011-04-08 14:08:50 +09:00
fail_clk :
usb_put_hcd ( hcd ) ;
return err ;
}
2012-11-19 13:26:20 -05:00
static int s5p_ehci_remove ( struct platform_device * pdev )
2011-04-08 14:08:50 +09:00
{
struct s5p_ehci_hcd * s5p_ehci = platform_get_drvdata ( pdev ) ;
struct usb_hcd * hcd = s5p_ehci - > hcd ;
usb_remove_hcd ( hcd ) ;
2013-01-22 18:30:42 +05:30
if ( s5p_ehci - > otg )
s5p_ehci - > otg - > set_host ( s5p_ehci - > otg , & s5p_ehci - > hcd - > self ) ;
s5p_ehci_phy_disable ( s5p_ehci ) ;
2011-04-08 14:08:50 +09:00
2012-10-03 08:40:42 +09:00
clk_disable_unprepare ( s5p_ehci - > clk ) ;
2011-04-08 14:08:50 +09:00
usb_put_hcd ( hcd ) ;
return 0 ;
}
static void s5p_ehci_shutdown ( struct platform_device * pdev )
{
struct s5p_ehci_hcd * s5p_ehci = platform_get_drvdata ( pdev ) ;
struct usb_hcd * hcd = s5p_ehci - > hcd ;
if ( hcd - > driver - > shutdown )
hcd - > driver - > shutdown ( hcd ) ;
}
2011-05-20 20:48:33 +09:00
# ifdef CONFIG_PM
static int s5p_ehci_suspend ( struct device * dev )
{
struct s5p_ehci_hcd * s5p_ehci = dev_get_drvdata ( dev ) ;
struct usb_hcd * hcd = s5p_ehci - > hcd ;
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 ) ;
2011-05-20 20:48:33 +09:00
2013-01-22 18:30:42 +05:30
if ( s5p_ehci - > otg )
s5p_ehci - > otg - > set_host ( s5p_ehci - > otg , & s5p_ehci - > hcd - > self ) ;
s5p_ehci_phy_disable ( s5p_ehci ) ;
2011-05-20 20:48:33 +09:00
2012-10-03 08:40:42 +09:00
clk_disable_unprepare ( s5p_ehci - > clk ) ;
2012-04-13 11:06:36 +09:00
2011-05-20 20:48:33 +09:00
return rc ;
}
static int s5p_ehci_resume ( struct device * dev )
{
struct s5p_ehci_hcd * s5p_ehci = dev_get_drvdata ( dev ) ;
struct usb_hcd * hcd = s5p_ehci - > hcd ;
2012-10-03 08:40:42 +09:00
clk_prepare_enable ( s5p_ehci - > clk ) ;
2012-04-13 11:06:36 +09:00
2013-01-22 18:30:42 +05:30
if ( s5p_ehci - > otg )
s5p_ehci - > otg - > set_host ( s5p_ehci - > otg , & s5p_ehci - > hcd - > self ) ;
s5p_ehci_phy_enable ( s5p_ehci ) ;
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
# define s5p_ehci_suspend NULL
# define s5p_ehci_resume NULL
# endif
static const struct dev_pm_ops s5p_ehci_pm_ops = {
. suspend = s5p_ehci_suspend ,
. resume = s5p_ehci_resume ,
} ;
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 " } ,
2012-07-16 11:25:37 +05:30
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , exynos_ehci_match ) ;
# endif
2011-04-08 14:08:50 +09:00
static struct platform_driver s5p_ehci_driver = {
. probe = s5p_ehci_probe ,
2012-11-19 13:21:08 -05:00
. remove = s5p_ehci_remove ,
2011-04-08 14:08:50 +09:00
. shutdown = s5p_ehci_shutdown ,
. driver = {
. name = " s5p-ehci " ,
. owner = THIS_MODULE ,
2011-05-20 20:48:33 +09:00
. pm = & s5p_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
}
} ;
MODULE_ALIAS ( " platform:s5p-ehci " ) ;