2011-01-02 07:52:56 +03:00
/*
* Copyright ( C ) 2010 Google , Inc .
*
* This software is licensed under the terms of the GNU General Public
* License version 2 , as published by the Free Software Foundation , and
* may be copied , distributed , and modified under those terms .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
*/
# include <linux/err.h>
2011-07-03 23:15:51 +04:00
# include <linux/module.h>
2011-01-02 07:52:56 +03:00
# include <linux/init.h>
# include <linux/platform_device.h>
# include <linux/clk.h>
# include <linux/io.h>
2011-08-30 23:17:16 +04:00
# include <linux/of.h>
2011-08-23 22:15:33 +04:00
# include <linux/of_gpio.h>
2011-01-02 07:52:56 +03:00
# include <linux/gpio.h>
# include <linux/mmc/card.h>
# include <linux/mmc/host.h>
2011-07-03 23:15:51 +04:00
# include <linux/module.h>
2011-01-02 07:52:56 +03:00
2011-07-26 13:59:32 +04:00
# include <asm/gpio.h>
2011-09-26 22:00:02 +04:00
# include <mach/gpio-tegra.h>
2011-01-02 07:52:56 +03:00
# include <mach/sdhci.h>
# include "sdhci-pltfm.h"
static u32 tegra_sdhci_readl ( struct sdhci_host * host , int reg )
{
u32 val ;
if ( unlikely ( reg = = SDHCI_PRESENT_STATE ) ) {
/* Use wp_gpio here instead? */
val = readl ( host - > ioaddr + reg ) ;
return val | SDHCI_WRITE_PROTECT ;
}
return readl ( host - > ioaddr + reg ) ;
}
static u16 tegra_sdhci_readw ( struct sdhci_host * host , int reg )
{
if ( unlikely ( reg = = SDHCI_HOST_VERSION ) ) {
/* Erratum: Version register is invalid in HW. */
return SDHCI_SPEC_200 ;
}
return readw ( host - > ioaddr + reg ) ;
}
static void tegra_sdhci_writel ( struct sdhci_host * host , u32 val , int reg )
{
/* Seems like we're getting spurious timeout and crc errors, so
* disable signalling of them . In case of real errors software
* timers should take care of eventually detecting them .
*/
if ( unlikely ( reg = = SDHCI_SIGNAL_ENABLE ) )
val & = ~ ( SDHCI_INT_TIMEOUT | SDHCI_INT_CRC ) ;
writel ( val , host - > ioaddr + reg ) ;
if ( unlikely ( reg = = SDHCI_INT_ENABLE ) ) {
/* Erratum: Must enable block gap interrupt detection */
u8 gap_ctrl = readb ( host - > ioaddr + SDHCI_BLOCK_GAP_CONTROL ) ;
if ( val & SDHCI_INT_CARD_INT )
gap_ctrl | = 0x8 ;
else
gap_ctrl & = ~ 0x8 ;
writeb ( gap_ctrl , host - > ioaddr + SDHCI_BLOCK_GAP_CONTROL ) ;
}
}
static unsigned int tegra_sdhci_get_ro ( struct sdhci_host * sdhci )
{
2011-08-23 22:15:33 +04:00
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( sdhci ) ;
struct tegra_sdhci_platform_data * plat = pltfm_host - > priv ;
2011-01-02 07:52:56 +03:00
if ( ! gpio_is_valid ( plat - > wp_gpio ) )
return - 1 ;
return gpio_get_value ( plat - > wp_gpio ) ;
}
static irqreturn_t carddetect_irq ( int irq , void * data )
{
struct sdhci_host * sdhost = ( struct sdhci_host * ) data ;
tasklet_schedule ( & sdhost - > card_tasklet ) ;
return IRQ_HANDLED ;
} ;
static int tegra_sdhci_8bit ( struct sdhci_host * host , int bus_width )
{
2011-08-23 22:15:33 +04:00
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
struct tegra_sdhci_platform_data * plat = pltfm_host - > priv ;
2011-01-02 07:52:56 +03:00
u32 ctrl ;
ctrl = sdhci_readb ( host , SDHCI_HOST_CONTROL ) ;
if ( plat - > is_8bit & & bus_width = = MMC_BUS_WIDTH_8 ) {
ctrl & = ~ SDHCI_CTRL_4BITBUS ;
ctrl | = SDHCI_CTRL_8BITBUS ;
} else {
ctrl & = ~ SDHCI_CTRL_8BITBUS ;
if ( bus_width = = MMC_BUS_WIDTH_4 )
ctrl | = SDHCI_CTRL_4BITBUS ;
else
ctrl & = ~ SDHCI_CTRL_4BITBUS ;
}
sdhci_writeb ( host , ctrl , SDHCI_HOST_CONTROL ) ;
return 0 ;
}
2011-05-27 19:48:12 +04:00
static struct sdhci_ops tegra_sdhci_ops = {
. get_ro = tegra_sdhci_get_ro ,
. read_l = tegra_sdhci_readl ,
. read_w = tegra_sdhci_readw ,
. write_l = tegra_sdhci_writel ,
. platform_8bit_width = tegra_sdhci_8bit ,
} ;
static struct sdhci_pltfm_data sdhci_tegra_pdata = {
. quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL |
SDHCI_QUIRK_SINGLE_POWER_WRITE |
SDHCI_QUIRK_NO_HISPD_BIT |
SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC ,
. ops = & tegra_sdhci_ops ,
} ;
2011-01-02 07:52:56 +03:00
2011-08-23 22:15:33 +04:00
static const struct of_device_id sdhci_tegra_dt_match [ ] __devinitdata = {
{ . compatible = " nvidia,tegra20-sdhci " , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , sdhci_dt_ids ) ;
static struct tegra_sdhci_platform_data * __devinit sdhci_tegra_dt_parse_pdata (
struct platform_device * pdev )
{
struct tegra_sdhci_platform_data * plat ;
struct device_node * np = pdev - > dev . of_node ;
if ( ! np )
return NULL ;
plat = devm_kzalloc ( & pdev - > dev , sizeof ( * plat ) , GFP_KERNEL ) ;
if ( ! plat ) {
dev_err ( & pdev - > dev , " Can't allocate platform data \n " ) ;
return NULL ;
}
plat - > cd_gpio = of_get_named_gpio ( np , " cd-gpios " , 0 ) ;
plat - > wp_gpio = of_get_named_gpio ( np , " wp-gpios " , 0 ) ;
plat - > power_gpio = of_get_named_gpio ( np , " power-gpios " , 0 ) ;
2011-08-30 23:17:16 +04:00
if ( of_find_property ( np , " support-8bit " , NULL ) )
plat - > is_8bit = 1 ;
2011-08-23 22:15:33 +04:00
return plat ;
}
2011-05-27 19:48:12 +04:00
static int __devinit sdhci_tegra_probe ( struct platform_device * pdev )
2011-01-02 07:52:56 +03:00
{
2011-05-27 19:48:12 +04:00
struct sdhci_pltfm_host * pltfm_host ;
2011-01-02 07:52:56 +03:00
struct tegra_sdhci_platform_data * plat ;
2011-05-27 19:48:12 +04:00
struct sdhci_host * host ;
2011-01-02 07:52:56 +03:00
struct clk * clk ;
int rc ;
2011-05-27 19:48:12 +04:00
host = sdhci_pltfm_init ( pdev , & sdhci_tegra_pdata ) ;
if ( IS_ERR ( host ) )
return PTR_ERR ( host ) ;
pltfm_host = sdhci_priv ( host ) ;
2011-01-02 07:52:56 +03:00
plat = pdev - > dev . platform_data ;
2011-05-27 19:48:12 +04:00
2011-08-23 22:15:33 +04:00
if ( plat = = NULL )
plat = sdhci_tegra_dt_parse_pdata ( pdev ) ;
2011-01-02 07:52:56 +03:00
if ( plat = = NULL ) {
dev_err ( mmc_dev ( host - > mmc ) , " missing platform data \n " ) ;
2011-05-27 19:48:12 +04:00
rc = - ENXIO ;
goto err_no_plat ;
2011-01-02 07:52:56 +03:00
}
2011-08-23 22:15:33 +04:00
pltfm_host - > priv = plat ;
2011-01-02 07:52:56 +03:00
if ( gpio_is_valid ( plat - > power_gpio ) ) {
rc = gpio_request ( plat - > power_gpio , " sdhci_power " ) ;
if ( rc ) {
dev_err ( mmc_dev ( host - > mmc ) ,
" failed to allocate power gpio \n " ) ;
2011-05-27 19:48:12 +04:00
goto err_power_req ;
2011-01-02 07:52:56 +03:00
}
tegra_gpio_enable ( plat - > power_gpio ) ;
gpio_direction_output ( plat - > power_gpio , 1 ) ;
}
if ( gpio_is_valid ( plat - > cd_gpio ) ) {
rc = gpio_request ( plat - > cd_gpio , " sdhci_cd " ) ;
if ( rc ) {
dev_err ( mmc_dev ( host - > mmc ) ,
" failed to allocate cd gpio \n " ) ;
2011-05-27 19:48:12 +04:00
goto err_cd_req ;
2011-01-02 07:52:56 +03:00
}
tegra_gpio_enable ( plat - > cd_gpio ) ;
gpio_direction_input ( plat - > cd_gpio ) ;
rc = request_irq ( gpio_to_irq ( plat - > cd_gpio ) , carddetect_irq ,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING ,
mmc_hostname ( host - > mmc ) , host ) ;
if ( rc ) {
dev_err ( mmc_dev ( host - > mmc ) , " request irq error \n " ) ;
2011-05-27 19:48:12 +04:00
goto err_cd_irq_req ;
2011-01-02 07:52:56 +03:00
}
}
if ( gpio_is_valid ( plat - > wp_gpio ) ) {
rc = gpio_request ( plat - > wp_gpio , " sdhci_wp " ) ;
if ( rc ) {
dev_err ( mmc_dev ( host - > mmc ) ,
" failed to allocate wp gpio \n " ) ;
2011-05-27 19:48:12 +04:00
goto err_wp_req ;
2011-01-02 07:52:56 +03:00
}
tegra_gpio_enable ( plat - > wp_gpio ) ;
gpio_direction_input ( plat - > wp_gpio ) ;
}
clk = clk_get ( mmc_dev ( host - > mmc ) , NULL ) ;
if ( IS_ERR ( clk ) ) {
dev_err ( mmc_dev ( host - > mmc ) , " clk err \n " ) ;
rc = PTR_ERR ( clk ) ;
2011-05-27 19:48:12 +04:00
goto err_clk_get ;
2011-01-02 07:52:56 +03:00
}
clk_enable ( clk ) ;
pltfm_host - > clk = clk ;
2011-03-26 03:37:47 +03:00
host - > mmc - > pm_caps = plat - > pm_flags ;
2011-01-02 07:52:56 +03:00
if ( plat - > is_8bit )
host - > mmc - > caps | = MMC_CAP_8_BIT_DATA ;
2011-05-27 19:48:12 +04:00
rc = sdhci_add_host ( host ) ;
if ( rc )
goto err_add_host ;
2011-01-02 07:52:56 +03:00
return 0 ;
2011-05-27 19:48:12 +04:00
err_add_host :
clk_disable ( pltfm_host - > clk ) ;
clk_put ( pltfm_host - > clk ) ;
err_clk_get :
2011-01-02 07:52:56 +03:00
if ( gpio_is_valid ( plat - > wp_gpio ) ) {
tegra_gpio_disable ( plat - > wp_gpio ) ;
gpio_free ( plat - > wp_gpio ) ;
}
2011-05-27 19:48:12 +04:00
err_wp_req :
2011-02-10 20:07:30 +03:00
if ( gpio_is_valid ( plat - > cd_gpio ) )
free_irq ( gpio_to_irq ( plat - > cd_gpio ) , host ) ;
2011-05-27 19:48:12 +04:00
err_cd_irq_req :
2011-01-02 07:52:56 +03:00
if ( gpio_is_valid ( plat - > cd_gpio ) ) {
tegra_gpio_disable ( plat - > cd_gpio ) ;
gpio_free ( plat - > cd_gpio ) ;
}
2011-05-27 19:48:12 +04:00
err_cd_req :
2011-01-02 07:52:56 +03:00
if ( gpio_is_valid ( plat - > power_gpio ) ) {
tegra_gpio_disable ( plat - > power_gpio ) ;
gpio_free ( plat - > power_gpio ) ;
}
2011-05-27 19:48:12 +04:00
err_power_req :
err_no_plat :
sdhci_pltfm_free ( pdev ) ;
2011-01-02 07:52:56 +03:00
return rc ;
}
2011-05-27 19:48:12 +04:00
static int __devexit sdhci_tegra_remove ( struct platform_device * pdev )
2011-01-02 07:52:56 +03:00
{
2011-05-27 19:48:12 +04:00
struct sdhci_host * host = platform_get_drvdata ( pdev ) ;
2011-01-02 07:52:56 +03:00
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
2011-08-23 22:15:33 +04:00
struct tegra_sdhci_platform_data * plat = pltfm_host - > priv ;
2011-05-27 19:48:12 +04:00
int dead = ( readl ( host - > ioaddr + SDHCI_INT_STATUS ) = = 0xffffffff ) ;
sdhci_remove_host ( host , dead ) ;
2011-01-02 07:52:56 +03:00
if ( gpio_is_valid ( plat - > wp_gpio ) ) {
tegra_gpio_disable ( plat - > wp_gpio ) ;
gpio_free ( plat - > wp_gpio ) ;
}
if ( gpio_is_valid ( plat - > cd_gpio ) ) {
2011-02-10 20:07:30 +03:00
free_irq ( gpio_to_irq ( plat - > cd_gpio ) , host ) ;
2011-01-02 07:52:56 +03:00
tegra_gpio_disable ( plat - > cd_gpio ) ;
gpio_free ( plat - > cd_gpio ) ;
}
if ( gpio_is_valid ( plat - > power_gpio ) ) {
tegra_gpio_disable ( plat - > power_gpio ) ;
gpio_free ( plat - > power_gpio ) ;
}
clk_disable ( pltfm_host - > clk ) ;
clk_put ( pltfm_host - > clk ) ;
2011-05-27 19:48:12 +04:00
sdhci_pltfm_free ( pdev ) ;
return 0 ;
2011-01-02 07:52:56 +03:00
}
2011-05-27 19:48:12 +04:00
static struct platform_driver sdhci_tegra_driver = {
. driver = {
. name = " sdhci-tegra " ,
. owner = THIS_MODULE ,
2011-08-23 22:15:33 +04:00
. of_match_table = sdhci_tegra_dt_match ,
2011-11-03 14:09:45 +04:00
. pm = SDHCI_PLTFM_PMOPS ,
2011-05-27 19:48:12 +04:00
} ,
. probe = sdhci_tegra_probe ,
. remove = __devexit_p ( sdhci_tegra_remove ) ,
2011-01-02 07:52:56 +03:00
} ;
2011-05-27 19:48:12 +04:00
static int __init sdhci_tegra_init ( void )
{
return platform_driver_register ( & sdhci_tegra_driver ) ;
}
module_init ( sdhci_tegra_init ) ;
static void __exit sdhci_tegra_exit ( void )
{
platform_driver_unregister ( & sdhci_tegra_driver ) ;
}
module_exit ( sdhci_tegra_exit ) ;
MODULE_DESCRIPTION ( " SDHCI driver for Tegra " ) ;
MODULE_AUTHOR ( " Google, Inc. " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;