2018-07-06 10:23:55 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for Synopsys DesignWare Cores Mobile Storage Host Controller
*
* Copyright ( C ) 2018 Synaptics Incorporated
*
* Author : Jisheng Zhang < jszhang @ kernel . org >
*/
# include <linux/clk.h>
2018-08-28 12:48:14 +03:00
# include <linux/dma-mapping.h>
# include <linux/kernel.h>
2018-07-06 10:23:55 +03:00
# include <linux/module.h>
# include <linux/of.h>
2018-08-28 12:48:14 +03:00
# include <linux/sizes.h>
2018-07-06 10:23:55 +03:00
# include "sdhci-pltfm.h"
2020-05-13 13:26:02 +03:00
/* DWCMSHC specific Mode Select value */
# define DWCMSHC_CTRL_HS400 0x7
2018-08-28 12:48:14 +03:00
# define BOUNDARY_OK(addr, len) \
( ( addr | ( SZ_128M - 1 ) ) = = ( ( addr + len - 1 ) | ( SZ_128M - 1 ) ) )
2018-07-06 10:23:55 +03:00
struct dwcmshc_priv {
struct clk * bus_clk ;
} ;
2018-08-28 12:48:14 +03:00
/*
* If DMA addr spans 128 MB boundary , we split the DMA transfer into two
* so that each DMA transfer doesn ' t exceed the boundary .
*/
static void dwcmshc_adma_write_desc ( struct sdhci_host * host , void * * desc ,
dma_addr_t addr , int len , unsigned int cmd )
{
int tmplen , offset ;
if ( likely ( ! len | | BOUNDARY_OK ( addr , len ) ) ) {
sdhci_adma_write_desc ( host , desc , addr , len , cmd ) ;
return ;
}
offset = addr & ( SZ_128M - 1 ) ;
tmplen = SZ_128M - offset ;
sdhci_adma_write_desc ( host , desc , addr , tmplen , cmd ) ;
addr + = tmplen ;
len - = tmplen ;
sdhci_adma_write_desc ( host , desc , addr , len , cmd ) ;
}
2020-05-13 13:26:02 +03:00
static void dwcmshc_set_uhs_signaling ( struct sdhci_host * host ,
unsigned int timing )
{
u16 ctrl_2 ;
ctrl_2 = sdhci_readw ( host , SDHCI_HOST_CONTROL2 ) ;
/* Select Bus Speed Mode for host */
ctrl_2 & = ~ SDHCI_CTRL_UHS_MASK ;
if ( ( timing = = MMC_TIMING_MMC_HS200 ) | |
( timing = = MMC_TIMING_UHS_SDR104 ) )
ctrl_2 | = SDHCI_CTRL_UHS_SDR104 ;
else if ( timing = = MMC_TIMING_UHS_SDR12 )
ctrl_2 | = SDHCI_CTRL_UHS_SDR12 ;
else if ( ( timing = = MMC_TIMING_UHS_SDR25 ) | |
( timing = = MMC_TIMING_MMC_HS ) )
ctrl_2 | = SDHCI_CTRL_UHS_SDR25 ;
else if ( timing = = MMC_TIMING_UHS_SDR50 )
ctrl_2 | = SDHCI_CTRL_UHS_SDR50 ;
else if ( ( timing = = MMC_TIMING_UHS_DDR50 ) | |
( timing = = MMC_TIMING_MMC_DDR52 ) )
ctrl_2 | = SDHCI_CTRL_UHS_DDR50 ;
else if ( timing = = MMC_TIMING_MMC_HS400 )
ctrl_2 | = DWCMSHC_CTRL_HS400 ;
sdhci_writew ( host , ctrl_2 , SDHCI_HOST_CONTROL2 ) ;
}
2018-07-06 10:23:55 +03:00
static const struct sdhci_ops sdhci_dwcmshc_ops = {
. set_clock = sdhci_set_clock ,
. set_bus_width = sdhci_set_bus_width ,
2020-05-13 13:26:02 +03:00
. set_uhs_signaling = dwcmshc_set_uhs_signaling ,
2018-07-06 10:23:55 +03:00
. get_max_clock = sdhci_pltfm_clk_get_max_clock ,
. reset = sdhci_reset ,
2018-08-28 12:48:14 +03:00
. adma_write_desc = dwcmshc_adma_write_desc ,
2018-07-06 10:23:55 +03:00
} ;
static const struct sdhci_pltfm_data sdhci_dwcmshc_pdata = {
. ops = & sdhci_dwcmshc_ops ,
. quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN ,
} ;
static int dwcmshc_probe ( struct platform_device * pdev )
{
struct sdhci_pltfm_host * pltfm_host ;
struct sdhci_host * host ;
struct dwcmshc_priv * priv ;
int err ;
2018-08-28 12:48:14 +03:00
u32 extra ;
2018-07-06 10:23:55 +03:00
host = sdhci_pltfm_init ( pdev , & sdhci_dwcmshc_pdata ,
sizeof ( struct dwcmshc_priv ) ) ;
if ( IS_ERR ( host ) )
return PTR_ERR ( host ) ;
2018-08-28 12:48:14 +03:00
/*
* extra adma table cnt for cross 128 M boundary handling .
*/
extra = DIV_ROUND_UP_ULL ( dma_get_required_mask ( & pdev - > dev ) , SZ_128M ) ;
if ( extra > SDHCI_MAX_SEGS )
extra = SDHCI_MAX_SEGS ;
host - > adma_table_cnt + = extra ;
2018-07-06 10:23:55 +03:00
pltfm_host = sdhci_priv ( host ) ;
priv = sdhci_pltfm_priv ( pltfm_host ) ;
pltfm_host - > clk = devm_clk_get ( & pdev - > dev , " core " ) ;
if ( IS_ERR ( pltfm_host - > clk ) ) {
err = PTR_ERR ( pltfm_host - > clk ) ;
dev_err ( & pdev - > dev , " failed to get core clk: %d \n " , err ) ;
goto free_pltfm ;
}
err = clk_prepare_enable ( pltfm_host - > clk ) ;
if ( err )
goto free_pltfm ;
priv - > bus_clk = devm_clk_get ( & pdev - > dev , " bus " ) ;
if ( ! IS_ERR ( priv - > bus_clk ) )
clk_prepare_enable ( priv - > bus_clk ) ;
err = mmc_of_parse ( host - > mmc ) ;
if ( err )
goto err_clk ;
sdhci_get_of_property ( pdev ) ;
err = sdhci_add_host ( host ) ;
if ( err )
goto err_clk ;
return 0 ;
err_clk :
clk_disable_unprepare ( pltfm_host - > clk ) ;
clk_disable_unprepare ( priv - > bus_clk ) ;
free_pltfm :
sdhci_pltfm_free ( pdev ) ;
return err ;
}
static int dwcmshc_remove ( struct platform_device * pdev )
{
struct sdhci_host * host = platform_get_drvdata ( pdev ) ;
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
struct dwcmshc_priv * priv = sdhci_pltfm_priv ( pltfm_host ) ;
sdhci_remove_host ( host , 0 ) ;
clk_disable_unprepare ( pltfm_host - > clk ) ;
clk_disable_unprepare ( priv - > bus_clk ) ;
sdhci_pltfm_free ( pdev ) ;
return 0 ;
}
2020-05-15 09:19:26 +03:00
# ifdef CONFIG_PM_SLEEP
static int dwcmshc_suspend ( struct device * dev )
{
struct sdhci_host * host = dev_get_drvdata ( dev ) ;
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
struct dwcmshc_priv * priv = sdhci_pltfm_priv ( pltfm_host ) ;
int ret ;
ret = sdhci_suspend_host ( host ) ;
if ( ret )
return ret ;
clk_disable_unprepare ( pltfm_host - > clk ) ;
if ( ! IS_ERR ( priv - > bus_clk ) )
clk_disable_unprepare ( priv - > bus_clk ) ;
return ret ;
}
static int dwcmshc_resume ( struct device * dev )
{
struct sdhci_host * host = dev_get_drvdata ( dev ) ;
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
struct dwcmshc_priv * priv = sdhci_pltfm_priv ( pltfm_host ) ;
int ret ;
ret = clk_prepare_enable ( pltfm_host - > clk ) ;
if ( ret )
return ret ;
if ( ! IS_ERR ( priv - > bus_clk ) ) {
ret = clk_prepare_enable ( priv - > bus_clk ) ;
if ( ret )
return ret ;
}
return sdhci_resume_host ( host ) ;
}
# endif
static SIMPLE_DEV_PM_OPS ( dwcmshc_pmops , dwcmshc_suspend , dwcmshc_resume ) ;
2018-07-06 10:23:55 +03:00
static const struct of_device_id sdhci_dwcmshc_dt_ids [ ] = {
{ . compatible = " snps,dwcmshc-sdhci " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , sdhci_dwcmshc_dt_ids ) ;
static struct platform_driver sdhci_dwcmshc_driver = {
. driver = {
. name = " sdhci-dwcmshc " ,
2020-09-04 02:24:39 +03:00
. probe_type = PROBE_PREFER_ASYNCHRONOUS ,
2018-07-06 10:23:55 +03:00
. of_match_table = sdhci_dwcmshc_dt_ids ,
2020-05-15 09:19:26 +03:00
. pm = & dwcmshc_pmops ,
2018-07-06 10:23:55 +03:00
} ,
. probe = dwcmshc_probe ,
. remove = dwcmshc_remove ,
} ;
module_platform_driver ( sdhci_dwcmshc_driver ) ;
MODULE_DESCRIPTION ( " SDHCI platform driver for Synopsys DWC MSHC " ) ;
MODULE_AUTHOR ( " Jisheng Zhang <jszhang@kernel.org> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;