diff mbox

[4/4] mmc: host: xilinxps: Merge with Xilinx branch

Message ID 1386923766-22543-5-git-send-email-daniel.sangorrin@toshiba.co.jp (mailing list archive)
State New, archived
Headers show

Commit Message

Daniel Sangorrin Dec. 13, 2013, 8:36 a.m. UTC
From: Michal Simek <michal.simek@xilinx.com>

Merge changes from the Xilinx branch to add support for the SD Card in
ZYNQ boards. (commit 085157e92870cf037d77194d4506bce664b760a3)

Signed-off-by: Daniel Sangorrin <daniel.sangorrin@toshiba.co.jp>
Signed-off-by: Yoshitake Kobayashi <yoshitake.kobayashi@toshiba.co.jp>
---
 drivers/mmc/host/Kconfig             |  13 ++
 drivers/mmc/host/Makefile            |   1 +
 drivers/mmc/host/sdhci-of-xilinxps.c | 240 +++++++++++++++++++++++++++++++++++
 3 files changed, 254 insertions(+)
 create mode 100644 drivers/mmc/host/sdhci-of-xilinxps.c
diff mbox

Patch

diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 9ab8f8d..8e57481 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -130,6 +130,19 @@  config MMC_SDHCI_OF_HLWD
 
 	  If unsure, say N.
 
+config MMC_SDHCI_OF_XILINX_PS
+	tristate "SDHCI OF support for the Xilinx Zynq SDHCI controllers"
+	depends on MMC_SDHCI_PLTFM
+	depends on OF
+	depends on ARCH_ZYNQ
+	help
+	  This selects the Secure Digital Host Controller Interface (SDHCI)
+	  found in the Xilinx Zynq.
+
+	  If you have a controller with this interface, say Y or M here.
+
+	  If unsure, say N.
+
 config MMC_SDHCI_CNS3XXX
 	tristate "SDHCI support on the Cavium Networks CNS3xxx SoC"
 	depends on ARCH_CNS3XXX
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index cd32280..f06990b 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -60,6 +60,7 @@  obj-$(CONFIG_MMC_SDHCI_DOVE)		+= sdhci-dove.o
 obj-$(CONFIG_MMC_SDHCI_TEGRA)		+= sdhci-tegra.o
 obj-$(CONFIG_MMC_SDHCI_OF_ESDHC)	+= sdhci-of-esdhc.o
 obj-$(CONFIG_MMC_SDHCI_OF_HLWD)		+= sdhci-of-hlwd.o
+obj-$(CONFIG_MMC_SDHCI_OF_XILINX_PS)	+= sdhci-of-xilinxps.o
 obj-$(CONFIG_MMC_SDHCI_BCM2835)		+= sdhci-bcm2835.o
 
 ifeq ($(CONFIG_CB710_DEBUG),y)
