ARC: dwmmc: Adding DesignWare MMC driver support for ARC devboards
authorEugeniy Paltsev <eugeniy.paltsev@synopsys.com>
Mon, 25 Feb 2019 15:35:28 +0000 (18:35 +0300)
committerAlexey Brodkin <abrodkin@synopsys.com>
Thu, 18 Apr 2019 06:12:38 +0000 (09:12 +0300)
Add the DM_MMC-compatible DesignWare MMC driver support for Synopsys
ARC devboards. It is created to switch ARC devboards to use DM_MMC.

It required information such as clocks (Bus Interface Unit clock,
Card Interface Unit clock) and SDIO bus width.

Signed-off-by: Eugeniy Paltsev <Eugeniy.Paltsev@synopsys.com>
Signed-off-by: Alexey Brodkin <abrodkin@synopsys.com>
MAINTAINERS
doc/device-tree-bindings/mmc/snps,dw-mmc.txt [new file with mode: 0644]
drivers/mmc/Kconfig
drivers/mmc/Makefile
drivers/mmc/snps_dw_mmc.c [new file with mode: 0644]

index c77abba1e5bebaf8cb06904c9b2006b03ac70f48..aa4b3bc6501e0dfe9bc2aa2cdcff86e31d1a47af 100644 (file)
@@ -74,6 +74,13 @@ L:   uboot-snps-arc@synopsys.com
 F:     doc/device-tree-bindings/gpio/snps,creg-gpio.txt
 F:     drivers/gpio/hsdk-creg-gpio.c
 
 F:     doc/device-tree-bindings/gpio/snps,creg-gpio.txt
 F:     drivers/gpio/hsdk-creg-gpio.c
 
+ARC SYNOPSYS DW MMC EXTENSIONS
+M:     Eugeniy Paltsev <Eugeniy.Paltsev@synopsys.com>
+S:     Maintained
+L:     uboot-snps-arc@synopsys.com
+F:     doc/device-tree-bindings/mmc/snps,dw-mmc.txt
+F:     drivers/mmc/snps_dw_mmc.c
+
 ARM
 M:     Albert Aribaud <albert.u.boot@aribaud.net>
 S:     Maintained
 ARM
 M:     Albert Aribaud <albert.u.boot@aribaud.net>
 S:     Maintained
diff --git a/doc/device-tree-bindings/mmc/snps,dw-mmc.txt b/doc/device-tree-bindings/mmc/snps,dw-mmc.txt
new file mode 100644 (file)
index 0000000..69faefa
--- /dev/null
@@ -0,0 +1,33 @@
+Synopsys Designware Mobile Storage Host Controller extensions
+used in Synopsys ARC devboards
+
+Required Properties:
+
+* compatible: should be - "snps,dw-mshc".
+* bus-width: number of data lines connected to the controller.
+* clocks: from common clock binding: handle to biu and ciu clocks for the
+  bus interface unit clock and the card interface unit clock.
+* clock-names: from common clock binding: Shall be "biu" and "ciu".
+
+Optional properties:
+
+* fifo-depth: The maximum size of the tx/rx fifo's. If this property is not
+  specified, the default value of the fifo size is determined from the
+  controller registers.
+* fifo-mode: Don't use DMA.
+* max-frequency: Maximum operating clock frequency, driver uses 'ciu' clock
+  frequency if it is not set.
+
+Example:
+
+mmc0@f000a000 {
+       compatible = "snps,dw-mshc";
+       reg = <0xf000a000 0x400>;
+
+       bus-width = <4>;
+       fifo-depth = <256>;
+       clocks = <&mmcclk_biu>, <&mmcclk_ciu>;
+       clock-names = "biu", "ciu";
+       max-frequency = <25000000>;
+};
+
index 04a4e7716f277ea062e97b773eefdda42d8404ab..c34dd5d18790f1c005c2d6b4c4a0cf502b64b138 100644 (file)
@@ -222,6 +222,16 @@ config MMC_DW_SOCFPGA
          Synopsys DesignWare Memory Card Interface driver. Select this option
          for platforms based on Altera SOCFPGA.
 
          Synopsys DesignWare Memory Card Interface driver. Select this option
          for platforms based on Altera SOCFPGA.
 
+config MMC_DW_SNPS
+       bool "Extensions for DW Memory Card Interface used in Synopsys ARC devboards"
+       depends on MMC_DW
+       depends on DM_MMC
+       depends on OF_CONTROL
+       depends on CLK
+       help
+         This selects support for Synopsys DesignWare Memory Card Interface driver
+         extensions used in various Synopsys ARC devboards.
+
 config MMC_MESON_GX
        bool "Meson GX EMMC controller support"
        depends on DM_MMC && BLK && ARCH_MESON
 config MMC_MESON_GX
        bool "Meson GX EMMC controller support"
        depends on DM_MMC && BLK && ARCH_MESON
