* [RESEND PATCH 5/9] mmc: dw_mmc-k3: deploay runtime PM facilities
From: Shawn Lin @ 2016-10-09 14:01 UTC (permalink / raw)
To: Jaehoon Chung, Ulf Hansson
Cc: linux-mmc, Doug Anderson, linux-rockchip, Shawn Lin
In-Reply-To: <1476021506-869-1-git-send-email-shawn.lin@rock-chips.com>
Let's migrate it to use runtime PM and remove the system
PM callback from this driver. With this patch, it could
handle system PM properly and could also use runtime PM
if we enable it.
Signed-off-by: Shawn Lin <shawn.lin@rock-chips.com>
---
drivers/mmc/host/dw_mmc-k3.c | 37 ++++++++++++++++---------------------
1 file changed, 16 insertions(+), 21 deletions(-)
diff --git a/drivers/mmc/host/dw_mmc-k3.c b/drivers/mmc/host/dw_mmc-k3.c
index 6247894..99b859d 100644
--- a/drivers/mmc/host/dw_mmc-k3.c
+++ b/drivers/mmc/host/dw_mmc-k3.c
@@ -15,6 +15,7 @@
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
@@ -162,35 +163,29 @@ static int dw_mci_k3_probe(struct platform_device *pdev)
return dw_mci_pltfm_register(pdev, drv_data);
}
-#ifdef CONFIG_PM_SLEEP
-static int dw_mci_k3_suspend(struct device *dev)
+#ifdef CONFIG_PM
+static int dw_mci_k3_runtime_suspend(struct device *dev)
{
struct dw_mci *host = dev_get_drvdata(dev);
- int ret;
-
- ret = dw_mci_suspend(host);
- if (!ret)
- clk_disable_unprepare(host->ciu_clk);
- return ret;
+ return dw_mci_runtime_suspend(host);
}
-static int dw_mci_k3_resume(struct device *dev)
+static int dw_mci_k3_runtime_resume(struct device *dev)
{
struct dw_mci *host = dev_get_drvdata(dev);
- int ret;
- ret = clk_prepare_enable(host->ciu_clk);
- if (ret) {
- dev_err(host->dev, "failed to enable ciu clock\n");
- return ret;
- }
-
- return dw_mci_resume(host);
+ return dw_mci_runtime_resume(host);
}
-#endif /* CONFIG_PM_SLEEP */
-
-static SIMPLE_DEV_PM_OPS(dw_mci_k3_pmops, dw_mci_k3_suspend, dw_mci_k3_resume);
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops dw_mci_k3_dev_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(dw_mci_k3_runtime_suspend,
+ dw_mci_k3_runtime_resume,
+ NULL)
+};
static struct platform_driver dw_mci_k3_pltfm_driver = {
.probe = dw_mci_k3_probe,
@@ -198,7 +193,7 @@ static struct platform_driver dw_mci_k3_pltfm_driver = {
.driver = {
.name = "dwmmc_k3",
.of_match_table = dw_mci_k3_match,
- .pm = &dw_mci_k3_pmops,
+ .pm = &dw_mci_k3_dev_pm_ops,
},
};
--
2.3.7
^ permalink raw reply related
* [RESEND PATCH 4/9] mmc: dw_mmc-rockchip: disable biu clk if possible
From: Shawn Lin @ 2016-10-09 13:58 UTC (permalink / raw)
To: Jaehoon Chung, Ulf Hansson
Cc: linux-mmc, Doug Anderson, linux-rockchip, Shawn Lin
In-Reply-To: <1476021506-869-1-git-send-email-shawn.lin@rock-chips.com>
We could disable biu clk and power-off genpd if gpio
card detect available.
Signed-off-by: Shawn Lin <shawn.lin@rock-chips.com>
---
drivers/mmc/host/dw_mmc-rockchip.c | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/drivers/mmc/host/dw_mmc-rockchip.c b/drivers/mmc/host/dw_mmc-rockchip.c
index 28e0220..7528720 100644
--- a/drivers/mmc/host/dw_mmc-rockchip.c
+++ b/drivers/mmc/host/dw_mmc-rockchip.c
@@ -13,6 +13,7 @@
#include <linux/mmc/host.h>
#include <linux/mmc/dw_mmc.h>
#include <linux/of_address.h>
+#include <linux/mmc/slot-gpio.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
@@ -366,14 +367,29 @@ static int dw_mci_rockchip_remove(struct platform_device *pdev)
static int dw_mci_rockchip_runtime_suspend(struct device *dev)
{
struct dw_mci *host = dev_get_drvdata(dev);
+ int ret;
+
+ ret = dw_mci_runtime_suspend(host);
+ if (ret)
+ return ret;
- return dw_mci_runtime_suspend(host);
+ if (host->cur_slot &&
+ (mmc_can_gpio_cd(host->cur_slot->mmc) ||
+ !mmc_card_is_removable(host->cur_slot->mmc)))
+ clk_disable_unprepare(host->biu_clk);
+
+ return 0;
}
static int dw_mci_rockchip_runtime_resume(struct device *dev)
{
struct dw_mci *host = dev_get_drvdata(dev);
+ if (host->cur_slot &&
+ (mmc_can_gpio_cd(host->cur_slot->mmc) ||
+ !mmc_card_is_removable(host->cur_slot->mmc)))
+ clk_prepare_enable(host->biu_clk);
+
return dw_mci_runtime_resume(host);
}
#endif /* CONFIG_PM */
--
2.3.7
^ permalink raw reply related
* [RESEND PATCH 3/9] mmc: core: expose the capability of gpio card detect
From: Shawn Lin @ 2016-10-09 13:58 UTC (permalink / raw)
To: Jaehoon Chung, Ulf Hansson
Cc: linux-mmc, Doug Anderson, linux-rockchip, Shawn Lin
In-Reply-To: <1476021506-869-1-git-send-email-shawn.lin@rock-chips.com>
Add new helper API mmc_can_gpio_cd for slot-gpio to make
host drivers know whether it supports gpio card detect.
Signed-off-by: Shawn Lin <shawn.lin@rock-chips.com>
---
drivers/mmc/core/slot-gpio.c | 8 ++++++++
include/linux/mmc/slot-gpio.h | 1 +
2 files changed, 9 insertions(+)
diff --git a/drivers/mmc/core/slot-gpio.c b/drivers/mmc/core/slot-gpio.c
index 27117ba..babe591 100644
--- a/drivers/mmc/core/slot-gpio.c
+++ b/drivers/mmc/core/slot-gpio.c
@@ -258,6 +258,14 @@ int mmc_gpiod_request_cd(struct mmc_host *host, const char *con_id,
}
EXPORT_SYMBOL(mmc_gpiod_request_cd);
+bool mmc_can_gpio_cd(struct mmc_host *host)
+{
+ struct mmc_gpio *ctx = host->slot.handler_priv;
+
+ return ctx->cd_gpio ? true : false;
+}
+EXPORT_SYMBOL(mmc_can_gpio_cd);
+
/**
* mmc_gpiod_request_ro - request a gpio descriptor for write protection
* @host: mmc host
diff --git a/include/linux/mmc/slot-gpio.h b/include/linux/mmc/slot-gpio.h
index 3945a8c..a7972cd 100644
--- a/include/linux/mmc/slot-gpio.h
+++ b/include/linux/mmc/slot-gpio.h
@@ -29,5 +29,6 @@ int mmc_gpiod_request_ro(struct mmc_host *host, const char *con_id,
void mmc_gpio_set_cd_isr(struct mmc_host *host,
irqreturn_t (*isr)(int irq, void *dev_id));
void mmc_gpiod_request_cd_irq(struct mmc_host *host);
+bool mmc_can_gpio_cd(struct mmc_host *host);
#endif
--
2.3.7
^ permalink raw reply related
* [RESEND PATCH 2/9] mmc: dw_mmc-rockchip: add runtime PM support
From: Shawn Lin @ 2016-10-09 13:58 UTC (permalink / raw)
To: Jaehoon Chung, Ulf Hansson
Cc: linux-mmc, Doug Anderson, linux-rockchip, Shawn Lin
In-Reply-To: <1476021506-869-1-git-send-email-shawn.lin@rock-chips.com>
This patch adds runtime PM support for dw_mmc-rockchip.
Signed-off-by: Shawn Lin <shawn.lin@rock-chips.com>
---
drivers/mmc/host/dw_mmc-rockchip.c | 57 ++++++++++++++++++++++++++++++++++++--
1 file changed, 54 insertions(+), 3 deletions(-)
diff --git a/drivers/mmc/host/dw_mmc-rockchip.c b/drivers/mmc/host/dw_mmc-rockchip.c
index 25eae35..28e0220 100644
--- a/drivers/mmc/host/dw_mmc-rockchip.c
+++ b/drivers/mmc/host/dw_mmc-rockchip.c
@@ -13,6 +13,7 @@
#include <linux/mmc/host.h>
#include <linux/mmc/dw_mmc.h>
#include <linux/of_address.h>
+#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include "dw_mmc.h"
@@ -325,6 +326,7 @@ static int dw_mci_rockchip_probe(struct platform_device *pdev)
{
const struct dw_mci_drv_data *drv_data;
const struct of_device_id *match;
+ int ret;
if (!pdev->dev.of_node)
return -ENODEV;
@@ -332,16 +334,65 @@ static int dw_mci_rockchip_probe(struct platform_device *pdev)
match = of_match_node(dw_mci_rockchip_match, pdev->dev.of_node);
drv_data = match->data;
- return dw_mci_pltfm_register(pdev, drv_data);
+ pm_runtime_get_noresume(&pdev->dev);
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+ pm_runtime_set_autosuspend_delay(&pdev->dev, 50);
+ pm_runtime_use_autosuspend(&pdev->dev);
+
+ ret = dw_mci_pltfm_register(pdev, drv_data);
+ if (ret) {
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_set_suspended(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
+ return ret;
+ }
+
+ pm_runtime_put_autosuspend(&pdev->dev);
+
+ return 0;
+}
+
+static int dw_mci_rockchip_remove(struct platform_device *pdev)
+{
+ pm_runtime_get_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
+
+ return dw_mci_pltfm_remove(pdev);
}
+#ifdef CONFIG_PM
+static int dw_mci_rockchip_runtime_suspend(struct device *dev)
+{
+ struct dw_mci *host = dev_get_drvdata(dev);
+
+ return dw_mci_runtime_suspend(host);
+}
+
+static int dw_mci_rockchip_runtime_resume(struct device *dev)
+{
+ struct dw_mci *host = dev_get_drvdata(dev);
+
+ return dw_mci_runtime_resume(host);
+}
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops dw_mci_rockchip_dev_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(dw_mci_rockchip_runtime_suspend,
+ dw_mci_rockchip_runtime_resume,
+ NULL)
+};
+
static struct platform_driver dw_mci_rockchip_pltfm_driver = {
.probe = dw_mci_rockchip_probe,
- .remove = dw_mci_pltfm_remove,
+ .remove = dw_mci_rockchip_remove,
.driver = {
.name = "dwmmc_rockchip",
.of_match_table = dw_mci_rockchip_match,
- .pm = &dw_mci_pltfm_pmops,
+ .pm = &dw_mci_rockchip_dev_pm_ops,
},
};
--
2.3.7
^ permalink raw reply related
* [RESEND PATCH 1/9] mmc: dw_mmc: add runtime PM callback
From: Shawn Lin @ 2016-10-09 13:58 UTC (permalink / raw)
To: Jaehoon Chung, Ulf Hansson
Cc: linux-mmc, Doug Anderson, linux-rockchip, Shawn Lin
In-Reply-To: <1476021506-869-1-git-send-email-shawn.lin@rock-chips.com>
This patch add dw_mci_runtime_suspend/resume interfaces
and expose it to dw_mci variant driver to support runtime
PM.
Signed-off-by: Shawn Lin <shawn.lin@rock-chips.com>
---
drivers/mmc/host/dw_mmc.c | 30 ++++++++++++++++++++++++++++--
drivers/mmc/host/dw_mmc.h | 4 +++-
2 files changed, 31 insertions(+), 3 deletions(-)
diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c
index 4fcbc40..c5ef263 100644
--- a/drivers/mmc/host/dw_mmc.c
+++ b/drivers/mmc/host/dw_mmc.c
@@ -3266,7 +3266,7 @@ EXPORT_SYMBOL(dw_mci_remove);
-#ifdef CONFIG_PM_SLEEP
+#ifdef CONFIG_PM
/*
* TODO: we should probably disable the clock to the card in the suspend path.
*/
@@ -3324,7 +3324,33 @@ int dw_mci_resume(struct dw_mci *host)
return 0;
}
EXPORT_SYMBOL(dw_mci_resume);
-#endif /* CONFIG_PM_SLEEP */
+
+int dw_mci_runtime_suspend(struct dw_mci *host)
+{
+ int err = 0;
+
+ err = dw_mci_suspend(host);
+ if (err)
+ return err;
+
+ clk_disable_unprepare(host->ciu_clk);
+
+ return err;
+}
+EXPORT_SYMBOL(dw_mci_runtime_suspend);
+
+int dw_mci_runtime_resume(struct dw_mci *host)
+{
+ int ret = 0;
+
+ ret = clk_prepare_enable(host->ciu_clk);
+ if (ret)
+ return ret;
+
+ return dw_mci_resume(host);
+}
+EXPORT_SYMBOL(dw_mci_runtime_resume);
+#endif /* CONFIG_PM */
static int __init dw_mci_init(void)
{
diff --git a/drivers/mmc/host/dw_mmc.h b/drivers/mmc/host/dw_mmc.h
index e8cd2de..baa7261 100644
--- a/drivers/mmc/host/dw_mmc.h
+++ b/drivers/mmc/host/dw_mmc.h
@@ -234,9 +234,11 @@
extern int dw_mci_probe(struct dw_mci *host);
extern void dw_mci_remove(struct dw_mci *host);
-#ifdef CONFIG_PM_SLEEP
+#ifdef CONFIG_PM
extern int dw_mci_suspend(struct dw_mci *host);
extern int dw_mci_resume(struct dw_mci *host);
+extern int dw_mci_runtime_suspend(struct dw_mci *host);
+extern int dw_mci_runtime_resume(struct dw_mci *host);
#endif
/**
--
2.3.7
^ permalink raw reply related
* [RESEND PATCH 0/9] Init runtime PM support for dw_mmc
From: Shawn Lin @ 2016-10-09 13:58 UTC (permalink / raw)
To: Jaehoon Chung, Ulf Hansson
Cc: linux-mmc, Doug Anderson, linux-rockchip, Shawn Lin
Hi Jaehoon and Ulf,
This patch is gonna support runtime PM for dw_mmc.
It could support to disable ciu_clk by default and disable
biu_clk if the devices are non-removeable, or removeable
with gpio-base card detect.
Then I remove the system PM since the runtime PM actually
does the same thing as it. So I help migrate the dw_mmc variant
drivers to use runtime PM pairs and pm_runtime_force_*. Note
that I only enable runtime PM for dw_mmc-rockchip as I will
leave the decision to the owners of the corresponding drivers.
I just tested it on my RK3288 platform with linux-next to make
the runtime PM and system PM work fine for my emmc, sd card and
sdio. But I don't have hardware to help test other variant drivers.
But in theory it should work fine as I mentioned that the runtime
PM does the same thing as system PM except for disabling ciu_clk
aggressively which should not be related to the variant hosts.
As you could see that I just extend the slot-gpio a bit, so the
ideal way is Ulf could pick them up with Jaehoon's ack. :)
Shawn Lin (9):
mmc: dw_mmc: add runtime PM callback
mmc: dw_mmc-rockchip: add runtime PM support
mmc: core: expose the capability of gpio card detect
mmc: dw_mmc-rockchip: disable biu clk if possible
mmc: dw_mmc-k3: deploay runtime PM facilities
mmc: dw_mmc-exynos: deploay runtime PM facilities
mmc: dw_mmc-pci: deploay runtime PM facilities
mmc: dw_mmc-pltfm: deploay runtime PM facilities
mmc: dw_mmc: remove system PM callback
drivers/mmc/core/slot-gpio.c | 8 +++++
drivers/mmc/host/dw_mmc-exynos.c | 21 ++++++-----
drivers/mmc/host/dw_mmc-k3.c | 37 +++++++++----------
drivers/mmc/host/dw_mmc-pci.c | 25 ++++++++-----
drivers/mmc/host/dw_mmc-pltfm.c | 26 ++++++++------
drivers/mmc/host/dw_mmc-rockchip.c | 73 ++++++++++++++++++++++++++++++++++++--
drivers/mmc/host/dw_mmc.c | 24 ++++++-------
drivers/mmc/host/dw_mmc.h | 6 ++--
include/linux/mmc/slot-gpio.h | 1 +
9 files changed, 152 insertions(+), 69 deletions(-)
--
2.3.7
^ permalink raw reply
* [PATCH 9/9] mmc: dw_mmc: remove system PM callback
From: Shawn Lin @ 2016-10-09 13:51 UTC (permalink / raw)
To: Jaehoon Chung, Ulf Hansson
Cc: linux-rockchip-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Shawn Lin,
linux-mmc-u79uwXL29TY76Z2rM5mHXA, Doug Anderson
In-Reply-To: <1476020951-31292-1-git-send-email-shawn.lin-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
Now there are no variant drivers using dw_mci_suspend
and dw_mci_resume, so let's remove it.
Signed-off-by: Shawn Lin <shawn.lin-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
---
drivers/mmc/host/dw_mmc.c | 44 ++++++++------------------------------------
drivers/mmc/host/dw_mmc.h | 2 --
2 files changed, 8 insertions(+), 38 deletions(-)
diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c
index c5ef263..46a14c5 100644
--- a/drivers/mmc/host/dw_mmc.c
+++ b/drivers/mmc/host/dw_mmc.c
@@ -3267,26 +3267,24 @@ EXPORT_SYMBOL(dw_mci_remove);
#ifdef CONFIG_PM
-/*
- * TODO: we should probably disable the clock to the card in the suspend path.
- */
-int dw_mci_suspend(struct dw_mci *host)
+int dw_mci_runtime_suspend(struct dw_mci *host)
{
if (host->use_dma && host->dma_ops->exit)
host->dma_ops->exit(host);
+ clk_disable_unprepare(host->ciu_clk);
+
return 0;
}
-EXPORT_SYMBOL(dw_mci_suspend);
+EXPORT_SYMBOL(dw_mci_runtime_suspend);
-int dw_mci_resume(struct dw_mci *host)
+int dw_mci_runtime_resume(struct dw_mci *host)
{
- int i, ret;
+ int i, ret = 0;
- if (!dw_mci_ctrl_reset(host, SDMMC_CTRL_ALL_RESET_FLAGS)) {
- ret = -ENODEV;
+ ret = clk_prepare_enable(host->ciu_clk);
+ if (ret)
return ret;
- }
if (host->use_dma && host->dma_ops->init)
host->dma_ops->init(host);
@@ -3323,32 +3321,6 @@ int dw_mci_resume(struct dw_mci *host)
return 0;
}
-EXPORT_SYMBOL(dw_mci_resume);
-
-int dw_mci_runtime_suspend(struct dw_mci *host)
-{
- int err = 0;
-
- err = dw_mci_suspend(host);
- if (err)
- return err;
-
- clk_disable_unprepare(host->ciu_clk);
-
- return err;
-}
-EXPORT_SYMBOL(dw_mci_runtime_suspend);
-
-int dw_mci_runtime_resume(struct dw_mci *host)
-{
- int ret = 0;
-
- ret = clk_prepare_enable(host->ciu_clk);
- if (ret)
- return ret;
-
- return dw_mci_resume(host);
-}
EXPORT_SYMBOL(dw_mci_runtime_resume);
#endif /* CONFIG_PM */
diff --git a/drivers/mmc/host/dw_mmc.h b/drivers/mmc/host/dw_mmc.h
index baa7261..b642dbb 100644
--- a/drivers/mmc/host/dw_mmc.h
+++ b/drivers/mmc/host/dw_mmc.h
@@ -235,8 +235,6 @@
extern int dw_mci_probe(struct dw_mci *host);
extern void dw_mci_remove(struct dw_mci *host);
#ifdef CONFIG_PM
-extern int dw_mci_suspend(struct dw_mci *host);
-extern int dw_mci_resume(struct dw_mci *host);
extern int dw_mci_runtime_suspend(struct dw_mci *host);
extern int dw_mci_runtime_resume(struct dw_mci *host);
#endif
--
2.3.7
^ permalink raw reply related
* [PATCH 8/9] mmc: dw_mmc-pltfm: deploay runtime PM facilities
From: Shawn Lin @ 2016-10-09 13:51 UTC (permalink / raw)
To: Jaehoon Chung, Ulf Hansson
Cc: linux-rockchip-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Shawn Lin,
linux-mmc-u79uwXL29TY76Z2rM5mHXA, Doug Anderson
In-Reply-To: <1476020951-31292-1-git-send-email-shawn.lin-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
Let's migrate it to use runtime PM and remove the system
PM callback from this driver. With this patch, it could
handle system PM properly and could also use runtime PM
if we enable it.
Signed-off-by: Shawn Lin <shawn.lin-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
---
drivers/mmc/host/dw_mmc-pltfm.c | 26 +++++++++++++++-----------
1 file changed, 15 insertions(+), 11 deletions(-)
diff --git a/drivers/mmc/host/dw_mmc-pltfm.c b/drivers/mmc/host/dw_mmc-pltfm.c
index c0bb0c7..214e593 100644
--- a/drivers/mmc/host/dw_mmc-pltfm.c
+++ b/drivers/mmc/host/dw_mmc-pltfm.c
@@ -16,6 +16,7 @@
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
@@ -57,26 +58,29 @@ int dw_mci_pltfm_register(struct platform_device *pdev,
}
EXPORT_SYMBOL_GPL(dw_mci_pltfm_register);
-#ifdef CONFIG_PM_SLEEP
-/*
- * TODO: we should probably disable the clock to the card in the suspend path.
- */
-static int dw_mci_pltfm_suspend(struct device *dev)
+#ifdef CONFIG_PM
+static int dw_mci_pltfm_runtime_suspend(struct device *dev)
{
struct dw_mci *host = dev_get_drvdata(dev);
- return dw_mci_suspend(host);
+ return dw_mci_runtime_suspend(host);
}
-static int dw_mci_pltfm_resume(struct device *dev)
+static int dw_mci_pltfm_runtime_resume(struct device *dev)
{
struct dw_mci *host = dev_get_drvdata(dev);
- return dw_mci_resume(host);
+ return dw_mci_runtime_resume(host);
}
-#endif /* CONFIG_PM_SLEEP */
-
-SIMPLE_DEV_PM_OPS(dw_mci_pltfm_pmops, dw_mci_pltfm_suspend, dw_mci_pltfm_resume);
+#endif /* CONFIG_PM */
+
+const struct dev_pm_ops dw_mci_pltfm_pmops = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(dw_mci_pltfm_runtime_suspend,
+ dw_mci_pltfm_runtime_resume,
+ NULL)
+};
EXPORT_SYMBOL_GPL(dw_mci_pltfm_pmops);
static const struct of_device_id dw_mci_pltfm_match[] = {
--
2.3.7
^ permalink raw reply related
* [PATCH 7/9] mmc: dw_mmc-pci: deploay runtime PM facilities
From: Shawn Lin @ 2016-10-09 13:50 UTC (permalink / raw)
To: Jaehoon Chung, Ulf Hansson
Cc: linux-rockchip-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Shawn Lin,
linux-mmc-u79uwXL29TY76Z2rM5mHXA, Doug Anderson
In-Reply-To: <1476020951-31292-1-git-send-email-shawn.lin-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
Let's migrate it to use runtime PM and remove the system
PM callback from this driver. With this patch, it could
handle system PM properly and could also use runtime PM
if we enable it.
Signed-off-by: Shawn Lin <shawn.lin-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
---
drivers/mmc/host/dw_mmc-pci.c | 25 ++++++++++++++++---------
1 file changed, 16 insertions(+), 9 deletions(-)
diff --git a/drivers/mmc/host/dw_mmc-pci.c b/drivers/mmc/host/dw_mmc-pci.c
index 4c69fbd..61789b1 100644
--- a/drivers/mmc/host/dw_mmc-pci.c
+++ b/drivers/mmc/host/dw_mmc-pci.c
@@ -14,6 +14,7 @@
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/pci.h>
+#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
@@ -79,25 +80,31 @@ static void dw_mci_pci_remove(struct pci_dev *pdev)
dw_mci_remove(host);
}
-#ifdef CONFIG_PM_SLEEP
-static int dw_mci_pci_suspend(struct device *dev)
+#ifdef CONFIG_PM
+static int dw_mci_pci_runtime_suspend(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct dw_mci *host = pci_get_drvdata(pdev);
- return dw_mci_suspend(host);
+ return dw_mci_runtime_suspend(host);
}
-static int dw_mci_pci_resume(struct device *dev)
+static int dw_mci_pci_runtime_resume(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct dw_mci *host = pci_get_drvdata(pdev);
- return dw_mci_resume(host);
+ return dw_mci_runtime_resume(host);
}
-#endif /* CONFIG_PM_SLEEP */
-
-static SIMPLE_DEV_PM_OPS(dw_mci_pci_pmops, dw_mci_pci_suspend, dw_mci_pci_resume);
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops dw_mci_pci_dev_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(dw_mci_pci_runtime_suspend,
+ dw_mci_pci_runtime_resume,
+ NULL)
+};
static const struct pci_device_id dw_mci_pci_id[] = {
{ PCI_DEVICE(SYNOPSYS_DW_MCI_VENDOR_ID, SYNOPSYS_DW_MCI_DEVICE_ID) },
@@ -111,7 +118,7 @@ static struct pci_driver dw_mci_pci_driver = {
.probe = dw_mci_pci_probe,
.remove = dw_mci_pci_remove,
.driver = {
- .pm = &dw_mci_pci_pmops
+ .pm = &dw_mci_pci_dev_pm_ops,
},
};
--
2.3.7
^ permalink raw reply related
* [PATCH 6/9] mmc: dw_mmc-exynos: deploay runtime PM facilities
From: Shawn Lin @ 2016-10-09 13:50 UTC (permalink / raw)
To: Jaehoon Chung, Ulf Hansson
Cc: linux-rockchip-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Shawn Lin,
linux-mmc-u79uwXL29TY76Z2rM5mHXA, Doug Anderson
In-Reply-To: <1476020951-31292-1-git-send-email-shawn.lin-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
Let's migrate it to use runtime PM and remove the system
PM callback from this driver. With this patch, it could
handle system PM properly and could also use runtime PM
if we enable it.
Signed-off-by: Shawn Lin <shawn.lin-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
---
drivers/mmc/host/dw_mmc-exynos.c | 21 ++++++++++++---------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/drivers/mmc/host/dw_mmc-exynos.c b/drivers/mmc/host/dw_mmc-exynos.c
index 7ab3d74..189fd38 100644
--- a/drivers/mmc/host/dw_mmc-exynos.c
+++ b/drivers/mmc/host/dw_mmc-exynos.c
@@ -17,6 +17,7 @@
#include <linux/mmc/mmc.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
+#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include "dw_mmc.h"
@@ -161,20 +162,20 @@ static void dw_mci_exynos_set_clksel_timing(struct dw_mci *host, u32 timing)
set_bit(DW_MMC_CARD_NO_USE_HOLD, &host->cur_slot->flags);
}
-#ifdef CONFIG_PM_SLEEP
-static int dw_mci_exynos_suspend(struct device *dev)
+#ifdef CONFIG_PM
+static int dw_mci_exynos_runtime_suspend(struct device *dev)
{
struct dw_mci *host = dev_get_drvdata(dev);
- return dw_mci_suspend(host);
+ return dw_mci_runtime_suspend(host);
}
-static int dw_mci_exynos_resume(struct device *dev)
+static int dw_mci_exynos_runtime_resume(struct device *dev)
{
struct dw_mci *host = dev_get_drvdata(dev);
dw_mci_exynos_config_smu(host);
- return dw_mci_resume(host);
+ return dw_mci_runtime_resume(host);
}
/**
@@ -211,10 +212,8 @@ static int dw_mci_exynos_resume_noirq(struct device *dev)
return 0;
}
#else
-#define dw_mci_exynos_suspend NULL
-#define dw_mci_exynos_resume NULL
#define dw_mci_exynos_resume_noirq NULL
-#endif /* CONFIG_PM_SLEEP */
+#endif /* CONFIG_PM */
static void dw_mci_exynos_config_hs400(struct dw_mci *host, u32 timing)
{
@@ -531,7 +530,11 @@ static int dw_mci_exynos_probe(struct platform_device *pdev)
}
static const struct dev_pm_ops dw_mci_exynos_pmops = {
- SET_SYSTEM_SLEEP_PM_OPS(dw_mci_exynos_suspend, dw_mci_exynos_resume)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(dw_mci_exynos_runtime_suspend,
+ dw_mci_exynos_runtime_resume,
+ NULL)
.resume_noirq = dw_mci_exynos_resume_noirq,
.thaw_noirq = dw_mci_exynos_resume_noirq,
.restore_noirq = dw_mci_exynos_resume_noirq,
--
2.3.7
^ permalink raw reply related
* [PATCH 5/9] mmc: dw_mmc-k3: deploay runtime PM facilities
From: Shawn Lin @ 2016-10-09 13:49 UTC (permalink / raw)
To: Jaehoon Chung, Ulf Hansson
Cc: linux-rockchip-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Shawn Lin,
linux-mmc-u79uwXL29TY76Z2rM5mHXA, Doug Anderson
In-Reply-To: <1476020951-31292-1-git-send-email-shawn.lin-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
Let's migrate it to use runtime PM and remove the system
PM callback from this driver. With this patch, it could
handle system PM properly and could also use runtime PM
if we enable it.
Signed-off-by: Shawn Lin <shawn.lin-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
---
drivers/mmc/host/dw_mmc-k3.c | 37 ++++++++++++++++---------------------
1 file changed, 16 insertions(+), 21 deletions(-)
diff --git a/drivers/mmc/host/dw_mmc-k3.c b/drivers/mmc/host/dw_mmc-k3.c
index 6247894..99b859d 100644
--- a/drivers/mmc/host/dw_mmc-k3.c
+++ b/drivers/mmc/host/dw_mmc-k3.c
@@ -15,6 +15,7 @@
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
@@ -162,35 +163,29 @@ static int dw_mci_k3_probe(struct platform_device *pdev)
return dw_mci_pltfm_register(pdev, drv_data);
}
-#ifdef CONFIG_PM_SLEEP
-static int dw_mci_k3_suspend(struct device *dev)
+#ifdef CONFIG_PM
+static int dw_mci_k3_runtime_suspend(struct device *dev)
{
struct dw_mci *host = dev_get_drvdata(dev);
- int ret;
-
- ret = dw_mci_suspend(host);
- if (!ret)
- clk_disable_unprepare(host->ciu_clk);
- return ret;
+ return dw_mci_runtime_suspend(host);
}
-static int dw_mci_k3_resume(struct device *dev)
+static int dw_mci_k3_runtime_resume(struct device *dev)
{
struct dw_mci *host = dev_get_drvdata(dev);
- int ret;
- ret = clk_prepare_enable(host->ciu_clk);
- if (ret) {
- dev_err(host->dev, "failed to enable ciu clock\n");
- return ret;
- }
-
- return dw_mci_resume(host);
+ return dw_mci_runtime_resume(host);
}
-#endif /* CONFIG_PM_SLEEP */
-
-static SIMPLE_DEV_PM_OPS(dw_mci_k3_pmops, dw_mci_k3_suspend, dw_mci_k3_resume);
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops dw_mci_k3_dev_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(dw_mci_k3_runtime_suspend,
+ dw_mci_k3_runtime_resume,
+ NULL)
+};
static struct platform_driver dw_mci_k3_pltfm_driver = {
.probe = dw_mci_k3_probe,
@@ -198,7 +193,7 @@ static struct platform_driver dw_mci_k3_pltfm_driver = {
.driver = {
.name = "dwmmc_k3",
.of_match_table = dw_mci_k3_match,
- .pm = &dw_mci_k3_pmops,
+ .pm = &dw_mci_k3_dev_pm_ops,
},
};
--
2.3.7
^ permalink raw reply related
* [PATCH 4/9] mmc: dw_mmc-rockchip: disable biu clk if possible
From: Shawn Lin @ 2016-10-09 13:49 UTC (permalink / raw)
To: Jaehoon Chung, Ulf Hansson
Cc: linux-rockchip-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Shawn Lin,
linux-mmc-u79uwXL29TY76Z2rM5mHXA, Doug Anderson
In-Reply-To: <1476020951-31292-1-git-send-email-shawn.lin-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
We could disable biu clk and power-off genpd if gpio
card detect available.
Signed-off-by: Shawn Lin <shawn.lin-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
---
drivers/mmc/host/dw_mmc-rockchip.c | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/drivers/mmc/host/dw_mmc-rockchip.c b/drivers/mmc/host/dw_mmc-rockchip.c
index 28e0220..7528720 100644
--- a/drivers/mmc/host/dw_mmc-rockchip.c
+++ b/drivers/mmc/host/dw_mmc-rockchip.c
@@ -13,6 +13,7 @@
#include <linux/mmc/host.h>
#include <linux/mmc/dw_mmc.h>
#include <linux/of_address.h>
+#include <linux/mmc/slot-gpio.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
@@ -366,14 +367,29 @@ static int dw_mci_rockchip_remove(struct platform_device *pdev)
static int dw_mci_rockchip_runtime_suspend(struct device *dev)
{
struct dw_mci *host = dev_get_drvdata(dev);
+ int ret;
+
+ ret = dw_mci_runtime_suspend(host);
+ if (ret)
+ return ret;
- return dw_mci_runtime_suspend(host);
+ if (host->cur_slot &&
+ (mmc_can_gpio_cd(host->cur_slot->mmc) ||
+ !mmc_card_is_removable(host->cur_slot->mmc)))
+ clk_disable_unprepare(host->biu_clk);
+
+ return 0;
}
static int dw_mci_rockchip_runtime_resume(struct device *dev)
{
struct dw_mci *host = dev_get_drvdata(dev);
+ if (host->cur_slot &&
+ (mmc_can_gpio_cd(host->cur_slot->mmc) ||
+ !mmc_card_is_removable(host->cur_slot->mmc)))
+ clk_prepare_enable(host->biu_clk);
+
return dw_mci_runtime_resume(host);
}
#endif /* CONFIG_PM */
--
2.3.7
^ permalink raw reply related
* [PATCH 3/9] mmc: core: expose the capability of gpio card detect
From: Shawn Lin @ 2016-10-09 13:49 UTC (permalink / raw)
To: Jaehoon Chung, Ulf Hansson
Cc: linux-mmc, Doug Anderson, linux-rockchip, Shawn Lin
In-Reply-To: <1476020951-31292-1-git-send-email-shawn.lin@rock-chips.com>
Add new helper API mmc_can_gpio_cd for slot-gpio to make
host drivers know whether it supports gpio card detect.
Signed-off-by: Shawn Lin <shawn.lin@rock-chips.com>
---
drivers/mmc/core/slot-gpio.c | 8 ++++++++
include/linux/mmc/slot-gpio.h | 1 +
2 files changed, 9 insertions(+)
diff --git a/drivers/mmc/core/slot-gpio.c b/drivers/mmc/core/slot-gpio.c
index 27117ba..babe591 100644
--- a/drivers/mmc/core/slot-gpio.c
+++ b/drivers/mmc/core/slot-gpio.c
@@ -258,6 +258,14 @@ int mmc_gpiod_request_cd(struct mmc_host *host, const char *con_id,
}
EXPORT_SYMBOL(mmc_gpiod_request_cd);
+bool mmc_can_gpio_cd(struct mmc_host *host)
+{
+ struct mmc_gpio *ctx = host->slot.handler_priv;
+
+ return ctx->cd_gpio ? true : false;
+}
+EXPORT_SYMBOL(mmc_can_gpio_cd);
+
/**
* mmc_gpiod_request_ro - request a gpio descriptor for write protection
* @host: mmc host
diff --git a/include/linux/mmc/slot-gpio.h b/include/linux/mmc/slot-gpio.h
index 3945a8c..a7972cd 100644
--- a/include/linux/mmc/slot-gpio.h
+++ b/include/linux/mmc/slot-gpio.h
@@ -29,5 +29,6 @@ int mmc_gpiod_request_ro(struct mmc_host *host, const char *con_id,
void mmc_gpio_set_cd_isr(struct mmc_host *host,
irqreturn_t (*isr)(int irq, void *dev_id));
void mmc_gpiod_request_cd_irq(struct mmc_host *host);
+bool mmc_can_gpio_cd(struct mmc_host *host);
#endif
--
2.3.7
^ permalink raw reply related
* [PATCH 2/9] mmc: dw_mmc-rockchip: add runtime PM support
From: Shawn Lin @ 2016-10-09 13:49 UTC (permalink / raw)
To: Jaehoon Chung, Ulf Hansson
Cc: linux-mmc, Doug Anderson, linux-rockchip, Shawn Lin
In-Reply-To: <1476020951-31292-1-git-send-email-shawn.lin@rock-chips.com>
This patch adds runtime PM support for dw_mmc-rockchip.
Signed-off-by: Shawn Lin <shawn.lin@rock-chips.com>
---
drivers/mmc/host/dw_mmc-rockchip.c | 57 ++++++++++++++++++++++++++++++++++++--
1 file changed, 54 insertions(+), 3 deletions(-)
diff --git a/drivers/mmc/host/dw_mmc-rockchip.c b/drivers/mmc/host/dw_mmc-rockchip.c
index 25eae35..28e0220 100644
--- a/drivers/mmc/host/dw_mmc-rockchip.c
+++ b/drivers/mmc/host/dw_mmc-rockchip.c
@@ -13,6 +13,7 @@
#include <linux/mmc/host.h>
#include <linux/mmc/dw_mmc.h>
#include <linux/of_address.h>
+#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include "dw_mmc.h"
@@ -325,6 +326,7 @@ static int dw_mci_rockchip_probe(struct platform_device *pdev)
{
const struct dw_mci_drv_data *drv_data;
const struct of_device_id *match;
+ int ret;
if (!pdev->dev.of_node)
return -ENODEV;
@@ -332,16 +334,65 @@ static int dw_mci_rockchip_probe(struct platform_device *pdev)
match = of_match_node(dw_mci_rockchip_match, pdev->dev.of_node);
drv_data = match->data;
- return dw_mci_pltfm_register(pdev, drv_data);
+ pm_runtime_get_noresume(&pdev->dev);
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+ pm_runtime_set_autosuspend_delay(&pdev->dev, 50);
+ pm_runtime_use_autosuspend(&pdev->dev);
+
+ ret = dw_mci_pltfm_register(pdev, drv_data);
+ if (ret) {
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_set_suspended(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
+ return ret;
+ }
+
+ pm_runtime_put_autosuspend(&pdev->dev);
+
+ return 0;
+}
+
+static int dw_mci_rockchip_remove(struct platform_device *pdev)
+{
+ pm_runtime_get_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
+
+ return dw_mci_pltfm_remove(pdev);
}
+#ifdef CONFIG_PM
+static int dw_mci_rockchip_runtime_suspend(struct device *dev)
+{
+ struct dw_mci *host = dev_get_drvdata(dev);
+
+ return dw_mci_runtime_suspend(host);
+}
+
+static int dw_mci_rockchip_runtime_resume(struct device *dev)
+{
+ struct dw_mci *host = dev_get_drvdata(dev);
+
+ return dw_mci_runtime_resume(host);
+}
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops dw_mci_rockchip_dev_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(dw_mci_rockchip_runtime_suspend,
+ dw_mci_rockchip_runtime_resume,
+ NULL)
+};
+
static struct platform_driver dw_mci_rockchip_pltfm_driver = {
.probe = dw_mci_rockchip_probe,
- .remove = dw_mci_pltfm_remove,
+ .remove = dw_mci_rockchip_remove,
.driver = {
.name = "dwmmc_rockchip",
.of_match_table = dw_mci_rockchip_match,
- .pm = &dw_mci_pltfm_pmops,
+ .pm = &dw_mci_rockchip_dev_pm_ops,
},
};
--
2.3.7
^ permalink raw reply related
* [PATCH 1/9] mmc: dw_mmc: add runtime PM callback
From: Shawn Lin @ 2016-10-09 13:49 UTC (permalink / raw)
To: Jaehoon Chung, Ulf Hansson
Cc: linux-mmc, Doug Anderson, linux-rockchip, Shawn Lin
This patch add dw_mci_runtime_suspend/resume interfaces
and expose it to dw_mci variant driver to support runtime
PM.
Signed-off-by: Shawn Lin <shawn.lin@rock-chips.com>
---
drivers/mmc/host/dw_mmc.c | 30 ++++++++++++++++++++++++++++--
drivers/mmc/host/dw_mmc.h | 4 +++-
2 files changed, 31 insertions(+), 3 deletions(-)
diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c
index 4fcbc40..c5ef263 100644
--- a/drivers/mmc/host/dw_mmc.c
+++ b/drivers/mmc/host/dw_mmc.c
@@ -3266,7 +3266,7 @@ EXPORT_SYMBOL(dw_mci_remove);
-#ifdef CONFIG_PM_SLEEP
+#ifdef CONFIG_PM
/*
* TODO: we should probably disable the clock to the card in the suspend path.
*/
@@ -3324,7 +3324,33 @@ int dw_mci_resume(struct dw_mci *host)
return 0;
}
EXPORT_SYMBOL(dw_mci_resume);
-#endif /* CONFIG_PM_SLEEP */
+
+int dw_mci_runtime_suspend(struct dw_mci *host)
+{
+ int err = 0;
+
+ err = dw_mci_suspend(host);
+ if (err)
+ return err;
+
+ clk_disable_unprepare(host->ciu_clk);
+
+ return err;
+}
+EXPORT_SYMBOL(dw_mci_runtime_suspend);
+
+int dw_mci_runtime_resume(struct dw_mci *host)
+{
+ int ret = 0;
+
+ ret = clk_prepare_enable(host->ciu_clk);
+ if (ret)
+ return ret;
+
+ return dw_mci_resume(host);
+}
+EXPORT_SYMBOL(dw_mci_runtime_resume);
+#endif /* CONFIG_PM */
static int __init dw_mci_init(void)
{
diff --git a/drivers/mmc/host/dw_mmc.h b/drivers/mmc/host/dw_mmc.h
index e8cd2de..baa7261 100644
--- a/drivers/mmc/host/dw_mmc.h
+++ b/drivers/mmc/host/dw_mmc.h
@@ -234,9 +234,11 @@
extern int dw_mci_probe(struct dw_mci *host);
extern void dw_mci_remove(struct dw_mci *host);
-#ifdef CONFIG_PM_SLEEP
+#ifdef CONFIG_PM
extern int dw_mci_suspend(struct dw_mci *host);
extern int dw_mci_resume(struct dw_mci *host);
+extern int dw_mci_runtime_suspend(struct dw_mci *host);
+extern int dw_mci_runtime_resume(struct dw_mci *host);
#endif
/**
--
2.3.7
^ permalink raw reply related
* Re: [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
From: Shawn Lin @ 2016-10-09 13:34 UTC (permalink / raw)
To: Ziji Hu, Gregory CLEMENT, Ulf Hansson, Adrian Hunter, linux-mmc
Cc: shawn.lin, Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
Rob Herring, devicetree, Thomas Petazzoni, linux-arm-kernel,
Jack(SH) Zhu, Jimmy Xu, Jisheng Zhang, Nadav Haklai, Ryan Gao,
Doug Jones, Shiwu Zhang, Victor Gu, Wei(SOCP) Liu, Wilson Ding,
Xueping Liu, Hilbert Zhang, Liuliu Zhao, Peng Zhu <zhup>
In-Reply-To: <ac786510-74b2-f14c-b2a7-9c65fb07e40f@marvell.com>
在 2016/10/8 17:28, Ziji Hu 写道:
> Hi Shawn,
>
> On 2016/10/8 10:44, Shawn Lin wrote:
>> 在 2016/10/7 23:22, Gregory CLEMENT 写道:
>>> From: Ziji Hu <huziji@marvell.com>
>>>
>>> Marvell Xenon eMMC/SD/SDIO Host Controller contains PHY.
>>> Three types of PHYs are supported.
>>>
>>> Add support to multiple types of PHYs init and configuration.
>>> Add register definitions of PHYs.
>>>
>>> Signed-off-by: Hu Ziji <huziji@marvell.com>
>>> Reviewed-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
>>> Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
>>> ---
>>> MAINTAINERS | 1 +-
>>> drivers/mmc/host/Makefile | 2 +-
>>> drivers/mmc/host/sdhci-xenon-phy.c | 1141 +++++++++++++++++++++++++++++-
>>> drivers/mmc/host/sdhci-xenon-phy.h | 157 ++++-
>>> drivers/mmc/host/sdhci-xenon.c | 4 +-
>>> drivers/mmc/host/sdhci-xenon.h | 17 +-
>>> 6 files changed, 1321 insertions(+), 1 deletion(-)
>>> create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c
>>> create mode 100644 drivers/mmc/host/sdhci-xenon-phy.h
>>>
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index 859420e5dfd3..b5673c2ee5f2 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -7583,6 +7583,7 @@ M: Ziji Hu <huziji@marvell.com>
>>> L: linux-mmc@vger.kernel.org
>>> S: Supported
>>> F: drivers/mmc/host/sdhci-xenon.*
>>> +F: drivers/mmc/host/sdhci-xenon-phy.*
>>
>> drivers/mmc/host/sdhci-xenon* shoube enough
>>
>>> F: Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>>>
>>> MATROX FRAMEBUFFER DRIVER
>>> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
>>> index 75eaf743486c..4f2854556ff7 100644
>>> --- a/drivers/mmc/host/Makefile
>>> +++ b/drivers/mmc/host/Makefile
>>> @@ -82,4 +82,4 @@ ifeq ($(CONFIG_CB710_DEBUG),y)
>>> endif
>>>
>>> obj-$(CONFIG_MMC_SDHCI_XENON) += sdhci-xenon-driver.o
>>> -sdhci-xenon-driver-y += sdhci-xenon.o
>>> +sdhci-xenon-driver-y += sdhci-xenon.o sdhci-xenon-phy.o
>>> diff --git a/drivers/mmc/host/sdhci-xenon-phy.c b/drivers/mmc/host/sdhci-xenon-phy.c
>>> new file mode 100644
>>> index 000000000000..4eb8fea1bec9
>>> --- /dev/null
>>> +++ b/drivers/mmc/host/sdhci-xenon-phy.c
>>
>> Well, it's legit to use phy API and move your phy
>> operations to PHY subsystem. :)
>>
>
> Actually we tried to put the PHY code into Linux PHY framework.
> But it cannot fit in Linux common PHY framework.
>
Indeed, it seems you need much intercation between the phy and host,
but the phy APIs are not so rich. :)
> Our Xenon SDHC PHY register is a part of Xenon SDHC register set.
> Besides, during MMC initialization, MMC sequence has to call several PHY functions to complete timing setting.
> In those PHY setting functions, they have to access SDHC register and know current MMC setting, such as bus width, clock frequency and speed mode.
> As a result, we have to implement PHY under MMC directory.
>
> Thank you.
>
> Best regards,
> Hu Ziji
>
>>> @@ -0,0 +1,1141 @@
>>> +/*
>>> + * PHY support for Xenon SDHC
>>> + *
>>> + * Copyright (C) 2016 Marvell, All Rights Reserved.
>>> + *
>>> + * Author: Hu Ziji <huziji@marvell.com>
>>> + * Date: 2016-8-24
>>> + *
>>> + * 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 version 2.
>>> + */
>>> +
>>> +#include <linux/slab.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/of_address.h>
>>> +#include <linux/mmc/host.h>
>>> +#include <linux/mmc/mmc.h>
>>> +#include <linux/mmc/card.h>
>>> +#include <linux/mmc/sdio.h>
>>> +
>>> +#include "sdhci.h"
>>> +#include "sdhci-pltfm.h"
>>> +#include "sdhci-xenon.h"
>>> +
>>> +static const char * const phy_types[] = {
>>> + "sdh phy",
>>> + "emmc 5.0 phy",
>>> + "emmc 5.1 phy"
>>> +};
>>> +
>>> +enum phy_type_enum {
>>> + SDH_PHY,
>>> + EMMC_5_0_PHY,
>>> + EMMC_5_1_PHY,
>>> + NR_PHY_TYPES
>>> +};
>>> +
>>> +struct soc_pad_ctrl_table {
>>> + const char *soc;
>>> + void (*set_soc_pad)(struct sdhci_host *host,
>>> + unsigned char signal_voltage);
>>> +};
>>> +
>>> +struct soc_pad_ctrl {
>>> + /* Register address of SOC PHY PAD ctrl */
>>> + void __iomem *reg;
>>> + /* SOC PHY PAD ctrl type */
>>> + enum soc_pad_ctrl_type pad_type;
>>> + /* SOC specific operation to set SOC PHY PAD */
>>> + void (*set_soc_pad)(struct sdhci_host *host,
>>> + unsigned char signal_voltage);
>>> +};
>>> +
>>> +static struct xenon_emmc_phy_regs xenon_emmc_5_0_phy_regs = {
>>> + .timing_adj = EMMC_5_0_PHY_TIMING_ADJUST,
>>> + .func_ctrl = EMMC_5_0_PHY_FUNC_CONTROL,
>>> + .pad_ctrl = EMMC_5_0_PHY_PAD_CONTROL,
>>> + .pad_ctrl2 = EMMC_5_0_PHY_PAD_CONTROL2,
>>> + .dll_ctrl = EMMC_5_0_PHY_DLL_CONTROL,
>>> + .logic_timing_adj = EMMC_5_0_PHY_LOGIC_TIMING_ADJUST,
>>> + .delay_mask = EMMC_5_0_PHY_FIXED_DELAY_MASK,
>>> + .dll_update = DLL_UPDATE_STROBE_5_0,
>>> +};
>>> +
>>> +static struct xenon_emmc_phy_regs xenon_emmc_5_1_phy_regs = {
>>> + .timing_adj = EMMC_PHY_TIMING_ADJUST,
>>> + .func_ctrl = EMMC_PHY_FUNC_CONTROL,
>>> + .pad_ctrl = EMMC_PHY_PAD_CONTROL,
>>> + .pad_ctrl2 = EMMC_PHY_PAD_CONTROL2,
>>> + .dll_ctrl = EMMC_PHY_DLL_CONTROL,
>>> + .logic_timing_adj = EMMC_PHY_LOGIC_TIMING_ADJUST,
>>> + .delay_mask = EMMC_PHY_FIXED_DELAY_MASK,
>>> + .dll_update = DLL_UPDATE,
>>> +};
>>> +
>>> +static int xenon_delay_adj_test(struct mmc_card *card);
>>> +
>>> +/*
>>> + * eMMC PHY configuration and operations
>>> + */
>>> +struct emmc_phy_params {
>>> + bool slow_mode;
>>> +
>>> + u8 znr;
>>> + u8 zpr;
>>> +
>>> + /* Nr of consecutive Sampling Points of a Valid Sampling Window */
>>> + u8 nr_tun_times;
>>> + /* Divider for calculating Tuning Step */
>>> + u8 tun_step_divider;
>>> +
>>> + struct soc_pad_ctrl pad_ctrl;
>>> +};
>>> +
>>> +static void xenon_emmc_phy_strobe_delay_adj(struct sdhci_host *host,
>>> + struct mmc_card *card);
>>> +static int xenon_emmc_phy_fix_sampl_delay_adj(struct sdhci_host *host,
>>> + struct mmc_card *card);
>>> +static void xenon_emmc_phy_set(struct sdhci_host *host,
>>> + unsigned char timing);
>>> +static void xenon_emmc_set_soc_pad(struct sdhci_host *host,
>>> + unsigned char signal_voltage);
>>> +
>>> +static const struct xenon_phy_ops emmc_phy_ops = {
>>> + .strobe_delay_adj = xenon_emmc_phy_strobe_delay_adj,
>>> + .fix_sampl_delay_adj = xenon_emmc_phy_fix_sampl_delay_adj,
>>> + .phy_set = xenon_emmc_phy_set,
>>> + .set_soc_pad = xenon_emmc_set_soc_pad,
>>> +};
>>> +
>>> +static int alloc_emmc_phy(struct sdhci_xenon_priv *priv)
>>> +{
>>> + struct emmc_phy_params *params;
>>> +
>>> + params = kzalloc(sizeof(*params), GFP_KERNEL);
>>> + if (!params)
>>> + return -ENOMEM;
>>> +
>>> + priv->phy_params = params;
>>> + priv->phy_ops = &emmc_phy_ops;
>>> + if (priv->phy_type == EMMC_5_0_PHY)
>>> + priv->emmc_phy_regs = &xenon_emmc_5_0_phy_regs;
>>> + else
>>> + priv->emmc_phy_regs = &xenon_emmc_5_1_phy_regs;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int xenon_emmc_phy_init(struct sdhci_host *host)
>>> +{
>>> + u32 reg;
>>> + u32 wait, clock;
>>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
>>> +
>>> + reg = sdhci_readl(host, phy_regs->timing_adj);
>>> + reg |= PHY_INITIALIZAION;
>>> + sdhci_writel(host, reg, phy_regs->timing_adj);
>>> +
>>> + /* Add duration of FC_SYNC_RST */
>>> + wait = ((reg >> FC_SYNC_RST_DURATION_SHIFT) &
>>> + FC_SYNC_RST_DURATION_MASK);
>>> + /* Add interval between FC_SYNC_EN and FC_SYNC_RST */
>>> + wait += ((reg >> FC_SYNC_RST_EN_DURATION_SHIFT) &
>>> + FC_SYNC_RST_EN_DURATION_MASK);
>>> + /* Add duration of asserting FC_SYNC_EN */
>>> + wait += ((reg >> FC_SYNC_EN_DURATION_SHIFT) &
>>> + FC_SYNC_EN_DURATION_MASK);
>>> + /* Add duration of waiting for PHY */
>>> + wait += ((reg >> WAIT_CYCLE_BEFORE_USING_SHIFT) &
>>> + WAIT_CYCLE_BEFORE_USING_MASK);
>>> + /* 4 addtional bus clock and 4 AXI bus clock are required */
>>> + wait += 8;
>>> + wait <<= 20;
>>> +
>>> + clock = host->clock;
>>> + if (!clock)
>>> + /* Use the possibly slowest bus frequency value */
>>> + clock = LOWEST_SDCLK_FREQ;
>>> + /* get the wait time */
>>> + wait /= clock;
>>> + wait++;
>>> + /* wait for host eMMC PHY init completes */
>>> + udelay(wait);
>>> +
>>> + reg = sdhci_readl(host, phy_regs->timing_adj);
>>> + reg &= PHY_INITIALIZAION;
>>> + if (reg) {
>>> + dev_err(mmc_dev(host->mmc), "eMMC PHY init cannot complete after %d us\n",
>>> + wait);
>>> + return -ETIMEDOUT;
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +#define ARMADA_3700_SOC_PAD_1_8V 0x1
>>> +#define ARMADA_3700_SOC_PAD_3_3V 0x0
>>> +
>>> +static void armada_3700_soc_pad_voltage_set(struct sdhci_host *host,
>>> + unsigned char signal_voltage)
>>> +{
>>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> + struct emmc_phy_params *params = priv->phy_params;
>>> +
>>> + if (params->pad_ctrl.pad_type == SOC_PAD_FIXED_1_8V) {
>>> + writel(ARMADA_3700_SOC_PAD_1_8V, params->pad_ctrl.reg);
>>> + } else if (params->pad_ctrl.pad_type == SOC_PAD_SD) {
>>> + if (signal_voltage == MMC_SIGNAL_VOLTAGE_180)
>>> + writel(ARMADA_3700_SOC_PAD_1_8V, params->pad_ctrl.reg);
>>> + else if (signal_voltage == MMC_SIGNAL_VOLTAGE_330)
>>> + writel(ARMADA_3700_SOC_PAD_3_3V, params->pad_ctrl.reg);
>>> + }
>>> +}
>>> +
>>> +static void xenon_emmc_set_soc_pad(struct sdhci_host *host,
>>> + unsigned char signal_voltage)
>>> +{
>>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> + struct emmc_phy_params *params = priv->phy_params;
>>> +
>>> + if (!params->pad_ctrl.reg)
>>> + return;
>>> +
>>> + if (params->pad_ctrl.set_soc_pad)
>>> + params->pad_ctrl.set_soc_pad(host, signal_voltage);
>>> +}
>>> +
>>> +static int emmc_phy_set_fix_sampl_delay(struct sdhci_host *host,
>>> + unsigned int delay,
>>> + bool invert,
>>> + bool delay_90_degree)
>>> +{
>>> + u32 reg;
>>> + unsigned long flags;
>>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
>>> + int ret = 0;
>>> +
>>> + spin_lock_irqsave(&host->lock, flags);
>>> +
>>> + /* Setup Sampling fix delay */
>>> + reg = sdhci_readl(host, SDHC_SLOT_OP_STATUS_CTRL);
>>> + reg &= ~phy_regs->delay_mask;
>>> + reg |= delay & phy_regs->delay_mask;
>>> + sdhci_writel(host, reg, SDHC_SLOT_OP_STATUS_CTRL);
>>> +
>>> + if (priv->phy_type == EMMC_5_0_PHY) {
>>> + /* set 90 degree phase if necessary */
>>> + reg &= ~DELAY_90_DEGREE_MASK_EMMC5;
>>> + reg |= (delay_90_degree << DELAY_90_DEGREE_SHIFT_EMMC5);
>>> + sdhci_writel(host, reg, SDHC_SLOT_OP_STATUS_CTRL);
>>> + }
>>> +
>>> + /* Disable SDCLK */
>>> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
>>> + reg &= ~(SDHCI_CLOCK_CARD_EN | SDHCI_CLOCK_INT_EN);
>>> + sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
>>> +
>>> + udelay(200);
>>> +
>>> + if (priv->phy_type == EMMC_5_1_PHY) {
>>> + /* set 90 degree phase if necessary */
>>> + reg = sdhci_readl(host, EMMC_PHY_FUNC_CONTROL);
>>> + reg &= ~ASYNC_DDRMODE_MASK;
>>> + reg |= (delay_90_degree << ASYNC_DDRMODE_SHIFT);
>>> + sdhci_writel(host, reg, EMMC_PHY_FUNC_CONTROL);
>>> + }
>>> +
>>> + /* Setup Inversion of Sampling edge */
>>> + reg = sdhci_readl(host, phy_regs->timing_adj);
>>> + reg &= ~SAMPL_INV_QSP_PHASE_SELECT;
>>> + reg |= (invert << SAMPL_INV_QSP_PHASE_SELECT_SHIFT);
>>> + sdhci_writel(host, reg, phy_regs->timing_adj);
>>> +
>>> + /* Enable SD internal clock */
>>> + ret = enable_xenon_internal_clk(host);
>>> + if (ret)
>>> + goto out;
>>> +
>>> + /* Enable SDCLK */
>>> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
>>> + reg |= SDHCI_CLOCK_CARD_EN;
>>> + sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
>>> +
>>> + udelay(200);
>>> +
>>> + /*
>>> + * Has to re-initialize eMMC PHY here to active PHY
>>> + * because later get status cmd will be issued.
>>> + */
>>> + ret = xenon_emmc_phy_init(host);
>>> +
>>> +out:
>>> + spin_unlock_irqrestore(&host->lock, flags);
>>> + return ret;
>>> +}
>>> +
>>> +static int emmc_phy_do_fix_sampl_delay(struct sdhci_host *host,
>>> + struct mmc_card *card,
>>> + unsigned int delay,
>>> + bool invert, bool quarter)
>>> +{
>>> + int ret;
>>> +
>>> + emmc_phy_set_fix_sampl_delay(host, delay, invert, quarter);
>>> +
>>> + ret = xenon_delay_adj_test(card);
>>> + if (ret) {
>>> + dev_dbg(mmc_dev(host->mmc),
>>> + "fail when sampling fix delay = %d, phase = %d degree\n",
>>> + delay, invert * 180 + quarter * 90);
>>> + return -1;
>>> + }
>>> + return 0;
>>> +}
>>> +
>>> +static int xenon_emmc_phy_fix_sampl_delay_adj(struct sdhci_host *host,
>>> + struct mmc_card *card)
>>> +{
>>> + enum sampl_fix_delay_phase phase;
>>> + int idx, nr_pair;
>>> + int ret;
>>> + unsigned int delay;
>>> + unsigned int min_delay, max_delay;
>>> + bool invert, quarter;
>>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
>>> + u32 coarse_step, fine_step;
>>> + const enum sampl_fix_delay_phase delay_edge[] = {
>>> + PHASE_0_DEGREE,
>>> + PHASE_180_DEGREE,
>>> + PHASE_90_DEGREE,
>>> + PHASE_270_DEGREE
>>> + };
>>> +
>>> + coarse_step = phy_regs->delay_mask >> 1;
>>> + fine_step = coarse_step >> 2;
>>> +
>>> + nr_pair = ARRAY_SIZE(delay_edge);
>>> +
>>> + for (idx = 0; idx < nr_pair; idx++) {
>>> + phase = delay_edge[idx];
>>> + invert = (phase & 0x2) ? true : false;
>>> + quarter = (phase & 0x1) ? true : false;
>>> +
>>> + /* increase delay value to get fix delay */
>>> + for (min_delay = 0;
>>> + min_delay <= phy_regs->delay_mask;
>>> + min_delay += coarse_step) {
>>> + ret = emmc_phy_do_fix_sampl_delay(host, card, min_delay,
>>> + invert, quarter);
>>> + if (!ret)
>>> + break;
>>> + }
>>> +
>>> + if (ret) {
>>> + dev_dbg(mmc_dev(host->mmc),
>>> + "Fail to set Sampling Fixed Delay with phase = %d degree\n",
>>> + phase * 90);
>>> + continue;
>>> + }
>>> +
>>> + for (max_delay = min_delay + fine_step;
>>> + max_delay < phy_regs->delay_mask;
>>> + max_delay += fine_step) {
>>> + ret = emmc_phy_do_fix_sampl_delay(host, card, max_delay,
>>> + invert, quarter);
>>> + if (ret) {
>>> + max_delay -= fine_step;
>>> + break;
>>> + }
>>> + }
>>> +
>>> + if (!ret) {
>>> + ret = emmc_phy_do_fix_sampl_delay(host, card,
>>> + phy_regs->delay_mask,
>>> + invert, quarter);
>>> + if (!ret)
>>> + max_delay = phy_regs->delay_mask;
>>> + }
>>> +
>>> + /*
>>> + * Sampling Fixed Delay line window should be large enough,
>>> + * thus the sampling point (the middle of the window)
>>> + * can work when environment varies.
>>> + * However, there is no clear conclusion how large the window
>>> + * should be.
>>> + */
>>> + if ((max_delay - min_delay) <=
>>> + EMMC_PHY_FIXED_DELAY_WINDOW_MIN) {
>>> + dev_info(mmc_dev(host->mmc),
>>> + "The window size %d with phase = %d degree is too small\n",
>>> + max_delay - min_delay, phase * 90);
>>> + continue;
>>> + }
>>> +
>>> + delay = (min_delay + max_delay) / 2;
>>> + emmc_phy_set_fix_sampl_delay(host, delay, invert, quarter);
>>> + dev_dbg(mmc_dev(host->mmc),
>>> + "sampling fix delay = %d with phase = %d degree\n",
>>> + delay, phase * 90);
>>> + return 0;
>>> + }
>>> +
>>> + return -EIO;
>>> +}
>>> +
>>> +static int xenon_emmc_phy_enable_dll(struct sdhci_host *host)
>>> +{
>>> + u32 reg;
>>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
>>> + u8 timeout;
>>> +
>>> + if (WARN_ON(host->clock <= MMC_HIGH_52_MAX_DTR))
>>> + return -EINVAL;
>>> +
>>> + reg = sdhci_readl(host, phy_regs->dll_ctrl);
>>> + if (reg & DLL_ENABLE)
>>> + return 0;
>>> +
>>> + /* Enable DLL */
>>> + reg = sdhci_readl(host, phy_regs->dll_ctrl);
>>> + reg |= (DLL_ENABLE | DLL_FAST_LOCK);
>>> +
>>> + /*
>>> + * Set Phase as 90 degree, which is most common value.
>>> + * Might set another value if necessary.
>>> + * The granularity is 1 degree.
>>> + */
>>> + reg &= ~((DLL_PHASE_MASK << DLL_PHSEL0_SHIFT) |
>>> + (DLL_PHASE_MASK << DLL_PHSEL1_SHIFT));
>>> + reg |= ((DLL_PHASE_90_DEGREE << DLL_PHSEL0_SHIFT) |
>>> + (DLL_PHASE_90_DEGREE << DLL_PHSEL1_SHIFT));
>>> +
>>> + reg &= ~DLL_BYPASS_EN;
>>> + reg |= phy_regs->dll_update;
>>> + if (priv->phy_type == EMMC_5_1_PHY)
>>> + reg &= ~DLL_REFCLK_SEL;
>>> + sdhci_writel(host, reg, phy_regs->dll_ctrl);
>>> +
>>> + /* Wait max 32 ms */
>>> + timeout = 32;
>>> + while (!(sdhci_readw(host, SDHC_SLOT_EXT_PRESENT_STATE) & LOCK_STATE)) {
>>> + if (!timeout) {
>>> + dev_err(mmc_dev(host->mmc), "Wait for DLL Lock time-out\n");
>>> + return -ETIMEDOUT;
>>> + }
>>> + timeout--;
>>> + mdelay(1);
>>> + }
>>> + return 0;
>>> +}
>>> +
>>> +static int __emmc_phy_config_tuning(struct sdhci_host *host)
>>> +{
>>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> + struct emmc_phy_params *params = priv->phy_params;
>>> + u32 reg, tuning_step;
>>> + int ret;
>>> + unsigned long flags;
>>> +
>>> + if (WARN_ON(host->clock <= MMC_HIGH_52_MAX_DTR))
>>> + return -EINVAL;
>>> +
>>> + spin_lock_irqsave(&host->lock, flags);
>>> +
>>> + ret = xenon_emmc_phy_enable_dll(host);
>>> + if (ret) {
>>> + spin_unlock_irqrestore(&host->lock, flags);
>>> + return ret;
>>> + }
>>> +
>>> + reg = sdhci_readl(host, SDHC_SLOT_DLL_CUR_DLY_VAL);
>>> + tuning_step = reg / params->tun_step_divider;
>>> + if (unlikely(tuning_step > TUNING_STEP_MASK)) {
>>> + dev_warn(mmc_dev(host->mmc),
>>> + "HS200 TUNING_STEP %d is larger than MAX value\n",
>>> + tuning_step);
>>> + tuning_step = TUNING_STEP_MASK;
>>> + }
>>> +
>>> + reg = sdhci_readl(host, SDHC_SLOT_OP_STATUS_CTRL);
>>> + reg &= ~(TUN_CONSECUTIVE_TIMES_MASK << TUN_CONSECUTIVE_TIMES_SHIFT);
>>> + reg |= (params->nr_tun_times << TUN_CONSECUTIVE_TIMES_SHIFT);
>>> + reg &= ~(TUNING_STEP_MASK << TUNING_STEP_SHIFT);
>>> + reg |= (tuning_step << TUNING_STEP_SHIFT);
>>> + sdhci_writel(host, reg, SDHC_SLOT_OP_STATUS_CTRL);
>>> +
>>> + spin_unlock_irqrestore(&host->lock, flags);
>>> + return 0;
>>> +}
>>> +
>>> +static int xenon_emmc_phy_config_tuning(struct sdhci_host *host)
>>> +{
>>> + return __emmc_phy_config_tuning(host);
>>> +}
>>> +
>>> +static void xenon_emmc_phy_strobe_delay_adj(struct sdhci_host *host,
>>> + struct mmc_card *card)
>>> +{
>>> + u32 reg;
>>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> + unsigned long flags;
>>> +
>>> + if (host->clock <= MMC_HIGH_52_MAX_DTR)
>>> + return;
>>> +
>>> + dev_dbg(mmc_dev(host->mmc), "starts HS400 strobe delay adjustment\n");
>>> +
>>> + spin_lock_irqsave(&host->lock, flags);
>>> +
>>> + xenon_emmc_phy_enable_dll(host);
>>> +
>>> + /* Enable SDHC Data Strobe */
>>> + reg = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
>>> + reg |= ENABLE_DATA_STROBE;
>>> + sdhci_writel(host, reg, SDHC_SLOT_EMMC_CTRL);
>>> +
>>> + /* Set Data Strobe Pull down */
>>> + if (priv->phy_type == EMMC_5_0_PHY) {
>>> + reg = sdhci_readl(host, EMMC_5_0_PHY_PAD_CONTROL);
>>> + reg |= EMMC5_FC_QSP_PD;
>>> + reg &= ~EMMC5_FC_QSP_PU;
>>> + sdhci_writel(host, reg, EMMC_5_0_PHY_PAD_CONTROL);
>>> + } else {
>>> + reg = sdhci_readl(host, EMMC_PHY_PAD_CONTROL1);
>>> + reg |= EMMC5_1_FC_QSP_PD;
>>> + reg &= ~EMMC5_1_FC_QSP_PU;
>>> + sdhci_writel(host, reg, EMMC_PHY_PAD_CONTROL1);
>>> + }
>>> + spin_unlock_irqrestore(&host->lock, flags);
>>> +}
>>> +
>>> +#define LOGIC_TIMING_VALUE 0x00AA8977
>>> +
>>> +static void xenon_emmc_phy_set(struct sdhci_host *host,
>>> + unsigned char timing)
>>> +{
>>> + u32 reg;
>>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> + struct emmc_phy_params *params = priv->phy_params;
>>> + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
>>> + struct mmc_card *card = priv->card_candidate;
>>> + unsigned long flags;
>>> +
>>> + dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting starts\n");
>>> +
>>> + spin_lock_irqsave(&host->lock, flags);
>>> +
>>> + /* Setup pad, set bit[28] and bits[26:24] */
>>> + reg = sdhci_readl(host, phy_regs->pad_ctrl);
>>> + reg |= (FC_DQ_RECEN | FC_CMD_RECEN | FC_QSP_RECEN | OEN_QSN);
>>> + /*
>>> + * All FC_XX_RECEIVCE should be set as CMOS Type
>>> + */
>>> + reg |= FC_ALL_CMOS_RECEIVER;
>>> + sdhci_writel(host, reg, phy_regs->pad_ctrl);
>>> +
>>> + /* Set CMD and DQ Pull Up */
>>> + if (priv->phy_type == EMMC_5_0_PHY) {
>>> + reg = sdhci_readl(host, EMMC_5_0_PHY_PAD_CONTROL);
>>> + reg |= (EMMC5_FC_CMD_PU | EMMC5_FC_DQ_PU);
>>> + reg &= ~(EMMC5_FC_CMD_PD | EMMC5_FC_DQ_PD);
>>> + sdhci_writel(host, reg, EMMC_5_0_PHY_PAD_CONTROL);
>>> + } else {
>>> + reg = sdhci_readl(host, EMMC_PHY_PAD_CONTROL1);
>>> + reg |= (EMMC5_1_FC_CMD_PU | EMMC5_1_FC_DQ_PU);
>>> + reg &= ~(EMMC5_1_FC_CMD_PD | EMMC5_1_FC_DQ_PD);
>>> + sdhci_writel(host, reg, EMMC_PHY_PAD_CONTROL1);
>>> + }
>>> +
>>> + if ((timing == MMC_TIMING_LEGACY) || !card)
>>> + goto phy_init;
>>> +
>>> + /*
>>> + * FIXME: should depends on the specific board timing.
>>> + */
>>> + if ((timing == MMC_TIMING_MMC_HS400) ||
>>> + (timing == MMC_TIMING_MMC_HS200) ||
>>> + (timing == MMC_TIMING_UHS_SDR50) ||
>>> + (timing == MMC_TIMING_UHS_SDR104) ||
>>> + (timing == MMC_TIMING_UHS_DDR50) ||
>>> + (timing == MMC_TIMING_UHS_SDR25) ||
>>> + (timing == MMC_TIMING_MMC_DDR52)) {
>>> + reg = sdhci_readl(host, phy_regs->timing_adj);
>>> + reg &= ~OUTPUT_QSN_PHASE_SELECT;
>>> + sdhci_writel(host, reg, phy_regs->timing_adj);
>>> + }
>>> +
>>> + /*
>>> + * If SDIO card, set SDIO Mode
>>> + * Otherwise, clear SDIO Mode and Slow Mode
>>> + */
>>> + if (mmc_card_sdio(card)) {
>>> + reg = sdhci_readl(host, phy_regs->timing_adj);
>>> + reg |= TIMING_ADJUST_SDIO_MODE;
>>> +
>>> + if ((timing == MMC_TIMING_UHS_SDR25) ||
>>> + (timing == MMC_TIMING_UHS_SDR12) ||
>>> + (timing == MMC_TIMING_SD_HS) ||
>>> + (timing == MMC_TIMING_LEGACY))
>>> + reg |= TIMING_ADJUST_SLOW_MODE;
>>> +
>>> + sdhci_writel(host, reg, phy_regs->timing_adj);
>>> + } else {
>>> + reg = sdhci_readl(host, phy_regs->timing_adj);
>>> + reg &= ~(TIMING_ADJUST_SDIO_MODE | TIMING_ADJUST_SLOW_MODE);
>>> + sdhci_writel(host, reg, phy_regs->timing_adj);
>>> + }
>>> +
>>> + if (((timing == MMC_TIMING_UHS_SDR50) ||
>>> + (timing == MMC_TIMING_UHS_SDR25) ||
>>> + (timing == MMC_TIMING_UHS_SDR12) ||
>>> + (timing == MMC_TIMING_SD_HS) ||
>>> + (timing == MMC_TIMING_MMC_HS) ||
>>> + (timing == MMC_TIMING_LEGACY)) && params->slow_mode) {
>>> + reg = sdhci_readl(host, phy_regs->timing_adj);
>>> + reg |= TIMING_ADJUST_SLOW_MODE;
>>> + sdhci_writel(host, reg, phy_regs->timing_adj);
>>> + }
>>> +
>>> + /*
>>> + * Set preferred ZNR and ZPR value
>>> + * The ZNR and ZPR value vary between different boards.
>>> + * Define them both in sdhci-xenon-emmc-phy.h.
>>> + */
>>> + reg = sdhci_readl(host, phy_regs->pad_ctrl2);
>>> + reg &= ~((ZNR_MASK << ZNR_SHIFT) | ZPR_MASK);
>>> + reg |= ((params->znr << ZNR_SHIFT) | params->zpr);
>>> + sdhci_writel(host, reg, phy_regs->pad_ctrl2);
>>> +
>>> + /*
>>> + * When setting EMMC_PHY_FUNC_CONTROL register,
>>> + * SD clock should be disabled
>>> + */
>>> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
>>> + reg &= ~SDHCI_CLOCK_CARD_EN;
>>> + sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL);
>>> +
>>> + if ((timing == MMC_TIMING_UHS_DDR50) ||
>>> + (timing == MMC_TIMING_MMC_HS400) ||
>>> + (timing == MMC_TIMING_MMC_DDR52)) {
>>> + reg = sdhci_readl(host, phy_regs->func_ctrl);
>>> + reg |= (DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) | CMD_DDR_MODE;
>>> + sdhci_writel(host, reg, phy_regs->func_ctrl);
>>> + }
>>> +
>>> + if (timing == MMC_TIMING_MMC_HS400) {
>>> + reg = sdhci_readl(host, phy_regs->func_ctrl);
>>> + reg &= ~DQ_ASYNC_MODE;
>>> + sdhci_writel(host, reg, phy_regs->func_ctrl);
>>> + }
>>> +
>>> + /* Enable bus clock */
>>> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
>>> + reg |= SDHCI_CLOCK_CARD_EN;
>>> + sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL);
>>> +
>>> + if (timing == MMC_TIMING_MMC_HS400)
>>> + /* Hardware team recommend a value for HS400 */
>>> + sdhci_writel(host, LOGIC_TIMING_VALUE,
>>> + phy_regs->logic_timing_adj);
>>> +
>>> +phy_init:
>>> + xenon_emmc_phy_init(host);
>>> +
>>> + spin_unlock_irqrestore(&host->lock, flags);
>>> +
>>> + dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting completes\n");
>>> +}
>>> +
>>> +static int get_dt_pad_ctrl_data(struct sdhci_host *host,
>>> + struct device_node *np,
>>> + struct emmc_phy_params *params)
>>> +{
>>> + int ret = 0;
>>> + const char *name;
>>> + struct resource iomem;
>>> +
>>> + if (of_device_is_compatible(np, "marvell,armada-3700-sdhci"))
>>> + params->pad_ctrl.set_soc_pad = armada_3700_soc_pad_voltage_set;
>>> + else
>>> + return 0;
>>> +
>>> + if (of_address_to_resource(np, 1, &iomem)) {
>>> + dev_err(mmc_dev(host->mmc), "Unable to find SOC PAD ctrl register address for %s\n",
>>> + np->name);
>>> + return -EINVAL;
>>> + }
>>> +
>>> + params->pad_ctrl.reg = devm_ioremap_resource(mmc_dev(host->mmc),
>>> + &iomem);
>>> + if (IS_ERR(params->pad_ctrl.reg)) {
>>> + dev_err(mmc_dev(host->mmc), "Unable to get SOC PHY PAD ctrl regiser for %s\n",
>>> + np->name);
>>> + return PTR_ERR(params->pad_ctrl.reg);
>>> + }
>>> +
>>> + ret = of_property_read_string(np, "xenon,pad-type", &name);
>>> + if (ret) {
>>> + dev_err(mmc_dev(host->mmc), "Unable to determine SOC PHY PAD ctrl type\n");
>>> + return ret;
>>> + }
>>> + if (!strcmp(name, "sd")) {
>>> + params->pad_ctrl.pad_type = SOC_PAD_SD;
>>> + } else if (!strcmp(name, "fixed-1-8v")) {
>>> + params->pad_ctrl.pad_type = SOC_PAD_FIXED_1_8V;
>>> + } else {
>>> + dev_err(mmc_dev(host->mmc), "Unsupported SOC PHY PAD ctrl type %s\n",
>>> + name);
>>> + return -EINVAL;
>>> + }
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static int emmc_phy_parse_param_dt(struct sdhci_host *host,
>>> + struct device_node *np,
>>> + struct emmc_phy_params *params)
>>> +{
>>> + u32 value;
>>> +
>>> + if (of_property_read_bool(np, "xenon,phy-slow-mode"))
>>> + params->slow_mode = true;
>>> + else
>>> + params->slow_mode = false;
>>> +
>>> + if (!of_property_read_u32(np, "xenon,phy-znr", &value))
>>> + params->znr = value & ZNR_MASK;
>>> + else
>>> + params->znr = ZNR_DEF_VALUE;
>>> +
>>> + if (!of_property_read_u32(np, "xenon,phy-zpr", &value))
>>> + params->zpr = value & ZPR_MASK;
>>> + else
>>> + params->zpr = ZPR_DEF_VALUE;
>>> +
>>> + if (!of_property_read_u32(np, "xenon,phy-nr-tun-times", &value))
>>> + params->nr_tun_times = value & TUN_CONSECUTIVE_TIMES_MASK;
>>> + else
>>> + params->nr_tun_times = TUN_CONSECUTIVE_TIMES;
>>> +
>>> + if (!of_property_read_u32(np, "xenon,phy-tun-step-divider", &value))
>>> + params->tun_step_divider = value & 0xFF;
>>> + else
>>> + params->tun_step_divider = TUNING_STEP_DIVIDER;
>>> +
>>> + return get_dt_pad_ctrl_data(host, np, params);
>>> +}
>>> +
>>> +/*
>>> + * SDH PHY configuration and operations
>>> + */
>>> +static int xenon_sdh_phy_set_fix_sampl_delay(struct sdhci_host *host,
>>> + unsigned int delay, bool invert)
>>> +{
>>> + u32 reg;
>>> + unsigned long flags;
>>> + int ret;
>>> +
>>> + if (invert)
>>> + invert = 0x1;
>>> + else
>>> + invert = 0x0;
>>> +
>>> + spin_lock_irqsave(&host->lock, flags);
>>> +
>>> + /* Disable SDCLK */
>>> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
>>> + reg &= ~(SDHCI_CLOCK_CARD_EN | SDHCI_CLOCK_INT_EN);
>>> + sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
>>> +
>>> + udelay(200);
>>> +
>>> + /* Setup Sampling fix delay */
>>> + reg = sdhci_readl(host, SDHC_SLOT_OP_STATUS_CTRL);
>>> + reg &= ~(SDH_PHY_FIXED_DELAY_MASK |
>>> + (0x1 << FORCE_SEL_INVERSE_CLK_SHIFT));
>>> + reg |= ((delay & SDH_PHY_FIXED_DELAY_MASK) |
>>> + (invert << FORCE_SEL_INVERSE_CLK_SHIFT));
>>> + sdhci_writel(host, reg, SDHC_SLOT_OP_STATUS_CTRL);
>>> +
>>> + /* Enable SD internal clock */
>>> + ret = enable_xenon_internal_clk(host);
>>> +
>>> + /* Enable SDCLK */
>>> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
>>> + reg |= SDHCI_CLOCK_CARD_EN;
>>> + sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
>>> +
>>> + udelay(200);
>>> +
>>> + spin_unlock_irqrestore(&host->lock, flags);
>>> + return ret;
>>> +}
>>> +
>>> +static int sdh_phy_do_fix_sampl_delay(struct sdhci_host *host,
>>> + struct mmc_card *card,
>>> + unsigned int delay, bool invert)
>>> +{
>>> + int ret;
>>> +
>>> + xenon_sdh_phy_set_fix_sampl_delay(host, delay, invert);
>>> +
>>> + ret = xenon_delay_adj_test(card);
>>> + if (ret) {
>>> + dev_dbg(mmc_dev(host->mmc),
>>> + "fail when sampling fix delay = %d, phase = %d degree\n",
>>> + delay, invert * 180);
>>> + return -1;
>>> + }
>>> + return 0;
>>> +}
>>> +
>>> +#define SDH_PHY_COARSE_FIX_DELAY (SDH_PHY_FIXED_DELAY_MASK / 2)
>>> +#define SDH_PHY_FINE_FIX_DELAY (SDH_PHY_COARSE_FIX_DELAY / 4)
>>> +
>>> +static int xenon_sdh_phy_fix_sampl_delay_adj(struct sdhci_host *host,
>>> + struct mmc_card *card)
>>> +{
>>> + u32 reg;
>>> + bool dll_enable = false;
>>> + unsigned int min_delay, max_delay, delay;
>>> + const bool sampl_edge[] = {
>>> + false,
>>> + true,
>>> + };
>>> + int i, nr;
>>> + int ret;
>>> +
>>> + if (host->clock > HIGH_SPEED_MAX_DTR) {
>>> + /* Enable DLL when SDCLK is higher than 50MHz */
>>> + reg = sdhci_readl(host, SDH_PHY_SLOT_DLL_CTRL);
>>> + if (!(reg & SDH_PHY_ENABLE_DLL)) {
>>> + reg |= (SDH_PHY_ENABLE_DLL | SDH_PHY_FAST_LOCK_EN);
>>> + sdhci_writel(host, reg, SDH_PHY_SLOT_DLL_CTRL);
>>> + mdelay(1);
>>> +
>>> + reg = sdhci_readl(host, SDH_PHY_SLOT_DLL_PHASE_SEL);
>>> + reg |= SDH_PHY_DLL_UPDATE_TUNING;
>>> + sdhci_writel(host, reg, SDH_PHY_SLOT_DLL_PHASE_SEL);
>>> + }
>>> + dll_enable = true;
>>> + }
>>> +
>>> + nr = dll_enable ? ARRAY_SIZE(sampl_edge) : 1;
>>> + for (i = 0; i < nr; i++) {
>>> + for (min_delay = 0; min_delay <= SDH_PHY_FIXED_DELAY_MASK;
>>> + min_delay += SDH_PHY_COARSE_FIX_DELAY) {
>>> + ret = sdh_phy_do_fix_sampl_delay(host, card, min_delay,
>>> + sampl_edge[i]);
>>> + if (!ret)
>>> + break;
>>> + }
>>> +
>>> + if (ret) {
>>> + dev_dbg(mmc_dev(host->mmc),
>>> + "Fail to set Fixed Sampling Delay with %s edge\n",
>>> + sampl_edge[i] ? "negative" : "positive");
>>> + continue;
>>> + }
>>> +
>>> + for (max_delay = min_delay + SDH_PHY_FINE_FIX_DELAY;
>>> + max_delay < SDH_PHY_FIXED_DELAY_MASK;
>>> + max_delay += SDH_PHY_FINE_FIX_DELAY) {
>>> + ret = sdh_phy_do_fix_sampl_delay(host, card, max_delay,
>>> + sampl_edge[i]);
>>> + if (ret) {
>>> + max_delay -= SDH_PHY_FINE_FIX_DELAY;
>>> + break;
>>> + }
>>> + }
>>> +
>>> + if (!ret) {
>>> + delay = SDH_PHY_FIXED_DELAY_MASK;
>>> + ret = sdh_phy_do_fix_sampl_delay(host, card, delay,
>>> + sampl_edge[i]);
>>> + if (!ret)
>>> + max_delay = SDH_PHY_FIXED_DELAY_MASK;
>>> + }
>>> +
>>> + if ((max_delay - min_delay) <= SDH_PHY_FIXED_DELAY_WINDOW_MIN) {
>>> + dev_info(mmc_dev(host->mmc),
>>> + "The window size %d with %s edge is too small\n",
>>> + max_delay - min_delay,
>>> + sampl_edge[i] ? "negative" : "positive");
>>> + continue;
>>> + }
>>> +
>>> + delay = (min_delay + max_delay) / 2;
>>> + xenon_sdh_phy_set_fix_sampl_delay(host, delay, sampl_edge[i]);
>>> + dev_dbg(mmc_dev(host->mmc), "sampling fix delay = %d with %s edge\n",
>>> + delay, sampl_edge[i] ? "negative" : "positive");
>>> + return 0;
>>> + }
>>> + return -EIO;
>>> +}
>>> +
>>> +static const struct xenon_phy_ops sdh_phy_ops = {
>>> + .fix_sampl_delay_adj = xenon_sdh_phy_fix_sampl_delay_adj,
>>> +};
>>> +
>>> +static int alloc_sdh_phy(struct sdhci_xenon_priv *priv)
>>> +{
>>> + priv->phy_params = NULL;
>>> + priv->phy_ops = &sdh_phy_ops;
>>> + return 0;
>>> +}
>>> +
>>> +/*
>>> + * Common functions for all PHYs
>>> + */
>>> +void xenon_soc_pad_ctrl(struct sdhci_host *host,
>>> + unsigned char signal_voltage)
>>> +{
>>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> +
>>> + if (priv->phy_ops->set_soc_pad)
>>> + priv->phy_ops->set_soc_pad(host, signal_voltage);
>>> +}
>>> +
>>> +static int __xenon_emmc_delay_adj_test(struct mmc_card *card)
>>> +{
>>> + int err;
>>> + u8 *ext_csd = NULL;
>>> +
>>> + err = mmc_get_ext_csd(card, &ext_csd);
>>> + kfree(ext_csd);
>>> +
>>> + return err;
>>> +}
>>> +
>>> +static int __xenon_sdio_delay_adj_test(struct mmc_card *card)
>>> +{
>>> + struct mmc_command cmd = {0};
>>> + int err;
>>> +
>>> + cmd.opcode = SD_IO_RW_DIRECT;
>>> + cmd.flags = MMC_RSP_R5 | MMC_CMD_AC;
>>> +
>>> + err = mmc_wait_for_cmd(card->host, &cmd, 0);
>>> + if (err)
>>> + return err;
>>> +
>>> + if (cmd.resp[0] & R5_ERROR)
>>> + return -EIO;
>>> + if (cmd.resp[0] & R5_FUNCTION_NUMBER)
>>> + return -EINVAL;
>>> + if (cmd.resp[0] & R5_OUT_OF_RANGE)
>>> + return -ERANGE;
>>> + return 0;
>>> +}
>>> +
>>> +static int __xenon_sd_delay_adj_test(struct mmc_card *card)
>>> +{
>>> + struct mmc_command cmd = {0};
>>> + int err;
>>> +
>>> + cmd.opcode = MMC_SEND_STATUS;
>>> + cmd.arg = card->rca << 16;
>>> + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
>>> +
>>> + err = mmc_wait_for_cmd(card->host, &cmd, 0);
>>> + return err;
>>> +}
>>> +
>>> +static int xenon_delay_adj_test(struct mmc_card *card)
>>> +{
>>> + WARN_ON(!card);
>>> + WARN_ON(!card->host);
>>> +
>>> + if (mmc_card_mmc(card))
>>> + return __xenon_emmc_delay_adj_test(card);
>>> + else if (mmc_card_sd(card))
>>> + return __xenon_sd_delay_adj_test(card);
>>> + else if (mmc_card_sdio(card))
>>> + return __xenon_sdio_delay_adj_test(card);
>>> + else
>>> + return -EINVAL;
>>> +}
>>> +
>>> +static void xenon_phy_set(struct sdhci_host *host, unsigned char timing)
>>> +{
>>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> +
>>> + if (priv->phy_ops->phy_set)
>>> + priv->phy_ops->phy_set(host, timing);
>>> +}
>>> +
>>> +static void xenon_hs400_strobe_delay_adj(struct sdhci_host *host,
>>> + struct mmc_card *card)
>>> +{
>>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> +
>>> + if (WARN_ON(!mmc_card_hs400(card)))
>>> + return;
>>> +
>>> + /* Enable the DLL to automatically adjust HS400 strobe delay.
>>> + */
>>> + if (priv->phy_ops->strobe_delay_adj)
>>> + priv->phy_ops->strobe_delay_adj(host, card);
>>> +}
>>> +
>>> +static int xenon_fix_sampl_delay_adj(struct sdhci_host *host,
>>> + struct mmc_card *card)
>>> +{
>>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> +
>>> + if (priv->phy_ops->fix_sampl_delay_adj)
>>> + return priv->phy_ops->fix_sampl_delay_adj(host, card);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +/*
>>> + * xenon_delay_adj should not be called inside IRQ context,
>>> + * either Hard IRQ or Softirq.
>>> + */
>>> +static int xenon_hs_delay_adj(struct sdhci_host *host,
>>> + struct mmc_card *card)
>>> +{
>>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> + int ret = 0;
>>> +
>>> + if (WARN_ON(host->clock <= DEFAULT_SDCLK_FREQ))
>>> + return -EINVAL;
>>> +
>>> + if (mmc_card_hs400(card)) {
>>> + xenon_hs400_strobe_delay_adj(host, card);
>>> + return 0;
>>> + }
>>> +
>>> + if (((priv->phy_type == EMMC_5_1_PHY) ||
>>> + (priv->phy_type == EMMC_5_0_PHY)) &&
>>> + (mmc_card_hs200(card) ||
>>> + (host->timing == MMC_TIMING_UHS_SDR104))) {
>>> + ret = xenon_emmc_phy_config_tuning(host);
>>> + if (!ret)
>>> + return 0;
>>> + }
>>> +
>>> + ret = xenon_fix_sampl_delay_adj(host, card);
>>> + if (ret)
>>> + dev_err(mmc_dev(host->mmc), "fails sampling fixed delay adjustment\n");
>>> + return ret;
>>> +}
>>> +
>>> +int xenon_phy_adj(struct sdhci_host *host, struct mmc_ios *ios)
>>> +{
>>> + struct mmc_host *mmc = host->mmc;
>>> + struct mmc_card *card;
>>> + int ret = 0;
>>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> +
>>> + if (!host->clock) {
>>> + priv->clock = 0;
>>> + return 0;
>>> + }
>>> +
>>> + /*
>>> + * The timing, frequency or bus width is changed,
>>> + * better to set eMMC PHY based on current setting
>>> + * and adjust Xenon SDHC delay.
>>> + */
>>> + if ((host->clock == priv->clock) &&
>>> + (ios->bus_width == priv->bus_width) &&
>>> + (ios->timing == priv->timing))
>>> + return 0;
>>> +
>>> + xenon_phy_set(host, ios->timing);
>>> +
>>> + /* Update the record */
>>> + priv->bus_width = ios->bus_width;
>>> + /* Temp stage from HS200 to HS400 */
>>> + if (((priv->timing == MMC_TIMING_MMC_HS200) &&
>>> + (ios->timing == MMC_TIMING_MMC_HS)) ||
>>> + ((ios->timing == MMC_TIMING_MMC_HS) &&
>>> + (priv->clock > host->clock))) {
>>> + priv->timing = ios->timing;
>>> + priv->clock = host->clock;
>>> + return 0;
>>> + }
>>> + priv->timing = ios->timing;
>>> + priv->clock = host->clock;
>>> +
>>> + /* Legacy mode is a special case */
>>> + if (ios->timing == MMC_TIMING_LEGACY)
>>> + return 0;
>>> +
>>> + card = priv->card_candidate;
>>> + if (unlikely(!card)) {
>>> + dev_warn(mmc_dev(mmc), "card is not present\n");
>>> + return -EINVAL;
>>> + }
>>> +
>>> + if (host->clock > DEFAULT_SDCLK_FREQ)
>>> + ret = xenon_hs_delay_adj(host, card);
>>> + return ret;
>>> +}
>>> +
>>> +static int add_xenon_phy(struct device_node *np, struct sdhci_host *host,
>>> + const char *phy_name)
>>> +{
>>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> + int i, ret;
>>> +
>>> + for (i = 0; i < NR_PHY_TYPES; i++) {
>>> + if (!strcmp(phy_name, phy_types[i])) {
>>> + priv->phy_type = i;
>>> + break;
>>> + }
>>> + }
>>> + if (i == NR_PHY_TYPES) {
>>> + dev_err(mmc_dev(host->mmc),
>>> + "Unable to determine PHY name %s. Use default eMMC 5.1 PHY\n",
>>> + phy_name);
>>> + priv->phy_type = EMMC_5_1_PHY;
>>> + }
>>> +
>>> + if (priv->phy_type == SDH_PHY) {
>>> + return alloc_sdh_phy(priv);
>>> + } else if ((priv->phy_type == EMMC_5_0_PHY) ||
>>> + (priv->phy_type == EMMC_5_1_PHY)) {
>>> + ret = alloc_emmc_phy(priv);
>>> + if (ret)
>>> + return ret;
>>> + return emmc_phy_parse_param_dt(host, np, priv->phy_params);
>>> + }
>>> +
>>> + return -EINVAL;
>>> +}
>>> +
>>> +int xenon_phy_parse_dt(struct device_node *np, struct sdhci_host *host)
>>> +{
>>> + const char *phy_type = NULL;
>>> +
>>> + if (!of_property_read_string(np, "xenon,phy-type", &phy_type))
>>> + return add_xenon_phy(np, host, phy_type);
>>> +
>>> + dev_err(mmc_dev(host->mmc), "Fail to get Xenon PHY type. Use default eMMC 5.1 PHY\n");
>>> + return add_xenon_phy(np, host, "emmc 5.1 phy");
>>> +}
>>> diff --git a/drivers/mmc/host/sdhci-xenon-phy.h b/drivers/mmc/host/sdhci-xenon-phy.h
>>> new file mode 100644
>>> index 000000000000..4373c71d3b7b
>>> --- /dev/null
>>> +++ b/drivers/mmc/host/sdhci-xenon-phy.h
>>> @@ -0,0 +1,157 @@
>>> +/* linux/drivers/mmc/host/sdhci-xenon-phy.h
>>> + *
>>> + * Author: Hu Ziji <huziji@marvell.com>
>>> + * Date: 2016-8-24
>>> + *
>>> + * Copyright (C) 2016 Marvell, All Rights Reserved.
>>> + *
>>> + * 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.
>>> + */
>>> +#ifndef SDHCI_XENON_PHY_H_
>>> +#define SDHCI_XENON_PHY_H_
>>> +
>>> +#include <linux/types.h>
>>> +#include "sdhci.h"
>>> +
>>> +/* Register base for eMMC PHY 5.0 Version */
>>> +#define EMMC_5_0_PHY_REG_BASE 0x0160
>>> +/* Register base for eMMC PHY 5.1 Version */
>>> +#define EMMC_PHY_REG_BASE 0x0170
>>> +
>>> +#define EMMC_PHY_TIMING_ADJUST EMMC_PHY_REG_BASE
>>> +#define EMMC_5_0_PHY_TIMING_ADJUST EMMC_5_0_PHY_REG_BASE
>>> +#define TIMING_ADJUST_SLOW_MODE BIT(29)
>>> +#define TIMING_ADJUST_SDIO_MODE BIT(28)
>>> +#define OUTPUT_QSN_PHASE_SELECT BIT(17)
>>> +#define SAMPL_INV_QSP_PHASE_SELECT BIT(18)
>>> +#define SAMPL_INV_QSP_PHASE_SELECT_SHIFT 18
>>> +#define PHY_INITIALIZAION BIT(31)
>>> +#define WAIT_CYCLE_BEFORE_USING_MASK 0xF
>>> +#define WAIT_CYCLE_BEFORE_USING_SHIFT 12
>>> +#define FC_SYNC_EN_DURATION_MASK 0xF
>>> +#define FC_SYNC_EN_DURATION_SHIFT 8
>>> +#define FC_SYNC_RST_EN_DURATION_MASK 0xF
>>> +#define FC_SYNC_RST_EN_DURATION_SHIFT 4
>>> +#define FC_SYNC_RST_DURATION_MASK 0xF
>>> +#define FC_SYNC_RST_DURATION_SHIFT 0
>>> +
>>> +#define EMMC_PHY_FUNC_CONTROL (EMMC_PHY_REG_BASE + 0x4)
>>> +#define EMMC_5_0_PHY_FUNC_CONTROL (EMMC_5_0_PHY_REG_BASE + 0x4)
>>> +#define ASYNC_DDRMODE_MASK BIT(23)
>>> +#define ASYNC_DDRMODE_SHIFT 23
>>> +#define CMD_DDR_MODE BIT(16)
>>> +#define DQ_DDR_MODE_SHIFT 8
>>> +#define DQ_DDR_MODE_MASK 0xFF
>>> +#define DQ_ASYNC_MODE BIT(4)
>>> +
>>> +#define EMMC_PHY_PAD_CONTROL (EMMC_PHY_REG_BASE + 0x8)
>>> +#define EMMC_5_0_PHY_PAD_CONTROL (EMMC_5_0_PHY_REG_BASE + 0x8)
>>> +#define REC_EN_SHIFT 24
>>> +#define REC_EN_MASK 0xF
>>> +#define FC_DQ_RECEN BIT(24)
>>> +#define FC_CMD_RECEN BIT(25)
>>> +#define FC_QSP_RECEN BIT(26)
>>> +#define FC_QSN_RECEN BIT(27)
>>> +#define OEN_QSN BIT(28)
>>> +#define AUTO_RECEN_CTRL BIT(30)
>>> +#define FC_ALL_CMOS_RECEIVER 0xF000
>>> +
>>> +#define EMMC5_FC_QSP_PD BIT(18)
>>> +#define EMMC5_FC_QSP_PU BIT(22)
>>> +#define EMMC5_FC_CMD_PD BIT(17)
>>> +#define EMMC5_FC_CMD_PU BIT(21)
>>> +#define EMMC5_FC_DQ_PD BIT(16)
>>> +#define EMMC5_FC_DQ_PU BIT(20)
>>> +
>>> +#define EMMC_PHY_PAD_CONTROL1 (EMMC_PHY_REG_BASE + 0xC)
>>> +#define EMMC5_1_FC_QSP_PD BIT(9)
>>> +#define EMMC5_1_FC_QSP_PU BIT(25)
>>> +#define EMMC5_1_FC_CMD_PD BIT(8)
>>> +#define EMMC5_1_FC_CMD_PU BIT(24)
>>> +#define EMMC5_1_FC_DQ_PD 0xFF
>>> +#define EMMC5_1_FC_DQ_PU (0xFF << 16)
>>> +
>>> +#define EMMC_PHY_PAD_CONTROL2 (EMMC_PHY_REG_BASE + 0x10)
>>> +#define EMMC_5_0_PHY_PAD_CONTROL2 (EMMC_5_0_PHY_REG_BASE + 0xC)
>>> +#define ZNR_MASK 0x1F
>>> +#define ZNR_SHIFT 8
>>> +#define ZPR_MASK 0x1F
>>> +/* Perferred ZNR and ZPR value vary between different boards.
>>> + * The specific ZNR and ZPR value should be defined here
>>> + * according to board actual timing.
>>> + */
>>> +#define ZNR_DEF_VALUE 0xF
>>> +#define ZPR_DEF_VALUE 0xF
>>> +
>>> +#define EMMC_PHY_DLL_CONTROL (EMMC_PHY_REG_BASE + 0x14)
>>> +#define EMMC_5_0_PHY_DLL_CONTROL (EMMC_5_0_PHY_REG_BASE + 0x10)
>>> +#define DLL_ENABLE BIT(31)
>>> +#define DLL_UPDATE_STROBE_5_0 BIT(30)
>>> +#define DLL_REFCLK_SEL BIT(30)
>>> +#define DLL_UPDATE BIT(23)
>>> +#define DLL_PHSEL1_SHIFT 24
>>> +#define DLL_PHSEL0_SHIFT 16
>>> +#define DLL_PHASE_MASK 0x3F
>>> +#define DLL_PHASE_90_DEGREE 0x1F
>>> +#define DLL_FAST_LOCK BIT(5)
>>> +#define DLL_GAIN2X BIT(3)
>>> +#define DLL_BYPASS_EN BIT(0)
>>> +
>>> +#define EMMC_5_0_PHY_LOGIC_TIMING_ADJUST (EMMC_5_0_PHY_REG_BASE + 0x14)
>>> +#define EMMC_PHY_LOGIC_TIMING_ADJUST (EMMC_PHY_REG_BASE + 0x18)
>>> +
>>> +enum sampl_fix_delay_phase {
>>> + PHASE_0_DEGREE = 0x0,
>>> + PHASE_90_DEGREE = 0x1,
>>> + PHASE_180_DEGREE = 0x2,
>>> + PHASE_270_DEGREE = 0x3,
>>> +};
>>> +
>>> +#define SDH_PHY_SLOT_DLL_CTRL (0x0138)
>>> +#define SDH_PHY_ENABLE_DLL BIT(1)
>>> +#define SDH_PHY_FAST_LOCK_EN BIT(5)
>>> +
>>> +#define SDH_PHY_SLOT_DLL_PHASE_SEL (0x013C)
>>> +#define SDH_PHY_DLL_UPDATE_TUNING BIT(15)
>>> +
>>> +enum soc_pad_ctrl_type {
>>> + SOC_PAD_SD,
>>> + SOC_PAD_FIXED_1_8V,
>>> +};
>>> +
>>> +/*
>>> + * List offset of PHY registers and some special register values
>>> + * in eMMC PHY 5.0 or eMMC PHY 5.1
>>> + */
>>> +struct xenon_emmc_phy_regs {
>>> + /* Offset of Timing Adjust register */
>>> + u16 timing_adj;
>>> + /* Offset of Func Control register */
>>> + u16 func_ctrl;
>>> + /* Offset of Pad Control register */
>>> + u16 pad_ctrl;
>>> + /* Offset of Pad Control register */
>>> + u16 pad_ctrl2;
>>> + /* Offset of DLL Control register */
>>> + u16 dll_ctrl;
>>> + /* Offset of Logic Timing Adjust register */
>>> + u16 logic_timing_adj;
>>> + /* Max value of eMMC Fixed Sampling Delay */
>>> + u32 delay_mask;
>>> + /* DLL Update Enable bit */
>>> + u32 dll_update;
>>> +};
>>> +
>>> +struct xenon_phy_ops {
>>> + void (*strobe_delay_adj)(struct sdhci_host *host,
>>> + struct mmc_card *card);
>>> + int (*fix_sampl_delay_adj)(struct sdhci_host *host,
>>> + struct mmc_card *card);
>>> + void (*phy_set)(struct sdhci_host *host, unsigned char timing);
>>> + void (*set_soc_pad)(struct sdhci_host *host,
>>> + unsigned char signal_voltage);
>>> +};
>>> +#endif
>>> diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c
>>> index 03ba183494d3..4d7d871544fc 100644
>>> --- a/drivers/mmc/host/sdhci-xenon.c
>>> +++ b/drivers/mmc/host/sdhci-xenon.c
>>> @@ -224,6 +224,7 @@ static void xenon_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
>>> spin_unlock_irqrestore(&host->lock, flags);
>>>
>>> sdhci_set_ios(mmc, ios);
>>> + xenon_phy_adj(host, ios);
>>>
>>> if (host->clock > DEFAULT_SDCLK_FREQ) {
>>> spin_lock_irqsave(&host->lock, flags);
>>> @@ -309,6 +310,8 @@ static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
>>> */
>>> enable_xenon_internal_clk(host);
>>>
>>> + xenon_soc_pad_ctrl(host, ios->signal_voltage);
>>> +
>>> if (priv->card_candidate) {
>>> if (mmc_card_mmc(priv->card_candidate))
>>> return xenon_emmc_signal_voltage_switch(mmc, ios);
>>> @@ -453,6 +456,7 @@ static int xenon_probe_dt(struct platform_device *pdev)
>>> sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
>>> }
>>>
>>> + err = xenon_phy_parse_dt(np, host);
>>> return err;
>>> }
>>>
>>> diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h
>>> index c2370493fbe8..06e5261a563c 100644
>>> --- a/drivers/mmc/host/sdhci-xenon.h
>>> +++ b/drivers/mmc/host/sdhci-xenon.h
>>> @@ -15,6 +15,7 @@
>>> #include <linux/mmc/card.h>
>>> #include <linux/of.h>
>>> #include "sdhci.h"
>>> +#include "sdhci-xenon-phy.h"
>>>
>>> /* Register Offset of SD Host Controller SOCP self-defined register */
>>> #define SDHC_SYS_CFG_INFO 0x0104
>>> @@ -76,6 +77,7 @@
>>> #define MMC_TIMING_FAKE 0xFF
>>>
>>> #define DEFAULT_SDCLK_FREQ (400000)
>>> +#define LOWEST_SDCLK_FREQ (100000)
>>>
>>> /* Xenon specific Mode Select value */
>>> #define XENON_SDHCI_CTRL_HS200 0x5
>>> @@ -97,6 +99,15 @@ struct sdhci_xenon_priv {
>>> /* Slot idx */
>>> u8 slot_idx;
>>>
>>> + int phy_type;
>>> + /*
>>> + * Contains board-specific PHY parameters
>>> + * passed from device tree.
>>> + */
>>> + void *phy_params;
>>> + const struct xenon_phy_ops *phy_ops;
>>> + struct xenon_emmc_phy_regs *emmc_phy_regs;
>>> +
>>> /*
>>> * When initializing card, Xenon has to determine card type and
>>> * adjust Sampling Fixed delay.
>>> @@ -131,4 +142,10 @@ static inline int enable_xenon_internal_clk(struct sdhci_host *host)
>>>
>>> return 0;
>>> }
>>> +
>>> +int xenon_phy_adj(struct sdhci_host *host, struct mmc_ios *ios);
>>> +int xenon_phy_parse_dt(struct device_node *np,
>>> + struct sdhci_host *host);
>>> +void xenon_soc_pad_ctrl(struct sdhci_host *host,
>>> + unsigned char signal_voltage);
>>> #endif
>>>
>>
>>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
--
Best Regards
Shawn Lin
^ permalink raw reply
* Re: [PATCH] sdhci-esdhc-imx: Correct two register accesses
From: Dong Aisheng @ 2016-10-08 13:10 UTC (permalink / raw)
To: Aaron Brice
Cc: david.russell, Adrian Hunter, Ulf Hansson, Dong Aisheng,
linux-mmc@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-arm-kernel@lists.infradead.org
In-Reply-To: <1475794457-17993-1-git-send-email-aaron.brice@datasoft.com>
On Fri, Oct 7, 2016 at 6:54 AM, Aaron Brice <aaron.brice@datasoft.com> wrote:
> - The DMA error interrupt bit is in a different position as
> compared to the sdhci standard. This is accounted for in
> many cases, but not handled in the case of clearing the
> INT_STATUS register by writing a 1 to that location.
> - The HOST_CONTROL register is very different as compared to
> the sdhci standard. This is accounted for in the write
> case, but not when read back out (which it is in the sdhci
> code).
>
> Signed-off-by: Dave Russell <david.russell@datasoft.com>
> Signed-off-by: Aaron Brice <aaron.brice@datasoft.com>
> ---
> drivers/mmc/host/sdhci-esdhc-imx.c | 23 ++++++++++++++++++++++-
> 1 file changed, 22 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c
> index 1f54fd8..d61ef16 100644
> --- a/drivers/mmc/host/sdhci-esdhc-imx.c
> +++ b/drivers/mmc/host/sdhci-esdhc-imx.c
> @@ -346,7 +346,8 @@ static void esdhc_writel_le(struct sdhci_host *host, u32 val, int reg)
> struct pltfm_imx_data *imx_data = sdhci_pltfm_priv(pltfm_host);
> u32 data;
>
> - if (unlikely(reg == SDHCI_INT_ENABLE || reg == SDHCI_SIGNAL_ENABLE)) {
> + if (unlikely(reg == SDHCI_INT_ENABLE || reg == SDHCI_SIGNAL_ENABLE ||
> + reg == SDHCI_INT_STATUS)) {
> if ((val & SDHCI_INT_CARD_INT) && !esdhc_is_usdhc(imx_data)) {
> /*
> * Clear and then set D3CD bit to avoid missing the
> @@ -555,6 +556,25 @@ static void esdhc_writew_le(struct sdhci_host *host, u16 val, int reg)
> esdhc_clrset_le(host, 0xffff, val, reg);
> }
>
> +static u8 esdhc_readb_le(struct sdhci_host *host, int reg)
> +{
> + u8 ret;
> + u32 long_val;
> +
> + switch (reg) {
> + case SDHCI_HOST_CONTROL:
> + long_val = readl(host->ioaddr + reg);
> +
> + ret = long_val & SDHCI_CTRL_LED;
> + ret |= (long_val >> 5) & SDHCI_CTRL_DMA_MASK;
> + ret |= (long_val & ESDHC_CTRL_4BITBUS);
> + ret |= (long_val & ESDHC_CTRL_8BITBUS) << 3;
> + return ret;
Thanks for the effort.
One nitpick: would be more like to use 'val' instead of 'long_val' to
be consistent with exist using.
(i saw a few 'new_val' as well, maybe we could clean up them in the future,
but at least we could avoid inventing more from now)
Otherwise,
Acked-by: Dong Aisheng <aisheng.dong@nxp.com>
Regards
Dong Aisheng
> + }
> +
> + return readb(host->ioaddr + reg);
> +}
> +
> static void esdhc_writeb_le(struct sdhci_host *host, u8 val, int reg)
> {
> struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> @@ -947,6 +967,7 @@ static void esdhc_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
> static struct sdhci_ops sdhci_esdhc_ops = {
> .read_l = esdhc_readl_le,
> .read_w = esdhc_readw_le,
> + .read_b = esdhc_readb_le,
> .write_l = esdhc_writel_le,
> .write_w = esdhc_writew_le,
> .write_b = esdhc_writeb_le,
> --
> 2.7.4
>
>
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
From: Ziji Hu @ 2016-10-08 9:28 UTC (permalink / raw)
To: Shawn Lin, Gregory CLEMENT, Ulf Hansson, Adrian Hunter, linux-mmc
Cc: Hilbert Zhang, Andrew Lunn, Romain Perier, Liuliu Zhao, Peng Zhu,
Nadav Haklai, Jack(SH) Zhu, Victor Gu, Doug Jones, Jisheng Zhang,
Yehuda Yitschak, Marcin Wojtas, Xueping Liu, Shiwu Zhang, Yu Cao,
Sebastian Hesselbarth, devicetree, Jason Cooper,
Kostya Porotchkin, Rob Herring, Ryan Gao, Wei(SOCP) Liu,
linux-arm-kernel, Thomas Petazzoni
In-Reply-To: <25146173-d98b-f346-b333-4d7466960496@rock-chips.com>
Hi Shawn,
On 2016/10/8 10:44, Shawn Lin wrote:
> 在 2016/10/7 23:22, Gregory CLEMENT 写道:
>> From: Ziji Hu <huziji@marvell.com>
>>
>> Marvell Xenon eMMC/SD/SDIO Host Controller contains PHY.
>> Three types of PHYs are supported.
>>
>> Add support to multiple types of PHYs init and configuration.
>> Add register definitions of PHYs.
>>
>> Signed-off-by: Hu Ziji <huziji@marvell.com>
>> Reviewed-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
>> Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
>> ---
>> MAINTAINERS | 1 +-
>> drivers/mmc/host/Makefile | 2 +-
>> drivers/mmc/host/sdhci-xenon-phy.c | 1141 +++++++++++++++++++++++++++++-
>> drivers/mmc/host/sdhci-xenon-phy.h | 157 ++++-
>> drivers/mmc/host/sdhci-xenon.c | 4 +-
>> drivers/mmc/host/sdhci-xenon.h | 17 +-
>> 6 files changed, 1321 insertions(+), 1 deletion(-)
>> create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c
>> create mode 100644 drivers/mmc/host/sdhci-xenon-phy.h
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 859420e5dfd3..b5673c2ee5f2 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -7583,6 +7583,7 @@ M: Ziji Hu <huziji@marvell.com>
>> L: linux-mmc@vger.kernel.org
>> S: Supported
>> F: drivers/mmc/host/sdhci-xenon.*
>> +F: drivers/mmc/host/sdhci-xenon-phy.*
>
> drivers/mmc/host/sdhci-xenon* shoube enough
>
>> F: Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>>
>> MATROX FRAMEBUFFER DRIVER
>> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
>> index 75eaf743486c..4f2854556ff7 100644
>> --- a/drivers/mmc/host/Makefile
>> +++ b/drivers/mmc/host/Makefile
>> @@ -82,4 +82,4 @@ ifeq ($(CONFIG_CB710_DEBUG),y)
>> endif
>>
>> obj-$(CONFIG_MMC_SDHCI_XENON) += sdhci-xenon-driver.o
>> -sdhci-xenon-driver-y += sdhci-xenon.o
>> +sdhci-xenon-driver-y += sdhci-xenon.o sdhci-xenon-phy.o
>> diff --git a/drivers/mmc/host/sdhci-xenon-phy.c b/drivers/mmc/host/sdhci-xenon-phy.c
>> new file mode 100644
>> index 000000000000..4eb8fea1bec9
>> --- /dev/null
>> +++ b/drivers/mmc/host/sdhci-xenon-phy.c
>
> Well, it's legit to use phy API and move your phy
> operations to PHY subsystem. :)
>
Actually we tried to put the PHY code into Linux PHY framework.
But it cannot fit in Linux common PHY framework.
Our Xenon SDHC PHY register is a part of Xenon SDHC register set.
Besides, during MMC initialization, MMC sequence has to call several PHY functions to complete timing setting.
In those PHY setting functions, they have to access SDHC register and know current MMC setting, such as bus width, clock frequency and speed mode.
As a result, we have to implement PHY under MMC directory.
Thank you.
Best regards,
Hu Ziji
>> @@ -0,0 +1,1141 @@
>> +/*
>> + * PHY support for Xenon SDHC
>> + *
>> + * Copyright (C) 2016 Marvell, All Rights Reserved.
>> + *
>> + * Author: Hu Ziji <huziji@marvell.com>
>> + * Date: 2016-8-24
>> + *
>> + * 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 version 2.
>> + */
>> +
>> +#include <linux/slab.h>
>> +#include <linux/delay.h>
>> +#include <linux/of_address.h>
>> +#include <linux/mmc/host.h>
>> +#include <linux/mmc/mmc.h>
>> +#include <linux/mmc/card.h>
>> +#include <linux/mmc/sdio.h>
>> +
>> +#include "sdhci.h"
>> +#include "sdhci-pltfm.h"
>> +#include "sdhci-xenon.h"
>> +
>> +static const char * const phy_types[] = {
>> + "sdh phy",
>> + "emmc 5.0 phy",
>> + "emmc 5.1 phy"
>> +};
>> +
>> +enum phy_type_enum {
>> + SDH_PHY,
>> + EMMC_5_0_PHY,
>> + EMMC_5_1_PHY,
>> + NR_PHY_TYPES
>> +};
>> +
>> +struct soc_pad_ctrl_table {
>> + const char *soc;
>> + void (*set_soc_pad)(struct sdhci_host *host,
>> + unsigned char signal_voltage);
>> +};
>> +
>> +struct soc_pad_ctrl {
>> + /* Register address of SOC PHY PAD ctrl */
>> + void __iomem *reg;
>> + /* SOC PHY PAD ctrl type */
>> + enum soc_pad_ctrl_type pad_type;
>> + /* SOC specific operation to set SOC PHY PAD */
>> + void (*set_soc_pad)(struct sdhci_host *host,
>> + unsigned char signal_voltage);
>> +};
>> +
>> +static struct xenon_emmc_phy_regs xenon_emmc_5_0_phy_regs = {
>> + .timing_adj = EMMC_5_0_PHY_TIMING_ADJUST,
>> + .func_ctrl = EMMC_5_0_PHY_FUNC_CONTROL,
>> + .pad_ctrl = EMMC_5_0_PHY_PAD_CONTROL,
>> + .pad_ctrl2 = EMMC_5_0_PHY_PAD_CONTROL2,
>> + .dll_ctrl = EMMC_5_0_PHY_DLL_CONTROL,
>> + .logic_timing_adj = EMMC_5_0_PHY_LOGIC_TIMING_ADJUST,
>> + .delay_mask = EMMC_5_0_PHY_FIXED_DELAY_MASK,
>> + .dll_update = DLL_UPDATE_STROBE_5_0,
>> +};
>> +
>> +static struct xenon_emmc_phy_regs xenon_emmc_5_1_phy_regs = {
>> + .timing_adj = EMMC_PHY_TIMING_ADJUST,
>> + .func_ctrl = EMMC_PHY_FUNC_CONTROL,
>> + .pad_ctrl = EMMC_PHY_PAD_CONTROL,
>> + .pad_ctrl2 = EMMC_PHY_PAD_CONTROL2,
>> + .dll_ctrl = EMMC_PHY_DLL_CONTROL,
>> + .logic_timing_adj = EMMC_PHY_LOGIC_TIMING_ADJUST,
>> + .delay_mask = EMMC_PHY_FIXED_DELAY_MASK,
>> + .dll_update = DLL_UPDATE,
>> +};
>> +
>> +static int xenon_delay_adj_test(struct mmc_card *card);
>> +
>> +/*
>> + * eMMC PHY configuration and operations
>> + */
>> +struct emmc_phy_params {
>> + bool slow_mode;
>> +
>> + u8 znr;
>> + u8 zpr;
>> +
>> + /* Nr of consecutive Sampling Points of a Valid Sampling Window */
>> + u8 nr_tun_times;
>> + /* Divider for calculating Tuning Step */
>> + u8 tun_step_divider;
>> +
>> + struct soc_pad_ctrl pad_ctrl;
>> +};
>> +
>> +static void xenon_emmc_phy_strobe_delay_adj(struct sdhci_host *host,
>> + struct mmc_card *card);
>> +static int xenon_emmc_phy_fix_sampl_delay_adj(struct sdhci_host *host,
>> + struct mmc_card *card);
>> +static void xenon_emmc_phy_set(struct sdhci_host *host,
>> + unsigned char timing);
>> +static void xenon_emmc_set_soc_pad(struct sdhci_host *host,
>> + unsigned char signal_voltage);
>> +
>> +static const struct xenon_phy_ops emmc_phy_ops = {
>> + .strobe_delay_adj = xenon_emmc_phy_strobe_delay_adj,
>> + .fix_sampl_delay_adj = xenon_emmc_phy_fix_sampl_delay_adj,
>> + .phy_set = xenon_emmc_phy_set,
>> + .set_soc_pad = xenon_emmc_set_soc_pad,
>> +};
>> +
>> +static int alloc_emmc_phy(struct sdhci_xenon_priv *priv)
>> +{
>> + struct emmc_phy_params *params;
>> +
>> + params = kzalloc(sizeof(*params), GFP_KERNEL);
>> + if (!params)
>> + return -ENOMEM;
>> +
>> + priv->phy_params = params;
>> + priv->phy_ops = &emmc_phy_ops;
>> + if (priv->phy_type == EMMC_5_0_PHY)
>> + priv->emmc_phy_regs = &xenon_emmc_5_0_phy_regs;
>> + else
>> + priv->emmc_phy_regs = &xenon_emmc_5_1_phy_regs;
>> +
>> + return 0;
>> +}
>> +
>> +static int xenon_emmc_phy_init(struct sdhci_host *host)
>> +{
>> + u32 reg;
>> + u32 wait, clock;
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
>> +
>> + reg = sdhci_readl(host, phy_regs->timing_adj);
>> + reg |= PHY_INITIALIZAION;
>> + sdhci_writel(host, reg, phy_regs->timing_adj);
>> +
>> + /* Add duration of FC_SYNC_RST */
>> + wait = ((reg >> FC_SYNC_RST_DURATION_SHIFT) &
>> + FC_SYNC_RST_DURATION_MASK);
>> + /* Add interval between FC_SYNC_EN and FC_SYNC_RST */
>> + wait += ((reg >> FC_SYNC_RST_EN_DURATION_SHIFT) &
>> + FC_SYNC_RST_EN_DURATION_MASK);
>> + /* Add duration of asserting FC_SYNC_EN */
>> + wait += ((reg >> FC_SYNC_EN_DURATION_SHIFT) &
>> + FC_SYNC_EN_DURATION_MASK);
>> + /* Add duration of waiting for PHY */
>> + wait += ((reg >> WAIT_CYCLE_BEFORE_USING_SHIFT) &
>> + WAIT_CYCLE_BEFORE_USING_MASK);
>> + /* 4 addtional bus clock and 4 AXI bus clock are required */
>> + wait += 8;
>> + wait <<= 20;
>> +
>> + clock = host->clock;
>> + if (!clock)
>> + /* Use the possibly slowest bus frequency value */
>> + clock = LOWEST_SDCLK_FREQ;
>> + /* get the wait time */
>> + wait /= clock;
>> + wait++;
>> + /* wait for host eMMC PHY init completes */
>> + udelay(wait);
>> +
>> + reg = sdhci_readl(host, phy_regs->timing_adj);
>> + reg &= PHY_INITIALIZAION;
>> + if (reg) {
>> + dev_err(mmc_dev(host->mmc), "eMMC PHY init cannot complete after %d us\n",
>> + wait);
>> + return -ETIMEDOUT;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +#define ARMADA_3700_SOC_PAD_1_8V 0x1
>> +#define ARMADA_3700_SOC_PAD_3_3V 0x0
>> +
>> +static void armada_3700_soc_pad_voltage_set(struct sdhci_host *host,
>> + unsigned char signal_voltage)
>> +{
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + struct emmc_phy_params *params = priv->phy_params;
>> +
>> + if (params->pad_ctrl.pad_type == SOC_PAD_FIXED_1_8V) {
>> + writel(ARMADA_3700_SOC_PAD_1_8V, params->pad_ctrl.reg);
>> + } else if (params->pad_ctrl.pad_type == SOC_PAD_SD) {
>> + if (signal_voltage == MMC_SIGNAL_VOLTAGE_180)
>> + writel(ARMADA_3700_SOC_PAD_1_8V, params->pad_ctrl.reg);
>> + else if (signal_voltage == MMC_SIGNAL_VOLTAGE_330)
>> + writel(ARMADA_3700_SOC_PAD_3_3V, params->pad_ctrl.reg);
>> + }
>> +}
>> +
>> +static void xenon_emmc_set_soc_pad(struct sdhci_host *host,
>> + unsigned char signal_voltage)
>> +{
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + struct emmc_phy_params *params = priv->phy_params;
>> +
>> + if (!params->pad_ctrl.reg)
>> + return;
>> +
>> + if (params->pad_ctrl.set_soc_pad)
>> + params->pad_ctrl.set_soc_pad(host, signal_voltage);
>> +}
>> +
>> +static int emmc_phy_set_fix_sampl_delay(struct sdhci_host *host,
>> + unsigned int delay,
>> + bool invert,
>> + bool delay_90_degree)
>> +{
>> + u32 reg;
>> + unsigned long flags;
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
>> + int ret = 0;
>> +
>> + spin_lock_irqsave(&host->lock, flags);
>> +
>> + /* Setup Sampling fix delay */
>> + reg = sdhci_readl(host, SDHC_SLOT_OP_STATUS_CTRL);
>> + reg &= ~phy_regs->delay_mask;
>> + reg |= delay & phy_regs->delay_mask;
>> + sdhci_writel(host, reg, SDHC_SLOT_OP_STATUS_CTRL);
>> +
>> + if (priv->phy_type == EMMC_5_0_PHY) {
>> + /* set 90 degree phase if necessary */
>> + reg &= ~DELAY_90_DEGREE_MASK_EMMC5;
>> + reg |= (delay_90_degree << DELAY_90_DEGREE_SHIFT_EMMC5);
>> + sdhci_writel(host, reg, SDHC_SLOT_OP_STATUS_CTRL);
>> + }
>> +
>> + /* Disable SDCLK */
>> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
>> + reg &= ~(SDHCI_CLOCK_CARD_EN | SDHCI_CLOCK_INT_EN);
>> + sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
>> +
>> + udelay(200);
>> +
>> + if (priv->phy_type == EMMC_5_1_PHY) {
>> + /* set 90 degree phase if necessary */
>> + reg = sdhci_readl(host, EMMC_PHY_FUNC_CONTROL);
>> + reg &= ~ASYNC_DDRMODE_MASK;
>> + reg |= (delay_90_degree << ASYNC_DDRMODE_SHIFT);
>> + sdhci_writel(host, reg, EMMC_PHY_FUNC_CONTROL);
>> + }
>> +
>> + /* Setup Inversion of Sampling edge */
>> + reg = sdhci_readl(host, phy_regs->timing_adj);
>> + reg &= ~SAMPL_INV_QSP_PHASE_SELECT;
>> + reg |= (invert << SAMPL_INV_QSP_PHASE_SELECT_SHIFT);
>> + sdhci_writel(host, reg, phy_regs->timing_adj);
>> +
>> + /* Enable SD internal clock */
>> + ret = enable_xenon_internal_clk(host);
>> + if (ret)
>> + goto out;
>> +
>> + /* Enable SDCLK */
>> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
>> + reg |= SDHCI_CLOCK_CARD_EN;
>> + sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
>> +
>> + udelay(200);
>> +
>> + /*
>> + * Has to re-initialize eMMC PHY here to active PHY
>> + * because later get status cmd will be issued.
>> + */
>> + ret = xenon_emmc_phy_init(host);
>> +
>> +out:
>> + spin_unlock_irqrestore(&host->lock, flags);
>> + return ret;
>> +}
>> +
>> +static int emmc_phy_do_fix_sampl_delay(struct sdhci_host *host,
>> + struct mmc_card *card,
>> + unsigned int delay,
>> + bool invert, bool quarter)
>> +{
>> + int ret;
>> +
>> + emmc_phy_set_fix_sampl_delay(host, delay, invert, quarter);
>> +
>> + ret = xenon_delay_adj_test(card);
>> + if (ret) {
>> + dev_dbg(mmc_dev(host->mmc),
>> + "fail when sampling fix delay = %d, phase = %d degree\n",
>> + delay, invert * 180 + quarter * 90);
>> + return -1;
>> + }
>> + return 0;
>> +}
>> +
>> +static int xenon_emmc_phy_fix_sampl_delay_adj(struct sdhci_host *host,
>> + struct mmc_card *card)
>> +{
>> + enum sampl_fix_delay_phase phase;
>> + int idx, nr_pair;
>> + int ret;
>> + unsigned int delay;
>> + unsigned int min_delay, max_delay;
>> + bool invert, quarter;
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
>> + u32 coarse_step, fine_step;
>> + const enum sampl_fix_delay_phase delay_edge[] = {
>> + PHASE_0_DEGREE,
>> + PHASE_180_DEGREE,
>> + PHASE_90_DEGREE,
>> + PHASE_270_DEGREE
>> + };
>> +
>> + coarse_step = phy_regs->delay_mask >> 1;
>> + fine_step = coarse_step >> 2;
>> +
>> + nr_pair = ARRAY_SIZE(delay_edge);
>> +
>> + for (idx = 0; idx < nr_pair; idx++) {
>> + phase = delay_edge[idx];
>> + invert = (phase & 0x2) ? true : false;
>> + quarter = (phase & 0x1) ? true : false;
>> +
>> + /* increase delay value to get fix delay */
>> + for (min_delay = 0;
>> + min_delay <= phy_regs->delay_mask;
>> + min_delay += coarse_step) {
>> + ret = emmc_phy_do_fix_sampl_delay(host, card, min_delay,
>> + invert, quarter);
>> + if (!ret)
>> + break;
>> + }
>> +
>> + if (ret) {
>> + dev_dbg(mmc_dev(host->mmc),
>> + "Fail to set Sampling Fixed Delay with phase = %d degree\n",
>> + phase * 90);
>> + continue;
>> + }
>> +
>> + for (max_delay = min_delay + fine_step;
>> + max_delay < phy_regs->delay_mask;
>> + max_delay += fine_step) {
>> + ret = emmc_phy_do_fix_sampl_delay(host, card, max_delay,
>> + invert, quarter);
>> + if (ret) {
>> + max_delay -= fine_step;
>> + break;
>> + }
>> + }
>> +
>> + if (!ret) {
>> + ret = emmc_phy_do_fix_sampl_delay(host, card,
>> + phy_regs->delay_mask,
>> + invert, quarter);
>> + if (!ret)
>> + max_delay = phy_regs->delay_mask;
>> + }
>> +
>> + /*
>> + * Sampling Fixed Delay line window should be large enough,
>> + * thus the sampling point (the middle of the window)
>> + * can work when environment varies.
>> + * However, there is no clear conclusion how large the window
>> + * should be.
>> + */
>> + if ((max_delay - min_delay) <=
>> + EMMC_PHY_FIXED_DELAY_WINDOW_MIN) {
>> + dev_info(mmc_dev(host->mmc),
>> + "The window size %d with phase = %d degree is too small\n",
>> + max_delay - min_delay, phase * 90);
>> + continue;
>> + }
>> +
>> + delay = (min_delay + max_delay) / 2;
>> + emmc_phy_set_fix_sampl_delay(host, delay, invert, quarter);
>> + dev_dbg(mmc_dev(host->mmc),
>> + "sampling fix delay = %d with phase = %d degree\n",
>> + delay, phase * 90);
>> + return 0;
>> + }
>> +
>> + return -EIO;
>> +}
>> +
>> +static int xenon_emmc_phy_enable_dll(struct sdhci_host *host)
>> +{
>> + u32 reg;
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
>> + u8 timeout;
>> +
>> + if (WARN_ON(host->clock <= MMC_HIGH_52_MAX_DTR))
>> + return -EINVAL;
>> +
>> + reg = sdhci_readl(host, phy_regs->dll_ctrl);
>> + if (reg & DLL_ENABLE)
>> + return 0;
>> +
>> + /* Enable DLL */
>> + reg = sdhci_readl(host, phy_regs->dll_ctrl);
>> + reg |= (DLL_ENABLE | DLL_FAST_LOCK);
>> +
>> + /*
>> + * Set Phase as 90 degree, which is most common value.
>> + * Might set another value if necessary.
>> + * The granularity is 1 degree.
>> + */
>> + reg &= ~((DLL_PHASE_MASK << DLL_PHSEL0_SHIFT) |
>> + (DLL_PHASE_MASK << DLL_PHSEL1_SHIFT));
>> + reg |= ((DLL_PHASE_90_DEGREE << DLL_PHSEL0_SHIFT) |
>> + (DLL_PHASE_90_DEGREE << DLL_PHSEL1_SHIFT));
>> +
>> + reg &= ~DLL_BYPASS_EN;
>> + reg |= phy_regs->dll_update;
>> + if (priv->phy_type == EMMC_5_1_PHY)
>> + reg &= ~DLL_REFCLK_SEL;
>> + sdhci_writel(host, reg, phy_regs->dll_ctrl);
>> +
>> + /* Wait max 32 ms */
>> + timeout = 32;
>> + while (!(sdhci_readw(host, SDHC_SLOT_EXT_PRESENT_STATE) & LOCK_STATE)) {
>> + if (!timeout) {
>> + dev_err(mmc_dev(host->mmc), "Wait for DLL Lock time-out\n");
>> + return -ETIMEDOUT;
>> + }
>> + timeout--;
>> + mdelay(1);
>> + }
>> + return 0;
>> +}
>> +
>> +static int __emmc_phy_config_tuning(struct sdhci_host *host)
>> +{
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + struct emmc_phy_params *params = priv->phy_params;
>> + u32 reg, tuning_step;
>> + int ret;
>> + unsigned long flags;
>> +
>> + if (WARN_ON(host->clock <= MMC_HIGH_52_MAX_DTR))
>> + return -EINVAL;
>> +
>> + spin_lock_irqsave(&host->lock, flags);
>> +
>> + ret = xenon_emmc_phy_enable_dll(host);
>> + if (ret) {
>> + spin_unlock_irqrestore(&host->lock, flags);
>> + return ret;
>> + }
>> +
>> + reg = sdhci_readl(host, SDHC_SLOT_DLL_CUR_DLY_VAL);
>> + tuning_step = reg / params->tun_step_divider;
>> + if (unlikely(tuning_step > TUNING_STEP_MASK)) {
>> + dev_warn(mmc_dev(host->mmc),
>> + "HS200 TUNING_STEP %d is larger than MAX value\n",
>> + tuning_step);
>> + tuning_step = TUNING_STEP_MASK;
>> + }
>> +
>> + reg = sdhci_readl(host, SDHC_SLOT_OP_STATUS_CTRL);
>> + reg &= ~(TUN_CONSECUTIVE_TIMES_MASK << TUN_CONSECUTIVE_TIMES_SHIFT);
>> + reg |= (params->nr_tun_times << TUN_CONSECUTIVE_TIMES_SHIFT);
>> + reg &= ~(TUNING_STEP_MASK << TUNING_STEP_SHIFT);
>> + reg |= (tuning_step << TUNING_STEP_SHIFT);
>> + sdhci_writel(host, reg, SDHC_SLOT_OP_STATUS_CTRL);
>> +
>> + spin_unlock_irqrestore(&host->lock, flags);
>> + return 0;
>> +}
>> +
>> +static int xenon_emmc_phy_config_tuning(struct sdhci_host *host)
>> +{
>> + return __emmc_phy_config_tuning(host);
>> +}
>> +
>> +static void xenon_emmc_phy_strobe_delay_adj(struct sdhci_host *host,
>> + struct mmc_card *card)
>> +{
>> + u32 reg;
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + unsigned long flags;
>> +
>> + if (host->clock <= MMC_HIGH_52_MAX_DTR)
>> + return;
>> +
>> + dev_dbg(mmc_dev(host->mmc), "starts HS400 strobe delay adjustment\n");
>> +
>> + spin_lock_irqsave(&host->lock, flags);
>> +
>> + xenon_emmc_phy_enable_dll(host);
>> +
>> + /* Enable SDHC Data Strobe */
>> + reg = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
>> + reg |= ENABLE_DATA_STROBE;
>> + sdhci_writel(host, reg, SDHC_SLOT_EMMC_CTRL);
>> +
>> + /* Set Data Strobe Pull down */
>> + if (priv->phy_type == EMMC_5_0_PHY) {
>> + reg = sdhci_readl(host, EMMC_5_0_PHY_PAD_CONTROL);
>> + reg |= EMMC5_FC_QSP_PD;
>> + reg &= ~EMMC5_FC_QSP_PU;
>> + sdhci_writel(host, reg, EMMC_5_0_PHY_PAD_CONTROL);
>> + } else {
>> + reg = sdhci_readl(host, EMMC_PHY_PAD_CONTROL1);
>> + reg |= EMMC5_1_FC_QSP_PD;
>> + reg &= ~EMMC5_1_FC_QSP_PU;
>> + sdhci_writel(host, reg, EMMC_PHY_PAD_CONTROL1);
>> + }
>> + spin_unlock_irqrestore(&host->lock, flags);
>> +}
>> +
>> +#define LOGIC_TIMING_VALUE 0x00AA8977
>> +
>> +static void xenon_emmc_phy_set(struct sdhci_host *host,
>> + unsigned char timing)
>> +{
>> + u32 reg;
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + struct emmc_phy_params *params = priv->phy_params;
>> + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
>> + struct mmc_card *card = priv->card_candidate;
>> + unsigned long flags;
>> +
>> + dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting starts\n");
>> +
>> + spin_lock_irqsave(&host->lock, flags);
>> +
>> + /* Setup pad, set bit[28] and bits[26:24] */
>> + reg = sdhci_readl(host, phy_regs->pad_ctrl);
>> + reg |= (FC_DQ_RECEN | FC_CMD_RECEN | FC_QSP_RECEN | OEN_QSN);
>> + /*
>> + * All FC_XX_RECEIVCE should be set as CMOS Type
>> + */
>> + reg |= FC_ALL_CMOS_RECEIVER;
>> + sdhci_writel(host, reg, phy_regs->pad_ctrl);
>> +
>> + /* Set CMD and DQ Pull Up */
>> + if (priv->phy_type == EMMC_5_0_PHY) {
>> + reg = sdhci_readl(host, EMMC_5_0_PHY_PAD_CONTROL);
>> + reg |= (EMMC5_FC_CMD_PU | EMMC5_FC_DQ_PU);
>> + reg &= ~(EMMC5_FC_CMD_PD | EMMC5_FC_DQ_PD);
>> + sdhci_writel(host, reg, EMMC_5_0_PHY_PAD_CONTROL);
>> + } else {
>> + reg = sdhci_readl(host, EMMC_PHY_PAD_CONTROL1);
>> + reg |= (EMMC5_1_FC_CMD_PU | EMMC5_1_FC_DQ_PU);
>> + reg &= ~(EMMC5_1_FC_CMD_PD | EMMC5_1_FC_DQ_PD);
>> + sdhci_writel(host, reg, EMMC_PHY_PAD_CONTROL1);
>> + }
>> +
>> + if ((timing == MMC_TIMING_LEGACY) || !card)
>> + goto phy_init;
>> +
>> + /*
>> + * FIXME: should depends on the specific board timing.
>> + */
>> + if ((timing == MMC_TIMING_MMC_HS400) ||
>> + (timing == MMC_TIMING_MMC_HS200) ||
>> + (timing == MMC_TIMING_UHS_SDR50) ||
>> + (timing == MMC_TIMING_UHS_SDR104) ||
>> + (timing == MMC_TIMING_UHS_DDR50) ||
>> + (timing == MMC_TIMING_UHS_SDR25) ||
>> + (timing == MMC_TIMING_MMC_DDR52)) {
>> + reg = sdhci_readl(host, phy_regs->timing_adj);
>> + reg &= ~OUTPUT_QSN_PHASE_SELECT;
>> + sdhci_writel(host, reg, phy_regs->timing_adj);
>> + }
>> +
>> + /*
>> + * If SDIO card, set SDIO Mode
>> + * Otherwise, clear SDIO Mode and Slow Mode
>> + */
>> + if (mmc_card_sdio(card)) {
>> + reg = sdhci_readl(host, phy_regs->timing_adj);
>> + reg |= TIMING_ADJUST_SDIO_MODE;
>> +
>> + if ((timing == MMC_TIMING_UHS_SDR25) ||
>> + (timing == MMC_TIMING_UHS_SDR12) ||
>> + (timing == MMC_TIMING_SD_HS) ||
>> + (timing == MMC_TIMING_LEGACY))
>> + reg |= TIMING_ADJUST_SLOW_MODE;
>> +
>> + sdhci_writel(host, reg, phy_regs->timing_adj);
>> + } else {
>> + reg = sdhci_readl(host, phy_regs->timing_adj);
>> + reg &= ~(TIMING_ADJUST_SDIO_MODE | TIMING_ADJUST_SLOW_MODE);
>> + sdhci_writel(host, reg, phy_regs->timing_adj);
>> + }
>> +
>> + if (((timing == MMC_TIMING_UHS_SDR50) ||
>> + (timing == MMC_TIMING_UHS_SDR25) ||
>> + (timing == MMC_TIMING_UHS_SDR12) ||
>> + (timing == MMC_TIMING_SD_HS) ||
>> + (timing == MMC_TIMING_MMC_HS) ||
>> + (timing == MMC_TIMING_LEGACY)) && params->slow_mode) {
>> + reg = sdhci_readl(host, phy_regs->timing_adj);
>> + reg |= TIMING_ADJUST_SLOW_MODE;
>> + sdhci_writel(host, reg, phy_regs->timing_adj);
>> + }
>> +
>> + /*
>> + * Set preferred ZNR and ZPR value
>> + * The ZNR and ZPR value vary between different boards.
>> + * Define them both in sdhci-xenon-emmc-phy.h.
>> + */
>> + reg = sdhci_readl(host, phy_regs->pad_ctrl2);
>> + reg &= ~((ZNR_MASK << ZNR_SHIFT) | ZPR_MASK);
>> + reg |= ((params->znr << ZNR_SHIFT) | params->zpr);
>> + sdhci_writel(host, reg, phy_regs->pad_ctrl2);
>> +
>> + /*
>> + * When setting EMMC_PHY_FUNC_CONTROL register,
>> + * SD clock should be disabled
>> + */
>> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
>> + reg &= ~SDHCI_CLOCK_CARD_EN;
>> + sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL);
>> +
>> + if ((timing == MMC_TIMING_UHS_DDR50) ||
>> + (timing == MMC_TIMING_MMC_HS400) ||
>> + (timing == MMC_TIMING_MMC_DDR52)) {
>> + reg = sdhci_readl(host, phy_regs->func_ctrl);
>> + reg |= (DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) | CMD_DDR_MODE;
>> + sdhci_writel(host, reg, phy_regs->func_ctrl);
>> + }
>> +
>> + if (timing == MMC_TIMING_MMC_HS400) {
>> + reg = sdhci_readl(host, phy_regs->func_ctrl);
>> + reg &= ~DQ_ASYNC_MODE;
>> + sdhci_writel(host, reg, phy_regs->func_ctrl);
>> + }
>> +
>> + /* Enable bus clock */
>> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
>> + reg |= SDHCI_CLOCK_CARD_EN;
>> + sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL);
>> +
>> + if (timing == MMC_TIMING_MMC_HS400)
>> + /* Hardware team recommend a value for HS400 */
>> + sdhci_writel(host, LOGIC_TIMING_VALUE,
>> + phy_regs->logic_timing_adj);
>> +
>> +phy_init:
>> + xenon_emmc_phy_init(host);
>> +
>> + spin_unlock_irqrestore(&host->lock, flags);
>> +
>> + dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting completes\n");
>> +}
>> +
>> +static int get_dt_pad_ctrl_data(struct sdhci_host *host,
>> + struct device_node *np,
>> + struct emmc_phy_params *params)
>> +{
>> + int ret = 0;
>> + const char *name;
>> + struct resource iomem;
>> +
>> + if (of_device_is_compatible(np, "marvell,armada-3700-sdhci"))
>> + params->pad_ctrl.set_soc_pad = armada_3700_soc_pad_voltage_set;
>> + else
>> + return 0;
>> +
>> + if (of_address_to_resource(np, 1, &iomem)) {
>> + dev_err(mmc_dev(host->mmc), "Unable to find SOC PAD ctrl register address for %s\n",
>> + np->name);
>> + return -EINVAL;
>> + }
>> +
>> + params->pad_ctrl.reg = devm_ioremap_resource(mmc_dev(host->mmc),
>> + &iomem);
>> + if (IS_ERR(params->pad_ctrl.reg)) {
>> + dev_err(mmc_dev(host->mmc), "Unable to get SOC PHY PAD ctrl regiser for %s\n",
>> + np->name);
>> + return PTR_ERR(params->pad_ctrl.reg);
>> + }
>> +
>> + ret = of_property_read_string(np, "xenon,pad-type", &name);
>> + if (ret) {
>> + dev_err(mmc_dev(host->mmc), "Unable to determine SOC PHY PAD ctrl type\n");
>> + return ret;
>> + }
>> + if (!strcmp(name, "sd")) {
>> + params->pad_ctrl.pad_type = SOC_PAD_SD;
>> + } else if (!strcmp(name, "fixed-1-8v")) {
>> + params->pad_ctrl.pad_type = SOC_PAD_FIXED_1_8V;
>> + } else {
>> + dev_err(mmc_dev(host->mmc), "Unsupported SOC PHY PAD ctrl type %s\n",
>> + name);
>> + return -EINVAL;
>> + }
>> +
>> + return ret;
>> +}
>> +
>> +static int emmc_phy_parse_param_dt(struct sdhci_host *host,
>> + struct device_node *np,
>> + struct emmc_phy_params *params)
>> +{
>> + u32 value;
>> +
>> + if (of_property_read_bool(np, "xenon,phy-slow-mode"))
>> + params->slow_mode = true;
>> + else
>> + params->slow_mode = false;
>> +
>> + if (!of_property_read_u32(np, "xenon,phy-znr", &value))
>> + params->znr = value & ZNR_MASK;
>> + else
>> + params->znr = ZNR_DEF_VALUE;
>> +
>> + if (!of_property_read_u32(np, "xenon,phy-zpr", &value))
>> + params->zpr = value & ZPR_MASK;
>> + else
>> + params->zpr = ZPR_DEF_VALUE;
>> +
>> + if (!of_property_read_u32(np, "xenon,phy-nr-tun-times", &value))
>> + params->nr_tun_times = value & TUN_CONSECUTIVE_TIMES_MASK;
>> + else
>> + params->nr_tun_times = TUN_CONSECUTIVE_TIMES;
>> +
>> + if (!of_property_read_u32(np, "xenon,phy-tun-step-divider", &value))
>> + params->tun_step_divider = value & 0xFF;
>> + else
>> + params->tun_step_divider = TUNING_STEP_DIVIDER;
>> +
>> + return get_dt_pad_ctrl_data(host, np, params);
>> +}
>> +
>> +/*
>> + * SDH PHY configuration and operations
>> + */
>> +static int xenon_sdh_phy_set_fix_sampl_delay(struct sdhci_host *host,
>> + unsigned int delay, bool invert)
>> +{
>> + u32 reg;
>> + unsigned long flags;
>> + int ret;
>> +
>> + if (invert)
>> + invert = 0x1;
>> + else
>> + invert = 0x0;
>> +
>> + spin_lock_irqsave(&host->lock, flags);
>> +
>> + /* Disable SDCLK */
>> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
>> + reg &= ~(SDHCI_CLOCK_CARD_EN | SDHCI_CLOCK_INT_EN);
>> + sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
>> +
>> + udelay(200);
>> +
>> + /* Setup Sampling fix delay */
>> + reg = sdhci_readl(host, SDHC_SLOT_OP_STATUS_CTRL);
>> + reg &= ~(SDH_PHY_FIXED_DELAY_MASK |
>> + (0x1 << FORCE_SEL_INVERSE_CLK_SHIFT));
>> + reg |= ((delay & SDH_PHY_FIXED_DELAY_MASK) |
>> + (invert << FORCE_SEL_INVERSE_CLK_SHIFT));
>> + sdhci_writel(host, reg, SDHC_SLOT_OP_STATUS_CTRL);
>> +
>> + /* Enable SD internal clock */
>> + ret = enable_xenon_internal_clk(host);
>> +
>> + /* Enable SDCLK */
>> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
>> + reg |= SDHCI_CLOCK_CARD_EN;
>> + sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
>> +
>> + udelay(200);
>> +
>> + spin_unlock_irqrestore(&host->lock, flags);
>> + return ret;
>> +}
>> +
>> +static int sdh_phy_do_fix_sampl_delay(struct sdhci_host *host,
>> + struct mmc_card *card,
>> + unsigned int delay, bool invert)
>> +{
>> + int ret;
>> +
>> + xenon_sdh_phy_set_fix_sampl_delay(host, delay, invert);
>> +
>> + ret = xenon_delay_adj_test(card);
>> + if (ret) {
>> + dev_dbg(mmc_dev(host->mmc),
>> + "fail when sampling fix delay = %d, phase = %d degree\n",
>> + delay, invert * 180);
>> + return -1;
>> + }
>> + return 0;
>> +}
>> +
>> +#define SDH_PHY_COARSE_FIX_DELAY (SDH_PHY_FIXED_DELAY_MASK / 2)
>> +#define SDH_PHY_FINE_FIX_DELAY (SDH_PHY_COARSE_FIX_DELAY / 4)
>> +
>> +static int xenon_sdh_phy_fix_sampl_delay_adj(struct sdhci_host *host,
>> + struct mmc_card *card)
>> +{
>> + u32 reg;
>> + bool dll_enable = false;
>> + unsigned int min_delay, max_delay, delay;
>> + const bool sampl_edge[] = {
>> + false,
>> + true,
>> + };
>> + int i, nr;
>> + int ret;
>> +
>> + if (host->clock > HIGH_SPEED_MAX_DTR) {
>> + /* Enable DLL when SDCLK is higher than 50MHz */
>> + reg = sdhci_readl(host, SDH_PHY_SLOT_DLL_CTRL);
>> + if (!(reg & SDH_PHY_ENABLE_DLL)) {
>> + reg |= (SDH_PHY_ENABLE_DLL | SDH_PHY_FAST_LOCK_EN);
>> + sdhci_writel(host, reg, SDH_PHY_SLOT_DLL_CTRL);
>> + mdelay(1);
>> +
>> + reg = sdhci_readl(host, SDH_PHY_SLOT_DLL_PHASE_SEL);
>> + reg |= SDH_PHY_DLL_UPDATE_TUNING;
>> + sdhci_writel(host, reg, SDH_PHY_SLOT_DLL_PHASE_SEL);
>> + }
>> + dll_enable = true;
>> + }
>> +
>> + nr = dll_enable ? ARRAY_SIZE(sampl_edge) : 1;
>> + for (i = 0; i < nr; i++) {
>> + for (min_delay = 0; min_delay <= SDH_PHY_FIXED_DELAY_MASK;
>> + min_delay += SDH_PHY_COARSE_FIX_DELAY) {
>> + ret = sdh_phy_do_fix_sampl_delay(host, card, min_delay,
>> + sampl_edge[i]);
>> + if (!ret)
>> + break;
>> + }
>> +
>> + if (ret) {
>> + dev_dbg(mmc_dev(host->mmc),
>> + "Fail to set Fixed Sampling Delay with %s edge\n",
>> + sampl_edge[i] ? "negative" : "positive");
>> + continue;
>> + }
>> +
>> + for (max_delay = min_delay + SDH_PHY_FINE_FIX_DELAY;
>> + max_delay < SDH_PHY_FIXED_DELAY_MASK;
>> + max_delay += SDH_PHY_FINE_FIX_DELAY) {
>> + ret = sdh_phy_do_fix_sampl_delay(host, card, max_delay,
>> + sampl_edge[i]);
>> + if (ret) {
>> + max_delay -= SDH_PHY_FINE_FIX_DELAY;
>> + break;
>> + }
>> + }
>> +
>> + if (!ret) {
>> + delay = SDH_PHY_FIXED_DELAY_MASK;
>> + ret = sdh_phy_do_fix_sampl_delay(host, card, delay,
>> + sampl_edge[i]);
>> + if (!ret)
>> + max_delay = SDH_PHY_FIXED_DELAY_MASK;
>> + }
>> +
>> + if ((max_delay - min_delay) <= SDH_PHY_FIXED_DELAY_WINDOW_MIN) {
>> + dev_info(mmc_dev(host->mmc),
>> + "The window size %d with %s edge is too small\n",
>> + max_delay - min_delay,
>> + sampl_edge[i] ? "negative" : "positive");
>> + continue;
>> + }
>> +
>> + delay = (min_delay + max_delay) / 2;
>> + xenon_sdh_phy_set_fix_sampl_delay(host, delay, sampl_edge[i]);
>> + dev_dbg(mmc_dev(host->mmc), "sampling fix delay = %d with %s edge\n",
>> + delay, sampl_edge[i] ? "negative" : "positive");
>> + return 0;
>> + }
>> + return -EIO;
>> +}
>> +
>> +static const struct xenon_phy_ops sdh_phy_ops = {
>> + .fix_sampl_delay_adj = xenon_sdh_phy_fix_sampl_delay_adj,
>> +};
>> +
>> +static int alloc_sdh_phy(struct sdhci_xenon_priv *priv)
>> +{
>> + priv->phy_params = NULL;
>> + priv->phy_ops = &sdh_phy_ops;
>> + return 0;
>> +}
>> +
>> +/*
>> + * Common functions for all PHYs
>> + */
>> +void xenon_soc_pad_ctrl(struct sdhci_host *host,
>> + unsigned char signal_voltage)
>> +{
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +
>> + if (priv->phy_ops->set_soc_pad)
>> + priv->phy_ops->set_soc_pad(host, signal_voltage);
>> +}
>> +
>> +static int __xenon_emmc_delay_adj_test(struct mmc_card *card)
>> +{
>> + int err;
>> + u8 *ext_csd = NULL;
>> +
>> + err = mmc_get_ext_csd(card, &ext_csd);
>> + kfree(ext_csd);
>> +
>> + return err;
>> +}
>> +
>> +static int __xenon_sdio_delay_adj_test(struct mmc_card *card)
>> +{
>> + struct mmc_command cmd = {0};
>> + int err;
>> +
>> + cmd.opcode = SD_IO_RW_DIRECT;
>> + cmd.flags = MMC_RSP_R5 | MMC_CMD_AC;
>> +
>> + err = mmc_wait_for_cmd(card->host, &cmd, 0);
>> + if (err)
>> + return err;
>> +
>> + if (cmd.resp[0] & R5_ERROR)
>> + return -EIO;
>> + if (cmd.resp[0] & R5_FUNCTION_NUMBER)
>> + return -EINVAL;
>> + if (cmd.resp[0] & R5_OUT_OF_RANGE)
>> + return -ERANGE;
>> + return 0;
>> +}
>> +
>> +static int __xenon_sd_delay_adj_test(struct mmc_card *card)
>> +{
>> + struct mmc_command cmd = {0};
>> + int err;
>> +
>> + cmd.opcode = MMC_SEND_STATUS;
>> + cmd.arg = card->rca << 16;
>> + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
>> +
>> + err = mmc_wait_for_cmd(card->host, &cmd, 0);
>> + return err;
>> +}
>> +
>> +static int xenon_delay_adj_test(struct mmc_card *card)
>> +{
>> + WARN_ON(!card);
>> + WARN_ON(!card->host);
>> +
>> + if (mmc_card_mmc(card))
>> + return __xenon_emmc_delay_adj_test(card);
>> + else if (mmc_card_sd(card))
>> + return __xenon_sd_delay_adj_test(card);
>> + else if (mmc_card_sdio(card))
>> + return __xenon_sdio_delay_adj_test(card);
>> + else
>> + return -EINVAL;
>> +}
>> +
>> +static void xenon_phy_set(struct sdhci_host *host, unsigned char timing)
>> +{
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +
>> + if (priv->phy_ops->phy_set)
>> + priv->phy_ops->phy_set(host, timing);
>> +}
>> +
>> +static void xenon_hs400_strobe_delay_adj(struct sdhci_host *host,
>> + struct mmc_card *card)
>> +{
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +
>> + if (WARN_ON(!mmc_card_hs400(card)))
>> + return;
>> +
>> + /* Enable the DLL to automatically adjust HS400 strobe delay.
>> + */
>> + if (priv->phy_ops->strobe_delay_adj)
>> + priv->phy_ops->strobe_delay_adj(host, card);
>> +}
>> +
>> +static int xenon_fix_sampl_delay_adj(struct sdhci_host *host,
>> + struct mmc_card *card)
>> +{
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +
>> + if (priv->phy_ops->fix_sampl_delay_adj)
>> + return priv->phy_ops->fix_sampl_delay_adj(host, card);
>> +
>> + return 0;
>> +}
>> +
>> +/*
>> + * xenon_delay_adj should not be called inside IRQ context,
>> + * either Hard IRQ or Softirq.
>> + */
>> +static int xenon_hs_delay_adj(struct sdhci_host *host,
>> + struct mmc_card *card)
>> +{
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + int ret = 0;
>> +
>> + if (WARN_ON(host->clock <= DEFAULT_SDCLK_FREQ))
>> + return -EINVAL;
>> +
>> + if (mmc_card_hs400(card)) {
>> + xenon_hs400_strobe_delay_adj(host, card);
>> + return 0;
>> + }
>> +
>> + if (((priv->phy_type == EMMC_5_1_PHY) ||
>> + (priv->phy_type == EMMC_5_0_PHY)) &&
>> + (mmc_card_hs200(card) ||
>> + (host->timing == MMC_TIMING_UHS_SDR104))) {
>> + ret = xenon_emmc_phy_config_tuning(host);
>> + if (!ret)
>> + return 0;
>> + }
>> +
>> + ret = xenon_fix_sampl_delay_adj(host, card);
>> + if (ret)
>> + dev_err(mmc_dev(host->mmc), "fails sampling fixed delay adjustment\n");
>> + return ret;
>> +}
>> +
>> +int xenon_phy_adj(struct sdhci_host *host, struct mmc_ios *ios)
>> +{
>> + struct mmc_host *mmc = host->mmc;
>> + struct mmc_card *card;
>> + int ret = 0;
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +
>> + if (!host->clock) {
>> + priv->clock = 0;
>> + return 0;
>> + }
>> +
>> + /*
>> + * The timing, frequency or bus width is changed,
>> + * better to set eMMC PHY based on current setting
>> + * and adjust Xenon SDHC delay.
>> + */
>> + if ((host->clock == priv->clock) &&
>> + (ios->bus_width == priv->bus_width) &&
>> + (ios->timing == priv->timing))
>> + return 0;
>> +
>> + xenon_phy_set(host, ios->timing);
>> +
>> + /* Update the record */
>> + priv->bus_width = ios->bus_width;
>> + /* Temp stage from HS200 to HS400 */
>> + if (((priv->timing == MMC_TIMING_MMC_HS200) &&
>> + (ios->timing == MMC_TIMING_MMC_HS)) ||
>> + ((ios->timing == MMC_TIMING_MMC_HS) &&
>> + (priv->clock > host->clock))) {
>> + priv->timing = ios->timing;
>> + priv->clock = host->clock;
>> + return 0;
>> + }
>> + priv->timing = ios->timing;
>> + priv->clock = host->clock;
>> +
>> + /* Legacy mode is a special case */
>> + if (ios->timing == MMC_TIMING_LEGACY)
>> + return 0;
>> +
>> + card = priv->card_candidate;
>> + if (unlikely(!card)) {
>> + dev_warn(mmc_dev(mmc), "card is not present\n");
>> + return -EINVAL;
>> + }
>> +
>> + if (host->clock > DEFAULT_SDCLK_FREQ)
>> + ret = xenon_hs_delay_adj(host, card);
>> + return ret;
>> +}
>> +
>> +static int add_xenon_phy(struct device_node *np, struct sdhci_host *host,
>> + const char *phy_name)
>> +{
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + int i, ret;
>> +
>> + for (i = 0; i < NR_PHY_TYPES; i++) {
>> + if (!strcmp(phy_name, phy_types[i])) {
>> + priv->phy_type = i;
>> + break;
>> + }
>> + }
>> + if (i == NR_PHY_TYPES) {
>> + dev_err(mmc_dev(host->mmc),
>> + "Unable to determine PHY name %s. Use default eMMC 5.1 PHY\n",
>> + phy_name);
>> + priv->phy_type = EMMC_5_1_PHY;
>> + }
>> +
>> + if (priv->phy_type == SDH_PHY) {
>> + return alloc_sdh_phy(priv);
>> + } else if ((priv->phy_type == EMMC_5_0_PHY) ||
>> + (priv->phy_type == EMMC_5_1_PHY)) {
>> + ret = alloc_emmc_phy(priv);
>> + if (ret)
>> + return ret;
>> + return emmc_phy_parse_param_dt(host, np, priv->phy_params);
>> + }
>> +
>> + return -EINVAL;
>> +}
>> +
>> +int xenon_phy_parse_dt(struct device_node *np, struct sdhci_host *host)
>> +{
>> + const char *phy_type = NULL;
>> +
>> + if (!of_property_read_string(np, "xenon,phy-type", &phy_type))
>> + return add_xenon_phy(np, host, phy_type);
>> +
>> + dev_err(mmc_dev(host->mmc), "Fail to get Xenon PHY type. Use default eMMC 5.1 PHY\n");
>> + return add_xenon_phy(np, host, "emmc 5.1 phy");
>> +}
>> diff --git a/drivers/mmc/host/sdhci-xenon-phy.h b/drivers/mmc/host/sdhci-xenon-phy.h
>> new file mode 100644
>> index 000000000000..4373c71d3b7b
>> --- /dev/null
>> +++ b/drivers/mmc/host/sdhci-xenon-phy.h
>> @@ -0,0 +1,157 @@
>> +/* linux/drivers/mmc/host/sdhci-xenon-phy.h
>> + *
>> + * Author: Hu Ziji <huziji@marvell.com>
>> + * Date: 2016-8-24
>> + *
>> + * Copyright (C) 2016 Marvell, All Rights Reserved.
>> + *
>> + * 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.
>> + */
>> +#ifndef SDHCI_XENON_PHY_H_
>> +#define SDHCI_XENON_PHY_H_
>> +
>> +#include <linux/types.h>
>> +#include "sdhci.h"
>> +
>> +/* Register base for eMMC PHY 5.0 Version */
>> +#define EMMC_5_0_PHY_REG_BASE 0x0160
>> +/* Register base for eMMC PHY 5.1 Version */
>> +#define EMMC_PHY_REG_BASE 0x0170
>> +
>> +#define EMMC_PHY_TIMING_ADJUST EMMC_PHY_REG_BASE
>> +#define EMMC_5_0_PHY_TIMING_ADJUST EMMC_5_0_PHY_REG_BASE
>> +#define TIMING_ADJUST_SLOW_MODE BIT(29)
>> +#define TIMING_ADJUST_SDIO_MODE BIT(28)
>> +#define OUTPUT_QSN_PHASE_SELECT BIT(17)
>> +#define SAMPL_INV_QSP_PHASE_SELECT BIT(18)
>> +#define SAMPL_INV_QSP_PHASE_SELECT_SHIFT 18
>> +#define PHY_INITIALIZAION BIT(31)
>> +#define WAIT_CYCLE_BEFORE_USING_MASK 0xF
>> +#define WAIT_CYCLE_BEFORE_USING_SHIFT 12
>> +#define FC_SYNC_EN_DURATION_MASK 0xF
>> +#define FC_SYNC_EN_DURATION_SHIFT 8
>> +#define FC_SYNC_RST_EN_DURATION_MASK 0xF
>> +#define FC_SYNC_RST_EN_DURATION_SHIFT 4
>> +#define FC_SYNC_RST_DURATION_MASK 0xF
>> +#define FC_SYNC_RST_DURATION_SHIFT 0
>> +
>> +#define EMMC_PHY_FUNC_CONTROL (EMMC_PHY_REG_BASE + 0x4)
>> +#define EMMC_5_0_PHY_FUNC_CONTROL (EMMC_5_0_PHY_REG_BASE + 0x4)
>> +#define ASYNC_DDRMODE_MASK BIT(23)
>> +#define ASYNC_DDRMODE_SHIFT 23
>> +#define CMD_DDR_MODE BIT(16)
>> +#define DQ_DDR_MODE_SHIFT 8
>> +#define DQ_DDR_MODE_MASK 0xFF
>> +#define DQ_ASYNC_MODE BIT(4)
>> +
>> +#define EMMC_PHY_PAD_CONTROL (EMMC_PHY_REG_BASE + 0x8)
>> +#define EMMC_5_0_PHY_PAD_CONTROL (EMMC_5_0_PHY_REG_BASE + 0x8)
>> +#define REC_EN_SHIFT 24
>> +#define REC_EN_MASK 0xF
>> +#define FC_DQ_RECEN BIT(24)
>> +#define FC_CMD_RECEN BIT(25)
>> +#define FC_QSP_RECEN BIT(26)
>> +#define FC_QSN_RECEN BIT(27)
>> +#define OEN_QSN BIT(28)
>> +#define AUTO_RECEN_CTRL BIT(30)
>> +#define FC_ALL_CMOS_RECEIVER 0xF000
>> +
>> +#define EMMC5_FC_QSP_PD BIT(18)
>> +#define EMMC5_FC_QSP_PU BIT(22)
>> +#define EMMC5_FC_CMD_PD BIT(17)
>> +#define EMMC5_FC_CMD_PU BIT(21)
>> +#define EMMC5_FC_DQ_PD BIT(16)
>> +#define EMMC5_FC_DQ_PU BIT(20)
>> +
>> +#define EMMC_PHY_PAD_CONTROL1 (EMMC_PHY_REG_BASE + 0xC)
>> +#define EMMC5_1_FC_QSP_PD BIT(9)
>> +#define EMMC5_1_FC_QSP_PU BIT(25)
>> +#define EMMC5_1_FC_CMD_PD BIT(8)
>> +#define EMMC5_1_FC_CMD_PU BIT(24)
>> +#define EMMC5_1_FC_DQ_PD 0xFF
>> +#define EMMC5_1_FC_DQ_PU (0xFF << 16)
>> +
>> +#define EMMC_PHY_PAD_CONTROL2 (EMMC_PHY_REG_BASE + 0x10)
>> +#define EMMC_5_0_PHY_PAD_CONTROL2 (EMMC_5_0_PHY_REG_BASE + 0xC)
>> +#define ZNR_MASK 0x1F
>> +#define ZNR_SHIFT 8
>> +#define ZPR_MASK 0x1F
>> +/* Perferred ZNR and ZPR value vary between different boards.
>> + * The specific ZNR and ZPR value should be defined here
>> + * according to board actual timing.
>> + */
>> +#define ZNR_DEF_VALUE 0xF
>> +#define ZPR_DEF_VALUE 0xF
>> +
>> +#define EMMC_PHY_DLL_CONTROL (EMMC_PHY_REG_BASE + 0x14)
>> +#define EMMC_5_0_PHY_DLL_CONTROL (EMMC_5_0_PHY_REG_BASE + 0x10)
>> +#define DLL_ENABLE BIT(31)
>> +#define DLL_UPDATE_STROBE_5_0 BIT(30)
>> +#define DLL_REFCLK_SEL BIT(30)
>> +#define DLL_UPDATE BIT(23)
>> +#define DLL_PHSEL1_SHIFT 24
>> +#define DLL_PHSEL0_SHIFT 16
>> +#define DLL_PHASE_MASK 0x3F
>> +#define DLL_PHASE_90_DEGREE 0x1F
>> +#define DLL_FAST_LOCK BIT(5)
>> +#define DLL_GAIN2X BIT(3)
>> +#define DLL_BYPASS_EN BIT(0)
>> +
>> +#define EMMC_5_0_PHY_LOGIC_TIMING_ADJUST (EMMC_5_0_PHY_REG_BASE + 0x14)
>> +#define EMMC_PHY_LOGIC_TIMING_ADJUST (EMMC_PHY_REG_BASE + 0x18)
>> +
>> +enum sampl_fix_delay_phase {
>> + PHASE_0_DEGREE = 0x0,
>> + PHASE_90_DEGREE = 0x1,
>> + PHASE_180_DEGREE = 0x2,
>> + PHASE_270_DEGREE = 0x3,
>> +};
>> +
>> +#define SDH_PHY_SLOT_DLL_CTRL (0x0138)
>> +#define SDH_PHY_ENABLE_DLL BIT(1)
>> +#define SDH_PHY_FAST_LOCK_EN BIT(5)
>> +
>> +#define SDH_PHY_SLOT_DLL_PHASE_SEL (0x013C)
>> +#define SDH_PHY_DLL_UPDATE_TUNING BIT(15)
>> +
>> +enum soc_pad_ctrl_type {
>> + SOC_PAD_SD,
>> + SOC_PAD_FIXED_1_8V,
>> +};
>> +
>> +/*
>> + * List offset of PHY registers and some special register values
>> + * in eMMC PHY 5.0 or eMMC PHY 5.1
>> + */
>> +struct xenon_emmc_phy_regs {
>> + /* Offset of Timing Adjust register */
>> + u16 timing_adj;
>> + /* Offset of Func Control register */
>> + u16 func_ctrl;
>> + /* Offset of Pad Control register */
>> + u16 pad_ctrl;
>> + /* Offset of Pad Control register */
>> + u16 pad_ctrl2;
>> + /* Offset of DLL Control register */
>> + u16 dll_ctrl;
>> + /* Offset of Logic Timing Adjust register */
>> + u16 logic_timing_adj;
>> + /* Max value of eMMC Fixed Sampling Delay */
>> + u32 delay_mask;
>> + /* DLL Update Enable bit */
>> + u32 dll_update;
>> +};
>> +
>> +struct xenon_phy_ops {
>> + void (*strobe_delay_adj)(struct sdhci_host *host,
>> + struct mmc_card *card);
>> + int (*fix_sampl_delay_adj)(struct sdhci_host *host,
>> + struct mmc_card *card);
>> + void (*phy_set)(struct sdhci_host *host, unsigned char timing);
>> + void (*set_soc_pad)(struct sdhci_host *host,
>> + unsigned char signal_voltage);
>> +};
>> +#endif
>> diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c
>> index 03ba183494d3..4d7d871544fc 100644
>> --- a/drivers/mmc/host/sdhci-xenon.c
>> +++ b/drivers/mmc/host/sdhci-xenon.c
>> @@ -224,6 +224,7 @@ static void xenon_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
>> spin_unlock_irqrestore(&host->lock, flags);
>>
>> sdhci_set_ios(mmc, ios);
>> + xenon_phy_adj(host, ios);
>>
>> if (host->clock > DEFAULT_SDCLK_FREQ) {
>> spin_lock_irqsave(&host->lock, flags);
>> @@ -309,6 +310,8 @@ static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
>> */
>> enable_xenon_internal_clk(host);
>>
>> + xenon_soc_pad_ctrl(host, ios->signal_voltage);
>> +
>> if (priv->card_candidate) {
>> if (mmc_card_mmc(priv->card_candidate))
>> return xenon_emmc_signal_voltage_switch(mmc, ios);
>> @@ -453,6 +456,7 @@ static int xenon_probe_dt(struct platform_device *pdev)
>> sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
>> }
>>
>> + err = xenon_phy_parse_dt(np, host);
>> return err;
>> }
>>
>> diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h
>> index c2370493fbe8..06e5261a563c 100644
>> --- a/drivers/mmc/host/sdhci-xenon.h
>> +++ b/drivers/mmc/host/sdhci-xenon.h
>> @@ -15,6 +15,7 @@
>> #include <linux/mmc/card.h>
>> #include <linux/of.h>
>> #include "sdhci.h"
>> +#include "sdhci-xenon-phy.h"
>>
>> /* Register Offset of SD Host Controller SOCP self-defined register */
>> #define SDHC_SYS_CFG_INFO 0x0104
>> @@ -76,6 +77,7 @@
>> #define MMC_TIMING_FAKE 0xFF
>>
>> #define DEFAULT_SDCLK_FREQ (400000)
>> +#define LOWEST_SDCLK_FREQ (100000)
>>
>> /* Xenon specific Mode Select value */
>> #define XENON_SDHCI_CTRL_HS200 0x5
>> @@ -97,6 +99,15 @@ struct sdhci_xenon_priv {
>> /* Slot idx */
>> u8 slot_idx;
>>
>> + int phy_type;
>> + /*
>> + * Contains board-specific PHY parameters
>> + * passed from device tree.
>> + */
>> + void *phy_params;
>> + const struct xenon_phy_ops *phy_ops;
>> + struct xenon_emmc_phy_regs *emmc_phy_regs;
>> +
>> /*
>> * When initializing card, Xenon has to determine card type and
>> * adjust Sampling Fixed delay.
>> @@ -131,4 +142,10 @@ static inline int enable_xenon_internal_clk(struct sdhci_host *host)
>>
>> return 0;
>> }
>> +
>> +int xenon_phy_adj(struct sdhci_host *host, struct mmc_ios *ios);
>> +int xenon_phy_parse_dt(struct device_node *np,
>> + struct sdhci_host *host);
>> +void xenon_soc_pad_ctrl(struct sdhci_host *host,
>> + unsigned char signal_voltage);
>> #endif
>>
>
>
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH 2/10] mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
From: Ziji Hu @ 2016-10-08 6:26 UTC (permalink / raw)
To: Shawn Lin, Gregory CLEMENT, Ulf Hansson, Adrian Hunter, linux-mmc
Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth, Rob Herring,
devicetree, Thomas Petazzoni, linux-arm-kernel, Jack(SH) Zhu,
Jimmy Xu, Jisheng Zhang, Nadav Haklai, Ryan Gao, Doug Jones,
Shiwu Zhang, Victor Gu, Wei(SOCP) Liu, Wilson Ding, Xueping Liu,
Hilbert Zhang, Liuliu Zhao, Peng Zhu, Yu Cao <yuca>
In-Reply-To: <9bfe6e9a-c06e-481a-b5ea-af58820093bf@rock-chips.com>
Hi Shawn,
On 2016/10/8 10:40, Shawn Lin wrote:
> Hi,
>
> 在 2016/10/7 23:22, Gregory CLEMENT 写道:
>> From: Ziji Hu <huziji@marvell.com>
>>
>> Export sdhci_start_signal_voltage_switch() from sdhci.c.
>> Thus vendor sdhci driver can implement its own signal voltage
>> switch routine.
>>
>
> You can overwtite this callback within your driver itself.
> That is what other sdhci variant drivers did, so patch 1-3 are
> unnecessary.
Thanks a lot for your reply.
Our SDHC driver just requests some pre- and post- operations besides common standard SDHC functions.
Overwriting those common functions costs too much.
Thank you.
Best regards,
Hu Ziji
>
>> Signed-off-by: Hu Ziji <huziji@marvell.com>
>> Reviewed-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
>> Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
>> ---
>> drivers/mmc/host/sdhci.c | 5 +++--
>> drivers/mmc/host/sdhci.h | 2 ++
>> 2 files changed, 5 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
>> index d4bb818c52d5..2250ea22231f 100644
>> --- a/drivers/mmc/host/sdhci.c
>> +++ b/drivers/mmc/host/sdhci.c
>> @@ -1828,8 +1828,8 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
>> spin_unlock_irqrestore(&host->lock, flags);
>> }
>>
>> -static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
>> - struct mmc_ios *ios)
>> +int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
>> + struct mmc_ios *ios)
>> {
>> struct sdhci_host *host = mmc_priv(mmc);
>> u16 ctrl;
>> @@ -1921,6 +1921,7 @@ static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
>> return 0;
>> }
>> }
>> +EXPORT_SYMBOL_GPL(sdhci_start_signal_voltage_switch);
>>
>> static int sdhci_card_busy(struct mmc_host *mmc)
>> {
>> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
>> index 21dc80b8ae3d..c38ab65b9a97 100644
>> --- a/drivers/mmc/host/sdhci.h
>> +++ b/drivers/mmc/host/sdhci.h
>> @@ -687,6 +687,8 @@ void sdhci_set_bus_width(struct sdhci_host *host, int width);
>> void sdhci_reset(struct sdhci_host *host, u8 mask);
>> void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing);
>> void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
>> +int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
>> + struct mmc_ios *ios);
>>
>> #ifdef CONFIG_PM
>> extern int sdhci_suspend_host(struct sdhci_host *host);
>>
>
>
^ permalink raw reply
* RE: [v12, 0/8] Fix eSDHC host version register bug
From: Y.B. Lu @ 2016-10-08 3:28 UTC (permalink / raw)
To: Y.B. Lu, linux-mmc@vger.kernel.org, ulf.hansson@linaro.org,
Scott Wood, Arnd Bergmann
Cc: linuxppc-dev@lists.ozlabs.org, devicetree@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, linux-clk@vger.kernel.org,
linux-i2c@vger.kernel.org, iommu@lists.linux-foundation.org,
netdev@vger.kernel.org, Mark Rutland, Rob Herring, Russell King,
Jochen Friedrich, Joerg Roedel, Claudiu Manoil, Bhupesh Sharma
In-Reply-To: <1474441040-11946-1-git-send-email-yangbo.lu@nxp.com>
Hi Uffe, Arnd and Scott,
Any comments on this latest patcheset?
Could we consider to merge it if no any other changes needed?
:)
Thanks.
Best regards,
Yangbo Lu
> -----Original Message-----
> From: Y.B. Lu
> Sent: Monday, September 26, 2016 11:15 AM
> To: linux-mmc@vger.kernel.org; ulf.hansson@linaro.org; Scott Wood; Arnd
> Bergmann
> Cc: linuxppc-dev@lists.ozlabs.org; devicetree@vger.kernel.org; linux-arm-
> kernel@lists.infradead.org; linux-kernel@vger.kernel.org; linux-
> clk@vger.kernel.org; linux-i2c@vger.kernel.org; iommu@lists.linux-
> foundation.org; netdev@vger.kernel.org; Mark Rutland; Rob Herring;
> Russell King; Jochen Friedrich; Joerg Roedel; Claudiu Manoil; Bhupesh
> Sharma; Qiang Zhao; Kumar Gala; Santosh Shilimkar; Leo Li; X.B. Xie; M.H.
> Lian
> Subject: RE: [v12, 0/8] Fix eSDHC host version register bug
>
> Any comments about this version patchset ?
>
> :)
>
>
> > -----Original Message-----
> > From: Yangbo Lu [mailto:yangbo.lu@nxp.com]
> > Sent: Wednesday, September 21, 2016 2:57 PM
> > To: linux-mmc@vger.kernel.org; ulf.hansson@linaro.org; Scott Wood;
> > Arnd Bergmann
> > Cc: linuxppc-dev@lists.ozlabs.org; devicetree@vger.kernel.org;
> > linux-arm- kernel@lists.infradead.org; linux-kernel@vger.kernel.org;
> > linux- clk@vger.kernel.org; linux-i2c@vger.kernel.org;
> > iommu@lists.linux- foundation.org; netdev@vger.kernel.org; Mark
> > Rutland; Rob Herring; Russell King; Jochen Friedrich; Joerg Roedel;
> > Claudiu Manoil; Bhupesh Sharma; Qiang Zhao; Kumar Gala; Santosh
> Shilimkar; Leo Li; X.B. Xie; M.H.
> > Lian; Y.B. Lu
> > Subject: [v12, 0/8] Fix eSDHC host version register bug
> >
> > This patchset is used to fix a host version register bug in the T4240-
> > R1.0-R2.0 eSDHC controller. To match the SoC version and revision, 10
> > previous version patchsets had tried many methods but all of them were
> > rejected by reviewers.
> > Such as
> > - dts compatible method
> > - syscon method
> > - ifdef PPC method
> > - GUTS driver getting SVR method
> > Anrd suggested a soc_device_match method in v10, and this is the only
> > available method left now. This v11 patchset introduces the
> > soc_device_match interface in soc driver.
> >
> > The first six patches of Yangbo are to add the GUTS driver. This is
> > used to register a soc device which contain soc version and revision
> > information.
> > The other two patches introduce the soc_device_match method in soc
> > driver and apply it on esdhc driver to fix this bug.
> >
> > Arnd Bergmann (1):
> > base: soc: introduce soc_device_match() interface
> >
> > Yangbo Lu (7):
> > dt: bindings: update Freescale DCFG compatible
> > ARM64: dts: ls2080a: add device configuration node
> > dt: bindings: move guts devicetree doc out of powerpc directory
> > powerpc/fsl: move mpc85xx.h to include/linux/fsl
> > soc: fsl: add GUTS driver for QorIQ platforms
> > MAINTAINERS: add entry for Freescale SoC drivers
> > mmc: sdhci-of-esdhc: fix host version for T4240-R1.0-R2.0
> >
> > Documentation/devicetree/bindings/arm/fsl.txt | 6 +-
> > .../bindings/{powerpc => soc}/fsl/guts.txt | 3 +
> > MAINTAINERS | 11 +-
> > arch/arm64/boot/dts/freescale/fsl-ls2080a.dtsi | 6 +
> > arch/powerpc/kernel/cpu_setup_fsl_booke.S | 2 +-
> > arch/powerpc/sysdev/fsl_pci.c | 2 +-
> > drivers/base/Kconfig | 1 +
> > drivers/base/soc.c | 66 ++++++
> > drivers/clk/clk-qoriq.c | 3 +-
> > drivers/i2c/busses/i2c-mpc.c | 2 +-
> > drivers/iommu/fsl_pamu.c | 3 +-
> > drivers/mmc/host/Kconfig | 1 +
> > drivers/mmc/host/sdhci-of-esdhc.c | 20 ++
> > drivers/net/ethernet/freescale/gianfar.c | 2 +-
> > drivers/soc/Kconfig | 2 +-
> > drivers/soc/fsl/Kconfig | 19 ++
> > drivers/soc/fsl/Makefile | 1 +
> > drivers/soc/fsl/guts.c | 257
> > +++++++++++++++++++++
> > include/linux/fsl/guts.h | 125 ++++++----
> > .../asm/mpc85xx.h => include/linux/fsl/svr.h | 4 +-
> > include/linux/sys_soc.h | 3 +
> > 21 files changed, 478 insertions(+), 61 deletions(-) rename
> > Documentation/devicetree/bindings/{powerpc => soc}/fsl/guts.txt (91%)
> > create mode 100644 drivers/soc/fsl/Kconfig create mode 100644
> > drivers/soc/fsl/guts.c rename arch/powerpc/include/asm/mpc85xx.h =>
> > include/linux/fsl/svr.h (97%)
> >
> > --
> > 2.1.0.27.g96db324
^ permalink raw reply
* Re: [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
From: Shawn Lin @ 2016-10-08 2:44 UTC (permalink / raw)
To: Gregory CLEMENT, Ulf Hansson, Adrian Hunter,
linux-mmc-u79uwXL29TY76Z2rM5mHXA
Cc: shawn.lin-TNX95d0MmH7DzftRWevZcw, Jason Cooper, Andrew Lunn,
Sebastian Hesselbarth, Rob Herring,
devicetree-u79uwXL29TY76Z2rM5mHXA, Thomas Petazzoni,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Ziji Hu,
Jack(SH) Zhu, Jimmy Xu, Jisheng Zhang, Nadav Haklai, Ryan Gao,
Doug Jones, Shiwu Zhang, Victor Gu, Wei(SOCP) Liu, Wilson Ding,
Xueping Liu, Hilbert Zhang, Keji Zhang
In-Reply-To: <e08ffb085d02a784b28456ac47fa4dc6540a9139.1475853198.git-series.gregory.clement-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
在 2016/10/7 23:22, Gregory CLEMENT 写道:
> From: Ziji Hu <huziji-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>
>
> Marvell Xenon eMMC/SD/SDIO Host Controller contains PHY.
> Three types of PHYs are supported.
>
> Add support to multiple types of PHYs init and configuration.
> Add register definitions of PHYs.
>
> Signed-off-by: Hu Ziji <huziji-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>
> Reviewed-by: Gregory CLEMENT <gregory.clement-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
> Signed-off-by: Gregory CLEMENT <gregory.clement-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
> ---
> MAINTAINERS | 1 +-
> drivers/mmc/host/Makefile | 2 +-
> drivers/mmc/host/sdhci-xenon-phy.c | 1141 +++++++++++++++++++++++++++++-
> drivers/mmc/host/sdhci-xenon-phy.h | 157 ++++-
> drivers/mmc/host/sdhci-xenon.c | 4 +-
> drivers/mmc/host/sdhci-xenon.h | 17 +-
> 6 files changed, 1321 insertions(+), 1 deletion(-)
> create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c
> create mode 100644 drivers/mmc/host/sdhci-xenon-phy.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 859420e5dfd3..b5673c2ee5f2 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -7583,6 +7583,7 @@ M: Ziji Hu <huziji-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>
> L: linux-mmc-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> S: Supported
> F: drivers/mmc/host/sdhci-xenon.*
> +F: drivers/mmc/host/sdhci-xenon-phy.*
drivers/mmc/host/sdhci-xenon* shoube enough
> F: Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>
> MATROX FRAMEBUFFER DRIVER
> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
> index 75eaf743486c..4f2854556ff7 100644
> --- a/drivers/mmc/host/Makefile
> +++ b/drivers/mmc/host/Makefile
> @@ -82,4 +82,4 @@ ifeq ($(CONFIG_CB710_DEBUG),y)
> endif
>
> obj-$(CONFIG_MMC_SDHCI_XENON) += sdhci-xenon-driver.o
> -sdhci-xenon-driver-y += sdhci-xenon.o
> +sdhci-xenon-driver-y += sdhci-xenon.o sdhci-xenon-phy.o
> diff --git a/drivers/mmc/host/sdhci-xenon-phy.c b/drivers/mmc/host/sdhci-xenon-phy.c
> new file mode 100644
> index 000000000000..4eb8fea1bec9
> --- /dev/null
> +++ b/drivers/mmc/host/sdhci-xenon-phy.c
Well, it's legit to use phy API and move your phy
operations to PHY subsystem. :)
> @@ -0,0 +1,1141 @@
> +/*
> + * PHY support for Xenon SDHC
> + *
> + * Copyright (C) 2016 Marvell, All Rights Reserved.
> + *
> + * Author: Hu Ziji <huziji-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>
> + * Date: 2016-8-24
> + *
> + * 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 version 2.
> + */
> +
> +#include <linux/slab.h>
> +#include <linux/delay.h>
> +#include <linux/of_address.h>
> +#include <linux/mmc/host.h>
> +#include <linux/mmc/mmc.h>
> +#include <linux/mmc/card.h>
> +#include <linux/mmc/sdio.h>
> +
> +#include "sdhci.h"
> +#include "sdhci-pltfm.h"
> +#include "sdhci-xenon.h"
> +
> +static const char * const phy_types[] = {
> + "sdh phy",
> + "emmc 5.0 phy",
> + "emmc 5.1 phy"
> +};
> +
> +enum phy_type_enum {
> + SDH_PHY,
> + EMMC_5_0_PHY,
> + EMMC_5_1_PHY,
> + NR_PHY_TYPES
> +};
> +
> +struct soc_pad_ctrl_table {
> + const char *soc;
> + void (*set_soc_pad)(struct sdhci_host *host,
> + unsigned char signal_voltage);
> +};
> +
> +struct soc_pad_ctrl {
> + /* Register address of SOC PHY PAD ctrl */
> + void __iomem *reg;
> + /* SOC PHY PAD ctrl type */
> + enum soc_pad_ctrl_type pad_type;
> + /* SOC specific operation to set SOC PHY PAD */
> + void (*set_soc_pad)(struct sdhci_host *host,
> + unsigned char signal_voltage);
> +};
> +
> +static struct xenon_emmc_phy_regs xenon_emmc_5_0_phy_regs = {
> + .timing_adj = EMMC_5_0_PHY_TIMING_ADJUST,
> + .func_ctrl = EMMC_5_0_PHY_FUNC_CONTROL,
> + .pad_ctrl = EMMC_5_0_PHY_PAD_CONTROL,
> + .pad_ctrl2 = EMMC_5_0_PHY_PAD_CONTROL2,
> + .dll_ctrl = EMMC_5_0_PHY_DLL_CONTROL,
> + .logic_timing_adj = EMMC_5_0_PHY_LOGIC_TIMING_ADJUST,
> + .delay_mask = EMMC_5_0_PHY_FIXED_DELAY_MASK,
> + .dll_update = DLL_UPDATE_STROBE_5_0,
> +};
> +
> +static struct xenon_emmc_phy_regs xenon_emmc_5_1_phy_regs = {
> + .timing_adj = EMMC_PHY_TIMING_ADJUST,
> + .func_ctrl = EMMC_PHY_FUNC_CONTROL,
> + .pad_ctrl = EMMC_PHY_PAD_CONTROL,
> + .pad_ctrl2 = EMMC_PHY_PAD_CONTROL2,
> + .dll_ctrl = EMMC_PHY_DLL_CONTROL,
> + .logic_timing_adj = EMMC_PHY_LOGIC_TIMING_ADJUST,
> + .delay_mask = EMMC_PHY_FIXED_DELAY_MASK,
> + .dll_update = DLL_UPDATE,
> +};
> +
> +static int xenon_delay_adj_test(struct mmc_card *card);
> +
> +/*
> + * eMMC PHY configuration and operations
> + */
> +struct emmc_phy_params {
> + bool slow_mode;
> +
> + u8 znr;
> + u8 zpr;
> +
> + /* Nr of consecutive Sampling Points of a Valid Sampling Window */
> + u8 nr_tun_times;
> + /* Divider for calculating Tuning Step */
> + u8 tun_step_divider;
> +
> + struct soc_pad_ctrl pad_ctrl;
> +};
> +
> +static void xenon_emmc_phy_strobe_delay_adj(struct sdhci_host *host,
> + struct mmc_card *card);
> +static int xenon_emmc_phy_fix_sampl_delay_adj(struct sdhci_host *host,
> + struct mmc_card *card);
> +static void xenon_emmc_phy_set(struct sdhci_host *host,
> + unsigned char timing);
> +static void xenon_emmc_set_soc_pad(struct sdhci_host *host,
> + unsigned char signal_voltage);
> +
> +static const struct xenon_phy_ops emmc_phy_ops = {
> + .strobe_delay_adj = xenon_emmc_phy_strobe_delay_adj,
> + .fix_sampl_delay_adj = xenon_emmc_phy_fix_sampl_delay_adj,
> + .phy_set = xenon_emmc_phy_set,
> + .set_soc_pad = xenon_emmc_set_soc_pad,
> +};
> +
> +static int alloc_emmc_phy(struct sdhci_xenon_priv *priv)
> +{
> + struct emmc_phy_params *params;
> +
> + params = kzalloc(sizeof(*params), GFP_KERNEL);
> + if (!params)
> + return -ENOMEM;
> +
> + priv->phy_params = params;
> + priv->phy_ops = &emmc_phy_ops;
> + if (priv->phy_type == EMMC_5_0_PHY)
> + priv->emmc_phy_regs = &xenon_emmc_5_0_phy_regs;
> + else
> + priv->emmc_phy_regs = &xenon_emmc_5_1_phy_regs;
> +
> + return 0;
> +}
> +
> +static int xenon_emmc_phy_init(struct sdhci_host *host)
> +{
> + u32 reg;
> + u32 wait, clock;
> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
> +
> + reg = sdhci_readl(host, phy_regs->timing_adj);
> + reg |= PHY_INITIALIZAION;
> + sdhci_writel(host, reg, phy_regs->timing_adj);
> +
> + /* Add duration of FC_SYNC_RST */
> + wait = ((reg >> FC_SYNC_RST_DURATION_SHIFT) &
> + FC_SYNC_RST_DURATION_MASK);
> + /* Add interval between FC_SYNC_EN and FC_SYNC_RST */
> + wait += ((reg >> FC_SYNC_RST_EN_DURATION_SHIFT) &
> + FC_SYNC_RST_EN_DURATION_MASK);
> + /* Add duration of asserting FC_SYNC_EN */
> + wait += ((reg >> FC_SYNC_EN_DURATION_SHIFT) &
> + FC_SYNC_EN_DURATION_MASK);
> + /* Add duration of waiting for PHY */
> + wait += ((reg >> WAIT_CYCLE_BEFORE_USING_SHIFT) &
> + WAIT_CYCLE_BEFORE_USING_MASK);
> + /* 4 addtional bus clock and 4 AXI bus clock are required */
> + wait += 8;
> + wait <<= 20;
> +
> + clock = host->clock;
> + if (!clock)
> + /* Use the possibly slowest bus frequency value */
> + clock = LOWEST_SDCLK_FREQ;
> + /* get the wait time */
> + wait /= clock;
> + wait++;
> + /* wait for host eMMC PHY init completes */
> + udelay(wait);
> +
> + reg = sdhci_readl(host, phy_regs->timing_adj);
> + reg &= PHY_INITIALIZAION;
> + if (reg) {
> + dev_err(mmc_dev(host->mmc), "eMMC PHY init cannot complete after %d us\n",
> + wait);
> + return -ETIMEDOUT;
> + }
> +
> + return 0;
> +}
> +
> +#define ARMADA_3700_SOC_PAD_1_8V 0x1
> +#define ARMADA_3700_SOC_PAD_3_3V 0x0
> +
> +static void armada_3700_soc_pad_voltage_set(struct sdhci_host *host,
> + unsigned char signal_voltage)
> +{
> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> + struct emmc_phy_params *params = priv->phy_params;
> +
> + if (params->pad_ctrl.pad_type == SOC_PAD_FIXED_1_8V) {
> + writel(ARMADA_3700_SOC_PAD_1_8V, params->pad_ctrl.reg);
> + } else if (params->pad_ctrl.pad_type == SOC_PAD_SD) {
> + if (signal_voltage == MMC_SIGNAL_VOLTAGE_180)
> + writel(ARMADA_3700_SOC_PAD_1_8V, params->pad_ctrl.reg);
> + else if (signal_voltage == MMC_SIGNAL_VOLTAGE_330)
> + writel(ARMADA_3700_SOC_PAD_3_3V, params->pad_ctrl.reg);
> + }
> +}
> +
> +static void xenon_emmc_set_soc_pad(struct sdhci_host *host,
> + unsigned char signal_voltage)
> +{
> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> + struct emmc_phy_params *params = priv->phy_params;
> +
> + if (!params->pad_ctrl.reg)
> + return;
> +
> + if (params->pad_ctrl.set_soc_pad)
> + params->pad_ctrl.set_soc_pad(host, signal_voltage);
> +}
> +
> +static int emmc_phy_set_fix_sampl_delay(struct sdhci_host *host,
> + unsigned int delay,
> + bool invert,
> + bool delay_90_degree)
> +{
> + u32 reg;
> + unsigned long flags;
> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
> + int ret = 0;
> +
> + spin_lock_irqsave(&host->lock, flags);
> +
> + /* Setup Sampling fix delay */
> + reg = sdhci_readl(host, SDHC_SLOT_OP_STATUS_CTRL);
> + reg &= ~phy_regs->delay_mask;
> + reg |= delay & phy_regs->delay_mask;
> + sdhci_writel(host, reg, SDHC_SLOT_OP_STATUS_CTRL);
> +
> + if (priv->phy_type == EMMC_5_0_PHY) {
> + /* set 90 degree phase if necessary */
> + reg &= ~DELAY_90_DEGREE_MASK_EMMC5;
> + reg |= (delay_90_degree << DELAY_90_DEGREE_SHIFT_EMMC5);
> + sdhci_writel(host, reg, SDHC_SLOT_OP_STATUS_CTRL);
> + }
> +
> + /* Disable SDCLK */
> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
> + reg &= ~(SDHCI_CLOCK_CARD_EN | SDHCI_CLOCK_INT_EN);
> + sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
> +
> + udelay(200);
> +
> + if (priv->phy_type == EMMC_5_1_PHY) {
> + /* set 90 degree phase if necessary */
> + reg = sdhci_readl(host, EMMC_PHY_FUNC_CONTROL);
> + reg &= ~ASYNC_DDRMODE_MASK;
> + reg |= (delay_90_degree << ASYNC_DDRMODE_SHIFT);
> + sdhci_writel(host, reg, EMMC_PHY_FUNC_CONTROL);
> + }
> +
> + /* Setup Inversion of Sampling edge */
> + reg = sdhci_readl(host, phy_regs->timing_adj);
> + reg &= ~SAMPL_INV_QSP_PHASE_SELECT;
> + reg |= (invert << SAMPL_INV_QSP_PHASE_SELECT_SHIFT);
> + sdhci_writel(host, reg, phy_regs->timing_adj);
> +
> + /* Enable SD internal clock */
> + ret = enable_xenon_internal_clk(host);
> + if (ret)
> + goto out;
> +
> + /* Enable SDCLK */
> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
> + reg |= SDHCI_CLOCK_CARD_EN;
> + sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
> +
> + udelay(200);
> +
> + /*
> + * Has to re-initialize eMMC PHY here to active PHY
> + * because later get status cmd will be issued.
> + */
> + ret = xenon_emmc_phy_init(host);
> +
> +out:
> + spin_unlock_irqrestore(&host->lock, flags);
> + return ret;
> +}
> +
> +static int emmc_phy_do_fix_sampl_delay(struct sdhci_host *host,
> + struct mmc_card *card,
> + unsigned int delay,
> + bool invert, bool quarter)
> +{
> + int ret;
> +
> + emmc_phy_set_fix_sampl_delay(host, delay, invert, quarter);
> +
> + ret = xenon_delay_adj_test(card);
> + if (ret) {
> + dev_dbg(mmc_dev(host->mmc),
> + "fail when sampling fix delay = %d, phase = %d degree\n",
> + delay, invert * 180 + quarter * 90);
> + return -1;
> + }
> + return 0;
> +}
> +
> +static int xenon_emmc_phy_fix_sampl_delay_adj(struct sdhci_host *host,
> + struct mmc_card *card)
> +{
> + enum sampl_fix_delay_phase phase;
> + int idx, nr_pair;
> + int ret;
> + unsigned int delay;
> + unsigned int min_delay, max_delay;
> + bool invert, quarter;
> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
> + u32 coarse_step, fine_step;
> + const enum sampl_fix_delay_phase delay_edge[] = {
> + PHASE_0_DEGREE,
> + PHASE_180_DEGREE,
> + PHASE_90_DEGREE,
> + PHASE_270_DEGREE
> + };
> +
> + coarse_step = phy_regs->delay_mask >> 1;
> + fine_step = coarse_step >> 2;
> +
> + nr_pair = ARRAY_SIZE(delay_edge);
> +
> + for (idx = 0; idx < nr_pair; idx++) {
> + phase = delay_edge[idx];
> + invert = (phase & 0x2) ? true : false;
> + quarter = (phase & 0x1) ? true : false;
> +
> + /* increase delay value to get fix delay */
> + for (min_delay = 0;
> + min_delay <= phy_regs->delay_mask;
> + min_delay += coarse_step) {
> + ret = emmc_phy_do_fix_sampl_delay(host, card, min_delay,
> + invert, quarter);
> + if (!ret)
> + break;
> + }
> +
> + if (ret) {
> + dev_dbg(mmc_dev(host->mmc),
> + "Fail to set Sampling Fixed Delay with phase = %d degree\n",
> + phase * 90);
> + continue;
> + }
> +
> + for (max_delay = min_delay + fine_step;
> + max_delay < phy_regs->delay_mask;
> + max_delay += fine_step) {
> + ret = emmc_phy_do_fix_sampl_delay(host, card, max_delay,
> + invert, quarter);
> + if (ret) {
> + max_delay -= fine_step;
> + break;
> + }
> + }
> +
> + if (!ret) {
> + ret = emmc_phy_do_fix_sampl_delay(host, card,
> + phy_regs->delay_mask,
> + invert, quarter);
> + if (!ret)
> + max_delay = phy_regs->delay_mask;
> + }
> +
> + /*
> + * Sampling Fixed Delay line window should be large enough,
> + * thus the sampling point (the middle of the window)
> + * can work when environment varies.
> + * However, there is no clear conclusion how large the window
> + * should be.
> + */
> + if ((max_delay - min_delay) <=
> + EMMC_PHY_FIXED_DELAY_WINDOW_MIN) {
> + dev_info(mmc_dev(host->mmc),
> + "The window size %d with phase = %d degree is too small\n",
> + max_delay - min_delay, phase * 90);
> + continue;
> + }
> +
> + delay = (min_delay + max_delay) / 2;
> + emmc_phy_set_fix_sampl_delay(host, delay, invert, quarter);
> + dev_dbg(mmc_dev(host->mmc),
> + "sampling fix delay = %d with phase = %d degree\n",
> + delay, phase * 90);
> + return 0;
> + }
> +
> + return -EIO;
> +}
> +
> +static int xenon_emmc_phy_enable_dll(struct sdhci_host *host)
> +{
> + u32 reg;
> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
> + u8 timeout;
> +
> + if (WARN_ON(host->clock <= MMC_HIGH_52_MAX_DTR))
> + return -EINVAL;
> +
> + reg = sdhci_readl(host, phy_regs->dll_ctrl);
> + if (reg & DLL_ENABLE)
> + return 0;
> +
> + /* Enable DLL */
> + reg = sdhci_readl(host, phy_regs->dll_ctrl);
> + reg |= (DLL_ENABLE | DLL_FAST_LOCK);
> +
> + /*
> + * Set Phase as 90 degree, which is most common value.
> + * Might set another value if necessary.
> + * The granularity is 1 degree.
> + */
> + reg &= ~((DLL_PHASE_MASK << DLL_PHSEL0_SHIFT) |
> + (DLL_PHASE_MASK << DLL_PHSEL1_SHIFT));
> + reg |= ((DLL_PHASE_90_DEGREE << DLL_PHSEL0_SHIFT) |
> + (DLL_PHASE_90_DEGREE << DLL_PHSEL1_SHIFT));
> +
> + reg &= ~DLL_BYPASS_EN;
> + reg |= phy_regs->dll_update;
> + if (priv->phy_type == EMMC_5_1_PHY)
> + reg &= ~DLL_REFCLK_SEL;
> + sdhci_writel(host, reg, phy_regs->dll_ctrl);
> +
> + /* Wait max 32 ms */
> + timeout = 32;
> + while (!(sdhci_readw(host, SDHC_SLOT_EXT_PRESENT_STATE) & LOCK_STATE)) {
> + if (!timeout) {
> + dev_err(mmc_dev(host->mmc), "Wait for DLL Lock time-out\n");
> + return -ETIMEDOUT;
> + }
> + timeout--;
> + mdelay(1);
> + }
> + return 0;
> +}
> +
> +static int __emmc_phy_config_tuning(struct sdhci_host *host)
> +{
> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> + struct emmc_phy_params *params = priv->phy_params;
> + u32 reg, tuning_step;
> + int ret;
> + unsigned long flags;
> +
> + if (WARN_ON(host->clock <= MMC_HIGH_52_MAX_DTR))
> + return -EINVAL;
> +
> + spin_lock_irqsave(&host->lock, flags);
> +
> + ret = xenon_emmc_phy_enable_dll(host);
> + if (ret) {
> + spin_unlock_irqrestore(&host->lock, flags);
> + return ret;
> + }
> +
> + reg = sdhci_readl(host, SDHC_SLOT_DLL_CUR_DLY_VAL);
> + tuning_step = reg / params->tun_step_divider;
> + if (unlikely(tuning_step > TUNING_STEP_MASK)) {
> + dev_warn(mmc_dev(host->mmc),
> + "HS200 TUNING_STEP %d is larger than MAX value\n",
> + tuning_step);
> + tuning_step = TUNING_STEP_MASK;
> + }
> +
> + reg = sdhci_readl(host, SDHC_SLOT_OP_STATUS_CTRL);
> + reg &= ~(TUN_CONSECUTIVE_TIMES_MASK << TUN_CONSECUTIVE_TIMES_SHIFT);
> + reg |= (params->nr_tun_times << TUN_CONSECUTIVE_TIMES_SHIFT);
> + reg &= ~(TUNING_STEP_MASK << TUNING_STEP_SHIFT);
> + reg |= (tuning_step << TUNING_STEP_SHIFT);
> + sdhci_writel(host, reg, SDHC_SLOT_OP_STATUS_CTRL);
> +
> + spin_unlock_irqrestore(&host->lock, flags);
> + return 0;
> +}
> +
> +static int xenon_emmc_phy_config_tuning(struct sdhci_host *host)
> +{
> + return __emmc_phy_config_tuning(host);
> +}
> +
> +static void xenon_emmc_phy_strobe_delay_adj(struct sdhci_host *host,
> + struct mmc_card *card)
> +{
> + u32 reg;
> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> + unsigned long flags;
> +
> + if (host->clock <= MMC_HIGH_52_MAX_DTR)
> + return;
> +
> + dev_dbg(mmc_dev(host->mmc), "starts HS400 strobe delay adjustment\n");
> +
> + spin_lock_irqsave(&host->lock, flags);
> +
> + xenon_emmc_phy_enable_dll(host);
> +
> + /* Enable SDHC Data Strobe */
> + reg = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
> + reg |= ENABLE_DATA_STROBE;
> + sdhci_writel(host, reg, SDHC_SLOT_EMMC_CTRL);
> +
> + /* Set Data Strobe Pull down */
> + if (priv->phy_type == EMMC_5_0_PHY) {
> + reg = sdhci_readl(host, EMMC_5_0_PHY_PAD_CONTROL);
> + reg |= EMMC5_FC_QSP_PD;
> + reg &= ~EMMC5_FC_QSP_PU;
> + sdhci_writel(host, reg, EMMC_5_0_PHY_PAD_CONTROL);
> + } else {
> + reg = sdhci_readl(host, EMMC_PHY_PAD_CONTROL1);
> + reg |= EMMC5_1_FC_QSP_PD;
> + reg &= ~EMMC5_1_FC_QSP_PU;
> + sdhci_writel(host, reg, EMMC_PHY_PAD_CONTROL1);
> + }
> + spin_unlock_irqrestore(&host->lock, flags);
> +}
> +
> +#define LOGIC_TIMING_VALUE 0x00AA8977
> +
> +static void xenon_emmc_phy_set(struct sdhci_host *host,
> + unsigned char timing)
> +{
> + u32 reg;
> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> + struct emmc_phy_params *params = priv->phy_params;
> + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
> + struct mmc_card *card = priv->card_candidate;
> + unsigned long flags;
> +
> + dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting starts\n");
> +
> + spin_lock_irqsave(&host->lock, flags);
> +
> + /* Setup pad, set bit[28] and bits[26:24] */
> + reg = sdhci_readl(host, phy_regs->pad_ctrl);
> + reg |= (FC_DQ_RECEN | FC_CMD_RECEN | FC_QSP_RECEN | OEN_QSN);
> + /*
> + * All FC_XX_RECEIVCE should be set as CMOS Type
> + */
> + reg |= FC_ALL_CMOS_RECEIVER;
> + sdhci_writel(host, reg, phy_regs->pad_ctrl);
> +
> + /* Set CMD and DQ Pull Up */
> + if (priv->phy_type == EMMC_5_0_PHY) {
> + reg = sdhci_readl(host, EMMC_5_0_PHY_PAD_CONTROL);
> + reg |= (EMMC5_FC_CMD_PU | EMMC5_FC_DQ_PU);
> + reg &= ~(EMMC5_FC_CMD_PD | EMMC5_FC_DQ_PD);
> + sdhci_writel(host, reg, EMMC_5_0_PHY_PAD_CONTROL);
> + } else {
> + reg = sdhci_readl(host, EMMC_PHY_PAD_CONTROL1);
> + reg |= (EMMC5_1_FC_CMD_PU | EMMC5_1_FC_DQ_PU);
> + reg &= ~(EMMC5_1_FC_CMD_PD | EMMC5_1_FC_DQ_PD);
> + sdhci_writel(host, reg, EMMC_PHY_PAD_CONTROL1);
> + }
> +
> + if ((timing == MMC_TIMING_LEGACY) || !card)
> + goto phy_init;
> +
> + /*
> + * FIXME: should depends on the specific board timing.
> + */
> + if ((timing == MMC_TIMING_MMC_HS400) ||
> + (timing == MMC_TIMING_MMC_HS200) ||
> + (timing == MMC_TIMING_UHS_SDR50) ||
> + (timing == MMC_TIMING_UHS_SDR104) ||
> + (timing == MMC_TIMING_UHS_DDR50) ||
> + (timing == MMC_TIMING_UHS_SDR25) ||
> + (timing == MMC_TIMING_MMC_DDR52)) {
> + reg = sdhci_readl(host, phy_regs->timing_adj);
> + reg &= ~OUTPUT_QSN_PHASE_SELECT;
> + sdhci_writel(host, reg, phy_regs->timing_adj);
> + }
> +
> + /*
> + * If SDIO card, set SDIO Mode
> + * Otherwise, clear SDIO Mode and Slow Mode
> + */
> + if (mmc_card_sdio(card)) {
> + reg = sdhci_readl(host, phy_regs->timing_adj);
> + reg |= TIMING_ADJUST_SDIO_MODE;
> +
> + if ((timing == MMC_TIMING_UHS_SDR25) ||
> + (timing == MMC_TIMING_UHS_SDR12) ||
> + (timing == MMC_TIMING_SD_HS) ||
> + (timing == MMC_TIMING_LEGACY))
> + reg |= TIMING_ADJUST_SLOW_MODE;
> +
> + sdhci_writel(host, reg, phy_regs->timing_adj);
> + } else {
> + reg = sdhci_readl(host, phy_regs->timing_adj);
> + reg &= ~(TIMING_ADJUST_SDIO_MODE | TIMING_ADJUST_SLOW_MODE);
> + sdhci_writel(host, reg, phy_regs->timing_adj);
> + }
> +
> + if (((timing == MMC_TIMING_UHS_SDR50) ||
> + (timing == MMC_TIMING_UHS_SDR25) ||
> + (timing == MMC_TIMING_UHS_SDR12) ||
> + (timing == MMC_TIMING_SD_HS) ||
> + (timing == MMC_TIMING_MMC_HS) ||
> + (timing == MMC_TIMING_LEGACY)) && params->slow_mode) {
> + reg = sdhci_readl(host, phy_regs->timing_adj);
> + reg |= TIMING_ADJUST_SLOW_MODE;
> + sdhci_writel(host, reg, phy_regs->timing_adj);
> + }
> +
> + /*
> + * Set preferred ZNR and ZPR value
> + * The ZNR and ZPR value vary between different boards.
> + * Define them both in sdhci-xenon-emmc-phy.h.
> + */
> + reg = sdhci_readl(host, phy_regs->pad_ctrl2);
> + reg &= ~((ZNR_MASK << ZNR_SHIFT) | ZPR_MASK);
> + reg |= ((params->znr << ZNR_SHIFT) | params->zpr);
> + sdhci_writel(host, reg, phy_regs->pad_ctrl2);
> +
> + /*
> + * When setting EMMC_PHY_FUNC_CONTROL register,
> + * SD clock should be disabled
> + */
> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
> + reg &= ~SDHCI_CLOCK_CARD_EN;
> + sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL);
> +
> + if ((timing == MMC_TIMING_UHS_DDR50) ||
> + (timing == MMC_TIMING_MMC_HS400) ||
> + (timing == MMC_TIMING_MMC_DDR52)) {
> + reg = sdhci_readl(host, phy_regs->func_ctrl);
> + reg |= (DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) | CMD_DDR_MODE;
> + sdhci_writel(host, reg, phy_regs->func_ctrl);
> + }
> +
> + if (timing == MMC_TIMING_MMC_HS400) {
> + reg = sdhci_readl(host, phy_regs->func_ctrl);
> + reg &= ~DQ_ASYNC_MODE;
> + sdhci_writel(host, reg, phy_regs->func_ctrl);
> + }
> +
> + /* Enable bus clock */
> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
> + reg |= SDHCI_CLOCK_CARD_EN;
> + sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL);
> +
> + if (timing == MMC_TIMING_MMC_HS400)
> + /* Hardware team recommend a value for HS400 */
> + sdhci_writel(host, LOGIC_TIMING_VALUE,
> + phy_regs->logic_timing_adj);
> +
> +phy_init:
> + xenon_emmc_phy_init(host);
> +
> + spin_unlock_irqrestore(&host->lock, flags);
> +
> + dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting completes\n");
> +}
> +
> +static int get_dt_pad_ctrl_data(struct sdhci_host *host,
> + struct device_node *np,
> + struct emmc_phy_params *params)
> +{
> + int ret = 0;
> + const char *name;
> + struct resource iomem;
> +
> + if (of_device_is_compatible(np, "marvell,armada-3700-sdhci"))
> + params->pad_ctrl.set_soc_pad = armada_3700_soc_pad_voltage_set;
> + else
> + return 0;
> +
> + if (of_address_to_resource(np, 1, &iomem)) {
> + dev_err(mmc_dev(host->mmc), "Unable to find SOC PAD ctrl register address for %s\n",
> + np->name);
> + return -EINVAL;
> + }
> +
> + params->pad_ctrl.reg = devm_ioremap_resource(mmc_dev(host->mmc),
> + &iomem);
> + if (IS_ERR(params->pad_ctrl.reg)) {
> + dev_err(mmc_dev(host->mmc), "Unable to get SOC PHY PAD ctrl regiser for %s\n",
> + np->name);
> + return PTR_ERR(params->pad_ctrl.reg);
> + }
> +
> + ret = of_property_read_string(np, "xenon,pad-type", &name);
> + if (ret) {
> + dev_err(mmc_dev(host->mmc), "Unable to determine SOC PHY PAD ctrl type\n");
> + return ret;
> + }
> + if (!strcmp(name, "sd")) {
> + params->pad_ctrl.pad_type = SOC_PAD_SD;
> + } else if (!strcmp(name, "fixed-1-8v")) {
> + params->pad_ctrl.pad_type = SOC_PAD_FIXED_1_8V;
> + } else {
> + dev_err(mmc_dev(host->mmc), "Unsupported SOC PHY PAD ctrl type %s\n",
> + name);
> + return -EINVAL;
> + }
> +
> + return ret;
> +}
> +
> +static int emmc_phy_parse_param_dt(struct sdhci_host *host,
> + struct device_node *np,
> + struct emmc_phy_params *params)
> +{
> + u32 value;
> +
> + if (of_property_read_bool(np, "xenon,phy-slow-mode"))
> + params->slow_mode = true;
> + else
> + params->slow_mode = false;
> +
> + if (!of_property_read_u32(np, "xenon,phy-znr", &value))
> + params->znr = value & ZNR_MASK;
> + else
> + params->znr = ZNR_DEF_VALUE;
> +
> + if (!of_property_read_u32(np, "xenon,phy-zpr", &value))
> + params->zpr = value & ZPR_MASK;
> + else
> + params->zpr = ZPR_DEF_VALUE;
> +
> + if (!of_property_read_u32(np, "xenon,phy-nr-tun-times", &value))
> + params->nr_tun_times = value & TUN_CONSECUTIVE_TIMES_MASK;
> + else
> + params->nr_tun_times = TUN_CONSECUTIVE_TIMES;
> +
> + if (!of_property_read_u32(np, "xenon,phy-tun-step-divider", &value))
> + params->tun_step_divider = value & 0xFF;
> + else
> + params->tun_step_divider = TUNING_STEP_DIVIDER;
> +
> + return get_dt_pad_ctrl_data(host, np, params);
> +}
> +
> +/*
> + * SDH PHY configuration and operations
> + */
> +static int xenon_sdh_phy_set_fix_sampl_delay(struct sdhci_host *host,
> + unsigned int delay, bool invert)
> +{
> + u32 reg;
> + unsigned long flags;
> + int ret;
> +
> + if (invert)
> + invert = 0x1;
> + else
> + invert = 0x0;
> +
> + spin_lock_irqsave(&host->lock, flags);
> +
> + /* Disable SDCLK */
> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
> + reg &= ~(SDHCI_CLOCK_CARD_EN | SDHCI_CLOCK_INT_EN);
> + sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
> +
> + udelay(200);
> +
> + /* Setup Sampling fix delay */
> + reg = sdhci_readl(host, SDHC_SLOT_OP_STATUS_CTRL);
> + reg &= ~(SDH_PHY_FIXED_DELAY_MASK |
> + (0x1 << FORCE_SEL_INVERSE_CLK_SHIFT));
> + reg |= ((delay & SDH_PHY_FIXED_DELAY_MASK) |
> + (invert << FORCE_SEL_INVERSE_CLK_SHIFT));
> + sdhci_writel(host, reg, SDHC_SLOT_OP_STATUS_CTRL);
> +
> + /* Enable SD internal clock */
> + ret = enable_xenon_internal_clk(host);
> +
> + /* Enable SDCLK */
> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
> + reg |= SDHCI_CLOCK_CARD_EN;
> + sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
> +
> + udelay(200);
> +
> + spin_unlock_irqrestore(&host->lock, flags);
> + return ret;
> +}
> +
> +static int sdh_phy_do_fix_sampl_delay(struct sdhci_host *host,
> + struct mmc_card *card,
> + unsigned int delay, bool invert)
> +{
> + int ret;
> +
> + xenon_sdh_phy_set_fix_sampl_delay(host, delay, invert);
> +
> + ret = xenon_delay_adj_test(card);
> + if (ret) {
> + dev_dbg(mmc_dev(host->mmc),
> + "fail when sampling fix delay = %d, phase = %d degree\n",
> + delay, invert * 180);
> + return -1;
> + }
> + return 0;
> +}
> +
> +#define SDH_PHY_COARSE_FIX_DELAY (SDH_PHY_FIXED_DELAY_MASK / 2)
> +#define SDH_PHY_FINE_FIX_DELAY (SDH_PHY_COARSE_FIX_DELAY / 4)
> +
> +static int xenon_sdh_phy_fix_sampl_delay_adj(struct sdhci_host *host,
> + struct mmc_card *card)
> +{
> + u32 reg;
> + bool dll_enable = false;
> + unsigned int min_delay, max_delay, delay;
> + const bool sampl_edge[] = {
> + false,
> + true,
> + };
> + int i, nr;
> + int ret;
> +
> + if (host->clock > HIGH_SPEED_MAX_DTR) {
> + /* Enable DLL when SDCLK is higher than 50MHz */
> + reg = sdhci_readl(host, SDH_PHY_SLOT_DLL_CTRL);
> + if (!(reg & SDH_PHY_ENABLE_DLL)) {
> + reg |= (SDH_PHY_ENABLE_DLL | SDH_PHY_FAST_LOCK_EN);
> + sdhci_writel(host, reg, SDH_PHY_SLOT_DLL_CTRL);
> + mdelay(1);
> +
> + reg = sdhci_readl(host, SDH_PHY_SLOT_DLL_PHASE_SEL);
> + reg |= SDH_PHY_DLL_UPDATE_TUNING;
> + sdhci_writel(host, reg, SDH_PHY_SLOT_DLL_PHASE_SEL);
> + }
> + dll_enable = true;
> + }
> +
> + nr = dll_enable ? ARRAY_SIZE(sampl_edge) : 1;
> + for (i = 0; i < nr; i++) {
> + for (min_delay = 0; min_delay <= SDH_PHY_FIXED_DELAY_MASK;
> + min_delay += SDH_PHY_COARSE_FIX_DELAY) {
> + ret = sdh_phy_do_fix_sampl_delay(host, card, min_delay,
> + sampl_edge[i]);
> + if (!ret)
> + break;
> + }
> +
> + if (ret) {
> + dev_dbg(mmc_dev(host->mmc),
> + "Fail to set Fixed Sampling Delay with %s edge\n",
> + sampl_edge[i] ? "negative" : "positive");
> + continue;
> + }
> +
> + for (max_delay = min_delay + SDH_PHY_FINE_FIX_DELAY;
> + max_delay < SDH_PHY_FIXED_DELAY_MASK;
> + max_delay += SDH_PHY_FINE_FIX_DELAY) {
> + ret = sdh_phy_do_fix_sampl_delay(host, card, max_delay,
> + sampl_edge[i]);
> + if (ret) {
> + max_delay -= SDH_PHY_FINE_FIX_DELAY;
> + break;
> + }
> + }
> +
> + if (!ret) {
> + delay = SDH_PHY_FIXED_DELAY_MASK;
> + ret = sdh_phy_do_fix_sampl_delay(host, card, delay,
> + sampl_edge[i]);
> + if (!ret)
> + max_delay = SDH_PHY_FIXED_DELAY_MASK;
> + }
> +
> + if ((max_delay - min_delay) <= SDH_PHY_FIXED_DELAY_WINDOW_MIN) {
> + dev_info(mmc_dev(host->mmc),
> + "The window size %d with %s edge is too small\n",
> + max_delay - min_delay,
> + sampl_edge[i] ? "negative" : "positive");
> + continue;
> + }
> +
> + delay = (min_delay + max_delay) / 2;
> + xenon_sdh_phy_set_fix_sampl_delay(host, delay, sampl_edge[i]);
> + dev_dbg(mmc_dev(host->mmc), "sampling fix delay = %d with %s edge\n",
> + delay, sampl_edge[i] ? "negative" : "positive");
> + return 0;
> + }
> + return -EIO;
> +}
> +
> +static const struct xenon_phy_ops sdh_phy_ops = {
> + .fix_sampl_delay_adj = xenon_sdh_phy_fix_sampl_delay_adj,
> +};
> +
> +static int alloc_sdh_phy(struct sdhci_xenon_priv *priv)
> +{
> + priv->phy_params = NULL;
> + priv->phy_ops = &sdh_phy_ops;
> + return 0;
> +}
> +
> +/*
> + * Common functions for all PHYs
> + */
> +void xenon_soc_pad_ctrl(struct sdhci_host *host,
> + unsigned char signal_voltage)
> +{
> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +
> + if (priv->phy_ops->set_soc_pad)
> + priv->phy_ops->set_soc_pad(host, signal_voltage);
> +}
> +
> +static int __xenon_emmc_delay_adj_test(struct mmc_card *card)
> +{
> + int err;
> + u8 *ext_csd = NULL;
> +
> + err = mmc_get_ext_csd(card, &ext_csd);
> + kfree(ext_csd);
> +
> + return err;
> +}
> +
> +static int __xenon_sdio_delay_adj_test(struct mmc_card *card)
> +{
> + struct mmc_command cmd = {0};
> + int err;
> +
> + cmd.opcode = SD_IO_RW_DIRECT;
> + cmd.flags = MMC_RSP_R5 | MMC_CMD_AC;
> +
> + err = mmc_wait_for_cmd(card->host, &cmd, 0);
> + if (err)
> + return err;
> +
> + if (cmd.resp[0] & R5_ERROR)
> + return -EIO;
> + if (cmd.resp[0] & R5_FUNCTION_NUMBER)
> + return -EINVAL;
> + if (cmd.resp[0] & R5_OUT_OF_RANGE)
> + return -ERANGE;
> + return 0;
> +}
> +
> +static int __xenon_sd_delay_adj_test(struct mmc_card *card)
> +{
> + struct mmc_command cmd = {0};
> + int err;
> +
> + cmd.opcode = MMC_SEND_STATUS;
> + cmd.arg = card->rca << 16;
> + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
> +
> + err = mmc_wait_for_cmd(card->host, &cmd, 0);
> + return err;
> +}
> +
> +static int xenon_delay_adj_test(struct mmc_card *card)
> +{
> + WARN_ON(!card);
> + WARN_ON(!card->host);
> +
> + if (mmc_card_mmc(card))
> + return __xenon_emmc_delay_adj_test(card);
> + else if (mmc_card_sd(card))
> + return __xenon_sd_delay_adj_test(card);
> + else if (mmc_card_sdio(card))
> + return __xenon_sdio_delay_adj_test(card);
> + else
> + return -EINVAL;
> +}
> +
> +static void xenon_phy_set(struct sdhci_host *host, unsigned char timing)
> +{
> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +
> + if (priv->phy_ops->phy_set)
> + priv->phy_ops->phy_set(host, timing);
> +}
> +
> +static void xenon_hs400_strobe_delay_adj(struct sdhci_host *host,
> + struct mmc_card *card)
> +{
> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +
> + if (WARN_ON(!mmc_card_hs400(card)))
> + return;
> +
> + /* Enable the DLL to automatically adjust HS400 strobe delay.
> + */
> + if (priv->phy_ops->strobe_delay_adj)
> + priv->phy_ops->strobe_delay_adj(host, card);
> +}
> +
> +static int xenon_fix_sampl_delay_adj(struct sdhci_host *host,
> + struct mmc_card *card)
> +{
> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +
> + if (priv->phy_ops->fix_sampl_delay_adj)
> + return priv->phy_ops->fix_sampl_delay_adj(host, card);
> +
> + return 0;
> +}
> +
> +/*
> + * xenon_delay_adj should not be called inside IRQ context,
> + * either Hard IRQ or Softirq.
> + */
> +static int xenon_hs_delay_adj(struct sdhci_host *host,
> + struct mmc_card *card)
> +{
> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> + int ret = 0;
> +
> + if (WARN_ON(host->clock <= DEFAULT_SDCLK_FREQ))
> + return -EINVAL;
> +
> + if (mmc_card_hs400(card)) {
> + xenon_hs400_strobe_delay_adj(host, card);
> + return 0;
> + }
> +
> + if (((priv->phy_type == EMMC_5_1_PHY) ||
> + (priv->phy_type == EMMC_5_0_PHY)) &&
> + (mmc_card_hs200(card) ||
> + (host->timing == MMC_TIMING_UHS_SDR104))) {
> + ret = xenon_emmc_phy_config_tuning(host);
> + if (!ret)
> + return 0;
> + }
> +
> + ret = xenon_fix_sampl_delay_adj(host, card);
> + if (ret)
> + dev_err(mmc_dev(host->mmc), "fails sampling fixed delay adjustment\n");
> + return ret;
> +}
> +
> +int xenon_phy_adj(struct sdhci_host *host, struct mmc_ios *ios)
> +{
> + struct mmc_host *mmc = host->mmc;
> + struct mmc_card *card;
> + int ret = 0;
> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +
> + if (!host->clock) {
> + priv->clock = 0;
> + return 0;
> + }
> +
> + /*
> + * The timing, frequency or bus width is changed,
> + * better to set eMMC PHY based on current setting
> + * and adjust Xenon SDHC delay.
> + */
> + if ((host->clock == priv->clock) &&
> + (ios->bus_width == priv->bus_width) &&
> + (ios->timing == priv->timing))
> + return 0;
> +
> + xenon_phy_set(host, ios->timing);
> +
> + /* Update the record */
> + priv->bus_width = ios->bus_width;
> + /* Temp stage from HS200 to HS400 */
> + if (((priv->timing == MMC_TIMING_MMC_HS200) &&
> + (ios->timing == MMC_TIMING_MMC_HS)) ||
> + ((ios->timing == MMC_TIMING_MMC_HS) &&
> + (priv->clock > host->clock))) {
> + priv->timing = ios->timing;
> + priv->clock = host->clock;
> + return 0;
> + }
> + priv->timing = ios->timing;
> + priv->clock = host->clock;
> +
> + /* Legacy mode is a special case */
> + if (ios->timing == MMC_TIMING_LEGACY)
> + return 0;
> +
> + card = priv->card_candidate;
> + if (unlikely(!card)) {
> + dev_warn(mmc_dev(mmc), "card is not present\n");
> + return -EINVAL;
> + }
> +
> + if (host->clock > DEFAULT_SDCLK_FREQ)
> + ret = xenon_hs_delay_adj(host, card);
> + return ret;
> +}
> +
> +static int add_xenon_phy(struct device_node *np, struct sdhci_host *host,
> + const char *phy_name)
> +{
> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> + int i, ret;
> +
> + for (i = 0; i < NR_PHY_TYPES; i++) {
> + if (!strcmp(phy_name, phy_types[i])) {
> + priv->phy_type = i;
> + break;
> + }
> + }
> + if (i == NR_PHY_TYPES) {
> + dev_err(mmc_dev(host->mmc),
> + "Unable to determine PHY name %s. Use default eMMC 5.1 PHY\n",
> + phy_name);
> + priv->phy_type = EMMC_5_1_PHY;
> + }
> +
> + if (priv->phy_type == SDH_PHY) {
> + return alloc_sdh_phy(priv);
> + } else if ((priv->phy_type == EMMC_5_0_PHY) ||
> + (priv->phy_type == EMMC_5_1_PHY)) {
> + ret = alloc_emmc_phy(priv);
> + if (ret)
> + return ret;
> + return emmc_phy_parse_param_dt(host, np, priv->phy_params);
> + }
> +
> + return -EINVAL;
> +}
> +
> +int xenon_phy_parse_dt(struct device_node *np, struct sdhci_host *host)
> +{
> + const char *phy_type = NULL;
> +
> + if (!of_property_read_string(np, "xenon,phy-type", &phy_type))
> + return add_xenon_phy(np, host, phy_type);
> +
> + dev_err(mmc_dev(host->mmc), "Fail to get Xenon PHY type. Use default eMMC 5.1 PHY\n");
> + return add_xenon_phy(np, host, "emmc 5.1 phy");
> +}
> diff --git a/drivers/mmc/host/sdhci-xenon-phy.h b/drivers/mmc/host/sdhci-xenon-phy.h
> new file mode 100644
> index 000000000000..4373c71d3b7b
> --- /dev/null
> +++ b/drivers/mmc/host/sdhci-xenon-phy.h
> @@ -0,0 +1,157 @@
> +/* linux/drivers/mmc/host/sdhci-xenon-phy.h
> + *
> + * Author: Hu Ziji <huziji-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>
> + * Date: 2016-8-24
> + *
> + * Copyright (C) 2016 Marvell, All Rights Reserved.
> + *
> + * 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.
> + */
> +#ifndef SDHCI_XENON_PHY_H_
> +#define SDHCI_XENON_PHY_H_
> +
> +#include <linux/types.h>
> +#include "sdhci.h"
> +
> +/* Register base for eMMC PHY 5.0 Version */
> +#define EMMC_5_0_PHY_REG_BASE 0x0160
> +/* Register base for eMMC PHY 5.1 Version */
> +#define EMMC_PHY_REG_BASE 0x0170
> +
> +#define EMMC_PHY_TIMING_ADJUST EMMC_PHY_REG_BASE
> +#define EMMC_5_0_PHY_TIMING_ADJUST EMMC_5_0_PHY_REG_BASE
> +#define TIMING_ADJUST_SLOW_MODE BIT(29)
> +#define TIMING_ADJUST_SDIO_MODE BIT(28)
> +#define OUTPUT_QSN_PHASE_SELECT BIT(17)
> +#define SAMPL_INV_QSP_PHASE_SELECT BIT(18)
> +#define SAMPL_INV_QSP_PHASE_SELECT_SHIFT 18
> +#define PHY_INITIALIZAION BIT(31)
> +#define WAIT_CYCLE_BEFORE_USING_MASK 0xF
> +#define WAIT_CYCLE_BEFORE_USING_SHIFT 12
> +#define FC_SYNC_EN_DURATION_MASK 0xF
> +#define FC_SYNC_EN_DURATION_SHIFT 8
> +#define FC_SYNC_RST_EN_DURATION_MASK 0xF
> +#define FC_SYNC_RST_EN_DURATION_SHIFT 4
> +#define FC_SYNC_RST_DURATION_MASK 0xF
> +#define FC_SYNC_RST_DURATION_SHIFT 0
> +
> +#define EMMC_PHY_FUNC_CONTROL (EMMC_PHY_REG_BASE + 0x4)
> +#define EMMC_5_0_PHY_FUNC_CONTROL (EMMC_5_0_PHY_REG_BASE + 0x4)
> +#define ASYNC_DDRMODE_MASK BIT(23)
> +#define ASYNC_DDRMODE_SHIFT 23
> +#define CMD_DDR_MODE BIT(16)
> +#define DQ_DDR_MODE_SHIFT 8
> +#define DQ_DDR_MODE_MASK 0xFF
> +#define DQ_ASYNC_MODE BIT(4)
> +
> +#define EMMC_PHY_PAD_CONTROL (EMMC_PHY_REG_BASE + 0x8)
> +#define EMMC_5_0_PHY_PAD_CONTROL (EMMC_5_0_PHY_REG_BASE + 0x8)
> +#define REC_EN_SHIFT 24
> +#define REC_EN_MASK 0xF
> +#define FC_DQ_RECEN BIT(24)
> +#define FC_CMD_RECEN BIT(25)
> +#define FC_QSP_RECEN BIT(26)
> +#define FC_QSN_RECEN BIT(27)
> +#define OEN_QSN BIT(28)
> +#define AUTO_RECEN_CTRL BIT(30)
> +#define FC_ALL_CMOS_RECEIVER 0xF000
> +
> +#define EMMC5_FC_QSP_PD BIT(18)
> +#define EMMC5_FC_QSP_PU BIT(22)
> +#define EMMC5_FC_CMD_PD BIT(17)
> +#define EMMC5_FC_CMD_PU BIT(21)
> +#define EMMC5_FC_DQ_PD BIT(16)
> +#define EMMC5_FC_DQ_PU BIT(20)
> +
> +#define EMMC_PHY_PAD_CONTROL1 (EMMC_PHY_REG_BASE + 0xC)
> +#define EMMC5_1_FC_QSP_PD BIT(9)
> +#define EMMC5_1_FC_QSP_PU BIT(25)
> +#define EMMC5_1_FC_CMD_PD BIT(8)
> +#define EMMC5_1_FC_CMD_PU BIT(24)
> +#define EMMC5_1_FC_DQ_PD 0xFF
> +#define EMMC5_1_FC_DQ_PU (0xFF << 16)
> +
> +#define EMMC_PHY_PAD_CONTROL2 (EMMC_PHY_REG_BASE + 0x10)
> +#define EMMC_5_0_PHY_PAD_CONTROL2 (EMMC_5_0_PHY_REG_BASE + 0xC)
> +#define ZNR_MASK 0x1F
> +#define ZNR_SHIFT 8
> +#define ZPR_MASK 0x1F
> +/* Perferred ZNR and ZPR value vary between different boards.
> + * The specific ZNR and ZPR value should be defined here
> + * according to board actual timing.
> + */
> +#define ZNR_DEF_VALUE 0xF
> +#define ZPR_DEF_VALUE 0xF
> +
> +#define EMMC_PHY_DLL_CONTROL (EMMC_PHY_REG_BASE + 0x14)
> +#define EMMC_5_0_PHY_DLL_CONTROL (EMMC_5_0_PHY_REG_BASE + 0x10)
> +#define DLL_ENABLE BIT(31)
> +#define DLL_UPDATE_STROBE_5_0 BIT(30)
> +#define DLL_REFCLK_SEL BIT(30)
> +#define DLL_UPDATE BIT(23)
> +#define DLL_PHSEL1_SHIFT 24
> +#define DLL_PHSEL0_SHIFT 16
> +#define DLL_PHASE_MASK 0x3F
> +#define DLL_PHASE_90_DEGREE 0x1F
> +#define DLL_FAST_LOCK BIT(5)
> +#define DLL_GAIN2X BIT(3)
> +#define DLL_BYPASS_EN BIT(0)
> +
> +#define EMMC_5_0_PHY_LOGIC_TIMING_ADJUST (EMMC_5_0_PHY_REG_BASE + 0x14)
> +#define EMMC_PHY_LOGIC_TIMING_ADJUST (EMMC_PHY_REG_BASE + 0x18)
> +
> +enum sampl_fix_delay_phase {
> + PHASE_0_DEGREE = 0x0,
> + PHASE_90_DEGREE = 0x1,
> + PHASE_180_DEGREE = 0x2,
> + PHASE_270_DEGREE = 0x3,
> +};
> +
> +#define SDH_PHY_SLOT_DLL_CTRL (0x0138)
> +#define SDH_PHY_ENABLE_DLL BIT(1)
> +#define SDH_PHY_FAST_LOCK_EN BIT(5)
> +
> +#define SDH_PHY_SLOT_DLL_PHASE_SEL (0x013C)
> +#define SDH_PHY_DLL_UPDATE_TUNING BIT(15)
> +
> +enum soc_pad_ctrl_type {
> + SOC_PAD_SD,
> + SOC_PAD_FIXED_1_8V,
> +};
> +
> +/*
> + * List offset of PHY registers and some special register values
> + * in eMMC PHY 5.0 or eMMC PHY 5.1
> + */
> +struct xenon_emmc_phy_regs {
> + /* Offset of Timing Adjust register */
> + u16 timing_adj;
> + /* Offset of Func Control register */
> + u16 func_ctrl;
> + /* Offset of Pad Control register */
> + u16 pad_ctrl;
> + /* Offset of Pad Control register */
> + u16 pad_ctrl2;
> + /* Offset of DLL Control register */
> + u16 dll_ctrl;
> + /* Offset of Logic Timing Adjust register */
> + u16 logic_timing_adj;
> + /* Max value of eMMC Fixed Sampling Delay */
> + u32 delay_mask;
> + /* DLL Update Enable bit */
> + u32 dll_update;
> +};
> +
> +struct xenon_phy_ops {
> + void (*strobe_delay_adj)(struct sdhci_host *host,
> + struct mmc_card *card);
> + int (*fix_sampl_delay_adj)(struct sdhci_host *host,
> + struct mmc_card *card);
> + void (*phy_set)(struct sdhci_host *host, unsigned char timing);
> + void (*set_soc_pad)(struct sdhci_host *host,
> + unsigned char signal_voltage);
> +};
> +#endif
> diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c
> index 03ba183494d3..4d7d871544fc 100644
> --- a/drivers/mmc/host/sdhci-xenon.c
> +++ b/drivers/mmc/host/sdhci-xenon.c
> @@ -224,6 +224,7 @@ static void xenon_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> spin_unlock_irqrestore(&host->lock, flags);
>
> sdhci_set_ios(mmc, ios);
> + xenon_phy_adj(host, ios);
>
> if (host->clock > DEFAULT_SDCLK_FREQ) {
> spin_lock_irqsave(&host->lock, flags);
> @@ -309,6 +310,8 @@ static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
> */
> enable_xenon_internal_clk(host);
>
> + xenon_soc_pad_ctrl(host, ios->signal_voltage);
> +
> if (priv->card_candidate) {
> if (mmc_card_mmc(priv->card_candidate))
> return xenon_emmc_signal_voltage_switch(mmc, ios);
> @@ -453,6 +456,7 @@ static int xenon_probe_dt(struct platform_device *pdev)
> sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
> }
>
> + err = xenon_phy_parse_dt(np, host);
> return err;
> }
>
> diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h
> index c2370493fbe8..06e5261a563c 100644
> --- a/drivers/mmc/host/sdhci-xenon.h
> +++ b/drivers/mmc/host/sdhci-xenon.h
> @@ -15,6 +15,7 @@
> #include <linux/mmc/card.h>
> #include <linux/of.h>
> #include "sdhci.h"
> +#include "sdhci-xenon-phy.h"
>
> /* Register Offset of SD Host Controller SOCP self-defined register */
> #define SDHC_SYS_CFG_INFO 0x0104
> @@ -76,6 +77,7 @@
> #define MMC_TIMING_FAKE 0xFF
>
> #define DEFAULT_SDCLK_FREQ (400000)
> +#define LOWEST_SDCLK_FREQ (100000)
>
> /* Xenon specific Mode Select value */
> #define XENON_SDHCI_CTRL_HS200 0x5
> @@ -97,6 +99,15 @@ struct sdhci_xenon_priv {
> /* Slot idx */
> u8 slot_idx;
>
> + int phy_type;
> + /*
> + * Contains board-specific PHY parameters
> + * passed from device tree.
> + */
> + void *phy_params;
> + const struct xenon_phy_ops *phy_ops;
> + struct xenon_emmc_phy_regs *emmc_phy_regs;
> +
> /*
> * When initializing card, Xenon has to determine card type and
> * adjust Sampling Fixed delay.
> @@ -131,4 +142,10 @@ static inline int enable_xenon_internal_clk(struct sdhci_host *host)
>
> return 0;
> }
> +
> +int xenon_phy_adj(struct sdhci_host *host, struct mmc_ios *ios);
> +int xenon_phy_parse_dt(struct device_node *np,
> + struct sdhci_host *host);
> +void xenon_soc_pad_ctrl(struct sdhci_host *host,
> + unsigned char signal_voltage);
> #endif
>
--
Best Regards
Shawn Lin
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH 2/10] mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
From: Shawn Lin @ 2016-10-08 2:40 UTC (permalink / raw)
To: Gregory CLEMENT, Ulf Hansson, Adrian Hunter,
linux-mmc-u79uwXL29TY76Z2rM5mHXA
Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth, Rob Herring,
devicetree-u79uwXL29TY76Z2rM5mHXA, Thomas Petazzoni,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Ziji Hu,
Jack(SH) Zhu, Jimmy Xu, Jisheng Zhang, Nadav Haklai, Ryan Gao,
Doug Jones, Shiwu Zhang, Victor Gu, Wei(SOCP) Liu, Wilson Ding,
Xueping Liu, Hilbert Zhang, Keji Zhang, Liuliu Zhao
In-Reply-To: <9a660a813af58ad7c108fd06289513302426c9fb.1475853198.git-series.gregory.clement-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
Hi,
在 2016/10/7 23:22, Gregory CLEMENT 写道:
> From: Ziji Hu <huziji-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>
>
> Export sdhci_start_signal_voltage_switch() from sdhci.c.
> Thus vendor sdhci driver can implement its own signal voltage
> switch routine.
>
You can overwtite this callback within your driver itself.
That is what other sdhci variant drivers did, so patch 1-3 are
unnecessary.
> Signed-off-by: Hu Ziji <huziji-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>
> Reviewed-by: Gregory CLEMENT <gregory.clement-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
> Signed-off-by: Gregory CLEMENT <gregory.clement-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
> ---
> drivers/mmc/host/sdhci.c | 5 +++--
> drivers/mmc/host/sdhci.h | 2 ++
> 2 files changed, 5 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> index d4bb818c52d5..2250ea22231f 100644
> --- a/drivers/mmc/host/sdhci.c
> +++ b/drivers/mmc/host/sdhci.c
> @@ -1828,8 +1828,8 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
> spin_unlock_irqrestore(&host->lock, flags);
> }
>
> -static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
> - struct mmc_ios *ios)
> +int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
> + struct mmc_ios *ios)
> {
> struct sdhci_host *host = mmc_priv(mmc);
> u16 ctrl;
> @@ -1921,6 +1921,7 @@ static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
> return 0;
> }
> }
> +EXPORT_SYMBOL_GPL(sdhci_start_signal_voltage_switch);
>
> static int sdhci_card_busy(struct mmc_host *mmc)
> {
> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
> index 21dc80b8ae3d..c38ab65b9a97 100644
> --- a/drivers/mmc/host/sdhci.h
> +++ b/drivers/mmc/host/sdhci.h
> @@ -687,6 +687,8 @@ void sdhci_set_bus_width(struct sdhci_host *host, int width);
> void sdhci_reset(struct sdhci_host *host, u8 mask);
> void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing);
> void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
> +int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
> + struct mmc_ios *ios);
>
> #ifdef CONFIG_PM
> extern int sdhci_suspend_host(struct sdhci_host *host);
>
--
Best Regards
Shawn Lin
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH 4/10] MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
From: Ziji Hu @ 2016-10-08 0:59 UTC (permalink / raw)
To: Joe Perches, Gregory CLEMENT, Ulf Hansson, Adrian Hunter,
linux-mmc
Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth, Rob Herring,
devicetree, Thomas Petazzoni, linux-arm-kernel, Jack(SH) Zhu,
Jimmy Xu, Jisheng Zhang, Nadav Haklai, Ryan Gao, Doug Jones,
Shiwu Zhang, Victor Gu, Wei(SOCP) Liu, Wilson Ding, Xueping Liu,
Hilbert Zhang, Liuliu Zhao, Peng Zhu, Yu Cao <yuca>
In-Reply-To: <1475873086.1945.20.camel@perches.com>
Hi Joe,
On 2016/10/8 4:44, Joe Perches wrote:
> On Fri, 2016-10-07 at 17:22 +0200, Gregory CLEMENT wrote:
>> Add maintainer entry for Marvell Xenon eMMC/SD/SDIO Host
>> Controller drivers.
> []
>> diff --git a/MAINTAINERS b/MAINTAINERS
> []
>> @@ -7578,6 +7578,11 @@ M: Nicolas Pitre <nico@fluxnic.net>
>> S: Odd Fixes
>> F: drivers/mmc/host/mvsdio.*
>>
>> +MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
>> +M: Ziji Hu <huziji@marvell.com>
>> +L: linux-mmc@vger.kernel.org
>> +S: Supported
>
> You should really add F: file patterns here
>
The specific file patterns will be added later when the corresponding file is included in patch.
Otherwise, it cannot pass checkpatch.pl.
Thank you.
Best regards,
Hu Ziji
^ permalink raw reply
* Re: [PATCH 4/10] MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
From: Joe Perches @ 2016-10-07 20:44 UTC (permalink / raw)
To: Gregory CLEMENT, Ulf Hansson, Adrian Hunter,
linux-mmc-u79uwXL29TY76Z2rM5mHXA
Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth, Rob Herring,
devicetree-u79uwXL29TY76Z2rM5mHXA, Thomas Petazzoni,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Ziji Hu,
Jack(SH) Zhu, Jimmy Xu, Jisheng Zhang, Nadav Haklai, Ryan Gao,
Doug Jones, Shiwu Zhang, Victor Gu, Wei(SOCP) Liu, Wilson Ding,
Xueping Liu, Hilbert Zhang, Keji Zhang, Liuliu Zhao
In-Reply-To: <86b237c01d9767d4e0edf6d41194ab959838e5c0.1475853198.git-series.gregory.clement-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
On Fri, 2016-10-07 at 17:22 +0200, Gregory CLEMENT wrote:
> Add maintainer entry for Marvell Xenon eMMC/SD/SDIO Host
> Controller drivers.
[]
> diff --git a/MAINTAINERS b/MAINTAINERS
[]
> @@ -7578,6 +7578,11 @@ M: Nicolas Pitre <nico-vtqb6HGKxmzR7s880joybQ@public.gmane.org>
> S: Odd Fixes
> F: drivers/mmc/host/mvsdio.*
>
> +MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
> +M: Ziji Hu <huziji-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>
> +L: linux-mmc-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> +S: Supported
You should really add F: file patterns here
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [RFC 1/2] sdhci: Add device tree property sd-broken-highspeed
From: Zach Brown @ 2016-10-07 18:56 UTC (permalink / raw)
To: Adrian Hunter
Cc: Shawn Lin, Julia Cartwright, Rob Herring, Ulf Hansson,
Mark Rutland, linux-mmc, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org
In-Reply-To: <7acb34a9-d323-b112-f796-92c2a962743c@intel.com>
On Thu, Oct 06, 2016 at 09:13:24AM +0300, Adrian Hunter wrote:
> On 06/10/16 04:34, Shawn Lin wrote:
> > On 2016/10/6 5:22, Julia Cartwright wrote:
> >> On Wed, Oct 05, 2016 at 03:03:44PM -0500, Rob Herring wrote:
> >>> On Wed, Oct 5, 2016 at 1:33 PM, Ulf Hansson <ulf.hansson@linaro.org> wrote:
> >>>> On 23 September 2016 at 22:01, Zach Brown <zach.brown@ni.com> wrote:
> >>>>> Certain board configurations can make highspeed malfunction due to
> >>>>> timing issues. In these cases a way is needed to force the controller
> >>>>> and card into standard speed even if they otherwise appear to be capable
> >>>>> of highspeed.
> >>>>>
> >>>>> The sd-broken-highspeed property will let the sdhci driver know that
> >>>>> highspeed will not work.
> >>>>>
> >>>>> Signed-off-by: Zach Brown <zach.brown@ni.com>
> >>>>> ---
> >>>>> Documentation/devicetree/bindings/mmc/mmc.txt | 2 ++
> >>>>> 1 file changed, 2 insertions(+)
> >>>>>
> >>>>> diff --git a/Documentation/devicetree/bindings/mmc/mmc.txt
> >>>>> b/Documentation/devicetree/bindings/mmc/mmc.txt
> >>>>> index 8a37782..59332ea 100644
> >>>>> --- a/Documentation/devicetree/bindings/mmc/mmc.txt
> >>>>> +++ b/Documentation/devicetree/bindings/mmc/mmc.txt
> >>>>> @@ -52,6 +52,8 @@ Optional properties:
> >>>>> - no-sdio: controller is limited to send sdio cmd during initialization
> >>>>> - no-sd: controller is limited to send sd cmd during initialization
> >>>>> - no-mmc: controller is limited to send mmc cmd during initialization
> >>>>> +- sd-broken-highspeed: Highspeed is broken, even if the controller and
> >>>>> card
> >>>>> + themselves claim they support highspeed.
> >>>>
> >>>> Regarding a broken card, that is managed via the card quirks and not in DT.
> >>>>
> >>>> If this is about a controller limitation, we already have the option
> >>>> to describe what it supports, so we don't need an option to tell what
> >>>> it *not* supports.
> >>>>
> >>>> For example "cap-sd-highspeed" tells whether the controller supports
> >>>> SD high-speed, please use that instead.
> >>>
> >>> If a controller has a capability register and it lies (perhaps the
> >>> board has limitations that the SoC does not), then you may need to
> >>> disable a feature.
> >>
> >> That's precisely the case here. This is a board-level problem, not a
> >> card or controller problem. As Zach mentioned in the cover letter, the
> >> trace length between controller and card on some of our boards is too
> >> long to meet high-speed timings, even though both card and controller
> >> advertise it.
> >
> > IIRC, I saw the same problem while using sdhci to bring up a
> > industrial board for vehicle. The trace length is so long that
> > I have to limit the max-frequency to make it works properly when
> > running at hishspeed.
> >
> > So could you try to limit the max-frequency in your DT to see
> > if it could work for you? I guess it should work as once reducing
> > the frequency, the timing per cycle will be large enough to meet
> > the spec. At least that helped me solve my problem.
> >
> > For further consideration, I deployed a mechanism called "tuning for
> > non UHS or non-hs200/400" for my donwstream tree at that time. The basic
> > concept is to ask devices to send ext_csd(or send status for SD case)
> > *repeatedly*. Some hosts, i.e. sdhci-of-arasan or dw_mmc-rockchip, can
> > manually set rx delay via some clock unit registers to capture the
> > working sample window and select the middle point of the longest good
> > phase region. The same for retune. But it is a little complicated, and
> > could only be applicable to the hosts who could adjust the rx delay
> > manually when claiming the caps of MMC_CAPS_CAN_TUNING_FOR_HS, whatever
> > the name is...
> >
> > I don't know if it is worth to add this, and I don't know if it's
> > a legit way. Anyway, I just share my thought(experience) for you and
> > linux-mmc to think more about how to deal with the case you meet rather
> > than sacrificing the performence by removing highspeed or reducing
> > frequency..
> >
>
> As Shawn points out, using max-frequency is a possibility. Did you consider
> that?
>
I hadn't. So I looked into it and, as I stated the issue using max-frequency
would work, but I forgot to add that, because we pass the signal through an
fpga it has to be standard speed. If the card and controller are not in
standard speed then it won't pass hold time. Specifically we need the data to
change on the falling edge of the clock.
I'll add this justification to the commit message.
> There are 2 high speed caps:
> cap-sd-highspeed
> cap-mmc-highspeed
>
> Yet patch in 2, the effect of sd-broken-highspeed is to suppress highspeed
> for both SD and MMC. Should it then be called just broken-highspeed?
>
I agree, broken-highspeed makes more sense. Will change in the next version.
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox