2016-12-08 21:50:55 +09:00
/*
* Copyright ( C ) 2016 Socionext Inc .
* Author : Masahiro Yamada < yamada . masahiro @ socionext . 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 .
*
* 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/bitops.h>
# include <linux/iopoll.h>
# include <linux/module.h>
# include <linux/mmc/host.h>
2016-12-30 13:47:21 +01:00
# include <linux/mmc/mmc.h>
2017-03-21 14:33:11 +00:00
# include <linux/of.h>
2016-12-08 21:50:55 +09:00
# include "sdhci-pltfm.h"
/* HRS - Host Register Set (specific to Cadence) */
# define SDHCI_CDNS_HRS04 0x10 /* PHY access port */
# define SDHCI_CDNS_HRS04_ACK BIT(26)
# define SDHCI_CDNS_HRS04_RD BIT(25)
# define SDHCI_CDNS_HRS04_WR BIT(24)
2017-02-14 20:05:40 +09:00
# define SDHCI_CDNS_HRS04_RDATA_SHIFT 16
2016-12-08 21:50:55 +09:00
# define SDHCI_CDNS_HRS04_WDATA_SHIFT 8
# define SDHCI_CDNS_HRS04_ADDR_SHIFT 0
# define SDHCI_CDNS_HRS06 0x18 /* eMMC control */
# define SDHCI_CDNS_HRS06_TUNE_UP BIT(15)
# define SDHCI_CDNS_HRS06_TUNE_SHIFT 8
# define SDHCI_CDNS_HRS06_TUNE_MASK 0x3f
# define SDHCI_CDNS_HRS06_MODE_MASK 0x7
# define SDHCI_CDNS_HRS06_MODE_SD 0x0
# define SDHCI_CDNS_HRS06_MODE_MMC_SDR 0x2
# define SDHCI_CDNS_HRS06_MODE_MMC_DDR 0x3
# define SDHCI_CDNS_HRS06_MODE_MMC_HS200 0x4
# define SDHCI_CDNS_HRS06_MODE_MMC_HS400 0x5
2017-03-06 08:28:41 +00:00
# define SDHCI_CDNS_HRS06_MODE_MMC_HS400ES 0x6
2016-12-08 21:50:55 +09:00
/* SRS - Slot Register Set (SDHCI-compatible) */
# define SDHCI_CDNS_SRS_BASE 0x200
/* PHY */
# define SDHCI_CDNS_PHY_DLY_SD_HS 0x00
# define SDHCI_CDNS_PHY_DLY_SD_DEFAULT 0x01
# define SDHCI_CDNS_PHY_DLY_UHS_SDR12 0x02
# define SDHCI_CDNS_PHY_DLY_UHS_SDR25 0x03
# define SDHCI_CDNS_PHY_DLY_UHS_SDR50 0x04
# define SDHCI_CDNS_PHY_DLY_UHS_DDR50 0x05
# define SDHCI_CDNS_PHY_DLY_EMMC_LEGACY 0x06
# define SDHCI_CDNS_PHY_DLY_EMMC_SDR 0x07
# define SDHCI_CDNS_PHY_DLY_EMMC_DDR 0x08
2017-03-21 14:33:11 +00:00
# define SDHCI_CDNS_PHY_DLY_SDCLK 0x0b
# define SDHCI_CDNS_PHY_DLY_HSMMC 0x0c
# define SDHCI_CDNS_PHY_DLY_STROBE 0x0d
2016-12-08 21:50:55 +09:00
/*
* The tuned val register is 6 bit - wide , but not the whole of the range is
* available . The range 0 - 42 seems to be available ( then 43 wraps around to 0 )
* but I am not quite sure if it is official . Use only 0 to 39 for safety .
*/
# define SDHCI_CDNS_MAX_TUNING_LOOP 40
struct sdhci_cdns_priv {
void __iomem * hrs_addr ;
2017-03-06 08:28:41 +00:00
bool enhanced_strobe ;
2016-12-08 21:50:55 +09:00
} ;
2017-03-21 14:33:11 +00:00
struct sdhci_cdns_phy_cfg {
const char * property ;
u8 addr ;
} ;
static const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs [ ] = {
{ " cdns,phy-input-delay-sd-highspeed " , SDHCI_CDNS_PHY_DLY_SD_HS , } ,
{ " cdns,phy-input-delay-legacy " , SDHCI_CDNS_PHY_DLY_SD_DEFAULT , } ,
{ " cdns,phy-input-delay-sd-uhs-sdr12 " , SDHCI_CDNS_PHY_DLY_UHS_SDR12 , } ,
{ " cdns,phy-input-delay-sd-uhs-sdr25 " , SDHCI_CDNS_PHY_DLY_UHS_SDR25 , } ,
{ " cdns,phy-input-delay-sd-uhs-sdr50 " , SDHCI_CDNS_PHY_DLY_UHS_SDR50 , } ,
{ " cdns,phy-input-delay-sd-uhs-ddr50 " , SDHCI_CDNS_PHY_DLY_UHS_DDR50 , } ,
{ " cdns,phy-input-delay-mmc-highspeed " , SDHCI_CDNS_PHY_DLY_EMMC_SDR , } ,
{ " cdns,phy-input-delay-mmc-ddr " , SDHCI_CDNS_PHY_DLY_EMMC_DDR , } ,
{ " cdns,phy-dll-delay-sdclk " , SDHCI_CDNS_PHY_DLY_SDCLK , } ,
{ " cdns,phy-dll-delay-sdclk-hsmmc " , SDHCI_CDNS_PHY_DLY_HSMMC , } ,
{ " cdns,phy-dll-delay-strobe " , SDHCI_CDNS_PHY_DLY_STROBE , } ,
} ;
2017-03-21 14:32:16 +00:00
static int sdhci_cdns_write_phy_reg ( struct sdhci_cdns_priv * priv ,
u8 addr , u8 data )
2016-12-08 21:50:55 +09:00
{
void __iomem * reg = priv - > hrs_addr + SDHCI_CDNS_HRS04 ;
u32 tmp ;
2017-03-21 14:32:16 +00:00
int ret ;
2016-12-08 21:50:55 +09:00
tmp = ( data < < SDHCI_CDNS_HRS04_WDATA_SHIFT ) |
( addr < < SDHCI_CDNS_HRS04_ADDR_SHIFT ) ;
writel ( tmp , reg ) ;
tmp | = SDHCI_CDNS_HRS04_WR ;
writel ( tmp , reg ) ;
2017-03-21 14:32:16 +00:00
ret = readl_poll_timeout ( reg , tmp , tmp & SDHCI_CDNS_HRS04_ACK , 0 , 10 ) ;
if ( ret )
return ret ;
2016-12-08 21:50:55 +09:00
tmp & = ~ SDHCI_CDNS_HRS04_WR ;
writel ( tmp , reg ) ;
2017-03-21 14:32:16 +00:00
return 0 ;
2016-12-08 21:50:55 +09:00
}
2017-03-21 14:33:11 +00:00
static int sdhci_cdns_phy_init ( struct device_node * np ,
struct sdhci_cdns_priv * priv )
2016-12-08 21:50:55 +09:00
{
2017-03-21 14:33:11 +00:00
u32 val ;
int ret , i ;
for ( i = 0 ; i < ARRAY_SIZE ( sdhci_cdns_phy_cfgs ) ; i + + ) {
ret = of_property_read_u32 ( np , sdhci_cdns_phy_cfgs [ i ] . property ,
& val ) ;
if ( ret )
continue ;
ret = sdhci_cdns_write_phy_reg ( priv ,
sdhci_cdns_phy_cfgs [ i ] . addr ,
val ) ;
if ( ret )
return ret ;
}
return 0 ;
2016-12-08 21:50:55 +09:00
}
static inline void * sdhci_cdns_priv ( struct sdhci_host * host )
{
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
return sdhci_pltfm_priv ( pltfm_host ) ;
}
static unsigned int sdhci_cdns_get_timeout_clock ( struct sdhci_host * host )
{
/*
* Cadence ' s spec says the Timeout Clock Frequency is the same as the
2017-03-24 15:50:12 +08:00
* Base Clock Frequency .
2016-12-08 21:50:55 +09:00
*/
2017-03-24 15:50:12 +08:00
return host - > max_clk ;
2016-12-08 21:50:55 +09:00
}
2017-03-06 08:28:41 +00:00
static void sdhci_cdns_set_emmc_mode ( struct sdhci_cdns_priv * priv , u32 mode )
{
u32 tmp ;
/* The speed mode for eMMC is selected by HRS06 register */
tmp = readl ( priv - > hrs_addr + SDHCI_CDNS_HRS06 ) ;
tmp & = ~ SDHCI_CDNS_HRS06_MODE_MASK ;
tmp | = mode ;
writel ( tmp , priv - > hrs_addr + SDHCI_CDNS_HRS06 ) ;
}
static u32 sdhci_cdns_get_emmc_mode ( struct sdhci_cdns_priv * priv )
{
u32 tmp ;
tmp = readl ( priv - > hrs_addr + SDHCI_CDNS_HRS06 ) ;
return tmp & SDHCI_CDNS_HRS06_MODE_MASK ;
}
2016-12-08 21:50:55 +09:00
static void sdhci_cdns_set_uhs_signaling ( struct sdhci_host * host ,
unsigned int timing )
{
struct sdhci_cdns_priv * priv = sdhci_cdns_priv ( host ) ;
2017-03-06 08:28:41 +00:00
u32 mode ;
2016-12-08 21:50:55 +09:00
switch ( timing ) {
case MMC_TIMING_MMC_HS :
mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR ;
break ;
case MMC_TIMING_MMC_DDR52 :
mode = SDHCI_CDNS_HRS06_MODE_MMC_DDR ;
break ;
case MMC_TIMING_MMC_HS200 :
mode = SDHCI_CDNS_HRS06_MODE_MMC_HS200 ;
break ;
case MMC_TIMING_MMC_HS400 :
2017-03-06 08:28:41 +00:00
if ( priv - > enhanced_strobe )
mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400ES ;
else
mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400 ;
2016-12-08 21:50:55 +09:00
break ;
default :
mode = SDHCI_CDNS_HRS06_MODE_SD ;
break ;
}
2017-03-06 08:28:41 +00:00
sdhci_cdns_set_emmc_mode ( priv , mode ) ;
2016-12-08 21:50:55 +09:00
/* For SD, fall back to the default handler */
if ( mode = = SDHCI_CDNS_HRS06_MODE_SD )
sdhci_set_uhs_signaling ( host , timing ) ;
}
static const struct sdhci_ops sdhci_cdns_ops = {
. set_clock = sdhci_set_clock ,
. get_timeout_clock = sdhci_cdns_get_timeout_clock ,
. set_bus_width = sdhci_set_bus_width ,
. reset = sdhci_reset ,
. set_uhs_signaling = sdhci_cdns_set_uhs_signaling ,
} ;
static const struct sdhci_pltfm_data sdhci_cdns_pltfm_data = {
. ops = & sdhci_cdns_ops ,
} ;
static int sdhci_cdns_set_tune_val ( struct sdhci_host * host , unsigned int val )
{
struct sdhci_cdns_priv * priv = sdhci_cdns_priv ( host ) ;
void __iomem * reg = priv - > hrs_addr + SDHCI_CDNS_HRS06 ;
u32 tmp ;
if ( WARN_ON ( val > SDHCI_CDNS_HRS06_TUNE_MASK ) )
return - EINVAL ;
tmp = readl ( reg ) ;
tmp & = ~ ( SDHCI_CDNS_HRS06_TUNE_MASK < < SDHCI_CDNS_HRS06_TUNE_SHIFT ) ;
tmp | = val < < SDHCI_CDNS_HRS06_TUNE_SHIFT ;
tmp | = SDHCI_CDNS_HRS06_TUNE_UP ;
writel ( tmp , reg ) ;
return readl_poll_timeout ( reg , tmp , ! ( tmp & SDHCI_CDNS_HRS06_TUNE_UP ) ,
0 , 1 ) ;
}
static int sdhci_cdns_execute_tuning ( struct mmc_host * mmc , u32 opcode )
{
struct sdhci_host * host = mmc_priv ( mmc ) ;
int cur_streak = 0 ;
int max_streak = 0 ;
int end_of_streak = 0 ;
int i ;
/*
* This handler only implements the eMMC tuning that is specific to
* this controller . Fall back to the standard method for SD timing .
*/
if ( host - > timing ! = MMC_TIMING_MMC_HS200 )
return sdhci_execute_tuning ( mmc , opcode ) ;
if ( WARN_ON ( opcode ! = MMC_SEND_TUNING_BLOCK_HS200 ) )
return - EINVAL ;
for ( i = 0 ; i < SDHCI_CDNS_MAX_TUNING_LOOP ; i + + ) {
if ( sdhci_cdns_set_tune_val ( host , i ) | |
mmc_send_tuning ( host - > mmc , opcode , NULL ) ) { /* bad */
cur_streak = 0 ;
} else { /* good */
cur_streak + + ;
if ( cur_streak > max_streak ) {
max_streak = cur_streak ;
end_of_streak = i ;
}
}
}
if ( ! max_streak ) {
dev_err ( mmc_dev ( host - > mmc ) , " no tuning point found \n " ) ;
return - EIO ;
}
return sdhci_cdns_set_tune_val ( host , end_of_streak - max_streak / 2 ) ;
}
2017-03-06 08:28:41 +00:00
static void sdhci_cdns_hs400_enhanced_strobe ( struct mmc_host * mmc ,
struct mmc_ios * ios )
{
struct sdhci_host * host = mmc_priv ( mmc ) ;
struct sdhci_cdns_priv * priv = sdhci_cdns_priv ( host ) ;
u32 mode ;
priv - > enhanced_strobe = ios - > enhanced_strobe ;
mode = sdhci_cdns_get_emmc_mode ( priv ) ;
if ( mode = = SDHCI_CDNS_HRS06_MODE_MMC_HS400 & & ios - > enhanced_strobe )
sdhci_cdns_set_emmc_mode ( priv ,
SDHCI_CDNS_HRS06_MODE_MMC_HS400ES ) ;
if ( mode = = SDHCI_CDNS_HRS06_MODE_MMC_HS400ES & & ! ios - > enhanced_strobe )
sdhci_cdns_set_emmc_mode ( priv ,
SDHCI_CDNS_HRS06_MODE_MMC_HS400 ) ;
}
2016-12-08 21:50:55 +09:00
static int sdhci_cdns_probe ( struct platform_device * pdev )
{
struct sdhci_host * host ;
struct sdhci_pltfm_host * pltfm_host ;
struct sdhci_cdns_priv * priv ;
struct clk * clk ;
int ret ;
2017-03-21 14:33:11 +00:00
struct device * dev = & pdev - > dev ;
2016-12-08 21:50:55 +09:00
2017-03-21 14:33:26 +00:00
clk = devm_clk_get ( dev , NULL ) ;
2016-12-08 21:50:55 +09:00
if ( IS_ERR ( clk ) )
return PTR_ERR ( clk ) ;
ret = clk_prepare_enable ( clk ) ;
if ( ret )
return ret ;
host = sdhci_pltfm_init ( pdev , & sdhci_cdns_pltfm_data , sizeof ( * priv ) ) ;
if ( IS_ERR ( host ) ) {
ret = PTR_ERR ( host ) ;
goto disable_clk ;
}
pltfm_host = sdhci_priv ( host ) ;
pltfm_host - > clk = clk ;
priv = sdhci_cdns_priv ( host ) ;
priv - > hrs_addr = host - > ioaddr ;
2017-03-06 08:28:41 +00:00
priv - > enhanced_strobe = false ;
2016-12-08 21:50:55 +09:00
host - > ioaddr + = SDHCI_CDNS_SRS_BASE ;
host - > mmc_host_ops . execute_tuning = sdhci_cdns_execute_tuning ;
2017-03-06 08:28:41 +00:00
host - > mmc_host_ops . hs400_enhanced_strobe =
sdhci_cdns_hs400_enhanced_strobe ;
2016-12-08 21:50:55 +09:00
2017-04-11 12:13:38 +01:00
sdhci_get_of_property ( pdev ) ;
2016-12-08 21:50:55 +09:00
ret = mmc_of_parse ( host - > mmc ) ;
if ( ret )
goto free ;
2017-03-21 14:33:11 +00:00
ret = sdhci_cdns_phy_init ( dev - > of_node , priv ) ;
if ( ret )
goto free ;
2016-12-08 21:50:55 +09:00
ret = sdhci_add_host ( host ) ;
if ( ret )
goto free ;
return 0 ;
free :
sdhci_pltfm_free ( pdev ) ;
disable_clk :
clk_disable_unprepare ( clk ) ;
return ret ;
}
static const struct of_device_id sdhci_cdns_match [ ] = {
2016-12-14 11:10:46 +09:00
{ . compatible = " socionext,uniphier-sd4hc " } ,
2016-12-08 21:50:55 +09:00
{ . compatible = " cdns,sd4hc " } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , sdhci_cdns_match ) ;
static struct platform_driver sdhci_cdns_driver = {
. driver = {
. name = " sdhci-cdns " ,
. pm = & sdhci_pltfm_pmops ,
. of_match_table = sdhci_cdns_match ,
} ,
. probe = sdhci_cdns_probe ,
. remove = sdhci_pltfm_unregister ,
} ;
module_platform_driver ( sdhci_cdns_driver ) ;
MODULE_AUTHOR ( " Masahiro Yamada <yamada.masahiro@socionext.com> " ) ;
MODULE_DESCRIPTION ( " Cadence SD/SDIO/eMMC Host Controller Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;