index 7892c468f05cb500249f7c54956249292199306a..0076fc393b8e491dcb29d61684bf6dcd9ce39862 100644 (file)
@@ -24,6 +24,7 @@ obj-$(CONFIG_MMC_DW_EXYNOS)           += exynos_dw_mmc.o
 obj-$(CONFIG_MMC_DW_K3)                        += hi6220_dw_mmc.o
 obj-$(CONFIG_MMC_DW_ROCKCHIP)          += rockchip_dw_mmc.o
 obj-$(CONFIG_MMC_DW_SOCFPGA)           += socfpga_dw_mmc.o
 obj-$(CONFIG_MMC_DW_K3)                        += hi6220_dw_mmc.o
 obj-$(CONFIG_MMC_DW_ROCKCHIP)          += rockchip_dw_mmc.o
 obj-$(CONFIG_MMC_DW_SOCFPGA)           += socfpga_dw_mmc.o
+obj-$(CONFIG_MMC_DW_SNPS)              += snps_dw_mmc.o
 obj-$(CONFIG_FSL_ESDHC) += fsl_esdhc.o
 obj-$(CONFIG_FTSDC010) += ftsdc010_mci.o
 obj-$(CONFIG_GENERIC_ATMEL_MCI) += gen_atmel_mci.o
 obj-$(CONFIG_FSL_ESDHC) += fsl_esdhc.o
 obj-$(CONFIG_FTSDC010) += ftsdc010_mci.o
 obj-$(CONFIG_GENERIC_ATMEL_MCI) += gen_atmel_mci.o
