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
From: Ziji Hu <[email protected]>
Export sdhci_execute_tuning() from sdhci.c.
Thus vendor sdhci driver can execute its own tuning process.
Signed-off-by: Hu Ziji <[email protected]>
Signed-off-by: Gregory CLEMENT <[email protected]>
---
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 8e6e4e37e3b4..e971abb1368f 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1950,7 +1950,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;
@@ -2139,6 +2139,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 cd18b6f19c3b..95beadc66849 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -691,6 +691,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
From: Ziji Hu <[email protected]>
Add maintainer entry for Marvell Xenon eMMC/SD/SDIO Host
Controller drivers.
Signed-off-by: Hu Ziji <[email protected]>
Signed-off-by: Gregory CLEMENT <[email protected]>
---
MAINTAINERS | 5 +++++
1 file changed, 5 insertions(+), 0 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index c44795306342..1a5c4c30ea24 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7604,6 +7604,11 @@ M: Nicolas Pitre <[email protected]>
S: Odd Fixes
F: drivers/mmc/host/mvsdio.*
+MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
+M: Ziji Hu <[email protected]>
+L: [email protected]
+S: Supported
+
MATROX FRAMEBUFFER DRIVER
L: [email protected]
S: Orphan
--
git-series 0.8.10
From: Ziji Hu <[email protected]>
Export sdhci_set_ios() in sdhci.c.
Thus vendor sdhci driver can implement its own set_ios() routine.
Signed-off-by: Hu Ziji <[email protected]>
Signed-off-by: Gregory CLEMENT <[email protected]>
---
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 71654b90227f..ea06faf8a437 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1563,7 +1563,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;
@@ -1723,6 +1723,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 766df17fb7eb..37771de4cafa 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -688,6 +688,7 @@ void sdhci_set_power_noreg(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
From: Ziji Hu <[email protected]>
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 <[email protected]>
Signed-off-by: Gregory CLEMENT <[email protected]>
---
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 ea06faf8a437..8e6e4e37e3b4 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1832,8 +1832,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;
@@ -1925,6 +1925,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 37771de4cafa..cd18b6f19c3b 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -689,6 +689,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
Also enable it on the Armada 7040 DB board
Signed-off-by: Gregory CLEMENT <[email protected]>
---
arch/arm64/boot/dts/marvell/armada-7040-db.dts | 8 ++++++++
arch/arm64/boot/dts/marvell/armada-ap806.dtsi | 9 +++++++++
2 files changed, 17 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..f8bdabdbd864 100644
--- a/arch/arm64/boot/dts/marvell/armada-7040-db.dts
+++ b/arch/arm64/boot/dts/marvell/armada-7040-db.dts
@@ -146,3 +146,11 @@
&cpm_usb3_1 {
status = "okay";
};
+
+&sdhci0 {
+ status = "okay";
+ bus-width = <4>;
+ no-1-8-v;
+ non-removable;
+ marvell,xenon-emmc;
+};
diff --git a/arch/arm64/boot/dts/marvell/armada-ap806.dtsi b/arch/arm64/boot/dts/marvell/armada-ap806.dtsi
index 7b6136182ad0..174c41b24d4c 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,xenon-sdhci";
+ 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
From: Ziji Hu <[email protected]>
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 <[email protected]>
Signed-off-by: Gregory CLEMENT <[email protected]>
---
MAINTAINERS | 2 +-
drivers/mmc/host/Makefile | 2 +-
drivers/mmc/host/sdhci-xenon-phy.c | 1181 +++++++++++++++++++++++++++++-
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, 1361 insertions(+), 2 deletions(-)
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 d92f4175574b..bb33286aeb48 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7608,7 +7608,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
M: Ziji Hu <[email protected]>
L: [email protected]
S: Supported
-F: drivers/mmc/host/sdhci-xenon.*
+F: drivers/mmc/host/sdhci-xenon*
F: Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.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..af32f8842e0b
--- /dev/null
+++ b/drivers/mmc/host/sdhci-xenon-phy.c
@@ -0,0 +1,1181 @@
+/*
+ * PHY support for Xenon SDHC
+ *
+ * Copyright (C) 2016 Marvell, All Rights Reserved.
+ *
+ * Author: Hu Ziji <[email protected]>
+ * 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);
+}
+
+static void __emmc_phy_disable_data_strobe(struct sdhci_host *host)
+{
+ u32 reg;
+
+ /* Disable SDHC Data Strobe */
+ reg = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
+ reg &= ~ENABLE_DATA_STROBE;
+ sdhci_writel(host, reg, SDHC_SLOT_EMMC_CTRL);
+}
+
+#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);
+
+ reg = sdhci_readl(host, phy_regs->func_ctrl);
+ if ((timing == MMC_TIMING_UHS_DDR50) ||
+ (timing == MMC_TIMING_MMC_HS400) ||
+ (timing == MMC_TIMING_MMC_DDR52))
+ reg |= (DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) | CMD_DDR_MODE;
+ else
+ reg &= ~((DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) |
+ CMD_DDR_MODE);
+
+ if (timing == MMC_TIMING_MMC_HS400)
+ reg &= ~DQ_ASYNC_MODE;
+ else
+ 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);
+ else
+ __emmc_phy_disable_data_strobe(host);
+
+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, "marvell,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, "marvell,xenon-phy-slow-mode"))
+ params->slow_mode = true;
+ else
+ params->slow_mode = false;
+
+ if (!of_property_read_u32(np, "marvell,xenon-phy-znr", &value))
+ params->znr = value & ZNR_MASK;
+ else
+ params->znr = ZNR_DEF_VALUE;
+
+ if (!of_property_read_u32(np, "marvell,xenon-phy-zpr", &value))
+ params->zpr = value & ZPR_MASK;
+ else
+ params->zpr = ZPR_DEF_VALUE;
+
+ if (!of_property_read_u32(np, "marvell,xenon-phy-nr-success-tun",
+ &value))
+ params->nr_tun_times = value & TUN_CONSECUTIVE_TIMES_MASK;
+ else
+ params->nr_tun_times = TUN_CONSECUTIVE_TIMES;
+
+ if (!of_property_read_u32(np, "marvell,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;
+ }
+ /*
+ * Skip temp stages from HS400 t0 HS200:
+ * from 200MHz to 52MHz in HS400
+ * from HS400 to HS DDR in 52MHz
+ * from HS DDR to HS in 52MHz
+ * from HS to HS200 in 52MHz
+ */
+ if (((priv->timing == MMC_TIMING_MMC_HS400) &&
+ ((host->clock == MMC_HIGH_52_MAX_DTR) ||
+ (ios->timing == MMC_TIMING_MMC_DDR52))) ||
+ ((priv->timing == MMC_TIMING_MMC_DDR52) &&
+ (ios->timing == MMC_TIMING_MMC_HS)) ||
+ ((ios->timing == MMC_TIMING_MMC_HS200) &&
+ (ios->clock == MMC_HIGH_52_MAX_DTR))) {
+ 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;
+
+ if (mmc->card)
+ card = mmc->card;
+ else
+ /*
+ * Only valid during initialization
+ * before mmc->card is set
+ */
+ 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, "marvell,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 <[email protected]>
+ * 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 3ea059f2aaab..ee02014a2917 100644
--- a/drivers/mmc/host/sdhci-xenon.c
+++ b/drivers/mmc/host/sdhci-xenon.c
@@ -228,6 +228,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);
@@ -313,6 +314,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->emmc_slot)
return xenon_emmc_signal_voltage_switch(mmc, ios);
@@ -448,6 +451,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 4601d0a4b22f..e6ee47c227aa 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
@@ -99,6 +101,15 @@ struct sdhci_xenon_priv {
/* Whether this slot is for eMMC */
bool emmc_slot;
+ 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 for the speed mode in which
@@ -139,4 +150,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
Add the eMMC support for Armada 37xx SoC and enable it in the Armada 3720
DB board.
Signed-off-by: Gregory CLEMENT <[email protected]>
---
arch/arm64/boot/dts/marvell/armada-3720-db.dts | 8 ++++++++
arch/arm64/boot/dts/marvell/armada-37xx.dtsi | 11 +++++++++++
2 files changed, 19 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..9107dd3e2a44 100644
--- a/arch/arm64/boot/dts/marvell/armada-3720-db.dts
+++ b/arch/arm64/boot/dts/marvell/armada-3720-db.dts
@@ -72,6 +72,14 @@
status = "okay";
};
+&sdhci0 {
+ non-removable;
+ bus-width = <8>;
+ marvell,xenon-emmc;
+ marvell,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
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 <[email protected]>
---
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
From: Ziji Hu <[email protected]>
Marvell Xenon SDHC can support eMMC/SD/SDIO.
Add Xenon-specific properties.
Also add properties for Xenon PHY setting.
Signed-off-by: Hu Ziji <[email protected]>
Signed-off-by: Gregory CLEMENT <[email protected]>
---
Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt | 161 +++++++-
MAINTAINERS | 1 +-
2 files changed, 162 insertions(+), 0 deletions(-)
create mode 100644 Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
diff --git a/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt b/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
new file mode 100644
index 000000000000..0d2d139494d3
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
@@ -0,0 +1,161 @@
+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 one of the following
+ - "marvell,armada-3700-sdhci": For controllers on Armada-3700 SOC.
+ Must provide a second register area and marvell,pad-type.
+ - "marvell,xenon-sdhci": For controllers on all the SOCs, other than
+ Armada-3700.
+
+- clocks:
+ Array of clocks required for SDHCI.
+ Requires at least one for Xenon IP core.
+ Some SOCs require additional clock for AXI bus.
+
+- clock-names:
+ Array of names corresponding to clocks property.
+ The input clock for Xenon IP core should be named as "core".
+ The optional AXI clock should be named as "axi".
+
+- reg:
+ * For "marvell,xenon-sdhci", one register area for Xenon IP.
+
+ * For "marvell,armada-3700-sdhci", two register areas.
+ The first one for Xenon IP register. The second one for the Armada 3700 SOC
+ PHY PAD Voltage Control register.
+ Please follow the examples with compatible "marvell,armada-3700-sdhci"
+ in below.
+ Please also check property marvell,pad-type in below.
+
+Optional Properties:
+- marvell,xenon-slotno:
+ Indicate the corresponding bit index of current Xenon SDHC slot in
+ SDHC System Operation Control Register Bit[7:0].
+ Set/clear the corresponding bit to enable/disable current Xenon SDHC
+ slot.
+ If this property is not provided, Xenon IP should contain only one
+ slot.
+
+- marvell,xenon-phy-type:
+ Xenon support mutilple types of PHYs.
+ To select eMMC 5.1 PHY, set:
+ marvell,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:
+ marvell,xenon-phy-type = "emmc 5.0 phy"
+ To select SDH PHY, set:
+ marvell,xenon-phy-type = "sdh phy"
+ Please note that eMMC PHY is a general PHY for eMMC/SD/SDIO, other than for
+ eMMC only.
+
+- marvell,xenon-phy-znr:
+ Set PHY ZNR value.
+ Only available for eMMC PHY 5.1 and eMMC PHY 5.0.
+ valid range = [0:0x1F].
+ ZNR is set as 0xF by default if this property is not provided.
+
+- marvell,xenon-phy-zpr:
+ Set PHY ZPR value.
+ Only available for eMMC PHY 5.1 and eMMC PHY 5.0.
+ valid range = [0:0x1F].
+ ZPR is set as 0xF by default if this property is not provided.
+
+- marvell,xenon-phy-nr-success-tun:
+ 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.
+
+- marvell,xenon-phy-tun-step-divider:
+ Set the divider for calculating TUN_STEP.
+ Set as 64 by default if this property is not provided.
+
+- marvell,xenon-phy-slow-mode:
+ 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.
+
+- marvell,xenon-mask-conflict-err:
+ Mask Conflict Error alert on some SOC. Disabled by default.
+
+- marvell,xenon-tun-count:
+ Xenon SDHC SOC usually doesn't provide re-tuning counter in
+ Capabilities Register 3 Bit[11:8].
+ This property provides the re-tuning counter.
+ If this property is not set, default re-tuning counter will
+ be set as 0x9 in driver.
+
+- marvell,pad-type:
+ Type of Armada 3700 SOC PHY PAD Voltiage Controller register.
+ Only valid when "marvell,armada-3700-sdhci" is selected.
+ 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.
+ Please follow the examples with compatible "marvell,armada-3700-sdhci"
+ in below.
+
+Example:
+- For eMMC slot:
+
+ sdhci@aa0000 {
+ compatible = "marvell,xenon-sdhci";
+ reg = <0xaa0000 0x1000>;
+ interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
+ clocks = <&emmc_clk>, <&axi_clock>;
+ clock-names = "core", "axi";
+ bus-width = <8>;
+ marvell,xenon-emmc;
+ marvell,xenon-slotno = <0>;
+ marvell,xenon-phy-type = "emmc 5.1 phy";
+ marvell,xenon-tun-count = <11>;
+ };
+
+- For SD/SDIO slot:
+
+ sdhci@ab0000 {
+ compatible = "marvell,xenon-sdhci";
+ reg = <0xab0000 0x1000>;
+ interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
+ vqmmc-supply = <&sd_regulator>;
+ clocks = <&sdclk>;
+ clock-names = "core";
+ bus-width = <4>;
+ marvell,xenon-tun-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>;
+ marvell,xenon-emmc;
+
+ marvell,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>;
+
+ marvell,pad-type = "sd";
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 1a5c4c30ea24..850a0afb0c8d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7608,6 +7608,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
M: Ziji Hu <[email protected]>
L: [email protected]
S: Supported
+F: Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
MATROX FRAMEBUFFER DRIVER
L: [email protected]
--
git-series 0.8.10
From: Ziji Hu <[email protected]>
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 <[email protected]>
Signed-off-by: Gregory CLEMENT <[email protected]>
---
MAINTAINERS | 1 +-
drivers/mmc/host/Kconfig | 9 +-
drivers/mmc/host/Makefile | 3 +-
drivers/mmc/host/sdhci-xenon.c | 594 ++++++++++++++++++++++++++++++++++-
drivers/mmc/host/sdhci-xenon.h | 142 ++++++++-
5 files changed, 749 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 850a0afb0c8d..d92f4175574b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7608,6 +7608,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
M: Ziji Hu <[email protected]>
L: [email protected]
S: Supported
+F: drivers/mmc/host/sdhci-xenon.*
F: Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.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..3ea059f2aaab
--- /dev/null
+++ b/drivers/mmc/host/sdhci-xenon.c
@@ -0,0 +1,594 @@
+/*
+ * Driver for Marvell SOC Platform Group Xenon SDHC as a platform device
+ *
+ * Copyright (C) 2016 Marvell, All Rights Reserved.
+ *
+ * Author: Hu Ziji <[email protected]>
+ * 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 <[email protected]>
+ * 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);
+
+ /* Disable 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-retuning 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_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;
+ u32 reg;
+
+ /*
+ * 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;
+
+ reg = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ reg &= ~SDHCI_CTRL_PRESET_VAL_ENABLE;
+ sdhci_writew(host, reg, SDHCI_HOST_CONTROL2);
+ } 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->emmc_slot)
+ 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)) {
+ /* 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 {
+ /*
+ * 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:
+ * emmc: explicitly indicate whether this slot is for eMMC
+ * slotno: the index of slot. Refer to SDHC_SYS_CFG_INFO register
+ * tun-count: the interval between re-tuning
+ * PHY type: "sdhc phy", "emmc phy 5.0" or "emmc phy 5.1"
+ */
+ if (of_property_read_bool(np, "marvell,xenon-emmc"))
+ priv->emmc_slot = true;
+ else
+ priv->emmc_slot = false;
+
+ if (!of_property_read_u32(np, "marvell,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, "marvell,xenon-tun-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, "marvell,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,xenon-sdhci",},
+ { .compatible = "marvell,armada-3700-sdhci",},
+ {}
+};
+MODULE_DEVICE_TABLE(of, sdhci_xenon_dt_ids);
+
+static struct platform_driver sdhci_xenon_driver = {
+ .driver = {
+ .name = "xenon-sdhci",
+ .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 <[email protected]>");
+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..4601d0a4b22f
--- /dev/null
+++ b/drivers/mmc/host/sdhci-xenon.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2016 Marvell, All Rights Reserved.
+ *
+ * Author: Hu Ziji <[email protected]>
+ * 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;
+ /* Whether this slot is for eMMC */
+ bool emmc_slot;
+
+ /*
+ * When initializing card, Xenon has to determine card type and
+ * adjust Sampling Fixed delay for the speed mode in which
+ * DLL tuning is not support.
+ * 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.
+ *
+ * It is only valid during initialization after it is updated in
+ * xenon_init_card().
+ * Do not access this variable in normal transfers after
+ * initialization completes.
+ */
+ 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
Hi,
On lun., oct. 31 2016, Gregory CLEMENT <[email protected]> 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: [email protected]: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
On Mon, Oct 31, 2016 at 12:09:54PM +0100, Gregory CLEMENT wrote:
> From: Ziji Hu <[email protected]>
>
> Marvell Xenon SDHC can support eMMC/SD/SDIO.
> Add Xenon-specific properties.
> Also add properties for Xenon PHY setting.
>
> Signed-off-by: Hu Ziji <[email protected]>
> Signed-off-by: Gregory CLEMENT <[email protected]>
> ---
> Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt | 161 +++++++-
> MAINTAINERS | 1 +-
> 2 files changed, 162 insertions(+), 0 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
>
> diff --git a/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt b/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
> new file mode 100644
> index 000000000000..0d2d139494d3
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
> @@ -0,0 +1,161 @@
> +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 one of the following
> + - "marvell,armada-3700-sdhci": For controllers on Armada-3700 SOC.
> + Must provide a second register area and marvell,pad-type.
> + - "marvell,xenon-sdhci": For controllers on all the SOCs, other than
> + Armada-3700.
Need SoC specific compatible strings.
> +
> +- clocks:
> + Array of clocks required for SDHCI.
> + Requires at least one for Xenon IP core.
> + Some SOCs require additional clock for AXI bus.
> +
> +- clock-names:
> + Array of names corresponding to clocks property.
> + The input clock for Xenon IP core should be named as "core".
> + The optional AXI clock should be named as "axi".
When is AXI clock optional? This should be required for ?? compatible
strings.
> +
> +- reg:
> + * For "marvell,xenon-sdhci", one register area for Xenon IP.
> +
> + * For "marvell,armada-3700-sdhci", two register areas.
> + The first one for Xenon IP register. The second one for the Armada 3700 SOC
> + PHY PAD Voltage Control register.
> + Please follow the examples with compatible "marvell,armada-3700-sdhci"
> + in below.
> + Please also check property marvell,pad-type in below.
> +
> +Optional Properties:
> +- marvell,xenon-slotno:
Multiple slots should be represented as child nodes IMO. I think some
other bindings already do this.
> + Indicate the corresponding bit index of current Xenon SDHC slot in
> + SDHC System Operation Control Register Bit[7:0].
> + Set/clear the corresponding bit to enable/disable current Xenon SDHC
> + slot.
> + If this property is not provided, Xenon IP should contain only one
> + slot.
> +
> +- marvell,xenon-phy-type:
> + Xenon support mutilple types of PHYs.
> + To select eMMC 5.1 PHY, set:
> + marvell,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:
> + marvell,xenon-phy-type = "emmc 5.0 phy"
> + To select SDH PHY, set:
> + marvell,xenon-phy-type = "sdh phy"
> + Please note that eMMC PHY is a general PHY for eMMC/SD/SDIO, other than for
> + eMMC only.
Does this vary per instance on a single SoC? If not, then an SoC
specific compatible should determine this.
Also, the " phy" part is redundant.
> +
> +- marvell,xenon-phy-znr:
> + Set PHY ZNR value.
> + Only available for eMMC PHY 5.1 and eMMC PHY 5.0.
> + valid range = [0:0x1F].
> + ZNR is set as 0xF by default if this property is not provided.
> +
> +- marvell,xenon-phy-zpr:
> + Set PHY ZPR value.
> + Only available for eMMC PHY 5.1 and eMMC PHY 5.0.
> + valid range = [0:0x1F].
> + ZPR is set as 0xF by default if this property is not provided.
> +
> +- marvell,xenon-phy-nr-success-tun:
> + 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.
> +
> +- marvell,xenon-phy-tun-step-divider:
> + Set the divider for calculating TUN_STEP.
> + Set as 64 by default if this property is not provided.
> +
> +- marvell,xenon-phy-slow-mode:
> + 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.
> +
> +- marvell,xenon-mask-conflict-err:
> + Mask Conflict Error alert on some SOC. Disabled by default.
> +
> +- marvell,xenon-tun-count:
> + Xenon SDHC SOC usually doesn't provide re-tuning counter in
> + Capabilities Register 3 Bit[11:8].
> + This property provides the re-tuning counter.
> + If this property is not set, default re-tuning counter will
> + be set as 0x9 in driver.
> +
> +- marvell,pad-type:
> + Type of Armada 3700 SOC PHY PAD Voltiage Controller register.
> + Only valid when "marvell,armada-3700-sdhci" is selected.
> + 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.
> + Please follow the examples with compatible "marvell,armada-3700-sdhci"
> + in below.
> +
> +Example:
> +- For eMMC slot:
> +
> + sdhci@aa0000 {
> + compatible = "marvell,xenon-sdhci";
> + reg = <0xaa0000 0x1000>;
> + interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
> + clocks = <&emmc_clk>, <&axi_clock>;
> + clock-names = "core", "axi";
> + bus-width = <8>;
> + marvell,xenon-emmc;
Not documented. If we need to specify the type of slot/card, then we
need to come up with a standard property. This was either already done
or attempted IIRC.
> + marvell,xenon-slotno = <0>;
> + marvell,xenon-phy-type = "emmc 5.1 phy";
> + marvell,xenon-tun-count = <11>;
> + };
> +
> +- For SD/SDIO slot:
> +
> + sdhci@ab0000 {
> + compatible = "marvell,xenon-sdhci";
> + reg = <0xab0000 0x1000>;
> + interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
> + vqmmc-supply = <&sd_regulator>;
> + clocks = <&sdclk>;
> + clock-names = "core";
> + bus-width = <4>;
> + marvell,xenon-tun-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>;
> + marvell,xenon-emmc;
> +
> + marvell,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>;
> +
> + marvell,pad-type = "sd";
> + };
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 1a5c4c30ea24..850a0afb0c8d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -7608,6 +7608,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
> M: Ziji Hu <[email protected]>
> L: [email protected]
> S: Supported
> +F: Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
>
> MATROX FRAMEBUFFER DRIVER
> L: [email protected]
> --
> git-series 0.8.10
Hi Rob,
On 2016/11/10 2:24, Rob Herring wrote:
> On Mon, Oct 31, 2016 at 12:09:54PM +0100, Gregory CLEMENT wrote:
>> From: Ziji Hu <[email protected]>
>>
>> Marvell Xenon SDHC can support eMMC/SD/SDIO.
>> Add Xenon-specific properties.
>> Also add properties for Xenon PHY setting.
>>
>> Signed-off-by: Hu Ziji <[email protected]>
>> Signed-off-by: Gregory CLEMENT <[email protected]>
>> ---
>> Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt | 161 +++++++-
>> MAINTAINERS | 1 +-
>> 2 files changed, 162 insertions(+), 0 deletions(-)
>> create mode 100644 Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
>>
>> diff --git a/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt b/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
>> new file mode 100644
>> index 000000000000..0d2d139494d3
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
>> @@ -0,0 +1,161 @@
>> +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 one of the following
>> + - "marvell,armada-3700-sdhci": For controllers on Armada-3700 SOC.
>> + Must provide a second register area and marvell,pad-type.
>> + - "marvell,xenon-sdhci": For controllers on all the SOCs, other than
>> + Armada-3700.
>
> Need SoC specific compatible strings.
>
Xenon SDHC is a common IP for all Marvell SOCs.
It is difficult to use a single SOC specific compatible to represent Xenon SDHC.
There will be so many SOC compatible strings in list if each specific SOC owns a compatible.
Actually only few SOCs require special properties.
Any suggestion please?
>> +
>> +- clocks:
>> + Array of clocks required for SDHCI.
>> + Requires at least one for Xenon IP core.
>> + Some SOCs require additional clock for AXI bus.
>> +
>> +- clock-names:
>> + Array of names corresponding to clocks property.
>> + The input clock for Xenon IP core should be named as "core".
>> + The optional AXI clock should be named as "axi".
>
> When is AXI clock optional? This should be required for ?? compatible
> strings.
>
It is required on some SOCs.
I will double check if a suitable compatible string can be determined for those SOCs.
>> +
>> +- reg:
>> + * For "marvell,xenon-sdhci", one register area for Xenon IP.
>> +
>> + * For "marvell,armada-3700-sdhci", two register areas.
>> + The first one for Xenon IP register. The second one for the Armada 3700 SOC
>> + PHY PAD Voltage Control register.
>> + Please follow the examples with compatible "marvell,armada-3700-sdhci"
>> + in below.
>> + Please also check property marvell,pad-type in below.
>> +
>> +Optional Properties:
>> +- marvell,xenon-slotno:
>
> Multiple slots should be represented as child nodes IMO. I think some
> other bindings already do this.
>
All the slots are entirely independent.
I prefer to consider it as multiple independent SDHCs placed in a single IP, instead of that a IP contains multiple child slots.
It is unlike the implementation which put multiple slots behind PCIe EP interface. sdhci-pci.c will handle each slot init one by one.
If Xenon SDHC slots are represented as child nodes, there should also be a main entry in Xenon driver to init each child node one by one.
In my very own opinion, it is inconvenient and unnecessary.
>> + Indicate the corresponding bit index of current Xenon SDHC slot in
>> + SDHC System Operation Control Register Bit[7:0].
>> + Set/clear the corresponding bit to enable/disable current Xenon SDHC
>> + slot.
>> + If this property is not provided, Xenon IP should contain only one
>> + slot.
>> +
>> +- marvell,xenon-phy-type:
>> + Xenon support mutilple types of PHYs.
>> + To select eMMC 5.1 PHY, set:
>> + marvell,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:
>> + marvell,xenon-phy-type = "emmc 5.0 phy"
>> + To select SDH PHY, set:
>> + marvell,xenon-phy-type = "sdh phy"
>> + Please note that eMMC PHY is a general PHY for eMMC/SD/SDIO, other than for
>> + eMMC only.
>
> Does this vary per instance on a single SoC? If not, then an SoC
> specific compatible should determine this.
>
> Also, the " phy" part is redundant.
>
Yes. Some SOCs might have multiple Xenon PHY types.
This property is only the name/type of PHY. It doesn't stand for the entire SDHC property.
"emmc 5.1 PHY" doesn't mean that this Xenon SDHC only support eMMC 5.1.
Xenon SDHC with "sdh PHY" can also support eMMC.
>> +
>> +- marvell,xenon-phy-znr:
>> + Set PHY ZNR value.
>> + Only available for eMMC PHY 5.1 and eMMC PHY 5.0.
>> + valid range = [0:0x1F].
>> + ZNR is set as 0xF by default if this property is not provided.
>> +
>> +- marvell,xenon-phy-zpr:
>> + Set PHY ZPR value.
>> + Only available for eMMC PHY 5.1 and eMMC PHY 5.0.
>> + valid range = [0:0x1F].
>> + ZPR is set as 0xF by default if this property is not provided.
>> +
>> +- marvell,xenon-phy-nr-success-tun:
>> + 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.
>> +
>> +- marvell,xenon-phy-tun-step-divider:
>> + Set the divider for calculating TUN_STEP.
>> + Set as 64 by default if this property is not provided.
>> +
>> +- marvell,xenon-phy-slow-mode:
>> + 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.
>> +
>> +- marvell,xenon-mask-conflict-err:
>> + Mask Conflict Error alert on some SOC. Disabled by default.
>> +
>> +- marvell,xenon-tun-count:
>> + Xenon SDHC SOC usually doesn't provide re-tuning counter in
>> + Capabilities Register 3 Bit[11:8].
>> + This property provides the re-tuning counter.
>> + If this property is not set, default re-tuning counter will
>> + be set as 0x9 in driver.
>> +
>> +- marvell,pad-type:
>> + Type of Armada 3700 SOC PHY PAD Voltiage Controller register.
>> + Only valid when "marvell,armada-3700-sdhci" is selected.
>> + 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.
>> + Please follow the examples with compatible "marvell,armada-3700-sdhci"
>> + in below.
>> +
>> +Example:
>> +- For eMMC slot:
>> +
>> + sdhci@aa0000 {
>> + compatible = "marvell,xenon-sdhci";
>> + reg = <0xaa0000 0x1000>;
>> + interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>
>> + clocks = <&emmc_clk>, <&axi_clock>;
>> + clock-names = "core", "axi";
>> + bus-width = <8>;
>> + marvell,xenon-emmc;
>
> Not documented. If we need to specify the type of slot/card, then we
> need to come up with a standard property. This was either already done
> or attempted IIRC.
Sorry to lost this property in above.
I will add it in above.
Thank you.
Best regards,
Hu Ziji
>
>> + marvell,xenon-slotno = <0>;
>> + marvell,xenon-phy-type = "emmc 5.1 phy";
>> + marvell,xenon-tun-count = <11>;
>> + };
>> +
>> +- For SD/SDIO slot:
>> +
>> + sdhci@ab0000 {
>> + compatible = "marvell,xenon-sdhci";
>> + reg = <0xab0000 0x1000>;
>> + interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>
>> + vqmmc-supply = <&sd_regulator>;
>> + clocks = <&sdclk>;
>> + clock-names = "core";
>> + bus-width = <4>;
>> + marvell,xenon-tun-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>;
>> + marvell,xenon-emmc;
>> +
>> + marvell,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>;
>> +
>> + marvell,pad-type = "sd";
>> + };
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 1a5c4c30ea24..850a0afb0c8d 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -7608,6 +7608,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
>> M: Ziji Hu <[email protected]>
>> L: [email protected]
>> S: Supported
>> +F: Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
>>
>> MATROX FRAMEBUFFER DRIVER
>> L: [email protected]
>> --
>> git-series 0.8.10
Hi Rob, Ziji,
On Thu, 10 Nov 2016 19:44:19 +0800 Ziji Hu wrote:
> Hi Rob,
>
> On 2016/11/10 2:24, Rob Herring wrote:
> > On Mon, Oct 31, 2016 at 12:09:54PM +0100, Gregory CLEMENT wrote:
> >> From: Ziji Hu <[email protected]>
> >>
> >> Marvell Xenon SDHC can support eMMC/SD/SDIO.
> >> Add Xenon-specific properties.
> >> Also add properties for Xenon PHY setting.
> >>
> >> Signed-off-by: Hu Ziji <[email protected]>
> >> Signed-off-by: Gregory CLEMENT <[email protected]>
> >> ---
> >> Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt | 161 +++++++-
> >> MAINTAINERS | 1 +-
> >> 2 files changed, 162 insertions(+), 0 deletions(-)
> >> create mode 100644 Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
> >>
> >> diff --git a/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt b/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
> >> new file mode 100644
> >> index 000000000000..0d2d139494d3
> >> --- /dev/null
> >> +++ b/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
> >> @@ -0,0 +1,161 @@
> >> +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 one of the following
> >> + - "marvell,armada-3700-sdhci": For controllers on Armada-3700 SOC.
> >> + Must provide a second register area and marvell,pad-type.
> >> + - "marvell,xenon-sdhci": For controllers on all the SOCs, other than
> >> + Armada-3700.
> >
> > Need SoC specific compatible strings.
> >
>
> Xenon SDHC is a common IP for all Marvell SOCs.
> It is difficult to use a single SOC specific compatible to represent Xenon SDHC.
> There will be so many SOC compatible strings in list if each specific SOC owns a compatible.
> Actually only few SOCs require special properties.
> Any suggestion please?
>
> >> +
> >> +- clocks:
> >> + Array of clocks required for SDHCI.
> >> + Requires at least one for Xenon IP core.
> >> + Some SOCs require additional clock for AXI bus.
> >> +
> >> +- clock-names:
> >> + Array of names corresponding to clocks property.
> >> + The input clock for Xenon IP core should be named as "core".
> >> + The optional AXI clock should be named as "axi".
> >
> > When is AXI clock optional? This should be required for ?? compatible
> > strings.
> >
> It is required on some SOCs.
> I will double check if a suitable compatible string can be determined for those SOCs.
Besides the core clk, berlin SoCs have one AXI clock. Usually, we have two
solutions:
solA: as current patch does, take "marvell,xenon-sdhci" as compatible string
and make the AXI clock property optional. Usually for berlin SoCs, we don't need
special properties.
PS: this solution is also what sdhci-pxav3.c takes
solB: As Rob said, add extra SoC compatible strings, so we'll have
something like:
static const struct of_device_id sdhci_xenon_of_match[] = {
{ .compatible = "marvell,armada-3700-sdhci", },
{ .compatible = "marvell,berlin4ct-sdhci", },
...
{ .compatible = "marvell,berlinxxx-mmc", },
}
then we take care the AXI clk for berlin SoCs in the code.
Which solution do you prefer?
Thanks,
Jisheng
On Fri, 11 Nov 2016 11:22:43 +0800 Jisheng Zhang wrote:
> Hi Rob, Ziji,
>
> On Thu, 10 Nov 2016 19:44:19 +0800 Ziji Hu wrote:
>
> > Hi Rob,
> >
> > On 2016/11/10 2:24, Rob Herring wrote:
> > > On Mon, Oct 31, 2016 at 12:09:54PM +0100, Gregory CLEMENT wrote:
> > >> From: Ziji Hu <[email protected]>
> > >>
> > >> Marvell Xenon SDHC can support eMMC/SD/SDIO.
> > >> Add Xenon-specific properties.
> > >> Also add properties for Xenon PHY setting.
> > >>
> > >> Signed-off-by: Hu Ziji <[email protected]>
> > >> Signed-off-by: Gregory CLEMENT <[email protected]>
> > >> ---
> > >> Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt | 161 +++++++-
> > >> MAINTAINERS | 1 +-
> > >> 2 files changed, 162 insertions(+), 0 deletions(-)
> > >> create mode 100644 Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
> > >>
> > >> diff --git a/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt b/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
> > >> new file mode 100644
> > >> index 000000000000..0d2d139494d3
> > >> --- /dev/null
> > >> +++ b/Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.txt
> > >> @@ -0,0 +1,161 @@
> > >> +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 one of the following
> > >> + - "marvell,armada-3700-sdhci": For controllers on Armada-3700 SOC.
> > >> + Must provide a second register area and marvell,pad-type.
> > >> + - "marvell,xenon-sdhci": For controllers on all the SOCs, other than
> > >> + Armada-3700.
> > >
> > > Need SoC specific compatible strings.
> > >
> >
> > Xenon SDHC is a common IP for all Marvell SOCs.
> > It is difficult to use a single SOC specific compatible to represent Xenon SDHC.
> > There will be so many SOC compatible strings in list if each specific SOC owns a compatible.
> > Actually only few SOCs require special properties.
> > Any suggestion please?
> >
> > >> +
> > >> +- clocks:
> > >> + Array of clocks required for SDHCI.
> > >> + Requires at least one for Xenon IP core.
> > >> + Some SOCs require additional clock for AXI bus.
> > >> +
> > >> +- clock-names:
> > >> + Array of names corresponding to clocks property.
> > >> + The input clock for Xenon IP core should be named as "core".
> > >> + The optional AXI clock should be named as "axi".
> > >
> > > When is AXI clock optional? This should be required for ?? compatible
> > > strings.
> > >
> > It is required on some SOCs.
> > I will double check if a suitable compatible string can be determined for those SOCs.
>
> Besides the core clk, berlin SoCs have one AXI clock. Usually, we have two
> solutions:
>
> solA: as current patch does, take "marvell,xenon-sdhci" as compatible string
> and make the AXI clock property optional. Usually for berlin SoCs, we don't need
> special properties.
Personally, I prefer solA: use the IP name as compatible string. This is IP
specific rather than SoC specific. The HW itself supports two clks
Thanks,
Jisheng
>
> PS: this solution is also what sdhci-pxav3.c takes
>
> solB: As Rob said, add extra SoC compatible strings, so we'll have
> something like:
>
> static const struct of_device_id sdhci_xenon_of_match[] = {
> { .compatible = "marvell,armada-3700-sdhci", },
> { .compatible = "marvell,berlin4ct-sdhci", },
> ...
> { .compatible = "marvell,berlinxxx-mmc", },
> }
>
> then we take care the AXI clk for berlin SoCs in the code.
>
>
> Which solution do you prefer?
>
> Thanks,
> Jisheng
Hi Rob,
On jeu., nov. 10 2016, Ziji Hu <[email protected]> wrote:
[...]
>>> +
>>> +- reg:
>>> + * For "marvell,xenon-sdhci", one register area for Xenon IP.
>>> +
>>> + * For "marvell,armada-3700-sdhci", two register areas.
>>> + The first one for Xenon IP register. The second one for the Armada 3700 SOC
>>> + PHY PAD Voltage Control register.
>>> + Please follow the examples with compatible "marvell,armada-3700-sdhci"
>>> + in below.
>>> + Please also check property marvell,pad-type in below.
>>> +
>>> +Optional Properties:
>>> +- marvell,xenon-slotno:
>>
>> Multiple slots should be represented as child nodes IMO. I think some
>> other bindings already do this.
>>
>
> All the slots are entirely independent.
> I prefer to consider it as multiple independent SDHCs placed in
> a single IP, instead of that a IP contains multiple child slots.
It was indeed what I tried to show in my answer for the 1st version:
http://lists.infradead.org/pipermail/linux-arm-kernel/2016-October/461860.html
Maybe you missed it.
You also mentioned other bindings using child nodes, but for this one
we have one controller with only one set of register with multiple slots
(Atmel is an example). Here each slot have it own set of register.
Actually giving the fact that each slot is controlled by a different set
of register I wonder why the hardware can't also deduce the slot number
from the address register. For me it looks like an hardware bug but we
have to deal with it.
Do you still think we needchild node here?
>
> It is unlike the implementation which put multiple slots behind PCIe EP interface. sdhci-pci.c will handle each slot init one by one.
> If Xenon SDHC slots are represented as child nodes, there should also be a main entry in Xenon driver to init each child node one by one.
> In my very own opinion, it is inconvenient and unnecessary.
Gregory
--
Gregory Clement, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com
Hi Ulf,
On lun., oct. 31 2016, Gregory CLEMENT <[email protected]> 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
On 22 November 2016 at 18:23, Gregory CLEMENT
<[email protected]> wrote:
> Hi Rob,
>
> On jeu., nov. 10 2016, Ziji Hu <[email protected]> wrote:
>
> [...]
>
>>>> +
>>>> +- reg:
>>>> + * For "marvell,xenon-sdhci", one register area for Xenon IP.
>>>> +
>>>> + * For "marvell,armada-3700-sdhci", two register areas.
>>>> + The first one for Xenon IP register. The second one for the Armada 3700 SOC
>>>> + PHY PAD Voltage Control register.
>>>> + Please follow the examples with compatible "marvell,armada-3700-sdhci"
>>>> + in below.
>>>> + Please also check property marvell,pad-type in below.
>>>> +
>>>> +Optional Properties:
>>>> +- marvell,xenon-slotno:
>>>
>>> Multiple slots should be represented as child nodes IMO. I think some
>>> other bindings already do this.
>>>
>>
>> All the slots are entirely independent.
>> I prefer to consider it as multiple independent SDHCs placed in
>> a single IP, instead of that a IP contains multiple child slots.
>
> It was indeed what I tried to show in my answer for the 1st version:
> http://lists.infradead.org/pipermail/linux-arm-kernel/2016-October/461860.html
>
> Maybe you missed it.
>
> You also mentioned other bindings using child nodes, but for this one
> we have one controller with only one set of register with multiple slots
> (Atmel is an example). Here each slot have it own set of register.
>
> Actually giving the fact that each slot is controlled by a different set
> of register I wonder why the hardware can't also deduce the slot number
> from the address register. For me it looks like an hardware bug but we
> have to deal with it.
>
> Do you still think we needchild node here?
Using child-nodes for slots like what's done in the atmel case, is
currently broken. I would recommend to avoid using child-nodes for
slots, if possible.
To give you some more background, currently the mmc core treats child
nodes as embedded non-removable cards or SDIO funcs. However, we can
change to make child-nodes also allowed to describe slots, but it
requires a specific compatible for "slots" and of course then we also
need to update the DT parsing of the child-nodes in the mmc core.
Documentation/devicetree/bindings/mmc/mmc.txt
Documentation/devicetree/bindings/mmc/mmc-card.txt
>
>>
>> It is unlike the implementation which put multiple slots behind PCIe EP interface. sdhci-pci.c will handle each slot init one by one.
>> If Xenon SDHC slots are represented as child nodes, there should also be a main entry in Xenon driver to init each child node one by one.
>> In my very own opinion, it is inconvenient and unnecessary.
>
Kind regards
Uffe
On Thursday, November 24, 2016 10:05:45 AM CET Ulf Hansson wrote:
> > You also mentioned other bindings using child nodes, but for this one
> > we have one controller with only one set of register with multiple slots
> > (Atmel is an example). Here each slot have it own set of register.
> >
> > Actually giving the fact that each slot is controlled by a different set
> > of register I wonder why the hardware can't also deduce the slot number
> > from the address register. For me it looks like an hardware bug but we
> > have to deal with it.
> >
> > Do you still think we needchild node here?
>
> Using child-nodes for slots like what's done in the atmel case, is
> currently broken. I would recommend to avoid using child-nodes for
> slots, if possible.
>
> To give you some more background, currently the mmc core treats child
> nodes as embedded non-removable cards or SDIO funcs. However, we can
> change to make child-nodes also allowed to describe slots, but it
> requires a specific compatible for "slots" and of course then we also
> need to update the DT parsing of the child-nodes in the mmc core.
>
> Documentation/devicetree/bindings/mmc/mmc.txt
> Documentation/devicetree/bindings/mmc/mmc-card.txt
I don't see anything wrong with having child nodes for the slots
even with the current binding, under one condition:
The mmc.txt binding above must refer only to the child node, while
the parent node conceptually becomes a plain bus or MFD that
happens to encapsulate multiple MMC host controllers, and possibly
provides some shared registers to them.
Arnd
Hi Arnd,
On jeu., nov. 24 2016, Arnd Bergmann <[email protected]> wrote:
> On Thursday, November 24, 2016 10:05:45 AM CET Ulf Hansson wrote:
>> > You also mentioned other bindings using child nodes, but for this one
>> > we have one controller with only one set of register with multiple slots
>> > (Atmel is an example). Here each slot have it own set of register.
>> >
>> > Actually giving the fact that each slot is controlled by a different set
>> > of register I wonder why the hardware can't also deduce the slot number
>> > from the address register. For me it looks like an hardware bug but we
>> > have to deal with it.
>> >
>> > Do you still think we needchild node here?
>>
>> Using child-nodes for slots like what's done in the atmel case, is
>> currently broken. I would recommend to avoid using child-nodes for
>> slots, if possible.
>>
>> To give you some more background, currently the mmc core treats child
>> nodes as embedded non-removable cards or SDIO funcs. However, we can
>> change to make child-nodes also allowed to describe slots, but it
>> requires a specific compatible for "slots" and of course then we also
>> need to update the DT parsing of the child-nodes in the mmc core.
>>
>> Documentation/devicetree/bindings/mmc/mmc.txt
>> Documentation/devicetree/bindings/mmc/mmc-card.txt
>
> I don't see anything wrong with having child nodes for the slots
> even with the current binding, under one condition:
>
> The mmc.txt binding above must refer only to the child node, while
> the parent node conceptually becomes a plain bus or MFD that
> happens to encapsulate multiple MMC host controllers, and possibly
> provides some shared registers to them.
I don't have an option for mmc in general, but using child node do not
fit at all the xenon controller.
For this controller each slot has its own set of register, so there is
no common ressource to share so no advantage to use it. Using child node
in our case will just make the code more complex for no benefit.
Gregory
>
> Arnd
--
Gregory Clement, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com
On Thursday, November 24, 2016 10:22:31 AM CET Gregory CLEMENT wrote:
>
> I don't have an option for mmc in general, but using child node do not
> fit at all the xenon controller.
>
> For this controller each slot has its own set of register, so there is
> no common ressource to share so no advantage to use it. Using child node
> in our case will just make the code more complex for no benefit.
If every slot has its own registers, what is it that makes up the
'controller'? It sounds to me that you just have to adjust the terminology
and talk about multiple controllers then, with one slot per controller.
Arnd
Hello,
On Thu, 24 Nov 2016 10:44:48 +0100, Gregory CLEMENT wrote:
> "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."
I think this wording is still very confusing, and continues to cause
confusion.
We should just state that each Xenon controller supports a single slot,
and that's it.
The text still says "a single Xenon IP can support multiple slots",
which continues to cause confusion.
Best regards,
Thomas
--
Thomas Petazzoni, CTO, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
Hi Gregory,
2016-11-24 10:44 GMT+01:00 Gregory CLEMENT <[email protected]>:
> Hi Arnd,
>
> On jeu., nov. 24 2016, Arnd Bergmann <[email protected]> wrote:
>
>> On Thursday, November 24, 2016 10:22:31 AM CET Gregory CLEMENT wrote:
>>>
>>> I don't have an option for mmc in general, but using child node do not
>>> fit at all the xenon controller.
>>>
>>> For this controller each slot has its own set of register, so there is
>>> no common ressource to share so no advantage to use it. Using child node
>>> in our case will just make the code more complex for no benefit.
>>
>> If every slot has its own registers, what is it that makes up the
>> 'controller'? It sounds to me that you just have to adjust the terminology
>> and talk about multiple controllers then, with one slot per controller.
>>
>
> I agree and actually there were some words about in at the begining of
> the binding:
>
> "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."
>
> All the confusion came from the fact that we still need to identify a
> slot ID. For an obscure reason the hardware can't guess the slot ID from
> the address register."
>
How about to avoid confusion, by simply renaming this number to
port-id/xenon-id or anything else but slot? I guess this may allow to
avoid some misunderstandings.
Best regards,
Marcin
On Monday, October 31, 2016 12:09:56 PM CET Gregory CLEMENT wrote:
> From: Ziji Hu <[email protected]>
>
> 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 <[email protected]>
> Signed-off-by: Gregory CLEMENT <[email protected]>
>
Please explain in the changelog why this is not a generic
phy driver (or three of them).
Arnd
On Thursday, November 24, 2016 10:48:58 AM CET Thomas Petazzoni wrote:
> Hello,
>
> On Thu, 24 Nov 2016 10:44:48 +0100, Gregory CLEMENT wrote:
>
> > "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."
>
> I think this wording is still very confusing, and continues to cause
> confusion.
>
> We should just state that each Xenon controller supports a single slot,
> and that's it.
>
> The text still says "a single Xenon IP can support multiple slots",
> which continues to cause confusion.
Agreed. Ideally we'd find out why exactly the slot number must
be used for accessing some of the registers to have a better
explanation to put in there, aside from stating that only one
slot is supported but the number must be set.
Could it be that this is some form of pinmuxing, i.e. that each
controller could in theory be used for any of the slots but you
have to pick one of them?
Arnd
Hello,
On Thu, 24 Nov 2016 10:49:23 +0100, Marcin Wojtas wrote:
> How about to avoid confusion, by simply renaming this number to
> port-id/xenon-id or anything else but slot? I guess this may allow to
> avoid some misunderstandings.
Agreed.
Thomas
--
Thomas Petazzoni, CTO, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
Hi all,
On 2016/11/24 18:10, Thomas Petazzoni wrote:
> Hello,
>
> On Thu, 24 Nov 2016 10:49:23 +0100, Marcin Wojtas wrote:
>
>> How about to avoid confusion, by simply renaming this number to
>> port-id/xenon-id or anything else but slot? I guess this may allow to
>> avoid some misunderstandings.
>
We borrow the term "slot" from PCIe interface from SD spec.
According to Appendix C in SD spec 3.0, slot means an independent set of register from the view of SW.
I can avoid using "slot" and replace "slot index" with "sdhc-id".
Thanks for the suggestions.
Thank you.
Best regards,
Hu Ziji
> Agreed.
>
> Thomas
>
On 31 October 2016 at 12:09, Gregory CLEMENT
<[email protected]> wrote:
> From: Ziji Hu <[email protected]>
>
> 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 <[email protected]>
> Signed-off-by: Gregory CLEMENT <[email protected]>
> ---
> MAINTAINERS | 1 +-
> drivers/mmc/host/Kconfig | 9 +-
> drivers/mmc/host/Makefile | 3 +-
> drivers/mmc/host/sdhci-xenon.c | 594 ++++++++++++++++++++++++++++++++++-
> drivers/mmc/host/sdhci-xenon.h | 142 ++++++++-
> 5 files changed, 749 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 850a0afb0c8d..d92f4175574b 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -7608,6 +7608,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER
> M: Ziji Hu <[email protected]>
> L: [email protected]
> S: Supported
> +F: drivers/mmc/host/sdhci-xenon.*
> F: Documentation/devicetree/bindings/mmc/marvell,xenon-sdhci.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..3ea059f2aaab
> --- /dev/null
> +++ b/drivers/mmc/host/sdhci-xenon.c
> @@ -0,0 +1,594 @@
> +/*
> + * Driver for Marvell SOC Platform Group Xenon SDHC as a platform device
> + *
> + * Copyright (C) 2016 Marvell, All Rights Reserved.
> + *
> + * Author: Hu Ziji <[email protected]>
> + * 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 <[email protected]>
> + * 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);
> +
> + /* Disable 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-retuning 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_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;
> + u32 reg;
> +
> + /*
> + * 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;
> +
> + reg = sdhci_readw(host, SDHCI_HOST_CONTROL2);
> + reg &= ~SDHCI_CTRL_PRESET_VAL_ENABLE;
> + sdhci_writew(host, reg, SDHCI_HOST_CONTROL2);
> + } 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;
This wrapper function seems unnessarry. It only adds a dev_err(), so
then might as well do that in __emmc_signal_voltage_switch().
> +}
> +
> +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 that's the case then that is wrong in the generic sdhci code.
What's the reason why it can't be fixed there instead of having this
workaround?
> + * 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->emmc_slot)
> + 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);
This looks weird. I assume this can be done as a part of the regular
tuning seqeunce!?
> +
> + slot_idx = priv->slot_idx;
> + if (!mmc_card_sdio(card)) {
> + /* Clear SDIO Card Inserted indication */
Why do you need this?
If you need to reset this, I think it's better to do it from
->set_ios() at MMC_POWER_OFF.
> + 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;
Most of this can be specified as DT configurations. Please use that instead.
More importantly, please don't use the ->init_card() ops to assign
host caps. If not DT, please do it from ->probe().
> + }
> + } else {
> + /*
> + * 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);
So this makes sence to have in the ->init_card() ops. The rest above, not.
> + }
> +}
> +
> +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:
> + * emmc: explicitly indicate whether this slot is for eMMC
> + * slotno: the index of slot. Refer to SDHC_SYS_CFG_INFO register
> + * tun-count: the interval between re-tuning
> + * PHY type: "sdhc phy", "emmc phy 5.0" or "emmc phy 5.1"
> + */
> + if (of_property_read_bool(np, "marvell,xenon-emmc"))
> + priv->emmc_slot = true;
So, you need this because of the eMMC voltage switch behaviour, right?
Then I would rather like to describe this a generic DT bindings for
the eMMC voltage level support. There have acutally been some earlier
discussions for this, but we haven't yet made some changes.
I think what is missing is a mmc-ddr-3_3v DT binding, which when set,
allows the host driver to accept I/O voltage switches to 3.3V. If not
supported the ->start_signal_voltage_switch() ops may return -EINVAL.
This would inform the mmc core to move on to the next supported
voltage level. There might be some minor additional changes to the mmc
card initialization sequence, but those should be simple.
I can help out to look into this, unless you want to do it yourself of course!?
> + else
> + priv->emmc_slot = false;
> +
> + if (!of_property_read_u32(np, "marvell,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, "marvell,xenon-tun-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;
> + }
To make the code a bit easier...
Maybe set "priv->tuning_count = DEF_TUNING_COUNT" before the "if", and
instead have the of_property_read_u32() to update the value when set.
> +
> + if (of_property_read_bool(np, "marvell,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;
What are these used for?
> +
> + 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);
Check error code.
> + pltfm_host->clk = clk;
Why not assign pltfm_host->clk immedately when doing devm_clk_get(),
that would make this a bit cleaner, right?
> +
> + /*
> + * 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;
> + }
Same comments as for the above core clock.
> +
> + 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,xenon-sdhci",},
> + { .compatible = "marvell,armada-3700-sdhci",},
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, sdhci_xenon_dt_ids);
> +
> +static struct platform_driver sdhci_xenon_driver = {
> + .driver = {
> + .name = "xenon-sdhci",
> + .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 <[email protected]>");
> +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..4601d0a4b22f
> --- /dev/null
> +++ b/drivers/mmc/host/sdhci-xenon.h
I don't think you need a specific header for this, let's instead just
put everthing in the c-file.
> @@ -0,0 +1,142 @@
> +/*
> + * Copyright (C) 2016 Marvell, All Rights Reserved.
> + *
> + * Author: Hu Ziji <[email protected]>
> + * 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
For all defines above:
All these defines needs some *SDHCI* prefix. Can you please update that.
> +
> +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;
These two are not used. Please remove.
> + unsigned char tuning_count;
> + unsigned int clock;
"clock" isn't used, please remove.
> + struct clk *axi_clk;
> +
> + /* Slot idx */
> + u8 slot_idx;
> + /* Whether this slot is for eMMC */
> + bool emmc_slot;
> +
> + /*
> + * When initializing card, Xenon has to determine card type and
> + * adjust Sampling Fixed delay for the speed mode in which
> + * DLL tuning is not support.
> + * 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.
> + *
> + * It is only valid during initialization after it is updated in
> + * xenon_init_card().
> + * Do not access this variable in normal transfers after
> + * initialization completes.
> + */
> + struct mmc_card *card_candidate;
Not activley used in this change, please remove and let's discuss it
in the next step.
> +};
> +
> +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
Kind regards
Uffe
Hi Arnd,
On 2016/11/24 17:56, Arnd Bergmann wrote:
> On Monday, October 31, 2016 12:09:56 PM CET Gregory CLEMENT wrote:
>> From: Ziji Hu <[email protected]>
>>
>> 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 <[email protected]>
>> Signed-off-by: Gregory CLEMENT <[email protected]>
>>
>
> Please explain in the changelog why this is not a generic
> phy driver (or three of them).
>
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
> Arnd
>
On Thursday, November 24, 2016 6:57:18 PM CET Ziji Hu wrote:
> >
> > Please explain in the changelog why this is not a generic
> > phy driver (or three of them).
> >
> 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.
>
Ok, that makes sense, just put the same text in the changelog comment.
Arnd
On 31 October 2016 at 12:09, Gregory CLEMENT
<[email protected]> wrote:
> From: Ziji Hu <[email protected]>
>
> 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 <[email protected]>
> Signed-off-by: Gregory CLEMENT <[email protected]>
> ---
> MAINTAINERS | 2 +-
> drivers/mmc/host/Makefile | 2 +-
> drivers/mmc/host/sdhci-xenon-phy.c | 1181 +++++++++++++++++++++++++++++-
> 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, 1361 insertions(+), 2 deletions(-)
> create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c
> create mode 100644 drivers/mmc/host/sdhci-xenon-phy.h
Can you please consider to split this up somehow!? It would make it
easier to review...
Anyway, allow me to provide some initial feedback, particularly around
those things that Adrian and you requested for my input.
[...]
>
> +
> +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);
Why do you read the ext csd here?
> +
> + 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;
No thanks! MMC/SD/SDIO protocol code belongs in the core.
> +}
> +
> +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;
No thanks! MMC/SD/SDIO protocol code belongs in the core.
> +}
> +
[...]
> +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;
> + }
> + /*
> + * Skip temp stages from HS400 t0 HS200:
> + * from 200MHz to 52MHz in HS400
> + * from HS400 to HS DDR in 52MHz
> + * from HS DDR to HS in 52MHz
> + * from HS to HS200 in 52MHz
> + */
> + if (((priv->timing == MMC_TIMING_MMC_HS400) &&
> + ((host->clock == MMC_HIGH_52_MAX_DTR) ||
> + (ios->timing == MMC_TIMING_MMC_DDR52))) ||
> + ((priv->timing == MMC_TIMING_MMC_DDR52) &&
> + (ios->timing == MMC_TIMING_MMC_HS)) ||
> + ((ios->timing == MMC_TIMING_MMC_HS200) &&
> + (ios->clock == MMC_HIGH_52_MAX_DTR))) {
> + 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;
> +
> + if (mmc->card)
> + card = mmc->card;
> + else
> + /*
> + * Only valid during initialization
> + * before mmc->card is set
> + */
> + card = priv->card_candidate;
> + if (unlikely(!card)) {
> + dev_warn(mmc_dev(mmc), "card is not present\n");
> + return -EINVAL;
> + }
That your host need to hold a copy of the card pointer, tells me that
something is not really correct.
I might be wrong, if this turns out to be a special case, but I doubt
it. Although, if it *is* a special such case, we shall most likely try
to extend the the mmc core layer instead of adding all these hacks in
your host driver.
[...]
Another suggestion of a general improvement; could you perhaps try to
add some brief information about what goes on in function headers.
Perhaps that could help to more easily understand things.
Kind regards
Uffe
Hi Ulf,
On 2016/11/24 18:43, Ulf Hansson wrote:
> On 31 October 2016 at 12:09, Gregory CLEMENT
> <[email protected]> wrote:
>> From: Ziji Hu <[email protected]>
>>
<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;
>
> This wrapper function seems unnessarry. It only adds a dev_err(), so
> then might as well do that in __emmc_signal_voltage_switch().
>
Sure. Will merge it back to __emmc_signal_voltage_switch().
>> +}
>> +
>> +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 that's the case then that is wrong in the generic sdhci code.
> What's the reason why it can't be fixed there instead of having this
> workaround?
>
In my very own opinion, SD Spec doesn't specify whether SDCLK should be
enabled or not during power setting.
Enabling SDCLK might be a special condition only required by our SDHC.
I try to avoid breaking other vendors' SDHC functionality
if their SDHCs require SDCLK disabled.
Thus I prefer to keep it inside our SDHC driver.
>> + * 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->emmc_slot)
>> + 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);
>
> This looks weird. I assume this can be done as a part of the regular
> tuning seqeunce!?
>
It is our SDHC specific preparation prior to tuning, rather than a
standard step in spec.
Thus I leave it inside our driver.
>> +
>> + slot_idx = priv->slot_idx;
>> + if (!mmc_card_sdio(card)) {
>> + /* Clear SDIO Card Inserted indication */
>
> Why do you need this?
>
> If you need to reset this, I think it's better to do it from
> ->set_ios() at MMC_POWER_OFF.
>
This field indicates SDIO card and controls async interrupt feature
of SDIO in our SDHC.
This async interrupt feature is enabled when SDIO card is inserted.
It should be disabled if SD card is inserted instead.
>> + 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;
>
> Most of this can be specified as DT configurations. Please use that instead.
>
> More importantly, please don't use the ->init_card() ops to assign
> host caps. If not DT, please do it from ->probe().
>
Sure. Will try to use DT instead.
>> + }
>> + } else {
>> + /*
>> + * 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);
>
> So this makes sence to have in the ->init_card() ops. The rest above, not.
>
>> + }
>> +}
>> +
>> +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:
>> + * emmc: explicitly indicate whether this slot is for eMMC
>> + * slotno: the index of slot. Refer to SDHC_SYS_CFG_INFO register
>> + * tun-count: the interval between re-tuning
>> + * PHY type: "sdhc phy", "emmc phy 5.0" or "emmc phy 5.1"
>> + */
>> + if (of_property_read_bool(np, "marvell,xenon-emmc"))
>> + priv->emmc_slot = true;
>
> So, you need this because of the eMMC voltage switch behaviour, right?
>
> Then I would rather like to describe this a generic DT bindings for
> the eMMC voltage level support. There have acutally been some earlier
> discussions for this, but we haven't yet made some changes.
>
> I think what is missing is a mmc-ddr-3_3v DT binding, which when set,
> allows the host driver to accept I/O voltage switches to 3.3V. If not
> supported the ->start_signal_voltage_switch() ops may return -EINVAL.
> This would inform the mmc core to move on to the next supported
> voltage level. There might be some minor additional changes to the mmc
> card initialization sequence, but those should be simple.
>
> I can help out to look into this, unless you want to do it yourself of course!?
>
Yes. One of the reasons is to provide eMMC specific voltage setting.
But in my very own opinion, it should be irrelevant to voltage level.
The eMMC voltage setting on our SDHC is different from SD/SDIO voltage switch.
It will become more complex with different SOC implementation details.
Unfortunately, MMC driver cannot determine the card type yet when eMMC voltage
setting should be executed.
Thus an flag is required here to tell driver to execute eMMC voltage setting.
Besides, additional eMMC specific settings might be implemented in future, besides
voltage setting. Most of them should be completed before MMC driver recognizes the
card type. Thus I have to keep this flag to indicate current SDHC is for eMMC.
>> + else
>> + priv->emmc_slot = false;
>> +
>> + if (!of_property_read_u32(np, "marvell,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, "marvell,xenon-tun-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;
>> + }
>
> To make the code a bit easier...
>
> Maybe set "priv->tuning_count = DEF_TUNING_COUNT" before the "if", and
> instead have the of_property_read_u32() to update the value when set.
>
Yes. You are correct.
>> +
>> + if (of_property_read_bool(np, "marvell,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;
>
> What are these used for?
>
During card initialization, our SDHC PHY setting depends on current
timing and SDCLK frequency.
priv->timing and priv->clock will be used in PHY setting later.
It can be considered as a clean-up.
Anyway, it does look ugly. I will improve them after our PHY setting
passes your review.
>> +
>> + 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);
>
> Check error code.
>
>> + pltfm_host->clk = clk;
>
> Why not assign pltfm_host->clk immedately when doing devm_clk_get(),
> that would make this a bit cleaner, right?
>
Yes, of course.
>> +
>> + /*
>> + * 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;
>> + }
>
> Same comments as for the above core clock.
>
OK.
>> +
>> + 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,xenon-sdhci",},
>> + { .compatible = "marvell,armada-3700-sdhci",},
>> + {}
>> +};
>> +MODULE_DEVICE_TABLE(of, sdhci_xenon_dt_ids);
>> +
>> +static struct platform_driver sdhci_xenon_driver = {
>> + .driver = {
>> + .name = "xenon-sdhci",
>> + .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 <[email protected]>");
>> +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..4601d0a4b22f
>> --- /dev/null
>> +++ b/drivers/mmc/host/sdhci-xenon.h
>
> I don't think you need a specific header for this, let's instead just
> put everthing in the c-file.
>
Some definitions inside this file will also be referred in PHY setting in
sdhci-xenon-phy.c.
Thus I put all the definitions together into a header file.
>> @@ -0,0 +1,142 @@
>> +/*
>> + * Copyright (C) 2016 Marvell, All Rights Reserved.
>> + *
>> + * Author: Hu Ziji <[email protected]>
>> + * 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
>
> For all defines above:
>
> All these defines needs some *SDHCI* prefix. Can you please update that.
Sure. Will add prefix for all of them.
>
>> +
>> +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;
>
> These two are not used. Please remove.
>
The above two variables will be used in PHY setting
in sdhci-xenon-phy.c.
Could you please help review them in next patch?
>> + unsigned char tuning_count;
>> + unsigned int clock;
>
> "clock" isn't used, please remove.
>
It will be accessed in PHY setting in sdhci-xenon-phy.c.
Could you please help review it in next patch?
>> + struct clk *axi_clk;
>> +
>> + /* Slot idx */
>> + u8 slot_idx;
>> + /* Whether this slot is for eMMC */
>> + bool emmc_slot;
>> +
>> + /*
>> + * When initializing card, Xenon has to determine card type and
>> + * adjust Sampling Fixed delay for the speed mode in which
>> + * DLL tuning is not support.
>> + * 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.
>> + *
>> + * It is only valid during initialization after it is updated in
>> + * xenon_init_card().
>> + * Do not access this variable in normal transfers after
>> + * initialization completes.
>> + */
>> + struct mmc_card *card_candidate;
>
> Not activley used in this change, please remove and let's discuss it
> in the next step.
>
This varible will be accessed in PHY setting in sdhci-xenon-phy.c.
I would like to discuss about it in PHY file. Could you please help
review it in next patch?
Thank you.
Best regards,
Hu Ziji
>> +};
>> +
>> +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
>
> Kind regards
> Uffe
>
On 24 November 2016 at 13:41, Ziji Hu <[email protected]> wrote:
> Hi Ulf,
>
> On 2016/11/24 18:43, Ulf Hansson wrote:
>> On 31 October 2016 at 12:09, Gregory CLEMENT
>> <[email protected]> wrote:
>>> From: Ziji Hu <[email protected]>
>>>
> <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;
>>
>> This wrapper function seems unnessarry. It only adds a dev_err(), so
>> then might as well do that in __emmc_signal_voltage_switch().
>>
> Sure. Will merge it back to __emmc_signal_voltage_switch().
>
>>> +}
>>> +
>>> +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 that's the case then that is wrong in the generic sdhci code.
>> What's the reason why it can't be fixed there instead of having this
>> workaround?
>>
> In my very own opinion, SD Spec doesn't specify whether SDCLK should be
> enabled or not during power setting.
> Enabling SDCLK might be a special condition only required by our SDHC.
> I try to avoid breaking other vendors' SDHC functionality
> if their SDHCs require SDCLK disabled.
> Thus I prefer to keep it inside our SDHC driver.
I let Adrian comment on this.
For sure we should avoid breaking other sdhci variant, but on the
other hand *if* the generic code is wrong we should fix it!
>
>>> + * 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->emmc_slot)
>>> + 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);
>>
>> This looks weird. I assume this can be done as a part of the regular
>> tuning seqeunce!?
>>
> It is our SDHC specific preparation prior to tuning, rather than a
> standard step in spec.
> Thus I leave it inside our driver.
My point is that this isn't the purpose of ->init_card(). thus you are
abusing it.
Try to make it work in another way, please. I think you can.
>
>>> +
>>> + slot_idx = priv->slot_idx;
>>> + if (!mmc_card_sdio(card)) {
>>> + /* Clear SDIO Card Inserted indication */
>>
>> Why do you need this?
>>
>> If you need to reset this, I think it's better to do it from
>> ->set_ios() at MMC_POWER_OFF.
>>
> This field indicates SDIO card and controls async interrupt feature
> of SDIO in our SDHC.
> This async interrupt feature is enabled when SDIO card is inserted.
> It should be disabled if SD card is inserted instead.
What do you mean by SDIO async interupts? Are you talking about SDIO
irqs on DAT1 line?
Those is supposed to be enabled when someone explicitly requests them,
not when the card is inserted.
In other words when an SDIO func driver have called sdio_claim_irq().
Moreover, we have ->enable_sdio_irq() ops that deals with this.
[...]
>>> +
>>> + /*
>>> + * Xenon Specific property:
>>> + * emmc: explicitly indicate whether this slot is for eMMC
>>> + * slotno: the index of slot. Refer to SDHC_SYS_CFG_INFO register
>>> + * tun-count: the interval between re-tuning
>>> + * PHY type: "sdhc phy", "emmc phy 5.0" or "emmc phy 5.1"
>>> + */
>>> + if (of_property_read_bool(np, "marvell,xenon-emmc"))
>>> + priv->emmc_slot = true;
>>
>> So, you need this because of the eMMC voltage switch behaviour, right?
>>
>> Then I would rather like to describe this a generic DT bindings for
>> the eMMC voltage level support. There have acutally been some earlier
>> discussions for this, but we haven't yet made some changes.
>>
>> I think what is missing is a mmc-ddr-3_3v DT binding, which when set,
>> allows the host driver to accept I/O voltage switches to 3.3V. If not
>> supported the ->start_signal_voltage_switch() ops may return -EINVAL.
>> This would inform the mmc core to move on to the next supported
>> voltage level. There might be some minor additional changes to the mmc
>> card initialization sequence, but those should be simple.
>>
>> I can help out to look into this, unless you want to do it yourself of course!?
>>
> Yes. One of the reasons is to provide eMMC specific voltage setting.
> But in my very own opinion, it should be irrelevant to voltage level.
> The eMMC voltage setting on our SDHC is different from SD/SDIO voltage switch.
> It will become more complex with different SOC implementation details.
Got it. Although I think we can cope with that fine just by using the
different SD/eMMC speed modes settings defined in DT (or from the
SDHCI caps register)
> Unfortunately, MMC driver cannot determine the card type yet when eMMC voltage
> setting should be executed.
> Thus an flag is required here to tell driver to execute eMMC voltage setting.
>
> Besides, additional eMMC specific settings might be implemented in future, besides
> voltage setting. Most of them should be completed before MMC driver recognizes the
> card type. Thus I have to keep this flag to indicate current SDHC is for eMMC.
I doubt you will need a generic "eMMC" flag, but let's see when we go forward.
Currently it's clear you don't need such a flag, so I will submit a
change adding a DT binding for "mmc-ddr-3_3v" then we can take it from
there, to see if it suits your needs.
[...]
Kind regards
Uffe
Hi Ulf,
Thanks a lot for the review.
On 2016/11/24 19:37, Ulf Hansson wrote:
> On 31 October 2016 at 12:09, Gregory CLEMENT
> <[email protected]> wrote:
>> From: Ziji Hu <[email protected]>
>>
>> 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 <[email protected]>
>> Signed-off-by: Gregory CLEMENT <[email protected]>
>> ---
>> MAINTAINERS | 2 +-
>> drivers/mmc/host/Makefile | 2 +-
>> drivers/mmc/host/sdhci-xenon-phy.c | 1181 +++++++++++++++++++++++++++++-
>> 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, 1361 insertions(+), 2 deletions(-)
>> create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c
>> create mode 100644 drivers/mmc/host/sdhci-xenon-phy.h
>
> Can you please consider to split this up somehow!? It would make it
> easier to review...
>
Sure. I will try to split them into smaller pieces.
> Anyway, allow me to provide some initial feedback, particularly around
> those things that Adrian and you requested for my input.
>
> [...]
>
>>
>> +
>> +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);
>
> Why do you read the ext csd here?
>
I would like to simply introduce the PHY setting of our SDHC.
The target of the PHY setting is to achieve a perfect sampling
point for transfers, during card initialization.
For HS200/HS400/SDR104 whose SDCLK is more than 50MHz, SDHC HW
will search for this sampling point with DLL's help.
For other speed mode whose SDLCK is less than or equals to 50MHz,
SW has to scan the PHY delay line to find out this perfect sampling
point. Our driver sends a command to verify a sampling point
in current environment.
As result, our SDHC driver has to implement the functionality to
send commands and check the results, in host layer.
If directly calling mmc_wait_for_cmd() is improper, could you please
give us some suggestions?
For eMMC, CMD8 is used to test current sampling point set in PHY.
>> +
>> + 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;
>
> No thanks! MMC/SD/SDIO protocol code belongs in the core.
>
For SDIO, SD_IO_RW_DIRECT command is sent to test current sampling point
in PHY.
Please help provide some suggestion to implement the command transfer.
>> +}
>> +
>> +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;
>
> No thanks! MMC/SD/SDIO protocol code belongs in the core.
>
>> +}
>> +
>
> [...]
>
>> +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;
>> + }
>> + /*
>> + * Skip temp stages from HS400 t0 HS200:
>> + * from 200MHz to 52MHz in HS400
>> + * from HS400 to HS DDR in 52MHz
>> + * from HS DDR to HS in 52MHz
>> + * from HS to HS200 in 52MHz
>> + */
>> + if (((priv->timing == MMC_TIMING_MMC_HS400) &&
>> + ((host->clock == MMC_HIGH_52_MAX_DTR) ||
>> + (ios->timing == MMC_TIMING_MMC_DDR52))) ||
>> + ((priv->timing == MMC_TIMING_MMC_DDR52) &&
>> + (ios->timing == MMC_TIMING_MMC_HS)) ||
>> + ((ios->timing == MMC_TIMING_MMC_HS200) &&
>> + (ios->clock == MMC_HIGH_52_MAX_DTR))) {
>> + 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;
>> +
>> + if (mmc->card)
>> + card = mmc->card;
>> + else
>> + /*
>> + * Only valid during initialization
>> + * before mmc->card is set
>> + */
>> + card = priv->card_candidate;
>> + if (unlikely(!card)) {
>> + dev_warn(mmc_dev(mmc), "card is not present\n");
>> + return -EINVAL;
>> + }
>
> That your host need to hold a copy of the card pointer, tells me that
> something is not really correct.
>
> I might be wrong, if this turns out to be a special case, but I doubt
> it. Although, if it *is* a special such case, we shall most likely try
> to extend the the mmc core layer instead of adding all these hacks in
> your host driver.
>
This card pointer copies the temporary structure mmc_card
used in mmc_init_card(), mmc_sd_init_card() and mmc_sdio_init_card().
Since we call mmc_wait_for_cmd() to send test commands, we need a copy
of that temporary mmc_card here in our host driver.
During PHY setting in card initialization, mmc_host->card is not updated
yet with that temporary mmc_card. Thus we are not able to directly use
mmc_host->card. Instead, this card pointer is introduced to enable
mmc_wait_for_cmd().
If we can improve our host driver to send test commands without mmc_card,
this card pointer can be removed.
Could you please share your opinion please?
> [...]
>
> Another suggestion of a general improvement; could you perhaps try to
> add some brief information about what goes on in function headers.
> Perhaps that could help to more easily understand things.
>
Sorry about any inconvenience. Most of the functions here are our host specific.
It is really difficult to understand them without proper comment.
I will add more information.
Thank you.
Best regards,
Hu Ziji
> Kind regards
> Uffe
>
[...]
>>
>>>
>>> +
>>> +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);
>>
>> Why do you read the ext csd here?
>>
> I would like to simply introduce the PHY setting of our SDHC.
> The target of the PHY setting is to achieve a perfect sampling
> point for transfers, during card initialization.
Okay, so the phy is involved when running the tuning sequence.
>
> For HS200/HS400/SDR104 whose SDCLK is more than 50MHz, SDHC HW
> will search for this sampling point with DLL's help.
Apologize for my ignorance, but what is a "DLL" in this case?
>
> For other speed mode whose SDLCK is less than or equals to 50MHz,
> SW has to scan the PHY delay line to find out this perfect sampling
> point. Our driver sends a command to verify a sampling point
> in current environment.
Ahh, okay! I guess the important part here is to not only send a
command, but also to make sure data becomes transferred on the DAT
lines, as to confirm your tuning sequence!?
In cases of HS200/HS400/SDR104 you should be able to use the
mmc_send_tuning() API, don't you think?
For the other cases (lower speed modes) which cards doesn't support
the tuning command, perhaps you can just assume the PHY scan succeeded
and then allow to core to continue with the card initialization
sequence? Or do you foresee any issues with that? My point is that, if
it will fail - it will fail anyway.
>
> As result, our SDHC driver has to implement the functionality to
> send commands and check the results, in host layer.
> If directly calling mmc_wait_for_cmd() is improper, could you please
> give us some suggestions?
>
> For eMMC, CMD8 is used to test current sampling point set in PHY.
Try to use mmc_send_tuning().
>
>>> +
>>> + 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;
>>
>> No thanks! MMC/SD/SDIO protocol code belongs in the core.
>>
> For SDIO, SD_IO_RW_DIRECT command is sent to test current sampling point
> in PHY.
> Please help provide some suggestion to implement the command transfer.
Again, I think mmc_send_tuning() should be possible for you to use.
[...]
>>> + if (mmc->card)
>>> + card = mmc->card;
>>> + else
>>> + /*
>>> + * Only valid during initialization
>>> + * before mmc->card is set
>>> + */
>>> + card = priv->card_candidate;
>>> + if (unlikely(!card)) {
>>> + dev_warn(mmc_dev(mmc), "card is not present\n");
>>> + return -EINVAL;
>>> + }
>>
>> That your host need to hold a copy of the card pointer, tells me that
>> something is not really correct.
>>
>> I might be wrong, if this turns out to be a special case, but I doubt
>> it. Although, if it *is* a special such case, we shall most likely try
>> to extend the the mmc core layer instead of adding all these hacks in
>> your host driver.
>>
> This card pointer copies the temporary structure mmc_card
> used in mmc_init_card(), mmc_sd_init_card() and mmc_sdio_init_card().
> Since we call mmc_wait_for_cmd() to send test commands, we need a copy
> of that temporary mmc_card here in our host driver.
I see, thanks for clarifying.
>
> During PHY setting in card initialization, mmc_host->card is not updated
> yet with that temporary mmc_card. Thus we are not able to directly use
> mmc_host->card. Instead, this card pointer is introduced to enable
> mmc_wait_for_cmd().
>
> If we can improve our host driver to send test commands without mmc_card,
> this card pointer can be removed.
> Could you please share your opinion please?
The mmc_send_tuning() API takes the mmc_host as parameter. If you
convert to that, perhaps you would be able to remove the need to hold
the card pointer.
BTW, the reason why mmc_send_tuning() doesn't take the card as a
parameter, is exactly those you just described above.
[...]
Kind regards
Uffe
Hi Ulf,
On 2016/11/24 21:34, Ulf Hansson wrote:
> On 24 November 2016 at 13:41, Ziji Hu <[email protected]> wrote:
>> Hi Ulf,
>>
>> On 2016/11/24 18:43, Ulf Hansson wrote:
>>> On 31 October 2016 at 12:09, Gregory CLEMENT
>>> <[email protected]> wrote:
>>>> From: Ziji Hu <[email protected]>
>>>>
>> <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;
>>>
>>> This wrapper function seems unnessarry. It only adds a dev_err(), so
>>> then might as well do that in __emmc_signal_voltage_switch().
>>>
>> Sure. Will merge it back to __emmc_signal_voltage_switch().
>>
>>>> +}
>>>> +
>>>> +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 that's the case then that is wrong in the generic sdhci code.
>>> What's the reason why it can't be fixed there instead of having this
>>> workaround?
>>>
>> In my very own opinion, SD Spec doesn't specify whether SDCLK should be
>> enabled or not during power setting.
>> Enabling SDCLK might be a special condition only required by our SDHC.
>> I try to avoid breaking other vendors' SDHC functionality
>> if their SDHCs require SDCLK disabled.
>> Thus I prefer to keep it inside our SDHC driver.
>
> I let Adrian comment on this.
>
> For sure we should avoid breaking other sdhci variant, but on the
> other hand *if* the generic code is wrong we should fix it!
>
Of course.
>>
>>>> + * 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->emmc_slot)
>>>> + 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);
>>>
>>> This looks weird. I assume this can be done as a part of the regular
>>> tuning seqeunce!?
>>>
>> It is our SDHC specific preparation prior to tuning, rather than a
>> standard step in spec.
>> Thus I leave it inside our driver.
>
> My point is that this isn't the purpose of ->init_card(). thus you are
> abusing it.
>
> Try to make it work in another way, please. I think you can.
>
Got it.
I will move it to our host specific probe function.
>>
>>>> +
>>>> + slot_idx = priv->slot_idx;
>>>> + if (!mmc_card_sdio(card)) {
>>>> + /* Clear SDIO Card Inserted indication */
>>>
>>> Why do you need this?
>>>
>>> If you need to reset this, I think it's better to do it from
>>> ->set_ios() at MMC_POWER_OFF.
>>>
>> This field indicates SDIO card and controls async interrupt feature
>> of SDIO in our SDHC.
>> This async interrupt feature is enabled when SDIO card is inserted.
>> It should be disabled if SD card is inserted instead.
>
> What do you mean by SDIO async interupts? Are you talking about SDIO
> irqs on DAT1 line?
>
> Those is supposed to be enabled when someone explicitly requests them,
> not when the card is inserted.
> In other words when an SDIO func driver have called sdio_claim_irq().
>
> Moreover, we have ->enable_sdio_irq() ops that deals with this.
>
Yes. I mean the SDIO irqs on DAT1 line in async mode.
This field enables our host to recognize the async SDIO irq from SDIO device.
It controls our host side behavior, other than the SDIO device.
I think ->enable_sdio_irq() is a more reasonable place to put this workraound.
I will export sdhci_enable_sdio_irq() and implement out host own
enable_sdio_irq() calling sdhci_enable_sdio)irq() plus this workaround.
Does it sound reasonable to you?
> [...]
>
>>>> +
>>>> + /*
>>>> + * Xenon Specific property:
>>>> + * emmc: explicitly indicate whether this slot is for eMMC
>>>> + * slotno: the index of slot. Refer to SDHC_SYS_CFG_INFO register
>>>> + * tun-count: the interval between re-tuning
>>>> + * PHY type: "sdhc phy", "emmc phy 5.0" or "emmc phy 5.1"
>>>> + */
>>>> + if (of_property_read_bool(np, "marvell,xenon-emmc"))
>>>> + priv->emmc_slot = true;
>>>
>>> So, you need this because of the eMMC voltage switch behaviour, right?
>>>
>>> Then I would rather like to describe this a generic DT bindings for
>>> the eMMC voltage level support. There have acutally been some earlier
>>> discussions for this, but we haven't yet made some changes.
>>>
>>> I think what is missing is a mmc-ddr-3_3v DT binding, which when set,
>>> allows the host driver to accept I/O voltage switches to 3.3V. If not
>>> supported the ->start_signal_voltage_switch() ops may return -EINVAL.
>>> This would inform the mmc core to move on to the next supported
>>> voltage level. There might be some minor additional changes to the mmc
>>> card initialization sequence, but those should be simple.
>>>
>>> I can help out to look into this, unless you want to do it yourself of course!?
>>>
>> Yes. One of the reasons is to provide eMMC specific voltage setting.
>> But in my very own opinion, it should be irrelevant to voltage level.
>> The eMMC voltage setting on our SDHC is different from SD/SDIO voltage switch.
>> It will become more complex with different SOC implementation details.
>
> Got it. Although I think we can cope with that fine just by using the
> different SD/eMMC speed modes settings defined in DT (or from the
> SDHCI caps register)
>
In my very opinion, I'm not sure if there is any corner case that driver cannot
determine the eMMC card type from DT and SDHC caps.
>> Unfortunately, MMC driver cannot determine the card type yet when eMMC voltage
>> setting should be executed.
>> Thus an flag is required here to tell driver to execute eMMC voltage setting.
>>
>> Besides, additional eMMC specific settings might be implemented in future, besides
>> voltage setting. Most of them should be completed before MMC driver recognizes the
>> card type. Thus I have to keep this flag to indicate current SDHC is for eMMC.
>
> I doubt you will need a generic "eMMC" flag, but let's see when we go forward.
>
> Currently it's clear you don't need such a flag, so I will submit a
> change adding a DT binding for "mmc-ddr-3_3v" then we can take it from
> there, to see if it suits your needs.
>
Actually, our eMMC is usually fixed as 1.8V.
The pair "no-sd" + "no-sdio" can provide the similar information.
But I'm not sure if it is proper to use those two property in such a way.
Thank you.
Best regards
Hu Ziji
> [...]
>
> Kind regards
> Uffe
>
Hi Ulf,
On 2016/11/24 22:33, Ulf Hansson wrote:
> [...]
>
>>>
>>>>
>>>> +
>>>> +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);
>>>
>>> Why do you read the ext csd here?
>>>
>> I would like to simply introduce the PHY setting of our SDHC.
>> The target of the PHY setting is to achieve a perfect sampling
>> point for transfers, during card initialization.
>
> Okay, so the phy is involved when running the tuning sequence.
>
Actually, all the transfers pass our host PHY.
>>
>> For HS200/HS400/SDR104 whose SDCLK is more than 50MHz, SDHC HW
>> will search for this sampling point with DLL's help.
>
> Apologize for my ignorance, but what is a "DLL" in this case?
>
DLL is Delay-locked Loop. It is a HW module similar to PLL.
>>
>> For other speed mode whose SDLCK is less than or equals to 50MHz,
>> SW has to scan the PHY delay line to find out this perfect sampling
>> point. Our driver sends a command to verify a sampling point
>> in current environment.
>
> Ahh, okay! I guess the important part here is to not only send a
> command, but also to make sure data becomes transferred on the DAT
> lines, as to confirm your tuning sequence!?
Yes.
It is the best if the test command can transfer on DAT lines.
>
> In cases of HS200/HS400/SDR104 you should be able to use the
> mmc_send_tuning() API, don't you think?
For HS200/HS400/SDR104, we finally call sdhci_execute_tuning() to
execute tuning. Those test commands are not used.
In HS200/HS400/SDR104, HW will provide our host driver with suitable
tuning step. Our host driver set the tuning step in SDHCI register and
then start standard tuning sequence. The tuning step value provided
by our host HW will enhance tuning.
>
> For the other cases (lower speed modes) which cards doesn't support
> the tuning command, perhaps you can just assume the PHY scan succeeded
> and then allow to core to continue with the card initialization
> sequence? Or do you foresee any issues with that? My point is that, if
> it will fail - it will fail anyway.
Usually, our host driver will always successfully scan and select a
perfect sampling point.
If driver cannot find any suitable sampling point, it is likely that
transfers will also fail after init. But usually it is a issue, caused by
incorrect setting on boards/SOC/other PHY parameters, especially in development.
We will fix the issue and then scan will succeed in final product.
>
>>
>> As result, our SDHC driver has to implement the functionality to
>> send commands and check the results, in host layer.
>> If directly calling mmc_wait_for_cmd() is improper, could you please
>> give us some suggestions?
>>
>> For eMMC, CMD8 is used to test current sampling point set in PHY.
>
> Try to use mmc_send_tuning().
>
Could you please tell me the requirement of "op_code" parameter in
mmc_send_tuning()?
According to mmc_send_tuning(),it seems that tuning command(CMD19/CMD21)
is required. Thus device will not response mmc_send_tuning() if current
speed mode doesn't support tuning command.
Please correct me if I am wrong.
>>
>>>> +
>>>> + 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;
>>>
>>> No thanks! MMC/SD/SDIO protocol code belongs in the core.
>>>
>> For SDIO, SD_IO_RW_DIRECT command is sent to test current sampling point
>> in PHY.
>> Please help provide some suggestion to implement the command transfer.
>
> Again, I think mmc_send_tuning() should be possible for you to use.
>
> [...]
>
>>>> + if (mmc->card)
>>>> + card = mmc->card;
>>>> + else
>>>> + /*
>>>> + * Only valid during initialization
>>>> + * before mmc->card is set
>>>> + */
>>>> + card = priv->card_candidate;
>>>> + if (unlikely(!card)) {
>>>> + dev_warn(mmc_dev(mmc), "card is not present\n");
>>>> + return -EINVAL;
>>>> + }
>>>
>>> That your host need to hold a copy of the card pointer, tells me that
>>> something is not really correct.
>>>
>>> I might be wrong, if this turns out to be a special case, but I doubt
>>> it. Although, if it *is* a special such case, we shall most likely try
>>> to extend the the mmc core layer instead of adding all these hacks in
>>> your host driver.
>>>
>> This card pointer copies the temporary structure mmc_card
>> used in mmc_init_card(), mmc_sd_init_card() and mmc_sdio_init_card().
>> Since we call mmc_wait_for_cmd() to send test commands, we need a copy
>> of that temporary mmc_card here in our host driver.
>
> I see, thanks for clarifying.
>
>>
>> During PHY setting in card initialization, mmc_host->card is not updated
>> yet with that temporary mmc_card. Thus we are not able to directly use
>> mmc_host->card. Instead, this card pointer is introduced to enable
>> mmc_wait_for_cmd().
>>
>> If we can improve our host driver to send test commands without mmc_card,
>> this card pointer can be removed.
>> Could you please share your opinion please?
>
> The mmc_send_tuning() API takes the mmc_host as parameter. If you
> convert to that, perhaps you would be able to remove the need to hold
> the card pointer.
>
> BTW, the reason why mmc_send_tuning() doesn't take the card as a
> parameter, is exactly those you just described above.
>
Got it.
Thanks a lot for the information.
Thank you for the great help.
Best regards,
Hu Ziji
> [...]
>
> Kind regards
> Uffe
>
Hi Ulf,
On 2016/11/24 23:00, Ziji Hu wrote:
> Hi Ulf,
>
> On 2016/11/24 21:34, Ulf Hansson wrote:
<snip>
>>>>> +
>>>>> + /*
>>>>> + * Xenon Specific property:
>>>>> + * emmc: explicitly indicate whether this slot is for eMMC
>>>>> + * slotno: the index of slot. Refer to SDHC_SYS_CFG_INFO register
>>>>> + * tun-count: the interval between re-tuning
>>>>> + * PHY type: "sdhc phy", "emmc phy 5.0" or "emmc phy 5.1"
>>>>> + */
>>>>> + if (of_property_read_bool(np, "marvell,xenon-emmc"))
>>>>> + priv->emmc_slot = true;
>>>>
>>>> So, you need this because of the eMMC voltage switch behaviour, right?
>>>>
>>>> Then I would rather like to describe this a generic DT bindings for
>>>> the eMMC voltage level support. There have acutally been some earlier
>>>> discussions for this, but we haven't yet made some changes.
>>>>
>>>> I think what is missing is a mmc-ddr-3_3v DT binding, which when set,
>>>> allows the host driver to accept I/O voltage switches to 3.3V. If not
>>>> supported the ->start_signal_voltage_switch() ops may return -EINVAL.
>>>> This would inform the mmc core to move on to the next supported
>>>> voltage level. There might be some minor additional changes to the mmc
>>>> card initialization sequence, but those should be simple.
>>>>
>>>> I can help out to look into this, unless you want to do it yourself of course!?
>>>>
>>> Yes. One of the reasons is to provide eMMC specific voltage setting.
>>> But in my very own opinion, it should be irrelevant to voltage level.
>>> The eMMC voltage setting on our SDHC is different from SD/SDIO voltage switch.
>>> It will become more complex with different SOC implementation details.
>>
>> Got it. Although I think we can cope with that fine just by using the
>> different SD/eMMC speed modes settings defined in DT (or from the
>> SDHCI caps register)
>>
> In my very opinion, I'm not sure if there is any corner case that driver cannot
> determine the eMMC card type from DT and SDHC caps.
>
>>> Unfortunately, MMC driver cannot determine the card type yet when eMMC voltage
>>> setting should be executed.
>>> Thus an flag is required here to tell driver to execute eMMC voltage setting.
>>>
>>> Besides, additional eMMC specific settings might be implemented in future, besides
>>> voltage setting. Most of them should be completed before MMC driver recognizes the
>>> card type. Thus I have to keep this flag to indicate current SDHC is for eMMC.
>>
>> I doubt you will need a generic "eMMC" flag, but let's see when we go forward.
>>
>> Currently it's clear you don't need such a flag, so I will submit a
>> change adding a DT binding for "mmc-ddr-3_3v" then we can take it from
>> there, to see if it suits your needs.
>>
Another reason for a special "xenon-emmc" property is that our host IP usually can
support both eMMC and SD. Whether a host is used as eMMC or SD depends on the
final implementation of the actual product.
Thus our host driver needs to know whether current SDHC is fixed as eMMC or SD.
So far, It can only get the information from DT.
After out host driver get the card type information from DT, it can prepare eMMC
specific voltage, set eMMC specific mmc->caps/caps2 flags and do other
vendor specific init, before card init procedure.
Otherwise, our host driver has to wait until card type is determined in mmc_rescan().
A generic "eMMC" flag is unnecessary. I just require a private property,
which is only used in our host driver and DT.
Thank you.
Best regards,
Hu Ziji
>
> Actually, our eMMC is usually fixed as 1.8V.
>
> The pair "no-sd" + "no-sdio" can provide the similar information.
> But I'm not sure if it is proper to use those two property in such a way.
>
> Thank you.
>
> Best regards
> Hu Ziji
>
>> [...]
>>
>> Kind regards
>> Uffe
>>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
[...]
>>
>> Moreover, we have ->enable_sdio_irq() ops that deals with this.
>>
> Yes. I mean the SDIO irqs on DAT1 line in async mode.
> This field enables our host to recognize the async SDIO irq from SDIO device.
> It controls our host side behavior, other than the SDIO device.
>
> I think ->enable_sdio_irq() is a more reasonable place to put this workraound.
> I will export sdhci_enable_sdio_irq() and implement out host own
> enable_sdio_irq() calling sdhci_enable_sdio)irq() plus this workaround.
> Does it sound reasonable to you?
Yes.
[...]
>>
>> Got it. Although I think we can cope with that fine just by using the
>> different SD/eMMC speed modes settings defined in DT (or from the
>> SDHCI caps register)
>>
> In my very opinion, I'm not sure if there is any corner case that driver cannot
> determine the eMMC card type from DT and SDHC caps.
>
>>> Unfortunately, MMC driver cannot determine the card type yet when eMMC voltage
>>> setting should be executed.
>>> Thus an flag is required here to tell driver to execute eMMC voltage setting.
>>>
>>> Besides, additional eMMC specific settings might be implemented in future, besides
>>> voltage setting. Most of them should be completed before MMC driver recognizes the
>>> card type. Thus I have to keep this flag to indicate current SDHC is for eMMC.
>>
>> I doubt you will need a generic "eMMC" flag, but let's see when we go forward.
>>
>> Currently it's clear you don't need such a flag, so I will submit a
>> change adding a DT binding for "mmc-ddr-3_3v" then we can take it from
>> there, to see if it suits your needs.
>>
>
> Actually, our eMMC is usually fixed as 1.8V.
>
> The pair "no-sd" + "no-sdio" can provide the similar information.
> But I'm not sure if it is proper to use those two property in such a way.
Well, potentially those could be used like that.
The point of why we added them was to allow hosts that don't support
the different protocols (which is somewhat true for your case, because
of signal voltage issues) to tell the mmc core about it. That instead
of having to all kind of crazy hacks in the drives, that in then
didn't work out so well.
Kind regards
Uffe
[...]
>>>>>> +
>>>>>> + /*
>>>>>> + * Xenon Specific property:
>>>>>> + * emmc: explicitly indicate whether this slot is for eMMC
>>>>>> + * slotno: the index of slot. Refer to SDHC_SYS_CFG_INFO register
>>>>>> + * tun-count: the interval between re-tuning
>>>>>> + * PHY type: "sdhc phy", "emmc phy 5.0" or "emmc phy 5.1"
>>>>>> + */
>>>>>> + if (of_property_read_bool(np, "marvell,xenon-emmc"))
>>>>>> + priv->emmc_slot = true;
>>>>>
>>>>> So, you need this because of the eMMC voltage switch behaviour, right?
>>>>>
>>>>> Then I would rather like to describe this a generic DT bindings for
>>>>> the eMMC voltage level support. There have acutally been some earlier
>>>>> discussions for this, but we haven't yet made some changes.
>>>>>
>>>>> I think what is missing is a mmc-ddr-3_3v DT binding, which when set,
>>>>> allows the host driver to accept I/O voltage switches to 3.3V. If not
>>>>> supported the ->start_signal_voltage_switch() ops may return -EINVAL.
>>>>> This would inform the mmc core to move on to the next supported
>>>>> voltage level. There might be some minor additional changes to the mmc
>>>>> card initialization sequence, but those should be simple.
>>>>>
>>>>> I can help out to look into this, unless you want to do it yourself of course!?
>>>>>
>>>> Yes. One of the reasons is to provide eMMC specific voltage setting.
>>>> But in my very own opinion, it should be irrelevant to voltage level.
>>>> The eMMC voltage setting on our SDHC is different from SD/SDIO voltage switch.
>>>> It will become more complex with different SOC implementation details.
>>>
>>> Got it. Although I think we can cope with that fine just by using the
>>> different SD/eMMC speed modes settings defined in DT (or from the
>>> SDHCI caps register)
>>>
>> In my very opinion, I'm not sure if there is any corner case that driver cannot
>> determine the eMMC card type from DT and SDHC caps.
>>
>>>> Unfortunately, MMC driver cannot determine the card type yet when eMMC voltage
>>>> setting should be executed.
>>>> Thus an flag is required here to tell driver to execute eMMC voltage setting.
>>>>
>>>> Besides, additional eMMC specific settings might be implemented in future, besides
>>>> voltage setting. Most of them should be completed before MMC driver recognizes the
>>>> card type. Thus I have to keep this flag to indicate current SDHC is for eMMC.
>>>
>>> I doubt you will need a generic "eMMC" flag, but let's see when we go forward.
>>>
>>> Currently it's clear you don't need such a flag, so I will submit a
>>> change adding a DT binding for "mmc-ddr-3_3v" then we can take it from
>>> there, to see if it suits your needs.
>>>
>
> Another reason for a special "xenon-emmc" property is that our host IP usually can
> support both eMMC and SD. Whether a host is used as eMMC or SD depends on the
> final implementation of the actual product.
> Thus our host driver needs to know whether current SDHC is fixed as eMMC or SD.
> So far, It can only get the information from DT.
As a matter of fact for mounted non-removable cards, such as eMMC, we
already have the option to describe some of their characteristics in
DT. Perhaps that's what you need?
Please have a look at:
Documentation/devicetree/bindings/mmc/mmc-card.txt
>
> After out host driver get the card type information from DT, it can prepare eMMC
> specific voltage, set eMMC specific mmc->caps/caps2 flags and do other
> vendor specific init, before card init procedure.
> Otherwise, our host driver has to wait until card type is determined in mmc_rescan().
>
> A generic "eMMC" flag is unnecessary. I just require a private property,
> which is only used in our host driver and DT.
>
> Thank you.
>
> Best regards,
> Hu Ziji
>
>>
>> Actually, our eMMC is usually fixed as 1.8V.
>>
>> The pair "no-sd" + "no-sdio" can provide the similar information.
>> But I'm not sure if it is proper to use those two property in such a way.
>>
>> Thank you.
>>
>> Best regards
>> Hu Ziji
>>
>>> [...]
>>>
>>> Kind regards
>>> Uffe
>>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
>> the body of a message to [email protected]
>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>>
Kind regards
Uffe
Hi Ulf,
On 2016/11/25 21:06, Ulf Hansson wrote:
> [...]
>
>>>>>>> +
>>>>>>> + /*
>>>>>>> + * Xenon Specific property:
>>>>>>> + * emmc: explicitly indicate whether this slot is for eMMC
>>>>>>> + * slotno: the index of slot. Refer to SDHC_SYS_CFG_INFO register
>>>>>>> + * tun-count: the interval between re-tuning
>>>>>>> + * PHY type: "sdhc phy", "emmc phy 5.0" or "emmc phy 5.1"
>>>>>>> + */
>>>>>>> + if (of_property_read_bool(np, "marvell,xenon-emmc"))
>>>>>>> + priv->emmc_slot = true;
>>>>>>
>>>>>> So, you need this because of the eMMC voltage switch behaviour, right?
>>>>>>
>>>>>> Then I would rather like to describe this a generic DT bindings for
>>>>>> the eMMC voltage level support. There have acutally been some earlier
>>>>>> discussions for this, but we haven't yet made some changes.
>>>>>>
>>>>>> I think what is missing is a mmc-ddr-3_3v DT binding, which when set,
>>>>>> allows the host driver to accept I/O voltage switches to 3.3V. If not
>>>>>> supported the ->start_signal_voltage_switch() ops may return -EINVAL.
>>>>>> This would inform the mmc core to move on to the next supported
>>>>>> voltage level. There might be some minor additional changes to the mmc
>>>>>> card initialization sequence, but those should be simple.
>>>>>>
>>>>>> I can help out to look into this, unless you want to do it yourself of course!?
>>>>>>
>>>>> Yes. One of the reasons is to provide eMMC specific voltage setting.
>>>>> But in my very own opinion, it should be irrelevant to voltage level.
>>>>> The eMMC voltage setting on our SDHC is different from SD/SDIO voltage switch.
>>>>> It will become more complex with different SOC implementation details.
>>>>
>>>> Got it. Although I think we can cope with that fine just by using the
>>>> different SD/eMMC speed modes settings defined in DT (or from the
>>>> SDHCI caps register)
>>>>
>>> In my very opinion, I'm not sure if there is any corner case that driver cannot
>>> determine the eMMC card type from DT and SDHC caps.
>>>
>>>>> Unfortunately, MMC driver cannot determine the card type yet when eMMC voltage
>>>>> setting should be executed.
>>>>> Thus an flag is required here to tell driver to execute eMMC voltage setting.
>>>>>
>>>>> Besides, additional eMMC specific settings might be implemented in future, besides
>>>>> voltage setting. Most of them should be completed before MMC driver recognizes the
>>>>> card type. Thus I have to keep this flag to indicate current SDHC is for eMMC.
>>>>
>>>> I doubt you will need a generic "eMMC" flag, but let's see when we go forward.
>>>>
>>>> Currently it's clear you don't need such a flag, so I will submit a
>>>> change adding a DT binding for "mmc-ddr-3_3v" then we can take it from
>>>> there, to see if it suits your needs.
>>>>
>>
>> Another reason for a special "xenon-emmc" property is that our host IP usually can
>> support both eMMC and SD. Whether a host is used as eMMC or SD depends on the
>> final implementation of the actual product.
>> Thus our host driver needs to know whether current SDHC is fixed as eMMC or SD.
>> So far, It can only get the information from DT.
>
> As a matter of fact for mounted non-removable cards, such as eMMC, we
> already have the option to describe some of their characteristics in
> DT. Perhaps that's what you need?
>
> Please have a look at:
> Documentation/devicetree/bindings/mmc/mmc-card.txt
>
Great!
I will try this mmc-card sub-node.
Thank you very much.
Best regards,
Hu Ziji
>>
>> After out host driver get the card type information from DT, it can prepare eMMC
>> specific voltage, set eMMC specific mmc->caps/caps2 flags and do other
>> vendor specific init, before card init procedure.
>> Otherwise, our host driver has to wait until card type is determined in mmc_rescan().
>>
>> A generic "eMMC" flag is unnecessary. I just require a private property,
>> which is only used in our host driver and DT.
>>
>> Thank you.
>>
>> Best regards,
>> Hu Ziji
>>
>>>
>>> Actually, our eMMC is usually fixed as 1.8V.
>>>
>>> The pair "no-sd" + "no-sdio" can provide the similar information.
>>> But I'm not sure if it is proper to use those two property in such a way.
>>>
>>> Thank you.
>>>
>>> Best regards
>>> Hu Ziji
>>>
>>>> [...]
>>>>
>>>> Kind regards
>>>> Uffe
>>>>
>>> --
>>> To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
>>> the body of a message to [email protected]
>>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>>>
>
> Kind regards
> Uffe
>
On 24/11/16 15:34, Ulf Hansson wrote:
> On 24 November 2016 at 13:41, Ziji Hu <[email protected]> wrote:
>> On 2016/11/24 18:43, Ulf Hansson wrote:
>>> On 31 October 2016 at 12:09, Gregory CLEMENT
>>> <[email protected]> wrote:
>>>> From: Ziji Hu <[email protected]>
>>>> +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 that's the case then that is wrong in the generic sdhci code.
>>> What's the reason why it can't be fixed there instead of having this
>>> workaround?
>>>
>> In my very own opinion, SD Spec doesn't specify whether SDCLK should be
>> enabled or not during power setting.
>> Enabling SDCLK might be a special condition only required by our SDHC.
>> I try to avoid breaking other vendors' SDHC functionality
>> if their SDHCs require SDCLK disabled.
>> Thus I prefer to keep it inside our SDHC driver.
>
> I let Adrian comment on this.
>
> For sure we should avoid breaking other sdhci variant, but on the
> other hand *if* the generic code is wrong we should fix it!
Yes, this looks like something that could perhaps be fixed in sdhci. I will
look into it.
Hi Ulf,
On 2016/11/24 23:37, Ziji Hu wrote:
> Hi Ulf,
>
> On 2016/11/24 22:33, Ulf Hansson wrote:
<snip>
>>>
>>> As result, our SDHC driver has to implement the functionality to
>>> send commands and check the results, in host layer.
>>> If directly calling mmc_wait_for_cmd() is improper, could you please
>>> give us some suggestions?
>>>
>>> For eMMC, CMD8 is used to test current sampling point set in PHY.
>>
>> Try to use mmc_send_tuning().
>>
>
> Could you please tell me the requirement of "op_code" parameter in
> mmc_send_tuning()?
> According to mmc_send_tuning(),it seems that tuning command(CMD19/CMD21)
> is required. Thus device will not response mmc_send_tuning() if current
> speed mode doesn't support tuning command.
> Please correct me if I am wrong.
>
As you suggest, I replace mmc_wait_for_cmd() with mmc_send_tuning(), to
send commands for testing current sampling point set in our host PHY.
According to my test result, it shows that mmc_send_tuning() can only support
tuning command (CMD21/CMD19).
As a result, we cannot use mmc_send_tuning() when card is in the speed modes
which doesn't support tuning, such as eMMC HS SDR, eMMC HS DRR and
SD SDR 12/SDR25/DDR50. Card will not response to tuning commands in those
speed modes.
Could you please provide suggestions for the speed mode in which tuning is
not available?
Thank you.
Best regards,
Hu Ziji
>>>
>>>>> +
>>>>> + 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;
>>>>
>>>> No thanks! MMC/SD/SDIO protocol code belongs in the core.
>>>>
>>> For SDIO, SD_IO_RW_DIRECT command is sent to test current sampling point
>>> in PHY.
>>> Please help provide some suggestion to implement the command transfer.
>>
>> Again, I think mmc_send_tuning() should be possible for you to use.
>>
>> [...]
>>
>>>>> + if (mmc->card)
>>>>> + card = mmc->card;
>>>>> + else
>>>>> + /*
>>>>> + * Only valid during initialization
>>>>> + * before mmc->card is set
>>>>> + */
>>>>> + card = priv->card_candidate;
>>>>> + if (unlikely(!card)) {
>>>>> + dev_warn(mmc_dev(mmc), "card is not present\n");
>>>>> + return -EINVAL;
>>>>> + }
>>>>
>>>> That your host need to hold a copy of the card pointer, tells me that
>>>> something is not really correct.
>>>>
>>>> I might be wrong, if this turns out to be a special case, but I doubt
>>>> it. Although, if it *is* a special such case, we shall most likely try
>>>> to extend the the mmc core layer instead of adding all these hacks in
>>>> your host driver.
>>>>
>>> This card pointer copies the temporary structure mmc_card
>>> used in mmc_init_card(), mmc_sd_init_card() and mmc_sdio_init_card().
>>> Since we call mmc_wait_for_cmd() to send test commands, we need a copy
>>> of that temporary mmc_card here in our host driver.
>>
>> I see, thanks for clarifying.
>>
>>>
>>> During PHY setting in card initialization, mmc_host->card is not updated
>>> yet with that temporary mmc_card. Thus we are not able to directly use
>>> mmc_host->card. Instead, this card pointer is introduced to enable
>>> mmc_wait_for_cmd().
>>>
>>> If we can improve our host driver to send test commands without mmc_card,
>>> this card pointer can be removed.
>>> Could you please share your opinion please?
>>
>> The mmc_send_tuning() API takes the mmc_host as parameter. If you
>> convert to that, perhaps you would be able to remove the need to hold
>> the card pointer.
>>
>> BTW, the reason why mmc_send_tuning() doesn't take the card as a
>> parameter, is exactly those you just described above.
>>
> Got it.
> Thanks a lot for the information.
>
> Thank you for the great help.
>
> Best regards,
> Hu Ziji
>
>> [...]
>>
>> Kind regards
>> Uffe
>>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
[...]
>
> Could you please tell me the requirement of "op_code" parameter in
> mmc_send_tuning()?
> According to mmc_send_tuning(),it seems that tuning command(CMD19/CMD21)
> is required. Thus device will not response mmc_send_tuning() if current
> speed mode doesn't support tuning command.
> Please correct me if I am wrong.
>
When the mmc core decides it's time to execute tuning, it invokes the
->execute_tuning() host ops, which has the "opcode" as a parameter.
You should be able to use it when calling mmc_send_tuning().
[...]
Kind regards
Uffe
>
> As you suggest, I replace mmc_wait_for_cmd() with mmc_send_tuning(), to
> send commands for testing current sampling point set in our host PHY.
>
> According to my test result, it shows that mmc_send_tuning() can only support
> tuning command (CMD21/CMD19).
> As a result, we cannot use mmc_send_tuning() when card is in the speed modes
> which doesn't support tuning, such as eMMC HS SDR, eMMC HS DRR and
> SD SDR 12/SDR25/DDR50. Card will not response to tuning commands in those
> speed modes.
>
> Could you please provide suggestions for the speed mode in which tuning is
> not available?
>
Normally the mmc host driver shouldn't have to care about what the
card supports, as that is the responsibility of the mmc core to
manage.
The host should only need to implement the ->execute_tuning() ops,
which gets called when the card supports tuning (CMD19/21). Does it
make sense?
Kind regards
Uffe
Hi Ulf,
On 2016/11/28 19:13, Ulf Hansson wrote:
>>
>> As you suggest, I replace mmc_wait_for_cmd() with mmc_send_tuning(), to
>> send commands for testing current sampling point set in our host PHY.
>>
>> According to my test result, it shows that mmc_send_tuning() can only support
>> tuning command (CMD21/CMD19).
>> As a result, we cannot use mmc_send_tuning() when card is in the speed modes
>> which doesn't support tuning, such as eMMC HS SDR, eMMC HS DRR and
>> SD SDR 12/SDR25/DDR50. Card will not response to tuning commands in those
>> speed modes.
>>
>> Could you please provide suggestions for the speed mode in which tuning is
>> not available?
>>
>
> Normally the mmc host driver shouldn't have to care about what the
> card supports, as that is the responsibility of the mmc core to
> manage.
>
> The host should only need to implement the ->execute_tuning() ops,
> which gets called when the card supports tuning (CMD19/21). Does it
> make sense?
>
I think it is irrelevant to tuning procedure.
Our host requires to adjust PHY setting after each time ios setting
(SDCLK/bus width/speed mode) is changed.
The simplified sequence is:
mmc change ios --> mmc_set_ios() --> ->set_ios() --> after sdhci_set_ios(),
adjust PHY setting.
During PHY setting adjustment, out host driver has to send commands to
test current sampling point. Tuning is another independent step.
Thus our host needs a valid command in PHY setting adjustment. Tuning command
can be borrowed to complete this task in SD SDR50. But for other speed mode,
we have to find out a valid command.
Any suggestion please?
Thank you.
Best regards,
Hu Ziji
> Kind regards
> Uffe
>
On 28 November 2016 at 12:38, Ziji Hu <[email protected]> wrote:
> Hi Ulf,
>
> On 2016/11/28 19:13, Ulf Hansson wrote:
>>>
>>> As you suggest, I replace mmc_wait_for_cmd() with mmc_send_tuning(), to
>>> send commands for testing current sampling point set in our host PHY.
>>>
>>> According to my test result, it shows that mmc_send_tuning() can only support
>>> tuning command (CMD21/CMD19).
>>> As a result, we cannot use mmc_send_tuning() when card is in the speed modes
>>> which doesn't support tuning, such as eMMC HS SDR, eMMC HS DRR and
>>> SD SDR 12/SDR25/DDR50. Card will not response to tuning commands in those
>>> speed modes.
>>>
>>> Could you please provide suggestions for the speed mode in which tuning is
>>> not available?
>>>
>>
>> Normally the mmc host driver shouldn't have to care about what the
>> card supports, as that is the responsibility of the mmc core to
>> manage.
>>
>> The host should only need to implement the ->execute_tuning() ops,
>> which gets called when the card supports tuning (CMD19/21). Does it
>> make sense?
>>
> I think it is irrelevant to tuning procedure.
>
> Our host requires to adjust PHY setting after each time ios setting
> (SDCLK/bus width/speed mode) is changed.
> The simplified sequence is:
> mmc change ios --> mmc_set_ios() --> ->set_ios() --> after sdhci_set_ios(),
> adjust PHY setting.
> During PHY setting adjustment, out host driver has to send commands to
> test current sampling point. Tuning is another independent step.
For those speed modes (or other ios changes) that *don't* requires
tuning, then what will you do when you send the command to confirm the
change of PHY setting and it fails?
My assumption is that you will fail anyway, by propagating the error
to the mmc core. At least that what was my understanding from your
earlier replies, right!?
Then, I think there are no point having the host driver sending a
command to confirm the PHY settings, as the mmc core will anyway
discover if something goes wrong when the next command is sent.
Please correct me if I am wrong!
>
> Thus our host needs a valid command in PHY setting adjustment. Tuning command
> can be borrowed to complete this task in SD SDR50. But for other speed mode,
> we have to find out a valid command.
I thought we agreed on this wasn't necessary? Please see my upper response.
Kind regards
Uffe
Hi Ulf,
On 2016/11/28 23:16, Ulf Hansson wrote:
> On 28 November 2016 at 12:38, Ziji Hu <[email protected]> wrote:
>> Hi Ulf,
>>
>> On 2016/11/28 19:13, Ulf Hansson wrote:
>>>>
>>>> As you suggest, I replace mmc_wait_for_cmd() with mmc_send_tuning(), to
>>>> send commands for testing current sampling point set in our host PHY.
>>>>
>>>> According to my test result, it shows that mmc_send_tuning() can only support
>>>> tuning command (CMD21/CMD19).
>>>> As a result, we cannot use mmc_send_tuning() when card is in the speed modes
>>>> which doesn't support tuning, such as eMMC HS SDR, eMMC HS DRR and
>>>> SD SDR 12/SDR25/DDR50. Card will not response to tuning commands in those
>>>> speed modes.
>>>>
>>>> Could you please provide suggestions for the speed mode in which tuning is
>>>> not available?
>>>>
>>>
>>> Normally the mmc host driver shouldn't have to care about what the
>>> card supports, as that is the responsibility of the mmc core to
>>> manage.
>>>
>>> The host should only need to implement the ->execute_tuning() ops,
>>> which gets called when the card supports tuning (CMD19/21). Does it
>>> make sense?
>>>
>> I think it is irrelevant to tuning procedure.
>>
>> Our host requires to adjust PHY setting after each time ios setting
>> (SDCLK/bus width/speed mode) is changed.
>> The simplified sequence is:
>> mmc change ios --> mmc_set_ios() --> ->set_ios() --> after sdhci_set_ios(),
>> adjust PHY setting.
>> During PHY setting adjustment, out host driver has to send commands to
>> test current sampling point. Tuning is another independent step.
>
> For those speed modes (or other ios changes) that *don't* requires
> tuning, then what will you do when you send the command to confirm the
> change of PHY setting and it fails?
>
> My assumption is that you will fail anyway, by propagating the error
> to the mmc core. At least that what was my understanding from your
> earlier replies, right!?
>
> Then, I think there are no point having the host driver sending a
> command to confirm the PHY settings, as the mmc core will anyway
> discover if something goes wrong when the next command is sent.
>
> Please correct me if I am wrong!
>
Sorry that I didn't make myself clear.
Our host PHY delay line consists of hundreds of sampling points.
Each sampling point represents a different phase shift.
In lower speed mode, our host driver will scan the delay line.
It will select and test multiple sampling points, other than testing
only single sampling point.
If a sampling point fails to transfer cmd/data, our host driver will
move to test next sampling point, until we find out a group of successful
sampling points which can transfer cmd/data. At last we will select
a perfect one from them.
Thank you.
Best regards,
Hu Ziji
>>
>> Thus our host needs a valid command in PHY setting adjustment. Tuning command
>> can be borrowed to complete this task in SD SDR50. But for other speed mode,
>> we have to find out a valid command.
>
> I thought we agreed on this wasn't necessary? Please see my upper response.
>
> Kind regards
> Uffe
>
On 29 November 2016 at 03:53, Ziji Hu <[email protected]> wrote:
> Hi Ulf,
>
> On 2016/11/28 23:16, Ulf Hansson wrote:
>> On 28 November 2016 at 12:38, Ziji Hu <[email protected]> wrote:
>>> Hi Ulf,
>>>
>>> On 2016/11/28 19:13, Ulf Hansson wrote:
>>>>>
>>>>> As you suggest, I replace mmc_wait_for_cmd() with mmc_send_tuning(), to
>>>>> send commands for testing current sampling point set in our host PHY.
>>>>>
>>>>> According to my test result, it shows that mmc_send_tuning() can only support
>>>>> tuning command (CMD21/CMD19).
>>>>> As a result, we cannot use mmc_send_tuning() when card is in the speed modes
>>>>> which doesn't support tuning, such as eMMC HS SDR, eMMC HS DRR and
>>>>> SD SDR 12/SDR25/DDR50. Card will not response to tuning commands in those
>>>>> speed modes.
>>>>>
>>>>> Could you please provide suggestions for the speed mode in which tuning is
>>>>> not available?
>>>>>
>>>>
>>>> Normally the mmc host driver shouldn't have to care about what the
>>>> card supports, as that is the responsibility of the mmc core to
>>>> manage.
>>>>
>>>> The host should only need to implement the ->execute_tuning() ops,
>>>> which gets called when the card supports tuning (CMD19/21). Does it
>>>> make sense?
>>>>
>>> I think it is irrelevant to tuning procedure.
>>>
>>> Our host requires to adjust PHY setting after each time ios setting
>>> (SDCLK/bus width/speed mode) is changed.
>>> The simplified sequence is:
>>> mmc change ios --> mmc_set_ios() --> ->set_ios() --> after sdhci_set_ios(),
>>> adjust PHY setting.
>>> During PHY setting adjustment, out host driver has to send commands to
>>> test current sampling point. Tuning is another independent step.
>>
>> For those speed modes (or other ios changes) that *don't* requires
>> tuning, then what will you do when you send the command to confirm the
>> change of PHY setting and it fails?
>>
>> My assumption is that you will fail anyway, by propagating the error
>> to the mmc core. At least that what was my understanding from your
>> earlier replies, right!?
>>
>> Then, I think there are no point having the host driver sending a
>> command to confirm the PHY settings, as the mmc core will anyway
>> discover if something goes wrong when the next command is sent.
>>
>> Please correct me if I am wrong!
>>
>
> Sorry that I didn't make myself clear.
>
> Our host PHY delay line consists of hundreds of sampling points.
> Each sampling point represents a different phase shift.
>
> In lower speed mode, our host driver will scan the delay line.
> It will select and test multiple sampling points, other than testing
> only single sampling point.
>
> If a sampling point fails to transfer cmd/data, our host driver will
> move to test next sampling point, until we find out a group of successful
> sampling points which can transfer cmd/data. At last we will select
> a perfect one from them.
Ahh, I see. Unfortunate, this is going to be very hard to implement properly.
The main problem is that the host driver has *no* knowledge about the
internal state of the card, as that is the responsibility of the mmc
core to keep track of.
If the host driver would send a command during every update of the
"ios" setting, from ->set_ios(), for sure it would lead to commands
being sent that are "forbidden" in the current internal state of the
card.
This would lead to that the card initialization sequence fails,
because the card may move to an unknown internal state and the mmc
core would have no knowledge about what happened.
Hmm..
Can you specify, *exactly*, under which "ios updates" you need to
verify updated PHY setting changes by sending a cmd/data? Also, please
specify if it's enough to only test the CMD line or also DATA lines.
Kind regards
Uffe
Hi Ulf,
On 2016/11/29 15:49, Ulf Hansson wrote:
> On 29 November 2016 at 03:53, Ziji Hu <[email protected]> wrote:
>> Hi Ulf,
>>
>> On 2016/11/28 23:16, Ulf Hansson wrote:
>>> On 28 November 2016 at 12:38, Ziji Hu <[email protected]> wrote:
>>>> Hi Ulf,
>>>>
>>>> On 2016/11/28 19:13, Ulf Hansson wrote:
>>>>>>
>>>>>> As you suggest, I replace mmc_wait_for_cmd() with mmc_send_tuning(), to
>>>>>> send commands for testing current sampling point set in our host PHY.
>>>>>>
>>>>>> According to my test result, it shows that mmc_send_tuning() can only support
>>>>>> tuning command (CMD21/CMD19).
>>>>>> As a result, we cannot use mmc_send_tuning() when card is in the speed modes
>>>>>> which doesn't support tuning, such as eMMC HS SDR, eMMC HS DRR and
>>>>>> SD SDR 12/SDR25/DDR50. Card will not response to tuning commands in those
>>>>>> speed modes.
>>>>>>
>>>>>> Could you please provide suggestions for the speed mode in which tuning is
>>>>>> not available?
>>>>>>
>>>>>
>>>>> Normally the mmc host driver shouldn't have to care about what the
>>>>> card supports, as that is the responsibility of the mmc core to
>>>>> manage.
>>>>>
>>>>> The host should only need to implement the ->execute_tuning() ops,
>>>>> which gets called when the card supports tuning (CMD19/21). Does it
>>>>> make sense?
>>>>>
>>>> I think it is irrelevant to tuning procedure.
>>>>
>>>> Our host requires to adjust PHY setting after each time ios setting
>>>> (SDCLK/bus width/speed mode) is changed.
>>>> The simplified sequence is:
>>>> mmc change ios --> mmc_set_ios() --> ->set_ios() --> after sdhci_set_ios(),
>>>> adjust PHY setting.
>>>> During PHY setting adjustment, out host driver has to send commands to
>>>> test current sampling point. Tuning is another independent step.
>>>
>>> For those speed modes (or other ios changes) that *don't* requires
>>> tuning, then what will you do when you send the command to confirm the
>>> change of PHY setting and it fails?
>>>
>>> My assumption is that you will fail anyway, by propagating the error
>>> to the mmc core. At least that what was my understanding from your
>>> earlier replies, right!?
>>>
>>> Then, I think there are no point having the host driver sending a
>>> command to confirm the PHY settings, as the mmc core will anyway
>>> discover if something goes wrong when the next command is sent.
>>>
>>> Please correct me if I am wrong!
>>>
>>
>> Sorry that I didn't make myself clear.
>>
>> Our host PHY delay line consists of hundreds of sampling points.
>> Each sampling point represents a different phase shift.
>>
>> In lower speed mode, our host driver will scan the delay line.
>> It will select and test multiple sampling points, other than testing
>> only single sampling point.
>>
>> If a sampling point fails to transfer cmd/data, our host driver will
>> move to test next sampling point, until we find out a group of successful
>> sampling points which can transfer cmd/data. At last we will select
>> a perfect one from them.
>
> Ahh, I see. Unfortunate, this is going to be very hard to implement properly.
>
> The main problem is that the host driver has *no* knowledge about the
> internal state of the card, as that is the responsibility of the mmc
> core to keep track of.
>
> If the host driver would send a command during every update of the
> "ios" setting, from ->set_ios(), for sure it would lead to commands
> being sent that are "forbidden" in the current internal state of the
> card.
> This would lead to that the card initialization sequence fails,
> because the card may move to an unknown internal state and the mmc
> core would have no knowledge about what happened.
>
Yes. In theory, host layer should not initiate a command by itself.
We assume that bus is idle and card is stable in Tran state, when core layer
asks host to switch "ios".
Besides, we only select the commands which is valid in the whole procedure,
such as CMD8 for eMMC.
Those test commands are actually like read operations to card registers.
The card will return to Tran state even if transfer fails. It is also easy
for host to recover.
> Hmm..
>
> Can you specify, *exactly*, under which "ios updates" you need to
> verify updated PHY setting changes by sending a cmd/data? Also, please
> specify if it's enough to only test the CMD line or also DATA lines.
>
When one of the three parameters in below changes, our host driver needs
to adjust PHY in lower speed mode.
1. Speed Mode (timing): like legacy mode --> HS DDR
2. Bus Clock: like 400KHz --> 50MHz
3. Bus Width: like 1-bit --> 4-bit/8-bit
For eMMC, we use CMD8 to test sampling point.
For SD, we use CMD13.
For SDIO, currently CMD52 is used to read a register from CCCR.
Those commands in above are all valid during the whole procedure to switch
to high speed mode from legacy mode.
It is the best case if the test command can transfer both on CMD and DAT lines.
CMD8 for eMMC can test both CMD line and DAT lines. CMD13 and CMD52 only test
CMD line. We might use ACMD51 for SD and CMD53 for SDIO later thus DAT lines
are also under test.
> Kind regards
> Uffe
>
[...]
>>>>
>>>
>>> Sorry that I didn't make myself clear.
>>>
>>> Our host PHY delay line consists of hundreds of sampling points.
>>> Each sampling point represents a different phase shift.
>>>
>>> In lower speed mode, our host driver will scan the delay line.
>>> It will select and test multiple sampling points, other than testing
>>> only single sampling point.
>>>
>>> If a sampling point fails to transfer cmd/data, our host driver will
>>> move to test next sampling point, until we find out a group of successful
>>> sampling points which can transfer cmd/data. At last we will select
>>> a perfect one from them.
>>
>> Ahh, I see. Unfortunate, this is going to be very hard to implement properly.
>>
>> The main problem is that the host driver has *no* knowledge about the
>> internal state of the card, as that is the responsibility of the mmc
>> core to keep track of.
>>
>> If the host driver would send a command during every update of the
>> "ios" setting, from ->set_ios(), for sure it would lead to commands
>> being sent that are "forbidden" in the current internal state of the
>> card.
>> This would lead to that the card initialization sequence fails,
>> because the card may move to an unknown internal state and the mmc
>> core would have no knowledge about what happened.
>>
>
> Yes. In theory, host layer should not initiate a command by itself.
>
> We assume that bus is idle and card is stable in Tran state, when core layer
> asks host to switch "ios".
Understand, but this is a wrong assumption. The card may very well in
another state than Tran state.
> Besides, we only select the commands which is valid in the whole procedure,
> such as CMD8 for eMMC.
> Those test commands are actually like read operations to card registers.
> The card will return to Tran state even if transfer fails. It is also easy
> for host to recover.
For example, I would recommend you to investigate in detail the
sequence for when a CMD6 command is sent to the card.
The host must *not* start sending commands from ->set_ios() during a
CMD6 sequence. For example a CMD8 is not allowed.
Moreover, due to this, I wonder if it is even possible to get this HW
to work properly.
>
>> Hmm..
>>
>> Can you specify, *exactly*, under which "ios updates" you need to
>> verify updated PHY setting changes by sending a cmd/data? Also, please
>> specify if it's enough to only test the CMD line or also DATA lines.
>>
>
> When one of the three parameters in below changes, our host driver needs
> to adjust PHY in lower speed mode.
> 1. Speed Mode (timing): like legacy mode --> HS DDR
> 2. Bus Clock: like 400KHz --> 50MHz
> 3. Bus Width: like 1-bit --> 4-bit/8-bit
>
> For eMMC, we use CMD8 to test sampling point.
> For SD, we use CMD13.
> For SDIO, currently CMD52 is used to read a register from CCCR.
> Those commands in above are all valid during the whole procedure to switch
> to high speed mode from legacy mode.
>
> It is the best case if the test command can transfer both on CMD and DAT lines.
> CMD8 for eMMC can test both CMD line and DAT lines. CMD13 and CMD52 only test
> CMD line. We might use ACMD51 for SD and CMD53 for SDIO later thus DAT lines
> are also under test.
Thanks for sharing these details!
So, if possible, I would recommend you to discuss these issues with
some of the HW designers. Perhaps you can figure out an alternative
method of confirming/testing PHY setting changes? Sending commands to
the card just doesn't work well for all cases.
Kind regards
Uffe
Hi Ulf,
On 2016/11/29 19:11, Ulf Hansson wrote:
> [...]
>
>>>>>
>>>>
>>>> Sorry that I didn't make myself clear.
>>>>
>>>> Our host PHY delay line consists of hundreds of sampling points.
>>>> Each sampling point represents a different phase shift.
>>>>
>>>> In lower speed mode, our host driver will scan the delay line.
>>>> It will select and test multiple sampling points, other than testing
>>>> only single sampling point.
>>>>
>>>> If a sampling point fails to transfer cmd/data, our host driver will
>>>> move to test next sampling point, until we find out a group of successful
>>>> sampling points which can transfer cmd/data. At last we will select
>>>> a perfect one from them.
>>>
>>> Ahh, I see. Unfortunate, this is going to be very hard to implement properly.
>>>
>>> The main problem is that the host driver has *no* knowledge about the
>>> internal state of the card, as that is the responsibility of the mmc
>>> core to keep track of.
>>>
>>> If the host driver would send a command during every update of the
>>> "ios" setting, from ->set_ios(), for sure it would lead to commands
>>> being sent that are "forbidden" in the current internal state of the
>>> card.
>>> This would lead to that the card initialization sequence fails,
>>> because the card may move to an unknown internal state and the mmc
>>> core would have no knowledge about what happened.
>>>
>>
>> Yes. In theory, host layer should not initiate a command by itself.
>>
>> We assume that bus is idle and card is stable in Tran state, when core layer
>> asks host to switch "ios".
>
> Understand, but this is a wrong assumption. The card may very well in
> another state than Tran state.
>
Could you please provide an example that card might not be in Tran state?
It seems that card should be in Tran state after CMD6 succeed.
If CMD6 fails, mmc driver will not execute ios setting. Thus ->set_ios()
will not be called.
>> Besides, we only select the commands which is valid in the whole procedure,
>> such as CMD8 for eMMC.
>> Those test commands are actually like read operations to card registers.
>> The card will return to Tran state even if transfer fails. It is also easy
>> for host to recover.
>
> For example, I would recommend you to investigate in detail the
> sequence for when a CMD6 command is sent to the card.
> The host must *not* start sending commands from ->set_ios() during a
> CMD6 sequence. For example a CMD8 is not allowed.
>
> Moreover, due to this, I wonder if it is even possible to get this HW
> to work properly.
>
In my very own opinion, ->set_ios() is only executed after CMD6 sequence
succeeds, based on current mmc.c/sd.c/sdio.c.
I personally think that it should not interfere CMD6 sequence.
I'm afraid that HW cannot help and SW driver has to take care of this.
>>
>>> Hmm..
>>>
>>> Can you specify, *exactly*, under which "ios updates" you need to
>>> verify updated PHY setting changes by sending a cmd/data? Also, please
>>> specify if it's enough to only test the CMD line or also DATA lines.
>>>
>>
>> When one of the three parameters in below changes, our host driver needs
>> to adjust PHY in lower speed mode.
>> 1. Speed Mode (timing): like legacy mode --> HS DDR
>> 2. Bus Clock: like 400KHz --> 50MHz
>> 3. Bus Width: like 1-bit --> 4-bit/8-bit
>>
>> For eMMC, we use CMD8 to test sampling point.
>> For SD, we use CMD13.
>> For SDIO, currently CMD52 is used to read a register from CCCR.
>> Those commands in above are all valid during the whole procedure to switch
>> to high speed mode from legacy mode.
>>
>> It is the best case if the test command can transfer both on CMD and DAT lines.
>> CMD8 for eMMC can test both CMD line and DAT lines. CMD13 and CMD52 only test
>> CMD line. We might use ACMD51 for SD and CMD53 for SDIO later thus DAT lines
>> are also under test.
>
> Thanks for sharing these details!
>
> So, if possible, I would recommend you to discuss these issues with
> some of the HW designers. Perhaps you can figure out an alternative
> method of confirming/testing PHY setting changes? Sending commands to
> the card just doesn't work well for all cases.
>
Thanks a lot for you patience.
Actually, we, including HW engineers, have been working on this for
a very long time. We also test a lot on many actual products. It is
quiet stable in real use scenarios.
I know it is still not good enough. It seems to be impossible to find
another practical and reliable solution, based on our tests.
Could you please provide some suggestions thus we can try our best to improve it
to meet your requirement?
Thank you.
Best regards,
Hu Ziji
> Kind regards
> Uffe
>