2011-03-09 16:28:56 -08:00
/*
* EHCI - compliant USB host controller driver for NVIDIA Tegra SoCs
*
* Copyright ( C ) 2010 Google , Inc .
2013-01-17 20:15:37 +00:00
* Copyright ( C ) 2009 - 2013 NVIDIA Corporation
2011-03-09 16:28:56 -08:00
*
* 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 .
*
* This program is distributed in the hope that it will be useful , but WITHOUT
* ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE . See the GNU General Public License for
* more details .
*
*/
# include <linux/clk.h>
2013-06-13 11:24:12 -06:00
# include <linux/dma-mapping.h>
2012-06-26 17:40:32 +05:30
# include <linux/err.h>
2011-11-04 09:12:40 +00:00
# include <linux/gpio.h>
2013-06-13 11:24:12 -06:00
# include <linux/io.h>
# include <linux/irq.h>
# include <linux/module.h>
2011-11-04 09:12:40 +00:00
# include <linux/of.h>
2013-08-12 16:06:54 +03:00
# include <linux/of_device.h>
2011-11-04 09:12:40 +00:00
# include <linux/of_gpio.h>
2013-06-13 11:24:12 -06:00
# include <linux/platform_device.h>
2012-05-01 11:28:49 -04:00
# include <linux/pm_runtime.h>
2013-11-06 16:53:58 -07:00
# include <linux/reset.h>
2013-06-13 11:24:12 -06:00
# include <linux/slab.h>
2013-01-17 20:15:37 +00:00
# include <linux/usb/ehci_def.h>
2012-09-05 18:50:23 +05:30
# include <linux/usb/tegra_usb_phy.h>
2013-06-13 11:24:12 -06:00
# include <linux/usb.h>
# include <linux/usb/hcd.h>
# include <linux/usb/otg.h>
# include "ehci.h"
2012-10-02 16:49:25 -06:00
2013-06-13 11:24:12 -06:00
# define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E)
2011-03-09 16:28:57 -08:00
# define TEGRA_USB_DMA_ALIGN 32
2013-06-13 11:24:12 -06:00
# define DRIVER_DESC "Tegra EHCI driver"
# define DRV_NAME "tegra-ehci"
static struct hc_driver __read_mostly tegra_ehci_hc_driver ;
2013-08-12 16:06:54 +03:00
struct tegra_ehci_soc_config {
bool has_hostpc ;
} ;
2011-03-09 16:28:56 -08:00
struct tegra_ehci_hcd {
struct tegra_usb_phy * phy ;
struct clk * clk ;
2013-11-06 16:53:58 -07:00
struct reset_control * rst ;
2011-03-09 16:28:56 -08:00
int port_resuming ;
2012-12-13 20:59:08 +00:00
bool needs_double_reset ;
2011-03-09 16:28:56 -08:00
enum tegra_usb_phy_port_speed port_speed ;
} ;
2011-04-17 11:58:25 +03:00
static int tegra_ehci_internal_port_reset (
struct ehci_hcd * ehci ,
u32 __iomem * portsc_reg
)
{
u32 temp ;
unsigned long flags ;
int retval = 0 ;
int i , tries ;
u32 saved_usbintr ;
spin_lock_irqsave ( & ehci - > lock , flags ) ;
saved_usbintr = ehci_readl ( ehci , & ehci - > regs - > intr_enable ) ;
/* disable USB interrupt */
ehci_writel ( ehci , 0 , & ehci - > regs - > intr_enable ) ;
spin_unlock_irqrestore ( & ehci - > lock , flags ) ;
/*
* Here we have to do Port Reset at most twice for
* Port Enable bit to be set .
*/
for ( i = 0 ; i < 2 ; i + + ) {
temp = ehci_readl ( ehci , portsc_reg ) ;
temp | = PORT_RESET ;
ehci_writel ( ehci , temp , portsc_reg ) ;
mdelay ( 10 ) ;
temp & = ~ PORT_RESET ;
ehci_writel ( ehci , temp , portsc_reg ) ;
mdelay ( 1 ) ;
tries = 100 ;
do {
mdelay ( 1 ) ;
/*
* Up to this point , Port Enable bit is
* expected to be set after 2 ms waiting .
* USB1 usually takes extra 45 ms , for safety ,
* we take 100 ms as timeout .
*/
temp = ehci_readl ( ehci , portsc_reg ) ;
} while ( ! ( temp & PORT_PE ) & & tries - - ) ;
if ( temp & PORT_PE )
break ;
}
if ( i = = 2 )
retval = - ETIMEDOUT ;
/*
* Clear Connect Status Change bit if it ' s set .
* We can ' t clear PORT_PEC . It will also cause PORT_PE to be cleared .
*/
if ( temp & PORT_CSC )
ehci_writel ( ehci , PORT_CSC , portsc_reg ) ;
/*
* Write to clear any interrupt status bits that might be set
* during port reset .
*/
temp = ehci_readl ( ehci , & ehci - > regs - > status ) ;
ehci_writel ( ehci , temp , & ehci - > regs - > status ) ;
/* restore original interrupt enable bits */
ehci_writel ( ehci , saved_usbintr , & ehci - > regs - > intr_enable ) ;
return retval ;
}
2011-03-09 16:28:56 -08:00
static int tegra_ehci_hub_control (
struct usb_hcd * hcd ,
u16 typeReq ,
u16 wValue ,
u16 wIndex ,
char * buf ,
u16 wLength
)
{
2013-06-13 11:24:13 -06:00
struct ehci_hcd * ehci = hcd_to_ehci ( hcd ) ;
struct tegra_ehci_hcd * tegra = ( struct tegra_ehci_hcd * ) ehci - > priv ;
2011-03-09 16:28:56 -08:00
u32 __iomem * status_reg ;
u32 temp ;
unsigned long flags ;
int retval = 0 ;
status_reg = & ehci - > regs - > port_status [ ( wIndex & 0xff ) - 1 ] ;
spin_lock_irqsave ( & ehci - > lock , flags ) ;
2012-04-18 15:32:46 -06:00
if ( typeReq = = GetPortStatus ) {
2011-03-09 16:28:56 -08:00
temp = ehci_readl ( ehci , status_reg ) ;
if ( tegra - > port_resuming & & ! ( temp & PORT_SUSPEND ) ) {
/* Resume completed, re-enable disconnect detection */
tegra - > port_resuming = 0 ;
2013-01-24 15:57:03 +05:30
tegra_usb_phy_postresume ( hcd - > phy ) ;
2011-03-09 16:28:56 -08:00
}
}
else if ( typeReq = = SetPortFeature & & wValue = = USB_PORT_FEAT_SUSPEND ) {
temp = ehci_readl ( ehci , status_reg ) ;
if ( ( temp & PORT_PE ) = = 0 | | ( temp & PORT_RESET ) ! = 0 ) {
retval = - EPIPE ;
goto done ;
}
2012-04-25 12:31:10 -06:00
temp & = ~ ( PORT_RWC_BITS | PORT_WKCONN_E ) ;
2011-03-09 16:28:56 -08:00
temp | = PORT_WKDISC_E | PORT_WKOC_E ;
ehci_writel ( ehci , temp | PORT_SUSPEND , status_reg ) ;
/*
* If a transaction is in progress , there may be a delay in
* suspending the port . Poll until the port is suspended .
*/
2013-06-13 11:24:09 -06:00
if ( ehci_handshake ( ehci , status_reg , PORT_SUSPEND ,
2011-03-09 16:28:56 -08:00
PORT_SUSPEND , 5000 ) )
pr_err ( " %s: timeout waiting for SUSPEND \n " , __func__ ) ;
set_bit ( ( wIndex & 0xff ) - 1 , & ehci - > suspended_ports ) ;
goto done ;
}
2011-04-17 11:58:25 +03:00
/* For USB1 port we need to issue Port Reset twice internally */
2012-12-13 20:59:08 +00:00
if ( tegra - > needs_double_reset & &
2011-04-17 11:58:25 +03:00
( typeReq = = SetPortFeature & & wValue = = USB_PORT_FEAT_RESET ) ) {
spin_unlock_irqrestore ( & ehci - > lock , flags ) ;
return tegra_ehci_internal_port_reset ( ehci , status_reg ) ;
}
2011-03-09 16:28:56 -08:00
/*
* Tegra host controller will time the resume operation to clear the bit
* when the port control state switches to HS or FS Idle . This behavior
* is different from EHCI where the host controller driver is required
* to set this bit to a zero after the resume duration is timed in the
* driver .
*/
else if ( typeReq = = ClearPortFeature & &
wValue = = USB_PORT_FEAT_SUSPEND ) {
temp = ehci_readl ( ehci , status_reg ) ;
if ( ( temp & PORT_RESET ) | | ! ( temp & PORT_PE ) ) {
retval = - EPIPE ;
goto done ;
}
if ( ! ( temp & PORT_SUSPEND ) )
goto done ;
/* Disable disconnect detection during port resume */
2013-01-24 15:57:03 +05:30
tegra_usb_phy_preresume ( hcd - > phy ) ;
2011-03-09 16:28:56 -08:00
ehci - > reset_done [ wIndex - 1 ] = jiffies + msecs_to_jiffies ( 25 ) ;
temp & = ~ ( PORT_RWC_BITS | PORT_WAKE_BITS ) ;
/* start resume signalling */
ehci_writel ( ehci , temp | PORT_RESUME , status_reg ) ;
2012-04-03 15:24:30 -04:00
set_bit ( wIndex - 1 , & ehci - > resuming_ports ) ;
2011-03-09 16:28:56 -08:00
spin_unlock_irqrestore ( & ehci - > lock , flags ) ;
msleep ( 20 ) ;
spin_lock_irqsave ( & ehci - > lock , flags ) ;
/* Poll until the controller clears RESUME and SUSPEND */
2013-06-13 11:24:09 -06:00
if ( ehci_handshake ( ehci , status_reg , PORT_RESUME , 0 , 2000 ) )
2011-03-09 16:28:56 -08:00
pr_err ( " %s: timeout waiting for RESUME \n " , __func__ ) ;
2013-06-13 11:24:09 -06:00
if ( ehci_handshake ( ehci , status_reg , PORT_SUSPEND , 0 , 2000 ) )
2011-03-09 16:28:56 -08:00
pr_err ( " %s: timeout waiting for SUSPEND \n " , __func__ ) ;
ehci - > reset_done [ wIndex - 1 ] = 0 ;
2012-04-03 15:24:30 -04:00
clear_bit ( wIndex - 1 , & ehci - > resuming_ports ) ;
2011-03-09 16:28:56 -08:00
tegra - > port_resuming = 1 ;
goto done ;
}
spin_unlock_irqrestore ( & ehci - > lock , flags ) ;
/* Handle the hub control events here */
2014-04-16 18:00:10 +02:00
return ehci_hub_control ( hcd , typeReq , wValue , wIndex , buf , wLength ) ;
2013-06-13 11:24:12 -06:00
2011-03-09 16:28:56 -08:00
done :
spin_unlock_irqrestore ( & ehci - > lock , flags ) ;
return retval ;
}
2012-04-05 11:25:30 +05:30
struct dma_aligned_buffer {
2011-03-09 16:28:57 -08:00
void * kmalloc_ptr ;
void * old_xfer_buffer ;
u8 data [ 0 ] ;
} ;
2012-04-05 11:25:30 +05:30
static void free_dma_aligned_buffer ( struct urb * urb )
2011-03-09 16:28:57 -08:00
{
2012-04-05 11:25:30 +05:30
struct dma_aligned_buffer * temp ;
2011-03-09 16:28:57 -08:00
if ( ! ( urb - > transfer_flags & URB_ALIGNED_TEMP_BUFFER ) )
return ;
2012-04-05 11:25:30 +05:30
temp = container_of ( urb - > transfer_buffer ,
struct dma_aligned_buffer , data ) ;
2011-03-09 16:28:57 -08:00
2012-04-05 11:25:30 +05:30
if ( usb_urb_dir_in ( urb ) )
2011-03-09 16:28:57 -08:00
memcpy ( temp - > old_xfer_buffer , temp - > data ,
urb - > transfer_buffer_length ) ;
urb - > transfer_buffer = temp - > old_xfer_buffer ;
kfree ( temp - > kmalloc_ptr ) ;
urb - > transfer_flags & = ~ URB_ALIGNED_TEMP_BUFFER ;
}
2012-04-05 11:25:30 +05:30
static int alloc_dma_aligned_buffer ( struct urb * urb , gfp_t mem_flags )
2011-03-09 16:28:57 -08:00
{
2012-04-05 11:25:30 +05:30
struct dma_aligned_buffer * temp , * kmalloc_ptr ;
2011-03-09 16:28:57 -08:00
size_t kmalloc_size ;
if ( urb - > num_sgs | | urb - > sg | |
urb - > transfer_buffer_length = = 0 | |
! ( ( uintptr_t ) urb - > transfer_buffer & ( TEGRA_USB_DMA_ALIGN - 1 ) ) )
return 0 ;
/* Allocate a buffer with enough padding for alignment */
kmalloc_size = urb - > transfer_buffer_length +
2012-04-05 11:25:30 +05:30
sizeof ( struct dma_aligned_buffer ) + TEGRA_USB_DMA_ALIGN - 1 ;
2011-03-09 16:28:57 -08:00
kmalloc_ptr = kmalloc ( kmalloc_size , mem_flags ) ;
if ( ! kmalloc_ptr )
return - ENOMEM ;
2012-04-05 11:25:30 +05:30
/* Position our struct dma_aligned_buffer such that data is aligned */
2011-03-09 16:28:57 -08:00
temp = PTR_ALIGN ( kmalloc_ptr + 1 , TEGRA_USB_DMA_ALIGN ) - 1 ;
temp - > kmalloc_ptr = kmalloc_ptr ;
temp - > old_xfer_buffer = urb - > transfer_buffer ;
2012-04-05 11:25:30 +05:30
if ( usb_urb_dir_out ( urb ) )
2011-03-09 16:28:57 -08:00
memcpy ( temp - > data , urb - > transfer_buffer ,
urb - > transfer_buffer_length ) ;
urb - > transfer_buffer = temp - > data ;
urb - > transfer_flags | = URB_ALIGNED_TEMP_BUFFER ;
return 0 ;
}
static int tegra_ehci_map_urb_for_dma ( struct usb_hcd * hcd , struct urb * urb ,
gfp_t mem_flags )
{
int ret ;
2012-04-05 11:25:30 +05:30
ret = alloc_dma_aligned_buffer ( urb , mem_flags ) ;
2011-03-09 16:28:57 -08:00
if ( ret )
return ret ;
ret = usb_hcd_map_urb_for_dma ( hcd , urb , mem_flags ) ;
if ( ret )
2012-04-05 11:25:30 +05:30
free_dma_aligned_buffer ( urb ) ;
2011-03-09 16:28:57 -08:00
return ret ;
}
static void tegra_ehci_unmap_urb_for_dma ( struct usb_hcd * hcd , struct urb * urb )
{
usb_hcd_unmap_urb_for_dma ( hcd , urb ) ;
2012-04-05 11:25:30 +05:30
free_dma_aligned_buffer ( urb ) ;
2011-03-09 16:28:57 -08:00
}
2013-08-12 16:06:54 +03:00
static const struct tegra_ehci_soc_config tegra30_soc_config = {
. has_hostpc = true ,
} ;
static const struct tegra_ehci_soc_config tegra20_soc_config = {
. has_hostpc = false ,
} ;
static struct of_device_id tegra_ehci_of_match [ ] = {
{ . compatible = " nvidia,tegra30-ehci " , . data = & tegra30_soc_config } ,
{ . compatible = " nvidia,tegra20-ehci " , . data = & tegra20_soc_config } ,
{ } ,
} ;
2011-03-09 16:28:56 -08:00
static int tegra_ehci_probe ( struct platform_device * pdev )
{
2013-08-12 16:06:54 +03:00
const struct of_device_id * match ;
const struct tegra_ehci_soc_config * soc_config ;
2011-03-09 16:28:56 -08:00
struct resource * res ;
struct usb_hcd * hcd ;
2013-06-13 11:24:13 -06:00
struct ehci_hcd * ehci ;
2011-03-09 16:28:56 -08:00
struct tegra_ehci_hcd * tegra ;
int err = 0 ;
int irq ;
2013-01-17 20:15:37 +00:00
struct usb_phy * u_phy ;
2011-03-09 16:28:56 -08:00
2013-08-12 16:06:54 +03:00
match = of_match_device ( tegra_ehci_of_match , & pdev - > dev ) ;
if ( ! match ) {
dev_err ( & pdev - > dev , " Error: No device match found \n " ) ;
return - ENODEV ;
}
soc_config = match - > data ;
2011-11-04 09:12:40 +00: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 have dma capability bindings this can go away .
*/
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 ;
2011-11-04 09:12:40 +00:00
2013-06-13 11:24:13 -06:00
hcd = usb_create_hcd ( & tegra_ehci_hc_driver , & pdev - > dev ,
dev_name ( & pdev - > dev ) ) ;
if ( ! hcd ) {
dev_err ( & pdev - > dev , " Unable to create HCD \n " ) ;
2013-07-17 10:37:49 +03:00
return - ENOMEM ;
2013-06-13 11:24:13 -06:00
}
platform_set_drvdata ( pdev , hcd ) ;
ehci = hcd_to_ehci ( hcd ) ;
tegra = ( struct tegra_ehci_hcd * ) ehci - > priv ;
hcd - > has_tt = 1 ;
2011-03-09 16:28:56 -08:00
2012-07-30 16:43:41 +02:00
tegra - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
2011-03-09 16:28:56 -08:00
if ( IS_ERR ( tegra - > clk ) ) {
dev_err ( & pdev - > dev , " Can't get ehci clock \n " ) ;
2013-06-13 11:24:13 -06:00
err = PTR_ERR ( tegra - > clk ) ;
goto cleanup_hcd_create ;
2011-03-09 16:28:56 -08:00
}
2013-11-06 16:53:58 -07:00
tegra - > rst = devm_reset_control_get ( & pdev - > dev , " usb " ) ;
if ( IS_ERR ( tegra - > rst ) ) {
dev_err ( & pdev - > dev , " Can't get ehci reset \n " ) ;
err = PTR_ERR ( tegra - > rst ) ;
goto cleanup_hcd_create ;
}
2012-06-05 09:59:38 +05:30
err = clk_prepare_enable ( tegra - > clk ) ;
2011-03-09 16:28:56 -08:00
if ( err )
2013-09-27 16:22:08 +08:00
goto cleanup_hcd_create ;
2011-03-09 16:28:56 -08:00
2013-11-06 16:53:58 -07:00
reset_control_assert ( tegra - > rst ) ;
2013-04-03 16:11:12 +05:30
udelay ( 1 ) ;
2013-11-06 16:53:58 -07:00
reset_control_deassert ( tegra - > rst ) ;
2013-04-03 16:11:12 +05:30
2013-07-25 21:38:06 +03:00
u_phy = devm_usb_get_phy_by_phandle ( & pdev - > dev , " nvidia,phy " , 0 ) ;
2013-05-16 19:43:02 +05:30
if ( IS_ERR ( u_phy ) ) {
err = PTR_ERR ( u_phy ) ;
2013-06-13 11:24:13 -06:00
goto cleanup_clk_en ;
2013-05-16 19:43:02 +05:30
}
2013-06-13 11:24:13 -06:00
hcd - > phy = u_phy ;
2013-05-16 19:43:02 +05:30
2012-12-13 20:59:08 +00:00
tegra - > needs_double_reset = of_property_read_bool ( pdev - > dev . of_node ,
" nvidia,needs-double-reset " ) ;
2011-03-09 16:28:56 -08:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! res ) {
dev_err ( & pdev - > dev , " Failed to get I/O memory \n " ) ;
err = - ENXIO ;
2013-06-13 11:24:13 -06:00
goto cleanup_clk_en ;
2011-03-09 16:28:56 -08:00
}
hcd - > rsrc_start = res - > start ;
hcd - > rsrc_len = resource_size ( res ) ;
2014-05-10 17:30:09 +05:30
hcd - > regs = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( hcd - > regs ) ) {
err = PTR_ERR ( hcd - > regs ) ;
2013-06-13 11:24:13 -06:00
goto cleanup_clk_en ;
2011-11-04 09:12:40 +00:00
}
2013-06-13 11:24:13 -06:00
ehci - > caps = hcd - > regs + 0x100 ;
2013-08-12 16:06:54 +03:00
ehci - > has_hostpc = soc_config - > has_hostpc ;
2011-11-04 09:12:40 +00:00
2013-05-16 19:43:02 +05:30
err = usb_phy_init ( hcd - > phy ) ;
if ( err ) {
dev_err ( & pdev - > dev , " Failed to initialize phy \n " ) ;
2013-06-13 11:24:13 -06:00
goto cleanup_clk_en ;
2011-03-09 16:28:56 -08:00
}
2013-01-17 20:15:37 +00:00
u_phy - > otg = devm_kzalloc ( & pdev - > dev , sizeof ( struct usb_otg ) ,
GFP_KERNEL ) ;
if ( ! u_phy - > otg ) {
dev_err ( & pdev - > dev , " Failed to alloc memory for otg \n " ) ;
err = - ENOMEM ;
2013-05-16 19:43:02 +05:30
goto cleanup_phy ;
2013-01-17 20:15:37 +00:00
}
u_phy - > otg - > host = hcd_to_bus ( hcd ) ;
2013-01-24 15:57:03 +05:30
err = usb_phy_set_suspend ( hcd - > phy , 0 ) ;
2011-03-09 16:28:56 -08:00
if ( err ) {
dev_err ( & pdev - > dev , " Failed to power on the phy \n " ) ;
2013-05-16 19:43:02 +05:30
goto cleanup_phy ;
2011-03-09 16:28:56 -08:00
}
irq = platform_get_irq ( pdev , 0 ) ;
if ( ! irq ) {
dev_err ( & pdev - > dev , " Failed to get IRQ \n " ) ;
err = - ENODEV ;
2013-05-16 19:43:02 +05:30
goto cleanup_phy ;
2011-03-09 16:28:56 -08:00
}
2013-07-25 21:38:02 +03:00
otg_set_host ( u_phy - > otg , & hcd - > self ) ;
2011-03-09 16:28:56 -08:00
2011-09-07 16:10:52 +08:00
err = usb_add_hcd ( hcd , irq , IRQF_SHARED ) ;
2011-03-09 16:28:56 -08:00
if ( err ) {
dev_err ( & pdev - > dev , " Failed to add USB HCD \n " ) ;
2013-07-25 21:38:02 +03:00
goto cleanup_otg_set_host ;
2011-03-09 16:28:56 -08:00
}
2013-11-05 10:46:02 +08:00
device_wakeup_enable ( hcd - > self . controller ) ;
2011-03-09 16:28:56 -08:00
return err ;
2013-07-25 21:38:02 +03:00
cleanup_otg_set_host :
otg_set_host ( u_phy - > otg , NULL ) ;
2013-06-14 13:21:21 +02:00
cleanup_phy :
2013-01-24 15:57:03 +05:30
usb_phy_shutdown ( hcd - > phy ) ;
2013-06-13 11:24:13 -06:00
cleanup_clk_en :
clk_disable_unprepare ( tegra - > clk ) ;
2013-05-16 19:43:02 +05:30
cleanup_hcd_create :
2011-03-09 16:28:56 -08:00
usb_put_hcd ( hcd ) ;
return err ;
}
static int tegra_ehci_remove ( struct platform_device * pdev )
{
2013-06-13 11:24:13 -06:00
struct usb_hcd * hcd = platform_get_drvdata ( pdev ) ;
struct tegra_ehci_hcd * tegra =
( struct tegra_ehci_hcd * ) hcd_to_ehci ( hcd ) - > priv ;
2011-03-09 16:28:56 -08:00
2013-07-25 21:38:02 +03:00
otg_set_host ( hcd - > phy - > otg , NULL ) ;
2011-03-09 16:28:56 -08:00
2013-01-24 15:57:03 +05:30
usb_phy_shutdown ( hcd - > phy ) ;
2011-03-09 16:28:56 -08:00
usb_remove_hcd ( hcd ) ;
2012-08-10 11:42:43 +05:30
usb_put_hcd ( hcd ) ;
2012-06-05 09:59:38 +05:30
clk_disable_unprepare ( tegra - > clk ) ;
2011-03-09 16:28:56 -08:00
return 0 ;
}
static void tegra_ehci_hcd_shutdown ( struct platform_device * pdev )
{
2013-06-13 11:24:13 -06:00
struct usb_hcd * hcd = platform_get_drvdata ( pdev ) ;
2011-03-09 16:28:56 -08:00
if ( hcd - > driver - > shutdown )
hcd - > driver - > shutdown ( hcd ) ;
}
static struct platform_driver tegra_ehci_driver = {
. probe = tegra_ehci_probe ,
. remove = tegra_ehci_remove ,
. shutdown = tegra_ehci_hcd_shutdown ,
. driver = {
2013-06-13 11:24:12 -06:00
. name = DRV_NAME ,
2011-11-04 09:12:40 +00:00
. of_match_table = tegra_ehci_of_match ,
2011-03-09 16:28:56 -08:00
}
} ;
2013-06-13 11:24:12 -06:00
2014-04-14 15:21:23 -06:00
static int tegra_ehci_reset ( struct usb_hcd * hcd )
{
struct ehci_hcd * ehci = hcd_to_ehci ( hcd ) ;
int retval ;
int txfifothresh ;
retval = ehci_setup ( hcd ) ;
if ( retval )
return retval ;
/*
* We should really pull this value out of tegra_ehci_soc_config , but
* to avoid needing access to it , make use of the fact that Tegra20 is
* the only one so far that needs a value of 10 , and Tegra20 is the
* only one which doesn ' t set has_hostpc .
*/
txfifothresh = ehci - > has_hostpc ? 0x10 : 10 ;
ehci_writel ( ehci , txfifothresh < < 16 , & ehci - > regs - > txfill_tuning ) ;
return 0 ;
}
2013-06-13 11:24:12 -06:00
static const struct ehci_driver_overrides tegra_overrides __initconst = {
. extra_priv_size = sizeof ( struct tegra_ehci_hcd ) ,
2014-04-14 15:21:23 -06:00
. reset = tegra_ehci_reset ,
2013-06-13 11:24:12 -06:00
} ;
static int __init ehci_tegra_init ( void )
{
if ( usb_disabled ( ) )
return - ENODEV ;
pr_info ( DRV_NAME " : " DRIVER_DESC " \n " ) ;
ehci_init_driver ( & tegra_ehci_hc_driver , & tegra_overrides ) ;
/*
* The Tegra HW has some unusual quirks , which require Tegra - specific
* workarounds . We override certain hc_driver functions here to
* achieve that . We explicitly do not enhance ehci_driver_overrides to
* allow this more easily , since this is an unusual case , and we don ' t
* want to encourage others to override these functions by making it
* too easy .
*/
tegra_ehci_hc_driver . map_urb_for_dma = tegra_ehci_map_urb_for_dma ;
tegra_ehci_hc_driver . unmap_urb_for_dma = tegra_ehci_unmap_urb_for_dma ;
tegra_ehci_hc_driver . hub_control = tegra_ehci_hub_control ;
return platform_driver_register ( & tegra_ehci_driver ) ;
}
module_init ( ehci_tegra_init ) ;
static void __exit ehci_tegra_cleanup ( void )
{
platform_driver_unregister ( & tegra_ehci_driver ) ;
}
module_exit ( ehci_tegra_cleanup ) ;
MODULE_DESCRIPTION ( DRIVER_DESC ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform: " DRV_NAME ) ;
MODULE_DEVICE_TABLE ( of , tegra_ehci_of_match ) ;