2017-11-03 11:28:30 +01:00
// SPDX-License-Identifier: GPL-2.0
2009-07-27 14:47:40 -07:00
/*
* Driver for EHCI UHP on Atmel chips
*
* Copyright ( C ) 2009 Atmel Corporation ,
* Nicolas Ferre < nicolas . ferre @ atmel . com >
*
* Based on various ehci - * . c drivers
*/
# include <linux/clk.h>
2013-04-02 18:24:02 +02:00
# include <linux/dma-mapping.h>
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/module.h>
2012-03-27 18:23:31 +02:00
# include <linux/of.h>
2011-11-22 12:11:13 +08:00
# include <linux/of_platform.h>
2013-04-02 18:24:02 +02:00
# include <linux/platform_device.h>
# include <linux/usb.h>
# include <linux/usb/hcd.h>
2021-09-10 19:38:42 +03:00
# include <linux/usb/phy.h>
# include <linux/usb/of.h>
2013-04-02 18:24:02 +02:00
# include "ehci.h"
# define DRIVER_DESC "EHCI Atmel driver"
2021-09-10 19:38:42 +03:00
# define EHCI_INSNREG(index) ((index) * 4 + 0x90)
# define EHCI_INSNREG08_HSIC_EN BIT(2)
2009-07-27 14:47:40 -07:00
/* interface and function clocks */
2015-01-20 14:39:01 +01:00
# define hcd_to_atmel_ehci_priv(h) \
( ( struct atmel_ehci_priv * ) hcd_to_ehci ( h ) - > priv )
struct atmel_ehci_priv {
struct clk * iclk ;
struct clk * uclk ;
bool clocked ;
} ;
static struct hc_driver __read_mostly ehci_atmel_hc_driver ;
static const struct ehci_driver_overrides ehci_atmel_drv_overrides __initconst = {
. extra_priv_size = sizeof ( struct atmel_ehci_priv ) ,
} ;
2009-07-27 14:47:40 -07:00
/*-------------------------------------------------------------------------*/
2015-01-20 14:39:01 +01:00
static void atmel_start_clock ( struct atmel_ehci_priv * atmel_ehci )
2009-07-27 14:47:40 -07:00
{
2015-01-20 14:39:01 +01:00
if ( atmel_ehci - > clocked )
2015-01-20 14:38:59 +01:00
return ;
2015-03-17 17:15:46 +01:00
clk_prepare_enable ( atmel_ehci - > uclk ) ;
2015-01-20 14:39:01 +01:00
clk_prepare_enable ( atmel_ehci - > iclk ) ;
atmel_ehci - > clocked = true ;
2009-07-27 14:47:40 -07:00
}
2015-01-20 14:39:01 +01:00
static void atmel_stop_clock ( struct atmel_ehci_priv * atmel_ehci )
2009-07-27 14:47:40 -07:00
{
2015-01-20 14:39:01 +01:00
if ( ! atmel_ehci - > clocked )
2015-01-20 14:38:59 +01:00
return ;
2015-03-17 17:15:46 +01:00
2015-01-20 14:39:01 +01:00
clk_disable_unprepare ( atmel_ehci - > iclk ) ;
2015-03-17 17:15:46 +01:00
clk_disable_unprepare ( atmel_ehci - > uclk ) ;
2015-01-20 14:39:01 +01:00
atmel_ehci - > clocked = false ;
2009-07-27 14:47:40 -07:00
}
static void atmel_start_ehci ( struct platform_device * pdev )
{
2015-01-20 14:39:01 +01:00
struct usb_hcd * hcd = platform_get_drvdata ( pdev ) ;
struct atmel_ehci_priv * atmel_ehci = hcd_to_atmel_ehci_priv ( hcd ) ;
2009-07-27 14:47:40 -07:00
dev_dbg ( & pdev - > dev , " start \n " ) ;
2015-01-20 14:39:01 +01:00
atmel_start_clock ( atmel_ehci ) ;
2009-07-27 14:47:40 -07:00
}
static void atmel_stop_ehci ( struct platform_device * pdev )
{
2015-01-20 14:39:01 +01:00
struct usb_hcd * hcd = platform_get_drvdata ( pdev ) ;
struct atmel_ehci_priv * atmel_ehci = hcd_to_atmel_ehci_priv ( hcd ) ;
2009-07-27 14:47:40 -07:00
dev_dbg ( & pdev - > dev , " stop \n " ) ;
2015-01-20 14:39:01 +01:00
atmel_stop_clock ( atmel_ehci ) ;
2009-07-27 14:47:40 -07:00
}
/*-------------------------------------------------------------------------*/
2012-11-19 13:21:48 -05:00
static int ehci_atmel_drv_probe ( struct platform_device * pdev )
2009-07-27 14:47:40 -07:00
{
struct usb_hcd * hcd ;
const struct hc_driver * driver = & ehci_atmel_hc_driver ;
struct resource * res ;
2013-04-02 18:24:02 +02:00
struct ehci_hcd * ehci ;
2015-01-20 14:39:01 +01:00
struct atmel_ehci_priv * atmel_ehci ;
2009-07-27 14:47:40 -07:00
int irq ;
int retval ;
if ( usb_disabled ( ) )
return - ENODEV ;
pr_debug ( " Initializing Atmel-SoC USB Host Controller \n " ) ;
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < = 0 ) {
retval = - ENODEV ;
goto fail_create_hcd ;
}
2011-11-22 12:11:13 +08: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
retval = dma_coerce_mask_and_coherent ( & pdev - > dev , DMA_BIT_MASK ( 32 ) ) ;
2013-06-10 16:28:49 +01:00
if ( retval )
goto fail_create_hcd ;
2011-11-22 12:11:13 +08:00
2009-07-27 14:47:40 -07:00
hcd = usb_create_hcd ( driver , & pdev - > dev , dev_name ( & pdev - > dev ) ) ;
if ( ! hcd ) {
retval = - ENOMEM ;
goto fail_create_hcd ;
}
2015-01-20 14:39:01 +01:00
atmel_ehci = hcd_to_atmel_ehci_priv ( hcd ) ;
2009-07-27 14:47:40 -07:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2013-01-21 11:09:22 +01:00
hcd - > regs = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( hcd - > regs ) ) {
retval = PTR_ERR ( hcd - > regs ) ;
2012-07-29 21:46:05 +02:00
goto fail_request_resource ;
2009-07-27 14:47:40 -07:00
}
2014-11-04 07:51:08 +05:30
hcd - > rsrc_start = res - > start ;
hcd - > rsrc_len = resource_size ( res ) ;
2015-01-20 14:39:01 +01:00
atmel_ehci - > iclk = devm_clk_get ( & pdev - > dev , " ehci_clk " ) ;
if ( IS_ERR ( atmel_ehci - > iclk ) ) {
2009-07-27 14:47:40 -07:00
dev_err ( & pdev - > dev , " Error getting interface clock \n " ) ;
retval = - ENOENT ;
2012-07-29 21:46:05 +02:00
goto fail_request_resource ;
2009-07-27 14:47:40 -07:00
}
2015-03-17 17:15:46 +01:00
atmel_ehci - > uclk = devm_clk_get ( & pdev - > dev , " usb_clk " ) ;
if ( IS_ERR ( atmel_ehci - > uclk ) ) {
dev_err ( & pdev - > dev , " failed to get uclk \n " ) ;
retval = PTR_ERR ( atmel_ehci - > uclk ) ;
2012-07-29 21:46:05 +02:00
goto fail_request_resource ;
2009-07-27 14:47:40 -07:00
}
2013-04-02 18:24:02 +02:00
ehci = hcd_to_ehci ( hcd ) ;
/* registers start at offset 0x0 */
ehci - > caps = hcd - > regs ;
2009-07-27 14:47:40 -07:00
atmel_start_ehci ( pdev ) ;
retval = usb_add_hcd ( hcd , irq , IRQF_SHARED ) ;
if ( retval )
goto fail_add_hcd ;
2013-11-05 10:46:02 +08:00
device_wakeup_enable ( hcd - > self . controller ) ;
2009-07-27 14:47:40 -07:00
2021-09-10 19:38:42 +03:00
if ( of_usb_get_phy_mode ( pdev - > dev . of_node ) = = USBPHY_INTERFACE_MODE_HSIC )
writel ( EHCI_INSNREG08_HSIC_EN , hcd - > regs + EHCI_INSNREG ( 8 ) ) ;
2009-07-27 14:47:40 -07:00
return retval ;
fail_add_hcd :
atmel_stop_ehci ( pdev ) ;
fail_request_resource :
usb_put_hcd ( hcd ) ;
fail_create_hcd :
dev_err ( & pdev - > dev , " init %s fail, %d \n " ,
dev_name ( & pdev - > dev ) , retval ) ;
return retval ;
}
2012-11-19 13:26:20 -05:00
static int ehci_atmel_drv_remove ( struct platform_device * pdev )
2009-07-27 14:47:40 -07:00
{
struct usb_hcd * hcd = platform_get_drvdata ( pdev ) ;
usb_remove_hcd ( hcd ) ;
usb_put_hcd ( hcd ) ;
atmel_stop_ehci ( pdev ) ;
return 0 ;
}
2016-03-02 16:24:06 +01:00
static int __maybe_unused ehci_atmel_drv_suspend ( struct device * dev )
2015-01-20 14:38:59 +01:00
{
struct usb_hcd * hcd = dev_get_drvdata ( dev ) ;
2015-01-20 14:39:01 +01:00
struct atmel_ehci_priv * atmel_ehci = hcd_to_atmel_ehci_priv ( hcd ) ;
2015-01-20 14:38:59 +01:00
int ret ;
ret = ehci_suspend ( hcd , false ) ;
if ( ret )
return ret ;
2015-01-20 14:39:01 +01:00
atmel_stop_clock ( atmel_ehci ) ;
2015-01-20 14:38:59 +01:00
return 0 ;
}
2016-03-02 16:24:06 +01:00
static int __maybe_unused ehci_atmel_drv_resume ( struct device * dev )
2015-01-20 14:38:59 +01:00
{
struct usb_hcd * hcd = dev_get_drvdata ( dev ) ;
2015-01-20 14:39:01 +01:00
struct atmel_ehci_priv * atmel_ehci = hcd_to_atmel_ehci_priv ( hcd ) ;
2015-01-20 14:38:59 +01:00
2015-01-20 14:39:01 +01:00
atmel_start_clock ( atmel_ehci ) ;
2017-09-28 11:46:24 +02:00
ehci_resume ( hcd , false ) ;
return 0 ;
2015-01-20 14:38:59 +01:00
}
2011-11-22 12:11:13 +08:00
# ifdef CONFIG_OF
static const struct of_device_id atmel_ehci_dt_ids [ ] = {
{ . compatible = " atmel,at91sam9g45-ehci " } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , atmel_ehci_dt_ids ) ;
# endif
2015-01-20 14:38:59 +01:00
static SIMPLE_DEV_PM_OPS ( ehci_atmel_pm_ops , ehci_atmel_drv_suspend ,
ehci_atmel_drv_resume ) ;
2009-07-27 14:47:40 -07:00
static struct platform_driver ehci_atmel_driver = {
. probe = ehci_atmel_drv_probe ,
2012-11-19 13:21:08 -05:00
. remove = ehci_atmel_drv_remove ,
2009-07-27 14:47:40 -07:00
. shutdown = usb_hcd_platform_shutdown ,
2011-11-22 12:11:13 +08:00
. driver = {
. name = " atmel-ehci " ,
2015-01-20 14:38:59 +01:00
. pm = & ehci_atmel_pm_ops ,
2011-11-22 12:11:13 +08:00
. of_match_table = of_match_ptr ( atmel_ehci_dt_ids ) ,
} ,
2009-07-27 14:47:40 -07:00
} ;
2013-04-02 18:24:02 +02:00
static int __init ehci_atmel_init ( void )
{
if ( usb_disabled ( ) )
return - ENODEV ;
2015-01-20 14:39:01 +01:00
ehci_init_driver ( & ehci_atmel_hc_driver , & ehci_atmel_drv_overrides ) ;
2013-04-02 18:24:02 +02:00
return platform_driver_register ( & ehci_atmel_driver ) ;
}
module_init ( ehci_atmel_init ) ;
static void __exit ehci_atmel_cleanup ( void )
{
platform_driver_unregister ( & ehci_atmel_driver ) ;
}
module_exit ( ehci_atmel_cleanup ) ;
MODULE_DESCRIPTION ( DRIVER_DESC ) ;
MODULE_ALIAS ( " platform:atmel-ehci " ) ;
MODULE_AUTHOR ( " Nicolas Ferre " ) ;
MODULE_LICENSE ( " GPL " ) ;