162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci * Driver for MMC and SSD cards for Cavium ThunderX SOCs. 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public 562306a36Sopenharmony_ci * License. See the file "COPYING" in the main directory of this archive 662306a36Sopenharmony_ci * for more details. 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Copyright (C) 2016 Cavium Inc. 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci#include <linux/device.h> 1162306a36Sopenharmony_ci#include <linux/dma-mapping.h> 1262306a36Sopenharmony_ci#include <linux/interrupt.h> 1362306a36Sopenharmony_ci#include <linux/mmc/mmc.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/of.h> 1662306a36Sopenharmony_ci#include <linux/of_platform.h> 1762306a36Sopenharmony_ci#include <linux/platform_device.h> 1862306a36Sopenharmony_ci#include <linux/pci.h> 1962306a36Sopenharmony_ci#include "cavium.h" 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistatic void thunder_mmc_acquire_bus(struct cvm_mmc_host *host) 2262306a36Sopenharmony_ci{ 2362306a36Sopenharmony_ci down(&host->mmc_serializer); 2462306a36Sopenharmony_ci} 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_cistatic void thunder_mmc_release_bus(struct cvm_mmc_host *host) 2762306a36Sopenharmony_ci{ 2862306a36Sopenharmony_ci up(&host->mmc_serializer); 2962306a36Sopenharmony_ci} 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistatic void thunder_mmc_int_enable(struct cvm_mmc_host *host, u64 val) 3262306a36Sopenharmony_ci{ 3362306a36Sopenharmony_ci writeq(val, host->base + MIO_EMM_INT(host)); 3462306a36Sopenharmony_ci writeq(val, host->base + MIO_EMM_INT_EN_SET(host)); 3562306a36Sopenharmony_ci} 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistatic int thunder_mmc_register_interrupts(struct cvm_mmc_host *host, 3862306a36Sopenharmony_ci struct pci_dev *pdev) 3962306a36Sopenharmony_ci{ 4062306a36Sopenharmony_ci int nvec, ret, i; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci nvec = pci_alloc_irq_vectors(pdev, 1, 9, PCI_IRQ_MSIX); 4362306a36Sopenharmony_ci if (nvec < 0) 4462306a36Sopenharmony_ci return nvec; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci /* register interrupts */ 4762306a36Sopenharmony_ci for (i = 0; i < nvec; i++) { 4862306a36Sopenharmony_ci ret = devm_request_irq(&pdev->dev, pci_irq_vector(pdev, i), 4962306a36Sopenharmony_ci cvm_mmc_interrupt, 5062306a36Sopenharmony_ci 0, cvm_mmc_irq_names[i], host); 5162306a36Sopenharmony_ci if (ret) 5262306a36Sopenharmony_ci return ret; 5362306a36Sopenharmony_ci } 5462306a36Sopenharmony_ci return 0; 5562306a36Sopenharmony_ci} 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_cistatic int thunder_mmc_probe(struct pci_dev *pdev, 5862306a36Sopenharmony_ci const struct pci_device_id *id) 5962306a36Sopenharmony_ci{ 6062306a36Sopenharmony_ci struct device_node *node = pdev->dev.of_node; 6162306a36Sopenharmony_ci struct device *dev = &pdev->dev; 6262306a36Sopenharmony_ci struct device_node *child_node; 6362306a36Sopenharmony_ci struct cvm_mmc_host *host; 6462306a36Sopenharmony_ci int ret, i = 0; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL); 6762306a36Sopenharmony_ci if (!host) 6862306a36Sopenharmony_ci return -ENOMEM; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci pci_set_drvdata(pdev, host); 7162306a36Sopenharmony_ci ret = pcim_enable_device(pdev); 7262306a36Sopenharmony_ci if (ret) 7362306a36Sopenharmony_ci return ret; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci ret = pci_request_regions(pdev, KBUILD_MODNAME); 7662306a36Sopenharmony_ci if (ret) 7762306a36Sopenharmony_ci return ret; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci host->base = pcim_iomap(pdev, 0, pci_resource_len(pdev, 0)); 8062306a36Sopenharmony_ci if (!host->base) { 8162306a36Sopenharmony_ci ret = -EINVAL; 8262306a36Sopenharmony_ci goto error; 8362306a36Sopenharmony_ci } 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci /* On ThunderX these are identical */ 8662306a36Sopenharmony_ci host->dma_base = host->base; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci host->reg_off = 0x2000; 8962306a36Sopenharmony_ci host->reg_off_dma = 0x160; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci host->clk = devm_clk_get(dev, NULL); 9262306a36Sopenharmony_ci if (IS_ERR(host->clk)) { 9362306a36Sopenharmony_ci ret = PTR_ERR(host->clk); 9462306a36Sopenharmony_ci goto error; 9562306a36Sopenharmony_ci } 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci ret = clk_prepare_enable(host->clk); 9862306a36Sopenharmony_ci if (ret) 9962306a36Sopenharmony_ci goto error; 10062306a36Sopenharmony_ci host->sys_freq = clk_get_rate(host->clk); 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci spin_lock_init(&host->irq_handler_lock); 10362306a36Sopenharmony_ci sema_init(&host->mmc_serializer, 1); 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci host->dev = dev; 10662306a36Sopenharmony_ci host->acquire_bus = thunder_mmc_acquire_bus; 10762306a36Sopenharmony_ci host->release_bus = thunder_mmc_release_bus; 10862306a36Sopenharmony_ci host->int_enable = thunder_mmc_int_enable; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci host->use_sg = true; 11162306a36Sopenharmony_ci host->big_dma_addr = true; 11262306a36Sopenharmony_ci host->need_irq_handler_lock = true; 11362306a36Sopenharmony_ci host->last_slot = -1; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci ret = dma_set_mask(dev, DMA_BIT_MASK(48)); 11662306a36Sopenharmony_ci if (ret) 11762306a36Sopenharmony_ci goto error; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci /* 12062306a36Sopenharmony_ci * Clear out any pending interrupts that may be left over from 12162306a36Sopenharmony_ci * bootloader. Writing 1 to the bits clears them. 12262306a36Sopenharmony_ci */ 12362306a36Sopenharmony_ci writeq(127, host->base + MIO_EMM_INT_EN(host)); 12462306a36Sopenharmony_ci writeq(3, host->base + MIO_EMM_DMA_INT_ENA_W1C(host)); 12562306a36Sopenharmony_ci /* Clear DMA FIFO */ 12662306a36Sopenharmony_ci writeq(BIT_ULL(16), host->base + MIO_EMM_DMA_FIFO_CFG(host)); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci ret = thunder_mmc_register_interrupts(host, pdev); 12962306a36Sopenharmony_ci if (ret) 13062306a36Sopenharmony_ci goto error; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci for_each_child_of_node(node, child_node) { 13362306a36Sopenharmony_ci /* 13462306a36Sopenharmony_ci * mmc_of_parse and devm* require one device per slot. 13562306a36Sopenharmony_ci * Create a dummy device per slot and set the node pointer to 13662306a36Sopenharmony_ci * the slot. The easiest way to get this is using 13762306a36Sopenharmony_ci * of_platform_device_create. 13862306a36Sopenharmony_ci */ 13962306a36Sopenharmony_ci if (of_device_is_compatible(child_node, "mmc-slot")) { 14062306a36Sopenharmony_ci host->slot_pdev[i] = of_platform_device_create(child_node, NULL, 14162306a36Sopenharmony_ci &pdev->dev); 14262306a36Sopenharmony_ci if (!host->slot_pdev[i]) 14362306a36Sopenharmony_ci continue; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci ret = cvm_mmc_of_slot_probe(&host->slot_pdev[i]->dev, host); 14662306a36Sopenharmony_ci if (ret) { 14762306a36Sopenharmony_ci of_node_put(child_node); 14862306a36Sopenharmony_ci goto error; 14962306a36Sopenharmony_ci } 15062306a36Sopenharmony_ci } 15162306a36Sopenharmony_ci i++; 15262306a36Sopenharmony_ci } 15362306a36Sopenharmony_ci dev_info(dev, "probed\n"); 15462306a36Sopenharmony_ci return 0; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_cierror: 15762306a36Sopenharmony_ci for (i = 0; i < CAVIUM_MAX_MMC; i++) { 15862306a36Sopenharmony_ci if (host->slot[i]) 15962306a36Sopenharmony_ci cvm_mmc_of_slot_remove(host->slot[i]); 16062306a36Sopenharmony_ci if (host->slot_pdev[i]) { 16162306a36Sopenharmony_ci get_device(&host->slot_pdev[i]->dev); 16262306a36Sopenharmony_ci of_platform_device_destroy(&host->slot_pdev[i]->dev, NULL); 16362306a36Sopenharmony_ci put_device(&host->slot_pdev[i]->dev); 16462306a36Sopenharmony_ci } 16562306a36Sopenharmony_ci } 16662306a36Sopenharmony_ci clk_disable_unprepare(host->clk); 16762306a36Sopenharmony_ci pci_release_regions(pdev); 16862306a36Sopenharmony_ci return ret; 16962306a36Sopenharmony_ci} 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_cistatic void thunder_mmc_remove(struct pci_dev *pdev) 17262306a36Sopenharmony_ci{ 17362306a36Sopenharmony_ci struct cvm_mmc_host *host = pci_get_drvdata(pdev); 17462306a36Sopenharmony_ci u64 dma_cfg; 17562306a36Sopenharmony_ci int i; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci for (i = 0; i < CAVIUM_MAX_MMC; i++) 17862306a36Sopenharmony_ci if (host->slot[i]) 17962306a36Sopenharmony_ci cvm_mmc_of_slot_remove(host->slot[i]); 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci dma_cfg = readq(host->dma_base + MIO_EMM_DMA_CFG(host)); 18262306a36Sopenharmony_ci dma_cfg &= ~MIO_EMM_DMA_CFG_EN; 18362306a36Sopenharmony_ci writeq(dma_cfg, host->dma_base + MIO_EMM_DMA_CFG(host)); 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci clk_disable_unprepare(host->clk); 18662306a36Sopenharmony_ci pci_release_regions(pdev); 18762306a36Sopenharmony_ci} 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_cistatic const struct pci_device_id thunder_mmc_id_table[] = { 19062306a36Sopenharmony_ci { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, 0xa010) }, 19162306a36Sopenharmony_ci { 0, } /* end of table */ 19262306a36Sopenharmony_ci}; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_cistatic struct pci_driver thunder_mmc_driver = { 19562306a36Sopenharmony_ci .name = KBUILD_MODNAME, 19662306a36Sopenharmony_ci .id_table = thunder_mmc_id_table, 19762306a36Sopenharmony_ci .probe = thunder_mmc_probe, 19862306a36Sopenharmony_ci .remove = thunder_mmc_remove, 19962306a36Sopenharmony_ci}; 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_cimodule_pci_driver(thunder_mmc_driver); 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ciMODULE_AUTHOR("Cavium Inc."); 20462306a36Sopenharmony_ciMODULE_DESCRIPTION("Cavium ThunderX eMMC Driver"); 20562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 20662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, thunder_mmc_id_table); 207