2006-01-22 10:32:13 -08:00
/*
* OHCI HCD ( Host Controller Driver ) for USB .
*
* Copyright ( C ) 2004 SAN People ( Pty ) Ltd .
* Copyright ( C ) 2005 Thibaut VARENE < varenet @ parisc - linux . org >
*
* AT91RM9200 Bus Glue
*
* Based on fragments of 2.4 driver by Rick Bronson .
* Based on ohci - omap . c
*
* This file is licenced under the GPL .
*/
# include <linux/clk.h>
# include <linux/platform_device.h>
# include <asm/mach-types.h>
# include <asm/hardware.h>
# include <asm/arch/board.h>
# ifndef CONFIG_ARCH_AT91RM9200
2006-04-02 20:26:21 -08:00
# error "CONFIG_ARCH_AT91RM9200 must be defined."
2006-01-22 10:32:13 -08:00
# endif
/* interface and function clocks */
static struct clk * iclk , * fclk ;
extern int usb_disabled ( void ) ;
/*-------------------------------------------------------------------------*/
static void at91_start_hc ( struct platform_device * pdev )
{
struct usb_hcd * hcd = platform_get_drvdata ( pdev ) ;
struct ohci_regs __iomem * regs = hcd - > regs ;
dev_dbg ( & pdev - > dev , " starting AT91RM9200 OHCI USB Controller \n " ) ;
/*
* Start the USB clocks .
*/
clk_enable ( iclk ) ;
clk_enable ( fclk ) ;
/*
* The USB host controller must remain in reset .
*/
writel ( 0 , & regs - > control ) ;
}
static void at91_stop_hc ( struct platform_device * pdev )
{
struct usb_hcd * hcd = platform_get_drvdata ( pdev ) ;
struct ohci_regs __iomem * regs = hcd - > regs ;
dev_dbg ( & pdev - > dev , " stopping AT91RM9200 OHCI USB Controller \n " ) ;
/*
* Put the USB host controller into reset .
*/
writel ( 0 , & regs - > control ) ;
/*
* Stop the USB clocks .
*/
clk_disable ( fclk ) ;
clk_disable ( iclk ) ;
}
/*-------------------------------------------------------------------------*/
static int usb_hcd_at91_remove ( struct usb_hcd * , struct platform_device * ) ;
/* configure so an HC device and id are always provided */
/* always called with process context; sleeping is OK */
/**
* usb_hcd_at91_probe - initialize AT91RM9200 - based HCDs
* Context : ! in_interrupt ( )
*
* Allocates basic resources for this USB host controller , and
* then invokes the start ( ) method for the HCD associated with it
* through the hotplug entry ' s driver_data .
*/
int usb_hcd_at91_probe ( const struct hc_driver * driver , struct platform_device * pdev )
{
int retval ;
struct usb_hcd * hcd = NULL ;
if ( pdev - > num_resources ! = 2 ) {
pr_debug ( " hcd probe: invalid num_resources " ) ;
return - ENODEV ;
}
if ( ( pdev - > resource [ 0 ] . flags ! = IORESOURCE_MEM ) | | ( pdev - > resource [ 1 ] . flags ! = IORESOURCE_IRQ ) ) {
pr_debug ( " hcd probe: invalid resource type \n " ) ;
return - ENODEV ;
}
hcd = usb_create_hcd ( driver , & pdev - > dev , " at91rm9200 " ) ;
if ( ! hcd )
return - ENOMEM ;
hcd - > rsrc_start = pdev - > resource [ 0 ] . start ;
hcd - > rsrc_len = pdev - > resource [ 0 ] . end - pdev - > resource [ 0 ] . start + 1 ;
if ( ! request_mem_region ( hcd - > rsrc_start , hcd - > rsrc_len , hcd_name ) ) {
pr_debug ( " request_mem_region failed \n " ) ;
retval = - EBUSY ;
goto err1 ;
}
hcd - > regs = ioremap ( hcd - > rsrc_start , hcd - > rsrc_len ) ;
if ( ! hcd - > regs ) {
pr_debug ( " ioremap failed \n " ) ;
retval = - EIO ;
goto err2 ;
}
iclk = clk_get ( & pdev - > dev , " ohci_clk " ) ;
fclk = clk_get ( & pdev - > dev , " uhpck " ) ;
at91_start_hc ( pdev ) ;
ohci_hcd_init ( hcd_to_ohci ( hcd ) ) ;
retval = usb_add_hcd ( hcd , pdev - > resource [ 1 ] . start , SA_INTERRUPT ) ;
if ( retval = = 0 )
return retval ;
/* Error handling */
at91_stop_hc ( pdev ) ;
clk_put ( fclk ) ;
clk_put ( iclk ) ;
iounmap ( hcd - > regs ) ;
err2 :
release_mem_region ( hcd - > rsrc_start , hcd - > rsrc_len ) ;
err1 :
usb_put_hcd ( hcd ) ;
return retval ;
}
/* may be called with controller, bus, and devices active */
/**
* usb_hcd_at91_remove - shutdown processing for AT91RM9200 - based HCDs
* @ dev : USB Host Controller being removed
* Context : ! in_interrupt ( )
*
* Reverses the effect of usb_hcd_at91_probe ( ) , first invoking
* the HCD ' s stop ( ) method . It is always called from a thread
* context , normally " rmmod " , " apmd " , or something similar .
*
*/
static int usb_hcd_at91_remove ( struct usb_hcd * hcd , struct platform_device * pdev )
{
usb_remove_hcd ( hcd ) ;
at91_stop_hc ( pdev ) ;
iounmap ( hcd - > regs ) ;
2006-04-02 20:26:21 -08:00
release_mem_region ( hcd - > rsrc_start , hcd - > rsrc_len ) ;
2006-01-22 10:32:13 -08:00
2006-04-02 20:26:21 -08:00
clk_put ( fclk ) ;
clk_put ( iclk ) ;
fclk = iclk = NULL ;
2006-01-22 10:32:13 -08:00
dev_set_drvdata ( & pdev - > dev , NULL ) ;
return 0 ;
}
/*-------------------------------------------------------------------------*/
static int __devinit
ohci_at91_start ( struct usb_hcd * hcd )
{
// struct at91_ohci_data *board = hcd->self.controller->platform_data;
struct ohci_hcd * ohci = hcd_to_ohci ( hcd ) ;
int ret ;
if ( ( ret = ohci_init ( ohci ) ) < 0 )
return ret ;
if ( ( ret = ohci_run ( ohci ) ) < 0 ) {
err ( " can't start %s " , hcd - > self . bus_name ) ;
ohci_stop ( hcd ) ;
return ret ;
}
// hcd->self.root_hub->maxchild = board->ports;
return 0 ;
}
/*-------------------------------------------------------------------------*/
static const struct hc_driver ohci_at91_hc_driver = {
. description = hcd_name ,
. product_desc = " AT91RM9200 OHCI " ,
. hcd_priv_size = sizeof ( struct ohci_hcd ) ,
/*
* generic hardware linkage
*/
. irq = ohci_irq ,
. flags = HCD_USB11 | HCD_MEMORY ,
/*
* basic lifecycle operations
*/
. start = ohci_at91_start ,
. stop = ohci_stop ,
/*
* managing i / o requests and associated device resources
*/
. urb_enqueue = ohci_urb_enqueue ,
. urb_dequeue = ohci_urb_dequeue ,
. endpoint_disable = ohci_endpoint_disable ,
/*
* scheduling support
*/
. get_frame_number = ohci_get_frame ,
/*
* root hub support
*/
. hub_status_data = ohci_hub_status_data ,
. hub_control = ohci_hub_control ,
# ifdef CONFIG_PM
2006-04-02 20:26:21 -08:00
. bus_suspend = ohci_bus_suspend ,
. bus_resume = ohci_bus_resume ,
2006-01-22 10:32:13 -08:00
# endif
. start_port_reset = ohci_start_port_reset ,
} ;
/*-------------------------------------------------------------------------*/
static int ohci_hcd_at91_drv_probe ( struct platform_device * dev )
{
return usb_hcd_at91_probe ( & ohci_at91_hc_driver , dev ) ;
}
static int ohci_hcd_at91_drv_remove ( struct platform_device * dev )
{
return usb_hcd_at91_remove ( platform_get_drvdata ( dev ) , dev ) ;
}
# ifdef CONFIG_PM
2006-04-02 20:26:21 -08:00
/* REVISIT suspend/resume look "too" simple here */
static int
ohci_hcd_at91_drv_suspend ( struct platform_device * dev , pm_message_t mesg )
{
2006-01-22 10:32:13 -08:00
clk_disable ( fclk ) ;
2006-04-02 20:26:21 -08:00
clk_disable ( iclk ) ;
2006-01-22 10:32:13 -08:00
return 0 ;
}
2006-04-02 20:26:21 -08:00
static int ohci_hcd_at91_drv_resume ( struct platform_device * dev )
2006-01-22 10:32:13 -08:00
{
2006-04-02 20:26:21 -08:00
clk_enable ( iclk ) ;
2006-01-22 10:32:13 -08:00
clk_enable ( fclk ) ;
return 0 ;
}
# else
# define ohci_hcd_at91_drv_suspend NULL
# define ohci_hcd_at91_drv_resume NULL
# endif
2006-04-02 20:26:21 -08:00
MODULE_ALIAS ( " at91rm9200-ohci " ) ;
2006-01-22 10:32:13 -08:00
static struct platform_driver ohci_hcd_at91_driver = {
. probe = ohci_hcd_at91_drv_probe ,
. remove = ohci_hcd_at91_drv_remove ,
. suspend = ohci_hcd_at91_drv_suspend ,
. resume = ohci_hcd_at91_drv_resume ,
. driver = {
. name = " at91rm9200-ohci " ,
. owner = THIS_MODULE ,
} ,
} ;
static int __init ohci_hcd_at91_init ( void )
{
if ( usb_disabled ( ) )
return - ENODEV ;
return platform_driver_register ( & ohci_hcd_at91_driver ) ;
}
static void __exit ohci_hcd_at91_cleanup ( void )
{
platform_driver_unregister ( & ohci_hcd_at91_driver ) ;
}
module_init ( ohci_hcd_at91_init ) ;
module_exit ( ohci_hcd_at91_cleanup ) ;