2015-01-20 11:05:18 +03:00
/*
* linux / drivers / mmc / host / sdhci_f_sdh30 . c
*
* Copyright ( C ) 2013 - 2015 Fujitsu Semiconductor , Ltd
* Vincent Yang < vincent . yang @ tw . fujitsu . com >
* Copyright ( C ) 2015 Linaro Ltd Andy Green < andy . green @ linaro . org >
*
* 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 , version 2 of the License .
*/
# include <linux/err.h>
# include <linux/delay.h>
# include <linux/module.h>
# include <linux/clk.h>
# include "sdhci-pltfm.h"
/* F_SDH30 extended Controller registers */
# define F_SDH30_AHB_CONFIG 0x100
# define F_SDH30_AHB_BIGED 0x00000040
# define F_SDH30_BUSLOCK_DMA 0x00000020
# define F_SDH30_BUSLOCK_EN 0x00000010
# define F_SDH30_SIN 0x00000008
# define F_SDH30_AHB_INCR_16 0x00000004
# define F_SDH30_AHB_INCR_8 0x00000002
# define F_SDH30_AHB_INCR_4 0x00000001
# define F_SDH30_TUNING_SETTING 0x108
# define F_SDH30_CMD_CHK_DIS 0x00010000
# define F_SDH30_IO_CONTROL2 0x114
# define F_SDH30_CRES_O_DN 0x00080000
# define F_SDH30_MSEL_O_1_8 0x00040000
# define F_SDH30_ESD_CONTROL 0x124
# define F_SDH30_EMMC_RST 0x00000002
# define F_SDH30_EMMC_HS200 0x01000000
# define F_SDH30_CMD_DAT_DELAY 0x200
# define F_SDH30_MIN_CLOCK 400000
struct f_sdhost_priv {
struct clk * clk_iface ;
struct clk * clk ;
u32 vendor_hs200 ;
struct device * dev ;
} ;
2015-04-24 10:00:29 +03:00
static void sdhci_f_sdh30_soft_voltage_switch ( struct sdhci_host * host )
2015-01-20 11:05:18 +03:00
{
struct f_sdhost_priv * priv = sdhci_priv ( host ) ;
u32 ctrl = 0 ;
usleep_range ( 2500 , 3000 ) ;
ctrl = sdhci_readl ( host , F_SDH30_IO_CONTROL2 ) ;
ctrl | = F_SDH30_CRES_O_DN ;
sdhci_writel ( host , ctrl , F_SDH30_IO_CONTROL2 ) ;
ctrl | = F_SDH30_MSEL_O_1_8 ;
sdhci_writel ( host , ctrl , F_SDH30_IO_CONTROL2 ) ;
ctrl & = ~ F_SDH30_CRES_O_DN ;
sdhci_writel ( host , ctrl , F_SDH30_IO_CONTROL2 ) ;
usleep_range ( 2500 , 3000 ) ;
if ( priv - > vendor_hs200 ) {
dev_info ( priv - > dev , " %s: setting hs200 \n " , __func__ ) ;
ctrl = sdhci_readl ( host , F_SDH30_ESD_CONTROL ) ;
ctrl | = priv - > vendor_hs200 ;
sdhci_writel ( host , ctrl , F_SDH30_ESD_CONTROL ) ;
}
ctrl = sdhci_readl ( host , F_SDH30_TUNING_SETTING ) ;
ctrl | = F_SDH30_CMD_CHK_DIS ;
sdhci_writel ( host , ctrl , F_SDH30_TUNING_SETTING ) ;
}
2015-04-24 10:00:29 +03:00
static unsigned int sdhci_f_sdh30_get_min_clock ( struct sdhci_host * host )
2015-01-20 11:05:18 +03:00
{
return F_SDH30_MIN_CLOCK ;
}
2015-04-24 10:00:29 +03:00
static void sdhci_f_sdh30_reset ( struct sdhci_host * host , u8 mask )
2015-01-20 11:05:18 +03:00
{
if ( sdhci_readw ( host , SDHCI_CLOCK_CONTROL ) = = 0 )
sdhci_writew ( host , 0xBC01 , SDHCI_CLOCK_CONTROL ) ;
sdhci_reset ( host , mask ) ;
}
static const struct sdhci_ops sdhci_f_sdh30_ops = {
. voltage_switch = sdhci_f_sdh30_soft_voltage_switch ,
. get_min_clock = sdhci_f_sdh30_get_min_clock ,
. reset = sdhci_f_sdh30_reset ,
. set_clock = sdhci_set_clock ,
. set_bus_width = sdhci_set_bus_width ,
. set_uhs_signaling = sdhci_set_uhs_signaling ,
} ;
static int sdhci_f_sdh30_probe ( struct platform_device * pdev )
{
struct sdhci_host * host ;
struct device * dev = & pdev - > dev ;
struct resource * res ;
int irq , ctrl = 0 , ret = 0 ;
struct f_sdhost_priv * priv ;
u32 reg = 0 ;
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < 0 ) {
dev_err ( dev , " %s: no irq specified \n " , __func__ ) ;
return irq ;
}
2015-04-24 10:02:00 +03:00
host = sdhci_alloc_host ( dev , sizeof ( struct f_sdhost_priv ) ) ;
2015-01-20 11:05:18 +03:00
if ( IS_ERR ( host ) )
return PTR_ERR ( host ) ;
priv = sdhci_priv ( host ) ;
priv - > dev = dev ;
host - > quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC |
SDHCI_QUIRK_INVERTED_WRITE_PROTECT ;
host - > quirks2 = SDHCI_QUIRK2_SUPPORT_SINGLE |
SDHCI_QUIRK2_TUNING_WORK_AROUND ;
ret = mmc_of_parse ( host - > mmc ) ;
if ( ret )
goto err ;
platform_set_drvdata ( pdev , host ) ;
sdhci_get_of_property ( pdev ) ;
host - > hw_name = " f_sdh30 " ;
host - > ops = & sdhci_f_sdh30_ops ;
host - > irq = irq ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
host - > ioaddr = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( host - > ioaddr ) ) {
2015-01-21 14:19:45 +03:00
ret = PTR_ERR ( host - > ioaddr ) ;
2015-01-20 11:05:18 +03:00
goto err ;
}
priv - > clk_iface = devm_clk_get ( & pdev - > dev , " iface " ) ;
if ( IS_ERR ( priv - > clk_iface ) ) {
ret = PTR_ERR ( priv - > clk_iface ) ;
goto err ;
}
ret = clk_prepare_enable ( priv - > clk_iface ) ;
if ( ret )
goto err ;
priv - > clk = devm_clk_get ( & pdev - > dev , " core " ) ;
if ( IS_ERR ( priv - > clk ) ) {
ret = PTR_ERR ( priv - > clk ) ;
goto err_clk ;
}
ret = clk_prepare_enable ( priv - > clk ) ;
if ( ret )
goto err_clk ;
/* init vendor specific regs */
ctrl = sdhci_readw ( host , F_SDH30_AHB_CONFIG ) ;
ctrl | = F_SDH30_SIN | F_SDH30_AHB_INCR_16 | F_SDH30_AHB_INCR_8 |
F_SDH30_AHB_INCR_4 ;
ctrl & = ~ ( F_SDH30_AHB_BIGED | F_SDH30_BUSLOCK_EN ) ;
sdhci_writew ( host , ctrl , F_SDH30_AHB_CONFIG ) ;
reg = sdhci_readl ( host , F_SDH30_ESD_CONTROL ) ;
sdhci_writel ( host , reg & ~ F_SDH30_EMMC_RST , F_SDH30_ESD_CONTROL ) ;
msleep ( 20 ) ;
sdhci_writel ( host , reg | F_SDH30_EMMC_RST , F_SDH30_ESD_CONTROL ) ;
reg = sdhci_readl ( host , SDHCI_CAPABILITIES ) ;
if ( reg & SDHCI_CAN_DO_8BIT )
priv - > vendor_hs200 = F_SDH30_EMMC_HS200 ;
ret = sdhci_add_host ( host ) ;
if ( ret )
goto err_add_host ;
return 0 ;
err_add_host :
clk_disable_unprepare ( priv - > clk ) ;
err_clk :
clk_disable_unprepare ( priv - > clk_iface ) ;
err :
sdhci_free_host ( host ) ;
return ret ;
}
static int sdhci_f_sdh30_remove ( struct platform_device * pdev )
{
struct sdhci_host * host = platform_get_drvdata ( pdev ) ;
struct f_sdhost_priv * priv = sdhci_priv ( host ) ;
sdhci_remove_host ( host , readl ( host - > ioaddr + SDHCI_INT_STATUS ) = =
0xffffffff ) ;
clk_disable_unprepare ( priv - > clk_iface ) ;
clk_disable_unprepare ( priv - > clk ) ;
sdhci_free_host ( host ) ;
platform_set_drvdata ( pdev , NULL ) ;
return 0 ;
}
static const struct of_device_id f_sdh30_dt_ids [ ] = {
{ . compatible = " fujitsu,mb86s70-sdhci-3.0 " } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , f_sdh30_dt_ids ) ;
static struct platform_driver sdhci_f_sdh30_driver = {
. driver = {
. name = " f_sdh30 " ,
. of_match_table = f_sdh30_dt_ids ,
2016-07-27 14:07:21 +03:00
. pm = & sdhci_pltfm_pmops ,
2015-01-20 11:05:18 +03:00
} ,
. probe = sdhci_f_sdh30_probe ,
. remove = sdhci_f_sdh30_remove ,
} ;
module_platform_driver ( sdhci_f_sdh30_driver ) ;
MODULE_DESCRIPTION ( " F_SDH30 SD Card Controller driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_AUTHOR ( " FUJITSU SEMICONDUCTOR LTD. " ) ;
MODULE_ALIAS ( " platform:f_sdh30 " ) ;