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>
2012-02-02 03:30:55 +04:00
# include <linux/of_device.h>
2011-01-02 07:52:56 +03:00
# include <linux/mmc/card.h>
# include <linux/mmc/host.h>
2013-03-12 00:44:11 +04:00
# include <linux/mmc/slot-gpio.h>
2015-03-31 00:39:25 +03:00
# include <linux/gpio/consumer.h>
2011-01-02 07:52:56 +03:00
# include "sdhci-pltfm.h"
2012-04-18 17:18:02 +04:00
/* Tegra SDHOST controller vendor register definitions */
# define SDHCI_TEGRA_VENDOR_MISC_CTRL 0x120
2014-05-22 19:55:35 +04:00
# define SDHCI_MISC_CTRL_ENABLE_SDR104 0x8
# define SDHCI_MISC_CTRL_ENABLE_SDR50 0x10
2012-04-18 17:18:02 +04:00
# define SDHCI_MISC_CTRL_ENABLE_SDHCI_SPEC_300 0x20
2014-05-22 19:55:35 +04:00
# define SDHCI_MISC_CTRL_ENABLE_DDR50 0x200
2012-04-18 17:18:02 +04:00
2012-02-02 03:30:55 +04:00
# define NVQUIRK_FORCE_SDHCI_SPEC_200 BIT(0)
# define NVQUIRK_ENABLE_BLOCK_GAP_DET BIT(1)
2012-04-18 17:18:02 +04:00
# define NVQUIRK_ENABLE_SDHCI_SPEC_300 BIT(2)
2014-05-22 19:55:35 +04:00
# define NVQUIRK_DISABLE_SDR50 BIT(3)
# define NVQUIRK_DISABLE_SDR104 BIT(4)
# define NVQUIRK_DISABLE_DDR50 BIT(5)
2012-02-02 03:30:55 +04:00
struct sdhci_tegra_soc_data {
2013-03-13 22:26:03 +04:00
const struct sdhci_pltfm_data * pdata ;
2012-02-02 03:30:55 +04:00
u32 nvquirks ;
} ;
struct sdhci_tegra {
const struct sdhci_tegra_soc_data * soc_data ;
2015-03-31 00:39:25 +03:00
struct gpio_desc * power_gpio ;
2012-02-02 03:30:55 +04:00
} ;
2011-01-02 07:52:56 +03:00
static u16 tegra_sdhci_readw ( struct sdhci_host * host , int reg )
{
2012-02-02 03:30:55 +04:00
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
struct sdhci_tegra * tegra_host = pltfm_host - > priv ;
const struct sdhci_tegra_soc_data * soc_data = tegra_host - > soc_data ;
if ( unlikely ( ( soc_data - > nvquirks & NVQUIRK_FORCE_SDHCI_SPEC_200 ) & &
( reg = = SDHCI_HOST_VERSION ) ) ) {
2011-01-02 07:52:56 +03:00
/* Erratum: Version register is invalid in HW. */
return SDHCI_SPEC_200 ;
}
return readw ( host - > ioaddr + reg ) ;
}
2015-01-28 19:45:16 +03:00
static void tegra_sdhci_writew ( struct sdhci_host * host , u16 val , int reg )
{
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
2015-02-11 20:55:51 +03:00
switch ( reg ) {
case SDHCI_TRANSFER_MODE :
/*
* Postpone this write , we must do it together with a
* command write that is down below .
*/
pltfm_host - > xfer_mode_shadow = val ;
return ;
case SDHCI_COMMAND :
writel ( ( val < < 16 ) | pltfm_host - > xfer_mode_shadow ,
host - > ioaddr + SDHCI_TRANSFER_MODE ) ;
return ;
2015-01-28 19:45:16 +03:00
}
writew ( val , host - > ioaddr + reg ) ;
}
2011-01-02 07:52:56 +03:00
static void tegra_sdhci_writel ( struct sdhci_host * host , u32 val , int reg )
{
2012-02-02 03:30:55 +04:00
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
struct sdhci_tegra * tegra_host = pltfm_host - > priv ;
const struct sdhci_tegra_soc_data * soc_data = tegra_host - > soc_data ;
2011-01-02 07:52:56 +03:00
/* 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 ) ;
2012-02-02 03:30:55 +04:00
if ( unlikely ( ( soc_data - > nvquirks & NVQUIRK_ENABLE_BLOCK_GAP_DET ) & &
( reg = = SDHCI_INT_ENABLE ) ) ) {
2011-01-02 07:52:56 +03:00
/* 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 ) ;
}
}
2012-02-02 03:30:55 +04:00
static unsigned int tegra_sdhci_get_ro ( struct sdhci_host * host )
2011-01-02 07:52:56 +03:00
{
2013-03-12 00:44:11 +04:00
return mmc_gpio_get_ro ( host - > mmc ) ;
2011-01-02 07:52:56 +03:00
}
2014-04-25 15:57:12 +04:00
static void tegra_sdhci_reset ( struct sdhci_host * host , u8 mask )
2012-04-18 17:18:02 +04:00
{
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
struct sdhci_tegra * tegra_host = pltfm_host - > priv ;
const struct sdhci_tegra_soc_data * soc_data = tegra_host - > soc_data ;
2014-05-22 19:55:35 +04:00
u32 misc_ctrl ;
2012-04-18 17:18:02 +04:00
2014-04-25 15:57:12 +04:00
sdhci_reset ( host , mask ) ;
2012-04-18 17:18:02 +04:00
if ( ! ( mask & SDHCI_RESET_ALL ) )
return ;
2014-05-22 19:55:35 +04:00
misc_ctrl = sdhci_readw ( host , SDHCI_TEGRA_VENDOR_MISC_CTRL ) ;
2012-04-18 17:18:02 +04:00
/* Erratum: Enable SDHCI spec v3.00 support */
2014-05-22 19:55:35 +04:00
if ( soc_data - > nvquirks & NVQUIRK_ENABLE_SDHCI_SPEC_300 )
2012-04-18 17:18:02 +04:00
misc_ctrl | = SDHCI_MISC_CTRL_ENABLE_SDHCI_SPEC_300 ;
2014-05-22 19:55:35 +04:00
/* Don't advertise UHS modes which aren't supported yet */
if ( soc_data - > nvquirks & NVQUIRK_DISABLE_SDR50 )
misc_ctrl & = ~ SDHCI_MISC_CTRL_ENABLE_SDR50 ;
if ( soc_data - > nvquirks & NVQUIRK_DISABLE_DDR50 )
misc_ctrl & = ~ SDHCI_MISC_CTRL_ENABLE_DDR50 ;
if ( soc_data - > nvquirks & NVQUIRK_DISABLE_SDR104 )
misc_ctrl & = ~ SDHCI_MISC_CTRL_ENABLE_SDR104 ;
sdhci_writew ( host , misc_ctrl , SDHCI_TEGRA_VENDOR_MISC_CTRL ) ;
2012-04-18 17:18:02 +04:00
}
2014-04-25 15:57:07 +04:00
static void tegra_sdhci_set_bus_width ( struct sdhci_host * host , int bus_width )
2011-01-02 07:52:56 +03:00
{
u32 ctrl ;
ctrl = sdhci_readb ( host , SDHCI_HOST_CONTROL ) ;
2013-03-12 00:44:11 +04:00
if ( ( host - > mmc - > caps & MMC_CAP_8_BIT_DATA ) & &
( bus_width = = MMC_BUS_WIDTH_8 ) ) {
2011-01-02 07:52:56 +03:00
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 ) ;
}
2013-03-13 22:26:05 +04:00
static const struct sdhci_ops tegra_sdhci_ops = {
2011-05-27 19:48:12 +04:00
. get_ro = tegra_sdhci_get_ro ,
. read_w = tegra_sdhci_readw ,
. write_l = tegra_sdhci_writel ,
2014-04-25 15:58:55 +04:00
. set_clock = sdhci_set_clock ,
2014-04-25 15:57:07 +04:00
. set_bus_width = tegra_sdhci_set_bus_width ,
2014-04-25 15:57:12 +04:00
. reset = tegra_sdhci_reset ,
2014-04-25 15:59:26 +04:00
. set_uhs_signaling = sdhci_set_uhs_signaling ,
2014-05-22 19:55:36 +04:00
. get_max_clock = sdhci_pltfm_clk_get_max_clock ,
2011-05-27 19:48:12 +04:00
} ;
2013-03-13 22:26:03 +04:00
static const struct sdhci_pltfm_data sdhci_tegra20_pdata = {
2012-02-02 03:30:55 +04:00
. quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL |
SDHCI_QUIRK_SINGLE_POWER_WRITE |
SDHCI_QUIRK_NO_HISPD_BIT |
2014-05-22 19:55:36 +04:00
SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC |
SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN ,
2012-02-02 03:30:55 +04:00
. ops = & tegra_sdhci_ops ,
} ;
static struct sdhci_tegra_soc_data soc_data_tegra20 = {
. pdata = & sdhci_tegra20_pdata ,
. nvquirks = NVQUIRK_FORCE_SDHCI_SPEC_200 |
NVQUIRK_ENABLE_BLOCK_GAP_DET ,
} ;
2013-03-13 22:26:03 +04:00
static const struct sdhci_pltfm_data sdhci_tegra30_pdata = {
2011-05-27 19:48:12 +04:00
. quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL |
2012-02-02 03:30:55 +04:00
SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK |
2011-05-27 19:48:12 +04:00
SDHCI_QUIRK_SINGLE_POWER_WRITE |
SDHCI_QUIRK_NO_HISPD_BIT |
2014-05-22 19:55:36 +04:00
SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC |
SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN ,
2011-05-27 19:48:12 +04:00
. ops = & tegra_sdhci_ops ,
} ;
2011-01-02 07:52:56 +03:00
2012-02-02 03:30:55 +04:00
static struct sdhci_tegra_soc_data soc_data_tegra30 = {
. pdata = & sdhci_tegra30_pdata ,
2014-05-22 19:55:35 +04:00
. nvquirks = NVQUIRK_ENABLE_SDHCI_SPEC_300 |
NVQUIRK_DISABLE_SDR50 |
NVQUIRK_DISABLE_SDR104 ,
2012-02-02 03:30:55 +04:00
} ;
2015-02-11 20:55:51 +03:00
static const struct sdhci_ops tegra114_sdhci_ops = {
. get_ro = tegra_sdhci_get_ro ,
. read_w = tegra_sdhci_readw ,
. write_w = tegra_sdhci_writew ,
. write_l = tegra_sdhci_writel ,
. set_clock = sdhci_set_clock ,
. set_bus_width = tegra_sdhci_set_bus_width ,
. reset = tegra_sdhci_reset ,
. set_uhs_signaling = sdhci_set_uhs_signaling ,
. get_max_clock = sdhci_pltfm_clk_get_max_clock ,
} ;
2013-03-13 22:26:03 +04:00
static const struct sdhci_pltfm_data sdhci_tegra114_pdata = {
2013-02-20 22:35:17 +04:00
. quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL |
SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK |
SDHCI_QUIRK_SINGLE_POWER_WRITE |
SDHCI_QUIRK_NO_HISPD_BIT |
2014-05-22 19:55:36 +04:00
SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC |
SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN ,
2015-02-11 20:55:51 +03:00
. ops = & tegra114_sdhci_ops ,
2013-02-20 22:35:17 +04:00
} ;
static struct sdhci_tegra_soc_data soc_data_tegra114 = {
. pdata = & sdhci_tegra114_pdata ,
2014-05-22 19:55:35 +04:00
. nvquirks = NVQUIRK_DISABLE_SDR50 |
NVQUIRK_DISABLE_DDR50 |
2015-02-11 20:55:51 +03:00
NVQUIRK_DISABLE_SDR104 ,
2013-02-20 22:35:17 +04:00
} ;
2012-11-19 22:24:22 +04:00
static const struct of_device_id sdhci_tegra_dt_match [ ] = {
2014-01-06 22:17:47 +04:00
{ . compatible = " nvidia,tegra124-sdhci " , . data = & soc_data_tegra114 } ,
2013-02-20 22:35:17 +04:00
{ . compatible = " nvidia,tegra114-sdhci " , . data = & soc_data_tegra114 } ,
2012-02-02 03:30:55 +04:00
{ . compatible = " nvidia,tegra30-sdhci " , . data = & soc_data_tegra30 } ,
{ . compatible = " nvidia,tegra20-sdhci " , . data = & soc_data_tegra20 } ,
2011-08-23 22:15:33 +04:00
{ }
} ;
2013-04-23 23:05:57 +04:00
MODULE_DEVICE_TABLE ( of , sdhci_tegra_dt_match ) ;
2011-08-23 22:15:33 +04:00
2012-11-19 22:23:06 +04:00
static int sdhci_tegra_probe ( struct platform_device * pdev )
2011-01-02 07:52:56 +03:00
{
2012-02-02 03:30:55 +04:00
const struct of_device_id * match ;
const struct sdhci_tegra_soc_data * soc_data ;
struct sdhci_host * host ;
2011-05-27 19:48:12 +04:00
struct sdhci_pltfm_host * pltfm_host ;
2012-02-02 03:30:55 +04:00
struct sdhci_tegra * tegra_host ;
2011-01-02 07:52:56 +03:00
struct clk * clk ;
int rc ;
2012-02-02 03:30:55 +04:00
match = of_match_device ( sdhci_tegra_dt_match , & pdev - > dev ) ;
2012-08-17 11:04:31 +04:00
if ( ! match )
return - EINVAL ;
soc_data = match - > data ;
2012-02-02 03:30:55 +04:00
2013-05-30 00:50:05 +04:00
host = sdhci_pltfm_init ( pdev , soc_data - > pdata , 0 ) ;
2011-05-27 19:48:12 +04:00
if ( IS_ERR ( host ) )
return PTR_ERR ( host ) ;
pltfm_host = sdhci_priv ( host ) ;
2012-02-02 03:30:55 +04:00
tegra_host = devm_kzalloc ( & pdev - > dev , sizeof ( * tegra_host ) , GFP_KERNEL ) ;
if ( ! tegra_host ) {
dev_err ( mmc_dev ( host - > mmc ) , " failed to allocate tegra_host \n " ) ;
rc = - ENOMEM ;
2013-02-16 02:07:19 +04:00
goto err_alloc_tegra_host ;
2012-02-02 03:30:55 +04:00
}
tegra_host - > soc_data = soc_data ;
pltfm_host - > priv = tegra_host ;
2011-08-23 22:15:33 +04:00
2015-03-31 00:39:25 +03:00
rc = mmc_of_parse ( host - > mmc ) ;
2013-06-10 00:14:16 +04:00
if ( rc )
goto err_parse_dt ;
2013-02-16 02:07:19 +04:00
2015-03-31 00:39:25 +03:00
tegra_host - > power_gpio = devm_gpiod_get_optional ( & pdev - > dev , " power " ,
GPIOD_OUT_HIGH ) ;
if ( IS_ERR ( tegra_host - > power_gpio ) ) {
rc = PTR_ERR ( tegra_host - > power_gpio ) ;
goto err_power_req ;
2011-01-02 07:52:56 +03:00
}
2015-02-27 10:47:27 +03:00
clk = devm_clk_get ( mmc_dev ( host - > mmc ) , NULL ) ;
2011-01-02 07:52:56 +03:00
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
}
2012-06-05 08:29:37 +04:00
clk_prepare_enable ( clk ) ;
2011-01-02 07:52:56 +03:00
pltfm_host - > clk = clk ;
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 :
2012-06-05 08:29:37 +04:00
clk_disable_unprepare ( pltfm_host - > clk ) ;
2011-05-27 19:48:12 +04:00
err_clk_get :
err_power_req :
2013-06-10 00:14:16 +04:00
err_parse_dt :
2013-02-16 02:07:19 +04:00
err_alloc_tegra_host :
2011-05-27 19:48:12 +04:00
sdhci_pltfm_free ( pdev ) ;
2011-01-02 07:52:56 +03:00
return rc ;
}
2011-05-27 19:48:12 +04:00
static struct platform_driver sdhci_tegra_driver = {
. driver = {
. name = " sdhci-tegra " ,
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 ,
2015-02-27 10:47:31 +03:00
. remove = sdhci_pltfm_unregister ,
2011-01-02 07:52:56 +03:00
} ;
2011-11-26 08:55:43 +04:00
module_platform_driver ( sdhci_tegra_driver ) ;
2011-05-27 19:48:12 +04:00
MODULE_DESCRIPTION ( " SDHCI driver for Tegra " ) ;
2012-02-02 03:30:55 +04:00
MODULE_AUTHOR ( " Google, Inc. " ) ;
2011-05-27 19:48:12 +04:00
MODULE_LICENSE ( " GPL v2 " ) ;