2016-01-13 18:15:45 -07:00
/*
* Support of SDHCI platform devices for Microchip PIC32 .
*
* Copyright ( C ) 2015 Microchip
* Andrei Pistirica , Paul Thacker
*
* Inspired by sdhci - pltfm . c
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed " as is " without any
* warranty of any kind , whether express or implied .
*/
# include <linux/clk.h>
# include <linux/delay.h>
# include <linux/highmem.h>
# include <linux/module.h>
# include <linux/interrupt.h>
# include <linux/irq.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/pm.h>
# include <linux/slab.h>
# include <linux/mmc/host.h>
# include <linux/io.h>
# include "sdhci.h"
# include "sdhci-pltfm.h"
# include <linux/platform_data/sdhci-pic32.h>
# define SDH_SHARED_BUS_CTRL 0x000000E0
# define SDH_SHARED_BUS_NR_CLK_PINS_MASK 0x7
# define SDH_SHARED_BUS_NR_IRQ_PINS_MASK 0x30
# define SDH_SHARED_BUS_CLK_PINS 0x10
# define SDH_SHARED_BUS_IRQ_PINS 0x14
# define SDH_CAPS_SDH_SLOT_TYPE_MASK 0xC0000000
# define SDH_SLOT_TYPE_REMOVABLE 0x0
# define SDH_SLOT_TYPE_EMBEDDED 0x1
# define SDH_SLOT_TYPE_SHARED_BUS 0x2
# define SDHCI_CTRL_CDSSEL 0x80
# define SDHCI_CTRL_CDTLVL 0x40
# define ADMA_FIFO_RD_THSHLD 512
# define ADMA_FIFO_WR_THSHLD 512
struct pic32_sdhci_priv {
struct platform_device * pdev ;
struct clk * sys_clk ;
struct clk * base_clk ;
} ;
static unsigned int pic32_sdhci_get_max_clock ( struct sdhci_host * host )
{
struct pic32_sdhci_priv * sdhci_pdata = sdhci_priv ( host ) ;
return clk_get_rate ( sdhci_pdata - > base_clk ) ;
}
static void pic32_sdhci_set_bus_width ( struct sdhci_host * host , int width )
{
u8 ctrl ;
ctrl = sdhci_readb ( host , SDHCI_HOST_CONTROL ) ;
if ( width = = MMC_BUS_WIDTH_8 ) {
ctrl & = ~ SDHCI_CTRL_4BITBUS ;
if ( host - > version > = SDHCI_SPEC_300 )
ctrl | = SDHCI_CTRL_8BITBUS ;
} else {
if ( host - > version > = SDHCI_SPEC_300 )
ctrl & = ~ SDHCI_CTRL_8BITBUS ;
if ( width = = MMC_BUS_WIDTH_4 )
ctrl | = SDHCI_CTRL_4BITBUS ;
else
ctrl & = ~ SDHCI_CTRL_4BITBUS ;
}
/* CD select and test bits must be set for errata workaround. */
ctrl & = ~ SDHCI_CTRL_CDTLVL ;
ctrl | = SDHCI_CTRL_CDSSEL ;
sdhci_writeb ( host , ctrl , SDHCI_HOST_CONTROL ) ;
}
static unsigned int pic32_sdhci_get_ro ( struct sdhci_host * host )
{
/*
* The SDHCI_WRITE_PROTECT bit is unstable on current hardware so we
* can ' t depend on its value in any way .
*/
return 0 ;
}
static const struct sdhci_ops pic32_sdhci_ops = {
. get_max_clock = pic32_sdhci_get_max_clock ,
. set_clock = sdhci_set_clock ,
. set_bus_width = pic32_sdhci_set_bus_width ,
. reset = sdhci_reset ,
. set_uhs_signaling = sdhci_set_uhs_signaling ,
. get_ro = pic32_sdhci_get_ro ,
} ;
2017-08-07 11:50:40 +02:00
static const struct sdhci_pltfm_data sdhci_pic32_pdata = {
2016-01-13 18:15:45 -07:00
. ops = & pic32_sdhci_ops ,
. quirks = SDHCI_QUIRK_NO_HISPD_BIT ,
. quirks2 = SDHCI_QUIRK2_NO_1_8_V ,
} ;
static void pic32_sdhci_shared_bus ( struct platform_device * pdev )
{
struct sdhci_host * host = platform_get_drvdata ( pdev ) ;
u32 bus = readl ( host - > ioaddr + SDH_SHARED_BUS_CTRL ) ;
u32 clk_pins = ( bus & SDH_SHARED_BUS_NR_CLK_PINS_MASK ) > > 0 ;
u32 irq_pins = ( bus & SDH_SHARED_BUS_NR_IRQ_PINS_MASK ) > > 4 ;
/* select first clock */
if ( clk_pins & 1 )
bus | = ( 1 < < SDH_SHARED_BUS_CLK_PINS ) ;
/* select first interrupt */
if ( irq_pins & 1 )
bus | = ( 1 < < SDH_SHARED_BUS_IRQ_PINS ) ;
writel ( bus , host - > ioaddr + SDH_SHARED_BUS_CTRL ) ;
}
static int pic32_sdhci_probe_platform ( struct platform_device * pdev ,
struct pic32_sdhci_priv * pdata )
{
int ret = 0 ;
u32 caps_slot_type ;
struct sdhci_host * host = platform_get_drvdata ( pdev ) ;
/* Check card slot connected on shared bus. */
host - > caps = readl ( host - > ioaddr + SDHCI_CAPABILITIES ) ;
caps_slot_type = ( host - > caps & SDH_CAPS_SDH_SLOT_TYPE_MASK ) > > 30 ;
if ( caps_slot_type = = SDH_SLOT_TYPE_SHARED_BUS )
pic32_sdhci_shared_bus ( pdev ) ;
return ret ;
}
static int pic32_sdhci_probe ( struct platform_device * pdev )
{
struct sdhci_host * host ;
struct sdhci_pltfm_host * pltfm_host ;
struct pic32_sdhci_priv * sdhci_pdata ;
struct pic32_sdhci_platform_data * plat_data ;
int ret ;
host = sdhci_pltfm_init ( pdev , & sdhci_pic32_pdata ,
sizeof ( struct pic32_sdhci_priv ) ) ;
if ( IS_ERR ( host ) ) {
ret = PTR_ERR ( host ) ;
goto err ;
}
pltfm_host = sdhci_priv ( host ) ;
sdhci_pdata = sdhci_pltfm_priv ( pltfm_host ) ;
plat_data = pdev - > dev . platform_data ;
if ( plat_data & & plat_data - > setup_dma ) {
ret = plat_data - > setup_dma ( ADMA_FIFO_RD_THSHLD ,
ADMA_FIFO_WR_THSHLD ) ;
if ( ret )
goto err_host ;
}
sdhci_pdata - > sys_clk = devm_clk_get ( & pdev - > dev , " sys_clk " ) ;
if ( IS_ERR ( sdhci_pdata - > sys_clk ) ) {
ret = PTR_ERR ( sdhci_pdata - > sys_clk ) ;
dev_err ( & pdev - > dev , " Error getting clock \n " ) ;
goto err_host ;
}
ret = clk_prepare_enable ( sdhci_pdata - > sys_clk ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Error enabling clock \n " ) ;
goto err_host ;
}
sdhci_pdata - > base_clk = devm_clk_get ( & pdev - > dev , " base_clk " ) ;
if ( IS_ERR ( sdhci_pdata - > base_clk ) ) {
ret = PTR_ERR ( sdhci_pdata - > base_clk ) ;
dev_err ( & pdev - > dev , " Error getting clock \n " ) ;
goto err_sys_clk ;
}
ret = clk_prepare_enable ( sdhci_pdata - > base_clk ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Error enabling clock \n " ) ;
goto err_base_clk ;
}
ret = mmc_of_parse ( host - > mmc ) ;
if ( ret )
goto err_base_clk ;
ret = pic32_sdhci_probe_platform ( pdev , sdhci_pdata ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to probe platform! \n " ) ;
goto err_base_clk ;
}
ret = sdhci_add_host ( host ) ;
2018-05-25 15:15:09 +08:00
if ( ret )
2016-01-13 18:15:45 -07:00
goto err_base_clk ;
dev_info ( & pdev - > dev , " Successfully added sdhci host \n " ) ;
return 0 ;
err_base_clk :
clk_disable_unprepare ( sdhci_pdata - > base_clk ) ;
err_sys_clk :
clk_disable_unprepare ( sdhci_pdata - > sys_clk ) ;
err_host :
sdhci_pltfm_free ( pdev ) ;
err :
dev_err ( & pdev - > dev , " pic32-sdhci probe failed: %d \n " , ret ) ;
return ret ;
}
static int pic32_sdhci_remove ( struct platform_device * pdev )
{
struct sdhci_host * host = platform_get_drvdata ( pdev ) ;
struct pic32_sdhci_priv * sdhci_pdata = sdhci_priv ( host ) ;
u32 scratch ;
scratch = readl ( host - > ioaddr + SDHCI_INT_STATUS ) ;
sdhci_remove_host ( host , scratch = = ( u32 ) ~ 0 ) ;
clk_disable_unprepare ( sdhci_pdata - > base_clk ) ;
clk_disable_unprepare ( sdhci_pdata - > sys_clk ) ;
sdhci_pltfm_free ( pdev ) ;
return 0 ;
}
static const struct of_device_id pic32_sdhci_id_table [ ] = {
{ . compatible = " microchip,pic32mzda-sdhci " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , pic32_sdhci_id_table ) ;
static struct platform_driver pic32_sdhci_driver = {
. driver = {
. name = " pic32-sdhci " ,
. of_match_table = of_match_ptr ( pic32_sdhci_id_table ) ,
} ,
. probe = pic32_sdhci_probe ,
. remove = pic32_sdhci_remove ,
} ;
module_platform_driver ( pic32_sdhci_driver ) ;
MODULE_DESCRIPTION ( " Microchip PIC32 SDHCI driver " ) ;
MODULE_AUTHOR ( " Pistirica Sorin Andrei & Sandeep Sheriker " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;