2017-03-30 18:31:25 +03:00
/*
* Driver for MMC and SSD cards for Cavium ThunderX SOCs .
*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file " COPYING " in the main directory of this archive
* for more details .
*
* Copyright ( C ) 2016 Cavium Inc .
*/
2017-09-07 14:24:17 +03:00
# include <linux/device.h>
2017-03-30 18:31:25 +03:00
# include <linux/dma-mapping.h>
# include <linux/interrupt.h>
# include <linux/mmc/mmc.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/of_platform.h>
# include <linux/pci.h>
# include "cavium.h"
static void thunder_mmc_acquire_bus ( struct cvm_mmc_host * host )
{
down ( & host - > mmc_serializer ) ;
}
static void thunder_mmc_release_bus ( struct cvm_mmc_host * host )
{
up ( & host - > mmc_serializer ) ;
}
static void thunder_mmc_int_enable ( struct cvm_mmc_host * host , u64 val )
{
writeq ( val , host - > base + MIO_EMM_INT ( host ) ) ;
writeq ( val , host - > base + MIO_EMM_INT_EN_SET ( host ) ) ;
}
static int thunder_mmc_register_interrupts ( struct cvm_mmc_host * host ,
struct pci_dev * pdev )
{
int nvec , ret , i ;
nvec = pci_alloc_irq_vectors ( pdev , 1 , 9 , PCI_IRQ_MSIX ) ;
if ( nvec < 0 )
return nvec ;
/* register interrupts */
for ( i = 0 ; i < nvec ; i + + ) {
ret = devm_request_irq ( & pdev - > dev , pci_irq_vector ( pdev , i ) ,
cvm_mmc_interrupt ,
0 , cvm_mmc_irq_names [ i ] , host ) ;
if ( ret )
return ret ;
}
return 0 ;
}
static int thunder_mmc_probe ( struct pci_dev * pdev ,
const struct pci_device_id * id )
{
struct device_node * node = pdev - > dev . of_node ;
struct device * dev = & pdev - > dev ;
struct device_node * child_node ;
struct cvm_mmc_host * host ;
int ret , i = 0 ;
host = devm_kzalloc ( dev , sizeof ( * host ) , GFP_KERNEL ) ;
if ( ! host )
return - ENOMEM ;
pci_set_drvdata ( pdev , host ) ;
ret = pcim_enable_device ( pdev ) ;
if ( ret )
return ret ;
ret = pci_request_regions ( pdev , KBUILD_MODNAME ) ;
if ( ret )
return ret ;
host - > base = pcim_iomap ( pdev , 0 , pci_resource_len ( pdev , 0 ) ) ;
2019-12-06 10:54:08 +03:00
if ( ! host - > base ) {
ret = - EINVAL ;
goto error ;
}
2017-03-30 18:31:25 +03:00
/* On ThunderX these are identical */
host - > dma_base = host - > base ;
host - > reg_off = 0x2000 ;
2017-03-30 18:31:26 +03:00
host - > reg_off_dma = 0x160 ;
2017-03-30 18:31:25 +03:00
host - > clk = devm_clk_get ( dev , NULL ) ;
2019-12-06 10:54:08 +03:00
if ( IS_ERR ( host - > clk ) ) {
ret = PTR_ERR ( host - > clk ) ;
goto error ;
}
2017-03-30 18:31:25 +03:00
ret = clk_prepare_enable ( host - > clk ) ;
if ( ret )
2019-12-06 10:54:08 +03:00
goto error ;
2017-03-30 18:31:25 +03:00
host - > sys_freq = clk_get_rate ( host - > clk ) ;
spin_lock_init ( & host - > irq_handler_lock ) ;
sema_init ( & host - > mmc_serializer , 1 ) ;
host - > dev = dev ;
host - > acquire_bus = thunder_mmc_acquire_bus ;
host - > release_bus = thunder_mmc_release_bus ;
host - > int_enable = thunder_mmc_int_enable ;
2017-03-30 18:31:26 +03:00
host - > use_sg = true ;
2017-03-30 18:31:25 +03:00
host - > big_dma_addr = true ;
host - > need_irq_handler_lock = true ;
host - > last_slot = - 1 ;
ret = dma_set_mask ( dev , DMA_BIT_MASK ( 48 ) ) ;
if ( ret )
goto error ;
/*
* Clear out any pending interrupts that may be left over from
* bootloader . Writing 1 to the bits clears them .
*/
writeq ( 127 , host - > base + MIO_EMM_INT_EN ( host ) ) ;
writeq ( 3 , host - > base + MIO_EMM_DMA_INT_ENA_W1C ( host ) ) ;
2017-03-30 18:31:26 +03:00
/* Clear DMA FIFO */
writeq ( BIT_ULL ( 16 ) , host - > base + MIO_EMM_DMA_FIFO_CFG ( host ) ) ;
2017-03-30 18:31:25 +03:00
ret = thunder_mmc_register_interrupts ( host , pdev ) ;
if ( ret )
goto error ;
for_each_child_of_node ( node , child_node ) {
/*
* mmc_of_parse and devm * require one device per slot .
* Create a dummy device per slot and set the node pointer to
* the slot . The easiest way to get this is using
* of_platform_device_create .
*/
if ( of_device_is_compatible ( child_node , " mmc-slot " ) ) {
host - > slot_pdev [ i ] = of_platform_device_create ( child_node , NULL ,
& pdev - > dev ) ;
if ( ! host - > slot_pdev [ i ] )
continue ;
ret = cvm_mmc_of_slot_probe ( & host - > slot_pdev [ i ] - > dev , host ) ;
if ( ret )
goto error ;
}
i + + ;
}
dev_info ( dev , " probed \n " ) ;
return 0 ;
error :
2017-05-22 14:09:21 +03:00
for ( i = 0 ; i < CAVIUM_MAX_MMC ; i + + ) {
if ( host - > slot [ i ] )
cvm_mmc_of_slot_remove ( host - > slot [ i ] ) ;
2017-09-07 14:24:17 +03:00
if ( host - > slot_pdev [ i ] ) {
get_device ( & host - > slot_pdev [ i ] - > dev ) ;
2017-05-22 14:09:21 +03:00
of_platform_device_destroy ( & host - > slot_pdev [ i ] - > dev , NULL ) ;
2017-09-07 14:24:17 +03:00
put_device ( & host - > slot_pdev [ i ] - > dev ) ;
}
2017-05-22 14:09:21 +03:00
}
2017-03-30 18:31:25 +03:00
clk_disable_unprepare ( host - > clk ) ;
2019-12-06 10:54:08 +03:00
pci_release_regions ( pdev ) ;
2017-03-30 18:31:25 +03:00
return ret ;
}
static void thunder_mmc_remove ( struct pci_dev * pdev )
{
struct cvm_mmc_host * host = pci_get_drvdata ( pdev ) ;
u64 dma_cfg ;
int i ;
for ( i = 0 ; i < CAVIUM_MAX_MMC ; i + + )
if ( host - > slot [ i ] )
cvm_mmc_of_slot_remove ( host - > slot [ i ] ) ;
dma_cfg = readq ( host - > dma_base + MIO_EMM_DMA_CFG ( host ) ) ;
dma_cfg & = ~ MIO_EMM_DMA_CFG_EN ;
writeq ( dma_cfg , host - > dma_base + MIO_EMM_DMA_CFG ( host ) ) ;
clk_disable_unprepare ( host - > clk ) ;
2019-12-06 10:54:08 +03:00
pci_release_regions ( pdev ) ;
2017-03-30 18:31:25 +03:00
}
static const struct pci_device_id thunder_mmc_id_table [ ] = {
{ PCI_DEVICE ( PCI_VENDOR_ID_CAVIUM , 0xa010 ) } ,
{ 0 , } /* end of table */
} ;
static struct pci_driver thunder_mmc_driver = {
. name = KBUILD_MODNAME ,
. id_table = thunder_mmc_id_table ,
. probe = thunder_mmc_probe ,
. remove = thunder_mmc_remove ,
} ;
2017-04-25 06:14:09 +03:00
module_pci_driver ( thunder_mmc_driver ) ;
2017-03-30 18:31:25 +03:00
MODULE_AUTHOR ( " Cavium Inc. " ) ;
MODULE_DESCRIPTION ( " Cavium ThunderX eMMC Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DEVICE_TABLE ( pci , thunder_mmc_id_table ) ;