Add a platform driver for ThunderX ARM SOCs. Signed-off-by: Jan Glauber <jglauber@xxxxxxxxxx> --- drivers/mmc/host/Kconfig | 9 ++ drivers/mmc/host/Makefile | 2 + drivers/mmc/host/cavium_mmc.h | 38 ++++++ drivers/mmc/host/thunderx_pcidrv_mmc.c | 214 +++++++++++++++++++++++++++++++++ 4 files changed, 263 insertions(+) create mode 100644 drivers/mmc/host/thunderx_pcidrv_mmc.c diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 6f22e16..38d7403 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -353,6 +353,15 @@ config MMC_OCTEON If unsure, say N. +config MMC_THUNDERX + tristate "Cavium ThunderX SD/MMC Card Interface support" + depends on PCI && 64BIT && (ARM64 || COMPILE_TEST) + help + This selects Cavium ThunderX SD/MMC Card Interface. + If you have an Cavium ARM64 board with a Multimedia Card slot + or builtin eMMC chip say Y or M here. If built as a module + the module will be called thunderx_mmc.ko. + config MMC_OMAP tristate "TI OMAP Multimedia Card Interface support" depends on ARCH_OMAP diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 6e57e11..488351f 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -23,6 +23,8 @@ obj-$(CONFIG_MMC_AU1X) += au1xmmc.o obj-$(CONFIG_MMC_MTK) += mtk-sd.o octeon_mmc-objs := cavium_core_mmc.o octeon_platdrv_mmc.o obj-$(CONFIG_MMC_OCTEON) += octeon_mmc.o +thunderx_mmc-objs := cavium_core_mmc.o thunderx_pcidrv_mmc.o +obj-$(CONFIG_MMC_THUNDERX) += thunderx_mmc.o obj-$(CONFIG_MMC_OMAP) += omap.o obj-$(CONFIG_MMC_OMAP_HS) += omap_hsmmc.o obj-$(CONFIG_MMC_ATMELMCI) += atmel-mci.o diff --git a/drivers/mmc/host/cavium_mmc.h b/drivers/mmc/host/cavium_mmc.h index 5f41be9..09fe6d9 100644 --- a/drivers/mmc/host/cavium_mmc.h +++ b/drivers/mmc/host/cavium_mmc.h @@ -11,11 +11,14 @@ #include <linux/io.h> #include <linux/mmc/host.h> #include <linux/of.h> +#include <linux/pci.h> #include <linux/scatterlist.h> #include <linux/semaphore.h> #define CAVIUM_MAX_MMC 4 +#if IS_ENABLED(CONFIG_OCTEON_MMC) + #define MIO_EMM_DMA_CFG 0x00 #define MIO_EMM_DMA_ADR 0x08 @@ -35,6 +38,36 @@ #define MIO_EMM_BUF_IDX 0xe0 #define MIO_EMM_BUF_DAT 0xe8 +#else /* CONFIG_THUNDERX_MMC */ + +#define MIO_EMM_DMA_CFG 0x180 +#define MIO_EMM_DMA_ADR 0x188 +#define MIO_EMM_DMA_INT 0x190 +#define MIO_EMM_DMA_INT_W1S 0x198 +#define MIO_EMM_DMA_INT_ENA_W1S 0x1a0 +#define MIO_EMM_DMA_INT_ENA_W1C 0x1a8 + +#define MIO_EMM_CFG 0x2000 +#define MIO_EMM_SWITCH 0x2048 +#define MIO_EMM_DMA 0x2050 +#define MIO_EMM_CMD 0x2058 +#define MIO_EMM_RSP_STS 0x2060 +#define MIO_EMM_RSP_LO 0x2068 +#define MIO_EMM_RSP_HI 0x2070 +#define MIO_EMM_INT 0x2078 +#define MIO_EMM_INT_EN 0x2080 +#define MIO_EMM_WDOG 0x2088 +#define MIO_EMM_SAMPLE 0x2090 +#define MIO_EMM_STS_MASK 0x2098 +#define MIO_EMM_RCA 0x20a0 +#define MIO_EMM_BUF_IDX 0x20e0 +#define MIO_EMM_BUF_DAT 0x20e8 + +#define MIO_EMM_INT_EN_SET 0x20b0 +#define MIO_EMM_INT_EN_CLR 0x20b8 + +#endif + struct cvm_mmc_host { struct device *dev; void __iomem *base; @@ -66,6 +99,11 @@ struct cvm_mmc_host { void (*dmar_fixup)(struct cvm_mmc_host *, struct mmc_command *, struct mmc_data *, u64); void (*dmar_fixup_done)(struct cvm_mmc_host *); + +#if IS_ENABLED(CONFIG_MMC_THUNDERX) + struct msix_entry *mmc_msix; + unsigned int msix_count; +#endif }; struct cvm_mmc_slot { diff --git a/drivers/mmc/host/thunderx_pcidrv_mmc.c b/drivers/mmc/host/thunderx_pcidrv_mmc.c new file mode 100644 index 0000000..04d03bf --- /dev/null +++ b/drivers/mmc/host/thunderx_pcidrv_mmc.c @@ -0,0 +1,214 @@ +/* + * 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. + */ +#include <linux/dma-mapping.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/slot-gpio.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include "cavium_mmc.h" + +#define DRV_NAME "thunderx_mmc" + +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); + writeq(val, host->base + MIO_EMM_INT_EN_SET); +} + +static int thunder_mmc_register_interrupts(struct cvm_mmc_host *host, + struct pci_dev *pdev) +{ + int ret, i; + + host->msix_count = pci_msix_vec_count(pdev); + host->mmc_msix = devm_kzalloc(&pdev->dev, + (sizeof(struct msix_entry)) * host->msix_count, GFP_KERNEL); + if (!host->mmc_msix) + return -ENOMEM; + + for (i = 0; i < host->msix_count; i++) + host->mmc_msix[i].entry = i; + + ret = pci_enable_msix(pdev, host->mmc_msix, host->msix_count); + if (ret) + return ret; + + /* register interrupts */ + for (i = 0; i < host->msix_count; i++) { + ret = devm_request_irq(&pdev->dev, host->mmc_msix[i].vector, + cvm_mmc_interrupt, + 0, DRV_NAME, 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; + + 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, DRV_NAME); + if (ret) + return ret; + + host->base = pcim_iomap(pdev, 0, pci_resource_len(pdev, 0)); + if (!host->base) + return -EINVAL; + + /* On ThunderX these are identical */ + host->dma_base = host->base; + + host->clk = devm_clk_get(dev, NULL); + if (IS_ERR(host->clk)) + return PTR_ERR(host->clk); + + ret = clk_prepare_enable(host->clk); + if (ret) + return ret; + 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; + + 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); + writeq(3, host->base + MIO_EMM_DMA_INT_ENA_W1C); + + ret = thunder_mmc_register_interrupts(host, pdev); + if (ret) + goto error; + + host->global_pwr_gpiod = devm_gpiod_get_optional(&pdev->dev, "power", + GPIOD_OUT_LOW); + if (IS_ERR(host->global_pwr_gpiod)) { + ret = PTR_ERR(host->global_pwr_gpiod); + goto error; + } + + for_each_child_of_node(node, child_node) { + /* + * XXX hack: mmc_of_parse looks only at the current device's + * DT node. That means we require one device per slot with + * it's node pointing to the slot. The easiest way to get this + * is using of_platform_device_create. Not sure what a proper + * solution is, maybe extend mmc_of_parse to handle multiple + * slots? --jang + */ + struct platform_device *slot_pdev; + + slot_pdev = of_platform_device_create(child_node, NULL, + &pdev->dev); + if (!slot_pdev) + continue; + ret = cvm_mmc_slot_probe(&slot_pdev->dev, host); + if (ret) { + gpiod_set_value_cansleep(host->global_pwr_gpiod, 0); + goto error; + } + } + dev_info(dev, "probed\n"); + return 0; + +error: + clk_disable_unprepare(host->clk); + return ret; +} + +static void thunder_mmc_remove(struct pci_dev *pdev) +{ + struct cvm_mmc_host *host = pci_get_drvdata(pdev); + union mio_emm_dma_cfg dma_cfg; + int i; + + for (i = 0; i < CAVIUM_MAX_MMC; i++) + if (host->slot[i]) + cvm_mmc_slot_remove(host->slot[i]); + + dma_cfg.val = readq(host->dma_base + MIO_EMM_DMA_CFG); + dma_cfg.s.en = 0; + writeq(dma_cfg.val, host->dma_base + MIO_EMM_DMA_CFG); + + gpiod_set_value_cansleep(host->global_pwr_gpiod, 0); + clk_disable_unprepare(host->clk); +} + +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 = DRV_NAME, + .id_table = thunder_mmc_id_table, + .probe = thunder_mmc_probe, + .remove = thunder_mmc_remove, +}; + +static int __init thunder_mmc_init_module(void) +{ + return pci_register_driver(&thunder_mmc_driver); +} + +static void __exit thunder_mmc_exit_module(void) +{ + pci_unregister_driver(&thunder_mmc_driver); +} + +module_init(thunder_mmc_init_module); +module_exit(thunder_mmc_exit_module); + +MODULE_AUTHOR("Cavium Inc."); +MODULE_DESCRIPTION("Cavium ThunderX eMMC Driver"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(pci, thunder_mmc_id_table); + -- 2.9.0.rc0.21.g7777322 -- To unsubscribe from this list: send the line "unsubscribe linux-mmc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html