2019-06-01 10:08:55 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2015-01-20 16:05:18 +08: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 >
2022-11-11 17:10:31 +09:00
* Copyright ( C ) 2019 Socionext Inc .
2015-01-20 16:05:18 +08:00
*/
2018-01-08 15:44:19 +00:00
# include <linux/acpi.h>
2015-01-20 16:05:18 +08:00
# include <linux/err.h>
# include <linux/delay.h>
# include <linux/module.h>
2018-01-08 15:44:19 +00:00
# include <linux/of.h>
2017-11-06 15:29:23 +00:00
# include <linux/property.h>
2015-01-20 16:05:18 +08:00
# include <linux/clk.h>
2022-11-11 17:10:29 +09:00
# include <linux/reset.h>
2015-01-20 16:05:18 +08:00
# include "sdhci-pltfm.h"
2019-09-10 10:41:06 +09:00
# include "sdhci_f_sdh30.h"
2015-01-20 16:05:18 +08:00
struct f_sdhost_priv {
struct clk * clk_iface ;
struct clk * clk ;
2022-11-11 17:10:29 +09:00
struct reset_control * rst ;
2015-01-20 16:05:18 +08:00
u32 vendor_hs200 ;
struct device * dev ;
2017-11-06 15:29:23 +00:00
bool enable_cmd_dat_delay ;
2015-01-20 16:05:18 +08:00
} ;
2023-06-30 09:45:33 +09:00
static void * sdhci_f_sdhost_priv ( struct sdhci_host * host )
{
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
return sdhci_pltfm_priv ( pltfm_host ) ;
}
2015-04-24 15:00:29 +08:00
static void sdhci_f_sdh30_soft_voltage_switch ( struct sdhci_host * host )
2015-01-20 16:05:18 +08:00
{
2023-06-30 09:45:33 +09:00
struct f_sdhost_priv * priv = sdhci_f_sdhost_priv ( host ) ;
2015-01-20 16:05:18 +08:00
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 15:00:29 +08:00
static unsigned int sdhci_f_sdh30_get_min_clock ( struct sdhci_host * host )
2015-01-20 16:05:18 +08:00
{
return F_SDH30_MIN_CLOCK ;
}
2015-04-24 15:00:29 +08:00
static void sdhci_f_sdh30_reset ( struct sdhci_host * host , u8 mask )
2015-01-20 16:05:18 +08:00
{
2023-06-30 09:45:33 +09:00
struct f_sdhost_priv * priv = sdhci_f_sdhost_priv ( host ) ;
2017-11-06 15:29:23 +00:00
u32 ctl ;
2015-01-20 16:05:18 +08:00
if ( sdhci_readw ( host , SDHCI_CLOCK_CONTROL ) = = 0 )
sdhci_writew ( host , 0xBC01 , SDHCI_CLOCK_CONTROL ) ;
sdhci_reset ( host , mask ) ;
2017-11-06 15:29:23 +00:00
if ( priv - > enable_cmd_dat_delay ) {
ctl = sdhci_readl ( host , F_SDH30_ESD_CONTROL ) ;
ctl | = F_SDH30_CMD_DAT_DELAY ;
sdhci_writel ( host , ctl , F_SDH30_ESD_CONTROL ) ;
}
2022-11-11 17:10:32 +09:00
if ( ( host - > mmc - > caps & MMC_CAP_NONREMOVABLE ) & &
! ( sdhci_readl ( host , SDHCI_PRESENT_STATE ) & SDHCI_CARD_PRESENT ) ) {
ctl = sdhci_readl ( host , F_SDH30_TEST ) ;
ctl | = F_SDH30_FORCE_CARD_INSERT ;
sdhci_writel ( host , ctl , F_SDH30_TEST ) ;
}
2015-01-20 16:05:18 +08:00
}
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 ,
} ;
2023-06-30 09:45:33 +09:00
static const struct sdhci_pltfm_data sdhci_f_sdh30_pltfm_data = {
. ops = & sdhci_f_sdh30_ops ,
. quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC
| SDHCI_QUIRK_INVERTED_WRITE_PROTECT ,
. quirks2 = SDHCI_QUIRK2_SUPPORT_SINGLE
| SDHCI_QUIRK2_TUNING_WORK_AROUND ,
} ;
2015-01-20 16:05:18 +08:00
static int sdhci_f_sdh30_probe ( struct platform_device * pdev )
{
struct sdhci_host * host ;
struct device * dev = & pdev - > dev ;
2023-06-30 09:45:33 +09:00
int ctrl = 0 , ret = 0 ;
2015-01-20 16:05:18 +08:00
struct f_sdhost_priv * priv ;
2023-06-30 09:45:33 +09:00
struct sdhci_pltfm_host * pltfm_host ;
2015-01-20 16:05:18 +08:00
u32 reg = 0 ;
2023-06-30 09:45:33 +09:00
host = sdhci_pltfm_init ( pdev , & sdhci_f_sdh30_pltfm_data ,
sizeof ( struct f_sdhost_priv ) ) ;
2015-01-20 16:05:18 +08:00
if ( IS_ERR ( host ) )
return PTR_ERR ( host ) ;
2023-06-30 09:45:33 +09:00
pltfm_host = sdhci_priv ( host ) ;
priv = sdhci_pltfm_priv ( pltfm_host ) ;
2015-01-20 16:05:18 +08:00
priv - > dev = dev ;
2017-11-06 15:29:23 +00:00
priv - > enable_cmd_dat_delay = device_property_read_bool ( dev ,
" fujitsu,cmd-dat-delay-select " ) ;
2015-01-20 16:05:18 +08:00
ret = mmc_of_parse ( host - > mmc ) ;
if ( ret )
goto err ;
2018-01-08 15:44:19 +00:00
if ( dev_of_node ( dev ) ) {
sdhci_get_of_property ( pdev ) ;
2015-01-20 16:05:18 +08:00
2018-01-08 15:44:19 +00:00
priv - > clk_iface = devm_clk_get ( & pdev - > dev , " iface " ) ;
if ( IS_ERR ( priv - > clk_iface ) ) {
ret = PTR_ERR ( priv - > clk_iface ) ;
goto err ;
}
2015-01-20 16:05:18 +08:00
2018-01-08 15:44:19 +00:00
ret = clk_prepare_enable ( priv - > clk_iface ) ;
if ( ret )
goto err ;
2015-01-20 16:05:18 +08:00
2018-01-08 15:44:19 +00:00
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 ;
2022-11-11 17:10:29 +09:00
priv - > rst = devm_reset_control_get_optional_shared ( dev , NULL ) ;
if ( IS_ERR ( priv - > rst ) ) {
ret = PTR_ERR ( priv - > rst ) ;
goto err_rst ;
}
ret = reset_control_deassert ( priv - > rst ) ;
if ( ret )
goto err_rst ;
2018-01-08 15:44:19 +00:00
}
2015-01-20 16:05:18 +08:00
/* 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 ;
2022-11-11 17:10:33 +09:00
if ( ! ( reg & SDHCI_TIMEOUT_CLK_MASK ) )
host - > quirks | = SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK ;
2015-01-20 16:05:18 +08:00
ret = sdhci_add_host ( host ) ;
if ( ret )
goto err_add_host ;
return 0 ;
err_add_host :
2022-11-11 17:10:29 +09:00
reset_control_assert ( priv - > rst ) ;
err_rst :
2015-01-20 16:05:18 +08:00
clk_disable_unprepare ( priv - > clk ) ;
err_clk :
clk_disable_unprepare ( priv - > clk_iface ) ;
err :
2023-06-30 09:45:33 +09:00
sdhci_pltfm_free ( pdev ) ;
2015-01-20 16:05:18 +08:00
return ret ;
}
static int sdhci_f_sdh30_remove ( struct platform_device * pdev )
{
struct sdhci_host * host = platform_get_drvdata ( pdev ) ;
2023-06-30 09:45:33 +09:00
struct f_sdhost_priv * priv = sdhci_f_sdhost_priv ( host ) ;
2015-01-20 16:05:18 +08:00
2022-11-11 17:10:29 +09:00
reset_control_assert ( priv - > rst ) ;
2015-01-20 16:05:18 +08:00
clk_disable_unprepare ( priv - > clk ) ;
2022-11-11 17:10:29 +09:00
clk_disable_unprepare ( priv - > clk_iface ) ;
2015-01-20 16:05:18 +08:00
2023-06-30 09:45:33 +09:00
sdhci_pltfm_unregister ( pdev ) ;
2015-01-20 16:05:18 +08:00
return 0 ;
}
2018-01-08 15:44:19 +00:00
# ifdef CONFIG_OF
2015-01-20 16:05:18 +08:00
static const struct of_device_id f_sdh30_dt_ids [ ] = {
{ . compatible = " fujitsu,mb86s70-sdhci-3.0 " } ,
2022-11-11 17:10:31 +09:00
{ . compatible = " socionext,f-sdh30-e51-mmc " } ,
2015-01-20 16:05:18 +08:00
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , f_sdh30_dt_ids ) ;
2018-01-08 15:44:19 +00:00
# endif
# ifdef CONFIG_ACPI
static const struct acpi_device_id f_sdh30_acpi_ids [ ] = {
{ " SCX0002 " } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( acpi , f_sdh30_acpi_ids ) ;
# endif
2015-01-20 16:05:18 +08:00
static struct platform_driver sdhci_f_sdh30_driver = {
. driver = {
. name = " f_sdh30 " ,
2020-09-03 16:24:36 -07:00
. probe_type = PROBE_PREFER_ASYNCHRONOUS ,
2018-01-08 15:44:19 +00:00
. of_match_table = of_match_ptr ( f_sdh30_dt_ids ) ,
. acpi_match_table = ACPI_PTR ( f_sdh30_acpi_ids ) ,
2016-07-27 13:07:21 +02:00
. pm = & sdhci_pltfm_pmops ,
2015-01-20 16:05:18 +08: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 " ) ;
2022-11-11 17:10:31 +09:00
MODULE_AUTHOR ( " FUJITSU SEMICONDUCTOR LTD., Socionext Inc. " ) ;
2015-01-20 16:05:18 +08:00
MODULE_ALIAS ( " platform:f_sdh30 " ) ;