2015-02-10 03:06:30 +03:00
/*
* Copyright ( C ) 2014 Broadcom Corporation
*
* 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.
*
* This program is distributed " as is " WITHOUT ANY WARRANTY of any
* kind , whether express or implied ; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*/
/*
* iProc SDHCI platform driver
*/
2018-08-05 10:52:52 +03:00
# include <linux/acpi.h>
2015-02-10 03:06:30 +03:00
# include <linux/delay.h>
# include <linux/module.h>
# include <linux/mmc/host.h>
# include <linux/of.h>
# include <linux/of_device.h>
# include "sdhci-pltfm.h"
struct sdhci_iproc_data {
const struct sdhci_pltfm_data * pdata ;
u32 caps ;
u32 caps1 ;
2016-01-28 01:25:40 +03:00
u32 mmc_caps ;
2015-02-10 03:06:30 +03:00
} ;
struct sdhci_iproc_host {
const struct sdhci_iproc_data * data ;
u32 shadow_cmd ;
u32 shadow_blk ;
2018-05-19 01:03:56 +03:00
bool is_cmd_shadowed ;
bool is_blk_shadowed ;
2015-02-10 03:06:30 +03:00
} ;
# define REG_OFFSET_IN_BITS(reg) ((reg) << 3 & 0x18)
static inline u32 sdhci_iproc_readl ( struct sdhci_host * host , int reg )
{
u32 val = readl ( host - > ioaddr + reg ) ;
pr_debug ( " %s: readl [0x%02x] 0x%08x \n " ,
mmc_hostname ( host - > mmc ) , reg , val ) ;
return val ;
}
static u16 sdhci_iproc_readw ( struct sdhci_host * host , int reg )
{
2018-05-19 01:03:56 +03:00
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
struct sdhci_iproc_host * iproc_host = sdhci_pltfm_priv ( pltfm_host ) ;
u32 val ;
u16 word ;
if ( ( reg = = SDHCI_TRANSFER_MODE ) & & iproc_host - > is_cmd_shadowed ) {
/* Get the saved transfer mode */
val = iproc_host - > shadow_cmd ;
} else if ( ( reg = = SDHCI_BLOCK_SIZE | | reg = = SDHCI_BLOCK_COUNT ) & &
iproc_host - > is_blk_shadowed ) {
/* Get the saved block info */
val = iproc_host - > shadow_blk ;
} else {
val = sdhci_iproc_readl ( host , ( reg & ~ 3 ) ) ;
}
word = val > > REG_OFFSET_IN_BITS ( reg ) & 0xffff ;
2015-02-10 03:06:30 +03:00
return word ;
}
static u8 sdhci_iproc_readb ( struct sdhci_host * host , int reg )
{
u32 val = sdhci_iproc_readl ( host , ( reg & ~ 3 ) ) ;
u8 byte = val > > REG_OFFSET_IN_BITS ( reg ) & 0xff ;
return byte ;
}
static inline void sdhci_iproc_writel ( struct sdhci_host * host , u32 val , int reg )
{
pr_debug ( " %s: writel [0x%02x] 0x%08x \n " ,
mmc_hostname ( host - > mmc ) , reg , val ) ;
writel ( val , host - > ioaddr + reg ) ;
if ( host - > clock < = 400000 ) {
/* Round up to micro-second four SD clock delay */
if ( host - > clock )
udelay ( ( 4 * 1000000 + host - > clock - 1 ) / host - > clock ) ;
else
udelay ( 10 ) ;
}
}
/*
* The Arasan has a bugette whereby it may lose the content of successive
* writes to the same register that are within two SD - card clock cycles of
* each other ( a clock domain crossing problem ) . The data
* register does not have this problem , which is just as well - otherwise we ' d
* have to nobble the DMA engine too .
*
* This wouldn ' t be a problem with the code except that we can only write the
* controller with 32 - bit writes . So two different 16 - bit registers are
* written back to back creates the problem .
*
* In reality , this only happens when SDHCI_BLOCK_SIZE and SDHCI_BLOCK_COUNT
* are written followed by SDHCI_TRANSFER_MODE and SDHCI_COMMAND .
* The BLOCK_SIZE and BLOCK_COUNT are meaningless until a command issued so
* the work around can be further optimized . We can keep shadow values of
* BLOCK_SIZE , BLOCK_COUNT , and TRANSFER_MODE until a COMMAND is issued .
* Then , write the BLOCK_SIZE + BLOCK_COUNT in a single 32 - bit write followed
* by the TRANSFER + COMMAND in another 32 - bit write .
*/
static void sdhci_iproc_writew ( struct sdhci_host * host , u16 val , int reg )
{
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
2015-03-13 04:11:01 +03:00
struct sdhci_iproc_host * iproc_host = sdhci_pltfm_priv ( pltfm_host ) ;
2015-02-10 03:06:30 +03:00
u32 word_shift = REG_OFFSET_IN_BITS ( reg ) ;
u32 mask = 0xffff < < word_shift ;
u32 oldval , newval ;
if ( reg = = SDHCI_COMMAND ) {
/* Write the block now as we are issuing a command */
2018-05-19 01:03:56 +03:00
if ( iproc_host - > is_blk_shadowed ) {
2015-02-10 03:06:30 +03:00
sdhci_iproc_writel ( host , iproc_host - > shadow_blk ,
SDHCI_BLOCK_SIZE ) ;
2018-05-19 01:03:56 +03:00
iproc_host - > is_blk_shadowed = false ;
2015-02-10 03:06:30 +03:00
}
oldval = iproc_host - > shadow_cmd ;
2018-05-19 01:03:56 +03:00
iproc_host - > is_cmd_shadowed = false ;
} else if ( ( reg = = SDHCI_BLOCK_SIZE | | reg = = SDHCI_BLOCK_COUNT ) & &
iproc_host - > is_blk_shadowed ) {
2015-02-10 03:06:30 +03:00
/* Block size and count are stored in shadow reg */
oldval = iproc_host - > shadow_blk ;
} else {
/* Read reg, all other registers are not shadowed */
oldval = sdhci_iproc_readl ( host , ( reg & ~ 3 ) ) ;
}
newval = ( oldval & ~ mask ) | ( val < < word_shift ) ;
if ( reg = = SDHCI_TRANSFER_MODE ) {
/* Save the transfer mode until the command is issued */
iproc_host - > shadow_cmd = newval ;
2018-05-19 01:03:56 +03:00
iproc_host - > is_cmd_shadowed = true ;
2015-02-10 03:06:30 +03:00
} else if ( reg = = SDHCI_BLOCK_SIZE | | reg = = SDHCI_BLOCK_COUNT ) {
/* Save the block info until the command is issued */
iproc_host - > shadow_blk = newval ;
2018-05-19 01:03:56 +03:00
iproc_host - > is_blk_shadowed = true ;
2015-02-10 03:06:30 +03:00
} else {
/* Command or other regular 32-bit write */
sdhci_iproc_writel ( host , newval , reg & ~ 3 ) ;
}
}
static void sdhci_iproc_writeb ( struct sdhci_host * host , u8 val , int reg )
{
u32 oldval = sdhci_iproc_readl ( host , ( reg & ~ 3 ) ) ;
u32 byte_shift = REG_OFFSET_IN_BITS ( reg ) ;
u32 mask = 0xff < < byte_shift ;
u32 newval = ( oldval & ~ mask ) | ( val < < byte_shift ) ;
sdhci_iproc_writel ( host , newval , reg & ~ 3 ) ;
}
2018-08-05 10:52:52 +03:00
static unsigned int sdhci_iproc_get_max_clock ( struct sdhci_host * host )
{
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
if ( pltfm_host - > clk )
return sdhci_pltfm_clk_get_max_clock ( host ) ;
else
return pltfm_host - > clock ;
}
2015-02-10 03:06:30 +03:00
static const struct sdhci_ops sdhci_iproc_ops = {
2016-11-01 19:37:57 +03:00
. set_clock = sdhci_set_clock ,
2018-08-05 10:52:52 +03:00
. get_max_clock = sdhci_iproc_get_max_clock ,
2016-11-01 19:37:57 +03:00
. set_bus_width = sdhci_set_bus_width ,
. reset = sdhci_reset ,
. set_uhs_signaling = sdhci_set_uhs_signaling ,
} ;
static const struct sdhci_ops sdhci_iproc_32only_ops = {
2015-02-10 03:06:30 +03:00
. read_l = sdhci_iproc_readl ,
. read_w = sdhci_iproc_readw ,
. read_b = sdhci_iproc_readb ,
. write_l = sdhci_iproc_writel ,
. write_w = sdhci_iproc_writew ,
. write_b = sdhci_iproc_writeb ,
. set_clock = sdhci_set_clock ,
2018-08-05 10:52:52 +03:00
. get_max_clock = sdhci_iproc_get_max_clock ,
2015-02-10 03:06:30 +03:00
. set_bus_width = sdhci_set_bus_width ,
. reset = sdhci_reset ,
. set_uhs_signaling = sdhci_set_uhs_signaling ,
} ;
2016-11-01 19:37:57 +03:00
static const struct sdhci_pltfm_data sdhci_iproc_cygnus_pltfm_data = {
2019-05-09 20:24:26 +03:00
. quirks = SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK |
SDHCI_QUIRK_NO_HISPD_BIT ,
2018-05-19 01:03:57 +03:00
. quirks2 = SDHCI_QUIRK2_ACMD23_BROKEN | SDHCI_QUIRK2_HOST_OFF_CARD_ON ,
2016-11-01 19:37:57 +03:00
. ops = & sdhci_iproc_32only_ops ,
} ;
static const struct sdhci_iproc_data iproc_cygnus_data = {
. pdata = & sdhci_iproc_cygnus_pltfm_data ,
. caps = ( ( 0x1 < < SDHCI_MAX_BLOCK_SHIFT )
& SDHCI_MAX_BLOCK_MASK ) |
SDHCI_CAN_VDD_330 |
SDHCI_CAN_VDD_180 |
SDHCI_CAN_DO_SUSPEND |
SDHCI_CAN_DO_HISPD |
SDHCI_CAN_DO_ADMA2 |
SDHCI_CAN_DO_SDMA ,
. caps1 = SDHCI_DRIVER_TYPE_C |
SDHCI_DRIVER_TYPE_D |
SDHCI_SUPPORT_DDR50 ,
. mmc_caps = MMC_CAP_1_8V_DDR ,
} ;
2015-02-10 03:06:30 +03:00
static const struct sdhci_pltfm_data sdhci_iproc_pltfm_data = {
2017-05-18 19:57:40 +03:00
. quirks = SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK |
2019-05-09 20:24:27 +03:00
SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 |
SDHCI_QUIRK_NO_HISPD_BIT ,
2015-02-10 03:06:30 +03:00
. quirks2 = SDHCI_QUIRK2_ACMD23_BROKEN ,
. ops = & sdhci_iproc_ops ,
} ;
static const struct sdhci_iproc_data iproc_data = {
. pdata = & sdhci_iproc_pltfm_data ,
2016-07-02 22:23:14 +03:00
. caps = ( ( 0x1 < < SDHCI_MAX_BLOCK_SHIFT )
& SDHCI_MAX_BLOCK_MASK ) |
SDHCI_CAN_VDD_330 |
SDHCI_CAN_VDD_180 |
SDHCI_CAN_DO_SUSPEND |
SDHCI_CAN_DO_HISPD |
SDHCI_CAN_DO_ADMA2 |
SDHCI_CAN_DO_SDMA ,
. caps1 = SDHCI_DRIVER_TYPE_C |
SDHCI_DRIVER_TYPE_D |
SDHCI_SUPPORT_DDR50 ,
2015-02-10 03:06:30 +03:00
} ;
2016-01-28 01:25:41 +03:00
static const struct sdhci_pltfm_data sdhci_bcm2835_pltfm_data = {
. quirks = SDHCI_QUIRK_BROKEN_CARD_DETECTION |
SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK |
2016-12-30 18:24:32 +03:00
SDHCI_QUIRK_MISSING_CAPS |
SDHCI_QUIRK_NO_HISPD_BIT ,
2018-02-12 23:02:44 +03:00
. quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN ,
2016-11-01 19:37:57 +03:00
. ops = & sdhci_iproc_32only_ops ,
2016-01-28 01:25:41 +03:00
} ;
static const struct sdhci_iproc_data bcm2835_data = {
. pdata = & sdhci_bcm2835_pltfm_data ,
2016-12-30 18:24:33 +03:00
. caps = ( ( 0x1 < < SDHCI_MAX_BLOCK_SHIFT )
& SDHCI_MAX_BLOCK_MASK ) |
SDHCI_CAN_VDD_330 |
2016-12-30 18:24:32 +03:00
SDHCI_CAN_DO_HISPD ,
. caps1 = SDHCI_DRIVER_TYPE_A |
SDHCI_DRIVER_TYPE_C ,
2016-01-28 01:25:41 +03:00
. mmc_caps = 0x00000000 ,
} ;
2019-07-22 09:23:23 +03:00
static const struct sdhci_pltfm_data sdhci_bcm2711_pltfm_data = {
. ops = & sdhci_iproc_32only_ops ,
} ;
static const struct sdhci_iproc_data bcm2711_data = {
. pdata = & sdhci_bcm2711_pltfm_data ,
} ;
2015-02-10 03:06:30 +03:00
static const struct of_device_id sdhci_iproc_of_match [ ] = {
2016-01-28 01:25:41 +03:00
{ . compatible = " brcm,bcm2835-sdhci " , . data = & bcm2835_data } ,
2019-07-22 09:23:23 +03:00
{ . compatible = " brcm,bcm2711-emmc2 " , . data = & bcm2711_data } ,
2016-11-01 19:37:57 +03:00
{ . compatible = " brcm,sdhci-iproc-cygnus " , . data = & iproc_cygnus_data } ,
{ . compatible = " brcm,sdhci-iproc " , . data = & iproc_data } ,
2015-02-10 03:06:30 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , sdhci_iproc_of_match ) ;
2018-08-05 10:52:52 +03:00
static const struct acpi_device_id sdhci_iproc_acpi_ids [ ] = {
{ . id = " BRCM5871 " , . driver_data = ( kernel_ulong_t ) & iproc_cygnus_data } ,
{ . id = " BRCM5872 " , . driver_data = ( kernel_ulong_t ) & iproc_data } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( acpi , sdhci_iproc_acpi_ids ) ;
2015-02-10 03:06:30 +03:00
static int sdhci_iproc_probe ( struct platform_device * pdev )
{
2018-08-05 10:52:52 +03:00
struct device * dev = & pdev - > dev ;
const struct sdhci_iproc_data * iproc_data = NULL ;
2015-02-10 03:06:30 +03:00
struct sdhci_host * host ;
struct sdhci_iproc_host * iproc_host ;
struct sdhci_pltfm_host * pltfm_host ;
int ret ;
2018-08-05 10:52:52 +03:00
iproc_data = device_get_match_data ( dev ) ;
if ( ! iproc_data )
return - ENODEV ;
2015-02-10 03:06:30 +03:00
host = sdhci_pltfm_init ( pdev , iproc_data - > pdata , sizeof ( * iproc_host ) ) ;
if ( IS_ERR ( host ) )
return PTR_ERR ( host ) ;
pltfm_host = sdhci_priv ( host ) ;
iproc_host = sdhci_pltfm_priv ( pltfm_host ) ;
iproc_host - > data = iproc_data ;
2018-12-23 23:59:17 +03:00
ret = mmc_of_parse ( host - > mmc ) ;
if ( ret )
goto err ;
2018-08-05 10:52:52 +03:00
sdhci_get_property ( pdev ) ;
2015-02-10 03:06:30 +03:00
2016-01-28 01:25:40 +03:00
host - > mmc - > caps | = iproc_host - > data - > mmc_caps ;
2015-02-10 03:06:30 +03:00
2018-08-05 10:52:52 +03:00
if ( dev - > of_node ) {
pltfm_host - > clk = devm_clk_get ( dev , NULL ) ;
if ( IS_ERR ( pltfm_host - > clk ) ) {
ret = PTR_ERR ( pltfm_host - > clk ) ;
goto err ;
}
ret = clk_prepare_enable ( pltfm_host - > clk ) ;
if ( ret ) {
dev_err ( dev , " failed to enable host clk \n " ) ;
goto err ;
}
2016-01-17 17:59:01 +03:00
}
2015-02-10 03:06:30 +03:00
if ( iproc_host - > data - > pdata - > quirks & SDHCI_QUIRK_MISSING_CAPS ) {
host - > caps = iproc_host - > data - > caps ;
host - > caps1 = iproc_host - > data - > caps1 ;
}
2016-01-17 17:59:00 +03:00
ret = sdhci_add_host ( host ) ;
if ( ret )
2016-01-17 17:59:01 +03:00
goto err_clk ;
2016-01-17 17:59:00 +03:00
return 0 ;
2015-02-10 03:06:30 +03:00
2016-01-17 17:59:01 +03:00
err_clk :
2018-08-05 10:52:52 +03:00
if ( dev - > of_node )
clk_disable_unprepare ( pltfm_host - > clk ) ;
2015-02-10 03:06:30 +03:00
err :
sdhci_pltfm_free ( pdev ) ;
return ret ;
}
static struct platform_driver sdhci_iproc_driver = {
. driver = {
. name = " sdhci-iproc " ,
. of_match_table = sdhci_iproc_of_match ,
2018-08-05 10:52:52 +03:00
. acpi_match_table = ACPI_PTR ( sdhci_iproc_acpi_ids ) ,
2016-07-27 14:07:21 +03:00
. pm = & sdhci_pltfm_pmops ,
2015-02-10 03:06:30 +03:00
} ,
. probe = sdhci_iproc_probe ,
2016-01-26 13:26:03 +03:00
. remove = sdhci_pltfm_unregister ,
2015-02-10 03:06:30 +03:00
} ;
module_platform_driver ( sdhci_iproc_driver ) ;
MODULE_AUTHOR ( " Broadcom " ) ;
MODULE_DESCRIPTION ( " IPROC SDHCI driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;