Hi Ulf,
I've not heard back from you regarding the bitfields so I assume this
means you're insisting on that point. I'd really like to make some
progress with this series, so I'm reposting now. All comments should be
addressed. Patches are on top of 4.11-rc1.
@Rob: I kept your ACK after the removal of the duplicate voltage line,
please let me know if that is not OK.
Changes to v11:
- Fix build error and kill IS_ENABLED() by using an offset per arch
- Added Rob's ACK for the DT bindings
- Removed obsolete voltage-ranges from DT example
- Replace pci_msix_enable() with pci_alloc_irq_vectors()
- Remove superior hardware comment
- Prefixed probe/removal functions with of_
- Merged OF parsing code into one function, change order of property
lookup and simplify code
- Removed slot->sclock, no need to store it there
- Substituted now invisible mmc_card_blockaddr()
- Use new 3.3V CAP for DDR
- Update Copyright
- Allow set_ios to set clock to zero
- Converted bitfields to shift-n-mask logic
- Improved error codes after receiving error interrupt
- Added ifndef guards to header
- Add meaningful interrupt names
- Remove stale mmc_host_ops prototype
Changes to v10:
- Renamed files to get a common prefix
- Select GPIO driver in Kconfig
- Support a fixed regulator
- dts: fixed quotes and re-ordered example
- Use new MMC_CAP_3_3V_DDR instead of 1_8V hack
- Use blksz instead of now internal mmc_card_blockaddr
- Added some maintainers
Previous versions:
v10: https://www.mail-archive.com/[email protected]/msg1295316.html
v9: http://marc.info/?l=linux-mmc&m=147431759215233&w=2
Cheers,
Jan
-------
Jan Glauber (9):
dt-bindings: mmc: Add Cavium SOCs MMC bindings
mmc: cavium: Add core MMC driver for Cavium SOCs
mmc: cavium: Add MMC platform driver for Octeon SOCs
mmc: cavium: Work-around hardware bug on cn6xxx and cnf7xxx
mmc: cavium: Add support for Octeon cn7890
mmc: cavium: Add MMC PCI driver for ThunderX SOCs
mmc: cavium: Add scatter-gather DMA support
mmc: cavium: Support DDR mode for eMMC devices
MAINTAINERS: Add entry for Cavium MMC driver
.../devicetree/bindings/mmc/cavium-mmc.txt | 58 +
MAINTAINERS | 8 +
arch/mips/cavium-octeon/Makefile | 1 +
arch/mips/cavium-octeon/octeon-mmc-l2c.c | 98 ++
drivers/mmc/host/Kconfig | 20 +
drivers/mmc/host/Makefile | 4 +
drivers/mmc/host/cavium-mmc.c | 1115 ++++++++++++++++++++
drivers/mmc/host/cavium-mmc.h | 212 ++++
drivers/mmc/host/cavium-pci-thunderx.c | 201 ++++
drivers/mmc/host/cavium-pltfm-octeon.c | 260 +++++
10 files changed, 1977 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mmc/cavium-mmc.txt
create mode 100644 arch/mips/cavium-octeon/octeon-mmc-l2c.c
create mode 100644 drivers/mmc/host/cavium-mmc.c
create mode 100644 drivers/mmc/host/cavium-mmc.h
create mode 100644 drivers/mmc/host/cavium-pci-thunderx.c
create mode 100644 drivers/mmc/host/cavium-pltfm-octeon.c
--
2.9.0.rc0.21.g7777322
Add a platform driver for ThunderX ARM SOCs.
Signed-off-by: Jan Glauber <[email protected]>
---
drivers/mmc/host/Kconfig | 10 ++
drivers/mmc/host/Makefile | 2 +
drivers/mmc/host/cavium-mmc.h | 10 +-
drivers/mmc/host/cavium-pci-thunderx.c | 198 +++++++++++++++++++++++++++++++++
4 files changed, 218 insertions(+), 2 deletions(-)
create mode 100644 drivers/mmc/host/cavium-pci-thunderx.c
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 68cc811..3983dee 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -632,6 +632,16 @@ config MMC_CAVIUM_OCTEON
If unsure, say N.
+config MMC_CAVIUM_THUNDERX
+ tristate "Cavium ThunderX SD/MMC Card Interface support"
+ depends on PCI && 64BIT && (ARM64 || COMPILE_TEST)
+ select GPIO_THUNDERX
+ 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_DW
tristate "Synopsys DesignWare Memory Card Interface"
depends on HAS_DMA
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index c7f0ccf..0068610 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -44,6 +44,8 @@ obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o
obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o
octeon-mmc-objs := cavium-mmc.o cavium-pltfm-octeon.o
obj-$(CONFIG_MMC_CAVIUM_OCTEON) += octeon-mmc.o
+thunderx-mmc-objs := cavium-mmc.o cavium-pci-thunderx.o
+obj-$(CONFIG_MMC_CAVIUM_THUNDERX) += thunderx-mmc.o
obj-$(CONFIG_MMC_DW) += dw_mmc.o
obj-$(CONFIG_MMC_DW_PLTFM) += dw_mmc-pltfm.o
obj-$(CONFIG_MMC_DW_EXYNOS) += dw_mmc-exynos.o
diff --git a/drivers/mmc/host/cavium-mmc.h b/drivers/mmc/host/cavium-mmc.h
index 4b22432..fb82aee 100644
--- a/drivers/mmc/host/cavium-mmc.h
+++ b/drivers/mmc/host/cavium-mmc.h
@@ -22,8 +22,12 @@
#define CAVIUM_MAX_MMC 4
/* DMA register addresses */
-#define MIO_EMM_DMA_CFG(x) (0x00 + x->reg_off_dma)
-#define MIO_EMM_DMA_ADR(x) (0x08 + x->reg_off_dma)
+#define MIO_EMM_DMA_CFG(x) (0x20 + x->reg_off_dma)
+#define MIO_EMM_DMA_ADR(x) (0x28 + x->reg_off_dma)
+#define MIO_EMM_DMA_INT(x) (0x30 + x->reg_off_dma)
+#define MIO_EMM_DMA_INT_W1S(x) (0x38 + x->reg_off_dma)
+#define MIO_EMM_DMA_INT_ENA_W1S(x) (0x40 + x->reg_off_dma)
+#define MIO_EMM_DMA_INT_ENA_W1C(x) (0x48 + x->reg_off_dma)
/* register addresses */
#define MIO_EMM_CFG(x) (0x00 + x->reg_off)
@@ -39,6 +43,8 @@
#define MIO_EMM_SAMPLE(x) (0x90 + x->reg_off)
#define MIO_EMM_STS_MASK(x) (0x98 + x->reg_off)
#define MIO_EMM_RCA(x) (0xa0 + x->reg_off)
+#define MIO_EMM_INT_EN_SET(x) (0xb0 + x->reg_off)
+#define MIO_EMM_INT_EN_CLR(x) (0xb8 + x->reg_off)
#define MIO_EMM_BUF_IDX(x) (0xe0 + x->reg_off)
#define MIO_EMM_BUF_DAT(x) (0xe8 + x->reg_off)
diff --git a/drivers/mmc/host/cavium-pci-thunderx.c b/drivers/mmc/host/cavium-pci-thunderx.c
new file mode 100644
index 0000000..6ad36b4
--- /dev/null
+++ b/drivers/mmc/host/cavium-pci-thunderx.c
@@ -0,0 +1,198 @@
+/*
+ * 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 <linux/pci.h>
+#include "cavium-mmc.h"
+
+struct platform_device *slot_pdev[2];
+
+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));
+ if (!host->base)
+ return -EINVAL;
+
+ /* On ThunderX these are identical */
+ host->dma_base = host->base;
+
+ host->reg_off = 0x2000;
+ host->reg_off_dma = 0x160;
+
+ 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(host));
+ writeq(3, host->base + MIO_EMM_DMA_INT_ENA_W1C(host));
+
+ ret = thunder_mmc_register_interrupts(host, pdev);
+ if (ret)
+ goto error;
+
+ for_each_child_of_node(node, child_node) {
+ /*
+ * TODO: 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 (!slot_pdev[i])
+ slot_pdev[i] = of_platform_device_create(child_node, NULL,
+ &pdev->dev);
+ if (!slot_pdev[i])
+ continue;
+ ret = cvm_mmc_of_slot_probe(&slot_pdev[i]->dev, host);
+ if (ret)
+ goto error;
+ i++;
+ }
+ 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);
+ 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]);
+ platform_device_del(slot_pdev[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);
+}
+
+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,
+};
+
+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
Add a platform driver for Octeon MIPS SOCs.
Signed-off-by: Jan Glauber <[email protected]>
Signed-off-by: David Daney <[email protected]>
Signed-off-by: Steven J. Hill <[email protected]>
---
drivers/mmc/host/Kconfig | 10 ++
drivers/mmc/host/Makefile | 2 +
drivers/mmc/host/cavium-pltfm-octeon.c | 183 +++++++++++++++++++++++++++++++++
3 files changed, 195 insertions(+)
create mode 100644 drivers/mmc/host/cavium-pltfm-octeon.c
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index f08691a..68cc811 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -622,6 +622,16 @@ config SDH_BFIN_MISSING_CMD_PULLUP_WORKAROUND
help
If you say yes here SD-Cards may work on the EZkit.
+config MMC_CAVIUM_OCTEON
+ tristate "Cavium OCTEON SD/MMC Card Interface support"
+ depends on CAVIUM_OCTEON_SOC
+ help
+ This selects Cavium OCTEON SD/MMC card Interface.
+ If you have an OCTEON board with a Multimedia Card slot,
+ say Y or M here.
+
+ If unsure, say N.
+
config MMC_DW
tristate "Synopsys DesignWare Memory Card Interface"
depends on HAS_DMA
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index 6d548c4..c7f0ccf 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -42,6 +42,8 @@ obj-$(CONFIG_MMC_SDHI) += sh_mobile_sdhi.o
obj-$(CONFIG_MMC_CB710) += cb710-mmc.o
obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o
obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o
+octeon-mmc-objs := cavium-mmc.o cavium-pltfm-octeon.o
+obj-$(CONFIG_MMC_CAVIUM_OCTEON) += octeon-mmc.o
obj-$(CONFIG_MMC_DW) += dw_mmc.o
obj-$(CONFIG_MMC_DW_PLTFM) += dw_mmc-pltfm.o
obj-$(CONFIG_MMC_DW_EXYNOS) += dw_mmc-exynos.o
diff --git a/drivers/mmc/host/cavium-pltfm-octeon.c b/drivers/mmc/host/cavium-pltfm-octeon.c
new file mode 100644
index 0000000..e83d143
--- /dev/null
+++ b/drivers/mmc/host/cavium-pltfm-octeon.c
@@ -0,0 +1,183 @@
+/*
+ * Driver for MMC and SSD cards for Cavium OCTEON 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) 2012-2017 Cavium Inc.
+ */
+#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 <asm/octeon/octeon.h>
+#include "cavium-mmc.h"
+
+#define CVMX_MIO_BOOT_CTL CVMX_ADD_IO_SEG(0x00011800000000D0ull)
+
+static void octeon_mmc_acquire_bus(struct cvm_mmc_host *host)
+{
+ /* Switch the MMC controller onto the bus. */
+ down(&octeon_bootbus_sem);
+ writeq(0, (void __iomem *)CVMX_MIO_BOOT_CTL);
+}
+
+static void octeon_mmc_release_bus(struct cvm_mmc_host *host)
+{
+ up(&octeon_bootbus_sem);
+}
+
+static void octeon_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(host));
+}
+
+static int octeon_mmc_probe(struct platform_device *pdev)
+{
+ struct device_node *cn, *node = pdev->dev.of_node;
+ struct cvm_mmc_host *host;
+ struct resource *res;
+ void __iomem *base;
+ int mmc_irq[9];
+ int i, ret = 0;
+ u64 val;
+
+ host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL);
+ if (!host)
+ return -ENOMEM;
+
+ host->dev = &pdev->dev;
+ host->acquire_bus = octeon_mmc_acquire_bus;
+ host->release_bus = octeon_mmc_release_bus;
+ host->int_enable = octeon_mmc_int_enable;
+
+ host->sys_freq = octeon_get_io_clock_rate();
+
+ /* First one is EMM second DMA */
+ for (i = 0; i < 2; i++) {
+ mmc_irq[i] = platform_get_irq(pdev, i);
+ if (mmc_irq[i] < 0)
+ return mmc_irq[i];
+ }
+ host->last_slot = -1;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "Platform resource[0] is missing\n");
+ return -ENXIO;
+ }
+ base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+ host->base = (void __iomem *)base;
+ host->reg_off = 0;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (!res) {
+ dev_err(&pdev->dev, "Platform resource[1] is missing\n");
+ return -EINVAL;
+ }
+ base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+ host->dma_base = (void __iomem *)base;
+ host->reg_off_dma = 0;
+
+ /*
+ * Clear out any pending interrupts that may be left over from
+ * bootloader.
+ */
+ val = readq(host->base + MIO_EMM_INT(host));
+ writeq(val, host->base + MIO_EMM_INT(host));
+
+ ret = devm_request_irq(&pdev->dev, mmc_irq[0],
+ cvm_mmc_interrupt, 0, KBUILD_MODNAME, host);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Error: devm_request_irq %d\n",
+ mmc_irq[0]);
+ return ret;
+ }
+
+ host->global_pwr_gpiod = devm_gpiod_get_optional(&pdev->dev, "power",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(host->global_pwr_gpiod)) {
+ dev_err(&pdev->dev, "Invalid power GPIO\n");
+ return PTR_ERR(host->global_pwr_gpiod);
+ }
+
+ platform_set_drvdata(pdev, host);
+
+ for_each_child_of_node(node, cn) {
+ struct platform_device *slot_pdev;
+
+ slot_pdev = of_platform_device_create(cn, NULL, &pdev->dev);
+ ret = cvm_mmc_of_slot_probe(&slot_pdev->dev, host);
+ if (ret) {
+ dev_err(&pdev->dev, "Error populating slots\n");
+ gpiod_set_value_cansleep(host->global_pwr_gpiod, 0);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int octeon_mmc_remove(struct platform_device *pdev)
+{
+ struct cvm_mmc_host *host = platform_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));
+
+ gpiod_set_value_cansleep(host->global_pwr_gpiod, 0);
+
+ return 0;
+}
+
+static const struct of_device_id octeon_mmc_match[] = {
+ {
+ .compatible = "cavium,octeon-6130-mmc",
+ },
+ {
+ .compatible = "cavium,octeon-7890-mmc",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, octeon_mmc_match);
+
+static struct platform_driver octeon_mmc_driver = {
+ .probe = octeon_mmc_probe,
+ .remove = octeon_mmc_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = octeon_mmc_match,
+ },
+};
+
+static int __init octeon_mmc_init(void)
+{
+ return platform_driver_register(&octeon_mmc_driver);
+}
+
+static void __exit octeon_mmc_cleanup(void)
+{
+ platform_driver_unregister(&octeon_mmc_driver);
+}
+
+module_init(octeon_mmc_init);
+module_exit(octeon_mmc_cleanup);
+
+MODULE_AUTHOR("Cavium Inc. <[email protected]>");
+MODULE_DESCRIPTION("Low-level driver for Cavium OCTEON MMC/SSD card");
+MODULE_LICENSE("GPL");
--
2.9.0.rc0.21.g7777322
The MMC unit on Octeon cn7890 differs in that it has multiple
interrupts. Requires a lock for the interrupt handler. DMA addresses
have a dedicated 64 bit register now, so use that when available.
Signed-off-by: Jan Glauber <[email protected]>
Signed-off-by: David Daney <[email protected]>
Signed-off-by: Steven J. Hill <[email protected]>
---
drivers/mmc/host/cavium-mmc.c | 16 ++++++-
drivers/mmc/host/cavium-mmc.h | 7 +++
drivers/mmc/host/cavium-pltfm-octeon.c | 79 +++++++++++++++++++++++++++-------
3 files changed, 85 insertions(+), 17 deletions(-)
diff --git a/drivers/mmc/host/cavium-mmc.c b/drivers/mmc/host/cavium-mmc.c
index c1d3c65..c57abed 100644
--- a/drivers/mmc/host/cavium-mmc.c
+++ b/drivers/mmc/host/cavium-mmc.c
@@ -412,9 +412,15 @@ irqreturn_t cvm_mmc_interrupt(int irq, void *dev_id)
{
struct cvm_mmc_host *host = dev_id;
struct mmc_request *req;
+ unsigned long flags = 0;
u64 emm_int, rsp_sts;
bool host_done;
+ if (host->need_irq_handler_lock)
+ spin_lock_irqsave(&host->irq_handler_lock, flags);
+ else
+ __acquire(&host->irq_handler_lock);
+
/* Clear interrupt bits (write 1 clears ). */
emm_int = readq(host->base + MIO_EMM_INT(host));
writeq(emm_int, host->base + MIO_EMM_INT(host));
@@ -473,6 +479,10 @@ irqreturn_t cvm_mmc_interrupt(int irq, void *dev_id)
if (host_done)
host->release_bus(host);
out:
+ if (host->need_irq_handler_lock)
+ spin_unlock_irqrestore(&host->irq_handler_lock, flags);
+ else
+ __release(&host->irq_handler_lock);
return IRQ_RETVAL(emm_int != 0);
}
@@ -500,11 +510,15 @@ static u64 prepare_dma_single(struct cvm_mmc_host *host, struct mmc_data *data)
(sg_dma_len(&data->sg[0]) / 8) - 1);
addr = sg_dma_address(&data->sg[0]);
- dma_cfg |= FIELD_PREP(MIO_EMM_DMA_CFG_ADR, addr);
+ if (!host->big_dma_addr)
+ dma_cfg |= FIELD_PREP(MIO_EMM_DMA_CFG_ADR, addr);
writeq(dma_cfg, host->dma_base + MIO_EMM_DMA_CFG(host));
pr_debug("[%s] sg_dma_len: %u total sg_elem: %d\n",
(rw) ? "W" : "R", sg_dma_len(&data->sg[0]), count);
+
+ if (host->big_dma_addr)
+ writeq(addr, host->dma_base + MIO_EMM_DMA_ADR(host));
return addr;
}
diff --git a/drivers/mmc/host/cavium-mmc.h b/drivers/mmc/host/cavium-mmc.h
index 3ee6dae..4b22432 100644
--- a/drivers/mmc/host/cavium-mmc.h
+++ b/drivers/mmc/host/cavium-mmc.h
@@ -23,6 +23,7 @@
/* DMA register addresses */
#define MIO_EMM_DMA_CFG(x) (0x00 + x->reg_off_dma)
+#define MIO_EMM_DMA_ADR(x) (0x08 + x->reg_off_dma)
/* register addresses */
#define MIO_EMM_CFG(x) (0x00 + x->reg_off)
@@ -57,6 +58,12 @@ struct cvm_mmc_host {
struct sg_mapping_iter smi;
bool dma_active;
+ bool has_ciu3;
+ bool big_dma_addr;
+ bool need_irq_handler_lock;
+ spinlock_t irq_handler_lock;
+ struct semaphore mmc_serializer;
+
struct gpio_desc *global_pwr_gpiod;
struct cvm_mmc_slot *slot[CAVIUM_MAX_MMC];
diff --git a/drivers/mmc/host/cavium-pltfm-octeon.c b/drivers/mmc/host/cavium-pltfm-octeon.c
index 9dabfa4..e1fe78b 100644
--- a/drivers/mmc/host/cavium-pltfm-octeon.c
+++ b/drivers/mmc/host/cavium-pltfm-octeon.c
@@ -23,20 +23,28 @@ extern void l2c_unlock_mem_region(u64 start, u64 len);
static void octeon_mmc_acquire_bus(struct cvm_mmc_host *host)
{
- /* Switch the MMC controller onto the bus. */
- down(&octeon_bootbus_sem);
- writeq(0, (void __iomem *)CVMX_MIO_BOOT_CTL);
+ if (!host->has_ciu3) {
+ /* Switch the MMC controller onto the bus. */
+ down(&octeon_bootbus_sem);
+ writeq(0, (void __iomem *)CVMX_MIO_BOOT_CTL);
+ } else {
+ down(&host->mmc_serializer);
+ }
}
static void octeon_mmc_release_bus(struct cvm_mmc_host *host)
{
- up(&octeon_bootbus_sem);
+ if (!host->has_ciu3)
+ up(&octeon_bootbus_sem);
+ else
+ up(&host->mmc_serializer);
}
static void octeon_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(host));
+ if (!host->dma_active || (host->dma_active && !host->has_ciu3))
+ writeq(val, host->base + MIO_EMM_INT_EN(host));
}
static void octeon_mmc_dmar_fixup(struct cvm_mmc_host *host,
@@ -75,6 +83,9 @@ static int octeon_mmc_probe(struct platform_device *pdev)
if (!host)
return -ENOMEM;
+ spin_lock_init(&host->irq_handler_lock);
+ sema_init(&host->mmc_serializer, 1);
+
host->dev = &pdev->dev;
host->acquire_bus = octeon_mmc_acquire_bus;
host->release_bus = octeon_mmc_release_bus;
@@ -87,12 +98,34 @@ static int octeon_mmc_probe(struct platform_device *pdev)
host->sys_freq = octeon_get_io_clock_rate();
- /* First one is EMM second DMA */
- for (i = 0; i < 2; i++) {
- mmc_irq[i] = platform_get_irq(pdev, i);
- if (mmc_irq[i] < 0)
- return mmc_irq[i];
+ if (of_device_is_compatible(node, "cavium,octeon-7890-mmc")) {
+ host->big_dma_addr = true;
+ host->need_irq_handler_lock = true;
+ host->has_ciu3 = true;
+ /*
+ * First seven are the EMM_INT bits 0..6, then two for
+ * the EMM_DMA_INT bits
+ */
+ for (i = 0; i < 9; i++) {
+ mmc_irq[i] = platform_get_irq(pdev, i);
+ if (mmc_irq[i] < 0)
+ return mmc_irq[i];
+
+ /* work around legacy u-boot device trees */
+ irq_set_irq_type(mmc_irq[i], IRQ_TYPE_EDGE_RISING);
+ }
+ } else {
+ host->big_dma_addr = false;
+ host->need_irq_handler_lock = false;
+ host->has_ciu3 = false;
+ /* First one is EMM second DMA */
+ for (i = 0; i < 2; i++) {
+ mmc_irq[i] = platform_get_irq(pdev, i);
+ if (mmc_irq[i] < 0)
+ return mmc_irq[i];
+ }
}
+
host->last_slot = -1;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
@@ -124,12 +157,26 @@ static int octeon_mmc_probe(struct platform_device *pdev)
val = readq(host->base + MIO_EMM_INT(host));
writeq(val, host->base + MIO_EMM_INT(host));
- ret = devm_request_irq(&pdev->dev, mmc_irq[0],
- cvm_mmc_interrupt, 0, KBUILD_MODNAME, host);
- if (ret < 0) {
- dev_err(&pdev->dev, "Error: devm_request_irq %d\n",
- mmc_irq[0]);
- return ret;
+ if (host->has_ciu3) {
+ /* Only CMD_DONE, DMA_DONE, CMD_ERR, DMA_ERR */
+ for (i = 1; i <= 4; i++) {
+ ret = devm_request_irq(&pdev->dev, mmc_irq[i],
+ cvm_mmc_interrupt,
+ 0, cvm_mmc_irq_names[i], host);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Error: devm_request_irq %d\n",
+ mmc_irq[i]);
+ return ret;
+ }
+ }
+ } else {
+ ret = devm_request_irq(&pdev->dev, mmc_irq[0],
+ cvm_mmc_interrupt, 0, KBUILD_MODNAME, host);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Error: devm_request_irq %d\n",
+ mmc_irq[0]);
+ return ret;
+ }
}
host->global_pwr_gpiod = devm_gpiod_get_optional(&pdev->dev, "power",
--
2.9.0.rc0.21.g7777322
Prevent data corruption on cn6xxx and cnf7xxx.
Due to an imperfection in the design of the MMC bus hardware,
the 2nd to last cache block of a DMA read must be locked into the L2
cache.
Signed-off-by: Jan Glauber <[email protected]>
Signed-off-by: David Daney <[email protected]>
Signed-off-by: Steven J. Hill <[email protected]>
---
arch/mips/cavium-octeon/Makefile | 1 +
arch/mips/cavium-octeon/octeon-mmc-l2c.c | 98 ++++++++++++++++++++++++++++++++
drivers/mmc/host/cavium-mmc.c | 5 ++
drivers/mmc/host/cavium-mmc.h | 5 ++
drivers/mmc/host/cavium-pltfm-octeon.c | 30 ++++++++++
5 files changed, 139 insertions(+)
create mode 100644 arch/mips/cavium-octeon/octeon-mmc-l2c.c
diff --git a/arch/mips/cavium-octeon/Makefile b/arch/mips/cavium-octeon/Makefile
index 7c02e54..3329a89 100644
--- a/arch/mips/cavium-octeon/Makefile
+++ b/arch/mips/cavium-octeon/Makefile
@@ -19,3 +19,4 @@ obj-$(CONFIG_MTD) += flash_setup.o
obj-$(CONFIG_SMP) += smp.o
obj-$(CONFIG_OCTEON_ILM) += oct_ilm.o
obj-$(CONFIG_USB) += octeon-usb.o
+obj-$(CONFIG_MMC_CAVIUM_OCTEON) += octeon-mmc-l2c.o
diff --git a/arch/mips/cavium-octeon/octeon-mmc-l2c.c b/arch/mips/cavium-octeon/octeon-mmc-l2c.c
new file mode 100644
index 0000000..6aaaf73
--- /dev/null
+++ b/arch/mips/cavium-octeon/octeon-mmc-l2c.c
@@ -0,0 +1,98 @@
+/*
+ * Driver for MMC and SSD cards for Cavium OCTEON 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) 2012-2016 Cavium Inc.
+ */
+#include <linux/export.h>
+#include <asm/octeon/octeon.h>
+
+/*
+ * The functions below are used for the EMMC-17978 workaround.
+ *
+ * Due to an imperfection in the design of the MMC bus hardware,
+ * the 2nd to last cache block of a DMA read must be locked into the L2 Cache.
+ * Otherwise, data corruption may occur.
+ */
+
+static inline void *phys_to_ptr(u64 address)
+{
+ return (void *)(address | (1ull << 63)); /* XKPHYS */
+}
+
+/**
+ * Lock a single line into L2. The line is zeroed before locking
+ * to make sure no dram accesses are made.
+ *
+ * @addr Physical address to lock
+ */
+static void l2c_lock_line(u64 addr)
+{
+ char *addr_ptr = phys_to_ptr(addr);
+
+ asm volatile (
+ "cache 31, %[line]" /* Unlock the line */
+ :: [line] "m" (*addr_ptr));
+}
+
+/**
+ * Unlock a single line in the L2 cache.
+ *
+ * @addr Physical address to unlock
+ *
+ * Return Zero on success
+ */
+static void l2c_unlock_line(u64 addr)
+{
+ char *addr_ptr = phys_to_ptr(addr);
+
+ asm volatile (
+ "cache 23, %[line]" /* Unlock the line */
+ :: [line] "m" (*addr_ptr));
+}
+
+/**
+ * Locks a memory region in the L2 cache
+ *
+ * @start - start address to begin locking
+ * @len - length in bytes to lock
+ */
+void l2c_lock_mem_region(u64 start, u64 len)
+{
+ u64 end;
+
+ /* Round start/end to cache line boundaries */
+ end = ALIGN(start + len - 1, CVMX_CACHE_LINE_SIZE);
+ start = ALIGN(start, CVMX_CACHE_LINE_SIZE);
+
+ while (start <= end) {
+ l2c_lock_line(start);
+ start += CVMX_CACHE_LINE_SIZE;
+ }
+ asm volatile("sync");
+}
+EXPORT_SYMBOL_GPL(l2c_lock_mem_region);
+
+/**
+ * Unlock a memory region in the L2 cache
+ *
+ * @start - start address to unlock
+ * @len - length to unlock in bytes
+ */
+void l2c_unlock_mem_region(u64 start, u64 len)
+{
+ u64 end;
+
+ /* Round start/end to cache line boundaries */
+ end = ALIGN(start + len - 1, CVMX_CACHE_LINE_SIZE);
+ start = ALIGN(start, CVMX_CACHE_LINE_SIZE);
+
+ while (start <= end) {
+ l2c_unlock_line(start);
+ start += CVMX_CACHE_LINE_SIZE;
+ }
+}
+EXPORT_SYMBOL_GPL(l2c_unlock_mem_region);
diff --git a/drivers/mmc/host/cavium-mmc.c b/drivers/mmc/host/cavium-mmc.c
index 11fdcfb..c1d3c65 100644
--- a/drivers/mmc/host/cavium-mmc.c
+++ b/drivers/mmc/host/cavium-mmc.c
@@ -468,6 +468,8 @@ irqreturn_t cvm_mmc_interrupt(int irq, void *dev_id)
req->done(req);
no_req_done:
+ if (host->dmar_fixup_done)
+ host->dmar_fixup_done(host);
if (host_done)
host->release_bus(host);
out:
@@ -572,6 +574,9 @@ static void cvm_mmc_dma_request(struct mmc_host *mmc,
host->int_enable(host, MIO_EMM_INT_CMD_ERR | MIO_EMM_INT_DMA_DONE |
MIO_EMM_INT_DMA_ERR);
+ if (host->dmar_fixup)
+ host->dmar_fixup(host, mrq->cmd, data, addr);
+
/*
* If we have a valid SD card in the slot, we set the response
* bit mask to check for CRC errors and timeouts only.
diff --git a/drivers/mmc/host/cavium-mmc.h b/drivers/mmc/host/cavium-mmc.h
index c3843448..3ee6dae 100644
--- a/drivers/mmc/host/cavium-mmc.h
+++ b/drivers/mmc/host/cavium-mmc.h
@@ -48,6 +48,7 @@ struct cvm_mmc_host {
int reg_off;
int reg_off_dma;
u64 emm_cfg;
+ u64 n_minus_one; /* OCTEON II workaround location */
int last_slot;
struct clk *clk;
int sys_freq;
@@ -63,6 +64,10 @@ struct cvm_mmc_host {
void (*acquire_bus)(struct cvm_mmc_host *);
void (*release_bus)(struct cvm_mmc_host *);
void (*int_enable)(struct cvm_mmc_host *, u64);
+ /* required on some MIPS models */
+ void (*dmar_fixup)(struct cvm_mmc_host *, struct mmc_command *,
+ struct mmc_data *, u64);
+ void (*dmar_fixup_done)(struct cvm_mmc_host *);
};
struct cvm_mmc_slot {
diff --git a/drivers/mmc/host/cavium-pltfm-octeon.c b/drivers/mmc/host/cavium-pltfm-octeon.c
index e83d143..9dabfa4 100644
--- a/drivers/mmc/host/cavium-pltfm-octeon.c
+++ b/drivers/mmc/host/cavium-pltfm-octeon.c
@@ -18,6 +18,9 @@
#define CVMX_MIO_BOOT_CTL CVMX_ADD_IO_SEG(0x00011800000000D0ull)
+extern void l2c_lock_mem_region(u64 start, u64 len);
+extern void l2c_unlock_mem_region(u64 start, u64 len);
+
static void octeon_mmc_acquire_bus(struct cvm_mmc_host *host)
{
/* Switch the MMC controller onto the bus. */
@@ -36,6 +39,28 @@ static void octeon_mmc_int_enable(struct cvm_mmc_host *host, u64 val)
writeq(val, host->base + MIO_EMM_INT_EN(host));
}
+static void octeon_mmc_dmar_fixup(struct cvm_mmc_host *host,
+ struct mmc_command *cmd,
+ struct mmc_data *data,
+ u64 addr)
+{
+ if (cmd->opcode != MMC_WRITE_MULTIPLE_BLOCK)
+ return;
+ if (data->blksz * data->blocks <= 1024)
+ return;
+
+ host->n_minus_one = addr + (data->blksz * data->blocks) - 1024;
+ l2c_lock_mem_region(host->n_minus_one, 512);
+}
+
+static void octeon_mmc_dmar_fixup_done(struct cvm_mmc_host *host)
+{
+ if (!host->n_minus_one)
+ return;
+ l2c_unlock_mem_region(host->n_minus_one, 512);
+ host->n_minus_one = 0;
+}
+
static int octeon_mmc_probe(struct platform_device *pdev)
{
struct device_node *cn, *node = pdev->dev.of_node;
@@ -54,6 +79,11 @@ static int octeon_mmc_probe(struct platform_device *pdev)
host->acquire_bus = octeon_mmc_acquire_bus;
host->release_bus = octeon_mmc_release_bus;
host->int_enable = octeon_mmc_int_enable;
+ if (OCTEON_IS_MODEL(OCTEON_CN6XXX) ||
+ OCTEON_IS_MODEL(OCTEON_CNF7XXX)) {
+ host->dmar_fixup = octeon_mmc_dmar_fixup;
+ host->dmar_fixup_done = octeon_mmc_dmar_fixup_done;
+ }
host->sys_freq = octeon_get_io_clock_rate();
--
2.9.0.rc0.21.g7777322
Add Support for the scatter-gather DMA available in the
ThunderX MMC units. Up to 16 DMA requests can be processed
together.
Signed-off-by: Jan Glauber <[email protected]>
---
drivers/mmc/host/cavium-mmc.c | 104 ++++++++++++++++++++++++++++++++-
drivers/mmc/host/cavium-mmc.h | 16 +++++
drivers/mmc/host/cavium-pci-thunderx.c | 3 +
3 files changed, 120 insertions(+), 3 deletions(-)
diff --git a/drivers/mmc/host/cavium-mmc.c b/drivers/mmc/host/cavium-mmc.c
index c57abed..fb6e1c1 100644
--- a/drivers/mmc/host/cavium-mmc.c
+++ b/drivers/mmc/host/cavium-mmc.c
@@ -377,9 +377,32 @@ static int finish_dma_single(struct cvm_mmc_host *host, struct mmc_data *data)
return 1;
}
+static int finish_dma_sg(struct cvm_mmc_host *host, struct mmc_data *data)
+{
+ u64 fifo_cfg;
+ int count;
+
+ /* Check if there are any pending requests left */
+ fifo_cfg = readq(host->dma_base + MIO_EMM_DMA_FIFO_CFG(host));
+ count = FIELD_GET(MIO_EMM_DMA_FIFO_CFG_COUNT, fifo_cfg);
+ if (count)
+ dev_err(host->dev, "%u requests still pending\n", count);
+
+ data->bytes_xfered = data->blocks * data->blksz;
+ data->error = 0;
+
+ /* Clear and disable FIFO */
+ writeq(BIT_ULL(16), host->dma_base + MIO_EMM_DMA_FIFO_CFG(host));
+ dma_unmap_sg(host->dev, data->sg, data->sg_len, get_dma_dir(data));
+ return 1;
+}
+
static int finish_dma(struct cvm_mmc_host *host, struct mmc_data *data)
{
- return finish_dma_single(host, data);
+ if (host->use_sg && data->sg_len > 1)
+ return finish_dma_sg(host, data);
+ else
+ return finish_dma_single(host, data);
}
static int check_status(u64 rsp_sts)
@@ -522,9 +545,81 @@ static u64 prepare_dma_single(struct cvm_mmc_host *host, struct mmc_data *data)
return addr;
}
+/*
+ * Queue complete sg list into the FIFO.
+ * Returns 0 on error, 1 otherwise.
+ */
+static u64 prepare_dma_sg(struct cvm_mmc_host *host, struct mmc_data *data)
+{
+ struct scatterlist *sg;
+ u64 fifo_cmd, addr;
+ int count, i, rw;
+
+ count = dma_map_sg(host->dev, data->sg, data->sg_len,
+ get_dma_dir(data));
+ if (!count)
+ return 0;
+ if (count > 16)
+ goto error;
+
+ /* Enable FIFO by removing CLR bit */
+ writeq(0, host->dma_base + MIO_EMM_DMA_FIFO_CFG(host));
+
+ for_each_sg(data->sg, sg, count, i) {
+ /* Program DMA address */
+ addr = sg_dma_address(sg);
+ if (addr & 7)
+ goto error;
+ writeq(addr, host->dma_base + MIO_EMM_DMA_FIFO_ADR(host));
+
+ /*
+ * If we have scatter-gather support we also have an extra
+ * register for the DMA addr, so no need to check
+ * host->big_dma_addr here.
+ */
+ rw = (data->flags & MMC_DATA_WRITE) ? 1 : 0;
+ fifo_cmd = FIELD_PREP(MIO_EMM_DMA_FIFO_CMD_RW, rw);
+
+ /* enable interrupts on the last element */
+ fifo_cmd |= FIELD_PREP(MIO_EMM_DMA_FIFO_CMD_INTDIS,
+ (i + 1 == count) ? 0 : 1);
+
+#ifdef __LITTLE_ENDIAN
+ fifo_cmd |= FIELD_PREP(MIO_EMM_DMA_FIFO_CMD_ENDIAN, 1);
+#endif
+ fifo_cmd |= FIELD_PREP(MIO_EMM_DMA_FIFO_CMD_SIZE,
+ sg_dma_len(sg) / 8 - 1);
+ /*
+ * The write copies the address and the command to the FIFO
+ * and increments the FIFO's COUNT field.
+ */
+ writeq(fifo_cmd, host->dma_base + MIO_EMM_DMA_FIFO_CMD(host));
+ pr_debug("[%s] sg_dma_len: %u sg_elem: %d/%d\n",
+ (rw) ? "W" : "R", sg_dma_len(sg), i, count);
+ }
+
+ /*
+ * In difference to prepare_dma_single we don't return the
+ * address here, as it would not make sense for scatter-gather.
+ * The dma fixup is only required on models that don't support
+ * scatter-gather, so that is not a problem.
+ */
+ return 1;
+
+error:
+ WARN_ON_ONCE(1);
+ dma_unmap_sg(host->dev, data->sg, data->sg_len, get_dma_dir(data));
+ /* Disable FIFO */
+ writeq(BIT_ULL(16), host->dma_base + MIO_EMM_DMA_FIFO_CFG(host));
+ return 0;
+}
+
static u64 prepare_dma(struct cvm_mmc_host *host, struct mmc_data *data)
{
- return prepare_dma_single(host, data);
+ if (host->use_sg && data->sg_len > 1)
+ return prepare_dma_sg(host, data);
+ else
+ return prepare_dma_single(host, data);
}
static u64 prepare_ext_dma(struct mmc_host *mmc, struct mmc_request *mrq)
@@ -965,7 +1060,10 @@ int cvm_mmc_of_slot_probe(struct device *dev, struct cvm_mmc_host *host)
mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED |
MMC_CAP_ERASE | MMC_CAP_CMD23 | MMC_CAP_POWER_OFF_CARD;
- mmc->max_segs = 1;
+ if (host->use_sg)
+ mmc->max_segs = 16;
+ else
+ mmc->max_segs = 1;
/* DMA size field can address up to 8 MB */
mmc->max_seg_size = 8 * 1024 * 1024;
diff --git a/drivers/mmc/host/cavium-mmc.h b/drivers/mmc/host/cavium-mmc.h
index fb82aee..5893f3a 100644
--- a/drivers/mmc/host/cavium-mmc.h
+++ b/drivers/mmc/host/cavium-mmc.h
@@ -22,6 +22,9 @@
#define CAVIUM_MAX_MMC 4
/* DMA register addresses */
+#define MIO_EMM_DMA_FIFO_CFG(x) (0x00 + x->reg_off_dma)
+#define MIO_EMM_DMA_FIFO_ADR(x) (0x10 + x->reg_off_dma)
+#define MIO_EMM_DMA_FIFO_CMD(x) (0x18 + x->reg_off_dma)
#define MIO_EMM_DMA_CFG(x) (0x20 + x->reg_off_dma)
#define MIO_EMM_DMA_ADR(x) (0x28 + x->reg_off_dma)
#define MIO_EMM_DMA_INT(x) (0x30 + x->reg_off_dma)
@@ -63,6 +66,7 @@ struct cvm_mmc_host {
struct mmc_request *current_req;
struct sg_mapping_iter smi;
bool dma_active;
+ bool use_sg;
bool has_ciu3;
bool big_dma_addr;
@@ -110,6 +114,18 @@ struct cvm_mmc_cr_mods {
};
/* Bitfield definitions */
+#define MIO_EMM_DMA_FIFO_CFG_CLR BIT_ULL(16)
+#define MIO_EMM_DMA_FIFO_CFG_INT_LVL GENMASK_ULL(12, 8)
+#define MIO_EMM_DMA_FIFO_CFG_COUNT GENMASK_ULL(4, 0)
+
+#define MIO_EMM_DMA_FIFO_CMD_RW BIT_ULL(62)
+#define MIO_EMM_DMA_FIFO_CMD_INTDIS BIT_ULL(60)
+#define MIO_EMM_DMA_FIFO_CMD_SWAP32 BIT_ULL(59)
+#define MIO_EMM_DMA_FIFO_CMD_SWAP16 BIT_ULL(58)
+#define MIO_EMM_DMA_FIFO_CMD_SWAP8 BIT_ULL(57)
+#define MIO_EMM_DMA_FIFO_CMD_ENDIAN BIT_ULL(56)
+#define MIO_EMM_DMA_FIFO_CMD_SIZE GENMASK_ULL(55, 36)
+
#define MIO_EMM_CMD_SKIP_BUSY BIT_ULL(62)
#define MIO_EMM_CMD_BUS_ID GENMASK_ULL(61, 60)
#define MIO_EMM_CMD_VAL BIT_ULL(59)
diff --git a/drivers/mmc/host/cavium-pci-thunderx.c b/drivers/mmc/host/cavium-pci-thunderx.c
index 6ad36b4..5b93a7b 100644
--- a/drivers/mmc/host/cavium-pci-thunderx.c
+++ b/drivers/mmc/host/cavium-pci-thunderx.c
@@ -104,6 +104,7 @@ static int thunder_mmc_probe(struct pci_dev *pdev,
host->release_bus = thunder_mmc_release_bus;
host->int_enable = thunder_mmc_int_enable;
+ host->use_sg = true;
host->big_dma_addr = true;
host->need_irq_handler_lock = true;
host->last_slot = -1;
@@ -118,6 +119,8 @@ static int thunder_mmc_probe(struct pci_dev *pdev,
*/
writeq(127, host->base + MIO_EMM_INT_EN(host));
writeq(3, host->base + MIO_EMM_DMA_INT_ENA_W1C(host));
+ /* Clear DMA FIFO */
+ writeq(BIT_ULL(16), host->base + MIO_EMM_DMA_FIFO_CFG(host));
ret = thunder_mmc_register_interrupts(host, pdev);
if (ret)
--
2.9.0.rc0.21.g7777322
Signed-off-by: Jan Glauber <[email protected]>
Signed-off-by: David Daney <[email protected]>
Signed-off-by: Steven J. Hill <[email protected]>
---
MAINTAINERS | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index c265a5f..ead1e89 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3041,6 +3041,14 @@ S: Supported
F: drivers/i2c/busses/i2c-octeon*
F: drivers/i2c/busses/i2c-thunderx*
+CAVIUM MMC DRIVER
+M: Jan Glauber <[email protected]>
+M: David Daney <[email protected]>
+M: Steven J. Hill <[email protected]>
+W: http://www.cavium.com
+S: Supported
+F: drivers/mmc/host/cavium-*
+
CAVIUM LIQUIDIO NETWORK DRIVER
M: Derek Chickles <[email protected]>
M: Satanand Burla <[email protected]>
--
2.9.0.rc0.21.g7777322
This core driver will be used by a MIPS platform driver
or by an ARM64 PCI driver. The core driver implements the
mmc_host_ops and slot probe & remove functions.
Callbacks are provided to allow platform specific interrupt
enable and bus locking.
The host controller supports:
- up to 4 slots that can contain sd-cards or eMMC chips
- 1, 4 and 8 bit bus width
- SDR and DDR
- transfers up to 52 Mhz (might be less when multiple slots are used)
- DMA read/write
- multi-block read/write (but not stream mode)
Voltage is limited to 3.3v and shared for all slots (vmmc and vmmcq).
A global lock for all MMC devices is required because the host
controller is shared.
Signed-off-by: Jan Glauber <[email protected]>
Signed-off-by: David Daney <[email protected]>
Signed-off-by: Steven J. Hill <[email protected]>
---
drivers/mmc/host/cavium-mmc.c | 988 ++++++++++++++++++++++++++++++++++++++++++
drivers/mmc/host/cavium-mmc.h | 178 ++++++++
2 files changed, 1166 insertions(+)
create mode 100644 drivers/mmc/host/cavium-mmc.c
create mode 100644 drivers/mmc/host/cavium-mmc.h
diff --git a/drivers/mmc/host/cavium-mmc.c b/drivers/mmc/host/cavium-mmc.c
new file mode 100644
index 0000000..11fdcfb
--- /dev/null
+++ b/drivers/mmc/host/cavium-mmc.c
@@ -0,0 +1,988 @@
+/*
+ * Shared part of driver for MMC/SDHC controller on Cavium OCTEON and
+ * 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) 2012-2017 Cavium Inc.
+ * Authors:
+ * David Daney <[email protected]>
+ * Peter Swain <[email protected]>
+ * Steven J. Hill <[email protected]>
+ * Jan Glauber <[email protected]>
+ */
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/dma-direction.h>
+#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/regulator/consumer.h>
+#include <linux/scatterlist.h>
+#include <linux/time.h>
+
+#include "cavium-mmc.h"
+
+const char *cvm_mmc_irq_names[] = {
+ "MMC Buffer",
+ "MMC Command",
+ "MMC DMA",
+ "MMC Command Error",
+ "MMC DMA Error",
+ "MMC Switch",
+ "MMC Switch Error",
+ "MMC DMA int Fifo",
+ "MMC DMA int",
+};
+
+/*
+ * The Cavium MMC host hardware assumes that all commands have fixed
+ * command and response types. These are correct if MMC devices are
+ * being used. However, non-MMC devices like SD use command and
+ * response types that are unexpected by the host hardware.
+ *
+ * The command and response types can be overridden by supplying an
+ * XOR value that is applied to the type. We calculate the XOR value
+ * from the values in this table and the flags passed from the MMC
+ * core.
+ */
+static struct cvm_mmc_cr_type cvm_mmc_cr_types[] = {
+ {0, 0}, /* CMD0 */
+ {0, 3}, /* CMD1 */
+ {0, 2}, /* CMD2 */
+ {0, 1}, /* CMD3 */
+ {0, 0}, /* CMD4 */
+ {0, 1}, /* CMD5 */
+ {0, 1}, /* CMD6 */
+ {0, 1}, /* CMD7 */
+ {1, 1}, /* CMD8 */
+ {0, 2}, /* CMD9 */
+ {0, 2}, /* CMD10 */
+ {1, 1}, /* CMD11 */
+ {0, 1}, /* CMD12 */
+ {0, 1}, /* CMD13 */
+ {1, 1}, /* CMD14 */
+ {0, 0}, /* CMD15 */
+ {0, 1}, /* CMD16 */
+ {1, 1}, /* CMD17 */
+ {1, 1}, /* CMD18 */
+ {3, 1}, /* CMD19 */
+ {2, 1}, /* CMD20 */
+ {0, 0}, /* CMD21 */
+ {0, 0}, /* CMD22 */
+ {0, 1}, /* CMD23 */
+ {2, 1}, /* CMD24 */
+ {2, 1}, /* CMD25 */
+ {2, 1}, /* CMD26 */
+ {2, 1}, /* CMD27 */
+ {0, 1}, /* CMD28 */
+ {0, 1}, /* CMD29 */
+ {1, 1}, /* CMD30 */
+ {1, 1}, /* CMD31 */
+ {0, 0}, /* CMD32 */
+ {0, 0}, /* CMD33 */
+ {0, 0}, /* CMD34 */
+ {0, 1}, /* CMD35 */
+ {0, 1}, /* CMD36 */
+ {0, 0}, /* CMD37 */
+ {0, 1}, /* CMD38 */
+ {0, 4}, /* CMD39 */
+ {0, 5}, /* CMD40 */
+ {0, 0}, /* CMD41 */
+ {2, 1}, /* CMD42 */
+ {0, 0}, /* CMD43 */
+ {0, 0}, /* CMD44 */
+ {0, 0}, /* CMD45 */
+ {0, 0}, /* CMD46 */
+ {0, 0}, /* CMD47 */
+ {0, 0}, /* CMD48 */
+ {0, 0}, /* CMD49 */
+ {0, 0}, /* CMD50 */
+ {0, 0}, /* CMD51 */
+ {0, 0}, /* CMD52 */
+ {0, 0}, /* CMD53 */
+ {0, 0}, /* CMD54 */
+ {0, 1}, /* CMD55 */
+ {0xff, 0xff}, /* CMD56 */
+ {0, 0}, /* CMD57 */
+ {0, 0}, /* CMD58 */
+ {0, 0}, /* CMD59 */
+ {0, 0}, /* CMD60 */
+ {0, 0}, /* CMD61 */
+ {0, 0}, /* CMD62 */
+ {0, 0} /* CMD63 */
+};
+
+static struct cvm_mmc_cr_mods cvm_mmc_get_cr_mods(struct mmc_command *cmd)
+{
+ struct cvm_mmc_cr_type *cr;
+ u8 hardware_ctype, hardware_rtype;
+ u8 desired_ctype = 0, desired_rtype = 0;
+ struct cvm_mmc_cr_mods r;
+
+ cr = cvm_mmc_cr_types + (cmd->opcode & 0x3f);
+ hardware_ctype = cr->ctype;
+ hardware_rtype = cr->rtype;
+ if (cmd->opcode == MMC_GEN_CMD)
+ hardware_ctype = (cmd->arg & 1) ? 1 : 2;
+
+ switch (mmc_cmd_type(cmd)) {
+ case MMC_CMD_ADTC:
+ desired_ctype = (cmd->data->flags & MMC_DATA_WRITE) ? 2 : 1;
+ break;
+ case MMC_CMD_AC:
+ case MMC_CMD_BC:
+ case MMC_CMD_BCR:
+ desired_ctype = 0;
+ break;
+ }
+
+ switch (mmc_resp_type(cmd)) {
+ case MMC_RSP_NONE:
+ desired_rtype = 0;
+ break;
+ case MMC_RSP_R1:/* MMC_RSP_R5, MMC_RSP_R6, MMC_RSP_R7 */
+ case MMC_RSP_R1B:
+ desired_rtype = 1;
+ break;
+ case MMC_RSP_R2:
+ desired_rtype = 2;
+ break;
+ case MMC_RSP_R3: /* MMC_RSP_R4 */
+ desired_rtype = 3;
+ break;
+ }
+ r.ctype_xor = desired_ctype ^ hardware_ctype;
+ r.rtype_xor = desired_rtype ^ hardware_rtype;
+ return r;
+}
+
+static void check_switch_errors(struct cvm_mmc_host *host)
+{
+ u64 emm_switch;
+
+ emm_switch = readq(host->base + MIO_EMM_SWITCH(host));
+ if (emm_switch & MIO_EMM_SWITCH_ERR0)
+ dev_err(host->dev, "Switch power class error\n");
+ if (emm_switch & MIO_EMM_SWITCH_ERR1)
+ dev_err(host->dev, "Switch hs timing error\n");
+ if (emm_switch & MIO_EMM_SWITCH_ERR2)
+ dev_err(host->dev, "Switch bus width error\n");
+}
+
+static void clear_bus_id(u64 *reg)
+{
+ u64 bus_id_mask = GENMASK_ULL(61, 60);
+
+ *reg &= ~bus_id_mask;
+}
+
+static void set_bus_id(u64 *reg, int bus_id)
+{
+ clear_bus_id(reg);
+ *reg |= FIELD_PREP(GENMASK(61, 60), bus_id);
+}
+
+static int get_bus_id(u64 reg)
+{
+ return FIELD_GET(GENMASK_ULL(61, 60), reg);
+}
+
+/*
+ * We never set the switch_exe bit since that would interfere
+ * with the commands send by the MMC core.
+ */
+static void do_switch(struct cvm_mmc_host *host, u64 emm_switch)
+{
+ int retries = 100;
+ u64 rsp_sts;
+ int bus_id;
+
+ /*
+ * Modes setting only taken from slot 0. Work around that hardware
+ * issue by first switching to slot 0.
+ */
+ bus_id = get_bus_id(emm_switch);
+ clear_bus_id(&emm_switch);
+ writeq(emm_switch, host->base + MIO_EMM_SWITCH(host));
+
+ set_bus_id(&emm_switch, bus_id);
+ writeq(emm_switch, host->base + MIO_EMM_SWITCH(host));
+
+ /* wait for the switch to finish */
+ do {
+ rsp_sts = readq(host->base + MIO_EMM_RSP_STS(host));
+ if (!(rsp_sts & MIO_EMM_RSP_STS_SWITCH_VAL))
+ break;
+ udelay(10);
+ } while (--retries);
+
+ check_switch_errors(host);
+}
+
+static bool switch_val_changed(struct cvm_mmc_slot *slot, u64 new_val)
+{
+ /* Match BUS_ID, HS_TIMING, BUS_WIDTH, POWER_CLASS, CLK_HI, CLK_LO */
+ u64 match = 0x3001070fffffffffull;
+
+ return (slot->cached_switch & match) != (new_val & match);
+}
+
+static void set_wdog(struct cvm_mmc_slot *slot, unsigned int ns)
+{
+ u64 timeout;
+
+ if (!slot->clock)
+ return;
+
+ if (ns)
+ timeout = (slot->clock * ns) / NSEC_PER_SEC;
+ else
+ timeout = (slot->clock * 850ull) / 1000ull;
+ writeq(timeout, slot->host->base + MIO_EMM_WDOG(slot->host));
+}
+
+static void cvm_mmc_reset_bus(struct cvm_mmc_slot *slot)
+{
+ struct cvm_mmc_host *host = slot->host;
+ u64 emm_switch, wdog;
+
+ emm_switch = readq(slot->host->base + MIO_EMM_SWITCH(host));
+ emm_switch &= ~(MIO_EMM_SWITCH_EXE | MIO_EMM_SWITCH_ERR0 |
+ MIO_EMM_SWITCH_ERR1 | MIO_EMM_SWITCH_ERR2);
+ set_bus_id(&emm_switch, slot->bus_id);
+
+ wdog = readq(slot->host->base + MIO_EMM_WDOG(host));
+ do_switch(slot->host, emm_switch);
+
+ slot->cached_switch = emm_switch;
+
+ msleep(20);
+
+ writeq(wdog, slot->host->base + MIO_EMM_WDOG(host));
+}
+
+/* Switch to another slot if needed */
+static void cvm_mmc_switch_to(struct cvm_mmc_slot *slot)
+{
+ struct cvm_mmc_host *host = slot->host;
+ struct cvm_mmc_slot *old_slot;
+ u64 emm_sample, emm_switch;
+
+ if (slot->bus_id == host->last_slot)
+ return;
+
+ if (host->last_slot >= 0 && host->slot[host->last_slot]) {
+ old_slot = host->slot[host->last_slot];
+ old_slot->cached_switch = readq(host->base + MIO_EMM_SWITCH(host));
+ old_slot->cached_rca = readq(host->base + MIO_EMM_RCA(host));
+ }
+
+ writeq(slot->cached_rca, host->base + MIO_EMM_RCA(host));
+ emm_switch = slot->cached_switch;
+ set_bus_id(&emm_switch, slot->bus_id);
+ do_switch(host, emm_switch);
+
+ emm_sample = FIELD_PREP(MIO_EMM_SAMPLE_CMD_CNT, slot->cmd_cnt) |
+ FIELD_PREP(MIO_EMM_SAMPLE_DAT_CNT, slot->dat_cnt);
+ writeq(emm_sample, host->base + MIO_EMM_SAMPLE(host));
+
+ host->last_slot = slot->bus_id;
+}
+
+static void do_read(struct cvm_mmc_host *host, struct mmc_request *req,
+ u64 dbuf)
+{
+ struct sg_mapping_iter *smi = &host->smi;
+ int data_len = req->data->blocks * req->data->blksz;
+ int bytes_xfered, shift = -1;
+ u64 dat = 0;
+
+ /* Auto inc from offset zero */
+ writeq((0x10000 | (dbuf << 6)), host->base + MIO_EMM_BUF_IDX(host));
+
+ for (bytes_xfered = 0; bytes_xfered < data_len;) {
+ if (smi->consumed >= smi->length) {
+ if (!sg_miter_next(smi))
+ break;
+ smi->consumed = 0;
+ }
+
+ if (shift < 0) {
+ dat = readq(host->base + MIO_EMM_BUF_DAT(host));
+ shift = 56;
+ }
+
+ while (smi->consumed < smi->length && shift >= 0) {
+ ((u8 *)smi->addr)[smi->consumed] = (dat >> shift) & 0xff;
+ bytes_xfered++;
+ smi->consumed++;
+ shift -= 8;
+ }
+ }
+
+ sg_miter_stop(smi);
+ req->data->bytes_xfered = bytes_xfered;
+ req->data->error = 0;
+}
+
+static void do_write(struct mmc_request *req)
+{
+ req->data->bytes_xfered = req->data->blocks * req->data->blksz;
+ req->data->error = 0;
+}
+
+static void set_cmd_response(struct cvm_mmc_host *host, struct mmc_request *req,
+ u64 rsp_sts)
+{
+ u64 rsp_hi, rsp_lo;
+
+ if (!(rsp_sts & MIO_EMM_RSP_STS_RSP_VAL))
+ return;
+
+ rsp_lo = readq(host->base + MIO_EMM_RSP_LO(host));
+
+ switch (FIELD_GET(MIO_EMM_RSP_STS_RSP_TYPE, rsp_sts)) {
+ case 1:
+ case 3:
+ req->cmd->resp[0] = (rsp_lo >> 8) & 0xffffffff;
+ req->cmd->resp[1] = 0;
+ req->cmd->resp[2] = 0;
+ req->cmd->resp[3] = 0;
+ break;
+ case 2:
+ req->cmd->resp[3] = rsp_lo & 0xffffffff;
+ req->cmd->resp[2] = (rsp_lo >> 32) & 0xffffffff;
+ rsp_hi = readq(host->base + MIO_EMM_RSP_HI(host));
+ req->cmd->resp[1] = rsp_hi & 0xffffffff;
+ req->cmd->resp[0] = (rsp_hi >> 32) & 0xffffffff;
+ break;
+ }
+}
+
+static int get_dma_dir(struct mmc_data *data)
+{
+ return (data->flags & MMC_DATA_WRITE) ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
+}
+
+static int finish_dma_single(struct cvm_mmc_host *host, struct mmc_data *data)
+{
+ data->bytes_xfered = data->blocks * data->blksz;
+ data->error = 0;
+ return 1;
+}
+
+static int finish_dma(struct cvm_mmc_host *host, struct mmc_data *data)
+{
+ return finish_dma_single(host, data);
+}
+
+static int check_status(u64 rsp_sts)
+{
+ if (rsp_sts & MIO_EMM_RSP_STS_RSP_BAD_STS ||
+ rsp_sts & MIO_EMM_RSP_STS_RSP_CRC_ERR ||
+ rsp_sts & MIO_EMM_RSP_STS_BLK_CRC_ERR)
+ return -EILSEQ;
+ if (rsp_sts & MIO_EMM_RSP_STS_RSP_TIMEOUT ||
+ rsp_sts & MIO_EMM_RSP_STS_BLK_TIMEOUT)
+ return -ETIMEDOUT;
+ if (rsp_sts & MIO_EMM_RSP_STS_DBUF_ERR)
+ return -EIO;
+ return 0;
+}
+
+/* Try to clean up failed DMA. */
+static void cleanup_dma(struct cvm_mmc_host *host, u64 rsp_sts)
+{
+ u64 emm_dma;
+
+ emm_dma = readq(host->base + MIO_EMM_DMA(host));
+ emm_dma |= FIELD_PREP(MIO_EMM_DMA_VAL, 1) |
+ FIELD_PREP(MIO_EMM_DMA_DAT_NULL, 1);
+ set_bus_id(&emm_dma, get_bus_id(rsp_sts));
+ writeq(emm_dma, host->base + MIO_EMM_DMA(host));
+}
+
+irqreturn_t cvm_mmc_interrupt(int irq, void *dev_id)
+{
+ struct cvm_mmc_host *host = dev_id;
+ struct mmc_request *req;
+ u64 emm_int, rsp_sts;
+ bool host_done;
+
+ /* Clear interrupt bits (write 1 clears ). */
+ emm_int = readq(host->base + MIO_EMM_INT(host));
+ writeq(emm_int, host->base + MIO_EMM_INT(host));
+
+ if (emm_int & MIO_EMM_INT_SWITCH_ERR)
+ check_switch_errors(host);
+
+ req = host->current_req;
+ if (!req)
+ goto out;
+
+ rsp_sts = readq(host->base + MIO_EMM_RSP_STS(host));
+ /*
+ * dma_val set means DMA is still in progress. Don't touch
+ * the request and wait for the interrupt indicating that
+ * the DMA is finished.
+ */
+ if ((rsp_sts & MIO_EMM_RSP_STS_DMA_VAL) && host->dma_active)
+ goto out;
+
+ if (!host->dma_active && req->data &&
+ (emm_int & MIO_EMM_INT_BUF_DONE)) {
+ unsigned int type = (rsp_sts >> 7) & 3;
+
+ if (type == 1)
+ do_read(host, req, rsp_sts & MIO_EMM_RSP_STS_DBUF);
+ else if (type == 2)
+ do_write(req);
+ }
+
+ host_done = emm_int & MIO_EMM_INT_CMD_DONE ||
+ emm_int & MIO_EMM_INT_DMA_DONE ||
+ emm_int & MIO_EMM_INT_CMD_ERR ||
+ emm_int & MIO_EMM_INT_DMA_ERR;
+
+ if (!(host_done && req->done))
+ goto no_req_done;
+
+ req->cmd->error = check_status(rsp_sts);
+
+ if (host->dma_active && req->data)
+ if (!finish_dma(host, req->data))
+ goto no_req_done;
+
+ set_cmd_response(host, req, rsp_sts);
+ if ((emm_int & MIO_EMM_INT_DMA_ERR) &&
+ (rsp_sts & MIO_EMM_RSP_STS_DMA_PEND))
+ cleanup_dma(host, rsp_sts);
+
+ host->current_req = NULL;
+ req->done(req);
+
+no_req_done:
+ if (host_done)
+ host->release_bus(host);
+out:
+ return IRQ_RETVAL(emm_int != 0);
+}
+
+/*
+ * Program DMA_CFG and if needed DMA_ADR.
+ * Returns 0 on error, DMA address otherwise.
+ */
+static u64 prepare_dma_single(struct cvm_mmc_host *host, struct mmc_data *data)
+{
+ u64 dma_cfg, addr;
+ int count, rw;
+
+ count = dma_map_sg(host->dev, data->sg, data->sg_len,
+ get_dma_dir(data));
+ if (!count)
+ return 0;
+
+ rw = (data->flags & MMC_DATA_WRITE) ? 1 : 0;
+ dma_cfg = FIELD_PREP(MIO_EMM_DMA_CFG_EN, 1) |
+ FIELD_PREP(MIO_EMM_DMA_CFG_RW, rw);
+#ifdef __LITTLE_ENDIAN
+ dma_cfg |= FIELD_PREP(MIO_EMM_DMA_CFG_ENDIAN, 1);
+#endif
+ dma_cfg |= FIELD_PREP(MIO_EMM_DMA_CFG_SIZE,
+ (sg_dma_len(&data->sg[0]) / 8) - 1);
+
+ addr = sg_dma_address(&data->sg[0]);
+ dma_cfg |= FIELD_PREP(MIO_EMM_DMA_CFG_ADR, addr);
+ writeq(dma_cfg, host->dma_base + MIO_EMM_DMA_CFG(host));
+
+ pr_debug("[%s] sg_dma_len: %u total sg_elem: %d\n",
+ (rw) ? "W" : "R", sg_dma_len(&data->sg[0]), count);
+ return addr;
+}
+
+static u64 prepare_dma(struct cvm_mmc_host *host, struct mmc_data *data)
+{
+ return prepare_dma_single(host, data);
+}
+
+static u64 prepare_ext_dma(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct cvm_mmc_slot *slot = mmc_priv(mmc);
+ u64 emm_dma;
+
+ emm_dma = FIELD_PREP(MIO_EMM_DMA_VAL, 1) |
+ FIELD_PREP(MIO_EMM_DMA_SECTOR,
+ (mrq->data->blksz == 512) ? 1 : 0) |
+ FIELD_PREP(MIO_EMM_DMA_RW,
+ (mrq->data->flags & MMC_DATA_WRITE) ? 1 : 0) |
+ FIELD_PREP(MIO_EMM_DMA_BLOCK_CNT, mrq->data->blocks) |
+ FIELD_PREP(MIO_EMM_DMA_CARD_ADDR, mrq->cmd->arg);
+ set_bus_id(&emm_dma, slot->bus_id);
+
+ if (mmc_card_mmc(mmc->card) || (mmc_card_sd(mmc->card) &&
+ (mmc->card->scr.cmds & SD_SCR_CMD23_SUPPORT)))
+ emm_dma |= FIELD_PREP(MIO_EMM_DMA_MULTI, 1);
+
+ pr_debug("[%s] blocks: %u multi: %d\n",
+ (emm_dma & MIO_EMM_DMA_RW) ? "W" : "R",
+ mrq->data->blocks, (emm_dma & MIO_EMM_DMA_MULTI) ? 1 : 0);
+ return emm_dma;
+}
+
+static void cvm_mmc_dma_request(struct mmc_host *mmc,
+ struct mmc_request *mrq)
+{
+ struct cvm_mmc_slot *slot = mmc_priv(mmc);
+ struct cvm_mmc_host *host = slot->host;
+ struct mmc_data *data;
+ u64 emm_dma, addr;
+
+ if (!mrq->data || !mrq->data->sg || !mrq->data->sg_len ||
+ !mrq->stop || mrq->stop->opcode != MMC_STOP_TRANSMISSION) {
+ dev_err(&mmc->card->dev,
+ "Error: cmv_mmc_dma_request no data\n");
+ goto error;
+ }
+
+ cvm_mmc_switch_to(slot);
+
+ data = mrq->data;
+ pr_debug("DMA request blocks: %d block_size: %d total_size: %d\n",
+ data->blocks, data->blksz, data->blocks * data->blksz);
+ if (data->timeout_ns)
+ set_wdog(slot, data->timeout_ns);
+
+ WARN_ON(host->current_req);
+ host->current_req = mrq;
+
+ emm_dma = prepare_ext_dma(mmc, mrq);
+ addr = prepare_dma(host, data);
+ if (!addr) {
+ dev_err(host->dev, "prepare_dma failed\n");
+ goto error;
+ }
+
+ host->dma_active = true;
+ host->int_enable(host, MIO_EMM_INT_CMD_ERR | MIO_EMM_INT_DMA_DONE |
+ MIO_EMM_INT_DMA_ERR);
+
+ /*
+ * If we have a valid SD card in the slot, we set the response
+ * bit mask to check for CRC errors and timeouts only.
+ * Otherwise, use the default power reset value.
+ */
+ if (mmc->card && mmc_card_sd(mmc->card))
+ writeq(0x00b00000ull, host->base + MIO_EMM_STS_MASK(host));
+ else
+ writeq(0xe4390080ull, host->base + MIO_EMM_STS_MASK(host));
+ writeq(emm_dma, host->base + MIO_EMM_DMA(host));
+ return;
+
+error:
+ mrq->cmd->error = -EINVAL;
+ if (mrq->done)
+ mrq->done(mrq);
+ host->release_bus(host);
+}
+
+static void do_read_request(struct cvm_mmc_host *host, struct mmc_request *mrq)
+{
+ sg_miter_start(&host->smi, mrq->data->sg, mrq->data->sg_len,
+ SG_MITER_ATOMIC | SG_MITER_TO_SG);
+}
+
+static void do_write_request(struct cvm_mmc_host *host, struct mmc_request *mrq)
+{
+ unsigned int data_len = mrq->data->blocks * mrq->data->blksz;
+ struct sg_mapping_iter *smi = &host->smi;
+ unsigned int bytes_xfered;
+ int shift = 56;
+ u64 dat = 0;
+
+ /* Copy data to the xmit buffer before issuing the command. */
+ sg_miter_start(smi, mrq->data->sg, mrq->data->sg_len, SG_MITER_FROM_SG);
+
+ /* Auto inc from offset zero, dbuf zero */
+ writeq(0x10000ull, host->base + MIO_EMM_BUF_IDX(host));
+
+ for (bytes_xfered = 0; bytes_xfered < data_len;) {
+ if (smi->consumed >= smi->length) {
+ if (!sg_miter_next(smi))
+ break;
+ smi->consumed = 0;
+ }
+
+ while (smi->consumed < smi->length && shift >= 0) {
+ dat |= ((u8 *)smi->addr)[smi->consumed] << shift;
+ bytes_xfered++;
+ smi->consumed++;
+ shift -= 8;
+ }
+
+ if (shift < 0) {
+ writeq(dat, host->base + MIO_EMM_BUF_DAT(host));
+ shift = 56;
+ dat = 0;
+ }
+ }
+ sg_miter_stop(smi);
+}
+
+static void cvm_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct cvm_mmc_slot *slot = mmc_priv(mmc);
+ struct cvm_mmc_host *host = slot->host;
+ struct mmc_command *cmd = mrq->cmd;
+ struct cvm_mmc_cr_mods mods;
+ u64 emm_cmd, rsp_sts;
+ int retries = 100;
+
+ /*
+ * Note about locking:
+ * All MMC devices share the same bus and controller. Allow only a
+ * single user of the bootbus/MMC bus at a time. The lock is acquired
+ * on all entry points from the MMC layer.
+ *
+ * For requests the lock is only released after the completion
+ * interrupt!
+ */
+ host->acquire_bus(host);
+
+ if (cmd->opcode == MMC_READ_MULTIPLE_BLOCK ||
+ cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK)
+ return cvm_mmc_dma_request(mmc, mrq);
+
+ cvm_mmc_switch_to(slot);
+
+ mods = cvm_mmc_get_cr_mods(cmd);
+
+ WARN_ON(host->current_req);
+ host->current_req = mrq;
+
+ if (cmd->data) {
+ if (cmd->data->flags & MMC_DATA_READ)
+ do_read_request(host, mrq);
+ else
+ do_write_request(host, mrq);
+
+ if (cmd->data->timeout_ns)
+ set_wdog(slot, cmd->data->timeout_ns);
+ } else
+ set_wdog(slot, 0);
+
+ host->dma_active = false;
+ host->int_enable(host, MIO_EMM_INT_CMD_DONE | MIO_EMM_INT_CMD_ERR);
+
+ emm_cmd = FIELD_PREP(MIO_EMM_CMD_VAL, 1) |
+ FIELD_PREP(MIO_EMM_CMD_CTYPE_XOR, mods.ctype_xor) |
+ FIELD_PREP(MIO_EMM_CMD_RTYPE_XOR, mods.rtype_xor) |
+ FIELD_PREP(MIO_EMM_CMD_IDX, cmd->opcode) |
+ FIELD_PREP(MIO_EMM_CMD_ARG, cmd->arg);
+ set_bus_id(&emm_cmd, slot->bus_id);
+ if (mmc_cmd_type(cmd) == MMC_CMD_ADTC)
+ emm_cmd |= FIELD_PREP(MIO_EMM_CMD_OFFSET,
+ 64 - ((cmd->data->blocks * cmd->data->blksz) / 8));
+
+ writeq(0, host->base + MIO_EMM_STS_MASK(host));
+
+retry:
+ rsp_sts = readq(host->base + MIO_EMM_RSP_STS(host));
+ if (rsp_sts & MIO_EMM_RSP_STS_DMA_VAL ||
+ rsp_sts & MIO_EMM_RSP_STS_CMD_VAL ||
+ rsp_sts & MIO_EMM_RSP_STS_SWITCH_VAL ||
+ rsp_sts & MIO_EMM_RSP_STS_DMA_PEND) {
+ udelay(10);
+ if (--retries)
+ goto retry;
+ }
+ if (!retries)
+ dev_err(host->dev, "Bad status: %Lx before command write\n", rsp_sts);
+ writeq(emm_cmd, host->base + MIO_EMM_CMD(host));
+}
+
+static void cvm_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct cvm_mmc_slot *slot = mmc_priv(mmc);
+ struct cvm_mmc_host *host = slot->host;
+ int clk_period = 0, power_class = 10, bus_width = 0;
+ u64 clock, emm_switch;
+
+ host->acquire_bus(host);
+ cvm_mmc_switch_to(slot);
+
+ /* Set the power state */
+ switch (ios->power_mode) {
+ case MMC_POWER_ON:
+ break;
+
+ case MMC_POWER_OFF:
+ cvm_mmc_reset_bus(slot);
+
+ if (host->global_pwr_gpiod)
+ gpiod_set_value_cansleep(host->global_pwr_gpiod, 0);
+ else
+ mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
+ break;
+
+ case MMC_POWER_UP:
+ if (host->global_pwr_gpiod)
+ gpiod_set_value_cansleep(host->global_pwr_gpiod, 1);
+ else
+ mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, ios->vdd);
+ break;
+ }
+
+ /* Set bus width */
+ switch (ios->bus_width) {
+ case MMC_BUS_WIDTH_8:
+ bus_width = 2;
+ break;
+ case MMC_BUS_WIDTH_4:
+ bus_width = 1;
+ break;
+ case MMC_BUS_WIDTH_1:
+ bus_width = 0;
+ break;
+ }
+
+ slot->bus_width = bus_width;
+
+ /* Change the clock frequency. */
+ clock = ios->clock;
+ if (clock > 52000000)
+ clock = 52000000;
+ slot->clock = clock;
+
+ if (clock)
+ clk_period = (host->sys_freq + clock - 1) / (2 * clock);
+
+ emm_switch = FIELD_PREP(MIO_EMM_SWITCH_HS_TIMING,
+ (ios->timing == MMC_TIMING_MMC_HS)) |
+ FIELD_PREP(MIO_EMM_SWITCH_BUS_WIDTH, bus_width) |
+ FIELD_PREP(MIO_EMM_SWITCH_POWER_CLASS, power_class) |
+ FIELD_PREP(MIO_EMM_SWITCH_CLK_HI, clk_period) |
+ FIELD_PREP(MIO_EMM_SWITCH_CLK_LO, clk_period);
+ set_bus_id(&emm_switch, slot->bus_id);
+
+ if (!switch_val_changed(slot, emm_switch))
+ goto out;
+
+ set_wdog(slot, 0);
+ do_switch(host, emm_switch);
+ slot->cached_switch = emm_switch;
+out:
+ host->release_bus(host);
+}
+
+static const struct mmc_host_ops cvm_mmc_ops = {
+ .request = cvm_mmc_request,
+ .set_ios = cvm_mmc_set_ios,
+ .get_ro = mmc_gpio_get_ro,
+ .get_cd = mmc_gpio_get_cd,
+};
+
+static void cvm_mmc_set_clock(struct cvm_mmc_slot *slot, unsigned int clock)
+{
+ struct mmc_host *mmc = slot->mmc;
+
+ clock = min(clock, mmc->f_max);
+ clock = max(clock, mmc->f_min);
+ slot->clock = clock;
+}
+
+static int cvm_mmc_init_lowlevel(struct cvm_mmc_slot *slot)
+{
+ struct cvm_mmc_host *host = slot->host;
+ u64 emm_switch;
+
+ /* Enable this bus slot. */
+ host->emm_cfg |= (1ull << slot->bus_id);
+ writeq(host->emm_cfg, slot->host->base + MIO_EMM_CFG(host));
+ udelay(10);
+
+ /* Program initial clock speed and power. */
+ cvm_mmc_set_clock(slot, slot->mmc->f_min);
+ emm_switch = FIELD_PREP(MIO_EMM_SWITCH_POWER_CLASS, 10);
+ emm_switch |= FIELD_PREP(MIO_EMM_SWITCH_CLK_HI,
+ (host->sys_freq / slot->clock) / 2);
+ emm_switch |= FIELD_PREP(MIO_EMM_SWITCH_CLK_LO,
+ (host->sys_freq / slot->clock) / 2);
+
+ /* Make the changes take effect on this bus slot. */
+ set_bus_id(&emm_switch, slot->bus_id);
+ do_switch(host, emm_switch);
+
+ slot->cached_switch = emm_switch;
+
+ /*
+ * Set watchdog timeout value and default reset value
+ * for the mask register. Finally, set the CARD_RCA
+ * bit so that we can get the card address relative
+ * to the CMD register for CMD7 transactions.
+ */
+ set_wdog(slot, 0);
+ writeq(0xe4390080ull, host->base + MIO_EMM_STS_MASK(host));
+ writeq(1, host->base + MIO_EMM_RCA(host));
+ return 0;
+}
+
+static int cvm_mmc_of_parse(struct device *dev, struct cvm_mmc_slot *slot)
+{
+ u32 id, cmd_skew = 0, dat_skew = 0, bus_width = 0, f_max = 0;
+ struct device_node *node = dev->of_node;
+ struct mmc_host *mmc = slot->mmc;
+ u64 clock_period;
+ int ret;
+
+ ret = of_property_read_u32(node, "reg", &id);
+ if (ret) {
+ dev_err(dev, "Missing or invalid reg property on %s\n",
+ of_node_full_name(node));
+ return ret;
+ }
+
+ if (id >= CAVIUM_MAX_MMC || slot->host->slot[id]) {
+ dev_err(dev, "Invalid reg property on %s\n",
+ of_node_full_name(node));
+ return -EINVAL;
+ }
+
+ /* Deprecated Cavium bindings for old firmware */
+ of_property_read_u32(node, "cavium,bus-max-width", &bus_width);
+ of_property_read_u32(node, "spi-max-frequency", &f_max);
+ if (slot->host->global_pwr_gpiod) {
+ /* Get a sane OCR mask for other parts of the MMC subsytem. */
+ ret = mmc_of_parse_voltage(node, &mmc->ocr_avail);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Cavium-specific DT properties */
+ of_property_read_u32(node, "cavium,cmd-clk-skew", &cmd_skew);
+ of_property_read_u32(node, "cavium,dat-clk-skew", &dat_skew);
+
+ if (!slot->host->global_pwr_gpiod) {
+ mmc->supply.vmmc = devm_regulator_get(dev, "vmmc");
+ if (IS_ERR(mmc->supply.vmmc))
+ return PTR_ERR(mmc->supply.vmmc);
+
+ ret = mmc_regulator_get_ocrmask(mmc->supply.vmmc);
+ if (ret > 0)
+ mmc->ocr_avail = ret;
+ }
+
+ /* Common MMC bindings */
+ ret = mmc_of_parse(mmc);
+ if (ret)
+ return ret;
+
+ /* Set bus width */
+ if (!bus_width) {
+ if (mmc->caps & MMC_CAP_8_BIT_DATA)
+ bus_width = 8;
+ else if (mmc->caps & MMC_CAP_4_BIT_DATA)
+ bus_width = 4;
+ else
+ bus_width = 1;
+ }
+
+ switch (bus_width) {
+ case 8:
+ slot->bus_width = 2;
+ slot->mmc->caps = MMC_CAP_8_BIT_DATA | MMC_CAP_4_BIT_DATA;
+ break;
+ case 4:
+ slot->bus_width = 1;
+ slot->mmc->caps = MMC_CAP_4_BIT_DATA;
+ break;
+ case 1:
+ slot->bus_width = 0;
+ break;
+ }
+
+ /* Set maximum and minimum frequency */
+ if (f_max)
+ mmc->f_max = f_max;
+ if (!mmc->f_max || mmc->f_max > 52000000)
+ mmc->f_max = 52000000;
+ mmc->f_min = 400000;
+
+ /* Sampling register settings, period in picoseconds */
+ clock_period = 1000000000000ull / slot->host->sys_freq;
+ slot->cmd_cnt = (cmd_skew + clock_period / 2) / clock_period;
+ slot->dat_cnt = (dat_skew + clock_period / 2) / clock_period;
+
+ return id;
+}
+
+int cvm_mmc_of_slot_probe(struct device *dev, struct cvm_mmc_host *host)
+{
+ struct cvm_mmc_slot *slot;
+ struct mmc_host *mmc;
+ int ret, id;
+
+ mmc = mmc_alloc_host(sizeof(struct cvm_mmc_slot), dev);
+ if (!mmc)
+ return -ENOMEM;
+
+ slot = mmc_priv(mmc);
+ slot->mmc = mmc;
+ slot->host = host;
+
+ ret = cvm_mmc_of_parse(dev, slot);
+ if (ret < 0)
+ goto error;
+ id = ret;
+
+ /* Set up host parameters */
+ mmc->ops = &cvm_mmc_ops;
+
+ mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED |
+ MMC_CAP_ERASE | MMC_CAP_CMD23 | MMC_CAP_POWER_OFF_CARD;
+
+ mmc->max_segs = 1;
+
+ /* DMA size field can address up to 8 MB */
+ mmc->max_seg_size = 8 * 1024 * 1024;
+ mmc->max_req_size = mmc->max_seg_size;
+ /* External DMA is in 512 byte blocks */
+ mmc->max_blk_size = 512;
+ /* DMA block count field is 15 bits */
+ mmc->max_blk_count = 32767;
+
+ slot->clock = mmc->f_min;
+ slot->bus_id = id;
+ slot->cached_rca = 1;
+
+ host->acquire_bus(host);
+ host->slot[id] = slot;
+ cvm_mmc_switch_to(slot);
+ cvm_mmc_init_lowlevel(slot);
+ host->release_bus(host);
+
+ ret = mmc_add_host(mmc);
+ if (ret) {
+ dev_err(dev, "mmc_add_host() returned %d\n", ret);
+ slot->host->slot[id] = NULL;
+ goto error;
+ }
+ return 0;
+
+error:
+ mmc_free_host(slot->mmc);
+ return ret;
+}
+
+int cvm_mmc_of_slot_remove(struct cvm_mmc_slot *slot)
+{
+ mmc_remove_host(slot->mmc);
+ slot->host->slot[slot->bus_id] = NULL;
+ mmc_free_host(slot->mmc);
+ return 0;
+}
diff --git a/drivers/mmc/host/cavium-mmc.h b/drivers/mmc/host/cavium-mmc.h
new file mode 100644
index 0000000..c3843448
--- /dev/null
+++ b/drivers/mmc/host/cavium-mmc.h
@@ -0,0 +1,178 @@
+/*
+ * Driver for MMC and SSD cards for Cavium OCTEON and 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) 2012-2017 Cavium Inc.
+ */
+
+#ifndef _CAVIUM_MMC_H_
+#define _CAVIUM_MMC_H_
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/mmc/host.h>
+#include <linux/of.h>
+#include <linux/scatterlist.h>
+#include <linux/semaphore.h>
+
+#define CAVIUM_MAX_MMC 4
+
+/* DMA register addresses */
+#define MIO_EMM_DMA_CFG(x) (0x00 + x->reg_off_dma)
+
+/* register addresses */
+#define MIO_EMM_CFG(x) (0x00 + x->reg_off)
+#define MIO_EMM_SWITCH(x) (0x48 + x->reg_off)
+#define MIO_EMM_DMA(x) (0x50 + x->reg_off)
+#define MIO_EMM_CMD(x) (0x58 + x->reg_off)
+#define MIO_EMM_RSP_STS(x) (0x60 + x->reg_off)
+#define MIO_EMM_RSP_LO(x) (0x68 + x->reg_off)
+#define MIO_EMM_RSP_HI(x) (0x70 + x->reg_off)
+#define MIO_EMM_INT(x) (0x78 + x->reg_off)
+#define MIO_EMM_INT_EN(x) (0x80 + x->reg_off)
+#define MIO_EMM_WDOG(x) (0x88 + x->reg_off)
+#define MIO_EMM_SAMPLE(x) (0x90 + x->reg_off)
+#define MIO_EMM_STS_MASK(x) (0x98 + x->reg_off)
+#define MIO_EMM_RCA(x) (0xa0 + x->reg_off)
+#define MIO_EMM_BUF_IDX(x) (0xe0 + x->reg_off)
+#define MIO_EMM_BUF_DAT(x) (0xe8 + x->reg_off)
+
+struct cvm_mmc_host {
+ struct device *dev;
+ void __iomem *base;
+ void __iomem *dma_base;
+ int reg_off;
+ int reg_off_dma;
+ u64 emm_cfg;
+ int last_slot;
+ struct clk *clk;
+ int sys_freq;
+
+ struct mmc_request *current_req;
+ struct sg_mapping_iter smi;
+ bool dma_active;
+
+ struct gpio_desc *global_pwr_gpiod;
+
+ struct cvm_mmc_slot *slot[CAVIUM_MAX_MMC];
+
+ void (*acquire_bus)(struct cvm_mmc_host *);
+ void (*release_bus)(struct cvm_mmc_host *);
+ void (*int_enable)(struct cvm_mmc_host *, u64);
+};
+
+struct cvm_mmc_slot {
+ struct mmc_host *mmc; /* slot-level mmc_core object */
+ struct cvm_mmc_host *host; /* common hw for all slots */
+
+ u64 clock;
+
+ u64 cached_switch;
+ u64 cached_rca;
+
+ unsigned int cmd_cnt; /* sample delay */
+ unsigned int dat_cnt; /* sample delay */
+
+ int bus_width;
+ int bus_id;
+};
+
+struct cvm_mmc_cr_type {
+ u8 ctype;
+ u8 rtype;
+};
+
+struct cvm_mmc_cr_mods {
+ u8 ctype_xor;
+ u8 rtype_xor;
+};
+
+/* Bitfield definitions */
+#define MIO_EMM_CMD_SKIP_BUSY BIT_ULL(62)
+#define MIO_EMM_CMD_BUS_ID GENMASK_ULL(61, 60)
+#define MIO_EMM_CMD_VAL BIT_ULL(59)
+#define MIO_EMM_CMD_DBUF BIT_ULL(55)
+#define MIO_EMM_CMD_OFFSET GENMASK_ULL(54, 49)
+#define MIO_EMM_CMD_CTYPE_XOR GENMASK_ULL(42, 41)
+#define MIO_EMM_CMD_RTYPE_XOR GENMASK_ULL(40, 38)
+#define MIO_EMM_CMD_IDX GENMASK_ULL(37, 32)
+#define MIO_EMM_CMD_ARG GENMASK_ULL(31, 0)
+
+#define MIO_EMM_DMA_SKIP_BUSY BIT_ULL(62)
+#define MIO_EMM_DMA_BUS_ID GENMASK_ULL(61, 60)
+#define MIO_EMM_DMA_VAL BIT_ULL(59)
+#define MIO_EMM_DMA_SECTOR BIT_ULL(58)
+#define MIO_EMM_DMA_DAT_NULL BIT_ULL(57)
+#define MIO_EMM_DMA_THRES GENMASK_ULL(56, 51)
+#define MIO_EMM_DMA_REL_WR BIT_ULL(50)
+#define MIO_EMM_DMA_RW BIT_ULL(49)
+#define MIO_EMM_DMA_MULTI BIT_ULL(48)
+#define MIO_EMM_DMA_BLOCK_CNT GENMASK_ULL(47, 32)
+#define MIO_EMM_DMA_CARD_ADDR GENMASK_ULL(31, 0)
+
+#define MIO_EMM_DMA_CFG_EN BIT_ULL(63)
+#define MIO_EMM_DMA_CFG_RW BIT_ULL(62)
+#define MIO_EMM_DMA_CFG_CLR BIT_ULL(61)
+#define MIO_EMM_DMA_CFG_SWAP32 BIT_ULL(59)
+#define MIO_EMM_DMA_CFG_SWAP16 BIT_ULL(58)
+#define MIO_EMM_DMA_CFG_SWAP8 BIT_ULL(57)
+#define MIO_EMM_DMA_CFG_ENDIAN BIT_ULL(56)
+#define MIO_EMM_DMA_CFG_SIZE GENMASK_ULL(55, 36)
+#define MIO_EMM_DMA_CFG_ADR GENMASK_ULL(35, 0)
+
+#define MIO_EMM_INT_SWITCH_ERR BIT_ULL(6)
+#define MIO_EMM_INT_SWITCH_DONE BIT_ULL(5)
+#define MIO_EMM_INT_DMA_ERR BIT_ULL(4)
+#define MIO_EMM_INT_CMD_ERR BIT_ULL(3)
+#define MIO_EMM_INT_DMA_DONE BIT_ULL(2)
+#define MIO_EMM_INT_CMD_DONE BIT_ULL(1)
+#define MIO_EMM_INT_BUF_DONE BIT_ULL(0)
+
+#define MIO_EMM_RSP_STS_BUS_ID GENMASK_ULL(61, 60)
+#define MIO_EMM_RSP_STS_CMD_VAL BIT_ULL(59)
+#define MIO_EMM_RSP_STS_SWITCH_VAL BIT_ULL(58)
+#define MIO_EMM_RSP_STS_DMA_VAL BIT_ULL(57)
+#define MIO_EMM_RSP_STS_DMA_PEND BIT_ULL(56)
+#define MIO_EMM_RSP_STS_DBUF_ERR BIT_ULL(28)
+#define MIO_EMM_RSP_STS_DBUF BIT_ULL(23)
+#define MIO_EMM_RSP_STS_BLK_TIMEOUT BIT_ULL(22)
+#define MIO_EMM_RSP_STS_BLK_CRC_ERR BIT_ULL(21)
+#define MIO_EMM_RSP_STS_RSP_BUSYBIT BIT_ULL(20)
+#define MIO_EMM_RSP_STS_STP_TIMEOUT BIT_ULL(19)
+#define MIO_EMM_RSP_STS_STP_CRC_ERR BIT_ULL(18)
+#define MIO_EMM_RSP_STS_STP_BAD_STS BIT_ULL(17)
+#define MIO_EMM_RSP_STS_STP_VAL BIT_ULL(16)
+#define MIO_EMM_RSP_STS_RSP_TIMEOUT BIT_ULL(15)
+#define MIO_EMM_RSP_STS_RSP_CRC_ERR BIT_ULL(14)
+#define MIO_EMM_RSP_STS_RSP_BAD_STS BIT_ULL(13)
+#define MIO_EMM_RSP_STS_RSP_VAL BIT_ULL(12)
+#define MIO_EMM_RSP_STS_RSP_TYPE GENMASK_ULL(11, 9)
+#define MIO_EMM_RSP_STS_CMD_TYPE GENMASK_ULL(8, 7)
+#define MIO_EMM_RSP_STS_CMD_IDX GENMASK_ULL(6, 1)
+#define MIO_EMM_RSP_STS_CMD_DONE BIT_ULL(0)
+
+#define MIO_EMM_SAMPLE_CMD_CNT GENMASK_ULL(25, 16)
+#define MIO_EMM_SAMPLE_DAT_CNT GENMASK_ULL(9, 0)
+
+#define MIO_EMM_SWITCH_BUS_ID GENMASK_ULL(61, 60)
+#define MIO_EMM_SWITCH_EXE BIT_ULL(59)
+#define MIO_EMM_SWITCH_ERR0 BIT_ULL(58)
+#define MIO_EMM_SWITCH_ERR1 BIT_ULL(57)
+#define MIO_EMM_SWITCH_ERR2 BIT_ULL(56)
+#define MIO_EMM_SWITCH_HS_TIMING BIT_ULL(48)
+#define MIO_EMM_SWITCH_BUS_WIDTH GENMASK_ULL(42, 40)
+#define MIO_EMM_SWITCH_POWER_CLASS GENMASK_ULL(35, 32)
+#define MIO_EMM_SWITCH_CLK_HI GENMASK_ULL(31, 16)
+#define MIO_EMM_SWITCH_CLK_LO GENMASK_ULL(15, 0)
+
+/* Protoypes */
+irqreturn_t cvm_mmc_interrupt(int irq, void *dev_id);
+int cvm_mmc_of_slot_probe(struct device *dev, struct cvm_mmc_host *host);
+int cvm_mmc_of_slot_remove(struct cvm_mmc_slot *slot);
+extern const char *cvm_mmc_irq_names[];
+
+#endif
--
2.9.0.rc0.21.g7777322
Add support for switching to DDR mode for eMMC devices.
Signed-off-by: Jan Glauber <[email protected]>
---
drivers/mmc/host/cavium-mmc.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/drivers/mmc/host/cavium-mmc.c b/drivers/mmc/host/cavium-mmc.c
index fb6e1c1..e340b95 100644
--- a/drivers/mmc/host/cavium-mmc.c
+++ b/drivers/mmc/host/cavium-mmc.c
@@ -865,6 +865,10 @@ static void cvm_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
break;
}
+ /* DDR is available for 4/8 bit bus width */
+ if (ios->bus_width && ios->timing == MMC_TIMING_MMC_DDR52)
+ bus_width |= 4;
+
slot->bus_width = bus_width;
/* Change the clock frequency. */
@@ -1057,8 +1061,14 @@ int cvm_mmc_of_slot_probe(struct device *dev, struct cvm_mmc_host *host)
/* Set up host parameters */
mmc->ops = &cvm_mmc_ops;
+ /*
+ * We only have a 3.3v supply, we cannot support any
+ * of the UHS modes. We do support the high speed DDR
+ * modes up to 52MHz.
+ */
mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED |
- MMC_CAP_ERASE | MMC_CAP_CMD23 | MMC_CAP_POWER_OFF_CARD;
+ MMC_CAP_ERASE | MMC_CAP_CMD23 | MMC_CAP_POWER_OFF_CARD |
+ MMC_CAP_3_3V_DDR;
if (host->use_sg)
mmc->max_segs = 16;
--
2.9.0.rc0.21.g7777322
Add description of Cavium Octeon and ThunderX SOC device tree bindings.
CC: Ulf Hansson <[email protected]>
CC: Rob Herring <[email protected]>
CC: Mark Rutland <[email protected]>
CC: [email protected]
Signed-off-by: Jan Glauber <[email protected]>
Signed-off-by: David Daney <[email protected]>
Signed-off-by: Steven J. Hill <[email protected]>
Acked-by: Rob Herring <[email protected]>
---
.../devicetree/bindings/mmc/cavium-mmc.txt | 58 ++++++++++++++++++++++
1 file changed, 58 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mmc/cavium-mmc.txt
diff --git a/Documentation/devicetree/bindings/mmc/cavium-mmc.txt b/Documentation/devicetree/bindings/mmc/cavium-mmc.txt
new file mode 100644
index 0000000..225c2be
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/cavium-mmc.txt
@@ -0,0 +1,58 @@
+* Cavium Octeon & ThunderX MMC controller
+
+The highspeed MMC host controller on Caviums SoCs provides an interface
+for MMC and SD types of memory cards.
+
+Supported maximum speeds are the ones of the eMMC standard 4.41 as well
+as the speed of SD standard 4.0. Only 3.3 Volt is supported.
+
+Required properties:
+ - compatible : should be one of:
+ cavium,octeon-6130-mmc
+ cavium,octeon-6130-mmc-slot
+ cavium,octeon-7890-mmc
+ cavium,octeon-7890-mmc-slot
+ cavium,thunder-8190-mmc
+ cavium,thunder-8190-mmc-slot
+ cavium,thunder-8390-mmc
+ cavium,thunder-8390-mmc-slot
+ - reg : mmc controller base registers
+ - clocks : phandle
+
+Optional properties:
+ - for cd, bus-width and additional generic mmc parameters
+ please refer to mmc.txt within this directory
+ - cavium,cmd-clk-skew : number of coprocessor clocks before sampling command
+ - cavium,dat-clk-skew : number of coprocessor clocks before sampling data
+
+Deprecated properties:
+- spi-max-frequency : use max-frequency instead
+- cavium,bus-max-width : use bus-width instead
+
+Examples:
+ mmc_1_4: mmc@1,4 {
+ compatible = "cavium,thunder-8390-mmc";
+ reg = <0x0c00 0 0 0 0>; /* DEVFN = 0x0c (1:4) */
+ #address-cells = <1>;
+ #size-cells = <0>;
+ clocks = <&sclk>;
+
+ mmc-slot@0 {
+ compatible = "cavium,thunder-8390-mmc-slot";
+ reg = <0>;
+ vmmc-supply = <&mmc_supply_3v3>;
+ max-frequency = <42000000>;
+ bus-width = <4>;
+ cap-sd-highspeed;
+ };
+
+ mmc-slot@1 {
+ compatible = "cavium,thunder-8390-mmc-slot";
+ reg = <1>;
+ vmmc-supply = <&mmc_supply_3v3>;
+ max-frequency = <42000000>;
+ bus-width = <8>;
+ cap-mmc-highspeed;
+ non-removable;
+ };
+ };
--
2.9.0.rc0.21.g7777322
On Fri, Mar 10, 2017 at 2:24 PM, Jan Glauber <[email protected]> wrote:
> Hi Ulf,
>
> I've not heard back from you regarding the bitfields so I assume this
> means you're insisting on that point. I'd really like to make some
> progress with this series, so I'm reposting now. All comments should be
> addressed. Patches are on top of 4.11-rc1.
>
> @Rob: I kept your ACK after the removal of the duplicate voltage line,
> please let me know if that is not OK.
>
> Changes to v11:
> - Fix build error and kill IS_ENABLED() by using an offset per arch
I've looked at the whole series again and didn't find anything that seemed
wrong any more, so
Acked-by: Arnd Bergmann <[email protected]>
On Wed, Mar 15, 2017 at 04:57:37PM +0100, Arnd Bergmann wrote:
> On Fri, Mar 10, 2017 at 2:24 PM, Jan Glauber <[email protected]> wrote:
> > Hi Ulf,
> >
> > I've not heard back from you regarding the bitfields so I assume this
> > means you're insisting on that point. I'd really like to make some
> > progress with this series, so I'm reposting now. All comments should be
> > addressed. Patches are on top of 4.11-rc1.
> >
> > @Rob: I kept your ACK after the removal of the duplicate voltage line,
> > please let me know if that is not OK.
> >
> > Changes to v11:
> > - Fix build error and kill IS_ENABLED() by using an offset per arch
>
> I've looked at the whole series again and didn't find anything that seemed
> wrong any more, so
>
> Acked-by: Arnd Bergmann <[email protected]>
Thanks Arnd!
Just to note, I think I forgot one thing Ulf wanted, the replacement of
mmc_of_parse_voltage() with a hardcoded value for the Octeon boards.
--Jan
On 10 March 2017 at 14:24, Jan Glauber <[email protected]> wrote:
> Add description of Cavium Octeon and ThunderX SOC device tree bindings.
>
> CC: Ulf Hansson <[email protected]>
> CC: Rob Herring <[email protected]>
> CC: Mark Rutland <[email protected]>
> CC: [email protected]
>
> Signed-off-by: Jan Glauber <[email protected]>
> Signed-off-by: David Daney <[email protected]>
> Signed-off-by: Steven J. Hill <[email protected]>
> Acked-by: Rob Herring <[email protected]>
> ---
> .../devicetree/bindings/mmc/cavium-mmc.txt | 58 ++++++++++++++++++++++
> 1 file changed, 58 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/mmc/cavium-mmc.txt
>
> diff --git a/Documentation/devicetree/bindings/mmc/cavium-mmc.txt b/Documentation/devicetree/bindings/mmc/cavium-mmc.txt
> new file mode 100644
> index 0000000..225c2be
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mmc/cavium-mmc.txt
> @@ -0,0 +1,58 @@
> +* Cavium Octeon & ThunderX MMC controller
> +
> +The highspeed MMC host controller on Caviums SoCs provides an interface
> +for MMC and SD types of memory cards.
> +
> +Supported maximum speeds are the ones of the eMMC standard 4.41 as well
> +as the speed of SD standard 4.0. Only 3.3 Volt is supported.
> +
> +Required properties:
> + - compatible : should be one of:
> + cavium,octeon-6130-mmc
> + cavium,octeon-6130-mmc-slot
> + cavium,octeon-7890-mmc
> + cavium,octeon-7890-mmc-slot
> + cavium,thunder-8190-mmc
> + cavium,thunder-8190-mmc-slot
> + cavium,thunder-8390-mmc
> + cavium,thunder-8390-mmc-slot
> + - reg : mmc controller base registers
> + - clocks : phandle
> +
> +Optional properties:
> + - for cd, bus-width and additional generic mmc parameters
> + please refer to mmc.txt within this directory
> + - cavium,cmd-clk-skew : number of coprocessor clocks before sampling command
> + - cavium,dat-clk-skew : number of coprocessor clocks before sampling data
> +
> +Deprecated properties:
> +- spi-max-frequency : use max-frequency instead
> +- cavium,bus-max-width : use bus-width instead
> +
> +Examples:
> + mmc_1_4: mmc@1,4 {
> + compatible = "cavium,thunder-8390-mmc";
> + reg = <0x0c00 0 0 0 0>; /* DEVFN = 0x0c (1:4) */
> + #address-cells = <1>;
> + #size-cells = <0>;
> + clocks = <&sclk>;
> +
> + mmc-slot@0 {
> + compatible = "cavium,thunder-8390-mmc-slot";
> + reg = <0>;
Just realized that I forgotten to follow up about the details for I
think we should generally describe slots nodes in DT.
Currently we treat a child node of a host device node, with reg=0 as
being an embedded mmc card [1] (in case it has the "mmc-card"
compatible set).
When reg is 1->7, those are reserved for SDIO function nodes [2] (as
those can be exactly 7, according to the SDIO spec).
Let's take the above into account and consider that a slot node may
also require a its own child node as to describe an embedded mmc card
or SDIO funcs. In this context I don't think it makes sense to use SoC
specific compatibles for slot nodes, instead I suggest we use only
"mmc-slot".
Does that makes sense?
> + vmmc-supply = <&mmc_supply_3v3>;
> + max-frequency = <42000000>;
> + bus-width = <4>;
> + cap-sd-highspeed;
> + };
> +
> + mmc-slot@1 {
> + compatible = "cavium,thunder-8390-mmc-slot";
> + reg = <1>;
> + vmmc-supply = <&mmc_supply_3v3>;
> + max-frequency = <42000000>;
> + bus-width = <8>;
> + cap-mmc-highspeed;
> + non-removable;
> + };
> + };
> --
> 2.9.0.rc0.21.g7777322
>
Kind regards
Uffe
On Fri, Mar 17, 2017 at 09:31:23AM +0100, Ulf Hansson wrote:
> On 10 March 2017 at 14:24, Jan Glauber <[email protected]> wrote:
> > Add description of Cavium Octeon and ThunderX SOC device tree bindings.
> >
> > CC: Ulf Hansson <[email protected]>
> > CC: Rob Herring <[email protected]>
> > CC: Mark Rutland <[email protected]>
> > CC: [email protected]
> >
> > Signed-off-by: Jan Glauber <[email protected]>
> > Signed-off-by: David Daney <[email protected]>
> > Signed-off-by: Steven J. Hill <[email protected]>
> > Acked-by: Rob Herring <[email protected]>
> > ---
> > .../devicetree/bindings/mmc/cavium-mmc.txt | 58 ++++++++++++++++++++++
> > 1 file changed, 58 insertions(+)
> > create mode 100644 Documentation/devicetree/bindings/mmc/cavium-mmc.txt
> >
> > diff --git a/Documentation/devicetree/bindings/mmc/cavium-mmc.txt b/Documentation/devicetree/bindings/mmc/cavium-mmc.txt
> > new file mode 100644
> > index 0000000..225c2be
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/mmc/cavium-mmc.txt
> > @@ -0,0 +1,58 @@
> > +* Cavium Octeon & ThunderX MMC controller
> > +
> > +The highspeed MMC host controller on Caviums SoCs provides an interface
> > +for MMC and SD types of memory cards.
> > +
> > +Supported maximum speeds are the ones of the eMMC standard 4.41 as well
> > +as the speed of SD standard 4.0. Only 3.3 Volt is supported.
> > +
> > +Required properties:
> > + - compatible : should be one of:
> > + cavium,octeon-6130-mmc
> > + cavium,octeon-6130-mmc-slot
> > + cavium,octeon-7890-mmc
> > + cavium,octeon-7890-mmc-slot
> > + cavium,thunder-8190-mmc
> > + cavium,thunder-8190-mmc-slot
> > + cavium,thunder-8390-mmc
> > + cavium,thunder-8390-mmc-slot
> > + - reg : mmc controller base registers
> > + - clocks : phandle
> > +
> > +Optional properties:
> > + - for cd, bus-width and additional generic mmc parameters
> > + please refer to mmc.txt within this directory
> > + - cavium,cmd-clk-skew : number of coprocessor clocks before sampling command
> > + - cavium,dat-clk-skew : number of coprocessor clocks before sampling data
> > +
> > +Deprecated properties:
> > +- spi-max-frequency : use max-frequency instead
> > +- cavium,bus-max-width : use bus-width instead
> > +
> > +Examples:
> > + mmc_1_4: mmc@1,4 {
> > + compatible = "cavium,thunder-8390-mmc";
> > + reg = <0x0c00 0 0 0 0>; /* DEVFN = 0x0c (1:4) */
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > + clocks = <&sclk>;
> > +
> > + mmc-slot@0 {
> > + compatible = "cavium,thunder-8390-mmc-slot";
> > + reg = <0>;
>
> Just realized that I forgotten to follow up about the details for I
> think we should generally describe slots nodes in DT.
>
> Currently we treat a child node of a host device node, with reg=0 as
> being an embedded mmc card [1] (in case it has the "mmc-card"
> compatible set).
> When reg is 1->7, those are reserved for SDIO function nodes [2] (as
> those can be exactly 7, according to the SDIO spec).
>
> Let's take the above into account and consider that a slot node may
> also require a its own child node as to describe an embedded mmc card
> or SDIO funcs. In this context I don't think it makes sense to use SoC
> specific compatibles for slot nodes, instead I suggest we use only
> "mmc-slot".
>
> Does that makes sense?
The slot compatible is currently not used, setting it to "mmc-slot"
looks like good to me.
--Jan
> > + vmmc-supply = <&mmc_supply_3v3>;
> > + max-frequency = <42000000>;
> > + bus-width = <4>;
> > + cap-sd-highspeed;
> > + };
> > +
> > + mmc-slot@1 {
> > + compatible = "cavium,thunder-8390-mmc-slot";
> > + reg = <1>;
> > + vmmc-supply = <&mmc_supply_3v3>;
> > + max-frequency = <42000000>;
> > + bus-width = <8>;
> > + cap-mmc-highspeed;
> > + non-removable;
> > + };
> > + };
> > --
> > 2.9.0.rc0.21.g7777322
> >
>
> Kind regards
> Uffe
On 10 March 2017 at 14:25, Jan Glauber <[email protected]> wrote:
> This core driver will be used by a MIPS platform driver
> or by an ARM64 PCI driver. The core driver implements the
> mmc_host_ops and slot probe & remove functions.
> Callbacks are provided to allow platform specific interrupt
> enable and bus locking.
>
> The host controller supports:
> - up to 4 slots that can contain sd-cards or eMMC chips
> - 1, 4 and 8 bit bus width
> - SDR and DDR
> - transfers up to 52 Mhz (might be less when multiple slots are used)
> - DMA read/write
> - multi-block read/write (but not stream mode)
>
> Voltage is limited to 3.3v and shared for all slots (vmmc and vmmcq).
>
> A global lock for all MMC devices is required because the host
> controller is shared.
>
> Signed-off-by: Jan Glauber <[email protected]>
> Signed-off-by: David Daney <[email protected]>
> Signed-off-by: Steven J. Hill <[email protected]>
> ---
> drivers/mmc/host/cavium-mmc.c | 988 ++++++++++++++++++++++++++++++++++++++++++
> drivers/mmc/host/cavium-mmc.h | 178 ++++++++
> 2 files changed, 1166 insertions(+)
> create mode 100644 drivers/mmc/host/cavium-mmc.c
> create mode 100644 drivers/mmc/host/cavium-mmc.h
>
> diff --git a/drivers/mmc/host/cavium-mmc.c b/drivers/mmc/host/cavium-mmc.c
> new file mode 100644
> index 0000000..11fdcfb
> --- /dev/null
> +++ b/drivers/mmc/host/cavium-mmc.c
> @@ -0,0 +1,988 @@
> +/*
> + * Shared part of driver for MMC/SDHC controller on Cavium OCTEON and
> + * 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) 2012-2017 Cavium Inc.
> + * Authors:
> + * David Daney <[email protected]>
> + * Peter Swain <[email protected]>
> + * Steven J. Hill <[email protected]>
> + * Jan Glauber <[email protected]>
> + */
> +#include <linux/bitfield.h>
> +#include <linux/delay.h>
> +#include <linux/dma-direction.h>
> +#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/regulator/consumer.h>
> +#include <linux/scatterlist.h>
> +#include <linux/time.h>
> +
> +#include "cavium-mmc.h"
> +
> +const char *cvm_mmc_irq_names[] = {
> + "MMC Buffer",
> + "MMC Command",
> + "MMC DMA",
> + "MMC Command Error",
> + "MMC DMA Error",
> + "MMC Switch",
> + "MMC Switch Error",
> + "MMC DMA int Fifo",
> + "MMC DMA int",
> +};
Debug-leftover?
[...]
> +
> +static void cvm_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> +{
> + struct cvm_mmc_slot *slot = mmc_priv(mmc);
> + struct cvm_mmc_host *host = slot->host;
> + int clk_period = 0, power_class = 10, bus_width = 0;
> + u64 clock, emm_switch;
> +
> + host->acquire_bus(host);
> + cvm_mmc_switch_to(slot);
> +
> + /* Set the power state */
> + switch (ios->power_mode) {
> + case MMC_POWER_ON:
> + break;
> +
> + case MMC_POWER_OFF:
> + cvm_mmc_reset_bus(slot);
> +
> + if (host->global_pwr_gpiod)
> + gpiod_set_value_cansleep(host->global_pwr_gpiod, 0);
If I have understood the changelog correctly this GPIO is shared for
all slots, right?
In such case, this doesn't work. You need to centralize the management
of this GPIO pin (to enable reference counting), as otherwise one slot
can decide to power off its card while another still uses their card
and expecting the power to be on.
Another option would be to model it as GPIO regulator (using a device
tree overlay as we discussed earlier), then you get the reference
counting for free - and easily get ocr_avail mask from the mmc core's
regulator API. :-)
Moreover, I didn't find this GPIO being documented as a DT binding, it
should and it should also be marked as deprecated.
> + else
> + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
> + break;
> +
> + case MMC_POWER_UP:
> + if (host->global_pwr_gpiod)
> + gpiod_set_value_cansleep(host->global_pwr_gpiod, 1);
Dittto.
> + else
> + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, ios->vdd);
> + break;
> + }
> +
[...]
> +
> +static int cvm_mmc_of_parse(struct device *dev, struct cvm_mmc_slot *slot)
> +{
> + u32 id, cmd_skew = 0, dat_skew = 0, bus_width = 0, f_max = 0;
> + struct device_node *node = dev->of_node;
> + struct mmc_host *mmc = slot->mmc;
> + u64 clock_period;
> + int ret;
> +
> + ret = of_property_read_u32(node, "reg", &id);
> + if (ret) {
> + dev_err(dev, "Missing or invalid reg property on %s\n",
> + of_node_full_name(node));
> + return ret;
> + }
> +
> + if (id >= CAVIUM_MAX_MMC || slot->host->slot[id]) {
> + dev_err(dev, "Invalid reg property on %s\n",
> + of_node_full_name(node));
> + return -EINVAL;
> + }
> +
> + /* Deprecated Cavium bindings for old firmware */
> + of_property_read_u32(node, "cavium,bus-max-width", &bus_width);
> + of_property_read_u32(node, "spi-max-frequency", &f_max);
> + if (slot->host->global_pwr_gpiod) {
> + /* Get a sane OCR mask for other parts of the MMC subsytem. */
> + ret = mmc_of_parse_voltage(node, &mmc->ocr_avail);
I noticed your comment to Arnd for the cover-letter. So I assume you
will remove this and instead assign mmc->ocr_avail a default value in
cases when you don't have a vmmc regulator to find it from.
> + if (ret < 0)
> + return ret;
> + }
> +
> + /* Cavium-specific DT properties */
> + of_property_read_u32(node, "cavium,cmd-clk-skew", &cmd_skew);
> + of_property_read_u32(node, "cavium,dat-clk-skew", &dat_skew);
> +
> + if (!slot->host->global_pwr_gpiod) {
> + mmc->supply.vmmc = devm_regulator_get(dev, "vmmc");
> + if (IS_ERR(mmc->supply.vmmc))
> + return PTR_ERR(mmc->supply.vmmc);
> +
> + ret = mmc_regulator_get_ocrmask(mmc->supply.vmmc);
> + if (ret > 0)
> + mmc->ocr_avail = ret;
> + }
> +
> + /* Common MMC bindings */
> + ret = mmc_of_parse(mmc);
> + if (ret)
> + return ret;
> +
> + /* Set bus width */
> + if (!bus_width) {
> + if (mmc->caps & MMC_CAP_8_BIT_DATA)
> + bus_width = 8;
> + else if (mmc->caps & MMC_CAP_4_BIT_DATA)
> + bus_width = 4;
> + else
> + bus_width = 1;
> + }
> +
> + switch (bus_width) {
> + case 8:
> + slot->bus_width = 2;
Why do you need to store this in slot struct? The information is
already available in the mmc host.
> + slot->mmc->caps = MMC_CAP_8_BIT_DATA | MMC_CAP_4_BIT_DATA;
This is wrong, as you will clear all the other mmc caps potentially
assigned by mmc_of_parse() above.
Moreover, you can use mmc->caps instead of slot->mmc->caps.
> + break;
> + case 4:
> + slot->bus_width = 1;
> + slot->mmc->caps = MMC_CAP_4_BIT_DATA;
> + break;
> + case 1:
> + slot->bus_width = 0;
> + break;
> + }
I would rather make the deprecated bindings to take the lowest
precedence and besides, this bus_width setup looks messy. How about
something like this instead:
mmc_of_parse();
if (!(mmc->caps & (MMC_CAP_8_BIT_DATA | MMC_CAP_4_BIT_DATA))) {
of_property_read_u32(node, "cavium,bus-max-width", &bus_width);
if (bus_width == 8)
mmc->caps |= MMC_CAP_8_BIT_DATA | MMC_CAP_4_BIT_DATA;
else if (bus_width == 4)
mmc->caps |= MMC_CAP_4_BIT_DATA;
}
> +
> + /* Set maximum and minimum frequency */
> + if (f_max)
> + mmc->f_max = f_max;
Again, let's make sure the deprecated bindings takes lower precedence.
Thus if mmc->f_max has a value let's use that and if not, then parse
the deprecated DT binding and use that value instead.
> + if (!mmc->f_max || mmc->f_max > 52000000)
> + mmc->f_max = 52000000;
> + mmc->f_min = 400000;
> +
> + /* Sampling register settings, period in picoseconds */
> + clock_period = 1000000000000ull / slot->host->sys_freq;
> + slot->cmd_cnt = (cmd_skew + clock_period / 2) / clock_period;
> + slot->dat_cnt = (dat_skew + clock_period / 2) / clock_period;
> +
> + return id;
> +}
[...]
> diff --git a/drivers/mmc/host/cavium-mmc.h b/drivers/mmc/host/cavium-mmc.h
> new file mode 100644
> index 0000000..c3843448
> --- /dev/null
> +++ b/drivers/mmc/host/cavium-mmc.h
[...]
+
> +/* Protoypes */
> +irqreturn_t cvm_mmc_interrupt(int irq, void *dev_id);
> +int cvm_mmc_of_slot_probe(struct device *dev, struct cvm_mmc_host *host);
> +int cvm_mmc_of_slot_remove(struct cvm_mmc_slot *slot);
> +extern const char *cvm_mmc_irq_names[];
Debug leftover?
Kind regards
Uffe
On Fri, Mar 17, 2017 at 12:24:57PM +0100, Ulf Hansson wrote:
> On 10 March 2017 at 14:25, Jan Glauber <[email protected]> wrote:
> > This core driver will be used by a MIPS platform driver
> > or by an ARM64 PCI driver. The core driver implements the
> > mmc_host_ops and slot probe & remove functions.
> > Callbacks are provided to allow platform specific interrupt
> > enable and bus locking.
> >
> > The host controller supports:
> > - up to 4 slots that can contain sd-cards or eMMC chips
> > - 1, 4 and 8 bit bus width
> > - SDR and DDR
> > - transfers up to 52 Mhz (might be less when multiple slots are used)
> > - DMA read/write
> > - multi-block read/write (but not stream mode)
> >
> > Voltage is limited to 3.3v and shared for all slots (vmmc and vmmcq).
> >
> > A global lock for all MMC devices is required because the host
> > controller is shared.
> >
> > Signed-off-by: Jan Glauber <[email protected]>
> > Signed-off-by: David Daney <[email protected]>
> > Signed-off-by: Steven J. Hill <[email protected]>
> > ---
> > drivers/mmc/host/cavium-mmc.c | 988 ++++++++++++++++++++++++++++++++++++++++++
> > drivers/mmc/host/cavium-mmc.h | 178 ++++++++
> > 2 files changed, 1166 insertions(+)
> > create mode 100644 drivers/mmc/host/cavium-mmc.c
> > create mode 100644 drivers/mmc/host/cavium-mmc.h
> >
> > diff --git a/drivers/mmc/host/cavium-mmc.c b/drivers/mmc/host/cavium-mmc.c
> > new file mode 100644
> > index 0000000..11fdcfb
> > --- /dev/null
> > +++ b/drivers/mmc/host/cavium-mmc.c
> > @@ -0,0 +1,988 @@
> > +/*
> > + * Shared part of driver for MMC/SDHC controller on Cavium OCTEON and
> > + * 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) 2012-2017 Cavium Inc.
> > + * Authors:
> > + * David Daney <[email protected]>
> > + * Peter Swain <[email protected]>
> > + * Steven J. Hill <[email protected]>
> > + * Jan Glauber <[email protected]>
> > + */
> > +#include <linux/bitfield.h>
> > +#include <linux/delay.h>
> > +#include <linux/dma-direction.h>
> > +#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/regulator/consumer.h>
> > +#include <linux/scatterlist.h>
> > +#include <linux/time.h>
> > +
> > +#include "cavium-mmc.h"
> > +
> > +const char *cvm_mmc_irq_names[] = {
> > + "MMC Buffer",
> > + "MMC Command",
> > + "MMC DMA",
> > + "MMC Command Error",
> > + "MMC DMA Error",
> > + "MMC Switch",
> > + "MMC Switch Error",
> > + "MMC DMA int Fifo",
> > + "MMC DMA int",
> > +};
>
> Debug-leftover?
>
> [...]
No, this is used by both Octeon and ThunderX drivers. Maybe I should
have put it into an extra patch.
> > +
> > +static void cvm_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> > +{
> > + struct cvm_mmc_slot *slot = mmc_priv(mmc);
> > + struct cvm_mmc_host *host = slot->host;
> > + int clk_period = 0, power_class = 10, bus_width = 0;
> > + u64 clock, emm_switch;
> > +
> > + host->acquire_bus(host);
> > + cvm_mmc_switch_to(slot);
> > +
> > + /* Set the power state */
> > + switch (ios->power_mode) {
> > + case MMC_POWER_ON:
> > + break;
> > +
> > + case MMC_POWER_OFF:
> > + cvm_mmc_reset_bus(slot);
> > +
> > + if (host->global_pwr_gpiod)
> > + gpiod_set_value_cansleep(host->global_pwr_gpiod, 0);
>
> If I have understood the changelog correctly this GPIO is shared for
> all slots, right?
Yes.
> In such case, this doesn't work. You need to centralize the management
> of this GPIO pin (to enable reference counting), as otherwise one slot
> can decide to power off its card while another still uses their card
> and expecting the power to be on.
OK. I could create a function in the shared part with ref counting.
On the other side, only the existing Octeon HW will use the global_pwr_gpiod,
and this HW only has one slot. For all new HW we'll use the GPIO regulator,
so I think it is not worth changing it. I'll add a comment.
> Another option would be to model it as GPIO regulator (using a device
> tree overlay as we discussed earlier), then you get the reference
> counting for free - and easily get ocr_avail mask from the mmc core's
> regulator API. :-)
>
No, this is what I already do in case host->global_pwr_gpiod is not set.
> Moreover, I didn't find this GPIO being documented as a DT binding, it
> should and it should also be marked as deprecated.
Good point, I'll add it.
> > + else
> > + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
> > + break;
> > +
> > + case MMC_POWER_UP:
> > + if (host->global_pwr_gpiod)
> > + gpiod_set_value_cansleep(host->global_pwr_gpiod, 1);
>
> Dittto.
>
> > + else
> > + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, ios->vdd);
> > + break;
> > + }
> > +
>
> [...]
>
> > +
> > +static int cvm_mmc_of_parse(struct device *dev, struct cvm_mmc_slot *slot)
> > +{
> > + u32 id, cmd_skew = 0, dat_skew = 0, bus_width = 0, f_max = 0;
> > + struct device_node *node = dev->of_node;
> > + struct mmc_host *mmc = slot->mmc;
> > + u64 clock_period;
> > + int ret;
> > +
> > + ret = of_property_read_u32(node, "reg", &id);
> > + if (ret) {
> > + dev_err(dev, "Missing or invalid reg property on %s\n",
> > + of_node_full_name(node));
> > + return ret;
> > + }
> > +
> > + if (id >= CAVIUM_MAX_MMC || slot->host->slot[id]) {
> > + dev_err(dev, "Invalid reg property on %s\n",
> > + of_node_full_name(node));
> > + return -EINVAL;
> > + }
> > +
> > + /* Deprecated Cavium bindings for old firmware */
> > + of_property_read_u32(node, "cavium,bus-max-width", &bus_width);
> > + of_property_read_u32(node, "spi-max-frequency", &f_max);
> > + if (slot->host->global_pwr_gpiod) {
> > + /* Get a sane OCR mask for other parts of the MMC subsytem. */
> > + ret = mmc_of_parse_voltage(node, &mmc->ocr_avail);
>
> I noticed your comment to Arnd for the cover-letter. So I assume you
> will remove this and instead assign mmc->ocr_avail a default value in
> cases when you don't have a vmmc regulator to find it from.
Yes.
> > + if (ret < 0)
> > + return ret;
> > + }
> > +
> > + /* Cavium-specific DT properties */
> > + of_property_read_u32(node, "cavium,cmd-clk-skew", &cmd_skew);
> > + of_property_read_u32(node, "cavium,dat-clk-skew", &dat_skew);
> > +
> > + if (!slot->host->global_pwr_gpiod) {
> > + mmc->supply.vmmc = devm_regulator_get(dev, "vmmc");
> > + if (IS_ERR(mmc->supply.vmmc))
> > + return PTR_ERR(mmc->supply.vmmc);
> > +
> > + ret = mmc_regulator_get_ocrmask(mmc->supply.vmmc);
> > + if (ret > 0)
> > + mmc->ocr_avail = ret;
> > + }
> > +
> > + /* Common MMC bindings */
> > + ret = mmc_of_parse(mmc);
> > + if (ret)
> > + return ret;
> > +
> > + /* Set bus width */
> > + if (!bus_width) {
> > + if (mmc->caps & MMC_CAP_8_BIT_DATA)
> > + bus_width = 8;
> > + else if (mmc->caps & MMC_CAP_4_BIT_DATA)
> > + bus_width = 4;
> > + else
> > + bus_width = 1;
> > + }
> > +
> > + switch (bus_width) {
> > + case 8:
> > + slot->bus_width = 2;
>
> Why do you need to store this in slot struct? The information is
> already available in the mmc host.
I guess it is in the slot because the HW encoding is different from the
mmc bus width. Let me see if removing it simplifies the code.
> > + slot->mmc->caps = MMC_CAP_8_BIT_DATA | MMC_CAP_4_BIT_DATA;
>
> This is wrong, as you will clear all the other mmc caps potentially
> assigned by mmc_of_parse() above.
Good catch.
> Moreover, you can use mmc->caps instead of slot->mmc->caps.
Yup.
> > + break;
> > + case 4:
> > + slot->bus_width = 1;
> > + slot->mmc->caps = MMC_CAP_4_BIT_DATA;
> > + break;
> > + case 1:
> > + slot->bus_width = 0;
> > + break;
> > + }
>
> I would rather make the deprecated bindings to take the lowest
> precedence and besides, this bus_width setup looks messy. How about
> something like this instead:
Previously you said I should parse deprecated bindings first, so I did
that ;-
> mmc_of_parse();
>
> if (!(mmc->caps & (MMC_CAP_8_BIT_DATA | MMC_CAP_4_BIT_DATA))) {
> of_property_read_u32(node, "cavium,bus-max-width", &bus_width);
> if (bus_width == 8)
> mmc->caps |= MMC_CAP_8_BIT_DATA | MMC_CAP_4_BIT_DATA;
> else if (bus_width == 4)
> mmc->caps |= MMC_CAP_4_BIT_DATA;
> }
OK.
> > +
> > + /* Set maximum and minimum frequency */
> > + if (f_max)
> > + mmc->f_max = f_max;
>
> Again, let's make sure the deprecated bindings takes lower precedence.
> Thus if mmc->f_max has a value let's use that and if not, then parse
> the deprecated DT binding and use that value instead.
OK.
> > + if (!mmc->f_max || mmc->f_max > 52000000)
> > + mmc->f_max = 52000000;
> > + mmc->f_min = 400000;
> > +
> > + /* Sampling register settings, period in picoseconds */
> > + clock_period = 1000000000000ull / slot->host->sys_freq;
> > + slot->cmd_cnt = (cmd_skew + clock_period / 2) / clock_period;
> > + slot->dat_cnt = (dat_skew + clock_period / 2) / clock_period;
> > +
> > + return id;
> > +}
>
> [...]
>
> > diff --git a/drivers/mmc/host/cavium-mmc.h b/drivers/mmc/host/cavium-mmc.h
> > new file mode 100644
> > index 0000000..c3843448
> > --- /dev/null
> > +++ b/drivers/mmc/host/cavium-mmc.h
>
> [...]
>
> +
> > +/* Protoypes */
> > +irqreturn_t cvm_mmc_interrupt(int irq, void *dev_id);
> > +int cvm_mmc_of_slot_probe(struct device *dev, struct cvm_mmc_host *host);
> > +int cvm_mmc_of_slot_remove(struct cvm_mmc_slot *slot);
> > +extern const char *cvm_mmc_irq_names[];
>
> Debug leftover?
No, as I said before this is the interface I need for sharing
cavium-mmc.c and using it from the Octeon and ThunderX drivers.
Should I put the interface into a separate patch (together with the
interrupt names)?
Thanks for the review!
Jan
> Kind regards
> Uffe
On 10 March 2017 at 14:25, Jan Glauber <[email protected]> wrote:
> Add a platform driver for Octeon MIPS SOCs.
>
> Signed-off-by: Jan Glauber <[email protected]>
> Signed-off-by: David Daney <[email protected]>
> Signed-off-by: Steven J. Hill <[email protected]>
> ---
> drivers/mmc/host/Kconfig | 10 ++
> drivers/mmc/host/Makefile | 2 +
> drivers/mmc/host/cavium-pltfm-octeon.c | 183 +++++++++++++++++++++++++++++++++
> 3 files changed, 195 insertions(+)
> create mode 100644 drivers/mmc/host/cavium-pltfm-octeon.c
Please rename the file to cavium-octeon.c
>
> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> index f08691a..68cc811 100644
> --- a/drivers/mmc/host/Kconfig
> +++ b/drivers/mmc/host/Kconfig
> @@ -622,6 +622,16 @@ config SDH_BFIN_MISSING_CMD_PULLUP_WORKAROUND
> help
> If you say yes here SD-Cards may work on the EZkit.
>
> +config MMC_CAVIUM_OCTEON
> + tristate "Cavium OCTEON SD/MMC Card Interface support"
> + depends on CAVIUM_OCTEON_SOC
> + help
> + This selects Cavium OCTEON SD/MMC card Interface.
> + If you have an OCTEON board with a Multimedia Card slot,
> + say Y or M here.
> +
> + If unsure, say N.
> +
Adding more new cavium variants becomes a bit messy in this approach.
May I suggest something similar we are using for SDHCI mmc driver.
That is:
For the core mmc cavium driver:
config MMC_CAVIUM (to build cavium-mmc.o)
I would also appreciate to rename that file to cavium.c
This also means you need to export the functions you provide from the
header cavium-mmc.h (rename to cavium.h)
For the octeon variant:
config MMC_CAVIUM_OCTEON (to build cavium-octeon.o)
depends on MMC_CAVIUM && CAVIUM_OCTEON_SOC
> config MMC_DW
> tristate "Synopsys DesignWare Memory Card Interface"
> depends on HAS_DMA
> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
> index 6d548c4..c7f0ccf 100644
> --- a/drivers/mmc/host/Makefile
> +++ b/drivers/mmc/host/Makefile
> @@ -42,6 +42,8 @@ obj-$(CONFIG_MMC_SDHI) += sh_mobile_sdhi.o
> obj-$(CONFIG_MMC_CB710) += cb710-mmc.o
> obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o
> obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o
> +octeon-mmc-objs := cavium-mmc.o cavium-pltfm-octeon.o
> +obj-$(CONFIG_MMC_CAVIUM_OCTEON) += octeon-mmc.o
By changing according to above, we get two build objects instead of
two. Nice an clean.
> obj-$(CONFIG_MMC_DW) += dw_mmc.o
> obj-$(CONFIG_MMC_DW_PLTFM) += dw_mmc-pltfm.o
> obj-$(CONFIG_MMC_DW_EXYNOS) += dw_mmc-exynos.o
> diff --git a/drivers/mmc/host/cavium-pltfm-octeon.c b/drivers/mmc/host/cavium-pltfm-octeon.c
> new file mode 100644
> index 0000000..e83d143
[...]
Changing the name of the files, may also lead to that you perhaps want
to change the prefix of the functions.
Otherwise this looks good to me.
Kind regards
Uffe
[...]
>> > +
>> > +const char *cvm_mmc_irq_names[] = {
>> > + "MMC Buffer",
>> > + "MMC Command",
>> > + "MMC DMA",
>> > + "MMC Command Error",
>> > + "MMC DMA Error",
>> > + "MMC Switch",
>> > + "MMC Switch Error",
>> > + "MMC DMA int Fifo",
>> > + "MMC DMA int",
>> > +};
>>
>> Debug-leftover?
>>
>> [...]
>
> No, this is used by both Octeon and ThunderX drivers. Maybe I should
> have put it into an extra patch.
No, it's fine let's keep here.
>
>> > +
>> > +static void cvm_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
>> > +{
>> > + struct cvm_mmc_slot *slot = mmc_priv(mmc);
>> > + struct cvm_mmc_host *host = slot->host;
>> > + int clk_period = 0, power_class = 10, bus_width = 0;
>> > + u64 clock, emm_switch;
>> > +
>> > + host->acquire_bus(host);
>> > + cvm_mmc_switch_to(slot);
>> > +
>> > + /* Set the power state */
>> > + switch (ios->power_mode) {
>> > + case MMC_POWER_ON:
>> > + break;
>> > +
>> > + case MMC_POWER_OFF:
>> > + cvm_mmc_reset_bus(slot);
>> > +
>> > + if (host->global_pwr_gpiod)
>> > + gpiod_set_value_cansleep(host->global_pwr_gpiod, 0);
>>
>> If I have understood the changelog correctly this GPIO is shared for
>> all slots, right?
>
> Yes.
>
>> In such case, this doesn't work. You need to centralize the management
>> of this GPIO pin (to enable reference counting), as otherwise one slot
>> can decide to power off its card while another still uses their card
>> and expecting the power to be on.
>
> OK. I could create a function in the shared part with ref counting.
> On the other side, only the existing Octeon HW will use the global_pwr_gpiod,
> and this HW only has one slot. For all new HW we'll use the GPIO regulator,
> so I think it is not worth changing it. I'll add a comment.
In such case, let's not make it part of the cavium core set ios
function. Instead leave it to the octeon variant to deal with this.
>
>> Another option would be to model it as GPIO regulator (using a device
>> tree overlay as we discussed earlier), then you get the reference
>> counting for free - and easily get ocr_avail mask from the mmc core's
>> regulator API. :-)
>>
>
> No, this is what I already do in case host->global_pwr_gpiod is not set.
Yes, that was my point. You would be able to remove some code both in
cavium core and octeon variant.
>
>> Moreover, I didn't find this GPIO being documented as a DT binding, it
>> should and it should also be marked as deprecated.
>
> Good point, I'll add it.
Great!
[...]
>> > + break;
>> > + case 4:
>> > + slot->bus_width = 1;
>> > + slot->mmc->caps = MMC_CAP_4_BIT_DATA;
>> > + break;
>> > + case 1:
>> > + slot->bus_width = 0;
>> > + break;
>> > + }
>>
>> I would rather make the deprecated bindings to take the lowest
>> precedence and besides, this bus_width setup looks messy. How about
>> something like this instead:
>
> Previously you said I should parse deprecated bindings first, so I did
> that ;-
Yes, I remember that now. Sorry! :-)
However, my point is about which binding that has the highest precedence.
>
>> mmc_of_parse();
>>
>> if (!(mmc->caps & (MMC_CAP_8_BIT_DATA | MMC_CAP_4_BIT_DATA))) {
>> of_property_read_u32(node, "cavium,bus-max-width", &bus_width);
>> if (bus_width == 8)
>> mmc->caps |= MMC_CAP_8_BIT_DATA | MMC_CAP_4_BIT_DATA;
>> else if (bus_width == 4)
>> mmc->caps |= MMC_CAP_4_BIT_DATA;
>> }
>
> OK.
>
>> > +
>> > + /* Set maximum and minimum frequency */
>> > + if (f_max)
>> > + mmc->f_max = f_max;
>>
>> Again, let's make sure the deprecated bindings takes lower precedence.
>> Thus if mmc->f_max has a value let's use that and if not, then parse
>> the deprecated DT binding and use that value instead.
>
> OK.
>
Great!
>> > + if (!mmc->f_max || mmc->f_max > 52000000)
>> > + mmc->f_max = 52000000;
>> > + mmc->f_min = 400000;
>> > +
>> > + /* Sampling register settings, period in picoseconds */
>> > + clock_period = 1000000000000ull / slot->host->sys_freq;
>> > + slot->cmd_cnt = (cmd_skew + clock_period / 2) / clock_period;
>> > + slot->dat_cnt = (dat_skew + clock_period / 2) / clock_period;
>> > +
>> > + return id;
>> > +}
>>
>> [...]
>>
>> > diff --git a/drivers/mmc/host/cavium-mmc.h b/drivers/mmc/host/cavium-mmc.h
>> > new file mode 100644
>> > index 0000000..c3843448
>> > --- /dev/null
>> > +++ b/drivers/mmc/host/cavium-mmc.h
>>
>> [...]
>>
>> +
>> > +/* Protoypes */
>> > +irqreturn_t cvm_mmc_interrupt(int irq, void *dev_id);
>> > +int cvm_mmc_of_slot_probe(struct device *dev, struct cvm_mmc_host *host);
>> > +int cvm_mmc_of_slot_remove(struct cvm_mmc_slot *slot);
>> > +extern const char *cvm_mmc_irq_names[];
>>
>> Debug leftover?
>
> No, as I said before this is the interface I need for sharing
> cavium-mmc.c and using it from the Octeon and ThunderX drivers.
>
> Should I put the interface into a separate patch (together with the
> interrupt names)?
No, it's fine!
Kind regards
Uffe
On 10 March 2017 at 14:25, Jan Glauber <[email protected]> wrote:
> Prevent data corruption on cn6xxx and cnf7xxx.
> Due to an imperfection in the design of the MMC bus hardware,
> the 2nd to last cache block of a DMA read must be locked into the L2
> cache.
>
> Signed-off-by: Jan Glauber <[email protected]>
> Signed-off-by: David Daney <[email protected]>
> Signed-off-by: Steven J. Hill <[email protected]>
> ---
> arch/mips/cavium-octeon/Makefile | 1 +
> arch/mips/cavium-octeon/octeon-mmc-l2c.c | 98 ++++++++++++++++++++++++++++++++
> drivers/mmc/host/cavium-mmc.c | 5 ++
> drivers/mmc/host/cavium-mmc.h | 5 ++
> drivers/mmc/host/cavium-pltfm-octeon.c | 30 ++++++++++
> 5 files changed, 139 insertions(+)
> create mode 100644 arch/mips/cavium-octeon/octeon-mmc-l2c.c
>
> diff --git a/arch/mips/cavium-octeon/Makefile b/arch/mips/cavium-octeon/Makefile
> index 7c02e54..3329a89 100644
> --- a/arch/mips/cavium-octeon/Makefile
> +++ b/arch/mips/cavium-octeon/Makefile
> @@ -19,3 +19,4 @@ obj-$(CONFIG_MTD) += flash_setup.o
> obj-$(CONFIG_SMP) += smp.o
> obj-$(CONFIG_OCTEON_ILM) += oct_ilm.o
> obj-$(CONFIG_USB) += octeon-usb.o
> +obj-$(CONFIG_MMC_CAVIUM_OCTEON) += octeon-mmc-l2c.o
> diff --git a/arch/mips/cavium-octeon/octeon-mmc-l2c.c b/arch/mips/cavium-octeon/octeon-mmc-l2c.c
> new file mode 100644
> index 0000000..6aaaf73
> --- /dev/null
> +++ b/arch/mips/cavium-octeon/octeon-mmc-l2c.c
> @@ -0,0 +1,98 @@
> +/*
> + * Driver for MMC and SSD cards for Cavium OCTEON 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) 2012-2016 Cavium Inc.
> + */
> +#include <linux/export.h>
> +#include <asm/octeon/octeon.h>
> +
> +/*
> + * The functions below are used for the EMMC-17978 workaround.
> + *
> + * Due to an imperfection in the design of the MMC bus hardware,
> + * the 2nd to last cache block of a DMA read must be locked into the L2 Cache.
> + * Otherwise, data corruption may occur.
> + */
> +
> +static inline void *phys_to_ptr(u64 address)
> +{
> + return (void *)(address | (1ull << 63)); /* XKPHYS */
> +}
> +
> +/**
> + * Lock a single line into L2. The line is zeroed before locking
> + * to make sure no dram accesses are made.
> + *
> + * @addr Physical address to lock
> + */
> +static void l2c_lock_line(u64 addr)
> +{
> + char *addr_ptr = phys_to_ptr(addr);
> +
> + asm volatile (
> + "cache 31, %[line]" /* Unlock the line */
> + :: [line] "m" (*addr_ptr));
> +}
> +
> +/**
> + * Unlock a single line in the L2 cache.
> + *
> + * @addr Physical address to unlock
> + *
> + * Return Zero on success
> + */
> +static void l2c_unlock_line(u64 addr)
> +{
> + char *addr_ptr = phys_to_ptr(addr);
> +
> + asm volatile (
> + "cache 23, %[line]" /* Unlock the line */
> + :: [line] "m" (*addr_ptr));
> +}
> +
> +/**
> + * Locks a memory region in the L2 cache
> + *
> + * @start - start address to begin locking
> + * @len - length in bytes to lock
> + */
> +void l2c_lock_mem_region(u64 start, u64 len)
> +{
> + u64 end;
> +
> + /* Round start/end to cache line boundaries */
> + end = ALIGN(start + len - 1, CVMX_CACHE_LINE_SIZE);
> + start = ALIGN(start, CVMX_CACHE_LINE_SIZE);
> +
> + while (start <= end) {
> + l2c_lock_line(start);
> + start += CVMX_CACHE_LINE_SIZE;
> + }
> + asm volatile("sync");
> +}
> +EXPORT_SYMBOL_GPL(l2c_lock_mem_region);
> +
> +/**
> + * Unlock a memory region in the L2 cache
> + *
> + * @start - start address to unlock
> + * @len - length to unlock in bytes
> + */
> +void l2c_unlock_mem_region(u64 start, u64 len)
> +{
> + u64 end;
> +
> + /* Round start/end to cache line boundaries */
> + end = ALIGN(start + len - 1, CVMX_CACHE_LINE_SIZE);
> + start = ALIGN(start, CVMX_CACHE_LINE_SIZE);
> +
> + while (start <= end) {
> + l2c_unlock_line(start);
> + start += CVMX_CACHE_LINE_SIZE;
> + }
> +}
> +EXPORT_SYMBOL_GPL(l2c_unlock_mem_region);
It seems like we should be able to implement these functions in the
octeon mmc driver, instead of having to export some SoC specific APIs.
You only need to figure out how to find the correct CACHE_LINE_SIZE,
but that should be possible to fix.
My point is really that we should avoid exporting SoC specific APIs
which shall be called from drivers. This is old fashion.
> diff --git a/drivers/mmc/host/cavium-mmc.c b/drivers/mmc/host/cavium-mmc.c
> index 11fdcfb..c1d3c65 100644
> --- a/drivers/mmc/host/cavium-mmc.c
> +++ b/drivers/mmc/host/cavium-mmc.c
> @@ -468,6 +468,8 @@ irqreturn_t cvm_mmc_interrupt(int irq, void *dev_id)
> req->done(req);
>
> no_req_done:
> + if (host->dmar_fixup_done)
> + host->dmar_fixup_done(host);
> if (host_done)
> host->release_bus(host);
> out:
> @@ -572,6 +574,9 @@ static void cvm_mmc_dma_request(struct mmc_host *mmc,
> host->int_enable(host, MIO_EMM_INT_CMD_ERR | MIO_EMM_INT_DMA_DONE |
> MIO_EMM_INT_DMA_ERR);
>
> + if (host->dmar_fixup)
> + host->dmar_fixup(host, mrq->cmd, data, addr);
> +
> /*
> * If we have a valid SD card in the slot, we set the response
> * bit mask to check for CRC errors and timeouts only.
> diff --git a/drivers/mmc/host/cavium-mmc.h b/drivers/mmc/host/cavium-mmc.h
> index c3843448..3ee6dae 100644
> --- a/drivers/mmc/host/cavium-mmc.h
> +++ b/drivers/mmc/host/cavium-mmc.h
> @@ -48,6 +48,7 @@ struct cvm_mmc_host {
> int reg_off;
> int reg_off_dma;
> u64 emm_cfg;
> + u64 n_minus_one; /* OCTEON II workaround location */
> int last_slot;
> struct clk *clk;
> int sys_freq;
> @@ -63,6 +64,10 @@ struct cvm_mmc_host {
> void (*acquire_bus)(struct cvm_mmc_host *);
> void (*release_bus)(struct cvm_mmc_host *);
> void (*int_enable)(struct cvm_mmc_host *, u64);
> + /* required on some MIPS models */
> + void (*dmar_fixup)(struct cvm_mmc_host *, struct mmc_command *,
> + struct mmc_data *, u64);
> + void (*dmar_fixup_done)(struct cvm_mmc_host *);
> };
>
> struct cvm_mmc_slot {
> diff --git a/drivers/mmc/host/cavium-pltfm-octeon.c b/drivers/mmc/host/cavium-pltfm-octeon.c
> index e83d143..9dabfa4 100644
> --- a/drivers/mmc/host/cavium-pltfm-octeon.c
> +++ b/drivers/mmc/host/cavium-pltfm-octeon.c
> @@ -18,6 +18,9 @@
>
> #define CVMX_MIO_BOOT_CTL CVMX_ADD_IO_SEG(0x00011800000000D0ull)
>
> +extern void l2c_lock_mem_region(u64 start, u64 len);
> +extern void l2c_unlock_mem_region(u64 start, u64 len);
> +
> static void octeon_mmc_acquire_bus(struct cvm_mmc_host *host)
> {
> /* Switch the MMC controller onto the bus. */
> @@ -36,6 +39,28 @@ static void octeon_mmc_int_enable(struct cvm_mmc_host *host, u64 val)
> writeq(val, host->base + MIO_EMM_INT_EN(host));
> }
>
> +static void octeon_mmc_dmar_fixup(struct cvm_mmc_host *host,
> + struct mmc_command *cmd,
> + struct mmc_data *data,
> + u64 addr)
> +{
> + if (cmd->opcode != MMC_WRITE_MULTIPLE_BLOCK)
> + return;
> + if (data->blksz * data->blocks <= 1024)
> + return;
> +
> + host->n_minus_one = addr + (data->blksz * data->blocks) - 1024;
> + l2c_lock_mem_region(host->n_minus_one, 512);
> +}
> +
> +static void octeon_mmc_dmar_fixup_done(struct cvm_mmc_host *host)
> +{
> + if (!host->n_minus_one)
> + return;
> + l2c_unlock_mem_region(host->n_minus_one, 512);
> + host->n_minus_one = 0;
> +}
> +
> static int octeon_mmc_probe(struct platform_device *pdev)
> {
> struct device_node *cn, *node = pdev->dev.of_node;
> @@ -54,6 +79,11 @@ static int octeon_mmc_probe(struct platform_device *pdev)
> host->acquire_bus = octeon_mmc_acquire_bus;
> host->release_bus = octeon_mmc_release_bus;
> host->int_enable = octeon_mmc_int_enable;
> + if (OCTEON_IS_MODEL(OCTEON_CN6XXX) ||
> + OCTEON_IS_MODEL(OCTEON_CNF7XXX)) {
I understand these macros are already being used for other octeon
drivers. I really don't like it, as it's better solved through device
tree instead using SoC specific APIs.
Moreover we recently added the soc_device_match() API (by Arnd) to
deal with cases exactly like this. In the long run, I recommend you to
convert to this approach.
That said, because we already have other users of these macros, we are
not making it much more worse, so I am going to look through my
fingers this time.
> + host->dmar_fixup = octeon_mmc_dmar_fixup;
> + host->dmar_fixup_done = octeon_mmc_dmar_fixup_done;
> + }
>
> host->sys_freq = octeon_get_io_clock_rate();
>
> --
> 2.9.0.rc0.21.g7777322
>
Kind regards
Uffe
On 10 March 2017 at 14:25, Jan Glauber <[email protected]> wrote:
> Add a platform driver for ThunderX ARM SOCs.
>
> Signed-off-by: Jan Glauber <[email protected]>
> ---
> drivers/mmc/host/Kconfig | 10 ++
> drivers/mmc/host/Makefile | 2 +
> drivers/mmc/host/cavium-mmc.h | 10 +-
> drivers/mmc/host/cavium-pci-thunderx.c | 198 +++++++++++++++++++++++++++++++++
> 4 files changed, 218 insertions(+), 2 deletions(-)
> create mode 100644 drivers/mmc/host/cavium-pci-thunderx.c
>
> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> index 68cc811..3983dee 100644
> --- a/drivers/mmc/host/Kconfig
> +++ b/drivers/mmc/host/Kconfig
> @@ -632,6 +632,16 @@ config MMC_CAVIUM_OCTEON
>
> If unsure, say N.
>
> +config MMC_CAVIUM_THUNDERX
> + tristate "Cavium ThunderX SD/MMC Card Interface support"
> + depends on PCI && 64BIT && (ARM64 || COMPILE_TEST)
> + select GPIO_THUNDERX
Do you really need to select GPIO_THUNDERX? What is the relationship?
Maybe "depends on GPIOLIB" instead?
> + 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_DW
> tristate "Synopsys DesignWare Memory Card Interface"
> depends on HAS_DMA
> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
> index c7f0ccf..0068610 100644
> --- a/drivers/mmc/host/Makefile
> +++ b/drivers/mmc/host/Makefile
> @@ -44,6 +44,8 @@ obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o
> obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o
> octeon-mmc-objs := cavium-mmc.o cavium-pltfm-octeon.o
> obj-$(CONFIG_MMC_CAVIUM_OCTEON) += octeon-mmc.o
> +thunderx-mmc-objs := cavium-mmc.o cavium-pci-thunderx.o
> +obj-$(CONFIG_MMC_CAVIUM_THUNDERX) += thunderx-mmc.o
> obj-$(CONFIG_MMC_DW) += dw_mmc.o
> obj-$(CONFIG_MMC_DW_PLTFM) += dw_mmc-pltfm.o
> obj-$(CONFIG_MMC_DW_EXYNOS) += dw_mmc-exynos.o
> diff --git a/drivers/mmc/host/cavium-mmc.h b/drivers/mmc/host/cavium-mmc.h
> index 4b22432..fb82aee 100644
> --- a/drivers/mmc/host/cavium-mmc.h
> +++ b/drivers/mmc/host/cavium-mmc.h
> @@ -22,8 +22,12 @@
> #define CAVIUM_MAX_MMC 4
>
> /* DMA register addresses */
> -#define MIO_EMM_DMA_CFG(x) (0x00 + x->reg_off_dma)
> -#define MIO_EMM_DMA_ADR(x) (0x08 + x->reg_off_dma)
> +#define MIO_EMM_DMA_CFG(x) (0x20 + x->reg_off_dma)
> +#define MIO_EMM_DMA_ADR(x) (0x28 + x->reg_off_dma)
> +#define MIO_EMM_DMA_INT(x) (0x30 + x->reg_off_dma)
> +#define MIO_EMM_DMA_INT_W1S(x) (0x38 + x->reg_off_dma)
> +#define MIO_EMM_DMA_INT_ENA_W1S(x) (0x40 + x->reg_off_dma)
> +#define MIO_EMM_DMA_INT_ENA_W1C(x) (0x48 + x->reg_off_dma)
>
> /* register addresses */
> #define MIO_EMM_CFG(x) (0x00 + x->reg_off)
> @@ -39,6 +43,8 @@
> #define MIO_EMM_SAMPLE(x) (0x90 + x->reg_off)
> #define MIO_EMM_STS_MASK(x) (0x98 + x->reg_off)
> #define MIO_EMM_RCA(x) (0xa0 + x->reg_off)
> +#define MIO_EMM_INT_EN_SET(x) (0xb0 + x->reg_off)
> +#define MIO_EMM_INT_EN_CLR(x) (0xb8 + x->reg_off)
> #define MIO_EMM_BUF_IDX(x) (0xe0 + x->reg_off)
> #define MIO_EMM_BUF_DAT(x) (0xe8 + x->reg_off)
>
> diff --git a/drivers/mmc/host/cavium-pci-thunderx.c b/drivers/mmc/host/cavium-pci-thunderx.c
> new file mode 100644
> index 0000000..6ad36b4
> --- /dev/null
> +++ b/drivers/mmc/host/cavium-pci-thunderx.c
> @@ -0,0 +1,198 @@
> +/*
> + * 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 <linux/pci.h>
> +#include "cavium-mmc.h"
> +
> +struct platform_device *slot_pdev[2];
Let's not be lazy. We don't want global arrays of platform devices,
whatever the reason.
> +
> +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));
> + if (!host->base)
> + return -EINVAL;
> +
> + /* On ThunderX these are identical */
> + host->dma_base = host->base;
> +
> + host->reg_off = 0x2000;
> + host->reg_off_dma = 0x160;
> +
> + 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(host));
> + writeq(3, host->base + MIO_EMM_DMA_INT_ENA_W1C(host));
> +
> + ret = thunder_mmc_register_interrupts(host, pdev);
> + if (ret)
> + goto error;
> +
> + for_each_child_of_node(node, child_node) {
> + /*
> + * TODO: mmc_of_parse and devm* require one device per slot.
I guess the TODO is about fixing this behavior in the mmc core, such
we can use mmc_of_parse() in more flexible manner. You may want to
remove the "TODO", but please keep the comment.
> + * 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 (!slot_pdev[i])
> + slot_pdev[i] = of_platform_device_create(child_node, NULL,
> + &pdev->dev);
Seems like we should verify that this is a slot node, by checking the
compatible, before creating a platform device for it. No?
> + if (!slot_pdev[i])
> + continue;
> + ret = cvm_mmc_of_slot_probe(&slot_pdev[i]->dev, host);
> + if (ret)
> + goto error;
> + i++;
> + }
> + 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);
> + 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]);
> + platform_device_del(slot_pdev[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);
> +}
> +
> +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,
> +};
> +
> +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
>
Kind regards
Uffe
On Fri, Mar 17, 2017 at 02:35:34PM +0100, Ulf Hansson wrote:
> On 10 March 2017 at 14:25, Jan Glauber <[email protected]> wrote:
> > Add a platform driver for Octeon MIPS SOCs.
> >
> > Signed-off-by: Jan Glauber <[email protected]>
> > Signed-off-by: David Daney <[email protected]>
> > Signed-off-by: Steven J. Hill <[email protected]>
> > ---
> > drivers/mmc/host/Kconfig | 10 ++
> > drivers/mmc/host/Makefile | 2 +
> > drivers/mmc/host/cavium-pltfm-octeon.c | 183 +++++++++++++++++++++++++++++++++
> > 3 files changed, 195 insertions(+)
> > create mode 100644 drivers/mmc/host/cavium-pltfm-octeon.c
>
> Please rename the file to cavium-octeon.c
OK.
> >
> > diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> > index f08691a..68cc811 100644
> > --- a/drivers/mmc/host/Kconfig
> > +++ b/drivers/mmc/host/Kconfig
> > @@ -622,6 +622,16 @@ config SDH_BFIN_MISSING_CMD_PULLUP_WORKAROUND
> > help
> > If you say yes here SD-Cards may work on the EZkit.
> >
> > +config MMC_CAVIUM_OCTEON
> > + tristate "Cavium OCTEON SD/MMC Card Interface support"
> > + depends on CAVIUM_OCTEON_SOC
> > + help
> > + This selects Cavium OCTEON SD/MMC card Interface.
> > + If you have an OCTEON board with a Multimedia Card slot,
> > + say Y or M here.
> > +
> > + If unsure, say N.
> > +
>
> Adding more new cavium variants becomes a bit messy in this approach.
> May I suggest something similar we are using for SDHCI mmc driver.
There will be no more new variants.
> That is:
>
> For the core mmc cavium driver:
> config MMC_CAVIUM (to build cavium-mmc.o)
> I would also appreciate to rename that file to cavium.c
> This also means you need to export the functions you provide from the
> header cavium-mmc.h (rename to cavium.h)
>
> For the octeon variant:
> config MMC_CAVIUM_OCTEON (to build cavium-octeon.o)
> depends on MMC_CAVIUM && CAVIUM_OCTEON_SOC
Why is this better? It creates a module out of the shared code that only
has one single user, the ThunderX or the Octeon driver and they will
never run both at the same time.
Why not just link the shared code to the driver that uses it? Calling
accross modules is also slower, although that doesn't matter here.
--Jan
> > config MMC_DW
> > tristate "Synopsys DesignWare Memory Card Interface"
> > depends on HAS_DMA
> > diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
> > index 6d548c4..c7f0ccf 100644
> > --- a/drivers/mmc/host/Makefile
> > +++ b/drivers/mmc/host/Makefile
> > @@ -42,6 +42,8 @@ obj-$(CONFIG_MMC_SDHI) += sh_mobile_sdhi.o
> > obj-$(CONFIG_MMC_CB710) += cb710-mmc.o
> > obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o
> > obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o
> > +octeon-mmc-objs := cavium-mmc.o cavium-pltfm-octeon.o
> > +obj-$(CONFIG_MMC_CAVIUM_OCTEON) += octeon-mmc.o
>
> By changing according to above, we get two build objects instead of
> two. Nice an clean.
>
> > obj-$(CONFIG_MMC_DW) += dw_mmc.o
> > obj-$(CONFIG_MMC_DW_PLTFM) += dw_mmc-pltfm.o
> > obj-$(CONFIG_MMC_DW_EXYNOS) += dw_mmc-exynos.o
> > diff --git a/drivers/mmc/host/cavium-pltfm-octeon.c b/drivers/mmc/host/cavium-pltfm-octeon.c
> > new file mode 100644
> > index 0000000..e83d143
>
> [...]
>
> Changing the name of the files, may also lead to that you perhaps want
> to change the prefix of the functions.
>
> Otherwise this looks good to me.
>
> Kind regards
> Uffe
On 20 March 2017 at 15:40, Jan Glauber <[email protected]> wrote:
> On Fri, Mar 17, 2017 at 02:35:34PM +0100, Ulf Hansson wrote:
>> On 10 March 2017 at 14:25, Jan Glauber <[email protected]> wrote:
>> > Add a platform driver for Octeon MIPS SOCs.
>> >
>> > Signed-off-by: Jan Glauber <[email protected]>
>> > Signed-off-by: David Daney <[email protected]>
>> > Signed-off-by: Steven J. Hill <[email protected]>
>> > ---
>> > drivers/mmc/host/Kconfig | 10 ++
>> > drivers/mmc/host/Makefile | 2 +
>> > drivers/mmc/host/cavium-pltfm-octeon.c | 183 +++++++++++++++++++++++++++++++++
>> > 3 files changed, 195 insertions(+)
>> > create mode 100644 drivers/mmc/host/cavium-pltfm-octeon.c
>>
>> Please rename the file to cavium-octeon.c
>
> OK.
>
>> >
>> > diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
>> > index f08691a..68cc811 100644
>> > --- a/drivers/mmc/host/Kconfig
>> > +++ b/drivers/mmc/host/Kconfig
>> > @@ -622,6 +622,16 @@ config SDH_BFIN_MISSING_CMD_PULLUP_WORKAROUND
>> > help
>> > If you say yes here SD-Cards may work on the EZkit.
>> >
>> > +config MMC_CAVIUM_OCTEON
>> > + tristate "Cavium OCTEON SD/MMC Card Interface support"
>> > + depends on CAVIUM_OCTEON_SOC
>> > + help
>> > + This selects Cavium OCTEON SD/MMC card Interface.
>> > + If you have an OCTEON board with a Multimedia Card slot,
>> > + say Y or M here.
>> > +
>> > + If unsure, say N.
>> > +
>>
>> Adding more new cavium variants becomes a bit messy in this approach.
>> May I suggest something similar we are using for SDHCI mmc driver.
>
> There will be no more new variants.
>
>> That is:
>>
>> For the core mmc cavium driver:
>> config MMC_CAVIUM (to build cavium-mmc.o)
>> I would also appreciate to rename that file to cavium.c
>> This also means you need to export the functions you provide from the
>> header cavium-mmc.h (rename to cavium.h)
>>
>> For the octeon variant:
>> config MMC_CAVIUM_OCTEON (to build cavium-octeon.o)
>> depends on MMC_CAVIUM && CAVIUM_OCTEON_SOC
>
> Why is this better? It creates a module out of the shared code that only
> has one single user, the ThunderX or the Octeon driver and they will
> never run both at the same time.
>
> Why not just link the shared code to the driver that uses it? Calling
> accross modules is also slower, although that doesn't matter here.
Okay, lets's keep it as is. We can always change it later on, if we
think it's needed.
Kind regards
Uffe
On 03/17/2017 07:13 AM, Ulf Hansson wrote:
> On 10 March 2017 at 14:25, Jan Glauber <[email protected]> wrote:
>> Prevent data corruption on cn6xxx and cnf7xxx.
>> Due to an imperfection in the design of the MMC bus hardware,
>> the 2nd to last cache block of a DMA read must be locked into the L2
>> cache.
>>
[...]
>> +/**
>> + * Unlock a memory region in the L2 cache
>> + *
>> + * @start - start address to unlock
>> + * @len - length to unlock in bytes
>> + */
>> +void l2c_unlock_mem_region(u64 start, u64 len)
>> +{
>> + u64 end;
>> +
>> + /* Round start/end to cache line boundaries */
>> + end = ALIGN(start + len - 1, CVMX_CACHE_LINE_SIZE);
>> + start = ALIGN(start, CVMX_CACHE_LINE_SIZE);
>> +
>> + while (start <= end) {
>> + l2c_unlock_line(start);
>> + start += CVMX_CACHE_LINE_SIZE;
>> + }
>> +}
>> +EXPORT_SYMBOL_GPL(l2c_unlock_mem_region);
>
> It seems like we should be able to implement these functions in the
> octeon mmc driver, instead of having to export some SoC specific APIs.
> You only need to figure out how to find the correct CACHE_LINE_SIZE,
> but that should be possible to fix.
It doesn't matter which source file the code lives in. If you are happy
having it in drivers/mmc/host/cavium-pltfm-octeon.c (or whatever we end
up calling the file), it could be put there.
CVMX_CACHE_LINE_SIZE == 128. It will never change.
>
> My point is really that we should avoid exporting SoC specific APIs
> which shall be called from drivers. This is old fashion.
Some people find it objectionable to see 1-off architecture specific
in-line asm in a driver file, but I agree that putting it as close to
the user as possible makes sense.
David Daney
On Fri, Mar 17, 2017 at 3:13 PM, Ulf Hansson <[email protected]> wrote:
> On 10 March 2017 at 14:25, Jan Glauber <[email protected]> wrote:
>> Prevent data corruption on cn6xxx and cnf7xxx.
>> Due to an imperfection in the design of the MMC bus hardware,
>> + */
>> +void l2c_unlock_mem_region(u64 start, u64 len)
>> +{
>> + u64 end;
>> +
>> + /* Round start/end to cache line boundaries */
>> + end = ALIGN(start + len - 1, CVMX_CACHE_LINE_SIZE);
>> + start = ALIGN(start, CVMX_CACHE_LINE_SIZE);
>> +
>> + while (start <= end) {
>> + l2c_unlock_line(start);
>> + start += CVMX_CACHE_LINE_SIZE;
>> + }
>> +}
>> +EXPORT_SYMBOL_GPL(l2c_unlock_mem_region);
>
> It seems like we should be able to implement these functions in the
> octeon mmc driver, instead of having to export some SoC specific APIs.
> You only need to figure out how to find the correct CACHE_LINE_SIZE,
> but that should be possible to fix.
>
> My point is really that we should avoid exporting SoC specific APIs
> which shall be called from drivers. This is old fashion.
Right, we wouldn't do this on arm64 either, so it's better not to introduce
it for mips now when you want to have an arm64 version next.
You should also try to have every driver enabled for
CONFIG_COMPILE_TEST, which doesn't work with architecture
specific hacks.
Arnd
On Mon, Mar 20, 2017 at 9:45 PM, David Daney <[email protected]> wrote:
> On 03/17/2017 07:13 AM, Ulf Hansson wrote:
>> My point is really that we should avoid exporting SoC specific APIs
>> which shall be called from drivers. This is old fashion.
>
>
> Some people find it objectionable to see 1-off architecture specific in-line
> asm in a driver file, but I agree that putting it as close to the user as
> possible makes sense.
The proper solution might be to create an architecture independent interface
for it, what it is that the function does. Can you explain what the purpose
of locking/unlocking the cache line for MMC is? Is this something that
could be done more generally in the dma_map_ops implementation?
Arnd
On 03/21/2017 01:58 AM, Arnd Bergmann wrote:
> On Mon, Mar 20, 2017 at 9:45 PM, David Daney <[email protected]> wrote:
>> On 03/17/2017 07:13 AM, Ulf Hansson wrote:
>>> My point is really that we should avoid exporting SoC specific APIs
>>> which shall be called from drivers. This is old fashion.
>>
>>
>> Some people find it objectionable to see 1-off architecture specific in-line
>> asm in a driver file, but I agree that putting it as close to the user as
>> possible makes sense.
>
> The proper solution might be to create an architecture independent interface
> for it, what it is that the function does. Can you explain what the purpose
> of locking/unlocking the cache line for MMC is? Is this something that
> could be done more generally in the dma_map_ops implementation?
It is a 1-off erratum workaround that is only needed on fewer than five
models/revisions of a mips64 based SoC family. As such, creating a
general purpose, architecture independent, framework is clearly not the
proper approach. This is a problem that effects only the Cavium MMC bus
hardware block as implemented on a mips64 based SoC. The arm64 based
SoCs do not have this problem, and and future users of the block will
also not contain the erratum. That is what it is.
At a high level, its purpose is to eliminate data corruption when doing
DMA write operations to transfer data into the SD/MMC devices. The
specific details and RTL code for effected SoCs will not be made public.
It should be enough to know that the 2nd-to-last cache block of a
transfer must be locked in the L2 cache to ensure reliable operation on
DMA writes. This is not some empirically derived workaround, but rather
it is based on a well understood root cause of the failure case.
David.
On Tue, Mar 21, 2017 at 4:19 PM, David Daney <[email protected]> wrote:
> On 03/21/2017 01:58 AM, Arnd Bergmann wrote:
>>
>> On Mon, Mar 20, 2017 at 9:45 PM, David Daney <[email protected]>
>> wrote:
>>>
>>> On 03/17/2017 07:13 AM, Ulf Hansson wrote:
>>>>
>>>> My point is really that we should avoid exporting SoC specific APIs
>>>> which shall be called from drivers. This is old fashion.
>>>
>>>
>>>
>>> Some people find it objectionable to see 1-off architecture specific
>>> in-line
>>> asm in a driver file, but I agree that putting it as close to the user as
>>> possible makes sense.
>>
>>
>> The proper solution might be to create an architecture independent
>> interface
>> for it, what it is that the function does. Can you explain what the
>> purpose
>> of locking/unlocking the cache line for MMC is? Is this something that
>> could be done more generally in the dma_map_ops implementation?
>
>
> It is a 1-off erratum workaround that is only needed on fewer than five
> models/revisions of a mips64 based SoC family. As such, creating a general
> purpose, architecture independent, framework is clearly not the proper
> approach.
If this is just for maintaining coherency of the DMA operation inbetween,
then there is already a generic API for that, which the driver calls.
Adding the workaround into octeon_dma_map_sg() would be a way
to abstract the platform erratum from the driver.
Arnd
On 03/21/2017 12:49 PM, Arnd Bergmann wrote:
> On Tue, Mar 21, 2017 at 4:19 PM, David Daney <[email protected]> wrote:
>> On 03/21/2017 01:58 AM, Arnd Bergmann wrote:
>>>
>>> On Mon, Mar 20, 2017 at 9:45 PM, David Daney <[email protected]>
>>> wrote:
>>>>
>>>> On 03/17/2017 07:13 AM, Ulf Hansson wrote:
>>>>>
>>>>> My point is really that we should avoid exporting SoC specific APIs
>>>>> which shall be called from drivers. This is old fashion.
>>>>
>>>>
>>>>
>>>> Some people find it objectionable to see 1-off architecture specific
>>>> in-line
>>>> asm in a driver file, but I agree that putting it as close to the user as
>>>> possible makes sense.
>>>
>>>
>>> The proper solution might be to create an architecture independent
>>> interface
>>> for it, what it is that the function does. Can you explain what the
>>> purpose
>>> of locking/unlocking the cache line for MMC is? Is this something that
>>> could be done more generally in the dma_map_ops implementation?
>>
>>
>> It is a 1-off erratum workaround that is only needed on fewer than five
>> models/revisions of a mips64 based SoC family. As such, creating a general
>> purpose, architecture independent, framework is clearly not the proper
>> approach.
>
> If this is just for maintaining coherency of the DMA operation inbetween,
> then there is already a generic API for that, which the driver calls.
> Adding the workaround into octeon_dma_map_sg() would be a way
> to abstract the platform erratum from the driver.
>
Either I am bad at explaining things, or you are not reading what I wrote.
These are two facts about the bug:
1) The bug has nothing to do with coherency management, so hacking
something into dma_map* is the wrong thing to do.
2) The bug effects exactly one device, so hacking something into common
code that is used by other devices is the wrong thing to do.
Suggesting that we use an alternate set of facts, although an
interesting exercise, doesn't get us closer to answering the question of
which source code file should contain the code.
This is one opinion about the bug:
1) The bug is in the device, not the "platform", so putting the
workaround code in the driver for the device may be the cleanest approach.
David Daney
On Tue, Mar 21, 2017 at 01:22:05PM -0700, David Daney wrote:
> On 03/21/2017 12:49 PM, Arnd Bergmann wrote:
> >On Tue, Mar 21, 2017 at 4:19 PM, David Daney <[email protected]> wrote:
> >>On 03/21/2017 01:58 AM, Arnd Bergmann wrote:
> >>>
> >>>On Mon, Mar 20, 2017 at 9:45 PM, David Daney <[email protected]>
> >>>wrote:
> >>>>
> >>>>On 03/17/2017 07:13 AM, Ulf Hansson wrote:
> >>>>>
> >>>>>My point is really that we should avoid exporting SoC specific APIs
> >>>>>which shall be called from drivers. This is old fashion.
> >>>>
> >>>>
> >>>>
> >>>>Some people find it objectionable to see 1-off architecture specific
> >>>>in-line
> >>>>asm in a driver file, but I agree that putting it as close to the user as
> >>>>possible makes sense.
> >>>
> >>>
> >>>The proper solution might be to create an architecture independent
> >>>interface
> >>>for it, what it is that the function does. Can you explain what the
> >>>purpose
> >>>of locking/unlocking the cache line for MMC is? Is this something that
> >>>could be done more generally in the dma_map_ops implementation?
> >>
> >>
> >>It is a 1-off erratum workaround that is only needed on fewer than five
> >>models/revisions of a mips64 based SoC family. As such, creating a general
> >>purpose, architecture independent, framework is clearly not the proper
> >>approach.
> >
> >If this is just for maintaining coherency of the DMA operation inbetween,
> >then there is already a generic API for that, which the driver calls.
> >Adding the workaround into octeon_dma_map_sg() would be a way
> >to abstract the platform erratum from the driver.
> >
>
> Either I am bad at explaining things, or you are not reading what I wrote.
>
> These are two facts about the bug:
>
> 1) The bug has nothing to do with coherency management, so hacking
> something into dma_map* is the wrong thing to do.
>
> 2) The bug effects exactly one device, so hacking something into
> common code that is used by other devices is the wrong thing to do.
>
> Suggesting that we use an alternate set of facts, although an
> interesting exercise, doesn't get us closer to answering the
> question of which source code file should contain the code.
>
> This is one opinion about the bug:
>
> 1) The bug is in the device, not the "platform", so putting the
> workaround code in the driver for the device may be the cleanest
> approach.
I've moved the code into the octeon driver
(drivers/mmc/host/cavium-octeon.c) and think this is the cleanest
way to do it.
--Jan
> David Daney
On Fri, Mar 17, 2017 at 03:58:26PM +0100, Ulf Hansson wrote:
> On 10 March 2017 at 14:25, Jan Glauber <[email protected]> wrote:
> > Add a platform driver for ThunderX ARM SOCs.
> >
> > Signed-off-by: Jan Glauber <[email protected]>
> > ---
> > drivers/mmc/host/Kconfig | 10 ++
> > drivers/mmc/host/Makefile | 2 +
> > drivers/mmc/host/cavium-mmc.h | 10 +-
> > drivers/mmc/host/cavium-pci-thunderx.c | 198 +++++++++++++++++++++++++++++++++
> > 4 files changed, 218 insertions(+), 2 deletions(-)
> > create mode 100644 drivers/mmc/host/cavium-pci-thunderx.c
> >
> > diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> > index 68cc811..3983dee 100644
> > --- a/drivers/mmc/host/Kconfig
> > +++ b/drivers/mmc/host/Kconfig
> > @@ -632,6 +632,16 @@ config MMC_CAVIUM_OCTEON
> >
> > If unsure, say N.
> >
> > +config MMC_CAVIUM_THUNDERX
> > + tristate "Cavium ThunderX SD/MMC Card Interface support"
> > + depends on PCI && 64BIT && (ARM64 || COMPILE_TEST)
> > + select GPIO_THUNDERX
>
> Do you really need to select GPIO_THUNDERX? What is the relationship?
I don't know much about gpio, but in the end despite all these layers
there must be a gpio set function called doing the writeq on our SOC
to enable/disable the power gpio, right?
GPIO_THUNDERX implements this gpio set function for Cavium's SOC.
> Maybe "depends on GPIOLIB" instead?
>
> > + 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_DW
> > tristate "Synopsys DesignWare Memory Card Interface"
> > depends on HAS_DMA
> > diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
> > index c7f0ccf..0068610 100644
> > --- a/drivers/mmc/host/Makefile
> > +++ b/drivers/mmc/host/Makefile
> > @@ -44,6 +44,8 @@ obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o
> > obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o
> > octeon-mmc-objs := cavium-mmc.o cavium-pltfm-octeon.o
> > obj-$(CONFIG_MMC_CAVIUM_OCTEON) += octeon-mmc.o
> > +thunderx-mmc-objs := cavium-mmc.o cavium-pci-thunderx.o
> > +obj-$(CONFIG_MMC_CAVIUM_THUNDERX) += thunderx-mmc.o
> > obj-$(CONFIG_MMC_DW) += dw_mmc.o
> > obj-$(CONFIG_MMC_DW_PLTFM) += dw_mmc-pltfm.o
> > obj-$(CONFIG_MMC_DW_EXYNOS) += dw_mmc-exynos.o
> > diff --git a/drivers/mmc/host/cavium-mmc.h b/drivers/mmc/host/cavium-mmc.h
> > index 4b22432..fb82aee 100644
> > --- a/drivers/mmc/host/cavium-mmc.h
> > +++ b/drivers/mmc/host/cavium-mmc.h
> > @@ -22,8 +22,12 @@
> > #define CAVIUM_MAX_MMC 4
> >
> > /* DMA register addresses */
> > -#define MIO_EMM_DMA_CFG(x) (0x00 + x->reg_off_dma)
> > -#define MIO_EMM_DMA_ADR(x) (0x08 + x->reg_off_dma)
> > +#define MIO_EMM_DMA_CFG(x) (0x20 + x->reg_off_dma)
> > +#define MIO_EMM_DMA_ADR(x) (0x28 + x->reg_off_dma)
> > +#define MIO_EMM_DMA_INT(x) (0x30 + x->reg_off_dma)
> > +#define MIO_EMM_DMA_INT_W1S(x) (0x38 + x->reg_off_dma)
> > +#define MIO_EMM_DMA_INT_ENA_W1S(x) (0x40 + x->reg_off_dma)
> > +#define MIO_EMM_DMA_INT_ENA_W1C(x) (0x48 + x->reg_off_dma)
> >
> > /* register addresses */
> > #define MIO_EMM_CFG(x) (0x00 + x->reg_off)
> > @@ -39,6 +43,8 @@
> > #define MIO_EMM_SAMPLE(x) (0x90 + x->reg_off)
> > #define MIO_EMM_STS_MASK(x) (0x98 + x->reg_off)
> > #define MIO_EMM_RCA(x) (0xa0 + x->reg_off)
> > +#define MIO_EMM_INT_EN_SET(x) (0xb0 + x->reg_off)
> > +#define MIO_EMM_INT_EN_CLR(x) (0xb8 + x->reg_off)
> > #define MIO_EMM_BUF_IDX(x) (0xe0 + x->reg_off)
> > #define MIO_EMM_BUF_DAT(x) (0xe8 + x->reg_off)
> >
> > diff --git a/drivers/mmc/host/cavium-pci-thunderx.c b/drivers/mmc/host/cavium-pci-thunderx.c
> > new file mode 100644
> > index 0000000..6ad36b4
> > --- /dev/null
> > +++ b/drivers/mmc/host/cavium-pci-thunderx.c
> > @@ -0,0 +1,198 @@
> > +/*
> > + * 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 <linux/pci.h>
> > +#include "cavium-mmc.h"
> > +
> > +struct platform_device *slot_pdev[2];
>
> Let's not be lazy. We don't want global arrays of platform devices,
> whatever the reason.
OK, I'll move this into the host struct.
> > +
> > +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));
> > + if (!host->base)
> > + return -EINVAL;
> > +
> > + /* On ThunderX these are identical */
> > + host->dma_base = host->base;
> > +
> > + host->reg_off = 0x2000;
> > + host->reg_off_dma = 0x160;
> > +
> > + 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(host));
> > + writeq(3, host->base + MIO_EMM_DMA_INT_ENA_W1C(host));
> > +
> > + ret = thunder_mmc_register_interrupts(host, pdev);
> > + if (ret)
> > + goto error;
> > +
> > + for_each_child_of_node(node, child_node) {
> > + /*
> > + * TODO: mmc_of_parse and devm* require one device per slot.
>
> I guess the TODO is about fixing this behavior in the mmc core, such
> we can use mmc_of_parse() in more flexible manner. You may want to
> remove the "TODO", but please keep the comment.
OK.
> > + * 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 (!slot_pdev[i])
> > + slot_pdev[i] = of_platform_device_create(child_node, NULL,
> > + &pdev->dev);
>
> Seems like we should verify that this is a slot node, by checking the
> compatible, before creating a platform device for it. No?
Not sure I understand you correctly. Before creating the platform device
I don't have a device for the slot. Looking at functions I could use to
check the compatible all of these need a device, which I'm just going to
create. Can you point me to a function I can use here?
> > + if (!slot_pdev[i])
> > + continue;
> > + ret = cvm_mmc_of_slot_probe(&slot_pdev[i]->dev, host);
> > + if (ret)
> > + goto error;
> > + i++;
> > + }
> > + 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);
> > + 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]);
> > + platform_device_del(slot_pdev[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);
> > +}
> > +
> > +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,
> > +};
> > +
> > +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
> >
>
> Kind regards
> Uffe
On 23 March 2017 at 09:58, Jan Glauber <[email protected]> wrote:
> On Fri, Mar 17, 2017 at 03:58:26PM +0100, Ulf Hansson wrote:
>> On 10 March 2017 at 14:25, Jan Glauber <[email protected]> wrote:
>> > Add a platform driver for ThunderX ARM SOCs.
>> >
>> > Signed-off-by: Jan Glauber <[email protected]>
>> > ---
>> > drivers/mmc/host/Kconfig | 10 ++
>> > drivers/mmc/host/Makefile | 2 +
>> > drivers/mmc/host/cavium-mmc.h | 10 +-
>> > drivers/mmc/host/cavium-pci-thunderx.c | 198 +++++++++++++++++++++++++++++++++
>> > 4 files changed, 218 insertions(+), 2 deletions(-)
>> > create mode 100644 drivers/mmc/host/cavium-pci-thunderx.c
>> >
>> > diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
>> > index 68cc811..3983dee 100644
>> > --- a/drivers/mmc/host/Kconfig
>> > +++ b/drivers/mmc/host/Kconfig
>> > @@ -632,6 +632,16 @@ config MMC_CAVIUM_OCTEON
>> >
>> > If unsure, say N.
>> >
>> > +config MMC_CAVIUM_THUNDERX
>> > + tristate "Cavium ThunderX SD/MMC Card Interface support"
>> > + depends on PCI && 64BIT && (ARM64 || COMPILE_TEST)
>> > + select GPIO_THUNDERX
>>
>> Do you really need to select GPIO_THUNDERX? What is the relationship?
>
> I don't know much about gpio, but in the end despite all these layers
> there must be a gpio set function called doing the writeq on our SOC
> to enable/disable the power gpio, right?
>
> GPIO_THUNDERX implements this gpio set function for Cavium's SOC.
Got it. However using "select" should be avoided.
Please use "depends on GPIOLIB" instead. The select of "GPIO_THUNDERX"
should be done part of the defconfig or via an SoC specifc Kconfig
file.
[...]
>
>> > + * 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 (!slot_pdev[i])
>> > + slot_pdev[i] = of_platform_device_create(child_node, NULL,
>> > + &pdev->dev);
>>
>> Seems like we should verify that this is a slot node, by checking the
>> compatible, before creating a platform device for it. No?
>
> Not sure I understand you correctly. Before creating the platform device
> I don't have a device for the slot. Looking at functions I could use to
> check the compatible all of these need a device, which I'm just going to
> create. Can you point me to a function I can use here?
if (of_device_is_compatible(child_node, "mmc-slot"))
->create device
[...]
Kind regards
Uffe
On 03/23/2017 02:28 AM, Ulf Hansson wrote:
> On 23 March 2017 at 09:58, Jan Glauber <[email protected]> wrote:
>> On Fri, Mar 17, 2017 at 03:58:26PM +0100, Ulf Hansson wrote:
>>> On 10 March 2017 at 14:25, Jan Glauber <[email protected]> wrote:
>>>> Add a platform driver for ThunderX ARM SOCs.
>>>>
>>>> Signed-off-by: Jan Glauber <[email protected]>
>>>> ---
>>>> drivers/mmc/host/Kconfig | 10 ++
>>>> drivers/mmc/host/Makefile | 2 +
>>>> drivers/mmc/host/cavium-mmc.h | 10 +-
>>>> drivers/mmc/host/cavium-pci-thunderx.c | 198 +++++++++++++++++++++++++++++++++
>>>> 4 files changed, 218 insertions(+), 2 deletions(-)
>>>> create mode 100644 drivers/mmc/host/cavium-pci-thunderx.c
>>>>
>>>> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
>>>> index 68cc811..3983dee 100644
>>>> --- a/drivers/mmc/host/Kconfig
>>>> +++ b/drivers/mmc/host/Kconfig
>>>> @@ -632,6 +632,16 @@ config MMC_CAVIUM_OCTEON
>>>>
>>>> If unsure, say N.
>>>>
>>>> +config MMC_CAVIUM_THUNDERX
>>>> + tristate "Cavium ThunderX SD/MMC Card Interface support"
>>>> + depends on PCI && 64BIT && (ARM64 || COMPILE_TEST)
>>>> + select GPIO_THUNDERX
>>>
>>> Do you really need to select GPIO_THUNDERX? What is the relationship?
>>
>> I don't know much about gpio, but in the end despite all these layers
>> there must be a gpio set function called doing the writeq on our SOC
>> to enable/disable the power gpio, right?
>>
>> GPIO_THUNDERX implements this gpio set function for Cavium's SOC.
>
> Got it. However using "select" should be avoided.
>
> Please use "depends on GPIOLIB" instead. The select of "GPIO_THUNDERX"
> should be done part of the defconfig or via an SoC specifc Kconfig
> file.
>
Thanks, I think that is the cleanest approach.
We may want to add an MSI-X dependency as well, as legacy interrupts are
not supported by the hardware.
David.