diff --git a/drivers/mmc/host/sdhci-of-xilinxps.c b/drivers/mmc/host/sdhci-of-xilinxps.c
new file mode 100644
index 0000000..f3a590a
--- /dev/null
+++ b/drivers/mmc/host/sdhci-of-xilinxps.c
@@ -0,0 +1,240 @@ 
+/*
+ * Xilinx Zynq Secure Digital Host Controller Interface.
+ * Copyright (C) 2011 - 2012 Michal Simek <monstr@monstr.eu>
+ * Copyright (c) 2012 Wind River Systems, Inc.
+ *
+ * Based on sdhci-of-esdhc.c
+ *
+ * Copyright (c) 2007 Freescale Semiconductor, Inc.
+ * Copyright (c) 2009 MontaVista Software, Inc.
+ *
+ * Authors: Xiaobo Xie <X.Xie@freescale.com>
+ *	    Anton Vorontsov <avorontsov@ru.mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/mmc/host.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include "sdhci-pltfm.h"
+
+/**
+ * struct xsdhcips
+ * @devclk		Pointer to the peripheral clock
+ * @aperclk		Pointer to the APER clock
+ * @clk_rate_change_nb	Notifier block for clock frequency change callback
+ */
+struct xsdhcips {
+	struct clk		*devclk;
+	struct clk		*aperclk;
+	struct notifier_block	clk_rate_change_nb;
+};
+
+static unsigned int zynq_of_get_max_clock(struct sdhci_host *host)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+
+	return pltfm_host->clock;
+}
+
+static struct sdhci_ops sdhci_zynq_ops = {
+	.get_max_clock = zynq_of_get_max_clock,
+};
+
+static struct sdhci_pltfm_data sdhci_zynq_pdata = {
+	.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
+		SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK,
+	.ops = &sdhci_zynq_ops,
+};
+
+static int xsdhcips_clk_notifier_cb(struct notifier_block *nb,
+		unsigned long event, void *data)
+{
+	switch (event) {
+	case PRE_RATE_CHANGE:
+		/* if a rate change is announced we need to check whether we can
+		 * maintain the current frequency by changing the clock
+		 * dividers. And we may have to suspend operation and return
+		 * after the rate change or its abort
+		 */
+		/* fall through */
+	case POST_RATE_CHANGE:
+		return NOTIFY_OK;
+	case ABORT_RATE_CHANGE:
+	default:
+		return NOTIFY_DONE;
+	}
+}
+
+#ifdef CONFIG_PM_SLEEP
+/**
+ * xsdhcips_suspend - Suspend method for the driver
+ * @dev:	Address of the device structure
+ * Returns 0 on success and error value on error
+ *
+ * Put the device in a low power state.
+ */
+static int xsdhcips_suspend(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct sdhci_host *host = platform_get_drvdata(pdev);
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct xsdhcips *xsdhcips = pltfm_host->priv;
+	int ret;
+
+	ret = sdhci_suspend_host(host);
+	if (ret)
+		return ret;
+
+	clk_disable(xsdhcips->devclk);
+	clk_disable(xsdhcips->aperclk);
+
+	return 0;
+}
+
+/**
+ * xsdhcips_resume - Resume method for the driver
+ * @dev:	Address of the device structure
+ * Returns 0 on success and error value on error
+ *
+ * Resume operation after suspend
+ */
+static int xsdhcips_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct sdhci_host *host = platform_get_drvdata(pdev);
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct xsdhcips *xsdhcips = pltfm_host->priv;
+	int ret;
+
+	ret = clk_enable(xsdhcips->aperclk);
+	if (ret) {
+		dev_err(dev, "Cannot enable APER clock.\n");
+		return ret;
+	}
+
+	ret = clk_enable(xsdhcips->devclk);
+	if (ret) {
+		dev_err(dev, "Cannot enable device clock.\n");
+		clk_disable(xsdhcips->aperclk);
+		return ret;
+	}
+
+	return sdhci_resume_host(host);
+}
+#endif /* ! CONFIG_PM_SLEEP */
+
+static SIMPLE_DEV_PM_OPS(xsdhcips_dev_pm_ops, xsdhcips_suspend,
+			 xsdhcips_resume);
+
+static int sdhci_zynq_probe(struct platform_device *pdev)
+{
+	int ret;
+	struct sdhci_host *host;
+	struct sdhci_pltfm_host *pltfm_host;
+	struct xsdhcips *xsdhcips;
+
+	xsdhcips = devm_kzalloc(&pdev->dev, sizeof(*xsdhcips), GFP_KERNEL);
+	if (!xsdhcips) {
+		dev_err(&pdev->dev, "unable to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	xsdhcips->aperclk = devm_clk_get(&pdev->dev, "aper_clk");
+	if (IS_ERR(xsdhcips->aperclk)) {
+		dev_err(&pdev->dev, "aper_clk clock not found.\n");
+		return PTR_ERR(xsdhcips->aperclk);
+	}
+
+	xsdhcips->devclk = devm_clk_get(&pdev->dev, "ref_clk");
+	if (IS_ERR(xsdhcips->devclk)) {
+		dev_err(&pdev->dev, "ref_clk clock not found.\n");
+		return PTR_ERR(xsdhcips->devclk);
+	}
+
+	ret = clk_prepare_enable(xsdhcips->aperclk);
+	if (ret) {
+		dev_err(&pdev->dev, "Unable to enable APER clock.\n");
+		return ret;
+	}
+
+	ret = clk_prepare_enable(xsdhcips->devclk);
+	if (ret) {
+		dev_err(&pdev->dev, "Unable to enable device clock.\n");
+		goto clk_dis_aper;
+	}
+
+	xsdhcips->clk_rate_change_nb.notifier_call = xsdhcips_clk_notifier_cb;
+	xsdhcips->clk_rate_change_nb.next = NULL;
+	if (clk_notifier_register(xsdhcips->devclk,
+				&xsdhcips->clk_rate_change_nb))
+		dev_warn(&pdev->dev, "Unable to register clock notifier.\n");
+
+
+	ret = sdhci_pltfm_register(pdev, &sdhci_zynq_pdata);
+	if (ret) {
+		dev_err(&pdev->dev, "Platform registration failed\n");
+		goto clk_notif_unreg;
+	}
+
+	host = platform_get_drvdata(pdev);
+	pltfm_host = sdhci_priv(host);
+	pltfm_host->priv = xsdhcips;
+
+	return 0;
+
+clk_notif_unreg:
+	clk_notifier_unregister(xsdhcips->devclk,
+			&xsdhcips->clk_rate_change_nb);
+	clk_disable_unprepare(xsdhcips->devclk);
+clk_dis_aper:
+	clk_disable_unprepare(xsdhcips->aperclk);
+
+	return ret;
+}
+
+static int sdhci_zynq_remove(struct platform_device *pdev)
+{
+	struct sdhci_host *host = platform_get_drvdata(pdev);
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct xsdhcips *xsdhcips = pltfm_host->priv;
+
+	clk_notifier_unregister(xsdhcips->devclk,
+			&xsdhcips->clk_rate_change_nb);
+	clk_disable_unprepare(xsdhcips->devclk);
+	clk_disable_unprepare(xsdhcips->aperclk);
+
+	return sdhci_pltfm_unregister(pdev);
+}
+
+static const struct of_device_id sdhci_zynq_of_match[] = {
+	{ .compatible = "xlnx,ps7-sdhci-1.00.a" },
+	{ .compatible = "generic-sdhci" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, sdhci_zynq_of_match);
+
+static struct platform_driver sdhci_zynq_driver = {
+	.driver = {
+		.name = "sdhci-zynq",
+		.owner = THIS_MODULE,
+		.of_match_table = sdhci_zynq_of_match,
+		.pm = &xsdhcips_dev_pm_ops,
+	},
+	.probe = sdhci_zynq_probe,
+	.remove = sdhci_zynq_remove,
+};
+
+module_platform_driver(sdhci_zynq_driver);
+
+MODULE_DESCRIPTION("Secure Digital Host Controller Interface OF driver");
+MODULE_AUTHOR("Michal Simek <monstr@monstr.eu>, Vlad Lungu <vlad.lungu@windriver.com>");
+MODULE_LICENSE("GPL v2");