All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/10] mmc: Add support to Marvell Xenon SD Host Controller
@ 2016-10-07 15:22 ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, Rob Herring, devicetree, Thomas Petazzoni,
	linux-arm-kernel, 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, Peng Zhu, Yu Cao, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

Hello,

This series adds support for the SDHCI Xenon controller which can be
currently found on the Armada 37xx and the Armada 7K/8K but will be
also used in more Marvell SoC (and not only the mvebu ones actually).

A few months ago a series has been sent to support this controller:
http://lists.infradead.org/pipermail/linux-arm-kernel/2016-June/434925.html

However, since this first version, the engineer team in charge of this
controller wished to have a better involvement in the mainline support
of the driver. This new series is the result the Marvell development
with the aim of supporting more feature that only the ones needed for
the Armada 3720 SoC.

About the hardware and the driver itself, according to Ziji Hu:
"Marvell Xenon SDHC is widely used in Marvell products.
It supports SD/SDIO/eMMC.
It conforms to SD Physical Layer Specification Version 3.01 and
is designed according to the guidelines provided in the SD Host
Controller Standard Specification Version 3.00.
Xenon SDHC IP contains PHY. There are tree types of Xenon PHY in use.
Each Xenon SDHC only contain one type of PHY."

Thanks,

Gregory

Gregory CLEMENT (3):
  arm64: dts: marvell: add eMMC support for Armada 37xx
  arm64: dts: marvell: add sdhci support for Armada 7K/8K
  arm64: configs: enable SDHCI driver for Xenon

Ziji Hu (7):
  mmc: sdhci: Export sdhci_set_ios() from sdhci.c
  mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
  mmc: sdhci: Export sdhci_execute_tuning() in sdhci.c
  MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
  dt: bindings: Add bindings for Marvell Xenon SD Host Controller
  mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
  mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC

 Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt |  164 +-
 MAINTAINERS                                                   |    8 +-
 arch/arm64/boot/dts/marvell/armada-3720-db.dts                |    7 +-
 arch/arm64/boot/dts/marvell/armada-37xx.dtsi                  |   11 +-
 arch/arm64/boot/dts/marvell/armada-7040-db.dts                |    7 +-
 arch/arm64/boot/dts/marvell/armada-ap806.dtsi                 |    9 +-
 arch/arm64/configs/defconfig                                  |    1 +-
 drivers/mmc/host/Kconfig                                      |    9 +-
 drivers/mmc/host/Makefile                                     |    3 +-
 drivers/mmc/host/sdhci-xenon-phy.c                            | 1141 +++++++-
 drivers/mmc/host/sdhci-xenon-phy.h                            |  157 +-
 drivers/mmc/host/sdhci-xenon.c                                |  603 ++++-
 drivers/mmc/host/sdhci-xenon.h                                |  151 +-
 drivers/mmc/host/sdhci.c                                      |   11 +-
 drivers/mmc/host/sdhci.h                                      |    4 +-
 15 files changed, 2282 insertions(+), 4 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
 create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c
 create mode 100644 drivers/mmc/host/sdhci-xenon-phy.h
 create mode 100644 drivers/mmc/host/sdhci-xenon.c
 create mode 100644 drivers/mmc/host/sdhci-xenon.h

base-commit: 32b7d4a95f79deb8b73e8b303a54dfdef4a3d845
-- 
git-series 0.8.10

^ permalink raw reply	[flat|nested] 105+ messages in thread

* [PATCH 0/10] mmc: Add support to Marvell Xenon SD Host Controller
@ 2016-10-07 15:22 ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc-u79uwXL29TY76Z2rM5mHXA
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, 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

Hello,

This series adds support for the SDHCI Xenon controller which can be
currently found on the Armada 37xx and the Armada 7K/8K but will be
also used in more Marvell SoC (and not only the mvebu ones actually).

A few months ago a series has been sent to support this controller:
http://lists.infradead.org/pipermail/linux-arm-kernel/2016-June/434925.html

However, since this first version, the engineer team in charge of this
controller wished to have a better involvement in the mainline support
of the driver. This new series is the result the Marvell development
with the aim of supporting more feature that only the ones needed for
the Armada 3720 SoC.

About the hardware and the driver itself, according to Ziji Hu:
"Marvell Xenon SDHC is widely used in Marvell products.
It supports SD/SDIO/eMMC.
It conforms to SD Physical Layer Specification Version 3.01 and
is designed according to the guidelines provided in the SD Host
Controller Standard Specification Version 3.00.
Xenon SDHC IP contains PHY. There are tree types of Xenon PHY in use.
Each Xenon SDHC only contain one type of PHY."

Thanks,

Gregory

Gregory CLEMENT (3):
  arm64: dts: marvell: add eMMC support for Armada 37xx
  arm64: dts: marvell: add sdhci support for Armada 7K/8K
  arm64: configs: enable SDHCI driver for Xenon

Ziji Hu (7):
  mmc: sdhci: Export sdhci_set_ios() from sdhci.c
  mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
  mmc: sdhci: Export sdhci_execute_tuning() in sdhci.c
  MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
  dt: bindings: Add bindings for Marvell Xenon SD Host Controller
  mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
  mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC

 Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt |  164 +-
 MAINTAINERS                                                   |    8 +-
 arch/arm64/boot/dts/marvell/armada-3720-db.dts                |    7 +-
 arch/arm64/boot/dts/marvell/armada-37xx.dtsi                  |   11 +-
 arch/arm64/boot/dts/marvell/armada-7040-db.dts                |    7 +-
 arch/arm64/boot/dts/marvell/armada-ap806.dtsi                 |    9 +-
 arch/arm64/configs/defconfig                                  |    1 +-
 drivers/mmc/host/Kconfig                                      |    9 +-
 drivers/mmc/host/Makefile                                     |    3 +-
 drivers/mmc/host/sdhci-xenon-phy.c                            | 1141 +++++++-
 drivers/mmc/host/sdhci-xenon-phy.h                            |  157 +-
 drivers/mmc/host/sdhci-xenon.c                                |  603 ++++-
 drivers/mmc/host/sdhci-xenon.h                                |  151 +-
 drivers/mmc/host/sdhci.c                                      |   11 +-
 drivers/mmc/host/sdhci.h                                      |    4 +-
 15 files changed, 2282 insertions(+), 4 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
 create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c
 create mode 100644 drivers/mmc/host/sdhci-xenon-phy.h
 create mode 100644 drivers/mmc/host/sdhci-xenon.c
 create mode 100644 drivers/mmc/host/sdhci-xenon.h

base-commit: 32b7d4a95f79deb8b73e8b303a54dfdef4a3d845
-- 
git-series 0.8.10
--
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	[flat|nested] 105+ messages in thread

* [PATCH 0/10] mmc: Add support to Marvell Xenon SD Host Controller
@ 2016-10-07 15:22 ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: linux-arm-kernel

Hello,

This series adds support for the SDHCI Xenon controller which can be
currently found on the Armada 37xx and the Armada 7K/8K but will be
also used in more Marvell SoC (and not only the mvebu ones actually).

A few months ago a series has been sent to support this controller:
http://lists.infradead.org/pipermail/linux-arm-kernel/2016-June/434925.html

However, since this first version, the engineer team in charge of this
controller wished to have a better involvement in the mainline support
of the driver. This new series is the result the Marvell development
with the aim of supporting more feature that only the ones needed for
the Armada 3720 SoC.

About the hardware and the driver itself, according to Ziji Hu:
"Marvell Xenon SDHC is widely used in Marvell products.
It supports SD/SDIO/eMMC.
It conforms to SD Physical Layer Specification Version 3.01 and
is designed according to the guidelines provided in the SD Host
Controller Standard Specification Version 3.00.
Xenon SDHC IP contains PHY. There are tree types of Xenon PHY in use.
Each Xenon SDHC only contain one type of PHY."

Thanks,

Gregory

Gregory CLEMENT (3):
  arm64: dts: marvell: add eMMC support for Armada 37xx
  arm64: dts: marvell: add sdhci support for Armada 7K/8K
  arm64: configs: enable SDHCI driver for Xenon

Ziji Hu (7):
  mmc: sdhci: Export sdhci_set_ios() from sdhci.c
  mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
  mmc: sdhci: Export sdhci_execute_tuning() in sdhci.c
  MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
  dt: bindings: Add bindings for Marvell Xenon SD Host Controller
  mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
  mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC

 Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt |  164 +-
 MAINTAINERS                                                   |    8 +-
 arch/arm64/boot/dts/marvell/armada-3720-db.dts                |    7 +-
 arch/arm64/boot/dts/marvell/armada-37xx.dtsi                  |   11 +-
 arch/arm64/boot/dts/marvell/armada-7040-db.dts                |    7 +-
 arch/arm64/boot/dts/marvell/armada-ap806.dtsi                 |    9 +-
 arch/arm64/configs/defconfig                                  |    1 +-
 drivers/mmc/host/Kconfig                                      |    9 +-
 drivers/mmc/host/Makefile                                     |    3 +-
 drivers/mmc/host/sdhci-xenon-phy.c                            | 1141 +++++++-
 drivers/mmc/host/sdhci-xenon-phy.h                            |  157 +-
 drivers/mmc/host/sdhci-xenon.c                                |  603 ++++-
 drivers/mmc/host/sdhci-xenon.h                                |  151 +-
 drivers/mmc/host/sdhci.c                                      |   11 +-
 drivers/mmc/host/sdhci.h                                      |    4 +-
 15 files changed, 2282 insertions(+), 4 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
 create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c
 create mode 100644 drivers/mmc/host/sdhci-xenon-phy.h
 create mode 100644 drivers/mmc/host/sdhci-xenon.c
 create mode 100644 drivers/mmc/host/sdhci-xenon.h

base-commit: 32b7d4a95f79deb8b73e8b303a54dfdef4a3d845
-- 
git-series 0.8.10

^ permalink raw reply	[flat|nested] 105+ messages in thread

* [PATCH 1/10] mmc: sdhci: Export sdhci_set_ios() from sdhci.c
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, Rob Herring, devicetree, Thomas Petazzoni,
	linux-arm-kernel, 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, Peng Zhu, Yu Cao, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

From: Ziji Hu <huziji@marvell.com>

Export sdhci_set_ios() in sdhci.c.
Thus vendor sdhci driver can implement its own set_ios() routine.

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 | 3 ++-
 drivers/mmc/host/sdhci.h | 1 +
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 48055666c655..d4bb818c52d5 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1562,7 +1562,7 @@ void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing)
 }
 EXPORT_SYMBOL_GPL(sdhci_set_uhs_signaling);
 
-static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
 {
 	struct sdhci_host *host = mmc_priv(mmc);
 	unsigned long flags;
@@ -1719,6 +1719,7 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
 	mmiowb();
 	spin_unlock_irqrestore(&host->lock, flags);
 }
+EXPORT_SYMBOL_GPL(sdhci_set_ios);
 
 static int sdhci_get_cd(struct mmc_host *mmc)
 {
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index c722cd23205c..21dc80b8ae3d 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -686,6 +686,7 @@ void sdhci_set_power(struct sdhci_host *host, unsigned char mode,
 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);
 
 #ifdef CONFIG_PM
 extern int sdhci_suspend_host(struct sdhci_host *host);
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 1/10] mmc: sdhci: Export sdhci_set_ios() from sdhci.c
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc-u79uwXL29TY76Z2rM5mHXA
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, 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

From: Ziji Hu <huziji-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>

Export sdhci_set_ios() in sdhci.c.
Thus vendor sdhci driver can implement its own set_ios() routine.

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 | 3 ++-
 drivers/mmc/host/sdhci.h | 1 +
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 48055666c655..d4bb818c52d5 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1562,7 +1562,7 @@ void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing)
 }
 EXPORT_SYMBOL_GPL(sdhci_set_uhs_signaling);
 
-static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
 {
 	struct sdhci_host *host = mmc_priv(mmc);
 	unsigned long flags;
@@ -1719,6 +1719,7 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
 	mmiowb();
 	spin_unlock_irqrestore(&host->lock, flags);
 }
+EXPORT_SYMBOL_GPL(sdhci_set_ios);
 
 static int sdhci_get_cd(struct mmc_host *mmc)
 {
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index c722cd23205c..21dc80b8ae3d 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -686,6 +686,7 @@ void sdhci_set_power(struct sdhci_host *host, unsigned char mode,
 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);
 
 #ifdef CONFIG_PM
 extern int sdhci_suspend_host(struct sdhci_host *host);
-- 
git-series 0.8.10
--
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 related	[flat|nested] 105+ messages in thread

* [PATCH 1/10] mmc: sdhci: Export sdhci_set_ios() from sdhci.c
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: linux-arm-kernel

From: Ziji Hu <huziji@marvell.com>

Export sdhci_set_ios() in sdhci.c.
Thus vendor sdhci driver can implement its own set_ios() routine.

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 | 3 ++-
 drivers/mmc/host/sdhci.h | 1 +
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 48055666c655..d4bb818c52d5 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1562,7 +1562,7 @@ void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing)
 }
 EXPORT_SYMBOL_GPL(sdhci_set_uhs_signaling);
 
-static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
 {
 	struct sdhci_host *host = mmc_priv(mmc);
 	unsigned long flags;
@@ -1719,6 +1719,7 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
 	mmiowb();
 	spin_unlock_irqrestore(&host->lock, flags);
 }
+EXPORT_SYMBOL_GPL(sdhci_set_ios);
 
 static int sdhci_get_cd(struct mmc_host *mmc)
 {
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index c722cd23205c..21dc80b8ae3d 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -686,6 +686,7 @@ void sdhci_set_power(struct sdhci_host *host, unsigned char mode,
 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);
 
 #ifdef CONFIG_PM
 extern int sdhci_suspend_host(struct sdhci_host *host);
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 2/10] mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
  2016-10-07 15:22 ` Gregory CLEMENT
  (?)
@ 2016-10-07 15:22   ` Gregory CLEMENT
  -1 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, Rob Herring, devicetree, Thomas Petazzoni,
	linux-arm-kernel, 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, Peng Zhu, Yu Cao, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

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.

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);
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 2/10] mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, Rob Herring, devicetree, Thomas Petazzoni,
	linux-arm-kernel, 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

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.

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);
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 2/10] mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: linux-arm-kernel

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.

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);
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 3/10] mmc: sdhci: Export sdhci_execute_tuning() in sdhci.c
  2016-10-07 15:22 ` Gregory CLEMENT
  (?)
@ 2016-10-07 15:22   ` Gregory CLEMENT
  -1 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, Rob Herring, devicetree, Thomas Petazzoni,
	linux-arm-kernel, 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, Peng Zhu, Yu Cao, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

From: Ziji Hu <huziji@marvell.com>

Export sdhci_execute_tuning() from sdhci.c.
Thus vendor sdhci driver can execute its own tuning process.

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 | 3 ++-
 drivers/mmc/host/sdhci.h | 1 +
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 2250ea22231f..330119cd5a93 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1946,7 +1946,7 @@ static int sdhci_prepare_hs400_tuning(struct mmc_host *mmc, struct mmc_ios *ios)
 	return 0;
 }
 
-static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
+int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
 {
 	struct sdhci_host *host = mmc_priv(mmc);
 	u16 ctrl;
@@ -2135,6 +2135,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
 	spin_unlock_irqrestore(&host->lock, flags);
 	return err;
 }
+EXPORT_SYMBOL_GPL(sdhci_execute_tuning);
 
 static int sdhci_select_drive_strength(struct mmc_card *card,
 				       unsigned int max_dtr, int host_drv,
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index c38ab65b9a97..035274ddea54 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -689,6 +689,7 @@ 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);
+int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode);
 
 #ifdef CONFIG_PM
 extern int sdhci_suspend_host(struct sdhci_host *host);
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 3/10] mmc: sdhci: Export sdhci_execute_tuning() in sdhci.c
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc
  Cc: Hilbert Zhang, Andrew Lunn, Romain Perier, Liuliu Zhao, Peng Zhu,
	Nadav Haklai, Ziji Hu, Victor Gu, Doug Jones, Jisheng Zhang,
	Yehuda Yitschak, Marcin Wojtas, Xueping Liu, Shiwu Zhang, Yu Cao,
	Sebastian Hesselbarth, devicetree, Jason Cooper, Hanna Hawa,
	Kostya Porotchkin, Rob Herring, Ryan Gao, Gregory CLEMENT,
	Wei(SOCP) Liu, linux-arm-kernel

From: Ziji Hu <huziji@marvell.com>

Export sdhci_execute_tuning() from sdhci.c.
Thus vendor sdhci driver can execute its own tuning process.

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 | 3 ++-
 drivers/mmc/host/sdhci.h | 1 +
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 2250ea22231f..330119cd5a93 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1946,7 +1946,7 @@ static int sdhci_prepare_hs400_tuning(struct mmc_host *mmc, struct mmc_ios *ios)
 	return 0;
 }
 
-static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
+int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
 {
 	struct sdhci_host *host = mmc_priv(mmc);
 	u16 ctrl;
@@ -2135,6 +2135,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
 	spin_unlock_irqrestore(&host->lock, flags);
 	return err;
 }
+EXPORT_SYMBOL_GPL(sdhci_execute_tuning);
 
 static int sdhci_select_drive_strength(struct mmc_card *card,
 				       unsigned int max_dtr, int host_drv,
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index c38ab65b9a97..035274ddea54 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -689,6 +689,7 @@ 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);
+int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode);
 
 #ifdef CONFIG_PM
 extern int sdhci_suspend_host(struct sdhci_host *host);
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 3/10] mmc: sdhci: Export sdhci_execute_tuning() in sdhci.c
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: linux-arm-kernel

From: Ziji Hu <huziji@marvell.com>

Export sdhci_execute_tuning() from sdhci.c.
Thus vendor sdhci driver can execute its own tuning process.

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 | 3 ++-
 drivers/mmc/host/sdhci.h | 1 +
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 2250ea22231f..330119cd5a93 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1946,7 +1946,7 @@ static int sdhci_prepare_hs400_tuning(struct mmc_host *mmc, struct mmc_ios *ios)
 	return 0;
 }
 
-static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
+int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
 {
 	struct sdhci_host *host = mmc_priv(mmc);
 	u16 ctrl;
@@ -2135,6 +2135,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
 	spin_unlock_irqrestore(&host->lock, flags);
 	return err;
 }
+EXPORT_SYMBOL_GPL(sdhci_execute_tuning);
 
 static int sdhci_select_drive_strength(struct mmc_card *card,
 				       unsigned int max_dtr, int host_drv,
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index c38ab65b9a97..035274ddea54 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -689,6 +689,7 @@ 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);
+int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode);
 
 #ifdef CONFIG_PM
 extern int sdhci_suspend_host(struct sdhci_host *host);
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 4/10] MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
  2016-10-07 15:22 ` Gregory CLEMENT
  (?)
@ 2016-10-07 15:22   ` Gregory CLEMENT
  -1 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, Rob Herring, devicetree, Thomas Petazzoni,
	linux-arm-kernel, 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, Peng Zhu, Yu Cao, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

From: Ziji Hu <huziji@marvell.com>

Add maintainer entry for Marvell Xenon eMMC/SD/SDIO Host
Controller drivers.

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 | 5 +++++
 1 file changed, 5 insertions(+), 0 deletions(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 207eaefd3b97..89adcd57aa25 100644
--- 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
+
 MATROX FRAMEBUFFER DRIVER
 L:	linux-fbdev@vger.kernel.org
 S:	Orphan
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 4/10] MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, Rob Herring, devicetree, Thomas Petazzoni,
	linux-arm-kernel, 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

From: Ziji Hu <huziji@marvell.com>

Add maintainer entry for Marvell Xenon eMMC/SD/SDIO Host
Controller drivers.

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 | 5 +++++
 1 file changed, 5 insertions(+), 0 deletions(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 207eaefd3b97..89adcd57aa25 100644
--- 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
+
 MATROX FRAMEBUFFER DRIVER
 L:	linux-fbdev@vger.kernel.org
 S:	Orphan
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 4/10] MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: linux-arm-kernel

From: Ziji Hu <huziji@marvell.com>

Add maintainer entry for Marvell Xenon eMMC/SD/SDIO Host
Controller drivers.

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 | 5 +++++
 1 file changed, 5 insertions(+), 0 deletions(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 207eaefd3b97..89adcd57aa25 100644
--- 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 at vger.kernel.org
+S:	Supported
+
 MATROX FRAMEBUFFER DRIVER
 L:	linux-fbdev at vger.kernel.org
 S:	Orphan
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 5/10] dt: bindings: Add bindings for Marvell Xenon SD Host Controller
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, Rob Herring, devicetree, Thomas Petazzoni,
	linux-arm-kernel, 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, Peng Zhu, Yu Cao, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

From: Ziji Hu <huziji@marvell.com>

Marvell Xenon SDHC can support eMMC/SD/SDIO.
Add Xenon-specific properties.
Also add properties for Xenon PHY setting.

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>
---
 Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt | 164 +++++++-
 MAINTAINERS                                                   |   1 +-
 2 files changed, 165 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt

diff --git a/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt b/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
new file mode 100644
index 000000000000..8b25ad28ebbd
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
@@ -0,0 +1,164 @@
+Marvell's Xenon SDHCI Controller device tree bindings
+This file documents differences between the core mmc properties
+described by mmc.txt and the properties used by the Xenon implementation.
+
+A single Xenon IP can support multiple slots.
+Each slot acts as an independent SDHC. It owns independent resources, such
+as register sets clock and PHY.
+Each slot should have an independent device tree node.
+
+Required Properties:
+- compatible: should be "marvell,sdhci-xenon" or "marvell,armada-3700-sdhci".
+
+- Input Clock Name
+  Some SOCs require additional clock for AXI bus.
+  The input clock for Xenon IP core should be named as "core".
+  The optional AXI clock should be named as "axi".
+  - clocks = <&core_clk>, <&axi_clock>;
+  - clock-names = "core", "axi";
+
+- Register Set Size
+  Different Xenon SDHC release has different register set size.
+  The specific size should also refer to the SOC implementation.
+
+Optional Properties:
+- Slot Index
+  A single Xenon IP can support multiple slots.
+  During initialization, each slot should set corresponding setting bit in
+  some Xenon-specific registers. The corresponding bit is determined by
+  this property.
+  - xenon,slotno = <slot_index>;
+  If this property is not provided, Xenon IP should contain only one slot
+  and the slot index will be 0x0 by default.
+
+- PHY Type
+  Xenon support mutilple types of PHYs.
+  To select eMMC 5.1 PHY, set:
+  - xenon,phy-type = "emmc 5.1 phy"
+  eMMC 5.1 PHY is the default choice if this property is not provided.
+  To select eMMC 5.0 PHY, set:
+  - xenon,phy-type = "emmc 5.0 phy"
+  To select SDH PHY, set:
+  - xenon,phy-type = "sdh phy"
+  Please note that eMMC PHY is a general PHY for eMMC/SD/SDIO, other than for
+  eMMC only.
+
+- Customized eMMC PHY Parameters
+  Some boards require different values of some specific eMMC PHY parameters.
+  Some SOCs also require specific workaround to set eMMC PHY.
+  These properties enable diverse boards to customize the eMMC PHY.
+  The supported eMMC PHY parameters are listed in below. All those properties
+  are only available for eMMC PHY 5.1 and eMMC PHY 5.0.
+  ZNR
+  valid range = [0:0x1F].
+  ZNR is set as 0xF by default if this property is not provided.
+  - xenon,phy-znr = <value>;
+
+  ZPR
+  valid range = [0:0x1F].
+  ZPR is set as 0xF by default if this property is not provided.
+  - xenon,phy-zpr = <value>;
+
+  Number of successful tuning times
+  Set the number of required consecutive successful sampling points used to
+  identify a valid sampling window, in tuning process.
+  Valid range = [1:7]. Set as 0x4 by default if this property is not provided.
+  - xenon,phy-nr-tun-times = <nr_times>;
+
+  Divider for TUN_STEP
+  Set the divider for calculating TUN_STEP.
+  Set as 64 by default if this property is not provided.
+  - xenon,phy-tun-step-divider = <divider>;
+
+  Force PHY into slow mode.
+  Only available when bus frequency lower than 50MHz in SDR mde.
+  Disabled by default. Please do not enable it unless it is necessary.
+  - xenon,phy-slow-mode;
+
+- Mask Conflict Error Report
+  Disable Conflict Error alert on some SOC. Disabled by default.
+  xenon,mask-conflict-err;
+
+- Re-tuning Counter
+  Xenon SDHC SOC usually doesn't provide re-tuning counter in
+  Capabilities Register 3 Bit[11:8].
+  This property provides the re-tuning counter.
+  xenon,tuning-count = <count>;
+  If this property is not set, default re-tuning counter will
+  be set as 0x9 in driver.
+
+- SOC PHY PAD Voltage Control register
+  Some SOCs have SOC PHY PAD Voltage Control register outside Xenon IP.
+  This register sets SOC PHY PAD Voltage to keep aligh with Vccq.
+  Two properties provide information of this control register.
+  These two properties are only valid when "marvell,armada-3700-sdhci"
+  is selected. Both of them must be provided when "marvell,armada-3700-sdhci"
+  is selected.
+  - xenon,pad-type
+    Two types: "sd" and "fixed-1-8v".
+    If "sd" is slected, SOC PHY PAD is set as 3.3V at the beginning and is
+    switched to 1.8V when SD in UHS-I.
+    If "fixed-1-8v" is slected, SOC PHY PAD is fixed 1.8V, such as for eMMC.
+  - reg
+    Physical address and size of SOC PHY PAD register.
+    Append after Xenon SDHC register space, as a second register field.
+
+  Please follow the examples with compatible "marvell,armada-3700-sdhci"
+  in below.
+
+Example:
+- For eMMC slot:
+
+	sdhci@aa0000 {
+		compatible = "marvell,sdhci-xenon";
+		reg = <0xaa0000 0x1000>;
+		interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
+		clocks = <&emmcclk>;
+		clock-names = "core";
+		xenon,slotno = <0>;
+		xenon,phy-type = "emmc 5.1 phy";
+		bus-width = <8>;
+		tuning-count = <11>;
+	};
+
+- For SD/SDIO slot:
+
+	sdhci@ab0000 {
+		compatible = "marvell,sdhci-xenon";
+		reg = <0xab0000 0x1000>;
+		interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
+		vqmmc-supply = <&sd_regulator>;
+		clocks = <&sdclk>;
+		clock-names = "core";
+		bus-width = <4>;
+		tuning-count = <9>;
+	};
+
+- For eMMC slot with compatible "marvell,armada-3700-sdhci":
+
+	sdhci@aa0000 {
+		compatible = "marvell,armada-3700-sdhci";
+		reg = <0xaa0000 0x1000>,
+		      <phy_addr 0x4>;
+		interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
+		clocks = <&emmcclk>;
+		clock-names = "core";
+		bus-width = <8>;
+
+		xenon,pad-type = "fixed-1-8v";
+	};
+
+- For SD/SDIO slot with compatible "marvell,armada-3700-sdhci":
+
+	sdhci@ab0000 {
+		compatible = "marvell,armada-3700-sdhci";
+		reg = <0xab0000 0x1000>,
+		      <phy_addr 0x4>;
+		interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
+		vqmmc-supply = <&sd_regulator>;
+		clocks = <&sdclk>;
+		clock-names = "core";
+		bus-width = <4>;
+
+		xenon,pad-type = "sd";
+	};
diff --git a/MAINTAINERS b/MAINTAINERS
index 89adcd57aa25..4aa0eac9bfc7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7582,6 +7582,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
 M:	Ziji Hu <huziji@marvell.com>
 L:	linux-mmc@vger.kernel.org
 S:	Supported
+F:	Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
 
 MATROX FRAMEBUFFER DRIVER
 L:	linux-fbdev@vger.kernel.org
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 5/10] dt: bindings: Add bindings for Marvell Xenon SD Host Controller
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc-u79uwXL29TY76Z2rM5mHXA
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, 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

From: Ziji Hu <huziji-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>

Marvell Xenon SDHC can support eMMC/SD/SDIO.
Add Xenon-specific properties.
Also add properties for Xenon PHY setting.

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>
---
 Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt | 164 +++++++-
 MAINTAINERS                                                   |   1 +-
 2 files changed, 165 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt

diff --git a/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt b/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
new file mode 100644
index 000000000000..8b25ad28ebbd
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
@@ -0,0 +1,164 @@
+Marvell's Xenon SDHCI Controller device tree bindings
+This file documents differences between the core mmc properties
+described by mmc.txt and the properties used by the Xenon implementation.
+
+A single Xenon IP can support multiple slots.
+Each slot acts as an independent SDHC. It owns independent resources, such
+as register sets clock and PHY.
+Each slot should have an independent device tree node.
+
+Required Properties:
+- compatible: should be "marvell,sdhci-xenon" or "marvell,armada-3700-sdhci".
+
+- Input Clock Name
+  Some SOCs require additional clock for AXI bus.
+  The input clock for Xenon IP core should be named as "core".
+  The optional AXI clock should be named as "axi".
+  - clocks = <&core_clk>, <&axi_clock>;
+  - clock-names = "core", "axi";
+
+- Register Set Size
+  Different Xenon SDHC release has different register set size.
+  The specific size should also refer to the SOC implementation.
+
+Optional Properties:
+- Slot Index
+  A single Xenon IP can support multiple slots.
+  During initialization, each slot should set corresponding setting bit in
+  some Xenon-specific registers. The corresponding bit is determined by
+  this property.
+  - xenon,slotno = <slot_index>;
+  If this property is not provided, Xenon IP should contain only one slot
+  and the slot index will be 0x0 by default.
+
+- PHY Type
+  Xenon support mutilple types of PHYs.
+  To select eMMC 5.1 PHY, set:
+  - xenon,phy-type = "emmc 5.1 phy"
+  eMMC 5.1 PHY is the default choice if this property is not provided.
+  To select eMMC 5.0 PHY, set:
+  - xenon,phy-type = "emmc 5.0 phy"
+  To select SDH PHY, set:
+  - xenon,phy-type = "sdh phy"
+  Please note that eMMC PHY is a general PHY for eMMC/SD/SDIO, other than for
+  eMMC only.
+
+- Customized eMMC PHY Parameters
+  Some boards require different values of some specific eMMC PHY parameters.
+  Some SOCs also require specific workaround to set eMMC PHY.
+  These properties enable diverse boards to customize the eMMC PHY.
+  The supported eMMC PHY parameters are listed in below. All those properties
+  are only available for eMMC PHY 5.1 and eMMC PHY 5.0.
+  ZNR
+  valid range = [0:0x1F].
+  ZNR is set as 0xF by default if this property is not provided.
+  - xenon,phy-znr = <value>;
+
+  ZPR
+  valid range = [0:0x1F].
+  ZPR is set as 0xF by default if this property is not provided.
+  - xenon,phy-zpr = <value>;
+
+  Number of successful tuning times
+  Set the number of required consecutive successful sampling points used to
+  identify a valid sampling window, in tuning process.
+  Valid range = [1:7]. Set as 0x4 by default if this property is not provided.
+  - xenon,phy-nr-tun-times = <nr_times>;
+
+  Divider for TUN_STEP
+  Set the divider for calculating TUN_STEP.
+  Set as 64 by default if this property is not provided.
+  - xenon,phy-tun-step-divider = <divider>;
+
+  Force PHY into slow mode.
+  Only available when bus frequency lower than 50MHz in SDR mde.
+  Disabled by default. Please do not enable it unless it is necessary.
+  - xenon,phy-slow-mode;
+
+- Mask Conflict Error Report
+  Disable Conflict Error alert on some SOC. Disabled by default.
+  xenon,mask-conflict-err;
+
+- Re-tuning Counter
+  Xenon SDHC SOC usually doesn't provide re-tuning counter in
+  Capabilities Register 3 Bit[11:8].
+  This property provides the re-tuning counter.
+  xenon,tuning-count = <count>;
+  If this property is not set, default re-tuning counter will
+  be set as 0x9 in driver.
+
+- SOC PHY PAD Voltage Control register
+  Some SOCs have SOC PHY PAD Voltage Control register outside Xenon IP.
+  This register sets SOC PHY PAD Voltage to keep aligh with Vccq.
+  Two properties provide information of this control register.
+  These two properties are only valid when "marvell,armada-3700-sdhci"
+  is selected. Both of them must be provided when "marvell,armada-3700-sdhci"
+  is selected.
+  - xenon,pad-type
+    Two types: "sd" and "fixed-1-8v".
+    If "sd" is slected, SOC PHY PAD is set as 3.3V at the beginning and is
+    switched to 1.8V when SD in UHS-I.
+    If "fixed-1-8v" is slected, SOC PHY PAD is fixed 1.8V, such as for eMMC.
+  - reg
+    Physical address and size of SOC PHY PAD register.
+    Append after Xenon SDHC register space, as a second register field.
+
+  Please follow the examples with compatible "marvell,armada-3700-sdhci"
+  in below.
+
+Example:
+- For eMMC slot:
+
+	sdhci@aa0000 {
+		compatible = "marvell,sdhci-xenon";
+		reg = <0xaa0000 0x1000>;
+		interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
+		clocks = <&emmcclk>;
+		clock-names = "core";
+		xenon,slotno = <0>;
+		xenon,phy-type = "emmc 5.1 phy";
+		bus-width = <8>;
+		tuning-count = <11>;
+	};
+
+- For SD/SDIO slot:
+
+	sdhci@ab0000 {
+		compatible = "marvell,sdhci-xenon";
+		reg = <0xab0000 0x1000>;
+		interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
+		vqmmc-supply = <&sd_regulator>;
+		clocks = <&sdclk>;
+		clock-names = "core";
+		bus-width = <4>;
+		tuning-count = <9>;
+	};
+
+- For eMMC slot with compatible "marvell,armada-3700-sdhci":
+
+	sdhci@aa0000 {
+		compatible = "marvell,armada-3700-sdhci";
+		reg = <0xaa0000 0x1000>,
+		      <phy_addr 0x4>;
+		interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
+		clocks = <&emmcclk>;
+		clock-names = "core";
+		bus-width = <8>;
+
+		xenon,pad-type = "fixed-1-8v";
+	};
+
+- For SD/SDIO slot with compatible "marvell,armada-3700-sdhci":
+
+	sdhci@ab0000 {
+		compatible = "marvell,armada-3700-sdhci";
+		reg = <0xab0000 0x1000>,
+		      <phy_addr 0x4>;
+		interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
+		vqmmc-supply = <&sd_regulator>;
+		clocks = <&sdclk>;
+		clock-names = "core";
+		bus-width = <4>;
+
+		xenon,pad-type = "sd";
+	};
diff --git a/MAINTAINERS b/MAINTAINERS
index 89adcd57aa25..4aa0eac9bfc7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7582,6 +7582,7 @@ 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
+F:	Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
 
 MATROX FRAMEBUFFER DRIVER
 L:	linux-fbdev-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
-- 
git-series 0.8.10
--
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 related	[flat|nested] 105+ messages in thread

* [PATCH 5/10] dt: bindings: Add bindings for Marvell Xenon SD Host Controller
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: linux-arm-kernel

From: Ziji Hu <huziji@marvell.com>

Marvell Xenon SDHC can support eMMC/SD/SDIO.
Add Xenon-specific properties.
Also add properties for Xenon PHY setting.

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>
---
 Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt | 164 +++++++-
 MAINTAINERS                                                   |   1 +-
 2 files changed, 165 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt

diff --git a/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt b/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
new file mode 100644
index 000000000000..8b25ad28ebbd
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
@@ -0,0 +1,164 @@
+Marvell's Xenon SDHCI Controller device tree bindings
+This file documents differences between the core mmc properties
+described by mmc.txt and the properties used by the Xenon implementation.
+
+A single Xenon IP can support multiple slots.
+Each slot acts as an independent SDHC. It owns independent resources, such
+as register sets clock and PHY.
+Each slot should have an independent device tree node.
+
+Required Properties:
+- compatible: should be "marvell,sdhci-xenon" or "marvell,armada-3700-sdhci".
+
+- Input Clock Name
+  Some SOCs require additional clock for AXI bus.
+  The input clock for Xenon IP core should be named as "core".
+  The optional AXI clock should be named as "axi".
+  - clocks = <&core_clk>, <&axi_clock>;
+  - clock-names = "core", "axi";
+
+- Register Set Size
+  Different Xenon SDHC release has different register set size.
+  The specific size should also refer to the SOC implementation.
+
+Optional Properties:
+- Slot Index
+  A single Xenon IP can support multiple slots.
+  During initialization, each slot should set corresponding setting bit in
+  some Xenon-specific registers. The corresponding bit is determined by
+  this property.
+  - xenon,slotno = <slot_index>;
+  If this property is not provided, Xenon IP should contain only one slot
+  and the slot index will be 0x0 by default.
+
+- PHY Type
+  Xenon support mutilple types of PHYs.
+  To select eMMC 5.1 PHY, set:
+  - xenon,phy-type = "emmc 5.1 phy"
+  eMMC 5.1 PHY is the default choice if this property is not provided.
+  To select eMMC 5.0 PHY, set:
+  - xenon,phy-type = "emmc 5.0 phy"
+  To select SDH PHY, set:
+  - xenon,phy-type = "sdh phy"
+  Please note that eMMC PHY is a general PHY for eMMC/SD/SDIO, other than for
+  eMMC only.
+
+- Customized eMMC PHY Parameters
+  Some boards require different values of some specific eMMC PHY parameters.
+  Some SOCs also require specific workaround to set eMMC PHY.
+  These properties enable diverse boards to customize the eMMC PHY.
+  The supported eMMC PHY parameters are listed in below. All those properties
+  are only available for eMMC PHY 5.1 and eMMC PHY 5.0.
+  ZNR
+  valid range = [0:0x1F].
+  ZNR is set as 0xF by default if this property is not provided.
+  - xenon,phy-znr = <value>;
+
+  ZPR
+  valid range = [0:0x1F].
+  ZPR is set as 0xF by default if this property is not provided.
+  - xenon,phy-zpr = <value>;
+
+  Number of successful tuning times
+  Set the number of required consecutive successful sampling points used to
+  identify a valid sampling window, in tuning process.
+  Valid range = [1:7]. Set as 0x4 by default if this property is not provided.
+  - xenon,phy-nr-tun-times = <nr_times>;
+
+  Divider for TUN_STEP
+  Set the divider for calculating TUN_STEP.
+  Set as 64 by default if this property is not provided.
+  - xenon,phy-tun-step-divider = <divider>;
+
+  Force PHY into slow mode.
+  Only available when bus frequency lower than 50MHz in SDR mde.
+  Disabled by default. Please do not enable it unless it is necessary.
+  - xenon,phy-slow-mode;
+
+- Mask Conflict Error Report
+  Disable Conflict Error alert on some SOC. Disabled by default.
+  xenon,mask-conflict-err;
+
+- Re-tuning Counter
+  Xenon SDHC SOC usually doesn't provide re-tuning counter in
+  Capabilities Register 3 Bit[11:8].
+  This property provides the re-tuning counter.
+  xenon,tuning-count = <count>;
+  If this property is not set, default re-tuning counter will
+  be set as 0x9 in driver.
+
+- SOC PHY PAD Voltage Control register
+  Some SOCs have SOC PHY PAD Voltage Control register outside Xenon IP.
+  This register sets SOC PHY PAD Voltage to keep aligh with Vccq.
+  Two properties provide information of this control register.
+  These two properties are only valid when "marvell,armada-3700-sdhci"
+  is selected. Both of them must be provided when "marvell,armada-3700-sdhci"
+  is selected.
+  - xenon,pad-type
+    Two types: "sd" and "fixed-1-8v".
+    If "sd" is slected, SOC PHY PAD is set as 3.3V at the beginning and is
+    switched to 1.8V when SD in UHS-I.
+    If "fixed-1-8v" is slected, SOC PHY PAD is fixed 1.8V, such as for eMMC.
+  - reg
+    Physical address and size of SOC PHY PAD register.
+    Append after Xenon SDHC register space, as a second register field.
+
+  Please follow the examples with compatible "marvell,armada-3700-sdhci"
+  in below.
+
+Example:
+- For eMMC slot:
+
+	sdhci at aa0000 {
+		compatible = "marvell,sdhci-xenon";
+		reg = <0xaa0000 0x1000>;
+		interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
+		clocks = <&emmcclk>;
+		clock-names = "core";
+		xenon,slotno = <0>;
+		xenon,phy-type = "emmc 5.1 phy";
+		bus-width = <8>;
+		tuning-count = <11>;
+	};
+
+- For SD/SDIO slot:
+
+	sdhci at ab0000 {
+		compatible = "marvell,sdhci-xenon";
+		reg = <0xab0000 0x1000>;
+		interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
+		vqmmc-supply = <&sd_regulator>;
+		clocks = <&sdclk>;
+		clock-names = "core";
+		bus-width = <4>;
+		tuning-count = <9>;
+	};
+
+- For eMMC slot with compatible "marvell,armada-3700-sdhci":
+
+	sdhci at aa0000 {
+		compatible = "marvell,armada-3700-sdhci";
+		reg = <0xaa0000 0x1000>,
+		      <phy_addr 0x4>;
+		interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
+		clocks = <&emmcclk>;
+		clock-names = "core";
+		bus-width = <8>;
+
+		xenon,pad-type = "fixed-1-8v";
+	};
+
+- For SD/SDIO slot with compatible "marvell,armada-3700-sdhci":
+
+	sdhci at ab0000 {
+		compatible = "marvell,armada-3700-sdhci";
+		reg = <0xab0000 0x1000>,
+		      <phy_addr 0x4>;
+		interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
+		vqmmc-supply = <&sd_regulator>;
+		clocks = <&sdclk>;
+		clock-names = "core";
+		bus-width = <4>;
+
+		xenon,pad-type = "sd";
+	};
diff --git a/MAINTAINERS b/MAINTAINERS
index 89adcd57aa25..4aa0eac9bfc7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7582,6 +7582,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
 M:	Ziji Hu <huziji@marvell.com>
 L:	linux-mmc at vger.kernel.org
 S:	Supported
+F:	Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
 
 MATROX FRAMEBUFFER DRIVER
 L:	linux-fbdev at vger.kernel.org
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, Rob Herring, devicetree, Thomas Petazzoni,
	linux-arm-kernel, 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, Peng Zhu, Yu Cao, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

From: Ziji Hu <huziji@marvell.com>

Add Xenon eMMC/SD/SDIO host controller core functionality.
Add Xenon specific intialization process.
Add Xenon specific mmc_host_ops APIs.
Add Xenon specific register definitions.

Add CONFIG_MMC_SDHCI_XENON support in drivers/mmc/host/Kconfig.

Marvell Xenon SDHC conforms to SD Physical Layer Specification
Version 3.01 and is designed according to the guidelines provided
in the SD Host Controller Standard Specification Version 3.00.

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/Kconfig       |   9 +-
 drivers/mmc/host/Makefile      |   3 +-
 drivers/mmc/host/sdhci-xenon.c | 599 ++++++++++++++++++++++++++++++++++-
 drivers/mmc/host/sdhci-xenon.h | 134 ++++++++-
 5 files changed, 746 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mmc/host/sdhci-xenon.c
 create mode 100644 drivers/mmc/host/sdhci-xenon.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 4aa0eac9bfc7..859420e5dfd3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7582,6 +7582,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
 M:	Ziji Hu <huziji@marvell.com>
 L:	linux-mmc@vger.kernel.org
 S:	Supported
+F:	drivers/mmc/host/sdhci-xenon.*
 F:	Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
 
 MATROX FRAMEBUFFER DRIVER
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 5274f503a39a..85a53623526a 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -798,3 +798,12 @@ config MMC_SDHCI_BRCMSTB
 	  Broadcom STB SoCs.
 
 	  If unsure, say Y.
+
+config MMC_SDHCI_XENON
+	tristate "Marvell Xenon eMMC/SD/SDIO SDHCI driver"
+	depends on MMC_SDHCI && MMC_SDHCI_PLTFM
+	help
+	  This selects Marvell Xenon eMMC/SD/SDIO SDHCI.
+	  If you have a machine with integrated Marvell Xenon SDHC IP,
+	  say Y or M here.
+	  If unsure, say N.
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index e2bdaaf43184..75eaf743486c 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -80,3 +80,6 @@ obj-$(CONFIG_MMC_SDHCI_BRCMSTB)		+= sdhci-brcmstb.o
 ifeq ($(CONFIG_CB710_DEBUG),y)
 	CFLAGS-cb710-mmc	+= -DDEBUG
 endif
+
+obj-$(CONFIG_MMC_SDHCI_XENON)	+= sdhci-xenon-driver.o
+sdhci-xenon-driver-y		+= sdhci-xenon.o
diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c
new file mode 100644
index 000000000000..03ba183494d3
--- /dev/null
+++ b/drivers/mmc/host/sdhci-xenon.c
@@ -0,0 +1,599 @@
+/*
+ * Driver for Marvell SOC Platform Group Xenon SDHC as a platform device
+ *
+ * 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.
+ *
+ * Inspired by Jisheng Zhang <jszhang@marvell.com>
+ * Special thanks to Video BG4 project team.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+#include <linux/module.h>
+#include <linux/of.h>
+
+#include "sdhci-pltfm.h"
+#include "sdhci.h"
+#include "sdhci-xenon.h"
+
+/* Set SDCLK-off-while-idle */
+static void xenon_set_sdclk_off_idle(struct sdhci_host *host,
+				     unsigned char slot_idx, bool enable)
+{
+	u32 reg;
+	u32 mask;
+
+	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
+	/* Get the bit shift basing on the slot index */
+	mask = (0x1 << (SDCLK_IDLEOFF_ENABLE_SHIFT + slot_idx));
+	if (enable)
+		reg |= mask;
+	else
+		reg &= ~mask;
+
+	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
+}
+
+/* Enable/Disable the Auto Clock Gating function */
+static void xenon_set_acg(struct sdhci_host *host, bool enable)
+{
+	u32 reg;
+
+	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
+	if (enable)
+		reg &= ~AUTO_CLKGATE_DISABLE_MASK;
+	else
+		reg |= AUTO_CLKGATE_DISABLE_MASK;
+	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
+}
+
+/* Enable this slot */
+static void xenon_enable_slot(struct sdhci_host *host,
+			      unsigned char slot_idx)
+{
+	u32 reg;
+
+	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
+	reg |= (BIT(slot_idx) << SLOT_ENABLE_SHIFT);
+	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
+
+	/*
+	 * Manually set the flag which all the slots require,
+	 * including SD, eMMC, SDIO
+	 */
+	host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY;
+}
+
+/* Disable this slot */
+static void xenon_disable_slot(struct sdhci_host *host,
+			       unsigned char slot_idx)
+{
+	u32 reg;
+
+	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
+	reg &= ~(BIT(slot_idx) << SLOT_ENABLE_SHIFT);
+	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
+}
+
+/* Enable Parallel Transfer Mode */
+static void xenon_enable_slot_parallel_tran(struct sdhci_host *host,
+					    unsigned char slot_idx)
+{
+	u32 reg;
+
+	reg = sdhci_readl(host, SDHC_SYS_EXT_OP_CTRL);
+	reg |= BIT(slot_idx);
+	sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
+}
+
+static void xenon_slot_tuning_setup(struct sdhci_host *host)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	u32 reg;
+
+	/* Disable the Re-Tuning Request functionality */
+	reg = sdhci_readl(host, SDHC_SLOT_RETUNING_REQ_CTRL);
+	reg &= ~RETUNING_COMPATIBLE;
+	sdhci_writel(host, reg, SDHC_SLOT_RETUNING_REQ_CTRL);
+
+	/* Disbale the Re-tuning Event Signal Enable */
+	reg = sdhci_readl(host, SDHCI_SIGNAL_ENABLE);
+	reg &= ~SDHCI_INT_RETUNE;
+	sdhci_writel(host, reg, SDHCI_SIGNAL_ENABLE);
+
+	/* Force to use Tuning Mode 1 */
+	host->tuning_mode = SDHCI_TUNING_MODE_1;
+	/* Set re-tuning period */
+	host->tuning_count = 1 << (priv->tuning_count - 1);
+}
+
+/*
+ * Operations inside struct sdhci_ops
+ */
+/* Recover the Register Setting cleared during SOFTWARE_RESET_ALL */
+static void sdhci_xenon_reset_exit(struct sdhci_host *host,
+				   unsigned char slot_idx, u8 mask)
+{
+	/* Only SOFTWARE RESET ALL will clear the register setting */
+	if (!(mask & SDHCI_RESET_ALL))
+		return;
+
+	/* Disable tuning request and auto-retuing again */
+	xenon_slot_tuning_setup(host);
+
+	xenon_set_acg(host, true);
+
+	xenon_set_sdclk_off_idle(host, slot_idx, false);
+}
+
+static void sdhci_xenon_reset(struct sdhci_host *host, u8 mask)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+
+	sdhci_reset(host, mask);
+	sdhci_xenon_reset_exit(host, priv->slot_idx, mask);
+}
+
+/*
+ * Xenon defines different values for HS200 and SDR104
+ * in Host_Control_2
+ */
+static void xenon_set_uhs_signaling(struct sdhci_host *host,
+				    unsigned int timing)
+{
+	u16 ctrl_2;
+
+	ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+	/* Select Bus Speed Mode for host */
+	ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
+	if (timing == MMC_TIMING_MMC_HS200)
+		ctrl_2 |= XENON_SDHCI_CTRL_HS200;
+	else if (timing == MMC_TIMING_UHS_SDR104)
+		ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
+	else if (timing == MMC_TIMING_UHS_SDR12)
+		ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
+	else if (timing == MMC_TIMING_UHS_SDR25)
+		ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
+	else if (timing == MMC_TIMING_UHS_SDR50)
+		ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
+	else if ((timing == MMC_TIMING_UHS_DDR50) ||
+		 (timing == MMC_TIMING_MMC_DDR52))
+		ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
+	else if (timing == MMC_TIMING_MMC_HS400)
+		ctrl_2 |= XENON_SDHCI_CTRL_HS400;
+	sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
+}
+
+static const struct sdhci_ops sdhci_xenon_ops = {
+	.set_clock		= sdhci_set_clock,
+	.set_bus_width		= sdhci_set_bus_width,
+	.reset			= sdhci_xenon_reset,
+	.set_uhs_signaling	= xenon_set_uhs_signaling,
+	.get_max_clock		= sdhci_pltfm_clk_get_max_clock,
+};
+
+static const struct sdhci_pltfm_data sdhci_xenon_pdata = {
+	.ops = &sdhci_xenon_ops,
+	.quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC |
+			SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 |
+			SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER |
+			SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
+};
+
+/*
+ * Xenon Specific Operations in mmc_host_ops
+ */
+static void xenon_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct sdhci_host *host = mmc_priv(mmc);
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	unsigned long flags;
+
+	/*
+	 * HS400/HS200/eMMC HS doesn't have Preset Value register.
+	 * However, sdhci_set_ios will read HS400/HS200 Preset register.
+	 * Disable Preset Value register for HS400/HS200.
+	 * eMMC HS with preset_enabled set will trigger a bug in
+	 * get_preset_value().
+	 */
+	spin_lock_irqsave(&host->lock, flags);
+	if ((ios->timing == MMC_TIMING_MMC_HS400) ||
+	    (ios->timing == MMC_TIMING_MMC_HS200) ||
+	    (ios->timing == MMC_TIMING_MMC_HS)) {
+		host->preset_enabled = false;
+		host->quirks2 |= SDHCI_QUIRK2_PRESET_VALUE_BROKEN;
+	} else {
+		host->quirks2 &= ~SDHCI_QUIRK2_PRESET_VALUE_BROKEN;
+	}
+	spin_unlock_irqrestore(&host->lock, flags);
+
+	sdhci_set_ios(mmc, ios);
+
+	if (host->clock > DEFAULT_SDCLK_FREQ) {
+		spin_lock_irqsave(&host->lock, flags);
+		xenon_set_sdclk_off_idle(host, priv->slot_idx, true);
+		spin_unlock_irqrestore(&host->lock, flags);
+	}
+}
+
+static int __emmc_signal_voltage_switch(struct mmc_host *mmc,
+					const unsigned char signal_voltage)
+{
+	u32 ctrl;
+	unsigned char voltage_code;
+	struct sdhci_host *host = mmc_priv(mmc);
+
+	if (signal_voltage == MMC_SIGNAL_VOLTAGE_330)
+		voltage_code = EMMC_VCCQ_3_3V;
+	else if (signal_voltage == MMC_SIGNAL_VOLTAGE_180)
+		voltage_code = EMMC_VCCQ_1_8V;
+	else
+		return -EINVAL;
+
+	/*
+	 * This host is for eMMC, XENON self-defined
+	 * eMMC slot control register should be accessed
+	 * instead of Host Control 2
+	 */
+	ctrl = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
+	ctrl &= ~EMMC_VCCQ_MASK;
+	ctrl |= voltage_code;
+	sdhci_writel(host, ctrl, SDHC_SLOT_EMMC_CTRL);
+
+	/* There is no standard to determine this waiting period */
+	usleep_range(1000, 2000);
+
+	/* Check whether io voltage switch is done */
+	ctrl = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
+	ctrl &= EMMC_VCCQ_MASK;
+	/*
+	 * This bit is set only when regulator feeds back the voltage switch
+	 * results to Xenon SDHC.
+	 * However, in actaul implementation, regulator might not provide
+	 * this feedback.
+	 * Thus we shall not rely on this bit to determine if switch failed.
+	 * If the bit is not set, just throw a message.
+	 * Besides, error code should not be returned.
+	 */
+	if (ctrl != voltage_code)
+		dev_info(mmc_dev(mmc), "fail to detect eMMC signal voltage stable\n");
+	return 0;
+}
+
+static int xenon_emmc_signal_voltage_switch(struct mmc_host *mmc,
+					    struct mmc_ios *ios)
+{
+	unsigned char voltage = ios->signal_voltage;
+
+	if ((voltage == MMC_SIGNAL_VOLTAGE_330) ||
+	    (voltage == MMC_SIGNAL_VOLTAGE_180))
+		return __emmc_signal_voltage_switch(mmc, voltage);
+
+	dev_err(mmc_dev(mmc), "Unsupported signal voltage: %d\n",
+		voltage);
+	return -EINVAL;
+}
+
+static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
+					     struct mmc_ios *ios)
+{
+	struct sdhci_host *host = mmc_priv(mmc);
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+
+	/*
+	 * Before SD/SDIO set signal voltage, SD bus clock should be
+	 * disabled. However, sdhci_set_clock will also disable the Internal
+	 * clock in mmc_set_signal_voltage().
+	 * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
+	 * Thus here manually enable internal clock.
+	 *
+	 * After switch completes, it is unnecessary to disable internal clock,
+	 * since keeping internal clock active obeys SD spec.
+	 */
+	enable_xenon_internal_clk(host);
+
+	if (priv->card_candidate) {
+		if (mmc_card_mmc(priv->card_candidate))
+			return xenon_emmc_signal_voltage_switch(mmc, ios);
+	}
+
+	return sdhci_start_signal_voltage_switch(mmc, ios);
+}
+
+/*
+ * After determining which slot is used for SDIO,
+ * some additional task is required.
+ */
+static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
+{
+	struct sdhci_host *host = mmc_priv(mmc);
+	u32 reg;
+	u8 slot_idx;
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+
+	/* Link the card for delay adjustment */
+	priv->card_candidate = card;
+	/* Set tuning functionality of this slot */
+	xenon_slot_tuning_setup(host);
+
+	slot_idx = priv->slot_idx;
+	if (!mmc_card_sdio(card)) {
+		/* Re-enable the Auto-CMD12 cap flag. */
+		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
+		host->flags |= SDHCI_AUTO_CMD12;
+
+		/* Clear SDIO Card Inserted indication */
+		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
+		reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
+		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
+
+		if (mmc_card_mmc(card)) {
+			mmc->caps |= MMC_CAP_NONREMOVABLE;
+			if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
+				mmc->caps |= MMC_CAP_1_8V_DDR;
+			/*
+			 * Force to clear BUS_TEST to
+			 * skip bus_test_pre and bus_test_post
+			 */
+			mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
+			mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
+				      MMC_CAP2_PACKED_CMD;
+			if (mmc->caps & MMC_CAP_8_BIT_DATA)
+				mmc->caps2 |= MMC_CAP2_HS400_1_8V;
+		}
+	} else {
+		/*
+		 * Delete the Auto-CMD12 cap flag.
+		 * Otherwise, when sending multi-block CMD53,
+		 * Driver will set Transfer Mode Register to enable Auto CMD12.
+		 * However, SDIO device cannot recognize CMD12.
+		 * Thus SDHC will time-out for waiting for CMD12 response.
+		 */
+		host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
+		host->flags &= ~SDHCI_AUTO_CMD12;
+
+		/*
+		 * Set SDIO Card Inserted indication
+		 * to inform that the current slot is for SDIO
+		 */
+		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
+		reg |= (1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
+		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
+	}
+}
+
+static int xenon_execute_tuning(struct mmc_host *mmc, u32 opcode)
+{
+	struct sdhci_host *host = mmc_priv(mmc);
+
+	if (host->timing == MMC_TIMING_UHS_DDR50)
+		return 0;
+
+	return sdhci_execute_tuning(mmc, opcode);
+}
+
+static void xenon_replace_mmc_host_ops(struct sdhci_host *host)
+{
+	host->mmc_host_ops.set_ios = xenon_set_ios;
+	host->mmc_host_ops.start_signal_voltage_switch =
+			xenon_start_signal_voltage_switch;
+	host->mmc_host_ops.init_card = xenon_init_card;
+	host->mmc_host_ops.execute_tuning = xenon_execute_tuning;
+}
+
+static int xenon_probe_dt(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct sdhci_host *host = platform_get_drvdata(pdev);
+	struct mmc_host *mmc = host->mmc;
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	int err;
+	u32 slot_idx, nr_slot;
+	u32 tuning_count;
+	u32 reg;
+
+	/* Standard MMC property */
+	err = mmc_of_parse(mmc);
+	if (err)
+		return err;
+
+	/* Standard SDHCI property */
+	sdhci_get_of_property(pdev);
+
+	/*
+	 * Xenon Specific property:
+	 * slotno: the index of slot. Refer to SDHC_SYS_CFG_INFO register
+	 * tuning-count: the interval between re-tuning
+	 * PHY type: "sdhc phy", "emmc phy 5.0" or "emmc phy 5.1"
+	 */
+	if (!of_property_read_u32(np, "xenon,slotno", &slot_idx)) {
+		nr_slot = sdhci_readl(host, SDHC_SYS_CFG_INFO);
+		nr_slot &= NR_SUPPORTED_SLOT_MASK;
+		if (unlikely(slot_idx > nr_slot)) {
+			dev_err(mmc_dev(mmc), "Slot Index %d exceeds Number of slots %d\n",
+				slot_idx, nr_slot);
+			return -EINVAL;
+		}
+	} else {
+		priv->slot_idx = 0x0;
+	}
+
+	if (!of_property_read_u32(np, "xenon,tuning-count", &tuning_count)) {
+		if (unlikely(tuning_count >= TMR_RETUN_NO_PRESENT)) {
+			dev_err(mmc_dev(mmc), "Wrong Re-tuning Count. Set default value %d\n",
+				DEF_TUNING_COUNT);
+			tuning_count = DEF_TUNING_COUNT;
+		}
+	} else {
+		priv->tuning_count = DEF_TUNING_COUNT;
+	}
+
+	if (of_property_read_bool(np, "xenon,mask-conflict-err")) {
+		reg = sdhci_readl(host, SDHC_SYS_EXT_OP_CTRL);
+		reg |= MASK_CMD_CONFLICT_ERROR;
+		sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
+	}
+
+	return err;
+}
+
+static int xenon_slot_probe(struct sdhci_host *host)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	u8 slot_idx = priv->slot_idx;
+
+	/* Enable slot */
+	xenon_enable_slot(host, slot_idx);
+
+	/* Enable ACG */
+	xenon_set_acg(host, true);
+
+	/* Enable Parallel Transfer Mode */
+	xenon_enable_slot_parallel_tran(host, slot_idx);
+
+	priv->timing = MMC_TIMING_FAKE;
+	priv->clock = 0;
+
+	return 0;
+}
+
+static void xenon_slot_remove(struct sdhci_host *host)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	u8 slot_idx = priv->slot_idx;
+
+	/* disable slot */
+	xenon_disable_slot(host, slot_idx);
+}
+
+static int sdhci_xenon_probe(struct platform_device *pdev)
+{
+	struct sdhci_pltfm_host *pltfm_host;
+	struct sdhci_host *host;
+	struct clk *clk, *axi_clk;
+	struct sdhci_xenon_priv *priv;
+	int err;
+
+	host = sdhci_pltfm_init(pdev, &sdhci_xenon_pdata,
+				sizeof(struct sdhci_xenon_priv));
+	if (IS_ERR(host))
+		return PTR_ERR(host);
+
+	pltfm_host = sdhci_priv(host);
+	priv = sdhci_pltfm_priv(pltfm_host);
+
+	xenon_set_acg(host, false);
+
+	/*
+	 * Link Xenon specific mmc_host_ops function,
+	 * to replace standard ones in sdhci_ops.
+	 */
+	xenon_replace_mmc_host_ops(host);
+
+	clk = devm_clk_get(&pdev->dev, "core");
+	if (IS_ERR(clk)) {
+		dev_err(&pdev->dev, "Failed to setup input clk.\n");
+		err = PTR_ERR(clk);
+		goto free_pltfm;
+	}
+	clk_prepare_enable(clk);
+	pltfm_host->clk = clk;
+
+	/*
+	 * Some SOCs require additional clock to
+	 * manage AXI bus clock.
+	 * It is optional.
+	 */
+	axi_clk = devm_clk_get(&pdev->dev, "axi");
+	if (!IS_ERR(axi_clk)) {
+		clk_prepare_enable(axi_clk);
+		priv->axi_clk = axi_clk;
+	}
+
+	err = xenon_probe_dt(pdev);
+	if (err)
+		goto err_clk;
+
+	err = xenon_slot_probe(host);
+	if (err)
+		goto err_clk;
+
+	err = sdhci_add_host(host);
+	if (err)
+		goto remove_slot;
+
+	return 0;
+
+remove_slot:
+	xenon_slot_remove(host);
+err_clk:
+	clk_disable_unprepare(pltfm_host->clk);
+	if (!IS_ERR(axi_clk))
+		clk_disable_unprepare(axi_clk);
+free_pltfm:
+	sdhci_pltfm_free(pdev);
+	return err;
+}
+
+static int sdhci_xenon_remove(struct platform_device *pdev)
+{
+	struct sdhci_host *host = platform_get_drvdata(pdev);
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xFFFFFFFF);
+
+	xenon_slot_remove(host);
+
+	sdhci_remove_host(host, dead);
+
+	clk_disable_unprepare(pltfm_host->clk);
+	clk_disable_unprepare(priv->axi_clk);
+
+	sdhci_pltfm_free(pdev);
+
+	return 0;
+}
+
+static const struct of_device_id sdhci_xenon_dt_ids[] = {
+	{ .compatible = "marvell,sdhci-xenon",},
+	{ .compatible = "marvell,armada-3700-sdhci",},
+	{}
+};
+MODULE_DEVICE_TABLE(of, sdhci_xenon_dt_ids);
+
+static struct platform_driver sdhci_xenon_driver = {
+	.driver	= {
+		.name	= "sdhci-xenon",
+		.of_match_table = sdhci_xenon_dt_ids,
+		.pm = &sdhci_pltfm_pmops,
+	},
+	.probe	= sdhci_xenon_probe,
+	.remove	= sdhci_xenon_remove,
+};
+
+module_platform_driver(sdhci_xenon_driver);
+
+MODULE_DESCRIPTION("SDHCI platform driver for Marvell Xenon SDHC");
+MODULE_AUTHOR("Hu Ziji <huziji@marvell.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h
new file mode 100644
index 000000000000..c2370493fbe8
--- /dev/null
+++ b/drivers/mmc/host/sdhci-xenon.h
@@ -0,0 +1,134 @@
+/*
+ * 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.
+ */
+#ifndef SDHCI_XENON_H_
+#define SDHCI_XENON_H_
+
+#include <linux/clk.h>
+#include <linux/mmc/card.h>
+#include <linux/of.h>
+#include "sdhci.h"
+
+/* Register Offset of SD Host Controller SOCP self-defined register */
+#define SDHC_SYS_CFG_INFO			0x0104
+#define SLOT_TYPE_SDIO_SHIFT			24
+#define SLOT_TYPE_EMMC_MASK			0xFF
+#define SLOT_TYPE_EMMC_SHIFT			16
+#define SLOT_TYPE_SD_SDIO_MMC_MASK		0xFF
+#define SLOT_TYPE_SD_SDIO_MMC_SHIFT		8
+#define NR_SUPPORTED_SLOT_MASK			0x7
+
+#define SDHC_SYS_OP_CTRL			0x0108
+#define AUTO_CLKGATE_DISABLE_MASK		BIT(20)
+#define SDCLK_IDLEOFF_ENABLE_SHIFT		8
+#define SLOT_ENABLE_SHIFT			0
+
+#define SDHC_SYS_EXT_OP_CTRL			0x010C
+#define MASK_CMD_CONFLICT_ERROR			BIT(8)
+
+#define SDHC_SLOT_OP_STATUS_CTRL		0x0128
+#define DELAY_90_DEGREE_MASK_EMMC5		BIT(7)
+#define DELAY_90_DEGREE_SHIFT_EMMC5		7
+#define EMMC_5_0_PHY_FIXED_DELAY_MASK		0x7F
+#define EMMC_PHY_FIXED_DELAY_MASK		0xFF
+#define EMMC_PHY_FIXED_DELAY_WINDOW_MIN		(EMMC_PHY_FIXED_DELAY_MASK >> 3)
+#define SDH_PHY_FIXED_DELAY_MASK		0x1FF
+#define SDH_PHY_FIXED_DELAY_WINDOW_MIN		(SDH_PHY_FIXED_DELAY_MASK >> 4)
+
+#define TUN_CONSECUTIVE_TIMES_SHIFT		16
+#define TUN_CONSECUTIVE_TIMES_MASK		0x7
+#define TUN_CONSECUTIVE_TIMES			0x4
+#define TUNING_STEP_SHIFT			12
+#define TUNING_STEP_MASK			0xF
+#define TUNING_STEP_DIVIDER			BIT(6)
+
+#define FORCE_SEL_INVERSE_CLK_SHIFT		11
+
+#define SDHC_SLOT_EMMC_CTRL			0x0130
+#define ENABLE_DATA_STROBE			BIT(24)
+#define SET_EMMC_RSTN				BIT(16)
+#define DISABLE_RD_DATA_CRC			BIT(14)
+#define DISABLE_CRC_STAT_TOKEN			BIT(13)
+#define EMMC_VCCQ_MASK				0x3
+#define EMMC_VCCQ_1_8V				0x1
+#define EMMC_VCCQ_3_3V				0x3
+
+#define SDHC_SLOT_RETUNING_REQ_CTRL		0x0144
+/* retuning compatible */
+#define RETUNING_COMPATIBLE			0x1
+
+#define SDHC_SLOT_EXT_PRESENT_STATE		0x014C
+#define LOCK_STATE				0x1
+
+#define SDHC_SLOT_DLL_CUR_DLY_VAL		0x0150
+
+/* Tuning Parameter */
+#define TMR_RETUN_NO_PRESENT			0xF
+#define DEF_TUNING_COUNT			0x9
+
+#define MMC_TIMING_FAKE				0xFF
+
+#define DEFAULT_SDCLK_FREQ			(400000)
+
+/* Xenon specific Mode Select value */
+#define XENON_SDHCI_CTRL_HS200			0x5
+#define XENON_SDHCI_CTRL_HS400			0x6
+
+struct sdhci_xenon_priv {
+	/*
+	 * The bus_width, timing, and clock fields in below
+	 * record the current setting of Xenon SDHC.
+	 * Driver will call a Sampling Fixed Delay Adjustment
+	 * if any setting is changed.
+	 */
+	unsigned char	bus_width;
+	unsigned char	timing;
+	unsigned char	tuning_count;
+	unsigned int	clock;
+	struct clk	*axi_clk;
+
+	/* Slot idx */
+	u8		slot_idx;
+
+	/*
+	 * When initializing card, Xenon has to determine card type and
+	 * adjust Sampling Fixed delay.
+	 * However, at that time, card structure is not linked to mmc_host.
+	 * Thus a card pointer is added here to provide
+	 * the delay adjustment function with the card structure
+	 * of the card during initialization
+	 */
+	struct mmc_card *card_candidate;
+};
+
+static inline int enable_xenon_internal_clk(struct sdhci_host *host)
+{
+	u32 reg;
+	u8 timeout;
+
+	reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
+	reg |= SDHCI_CLOCK_INT_EN;
+	sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
+	/* Wait max 20 ms */
+	timeout = 20;
+	while (!((reg = sdhci_readw(host, SDHCI_CLOCK_CONTROL))
+			& SDHCI_CLOCK_INT_STABLE)) {
+		if (timeout == 0) {
+			pr_err("%s: Internal clock never stabilised.\n",
+			       mmc_hostname(host->mmc));
+			return -ETIMEDOUT;
+		}
+		timeout--;
+		mdelay(1);
+	}
+
+	return 0;
+}
+#endif
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc-u79uwXL29TY76Z2rM5mHXA
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, 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

From: Ziji Hu <huziji-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>

Add Xenon eMMC/SD/SDIO host controller core functionality.
Add Xenon specific intialization process.
Add Xenon specific mmc_host_ops APIs.
Add Xenon specific register definitions.

Add CONFIG_MMC_SDHCI_XENON support in drivers/mmc/host/Kconfig.

Marvell Xenon SDHC conforms to SD Physical Layer Specification
Version 3.01 and is designed according to the guidelines provided
in the SD Host Controller Standard Specification Version 3.00.

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/Kconfig       |   9 +-
 drivers/mmc/host/Makefile      |   3 +-
 drivers/mmc/host/sdhci-xenon.c | 599 ++++++++++++++++++++++++++++++++++-
 drivers/mmc/host/sdhci-xenon.h | 134 ++++++++-
 5 files changed, 746 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mmc/host/sdhci-xenon.c
 create mode 100644 drivers/mmc/host/sdhci-xenon.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 4aa0eac9bfc7..859420e5dfd3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7582,6 +7582,7 @@ 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
+F:	drivers/mmc/host/sdhci-xenon.*
 F:	Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
 
 MATROX FRAMEBUFFER DRIVER
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 5274f503a39a..85a53623526a 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -798,3 +798,12 @@ config MMC_SDHCI_BRCMSTB
 	  Broadcom STB SoCs.
 
 	  If unsure, say Y.
+
+config MMC_SDHCI_XENON
+	tristate "Marvell Xenon eMMC/SD/SDIO SDHCI driver"
+	depends on MMC_SDHCI && MMC_SDHCI_PLTFM
+	help
+	  This selects Marvell Xenon eMMC/SD/SDIO SDHCI.
+	  If you have a machine with integrated Marvell Xenon SDHC IP,
+	  say Y or M here.
+	  If unsure, say N.
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index e2bdaaf43184..75eaf743486c 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -80,3 +80,6 @@ obj-$(CONFIG_MMC_SDHCI_BRCMSTB)		+= sdhci-brcmstb.o
 ifeq ($(CONFIG_CB710_DEBUG),y)
 	CFLAGS-cb710-mmc	+= -DDEBUG
 endif
+
+obj-$(CONFIG_MMC_SDHCI_XENON)	+= sdhci-xenon-driver.o
+sdhci-xenon-driver-y		+= sdhci-xenon.o
diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c
new file mode 100644
index 000000000000..03ba183494d3
--- /dev/null
+++ b/drivers/mmc/host/sdhci-xenon.c
@@ -0,0 +1,599 @@
+/*
+ * Driver for Marvell SOC Platform Group Xenon SDHC as a platform device
+ *
+ * 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.
+ *
+ * Inspired by Jisheng Zhang <jszhang-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>
+ * Special thanks to Video BG4 project team.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+#include <linux/module.h>
+#include <linux/of.h>
+
+#include "sdhci-pltfm.h"
+#include "sdhci.h"
+#include "sdhci-xenon.h"
+
+/* Set SDCLK-off-while-idle */
+static void xenon_set_sdclk_off_idle(struct sdhci_host *host,
+				     unsigned char slot_idx, bool enable)
+{
+	u32 reg;
+	u32 mask;
+
+	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
+	/* Get the bit shift basing on the slot index */
+	mask = (0x1 << (SDCLK_IDLEOFF_ENABLE_SHIFT + slot_idx));
+	if (enable)
+		reg |= mask;
+	else
+		reg &= ~mask;
+
+	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
+}
+
+/* Enable/Disable the Auto Clock Gating function */
+static void xenon_set_acg(struct sdhci_host *host, bool enable)
+{
+	u32 reg;
+
+	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
+	if (enable)
+		reg &= ~AUTO_CLKGATE_DISABLE_MASK;
+	else
+		reg |= AUTO_CLKGATE_DISABLE_MASK;
+	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
+}
+
+/* Enable this slot */
+static void xenon_enable_slot(struct sdhci_host *host,
+			      unsigned char slot_idx)
+{
+	u32 reg;
+
+	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
+	reg |= (BIT(slot_idx) << SLOT_ENABLE_SHIFT);
+	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
+
+	/*
+	 * Manually set the flag which all the slots require,
+	 * including SD, eMMC, SDIO
+	 */
+	host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY;
+}
+
+/* Disable this slot */
+static void xenon_disable_slot(struct sdhci_host *host,
+			       unsigned char slot_idx)
+{
+	u32 reg;
+
+	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
+	reg &= ~(BIT(slot_idx) << SLOT_ENABLE_SHIFT);
+	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
+}
+
+/* Enable Parallel Transfer Mode */
+static void xenon_enable_slot_parallel_tran(struct sdhci_host *host,
+					    unsigned char slot_idx)
+{
+	u32 reg;
+
+	reg = sdhci_readl(host, SDHC_SYS_EXT_OP_CTRL);
+	reg |= BIT(slot_idx);
+	sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
+}
+
+static void xenon_slot_tuning_setup(struct sdhci_host *host)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	u32 reg;
+
+	/* Disable the Re-Tuning Request functionality */
+	reg = sdhci_readl(host, SDHC_SLOT_RETUNING_REQ_CTRL);
+	reg &= ~RETUNING_COMPATIBLE;
+	sdhci_writel(host, reg, SDHC_SLOT_RETUNING_REQ_CTRL);
+
+	/* Disbale the Re-tuning Event Signal Enable */
+	reg = sdhci_readl(host, SDHCI_SIGNAL_ENABLE);
+	reg &= ~SDHCI_INT_RETUNE;
+	sdhci_writel(host, reg, SDHCI_SIGNAL_ENABLE);
+
+	/* Force to use Tuning Mode 1 */
+	host->tuning_mode = SDHCI_TUNING_MODE_1;
+	/* Set re-tuning period */
+	host->tuning_count = 1 << (priv->tuning_count - 1);
+}
+
+/*
+ * Operations inside struct sdhci_ops
+ */
+/* Recover the Register Setting cleared during SOFTWARE_RESET_ALL */
+static void sdhci_xenon_reset_exit(struct sdhci_host *host,
+				   unsigned char slot_idx, u8 mask)
+{
+	/* Only SOFTWARE RESET ALL will clear the register setting */
+	if (!(mask & SDHCI_RESET_ALL))
+		return;
+
+	/* Disable tuning request and auto-retuing again */
+	xenon_slot_tuning_setup(host);
+
+	xenon_set_acg(host, true);
+
+	xenon_set_sdclk_off_idle(host, slot_idx, false);
+}
+
+static void sdhci_xenon_reset(struct sdhci_host *host, u8 mask)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+
+	sdhci_reset(host, mask);
+	sdhci_xenon_reset_exit(host, priv->slot_idx, mask);
+}
+
+/*
+ * Xenon defines different values for HS200 and SDR104
+ * in Host_Control_2
+ */
+static void xenon_set_uhs_signaling(struct sdhci_host *host,
+				    unsigned int timing)
+{
+	u16 ctrl_2;
+
+	ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+	/* Select Bus Speed Mode for host */
+	ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
+	if (timing == MMC_TIMING_MMC_HS200)
+		ctrl_2 |= XENON_SDHCI_CTRL_HS200;
+	else if (timing == MMC_TIMING_UHS_SDR104)
+		ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
+	else if (timing == MMC_TIMING_UHS_SDR12)
+		ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
+	else if (timing == MMC_TIMING_UHS_SDR25)
+		ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
+	else if (timing == MMC_TIMING_UHS_SDR50)
+		ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
+	else if ((timing == MMC_TIMING_UHS_DDR50) ||
+		 (timing == MMC_TIMING_MMC_DDR52))
+		ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
+	else if (timing == MMC_TIMING_MMC_HS400)
+		ctrl_2 |= XENON_SDHCI_CTRL_HS400;
+	sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
+}
+
+static const struct sdhci_ops sdhci_xenon_ops = {
+	.set_clock		= sdhci_set_clock,
+	.set_bus_width		= sdhci_set_bus_width,
+	.reset			= sdhci_xenon_reset,
+	.set_uhs_signaling	= xenon_set_uhs_signaling,
+	.get_max_clock		= sdhci_pltfm_clk_get_max_clock,
+};
+
+static const struct sdhci_pltfm_data sdhci_xenon_pdata = {
+	.ops = &sdhci_xenon_ops,
+	.quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC |
+			SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 |
+			SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER |
+			SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
+};
+
+/*
+ * Xenon Specific Operations in mmc_host_ops
+ */
+static void xenon_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct sdhci_host *host = mmc_priv(mmc);
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	unsigned long flags;
+
+	/*
+	 * HS400/HS200/eMMC HS doesn't have Preset Value register.
+	 * However, sdhci_set_ios will read HS400/HS200 Preset register.
+	 * Disable Preset Value register for HS400/HS200.
+	 * eMMC HS with preset_enabled set will trigger a bug in
+	 * get_preset_value().
+	 */
+	spin_lock_irqsave(&host->lock, flags);
+	if ((ios->timing == MMC_TIMING_MMC_HS400) ||
+	    (ios->timing == MMC_TIMING_MMC_HS200) ||
+	    (ios->timing == MMC_TIMING_MMC_HS)) {
+		host->preset_enabled = false;
+		host->quirks2 |= SDHCI_QUIRK2_PRESET_VALUE_BROKEN;
+	} else {
+		host->quirks2 &= ~SDHCI_QUIRK2_PRESET_VALUE_BROKEN;
+	}
+	spin_unlock_irqrestore(&host->lock, flags);
+
+	sdhci_set_ios(mmc, ios);
+
+	if (host->clock > DEFAULT_SDCLK_FREQ) {
+		spin_lock_irqsave(&host->lock, flags);
+		xenon_set_sdclk_off_idle(host, priv->slot_idx, true);
+		spin_unlock_irqrestore(&host->lock, flags);
+	}
+}
+
+static int __emmc_signal_voltage_switch(struct mmc_host *mmc,
+					const unsigned char signal_voltage)
+{
+	u32 ctrl;
+	unsigned char voltage_code;
+	struct sdhci_host *host = mmc_priv(mmc);
+
+	if (signal_voltage == MMC_SIGNAL_VOLTAGE_330)
+		voltage_code = EMMC_VCCQ_3_3V;
+	else if (signal_voltage == MMC_SIGNAL_VOLTAGE_180)
+		voltage_code = EMMC_VCCQ_1_8V;
+	else
+		return -EINVAL;
+
+	/*
+	 * This host is for eMMC, XENON self-defined
+	 * eMMC slot control register should be accessed
+	 * instead of Host Control 2
+	 */
+	ctrl = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
+	ctrl &= ~EMMC_VCCQ_MASK;
+	ctrl |= voltage_code;
+	sdhci_writel(host, ctrl, SDHC_SLOT_EMMC_CTRL);
+
+	/* There is no standard to determine this waiting period */
+	usleep_range(1000, 2000);
+
+	/* Check whether io voltage switch is done */
+	ctrl = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
+	ctrl &= EMMC_VCCQ_MASK;
+	/*
+	 * This bit is set only when regulator feeds back the voltage switch
+	 * results to Xenon SDHC.
+	 * However, in actaul implementation, regulator might not provide
+	 * this feedback.
+	 * Thus we shall not rely on this bit to determine if switch failed.
+	 * If the bit is not set, just throw a message.
+	 * Besides, error code should not be returned.
+	 */
+	if (ctrl != voltage_code)
+		dev_info(mmc_dev(mmc), "fail to detect eMMC signal voltage stable\n");
+	return 0;
+}
+
+static int xenon_emmc_signal_voltage_switch(struct mmc_host *mmc,
+					    struct mmc_ios *ios)
+{
+	unsigned char voltage = ios->signal_voltage;
+
+	if ((voltage == MMC_SIGNAL_VOLTAGE_330) ||
+	    (voltage == MMC_SIGNAL_VOLTAGE_180))
+		return __emmc_signal_voltage_switch(mmc, voltage);
+
+	dev_err(mmc_dev(mmc), "Unsupported signal voltage: %d\n",
+		voltage);
+	return -EINVAL;
+}
+
+static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
+					     struct mmc_ios *ios)
+{
+	struct sdhci_host *host = mmc_priv(mmc);
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+
+	/*
+	 * Before SD/SDIO set signal voltage, SD bus clock should be
+	 * disabled. However, sdhci_set_clock will also disable the Internal
+	 * clock in mmc_set_signal_voltage().
+	 * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
+	 * Thus here manually enable internal clock.
+	 *
+	 * After switch completes, it is unnecessary to disable internal clock,
+	 * since keeping internal clock active obeys SD spec.
+	 */
+	enable_xenon_internal_clk(host);
+
+	if (priv->card_candidate) {
+		if (mmc_card_mmc(priv->card_candidate))
+			return xenon_emmc_signal_voltage_switch(mmc, ios);
+	}
+
+	return sdhci_start_signal_voltage_switch(mmc, ios);
+}
+
+/*
+ * After determining which slot is used for SDIO,
+ * some additional task is required.
+ */
+static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
+{
+	struct sdhci_host *host = mmc_priv(mmc);
+	u32 reg;
+	u8 slot_idx;
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+
+	/* Link the card for delay adjustment */
+	priv->card_candidate = card;
+	/* Set tuning functionality of this slot */
+	xenon_slot_tuning_setup(host);
+
+	slot_idx = priv->slot_idx;
+	if (!mmc_card_sdio(card)) {
+		/* Re-enable the Auto-CMD12 cap flag. */
+		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
+		host->flags |= SDHCI_AUTO_CMD12;
+
+		/* Clear SDIO Card Inserted indication */
+		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
+		reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
+		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
+
+		if (mmc_card_mmc(card)) {
+			mmc->caps |= MMC_CAP_NONREMOVABLE;
+			if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
+				mmc->caps |= MMC_CAP_1_8V_DDR;
+			/*
+			 * Force to clear BUS_TEST to
+			 * skip bus_test_pre and bus_test_post
+			 */
+			mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
+			mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
+				      MMC_CAP2_PACKED_CMD;
+			if (mmc->caps & MMC_CAP_8_BIT_DATA)
+				mmc->caps2 |= MMC_CAP2_HS400_1_8V;
+		}
+	} else {
+		/*
+		 * Delete the Auto-CMD12 cap flag.
+		 * Otherwise, when sending multi-block CMD53,
+		 * Driver will set Transfer Mode Register to enable Auto CMD12.
+		 * However, SDIO device cannot recognize CMD12.
+		 * Thus SDHC will time-out for waiting for CMD12 response.
+		 */
+		host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
+		host->flags &= ~SDHCI_AUTO_CMD12;
+
+		/*
+		 * Set SDIO Card Inserted indication
+		 * to inform that the current slot is for SDIO
+		 */
+		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
+		reg |= (1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
+		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
+	}
+}
+
+static int xenon_execute_tuning(struct mmc_host *mmc, u32 opcode)
+{
+	struct sdhci_host *host = mmc_priv(mmc);
+
+	if (host->timing == MMC_TIMING_UHS_DDR50)
+		return 0;
+
+	return sdhci_execute_tuning(mmc, opcode);
+}
+
+static void xenon_replace_mmc_host_ops(struct sdhci_host *host)
+{
+	host->mmc_host_ops.set_ios = xenon_set_ios;
+	host->mmc_host_ops.start_signal_voltage_switch =
+			xenon_start_signal_voltage_switch;
+	host->mmc_host_ops.init_card = xenon_init_card;
+	host->mmc_host_ops.execute_tuning = xenon_execute_tuning;
+}
+
+static int xenon_probe_dt(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct sdhci_host *host = platform_get_drvdata(pdev);
+	struct mmc_host *mmc = host->mmc;
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	int err;
+	u32 slot_idx, nr_slot;
+	u32 tuning_count;
+	u32 reg;
+
+	/* Standard MMC property */
+	err = mmc_of_parse(mmc);
+	if (err)
+		return err;
+
+	/* Standard SDHCI property */
+	sdhci_get_of_property(pdev);
+
+	/*
+	 * Xenon Specific property:
+	 * slotno: the index of slot. Refer to SDHC_SYS_CFG_INFO register
+	 * tuning-count: the interval between re-tuning
+	 * PHY type: "sdhc phy", "emmc phy 5.0" or "emmc phy 5.1"
+	 */
+	if (!of_property_read_u32(np, "xenon,slotno", &slot_idx)) {
+		nr_slot = sdhci_readl(host, SDHC_SYS_CFG_INFO);
+		nr_slot &= NR_SUPPORTED_SLOT_MASK;
+		if (unlikely(slot_idx > nr_slot)) {
+			dev_err(mmc_dev(mmc), "Slot Index %d exceeds Number of slots %d\n",
+				slot_idx, nr_slot);
+			return -EINVAL;
+		}
+	} else {
+		priv->slot_idx = 0x0;
+	}
+
+	if (!of_property_read_u32(np, "xenon,tuning-count", &tuning_count)) {
+		if (unlikely(tuning_count >= TMR_RETUN_NO_PRESENT)) {
+			dev_err(mmc_dev(mmc), "Wrong Re-tuning Count. Set default value %d\n",
+				DEF_TUNING_COUNT);
+			tuning_count = DEF_TUNING_COUNT;
+		}
+	} else {
+		priv->tuning_count = DEF_TUNING_COUNT;
+	}
+
+	if (of_property_read_bool(np, "xenon,mask-conflict-err")) {
+		reg = sdhci_readl(host, SDHC_SYS_EXT_OP_CTRL);
+		reg |= MASK_CMD_CONFLICT_ERROR;
+		sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
+	}
+
+	return err;
+}
+
+static int xenon_slot_probe(struct sdhci_host *host)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	u8 slot_idx = priv->slot_idx;
+
+	/* Enable slot */
+	xenon_enable_slot(host, slot_idx);
+
+	/* Enable ACG */
+	xenon_set_acg(host, true);
+
+	/* Enable Parallel Transfer Mode */
+	xenon_enable_slot_parallel_tran(host, slot_idx);
+
+	priv->timing = MMC_TIMING_FAKE;
+	priv->clock = 0;
+
+	return 0;
+}
+
+static void xenon_slot_remove(struct sdhci_host *host)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	u8 slot_idx = priv->slot_idx;
+
+	/* disable slot */
+	xenon_disable_slot(host, slot_idx);
+}
+
+static int sdhci_xenon_probe(struct platform_device *pdev)
+{
+	struct sdhci_pltfm_host *pltfm_host;
+	struct sdhci_host *host;
+	struct clk *clk, *axi_clk;
+	struct sdhci_xenon_priv *priv;
+	int err;
+
+	host = sdhci_pltfm_init(pdev, &sdhci_xenon_pdata,
+				sizeof(struct sdhci_xenon_priv));
+	if (IS_ERR(host))
+		return PTR_ERR(host);
+
+	pltfm_host = sdhci_priv(host);
+	priv = sdhci_pltfm_priv(pltfm_host);
+
+	xenon_set_acg(host, false);
+
+	/*
+	 * Link Xenon specific mmc_host_ops function,
+	 * to replace standard ones in sdhci_ops.
+	 */
+	xenon_replace_mmc_host_ops(host);
+
+	clk = devm_clk_get(&pdev->dev, "core");
+	if (IS_ERR(clk)) {
+		dev_err(&pdev->dev, "Failed to setup input clk.\n");
+		err = PTR_ERR(clk);
+		goto free_pltfm;
+	}
+	clk_prepare_enable(clk);
+	pltfm_host->clk = clk;
+
+	/*
+	 * Some SOCs require additional clock to
+	 * manage AXI bus clock.
+	 * It is optional.
+	 */
+	axi_clk = devm_clk_get(&pdev->dev, "axi");
+	if (!IS_ERR(axi_clk)) {
+		clk_prepare_enable(axi_clk);
+		priv->axi_clk = axi_clk;
+	}
+
+	err = xenon_probe_dt(pdev);
+	if (err)
+		goto err_clk;
+
+	err = xenon_slot_probe(host);
+	if (err)
+		goto err_clk;
+
+	err = sdhci_add_host(host);
+	if (err)
+		goto remove_slot;
+
+	return 0;
+
+remove_slot:
+	xenon_slot_remove(host);
+err_clk:
+	clk_disable_unprepare(pltfm_host->clk);
+	if (!IS_ERR(axi_clk))
+		clk_disable_unprepare(axi_clk);
+free_pltfm:
+	sdhci_pltfm_free(pdev);
+	return err;
+}
+
+static int sdhci_xenon_remove(struct platform_device *pdev)
+{
+	struct sdhci_host *host = platform_get_drvdata(pdev);
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xFFFFFFFF);
+
+	xenon_slot_remove(host);
+
+	sdhci_remove_host(host, dead);
+
+	clk_disable_unprepare(pltfm_host->clk);
+	clk_disable_unprepare(priv->axi_clk);
+
+	sdhci_pltfm_free(pdev);
+
+	return 0;
+}
+
+static const struct of_device_id sdhci_xenon_dt_ids[] = {
+	{ .compatible = "marvell,sdhci-xenon",},
+	{ .compatible = "marvell,armada-3700-sdhci",},
+	{}
+};
+MODULE_DEVICE_TABLE(of, sdhci_xenon_dt_ids);
+
+static struct platform_driver sdhci_xenon_driver = {
+	.driver	= {
+		.name	= "sdhci-xenon",
+		.of_match_table = sdhci_xenon_dt_ids,
+		.pm = &sdhci_pltfm_pmops,
+	},
+	.probe	= sdhci_xenon_probe,
+	.remove	= sdhci_xenon_remove,
+};
+
+module_platform_driver(sdhci_xenon_driver);
+
+MODULE_DESCRIPTION("SDHCI platform driver for Marvell Xenon SDHC");
+MODULE_AUTHOR("Hu Ziji <huziji-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h
new file mode 100644
index 000000000000..c2370493fbe8
--- /dev/null
+++ b/drivers/mmc/host/sdhci-xenon.h
@@ -0,0 +1,134 @@
+/*
+ * 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.
+ */
+#ifndef SDHCI_XENON_H_
+#define SDHCI_XENON_H_
+
+#include <linux/clk.h>
+#include <linux/mmc/card.h>
+#include <linux/of.h>
+#include "sdhci.h"
+
+/* Register Offset of SD Host Controller SOCP self-defined register */
+#define SDHC_SYS_CFG_INFO			0x0104
+#define SLOT_TYPE_SDIO_SHIFT			24
+#define SLOT_TYPE_EMMC_MASK			0xFF
+#define SLOT_TYPE_EMMC_SHIFT			16
+#define SLOT_TYPE_SD_SDIO_MMC_MASK		0xFF
+#define SLOT_TYPE_SD_SDIO_MMC_SHIFT		8
+#define NR_SUPPORTED_SLOT_MASK			0x7
+
+#define SDHC_SYS_OP_CTRL			0x0108
+#define AUTO_CLKGATE_DISABLE_MASK		BIT(20)
+#define SDCLK_IDLEOFF_ENABLE_SHIFT		8
+#define SLOT_ENABLE_SHIFT			0
+
+#define SDHC_SYS_EXT_OP_CTRL			0x010C
+#define MASK_CMD_CONFLICT_ERROR			BIT(8)
+
+#define SDHC_SLOT_OP_STATUS_CTRL		0x0128
+#define DELAY_90_DEGREE_MASK_EMMC5		BIT(7)
+#define DELAY_90_DEGREE_SHIFT_EMMC5		7
+#define EMMC_5_0_PHY_FIXED_DELAY_MASK		0x7F
+#define EMMC_PHY_FIXED_DELAY_MASK		0xFF
+#define EMMC_PHY_FIXED_DELAY_WINDOW_MIN		(EMMC_PHY_FIXED_DELAY_MASK >> 3)
+#define SDH_PHY_FIXED_DELAY_MASK		0x1FF
+#define SDH_PHY_FIXED_DELAY_WINDOW_MIN		(SDH_PHY_FIXED_DELAY_MASK >> 4)
+
+#define TUN_CONSECUTIVE_TIMES_SHIFT		16
+#define TUN_CONSECUTIVE_TIMES_MASK		0x7
+#define TUN_CONSECUTIVE_TIMES			0x4
+#define TUNING_STEP_SHIFT			12
+#define TUNING_STEP_MASK			0xF
+#define TUNING_STEP_DIVIDER			BIT(6)
+
+#define FORCE_SEL_INVERSE_CLK_SHIFT		11
+
+#define SDHC_SLOT_EMMC_CTRL			0x0130
+#define ENABLE_DATA_STROBE			BIT(24)
+#define SET_EMMC_RSTN				BIT(16)
+#define DISABLE_RD_DATA_CRC			BIT(14)
+#define DISABLE_CRC_STAT_TOKEN			BIT(13)
+#define EMMC_VCCQ_MASK				0x3
+#define EMMC_VCCQ_1_8V				0x1
+#define EMMC_VCCQ_3_3V				0x3
+
+#define SDHC_SLOT_RETUNING_REQ_CTRL		0x0144
+/* retuning compatible */
+#define RETUNING_COMPATIBLE			0x1
+
+#define SDHC_SLOT_EXT_PRESENT_STATE		0x014C
+#define LOCK_STATE				0x1
+
+#define SDHC_SLOT_DLL_CUR_DLY_VAL		0x0150
+
+/* Tuning Parameter */
+#define TMR_RETUN_NO_PRESENT			0xF
+#define DEF_TUNING_COUNT			0x9
+
+#define MMC_TIMING_FAKE				0xFF
+
+#define DEFAULT_SDCLK_FREQ			(400000)
+
+/* Xenon specific Mode Select value */
+#define XENON_SDHCI_CTRL_HS200			0x5
+#define XENON_SDHCI_CTRL_HS400			0x6
+
+struct sdhci_xenon_priv {
+	/*
+	 * The bus_width, timing, and clock fields in below
+	 * record the current setting of Xenon SDHC.
+	 * Driver will call a Sampling Fixed Delay Adjustment
+	 * if any setting is changed.
+	 */
+	unsigned char	bus_width;
+	unsigned char	timing;
+	unsigned char	tuning_count;
+	unsigned int	clock;
+	struct clk	*axi_clk;
+
+	/* Slot idx */
+	u8		slot_idx;
+
+	/*
+	 * When initializing card, Xenon has to determine card type and
+	 * adjust Sampling Fixed delay.
+	 * However, at that time, card structure is not linked to mmc_host.
+	 * Thus a card pointer is added here to provide
+	 * the delay adjustment function with the card structure
+	 * of the card during initialization
+	 */
+	struct mmc_card *card_candidate;
+};
+
+static inline int enable_xenon_internal_clk(struct sdhci_host *host)
+{
+	u32 reg;
+	u8 timeout;
+
+	reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
+	reg |= SDHCI_CLOCK_INT_EN;
+	sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
+	/* Wait max 20 ms */
+	timeout = 20;
+	while (!((reg = sdhci_readw(host, SDHCI_CLOCK_CONTROL))
+			& SDHCI_CLOCK_INT_STABLE)) {
+		if (timeout == 0) {
+			pr_err("%s: Internal clock never stabilised.\n",
+			       mmc_hostname(host->mmc));
+			return -ETIMEDOUT;
+		}
+		timeout--;
+		mdelay(1);
+	}
+
+	return 0;
+}
+#endif
-- 
git-series 0.8.10
--
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 related	[flat|nested] 105+ messages in thread

* [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: linux-arm-kernel

From: Ziji Hu <huziji@marvell.com>

Add Xenon eMMC/SD/SDIO host controller core functionality.
Add Xenon specific intialization process.
Add Xenon specific mmc_host_ops APIs.
Add Xenon specific register definitions.

Add CONFIG_MMC_SDHCI_XENON support in drivers/mmc/host/Kconfig.

Marvell Xenon SDHC conforms to SD Physical Layer Specification
Version 3.01 and is designed according to the guidelines provided
in the SD Host Controller Standard Specification Version 3.00.

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/Kconfig       |   9 +-
 drivers/mmc/host/Makefile      |   3 +-
 drivers/mmc/host/sdhci-xenon.c | 599 ++++++++++++++++++++++++++++++++++-
 drivers/mmc/host/sdhci-xenon.h | 134 ++++++++-
 5 files changed, 746 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mmc/host/sdhci-xenon.c
 create mode 100644 drivers/mmc/host/sdhci-xenon.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 4aa0eac9bfc7..859420e5dfd3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7582,6 +7582,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
 M:	Ziji Hu <huziji@marvell.com>
 L:	linux-mmc at vger.kernel.org
 S:	Supported
+F:	drivers/mmc/host/sdhci-xenon.*
 F:	Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
 
 MATROX FRAMEBUFFER DRIVER
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 5274f503a39a..85a53623526a 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -798,3 +798,12 @@ config MMC_SDHCI_BRCMSTB
 	  Broadcom STB SoCs.
 
 	  If unsure, say Y.
+
+config MMC_SDHCI_XENON
+	tristate "Marvell Xenon eMMC/SD/SDIO SDHCI driver"
+	depends on MMC_SDHCI && MMC_SDHCI_PLTFM
+	help
+	  This selects Marvell Xenon eMMC/SD/SDIO SDHCI.
+	  If you have a machine with integrated Marvell Xenon SDHC IP,
+	  say Y or M here.
+	  If unsure, say N.
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index e2bdaaf43184..75eaf743486c 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -80,3 +80,6 @@ obj-$(CONFIG_MMC_SDHCI_BRCMSTB)		+= sdhci-brcmstb.o
 ifeq ($(CONFIG_CB710_DEBUG),y)
 	CFLAGS-cb710-mmc	+= -DDEBUG
 endif
+
+obj-$(CONFIG_MMC_SDHCI_XENON)	+= sdhci-xenon-driver.o
+sdhci-xenon-driver-y		+= sdhci-xenon.o
diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c
new file mode 100644
index 000000000000..03ba183494d3
--- /dev/null
+++ b/drivers/mmc/host/sdhci-xenon.c
@@ -0,0 +1,599 @@
+/*
+ * Driver for Marvell SOC Platform Group Xenon SDHC as a platform device
+ *
+ * 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.
+ *
+ * Inspired by Jisheng Zhang <jszhang@marvell.com>
+ * Special thanks to Video BG4 project team.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+#include <linux/module.h>
+#include <linux/of.h>
+
+#include "sdhci-pltfm.h"
+#include "sdhci.h"
+#include "sdhci-xenon.h"
+
+/* Set SDCLK-off-while-idle */
+static void xenon_set_sdclk_off_idle(struct sdhci_host *host,
+				     unsigned char slot_idx, bool enable)
+{
+	u32 reg;
+	u32 mask;
+
+	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
+	/* Get the bit shift basing on the slot index */
+	mask = (0x1 << (SDCLK_IDLEOFF_ENABLE_SHIFT + slot_idx));
+	if (enable)
+		reg |= mask;
+	else
+		reg &= ~mask;
+
+	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
+}
+
+/* Enable/Disable the Auto Clock Gating function */
+static void xenon_set_acg(struct sdhci_host *host, bool enable)
+{
+	u32 reg;
+
+	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
+	if (enable)
+		reg &= ~AUTO_CLKGATE_DISABLE_MASK;
+	else
+		reg |= AUTO_CLKGATE_DISABLE_MASK;
+	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
+}
+
+/* Enable this slot */
+static void xenon_enable_slot(struct sdhci_host *host,
+			      unsigned char slot_idx)
+{
+	u32 reg;
+
+	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
+	reg |= (BIT(slot_idx) << SLOT_ENABLE_SHIFT);
+	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
+
+	/*
+	 * Manually set the flag which all the slots require,
+	 * including SD, eMMC, SDIO
+	 */
+	host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY;
+}
+
+/* Disable this slot */
+static void xenon_disable_slot(struct sdhci_host *host,
+			       unsigned char slot_idx)
+{
+	u32 reg;
+
+	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
+	reg &= ~(BIT(slot_idx) << SLOT_ENABLE_SHIFT);
+	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
+}
+
+/* Enable Parallel Transfer Mode */
+static void xenon_enable_slot_parallel_tran(struct sdhci_host *host,
+					    unsigned char slot_idx)
+{
+	u32 reg;
+
+	reg = sdhci_readl(host, SDHC_SYS_EXT_OP_CTRL);
+	reg |= BIT(slot_idx);
+	sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
+}
+
+static void xenon_slot_tuning_setup(struct sdhci_host *host)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	u32 reg;
+
+	/* Disable the Re-Tuning Request functionality */
+	reg = sdhci_readl(host, SDHC_SLOT_RETUNING_REQ_CTRL);
+	reg &= ~RETUNING_COMPATIBLE;
+	sdhci_writel(host, reg, SDHC_SLOT_RETUNING_REQ_CTRL);
+
+	/* Disbale the Re-tuning Event Signal Enable */
+	reg = sdhci_readl(host, SDHCI_SIGNAL_ENABLE);
+	reg &= ~SDHCI_INT_RETUNE;
+	sdhci_writel(host, reg, SDHCI_SIGNAL_ENABLE);
+
+	/* Force to use Tuning Mode 1 */
+	host->tuning_mode = SDHCI_TUNING_MODE_1;
+	/* Set re-tuning period */
+	host->tuning_count = 1 << (priv->tuning_count - 1);
+}
+
+/*
+ * Operations inside struct sdhci_ops
+ */
+/* Recover the Register Setting cleared during SOFTWARE_RESET_ALL */
+static void sdhci_xenon_reset_exit(struct sdhci_host *host,
+				   unsigned char slot_idx, u8 mask)
+{
+	/* Only SOFTWARE RESET ALL will clear the register setting */
+	if (!(mask & SDHCI_RESET_ALL))
+		return;
+
+	/* Disable tuning request and auto-retuing again */
+	xenon_slot_tuning_setup(host);
+
+	xenon_set_acg(host, true);
+
+	xenon_set_sdclk_off_idle(host, slot_idx, false);
+}
+
+static void sdhci_xenon_reset(struct sdhci_host *host, u8 mask)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+
+	sdhci_reset(host, mask);
+	sdhci_xenon_reset_exit(host, priv->slot_idx, mask);
+}
+
+/*
+ * Xenon defines different values for HS200 and SDR104
+ * in Host_Control_2
+ */
+static void xenon_set_uhs_signaling(struct sdhci_host *host,
+				    unsigned int timing)
+{
+	u16 ctrl_2;
+
+	ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+	/* Select Bus Speed Mode for host */
+	ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
+	if (timing == MMC_TIMING_MMC_HS200)
+		ctrl_2 |= XENON_SDHCI_CTRL_HS200;
+	else if (timing == MMC_TIMING_UHS_SDR104)
+		ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
+	else if (timing == MMC_TIMING_UHS_SDR12)
+		ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
+	else if (timing == MMC_TIMING_UHS_SDR25)
+		ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
+	else if (timing == MMC_TIMING_UHS_SDR50)
+		ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
+	else if ((timing == MMC_TIMING_UHS_DDR50) ||
+		 (timing == MMC_TIMING_MMC_DDR52))
+		ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
+	else if (timing == MMC_TIMING_MMC_HS400)
+		ctrl_2 |= XENON_SDHCI_CTRL_HS400;
+	sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
+}
+
+static const struct sdhci_ops sdhci_xenon_ops = {
+	.set_clock		= sdhci_set_clock,
+	.set_bus_width		= sdhci_set_bus_width,
+	.reset			= sdhci_xenon_reset,
+	.set_uhs_signaling	= xenon_set_uhs_signaling,
+	.get_max_clock		= sdhci_pltfm_clk_get_max_clock,
+};
+
+static const struct sdhci_pltfm_data sdhci_xenon_pdata = {
+	.ops = &sdhci_xenon_ops,
+	.quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC |
+			SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 |
+			SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER |
+			SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
+};
+
+/*
+ * Xenon Specific Operations in mmc_host_ops
+ */
+static void xenon_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct sdhci_host *host = mmc_priv(mmc);
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	unsigned long flags;
+
+	/*
+	 * HS400/HS200/eMMC HS doesn't have Preset Value register.
+	 * However, sdhci_set_ios will read HS400/HS200 Preset register.
+	 * Disable Preset Value register for HS400/HS200.
+	 * eMMC HS with preset_enabled set will trigger a bug in
+	 * get_preset_value().
+	 */
+	spin_lock_irqsave(&host->lock, flags);
+	if ((ios->timing == MMC_TIMING_MMC_HS400) ||
+	    (ios->timing == MMC_TIMING_MMC_HS200) ||
+	    (ios->timing == MMC_TIMING_MMC_HS)) {
+		host->preset_enabled = false;
+		host->quirks2 |= SDHCI_QUIRK2_PRESET_VALUE_BROKEN;
+	} else {
+		host->quirks2 &= ~SDHCI_QUIRK2_PRESET_VALUE_BROKEN;
+	}
+	spin_unlock_irqrestore(&host->lock, flags);
+
+	sdhci_set_ios(mmc, ios);
+
+	if (host->clock > DEFAULT_SDCLK_FREQ) {
+		spin_lock_irqsave(&host->lock, flags);
+		xenon_set_sdclk_off_idle(host, priv->slot_idx, true);
+		spin_unlock_irqrestore(&host->lock, flags);
+	}
+}
+
+static int __emmc_signal_voltage_switch(struct mmc_host *mmc,
+					const unsigned char signal_voltage)
+{
+	u32 ctrl;
+	unsigned char voltage_code;
+	struct sdhci_host *host = mmc_priv(mmc);
+
+	if (signal_voltage == MMC_SIGNAL_VOLTAGE_330)
+		voltage_code = EMMC_VCCQ_3_3V;
+	else if (signal_voltage == MMC_SIGNAL_VOLTAGE_180)
+		voltage_code = EMMC_VCCQ_1_8V;
+	else
+		return -EINVAL;
+
+	/*
+	 * This host is for eMMC, XENON self-defined
+	 * eMMC slot control register should be accessed
+	 * instead of Host Control 2
+	 */
+	ctrl = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
+	ctrl &= ~EMMC_VCCQ_MASK;
+	ctrl |= voltage_code;
+	sdhci_writel(host, ctrl, SDHC_SLOT_EMMC_CTRL);
+
+	/* There is no standard to determine this waiting period */
+	usleep_range(1000, 2000);
+
+	/* Check whether io voltage switch is done */
+	ctrl = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
+	ctrl &= EMMC_VCCQ_MASK;
+	/*
+	 * This bit is set only when regulator feeds back the voltage switch
+	 * results to Xenon SDHC.
+	 * However, in actaul implementation, regulator might not provide
+	 * this feedback.
+	 * Thus we shall not rely on this bit to determine if switch failed.
+	 * If the bit is not set, just throw a message.
+	 * Besides, error code should not be returned.
+	 */
+	if (ctrl != voltage_code)
+		dev_info(mmc_dev(mmc), "fail to detect eMMC signal voltage stable\n");
+	return 0;
+}
+
+static int xenon_emmc_signal_voltage_switch(struct mmc_host *mmc,
+					    struct mmc_ios *ios)
+{
+	unsigned char voltage = ios->signal_voltage;
+
+	if ((voltage == MMC_SIGNAL_VOLTAGE_330) ||
+	    (voltage == MMC_SIGNAL_VOLTAGE_180))
+		return __emmc_signal_voltage_switch(mmc, voltage);
+
+	dev_err(mmc_dev(mmc), "Unsupported signal voltage: %d\n",
+		voltage);
+	return -EINVAL;
+}
+
+static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
+					     struct mmc_ios *ios)
+{
+	struct sdhci_host *host = mmc_priv(mmc);
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+
+	/*
+	 * Before SD/SDIO set signal voltage, SD bus clock should be
+	 * disabled. However, sdhci_set_clock will also disable the Internal
+	 * clock in mmc_set_signal_voltage().
+	 * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
+	 * Thus here manually enable internal clock.
+	 *
+	 * After switch completes, it is unnecessary to disable internal clock,
+	 * since keeping internal clock active obeys SD spec.
+	 */
+	enable_xenon_internal_clk(host);
+
+	if (priv->card_candidate) {
+		if (mmc_card_mmc(priv->card_candidate))
+			return xenon_emmc_signal_voltage_switch(mmc, ios);
+	}
+
+	return sdhci_start_signal_voltage_switch(mmc, ios);
+}
+
+/*
+ * After determining which slot is used for SDIO,
+ * some additional task is required.
+ */
+static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
+{
+	struct sdhci_host *host = mmc_priv(mmc);
+	u32 reg;
+	u8 slot_idx;
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+
+	/* Link the card for delay adjustment */
+	priv->card_candidate = card;
+	/* Set tuning functionality of this slot */
+	xenon_slot_tuning_setup(host);
+
+	slot_idx = priv->slot_idx;
+	if (!mmc_card_sdio(card)) {
+		/* Re-enable the Auto-CMD12 cap flag. */
+		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
+		host->flags |= SDHCI_AUTO_CMD12;
+
+		/* Clear SDIO Card Inserted indication */
+		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
+		reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
+		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
+
+		if (mmc_card_mmc(card)) {
+			mmc->caps |= MMC_CAP_NONREMOVABLE;
+			if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
+				mmc->caps |= MMC_CAP_1_8V_DDR;
+			/*
+			 * Force to clear BUS_TEST to
+			 * skip bus_test_pre and bus_test_post
+			 */
+			mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
+			mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
+				      MMC_CAP2_PACKED_CMD;
+			if (mmc->caps & MMC_CAP_8_BIT_DATA)
+				mmc->caps2 |= MMC_CAP2_HS400_1_8V;
+		}
+	} else {
+		/*
+		 * Delete the Auto-CMD12 cap flag.
+		 * Otherwise, when sending multi-block CMD53,
+		 * Driver will set Transfer Mode Register to enable Auto CMD12.
+		 * However, SDIO device cannot recognize CMD12.
+		 * Thus SDHC will time-out for waiting for CMD12 response.
+		 */
+		host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
+		host->flags &= ~SDHCI_AUTO_CMD12;
+
+		/*
+		 * Set SDIO Card Inserted indication
+		 * to inform that the current slot is for SDIO
+		 */
+		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
+		reg |= (1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
+		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
+	}
+}
+
+static int xenon_execute_tuning(struct mmc_host *mmc, u32 opcode)
+{
+	struct sdhci_host *host = mmc_priv(mmc);
+
+	if (host->timing == MMC_TIMING_UHS_DDR50)
+		return 0;
+
+	return sdhci_execute_tuning(mmc, opcode);
+}
+
+static void xenon_replace_mmc_host_ops(struct sdhci_host *host)
+{
+	host->mmc_host_ops.set_ios = xenon_set_ios;
+	host->mmc_host_ops.start_signal_voltage_switch =
+			xenon_start_signal_voltage_switch;
+	host->mmc_host_ops.init_card = xenon_init_card;
+	host->mmc_host_ops.execute_tuning = xenon_execute_tuning;
+}
+
+static int xenon_probe_dt(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct sdhci_host *host = platform_get_drvdata(pdev);
+	struct mmc_host *mmc = host->mmc;
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	int err;
+	u32 slot_idx, nr_slot;
+	u32 tuning_count;
+	u32 reg;
+
+	/* Standard MMC property */
+	err = mmc_of_parse(mmc);
+	if (err)
+		return err;
+
+	/* Standard SDHCI property */
+	sdhci_get_of_property(pdev);
+
+	/*
+	 * Xenon Specific property:
+	 * slotno: the index of slot. Refer to SDHC_SYS_CFG_INFO register
+	 * tuning-count: the interval between re-tuning
+	 * PHY type: "sdhc phy", "emmc phy 5.0" or "emmc phy 5.1"
+	 */
+	if (!of_property_read_u32(np, "xenon,slotno", &slot_idx)) {
+		nr_slot = sdhci_readl(host, SDHC_SYS_CFG_INFO);
+		nr_slot &= NR_SUPPORTED_SLOT_MASK;
+		if (unlikely(slot_idx > nr_slot)) {
+			dev_err(mmc_dev(mmc), "Slot Index %d exceeds Number of slots %d\n",
+				slot_idx, nr_slot);
+			return -EINVAL;
+		}
+	} else {
+		priv->slot_idx = 0x0;
+	}
+
+	if (!of_property_read_u32(np, "xenon,tuning-count", &tuning_count)) {
+		if (unlikely(tuning_count >= TMR_RETUN_NO_PRESENT)) {
+			dev_err(mmc_dev(mmc), "Wrong Re-tuning Count. Set default value %d\n",
+				DEF_TUNING_COUNT);
+			tuning_count = DEF_TUNING_COUNT;
+		}
+	} else {
+		priv->tuning_count = DEF_TUNING_COUNT;
+	}
+
+	if (of_property_read_bool(np, "xenon,mask-conflict-err")) {
+		reg = sdhci_readl(host, SDHC_SYS_EXT_OP_CTRL);
+		reg |= MASK_CMD_CONFLICT_ERROR;
+		sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
+	}
+
+	return err;
+}
+
+static int xenon_slot_probe(struct sdhci_host *host)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	u8 slot_idx = priv->slot_idx;
+
+	/* Enable slot */
+	xenon_enable_slot(host, slot_idx);
+
+	/* Enable ACG */
+	xenon_set_acg(host, true);
+
+	/* Enable Parallel Transfer Mode */
+	xenon_enable_slot_parallel_tran(host, slot_idx);
+
+	priv->timing = MMC_TIMING_FAKE;
+	priv->clock = 0;
+
+	return 0;
+}
+
+static void xenon_slot_remove(struct sdhci_host *host)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	u8 slot_idx = priv->slot_idx;
+
+	/* disable slot */
+	xenon_disable_slot(host, slot_idx);
+}
+
+static int sdhci_xenon_probe(struct platform_device *pdev)
+{
+	struct sdhci_pltfm_host *pltfm_host;
+	struct sdhci_host *host;
+	struct clk *clk, *axi_clk;
+	struct sdhci_xenon_priv *priv;
+	int err;
+
+	host = sdhci_pltfm_init(pdev, &sdhci_xenon_pdata,
+				sizeof(struct sdhci_xenon_priv));
+	if (IS_ERR(host))
+		return PTR_ERR(host);
+
+	pltfm_host = sdhci_priv(host);
+	priv = sdhci_pltfm_priv(pltfm_host);
+
+	xenon_set_acg(host, false);
+
+	/*
+	 * Link Xenon specific mmc_host_ops function,
+	 * to replace standard ones in sdhci_ops.
+	 */
+	xenon_replace_mmc_host_ops(host);
+
+	clk = devm_clk_get(&pdev->dev, "core");
+	if (IS_ERR(clk)) {
+		dev_err(&pdev->dev, "Failed to setup input clk.\n");
+		err = PTR_ERR(clk);
+		goto free_pltfm;
+	}
+	clk_prepare_enable(clk);
+	pltfm_host->clk = clk;
+
+	/*
+	 * Some SOCs require additional clock to
+	 * manage AXI bus clock.
+	 * It is optional.
+	 */
+	axi_clk = devm_clk_get(&pdev->dev, "axi");
+	if (!IS_ERR(axi_clk)) {
+		clk_prepare_enable(axi_clk);
+		priv->axi_clk = axi_clk;
+	}
+
+	err = xenon_probe_dt(pdev);
+	if (err)
+		goto err_clk;
+
+	err = xenon_slot_probe(host);
+	if (err)
+		goto err_clk;
+
+	err = sdhci_add_host(host);
+	if (err)
+		goto remove_slot;
+
+	return 0;
+
+remove_slot:
+	xenon_slot_remove(host);
+err_clk:
+	clk_disable_unprepare(pltfm_host->clk);
+	if (!IS_ERR(axi_clk))
+		clk_disable_unprepare(axi_clk);
+free_pltfm:
+	sdhci_pltfm_free(pdev);
+	return err;
+}
+
+static int sdhci_xenon_remove(struct platform_device *pdev)
+{
+	struct sdhci_host *host = platform_get_drvdata(pdev);
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xFFFFFFFF);
+
+	xenon_slot_remove(host);
+
+	sdhci_remove_host(host, dead);
+
+	clk_disable_unprepare(pltfm_host->clk);
+	clk_disable_unprepare(priv->axi_clk);
+
+	sdhci_pltfm_free(pdev);
+
+	return 0;
+}
+
+static const struct of_device_id sdhci_xenon_dt_ids[] = {
+	{ .compatible = "marvell,sdhci-xenon",},
+	{ .compatible = "marvell,armada-3700-sdhci",},
+	{}
+};
+MODULE_DEVICE_TABLE(of, sdhci_xenon_dt_ids);
+
+static struct platform_driver sdhci_xenon_driver = {
+	.driver	= {
+		.name	= "sdhci-xenon",
+		.of_match_table = sdhci_xenon_dt_ids,
+		.pm = &sdhci_pltfm_pmops,
+	},
+	.probe	= sdhci_xenon_probe,
+	.remove	= sdhci_xenon_remove,
+};
+
+module_platform_driver(sdhci_xenon_driver);
+
+MODULE_DESCRIPTION("SDHCI platform driver for Marvell Xenon SDHC");
+MODULE_AUTHOR("Hu Ziji <huziji@marvell.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h
new file mode 100644
index 000000000000..c2370493fbe8
--- /dev/null
+++ b/drivers/mmc/host/sdhci-xenon.h
@@ -0,0 +1,134 @@
+/*
+ * 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.
+ */
+#ifndef SDHCI_XENON_H_
+#define SDHCI_XENON_H_
+
+#include <linux/clk.h>
+#include <linux/mmc/card.h>
+#include <linux/of.h>
+#include "sdhci.h"
+
+/* Register Offset of SD Host Controller SOCP self-defined register */
+#define SDHC_SYS_CFG_INFO			0x0104
+#define SLOT_TYPE_SDIO_SHIFT			24
+#define SLOT_TYPE_EMMC_MASK			0xFF
+#define SLOT_TYPE_EMMC_SHIFT			16
+#define SLOT_TYPE_SD_SDIO_MMC_MASK		0xFF
+#define SLOT_TYPE_SD_SDIO_MMC_SHIFT		8
+#define NR_SUPPORTED_SLOT_MASK			0x7
+
+#define SDHC_SYS_OP_CTRL			0x0108
+#define AUTO_CLKGATE_DISABLE_MASK		BIT(20)
+#define SDCLK_IDLEOFF_ENABLE_SHIFT		8
+#define SLOT_ENABLE_SHIFT			0
+
+#define SDHC_SYS_EXT_OP_CTRL			0x010C
+#define MASK_CMD_CONFLICT_ERROR			BIT(8)
+
+#define SDHC_SLOT_OP_STATUS_CTRL		0x0128
+#define DELAY_90_DEGREE_MASK_EMMC5		BIT(7)
+#define DELAY_90_DEGREE_SHIFT_EMMC5		7
+#define EMMC_5_0_PHY_FIXED_DELAY_MASK		0x7F
+#define EMMC_PHY_FIXED_DELAY_MASK		0xFF
+#define EMMC_PHY_FIXED_DELAY_WINDOW_MIN		(EMMC_PHY_FIXED_DELAY_MASK >> 3)
+#define SDH_PHY_FIXED_DELAY_MASK		0x1FF
+#define SDH_PHY_FIXED_DELAY_WINDOW_MIN		(SDH_PHY_FIXED_DELAY_MASK >> 4)
+
+#define TUN_CONSECUTIVE_TIMES_SHIFT		16
+#define TUN_CONSECUTIVE_TIMES_MASK		0x7
+#define TUN_CONSECUTIVE_TIMES			0x4
+#define TUNING_STEP_SHIFT			12
+#define TUNING_STEP_MASK			0xF
+#define TUNING_STEP_DIVIDER			BIT(6)
+
+#define FORCE_SEL_INVERSE_CLK_SHIFT		11
+
+#define SDHC_SLOT_EMMC_CTRL			0x0130
+#define ENABLE_DATA_STROBE			BIT(24)
+#define SET_EMMC_RSTN				BIT(16)
+#define DISABLE_RD_DATA_CRC			BIT(14)
+#define DISABLE_CRC_STAT_TOKEN			BIT(13)
+#define EMMC_VCCQ_MASK				0x3
+#define EMMC_VCCQ_1_8V				0x1
+#define EMMC_VCCQ_3_3V				0x3
+
+#define SDHC_SLOT_RETUNING_REQ_CTRL		0x0144
+/* retuning compatible */
+#define RETUNING_COMPATIBLE			0x1
+
+#define SDHC_SLOT_EXT_PRESENT_STATE		0x014C
+#define LOCK_STATE				0x1
+
+#define SDHC_SLOT_DLL_CUR_DLY_VAL		0x0150
+
+/* Tuning Parameter */
+#define TMR_RETUN_NO_PRESENT			0xF
+#define DEF_TUNING_COUNT			0x9
+
+#define MMC_TIMING_FAKE				0xFF
+
+#define DEFAULT_SDCLK_FREQ			(400000)
+
+/* Xenon specific Mode Select value */
+#define XENON_SDHCI_CTRL_HS200			0x5
+#define XENON_SDHCI_CTRL_HS400			0x6
+
+struct sdhci_xenon_priv {
+	/*
+	 * The bus_width, timing, and clock fields in below
+	 * record the current setting of Xenon SDHC.
+	 * Driver will call a Sampling Fixed Delay Adjustment
+	 * if any setting is changed.
+	 */
+	unsigned char	bus_width;
+	unsigned char	timing;
+	unsigned char	tuning_count;
+	unsigned int	clock;
+	struct clk	*axi_clk;
+
+	/* Slot idx */
+	u8		slot_idx;
+
+	/*
+	 * When initializing card, Xenon has to determine card type and
+	 * adjust Sampling Fixed delay.
+	 * However, at that time, card structure is not linked to mmc_host.
+	 * Thus a card pointer is added here to provide
+	 * the delay adjustment function with the card structure
+	 * of the card during initialization
+	 */
+	struct mmc_card *card_candidate;
+};
+
+static inline int enable_xenon_internal_clk(struct sdhci_host *host)
+{
+	u32 reg;
+	u8 timeout;
+
+	reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
+	reg |= SDHCI_CLOCK_INT_EN;
+	sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
+	/* Wait max 20 ms */
+	timeout = 20;
+	while (!((reg = sdhci_readw(host, SDHCI_CLOCK_CONTROL))
+			& SDHCI_CLOCK_INT_STABLE)) {
+		if (timeout == 0) {
+			pr_err("%s: Internal clock never stabilised.\n",
+			       mmc_hostname(host->mmc));
+			return -ETIMEDOUT;
+		}
+		timeout--;
+		mdelay(1);
+	}
+
+	return 0;
+}
+#endif
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
  2016-10-07 15:22 ` Gregory CLEMENT
  (?)
@ 2016-10-07 15:22   ` Gregory CLEMENT
  -1 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, Rob Herring, devicetree, Thomas Petazzoni,
	linux-arm-kernel, 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, Peng Zhu, Yu Cao, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

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.*
 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
@@ -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
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, Rob Herring, devicetree, Thomas Petazzoni,
	linux-arm-kernel, 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

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.*
 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
@@ -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
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: linux-arm-kernel

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 at vger.kernel.org
 S:	Supported
 F:	drivers/mmc/host/sdhci-xenon.*
+F:	drivers/mmc/host/sdhci-xenon-phy.*
 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
@@ -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
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 8/10] arm64: dts: marvell: add eMMC support for Armada 37xx
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, Rob Herring, devicetree, Thomas Petazzoni,
	linux-arm-kernel, 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, Peng Zhu, Yu Cao, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

Add the eMMC support for Armada 37xx SoC and enable it in the Armada 3720
DB board.

Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
 arch/arm64/boot/dts/marvell/armada-3720-db.dts |  7 +++++++
 arch/arm64/boot/dts/marvell/armada-37xx.dtsi   | 11 +++++++++++
 2 files changed, 18 insertions(+), 0 deletions(-)

diff --git a/arch/arm64/boot/dts/marvell/armada-3720-db.dts b/arch/arm64/boot/dts/marvell/armada-3720-db.dts
index 1372e9a6aaa4..5b9cff4d5d7a 100644
--- a/arch/arm64/boot/dts/marvell/armada-3720-db.dts
+++ b/arch/arm64/boot/dts/marvell/armada-3720-db.dts
@@ -72,6 +72,13 @@
 	status = "okay";
 };
 
+&sdhci0 {
+	non-removable;
+	bus-width = <8>;
+	xenon,pad-type = "fixed-1-8v";
+	status = "okay";
+};
+
 /* CON31 */
 &usb3 {
 	status = "okay";
diff --git a/arch/arm64/boot/dts/marvell/armada-37xx.dtsi b/arch/arm64/boot/dts/marvell/armada-37xx.dtsi
index c4762538ec01..0c4cafe92e66 100644
--- a/arch/arm64/boot/dts/marvell/armada-37xx.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-37xx.dtsi
@@ -161,6 +161,17 @@
 				};
 			};
 
+			sdhci0: sdhci@d8000 {
+				compatible = "marvell,armada-3700-sdhci",
+				"marvell,sdhci-xenon";
+				reg = <0xd8000 0x300
+				       0x17808 0x4>;
+				interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
+				clocks = <&nb_perih_clk 0>;
+				clock-names = "core";
+				status = "disabled";
+			};
+
 			sata: sata@e0000 {
 				compatible = "marvell,armada-3700-ahci";
 				reg = <0xe0000 0x2000>;
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 8/10] arm64: dts: marvell: add eMMC support for Armada 37xx
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc-u79uwXL29TY76Z2rM5mHXA
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, 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

Add the eMMC support for Armada 37xx SoC and enable it in the Armada 3720
DB board.

Signed-off-by: Gregory CLEMENT <gregory.clement-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 arch/arm64/boot/dts/marvell/armada-3720-db.dts |  7 +++++++
 arch/arm64/boot/dts/marvell/armada-37xx.dtsi   | 11 +++++++++++
 2 files changed, 18 insertions(+), 0 deletions(-)

diff --git a/arch/arm64/boot/dts/marvell/armada-3720-db.dts b/arch/arm64/boot/dts/marvell/armada-3720-db.dts
index 1372e9a6aaa4..5b9cff4d5d7a 100644
--- a/arch/arm64/boot/dts/marvell/armada-3720-db.dts
+++ b/arch/arm64/boot/dts/marvell/armada-3720-db.dts
@@ -72,6 +72,13 @@
 	status = "okay";
 };
 
+&sdhci0 {
+	non-removable;
+	bus-width = <8>;
+	xenon,pad-type = "fixed-1-8v";
+	status = "okay";
+};
+
 /* CON31 */
 &usb3 {
 	status = "okay";
diff --git a/arch/arm64/boot/dts/marvell/armada-37xx.dtsi b/arch/arm64/boot/dts/marvell/armada-37xx.dtsi
index c4762538ec01..0c4cafe92e66 100644
--- a/arch/arm64/boot/dts/marvell/armada-37xx.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-37xx.dtsi
@@ -161,6 +161,17 @@
 				};
 			};
 
+			sdhci0: sdhci@d8000 {
+				compatible = "marvell,armada-3700-sdhci",
+				"marvell,sdhci-xenon";
+				reg = <0xd8000 0x300
+				       0x17808 0x4>;
+				interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
+				clocks = <&nb_perih_clk 0>;
+				clock-names = "core";
+				status = "disabled";
+			};
+
 			sata: sata@e0000 {
 				compatible = "marvell,armada-3700-ahci";
 				reg = <0xe0000 0x2000>;
-- 
git-series 0.8.10
--
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 related	[flat|nested] 105+ messages in thread

* [PATCH 8/10] arm64: dts: marvell: add eMMC support for Armada 37xx
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: linux-arm-kernel

Add the eMMC support for Armada 37xx SoC and enable it in the Armada 3720
DB board.

Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
 arch/arm64/boot/dts/marvell/armada-3720-db.dts |  7 +++++++
 arch/arm64/boot/dts/marvell/armada-37xx.dtsi   | 11 +++++++++++
 2 files changed, 18 insertions(+), 0 deletions(-)

diff --git a/arch/arm64/boot/dts/marvell/armada-3720-db.dts b/arch/arm64/boot/dts/marvell/armada-3720-db.dts
index 1372e9a6aaa4..5b9cff4d5d7a 100644
--- a/arch/arm64/boot/dts/marvell/armada-3720-db.dts
+++ b/arch/arm64/boot/dts/marvell/armada-3720-db.dts
@@ -72,6 +72,13 @@
 	status = "okay";
 };
 
+&sdhci0 {
+	non-removable;
+	bus-width = <8>;
+	xenon,pad-type = "fixed-1-8v";
+	status = "okay";
+};
+
 /* CON31 */
 &usb3 {
 	status = "okay";
diff --git a/arch/arm64/boot/dts/marvell/armada-37xx.dtsi b/arch/arm64/boot/dts/marvell/armada-37xx.dtsi
index c4762538ec01..0c4cafe92e66 100644
--- a/arch/arm64/boot/dts/marvell/armada-37xx.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-37xx.dtsi
@@ -161,6 +161,17 @@
 				};
 			};
 
+			sdhci0: sdhci at d8000 {
+				compatible = "marvell,armada-3700-sdhci",
+				"marvell,sdhci-xenon";
+				reg = <0xd8000 0x300
+				       0x17808 0x4>;
+				interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
+				clocks = <&nb_perih_clk 0>;
+				clock-names = "core";
+				status = "disabled";
+			};
+
 			sata: sata at e0000 {
 				compatible = "marvell,armada-3700-ahci";
 				reg = <0xe0000 0x2000>;
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 9/10] arm64: dts: marvell: add sdhci support for Armada 7K/8K
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, Rob Herring, devicetree, Thomas Petazzoni,
	linux-arm-kernel, 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, Peng Zhu, Yu Cao, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

Also enable it on the Armada 7040 DB board

Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
 arch/arm64/boot/dts/marvell/armada-7040-db.dts |  7 +++++++
 arch/arm64/boot/dts/marvell/armada-ap806.dtsi  |  9 +++++++++
 2 files changed, 16 insertions(+), 0 deletions(-)

diff --git a/arch/arm64/boot/dts/marvell/armada-7040-db.dts b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
index 070b589680c5..f7f978a12a49 100644
--- a/arch/arm64/boot/dts/marvell/armada-7040-db.dts
+++ b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
@@ -146,3 +146,10 @@
 &cpm_usb3_1 {
 	status = "okay";
 };
+
+&sdhci0 {
+	status = "okay";
+	bus-width = <4>;
+	no-1-8-v;
+	non-removable;
+};
diff --git a/arch/arm64/boot/dts/marvell/armada-ap806.dtsi b/arch/arm64/boot/dts/marvell/armada-ap806.dtsi
index 7b6136182ad0..ef2ce6be7205 100644
--- a/arch/arm64/boot/dts/marvell/armada-ap806.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-ap806.dtsi
@@ -229,6 +229,15 @@
 
 			};
 
+			sdhci0: sdhci@6e0000 {
+				compatible = "marvell,sdhci-xenon";
+				reg = <0x6e0000 0x300>;
+				interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH>;
+				clock-names = "core";
+				clocks = <&cpm_syscon0 1 4>;
+				status = "disabled";
+			};
+
 			ap_syscon: system-controller@6f4000 {
 				compatible = "marvell,ap806-system-controller",
 					     "syscon";
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 9/10] arm64: dts: marvell: add sdhci support for Armada 7K/8K
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc-u79uwXL29TY76Z2rM5mHXA
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, 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

Also enable it on the Armada 7040 DB board

Signed-off-by: Gregory CLEMENT <gregory.clement-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 arch/arm64/boot/dts/marvell/armada-7040-db.dts |  7 +++++++
 arch/arm64/boot/dts/marvell/armada-ap806.dtsi  |  9 +++++++++
 2 files changed, 16 insertions(+), 0 deletions(-)

diff --git a/arch/arm64/boot/dts/marvell/armada-7040-db.dts b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
index 070b589680c5..f7f978a12a49 100644
--- a/arch/arm64/boot/dts/marvell/armada-7040-db.dts
+++ b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
@@ -146,3 +146,10 @@
 &cpm_usb3_1 {
 	status = "okay";
 };
+
+&sdhci0 {
+	status = "okay";
+	bus-width = <4>;
+	no-1-8-v;
+	non-removable;
+};
diff --git a/arch/arm64/boot/dts/marvell/armada-ap806.dtsi b/arch/arm64/boot/dts/marvell/armada-ap806.dtsi
index 7b6136182ad0..ef2ce6be7205 100644
--- a/arch/arm64/boot/dts/marvell/armada-ap806.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-ap806.dtsi
@@ -229,6 +229,15 @@
 
 			};
 
+			sdhci0: sdhci@6e0000 {
+				compatible = "marvell,sdhci-xenon";
+				reg = <0x6e0000 0x300>;
+				interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH>;
+				clock-names = "core";
+				clocks = <&cpm_syscon0 1 4>;
+				status = "disabled";
+			};
+
 			ap_syscon: system-controller@6f4000 {
 				compatible = "marvell,ap806-system-controller",
 					     "syscon";
-- 
git-series 0.8.10
--
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 related	[flat|nested] 105+ messages in thread

* [PATCH 9/10] arm64: dts: marvell: add sdhci support for Armada 7K/8K
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: linux-arm-kernel

Also enable it on the Armada 7040 DB board

Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
 arch/arm64/boot/dts/marvell/armada-7040-db.dts |  7 +++++++
 arch/arm64/boot/dts/marvell/armada-ap806.dtsi  |  9 +++++++++
 2 files changed, 16 insertions(+), 0 deletions(-)

diff --git a/arch/arm64/boot/dts/marvell/armada-7040-db.dts b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
index 070b589680c5..f7f978a12a49 100644
--- a/arch/arm64/boot/dts/marvell/armada-7040-db.dts
+++ b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
@@ -146,3 +146,10 @@
 &cpm_usb3_1 {
 	status = "okay";
 };
+
+&sdhci0 {
+	status = "okay";
+	bus-width = <4>;
+	no-1-8-v;
+	non-removable;
+};
diff --git a/arch/arm64/boot/dts/marvell/armada-ap806.dtsi b/arch/arm64/boot/dts/marvell/armada-ap806.dtsi
index 7b6136182ad0..ef2ce6be7205 100644
--- a/arch/arm64/boot/dts/marvell/armada-ap806.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-ap806.dtsi
@@ -229,6 +229,15 @@
 
 			};
 
+			sdhci0: sdhci at 6e0000 {
+				compatible = "marvell,sdhci-xenon";
+				reg = <0x6e0000 0x300>;
+				interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH>;
+				clock-names = "core";
+				clocks = <&cpm_syscon0 1 4>;
+				status = "disabled";
+			};
+
 			ap_syscon: system-controller at 6f4000 {
 				compatible = "marvell,ap806-system-controller",
 					     "syscon";
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 10/10] arm64: configs: enable SDHCI driver for Xenon
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, Rob Herring, devicetree, Thomas Petazzoni,
	linux-arm-kernel, 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, Peng Zhu, Yu Cao, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

This patch enables the driver for the SDHCI controller found on the
Marvell Armada 3700 and 7K/8K ARM64 SoCs.

Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
 arch/arm64/configs/defconfig | 1 +
 1 file changed, 1 insertion(+), 0 deletions(-)

diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index dab2cb0c1f1c..2d1f5ee62b18 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -353,6 +353,7 @@ CONFIG_MMC_DW=y
 CONFIG_MMC_DW_EXYNOS=y
 CONFIG_MMC_DW_K3=y
 CONFIG_MMC_SUNXI=y
+CONFIG_MMC_SDHCI_XENON=y
 CONFIG_NEW_LEDS=y
 CONFIG_LEDS_CLASS=y
 CONFIG_LEDS_GPIO=y
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* [PATCH 10/10] arm64: configs: enable SDHCI driver for Xenon
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc-u79uwXL29TY76Z2rM5mHXA
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, 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

This patch enables the driver for the SDHCI controller found on the
Marvell Armada 3700 and 7K/8K ARM64 SoCs.

Signed-off-by: Gregory CLEMENT <gregory.clement-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 arch/arm64/configs/defconfig | 1 +
 1 file changed, 1 insertion(+), 0 deletions(-)

diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index dab2cb0c1f1c..2d1f5ee62b18 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -353,6 +353,7 @@ CONFIG_MMC_DW=y
 CONFIG_MMC_DW_EXYNOS=y
 CONFIG_MMC_DW_K3=y
 CONFIG_MMC_SUNXI=y
+CONFIG_MMC_SDHCI_XENON=y
 CONFIG_NEW_LEDS=y
 CONFIG_LEDS_CLASS=y
 CONFIG_LEDS_GPIO=y
-- 
git-series 0.8.10
--
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 related	[flat|nested] 105+ messages in thread

* [PATCH 10/10] arm64: configs: enable SDHCI driver for Xenon
@ 2016-10-07 15:22   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-07 15:22 UTC (permalink / raw)
  To: linux-arm-kernel

This patch enables the driver for the SDHCI controller found on the
Marvell Armada 3700 and 7K/8K ARM64 SoCs.

Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
---
 arch/arm64/configs/defconfig | 1 +
 1 file changed, 1 insertion(+), 0 deletions(-)

diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index dab2cb0c1f1c..2d1f5ee62b18 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -353,6 +353,7 @@ CONFIG_MMC_DW=y
 CONFIG_MMC_DW_EXYNOS=y
 CONFIG_MMC_DW_K3=y
 CONFIG_MMC_SUNXI=y
+CONFIG_MMC_SDHCI_XENON=y
 CONFIG_NEW_LEDS=y
 CONFIG_LEDS_CLASS=y
 CONFIG_LEDS_GPIO=y
-- 
git-series 0.8.10

^ permalink raw reply related	[flat|nested] 105+ messages in thread

* Re: [PATCH 4/10] MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
@ 2016-10-07 20:44     ` Joe Perches
  0 siblings, 0 replies; 105+ messages in thread
From: Joe Perches @ 2016-10-07 20:44 UTC (permalink / raw)
  To: Gregory CLEMENT, Ulf Hansson, Adrian Hunter, linux-mmc
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth, Rob Herring,
	devicetree, Thomas Petazzoni, linux-arm-kernel, 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, Peng Zhu,
	Yu Cao, Romain Perier, Yehuda Yitschak, Marcin Wojtas,
	Hanna Hawa, Kostya Porotchkin, linux-kernel

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

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 4/10] MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
@ 2016-10-07 20:44     ` Joe Perches
  0 siblings, 0 replies; 105+ messages in thread
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

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	[flat|nested] 105+ messages in thread

* [PATCH 4/10] MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
@ 2016-10-07 20:44     ` Joe Perches
  0 siblings, 0 replies; 105+ messages in thread
From: Joe Perches @ 2016-10-07 20:44 UTC (permalink / raw)
  To: linux-arm-kernel

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 at vger.kernel.org
> +S:	Supported

You should really add F: file patterns here

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 4/10] MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
  2016-10-07 20:44     ` Joe Perches
  (?)
@ 2016-10-08  0:59       ` Ziji Hu
  -1 siblings, 0 replies; 105+ messages in thread
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, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

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	[flat|nested] 105+ messages in thread

* Re: [PATCH 4/10] MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
@ 2016-10-08  0:59       ` Ziji Hu
  0 siblings, 0 replies; 105+ messages in thread
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

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	[flat|nested] 105+ messages in thread

* [PATCH 4/10] MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
@ 2016-10-08  0:59       ` Ziji Hu
  0 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-08  0:59 UTC (permalink / raw)
  To: linux-arm-kernel

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 at 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	[flat|nested] 105+ messages in thread

* Re: [PATCH 2/10] mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
@ 2016-10-08  2:40     ` Shawn Lin
  0 siblings, 0 replies; 105+ messages in thread
From: Shawn Lin @ 2016-10-08  2:40 UTC (permalink / raw)
  To: Gregory CLEMENT, Ulf Hansson, Adrian Hunter, linux-mmc
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth, Rob Herring,
	devicetree, Thomas Petazzoni, linux-arm-kernel, 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, Peng Zhu,
	Yu Cao, Romain Perier, Yehuda Yitschak, Marcin Wojtas,
	Hanna Hawa, Kostya Porotchkin, linux-kernel, shawn.lin

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.

> 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);
>


-- 
Best Regards
Shawn Lin

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 2/10] mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
@ 2016-10-08  2:40     ` Shawn Lin
  0 siblings, 0 replies; 105+ messages in thread
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

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	[flat|nested] 105+ messages in thread

* [PATCH 2/10] mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
@ 2016-10-08  2:40     ` Shawn Lin
  0 siblings, 0 replies; 105+ messages in thread
From: Shawn Lin @ 2016-10-08  2:40 UTC (permalink / raw)
  To: linux-arm-kernel

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.

> 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);
>


-- 
Best Regards
Shawn Lin

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
@ 2016-10-08  2:44     ` Shawn Lin
  0 siblings, 0 replies; 105+ messages in thread
From: Shawn Lin @ 2016-10-08  2:44 UTC (permalink / raw)
  To: 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,
	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,
	Peng Zhu, Yu Cao, Romain Perier, Yehuda Yitschak, Marcin Wojtas,
	Hanna Hawa, Kostya Porotchkin, linux-kernel

在 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. :)

> @@ -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
>


-- 
Best Regards
Shawn Lin

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
@ 2016-10-08  2:44     ` Shawn Lin
  0 siblings, 0 replies; 105+ messages in thread
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

在 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	[flat|nested] 105+ messages in thread

* [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
@ 2016-10-08  2:44     ` Shawn Lin
  0 siblings, 0 replies; 105+ messages in thread
From: Shawn Lin @ 2016-10-08  2:44 UTC (permalink / raw)
  To: linux-arm-kernel

? 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 at 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. :)

> @@ -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
>


-- 
Best Regards
Shawn Lin

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 2/10] mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
  2016-10-08  2:40     ` Shawn Lin
  (?)
@ 2016-10-08  6:26       ` Ziji Hu
  -1 siblings, 0 replies; 105+ messages in thread
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, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

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	[flat|nested] 105+ messages in thread

* Re: [PATCH 2/10] mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
@ 2016-10-08  6:26       ` Ziji Hu
  0 siblings, 0 replies; 105+ messages in thread
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

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	[flat|nested] 105+ messages in thread

* [PATCH 2/10] mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
@ 2016-10-08  6:26       ` Ziji Hu
  0 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-08  6:26 UTC (permalink / raw)
  To: linux-arm-kernel

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	[flat|nested] 105+ messages in thread

* Re: [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
  2016-10-08  2:44     ` Shawn Lin
  (?)
@ 2016-10-08  9:28       ` Ziji Hu
  -1 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-08  9:28 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, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

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
>>
> 
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
@ 2016-10-08  9:28       ` Ziji Hu
  0 siblings, 0 replies; 105+ messages in thread
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

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	[flat|nested] 105+ messages in thread

* [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
@ 2016-10-08  9:28       ` Ziji Hu
  0 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-08  9:28 UTC (permalink / raw)
  To: linux-arm-kernel

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 at 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
>>
> 
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
  2016-10-08  9:28       ` Ziji Hu
  (?)
@ 2016-10-09 13:34         ` Shawn Lin
  -1 siblings, 0 replies; 105+ messages in thread
From: Shawn Lin @ 2016-10-09 13:34 UTC (permalink / raw)
  To: Ziji Hu, Shawn Lin, 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, Yu Cao,
	Romain Perier, Yehuda Yitschak, Marcin Wojtas, Hanna Hawa,
	Kostya Porotchkin, linux-kernel

在 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	[flat|nested] 105+ messages in thread

* Re: [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
@ 2016-10-09 13:34         ` Shawn Lin
  0 siblings, 0 replies; 105+ messages in thread
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

在 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	[flat|nested] 105+ messages in thread

* [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
@ 2016-10-09 13:34         ` Shawn Lin
  0 siblings, 0 replies; 105+ messages in thread
From: Shawn Lin @ 2016-10-09 13:34 UTC (permalink / raw)
  To: linux-arm-kernel

? 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 at 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 at vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>


-- 
Best Regards
Shawn Lin

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 5/10] dt: bindings: Add bindings for Marvell Xenon SD Host Controller
  2016-10-07 15:22   ` Gregory CLEMENT
  (?)
@ 2016-10-10 21:34     ` Rob Herring
  -1 siblings, 0 replies; 105+ messages in thread
From: Rob Herring @ 2016-10-10 21:34 UTC (permalink / raw)
  To: Gregory CLEMENT
  Cc: Ulf Hansson, Adrian Hunter, linux-mmc, Jason Cooper, Andrew Lunn,
	Sebastian Hesselbarth, devicetree, Thomas Petazzoni,
	linux-arm-kernel, 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, Peng Zhu, Yu Cao, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

On Fri, Oct 07, 2016 at 05:22:51PM +0200, Gregory CLEMENT wrote:
> From: Ziji Hu <huziji@marvell.com>
> 
> Marvell Xenon SDHC can support eMMC/SD/SDIO.
> Add Xenon-specific properties.
> Also add properties for Xenon PHY setting.
> 
> 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>
> ---
>  Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt | 164 +++++++-
>  MAINTAINERS                                                   |   1 +-
>  2 files changed, 165 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
> 
> diff --git a/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt b/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
> new file mode 100644
> index 000000000000..8b25ad28ebbd
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
> @@ -0,0 +1,164 @@
> +Marvell's Xenon SDHCI Controller device tree bindings
> +This file documents differences between the core mmc properties
> +described by mmc.txt and the properties used by the Xenon implementation.
> +
> +A single Xenon IP can support multiple slots.
> +Each slot acts as an independent SDHC. It owns independent resources, such
> +as register sets clock and PHY.

Is the phy really part of the same block?

> +Each slot should have an independent device tree node.
> +
> +Required Properties:
> +- compatible: should be "marvell,sdhci-xenon" or "marvell,armada-3700-sdhci".

Perhaps some consistent ordering (w/ -sdhci on the end).

> +
> +- Input Clock Name

Your formatting of properties is a bit strange. Please restructure like 
most bindings so the property names are before all the description.

> +  Some SOCs require additional clock for AXI bus.

Those SoCs should have a specific compatible string and you need to 
define which compatible strings have 2 clocks vs. 1 clock.

> +  The input clock for Xenon IP core should be named as "core".
> +  The optional AXI clock should be named as "axi".
> +  - clocks = <&core_clk>, <&axi_clock>;
> +  - clock-names = "core", "axi";
> +
> +- Register Set Size

Is this a property name?

> +  Different Xenon SDHC release has different register set size.
> +  The specific size should also refer to the SOC implementation.
> +
> +Optional Properties:
> +- Slot Index
> +  A single Xenon IP can support multiple slots.
> +  During initialization, each slot should set corresponding setting bit in
> +  some Xenon-specific registers. The corresponding bit is determined by
> +  this property.
> +  - xenon,slotno = <slot_index>;

Slots should probably be represented as child nodes with the reg 
property being the slot number.

Also, xenon is not a vendor prefix.

> +  If this property is not provided, Xenon IP should contain only one slot
> +  and the slot index will be 0x0 by default.
> +
> +- PHY Type

You're going to need to come of with a common binding for this.

> +  Xenon support mutilple types of PHYs.
> +  To select eMMC 5.1 PHY, set:
> +  - xenon,phy-type = "emmc 5.1 phy"
> +  eMMC 5.1 PHY is the default choice if this property is not provided.
> +  To select eMMC 5.0 PHY, set:
> +  - xenon,phy-type = "emmc 5.0 phy"
> +  To select SDH PHY, set:
> +  - xenon,phy-type = "sdh phy"
> +  Please note that eMMC PHY is a general PHY for eMMC/SD/SDIO, other than for
> +  eMMC only.
> +
> +- Customized eMMC PHY Parameters
> +  Some boards require different values of some specific eMMC PHY parameters.
> +  Some SOCs also require specific workaround to set eMMC PHY.
> +  These properties enable diverse boards to customize the eMMC PHY.
> +  The supported eMMC PHY parameters are listed in below. All those properties
> +  are only available for eMMC PHY 5.1 and eMMC PHY 5.0.
> +  ZNR
> +  valid range = [0:0x1F].
> +  ZNR is set as 0xF by default if this property is not provided.
> +  - xenon,phy-znr = <value>;
> +
> +  ZPR
> +  valid range = [0:0x1F].
> +  ZPR is set as 0xF by default if this property is not provided.
> +  - xenon,phy-zpr = <value>;

marvell is the vendor prefix.

> +
> +  Number of successful tuning times
> +  Set the number of required consecutive successful sampling points used to
> +  identify a valid sampling window, in tuning process.
> +  Valid range = [1:7]. Set as 0x4 by default if this property is not provided.
> +  - xenon,phy-nr-tun-times = <nr_times>;
> +
> +  Divider for TUN_STEP
> +  Set the divider for calculating TUN_STEP.
> +  Set as 64 by default if this property is not provided.
> +  - xenon,phy-tun-step-divider = <divider>;
> +
> +  Force PHY into slow mode.
> +  Only available when bus frequency lower than 50MHz in SDR mde.
> +  Disabled by default. Please do not enable it unless it is necessary.
> +  - xenon,phy-slow-mode;
> +
> +- Mask Conflict Error Report
> +  Disable Conflict Error alert on some SOC. Disabled by default.
> +  xenon,mask-conflict-err;
> +
> +- Re-tuning Counter
> +  Xenon SDHC SOC usually doesn't provide re-tuning counter in
> +  Capabilities Register 3 Bit[11:8].
> +  This property provides the re-tuning counter.
> +  xenon,tuning-count = <count>;
> +  If this property is not set, default re-tuning counter will
> +  be set as 0x9 in driver.
> +
> +- SOC PHY PAD Voltage Control register
> +  Some SOCs have SOC PHY PAD Voltage Control register outside Xenon IP.
> +  This register sets SOC PHY PAD Voltage to keep aligh with Vccq.
> +  Two properties provide information of this control register.
> +  These two properties are only valid when "marvell,armada-3700-sdhci"
> +  is selected. Both of them must be provided when "marvell,armada-3700-sdhci"
> +  is selected.
> +  - xenon,pad-type
> +    Two types: "sd" and "fixed-1-8v".
> +    If "sd" is slected, SOC PHY PAD is set as 3.3V at the beginning and is
> +    switched to 1.8V when SD in UHS-I.
> +    If "fixed-1-8v" is slected, SOC PHY PAD is fixed 1.8V, such as for eMMC.

You should be able to existing, common properties for i/o voltage 
capabilities/constraints.

> +  - reg
> +    Physical address and size of SOC PHY PAD register.
> +    Append after Xenon SDHC register space, as a second register field.
> +
> +  Please follow the examples with compatible "marvell,armada-3700-sdhci"
> +  in below.
> +
> +Example:
> +- For eMMC slot:
> +
> +	sdhci@aa0000 {
> +		compatible = "marvell,sdhci-xenon";
> +		reg = <0xaa0000 0x1000>;
> +		interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
> +		clocks = <&emmcclk>;
> +		clock-names = "core";
> +		xenon,slotno = <0>;
> +		xenon,phy-type = "emmc 5.1 phy";
> +		bus-width = <8>;
> +		tuning-count = <11>;
> +	};
> +
> +- For SD/SDIO slot:
> +
> +	sdhci@ab0000 {
> +		compatible = "marvell,sdhci-xenon";
> +		reg = <0xab0000 0x1000>;
> +		interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
> +		vqmmc-supply = <&sd_regulator>;
> +		clocks = <&sdclk>;
> +		clock-names = "core";
> +		bus-width = <4>;
> +		tuning-count = <9>;
> +	};
> +
> +- For eMMC slot with compatible "marvell,armada-3700-sdhci":
> +
> +	sdhci@aa0000 {
> +		compatible = "marvell,armada-3700-sdhci";
> +		reg = <0xaa0000 0x1000>,
> +		      <phy_addr 0x4>;
> +		interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
> +		clocks = <&emmcclk>;
> +		clock-names = "core";
> +		bus-width = <8>;
> +
> +		xenon,pad-type = "fixed-1-8v";
> +	};
> +
> +- For SD/SDIO slot with compatible "marvell,armada-3700-sdhci":
> +
> +	sdhci@ab0000 {
> +		compatible = "marvell,armada-3700-sdhci";
> +		reg = <0xab0000 0x1000>,
> +		      <phy_addr 0x4>;
> +		interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
> +		vqmmc-supply = <&sd_regulator>;
> +		clocks = <&sdclk>;
> +		clock-names = "core";
> +		bus-width = <4>;
> +
> +		xenon,pad-type = "sd";
> +	};
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 89adcd57aa25..4aa0eac9bfc7 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -7582,6 +7582,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
>  M:	Ziji Hu <huziji@marvell.com>
>  L:	linux-mmc@vger.kernel.org
>  S:	Supported
> +F:	Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>  
>  MATROX FRAMEBUFFER DRIVER
>  L:	linux-fbdev@vger.kernel.org
> -- 
> git-series 0.8.10

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 5/10] dt: bindings: Add bindings for Marvell Xenon SD Host Controller
@ 2016-10-10 21:34     ` Rob Herring
  0 siblings, 0 replies; 105+ messages in thread
From: Rob Herring @ 2016-10-10 21:34 UTC (permalink / raw)
  To: Gregory CLEMENT
  Cc: Ulf Hansson, Adrian Hunter, linux-mmc, Jason Cooper, Andrew Lunn,
	Sebastian Hesselbarth, devicetree, Thomas Petazzoni,
	linux-arm-kernel, 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

On Fri, Oct 07, 2016 at 05:22:51PM +0200, Gregory CLEMENT wrote:
> From: Ziji Hu <huziji@marvell.com>
> 
> Marvell Xenon SDHC can support eMMC/SD/SDIO.
> Add Xenon-specific properties.
> Also add properties for Xenon PHY setting.
> 
> 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>
> ---
>  Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt | 164 +++++++-
>  MAINTAINERS                                                   |   1 +-
>  2 files changed, 165 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
> 
> diff --git a/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt b/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
> new file mode 100644
> index 000000000000..8b25ad28ebbd
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
> @@ -0,0 +1,164 @@
> +Marvell's Xenon SDHCI Controller device tree bindings
> +This file documents differences between the core mmc properties
> +described by mmc.txt and the properties used by the Xenon implementation.
> +
> +A single Xenon IP can support multiple slots.
> +Each slot acts as an independent SDHC. It owns independent resources, such
> +as register sets clock and PHY.

Is the phy really part of the same block?

> +Each slot should have an independent device tree node.
> +
> +Required Properties:
> +- compatible: should be "marvell,sdhci-xenon" or "marvell,armada-3700-sdhci".

Perhaps some consistent ordering (w/ -sdhci on the end).

> +
> +- Input Clock Name

Your formatting of properties is a bit strange. Please restructure like 
most bindings so the property names are before all the description.

> +  Some SOCs require additional clock for AXI bus.

Those SoCs should have a specific compatible string and you need to 
define which compatible strings have 2 clocks vs. 1 clock.

> +  The input clock for Xenon IP core should be named as "core".
> +  The optional AXI clock should be named as "axi".
> +  - clocks = <&core_clk>, <&axi_clock>;
> +  - clock-names = "core", "axi";
> +
> +- Register Set Size

Is this a property name?

> +  Different Xenon SDHC release has different register set size.
> +  The specific size should also refer to the SOC implementation.
> +
> +Optional Properties:
> +- Slot Index
> +  A single Xenon IP can support multiple slots.
> +  During initialization, each slot should set corresponding setting bit in
> +  some Xenon-specific registers. The corresponding bit is determined by
> +  this property.
> +  - xenon,slotno = <slot_index>;

Slots should probably be represented as child nodes with the reg 
property being the slot number.

Also, xenon is not a vendor prefix.

> +  If this property is not provided, Xenon IP should contain only one slot
> +  and the slot index will be 0x0 by default.
> +
> +- PHY Type

You're going to need to come of with a common binding for this.

> +  Xenon support mutilple types of PHYs.
> +  To select eMMC 5.1 PHY, set:
> +  - xenon,phy-type = "emmc 5.1 phy"
> +  eMMC 5.1 PHY is the default choice if this property is not provided.
> +  To select eMMC 5.0 PHY, set:
> +  - xenon,phy-type = "emmc 5.0 phy"
> +  To select SDH PHY, set:
> +  - xenon,phy-type = "sdh phy"
> +  Please note that eMMC PHY is a general PHY for eMMC/SD/SDIO, other than for
> +  eMMC only.
> +
> +- Customized eMMC PHY Parameters
> +  Some boards require different values of some specific eMMC PHY parameters.
> +  Some SOCs also require specific workaround to set eMMC PHY.
> +  These properties enable diverse boards to customize the eMMC PHY.
> +  The supported eMMC PHY parameters are listed in below. All those properties
> +  are only available for eMMC PHY 5.1 and eMMC PHY 5.0.
> +  ZNR
> +  valid range = [0:0x1F].
> +  ZNR is set as 0xF by default if this property is not provided.
> +  - xenon,phy-znr = <value>;
> +
> +  ZPR
> +  valid range = [0:0x1F].
> +  ZPR is set as 0xF by default if this property is not provided.
> +  - xenon,phy-zpr = <value>;

marvell is the vendor prefix.

> +
> +  Number of successful tuning times
> +  Set the number of required consecutive successful sampling points used to
> +  identify a valid sampling window, in tuning process.
> +  Valid range = [1:7]. Set as 0x4 by default if this property is not provided.
> +  - xenon,phy-nr-tun-times = <nr_times>;
> +
> +  Divider for TUN_STEP
> +  Set the divider for calculating TUN_STEP.
> +  Set as 64 by default if this property is not provided.
> +  - xenon,phy-tun-step-divider = <divider>;
> +
> +  Force PHY into slow mode.
> +  Only available when bus frequency lower than 50MHz in SDR mde.
> +  Disabled by default. Please do not enable it unless it is necessary.
> +  - xenon,phy-slow-mode;
> +
> +- Mask Conflict Error Report
> +  Disable Conflict Error alert on some SOC. Disabled by default.
> +  xenon,mask-conflict-err;
> +
> +- Re-tuning Counter
> +  Xenon SDHC SOC usually doesn't provide re-tuning counter in
> +  Capabilities Register 3 Bit[11:8].
> +  This property provides the re-tuning counter.
> +  xenon,tuning-count = <count>;
> +  If this property is not set, default re-tuning counter will
> +  be set as 0x9 in driver.
> +
> +- SOC PHY PAD Voltage Control register
> +  Some SOCs have SOC PHY PAD Voltage Control register outside Xenon IP.
> +  This register sets SOC PHY PAD Voltage to keep aligh with Vccq.
> +  Two properties provide information of this control register.
> +  These two properties are only valid when "marvell,armada-3700-sdhci"
> +  is selected. Both of them must be provided when "marvell,armada-3700-sdhci"
> +  is selected.
> +  - xenon,pad-type
> +    Two types: "sd" and "fixed-1-8v".
> +    If "sd" is slected, SOC PHY PAD is set as 3.3V at the beginning and is
> +    switched to 1.8V when SD in UHS-I.
> +    If "fixed-1-8v" is slected, SOC PHY PAD is fixed 1.8V, such as for eMMC.

You should be able to existing, common properties for i/o voltage 
capabilities/constraints.

> +  - reg
> +    Physical address and size of SOC PHY PAD register.
> +    Append after Xenon SDHC register space, as a second register field.
> +
> +  Please follow the examples with compatible "marvell,armada-3700-sdhci"
> +  in below.
> +
> +Example:
> +- For eMMC slot:
> +
> +	sdhci@aa0000 {
> +		compatible = "marvell,sdhci-xenon";
> +		reg = <0xaa0000 0x1000>;
> +		interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
> +		clocks = <&emmcclk>;
> +		clock-names = "core";
> +		xenon,slotno = <0>;
> +		xenon,phy-type = "emmc 5.1 phy";
> +		bus-width = <8>;
> +		tuning-count = <11>;
> +	};
> +
> +- For SD/SDIO slot:
> +
> +	sdhci@ab0000 {
> +		compatible = "marvell,sdhci-xenon";
> +		reg = <0xab0000 0x1000>;
> +		interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
> +		vqmmc-supply = <&sd_regulator>;
> +		clocks = <&sdclk>;
> +		clock-names = "core";
> +		bus-width = <4>;
> +		tuning-count = <9>;
> +	};
> +
> +- For eMMC slot with compatible "marvell,armada-3700-sdhci":
> +
> +	sdhci@aa0000 {
> +		compatible = "marvell,armada-3700-sdhci";
> +		reg = <0xaa0000 0x1000>,
> +		      <phy_addr 0x4>;
> +		interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
> +		clocks = <&emmcclk>;
> +		clock-names = "core";
> +		bus-width = <8>;
> +
> +		xenon,pad-type = "fixed-1-8v";
> +	};
> +
> +- For SD/SDIO slot with compatible "marvell,armada-3700-sdhci":
> +
> +	sdhci@ab0000 {
> +		compatible = "marvell,armada-3700-sdhci";
> +		reg = <0xab0000 0x1000>,
> +		      <phy_addr 0x4>;
> +		interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
> +		vqmmc-supply = <&sd_regulator>;
> +		clocks = <&sdclk>;
> +		clock-names = "core";
> +		bus-width = <4>;
> +
> +		xenon,pad-type = "sd";
> +	};
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 89adcd57aa25..4aa0eac9bfc7 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -7582,6 +7582,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
>  M:	Ziji Hu <huziji@marvell.com>
>  L:	linux-mmc@vger.kernel.org
>  S:	Supported
> +F:	Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>  
>  MATROX FRAMEBUFFER DRIVER
>  L:	linux-fbdev@vger.kernel.org
> -- 
> git-series 0.8.10

^ permalink raw reply	[flat|nested] 105+ messages in thread

* [PATCH 5/10] dt: bindings: Add bindings for Marvell Xenon SD Host Controller
@ 2016-10-10 21:34     ` Rob Herring
  0 siblings, 0 replies; 105+ messages in thread
From: Rob Herring @ 2016-10-10 21:34 UTC (permalink / raw)
  To: linux-arm-kernel

On Fri, Oct 07, 2016 at 05:22:51PM +0200, Gregory CLEMENT wrote:
> From: Ziji Hu <huziji@marvell.com>
> 
> Marvell Xenon SDHC can support eMMC/SD/SDIO.
> Add Xenon-specific properties.
> Also add properties for Xenon PHY setting.
> 
> 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>
> ---
>  Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt | 164 +++++++-
>  MAINTAINERS                                                   |   1 +-
>  2 files changed, 165 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
> 
> diff --git a/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt b/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
> new file mode 100644
> index 000000000000..8b25ad28ebbd
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
> @@ -0,0 +1,164 @@
> +Marvell's Xenon SDHCI Controller device tree bindings
> +This file documents differences between the core mmc properties
> +described by mmc.txt and the properties used by the Xenon implementation.
> +
> +A single Xenon IP can support multiple slots.
> +Each slot acts as an independent SDHC. It owns independent resources, such
> +as register sets clock and PHY.

Is the phy really part of the same block?

> +Each slot should have an independent device tree node.
> +
> +Required Properties:
> +- compatible: should be "marvell,sdhci-xenon" or "marvell,armada-3700-sdhci".

Perhaps some consistent ordering (w/ -sdhci on the end).

> +
> +- Input Clock Name

Your formatting of properties is a bit strange. Please restructure like 
most bindings so the property names are before all the description.

> +  Some SOCs require additional clock for AXI bus.

Those SoCs should have a specific compatible string and you need to 
define which compatible strings have 2 clocks vs. 1 clock.

> +  The input clock for Xenon IP core should be named as "core".
> +  The optional AXI clock should be named as "axi".
> +  - clocks = <&core_clk>, <&axi_clock>;
> +  - clock-names = "core", "axi";
> +
> +- Register Set Size

Is this a property name?

> +  Different Xenon SDHC release has different register set size.
> +  The specific size should also refer to the SOC implementation.
> +
> +Optional Properties:
> +- Slot Index
> +  A single Xenon IP can support multiple slots.
> +  During initialization, each slot should set corresponding setting bit in
> +  some Xenon-specific registers. The corresponding bit is determined by
> +  this property.
> +  - xenon,slotno = <slot_index>;

Slots should probably be represented as child nodes with the reg 
property being the slot number.

Also, xenon is not a vendor prefix.

> +  If this property is not provided, Xenon IP should contain only one slot
> +  and the slot index will be 0x0 by default.
> +
> +- PHY Type

You're going to need to come of with a common binding for this.

> +  Xenon support mutilple types of PHYs.
> +  To select eMMC 5.1 PHY, set:
> +  - xenon,phy-type = "emmc 5.1 phy"
> +  eMMC 5.1 PHY is the default choice if this property is not provided.
> +  To select eMMC 5.0 PHY, set:
> +  - xenon,phy-type = "emmc 5.0 phy"
> +  To select SDH PHY, set:
> +  - xenon,phy-type = "sdh phy"
> +  Please note that eMMC PHY is a general PHY for eMMC/SD/SDIO, other than for
> +  eMMC only.
> +
> +- Customized eMMC PHY Parameters
> +  Some boards require different values of some specific eMMC PHY parameters.
> +  Some SOCs also require specific workaround to set eMMC PHY.
> +  These properties enable diverse boards to customize the eMMC PHY.
> +  The supported eMMC PHY parameters are listed in below. All those properties
> +  are only available for eMMC PHY 5.1 and eMMC PHY 5.0.
> +  ZNR
> +  valid range = [0:0x1F].
> +  ZNR is set as 0xF by default if this property is not provided.
> +  - xenon,phy-znr = <value>;
> +
> +  ZPR
> +  valid range = [0:0x1F].
> +  ZPR is set as 0xF by default if this property is not provided.
> +  - xenon,phy-zpr = <value>;

marvell is the vendor prefix.

> +
> +  Number of successful tuning times
> +  Set the number of required consecutive successful sampling points used to
> +  identify a valid sampling window, in tuning process.
> +  Valid range = [1:7]. Set as 0x4 by default if this property is not provided.
> +  - xenon,phy-nr-tun-times = <nr_times>;
> +
> +  Divider for TUN_STEP
> +  Set the divider for calculating TUN_STEP.
> +  Set as 64 by default if this property is not provided.
> +  - xenon,phy-tun-step-divider = <divider>;
> +
> +  Force PHY into slow mode.
> +  Only available when bus frequency lower than 50MHz in SDR mde.
> +  Disabled by default. Please do not enable it unless it is necessary.
> +  - xenon,phy-slow-mode;
> +
> +- Mask Conflict Error Report
> +  Disable Conflict Error alert on some SOC. Disabled by default.
> +  xenon,mask-conflict-err;
> +
> +- Re-tuning Counter
> +  Xenon SDHC SOC usually doesn't provide re-tuning counter in
> +  Capabilities Register 3 Bit[11:8].
> +  This property provides the re-tuning counter.
> +  xenon,tuning-count = <count>;
> +  If this property is not set, default re-tuning counter will
> +  be set as 0x9 in driver.
> +
> +- SOC PHY PAD Voltage Control register
> +  Some SOCs have SOC PHY PAD Voltage Control register outside Xenon IP.
> +  This register sets SOC PHY PAD Voltage to keep aligh with Vccq.
> +  Two properties provide information of this control register.
> +  These two properties are only valid when "marvell,armada-3700-sdhci"
> +  is selected. Both of them must be provided when "marvell,armada-3700-sdhci"
> +  is selected.
> +  - xenon,pad-type
> +    Two types: "sd" and "fixed-1-8v".
> +    If "sd" is slected, SOC PHY PAD is set as 3.3V at the beginning and is
> +    switched to 1.8V when SD in UHS-I.
> +    If "fixed-1-8v" is slected, SOC PHY PAD is fixed 1.8V, such as for eMMC.

You should be able to existing, common properties for i/o voltage 
capabilities/constraints.

> +  - reg
> +    Physical address and size of SOC PHY PAD register.
> +    Append after Xenon SDHC register space, as a second register field.
> +
> +  Please follow the examples with compatible "marvell,armada-3700-sdhci"
> +  in below.
> +
> +Example:
> +- For eMMC slot:
> +
> +	sdhci at aa0000 {
> +		compatible = "marvell,sdhci-xenon";
> +		reg = <0xaa0000 0x1000>;
> +		interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
> +		clocks = <&emmcclk>;
> +		clock-names = "core";
> +		xenon,slotno = <0>;
> +		xenon,phy-type = "emmc 5.1 phy";
> +		bus-width = <8>;
> +		tuning-count = <11>;
> +	};
> +
> +- For SD/SDIO slot:
> +
> +	sdhci at ab0000 {
> +		compatible = "marvell,sdhci-xenon";
> +		reg = <0xab0000 0x1000>;
> +		interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
> +		vqmmc-supply = <&sd_regulator>;
> +		clocks = <&sdclk>;
> +		clock-names = "core";
> +		bus-width = <4>;
> +		tuning-count = <9>;
> +	};
> +
> +- For eMMC slot with compatible "marvell,armada-3700-sdhci":
> +
> +	sdhci at aa0000 {
> +		compatible = "marvell,armada-3700-sdhci";
> +		reg = <0xaa0000 0x1000>,
> +		      <phy_addr 0x4>;
> +		interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
> +		clocks = <&emmcclk>;
> +		clock-names = "core";
> +		bus-width = <8>;
> +
> +		xenon,pad-type = "fixed-1-8v";
> +	};
> +
> +- For SD/SDIO slot with compatible "marvell,armada-3700-sdhci":
> +
> +	sdhci at ab0000 {
> +		compatible = "marvell,armada-3700-sdhci";
> +		reg = <0xab0000 0x1000>,
> +		      <phy_addr 0x4>;
> +		interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
> +		vqmmc-supply = <&sd_regulator>;
> +		clocks = <&sdclk>;
> +		clock-names = "core";
> +		bus-width = <4>;
> +
> +		xenon,pad-type = "sd";
> +	};
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 89adcd57aa25..4aa0eac9bfc7 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -7582,6 +7582,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
>  M:	Ziji Hu <huziji@marvell.com>
>  L:	linux-mmc at vger.kernel.org
>  S:	Supported
> +F:	Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>  
>  MATROX FRAMEBUFFER DRIVER
>  L:	linux-fbdev at vger.kernel.org
> -- 
> git-series 0.8.10

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 5/10] dt: bindings: Add bindings for Marvell Xenon SD Host Controller
  2016-10-10 21:34     ` Rob Herring
  (?)
@ 2016-10-11 10:03       ` Ziji Hu
  -1 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-11 10:03 UTC (permalink / raw)
  To: Rob Herring, Gregory CLEMENT
  Cc: Ulf Hansson, Adrian Hunter, linux-mmc, Jason Cooper, Andrew Lunn,
	Sebastian Hesselbarth, 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,
	Keji Zhang, Liuliu Zhao, Peng Zhu, Yu Cao, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

Hi Rob,

	Thanks a for the review.
	It is really helpful to me.

On 2016/10/11 5:34, Rob Herring wrote:
> On Fri, Oct 07, 2016 at 05:22:51PM +0200, Gregory CLEMENT wrote:
>> From: Ziji Hu <huziji@marvell.com>
>>
>> Marvell Xenon SDHC can support eMMC/SD/SDIO.
>> Add Xenon-specific properties.
>> Also add properties for Xenon PHY setting.
>>
>> 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>
>> ---
>>  Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt | 164 +++++++-
>>  MAINTAINERS                                                   |   1 +-
>>  2 files changed, 165 insertions(+), 0 deletions(-)
>>  create mode 100644 Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>>
>> diff --git a/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt b/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>> new file mode 100644
>> index 000000000000..8b25ad28ebbd
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>> @@ -0,0 +1,164 @@
>> +Marvell's Xenon SDHCI Controller device tree bindings
>> +This file documents differences between the core mmc properties
>> +described by mmc.txt and the properties used by the Xenon implementation.
>> +
>> +A single Xenon IP can support multiple slots.
>> +Each slot acts as an independent SDHC. It owns independent resources, such
>> +as register sets clock and PHY.
> 
> Is the phy really part of the same block?
> 
	Each SDHC slot owns its PHY. It is part of a SDHC slot.
	It is independent to another SDHC slot.

>> +Each slot should have an independent device tree node.
>> +
>> +Required Properties:
>> +- compatible: should be "marvell,sdhci-xenon" or "marvell,armada-3700-sdhci".
> 
> Perhaps some consistent ordering (w/ -sdhci on the end).
	Sure.
	I will adjust the ordering.

> 
>> +
>> +- Input Clock Name
> 
> Your formatting of properties is a bit strange. Please restructure like 
> most bindings so the property names are before all the description.
> 
	OK.
	I will fix the format.

>> +  Some SOCs require additional clock for AXI bus.
> 
> Those SoCs should have a specific compatible string and you need to 
> define which compatible strings have 2 clocks vs. 1 clock.
> 
	Actually, I copy this implementation from another Marvell SDIO Host Controller, sdhci-pxa.
	It is in sdhci-pxa.txt.
	I would like to know if it is still acceptable.

>> +  The input clock for Xenon IP core should be named as "core".
>> +  The optional AXI clock should be named as "axi".
>> +  - clocks = <&core_clk>, <&axi_clock>;
>> +  - clock-names = "core", "axi";
>> +
>> +- Register Set Size
> 
> Is this a property name?

	Sorry, it isn't.
	I will fix the format.

> 
>> +  Different Xenon SDHC release has different register set size.
>> +  The specific size should also refer to the SOC implementation.
>> +
>> +Optional Properties:
>> +- Slot Index
>> +  A single Xenon IP can support multiple slots.
>> +  During initialization, each slot should set corresponding setting bit in
>> +  some Xenon-specific registers. The corresponding bit is determined by
>> +  this property.
>> +  - xenon,slotno = <slot_index>;
> 
> Slots should probably be represented as child nodes with the reg 
> property being the slot number.

	Since each SDHC slot is independent, I find it is more convenient to implement each one as independent SD host/MMC host instant.
	Otherwise, a main function should loop and initialize each slot, like sdhci-pci. I prefer to avoiding such a unnecessary main function.

	It is very hard to determine the slot number by reg property.
	Xenon slots are likely to be different types. 1st slot might be eMMC and 2nd one might be SD. They might have different register size.
	The register size might also varies in different Xenon versions.

> 
> Also, xenon is not a vendor prefix.
> 
	Yes. The issue is that there are multiple Marvell SD Host Controllers existing in kernel.
	If marvell is used as a prefix here, I concern that it might be confused with other Marvell sdhc.
	Can I use a combination of marvell and xenon as a prefix, such as mrvl-xenon?

>> +  If this property is not provided, Xenon IP should contain only one slot
>> +  and the slot index will be 0x0 by default.
>> +
>> +- PHY Type
> 
> You're going to need to come of with a common binding for this.
> 
	Could you please provide more details about the "common binding" here?

	The PHY Type property is Xenon-specific, instead of a standard or a spec.
	Thus I cannot find a common property to stand for it.

>> +  Xenon support mutilple types of PHYs.
>> +  To select eMMC 5.1 PHY, set:
>> +  - xenon,phy-type = "emmc 5.1 phy"
>> +  eMMC 5.1 PHY is the default choice if this property is not provided.
>> +  To select eMMC 5.0 PHY, set:
>> +  - xenon,phy-type = "emmc 5.0 phy"
>> +  To select SDH PHY, set:
>> +  - xenon,phy-type = "sdh phy"
>> +  Please note that eMMC PHY is a general PHY for eMMC/SD/SDIO, other than for
>> +  eMMC only.
>> +
>> +- Customized eMMC PHY Parameters
>> +  Some boards require different values of some specific eMMC PHY parameters.
>> +  Some SOCs also require specific workaround to set eMMC PHY.
>> +  These properties enable diverse boards to customize the eMMC PHY.
>> +  The supported eMMC PHY parameters are listed in below. All those properties
>> +  are only available for eMMC PHY 5.1 and eMMC PHY 5.0.
>> +  ZNR
>> +  valid range = [0:0x1F].
>> +  ZNR is set as 0xF by default if this property is not provided.
>> +  - xenon,phy-znr = <value>;
>> +
>> +  ZPR
>> +  valid range = [0:0x1F].
>> +  ZPR is set as 0xF by default if this property is not provided.
>> +  - xenon,phy-zpr = <value>;
> 
> marvell is the vendor prefix.
> 
>> +
>> +  Number of successful tuning times
>> +  Set the number of required consecutive successful sampling points used to
>> +  identify a valid sampling window, in tuning process.
>> +  Valid range = [1:7]. Set as 0x4 by default if this property is not provided.
>> +  - xenon,phy-nr-tun-times = <nr_times>;
>> +
>> +  Divider for TUN_STEP
>> +  Set the divider for calculating TUN_STEP.
>> +  Set as 64 by default if this property is not provided.
>> +  - xenon,phy-tun-step-divider = <divider>;
>> +
>> +  Force PHY into slow mode.
>> +  Only available when bus frequency lower than 50MHz in SDR mde.
>> +  Disabled by default. Please do not enable it unless it is necessary.
>> +  - xenon,phy-slow-mode;
>> +
>> +- Mask Conflict Error Report
>> +  Disable Conflict Error alert on some SOC. Disabled by default.
>> +  xenon,mask-conflict-err;
>> +
>> +- Re-tuning Counter
>> +  Xenon SDHC SOC usually doesn't provide re-tuning counter in
>> +  Capabilities Register 3 Bit[11:8].
>> +  This property provides the re-tuning counter.
>> +  xenon,tuning-count = <count>;
>> +  If this property is not set, default re-tuning counter will
>> +  be set as 0x9 in driver.
>> +
>> +- SOC PHY PAD Voltage Control register
>> +  Some SOCs have SOC PHY PAD Voltage Control register outside Xenon IP.
>> +  This register sets SOC PHY PAD Voltage to keep aligh with Vccq.
>> +  Two properties provide information of this control register.
>> +  These two properties are only valid when "marvell,armada-3700-sdhci"
>> +  is selected. Both of them must be provided when "marvell,armada-3700-sdhci"
>> +  is selected.
>> +  - xenon,pad-type
>> +    Two types: "sd" and "fixed-1-8v".
>> +    If "sd" is slected, SOC PHY PAD is set as 3.3V at the beginning and is
>> +    switched to 1.8V when SD in UHS-I.
>> +    If "fixed-1-8v" is slected, SOC PHY PAD is fixed 1.8V, such as for eMMC.
> 
> You should be able to existing, common properties for i/o voltage 
> capabilities/constraints.
> 
	The above property is for a special SOC platform in Marvell.
	It is irrelevant to common PHY framework or standard MMC bindings.
	Thus I cannot find a existing and common property to represent it.

	Thank you.

Best regards,
Hu Ziji

>> +  - reg
>> +    Physical address and size of SOC PHY PAD register.
>> +    Append after Xenon SDHC register space, as a second register field.
>> +
>> +  Please follow the examples with compatible "marvell,armada-3700-sdhci"
>> +  in below.
>> +
>> +Example:
>> +- For eMMC slot:
>> +
>> +	sdhci@aa0000 {
>> +		compatible = "marvell,sdhci-xenon";
>> +		reg = <0xaa0000 0x1000>;
>> +		interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
>> +		clocks = <&emmcclk>;
>> +		clock-names = "core";
>> +		xenon,slotno = <0>;
>> +		xenon,phy-type = "emmc 5.1 phy";
>> +		bus-width = <8>;
>> +		tuning-count = <11>;
>> +	};
>> +
>> +- For SD/SDIO slot:
>> +
>> +	sdhci@ab0000 {
>> +		compatible = "marvell,sdhci-xenon";
>> +		reg = <0xab0000 0x1000>;
>> +		interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
>> +		vqmmc-supply = <&sd_regulator>;
>> +		clocks = <&sdclk>;
>> +		clock-names = "core";
>> +		bus-width = <4>;
>> +		tuning-count = <9>;
>> +	};
>> +
>> +- For eMMC slot with compatible "marvell,armada-3700-sdhci":
>> +
>> +	sdhci@aa0000 {
>> +		compatible = "marvell,armada-3700-sdhci";
>> +		reg = <0xaa0000 0x1000>,
>> +		      <phy_addr 0x4>;
>> +		interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
>> +		clocks = <&emmcclk>;
>> +		clock-names = "core";
>> +		bus-width = <8>;
>> +
>> +		xenon,pad-type = "fixed-1-8v";
>> +	};
>> +
>> +- For SD/SDIO slot with compatible "marvell,armada-3700-sdhci":
>> +
>> +	sdhci@ab0000 {
>> +		compatible = "marvell,armada-3700-sdhci";
>> +		reg = <0xab0000 0x1000>,
>> +		      <phy_addr 0x4>;
>> +		interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
>> +		vqmmc-supply = <&sd_regulator>;
>> +		clocks = <&sdclk>;
>> +		clock-names = "core";
>> +		bus-width = <4>;
>> +
>> +		xenon,pad-type = "sd";
>> +	};
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 89adcd57aa25..4aa0eac9bfc7 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -7582,6 +7582,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
>>  M:	Ziji Hu <huziji@marvell.com>
>>  L:	linux-mmc@vger.kernel.org
>>  S:	Supported
>> +F:	Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>>  
>>  MATROX FRAMEBUFFER DRIVER
>>  L:	linux-fbdev@vger.kernel.org
>> -- 
>> git-series 0.8.10

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 5/10] dt: bindings: Add bindings for Marvell Xenon SD Host Controller
@ 2016-10-11 10:03       ` Ziji Hu
  0 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-11 10:03 UTC (permalink / raw)
  To: Rob Herring, Gregory CLEMENT
  Cc: Ulf Hansson, Adrian Hunter, linux-mmc, Jason Cooper, Andrew Lunn,
	Sebastian Hesselbarth, 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

Hi Rob,

	Thanks a for the review.
	It is really helpful to me.

On 2016/10/11 5:34, Rob Herring wrote:
> On Fri, Oct 07, 2016 at 05:22:51PM +0200, Gregory CLEMENT wrote:
>> From: Ziji Hu <huziji@marvell.com>
>>
>> Marvell Xenon SDHC can support eMMC/SD/SDIO.
>> Add Xenon-specific properties.
>> Also add properties for Xenon PHY setting.
>>
>> 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>
>> ---
>>  Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt | 164 +++++++-
>>  MAINTAINERS                                                   |   1 +-
>>  2 files changed, 165 insertions(+), 0 deletions(-)
>>  create mode 100644 Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>>
>> diff --git a/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt b/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>> new file mode 100644
>> index 000000000000..8b25ad28ebbd
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>> @@ -0,0 +1,164 @@
>> +Marvell's Xenon SDHCI Controller device tree bindings
>> +This file documents differences between the core mmc properties
>> +described by mmc.txt and the properties used by the Xenon implementation.
>> +
>> +A single Xenon IP can support multiple slots.
>> +Each slot acts as an independent SDHC. It owns independent resources, such
>> +as register sets clock and PHY.
> 
> Is the phy really part of the same block?
> 
	Each SDHC slot owns its PHY. It is part of a SDHC slot.
	It is independent to another SDHC slot.

>> +Each slot should have an independent device tree node.
>> +
>> +Required Properties:
>> +- compatible: should be "marvell,sdhci-xenon" or "marvell,armada-3700-sdhci".
> 
> Perhaps some consistent ordering (w/ -sdhci on the end).
	Sure.
	I will adjust the ordering.

> 
>> +
>> +- Input Clock Name
> 
> Your formatting of properties is a bit strange. Please restructure like 
> most bindings so the property names are before all the description.
> 
	OK.
	I will fix the format.

>> +  Some SOCs require additional clock for AXI bus.
> 
> Those SoCs should have a specific compatible string and you need to 
> define which compatible strings have 2 clocks vs. 1 clock.
> 
	Actually, I copy this implementation from another Marvell SDIO Host Controller, sdhci-pxa.
	It is in sdhci-pxa.txt.
	I would like to know if it is still acceptable.

>> +  The input clock for Xenon IP core should be named as "core".
>> +  The optional AXI clock should be named as "axi".
>> +  - clocks = <&core_clk>, <&axi_clock>;
>> +  - clock-names = "core", "axi";
>> +
>> +- Register Set Size
> 
> Is this a property name?

	Sorry, it isn't.
	I will fix the format.

> 
>> +  Different Xenon SDHC release has different register set size.
>> +  The specific size should also refer to the SOC implementation.
>> +
>> +Optional Properties:
>> +- Slot Index
>> +  A single Xenon IP can support multiple slots.
>> +  During initialization, each slot should set corresponding setting bit in
>> +  some Xenon-specific registers. The corresponding bit is determined by
>> +  this property.
>> +  - xenon,slotno = <slot_index>;
> 
> Slots should probably be represented as child nodes with the reg 
> property being the slot number.

	Since each SDHC slot is independent, I find it is more convenient to implement each one as independent SD host/MMC host instant.
	Otherwise, a main function should loop and initialize each slot, like sdhci-pci. I prefer to avoiding such a unnecessary main function.

	It is very hard to determine the slot number by reg property.
	Xenon slots are likely to be different types. 1st slot might be eMMC and 2nd one might be SD. They might have different register size.
	The register size might also varies in different Xenon versions.

> 
> Also, xenon is not a vendor prefix.
> 
	Yes. The issue is that there are multiple Marvell SD Host Controllers existing in kernel.
	If marvell is used as a prefix here, I concern that it might be confused with other Marvell sdhc.
	Can I use a combination of marvell and xenon as a prefix, such as mrvl-xenon?

>> +  If this property is not provided, Xenon IP should contain only one slot
>> +  and the slot index will be 0x0 by default.
>> +
>> +- PHY Type
> 
> You're going to need to come of with a common binding for this.
> 
	Could you please provide more details about the "common binding" here?

	The PHY Type property is Xenon-specific, instead of a standard or a spec.
	Thus I cannot find a common property to stand for it.

>> +  Xenon support mutilple types of PHYs.
>> +  To select eMMC 5.1 PHY, set:
>> +  - xenon,phy-type = "emmc 5.1 phy"
>> +  eMMC 5.1 PHY is the default choice if this property is not provided.
>> +  To select eMMC 5.0 PHY, set:
>> +  - xenon,phy-type = "emmc 5.0 phy"
>> +  To select SDH PHY, set:
>> +  - xenon,phy-type = "sdh phy"
>> +  Please note that eMMC PHY is a general PHY for eMMC/SD/SDIO, other than for
>> +  eMMC only.
>> +
>> +- Customized eMMC PHY Parameters
>> +  Some boards require different values of some specific eMMC PHY parameters.
>> +  Some SOCs also require specific workaround to set eMMC PHY.
>> +  These properties enable diverse boards to customize the eMMC PHY.
>> +  The supported eMMC PHY parameters are listed in below. All those properties
>> +  are only available for eMMC PHY 5.1 and eMMC PHY 5.0.
>> +  ZNR
>> +  valid range = [0:0x1F].
>> +  ZNR is set as 0xF by default if this property is not provided.
>> +  - xenon,phy-znr = <value>;
>> +
>> +  ZPR
>> +  valid range = [0:0x1F].
>> +  ZPR is set as 0xF by default if this property is not provided.
>> +  - xenon,phy-zpr = <value>;
> 
> marvell is the vendor prefix.
> 
>> +
>> +  Number of successful tuning times
>> +  Set the number of required consecutive successful sampling points used to
>> +  identify a valid sampling window, in tuning process.
>> +  Valid range = [1:7]. Set as 0x4 by default if this property is not provided.
>> +  - xenon,phy-nr-tun-times = <nr_times>;
>> +
>> +  Divider for TUN_STEP
>> +  Set the divider for calculating TUN_STEP.
>> +  Set as 64 by default if this property is not provided.
>> +  - xenon,phy-tun-step-divider = <divider>;
>> +
>> +  Force PHY into slow mode.
>> +  Only available when bus frequency lower than 50MHz in SDR mde.
>> +  Disabled by default. Please do not enable it unless it is necessary.
>> +  - xenon,phy-slow-mode;
>> +
>> +- Mask Conflict Error Report
>> +  Disable Conflict Error alert on some SOC. Disabled by default.
>> +  xenon,mask-conflict-err;
>> +
>> +- Re-tuning Counter
>> +  Xenon SDHC SOC usually doesn't provide re-tuning counter in
>> +  Capabilities Register 3 Bit[11:8].
>> +  This property provides the re-tuning counter.
>> +  xenon,tuning-count = <count>;
>> +  If this property is not set, default re-tuning counter will
>> +  be set as 0x9 in driver.
>> +
>> +- SOC PHY PAD Voltage Control register
>> +  Some SOCs have SOC PHY PAD Voltage Control register outside Xenon IP.
>> +  This register sets SOC PHY PAD Voltage to keep aligh with Vccq.
>> +  Two properties provide information of this control register.
>> +  These two properties are only valid when "marvell,armada-3700-sdhci"
>> +  is selected. Both of them must be provided when "marvell,armada-3700-sdhci"
>> +  is selected.
>> +  - xenon,pad-type
>> +    Two types: "sd" and "fixed-1-8v".
>> +    If "sd" is slected, SOC PHY PAD is set as 3.3V at the beginning and is
>> +    switched to 1.8V when SD in UHS-I.
>> +    If "fixed-1-8v" is slected, SOC PHY PAD is fixed 1.8V, such as for eMMC.
> 
> You should be able to existing, common properties for i/o voltage 
> capabilities/constraints.
> 
	The above property is for a special SOC platform in Marvell.
	It is irrelevant to common PHY framework or standard MMC bindings.
	Thus I cannot find a existing and common property to represent it.

	Thank you.

Best regards,
Hu Ziji

>> +  - reg
>> +    Physical address and size of SOC PHY PAD register.
>> +    Append after Xenon SDHC register space, as a second register field.
>> +
>> +  Please follow the examples with compatible "marvell,armada-3700-sdhci"
>> +  in below.
>> +
>> +Example:
>> +- For eMMC slot:
>> +
>> +	sdhci@aa0000 {
>> +		compatible = "marvell,sdhci-xenon";
>> +		reg = <0xaa0000 0x1000>;
>> +		interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
>> +		clocks = <&emmcclk>;
>> +		clock-names = "core";
>> +		xenon,slotno = <0>;
>> +		xenon,phy-type = "emmc 5.1 phy";
>> +		bus-width = <8>;
>> +		tuning-count = <11>;
>> +	};
>> +
>> +- For SD/SDIO slot:
>> +
>> +	sdhci@ab0000 {
>> +		compatible = "marvell,sdhci-xenon";
>> +		reg = <0xab0000 0x1000>;
>> +		interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
>> +		vqmmc-supply = <&sd_regulator>;
>> +		clocks = <&sdclk>;
>> +		clock-names = "core";
>> +		bus-width = <4>;
>> +		tuning-count = <9>;
>> +	};
>> +
>> +- For eMMC slot with compatible "marvell,armada-3700-sdhci":
>> +
>> +	sdhci@aa0000 {
>> +		compatible = "marvell,armada-3700-sdhci";
>> +		reg = <0xaa0000 0x1000>,
>> +		      <phy_addr 0x4>;
>> +		interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
>> +		clocks = <&emmcclk>;
>> +		clock-names = "core";
>> +		bus-width = <8>;
>> +
>> +		xenon,pad-type = "fixed-1-8v";
>> +	};
>> +
>> +- For SD/SDIO slot with compatible "marvell,armada-3700-sdhci":
>> +
>> +	sdhci@ab0000 {
>> +		compatible = "marvell,armada-3700-sdhci";
>> +		reg = <0xab0000 0x1000>,
>> +		      <phy_addr 0x4>;
>> +		interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
>> +		vqmmc-supply = <&sd_regulator>;
>> +		clocks = <&sdclk>;
>> +		clock-names = "core";
>> +		bus-width = <4>;
>> +
>> +		xenon,pad-type = "sd";
>> +	};
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 89adcd57aa25..4aa0eac9bfc7 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -7582,6 +7582,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
>>  M:	Ziji Hu <huziji@marvell.com>
>>  L:	linux-mmc@vger.kernel.org
>>  S:	Supported
>> +F:	Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>>  
>>  MATROX FRAMEBUFFER DRIVER
>>  L:	linux-fbdev@vger.kernel.org
>> -- 
>> git-series 0.8.10

^ permalink raw reply	[flat|nested] 105+ messages in thread

* [PATCH 5/10] dt: bindings: Add bindings for Marvell Xenon SD Host Controller
@ 2016-10-11 10:03       ` Ziji Hu
  0 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-11 10:03 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Rob,

	Thanks a for the review.
	It is really helpful to me.

On 2016/10/11 5:34, Rob Herring wrote:
> On Fri, Oct 07, 2016 at 05:22:51PM +0200, Gregory CLEMENT wrote:
>> From: Ziji Hu <huziji@marvell.com>
>>
>> Marvell Xenon SDHC can support eMMC/SD/SDIO.
>> Add Xenon-specific properties.
>> Also add properties for Xenon PHY setting.
>>
>> 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>
>> ---
>>  Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt | 164 +++++++-
>>  MAINTAINERS                                                   |   1 +-
>>  2 files changed, 165 insertions(+), 0 deletions(-)
>>  create mode 100644 Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>>
>> diff --git a/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt b/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>> new file mode 100644
>> index 000000000000..8b25ad28ebbd
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>> @@ -0,0 +1,164 @@
>> +Marvell's Xenon SDHCI Controller device tree bindings
>> +This file documents differences between the core mmc properties
>> +described by mmc.txt and the properties used by the Xenon implementation.
>> +
>> +A single Xenon IP can support multiple slots.
>> +Each slot acts as an independent SDHC. It owns independent resources, such
>> +as register sets clock and PHY.
> 
> Is the phy really part of the same block?
> 
	Each SDHC slot owns its PHY. It is part of a SDHC slot.
	It is independent to another SDHC slot.

>> +Each slot should have an independent device tree node.
>> +
>> +Required Properties:
>> +- compatible: should be "marvell,sdhci-xenon" or "marvell,armada-3700-sdhci".
> 
> Perhaps some consistent ordering (w/ -sdhci on the end).
	Sure.
	I will adjust the ordering.

> 
>> +
>> +- Input Clock Name
> 
> Your formatting of properties is a bit strange. Please restructure like 
> most bindings so the property names are before all the description.
> 
	OK.
	I will fix the format.

>> +  Some SOCs require additional clock for AXI bus.
> 
> Those SoCs should have a specific compatible string and you need to 
> define which compatible strings have 2 clocks vs. 1 clock.
> 
	Actually, I copy this implementation from another Marvell SDIO Host Controller, sdhci-pxa.
	It is in sdhci-pxa.txt.
	I would like to know if it is still acceptable.

>> +  The input clock for Xenon IP core should be named as "core".
>> +  The optional AXI clock should be named as "axi".
>> +  - clocks = <&core_clk>, <&axi_clock>;
>> +  - clock-names = "core", "axi";
>> +
>> +- Register Set Size
> 
> Is this a property name?

	Sorry, it isn't.
	I will fix the format.

> 
>> +  Different Xenon SDHC release has different register set size.
>> +  The specific size should also refer to the SOC implementation.
>> +
>> +Optional Properties:
>> +- Slot Index
>> +  A single Xenon IP can support multiple slots.
>> +  During initialization, each slot should set corresponding setting bit in
>> +  some Xenon-specific registers. The corresponding bit is determined by
>> +  this property.
>> +  - xenon,slotno = <slot_index>;
> 
> Slots should probably be represented as child nodes with the reg 
> property being the slot number.

	Since each SDHC slot is independent, I find it is more convenient to implement each one as independent SD host/MMC host instant.
	Otherwise, a main function should loop and initialize each slot, like sdhci-pci. I prefer to avoiding such a unnecessary main function.

	It is very hard to determine the slot number by reg property.
	Xenon slots are likely to be different types. 1st slot might be eMMC and 2nd one might be SD. They might have different register size.
	The register size might also varies in different Xenon versions.

> 
> Also, xenon is not a vendor prefix.
> 
	Yes. The issue is that there are multiple Marvell SD Host Controllers existing in kernel.
	If marvell is used as a prefix here, I concern that it might be confused with other Marvell sdhc.
	Can I use a combination of marvell and xenon as a prefix, such as mrvl-xenon?

>> +  If this property is not provided, Xenon IP should contain only one slot
>> +  and the slot index will be 0x0 by default.
>> +
>> +- PHY Type
> 
> You're going to need to come of with a common binding for this.
> 
	Could you please provide more details about the "common binding" here?

	The PHY Type property is Xenon-specific, instead of a standard or a spec.
	Thus I cannot find a common property to stand for it.

>> +  Xenon support mutilple types of PHYs.
>> +  To select eMMC 5.1 PHY, set:
>> +  - xenon,phy-type = "emmc 5.1 phy"
>> +  eMMC 5.1 PHY is the default choice if this property is not provided.
>> +  To select eMMC 5.0 PHY, set:
>> +  - xenon,phy-type = "emmc 5.0 phy"
>> +  To select SDH PHY, set:
>> +  - xenon,phy-type = "sdh phy"
>> +  Please note that eMMC PHY is a general PHY for eMMC/SD/SDIO, other than for
>> +  eMMC only.
>> +
>> +- Customized eMMC PHY Parameters
>> +  Some boards require different values of some specific eMMC PHY parameters.
>> +  Some SOCs also require specific workaround to set eMMC PHY.
>> +  These properties enable diverse boards to customize the eMMC PHY.
>> +  The supported eMMC PHY parameters are listed in below. All those properties
>> +  are only available for eMMC PHY 5.1 and eMMC PHY 5.0.
>> +  ZNR
>> +  valid range = [0:0x1F].
>> +  ZNR is set as 0xF by default if this property is not provided.
>> +  - xenon,phy-znr = <value>;
>> +
>> +  ZPR
>> +  valid range = [0:0x1F].
>> +  ZPR is set as 0xF by default if this property is not provided.
>> +  - xenon,phy-zpr = <value>;
> 
> marvell is the vendor prefix.
> 
>> +
>> +  Number of successful tuning times
>> +  Set the number of required consecutive successful sampling points used to
>> +  identify a valid sampling window, in tuning process.
>> +  Valid range = [1:7]. Set as 0x4 by default if this property is not provided.
>> +  - xenon,phy-nr-tun-times = <nr_times>;
>> +
>> +  Divider for TUN_STEP
>> +  Set the divider for calculating TUN_STEP.
>> +  Set as 64 by default if this property is not provided.
>> +  - xenon,phy-tun-step-divider = <divider>;
>> +
>> +  Force PHY into slow mode.
>> +  Only available when bus frequency lower than 50MHz in SDR mde.
>> +  Disabled by default. Please do not enable it unless it is necessary.
>> +  - xenon,phy-slow-mode;
>> +
>> +- Mask Conflict Error Report
>> +  Disable Conflict Error alert on some SOC. Disabled by default.
>> +  xenon,mask-conflict-err;
>> +
>> +- Re-tuning Counter
>> +  Xenon SDHC SOC usually doesn't provide re-tuning counter in
>> +  Capabilities Register 3 Bit[11:8].
>> +  This property provides the re-tuning counter.
>> +  xenon,tuning-count = <count>;
>> +  If this property is not set, default re-tuning counter will
>> +  be set as 0x9 in driver.
>> +
>> +- SOC PHY PAD Voltage Control register
>> +  Some SOCs have SOC PHY PAD Voltage Control register outside Xenon IP.
>> +  This register sets SOC PHY PAD Voltage to keep aligh with Vccq.
>> +  Two properties provide information of this control register.
>> +  These two properties are only valid when "marvell,armada-3700-sdhci"
>> +  is selected. Both of them must be provided when "marvell,armada-3700-sdhci"
>> +  is selected.
>> +  - xenon,pad-type
>> +    Two types: "sd" and "fixed-1-8v".
>> +    If "sd" is slected, SOC PHY PAD is set as 3.3V at the beginning and is
>> +    switched to 1.8V when SD in UHS-I.
>> +    If "fixed-1-8v" is slected, SOC PHY PAD is fixed 1.8V, such as for eMMC.
> 
> You should be able to existing, common properties for i/o voltage 
> capabilities/constraints.
> 
	The above property is for a special SOC platform in Marvell.
	It is irrelevant to common PHY framework or standard MMC bindings.
	Thus I cannot find a existing and common property to represent it.

	Thank you.

Best regards,
Hu Ziji

>> +  - reg
>> +    Physical address and size of SOC PHY PAD register.
>> +    Append after Xenon SDHC register space, as a second register field.
>> +
>> +  Please follow the examples with compatible "marvell,armada-3700-sdhci"
>> +  in below.
>> +
>> +Example:
>> +- For eMMC slot:
>> +
>> +	sdhci at aa0000 {
>> +		compatible = "marvell,sdhci-xenon";
>> +		reg = <0xaa0000 0x1000>;
>> +		interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
>> +		clocks = <&emmcclk>;
>> +		clock-names = "core";
>> +		xenon,slotno = <0>;
>> +		xenon,phy-type = "emmc 5.1 phy";
>> +		bus-width = <8>;
>> +		tuning-count = <11>;
>> +	};
>> +
>> +- For SD/SDIO slot:
>> +
>> +	sdhci at ab0000 {
>> +		compatible = "marvell,sdhci-xenon";
>> +		reg = <0xab0000 0x1000>;
>> +		interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
>> +		vqmmc-supply = <&sd_regulator>;
>> +		clocks = <&sdclk>;
>> +		clock-names = "core";
>> +		bus-width = <4>;
>> +		tuning-count = <9>;
>> +	};
>> +
>> +- For eMMC slot with compatible "marvell,armada-3700-sdhci":
>> +
>> +	sdhci at aa0000 {
>> +		compatible = "marvell,armada-3700-sdhci";
>> +		reg = <0xaa0000 0x1000>,
>> +		      <phy_addr 0x4>;
>> +		interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
>> +		clocks = <&emmcclk>;
>> +		clock-names = "core";
>> +		bus-width = <8>;
>> +
>> +		xenon,pad-type = "fixed-1-8v";
>> +	};
>> +
>> +- For SD/SDIO slot with compatible "marvell,armada-3700-sdhci":
>> +
>> +	sdhci at ab0000 {
>> +		compatible = "marvell,armada-3700-sdhci";
>> +		reg = <0xab0000 0x1000>,
>> +		      <phy_addr 0x4>;
>> +		interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
>> +		vqmmc-supply = <&sd_regulator>;
>> +		clocks = <&sdclk>;
>> +		clock-names = "core";
>> +		bus-width = <4>;
>> +
>> +		xenon,pad-type = "sd";
>> +	};
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 89adcd57aa25..4aa0eac9bfc7 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -7582,6 +7582,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
>>  M:	Ziji Hu <huziji@marvell.com>
>>  L:	linux-mmc at vger.kernel.org
>>  S:	Supported
>> +F:	Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>>  
>>  MATROX FRAMEBUFFER DRIVER
>>  L:	linux-fbdev at vger.kernel.org
>> -- 
>> git-series 0.8.10

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
  2016-10-07 15:22   ` Gregory CLEMENT
  (?)
@ 2016-10-11 12:37     ` Adrian Hunter
  -1 siblings, 0 replies; 105+ messages in thread
From: Adrian Hunter @ 2016-10-11 12:37 UTC (permalink / raw)
  To: Gregory CLEMENT, Ulf Hansson, linux-mmc
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth, Rob Herring,
	devicetree, Thomas Petazzoni, linux-arm-kernel, 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, Peng Zhu,
	Yu Cao, Romain Perier, Yehuda Yitschak, Marcin Wojtas,
	Hanna Hawa, Kostya Porotchkin, linux-kernel

On 07/10/16 18:22, Gregory CLEMENT wrote:
> From: Ziji Hu <huziji@marvell.com>
> 
> Add Xenon eMMC/SD/SDIO host controller core functionality.
> Add Xenon specific intialization process.
> Add Xenon specific mmc_host_ops APIs.
> Add Xenon specific register definitions.
> 
> Add CONFIG_MMC_SDHCI_XENON support in drivers/mmc/host/Kconfig.
> 
> Marvell Xenon SDHC conforms to SD Physical Layer Specification
> Version 3.01 and is designed according to the guidelines provided
> in the SD Host Controller Standard Specification Version 3.00.
> 
> 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>

I looked at a couple of things but you need to sort out the issues with
card_candidate before going further.

> ---
>  MAINTAINERS                    |   1 +-
>  drivers/mmc/host/Kconfig       |   9 +-
>  drivers/mmc/host/Makefile      |   3 +-
>  drivers/mmc/host/sdhci-xenon.c | 599 ++++++++++++++++++++++++++++++++++-
>  drivers/mmc/host/sdhci-xenon.h | 134 ++++++++-
>  5 files changed, 746 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/mmc/host/sdhci-xenon.c
>  create mode 100644 drivers/mmc/host/sdhci-xenon.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 4aa0eac9bfc7..859420e5dfd3 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -7582,6 +7582,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
>  M:	Ziji Hu <huziji@marvell.com>
>  L:	linux-mmc@vger.kernel.org
>  S:	Supported
> +F:	drivers/mmc/host/sdhci-xenon.*
>  F:	Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>  
>  MATROX FRAMEBUFFER DRIVER
> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> index 5274f503a39a..85a53623526a 100644
> --- a/drivers/mmc/host/Kconfig
> +++ b/drivers/mmc/host/Kconfig
> @@ -798,3 +798,12 @@ config MMC_SDHCI_BRCMSTB
>  	  Broadcom STB SoCs.
>  
>  	  If unsure, say Y.
> +
> +config MMC_SDHCI_XENON
> +	tristate "Marvell Xenon eMMC/SD/SDIO SDHCI driver"
> +	depends on MMC_SDHCI && MMC_SDHCI_PLTFM
> +	help
> +	  This selects Marvell Xenon eMMC/SD/SDIO SDHCI.
> +	  If you have a machine with integrated Marvell Xenon SDHC IP,
> +	  say Y or M here.
> +	  If unsure, say N.
> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
> index e2bdaaf43184..75eaf743486c 100644
> --- a/drivers/mmc/host/Makefile
> +++ b/drivers/mmc/host/Makefile
> @@ -80,3 +80,6 @@ obj-$(CONFIG_MMC_SDHCI_BRCMSTB)		+= sdhci-brcmstb.o
>  ifeq ($(CONFIG_CB710_DEBUG),y)
>  	CFLAGS-cb710-mmc	+= -DDEBUG
>  endif
> +
> +obj-$(CONFIG_MMC_SDHCI_XENON)	+= sdhci-xenon-driver.o
> +sdhci-xenon-driver-y		+= sdhci-xenon.o
> diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c
> new file mode 100644
> index 000000000000..03ba183494d3
> --- /dev/null
> +++ b/drivers/mmc/host/sdhci-xenon.c
> @@ -0,0 +1,599 @@
> +/*
> + * Driver for Marvell SOC Platform Group Xenon SDHC as a platform device
> + *
> + * 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.
> + *
> + * Inspired by Jisheng Zhang <jszhang@marvell.com>
> + * Special thanks to Video BG4 project team.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/mmc/host.h>
> +#include <linux/mmc/mmc.h>
> +#include <linux/mmc/sdio.h>
> +#include <linux/mmc/card.h>
> +#include <linux/mmc/host.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +
> +#include "sdhci-pltfm.h"
> +#include "sdhci.h"
> +#include "sdhci-xenon.h"
> +
> +/* Set SDCLK-off-while-idle */
> +static void xenon_set_sdclk_off_idle(struct sdhci_host *host,
> +				     unsigned char slot_idx, bool enable)
> +{
> +	u32 reg;
> +	u32 mask;
> +
> +	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
> +	/* Get the bit shift basing on the slot index */
> +	mask = (0x1 << (SDCLK_IDLEOFF_ENABLE_SHIFT + slot_idx));
> +	if (enable)
> +		reg |= mask;
> +	else
> +		reg &= ~mask;
> +
> +	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
> +}
> +
> +/* Enable/Disable the Auto Clock Gating function */
> +static void xenon_set_acg(struct sdhci_host *host, bool enable)
> +{
> +	u32 reg;
> +
> +	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
> +	if (enable)
> +		reg &= ~AUTO_CLKGATE_DISABLE_MASK;
> +	else
> +		reg |= AUTO_CLKGATE_DISABLE_MASK;
> +	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
> +}
> +
> +/* Enable this slot */
> +static void xenon_enable_slot(struct sdhci_host *host,
> +			      unsigned char slot_idx)
> +{
> +	u32 reg;
> +
> +	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
> +	reg |= (BIT(slot_idx) << SLOT_ENABLE_SHIFT);
> +	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
> +
> +	/*
> +	 * Manually set the flag which all the slots require,
> +	 * including SD, eMMC, SDIO
> +	 */
> +	host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY;
> +}
> +
> +/* Disable this slot */
> +static void xenon_disable_slot(struct sdhci_host *host,
> +			       unsigned char slot_idx)
> +{
> +	u32 reg;
> +
> +	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
> +	reg &= ~(BIT(slot_idx) << SLOT_ENABLE_SHIFT);
> +	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
> +}
> +
> +/* Enable Parallel Transfer Mode */
> +static void xenon_enable_slot_parallel_tran(struct sdhci_host *host,
> +					    unsigned char slot_idx)
> +{
> +	u32 reg;
> +
> +	reg = sdhci_readl(host, SDHC_SYS_EXT_OP_CTRL);
> +	reg |= BIT(slot_idx);
> +	sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
> +}
> +
> +static void xenon_slot_tuning_setup(struct sdhci_host *host)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +	u32 reg;
> +
> +	/* Disable the Re-Tuning Request functionality */
> +	reg = sdhci_readl(host, SDHC_SLOT_RETUNING_REQ_CTRL);
> +	reg &= ~RETUNING_COMPATIBLE;
> +	sdhci_writel(host, reg, SDHC_SLOT_RETUNING_REQ_CTRL);
> +
> +	/* Disbale the Re-tuning Event Signal Enable */

Disbale -> Disable

> +	reg = sdhci_readl(host, SDHCI_SIGNAL_ENABLE);
> +	reg &= ~SDHCI_INT_RETUNE;
> +	sdhci_writel(host, reg, SDHCI_SIGNAL_ENABLE);
> +
> +	/* Force to use Tuning Mode 1 */
> +	host->tuning_mode = SDHCI_TUNING_MODE_1;
> +	/* Set re-tuning period */
> +	host->tuning_count = 1 << (priv->tuning_count - 1);
> +}
> +
> +/*
> + * Operations inside struct sdhci_ops
> + */
> +/* Recover the Register Setting cleared during SOFTWARE_RESET_ALL */
> +static void sdhci_xenon_reset_exit(struct sdhci_host *host,
> +				   unsigned char slot_idx, u8 mask)
> +{
> +	/* Only SOFTWARE RESET ALL will clear the register setting */
> +	if (!(mask & SDHCI_RESET_ALL))
> +		return;
> +
> +	/* Disable tuning request and auto-retuing again */

retuing -> retuning

> +	xenon_slot_tuning_setup(host);
> +
> +	xenon_set_acg(host, true);
> +
> +	xenon_set_sdclk_off_idle(host, slot_idx, false);
> +}
> +
> +static void sdhci_xenon_reset(struct sdhci_host *host, u8 mask)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +
> +	sdhci_reset(host, mask);
> +	sdhci_xenon_reset_exit(host, priv->slot_idx, mask);
> +}
> +
> +/*
> + * Xenon defines different values for HS200 and SDR104
> + * in Host_Control_2
> + */
> +static void xenon_set_uhs_signaling(struct sdhci_host *host,
> +				    unsigned int timing)
> +{
> +	u16 ctrl_2;
> +
> +	ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
> +	/* Select Bus Speed Mode for host */
> +	ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
> +	if (timing == MMC_TIMING_MMC_HS200)
> +		ctrl_2 |= XENON_SDHCI_CTRL_HS200;
> +	else if (timing == MMC_TIMING_UHS_SDR104)
> +		ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
> +	else if (timing == MMC_TIMING_UHS_SDR12)
> +		ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
> +	else if (timing == MMC_TIMING_UHS_SDR25)
> +		ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
> +	else if (timing == MMC_TIMING_UHS_SDR50)
> +		ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
> +	else if ((timing == MMC_TIMING_UHS_DDR50) ||
> +		 (timing == MMC_TIMING_MMC_DDR52))
> +		ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
> +	else if (timing == MMC_TIMING_MMC_HS400)
> +		ctrl_2 |= XENON_SDHCI_CTRL_HS400;
> +	sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
> +}
> +
> +static const struct sdhci_ops sdhci_xenon_ops = {
> +	.set_clock		= sdhci_set_clock,
> +	.set_bus_width		= sdhci_set_bus_width,
> +	.reset			= sdhci_xenon_reset,
> +	.set_uhs_signaling	= xenon_set_uhs_signaling,
> +	.get_max_clock		= sdhci_pltfm_clk_get_max_clock,
> +};
> +
> +static const struct sdhci_pltfm_data sdhci_xenon_pdata = {
> +	.ops = &sdhci_xenon_ops,
> +	.quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC |
> +			SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 |
> +			SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER |
> +			SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
> +};
> +
> +/*
> + * Xenon Specific Operations in mmc_host_ops
> + */
> +static void xenon_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> +{
> +	struct sdhci_host *host = mmc_priv(mmc);
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +	unsigned long flags;
> +
> +	/*
> +	 * HS400/HS200/eMMC HS doesn't have Preset Value register.
> +	 * However, sdhci_set_ios will read HS400/HS200 Preset register.
> +	 * Disable Preset Value register for HS400/HS200.
> +	 * eMMC HS with preset_enabled set will trigger a bug in
> +	 * get_preset_value().
> +	 */
> +	spin_lock_irqsave(&host->lock, flags);
> +	if ((ios->timing == MMC_TIMING_MMC_HS400) ||
> +	    (ios->timing == MMC_TIMING_MMC_HS200) ||
> +	    (ios->timing == MMC_TIMING_MMC_HS)) {
> +		host->preset_enabled = false;
> +		host->quirks2 |= SDHCI_QUIRK2_PRESET_VALUE_BROKEN;
> +	} else {
> +		host->quirks2 &= ~SDHCI_QUIRK2_PRESET_VALUE_BROKEN;
> +	}
> +	spin_unlock_irqrestore(&host->lock, flags);
> +
> +	sdhci_set_ios(mmc, ios);
> +
> +	if (host->clock > DEFAULT_SDCLK_FREQ) {
> +		spin_lock_irqsave(&host->lock, flags);
> +		xenon_set_sdclk_off_idle(host, priv->slot_idx, true);
> +		spin_unlock_irqrestore(&host->lock, flags);
> +	}
> +}
> +
> +static int __emmc_signal_voltage_switch(struct mmc_host *mmc,
> +					const unsigned char signal_voltage)
> +{
> +	u32 ctrl;
> +	unsigned char voltage_code;
> +	struct sdhci_host *host = mmc_priv(mmc);
> +
> +	if (signal_voltage == MMC_SIGNAL_VOLTAGE_330)
> +		voltage_code = EMMC_VCCQ_3_3V;
> +	else if (signal_voltage == MMC_SIGNAL_VOLTAGE_180)
> +		voltage_code = EMMC_VCCQ_1_8V;
> +	else
> +		return -EINVAL;
> +
> +	/*
> +	 * This host is for eMMC, XENON self-defined
> +	 * eMMC slot control register should be accessed
> +	 * instead of Host Control 2
> +	 */
> +	ctrl = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
> +	ctrl &= ~EMMC_VCCQ_MASK;
> +	ctrl |= voltage_code;
> +	sdhci_writel(host, ctrl, SDHC_SLOT_EMMC_CTRL);
> +
> +	/* There is no standard to determine this waiting period */
> +	usleep_range(1000, 2000);
> +
> +	/* Check whether io voltage switch is done */
> +	ctrl = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
> +	ctrl &= EMMC_VCCQ_MASK;
> +	/*
> +	 * This bit is set only when regulator feeds back the voltage switch
> +	 * results to Xenon SDHC.
> +	 * However, in actaul implementation, regulator might not provide
> +	 * this feedback.
> +	 * Thus we shall not rely on this bit to determine if switch failed.
> +	 * If the bit is not set, just throw a message.
> +	 * Besides, error code should not be returned.
> +	 */
> +	if (ctrl != voltage_code)
> +		dev_info(mmc_dev(mmc), "fail to detect eMMC signal voltage stable\n");
> +	return 0;
> +}
> +
> +static int xenon_emmc_signal_voltage_switch(struct mmc_host *mmc,
> +					    struct mmc_ios *ios)
> +{
> +	unsigned char voltage = ios->signal_voltage;
> +
> +	if ((voltage == MMC_SIGNAL_VOLTAGE_330) ||
> +	    (voltage == MMC_SIGNAL_VOLTAGE_180))
> +		return __emmc_signal_voltage_switch(mmc, voltage);
> +
> +	dev_err(mmc_dev(mmc), "Unsupported signal voltage: %d\n",
> +		voltage);
> +	return -EINVAL;
> +}
> +
> +static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
> +					     struct mmc_ios *ios)
> +{
> +	struct sdhci_host *host = mmc_priv(mmc);
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +
> +	/*
> +	 * Before SD/SDIO set signal voltage, SD bus clock should be
> +	 * disabled. However, sdhci_set_clock will also disable the Internal
> +	 * clock in mmc_set_signal_voltage().
> +	 * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
> +	 * Thus here manually enable internal clock.
> +	 *
> +	 * After switch completes, it is unnecessary to disable internal clock,
> +	 * since keeping internal clock active obeys SD spec.
> +	 */
> +	enable_xenon_internal_clk(host);
> +
> +	if (priv->card_candidate) {

mmc_power_up() calls __mmc_set_signal_voltage() calls
host->ops->start_signal_voltage_switch so priv->card_candidate could be an
invalid reference to an old card.

So that's not going to work if the card changes - not only for removable
cards but even for eMMC if init fails and retries.

> +		if (mmc_card_mmc(priv->card_candidate))
> +			return xenon_emmc_signal_voltage_switch(mmc, ios);

So if all you need to know is whether it is a eMMC, why can't DT tell you?

> +	}
> +
> +	return sdhci_start_signal_voltage_switch(mmc, ios);
> +}
> +
> +/*
> + * After determining which slot is used for SDIO,
> + * some additional task is required.
> + */
> +static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
> +{
> +	struct sdhci_host *host = mmc_priv(mmc);
> +	u32 reg;
> +	u8 slot_idx;
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +
> +	/* Link the card for delay adjustment */
> +	priv->card_candidate = card;

You really need a better way to get the card.  I suggest you take up the
issue with Ulf.  One possibility is to have mmc core set host->card = card
much earlier.

> +	/* Set tuning functionality of this slot */
> +	xenon_slot_tuning_setup(host);
> +
> +	slot_idx = priv->slot_idx;
> +	if (!mmc_card_sdio(card)) {
> +		/* Re-enable the Auto-CMD12 cap flag. */
> +		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
> +		host->flags |= SDHCI_AUTO_CMD12;
> +
> +		/* Clear SDIO Card Inserted indication */
> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
> +		reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
> +
> +		if (mmc_card_mmc(card)) {
> +			mmc->caps |= MMC_CAP_NONREMOVABLE;
> +			if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
> +				mmc->caps |= MMC_CAP_1_8V_DDR;
> +			/*
> +			 * Force to clear BUS_TEST to
> +			 * skip bus_test_pre and bus_test_post
> +			 */
> +			mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
> +			mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
> +				      MMC_CAP2_PACKED_CMD;
> +			if (mmc->caps & MMC_CAP_8_BIT_DATA)
> +				mmc->caps2 |= MMC_CAP2_HS400_1_8V;
> +		}
> +	} else {
> +		/*
> +		 * Delete the Auto-CMD12 cap flag.
> +		 * Otherwise, when sending multi-block CMD53,
> +		 * Driver will set Transfer Mode Register to enable Auto CMD12.
> +		 * However, SDIO device cannot recognize CMD12.
> +		 * Thus SDHC will time-out for waiting for CMD12 response.
> +		 */
> +		host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
> +		host->flags &= ~SDHCI_AUTO_CMD12;

sdhci_set_transfer_mode() won't enable auto-CMD12 for CMD53 anyway, so is
this needed?

> +
> +		/*
> +		 * Set SDIO Card Inserted indication
> +		 * to inform that the current slot is for SDIO
> +		 */
> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
> +		reg |= (1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
> +	}
> +}
> +
> +static int xenon_execute_tuning(struct mmc_host *mmc, u32 opcode)
> +{
> +	struct sdhci_host *host = mmc_priv(mmc);
> +
> +	if (host->timing == MMC_TIMING_UHS_DDR50)
> +		return 0;
> +
> +	return sdhci_execute_tuning(mmc, opcode);
> +}
> +
> +static void xenon_replace_mmc_host_ops(struct sdhci_host *host)
> +{
> +	host->mmc_host_ops.set_ios = xenon_set_ios;
> +	host->mmc_host_ops.start_signal_voltage_switch =
> +			xenon_start_signal_voltage_switch;
> +	host->mmc_host_ops.init_card = xenon_init_card;
> +	host->mmc_host_ops.execute_tuning = xenon_execute_tuning;
> +}
> +
> +static int xenon_probe_dt(struct platform_device *pdev)
> +{
> +	struct device_node *np = pdev->dev.of_node;
> +	struct sdhci_host *host = platform_get_drvdata(pdev);
> +	struct mmc_host *mmc = host->mmc;
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +	int err;
> +	u32 slot_idx, nr_slot;
> +	u32 tuning_count;
> +	u32 reg;
> +
> +	/* Standard MMC property */
> +	err = mmc_of_parse(mmc);
> +	if (err)
> +		return err;
> +
> +	/* Standard SDHCI property */
> +	sdhci_get_of_property(pdev);
> +
> +	/*
> +	 * Xenon Specific property:
> +	 * slotno: the index of slot. Refer to SDHC_SYS_CFG_INFO register
> +	 * tuning-count: the interval between re-tuning
> +	 * PHY type: "sdhc phy", "emmc phy 5.0" or "emmc phy 5.1"
> +	 */
> +	if (!of_property_read_u32(np, "xenon,slotno", &slot_idx)) {
> +		nr_slot = sdhci_readl(host, SDHC_SYS_CFG_INFO);
> +		nr_slot &= NR_SUPPORTED_SLOT_MASK;
> +		if (unlikely(slot_idx > nr_slot)) {
> +			dev_err(mmc_dev(mmc), "Slot Index %d exceeds Number of slots %d\n",
> +				slot_idx, nr_slot);
> +			return -EINVAL;
> +		}
> +	} else {
> +		priv->slot_idx = 0x0;
> +	}
> +
> +	if (!of_property_read_u32(np, "xenon,tuning-count", &tuning_count)) {
> +		if (unlikely(tuning_count >= TMR_RETUN_NO_PRESENT)) {
> +			dev_err(mmc_dev(mmc), "Wrong Re-tuning Count. Set default value %d\n",
> +				DEF_TUNING_COUNT);
> +			tuning_count = DEF_TUNING_COUNT;
> +		}
> +	} else {
> +		priv->tuning_count = DEF_TUNING_COUNT;
> +	}
> +
> +	if (of_property_read_bool(np, "xenon,mask-conflict-err")) {
> +		reg = sdhci_readl(host, SDHC_SYS_EXT_OP_CTRL);
> +		reg |= MASK_CMD_CONFLICT_ERROR;
> +		sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
> +	}
> +
> +	return err;
> +}
> +
> +static int xenon_slot_probe(struct sdhci_host *host)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +	u8 slot_idx = priv->slot_idx;
> +
> +	/* Enable slot */
> +	xenon_enable_slot(host, slot_idx);
> +
> +	/* Enable ACG */
> +	xenon_set_acg(host, true);
> +
> +	/* Enable Parallel Transfer Mode */
> +	xenon_enable_slot_parallel_tran(host, slot_idx);
> +
> +	priv->timing = MMC_TIMING_FAKE;
> +	priv->clock = 0;
> +
> +	return 0;
> +}
> +
> +static void xenon_slot_remove(struct sdhci_host *host)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +	u8 slot_idx = priv->slot_idx;
> +
> +	/* disable slot */
> +	xenon_disable_slot(host, slot_idx);
> +}
> +
> +static int sdhci_xenon_probe(struct platform_device *pdev)
> +{
> +	struct sdhci_pltfm_host *pltfm_host;
> +	struct sdhci_host *host;
> +	struct clk *clk, *axi_clk;
> +	struct sdhci_xenon_priv *priv;
> +	int err;
> +
> +	host = sdhci_pltfm_init(pdev, &sdhci_xenon_pdata,
> +				sizeof(struct sdhci_xenon_priv));
> +	if (IS_ERR(host))
> +		return PTR_ERR(host);
> +
> +	pltfm_host = sdhci_priv(host);
> +	priv = sdhci_pltfm_priv(pltfm_host);
> +
> +	xenon_set_acg(host, false);
> +
> +	/*
> +	 * Link Xenon specific mmc_host_ops function,
> +	 * to replace standard ones in sdhci_ops.
> +	 */
> +	xenon_replace_mmc_host_ops(host);
> +
> +	clk = devm_clk_get(&pdev->dev, "core");
> +	if (IS_ERR(clk)) {
> +		dev_err(&pdev->dev, "Failed to setup input clk.\n");
> +		err = PTR_ERR(clk);
> +		goto free_pltfm;
> +	}
> +	clk_prepare_enable(clk);
> +	pltfm_host->clk = clk;
> +
> +	/*
> +	 * Some SOCs require additional clock to
> +	 * manage AXI bus clock.
> +	 * It is optional.
> +	 */
> +	axi_clk = devm_clk_get(&pdev->dev, "axi");
> +	if (!IS_ERR(axi_clk)) {
> +		clk_prepare_enable(axi_clk);
> +		priv->axi_clk = axi_clk;
> +	}
> +
> +	err = xenon_probe_dt(pdev);
> +	if (err)
> +		goto err_clk;
> +
> +	err = xenon_slot_probe(host);
> +	if (err)
> +		goto err_clk;
> +
> +	err = sdhci_add_host(host);
> +	if (err)
> +		goto remove_slot;
> +
> +	return 0;
> +
> +remove_slot:
> +	xenon_slot_remove(host);
> +err_clk:
> +	clk_disable_unprepare(pltfm_host->clk);
> +	if (!IS_ERR(axi_clk))
> +		clk_disable_unprepare(axi_clk);
> +free_pltfm:
> +	sdhci_pltfm_free(pdev);
> +	return err;
> +}
> +
> +static int sdhci_xenon_remove(struct platform_device *pdev)
> +{
> +	struct sdhci_host *host = platform_get_drvdata(pdev);
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +	int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xFFFFFFFF);
> +
> +	xenon_slot_remove(host);
> +
> +	sdhci_remove_host(host, dead);
> +
> +	clk_disable_unprepare(pltfm_host->clk);
> +	clk_disable_unprepare(priv->axi_clk);
> +
> +	sdhci_pltfm_free(pdev);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id sdhci_xenon_dt_ids[] = {
> +	{ .compatible = "marvell,sdhci-xenon",},
> +	{ .compatible = "marvell,armada-3700-sdhci",},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, sdhci_xenon_dt_ids);
> +
> +static struct platform_driver sdhci_xenon_driver = {
> +	.driver	= {
> +		.name	= "sdhci-xenon",
> +		.of_match_table = sdhci_xenon_dt_ids,
> +		.pm = &sdhci_pltfm_pmops,
> +	},
> +	.probe	= sdhci_xenon_probe,
> +	.remove	= sdhci_xenon_remove,
> +};
> +
> +module_platform_driver(sdhci_xenon_driver);
> +
> +MODULE_DESCRIPTION("SDHCI platform driver for Marvell Xenon SDHC");
> +MODULE_AUTHOR("Hu Ziji <huziji@marvell.com>");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h
> new file mode 100644
> index 000000000000..c2370493fbe8
> --- /dev/null
> +++ b/drivers/mmc/host/sdhci-xenon.h
> @@ -0,0 +1,134 @@
> +/*
> + * 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.
> + */
> +#ifndef SDHCI_XENON_H_
> +#define SDHCI_XENON_H_
> +
> +#include <linux/clk.h>
> +#include <linux/mmc/card.h>
> +#include <linux/of.h>
> +#include "sdhci.h"
> +
> +/* Register Offset of SD Host Controller SOCP self-defined register */
> +#define SDHC_SYS_CFG_INFO			0x0104
> +#define SLOT_TYPE_SDIO_SHIFT			24
> +#define SLOT_TYPE_EMMC_MASK			0xFF
> +#define SLOT_TYPE_EMMC_SHIFT			16
> +#define SLOT_TYPE_SD_SDIO_MMC_MASK		0xFF
> +#define SLOT_TYPE_SD_SDIO_MMC_SHIFT		8
> +#define NR_SUPPORTED_SLOT_MASK			0x7
> +
> +#define SDHC_SYS_OP_CTRL			0x0108
> +#define AUTO_CLKGATE_DISABLE_MASK		BIT(20)
> +#define SDCLK_IDLEOFF_ENABLE_SHIFT		8
> +#define SLOT_ENABLE_SHIFT			0
> +
> +#define SDHC_SYS_EXT_OP_CTRL			0x010C
> +#define MASK_CMD_CONFLICT_ERROR			BIT(8)
> +
> +#define SDHC_SLOT_OP_STATUS_CTRL		0x0128
> +#define DELAY_90_DEGREE_MASK_EMMC5		BIT(7)
> +#define DELAY_90_DEGREE_SHIFT_EMMC5		7
> +#define EMMC_5_0_PHY_FIXED_DELAY_MASK		0x7F
> +#define EMMC_PHY_FIXED_DELAY_MASK		0xFF
> +#define EMMC_PHY_FIXED_DELAY_WINDOW_MIN		(EMMC_PHY_FIXED_DELAY_MASK >> 3)
> +#define SDH_PHY_FIXED_DELAY_MASK		0x1FF
> +#define SDH_PHY_FIXED_DELAY_WINDOW_MIN		(SDH_PHY_FIXED_DELAY_MASK >> 4)
> +
> +#define TUN_CONSECUTIVE_TIMES_SHIFT		16
> +#define TUN_CONSECUTIVE_TIMES_MASK		0x7
> +#define TUN_CONSECUTIVE_TIMES			0x4
> +#define TUNING_STEP_SHIFT			12
> +#define TUNING_STEP_MASK			0xF
> +#define TUNING_STEP_DIVIDER			BIT(6)
> +
> +#define FORCE_SEL_INVERSE_CLK_SHIFT		11
> +
> +#define SDHC_SLOT_EMMC_CTRL			0x0130
> +#define ENABLE_DATA_STROBE			BIT(24)
> +#define SET_EMMC_RSTN				BIT(16)
> +#define DISABLE_RD_DATA_CRC			BIT(14)
> +#define DISABLE_CRC_STAT_TOKEN			BIT(13)
> +#define EMMC_VCCQ_MASK				0x3
> +#define EMMC_VCCQ_1_8V				0x1
> +#define EMMC_VCCQ_3_3V				0x3
> +
> +#define SDHC_SLOT_RETUNING_REQ_CTRL		0x0144
> +/* retuning compatible */
> +#define RETUNING_COMPATIBLE			0x1
> +
> +#define SDHC_SLOT_EXT_PRESENT_STATE		0x014C
> +#define LOCK_STATE				0x1
> +
> +#define SDHC_SLOT_DLL_CUR_DLY_VAL		0x0150
> +
> +/* Tuning Parameter */
> +#define TMR_RETUN_NO_PRESENT			0xF
> +#define DEF_TUNING_COUNT			0x9
> +
> +#define MMC_TIMING_FAKE				0xFF
> +
> +#define DEFAULT_SDCLK_FREQ			(400000)
> +
> +/* Xenon specific Mode Select value */
> +#define XENON_SDHCI_CTRL_HS200			0x5
> +#define XENON_SDHCI_CTRL_HS400			0x6
> +
> +struct sdhci_xenon_priv {
> +	/*
> +	 * The bus_width, timing, and clock fields in below
> +	 * record the current setting of Xenon SDHC.
> +	 * Driver will call a Sampling Fixed Delay Adjustment
> +	 * if any setting is changed.
> +	 */
> +	unsigned char	bus_width;
> +	unsigned char	timing;
> +	unsigned char	tuning_count;
> +	unsigned int	clock;
> +	struct clk	*axi_clk;
> +
> +	/* Slot idx */
> +	u8		slot_idx;
> +
> +	/*
> +	 * When initializing card, Xenon has to determine card type and
> +	 * adjust Sampling Fixed delay.
> +	 * However, at that time, card structure is not linked to mmc_host.
> +	 * Thus a card pointer is added here to provide
> +	 * the delay adjustment function with the card structure
> +	 * of the card during initialization
> +	 */
> +	struct mmc_card *card_candidate;
> +};
> +
> +static inline int enable_xenon_internal_clk(struct sdhci_host *host)
> +{
> +	u32 reg;
> +	u8 timeout;
> +
> +	reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
> +	reg |= SDHCI_CLOCK_INT_EN;
> +	sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
> +	/* Wait max 20 ms */
> +	timeout = 20;
> +	while (!((reg = sdhci_readw(host, SDHCI_CLOCK_CONTROL))
> +			& SDHCI_CLOCK_INT_STABLE)) {
> +		if (timeout == 0) {
> +			pr_err("%s: Internal clock never stabilised.\n",
> +			       mmc_hostname(host->mmc));
> +			return -ETIMEDOUT;
> +		}
> +		timeout--;
> +		mdelay(1);
> +	}
> +
> +	return 0;
> +}
> +#endif
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
@ 2016-10-11 12:37     ` Adrian Hunter
  0 siblings, 0 replies; 105+ messages in thread
From: Adrian Hunter @ 2016-10-11 12:37 UTC (permalink / raw)
  To: Gregory CLEMENT, Ulf Hansson, linux-mmc
  Cc: Hilbert Zhang, Andrew Lunn, Romain Perier, Liuliu Zhao, Peng Zhu,
	Nadav Haklai, Ziji Hu, Victor Gu, Doug Jones, Jisheng Zhang,
	Yehuda Yitschak, Marcin Wojtas, Xueping Liu, Shiwu Zhang, Yu Cao,
	Sebastian Hesselbarth, devicetree, Jason Cooper, Hanna Hawa,
	Kostya Porotchkin, Rob Herring, Ryan Gao, Wei(SOCP) Liu,
	linux-arm-kernel, Thomas Petazzoni

On 07/10/16 18:22, Gregory CLEMENT wrote:
> From: Ziji Hu <huziji@marvell.com>
> 
> Add Xenon eMMC/SD/SDIO host controller core functionality.
> Add Xenon specific intialization process.
> Add Xenon specific mmc_host_ops APIs.
> Add Xenon specific register definitions.
> 
> Add CONFIG_MMC_SDHCI_XENON support in drivers/mmc/host/Kconfig.
> 
> Marvell Xenon SDHC conforms to SD Physical Layer Specification
> Version 3.01 and is designed according to the guidelines provided
> in the SD Host Controller Standard Specification Version 3.00.
> 
> 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>

I looked at a couple of things but you need to sort out the issues with
card_candidate before going further.

> ---
>  MAINTAINERS                    |   1 +-
>  drivers/mmc/host/Kconfig       |   9 +-
>  drivers/mmc/host/Makefile      |   3 +-
>  drivers/mmc/host/sdhci-xenon.c | 599 ++++++++++++++++++++++++++++++++++-
>  drivers/mmc/host/sdhci-xenon.h | 134 ++++++++-
>  5 files changed, 746 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/mmc/host/sdhci-xenon.c
>  create mode 100644 drivers/mmc/host/sdhci-xenon.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 4aa0eac9bfc7..859420e5dfd3 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -7582,6 +7582,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
>  M:	Ziji Hu <huziji@marvell.com>
>  L:	linux-mmc@vger.kernel.org
>  S:	Supported
> +F:	drivers/mmc/host/sdhci-xenon.*
>  F:	Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>  
>  MATROX FRAMEBUFFER DRIVER
> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> index 5274f503a39a..85a53623526a 100644
> --- a/drivers/mmc/host/Kconfig
> +++ b/drivers/mmc/host/Kconfig
> @@ -798,3 +798,12 @@ config MMC_SDHCI_BRCMSTB
>  	  Broadcom STB SoCs.
>  
>  	  If unsure, say Y.
> +
> +config MMC_SDHCI_XENON
> +	tristate "Marvell Xenon eMMC/SD/SDIO SDHCI driver"
> +	depends on MMC_SDHCI && MMC_SDHCI_PLTFM
> +	help
> +	  This selects Marvell Xenon eMMC/SD/SDIO SDHCI.
> +	  If you have a machine with integrated Marvell Xenon SDHC IP,
> +	  say Y or M here.
> +	  If unsure, say N.
> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
> index e2bdaaf43184..75eaf743486c 100644
> --- a/drivers/mmc/host/Makefile
> +++ b/drivers/mmc/host/Makefile
> @@ -80,3 +80,6 @@ obj-$(CONFIG_MMC_SDHCI_BRCMSTB)		+= sdhci-brcmstb.o
>  ifeq ($(CONFIG_CB710_DEBUG),y)
>  	CFLAGS-cb710-mmc	+= -DDEBUG
>  endif
> +
> +obj-$(CONFIG_MMC_SDHCI_XENON)	+= sdhci-xenon-driver.o
> +sdhci-xenon-driver-y		+= sdhci-xenon.o
> diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c
> new file mode 100644
> index 000000000000..03ba183494d3
> --- /dev/null
> +++ b/drivers/mmc/host/sdhci-xenon.c
> @@ -0,0 +1,599 @@
> +/*
> + * Driver for Marvell SOC Platform Group Xenon SDHC as a platform device
> + *
> + * 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.
> + *
> + * Inspired by Jisheng Zhang <jszhang@marvell.com>
> + * Special thanks to Video BG4 project team.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/mmc/host.h>
> +#include <linux/mmc/mmc.h>
> +#include <linux/mmc/sdio.h>
> +#include <linux/mmc/card.h>
> +#include <linux/mmc/host.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +
> +#include "sdhci-pltfm.h"
> +#include "sdhci.h"
> +#include "sdhci-xenon.h"
> +
> +/* Set SDCLK-off-while-idle */
> +static void xenon_set_sdclk_off_idle(struct sdhci_host *host,
> +				     unsigned char slot_idx, bool enable)
> +{
> +	u32 reg;
> +	u32 mask;
> +
> +	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
> +	/* Get the bit shift basing on the slot index */
> +	mask = (0x1 << (SDCLK_IDLEOFF_ENABLE_SHIFT + slot_idx));
> +	if (enable)
> +		reg |= mask;
> +	else
> +		reg &= ~mask;
> +
> +	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
> +}
> +
> +/* Enable/Disable the Auto Clock Gating function */
> +static void xenon_set_acg(struct sdhci_host *host, bool enable)
> +{
> +	u32 reg;
> +
> +	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
> +	if (enable)
> +		reg &= ~AUTO_CLKGATE_DISABLE_MASK;
> +	else
> +		reg |= AUTO_CLKGATE_DISABLE_MASK;
> +	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
> +}
> +
> +/* Enable this slot */
> +static void xenon_enable_slot(struct sdhci_host *host,
> +			      unsigned char slot_idx)
> +{
> +	u32 reg;
> +
> +	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
> +	reg |= (BIT(slot_idx) << SLOT_ENABLE_SHIFT);
> +	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
> +
> +	/*
> +	 * Manually set the flag which all the slots require,
> +	 * including SD, eMMC, SDIO
> +	 */
> +	host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY;
> +}
> +
> +/* Disable this slot */
> +static void xenon_disable_slot(struct sdhci_host *host,
> +			       unsigned char slot_idx)
> +{
> +	u32 reg;
> +
> +	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
> +	reg &= ~(BIT(slot_idx) << SLOT_ENABLE_SHIFT);
> +	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
> +}
> +
> +/* Enable Parallel Transfer Mode */
> +static void xenon_enable_slot_parallel_tran(struct sdhci_host *host,
> +					    unsigned char slot_idx)
> +{
> +	u32 reg;
> +
> +	reg = sdhci_readl(host, SDHC_SYS_EXT_OP_CTRL);
> +	reg |= BIT(slot_idx);
> +	sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
> +}
> +
> +static void xenon_slot_tuning_setup(struct sdhci_host *host)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +	u32 reg;
> +
> +	/* Disable the Re-Tuning Request functionality */
> +	reg = sdhci_readl(host, SDHC_SLOT_RETUNING_REQ_CTRL);
> +	reg &= ~RETUNING_COMPATIBLE;
> +	sdhci_writel(host, reg, SDHC_SLOT_RETUNING_REQ_CTRL);
> +
> +	/* Disbale the Re-tuning Event Signal Enable */

Disbale -> Disable

> +	reg = sdhci_readl(host, SDHCI_SIGNAL_ENABLE);
> +	reg &= ~SDHCI_INT_RETUNE;
> +	sdhci_writel(host, reg, SDHCI_SIGNAL_ENABLE);
> +
> +	/* Force to use Tuning Mode 1 */
> +	host->tuning_mode = SDHCI_TUNING_MODE_1;
> +	/* Set re-tuning period */
> +	host->tuning_count = 1 << (priv->tuning_count - 1);
> +}
> +
> +/*
> + * Operations inside struct sdhci_ops
> + */
> +/* Recover the Register Setting cleared during SOFTWARE_RESET_ALL */
> +static void sdhci_xenon_reset_exit(struct sdhci_host *host,
> +				   unsigned char slot_idx, u8 mask)
> +{
> +	/* Only SOFTWARE RESET ALL will clear the register setting */
> +	if (!(mask & SDHCI_RESET_ALL))
> +		return;
> +
> +	/* Disable tuning request and auto-retuing again */

retuing -> retuning

> +	xenon_slot_tuning_setup(host);
> +
> +	xenon_set_acg(host, true);
> +
> +	xenon_set_sdclk_off_idle(host, slot_idx, false);
> +}
> +
> +static void sdhci_xenon_reset(struct sdhci_host *host, u8 mask)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +
> +	sdhci_reset(host, mask);
> +	sdhci_xenon_reset_exit(host, priv->slot_idx, mask);
> +}
> +
> +/*
> + * Xenon defines different values for HS200 and SDR104
> + * in Host_Control_2
> + */
> +static void xenon_set_uhs_signaling(struct sdhci_host *host,
> +				    unsigned int timing)
> +{
> +	u16 ctrl_2;
> +
> +	ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
> +	/* Select Bus Speed Mode for host */
> +	ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
> +	if (timing == MMC_TIMING_MMC_HS200)
> +		ctrl_2 |= XENON_SDHCI_CTRL_HS200;
> +	else if (timing == MMC_TIMING_UHS_SDR104)
> +		ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
> +	else if (timing == MMC_TIMING_UHS_SDR12)
> +		ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
> +	else if (timing == MMC_TIMING_UHS_SDR25)
> +		ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
> +	else if (timing == MMC_TIMING_UHS_SDR50)
> +		ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
> +	else if ((timing == MMC_TIMING_UHS_DDR50) ||
> +		 (timing == MMC_TIMING_MMC_DDR52))
> +		ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
> +	else if (timing == MMC_TIMING_MMC_HS400)
> +		ctrl_2 |= XENON_SDHCI_CTRL_HS400;
> +	sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
> +}
> +
> +static const struct sdhci_ops sdhci_xenon_ops = {
> +	.set_clock		= sdhci_set_clock,
> +	.set_bus_width		= sdhci_set_bus_width,
> +	.reset			= sdhci_xenon_reset,
> +	.set_uhs_signaling	= xenon_set_uhs_signaling,
> +	.get_max_clock		= sdhci_pltfm_clk_get_max_clock,
> +};
> +
> +static const struct sdhci_pltfm_data sdhci_xenon_pdata = {
> +	.ops = &sdhci_xenon_ops,
> +	.quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC |
> +			SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 |
> +			SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER |
> +			SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
> +};
> +
> +/*
> + * Xenon Specific Operations in mmc_host_ops
> + */
> +static void xenon_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> +{
> +	struct sdhci_host *host = mmc_priv(mmc);
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +	unsigned long flags;
> +
> +	/*
> +	 * HS400/HS200/eMMC HS doesn't have Preset Value register.
> +	 * However, sdhci_set_ios will read HS400/HS200 Preset register.
> +	 * Disable Preset Value register for HS400/HS200.
> +	 * eMMC HS with preset_enabled set will trigger a bug in
> +	 * get_preset_value().
> +	 */
> +	spin_lock_irqsave(&host->lock, flags);
> +	if ((ios->timing == MMC_TIMING_MMC_HS400) ||
> +	    (ios->timing == MMC_TIMING_MMC_HS200) ||
> +	    (ios->timing == MMC_TIMING_MMC_HS)) {
> +		host->preset_enabled = false;
> +		host->quirks2 |= SDHCI_QUIRK2_PRESET_VALUE_BROKEN;
> +	} else {
> +		host->quirks2 &= ~SDHCI_QUIRK2_PRESET_VALUE_BROKEN;
> +	}
> +	spin_unlock_irqrestore(&host->lock, flags);
> +
> +	sdhci_set_ios(mmc, ios);
> +
> +	if (host->clock > DEFAULT_SDCLK_FREQ) {
> +		spin_lock_irqsave(&host->lock, flags);
> +		xenon_set_sdclk_off_idle(host, priv->slot_idx, true);
> +		spin_unlock_irqrestore(&host->lock, flags);
> +	}
> +}
> +
> +static int __emmc_signal_voltage_switch(struct mmc_host *mmc,
> +					const unsigned char signal_voltage)
> +{
> +	u32 ctrl;
> +	unsigned char voltage_code;
> +	struct sdhci_host *host = mmc_priv(mmc);
> +
> +	if (signal_voltage == MMC_SIGNAL_VOLTAGE_330)
> +		voltage_code = EMMC_VCCQ_3_3V;
> +	else if (signal_voltage == MMC_SIGNAL_VOLTAGE_180)
> +		voltage_code = EMMC_VCCQ_1_8V;
> +	else
> +		return -EINVAL;
> +
> +	/*
> +	 * This host is for eMMC, XENON self-defined
> +	 * eMMC slot control register should be accessed
> +	 * instead of Host Control 2
> +	 */
> +	ctrl = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
> +	ctrl &= ~EMMC_VCCQ_MASK;
> +	ctrl |= voltage_code;
> +	sdhci_writel(host, ctrl, SDHC_SLOT_EMMC_CTRL);
> +
> +	/* There is no standard to determine this waiting period */
> +	usleep_range(1000, 2000);
> +
> +	/* Check whether io voltage switch is done */
> +	ctrl = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
> +	ctrl &= EMMC_VCCQ_MASK;
> +	/*
> +	 * This bit is set only when regulator feeds back the voltage switch
> +	 * results to Xenon SDHC.
> +	 * However, in actaul implementation, regulator might not provide
> +	 * this feedback.
> +	 * Thus we shall not rely on this bit to determine if switch failed.
> +	 * If the bit is not set, just throw a message.
> +	 * Besides, error code should not be returned.
> +	 */
> +	if (ctrl != voltage_code)
> +		dev_info(mmc_dev(mmc), "fail to detect eMMC signal voltage stable\n");
> +	return 0;
> +}
> +
> +static int xenon_emmc_signal_voltage_switch(struct mmc_host *mmc,
> +					    struct mmc_ios *ios)
> +{
> +	unsigned char voltage = ios->signal_voltage;
> +
> +	if ((voltage == MMC_SIGNAL_VOLTAGE_330) ||
> +	    (voltage == MMC_SIGNAL_VOLTAGE_180))
> +		return __emmc_signal_voltage_switch(mmc, voltage);
> +
> +	dev_err(mmc_dev(mmc), "Unsupported signal voltage: %d\n",
> +		voltage);
> +	return -EINVAL;
> +}
> +
> +static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
> +					     struct mmc_ios *ios)
> +{
> +	struct sdhci_host *host = mmc_priv(mmc);
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +
> +	/*
> +	 * Before SD/SDIO set signal voltage, SD bus clock should be
> +	 * disabled. However, sdhci_set_clock will also disable the Internal
> +	 * clock in mmc_set_signal_voltage().
> +	 * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
> +	 * Thus here manually enable internal clock.
> +	 *
> +	 * After switch completes, it is unnecessary to disable internal clock,
> +	 * since keeping internal clock active obeys SD spec.
> +	 */
> +	enable_xenon_internal_clk(host);
> +
> +	if (priv->card_candidate) {

mmc_power_up() calls __mmc_set_signal_voltage() calls
host->ops->start_signal_voltage_switch so priv->card_candidate could be an
invalid reference to an old card.

So that's not going to work if the card changes - not only for removable
cards but even for eMMC if init fails and retries.

> +		if (mmc_card_mmc(priv->card_candidate))
> +			return xenon_emmc_signal_voltage_switch(mmc, ios);

So if all you need to know is whether it is a eMMC, why can't DT tell you?

> +	}
> +
> +	return sdhci_start_signal_voltage_switch(mmc, ios);
> +}
> +
> +/*
> + * After determining which slot is used for SDIO,
> + * some additional task is required.
> + */
> +static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
> +{
> +	struct sdhci_host *host = mmc_priv(mmc);
> +	u32 reg;
> +	u8 slot_idx;
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +
> +	/* Link the card for delay adjustment */
> +	priv->card_candidate = card;

You really need a better way to get the card.  I suggest you take up the
issue with Ulf.  One possibility is to have mmc core set host->card = card
much earlier.

> +	/* Set tuning functionality of this slot */
> +	xenon_slot_tuning_setup(host);
> +
> +	slot_idx = priv->slot_idx;
> +	if (!mmc_card_sdio(card)) {
> +		/* Re-enable the Auto-CMD12 cap flag. */
> +		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
> +		host->flags |= SDHCI_AUTO_CMD12;
> +
> +		/* Clear SDIO Card Inserted indication */
> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
> +		reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
> +
> +		if (mmc_card_mmc(card)) {
> +			mmc->caps |= MMC_CAP_NONREMOVABLE;
> +			if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
> +				mmc->caps |= MMC_CAP_1_8V_DDR;
> +			/*
> +			 * Force to clear BUS_TEST to
> +			 * skip bus_test_pre and bus_test_post
> +			 */
> +			mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
> +			mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
> +				      MMC_CAP2_PACKED_CMD;
> +			if (mmc->caps & MMC_CAP_8_BIT_DATA)
> +				mmc->caps2 |= MMC_CAP2_HS400_1_8V;
> +		}
> +	} else {
> +		/*
> +		 * Delete the Auto-CMD12 cap flag.
> +		 * Otherwise, when sending multi-block CMD53,
> +		 * Driver will set Transfer Mode Register to enable Auto CMD12.
> +		 * However, SDIO device cannot recognize CMD12.
> +		 * Thus SDHC will time-out for waiting for CMD12 response.
> +		 */
> +		host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
> +		host->flags &= ~SDHCI_AUTO_CMD12;

sdhci_set_transfer_mode() won't enable auto-CMD12 for CMD53 anyway, so is
this needed?

> +
> +		/*
> +		 * Set SDIO Card Inserted indication
> +		 * to inform that the current slot is for SDIO
> +		 */
> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
> +		reg |= (1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
> +	}
> +}
> +
> +static int xenon_execute_tuning(struct mmc_host *mmc, u32 opcode)
> +{
> +	struct sdhci_host *host = mmc_priv(mmc);
> +
> +	if (host->timing == MMC_TIMING_UHS_DDR50)
> +		return 0;
> +
> +	return sdhci_execute_tuning(mmc, opcode);
> +}
> +
> +static void xenon_replace_mmc_host_ops(struct sdhci_host *host)
> +{
> +	host->mmc_host_ops.set_ios = xenon_set_ios;
> +	host->mmc_host_ops.start_signal_voltage_switch =
> +			xenon_start_signal_voltage_switch;
> +	host->mmc_host_ops.init_card = xenon_init_card;
> +	host->mmc_host_ops.execute_tuning = xenon_execute_tuning;
> +}
> +
> +static int xenon_probe_dt(struct platform_device *pdev)
> +{
> +	struct device_node *np = pdev->dev.of_node;
> +	struct sdhci_host *host = platform_get_drvdata(pdev);
> +	struct mmc_host *mmc = host->mmc;
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +	int err;
> +	u32 slot_idx, nr_slot;
> +	u32 tuning_count;
> +	u32 reg;
> +
> +	/* Standard MMC property */
> +	err = mmc_of_parse(mmc);
> +	if (err)
> +		return err;
> +
> +	/* Standard SDHCI property */
> +	sdhci_get_of_property(pdev);
> +
> +	/*
> +	 * Xenon Specific property:
> +	 * slotno: the index of slot. Refer to SDHC_SYS_CFG_INFO register
> +	 * tuning-count: the interval between re-tuning
> +	 * PHY type: "sdhc phy", "emmc phy 5.0" or "emmc phy 5.1"
> +	 */
> +	if (!of_property_read_u32(np, "xenon,slotno", &slot_idx)) {
> +		nr_slot = sdhci_readl(host, SDHC_SYS_CFG_INFO);
> +		nr_slot &= NR_SUPPORTED_SLOT_MASK;
> +		if (unlikely(slot_idx > nr_slot)) {
> +			dev_err(mmc_dev(mmc), "Slot Index %d exceeds Number of slots %d\n",
> +				slot_idx, nr_slot);
> +			return -EINVAL;
> +		}
> +	} else {
> +		priv->slot_idx = 0x0;
> +	}
> +
> +	if (!of_property_read_u32(np, "xenon,tuning-count", &tuning_count)) {
> +		if (unlikely(tuning_count >= TMR_RETUN_NO_PRESENT)) {
> +			dev_err(mmc_dev(mmc), "Wrong Re-tuning Count. Set default value %d\n",
> +				DEF_TUNING_COUNT);
> +			tuning_count = DEF_TUNING_COUNT;
> +		}
> +	} else {
> +		priv->tuning_count = DEF_TUNING_COUNT;
> +	}
> +
> +	if (of_property_read_bool(np, "xenon,mask-conflict-err")) {
> +		reg = sdhci_readl(host, SDHC_SYS_EXT_OP_CTRL);
> +		reg |= MASK_CMD_CONFLICT_ERROR;
> +		sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
> +	}
> +
> +	return err;
> +}
> +
> +static int xenon_slot_probe(struct sdhci_host *host)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +	u8 slot_idx = priv->slot_idx;
> +
> +	/* Enable slot */
> +	xenon_enable_slot(host, slot_idx);
> +
> +	/* Enable ACG */
> +	xenon_set_acg(host, true);
> +
> +	/* Enable Parallel Transfer Mode */
> +	xenon_enable_slot_parallel_tran(host, slot_idx);
> +
> +	priv->timing = MMC_TIMING_FAKE;
> +	priv->clock = 0;
> +
> +	return 0;
> +}
> +
> +static void xenon_slot_remove(struct sdhci_host *host)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +	u8 slot_idx = priv->slot_idx;
> +
> +	/* disable slot */
> +	xenon_disable_slot(host, slot_idx);
> +}
> +
> +static int sdhci_xenon_probe(struct platform_device *pdev)
> +{
> +	struct sdhci_pltfm_host *pltfm_host;
> +	struct sdhci_host *host;
> +	struct clk *clk, *axi_clk;
> +	struct sdhci_xenon_priv *priv;
> +	int err;
> +
> +	host = sdhci_pltfm_init(pdev, &sdhci_xenon_pdata,
> +				sizeof(struct sdhci_xenon_priv));
> +	if (IS_ERR(host))
> +		return PTR_ERR(host);
> +
> +	pltfm_host = sdhci_priv(host);
> +	priv = sdhci_pltfm_priv(pltfm_host);
> +
> +	xenon_set_acg(host, false);
> +
> +	/*
> +	 * Link Xenon specific mmc_host_ops function,
> +	 * to replace standard ones in sdhci_ops.
> +	 */
> +	xenon_replace_mmc_host_ops(host);
> +
> +	clk = devm_clk_get(&pdev->dev, "core");
> +	if (IS_ERR(clk)) {
> +		dev_err(&pdev->dev, "Failed to setup input clk.\n");
> +		err = PTR_ERR(clk);
> +		goto free_pltfm;
> +	}
> +	clk_prepare_enable(clk);
> +	pltfm_host->clk = clk;
> +
> +	/*
> +	 * Some SOCs require additional clock to
> +	 * manage AXI bus clock.
> +	 * It is optional.
> +	 */
> +	axi_clk = devm_clk_get(&pdev->dev, "axi");
> +	if (!IS_ERR(axi_clk)) {
> +		clk_prepare_enable(axi_clk);
> +		priv->axi_clk = axi_clk;
> +	}
> +
> +	err = xenon_probe_dt(pdev);
> +	if (err)
> +		goto err_clk;
> +
> +	err = xenon_slot_probe(host);
> +	if (err)
> +		goto err_clk;
> +
> +	err = sdhci_add_host(host);
> +	if (err)
> +		goto remove_slot;
> +
> +	return 0;
> +
> +remove_slot:
> +	xenon_slot_remove(host);
> +err_clk:
> +	clk_disable_unprepare(pltfm_host->clk);
> +	if (!IS_ERR(axi_clk))
> +		clk_disable_unprepare(axi_clk);
> +free_pltfm:
> +	sdhci_pltfm_free(pdev);
> +	return err;
> +}
> +
> +static int sdhci_xenon_remove(struct platform_device *pdev)
> +{
> +	struct sdhci_host *host = platform_get_drvdata(pdev);
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +	int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xFFFFFFFF);
> +
> +	xenon_slot_remove(host);
> +
> +	sdhci_remove_host(host, dead);
> +
> +	clk_disable_unprepare(pltfm_host->clk);
> +	clk_disable_unprepare(priv->axi_clk);
> +
> +	sdhci_pltfm_free(pdev);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id sdhci_xenon_dt_ids[] = {
> +	{ .compatible = "marvell,sdhci-xenon",},
> +	{ .compatible = "marvell,armada-3700-sdhci",},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, sdhci_xenon_dt_ids);
> +
> +static struct platform_driver sdhci_xenon_driver = {
> +	.driver	= {
> +		.name	= "sdhci-xenon",
> +		.of_match_table = sdhci_xenon_dt_ids,
> +		.pm = &sdhci_pltfm_pmops,
> +	},
> +	.probe	= sdhci_xenon_probe,
> +	.remove	= sdhci_xenon_remove,
> +};
> +
> +module_platform_driver(sdhci_xenon_driver);
> +
> +MODULE_DESCRIPTION("SDHCI platform driver for Marvell Xenon SDHC");
> +MODULE_AUTHOR("Hu Ziji <huziji@marvell.com>");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h
> new file mode 100644
> index 000000000000..c2370493fbe8
> --- /dev/null
> +++ b/drivers/mmc/host/sdhci-xenon.h
> @@ -0,0 +1,134 @@
> +/*
> + * 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.
> + */
> +#ifndef SDHCI_XENON_H_
> +#define SDHCI_XENON_H_
> +
> +#include <linux/clk.h>
> +#include <linux/mmc/card.h>
> +#include <linux/of.h>
> +#include "sdhci.h"
> +
> +/* Register Offset of SD Host Controller SOCP self-defined register */
> +#define SDHC_SYS_CFG_INFO			0x0104
> +#define SLOT_TYPE_SDIO_SHIFT			24
> +#define SLOT_TYPE_EMMC_MASK			0xFF
> +#define SLOT_TYPE_EMMC_SHIFT			16
> +#define SLOT_TYPE_SD_SDIO_MMC_MASK		0xFF
> +#define SLOT_TYPE_SD_SDIO_MMC_SHIFT		8
> +#define NR_SUPPORTED_SLOT_MASK			0x7
> +
> +#define SDHC_SYS_OP_CTRL			0x0108
> +#define AUTO_CLKGATE_DISABLE_MASK		BIT(20)
> +#define SDCLK_IDLEOFF_ENABLE_SHIFT		8
> +#define SLOT_ENABLE_SHIFT			0
> +
> +#define SDHC_SYS_EXT_OP_CTRL			0x010C
> +#define MASK_CMD_CONFLICT_ERROR			BIT(8)
> +
> +#define SDHC_SLOT_OP_STATUS_CTRL		0x0128
> +#define DELAY_90_DEGREE_MASK_EMMC5		BIT(7)
> +#define DELAY_90_DEGREE_SHIFT_EMMC5		7
> +#define EMMC_5_0_PHY_FIXED_DELAY_MASK		0x7F
> +#define EMMC_PHY_FIXED_DELAY_MASK		0xFF
> +#define EMMC_PHY_FIXED_DELAY_WINDOW_MIN		(EMMC_PHY_FIXED_DELAY_MASK >> 3)
> +#define SDH_PHY_FIXED_DELAY_MASK		0x1FF
> +#define SDH_PHY_FIXED_DELAY_WINDOW_MIN		(SDH_PHY_FIXED_DELAY_MASK >> 4)
> +
> +#define TUN_CONSECUTIVE_TIMES_SHIFT		16
> +#define TUN_CONSECUTIVE_TIMES_MASK		0x7
> +#define TUN_CONSECUTIVE_TIMES			0x4
> +#define TUNING_STEP_SHIFT			12
> +#define TUNING_STEP_MASK			0xF
> +#define TUNING_STEP_DIVIDER			BIT(6)
> +
> +#define FORCE_SEL_INVERSE_CLK_SHIFT		11
> +
> +#define SDHC_SLOT_EMMC_CTRL			0x0130
> +#define ENABLE_DATA_STROBE			BIT(24)
> +#define SET_EMMC_RSTN				BIT(16)
> +#define DISABLE_RD_DATA_CRC			BIT(14)
> +#define DISABLE_CRC_STAT_TOKEN			BIT(13)
> +#define EMMC_VCCQ_MASK				0x3
> +#define EMMC_VCCQ_1_8V				0x1
> +#define EMMC_VCCQ_3_3V				0x3
> +
> +#define SDHC_SLOT_RETUNING_REQ_CTRL		0x0144
> +/* retuning compatible */
> +#define RETUNING_COMPATIBLE			0x1
> +
> +#define SDHC_SLOT_EXT_PRESENT_STATE		0x014C
> +#define LOCK_STATE				0x1
> +
> +#define SDHC_SLOT_DLL_CUR_DLY_VAL		0x0150
> +
> +/* Tuning Parameter */
> +#define TMR_RETUN_NO_PRESENT			0xF
> +#define DEF_TUNING_COUNT			0x9
> +
> +#define MMC_TIMING_FAKE				0xFF
> +
> +#define DEFAULT_SDCLK_FREQ			(400000)
> +
> +/* Xenon specific Mode Select value */
> +#define XENON_SDHCI_CTRL_HS200			0x5
> +#define XENON_SDHCI_CTRL_HS400			0x6
> +
> +struct sdhci_xenon_priv {
> +	/*
> +	 * The bus_width, timing, and clock fields in below
> +	 * record the current setting of Xenon SDHC.
> +	 * Driver will call a Sampling Fixed Delay Adjustment
> +	 * if any setting is changed.
> +	 */
> +	unsigned char	bus_width;
> +	unsigned char	timing;
> +	unsigned char	tuning_count;
> +	unsigned int	clock;
> +	struct clk	*axi_clk;
> +
> +	/* Slot idx */
> +	u8		slot_idx;
> +
> +	/*
> +	 * When initializing card, Xenon has to determine card type and
> +	 * adjust Sampling Fixed delay.
> +	 * However, at that time, card structure is not linked to mmc_host.
> +	 * Thus a card pointer is added here to provide
> +	 * the delay adjustment function with the card structure
> +	 * of the card during initialization
> +	 */
> +	struct mmc_card *card_candidate;
> +};
> +
> +static inline int enable_xenon_internal_clk(struct sdhci_host *host)
> +{
> +	u32 reg;
> +	u8 timeout;
> +
> +	reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
> +	reg |= SDHCI_CLOCK_INT_EN;
> +	sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
> +	/* Wait max 20 ms */
> +	timeout = 20;
> +	while (!((reg = sdhci_readw(host, SDHCI_CLOCK_CONTROL))
> +			& SDHCI_CLOCK_INT_STABLE)) {
> +		if (timeout == 0) {
> +			pr_err("%s: Internal clock never stabilised.\n",
> +			       mmc_hostname(host->mmc));
> +			return -ETIMEDOUT;
> +		}
> +		timeout--;
> +		mdelay(1);
> +	}
> +
> +	return 0;
> +}
> +#endif
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
@ 2016-10-11 12:37     ` Adrian Hunter
  0 siblings, 0 replies; 105+ messages in thread
From: Adrian Hunter @ 2016-10-11 12:37 UTC (permalink / raw)
  To: linux-arm-kernel

On 07/10/16 18:22, Gregory CLEMENT wrote:
> From: Ziji Hu <huziji@marvell.com>
> 
> Add Xenon eMMC/SD/SDIO host controller core functionality.
> Add Xenon specific intialization process.
> Add Xenon specific mmc_host_ops APIs.
> Add Xenon specific register definitions.
> 
> Add CONFIG_MMC_SDHCI_XENON support in drivers/mmc/host/Kconfig.
> 
> Marvell Xenon SDHC conforms to SD Physical Layer Specification
> Version 3.01 and is designed according to the guidelines provided
> in the SD Host Controller Standard Specification Version 3.00.
> 
> 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>

I looked at a couple of things but you need to sort out the issues with
card_candidate before going further.

> ---
>  MAINTAINERS                    |   1 +-
>  drivers/mmc/host/Kconfig       |   9 +-
>  drivers/mmc/host/Makefile      |   3 +-
>  drivers/mmc/host/sdhci-xenon.c | 599 ++++++++++++++++++++++++++++++++++-
>  drivers/mmc/host/sdhci-xenon.h | 134 ++++++++-
>  5 files changed, 746 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/mmc/host/sdhci-xenon.c
>  create mode 100644 drivers/mmc/host/sdhci-xenon.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 4aa0eac9bfc7..859420e5dfd3 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -7582,6 +7582,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
>  M:	Ziji Hu <huziji@marvell.com>
>  L:	linux-mmc at vger.kernel.org
>  S:	Supported
> +F:	drivers/mmc/host/sdhci-xenon.*
>  F:	Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>  
>  MATROX FRAMEBUFFER DRIVER
> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> index 5274f503a39a..85a53623526a 100644
> --- a/drivers/mmc/host/Kconfig
> +++ b/drivers/mmc/host/Kconfig
> @@ -798,3 +798,12 @@ config MMC_SDHCI_BRCMSTB
>  	  Broadcom STB SoCs.
>  
>  	  If unsure, say Y.
> +
> +config MMC_SDHCI_XENON
> +	tristate "Marvell Xenon eMMC/SD/SDIO SDHCI driver"
> +	depends on MMC_SDHCI && MMC_SDHCI_PLTFM
> +	help
> +	  This selects Marvell Xenon eMMC/SD/SDIO SDHCI.
> +	  If you have a machine with integrated Marvell Xenon SDHC IP,
> +	  say Y or M here.
> +	  If unsure, say N.
> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
> index e2bdaaf43184..75eaf743486c 100644
> --- a/drivers/mmc/host/Makefile
> +++ b/drivers/mmc/host/Makefile
> @@ -80,3 +80,6 @@ obj-$(CONFIG_MMC_SDHCI_BRCMSTB)		+= sdhci-brcmstb.o
>  ifeq ($(CONFIG_CB710_DEBUG),y)
>  	CFLAGS-cb710-mmc	+= -DDEBUG
>  endif
> +
> +obj-$(CONFIG_MMC_SDHCI_XENON)	+= sdhci-xenon-driver.o
> +sdhci-xenon-driver-y		+= sdhci-xenon.o
> diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c
> new file mode 100644
> index 000000000000..03ba183494d3
> --- /dev/null
> +++ b/drivers/mmc/host/sdhci-xenon.c
> @@ -0,0 +1,599 @@
> +/*
> + * Driver for Marvell SOC Platform Group Xenon SDHC as a platform device
> + *
> + * 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.
> + *
> + * Inspired by Jisheng Zhang <jszhang@marvell.com>
> + * Special thanks to Video BG4 project team.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/mmc/host.h>
> +#include <linux/mmc/mmc.h>
> +#include <linux/mmc/sdio.h>
> +#include <linux/mmc/card.h>
> +#include <linux/mmc/host.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +
> +#include "sdhci-pltfm.h"
> +#include "sdhci.h"
> +#include "sdhci-xenon.h"
> +
> +/* Set SDCLK-off-while-idle */
> +static void xenon_set_sdclk_off_idle(struct sdhci_host *host,
> +				     unsigned char slot_idx, bool enable)
> +{
> +	u32 reg;
> +	u32 mask;
> +
> +	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
> +	/* Get the bit shift basing on the slot index */
> +	mask = (0x1 << (SDCLK_IDLEOFF_ENABLE_SHIFT + slot_idx));
> +	if (enable)
> +		reg |= mask;
> +	else
> +		reg &= ~mask;
> +
> +	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
> +}
> +
> +/* Enable/Disable the Auto Clock Gating function */
> +static void xenon_set_acg(struct sdhci_host *host, bool enable)
> +{
> +	u32 reg;
> +
> +	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
> +	if (enable)
> +		reg &= ~AUTO_CLKGATE_DISABLE_MASK;
> +	else
> +		reg |= AUTO_CLKGATE_DISABLE_MASK;
> +	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
> +}
> +
> +/* Enable this slot */
> +static void xenon_enable_slot(struct sdhci_host *host,
> +			      unsigned char slot_idx)
> +{
> +	u32 reg;
> +
> +	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
> +	reg |= (BIT(slot_idx) << SLOT_ENABLE_SHIFT);
> +	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
> +
> +	/*
> +	 * Manually set the flag which all the slots require,
> +	 * including SD, eMMC, SDIO
> +	 */
> +	host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY;
> +}
> +
> +/* Disable this slot */
> +static void xenon_disable_slot(struct sdhci_host *host,
> +			       unsigned char slot_idx)
> +{
> +	u32 reg;
> +
> +	reg = sdhci_readl(host, SDHC_SYS_OP_CTRL);
> +	reg &= ~(BIT(slot_idx) << SLOT_ENABLE_SHIFT);
> +	sdhci_writel(host, reg, SDHC_SYS_OP_CTRL);
> +}
> +
> +/* Enable Parallel Transfer Mode */
> +static void xenon_enable_slot_parallel_tran(struct sdhci_host *host,
> +					    unsigned char slot_idx)
> +{
> +	u32 reg;
> +
> +	reg = sdhci_readl(host, SDHC_SYS_EXT_OP_CTRL);
> +	reg |= BIT(slot_idx);
> +	sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
> +}
> +
> +static void xenon_slot_tuning_setup(struct sdhci_host *host)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +	u32 reg;
> +
> +	/* Disable the Re-Tuning Request functionality */
> +	reg = sdhci_readl(host, SDHC_SLOT_RETUNING_REQ_CTRL);
> +	reg &= ~RETUNING_COMPATIBLE;
> +	sdhci_writel(host, reg, SDHC_SLOT_RETUNING_REQ_CTRL);
> +
> +	/* Disbale the Re-tuning Event Signal Enable */

Disbale -> Disable

> +	reg = sdhci_readl(host, SDHCI_SIGNAL_ENABLE);
> +	reg &= ~SDHCI_INT_RETUNE;
> +	sdhci_writel(host, reg, SDHCI_SIGNAL_ENABLE);
> +
> +	/* Force to use Tuning Mode 1 */
> +	host->tuning_mode = SDHCI_TUNING_MODE_1;
> +	/* Set re-tuning period */
> +	host->tuning_count = 1 << (priv->tuning_count - 1);
> +}
> +
> +/*
> + * Operations inside struct sdhci_ops
> + */
> +/* Recover the Register Setting cleared during SOFTWARE_RESET_ALL */
> +static void sdhci_xenon_reset_exit(struct sdhci_host *host,
> +				   unsigned char slot_idx, u8 mask)
> +{
> +	/* Only SOFTWARE RESET ALL will clear the register setting */
> +	if (!(mask & SDHCI_RESET_ALL))
> +		return;
> +
> +	/* Disable tuning request and auto-retuing again */

retuing -> retuning

> +	xenon_slot_tuning_setup(host);
> +
> +	xenon_set_acg(host, true);
> +
> +	xenon_set_sdclk_off_idle(host, slot_idx, false);
> +}
> +
> +static void sdhci_xenon_reset(struct sdhci_host *host, u8 mask)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +
> +	sdhci_reset(host, mask);
> +	sdhci_xenon_reset_exit(host, priv->slot_idx, mask);
> +}
> +
> +/*
> + * Xenon defines different values for HS200 and SDR104
> + * in Host_Control_2
> + */
> +static void xenon_set_uhs_signaling(struct sdhci_host *host,
> +				    unsigned int timing)
> +{
> +	u16 ctrl_2;
> +
> +	ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
> +	/* Select Bus Speed Mode for host */
> +	ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
> +	if (timing == MMC_TIMING_MMC_HS200)
> +		ctrl_2 |= XENON_SDHCI_CTRL_HS200;
> +	else if (timing == MMC_TIMING_UHS_SDR104)
> +		ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
> +	else if (timing == MMC_TIMING_UHS_SDR12)
> +		ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
> +	else if (timing == MMC_TIMING_UHS_SDR25)
> +		ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
> +	else if (timing == MMC_TIMING_UHS_SDR50)
> +		ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
> +	else if ((timing == MMC_TIMING_UHS_DDR50) ||
> +		 (timing == MMC_TIMING_MMC_DDR52))
> +		ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
> +	else if (timing == MMC_TIMING_MMC_HS400)
> +		ctrl_2 |= XENON_SDHCI_CTRL_HS400;
> +	sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
> +}
> +
> +static const struct sdhci_ops sdhci_xenon_ops = {
> +	.set_clock		= sdhci_set_clock,
> +	.set_bus_width		= sdhci_set_bus_width,
> +	.reset			= sdhci_xenon_reset,
> +	.set_uhs_signaling	= xenon_set_uhs_signaling,
> +	.get_max_clock		= sdhci_pltfm_clk_get_max_clock,
> +};
> +
> +static const struct sdhci_pltfm_data sdhci_xenon_pdata = {
> +	.ops = &sdhci_xenon_ops,
> +	.quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC |
> +			SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 |
> +			SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER |
> +			SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
> +};
> +
> +/*
> + * Xenon Specific Operations in mmc_host_ops
> + */
> +static void xenon_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> +{
> +	struct sdhci_host *host = mmc_priv(mmc);
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +	unsigned long flags;
> +
> +	/*
> +	 * HS400/HS200/eMMC HS doesn't have Preset Value register.
> +	 * However, sdhci_set_ios will read HS400/HS200 Preset register.
> +	 * Disable Preset Value register for HS400/HS200.
> +	 * eMMC HS with preset_enabled set will trigger a bug in
> +	 * get_preset_value().
> +	 */
> +	spin_lock_irqsave(&host->lock, flags);
> +	if ((ios->timing == MMC_TIMING_MMC_HS400) ||
> +	    (ios->timing == MMC_TIMING_MMC_HS200) ||
> +	    (ios->timing == MMC_TIMING_MMC_HS)) {
> +		host->preset_enabled = false;
> +		host->quirks2 |= SDHCI_QUIRK2_PRESET_VALUE_BROKEN;
> +	} else {
> +		host->quirks2 &= ~SDHCI_QUIRK2_PRESET_VALUE_BROKEN;
> +	}
> +	spin_unlock_irqrestore(&host->lock, flags);
> +
> +	sdhci_set_ios(mmc, ios);
> +
> +	if (host->clock > DEFAULT_SDCLK_FREQ) {
> +		spin_lock_irqsave(&host->lock, flags);
> +		xenon_set_sdclk_off_idle(host, priv->slot_idx, true);
> +		spin_unlock_irqrestore(&host->lock, flags);
> +	}
> +}
> +
> +static int __emmc_signal_voltage_switch(struct mmc_host *mmc,
> +					const unsigned char signal_voltage)
> +{
> +	u32 ctrl;
> +	unsigned char voltage_code;
> +	struct sdhci_host *host = mmc_priv(mmc);
> +
> +	if (signal_voltage == MMC_SIGNAL_VOLTAGE_330)
> +		voltage_code = EMMC_VCCQ_3_3V;
> +	else if (signal_voltage == MMC_SIGNAL_VOLTAGE_180)
> +		voltage_code = EMMC_VCCQ_1_8V;
> +	else
> +		return -EINVAL;
> +
> +	/*
> +	 * This host is for eMMC, XENON self-defined
> +	 * eMMC slot control register should be accessed
> +	 * instead of Host Control 2
> +	 */
> +	ctrl = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
> +	ctrl &= ~EMMC_VCCQ_MASK;
> +	ctrl |= voltage_code;
> +	sdhci_writel(host, ctrl, SDHC_SLOT_EMMC_CTRL);
> +
> +	/* There is no standard to determine this waiting period */
> +	usleep_range(1000, 2000);
> +
> +	/* Check whether io voltage switch is done */
> +	ctrl = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
> +	ctrl &= EMMC_VCCQ_MASK;
> +	/*
> +	 * This bit is set only when regulator feeds back the voltage switch
> +	 * results to Xenon SDHC.
> +	 * However, in actaul implementation, regulator might not provide
> +	 * this feedback.
> +	 * Thus we shall not rely on this bit to determine if switch failed.
> +	 * If the bit is not set, just throw a message.
> +	 * Besides, error code should not be returned.
> +	 */
> +	if (ctrl != voltage_code)
> +		dev_info(mmc_dev(mmc), "fail to detect eMMC signal voltage stable\n");
> +	return 0;
> +}
> +
> +static int xenon_emmc_signal_voltage_switch(struct mmc_host *mmc,
> +					    struct mmc_ios *ios)
> +{
> +	unsigned char voltage = ios->signal_voltage;
> +
> +	if ((voltage == MMC_SIGNAL_VOLTAGE_330) ||
> +	    (voltage == MMC_SIGNAL_VOLTAGE_180))
> +		return __emmc_signal_voltage_switch(mmc, voltage);
> +
> +	dev_err(mmc_dev(mmc), "Unsupported signal voltage: %d\n",
> +		voltage);
> +	return -EINVAL;
> +}
> +
> +static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
> +					     struct mmc_ios *ios)
> +{
> +	struct sdhci_host *host = mmc_priv(mmc);
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +
> +	/*
> +	 * Before SD/SDIO set signal voltage, SD bus clock should be
> +	 * disabled. However, sdhci_set_clock will also disable the Internal
> +	 * clock in mmc_set_signal_voltage().
> +	 * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
> +	 * Thus here manually enable internal clock.
> +	 *
> +	 * After switch completes, it is unnecessary to disable internal clock,
> +	 * since keeping internal clock active obeys SD spec.
> +	 */
> +	enable_xenon_internal_clk(host);
> +
> +	if (priv->card_candidate) {

mmc_power_up() calls __mmc_set_signal_voltage() calls
host->ops->start_signal_voltage_switch so priv->card_candidate could be an
invalid reference to an old card.

So that's not going to work if the card changes - not only for removable
cards but even for eMMC if init fails and retries.

> +		if (mmc_card_mmc(priv->card_candidate))
> +			return xenon_emmc_signal_voltage_switch(mmc, ios);

So if all you need to know is whether it is a eMMC, why can't DT tell you?

> +	}
> +
> +	return sdhci_start_signal_voltage_switch(mmc, ios);
> +}
> +
> +/*
> + * After determining which slot is used for SDIO,
> + * some additional task is required.
> + */
> +static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
> +{
> +	struct sdhci_host *host = mmc_priv(mmc);
> +	u32 reg;
> +	u8 slot_idx;
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +
> +	/* Link the card for delay adjustment */
> +	priv->card_candidate = card;

You really need a better way to get the card.  I suggest you take up the
issue with Ulf.  One possibility is to have mmc core set host->card = card
much earlier.

> +	/* Set tuning functionality of this slot */
> +	xenon_slot_tuning_setup(host);
> +
> +	slot_idx = priv->slot_idx;
> +	if (!mmc_card_sdio(card)) {
> +		/* Re-enable the Auto-CMD12 cap flag. */
> +		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
> +		host->flags |= SDHCI_AUTO_CMD12;
> +
> +		/* Clear SDIO Card Inserted indication */
> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
> +		reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
> +
> +		if (mmc_card_mmc(card)) {
> +			mmc->caps |= MMC_CAP_NONREMOVABLE;
> +			if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
> +				mmc->caps |= MMC_CAP_1_8V_DDR;
> +			/*
> +			 * Force to clear BUS_TEST to
> +			 * skip bus_test_pre and bus_test_post
> +			 */
> +			mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
> +			mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
> +				      MMC_CAP2_PACKED_CMD;
> +			if (mmc->caps & MMC_CAP_8_BIT_DATA)
> +				mmc->caps2 |= MMC_CAP2_HS400_1_8V;
> +		}
> +	} else {
> +		/*
> +		 * Delete the Auto-CMD12 cap flag.
> +		 * Otherwise, when sending multi-block CMD53,
> +		 * Driver will set Transfer Mode Register to enable Auto CMD12.
> +		 * However, SDIO device cannot recognize CMD12.
> +		 * Thus SDHC will time-out for waiting for CMD12 response.
> +		 */
> +		host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
> +		host->flags &= ~SDHCI_AUTO_CMD12;

sdhci_set_transfer_mode() won't enable auto-CMD12 for CMD53 anyway, so is
this needed?

> +
> +		/*
> +		 * Set SDIO Card Inserted indication
> +		 * to inform that the current slot is for SDIO
> +		 */
> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
> +		reg |= (1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
> +	}
> +}
> +
> +static int xenon_execute_tuning(struct mmc_host *mmc, u32 opcode)
> +{
> +	struct sdhci_host *host = mmc_priv(mmc);
> +
> +	if (host->timing == MMC_TIMING_UHS_DDR50)
> +		return 0;
> +
> +	return sdhci_execute_tuning(mmc, opcode);
> +}
> +
> +static void xenon_replace_mmc_host_ops(struct sdhci_host *host)
> +{
> +	host->mmc_host_ops.set_ios = xenon_set_ios;
> +	host->mmc_host_ops.start_signal_voltage_switch =
> +			xenon_start_signal_voltage_switch;
> +	host->mmc_host_ops.init_card = xenon_init_card;
> +	host->mmc_host_ops.execute_tuning = xenon_execute_tuning;
> +}
> +
> +static int xenon_probe_dt(struct platform_device *pdev)
> +{
> +	struct device_node *np = pdev->dev.of_node;
> +	struct sdhci_host *host = platform_get_drvdata(pdev);
> +	struct mmc_host *mmc = host->mmc;
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +	int err;
> +	u32 slot_idx, nr_slot;
> +	u32 tuning_count;
> +	u32 reg;
> +
> +	/* Standard MMC property */
> +	err = mmc_of_parse(mmc);
> +	if (err)
> +		return err;
> +
> +	/* Standard SDHCI property */
> +	sdhci_get_of_property(pdev);
> +
> +	/*
> +	 * Xenon Specific property:
> +	 * slotno: the index of slot. Refer to SDHC_SYS_CFG_INFO register
> +	 * tuning-count: the interval between re-tuning
> +	 * PHY type: "sdhc phy", "emmc phy 5.0" or "emmc phy 5.1"
> +	 */
> +	if (!of_property_read_u32(np, "xenon,slotno", &slot_idx)) {
> +		nr_slot = sdhci_readl(host, SDHC_SYS_CFG_INFO);
> +		nr_slot &= NR_SUPPORTED_SLOT_MASK;
> +		if (unlikely(slot_idx > nr_slot)) {
> +			dev_err(mmc_dev(mmc), "Slot Index %d exceeds Number of slots %d\n",
> +				slot_idx, nr_slot);
> +			return -EINVAL;
> +		}
> +	} else {
> +		priv->slot_idx = 0x0;
> +	}
> +
> +	if (!of_property_read_u32(np, "xenon,tuning-count", &tuning_count)) {
> +		if (unlikely(tuning_count >= TMR_RETUN_NO_PRESENT)) {
> +			dev_err(mmc_dev(mmc), "Wrong Re-tuning Count. Set default value %d\n",
> +				DEF_TUNING_COUNT);
> +			tuning_count = DEF_TUNING_COUNT;
> +		}
> +	} else {
> +		priv->tuning_count = DEF_TUNING_COUNT;
> +	}
> +
> +	if (of_property_read_bool(np, "xenon,mask-conflict-err")) {
> +		reg = sdhci_readl(host, SDHC_SYS_EXT_OP_CTRL);
> +		reg |= MASK_CMD_CONFLICT_ERROR;
> +		sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
> +	}
> +
> +	return err;
> +}
> +
> +static int xenon_slot_probe(struct sdhci_host *host)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +	u8 slot_idx = priv->slot_idx;
> +
> +	/* Enable slot */
> +	xenon_enable_slot(host, slot_idx);
> +
> +	/* Enable ACG */
> +	xenon_set_acg(host, true);
> +
> +	/* Enable Parallel Transfer Mode */
> +	xenon_enable_slot_parallel_tran(host, slot_idx);
> +
> +	priv->timing = MMC_TIMING_FAKE;
> +	priv->clock = 0;
> +
> +	return 0;
> +}
> +
> +static void xenon_slot_remove(struct sdhci_host *host)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +	u8 slot_idx = priv->slot_idx;
> +
> +	/* disable slot */
> +	xenon_disable_slot(host, slot_idx);
> +}
> +
> +static int sdhci_xenon_probe(struct platform_device *pdev)
> +{
> +	struct sdhci_pltfm_host *pltfm_host;
> +	struct sdhci_host *host;
> +	struct clk *clk, *axi_clk;
> +	struct sdhci_xenon_priv *priv;
> +	int err;
> +
> +	host = sdhci_pltfm_init(pdev, &sdhci_xenon_pdata,
> +				sizeof(struct sdhci_xenon_priv));
> +	if (IS_ERR(host))
> +		return PTR_ERR(host);
> +
> +	pltfm_host = sdhci_priv(host);
> +	priv = sdhci_pltfm_priv(pltfm_host);
> +
> +	xenon_set_acg(host, false);
> +
> +	/*
> +	 * Link Xenon specific mmc_host_ops function,
> +	 * to replace standard ones in sdhci_ops.
> +	 */
> +	xenon_replace_mmc_host_ops(host);
> +
> +	clk = devm_clk_get(&pdev->dev, "core");
> +	if (IS_ERR(clk)) {
> +		dev_err(&pdev->dev, "Failed to setup input clk.\n");
> +		err = PTR_ERR(clk);
> +		goto free_pltfm;
> +	}
> +	clk_prepare_enable(clk);
> +	pltfm_host->clk = clk;
> +
> +	/*
> +	 * Some SOCs require additional clock to
> +	 * manage AXI bus clock.
> +	 * It is optional.
> +	 */
> +	axi_clk = devm_clk_get(&pdev->dev, "axi");
> +	if (!IS_ERR(axi_clk)) {
> +		clk_prepare_enable(axi_clk);
> +		priv->axi_clk = axi_clk;
> +	}
> +
> +	err = xenon_probe_dt(pdev);
> +	if (err)
> +		goto err_clk;
> +
> +	err = xenon_slot_probe(host);
> +	if (err)
> +		goto err_clk;
> +
> +	err = sdhci_add_host(host);
> +	if (err)
> +		goto remove_slot;
> +
> +	return 0;
> +
> +remove_slot:
> +	xenon_slot_remove(host);
> +err_clk:
> +	clk_disable_unprepare(pltfm_host->clk);
> +	if (!IS_ERR(axi_clk))
> +		clk_disable_unprepare(axi_clk);
> +free_pltfm:
> +	sdhci_pltfm_free(pdev);
> +	return err;
> +}
> +
> +static int sdhci_xenon_remove(struct platform_device *pdev)
> +{
> +	struct sdhci_host *host = platform_get_drvdata(pdev);
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +	int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xFFFFFFFF);
> +
> +	xenon_slot_remove(host);
> +
> +	sdhci_remove_host(host, dead);
> +
> +	clk_disable_unprepare(pltfm_host->clk);
> +	clk_disable_unprepare(priv->axi_clk);
> +
> +	sdhci_pltfm_free(pdev);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id sdhci_xenon_dt_ids[] = {
> +	{ .compatible = "marvell,sdhci-xenon",},
> +	{ .compatible = "marvell,armada-3700-sdhci",},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, sdhci_xenon_dt_ids);
> +
> +static struct platform_driver sdhci_xenon_driver = {
> +	.driver	= {
> +		.name	= "sdhci-xenon",
> +		.of_match_table = sdhci_xenon_dt_ids,
> +		.pm = &sdhci_pltfm_pmops,
> +	},
> +	.probe	= sdhci_xenon_probe,
> +	.remove	= sdhci_xenon_remove,
> +};
> +
> +module_platform_driver(sdhci_xenon_driver);
> +
> +MODULE_DESCRIPTION("SDHCI platform driver for Marvell Xenon SDHC");
> +MODULE_AUTHOR("Hu Ziji <huziji@marvell.com>");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h
> new file mode 100644
> index 000000000000..c2370493fbe8
> --- /dev/null
> +++ b/drivers/mmc/host/sdhci-xenon.h
> @@ -0,0 +1,134 @@
> +/*
> + * 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.
> + */
> +#ifndef SDHCI_XENON_H_
> +#define SDHCI_XENON_H_
> +
> +#include <linux/clk.h>
> +#include <linux/mmc/card.h>
> +#include <linux/of.h>
> +#include "sdhci.h"
> +
> +/* Register Offset of SD Host Controller SOCP self-defined register */
> +#define SDHC_SYS_CFG_INFO			0x0104
> +#define SLOT_TYPE_SDIO_SHIFT			24
> +#define SLOT_TYPE_EMMC_MASK			0xFF
> +#define SLOT_TYPE_EMMC_SHIFT			16
> +#define SLOT_TYPE_SD_SDIO_MMC_MASK		0xFF
> +#define SLOT_TYPE_SD_SDIO_MMC_SHIFT		8
> +#define NR_SUPPORTED_SLOT_MASK			0x7
> +
> +#define SDHC_SYS_OP_CTRL			0x0108
> +#define AUTO_CLKGATE_DISABLE_MASK		BIT(20)
> +#define SDCLK_IDLEOFF_ENABLE_SHIFT		8
> +#define SLOT_ENABLE_SHIFT			0
> +
> +#define SDHC_SYS_EXT_OP_CTRL			0x010C
> +#define MASK_CMD_CONFLICT_ERROR			BIT(8)
> +
> +#define SDHC_SLOT_OP_STATUS_CTRL		0x0128
> +#define DELAY_90_DEGREE_MASK_EMMC5		BIT(7)
> +#define DELAY_90_DEGREE_SHIFT_EMMC5		7
> +#define EMMC_5_0_PHY_FIXED_DELAY_MASK		0x7F
> +#define EMMC_PHY_FIXED_DELAY_MASK		0xFF
> +#define EMMC_PHY_FIXED_DELAY_WINDOW_MIN		(EMMC_PHY_FIXED_DELAY_MASK >> 3)
> +#define SDH_PHY_FIXED_DELAY_MASK		0x1FF
> +#define SDH_PHY_FIXED_DELAY_WINDOW_MIN		(SDH_PHY_FIXED_DELAY_MASK >> 4)
> +
> +#define TUN_CONSECUTIVE_TIMES_SHIFT		16
> +#define TUN_CONSECUTIVE_TIMES_MASK		0x7
> +#define TUN_CONSECUTIVE_TIMES			0x4
> +#define TUNING_STEP_SHIFT			12
> +#define TUNING_STEP_MASK			0xF
> +#define TUNING_STEP_DIVIDER			BIT(6)
> +
> +#define FORCE_SEL_INVERSE_CLK_SHIFT		11
> +
> +#define SDHC_SLOT_EMMC_CTRL			0x0130
> +#define ENABLE_DATA_STROBE			BIT(24)
> +#define SET_EMMC_RSTN				BIT(16)
> +#define DISABLE_RD_DATA_CRC			BIT(14)
> +#define DISABLE_CRC_STAT_TOKEN			BIT(13)
> +#define EMMC_VCCQ_MASK				0x3
> +#define EMMC_VCCQ_1_8V				0x1
> +#define EMMC_VCCQ_3_3V				0x3
> +
> +#define SDHC_SLOT_RETUNING_REQ_CTRL		0x0144
> +/* retuning compatible */
> +#define RETUNING_COMPATIBLE			0x1
> +
> +#define SDHC_SLOT_EXT_PRESENT_STATE		0x014C
> +#define LOCK_STATE				0x1
> +
> +#define SDHC_SLOT_DLL_CUR_DLY_VAL		0x0150
> +
> +/* Tuning Parameter */
> +#define TMR_RETUN_NO_PRESENT			0xF
> +#define DEF_TUNING_COUNT			0x9
> +
> +#define MMC_TIMING_FAKE				0xFF
> +
> +#define DEFAULT_SDCLK_FREQ			(400000)
> +
> +/* Xenon specific Mode Select value */
> +#define XENON_SDHCI_CTRL_HS200			0x5
> +#define XENON_SDHCI_CTRL_HS400			0x6
> +
> +struct sdhci_xenon_priv {
> +	/*
> +	 * The bus_width, timing, and clock fields in below
> +	 * record the current setting of Xenon SDHC.
> +	 * Driver will call a Sampling Fixed Delay Adjustment
> +	 * if any setting is changed.
> +	 */
> +	unsigned char	bus_width;
> +	unsigned char	timing;
> +	unsigned char	tuning_count;
> +	unsigned int	clock;
> +	struct clk	*axi_clk;
> +
> +	/* Slot idx */
> +	u8		slot_idx;
> +
> +	/*
> +	 * When initializing card, Xenon has to determine card type and
> +	 * adjust Sampling Fixed delay.
> +	 * However, at that time, card structure is not linked to mmc_host.
> +	 * Thus a card pointer is added here to provide
> +	 * the delay adjustment function with the card structure
> +	 * of the card during initialization
> +	 */
> +	struct mmc_card *card_candidate;
> +};
> +
> +static inline int enable_xenon_internal_clk(struct sdhci_host *host)
> +{
> +	u32 reg;
> +	u8 timeout;
> +
> +	reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
> +	reg |= SDHCI_CLOCK_INT_EN;
> +	sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
> +	/* Wait max 20 ms */
> +	timeout = 20;
> +	while (!((reg = sdhci_readw(host, SDHCI_CLOCK_CONTROL))
> +			& SDHCI_CLOCK_INT_STABLE)) {
> +		if (timeout == 0) {
> +			pr_err("%s: Internal clock never stabilised.\n",
> +			       mmc_hostname(host->mmc));
> +			return -ETIMEDOUT;
> +		}
> +		timeout--;
> +		mdelay(1);
> +	}
> +
> +	return 0;
> +}
> +#endif
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
  2016-10-07 15:22   ` Gregory CLEMENT
  (?)
@ 2016-10-11 12:39     ` Adrian Hunter
  -1 siblings, 0 replies; 105+ messages in thread
From: Adrian Hunter @ 2016-10-11 12:39 UTC (permalink / raw)
  To: Gregory CLEMENT, Ulf Hansson, linux-mmc
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth, Rob Herring,
	devicetree, Thomas Petazzoni, linux-arm-kernel, 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, Peng Zhu,
	Yu Cao, Romain Perier, Yehuda Yitschak, Marcin Wojtas,
	Hanna Hawa, Kostya Porotchkin, linux-kernel

On 07/10/16 18:22, Gregory CLEMENT wrote:
> 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.*
>  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

<SNIP>

> +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;
> +}

So you are issuing commands from the ->set_ios() callback.  I would want to
get Ulf's OK for that before going further.

One thing: you will need to ensure you don't trigger get HS400 re-tuning
because it will call back into ->set_ios().

And you have the problem that you need to get a reference to the card before
the card device has been added.  As I wrote in response to the previous
patch, you should get Ulf's help with that too.

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
@ 2016-10-11 12:39     ` Adrian Hunter
  0 siblings, 0 replies; 105+ messages in thread
From: Adrian Hunter @ 2016-10-11 12:39 UTC (permalink / raw)
  To: Gregory CLEMENT, Ulf Hansson, linux-mmc
  Cc: Hilbert Zhang, Andrew Lunn, Romain Perier, Liuliu Zhao, Peng Zhu,
	Nadav Haklai, Ziji Hu, Victor Gu, Doug Jones, Jisheng Zhang,
	Yehuda Yitschak, Marcin Wojtas, Xueping Liu, Shiwu Zhang, Yu Cao,
	Sebastian Hesselbarth, devicetree, Jason Cooper, Hanna Hawa,
	Kostya Porotchkin, Rob Herring, Ryan Gao, Wei(SOCP) Liu,
	linux-arm-kernel, Thomas Petazzoni

On 07/10/16 18:22, Gregory CLEMENT wrote:
> 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.*
>  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

<SNIP>

> +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;
> +}

So you are issuing commands from the ->set_ios() callback.  I would want to
get Ulf's OK for that before going further.

One thing: you will need to ensure you don't trigger get HS400 re-tuning
because it will call back into ->set_ios().

And you have the problem that you need to get a reference to the card before
the card device has been added.  As I wrote in response to the previous
patch, you should get Ulf's help with that too.

^ permalink raw reply	[flat|nested] 105+ messages in thread

* [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
@ 2016-10-11 12:39     ` Adrian Hunter
  0 siblings, 0 replies; 105+ messages in thread
From: Adrian Hunter @ 2016-10-11 12:39 UTC (permalink / raw)
  To: linux-arm-kernel

On 07/10/16 18:22, Gregory CLEMENT wrote:
> 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 at vger.kernel.org
>  S:	Supported
>  F:	drivers/mmc/host/sdhci-xenon.*
> +F:	drivers/mmc/host/sdhci-xenon-phy.*
>  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

<SNIP>

> +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;
> +}

So you are issuing commands from the ->set_ios() callback.  I would want to
get Ulf's OK for that before going further.

One thing: you will need to ensure you don't trigger get HS400 re-tuning
because it will call back into ->set_ios().

And you have the problem that you need to get a reference to the card before
the card device has been added.  As I wrote in response to the previous
patch, you should get Ulf's help with that too.

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
  2016-10-11 12:37     ` Adrian Hunter
  (?)
@ 2016-10-12 11:58       ` Ziji Hu
  -1 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-12 11:58 UTC (permalink / raw)
  To: Adrian Hunter, Gregory CLEMENT, Ulf Hansson, 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, Keji Zhang, Liuliu Zhao, Peng Zhu, Yu Cao,
	Romain Perier, Yehuda Yitschak, Marcin Wojtas, Hanna Hawa,
	Kostya Porotchkin, linux-kernel

Hi Adrian,

	Thank you very much for your review.
	I will firstly fix the typo.

On 2016/10/11 20:37, Adrian Hunter wrote:
> On 07/10/16 18:22, Gregory CLEMENT wrote:
>> From: Ziji Hu <huziji@marvell.com>
>>
>> Add Xenon eMMC/SD/SDIO host controller core functionality.
>> Add Xenon specific intialization process.
>> Add Xenon specific mmc_host_ops APIs.
>> Add Xenon specific register definitions.
>>
>> Add CONFIG_MMC_SDHCI_XENON support in drivers/mmc/host/Kconfig.
>>
>> Marvell Xenon SDHC conforms to SD Physical Layer Specification
>> Version 3.01 and is designed according to the guidelines provided
>> in the SD Host Controller Standard Specification Version 3.00.
>>
>> 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>
> 
> I looked at a couple of things but you need to sort out the issues with
> card_candidate before going further.
> 
	Understood.
	I will improve the card_candidate. Please help check the details in below.

>> ---
<snip>
>> +
>> +static int xenon_emmc_signal_voltage_switch(struct mmc_host *mmc,
>> +					    struct mmc_ios *ios)
>> +{
>> +	unsigned char voltage = ios->signal_voltage;
>> +
>> +	if ((voltage == MMC_SIGNAL_VOLTAGE_330) ||
>> +	    (voltage == MMC_SIGNAL_VOLTAGE_180))
>> +		return __emmc_signal_voltage_switch(mmc, voltage);
>> +
>> +	dev_err(mmc_dev(mmc), "Unsupported signal voltage: %d\n",
>> +		voltage);
>> +	return -EINVAL;
>> +}
>> +
>> +static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
>> +					     struct mmc_ios *ios)
>> +{
>> +	struct sdhci_host *host = mmc_priv(mmc);
>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +
>> +	/*
>> +	 * Before SD/SDIO set signal voltage, SD bus clock should be
>> +	 * disabled. However, sdhci_set_clock will also disable the Internal
>> +	 * clock in mmc_set_signal_voltage().
>> +	 * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
>> +	 * Thus here manually enable internal clock.
>> +	 *
>> +	 * After switch completes, it is unnecessary to disable internal clock,
>> +	 * since keeping internal clock active obeys SD spec.
>> +	 */
>> +	enable_xenon_internal_clk(host);
>> +
>> +	if (priv->card_candidate) {
> 
> mmc_power_up() calls __mmc_set_signal_voltage() calls
> host->ops->start_signal_voltage_switch so priv->card_candidate could be an
> invalid reference to an old card.
> 
> So that's not going to work if the card changes - not only for removable
> cards but even for eMMC if init fails and retries.
> 
	As you point out, this piece of code have defects, even though it actually works on Marvell multiple platforms, unless eMMC card is removable.

	I can add a property to explicitly indicate eMMC type in DTS.
	Then card_candidate access can be removed here.
	Does it sounds more reasonable to you?

>> +		if (mmc_card_mmc(priv->card_candidate))
>> +			return xenon_emmc_signal_voltage_switch(mmc, ios);
> 
> So if all you need to know is whether it is a eMMC, why can't DT tell you?
> 
	I can add an eMMC type property in DTS, to remove the card_candidate access here.

>> +	}
>> +
>> +	return sdhci_start_signal_voltage_switch(mmc, ios);
>> +}
>> +
>> +/*
>> + * After determining which slot is used for SDIO,
>> + * some additional task is required.
>> + */
>> +static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
>> +{
>> +	struct sdhci_host *host = mmc_priv(mmc);
>> +	u32 reg;
>> +	u8 slot_idx;
>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +
>> +	/* Link the card for delay adjustment */
>> +	priv->card_candidate = card;
> 
> You really need a better way to get the card.  I suggest you take up the
> issue with Ulf.  One possibility is to have mmc core set host->card = card
> much earlier.
> 
	Could you please tell me if any issue related to card_candidate still exists, after the card_candidate is removed from xenon_start_signal_voltage_switch() in above?
	It seems that when init_card is called, the structure card has already been updated and stable in MMC/SD/SDIO initialization sequence.
	May I keep it here?

>> +	/* Set tuning functionality of this slot */
>> +	xenon_slot_tuning_setup(host);
>> +
>> +	slot_idx = priv->slot_idx;
>> +	if (!mmc_card_sdio(card)) {
>> +		/* Re-enable the Auto-CMD12 cap flag. */
>> +		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>> +		host->flags |= SDHCI_AUTO_CMD12;
>> +
>> +		/* Clear SDIO Card Inserted indication */
>> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
>> +		reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
>> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
>> +
>> +		if (mmc_card_mmc(card)) {
>> +			mmc->caps |= MMC_CAP_NONREMOVABLE;
>> +			if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
>> +				mmc->caps |= MMC_CAP_1_8V_DDR;
>> +			/*
>> +			 * Force to clear BUS_TEST to
>> +			 * skip bus_test_pre and bus_test_post
>> +			 */
>> +			mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
>> +			mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
>> +				      MMC_CAP2_PACKED_CMD;
>> +			if (mmc->caps & MMC_CAP_8_BIT_DATA)
>> +				mmc->caps2 |= MMC_CAP2_HS400_1_8V;
>> +		}
>> +	} else {
>> +		/*
>> +		 * Delete the Auto-CMD12 cap flag.
>> +		 * Otherwise, when sending multi-block CMD53,
>> +		 * Driver will set Transfer Mode Register to enable Auto CMD12.
>> +		 * However, SDIO device cannot recognize CMD12.
>> +		 * Thus SDHC will time-out for waiting for CMD12 response.
>> +		 */
>> +		host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>> +		host->flags &= ~SDHCI_AUTO_CMD12;
> 
> sdhci_set_transfer_mode() won't enable auto-CMD12 for CMD53 anyway, so is
> this needed?
> 
	In Xenon driver, Auto-CMD12 flag is set to enable full support to Auto-CMD feature, both Auto-CMD12 and Auto-CMD23.
	As a result, when Xenon SDHC slot can both support SD and SDIO, Auto-CMD12 is disabled when SDIO card is inserted, and renabled when SD is inserted.

	I recheck the sdhci code to set Auto-CMD bit in Transfer Mode register, in sdhci_set_transfer_mode():
	if (mmc_op_multi(cmd->opcode) || data->blocks > 1)
	As you can see, as long as it is CMD18/CMD25 OR there are multiple data blocks, Auto-CMD field will be set.
	CMD53 doesn't have CMD23. Thus Auto-CMD12 is selected since Auto-CMD12 flag is set.
	Thus I have to clear Auto-CMD12 to avoid issuing Auto-CMD12 in SDIO transfer.

	I just meet a similar issue in RPMB.
	When Auto-CMD12 flag is set, eMMC RPMB access will trigger Auto-CMD12, since CMD25 is in use.
	It will cause RPMB access failed.

	One possible solution is to drop Auto-CMD12 support and use Auto-CMD23 only, in Xenon driver.
	May I know you opinion, please?

>> +
>> +		/*
>> +		 * Set SDIO Card Inserted indication
>> +		 * to inform that the current slot is for SDIO
>> +		 */
>> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
>> +		reg |= (1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
>> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
>> +	}
>> +}
>> +
>> +static int xenon_execute_tuning(struct mmc_host *mmc, u32 opcode)
>> +{
>> +	struct sdhci_host *host = mmc_priv(mmc);
>> +
>> +	if (host->timing == MMC_TIMING_UHS_DDR50)
>> +		return 0;
>> +
>> +	return sdhci_execute_tuning(mmc, opcode);
>> +}
>> +
>> +static void xenon_replace_mmc_host_ops(struct sdhci_host *host)
>> +{
>> +	host->mmc_host_ops.set_ios = xenon_set_ios;
>> +	host->mmc_host_ops.start_signal_voltage_switch =
>> +			xenon_start_signal_voltage_switch;
>> +	host->mmc_host_ops.init_card = xenon_init_card;
>> +	host->mmc_host_ops.execute_tuning = xenon_execute_tuning;
>> +}
>> +
>> +static int xenon_probe_dt(struct platform_device *pdev)
>> +{
>> +	struct device_node *np = pdev->dev.of_node;
>> +	struct sdhci_host *host = platform_get_drvdata(pdev);
>> +	struct mmc_host *mmc = host->mmc;
>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +	int err;
>> +	u32 slot_idx, nr_slot;
>> +	u32 tuning_count;
>> +	u32 reg;
>> +
>> +	/* Standard MMC property */
>> +	err = mmc_of_parse(mmc);
>> +	if (err)
>> +		return err;
>> +
>> +	/* Standard SDHCI property */
>> +	sdhci_get_of_property(pdev);
>> +
>> +	/*
>> +	 * Xenon Specific property:
>> +	 * slotno: the index of slot. Refer to SDHC_SYS_CFG_INFO register
>> +	 * tuning-count: the interval between re-tuning
>> +	 * PHY type: "sdhc phy", "emmc phy 5.0" or "emmc phy 5.1"
>> +	 */
>> +	if (!of_property_read_u32(np, "xenon,slotno", &slot_idx)) {
>> +		nr_slot = sdhci_readl(host, SDHC_SYS_CFG_INFO);
>> +		nr_slot &= NR_SUPPORTED_SLOT_MASK;
>> +		if (unlikely(slot_idx > nr_slot)) {
>> +			dev_err(mmc_dev(mmc), "Slot Index %d exceeds Number of slots %d\n",
>> +				slot_idx, nr_slot);
>> +			return -EINVAL;
>> +		}
>> +	} else {
>> +		priv->slot_idx = 0x0;
>> +	}
>> +
>> +	if (!of_property_read_u32(np, "xenon,tuning-count", &tuning_count)) {
>> +		if (unlikely(tuning_count >= TMR_RETUN_NO_PRESENT)) {
>> +			dev_err(mmc_dev(mmc), "Wrong Re-tuning Count. Set default value %d\n",
>> +				DEF_TUNING_COUNT);
>> +			tuning_count = DEF_TUNING_COUNT;
>> +		}
>> +	} else {
>> +		priv->tuning_count = DEF_TUNING_COUNT;
>> +	}
>> +
>> +	if (of_property_read_bool(np, "xenon,mask-conflict-err")) {
>> +		reg = sdhci_readl(host, SDHC_SYS_EXT_OP_CTRL);
>> +		reg |= MASK_CMD_CONFLICT_ERROR;
>> +		sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
>> +	}
>> +
>> +	return err;
>> +}
>> +
>> +static int xenon_slot_probe(struct sdhci_host *host)
>> +{
>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +	u8 slot_idx = priv->slot_idx;
>> +
>> +	/* Enable slot */
>> +	xenon_enable_slot(host, slot_idx);
>> +
>> +	/* Enable ACG */
>> +	xenon_set_acg(host, true);
>> +
>> +	/* Enable Parallel Transfer Mode */
>> +	xenon_enable_slot_parallel_tran(host, slot_idx);
>> +
>> +	priv->timing = MMC_TIMING_FAKE;
>> +	priv->clock = 0;
>> +
>> +	return 0;
>> +}
>> +
>> +static void xenon_slot_remove(struct sdhci_host *host)
>> +{
>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +	u8 slot_idx = priv->slot_idx;
>> +
>> +	/* disable slot */
>> +	xenon_disable_slot(host, slot_idx);
>> +}
>> +
>> +static int sdhci_xenon_probe(struct platform_device *pdev)
>> +{
>> +	struct sdhci_pltfm_host *pltfm_host;
>> +	struct sdhci_host *host;
>> +	struct clk *clk, *axi_clk;
>> +	struct sdhci_xenon_priv *priv;
>> +	int err;
>> +
>> +	host = sdhci_pltfm_init(pdev, &sdhci_xenon_pdata,
>> +				sizeof(struct sdhci_xenon_priv));
>> +	if (IS_ERR(host))
>> +		return PTR_ERR(host);
>> +
>> +	pltfm_host = sdhci_priv(host);
>> +	priv = sdhci_pltfm_priv(pltfm_host);
>> +
>> +	xenon_set_acg(host, false);
>> +
>> +	/*
>> +	 * Link Xenon specific mmc_host_ops function,
>> +	 * to replace standard ones in sdhci_ops.
>> +	 */
>> +	xenon_replace_mmc_host_ops(host);
>> +
>> +	clk = devm_clk_get(&pdev->dev, "core");
>> +	if (IS_ERR(clk)) {
>> +		dev_err(&pdev->dev, "Failed to setup input clk.\n");
>> +		err = PTR_ERR(clk);
>> +		goto free_pltfm;
>> +	}
>> +	clk_prepare_enable(clk);
>> +	pltfm_host->clk = clk;
>> +
>> +	/*
>> +	 * Some SOCs require additional clock to
>> +	 * manage AXI bus clock.
>> +	 * It is optional.
>> +	 */
>> +	axi_clk = devm_clk_get(&pdev->dev, "axi");
>> +	if (!IS_ERR(axi_clk)) {
>> +		clk_prepare_enable(axi_clk);
>> +		priv->axi_clk = axi_clk;
>> +	}
>> +
>> +	err = xenon_probe_dt(pdev);
>> +	if (err)
>> +		goto err_clk;
>> +
>> +	err = xenon_slot_probe(host);
>> +	if (err)
>> +		goto err_clk;
>> +
>> +	err = sdhci_add_host(host);
>> +	if (err)
>> +		goto remove_slot;
>> +
>> +	return 0;
>> +
>> +remove_slot:
>> +	xenon_slot_remove(host);
>> +err_clk:
>> +	clk_disable_unprepare(pltfm_host->clk);
>> +	if (!IS_ERR(axi_clk))
>> +		clk_disable_unprepare(axi_clk);
>> +free_pltfm:
>> +	sdhci_pltfm_free(pdev);
>> +	return err;
>> +}
>> +
>> +static int sdhci_xenon_remove(struct platform_device *pdev)
>> +{
>> +	struct sdhci_host *host = platform_get_drvdata(pdev);
>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +	int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xFFFFFFFF);
>> +
>> +	xenon_slot_remove(host);
>> +
>> +	sdhci_remove_host(host, dead);
>> +
>> +	clk_disable_unprepare(pltfm_host->clk);
>> +	clk_disable_unprepare(priv->axi_clk);
>> +
>> +	sdhci_pltfm_free(pdev);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id sdhci_xenon_dt_ids[] = {
>> +	{ .compatible = "marvell,sdhci-xenon",},
>> +	{ .compatible = "marvell,armada-3700-sdhci",},
>> +	{}
>> +};
>> +MODULE_DEVICE_TABLE(of, sdhci_xenon_dt_ids);
>> +
>> +static struct platform_driver sdhci_xenon_driver = {
>> +	.driver	= {
>> +		.name	= "sdhci-xenon",
>> +		.of_match_table = sdhci_xenon_dt_ids,
>> +		.pm = &sdhci_pltfm_pmops,
>> +	},
>> +	.probe	= sdhci_xenon_probe,
>> +	.remove	= sdhci_xenon_remove,
>> +};
>> +
>> +module_platform_driver(sdhci_xenon_driver);
>> +
>> +MODULE_DESCRIPTION("SDHCI platform driver for Marvell Xenon SDHC");
>> +MODULE_AUTHOR("Hu Ziji <huziji@marvell.com>");
>> +MODULE_LICENSE("GPL v2");
>> diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h
>> new file mode 100644
>> index 000000000000..c2370493fbe8
>> --- /dev/null
>> +++ b/drivers/mmc/host/sdhci-xenon.h
>> @@ -0,0 +1,134 @@
>> +/*
>> + * 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.
>> + */
>> +#ifndef SDHCI_XENON_H_
>> +#define SDHCI_XENON_H_
>> +
>> +#include <linux/clk.h>
>> +#include <linux/mmc/card.h>
>> +#include <linux/of.h>
>> +#include "sdhci.h"
>> +
>> +/* Register Offset of SD Host Controller SOCP self-defined register */
>> +#define SDHC_SYS_CFG_INFO			0x0104
>> +#define SLOT_TYPE_SDIO_SHIFT			24
>> +#define SLOT_TYPE_EMMC_MASK			0xFF
>> +#define SLOT_TYPE_EMMC_SHIFT			16
>> +#define SLOT_TYPE_SD_SDIO_MMC_MASK		0xFF
>> +#define SLOT_TYPE_SD_SDIO_MMC_SHIFT		8
>> +#define NR_SUPPORTED_SLOT_MASK			0x7
>> +
>> +#define SDHC_SYS_OP_CTRL			0x0108
>> +#define AUTO_CLKGATE_DISABLE_MASK		BIT(20)
>> +#define SDCLK_IDLEOFF_ENABLE_SHIFT		8
>> +#define SLOT_ENABLE_SHIFT			0
>> +
>> +#define SDHC_SYS_EXT_OP_CTRL			0x010C
>> +#define MASK_CMD_CONFLICT_ERROR			BIT(8)
>> +
>> +#define SDHC_SLOT_OP_STATUS_CTRL		0x0128
>> +#define DELAY_90_DEGREE_MASK_EMMC5		BIT(7)
>> +#define DELAY_90_DEGREE_SHIFT_EMMC5		7
>> +#define EMMC_5_0_PHY_FIXED_DELAY_MASK		0x7F
>> +#define EMMC_PHY_FIXED_DELAY_MASK		0xFF
>> +#define EMMC_PHY_FIXED_DELAY_WINDOW_MIN		(EMMC_PHY_FIXED_DELAY_MASK >> 3)
>> +#define SDH_PHY_FIXED_DELAY_MASK		0x1FF
>> +#define SDH_PHY_FIXED_DELAY_WINDOW_MIN		(SDH_PHY_FIXED_DELAY_MASK >> 4)
>> +
>> +#define TUN_CONSECUTIVE_TIMES_SHIFT		16
>> +#define TUN_CONSECUTIVE_TIMES_MASK		0x7
>> +#define TUN_CONSECUTIVE_TIMES			0x4
>> +#define TUNING_STEP_SHIFT			12
>> +#define TUNING_STEP_MASK			0xF
>> +#define TUNING_STEP_DIVIDER			BIT(6)
>> +
>> +#define FORCE_SEL_INVERSE_CLK_SHIFT		11
>> +
>> +#define SDHC_SLOT_EMMC_CTRL			0x0130
>> +#define ENABLE_DATA_STROBE			BIT(24)
>> +#define SET_EMMC_RSTN				BIT(16)
>> +#define DISABLE_RD_DATA_CRC			BIT(14)
>> +#define DISABLE_CRC_STAT_TOKEN			BIT(13)
>> +#define EMMC_VCCQ_MASK				0x3
>> +#define EMMC_VCCQ_1_8V				0x1
>> +#define EMMC_VCCQ_3_3V				0x3
>> +
>> +#define SDHC_SLOT_RETUNING_REQ_CTRL		0x0144
>> +/* retuning compatible */
>> +#define RETUNING_COMPATIBLE			0x1
>> +
>> +#define SDHC_SLOT_EXT_PRESENT_STATE		0x014C
>> +#define LOCK_STATE				0x1
>> +
>> +#define SDHC_SLOT_DLL_CUR_DLY_VAL		0x0150
>> +
>> +/* Tuning Parameter */
>> +#define TMR_RETUN_NO_PRESENT			0xF
>> +#define DEF_TUNING_COUNT			0x9
>> +
>> +#define MMC_TIMING_FAKE				0xFF
>> +
>> +#define DEFAULT_SDCLK_FREQ			(400000)
>> +
>> +/* Xenon specific Mode Select value */
>> +#define XENON_SDHCI_CTRL_HS200			0x5
>> +#define XENON_SDHCI_CTRL_HS400			0x6
>> +
>> +struct sdhci_xenon_priv {
>> +	/*
>> +	 * The bus_width, timing, and clock fields in below
>> +	 * record the current setting of Xenon SDHC.
>> +	 * Driver will call a Sampling Fixed Delay Adjustment
>> +	 * if any setting is changed.
>> +	 */
>> +	unsigned char	bus_width;
>> +	unsigned char	timing;
>> +	unsigned char	tuning_count;
>> +	unsigned int	clock;
>> +	struct clk	*axi_clk;
>> +
>> +	/* Slot idx */
>> +	u8		slot_idx;
>> +
>> +	/*
>> +	 * When initializing card, Xenon has to determine card type and
>> +	 * adjust Sampling Fixed delay.
>> +	 * However, at that time, card structure is not linked to mmc_host.
>> +	 * Thus a card pointer is added here to provide
>> +	 * the delay adjustment function with the card structure
>> +	 * of the card during initialization
>> +	 */
>> +	struct mmc_card *card_candidate;
>> +};
>> +
>> +static inline int enable_xenon_internal_clk(struct sdhci_host *host)
>> +{
>> +	u32 reg;
>> +	u8 timeout;
>> +
>> +	reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
>> +	reg |= SDHCI_CLOCK_INT_EN;
>> +	sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
>> +	/* Wait max 20 ms */
>> +	timeout = 20;
>> +	while (!((reg = sdhci_readw(host, SDHCI_CLOCK_CONTROL))
>> +			& SDHCI_CLOCK_INT_STABLE)) {
>> +		if (timeout == 0) {
>> +			pr_err("%s: Internal clock never stabilised.\n",
>> +			       mmc_hostname(host->mmc));
>> +			return -ETIMEDOUT;
>> +		}
>> +		timeout--;
>> +		mdelay(1);
>> +	}
>> +
>> +	return 0;
>> +}
>> +#endif
>>
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
@ 2016-10-12 11:58       ` Ziji Hu
  0 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-12 11:58 UTC (permalink / raw)
  To: Adrian Hunter, Gregory CLEMENT, Ulf Hansson, 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, Keji Zhang, Liuliu Zhao, Peng

Hi Adrian,

	Thank you very much for your review.
	I will firstly fix the typo.

On 2016/10/11 20:37, Adrian Hunter wrote:
> On 07/10/16 18:22, Gregory CLEMENT wrote:
>> From: Ziji Hu <huziji@marvell.com>
>>
>> Add Xenon eMMC/SD/SDIO host controller core functionality.
>> Add Xenon specific intialization process.
>> Add Xenon specific mmc_host_ops APIs.
>> Add Xenon specific register definitions.
>>
>> Add CONFIG_MMC_SDHCI_XENON support in drivers/mmc/host/Kconfig.
>>
>> Marvell Xenon SDHC conforms to SD Physical Layer Specification
>> Version 3.01 and is designed according to the guidelines provided
>> in the SD Host Controller Standard Specification Version 3.00.
>>
>> 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>
> 
> I looked at a couple of things but you need to sort out the issues with
> card_candidate before going further.
> 
	Understood.
	I will improve the card_candidate. Please help check the details in below.

>> ---
<snip>
>> +
>> +static int xenon_emmc_signal_voltage_switch(struct mmc_host *mmc,
>> +					    struct mmc_ios *ios)
>> +{
>> +	unsigned char voltage = ios->signal_voltage;
>> +
>> +	if ((voltage == MMC_SIGNAL_VOLTAGE_330) ||
>> +	    (voltage == MMC_SIGNAL_VOLTAGE_180))
>> +		return __emmc_signal_voltage_switch(mmc, voltage);
>> +
>> +	dev_err(mmc_dev(mmc), "Unsupported signal voltage: %d\n",
>> +		voltage);
>> +	return -EINVAL;
>> +}
>> +
>> +static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
>> +					     struct mmc_ios *ios)
>> +{
>> +	struct sdhci_host *host = mmc_priv(mmc);
>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +
>> +	/*
>> +	 * Before SD/SDIO set signal voltage, SD bus clock should be
>> +	 * disabled. However, sdhci_set_clock will also disable the Internal
>> +	 * clock in mmc_set_signal_voltage().
>> +	 * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
>> +	 * Thus here manually enable internal clock.
>> +	 *
>> +	 * After switch completes, it is unnecessary to disable internal clock,
>> +	 * since keeping internal clock active obeys SD spec.
>> +	 */
>> +	enable_xenon_internal_clk(host);
>> +
>> +	if (priv->card_candidate) {
> 
> mmc_power_up() calls __mmc_set_signal_voltage() calls
> host->ops->start_signal_voltage_switch so priv->card_candidate could be an
> invalid reference to an old card.
> 
> So that's not going to work if the card changes - not only for removable
> cards but even for eMMC if init fails and retries.
> 
	As you point out, this piece of code have defects, even though it actually works on Marvell multiple platforms, unless eMMC card is removable.

	I can add a property to explicitly indicate eMMC type in DTS.
	Then card_candidate access can be removed here.
	Does it sounds more reasonable to you?

>> +		if (mmc_card_mmc(priv->card_candidate))
>> +			return xenon_emmc_signal_voltage_switch(mmc, ios);
> 
> So if all you need to know is whether it is a eMMC, why can't DT tell you?
> 
	I can add an eMMC type property in DTS, to remove the card_candidate access here.

>> +	}
>> +
>> +	return sdhci_start_signal_voltage_switch(mmc, ios);
>> +}
>> +
>> +/*
>> + * After determining which slot is used for SDIO,
>> + * some additional task is required.
>> + */
>> +static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
>> +{
>> +	struct sdhci_host *host = mmc_priv(mmc);
>> +	u32 reg;
>> +	u8 slot_idx;
>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +
>> +	/* Link the card for delay adjustment */
>> +	priv->card_candidate = card;
> 
> You really need a better way to get the card.  I suggest you take up the
> issue with Ulf.  One possibility is to have mmc core set host->card = card
> much earlier.
> 
	Could you please tell me if any issue related to card_candidate still exists, after the card_candidate is removed from xenon_start_signal_voltage_switch() in above?
	It seems that when init_card is called, the structure card has already been updated and stable in MMC/SD/SDIO initialization sequence.
	May I keep it here?

>> +	/* Set tuning functionality of this slot */
>> +	xenon_slot_tuning_setup(host);
>> +
>> +	slot_idx = priv->slot_idx;
>> +	if (!mmc_card_sdio(card)) {
>> +		/* Re-enable the Auto-CMD12 cap flag. */
>> +		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>> +		host->flags |= SDHCI_AUTO_CMD12;
>> +
>> +		/* Clear SDIO Card Inserted indication */
>> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
>> +		reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
>> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
>> +
>> +		if (mmc_card_mmc(card)) {
>> +			mmc->caps |= MMC_CAP_NONREMOVABLE;
>> +			if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
>> +				mmc->caps |= MMC_CAP_1_8V_DDR;
>> +			/*
>> +			 * Force to clear BUS_TEST to
>> +			 * skip bus_test_pre and bus_test_post
>> +			 */
>> +			mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
>> +			mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
>> +				      MMC_CAP2_PACKED_CMD;
>> +			if (mmc->caps & MMC_CAP_8_BIT_DATA)
>> +				mmc->caps2 |= MMC_CAP2_HS400_1_8V;
>> +		}
>> +	} else {
>> +		/*
>> +		 * Delete the Auto-CMD12 cap flag.
>> +		 * Otherwise, when sending multi-block CMD53,
>> +		 * Driver will set Transfer Mode Register to enable Auto CMD12.
>> +		 * However, SDIO device cannot recognize CMD12.
>> +		 * Thus SDHC will time-out for waiting for CMD12 response.
>> +		 */
>> +		host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>> +		host->flags &= ~SDHCI_AUTO_CMD12;
> 
> sdhci_set_transfer_mode() won't enable auto-CMD12 for CMD53 anyway, so is
> this needed?
> 
	In Xenon driver, Auto-CMD12 flag is set to enable full support to Auto-CMD feature, both Auto-CMD12 and Auto-CMD23.
	As a result, when Xenon SDHC slot can both support SD and SDIO, Auto-CMD12 is disabled when SDIO card is inserted, and renabled when SD is inserted.

	I recheck the sdhci code to set Auto-CMD bit in Transfer Mode register, in sdhci_set_transfer_mode():
	if (mmc_op_multi(cmd->opcode) || data->blocks > 1)
	As you can see, as long as it is CMD18/CMD25 OR there are multiple data blocks, Auto-CMD field will be set.
	CMD53 doesn't have CMD23. Thus Auto-CMD12 is selected since Auto-CMD12 flag is set.
	Thus I have to clear Auto-CMD12 to avoid issuing Auto-CMD12 in SDIO transfer.

	I just meet a similar issue in RPMB.
	When Auto-CMD12 flag is set, eMMC RPMB access will trigger Auto-CMD12, since CMD25 is in use.
	It will cause RPMB access failed.

	One possible solution is to drop Auto-CMD12 support and use Auto-CMD23 only, in Xenon driver.
	May I know you opinion, please?

>> +
>> +		/*
>> +		 * Set SDIO Card Inserted indication
>> +		 * to inform that the current slot is for SDIO
>> +		 */
>> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
>> +		reg |= (1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
>> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
>> +	}
>> +}
>> +
>> +static int xenon_execute_tuning(struct mmc_host *mmc, u32 opcode)
>> +{
>> +	struct sdhci_host *host = mmc_priv(mmc);
>> +
>> +	if (host->timing == MMC_TIMING_UHS_DDR50)
>> +		return 0;
>> +
>> +	return sdhci_execute_tuning(mmc, opcode);
>> +}
>> +
>> +static void xenon_replace_mmc_host_ops(struct sdhci_host *host)
>> +{
>> +	host->mmc_host_ops.set_ios = xenon_set_ios;
>> +	host->mmc_host_ops.start_signal_voltage_switch =
>> +			xenon_start_signal_voltage_switch;
>> +	host->mmc_host_ops.init_card = xenon_init_card;
>> +	host->mmc_host_ops.execute_tuning = xenon_execute_tuning;
>> +}
>> +
>> +static int xenon_probe_dt(struct platform_device *pdev)
>> +{
>> +	struct device_node *np = pdev->dev.of_node;
>> +	struct sdhci_host *host = platform_get_drvdata(pdev);
>> +	struct mmc_host *mmc = host->mmc;
>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +	int err;
>> +	u32 slot_idx, nr_slot;
>> +	u32 tuning_count;
>> +	u32 reg;
>> +
>> +	/* Standard MMC property */
>> +	err = mmc_of_parse(mmc);
>> +	if (err)
>> +		return err;
>> +
>> +	/* Standard SDHCI property */
>> +	sdhci_get_of_property(pdev);
>> +
>> +	/*
>> +	 * Xenon Specific property:
>> +	 * slotno: the index of slot. Refer to SDHC_SYS_CFG_INFO register
>> +	 * tuning-count: the interval between re-tuning
>> +	 * PHY type: "sdhc phy", "emmc phy 5.0" or "emmc phy 5.1"
>> +	 */
>> +	if (!of_property_read_u32(np, "xenon,slotno", &slot_idx)) {
>> +		nr_slot = sdhci_readl(host, SDHC_SYS_CFG_INFO);
>> +		nr_slot &= NR_SUPPORTED_SLOT_MASK;
>> +		if (unlikely(slot_idx > nr_slot)) {
>> +			dev_err(mmc_dev(mmc), "Slot Index %d exceeds Number of slots %d\n",
>> +				slot_idx, nr_slot);
>> +			return -EINVAL;
>> +		}
>> +	} else {
>> +		priv->slot_idx = 0x0;
>> +	}
>> +
>> +	if (!of_property_read_u32(np, "xenon,tuning-count", &tuning_count)) {
>> +		if (unlikely(tuning_count >= TMR_RETUN_NO_PRESENT)) {
>> +			dev_err(mmc_dev(mmc), "Wrong Re-tuning Count. Set default value %d\n",
>> +				DEF_TUNING_COUNT);
>> +			tuning_count = DEF_TUNING_COUNT;
>> +		}
>> +	} else {
>> +		priv->tuning_count = DEF_TUNING_COUNT;
>> +	}
>> +
>> +	if (of_property_read_bool(np, "xenon,mask-conflict-err")) {
>> +		reg = sdhci_readl(host, SDHC_SYS_EXT_OP_CTRL);
>> +		reg |= MASK_CMD_CONFLICT_ERROR;
>> +		sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
>> +	}
>> +
>> +	return err;
>> +}
>> +
>> +static int xenon_slot_probe(struct sdhci_host *host)
>> +{
>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +	u8 slot_idx = priv->slot_idx;
>> +
>> +	/* Enable slot */
>> +	xenon_enable_slot(host, slot_idx);
>> +
>> +	/* Enable ACG */
>> +	xenon_set_acg(host, true);
>> +
>> +	/* Enable Parallel Transfer Mode */
>> +	xenon_enable_slot_parallel_tran(host, slot_idx);
>> +
>> +	priv->timing = MMC_TIMING_FAKE;
>> +	priv->clock = 0;
>> +
>> +	return 0;
>> +}
>> +
>> +static void xenon_slot_remove(struct sdhci_host *host)
>> +{
>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +	u8 slot_idx = priv->slot_idx;
>> +
>> +	/* disable slot */
>> +	xenon_disable_slot(host, slot_idx);
>> +}
>> +
>> +static int sdhci_xenon_probe(struct platform_device *pdev)
>> +{
>> +	struct sdhci_pltfm_host *pltfm_host;
>> +	struct sdhci_host *host;
>> +	struct clk *clk, *axi_clk;
>> +	struct sdhci_xenon_priv *priv;
>> +	int err;
>> +
>> +	host = sdhci_pltfm_init(pdev, &sdhci_xenon_pdata,
>> +				sizeof(struct sdhci_xenon_priv));
>> +	if (IS_ERR(host))
>> +		return PTR_ERR(host);
>> +
>> +	pltfm_host = sdhci_priv(host);
>> +	priv = sdhci_pltfm_priv(pltfm_host);
>> +
>> +	xenon_set_acg(host, false);
>> +
>> +	/*
>> +	 * Link Xenon specific mmc_host_ops function,
>> +	 * to replace standard ones in sdhci_ops.
>> +	 */
>> +	xenon_replace_mmc_host_ops(host);
>> +
>> +	clk = devm_clk_get(&pdev->dev, "core");
>> +	if (IS_ERR(clk)) {
>> +		dev_err(&pdev->dev, "Failed to setup input clk.\n");
>> +		err = PTR_ERR(clk);
>> +		goto free_pltfm;
>> +	}
>> +	clk_prepare_enable(clk);
>> +	pltfm_host->clk = clk;
>> +
>> +	/*
>> +	 * Some SOCs require additional clock to
>> +	 * manage AXI bus clock.
>> +	 * It is optional.
>> +	 */
>> +	axi_clk = devm_clk_get(&pdev->dev, "axi");
>> +	if (!IS_ERR(axi_clk)) {
>> +		clk_prepare_enable(axi_clk);
>> +		priv->axi_clk = axi_clk;
>> +	}
>> +
>> +	err = xenon_probe_dt(pdev);
>> +	if (err)
>> +		goto err_clk;
>> +
>> +	err = xenon_slot_probe(host);
>> +	if (err)
>> +		goto err_clk;
>> +
>> +	err = sdhci_add_host(host);
>> +	if (err)
>> +		goto remove_slot;
>> +
>> +	return 0;
>> +
>> +remove_slot:
>> +	xenon_slot_remove(host);
>> +err_clk:
>> +	clk_disable_unprepare(pltfm_host->clk);
>> +	if (!IS_ERR(axi_clk))
>> +		clk_disable_unprepare(axi_clk);
>> +free_pltfm:
>> +	sdhci_pltfm_free(pdev);
>> +	return err;
>> +}
>> +
>> +static int sdhci_xenon_remove(struct platform_device *pdev)
>> +{
>> +	struct sdhci_host *host = platform_get_drvdata(pdev);
>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +	int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xFFFFFFFF);
>> +
>> +	xenon_slot_remove(host);
>> +
>> +	sdhci_remove_host(host, dead);
>> +
>> +	clk_disable_unprepare(pltfm_host->clk);
>> +	clk_disable_unprepare(priv->axi_clk);
>> +
>> +	sdhci_pltfm_free(pdev);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id sdhci_xenon_dt_ids[] = {
>> +	{ .compatible = "marvell,sdhci-xenon",},
>> +	{ .compatible = "marvell,armada-3700-sdhci",},
>> +	{}
>> +};
>> +MODULE_DEVICE_TABLE(of, sdhci_xenon_dt_ids);
>> +
>> +static struct platform_driver sdhci_xenon_driver = {
>> +	.driver	= {
>> +		.name	= "sdhci-xenon",
>> +		.of_match_table = sdhci_xenon_dt_ids,
>> +		.pm = &sdhci_pltfm_pmops,
>> +	},
>> +	.probe	= sdhci_xenon_probe,
>> +	.remove	= sdhci_xenon_remove,
>> +};
>> +
>> +module_platform_driver(sdhci_xenon_driver);
>> +
>> +MODULE_DESCRIPTION("SDHCI platform driver for Marvell Xenon SDHC");
>> +MODULE_AUTHOR("Hu Ziji <huziji@marvell.com>");
>> +MODULE_LICENSE("GPL v2");
>> diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h
>> new file mode 100644
>> index 000000000000..c2370493fbe8
>> --- /dev/null
>> +++ b/drivers/mmc/host/sdhci-xenon.h
>> @@ -0,0 +1,134 @@
>> +/*
>> + * 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.
>> + */
>> +#ifndef SDHCI_XENON_H_
>> +#define SDHCI_XENON_H_
>> +
>> +#include <linux/clk.h>
>> +#include <linux/mmc/card.h>
>> +#include <linux/of.h>
>> +#include "sdhci.h"
>> +
>> +/* Register Offset of SD Host Controller SOCP self-defined register */
>> +#define SDHC_SYS_CFG_INFO			0x0104
>> +#define SLOT_TYPE_SDIO_SHIFT			24
>> +#define SLOT_TYPE_EMMC_MASK			0xFF
>> +#define SLOT_TYPE_EMMC_SHIFT			16
>> +#define SLOT_TYPE_SD_SDIO_MMC_MASK		0xFF
>> +#define SLOT_TYPE_SD_SDIO_MMC_SHIFT		8
>> +#define NR_SUPPORTED_SLOT_MASK			0x7
>> +
>> +#define SDHC_SYS_OP_CTRL			0x0108
>> +#define AUTO_CLKGATE_DISABLE_MASK		BIT(20)
>> +#define SDCLK_IDLEOFF_ENABLE_SHIFT		8
>> +#define SLOT_ENABLE_SHIFT			0
>> +
>> +#define SDHC_SYS_EXT_OP_CTRL			0x010C
>> +#define MASK_CMD_CONFLICT_ERROR			BIT(8)
>> +
>> +#define SDHC_SLOT_OP_STATUS_CTRL		0x0128
>> +#define DELAY_90_DEGREE_MASK_EMMC5		BIT(7)
>> +#define DELAY_90_DEGREE_SHIFT_EMMC5		7
>> +#define EMMC_5_0_PHY_FIXED_DELAY_MASK		0x7F
>> +#define EMMC_PHY_FIXED_DELAY_MASK		0xFF
>> +#define EMMC_PHY_FIXED_DELAY_WINDOW_MIN		(EMMC_PHY_FIXED_DELAY_MASK >> 3)
>> +#define SDH_PHY_FIXED_DELAY_MASK		0x1FF
>> +#define SDH_PHY_FIXED_DELAY_WINDOW_MIN		(SDH_PHY_FIXED_DELAY_MASK >> 4)
>> +
>> +#define TUN_CONSECUTIVE_TIMES_SHIFT		16
>> +#define TUN_CONSECUTIVE_TIMES_MASK		0x7
>> +#define TUN_CONSECUTIVE_TIMES			0x4
>> +#define TUNING_STEP_SHIFT			12
>> +#define TUNING_STEP_MASK			0xF
>> +#define TUNING_STEP_DIVIDER			BIT(6)
>> +
>> +#define FORCE_SEL_INVERSE_CLK_SHIFT		11
>> +
>> +#define SDHC_SLOT_EMMC_CTRL			0x0130
>> +#define ENABLE_DATA_STROBE			BIT(24)
>> +#define SET_EMMC_RSTN				BIT(16)
>> +#define DISABLE_RD_DATA_CRC			BIT(14)
>> +#define DISABLE_CRC_STAT_TOKEN			BIT(13)
>> +#define EMMC_VCCQ_MASK				0x3
>> +#define EMMC_VCCQ_1_8V				0x1
>> +#define EMMC_VCCQ_3_3V				0x3
>> +
>> +#define SDHC_SLOT_RETUNING_REQ_CTRL		0x0144
>> +/* retuning compatible */
>> +#define RETUNING_COMPATIBLE			0x1
>> +
>> +#define SDHC_SLOT_EXT_PRESENT_STATE		0x014C
>> +#define LOCK_STATE				0x1
>> +
>> +#define SDHC_SLOT_DLL_CUR_DLY_VAL		0x0150
>> +
>> +/* Tuning Parameter */
>> +#define TMR_RETUN_NO_PRESENT			0xF
>> +#define DEF_TUNING_COUNT			0x9
>> +
>> +#define MMC_TIMING_FAKE				0xFF
>> +
>> +#define DEFAULT_SDCLK_FREQ			(400000)
>> +
>> +/* Xenon specific Mode Select value */
>> +#define XENON_SDHCI_CTRL_HS200			0x5
>> +#define XENON_SDHCI_CTRL_HS400			0x6
>> +
>> +struct sdhci_xenon_priv {
>> +	/*
>> +	 * The bus_width, timing, and clock fields in below
>> +	 * record the current setting of Xenon SDHC.
>> +	 * Driver will call a Sampling Fixed Delay Adjustment
>> +	 * if any setting is changed.
>> +	 */
>> +	unsigned char	bus_width;
>> +	unsigned char	timing;
>> +	unsigned char	tuning_count;
>> +	unsigned int	clock;
>> +	struct clk	*axi_clk;
>> +
>> +	/* Slot idx */
>> +	u8		slot_idx;
>> +
>> +	/*
>> +	 * When initializing card, Xenon has to determine card type and
>> +	 * adjust Sampling Fixed delay.
>> +	 * However, at that time, card structure is not linked to mmc_host.
>> +	 * Thus a card pointer is added here to provide
>> +	 * the delay adjustment function with the card structure
>> +	 * of the card during initialization
>> +	 */
>> +	struct mmc_card *card_candidate;
>> +};
>> +
>> +static inline int enable_xenon_internal_clk(struct sdhci_host *host)
>> +{
>> +	u32 reg;
>> +	u8 timeout;
>> +
>> +	reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
>> +	reg |= SDHCI_CLOCK_INT_EN;
>> +	sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
>> +	/* Wait max 20 ms */
>> +	timeout = 20;
>> +	while (!((reg = sdhci_readw(host, SDHCI_CLOCK_CONTROL))
>> +			& SDHCI_CLOCK_INT_STABLE)) {
>> +		if (timeout == 0) {
>> +			pr_err("%s: Internal clock never stabilised.\n",
>> +			       mmc_hostname(host->mmc));
>> +			return -ETIMEDOUT;
>> +		}
>> +		timeout--;
>> +		mdelay(1);
>> +	}
>> +
>> +	return 0;
>> +}
>> +#endif
>>
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
@ 2016-10-12 11:58       ` Ziji Hu
  0 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-12 11:58 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Adrian,

	Thank you very much for your review.
	I will firstly fix the typo.

On 2016/10/11 20:37, Adrian Hunter wrote:
> On 07/10/16 18:22, Gregory CLEMENT wrote:
>> From: Ziji Hu <huziji@marvell.com>
>>
>> Add Xenon eMMC/SD/SDIO host controller core functionality.
>> Add Xenon specific intialization process.
>> Add Xenon specific mmc_host_ops APIs.
>> Add Xenon specific register definitions.
>>
>> Add CONFIG_MMC_SDHCI_XENON support in drivers/mmc/host/Kconfig.
>>
>> Marvell Xenon SDHC conforms to SD Physical Layer Specification
>> Version 3.01 and is designed according to the guidelines provided
>> in the SD Host Controller Standard Specification Version 3.00.
>>
>> 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>
> 
> I looked at a couple of things but you need to sort out the issues with
> card_candidate before going further.
> 
	Understood.
	I will improve the card_candidate. Please help check the details in below.

>> ---
<snip>
>> +
>> +static int xenon_emmc_signal_voltage_switch(struct mmc_host *mmc,
>> +					    struct mmc_ios *ios)
>> +{
>> +	unsigned char voltage = ios->signal_voltage;
>> +
>> +	if ((voltage == MMC_SIGNAL_VOLTAGE_330) ||
>> +	    (voltage == MMC_SIGNAL_VOLTAGE_180))
>> +		return __emmc_signal_voltage_switch(mmc, voltage);
>> +
>> +	dev_err(mmc_dev(mmc), "Unsupported signal voltage: %d\n",
>> +		voltage);
>> +	return -EINVAL;
>> +}
>> +
>> +static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
>> +					     struct mmc_ios *ios)
>> +{
>> +	struct sdhci_host *host = mmc_priv(mmc);
>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +
>> +	/*
>> +	 * Before SD/SDIO set signal voltage, SD bus clock should be
>> +	 * disabled. However, sdhci_set_clock will also disable the Internal
>> +	 * clock in mmc_set_signal_voltage().
>> +	 * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
>> +	 * Thus here manually enable internal clock.
>> +	 *
>> +	 * After switch completes, it is unnecessary to disable internal clock,
>> +	 * since keeping internal clock active obeys SD spec.
>> +	 */
>> +	enable_xenon_internal_clk(host);
>> +
>> +	if (priv->card_candidate) {
> 
> mmc_power_up() calls __mmc_set_signal_voltage() calls
> host->ops->start_signal_voltage_switch so priv->card_candidate could be an
> invalid reference to an old card.
> 
> So that's not going to work if the card changes - not only for removable
> cards but even for eMMC if init fails and retries.
> 
	As you point out, this piece of code have defects, even though it actually works on Marvell multiple platforms, unless eMMC card is removable.

	I can add a property to explicitly indicate eMMC type in DTS.
	Then card_candidate access can be removed here.
	Does it sounds more reasonable to you?

>> +		if (mmc_card_mmc(priv->card_candidate))
>> +			return xenon_emmc_signal_voltage_switch(mmc, ios);
> 
> So if all you need to know is whether it is a eMMC, why can't DT tell you?
> 
	I can add an eMMC type property in DTS, to remove the card_candidate access here.

>> +	}
>> +
>> +	return sdhci_start_signal_voltage_switch(mmc, ios);
>> +}
>> +
>> +/*
>> + * After determining which slot is used for SDIO,
>> + * some additional task is required.
>> + */
>> +static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
>> +{
>> +	struct sdhci_host *host = mmc_priv(mmc);
>> +	u32 reg;
>> +	u8 slot_idx;
>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +
>> +	/* Link the card for delay adjustment */
>> +	priv->card_candidate = card;
> 
> You really need a better way to get the card.  I suggest you take up the
> issue with Ulf.  One possibility is to have mmc core set host->card = card
> much earlier.
> 
	Could you please tell me if any issue related to card_candidate still exists, after the card_candidate is removed from xenon_start_signal_voltage_switch() in above?
	It seems that when init_card is called, the structure card has already been updated and stable in MMC/SD/SDIO initialization sequence.
	May I keep it here?

>> +	/* Set tuning functionality of this slot */
>> +	xenon_slot_tuning_setup(host);
>> +
>> +	slot_idx = priv->slot_idx;
>> +	if (!mmc_card_sdio(card)) {
>> +		/* Re-enable the Auto-CMD12 cap flag. */
>> +		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>> +		host->flags |= SDHCI_AUTO_CMD12;
>> +
>> +		/* Clear SDIO Card Inserted indication */
>> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
>> +		reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
>> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
>> +
>> +		if (mmc_card_mmc(card)) {
>> +			mmc->caps |= MMC_CAP_NONREMOVABLE;
>> +			if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
>> +				mmc->caps |= MMC_CAP_1_8V_DDR;
>> +			/*
>> +			 * Force to clear BUS_TEST to
>> +			 * skip bus_test_pre and bus_test_post
>> +			 */
>> +			mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
>> +			mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
>> +				      MMC_CAP2_PACKED_CMD;
>> +			if (mmc->caps & MMC_CAP_8_BIT_DATA)
>> +				mmc->caps2 |= MMC_CAP2_HS400_1_8V;
>> +		}
>> +	} else {
>> +		/*
>> +		 * Delete the Auto-CMD12 cap flag.
>> +		 * Otherwise, when sending multi-block CMD53,
>> +		 * Driver will set Transfer Mode Register to enable Auto CMD12.
>> +		 * However, SDIO device cannot recognize CMD12.
>> +		 * Thus SDHC will time-out for waiting for CMD12 response.
>> +		 */
>> +		host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>> +		host->flags &= ~SDHCI_AUTO_CMD12;
> 
> sdhci_set_transfer_mode() won't enable auto-CMD12 for CMD53 anyway, so is
> this needed?
> 
	In Xenon driver, Auto-CMD12 flag is set to enable full support to Auto-CMD feature, both Auto-CMD12 and Auto-CMD23.
	As a result, when Xenon SDHC slot can both support SD and SDIO, Auto-CMD12 is disabled when SDIO card is inserted, and renabled when SD is inserted.

	I recheck the sdhci code to set Auto-CMD bit in Transfer Mode register, in sdhci_set_transfer_mode():
	if (mmc_op_multi(cmd->opcode) || data->blocks > 1)
	As you can see, as long as it is CMD18/CMD25 OR there are multiple data blocks, Auto-CMD field will be set.
	CMD53 doesn't have CMD23. Thus Auto-CMD12 is selected since Auto-CMD12 flag is set.
	Thus I have to clear Auto-CMD12 to avoid issuing Auto-CMD12 in SDIO transfer.

	I just meet a similar issue in RPMB.
	When Auto-CMD12 flag is set, eMMC RPMB access will trigger Auto-CMD12, since CMD25 is in use.
	It will cause RPMB access failed.

	One possible solution is to drop Auto-CMD12 support and use Auto-CMD23 only, in Xenon driver.
	May I know you opinion, please?

>> +
>> +		/*
>> +		 * Set SDIO Card Inserted indication
>> +		 * to inform that the current slot is for SDIO
>> +		 */
>> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
>> +		reg |= (1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
>> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
>> +	}
>> +}
>> +
>> +static int xenon_execute_tuning(struct mmc_host *mmc, u32 opcode)
>> +{
>> +	struct sdhci_host *host = mmc_priv(mmc);
>> +
>> +	if (host->timing == MMC_TIMING_UHS_DDR50)
>> +		return 0;
>> +
>> +	return sdhci_execute_tuning(mmc, opcode);
>> +}
>> +
>> +static void xenon_replace_mmc_host_ops(struct sdhci_host *host)
>> +{
>> +	host->mmc_host_ops.set_ios = xenon_set_ios;
>> +	host->mmc_host_ops.start_signal_voltage_switch =
>> +			xenon_start_signal_voltage_switch;
>> +	host->mmc_host_ops.init_card = xenon_init_card;
>> +	host->mmc_host_ops.execute_tuning = xenon_execute_tuning;
>> +}
>> +
>> +static int xenon_probe_dt(struct platform_device *pdev)
>> +{
>> +	struct device_node *np = pdev->dev.of_node;
>> +	struct sdhci_host *host = platform_get_drvdata(pdev);
>> +	struct mmc_host *mmc = host->mmc;
>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +	int err;
>> +	u32 slot_idx, nr_slot;
>> +	u32 tuning_count;
>> +	u32 reg;
>> +
>> +	/* Standard MMC property */
>> +	err = mmc_of_parse(mmc);
>> +	if (err)
>> +		return err;
>> +
>> +	/* Standard SDHCI property */
>> +	sdhci_get_of_property(pdev);
>> +
>> +	/*
>> +	 * Xenon Specific property:
>> +	 * slotno: the index of slot. Refer to SDHC_SYS_CFG_INFO register
>> +	 * tuning-count: the interval between re-tuning
>> +	 * PHY type: "sdhc phy", "emmc phy 5.0" or "emmc phy 5.1"
>> +	 */
>> +	if (!of_property_read_u32(np, "xenon,slotno", &slot_idx)) {
>> +		nr_slot = sdhci_readl(host, SDHC_SYS_CFG_INFO);
>> +		nr_slot &= NR_SUPPORTED_SLOT_MASK;
>> +		if (unlikely(slot_idx > nr_slot)) {
>> +			dev_err(mmc_dev(mmc), "Slot Index %d exceeds Number of slots %d\n",
>> +				slot_idx, nr_slot);
>> +			return -EINVAL;
>> +		}
>> +	} else {
>> +		priv->slot_idx = 0x0;
>> +	}
>> +
>> +	if (!of_property_read_u32(np, "xenon,tuning-count", &tuning_count)) {
>> +		if (unlikely(tuning_count >= TMR_RETUN_NO_PRESENT)) {
>> +			dev_err(mmc_dev(mmc), "Wrong Re-tuning Count. Set default value %d\n",
>> +				DEF_TUNING_COUNT);
>> +			tuning_count = DEF_TUNING_COUNT;
>> +		}
>> +	} else {
>> +		priv->tuning_count = DEF_TUNING_COUNT;
>> +	}
>> +
>> +	if (of_property_read_bool(np, "xenon,mask-conflict-err")) {
>> +		reg = sdhci_readl(host, SDHC_SYS_EXT_OP_CTRL);
>> +		reg |= MASK_CMD_CONFLICT_ERROR;
>> +		sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
>> +	}
>> +
>> +	return err;
>> +}
>> +
>> +static int xenon_slot_probe(struct sdhci_host *host)
>> +{
>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +	u8 slot_idx = priv->slot_idx;
>> +
>> +	/* Enable slot */
>> +	xenon_enable_slot(host, slot_idx);
>> +
>> +	/* Enable ACG */
>> +	xenon_set_acg(host, true);
>> +
>> +	/* Enable Parallel Transfer Mode */
>> +	xenon_enable_slot_parallel_tran(host, slot_idx);
>> +
>> +	priv->timing = MMC_TIMING_FAKE;
>> +	priv->clock = 0;
>> +
>> +	return 0;
>> +}
>> +
>> +static void xenon_slot_remove(struct sdhci_host *host)
>> +{
>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +	u8 slot_idx = priv->slot_idx;
>> +
>> +	/* disable slot */
>> +	xenon_disable_slot(host, slot_idx);
>> +}
>> +
>> +static int sdhci_xenon_probe(struct platform_device *pdev)
>> +{
>> +	struct sdhci_pltfm_host *pltfm_host;
>> +	struct sdhci_host *host;
>> +	struct clk *clk, *axi_clk;
>> +	struct sdhci_xenon_priv *priv;
>> +	int err;
>> +
>> +	host = sdhci_pltfm_init(pdev, &sdhci_xenon_pdata,
>> +				sizeof(struct sdhci_xenon_priv));
>> +	if (IS_ERR(host))
>> +		return PTR_ERR(host);
>> +
>> +	pltfm_host = sdhci_priv(host);
>> +	priv = sdhci_pltfm_priv(pltfm_host);
>> +
>> +	xenon_set_acg(host, false);
>> +
>> +	/*
>> +	 * Link Xenon specific mmc_host_ops function,
>> +	 * to replace standard ones in sdhci_ops.
>> +	 */
>> +	xenon_replace_mmc_host_ops(host);
>> +
>> +	clk = devm_clk_get(&pdev->dev, "core");
>> +	if (IS_ERR(clk)) {
>> +		dev_err(&pdev->dev, "Failed to setup input clk.\n");
>> +		err = PTR_ERR(clk);
>> +		goto free_pltfm;
>> +	}
>> +	clk_prepare_enable(clk);
>> +	pltfm_host->clk = clk;
>> +
>> +	/*
>> +	 * Some SOCs require additional clock to
>> +	 * manage AXI bus clock.
>> +	 * It is optional.
>> +	 */
>> +	axi_clk = devm_clk_get(&pdev->dev, "axi");
>> +	if (!IS_ERR(axi_clk)) {
>> +		clk_prepare_enable(axi_clk);
>> +		priv->axi_clk = axi_clk;
>> +	}
>> +
>> +	err = xenon_probe_dt(pdev);
>> +	if (err)
>> +		goto err_clk;
>> +
>> +	err = xenon_slot_probe(host);
>> +	if (err)
>> +		goto err_clk;
>> +
>> +	err = sdhci_add_host(host);
>> +	if (err)
>> +		goto remove_slot;
>> +
>> +	return 0;
>> +
>> +remove_slot:
>> +	xenon_slot_remove(host);
>> +err_clk:
>> +	clk_disable_unprepare(pltfm_host->clk);
>> +	if (!IS_ERR(axi_clk))
>> +		clk_disable_unprepare(axi_clk);
>> +free_pltfm:
>> +	sdhci_pltfm_free(pdev);
>> +	return err;
>> +}
>> +
>> +static int sdhci_xenon_remove(struct platform_device *pdev)
>> +{
>> +	struct sdhci_host *host = platform_get_drvdata(pdev);
>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +	int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xFFFFFFFF);
>> +
>> +	xenon_slot_remove(host);
>> +
>> +	sdhci_remove_host(host, dead);
>> +
>> +	clk_disable_unprepare(pltfm_host->clk);
>> +	clk_disable_unprepare(priv->axi_clk);
>> +
>> +	sdhci_pltfm_free(pdev);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id sdhci_xenon_dt_ids[] = {
>> +	{ .compatible = "marvell,sdhci-xenon",},
>> +	{ .compatible = "marvell,armada-3700-sdhci",},
>> +	{}
>> +};
>> +MODULE_DEVICE_TABLE(of, sdhci_xenon_dt_ids);
>> +
>> +static struct platform_driver sdhci_xenon_driver = {
>> +	.driver	= {
>> +		.name	= "sdhci-xenon",
>> +		.of_match_table = sdhci_xenon_dt_ids,
>> +		.pm = &sdhci_pltfm_pmops,
>> +	},
>> +	.probe	= sdhci_xenon_probe,
>> +	.remove	= sdhci_xenon_remove,
>> +};
>> +
>> +module_platform_driver(sdhci_xenon_driver);
>> +
>> +MODULE_DESCRIPTION("SDHCI platform driver for Marvell Xenon SDHC");
>> +MODULE_AUTHOR("Hu Ziji <huziji@marvell.com>");
>> +MODULE_LICENSE("GPL v2");
>> diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h
>> new file mode 100644
>> index 000000000000..c2370493fbe8
>> --- /dev/null
>> +++ b/drivers/mmc/host/sdhci-xenon.h
>> @@ -0,0 +1,134 @@
>> +/*
>> + * 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.
>> + */
>> +#ifndef SDHCI_XENON_H_
>> +#define SDHCI_XENON_H_
>> +
>> +#include <linux/clk.h>
>> +#include <linux/mmc/card.h>
>> +#include <linux/of.h>
>> +#include "sdhci.h"
>> +
>> +/* Register Offset of SD Host Controller SOCP self-defined register */
>> +#define SDHC_SYS_CFG_INFO			0x0104
>> +#define SLOT_TYPE_SDIO_SHIFT			24
>> +#define SLOT_TYPE_EMMC_MASK			0xFF
>> +#define SLOT_TYPE_EMMC_SHIFT			16
>> +#define SLOT_TYPE_SD_SDIO_MMC_MASK		0xFF
>> +#define SLOT_TYPE_SD_SDIO_MMC_SHIFT		8
>> +#define NR_SUPPORTED_SLOT_MASK			0x7
>> +
>> +#define SDHC_SYS_OP_CTRL			0x0108
>> +#define AUTO_CLKGATE_DISABLE_MASK		BIT(20)
>> +#define SDCLK_IDLEOFF_ENABLE_SHIFT		8
>> +#define SLOT_ENABLE_SHIFT			0
>> +
>> +#define SDHC_SYS_EXT_OP_CTRL			0x010C
>> +#define MASK_CMD_CONFLICT_ERROR			BIT(8)
>> +
>> +#define SDHC_SLOT_OP_STATUS_CTRL		0x0128
>> +#define DELAY_90_DEGREE_MASK_EMMC5		BIT(7)
>> +#define DELAY_90_DEGREE_SHIFT_EMMC5		7
>> +#define EMMC_5_0_PHY_FIXED_DELAY_MASK		0x7F
>> +#define EMMC_PHY_FIXED_DELAY_MASK		0xFF
>> +#define EMMC_PHY_FIXED_DELAY_WINDOW_MIN		(EMMC_PHY_FIXED_DELAY_MASK >> 3)
>> +#define SDH_PHY_FIXED_DELAY_MASK		0x1FF
>> +#define SDH_PHY_FIXED_DELAY_WINDOW_MIN		(SDH_PHY_FIXED_DELAY_MASK >> 4)
>> +
>> +#define TUN_CONSECUTIVE_TIMES_SHIFT		16
>> +#define TUN_CONSECUTIVE_TIMES_MASK		0x7
>> +#define TUN_CONSECUTIVE_TIMES			0x4
>> +#define TUNING_STEP_SHIFT			12
>> +#define TUNING_STEP_MASK			0xF
>> +#define TUNING_STEP_DIVIDER			BIT(6)
>> +
>> +#define FORCE_SEL_INVERSE_CLK_SHIFT		11
>> +
>> +#define SDHC_SLOT_EMMC_CTRL			0x0130
>> +#define ENABLE_DATA_STROBE			BIT(24)
>> +#define SET_EMMC_RSTN				BIT(16)
>> +#define DISABLE_RD_DATA_CRC			BIT(14)
>> +#define DISABLE_CRC_STAT_TOKEN			BIT(13)
>> +#define EMMC_VCCQ_MASK				0x3
>> +#define EMMC_VCCQ_1_8V				0x1
>> +#define EMMC_VCCQ_3_3V				0x3
>> +
>> +#define SDHC_SLOT_RETUNING_REQ_CTRL		0x0144
>> +/* retuning compatible */
>> +#define RETUNING_COMPATIBLE			0x1
>> +
>> +#define SDHC_SLOT_EXT_PRESENT_STATE		0x014C
>> +#define LOCK_STATE				0x1
>> +
>> +#define SDHC_SLOT_DLL_CUR_DLY_VAL		0x0150
>> +
>> +/* Tuning Parameter */
>> +#define TMR_RETUN_NO_PRESENT			0xF
>> +#define DEF_TUNING_COUNT			0x9
>> +
>> +#define MMC_TIMING_FAKE				0xFF
>> +
>> +#define DEFAULT_SDCLK_FREQ			(400000)
>> +
>> +/* Xenon specific Mode Select value */
>> +#define XENON_SDHCI_CTRL_HS200			0x5
>> +#define XENON_SDHCI_CTRL_HS400			0x6
>> +
>> +struct sdhci_xenon_priv {
>> +	/*
>> +	 * The bus_width, timing, and clock fields in below
>> +	 * record the current setting of Xenon SDHC.
>> +	 * Driver will call a Sampling Fixed Delay Adjustment
>> +	 * if any setting is changed.
>> +	 */
>> +	unsigned char	bus_width;
>> +	unsigned char	timing;
>> +	unsigned char	tuning_count;
>> +	unsigned int	clock;
>> +	struct clk	*axi_clk;
>> +
>> +	/* Slot idx */
>> +	u8		slot_idx;
>> +
>> +	/*
>> +	 * When initializing card, Xenon has to determine card type and
>> +	 * adjust Sampling Fixed delay.
>> +	 * However, at that time, card structure is not linked to mmc_host.
>> +	 * Thus a card pointer is added here to provide
>> +	 * the delay adjustment function with the card structure
>> +	 * of the card during initialization
>> +	 */
>> +	struct mmc_card *card_candidate;
>> +};
>> +
>> +static inline int enable_xenon_internal_clk(struct sdhci_host *host)
>> +{
>> +	u32 reg;
>> +	u8 timeout;
>> +
>> +	reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
>> +	reg |= SDHCI_CLOCK_INT_EN;
>> +	sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
>> +	/* Wait max 20 ms */
>> +	timeout = 20;
>> +	while (!((reg = sdhci_readw(host, SDHCI_CLOCK_CONTROL))
>> +			& SDHCI_CLOCK_INT_STABLE)) {
>> +		if (timeout == 0) {
>> +			pr_err("%s: Internal clock never stabilised.\n",
>> +			       mmc_hostname(host->mmc));
>> +			return -ETIMEDOUT;
>> +		}
>> +		timeout--;
>> +		mdelay(1);
>> +	}
>> +
>> +	return 0;
>> +}
>> +#endif
>>
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
  2016-10-11 12:39     ` Adrian Hunter
  (?)
@ 2016-10-12 12:17       ` Ziji Hu
  -1 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-12 12:17 UTC (permalink / raw)
  To: Adrian Hunter, Gregory CLEMENT, Ulf Hansson, 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, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

Hi Adrian,

On 2016/10/11 20:39, Adrian Hunter wrote:
> On 07/10/16 18:22, Gregory CLEMENT wrote:
>> 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.*
>>  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
> 
> <SNIP>
> 
>> +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;
>> +}
> 
> So you are issuing commands from the ->set_ios() callback.  I would want to
> get Ulf's OK for that before going further.
> 
	Yes, you are correct.
	In some speed mode, Xenon SDHC has to send a series of transfers to search for a perfect sampling point in PHY delay line.
	It is like tuning process.

> One thing: you will need to ensure you don't trigger get HS400 re-tuning
> because it will call back into ->set_ios().
> 
	Could you please make the term "HS400 re-tuning" more detailed?
	In current MMC driver, "HS400 re-tuning" will go back to HS200, execute HS200 tuning and come back to HS400.
	I'm sure our Xenon SDHC will not execute it.

	However, in coming eMMC 5.2, there is a real HS400 re-tuning, in which tuning can be directly executed in HS400 mode.
	Our Xenon SDHC will neither trigger this HS400 re-tuning.
	But since so far there is no such feature in MMC driver, I cannot give you a 100% guarantee now.

> And you have the problem that you need to get a reference to the card before
> the card device has been added.  As I wrote in response to the previous
> patch, you should get Ulf's help with that too.
> 
	Sure.
	I will get card_candidate solved at first.
	Thank you again for your review and help.

	Thank you.

Best regards,
Hu Ziji
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
@ 2016-10-12 12:17       ` Ziji Hu
  0 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-12 12:17 UTC (permalink / raw)
  To: Adrian Hunter, Gregory CLEMENT, Ulf Hansson, 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

Hi Adrian,

On 2016/10/11 20:39, Adrian Hunter wrote:
> On 07/10/16 18:22, Gregory CLEMENT wrote:
>> 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.*
>>  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
> 
> <SNIP>
> 
>> +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;
>> +}
> 
> So you are issuing commands from the ->set_ios() callback.  I would want to
> get Ulf's OK for that before going further.
> 
	Yes, you are correct.
	In some speed mode, Xenon SDHC has to send a series of transfers to search for a perfect sampling point in PHY delay line.
	It is like tuning process.

> One thing: you will need to ensure you don't trigger get HS400 re-tuning
> because it will call back into ->set_ios().
> 
	Could you please make the term "HS400 re-tuning" more detailed?
	In current MMC driver, "HS400 re-tuning" will go back to HS200, execute HS200 tuning and come back to HS400.
	I'm sure our Xenon SDHC will not execute it.

	However, in coming eMMC 5.2, there is a real HS400 re-tuning, in which tuning can be directly executed in HS400 mode.
	Our Xenon SDHC will neither trigger this HS400 re-tuning.
	But since so far there is no such feature in MMC driver, I cannot give you a 100% guarantee now.

> And you have the problem that you need to get a reference to the card before
> the card device has been added.  As I wrote in response to the previous
> patch, you should get Ulf's help with that too.
> 
	Sure.
	I will get card_candidate solved at first.
	Thank you again for your review and help.

	Thank you.

Best regards,
Hu Ziji
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
@ 2016-10-12 12:17       ` Ziji Hu
  0 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-12 12:17 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Adrian,

On 2016/10/11 20:39, Adrian Hunter wrote:
> On 07/10/16 18:22, Gregory CLEMENT wrote:
>> 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 at vger.kernel.org
>>  S:	Supported
>>  F:	drivers/mmc/host/sdhci-xenon.*
>> +F:	drivers/mmc/host/sdhci-xenon-phy.*
>>  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
> 
> <SNIP>
> 
>> +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;
>> +}
> 
> So you are issuing commands from the ->set_ios() callback.  I would want to
> get Ulf's OK for that before going further.
> 
	Yes, you are correct.
	In some speed mode, Xenon SDHC has to send a series of transfers to search for a perfect sampling point in PHY delay line.
	It is like tuning process.

> One thing: you will need to ensure you don't trigger get HS400 re-tuning
> because it will call back into ->set_ios().
> 
	Could you please make the term "HS400 re-tuning" more detailed?
	In current MMC driver, "HS400 re-tuning" will go back to HS200, execute HS200 tuning and come back to HS400.
	I'm sure our Xenon SDHC will not execute it.

	However, in coming eMMC 5.2, there is a real HS400 re-tuning, in which tuning can be directly executed in HS400 mode.
	Our Xenon SDHC will neither trigger this HS400 re-tuning.
	But since so far there is no such feature in MMC driver, I cannot give you a 100% guarantee now.

> And you have the problem that you need to get a reference to the card before
> the card device has been added.  As I wrote in response to the previous
> patch, you should get Ulf's help with that too.
> 
	Sure.
	I will get card_candidate solved at first.
	Thank you again for your review and help.

	Thank you.

Best regards,
Hu Ziji
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
  2016-10-12 11:58       ` Ziji Hu
  (?)
@ 2016-10-12 13:07         ` Adrian Hunter
  -1 siblings, 0 replies; 105+ messages in thread
From: Adrian Hunter @ 2016-10-12 13:07 UTC (permalink / raw)
  To: Ziji Hu, Gregory CLEMENT, Ulf Hansson, 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, Keji Zhang, Liuliu Zhao, Peng Zhu, Yu Cao,
	Romain Perier, Yehuda Yitschak, Marcin Wojtas, Hanna Hawa,
	Kostya Porotchkin, linux-kernel

On 12/10/16 14:58, Ziji Hu wrote:
> Hi Adrian,
> 
> 	Thank you very much for your review.
> 	I will firstly fix the typo.
> 
> On 2016/10/11 20:37, Adrian Hunter wrote:
>> On 07/10/16 18:22, Gregory CLEMENT wrote:
>>> From: Ziji Hu <huziji@marvell.com>
>>>
>>> Add Xenon eMMC/SD/SDIO host controller core functionality.
>>> Add Xenon specific intialization process.
>>> Add Xenon specific mmc_host_ops APIs.
>>> Add Xenon specific register definitions.
>>>
>>> Add CONFIG_MMC_SDHCI_XENON support in drivers/mmc/host/Kconfig.
>>>
>>> Marvell Xenon SDHC conforms to SD Physical Layer Specification
>>> Version 3.01 and is designed according to the guidelines provided
>>> in the SD Host Controller Standard Specification Version 3.00.
>>>
>>> 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>
>>
>> I looked at a couple of things but you need to sort out the issues with
>> card_candidate before going further.
>>
> 	Understood.
> 	I will improve the card_candidate. Please help check the details in below.
> 
>>> ---
> <snip>
>>> +
>>> +static int xenon_emmc_signal_voltage_switch(struct mmc_host *mmc,
>>> +					    struct mmc_ios *ios)
>>> +{
>>> +	unsigned char voltage = ios->signal_voltage;
>>> +
>>> +	if ((voltage == MMC_SIGNAL_VOLTAGE_330) ||
>>> +	    (voltage == MMC_SIGNAL_VOLTAGE_180))
>>> +		return __emmc_signal_voltage_switch(mmc, voltage);
>>> +
>>> +	dev_err(mmc_dev(mmc), "Unsupported signal voltage: %d\n",
>>> +		voltage);
>>> +	return -EINVAL;
>>> +}
>>> +
>>> +static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
>>> +					     struct mmc_ios *ios)
>>> +{
>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> +
>>> +	/*
>>> +	 * Before SD/SDIO set signal voltage, SD bus clock should be
>>> +	 * disabled. However, sdhci_set_clock will also disable the Internal
>>> +	 * clock in mmc_set_signal_voltage().
>>> +	 * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
>>> +	 * Thus here manually enable internal clock.
>>> +	 *
>>> +	 * After switch completes, it is unnecessary to disable internal clock,
>>> +	 * since keeping internal clock active obeys SD spec.
>>> +	 */
>>> +	enable_xenon_internal_clk(host);
>>> +
>>> +	if (priv->card_candidate) {
>>
>> mmc_power_up() calls __mmc_set_signal_voltage() calls
>> host->ops->start_signal_voltage_switch so priv->card_candidate could be an
>> invalid reference to an old card.
>>
>> So that's not going to work if the card changes - not only for removable
>> cards but even for eMMC if init fails and retries.
>>
> 	As you point out, this piece of code have defects, even though it actually works on Marvell multiple platforms, unless eMMC card is removable.
> 
> 	I can add a property to explicitly indicate eMMC type in DTS.
> 	Then card_candidate access can be removed here.
> 	Does it sounds more reasonable to you?

Sure

> 
>>> +		if (mmc_card_mmc(priv->card_candidate))
>>> +			return xenon_emmc_signal_voltage_switch(mmc, ios);
>>
>> So if all you need to know is whether it is a eMMC, why can't DT tell you?
>>
> 	I can add an eMMC type property in DTS, to remove the card_candidate access here.
> 
>>> +	}
>>> +
>>> +	return sdhci_start_signal_voltage_switch(mmc, ios);
>>> +}
>>> +
>>> +/*
>>> + * After determining which slot is used for SDIO,
>>> + * some additional task is required.
>>> + */
>>> +static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
>>> +{
>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>> +	u32 reg;
>>> +	u8 slot_idx;
>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> +
>>> +	/* Link the card for delay adjustment */
>>> +	priv->card_candidate = card;
>>
>> You really need a better way to get the card.  I suggest you take up the
>> issue with Ulf.  One possibility is to have mmc core set host->card = card
>> much earlier.
>>
> 	Could you please tell me if any issue related to card_candidate still exists, after the card_candidate is removed from xenon_start_signal_voltage_switch() in above?
> 	It seems that when init_card is called, the structure card has already been updated and stable in MMC/SD/SDIO initialization sequence.
> 	May I keep it here?

It works by accident rather than by design.  We can do better.

> 
>>> +	/* Set tuning functionality of this slot */
>>> +	xenon_slot_tuning_setup(host);
>>> +
>>> +	slot_idx = priv->slot_idx;
>>> +	if (!mmc_card_sdio(card)) {
>>> +		/* Re-enable the Auto-CMD12 cap flag. */
>>> +		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>> +		host->flags |= SDHCI_AUTO_CMD12;
>>> +
>>> +		/* Clear SDIO Card Inserted indication */
>>> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
>>> +		reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
>>> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
>>> +
>>> +		if (mmc_card_mmc(card)) {
>>> +			mmc->caps |= MMC_CAP_NONREMOVABLE;
>>> +			if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
>>> +				mmc->caps |= MMC_CAP_1_8V_DDR;
>>> +			/*
>>> +			 * Force to clear BUS_TEST to
>>> +			 * skip bus_test_pre and bus_test_post
>>> +			 */
>>> +			mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
>>> +			mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
>>> +				      MMC_CAP2_PACKED_CMD;
>>> +			if (mmc->caps & MMC_CAP_8_BIT_DATA)
>>> +				mmc->caps2 |= MMC_CAP2_HS400_1_8V;
>>> +		}
>>> +	} else {
>>> +		/*
>>> +		 * Delete the Auto-CMD12 cap flag.
>>> +		 * Otherwise, when sending multi-block CMD53,
>>> +		 * Driver will set Transfer Mode Register to enable Auto CMD12.
>>> +		 * However, SDIO device cannot recognize CMD12.
>>> +		 * Thus SDHC will time-out for waiting for CMD12 response.
>>> +		 */
>>> +		host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>> +		host->flags &= ~SDHCI_AUTO_CMD12;
>>
>> sdhci_set_transfer_mode() won't enable auto-CMD12 for CMD53 anyway, so is
>> this needed?
>>
> 	In Xenon driver, Auto-CMD12 flag is set to enable full support to Auto-CMD feature, both Auto-CMD12 and Auto-CMD23.
> 	As a result, when Xenon SDHC slot can both support SD and SDIO, Auto-CMD12 is disabled when SDIO card is inserted, and renabled when SD is inserted.
> 
> 	I recheck the sdhci code to set Auto-CMD bit in Transfer Mode register, in sdhci_set_transfer_mode():
> 	if (mmc_op_multi(cmd->opcode) || data->blocks > 1)
> 	As you can see, as long as it is CMD18/CMD25 OR there are multiple data blocks, Auto-CMD field will be set.
> 	CMD53 doesn't have CMD23. Thus Auto-CMD12 is selected since Auto-CMD12 flag is set.
> 	Thus I have to clear Auto-CMD12 to avoid issuing Auto-CMD12 in SDIO transfer.


The code is:

	if (mmc_op_multi(cmd->opcode) || data->blocks > 1) {
		mode = SDHCI_TRNS_BLK_CNT_EN | SDHCI_TRNS_MULTI;
		/*
		 * If we are sending CMD23, CMD12 never gets sent
		 * on successful completion (so no Auto-CMD12).
		 */
		if (sdhci_auto_cmd12(host, cmd->mrq) &&
		    (cmd->opcode != SD_IO_RW_EXTENDED))
			mode |= SDHCI_TRNS_AUTO_CMD12;
		else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
			mode |= SDHCI_TRNS_AUTO_CMD23;
			sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);
		}
	}

You can see the check for SD_IO_RW_EXTENDED which is CMD53.

> 
> 	I just meet a similar issue in RPMB.
> 	When Auto-CMD12 flag is set, eMMC RPMB access will trigger Auto-CMD12, since CMD25 is in use.
> 	It will cause RPMB access failed.

Can you explain more about the RPMB issue.  Doesn't it use CMD23, so CMD12
wouldn't be used - auto or manually.

> 
> 	One possible solution is to drop Auto-CMD12 support and use Auto-CMD23 only, in Xenon driver.
> 	May I know you opinion, please?

I don't use auto-CMD12 because I don't know if it provides any benefit and
sdhci does not seem to have implemented Auto CMD12 Error Recovery, although
I have never looked at it closely.

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
@ 2016-10-12 13:07         ` Adrian Hunter
  0 siblings, 0 replies; 105+ messages in thread
From: Adrian Hunter @ 2016-10-12 13:07 UTC (permalink / raw)
  To: Ziji Hu, Gregory CLEMENT, Ulf Hansson, 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, Hanna Hawa,
	Kostya Porotchkin, Rob Herring, Ryan Gao, Wei(SOCP) Liu,
	linux-arm-kernel, Thomas Petazzoni

On 12/10/16 14:58, Ziji Hu wrote:
> Hi Adrian,
> 
> 	Thank you very much for your review.
> 	I will firstly fix the typo.
> 
> On 2016/10/11 20:37, Adrian Hunter wrote:
>> On 07/10/16 18:22, Gregory CLEMENT wrote:
>>> From: Ziji Hu <huziji@marvell.com>
>>>
>>> Add Xenon eMMC/SD/SDIO host controller core functionality.
>>> Add Xenon specific intialization process.
>>> Add Xenon specific mmc_host_ops APIs.
>>> Add Xenon specific register definitions.
>>>
>>> Add CONFIG_MMC_SDHCI_XENON support in drivers/mmc/host/Kconfig.
>>>
>>> Marvell Xenon SDHC conforms to SD Physical Layer Specification
>>> Version 3.01 and is designed according to the guidelines provided
>>> in the SD Host Controller Standard Specification Version 3.00.
>>>
>>> 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>
>>
>> I looked at a couple of things but you need to sort out the issues with
>> card_candidate before going further.
>>
> 	Understood.
> 	I will improve the card_candidate. Please help check the details in below.
> 
>>> ---
> <snip>
>>> +
>>> +static int xenon_emmc_signal_voltage_switch(struct mmc_host *mmc,
>>> +					    struct mmc_ios *ios)
>>> +{
>>> +	unsigned char voltage = ios->signal_voltage;
>>> +
>>> +	if ((voltage == MMC_SIGNAL_VOLTAGE_330) ||
>>> +	    (voltage == MMC_SIGNAL_VOLTAGE_180))
>>> +		return __emmc_signal_voltage_switch(mmc, voltage);
>>> +
>>> +	dev_err(mmc_dev(mmc), "Unsupported signal voltage: %d\n",
>>> +		voltage);
>>> +	return -EINVAL;
>>> +}
>>> +
>>> +static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
>>> +					     struct mmc_ios *ios)
>>> +{
>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> +
>>> +	/*
>>> +	 * Before SD/SDIO set signal voltage, SD bus clock should be
>>> +	 * disabled. However, sdhci_set_clock will also disable the Internal
>>> +	 * clock in mmc_set_signal_voltage().
>>> +	 * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
>>> +	 * Thus here manually enable internal clock.
>>> +	 *
>>> +	 * After switch completes, it is unnecessary to disable internal clock,
>>> +	 * since keeping internal clock active obeys SD spec.
>>> +	 */
>>> +	enable_xenon_internal_clk(host);
>>> +
>>> +	if (priv->card_candidate) {
>>
>> mmc_power_up() calls __mmc_set_signal_voltage() calls
>> host->ops->start_signal_voltage_switch so priv->card_candidate could be an
>> invalid reference to an old card.
>>
>> So that's not going to work if the card changes - not only for removable
>> cards but even for eMMC if init fails and retries.
>>
> 	As you point out, this piece of code have defects, even though it actually works on Marvell multiple platforms, unless eMMC card is removable.
> 
> 	I can add a property to explicitly indicate eMMC type in DTS.
> 	Then card_candidate access can be removed here.
> 	Does it sounds more reasonable to you?

Sure

> 
>>> +		if (mmc_card_mmc(priv->card_candidate))
>>> +			return xenon_emmc_signal_voltage_switch(mmc, ios);
>>
>> So if all you need to know is whether it is a eMMC, why can't DT tell you?
>>
> 	I can add an eMMC type property in DTS, to remove the card_candidate access here.
> 
>>> +	}
>>> +
>>> +	return sdhci_start_signal_voltage_switch(mmc, ios);
>>> +}
>>> +
>>> +/*
>>> + * After determining which slot is used for SDIO,
>>> + * some additional task is required.
>>> + */
>>> +static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
>>> +{
>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>> +	u32 reg;
>>> +	u8 slot_idx;
>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> +
>>> +	/* Link the card for delay adjustment */
>>> +	priv->card_candidate = card;
>>
>> You really need a better way to get the card.  I suggest you take up the
>> issue with Ulf.  One possibility is to have mmc core set host->card = card
>> much earlier.
>>
> 	Could you please tell me if any issue related to card_candidate still exists, after the card_candidate is removed from xenon_start_signal_voltage_switch() in above?
> 	It seems that when init_card is called, the structure card has already been updated and stable in MMC/SD/SDIO initialization sequence.
> 	May I keep it here?

It works by accident rather than by design.  We can do better.

> 
>>> +	/* Set tuning functionality of this slot */
>>> +	xenon_slot_tuning_setup(host);
>>> +
>>> +	slot_idx = priv->slot_idx;
>>> +	if (!mmc_card_sdio(card)) {
>>> +		/* Re-enable the Auto-CMD12 cap flag. */
>>> +		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>> +		host->flags |= SDHCI_AUTO_CMD12;
>>> +
>>> +		/* Clear SDIO Card Inserted indication */
>>> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
>>> +		reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
>>> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
>>> +
>>> +		if (mmc_card_mmc(card)) {
>>> +			mmc->caps |= MMC_CAP_NONREMOVABLE;
>>> +			if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
>>> +				mmc->caps |= MMC_CAP_1_8V_DDR;
>>> +			/*
>>> +			 * Force to clear BUS_TEST to
>>> +			 * skip bus_test_pre and bus_test_post
>>> +			 */
>>> +			mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
>>> +			mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
>>> +				      MMC_CAP2_PACKED_CMD;
>>> +			if (mmc->caps & MMC_CAP_8_BIT_DATA)
>>> +				mmc->caps2 |= MMC_CAP2_HS400_1_8V;
>>> +		}
>>> +	} else {
>>> +		/*
>>> +		 * Delete the Auto-CMD12 cap flag.
>>> +		 * Otherwise, when sending multi-block CMD53,
>>> +		 * Driver will set Transfer Mode Register to enable Auto CMD12.
>>> +		 * However, SDIO device cannot recognize CMD12.
>>> +		 * Thus SDHC will time-out for waiting for CMD12 response.
>>> +		 */
>>> +		host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>> +		host->flags &= ~SDHCI_AUTO_CMD12;
>>
>> sdhci_set_transfer_mode() won't enable auto-CMD12 for CMD53 anyway, so is
>> this needed?
>>
> 	In Xenon driver, Auto-CMD12 flag is set to enable full support to Auto-CMD feature, both Auto-CMD12 and Auto-CMD23.
> 	As a result, when Xenon SDHC slot can both support SD and SDIO, Auto-CMD12 is disabled when SDIO card is inserted, and renabled when SD is inserted.
> 
> 	I recheck the sdhci code to set Auto-CMD bit in Transfer Mode register, in sdhci_set_transfer_mode():
> 	if (mmc_op_multi(cmd->opcode) || data->blocks > 1)
> 	As you can see, as long as it is CMD18/CMD25 OR there are multiple data blocks, Auto-CMD field will be set.
> 	CMD53 doesn't have CMD23. Thus Auto-CMD12 is selected since Auto-CMD12 flag is set.
> 	Thus I have to clear Auto-CMD12 to avoid issuing Auto-CMD12 in SDIO transfer.


The code is:

	if (mmc_op_multi(cmd->opcode) || data->blocks > 1) {
		mode = SDHCI_TRNS_BLK_CNT_EN | SDHCI_TRNS_MULTI;
		/*
		 * If we are sending CMD23, CMD12 never gets sent
		 * on successful completion (so no Auto-CMD12).
		 */
		if (sdhci_auto_cmd12(host, cmd->mrq) &&
		    (cmd->opcode != SD_IO_RW_EXTENDED))
			mode |= SDHCI_TRNS_AUTO_CMD12;
		else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
			mode |= SDHCI_TRNS_AUTO_CMD23;
			sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);
		}
	}

You can see the check for SD_IO_RW_EXTENDED which is CMD53.

> 
> 	I just meet a similar issue in RPMB.
> 	When Auto-CMD12 flag is set, eMMC RPMB access will trigger Auto-CMD12, since CMD25 is in use.
> 	It will cause RPMB access failed.

Can you explain more about the RPMB issue.  Doesn't it use CMD23, so CMD12
wouldn't be used - auto or manually.

> 
> 	One possible solution is to drop Auto-CMD12 support and use Auto-CMD23 only, in Xenon driver.
> 	May I know you opinion, please?

I don't use auto-CMD12 because I don't know if it provides any benefit and
sdhci does not seem to have implemented Auto CMD12 Error Recovery, although
I have never looked at it closely.

^ permalink raw reply	[flat|nested] 105+ messages in thread

* [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
@ 2016-10-12 13:07         ` Adrian Hunter
  0 siblings, 0 replies; 105+ messages in thread
From: Adrian Hunter @ 2016-10-12 13:07 UTC (permalink / raw)
  To: linux-arm-kernel

On 12/10/16 14:58, Ziji Hu wrote:
> Hi Adrian,
> 
> 	Thank you very much for your review.
> 	I will firstly fix the typo.
> 
> On 2016/10/11 20:37, Adrian Hunter wrote:
>> On 07/10/16 18:22, Gregory CLEMENT wrote:
>>> From: Ziji Hu <huziji@marvell.com>
>>>
>>> Add Xenon eMMC/SD/SDIO host controller core functionality.
>>> Add Xenon specific intialization process.
>>> Add Xenon specific mmc_host_ops APIs.
>>> Add Xenon specific register definitions.
>>>
>>> Add CONFIG_MMC_SDHCI_XENON support in drivers/mmc/host/Kconfig.
>>>
>>> Marvell Xenon SDHC conforms to SD Physical Layer Specification
>>> Version 3.01 and is designed according to the guidelines provided
>>> in the SD Host Controller Standard Specification Version 3.00.
>>>
>>> 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>
>>
>> I looked at a couple of things but you need to sort out the issues with
>> card_candidate before going further.
>>
> 	Understood.
> 	I will improve the card_candidate. Please help check the details in below.
> 
>>> ---
> <snip>
>>> +
>>> +static int xenon_emmc_signal_voltage_switch(struct mmc_host *mmc,
>>> +					    struct mmc_ios *ios)
>>> +{
>>> +	unsigned char voltage = ios->signal_voltage;
>>> +
>>> +	if ((voltage == MMC_SIGNAL_VOLTAGE_330) ||
>>> +	    (voltage == MMC_SIGNAL_VOLTAGE_180))
>>> +		return __emmc_signal_voltage_switch(mmc, voltage);
>>> +
>>> +	dev_err(mmc_dev(mmc), "Unsupported signal voltage: %d\n",
>>> +		voltage);
>>> +	return -EINVAL;
>>> +}
>>> +
>>> +static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
>>> +					     struct mmc_ios *ios)
>>> +{
>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> +
>>> +	/*
>>> +	 * Before SD/SDIO set signal voltage, SD bus clock should be
>>> +	 * disabled. However, sdhci_set_clock will also disable the Internal
>>> +	 * clock in mmc_set_signal_voltage().
>>> +	 * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
>>> +	 * Thus here manually enable internal clock.
>>> +	 *
>>> +	 * After switch completes, it is unnecessary to disable internal clock,
>>> +	 * since keeping internal clock active obeys SD spec.
>>> +	 */
>>> +	enable_xenon_internal_clk(host);
>>> +
>>> +	if (priv->card_candidate) {
>>
>> mmc_power_up() calls __mmc_set_signal_voltage() calls
>> host->ops->start_signal_voltage_switch so priv->card_candidate could be an
>> invalid reference to an old card.
>>
>> So that's not going to work if the card changes - not only for removable
>> cards but even for eMMC if init fails and retries.
>>
> 	As you point out, this piece of code have defects, even though it actually works on Marvell multiple platforms, unless eMMC card is removable.
> 
> 	I can add a property to explicitly indicate eMMC type in DTS.
> 	Then card_candidate access can be removed here.
> 	Does it sounds more reasonable to you?

Sure

> 
>>> +		if (mmc_card_mmc(priv->card_candidate))
>>> +			return xenon_emmc_signal_voltage_switch(mmc, ios);
>>
>> So if all you need to know is whether it is a eMMC, why can't DT tell you?
>>
> 	I can add an eMMC type property in DTS, to remove the card_candidate access here.
> 
>>> +	}
>>> +
>>> +	return sdhci_start_signal_voltage_switch(mmc, ios);
>>> +}
>>> +
>>> +/*
>>> + * After determining which slot is used for SDIO,
>>> + * some additional task is required.
>>> + */
>>> +static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
>>> +{
>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>> +	u32 reg;
>>> +	u8 slot_idx;
>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>> +
>>> +	/* Link the card for delay adjustment */
>>> +	priv->card_candidate = card;
>>
>> You really need a better way to get the card.  I suggest you take up the
>> issue with Ulf.  One possibility is to have mmc core set host->card = card
>> much earlier.
>>
> 	Could you please tell me if any issue related to card_candidate still exists, after the card_candidate is removed from xenon_start_signal_voltage_switch() in above?
> 	It seems that when init_card is called, the structure card has already been updated and stable in MMC/SD/SDIO initialization sequence.
> 	May I keep it here?

It works by accident rather than by design.  We can do better.

> 
>>> +	/* Set tuning functionality of this slot */
>>> +	xenon_slot_tuning_setup(host);
>>> +
>>> +	slot_idx = priv->slot_idx;
>>> +	if (!mmc_card_sdio(card)) {
>>> +		/* Re-enable the Auto-CMD12 cap flag. */
>>> +		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>> +		host->flags |= SDHCI_AUTO_CMD12;
>>> +
>>> +		/* Clear SDIO Card Inserted indication */
>>> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
>>> +		reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
>>> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
>>> +
>>> +		if (mmc_card_mmc(card)) {
>>> +			mmc->caps |= MMC_CAP_NONREMOVABLE;
>>> +			if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
>>> +				mmc->caps |= MMC_CAP_1_8V_DDR;
>>> +			/*
>>> +			 * Force to clear BUS_TEST to
>>> +			 * skip bus_test_pre and bus_test_post
>>> +			 */
>>> +			mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
>>> +			mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
>>> +				      MMC_CAP2_PACKED_CMD;
>>> +			if (mmc->caps & MMC_CAP_8_BIT_DATA)
>>> +				mmc->caps2 |= MMC_CAP2_HS400_1_8V;
>>> +		}
>>> +	} else {
>>> +		/*
>>> +		 * Delete the Auto-CMD12 cap flag.
>>> +		 * Otherwise, when sending multi-block CMD53,
>>> +		 * Driver will set Transfer Mode Register to enable Auto CMD12.
>>> +		 * However, SDIO device cannot recognize CMD12.
>>> +		 * Thus SDHC will time-out for waiting for CMD12 response.
>>> +		 */
>>> +		host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>> +		host->flags &= ~SDHCI_AUTO_CMD12;
>>
>> sdhci_set_transfer_mode() won't enable auto-CMD12 for CMD53 anyway, so is
>> this needed?
>>
> 	In Xenon driver, Auto-CMD12 flag is set to enable full support to Auto-CMD feature, both Auto-CMD12 and Auto-CMD23.
> 	As a result, when Xenon SDHC slot can both support SD and SDIO, Auto-CMD12 is disabled when SDIO card is inserted, and renabled when SD is inserted.
> 
> 	I recheck the sdhci code to set Auto-CMD bit in Transfer Mode register, in sdhci_set_transfer_mode():
> 	if (mmc_op_multi(cmd->opcode) || data->blocks > 1)
> 	As you can see, as long as it is CMD18/CMD25 OR there are multiple data blocks, Auto-CMD field will be set.
> 	CMD53 doesn't have CMD23. Thus Auto-CMD12 is selected since Auto-CMD12 flag is set.
> 	Thus I have to clear Auto-CMD12 to avoid issuing Auto-CMD12 in SDIO transfer.


The code is:

	if (mmc_op_multi(cmd->opcode) || data->blocks > 1) {
		mode = SDHCI_TRNS_BLK_CNT_EN | SDHCI_TRNS_MULTI;
		/*
		 * If we are sending CMD23, CMD12 never gets sent
		 * on successful completion (so no Auto-CMD12).
		 */
		if (sdhci_auto_cmd12(host, cmd->mrq) &&
		    (cmd->opcode != SD_IO_RW_EXTENDED))
			mode |= SDHCI_TRNS_AUTO_CMD12;
		else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
			mode |= SDHCI_TRNS_AUTO_CMD23;
			sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);
		}
	}

You can see the check for SD_IO_RW_EXTENDED which is CMD53.

> 
> 	I just meet a similar issue in RPMB.
> 	When Auto-CMD12 flag is set, eMMC RPMB access will trigger Auto-CMD12, since CMD25 is in use.
> 	It will cause RPMB access failed.

Can you explain more about the RPMB issue.  Doesn't it use CMD23, so CMD12
wouldn't be used - auto or manually.

> 
> 	One possible solution is to drop Auto-CMD12 support and use Auto-CMD23 only, in Xenon driver.
> 	May I know you opinion, please?

I don't use auto-CMD12 because I don't know if it provides any benefit and
sdhci does not seem to have implemented Auto CMD12 Error Recovery, although
I have never looked at it closely.

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
  2016-10-12 13:07         ` Adrian Hunter
  (?)
@ 2016-10-13  5:38           ` Ziji Hu
  -1 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-13  5:38 UTC (permalink / raw)
  To: Adrian Hunter, Gregory CLEMENT, Ulf Hansson, 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, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

Hi Adrian,

On 2016/10/12 21:07, Adrian Hunter wrote:
> On 12/10/16 14:58, Ziji Hu wrote:
>> Hi Adrian,
>>
>> 	Thank you very much for your review.
>> 	I will firstly fix the typo.
>>
>> On 2016/10/11 20:37, Adrian Hunter wrote:
<snip>
>>>> +
>>>> +static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
>>>> +					     struct mmc_ios *ios)
>>>> +{
>>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>>> +
>>>> +	/*
>>>> +	 * Before SD/SDIO set signal voltage, SD bus clock should be
>>>> +	 * disabled. However, sdhci_set_clock will also disable the Internal
>>>> +	 * clock in mmc_set_signal_voltage().
>>>> +	 * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
>>>> +	 * Thus here manually enable internal clock.
>>>> +	 *
>>>> +	 * After switch completes, it is unnecessary to disable internal clock,
>>>> +	 * since keeping internal clock active obeys SD spec.
>>>> +	 */
>>>> +	enable_xenon_internal_clk(host);
>>>> +
>>>> +	if (priv->card_candidate) {
>>>
>>> mmc_power_up() calls __mmc_set_signal_voltage() calls
>>> host->ops->start_signal_voltage_switch so priv->card_candidate could be an
>>> invalid reference to an old card.
>>>
>>> So that's not going to work if the card changes - not only for removable
>>> cards but even for eMMC if init fails and retries.
>>>
>> 	As you point out, this piece of code have defects, even though it actually works on Marvell multiple platforms, unless eMMC card is removable.
>>
>> 	I can add a property to explicitly indicate eMMC type in DTS.
>> 	Then card_candidate access can be removed here.
>> 	Does it sounds more reasonable to you?
> 
> Sure
> 
>>
>>>> +		if (mmc_card_mmc(priv->card_candidate))
>>>> +			return xenon_emmc_signal_voltage_switch(mmc, ios);
>>>
>>> So if all you need to know is whether it is a eMMC, why can't DT tell you?
>>>
>> 	I can add an eMMC type property in DTS, to remove the card_candidate access here.
>>
>>>> +	}
>>>> +
>>>> +	return sdhci_start_signal_voltage_switch(mmc, ios);
>>>> +}
>>>> +
>>>> +/*
>>>> + * After determining which slot is used for SDIO,
>>>> + * some additional task is required.
>>>> + */
>>>> +static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
>>>> +{
>>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>>> +	u32 reg;
>>>> +	u8 slot_idx;
>>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>>> +
>>>> +	/* Link the card for delay adjustment */
>>>> +	priv->card_candidate = card;
>>>
>>> You really need a better way to get the card.  I suggest you take up the
>>> issue with Ulf.  One possibility is to have mmc core set host->card = card
>>> much earlier.
>>>
>> 	Could you please tell me if any issue related to card_candidate still exists, after the card_candidate is removed from xenon_start_signal_voltage_switch() in above?
>> 	It seems that when init_card is called, the structure card has already been updated and stable in MMC/SD/SDIO initialization sequence.
>> 	May I keep it here?
> 
> It works by accident rather than by design.  We can do better.
> 
	Could you please tell me some details which are satisfied about card_candidate?

	I must admit that card_candidate in xenon_start_signal_voltage_switch() is imperfect.
	But card_candidate in init_card() and later in set_ios() work by design, rather than by accident. We did a lot of tests on several platforms.
	
	The structure mmc_card passed in here is a stable one. Thus in my very own opinion, it is safe and stable to use mmc_card here.
	card_candidate is used only in card initialization. It is not active in later transfers after initialization is done.
	It will always updated with mmc_card in next card initialization.

>>
>>>> +	/* Set tuning functionality of this slot */
>>>> +	xenon_slot_tuning_setup(host);
>>>> +
>>>> +	slot_idx = priv->slot_idx;
>>>> +	if (!mmc_card_sdio(card)) {
>>>> +		/* Re-enable the Auto-CMD12 cap flag. */
>>>> +		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>>> +		host->flags |= SDHCI_AUTO_CMD12;
>>>> +
>>>> +		/* Clear SDIO Card Inserted indication */
>>>> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
>>>> +		reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
>>>> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
>>>> +
>>>> +		if (mmc_card_mmc(card)) {
>>>> +			mmc->caps |= MMC_CAP_NONREMOVABLE;
>>>> +			if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
>>>> +				mmc->caps |= MMC_CAP_1_8V_DDR;
>>>> +			/*
>>>> +			 * Force to clear BUS_TEST to
>>>> +			 * skip bus_test_pre and bus_test_post
>>>> +			 */
>>>> +			mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
>>>> +			mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
>>>> +				      MMC_CAP2_PACKED_CMD;
>>>> +			if (mmc->caps & MMC_CAP_8_BIT_DATA)
>>>> +				mmc->caps2 |= MMC_CAP2_HS400_1_8V;
>>>> +		}
>>>> +	} else {
>>>> +		/*
>>>> +		 * Delete the Auto-CMD12 cap flag.
>>>> +		 * Otherwise, when sending multi-block CMD53,
>>>> +		 * Driver will set Transfer Mode Register to enable Auto CMD12.
>>>> +		 * However, SDIO device cannot recognize CMD12.
>>>> +		 * Thus SDHC will time-out for waiting for CMD12 response.
>>>> +		 */
>>>> +		host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>>> +		host->flags &= ~SDHCI_AUTO_CMD12;
>>>
>>> sdhci_set_transfer_mode() won't enable auto-CMD12 for CMD53 anyway, so is
>>> this needed?
>>>
>> 	In Xenon driver, Auto-CMD12 flag is set to enable full support to Auto-CMD feature, both Auto-CMD12 and Auto-CMD23.
>> 	As a result, when Xenon SDHC slot can both support SD and SDIO, Auto-CMD12 is disabled when SDIO card is inserted, and renabled when SD is inserted.
>>
>> 	I recheck the sdhci code to set Auto-CMD bit in Transfer Mode register, in sdhci_set_transfer_mode():
>> 	if (mmc_op_multi(cmd->opcode) || data->blocks > 1)
>> 	As you can see, as long as it is CMD18/CMD25 OR there are multiple data blocks, Auto-CMD field will be set.
>> 	CMD53 doesn't have CMD23. Thus Auto-CMD12 is selected since Auto-CMD12 flag is set.
>> 	Thus I have to clear Auto-CMD12 to avoid issuing Auto-CMD12 in SDIO transfer.
> 
> 
> The code is:
> 
> 	if (mmc_op_multi(cmd->opcode) || data->blocks > 1) {
> 		mode = SDHCI_TRNS_BLK_CNT_EN | SDHCI_TRNS_MULTI;
> 		/*
> 		 * If we are sending CMD23, CMD12 never gets sent
> 		 * on successful completion (so no Auto-CMD12).
> 		 */
> 		if (sdhci_auto_cmd12(host, cmd->mrq) &&
> 		    (cmd->opcode != SD_IO_RW_EXTENDED))
> 			mode |= SDHCI_TRNS_AUTO_CMD12;
> 		else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
> 			mode |= SDHCI_TRNS_AUTO_CMD23;
> 			sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);
> 		}
> 	}
> 
> You can see the check for SD_IO_RW_EXTENDED which is CMD53.
> 
	Sorry. I didn't notice CMD53 check was added.
	I introduced this Auto-CMD12 hack since kernel 3.8. It seems that this check is not added in kernel 3.8.
	Thanks for the information. I will remove the Auto-CMD12 hack.

>>
>> 	I just meet a similar issue in RPMB.
>> 	When Auto-CMD12 flag is set, eMMC RPMB access will trigger Auto-CMD12, since CMD25 is in use.
>> 	It will cause RPMB access failed.
> 
> Can you explain more about the RPMB issue.  Doesn't it use CMD23, so CMD12
> wouldn't be used - auto or manually.
> 
	RPMB go through the MMC ioctl routine.
	Unlike normal data transfer, MMC ioctl for RPMB explicitly issues CMD23. When CMD25 is issued, there is neither data->sbc nor Auto-CMD23.
	As a result, sdhci driver will automatically enable Auto-CMD12 for RPMB CMD25 if Auto-CMD12 flag is set.

>>
>> 	One possible solution is to drop Auto-CMD12 support and use Auto-CMD23 only, in Xenon driver.
>> 	May I know you opinion, please?
> 
> I don't use auto-CMD12 because I don't know if it provides any benefit and
> sdhci does not seem to have implemented Auto CMD12 Error Recovery, although
> I have never looked at it closely.
>
	Actually, Auto-CMD23 is always used on our Xenon. Auto-CMD12 is not used at all.
	But since this driver is a general one for all Marvell products, Auto-CMD12 is also supported in case that Auto-CMD23 is not available.
 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
@ 2016-10-13  5:38           ` Ziji Hu
  0 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-13  5:38 UTC (permalink / raw)
  To: Adrian Hunter, Gregory CLEMENT, Ulf Hansson, 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

Hi Adrian,

On 2016/10/12 21:07, Adrian Hunter wrote:
> On 12/10/16 14:58, Ziji Hu wrote:
>> Hi Adrian,
>>
>> 	Thank you very much for your review.
>> 	I will firstly fix the typo.
>>
>> On 2016/10/11 20:37, Adrian Hunter wrote:
<snip>
>>>> +
>>>> +static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
>>>> +					     struct mmc_ios *ios)
>>>> +{
>>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>>> +
>>>> +	/*
>>>> +	 * Before SD/SDIO set signal voltage, SD bus clock should be
>>>> +	 * disabled. However, sdhci_set_clock will also disable the Internal
>>>> +	 * clock in mmc_set_signal_voltage().
>>>> +	 * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
>>>> +	 * Thus here manually enable internal clock.
>>>> +	 *
>>>> +	 * After switch completes, it is unnecessary to disable internal clock,
>>>> +	 * since keeping internal clock active obeys SD spec.
>>>> +	 */
>>>> +	enable_xenon_internal_clk(host);
>>>> +
>>>> +	if (priv->card_candidate) {
>>>
>>> mmc_power_up() calls __mmc_set_signal_voltage() calls
>>> host->ops->start_signal_voltage_switch so priv->card_candidate could be an
>>> invalid reference to an old card.
>>>
>>> So that's not going to work if the card changes - not only for removable
>>> cards but even for eMMC if init fails and retries.
>>>
>> 	As you point out, this piece of code have defects, even though it actually works on Marvell multiple platforms, unless eMMC card is removable.
>>
>> 	I can add a property to explicitly indicate eMMC type in DTS.
>> 	Then card_candidate access can be removed here.
>> 	Does it sounds more reasonable to you?
> 
> Sure
> 
>>
>>>> +		if (mmc_card_mmc(priv->card_candidate))
>>>> +			return xenon_emmc_signal_voltage_switch(mmc, ios);
>>>
>>> So if all you need to know is whether it is a eMMC, why can't DT tell you?
>>>
>> 	I can add an eMMC type property in DTS, to remove the card_candidate access here.
>>
>>>> +	}
>>>> +
>>>> +	return sdhci_start_signal_voltage_switch(mmc, ios);
>>>> +}
>>>> +
>>>> +/*
>>>> + * After determining which slot is used for SDIO,
>>>> + * some additional task is required.
>>>> + */
>>>> +static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
>>>> +{
>>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>>> +	u32 reg;
>>>> +	u8 slot_idx;
>>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>>> +
>>>> +	/* Link the card for delay adjustment */
>>>> +	priv->card_candidate = card;
>>>
>>> You really need a better way to get the card.  I suggest you take up the
>>> issue with Ulf.  One possibility is to have mmc core set host->card = card
>>> much earlier.
>>>
>> 	Could you please tell me if any issue related to card_candidate still exists, after the card_candidate is removed from xenon_start_signal_voltage_switch() in above?
>> 	It seems that when init_card is called, the structure card has already been updated and stable in MMC/SD/SDIO initialization sequence.
>> 	May I keep it here?
> 
> It works by accident rather than by design.  We can do better.
> 
	Could you please tell me some details which are satisfied about card_candidate?

	I must admit that card_candidate in xenon_start_signal_voltage_switch() is imperfect.
	But card_candidate in init_card() and later in set_ios() work by design, rather than by accident. We did a lot of tests on several platforms.
	
	The structure mmc_card passed in here is a stable one. Thus in my very own opinion, it is safe and stable to use mmc_card here.
	card_candidate is used only in card initialization. It is not active in later transfers after initialization is done.
	It will always updated with mmc_card in next card initialization.

>>
>>>> +	/* Set tuning functionality of this slot */
>>>> +	xenon_slot_tuning_setup(host);
>>>> +
>>>> +	slot_idx = priv->slot_idx;
>>>> +	if (!mmc_card_sdio(card)) {
>>>> +		/* Re-enable the Auto-CMD12 cap flag. */
>>>> +		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>>> +		host->flags |= SDHCI_AUTO_CMD12;
>>>> +
>>>> +		/* Clear SDIO Card Inserted indication */
>>>> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
>>>> +		reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
>>>> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
>>>> +
>>>> +		if (mmc_card_mmc(card)) {
>>>> +			mmc->caps |= MMC_CAP_NONREMOVABLE;
>>>> +			if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
>>>> +				mmc->caps |= MMC_CAP_1_8V_DDR;
>>>> +			/*
>>>> +			 * Force to clear BUS_TEST to
>>>> +			 * skip bus_test_pre and bus_test_post
>>>> +			 */
>>>> +			mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
>>>> +			mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
>>>> +				      MMC_CAP2_PACKED_CMD;
>>>> +			if (mmc->caps & MMC_CAP_8_BIT_DATA)
>>>> +				mmc->caps2 |= MMC_CAP2_HS400_1_8V;
>>>> +		}
>>>> +	} else {
>>>> +		/*
>>>> +		 * Delete the Auto-CMD12 cap flag.
>>>> +		 * Otherwise, when sending multi-block CMD53,
>>>> +		 * Driver will set Transfer Mode Register to enable Auto CMD12.
>>>> +		 * However, SDIO device cannot recognize CMD12.
>>>> +		 * Thus SDHC will time-out for waiting for CMD12 response.
>>>> +		 */
>>>> +		host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>>> +		host->flags &= ~SDHCI_AUTO_CMD12;
>>>
>>> sdhci_set_transfer_mode() won't enable auto-CMD12 for CMD53 anyway, so is
>>> this needed?
>>>
>> 	In Xenon driver, Auto-CMD12 flag is set to enable full support to Auto-CMD feature, both Auto-CMD12 and Auto-CMD23.
>> 	As a result, when Xenon SDHC slot can both support SD and SDIO, Auto-CMD12 is disabled when SDIO card is inserted, and renabled when SD is inserted.
>>
>> 	I recheck the sdhci code to set Auto-CMD bit in Transfer Mode register, in sdhci_set_transfer_mode():
>> 	if (mmc_op_multi(cmd->opcode) || data->blocks > 1)
>> 	As you can see, as long as it is CMD18/CMD25 OR there are multiple data blocks, Auto-CMD field will be set.
>> 	CMD53 doesn't have CMD23. Thus Auto-CMD12 is selected since Auto-CMD12 flag is set.
>> 	Thus I have to clear Auto-CMD12 to avoid issuing Auto-CMD12 in SDIO transfer.
> 
> 
> The code is:
> 
> 	if (mmc_op_multi(cmd->opcode) || data->blocks > 1) {
> 		mode = SDHCI_TRNS_BLK_CNT_EN | SDHCI_TRNS_MULTI;
> 		/*
> 		 * If we are sending CMD23, CMD12 never gets sent
> 		 * on successful completion (so no Auto-CMD12).
> 		 */
> 		if (sdhci_auto_cmd12(host, cmd->mrq) &&
> 		    (cmd->opcode != SD_IO_RW_EXTENDED))
> 			mode |= SDHCI_TRNS_AUTO_CMD12;
> 		else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
> 			mode |= SDHCI_TRNS_AUTO_CMD23;
> 			sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);
> 		}
> 	}
> 
> You can see the check for SD_IO_RW_EXTENDED which is CMD53.
> 
	Sorry. I didn't notice CMD53 check was added.
	I introduced this Auto-CMD12 hack since kernel 3.8. It seems that this check is not added in kernel 3.8.
	Thanks for the information. I will remove the Auto-CMD12 hack.

>>
>> 	I just meet a similar issue in RPMB.
>> 	When Auto-CMD12 flag is set, eMMC RPMB access will trigger Auto-CMD12, since CMD25 is in use.
>> 	It will cause RPMB access failed.
> 
> Can you explain more about the RPMB issue.  Doesn't it use CMD23, so CMD12
> wouldn't be used - auto or manually.
> 
	RPMB go through the MMC ioctl routine.
	Unlike normal data transfer, MMC ioctl for RPMB explicitly issues CMD23. When CMD25 is issued, there is neither data->sbc nor Auto-CMD23.
	As a result, sdhci driver will automatically enable Auto-CMD12 for RPMB CMD25 if Auto-CMD12 flag is set.

>>
>> 	One possible solution is to drop Auto-CMD12 support and use Auto-CMD23 only, in Xenon driver.
>> 	May I know you opinion, please?
> 
> I don't use auto-CMD12 because I don't know if it provides any benefit and
> sdhci does not seem to have implemented Auto CMD12 Error Recovery, although
> I have never looked at it closely.
>
	Actually, Auto-CMD23 is always used on our Xenon. Auto-CMD12 is not used at all.
	But since this driver is a general one for all Marvell products, Auto-CMD12 is also supported in case that Auto-CMD23 is not available.

^ permalink raw reply	[flat|nested] 105+ messages in thread

* [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
@ 2016-10-13  5:38           ` Ziji Hu
  0 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-13  5:38 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Adrian,

On 2016/10/12 21:07, Adrian Hunter wrote:
> On 12/10/16 14:58, Ziji Hu wrote:
>> Hi Adrian,
>>
>> 	Thank you very much for your review.
>> 	I will firstly fix the typo.
>>
>> On 2016/10/11 20:37, Adrian Hunter wrote:
<snip>
>>>> +
>>>> +static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
>>>> +					     struct mmc_ios *ios)
>>>> +{
>>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>>> +
>>>> +	/*
>>>> +	 * Before SD/SDIO set signal voltage, SD bus clock should be
>>>> +	 * disabled. However, sdhci_set_clock will also disable the Internal
>>>> +	 * clock in mmc_set_signal_voltage().
>>>> +	 * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
>>>> +	 * Thus here manually enable internal clock.
>>>> +	 *
>>>> +	 * After switch completes, it is unnecessary to disable internal clock,
>>>> +	 * since keeping internal clock active obeys SD spec.
>>>> +	 */
>>>> +	enable_xenon_internal_clk(host);
>>>> +
>>>> +	if (priv->card_candidate) {
>>>
>>> mmc_power_up() calls __mmc_set_signal_voltage() calls
>>> host->ops->start_signal_voltage_switch so priv->card_candidate could be an
>>> invalid reference to an old card.
>>>
>>> So that's not going to work if the card changes - not only for removable
>>> cards but even for eMMC if init fails and retries.
>>>
>> 	As you point out, this piece of code have defects, even though it actually works on Marvell multiple platforms, unless eMMC card is removable.
>>
>> 	I can add a property to explicitly indicate eMMC type in DTS.
>> 	Then card_candidate access can be removed here.
>> 	Does it sounds more reasonable to you?
> 
> Sure
> 
>>
>>>> +		if (mmc_card_mmc(priv->card_candidate))
>>>> +			return xenon_emmc_signal_voltage_switch(mmc, ios);
>>>
>>> So if all you need to know is whether it is a eMMC, why can't DT tell you?
>>>
>> 	I can add an eMMC type property in DTS, to remove the card_candidate access here.
>>
>>>> +	}
>>>> +
>>>> +	return sdhci_start_signal_voltage_switch(mmc, ios);
>>>> +}
>>>> +
>>>> +/*
>>>> + * After determining which slot is used for SDIO,
>>>> + * some additional task is required.
>>>> + */
>>>> +static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
>>>> +{
>>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>>> +	u32 reg;
>>>> +	u8 slot_idx;
>>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>>> +
>>>> +	/* Link the card for delay adjustment */
>>>> +	priv->card_candidate = card;
>>>
>>> You really need a better way to get the card.  I suggest you take up the
>>> issue with Ulf.  One possibility is to have mmc core set host->card = card
>>> much earlier.
>>>
>> 	Could you please tell me if any issue related to card_candidate still exists, after the card_candidate is removed from xenon_start_signal_voltage_switch() in above?
>> 	It seems that when init_card is called, the structure card has already been updated and stable in MMC/SD/SDIO initialization sequence.
>> 	May I keep it here?
> 
> It works by accident rather than by design.  We can do better.
> 
	Could you please tell me some details which are satisfied about card_candidate?

	I must admit that card_candidate in xenon_start_signal_voltage_switch() is imperfect.
	But card_candidate in init_card() and later in set_ios() work by design, rather than by accident. We did a lot of tests on several platforms.
	
	The structure mmc_card passed in here is a stable one. Thus in my very own opinion, it is safe and stable to use mmc_card here.
	card_candidate is used only in card initialization. It is not active in later transfers after initialization is done.
	It will always updated with mmc_card in next card initialization.

>>
>>>> +	/* Set tuning functionality of this slot */
>>>> +	xenon_slot_tuning_setup(host);
>>>> +
>>>> +	slot_idx = priv->slot_idx;
>>>> +	if (!mmc_card_sdio(card)) {
>>>> +		/* Re-enable the Auto-CMD12 cap flag. */
>>>> +		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>>> +		host->flags |= SDHCI_AUTO_CMD12;
>>>> +
>>>> +		/* Clear SDIO Card Inserted indication */
>>>> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
>>>> +		reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
>>>> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
>>>> +
>>>> +		if (mmc_card_mmc(card)) {
>>>> +			mmc->caps |= MMC_CAP_NONREMOVABLE;
>>>> +			if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
>>>> +				mmc->caps |= MMC_CAP_1_8V_DDR;
>>>> +			/*
>>>> +			 * Force to clear BUS_TEST to
>>>> +			 * skip bus_test_pre and bus_test_post
>>>> +			 */
>>>> +			mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
>>>> +			mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
>>>> +				      MMC_CAP2_PACKED_CMD;
>>>> +			if (mmc->caps & MMC_CAP_8_BIT_DATA)
>>>> +				mmc->caps2 |= MMC_CAP2_HS400_1_8V;
>>>> +		}
>>>> +	} else {
>>>> +		/*
>>>> +		 * Delete the Auto-CMD12 cap flag.
>>>> +		 * Otherwise, when sending multi-block CMD53,
>>>> +		 * Driver will set Transfer Mode Register to enable Auto CMD12.
>>>> +		 * However, SDIO device cannot recognize CMD12.
>>>> +		 * Thus SDHC will time-out for waiting for CMD12 response.
>>>> +		 */
>>>> +		host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>>> +		host->flags &= ~SDHCI_AUTO_CMD12;
>>>
>>> sdhci_set_transfer_mode() won't enable auto-CMD12 for CMD53 anyway, so is
>>> this needed?
>>>
>> 	In Xenon driver, Auto-CMD12 flag is set to enable full support to Auto-CMD feature, both Auto-CMD12 and Auto-CMD23.
>> 	As a result, when Xenon SDHC slot can both support SD and SDIO, Auto-CMD12 is disabled when SDIO card is inserted, and renabled when SD is inserted.
>>
>> 	I recheck the sdhci code to set Auto-CMD bit in Transfer Mode register, in sdhci_set_transfer_mode():
>> 	if (mmc_op_multi(cmd->opcode) || data->blocks > 1)
>> 	As you can see, as long as it is CMD18/CMD25 OR there are multiple data blocks, Auto-CMD field will be set.
>> 	CMD53 doesn't have CMD23. Thus Auto-CMD12 is selected since Auto-CMD12 flag is set.
>> 	Thus I have to clear Auto-CMD12 to avoid issuing Auto-CMD12 in SDIO transfer.
> 
> 
> The code is:
> 
> 	if (mmc_op_multi(cmd->opcode) || data->blocks > 1) {
> 		mode = SDHCI_TRNS_BLK_CNT_EN | SDHCI_TRNS_MULTI;
> 		/*
> 		 * If we are sending CMD23, CMD12 never gets sent
> 		 * on successful completion (so no Auto-CMD12).
> 		 */
> 		if (sdhci_auto_cmd12(host, cmd->mrq) &&
> 		    (cmd->opcode != SD_IO_RW_EXTENDED))
> 			mode |= SDHCI_TRNS_AUTO_CMD12;
> 		else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
> 			mode |= SDHCI_TRNS_AUTO_CMD23;
> 			sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);
> 		}
> 	}
> 
> You can see the check for SD_IO_RW_EXTENDED which is CMD53.
> 
	Sorry. I didn't notice CMD53 check was added.
	I introduced this Auto-CMD12 hack since kernel 3.8. It seems that this check is not added in kernel 3.8.
	Thanks for the information. I will remove the Auto-CMD12 hack.

>>
>> 	I just meet a similar issue in RPMB.
>> 	When Auto-CMD12 flag is set, eMMC RPMB access will trigger Auto-CMD12, since CMD25 is in use.
>> 	It will cause RPMB access failed.
> 
> Can you explain more about the RPMB issue.  Doesn't it use CMD23, so CMD12
> wouldn't be used - auto or manually.
> 
	RPMB go through the MMC ioctl routine.
	Unlike normal data transfer, MMC ioctl for RPMB explicitly issues CMD23. When CMD25 is issued, there is neither data->sbc nor Auto-CMD23.
	As a result, sdhci driver will automatically enable Auto-CMD12 for RPMB CMD25 if Auto-CMD12 flag is set.

>>
>> 	One possible solution is to drop Auto-CMD12 support and use Auto-CMD23 only, in Xenon driver.
>> 	May I know you opinion, please?
> 
> I don't use auto-CMD12 because I don't know if it provides any benefit and
> sdhci does not seem to have implemented Auto CMD12 Error Recovery, although
> I have never looked at it closely.
>
	Actually, Auto-CMD23 is always used on our Xenon. Auto-CMD12 is not used at all.
	But since this driver is a general one for all Marvell products, Auto-CMD12 is also supported in case that Auto-CMD23 is not available.
 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
  2016-10-12 12:17       ` Ziji Hu
  (?)
@ 2016-10-17  7:55         ` Adrian Hunter
  -1 siblings, 0 replies; 105+ messages in thread
From: Adrian Hunter @ 2016-10-17  7:55 UTC (permalink / raw)
  To: Ziji Hu, Gregory CLEMENT, Ulf Hansson, 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, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

On 12/10/16 15:17, Ziji Hu wrote:
> Hi Adrian,
> 
> On 2016/10/11 20:39, Adrian Hunter wrote:
>> On 07/10/16 18:22, Gregory CLEMENT wrote:
>>> 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.*
>>>  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
>>
>> <SNIP>
>>
>>> +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;
>>> +}
>>
>> So you are issuing commands from the ->set_ios() callback.  I would want to
>> get Ulf's OK for that before going further.
>>
> 	Yes, you are correct.
> 	In some speed mode, Xenon SDHC has to send a series of transfers to search for a perfect sampling point in PHY delay line.
> 	It is like tuning process.
> 
>> One thing: you will need to ensure you don't trigger get HS400 re-tuning
>> because it will call back into ->set_ios().
>>
> 	Could you please make the term "HS400 re-tuning" more detailed?
> 	In current MMC driver, "HS400 re-tuning" will go back to HS200, execute HS200 tuning and come back to HS400.
> 	I'm sure our Xenon SDHC will not execute it.

Currently, re-tuning is automatically enabled whenever tuning is executed,
and then re-tuning will be done periodically or after CRC errors.  The
function to disable re-tuning mmc_retune_disable() is not exported, however
if you have you are determining the sampling point your own way, you could
simply not implement ->execute_tuning() and then there would be no tuning
and no re-tuning.

> 
> 	However, in coming eMMC 5.2, there is a real HS400 re-tuning, in which tuning can be directly executed in HS400 mode.
> 	Our Xenon SDHC will neither trigger this HS400 re-tuning.
> 	But since so far there is no such feature in MMC driver, I cannot give you a 100% guarantee now.
> 
>> And you have the problem that you need to get a reference to the card before
>> the card device has been added.  As I wrote in response to the previous
>> patch, you should get Ulf's help with that too.
>>
> 	Sure.
> 	I will get card_candidate solved at first.
> 	Thank you again for your review and help.
> 
> 	Thank you.
> 
> Best regards,
> Hu Ziji
>>
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
@ 2016-10-17  7:55         ` Adrian Hunter
  0 siblings, 0 replies; 105+ messages in thread
From: Adrian Hunter @ 2016-10-17  7:55 UTC (permalink / raw)
  To: Ziji Hu, Gregory CLEMENT, Ulf Hansson, 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

On 12/10/16 15:17, Ziji Hu wrote:
> Hi Adrian,
> 
> On 2016/10/11 20:39, Adrian Hunter wrote:
>> On 07/10/16 18:22, Gregory CLEMENT wrote:
>>> 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.*
>>>  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
>>
>> <SNIP>
>>
>>> +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;
>>> +}
>>
>> So you are issuing commands from the ->set_ios() callback.  I would want to
>> get Ulf's OK for that before going further.
>>
> 	Yes, you are correct.
> 	In some speed mode, Xenon SDHC has to send a series of transfers to search for a perfect sampling point in PHY delay line.
> 	It is like tuning process.
> 
>> One thing: you will need to ensure you don't trigger get HS400 re-tuning
>> because it will call back into ->set_ios().
>>
> 	Could you please make the term "HS400 re-tuning" more detailed?
> 	In current MMC driver, "HS400 re-tuning" will go back to HS200, execute HS200 tuning and come back to HS400.
> 	I'm sure our Xenon SDHC will not execute it.

Currently, re-tuning is automatically enabled whenever tuning is executed,
and then re-tuning will be done periodically or after CRC errors.  The
function to disable re-tuning mmc_retune_disable() is not exported, however
if you have you are determining the sampling point your own way, you could
simply not implement ->execute_tuning() and then there would be no tuning
and no re-tuning.

> 
> 	However, in coming eMMC 5.2, there is a real HS400 re-tuning, in which tuning can be directly executed in HS400 mode.
> 	Our Xenon SDHC will neither trigger this HS400 re-tuning.
> 	But since so far there is no such feature in MMC driver, I cannot give you a 100% guarantee now.
> 
>> And you have the problem that you need to get a reference to the card before
>> the card device has been added.  As I wrote in response to the previous
>> patch, you should get Ulf's help with that too.
>>
> 	Sure.
> 	I will get card_candidate solved at first.
> 	Thank you again for your review and help.
> 
> 	Thank you.
> 
> Best regards,
> Hu Ziji
>>
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
@ 2016-10-17  7:55         ` Adrian Hunter
  0 siblings, 0 replies; 105+ messages in thread
From: Adrian Hunter @ 2016-10-17  7:55 UTC (permalink / raw)
  To: linux-arm-kernel

On 12/10/16 15:17, Ziji Hu wrote:
> Hi Adrian,
> 
> On 2016/10/11 20:39, Adrian Hunter wrote:
>> On 07/10/16 18:22, Gregory CLEMENT wrote:
>>> 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 at vger.kernel.org
>>>  S:	Supported
>>>  F:	drivers/mmc/host/sdhci-xenon.*
>>> +F:	drivers/mmc/host/sdhci-xenon-phy.*
>>>  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
>>
>> <SNIP>
>>
>>> +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;
>>> +}
>>
>> So you are issuing commands from the ->set_ios() callback.  I would want to
>> get Ulf's OK for that before going further.
>>
> 	Yes, you are correct.
> 	In some speed mode, Xenon SDHC has to send a series of transfers to search for a perfect sampling point in PHY delay line.
> 	It is like tuning process.
> 
>> One thing: you will need to ensure you don't trigger get HS400 re-tuning
>> because it will call back into ->set_ios().
>>
> 	Could you please make the term "HS400 re-tuning" more detailed?
> 	In current MMC driver, "HS400 re-tuning" will go back to HS200, execute HS200 tuning and come back to HS400.
> 	I'm sure our Xenon SDHC will not execute it.

Currently, re-tuning is automatically enabled whenever tuning is executed,
and then re-tuning will be done periodically or after CRC errors.  The
function to disable re-tuning mmc_retune_disable() is not exported, however
if you have you are determining the sampling point your own way, you could
simply not implement ->execute_tuning() and then there would be no tuning
and no re-tuning.

> 
> 	However, in coming eMMC 5.2, there is a real HS400 re-tuning, in which tuning can be directly executed in HS400 mode.
> 	Our Xenon SDHC will neither trigger this HS400 re-tuning.
> 	But since so far there is no such feature in MMC driver, I cannot give you a 100% guarantee now.
> 
>> And you have the problem that you need to get a reference to the card before
>> the card device has been added.  As I wrote in response to the previous
>> patch, you should get Ulf's help with that too.
>>
> 	Sure.
> 	I will get card_candidate solved at first.
> 	Thank you again for your review and help.
> 
> 	Thank you.
> 
> Best regards,
> Hu Ziji
>>
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
  2016-10-13  5:38           ` Ziji Hu
  (?)
@ 2016-10-17  8:14             ` Adrian Hunter
  -1 siblings, 0 replies; 105+ messages in thread
From: Adrian Hunter @ 2016-10-17  8:14 UTC (permalink / raw)
  To: Ziji Hu, Gregory CLEMENT, Ulf Hansson, 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, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

On 13/10/16 08:38, Ziji Hu wrote:
> Hi Adrian,
> 
> On 2016/10/12 21:07, Adrian Hunter wrote:
>> On 12/10/16 14:58, Ziji Hu wrote:
>>> Hi Adrian,
>>>
>>> 	Thank you very much for your review.
>>> 	I will firstly fix the typo.
>>>
>>> On 2016/10/11 20:37, Adrian Hunter wrote:
> <snip>
>>>>> +
>>>>> +static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
>>>>> +					     struct mmc_ios *ios)
>>>>> +{
>>>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>>>> +
>>>>> +	/*
>>>>> +	 * Before SD/SDIO set signal voltage, SD bus clock should be
>>>>> +	 * disabled. However, sdhci_set_clock will also disable the Internal
>>>>> +	 * clock in mmc_set_signal_voltage().
>>>>> +	 * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
>>>>> +	 * Thus here manually enable internal clock.
>>>>> +	 *
>>>>> +	 * After switch completes, it is unnecessary to disable internal clock,
>>>>> +	 * since keeping internal clock active obeys SD spec.
>>>>> +	 */
>>>>> +	enable_xenon_internal_clk(host);
>>>>> +
>>>>> +	if (priv->card_candidate) {
>>>>
>>>> mmc_power_up() calls __mmc_set_signal_voltage() calls
>>>> host->ops->start_signal_voltage_switch so priv->card_candidate could be an
>>>> invalid reference to an old card.
>>>>
>>>> So that's not going to work if the card changes - not only for removable
>>>> cards but even for eMMC if init fails and retries.
>>>>
>>> 	As you point out, this piece of code have defects, even though it actually works on Marvell multiple platforms, unless eMMC card is removable.
>>>
>>> 	I can add a property to explicitly indicate eMMC type in DTS.
>>> 	Then card_candidate access can be removed here.
>>> 	Does it sounds more reasonable to you?
>>
>> Sure
>>
>>>
>>>>> +		if (mmc_card_mmc(priv->card_candidate))
>>>>> +			return xenon_emmc_signal_voltage_switch(mmc, ios);
>>>>
>>>> So if all you need to know is whether it is a eMMC, why can't DT tell you?
>>>>
>>> 	I can add an eMMC type property in DTS, to remove the card_candidate access here.
>>>
>>>>> +	}
>>>>> +
>>>>> +	return sdhci_start_signal_voltage_switch(mmc, ios);
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * After determining which slot is used for SDIO,
>>>>> + * some additional task is required.
>>>>> + */
>>>>> +static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
>>>>> +{
>>>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>>>> +	u32 reg;
>>>>> +	u8 slot_idx;
>>>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>>>> +
>>>>> +	/* Link the card for delay adjustment */
>>>>> +	priv->card_candidate = card;
>>>>
>>>> You really need a better way to get the card.  I suggest you take up the
>>>> issue with Ulf.  One possibility is to have mmc core set host->card = card
>>>> much earlier.
>>>>
>>> 	Could you please tell me if any issue related to card_candidate still exists, after the card_candidate is removed from xenon_start_signal_voltage_switch() in above?
>>> 	It seems that when init_card is called, the structure card has already been updated and stable in MMC/SD/SDIO initialization sequence.
>>> 	May I keep it here?
>>
>> It works by accident rather than by design.  We can do better.
>>
> 	Could you please tell me some details which are satisfied about card_candidate?
> 
> 	I must admit that card_candidate in xenon_start_signal_voltage_switch() is imperfect.
> 	But card_candidate in init_card() and later in set_ios() work by design, rather than by accident. We did a lot of tests on several platforms.
> 	
> 	The structure mmc_card passed in here is a stable one. Thus in my very own opinion, it is safe and stable to use mmc_card here.
> 	card_candidate is used only in card initialization. It is not active in later transfers after initialization is done.
> 	It will always updated with mmc_card in next card initialization.

Ok, so maybe just add some comments and more explanation of how it works.


> 
>>>
>>>>> +	/* Set tuning functionality of this slot */
>>>>> +	xenon_slot_tuning_setup(host);
>>>>> +
>>>>> +	slot_idx = priv->slot_idx;
>>>>> +	if (!mmc_card_sdio(card)) {
>>>>> +		/* Re-enable the Auto-CMD12 cap flag. */
>>>>> +		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>>>> +		host->flags |= SDHCI_AUTO_CMD12;
>>>>> +
>>>>> +		/* Clear SDIO Card Inserted indication */
>>>>> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
>>>>> +		reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
>>>>> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
>>>>> +
>>>>> +		if (mmc_card_mmc(card)) {
>>>>> +			mmc->caps |= MMC_CAP_NONREMOVABLE;
>>>>> +			if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
>>>>> +				mmc->caps |= MMC_CAP_1_8V_DDR;
>>>>> +			/*
>>>>> +			 * Force to clear BUS_TEST to
>>>>> +			 * skip bus_test_pre and bus_test_post
>>>>> +			 */
>>>>> +			mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
>>>>> +			mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
>>>>> +				      MMC_CAP2_PACKED_CMD;
>>>>> +			if (mmc->caps & MMC_CAP_8_BIT_DATA)
>>>>> +				mmc->caps2 |= MMC_CAP2_HS400_1_8V;
>>>>> +		}
>>>>> +	} else {
>>>>> +		/*
>>>>> +		 * Delete the Auto-CMD12 cap flag.
>>>>> +		 * Otherwise, when sending multi-block CMD53,
>>>>> +		 * Driver will set Transfer Mode Register to enable Auto CMD12.
>>>>> +		 * However, SDIO device cannot recognize CMD12.
>>>>> +		 * Thus SDHC will time-out for waiting for CMD12 response.
>>>>> +		 */
>>>>> +		host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>>>> +		host->flags &= ~SDHCI_AUTO_CMD12;
>>>>
>>>> sdhci_set_transfer_mode() won't enable auto-CMD12 for CMD53 anyway, so is
>>>> this needed?
>>>>
>>> 	In Xenon driver, Auto-CMD12 flag is set to enable full support to Auto-CMD feature, both Auto-CMD12 and Auto-CMD23.
>>> 	As a result, when Xenon SDHC slot can both support SD and SDIO, Auto-CMD12 is disabled when SDIO card is inserted, and renabled when SD is inserted.
>>>
>>> 	I recheck the sdhci code to set Auto-CMD bit in Transfer Mode register, in sdhci_set_transfer_mode():
>>> 	if (mmc_op_multi(cmd->opcode) || data->blocks > 1)
>>> 	As you can see, as long as it is CMD18/CMD25 OR there are multiple data blocks, Auto-CMD field will be set.
>>> 	CMD53 doesn't have CMD23. Thus Auto-CMD12 is selected since Auto-CMD12 flag is set.
>>> 	Thus I have to clear Auto-CMD12 to avoid issuing Auto-CMD12 in SDIO transfer.
>>
>>
>> The code is:
>>
>> 	if (mmc_op_multi(cmd->opcode) || data->blocks > 1) {
>> 		mode = SDHCI_TRNS_BLK_CNT_EN | SDHCI_TRNS_MULTI;
>> 		/*
>> 		 * If we are sending CMD23, CMD12 never gets sent
>> 		 * on successful completion (so no Auto-CMD12).
>> 		 */
>> 		if (sdhci_auto_cmd12(host, cmd->mrq) &&
>> 		    (cmd->opcode != SD_IO_RW_EXTENDED))
>> 			mode |= SDHCI_TRNS_AUTO_CMD12;
>> 		else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
>> 			mode |= SDHCI_TRNS_AUTO_CMD23;
>> 			sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);
>> 		}
>> 	}
>>
>> You can see the check for SD_IO_RW_EXTENDED which is CMD53.
>>
> 	Sorry. I didn't notice CMD53 check was added.
> 	I introduced this Auto-CMD12 hack since kernel 3.8. It seems that this check is not added in kernel 3.8.
> 	Thanks for the information. I will remove the Auto-CMD12 hack.
> 
>>>
>>> 	I just meet a similar issue in RPMB.
>>> 	When Auto-CMD12 flag is set, eMMC RPMB access will trigger Auto-CMD12, since CMD25 is in use.
>>> 	It will cause RPMB access failed.
>>
>> Can you explain more about the RPMB issue.  Doesn't it use CMD23, so CMD12
>> wouldn't be used - auto or manually.
>>
> 	RPMB go through the MMC ioctl routine.
> 	Unlike normal data transfer, MMC ioctl for RPMB explicitly issues CMD23. When CMD25 is issued, there is neither data->sbc nor Auto-CMD23.
> 	As a result, sdhci driver will automatically enable Auto-CMD12 for RPMB CMD25 if Auto-CMD12 flag is set.

OK, so SDHCI should also not allow auto-cmd12 if there is no stop command
i.e. data->stop is null.

> 
>>>
>>> 	One possible solution is to drop Auto-CMD12 support and use Auto-CMD23 only, in Xenon driver.
>>> 	May I know you opinion, please?
>>
>> I don't use auto-CMD12 because I don't know if it provides any benefit and
>> sdhci does not seem to have implemented Auto CMD12 Error Recovery, although
>> I have never looked at it closely.
>>
> 	Actually, Auto-CMD23 is always used on our Xenon. Auto-CMD12 is not used at all.
> 	But since this driver is a general one for all Marvell products, Auto-CMD12 is also supported in case that Auto-CMD23 is not available.
>  
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
@ 2016-10-17  8:14             ` Adrian Hunter
  0 siblings, 0 replies; 105+ messages in thread
From: Adrian Hunter @ 2016-10-17  8:14 UTC (permalink / raw)
  To: Ziji Hu, Gregory CLEMENT, Ulf Hansson, 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

On 13/10/16 08:38, Ziji Hu wrote:
> Hi Adrian,
> 
> On 2016/10/12 21:07, Adrian Hunter wrote:
>> On 12/10/16 14:58, Ziji Hu wrote:
>>> Hi Adrian,
>>>
>>> 	Thank you very much for your review.
>>> 	I will firstly fix the typo.
>>>
>>> On 2016/10/11 20:37, Adrian Hunter wrote:
> <snip>
>>>>> +
>>>>> +static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
>>>>> +					     struct mmc_ios *ios)
>>>>> +{
>>>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>>>> +
>>>>> +	/*
>>>>> +	 * Before SD/SDIO set signal voltage, SD bus clock should be
>>>>> +	 * disabled. However, sdhci_set_clock will also disable the Internal
>>>>> +	 * clock in mmc_set_signal_voltage().
>>>>> +	 * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
>>>>> +	 * Thus here manually enable internal clock.
>>>>> +	 *
>>>>> +	 * After switch completes, it is unnecessary to disable internal clock,
>>>>> +	 * since keeping internal clock active obeys SD spec.
>>>>> +	 */
>>>>> +	enable_xenon_internal_clk(host);
>>>>> +
>>>>> +	if (priv->card_candidate) {
>>>>
>>>> mmc_power_up() calls __mmc_set_signal_voltage() calls
>>>> host->ops->start_signal_voltage_switch so priv->card_candidate could be an
>>>> invalid reference to an old card.
>>>>
>>>> So that's not going to work if the card changes - not only for removable
>>>> cards but even for eMMC if init fails and retries.
>>>>
>>> 	As you point out, this piece of code have defects, even though it actually works on Marvell multiple platforms, unless eMMC card is removable.
>>>
>>> 	I can add a property to explicitly indicate eMMC type in DTS.
>>> 	Then card_candidate access can be removed here.
>>> 	Does it sounds more reasonable to you?
>>
>> Sure
>>
>>>
>>>>> +		if (mmc_card_mmc(priv->card_candidate))
>>>>> +			return xenon_emmc_signal_voltage_switch(mmc, ios);
>>>>
>>>> So if all you need to know is whether it is a eMMC, why can't DT tell you?
>>>>
>>> 	I can add an eMMC type property in DTS, to remove the card_candidate access here.
>>>
>>>>> +	}
>>>>> +
>>>>> +	return sdhci_start_signal_voltage_switch(mmc, ios);
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * After determining which slot is used for SDIO,
>>>>> + * some additional task is required.
>>>>> + */
>>>>> +static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
>>>>> +{
>>>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>>>> +	u32 reg;
>>>>> +	u8 slot_idx;
>>>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>>>> +
>>>>> +	/* Link the card for delay adjustment */
>>>>> +	priv->card_candidate = card;
>>>>
>>>> You really need a better way to get the card.  I suggest you take up the
>>>> issue with Ulf.  One possibility is to have mmc core set host->card = card
>>>> much earlier.
>>>>
>>> 	Could you please tell me if any issue related to card_candidate still exists, after the card_candidate is removed from xenon_start_signal_voltage_switch() in above?
>>> 	It seems that when init_card is called, the structure card has already been updated and stable in MMC/SD/SDIO initialization sequence.
>>> 	May I keep it here?
>>
>> It works by accident rather than by design.  We can do better.
>>
> 	Could you please tell me some details which are satisfied about card_candidate?
> 
> 	I must admit that card_candidate in xenon_start_signal_voltage_switch() is imperfect.
> 	But card_candidate in init_card() and later in set_ios() work by design, rather than by accident. We did a lot of tests on several platforms.
> 	
> 	The structure mmc_card passed in here is a stable one. Thus in my very own opinion, it is safe and stable to use mmc_card here.
> 	card_candidate is used only in card initialization. It is not active in later transfers after initialization is done.
> 	It will always updated with mmc_card in next card initialization.

Ok, so maybe just add some comments and more explanation of how it works.


> 
>>>
>>>>> +	/* Set tuning functionality of this slot */
>>>>> +	xenon_slot_tuning_setup(host);
>>>>> +
>>>>> +	slot_idx = priv->slot_idx;
>>>>> +	if (!mmc_card_sdio(card)) {
>>>>> +		/* Re-enable the Auto-CMD12 cap flag. */
>>>>> +		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>>>> +		host->flags |= SDHCI_AUTO_CMD12;
>>>>> +
>>>>> +		/* Clear SDIO Card Inserted indication */
>>>>> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
>>>>> +		reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
>>>>> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
>>>>> +
>>>>> +		if (mmc_card_mmc(card)) {
>>>>> +			mmc->caps |= MMC_CAP_NONREMOVABLE;
>>>>> +			if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
>>>>> +				mmc->caps |= MMC_CAP_1_8V_DDR;
>>>>> +			/*
>>>>> +			 * Force to clear BUS_TEST to
>>>>> +			 * skip bus_test_pre and bus_test_post
>>>>> +			 */
>>>>> +			mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
>>>>> +			mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
>>>>> +				      MMC_CAP2_PACKED_CMD;
>>>>> +			if (mmc->caps & MMC_CAP_8_BIT_DATA)
>>>>> +				mmc->caps2 |= MMC_CAP2_HS400_1_8V;
>>>>> +		}
>>>>> +	} else {
>>>>> +		/*
>>>>> +		 * Delete the Auto-CMD12 cap flag.
>>>>> +		 * Otherwise, when sending multi-block CMD53,
>>>>> +		 * Driver will set Transfer Mode Register to enable Auto CMD12.
>>>>> +		 * However, SDIO device cannot recognize CMD12.
>>>>> +		 * Thus SDHC will time-out for waiting for CMD12 response.
>>>>> +		 */
>>>>> +		host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>>>> +		host->flags &= ~SDHCI_AUTO_CMD12;
>>>>
>>>> sdhci_set_transfer_mode() won't enable auto-CMD12 for CMD53 anyway, so is
>>>> this needed?
>>>>
>>> 	In Xenon driver, Auto-CMD12 flag is set to enable full support to Auto-CMD feature, both Auto-CMD12 and Auto-CMD23.
>>> 	As a result, when Xenon SDHC slot can both support SD and SDIO, Auto-CMD12 is disabled when SDIO card is inserted, and renabled when SD is inserted.
>>>
>>> 	I recheck the sdhci code to set Auto-CMD bit in Transfer Mode register, in sdhci_set_transfer_mode():
>>> 	if (mmc_op_multi(cmd->opcode) || data->blocks > 1)
>>> 	As you can see, as long as it is CMD18/CMD25 OR there are multiple data blocks, Auto-CMD field will be set.
>>> 	CMD53 doesn't have CMD23. Thus Auto-CMD12 is selected since Auto-CMD12 flag is set.
>>> 	Thus I have to clear Auto-CMD12 to avoid issuing Auto-CMD12 in SDIO transfer.
>>
>>
>> The code is:
>>
>> 	if (mmc_op_multi(cmd->opcode) || data->blocks > 1) {
>> 		mode = SDHCI_TRNS_BLK_CNT_EN | SDHCI_TRNS_MULTI;
>> 		/*
>> 		 * If we are sending CMD23, CMD12 never gets sent
>> 		 * on successful completion (so no Auto-CMD12).
>> 		 */
>> 		if (sdhci_auto_cmd12(host, cmd->mrq) &&
>> 		    (cmd->opcode != SD_IO_RW_EXTENDED))
>> 			mode |= SDHCI_TRNS_AUTO_CMD12;
>> 		else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
>> 			mode |= SDHCI_TRNS_AUTO_CMD23;
>> 			sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);
>> 		}
>> 	}
>>
>> You can see the check for SD_IO_RW_EXTENDED which is CMD53.
>>
> 	Sorry. I didn't notice CMD53 check was added.
> 	I introduced this Auto-CMD12 hack since kernel 3.8. It seems that this check is not added in kernel 3.8.
> 	Thanks for the information. I will remove the Auto-CMD12 hack.
> 
>>>
>>> 	I just meet a similar issue in RPMB.
>>> 	When Auto-CMD12 flag is set, eMMC RPMB access will trigger Auto-CMD12, since CMD25 is in use.
>>> 	It will cause RPMB access failed.
>>
>> Can you explain more about the RPMB issue.  Doesn't it use CMD23, so CMD12
>> wouldn't be used - auto or manually.
>>
> 	RPMB go through the MMC ioctl routine.
> 	Unlike normal data transfer, MMC ioctl for RPMB explicitly issues CMD23. When CMD25 is issued, there is neither data->sbc nor Auto-CMD23.
> 	As a result, sdhci driver will automatically enable Auto-CMD12 for RPMB CMD25 if Auto-CMD12 flag is set.

OK, so SDHCI should also not allow auto-cmd12 if there is no stop command
i.e. data->stop is null.

> 
>>>
>>> 	One possible solution is to drop Auto-CMD12 support and use Auto-CMD23 only, in Xenon driver.
>>> 	May I know you opinion, please?
>>
>> I don't use auto-CMD12 because I don't know if it provides any benefit and
>> sdhci does not seem to have implemented Auto CMD12 Error Recovery, although
>> I have never looked at it closely.
>>
> 	Actually, Auto-CMD23 is always used on our Xenon. Auto-CMD12 is not used at all.
> 	But since this driver is a general one for all Marvell products, Auto-CMD12 is also supported in case that Auto-CMD23 is not available.
>  
> 


^ permalink raw reply	[flat|nested] 105+ messages in thread

* [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
@ 2016-10-17  8:14             ` Adrian Hunter
  0 siblings, 0 replies; 105+ messages in thread
From: Adrian Hunter @ 2016-10-17  8:14 UTC (permalink / raw)
  To: linux-arm-kernel

On 13/10/16 08:38, Ziji Hu wrote:
> Hi Adrian,
> 
> On 2016/10/12 21:07, Adrian Hunter wrote:
>> On 12/10/16 14:58, Ziji Hu wrote:
>>> Hi Adrian,
>>>
>>> 	Thank you very much for your review.
>>> 	I will firstly fix the typo.
>>>
>>> On 2016/10/11 20:37, Adrian Hunter wrote:
> <snip>
>>>>> +
>>>>> +static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
>>>>> +					     struct mmc_ios *ios)
>>>>> +{
>>>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>>>> +
>>>>> +	/*
>>>>> +	 * Before SD/SDIO set signal voltage, SD bus clock should be
>>>>> +	 * disabled. However, sdhci_set_clock will also disable the Internal
>>>>> +	 * clock in mmc_set_signal_voltage().
>>>>> +	 * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
>>>>> +	 * Thus here manually enable internal clock.
>>>>> +	 *
>>>>> +	 * After switch completes, it is unnecessary to disable internal clock,
>>>>> +	 * since keeping internal clock active obeys SD spec.
>>>>> +	 */
>>>>> +	enable_xenon_internal_clk(host);
>>>>> +
>>>>> +	if (priv->card_candidate) {
>>>>
>>>> mmc_power_up() calls __mmc_set_signal_voltage() calls
>>>> host->ops->start_signal_voltage_switch so priv->card_candidate could be an
>>>> invalid reference to an old card.
>>>>
>>>> So that's not going to work if the card changes - not only for removable
>>>> cards but even for eMMC if init fails and retries.
>>>>
>>> 	As you point out, this piece of code have defects, even though it actually works on Marvell multiple platforms, unless eMMC card is removable.
>>>
>>> 	I can add a property to explicitly indicate eMMC type in DTS.
>>> 	Then card_candidate access can be removed here.
>>> 	Does it sounds more reasonable to you?
>>
>> Sure
>>
>>>
>>>>> +		if (mmc_card_mmc(priv->card_candidate))
>>>>> +			return xenon_emmc_signal_voltage_switch(mmc, ios);
>>>>
>>>> So if all you need to know is whether it is a eMMC, why can't DT tell you?
>>>>
>>> 	I can add an eMMC type property in DTS, to remove the card_candidate access here.
>>>
>>>>> +	}
>>>>> +
>>>>> +	return sdhci_start_signal_voltage_switch(mmc, ios);
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * After determining which slot is used for SDIO,
>>>>> + * some additional task is required.
>>>>> + */
>>>>> +static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
>>>>> +{
>>>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>>>> +	u32 reg;
>>>>> +	u8 slot_idx;
>>>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>>>> +
>>>>> +	/* Link the card for delay adjustment */
>>>>> +	priv->card_candidate = card;
>>>>
>>>> You really need a better way to get the card.  I suggest you take up the
>>>> issue with Ulf.  One possibility is to have mmc core set host->card = card
>>>> much earlier.
>>>>
>>> 	Could you please tell me if any issue related to card_candidate still exists, after the card_candidate is removed from xenon_start_signal_voltage_switch() in above?
>>> 	It seems that when init_card is called, the structure card has already been updated and stable in MMC/SD/SDIO initialization sequence.
>>> 	May I keep it here?
>>
>> It works by accident rather than by design.  We can do better.
>>
> 	Could you please tell me some details which are satisfied about card_candidate?
> 
> 	I must admit that card_candidate in xenon_start_signal_voltage_switch() is imperfect.
> 	But card_candidate in init_card() and later in set_ios() work by design, rather than by accident. We did a lot of tests on several platforms.
> 	
> 	The structure mmc_card passed in here is a stable one. Thus in my very own opinion, it is safe and stable to use mmc_card here.
> 	card_candidate is used only in card initialization. It is not active in later transfers after initialization is done.
> 	It will always updated with mmc_card in next card initialization.

Ok, so maybe just add some comments and more explanation of how it works.


> 
>>>
>>>>> +	/* Set tuning functionality of this slot */
>>>>> +	xenon_slot_tuning_setup(host);
>>>>> +
>>>>> +	slot_idx = priv->slot_idx;
>>>>> +	if (!mmc_card_sdio(card)) {
>>>>> +		/* Re-enable the Auto-CMD12 cap flag. */
>>>>> +		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>>>> +		host->flags |= SDHCI_AUTO_CMD12;
>>>>> +
>>>>> +		/* Clear SDIO Card Inserted indication */
>>>>> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
>>>>> +		reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
>>>>> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
>>>>> +
>>>>> +		if (mmc_card_mmc(card)) {
>>>>> +			mmc->caps |= MMC_CAP_NONREMOVABLE;
>>>>> +			if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
>>>>> +				mmc->caps |= MMC_CAP_1_8V_DDR;
>>>>> +			/*
>>>>> +			 * Force to clear BUS_TEST to
>>>>> +			 * skip bus_test_pre and bus_test_post
>>>>> +			 */
>>>>> +			mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
>>>>> +			mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
>>>>> +				      MMC_CAP2_PACKED_CMD;
>>>>> +			if (mmc->caps & MMC_CAP_8_BIT_DATA)
>>>>> +				mmc->caps2 |= MMC_CAP2_HS400_1_8V;
>>>>> +		}
>>>>> +	} else {
>>>>> +		/*
>>>>> +		 * Delete the Auto-CMD12 cap flag.
>>>>> +		 * Otherwise, when sending multi-block CMD53,
>>>>> +		 * Driver will set Transfer Mode Register to enable Auto CMD12.
>>>>> +		 * However, SDIO device cannot recognize CMD12.
>>>>> +		 * Thus SDHC will time-out for waiting for CMD12 response.
>>>>> +		 */
>>>>> +		host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>>>> +		host->flags &= ~SDHCI_AUTO_CMD12;
>>>>
>>>> sdhci_set_transfer_mode() won't enable auto-CMD12 for CMD53 anyway, so is
>>>> this needed?
>>>>
>>> 	In Xenon driver, Auto-CMD12 flag is set to enable full support to Auto-CMD feature, both Auto-CMD12 and Auto-CMD23.
>>> 	As a result, when Xenon SDHC slot can both support SD and SDIO, Auto-CMD12 is disabled when SDIO card is inserted, and renabled when SD is inserted.
>>>
>>> 	I recheck the sdhci code to set Auto-CMD bit in Transfer Mode register, in sdhci_set_transfer_mode():
>>> 	if (mmc_op_multi(cmd->opcode) || data->blocks > 1)
>>> 	As you can see, as long as it is CMD18/CMD25 OR there are multiple data blocks, Auto-CMD field will be set.
>>> 	CMD53 doesn't have CMD23. Thus Auto-CMD12 is selected since Auto-CMD12 flag is set.
>>> 	Thus I have to clear Auto-CMD12 to avoid issuing Auto-CMD12 in SDIO transfer.
>>
>>
>> The code is:
>>
>> 	if (mmc_op_multi(cmd->opcode) || data->blocks > 1) {
>> 		mode = SDHCI_TRNS_BLK_CNT_EN | SDHCI_TRNS_MULTI;
>> 		/*
>> 		 * If we are sending CMD23, CMD12 never gets sent
>> 		 * on successful completion (so no Auto-CMD12).
>> 		 */
>> 		if (sdhci_auto_cmd12(host, cmd->mrq) &&
>> 		    (cmd->opcode != SD_IO_RW_EXTENDED))
>> 			mode |= SDHCI_TRNS_AUTO_CMD12;
>> 		else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
>> 			mode |= SDHCI_TRNS_AUTO_CMD23;
>> 			sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);
>> 		}
>> 	}
>>
>> You can see the check for SD_IO_RW_EXTENDED which is CMD53.
>>
> 	Sorry. I didn't notice CMD53 check was added.
> 	I introduced this Auto-CMD12 hack since kernel 3.8. It seems that this check is not added in kernel 3.8.
> 	Thanks for the information. I will remove the Auto-CMD12 hack.
> 
>>>
>>> 	I just meet a similar issue in RPMB.
>>> 	When Auto-CMD12 flag is set, eMMC RPMB access will trigger Auto-CMD12, since CMD25 is in use.
>>> 	It will cause RPMB access failed.
>>
>> Can you explain more about the RPMB issue.  Doesn't it use CMD23, so CMD12
>> wouldn't be used - auto or manually.
>>
> 	RPMB go through the MMC ioctl routine.
> 	Unlike normal data transfer, MMC ioctl for RPMB explicitly issues CMD23. When CMD25 is issued, there is neither data->sbc nor Auto-CMD23.
> 	As a result, sdhci driver will automatically enable Auto-CMD12 for RPMB CMD25 if Auto-CMD12 flag is set.

OK, so SDHCI should also not allow auto-cmd12 if there is no stop command
i.e. data->stop is null.

> 
>>>
>>> 	One possible solution is to drop Auto-CMD12 support and use Auto-CMD23 only, in Xenon driver.
>>> 	May I know you opinion, please?
>>
>> I don't use auto-CMD12 because I don't know if it provides any benefit and
>> sdhci does not seem to have implemented Auto CMD12 Error Recovery, although
>> I have never looked at it closely.
>>
> 	Actually, Auto-CMD23 is always used on our Xenon. Auto-CMD12 is not used at all.
> 	But since this driver is a general one for all Marvell products, Auto-CMD12 is also supported in case that Auto-CMD23 is not available.
>  
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
  2016-10-17  7:55         ` Adrian Hunter
  (?)
@ 2016-10-18 12:04           ` Ziji Hu
  -1 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-18 12:04 UTC (permalink / raw)
  To: Adrian Hunter, Gregory CLEMENT, Ulf Hansson, 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, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

Hi Adrian,

On 2016/10/17 15:55, Adrian Hunter wrote:
> On 12/10/16 15:17, Ziji Hu wrote:
>> Hi Adrian,
>>
>> On 2016/10/11 20:39, Adrian Hunter wrote:
<snip>
>>>> +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;
>>>> +}
>>>
>>> So you are issuing commands from the ->set_ios() callback.  I would want to
>>> get Ulf's OK for that before going further.
>>>
>> 	Yes, you are correct.
>> 	In some speed mode, Xenon SDHC has to send a series of transfers to search for a perfect sampling point in PHY delay line.
>> 	It is like tuning process.
>>
>>> One thing: you will need to ensure you don't trigger get HS400 re-tuning
>>> because it will call back into ->set_ios().
>>>
>> 	Could you please make the term "HS400 re-tuning" more detailed?
>> 	In current MMC driver, "HS400 re-tuning" will go back to HS200, execute HS200 tuning and come back to HS400.
>> 	I'm sure our Xenon SDHC will not execute it.
> 
> Currently, re-tuning is automatically enabled whenever tuning is executed,
> and then re-tuning will be done periodically or after CRC errors.  The
> function to disable re-tuning mmc_retune_disable() is not exported, however
> if you have you are determining the sampling point your own way, you could
> simply not implement ->execute_tuning() and then there would be no tuning
> and no re-tuning.
>

	It is a little complex in our Xenon SDHC.
	For the speed mode which requests tuning, such as SDR104 and HS200, our driver will execute standard tuning as spec requires.
	However, for those speed mode in which tuning is not requested in spec, our driver has to issues commands to search for the best sampling point.
	
	It seems that HS400 re-tuning/tuning is disabled by default. Is it correct?

>>
>> 	However, in coming eMMC 5.2, there is a real HS400 re-tuning, in which tuning can be directly executed in HS400 mode.
>> 	Our Xenon SDHC will neither trigger this HS400 re-tuning.
>> 	But since so far there is no such feature in MMC driver, I cannot give you a 100% guarantee now.
>>
>>> And you have the problem that you need to get a reference to the card before
>>> the card device has been added.  As I wrote in response to the previous
>>> patch, you should get Ulf's help with that too.
>>>
>> 	Sure.
>> 	I will get card_candidate solved at first.
>> 	Thank you again for your review and help.
>>
>> 	Thank you.
>>
>> Best regards,
>> Hu Ziji
>>>
>>
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
@ 2016-10-18 12:04           ` Ziji Hu
  0 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-18 12:04 UTC (permalink / raw)
  To: Adrian Hunter, Gregory CLEMENT, Ulf Hansson, 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

Hi Adrian,

On 2016/10/17 15:55, Adrian Hunter wrote:
> On 12/10/16 15:17, Ziji Hu wrote:
>> Hi Adrian,
>>
>> On 2016/10/11 20:39, Adrian Hunter wrote:
<snip>
>>>> +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;
>>>> +}
>>>
>>> So you are issuing commands from the ->set_ios() callback.  I would want to
>>> get Ulf's OK for that before going further.
>>>
>> 	Yes, you are correct.
>> 	In some speed mode, Xenon SDHC has to send a series of transfers to search for a perfect sampling point in PHY delay line.
>> 	It is like tuning process.
>>
>>> One thing: you will need to ensure you don't trigger get HS400 re-tuning
>>> because it will call back into ->set_ios().
>>>
>> 	Could you please make the term "HS400 re-tuning" more detailed?
>> 	In current MMC driver, "HS400 re-tuning" will go back to HS200, execute HS200 tuning and come back to HS400.
>> 	I'm sure our Xenon SDHC will not execute it.
> 
> Currently, re-tuning is automatically enabled whenever tuning is executed,
> and then re-tuning will be done periodically or after CRC errors.  The
> function to disable re-tuning mmc_retune_disable() is not exported, however
> if you have you are determining the sampling point your own way, you could
> simply not implement ->execute_tuning() and then there would be no tuning
> and no re-tuning.
>

	It is a little complex in our Xenon SDHC.
	For the speed mode which requests tuning, such as SDR104 and HS200, our driver will execute standard tuning as spec requires.
	However, for those speed mode in which tuning is not requested in spec, our driver has to issues commands to search for the best sampling point.
	
	It seems that HS400 re-tuning/tuning is disabled by default. Is it correct?

>>
>> 	However, in coming eMMC 5.2, there is a real HS400 re-tuning, in which tuning can be directly executed in HS400 mode.
>> 	Our Xenon SDHC will neither trigger this HS400 re-tuning.
>> 	But since so far there is no such feature in MMC driver, I cannot give you a 100% guarantee now.
>>
>>> And you have the problem that you need to get a reference to the card before
>>> the card device has been added.  As I wrote in response to the previous
>>> patch, you should get Ulf's help with that too.
>>>
>> 	Sure.
>> 	I will get card_candidate solved at first.
>> 	Thank you again for your review and help.
>>
>> 	Thank you.
>>
>> Best regards,
>> Hu Ziji
>>>
>>
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
@ 2016-10-18 12:04           ` Ziji Hu
  0 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-18 12:04 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Adrian,

On 2016/10/17 15:55, Adrian Hunter wrote:
> On 12/10/16 15:17, Ziji Hu wrote:
>> Hi Adrian,
>>
>> On 2016/10/11 20:39, Adrian Hunter wrote:
<snip>
>>>> +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;
>>>> +}
>>>
>>> So you are issuing commands from the ->set_ios() callback.  I would want to
>>> get Ulf's OK for that before going further.
>>>
>> 	Yes, you are correct.
>> 	In some speed mode, Xenon SDHC has to send a series of transfers to search for a perfect sampling point in PHY delay line.
>> 	It is like tuning process.
>>
>>> One thing: you will need to ensure you don't trigger get HS400 re-tuning
>>> because it will call back into ->set_ios().
>>>
>> 	Could you please make the term "HS400 re-tuning" more detailed?
>> 	In current MMC driver, "HS400 re-tuning" will go back to HS200, execute HS200 tuning and come back to HS400.
>> 	I'm sure our Xenon SDHC will not execute it.
> 
> Currently, re-tuning is automatically enabled whenever tuning is executed,
> and then re-tuning will be done periodically or after CRC errors.  The
> function to disable re-tuning mmc_retune_disable() is not exported, however
> if you have you are determining the sampling point your own way, you could
> simply not implement ->execute_tuning() and then there would be no tuning
> and no re-tuning.
>

	It is a little complex in our Xenon SDHC.
	For the speed mode which requests tuning, such as SDR104 and HS200, our driver will execute standard tuning as spec requires.
	However, for those speed mode in which tuning is not requested in spec, our driver has to issues commands to search for the best sampling point.
	
	It seems that HS400 re-tuning/tuning is disabled by default. Is it correct?

>>
>> 	However, in coming eMMC 5.2, there is a real HS400 re-tuning, in which tuning can be directly executed in HS400 mode.
>> 	Our Xenon SDHC will neither trigger this HS400 re-tuning.
>> 	But since so far there is no such feature in MMC driver, I cannot give you a 100% guarantee now.
>>
>>> And you have the problem that you need to get a reference to the card before
>>> the card device has been added.  As I wrote in response to the previous
>>> patch, you should get Ulf's help with that too.
>>>
>> 	Sure.
>> 	I will get card_candidate solved at first.
>> 	Thank you again for your review and help.
>>
>> 	Thank you.
>>
>> Best regards,
>> Hu Ziji
>>>
>>
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
  2016-10-17  8:14             ` Adrian Hunter
  (?)
@ 2016-10-18 12:09               ` Ziji Hu
  -1 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-18 12:09 UTC (permalink / raw)
  To: Adrian Hunter, Gregory CLEMENT, Ulf Hansson, 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, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

Hi Adrian,

On 2016/10/17 16:14, Adrian Hunter wrote:
> On 13/10/16 08:38, Ziji Hu wrote:
>> Hi Adrian,
>>
>> On 2016/10/12 21:07, Adrian Hunter wrote:
>>> On 12/10/16 14:58, Ziji Hu wrote:
>>>> Hi Adrian,
>>>>
>>>> 	Thank you very much for your review.
>>>> 	I will firstly fix the typo.
>>>>
>>>> On 2016/10/11 20:37, Adrian Hunter wrote:
>> <snip>
>>>>>> +
>>>>>> +static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
>>>>>> +					     struct mmc_ios *ios)
>>>>>> +{
>>>>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>>>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>>>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>>>>> +
>>>>>> +	/*
>>>>>> +	 * Before SD/SDIO set signal voltage, SD bus clock should be
>>>>>> +	 * disabled. However, sdhci_set_clock will also disable the Internal
>>>>>> +	 * clock in mmc_set_signal_voltage().
>>>>>> +	 * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
>>>>>> +	 * Thus here manually enable internal clock.
>>>>>> +	 *
>>>>>> +	 * After switch completes, it is unnecessary to disable internal clock,
>>>>>> +	 * since keeping internal clock active obeys SD spec.
>>>>>> +	 */
>>>>>> +	enable_xenon_internal_clk(host);
>>>>>> +
>>>>>> +	if (priv->card_candidate) {
>>>>>
>>>>> mmc_power_up() calls __mmc_set_signal_voltage() calls
>>>>> host->ops->start_signal_voltage_switch so priv->card_candidate could be an
>>>>> invalid reference to an old card.
>>>>>
>>>>> So that's not going to work if the card changes - not only for removable
>>>>> cards but even for eMMC if init fails and retries.
>>>>>
>>>> 	As you point out, this piece of code have defects, even though it actually works on Marvell multiple platforms, unless eMMC card is removable.
>>>>
>>>> 	I can add a property to explicitly indicate eMMC type in DTS.
>>>> 	Then card_candidate access can be removed here.
>>>> 	Does it sounds more reasonable to you?
>>>
>>> Sure
>>>
>>>>
>>>>>> +		if (mmc_card_mmc(priv->card_candidate))
>>>>>> +			return xenon_emmc_signal_voltage_switch(mmc, ios);
>>>>>
>>>>> So if all you need to know is whether it is a eMMC, why can't DT tell you?
>>>>>
>>>> 	I can add an eMMC type property in DTS, to remove the card_candidate access here.
>>>>
>>>>>> +	}
>>>>>> +
>>>>>> +	return sdhci_start_signal_voltage_switch(mmc, ios);
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * After determining which slot is used for SDIO,
>>>>>> + * some additional task is required.
>>>>>> + */
>>>>>> +static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
>>>>>> +{
>>>>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>>>>> +	u32 reg;
>>>>>> +	u8 slot_idx;
>>>>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>>>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>>>>> +
>>>>>> +	/* Link the card for delay adjustment */
>>>>>> +	priv->card_candidate = card;
>>>>>
>>>>> You really need a better way to get the card.  I suggest you take up the
>>>>> issue with Ulf.  One possibility is to have mmc core set host->card = card
>>>>> much earlier.
>>>>>
>>>> 	Could you please tell me if any issue related to card_candidate still exists, after the card_candidate is removed from xenon_start_signal_voltage_switch() in above?
>>>> 	It seems that when init_card is called, the structure card has already been updated and stable in MMC/SD/SDIO initialization sequence.
>>>> 	May I keep it here?
>>>
>>> It works by accident rather than by design.  We can do better.
>>>
>> 	Could you please tell me some details which are satisfied about card_candidate?
>>
>> 	I must admit that card_candidate in xenon_start_signal_voltage_switch() is imperfect.
>> 	But card_candidate in init_card() and later in set_ios() work by design, rather than by accident. We did a lot of tests on several platforms.
>> 	
>> 	The structure mmc_card passed in here is a stable one. Thus in my very own opinion, it is safe and stable to use mmc_card here.
>> 	card_candidate is used only in card initialization. It is not active in later transfers after initialization is done.
>> 	It will always updated with mmc_card in next card initialization.
> 
> Ok, so maybe just add some comments and more explanation of how it works.
> 
> 
	Sure. I will add more detailed comments.
>>
>>>>
>>>>>> +	/* Set tuning functionality of this slot */
>>>>>> +	xenon_slot_tuning_setup(host);
>>>>>> +
>>>>>> +	slot_idx = priv->slot_idx;
>>>>>> +	if (!mmc_card_sdio(card)) {
>>>>>> +		/* Re-enable the Auto-CMD12 cap flag. */
>>>>>> +		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>>>>> +		host->flags |= SDHCI_AUTO_CMD12;
>>>>>> +
>>>>>> +		/* Clear SDIO Card Inserted indication */
>>>>>> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
>>>>>> +		reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
>>>>>> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
>>>>>> +
>>>>>> +		if (mmc_card_mmc(card)) {
>>>>>> +			mmc->caps |= MMC_CAP_NONREMOVABLE;
>>>>>> +			if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
>>>>>> +				mmc->caps |= MMC_CAP_1_8V_DDR;
>>>>>> +			/*
>>>>>> +			 * Force to clear BUS_TEST to
>>>>>> +			 * skip bus_test_pre and bus_test_post
>>>>>> +			 */
>>>>>> +			mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
>>>>>> +			mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
>>>>>> +				      MMC_CAP2_PACKED_CMD;
>>>>>> +			if (mmc->caps & MMC_CAP_8_BIT_DATA)
>>>>>> +				mmc->caps2 |= MMC_CAP2_HS400_1_8V;
>>>>>> +		}
>>>>>> +	} else {
>>>>>> +		/*
>>>>>> +		 * Delete the Auto-CMD12 cap flag.
>>>>>> +		 * Otherwise, when sending multi-block CMD53,
>>>>>> +		 * Driver will set Transfer Mode Register to enable Auto CMD12.
>>>>>> +		 * However, SDIO device cannot recognize CMD12.
>>>>>> +		 * Thus SDHC will time-out for waiting for CMD12 response.
>>>>>> +		 */
>>>>>> +		host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>>>>> +		host->flags &= ~SDHCI_AUTO_CMD12;
>>>>>
>>>>> sdhci_set_transfer_mode() won't enable auto-CMD12 for CMD53 anyway, so is
>>>>> this needed?
>>>>>
>>>> 	In Xenon driver, Auto-CMD12 flag is set to enable full support to Auto-CMD feature, both Auto-CMD12 and Auto-CMD23.
>>>> 	As a result, when Xenon SDHC slot can both support SD and SDIO, Auto-CMD12 is disabled when SDIO card is inserted, and renabled when SD is inserted.
>>>>
>>>> 	I recheck the sdhci code to set Auto-CMD bit in Transfer Mode register, in sdhci_set_transfer_mode():
>>>> 	if (mmc_op_multi(cmd->opcode) || data->blocks > 1)
>>>> 	As you can see, as long as it is CMD18/CMD25 OR there are multiple data blocks, Auto-CMD field will be set.
>>>> 	CMD53 doesn't have CMD23. Thus Auto-CMD12 is selected since Auto-CMD12 flag is set.
>>>> 	Thus I have to clear Auto-CMD12 to avoid issuing Auto-CMD12 in SDIO transfer.
>>>
>>>
>>> The code is:
>>>
>>> 	if (mmc_op_multi(cmd->opcode) || data->blocks > 1) {
>>> 		mode = SDHCI_TRNS_BLK_CNT_EN | SDHCI_TRNS_MULTI;
>>> 		/*
>>> 		 * If we are sending CMD23, CMD12 never gets sent
>>> 		 * on successful completion (so no Auto-CMD12).
>>> 		 */
>>> 		if (sdhci_auto_cmd12(host, cmd->mrq) &&
>>> 		    (cmd->opcode != SD_IO_RW_EXTENDED))
>>> 			mode |= SDHCI_TRNS_AUTO_CMD12;
>>> 		else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
>>> 			mode |= SDHCI_TRNS_AUTO_CMD23;
>>> 			sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);
>>> 		}
>>> 	}
>>>
>>> You can see the check for SD_IO_RW_EXTENDED which is CMD53.
>>>
>> 	Sorry. I didn't notice CMD53 check was added.
>> 	I introduced this Auto-CMD12 hack since kernel 3.8. It seems that this check is not added in kernel 3.8.
>> 	Thanks for the information. I will remove the Auto-CMD12 hack.
>>
>>>>
>>>> 	I just meet a similar issue in RPMB.
>>>> 	When Auto-CMD12 flag is set, eMMC RPMB access will trigger Auto-CMD12, since CMD25 is in use.
>>>> 	It will cause RPMB access failed.
>>>
>>> Can you explain more about the RPMB issue.  Doesn't it use CMD23, so CMD12
>>> wouldn't be used - auto or manually.
>>>
>> 	RPMB go through the MMC ioctl routine.
>> 	Unlike normal data transfer, MMC ioctl for RPMB explicitly issues CMD23. When CMD25 is issued, there is neither data->sbc nor Auto-CMD23.
>> 	As a result, sdhci driver will automatically enable Auto-CMD12 for RPMB CMD25 if Auto-CMD12 flag is set.
> 
> OK, so SDHCI should also not allow auto-cmd12 if there is no stop command
> i.e. data->stop is null.
> 
	data->stop and cmd->stop are forced to be NULL if Auto-CMD12 is enabled.
	Instead, currently I use cmd->arg to check if it is a RPMB CMD25. If CMD25 argument is NULL, it should be RPMB access.
	But I only verified it on Marvell platform. Please help check it when later I propose the formal patch.
	
>>
>>>>
>>>> 	One possible solution is to drop Auto-CMD12 support and use Auto-CMD23 only, in Xenon driver.
>>>> 	May I know you opinion, please?
>>>
>>> I don't use auto-CMD12 because I don't know if it provides any benefit and
>>> sdhci does not seem to have implemented Auto CMD12 Error Recovery, although
>>> I have never looked at it closely.
>>>
>> 	Actually, Auto-CMD23 is always used on our Xenon. Auto-CMD12 is not used at all.
>> 	But since this driver is a general one for all Marvell products, Auto-CMD12 is also supported in case that Auto-CMD23 is not available.
>>  
>>
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
@ 2016-10-18 12:09               ` Ziji Hu
  0 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-18 12:09 UTC (permalink / raw)
  To: Adrian Hunter, Gregory CLEMENT, Ulf Hansson, 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

Hi Adrian,

On 2016/10/17 16:14, Adrian Hunter wrote:
> On 13/10/16 08:38, Ziji Hu wrote:
>> Hi Adrian,
>>
>> On 2016/10/12 21:07, Adrian Hunter wrote:
>>> On 12/10/16 14:58, Ziji Hu wrote:
>>>> Hi Adrian,
>>>>
>>>> 	Thank you very much for your review.
>>>> 	I will firstly fix the typo.
>>>>
>>>> On 2016/10/11 20:37, Adrian Hunter wrote:
>> <snip>
>>>>>> +
>>>>>> +static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
>>>>>> +					     struct mmc_ios *ios)
>>>>>> +{
>>>>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>>>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>>>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>>>>> +
>>>>>> +	/*
>>>>>> +	 * Before SD/SDIO set signal voltage, SD bus clock should be
>>>>>> +	 * disabled. However, sdhci_set_clock will also disable the Internal
>>>>>> +	 * clock in mmc_set_signal_voltage().
>>>>>> +	 * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
>>>>>> +	 * Thus here manually enable internal clock.
>>>>>> +	 *
>>>>>> +	 * After switch completes, it is unnecessary to disable internal clock,
>>>>>> +	 * since keeping internal clock active obeys SD spec.
>>>>>> +	 */
>>>>>> +	enable_xenon_internal_clk(host);
>>>>>> +
>>>>>> +	if (priv->card_candidate) {
>>>>>
>>>>> mmc_power_up() calls __mmc_set_signal_voltage() calls
>>>>> host->ops->start_signal_voltage_switch so priv->card_candidate could be an
>>>>> invalid reference to an old card.
>>>>>
>>>>> So that's not going to work if the card changes - not only for removable
>>>>> cards but even for eMMC if init fails and retries.
>>>>>
>>>> 	As you point out, this piece of code have defects, even though it actually works on Marvell multiple platforms, unless eMMC card is removable.
>>>>
>>>> 	I can add a property to explicitly indicate eMMC type in DTS.
>>>> 	Then card_candidate access can be removed here.
>>>> 	Does it sounds more reasonable to you?
>>>
>>> Sure
>>>
>>>>
>>>>>> +		if (mmc_card_mmc(priv->card_candidate))
>>>>>> +			return xenon_emmc_signal_voltage_switch(mmc, ios);
>>>>>
>>>>> So if all you need to know is whether it is a eMMC, why can't DT tell you?
>>>>>
>>>> 	I can add an eMMC type property in DTS, to remove the card_candidate access here.
>>>>
>>>>>> +	}
>>>>>> +
>>>>>> +	return sdhci_start_signal_voltage_switch(mmc, ios);
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * After determining which slot is used for SDIO,
>>>>>> + * some additional task is required.
>>>>>> + */
>>>>>> +static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
>>>>>> +{
>>>>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>>>>> +	u32 reg;
>>>>>> +	u8 slot_idx;
>>>>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>>>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>>>>> +
>>>>>> +	/* Link the card for delay adjustment */
>>>>>> +	priv->card_candidate = card;
>>>>>
>>>>> You really need a better way to get the card.  I suggest you take up the
>>>>> issue with Ulf.  One possibility is to have mmc core set host->card = card
>>>>> much earlier.
>>>>>
>>>> 	Could you please tell me if any issue related to card_candidate still exists, after the card_candidate is removed from xenon_start_signal_voltage_switch() in above?
>>>> 	It seems that when init_card is called, the structure card has already been updated and stable in MMC/SD/SDIO initialization sequence.
>>>> 	May I keep it here?
>>>
>>> It works by accident rather than by design.  We can do better.
>>>
>> 	Could you please tell me some details which are satisfied about card_candidate?
>>
>> 	I must admit that card_candidate in xenon_start_signal_voltage_switch() is imperfect.
>> 	But card_candidate in init_card() and later in set_ios() work by design, rather than by accident. We did a lot of tests on several platforms.
>> 	
>> 	The structure mmc_card passed in here is a stable one. Thus in my very own opinion, it is safe and stable to use mmc_card here.
>> 	card_candidate is used only in card initialization. It is not active in later transfers after initialization is done.
>> 	It will always updated with mmc_card in next card initialization.
> 
> Ok, so maybe just add some comments and more explanation of how it works.
> 
> 
	Sure. I will add more detailed comments.
>>
>>>>
>>>>>> +	/* Set tuning functionality of this slot */
>>>>>> +	xenon_slot_tuning_setup(host);
>>>>>> +
>>>>>> +	slot_idx = priv->slot_idx;
>>>>>> +	if (!mmc_card_sdio(card)) {
>>>>>> +		/* Re-enable the Auto-CMD12 cap flag. */
>>>>>> +		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>>>>> +		host->flags |= SDHCI_AUTO_CMD12;
>>>>>> +
>>>>>> +		/* Clear SDIO Card Inserted indication */
>>>>>> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
>>>>>> +		reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
>>>>>> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
>>>>>> +
>>>>>> +		if (mmc_card_mmc(card)) {
>>>>>> +			mmc->caps |= MMC_CAP_NONREMOVABLE;
>>>>>> +			if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
>>>>>> +				mmc->caps |= MMC_CAP_1_8V_DDR;
>>>>>> +			/*
>>>>>> +			 * Force to clear BUS_TEST to
>>>>>> +			 * skip bus_test_pre and bus_test_post
>>>>>> +			 */
>>>>>> +			mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
>>>>>> +			mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
>>>>>> +				      MMC_CAP2_PACKED_CMD;
>>>>>> +			if (mmc->caps & MMC_CAP_8_BIT_DATA)
>>>>>> +				mmc->caps2 |= MMC_CAP2_HS400_1_8V;
>>>>>> +		}
>>>>>> +	} else {
>>>>>> +		/*
>>>>>> +		 * Delete the Auto-CMD12 cap flag.
>>>>>> +		 * Otherwise, when sending multi-block CMD53,
>>>>>> +		 * Driver will set Transfer Mode Register to enable Auto CMD12.
>>>>>> +		 * However, SDIO device cannot recognize CMD12.
>>>>>> +		 * Thus SDHC will time-out for waiting for CMD12 response.
>>>>>> +		 */
>>>>>> +		host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>>>>> +		host->flags &= ~SDHCI_AUTO_CMD12;
>>>>>
>>>>> sdhci_set_transfer_mode() won't enable auto-CMD12 for CMD53 anyway, so is
>>>>> this needed?
>>>>>
>>>> 	In Xenon driver, Auto-CMD12 flag is set to enable full support to Auto-CMD feature, both Auto-CMD12 and Auto-CMD23.
>>>> 	As a result, when Xenon SDHC slot can both support SD and SDIO, Auto-CMD12 is disabled when SDIO card is inserted, and renabled when SD is inserted.
>>>>
>>>> 	I recheck the sdhci code to set Auto-CMD bit in Transfer Mode register, in sdhci_set_transfer_mode():
>>>> 	if (mmc_op_multi(cmd->opcode) || data->blocks > 1)
>>>> 	As you can see, as long as it is CMD18/CMD25 OR there are multiple data blocks, Auto-CMD field will be set.
>>>> 	CMD53 doesn't have CMD23. Thus Auto-CMD12 is selected since Auto-CMD12 flag is set.
>>>> 	Thus I have to clear Auto-CMD12 to avoid issuing Auto-CMD12 in SDIO transfer.
>>>
>>>
>>> The code is:
>>>
>>> 	if (mmc_op_multi(cmd->opcode) || data->blocks > 1) {
>>> 		mode = SDHCI_TRNS_BLK_CNT_EN | SDHCI_TRNS_MULTI;
>>> 		/*
>>> 		 * If we are sending CMD23, CMD12 never gets sent
>>> 		 * on successful completion (so no Auto-CMD12).
>>> 		 */
>>> 		if (sdhci_auto_cmd12(host, cmd->mrq) &&
>>> 		    (cmd->opcode != SD_IO_RW_EXTENDED))
>>> 			mode |= SDHCI_TRNS_AUTO_CMD12;
>>> 		else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
>>> 			mode |= SDHCI_TRNS_AUTO_CMD23;
>>> 			sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);
>>> 		}
>>> 	}
>>>
>>> You can see the check for SD_IO_RW_EXTENDED which is CMD53.
>>>
>> 	Sorry. I didn't notice CMD53 check was added.
>> 	I introduced this Auto-CMD12 hack since kernel 3.8. It seems that this check is not added in kernel 3.8.
>> 	Thanks for the information. I will remove the Auto-CMD12 hack.
>>
>>>>
>>>> 	I just meet a similar issue in RPMB.
>>>> 	When Auto-CMD12 flag is set, eMMC RPMB access will trigger Auto-CMD12, since CMD25 is in use.
>>>> 	It will cause RPMB access failed.
>>>
>>> Can you explain more about the RPMB issue.  Doesn't it use CMD23, so CMD12
>>> wouldn't be used - auto or manually.
>>>
>> 	RPMB go through the MMC ioctl routine.
>> 	Unlike normal data transfer, MMC ioctl for RPMB explicitly issues CMD23. When CMD25 is issued, there is neither data->sbc nor Auto-CMD23.
>> 	As a result, sdhci driver will automatically enable Auto-CMD12 for RPMB CMD25 if Auto-CMD12 flag is set.
> 
> OK, so SDHCI should also not allow auto-cmd12 if there is no stop command
> i.e. data->stop is null.
> 
	data->stop and cmd->stop are forced to be NULL if Auto-CMD12 is enabled.
	Instead, currently I use cmd->arg to check if it is a RPMB CMD25. If CMD25 argument is NULL, it should be RPMB access.
	But I only verified it on Marvell platform. Please help check it when later I propose the formal patch.
	
>>
>>>>
>>>> 	One possible solution is to drop Auto-CMD12 support and use Auto-CMD23 only, in Xenon driver.
>>>> 	May I know you opinion, please?
>>>
>>> I don't use auto-CMD12 because I don't know if it provides any benefit and
>>> sdhci does not seem to have implemented Auto CMD12 Error Recovery, although
>>> I have never looked at it closely.
>>>
>> 	Actually, Auto-CMD23 is always used on our Xenon. Auto-CMD12 is not used at all.
>> 	But since this driver is a general one for all Marvell products, Auto-CMD12 is also supported in case that Auto-CMD23 is not available.
>>  
>>
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
@ 2016-10-18 12:09               ` Ziji Hu
  0 siblings, 0 replies; 105+ messages in thread
From: Ziji Hu @ 2016-10-18 12:09 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Adrian,

On 2016/10/17 16:14, Adrian Hunter wrote:
> On 13/10/16 08:38, Ziji Hu wrote:
>> Hi Adrian,
>>
>> On 2016/10/12 21:07, Adrian Hunter wrote:
>>> On 12/10/16 14:58, Ziji Hu wrote:
>>>> Hi Adrian,
>>>>
>>>> 	Thank you very much for your review.
>>>> 	I will firstly fix the typo.
>>>>
>>>> On 2016/10/11 20:37, Adrian Hunter wrote:
>> <snip>
>>>>>> +
>>>>>> +static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
>>>>>> +					     struct mmc_ios *ios)
>>>>>> +{
>>>>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>>>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>>>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>>>>> +
>>>>>> +	/*
>>>>>> +	 * Before SD/SDIO set signal voltage, SD bus clock should be
>>>>>> +	 * disabled. However, sdhci_set_clock will also disable the Internal
>>>>>> +	 * clock in mmc_set_signal_voltage().
>>>>>> +	 * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
>>>>>> +	 * Thus here manually enable internal clock.
>>>>>> +	 *
>>>>>> +	 * After switch completes, it is unnecessary to disable internal clock,
>>>>>> +	 * since keeping internal clock active obeys SD spec.
>>>>>> +	 */
>>>>>> +	enable_xenon_internal_clk(host);
>>>>>> +
>>>>>> +	if (priv->card_candidate) {
>>>>>
>>>>> mmc_power_up() calls __mmc_set_signal_voltage() calls
>>>>> host->ops->start_signal_voltage_switch so priv->card_candidate could be an
>>>>> invalid reference to an old card.
>>>>>
>>>>> So that's not going to work if the card changes - not only for removable
>>>>> cards but even for eMMC if init fails and retries.
>>>>>
>>>> 	As you point out, this piece of code have defects, even though it actually works on Marvell multiple platforms, unless eMMC card is removable.
>>>>
>>>> 	I can add a property to explicitly indicate eMMC type in DTS.
>>>> 	Then card_candidate access can be removed here.
>>>> 	Does it sounds more reasonable to you?
>>>
>>> Sure
>>>
>>>>
>>>>>> +		if (mmc_card_mmc(priv->card_candidate))
>>>>>> +			return xenon_emmc_signal_voltage_switch(mmc, ios);
>>>>>
>>>>> So if all you need to know is whether it is a eMMC, why can't DT tell you?
>>>>>
>>>> 	I can add an eMMC type property in DTS, to remove the card_candidate access here.
>>>>
>>>>>> +	}
>>>>>> +
>>>>>> +	return sdhci_start_signal_voltage_switch(mmc, ios);
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * After determining which slot is used for SDIO,
>>>>>> + * some additional task is required.
>>>>>> + */
>>>>>> +static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
>>>>>> +{
>>>>>> +	struct sdhci_host *host = mmc_priv(mmc);
>>>>>> +	u32 reg;
>>>>>> +	u8 slot_idx;
>>>>>> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>>>>> +	struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>>>>> +
>>>>>> +	/* Link the card for delay adjustment */
>>>>>> +	priv->card_candidate = card;
>>>>>
>>>>> You really need a better way to get the card.  I suggest you take up the
>>>>> issue with Ulf.  One possibility is to have mmc core set host->card = card
>>>>> much earlier.
>>>>>
>>>> 	Could you please tell me if any issue related to card_candidate still exists, after the card_candidate is removed from xenon_start_signal_voltage_switch() in above?
>>>> 	It seems that when init_card is called, the structure card has already been updated and stable in MMC/SD/SDIO initialization sequence.
>>>> 	May I keep it here?
>>>
>>> It works by accident rather than by design.  We can do better.
>>>
>> 	Could you please tell me some details which are satisfied about card_candidate?
>>
>> 	I must admit that card_candidate in xenon_start_signal_voltage_switch() is imperfect.
>> 	But card_candidate in init_card() and later in set_ios() work by design, rather than by accident. We did a lot of tests on several platforms.
>> 	
>> 	The structure mmc_card passed in here is a stable one. Thus in my very own opinion, it is safe and stable to use mmc_card here.
>> 	card_candidate is used only in card initialization. It is not active in later transfers after initialization is done.
>> 	It will always updated with mmc_card in next card initialization.
> 
> Ok, so maybe just add some comments and more explanation of how it works.
> 
> 
	Sure. I will add more detailed comments.
>>
>>>>
>>>>>> +	/* Set tuning functionality of this slot */
>>>>>> +	xenon_slot_tuning_setup(host);
>>>>>> +
>>>>>> +	slot_idx = priv->slot_idx;
>>>>>> +	if (!mmc_card_sdio(card)) {
>>>>>> +		/* Re-enable the Auto-CMD12 cap flag. */
>>>>>> +		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>>>>> +		host->flags |= SDHCI_AUTO_CMD12;
>>>>>> +
>>>>>> +		/* Clear SDIO Card Inserted indication */
>>>>>> +		reg = sdhci_readl(host, SDHC_SYS_CFG_INFO);
>>>>>> +		reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT));
>>>>>> +		sdhci_writel(host, reg, SDHC_SYS_CFG_INFO);
>>>>>> +
>>>>>> +		if (mmc_card_mmc(card)) {
>>>>>> +			mmc->caps |= MMC_CAP_NONREMOVABLE;
>>>>>> +			if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V))
>>>>>> +				mmc->caps |= MMC_CAP_1_8V_DDR;
>>>>>> +			/*
>>>>>> +			 * Force to clear BUS_TEST to
>>>>>> +			 * skip bus_test_pre and bus_test_post
>>>>>> +			 */
>>>>>> +			mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
>>>>>> +			mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ |
>>>>>> +				      MMC_CAP2_PACKED_CMD;
>>>>>> +			if (mmc->caps & MMC_CAP_8_BIT_DATA)
>>>>>> +				mmc->caps2 |= MMC_CAP2_HS400_1_8V;
>>>>>> +		}
>>>>>> +	} else {
>>>>>> +		/*
>>>>>> +		 * Delete the Auto-CMD12 cap flag.
>>>>>> +		 * Otherwise, when sending multi-block CMD53,
>>>>>> +		 * Driver will set Transfer Mode Register to enable Auto CMD12.
>>>>>> +		 * However, SDIO device cannot recognize CMD12.
>>>>>> +		 * Thus SDHC will time-out for waiting for CMD12 response.
>>>>>> +		 */
>>>>>> +		host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
>>>>>> +		host->flags &= ~SDHCI_AUTO_CMD12;
>>>>>
>>>>> sdhci_set_transfer_mode() won't enable auto-CMD12 for CMD53 anyway, so is
>>>>> this needed?
>>>>>
>>>> 	In Xenon driver, Auto-CMD12 flag is set to enable full support to Auto-CMD feature, both Auto-CMD12 and Auto-CMD23.
>>>> 	As a result, when Xenon SDHC slot can both support SD and SDIO, Auto-CMD12 is disabled when SDIO card is inserted, and renabled when SD is inserted.
>>>>
>>>> 	I recheck the sdhci code to set Auto-CMD bit in Transfer Mode register, in sdhci_set_transfer_mode():
>>>> 	if (mmc_op_multi(cmd->opcode) || data->blocks > 1)
>>>> 	As you can see, as long as it is CMD18/CMD25 OR there are multiple data blocks, Auto-CMD field will be set.
>>>> 	CMD53 doesn't have CMD23. Thus Auto-CMD12 is selected since Auto-CMD12 flag is set.
>>>> 	Thus I have to clear Auto-CMD12 to avoid issuing Auto-CMD12 in SDIO transfer.
>>>
>>>
>>> The code is:
>>>
>>> 	if (mmc_op_multi(cmd->opcode) || data->blocks > 1) {
>>> 		mode = SDHCI_TRNS_BLK_CNT_EN | SDHCI_TRNS_MULTI;
>>> 		/*
>>> 		 * If we are sending CMD23, CMD12 never gets sent
>>> 		 * on successful completion (so no Auto-CMD12).
>>> 		 */
>>> 		if (sdhci_auto_cmd12(host, cmd->mrq) &&
>>> 		    (cmd->opcode != SD_IO_RW_EXTENDED))
>>> 			mode |= SDHCI_TRNS_AUTO_CMD12;
>>> 		else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
>>> 			mode |= SDHCI_TRNS_AUTO_CMD23;
>>> 			sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);
>>> 		}
>>> 	}
>>>
>>> You can see the check for SD_IO_RW_EXTENDED which is CMD53.
>>>
>> 	Sorry. I didn't notice CMD53 check was added.
>> 	I introduced this Auto-CMD12 hack since kernel 3.8. It seems that this check is not added in kernel 3.8.
>> 	Thanks for the information. I will remove the Auto-CMD12 hack.
>>
>>>>
>>>> 	I just meet a similar issue in RPMB.
>>>> 	When Auto-CMD12 flag is set, eMMC RPMB access will trigger Auto-CMD12, since CMD25 is in use.
>>>> 	It will cause RPMB access failed.
>>>
>>> Can you explain more about the RPMB issue.  Doesn't it use CMD23, so CMD12
>>> wouldn't be used - auto or manually.
>>>
>> 	RPMB go through the MMC ioctl routine.
>> 	Unlike normal data transfer, MMC ioctl for RPMB explicitly issues CMD23. When CMD25 is issued, there is neither data->sbc nor Auto-CMD23.
>> 	As a result, sdhci driver will automatically enable Auto-CMD12 for RPMB CMD25 if Auto-CMD12 flag is set.
> 
> OK, so SDHCI should also not allow auto-cmd12 if there is no stop command
> i.e. data->stop is null.
> 
	data->stop and cmd->stop are forced to be NULL if Auto-CMD12 is enabled.
	Instead, currently I use cmd->arg to check if it is a RPMB CMD25. If CMD25 argument is NULL, it should be RPMB access.
	But I only verified it on Marvell platform. Please help check it when later I propose the formal patch.
	
>>
>>>>
>>>> 	One possible solution is to drop Auto-CMD12 support and use Auto-CMD23 only, in Xenon driver.
>>>> 	May I know you opinion, please?
>>>
>>> I don't use auto-CMD12 because I don't know if it provides any benefit and
>>> sdhci does not seem to have implemented Auto CMD12 Error Recovery, although
>>> I have never looked at it closely.
>>>
>> 	Actually, Auto-CMD23 is always used on our Xenon. Auto-CMD12 is not used at all.
>> 	But since this driver is a general one for all Marvell products, Auto-CMD12 is also supported in case that Auto-CMD23 is not available.
>>  
>>
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
  2016-10-18 12:04           ` Ziji Hu
  (?)
@ 2016-10-18 13:09             ` Adrian Hunter
  -1 siblings, 0 replies; 105+ messages in thread
From: Adrian Hunter @ 2016-10-18 13:09 UTC (permalink / raw)
  To: Ziji Hu, Gregory CLEMENT, Ulf Hansson, 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, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

On 18/10/16 15:04, Ziji Hu wrote:
> Hi Adrian,
> 
> On 2016/10/17 15:55, Adrian Hunter wrote:
>> On 12/10/16 15:17, Ziji Hu wrote:
>>> Hi Adrian,
>>>
>>> On 2016/10/11 20:39, Adrian Hunter wrote:
> <snip>
>>>>> +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;
>>>>> +}
>>>>
>>>> So you are issuing commands from the ->set_ios() callback.  I would want to
>>>> get Ulf's OK for that before going further.
>>>>
>>> 	Yes, you are correct.
>>> 	In some speed mode, Xenon SDHC has to send a series of transfers to search for a perfect sampling point in PHY delay line.
>>> 	It is like tuning process.
>>>
>>>> One thing: you will need to ensure you don't trigger get HS400 re-tuning
>>>> because it will call back into ->set_ios().
>>>>
>>> 	Could you please make the term "HS400 re-tuning" more detailed?
>>> 	In current MMC driver, "HS400 re-tuning" will go back to HS200, execute HS200 tuning and come back to HS400.
>>> 	I'm sure our Xenon SDHC will not execute it.
>>
>> Currently, re-tuning is automatically enabled whenever tuning is executed,
>> and then re-tuning will be done periodically or after CRC errors.  The
>> function to disable re-tuning mmc_retune_disable() is not exported, however
>> if you have you are determining the sampling point your own way, you could
>> simply not implement ->execute_tuning() and then there would be no tuning
>> and no re-tuning.
>>
> 
> 	It is a little complex in our Xenon SDHC.
> 	For the speed mode which requests tuning, such as SDR104 and HS200, our driver will execute standard tuning as spec requires.
> 	However, for those speed mode in which tuning is not requested in spec, our driver has to issues commands to search for the best sampling point.
> 	
> 	It seems that HS400 re-tuning/tuning is disabled by default. Is it correct?

No, it is enabled by default - there is currently no periodic re-tuning  for
HS400 but CRC errors or runtime suspend / resume will cause re-tuning.

> 
>>>
>>> 	However, in coming eMMC 5.2, there is a real HS400 re-tuning, in which tuning can be directly executed in HS400 mode.
>>> 	Our Xenon SDHC will neither trigger this HS400 re-tuning.
>>> 	But since so far there is no such feature in MMC driver, I cannot give you a 100% guarantee now.
>>>
>>>> And you have the problem that you need to get a reference to the card before
>>>> the card device has been added.  As I wrote in response to the previous
>>>> patch, you should get Ulf's help with that too.
>>>>
>>> 	Sure.
>>> 	I will get card_candidate solved at first.
>>> 	Thank you again for your review and help.
>>>
>>> 	Thank you.
>>>
>>> Best regards,
>>> Hu Ziji
>>>>
>>>
>>
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
@ 2016-10-18 13:09             ` Adrian Hunter
  0 siblings, 0 replies; 105+ messages in thread
From: Adrian Hunter @ 2016-10-18 13:09 UTC (permalink / raw)
  To: Ziji Hu, Gregory CLEMENT, Ulf Hansson, 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

On 18/10/16 15:04, Ziji Hu wrote:
> Hi Adrian,
> 
> On 2016/10/17 15:55, Adrian Hunter wrote:
>> On 12/10/16 15:17, Ziji Hu wrote:
>>> Hi Adrian,
>>>
>>> On 2016/10/11 20:39, Adrian Hunter wrote:
> <snip>
>>>>> +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;
>>>>> +}
>>>>
>>>> So you are issuing commands from the ->set_ios() callback.  I would want to
>>>> get Ulf's OK for that before going further.
>>>>
>>> 	Yes, you are correct.
>>> 	In some speed mode, Xenon SDHC has to send a series of transfers to search for a perfect sampling point in PHY delay line.
>>> 	It is like tuning process.
>>>
>>>> One thing: you will need to ensure you don't trigger get HS400 re-tuning
>>>> because it will call back into ->set_ios().
>>>>
>>> 	Could you please make the term "HS400 re-tuning" more detailed?
>>> 	In current MMC driver, "HS400 re-tuning" will go back to HS200, execute HS200 tuning and come back to HS400.
>>> 	I'm sure our Xenon SDHC will not execute it.
>>
>> Currently, re-tuning is automatically enabled whenever tuning is executed,
>> and then re-tuning will be done periodically or after CRC errors.  The
>> function to disable re-tuning mmc_retune_disable() is not exported, however
>> if you have you are determining the sampling point your own way, you could
>> simply not implement ->execute_tuning() and then there would be no tuning
>> and no re-tuning.
>>
> 
> 	It is a little complex in our Xenon SDHC.
> 	For the speed mode which requests tuning, such as SDR104 and HS200, our driver will execute standard tuning as spec requires.
> 	However, for those speed mode in which tuning is not requested in spec, our driver has to issues commands to search for the best sampling point.
> 	
> 	It seems that HS400 re-tuning/tuning is disabled by default. Is it correct?

No, it is enabled by default - there is currently no periodic re-tuning  for
HS400 but CRC errors or runtime suspend / resume will cause re-tuning.

> 
>>>
>>> 	However, in coming eMMC 5.2, there is a real HS400 re-tuning, in which tuning can be directly executed in HS400 mode.
>>> 	Our Xenon SDHC will neither trigger this HS400 re-tuning.
>>> 	But since so far there is no such feature in MMC driver, I cannot give you a 100% guarantee now.
>>>
>>>> And you have the problem that you need to get a reference to the card before
>>>> the card device has been added.  As I wrote in response to the previous
>>>> patch, you should get Ulf's help with that too.
>>>>
>>> 	Sure.
>>> 	I will get card_candidate solved at first.
>>> 	Thank you again for your review and help.
>>>
>>> 	Thank you.
>>>
>>> Best regards,
>>> Hu Ziji
>>>>
>>>
>>
> 


^ permalink raw reply	[flat|nested] 105+ messages in thread

* [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
@ 2016-10-18 13:09             ` Adrian Hunter
  0 siblings, 0 replies; 105+ messages in thread
From: Adrian Hunter @ 2016-10-18 13:09 UTC (permalink / raw)
  To: linux-arm-kernel

On 18/10/16 15:04, Ziji Hu wrote:
> Hi Adrian,
> 
> On 2016/10/17 15:55, Adrian Hunter wrote:
>> On 12/10/16 15:17, Ziji Hu wrote:
>>> Hi Adrian,
>>>
>>> On 2016/10/11 20:39, Adrian Hunter wrote:
> <snip>
>>>>> +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;
>>>>> +}
>>>>
>>>> So you are issuing commands from the ->set_ios() callback.  I would want to
>>>> get Ulf's OK for that before going further.
>>>>
>>> 	Yes, you are correct.
>>> 	In some speed mode, Xenon SDHC has to send a series of transfers to search for a perfect sampling point in PHY delay line.
>>> 	It is like tuning process.
>>>
>>>> One thing: you will need to ensure you don't trigger get HS400 re-tuning
>>>> because it will call back into ->set_ios().
>>>>
>>> 	Could you please make the term "HS400 re-tuning" more detailed?
>>> 	In current MMC driver, "HS400 re-tuning" will go back to HS200, execute HS200 tuning and come back to HS400.
>>> 	I'm sure our Xenon SDHC will not execute it.
>>
>> Currently, re-tuning is automatically enabled whenever tuning is executed,
>> and then re-tuning will be done periodically or after CRC errors.  The
>> function to disable re-tuning mmc_retune_disable() is not exported, however
>> if you have you are determining the sampling point your own way, you could
>> simply not implement ->execute_tuning() and then there would be no tuning
>> and no re-tuning.
>>
> 
> 	It is a little complex in our Xenon SDHC.
> 	For the speed mode which requests tuning, such as SDR104 and HS200, our driver will execute standard tuning as spec requires.
> 	However, for those speed mode in which tuning is not requested in spec, our driver has to issues commands to search for the best sampling point.
> 	
> 	It seems that HS400 re-tuning/tuning is disabled by default. Is it correct?

No, it is enabled by default - there is currently no periodic re-tuning  for
HS400 but CRC errors or runtime suspend / resume will cause re-tuning.

> 
>>>
>>> 	However, in coming eMMC 5.2, there is a real HS400 re-tuning, in which tuning can be directly executed in HS400 mode.
>>> 	Our Xenon SDHC will neither trigger this HS400 re-tuning.
>>> 	But since so far there is no such feature in MMC driver, I cannot give you a 100% guarantee now.
>>>
>>>> And you have the problem that you need to get a reference to the card before
>>>> the card device has been added.  As I wrote in response to the previous
>>>> patch, you should get Ulf's help with that too.
>>>>
>>> 	Sure.
>>> 	I will get card_candidate solved at first.
>>> 	Thank you again for your review and help.
>>>
>>> 	Thank you.
>>>
>>> Best regards,
>>> Hu Ziji
>>>>
>>>
>>
> 

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 5/10] dt: bindings: Add bindings for Marvell Xenon SD Host Controller
  2016-10-11 10:03       ` Ziji Hu
  (?)
@ 2016-10-18 13:29         ` Gregory CLEMENT
  -1 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-18 13:29 UTC (permalink / raw)
  To: Rob Herring
  Cc: Rob Herring, Ziji Hu, Ulf Hansson, Adrian Hunter, linux-mmc,
	Jason Cooper, Andrew Lunn, Sebastian Hesselbarth, 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, Keji Zhang, Liuliu Zhao, Peng Zhu, Yu Cao,
	Romain Perier, Yehuda Yitschak, Marcin Wojtas, Hanna Hawa,
	Kostya Porotchkin, linux-kernel

Hi Rob,
 
 On mar., oct. 11 2016, Ziji Hu <huziji@marvell.com> wrote:
[...]

>>> +  Different Xenon SDHC release has different register set size.
>>> +  The specific size should also refer to the SOC implementation.
>>> +
>>> +Optional Properties:
>>> +- Slot Index
>>> +  A single Xenon IP can support multiple slots.
>>> +  During initialization, each slot should set corresponding setting bit in
>>> +  some Xenon-specific registers. The corresponding bit is determined by
>>> +  this property.
>>> +  - xenon,slotno = <slot_index>;
>> 
>> Slots should probably be represented as child nodes with the reg 
>> property being the slot number.
>
> 	Since each SDHC slot is independent, I find it is more
> convenient to implement each one as independent SD host/MMC host
> instant.
> 	Otherwise, a main function should loop and initialize each
> slot, like sdhci-pci. I prefer to avoiding such a unnecessary main
> function.
>
> 	It is very hard to determine the slot number by reg property.
> 	Xenon slots are likely to be different types. 1st slot might
> be eMMC and 2nd one might be SD. They might have different register
> size.
> 	The register size might also varies in different Xenon versions.
>

Something that took me a while to figure out is that even it is the same
hardware block which handle multiple SoCs.

Each slots is managed by its own set of register. From the point of view
of the OS, it is as if we have an independent controller for each
slot.

But for an obscure reason, some command need to know which slot is
used. That's why we ended with this property.

With some example what you had in mind was something like that:
sdhci@aa0000 {
		compatible = "marvell,armada-3700-sdhci";
		reg = <0xaa0000 0x1000>;
[...]
                slot0 {
                      /* slot0 is an eMMC */
                      reg = <0>;
                      bus-width = <8>;
                      xenon,pad-type = "fixed-1-8v";

                }
                slot1 {
                      /* slot1 is an SD Card */
                      reg = <1>;
                      bus-width = <4>;
                      xenon,pad-type = "fixed-1-8v";

                }
	};

But it won't work as each slot uses its own address registers, that why we
ended with this:
sdhci@aa0000 {
                /* slot0 is an eMMC */
		compatible = "marvell,armada-3700-sdhci";
		reg = <0xaa0000 0x1000>;
[...]
                xenon,slotno = <0>;
                bus-width = <8>;
                xenon,pad-type = "fixed-1-8v";
	};
sdhci@bb0000 {
                /* slot1 is an SD Card */
		compatible = "marvell,armada-3700-sdhci";
		reg = <0xbb0000 0x1000>;
[...]
                xenon,slotno = <1>;
                bus-width = <4>;
                xenon,pad-type = "fixed-1-8v";
	};

I hope it is more clear now.

Gregory

-- 
Gregory Clement, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 5/10] dt: bindings: Add bindings for Marvell Xenon SD Host Controller
@ 2016-10-18 13:29         ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-18 13:29 UTC (permalink / raw)
  To: Rob Herring
  Cc: Rob Herring, Ziji Hu, Ulf Hansson, Adrian Hunter, linux-mmc,
	Jason Cooper, Andrew Lunn, Sebastian Hesselbarth, 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

Hi Rob,
 
 On mar., oct. 11 2016, Ziji Hu <huziji@marvell.com> wrote:
[...]

>>> +  Different Xenon SDHC release has different register set size.
>>> +  The specific size should also refer to the SOC implementation.
>>> +
>>> +Optional Properties:
>>> +- Slot Index
>>> +  A single Xenon IP can support multiple slots.
>>> +  During initialization, each slot should set corresponding setting bit in
>>> +  some Xenon-specific registers. The corresponding bit is determined by
>>> +  this property.
>>> +  - xenon,slotno = <slot_index>;
>> 
>> Slots should probably be represented as child nodes with the reg 
>> property being the slot number.
>
> 	Since each SDHC slot is independent, I find it is more
> convenient to implement each one as independent SD host/MMC host
> instant.
> 	Otherwise, a main function should loop and initialize each
> slot, like sdhci-pci. I prefer to avoiding such a unnecessary main
> function.
>
> 	It is very hard to determine the slot number by reg property.
> 	Xenon slots are likely to be different types. 1st slot might
> be eMMC and 2nd one might be SD. They might have different register
> size.
> 	The register size might also varies in different Xenon versions.
>

Something that took me a while to figure out is that even it is the same
hardware block which handle multiple SoCs.

Each slots is managed by its own set of register. From the point of view
of the OS, it is as if we have an independent controller for each
slot.

But for an obscure reason, some command need to know which slot is
used. That's why we ended with this property.

With some example what you had in mind was something like that:
sdhci@aa0000 {
		compatible = "marvell,armada-3700-sdhci";
		reg = <0xaa0000 0x1000>;
[...]
                slot0 {
                      /* slot0 is an eMMC */
                      reg = <0>;
                      bus-width = <8>;
                      xenon,pad-type = "fixed-1-8v";

                }
                slot1 {
                      /* slot1 is an SD Card */
                      reg = <1>;
                      bus-width = <4>;
                      xenon,pad-type = "fixed-1-8v";

                }
	};

But it won't work as each slot uses its own address registers, that why we
ended with this:
sdhci@aa0000 {
                /* slot0 is an eMMC */
		compatible = "marvell,armada-3700-sdhci";
		reg = <0xaa0000 0x1000>;
[...]
                xenon,slotno = <0>;
                bus-width = <8>;
                xenon,pad-type = "fixed-1-8v";
	};
sdhci@bb0000 {
                /* slot1 is an SD Card */
		compatible = "marvell,armada-3700-sdhci";
		reg = <0xbb0000 0x1000>;
[...]
                xenon,slotno = <1>;
                bus-width = <4>;
                xenon,pad-type = "fixed-1-8v";
	};

I hope it is more clear now.

Gregory

-- 
Gregory Clement, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com

^ permalink raw reply	[flat|nested] 105+ messages in thread

* [PATCH 5/10] dt: bindings: Add bindings for Marvell Xenon SD Host Controller
@ 2016-10-18 13:29         ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-18 13:29 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Rob,
 
 On mar., oct. 11 2016, Ziji Hu <huziji@marvell.com> wrote:
[...]

>>> +  Different Xenon SDHC release has different register set size.
>>> +  The specific size should also refer to the SOC implementation.
>>> +
>>> +Optional Properties:
>>> +- Slot Index
>>> +  A single Xenon IP can support multiple slots.
>>> +  During initialization, each slot should set corresponding setting bit in
>>> +  some Xenon-specific registers. The corresponding bit is determined by
>>> +  this property.
>>> +  - xenon,slotno = <slot_index>;
>> 
>> Slots should probably be represented as child nodes with the reg 
>> property being the slot number.
>
> 	Since each SDHC slot is independent, I find it is more
> convenient to implement each one as independent SD host/MMC host
> instant.
> 	Otherwise, a main function should loop and initialize each
> slot, like sdhci-pci. I prefer to avoiding such a unnecessary main
> function.
>
> 	It is very hard to determine the slot number by reg property.
> 	Xenon slots are likely to be different types. 1st slot might
> be eMMC and 2nd one might be SD. They might have different register
> size.
> 	The register size might also varies in different Xenon versions.
>

Something that took me a while to figure out is that even it is the same
hardware block which handle multiple SoCs.

Each slots is managed by its own set of register. From the point of view
of the OS, it is as if we have an independent controller for each
slot.

But for an obscure reason, some command need to know which slot is
used. That's why we ended with this property.

With some example what you had in mind was something like that:
sdhci at aa0000 {
		compatible = "marvell,armada-3700-sdhci";
		reg = <0xaa0000 0x1000>;
[...]
                slot0 {
                      /* slot0 is an eMMC */
                      reg = <0>;
                      bus-width = <8>;
                      xenon,pad-type = "fixed-1-8v";

                }
                slot1 {
                      /* slot1 is an SD Card */
                      reg = <1>;
                      bus-width = <4>;
                      xenon,pad-type = "fixed-1-8v";

                }
	};

But it won't work as each slot uses its own address registers, that why we
ended with this:
sdhci at aa0000 {
                /* slot0 is an eMMC */
		compatible = "marvell,armada-3700-sdhci";
		reg = <0xaa0000 0x1000>;
[...]
                xenon,slotno = <0>;
                bus-width = <8>;
                xenon,pad-type = "fixed-1-8v";
	};
sdhci at bb0000 {
                /* slot1 is an SD Card */
		compatible = "marvell,armada-3700-sdhci";
		reg = <0xbb0000 0x1000>;
[...]
                xenon,slotno = <1>;
                bus-width = <4>;
                xenon,pad-type = "fixed-1-8v";
	};

I hope it is more clear now.

Gregory

-- 
Gregory Clement, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 0/10] mmc: Add support to Marvell Xenon SD Host Controller
  2016-10-31 11:09 ` Gregory CLEMENT
  (?)
@ 2016-11-23  8:30   ` Gregory CLEMENT
  -1 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-11-23  8:30 UTC (permalink / raw)
  To: Ulf Hansson
  Cc: Adrian Hunter, linux-mmc, Jason Cooper, Andrew Lunn,
	Sebastian Hesselbarth, Rob Herring, devicetree, Thomas Petazzoni,
	linux-arm-kernel, 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, Peng Zhu, Yu Cao, Romain Perier,
	Yehu da Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

Hi Ulf,
 
 On lun., oct. 31 2016, Gregory CLEMENT <gregory.clement@free-electrons.com> wrote:

> Hello,
>
> This the second version of the series adding support for the SDHCI
> Xenon controller. It can be currently found on the Armada 37xx and the
> Armada 7K/8K but will be also used in more Marvell SoC (and not only
> the mvebu ones actually).
>
> Some of the remarks had been taking into account since the first
> version, according to Ziji Hu, here are the following chcanges:
> "Changes in V2:
>   rebase on v4.9-rc2.
>   Re-write Xenon bindings. Ajust Xenon DT property naming.
>   Add a new DT property to indicate eMMC card type, instead of using
>   variable card_candidate.
>   Clear quirks SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 in Xenon platform data
>   Add support to HS400 retuning."
>
> I think the main open point which remains is about issuing commands
> from the ->set_ios() callback (in patch 7).
> Ulf, could you comment about it?

This part is the last thing missing, we are about to solve the last
issues about the binding, but we still didn't have your opinion about
issuing commands from the ->set_ios() callback and Adrian required it to
take this series.

To have more context you can have a look on:
http://marc.info/?l=linux-mmc&m=147618996414673&w=2

it is the original email where Adrian wanted your agreement.

Thanks,

Gregory

-- 
Gregory Clement, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 0/10] mmc: Add support to Marvell Xenon SD Host Controller
@ 2016-11-23  8:30   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-11-23  8:30 UTC (permalink / raw)
  To: Ulf Hansson
  Cc: Hilbert Zhang, Andrew Lunn, Romain Perier, Liuliu Zhao, Peng Zhu,
	linux-kernel, Nadav Haklai, Ziji Hu, Victor Gu, Doug Jones,
	Jisheng Zhang, Yehu da Yitschak, Marcin Wojtas, Xueping Liu,
	Shiwu Zhang, Yu Cao, Sebastian Hesselbarth, devicetree,
	Jason Cooper, Hanna Hawa, Kostya Porotchkin, Rob Herring,
	Ryan Gao, Wei(SOCP) Liu, linux-arm-kernel

Hi Ulf,
 
 On lun., oct. 31 2016, Gregory CLEMENT <gregory.clement@free-electrons.com> wrote:

> Hello,
>
> This the second version of the series adding support for the SDHCI
> Xenon controller. It can be currently found on the Armada 37xx and the
> Armada 7K/8K but will be also used in more Marvell SoC (and not only
> the mvebu ones actually).
>
> Some of the remarks had been taking into account since the first
> version, according to Ziji Hu, here are the following chcanges:
> "Changes in V2:
>   rebase on v4.9-rc2.
>   Re-write Xenon bindings. Ajust Xenon DT property naming.
>   Add a new DT property to indicate eMMC card type, instead of using
>   variable card_candidate.
>   Clear quirks SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 in Xenon platform data
>   Add support to HS400 retuning."
>
> I think the main open point which remains is about issuing commands
> from the ->set_ios() callback (in patch 7).
> Ulf, could you comment about it?

This part is the last thing missing, we are about to solve the last
issues about the binding, but we still didn't have your opinion about
issuing commands from the ->set_ios() callback and Adrian required it to
take this series.

To have more context you can have a look on:
http://marc.info/?l=linux-mmc&m=147618996414673&w=2

it is the original email where Adrian wanted your agreement.

Thanks,

Gregory

-- 
Gregory Clement, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com

^ permalink raw reply	[flat|nested] 105+ messages in thread

* [PATCH 0/10] mmc: Add support to Marvell Xenon SD Host Controller
@ 2016-11-23  8:30   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-11-23  8:30 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Ulf,
 
 On lun., oct. 31 2016, Gregory CLEMENT <gregory.clement@free-electrons.com> wrote:

> Hello,
>
> This the second version of the series adding support for the SDHCI
> Xenon controller. It can be currently found on the Armada 37xx and the
> Armada 7K/8K but will be also used in more Marvell SoC (and not only
> the mvebu ones actually).
>
> Some of the remarks had been taking into account since the first
> version, according to Ziji Hu, here are the following chcanges:
> "Changes in V2:
>   rebase on v4.9-rc2.
>   Re-write Xenon bindings. Ajust Xenon DT property naming.
>   Add a new DT property to indicate eMMC card type, instead of using
>   variable card_candidate.
>   Clear quirks SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 in Xenon platform data
>   Add support to HS400 retuning."
>
> I think the main open point which remains is about issuing commands
> from the ->set_ios() callback (in patch 7).
> Ulf, could you comment about it?

This part is the last thing missing, we are about to solve the last
issues about the binding, but we still didn't have your opinion about
issuing commands from the ->set_ios() callback and Adrian required it to
take this series.

To have more context you can have a look on:
http://marc.info/?l=linux-mmc&m=147618996414673&w=2

it is the original email where Adrian wanted your agreement.

Thanks,

Gregory

-- 
Gregory Clement, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 0/10] mmc: Add support to Marvell Xenon SD Host Controller
@ 2016-11-04 11:20   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-11-04 11:20 UTC (permalink / raw)
  To: Ulf Hansson
  Cc: Adrian Hunter, linux-mmc, Jason Cooper, Andrew Lunn,
	Sebastian Hesselbarth, Rob Herring, devicetree, Thomas Petazzoni,
	linux-arm-kernel, 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, Peng Zhu, Yu Cao, Romain Perier,
	Yehu da Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

Hi,
 
 On lun., oct. 31 2016, Gregory CLEMENT <gregory.clement@free-electrons.com> wrote:

> Hello,
>
> This the second version of the series adding support for the SDHCI
> Xenon controller. It can be currently found on the Armada 37xx and the
> Armada 7K/8K but will be also used in more Marvell SoC (and not only
> the mvebu ones actually).
>
> Some of the remarks had been taking into account since the first
> version, according to Ziji Hu, here are the following chcanges:
> "Changes in V2:
>   rebase on v4.9-rc2.
>   Re-write Xenon bindings. Ajust Xenon DT property naming.
>   Add a new DT property to indicate eMMC card type, instead of using
>   variable card_candidate.
>   Clear quirks SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 in Xenon platform data
>   Add support to HS400 retuning."
>
> I think the main open point which remains is about issuing commands
> from the ->set_ios() callback (in patch 7).
> Ulf, could you comment about it?

A few comments:

- I forgot to add the v2 prefix on the series I hope it won't be too
  annoying, if needed I can re-post the series with the correct title.

- I also forgot to add my Reviewed-by flag on the 7 first patches, I
  will add them back on the v3.

- For the ones who want to get the series using git here is the place
  you can get it here:

repository: git@github.com:MISL-EBU-System-SW/mainline-public.git
branch: sdhci-xenon-v2

Thanks,

Gregory

>
> Thanks,
>
> Gregory
>
> Gregory CLEMENT (3):
>   arm64: dts: marvell: add eMMC support for Armada 37xx
>   arm64: dts: marvell: add sdhci support for Armada 7K/8K
>   arm64: configs: enable SDHCI driver for Xenon
>
> Ziji Hu (7):
>   mmc: sdhci: Export sdhci_set_ios() from sdhci.c
>   mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
>   mmc: sdhci: Export sdhci_execute_tuning() in sdhci.c
>   MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
>   dt: bindings: Add bindings for Marvell Xenon SD Host Controller
>   mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
>   mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
>
>  Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt |  161 +-
>  MAINTAINERS                                                   |    7 +-
>  arch/arm64/boot/dts/marvell/armada-3720-db.dts                |    8 +-
>  arch/arm64/boot/dts/marvell/armada-37xx.dtsi                  |   11 +-
>  arch/arm64/boot/dts/marvell/armada-7040-db.dts                |    8 +-
>  arch/arm64/boot/dts/marvell/armada-ap806.dtsi                 |    9 +-
>  arch/arm64/configs/defconfig                                  |    1 +-
>  drivers/mmc/host/Kconfig                                      |    9 +-
>  drivers/mmc/host/Makefile                                     |    3 +-
>  drivers/mmc/host/sdhci-xenon-phy.c                            | 1181 +++++++-
>  drivers/mmc/host/sdhci-xenon-phy.h                            |  157 +-
>  drivers/mmc/host/sdhci-xenon.c                                |  598 ++++-
>  drivers/mmc/host/sdhci-xenon.h                                |  159 +-
>  drivers/mmc/host/sdhci.c                                      |   11 +-
>  drivers/mmc/host/sdhci.h                                      |    4 +-
>  15 files changed, 2323 insertions(+), 4 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
>  create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c
>  create mode 100644 drivers/mmc/host/sdhci-xenon-phy.h
>  create mode 100644 drivers/mmc/host/sdhci-xenon.c
>  create mode 100644 drivers/mmc/host/sdhci-xenon.h
>
> base-commit: 9fe68cad6e74967b88d0c6aeca7d9cd6b6e91942
> -- 
> git-series 0.8.10

-- 
Gregory Clement, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com

^ permalink raw reply	[flat|nested] 105+ messages in thread

* Re: [PATCH 0/10] mmc: Add support to Marvell Xenon SD Host Controller
@ 2016-11-04 11:20   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-11-04 11:20 UTC (permalink / raw)
  To: Ulf Hansson
  Cc: Adrian Hunter, linux-mmc-u79uwXL29TY76Z2rM5mHXA, 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

Hi,
 
 On lun., oct. 31 2016, Gregory CLEMENT <gregory.clement-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> wrote:

> Hello,
>
> This the second version of the series adding support for the SDHCI
> Xenon controller. It can be currently found on the Armada 37xx and the
> Armada 7K/8K but will be also used in more Marvell SoC (and not only
> the mvebu ones actually).
>
> Some of the remarks had been taking into account since the first
> version, according to Ziji Hu, here are the following chcanges:
> "Changes in V2:
>   rebase on v4.9-rc2.
>   Re-write Xenon bindings. Ajust Xenon DT property naming.
>   Add a new DT property to indicate eMMC card type, instead of using
>   variable card_candidate.
>   Clear quirks SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 in Xenon platform data
>   Add support to HS400 retuning."
>
> I think the main open point which remains is about issuing commands
> from the ->set_ios() callback (in patch 7).
> Ulf, could you comment about it?

A few comments:

- I forgot to add the v2 prefix on the series I hope it won't be too
  annoying, if needed I can re-post the series with the correct title.

- I also forgot to add my Reviewed-by flag on the 7 first patches, I
  will add them back on the v3.

- For the ones who want to get the series using git here is the place
  you can get it here:

repository: git-9UaJU3cA/F/QT0dZR+AlfA@public.gmane.org:MISL-EBU-System-SW/mainline-public.git
branch: sdhci-xenon-v2

Thanks,

Gregory

>
> Thanks,
>
> Gregory
>
> Gregory CLEMENT (3):
>   arm64: dts: marvell: add eMMC support for Armada 37xx
>   arm64: dts: marvell: add sdhci support for Armada 7K/8K
>   arm64: configs: enable SDHCI driver for Xenon
>
> Ziji Hu (7):
>   mmc: sdhci: Export sdhci_set_ios() from sdhci.c
>   mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
>   mmc: sdhci: Export sdhci_execute_tuning() in sdhci.c
>   MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
>   dt: bindings: Add bindings for Marvell Xenon SD Host Controller
>   mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
>   mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
>
>  Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt |  161 +-
>  MAINTAINERS                                                   |    7 +-
>  arch/arm64/boot/dts/marvell/armada-3720-db.dts                |    8 +-
>  arch/arm64/boot/dts/marvell/armada-37xx.dtsi                  |   11 +-
>  arch/arm64/boot/dts/marvell/armada-7040-db.dts                |    8 +-
>  arch/arm64/boot/dts/marvell/armada-ap806.dtsi                 |    9 +-
>  arch/arm64/configs/defconfig                                  |    1 +-
>  drivers/mmc/host/Kconfig                                      |    9 +-
>  drivers/mmc/host/Makefile                                     |    3 +-
>  drivers/mmc/host/sdhci-xenon-phy.c                            | 1181 +++++++-
>  drivers/mmc/host/sdhci-xenon-phy.h                            |  157 +-
>  drivers/mmc/host/sdhci-xenon.c                                |  598 ++++-
>  drivers/mmc/host/sdhci-xenon.h                                |  159 +-
>  drivers/mmc/host/sdhci.c                                      |   11 +-
>  drivers/mmc/host/sdhci.h                                      |    4 +-
>  15 files changed, 2323 insertions(+), 4 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
>  create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c
>  create mode 100644 drivers/mmc/host/sdhci-xenon-phy.h
>  create mode 100644 drivers/mmc/host/sdhci-xenon.c
>  create mode 100644 drivers/mmc/host/sdhci-xenon.h
>
> base-commit: 9fe68cad6e74967b88d0c6aeca7d9cd6b6e91942
> -- 
> git-series 0.8.10

-- 
Gregory Clement, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com
--
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	[flat|nested] 105+ messages in thread

* [PATCH 0/10] mmc: Add support to Marvell Xenon SD Host Controller
@ 2016-11-04 11:20   ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-11-04 11:20 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,
 
 On lun., oct. 31 2016, Gregory CLEMENT <gregory.clement@free-electrons.com> wrote:

> Hello,
>
> This the second version of the series adding support for the SDHCI
> Xenon controller. It can be currently found on the Armada 37xx and the
> Armada 7K/8K but will be also used in more Marvell SoC (and not only
> the mvebu ones actually).
>
> Some of the remarks had been taking into account since the first
> version, according to Ziji Hu, here are the following chcanges:
> "Changes in V2:
>   rebase on v4.9-rc2.
>   Re-write Xenon bindings. Ajust Xenon DT property naming.
>   Add a new DT property to indicate eMMC card type, instead of using
>   variable card_candidate.
>   Clear quirks SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 in Xenon platform data
>   Add support to HS400 retuning."
>
> I think the main open point which remains is about issuing commands
> from the ->set_ios() callback (in patch 7).
> Ulf, could you comment about it?

A few comments:

- I forgot to add the v2 prefix on the series I hope it won't be too
  annoying, if needed I can re-post the series with the correct title.

- I also forgot to add my Reviewed-by flag on the 7 first patches, I
  will add them back on the v3.

- For the ones who want to get the series using git here is the place
  you can get it here:

repository: git at github.com:MISL-EBU-System-SW/mainline-public.git
branch: sdhci-xenon-v2

Thanks,

Gregory

>
> Thanks,
>
> Gregory
>
> Gregory CLEMENT (3):
>   arm64: dts: marvell: add eMMC support for Armada 37xx
>   arm64: dts: marvell: add sdhci support for Armada 7K/8K
>   arm64: configs: enable SDHCI driver for Xenon
>
> Ziji Hu (7):
>   mmc: sdhci: Export sdhci_set_ios() from sdhci.c
>   mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
>   mmc: sdhci: Export sdhci_execute_tuning() in sdhci.c
>   MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
>   dt: bindings: Add bindings for Marvell Xenon SD Host Controller
>   mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
>   mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
>
>  Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt |  161 +-
>  MAINTAINERS                                                   |    7 +-
>  arch/arm64/boot/dts/marvell/armada-3720-db.dts                |    8 +-
>  arch/arm64/boot/dts/marvell/armada-37xx.dtsi                  |   11 +-
>  arch/arm64/boot/dts/marvell/armada-7040-db.dts                |    8 +-
>  arch/arm64/boot/dts/marvell/armada-ap806.dtsi                 |    9 +-
>  arch/arm64/configs/defconfig                                  |    1 +-
>  drivers/mmc/host/Kconfig                                      |    9 +-
>  drivers/mmc/host/Makefile                                     |    3 +-
>  drivers/mmc/host/sdhci-xenon-phy.c                            | 1181 +++++++-
>  drivers/mmc/host/sdhci-xenon-phy.h                            |  157 +-
>  drivers/mmc/host/sdhci-xenon.c                                |  598 ++++-
>  drivers/mmc/host/sdhci-xenon.h                                |  159 +-
>  drivers/mmc/host/sdhci.c                                      |   11 +-
>  drivers/mmc/host/sdhci.h                                      |    4 +-
>  15 files changed, 2323 insertions(+), 4 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
>  create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c
>  create mode 100644 drivers/mmc/host/sdhci-xenon-phy.h
>  create mode 100644 drivers/mmc/host/sdhci-xenon.c
>  create mode 100644 drivers/mmc/host/sdhci-xenon.h
>
> base-commit: 9fe68cad6e74967b88d0c6aeca7d9cd6b6e91942
> -- 
> git-series 0.8.10

-- 
Gregory Clement, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com

^ permalink raw reply	[flat|nested] 105+ messages in thread

* [PATCH 0/10] mmc: Add support to Marvell Xenon SD Host Controller
@ 2016-10-31 11:09 ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-31 11:09 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, Rob Herring, devicetree, Thomas Petazzoni,
	linux-arm-kernel, 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, Peng Zhu, Yu Cao, Romain Perier,
	Yehuda Yitschak, Marcin Wojtas, Hanna Hawa, Kostya Porotchkin,
	linux-kernel

Hello,

This the second version of the series adding support for the SDHCI
Xenon controller. It can be currently found on the Armada 37xx and the
Armada 7K/8K but will be also used in more Marvell SoC (and not only
the mvebu ones actually).

Some of the remarks had been taking into account since the first
version, according to Ziji Hu, here are the following chcanges:
"Changes in V2:
  rebase on v4.9-rc2.
  Re-write Xenon bindings. Ajust Xenon DT property naming.
  Add a new DT property to indicate eMMC card type, instead of using
  variable card_candidate.
  Clear quirks SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 in Xenon platform data
  Add support to HS400 retuning."

I think the main open point which remains is about issuing commands
from the ->set_ios() callback (in patch 7).
Ulf, could you comment about it?

Thanks,

Gregory

Gregory CLEMENT (3):
  arm64: dts: marvell: add eMMC support for Armada 37xx
  arm64: dts: marvell: add sdhci support for Armada 7K/8K
  arm64: configs: enable SDHCI driver for Xenon

Ziji Hu (7):
  mmc: sdhci: Export sdhci_set_ios() from sdhci.c
  mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
  mmc: sdhci: Export sdhci_execute_tuning() in sdhci.c
  MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
  dt: bindings: Add bindings for Marvell Xenon SD Host Controller
  mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
  mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC

 Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt |  161 +-
 MAINTAINERS                                                   |    7 +-
 arch/arm64/boot/dts/marvell/armada-3720-db.dts                |    8 +-
 arch/arm64/boot/dts/marvell/armada-37xx.dtsi                  |   11 +-
 arch/arm64/boot/dts/marvell/armada-7040-db.dts                |    8 +-
 arch/arm64/boot/dts/marvell/armada-ap806.dtsi                 |    9 +-
 arch/arm64/configs/defconfig                                  |    1 +-
 drivers/mmc/host/Kconfig                                      |    9 +-
 drivers/mmc/host/Makefile                                     |    3 +-
 drivers/mmc/host/sdhci-xenon-phy.c                            | 1181 +++++++-
 drivers/mmc/host/sdhci-xenon-phy.h                            |  157 +-
 drivers/mmc/host/sdhci-xenon.c                                |  598 ++++-
 drivers/mmc/host/sdhci-xenon.h                                |  159 +-
 drivers/mmc/host/sdhci.c                                      |   11 +-
 drivers/mmc/host/sdhci.h                                      |    4 +-
 15 files changed, 2323 insertions(+), 4 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
 create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c
 create mode 100644 drivers/mmc/host/sdhci-xenon-phy.h
 create mode 100644 drivers/mmc/host/sdhci-xenon.c
 create mode 100644 drivers/mmc/host/sdhci-xenon.h

base-commit: 9fe68cad6e74967b88d0c6aeca7d9cd6b6e91942
-- 
git-series 0.8.10

^ permalink raw reply	[flat|nested] 105+ messages in thread

* [PATCH 0/10] mmc: Add support to Marvell Xenon SD Host Controller
@ 2016-10-31 11:09 ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-31 11:09 UTC (permalink / raw)
  To: Ulf Hansson, Adrian Hunter, linux-mmc
  Cc: Jason Cooper, Andrew Lunn, Sebastian Hesselbarth,
	Gregory CLEMENT, Rob Herring, devicetree, Thomas Petazzoni,
	linux-arm-kernel, 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

Hello,

This the second version of the series adding support for the SDHCI
Xenon controller. It can be currently found on the Armada 37xx and the
Armada 7K/8K but will be also used in more Marvell SoC (and not only
the mvebu ones actually).

Some of the remarks had been taking into account since the first
version, according to Ziji Hu, here are the following chcanges:
"Changes in V2:
  rebase on v4.9-rc2.
  Re-write Xenon bindings. Ajust Xenon DT property naming.
  Add a new DT property to indicate eMMC card type, instead of using
  variable card_candidate.
  Clear quirks SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 in Xenon platform data
  Add support to HS400 retuning."

I think the main open point which remains is about issuing commands
from the ->set_ios() callback (in patch 7).
Ulf, could you comment about it?

Thanks,

Gregory

Gregory CLEMENT (3):
  arm64: dts: marvell: add eMMC support for Armada 37xx
  arm64: dts: marvell: add sdhci support for Armada 7K/8K
  arm64: configs: enable SDHCI driver for Xenon

Ziji Hu (7):
  mmc: sdhci: Export sdhci_set_ios() from sdhci.c
  mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
  mmc: sdhci: Export sdhci_execute_tuning() in sdhci.c
  MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
  dt: bindings: Add bindings for Marvell Xenon SD Host Controller
  mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
  mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC

 Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt |  161 +-
 MAINTAINERS                                                   |    7 +-
 arch/arm64/boot/dts/marvell/armada-3720-db.dts                |    8 +-
 arch/arm64/boot/dts/marvell/armada-37xx.dtsi                  |   11 +-
 arch/arm64/boot/dts/marvell/armada-7040-db.dts                |    8 +-
 arch/arm64/boot/dts/marvell/armada-ap806.dtsi                 |    9 +-
 arch/arm64/configs/defconfig                                  |    1 +-
 drivers/mmc/host/Kconfig                                      |    9 +-
 drivers/mmc/host/Makefile                                     |    3 +-
 drivers/mmc/host/sdhci-xenon-phy.c                            | 1181 +++++++-
 drivers/mmc/host/sdhci-xenon-phy.h                            |  157 +-
 drivers/mmc/host/sdhci-xenon.c                                |  598 ++++-
 drivers/mmc/host/sdhci-xenon.h                                |  159 +-
 drivers/mmc/host/sdhci.c                                      |   11 +-
 drivers/mmc/host/sdhci.h                                      |    4 +-
 15 files changed, 2323 insertions(+), 4 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
 create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c
 create mode 100644 drivers/mmc/host/sdhci-xenon-phy.h
 create mode 100644 drivers/mmc/host/sdhci-xenon.c
 create mode 100644 drivers/mmc/host/sdhci-xenon.h

base-commit: 9fe68cad6e74967b88d0c6aeca7d9cd6b6e91942
-- 
git-series 0.8.10

^ permalink raw reply	[flat|nested] 105+ messages in thread

* [PATCH 0/10] mmc: Add support to Marvell Xenon SD Host Controller
@ 2016-10-31 11:09 ` Gregory CLEMENT
  0 siblings, 0 replies; 105+ messages in thread
From: Gregory CLEMENT @ 2016-10-31 11:09 UTC (permalink / raw)
  To: linux-arm-kernel

Hello,

This the second version of the series adding support for the SDHCI
Xenon controller. It can be currently found on the Armada 37xx and the
Armada 7K/8K but will be also used in more Marvell SoC (and not only
the mvebu ones actually).

Some of the remarks had been taking into account since the first
version, according to Ziji Hu, here are the following chcanges:
"Changes in V2:
  rebase on v4.9-rc2.
  Re-write Xenon bindings. Ajust Xenon DT property naming.
  Add a new DT property to indicate eMMC card type, instead of using
  variable card_candidate.
  Clear quirks SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 in Xenon platform data
  Add support to HS400 retuning."

I think the main open point which remains is about issuing commands
from the ->set_ios() callback (in patch 7).
Ulf, could you comment about it?

Thanks,

Gregory

Gregory CLEMENT (3):
  arm64: dts: marvell: add eMMC support for Armada 37xx
  arm64: dts: marvell: add sdhci support for Armada 7K/8K
  arm64: configs: enable SDHCI driver for Xenon

Ziji Hu (7):
  mmc: sdhci: Export sdhci_set_ios() from sdhci.c
  mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
  mmc: sdhci: Export sdhci_execute_tuning() in sdhci.c
  MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers
  dt: bindings: Add bindings for Marvell Xenon SD Host Controller
  mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
  mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC

 Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt |  161 +-
 MAINTAINERS                                                   |    7 +-
 arch/arm64/boot/dts/marvell/armada-3720-db.dts                |    8 +-
 arch/arm64/boot/dts/marvell/armada-37xx.dtsi                  |   11 +-
 arch/arm64/boot/dts/marvell/armada-7040-db.dts                |    8 +-
 arch/arm64/boot/dts/marvell/armada-ap806.dtsi                 |    9 +-
 arch/arm64/configs/defconfig                                  |    1 +-
 drivers/mmc/host/Kconfig                                      |    9 +-
 drivers/mmc/host/Makefile                                     |    3 +-
 drivers/mmc/host/sdhci-xenon-phy.c                            | 1181 +++++++-
 drivers/mmc/host/sdhci-xenon-phy.h                            |  157 +-
 drivers/mmc/host/sdhci-xenon.c                                |  598 ++++-
 drivers/mmc/host/sdhci-xenon.h                                |  159 +-
 drivers/mmc/host/sdhci.c                                      |   11 +-
 drivers/mmc/host/sdhci.h                                      |    4 +-
 15 files changed, 2323 insertions(+), 4 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
 create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c
 create mode 100644 drivers/mmc/host/sdhci-xenon-phy.h
 create mode 100644 drivers/mmc/host/sdhci-xenon.c
 create mode 100644 drivers/mmc/host/sdhci-xenon.h

base-commit: 9fe68cad6e74967b88d0c6aeca7d9cd6b6e91942
-- 
git-series 0.8.10

^ permalink raw reply	[flat|nested] 105+ messages in thread

end of thread, other threads:[~2016-11-23  8:37 UTC | newest]

Thread overview: 105+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-10-07 15:22 [PATCH 0/10] mmc: Add support to Marvell Xenon SD Host Controller Gregory CLEMENT
2016-10-07 15:22 ` Gregory CLEMENT
2016-10-07 15:22 ` Gregory CLEMENT
2016-10-07 15:22 ` [PATCH 1/10] mmc: sdhci: Export sdhci_set_ios() from sdhci.c Gregory CLEMENT
2016-10-07 15:22   ` Gregory CLEMENT
2016-10-07 15:22   ` Gregory CLEMENT
2016-10-07 15:22 ` [PATCH 2/10] mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c Gregory CLEMENT
2016-10-07 15:22   ` Gregory CLEMENT
2016-10-07 15:22   ` Gregory CLEMENT
2016-10-08  2:40   ` Shawn Lin
2016-10-08  2:40     ` Shawn Lin
2016-10-08  2:40     ` Shawn Lin
2016-10-08  6:26     ` Ziji Hu
2016-10-08  6:26       ` Ziji Hu
2016-10-08  6:26       ` Ziji Hu
2016-10-07 15:22 ` [PATCH 3/10] mmc: sdhci: Export sdhci_execute_tuning() " Gregory CLEMENT
2016-10-07 15:22   ` Gregory CLEMENT
2016-10-07 15:22   ` Gregory CLEMENT
2016-10-07 15:22 ` [PATCH 4/10] MAINTAINERS: add entry for Marvell Xenon MMC Host Controller drivers Gregory CLEMENT
2016-10-07 15:22   ` Gregory CLEMENT
2016-10-07 15:22   ` Gregory CLEMENT
2016-10-07 20:44   ` Joe Perches
2016-10-07 20:44     ` Joe Perches
2016-10-07 20:44     ` Joe Perches
2016-10-08  0:59     ` Ziji Hu
2016-10-08  0:59       ` Ziji Hu
2016-10-08  0:59       ` Ziji Hu
2016-10-07 15:22 ` [PATCH 5/10] dt: bindings: Add bindings for Marvell Xenon SD Host Controller Gregory CLEMENT
2016-10-07 15:22   ` Gregory CLEMENT
2016-10-07 15:22   ` Gregory CLEMENT
2016-10-10 21:34   ` Rob Herring
2016-10-10 21:34     ` Rob Herring
2016-10-10 21:34     ` Rob Herring
2016-10-11 10:03     ` Ziji Hu
2016-10-11 10:03       ` Ziji Hu
2016-10-11 10:03       ` Ziji Hu
2016-10-18 13:29       ` Gregory CLEMENT
2016-10-18 13:29         ` Gregory CLEMENT
2016-10-18 13:29         ` Gregory CLEMENT
2016-10-07 15:22 ` [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality Gregory CLEMENT
2016-10-07 15:22   ` Gregory CLEMENT
2016-10-07 15:22   ` Gregory CLEMENT
2016-10-11 12:37   ` Adrian Hunter
2016-10-11 12:37     ` Adrian Hunter
2016-10-11 12:37     ` Adrian Hunter
2016-10-12 11:58     ` Ziji Hu
2016-10-12 11:58       ` Ziji Hu
2016-10-12 11:58       ` Ziji Hu
2016-10-12 13:07       ` Adrian Hunter
2016-10-12 13:07         ` Adrian Hunter
2016-10-12 13:07         ` Adrian Hunter
2016-10-13  5:38         ` Ziji Hu
2016-10-13  5:38           ` Ziji Hu
2016-10-13  5:38           ` Ziji Hu
2016-10-17  8:14           ` Adrian Hunter
2016-10-17  8:14             ` Adrian Hunter
2016-10-17  8:14             ` Adrian Hunter
2016-10-18 12:09             ` Ziji Hu
2016-10-18 12:09               ` Ziji Hu
2016-10-18 12:09               ` Ziji Hu
2016-10-07 15:22 ` [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC Gregory CLEMENT
2016-10-07 15:22   ` Gregory CLEMENT
2016-10-07 15:22   ` Gregory CLEMENT
2016-10-08  2:44   ` Shawn Lin
2016-10-08  2:44     ` Shawn Lin
2016-10-08  2:44     ` Shawn Lin
2016-10-08  9:28     ` Ziji Hu
2016-10-08  9:28       ` Ziji Hu
2016-10-08  9:28       ` Ziji Hu
2016-10-09 13:34       ` Shawn Lin
2016-10-09 13:34         ` Shawn Lin
2016-10-09 13:34         ` Shawn Lin
2016-10-11 12:39   ` Adrian Hunter
2016-10-11 12:39     ` Adrian Hunter
2016-10-11 12:39     ` Adrian Hunter
2016-10-12 12:17     ` Ziji Hu
2016-10-12 12:17       ` Ziji Hu
2016-10-12 12:17       ` Ziji Hu
2016-10-17  7:55       ` Adrian Hunter
2016-10-17  7:55         ` Adrian Hunter
2016-10-17  7:55         ` Adrian Hunter
2016-10-18 12:04         ` Ziji Hu
2016-10-18 12:04           ` Ziji Hu
2016-10-18 12:04           ` Ziji Hu
2016-10-18 13:09           ` Adrian Hunter
2016-10-18 13:09             ` Adrian Hunter
2016-10-18 13:09             ` Adrian Hunter
2016-10-07 15:22 ` [PATCH 8/10] arm64: dts: marvell: add eMMC support for Armada 37xx Gregory CLEMENT
2016-10-07 15:22   ` Gregory CLEMENT
2016-10-07 15:22   ` Gregory CLEMENT
2016-10-07 15:22 ` [PATCH 9/10] arm64: dts: marvell: add sdhci support for Armada 7K/8K Gregory CLEMENT
2016-10-07 15:22   ` Gregory CLEMENT
2016-10-07 15:22   ` Gregory CLEMENT
2016-10-07 15:22 ` [PATCH 10/10] arm64: configs: enable SDHCI driver for Xenon Gregory CLEMENT
2016-10-07 15:22   ` Gregory CLEMENT
2016-10-07 15:22   ` Gregory CLEMENT
2016-10-31 11:09 [PATCH 0/10] mmc: Add support to Marvell Xenon SD Host Controller Gregory CLEMENT
2016-10-31 11:09 ` Gregory CLEMENT
2016-10-31 11:09 ` Gregory CLEMENT
2016-11-04 11:20 ` Gregory CLEMENT
2016-11-04 11:20   ` Gregory CLEMENT
2016-11-04 11:20   ` Gregory CLEMENT
2016-11-23  8:30 ` Gregory CLEMENT
2016-11-23  8:30   ` Gregory CLEMENT
2016-11-23  8:30   ` Gregory CLEMENT

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.