2013-12-02 10:02:36 -08:00
/*
* Arasan Secure Digital Host Controller Interface .
* Copyright ( C ) 2011 - 2012 Michal Simek < monstr @ monstr . eu >
* Copyright ( c ) 2012 Wind River Systems , Inc .
* Copyright ( C ) 2013 Pengutronix e . K .
* Copyright ( C ) 2013 Xilinx Inc .
*
* Based on sdhci - of - esdhc . c
*
* Copyright ( c ) 2007 Freescale Semiconductor , Inc .
* Copyright ( c ) 2009 MontaVista Software , Inc .
*
* Authors : Xiaobo Xie < X . Xie @ freescale . com >
* Anton Vorontsov < avorontsov @ ru . mvista . 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/module.h>
2015-05-04 19:09:51 +05:30
# include <linux/of_device.h>
2013-12-02 10:02:36 -08:00
# include "sdhci-pltfm.h"
# define SDHCI_ARASAN_CLK_CTRL_OFFSET 0x2c
# define CLK_CTRL_TIMEOUT_SHIFT 16
# define CLK_CTRL_TIMEOUT_MASK (0xf << CLK_CTRL_TIMEOUT_SHIFT)
# define CLK_CTRL_TIMEOUT_MIN_EXP 13
/**
* struct sdhci_arasan_data
* @ clk_ahb : Pointer to the AHB clock
*/
struct sdhci_arasan_data {
struct clk * clk_ahb ;
} ;
static unsigned int sdhci_arasan_get_timeout_clock ( struct sdhci_host * host )
{
u32 div ;
unsigned long freq ;
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
div = readl ( host - > ioaddr + SDHCI_ARASAN_CLK_CTRL_OFFSET ) ;
div = ( div & CLK_CTRL_TIMEOUT_MASK ) > > CLK_CTRL_TIMEOUT_SHIFT ;
freq = clk_get_rate ( pltfm_host - > clk ) ;
freq / = 1 < < ( CLK_CTRL_TIMEOUT_MIN_EXP + div ) ;
return freq ;
}
static struct sdhci_ops sdhci_arasan_ops = {
2014-04-25 12:58:55 +01:00
. set_clock = sdhci_set_clock ,
2013-12-02 10:02:36 -08:00
. get_max_clock = sdhci_pltfm_clk_get_max_clock ,
. get_timeout_clock = sdhci_arasan_get_timeout_clock ,
2014-04-25 12:57:07 +01:00
. set_bus_width = sdhci_set_bus_width ,
2014-04-25 12:57:12 +01:00
. reset = sdhci_reset ,
2014-04-25 12:59:26 +01:00
. set_uhs_signaling = sdhci_set_uhs_signaling ,
2013-12-02 10:02:36 -08:00
} ;
static struct sdhci_pltfm_data sdhci_arasan_pdata = {
. ops = & sdhci_arasan_ops ,
2015-06-09 13:01:51 +05:30
. quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN ,
. quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN ,
2013-12-02 10:02:36 -08:00
} ;
# ifdef CONFIG_PM_SLEEP
/**
* sdhci_arasan_suspend - Suspend method for the driver
* @ dev : Address of the device structure
* Returns 0 on success and error value on error
*
* Put the device in a low power state .
*/
static int sdhci_arasan_suspend ( struct device * dev )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
struct sdhci_host * host = platform_get_drvdata ( pdev ) ;
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
struct sdhci_arasan_data * sdhci_arasan = pltfm_host - > priv ;
int ret ;
ret = sdhci_suspend_host ( host ) ;
if ( ret )
return ret ;
clk_disable ( pltfm_host - > clk ) ;
clk_disable ( sdhci_arasan - > clk_ahb ) ;
return 0 ;
}
/**
* sdhci_arasan_resume - Resume method for the driver
* @ dev : Address of the device structure
* Returns 0 on success and error value on error
*
* Resume operation after suspend
*/
static int sdhci_arasan_resume ( struct device * dev )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
struct sdhci_host * host = platform_get_drvdata ( pdev ) ;
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
struct sdhci_arasan_data * sdhci_arasan = pltfm_host - > priv ;
int ret ;
ret = clk_enable ( sdhci_arasan - > clk_ahb ) ;
if ( ret ) {
dev_err ( dev , " Cannot enable AHB clock. \n " ) ;
return ret ;
}
ret = clk_enable ( pltfm_host - > clk ) ;
if ( ret ) {
dev_err ( dev , " Cannot enable SD clock. \n " ) ;
clk_disable ( sdhci_arasan - > clk_ahb ) ;
return ret ;
}
return sdhci_resume_host ( host ) ;
}
# endif /* ! CONFIG_PM_SLEEP */
static SIMPLE_DEV_PM_OPS ( sdhci_arasan_dev_pm_ops , sdhci_arasan_suspend ,
sdhci_arasan_resume ) ;
static int sdhci_arasan_probe ( struct platform_device * pdev )
{
int ret ;
struct clk * clk_xin ;
struct sdhci_host * host ;
struct sdhci_pltfm_host * pltfm_host ;
struct sdhci_arasan_data * sdhci_arasan ;
sdhci_arasan = devm_kzalloc ( & pdev - > dev , sizeof ( * sdhci_arasan ) ,
GFP_KERNEL ) ;
if ( ! sdhci_arasan )
return - ENOMEM ;
sdhci_arasan - > clk_ahb = devm_clk_get ( & pdev - > dev , " clk_ahb " ) ;
if ( IS_ERR ( sdhci_arasan - > clk_ahb ) ) {
dev_err ( & pdev - > dev , " clk_ahb clock not found. \n " ) ;
return PTR_ERR ( sdhci_arasan - > clk_ahb ) ;
}
clk_xin = devm_clk_get ( & pdev - > dev , " clk_xin " ) ;
if ( IS_ERR ( clk_xin ) ) {
dev_err ( & pdev - > dev , " clk_xin clock not found. \n " ) ;
return PTR_ERR ( clk_xin ) ;
}
ret = clk_prepare_enable ( sdhci_arasan - > clk_ahb ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Unable to enable AHB clock. \n " ) ;
return ret ;
}
ret = clk_prepare_enable ( clk_xin ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Unable to enable SD clock. \n " ) ;
goto clk_dis_ahb ;
}
host = sdhci_pltfm_init ( pdev , & sdhci_arasan_pdata , 0 ) ;
if ( IS_ERR ( host ) ) {
ret = PTR_ERR ( host ) ;
goto clk_disable_all ;
}
2015-05-04 19:09:51 +05:30
if ( of_device_is_compatible ( pdev - > dev . of_node , " arasan,sdhci-4.9a " ) ) {
host - > quirks | = SDHCI_QUIRK_NO_HISPD_BIT ;
host - > quirks2 | = SDHCI_QUIRK2_HOST_NO_CMD23 ;
}
2013-12-02 10:02:36 -08:00
sdhci_get_of_property ( pdev ) ;
pltfm_host = sdhci_priv ( host ) ;
pltfm_host - > priv = sdhci_arasan ;
pltfm_host - > clk = clk_xin ;
2015-04-07 07:57:32 +02:00
ret = mmc_of_parse ( host - > mmc ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " parsing dt failed (%u) \n " , ret ) ;
goto clk_disable_all ;
}
2013-12-02 10:02:36 -08:00
ret = sdhci_add_host ( host ) ;
2014-10-28 08:53:21 +01:00
if ( ret )
2013-12-02 10:02:36 -08:00
goto err_pltfm_free ;
return 0 ;
err_pltfm_free :
sdhci_pltfm_free ( pdev ) ;
clk_disable_all :
clk_disable_unprepare ( clk_xin ) ;
clk_dis_ahb :
clk_disable_unprepare ( sdhci_arasan - > clk_ahb ) ;
return ret ;
}
static int sdhci_arasan_remove ( struct platform_device * pdev )
{
struct sdhci_host * host = platform_get_drvdata ( pdev ) ;
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
struct sdhci_arasan_data * sdhci_arasan = pltfm_host - > priv ;
clk_disable_unprepare ( sdhci_arasan - > clk_ahb ) ;
return sdhci_pltfm_unregister ( pdev ) ;
}
static const struct of_device_id sdhci_arasan_of_match [ ] = {
{ . compatible = " arasan,sdhci-8.9a " } ,
2015-08-11 15:57:05 +08:00
{ . compatible = " arasan,sdhci-5.1 " } ,
2015-05-04 19:09:51 +05:30
{ . compatible = " arasan,sdhci-4.9a " } ,
2013-12-02 10:02:36 -08:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , sdhci_arasan_of_match ) ;
static struct platform_driver sdhci_arasan_driver = {
. driver = {
. name = " sdhci-arasan " ,
. of_match_table = sdhci_arasan_of_match ,
. pm = & sdhci_arasan_dev_pm_ops ,
} ,
. probe = sdhci_arasan_probe ,
. remove = sdhci_arasan_remove ,
} ;
module_platform_driver ( sdhci_arasan_driver ) ;
MODULE_DESCRIPTION ( " Driver for the Arasan SDHCI Controller " ) ;
MODULE_AUTHOR ( " Soeren Brinkmann <soren.brinkmann@xilinx.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;