2010-05-27 01:42:10 +04:00
/*
* drivers / mmc / host / sdhci - spear . c
*
* Support of SDHCI platform devices for spear soc family
*
* Copyright ( C ) 2010 ST Microelectronics
2012-06-20 23:53:02 +04:00
* Viresh Kumar < viresh . linux @ gmail . com >
2010-05-27 01:42:10 +04:00
*
* Inspired by sdhci - pltfm . c
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed " as is " without any
* warranty of any kind , whether express or implied .
*/
# include <linux/clk.h>
# include <linux/delay.h>
# include <linux/gpio.h>
# include <linux/highmem.h>
2011-07-03 23:15:51 +04:00
# include <linux/module.h>
2010-05-27 01:42:10 +04:00
# include <linux/interrupt.h>
# include <linux/irq.h>
2012-09-28 14:28:22 +04:00
# include <linux/of.h>
# include <linux/of_gpio.h>
2010-05-27 01:42:10 +04:00
# include <linux/platform_device.h>
2011-11-15 14:54:40 +04:00
# include <linux/pm.h>
2010-05-27 01:42:10 +04:00
# include <linux/slab.h>
# include <linux/mmc/host.h>
# include <linux/mmc/sdhci-spear.h>
# include <linux/io.h>
# include "sdhci.h"
struct spear_sdhci {
struct clk * clk ;
struct sdhci_plat_data * data ;
} ;
/* sdhci ops */
2013-03-13 22:26:05 +04:00
static const struct sdhci_ops sdhci_pltfm_ops = {
2010-05-27 01:42:10 +04:00
/* Nothing to do for now. */
} ;
/* gpio card detection interrupt handler */
static irqreturn_t sdhci_gpio_irq ( int irq , void * dev_id )
{
struct platform_device * pdev = dev_id ;
struct sdhci_host * host = platform_get_drvdata ( pdev ) ;
struct spear_sdhci * sdhci = dev_get_platdata ( & pdev - > dev ) ;
unsigned long gpio_irq_type ;
int val ;
val = gpio_get_value ( sdhci - > data - > card_int_gpio ) ;
/* val == 1 -> card removed, val == 0 -> card inserted */
/* if card removed - set irq for low level, else vice versa */
gpio_irq_type = val ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH ;
2011-03-28 19:49:12 +04:00
irq_set_irq_type ( irq , gpio_irq_type ) ;
2010-05-27 01:42:10 +04:00
if ( sdhci - > data - > card_power_gpio > = 0 ) {
if ( ! sdhci - > data - > power_always_enb ) {
/* if card inserted, give power, otherwise remove it */
val = sdhci - > data - > power_active_high ? ! val : val ;
gpio_set_value ( sdhci - > data - > card_power_gpio , val ) ;
}
}
/* inform sdhci driver about card insertion/removal */
tasklet_schedule ( & host - > card_tasklet ) ;
return IRQ_HANDLED ;
}
2012-09-28 14:28:22 +04:00
# ifdef CONFIG_OF
2012-11-19 22:23:06 +04:00
static struct sdhci_plat_data * sdhci_probe_config_dt ( struct platform_device * pdev )
2012-09-28 14:28:22 +04:00
{
struct device_node * np = pdev - > dev . of_node ;
struct sdhci_plat_data * pdata = NULL ;
int cd_gpio ;
cd_gpio = of_get_named_gpio ( np , " cd-gpios " , 0 ) ;
if ( ! gpio_is_valid ( cd_gpio ) )
cd_gpio = - 1 ;
/* If pdata is required */
if ( cd_gpio ! = - 1 ) {
pdata = devm_kzalloc ( & pdev - > dev , sizeof ( * pdata ) , GFP_KERNEL ) ;
if ( ! pdata ) {
dev_err ( & pdev - > dev , " DT: kzalloc failed \n " ) ;
return ERR_PTR ( - ENOMEM ) ;
}
}
pdata - > card_int_gpio = cd_gpio ;
return pdata ;
}
# else
2012-11-19 22:23:06 +04:00
static struct sdhci_plat_data * sdhci_probe_config_dt ( struct platform_device * pdev )
2012-09-28 14:28:22 +04:00
{
return ERR_PTR ( - ENOSYS ) ;
}
# endif
2012-11-19 22:23:06 +04:00
static int sdhci_probe ( struct platform_device * pdev )
2010-05-27 01:42:10 +04:00
{
2012-09-28 14:28:22 +04:00
struct device_node * np = pdev - > dev . of_node ;
2010-05-27 01:42:10 +04:00
struct sdhci_host * host ;
struct resource * iomem ;
struct spear_sdhci * sdhci ;
int ret ;
iomem = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! iomem ) {
ret = - ENOMEM ;
dev_dbg ( & pdev - > dev , " memory resource not defined \n " ) ;
goto err ;
}
2012-03-27 07:10:35 +04:00
if ( ! devm_request_mem_region ( & pdev - > dev , iomem - > start ,
resource_size ( iomem ) , " spear-sdhci " ) ) {
2010-05-27 01:42:10 +04:00
ret = - EBUSY ;
dev_dbg ( & pdev - > dev , " cannot request region \n " ) ;
goto err ;
}
2012-03-27 07:10:35 +04:00
sdhci = devm_kzalloc ( & pdev - > dev , sizeof ( * sdhci ) , GFP_KERNEL ) ;
2010-05-27 01:42:10 +04:00
if ( ! sdhci ) {
ret = - ENOMEM ;
dev_dbg ( & pdev - > dev , " cannot allocate memory for sdhci \n " ) ;
2012-03-27 07:10:35 +04:00
goto err ;
2010-05-27 01:42:10 +04:00
}
/* clk enable */
sdhci - > clk = clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( sdhci - > clk ) ) {
ret = PTR_ERR ( sdhci - > clk ) ;
dev_dbg ( & pdev - > dev , " Error getting clock \n " ) ;
2012-03-27 07:10:35 +04:00
goto err ;
2010-05-27 01:42:10 +04:00
}
2012-09-28 14:28:23 +04:00
ret = clk_prepare_enable ( sdhci - > clk ) ;
2010-05-27 01:42:10 +04:00
if ( ret ) {
dev_dbg ( & pdev - > dev , " Error enabling clock \n " ) ;
2012-03-27 07:10:35 +04:00
goto put_clk ;
2010-05-27 01:42:10 +04:00
}
2012-11-08 19:09:09 +04:00
ret = clk_set_rate ( sdhci - > clk , 50000000 ) ;
if ( ret )
dev_dbg ( & pdev - > dev , " Error setting desired clk, clk=%lu \n " ,
clk_get_rate ( sdhci - > clk ) ) ;
2012-09-28 14:28:22 +04:00
if ( np ) {
sdhci - > data = sdhci_probe_config_dt ( pdev ) ;
if ( IS_ERR ( sdhci - > data ) ) {
dev_err ( & pdev - > dev , " DT: Failed to get pdata \n " ) ;
return - ENODEV ;
}
} else {
sdhci - > data = dev_get_platdata ( & pdev - > dev ) ;
}
2010-05-27 01:42:10 +04:00
pdev - > dev . platform_data = sdhci ;
if ( pdev - > dev . parent )
host = sdhci_alloc_host ( pdev - > dev . parent , 0 ) ;
else
host = sdhci_alloc_host ( & pdev - > dev , 0 ) ;
if ( IS_ERR ( host ) ) {
ret = PTR_ERR ( host ) ;
dev_dbg ( & pdev - > dev , " error allocating host \n " ) ;
2012-03-27 07:10:35 +04:00
goto disable_clk ;
2010-05-27 01:42:10 +04:00
}
host - > hw_name = " sdhci " ;
host - > ops = & sdhci_pltfm_ops ;
host - > irq = platform_get_irq ( pdev , 0 ) ;
host - > quirks = SDHCI_QUIRK_BROKEN_ADMA ;
2012-03-27 07:10:35 +04:00
host - > ioaddr = devm_ioremap ( & pdev - > dev , iomem - > start ,
resource_size ( iomem ) ) ;
2010-05-27 01:42:10 +04:00
if ( ! host - > ioaddr ) {
ret = - ENOMEM ;
dev_dbg ( & pdev - > dev , " failed to remap registers \n " ) ;
2012-03-27 07:10:35 +04:00
goto free_host ;
2010-05-27 01:42:10 +04:00
}
ret = sdhci_add_host ( host ) ;
if ( ret ) {
dev_dbg ( & pdev - > dev , " error adding host \n " ) ;
2012-03-27 07:10:35 +04:00
goto free_host ;
2010-05-27 01:42:10 +04:00
}
platform_set_drvdata ( pdev , host ) ;
/*
* It is optional to use GPIOs for sdhci Power control & sdhci card
* interrupt detection . If sdhci - > data is NULL , then use original sdhci
* lines otherwise GPIO lines .
* If GPIO is selected for power control , then power should be disabled
* after card removal and should be enabled when card insertion
* interrupt occurs
*/
if ( ! sdhci - > data )
return 0 ;
if ( sdhci - > data - > card_power_gpio > = 0 ) {
int val = 0 ;
2012-03-27 07:10:35 +04:00
ret = devm_gpio_request ( & pdev - > dev ,
sdhci - > data - > card_power_gpio , " sdhci " ) ;
2010-05-27 01:42:10 +04:00
if ( ret < 0 ) {
dev_dbg ( & pdev - > dev , " gpio request fail: %d \n " ,
sdhci - > data - > card_power_gpio ) ;
2012-03-27 07:10:35 +04:00
goto set_drvdata ;
2010-05-27 01:42:10 +04:00
}
if ( sdhci - > data - > power_always_enb )
val = sdhci - > data - > power_active_high ;
else
val = ! sdhci - > data - > power_active_high ;
ret = gpio_direction_output ( sdhci - > data - > card_power_gpio , val ) ;
if ( ret ) {
dev_dbg ( & pdev - > dev , " gpio set direction fail: %d \n " ,
sdhci - > data - > card_power_gpio ) ;
2012-03-27 07:10:35 +04:00
goto set_drvdata ;
2010-05-27 01:42:10 +04:00
}
}
if ( sdhci - > data - > card_int_gpio > = 0 ) {
2012-03-27 07:10:35 +04:00
ret = devm_gpio_request ( & pdev - > dev , sdhci - > data - > card_int_gpio ,
" sdhci " ) ;
2010-05-27 01:42:10 +04:00
if ( ret < 0 ) {
dev_dbg ( & pdev - > dev , " gpio request fail: %d \n " ,
sdhci - > data - > card_int_gpio ) ;
2012-03-27 07:10:35 +04:00
goto set_drvdata ;
2010-05-27 01:42:10 +04:00
}
ret = gpio_direction_input ( sdhci - > data - > card_int_gpio ) ;
if ( ret ) {
dev_dbg ( & pdev - > dev , " gpio set direction fail: %d \n " ,
sdhci - > data - > card_int_gpio ) ;
2012-03-27 07:10:35 +04:00
goto set_drvdata ;
2010-05-27 01:42:10 +04:00
}
2012-03-27 07:10:35 +04:00
ret = devm_request_irq ( & pdev - > dev ,
gpio_to_irq ( sdhci - > data - > card_int_gpio ) ,
2010-05-27 01:42:10 +04:00
sdhci_gpio_irq , IRQF_TRIGGER_LOW ,
mmc_hostname ( host - > mmc ) , pdev ) ;
if ( ret ) {
dev_dbg ( & pdev - > dev , " gpio request irq fail: %d \n " ,
sdhci - > data - > card_int_gpio ) ;
2012-03-27 07:10:35 +04:00
goto set_drvdata ;
2010-05-27 01:42:10 +04:00
}
}
return 0 ;
2012-03-27 07:10:35 +04:00
set_drvdata :
2010-05-27 01:42:10 +04:00
sdhci_remove_host ( host , 1 ) ;
2012-03-27 07:10:35 +04:00
free_host :
2010-05-27 01:42:10 +04:00
sdhci_free_host ( host ) ;
2012-03-27 07:10:35 +04:00
disable_clk :
2012-09-28 14:28:23 +04:00
clk_disable_unprepare ( sdhci - > clk ) ;
2012-03-27 07:10:35 +04:00
put_clk :
2010-05-27 01:42:10 +04:00
clk_put ( sdhci - > clk ) ;
err :
dev_err ( & pdev - > dev , " spear-sdhci probe failed: %d \n " , ret ) ;
return ret ;
}
2012-11-19 22:26:03 +04:00
static int sdhci_remove ( struct platform_device * pdev )
2010-05-27 01:42:10 +04:00
{
struct sdhci_host * host = platform_get_drvdata ( pdev ) ;
struct spear_sdhci * sdhci = dev_get_platdata ( & pdev - > dev ) ;
2012-03-27 07:10:35 +04:00
int dead = 0 ;
2010-05-27 01:42:10 +04:00
u32 scratch ;
scratch = readl ( host - > ioaddr + SDHCI_INT_STATUS ) ;
if ( scratch = = ( u32 ) - 1 )
dead = 1 ;
sdhci_remove_host ( host , dead ) ;
sdhci_free_host ( host ) ;
2012-09-28 14:28:23 +04:00
clk_disable_unprepare ( sdhci - > clk ) ;
2010-05-27 01:42:10 +04:00
clk_put ( sdhci - > clk ) ;
return 0 ;
}
2013-03-29 11:08:00 +04:00
# ifdef CONFIG_PM_SLEEP
2011-11-15 14:54:40 +04:00
static int sdhci_suspend ( struct device * dev )
{
struct sdhci_host * host = dev_get_drvdata ( dev ) ;
struct spear_sdhci * sdhci = dev_get_platdata ( dev ) ;
int ret ;
2012-01-04 10:18:42 +04:00
ret = sdhci_suspend_host ( host ) ;
2011-11-15 14:54:40 +04:00
if ( ! ret )
2012-11-08 19:09:10 +04:00
clk_disable ( sdhci - > clk ) ;
2011-11-15 14:54:40 +04:00
return ret ;
}
static int sdhci_resume ( struct device * dev )
{
struct sdhci_host * host = dev_get_drvdata ( dev ) ;
struct spear_sdhci * sdhci = dev_get_platdata ( dev ) ;
int ret ;
2012-11-08 19:09:10 +04:00
ret = clk_enable ( sdhci - > clk ) ;
2011-11-15 14:54:40 +04:00
if ( ret ) {
dev_dbg ( dev , " Resume: Error enabling clock \n " ) ;
return ret ;
}
return sdhci_resume_host ( host ) ;
}
# endif
2012-02-24 10:25:35 +04:00
static SIMPLE_DEV_PM_OPS ( sdhci_pm_ops , sdhci_suspend , sdhci_resume ) ;
2012-09-28 14:28:22 +04:00
# ifdef CONFIG_OF
static const struct of_device_id sdhci_spear_id_table [ ] = {
{ . compatible = " st,spear300-sdhci " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , sdhci_spear_id_table ) ;
# endif
2010-05-27 01:42:10 +04:00
static struct platform_driver sdhci_driver = {
. driver = {
. name = " sdhci " ,
. owner = THIS_MODULE ,
2011-11-15 14:54:40 +04:00
. pm = & sdhci_pm_ops ,
2012-09-28 14:28:22 +04:00
. of_match_table = of_match_ptr ( sdhci_spear_id_table ) ,
2010-05-27 01:42:10 +04:00
} ,
. probe = sdhci_probe ,
2012-11-19 22:20:26 +04:00
. remove = sdhci_remove ,
2010-05-27 01:42:10 +04:00
} ;
2011-11-26 08:55:43 +04:00
module_platform_driver ( sdhci_driver ) ;
2010-05-27 01:42:10 +04:00
MODULE_DESCRIPTION ( " SPEAr Secure Digital Host Controller Interface driver " ) ;
2012-06-20 23:53:02 +04:00
MODULE_AUTHOR ( " Viresh Kumar <viresh.linux@gmail.com> " ) ;
2010-05-27 01:42:10 +04:00
MODULE_LICENSE ( " GPL v2 " ) ;