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>
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)
static void pxav3_set_private_registers ( struct sdhci_host * host , u8 mask )
{
struct platform_device * pdev = to_platform_device ( mmc_dev ( host - > mmc ) ) ;
struct sdhci_pxa_platdata * pdata = pdev - > dev . platform_data ;
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 ;
}
static int pxav3_set_uhs_signaling ( struct sdhci_host * host , unsigned int uhs )
{
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 ) ;
return 0 ;
}
2013-03-13 22:26:05 +04:00
static const struct sdhci_ops pxav3_sdhci_ops = {
2011-06-08 13:41:57 +04:00
. platform_reset_exit = pxav3_set_private_registers ,
. set_uhs_signaling = pxav3_set_uhs_signaling ,
. 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 ,
2011-06-08 13:41:57 +04:00
} ;
2013-03-25 13:42:56 +04:00
static struct sdhci_pltfm_data sdhci_pxav3_pdata = {
. quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL
| 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 " ,
} ,
{ } ,
} ;
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 bus_width ;
u32 clk_delay_cycles ;
2012-09-19 12:29:12 +04:00
enum of_gpio_flags gpio_flags ;
2012-04-11 06:34:33 +04:00
pdata = devm_kzalloc ( dev , sizeof ( * pdata ) , GFP_KERNEL ) ;
if ( ! pdata )
return NULL ;
if ( of_find_property ( np , " non-removable " , NULL ) )
pdata - > flags | = PXA_FLAG_CARD_PERMANENT ;
of_property_read_u32 ( np , " bus-width " , & bus_width ) ;
if ( bus_width = = 8 )
pdata - > flags | = PXA_FLAG_SD_8_BIT_CAPABLE_SLOT ;
of_property_read_u32 ( np , " mrvl,clk-delay-cycles " , & clk_delay_cycles ) ;
if ( clk_delay_cycles > 0 )
pdata - > clk_delay_cycles = clk_delay_cycles ;
2012-09-19 12:29:12 +04:00
pdata - > ext_cd_gpio = of_get_named_gpio_flags ( np , " cd-gpios " , 0 , & gpio_flags ) ;
if ( gpio_flags ! = OF_GPIO_ACTIVE_LOW )
pdata - > host_caps2 | = MMC_CAP2_CD_ACTIVE_HIGH ;
2012-04-11 06:34:33 +04:00
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 ;
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 ;
pxa = kzalloc ( sizeof ( struct sdhci_pxa ) , GFP_KERNEL ) ;
if ( ! pxa )
return - ENOMEM ;
2013-03-25 13:42:56 +04:00
host = sdhci_pltfm_init ( pdev , & sdhci_pxav3_pdata ) ;
2011-06-08 13:41:57 +04:00
if ( IS_ERR ( host ) ) {
kfree ( pxa ) ;
return PTR_ERR ( host ) ;
}
pltfm_host = sdhci_priv ( host ) ;
pltfm_host - > priv = pxa ;
2012-07-31 10:35:25 +04:00
clk = 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 ) ;
if ( match )
pdata = pxav3_get_mmc_pdata ( dev ) ;
2011-06-08 13:41:57 +04:00
if ( pdata ) {
if ( pdata - > flags & PXA_FLAG_CARD_PERMANENT ) {
/* on-chip device */
host - > quirks | = SDHCI_QUIRK_BROKEN_CARD_DETECTION ;
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 ) ) {
ret = mmc_gpio_request_cd ( host - > mmc , pdata - > ext_cd_gpio ) ;
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
}
2012-09-17 10:40:15 +04:00
sdhci_get_of_property ( pdev ) ;
2013-02-01 13:48:30 +04:00
pm_runtime_set_active ( & pdev - > dev ) ;
pm_runtime_enable ( & pdev - > dev ) ;
pm_runtime_set_autosuspend_delay ( & pdev - > dev , PXAV3_RPM_DELAY_MS ) ;
pm_runtime_use_autosuspend ( & pdev - > dev ) ;
pm_suspend_ignore_children ( & pdev - > dev , 1 ) ;
pm_runtime_get_noresume ( & pdev - > dev ) ;
2011-06-08 13:41:57 +04:00
ret = sdhci_add_host ( host ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to add host \n " ) ;
2013-02-01 13:48:30 +04:00
pm_runtime_forbid ( & pdev - > dev ) ;
pm_runtime_disable ( & pdev - > dev ) ;
2011-06-08 13:41:57 +04:00
goto err_add_host ;
}
platform_set_drvdata ( pdev , host ) ;
2013-01-14 23:38:53 +04:00
if ( pdata - > pm_caps & MMC_PM_KEEP_POWER ) {
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 :
2012-07-31 10:35:25 +04:00
clk_disable_unprepare ( clk ) ;
2011-06-08 13:41:57 +04:00
clk_put ( clk ) ;
2012-09-19 12:29:12 +04:00
err_cd_req :
2011-06-08 13:41:57 +04:00
err_clk_get :
sdhci_pltfm_free ( pdev ) ;
kfree ( pxa ) ;
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 ) ;
struct sdhci_pxa * pxa = pltfm_host - > priv ;
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 ) ;
2011-06-08 13:41:57 +04:00
clk_put ( pltfm_host - > clk ) ;
2012-09-19 12:29:12 +04:00
2011-06-08 13:41:57 +04:00
sdhci_pltfm_free ( pdev ) ;
kfree ( pxa ) ;
platform_set_drvdata ( pdev , NULL ) ;
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
2011-06-08 13:41:57 +04:00
. owner = THIS_MODULE ,
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 " ) ;