diff --git a/drivers/mmc/snps_dw_mmc.c b/drivers/mmc/snps_dw_mmc.c
new file mode 100644 (file)
index 0000000..5a413f0
--- /dev/null
@@ -0,0 +1,199 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Synopsys DesignWare Multimedia Card Interface driver
+ * extensions used in various Synopsys ARC devboards.
+ *
+ * Copyright (C) 2019 Synopsys
+ * Author: Eugeniy Paltsev <Eugeniy.Paltsev@synopsys.com>
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <dm.h>
+#include <dwmmc.h>
+#include <errno.h>
+#include <fdtdec.h>
+#include <linux/libfdt.h>
+#include <linux/err.h>
+#include <malloc.h>
+
+#define CLOCK_MIN              400000  /*  400 kHz */
+#define FIFO_MIN               8
+#define FIFO_MAX               4096
+
+struct snps_dwmci_plat {
+       struct mmc_config       cfg;
+       struct mmc              mmc;
+};
+
+struct snps_dwmci_priv_data {
+       struct dwmci_host       host;
+       u32                     f_max;
+};
+
+static int snps_dwmmc_clk_setup(struct udevice *dev)
+{
+       struct snps_dwmci_priv_data *priv = dev_get_priv(dev);
+       struct dwmci_host *host = &priv->host;
+
+       struct clk clk_ciu, clk_biu;
+       int ret;
+
+       ret = clk_get_by_name(dev, "ciu", &clk_ciu);
+       if (ret)
+               goto clk_err;
+
+       ret = clk_enable(&clk_ciu);
+       if (ret && ret != -ENOSYS && ret != -ENOTSUPP)
+               goto clk_err_ciu;
+
+       host->bus_hz = clk_get_rate(&clk_ciu);
+       if (host->bus_hz < CLOCK_MIN) {
+               ret = -EINVAL;
+               goto clk_err_ciu_dis;
+       }
+
+       ret = clk_get_by_name(dev, "biu", &clk_biu);
+       if (ret)
+               goto clk_err_ciu_dis;
+
+       ret = clk_enable(&clk_biu);
+       if (ret && ret != -ENOSYS && ret != -ENOTSUPP)
+               goto clk_err_biu;
+
+       return 0;
+
+clk_err_biu:
+       clk_free(&clk_biu);
+clk_err_ciu_dis:
+       clk_disable(&clk_ciu);
+clk_err_ciu:
+       clk_free(&clk_ciu);
+clk_err:
+       dev_err(dev, "failed to setup clocks, ret %d\n", ret);
+
+       return ret;
+}
+
+static int snps_dwmmc_ofdata_to_platdata(struct udevice *dev)
+{
+       struct snps_dwmci_priv_data *priv = dev_get_priv(dev);
+       struct dwmci_host *host = &priv->host;
+       u32 fifo_depth;
+       int ret;
+
+       host->ioaddr = devfdt_get_addr_ptr(dev);
+
+       /*
+        * If fifo-depth is unset don't set fifoth_val - we will try to
+        * auto detect it.
+        */
+       ret = dev_read_u32(dev, "fifo-depth", &fifo_depth);
+       if (!ret) {
+               if (fifo_depth < FIFO_MIN || fifo_depth > FIFO_MAX)
+                       return -EINVAL;
+
+               host->fifoth_val = MSIZE(0x2) |
+                                  RX_WMARK(fifo_depth / 2 - 1) |
+                                  TX_WMARK(fifo_depth / 2);
+       }
+
+       host->buswidth = dev_read_u32_default(dev, "bus-width", 4);
+       if (host->buswidth != 1 && host->buswidth != 4 && host->buswidth != 8)
+               return -EINVAL;
+
+       /*
+        * If max-frequency is unset don't set priv->f_max - we will use
+        * host->bus_hz in probe() instead.
+        */
+       ret = dev_read_u32(dev, "max-frequency", &priv->f_max);
+       if (!ret && priv->f_max < CLOCK_MIN)
+               return -EINVAL;
+
+       host->fifo_mode = dev_read_bool(dev, "fifo-mode");
+       host->name = dev->name;
+       host->dev_index = 0;
+       host->priv = priv;
+
+       return 0;
+}
+
+int snps_dwmmc_getcd(struct udevice *dev)
+{
+       struct snps_dwmci_priv_data *priv = dev_get_priv(dev);
+       struct dwmci_host *host = &priv->host;
+
+       return !(dwmci_readl(host, DWMCI_CDETECT) & 1);
+}
+
+struct dm_mmc_ops snps_dwmci_dm_ops;
+
+static int snps_dwmmc_probe(struct udevice *dev)
+{
+#ifdef CONFIG_BLK
+       struct snps_dwmci_plat *plat = dev_get_platdata(dev);
+#endif
+       struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
+       struct snps_dwmci_priv_data *priv = dev_get_priv(dev);
+       struct dwmci_host *host = &priv->host;
+       unsigned int clock_max;
+       int ret;
+
+       /* Extend generic 'dm_dwmci_ops' with our 'getcd' implementation */
+       memcpy(&snps_dwmci_dm_ops, &dm_dwmci_ops, sizeof(struct dm_mmc_ops));
+       snps_dwmci_dm_ops.get_cd = snps_dwmmc_getcd;
+
+       ret = snps_dwmmc_clk_setup(dev);
+       if (ret)
+               return ret;
+
+       if (!priv->f_max)
+               clock_max = host->bus_hz;
+       else
+               clock_max = min_t(unsigned int, host->bus_hz, priv->f_max);
+
+#ifdef CONFIG_BLK
+       dwmci_setup_cfg(&plat->cfg, host, clock_max, CLOCK_MIN);
+       host->mmc = &plat->mmc;
+#else
+       ret = add_dwmci(host, clock_max, CLOCK_MIN);
+       if (ret)
+               return ret;
+#endif
+       host->mmc->priv = &priv->host;
+       upriv->mmc = host->mmc;
+       host->mmc->dev = dev;
+
+       return dwmci_probe(dev);
+}
+
+static int snps_dwmmc_bind(struct udevice *dev)
+{
+#ifdef CONFIG_BLK
+       struct snps_dwmci_plat *plat = dev_get_platdata(dev);
+       int ret;
+
+       ret = dwmci_bind(dev, &plat->mmc, &plat->cfg);
+       if (ret)
+               return ret;
+#endif
+
+       return 0;
+}
+
+static const struct udevice_id snps_dwmmc_ids[] = {
+       { .compatible = "snps,dw-mshc" },
+       { }
+};
+
+U_BOOT_DRIVER(snps_dwmmc_drv) = {
+       .name                           = "snps_dw_mmc",
+       .id                             = UCLASS_MMC,
+       .of_match                       = snps_dwmmc_ids,
+       .ofdata_to_platdata             = snps_dwmmc_ofdata_to_platdata,
+       .ops                            = &snps_dwmci_dm_ops,
+       .bind                           = snps_dwmmc_bind,
+       .probe                          = snps_dwmmc_probe,
+       .priv_auto_alloc_size           = sizeof(struct snps_dwmci_priv_data),
+       .platdata_auto_alloc_size       = sizeof(struct snps_dwmci_plat),
+};