2006-01-21 01:06:09 +03:00
/*
* EHCI HCD ( Host Controller Driver ) for USB .
*
* Bus Glue for AMD Alchemy Au1xxx
*
* Based on " ohci-au1xxx.c " by Matt Porter < mporter @ kernel . crashing . org >
*
* Modified for AMD Alchemy Au1200 EHC
* by K . Boge < karsten . boge @ amd . com >
*
* This file is licenced under the GPL .
*/
# include <linux/platform_device.h>
# include <asm/mach-au1x00/au1000.h>
# define USB_HOST_CONFIG (USB_MSR_BASE + USB_MSR_MCFG)
# define USB_MCFG_PFEN (1<<31)
# define USB_MCFG_RDCOMB (1<<30)
# define USB_MCFG_SSDEN (1<<23)
# define USB_MCFG_PHYPLLEN (1<<19)
2008-06-23 11:08:29 +04:00
# define USB_MCFG_UCECLKEN (1<<18)
2006-01-21 01:06:09 +03:00
# define USB_MCFG_EHCCLKEN (1<<17)
2008-06-23 11:08:29 +04:00
# ifdef CONFIG_DMA_COHERENT
2006-01-21 01:06:09 +03:00
# define USB_MCFG_UCAM (1<<7)
2008-06-23 11:08:29 +04:00
# else
# define USB_MCFG_UCAM (0)
# endif
2006-01-21 01:06:09 +03:00
# define USB_MCFG_EBMEN (1<<3)
# define USB_MCFG_EMEMEN (1<<2)
2008-06-23 11:08:29 +04:00
# define USBH_ENABLE_CE (USB_MCFG_PHYPLLEN | USB_MCFG_EHCCLKEN)
# define USBH_ENABLE_INIT (USB_MCFG_PFEN | USB_MCFG_RDCOMB | \
USBH_ENABLE_CE | USB_MCFG_SSDEN | \
USB_MCFG_UCAM | USB_MCFG_EBMEN | \
USB_MCFG_EMEMEN )
2006-01-21 01:06:09 +03:00
# define USBH_DISABLE (USB_MCFG_EBMEN | USB_MCFG_EMEMEN)
extern int usb_disabled ( void ) ;
2008-06-23 11:08:29 +04:00
static void au1xxx_start_ehc ( void )
2006-01-21 01:06:09 +03:00
{
2008-06-23 11:08:29 +04:00
/* enable clock to EHCI block and HS PHY PLL*/
au_writel ( au_readl ( USB_HOST_CONFIG ) | USBH_ENABLE_CE , USB_HOST_CONFIG ) ;
au_sync ( ) ;
2006-01-21 01:06:09 +03:00
udelay ( 1000 ) ;
2008-06-23 11:08:29 +04:00
/* enable EHCI mmio */
au_writel ( au_readl ( USB_HOST_CONFIG ) | USBH_ENABLE_INIT , USB_HOST_CONFIG ) ;
au_sync ( ) ;
udelay ( 1000 ) ;
2006-01-21 01:06:09 +03:00
}
2008-06-23 11:08:29 +04:00
static void au1xxx_stop_ehc ( void )
2006-01-21 01:06:09 +03:00
{
2008-06-23 11:08:29 +04:00
unsigned long c ;
2006-01-21 01:06:09 +03:00
/* Disable mem */
2008-06-23 11:08:29 +04:00
au_writel ( au_readl ( USB_HOST_CONFIG ) & ~ USBH_DISABLE , USB_HOST_CONFIG ) ;
au_sync ( ) ;
2006-01-21 01:06:09 +03:00
udelay ( 1000 ) ;
2008-06-23 11:08:29 +04:00
/* Disable EHC clock. If the HS PHY is unused disable it too. */
c = au_readl ( USB_HOST_CONFIG ) & ~ USB_MCFG_EHCCLKEN ;
if ( ! ( c & USB_MCFG_UCECLKEN ) ) /* UDC disabled? */
c & = ~ USB_MCFG_PHYPLLEN ; /* yes: disable HS PHY PLL */
au_writel ( c , USB_HOST_CONFIG ) ;
au_sync ( ) ;
2006-01-21 01:06:09 +03:00
}
2008-06-23 11:08:29 +04:00
static const struct hc_driver ehci_au1xxx_hc_driver = {
. description = hcd_name ,
. product_desc = " Au1xxx EHCI " ,
. hcd_priv_size = sizeof ( struct ehci_hcd ) ,
2006-01-21 01:06:09 +03:00
2008-06-23 11:08:29 +04:00
/*
* generic hardware linkage
*/
. irq = ehci_irq ,
. flags = HCD_MEMORY | HCD_USB2 ,
2006-01-21 01:06:09 +03:00
2008-06-23 11:08:29 +04:00
/*
* basic lifecycle operations
*
* FIXME - - ehci_init ( ) doesn ' t do enough here .
* See ehci - ppc - soc for a complete implementation .
*/
. reset = ehci_init ,
. start = ehci_run ,
. stop = ehci_stop ,
. shutdown = ehci_shutdown ,
/*
* managing i / o requests and associated device resources
*/
. urb_enqueue = ehci_urb_enqueue ,
. urb_dequeue = ehci_urb_dequeue ,
. endpoint_disable = ehci_endpoint_disable ,
2009-05-28 02:21:56 +04:00
. endpoint_reset = ehci_endpoint_reset ,
2008-06-23 11:08:29 +04:00
/*
* scheduling support
*/
. get_frame_number = ehci_get_frame ,
/*
* root hub support
*/
. 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 ,
2009-06-29 18:47:30 +04:00
. clear_tt_buffer_complete = ehci_clear_tt_buffer_complete ,
2008-06-23 11:08:29 +04:00
} ;
static int ehci_hcd_au1xxx_drv_probe ( struct platform_device * pdev )
2006-01-21 01:06:09 +03:00
{
struct usb_hcd * hcd ;
struct ehci_hcd * ehci ;
2008-06-23 11:08:29 +04:00
int ret ;
2006-01-21 01:06:09 +03:00
2008-06-23 11:08:29 +04:00
if ( usb_disabled ( ) )
return - ENODEV ;
2006-01-21 01:06:09 +03:00
2008-06-23 11:08:29 +04:00
# if defined(CONFIG_SOC_AU1200) && defined(CONFIG_DMA_COHERENT)
2006-01-21 01:06:09 +03:00
/* Au1200 AB USB does not support coherent memory */
if ( ! ( read_c0_prid ( ) & 0xff ) ) {
2008-06-23 11:08:29 +04:00
printk ( KERN_INFO " %s: this is chip revision AB! \n " , pdev - > name ) ;
printk ( KERN_INFO " %s: update your board or re-configure "
" the kernel \n " , pdev - > name ) ;
2006-01-21 01:06:09 +03:00
return - ENODEV ;
}
# endif
2008-06-23 11:08:29 +04:00
if ( pdev - > resource [ 1 ] . flags ! = IORESOURCE_IRQ ) {
2006-01-21 01:06:09 +03:00
pr_debug ( " resource[1] is not IORESOURCE_IRQ " ) ;
2008-06-23 11:08:29 +04:00
return - ENOMEM ;
2006-01-21 01:06:09 +03:00
}
2008-06-23 11:08:29 +04:00
hcd = usb_create_hcd ( & ehci_au1xxx_hc_driver , & pdev - > dev , " Au1xxx " ) ;
2006-01-21 01:06:09 +03:00
if ( ! hcd )
return - ENOMEM ;
2008-06-23 11:08:29 +04:00
hcd - > rsrc_start = pdev - > resource [ 0 ] . start ;
hcd - > rsrc_len = pdev - > resource [ 0 ] . end - pdev - > resource [ 0 ] . start + 1 ;
2006-01-21 01:06:09 +03:00
if ( ! request_mem_region ( hcd - > rsrc_start , hcd - > rsrc_len , hcd_name ) ) {
pr_debug ( " request_mem_region failed " ) ;
2008-06-23 11:08:29 +04:00
ret = - EBUSY ;
2006-01-21 01:06:09 +03:00
goto err1 ;
}
hcd - > regs = ioremap ( hcd - > rsrc_start , hcd - > rsrc_len ) ;
if ( ! hcd - > regs ) {
pr_debug ( " ioremap failed " ) ;
2008-06-23 11:08:29 +04:00
ret = - ENOMEM ;
2006-01-21 01:06:09 +03:00
goto err2 ;
}
2008-06-23 11:08:29 +04:00
au1xxx_start_ehc ( ) ;
2006-01-21 01:06:09 +03:00
ehci = hcd_to_ehci ( hcd ) ;
ehci - > caps = hcd - > regs ;
ehci - > regs = hcd - > regs + HC_LENGTH ( readl ( & ehci - > caps - > hc_capbase ) ) ;
/* cache this readonly data; minimize chip reads */
ehci - > hcs_params = readl ( & ehci - > caps - > hcs_params ) ;
2008-06-23 11:08:29 +04:00
ret = usb_add_hcd ( hcd , pdev - > resource [ 1 ] . start ,
IRQF_DISABLED | IRQF_SHARED ) ;
if ( ret = = 0 ) {
platform_set_drvdata ( pdev , hcd ) ;
return ret ;
}
2006-01-21 01:06:09 +03:00
2008-06-23 11:08:29 +04:00
au1xxx_stop_ehc ( ) ;
2006-01-21 01:06:09 +03:00
iounmap ( hcd - > regs ) ;
err2 :
release_mem_region ( hcd - > rsrc_start , hcd - > rsrc_len ) ;
err1 :
usb_put_hcd ( hcd ) ;
2008-06-23 11:08:29 +04:00
return ret ;
2006-01-21 01:06:09 +03:00
}
2008-06-23 11:08:29 +04:00
static int ehci_hcd_au1xxx_drv_remove ( struct platform_device * pdev )
2006-01-21 01:06:09 +03:00
{
2008-06-23 11:08:29 +04:00
struct usb_hcd * hcd = platform_get_drvdata ( pdev ) ;
2006-01-21 01:06:09 +03:00
usb_remove_hcd ( hcd ) ;
iounmap ( hcd - > regs ) ;
release_mem_region ( hcd - > rsrc_start , hcd - > rsrc_len ) ;
usb_put_hcd ( hcd ) ;
2008-06-23 11:08:29 +04:00
au1xxx_stop_ehc ( ) ;
platform_set_drvdata ( pdev , NULL ) ;
2006-01-21 01:06:09 +03:00
return 0 ;
}
2008-06-23 11:09:37 +04:00
# ifdef CONFIG_PM
2009-07-29 21:13:13 +04:00
static int ehci_hcd_au1xxx_drv_suspend ( struct device * dev )
2006-01-21 01:06:09 +03:00
{
2009-07-29 21:13:13 +04:00
struct usb_hcd * hcd = dev_get_drvdata ( dev ) ;
2008-06-23 11:09:37 +04:00
struct ehci_hcd * ehci = hcd_to_ehci ( hcd ) ;
unsigned long flags ;
int rc ;
2006-01-21 01:06:09 +03:00
return 0 ;
2008-06-23 11:09:37 +04:00
rc = 0 ;
if ( time_before ( jiffies , ehci - > next_statechange ) )
msleep ( 10 ) ;
/* 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 .
*
* This is still racy as hcd - > state is manipulated outside of
* any locks = P But that will be a different fix .
*/
spin_lock_irqsave ( & ehci - > lock , flags ) ;
if ( hcd - > state ! = HC_STATE_SUSPENDED ) {
rc = - EINVAL ;
goto bail ;
}
ehci_writel ( ehci , 0 , & ehci - > regs - > intr_enable ) ;
( void ) ehci_readl ( ehci , & ehci - > regs - > intr_enable ) ;
clear_bit ( HCD_FLAG_HW_ACCESSIBLE , & hcd - > flags ) ;
au1xxx_stop_ehc ( ) ;
bail :
spin_unlock_irqrestore ( & ehci - > lock , flags ) ;
// could save FLADJ in case of Vaux power loss
// ... we'd only use it to handle clock skew
return rc ;
2006-01-21 01:06:09 +03:00
}
2008-06-23 11:09:37 +04:00
2009-07-29 21:13:13 +04:00
static int ehci_hcd_au1xxx_drv_resume ( struct device * dev )
2006-01-21 01:06:09 +03:00
{
2009-07-29 21:13:13 +04:00
struct usb_hcd * hcd = dev_get_drvdata ( dev ) ;
2008-06-23 11:09:37 +04:00
struct ehci_hcd * ehci = hcd_to_ehci ( hcd ) ;
au1xxx_start_ehc ( ) ;
// maybe restore FLADJ
if ( time_before ( jiffies , ehci - > next_statechange ) )
msleep ( 100 ) ;
/* Mark hardware accessible again as we are out of D3 state by now */
set_bit ( HCD_FLAG_HW_ACCESSIBLE , & hcd - > flags ) ;
/* If CF is still set, we maintained PCI Vaux power.
* Just undo the effect of ehci_pci_suspend ( ) .
*/
if ( ehci_readl ( ehci , & ehci - > regs - > configured_flag ) = = FLAG_CF ) {
int mask = INTR_MASK ;
if ( ! hcd - > self . root_hub - > do_remote_wakeup )
mask & = ~ STS_PCD ;
ehci_writel ( ehci , mask , & ehci - > regs - > intr_enable ) ;
ehci_readl ( ehci , & ehci - > regs - > intr_enable ) ;
return 0 ;
}
ehci_dbg ( ehci , " lost power, restarting \n " ) ;
usb_root_hub_lost_power ( hcd - > self . root_hub ) ;
/* Else reset, to cope with power loss or flush-to-storage
* style " resume " having let BIOS kick in during reboot .
*/
( void ) ehci_halt ( ehci ) ;
( void ) ehci_reset ( ehci ) ;
/* emptying the schedule aborts any urbs */
spin_lock_irq ( & ehci - > lock ) ;
if ( ehci - > reclaim )
end_unlink_async ( ehci ) ;
ehci_work ( ehci ) ;
spin_unlock_irq ( & ehci - > lock ) ;
ehci_writel ( ehci , ehci - > command , & ehci - > regs - > command ) ;
ehci_writel ( ehci , FLAG_CF , & ehci - > regs - > configured_flag ) ;
ehci_readl ( ehci , & ehci - > regs - > command ) ; /* unblock posted writes */
/* here we "know" root ports should always stay powered */
ehci_port_power ( ehci , 1 ) ;
hcd - > state = HC_STATE_SUSPENDED ;
2006-01-21 01:06:09 +03:00
return 0 ;
}
2008-06-23 11:09:37 +04:00
2009-07-29 21:13:13 +04:00
static struct dev_pm_ops au1xxx_ehci_pmops = {
. suspend = ehci_hcd_au1xxx_drv_suspend ,
. resume = ehci_hcd_au1xxx_drv_resume ,
} ;
# define AU1XXX_EHCI_PMOPS &au1xxx_ehci_pmops
2008-06-23 11:09:37 +04:00
# else
2009-07-29 21:13:13 +04:00
# define AU1XXX_EHCI_PMOPS NULL
2008-06-23 11:09:37 +04:00
# endif
2008-06-23 11:08:29 +04:00
2006-06-24 00:36:07 +04:00
static struct platform_driver ehci_hcd_au1xxx_driver = {
2008-06-23 11:08:29 +04:00
. probe = ehci_hcd_au1xxx_drv_probe ,
. remove = ehci_hcd_au1xxx_drv_remove ,
. shutdown = usb_hcd_platform_shutdown ,
2006-06-24 00:36:07 +04:00
. driver = {
2008-06-23 11:08:29 +04:00
. name = " au1xxx-ehci " ,
. owner = THIS_MODULE ,
2009-07-29 21:13:13 +04:00
. pm = AU1XXX_EHCI_PMOPS ,
2006-06-24 00:36:07 +04:00
}
2006-01-21 01:06:09 +03:00
} ;
2008-06-23 11:08:29 +04:00
MODULE_ALIAS ( " platform:au1xxx-ehci " ) ;