2012-09-17 18:16:43 +00:00
/*
* Exynos Specific Extensions for Synopsys DW Multimedia Card Interface driver
*
* Copyright ( C ) 2012 , Samsung Electronics Co . , Ltd .
*
* 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 .
*/
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/clk.h>
# include <linux/mmc/host.h>
# include <linux/mmc/dw_mmc.h>
# include <linux/of.h>
# include <linux/of_gpio.h>
# include "dw_mmc.h"
# include "dw_mmc-pltfm.h"
# define NUM_PINS(x) (x + 2)
# define SDMMC_CLKSEL 0x09C
# define SDMMC_CLKSEL_CCLK_SAMPLE(x) (((x) & 7) << 0)
# define SDMMC_CLKSEL_CCLK_DRIVE(x) (((x) & 7) << 16)
# define SDMMC_CLKSEL_CCLK_DIVIDER(x) (((x) & 7) << 24)
# define SDMMC_CLKSEL_GET_DRV_WD3(x) (((x) >> 16) & 0x7)
# define SDMMC_CLKSEL_TIMING(x, y, z) (SDMMC_CLKSEL_CCLK_SAMPLE(x) | \
SDMMC_CLKSEL_CCLK_DRIVE ( y ) | \
SDMMC_CLKSEL_CCLK_DIVIDER ( z ) )
2013-08-31 00:11:21 +09:00
# define SDMMC_CLKSEL_WAKEUP_INT BIT(11)
2012-09-17 18:16:43 +00:00
# define EXYNOS4210_FIXED_CIU_CLK_DIV 2
# define EXYNOS4412_FIXED_CIU_CLK_DIV 4
2013-08-31 00:12:35 +09:00
/* Block number in eMMC */
# define DWMCI_BLOCK_NUM 0xFFFFFFFF
# define SDMMC_EMMCP_BASE 0x1000
# define SDMMC_MPSECURITY (SDMMC_EMMCP_BASE + 0x0010)
# define SDMMC_MPSBEGIN0 (SDMMC_EMMCP_BASE + 0x0200)
# define SDMMC_MPSEND0 (SDMMC_EMMCP_BASE + 0x0204)
# define SDMMC_MPSCTRL0 (SDMMC_EMMCP_BASE + 0x020C)
/* SMU control bits */
# define DWMCI_MPSCTRL_SECURE_READ_BIT BIT(7)
# define DWMCI_MPSCTRL_SECURE_WRITE_BIT BIT(6)
# define DWMCI_MPSCTRL_NON_SECURE_READ_BIT BIT(5)
# define DWMCI_MPSCTRL_NON_SECURE_WRITE_BIT BIT(4)
# define DWMCI_MPSCTRL_USE_FUSE_KEY BIT(3)
# define DWMCI_MPSCTRL_ECB_MODE BIT(2)
# define DWMCI_MPSCTRL_ENCRYPTION BIT(1)
# define DWMCI_MPSCTRL_VALID BIT(0)
2012-09-17 18:16:43 +00:00
/* Variations in Exynos specific dw-mshc controller */
enum dw_mci_exynos_type {
DW_MCI_TYPE_EXYNOS4210 ,
DW_MCI_TYPE_EXYNOS4412 ,
DW_MCI_TYPE_EXYNOS5250 ,
2013-05-24 15:34:32 +05:30
DW_MCI_TYPE_EXYNOS5420 ,
2013-08-31 00:12:35 +09:00
DW_MCI_TYPE_EXYNOS5420_SMU ,
2012-09-17 18:16:43 +00:00
} ;
/* Exynos implementation specific driver private data */
struct dw_mci_exynos_priv_data {
enum dw_mci_exynos_type ctrl_type ;
u8 ciu_div ;
u32 sdr_timing ;
u32 ddr_timing ;
} ;
static struct dw_mci_exynos_compatible {
char * compatible ;
enum dw_mci_exynos_type ctrl_type ;
} exynos_compat [ ] = {
{
. compatible = " samsung,exynos4210-dw-mshc " ,
. ctrl_type = DW_MCI_TYPE_EXYNOS4210 ,
} , {
. compatible = " samsung,exynos4412-dw-mshc " ,
. ctrl_type = DW_MCI_TYPE_EXYNOS4412 ,
} , {
. compatible = " samsung,exynos5250-dw-mshc " ,
. ctrl_type = DW_MCI_TYPE_EXYNOS5250 ,
2013-05-24 15:34:32 +05:30
} , {
. compatible = " samsung,exynos5420-dw-mshc " ,
. ctrl_type = DW_MCI_TYPE_EXYNOS5420 ,
2013-08-31 00:12:35 +09:00
} , {
. compatible = " samsung,exynos5420-dw-mshc-smu " ,
. ctrl_type = DW_MCI_TYPE_EXYNOS5420_SMU ,
2012-09-17 18:16:43 +00:00
} ,
} ;
static int dw_mci_exynos_priv_init ( struct dw_mci * host )
{
2013-08-31 00:11:57 +09:00
struct dw_mci_exynos_priv_data * priv = host - > priv ;
2012-09-17 18:16:43 +00:00
2013-08-31 00:12:35 +09:00
if ( priv - > ctrl_type = = DW_MCI_TYPE_EXYNOS5420_SMU ) {
mci_writel ( host , MPSBEGIN0 , 0 ) ;
mci_writel ( host , MPSEND0 , DWMCI_BLOCK_NUM ) ;
mci_writel ( host , MPSCTRL0 , DWMCI_MPSCTRL_SECURE_WRITE_BIT |
DWMCI_MPSCTRL_NON_SECURE_READ_BIT |
DWMCI_MPSCTRL_VALID |
DWMCI_MPSCTRL_NON_SECURE_WRITE_BIT ) ;
}
2012-09-17 18:16:43 +00:00
return 0 ;
}
static int dw_mci_exynos_setup_clock ( struct dw_mci * host )
{
struct dw_mci_exynos_priv_data * priv = host - > priv ;
2013-05-24 15:34:32 +05:30
if ( priv - > ctrl_type = = DW_MCI_TYPE_EXYNOS5250 | |
2013-08-31 00:12:35 +09:00
priv - > ctrl_type = = DW_MCI_TYPE_EXYNOS5420 | |
priv - > ctrl_type = = DW_MCI_TYPE_EXYNOS5420_SMU )
2012-09-17 18:16:43 +00:00
host - > bus_hz / = ( priv - > ciu_div + 1 ) ;
else if ( priv - > ctrl_type = = DW_MCI_TYPE_EXYNOS4412 )
host - > bus_hz / = EXYNOS4412_FIXED_CIU_CLK_DIV ;
else if ( priv - > ctrl_type = = DW_MCI_TYPE_EXYNOS4210 )
host - > bus_hz / = EXYNOS4210_FIXED_CIU_CLK_DIV ;
return 0 ;
}
2013-08-31 00:11:21 +09:00
# ifdef CONFIG_PM_SLEEP
static int dw_mci_exynos_suspend ( struct device * dev )
{
struct dw_mci * host = dev_get_drvdata ( dev ) ;
return dw_mci_suspend ( host ) ;
}
static int dw_mci_exynos_resume ( struct device * dev )
{
struct dw_mci * host = dev_get_drvdata ( dev ) ;
2013-08-31 00:12:35 +09:00
dw_mci_exynos_priv_init ( host ) ;
2013-08-31 00:11:21 +09:00
return dw_mci_resume ( host ) ;
}
/**
* dw_mci_exynos_resume_noirq - Exynos - specific resume code
*
* On exynos5420 there is a silicon errata that will sometimes leave the
* WAKEUP_INT bit in the CLKSEL register asserted . This bit is 1 to indicate
* that it fired and we can clear it by writing a 1 back . Clear it to prevent
* interrupts from going off constantly .
*
* We run this code on all exynos variants because it doesn ' t hurt .
*/
static int dw_mci_exynos_resume_noirq ( struct device * dev )
{
struct dw_mci * host = dev_get_drvdata ( dev ) ;
u32 clksel ;
clksel = mci_readl ( host , CLKSEL ) ;
if ( clksel & SDMMC_CLKSEL_WAKEUP_INT )
mci_writel ( host , CLKSEL , clksel ) ;
return 0 ;
}
# else
# define dw_mci_exynos_suspend NULL
# define dw_mci_exynos_resume NULL
# define dw_mci_exynos_resume_noirq NULL
# endif /* CONFIG_PM_SLEEP */
2012-09-17 18:16:43 +00:00
static void dw_mci_exynos_prepare_command ( struct dw_mci * host , u32 * cmdr )
{
/*
* Exynos4412 and Exynos5250 extends the use of CMD register with the
* use of bit 29 ( which is reserved on standard MSHC controllers ) for
* optionally bypassing the HOLD register for command and data . The
* HOLD register should be bypassed in case there is no phase shift
* applied on CMD / DATA that is sent to the card .
*/
if ( SDMMC_CLKSEL_GET_DRV_WD3 ( mci_readl ( host , CLKSEL ) ) )
* cmdr | = SDMMC_CMD_USE_HOLD_REG ;
}
static void dw_mci_exynos_set_ios ( struct dw_mci * host , struct mmc_ios * ios )
{
struct dw_mci_exynos_priv_data * priv = host - > priv ;
if ( ios - > timing = = MMC_TIMING_UHS_DDR50 )
mci_writel ( host , CLKSEL , priv - > ddr_timing ) ;
else
mci_writel ( host , CLKSEL , priv - > sdr_timing ) ;
}
static int dw_mci_exynos_parse_dt ( struct dw_mci * host )
{
2013-08-31 00:11:57 +09:00
struct dw_mci_exynos_priv_data * priv ;
2012-09-17 18:16:43 +00:00
struct device_node * np = host - > dev - > of_node ;
u32 timing [ 2 ] ;
u32 div = 0 ;
2013-08-31 00:11:57 +09:00
int idx ;
2012-09-17 18:16:43 +00:00
int ret ;
2013-08-31 00:11:57 +09:00
priv = devm_kzalloc ( host - > dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv ) {
dev_err ( host - > dev , " mem alloc failed for private data \n " ) ;
return - ENOMEM ;
}
for ( idx = 0 ; idx < ARRAY_SIZE ( exynos_compat ) ; idx + + ) {
if ( of_device_is_compatible ( np , exynos_compat [ idx ] . compatible ) )
priv - > ctrl_type = exynos_compat [ idx ] . ctrl_type ;
}
2012-09-17 18:16:43 +00:00
of_property_read_u32 ( np , " samsung,dw-mshc-ciu-div " , & div ) ;
priv - > ciu_div = div ;
ret = of_property_read_u32_array ( np ,
" samsung,dw-mshc-sdr-timing " , timing , 2 ) ;
if ( ret )
return ret ;
ret = of_property_read_u32_array ( np ,
" samsung,dw-mshc-ddr-timing " , timing , 2 ) ;
if ( ret )
return ret ;
2013-08-31 00:11:57 +09:00
priv - > sdr_timing = SDMMC_CLKSEL_TIMING ( timing [ 0 ] , timing [ 1 ] , div ) ;
2012-09-17 18:16:43 +00:00
priv - > ddr_timing = SDMMC_CLKSEL_TIMING ( timing [ 0 ] , timing [ 1 ] , div ) ;
2013-08-31 00:11:57 +09:00
host - > priv = priv ;
2012-09-17 18:16:43 +00:00
return 0 ;
}
2013-02-23 00:17:45 +09:00
/* Common capabilities of Exynos4/Exynos5 SoC */
static unsigned long exynos_dwmmc_caps [ 4 ] = {
2012-09-17 18:16:43 +00:00
MMC_CAP_UHS_DDR50 | MMC_CAP_1_8V_DDR |
MMC_CAP_8_BIT_DATA | MMC_CAP_CMD23 ,
MMC_CAP_CMD23 ,
MMC_CAP_CMD23 ,
MMC_CAP_CMD23 ,
} ;
2013-02-23 00:17:45 +09:00
static const struct dw_mci_drv_data exynos_drv_data = {
. caps = exynos_dwmmc_caps ,
2012-09-17 18:16:43 +00:00
. init = dw_mci_exynos_priv_init ,
. setup_clock = dw_mci_exynos_setup_clock ,
. prepare_command = dw_mci_exynos_prepare_command ,
. set_ios = dw_mci_exynos_set_ios ,
. parse_dt = dw_mci_exynos_parse_dt ,
} ;
static const struct of_device_id dw_mci_exynos_match [ ] = {
2013-02-23 00:17:45 +09:00
{ . compatible = " samsung,exynos4412-dw-mshc " ,
. data = & exynos_drv_data , } ,
2012-09-17 18:16:43 +00:00
{ . compatible = " samsung,exynos5250-dw-mshc " ,
2013-02-23 00:17:45 +09:00
. data = & exynos_drv_data , } ,
2013-05-24 15:34:32 +05:30
{ . compatible = " samsung,exynos5420-dw-mshc " ,
. data = & exynos_drv_data , } ,
2013-08-31 00:12:35 +09:00
{ . compatible = " samsung,exynos5420-dw-mshc-smu " ,
. data = & exynos_drv_data , } ,
2012-09-17 18:16:43 +00:00
{ } ,
} ;
2012-11-06 22:55:30 +01:00
MODULE_DEVICE_TABLE ( of , dw_mci_exynos_match ) ;
2012-09-17 18:16:43 +00:00
2013-02-18 14:23:08 +05:30
static int dw_mci_exynos_probe ( struct platform_device * pdev )
2012-09-17 18:16:43 +00:00
{
2012-11-06 22:55:31 +01:00
const struct dw_mci_drv_data * drv_data ;
2012-09-17 18:16:43 +00:00
const struct of_device_id * match ;
match = of_match_node ( dw_mci_exynos_match , pdev - > dev . of_node ) ;
drv_data = match - > data ;
return dw_mci_pltfm_register ( pdev , drv_data ) ;
}
2013-08-31 00:11:21 +09:00
const struct dev_pm_ops dw_mci_exynos_pmops = {
SET_SYSTEM_SLEEP_PM_OPS ( dw_mci_exynos_suspend , dw_mci_exynos_resume )
. resume_noirq = dw_mci_exynos_resume_noirq ,
. thaw_noirq = dw_mci_exynos_resume_noirq ,
. restore_noirq = dw_mci_exynos_resume_noirq ,
} ;
2012-09-17 18:16:43 +00:00
static struct platform_driver dw_mci_exynos_pltfm_driver = {
. probe = dw_mci_exynos_probe ,
. remove = __exit_p ( dw_mci_pltfm_remove ) ,
. driver = {
. name = " dwmmc_exynos " ,
2013-02-18 14:23:09 +05:30
. of_match_table = dw_mci_exynos_match ,
2013-08-31 00:11:21 +09:00
. pm = & dw_mci_exynos_pmops ,
2012-09-17 18:16:43 +00:00
} ,
} ;
module_platform_driver ( dw_mci_exynos_pltfm_driver ) ;
MODULE_DESCRIPTION ( " Samsung Specific DW-MSHC Driver Extension " ) ;
MODULE_AUTHOR ( " Thomas Abraham <thomas.ab@samsung.com " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform:dwmmc-exynos " ) ;