2018-07-06 15:23:55 +08: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 17:48:14 +08:00
# include <linux/dma-mapping.h>
# include <linux/kernel.h>
2018-07-06 15:23:55 +08:00
# include <linux/module.h>
# include <linux/of.h>
2018-08-28 17:48:14 +08:00
# include <linux/sizes.h>
2018-07-06 15:23:55 +08:00
# include "sdhci-pltfm.h"
2018-08-28 17:48:14 +08:00
# define BOUNDARY_OK(addr, len) \
( ( addr | ( SZ_128M - 1 ) ) = = ( ( addr + len - 1 ) | ( SZ_128M - 1 ) ) )
2018-07-06 15:23:55 +08:00
struct dwcmshc_priv {
struct clk * bus_clk ;
} ;
2018-08-28 17:48:14 +08: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 ) ;
}
2018-07-06 15:23:55 +08:00
static const struct sdhci_ops sdhci_dwcmshc_ops = {
. set_clock = sdhci_set_clock ,
. set_bus_width = sdhci_set_bus_width ,
. set_uhs_signaling = sdhci_set_uhs_signaling ,
. get_max_clock = sdhci_pltfm_clk_get_max_clock ,
. reset = sdhci_reset ,
2018-08-28 17:48:14 +08:00
. adma_write_desc = dwcmshc_adma_write_desc ,
2018-07-06 15:23:55 +08: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 17:48:14 +08:00
u32 extra ;
2018-07-06 15:23:55 +08:00
host = sdhci_pltfm_init ( pdev , & sdhci_dwcmshc_pdata ,
sizeof ( struct dwcmshc_priv ) ) ;
if ( IS_ERR ( host ) )
return PTR_ERR ( host ) ;
2018-08-28 17:48:14 +08: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 15:23:55 +08: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 ;
}
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 " ,
. of_match_table = sdhci_dwcmshc_dt_ids ,
} ,
. 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 " ) ;