2011-06-08 13:41:57 +04:00
/*
* Copyright ( C ) 2010 Marvell International Ltd .
* Zhangfei Gao < zhangfei . gao @ marvell . com >
* Kevin Wang < dwang4 @ marvell . com >
* Mingwei Wang < mwwang @ marvell . com >
* Philip Rakity < prakity @ marvell . com >
* Mark Brown < markb @ marvell . com >
*
* This software is licensed under the terms of the GNU General Public
* License version 2 , as published by the Free Software Foundation , and
* may be copied , distributed , and modified under those terms .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
*/
# include <linux/err.h>
# include <linux/init.h>
# include <linux/platform_device.h>
# include <linux/clk.h>
# include <linux/io.h>
# include <linux/gpio.h>
# include <linux/mmc/card.h>
# include <linux/mmc/host.h>
2012-09-19 12:29:12 +04:00
# include <linux/mmc/slot-gpio.h>
2011-06-20 18:11:52 +04:00
# include <linux/platform_data/pxa_sdhci.h>
2011-06-08 13:41:57 +04:00
# include <linux/slab.h>
# include <linux/delay.h>
2011-07-03 23:15:51 +04:00
# include <linux/module.h>
2012-04-11 06:34:33 +04:00
# include <linux/of.h>
# include <linux/of_device.h>
2012-09-19 12:29:12 +04:00
# include <linux/of_gpio.h>
2013-02-01 13:48:30 +04:00
# include <linux/pm.h>
# include <linux/pm_runtime.h>
2014-02-18 19:08:29 +04:00
# include <linux/mbus.h>
2012-04-11 06:34:33 +04:00
2011-06-08 13:41:57 +04:00
# include "sdhci.h"
# include "sdhci-pltfm.h"
2013-02-01 13:48:30 +04:00
# define PXAV3_RPM_DELAY_MS 50
2011-06-08 13:41:57 +04:00
# define SD_CLOCK_BURST_SIZE_SETUP 0x10A
# define SDCLK_SEL 0x100
# define SDCLK_DELAY_SHIFT 9
# define SDCLK_DELAY_MASK 0x1f
# define SD_CFG_FIFO_PARAM 0x100
# define SDCFG_GEN_PAD_CLK_ON (1<<6)
# define SDCFG_GEN_PAD_CLK_CNT_MASK 0xFF
# define SDCFG_GEN_PAD_CLK_CNT_SHIFT 24
# define SD_SPI_MODE 0x108
# define SD_CE_ATA_1 0x10C
# define SD_CE_ATA_2 0x10E
# define SDCE_MISC_INT (1<<2)
# define SDCE_MISC_INT_EN (1<<1)
2014-02-18 19:08:29 +04:00
/*
* These registers are relative to the second register region , for the
* MBus bridge .
*/
# define SDHCI_WINDOW_CTRL(i) (0x80 + ((i) << 3))
# define SDHCI_WINDOW_BASE(i) (0x84 + ((i) << 3))
# define SDHCI_MAX_WIN_NUM 8
static int mv_conf_mbus_windows ( struct platform_device * pdev ,
const struct mbus_dram_target_info * dram )
{
int i ;
void __iomem * regs ;
struct resource * res ;
if ( ! dram ) {
dev_err ( & pdev - > dev , " no mbus dram info \n " ) ;
return - EINVAL ;
}
res = platform_get_resource ( pdev , IORESOURCE_MEM , 1 ) ;
if ( ! res ) {
dev_err ( & pdev - > dev , " cannot get mbus registers \n " ) ;
return - EINVAL ;
}
regs = ioremap ( res - > start , resource_size ( res ) ) ;
if ( ! regs ) {
dev_err ( & pdev - > dev , " cannot map mbus registers \n " ) ;
return - ENOMEM ;
}
for ( i = 0 ; i < SDHCI_MAX_WIN_NUM ; i + + ) {
writel ( 0 , regs + SDHCI_WINDOW_CTRL ( i ) ) ;
writel ( 0 , regs + SDHCI_WINDOW_BASE ( i ) ) ;
}
for ( i = 0 ; i < dram - > num_cs ; i + + ) {
const struct mbus_dram_window * cs = dram - > cs + i ;
/* Write size, attributes and target id to control register */
writel ( ( ( cs - > size - 1 ) & 0xffff0000 ) |
( cs - > mbus_attr < < 8 ) |
( dram - > mbus_dram_target_id < < 4 ) | 1 ,
regs + SDHCI_WINDOW_CTRL ( i ) ) ;
/* Write base address to base register */
writel ( cs - > base , regs + SDHCI_WINDOW_BASE ( i ) ) ;
}
iounmap ( regs ) ;
return 0 ;
}
2014-04-25 15:57:12 +04:00
static void pxav3_reset ( struct sdhci_host * host , u8 mask )
2011-06-08 13:41:57 +04:00
{
struct platform_device * pdev = to_platform_device ( mmc_dev ( host - > mmc ) ) ;
struct sdhci_pxa_platdata * pdata = pdev - > dev . platform_data ;
2014-04-25 15:57:12 +04:00
sdhci_reset ( host , mask ) ;
2011-06-08 13:41:57 +04:00
if ( mask = = SDHCI_RESET_ALL ) {
/*
* tune timing of read data / command when crc error happen
* no performance impact
*/
if ( pdata & & 0 ! = pdata - > clk_delay_cycles ) {
u16 tmp ;
tmp = readw ( host - > ioaddr + SD_CLOCK_BURST_SIZE_SETUP ) ;
tmp | = ( pdata - > clk_delay_cycles & SDCLK_DELAY_MASK )
< < SDCLK_DELAY_SHIFT ;
tmp | = SDCLK_SEL ;
writew ( tmp , host - > ioaddr + SD_CLOCK_BURST_SIZE_SETUP ) ;
}
}
}
# define MAX_WAIT_COUNT 5
static void pxav3_gen_init_74_clocks ( struct sdhci_host * host , u8 power_mode )
{
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
struct sdhci_pxa * pxa = pltfm_host - > priv ;
u16 tmp ;
int count ;
if ( pxa - > power_mode = = MMC_POWER_UP
& & power_mode = = MMC_POWER_ON ) {
dev_dbg ( mmc_dev ( host - > mmc ) ,
" %s: slot->power_mode = %d, "
" ios->power_mode = %d \n " ,
__func__ ,
pxa - > power_mode ,
power_mode ) ;
/* set we want notice of when 74 clocks are sent */
tmp = readw ( host - > ioaddr + SD_CE_ATA_2 ) ;
tmp | = SDCE_MISC_INT_EN ;
writew ( tmp , host - > ioaddr + SD_CE_ATA_2 ) ;
/* start sending the 74 clocks */
tmp = readw ( host - > ioaddr + SD_CFG_FIFO_PARAM ) ;
tmp | = SDCFG_GEN_PAD_CLK_ON ;
writew ( tmp , host - > ioaddr + SD_CFG_FIFO_PARAM ) ;
/* slowest speed is about 100KHz or 10usec per clock */
udelay ( 740 ) ;
count = 0 ;
while ( count + + < MAX_WAIT_COUNT ) {
if ( ( readw ( host - > ioaddr + SD_CE_ATA_2 )
& SDCE_MISC_INT ) = = 0 )
break ;
udelay ( 10 ) ;
}
if ( count = = MAX_WAIT_COUNT )
dev_warn ( mmc_dev ( host - > mmc ) , " 74 clock interrupt not cleared \n " ) ;
/* clear the interrupt bit if posted */
tmp = readw ( host - > ioaddr + SD_CE_ATA_2 ) ;
tmp | = SDCE_MISC_INT ;
writew ( tmp , host - > ioaddr + SD_CE_ATA_2 ) ;
}
pxa - > power_mode = power_mode ;
}
2014-04-25 15:59:20 +04:00
static void pxav3_set_uhs_signaling ( struct sdhci_host * host , unsigned int uhs )
2011-06-08 13:41:57 +04:00
{
u16 ctrl_2 ;
/*
* Set V18_EN - - UHS modes do not work without this .
* does not change signaling voltage
*/
ctrl_2 = sdhci_readw ( host , SDHCI_HOST_CONTROL2 ) ;
/* Select Bus Speed Mode for host */
ctrl_2 & = ~ SDHCI_CTRL_UHS_MASK ;
switch ( uhs ) {
case MMC_TIMING_UHS_SDR12 :
ctrl_2 | = SDHCI_CTRL_UHS_SDR12 ;
break ;
case MMC_TIMING_UHS_SDR25 :
ctrl_2 | = SDHCI_CTRL_UHS_SDR25 ;
break ;
case MMC_TIMING_UHS_SDR50 :
ctrl_2 | = SDHCI_CTRL_UHS_SDR50 | SDHCI_CTRL_VDD_180 ;
break ;
case MMC_TIMING_UHS_SDR104 :
ctrl_2 | = SDHCI_CTRL_UHS_SDR104 | SDHCI_CTRL_VDD_180 ;
break ;
case MMC_TIMING_UHS_DDR50 :
ctrl_2 | = SDHCI_CTRL_UHS_DDR50 | SDHCI_CTRL_VDD_180 ;
break ;
}
sdhci_writew ( host , ctrl_2 , SDHCI_HOST_CONTROL2 ) ;
dev_dbg ( mmc_dev ( host - > mmc ) ,
" %s uhs = %d, ctrl_2 = %04X \n " ,
__func__ , uhs , ctrl_2 ) ;
}
2013-03-13 22:26:05 +04:00
static const struct sdhci_ops pxav3_sdhci_ops = {
2014-04-25 15:58:55 +04:00
. set_clock = sdhci_set_clock ,
2011-06-08 13:41:57 +04:00
. platform_send_init_74_clocks = pxav3_gen_init_74_clocks ,
2013-01-28 22:27:12 +04:00
. get_max_clock = sdhci_pltfm_clk_get_max_clock ,
2014-04-25 15:57:07 +04:00
. set_bus_width = sdhci_set_bus_width ,
2014-04-25 15:57:12 +04:00
. reset = pxav3_reset ,
2014-08-15 17:02:15 +04:00
. set_uhs_signaling = pxav3_set_uhs_signaling ,
2011-06-08 13:41:57 +04:00
} ;
2013-03-25 13:42:56 +04:00
static struct sdhci_pltfm_data sdhci_pxav3_pdata = {
2013-03-25 13:42:59 +04:00
. quirks = SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK
2013-03-25 13:42:56 +04:00
| SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC
| SDHCI_QUIRK_32BIT_ADMA_SIZE
| SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN ,
. ops = & pxav3_sdhci_ops ,
} ;
2012-04-11 06:34:33 +04:00
# ifdef CONFIG_OF
static const struct of_device_id sdhci_pxav3_of_match [ ] = {
{
. compatible = " mrvl,pxav3-mmc " ,
} ,
2014-02-18 19:08:29 +04:00
{
. compatible = " marvell,armada-380-sdhci " ,
} ,
2012-04-11 06:34:33 +04:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , sdhci_pxav3_of_match ) ;
static struct sdhci_pxa_platdata * pxav3_get_mmc_pdata ( struct device * dev )
{
struct sdhci_pxa_platdata * pdata ;
struct device_node * np = dev - > of_node ;
u32 clk_delay_cycles ;
pdata = devm_kzalloc ( dev , sizeof ( * pdata ) , GFP_KERNEL ) ;
if ( ! pdata )
return NULL ;
of_property_read_u32 ( np , " mrvl,clk-delay-cycles " , & clk_delay_cycles ) ;
if ( clk_delay_cycles > 0 )
pdata - > clk_delay_cycles = clk_delay_cycles ;
return pdata ;
}
# else
static inline struct sdhci_pxa_platdata * pxav3_get_mmc_pdata ( struct device * dev )
{
return NULL ;
}
# endif
2012-11-19 22:23:06 +04:00
static int sdhci_pxav3_probe ( struct platform_device * pdev )
2011-06-08 13:41:57 +04:00
{
struct sdhci_pltfm_host * pltfm_host ;
struct sdhci_pxa_platdata * pdata = pdev - > dev . platform_data ;
struct device * dev = & pdev - > dev ;
2014-02-18 19:08:29 +04:00
struct device_node * np = pdev - > dev . of_node ;
2011-06-08 13:41:57 +04:00
struct sdhci_host * host = NULL ;
struct sdhci_pxa * pxa = NULL ;
2012-04-11 06:34:33 +04:00
const struct of_device_id * match ;
2011-06-08 13:41:57 +04:00
int ret ;
struct clk * clk ;
2014-07-16 13:53:42 +04:00
pxa = devm_kzalloc ( & pdev - > dev , sizeof ( struct sdhci_pxa ) , GFP_KERNEL ) ;
2011-06-08 13:41:57 +04:00
if ( ! pxa )
return - ENOMEM ;
2013-05-30 00:50:05 +04:00
host = sdhci_pltfm_init ( pdev , & sdhci_pxav3_pdata , 0 ) ;
2014-07-16 13:53:42 +04:00
if ( IS_ERR ( host ) )
2011-06-08 13:41:57 +04:00
return PTR_ERR ( host ) ;
2014-02-18 19:08:29 +04:00
if ( of_device_is_compatible ( np , " marvell,armada-380-sdhci " ) ) {
ret = mv_conf_mbus_windows ( pdev , mv_mbus_dram_info ( ) ) ;
if ( ret < 0 )
goto err_mbus_win ;
}
2011-06-08 13:41:57 +04:00
pltfm_host = sdhci_priv ( host ) ;
pltfm_host - > priv = pxa ;
2014-07-16 13:53:42 +04:00
clk = devm_clk_get ( dev , NULL ) ;
2011-06-08 13:41:57 +04:00
if ( IS_ERR ( clk ) ) {
dev_err ( dev , " failed to get io clock \n " ) ;
ret = PTR_ERR ( clk ) ;
goto err_clk_get ;
}
pltfm_host - > clk = clk ;
2012-07-31 10:35:25 +04:00
clk_prepare_enable ( clk ) ;
2011-06-08 13:41:57 +04:00
/* enable 1/8V DDR capable */
host - > mmc - > caps | = MMC_CAP_1_8V_DDR ;
2012-04-11 06:34:33 +04:00
match = of_match_device ( of_match_ptr ( sdhci_pxav3_of_match ) , & pdev - > dev ) ;
2013-03-25 13:42:58 +04:00
if ( match ) {
2013-06-10 00:14:15 +04:00
ret = mmc_of_parse ( host - > mmc ) ;
if ( ret )
goto err_of_parse ;
2013-03-25 13:42:58 +04:00
sdhci_get_of_property ( pdev ) ;
2012-04-11 06:34:33 +04:00
pdata = pxav3_get_mmc_pdata ( dev ) ;
2013-03-25 13:42:58 +04:00
} else if ( pdata ) {
2013-03-25 13:42:57 +04:00
/* on-chip device */
if ( pdata - > flags & PXA_FLAG_CARD_PERMANENT )
2011-06-08 13:41:57 +04:00
host - > mmc - > caps | = MMC_CAP_NONREMOVABLE ;
/* If slot design supports 8 bit data, indicate this to MMC. */
if ( pdata - > flags & PXA_FLAG_SD_8_BIT_CAPABLE_SLOT )
host - > mmc - > caps | = MMC_CAP_8_BIT_DATA ;
if ( pdata - > quirks )
host - > quirks | = pdata - > quirks ;
2012-10-17 15:04:48 +04:00
if ( pdata - > quirks2 )
host - > quirks2 | = pdata - > quirks2 ;
2011-06-08 13:41:57 +04:00
if ( pdata - > host_caps )
host - > mmc - > caps | = pdata - > host_caps ;
2012-09-19 12:29:12 +04:00
if ( pdata - > host_caps2 )
host - > mmc - > caps2 | = pdata - > host_caps2 ;
2011-06-08 13:41:57 +04:00
if ( pdata - > pm_caps )
host - > mmc - > pm_caps | = pdata - > pm_caps ;
2012-09-19 12:29:12 +04:00
if ( gpio_is_valid ( pdata - > ext_cd_gpio ) ) {
2013-08-08 14:38:31 +04:00
ret = mmc_gpio_request_cd ( host - > mmc , pdata - > ext_cd_gpio ,
0 ) ;
2012-09-19 12:29:12 +04:00
if ( ret ) {
dev_err ( mmc_dev ( host - > mmc ) ,
" failed to allocate card detect gpio \n " ) ;
goto err_cd_req ;
}
}
2011-06-08 13:41:57 +04:00
}
2013-02-01 13:48:30 +04:00
pm_runtime_enable ( & pdev - > dev ) ;
2013-06-27 19:46:29 +04:00
pm_runtime_get_sync ( & pdev - > dev ) ;
2013-02-01 13:48:30 +04:00
pm_runtime_set_autosuspend_delay ( & pdev - > dev , PXAV3_RPM_DELAY_MS ) ;
pm_runtime_use_autosuspend ( & pdev - > dev ) ;
pm_suspend_ignore_children ( & pdev - > dev , 1 ) ;
2011-06-08 13:41:57 +04:00
ret = sdhci_add_host ( host ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to add host \n " ) ;
goto err_add_host ;
}
platform_set_drvdata ( pdev , host ) ;
2013-03-25 13:42:58 +04:00
if ( host - > mmc - > pm_caps & MMC_PM_KEEP_POWER ) {
2013-01-14 23:38:53 +04:00
device_init_wakeup ( & pdev - > dev , 1 ) ;
host - > mmc - > pm_flags | = MMC_PM_WAKE_SDIO_IRQ ;
} else {
device_init_wakeup ( & pdev - > dev , 0 ) ;
}
2013-02-01 13:48:30 +04:00
pm_runtime_put_autosuspend ( & pdev - > dev ) ;
2011-06-08 13:41:57 +04:00
return 0 ;
err_add_host :
2013-06-27 19:46:29 +04:00
pm_runtime_put_sync ( & pdev - > dev ) ;
pm_runtime_disable ( & pdev - > dev ) ;
2014-07-16 11:50:09 +04:00
err_of_parse :
err_cd_req :
2012-07-31 10:35:25 +04:00
clk_disable_unprepare ( clk ) ;
2011-06-08 13:41:57 +04:00
err_clk_get :
2014-02-18 19:08:29 +04:00
err_mbus_win :
2011-06-08 13:41:57 +04:00
sdhci_pltfm_free ( pdev ) ;
return ret ;
}
2012-11-19 22:26:03 +04:00
static int sdhci_pxav3_remove ( struct platform_device * pdev )
2011-06-08 13:41:57 +04:00
{
struct sdhci_host * host = platform_get_drvdata ( pdev ) ;
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
2013-02-01 13:48:30 +04:00
pm_runtime_get_sync ( & pdev - > dev ) ;
2011-06-08 13:41:57 +04:00
sdhci_remove_host ( host , 1 ) ;
2013-02-01 13:48:30 +04:00
pm_runtime_disable ( & pdev - > dev ) ;
2011-06-08 13:41:57 +04:00
2012-07-31 10:35:25 +04:00
clk_disable_unprepare ( pltfm_host - > clk ) ;
2012-09-19 12:29:12 +04:00
2011-06-08 13:41:57 +04:00
sdhci_pltfm_free ( pdev ) ;
return 0 ;
}
2013-02-01 13:48:30 +04:00
# ifdef CONFIG_PM_SLEEP
static int sdhci_pxav3_suspend ( struct device * dev )
{
int ret ;
struct sdhci_host * host = dev_get_drvdata ( dev ) ;
pm_runtime_get_sync ( dev ) ;
ret = sdhci_suspend_host ( host ) ;
pm_runtime_mark_last_busy ( dev ) ;
pm_runtime_put_autosuspend ( dev ) ;
return ret ;
}
static int sdhci_pxav3_resume ( struct device * dev )
{
int ret ;
struct sdhci_host * host = dev_get_drvdata ( dev ) ;
pm_runtime_get_sync ( dev ) ;
ret = sdhci_resume_host ( host ) ;
pm_runtime_mark_last_busy ( dev ) ;
pm_runtime_put_autosuspend ( dev ) ;
return ret ;
}
# endif
# ifdef CONFIG_PM_RUNTIME
static int sdhci_pxav3_runtime_suspend ( struct device * dev )
{
struct sdhci_host * host = dev_get_drvdata ( dev ) ;
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
unsigned long flags ;
if ( pltfm_host - > clk ) {
spin_lock_irqsave ( & host - > lock , flags ) ;
host - > runtime_suspended = true ;
spin_unlock_irqrestore ( & host - > lock , flags ) ;
clk_disable_unprepare ( pltfm_host - > clk ) ;
}
return 0 ;
}
static int sdhci_pxav3_runtime_resume ( struct device * dev )
{
struct sdhci_host * host = dev_get_drvdata ( dev ) ;
struct sdhci_pltfm_host * pltfm_host = sdhci_priv ( host ) ;
unsigned long flags ;
if ( pltfm_host - > clk ) {
clk_prepare_enable ( pltfm_host - > clk ) ;
spin_lock_irqsave ( & host - > lock , flags ) ;
host - > runtime_suspended = false ;
spin_unlock_irqrestore ( & host - > lock , flags ) ;
}
return 0 ;
}
# endif
# ifdef CONFIG_PM
static const struct dev_pm_ops sdhci_pxav3_pmops = {
SET_SYSTEM_SLEEP_PM_OPS ( sdhci_pxav3_suspend , sdhci_pxav3_resume )
SET_RUNTIME_PM_OPS ( sdhci_pxav3_runtime_suspend ,
sdhci_pxav3_runtime_resume , NULL )
} ;
# define SDHCI_PXAV3_PMOPS (&sdhci_pxav3_pmops)
# else
# define SDHCI_PXAV3_PMOPS NULL
# endif
2011-06-08 13:41:57 +04:00
static struct platform_driver sdhci_pxav3_driver = {
. driver = {
. name = " sdhci-pxav3 " ,
2012-04-11 06:34:33 +04:00
# ifdef CONFIG_OF
. of_match_table = sdhci_pxav3_of_match ,
# endif
2013-02-01 13:48:30 +04:00
. pm = SDHCI_PXAV3_PMOPS ,
2011-06-08 13:41:57 +04:00
} ,
. probe = sdhci_pxav3_probe ,
2012-11-19 22:20:26 +04:00
. remove = sdhci_pxav3_remove ,
2011-06-08 13:41:57 +04:00
} ;
2011-11-26 08:55:43 +04:00
module_platform_driver ( sdhci_pxav3_driver ) ;
2011-06-08 13:41:57 +04:00
MODULE_DESCRIPTION ( " SDHCI driver for pxav3 " ) ;
MODULE_AUTHOR ( " Marvell International Ltd. " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;